From de70fada5061965884492178a7d6166646424a4e Mon Sep 17 00:00:00 2001 From: Alfred Reynolds Date: Fri, 30 Aug 2013 13:34:05 -0700 Subject: [PATCH] initial seed of Half-Life 1 SDK --- cl_dll/Exports.h | 117 + cl_dll/GameStudioModelRenderer.cpp | 121 + cl_dll/GameStudioModelRenderer.h | 26 + cl_dll/GameStudioModelRenderer_Sample.cpp | 992 ++ cl_dll/GameStudioModelRenderer_Sample.h | 55 + cl_dll/MOTD.cpp | 155 + cl_dll/StudioModelRenderer.cpp | 2114 +++ cl_dll/StudioModelRenderer.h | 189 + cl_dll/ammo.cpp | 1200 ++ cl_dll/ammo.h | 62 + cl_dll/ammo_secondary.cpp | 159 + cl_dll/ammohistory.cpp | 190 + cl_dll/ammohistory.h | 143 + cl_dll/battery.cpp | 158 + cl_dll/bench.h | 26 + cl_dll/camera.h | 24 + cl_dll/cdll_int.cpp | 445 + cl_dll/cl_dll.dsp | 648 + cl_dll/cl_dll.h | 43 + cl_dll/cl_util.h | 196 + cl_dll/com_weapons.cpp | 277 + cl_dll/com_weapons.h | 44 + cl_dll/death.cpp | 304 + cl_dll/demo.cpp | 99 + cl_dll/demo.h | 27 + cl_dll/entity.cpp | 785 ++ cl_dll/ev_common.cpp | 205 + cl_dll/ev_hldm.cpp | 1704 +++ cl_dll/ev_hldm.h | 95 + cl_dll/events.cpp | 23 + cl_dll/eventscripts.h | 73 + cl_dll/flashlight.cpp | 149 + cl_dll/geiger.cpp | 184 + cl_dll/global_consts.h | 30 + cl_dll/health.cpp | 473 + cl_dll/health.h | 127 + cl_dll/hl/hl_baseentity.cpp | 348 + cl_dll/hl/hl_events.cpp | 80 + cl_dll/hl/hl_objects.cpp | 90 + cl_dll/hl/hl_weapons.cpp | 1081 ++ cl_dll/hud.cpp | 698 + cl_dll/hud.h | 658 + cl_dll/hud_bench.cpp | 1129 ++ cl_dll/hud_benchtrace.cpp | 234 + cl_dll/hud_benchtrace.h | 8 + cl_dll/hud_iface.h | 17 + cl_dll/hud_msg.cpp | 141 + cl_dll/hud_redraw.cpp | 346 + cl_dll/hud_servers.cpp | 1234 ++ cl_dll/hud_servers.h | 41 + cl_dll/hud_servers_priv.h | 98 + cl_dll/hud_spectator.cpp | 1981 +++ cl_dll/hud_spectator.h | 156 + cl_dll/hud_update.cpp | 54 + cl_dll/in_camera.cpp | 631 + cl_dll/in_defs.h | 20 + cl_dll/input.cpp | 1041 ++ cl_dll/inputw32.cpp | 1114 ++ cl_dll/interpolation.cpp | 222 + cl_dll/interpolation.h | 56 + cl_dll/kbutton.h | 18 + cl_dll/menu.cpp | 283 + cl_dll/message.cpp | 536 + cl_dll/overview.cpp | 160 + cl_dll/overview.h | 31 + cl_dll/player_info.h | 20 + cl_dll/readme.txt | 107 + cl_dll/saytext.cpp | 326 + cl_dll/scoreboard.cpp | 586 + cl_dll/soundsystem.cpp | 162 + cl_dll/status_icons.cpp | 163 + cl_dll/statusbar.cpp | 265 + cl_dll/studio_util.cpp | 251 + cl_dll/studio_util.h | 40 + cl_dll/text_message.cpp | 214 + cl_dll/tf_defs.h | 1389 ++ cl_dll/train.cpp | 85 + cl_dll/tri.cpp | 60 + cl_dll/tri.h | 13 + cl_dll/util.cpp | 133 + cl_dll/util_vector.h | 125 + cl_dll/vgui_ClassMenu.cpp | 437 + cl_dll/vgui_ConsolePanel.cpp | 101 + cl_dll/vgui_ConsolePanel.h | 38 + cl_dll/vgui_ControlConfigPanel.cpp | 212 + cl_dll/vgui_ControlConfigPanel.h | 47 + cl_dll/vgui_CustomObjects.cpp | 547 + cl_dll/vgui_MOTDWindow.cpp | 154 + cl_dll/vgui_SchemeManager.cpp | 556 + cl_dll/vgui_SchemeManager.h | 54 + cl_dll/vgui_ScorePanel.cpp | 1151 ++ cl_dll/vgui_ScorePanel.h | 310 + cl_dll/vgui_ServerBrowser.cpp | 623 + cl_dll/vgui_ServerBrowser.h | 50 + cl_dll/vgui_SpectatorPanel.cpp | 426 + cl_dll/vgui_SpectatorPanel.h | 112 + cl_dll/vgui_TeamFortressViewport.cpp | 2644 ++++ cl_dll/vgui_TeamFortressViewport.h | 1818 +++ cl_dll/vgui_int.cpp | 128 + cl_dll/vgui_int.h | 21 + cl_dll/vgui_teammenu.cpp | 393 + cl_dll/view.cpp | 1802 +++ cl_dll/view.h | 15 + cl_dll/voice_status.cpp | 885 ++ cl_dll/voice_status.h | 228 + cl_dll/wrect.h | 16 + common/Sequence.h | 201 + common/beamdef.h | 62 + common/cl_entity.h | 115 + common/com_model.h | 351 + common/con_nprint.h | 38 + common/const.h | 783 ++ common/crc.h | 65 + common/cvardef.h | 37 + common/demo_api.h | 31 + common/director_cmds.h | 38 + common/dlight.h | 33 + common/dll_state.h | 23 + common/entity_state.h | 193 + common/entity_types.h | 26 + common/enums.h | 27 + common/event_api.h | 51 + common/event_args.h | 50 + common/event_flags.h | 47 + common/hltv.h | 54 + common/in_buttons.h | 38 + common/interface.cpp | 150 + common/interface.h | 129 + common/ivoicetweak.h | 38 + common/mathlib.h | 158 + common/net_api.h | 99 + common/netadr.h | 40 + common/nowin.h | 16 + common/parsemsg.cpp | 259 + common/parsemsg.h | 66 + common/particledef.h | 57 + common/pmtrace.h | 43 + common/port.h | 122 + common/qfont.h | 41 + common/r_efx.h | 197 + common/r_studioint.h | 144 + common/ref_params.h | 75 + common/screenfade.h | 24 + common/studio_event.h | 29 + common/triangleapi.h | 64 + common/usercmd.h | 41 + common/weaponinfo.h | 52 + dlls/AI_BaseNPC_Schedule.cpp | 1514 +++ dlls/Makefile | 186 + dlls/Wxdebug.cpp | 395 + dlls/activity.h | 109 + dlls/activitymap.h | 97 + dlls/aflock.cpp | 912 ++ dlls/agrunt.cpp | 1186 ++ dlls/airtank.cpp | 118 + dlls/animating.cpp | 318 + dlls/animation.cpp | 528 + dlls/animation.h | 47 + dlls/apache.cpp | 1050 ++ dlls/barnacle.cpp | 428 + dlls/barney.cpp | 841 ++ dlls/basemonster.h | 339 + dlls/bigmomma.cpp | 1251 ++ dlls/bloater.cpp | 219 + dlls/bmodels.cpp | 958 ++ dlls/bullsquid.cpp | 1275 ++ dlls/buttons.cpp | 1284 ++ dlls/cbase.cpp | 771 ++ dlls/cbase.h | 802 ++ dlls/cdll_dll.h | 46 + dlls/client.cpp | 1925 +++ dlls/client.h | 65 + dlls/combat.cpp | 1706 +++ dlls/controller.cpp | 1427 ++ dlls/crossbow.cpp | 548 + dlls/crowbar.cpp | 318 + dlls/decals.h | 75 + dlls/defaultai.cpp | 1232 ++ dlls/defaultai.h | 98 + dlls/doors.cpp | 1052 ++ dlls/doors.h | 33 + dlls/effects.cpp | 2268 ++++ dlls/effects.h | 209 + dlls/egon.cpp | 568 + dlls/enginecallback.h | 158 + dlls/explode.cpp | 273 + dlls/explode.h | 32 + dlls/extdll.h | 91 + dlls/flyingmonster.cpp | 281 + dlls/flyingmonster.h | 53 + dlls/func_break.cpp | 1004 ++ dlls/func_break.h | 74 + dlls/func_tank.cpp | 1039 ++ dlls/game.cpp | 890 ++ dlls/game.h | 45 + dlls/gamerules.cpp | 347 + dlls/gamerules.h | 360 + dlls/gargantua.cpp | 1368 ++ dlls/gauss.cpp | 623 + dlls/genericmonster.cpp | 140 + dlls/ggrenade.cpp | 488 + dlls/globals.cpp | 39 + dlls/glock.cpp | 325 + dlls/gman.cpp | 237 + dlls/h_ai.cpp | 199 + dlls/h_battery.cpp | 200 + dlls/h_cine.cpp | 241 + dlls/h_cycler.cpp | 471 + dlls/h_export.cpp | 63 + dlls/handgrenade.cpp | 233 + dlls/hassassin.cpp | 1015 ++ dlls/headcrab.cpp | 555 + dlls/healthkit.cpp | 264 + dlls/hgrunt.cpp | 2517 ++++ dlls/hl.def | 5 + dlls/hl.dsp | 1637 +++ dlls/hlgl.def | 15 + dlls/hornet.cpp | 419 + dlls/hornet.h | 58 + dlls/hornetgun.cpp | 305 + dlls/houndeye.cpp | 1304 ++ dlls/ichthyosaur.cpp | 1108 ++ dlls/islave.cpp | 866 ++ dlls/items.cpp | 342 + dlls/items.h | 29 + dlls/leech.cpp | 723 + dlls/lights.cpp | 199 + dlls/maprules.cpp | 918 ++ dlls/maprules.h | 22 + dlls/monsterevent.h | 34 + dlls/monstermaker.cpp | 292 + dlls/monsters.cpp | 3448 +++++ dlls/monsters.h | 183 + dlls/monsterstate.cpp | 234 + dlls/mortar.cpp | 323 + dlls/mp5.cpp | 385 + dlls/mpstubb.cpp | 264 + dlls/multiplay_gamerules.cpp | 1692 +++ dlls/nihilanth.cpp | 1852 +++ dlls/nodes.cpp | 3657 +++++ dlls/nodes.h | 374 + dlls/observer.cpp | 280 + dlls/osprey.cpp | 805 ++ dlls/pathcorner.cpp | 428 + dlls/plane.cpp | 60 + dlls/plane.h | 43 + dlls/plats.cpp | 2285 ++++ dlls/player.cpp | 4885 +++++++ dlls/player.h | 337 + dlls/playermonster.cpp | 122 + dlls/python.cpp | 309 + dlls/rat.cpp | 98 + dlls/roach.cpp | 460 + dlls/rpg.cpp | 617 + dlls/satchel.cpp | 494 + dlls/saverestore.h | 169 + dlls/schedule.cpp | 1514 +++ dlls/schedule.h | 290 + dlls/scientist.cpp | 1428 ++ dlls/scripted.cpp | 1260 ++ dlls/scripted.h | 107 + dlls/scriptevent.h | 29 + dlls/shotgun.cpp | 401 + dlls/singleplay_gamerules.cpp | 328 + dlls/skill.cpp | 47 + dlls/skill.h | 147 + dlls/sound.cpp | 1982 +++ dlls/soundent.cpp | 379 + dlls/soundent.h | 95 + dlls/spectator.cpp | 149 + dlls/spectator.h | 27 + dlls/squad.h | 20 + dlls/squadmonster.cpp | 623 + dlls/squadmonster.h | 120 + dlls/squeakgrenade.cpp | 601 + dlls/stats.cpp | 156 + dlls/subs.cpp | 567 + dlls/talkmonster.cpp | 1472 ++ dlls/talkmonster.h | 183 + dlls/teamplay_gamerules.cpp | 630 + dlls/teamplay_gamerules.h | 57 + dlls/tempmonster.cpp | 117 + dlls/tentacle.cpp | 1044 ++ dlls/trains.h | 127 + dlls/triggers.cpp | 2430 ++++ dlls/tripmine.cpp | 526 + dlls/turret.cpp | 1305 ++ dlls/util.cpp | 2549 ++++ dlls/util.h | 546 + dlls/vector.h | 112 + dlls/weapons.cpp | 1617 +++ dlls/weapons.h | 1019 ++ dlls/world.cpp | 742 + dlls/wpn_shared/hl_wpn_glock.cpp | 274 + dlls/wxdebug.h | 137 + dlls/xen.cpp | 584 + dlls/zombie.cpp | 346 + dmc/cl_dll/CTF_FlagStatus.cpp | 246 + dmc/cl_dll/CTF_HudMessage.cpp | 184 + dmc/cl_dll/DMC_BSPFile.h | 48 + dmc/cl_dll/DMC_Teleporters.cpp | 520 + dmc/cl_dll/DMC_Teleporters.h | 16 + dmc/cl_dll/Exports.h | 117 + dmc/cl_dll/GameStudioModelRenderer.cpp | 119 + dmc/cl_dll/GameStudioModelRenderer.h | 26 + dmc/cl_dll/MOTD.cpp | 155 + dmc/cl_dll/StudioModelRenderer.cpp | 1723 +++ dmc/cl_dll/StudioModelRenderer.h | 189 + dmc/cl_dll/ammo.cpp | 1131 ++ dmc/cl_dll/ammo.h | 62 + dmc/cl_dll/ammo_secondary.cpp | 159 + dmc/cl_dll/ammohistory.cpp | 190 + dmc/cl_dll/ammohistory.h | 144 + dmc/cl_dll/battery.cpp | 135 + dmc/cl_dll/camera.h | 24 + dmc/cl_dll/cdll_int.cpp | 342 + dmc/cl_dll/cl_dll.dsp | 598 + dmc/cl_dll/cl_dll.h | 43 + dmc/cl_dll/cl_util.h | 199 + dmc/cl_dll/com_model.h | 359 + dmc/cl_dll/com_weapons.cpp | 278 + dmc/cl_dll/com_weapons.h | 48 + dmc/cl_dll/death.cpp | 293 + dmc/cl_dll/demo.cpp | 61 + dmc/cl_dll/demo.h | 20 + dmc/cl_dll/entity.cpp | 860 ++ dmc/cl_dll/ev_common.cpp | 198 + dmc/cl_dll/ev_hldm.cpp | 1544 +++ dmc/cl_dll/ev_hldm.h | 96 + dmc/cl_dll/events.cpp | 30 + dmc/cl_dll/eventscripts.h | 73 + dmc/cl_dll/flashlight.cpp | 146 + dmc/cl_dll/geiger.cpp | 184 + dmc/cl_dll/health.cpp | 476 + dmc/cl_dll/health.h | 130 + dmc/cl_dll/hud.cpp | 594 + dmc/cl_dll/hud.h | 659 + dmc/cl_dll/hud_iface.h | 24 + dmc/cl_dll/hud_msg.cpp | 171 + dmc/cl_dll/hud_redraw.cpp | 447 + dmc/cl_dll/hud_servers.cpp | 1254 ++ dmc/cl_dll/hud_servers.h | 41 + dmc/cl_dll/hud_servers_priv.h | 98 + dmc/cl_dll/hud_spectator.cpp | 1576 +++ dmc/cl_dll/hud_spectator.h | 129 + dmc/cl_dll/hud_update.cpp | 56 + dmc/cl_dll/in_camera.cpp | 628 + dmc/cl_dll/in_defs.h | 21 + dmc/cl_dll/input.cpp | 1039 ++ dmc/cl_dll/inputw32.cpp | 942 ++ dmc/cl_dll/kbutton.h | 18 + dmc/cl_dll/menu.cpp | 282 + dmc/cl_dll/message.cpp | 482 + dmc/cl_dll/quake/quake_baseentity.cpp | 330 + dmc/cl_dll/quake/quake_events.cpp | 83 + dmc/cl_dll/quake/quake_objects.cpp | 81 + dmc/cl_dll/quake/quake_weapons.cpp | 988 ++ dmc/cl_dll/readme.txt | 107 + dmc/cl_dll/saytext.cpp | 312 + dmc/cl_dll/scoreboard.cpp | 527 + dmc/cl_dll/status_icons.cpp | 150 + dmc/cl_dll/statusbar.cpp | 262 + dmc/cl_dll/studio_util.cpp | 251 + dmc/cl_dll/studio_util.h | 40 + dmc/cl_dll/text_message.cpp | 208 + dmc/cl_dll/train.cpp | 85 + dmc/cl_dll/tri.cpp | 129 + dmc/cl_dll/util.cpp | 134 + dmc/cl_dll/util.h | 152 + dmc/cl_dll/util_vector.h | 121 + dmc/cl_dll/vgui_ControlConfigPanel.h | 47 + dmc/cl_dll/vgui_CustomObjects.cpp | 428 + dmc/cl_dll/vgui_MOTDWindow.cpp | 153 + dmc/cl_dll/vgui_SchemeManager.cpp | 556 + dmc/cl_dll/vgui_SchemeManager.h | 52 + dmc/cl_dll/vgui_ScorePanel.cpp | 1031 ++ dmc/cl_dll/vgui_ScorePanel.h | 314 + dmc/cl_dll/vgui_ServerBrowser.cpp | 623 + dmc/cl_dll/vgui_ServerBrowser.h | 50 + dmc/cl_dll/vgui_SpectatorPanel.cpp | 204 + dmc/cl_dll/vgui_SpectatorPanel.h | 93 + dmc/cl_dll/vgui_int.cpp | 128 + dmc/cl_dll/vgui_int.h | 21 + dmc/cl_dll/vgui_viewport.cpp | 1983 +++ dmc/cl_dll/vgui_viewport.h | 1331 ++ dmc/cl_dll/view.cpp | 1677 +++ dmc/cl_dll/view.h | 15 + dmc/cl_dll/voice_status.cpp | 885 ++ dmc/cl_dll/voice_status.h | 228 + dmc/cl_dll/wrect.h | 16 + dmc/dlls/Makefile | 126 + dmc/dlls/activity.h | 109 + dmc/dlls/activitymap.h | 97 + dmc/dlls/animating.cpp | 313 + dmc/dlls/animation.cpp | 527 + dmc/dlls/animation.h | 47 + dmc/dlls/basemonster.h | 339 + dmc/dlls/bmodels.cpp | 958 ++ dmc/dlls/buttons.cpp | 1279 ++ dmc/dlls/cbase.cpp | 767 ++ dmc/dlls/cbase.h | 805 ++ dmc/dlls/cdll_dll.h | 46 + dmc/dlls/client.cpp | 1817 +++ dmc/dlls/client.h | 65 + dmc/dlls/combat.cpp | 1099 ++ dmc/dlls/decals.h | 75 + dmc/dlls/defaultai.cpp | 240 + dmc/dlls/defaultai.h | 98 + dmc/dlls/dmc.def | 5 + dmc/dlls/dmcgl.def | 15 + dmc/dlls/doors.cpp | 1099 ++ dmc/dlls/doors.h | 33 + dmc/dlls/effects.cpp | 2268 ++++ dmc/dlls/effects.h | 209 + dmc/dlls/enginecallback.h | 159 + dmc/dlls/explode.cpp | 273 + dmc/dlls/explode.h | 32 + dmc/dlls/extdll.h | 99 + dmc/dlls/func_break.cpp | 998 ++ dmc/dlls/func_break.h | 74 + dmc/dlls/func_tank.cpp | 1039 ++ dmc/dlls/game.cpp | 892 ++ dmc/dlls/game.h | 46 + dmc/dlls/gamerules.cpp | 179 + dmc/dlls/gamerules.h | 370 + dmc/dlls/globals.cpp | 39 + dmc/dlls/h_ai.cpp | 198 + dmc/dlls/h_export.cpp | 63 + dmc/dlls/hl.def | 5 + dmc/dlls/hl.dsp | 509 + dmc/dlls/hlgl.def | 15 + dmc/dlls/items.cpp | 337 + dmc/dlls/items.h | 29 + dmc/dlls/lights.cpp | 199 + dmc/dlls/maprules.cpp | 918 ++ dmc/dlls/maprules.h | 22 + dmc/dlls/monsterevent.h | 34 + dmc/dlls/monsters.cpp | 134 + dmc/dlls/monsters.h | 183 + dmc/dlls/monsterstate.cpp | 28 + dmc/dlls/multiplay_gamerules.cpp | 1672 +++ dmc/dlls/nodes.cpp | 3654 +++++ dmc/dlls/nodes.h | 374 + dmc/dlls/observer.cpp | 142 + dmc/dlls/pathcorner.cpp | 428 + dmc/dlls/plane.cpp | 60 + dmc/dlls/plane.h | 43 + dmc/dlls/plats.cpp | 2285 ++++ dmc/dlls/player.cpp | 4887 +++++++ dmc/dlls/player.h | 497 + dmc/dlls/quake_gun.cpp | 174 + dmc/dlls/quake_gun.h | 44 + dmc/dlls/quake_items.cpp | 1866 +++ dmc/dlls/quake_nail.cpp | 122 + dmc/dlls/quake_player.cpp | 250 + dmc/dlls/quake_rocket.cpp | 191 + dmc/dlls/quake_weapons_all.cpp | 1087 ++ dmc/dlls/saverestore.h | 170 + dmc/dlls/schedule.cpp | 39 + dmc/dlls/schedule.h | 290 + dmc/dlls/scripted.cpp | 1260 ++ dmc/dlls/scripted.h | 107 + dmc/dlls/scriptevent.h | 29 + dmc/dlls/singleplay_gamerules.cpp | 336 + dmc/dlls/skill.cpp | 47 + dmc/dlls/skill.h | 147 + dmc/dlls/sound.cpp | 1970 +++ dmc/dlls/soundent.cpp | 379 + dmc/dlls/soundent.h | 95 + dmc/dlls/spectator.cpp | 149 + dmc/dlls/spectator.h | 27 + dmc/dlls/subs.cpp | 559 + dmc/dlls/teamplay_gamerules.cpp | 618 + dmc/dlls/teamplay_gamerules.h | 62 + dmc/dlls/threewave_gamerules.cpp | 3275 +++++ dmc/dlls/threewave_gamerules.h | 221 + dmc/dlls/trains.h | 127 + dmc/dlls/triggers.cpp | 2716 ++++ dmc/dlls/util.cpp | 2713 ++++ dmc/dlls/util.h | 601 + dmc/dlls/vector.h | 112 + dmc/dlls/weapons.cpp | 1417 ++ dmc/dlls/weapons.h | 492 + dmc/dlls/world.cpp | 745 ++ dmc/pm_shared/pm_debug.c | 296 + dmc/pm_shared/pm_debug.h | 17 + dmc/pm_shared/pm_defs.h | 213 + dmc/pm_shared/pm_info.h | 15 + dmc/pm_shared/pm_materials.h | 26 + dmc/pm_shared/pm_math.c | 416 + dmc/pm_shared/pm_movevars.h | 47 + dmc/pm_shared/pm_shared.c | 2798 ++++ dmc/pm_shared/pm_shared.h | 36 + engine/APIProxy.h | 939 ++ engine/anorms.h | 177 + engine/cdll_int.h | 467 + engine/custom.h | 103 + engine/customentity.h | 38 + engine/edict.h | 36 + engine/eiface.h | 532 + engine/progdefs.h | 224 + engine/progs.h | 82 + engine/shake.h | 56 + engine/studio.h | 368 + external/SDL2/SDL.h | 163 + external/SDL2/SDL_assert.h | 241 + external/SDL2/SDL_atomic.h | 316 + external/SDL2/SDL_audio.h | 509 + external/SDL2/SDL_bits.h | 94 + external/SDL2/SDL_blendmode.h | 60 + external/SDL2/SDL_clipboard.h | 75 + external/SDL2/SDL_config.h | 51 + external/SDL2/SDL_config_android.h | 136 + external/SDL2/SDL_config_iphoneos.h | 151 + external/SDL2/SDL_config_macosx.h | 183 + external/SDL2/SDL_config_minimal.h | 302 + external/SDL2/SDL_config_nintendods.h | 129 + external/SDL2/SDL_config_pandora.h | 124 + external/SDL2/SDL_config_windows.h | 192 + external/SDL2/SDL_config_wiz.h | 119 + external/SDL2/SDL_copying.h | 20 + external/SDL2/SDL_cpuinfo.h | 150 + external/SDL2/SDL_endian.h | 243 + external/SDL2/SDL_error.h | 80 + external/SDL2/SDL_events.h | 690 + external/SDL2/SDL_gamecontroller.h | 298 + external/SDL2/SDL_gesture.h | 91 + external/SDL2/SDL_haptic.h | 1200 ++ external/SDL2/SDL_hints.h | 290 + external/SDL2/SDL_input.h | 87 + external/SDL2/SDL_joystick.h | 253 + external/SDL2/SDL_keyboard.h | 219 + external/SDL2/SDL_keycode.h | 341 + external/SDL2/SDL_loadso.h | 85 + external/SDL2/SDL_log.h | 215 + external/SDL2/SDL_main.h | 98 + external/SDL2/SDL_messagebox.h | 147 + external/SDL2/SDL_mouse.h | 223 + external/SDL2/SDL_mutex.h | 255 + external/SDL2/SDL_name.h | 11 + external/SDL2/SDL_opengl.h | 11132 ++++++++++++++++ external/SDL2/SDL_opengles.h | 38 + external/SDL2/SDL_opengles2.h | 38 + external/SDL2/SDL_pixels.h | 431 + external/SDL2/SDL_platform.h | 149 + external/SDL2/SDL_power.h | 79 + external/SDL2/SDL_quit.h | 58 + external/SDL2/SDL_rect.h | 137 + external/SDL2/SDL_render.h | 788 ++ external/SDL2/SDL_revision.h | 2 + external/SDL2/SDL_rwops.h | 235 + external/SDL2/SDL_scancode.h | 401 + external/SDL2/SDL_shape.h | 147 + external/SDL2/SDL_stdinc.h | 795 ++ external/SDL2/SDL_surface.h | 502 + external/SDL2/SDL_system.h | 106 + external/SDL2/SDL_syswm.h | 241 + external/SDL2/SDL_test.h | 72 + external/SDL2/SDL_test_assert.h | 109 + external/SDL2/SDL_test_common.h | 186 + external/SDL2/SDL_test_compare.h | 73 + external/SDL2/SDL_test_crc32.h | 128 + external/SDL2/SDL_test_font.h | 66 + external/SDL2/SDL_test_fuzzer.h | 388 + external/SDL2/SDL_test_harness.h | 126 + external/SDL2/SDL_test_images.h | 82 + external/SDL2/SDL_test_log.h | 71 + external/SDL2/SDL_test_md5.h | 133 + external/SDL2/SDL_test_random.h | 119 + external/SDL2/SDL_thread.h | 182 + external/SDL2/SDL_timer.h | 108 + external/SDL2/SDL_touch.h | 90 + external/SDL2/SDL_types.h | 29 + external/SDL2/SDL_version.h | 166 + external/SDL2/SDL_video.h | 934 ++ external/SDL2/begin_code.h | 150 + external/SDL2/close_code.h | 37 + external/SDL2/merge_sdl2.sh | 11 + filecopy.bat | 4 + game_shared/GameEvent.h | 282 + game_shared/bitvec.h | 181 + game_shared/bot/bot.cpp | 626 + game_shared/bot/bot.h | 386 + game_shared/bot/bot_constants.h | 46 + game_shared/bot/bot_manager.cpp | 474 + game_shared/bot/bot_manager.h | 107 + game_shared/bot/bot_profile.cpp | 703 + game_shared/bot/bot_profile.h | 220 + game_shared/bot/bot_util.cpp | 859 ++ game_shared/bot/bot_util.h | 326 + game_shared/bot/improv.h | 118 + game_shared/bot/nav.h | 363 + game_shared/bot/nav_area.cpp | 5219 ++++++++ game_shared/bot/nav_area.h | 1231 ++ game_shared/bot/nav_file.cpp | 1080 ++ game_shared/bot/nav_node.cpp | 111 + game_shared/bot/nav_node.h | 113 + game_shared/bot/nav_path.cpp | 1192 ++ game_shared/bot/nav_path.h | 230 + game_shared/bot/simple_state_machine.h | 85 + game_shared/perf_counter.h | 176 + game_shared/shared_util.cpp | 261 + game_shared/shared_util.h | 120 + game_shared/simple_checksum.h | 25 + game_shared/steam_util.h | 58 + game_shared/vgui_checkbutton2.cpp | 197 + game_shared/vgui_checkbutton2.h | 101 + game_shared/vgui_defaultinputsignal.h | 39 + game_shared/vgui_grid.cpp | 398 + game_shared/vgui_grid.h | 122 + game_shared/vgui_helpers.cpp | 45 + game_shared/vgui_helpers.h | 31 + game_shared/vgui_listbox.cpp | 207 + game_shared/vgui_listbox.h | 115 + game_shared/vgui_loadtga.cpp | 93 + game_shared/vgui_loadtga.h | 22 + game_shared/vgui_scrollbar2.cpp | 310 + game_shared/vgui_scrollbar2.h | 62 + game_shared/vgui_slider2.cpp | 436 + game_shared/vgui_slider2.h | 67 + game_shared/voice_banmgr.cpp | 197 + game_shared/voice_banmgr.h | 57 + game_shared/voice_common.h | 24 + game_shared/voice_gamemgr.cpp | 274 + game_shared/voice_gamemgr.h | 82 + game_shared/voice_status.cpp | 467 + game_shared/voice_status.h | 205 + game_shared/voice_status_hud.cpp | 412 + game_shared/voice_status_hud.h | 158 + game_shared/voice_vgui_tweakdlg.cpp | 289 + game_shared/voice_vgui_tweakdlg.h | 25 + lib/public/SDL2.lib | Bin 0 -> 108368 bytes lib/public/game_controls.lib | Bin 0 -> 507268 bytes linux/Makefile | 170 + linux/Makefile.dmc_cdll | 164 + linux/Makefile.dmcdll | 114 + linux/Makefile.hl_cdll | 195 + linux/Makefile.hldll | 175 + linux/Makefile.ricochet_cdll | 163 + linux/Makefile.ricochetdll | 128 + linux/gendbg.sh | 53 + linux/libSDL2-2.0.0.dylib | Bin 0 -> 1408108 bytes .../Contents/Info.plist | 20 + .../Resources/DWARF/libsdl2-2.0.0.dylib | Bin 0 -> 1149526 bytes linux/libSDL2-2.0.so.0 | Bin 0 -> 923556 bytes linux/libSDL2-2.0.so.0.dbg | Bin 0 -> 3566049 bytes linux/libSDL2.dylib | 1 + linux/libSDL2.so | 1 + linux/release/vgui.dylib | Bin 0 -> 431988 bytes linux/release/vgui.so | Bin 0 -> 975981 bytes network/Delta.txt | 2 + network/delta.lst | 265 + pm_shared/pm_debug.c | 320 + pm_shared/pm_debug.h | 24 + pm_shared/pm_defs.h | 223 + pm_shared/pm_info.h | 24 + pm_shared/pm_materials.h | 34 + pm_shared/pm_math.c | 433 + pm_shared/pm_movevars.h | 47 + pm_shared/pm_shared.c | 3367 +++++ pm_shared/pm_shared.h | 36 + public/FileSystem.h | 188 + public/archtypes.h | 21 + public/cl_dll/IGameClientExports.h | 34 + public/interface.cpp | 270 + public/interface.h | 149 + public/keydefs.h | 124 + public/particleman.h | 101 + public/pman_particlemem.h | 197 + public/pman_triangleffect.h | 209 + public/steam/steamtypes.h | 177 + ricochet/cl_dll/Exports.h | 224 + ricochet/cl_dll/GameStudioModelRenderer.cpp | 981 ++ ricochet/cl_dll/GameStudioModelRenderer.h | 48 + ricochet/cl_dll/Ricochet_BSPFile.h | 41 + ricochet/cl_dll/Ricochet_JumpPads.cpp | 580 + ricochet/cl_dll/Ricochet_JumpPads.h | 9 + ricochet/cl_dll/StudioModelRenderer.cpp | 1607 +++ ricochet/cl_dll/StudioModelRenderer.h | 182 + ricochet/cl_dll/ammo.cpp | 930 ++ ricochet/cl_dll/ammo.h | 62 + ricochet/cl_dll/ammo_secondary.cpp | 159 + ricochet/cl_dll/ammohistory.cpp | 190 + ricochet/cl_dll/ammohistory.h | 143 + ricochet/cl_dll/battery.cpp | 79 + ricochet/cl_dll/camera.h | 31 + ricochet/cl_dll/cdll_int.cpp | 278 + ricochet/cl_dll/cl_dll.dsp | 555 + ricochet/cl_dll/cl_dll.h | 42 + ricochet/cl_dll/cl_util.h | 189 + ricochet/cl_dll/com_weapons.cpp | 278 + ricochet/cl_dll/com_weapons.h | 41 + ricochet/cl_dll/death.cpp | 307 + ricochet/cl_dll/demo.cpp | 68 + ricochet/cl_dll/demo.h | 27 + ricochet/cl_dll/entity.cpp | 641 + ricochet/cl_dll/ev_common.cpp | 198 + ricochet/cl_dll/ev_hldm.cpp | 150 + ricochet/cl_dll/ev_hldm.h | 16 + ricochet/cl_dll/events.cpp | 30 + ricochet/cl_dll/eventscripts.h | 80 + ricochet/cl_dll/flashlight.cpp | 100 + ricochet/cl_dll/geiger.cpp | 184 + ricochet/cl_dll/health.cpp | 425 + ricochet/cl_dll/health.h | 128 + ricochet/cl_dll/hl/hl_baseentity.cpp | 248 + ricochet/cl_dll/hl/hl_events.cpp | 45 + ricochet/cl_dll/hl/hl_objects.cpp | 28 + ricochet/cl_dll/hl/hl_weapons.cpp | 1423 ++ ricochet/cl_dll/hud.cpp | 590 + ricochet/cl_dll/hud.h | 586 + ricochet/cl_dll/hud_iface.h | 31 + ricochet/cl_dll/hud_msg.cpp | 117 + ricochet/cl_dll/hud_redraw.cpp | 264 + ricochet/cl_dll/hud_servers.cpp | 1247 ++ ricochet/cl_dll/hud_servers.h | 34 + ricochet/cl_dll/hud_servers_priv.h | 91 + ricochet/cl_dll/hud_update.cpp | 54 + ricochet/cl_dll/in_camera.cpp | 630 + ricochet/cl_dll/in_defs.h | 27 + ricochet/cl_dll/input.cpp | 988 ++ ricochet/cl_dll/inputw32.cpp | 938 ++ ricochet/cl_dll/kbutton.h | 25 + ricochet/cl_dll/menu.cpp | 188 + ricochet/cl_dll/message.cpp | 463 + ricochet/cl_dll/readme.txt | 107 + ricochet/cl_dll/saytext.cpp | 308 + ricochet/cl_dll/status_icons.cpp | 150 + ricochet/cl_dll/statusbar.cpp | 252 + ricochet/cl_dll/studio_util.cpp | 244 + ricochet/cl_dll/studio_util.h | 33 + ricochet/cl_dll/text_message.cpp | 208 + ricochet/cl_dll/tf_defs.h | 1357 ++ ricochet/cl_dll/train.cpp | 85 + ricochet/cl_dll/tri.cpp | 127 + ricochet/cl_dll/util.cpp | 101 + ricochet/cl_dll/util_vector.h | 121 + ricochet/cl_dll/vgui_ConsolePanel.cpp | 95 + ricochet/cl_dll/vgui_ConsolePanel.h | 32 + ricochet/cl_dll/vgui_ControlConfigPanel.cpp | 206 + ricochet/cl_dll/vgui_ControlConfigPanel.h | 41 + ricochet/cl_dll/vgui_CustomObjects.cpp | 501 + ricochet/cl_dll/vgui_MOTDWindow.cpp | 154 + ricochet/cl_dll/vgui_SchemeManager.cpp | 556 + ricochet/cl_dll/vgui_SchemeManager.h | 47 + ricochet/cl_dll/vgui_ScorePanel.cpp | 1145 ++ ricochet/cl_dll/vgui_ScorePanel.h | 307 + ricochet/cl_dll/vgui_ServerBrowser.cpp | 616 + ricochet/cl_dll/vgui_ServerBrowser.h | 44 + ricochet/cl_dll/vgui_TeamFortressViewport.cpp | 2220 +++ ricochet/cl_dll/vgui_TeamFortressViewport.h | 1207 ++ ricochet/cl_dll/vgui_discobjects.cpp | 593 + ricochet/cl_dll/vgui_discobjects.h | 109 + ricochet/cl_dll/vgui_int.cpp | 122 + ricochet/cl_dll/vgui_int.h | 15 + ricochet/cl_dll/view.cpp | 1061 ++ ricochet/cl_dll/view.h | 22 + ricochet/cl_dll/voice_status.cpp | 885 ++ ricochet/cl_dll/voice_status.h | 228 + ricochet/cl_dll/wrect.h | 23 + ricochet/dlls/Makefile | 136 + ricochet/dlls/activity.h | 79 + ricochet/dlls/activitymap.h | 37 + ricochet/dlls/airtank.cpp | 118 + ricochet/dlls/animating.cpp | 313 + ricochet/dlls/animation.cpp | 526 + ricochet/dlls/animation.h | 47 + ricochet/dlls/basemonster.h | 94 + ricochet/dlls/bmodels.cpp | 958 ++ ricochet/dlls/buttons.cpp | 1279 ++ ricochet/dlls/cbase.cpp | 796 ++ ricochet/dlls/cbase.h | 803 ++ ricochet/dlls/cdll_dll.h | 46 + ricochet/dlls/client.cpp | 1822 +++ ricochet/dlls/client.h | 67 + ricochet/dlls/combat.cpp | 1453 ++ ricochet/dlls/decals.h | 75 + ricochet/dlls/disc_arena.cpp | 1048 ++ ricochet/dlls/disc_arena.h | 111 + ricochet/dlls/disc_objects.h | 173 + ricochet/dlls/disc_powerups.cpp | 234 + ricochet/dlls/disc_weapon.h | 33 + ricochet/dlls/discwar.h | 60 + ricochet/dlls/doors.cpp | 1026 ++ ricochet/dlls/doors.h | 33 + ricochet/dlls/effects.cpp | 2268 ++++ ricochet/dlls/effects.h | 209 + ricochet/dlls/enginecallback.h | 159 + ricochet/dlls/explode.cpp | 273 + ricochet/dlls/explode.h | 32 + ricochet/dlls/extdll.h | 99 + ricochet/dlls/func_break.cpp | 998 ++ ricochet/dlls/func_break.h | 74 + ricochet/dlls/func_tank.cpp | 1035 ++ ricochet/dlls/game.cpp | 899 ++ ricochet/dlls/game.h | 44 + ricochet/dlls/gamerules.cpp | 328 + ricochet/dlls/gamerules.h | 360 + ricochet/dlls/ggrenade.cpp | 488 + ricochet/dlls/globals.cpp | 39 + ricochet/dlls/h_ai.cpp | 198 + ricochet/dlls/h_battery.cpp | 200 + ricochet/dlls/h_cycler.cpp | 471 + ricochet/dlls/h_export.cpp | 61 + ricochet/dlls/healthkit.cpp | 259 + ricochet/dlls/items.cpp | 337 + ricochet/dlls/items.h | 29 + ricochet/dlls/lights.cpp | 199 + ricochet/dlls/maprules.cpp | 918 ++ ricochet/dlls/maprules.h | 22 + ricochet/dlls/monsterevent.h | 34 + ricochet/dlls/monsters.h | 88 + ricochet/dlls/mortar.cpp | 323 + ricochet/dlls/mp.def | 5 + ricochet/dlls/mp.dsp | 516 + ricochet/dlls/mpstubb.cpp | 264 + ricochet/dlls/multiplay_gamerules.cpp | 1620 +++ ricochet/dlls/nodes.h | 54 + ricochet/dlls/observer.cpp | 325 + ricochet/dlls/pathcorner.cpp | 428 + ricochet/dlls/plane.cpp | 60 + ricochet/dlls/plane.h | 43 + ricochet/dlls/plats.cpp | 2285 ++++ ricochet/dlls/player.cpp | 4901 +++++++ ricochet/dlls/player.h | 356 + ricochet/dlls/saverestore.h | 170 + ricochet/dlls/schedule.h | 31 + ricochet/dlls/scriptevent.h | 29 + ricochet/dlls/singleplay_gamerules.cpp | 331 + ricochet/dlls/skill.cpp | 47 + ricochet/dlls/skill.h | 147 + ricochet/dlls/sound.cpp | 1985 +++ ricochet/dlls/soundent.cpp | 379 + ricochet/dlls/soundent.h | 95 + ricochet/dlls/spectator.cpp | 149 + ricochet/dlls/spectator.h | 27 + ricochet/dlls/subs.cpp | 582 + ricochet/dlls/talkmonster.h | 26 + ricochet/dlls/teamplay_gamerules.cpp | 611 + ricochet/dlls/teamplay_gamerules.h | 57 + ricochet/dlls/trains.h | 127 + ricochet/dlls/triggers.cpp | 2790 ++++ ricochet/dlls/util.cpp | 2565 ++++ ricochet/dlls/util.h | 564 + ricochet/dlls/vector.h | 112 + ricochet/dlls/weapons.cpp | 1058 ++ ricochet/dlls/weapons.h | 451 + ricochet/dlls/world.cpp | 782 ++ ricochet/dlls/wpn_shared/disc_weapon_disc.cpp | 753 ++ ricochet/dlls/xen.cpp | 530 + ricochet/pm_shared/pm_debug.c | 305 + ricochet/pm_shared/pm_debug.h | 10 + ricochet/pm_shared/pm_defs.h | 208 + ricochet/pm_shared/pm_info.h | 10 + ricochet/pm_shared/pm_materials.h | 19 + ricochet/pm_shared/pm_math.c | 326 + ricochet/pm_shared/pm_movevars.h | 40 + ricochet/pm_shared/pm_shared.c | 3374 +++++ ricochet/pm_shared/pm_shared.h | 32 + utils/bspinfo/bspinfo.c | 48 + utils/bspinfo/bspinfo.dsp | 124 + utils/bspinfo/bspinfo.dsw | 37 + utils/common/bspfile.c | 709 + utils/common/bspfile.h | 332 + utils/common/bsplib.c | 709 + utils/common/bsplib.h | 332 + utils/common/cmdlib.c | 1040 ++ utils/common/cmdlib.h | 150 + utils/common/l3dslib.c | 282 + utils/common/l3dslib.h | 5 + utils/common/lbmlib.c | 744 ++ utils/common/lbmlib.h | 57 + utils/common/mathlib.c | 351 + utils/common/mathlib.h | 89 + utils/common/meter.c | 34 + utils/common/meter.h | 16 + utils/common/movie.h | 36 + utils/common/polylib.c | 708 + utils/common/polylib.h | 36 + utils/common/scriplib.c | 268 + utils/common/scriplib.h | 33 + utils/common/threads.c | 330 + utils/common/threads.h | 24 + utils/common/trilib.c | 180 + utils/common/trilib.h | 21 + utils/common/wadlib.c | 339 + utils/common/wadlib.h | 63 + utils/light/light.c | 228 + utils/light/light.dsp | 132 + utils/light/light.h | 60 + utils/light/ltface.c | 688 + utils/light/new/light.c | 193 + utils/light/new/light.h | 54 + utils/light/new/ltface.c | 585 + utils/light/new/trace.c | 199 + utils/light/trace.c | 206 + utils/makefont/makefont.cpp | 494 + utils/makefont/makefont.dsp | 138 + utils/makefont/makefont.dsw | 29 + utils/makels/makels.cpp | 173 + utils/makels/makels.dsp | 100 + utils/makels/makels.dsw | 37 + utils/makels/makels.mak | 212 + utils/mdlviewer/mdlviewer.cpp | 268 + utils/mdlviewer/mdlviewer.dsp | 122 + utils/mdlviewer/mdlviewer.dsw | 29 + utils/mdlviewer/mdlviewer.h | 78 + utils/mdlviewer/studio_render.cpp | 727 + utils/mdlviewer/studio_utils.cpp | 425 + utils/mkmovie/mkmovie.c | 270 + utils/mkmovie/mkmovie.dsp | 103 + utils/procinfo/lib/win32_vc6/procinfo.lib | Bin 0 -> 2440 bytes utils/procinfo/procinfo.cpp | 189 + utils/procinfo/procinfo.dsp | 118 + utils/procinfo/procinfo.h | 15 + utils/qbsp2/bsp5.h | 269 + utils/qbsp2/cull.c | 28 + utils/qbsp2/gldraw.c | 164 + utils/qbsp2/makefile | 65 + utils/qbsp2/merge.c | 282 + utils/qbsp2/nodraw.c | 59 + utils/qbsp2/outside.c | 421 + utils/qbsp2/portals.c | 433 + utils/qbsp2/qbsp.c | 1016 ++ utils/qbsp2/qbsp2.dsp | 176 + utils/qbsp2/qbsp2.dsw | 29 + utils/qbsp2/solidbsp.c | 984 ++ utils/qbsp2/surfaces.c | 474 + utils/qbsp2/tjunc.c | 520 + utils/qbsp2/writebsp.c | 289 + utils/qcsg/brush.c | 987 ++ utils/qcsg/csg.h | 144 + utils/qcsg/gldraw.c | 154 + utils/qcsg/hullfile.c | 87 + utils/qcsg/hulls.txt | 4 + utils/qcsg/map.c | 344 + utils/qcsg/qcsg.c | 879 ++ utils/qcsg/qcsg.dsp | 172 + utils/qcsg/qcsg.dsw | 37 + utils/qcsg/source.p0 | 0 utils/qcsg/source.p1 | 0 utils/qcsg/source.p2 | 0 utils/qcsg/textures.c | 520 + utils/qlumpy/qlumpy.c | 430 + utils/qlumpy/qlumpy.doc | Bin 0 -> 12800 bytes utils/qlumpy/qlumpy.dsp | 140 + utils/qlumpy/qlumpy.dsw | 37 + utils/qlumpy/qlumpy.h | 26 + utils/qlumpy/quakegrb.c | 841 ++ utils/qrad/lightmap.c | 1723 +++ utils/qrad/qrad.c | 1569 +++ utils/qrad/qrad.dsp | 164 + utils/qrad/qrad.dsw | 37 + utils/qrad/qrad.h | 154 + utils/qrad/qrad.txt | 152 + utils/qrad/trace.c | 320 + utils/qrad/vismat.c | 569 + utils/serverctrl/ServerCtrl.cpp | 61 + utils/serverctrl/ServerCtrl.h | 47 + utils/serverctrl/ServerCtrl.rc | 179 + utils/serverctrl/ServerCtrlDlg.cpp | 658 + utils/serverctrl/ServerCtrlDlg.h | 79 + utils/serverctrl/StdAfx.cpp | 8 + utils/serverctrl/StdAfx.h | 26 + utils/serverctrl/res/serverctrl.ico | Bin 0 -> 1078 bytes utils/serverctrl/res/serverctrl.rc2 | 13 + utils/serverctrl/resource.h | 22 + utils/serverctrl/serverctrl.dsp | 144 + utils/smdlexp/smdlexp.cpp | 1081 ++ utils/smdlexp/smdlexp.def | 8 + utils/smdlexp/smdlexp.dsp | 119 + utils/smdlexp/smdlexp.dsw | 33 + utils/smdlexp/smdlexp.rc | 147 + utils/smdlexp/smedefs.h | 181 + utils/smdlexp/smexprc.h | 21 + utils/sprgen/s_bubble.spr | Bin 0 -> 588 bytes utils/sprgen/s_explod.spr | Bin 0 -> 18972 bytes utils/sprgen/s_light.spr | Bin 0 -> 1080 bytes utils/sprgen/sprgen.c | 617 + utils/sprgen/sprgen.dsp | 128 + utils/sprgen/sprgen.dsw | 37 + utils/sprgen/spritegn.h | 104 + utils/studiomdl/bmpread.c | 100 + utils/studiomdl/studiomdl.c | 3438 +++++ utils/studiomdl/studiomdl.dsp | 156 + utils/studiomdl/studiomdl.dsw | 37 + utils/studiomdl/studiomdl.h | 403 + utils/studiomdl/tristrip.c | 353 + utils/studiomdl/write.c | 650 + utils/vgui/include/VGUI.h | 108 + utils/vgui/include/VGUI_ActionSignal.h | 84 + utils/vgui/include/VGUI_App.h | 130 + utils/vgui/include/VGUI_Bitmap.h | 37 + utils/vgui/include/VGUI_BitmapTGA.h | 29 + utils/vgui/include/VGUI_Border.h | 45 + utils/vgui/include/VGUI_BorderLayout.h | 46 + utils/vgui/include/VGUI_BorderPair.h | 33 + utils/vgui/include/VGUI_BuildGroup.h | 68 + utils/vgui/include/VGUI_Button.h | 61 + utils/vgui/include/VGUI_ButtonController.h | 27 + utils/vgui/include/VGUI_ButtonGroup.h | 30 + utils/vgui/include/VGUI_ChangeSignal.h | 26 + utils/vgui/include/VGUI_CheckButton.h | 28 + utils/vgui/include/VGUI_Color.h | 44 + utils/vgui/include/VGUI_ComboKey.h | 34 + utils/vgui/include/VGUI_ConfigWizard.h | 40 + utils/vgui/include/VGUI_Cursor.h | 57 + utils/vgui/include/VGUI_Dar.h | 194 + utils/vgui/include/VGUI_DataInputStream.h | 49 + utils/vgui/include/VGUI_Desktop.h | 42 + utils/vgui/include/VGUI_DesktopIcon.h | 41 + utils/vgui/include/VGUI_EditPanel.h | 65 + utils/vgui/include/VGUI_EtchedBorder.h | 29 + utils/vgui/include/VGUI_FileInputStream.h | 38 + utils/vgui/include/VGUI_FlowLayout.h | 29 + utils/vgui/include/VGUI_FocusChangeSignal.h | 26 + utils/vgui/include/VGUI_FocusNavGroup.h | 35 + utils/vgui/include/VGUI_Font.h | 51 + utils/vgui/include/VGUI_Frame.h | 73 + utils/vgui/include/VGUI_FrameSignal.h | 27 + utils/vgui/include/VGUI_GridLayout.h | 30 + utils/vgui/include/VGUI_HeaderPanel.h | 65 + utils/vgui/include/VGUI_Image.h | 62 + utils/vgui/include/VGUI_ImagePanel.h | 38 + utils/vgui/include/VGUI_InputSignal.h | 39 + utils/vgui/include/VGUI_InputStream.h | 30 + utils/vgui/include/VGUI_IntChangeSignal.h | 26 + utils/vgui/include/VGUI_IntLabel.h | 35 + utils/vgui/include/VGUI_KeyCode.h | 126 + utils/vgui/include/VGUI_Label.h | 81 + utils/vgui/include/VGUI_Layout.h | 31 + utils/vgui/include/VGUI_LayoutInfo.h | 21 + utils/vgui/include/VGUI_LineBorder.h | 39 + utils/vgui/include/VGUI_ListPanel.h | 40 + utils/vgui/include/VGUI_LoweredBorder.h | 29 + utils/vgui/include/VGUI_Menu.h | 30 + utils/vgui/include/VGUI_MenuItem.h | 30 + utils/vgui/include/VGUI_MenuSeparator.h | 27 + utils/vgui/include/VGUI_MessageBox.h | 53 + utils/vgui/include/VGUI_MiniApp.h | 33 + utils/vgui/include/VGUI_MouseCode.h | 24 + utils/vgui/include/VGUI_Panel.h | 228 + utils/vgui/include/VGUI_Point.h | 117 + utils/vgui/include/VGUI_PopupMenu.h | 30 + utils/vgui/include/VGUI_ProgressBar.h | 33 + utils/vgui/include/VGUI_RadioButton.h | 29 + utils/vgui/include/VGUI_RaisedBorder.h | 29 + utils/vgui/include/VGUI_RepaintSignal.h | 26 + utils/vgui/include/VGUI_Scheme.h | 82 + utils/vgui/include/VGUI_ScrollBar.h | 56 + utils/vgui/include/VGUI_ScrollPanel.h | 55 + utils/vgui/include/VGUI_Slider.h | 69 + utils/vgui/include/VGUI_StackLayout.h | 30 + utils/vgui/include/VGUI_String.h | 61 + utils/vgui/include/VGUI_Surface.h | 68 + utils/vgui/include/VGUI_SurfaceBase.h | 82 + utils/vgui/include/VGUI_SurfaceGL.h | 98 + utils/vgui/include/VGUI_TabPanel.h | 49 + utils/vgui/include/VGUI_TablePanel.h | 71 + utils/vgui/include/VGUI_TaskBar.h | 37 + utils/vgui/include/VGUI_TextEntry.h | 80 + utils/vgui/include/VGUI_TextGrid.h | 42 + utils/vgui/include/VGUI_TextImage.h | 58 + utils/vgui/include/VGUI_TextPanel.h | 46 + utils/vgui/include/VGUI_TickSignal.h | 22 + utils/vgui/include/VGUI_ToggleButton.h | 26 + utils/vgui/include/VGUI_TreeFolder.h | 36 + utils/vgui/include/VGUI_WizardPanel.h | 147 + utils/vgui/lib/win32_vc6/vgui.lib | Bin 0 -> 416988 bytes utils/visx2/flow.c | 687 + utils/visx2/soundpvs.c | 156 + utils/visx2/vis.c | 527 + utils/visx2/vis.dsp | 161 + utils/visx2/vis.dsw | 37 + utils/visx2/vis.h | 141 + utils/xwad/xwad.c | 157 + utils/xwad/xwad.dsp | 100 + 1077 files changed, 416347 insertions(+) create mode 100644 cl_dll/Exports.h create mode 100644 cl_dll/GameStudioModelRenderer.cpp create mode 100644 cl_dll/GameStudioModelRenderer.h create mode 100644 cl_dll/GameStudioModelRenderer_Sample.cpp create mode 100644 cl_dll/GameStudioModelRenderer_Sample.h create mode 100644 cl_dll/MOTD.cpp create mode 100644 cl_dll/StudioModelRenderer.cpp create mode 100644 cl_dll/StudioModelRenderer.h create mode 100644 cl_dll/ammo.cpp create mode 100644 cl_dll/ammo.h create mode 100644 cl_dll/ammo_secondary.cpp create mode 100644 cl_dll/ammohistory.cpp create mode 100644 cl_dll/ammohistory.h create mode 100644 cl_dll/battery.cpp create mode 100644 cl_dll/bench.h create mode 100644 cl_dll/camera.h create mode 100644 cl_dll/cdll_int.cpp create mode 100644 cl_dll/cl_dll.dsp create mode 100644 cl_dll/cl_dll.h create mode 100644 cl_dll/cl_util.h create mode 100644 cl_dll/com_weapons.cpp create mode 100644 cl_dll/com_weapons.h create mode 100644 cl_dll/death.cpp create mode 100644 cl_dll/demo.cpp create mode 100644 cl_dll/demo.h create mode 100644 cl_dll/entity.cpp create mode 100644 cl_dll/ev_common.cpp create mode 100644 cl_dll/ev_hldm.cpp create mode 100644 cl_dll/ev_hldm.h create mode 100644 cl_dll/events.cpp create mode 100644 cl_dll/eventscripts.h create mode 100644 cl_dll/flashlight.cpp create mode 100644 cl_dll/geiger.cpp create mode 100644 cl_dll/global_consts.h create mode 100644 cl_dll/health.cpp create mode 100644 cl_dll/health.h create mode 100644 cl_dll/hl/hl_baseentity.cpp create mode 100644 cl_dll/hl/hl_events.cpp create mode 100644 cl_dll/hl/hl_objects.cpp create mode 100644 cl_dll/hl/hl_weapons.cpp create mode 100644 cl_dll/hud.cpp create mode 100644 cl_dll/hud.h create mode 100644 cl_dll/hud_bench.cpp create mode 100644 cl_dll/hud_benchtrace.cpp create mode 100644 cl_dll/hud_benchtrace.h create mode 100644 cl_dll/hud_iface.h create mode 100644 cl_dll/hud_msg.cpp create mode 100644 cl_dll/hud_redraw.cpp create mode 100644 cl_dll/hud_servers.cpp create mode 100644 cl_dll/hud_servers.h create mode 100644 cl_dll/hud_servers_priv.h create mode 100644 cl_dll/hud_spectator.cpp create mode 100644 cl_dll/hud_spectator.h create mode 100644 cl_dll/hud_update.cpp create mode 100644 cl_dll/in_camera.cpp create mode 100644 cl_dll/in_defs.h create mode 100644 cl_dll/input.cpp create mode 100644 cl_dll/inputw32.cpp create mode 100644 cl_dll/interpolation.cpp create mode 100644 cl_dll/interpolation.h create mode 100644 cl_dll/kbutton.h create mode 100644 cl_dll/menu.cpp create mode 100644 cl_dll/message.cpp create mode 100644 cl_dll/overview.cpp create mode 100644 cl_dll/overview.h create mode 100644 cl_dll/player_info.h create mode 100644 cl_dll/readme.txt create mode 100644 cl_dll/saytext.cpp create mode 100644 cl_dll/scoreboard.cpp create mode 100644 cl_dll/soundsystem.cpp create mode 100644 cl_dll/status_icons.cpp create mode 100644 cl_dll/statusbar.cpp create mode 100644 cl_dll/studio_util.cpp create mode 100644 cl_dll/studio_util.h create mode 100644 cl_dll/text_message.cpp create mode 100644 cl_dll/tf_defs.h create mode 100644 cl_dll/train.cpp create mode 100644 cl_dll/tri.cpp create mode 100644 cl_dll/tri.h create mode 100644 cl_dll/util.cpp create mode 100644 cl_dll/util_vector.h create mode 100644 cl_dll/vgui_ClassMenu.cpp create mode 100644 cl_dll/vgui_ConsolePanel.cpp create mode 100644 cl_dll/vgui_ConsolePanel.h create mode 100644 cl_dll/vgui_ControlConfigPanel.cpp create mode 100644 cl_dll/vgui_ControlConfigPanel.h create mode 100644 cl_dll/vgui_CustomObjects.cpp create mode 100644 cl_dll/vgui_MOTDWindow.cpp create mode 100644 cl_dll/vgui_SchemeManager.cpp create mode 100644 cl_dll/vgui_SchemeManager.h create mode 100644 cl_dll/vgui_ScorePanel.cpp create mode 100644 cl_dll/vgui_ScorePanel.h create mode 100644 cl_dll/vgui_ServerBrowser.cpp create mode 100644 cl_dll/vgui_ServerBrowser.h create mode 100644 cl_dll/vgui_SpectatorPanel.cpp create mode 100644 cl_dll/vgui_SpectatorPanel.h create mode 100644 cl_dll/vgui_TeamFortressViewport.cpp create mode 100644 cl_dll/vgui_TeamFortressViewport.h create mode 100644 cl_dll/vgui_int.cpp create mode 100644 cl_dll/vgui_int.h create mode 100644 cl_dll/vgui_teammenu.cpp create mode 100644 cl_dll/view.cpp create mode 100644 cl_dll/view.h create mode 100644 cl_dll/voice_status.cpp create mode 100644 cl_dll/voice_status.h create mode 100644 cl_dll/wrect.h create mode 100644 common/Sequence.h create mode 100644 common/beamdef.h create mode 100644 common/cl_entity.h create mode 100644 common/com_model.h create mode 100644 common/con_nprint.h create mode 100644 common/const.h create mode 100644 common/crc.h create mode 100644 common/cvardef.h create mode 100644 common/demo_api.h create mode 100644 common/director_cmds.h create mode 100644 common/dlight.h create mode 100644 common/dll_state.h create mode 100644 common/entity_state.h create mode 100644 common/entity_types.h create mode 100644 common/enums.h create mode 100644 common/event_api.h create mode 100644 common/event_args.h create mode 100644 common/event_flags.h create mode 100644 common/hltv.h create mode 100644 common/in_buttons.h create mode 100644 common/interface.cpp create mode 100644 common/interface.h create mode 100644 common/ivoicetweak.h create mode 100644 common/mathlib.h create mode 100644 common/net_api.h create mode 100644 common/netadr.h create mode 100644 common/nowin.h create mode 100644 common/parsemsg.cpp create mode 100644 common/parsemsg.h create mode 100644 common/particledef.h create mode 100644 common/pmtrace.h create mode 100644 common/port.h create mode 100644 common/qfont.h create mode 100644 common/r_efx.h create mode 100644 common/r_studioint.h create mode 100644 common/ref_params.h create mode 100644 common/screenfade.h create mode 100644 common/studio_event.h create mode 100644 common/triangleapi.h create mode 100644 common/usercmd.h create mode 100644 common/weaponinfo.h create mode 100644 dlls/AI_BaseNPC_Schedule.cpp create mode 100644 dlls/Makefile create mode 100644 dlls/Wxdebug.cpp create mode 100644 dlls/activity.h create mode 100644 dlls/activitymap.h create mode 100644 dlls/aflock.cpp create mode 100644 dlls/agrunt.cpp create mode 100644 dlls/airtank.cpp create mode 100644 dlls/animating.cpp create mode 100644 dlls/animation.cpp create mode 100644 dlls/animation.h create mode 100644 dlls/apache.cpp create mode 100644 dlls/barnacle.cpp create mode 100644 dlls/barney.cpp create mode 100644 dlls/basemonster.h create mode 100644 dlls/bigmomma.cpp create mode 100644 dlls/bloater.cpp create mode 100644 dlls/bmodels.cpp create mode 100644 dlls/bullsquid.cpp create mode 100644 dlls/buttons.cpp create mode 100644 dlls/cbase.cpp create mode 100644 dlls/cbase.h create mode 100644 dlls/cdll_dll.h create mode 100644 dlls/client.cpp create mode 100644 dlls/client.h create mode 100644 dlls/combat.cpp create mode 100644 dlls/controller.cpp create mode 100644 dlls/crossbow.cpp create mode 100644 dlls/crowbar.cpp create mode 100644 dlls/decals.h create mode 100644 dlls/defaultai.cpp create mode 100644 dlls/defaultai.h create mode 100644 dlls/doors.cpp create mode 100644 dlls/doors.h create mode 100644 dlls/effects.cpp create mode 100644 dlls/effects.h create mode 100644 dlls/egon.cpp create mode 100644 dlls/enginecallback.h create mode 100644 dlls/explode.cpp create mode 100644 dlls/explode.h create mode 100644 dlls/extdll.h create mode 100644 dlls/flyingmonster.cpp create mode 100644 dlls/flyingmonster.h create mode 100644 dlls/func_break.cpp create mode 100644 dlls/func_break.h create mode 100644 dlls/func_tank.cpp create mode 100644 dlls/game.cpp create mode 100644 dlls/game.h create mode 100644 dlls/gamerules.cpp create mode 100644 dlls/gamerules.h create mode 100644 dlls/gargantua.cpp create mode 100644 dlls/gauss.cpp create mode 100644 dlls/genericmonster.cpp create mode 100644 dlls/ggrenade.cpp create mode 100644 dlls/globals.cpp create mode 100644 dlls/glock.cpp create mode 100644 dlls/gman.cpp create mode 100644 dlls/h_ai.cpp create mode 100644 dlls/h_battery.cpp create mode 100644 dlls/h_cine.cpp create mode 100644 dlls/h_cycler.cpp create mode 100644 dlls/h_export.cpp create mode 100644 dlls/handgrenade.cpp create mode 100644 dlls/hassassin.cpp create mode 100644 dlls/headcrab.cpp create mode 100644 dlls/healthkit.cpp create mode 100644 dlls/hgrunt.cpp create mode 100644 dlls/hl.def create mode 100644 dlls/hl.dsp create mode 100644 dlls/hlgl.def create mode 100644 dlls/hornet.cpp create mode 100644 dlls/hornet.h create mode 100644 dlls/hornetgun.cpp create mode 100644 dlls/houndeye.cpp create mode 100644 dlls/ichthyosaur.cpp create mode 100644 dlls/islave.cpp create mode 100644 dlls/items.cpp create mode 100644 dlls/items.h create mode 100644 dlls/leech.cpp create mode 100644 dlls/lights.cpp create mode 100644 dlls/maprules.cpp create mode 100644 dlls/maprules.h create mode 100644 dlls/monsterevent.h create mode 100644 dlls/monstermaker.cpp create mode 100644 dlls/monsters.cpp create mode 100644 dlls/monsters.h create mode 100644 dlls/monsterstate.cpp create mode 100644 dlls/mortar.cpp create mode 100644 dlls/mp5.cpp create mode 100644 dlls/mpstubb.cpp create mode 100644 dlls/multiplay_gamerules.cpp create mode 100644 dlls/nihilanth.cpp create mode 100644 dlls/nodes.cpp create mode 100644 dlls/nodes.h create mode 100644 dlls/observer.cpp create mode 100644 dlls/osprey.cpp create mode 100644 dlls/pathcorner.cpp create mode 100644 dlls/plane.cpp create mode 100644 dlls/plane.h create mode 100644 dlls/plats.cpp create mode 100644 dlls/player.cpp create mode 100644 dlls/player.h create mode 100644 dlls/playermonster.cpp create mode 100644 dlls/python.cpp create mode 100644 dlls/rat.cpp create mode 100644 dlls/roach.cpp create mode 100644 dlls/rpg.cpp create mode 100644 dlls/satchel.cpp create mode 100644 dlls/saverestore.h create mode 100644 dlls/schedule.cpp create mode 100644 dlls/schedule.h create mode 100644 dlls/scientist.cpp create mode 100644 dlls/scripted.cpp create mode 100644 dlls/scripted.h create mode 100644 dlls/scriptevent.h create mode 100644 dlls/shotgun.cpp create mode 100644 dlls/singleplay_gamerules.cpp create mode 100644 dlls/skill.cpp create mode 100644 dlls/skill.h create mode 100644 dlls/sound.cpp create mode 100644 dlls/soundent.cpp create mode 100644 dlls/soundent.h create mode 100644 dlls/spectator.cpp create mode 100644 dlls/spectator.h create mode 100644 dlls/squad.h create mode 100644 dlls/squadmonster.cpp create mode 100644 dlls/squadmonster.h create mode 100644 dlls/squeakgrenade.cpp create mode 100644 dlls/stats.cpp create mode 100644 dlls/subs.cpp create mode 100644 dlls/talkmonster.cpp create mode 100644 dlls/talkmonster.h create mode 100644 dlls/teamplay_gamerules.cpp create mode 100644 dlls/teamplay_gamerules.h create mode 100644 dlls/tempmonster.cpp create mode 100644 dlls/tentacle.cpp create mode 100644 dlls/trains.h create mode 100644 dlls/triggers.cpp create mode 100644 dlls/tripmine.cpp create mode 100644 dlls/turret.cpp create mode 100644 dlls/util.cpp create mode 100644 dlls/util.h create mode 100644 dlls/vector.h create mode 100644 dlls/weapons.cpp create mode 100644 dlls/weapons.h create mode 100644 dlls/world.cpp create mode 100644 dlls/wpn_shared/hl_wpn_glock.cpp create mode 100644 dlls/wxdebug.h create mode 100644 dlls/xen.cpp create mode 100644 dlls/zombie.cpp create mode 100644 dmc/cl_dll/CTF_FlagStatus.cpp create mode 100644 dmc/cl_dll/CTF_HudMessage.cpp create mode 100644 dmc/cl_dll/DMC_BSPFile.h create mode 100644 dmc/cl_dll/DMC_Teleporters.cpp create mode 100644 dmc/cl_dll/DMC_Teleporters.h create mode 100644 dmc/cl_dll/Exports.h create mode 100644 dmc/cl_dll/GameStudioModelRenderer.cpp create mode 100644 dmc/cl_dll/GameStudioModelRenderer.h create mode 100644 dmc/cl_dll/MOTD.cpp create mode 100644 dmc/cl_dll/StudioModelRenderer.cpp create mode 100644 dmc/cl_dll/StudioModelRenderer.h create mode 100644 dmc/cl_dll/ammo.cpp create mode 100644 dmc/cl_dll/ammo.h create mode 100644 dmc/cl_dll/ammo_secondary.cpp create mode 100644 dmc/cl_dll/ammohistory.cpp create mode 100644 dmc/cl_dll/ammohistory.h create mode 100644 dmc/cl_dll/battery.cpp create mode 100644 dmc/cl_dll/camera.h create mode 100644 dmc/cl_dll/cdll_int.cpp create mode 100644 dmc/cl_dll/cl_dll.dsp create mode 100644 dmc/cl_dll/cl_dll.h create mode 100644 dmc/cl_dll/cl_util.h create mode 100644 dmc/cl_dll/com_model.h create mode 100644 dmc/cl_dll/com_weapons.cpp create mode 100644 dmc/cl_dll/com_weapons.h create mode 100644 dmc/cl_dll/death.cpp create mode 100644 dmc/cl_dll/demo.cpp create mode 100644 dmc/cl_dll/demo.h create mode 100644 dmc/cl_dll/entity.cpp create mode 100644 dmc/cl_dll/ev_common.cpp create mode 100644 dmc/cl_dll/ev_hldm.cpp create mode 100644 dmc/cl_dll/ev_hldm.h create mode 100644 dmc/cl_dll/events.cpp create mode 100644 dmc/cl_dll/eventscripts.h create mode 100644 dmc/cl_dll/flashlight.cpp create mode 100644 dmc/cl_dll/geiger.cpp create mode 100644 dmc/cl_dll/health.cpp create mode 100644 dmc/cl_dll/health.h create mode 100644 dmc/cl_dll/hud.cpp create mode 100644 dmc/cl_dll/hud.h create mode 100644 dmc/cl_dll/hud_iface.h create mode 100644 dmc/cl_dll/hud_msg.cpp create mode 100644 dmc/cl_dll/hud_redraw.cpp create mode 100644 dmc/cl_dll/hud_servers.cpp create mode 100644 dmc/cl_dll/hud_servers.h create mode 100644 dmc/cl_dll/hud_servers_priv.h create mode 100644 dmc/cl_dll/hud_spectator.cpp create mode 100644 dmc/cl_dll/hud_spectator.h create mode 100644 dmc/cl_dll/hud_update.cpp create mode 100644 dmc/cl_dll/in_camera.cpp create mode 100644 dmc/cl_dll/in_defs.h create mode 100644 dmc/cl_dll/input.cpp create mode 100644 dmc/cl_dll/inputw32.cpp create mode 100644 dmc/cl_dll/kbutton.h create mode 100644 dmc/cl_dll/menu.cpp create mode 100644 dmc/cl_dll/message.cpp create mode 100644 dmc/cl_dll/quake/quake_baseentity.cpp create mode 100644 dmc/cl_dll/quake/quake_events.cpp create mode 100644 dmc/cl_dll/quake/quake_objects.cpp create mode 100644 dmc/cl_dll/quake/quake_weapons.cpp create mode 100644 dmc/cl_dll/readme.txt create mode 100644 dmc/cl_dll/saytext.cpp create mode 100644 dmc/cl_dll/scoreboard.cpp create mode 100644 dmc/cl_dll/status_icons.cpp create mode 100644 dmc/cl_dll/statusbar.cpp create mode 100644 dmc/cl_dll/studio_util.cpp create mode 100644 dmc/cl_dll/studio_util.h create mode 100644 dmc/cl_dll/text_message.cpp create mode 100644 dmc/cl_dll/train.cpp create mode 100644 dmc/cl_dll/tri.cpp create mode 100644 dmc/cl_dll/util.cpp create mode 100644 dmc/cl_dll/util.h create mode 100644 dmc/cl_dll/util_vector.h create mode 100644 dmc/cl_dll/vgui_ControlConfigPanel.h create mode 100644 dmc/cl_dll/vgui_CustomObjects.cpp create mode 100644 dmc/cl_dll/vgui_MOTDWindow.cpp create mode 100644 dmc/cl_dll/vgui_SchemeManager.cpp create mode 100644 dmc/cl_dll/vgui_SchemeManager.h create mode 100644 dmc/cl_dll/vgui_ScorePanel.cpp create mode 100644 dmc/cl_dll/vgui_ScorePanel.h create mode 100644 dmc/cl_dll/vgui_ServerBrowser.cpp create mode 100644 dmc/cl_dll/vgui_ServerBrowser.h create mode 100644 dmc/cl_dll/vgui_SpectatorPanel.cpp create mode 100644 dmc/cl_dll/vgui_SpectatorPanel.h create mode 100644 dmc/cl_dll/vgui_int.cpp create mode 100644 dmc/cl_dll/vgui_int.h create mode 100644 dmc/cl_dll/vgui_viewport.cpp create mode 100644 dmc/cl_dll/vgui_viewport.h create mode 100644 dmc/cl_dll/view.cpp create mode 100644 dmc/cl_dll/view.h create mode 100644 dmc/cl_dll/voice_status.cpp create mode 100644 dmc/cl_dll/voice_status.h create mode 100644 dmc/cl_dll/wrect.h create mode 100644 dmc/dlls/Makefile create mode 100644 dmc/dlls/activity.h create mode 100644 dmc/dlls/activitymap.h create mode 100644 dmc/dlls/animating.cpp create mode 100644 dmc/dlls/animation.cpp create mode 100644 dmc/dlls/animation.h create mode 100644 dmc/dlls/basemonster.h create mode 100644 dmc/dlls/bmodels.cpp create mode 100644 dmc/dlls/buttons.cpp create mode 100644 dmc/dlls/cbase.cpp create mode 100644 dmc/dlls/cbase.h create mode 100644 dmc/dlls/cdll_dll.h create mode 100644 dmc/dlls/client.cpp create mode 100644 dmc/dlls/client.h create mode 100644 dmc/dlls/combat.cpp create mode 100644 dmc/dlls/decals.h create mode 100644 dmc/dlls/defaultai.cpp create mode 100644 dmc/dlls/defaultai.h create mode 100644 dmc/dlls/dmc.def create mode 100644 dmc/dlls/dmcgl.def create mode 100644 dmc/dlls/doors.cpp create mode 100644 dmc/dlls/doors.h create mode 100644 dmc/dlls/effects.cpp create mode 100644 dmc/dlls/effects.h create mode 100644 dmc/dlls/enginecallback.h create mode 100644 dmc/dlls/explode.cpp create mode 100644 dmc/dlls/explode.h create mode 100644 dmc/dlls/extdll.h create mode 100644 dmc/dlls/func_break.cpp create mode 100644 dmc/dlls/func_break.h create mode 100644 dmc/dlls/func_tank.cpp create mode 100644 dmc/dlls/game.cpp create mode 100644 dmc/dlls/game.h create mode 100644 dmc/dlls/gamerules.cpp create mode 100644 dmc/dlls/gamerules.h create mode 100644 dmc/dlls/globals.cpp create mode 100644 dmc/dlls/h_ai.cpp create mode 100644 dmc/dlls/h_export.cpp create mode 100644 dmc/dlls/hl.def create mode 100644 dmc/dlls/hl.dsp create mode 100644 dmc/dlls/hlgl.def create mode 100644 dmc/dlls/items.cpp create mode 100644 dmc/dlls/items.h create mode 100644 dmc/dlls/lights.cpp create mode 100644 dmc/dlls/maprules.cpp create mode 100644 dmc/dlls/maprules.h create mode 100644 dmc/dlls/monsterevent.h create mode 100644 dmc/dlls/monsters.cpp create mode 100644 dmc/dlls/monsters.h create mode 100644 dmc/dlls/monsterstate.cpp create mode 100644 dmc/dlls/multiplay_gamerules.cpp create mode 100644 dmc/dlls/nodes.cpp create mode 100644 dmc/dlls/nodes.h create mode 100644 dmc/dlls/observer.cpp create mode 100644 dmc/dlls/pathcorner.cpp create mode 100644 dmc/dlls/plane.cpp create mode 100644 dmc/dlls/plane.h create mode 100644 dmc/dlls/plats.cpp create mode 100644 dmc/dlls/player.cpp create mode 100644 dmc/dlls/player.h create mode 100644 dmc/dlls/quake_gun.cpp create mode 100644 dmc/dlls/quake_gun.h create mode 100644 dmc/dlls/quake_items.cpp create mode 100644 dmc/dlls/quake_nail.cpp create mode 100644 dmc/dlls/quake_player.cpp create mode 100644 dmc/dlls/quake_rocket.cpp create mode 100644 dmc/dlls/quake_weapons_all.cpp create mode 100644 dmc/dlls/saverestore.h create mode 100644 dmc/dlls/schedule.cpp create mode 100644 dmc/dlls/schedule.h create mode 100644 dmc/dlls/scripted.cpp create mode 100644 dmc/dlls/scripted.h create mode 100644 dmc/dlls/scriptevent.h create mode 100644 dmc/dlls/singleplay_gamerules.cpp create mode 100644 dmc/dlls/skill.cpp create mode 100644 dmc/dlls/skill.h create mode 100644 dmc/dlls/sound.cpp create mode 100644 dmc/dlls/soundent.cpp create mode 100644 dmc/dlls/soundent.h create mode 100644 dmc/dlls/spectator.cpp create mode 100644 dmc/dlls/spectator.h create mode 100644 dmc/dlls/subs.cpp create mode 100644 dmc/dlls/teamplay_gamerules.cpp create mode 100644 dmc/dlls/teamplay_gamerules.h create mode 100644 dmc/dlls/threewave_gamerules.cpp create mode 100644 dmc/dlls/threewave_gamerules.h create mode 100644 dmc/dlls/trains.h create mode 100644 dmc/dlls/triggers.cpp create mode 100644 dmc/dlls/util.cpp create mode 100644 dmc/dlls/util.h create mode 100644 dmc/dlls/vector.h create mode 100644 dmc/dlls/weapons.cpp create mode 100644 dmc/dlls/weapons.h create mode 100644 dmc/dlls/world.cpp create mode 100644 dmc/pm_shared/pm_debug.c create mode 100644 dmc/pm_shared/pm_debug.h create mode 100644 dmc/pm_shared/pm_defs.h create mode 100644 dmc/pm_shared/pm_info.h create mode 100644 dmc/pm_shared/pm_materials.h create mode 100644 dmc/pm_shared/pm_math.c create mode 100644 dmc/pm_shared/pm_movevars.h create mode 100644 dmc/pm_shared/pm_shared.c create mode 100644 dmc/pm_shared/pm_shared.h create mode 100644 engine/APIProxy.h create mode 100644 engine/anorms.h create mode 100644 engine/cdll_int.h create mode 100644 engine/custom.h create mode 100644 engine/customentity.h create mode 100644 engine/edict.h create mode 100644 engine/eiface.h create mode 100644 engine/progdefs.h create mode 100644 engine/progs.h create mode 100644 engine/shake.h create mode 100644 engine/studio.h create mode 100644 external/SDL2/SDL.h create mode 100644 external/SDL2/SDL_assert.h create mode 100644 external/SDL2/SDL_atomic.h create mode 100644 external/SDL2/SDL_audio.h create mode 100644 external/SDL2/SDL_bits.h create mode 100644 external/SDL2/SDL_blendmode.h create mode 100644 external/SDL2/SDL_clipboard.h create mode 100644 external/SDL2/SDL_config.h create mode 100644 external/SDL2/SDL_config_android.h create mode 100644 external/SDL2/SDL_config_iphoneos.h create mode 100644 external/SDL2/SDL_config_macosx.h create mode 100644 external/SDL2/SDL_config_minimal.h create mode 100644 external/SDL2/SDL_config_nintendods.h create mode 100644 external/SDL2/SDL_config_pandora.h create mode 100644 external/SDL2/SDL_config_windows.h create mode 100644 external/SDL2/SDL_config_wiz.h create mode 100644 external/SDL2/SDL_copying.h create mode 100644 external/SDL2/SDL_cpuinfo.h create mode 100644 external/SDL2/SDL_endian.h create mode 100644 external/SDL2/SDL_error.h create mode 100644 external/SDL2/SDL_events.h create mode 100644 external/SDL2/SDL_gamecontroller.h create mode 100644 external/SDL2/SDL_gesture.h create mode 100644 external/SDL2/SDL_haptic.h create mode 100644 external/SDL2/SDL_hints.h create mode 100644 external/SDL2/SDL_input.h create mode 100644 external/SDL2/SDL_joystick.h create mode 100644 external/SDL2/SDL_keyboard.h create mode 100644 external/SDL2/SDL_keycode.h create mode 100644 external/SDL2/SDL_loadso.h create mode 100644 external/SDL2/SDL_log.h create mode 100644 external/SDL2/SDL_main.h create mode 100644 external/SDL2/SDL_messagebox.h create mode 100644 external/SDL2/SDL_mouse.h create mode 100644 external/SDL2/SDL_mutex.h create mode 100644 external/SDL2/SDL_name.h create mode 100644 external/SDL2/SDL_opengl.h create mode 100644 external/SDL2/SDL_opengles.h create mode 100644 external/SDL2/SDL_opengles2.h create mode 100644 external/SDL2/SDL_pixels.h create mode 100644 external/SDL2/SDL_platform.h create mode 100644 external/SDL2/SDL_power.h create mode 100644 external/SDL2/SDL_quit.h create mode 100644 external/SDL2/SDL_rect.h create mode 100644 external/SDL2/SDL_render.h create mode 100644 external/SDL2/SDL_revision.h create mode 100644 external/SDL2/SDL_rwops.h create mode 100644 external/SDL2/SDL_scancode.h create mode 100644 external/SDL2/SDL_shape.h create mode 100644 external/SDL2/SDL_stdinc.h create mode 100644 external/SDL2/SDL_surface.h create mode 100644 external/SDL2/SDL_system.h create mode 100644 external/SDL2/SDL_syswm.h create mode 100644 external/SDL2/SDL_test.h create mode 100644 external/SDL2/SDL_test_assert.h create mode 100644 external/SDL2/SDL_test_common.h create mode 100644 external/SDL2/SDL_test_compare.h create mode 100644 external/SDL2/SDL_test_crc32.h create mode 100644 external/SDL2/SDL_test_font.h create mode 100644 external/SDL2/SDL_test_fuzzer.h create mode 100644 external/SDL2/SDL_test_harness.h create mode 100644 external/SDL2/SDL_test_images.h create mode 100644 external/SDL2/SDL_test_log.h create mode 100644 external/SDL2/SDL_test_md5.h create mode 100644 external/SDL2/SDL_test_random.h create mode 100644 external/SDL2/SDL_thread.h create mode 100644 external/SDL2/SDL_timer.h create mode 100644 external/SDL2/SDL_touch.h create mode 100644 external/SDL2/SDL_types.h create mode 100644 external/SDL2/SDL_version.h create mode 100644 external/SDL2/SDL_video.h create mode 100644 external/SDL2/begin_code.h create mode 100644 external/SDL2/close_code.h create mode 100755 external/SDL2/merge_sdl2.sh create mode 100755 filecopy.bat create mode 100644 game_shared/GameEvent.h create mode 100644 game_shared/bitvec.h create mode 100644 game_shared/bot/bot.cpp create mode 100644 game_shared/bot/bot.h create mode 100644 game_shared/bot/bot_constants.h create mode 100644 game_shared/bot/bot_manager.cpp create mode 100644 game_shared/bot/bot_manager.h create mode 100644 game_shared/bot/bot_profile.cpp create mode 100644 game_shared/bot/bot_profile.h create mode 100644 game_shared/bot/bot_util.cpp create mode 100644 game_shared/bot/bot_util.h create mode 100644 game_shared/bot/improv.h create mode 100644 game_shared/bot/nav.h create mode 100644 game_shared/bot/nav_area.cpp create mode 100644 game_shared/bot/nav_area.h create mode 100644 game_shared/bot/nav_file.cpp create mode 100644 game_shared/bot/nav_node.cpp create mode 100644 game_shared/bot/nav_node.h create mode 100644 game_shared/bot/nav_path.cpp create mode 100644 game_shared/bot/nav_path.h create mode 100644 game_shared/bot/simple_state_machine.h create mode 100644 game_shared/perf_counter.h create mode 100644 game_shared/shared_util.cpp create mode 100644 game_shared/shared_util.h create mode 100644 game_shared/simple_checksum.h create mode 100644 game_shared/steam_util.h create mode 100644 game_shared/vgui_checkbutton2.cpp create mode 100644 game_shared/vgui_checkbutton2.h create mode 100644 game_shared/vgui_defaultinputsignal.h create mode 100644 game_shared/vgui_grid.cpp create mode 100644 game_shared/vgui_grid.h create mode 100644 game_shared/vgui_helpers.cpp create mode 100644 game_shared/vgui_helpers.h create mode 100644 game_shared/vgui_listbox.cpp create mode 100644 game_shared/vgui_listbox.h create mode 100644 game_shared/vgui_loadtga.cpp create mode 100644 game_shared/vgui_loadtga.h create mode 100644 game_shared/vgui_scrollbar2.cpp create mode 100644 game_shared/vgui_scrollbar2.h create mode 100644 game_shared/vgui_slider2.cpp create mode 100644 game_shared/vgui_slider2.h create mode 100644 game_shared/voice_banmgr.cpp create mode 100644 game_shared/voice_banmgr.h create mode 100644 game_shared/voice_common.h create mode 100644 game_shared/voice_gamemgr.cpp create mode 100644 game_shared/voice_gamemgr.h create mode 100644 game_shared/voice_status.cpp create mode 100644 game_shared/voice_status.h create mode 100644 game_shared/voice_status_hud.cpp create mode 100644 game_shared/voice_status_hud.h create mode 100644 game_shared/voice_vgui_tweakdlg.cpp create mode 100644 game_shared/voice_vgui_tweakdlg.h create mode 100644 lib/public/SDL2.lib create mode 100644 lib/public/game_controls.lib create mode 100644 linux/Makefile create mode 100644 linux/Makefile.dmc_cdll create mode 100644 linux/Makefile.dmcdll create mode 100644 linux/Makefile.hl_cdll create mode 100644 linux/Makefile.hldll create mode 100644 linux/Makefile.ricochet_cdll create mode 100644 linux/Makefile.ricochetdll create mode 100755 linux/gendbg.sh create mode 100644 linux/libSDL2-2.0.0.dylib create mode 100644 linux/libSDL2-2.0.0.dylib.dSYM/Contents/Info.plist create mode 100644 linux/libSDL2-2.0.0.dylib.dSYM/Contents/Resources/DWARF/libsdl2-2.0.0.dylib create mode 100755 linux/libSDL2-2.0.so.0 create mode 100755 linux/libSDL2-2.0.so.0.dbg create mode 120000 linux/libSDL2.dylib create mode 120000 linux/libSDL2.so create mode 100755 linux/release/vgui.dylib create mode 100755 linux/release/vgui.so create mode 100644 network/Delta.txt create mode 100644 network/delta.lst create mode 100644 pm_shared/pm_debug.c create mode 100644 pm_shared/pm_debug.h create mode 100644 pm_shared/pm_defs.h create mode 100644 pm_shared/pm_info.h create mode 100644 pm_shared/pm_materials.h create mode 100644 pm_shared/pm_math.c create mode 100644 pm_shared/pm_movevars.h create mode 100644 pm_shared/pm_shared.c create mode 100644 pm_shared/pm_shared.h create mode 100644 public/FileSystem.h create mode 100644 public/archtypes.h create mode 100644 public/cl_dll/IGameClientExports.h create mode 100644 public/interface.cpp create mode 100644 public/interface.h create mode 100644 public/keydefs.h create mode 100644 public/particleman.h create mode 100644 public/pman_particlemem.h create mode 100644 public/pman_triangleffect.h create mode 100644 public/steam/steamtypes.h create mode 100644 ricochet/cl_dll/Exports.h create mode 100644 ricochet/cl_dll/GameStudioModelRenderer.cpp create mode 100644 ricochet/cl_dll/GameStudioModelRenderer.h create mode 100644 ricochet/cl_dll/Ricochet_BSPFile.h create mode 100644 ricochet/cl_dll/Ricochet_JumpPads.cpp create mode 100644 ricochet/cl_dll/Ricochet_JumpPads.h create mode 100644 ricochet/cl_dll/StudioModelRenderer.cpp create mode 100644 ricochet/cl_dll/StudioModelRenderer.h create mode 100644 ricochet/cl_dll/ammo.cpp create mode 100644 ricochet/cl_dll/ammo.h create mode 100644 ricochet/cl_dll/ammo_secondary.cpp create mode 100644 ricochet/cl_dll/ammohistory.cpp create mode 100644 ricochet/cl_dll/ammohistory.h create mode 100644 ricochet/cl_dll/battery.cpp create mode 100644 ricochet/cl_dll/camera.h create mode 100644 ricochet/cl_dll/cdll_int.cpp create mode 100644 ricochet/cl_dll/cl_dll.dsp create mode 100644 ricochet/cl_dll/cl_dll.h create mode 100644 ricochet/cl_dll/cl_util.h create mode 100644 ricochet/cl_dll/com_weapons.cpp create mode 100644 ricochet/cl_dll/com_weapons.h create mode 100644 ricochet/cl_dll/death.cpp create mode 100644 ricochet/cl_dll/demo.cpp create mode 100644 ricochet/cl_dll/demo.h create mode 100644 ricochet/cl_dll/entity.cpp create mode 100644 ricochet/cl_dll/ev_common.cpp create mode 100644 ricochet/cl_dll/ev_hldm.cpp create mode 100644 ricochet/cl_dll/ev_hldm.h create mode 100644 ricochet/cl_dll/events.cpp create mode 100644 ricochet/cl_dll/eventscripts.h create mode 100644 ricochet/cl_dll/flashlight.cpp create mode 100644 ricochet/cl_dll/geiger.cpp create mode 100644 ricochet/cl_dll/health.cpp create mode 100644 ricochet/cl_dll/health.h create mode 100644 ricochet/cl_dll/hl/hl_baseentity.cpp create mode 100644 ricochet/cl_dll/hl/hl_events.cpp create mode 100644 ricochet/cl_dll/hl/hl_objects.cpp create mode 100644 ricochet/cl_dll/hl/hl_weapons.cpp create mode 100644 ricochet/cl_dll/hud.cpp create mode 100644 ricochet/cl_dll/hud.h create mode 100644 ricochet/cl_dll/hud_iface.h create mode 100644 ricochet/cl_dll/hud_msg.cpp create mode 100644 ricochet/cl_dll/hud_redraw.cpp create mode 100644 ricochet/cl_dll/hud_servers.cpp create mode 100644 ricochet/cl_dll/hud_servers.h create mode 100644 ricochet/cl_dll/hud_servers_priv.h create mode 100644 ricochet/cl_dll/hud_update.cpp create mode 100644 ricochet/cl_dll/in_camera.cpp create mode 100644 ricochet/cl_dll/in_defs.h create mode 100644 ricochet/cl_dll/input.cpp create mode 100644 ricochet/cl_dll/inputw32.cpp create mode 100644 ricochet/cl_dll/kbutton.h create mode 100644 ricochet/cl_dll/menu.cpp create mode 100644 ricochet/cl_dll/message.cpp create mode 100644 ricochet/cl_dll/readme.txt create mode 100644 ricochet/cl_dll/saytext.cpp create mode 100644 ricochet/cl_dll/status_icons.cpp create mode 100644 ricochet/cl_dll/statusbar.cpp create mode 100644 ricochet/cl_dll/studio_util.cpp create mode 100644 ricochet/cl_dll/studio_util.h create mode 100644 ricochet/cl_dll/text_message.cpp create mode 100644 ricochet/cl_dll/tf_defs.h create mode 100644 ricochet/cl_dll/train.cpp create mode 100644 ricochet/cl_dll/tri.cpp create mode 100644 ricochet/cl_dll/util.cpp create mode 100644 ricochet/cl_dll/util_vector.h create mode 100644 ricochet/cl_dll/vgui_ConsolePanel.cpp create mode 100644 ricochet/cl_dll/vgui_ConsolePanel.h create mode 100644 ricochet/cl_dll/vgui_ControlConfigPanel.cpp create mode 100644 ricochet/cl_dll/vgui_ControlConfigPanel.h create mode 100644 ricochet/cl_dll/vgui_CustomObjects.cpp create mode 100644 ricochet/cl_dll/vgui_MOTDWindow.cpp create mode 100644 ricochet/cl_dll/vgui_SchemeManager.cpp create mode 100644 ricochet/cl_dll/vgui_SchemeManager.h create mode 100644 ricochet/cl_dll/vgui_ScorePanel.cpp create mode 100644 ricochet/cl_dll/vgui_ScorePanel.h create mode 100644 ricochet/cl_dll/vgui_ServerBrowser.cpp create mode 100644 ricochet/cl_dll/vgui_ServerBrowser.h create mode 100644 ricochet/cl_dll/vgui_TeamFortressViewport.cpp create mode 100644 ricochet/cl_dll/vgui_TeamFortressViewport.h create mode 100644 ricochet/cl_dll/vgui_discobjects.cpp create mode 100644 ricochet/cl_dll/vgui_discobjects.h create mode 100644 ricochet/cl_dll/vgui_int.cpp create mode 100644 ricochet/cl_dll/vgui_int.h create mode 100644 ricochet/cl_dll/view.cpp create mode 100644 ricochet/cl_dll/view.h create mode 100644 ricochet/cl_dll/voice_status.cpp create mode 100644 ricochet/cl_dll/voice_status.h create mode 100644 ricochet/cl_dll/wrect.h create mode 100644 ricochet/dlls/Makefile create mode 100644 ricochet/dlls/activity.h create mode 100644 ricochet/dlls/activitymap.h create mode 100644 ricochet/dlls/airtank.cpp create mode 100644 ricochet/dlls/animating.cpp create mode 100644 ricochet/dlls/animation.cpp create mode 100644 ricochet/dlls/animation.h create mode 100644 ricochet/dlls/basemonster.h create mode 100644 ricochet/dlls/bmodels.cpp create mode 100644 ricochet/dlls/buttons.cpp create mode 100644 ricochet/dlls/cbase.cpp create mode 100644 ricochet/dlls/cbase.h create mode 100644 ricochet/dlls/cdll_dll.h create mode 100644 ricochet/dlls/client.cpp create mode 100644 ricochet/dlls/client.h create mode 100644 ricochet/dlls/combat.cpp create mode 100644 ricochet/dlls/decals.h create mode 100644 ricochet/dlls/disc_arena.cpp create mode 100644 ricochet/dlls/disc_arena.h create mode 100644 ricochet/dlls/disc_objects.h create mode 100644 ricochet/dlls/disc_powerups.cpp create mode 100644 ricochet/dlls/disc_weapon.h create mode 100644 ricochet/dlls/discwar.h create mode 100644 ricochet/dlls/doors.cpp create mode 100644 ricochet/dlls/doors.h create mode 100644 ricochet/dlls/effects.cpp create mode 100644 ricochet/dlls/effects.h create mode 100644 ricochet/dlls/enginecallback.h create mode 100644 ricochet/dlls/explode.cpp create mode 100644 ricochet/dlls/explode.h create mode 100644 ricochet/dlls/extdll.h create mode 100644 ricochet/dlls/func_break.cpp create mode 100644 ricochet/dlls/func_break.h create mode 100644 ricochet/dlls/func_tank.cpp create mode 100644 ricochet/dlls/game.cpp create mode 100644 ricochet/dlls/game.h create mode 100644 ricochet/dlls/gamerules.cpp create mode 100644 ricochet/dlls/gamerules.h create mode 100644 ricochet/dlls/ggrenade.cpp create mode 100644 ricochet/dlls/globals.cpp create mode 100644 ricochet/dlls/h_ai.cpp create mode 100644 ricochet/dlls/h_battery.cpp create mode 100644 ricochet/dlls/h_cycler.cpp create mode 100644 ricochet/dlls/h_export.cpp create mode 100644 ricochet/dlls/healthkit.cpp create mode 100644 ricochet/dlls/items.cpp create mode 100644 ricochet/dlls/items.h create mode 100644 ricochet/dlls/lights.cpp create mode 100644 ricochet/dlls/maprules.cpp create mode 100644 ricochet/dlls/maprules.h create mode 100644 ricochet/dlls/monsterevent.h create mode 100644 ricochet/dlls/monsters.h create mode 100644 ricochet/dlls/mortar.cpp create mode 100644 ricochet/dlls/mp.def create mode 100644 ricochet/dlls/mp.dsp create mode 100644 ricochet/dlls/mpstubb.cpp create mode 100644 ricochet/dlls/multiplay_gamerules.cpp create mode 100644 ricochet/dlls/nodes.h create mode 100644 ricochet/dlls/observer.cpp create mode 100644 ricochet/dlls/pathcorner.cpp create mode 100644 ricochet/dlls/plane.cpp create mode 100644 ricochet/dlls/plane.h create mode 100644 ricochet/dlls/plats.cpp create mode 100644 ricochet/dlls/player.cpp create mode 100644 ricochet/dlls/player.h create mode 100644 ricochet/dlls/saverestore.h create mode 100644 ricochet/dlls/schedule.h create mode 100644 ricochet/dlls/scriptevent.h create mode 100644 ricochet/dlls/singleplay_gamerules.cpp create mode 100644 ricochet/dlls/skill.cpp create mode 100644 ricochet/dlls/skill.h create mode 100644 ricochet/dlls/sound.cpp create mode 100644 ricochet/dlls/soundent.cpp create mode 100644 ricochet/dlls/soundent.h create mode 100644 ricochet/dlls/spectator.cpp create mode 100644 ricochet/dlls/spectator.h create mode 100644 ricochet/dlls/subs.cpp create mode 100644 ricochet/dlls/talkmonster.h create mode 100644 ricochet/dlls/teamplay_gamerules.cpp create mode 100644 ricochet/dlls/teamplay_gamerules.h create mode 100644 ricochet/dlls/trains.h create mode 100644 ricochet/dlls/triggers.cpp create mode 100644 ricochet/dlls/util.cpp create mode 100644 ricochet/dlls/util.h create mode 100644 ricochet/dlls/vector.h create mode 100644 ricochet/dlls/weapons.cpp create mode 100644 ricochet/dlls/weapons.h create mode 100644 ricochet/dlls/world.cpp create mode 100644 ricochet/dlls/wpn_shared/disc_weapon_disc.cpp create mode 100644 ricochet/dlls/xen.cpp create mode 100644 ricochet/pm_shared/pm_debug.c create mode 100644 ricochet/pm_shared/pm_debug.h create mode 100644 ricochet/pm_shared/pm_defs.h create mode 100644 ricochet/pm_shared/pm_info.h create mode 100644 ricochet/pm_shared/pm_materials.h create mode 100644 ricochet/pm_shared/pm_math.c create mode 100644 ricochet/pm_shared/pm_movevars.h create mode 100644 ricochet/pm_shared/pm_shared.c create mode 100644 ricochet/pm_shared/pm_shared.h create mode 100644 utils/bspinfo/bspinfo.c create mode 100644 utils/bspinfo/bspinfo.dsp create mode 100644 utils/bspinfo/bspinfo.dsw create mode 100644 utils/common/bspfile.c create mode 100644 utils/common/bspfile.h create mode 100644 utils/common/bsplib.c create mode 100644 utils/common/bsplib.h create mode 100644 utils/common/cmdlib.c create mode 100644 utils/common/cmdlib.h create mode 100644 utils/common/l3dslib.c create mode 100644 utils/common/l3dslib.h create mode 100644 utils/common/lbmlib.c create mode 100644 utils/common/lbmlib.h create mode 100644 utils/common/mathlib.c create mode 100644 utils/common/mathlib.h create mode 100644 utils/common/meter.c create mode 100644 utils/common/meter.h create mode 100644 utils/common/movie.h create mode 100644 utils/common/polylib.c create mode 100644 utils/common/polylib.h create mode 100644 utils/common/scriplib.c create mode 100644 utils/common/scriplib.h create mode 100644 utils/common/threads.c create mode 100644 utils/common/threads.h create mode 100644 utils/common/trilib.c create mode 100644 utils/common/trilib.h create mode 100644 utils/common/wadlib.c create mode 100644 utils/common/wadlib.h create mode 100644 utils/light/light.c create mode 100644 utils/light/light.dsp create mode 100644 utils/light/light.h create mode 100644 utils/light/ltface.c create mode 100644 utils/light/new/light.c create mode 100644 utils/light/new/light.h create mode 100644 utils/light/new/ltface.c create mode 100644 utils/light/new/trace.c create mode 100644 utils/light/trace.c create mode 100644 utils/makefont/makefont.cpp create mode 100644 utils/makefont/makefont.dsp create mode 100644 utils/makefont/makefont.dsw create mode 100644 utils/makels/makels.cpp create mode 100644 utils/makels/makels.dsp create mode 100644 utils/makels/makels.dsw create mode 100644 utils/makels/makels.mak create mode 100644 utils/mdlviewer/mdlviewer.cpp create mode 100644 utils/mdlviewer/mdlviewer.dsp create mode 100644 utils/mdlviewer/mdlviewer.dsw create mode 100644 utils/mdlviewer/mdlviewer.h create mode 100644 utils/mdlviewer/studio_render.cpp create mode 100644 utils/mdlviewer/studio_utils.cpp create mode 100644 utils/mkmovie/mkmovie.c create mode 100644 utils/mkmovie/mkmovie.dsp create mode 100644 utils/procinfo/lib/win32_vc6/procinfo.lib create mode 100644 utils/procinfo/procinfo.cpp create mode 100644 utils/procinfo/procinfo.dsp create mode 100644 utils/procinfo/procinfo.h create mode 100644 utils/qbsp2/bsp5.h create mode 100644 utils/qbsp2/cull.c create mode 100644 utils/qbsp2/gldraw.c create mode 100644 utils/qbsp2/makefile create mode 100644 utils/qbsp2/merge.c create mode 100644 utils/qbsp2/nodraw.c create mode 100644 utils/qbsp2/outside.c create mode 100644 utils/qbsp2/portals.c create mode 100644 utils/qbsp2/qbsp.c create mode 100644 utils/qbsp2/qbsp2.dsp create mode 100644 utils/qbsp2/qbsp2.dsw create mode 100644 utils/qbsp2/solidbsp.c create mode 100644 utils/qbsp2/surfaces.c create mode 100644 utils/qbsp2/tjunc.c create mode 100644 utils/qbsp2/writebsp.c create mode 100644 utils/qcsg/brush.c create mode 100644 utils/qcsg/csg.h create mode 100644 utils/qcsg/gldraw.c create mode 100644 utils/qcsg/hullfile.c create mode 100644 utils/qcsg/hulls.txt create mode 100644 utils/qcsg/map.c create mode 100644 utils/qcsg/qcsg.c create mode 100644 utils/qcsg/qcsg.dsp create mode 100644 utils/qcsg/qcsg.dsw create mode 100644 utils/qcsg/source.p0 create mode 100644 utils/qcsg/source.p1 create mode 100644 utils/qcsg/source.p2 create mode 100644 utils/qcsg/textures.c create mode 100644 utils/qlumpy/qlumpy.c create mode 100644 utils/qlumpy/qlumpy.doc create mode 100644 utils/qlumpy/qlumpy.dsp create mode 100644 utils/qlumpy/qlumpy.dsw create mode 100644 utils/qlumpy/qlumpy.h create mode 100644 utils/qlumpy/quakegrb.c create mode 100644 utils/qrad/lightmap.c create mode 100644 utils/qrad/qrad.c create mode 100644 utils/qrad/qrad.dsp create mode 100644 utils/qrad/qrad.dsw create mode 100644 utils/qrad/qrad.h create mode 100644 utils/qrad/qrad.txt create mode 100644 utils/qrad/trace.c create mode 100644 utils/qrad/vismat.c create mode 100644 utils/serverctrl/ServerCtrl.cpp create mode 100644 utils/serverctrl/ServerCtrl.h create mode 100644 utils/serverctrl/ServerCtrl.rc create mode 100644 utils/serverctrl/ServerCtrlDlg.cpp create mode 100644 utils/serverctrl/ServerCtrlDlg.h create mode 100644 utils/serverctrl/StdAfx.cpp create mode 100644 utils/serverctrl/StdAfx.h create mode 100644 utils/serverctrl/res/serverctrl.ico create mode 100644 utils/serverctrl/res/serverctrl.rc2 create mode 100644 utils/serverctrl/resource.h create mode 100644 utils/serverctrl/serverctrl.dsp create mode 100644 utils/smdlexp/smdlexp.cpp create mode 100644 utils/smdlexp/smdlexp.def create mode 100644 utils/smdlexp/smdlexp.dsp create mode 100644 utils/smdlexp/smdlexp.dsw create mode 100644 utils/smdlexp/smdlexp.rc create mode 100644 utils/smdlexp/smedefs.h create mode 100644 utils/smdlexp/smexprc.h create mode 100644 utils/sprgen/s_bubble.spr create mode 100644 utils/sprgen/s_explod.spr create mode 100644 utils/sprgen/s_light.spr create mode 100644 utils/sprgen/sprgen.c create mode 100644 utils/sprgen/sprgen.dsp create mode 100644 utils/sprgen/sprgen.dsw create mode 100644 utils/sprgen/spritegn.h create mode 100644 utils/studiomdl/bmpread.c create mode 100644 utils/studiomdl/studiomdl.c create mode 100644 utils/studiomdl/studiomdl.dsp create mode 100644 utils/studiomdl/studiomdl.dsw create mode 100644 utils/studiomdl/studiomdl.h create mode 100644 utils/studiomdl/tristrip.c create mode 100644 utils/studiomdl/write.c create mode 100644 utils/vgui/include/VGUI.h create mode 100644 utils/vgui/include/VGUI_ActionSignal.h create mode 100644 utils/vgui/include/VGUI_App.h create mode 100644 utils/vgui/include/VGUI_Bitmap.h create mode 100644 utils/vgui/include/VGUI_BitmapTGA.h create mode 100644 utils/vgui/include/VGUI_Border.h create mode 100644 utils/vgui/include/VGUI_BorderLayout.h create mode 100644 utils/vgui/include/VGUI_BorderPair.h create mode 100644 utils/vgui/include/VGUI_BuildGroup.h create mode 100644 utils/vgui/include/VGUI_Button.h create mode 100644 utils/vgui/include/VGUI_ButtonController.h create mode 100644 utils/vgui/include/VGUI_ButtonGroup.h create mode 100644 utils/vgui/include/VGUI_ChangeSignal.h create mode 100644 utils/vgui/include/VGUI_CheckButton.h create mode 100644 utils/vgui/include/VGUI_Color.h create mode 100644 utils/vgui/include/VGUI_ComboKey.h create mode 100644 utils/vgui/include/VGUI_ConfigWizard.h create mode 100644 utils/vgui/include/VGUI_Cursor.h create mode 100644 utils/vgui/include/VGUI_Dar.h create mode 100644 utils/vgui/include/VGUI_DataInputStream.h create mode 100644 utils/vgui/include/VGUI_Desktop.h create mode 100644 utils/vgui/include/VGUI_DesktopIcon.h create mode 100644 utils/vgui/include/VGUI_EditPanel.h create mode 100644 utils/vgui/include/VGUI_EtchedBorder.h create mode 100644 utils/vgui/include/VGUI_FileInputStream.h create mode 100644 utils/vgui/include/VGUI_FlowLayout.h create mode 100644 utils/vgui/include/VGUI_FocusChangeSignal.h create mode 100644 utils/vgui/include/VGUI_FocusNavGroup.h create mode 100644 utils/vgui/include/VGUI_Font.h create mode 100644 utils/vgui/include/VGUI_Frame.h create mode 100644 utils/vgui/include/VGUI_FrameSignal.h create mode 100644 utils/vgui/include/VGUI_GridLayout.h create mode 100644 utils/vgui/include/VGUI_HeaderPanel.h create mode 100644 utils/vgui/include/VGUI_Image.h create mode 100644 utils/vgui/include/VGUI_ImagePanel.h create mode 100644 utils/vgui/include/VGUI_InputSignal.h create mode 100644 utils/vgui/include/VGUI_InputStream.h create mode 100644 utils/vgui/include/VGUI_IntChangeSignal.h create mode 100644 utils/vgui/include/VGUI_IntLabel.h create mode 100644 utils/vgui/include/VGUI_KeyCode.h create mode 100644 utils/vgui/include/VGUI_Label.h create mode 100644 utils/vgui/include/VGUI_Layout.h create mode 100644 utils/vgui/include/VGUI_LayoutInfo.h create mode 100644 utils/vgui/include/VGUI_LineBorder.h create mode 100644 utils/vgui/include/VGUI_ListPanel.h create mode 100644 utils/vgui/include/VGUI_LoweredBorder.h create mode 100644 utils/vgui/include/VGUI_Menu.h create mode 100644 utils/vgui/include/VGUI_MenuItem.h create mode 100644 utils/vgui/include/VGUI_MenuSeparator.h create mode 100644 utils/vgui/include/VGUI_MessageBox.h create mode 100644 utils/vgui/include/VGUI_MiniApp.h create mode 100644 utils/vgui/include/VGUI_MouseCode.h create mode 100644 utils/vgui/include/VGUI_Panel.h create mode 100644 utils/vgui/include/VGUI_Point.h create mode 100644 utils/vgui/include/VGUI_PopupMenu.h create mode 100644 utils/vgui/include/VGUI_ProgressBar.h create mode 100644 utils/vgui/include/VGUI_RadioButton.h create mode 100644 utils/vgui/include/VGUI_RaisedBorder.h create mode 100644 utils/vgui/include/VGUI_RepaintSignal.h create mode 100644 utils/vgui/include/VGUI_Scheme.h create mode 100644 utils/vgui/include/VGUI_ScrollBar.h create mode 100644 utils/vgui/include/VGUI_ScrollPanel.h create mode 100644 utils/vgui/include/VGUI_Slider.h create mode 100644 utils/vgui/include/VGUI_StackLayout.h create mode 100644 utils/vgui/include/VGUI_String.h create mode 100644 utils/vgui/include/VGUI_Surface.h create mode 100644 utils/vgui/include/VGUI_SurfaceBase.h create mode 100644 utils/vgui/include/VGUI_SurfaceGL.h create mode 100644 utils/vgui/include/VGUI_TabPanel.h create mode 100644 utils/vgui/include/VGUI_TablePanel.h create mode 100644 utils/vgui/include/VGUI_TaskBar.h create mode 100644 utils/vgui/include/VGUI_TextEntry.h create mode 100644 utils/vgui/include/VGUI_TextGrid.h create mode 100644 utils/vgui/include/VGUI_TextImage.h create mode 100644 utils/vgui/include/VGUI_TextPanel.h create mode 100644 utils/vgui/include/VGUI_TickSignal.h create mode 100644 utils/vgui/include/VGUI_ToggleButton.h create mode 100644 utils/vgui/include/VGUI_TreeFolder.h create mode 100644 utils/vgui/include/VGUI_WizardPanel.h create mode 100644 utils/vgui/lib/win32_vc6/vgui.lib create mode 100644 utils/visx2/flow.c create mode 100644 utils/visx2/soundpvs.c create mode 100644 utils/visx2/vis.c create mode 100644 utils/visx2/vis.dsp create mode 100644 utils/visx2/vis.dsw create mode 100644 utils/visx2/vis.h create mode 100644 utils/xwad/xwad.c create mode 100644 utils/xwad/xwad.dsp diff --git a/cl_dll/Exports.h b/cl_dll/Exports.h new file mode 100644 index 0000000..cc66e15 --- /dev/null +++ b/cl_dll/Exports.h @@ -0,0 +1,117 @@ +// CL_DLLEXPORT is the client version of dllexport. It's turned off for secure clients. +#ifdef _WIN32 +#define CL_DLLEXPORT __declspec(dllexport) +#else +#define CL_DLLEXPORT __attribute__ ((visibility("default"))) +#endif + +extern "C" +{ + // From hl_weapons + void CL_DLLEXPORT HUD_PostRunCmd( struct local_state_s *from, struct local_state_s *to, struct usercmd_s *cmd, int runfuncs, double time, unsigned int random_seed ); + + // From cdll_int + int CL_DLLEXPORT Initialize( cl_enginefunc_t *pEnginefuncs, int iVersion ); + int CL_DLLEXPORT HUD_VidInit( void ); + void CL_DLLEXPORT HUD_Init( void ); + int CL_DLLEXPORT HUD_Redraw( float flTime, int intermission ); + int CL_DLLEXPORT HUD_UpdateClientData( client_data_t *cdata, float flTime ); + void CL_DLLEXPORT HUD_Reset ( void ); + void CL_DLLEXPORT HUD_PlayerMove( struct playermove_s *ppmove, int server ); + void CL_DLLEXPORT HUD_PlayerMoveInit( struct playermove_s *ppmove ); + char CL_DLLEXPORT HUD_PlayerMoveTexture( char *name ); + int CL_DLLEXPORT HUD_ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); + int CL_DLLEXPORT HUD_GetHullBounds( int hullnumber, float *mins, float *maxs ); + void CL_DLLEXPORT HUD_Frame( double time ); + void CL_DLLEXPORT HUD_VoiceStatus(int entindex, qboolean bTalking); + void CL_DLLEXPORT HUD_DirectorMessage( int iSize, void *pbuf ); + void CL_DLLEXPORT HUD_ChatInputPosition( int *x, int *y ); + + // From demo + void CL_DLLEXPORT Demo_ReadBuffer( int size, unsigned char *buffer ); + + // From entity + int CL_DLLEXPORT HUD_AddEntity( int type, struct cl_entity_s *ent, const char *modelname ); + void CL_DLLEXPORT HUD_CreateEntities( void ); + void CL_DLLEXPORT HUD_StudioEvent( const struct mstudioevent_s *event, const struct cl_entity_s *entity ); + void CL_DLLEXPORT HUD_TxferLocalOverrides( struct entity_state_s *state, const struct clientdata_s *client ); + void CL_DLLEXPORT HUD_ProcessPlayerState( struct entity_state_s *dst, const struct entity_state_s *src ); + void CL_DLLEXPORT HUD_TxferPredictionData ( struct entity_state_s *ps, const struct entity_state_s *pps, struct clientdata_s *pcd, const struct clientdata_s *ppcd, struct weapon_data_s *wd, const struct weapon_data_s *pwd ); + void CL_DLLEXPORT HUD_TempEntUpdate( double frametime, double client_time, double cl_gravity, struct tempent_s **ppTempEntFree, struct tempent_s **ppTempEntActive, int ( *Callback_AddVisibleEntity )( struct cl_entity_s *pEntity ), void ( *Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp ) ); + struct cl_entity_s CL_DLLEXPORT *HUD_GetUserEntity( int index ); + + // From in_camera + void CL_DLLEXPORT CAM_Think( void ); + int CL_DLLEXPORT CL_IsThirdPerson( void ); + void CL_DLLEXPORT CL_CameraOffset( float *ofs ); + + // From input + struct kbutton_s CL_DLLEXPORT *KB_Find( const char *name ); + void CL_DLLEXPORT CL_CreateMove ( float frametime, struct usercmd_s *cmd, int active ); + void CL_DLLEXPORT HUD_Shutdown( void ); + int CL_DLLEXPORT HUD_Key_Event( int eventcode, int keynum, const char *pszCurrentBinding ); + + // From inputw32 + void CL_DLLEXPORT IN_ActivateMouse( void ); + void CL_DLLEXPORT IN_DeactivateMouse( void ); + void CL_DLLEXPORT IN_MouseEvent (int mstate); + void CL_DLLEXPORT IN_Accumulate (void); + void CL_DLLEXPORT IN_ClearStates (void); + + // From tri + void CL_DLLEXPORT HUD_DrawNormalTriangles( void ); + void CL_DLLEXPORT HUD_DrawTransparentTriangles( void ); + + // From view + void CL_DLLEXPORT V_CalcRefdef( struct ref_params_s *pparams ); + + // From GameStudioModelRenderer + int CL_DLLEXPORT HUD_GetStudioModelInterface( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio ); +} + +/* +extern cldll_func_dst_t *g_pcldstAddrs; + +// Macros for the client receiving calls from the engine +#define RecClInitialize(a, b) (g_pcldstAddrs->pInitFunc(&a, &b)) +#define RecClHudInit() (g_pcldstAddrs->pHudInitFunc()) +#define RecClHudVidInit() (g_pcldstAddrs->pHudVidInitFunc()) +#define RecClHudRedraw(a, b) (g_pcldstAddrs->pHudRedrawFunc(&a, &b)) +#define RecClHudUpdateClientData(a, b) (g_pcldstAddrs->pHudUpdateClientDataFunc(&a, &b)) +#define RecClHudReset() (g_pcldstAddrs->pHudResetFunc()) +#define RecClClientMove(a, b) (g_pcldstAddrs->pClientMove(&a, &b)) +#define RecClClientMoveInit(a) (g_pcldstAddrs->pClientMoveInit(&a)) +#define RecClClientTextureType(a) (g_pcldstAddrs->pClientTextureType(&a)) +#define RecClIN_ActivateMouse() (g_pcldstAddrs->pIN_ActivateMouse()) +#define RecClIN_DeactivateMouse() (g_pcldstAddrs->pIN_DeactivateMouse()) +#define RecClIN_MouseEvent(a) (g_pcldstAddrs->pIN_MouseEvent(&a)) +#define RecClIN_ClearStates() (g_pcldstAddrs->pIN_ClearStates()) +#define RecClIN_Accumulate() (g_pcldstAddrs->pIN_Accumulate()) +#define RecClCL_CreateMove(a, b, c) (g_pcldstAddrs->pCL_CreateMove(&a, &b, &c)) +#define RecClCL_IsThirdPerson() (g_pcldstAddrs->pCL_IsThirdPerson()) +#define RecClCL_GetCameraOffsets(a) (g_pcldstAddrs->pCL_GetCameraOffsets(&a)) +#define RecClFindKey(a) (g_pcldstAddrs->pFindKey(&a)) +#define RecClCamThink() (g_pcldstAddrs->pCamThink()) +#define RecClCalcRefdef(a) (g_pcldstAddrs->pCalcRefdef(&a)) +#define RecClAddEntity(a, b, c) (g_pcldstAddrs->pAddEntity(&a, &b, &c)) +#define RecClCreateEntities() (g_pcldstAddrs->pCreateEntities()) +#define RecClDrawNormalTriangles() (g_pcldstAddrs->pDrawNormalTriangles()) +#define RecClDrawTransparentTriangles() (g_pcldstAddrs->pDrawTransparentTriangles()) +#define RecClStudioEvent(a, b) (g_pcldstAddrs->pStudioEvent(&a, &b)) +#define RecClPostRunCmd(a, b, c, d, e, f) (g_pcldstAddrs->pPostRunCmd(&a, &b, &c, &d, &e, &f)) +#define RecClShutdown() (g_pcldstAddrs->pShutdown()) +#define RecClTxferLocalOverrides(a, b) (g_pcldstAddrs->pTxferLocalOverrides(&a, &b)) +#define RecClProcessPlayerState(a, b) (g_pcldstAddrs->pProcessPlayerState(&a, &b)) +#define RecClTxferPredictionData(a, b, c, d, e, f) (g_pcldstAddrs->pTxferPredictionData(&a, &b, &c, &d, &e, &f)) +#define RecClReadDemoBuffer(a, b) (g_pcldstAddrs->pReadDemoBuffer(&a, &b)) +#define RecClConnectionlessPacket(a, b, c, d) (g_pcldstAddrs->pConnectionlessPacket(&a, &b, &c, &d)) +#define RecClGetHullBounds(a, b, c) (g_pcldstAddrs->pGetHullBounds(&a, &b, &c)) +#define RecClHudFrame(a) (g_pcldstAddrs->pHudFrame(&a)) +#define RecClKeyEvent(a, b, c) (g_pcldstAddrs->pKeyEvent(&a, &b, &c)) +#define RecClTempEntUpdate(a, b, c, d, e, f, g) (g_pcldstAddrs->pTempEntUpdate(&a, &b, &c, &d, &e, &f, &g)) +#define RecClGetUserEntity(a) (g_pcldstAddrs->pGetUserEntity(&a)) +#define RecClVoiceStatus(a, b) (g_pcldstAddrs->pVoiceStatus(&a, &b)) +#define RecClDirectorMessage(a, b) (g_pcldstAddrs->pDirectorMessage(&a, &b)) +#define RecClStudioInterface(a, b, c) (g_pcldstAddrs->pStudioInterface(&a, &b, &c)) +#define RecClChatInputPosition(a, b) (g_pcldstAddrs->pChatInputPosition(&a, &b)) +*/ \ No newline at end of file diff --git a/cl_dll/GameStudioModelRenderer.cpp b/cl_dll/GameStudioModelRenderer.cpp new file mode 100644 index 0000000..304630f --- /dev/null +++ b/cl_dll/GameStudioModelRenderer.cpp @@ -0,0 +1,121 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include "studio.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "dlight.h" +#include "triangleapi.h" + +#include +#include +#include +#include + +#include "studio_util.h" +#include "r_studioint.h" + +#include "StudioModelRenderer.h" +#include "GameStudioModelRenderer.h" +#include "Exports.h" + +// +// Override the StudioModelRender virtual member functions here to implement custom bone +// setup, blending, etc. +// + +// Global engine <-> studio model rendering code interface +extern engine_studio_api_t IEngineStudio; + +// The renderer object, created on the stack. +CGameStudioModelRenderer g_StudioRenderer; +/* +==================== +CGameStudioModelRenderer + +==================== +*/ +CGameStudioModelRenderer::CGameStudioModelRenderer( void ) +{ +} + +//////////////////////////////////// +// Hooks to class implementation +//////////////////////////////////// + +/* +==================== +R_StudioDrawPlayer + +==================== +*/ +int R_StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + return g_StudioRenderer.StudioDrawPlayer( flags, pplayer ); +} + +/* +==================== +R_StudioDrawModel + +==================== +*/ +int R_StudioDrawModel( int flags ) +{ + return g_StudioRenderer.StudioDrawModel( flags ); +} + +/* +==================== +R_StudioInit + +==================== +*/ +void R_StudioInit( void ) +{ + g_StudioRenderer.Init(); +} + +// The simple drawing interface we'll pass back to the engine +r_studio_interface_t studio = +{ + STUDIO_INTERFACE_VERSION, + R_StudioDrawModel, + R_StudioDrawPlayer, +}; + +/* +==================== +HUD_GetStudioModelInterface + +Export this function for the engine to use the studio renderer class to render objects. +==================== +*/ +int CL_DLLEXPORT HUD_GetStudioModelInterface( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio ) +{ +// RecClStudioInterface(version, ppinterface, pstudio); + + if ( version != STUDIO_INTERFACE_VERSION ) + return 0; + + // Point the engine to our callbacks + *ppinterface = &studio; + + // Copy in engine helper functions + memcpy( &IEngineStudio, pstudio, sizeof( IEngineStudio ) ); + + // Initialize local variables, etc. + R_StudioInit(); + + // Success + return 1; +} diff --git a/cl_dll/GameStudioModelRenderer.h b/cl_dll/GameStudioModelRenderer.h new file mode 100644 index 0000000..7d06f70 --- /dev/null +++ b/cl_dll/GameStudioModelRenderer.h @@ -0,0 +1,26 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( GAMESTUDIOMODELRENDERER_H ) +#define GAMESTUDIOMODELRENDERER_H +#if defined( _WIN32 ) +#pragma once +#endif + +/* +==================== +CGameStudioModelRenderer + +==================== +*/ +class CGameStudioModelRenderer : public CStudioModelRenderer +{ +public: + CGameStudioModelRenderer( void ); +}; + +#endif // GAMESTUDIOMODELRENDERER_H \ No newline at end of file diff --git a/cl_dll/GameStudioModelRenderer_Sample.cpp b/cl_dll/GameStudioModelRenderer_Sample.cpp new file mode 100644 index 0000000..e46d237 --- /dev/null +++ b/cl_dll/GameStudioModelRenderer_Sample.cpp @@ -0,0 +1,992 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include "studio.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "dlight.h" +#include "triangleapi.h" + +#include +#include +#include +#include + +#include "studio_util.h" +#include "r_studioint.h" + +#include "StudioModelRenderer.h" +#include "GameStudioModelRenderer.h" + +// Predicted values saved off in hl_weapons.cpp +void Game_GetSequence( int *seq, int *gaitseq ); +void Game_GetOrientation( float *o, float *a ); + +float g_flStartScaleTime; +int iPrevRenderState; +int iRenderStateChanged; + +// Global engine <-> studio model rendering code interface +extern engine_studio_api_t IEngineStudio; + +typedef struct +{ + vec3_t origin; + vec3_t angles; + + vec3_t realangles; + + float animtime; + float frame; + int sequence; + int gaitsequence; + float framerate; + + int m_fSequenceLoops; + int m_fSequenceFinished; + + byte controller[ 4 ]; + byte blending[ 2 ]; + + latchedvars_t lv; +} client_anim_state_t; + +static client_anim_state_t g_state; +static client_anim_state_t g_clientstate; + +// The renderer object, created on the stack. +CGameStudioModelRenderer g_StudioRenderer; +/* +==================== +CGameStudioModelRenderer + +==================== +*/ +CGameStudioModelRenderer::CGameStudioModelRenderer( void ) +{ + // If you want to predict animations locally, set this to TRUE + // NOTE: The animation code is somewhat broken, but gives you a sense for how + // to do client side animation of the predicted player in a third person game. + m_bLocal = false; +} + +/* +==================== +StudioSetupBones + +==================== +*/ +void CGameStudioModelRenderer::StudioSetupBones ( void ) +{ + int i; + double f; + + mstudiobone_t *pbones; + mstudioseqdesc_t *pseqdesc; + mstudioanim_t *panim; + + static float pos[MAXSTUDIOBONES][3]; + static vec4_t q[MAXSTUDIOBONES]; + float bonematrix[3][4]; + + static float pos2[MAXSTUDIOBONES][3]; + static vec4_t q2[MAXSTUDIOBONES]; + static float pos3[MAXSTUDIOBONES][3]; + static vec4_t q3[MAXSTUDIOBONES]; + static float pos4[MAXSTUDIOBONES][3]; + static vec4_t q4[MAXSTUDIOBONES]; + + // Use default bone setup for nonplayers + if ( !m_pCurrentEntity->player ) + { + CStudioModelRenderer::StudioSetupBones(); + return; + } + + // Bound sequence number. + if ( m_pCurrentEntity->curstate.sequence >= m_pStudioHeader->numseq ) + { + m_pCurrentEntity->curstate.sequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + if ( m_pPlayerInfo && m_pPlayerInfo->gaitsequence != 0 ) + { + f = m_pPlayerInfo->gaitframe; + } + else + { + f = StudioEstimateFrame( pseqdesc ); + } + + // This game knows how to do three way blending + if ( pseqdesc->numblends == 3 ) + { + float s; + + // Get left anim + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + + // Blending is 0-127 == Left to Middle, 128 to 255 == Middle to right + if ( m_pCurrentEntity->curstate.blending[0] <= 127 ) + { + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + + // Scale 0-127 blending up to 0-255 + s = m_pCurrentEntity->curstate.blending[0]; + s = ( s * 2.0 ); + } + else + { + + // Skip ahead to middle + panim += m_pStudioHeader->numbones; + + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + + // Scale 127-255 blending up to 0-255 + s = m_pCurrentEntity->curstate.blending[0]; + s = 2.0 * ( s - 127.0 ); + } + + // Normalize interpolant + s /= 255.0; + + // Go to middle or right + panim += m_pStudioHeader->numbones; + + StudioCalcRotations( pos2, q2, pseqdesc, panim, f ); + + // Spherically interpolate the bones + StudioSlerpBones( q, pos, q2, pos2, s ); + } + else + { + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + } + + // Are we in the process of transitioning from one sequence to another. + if ( m_fDoInterp && + m_pCurrentEntity->latched.sequencetime && + ( m_pCurrentEntity->latched.sequencetime + 0.2 > m_clTime ) && + ( m_pCurrentEntity->latched.prevsequence < m_pStudioHeader->numseq )) + { + // blend from last sequence + static float pos1b[MAXSTUDIOBONES][3]; + static vec4_t q1b[MAXSTUDIOBONES]; + float s; + + // Blending value into last sequence + unsigned char prevseqblending = m_pCurrentEntity->latched.prevseqblending[ 0 ]; + + // Point at previous sequenece + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->latched.prevsequence; + + // Know how to do three way blends + if ( pseqdesc->numblends == 3 ) + { + float s; + + // Get left animation + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + + if ( prevseqblending <= 127 ) + { + // Set up bones based on final frame of previous sequence + StudioCalcRotations( pos1b, q1b, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + s = prevseqblending; + s = ( s * 2.0 ); + } + else + { + // Skip to middle blend + panim += m_pStudioHeader->numbones; + + StudioCalcRotations( pos1b, q1b, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + s = prevseqblending; + s = 2.0 * ( s - 127.0 ); + } + + // Normalize + s /= 255.0; + + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos2, q2, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + // Interpolate bones + StudioSlerpBones( q1b, pos1b, q2, pos2, s ); + } + else + { + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + // clip prevframe + StudioCalcRotations( pos1b, q1b, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + } + + // Now blend last frame of previous sequence with current sequence. + s = 1.0 - (m_clTime - m_pCurrentEntity->latched.sequencetime) / 0.2; + StudioSlerpBones( q, pos, q1b, pos1b, s ); + } + else + { + m_pCurrentEntity->latched.prevframe = f; + } + + // Now convert quaternions and bone positions into matrices + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + QuaternionMatrix( q[i], bonematrix ); + + bonematrix[0][3] = pos[i][0]; + bonematrix[1][3] = pos[i][1]; + bonematrix[2][3] = pos[i][2]; + + if (pbones[i].parent == -1) + { + if ( IEngineStudio.IsHardware() ) + { + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + } + else + { + ConcatTransforms ((*m_paliastransform), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + } + + // Apply client-side effects to the transformation matrix + StudioFxTransform( m_pCurrentEntity, (*m_pbonetransform)[i] ); + } + else + { + ConcatTransforms ((*m_pbonetransform)[pbones[i].parent], bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_plighttransform)[pbones[i].parent], bonematrix, (*m_plighttransform)[i]); + } + } +} + +/* +==================== +StudioEstimateGait + +==================== +*/ +void CGameStudioModelRenderer::StudioEstimateGait( entity_state_t *pplayer ) +{ + float dt; + vec3_t est_velocity; + + dt = (m_clTime - m_clOldTime); + dt = max( 0.0, dt ); + dt = min( 1.0, dt ); + + if (dt == 0 || m_pPlayerInfo->renderframe == m_nFrameCount) + { + m_flGaitMovement = 0; + return; + } + + // VectorAdd( pplayer->velocity, pplayer->prediction_error, est_velocity ); + if ( m_fGaitEstimation ) + { + VectorSubtract( m_pCurrentEntity->origin, m_pPlayerInfo->prevgaitorigin, est_velocity ); + VectorCopy( m_pCurrentEntity->origin, m_pPlayerInfo->prevgaitorigin ); + m_flGaitMovement = Length( est_velocity ); + if (dt <= 0 || m_flGaitMovement / dt < 5) + { + m_flGaitMovement = 0; + est_velocity[0] = 0; + est_velocity[1] = 0; + } + } + else + { + VectorCopy( pplayer->velocity, est_velocity ); + m_flGaitMovement = Length( est_velocity ) * dt; + } + + if (est_velocity[1] == 0 && est_velocity[0] == 0) + { + float flYawDiff = m_pCurrentEntity->angles[YAW] - m_pPlayerInfo->gaityaw; + flYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360; + if (flYawDiff > 180) + flYawDiff -= 360; + if (flYawDiff < -180) + flYawDiff += 360; + + if (dt < 0.25) + flYawDiff *= dt * 4; + else + flYawDiff *= dt; + + m_pPlayerInfo->gaityaw += flYawDiff; + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - (int)(m_pPlayerInfo->gaityaw / 360) * 360; + + m_flGaitMovement = 0; + } + else + { + m_pPlayerInfo->gaityaw = (atan2(est_velocity[1], est_velocity[0]) * 180 / M_PI); + if (m_pPlayerInfo->gaityaw > 180) + m_pPlayerInfo->gaityaw = 180; + if (m_pPlayerInfo->gaityaw < -180) + m_pPlayerInfo->gaityaw = -180; + } + +} + +/* +==================== +StudioProcessGait + +==================== +*/ +void CGameStudioModelRenderer::StudioProcessGait( entity_state_t *pplayer ) +{ + mstudioseqdesc_t *pseqdesc; + float dt; + float flYaw; // view direction relative to movement + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + m_pCurrentEntity->angles[PITCH] = 0; + m_pCurrentEntity->latched.prevangles[PITCH] = m_pCurrentEntity->angles[PITCH]; + + dt = (m_clTime - m_clOldTime); + dt = max( 0.0, dt ); + dt = min( 1.0, dt ); + + StudioEstimateGait( pplayer ); + + // calc side to side turning + flYaw = m_pCurrentEntity->angles[YAW] - m_pPlayerInfo->gaityaw; + + flYaw = fmod( flYaw, 360.0 ); + + if (flYaw < -180) + { + flYaw = flYaw + 360; + } + else if (flYaw > 180) + { + flYaw = flYaw - 360; + } + + float maxyaw = 120.0; + + if (flYaw > maxyaw) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - 180; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw - 180; + } + else if (flYaw < -maxyaw) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw + 180; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw + 180; + } + + float blend_yaw = ( flYaw / 90.0 ) * 128.0 + 127.0; + blend_yaw = min( 255.0, blend_yaw ); + blend_yaw = max( 0.0, blend_yaw ); + + blend_yaw = 255.0 - blend_yaw; + + m_pCurrentEntity->curstate.blending[0] = (int)(blend_yaw); + m_pCurrentEntity->latched.prevblending[0] = m_pCurrentEntity->curstate.blending[0]; + m_pCurrentEntity->latched.prevseqblending[0] = m_pCurrentEntity->curstate.blending[0]; + + m_pCurrentEntity->angles[YAW] = m_pPlayerInfo->gaityaw; + if (m_pCurrentEntity->angles[YAW] < -0) + { + m_pCurrentEntity->angles[YAW] += 360; + } + m_pCurrentEntity->latched.prevangles[YAW] = m_pCurrentEntity->angles[YAW]; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + pplayer->gaitsequence; + + // Calc gait frame + if (pseqdesc->linearmovement[0] > 0) + { + m_pPlayerInfo->gaitframe += (m_flGaitMovement / pseqdesc->linearmovement[0]) * pseqdesc->numframes; + } + else + { + m_pPlayerInfo->gaitframe += pseqdesc->fps * dt * m_pCurrentEntity->curstate.framerate; + } + + // Do modulo + m_pPlayerInfo->gaitframe = m_pPlayerInfo->gaitframe - (int)(m_pPlayerInfo->gaitframe / pseqdesc->numframes) * pseqdesc->numframes; + if (m_pPlayerInfo->gaitframe < 0) + { + m_pPlayerInfo->gaitframe += pseqdesc->numframes; + } +} + +/* +============================== +SavePlayerState + +For local player, in third person, we need to store real render data and then + setup for with fake/client side animation data +============================== +*/ +void CGameStudioModelRenderer::SavePlayerState( entity_state_t *pplayer ) +{ + client_anim_state_t *st; + cl_entity_t *ent = IEngineStudio.GetCurrentEntity(); + assert( ent ); + if ( !ent ) + return; + + st = &g_state; + + st->angles = ent->curstate.angles; + st->origin = ent->curstate.origin; + + st->realangles = ent->angles; + + st->sequence = ent->curstate.sequence; + st->gaitsequence = pplayer->gaitsequence; + st->animtime = ent->curstate.animtime; + st->frame = ent->curstate.frame; + st->framerate = ent->curstate.framerate; + memcpy( st->blending, ent->curstate.blending, 2 ); + memcpy( st->controller, ent->curstate.controller, 4 ); + + st->lv = ent->latched; +} + +void GetSequenceInfo( void *pmodel, client_anim_state_t *pev, float *pflFrameRate, float *pflGroundSpeed ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return; + + mstudioseqdesc_t *pseqdesc; + + if (pev->sequence >= pstudiohdr->numseq) + { + *pflFrameRate = 0.0; + *pflGroundSpeed = 0.0; + return; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + if (pseqdesc->numframes > 1) + { + *pflFrameRate = 256 * pseqdesc->fps / (pseqdesc->numframes - 1); + *pflGroundSpeed = sqrt( pseqdesc->linearmovement[0]*pseqdesc->linearmovement[0]+ pseqdesc->linearmovement[1]*pseqdesc->linearmovement[1]+ pseqdesc->linearmovement[2]*pseqdesc->linearmovement[2] ); + *pflGroundSpeed = *pflGroundSpeed * pseqdesc->fps / (pseqdesc->numframes - 1); + } + else + { + *pflFrameRate = 256.0; + *pflGroundSpeed = 0.0; + } +} + +int GetSequenceFlags( void *pmodel, client_anim_state_t *pev ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq ) + return 0; + + mstudioseqdesc_t *pseqdesc; + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + return pseqdesc->flags; +} + +float StudioFrameAdvance ( client_anim_state_t *st, float framerate, float flInterval ) +{ + if (flInterval == 0.0) + { + flInterval = (gEngfuncs.GetClientTime() - st->animtime); + if (flInterval <= 0.001) + { + st->animtime = gEngfuncs.GetClientTime(); + return 0.0; + } + } + if (!st->animtime) + flInterval = 0.0; + + st->frame += flInterval * framerate * st->framerate; + st->animtime = gEngfuncs.GetClientTime(); + + if (st->frame < 0.0 || st->frame >= 256.0) + { + if ( st->m_fSequenceLoops ) + st->frame -= (int)(st->frame / 256.0) * 256.0; + else + st->frame = (st->frame < 0.0) ? 0 : 255; + st->m_fSequenceFinished = TRUE; // just in case it wasn't caught in GetEvents + } + + return flInterval; +} + +/* +============================== +SetupClientAnimation + +Called to set up local player's animation values +============================== +*/ +void CGameStudioModelRenderer::SetupClientAnimation( entity_state_t *pplayer ) +{ + static double oldtime; + double curtime, dt; + + client_anim_state_t *st; + float fr, gs; + + cl_entity_t *ent = IEngineStudio.GetCurrentEntity(); + assert( ent ); + if ( !ent ) + return; + + curtime = gEngfuncs.GetClientTime(); + dt = curtime - oldtime; + dt = min( 1.0, max( 0.0, dt ) ); + + oldtime = curtime; + st = &g_clientstate; + + st->framerate = 1.0; + + int oldseq = st->sequence; + Game_GetSequence( &st->sequence, &st->gaitsequence ); //CVAR_GET_FLOAT( "sequence" ); + Game_GetOrientation( (float *)&st->origin, (float *)&st->angles ); + st->realangles = st->angles; + + if ( st->sequence != oldseq ) + { + st->frame = 0.0; + st->lv.prevsequence = oldseq; + st->lv.sequencetime = st->animtime; + + memcpy( st->lv.prevseqblending, st->blending, 2 ); + memcpy( st->lv.prevcontroller, st->controller, 4 ); + } + + void *pmodel = (studiohdr_t *)IEngineStudio.Mod_Extradata( ent->model ); + + GetSequenceInfo( pmodel, st, &fr, &gs ); + st->m_fSequenceLoops = ((GetSequenceFlags( pmodel, st ) & STUDIO_LOOPING) != 0); + StudioFrameAdvance( st, fr, dt ); + +// gEngfuncs.Con_Printf( "gs %i frame %f\n", st->gaitsequence, st->frame ); + + ent->angles = st->realangles; + ent->curstate.angles = st->angles; + ent->curstate.origin = st->origin; + + ent->curstate.sequence = st->sequence; + pplayer->gaitsequence = st->gaitsequence; + ent->curstate.animtime = st->animtime; + ent->curstate.frame = st->frame; + ent->curstate.framerate = st->framerate; + memcpy( ent->curstate.blending, st->blending, 2 ); + memcpy( ent->curstate.controller, st->controller, 4 ); + + ent->latched = st->lv; +} + +/* +============================== +RestorePlayerState + +Called to restore original player state information +============================== +*/ +void CGameStudioModelRenderer::RestorePlayerState( entity_state_t *pplayer ) +{ + client_anim_state_t *st; + cl_entity_t *ent = IEngineStudio.GetCurrentEntity(); + assert( ent ); + if ( !ent ) + return; + + st = &g_clientstate; + + st->angles = ent->curstate.angles; + st->origin = ent->curstate.origin; + st->realangles = ent->angles; + + st->sequence = ent->curstate.sequence; + st->gaitsequence = pplayer->gaitsequence; + st->animtime = ent->curstate.animtime; + st->frame = ent->curstate.frame; + st->framerate = ent->curstate.framerate; + memcpy( st->blending, ent->curstate.blending, 2 ); + memcpy( st->controller, ent->curstate.controller, 4 ); + + st->lv = ent->latched; + + st = &g_state; + + ent->curstate.angles = st->angles; + ent->curstate.origin = st->origin; + ent->angles = st->realangles; + + ent->curstate.sequence = st->sequence; + pplayer->gaitsequence = st->gaitsequence; + ent->curstate.animtime = st->animtime; + ent->curstate.frame = st->frame; + ent->curstate.framerate = st->framerate; + memcpy( ent->curstate.blending, st->blending, 2 ); + memcpy( ent->curstate.controller, st->controller, 4 ); + + ent->latched = st->lv; +} + +/* +============================== +StudioDrawPlayer + +============================== +*/ +int CGameStudioModelRenderer::StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + int iret = 0; + + bool isLocalPlayer = false; + + // Set up for client? + if ( m_bLocal && IEngineStudio.GetCurrentEntity() == gEngfuncs.GetLocalPlayer() ) + { + isLocalPlayer = true; + } + + if ( isLocalPlayer ) + { + // Store original data + SavePlayerState( pplayer ); + + // Copy in client side animation data + SetupClientAnimation( pplayer ); + } + + // Call real draw function + iret = _StudioDrawPlayer( flags, pplayer ); + + // Restore for client? + if ( isLocalPlayer ) + { + // Restore the original data for the player + RestorePlayerState( pplayer ); + } + + return iret; +} + +/* +==================== +_StudioDrawPlayer + +==================== +*/ +int CGameStudioModelRenderer::_StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + alight_t lighting; + vec3_t dir; + + m_pCurrentEntity = IEngineStudio.GetCurrentEntity(); + IEngineStudio.GetTimes( &m_nFrameCount, &m_clTime, &m_clOldTime ); + IEngineStudio.GetViewInfo( m_vRenderOrigin, m_vUp, m_vRight, m_vNormal ); + IEngineStudio.GetAliasScale( &m_fSoftwareXScale, &m_fSoftwareYScale ); + + m_nPlayerIndex = pplayer->number - 1; + + if (m_nPlayerIndex < 0 || m_nPlayerIndex >= gEngfuncs.GetMaxClients()) + return 0; + + m_pRenderModel = IEngineStudio.SetupPlayerModel( m_nPlayerIndex ); + if (m_pRenderModel == NULL) + return 0; + + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (m_pRenderModel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + IEngineStudio.SetRenderModel( m_pRenderModel ); + + if (pplayer->gaitsequence) + { + vec3_t orig_angles; + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + + VectorCopy( m_pCurrentEntity->angles, orig_angles ); + + StudioProcessGait( pplayer ); + + m_pPlayerInfo->gaitsequence = pplayer->gaitsequence; + m_pPlayerInfo = NULL; + + StudioSetUpTransform( 0 ); + VectorCopy( orig_angles, m_pCurrentEntity->angles ); + } + else + { + m_pCurrentEntity->curstate.controller[0] = 127; + m_pCurrentEntity->curstate.controller[1] = 127; + m_pCurrentEntity->curstate.controller[2] = 127; + m_pCurrentEntity->curstate.controller[3] = 127; + m_pCurrentEntity->latched.prevcontroller[0] = m_pCurrentEntity->curstate.controller[0]; + m_pCurrentEntity->latched.prevcontroller[1] = m_pCurrentEntity->curstate.controller[1]; + m_pCurrentEntity->latched.prevcontroller[2] = m_pCurrentEntity->curstate.controller[2]; + m_pCurrentEntity->latched.prevcontroller[3] = m_pCurrentEntity->curstate.controller[3]; + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + m_pPlayerInfo->gaitsequence = 0; + + StudioSetUpTransform( 0 ); + } + + if (flags & STUDIO_RENDER) + { + // see if the bounding box lets us trivially reject, also sets + if (!IEngineStudio.StudioCheckBBox ()) + return 0; + + (*m_pModelsDrawn)++; + (*m_pStudioModelCount)++; // render data cache cookie + + if (m_pStudioHeader->numbodyparts == 0) + return 1; + } + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + StudioSetupBones( ); + StudioSaveBones( ); + m_pPlayerInfo->renderframe = m_nFrameCount; + + m_pPlayerInfo = NULL; + + if (flags & STUDIO_EVENTS) + { + StudioCalcAttachments( ); + IEngineStudio.StudioClientEvents( ); + // copy attachments into global entity array + if ( m_pCurrentEntity->index > 0 ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( m_pCurrentEntity->index ); + + memcpy( ent->attachment, m_pCurrentEntity->attachment, sizeof( vec3_t ) * 4 ); + } + } + + if (flags & STUDIO_RENDER) + { + /* + if (m_pCvarHiModels->value && m_pRenderModel != m_pCurrentEntity->model ) + { + // show highest resolution multiplayer model + m_pCurrentEntity->curstate.body = 255; + } + + if (!(m_pCvarDeveloper->value == 0 && gEngfuncs.GetMaxClients() == 1 ) && ( m_pRenderModel == m_pCurrentEntity->model ) ) + { + m_pCurrentEntity->curstate.body = 1; // force helmet + } + */ + + lighting.plightvec = dir; + IEngineStudio.StudioDynamicLight(m_pCurrentEntity, &lighting ); + + IEngineStudio.StudioEntityLight( &lighting ); + + // model and frame independant + IEngineStudio.StudioSetupLighting (&lighting); + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + + // get remap colors + m_nTopColor = m_pPlayerInfo->topcolor; + if (m_nTopColor < 0) + m_nTopColor = 0; + if (m_nTopColor > 360) + m_nTopColor = 360; + m_nBottomColor = m_pPlayerInfo->bottomcolor; + if (m_nBottomColor < 0) + m_nBottomColor = 0; + if (m_nBottomColor > 360) + m_nBottomColor = 360; + + IEngineStudio.StudioSetRemapColors( m_nTopColor, m_nBottomColor ); + + StudioRenderModel( ); + m_pPlayerInfo = NULL; + + if (pplayer->weaponmodel) + { + cl_entity_t saveent = *m_pCurrentEntity; + + model_t *pweaponmodel = IEngineStudio.GetModelByIndex( pplayer->weaponmodel ); + + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (pweaponmodel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + + StudioMergeBones( pweaponmodel); + + IEngineStudio.StudioSetupLighting (&lighting); + + StudioRenderModel( ); + + StudioCalcAttachments( ); + + *m_pCurrentEntity = saveent; + } + } + + return 1; +} + +/* +==================== +Studio_FxTransform + +==================== +*/ +void CGameStudioModelRenderer::StudioFxTransform( cl_entity_t *ent, float transform[3][4] ) +{ + switch( ent->curstate.renderfx ) + { + case kRenderFxDistort: + case kRenderFxHologram: + if ( gEngfuncs.pfnRandomLong(0,49) == 0 ) + { + int axis = gEngfuncs.pfnRandomLong(0,1); + if ( axis == 1 ) // Choose between x & z + axis = 2; + VectorScale( transform[axis], gEngfuncs.pfnRandomFloat(1,1.484), transform[axis] ); + } + else if ( gEngfuncs.pfnRandomLong(0,49) == 0 ) + { + float offset; + int axis = gEngfuncs.pfnRandomLong(0,1); + if ( axis == 1 ) // Choose between x & z + axis = 2; + offset = gEngfuncs.pfnRandomFloat(-10,10); + transform[gEngfuncs.pfnRandomLong(0,2)][3] += offset; + } + break; + case kRenderFxExplode: + { + if ( iRenderStateChanged ) + { + g_flStartScaleTime = m_clTime; + iRenderStateChanged = FALSE; + } + + // Make the Model continue to shrink + float flTimeDelta = m_clTime - g_flStartScaleTime; + if ( flTimeDelta > 0 ) + { + float flScale = 0.001; + // Goes almost all away + if ( flTimeDelta <= 2.0 ) + flScale = 1.0 - (flTimeDelta / 2.0); + + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + transform[i][j] *= flScale; + } + } + } + break; + } +} + +//////////////////////////////////// +// Hooks to class implementation +//////////////////////////////////// + +/* +==================== +R_StudioDrawPlayer + +==================== +*/ +int R_StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + return g_StudioRenderer.StudioDrawPlayer( flags, pplayer ); +} + +/* +==================== +R_StudioDrawModel + +==================== +*/ +int R_StudioDrawModel( int flags ) +{ + return g_StudioRenderer.StudioDrawModel( flags ); +} + +/* +==================== +R_StudioInit + +==================== +*/ +void R_StudioInit( void ) +{ + g_StudioRenderer.Init(); +} + +// The simple drawing interface we'll pass back to the engine +r_studio_interface_t studio = +{ + STUDIO_INTERFACE_VERSION, + R_StudioDrawModel, + R_StudioDrawPlayer, +}; + +/* +==================== +HUD_GetStudioModelInterface + +Export this function for the engine to use the studio renderer class to render objects. +==================== +*/ +#define DLLEXPORT __declspec( dllexport ) +extern "C" int DLLEXPORT HUD_GetStudioModelInterface( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio ) +{ + if ( version != STUDIO_INTERFACE_VERSION ) + return 0; + + // Point the engine to our callbacks + *ppinterface = &studio; + + // Copy in engine helper functions + memcpy( &IEngineStudio, pstudio, sizeof( IEngineStudio ) ); + + // Initialize local variables, etc. + R_StudioInit(); + + // Success + return 1; +} diff --git a/cl_dll/GameStudioModelRenderer_Sample.h b/cl_dll/GameStudioModelRenderer_Sample.h new file mode 100644 index 0000000..c924ba3 --- /dev/null +++ b/cl_dll/GameStudioModelRenderer_Sample.h @@ -0,0 +1,55 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( GAMESTUDIOMODELRENDERER_H ) +#define GAMESTUDIOMODELRENDERER_H +#if defined( _WIN32 ) +#pragma once +#endif + +/* +==================== +CGameStudioModelRenderer + +==================== +*/ +class CGameStudioModelRenderer : public CStudioModelRenderer +{ +public: + CGameStudioModelRenderer( void ); + + // Set up model bone positions + virtual void StudioSetupBones ( void ); + + // Estimate gait frame for player + virtual void StudioEstimateGait ( entity_state_t *pplayer ); + + // Process movement of player + virtual void StudioProcessGait ( entity_state_t *pplayer ); + + // Player drawing code + virtual int StudioDrawPlayer( int flags, entity_state_t *pplayer ); + virtual int _StudioDrawPlayer( int flags, entity_state_t *pplayer ); + + // Apply special effects to transform matrix + virtual void StudioFxTransform( cl_entity_t *ent, float transform[3][4] ); + +private: + // For local player, in third person, we need to store real render data and then + // setup for with fake/client side animation data + void SavePlayerState( entity_state_t *pplayer ); + // Called to set up local player's animation values + void SetupClientAnimation( entity_state_t *pplayer ); + // Called to restore original player state information + void RestorePlayerState( entity_state_t *pplayer ); + +private: + // Private data + bool m_bLocal; +}; + +#endif // GAMESTUDIOMODELRENDERER_H \ No newline at end of file diff --git a/cl_dll/MOTD.cpp b/cl_dll/MOTD.cpp new file mode 100644 index 0000000..e4d5208 --- /dev/null +++ b/cl_dll/MOTD.cpp @@ -0,0 +1,155 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// MOTD.cpp +// +// for displaying a server-sent message of the day +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +//DECLARE_MESSAGE( m_MOTD, MOTD ); + +int CHudMOTD::MOTD_DISPLAY_TIME; + +int CHudMOTD :: Init( void ) +{ + gHUD.AddHudElem( this ); + + // HOOK_MESSAGE( MOTD ); + + CVAR_CREATE( "motd_display_time", "15", 0 ); + + m_iFlags &= ~HUD_ACTIVE; // start out inactive + m_szMOTD[0] = 0; + + return 1; +} + +int CHudMOTD :: VidInit( void ) +{ + // Load sprites here + + return 1; +} + +void CHudMOTD :: Reset( void ) +{ + m_iFlags &= ~HUD_ACTIVE; // start out inactive + m_szMOTD[0] = 0; + m_iLines = 0; + m_flActiveRemaining = 0; +} + +#define LINE_HEIGHT 13 + +int CHudMOTD :: Draw( float fTime ) +{ + static float sfLastTime; + float fElapsed; + + // Draw MOTD line-by-line + if ( m_flActiveRemaining <= 0.0 ) + { + // finished with MOTD, disable it + m_szMOTD[0] = 0; + m_iLines = 0; + m_iFlags &= ~HUD_ACTIVE; + m_flActiveRemaining = 0.0; + return 1; + } + + fElapsed = gHUD.m_flTime - sfLastTime; + + // Don't let time go negative ( level transition? ) + fElapsed = max( 0.0, fElapsed ); + // Don't let time go hugely positive ( first connection to active server ? ) + fElapsed = min( 1.0, fElapsed ); + + // Remember last timestamp + sfLastTime = gHUD.m_flTime; + + // Remove a bit of time + m_flActiveRemaining -= fElapsed; + + // find the top of where the MOTD should be drawn, so the whole thing is centered in the screen + int ypos = max(((ScreenHeight - (m_iLines * LINE_HEIGHT)) / 2) - 40, 30 ); // shift it up slightly + char *ch = m_szMOTD; + while ( *ch ) + { + int line_length = 0; // count the length of the current line + for ( char *next_line = ch; *next_line != '\n' && *next_line != 0; next_line++ ) + line_length += gHUD.m_scrinfo.charWidths[ *next_line ]; + char *top = next_line; + if ( *top == '\n' ) + *top = 0; + else + top = NULL; + + // find where to start drawing the line + int xpos = (ScreenWidth - line_length) / 2; + + gHUD.DrawHudString( xpos, ypos, ScreenWidth, ch, 255, 180, 0 ); + + ypos += LINE_HEIGHT; + + if ( top ) // restore + *top = '\n'; + ch = next_line; + if ( *ch == '\n' ) + ch++; + + if ( ypos > (ScreenHeight - 20) ) + break; // don't let it draw too low + } + + return 1; +} + +int CHudMOTD :: MsgFunc_MOTD( const char *pszName, int iSize, void *pbuf ) +{ + if ( m_iFlags & HUD_ACTIVE ) + { + Reset(); // clear the current MOTD in prep for this one + } + + BEGIN_READ( pbuf, iSize ); + + int is_finished = READ_BYTE(); + strcat( m_szMOTD, READ_STRING() ); + + if ( is_finished ) + { + m_iFlags |= HUD_ACTIVE; + + MOTD_DISPLAY_TIME = max( 10, CVAR_GET_FLOAT( "motd_display_time" ) ); + + m_flActiveRemaining = MOTD_DISPLAY_TIME; + + for ( char *sz = m_szMOTD; *sz != 0; sz++ ) // count the number of lines in the MOTD + { + if ( *sz == '\n' ) + m_iLines++; + } + } + + return 1; +} + diff --git a/cl_dll/StudioModelRenderer.cpp b/cl_dll/StudioModelRenderer.cpp new file mode 100644 index 0000000..1a61ca3 --- /dev/null +++ b/cl_dll/StudioModelRenderer.cpp @@ -0,0 +1,2114 @@ +// studio_model.cpp +// routines for setting up to draw 3DStudio models + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include "studio.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "dlight.h" +#include "triangleapi.h" + +#include +#include +#include +#include + +#include "studio_util.h" +#include "r_studioint.h" + +#include "StudioModelRenderer.h" +#include "GameStudioModelRenderer.h" + +extern cvar_t *tfc_newmodels; + +extern extra_player_info_t g_PlayerExtraInfo[MAX_PLAYERS+1]; + +// team colors for old TFC models +#define TEAM1_COLOR 150 +#define TEAM2_COLOR 250 +#define TEAM3_COLOR 45 +#define TEAM4_COLOR 100 + +int m_nPlayerGaitSequences[MAX_CLIENTS]; + +// Global engine <-> studio model rendering code interface +engine_studio_api_t IEngineStudio; + +///////////////////// +// Implementation of CStudioModelRenderer.h + +/* +==================== +Init + +==================== +*/ +void CStudioModelRenderer::Init( void ) +{ + // Set up some variables shared with engine + m_pCvarHiModels = IEngineStudio.GetCvar( "cl_himodels" ); + m_pCvarDeveloper = IEngineStudio.GetCvar( "developer" ); + m_pCvarDrawEntities = IEngineStudio.GetCvar( "r_drawentities" ); + + m_pChromeSprite = IEngineStudio.GetChromeSprite(); + + IEngineStudio.GetModelCounters( &m_pStudioModelCount, &m_pModelsDrawn ); + + // Get pointers to engine data structures + m_pbonetransform = (float (*)[MAXSTUDIOBONES][3][4])IEngineStudio.StudioGetBoneTransform(); + m_plighttransform = (float (*)[MAXSTUDIOBONES][3][4])IEngineStudio.StudioGetLightTransform(); + m_paliastransform = (float (*)[3][4])IEngineStudio.StudioGetAliasTransform(); + m_protationmatrix = (float (*)[3][4])IEngineStudio.StudioGetRotationMatrix(); +} + +/* +==================== +CStudioModelRenderer + +==================== +*/ +CStudioModelRenderer::CStudioModelRenderer( void ) +{ + m_fDoInterp = 1; + m_fGaitEstimation = 1; + m_pCurrentEntity = NULL; + m_pCvarHiModels = NULL; + m_pCvarDeveloper = NULL; + m_pCvarDrawEntities = NULL; + m_pChromeSprite = NULL; + m_pStudioModelCount = NULL; + m_pModelsDrawn = NULL; + m_protationmatrix = NULL; + m_paliastransform = NULL; + m_pbonetransform = NULL; + m_plighttransform = NULL; + m_pStudioHeader = NULL; + m_pBodyPart = NULL; + m_pSubModel = NULL; + m_pPlayerInfo = NULL; + m_pRenderModel = NULL; +} + +/* +==================== +~CStudioModelRenderer + +==================== +*/ +CStudioModelRenderer::~CStudioModelRenderer( void ) +{ +} + +/* +==================== +StudioCalcBoneAdj + +==================== +*/ +void CStudioModelRenderer::StudioCalcBoneAdj( float dadt, float *adj, const byte *pcontroller1, const byte *pcontroller2, byte mouthopen ) +{ + int i, j; + float value; + mstudiobonecontroller_t *pbonecontroller; + + pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex); + + for (j = 0; j < m_pStudioHeader->numbonecontrollers; j++) + { + i = pbonecontroller[j].index; + if (i <= 3) + { + // check for 360% wrapping + if (pbonecontroller[j].type & STUDIO_RLOOP) + { + if (abs(pcontroller1[i] - pcontroller2[i]) > 128) + { + int a, b; + a = (pcontroller1[j] + 128) % 256; + b = (pcontroller2[j] + 128) % 256; + value = ((a * dadt) + (b * (1 - dadt)) - 128) * (360.0/256.0) + pbonecontroller[j].start; + } + else + { + value = ((pcontroller1[i] * dadt + (pcontroller2[i]) * (1.0 - dadt))) * (360.0/256.0) + pbonecontroller[j].start; + } + } + else + { + value = (pcontroller1[i] * dadt + pcontroller2[i] * (1.0 - dadt)) / 255.0; + if (value < 0) value = 0; + if (value > 1.0) value = 1.0; + value = (1.0 - value) * pbonecontroller[j].start + value * pbonecontroller[j].end; + } + // Con_DPrintf( "%d %d %f : %f\n", m_pCurrentEntity->curstate.controller[j], m_pCurrentEntity->latched.prevcontroller[j], value, dadt ); + } + else + { + value = mouthopen / 64.0; + if (value > 1.0) value = 1.0; + value = (1.0 - value) * pbonecontroller[j].start + value * pbonecontroller[j].end; + // Con_DPrintf("%d %f\n", mouthopen, value ); + } + switch(pbonecontroller[j].type & STUDIO_TYPES) + { + case STUDIO_XR: + case STUDIO_YR: + case STUDIO_ZR: + adj[j] = value * (M_PI / 180.0); + break; + case STUDIO_X: + case STUDIO_Y: + case STUDIO_Z: + adj[j] = value; + break; + } + } +} + + +/* +==================== +StudioCalcBoneQuaterion + +==================== +*/ +void CStudioModelRenderer::StudioCalcBoneQuaterion( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *q ) +{ + int j, k; + vec4_t q1, q2; + vec3_t angle1, angle2; + mstudioanimvalue_t *panimvalue; + + for (j = 0; j < 3; j++) + { + if (panim->offset[j+3] == 0) + { + angle2[j] = angle1[j] = pbone->value[j+3]; // default; + } + else + { + panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[j+3]); + k = frame; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + while (panimvalue->num.total <= k) + { + k -= panimvalue->num.total; + panimvalue += panimvalue->num.valid + 1; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + } + // Bah, missing blend! + if (panimvalue->num.valid > k) + { + angle1[j] = panimvalue[k+1].value; + + if (panimvalue->num.valid > k + 1) + { + angle2[j] = panimvalue[k+2].value; + } + else + { + if (panimvalue->num.total > k + 1) + angle2[j] = angle1[j]; + else + angle2[j] = panimvalue[panimvalue->num.valid+2].value; + } + } + else + { + angle1[j] = panimvalue[panimvalue->num.valid].value; + if (panimvalue->num.total > k + 1) + { + angle2[j] = angle1[j]; + } + else + { + angle2[j] = panimvalue[panimvalue->num.valid + 2].value; + } + } + angle1[j] = pbone->value[j+3] + angle1[j] * pbone->scale[j+3]; + angle2[j] = pbone->value[j+3] + angle2[j] * pbone->scale[j+3]; + } + + if (pbone->bonecontroller[j+3] != -1) + { + angle1[j] += adj[pbone->bonecontroller[j+3]]; + angle2[j] += adj[pbone->bonecontroller[j+3]]; + } + } + + if (!VectorCompare( angle1, angle2 )) + { + AngleQuaternion( angle1, q1 ); + AngleQuaternion( angle2, q2 ); + QuaternionSlerp( q1, q2, s, q ); + } + else + { + AngleQuaternion( angle1, q ); + } +} + +/* +==================== +StudioCalcBonePosition + +==================== +*/ +void CStudioModelRenderer::StudioCalcBonePosition( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *pos ) +{ + int j, k; + mstudioanimvalue_t *panimvalue; + + for (j = 0; j < 3; j++) + { + pos[j] = pbone->value[j]; // default; + if (panim->offset[j] != 0) + { + panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[j]); + /* + if (i == 0 && j == 0) + Con_DPrintf("%d %d:%d %f\n", frame, panimvalue->num.valid, panimvalue->num.total, s ); + */ + + k = frame; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + // find span of values that includes the frame we want + while (panimvalue->num.total <= k) + { + k -= panimvalue->num.total; + panimvalue += panimvalue->num.valid + 1; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + } + // if we're inside the span + if (panimvalue->num.valid > k) + { + // and there's more data in the span + if (panimvalue->num.valid > k + 1) + { + pos[j] += (panimvalue[k+1].value * (1.0 - s) + s * panimvalue[k+2].value) * pbone->scale[j]; + } + else + { + pos[j] += panimvalue[k+1].value * pbone->scale[j]; + } + } + else + { + // are we at the end of the repeating values section and there's another section with data? + if (panimvalue->num.total <= k + 1) + { + pos[j] += (panimvalue[panimvalue->num.valid].value * (1.0 - s) + s * panimvalue[panimvalue->num.valid + 2].value) * pbone->scale[j]; + } + else + { + pos[j] += panimvalue[panimvalue->num.valid].value * pbone->scale[j]; + } + } + } + if ( pbone->bonecontroller[j] != -1 && adj ) + { + pos[j] += adj[pbone->bonecontroller[j]]; + } + } +} + +/* +==================== +StudioSlerpBones + +==================== +*/ +void CStudioModelRenderer::StudioSlerpBones( vec4_t q1[], float pos1[][3], vec4_t q2[], float pos2[][3], float s ) +{ + int i; + vec4_t q3; + float s1; + + if (s < 0) s = 0; + else if (s > 1.0) s = 1.0; + + s1 = 1.0 - s; + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + QuaternionSlerp( q1[i], q2[i], s, q3 ); + q1[i][0] = q3[0]; + q1[i][1] = q3[1]; + q1[i][2] = q3[2]; + q1[i][3] = q3[3]; + pos1[i][0] = pos1[i][0] * s1 + pos2[i][0] * s; + pos1[i][1] = pos1[i][1] * s1 + pos2[i][1] * s; + pos1[i][2] = pos1[i][2] * s1 + pos2[i][2] * s; + } +} + +/* +==================== +StudioGetAnim + +==================== +*/ +mstudioanim_t *CStudioModelRenderer::StudioGetAnim( model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc ) +{ + mstudioseqgroup_t *pseqgroup; + cache_user_t *paSequences; + + pseqgroup = (mstudioseqgroup_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqgroupindex) + pseqdesc->seqgroup; + + if (pseqdesc->seqgroup == 0) + { + return (mstudioanim_t *)((byte *)m_pStudioHeader + pseqdesc->animindex); + } + + paSequences = (cache_user_t *)m_pSubModel->submodels; + + if (paSequences == NULL) + { + paSequences = (cache_user_t *)IEngineStudio.Mem_Calloc( 16, sizeof( cache_user_t ) ); // UNDONE: leak! + m_pSubModel->submodels = (dmodel_t *)paSequences; + } + + if (!IEngineStudio.Cache_Check( (struct cache_user_s *)&(paSequences[pseqdesc->seqgroup]))) + { + gEngfuncs.Con_DPrintf("loading %s\n", pseqgroup->name ); + IEngineStudio.LoadCacheFile( pseqgroup->name, (struct cache_user_s *)&paSequences[pseqdesc->seqgroup] ); + } + return (mstudioanim_t *)((byte *)paSequences[pseqdesc->seqgroup].data + pseqdesc->animindex); +} + +/* +==================== +StudioPlayerBlend + +==================== +*/ +void CStudioModelRenderer::StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ) +{ + // calc up/down pointing + *pBlend = (*pPitch * 3); + if (*pBlend < pseqdesc->blendstart[0]) + { + *pPitch -= pseqdesc->blendstart[0] / 3.0; + *pBlend = 0; + } + else if (*pBlend > pseqdesc->blendend[0]) + { + *pPitch -= pseqdesc->blendend[0] / 3.0; + *pBlend = 255; + } + else + { + if (pseqdesc->blendend[0] - pseqdesc->blendstart[0] < 0.1) // catch qc error + *pBlend = 127; + else + *pBlend = 255 * (*pBlend - pseqdesc->blendstart[0]) / (pseqdesc->blendend[0] - pseqdesc->blendstart[0]); + *pPitch = 0; + } +} + +/* +==================== +StudioSetUpTransform + +==================== +*/ +void CStudioModelRenderer::StudioSetUpTransform (int trivial_accept) +{ + int i; + vec3_t angles; + vec3_t modelpos; + +// tweek model origin + //for (i = 0; i < 3; i++) + // modelpos[i] = m_pCurrentEntity->origin[i]; + + VectorCopy( m_pCurrentEntity->origin, modelpos ); + +// TODO: should really be stored with the entity instead of being reconstructed +// TODO: should use a look-up table +// TODO: could cache lazily, stored in the entity + angles[ROLL] = m_pCurrentEntity->curstate.angles[ROLL]; + angles[PITCH] = m_pCurrentEntity->curstate.angles[PITCH]; + angles[YAW] = m_pCurrentEntity->curstate.angles[YAW]; + + //Con_DPrintf("Angles %4.2f prev %4.2f for %i\n", angles[PITCH], m_pCurrentEntity->index); + //Con_DPrintf("movetype %d %d\n", m_pCurrentEntity->movetype, m_pCurrentEntity->aiment ); + if (m_pCurrentEntity->curstate.movetype == MOVETYPE_STEP) + { + float f = 0; + float d; + + // don't do it if the goalstarttime hasn't updated in a while. + + // NOTE: Because we need to interpolate multiplayer characters, the interpolation time limit + // was increased to 1.0 s., which is 2x the max lag we are accounting for. + + if ( ( m_clTime < m_pCurrentEntity->curstate.animtime + 1.0f ) && + ( m_pCurrentEntity->curstate.animtime != m_pCurrentEntity->latched.prevanimtime ) ) + { + f = (m_clTime - m_pCurrentEntity->curstate.animtime) / (m_pCurrentEntity->curstate.animtime - m_pCurrentEntity->latched.prevanimtime); + //Con_DPrintf("%4.2f %.2f %.2f\n", f, m_pCurrentEntity->curstate.animtime, m_clTime); + } + + if (m_fDoInterp) + { + // ugly hack to interpolate angle, position. current is reached 0.1 seconds after being set + f = f - 1.0; + } + else + { + f = 0; + } + + for (i = 0; i < 3; i++) + { + modelpos[i] += (m_pCurrentEntity->origin[i] - m_pCurrentEntity->latched.prevorigin[i]) * f; + } + + // NOTE: Because multiplayer lag can be relatively large, we don't want to cap + // f at 1.5 anymore. + //if (f > -1.0 && f < 1.5) {} + +// Con_DPrintf("%.0f %.0f\n",m_pCurrentEntity->msg_angles[0][YAW], m_pCurrentEntity->msg_angles[1][YAW] ); + for (i = 0; i < 3; i++) + { + float ang1, ang2; + + ang1 = m_pCurrentEntity->angles[i]; + ang2 = m_pCurrentEntity->latched.prevangles[i]; + + d = ang1 - ang2; + if (d > 180) + { + d -= 360; + } + else if (d < -180) + { + d += 360; + } + + angles[i] += d * f; + } + //Con_DPrintf("%.3f \n", f ); + } + else if ( m_pCurrentEntity->curstate.movetype != MOVETYPE_NONE ) + { + VectorCopy( m_pCurrentEntity->angles, angles ); + } + + //Con_DPrintf("%.0f %0.f %0.f\n", modelpos[0], modelpos[1], modelpos[2] ); + //Con_DPrintf("%.0f %0.f %0.f\n", angles[0], angles[1], angles[2] ); + + angles[PITCH] = -angles[PITCH]; + AngleMatrix (angles, (*m_protationmatrix)); + + if ( !IEngineStudio.IsHardware() ) + { + static float viewmatrix[3][4]; + + VectorCopy (m_vRight, viewmatrix[0]); + VectorCopy (m_vUp, viewmatrix[1]); + VectorInverse (viewmatrix[1]); + VectorCopy (m_vNormal, viewmatrix[2]); + + (*m_protationmatrix)[0][3] = modelpos[0] - m_vRenderOrigin[0]; + (*m_protationmatrix)[1][3] = modelpos[1] - m_vRenderOrigin[1]; + (*m_protationmatrix)[2][3] = modelpos[2] - m_vRenderOrigin[2]; + + ConcatTransforms (viewmatrix, (*m_protationmatrix), (*m_paliastransform)); + + // do the scaling up of x and y to screen coordinates as part of the transform + // for the unclipped case (it would mess up clipping in the clipped case). + // Also scale down z, so 1/z is scaled 31 bits for free, and scale down x and y + // correspondingly so the projected x and y come out right + // FIXME: make this work for clipped case too? + if (trivial_accept) + { + for (i=0 ; i<4 ; i++) + { + (*m_paliastransform)[0][i] *= m_fSoftwareXScale * + (1.0 / (ZISCALE * 0x10000)); + (*m_paliastransform)[1][i] *= m_fSoftwareYScale * + (1.0 / (ZISCALE * 0x10000)); + (*m_paliastransform)[2][i] *= 1.0 / (ZISCALE * 0x10000); + + } + } + } + + (*m_protationmatrix)[0][3] = modelpos[0]; + (*m_protationmatrix)[1][3] = modelpos[1]; + (*m_protationmatrix)[2][3] = modelpos[2]; +} + + +/* +==================== +StudioEstimateInterpolant + +==================== +*/ +float CStudioModelRenderer::StudioEstimateInterpolant( void ) +{ + float dadt = 1.0; + + if ( m_fDoInterp && ( m_pCurrentEntity->curstate.animtime >= m_pCurrentEntity->latched.prevanimtime + 0.01 ) ) + { + dadt = (m_clTime - m_pCurrentEntity->curstate.animtime) / 0.1; + if (dadt > 2.0) + { + dadt = 2.0; + } + } + return dadt; +} + +/* +==================== +StudioCalcRotations + +==================== +*/ +void CStudioModelRenderer::StudioCalcRotations ( float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f ) +{ + int i; + int frame; + mstudiobone_t *pbone; + + float s; + float adj[MAXSTUDIOCONTROLLERS]; + float dadt; + + if (f > pseqdesc->numframes - 1) + { + f = 0; // bah, fix this bug with changing sequences too fast + } + // BUG ( somewhere else ) but this code should validate this data. + // This could cause a crash if the frame # is negative, so we'll go ahead + // and clamp it here + else if ( f < -0.01 ) + { + f = -0.01; + } + + frame = (int)f; + + // Con_DPrintf("%d %.4f %.4f %.4f %.4f %d\n", m_pCurrentEntity->curstate.sequence, m_clTime, m_pCurrentEntity->animtime, m_pCurrentEntity->frame, f, frame ); + + // Con_DPrintf( "%f %f %f\n", m_pCurrentEntity->angles[ROLL], m_pCurrentEntity->angles[PITCH], m_pCurrentEntity->angles[YAW] ); + + // Con_DPrintf("frame %d %d\n", frame1, frame2 ); + + + dadt = StudioEstimateInterpolant( ); + s = (f - frame); + + // add in programtic controllers + pbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + StudioCalcBoneAdj( dadt, adj, m_pCurrentEntity->curstate.controller, m_pCurrentEntity->latched.prevcontroller, m_pCurrentEntity->mouth.mouthopen ); + + for (i = 0; i < m_pStudioHeader->numbones; i++, pbone++, panim++) + { + StudioCalcBoneQuaterion( frame, s, pbone, panim, adj, q[i] ); + + StudioCalcBonePosition( frame, s, pbone, panim, adj, pos[i] ); + // if (0 && i == 0) + // Con_DPrintf("%d %d %d %d\n", m_pCurrentEntity->curstate.sequence, frame, j, k ); + } + + if (pseqdesc->motiontype & STUDIO_X) + { + pos[pseqdesc->motionbone][0] = 0.0; + } + if (pseqdesc->motiontype & STUDIO_Y) + { + pos[pseqdesc->motionbone][1] = 0.0; + } + if (pseqdesc->motiontype & STUDIO_Z) + { + pos[pseqdesc->motionbone][2] = 0.0; + } + + s = 0 * ((1.0 - (f - (int)(f))) / (pseqdesc->numframes)) * m_pCurrentEntity->curstate.framerate; + + if (pseqdesc->motiontype & STUDIO_LX) + { + pos[pseqdesc->motionbone][0] += s * pseqdesc->linearmovement[0]; + } + if (pseqdesc->motiontype & STUDIO_LY) + { + pos[pseqdesc->motionbone][1] += s * pseqdesc->linearmovement[1]; + } + if (pseqdesc->motiontype & STUDIO_LZ) + { + pos[pseqdesc->motionbone][2] += s * pseqdesc->linearmovement[2]; + } +} + +/* +==================== +Studio_FxTransform + +==================== +*/ +void CStudioModelRenderer::StudioFxTransform( cl_entity_t *ent, float transform[3][4] ) +{ + switch( ent->curstate.renderfx ) + { + case kRenderFxDistort: + case kRenderFxHologram: + if ( gEngfuncs.pfnRandomLong(0,49) == 0 ) + { + int axis = gEngfuncs.pfnRandomLong(0,1); + if ( axis == 1 ) // Choose between x & z + axis = 2; + VectorScale( transform[axis], gEngfuncs.pfnRandomFloat(1,1.484), transform[axis] ); + } + else if ( gEngfuncs.pfnRandomLong(0,49) == 0 ) + { + float offset; + int axis = gEngfuncs.pfnRandomLong(0,1); + if ( axis == 1 ) // Choose between x & z + axis = 2; + offset = gEngfuncs.pfnRandomFloat(-10,10); + transform[gEngfuncs.pfnRandomLong(0,2)][3] += offset; + } + break; + case kRenderFxExplode: + { + float scale; + + scale = 1.0 + ( m_clTime - ent->curstate.animtime) * 10.0; + if ( scale > 2 ) // Don't blow up more than 200% + scale = 2; + transform[0][1] *= scale; + transform[1][1] *= scale; + transform[2][1] *= scale; + } + break; + + } +} + +/* +==================== +StudioEstimateFrame + +==================== +*/ +float CStudioModelRenderer::StudioEstimateFrame( mstudioseqdesc_t *pseqdesc ) +{ + double dfdt, f; + + if ( m_fDoInterp ) + { + if ( m_clTime < m_pCurrentEntity->curstate.animtime ) + { + dfdt = 0; + } + else + { + dfdt = (m_clTime - m_pCurrentEntity->curstate.animtime) * m_pCurrentEntity->curstate.framerate * pseqdesc->fps; + + } + } + else + { + dfdt = 0; + } + + if (pseqdesc->numframes <= 1) + { + f = 0; + } + else + { + f = (m_pCurrentEntity->curstate.frame * (pseqdesc->numframes - 1)) / 256.0; + } + + f += dfdt; + + if (pseqdesc->flags & STUDIO_LOOPING) + { + if (pseqdesc->numframes > 1) + { + f -= (int)(f / (pseqdesc->numframes - 1)) * (pseqdesc->numframes - 1); + } + if (f < 0) + { + f += (pseqdesc->numframes - 1); + } + } + else + { + if (f >= pseqdesc->numframes - 1.001) + { + f = pseqdesc->numframes - 1.001; + } + if (f < 0.0) + { + f = 0.0; + } + } + return f; +} + +/* +==================== +StudioSetupBones + +==================== +*/ +void CStudioModelRenderer::StudioSetupBones ( void ) +{ + int i; + double f; + + mstudiobone_t *pbones; + mstudioseqdesc_t *pseqdesc; + mstudioanim_t *panim; + + static float pos[MAXSTUDIOBONES][3]; + static vec4_t q[MAXSTUDIOBONES]; + float bonematrix[3][4]; + + static float pos2[MAXSTUDIOBONES][3]; + static vec4_t q2[MAXSTUDIOBONES]; + static float pos3[MAXSTUDIOBONES][3]; + static vec4_t q3[MAXSTUDIOBONES]; + static float pos4[MAXSTUDIOBONES][3]; + static vec4_t q4[MAXSTUDIOBONES]; + + if (m_pCurrentEntity->curstate.sequence >= m_pStudioHeader->numseq) + { + m_pCurrentEntity->curstate.sequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + // always want new gait sequences to start on frame zero +/* if ( m_pPlayerInfo ) + { + int playerNum = m_pCurrentEntity->index - 1; + + // new jump gaitsequence? start from frame zero + if ( m_nPlayerGaitSequences[ playerNum ] != m_pPlayerInfo->gaitsequence ) + { + // m_pPlayerInfo->gaitframe = 0.0; + gEngfuncs.Con_Printf( "Setting gaitframe to 0\n" ); + } + + m_nPlayerGaitSequences[ playerNum ] = m_pPlayerInfo->gaitsequence; +// gEngfuncs.Con_Printf( "index: %d gaitsequence: %d\n",playerNum, m_pPlayerInfo->gaitsequence); + } +*/ + f = StudioEstimateFrame( pseqdesc ); + + if (m_pCurrentEntity->latched.prevframe > f) + { + //Con_DPrintf("%f %f\n", m_pCurrentEntity->prevframe, f ); + } + + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + + if (pseqdesc->numblends > 1) + { + float s; + float dadt; + + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos2, q2, pseqdesc, panim, f ); + + dadt = StudioEstimateInterpolant(); + s = (m_pCurrentEntity->curstate.blending[0] * dadt + m_pCurrentEntity->latched.prevblending[0] * (1.0 - dadt)) / 255.0; + + StudioSlerpBones( q, pos, q2, pos2, s ); + + if (pseqdesc->numblends == 4) + { + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos3, q3, pseqdesc, panim, f ); + + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos4, q4, pseqdesc, panim, f ); + + s = (m_pCurrentEntity->curstate.blending[0] * dadt + m_pCurrentEntity->latched.prevblending[0] * (1.0 - dadt)) / 255.0; + StudioSlerpBones( q3, pos3, q4, pos4, s ); + + s = (m_pCurrentEntity->curstate.blending[1] * dadt + m_pCurrentEntity->latched.prevblending[1] * (1.0 - dadt)) / 255.0; + StudioSlerpBones( q, pos, q3, pos3, s ); + } + } + + if (m_fDoInterp && + m_pCurrentEntity->latched.sequencetime && + ( m_pCurrentEntity->latched.sequencetime + 0.2 > m_clTime ) && + ( m_pCurrentEntity->latched.prevsequence < m_pStudioHeader->numseq )) + { + // blend from last sequence + static float pos1b[MAXSTUDIOBONES][3]; + static vec4_t q1b[MAXSTUDIOBONES]; + float s; + + if (m_pCurrentEntity->latched.prevsequence >= m_pStudioHeader->numseq) + { + m_pCurrentEntity->latched.prevsequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->latched.prevsequence; + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + // clip prevframe + StudioCalcRotations( pos1b, q1b, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + if (pseqdesc->numblends > 1) + { + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos2, q2, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + s = (m_pCurrentEntity->latched.prevseqblending[0]) / 255.0; + StudioSlerpBones( q1b, pos1b, q2, pos2, s ); + + if (pseqdesc->numblends == 4) + { + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos3, q3, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos4, q4, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + s = (m_pCurrentEntity->latched.prevseqblending[0]) / 255.0; + StudioSlerpBones( q3, pos3, q4, pos4, s ); + + s = (m_pCurrentEntity->latched.prevseqblending[1]) / 255.0; + StudioSlerpBones( q1b, pos1b, q3, pos3, s ); + } + } + + s = 1.0 - (m_clTime - m_pCurrentEntity->latched.sequencetime) / 0.2; + StudioSlerpBones( q, pos, q1b, pos1b, s ); + } + else + { + //Con_DPrintf("prevframe = %4.2f\n", f); + m_pCurrentEntity->latched.prevframe = f; + } + + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + // bounds checking + if ( m_pPlayerInfo ) + { + if ( m_pPlayerInfo->gaitsequence >= m_pStudioHeader->numseq ) + { + m_pPlayerInfo->gaitsequence = 0; + } + } + + // calc gait animation + if ( m_pPlayerInfo && m_pPlayerInfo->gaitsequence != 0 ) + { + if (m_pPlayerInfo->gaitsequence >= m_pStudioHeader->numseq) + { + m_pPlayerInfo->gaitsequence = 0; + } + + int copy = 1; + + pseqdesc = (mstudioseqdesc_t *)( (byte *)m_pStudioHeader + m_pStudioHeader->seqindex ) + m_pPlayerInfo->gaitsequence; + + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + StudioCalcRotations( pos2, q2, pseqdesc, panim, m_pPlayerInfo->gaitframe ); + + for ( i = 0; i < m_pStudioHeader->numbones; i++ ) + { + if ( !strcmp( pbones[i].name, "Bip01 Spine" ) ) + { + copy = 0; + } + else if ( !strcmp( pbones[ pbones[i].parent ].name, "Bip01 Pelvis" ) ) + { + copy = 1; + } + + if ( copy ) + { + memcpy( pos[i], pos2[i], sizeof( pos[i] ) ); + memcpy( q[i], q2[i], sizeof( q[i] ) ); + } + } + } + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + QuaternionMatrix( q[i], bonematrix ); + + bonematrix[0][3] = pos[i][0]; + bonematrix[1][3] = pos[i][1]; + bonematrix[2][3] = pos[i][2]; + + if (pbones[i].parent == -1) + { + if ( IEngineStudio.IsHardware() ) + { + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_pbonetransform)[i]); + + // MatrixCopy should be faster... + //ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + MatrixCopy( (*m_pbonetransform)[i], (*m_plighttransform)[i] ); + } + else + { + ConcatTransforms ((*m_paliastransform), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + } + + // Apply client-side effects to the transformation matrix + StudioFxTransform( m_pCurrentEntity, (*m_pbonetransform)[i] ); + } + else + { + ConcatTransforms ((*m_pbonetransform)[pbones[i].parent], bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_plighttransform)[pbones[i].parent], bonematrix, (*m_plighttransform)[i]); + } + } +} + + +/* +==================== +StudioSaveBones + +==================== +*/ +void CStudioModelRenderer::StudioSaveBones( void ) +{ + int i; + + mstudiobone_t *pbones; + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + m_nCachedBones = m_pStudioHeader->numbones; + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + strcpy( m_nCachedBoneNames[i], pbones[i].name ); + MatrixCopy( (*m_pbonetransform)[i], m_rgCachedBoneTransform[i] ); + MatrixCopy( (*m_plighttransform)[i], m_rgCachedLightTransform[i] ); + } +} + + +/* +==================== +StudioMergeBones + +==================== +*/ +void CStudioModelRenderer::StudioMergeBones ( model_t *m_pSubModel ) +{ + int i, j; + double f; + int do_hunt = true; + + mstudiobone_t *pbones; + mstudioseqdesc_t *pseqdesc; + mstudioanim_t *panim; + + static float pos[MAXSTUDIOBONES][3]; + float bonematrix[3][4]; + static vec4_t q[MAXSTUDIOBONES]; + + if (m_pCurrentEntity->curstate.sequence >= m_pStudioHeader->numseq) + { + m_pCurrentEntity->curstate.sequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + f = StudioEstimateFrame( pseqdesc ); + + if (m_pCurrentEntity->latched.prevframe > f) + { + //Con_DPrintf("%f %f\n", m_pCurrentEntity->prevframe, f ); + } + + panim = StudioGetAnim( m_pSubModel, pseqdesc ); + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + for (j = 0; j < m_nCachedBones; j++) + { + if (stricmp(pbones[i].name, m_nCachedBoneNames[j]) == 0) + { + MatrixCopy( m_rgCachedBoneTransform[j], (*m_pbonetransform)[i] ); + MatrixCopy( m_rgCachedLightTransform[j], (*m_plighttransform)[i] ); + break; + } + } + if (j >= m_nCachedBones) + { + QuaternionMatrix( q[i], bonematrix ); + + bonematrix[0][3] = pos[i][0]; + bonematrix[1][3] = pos[i][1]; + bonematrix[2][3] = pos[i][2]; + + if (pbones[i].parent == -1) + { + if ( IEngineStudio.IsHardware() ) + { + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_pbonetransform)[i]); + + // MatrixCopy should be faster... + //ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + MatrixCopy( (*m_pbonetransform)[i], (*m_plighttransform)[i] ); + } + else + { + ConcatTransforms ((*m_paliastransform), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + } + + // Apply client-side effects to the transformation matrix + StudioFxTransform( m_pCurrentEntity, (*m_pbonetransform)[i] ); + } + else + { + ConcatTransforms ((*m_pbonetransform)[pbones[i].parent], bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_plighttransform)[pbones[i].parent], bonematrix, (*m_plighttransform)[i]); + } + } + } +} + +#if defined( _TFC ) +#include "pm_shared.h" +const Vector& GetTeamColor( int team_no ); +#define IS_FIRSTPERSON_SPEC ( g_iUser1 == OBS_IN_EYE || (g_iUser1 && (gHUD.m_Spectator.m_pip->value == INSET_IN_EYE)) ) + +int GetRemapColor( int iTeam, bool bTopColor ) +{ + int retVal = 0; + + switch( iTeam ) + { + default: + case 1: + if ( bTopColor ) + retVal = TEAM1_COLOR; + else + retVal = TEAM1_COLOR - 10; + + break; + case 2: + if ( bTopColor ) + retVal = TEAM2_COLOR; + else + retVal = TEAM2_COLOR - 10; + + break; + case 3: + if ( bTopColor ) + retVal = TEAM3_COLOR; + else + retVal = TEAM3_COLOR - 10; + + break; + case 4: + if ( bTopColor ) + retVal = TEAM4_COLOR; + else + retVal = TEAM4_COLOR - 10; + + break; + } + + return retVal; +} +#endif + +/* +==================== +StudioDrawModel + +==================== +*/ +int CStudioModelRenderer::StudioDrawModel( int flags ) +{ + alight_t lighting; + vec3_t dir; + + m_pCurrentEntity = IEngineStudio.GetCurrentEntity(); + IEngineStudio.GetTimes( &m_nFrameCount, &m_clTime, &m_clOldTime ); + IEngineStudio.GetViewInfo( m_vRenderOrigin, m_vUp, m_vRight, m_vNormal ); + IEngineStudio.GetAliasScale( &m_fSoftwareXScale, &m_fSoftwareYScale ); + + if (m_pCurrentEntity->curstate.renderfx == kRenderFxDeadPlayer) + { + entity_state_t deadplayer; + + int result; + int save_interp; + + if (m_pCurrentEntity->curstate.renderamt <= 0 || m_pCurrentEntity->curstate.renderamt > gEngfuncs.GetMaxClients() ) + return 0; + + // get copy of player + deadplayer = *(IEngineStudio.GetPlayerState( m_pCurrentEntity->curstate.renderamt - 1 )); //cl.frames[cl.parsecount & CL_UPDATE_MASK].playerstate[m_pCurrentEntity->curstate.renderamt-1]; + + // clear weapon, movement state + deadplayer.number = m_pCurrentEntity->curstate.renderamt; + deadplayer.weaponmodel = 0; + deadplayer.gaitsequence = 0; + + deadplayer.movetype = MOVETYPE_NONE; + VectorCopy( m_pCurrentEntity->curstate.angles, deadplayer.angles ); + VectorCopy( m_pCurrentEntity->curstate.origin, deadplayer.origin ); + + save_interp = m_fDoInterp; + m_fDoInterp = 0; + + // draw as though it were a player + result = StudioDrawPlayer( flags, &deadplayer ); + + m_fDoInterp = save_interp; + return result; + } + + m_pRenderModel = m_pCurrentEntity->model; + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (m_pRenderModel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + IEngineStudio.SetRenderModel( m_pRenderModel ); + + StudioSetUpTransform( 0 ); + + if (flags & STUDIO_RENDER) + { + // see if the bounding box lets us trivially reject, also sets + if (!IEngineStudio.StudioCheckBBox ()) + return 0; + + (*m_pModelsDrawn)++; + (*m_pStudioModelCount)++; // render data cache cookie + + if (m_pStudioHeader->numbodyparts == 0) + return 1; + } + + if (m_pCurrentEntity->curstate.movetype == MOVETYPE_FOLLOW) + { + StudioMergeBones( m_pRenderModel ); + } + else + { + StudioSetupBones( ); + } + StudioSaveBones( ); + + if (flags & STUDIO_EVENTS) + { + StudioCalcAttachments( ); + IEngineStudio.StudioClientEvents( ); + // copy attachments into global entity array + if ( m_pCurrentEntity->index > 0 ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( m_pCurrentEntity->index ); + + memcpy( ent->attachment, m_pCurrentEntity->attachment, sizeof( vec3_t ) * 4 ); + } + } + + if (flags & STUDIO_RENDER) + { + lighting.plightvec = dir; + IEngineStudio.StudioDynamicLight(m_pCurrentEntity, &lighting ); + + IEngineStudio.StudioEntityLight( &lighting ); + + // model and frame independant + IEngineStudio.StudioSetupLighting (&lighting); + + // get remap colors +#if defined( _TFC ) + + m_nTopColor = m_pCurrentEntity->curstate.colormap & 0xFF; + m_nBottomColor = (m_pCurrentEntity->curstate.colormap & 0xFF00) >> 8; + + // use the old tfc colors for the models (view models) + // team 1 + if ( ( m_nTopColor < 155 ) && ( m_nTopColor > 135 ) ) + { + m_nTopColor = TEAM1_COLOR; + m_nBottomColor = TEAM1_COLOR - 10; + } + // team 2 + else if ( ( m_nTopColor < 260 ) && ( ( m_nTopColor > 240 ) || ( m_nTopColor == 5 ) ) ) + { + m_nTopColor = TEAM2_COLOR; + m_nBottomColor = TEAM2_COLOR - 10; + } + // team 3 + else if ( ( m_nTopColor < 50 ) && ( m_nTopColor > 40 ) ) + { + m_nTopColor = TEAM3_COLOR; + m_nBottomColor = TEAM3_COLOR - 10; + } + // team 4 + else if ( ( m_nTopColor < 110 ) && ( m_nTopColor > 75 ) ) + { + m_nTopColor = TEAM4_COLOR; + m_nBottomColor = TEAM4_COLOR - 10; + } + + // is this our view model and should it be glowing? we also fix a remap colors + // problem if we're spectating in first-person mode + if ( m_pCurrentEntity == gEngfuncs.GetViewModel() ) + { + cl_entity_t *pTarget = NULL; + + // we're spectating someone via first-person mode + if ( IS_FIRSTPERSON_SPEC ) + { + pTarget = gEngfuncs.GetEntityByIndex( g_iUser2 ); + + if ( pTarget ) + { + // we also need to correct the m_nTopColor and m_nBottomColor for the + // view model here. this is separate from the glowshell stuff, but + // the same conditions need to be met (this is the view model and we're + // in first-person spectator mode) + m_nTopColor = GetRemapColor( g_PlayerExtraInfo[pTarget->index].teamnumber, true ); + m_nBottomColor = GetRemapColor( g_PlayerExtraInfo[pTarget->index].teamnumber, false ); + } + } + // we're not spectating, this is OUR view model + else + { + pTarget = gEngfuncs.GetLocalPlayer(); + } + + if ( pTarget && pTarget->curstate.renderfx == kRenderFxGlowShell ) + { + m_pCurrentEntity->curstate.renderfx = kRenderFxGlowShell; + m_pCurrentEntity->curstate.rendercolor.r = pTarget->curstate.rendercolor.r; + m_pCurrentEntity->curstate.rendercolor.g = pTarget->curstate.rendercolor.g; + m_pCurrentEntity->curstate.rendercolor.b = pTarget->curstate.rendercolor.b; + } + else + { + m_pCurrentEntity->curstate.renderfx = kRenderFxNone; + m_pCurrentEntity->curstate.rendercolor.r = 0; + m_pCurrentEntity->curstate.rendercolor.g = 0; + m_pCurrentEntity->curstate.rendercolor.b = 0; + } + } + +#else + m_nTopColor = m_pCurrentEntity->curstate.colormap & 0xFF; + m_nBottomColor = (m_pCurrentEntity->curstate.colormap & 0xFF00) >> 8; + +#endif + + IEngineStudio.StudioSetRemapColors( m_nTopColor, m_nBottomColor ); + + StudioRenderModel( ); + } + + return 1; +} + +/* +==================== +StudioEstimateGait + +==================== +*/ +void CStudioModelRenderer::StudioEstimateGait( entity_state_t *pplayer ) +{ + float dt; + vec3_t est_velocity; + + dt = (m_clTime - m_clOldTime); + if (dt < 0) + dt = 0; + else if (dt > 1.0) + dt = 1; + + if (dt == 0 || m_pPlayerInfo->renderframe == m_nFrameCount) + { + m_flGaitMovement = 0; + return; + } + + // VectorAdd( pplayer->velocity, pplayer->prediction_error, est_velocity ); + if ( m_fGaitEstimation ) + { + VectorSubtract( m_pCurrentEntity->origin, m_pPlayerInfo->prevgaitorigin, est_velocity ); + VectorCopy( m_pCurrentEntity->origin, m_pPlayerInfo->prevgaitorigin ); + m_flGaitMovement = Length( est_velocity ); + if (dt <= 0 || m_flGaitMovement / dt < 5) + { + m_flGaitMovement = 0; + est_velocity[0] = 0; + est_velocity[1] = 0; + } + } + else + { + VectorCopy( pplayer->velocity, est_velocity ); + m_flGaitMovement = Length( est_velocity ) * dt; + } + + if (est_velocity[1] == 0 && est_velocity[0] == 0) + { + float flYawDiff = m_pCurrentEntity->angles[YAW] - m_pPlayerInfo->gaityaw; + flYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360; + if (flYawDiff > 180) + flYawDiff -= 360; + if (flYawDiff < -180) + flYawDiff += 360; + + if (dt < 0.25) + flYawDiff *= dt * 4; + else + flYawDiff *= dt; + + m_pPlayerInfo->gaityaw += flYawDiff; + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - (int)(m_pPlayerInfo->gaityaw / 360) * 360; + + m_flGaitMovement = 0; + } + else + { + m_pPlayerInfo->gaityaw = (atan2(est_velocity[1], est_velocity[0]) * 180 / M_PI); + if (m_pPlayerInfo->gaityaw > 180) + m_pPlayerInfo->gaityaw = 180; + if (m_pPlayerInfo->gaityaw < -180) + m_pPlayerInfo->gaityaw = -180; + } + +} + +/* +==================== +StudioProcessGait + +==================== +*/ +void CStudioModelRenderer::StudioProcessGait( entity_state_t *pplayer ) +{ + mstudioseqdesc_t *pseqdesc; + float dt; + int iBlend; + float flYaw; // view direction relative to movement + + if (m_pCurrentEntity->curstate.sequence >= m_pStudioHeader->numseq) + { + m_pCurrentEntity->curstate.sequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + StudioPlayerBlend( pseqdesc, &iBlend, &m_pCurrentEntity->angles[PITCH] ); + + m_pCurrentEntity->latched.prevangles[PITCH] = m_pCurrentEntity->angles[PITCH]; + m_pCurrentEntity->curstate.blending[0] = iBlend; + m_pCurrentEntity->latched.prevblending[0] = m_pCurrentEntity->curstate.blending[0]; + m_pCurrentEntity->latched.prevseqblending[0] = m_pCurrentEntity->curstate.blending[0]; + + // Con_DPrintf("%f %d\n", m_pCurrentEntity->angles[PITCH], m_pCurrentEntity->blending[0] ); + + dt = (m_clTime - m_clOldTime); + if (dt < 0) + dt = 0; + else if (dt > 1.0) + dt = 1; + + StudioEstimateGait( pplayer ); + + // Con_DPrintf("%f %f\n", m_pCurrentEntity->angles[YAW], m_pPlayerInfo->gaityaw ); + + // calc side to side turning + flYaw = m_pCurrentEntity->angles[YAW] - m_pPlayerInfo->gaityaw; + flYaw = flYaw - (int)(flYaw / 360) * 360; + if (flYaw < -180) + flYaw = flYaw + 360; + if (flYaw > 180) + flYaw = flYaw - 360; + + if (flYaw > 120) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - 180; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw - 180; + } + else if (flYaw < -120) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw + 180; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw + 180; + } + + // adjust torso + m_pCurrentEntity->curstate.controller[0] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->curstate.controller[1] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->curstate.controller[2] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->curstate.controller[3] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->latched.prevcontroller[0] = m_pCurrentEntity->curstate.controller[0]; + m_pCurrentEntity->latched.prevcontroller[1] = m_pCurrentEntity->curstate.controller[1]; + m_pCurrentEntity->latched.prevcontroller[2] = m_pCurrentEntity->curstate.controller[2]; + m_pCurrentEntity->latched.prevcontroller[3] = m_pCurrentEntity->curstate.controller[3]; + + m_pCurrentEntity->angles[YAW] = m_pPlayerInfo->gaityaw; + if (m_pCurrentEntity->angles[YAW] < -0) + m_pCurrentEntity->angles[YAW] += 360; + m_pCurrentEntity->latched.prevangles[YAW] = m_pCurrentEntity->angles[YAW]; + + if (pplayer->gaitsequence >= m_pStudioHeader->numseq) + { + pplayer->gaitsequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + pplayer->gaitsequence; + + // calc gait frame + if (pseqdesc->linearmovement[0] > 0) + { + m_pPlayerInfo->gaitframe += (m_flGaitMovement / pseqdesc->linearmovement[0]) * pseqdesc->numframes; + } + else + { + m_pPlayerInfo->gaitframe += pseqdesc->fps * dt; + } + + // do modulo + m_pPlayerInfo->gaitframe = m_pPlayerInfo->gaitframe - (int)(m_pPlayerInfo->gaitframe / pseqdesc->numframes) * pseqdesc->numframes; + if (m_pPlayerInfo->gaitframe < 0) + m_pPlayerInfo->gaitframe += pseqdesc->numframes; +} + +#if defined _TFC + +#define PC_UNDEFINED 0 + +#define PC_SCOUT 1 +#define PC_SNIPER 2 +#define PC_SOLDIER 3 +#define PC_DEMOMAN 4 +#define PC_MEDIC 5 +#define PC_HVYWEAP 6 +#define PC_PYRO 7 +#define PC_SPY 8 +#define PC_ENGINEER 9 +#define PC_RANDOM 10 +#define PC_CIVILIAN 11 + +#define PC_LASTCLASS 12 + +#define TFC_MODELS_OLD 0 + +extern cvar_t *tfc_newmodels; + +char *sNewClassModelFiles[] = +{ + NULL, + "models/player/scout/scout.mdl", + "models/player/sniper/sniper.mdl", + "models/player/soldier/soldier.mdl", + "models/player/demo/demo.mdl", + "models/player/medic/medic.mdl", + "models/player/hvyweapon/hvyweapon.mdl", + "models/player/pyro/pyro.mdl", + "models/player/spy/spy.mdl", + "models/player/engineer/engineer.mdl", + "models/player/scout/scout.mdl", // PC_RANDOM + "models/player/civilian/civilian.mdl", +}; + +char *sOldClassModelFiles[] = +{ + NULL, + "models/player/scout/scout2.mdl", + "models/player/sniper/sniper2.mdl", + "models/player/soldier/soldier2.mdl", + "models/player/demo/demo2.mdl", + "models/player/medic/medic2.mdl", + "models/player/hvyweapon/hvyweapon2.mdl", + "models/player/pyro/pyro2.mdl", + "models/player/spy/spy2.mdl", + "models/player/engineer/engineer2.mdl", + "models/player/scout/scout2.mdl", // PC_RANDOM + "models/player/civilian/civilian.mdl", +}; + +#define NUM_WEAPON_PMODELS 18 + +char *sNewWeaponPModels[] = +{ + "models/p_9mmhandgun.mdl", + "models/p_crowbar.mdl", + "models/p_egon.mdl", + "models/p_glauncher.mdl", + "models/p_grenade.mdl", + "models/p_knife.mdl", + "models/p_medkit.mdl", + "models/p_mini.mdl", + "models/p_nailgun.mdl", + "models/p_srpg.mdl", + "models/p_shotgun.mdl", + "models/p_snailgun.mdl", + "models/p_sniper.mdl", + "models/p_spanner.mdl", + "models/p_umbrella.mdl", + "models/p_rpg.mdl", + "models/p_spygun.mdl", + "models/p_smallshotgun.mdl" +}; + +char *sOldWeaponPModels[] = +{ + "models/p_9mmhandgun2.mdl", + "models/p_crowbar2.mdl", + "models/p_egon2.mdl", + "models/p_glauncher2.mdl", + "models/p_grenade2.mdl", + "models/p_knife2.mdl", + "models/p_medkit2.mdl", + "models/p_mini2.mdl", + "models/p_nailgun2.mdl", + "models/p_rpg2.mdl", + "models/p_shotgun2.mdl", + "models/p_snailgun2.mdl", + "models/p_sniper2.mdl", + "models/p_spanner2.mdl", + "models/p_umbrella.mdl", + "models/p_rpg2.mdl", + "models/p_9mmhandgun2.mdl", + "models/p_shotgun2.mdl" +}; + + +int CStudioModelRenderer :: ReturnDiguisedClass ( int iPlayerIndex ) +{ + m_pRenderModel = IEngineStudio.SetupPlayerModel( iPlayerIndex ); + + if ( !m_pRenderModel ) + return PC_SCOUT; + + for ( int i = PC_SCOUT ; i < PC_LASTCLASS ; i++ ) + { + if ( !strcmp ( m_pRenderModel->name, sNewClassModelFiles[ i ] ) ) + return i; + } + + return PC_SCOUT; +} + +char * ReturnCorrectedModelString ( int iSwitchClass ) +{ + if ( tfc_newmodels->value == TFC_MODELS_OLD ) + { + if ( sOldClassModelFiles[ iSwitchClass ] ) + return sOldClassModelFiles[ iSwitchClass ]; + else + return sOldClassModelFiles[ PC_SCOUT ]; + } + else + { + if ( sNewClassModelFiles[ iSwitchClass ] ) + return sNewClassModelFiles[ iSwitchClass ]; + else + return sNewClassModelFiles[ PC_SCOUT ]; + } +} + +#endif + +#ifdef _TFC +float g_flSpinUpTime[ 33 ]; +float g_flSpinDownTime[ 33 ]; +#endif + + +/* +==================== +StudioDrawPlayer + +==================== +*/ +int CStudioModelRenderer::StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + alight_t lighting; + vec3_t dir; + + m_pCurrentEntity = IEngineStudio.GetCurrentEntity(); + IEngineStudio.GetTimes( &m_nFrameCount, &m_clTime, &m_clOldTime ); + IEngineStudio.GetViewInfo( m_vRenderOrigin, m_vUp, m_vRight, m_vNormal ); + IEngineStudio.GetAliasScale( &m_fSoftwareXScale, &m_fSoftwareYScale ); + + m_nPlayerIndex = pplayer->number - 1; + + if (m_nPlayerIndex < 0 || m_nPlayerIndex >= gEngfuncs.GetMaxClients()) + return 0; + +#if defined( _TFC ) + + int modelindex; + int iSwitchClass = pplayer->playerclass; + + if ( iSwitchClass == PC_SPY ) + iSwitchClass = ReturnDiguisedClass( m_nPlayerIndex ); + + // do we have a "replacement_model" for this player? + if ( pplayer->fuser1 ) + { + m_pRenderModel = IEngineStudio.SetupPlayerModel( m_nPlayerIndex ); + } + else + { + // get the model pointer using a "corrected" model string based on tfc_newmodels + m_pRenderModel = gEngfuncs.CL_LoadModel( ReturnCorrectedModelString( iSwitchClass ), &modelindex ); + } + +#else + + m_pRenderModel = IEngineStudio.SetupPlayerModel( m_nPlayerIndex ); + +#endif + + if (m_pRenderModel == NULL) + return 0; + + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (m_pRenderModel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + IEngineStudio.SetRenderModel( m_pRenderModel ); + + if (pplayer->gaitsequence) + { + vec3_t orig_angles; + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + + VectorCopy( m_pCurrentEntity->angles, orig_angles ); + + StudioProcessGait( pplayer ); + + m_pPlayerInfo->gaitsequence = pplayer->gaitsequence; + m_pPlayerInfo = NULL; + + StudioSetUpTransform( 0 ); + VectorCopy( orig_angles, m_pCurrentEntity->angles ); + } + else + { + m_pCurrentEntity->curstate.controller[0] = 127; + m_pCurrentEntity->curstate.controller[1] = 127; + m_pCurrentEntity->curstate.controller[2] = 127; + m_pCurrentEntity->curstate.controller[3] = 127; + m_pCurrentEntity->latched.prevcontroller[0] = m_pCurrentEntity->curstate.controller[0]; + m_pCurrentEntity->latched.prevcontroller[1] = m_pCurrentEntity->curstate.controller[1]; + m_pCurrentEntity->latched.prevcontroller[2] = m_pCurrentEntity->curstate.controller[2]; + m_pCurrentEntity->latched.prevcontroller[3] = m_pCurrentEntity->curstate.controller[3]; + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + m_pPlayerInfo->gaitsequence = 0; + + StudioSetUpTransform( 0 ); + } + + if (flags & STUDIO_RENDER) + { + // see if the bounding box lets us trivially reject, also sets + if (!IEngineStudio.StudioCheckBBox ()) + return 0; + + (*m_pModelsDrawn)++; + (*m_pStudioModelCount)++; // render data cache cookie + + if (m_pStudioHeader->numbodyparts == 0) + return 1; + } + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + StudioSetupBones( ); + StudioSaveBones( ); + m_pPlayerInfo->renderframe = m_nFrameCount; + + m_pPlayerInfo = NULL; + + if (flags & STUDIO_EVENTS) + { + StudioCalcAttachments( ); + IEngineStudio.StudioClientEvents( ); + // copy attachments into global entity array + if ( m_pCurrentEntity->index > 0 ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( m_pCurrentEntity->index ); + + memcpy( ent->attachment, m_pCurrentEntity->attachment, sizeof( vec3_t ) * 4 ); + } + } + + if (flags & STUDIO_RENDER) + { + if (m_pCvarHiModels->value && m_pRenderModel != m_pCurrentEntity->model ) + { + // show highest resolution multiplayer model + m_pCurrentEntity->curstate.body = 255; + } + + if (!(m_pCvarDeveloper->value == 0 && gEngfuncs.GetMaxClients() == 1 ) && ( m_pRenderModel == m_pCurrentEntity->model ) ) + { + m_pCurrentEntity->curstate.body = 1; // force helmet + } + + lighting.plightvec = dir; + IEngineStudio.StudioDynamicLight(m_pCurrentEntity, &lighting ); + + IEngineStudio.StudioEntityLight( &lighting ); + + // model and frame independant + IEngineStudio.StudioSetupLighting (&lighting); + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + +#if defined _TFC + + m_nTopColor = m_pPlayerInfo->topcolor; + m_nBottomColor = m_pPlayerInfo->bottomcolor; + + // get old remap colors + if ( tfc_newmodels->value == TFC_MODELS_OLD ) + { + // team 1 + if ( ( m_nTopColor < 155 ) && ( m_nTopColor > 135 ) ) + { + m_nTopColor = TEAM1_COLOR; + m_nBottomColor = TEAM1_COLOR - 10; + } + // team 2 + else if ( ( m_nTopColor < 260 ) && ( ( m_nTopColor > 240 ) || ( m_nTopColor == 5 ) ) ) + { + m_nTopColor = TEAM2_COLOR; + m_nBottomColor = TEAM2_COLOR - 10; + } + // team 3 + else if ( ( m_nTopColor < 50 ) && ( m_nTopColor > 40 ) ) + { + m_nTopColor = TEAM3_COLOR; + m_nBottomColor = TEAM3_COLOR - 10; + } + // team 4 + else if ( ( m_nTopColor < 110 ) && ( m_nTopColor > 75 ) ) + { + m_nTopColor = TEAM4_COLOR; + m_nBottomColor = TEAM4_COLOR - 10; + } + } + +#else + // get remap colors + m_nTopColor = m_pPlayerInfo->topcolor; + m_nBottomColor = m_pPlayerInfo->bottomcolor; + +#endif + + // bounds check + if (m_nTopColor < 0) + m_nTopColor = 0; + if (m_nTopColor > 360) + m_nTopColor = 360; + if (m_nBottomColor < 0) + m_nBottomColor = 0; + if (m_nBottomColor > 360) + m_nBottomColor = 360; + + IEngineStudio.StudioSetRemapColors( m_nTopColor, m_nBottomColor ); + + StudioRenderModel( ); + m_pPlayerInfo = NULL; + + if (pplayer->weaponmodel) + { + cl_entity_t saveent = *m_pCurrentEntity; + + model_t *pweaponmodel = IEngineStudio.GetModelByIndex( pplayer->weaponmodel ); + +#if defined _TFC + if ( pweaponmodel ) + { + // if we want to see the old p_models + if ( tfc_newmodels->value == TFC_MODELS_OLD ) + { + for ( int i = 0 ; i < NUM_WEAPON_PMODELS ; ++i ) + { + if ( !stricmp( pweaponmodel->name, sNewWeaponPModels[i] ) ) + { + gEngfuncs.CL_LoadModel( sOldWeaponPModels[i] , &modelindex ); + pweaponmodel = IEngineStudio.GetModelByIndex( modelindex ); + break; + } + } + } + } +#endif + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (pweaponmodel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + +#ifdef _TFC + //Do spinning stuff for the HWGuy minigun + if ( strstr ( m_pStudioHeader->name, "p_mini.mdl" ) ) + { + if ( g_flSpinUpTime[ m_nPlayerIndex ] && g_flSpinUpTime[ m_nPlayerIndex ] > gEngfuncs.GetClientTime() ) + { + float flmod = ( g_flSpinUpTime[ m_nPlayerIndex ] - ( gEngfuncs.GetClientTime() + 3.5 ) ); + flmod *= -30; + + m_pCurrentEntity->curstate.frame = flmod; + m_pCurrentEntity->curstate.sequence = 2; + } + + else if ( g_flSpinUpTime[ m_nPlayerIndex ] && g_flSpinUpTime[ m_nPlayerIndex ] <= gEngfuncs.GetClientTime() ) + g_flSpinUpTime[ m_nPlayerIndex ] = 0.0; + + else if ( g_flSpinDownTime[ m_nPlayerIndex ] && g_flSpinDownTime[ m_nPlayerIndex ] > gEngfuncs.GetClientTime() && !g_flSpinUpTime[ m_nPlayerIndex ] ) + { + float flmod = ( g_flSpinDownTime[ m_nPlayerIndex ] - ( gEngfuncs.GetClientTime() + 3.5 ) ); + flmod *= -30; + + m_pCurrentEntity->curstate.frame = flmod; + m_pCurrentEntity->curstate.sequence = 3; + } + + else if ( g_flSpinDownTime[ m_nPlayerIndex ] && g_flSpinDownTime[ m_nPlayerIndex ] <= gEngfuncs.GetClientTime() && !g_flSpinUpTime[ m_nPlayerIndex ] ) + g_flSpinDownTime[ m_nPlayerIndex ] = 0.0; + + if ( m_pCurrentEntity->curstate.sequence == 70 || m_pCurrentEntity->curstate.sequence == 72 ) + { + if ( g_flSpinUpTime[ m_nPlayerIndex ] ) + g_flSpinUpTime[ m_nPlayerIndex ] = 0.0; + + m_pCurrentEntity->curstate.sequence = 1; + } + + StudioSetupBones( ); + } + else + { + if ( g_flSpinUpTime[ m_nPlayerIndex ] || g_flSpinDownTime[ m_nPlayerIndex ] ) + { + g_flSpinUpTime[ m_nPlayerIndex ] = 0.0; + g_flSpinDownTime[ m_nPlayerIndex ] = 0.0; + } + } + +#endif + + StudioMergeBones( pweaponmodel ); + + IEngineStudio.StudioSetupLighting (&lighting); + + StudioRenderModel( ); + + StudioCalcAttachments( ); + + *m_pCurrentEntity = saveent; + } + } + + return 1; +} + +/* +==================== +StudioCalcAttachments + +==================== +*/ +void CStudioModelRenderer::StudioCalcAttachments( void ) +{ + int i; + mstudioattachment_t *pattachment; + + if ( m_pStudioHeader->numattachments > 4 ) + { + gEngfuncs.Con_DPrintf( "Too many attachments on %s\n", m_pCurrentEntity->model->name ); + exit( -1 ); + } + + // calculate attachment points + pattachment = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); + for (i = 0; i < m_pStudioHeader->numattachments; i++) + { + VectorTransform( pattachment[i].org, (*m_plighttransform)[pattachment[i].bone], m_pCurrentEntity->attachment[i] ); + } +} + +/* +==================== +StudioRenderModel + +==================== +*/ +void CStudioModelRenderer::StudioRenderModel( void ) +{ + IEngineStudio.SetChromeOrigin(); + IEngineStudio.SetForceFaceFlags( 0 ); + + if ( m_pCurrentEntity->curstate.renderfx == kRenderFxGlowShell ) + { + m_pCurrentEntity->curstate.renderfx = kRenderFxNone; + StudioRenderFinal( ); + + if ( !IEngineStudio.IsHardware() ) + { + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + } + + IEngineStudio.SetForceFaceFlags( STUDIO_NF_CHROME ); + + gEngfuncs.pTriAPI->SpriteTexture( m_pChromeSprite, 0 ); + m_pCurrentEntity->curstate.renderfx = kRenderFxGlowShell; + + StudioRenderFinal( ); + if ( !IEngineStudio.IsHardware() ) + { + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + } + } + else + { + StudioRenderFinal( ); + } +} + +/* +==================== +StudioRenderFinal_Software + +==================== +*/ +void CStudioModelRenderer::StudioRenderFinal_Software( void ) +{ + int i; + + // Note, rendermode set here has effect in SW + IEngineStudio.SetupRenderer( 0 ); + + if (m_pCvarDrawEntities->value == 2) + { + IEngineStudio.StudioDrawBones( ); + } + else if (m_pCvarDrawEntities->value == 3) + { + IEngineStudio.StudioDrawHulls( ); + } + else + { + for (i=0 ; i < m_pStudioHeader->numbodyparts ; i++) + { + IEngineStudio.StudioSetupModel( i, (void **)&m_pBodyPart, (void **)&m_pSubModel ); + IEngineStudio.StudioDrawPoints( ); + } + } + + if (m_pCvarDrawEntities->value == 4) + { + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + IEngineStudio.StudioDrawHulls( ); + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + } + + if (m_pCvarDrawEntities->value == 5) + { + IEngineStudio.StudioDrawAbsBBox( ); + } + + IEngineStudio.RestoreRenderer(); +} + +/* +==================== +StudioRenderFinal_Hardware + +==================== +*/ +void CStudioModelRenderer::StudioRenderFinal_Hardware( void ) +{ + int i; + int rendermode; + + rendermode = IEngineStudio.GetForceFaceFlags() ? kRenderTransAdd : m_pCurrentEntity->curstate.rendermode; + IEngineStudio.SetupRenderer( rendermode ); + + if (m_pCvarDrawEntities->value == 2) + { + IEngineStudio.StudioDrawBones(); + } + else if (m_pCvarDrawEntities->value == 3) + { + IEngineStudio.StudioDrawHulls(); + } + else + { + for (i=0 ; i < m_pStudioHeader->numbodyparts ; i++) + { + IEngineStudio.StudioSetupModel( i, (void **)&m_pBodyPart, (void **)&m_pSubModel ); + + if (m_fDoInterp) + { + // interpolation messes up bounding boxes. + m_pCurrentEntity->trivial_accept = 0; + } + + IEngineStudio.GL_SetRenderMode( rendermode ); + IEngineStudio.StudioDrawPoints(); + IEngineStudio.GL_StudioDrawShadow(); + } + } + + if ( m_pCvarDrawEntities->value == 4 ) + { + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + IEngineStudio.StudioDrawHulls( ); + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + } + + IEngineStudio.RestoreRenderer(); +} + +/* +==================== +StudioRenderFinal + +==================== +*/ +void CStudioModelRenderer::StudioRenderFinal(void) +{ + if ( IEngineStudio.IsHardware() ) + { + StudioRenderFinal_Hardware(); + } + else + { + StudioRenderFinal_Software(); + } +} + + diff --git a/cl_dll/StudioModelRenderer.h b/cl_dll/StudioModelRenderer.h new file mode 100644 index 0000000..8f1427f --- /dev/null +++ b/cl_dll/StudioModelRenderer.h @@ -0,0 +1,189 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined ( STUDIOMODELRENDERER_H ) +#define STUDIOMODELRENDERER_H +#if defined( _WIN32 ) +#pragma once +#endif + +/* +==================== +CStudioModelRenderer + +==================== +*/ +class CStudioModelRenderer +{ +public: + // Construction/Destruction + CStudioModelRenderer( void ); + virtual ~CStudioModelRenderer( void ); + + // Initialization + virtual void Init( void ); + +public: + // Public Interfaces + virtual int StudioDrawModel ( int flags ); + virtual int StudioDrawPlayer ( int flags, struct entity_state_s *pplayer ); + +public: + // Local interfaces + // + + // Look up animation data for sequence + virtual mstudioanim_t *StudioGetAnim ( model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc ); + + // Interpolate model position and angles and set up matrices + virtual void StudioSetUpTransform (int trivial_accept); + + // Set up model bone positions + virtual void StudioSetupBones ( void ); + + // Find final attachment points + virtual void StudioCalcAttachments ( void ); + + // Save bone matrices and names + virtual void StudioSaveBones( void ); + + // Merge cached bones with current bones for model + virtual void StudioMergeBones ( model_t *m_pSubModel ); + + // Determine interpolation fraction + virtual float StudioEstimateInterpolant( void ); + + // Determine current frame for rendering + virtual float StudioEstimateFrame ( mstudioseqdesc_t *pseqdesc ); + + // Apply special effects to transform matrix + virtual void StudioFxTransform( cl_entity_t *ent, float transform[3][4] ); + + // Spherical interpolation of bones + virtual void StudioSlerpBones ( vec4_t q1[], float pos1[][3], vec4_t q2[], float pos2[][3], float s ); + + // Compute bone adjustments ( bone controllers ) + virtual void StudioCalcBoneAdj ( float dadt, float *adj, const byte *pcontroller1, const byte *pcontroller2, byte mouthopen ); + + // Get bone quaternions + virtual void StudioCalcBoneQuaterion ( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *q ); + + // Get bone positions + virtual void StudioCalcBonePosition ( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *pos ); + + // Compute rotations + virtual void StudioCalcRotations ( float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f ); + + // Send bones and verts to renderer + virtual void StudioRenderModel ( void ); + + // Finalize rendering + virtual void StudioRenderFinal (void); + + // GL&D3D vs. Software renderer finishing functions + virtual void StudioRenderFinal_Software ( void ); + virtual void StudioRenderFinal_Hardware ( void ); + + // Player specific data + // Determine pitch and blending amounts for players + virtual void StudioPlayerBlend ( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ); + + // Estimate gait frame for player + virtual void StudioEstimateGait ( entity_state_t *pplayer ); + + // Process movement of player + virtual void StudioProcessGait ( entity_state_t *pplayer ); + +public: + + // Client clock + double m_clTime; + // Old Client clock + double m_clOldTime; + + // Do interpolation? + int m_fDoInterp; + // Do gait estimation? + int m_fGaitEstimation; + + // Current render frame # + int m_nFrameCount; + + // Cvars that studio model code needs to reference + // + // Use high quality models? + cvar_t *m_pCvarHiModels; + // Developer debug output desired? + cvar_t *m_pCvarDeveloper; + // Draw entities bone hit boxes, etc? + cvar_t *m_pCvarDrawEntities; + + // The entity which we are currently rendering. + cl_entity_t *m_pCurrentEntity; + + // The model for the entity being rendered + model_t *m_pRenderModel; + + // Player info for current player, if drawing a player + player_info_t *m_pPlayerInfo; + + // The index of the player being drawn + int m_nPlayerIndex; + + // The player's gait movement + float m_flGaitMovement; + + // Pointer to header block for studio model data + studiohdr_t *m_pStudioHeader; + + // Pointers to current body part and submodel + mstudiobodyparts_t *m_pBodyPart; + mstudiomodel_t *m_pSubModel; + + // Palette substition for top and bottom of model + int m_nTopColor; + int m_nBottomColor; + + // + // Sprite model used for drawing studio model chrome + model_t *m_pChromeSprite; + + // Caching + // Number of bones in bone cache + int m_nCachedBones; + // Names of cached bones + char m_nCachedBoneNames[ MAXSTUDIOBONES ][ 32 ]; + // Cached bone & light transformation matrices + float m_rgCachedBoneTransform [ MAXSTUDIOBONES ][ 3 ][ 4 ]; + float m_rgCachedLightTransform[ MAXSTUDIOBONES ][ 3 ][ 4 ]; + + // Software renderer scale factors + float m_fSoftwareXScale, m_fSoftwareYScale; + + // Current view vectors and render origin + float m_vUp[ 3 ]; + float m_vRight[ 3 ]; + float m_vNormal[ 3 ]; + + float m_vRenderOrigin[ 3 ]; + + // Model render counters ( from engine ) + int *m_pStudioModelCount; + int *m_pModelsDrawn; + + // Matrices + // Model to world transformation + float (*m_protationmatrix)[ 3 ][ 4 ]; + // Model to view transformation + float (*m_paliastransform)[ 3 ][ 4 ]; + + // Concatenated bone and light transforms + float (*m_pbonetransform) [ MAXSTUDIOBONES ][ 3 ][ 4 ]; + float (*m_plighttransform)[ MAXSTUDIOBONES ][ 3 ][ 4 ]; +}; + +#endif // STUDIOMODELRENDERER_H \ No newline at end of file diff --git a/cl_dll/ammo.cpp b/cl_dll/ammo.cpp new file mode 100644 index 0000000..6121b68 --- /dev/null +++ b/cl_dll/ammo.cpp @@ -0,0 +1,1200 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Ammo.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" +#include "pm_shared.h" + +#include +#include + +#include "ammohistory.h" +#include "vgui_TeamFortressViewport.h" + +WEAPON *gpActiveSel; // NULL means off, 1 means just the menu bar, otherwise + // this points to the active weapon menu item +WEAPON *gpLastSel; // Last weapon menu selection + +client_sprite_t *GetSpriteList(client_sprite_t *pList, const char *psz, int iRes, int iCount); + +WeaponsResource gWR; + +int g_weaponselect = 0; + +void WeaponsResource :: LoadAllWeaponSprites( void ) +{ + for (int i = 0; i < MAX_WEAPONS; i++) + { + if ( rgWeapons[i].iId ) + LoadWeaponSprites( &rgWeapons[i] ); + } +} + +int WeaponsResource :: CountAmmo( int iId ) +{ + if ( iId < 0 ) + return 0; + + return riAmmo[iId]; +} + +int WeaponsResource :: HasAmmo( WEAPON *p ) +{ + if ( !p ) + return FALSE; + + // weapons with no max ammo can always be selected + if ( p->iMax1 == -1 ) + return TRUE; + + return (p->iAmmoType == -1) || p->iClip > 0 || CountAmmo(p->iAmmoType) + || CountAmmo(p->iAmmo2Type) || ( p->iFlags & WEAPON_FLAGS_SELECTONEMPTY ); +} + + +void WeaponsResource :: LoadWeaponSprites( WEAPON *pWeapon ) +{ + int i, iRes; + + if (ScreenWidth < 640) + iRes = 320; + else + iRes = 640; + + char sz[128]; + + if ( !pWeapon ) + return; + + memset( &pWeapon->rcActive, 0, sizeof(wrect_t) ); + memset( &pWeapon->rcInactive, 0, sizeof(wrect_t) ); + memset( &pWeapon->rcAmmo, 0, sizeof(wrect_t) ); + memset( &pWeapon->rcAmmo2, 0, sizeof(wrect_t) ); + pWeapon->hInactive = 0; + pWeapon->hActive = 0; + pWeapon->hAmmo = 0; + pWeapon->hAmmo2 = 0; + + sprintf(sz, "sprites/%s.txt", pWeapon->szName); + client_sprite_t *pList = SPR_GetList(sz, &i); + + if (!pList) + return; + + client_sprite_t *p; + + p = GetSpriteList( pList, "crosshair", iRes, i ); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hCrosshair = SPR_Load(sz); + pWeapon->rcCrosshair = p->rc; + } + else + pWeapon->hCrosshair = NULL; + + p = GetSpriteList(pList, "autoaim", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hAutoaim = SPR_Load(sz); + pWeapon->rcAutoaim = p->rc; + } + else + pWeapon->hAutoaim = 0; + + p = GetSpriteList( pList, "zoom", iRes, i ); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hZoomedCrosshair = SPR_Load(sz); + pWeapon->rcZoomedCrosshair = p->rc; + } + else + { + pWeapon->hZoomedCrosshair = pWeapon->hCrosshair; //default to non-zoomed crosshair + pWeapon->rcZoomedCrosshair = pWeapon->rcCrosshair; + } + + p = GetSpriteList(pList, "zoom_autoaim", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hZoomedAutoaim = SPR_Load(sz); + pWeapon->rcZoomedAutoaim = p->rc; + } + else + { + pWeapon->hZoomedAutoaim = pWeapon->hZoomedCrosshair; //default to zoomed crosshair + pWeapon->rcZoomedAutoaim = pWeapon->rcZoomedCrosshair; + } + + p = GetSpriteList(pList, "weapon", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hInactive = SPR_Load(sz); + pWeapon->rcInactive = p->rc; + + gHR.iHistoryGap = max( gHR.iHistoryGap, pWeapon->rcActive.bottom - pWeapon->rcActive.top ); + } + else + pWeapon->hInactive = 0; + + p = GetSpriteList(pList, "weapon_s", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hActive = SPR_Load(sz); + pWeapon->rcActive = p->rc; + } + else + pWeapon->hActive = 0; + + p = GetSpriteList(pList, "ammo", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hAmmo = SPR_Load(sz); + pWeapon->rcAmmo = p->rc; + + gHR.iHistoryGap = max( gHR.iHistoryGap, pWeapon->rcActive.bottom - pWeapon->rcActive.top ); + } + else + pWeapon->hAmmo = 0; + + p = GetSpriteList(pList, "ammo2", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hAmmo2 = SPR_Load(sz); + pWeapon->rcAmmo2 = p->rc; + + gHR.iHistoryGap = max( gHR.iHistoryGap, pWeapon->rcActive.bottom - pWeapon->rcActive.top ); + } + else + pWeapon->hAmmo2 = 0; + +} + +// Returns the first weapon for a given slot. +WEAPON *WeaponsResource :: GetFirstPos( int iSlot ) +{ + WEAPON *pret = NULL; + + for (int i = 0; i < MAX_WEAPON_POSITIONS; i++) + { + if ( rgSlots[iSlot][i] && HasAmmo( rgSlots[iSlot][i] ) ) + { + pret = rgSlots[iSlot][i]; + break; + } + } + + return pret; +} + + +WEAPON* WeaponsResource :: GetNextActivePos( int iSlot, int iSlotPos ) +{ + if ( iSlotPos >= MAX_WEAPON_POSITIONS || iSlot >= MAX_WEAPON_SLOTS ) + return NULL; + + WEAPON *p = gWR.rgSlots[ iSlot ][ iSlotPos+1 ]; + + if ( !p || !gWR.HasAmmo(p) ) + return GetNextActivePos( iSlot, iSlotPos + 1 ); + + return p; +} + + +int giBucketHeight, giBucketWidth, giABHeight, giABWidth; // Ammo Bar width and height + +HSPRITE ghsprBuckets; // Sprite for top row of weapons menu + +DECLARE_MESSAGE(m_Ammo, CurWeapon ); // Current weapon and clip +DECLARE_MESSAGE(m_Ammo, WeaponList); // new weapon type +DECLARE_MESSAGE(m_Ammo, AmmoX); // update known ammo type's count +DECLARE_MESSAGE(m_Ammo, AmmoPickup); // flashes an ammo pickup record +DECLARE_MESSAGE(m_Ammo, WeapPickup); // flashes a weapon pickup record +DECLARE_MESSAGE(m_Ammo, HideWeapon); // hides the weapon, ammo, and crosshair displays temporarily +DECLARE_MESSAGE(m_Ammo, ItemPickup); + +DECLARE_COMMAND(m_Ammo, Slot1); +DECLARE_COMMAND(m_Ammo, Slot2); +DECLARE_COMMAND(m_Ammo, Slot3); +DECLARE_COMMAND(m_Ammo, Slot4); +DECLARE_COMMAND(m_Ammo, Slot5); +DECLARE_COMMAND(m_Ammo, Slot6); +DECLARE_COMMAND(m_Ammo, Slot7); +DECLARE_COMMAND(m_Ammo, Slot8); +DECLARE_COMMAND(m_Ammo, Slot9); +DECLARE_COMMAND(m_Ammo, Slot10); +DECLARE_COMMAND(m_Ammo, Close); +DECLARE_COMMAND(m_Ammo, NextWeapon); +DECLARE_COMMAND(m_Ammo, PrevWeapon); + +// width of ammo fonts +#define AMMO_SMALL_WIDTH 10 +#define AMMO_LARGE_WIDTH 20 + +#define HISTORY_DRAW_TIME "5" + +int CHudAmmo::Init(void) +{ + gHUD.AddHudElem(this); + + HOOK_MESSAGE(CurWeapon); + HOOK_MESSAGE(WeaponList); + HOOK_MESSAGE(AmmoPickup); + HOOK_MESSAGE(WeapPickup); + HOOK_MESSAGE(ItemPickup); + HOOK_MESSAGE(HideWeapon); + HOOK_MESSAGE(AmmoX); + + HOOK_COMMAND("slot1", Slot1); + HOOK_COMMAND("slot2", Slot2); + HOOK_COMMAND("slot3", Slot3); + HOOK_COMMAND("slot4", Slot4); + HOOK_COMMAND("slot5", Slot5); + HOOK_COMMAND("slot6", Slot6); + HOOK_COMMAND("slot7", Slot7); + HOOK_COMMAND("slot8", Slot8); + HOOK_COMMAND("slot9", Slot9); + HOOK_COMMAND("slot10", Slot10); + HOOK_COMMAND("cancelselect", Close); + HOOK_COMMAND("invnext", NextWeapon); + HOOK_COMMAND("invprev", PrevWeapon); + + Reset(); + + CVAR_CREATE( "hud_drawhistory_time", HISTORY_DRAW_TIME, 0 ); + CVAR_CREATE( "hud_fastswitch", "0", FCVAR_ARCHIVE ); // controls whether or not weapons can be selected in one keypress + + m_iFlags |= HUD_ACTIVE; //!!! + + gWR.Init(); + gHR.Init(); + + return 1; +}; + +void CHudAmmo::Reset(void) +{ + m_fFade = 0; + m_iFlags |= HUD_ACTIVE; //!!! + + gpActiveSel = NULL; + gHUD.m_iHideHUDDisplay = 0; + + gWR.Reset(); + gHR.Reset(); +} + +int CHudAmmo::VidInit(void) +{ + // Load sprites for buckets (top row of weapon menu) + m_HUD_bucket0 = gHUD.GetSpriteIndex( "bucket1" ); + m_HUD_selection = gHUD.GetSpriteIndex( "selection" ); + + ghsprBuckets = gHUD.GetSprite(m_HUD_bucket0); + giBucketWidth = gHUD.GetSpriteRect(m_HUD_bucket0).right - gHUD.GetSpriteRect(m_HUD_bucket0).left; + giBucketHeight = gHUD.GetSpriteRect(m_HUD_bucket0).bottom - gHUD.GetSpriteRect(m_HUD_bucket0).top; + + gHR.iHistoryGap = max( gHR.iHistoryGap, gHUD.GetSpriteRect(m_HUD_bucket0).bottom - gHUD.GetSpriteRect(m_HUD_bucket0).top); + + // If we've already loaded weapons, let's get new sprites + gWR.LoadAllWeaponSprites(); + + if (ScreenWidth >= 640) + { + giABWidth = 20; + giABHeight = 4; + } + else + { + giABWidth = 10; + giABHeight = 2; + } + + return 1; +} + +// +// Think: +// Used for selection of weapon menu item. +// +void CHudAmmo::Think(void) +{ + if ( gHUD.m_fPlayerDead ) + return; + + if ( gHUD.m_iWeaponBits != gWR.iOldWeaponBits ) + { + gWR.iOldWeaponBits = gHUD.m_iWeaponBits; + + for (int i = MAX_WEAPONS-1; i > 0; i-- ) + { + WEAPON *p = gWR.GetWeapon(i); + + if ( p ) + { + if ( gHUD.m_iWeaponBits & ( 1 << p->iId ) ) + gWR.PickupWeapon( p ); + else + gWR.DropWeapon( p ); + } + } + } + + if (!gpActiveSel) + return; + + // has the player selected one? + if (gHUD.m_iKeyBits & IN_ATTACK) + { + if (gpActiveSel != (WEAPON *)1) + { + ServerCmd(gpActiveSel->szName); + g_weaponselect = gpActiveSel->iId; + } + + gpLastSel = gpActiveSel; + gpActiveSel = NULL; + gHUD.m_iKeyBits &= ~IN_ATTACK; + + PlaySound("common/wpn_select.wav", 1); + } + +} + +// +// Helper function to return a Ammo pointer from id +// + +HSPRITE* WeaponsResource :: GetAmmoPicFromWeapon( int iAmmoId, wrect_t& rect ) +{ + for ( int i = 0; i < MAX_WEAPONS; i++ ) + { + if ( rgWeapons[i].iAmmoType == iAmmoId ) + { + rect = rgWeapons[i].rcAmmo; + return &rgWeapons[i].hAmmo; + } + else if ( rgWeapons[i].iAmmo2Type == iAmmoId ) + { + rect = rgWeapons[i].rcAmmo2; + return &rgWeapons[i].hAmmo2; + } + } + + return NULL; +} + + +// Menu Selection Code + +void WeaponsResource :: SelectSlot( int iSlot, int fAdvance, int iDirection ) +{ + if ( gHUD.m_Menu.m_fMenuDisplayed && (fAdvance == FALSE) && (iDirection == 1) ) + { // menu is overriding slot use commands + gHUD.m_Menu.SelectMenuItem( iSlot + 1 ); // slots are one off the key numbers + return; + } + + if ( iSlot > MAX_WEAPON_SLOTS ) + return; + + if ( gHUD.m_fPlayerDead || gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL ) ) + return; + + if (!(gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT)) )) + return; + + if ( ! ( gHUD.m_iWeaponBits & ~(1<<(WEAPON_SUIT)) )) + return; + + WEAPON *p = NULL; + bool fastSwitch = CVAR_GET_FLOAT( "hud_fastswitch" ) != 0; + + if ( (gpActiveSel == NULL) || (gpActiveSel == (WEAPON *)1) || (iSlot != gpActiveSel->iSlot) ) + { + PlaySound( "common/wpn_hudon.wav", 1 ); + p = GetFirstPos( iSlot ); + + if ( p && fastSwitch ) // check for fast weapon switch mode + { + // if fast weapon switch is on, then weapons can be selected in a single keypress + // but only if there is only one item in the bucket + WEAPON *p2 = GetNextActivePos( p->iSlot, p->iSlotPos ); + if ( !p2 ) + { // only one active item in bucket, so change directly to weapon + ServerCmd( p->szName ); + g_weaponselect = p->iId; + return; + } + } + } + else + { + PlaySound("common/wpn_moveselect.wav", 1); + if ( gpActiveSel ) + p = GetNextActivePos( gpActiveSel->iSlot, gpActiveSel->iSlotPos ); + if ( !p ) + p = GetFirstPos( iSlot ); + } + + + if ( !p ) // no selection found + { + // just display the weapon list, unless fastswitch is on just ignore it + if ( !fastSwitch ) + gpActiveSel = (WEAPON *)1; + else + gpActiveSel = NULL; + } + else + gpActiveSel = p; +} + +//------------------------------------------------------------------------ +// Message Handlers +//------------------------------------------------------------------------ + +// +// AmmoX -- Update the count of a known type of ammo +// +int CHudAmmo::MsgFunc_AmmoX(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + int iIndex = READ_BYTE(); + int iCount = READ_BYTE(); + + gWR.SetAmmo( iIndex, abs(iCount) ); + + return 1; +} + +int CHudAmmo::MsgFunc_AmmoPickup( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + int iIndex = READ_BYTE(); + int iCount = READ_BYTE(); + + // Add ammo to the history + gHR.AddToHistory( HISTSLOT_AMMO, iIndex, abs(iCount) ); + + return 1; +} + +int CHudAmmo::MsgFunc_WeapPickup( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + int iIndex = READ_BYTE(); + + // Add the weapon to the history + gHR.AddToHistory( HISTSLOT_WEAP, iIndex ); + + return 1; +} + +int CHudAmmo::MsgFunc_ItemPickup( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + const char *szName = READ_STRING(); + + // Add the weapon to the history + gHR.AddToHistory( HISTSLOT_ITEM, szName ); + + return 1; +} + + +int CHudAmmo::MsgFunc_HideWeapon( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + gHUD.m_iHideHUDDisplay = READ_BYTE(); + + if (gEngfuncs.IsSpectateOnly()) + return 1; + + if ( gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL ) ) + { + static wrect_t nullrc; + gpActiveSel = NULL; + SetCrosshair( 0, nullrc, 0, 0, 0 ); + } + else + { + if ( m_pWeapon ) + SetCrosshair( m_pWeapon->hCrosshair, m_pWeapon->rcCrosshair, 255, 255, 255 ); + } + + return 1; +} + +// +// CurWeapon: Update hud state with the current weapon and clip count. Ammo +// counts are updated with AmmoX. Server assures that the Weapon ammo type +// numbers match a real ammo type. +// +int CHudAmmo::MsgFunc_CurWeapon(const char *pszName, int iSize, void *pbuf ) +{ + static wrect_t nullrc; + int fOnTarget = FALSE; + + BEGIN_READ( pbuf, iSize ); + + int iState = READ_BYTE(); + int iId = READ_CHAR(); + int iClip = READ_CHAR(); + + // detect if we're also on target + if ( iState > 1 ) + { + fOnTarget = TRUE; + } + + if ( iId < 1 ) + { + SetCrosshair(0, nullrc, 0, 0, 0); + return 0; + } + + if ( g_iUser1 != OBS_IN_EYE ) + { + // Is player dead??? + if ((iId == -1) && (iClip == -1)) + { + gHUD.m_fPlayerDead = TRUE; + gpActiveSel = NULL; + return 1; + } + gHUD.m_fPlayerDead = FALSE; + } + + WEAPON *pWeapon = gWR.GetWeapon( iId ); + + if ( !pWeapon ) + return 0; + + if ( iClip < -1 ) + pWeapon->iClip = abs(iClip); + else + pWeapon->iClip = iClip; + + + if ( iState == 0 ) // we're not the current weapon, so update no more + return 1; + + m_pWeapon = pWeapon; + + if ( gHUD.m_iFOV >= 90 ) + { // normal crosshairs + if (fOnTarget && m_pWeapon->hAutoaim) + SetCrosshair(m_pWeapon->hAutoaim, m_pWeapon->rcAutoaim, 255, 255, 255); + else + SetCrosshair(m_pWeapon->hCrosshair, m_pWeapon->rcCrosshair, 255, 255, 255); + } + else + { // zoomed crosshairs + if (fOnTarget && m_pWeapon->hZoomedAutoaim) + SetCrosshair(m_pWeapon->hZoomedAutoaim, m_pWeapon->rcZoomedAutoaim, 255, 255, 255); + else + SetCrosshair(m_pWeapon->hZoomedCrosshair, m_pWeapon->rcZoomedCrosshair, 255, 255, 255); + + } + + m_fFade = 200.0f; //!!! + m_iFlags |= HUD_ACTIVE; + + return 1; +} + +// +// WeaponList -- Tells the hud about a new weapon type. +// +int CHudAmmo::MsgFunc_WeaponList(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + WEAPON Weapon; + + strcpy( Weapon.szName, READ_STRING() ); + Weapon.iAmmoType = (int)READ_CHAR(); + + Weapon.iMax1 = READ_BYTE(); + if (Weapon.iMax1 == 255) + Weapon.iMax1 = -1; + + Weapon.iAmmo2Type = READ_CHAR(); + Weapon.iMax2 = READ_BYTE(); + if (Weapon.iMax2 == 255) + Weapon.iMax2 = -1; + + Weapon.iSlot = READ_CHAR(); + Weapon.iSlotPos = READ_CHAR(); + Weapon.iId = READ_CHAR(); + Weapon.iFlags = READ_BYTE(); + Weapon.iClip = 0; + + gWR.AddWeapon( &Weapon ); + + return 1; + +} + +//------------------------------------------------------------------------ +// Command Handlers +//------------------------------------------------------------------------ +// Slot button pressed +void CHudAmmo::SlotInput( int iSlot ) +{ + if ( gViewPort && gViewPort->SlotInput( iSlot ) ) + return; + + gWR.SelectSlot(iSlot, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot1(void) +{ + SlotInput( 0 ); +} + +void CHudAmmo::UserCmd_Slot2(void) +{ + SlotInput( 1 ); +} + +void CHudAmmo::UserCmd_Slot3(void) +{ + SlotInput( 2 ); +} + +void CHudAmmo::UserCmd_Slot4(void) +{ + SlotInput( 3 ); +} + +void CHudAmmo::UserCmd_Slot5(void) +{ + SlotInput( 4 ); +} + +void CHudAmmo::UserCmd_Slot6(void) +{ + SlotInput( 5 ); +} + +void CHudAmmo::UserCmd_Slot7(void) +{ + SlotInput( 6 ); +} + +void CHudAmmo::UserCmd_Slot8(void) +{ + SlotInput( 7 ); +} + +void CHudAmmo::UserCmd_Slot9(void) +{ + SlotInput( 8 ); +} + +void CHudAmmo::UserCmd_Slot10(void) +{ + SlotInput( 9 ); +} + +void CHudAmmo::UserCmd_Close(void) +{ + if (gpActiveSel) + { + gpLastSel = gpActiveSel; + gpActiveSel = NULL; + PlaySound("common/wpn_hudoff.wav", 1); + } + else + EngineClientCmd("escape"); +} + + +// Selects the next item in the weapon menu +void CHudAmmo::UserCmd_NextWeapon(void) +{ + if ( gHUD.m_fPlayerDead || (gHUD.m_iHideHUDDisplay & (HIDEHUD_WEAPONS | HIDEHUD_ALL)) ) + return; + + if ( !gpActiveSel || gpActiveSel == (WEAPON*)1 ) + gpActiveSel = m_pWeapon; + + int pos = 0; + int slot = 0; + if ( gpActiveSel ) + { + pos = gpActiveSel->iSlotPos + 1; + slot = gpActiveSel->iSlot; + } + + for ( int loop = 0; loop <= 1; loop++ ) + { + for ( ; slot < MAX_WEAPON_SLOTS; slot++ ) + { + for ( ; pos < MAX_WEAPON_POSITIONS; pos++ ) + { + WEAPON *wsp = gWR.GetWeaponSlot( slot, pos ); + + if ( wsp && gWR.HasAmmo(wsp) ) + { + gpActiveSel = wsp; + return; + } + } + + pos = 0; + } + + slot = 0; // start looking from the first slot again + } + + gpActiveSel = NULL; +} + +// Selects the previous item in the menu +void CHudAmmo::UserCmd_PrevWeapon(void) +{ + if ( gHUD.m_fPlayerDead || (gHUD.m_iHideHUDDisplay & (HIDEHUD_WEAPONS | HIDEHUD_ALL)) ) + return; + + if ( !gpActiveSel || gpActiveSel == (WEAPON*)1 ) + gpActiveSel = m_pWeapon; + + int pos = MAX_WEAPON_POSITIONS-1; + int slot = MAX_WEAPON_SLOTS-1; + if ( gpActiveSel ) + { + pos = gpActiveSel->iSlotPos - 1; + slot = gpActiveSel->iSlot; + } + + for ( int loop = 0; loop <= 1; loop++ ) + { + for ( ; slot >= 0; slot-- ) + { + for ( ; pos >= 0; pos-- ) + { + WEAPON *wsp = gWR.GetWeaponSlot( slot, pos ); + + if ( wsp && gWR.HasAmmo(wsp) ) + { + gpActiveSel = wsp; + return; + } + } + + pos = MAX_WEAPON_POSITIONS-1; + } + + slot = MAX_WEAPON_SLOTS-1; + } + + gpActiveSel = NULL; +} + + + +//------------------------------------------------------------------------- +// Drawing code +//------------------------------------------------------------------------- + +int CHudAmmo::Draw(float flTime) +{ + int a, x, y, r, g, b; + int AmmoWidth; + + if (!(gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT)) )) + return 1; + + if ( (gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL )) ) + return 1; + + // Draw Weapon Menu + DrawWList(flTime); + + // Draw ammo pickup history + gHR.DrawAmmoHistory( flTime ); + + if (!(m_iFlags & HUD_ACTIVE)) + return 0; + + if (!m_pWeapon) + return 0; + + WEAPON *pw = m_pWeapon; // shorthand + + // SPR_Draw Ammo + if ((pw->iAmmoType < 0) && (pw->iAmmo2Type < 0)) + return 0; + + + int iFlags = DHN_DRAWZERO; // draw 0 values + + AmmoWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; + + a = (int) max( MIN_ALPHA, m_fFade ); + + if (m_fFade > 0) + m_fFade -= (gHUD.m_flTimeDelta * 20); + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + ScaleColors(r, g, b, a ); + + // Does this weapon have a clip? + y = ScreenHeight - gHUD.m_iFontHeight - gHUD.m_iFontHeight/2; + + // Does weapon have any ammo at all? + if (m_pWeapon->iAmmoType > 0) + { + int iIconWidth = m_pWeapon->rcAmmo.right - m_pWeapon->rcAmmo.left; + + if (pw->iClip >= 0) + { + // room for the number and the '|' and the current ammo + + x = ScreenWidth - (8 * AmmoWidth) - iIconWidth; + x = gHUD.DrawHudNumber(x, y, iFlags | DHN_3DIGITS, pw->iClip, r, g, b); + + wrect_t rc; + rc.top = 0; + rc.left = 0; + rc.right = AmmoWidth; + rc.bottom = 100; + + int iBarWidth = AmmoWidth/10; + + x += AmmoWidth/2; + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + // draw the | bar + FillRGBA(x, y, iBarWidth, gHUD.m_iFontHeight, r, g, b, a); + + x += iBarWidth + AmmoWidth/2;; + + // GL Seems to need this + ScaleColors(r, g, b, a ); + x = gHUD.DrawHudNumber(x, y, iFlags | DHN_3DIGITS, gWR.CountAmmo(pw->iAmmoType), r, g, b); + + + } + else + { + // SPR_Draw a bullets only line + x = ScreenWidth - 4 * AmmoWidth - iIconWidth; + x = gHUD.DrawHudNumber(x, y, iFlags | DHN_3DIGITS, gWR.CountAmmo(pw->iAmmoType), r, g, b); + } + + // Draw the ammo Icon + int iOffset = (m_pWeapon->rcAmmo.bottom - m_pWeapon->rcAmmo.top)/8; + SPR_Set(m_pWeapon->hAmmo, r, g, b); + SPR_DrawAdditive(0, x, y - iOffset, &m_pWeapon->rcAmmo); + } + + // Does weapon have seconday ammo? + if (pw->iAmmo2Type > 0) + { + int iIconWidth = m_pWeapon->rcAmmo2.right - m_pWeapon->rcAmmo2.left; + + // Do we have secondary ammo? + if ((pw->iAmmo2Type != 0) && (gWR.CountAmmo(pw->iAmmo2Type) > 0)) + { + y -= gHUD.m_iFontHeight + gHUD.m_iFontHeight/4; + x = ScreenWidth - 4 * AmmoWidth - iIconWidth; + x = gHUD.DrawHudNumber(x, y, iFlags|DHN_3DIGITS, gWR.CountAmmo(pw->iAmmo2Type), r, g, b); + + // Draw the ammo Icon + SPR_Set(m_pWeapon->hAmmo2, r, g, b); + int iOffset = (m_pWeapon->rcAmmo2.bottom - m_pWeapon->rcAmmo2.top)/8; + SPR_DrawAdditive(0, x, y - iOffset, &m_pWeapon->rcAmmo2); + } + } + return 1; +} + + +// +// Draws the ammo bar on the hud +// +int DrawBar(int x, int y, int width, int height, float f) +{ + int r, g, b; + + if (f < 0) + f = 0; + if (f > 1) + f = 1; + + if (f) + { + int w = f * width; + + // Always show at least one pixel if we have ammo. + if (w <= 0) + w = 1; + UnpackRGB(r, g, b, RGB_GREENISH); + FillRGBA(x, y, w, height, r, g, b, 255); + x += w; + width -= w; + } + + UnpackRGB(r, g, b, RGB_YELLOWISH); + + FillRGBA(x, y, width, height, r, g, b, 128); + + return (x + width); +} + + + +void DrawAmmoBar(WEAPON *p, int x, int y, int width, int height) +{ + if ( !p ) + return; + + if (p->iAmmoType != -1) + { + if (!gWR.CountAmmo(p->iAmmoType)) + return; + + float f = (float)gWR.CountAmmo(p->iAmmoType)/(float)p->iMax1; + + x = DrawBar(x, y, width, height, f); + + + // Do we have secondary ammo too? + + if (p->iAmmo2Type != -1) + { + f = (float)gWR.CountAmmo(p->iAmmo2Type)/(float)p->iMax2; + + x += 5; //!!! + + DrawBar(x, y, width, height, f); + } + } +} + + + + +// +// Draw Weapon Menu +// +int CHudAmmo::DrawWList(float flTime) +{ + int r,g,b,x,y,a,i; + + if ( !gpActiveSel ) + return 0; + + int iActiveSlot; + + if ( gpActiveSel == (WEAPON *)1 ) + iActiveSlot = -1; // current slot has no weapons + else + iActiveSlot = gpActiveSel->iSlot; + + x = 10; //!!! + y = 10; //!!! + + + // Ensure that there are available choices in the active slot + if ( iActiveSlot > 0 ) + { + if ( !gWR.GetFirstPos( iActiveSlot ) ) + { + gpActiveSel = (WEAPON *)1; + iActiveSlot = -1; + } + } + + // Draw top line + for ( i = 0; i < MAX_WEAPON_SLOTS; i++ ) + { + int iWidth; + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + if ( iActiveSlot == i ) + a = 255; + else + a = 192; + + ScaleColors(r, g, b, 255); + SPR_Set(gHUD.GetSprite(m_HUD_bucket0 + i), r, g, b ); + + // make active slot wide enough to accomodate gun pictures + if ( i == iActiveSlot ) + { + WEAPON *p = gWR.GetFirstPos(iActiveSlot); + if ( p ) + iWidth = p->rcActive.right - p->rcActive.left; + else + iWidth = giBucketWidth; + } + else + iWidth = giBucketWidth; + + SPR_DrawAdditive(0, x, y, &gHUD.GetSpriteRect(m_HUD_bucket0 + i)); + + x += iWidth + 5; + } + + + a = 128; //!!! + x = 10; + + // Draw all of the buckets + for (i = 0; i < MAX_WEAPON_SLOTS; i++) + { + y = giBucketHeight + 10; + + // If this is the active slot, draw the bigger pictures, + // otherwise just draw boxes + if ( i == iActiveSlot ) + { + WEAPON *p = gWR.GetFirstPos( i ); + int iWidth = giBucketWidth; + if ( p ) + iWidth = p->rcActive.right - p->rcActive.left; + + for ( int iPos = 0; iPos < MAX_WEAPON_POSITIONS; iPos++ ) + { + p = gWR.GetWeaponSlot( i, iPos ); + + if ( !p || !p->iId ) + continue; + + UnpackRGB( r,g,b, RGB_YELLOWISH ); + + // if active, then we must have ammo. + + if ( gpActiveSel == p ) + { + SPR_Set(p->hActive, r, g, b ); + SPR_DrawAdditive(0, x, y, &p->rcActive); + + SPR_Set(gHUD.GetSprite(m_HUD_selection), r, g, b ); + SPR_DrawAdditive(0, x, y, &gHUD.GetSpriteRect(m_HUD_selection)); + } + else + { + // Draw Weapon if Red if no ammo + + if ( gWR.HasAmmo(p) ) + ScaleColors(r, g, b, 192); + else + { + UnpackRGB(r,g,b, RGB_REDISH); + ScaleColors(r, g, b, 128); + } + + SPR_Set( p->hInactive, r, g, b ); + SPR_DrawAdditive( 0, x, y, &p->rcInactive ); + } + + // Draw Ammo Bar + + DrawAmmoBar(p, x + giABWidth/2, y, giABWidth, giABHeight); + + y += p->rcActive.bottom - p->rcActive.top + 5; + } + + x += iWidth + 5; + + } + else + { + // Draw Row of weapons. + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + for ( int iPos = 0; iPos < MAX_WEAPON_POSITIONS; iPos++ ) + { + WEAPON *p = gWR.GetWeaponSlot( i, iPos ); + + if ( !p || !p->iId ) + continue; + + if ( gWR.HasAmmo(p) ) + { + UnpackRGB(r,g,b, RGB_YELLOWISH); + a = 128; + } + else + { + UnpackRGB(r,g,b, RGB_REDISH); + a = 96; + } + + FillRGBA( x, y, giBucketWidth, giBucketHeight, r, g, b, a ); + + y += giBucketHeight + 5; + } + + x += giBucketWidth + 5; + } + } + + return 1; + +} + + +/* ================================= + GetSpriteList + +Finds and returns the matching +sprite name 'psz' and resolution 'iRes' +in the given sprite list 'pList' +iCount is the number of items in the pList +================================= */ +client_sprite_t *GetSpriteList(client_sprite_t *pList, const char *psz, int iRes, int iCount) +{ + if (!pList) + return NULL; + + int i = iCount; + client_sprite_t *p = pList; + + while(i--) + { + if ((!strcmp(psz, p->szName)) && (p->iRes == iRes)) + return p; + p++; + } + + return NULL; +} diff --git a/cl_dll/ammo.h b/cl_dll/ammo.h new file mode 100644 index 0000000..5e44065 --- /dev/null +++ b/cl_dll/ammo.h @@ -0,0 +1,62 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef __AMMO_H__ +#define __AMMO_H__ + +#define MAX_WEAPON_NAME 128 + + +#define WEAPON_FLAGS_SELECTONEMPTY 1 + +#define WEAPON_IS_ONTARGET 0x40 + +struct WEAPON +{ + char szName[MAX_WEAPON_NAME]; + int iAmmoType; + int iAmmo2Type; + int iMax1; + int iMax2; + int iSlot; + int iSlotPos; + int iFlags; + int iId; + int iClip; + + int iCount; // # of itesm in plist + + HSPRITE hActive; + wrect_t rcActive; + HSPRITE hInactive; + wrect_t rcInactive; + HSPRITE hAmmo; + wrect_t rcAmmo; + HSPRITE hAmmo2; + wrect_t rcAmmo2; + HSPRITE hCrosshair; + wrect_t rcCrosshair; + HSPRITE hAutoaim; + wrect_t rcAutoaim; + HSPRITE hZoomedCrosshair; + wrect_t rcZoomedCrosshair; + HSPRITE hZoomedAutoaim; + wrect_t rcZoomedAutoaim; +}; + +typedef int AMMO; + + +#endif \ No newline at end of file diff --git a/cl_dll/ammo_secondary.cpp b/cl_dll/ammo_secondary.cpp new file mode 100644 index 0000000..e2a2f59 --- /dev/null +++ b/cl_dll/ammo_secondary.cpp @@ -0,0 +1,159 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ammo_secondary.cpp +// +// implementation of CHudAmmoSecondary class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE( m_AmmoSecondary, SecAmmoVal ); +DECLARE_MESSAGE( m_AmmoSecondary, SecAmmoIcon ); + +int CHudAmmoSecondary :: Init( void ) +{ + HOOK_MESSAGE( SecAmmoVal ); + HOOK_MESSAGE( SecAmmoIcon ); + + gHUD.AddHudElem(this); + m_HUD_ammoicon = 0; + + for ( int i = 0; i < MAX_SEC_AMMO_VALUES; i++ ) + m_iAmmoAmounts[i] = -1; // -1 means don't draw this value + + Reset(); + + return 1; +} + +void CHudAmmoSecondary :: Reset( void ) +{ + m_fFade = 0; +} + +int CHudAmmoSecondary :: VidInit( void ) +{ + return 1; +} + +int CHudAmmoSecondary :: Draw(float flTime) +{ + if ( (gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL )) ) + return 1; + + // draw secondary ammo icons above normal ammo readout + int a, x, y, r, g, b, AmmoWidth; + UnpackRGB( r, g, b, RGB_YELLOWISH ); + a = (int) max( MIN_ALPHA, m_fFade ); + if (m_fFade > 0) + m_fFade -= (gHUD.m_flTimeDelta * 20); // slowly lower alpha to fade out icons + ScaleColors( r, g, b, a ); + + AmmoWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; + + y = ScreenHeight - (gHUD.m_iFontHeight*4); // this is one font height higher than the weapon ammo values + x = ScreenWidth - AmmoWidth; + + if ( m_HUD_ammoicon ) + { + // Draw the ammo icon + x -= (gHUD.GetSpriteRect(m_HUD_ammoicon).right - gHUD.GetSpriteRect(m_HUD_ammoicon).left); + y -= (gHUD.GetSpriteRect(m_HUD_ammoicon).top - gHUD.GetSpriteRect(m_HUD_ammoicon).bottom); + + SPR_Set( gHUD.GetSprite(m_HUD_ammoicon), r, g, b ); + SPR_DrawAdditive( 0, x, y, &gHUD.GetSpriteRect(m_HUD_ammoicon) ); + } + else + { // move the cursor by the '0' char instead, since we don't have an icon to work with + x -= AmmoWidth; + y -= (gHUD.GetSpriteRect(gHUD.m_HUD_number_0).top - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).bottom); + } + + // draw the ammo counts, in reverse order, from right to left + for ( int i = MAX_SEC_AMMO_VALUES-1; i >= 0; i-- ) + { + if ( m_iAmmoAmounts[i] < 0 ) + continue; // negative ammo amounts imply that they shouldn't be drawn + + // half a char gap between the ammo number and the previous pic + x -= (AmmoWidth / 2); + + // draw the number, right-aligned + x -= (gHUD.GetNumWidth( m_iAmmoAmounts[i], DHN_DRAWZERO ) * AmmoWidth); + gHUD.DrawHudNumber( x, y, DHN_DRAWZERO, m_iAmmoAmounts[i], r, g, b ); + + if ( i != 0 ) + { + // draw the divider bar + x -= (AmmoWidth / 2); + FillRGBA(x, y, (AmmoWidth/10), gHUD.m_iFontHeight, r, g, b, a); + } + } + + return 1; +} + +// Message handler for Secondary Ammo Value +// accepts one value: +// string: sprite name +int CHudAmmoSecondary :: MsgFunc_SecAmmoIcon( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + m_HUD_ammoicon = gHUD.GetSpriteIndex( READ_STRING() ); + + return 1; +} + +// Message handler for Secondary Ammo Icon +// Sets an ammo value +// takes two values: +// byte: ammo index +// byte: ammo value +int CHudAmmoSecondary :: MsgFunc_SecAmmoVal( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int index = READ_BYTE(); + if ( index < 0 || index >= MAX_SEC_AMMO_VALUES ) + return 1; + + m_iAmmoAmounts[index] = READ_BYTE(); + m_iFlags |= HUD_ACTIVE; + + // check to see if there is anything left to draw + int count = 0; + for ( int i = 0; i < MAX_SEC_AMMO_VALUES; i++ ) + { + count += max( 0, m_iAmmoAmounts[i] ); + } + + if ( count == 0 ) + { // the ammo fields are all empty, so turn off this hud area + m_iFlags &= ~HUD_ACTIVE; + return 1; + } + + // make the icons light up + m_fFade = 200.0f; + + return 1; +} + + diff --git a/cl_dll/ammohistory.cpp b/cl_dll/ammohistory.cpp new file mode 100644 index 0000000..bc78dad --- /dev/null +++ b/cl_dll/ammohistory.cpp @@ -0,0 +1,190 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ammohistory.cpp +// + + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "ammohistory.h" + +HistoryResource gHR; + +#define AMMO_PICKUP_GAP (gHR.iHistoryGap+5) +#define AMMO_PICKUP_PICK_HEIGHT (32 + (gHR.iHistoryGap * 2)) +#define AMMO_PICKUP_HEIGHT_MAX (ScreenHeight - 100) + +#define MAX_ITEM_NAME 32 +int HISTORY_DRAW_TIME = 5; + +// keep a list of items +struct ITEM_INFO +{ + char szName[MAX_ITEM_NAME]; + HSPRITE spr; + wrect_t rect; +}; + +void HistoryResource :: AddToHistory( int iType, int iId, int iCount ) +{ + if ( iType == HISTSLOT_AMMO && !iCount ) + return; // no amount, so don't add + + if ( (((AMMO_PICKUP_GAP * iCurrentHistorySlot) + AMMO_PICKUP_PICK_HEIGHT) > AMMO_PICKUP_HEIGHT_MAX) || (iCurrentHistorySlot >= MAX_HISTORY) ) + { // the pic would have to be drawn too high + // so start from the bottom + iCurrentHistorySlot = 0; + } + + HIST_ITEM *freeslot = &rgAmmoHistory[iCurrentHistorySlot++]; // default to just writing to the first slot + HISTORY_DRAW_TIME = CVAR_GET_FLOAT( "hud_drawhistory_time" ); + + freeslot->type = iType; + freeslot->iId = iId; + freeslot->iCount = iCount; + freeslot->DisplayTime = gHUD.m_flTime + HISTORY_DRAW_TIME; +} + +void HistoryResource :: AddToHistory( int iType, const char *szName, int iCount ) +{ + if ( iType != HISTSLOT_ITEM ) + return; + + if ( (((AMMO_PICKUP_GAP * iCurrentHistorySlot) + AMMO_PICKUP_PICK_HEIGHT) > AMMO_PICKUP_HEIGHT_MAX) || (iCurrentHistorySlot >= MAX_HISTORY) ) + { // the pic would have to be drawn too high + // so start from the bottom + iCurrentHistorySlot = 0; + } + + HIST_ITEM *freeslot = &rgAmmoHistory[iCurrentHistorySlot++]; // default to just writing to the first slot + + // I am really unhappy with all the code in this file + + int i = gHUD.GetSpriteIndex( szName ); + if ( i == -1 ) + return; // unknown sprite name, don't add it to history + + freeslot->iId = i; + freeslot->type = iType; + freeslot->iCount = iCount; + + HISTORY_DRAW_TIME = CVAR_GET_FLOAT( "hud_drawhistory_time" ); + freeslot->DisplayTime = gHUD.m_flTime + HISTORY_DRAW_TIME; +} + + +void HistoryResource :: CheckClearHistory( void ) +{ + for ( int i = 0; i < MAX_HISTORY; i++ ) + { + if ( rgAmmoHistory[i].type ) + return; + } + + iCurrentHistorySlot = 0; +} + +// +// Draw Ammo pickup history +// +int HistoryResource :: DrawAmmoHistory( float flTime ) +{ + for ( int i = 0; i < MAX_HISTORY; i++ ) + { + if ( rgAmmoHistory[i].type ) + { + rgAmmoHistory[i].DisplayTime = min( rgAmmoHistory[i].DisplayTime, gHUD.m_flTime + HISTORY_DRAW_TIME ); + + if ( rgAmmoHistory[i].DisplayTime <= flTime ) + { // pic drawing time has expired + memset( &rgAmmoHistory[i], 0, sizeof(HIST_ITEM) ); + CheckClearHistory(); + } + else if ( rgAmmoHistory[i].type == HISTSLOT_AMMO ) + { + wrect_t rcPic; + HSPRITE *spr = gWR.GetAmmoPicFromWeapon( rgAmmoHistory[i].iId, rcPic ); + + int r, g, b; + UnpackRGB(r,g,b, RGB_YELLOWISH); + float scale = (rgAmmoHistory[i].DisplayTime - flTime) * 80; + ScaleColors(r, g, b, min(scale, 255) ); + + // Draw the pic + int ypos = ScreenHeight - (AMMO_PICKUP_PICK_HEIGHT + (AMMO_PICKUP_GAP * i)); + int xpos = ScreenWidth - 24; + if ( spr && *spr ) // weapon isn't loaded yet so just don't draw the pic + { // the dll has to make sure it has sent info the weapons you need + SPR_Set( *spr, r, g, b ); + SPR_DrawAdditive( 0, xpos, ypos, &rcPic ); + } + + // Draw the number + gHUD.DrawHudNumberString( xpos - 10, ypos, xpos - 100, rgAmmoHistory[i].iCount, r, g, b ); + } + else if ( rgAmmoHistory[i].type == HISTSLOT_WEAP ) + { + WEAPON *weap = gWR.GetWeapon( rgAmmoHistory[i].iId ); + + if ( !weap ) + return 1; // we don't know about the weapon yet, so don't draw anything + + int r, g, b; + UnpackRGB(r,g,b, RGB_YELLOWISH); + + if ( !gWR.HasAmmo( weap ) ) + UnpackRGB(r,g,b, RGB_REDISH); // if the weapon doesn't have ammo, display it as red + + float scale = (rgAmmoHistory[i].DisplayTime - flTime) * 80; + ScaleColors(r, g, b, min(scale, 255) ); + + int ypos = ScreenHeight - (AMMO_PICKUP_PICK_HEIGHT + (AMMO_PICKUP_GAP * i)); + int xpos = ScreenWidth - (weap->rcInactive.right - weap->rcInactive.left); + SPR_Set( weap->hInactive, r, g, b ); + SPR_DrawAdditive( 0, xpos, ypos, &weap->rcInactive ); + } + else if ( rgAmmoHistory[i].type == HISTSLOT_ITEM ) + { + int r, g, b; + + if ( !rgAmmoHistory[i].iId ) + continue; // sprite not loaded + + wrect_t rect = gHUD.GetSpriteRect( rgAmmoHistory[i].iId ); + + UnpackRGB(r,g,b, RGB_YELLOWISH); + float scale = (rgAmmoHistory[i].DisplayTime - flTime) * 80; + ScaleColors(r, g, b, min(scale, 255) ); + + int ypos = ScreenHeight - (AMMO_PICKUP_PICK_HEIGHT + (AMMO_PICKUP_GAP * i)); + int xpos = ScreenWidth - (rect.right - rect.left) - 10; + + SPR_Set( gHUD.GetSprite( rgAmmoHistory[i].iId ), r, g, b ); + SPR_DrawAdditive( 0, xpos, ypos, &rect ); + } + } + } + + + return 1; +} + + diff --git a/cl_dll/ammohistory.h b/cl_dll/ammohistory.h new file mode 100644 index 0000000..48eeafc --- /dev/null +++ b/cl_dll/ammohistory.h @@ -0,0 +1,143 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ammohistory.h +// + +// this is the max number of items in each bucket +#define MAX_WEAPON_POSITIONS MAX_WEAPON_SLOTS + +class WeaponsResource +{ +private: + // Information about weapons & ammo + WEAPON rgWeapons[MAX_WEAPONS]; // Weapons Array + + // counts of weapons * ammo + WEAPON* rgSlots[MAX_WEAPON_SLOTS+1][MAX_WEAPON_POSITIONS+1]; // The slots currently in use by weapons. The value is a pointer to the weapon; if it's NULL, no weapon is there + int riAmmo[MAX_AMMO_TYPES]; // count of each ammo type + +public: + void Init( void ) + { + memset( rgWeapons, 0, sizeof rgWeapons ); + Reset(); + } + + void Reset( void ) + { + iOldWeaponBits = 0; + memset( rgSlots, 0, sizeof rgSlots ); + memset( riAmmo, 0, sizeof riAmmo ); + } + +///// WEAPON ///// + int iOldWeaponBits; + + WEAPON *GetWeapon( int iId ) { return &rgWeapons[iId]; } + void AddWeapon( WEAPON *wp ) + { + rgWeapons[ wp->iId ] = *wp; + LoadWeaponSprites( &rgWeapons[ wp->iId ] ); + } + + void PickupWeapon( WEAPON *wp ) + { + rgSlots[ wp->iSlot ][ wp->iSlotPos ] = wp; + } + + void DropWeapon( WEAPON *wp ) + { + rgSlots[ wp->iSlot ][ wp->iSlotPos ] = NULL; + } + + void DropAllWeapons( void ) + { + for ( int i = 0; i < MAX_WEAPONS; i++ ) + { + if ( rgWeapons[i].iId ) + DropWeapon( &rgWeapons[i] ); + } + } + + WEAPON* GetWeaponSlot( int slot, int pos ) { return rgSlots[slot][pos]; } + + void LoadWeaponSprites( WEAPON* wp ); + void LoadAllWeaponSprites( void ); + WEAPON* GetFirstPos( int iSlot ); + void SelectSlot( int iSlot, int fAdvance, int iDirection ); + WEAPON* GetNextActivePos( int iSlot, int iSlotPos ); + + int HasAmmo( WEAPON *p ); + +///// AMMO ///// + AMMO GetAmmo( int iId ) { return iId; } + + void SetAmmo( int iId, int iCount ) { riAmmo[ iId ] = iCount; } + + int CountAmmo( int iId ); + + HSPRITE* GetAmmoPicFromWeapon( int iAmmoId, wrect_t& rect ); +}; + +extern WeaponsResource gWR; + + +#define MAX_HISTORY 12 +enum { + HISTSLOT_EMPTY, + HISTSLOT_AMMO, + HISTSLOT_WEAP, + HISTSLOT_ITEM, +}; + +class HistoryResource +{ +private: + struct HIST_ITEM { + int type; + float DisplayTime; // the time at which this item should be removed from the history + int iCount; + int iId; + }; + + HIST_ITEM rgAmmoHistory[MAX_HISTORY]; + +public: + + void Init( void ) + { + Reset(); + } + + void Reset( void ) + { + memset( rgAmmoHistory, 0, sizeof rgAmmoHistory ); + } + + int iHistoryGap; + int iCurrentHistorySlot; + + void AddToHistory( int iType, int iId, int iCount = 0 ); + void AddToHistory( int iType, const char *szName, int iCount = 0 ); + + void CheckClearHistory( void ); + int DrawAmmoHistory( float flTime ); +}; + +extern HistoryResource gHR; + + + diff --git a/cl_dll/battery.cpp b/cl_dll/battery.cpp new file mode 100644 index 0000000..2befb90 --- /dev/null +++ b/cl_dll/battery.cpp @@ -0,0 +1,158 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// battery.cpp +// +// implementation of CHudBattery class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +DECLARE_MESSAGE(m_Battery, Battery) + +int CHudBattery::Init(void) +{ + m_iBat = 0; + m_fFade = 0; + m_iFlags = 0; + + HOOK_MESSAGE(Battery); + + gHUD.AddHudElem(this); + + return 1; +}; + + +int CHudBattery::VidInit(void) +{ + int HUD_suit_empty = gHUD.GetSpriteIndex( "suit_empty" ); + int HUD_suit_full = gHUD.GetSpriteIndex( "suit_full" ); + + m_hSprite1 = m_hSprite2 = 0; // delaying get sprite handles until we know the sprites are loaded + m_prc1 = &gHUD.GetSpriteRect( HUD_suit_empty ); + m_prc2 = &gHUD.GetSpriteRect( HUD_suit_full ); + m_iHeight = m_prc2->bottom - m_prc1->top; + m_fFade = 0; + return 1; +}; + +int CHudBattery:: MsgFunc_Battery(const char *pszName, int iSize, void *pbuf ) +{ + m_iFlags |= HUD_ACTIVE; + + BEGIN_READ( pbuf, iSize ); + int x = READ_SHORT(); + +#if defined( _TFC ) + int y = READ_SHORT(); + + if ( x != m_iBat || y != m_iBatMax ) + { + m_fFade = FADE_TIME; + m_iBat = x; + m_iBatMax = y; + } +#else + if ( x != m_iBat ) + { + m_fFade = FADE_TIME; + m_iBat = x; + } +#endif + + return 1; +} + + +int CHudBattery::Draw(float flTime) +{ + if ( gHUD.m_iHideHUDDisplay & HIDEHUD_HEALTH ) + return 1; + + int r, g, b, x, y, a; + wrect_t rc; + + rc = *m_prc2; + +#if defined( _TFC ) + float fScale = 0.0; + + if ( m_iBatMax > 0 ) + fScale = 1.0 / (float)m_iBatMax; + + rc.top += m_iHeight * ((float)(m_iBatMax-(min(m_iBatMax,m_iBat))) * fScale); // battery can go from 0 to m_iBatMax so * fScale goes from 0 to 1 +#else + rc.top += m_iHeight * ((float)(100-(min(100,m_iBat))) * 0.01); // battery can go from 0 to 100 so * 0.01 goes from 0 to 1 +#endif + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + if (!(gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT)) )) + return 1; + + // Has health changed? Flash the health # + if (m_fFade) + { + if (m_fFade > FADE_TIME) + m_fFade = FADE_TIME; + + m_fFade -= (gHUD.m_flTimeDelta * 20); + if (m_fFade <= 0) + { + a = 128; + m_fFade = 0; + } + + // Fade the health number back to dim + + a = MIN_ALPHA + (m_fFade/FADE_TIME) * 128; + + } + else + a = MIN_ALPHA; + + ScaleColors(r, g, b, a ); + + int iOffset = (m_prc1->bottom - m_prc1->top)/6; + + y = ScreenHeight - gHUD.m_iFontHeight - gHUD.m_iFontHeight / 2; + x = ScreenWidth/5; + + // make sure we have the right sprite handles + if ( !m_hSprite1 ) + m_hSprite1 = gHUD.GetSprite( gHUD.GetSpriteIndex( "suit_empty" ) ); + if ( !m_hSprite2 ) + m_hSprite2 = gHUD.GetSprite( gHUD.GetSpriteIndex( "suit_full" ) ); + + SPR_Set(m_hSprite1, r, g, b ); + SPR_DrawAdditive( 0, x, y - iOffset, m_prc1); + + if (rc.bottom > rc.top) + { + SPR_Set(m_hSprite2, r, g, b ); + SPR_DrawAdditive( 0, x, y - iOffset + (rc.top - m_prc2->top), &rc); + } + + x += (m_prc1->right - m_prc1->left); + x = gHUD.DrawHudNumber(x, y, DHN_3DIGITS | DHN_DRAWZERO, m_iBat, r, g, b); + + return 1; +} \ No newline at end of file diff --git a/cl_dll/bench.h b/cl_dll/bench.h new file mode 100644 index 0000000..e0a3304 --- /dev/null +++ b/cl_dll/bench.h @@ -0,0 +1,26 @@ +#if !defined ( BENCHH ) +#define BENCHH +#pragma once + +#define FIRST_STAGE 1 +#define SECOND_STAGE 2 +#define THIRD_STAGE 3 +#define FOURTH_STAGE 4 +#define LAST_STAGE ( FOURTH_STAGE ) + +void Bench_CheckStart( void ); + +int Bench_InStage( int stage ); +int Bench_GetPowerPlay( void ); +int Bench_GetStage( void ); +void Bench_SetPowerPlay( int set ); +int Bench_Active( void ); + +void Bench_SetDotAdded( int dot ); +void Bench_SpotPosition( vec3_t dot, vec3_t target ); +void Bench_CheckEntity( int type, struct cl_entity_s *ent, const char *modelname ); +void Bench_AddObjects( void ); +void Bench_SetViewAngles( int recalc_wander, float *viewangles, float frametime, struct usercmd_s *cmd ); +void Bench_SetViewOrigin( float *vieworigin, float frametime ); + +#endif \ No newline at end of file diff --git a/cl_dll/camera.h b/cl_dll/camera.h new file mode 100644 index 0000000..ef22d26 --- /dev/null +++ b/cl_dll/camera.h @@ -0,0 +1,24 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// Camera.h -- defines and such for a 3rd person camera +// NOTE: must include quakedef.h first + +#ifndef _CAMERA_H_ +#define _CAMEA_H_ + +// pitch, yaw, dist +extern vec3_t cam_ofs; +// Using third person camera +extern int cam_thirdperson; + +void CAM_Init( void ); +void CAM_ClearStates( void ); +void CAM_StartMouseMove(void); +void CAM_EndMouseMove(void); + +#endif // _CAMERA_H_ diff --git a/cl_dll/cdll_int.cpp b/cl_dll/cdll_int.cpp new file mode 100644 index 0000000..9cc1e65 --- /dev/null +++ b/cl_dll/cdll_int.cpp @@ -0,0 +1,445 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cdll_int.c +// +// this implementation handles the linking of the engine to the DLL +// + +#include "hud.h" +#include "cl_util.h" +#include "netadr.h" +#undef INTERFACE_H +#include "../public/interface.h" +//#include "vgui_schememanager.h" + +extern "C" +{ +#include "pm_shared.h" +} + +#include +#include "hud_servers.h" +#include "vgui_int.h" +#include "interface.h" + +#ifdef _WIN32 +#include +#endif +#include "Exports.h" +# +#include "tri.h" +#include "vgui_TeamFortressViewport.h" +#include "../public/interface.h" + +cl_enginefunc_t gEngfuncs; +CHud gHUD; +TeamFortressViewport *gViewPort = NULL; + + +#include "particleman.h" +CSysModule *g_hParticleManModule = NULL; +IParticleMan *g_pParticleMan = NULL; + +void CL_LoadParticleMan( void ); +void CL_UnloadParticleMan( void ); + +void InitInput (void); +void EV_HookEvents( void ); +void IN_Commands( void ); + +/* +================================ +HUD_GetHullBounds + + Engine calls this to enumerate player collision hulls, for prediction. Return 0 if the hullnumber doesn't exist. +================================ +*/ +int CL_DLLEXPORT HUD_GetHullBounds( int hullnumber, float *mins, float *maxs ) +{ +// RecClGetHullBounds(hullnumber, mins, maxs); + + int iret = 0; + + switch ( hullnumber ) + { + case 0: // Normal player + mins = Vector(-16, -16, -36); + maxs = Vector(16, 16, 36); + iret = 1; + break; + case 1: // Crouched player + mins = Vector(-16, -16, -18 ); + maxs = Vector(16, 16, 18 ); + iret = 1; + break; + case 2: // Point based hull + mins = Vector( 0, 0, 0 ); + maxs = Vector( 0, 0, 0 ); + iret = 1; + break; + } + + return iret; +} + +/* +================================ +HUD_ConnectionlessPacket + + Return 1 if the packet is valid. Set response_buffer_size if you want to send a response packet. Incoming, it holds the max + size of the response_buffer, so you must zero it out if you choose not to respond. +================================ +*/ +int CL_DLLEXPORT HUD_ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ) +{ +// RecClConnectionlessPacket(net_from, args, response_buffer, response_buffer_size); + + // Parse stuff from args + int max_buffer_size = *response_buffer_size; + + // Zero it out since we aren't going to respond. + // If we wanted to response, we'd write data into response_buffer + *response_buffer_size = 0; + + // Since we don't listen for anything here, just respond that it's a bogus message + // If we didn't reject the message, we'd return 1 for success instead. + return 0; +} + +void CL_DLLEXPORT HUD_PlayerMoveInit( struct playermove_s *ppmove ) +{ +// RecClClientMoveInit(ppmove); + + PM_Init( ppmove ); +} + +char CL_DLLEXPORT HUD_PlayerMoveTexture( char *name ) +{ +// RecClClientTextureType(name); + + return PM_FindTextureType( name ); +} + +void CL_DLLEXPORT HUD_PlayerMove( struct playermove_s *ppmove, int server ) +{ +// RecClClientMove(ppmove, server); + + PM_Move( ppmove, server ); +} + +int CL_DLLEXPORT Initialize( cl_enginefunc_t *pEnginefuncs, int iVersion ) +{ + gEngfuncs = *pEnginefuncs; + +// RecClInitialize(pEnginefuncs, iVersion); + + if (iVersion != CLDLL_INTERFACE_VERSION) + return 0; + + memcpy(&gEngfuncs, pEnginefuncs, sizeof(cl_enginefunc_t)); + + EV_HookEvents(); + CL_LoadParticleMan(); + + // get tracker interface, if any + return 1; +} + + +/* +========================== + HUD_VidInit + +Called when the game initializes +and whenever the vid_mode is changed +so the HUD can reinitialize itself. +========================== +*/ + +int CL_DLLEXPORT HUD_VidInit( void ) +{ +// RecClHudVidInit(); + gHUD.VidInit(); + + VGui_Startup(); + + return 1; +} + +/* +========================== + HUD_Init + +Called whenever the client connects +to a server. Reinitializes all +the hud variables. +========================== +*/ + +void CL_DLLEXPORT HUD_Init( void ) +{ +// RecClHudInit(); + InitInput(); + gHUD.Init(); + Scheme_Init(); +} + + +/* +========================== + HUD_Redraw + +called every screen frame to +redraw the HUD. +=========================== +*/ + +int CL_DLLEXPORT HUD_Redraw( float time, int intermission ) +{ +// RecClHudRedraw(time, intermission); + + gHUD.Redraw( time, intermission ); + + return 1; +} + + +/* +========================== + HUD_UpdateClientData + +called every time shared client +dll/engine data gets changed, +and gives the cdll a chance +to modify the data. + +returns 1 if anything has been changed, 0 otherwise. +========================== +*/ + +int CL_DLLEXPORT HUD_UpdateClientData(client_data_t *pcldata, float flTime ) +{ +// RecClHudUpdateClientData(pcldata, flTime); + + IN_Commands(); + + return gHUD.UpdateClientData(pcldata, flTime ); +} + +/* +========================== + HUD_Reset + +Called at start and end of demos to restore to "non"HUD state. +========================== +*/ + +void CL_DLLEXPORT HUD_Reset( void ) +{ +// RecClHudReset(); + + gHUD.VidInit(); +} + +/* +========================== +HUD_Frame + +Called by engine every frame that client .dll is loaded +========================== +*/ + +void CL_DLLEXPORT HUD_Frame( double time ) +{ +// RecClHudFrame(time); + + ServersThink( time ); + + GetClientVoiceMgr()->Frame(time); +} + + +/* +========================== +HUD_VoiceStatus + +Called when a player starts or stops talking. +========================== +*/ + +void CL_DLLEXPORT HUD_VoiceStatus(int entindex, qboolean bTalking) +{ +//// RecClVoiceStatus(entindex, bTalking); + + GetClientVoiceMgr()->UpdateSpeakerStatus(entindex, bTalking); +} + +/* +========================== +HUD_DirectorMessage + +Called when a director event message was received +========================== +*/ + +void CL_DLLEXPORT HUD_DirectorMessage( int iSize, void *pbuf ) +{ +// RecClDirectorMessage(iSize, pbuf); + + gHUD.m_Spectator.DirectorMessage( iSize, pbuf ); +} + +void CL_UnloadParticleMan( void ) +{ + Sys_UnloadModule( g_hParticleManModule ); + + g_pParticleMan = NULL; + g_hParticleManModule = NULL; +} + +void CL_LoadParticleMan( void ) +{ + char szPDir[512]; + + if ( gEngfuncs.COM_ExpandFilename( PARTICLEMAN_DLLNAME, szPDir, sizeof( szPDir ) ) == FALSE ) + { + g_pParticleMan = NULL; + g_hParticleManModule = NULL; + return; + } + + g_hParticleManModule = Sys_LoadModule( szPDir ); + CreateInterfaceFn particleManFactory = Sys_GetFactory( g_hParticleManModule ); + + if ( particleManFactory == NULL ) + { + g_pParticleMan = NULL; + g_hParticleManModule = NULL; + return; + } + + g_pParticleMan = (IParticleMan *)particleManFactory( PARTICLEMAN_INTERFACE, NULL); + + if ( g_pParticleMan ) + { + g_pParticleMan->SetUp( &gEngfuncs ); + + // Add custom particle classes here BEFORE calling anything else or you will die. + g_pParticleMan->AddCustomParticleClassSize ( sizeof ( CBaseParticle ) ); + } +} + +cldll_func_dst_t *g_pcldstAddrs; + +extern "C" void CL_DLLEXPORT F(void *pv) +{ + cldll_func_t *pcldll_func = (cldll_func_t *)pv; + + // Hack! + g_pcldstAddrs = ((cldll_func_dst_t *)pcldll_func->pHudVidInitFunc); + + cldll_func_t cldll_func = + { + Initialize, + HUD_Init, + HUD_VidInit, + HUD_Redraw, + HUD_UpdateClientData, + HUD_Reset, + HUD_PlayerMove, + HUD_PlayerMoveInit, + HUD_PlayerMoveTexture, + IN_ActivateMouse, + IN_DeactivateMouse, + IN_MouseEvent, + IN_ClearStates, + IN_Accumulate, + CL_CreateMove, + CL_IsThirdPerson, + CL_CameraOffset, + KB_Find, + CAM_Think, + V_CalcRefdef, + HUD_AddEntity, + HUD_CreateEntities, + HUD_DrawNormalTriangles, + HUD_DrawTransparentTriangles, + HUD_StudioEvent, + HUD_PostRunCmd, + HUD_Shutdown, + HUD_TxferLocalOverrides, + HUD_ProcessPlayerState, + HUD_TxferPredictionData, + Demo_ReadBuffer, + HUD_ConnectionlessPacket, + HUD_GetHullBounds, + HUD_Frame, + HUD_Key_Event, + HUD_TempEntUpdate, + HUD_GetUserEntity, + HUD_VoiceStatus, + HUD_DirectorMessage, + HUD_GetStudioModelInterface, + HUD_ChatInputPosition, + }; + + *pcldll_func = cldll_func; +} + +#include "cl_dll/IGameClientExports.h" + +//----------------------------------------------------------------------------- +// Purpose: Exports functions that are used by the gameUI for UI dialogs +//----------------------------------------------------------------------------- +class CClientExports : public IGameClientExports +{ +public: + // returns the name of the server the user is connected to, if any + virtual const char *GetServerHostName() + { + /*if (gViewPortInterface) + { + return gViewPortInterface->GetServerName(); + }*/ + return ""; + } + + // ingame voice manipulation + virtual bool IsPlayerGameVoiceMuted(int playerIndex) + { + if (GetClientVoiceMgr()) + return GetClientVoiceMgr()->IsPlayerBlocked(playerIndex); + return false; + } + + virtual void MutePlayerGameVoice(int playerIndex) + { + if (GetClientVoiceMgr()) + { + GetClientVoiceMgr()->SetPlayerBlockedState(playerIndex, true); + } + } + + virtual void UnmutePlayerGameVoice(int playerIndex) + { + if (GetClientVoiceMgr()) + { + GetClientVoiceMgr()->SetPlayerBlockedState(playerIndex, false); + } + } +}; + +EXPOSE_SINGLE_INTERFACE(CClientExports, IGameClientExports, GAMECLIENTEXPORTS_INTERFACE_VERSION); diff --git a/cl_dll/cl_dll.dsp b/cl_dll/cl_dll.dsp new file mode 100644 index 0000000..e93c00c --- /dev/null +++ b/cl_dll/cl_dll.dsp @@ -0,0 +1,648 @@ +# Microsoft Developer Studio Project File - Name="cl_dll" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=cl_dll - 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 "cl_dll.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 "cl_dll.mak" CFG="cl_dll - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "cl_dll - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "cl_dll - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "cl_dll - 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" /YX /c +# ADD CPP /nologo /W3 /GR /GX /Zi /O2 /I "..\dlls" /I "." /I "..\tfc" /I "..\public" /I "..\common" /I "..\pm_shared" /I "..\engine" /I "..\utils\vgui\include" /I "..\game_shared" /I "..\external" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "CLIENT_DLL" /D "CLIENT_WEAPONS" /D "HL_DLL" /FR /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /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 /subsystem:windows /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib winmm.lib ..\utils\vgui\lib\win32_vc6\vgui.lib wsock32.lib ..\lib\public\sdl2.lib /nologo /base:"0x01900000" /subsystem:windows /dll /map /debug /machine:I386 /nodefaultlib:"LIBCMTD" /nodefaultlib:"LIBCD" /out:".\Release\client.dll" +# SUBTRACT LINK32 /pdb:none +# Begin Custom Build +InputDir=.\Release +ProjDir=. +InputPath=.\Release\client.dll +InputName=client +SOURCE="$(InputPath)" + +BuildCmds= \ + call ..\filecopy.bat $(InputDir)\$(InputName).dll $(ProjDir)\..\..\game\mod\cl_dlls\$(InputName).dll \ + call ..\filecopy.bat $(InputDir)\$(InputName).pdb $(ProjDir)\..\..\game\mod\cl_dlls\$(InputName).pdb \ + + +"$(ProjDir)\..\..\game\mod\cl_dlls\$(InputName).dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"$(ProjDir)\..\..\game\mod\cl_dlls\$(InputName).pdb" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) +# End Custom Build + +!ELSEIF "$(CFG)" == "cl_dll - 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" /YX /c +# ADD CPP /nologo /G5 /MTd /W3 /Gm /GR /GX /ZI /Od /I "..\dlls" /I "." /I "..\tfc" /I "..\public" /I "..\common" /I "..\pm_shared" /I "..\engine" /I "..\utils\vgui\include" /I "..\game_shared" /I "..\external" /D "_DEBUG" /D "_MBCS" /D "WIN32" /D "_WINDOWS" /D "CLIENT_DLL" /D "CLIENT_WEAPONS" /D "_WINDLL" /D "HL_DLL" /FR /YX /FD /c +# ADD BASE MTL /nologo /D "_DEBUG" /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 gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 +# ADD LINK32 oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib winmm.lib ..\utils\vgui\lib\win32_vc6\vgui.lib wsock32.lib ..\lib\public\sdl2.lib /nologo /base:"0x01900000" /subsystem:windows /dll /debug /machine:I386 /out:".\Debug\client.dll" +# SUBTRACT LINK32 /pdb:none +# Begin Custom Build +InputDir=.\Debug +ProjDir=. +InputPath=.\Debug\client.dll +InputName=client +SOURCE="$(InputPath)" + +BuildCmds= \ + call ..\filecopy.bat $(InputDir)\$(InputName).dll $(ProjDir)\..\..\game\mod\cl_dlls\$(InputName).dll \ + call ..\filecopy.bat $(InputDir)\$(InputName).pdb $(ProjDir)\..\..\game\mod\cl_dlls\$(InputName).pdb \ + + +"$(ProjDir)\..\..\game\mod\cl_dlls\$(InputName).dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"$(ProjDir)\..\..\game\mod\cl_dlls\$(InputName).pdb" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "cl_dll - Win32 Release" +# Name "cl_dll - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Group "hl" + +# PROP Default_Filter "*.cpp" +# Begin Source File + +SOURCE=..\dlls\crossbow.cpp +# End Source File +# Begin Source File + +SOURCE=..\dlls\crowbar.cpp +# End Source File +# Begin Source File + +SOURCE=..\dlls\egon.cpp +# End Source File +# Begin Source File + +SOURCE=.\ev_hldm.cpp +# End Source File +# Begin Source File + +SOURCE=..\dlls\gauss.cpp +# End Source File +# Begin Source File + +SOURCE=..\dlls\handgrenade.cpp +# End Source File +# Begin Source File + +SOURCE=.\hl\hl_baseentity.cpp +# End Source File +# Begin Source File + +SOURCE=.\hl\hl_events.cpp +# End Source File +# Begin Source File + +SOURCE=.\hl\hl_objects.cpp +# End Source File +# Begin Source File + +SOURCE=.\hl\hl_weapons.cpp +# End Source File +# Begin Source File + +SOURCE=..\dlls\wpn_shared\hl_wpn_glock.cpp +# End Source File +# Begin Source File + +SOURCE=..\dlls\hornetgun.cpp +# End Source File +# Begin Source File + +SOURCE=..\dlls\mp5.cpp +# End Source File +# Begin Source File + +SOURCE=..\dlls\python.cpp +# End Source File +# Begin Source File + +SOURCE=..\dlls\rpg.cpp +# End Source File +# Begin Source File + +SOURCE=..\dlls\satchel.cpp +# End Source File +# Begin Source File + +SOURCE=..\dlls\shotgun.cpp +# End Source File +# Begin Source File + +SOURCE=..\dlls\squeakgrenade.cpp +# End Source File +# Begin Source File + +SOURCE=..\dlls\tripmine.cpp +# End Source File +# End Group +# Begin Source File + +SOURCE=.\ammo.cpp +# End Source File +# Begin Source File + +SOURCE=.\ammo_secondary.cpp +# End Source File +# Begin Source File + +SOURCE=.\ammohistory.cpp +# End Source File +# Begin Source File + +SOURCE=.\battery.cpp +# End Source File +# Begin Source File + +SOURCE=.\cdll_int.cpp +# End Source File +# Begin Source File + +SOURCE=.\com_weapons.cpp +# End Source File +# Begin Source File + +SOURCE=.\death.cpp +# End Source File +# Begin Source File + +SOURCE=.\demo.cpp +# End Source File +# Begin Source File + +SOURCE=.\entity.cpp + +!IF "$(CFG)" == "cl_dll - Win32 Release" + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + +# ADD CPP /MT + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ev_common.cpp +# End Source File +# Begin Source File + +SOURCE=.\events.cpp +# End Source File +# Begin Source File + +SOURCE=.\flashlight.cpp +# End Source File +# Begin Source File + +SOURCE=.\GameStudioModelRenderer.cpp +# End Source File +# Begin Source File + +SOURCE=.\geiger.cpp +# End Source File +# Begin Source File + +SOURCE=.\health.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud_bench.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud_benchtrace.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud_msg.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud_redraw.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud_servers.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud_spectator.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud_update.cpp +# End Source File +# Begin Source File + +SOURCE=.\in_camera.cpp +# End Source File +# Begin Source File + +SOURCE=.\input.cpp +# End Source File +# Begin Source File + +SOURCE=.\inputw32.cpp +# End Source File +# Begin Source File + +SOURCE=..\public\interface.cpp +# End Source File +# Begin Source File + +SOURCE=.\interpolation.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu.cpp +# End Source File +# Begin Source File + +SOURCE=.\message.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\parsemsg.cpp +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_debug.c +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_math.c +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_shared.c +# End Source File +# Begin Source File + +SOURCE=.\saytext.cpp +# End Source File +# Begin Source File + +SOURCE=.\status_icons.cpp +# End Source File +# Begin Source File + +SOURCE=.\statusbar.cpp +# End Source File +# Begin Source File + +SOURCE=.\studio_util.cpp +# End Source File +# Begin Source File + +SOURCE=.\StudioModelRenderer.cpp +# End Source File +# Begin Source File + +SOURCE=.\text_message.cpp +# End Source File +# Begin Source File + +SOURCE=.\train.cpp +# End Source File +# Begin Source File + +SOURCE=.\tri.cpp +# End Source File +# Begin Source File + +SOURCE=.\util.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_checkbutton2.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_ClassMenu.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_ControlConfigPanel.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_CustomObjects.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_grid.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_helpers.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_int.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_listbox.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_loadtga.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_MOTDWindow.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_SchemeManager.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_ScorePanel.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_scrollbar2.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_ServerBrowser.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_slider2.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_SpectatorPanel.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_TeamFortressViewport.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_TeamMenu.cpp +# End Source File +# Begin Source File + +SOURCE=.\view.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\voice_banmgr.cpp +# End Source File +# Begin Source File + +SOURCE=.\voice_status.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=.\ammo.h +# End Source File +# Begin Source File + +SOURCE=.\ammohistory.h +# End Source File +# Begin Source File + +SOURCE=.\camera.h +# End Source File +# Begin Source File + +SOURCE=.\cl_dll.h +# End Source File +# Begin Source File + +SOURCE=.\cl_util.h +# End Source File +# Begin Source File + +SOURCE=.\com_weapons.h +# End Source File +# Begin Source File + +SOURCE=.\demo.h +# End Source File +# Begin Source File + +SOURCE=.\ev_hldm.h +# End Source File +# Begin Source File + +SOURCE=.\eventscripts.h +# End Source File +# Begin Source File + +SOURCE=.\GameStudioModelRenderer.h +# End Source File +# Begin Source File + +SOURCE=.\health.h +# End Source File +# Begin Source File + +SOURCE=.\hud.h +# End Source File +# Begin Source File + +SOURCE=.\hud_servers.h +# End Source File +# Begin Source File + +SOURCE=.\hud_servers_priv.h +# End Source File +# Begin Source File + +SOURCE=.\hud_spectator.h +# End Source File +# Begin Source File + +SOURCE=.\in_defs.h +# End Source File +# Begin Source File + +SOURCE=.\interpolation.h +# End Source File +# Begin Source File + +SOURCE=.\kbutton.h +# End Source File +# Begin Source File + +SOURCE=..\common\parsemsg.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_debug.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_defs.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_info.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_materials.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_movevars.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_shared.h +# End Source File +# Begin Source File + +SOURCE=.\StudioModelRenderer.h +# End Source File +# Begin Source File + +SOURCE=.\tri.h +# End Source File +# Begin Source File + +SOURCE=.\util_vector.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_ControlConfigPanel.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_int.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_SchemeManager.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_ScorePanel.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_scrollbar2.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_ServerBrowser.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_slider2.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_SpectatorPanel.h +# End Source File +# Begin Source File + +SOURCE=.\view.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\voice_banmgr.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\voice_status.h +# End Source File +# Begin Source File + +SOURCE=.\wrect.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# Begin Source File + +SOURCE=..\lib\public\game_controls.lib +# End Source File +# End Target +# End Project diff --git a/cl_dll/cl_dll.h b/cl_dll/cl_dll.h new file mode 100644 index 0000000..cc4bdc9 --- /dev/null +++ b/cl_dll/cl_dll.h @@ -0,0 +1,43 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cl_dll.h +// + +// 4-23-98 JOHN + +// +// This DLL is linked by the client when they first initialize. +// This DLL is responsible for the following tasks: +// - Loading the HUD graphics upon initialization +// - Drawing the HUD graphics every frame +// - Handling the custum HUD-update packets +// +typedef unsigned char byte; +typedef unsigned short word; +typedef float vec_t; +typedef int (*pfnUserMsgHook)(const char *pszName, int iSize, void *pbuf); + +#include "util_vector.h" +#ifdef _WIN32 +#define EXPORT _declspec( dllexport ) +#else +#define EXPORT __attribute__ ((visibility("default"))) +#endif + +#include "../engine/cdll_int.h" +#include "../dlls/cdll_dll.h" + +extern cl_enginefunc_t gEngfuncs; diff --git a/cl_dll/cl_util.h b/cl_dll/cl_util.h new file mode 100644 index 0000000..d9034d9 --- /dev/null +++ b/cl_dll/cl_util.h @@ -0,0 +1,196 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cl_util.h +// + +#include "cvardef.h" + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +#include // for safe_sprintf() +#include // " +#include // for strncpy() + +// Macros to hook function calls into the HUD object +#define HOOK_MESSAGE(x) gEngfuncs.pfnHookUserMsg(#x, __MsgFunc_##x ); + +#define DECLARE_MESSAGE(y, x) int __MsgFunc_##x(const char *pszName, int iSize, void *pbuf) \ + { \ + return gHUD.y.MsgFunc_##x(pszName, iSize, pbuf ); \ + } + + +#define HOOK_COMMAND(x, y) gEngfuncs.pfnAddCommand( x, __CmdFunc_##y ); +#define DECLARE_COMMAND(y, x) void __CmdFunc_##x( void ) \ + { \ + gHUD.y.UserCmd_##x( ); \ + } + +inline float CVAR_GET_FLOAT( const char *x ) { return gEngfuncs.pfnGetCvarFloat( (char*)x ); } +inline char* CVAR_GET_STRING( const char *x ) { return gEngfuncs.pfnGetCvarString( (char*)x ); } +inline struct cvar_s *CVAR_CREATE( const char *cv, const char *val, const int flags ) { return gEngfuncs.pfnRegisterVariable( (char*)cv, (char*)val, flags ); } + +#define SPR_Load (*gEngfuncs.pfnSPR_Load) +#define SPR_Set (*gEngfuncs.pfnSPR_Set) +#define SPR_Frames (*gEngfuncs.pfnSPR_Frames) +#define SPR_GetList (*gEngfuncs.pfnSPR_GetList) + +// SPR_Draw draws a the current sprite as solid +#define SPR_Draw (*gEngfuncs.pfnSPR_Draw) +// SPR_DrawHoles draws the current sprites, with color index255 not drawn (transparent) +#define SPR_DrawHoles (*gEngfuncs.pfnSPR_DrawHoles) +// SPR_DrawAdditive adds the sprites RGB values to the background (additive transulency) +#define SPR_DrawAdditive (*gEngfuncs.pfnSPR_DrawAdditive) + +// SPR_EnableScissor sets a clipping rect for HUD sprites. (0,0) is the top-left hand corner of the screen. +#define SPR_EnableScissor (*gEngfuncs.pfnSPR_EnableScissor) +// SPR_DisableScissor disables the clipping rect +#define SPR_DisableScissor (*gEngfuncs.pfnSPR_DisableScissor) +// +#define FillRGBA (*gEngfuncs.pfnFillRGBA) + + +// ScreenHeight returns the height of the screen, in pixels +#define ScreenHeight (gHUD.m_scrinfo.iHeight) +// ScreenWidth returns the width of the screen, in pixels +#define ScreenWidth (gHUD.m_scrinfo.iWidth) + +#define BASE_XRES 640.f + +// use this to project world coordinates to screen coordinates +#define XPROJECT(x) ( (1.0f+(x))*ScreenWidth*0.5f ) +#define YPROJECT(y) ( (1.0f-(y))*ScreenHeight*0.5f ) + +#define XRES(x) (x * ((float)ScreenWidth / 640)) +#define YRES(y) (y * ((float)ScreenHeight / 480)) + +#define GetScreenInfo (*gEngfuncs.pfnGetScreenInfo) +#define ServerCmd (*gEngfuncs.pfnServerCmd) +#define EngineClientCmd (*gEngfuncs.pfnClientCmd) +#define SetCrosshair (*gEngfuncs.pfnSetCrosshair) +#define AngleVectors (*gEngfuncs.pfnAngleVectors) + + +// Gets the height & width of a sprite, at the specified frame +inline int SPR_Height( HSPRITE x, int f ) { return gEngfuncs.pfnSPR_Height(x, f); } +inline int SPR_Width( HSPRITE x, int f ) { return gEngfuncs.pfnSPR_Width(x, f); } + +inline client_textmessage_t *TextMessageGet( const char *pName ) { return gEngfuncs.pfnTextMessageGet( pName ); } +inline int TextMessageDrawChar( int x, int y, int number, int r, int g, int b ) +{ + return gEngfuncs.pfnDrawCharacter( x, y, number, r, g, b ); +} + +inline int DrawConsoleString( int x, int y, const char *string ) +{ + return gEngfuncs.pfnDrawConsoleString( x, y, (char*) string ); +} + +inline void GetConsoleStringSize( const char *string, int *width, int *height ) +{ + gEngfuncs.pfnDrawConsoleStringLen( string, width, height ); +} + +inline int ConsoleStringLen( const char *string ) +{ + int _width, _height; + GetConsoleStringSize( string, &_width, &_height ); + return _width; +} + +inline void ConsolePrint( const char *string ) +{ + gEngfuncs.pfnConsolePrint( string ); +} + +inline void CenterPrint( const char *string ) +{ + gEngfuncs.pfnCenterPrint( string ); +} + + +inline char *safe_strcpy( char *dst, const char *src, int len_dst) +{ + if( len_dst <= 0 ) + { + return NULL; // this is bad + } + + strncpy(dst,src,len_dst); + dst[ len_dst - 1 ] = '\0'; + + return dst; +} + +inline int safe_sprintf( char *dst, int len_dst, const char *format, ...) +{ + if( len_dst <= 0 ) + { + return -1; // this is bad + } + + va_list v; + + va_start(v, format); + + _vsnprintf(dst,len_dst,format,v); + + va_end(v); + + dst[ len_dst - 1 ] = '\0'; + + return 0; +} + +// sound functions +inline void PlaySound( char *szSound, float vol ) { gEngfuncs.pfnPlaySoundByName( szSound, vol ); } +inline void PlaySound( int iSound, float vol ) { gEngfuncs.pfnPlaySoundByIndex( iSound, vol ); } + +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#define fabs(x) ((x) > 0 ? (x) : 0 - (x)) + +void ScaleColors( int &r, int &g, int &b, int a ); + +#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];} +inline void VectorClear(float *a) { a[0]=0.0;a[1]=0.0;a[2]=0.0;} +float Length(const float *v); +void VectorMA (const float *veca, float scale, const float *vecb, float *vecc); +void VectorScale (const float *in, float scale, float *out); +float VectorNormalize (float *v); +void VectorInverse ( float *v ); + +extern vec3_t vec3_origin; + +// disable 'possible loss of data converting float to int' warning message +#pragma warning( disable: 4244 ) +// disable 'truncation from 'const double' to 'float' warning message +#pragma warning( disable: 4305 ) + +inline void UnpackRGB(int &r, int &g, int &b, unsigned long ulRGB)\ +{\ + r = (ulRGB & 0xFF0000) >>16;\ + g = (ulRGB & 0xFF00) >> 8;\ + b = ulRGB & 0xFF;\ +} + +HSPRITE LoadSprite(const char *pszName); diff --git a/cl_dll/com_weapons.cpp b/cl_dll/com_weapons.cpp new file mode 100644 index 0000000..a0e6126 --- /dev/null +++ b/cl_dll/com_weapons.cpp @@ -0,0 +1,277 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +// Com_Weapons.cpp +// Shared weapons common/shared functions +#include +#include "hud.h" +#include "cl_util.h" +#include "com_weapons.h" + +#include "const.h" +#include "entity_state.h" +#include "r_efx.h" + +// g_runfuncs is true if this is the first time we've "predicated" a particular movement/firing +// command. If it is 1, then we should play events/sounds etc., otherwise, we just will be +// updating state info, but not firing events +int g_runfuncs = 0; + +// During our weapon prediction processing, we'll need to reference some data that is part of +// the final state passed into the postthink functionality. We'll set this pointer and then +// reset it to NULL as appropriate +struct local_state_s *g_finalstate = NULL; + +/* +==================== +COM_Log + +Log debug messages to file ( appends ) +==================== +*/ +void COM_Log( char *pszFile, char *fmt, ...) +{ + va_list argptr; + char string[1024]; + FILE *fp; + char *pfilename; + + if ( !pszFile ) + { + pfilename = "c:\\hllog.txt"; + } + else + { + pfilename = pszFile; + } + + va_start (argptr,fmt); + vsprintf (string, fmt,argptr); + va_end (argptr); + + fp = fopen( pfilename, "a+t"); + if (fp) + { + fprintf(fp, "%s", string); + fclose(fp); + } +} + +// remember the current animation for the view model, in case we get out of sync with +// server. +static int g_currentanim; + +/* +===================== +HUD_SendWeaponAnim + +Change weapon model animation +===================== +*/ +void HUD_SendWeaponAnim( int iAnim, int body, int force ) +{ + // Don't actually change it. + if ( !g_runfuncs && !force ) + return; + + g_currentanim = iAnim; + + // Tell animation system new info + gEngfuncs.pfnWeaponAnim( iAnim, body ); +} + +/* +===================== +HUD_GetWeaponAnim + +Retrieve current predicted weapon animation +===================== +*/ +int HUD_GetWeaponAnim( void ) +{ + return g_currentanim; +} + +/* +===================== +HUD_PlaySound + +Play a sound, if we are seeing this command for the first time +===================== +*/ +void HUD_PlaySound( char *sound, float volume ) +{ + if ( !g_runfuncs || !g_finalstate ) + return; + + gEngfuncs.pfnPlaySoundByNameAtLocation( sound, volume, (float *)&g_finalstate->playerstate.origin ); +} + +/* +===================== +HUD_PlaybackEvent + +Directly queue up an event on the client +===================== +*/ +void HUD_PlaybackEvent( int flags, const edict_t *pInvoker, unsigned short eventindex, float delay, + float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ) +{ + vec3_t org; + vec3_t ang; + + if ( !g_runfuncs || !g_finalstate ) + return; + + // Weapon prediction events are assumed to occur at the player's origin + org = g_finalstate->playerstate.origin; + ang = v_angles; + gEngfuncs.pfnPlaybackEvent( flags, pInvoker, eventindex, delay, (float *)&org, (float *)&ang, fparam1, fparam2, iparam1, iparam2, bparam1, bparam2 ); +} + +/* +===================== +HUD_SetMaxSpeed + +===================== +*/ +void HUD_SetMaxSpeed( const edict_t *ed, float speed ) +{ +} + + +/* +===================== +UTIL_WeaponTimeBase + +Always 0.0 on client, even if not predicting weapons ( won't get called + in that case ) +===================== +*/ +float UTIL_WeaponTimeBase( void ) +{ + return 0.0; +} + +static unsigned int glSeed = 0; + +unsigned int seed_table[ 256 ] = +{ + 28985, 27138, 26457, 9451, 17764, 10909, 28790, 8716, 6361, 4853, 17798, 21977, 19643, 20662, 10834, 20103, + 27067, 28634, 18623, 25849, 8576, 26234, 23887, 18228, 32587, 4836, 3306, 1811, 3035, 24559, 18399, 315, + 26766, 907, 24102, 12370, 9674, 2972, 10472, 16492, 22683, 11529, 27968, 30406, 13213, 2319, 23620, 16823, + 10013, 23772, 21567, 1251, 19579, 20313, 18241, 30130, 8402, 20807, 27354, 7169, 21211, 17293, 5410, 19223, + 10255, 22480, 27388, 9946, 15628, 24389, 17308, 2370, 9530, 31683, 25927, 23567, 11694, 26397, 32602, 15031, + 18255, 17582, 1422, 28835, 23607, 12597, 20602, 10138, 5212, 1252, 10074, 23166, 19823, 31667, 5902, 24630, + 18948, 14330, 14950, 8939, 23540, 21311, 22428, 22391, 3583, 29004, 30498, 18714, 4278, 2437, 22430, 3439, + 28313, 23161, 25396, 13471, 19324, 15287, 2563, 18901, 13103, 16867, 9714, 14322, 15197, 26889, 19372, 26241, + 31925, 14640, 11497, 8941, 10056, 6451, 28656, 10737, 13874, 17356, 8281, 25937, 1661, 4850, 7448, 12744, + 21826, 5477, 10167, 16705, 26897, 8839, 30947, 27978, 27283, 24685, 32298, 3525, 12398, 28726, 9475, 10208, + 617, 13467, 22287, 2376, 6097, 26312, 2974, 9114, 21787, 28010, 4725, 15387, 3274, 10762, 31695, 17320, + 18324, 12441, 16801, 27376, 22464, 7500, 5666, 18144, 15314, 31914, 31627, 6495, 5226, 31203, 2331, 4668, + 12650, 18275, 351, 7268, 31319, 30119, 7600, 2905, 13826, 11343, 13053, 15583, 30055, 31093, 5067, 761, + 9685, 11070, 21369, 27155, 3663, 26542, 20169, 12161, 15411, 30401, 7580, 31784, 8985, 29367, 20989, 14203, + 29694, 21167, 10337, 1706, 28578, 887, 3373, 19477, 14382, 675, 7033, 15111, 26138, 12252, 30996, 21409, + 25678, 18555, 13256, 23316, 22407, 16727, 991, 9236, 5373, 29402, 6117, 15241, 27715, 19291, 19888, 19847 +}; + +unsigned int U_Random( void ) +{ + glSeed *= 69069; + glSeed += seed_table[ glSeed & 0xff ]; + + return ( ++glSeed & 0x0fffffff ); +} + +void U_Srand( unsigned int seed ) +{ + glSeed = seed_table[ seed & 0xff ]; +} + +/* +===================== +UTIL_SharedRandomLong +===================== +*/ +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ) +{ + unsigned int range; + + U_Srand( (int)seed + low + high ); + + range = high - low + 1; + if ( !(range - 1) ) + { + return low; + } + else + { + int offset; + int rnum; + + rnum = U_Random(); + + offset = rnum % range; + + return (low + offset); + } +} + +/* +===================== +UTIL_SharedRandomFloat +===================== +*/ +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ) +{ + // + unsigned int range; + + U_Srand( (int)seed + *(int *)&low + *(int *)&high ); + + U_Random(); + U_Random(); + + range = high - low; + if ( !range ) + { + return low; + } + else + { + int tensixrand; + float offset; + + tensixrand = U_Random() & 65535; + + offset = (float)tensixrand / 65536.0; + + return (low + offset * range ); + } +} + +/* +====================== +stub_* + +stub functions for such things as precaching. So we don't have to modify weapons code that + is compiled into both game and client .dlls. +====================== +*/ +int stub_PrecacheModel ( char* s ) { return 0; } +int stub_PrecacheSound ( char* s ) { return 0; } +unsigned short stub_PrecacheEvent ( int type, const char *s ) { return 0; } +const char *stub_NameForFunction ( uint32 function ) { return "func"; } +void stub_SetModel ( edict_t *e, const char *m ) {} diff --git a/cl_dll/com_weapons.h b/cl_dll/com_weapons.h new file mode 100644 index 0000000..34097d5 --- /dev/null +++ b/cl_dll/com_weapons.h @@ -0,0 +1,44 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// com_weapons.h +// Shared weapons common function prototypes +#if !defined( COM_WEAPONSH ) +#define COM_WEAPONSH +#ifdef _WIN32 +#pragma once +#endif + +#include "hud_iface.h" +#include "Exports.h" + +void COM_Log( char *pszFile, char *fmt, ...); +int CL_IsDead( void ); + +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ); +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ); + +int HUD_GetWeaponAnim( void ); +void HUD_SendWeaponAnim( int iAnim, int body, int force ); +void HUD_PlaySound( char *sound, float volume ); +void HUD_PlaybackEvent( int flags, const struct edict_s *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); +void HUD_SetMaxSpeed( const struct edict_s *ed, float speed ); +int stub_PrecacheModel( char* s ); +int stub_PrecacheSound( char* s ); +unsigned short stub_PrecacheEvent( int type, const char *s ); +const char *stub_NameForFunction ( uint32 function ); +void stub_SetModel ( struct edict_s *e, const char *m ); + + +extern cvar_t *cl_lw; + +extern int g_runfuncs; +extern vec3_t v_angles; +extern float g_lastFOV; +extern struct local_state_s *g_finalstate; + +#endif \ No newline at end of file diff --git a/cl_dll/death.cpp b/cl_dll/death.cpp new file mode 100644 index 0000000..836ac47 --- /dev/null +++ b/cl_dll/death.cpp @@ -0,0 +1,304 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// death notice +// +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "vgui_TeamFortressViewport.h" + +DECLARE_MESSAGE( m_DeathNotice, DeathMsg ); + +struct DeathNoticeItem { + char szKiller[MAX_PLAYER_NAME_LENGTH*2]; + char szVictim[MAX_PLAYER_NAME_LENGTH*2]; + int iId; // the index number of the associated sprite + int iSuicide; + int iTeamKill; + int iNonPlayerKill; + float flDisplayTime; + float *KillerColor; + float *VictimColor; +}; + +#define MAX_DEATHNOTICES 4 +static int DEATHNOTICE_DISPLAY_TIME = 6; + +#define DEATHNOTICE_TOP 32 + +DeathNoticeItem rgDeathNoticeList[ MAX_DEATHNOTICES + 1 ]; + +float g_ColorBlue[3] = { 0.6, 0.8, 1.0 }; +float g_ColorRed[3] = { 1.0, 0.25, 0.25 }; +float g_ColorGreen[3] = { 0.6, 1.0, 0.6 }; +float g_ColorYellow[3] = { 1.0, 0.7, 0.0 }; +float g_ColorGrey[3] = { 0.8, 0.8, 0.8 }; + +float *GetClientColor( int clientIndex ) +{ + switch ( g_PlayerExtraInfo[clientIndex].teamnumber ) + { + case 1: return g_ColorBlue; + case 2: return g_ColorRed; + case 3: return g_ColorYellow; + case 4: return g_ColorGreen; + case 0: return g_ColorYellow; + + default : return g_ColorGrey; + } + + return NULL; +} + +int CHudDeathNotice :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( DeathMsg ); + + CVAR_CREATE( "hud_deathnotice_time", "6", 0 ); + + return 1; +} + + +void CHudDeathNotice :: InitHUDData( void ) +{ + memset( rgDeathNoticeList, 0, sizeof(rgDeathNoticeList) ); +} + + +int CHudDeathNotice :: VidInit( void ) +{ + m_HUD_d_skull = gHUD.GetSpriteIndex( "d_skull" ); + + return 1; +} + +int CHudDeathNotice :: Draw( float flTime ) +{ + int x, y, r, g, b; + + for ( int i = 0; i < MAX_DEATHNOTICES; i++ ) + { + if ( rgDeathNoticeList[i].iId == 0 ) + break; // we've gone through them all + + if ( rgDeathNoticeList[i].flDisplayTime < flTime ) + { // display time has expired + // remove the current item from the list + memmove( &rgDeathNoticeList[i], &rgDeathNoticeList[i+1], sizeof(DeathNoticeItem) * (MAX_DEATHNOTICES - i) ); + i--; // continue on the next item; stop the counter getting incremented + continue; + } + + rgDeathNoticeList[i].flDisplayTime = min( rgDeathNoticeList[i].flDisplayTime, gHUD.m_flTime + DEATHNOTICE_DISPLAY_TIME ); + + // Only draw if the viewport will let me + if ( gViewPort && gViewPort->AllowedToPrintText() ) + { + // Draw the death notice + y = DEATHNOTICE_TOP + 2 + (20 * i); //!!! + + int id = (rgDeathNoticeList[i].iId == -1) ? m_HUD_d_skull : rgDeathNoticeList[i].iId; + x = ScreenWidth - ConsoleStringLen(rgDeathNoticeList[i].szVictim) - (gHUD.GetSpriteRect(id).right - gHUD.GetSpriteRect(id).left); + + if ( !rgDeathNoticeList[i].iSuicide ) + { + x -= (5 + ConsoleStringLen( rgDeathNoticeList[i].szKiller ) ); + + // Draw killers name + if ( rgDeathNoticeList[i].KillerColor ) + gEngfuncs.pfnDrawSetTextColor( rgDeathNoticeList[i].KillerColor[0], rgDeathNoticeList[i].KillerColor[1], rgDeathNoticeList[i].KillerColor[2] ); + x = 5 + DrawConsoleString( x, y, rgDeathNoticeList[i].szKiller ); + } + + r = 255; g = 80; b = 0; + if ( rgDeathNoticeList[i].iTeamKill ) + { + r = 10; g = 240; b = 10; // display it in sickly green + } + + // Draw death weapon + SPR_Set( gHUD.GetSprite(id), r, g, b ); + SPR_DrawAdditive( 0, x, y, &gHUD.GetSpriteRect(id) ); + + x += (gHUD.GetSpriteRect(id).right - gHUD.GetSpriteRect(id).left); + + // Draw victims name (if it was a player that was killed) + if (rgDeathNoticeList[i].iNonPlayerKill == FALSE) + { + if ( rgDeathNoticeList[i].VictimColor ) + gEngfuncs.pfnDrawSetTextColor( rgDeathNoticeList[i].VictimColor[0], rgDeathNoticeList[i].VictimColor[1], rgDeathNoticeList[i].VictimColor[2] ); + x = DrawConsoleString( x, y, rgDeathNoticeList[i].szVictim ); + } + } + } + + return 1; +} + +// This message handler may be better off elsewhere +int CHudDeathNotice :: MsgFunc_DeathMsg( const char *pszName, int iSize, void *pbuf ) +{ + m_iFlags |= HUD_ACTIVE; + + BEGIN_READ( pbuf, iSize ); + + int killer = READ_BYTE(); + int victim = READ_BYTE(); + + char killedwith[32]; + strcpy( killedwith, "d_" ); + strncat( killedwith, READ_STRING(), 32 ); + + if (gViewPort) + gViewPort->DeathMsg( killer, victim ); + + gHUD.m_Spectator.DeathMessage(victim); + int i; + for ( i = 0; i < MAX_DEATHNOTICES; i++ ) + { + if ( rgDeathNoticeList[i].iId == 0 ) + break; + } + if ( i == MAX_DEATHNOTICES ) + { // move the rest of the list forward to make room for this item + memmove( rgDeathNoticeList, rgDeathNoticeList+1, sizeof(DeathNoticeItem) * MAX_DEATHNOTICES ); + i = MAX_DEATHNOTICES - 1; + } + + if (gViewPort) + gViewPort->GetAllPlayersInfo(); + + // Get the Killer's name + char *killer_name = g_PlayerInfoList[ killer ].name; + if ( !killer_name ) + { + killer_name = ""; + rgDeathNoticeList[i].szKiller[0] = 0; + } + else + { + rgDeathNoticeList[i].KillerColor = GetClientColor( killer ); + strncpy( rgDeathNoticeList[i].szKiller, killer_name, MAX_PLAYER_NAME_LENGTH ); + rgDeathNoticeList[i].szKiller[MAX_PLAYER_NAME_LENGTH-1] = 0; + } + + // Get the Victim's name + char *victim_name = NULL; + // If victim is -1, the killer killed a specific, non-player object (like a sentrygun) + if ( ((char)victim) != -1 ) + victim_name = g_PlayerInfoList[ victim ].name; + if ( !victim_name ) + { + victim_name = ""; + rgDeathNoticeList[i].szVictim[0] = 0; + } + else + { + rgDeathNoticeList[i].VictimColor = GetClientColor( victim ); + strncpy( rgDeathNoticeList[i].szVictim, victim_name, MAX_PLAYER_NAME_LENGTH ); + rgDeathNoticeList[i].szVictim[MAX_PLAYER_NAME_LENGTH-1] = 0; + } + + // Is it a non-player object kill? + if ( ((char)victim) == -1 ) + { + rgDeathNoticeList[i].iNonPlayerKill = TRUE; + + // Store the object's name in the Victim slot (skip the d_ bit) + strcpy( rgDeathNoticeList[i].szVictim, killedwith+2 ); + } + else + { + if ( killer == victim || killer == 0 ) + rgDeathNoticeList[i].iSuicide = TRUE; + + if ( !strcmp( killedwith, "d_teammate" ) ) + rgDeathNoticeList[i].iTeamKill = TRUE; + } + + // Find the sprite in the list + int spr = gHUD.GetSpriteIndex( killedwith ); + + rgDeathNoticeList[i].iId = spr; + + DEATHNOTICE_DISPLAY_TIME = CVAR_GET_FLOAT( "hud_deathnotice_time" ); + rgDeathNoticeList[i].flDisplayTime = gHUD.m_flTime + DEATHNOTICE_DISPLAY_TIME; + + if (rgDeathNoticeList[i].iNonPlayerKill) + { + ConsolePrint( rgDeathNoticeList[i].szKiller ); + ConsolePrint( " killed a " ); + ConsolePrint( rgDeathNoticeList[i].szVictim ); + ConsolePrint( "\n" ); + } + else + { + // record the death notice in the console + if ( rgDeathNoticeList[i].iSuicide ) + { + ConsolePrint( rgDeathNoticeList[i].szVictim ); + + if ( !strcmp( killedwith, "d_world" ) ) + { + ConsolePrint( " died" ); + } + else + { + ConsolePrint( " killed self" ); + } + } + else if ( rgDeathNoticeList[i].iTeamKill ) + { + ConsolePrint( rgDeathNoticeList[i].szKiller ); + ConsolePrint( " killed his teammate " ); + ConsolePrint( rgDeathNoticeList[i].szVictim ); + } + else + { + ConsolePrint( rgDeathNoticeList[i].szKiller ); + ConsolePrint( " killed " ); + ConsolePrint( rgDeathNoticeList[i].szVictim ); + } + + if ( killedwith && *killedwith && (*killedwith > 13 ) && strcmp( killedwith, "d_world" ) && !rgDeathNoticeList[i].iTeamKill ) + { + ConsolePrint( " with " ); + + // replace the code names with the 'real' names + if ( !strcmp( killedwith+2, "egon" ) ) + strcpy( killedwith, "d_gluon gun" ); + if ( !strcmp( killedwith+2, "gauss" ) ) + strcpy( killedwith, "d_tau cannon" ); + + ConsolePrint( killedwith+2 ); // skip over the "d_" part + } + + ConsolePrint( "\n" ); + } + + return 1; +} + + + + diff --git a/cl_dll/demo.cpp b/cl_dll/demo.cpp new file mode 100644 index 0000000..9fd9a98 --- /dev/null +++ b/cl_dll/demo.cpp @@ -0,0 +1,99 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "hud.h" +#include "cl_util.h" +#include "demo.h" +#include "demo_api.h" +#include +#include "Exports.h" + +int g_demosniper = 0; +int g_demosniperdamage = 0; +float g_demosniperorg[3]; +float g_demosniperangles[3]; +float g_demozoom; + +// FIXME: There should be buffer helper functions to avoid all of the *(int *)& crap. + +/* +===================== +Demo_WriteBuffer + +Write some data to the demo stream +===================== +*/ +void Demo_WriteBuffer( int type, int size, unsigned char *buffer ) +{ + int pos = 0; + unsigned char buf[ 32 * 1024 ]; + *( int * )&buf[pos] = type; + pos+=sizeof( int ); + + memcpy( &buf[pos], buffer, size ); + + // Write full buffer out + gEngfuncs.pDemoAPI->WriteBuffer( size + sizeof( int ), buf ); +} + +/* +===================== +Demo_ReadBuffer + +Engine wants us to parse some data from the demo stream +===================== +*/ +void CL_DLLEXPORT Demo_ReadBuffer( int size, unsigned char *buffer ) +{ +// RecClReadDemoBuffer(size, buffer); + + int type; + int i = 0; + + type = *( int * )buffer; + i += sizeof( int ); + switch ( type ) + { + case TYPE_SNIPERDOT: + g_demosniper = *(int * )&buffer[ i ]; + i += sizeof( int ); + + if ( g_demosniper ) + { + g_demosniperdamage = *( int * )&buffer[ i ]; + i += sizeof( int ); + + g_demosniperangles[ 0 ] = *(float *)&buffer[i]; + i += sizeof( float ); + g_demosniperangles[ 1 ] = *(float *)&buffer[i]; + i += sizeof( float ); + g_demosniperangles[ 2 ] = *(float *)&buffer[i]; + i += sizeof( float ); + g_demosniperorg[ 0 ] = *(float *)&buffer[i]; + i += sizeof( float ); + g_demosniperorg[ 1 ] = *(float *)&buffer[i]; + i += sizeof( float ); + g_demosniperorg[ 2 ] = *(float *)&buffer[i]; + i += sizeof( float ); + } + break; + case TYPE_ZOOM: + g_demozoom = *(float * )&buffer[ i ]; + i += sizeof( float ); + break; + default: + gEngfuncs.Con_DPrintf( "Unknown demo buffer type, skipping.\n" ); + break; + } +} \ No newline at end of file diff --git a/cl_dll/demo.h b/cl_dll/demo.h new file mode 100644 index 0000000..3163ef3 --- /dev/null +++ b/cl_dll/demo.h @@ -0,0 +1,27 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( DEMOH ) +#define DEMOH +#pragma once + +// Types of demo messages we can write/parse +enum +{ + TYPE_SNIPERDOT = 0, + TYPE_ZOOM +}; + +void Demo_WriteBuffer( int type, int size, unsigned char *buffer ); + +extern int g_demosniper; +extern int g_demosniperdamage; +extern float g_demosniperorg[3]; +extern float g_demosniperangles[3]; +extern float g_demozoom; + +#endif \ No newline at end of file diff --git a/cl_dll/entity.cpp b/cl_dll/entity.cpp new file mode 100644 index 0000000..77d2f97 --- /dev/null +++ b/cl_dll/entity.cpp @@ -0,0 +1,785 @@ +// Client side entity management functions + +#include + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_types.h" +#include "studio_event.h" // def. of mstudioevent_t +#include "r_efx.h" +#include "event_api.h" +#include "pm_defs.h" +#include "pmtrace.h" +#include "pm_shared.h" +#include "bench.h" +#include "Exports.h" + +#include "particleman.h" +extern IParticleMan *g_pParticleMan; + +void Game_AddObjects( void ); + +extern vec3_t v_origin; + +int g_iAlive = 1; + +/* +======================== +HUD_AddEntity + Return 0 to filter entity from visible list for rendering +======================== +*/ +int CL_DLLEXPORT HUD_AddEntity( int type, struct cl_entity_s *ent, const char *modelname ) +{ +// RecClAddEntity(type, ent, modelname); + + switch ( type ) + { + case ET_NORMAL: + Bench_CheckEntity( type, ent, modelname ); + break; + case ET_PLAYER: + case ET_BEAM: + case ET_TEMPENTITY: + case ET_FRAGMENTED: + default: + break; + } + // each frame every entity passes this function, so the overview hooks it to filter the overview entities + // in spectator mode: + // each frame every entity passes this function, so the overview hooks + // it to filter the overview entities + + if ( g_iUser1 ) + { + gHUD.m_Spectator.AddOverviewEntity( type, ent, modelname ); + + if ( ( g_iUser1 == OBS_IN_EYE || gHUD.m_Spectator.m_pip->value == INSET_IN_EYE ) && + ent->index == g_iUser2 ) + return 0; // don't draw the player we are following in eye + + } + + return 1; +} + +/* +========================= +HUD_TxferLocalOverrides + +The server sends us our origin with extra precision as part of the clientdata structure, not during the normal +playerstate update in entity_state_t. In order for these overrides to eventually get to the appropriate playerstate +structure, we need to copy them into the state structure at this point. +========================= +*/ +void CL_DLLEXPORT HUD_TxferLocalOverrides( struct entity_state_s *state, const struct clientdata_s *client ) +{ +// RecClTxferLocalOverrides(state, client); + + VectorCopy( client->origin, state->origin ); + + // Spectator + state->iuser1 = client->iuser1; + state->iuser2 = client->iuser2; + + // Duck prevention + state->iuser3 = client->iuser3; + + // Fire prevention + state->iuser4 = client->iuser4; +} + +/* +========================= +HUD_ProcessPlayerState + +We have received entity_state_t for this player over the network. We need to copy appropriate fields to the +playerstate structure +========================= +*/ +void CL_DLLEXPORT HUD_ProcessPlayerState( struct entity_state_s *dst, const struct entity_state_s *src ) +{ +// RecClProcessPlayerState(dst, src); + + // Copy in network data + VectorCopy( src->origin, dst->origin ); + VectorCopy( src->angles, dst->angles ); + + VectorCopy( src->velocity, dst->velocity ); + + dst->frame = src->frame; + dst->modelindex = src->modelindex; + dst->skin = src->skin; + dst->effects = src->effects; + dst->weaponmodel = src->weaponmodel; + dst->movetype = src->movetype; + dst->sequence = src->sequence; + dst->animtime = src->animtime; + + dst->solid = src->solid; + + dst->rendermode = src->rendermode; + dst->renderamt = src->renderamt; + dst->rendercolor.r = src->rendercolor.r; + dst->rendercolor.g = src->rendercolor.g; + dst->rendercolor.b = src->rendercolor.b; + dst->renderfx = src->renderfx; + + dst->framerate = src->framerate; + dst->body = src->body; + + memcpy( &dst->controller[0], &src->controller[0], 4 * sizeof( byte ) ); + memcpy( &dst->blending[0], &src->blending[0], 2 * sizeof( byte ) ); + + VectorCopy( src->basevelocity, dst->basevelocity ); + + dst->friction = src->friction; + dst->gravity = src->gravity; + dst->gaitsequence = src->gaitsequence; + dst->spectator = src->spectator; + dst->usehull = src->usehull; + dst->playerclass = src->playerclass; + dst->team = src->team; + dst->colormap = src->colormap; + +#if defined( _TFC ) + dst->fuser1 = src->fuser1; +#endif + + // Save off some data so other areas of the Client DLL can get to it + cl_entity_t *player = gEngfuncs.GetLocalPlayer(); // Get the local player's index + if ( dst->number == player->index ) + { + g_iPlayerClass = dst->playerclass; + g_iTeamNumber = dst->team; + + g_iUser1 = src->iuser1; + g_iUser2 = src->iuser2; + g_iUser3 = src->iuser3; + } +} + +/* +========================= +HUD_TxferPredictionData + +Because we can predict an arbitrary number of frames before the server responds with an update, we need to be able to copy client side prediction data in + from the state that the server ack'd receiving, which can be anywhere along the predicted frame path ( i.e., we could predict 20 frames into the future and the server ack's + up through 10 of those frames, so we need to copy persistent client-side only state from the 10th predicted frame to the slot the server + update is occupying. +========================= +*/ +void CL_DLLEXPORT HUD_TxferPredictionData ( struct entity_state_s *ps, const struct entity_state_s *pps, struct clientdata_s *pcd, const struct clientdata_s *ppcd, struct weapon_data_s *wd, const struct weapon_data_s *pwd ) +{ +// RecClTxferPredictionData(ps, pps, pcd, ppcd, wd, pwd); + + ps->oldbuttons = pps->oldbuttons; + ps->flFallVelocity = pps->flFallVelocity; + ps->iStepLeft = pps->iStepLeft; + ps->playerclass = pps->playerclass; + + pcd->viewmodel = ppcd->viewmodel; + pcd->m_iId = ppcd->m_iId; + pcd->ammo_shells = ppcd->ammo_shells; + pcd->ammo_nails = ppcd->ammo_nails; + pcd->ammo_cells = ppcd->ammo_cells; + pcd->ammo_rockets = ppcd->ammo_rockets; + pcd->m_flNextAttack = ppcd->m_flNextAttack; + pcd->fov = ppcd->fov; + pcd->weaponanim = ppcd->weaponanim; + pcd->tfstate = ppcd->tfstate; + pcd->maxspeed = ppcd->maxspeed; + + pcd->deadflag = ppcd->deadflag; + + // Spectating or not dead == get control over view angles. + g_iAlive = ( ppcd->iuser1 || ( pcd->deadflag == DEAD_NO ) ) ? 1 : 0; + + // Spectator + pcd->iuser1 = ppcd->iuser1; + pcd->iuser2 = ppcd->iuser2; + + // Duck prevention + pcd->iuser3 = ppcd->iuser3; + + if ( gEngfuncs.IsSpectateOnly() ) + { + // in specator mode we tell the engine who we want to spectate and how + // iuser3 is not used for duck prevention (since the spectator can't duck at all) + pcd->iuser1 = g_iUser1; // observer mode + pcd->iuser2 = g_iUser2; // first target + pcd->iuser3 = g_iUser3; // second target + } + + // Fire prevention + pcd->iuser4 = ppcd->iuser4; + + pcd->fuser2 = ppcd->fuser2; + pcd->fuser3 = ppcd->fuser3; + + VectorCopy( ppcd->vuser1, pcd->vuser1 ); + VectorCopy( ppcd->vuser2, pcd->vuser2 ); + VectorCopy( ppcd->vuser3, pcd->vuser3 ); + VectorCopy( ppcd->vuser4, pcd->vuser4 ); + + memcpy( wd, pwd, 32 * sizeof( weapon_data_t ) ); +} + +#if defined( BEAM_TEST ) +// Note can't index beam[ 0 ] in Beam callback, so don't use that index +// Room for 1 beam ( 0 can't be used ) +static cl_entity_t beams[ 2 ]; + +void BeamEndModel( void ) +{ + cl_entity_t *player, *model; + int modelindex; + struct model_s *mod; + + // Load it up with some bogus data + player = gEngfuncs.GetLocalPlayer(); + if ( !player ) + return; + + mod = gEngfuncs.CL_LoadModel( "models/sentry3.mdl", &modelindex ); + if ( !mod ) + return; + + // Slot 1 + model = &beams[ 1 ]; + + *model = *player; + model->player = 0; + model->model = mod; + model->curstate.modelindex = modelindex; + + // Move it out a bit + model->origin[0] = player->origin[0] - 100; + model->origin[1] = player->origin[1]; + + model->attachment[0] = model->origin; + model->attachment[1] = model->origin; + model->attachment[2] = model->origin; + model->attachment[3] = model->origin; + + gEngfuncs.CL_CreateVisibleEntity( ET_NORMAL, model ); +} + +void Beams( void ) +{ + static float lasttime; + float curtime; + struct model_s *mod; + int index; + + BeamEndModel(); + + curtime = gEngfuncs.GetClientTime(); + float end[ 3 ]; + + if ( ( curtime - lasttime ) < 10.0 ) + return; + + mod = gEngfuncs.CL_LoadModel( "sprites/laserbeam.spr", &index ); + if ( !mod ) + return; + + lasttime = curtime; + + end [ 0 ] = v_origin.x + 100; + end [ 1 ] = v_origin.y + 100; + end [ 2 ] = v_origin.z; + + BEAM *p1; + p1 = gEngfuncs.pEfxAPI->R_BeamEntPoint( -1, end, index, + 10.0, 2.0, 0.3, 1.0, 5.0, 0.0, 1.0, 1.0, 1.0, 1.0 ); +} +#endif + +/* +========================= +HUD_CreateEntities + +Gives us a chance to add additional entities to the render this frame +========================= +*/ +void CL_DLLEXPORT HUD_CreateEntities( void ) +{ +// RecClCreateEntities(); + +#if defined( BEAM_TEST ) + Beams(); +#endif + + Bench_AddObjects(); + + // Add in any game specific objects + Game_AddObjects(); + + GetClientVoiceMgr()->CreateEntities(); +} + +#if defined( _TFC ) +extern int g_bACSpinning[33]; +#endif + +/* +========================= +HUD_StudioEvent + +The entity's studio model description indicated an event was +fired during this frame, handle the event by it's tag ( e.g., muzzleflash, sound ) +========================= +*/ +void CL_DLLEXPORT HUD_StudioEvent( const struct mstudioevent_s *event, const struct cl_entity_s *entity ) +{ +// RecClStudioEvent(event, entity); + + int iMuzzleFlash = 1; + +#if defined( _TFC ) + + if ( g_bACSpinning[ entity->index - 1 ] ) + iMuzzleFlash = 0; + +#endif + + switch( event->event ) + { + case 5001: + if ( iMuzzleFlash ) + gEngfuncs.pEfxAPI->R_MuzzleFlash( (float *)&entity->attachment[0], atoi( event->options) ); + break; + case 5011: + if ( iMuzzleFlash ) + gEngfuncs.pEfxAPI->R_MuzzleFlash( (float *)&entity->attachment[1], atoi( event->options) ); + break; + case 5021: + if ( iMuzzleFlash ) + gEngfuncs.pEfxAPI->R_MuzzleFlash( (float *)&entity->attachment[2], atoi( event->options) ); + break; + case 5031: + if ( iMuzzleFlash ) + gEngfuncs.pEfxAPI->R_MuzzleFlash( (float *)&entity->attachment[3], atoi( event->options) ); + break; + case 5002: + gEngfuncs.pEfxAPI->R_SparkEffect( (float *)&entity->attachment[0], atoi( event->options), -100, 100 ); + break; + // Client side sound + case 5004: + gEngfuncs.pfnPlaySoundByNameAtLocation( (char *)event->options, 1.0, (float *)&entity->attachment[0] ); + break; + default: + break; + } +} + +/* +================= +CL_UpdateTEnts + +Simulation and cleanup of temporary entities +================= +*/ +void CL_DLLEXPORT HUD_TempEntUpdate ( + double frametime, // Simulation time + double client_time, // Absolute time on client + double cl_gravity, // True gravity on client + TEMPENTITY **ppTempEntFree, // List of freed temporary ents + TEMPENTITY **ppTempEntActive, // List + int ( *Callback_AddVisibleEntity )( cl_entity_t *pEntity ), + void ( *Callback_TempEntPlaySound )( TEMPENTITY *pTemp, float damp ) ) +{ +// RecClTempEntUpdate(frametime, client_time, cl_gravity, ppTempEntFree, ppTempEntActive, Callback_AddVisibleEntity, Callback_TempEntPlaySound); + + static int gTempEntFrame = 0; + int i; + TEMPENTITY *pTemp, *pnext, *pprev; + float freq, gravity, gravitySlow, life, fastFreq; + + Vector vAngles; + + gEngfuncs.GetViewAngles( (float*)vAngles ); + + if ( g_pParticleMan ) + g_pParticleMan->SetVariables( cl_gravity, vAngles ); + + // Nothing to simulate + if ( !*ppTempEntActive ) + return; + + // in order to have tents collide with players, we have to run the player prediction code so + // that the client has the player list. We run this code once when we detect any COLLIDEALL + // tent, then set this BOOL to true so the code doesn't get run again if there's more than + // one COLLIDEALL ent for this update. (often are). + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( -1 ); + + // !!!BUGBUG -- This needs to be time based + gTempEntFrame = (gTempEntFrame+1) & 31; + + pTemp = *ppTempEntActive; + + // !!! Don't simulate while paused.... This is sort of a hack, revisit. + if ( frametime <= 0 ) + { + while ( pTemp ) + { + if ( !(pTemp->flags & FTENT_NOMODEL ) ) + { + Callback_AddVisibleEntity( &pTemp->entity ); + } + pTemp = pTemp->next; + } + goto finish; + } + + pprev = NULL; + freq = client_time * 0.01; + fastFreq = client_time * 5.5; + gravity = -frametime * cl_gravity; + gravitySlow = gravity * 0.5; + + while ( pTemp ) + { + int active; + + active = 1; + + life = pTemp->die - client_time; + pnext = pTemp->next; + if ( life < 0 ) + { + if ( pTemp->flags & FTENT_FADEOUT ) + { + if (pTemp->entity.curstate.rendermode == kRenderNormal) + pTemp->entity.curstate.rendermode = kRenderTransTexture; + pTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt * ( 1 + life * pTemp->fadeSpeed ); + if ( pTemp->entity.curstate.renderamt <= 0 ) + active = 0; + + } + else + active = 0; + } + if ( !active ) // Kill it + { + pTemp->next = *ppTempEntFree; + *ppTempEntFree = pTemp; + if ( !pprev ) // Deleting at head of list + *ppTempEntActive = pnext; + else + pprev->next = pnext; + } + else + { + pprev = pTemp; + + VectorCopy( pTemp->entity.origin, pTemp->entity.prevstate.origin ); + + if ( pTemp->flags & FTENT_SPARKSHOWER ) + { + // Adjust speed if it's time + // Scale is next think time + if ( client_time > pTemp->entity.baseline.scale ) + { + // Show Sparks + gEngfuncs.pEfxAPI->R_SparkEffect( pTemp->entity.origin, 8, -200, 200 ); + + // Reduce life + pTemp->entity.baseline.framerate -= 0.1; + + if ( pTemp->entity.baseline.framerate <= 0.0 ) + { + pTemp->die = client_time; + } + else + { + // So it will die no matter what + pTemp->die = client_time + 0.5; + + // Next think + pTemp->entity.baseline.scale = client_time + 0.1; + } + } + } + else if ( pTemp->flags & FTENT_PLYRATTACHMENT ) + { + cl_entity_t *pClient; + + pClient = gEngfuncs.GetEntityByIndex( pTemp->clientIndex ); + + VectorAdd( pClient->origin, pTemp->tentOffset, pTemp->entity.origin ); + } + else if ( pTemp->flags & FTENT_SINEWAVE ) + { + pTemp->x += pTemp->entity.baseline.origin[0] * frametime; + pTemp->y += pTemp->entity.baseline.origin[1] * frametime; + + pTemp->entity.origin[0] = pTemp->x + sin( pTemp->entity.baseline.origin[2] + client_time * pTemp->entity.prevstate.frame ) * (10*pTemp->entity.curstate.framerate); + pTemp->entity.origin[1] = pTemp->y + sin( pTemp->entity.baseline.origin[2] + fastFreq + 0.7 ) * (8*pTemp->entity.curstate.framerate); + pTemp->entity.origin[2] += pTemp->entity.baseline.origin[2] * frametime; + } + else if ( pTemp->flags & FTENT_SPIRAL ) + { + float s, c; + s = sin( pTemp->entity.baseline.origin[2] + fastFreq ); + c = cos( pTemp->entity.baseline.origin[2] + fastFreq ); + + pTemp->entity.origin[0] += pTemp->entity.baseline.origin[0] * frametime + 8 * sin( client_time * 20 + (int)pTemp ); + pTemp->entity.origin[1] += pTemp->entity.baseline.origin[1] * frametime + 4 * sin( client_time * 30 + (int)pTemp ); + pTemp->entity.origin[2] += pTemp->entity.baseline.origin[2] * frametime; + } + + else + { + for ( i = 0; i < 3; i++ ) + pTemp->entity.origin[i] += pTemp->entity.baseline.origin[i] * frametime; + } + + if ( pTemp->flags & FTENT_SPRANIMATE ) + { + pTemp->entity.curstate.frame += frametime * pTemp->entity.curstate.framerate; + if ( pTemp->entity.curstate.frame >= pTemp->frameMax ) + { + pTemp->entity.curstate.frame = pTemp->entity.curstate.frame - (int)(pTemp->entity.curstate.frame); + + if ( !(pTemp->flags & FTENT_SPRANIMATELOOP) ) + { + // this animating sprite isn't set to loop, so destroy it. + pTemp->die = client_time; + pTemp = pnext; + continue; + } + } + } + else if ( pTemp->flags & FTENT_SPRCYCLE ) + { + pTemp->entity.curstate.frame += frametime * 10; + if ( pTemp->entity.curstate.frame >= pTemp->frameMax ) + { + pTemp->entity.curstate.frame = pTemp->entity.curstate.frame - (int)(pTemp->entity.curstate.frame); + } + } +// Experiment +#if 0 + if ( pTemp->flags & FTENT_SCALE ) + pTemp->entity.curstate.framerate += 20.0 * (frametime / pTemp->entity.curstate.framerate); +#endif + + if ( pTemp->flags & FTENT_ROTATE ) + { + pTemp->entity.angles[0] += pTemp->entity.baseline.angles[0] * frametime; + pTemp->entity.angles[1] += pTemp->entity.baseline.angles[1] * frametime; + pTemp->entity.angles[2] += pTemp->entity.baseline.angles[2] * frametime; + + VectorCopy( pTemp->entity.angles, pTemp->entity.latched.prevangles ); + } + + if ( pTemp->flags & (FTENT_COLLIDEALL | FTENT_COLLIDEWORLD) ) + { + vec3_t traceNormal; + float traceFraction = 1; + + if ( pTemp->flags & FTENT_COLLIDEALL ) + { + pmtrace_t pmtrace; + physent_t *pe; + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + + gEngfuncs.pEventAPI->EV_PlayerTrace( pTemp->entity.prevstate.origin, pTemp->entity.origin, PM_STUDIO_BOX, -1, &pmtrace ); + + + if ( pmtrace.fraction != 1 ) + { + pe = gEngfuncs.pEventAPI->EV_GetPhysent( pmtrace.ent ); + + if ( !pmtrace.ent || ( pe->info != pTemp->clientIndex ) ) + { + traceFraction = pmtrace.fraction; + VectorCopy( pmtrace.plane.normal, traceNormal ); + + if ( pTemp->hitcallback ) + { + (*pTemp->hitcallback)( pTemp, &pmtrace ); + } + } + } + } + else if ( pTemp->flags & FTENT_COLLIDEWORLD ) + { + pmtrace_t pmtrace; + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + + gEngfuncs.pEventAPI->EV_PlayerTrace( pTemp->entity.prevstate.origin, pTemp->entity.origin, PM_STUDIO_BOX | PM_WORLD_ONLY, -1, &pmtrace ); + + if ( pmtrace.fraction != 1 ) + { + traceFraction = pmtrace.fraction; + VectorCopy( pmtrace.plane.normal, traceNormal ); + + if ( pTemp->flags & FTENT_SPARKSHOWER ) + { + // Chop spark speeds a bit more + // + VectorScale( pTemp->entity.baseline.origin, 0.6, pTemp->entity.baseline.origin ); + + if ( Length( pTemp->entity.baseline.origin ) < 10 ) + { + pTemp->entity.baseline.framerate = 0.0; + } + } + + if ( pTemp->hitcallback ) + { + (*pTemp->hitcallback)( pTemp, &pmtrace ); + } + } + } + + if ( traceFraction != 1 ) // Decent collision now, and damping works + { + float proj, damp; + + // Place at contact point + VectorMA( pTemp->entity.prevstate.origin, traceFraction*frametime, pTemp->entity.baseline.origin, pTemp->entity.origin ); + // Damp velocity + damp = pTemp->bounceFactor; + if ( pTemp->flags & (FTENT_GRAVITY|FTENT_SLOWGRAVITY) ) + { + damp *= 0.5; + if ( traceNormal[2] > 0.9 ) // Hit floor? + { + if ( pTemp->entity.baseline.origin[2] <= 0 && pTemp->entity.baseline.origin[2] >= gravity*3 ) + { + damp = 0; // Stop + pTemp->flags &= ~(FTENT_ROTATE|FTENT_GRAVITY|FTENT_SLOWGRAVITY|FTENT_COLLIDEWORLD|FTENT_SMOKETRAIL); + pTemp->entity.angles[0] = 0; + pTemp->entity.angles[2] = 0; + } + } + } + + if (pTemp->hitSound) + { + Callback_TempEntPlaySound(pTemp, damp); + } + + if (pTemp->flags & FTENT_COLLIDEKILL) + { + // die on impact + pTemp->flags &= ~FTENT_FADEOUT; + pTemp->die = client_time; + } + else + { + // Reflect velocity + if ( damp != 0 ) + { + proj = DotProduct( pTemp->entity.baseline.origin, traceNormal ); + VectorMA( pTemp->entity.baseline.origin, -proj*2, traceNormal, pTemp->entity.baseline.origin ); + // Reflect rotation (fake) + + pTemp->entity.angles[1] = -pTemp->entity.angles[1]; + } + + if ( damp != 1 ) + { + + VectorScale( pTemp->entity.baseline.origin, damp, pTemp->entity.baseline.origin ); + VectorScale( pTemp->entity.angles, 0.9, pTemp->entity.angles ); + } + } + } + } + + + if ( (pTemp->flags & FTENT_FLICKER) && gTempEntFrame == pTemp->entity.curstate.effects ) + { + dlight_t *dl = gEngfuncs.pEfxAPI->CL_AllocDlight (0); + VectorCopy (pTemp->entity.origin, dl->origin); + dl->radius = 60; + dl->color.r = 255; + dl->color.g = 120; + dl->color.b = 0; + dl->die = client_time + 0.01; + } + + if ( pTemp->flags & FTENT_SMOKETRAIL ) + { + gEngfuncs.pEfxAPI->R_RocketTrail (pTemp->entity.prevstate.origin, pTemp->entity.origin, 1); + } + + if ( pTemp->flags & FTENT_GRAVITY ) + pTemp->entity.baseline.origin[2] += gravity; + else if ( pTemp->flags & FTENT_SLOWGRAVITY ) + pTemp->entity.baseline.origin[2] += gravitySlow; + + if ( pTemp->flags & FTENT_CLIENTCUSTOM ) + { + if ( pTemp->callback ) + { + ( *pTemp->callback )( pTemp, frametime, client_time ); + } + } + + // Cull to PVS (not frustum cull, just PVS) + if ( !(pTemp->flags & FTENT_NOMODEL ) ) + { + if ( !Callback_AddVisibleEntity( &pTemp->entity ) ) + { + if ( !(pTemp->flags & FTENT_PERSIST) ) + { + pTemp->die = client_time; // If we can't draw it this frame, just dump it. + pTemp->flags &= ~FTENT_FADEOUT; // Don't fade out, just die + } + } + } + } + pTemp = pnext; + } + +finish: + // Restore state info + gEngfuncs.pEventAPI->EV_PopPMStates(); +} + +/* +================= +HUD_GetUserEntity + +If you specify negative numbers for beam start and end point entities, then + the engine will call back into this function requesting a pointer to a cl_entity_t + object that describes the entity to attach the beam onto. + +Indices must start at 1, not zero. +================= +*/ +cl_entity_t CL_DLLEXPORT *HUD_GetUserEntity( int index ) +{ +// RecClGetUserEntity(index); + +#if defined( BEAM_TEST ) + // None by default, you would return a valic pointer if you create a client side + // beam and attach it to a client side entity. + if ( index > 0 && index <= 1 ) + { + return &beams[ index ]; + } + else + { + return NULL; + } +#else + return NULL; +#endif +} + diff --git a/cl_dll/ev_common.cpp b/cl_dll/ev_common.cpp new file mode 100644 index 0000000..8b3412b --- /dev/null +++ b/cl_dll/ev_common.cpp @@ -0,0 +1,205 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// shared event functions +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" + +#include "r_efx.h" + +#include "eventscripts.h" +#include "event_api.h" +#include "pm_shared.h" + +#define IS_FIRSTPERSON_SPEC ( g_iUser1 == OBS_IN_EYE || (g_iUser1 && (gHUD.m_Spectator.m_pip->value == INSET_IN_EYE)) ) +/* +================= +GetEntity + +Return's the requested cl_entity_t +================= +*/ +struct cl_entity_s *GetEntity( int idx ) +{ + return gEngfuncs.GetEntityByIndex( idx ); +} + +/* +================= +GetViewEntity + +Return's the current weapon/view model +================= +*/ +struct cl_entity_s *GetViewEntity( void ) +{ + return gEngfuncs.GetViewModel(); +} + +/* +================= +EV_CreateTracer + +Creates a tracer effect +================= +*/ +void EV_CreateTracer( float *start, float *end ) +{ + gEngfuncs.pEfxAPI->R_TracerEffect( start, end ); +} + +/* +================= +EV_IsPlayer + +Is the entity's index in the player range? +================= +*/ +qboolean EV_IsPlayer( int idx ) +{ + if ( idx >= 1 && idx <= gEngfuncs.GetMaxClients() ) + return true; + + return false; +} + +/* +================= +EV_IsLocal + +Is the entity == the local player +================= +*/ +qboolean EV_IsLocal( int idx ) +{ + // check if we are in some way in first person spec mode + if ( IS_FIRSTPERSON_SPEC ) + return (g_iUser2 == idx); + else + return gEngfuncs.pEventAPI->EV_IsLocal( idx - 1 ) ? true : false; +} + +/* +================= +EV_GetGunPosition + +Figure out the height of the gun +================= +*/ +void EV_GetGunPosition( event_args_t *args, float *pos, float *origin ) +{ + int idx; + vec3_t view_ofs; + + idx = args->entindex; + + VectorClear( view_ofs ); + view_ofs[2] = DEFAULT_VIEWHEIGHT; + + if ( EV_IsPlayer( idx ) ) + { + // in spec mode use entity viewheigh, not own + if ( EV_IsLocal( idx ) && !IS_FIRSTPERSON_SPEC ) + { + // Grab predicted result for local player + gEngfuncs.pEventAPI->EV_LocalPlayerViewheight( view_ofs ); + } + else if ( args->ducking == 1 ) + { + view_ofs[2] = VEC_DUCK_VIEW; + } + } + + VectorAdd( origin, view_ofs, pos ); +} + +/* +================= +EV_EjectBrass + +Bullet shell casings +================= +*/ +void EV_EjectBrass( float *origin, float *velocity, float rotation, int model, int soundtype ) +{ + vec3_t endpos; + VectorClear( endpos ); + endpos[1] = rotation; + gEngfuncs.pEfxAPI->R_TempModel( origin, velocity, endpos, 2.5, model, soundtype ); +} + +/* +================= +EV_GetDefaultShellInfo + +Determine where to eject shells from +================= +*/ +void EV_GetDefaultShellInfo( event_args_t *args, float *origin, float *velocity, float *ShellVelocity, float *ShellOrigin, float *forward, float *right, float *up, float forwardScale, float upScale, float rightScale ) +{ + int i; + vec3_t view_ofs; + float fR, fU; + + int idx; + + idx = args->entindex; + + VectorClear( view_ofs ); + view_ofs[2] = DEFAULT_VIEWHEIGHT; + + if ( EV_IsPlayer( idx ) ) + { + if ( EV_IsLocal( idx ) ) + { + gEngfuncs.pEventAPI->EV_LocalPlayerViewheight( view_ofs ); + } + else if ( args->ducking == 1 ) + { + view_ofs[2] = VEC_DUCK_VIEW; + } + } + + fR = gEngfuncs.pfnRandomFloat( 50, 70 ); + fU = gEngfuncs.pfnRandomFloat( 100, 150 ); + + for ( i = 0; i < 3; i++ ) + { + ShellVelocity[i] = velocity[i] + right[i] * fR + up[i] * fU + forward[i] * 25; + ShellOrigin[i] = origin[i] + view_ofs[i] + up[i] * upScale + forward[i] * forwardScale + right[i] * rightScale; + } +} + +/* +================= +EV_MuzzleFlash + +Flag weapon/view model for muzzle flash +================= +*/ +void EV_MuzzleFlash( void ) +{ + // Add muzzle flash to current weapon model + cl_entity_t *ent = GetViewEntity(); + if ( !ent ) + { + return; + } + + // Or in the muzzle flash + ent->curstate.effects |= EF_MUZZLEFLASH; +} diff --git a/cl_dll/ev_hldm.cpp b/cl_dll/ev_hldm.cpp new file mode 100644 index 0000000..892f2ab --- /dev/null +++ b/cl_dll/ev_hldm.cpp @@ -0,0 +1,1704 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "entity_types.h" +#include "usercmd.h" +#include "pm_defs.h" +#include "pm_materials.h" + +#include "eventscripts.h" +#include "ev_hldm.h" + +#include "r_efx.h" +#include "event_api.h" +#include "event_args.h" +#include "in_defs.h" + +#include + +#include "r_studioint.h" +#include "com_model.h" + +extern engine_studio_api_t IEngineStudio; + +static int tracerCount[ 32 ]; + +extern "C" +{ +#include "pm_shared.h" +} + +void V_PunchAxis( int axis, float punch ); +void VectorAngles( const float *forward, float *angles ); + +extern cvar_t *cl_lw; + +extern "C" +{ + +// HLDM +void EV_FireGlock1( struct event_args_s *args ); +void EV_FireGlock2( struct event_args_s *args ); +void EV_FireShotGunSingle( struct event_args_s *args ); +void EV_FireShotGunDouble( struct event_args_s *args ); +void EV_FireMP5( struct event_args_s *args ); +void EV_FireMP52( struct event_args_s *args ); +void EV_FirePython( struct event_args_s *args ); +void EV_FireGauss( struct event_args_s *args ); +void EV_SpinGauss( struct event_args_s *args ); +void EV_Crowbar( struct event_args_s *args ); +void EV_FireCrossbow( struct event_args_s *args ); +void EV_FireCrossbow2( struct event_args_s *args ); +void EV_FireRpg( struct event_args_s *args ); +void EV_EgonFire( struct event_args_s *args ); +void EV_EgonStop( struct event_args_s *args ); +void EV_HornetGunFire( struct event_args_s *args ); +void EV_TripmineFire( struct event_args_s *args ); +void EV_SnarkFire( struct event_args_s *args ); + + +void EV_TrainPitchAdjust( struct event_args_s *args ); +} + +#define VECTOR_CONE_1DEGREES Vector( 0.00873, 0.00873, 0.00873 ) +#define VECTOR_CONE_2DEGREES Vector( 0.01745, 0.01745, 0.01745 ) +#define VECTOR_CONE_3DEGREES Vector( 0.02618, 0.02618, 0.02618 ) +#define VECTOR_CONE_4DEGREES Vector( 0.03490, 0.03490, 0.03490 ) +#define VECTOR_CONE_5DEGREES Vector( 0.04362, 0.04362, 0.04362 ) +#define VECTOR_CONE_6DEGREES Vector( 0.05234, 0.05234, 0.05234 ) +#define VECTOR_CONE_7DEGREES Vector( 0.06105, 0.06105, 0.06105 ) +#define VECTOR_CONE_8DEGREES Vector( 0.06976, 0.06976, 0.06976 ) +#define VECTOR_CONE_9DEGREES Vector( 0.07846, 0.07846, 0.07846 ) +#define VECTOR_CONE_10DEGREES Vector( 0.08716, 0.08716, 0.08716 ) +#define VECTOR_CONE_15DEGREES Vector( 0.13053, 0.13053, 0.13053 ) +#define VECTOR_CONE_20DEGREES Vector( 0.17365, 0.17365, 0.17365 ) + + +// play a strike sound based on the texture that was hit by the attack traceline. VecSrc/VecEnd are the +// original traceline endpoints used by the attacker, iBulletType is the type of bullet that hit the texture. +// returns volume of strike instrument (crowbar) to play +float EV_HLDM_PlayTextureSound( int idx, pmtrace_t *ptr, float *vecSrc, float *vecEnd, int iBulletType ) +{ + // hit the world, try to play sound based on texture material type + char chTextureType = CHAR_TEX_CONCRETE; + float fvol; + float fvolbar; + char *rgsz[4]; + int cnt; + float fattn = ATTN_NORM; + int entity; + char *pTextureName; + char texname[ 64 ]; + char szbuffer[ 64 ]; + + entity = gEngfuncs.pEventAPI->EV_IndexFromTrace( ptr ); + + // FIXME check if playtexture sounds movevar is set + // + + chTextureType = 0; + + // Player + if ( entity >= 1 && entity <= gEngfuncs.GetMaxClients() ) + { + // hit body + chTextureType = CHAR_TEX_FLESH; + } + else if ( entity == 0 ) + { + // get texture from entity or world (world is ent(0)) + pTextureName = (char *)gEngfuncs.pEventAPI->EV_TraceTexture( ptr->ent, vecSrc, vecEnd ); + + if ( pTextureName ) + { + strcpy( texname, pTextureName ); + pTextureName = texname; + + // strip leading '-0' or '+0~' or '{' or '!' + if (*pTextureName == '-' || *pTextureName == '+') + { + pTextureName += 2; + } + + if (*pTextureName == '{' || *pTextureName == '!' || *pTextureName == '~' || *pTextureName == ' ') + { + pTextureName++; + } + + // '}}' + strcpy( szbuffer, pTextureName ); + szbuffer[ CBTEXTURENAMEMAX - 1 ] = 0; + + // get texture type + chTextureType = PM_FindTextureType( szbuffer ); + } + } + + switch (chTextureType) + { + default: + case CHAR_TEX_CONCRETE: fvol = 0.9; fvolbar = 0.6; + rgsz[0] = "player/pl_step1.wav"; + rgsz[1] = "player/pl_step2.wav"; + cnt = 2; + break; + case CHAR_TEX_METAL: fvol = 0.9; fvolbar = 0.3; + rgsz[0] = "player/pl_metal1.wav"; + rgsz[1] = "player/pl_metal2.wav"; + cnt = 2; + break; + case CHAR_TEX_DIRT: fvol = 0.9; fvolbar = 0.1; + rgsz[0] = "player/pl_dirt1.wav"; + rgsz[1] = "player/pl_dirt2.wav"; + rgsz[2] = "player/pl_dirt3.wav"; + cnt = 3; + break; + case CHAR_TEX_VENT: fvol = 0.5; fvolbar = 0.3; + rgsz[0] = "player/pl_duct1.wav"; + rgsz[1] = "player/pl_duct1.wav"; + cnt = 2; + break; + case CHAR_TEX_GRATE: fvol = 0.9; fvolbar = 0.5; + rgsz[0] = "player/pl_grate1.wav"; + rgsz[1] = "player/pl_grate4.wav"; + cnt = 2; + break; + case CHAR_TEX_TILE: fvol = 0.8; fvolbar = 0.2; + rgsz[0] = "player/pl_tile1.wav"; + rgsz[1] = "player/pl_tile3.wav"; + rgsz[2] = "player/pl_tile2.wav"; + rgsz[3] = "player/pl_tile4.wav"; + cnt = 4; + break; + case CHAR_TEX_SLOSH: fvol = 0.9; fvolbar = 0.0; + rgsz[0] = "player/pl_slosh1.wav"; + rgsz[1] = "player/pl_slosh3.wav"; + rgsz[2] = "player/pl_slosh2.wav"; + rgsz[3] = "player/pl_slosh4.wav"; + cnt = 4; + break; + case CHAR_TEX_WOOD: fvol = 0.9; fvolbar = 0.2; + rgsz[0] = "debris/wood1.wav"; + rgsz[1] = "debris/wood2.wav"; + rgsz[2] = "debris/wood3.wav"; + cnt = 3; + break; + case CHAR_TEX_GLASS: + case CHAR_TEX_COMPUTER: + fvol = 0.8; fvolbar = 0.2; + rgsz[0] = "debris/glass1.wav"; + rgsz[1] = "debris/glass2.wav"; + rgsz[2] = "debris/glass3.wav"; + cnt = 3; + break; + case CHAR_TEX_FLESH: + if (iBulletType == BULLET_PLAYER_CROWBAR) + return 0.0; // crowbar already makes this sound + fvol = 1.0; fvolbar = 0.2; + rgsz[0] = "weapons/bullet_hit1.wav"; + rgsz[1] = "weapons/bullet_hit2.wav"; + fattn = 1.0; + cnt = 2; + break; + } + + // play material hit sound + gEngfuncs.pEventAPI->EV_PlaySound( 0, ptr->endpos, CHAN_STATIC, rgsz[gEngfuncs.pfnRandomLong(0,cnt-1)], fvol, fattn, 0, 96 + gEngfuncs.pfnRandomLong(0,0xf) ); + return fvolbar; +} + +char *EV_HLDM_DamageDecal( physent_t *pe ) +{ + static char decalname[ 32 ]; + int idx; + + if ( pe->classnumber == 1 ) + { + idx = gEngfuncs.pfnRandomLong( 0, 2 ); + sprintf( decalname, "{break%i", idx + 1 ); + } + else if ( pe->rendermode != kRenderNormal ) + { + sprintf( decalname, "{bproof1" ); + } + else + { + idx = gEngfuncs.pfnRandomLong( 0, 4 ); + sprintf( decalname, "{shot%i", idx + 1 ); + } + return decalname; +} + +void EV_HLDM_GunshotDecalTrace( pmtrace_t *pTrace, char *decalName ) +{ + int iRand; + physent_t *pe; + + gEngfuncs.pEfxAPI->R_BulletImpactParticles( pTrace->endpos ); + + iRand = gEngfuncs.pfnRandomLong(0,0x7FFF); + if ( iRand < (0x7fff/2) )// not every bullet makes a sound. + { + switch( iRand % 5) + { + case 0: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric1.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric2.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 2: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric3.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric4.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 4: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric5.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + } + } + + pe = gEngfuncs.pEventAPI->EV_GetPhysent( pTrace->ent ); + + // Only decal brush models such as the world etc. + if ( decalName && decalName[0] && pe && ( pe->solid == SOLID_BSP || pe->movetype == MOVETYPE_PUSHSTEP ) ) + { + if ( CVAR_GET_FLOAT( "r_decals" ) ) + { + gEngfuncs.pEfxAPI->R_DecalShoot( + gEngfuncs.pEfxAPI->Draw_DecalIndex( gEngfuncs.pEfxAPI->Draw_DecalIndexFromName( decalName ) ), + gEngfuncs.pEventAPI->EV_IndexFromTrace( pTrace ), 0, pTrace->endpos, 0 ); + } + } +} + +void EV_HLDM_DecalGunshot( pmtrace_t *pTrace, int iBulletType ) +{ + physent_t *pe; + + pe = gEngfuncs.pEventAPI->EV_GetPhysent( pTrace->ent ); + + if ( pe && pe->solid == SOLID_BSP ) + { + switch( iBulletType ) + { + case BULLET_PLAYER_9MM: + case BULLET_MONSTER_9MM: + case BULLET_PLAYER_MP5: + case BULLET_MONSTER_MP5: + case BULLET_PLAYER_BUCKSHOT: + case BULLET_PLAYER_357: + default: + // smoke and decal + EV_HLDM_GunshotDecalTrace( pTrace, EV_HLDM_DamageDecal( pe ) ); + break; + } + } +} + +int EV_HLDM_CheckTracer( int idx, float *vecSrc, float *end, float *forward, float *right, int iBulletType, int iTracerFreq, int *tracerCount ) +{ + int tracer = 0; + int i; + qboolean player = idx >= 1 && idx <= gEngfuncs.GetMaxClients() ? true : false; + + if ( iTracerFreq != 0 && ( (*tracerCount)++ % iTracerFreq) == 0 ) + { + vec3_t vecTracerSrc; + + if ( player ) + { + vec3_t offset( 0, 0, -4 ); + + // adjust tracer position for player + for ( i = 0; i < 3; i++ ) + { + vecTracerSrc[ i ] = vecSrc[ i ] + offset[ i ] + right[ i ] * 2 + forward[ i ] * 16; + } + } + else + { + VectorCopy( vecSrc, vecTracerSrc ); + } + + if ( iTracerFreq != 1 ) // guns that always trace also always decal + tracer = 1; + + switch( iBulletType ) + { + case BULLET_PLAYER_MP5: + case BULLET_MONSTER_MP5: + case BULLET_MONSTER_9MM: + case BULLET_MONSTER_12MM: + default: + EV_CreateTracer( vecTracerSrc, end ); + break; + } + } + + return tracer; +} + + +/* +================ +FireBullets + +Go to the trouble of combining multiple pellets into a single damage call. +================ +*/ +void EV_HLDM_FireBullets( int idx, float *forward, float *right, float *up, int cShots, float *vecSrc, float *vecDirShooting, float flDistance, int iBulletType, int iTracerFreq, int *tracerCount, float flSpreadX, float flSpreadY ) +{ + int i; + pmtrace_t tr; + int iShot; + int tracer; + + for ( iShot = 1; iShot <= cShots; iShot++ ) + { + vec3_t vecDir, vecEnd; + + float x, y, z; + //We randomize for the Shotgun. + if ( iBulletType == BULLET_PLAYER_BUCKSHOT ) + { + do { + x = gEngfuncs.pfnRandomFloat(-0.5,0.5) + gEngfuncs.pfnRandomFloat(-0.5,0.5); + y = gEngfuncs.pfnRandomFloat(-0.5,0.5) + gEngfuncs.pfnRandomFloat(-0.5,0.5); + z = x*x+y*y; + } while (z > 1); + + for ( i = 0 ; i < 3; i++ ) + { + vecDir[i] = vecDirShooting[i] + x * flSpreadX * right[ i ] + y * flSpreadY * up [ i ]; + vecEnd[i] = vecSrc[ i ] + flDistance * vecDir[ i ]; + } + }//But other guns already have their spread randomized in the synched spread. + else + { + + for ( i = 0 ; i < 3; i++ ) + { + vecDir[i] = vecDirShooting[i] + flSpreadX * right[ i ] + flSpreadY * up [ i ]; + vecEnd[i] = vecSrc[ i ] + flDistance * vecDir[ i ]; + } + } + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( idx - 1 ); + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( vecSrc, vecEnd, PM_STUDIO_BOX, -1, &tr ); + + tracer = EV_HLDM_CheckTracer( idx, vecSrc, tr.endpos, forward, right, iBulletType, iTracerFreq, tracerCount ); + + // do damage, paint decals + if ( tr.fraction != 1.0 ) + { + switch(iBulletType) + { + default: + case BULLET_PLAYER_9MM: + + EV_HLDM_PlayTextureSound( idx, &tr, vecSrc, vecEnd, iBulletType ); + EV_HLDM_DecalGunshot( &tr, iBulletType ); + + break; + case BULLET_PLAYER_MP5: + + if ( !tracer ) + { + EV_HLDM_PlayTextureSound( idx, &tr, vecSrc, vecEnd, iBulletType ); + EV_HLDM_DecalGunshot( &tr, iBulletType ); + } + break; + case BULLET_PLAYER_BUCKSHOT: + + EV_HLDM_DecalGunshot( &tr, iBulletType ); + + break; + case BULLET_PLAYER_357: + + EV_HLDM_PlayTextureSound( idx, &tr, vecSrc, vecEnd, iBulletType ); + EV_HLDM_DecalGunshot( &tr, iBulletType ); + + break; + + } + } + + gEngfuncs.pEventAPI->EV_PopPMStates(); + } +} + +//====================== +// GLOCK START +//====================== +void EV_FireGlock1( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + int empty; + + vec3_t ShellVelocity; + vec3_t ShellOrigin; + int shell; + vec3_t vecSrc, vecAiming; + vec3_t up, right, forward; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + + empty = args->bparam1; + AngleVectors( angles, forward, right, up ); + + shell = gEngfuncs.pEventAPI->EV_FindModelIndex ("models/shell.mdl");// brass shell + + if ( EV_IsLocal( idx ) ) + { + EV_MuzzleFlash(); + gEngfuncs.pEventAPI->EV_WeaponAnimation( empty ? GLOCK_SHOOT_EMPTY : GLOCK_SHOOT, 2 ); + + V_PunchAxis( 0, -2.0 ); + } + + EV_GetDefaultShellInfo( args, origin, velocity, ShellVelocity, ShellOrigin, forward, right, up, 20, -12, 4 ); + + EV_EjectBrass ( ShellOrigin, ShellVelocity, angles[ YAW ], shell, TE_BOUNCE_SHELL ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/pl_gun3.wav", gEngfuncs.pfnRandomFloat(0.92, 1.0), ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); + + EV_GetGunPosition( args, vecSrc, origin ); + + VectorCopy( forward, vecAiming ); + + EV_HLDM_FireBullets( idx, forward, right, up, 1, vecSrc, vecAiming, 8192, BULLET_PLAYER_9MM, 0, 0, args->fparam1, args->fparam2 ); +} + +void EV_FireGlock2( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + + vec3_t ShellVelocity; + vec3_t ShellOrigin; + int shell; + vec3_t vecSrc, vecAiming; + vec3_t vecSpread; + vec3_t up, right, forward; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + + AngleVectors( angles, forward, right, up ); + + shell = gEngfuncs.pEventAPI->EV_FindModelIndex ("models/shell.mdl");// brass shell + + if ( EV_IsLocal( idx ) ) + { + // Add muzzle flash to current weapon model + EV_MuzzleFlash(); + gEngfuncs.pEventAPI->EV_WeaponAnimation( GLOCK_SHOOT, 2 ); + + V_PunchAxis( 0, -2.0 ); + } + + EV_GetDefaultShellInfo( args, origin, velocity, ShellVelocity, ShellOrigin, forward, right, up, 20, -12, 4 ); + + EV_EjectBrass ( ShellOrigin, ShellVelocity, angles[ YAW ], shell, TE_BOUNCE_SHELL ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/pl_gun3.wav", gEngfuncs.pfnRandomFloat(0.92, 1.0), ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); + + EV_GetGunPosition( args, vecSrc, origin ); + + VectorCopy( forward, vecAiming ); + + EV_HLDM_FireBullets( idx, forward, right, up, 1, vecSrc, vecAiming, 8192, BULLET_PLAYER_9MM, 0, &tracerCount[idx-1], args->fparam1, args->fparam2 ); + +} +//====================== +// GLOCK END +//====================== + +//====================== +// SHOTGUN START +//====================== +void EV_FireShotGunDouble( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + + int j; + vec3_t ShellVelocity; + vec3_t ShellOrigin; + int shell; + vec3_t vecSrc, vecAiming; + vec3_t vecSpread; + vec3_t up, right, forward; + float flSpread = 0.01; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + + AngleVectors( angles, forward, right, up ); + + shell = gEngfuncs.pEventAPI->EV_FindModelIndex ("models/shotgunshell.mdl");// brass shell + + if ( EV_IsLocal( idx ) ) + { + // Add muzzle flash to current weapon model + EV_MuzzleFlash(); + gEngfuncs.pEventAPI->EV_WeaponAnimation( SHOTGUN_FIRE2, 2 ); + V_PunchAxis( 0, -10.0 ); + } + + for ( j = 0; j < 2; j++ ) + { + EV_GetDefaultShellInfo( args, origin, velocity, ShellVelocity, ShellOrigin, forward, right, up, 32, -12, 6 ); + + EV_EjectBrass ( ShellOrigin, ShellVelocity, angles[ YAW ], shell, TE_BOUNCE_SHOTSHELL ); + } + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/dbarrel1.wav", gEngfuncs.pfnRandomFloat(0.98, 1.0), ATTN_NORM, 0, 85 + gEngfuncs.pfnRandomLong( 0, 0x1f ) ); + + EV_GetGunPosition( args, vecSrc, origin ); + VectorCopy( forward, vecAiming ); + + if ( gEngfuncs.GetMaxClients() > 1 ) + { + EV_HLDM_FireBullets( idx, forward, right, up, 8, vecSrc, vecAiming, 2048, BULLET_PLAYER_BUCKSHOT, 0, &tracerCount[idx-1], 0.17365, 0.04362 ); + } + else + { + EV_HLDM_FireBullets( idx, forward, right, up, 12, vecSrc, vecAiming, 2048, BULLET_PLAYER_BUCKSHOT, 0, &tracerCount[idx-1], 0.08716, 0.08716 ); + } +} + +void EV_FireShotGunSingle( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + + vec3_t ShellVelocity; + vec3_t ShellOrigin; + int shell; + vec3_t vecSrc, vecAiming; + vec3_t vecSpread; + vec3_t up, right, forward; + float flSpread = 0.01; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + + AngleVectors( angles, forward, right, up ); + + shell = gEngfuncs.pEventAPI->EV_FindModelIndex ("models/shotgunshell.mdl");// brass shell + + if ( EV_IsLocal( idx ) ) + { + // Add muzzle flash to current weapon model + EV_MuzzleFlash(); + gEngfuncs.pEventAPI->EV_WeaponAnimation( SHOTGUN_FIRE, 2 ); + + V_PunchAxis( 0, -5.0 ); + } + + EV_GetDefaultShellInfo( args, origin, velocity, ShellVelocity, ShellOrigin, forward, right, up, 32, -12, 6 ); + + EV_EjectBrass ( ShellOrigin, ShellVelocity, angles[ YAW ], shell, TE_BOUNCE_SHOTSHELL ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/sbarrel1.wav", gEngfuncs.pfnRandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + gEngfuncs.pfnRandomLong( 0, 0x1f ) ); + + EV_GetGunPosition( args, vecSrc, origin ); + VectorCopy( forward, vecAiming ); + + if ( gEngfuncs.GetMaxClients() > 1 ) + { + EV_HLDM_FireBullets( idx, forward, right, up, 4, vecSrc, vecAiming, 2048, BULLET_PLAYER_BUCKSHOT, 0, &tracerCount[idx-1], 0.08716, 0.04362 ); + } + else + { + EV_HLDM_FireBullets( idx, forward, right, up, 6, vecSrc, vecAiming, 2048, BULLET_PLAYER_BUCKSHOT, 0, &tracerCount[idx-1], 0.08716, 0.08716 ); + } +} +//====================== +// SHOTGUN END +//====================== + +//====================== +// MP5 START +//====================== +void EV_FireMP5( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + + vec3_t ShellVelocity; + vec3_t ShellOrigin; + int shell; + vec3_t vecSrc, vecAiming; + vec3_t up, right, forward; + float flSpread = 0.01; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + + AngleVectors( angles, forward, right, up ); + + shell = gEngfuncs.pEventAPI->EV_FindModelIndex ("models/shell.mdl");// brass shell + + if ( EV_IsLocal( idx ) ) + { + // Add muzzle flash to current weapon model + EV_MuzzleFlash(); + gEngfuncs.pEventAPI->EV_WeaponAnimation( MP5_FIRE1 + gEngfuncs.pfnRandomLong(0,2), 2 ); + + V_PunchAxis( 0, gEngfuncs.pfnRandomFloat( -2, 2 ) ); + } + + EV_GetDefaultShellInfo( args, origin, velocity, ShellVelocity, ShellOrigin, forward, right, up, 20, -12, 4 ); + + EV_EjectBrass ( ShellOrigin, ShellVelocity, angles[ YAW ], shell, TE_BOUNCE_SHELL ); + + switch( gEngfuncs.pfnRandomLong( 0, 1 ) ) + { + case 0: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/hks1.wav", 1, ATTN_NORM, 0, 94 + gEngfuncs.pfnRandomLong( 0, 0xf ) ); + break; + case 1: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/hks2.wav", 1, ATTN_NORM, 0, 94 + gEngfuncs.pfnRandomLong( 0, 0xf ) ); + break; + } + + EV_GetGunPosition( args, vecSrc, origin ); + VectorCopy( forward, vecAiming ); + + if ( gEngfuncs.GetMaxClients() > 1 ) + { + EV_HLDM_FireBullets( idx, forward, right, up, 1, vecSrc, vecAiming, 8192, BULLET_PLAYER_MP5, 2, &tracerCount[idx-1], args->fparam1, args->fparam2 ); + } + else + { + EV_HLDM_FireBullets( idx, forward, right, up, 1, vecSrc, vecAiming, 8192, BULLET_PLAYER_MP5, 2, &tracerCount[idx-1], args->fparam1, args->fparam2 ); + } +} + +// We only predict the animation and sound +// The grenade is still launched from the server. +void EV_FireMP52( event_args_t *args ) +{ + int idx; + vec3_t origin; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + + if ( EV_IsLocal( idx ) ) + { + gEngfuncs.pEventAPI->EV_WeaponAnimation( MP5_LAUNCH, 2 ); + V_PunchAxis( 0, -10 ); + } + + switch( gEngfuncs.pfnRandomLong( 0, 1 ) ) + { + case 0: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/glauncher.wav", 1, ATTN_NORM, 0, 94 + gEngfuncs.pfnRandomLong( 0, 0xf ) ); + break; + case 1: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/glauncher2.wav", 1, ATTN_NORM, 0, 94 + gEngfuncs.pfnRandomLong( 0, 0xf ) ); + break; + } +} +//====================== +// MP5 END +//====================== + +//====================== +// PHYTON START +// ( .357 ) +//====================== +void EV_FirePython( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + + vec3_t vecSrc, vecAiming; + vec3_t up, right, forward; + float flSpread = 0.01; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + + AngleVectors( angles, forward, right, up ); + + if ( EV_IsLocal( idx ) ) + { + // Python uses different body in multiplayer versus single player + int multiplayer = gEngfuncs.GetMaxClients() == 1 ? 0 : 1; + + // Add muzzle flash to current weapon model + EV_MuzzleFlash(); + gEngfuncs.pEventAPI->EV_WeaponAnimation( PYTHON_FIRE1, multiplayer ? 1 : 0 ); + + V_PunchAxis( 0, -10.0 ); + } + + switch( gEngfuncs.pfnRandomLong( 0, 1 ) ) + { + case 0: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/357_shot1.wav", gEngfuncs.pfnRandomFloat(0.8, 0.9), ATTN_NORM, 0, PITCH_NORM ); + break; + case 1: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/357_shot2.wav", gEngfuncs.pfnRandomFloat(0.8, 0.9), ATTN_NORM, 0, PITCH_NORM ); + break; + } + + EV_GetGunPosition( args, vecSrc, origin ); + + VectorCopy( forward, vecAiming ); + + EV_HLDM_FireBullets( idx, forward, right, up, 1, vecSrc, vecAiming, 8192, BULLET_PLAYER_357, 0, 0, args->fparam1, args->fparam2 ); +} +//====================== +// PHYTON END +// ( .357 ) +//====================== + +//====================== +// GAUSS START +//====================== +#define SND_CHANGE_PITCH (1<<7) // duplicated in protocol.h change sound pitch + +void EV_SpinGauss( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + int iSoundState = 0; + + int pitch; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + + pitch = args->iparam1; + + iSoundState = args->bparam1 ? SND_CHANGE_PITCH : 0; + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "ambience/pulsemachine.wav", 1.0, ATTN_NORM, iSoundState, pitch ); +} + +/* +============================== +EV_StopPreviousGauss + +============================== +*/ +void EV_StopPreviousGauss( int idx ) +{ + // Make sure we don't have a gauss spin event in the queue for this guy + gEngfuncs.pEventAPI->EV_KillEvents( idx, "events/gaussspin.sc" ); + gEngfuncs.pEventAPI->EV_StopSound( idx, CHAN_WEAPON, "ambience/pulsemachine.wav" ); +} + +extern float g_flApplyVel; + +void EV_FireGauss( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + float flDamage = args->fparam1; + int primaryfire = args->bparam1; + + int m_fPrimaryFire = args->bparam1; + int m_iWeaponVolume = GAUSS_PRIMARY_FIRE_VOLUME; + vec3_t vecSrc; + vec3_t vecDest; + edict_t *pentIgnore; + pmtrace_t tr, beam_tr; + float flMaxFrac = 1.0; + int nTotal = 0; + int fHasPunched = 0; + int fFirstBeam = 1; + int nMaxHits = 10; + physent_t *pEntity; + int m_iBeam, m_iGlow, m_iBalls; + vec3_t up, right, forward; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + + if ( args->bparam2 ) + { + EV_StopPreviousGauss( idx ); + return; + } + +// Con_Printf( "Firing gauss with %f\n", flDamage ); + EV_GetGunPosition( args, vecSrc, origin ); + + m_iBeam = gEngfuncs.pEventAPI->EV_FindModelIndex( "sprites/smoke.spr" ); + m_iBalls = m_iGlow = gEngfuncs.pEventAPI->EV_FindModelIndex( "sprites/hotglow.spr" ); + + AngleVectors( angles, forward, right, up ); + + VectorMA( vecSrc, 8192, forward, vecDest ); + + if ( EV_IsLocal( idx ) ) + { + V_PunchAxis( 0, -2.0 ); + gEngfuncs.pEventAPI->EV_WeaponAnimation( GAUSS_FIRE2, 2 ); + + if ( m_fPrimaryFire == false ) + g_flApplyVel = flDamage; + + } + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/gauss2.wav", 0.5 + flDamage * (1.0 / 400.0), ATTN_NORM, 0, 85 + gEngfuncs.pfnRandomLong( 0, 0x1f ) ); + + while (flDamage > 10 && nMaxHits > 0) + { + nMaxHits--; + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( idx - 1 ); + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( vecSrc, vecDest, PM_STUDIO_BOX, -1, &tr ); + + gEngfuncs.pEventAPI->EV_PopPMStates(); + + if ( tr.allsolid ) + break; + + if (fFirstBeam) + { + if ( EV_IsLocal( idx ) ) + { + // Add muzzle flash to current weapon model + EV_MuzzleFlash(); + } + fFirstBeam = 0; + + gEngfuncs.pEfxAPI->R_BeamEntPoint( + idx | 0x1000, + tr.endpos, + m_iBeam, + 0.1, + m_fPrimaryFire ? 1.0 : 2.5, + 0.0, + m_fPrimaryFire ? 128.0 : flDamage, + 0, + 0, + 0, + m_fPrimaryFire ? 255 : 255, + m_fPrimaryFire ? 128 : 255, + m_fPrimaryFire ? 0 : 255 + ); + } + else + { + gEngfuncs.pEfxAPI->R_BeamPoints( vecSrc, + tr.endpos, + m_iBeam, + 0.1, + m_fPrimaryFire ? 1.0 : 2.5, + 0.0, + m_fPrimaryFire ? 128.0 : flDamage, + 0, + 0, + 0, + m_fPrimaryFire ? 255 : 255, + m_fPrimaryFire ? 128 : 255, + m_fPrimaryFire ? 0 : 255 + ); + } + + pEntity = gEngfuncs.pEventAPI->EV_GetPhysent( tr.ent ); + if ( pEntity == NULL ) + break; + + if ( pEntity->solid == SOLID_BSP ) + { + float n; + + pentIgnore = NULL; + + n = -DotProduct( tr.plane.normal, forward ); + + if (n < 0.5) // 60 degrees + { + // ALERT( at_console, "reflect %f\n", n ); + // reflect + vec3_t r; + + VectorMA( forward, 2.0 * n, tr.plane.normal, r ); + + flMaxFrac = flMaxFrac - tr.fraction; + + VectorCopy( r, forward ); + + VectorMA( tr.endpos, 8.0, forward, vecSrc ); + VectorMA( vecSrc, 8192.0, forward, vecDest ); + + gEngfuncs.pEfxAPI->R_TempSprite( tr.endpos, vec3_origin, 0.2, m_iGlow, kRenderGlow, kRenderFxNoDissipation, flDamage * n / 255.0, flDamage * n * 0.5 * 0.1, FTENT_FADEOUT ); + + vec3_t fwd; + VectorAdd( tr.endpos, tr.plane.normal, fwd ); + + gEngfuncs.pEfxAPI->R_Sprite_Trail( TE_SPRITETRAIL, tr.endpos, fwd, m_iBalls, 3, 0.1, gEngfuncs.pfnRandomFloat( 10, 20 ) / 100.0, 100, + 255, 100 ); + + // lose energy + if ( n == 0 ) + { + n = 0.1; + } + + flDamage = flDamage * (1 - n); + + } + else + { + // tunnel + EV_HLDM_DecalGunshot( &tr, BULLET_MONSTER_12MM ); + + gEngfuncs.pEfxAPI->R_TempSprite( tr.endpos, vec3_origin, 1.0, m_iGlow, kRenderGlow, kRenderFxNoDissipation, flDamage / 255.0, 6.0, FTENT_FADEOUT ); + + // limit it to one hole punch + if (fHasPunched) + { + break; + } + fHasPunched = 1; + + // try punching through wall if secondary attack (primary is incapable of breaking through) + if ( !m_fPrimaryFire ) + { + vec3_t start; + + VectorMA( tr.endpos, 8.0, forward, start ); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( idx - 1 ); + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( start, vecDest, PM_STUDIO_BOX, -1, &beam_tr ); + + if ( !beam_tr.allsolid ) + { + vec3_t delta; + float n; + + // trace backwards to find exit point + + gEngfuncs.pEventAPI->EV_PlayerTrace( beam_tr.endpos, tr.endpos, PM_STUDIO_BOX, -1, &beam_tr ); + + VectorSubtract( beam_tr.endpos, tr.endpos, delta ); + + n = Length( delta ); + + if (n < flDamage) + { + if (n == 0) + n = 1; + flDamage -= n; + + // absorption balls + { + vec3_t fwd; + VectorSubtract( tr.endpos, forward, fwd ); + gEngfuncs.pEfxAPI->R_Sprite_Trail( TE_SPRITETRAIL, tr.endpos, fwd, m_iBalls, 3, 0.1, gEngfuncs.pfnRandomFloat( 10, 20 ) / 100.0, 100, + 255, 100 ); + } + + //////////////////////////////////// WHAT TO DO HERE + // CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, NORMAL_EXPLOSION_VOLUME, 3.0 ); + + EV_HLDM_DecalGunshot( &beam_tr, BULLET_MONSTER_12MM ); + + gEngfuncs.pEfxAPI->R_TempSprite( beam_tr.endpos, vec3_origin, 0.1, m_iGlow, kRenderGlow, kRenderFxNoDissipation, flDamage / 255.0, 6.0, FTENT_FADEOUT ); + + // balls + { + vec3_t fwd; + VectorSubtract( beam_tr.endpos, forward, fwd ); + gEngfuncs.pEfxAPI->R_Sprite_Trail( TE_SPRITETRAIL, beam_tr.endpos, fwd, m_iBalls, (int)(flDamage * 0.3), 0.1, gEngfuncs.pfnRandomFloat( 10, 20 ) / 100.0, 200, + 255, 40 ); + } + + VectorAdd( beam_tr.endpos, forward, vecSrc ); + } + } + else + { + flDamage = 0; + } + + gEngfuncs.pEventAPI->EV_PopPMStates(); + } + else + { + if ( m_fPrimaryFire ) + { + // slug doesn't punch through ever with primary + // fire, so leave a little glowy bit and make some balls + gEngfuncs.pEfxAPI->R_TempSprite( tr.endpos, vec3_origin, 0.2, m_iGlow, kRenderGlow, kRenderFxNoDissipation, 200.0 / 255.0, 0.3, FTENT_FADEOUT ); + + { + vec3_t fwd; + VectorAdd( tr.endpos, tr.plane.normal, fwd ); + gEngfuncs.pEfxAPI->R_Sprite_Trail( TE_SPRITETRAIL, tr.endpos, fwd, m_iBalls, 8, 0.6, gEngfuncs.pfnRandomFloat( 10, 20 ) / 100.0, 100, + 255, 200 ); + } + } + + flDamage = 0; + } + } + } + else + { + VectorAdd( tr.endpos, forward, vecSrc ); + } + } +} +//====================== +// GAUSS END +//====================== + +//====================== +// CROWBAR START +//====================== + +enum crowbar_e { + CROWBAR_IDLE = 0, + CROWBAR_DRAW, + CROWBAR_HOLSTER, + CROWBAR_ATTACK1HIT, + CROWBAR_ATTACK1MISS, + CROWBAR_ATTACK2MISS, + CROWBAR_ATTACK2HIT, + CROWBAR_ATTACK3MISS, + CROWBAR_ATTACK3HIT +}; + +int g_iSwing; + +//Only predict the miss sounds, hit sounds are still played +//server side, so players don't get the wrong idea. +void EV_Crowbar( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + + //Play Swing sound + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/cbar_miss1.wav", 1, ATTN_NORM, 0, PITCH_NORM); + + if ( EV_IsLocal( idx ) ) + { + gEngfuncs.pEventAPI->EV_WeaponAnimation( CROWBAR_ATTACK1MISS, 1 ); + + switch( (g_iSwing++) % 3 ) + { + case 0: + gEngfuncs.pEventAPI->EV_WeaponAnimation ( CROWBAR_ATTACK1MISS, 1 ); break; + case 1: + gEngfuncs.pEventAPI->EV_WeaponAnimation ( CROWBAR_ATTACK2MISS, 1 ); break; + case 2: + gEngfuncs.pEventAPI->EV_WeaponAnimation ( CROWBAR_ATTACK3MISS, 1 ); break; + } + } +} +//====================== +// CROWBAR END +//====================== + +//====================== +// CROSSBOW START +//====================== +enum crossbow_e { + CROSSBOW_IDLE1 = 0, // full + CROSSBOW_IDLE2, // empty + CROSSBOW_FIDGET1, // full + CROSSBOW_FIDGET2, // empty + CROSSBOW_FIRE1, // full + CROSSBOW_FIRE2, // reload + CROSSBOW_FIRE3, // empty + CROSSBOW_RELOAD, // from empty + CROSSBOW_DRAW1, // full + CROSSBOW_DRAW2, // empty + CROSSBOW_HOLSTER1, // full + CROSSBOW_HOLSTER2, // empty +}; + +//===================== +// EV_BoltCallback +// This function is used to correct the origin and angles +// of the bolt, so it looks like it's stuck on the wall. +//===================== +void EV_BoltCallback ( struct tempent_s *ent, float frametime, float currenttime ) +{ + ent->entity.origin = ent->entity.baseline.vuser1; + ent->entity.angles = ent->entity.baseline.vuser2; +} + +void EV_FireCrossbow2( event_args_t *args ) +{ + vec3_t vecSrc, vecEnd; + vec3_t up, right, forward; + pmtrace_t tr; + + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + + VectorCopy( args->velocity, velocity ); + + AngleVectors( angles, forward, right, up ); + + EV_GetGunPosition( args, vecSrc, origin ); + + VectorMA( vecSrc, 8192, forward, vecEnd ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/xbow_fire1.wav", 1, ATTN_NORM, 0, 93 + gEngfuncs.pfnRandomLong(0,0xF) ); + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_ITEM, "weapons/xbow_reload1.wav", gEngfuncs.pfnRandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + gEngfuncs.pfnRandomLong(0,0xF) ); + + if ( EV_IsLocal( idx ) ) + { + if ( args->iparam1 ) + gEngfuncs.pEventAPI->EV_WeaponAnimation( CROSSBOW_FIRE1, 1 ); + else if ( args->iparam2 ) + gEngfuncs.pEventAPI->EV_WeaponAnimation( CROSSBOW_FIRE3, 1 ); + } + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( idx - 1 ); + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( vecSrc, vecEnd, PM_STUDIO_BOX, -1, &tr ); + + //We hit something + if ( tr.fraction < 1.0 ) + { + physent_t *pe = gEngfuncs.pEventAPI->EV_GetPhysent( tr.ent ); + + //Not the world, let's assume we hit something organic ( dog, cat, uncle joe, etc ). + if ( pe->solid != SOLID_BSP ) + { + switch( gEngfuncs.pfnRandomLong(0,1) ) + { + case 0: + gEngfuncs.pEventAPI->EV_PlaySound( idx, tr.endpos, CHAN_BODY, "weapons/xbow_hitbod1.wav", 1, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: + gEngfuncs.pEventAPI->EV_PlaySound( idx, tr.endpos, CHAN_BODY, "weapons/xbow_hitbod2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); break; + } + } + //Stick to world but don't stick to glass, it might break and leave the bolt floating. It can still stick to other non-transparent breakables though. + else if ( pe->rendermode == kRenderNormal ) + { + gEngfuncs.pEventAPI->EV_PlaySound( 0, tr.endpos, CHAN_BODY, "weapons/xbow_hit1.wav", gEngfuncs.pfnRandomFloat(0.95, 1.0), ATTN_NORM, 0, PITCH_NORM ); + + //Not underwater, do some sparks... + if ( gEngfuncs.PM_PointContents( tr.endpos, NULL ) != CONTENTS_WATER) + gEngfuncs.pEfxAPI->R_SparkShower( tr.endpos ); + + vec3_t vBoltAngles; + int iModelIndex = gEngfuncs.pEventAPI->EV_FindModelIndex( "models/crossbow_bolt.mdl" ); + + VectorAngles( forward, vBoltAngles ); + + TEMPENTITY *bolt = gEngfuncs.pEfxAPI->R_TempModel( tr.endpos - forward * 10, Vector( 0, 0, 0), vBoltAngles , 5, iModelIndex, TE_BOUNCE_NULL ); + + if ( bolt ) + { + bolt->flags |= ( FTENT_CLIENTCUSTOM ); //So it calls the callback function. + bolt->entity.baseline.vuser1 = tr.endpos - forward * 10; // Pull out a little bit + bolt->entity.baseline.vuser2 = vBoltAngles; //Look forward! + bolt->callback = EV_BoltCallback; //So we can set the angles and origin back. (Stick the bolt to the wall) + } + } + } + + gEngfuncs.pEventAPI->EV_PopPMStates(); +} + +//TODO: Fully predict the fliying bolt. +void EV_FireCrossbow( event_args_t *args ) +{ + int idx; + vec3_t origin; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/xbow_fire1.wav", 1, ATTN_NORM, 0, 93 + gEngfuncs.pfnRandomLong(0,0xF) ); + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_ITEM, "weapons/xbow_reload1.wav", gEngfuncs.pfnRandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + gEngfuncs.pfnRandomLong(0,0xF) ); + + //Only play the weapon anims if I shot it. + if ( EV_IsLocal( idx ) ) + { + if ( args->iparam1 ) + gEngfuncs.pEventAPI->EV_WeaponAnimation( CROSSBOW_FIRE1, 1 ); + else if ( args->iparam2 ) + gEngfuncs.pEventAPI->EV_WeaponAnimation( CROSSBOW_FIRE3, 1 ); + + V_PunchAxis( 0, -2.0 ); + } +} +//====================== +// CROSSBOW END +//====================== + +//====================== +// RPG START +//====================== +enum rpg_e { + RPG_IDLE = 0, + RPG_FIDGET, + RPG_RELOAD, // to reload + RPG_FIRE2, // to empty + RPG_HOLSTER1, // loaded + RPG_DRAW1, // loaded + RPG_HOLSTER2, // unloaded + RPG_DRAW_UL, // unloaded + RPG_IDLE_UL, // unloaded idle + RPG_FIDGET_UL, // unloaded fidget +}; + +void EV_FireRpg( event_args_t *args ) +{ + int idx; + vec3_t origin; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/rocketfire1.wav", 0.9, ATTN_NORM, 0, PITCH_NORM ); + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_ITEM, "weapons/glauncher.wav", 0.7, ATTN_NORM, 0, PITCH_NORM ); + + //Only play the weapon anims if I shot it. + if ( EV_IsLocal( idx ) ) + { + gEngfuncs.pEventAPI->EV_WeaponAnimation( RPG_FIRE2, 1 ); + + V_PunchAxis( 0, -5.0 ); + } +} +//====================== +// RPG END +//====================== + +//====================== +// EGON END +//====================== +enum egon_e { + EGON_IDLE1 = 0, + EGON_FIDGET1, + EGON_ALTFIREON, + EGON_ALTFIRECYCLE, + EGON_ALTFIREOFF, + EGON_FIRE1, + EGON_FIRE2, + EGON_FIRE3, + EGON_FIRE4, + EGON_DRAW, + EGON_HOLSTER +}; + +int g_fireAnims1[] = { EGON_FIRE1, EGON_FIRE2, EGON_FIRE3, EGON_FIRE4 }; +int g_fireAnims2[] = { EGON_ALTFIRECYCLE }; + +enum EGON_FIRESTATE { FIRE_OFF, FIRE_CHARGE }; +enum EGON_FIREMODE { FIRE_NARROW, FIRE_WIDE}; + +#define EGON_PRIMARY_VOLUME 450 +#define EGON_BEAM_SPRITE "sprites/xbeam1.spr" +#define EGON_FLARE_SPRITE "sprites/XSpark1.spr" +#define EGON_SOUND_OFF "weapons/egon_off1.wav" +#define EGON_SOUND_RUN "weapons/egon_run3.wav" +#define EGON_SOUND_STARTUP "weapons/egon_windup2.wav" + +#define ARRAYSIZE(p) (sizeof(p)/sizeof(p[0])) + +BEAM *pBeam; +BEAM *pBeam2; + +void EV_EgonFire( event_args_t *args ) +{ + int idx, iFireState, iFireMode; + vec3_t origin; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + iFireState = args->iparam1; + iFireMode = args->iparam2; + int iStartup = args->bparam1; + + + if ( iStartup ) + { + if ( iFireMode == FIRE_WIDE ) + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, EGON_SOUND_STARTUP, 0.98, ATTN_NORM, 0, 125 ); + else + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, EGON_SOUND_STARTUP, 0.9, ATTN_NORM, 0, 100 ); + } + else + { + if ( iFireMode == FIRE_WIDE ) + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_STATIC, EGON_SOUND_RUN, 0.98, ATTN_NORM, 0, 125 ); + else + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_STATIC, EGON_SOUND_RUN, 0.9, ATTN_NORM, 0, 100 ); + } + + //Only play the weapon anims if I shot it. + if ( EV_IsLocal( idx ) ) + gEngfuncs.pEventAPI->EV_WeaponAnimation ( g_fireAnims1[ gEngfuncs.pfnRandomLong( 0, 3 ) ], 1 ); + + if ( iStartup == 1 && EV_IsLocal( idx ) && !pBeam && !pBeam2 && cl_lw->value ) //Adrian: Added the cl_lw check for those lital people that hate weapon prediction. + { + vec3_t vecSrc, vecEnd, origin, angles, forward, right, up; + pmtrace_t tr; + + cl_entity_t *pl = gEngfuncs.GetEntityByIndex( idx ); + + if ( pl ) + { + VectorCopy( gHUD.m_vecAngles, angles ); + + AngleVectors( angles, forward, right, up ); + + EV_GetGunPosition( args, vecSrc, pl->origin ); + + VectorMA( vecSrc, 2048, forward, vecEnd ); + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( idx - 1 ); + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( vecSrc, vecEnd, PM_STUDIO_BOX, -1, &tr ); + + gEngfuncs.pEventAPI->EV_PopPMStates(); + + int iBeamModelIndex = gEngfuncs.pEventAPI->EV_FindModelIndex( EGON_BEAM_SPRITE ); + + float r = 50.0f; + float g = 50.0f; + float b = 125.0f; + + if ( IEngineStudio.IsHardware() ) + { + r /= 100.0f; + g /= 100.0f; + } + + + pBeam = gEngfuncs.pEfxAPI->R_BeamEntPoint ( idx | 0x1000, tr.endpos, iBeamModelIndex, 99999, 3.5, 0.2, 0.7, 55, 0, 0, r, g, b ); + + if ( pBeam ) + pBeam->flags |= ( FBEAM_SINENOISE ); + + pBeam2 = gEngfuncs.pEfxAPI->R_BeamEntPoint ( idx | 0x1000, tr.endpos, iBeamModelIndex, 99999, 5.0, 0.08, 0.7, 25, 0, 0, r, g, b ); + } + } +} + +void EV_EgonStop( event_args_t *args ) +{ + int idx; + vec3_t origin; + + idx = args->entindex; + VectorCopy ( args->origin, origin ); + + gEngfuncs.pEventAPI->EV_StopSound( idx, CHAN_STATIC, EGON_SOUND_RUN ); + + if ( args->iparam1 ) + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, EGON_SOUND_OFF, 0.98, ATTN_NORM, 0, 100 ); + + if ( EV_IsLocal( idx ) ) + { + if ( pBeam ) + { + pBeam->die = 0.0; + pBeam = NULL; + } + + + if ( pBeam2 ) + { + pBeam2->die = 0.0; + pBeam2 = NULL; + } + } +} +//====================== +// EGON END +//====================== + +//====================== +// HORNET START +//====================== +enum hgun_e { + HGUN_IDLE1 = 0, + HGUN_FIDGETSWAY, + HGUN_FIDGETSHAKE, + HGUN_DOWN, + HGUN_UP, + HGUN_SHOOT +}; + +void EV_HornetGunFire( event_args_t *args ) +{ + int idx, iFireMode; + vec3_t origin, angles, vecSrc, forward, right, up; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + iFireMode = args->iparam1; + + //Only play the weapon anims if I shot it. + if ( EV_IsLocal( idx ) ) + { + V_PunchAxis( 0, gEngfuncs.pfnRandomLong ( 0, 2 ) ); + gEngfuncs.pEventAPI->EV_WeaponAnimation ( HGUN_SHOOT, 1 ); + } + + switch ( gEngfuncs.pfnRandomLong ( 0 , 2 ) ) + { + case 0: gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "agrunt/ag_fire1.wav", 1, ATTN_NORM, 0, 100 ); break; + case 1: gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "agrunt/ag_fire2.wav", 1, ATTN_NORM, 0, 100 ); break; + case 2: gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "agrunt/ag_fire3.wav", 1, ATTN_NORM, 0, 100 ); break; + } +} +//====================== +// HORNET END +//====================== + +//====================== +// TRIPMINE START +//====================== +enum tripmine_e { + TRIPMINE_IDLE1 = 0, + TRIPMINE_IDLE2, + TRIPMINE_ARM1, + TRIPMINE_ARM2, + TRIPMINE_FIDGET, + TRIPMINE_HOLSTER, + TRIPMINE_DRAW, + TRIPMINE_WORLD, + TRIPMINE_GROUND, +}; + +//We only check if it's possible to put a trip mine +//and if it is, then we play the animation. Server still places it. +void EV_TripmineFire( event_args_t *args ) +{ + int idx; + vec3_t vecSrc, angles, view_ofs, forward; + pmtrace_t tr; + + idx = args->entindex; + VectorCopy( args->origin, vecSrc ); + VectorCopy( args->angles, angles ); + + AngleVectors ( angles, forward, NULL, NULL ); + + if ( !EV_IsLocal ( idx ) ) + return; + + // Grab predicted result for local player + gEngfuncs.pEventAPI->EV_LocalPlayerViewheight( view_ofs ); + + vecSrc = vecSrc + view_ofs; + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( idx - 1 ); + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( vecSrc, vecSrc + forward * 128, PM_NORMAL, -1, &tr ); + + //Hit something solid + if ( tr.fraction < 1.0 ) + gEngfuncs.pEventAPI->EV_WeaponAnimation ( TRIPMINE_DRAW, 0 ); + + gEngfuncs.pEventAPI->EV_PopPMStates(); +} +//====================== +// TRIPMINE END +//====================== + +//====================== +// SQUEAK START +//====================== +enum squeak_e { + SQUEAK_IDLE1 = 0, + SQUEAK_FIDGETFIT, + SQUEAK_FIDGETNIP, + SQUEAK_DOWN, + SQUEAK_UP, + SQUEAK_THROW +}; + +#define VEC_HULL_MIN Vector(-16, -16, -36) +#define VEC_DUCK_HULL_MIN Vector(-16, -16, -18 ) + +void EV_SnarkFire( event_args_t *args ) +{ + int idx; + vec3_t vecSrc, angles, view_ofs, forward; + pmtrace_t tr; + + idx = args->entindex; + VectorCopy( args->origin, vecSrc ); + VectorCopy( args->angles, angles ); + + AngleVectors ( angles, forward, NULL, NULL ); + + if ( !EV_IsLocal ( idx ) ) + return; + + if ( args->ducking ) + vecSrc = vecSrc - ( VEC_HULL_MIN - VEC_DUCK_HULL_MIN ); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( idx - 1 ); + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( vecSrc + forward * 20, vecSrc + forward * 64, PM_NORMAL, -1, &tr ); + + //Find space to drop the thing. + if ( tr.allsolid == 0 && tr.startsolid == 0 && tr.fraction > 0.25 ) + gEngfuncs.pEventAPI->EV_WeaponAnimation ( SQUEAK_THROW, 0 ); + + gEngfuncs.pEventAPI->EV_PopPMStates(); +} +//====================== +// SQUEAK END +//====================== + +void EV_TrainPitchAdjust( event_args_t *args ) +{ + int idx; + vec3_t origin; + + unsigned short us_params; + int noise; + float m_flVolume; + int pitch; + int stop; + + char sz[ 256 ]; + + idx = args->entindex; + + VectorCopy( args->origin, origin ); + + us_params = (unsigned short)args->iparam1; + stop = args->bparam1; + + m_flVolume = (float)(us_params & 0x003f)/40.0; + noise = (int)(((us_params) >> 12 ) & 0x0007); + pitch = (int)( 10.0 * (float)( ( us_params >> 6 ) & 0x003f ) ); + + switch ( noise ) + { + case 1: strcpy( sz, "plats/ttrain1.wav"); break; + case 2: strcpy( sz, "plats/ttrain2.wav"); break; + case 3: strcpy( sz, "plats/ttrain3.wav"); break; + case 4: strcpy( sz, "plats/ttrain4.wav"); break; + case 5: strcpy( sz, "plats/ttrain6.wav"); break; + case 6: strcpy( sz, "plats/ttrain7.wav"); break; + default: + // no sound + strcpy( sz, "" ); + return; + } + + if ( stop ) + { + gEngfuncs.pEventAPI->EV_StopSound( idx, CHAN_STATIC, sz ); + } + else + { + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_STATIC, sz, m_flVolume, ATTN_NORM, SND_CHANGE_PITCH, pitch ); + } +} + +int EV_TFC_IsAllyTeam( int iTeam1, int iTeam2 ) +{ + return 0; +} \ No newline at end of file diff --git a/cl_dll/ev_hldm.h b/cl_dll/ev_hldm.h new file mode 100644 index 0000000..5fd90c1 --- /dev/null +++ b/cl_dll/ev_hldm.h @@ -0,0 +1,95 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined ( EV_HLDMH ) +#define EV_HLDMH + +// bullet types +typedef enum +{ + BULLET_NONE = 0, + BULLET_PLAYER_9MM, // glock + BULLET_PLAYER_MP5, // mp5 + BULLET_PLAYER_357, // python + BULLET_PLAYER_BUCKSHOT, // shotgun + BULLET_PLAYER_CROWBAR, // crowbar swipe + + BULLET_MONSTER_9MM, + BULLET_MONSTER_MP5, + BULLET_MONSTER_12MM, +} Bullet; + +enum glock_e { + GLOCK_IDLE1 = 0, + GLOCK_IDLE2, + GLOCK_IDLE3, + GLOCK_SHOOT, + GLOCK_SHOOT_EMPTY, + GLOCK_RELOAD, + GLOCK_RELOAD_NOT_EMPTY, + GLOCK_DRAW, + GLOCK_HOLSTER, + GLOCK_ADD_SILENCER +}; + +enum shotgun_e { + SHOTGUN_IDLE = 0, + SHOTGUN_FIRE, + SHOTGUN_FIRE2, + SHOTGUN_RELOAD, + SHOTGUN_PUMP, + SHOTGUN_START_RELOAD, + SHOTGUN_DRAW, + SHOTGUN_HOLSTER, + SHOTGUN_IDLE4, + SHOTGUN_IDLE_DEEP +}; + +enum mp5_e +{ + MP5_LONGIDLE = 0, + MP5_IDLE1, + MP5_LAUNCH, + MP5_RELOAD, + MP5_DEPLOY, + MP5_FIRE1, + MP5_FIRE2, + MP5_FIRE3, +}; + +enum python_e { + PYTHON_IDLE1 = 0, + PYTHON_FIDGET, + PYTHON_FIRE1, + PYTHON_RELOAD, + PYTHON_HOLSTER, + PYTHON_DRAW, + PYTHON_IDLE2, + PYTHON_IDLE3 +}; + +#define GAUSS_PRIMARY_CHARGE_VOLUME 256// how loud gauss is while charging +#define GAUSS_PRIMARY_FIRE_VOLUME 450// how loud gauss is when discharged + +enum gauss_e { + GAUSS_IDLE = 0, + GAUSS_IDLE2, + GAUSS_FIDGET, + GAUSS_SPINUP, + GAUSS_SPIN, + GAUSS_FIRE, + GAUSS_FIRE2, + GAUSS_HOLSTER, + GAUSS_DRAW +}; + +void EV_HLDM_GunshotDecalTrace( pmtrace_t *pTrace, char *decalName ); +void EV_HLDM_DecalGunshot( pmtrace_t *pTrace, int iBulletType ); +int EV_HLDM_CheckTracer( int idx, float *vecSrc, float *end, float *forward, float *right, int iBulletType, int iTracerFreq, int *tracerCount ); +void EV_HLDM_FireBullets( int idx, float *forward, float *right, float *up, int cShots, float *vecSrc, float *vecDirShooting, float flDistance, int iBulletType, int iTracerFreq, int *tracerCount, float flSpreadX, float flSpreadY ); + +#endif // EV_HLDMH \ No newline at end of file diff --git a/cl_dll/events.cpp b/cl_dll/events.cpp new file mode 100644 index 0000000..a51dd72 --- /dev/null +++ b/cl_dll/events.cpp @@ -0,0 +1,23 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "hud.h" +#include "cl_util.h" + +void Game_HookEvents( void ); + +/* +=================== +EV_HookEvents + +See if game specific code wants to hook any events. +=================== +*/ +void EV_HookEvents( void ) +{ + Game_HookEvents(); +} \ No newline at end of file diff --git a/cl_dll/eventscripts.h b/cl_dll/eventscripts.h new file mode 100644 index 0000000..5c4677a --- /dev/null +++ b/cl_dll/eventscripts.h @@ -0,0 +1,73 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// eventscripts.h +#if !defined ( EVENTSCRIPTSH ) +#define EVENTSCRIPTSH + +// defaults for clientinfo messages +#define DEFAULT_VIEWHEIGHT 28 +#define VEC_DUCK_VIEW 12 + +#define FTENT_FADEOUT 0x00000080 + +#define DMG_GENERIC 0 // generic damage was done +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FREEZE (1 << 4) // frozen +#define DMG_FALL (1 << 5) // fell too far +#define DMG_BLAST (1 << 6) // explosive blast damage +#define DMG_CLUB (1 << 7) // crowbar, punch, headbutt +#define DMG_SHOCK (1 << 8) // electric shock +#define DMG_SONIC (1 << 9) // sound pulse shockwave +#define DMG_ENERGYBEAM (1 << 10) // laser or other high energy beam +#define DMG_NEVERGIB (1 << 12) // with this bit OR'd in, no damage type will be able to gib victims upon death +#define DMG_ALWAYSGIB (1 << 13) // with this bit OR'd in, any damage type can be made to gib victims upon death. + +// time-based damage +//mask off TF-specific stuff too +#define DMG_TIMEBASED (~(0xff003fff)) // mask for time-based damage + +#define DMG_DROWN (1 << 14) // Drowning +#define DMG_FIRSTTIMEBASED DMG_DROWN + +#define DMG_PARALYZE (1 << 15) // slows affected creature down +#define DMG_NERVEGAS (1 << 16) // nerve toxins, very bad +#define DMG_POISON (1 << 17) // blood poisioning +#define DMG_RADIATION (1 << 18) // radiation exposure +#define DMG_DROWNRECOVER (1 << 19) // drowning recovery +#define DMG_ACID (1 << 20) // toxic chemicals or acid burns +#define DMG_SLOWBURN (1 << 21) // in an oven +#define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer +#define DMG_MORTAR (1 << 23) // Hit by air raid (done to distinguish grenade from mortar) + +//TF ADDITIONS +#define DMG_IGNITE (1 << 24) // Players hit by this begin to burn +#define DMG_RADIUS_MAX (1 << 25) // Radius damage with this flag doesn't decrease over distance +#define DMG_RADIUS_QUAKE (1 << 26) // Radius damage is done like Quake. 1/2 damage at 1/2 radius. +#define DMG_IGNOREARMOR (1 << 27) // Damage ignores target's armor +#define DMG_AIMED (1 << 28) // Does Hit location damage +#define DMG_WALLPIERCING (1 << 29) // Blast Damages ents through walls + +#define DMG_CALTROP (1<<30) +#define DMG_HALLUC (1<<31) + +// Some of these are HL/TFC specific? +void EV_EjectBrass( float *origin, float *velocity, float rotation, int model, int soundtype ); +void EV_GetGunPosition( struct event_args_s *args, float *pos, float *origin ); +void EV_GetDefaultShellInfo( struct event_args_s *args, float *origin, float *velocity, float *ShellVelocity, float *ShellOrigin, float *forward, float *right, float *up, float forwardScale, float upScale, float rightScale ); +qboolean EV_IsLocal( int idx ); +qboolean EV_IsPlayer( int idx ); +void EV_CreateTracer( float *start, float *end ); + +struct cl_entity_s *GetEntity( int idx ); +struct cl_entity_s *GetViewEntity( void ); +void EV_MuzzleFlash( void ); + +#endif // EVENTSCRIPTSH diff --git a/cl_dll/flashlight.cpp b/cl_dll/flashlight.cpp new file mode 100644 index 0000000..d12cfbd --- /dev/null +++ b/cl_dll/flashlight.cpp @@ -0,0 +1,149 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// flashlight.cpp +// +// implementation of CHudFlashlight class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + + + +DECLARE_MESSAGE(m_Flash, FlashBat) +DECLARE_MESSAGE(m_Flash, Flashlight) + +#define BAT_NAME "sprites/%d_Flashlight.spr" + +int CHudFlashlight::Init(void) +{ + m_fFade = 0; + m_fOn = 0; + + HOOK_MESSAGE(Flashlight); + HOOK_MESSAGE(FlashBat); + + m_iFlags |= HUD_ACTIVE; + + gHUD.AddHudElem(this); + + return 1; +}; + +void CHudFlashlight::Reset(void) +{ + m_fFade = 0; + m_fOn = 0; +} + +int CHudFlashlight::VidInit(void) +{ + int HUD_flash_empty = gHUD.GetSpriteIndex( "flash_empty" ); + int HUD_flash_full = gHUD.GetSpriteIndex( "flash_full" ); + int HUD_flash_beam = gHUD.GetSpriteIndex( "flash_beam" ); + + m_hSprite1 = gHUD.GetSprite(HUD_flash_empty); + m_hSprite2 = gHUD.GetSprite(HUD_flash_full); + m_hBeam = gHUD.GetSprite(HUD_flash_beam); + m_prc1 = &gHUD.GetSpriteRect(HUD_flash_empty); + m_prc2 = &gHUD.GetSpriteRect(HUD_flash_full); + m_prcBeam = &gHUD.GetSpriteRect(HUD_flash_beam); + m_iWidth = m_prc2->right - m_prc2->left; + + return 1; +}; + +int CHudFlashlight:: MsgFunc_FlashBat(const char *pszName, int iSize, void *pbuf ) +{ + + + BEGIN_READ( pbuf, iSize ); + int x = READ_BYTE(); + m_iBat = x; + m_flBat = ((float)x)/100.0; + + return 1; +} + +int CHudFlashlight:: MsgFunc_Flashlight(const char *pszName, int iSize, void *pbuf ) +{ + + BEGIN_READ( pbuf, iSize ); + m_fOn = READ_BYTE(); + int x = READ_BYTE(); + m_iBat = x; + m_flBat = ((float)x)/100.0; + + return 1; +} + +int CHudFlashlight::Draw(float flTime) +{ + if ( gHUD.m_iHideHUDDisplay & ( HIDEHUD_FLASHLIGHT | HIDEHUD_ALL ) ) + return 1; + + int r, g, b, x, y, a; + wrect_t rc; + + if (!(gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT)) )) + return 1; + + if (m_fOn) + a = 225; + else + a = MIN_ALPHA; + + if (m_flBat < 0.20) + UnpackRGB(r,g,b, RGB_REDISH); + else + UnpackRGB(r,g,b, RGB_YELLOWISH); + + ScaleColors(r, g, b, a); + + y = (m_prc1->bottom - m_prc2->top)/2; + x = ScreenWidth - m_iWidth - m_iWidth/2 ; + + // Draw the flashlight casing + SPR_Set(m_hSprite1, r, g, b ); + SPR_DrawAdditive( 0, x, y, m_prc1); + + if ( m_fOn ) + { // draw the flashlight beam + x = ScreenWidth - m_iWidth/2; + + SPR_Set( m_hBeam, r, g, b ); + SPR_DrawAdditive( 0, x, y, m_prcBeam ); + } + + // draw the flashlight energy level + x = ScreenWidth - m_iWidth - m_iWidth/2 ; + int iOffset = m_iWidth * (1.0 - m_flBat); + if (iOffset < m_iWidth) + { + rc = *m_prc2; + rc.left += iOffset; + + SPR_Set(m_hSprite2, r, g, b ); + SPR_DrawAdditive( 0, x + iOffset, y, &rc); + } + + + return 1; +} \ No newline at end of file diff --git a/cl_dll/geiger.cpp b/cl_dll/geiger.cpp new file mode 100644 index 0000000..8fa1407 --- /dev/null +++ b/cl_dll/geiger.cpp @@ -0,0 +1,184 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Geiger.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include + +#include "parsemsg.h" + +DECLARE_MESSAGE(m_Geiger, Geiger ) + +int CHudGeiger::Init(void) +{ + HOOK_MESSAGE( Geiger ); + + m_iGeigerRange = 0; + m_iFlags = 0; + + gHUD.AddHudElem(this); + + srand( (unsigned)time( NULL ) ); + + return 1; +}; + +int CHudGeiger::VidInit(void) +{ + return 1; +}; + +int CHudGeiger::MsgFunc_Geiger(const char *pszName, int iSize, void *pbuf) +{ + + BEGIN_READ( pbuf, iSize ); + + // update geiger data + m_iGeigerRange = READ_BYTE(); + m_iGeigerRange = m_iGeigerRange << 2; + + m_iFlags |= HUD_ACTIVE; + + return 1; +} + +int CHudGeiger::Draw (float flTime) +{ + int pct; + float flvol; + int rg[3]; + int i; + + if (m_iGeigerRange < 1000 && m_iGeigerRange > 0) + { + // peicewise linear is better than continuous formula for this + if (m_iGeigerRange > 800) + { + pct = 0; //Con_Printf ( "range > 800\n"); + } + else if (m_iGeigerRange > 600) + { + pct = 2; + flvol = 0.4; //Con_Printf ( "range > 600\n"); + rg[0] = 1; + rg[1] = 1; + i = 2; + } + else if (m_iGeigerRange > 500) + { + pct = 4; + flvol = 0.5; //Con_Printf ( "range > 500\n"); + rg[0] = 1; + rg[1] = 2; + i = 2; + } + else if (m_iGeigerRange > 400) + { + pct = 8; + flvol = 0.6; //Con_Printf ( "range > 400\n"); + rg[0] = 1; + rg[1] = 2; + rg[2] = 3; + i = 3; + } + else if (m_iGeigerRange > 300) + { + pct = 8; + flvol = 0.7; //Con_Printf ( "range > 300\n"); + rg[0] = 2; + rg[1] = 3; + rg[2] = 4; + i = 3; + } + else if (m_iGeigerRange > 200) + { + pct = 28; + flvol = 0.78; //Con_Printf ( "range > 200\n"); + rg[0] = 2; + rg[1] = 3; + rg[2] = 4; + i = 3; + } + else if (m_iGeigerRange > 150) + { + pct = 40; + flvol = 0.80; //Con_Printf ( "range > 150\n"); + rg[0] = 3; + rg[1] = 4; + rg[2] = 5; + i = 3; + } + else if (m_iGeigerRange > 100) + { + pct = 60; + flvol = 0.85; //Con_Printf ( "range > 100\n"); + rg[0] = 3; + rg[1] = 4; + rg[2] = 5; + i = 3; + } + else if (m_iGeigerRange > 75) + { + pct = 80; + flvol = 0.9; //Con_Printf ( "range > 75\n"); + //gflGeigerDelay = cl.time + GEIGERDELAY * 0.75; + rg[0] = 4; + rg[1] = 5; + rg[2] = 6; + i = 3; + } + else if (m_iGeigerRange > 50) + { + pct = 90; + flvol = 0.95; //Con_Printf ( "range > 50\n"); + rg[0] = 5; + rg[1] = 6; + i = 2; + } + else + { + pct = 95; + flvol = 1.0; //Con_Printf ( "range < 50\n"); + rg[0] = 5; + rg[1] = 6; + i = 2; + } + + flvol = (flvol * ((rand() & 127)) / 255) + 0.25; // UTIL_RandomFloat(0.25, 0.5); + + if ((rand() & 127) < pct || (rand() & 127) < pct) + { + //S_StartDynamicSound (-1, 0, rgsfx[rand() % i], r_origin, flvol, 1.0, 0, 100); + char sz[256]; + + int j = rand() & 1; + if (i > 2) + j += rand() & 1; + + sprintf(sz, "player/geiger%d.wav", j + 1); + PlaySound(sz, flvol); + + } + } + + return 1; +} diff --git a/cl_dll/global_consts.h b/cl_dll/global_consts.h new file mode 100644 index 0000000..96be61e --- /dev/null +++ b/cl_dll/global_consts.h @@ -0,0 +1,30 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= +#ifndef GLOBALCONSTS_H +#define GLOBALCONSTS_H +#ifdef _WIN32 +#pragma once +#endif + + + +enum +{ + MAX_PLAYERS = 64, + MAX_TEAMS = 64, + MAX_TEAM_NAME = 16, +}; + +#define MAX_SCORES 10 +#define MAX_SCOREBOARD_TEAMS 5 + +#define NUM_ROWS (MAX_PLAYERS + (MAX_SCOREBOARD_TEAMS * 2)) + +#define MAX_SERVERNAME_LENGTH 64 +#define MAX_TEAMNAME_SIZE 32 + +#endif // GLOBALCONSTS_H \ No newline at end of file diff --git a/cl_dll/health.cpp b/cl_dll/health.cpp new file mode 100644 index 0000000..67b3a76 --- /dev/null +++ b/cl_dll/health.cpp @@ -0,0 +1,473 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Health.cpp +// +// implementation of CHudHealth class +// + +#include "stdio.h" +#include "stdlib.h" +#include "math.h" + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" +#include + + +DECLARE_MESSAGE(m_Health, Health ) +DECLARE_MESSAGE(m_Health, Damage ) + +#define PAIN_NAME "sprites/%d_pain.spr" +#define DAMAGE_NAME "sprites/%d_dmg.spr" + +int giDmgHeight, giDmgWidth; + +int giDmgFlags[NUM_DMG_TYPES] = +{ + DMG_POISON, + DMG_ACID, + DMG_FREEZE|DMG_SLOWFREEZE, + DMG_DROWN, + DMG_BURN|DMG_SLOWBURN, + DMG_NERVEGAS, + DMG_RADIATION, + DMG_SHOCK, + DMG_CALTROP, + DMG_TRANQ, + DMG_CONCUSS, + DMG_HALLUC +}; + +int CHudHealth::Init(void) +{ + HOOK_MESSAGE(Health); + HOOK_MESSAGE(Damage); + m_iHealth = 100; + m_fFade = 0; + m_iFlags = 0; + m_bitsDamage = 0; + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 0; + giDmgHeight = 0; + giDmgWidth = 0; + + memset(m_dmg, 0, sizeof(DAMAGE_IMAGE) * NUM_DMG_TYPES); + + + gHUD.AddHudElem(this); + return 1; +} + +void CHudHealth::Reset( void ) +{ + // make sure the pain compass is cleared when the player respawns + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 0; + + + // force all the flashing damage icons to expire + m_bitsDamage = 0; + for ( int i = 0; i < NUM_DMG_TYPES; i++ ) + { + m_dmg[i].fExpire = 0; + } +} + +int CHudHealth::VidInit(void) +{ + m_hSprite = 0; + + m_HUD_dmg_bio = gHUD.GetSpriteIndex( "dmg_bio" ) + 1; + m_HUD_cross = gHUD.GetSpriteIndex( "cross" ); + + giDmgHeight = gHUD.GetSpriteRect(m_HUD_dmg_bio).right - gHUD.GetSpriteRect(m_HUD_dmg_bio).left; + giDmgWidth = gHUD.GetSpriteRect(m_HUD_dmg_bio).bottom - gHUD.GetSpriteRect(m_HUD_dmg_bio).top; + return 1; +} + +int CHudHealth:: MsgFunc_Health(const char *pszName, int iSize, void *pbuf ) +{ + // TODO: update local health data + BEGIN_READ( pbuf, iSize ); + int x = READ_BYTE(); + + m_iFlags |= HUD_ACTIVE; + + // Only update the fade if we've changed health + if (x != m_iHealth) + { + m_fFade = FADE_TIME; + m_iHealth = x; + } + + return 1; +} + + +int CHudHealth:: MsgFunc_Damage(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int armor = READ_BYTE(); // armor + int damageTaken = READ_BYTE(); // health + long bitsDamage = READ_LONG(); // damage bits + + vec3_t vecFrom; + + for ( int i = 0 ; i < 3 ; i++) + vecFrom[i] = READ_COORD(); + + UpdateTiles(gHUD.m_flTime, bitsDamage); + + // Actually took damage? + if ( damageTaken > 0 || armor > 0 ) + CalcDamageDirection(vecFrom); + + return 1; +} + + +// Returns back a color from the +// Green <-> Yellow <-> Red ramp +void CHudHealth::GetPainColor( int &r, int &g, int &b ) +{ + int iHealth = m_iHealth; + + if (iHealth > 25) + iHealth -= 25; + else if ( iHealth < 0 ) + iHealth = 0; +#if 0 + g = iHealth * 255 / 100; + r = 255 - g; + b = 0; +#else + if (m_iHealth > 25) + { + UnpackRGB(r,g,b, RGB_YELLOWISH); + } + else + { + r = 250; + g = 0; + b = 0; + } +#endif +} + +int CHudHealth::Draw(float flTime) +{ + int r, g, b; + int a = 0, x, y; + int HealthWidth; + + if ( (gHUD.m_iHideHUDDisplay & HIDEHUD_HEALTH) || gEngfuncs.IsSpectateOnly() ) + return 1; + + if ( !m_hSprite ) + m_hSprite = LoadSprite(PAIN_NAME); + + // Has health changed? Flash the health # + if (m_fFade) + { + m_fFade -= (gHUD.m_flTimeDelta * 20); + if (m_fFade <= 0) + { + a = MIN_ALPHA; + m_fFade = 0; + } + + // Fade the health number back to dim + + a = MIN_ALPHA + (m_fFade/FADE_TIME) * 128; + + } + else + a = MIN_ALPHA; + + // If health is getting low, make it bright red + if (m_iHealth <= 15) + a = 255; + + GetPainColor( r, g, b ); + ScaleColors(r, g, b, a ); + + // Only draw health if we have the suit. + if (gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT))) + { + HealthWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; + int CrossWidth = gHUD.GetSpriteRect(m_HUD_cross).right - gHUD.GetSpriteRect(m_HUD_cross).left; + + y = ScreenHeight - gHUD.m_iFontHeight - gHUD.m_iFontHeight / 2; + x = CrossWidth /2; + + SPR_Set(gHUD.GetSprite(m_HUD_cross), r, g, b); + SPR_DrawAdditive(0, x, y, &gHUD.GetSpriteRect(m_HUD_cross)); + + x = CrossWidth + HealthWidth / 2; + + x = gHUD.DrawHudNumber(x, y, DHN_3DIGITS | DHN_DRAWZERO, m_iHealth, r, g, b); + + x += HealthWidth/2; + + int iHeight = gHUD.m_iFontHeight; + int iWidth = HealthWidth/10; + FillRGBA(x, y, iWidth, iHeight, 255, 160, 0, a); + } + + DrawDamage(flTime); + return DrawPain(flTime); +} + +void CHudHealth::CalcDamageDirection(vec3_t vecFrom) +{ + vec3_t forward, right, up; + float side, front; + vec3_t vecOrigin, vecAngles; + + if (!vecFrom[0] && !vecFrom[1] && !vecFrom[2]) + { + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 0; + return; + } + + + memcpy(vecOrigin, gHUD.m_vecOrigin, sizeof(vec3_t)); + memcpy(vecAngles, gHUD.m_vecAngles, sizeof(vec3_t)); + + + VectorSubtract (vecFrom, vecOrigin, vecFrom); + + float flDistToTarget = vecFrom.Length(); + + vecFrom = vecFrom.Normalize(); + AngleVectors (vecAngles, forward, right, up); + + front = DotProduct (vecFrom, right); + side = DotProduct (vecFrom, forward); + + if (flDistToTarget <= 50) + { + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 1; + } + else + { + if (side > 0) + { + if (side > 0.3) + m_fAttackFront = max(m_fAttackFront, side); + } + else + { + float f = fabs(side); + if (f > 0.3) + m_fAttackRear = max(m_fAttackRear, f); + } + + if (front > 0) + { + if (front > 0.3) + m_fAttackRight = max(m_fAttackRight, front); + } + else + { + float f = fabs(front); + if (f > 0.3) + m_fAttackLeft = max(m_fAttackLeft, f); + } + } +} + +int CHudHealth::DrawPain(float flTime) +{ + if (!(m_fAttackFront || m_fAttackRear || m_fAttackLeft || m_fAttackRight)) + return 1; + + int r, g, b; + int x, y, a, shade; + + // TODO: get the shift value of the health + a = 255; // max brightness until then + + float fFade = gHUD.m_flTimeDelta * 2; + + // SPR_Draw top + if (m_fAttackFront > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackFront, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 - SPR_Width(m_hSprite, 0)/2; + y = ScreenHeight/2 - SPR_Height(m_hSprite,0) * 3; + SPR_DrawAdditive(0, x, y, NULL); + m_fAttackFront = max( 0, m_fAttackFront - fFade ); + } else + m_fAttackFront = 0; + + if (m_fAttackRight > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackRight, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 + SPR_Width(m_hSprite, 1) * 2; + y = ScreenHeight/2 - SPR_Height(m_hSprite,1)/2; + SPR_DrawAdditive(1, x, y, NULL); + m_fAttackRight = max( 0, m_fAttackRight - fFade ); + } else + m_fAttackRight = 0; + + if (m_fAttackRear > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackRear, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 - SPR_Width(m_hSprite, 2)/2; + y = ScreenHeight/2 + SPR_Height(m_hSprite,2) * 2; + SPR_DrawAdditive(2, x, y, NULL); + m_fAttackRear = max( 0, m_fAttackRear - fFade ); + } else + m_fAttackRear = 0; + + if (m_fAttackLeft > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackLeft, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 - SPR_Width(m_hSprite, 3) * 3; + y = ScreenHeight/2 - SPR_Height(m_hSprite,3)/2; + SPR_DrawAdditive(3, x, y, NULL); + + m_fAttackLeft = max( 0, m_fAttackLeft - fFade ); + } else + m_fAttackLeft = 0; + + return 1; +} + +int CHudHealth::DrawDamage(float flTime) +{ + int r, g, b, a; + DAMAGE_IMAGE *pdmg; + + if (!m_bitsDamage) + return 1; + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + a = (int)( fabs(sin(flTime*2)) * 256.0); + + ScaleColors(r, g, b, a); + + // Draw all the items + int i; + for ( i = 0; i < NUM_DMG_TYPES; i++) + { + if (m_bitsDamage & giDmgFlags[i]) + { + pdmg = &m_dmg[i]; + SPR_Set(gHUD.GetSprite(m_HUD_dmg_bio + i), r, g, b ); + SPR_DrawAdditive(0, pdmg->x, pdmg->y, &gHUD.GetSpriteRect(m_HUD_dmg_bio + i)); + } + } + + + // check for bits that should be expired + for ( i = 0; i < NUM_DMG_TYPES; i++ ) + { + DAMAGE_IMAGE *pdmg = &m_dmg[i]; + + if ( m_bitsDamage & giDmgFlags[i] ) + { + pdmg->fExpire = min( flTime + DMG_IMAGE_LIFE, pdmg->fExpire ); + + if ( pdmg->fExpire <= flTime // when the time has expired + && a < 40 ) // and the flash is at the low point of the cycle + { + pdmg->fExpire = 0; + + int y = pdmg->y; + pdmg->x = pdmg->y = 0; + + // move everyone above down + for (int j = 0; j < NUM_DMG_TYPES; j++) + { + pdmg = &m_dmg[j]; + if ((pdmg->y) && (pdmg->y < y)) + pdmg->y += giDmgHeight; + + } + + m_bitsDamage &= ~giDmgFlags[i]; // clear the bits + } + } + } + + return 1; +} + + +void CHudHealth::UpdateTiles(float flTime, long bitsDamage) +{ + DAMAGE_IMAGE *pdmg; + + // Which types are new? + long bitsOn = ~m_bitsDamage & bitsDamage; + + for (int i = 0; i < NUM_DMG_TYPES; i++) + { + pdmg = &m_dmg[i]; + + // Is this one already on? + if (m_bitsDamage & giDmgFlags[i]) + { + pdmg->fExpire = flTime + DMG_IMAGE_LIFE; // extend the duration + if (!pdmg->fBaseline) + pdmg->fBaseline = flTime; + } + + // Are we just turning it on? + if (bitsOn & giDmgFlags[i]) + { + // put this one at the bottom + pdmg->x = giDmgWidth/8; + pdmg->y = ScreenHeight - giDmgHeight * 2; + pdmg->fExpire=flTime + DMG_IMAGE_LIFE; + + // move everyone else up + for (int j = 0; j < NUM_DMG_TYPES; j++) + { + if (j == i) + continue; + + pdmg = &m_dmg[j]; + if (pdmg->y) + pdmg->y -= giDmgHeight; + + } + pdmg = &m_dmg[i]; + } + } + + // damage bits are only turned on here; they are turned off when the draw time has expired (in DrawDamage()) + m_bitsDamage |= bitsDamage; +} diff --git a/cl_dll/health.h b/cl_dll/health.h new file mode 100644 index 0000000..067db4b --- /dev/null +++ b/cl_dll/health.h @@ -0,0 +1,127 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#define DMG_IMAGE_LIFE 2 // seconds that image is up + +#define DMG_IMAGE_POISON 0 +#define DMG_IMAGE_ACID 1 +#define DMG_IMAGE_COLD 2 +#define DMG_IMAGE_DROWN 3 +#define DMG_IMAGE_BURN 4 +#define DMG_IMAGE_NERVE 5 +#define DMG_IMAGE_RAD 6 +#define DMG_IMAGE_SHOCK 7 +//tf defines +#define DMG_IMAGE_CALTROP 8 +#define DMG_IMAGE_TRANQ 9 +#define DMG_IMAGE_CONCUSS 10 +#define DMG_IMAGE_HALLUC 11 +#define NUM_DMG_TYPES 12 +// instant damage + +#define DMG_GENERIC 0 // generic damage was done +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FREEZE (1 << 4) // frozen +#define DMG_FALL (1 << 5) // fell too far +#define DMG_BLAST (1 << 6) // explosive blast damage +#define DMG_CLUB (1 << 7) // crowbar, punch, headbutt +#define DMG_SHOCK (1 << 8) // electric shock +#define DMG_SONIC (1 << 9) // sound pulse shockwave +#define DMG_ENERGYBEAM (1 << 10) // laser or other high energy beam +#define DMG_NEVERGIB (1 << 12) // with this bit OR'd in, no damage type will be able to gib victims upon death +#define DMG_ALWAYSGIB (1 << 13) // with this bit OR'd in, any damage type can be made to gib victims upon death. + + +// time-based damage +//mask off TF-specific stuff too +#define DMG_TIMEBASED (~(0xff003fff)) // mask for time-based damage + + +#define DMG_DROWN (1 << 14) // Drowning +#define DMG_FIRSTTIMEBASED DMG_DROWN + +#define DMG_PARALYZE (1 << 15) // slows affected creature down +#define DMG_NERVEGAS (1 << 16) // nerve toxins, very bad +#define DMG_POISON (1 << 17) // blood poisioning +#define DMG_RADIATION (1 << 18) // radiation exposure +#define DMG_DROWNRECOVER (1 << 19) // drowning recovery +#define DMG_ACID (1 << 20) // toxic chemicals or acid burns +#define DMG_SLOWBURN (1 << 21) // in an oven +#define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer +#define DMG_MORTAR (1 << 23) // Hit by air raid (done to distinguish grenade from mortar) + +//TF ADDITIONS +#define DMG_IGNITE (1 << 24) // Players hit by this begin to burn +#define DMG_RADIUS_MAX (1 << 25) // Radius damage with this flag doesn't decrease over distance +#define DMG_RADIUS_QUAKE (1 << 26) // Radius damage is done like Quake. 1/2 damage at 1/2 radius. +#define DMG_IGNOREARMOR (1 << 27) // Damage ignores target's armor +#define DMG_AIMED (1 << 28) // Does Hit location damage +#define DMG_WALLPIERCING (1 << 29) // Blast Damages ents through walls + +#define DMG_CALTROP (1<<30) +#define DMG_HALLUC (1<<31) + +// TF Healing Additions for TakeHealth +#define DMG_IGNORE_MAXHEALTH DMG_IGNITE +// TF Redefines since we never use the originals +#define DMG_NAIL DMG_SLASH +#define DMG_NOT_SELF DMG_FREEZE + + +#define DMG_TRANQ DMG_MORTAR +#define DMG_CONCUSS DMG_SONIC + + + +typedef struct +{ + float fExpire; + float fBaseline; + int x, y; +} DAMAGE_IMAGE; + +// +//----------------------------------------------------- +// +class CHudHealth: public CHudBase +{ +public: + virtual int Init( void ); + virtual int VidInit( void ); + virtual int Draw(float fTime); + virtual void Reset( void ); + int MsgFunc_Health(const char *pszName, int iSize, void *pbuf); + int MsgFunc_Damage(const char *pszName, int iSize, void *pbuf); + int m_iHealth; + int m_HUD_dmg_bio; + int m_HUD_cross; + float m_fAttackFront, m_fAttackRear, m_fAttackLeft, m_fAttackRight; + void GetPainColor( int &r, int &g, int &b ); + float m_fFade; + +private: + HSPRITE m_hSprite; + HSPRITE m_hDamage; + + DAMAGE_IMAGE m_dmg[NUM_DMG_TYPES]; + int m_bitsDamage; + int DrawPain(float fTime); + int DrawDamage(float fTime); + void CalcDamageDirection(vec3_t vecFrom); + void UpdateTiles(float fTime, long bits); +}; diff --git a/cl_dll/hl/hl_baseentity.cpp b/cl_dll/hl/hl_baseentity.cpp new file mode 100644 index 0000000..7b4a0b9 --- /dev/null +++ b/cl_dll/hl/hl_baseentity.cpp @@ -0,0 +1,348 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +/* +========================== +This file contains "stubs" of class member implementations so that we can predict certain + weapons client side. From time to time you might find that you need to implement part of the + these functions. If so, cut it from here, paste it in hl_weapons.cpp or somewhere else and + add in the functionality you need. +========================== +*/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "nodes.h" +#include "soundent.h" +#include "skill.h" + +// Globals used by game logic +const Vector g_vecZero = Vector( 0, 0, 0 ); +int gmsgWeapPickup = 0; +enginefuncs_t g_engfuncs; +globalvars_t *gpGlobals; + +ItemInfo CBasePlayerItem::ItemInfoArray[MAX_WEAPONS]; + +void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volume, float attenuation, int flags, int pitch) { } + +// CBaseEntity Stubs +int CBaseEntity :: TakeHealth( float flHealth, int bitsDamageType ) { return 1; } +int CBaseEntity :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) { return 1; } +CBaseEntity *CBaseEntity::GetNextTarget( void ) { return NULL; } +int CBaseEntity::Save( CSave &save ) { return 1; } +int CBaseEntity::Restore( CRestore &restore ) { return 1; } +void CBaseEntity::SetObjectCollisionBox( void ) { } +int CBaseEntity :: Intersects( CBaseEntity *pOther ) { return 0; } +void CBaseEntity :: MakeDormant( void ) { } +int CBaseEntity :: IsDormant( void ) { return 0; } +BOOL CBaseEntity :: IsInWorld( void ) { return TRUE; } +int CBaseEntity::ShouldToggle( USE_TYPE useType, BOOL currentState ) { return 0; } +int CBaseEntity :: DamageDecal( int bitsDamageType ) { return -1; } +CBaseEntity * CBaseEntity::Create( char *szName, const Vector &vecOrigin, const Vector &vecAngles, edict_t *pentOwner ) { return NULL; } +void CBaseEntity::SUB_Remove( void ) { } + +// CBaseDelay Stubs +void CBaseDelay :: KeyValue( struct KeyValueData_s * ) { } +int CBaseDelay::Restore( class CRestore & ) { return 1; } +int CBaseDelay::Save( class CSave & ) { return 1; } + +// CBaseAnimating Stubs +int CBaseAnimating::Restore( class CRestore & ) { return 1; } +int CBaseAnimating::Save( class CSave & ) { return 1; } + +// DEBUG Stubs +edict_t *DBG_EntOfVars( const entvars_t *pev ) { return NULL; } +void DBG_AssertFunction(BOOL fExpr, const char* szExpr, const char* szFile, int szLine, const char* szMessage) { } + +// UTIL_* Stubs +void UTIL_PrecacheOther( const char *szClassname ) { } +void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, int amount ) { } +void UTIL_DecalTrace( TraceResult *pTrace, int decalNumber ) { } +void UTIL_GunshotDecalTrace( TraceResult *pTrace, int decalNumber ) { } +void UTIL_MakeVectors( const Vector &vecAngles ) { } +BOOL UTIL_IsValidEntity( edict_t *pent ) { return TRUE; } +void UTIL_SetOrigin( entvars_t *, const Vector &org ) { } +BOOL UTIL_GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) { return TRUE; } +void UTIL_LogPrintf(char *,...) { } +void UTIL_ClientPrintAll( int,char const *,char const *,char const *,char const *,char const *) { } +void ClientPrint( entvars_t *client, int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) { } + +// CBaseToggle Stubs +int CBaseToggle::Restore( class CRestore & ) { return 1; } +int CBaseToggle::Save( class CSave & ) { return 1; } +void CBaseToggle :: KeyValue( struct KeyValueData_s * ) { } + +// CGrenade Stubs +void CGrenade::BounceSound( void ) { } +void CGrenade::Explode( Vector, Vector ) { } +void CGrenade::Explode( TraceResult *, int ) { } +void CGrenade::Killed( entvars_t *, int ) { } +void CGrenade::Spawn( void ) { } +CGrenade * CGrenade:: ShootTimed( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time ){ return 0; } +CGrenade *CGrenade::ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ){ return 0; } +void CGrenade::DetonateUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ){ } + +void UTIL_Remove( CBaseEntity *pEntity ){ } +struct skilldata_t gSkillData; +void UTIL_SetSize( entvars_t *pev, const Vector &vecMin, const Vector &vecMax ){ } +CBaseEntity *UTIL_FindEntityInSphere( CBaseEntity *pStartEntity, const Vector &vecCenter, float flRadius ){ return 0;} + +Vector UTIL_VecToAngles( const Vector &vec ){ return 0; } +CSprite *CSprite::SpriteCreate( const char *pSpriteName, const Vector &origin, BOOL animate ) { return 0; } +void CBeam::PointEntInit( const Vector &start, int endIndex ) { } +CBeam *CBeam::BeamCreate( const char *pSpriteName, int width ) { return NULL; } +void CSprite::Expand( float scaleSpeed, float fadeSpeed ) { } + + +CBaseEntity* CBaseMonster :: CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ) { return NULL; } +void CBaseMonster :: Eat ( float flFullDuration ) { } +BOOL CBaseMonster :: FShouldEat ( void ) { return TRUE; } +void CBaseMonster :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) { } +void CBaseMonster :: BarnacleVictimReleased ( void ) { } +void CBaseMonster :: Listen ( void ) { } +float CBaseMonster :: FLSoundVolume ( CSound *pSound ) { return 0.0; } +BOOL CBaseMonster :: FValidateHintType ( short sHint ) { return FALSE; } +void CBaseMonster :: Look ( int iDistance ) { } +int CBaseMonster :: ISoundMask ( void ) { return 0; } +CSound* CBaseMonster :: PBestSound ( void ) { return NULL; } +CSound* CBaseMonster :: PBestScent ( void ) { return NULL; } +float CBaseAnimating :: StudioFrameAdvance ( float flInterval ) { return 0.0; } +void CBaseMonster :: MonsterThink ( void ) { } +void CBaseMonster :: MonsterUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { } +int CBaseMonster :: IgnoreConditions ( void ) { return 0; } +void CBaseMonster :: RouteClear ( void ) { } +void CBaseMonster :: RouteNew ( void ) { } +BOOL CBaseMonster :: FRouteClear ( void ) { return FALSE; } +BOOL CBaseMonster :: FRefreshRoute ( void ) { return 0; } +BOOL CBaseMonster::MoveToEnemy( Activity movementAct, float waitTime ) { return FALSE; } +BOOL CBaseMonster::MoveToLocation( Activity movementAct, float waitTime, const Vector &goal ) { return FALSE; } +BOOL CBaseMonster::MoveToTarget( Activity movementAct, float waitTime ) { return FALSE; } +BOOL CBaseMonster::MoveToNode( Activity movementAct, float waitTime, const Vector &goal ) { return FALSE; } +int ShouldSimplify( int routeType ) { return TRUE; } +void CBaseMonster :: RouteSimplify( CBaseEntity *pTargetEnt ) { } +BOOL CBaseMonster :: FBecomeProne ( void ) { return TRUE; } +BOOL CBaseMonster :: CheckRangeAttack1 ( float flDot, float flDist ) { return FALSE; } +BOOL CBaseMonster :: CheckRangeAttack2 ( float flDot, float flDist ) { return FALSE; } +BOOL CBaseMonster :: CheckMeleeAttack1 ( float flDot, float flDist ) { return FALSE; } +BOOL CBaseMonster :: CheckMeleeAttack2 ( float flDot, float flDist ) { return FALSE; } +void CBaseMonster :: CheckAttacks ( CBaseEntity *pTarget, float flDist ) { } +BOOL CBaseMonster :: FCanCheckAttacks ( void ) { return FALSE; } +int CBaseMonster :: CheckEnemy ( CBaseEntity *pEnemy ) { return 0; } +void CBaseMonster :: PushEnemy( CBaseEntity *pEnemy, Vector &vecLastKnownPos ) { } +BOOL CBaseMonster :: PopEnemy( ) { return FALSE; } +void CBaseMonster :: SetActivity ( Activity NewActivity ) { } +void CBaseMonster :: SetSequenceByName ( char *szSequence ) { } +int CBaseMonster :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ) { return 0; } +float CBaseMonster :: OpenDoorAndWait( entvars_t *pevDoor ) { return 0.0; } +void CBaseMonster :: AdvanceRoute ( float distance ) { } +int CBaseMonster :: RouteClassify( int iMoveFlag ) { return 0; } +BOOL CBaseMonster :: BuildRoute ( const Vector &vecGoal, int iMoveFlag, CBaseEntity *pTarget ) { return FALSE; } +void CBaseMonster :: InsertWaypoint ( Vector vecLocation, int afMoveFlags ) { } +BOOL CBaseMonster :: FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ) { return FALSE; } +void CBaseMonster :: Move ( float flInterval ) { } +BOOL CBaseMonster:: ShouldAdvanceRoute( float flWaypointDist ) { return FALSE; } +void CBaseMonster::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) { } +void CBaseMonster :: MonsterInit ( void ) { } +void CBaseMonster :: MonsterInitThink ( void ) { } +void CBaseMonster :: StartMonster ( void ) { } +void CBaseMonster :: MovementComplete( void ) { } +int CBaseMonster::TaskIsRunning( void ) { return 0; } +int CBaseMonster::IRelationship ( CBaseEntity *pTarget ) { return 0; } +BOOL CBaseMonster :: FindCover ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ) { return FALSE; } +BOOL CBaseMonster :: BuildNearestRoute ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ) { return FALSE; } +CBaseEntity *CBaseMonster :: BestVisibleEnemy ( void ) { return NULL; } +BOOL CBaseMonster :: FInViewCone ( CBaseEntity *pEntity ) { return FALSE; } +BOOL CBaseMonster :: FInViewCone ( Vector *pOrigin ) { return FALSE; } +BOOL CBaseEntity :: FVisible ( CBaseEntity *pEntity ) { return FALSE; } +BOOL CBaseEntity :: FVisible ( const Vector &vecOrigin ) { return FALSE; } +void CBaseMonster :: MakeIdealYaw( Vector vecTarget ) { } +float CBaseMonster::FlYawDiff ( void ) { return 0.0; } +float CBaseMonster::ChangeYaw ( int yawSpeed ) { return 0; } +float CBaseMonster::VecToYaw ( Vector vecDir ) { return 0.0; } +int CBaseAnimating :: LookupActivity ( int activity ) { return 0; } +int CBaseAnimating :: LookupActivityHeaviest ( int activity ) { return 0; } +void CBaseMonster :: SetEyePosition ( void ) { } +int CBaseAnimating :: LookupSequence ( const char *label ) { return 0; } +void CBaseAnimating :: ResetSequenceInfo ( ) { } +BOOL CBaseAnimating :: GetSequenceFlags( ) { return FALSE; } +void CBaseAnimating :: DispatchAnimEvents ( float flInterval ) { } +void CBaseMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) { } +float CBaseAnimating :: SetBoneController ( int iController, float flValue ) { return 0.0; } +void CBaseAnimating :: InitBoneControllers ( void ) { } +float CBaseAnimating :: SetBlending ( int iBlender, float flValue ) { return 0; } +void CBaseAnimating :: GetBonePosition ( int iBone, Vector &origin, Vector &angles ) { } +void CBaseAnimating :: GetAttachment ( int iAttachment, Vector &origin, Vector &angles ) { } +int CBaseAnimating :: FindTransition( int iEndingSequence, int iGoalSequence, int *piDir ) { return -1; } +void CBaseAnimating :: GetAutomovement( Vector &origin, Vector &angles, float flInterval ) { } +void CBaseAnimating :: SetBodygroup( int iGroup, int iValue ) { } +int CBaseAnimating :: GetBodygroup( int iGroup ) { return 0; } +Vector CBaseMonster :: GetGunPosition( void ) { return g_vecZero; } +void CBaseEntity::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) { } +void CBaseEntity::FireBullets(ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq, int iDamage, entvars_t *pevAttacker ) { } +void CBaseEntity :: TraceBleed( float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) { } +void CBaseMonster :: MakeDamageBloodDecal ( int cCount, float flNoise, TraceResult *ptr, const Vector &vecDir ) { } +BOOL CBaseMonster :: FGetNodeRoute ( Vector vecDest ) { return TRUE; } +int CBaseMonster :: FindHintNode ( void ) { return NO_NODE; } +void CBaseMonster::ReportAIState( void ) { } +void CBaseMonster :: KeyValue( KeyValueData *pkvd ) { } +BOOL CBaseMonster :: FCheckAITrigger ( void ) { return FALSE; } +int CBaseMonster :: CanPlaySequence( BOOL fDisregardMonsterState, int interruptLevel ) { return FALSE; } +BOOL CBaseMonster :: FindLateralCover ( const Vector &vecThreat, const Vector &vecViewOffset ) { return FALSE; } +Vector CBaseMonster :: ShootAtEnemy( const Vector &shootOrigin ) { return g_vecZero; } +BOOL CBaseMonster :: FacingIdeal( void ) { return FALSE; } +BOOL CBaseMonster :: FCanActiveIdle ( void ) { return FALSE; } +void CBaseMonster::PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ) { } +void CBaseMonster::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ) { } +void CBaseMonster::SentenceStop( void ) { } +void CBaseMonster::CorpseFallThink( void ) { } +void CBaseMonster :: MonsterInitDead( void ) { } +BOOL CBaseMonster :: BBoxFlat ( void ) { return TRUE; } +BOOL CBaseMonster :: GetEnemy ( void ) { return FALSE; } +void CBaseMonster :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) { } +CBaseEntity* CBaseMonster :: DropItem ( char *pszItemName, const Vector &vecPos, const Vector &vecAng ) { return NULL; } +BOOL CBaseMonster :: ShouldFadeOnDeath( void ) { return FALSE; } +void CBaseMonster :: RadiusDamage(entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) { } +void CBaseMonster :: RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) { } +void CBaseMonster::FadeMonster( void ) { } +void CBaseMonster :: GibMonster( void ) { } +BOOL CBaseMonster :: HasHumanGibs( void ) { return FALSE; } +BOOL CBaseMonster :: HasAlienGibs( void ) { return FALSE; } +Activity CBaseMonster :: GetDeathActivity ( void ) { return ACT_DIE_HEADSHOT; } +MONSTERSTATE CBaseMonster :: GetIdealState ( void ) { return MONSTERSTATE_ALERT; } +Schedule_t* CBaseMonster :: GetScheduleOfType ( int Type ) { return NULL; } +Schedule_t *CBaseMonster :: GetSchedule ( void ) { return NULL; } +void CBaseMonster :: RunTask ( Task_t *pTask ) { } +void CBaseMonster :: StartTask ( Task_t *pTask ) { } +Schedule_t *CBaseMonster::ScheduleFromName( const char *pName ) { return NULL;} +void CBaseMonster::BecomeDead( void ) {} +void CBaseMonster :: RunAI ( void ) {} +void CBaseMonster :: Killed( entvars_t *pevAttacker, int iGib ) {} +int CBaseMonster :: TakeHealth (float flHealth, int bitsDamageType) { return 0; } +int CBaseMonster :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { return 0; } +int CBaseMonster::Restore( class CRestore & ) { return 1; } +int CBaseMonster::Save( class CSave & ) { return 1; } + +int TrainSpeed(int iSpeed, int iMax) { return 0; } +void CBasePlayer :: DeathSound( void ) { } +int CBasePlayer :: TakeHealth( float flHealth, int bitsDamageType ) { return 0; } +void CBasePlayer :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) { } +int CBasePlayer :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { return 0; } +void CBasePlayer::PackDeadPlayerItems( void ) { } +void CBasePlayer::RemoveAllItems( BOOL removeSuit ) { } +void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) { } +void CBasePlayer::WaterMove() { } +BOOL CBasePlayer::IsOnLadder( void ) { return FALSE; } +void CBasePlayer::PlayerDeathThink(void) { } +void CBasePlayer::StartDeathCam( void ) { } +void CBasePlayer::StartObserver( Vector vecPosition, Vector vecViewAngle ) { } +void CBasePlayer::PlayerUse ( void ) { } +void CBasePlayer::Jump() { } +void CBasePlayer::Duck( ) { } +int CBasePlayer::Classify ( void ) { return 0; } +void CBasePlayer::PreThink(void) { } +void CBasePlayer::CheckTimeBasedDamage() { } +void CBasePlayer :: UpdateGeigerCounter( void ) { } +void CBasePlayer::CheckSuitUpdate() { } +void CBasePlayer::SetSuitUpdate(char *name, int fgroup, int iNoRepeatTime) { } +void CBasePlayer :: UpdatePlayerSound ( void ) { } +void CBasePlayer::PostThink() { } +void CBasePlayer :: Precache( void ) { } +int CBasePlayer::Save( CSave &save ) { return 0; } +void CBasePlayer::RenewItems(void) { } +int CBasePlayer::Restore( CRestore &restore ) { return 0; } +void CBasePlayer::SelectNextItem( int iItem ) { } +BOOL CBasePlayer::HasWeapons( void ) { return FALSE; } +void CBasePlayer::SelectPrevItem( int iItem ) { } +CBaseEntity *FindEntityForward( CBaseEntity *pMe ) { return NULL; } +BOOL CBasePlayer :: FlashlightIsOn( void ) { return FALSE; } +void CBasePlayer :: FlashlightTurnOn( void ) { } +void CBasePlayer :: FlashlightTurnOff( void ) { } +void CBasePlayer :: ForceClientDllUpdate( void ) { } +void CBasePlayer::ImpulseCommands( ) { } +void CBasePlayer::CheatImpulseCommands( int iImpulse ) { } +int CBasePlayer::AddPlayerItem( CBasePlayerItem *pItem ) { return FALSE; } +int CBasePlayer::RemovePlayerItem( CBasePlayerItem *pItem ) { return FALSE; } +void CBasePlayer::ItemPreFrame() { } +void CBasePlayer::ItemPostFrame() { } +int CBasePlayer::AmmoInventory( int iAmmoIndex ) { return -1; } +int CBasePlayer::GetAmmoIndex(const char *psz) { return -1; } +void CBasePlayer::SendAmmoUpdate(void) { } +void CBasePlayer :: UpdateClientData( void ) { } +BOOL CBasePlayer :: FBecomeProne ( void ) { return TRUE; } +void CBasePlayer :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) { } +void CBasePlayer :: BarnacleVictimReleased ( void ) { } +int CBasePlayer :: Illumination( void ) { return 0; } +void CBasePlayer :: EnableControl(BOOL fControl) { } +Vector CBasePlayer :: GetAutoaimVector( float flDelta ) { return g_vecZero; } +Vector CBasePlayer :: AutoaimDeflection( Vector &vecSrc, float flDist, float flDelta ) { return g_vecZero; } +void CBasePlayer :: ResetAutoaim( ) { } +void CBasePlayer :: SetCustomDecalFrames( int nFrames ) { } +int CBasePlayer :: GetCustomDecalFrames( void ) { return -1; } +void CBasePlayer::DropPlayerItem ( char *pszItemName ) { } +BOOL CBasePlayer::HasPlayerItem( CBasePlayerItem *pCheckItem ) { return FALSE; } +BOOL CBasePlayer :: SwitchWeapon( CBasePlayerItem *pWeapon ) { return FALSE; } +Vector CBasePlayer :: GetGunPosition( void ) { return g_vecZero; } +const char *CBasePlayer::TeamID( void ) { return ""; } +int CBasePlayer :: GiveAmmo( int iCount, char *szName, int iMax ) { return 0; } +void CBasePlayer::AddPoints( int score, BOOL bAllowNegativeScore ) { } +void CBasePlayer::AddPointsToTeam( int score, BOOL bAllowNegativeScore ) { } + +void ClearMultiDamage(void) { } +void ApplyMultiDamage(entvars_t *pevInflictor, entvars_t *pevAttacker ) { } +void AddMultiDamage( entvars_t *pevInflictor, CBaseEntity *pEntity, float flDamage, int bitsDamageType) { } +void SpawnBlood(Vector vecSpot, int bloodColor, float flDamage) { } +int DamageDecal( CBaseEntity *pEntity, int bitsDamageType ) { return 0; } +void DecalGunshot( TraceResult *pTrace, int iBulletType ) { } +void EjectBrass ( const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int model, int soundtype ) { } +void AddAmmoNameToAmmoRegistry( const char *szAmmoname ) { } +int CBasePlayerItem::Restore( class CRestore & ) { return 1; } +int CBasePlayerItem::Save( class CSave & ) { return 1; } +int CBasePlayerWeapon::Restore( class CRestore & ) { return 1; } +int CBasePlayerWeapon::Save( class CSave & ) { return 1; } +float CBasePlayerWeapon::GetNextAttackDelay( float flTime ) { return flTime; } +void CBasePlayerItem :: SetObjectCollisionBox( void ) { } +void CBasePlayerItem :: FallInit( void ) { } +void CBasePlayerItem::FallThink ( void ) { } +void CBasePlayerItem::Materialize( void ) { } +void CBasePlayerItem::AttemptToMaterialize( void ) { } +void CBasePlayerItem :: CheckRespawn ( void ) { } +CBaseEntity* CBasePlayerItem::Respawn( void ) { return NULL; } +void CBasePlayerItem::DefaultTouch( CBaseEntity *pOther ) { } +void CBasePlayerItem::DestroyItem( void ) { } +int CBasePlayerItem::AddToPlayer( CBasePlayer *pPlayer ) { return TRUE; } +void CBasePlayerItem::Drop( void ) { } +void CBasePlayerItem::Kill( void ) { } +void CBasePlayerItem::Holster( int skiplocal ) { } +void CBasePlayerItem::AttachToPlayer ( CBasePlayer *pPlayer ) { } +int CBasePlayerWeapon::AddDuplicate( CBasePlayerItem *pOriginal ) { return 0; } +int CBasePlayerWeapon::AddToPlayer( CBasePlayer *pPlayer ) { return FALSE; } +int CBasePlayerWeapon::UpdateClientData( CBasePlayer *pPlayer ) { return 0; } +BOOL CBasePlayerWeapon :: AddPrimaryAmmo( int iCount, char *szName, int iMaxClip, int iMaxCarry ) { return TRUE; } +BOOL CBasePlayerWeapon :: AddSecondaryAmmo( int iCount, char *szName, int iMax ) { return TRUE; } +BOOL CBasePlayerWeapon :: IsUseable( void ) { return TRUE; } +int CBasePlayerWeapon::PrimaryAmmoIndex( void ) { return -1; } +int CBasePlayerWeapon::SecondaryAmmoIndex( void ) { return -1; } +void CBasePlayerAmmo::Spawn( void ) { } +CBaseEntity* CBasePlayerAmmo::Respawn( void ) { return this; } +void CBasePlayerAmmo::Materialize( void ) { } +void CBasePlayerAmmo :: DefaultTouch( CBaseEntity *pOther ) { } +int CBasePlayerWeapon::ExtractAmmo( CBasePlayerWeapon *pWeapon ) { return 0; } +int CBasePlayerWeapon::ExtractClipAmmo( CBasePlayerWeapon *pWeapon ) { return 0; } +void CBasePlayerWeapon::RetireWeapon( void ) { } +void CSoundEnt::InsertSound ( int iType, const Vector &vecOrigin, int iVolume, float flDuration ) {} +void RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, float flRadius, int iClassIgnore, int bitsDamageType ){} \ No newline at end of file diff --git a/cl_dll/hl/hl_events.cpp b/cl_dll/hl/hl_events.cpp new file mode 100644 index 0000000..7e9c730 --- /dev/null +++ b/cl_dll/hl/hl_events.cpp @@ -0,0 +1,80 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "../hud.h" +#include "../cl_util.h" +#include "event_api.h" + +extern "C" +{ +// HLDM +void EV_FireGlock1( struct event_args_s *args ); +void EV_FireGlock2( struct event_args_s *args ); +void EV_FireShotGunSingle( struct event_args_s *args ); +void EV_FireShotGunDouble( struct event_args_s *args ); +void EV_FireMP5( struct event_args_s *args ); +void EV_FireMP52( struct event_args_s *args ); +void EV_FirePython( struct event_args_s *args ); +void EV_FireGauss( struct event_args_s *args ); +void EV_SpinGauss( struct event_args_s *args ); +void EV_Crowbar( struct event_args_s *args ); +void EV_FireCrossbow( struct event_args_s *args ); +void EV_FireCrossbow2( struct event_args_s *args ); +void EV_FireRpg( struct event_args_s *args ); +void EV_EgonFire( struct event_args_s *args ); +void EV_EgonStop( struct event_args_s *args ); +void EV_HornetGunFire( struct event_args_s *args ); +void EV_TripmineFire( struct event_args_s *args ); +void EV_SnarkFire( struct event_args_s *args ); + + + +void EV_TrainPitchAdjust( struct event_args_s *args ); +} + +/* +====================== +Game_HookEvents + +Associate script file name with callback functions. Callback's must be extern "C" so + the engine doesn't get confused about name mangling stuff. Note that the format is + always the same. Of course, a clever mod team could actually embed parameters, behavior + into the actual .sc files and create a .sc file parser and hook their functionality through + that.. i.e., a scripting system. + +That was what we were going to do, but we ran out of time...oh well. +====================== +*/ +void Game_HookEvents( void ) +{ + gEngfuncs.pfnHookEvent( "events/glock1.sc", EV_FireGlock1 ); + gEngfuncs.pfnHookEvent( "events/glock2.sc", EV_FireGlock2 ); + gEngfuncs.pfnHookEvent( "events/shotgun1.sc", EV_FireShotGunSingle ); + gEngfuncs.pfnHookEvent( "events/shotgun2.sc", EV_FireShotGunDouble ); + gEngfuncs.pfnHookEvent( "events/mp5.sc", EV_FireMP5 ); + gEngfuncs.pfnHookEvent( "events/mp52.sc", EV_FireMP52 ); + gEngfuncs.pfnHookEvent( "events/python.sc", EV_FirePython ); + gEngfuncs.pfnHookEvent( "events/gauss.sc", EV_FireGauss ); + gEngfuncs.pfnHookEvent( "events/gaussspin.sc", EV_SpinGauss ); + gEngfuncs.pfnHookEvent( "events/train.sc", EV_TrainPitchAdjust ); + gEngfuncs.pfnHookEvent( "events/crowbar.sc", EV_Crowbar ); + gEngfuncs.pfnHookEvent( "events/crossbow1.sc", EV_FireCrossbow ); + gEngfuncs.pfnHookEvent( "events/crossbow2.sc", EV_FireCrossbow2 ); + gEngfuncs.pfnHookEvent( "events/rpg.sc", EV_FireRpg ); + gEngfuncs.pfnHookEvent( "events/egon_fire.sc", EV_EgonFire ); + gEngfuncs.pfnHookEvent( "events/egon_stop.sc", EV_EgonStop ); + gEngfuncs.pfnHookEvent( "events/firehornet.sc", EV_HornetGunFire ); + gEngfuncs.pfnHookEvent( "events/tripfire.sc", EV_TripmineFire ); + gEngfuncs.pfnHookEvent( "events/snarkfire.sc", EV_SnarkFire ); +} diff --git a/cl_dll/hl/hl_objects.cpp b/cl_dll/hl/hl_objects.cpp new file mode 100644 index 0000000..e61db88 --- /dev/null +++ b/cl_dll/hl/hl_objects.cpp @@ -0,0 +1,90 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "../hud.h" +#include "../cl_util.h" +#include "../demo.h" + +#include "demo_api.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" + +#include "pm_defs.h" +#include "event_api.h" +#include "entity_types.h" +#include "r_efx.h" + +extern BEAM *pBeam; +extern BEAM *pBeam2; +void HUD_GetLastOrg( float *org ); + +void UpdateBeams ( void ) +{ + vec3_t forward, vecSrc, vecEnd, origin, angles, right, up; + vec3_t view_ofs; + pmtrace_t tr; + cl_entity_t *pthisplayer = gEngfuncs.GetLocalPlayer(); + int idx = pthisplayer->index; + + // Get our exact viewangles from engine + gEngfuncs.GetViewAngles( (float *)angles ); + + // Determine our last predicted origin + HUD_GetLastOrg( (float *)&origin ); + + AngleVectors( angles, forward, right, up ); + + VectorCopy( origin, vecSrc ); + + VectorMA( vecSrc, 2048, forward, vecEnd ); + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( idx - 1 ); + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( vecSrc, vecEnd, PM_STUDIO_BOX, -1, &tr ); + + gEngfuncs.pEventAPI->EV_PopPMStates(); + + if ( pBeam ) + { + pBeam->target = tr.endpos; + pBeam->die = gEngfuncs.GetClientTime() + 0.1; // We keep it alive just a little bit forward in the future, just in case. + } + + if ( pBeam2 ) + { + pBeam2->target = tr.endpos; + pBeam2->die = gEngfuncs.GetClientTime() + 0.1; // We keep it alive just a little bit forward in the future, just in case. + } +} + +/* +===================== +Game_AddObjects + +Add game specific, client-side objects here +===================== +*/ +void Game_AddObjects( void ) +{ + if ( pBeam && pBeam2 ) + UpdateBeams(); +} diff --git a/cl_dll/hl/hl_weapons.cpp b/cl_dll/hl/hl_weapons.cpp new file mode 100644 index 0000000..f6a5044 --- /dev/null +++ b/cl_dll/hl/hl_weapons.cpp @@ -0,0 +1,1081 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" + +#include "usercmd.h" +#include "entity_state.h" +#include "demo_api.h" +#include "pm_defs.h" +#include "event_api.h" +#include "r_efx.h" + +#include "../hud_iface.h" +#include "../com_weapons.h" +#include "../demo.h" + +extern globalvars_t *gpGlobals; +extern int g_iUser1; + +// Pool of client side entities/entvars_t +static entvars_t ev[ 32 ]; +static int num_ents = 0; + +// The entity we'll use to represent the local client +static CBasePlayer player; + +// Local version of game .dll global variables ( time, etc. ) +static globalvars_t Globals; + +static CBasePlayerWeapon *g_pWpns[ 32 ]; + +float g_flApplyVel = 0.0; +int g_irunninggausspred = 0; + +vec3_t previousorigin; + +// HLDM Weapon placeholder entities. +CGlock g_Glock; +CCrowbar g_Crowbar; +CPython g_Python; +CMP5 g_Mp5; +CCrossbow g_Crossbow; +CShotgun g_Shotgun; +CRpg g_Rpg; +CGauss g_Gauss; +CEgon g_Egon; +CHgun g_HGun; +CHandGrenade g_HandGren; +CSatchel g_Satchel; +CTripmine g_Tripmine; +CSqueak g_Snark; + + +/* +====================== +AlertMessage + +Print debug messages to console +====================== +*/ +void AlertMessage( ALERT_TYPE atype, char *szFmt, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start (argptr, szFmt); + vsprintf (string, szFmt,argptr); + va_end (argptr); + + gEngfuncs.Con_Printf( "cl: " ); + gEngfuncs.Con_Printf( string ); +} + +//Returns if it's multiplayer. +//Mostly used by the client side weapons. +bool bIsMultiplayer ( void ) +{ + return gEngfuncs.GetMaxClients() == 1 ? 0 : 1; +} +//Just loads a v_ model. +void LoadVModel ( char *szViewModel, CBasePlayer *m_pPlayer ) +{ + gEngfuncs.CL_LoadModel( szViewModel, &m_pPlayer->pev->viewmodel ); +} + +/* +===================== +HUD_PrepEntity + +Links the raw entity to an entvars_s holder. If a player is passed in as the owner, then +we set up the m_pPlayer field. +===================== +*/ +void HUD_PrepEntity( CBaseEntity *pEntity, CBasePlayer *pWeaponOwner ) +{ + memset( &ev[ num_ents ], 0, sizeof( entvars_t ) ); + pEntity->pev = &ev[ num_ents++ ]; + + pEntity->Precache(); + pEntity->Spawn(); + + if ( pWeaponOwner ) + { + ItemInfo info; + + ((CBasePlayerWeapon *)pEntity)->m_pPlayer = pWeaponOwner; + + ((CBasePlayerWeapon *)pEntity)->GetItemInfo( &info ); + + g_pWpns[ info.iId ] = (CBasePlayerWeapon *)pEntity; + } +} + +/* +===================== +CBaseEntity :: Killed + +If weapons code "kills" an entity, just set its effects to EF_NODRAW +===================== +*/ +void CBaseEntity :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->effects |= EF_NODRAW; +} + +/* +===================== +CBasePlayerWeapon :: DefaultReload +===================== +*/ +BOOL CBasePlayerWeapon :: DefaultReload( int iClipSize, int iAnim, float fDelay, int body ) +{ + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + return FALSE; + + int j = min(iClipSize - m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); + + if (j == 0) + return FALSE; + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + fDelay; + + //!!UNDONE -- reload sound goes here !!! + SendWeaponAnim( iAnim, UseDecrement(), body ); + + m_fInReload = TRUE; + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 3; + return TRUE; +} + +/* +===================== +CBasePlayerWeapon :: CanDeploy +===================== +*/ +BOOL CBasePlayerWeapon :: CanDeploy( void ) +{ + BOOL bHasAmmo = 0; + + if ( !pszAmmo1() ) + { + // this weapon doesn't use ammo, can always deploy. + return TRUE; + } + + if ( pszAmmo1() ) + { + bHasAmmo |= (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] != 0); + } + if ( pszAmmo2() ) + { + bHasAmmo |= (m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] != 0); + } + if (m_iClip > 0) + { + bHasAmmo |= 1; + } + if (!bHasAmmo) + { + return FALSE; + } + + return TRUE; +} + +/* +===================== +CBasePlayerWeapon :: DefaultDeploy + +===================== +*/ +BOOL CBasePlayerWeapon :: DefaultDeploy( char *szViewModel, char *szWeaponModel, int iAnim, char *szAnimExt, int skiplocal, int body ) +{ + if ( !CanDeploy() ) + return FALSE; + + gEngfuncs.CL_LoadModel( szViewModel, &m_pPlayer->pev->viewmodel ); + + SendWeaponAnim( iAnim, skiplocal, body ); + + g_irunninggausspred = false; + m_pPlayer->m_flNextAttack = 0.5; + m_flTimeWeaponIdle = 1.0; + return TRUE; +} + +/* +===================== +CBasePlayerWeapon :: PlayEmptySound + +===================== +*/ +BOOL CBasePlayerWeapon :: PlayEmptySound( void ) +{ + if (m_iPlayEmptySound) + { + HUD_PlaySound( "weapons/357_cock1.wav", 0.8 ); + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +/* +===================== +CBasePlayerWeapon :: ResetEmptySound + +===================== +*/ +void CBasePlayerWeapon :: ResetEmptySound( void ) +{ + m_iPlayEmptySound = 1; +} + +/* +===================== +CBasePlayerWeapon::Holster + +Put away weapon +===================== +*/ +void CBasePlayerWeapon::Holster( int skiplocal /* = 0 */ ) +{ + m_fInReload = FALSE; // cancel any reload in progress. + g_irunninggausspred = false; + m_pPlayer->pev->viewmodel = 0; +} + +/* +===================== +CBasePlayerWeapon::SendWeaponAnim + +Animate weapon model +===================== +*/ +void CBasePlayerWeapon::SendWeaponAnim( int iAnim, int skiplocal, int body ) +{ + m_pPlayer->pev->weaponanim = iAnim; + + HUD_SendWeaponAnim( iAnim, body, 0 ); +} + +/* +===================== +CBaseEntity::FireBulletsPlayer + +Only produces random numbers to match the server ones. +===================== +*/ +Vector CBaseEntity::FireBulletsPlayer ( ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq, int iDamage, entvars_t *pevAttacker, int shared_rand ) +{ + float x, y, z; + + for ( ULONG iShot = 1; iShot <= cShots; iShot++ ) + { + if ( pevAttacker == NULL ) + { + // get circular gaussian spread + do { + x = RANDOM_FLOAT(-0.5, 0.5) + RANDOM_FLOAT(-0.5, 0.5); + y = RANDOM_FLOAT(-0.5, 0.5) + RANDOM_FLOAT(-0.5, 0.5); + z = x*x+y*y; + } while (z > 1); + } + else + { + //Use player's random seed. + // get circular gaussian spread + x = UTIL_SharedRandomFloat( shared_rand + iShot, -0.5, 0.5 ) + UTIL_SharedRandomFloat( shared_rand + ( 1 + iShot ) , -0.5, 0.5 ); + y = UTIL_SharedRandomFloat( shared_rand + ( 2 + iShot ), -0.5, 0.5 ) + UTIL_SharedRandomFloat( shared_rand + ( 3 + iShot ), -0.5, 0.5 ); + z = x * x + y * y; + } + + } + + return Vector ( x * vecSpread.x, y * vecSpread.y, 0.0 ); +} + +/* +===================== +CBasePlayerWeapon::ItemPostFrame + +Handles weapon firing, reloading, etc. +===================== +*/ +void CBasePlayerWeapon::ItemPostFrame( void ) +{ + if ((m_fInReload) && (m_pPlayer->m_flNextAttack <= 0.0)) + { +#if 0 // FIXME, need ammo on client to make this work right + // complete the reload. + int j = min( iMaxClip() - m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); + + // Add them to the clip + m_iClip += j; + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= j; +#else + m_iClip += 10; +#endif + m_fInReload = FALSE; + } + + if ((m_pPlayer->pev->button & IN_ATTACK2) && (m_flNextSecondaryAttack <= 0.0)) + { + if ( pszAmmo2() && !m_pPlayer->m_rgAmmo[SecondaryAmmoIndex()] ) + { + m_fFireOnEmpty = TRUE; + } + + SecondaryAttack(); + m_pPlayer->pev->button &= ~IN_ATTACK2; + } + else if ((m_pPlayer->pev->button & IN_ATTACK) && (m_flNextPrimaryAttack <= 0.0)) + { + if ( (m_iClip == 0 && pszAmmo1()) || (iMaxClip() == -1 && !m_pPlayer->m_rgAmmo[PrimaryAmmoIndex()] ) ) + { + m_fFireOnEmpty = TRUE; + } + + PrimaryAttack(); + } + else if ( m_pPlayer->pev->button & IN_RELOAD && iMaxClip() != WEAPON_NOCLIP && !m_fInReload ) + { + // reload when reload is pressed, or if no buttons are down and weapon is empty. + Reload(); + } + else if ( !(m_pPlayer->pev->button & (IN_ATTACK|IN_ATTACK2) ) ) + { + // no fire buttons down + + m_fFireOnEmpty = FALSE; + + // weapon is useable. Reload if empty and weapon has waited as long as it has to after firing + if ( m_iClip == 0 && !(iFlags() & ITEM_FLAG_NOAUTORELOAD) && m_flNextPrimaryAttack < 0.0 ) + { + Reload(); + return; + } + + WeaponIdle( ); + return; + } + + // catch all + if ( ShouldWeaponIdle() ) + { + WeaponIdle(); + } +} + +/* +===================== +CBasePlayer::SelectItem + + Switch weapons +===================== +*/ +void CBasePlayer::SelectItem(const char *pstr) +{ + if (!pstr) + return; + + CBasePlayerItem *pItem = NULL; + + if (!pItem) + return; + + + if (pItem == m_pActiveItem) + return; + + if (m_pActiveItem) + m_pActiveItem->Holster( ); + + m_pLastItem = m_pActiveItem; + m_pActiveItem = pItem; + + if (m_pActiveItem) + { + m_pActiveItem->Deploy( ); + } +} + +/* +===================== +CBasePlayer::SelectLastItem + +===================== +*/ +void CBasePlayer::SelectLastItem(void) +{ + if (!m_pLastItem) + { + return; + } + + if ( m_pActiveItem && !m_pActiveItem->CanHolster() ) + { + return; + } + + if (m_pActiveItem) + m_pActiveItem->Holster( ); + + CBasePlayerItem *pTemp = m_pActiveItem; + m_pActiveItem = m_pLastItem; + m_pLastItem = pTemp; + m_pActiveItem->Deploy( ); +} + +/* +===================== +CBasePlayer::Killed + +===================== +*/ +void CBasePlayer::Killed( entvars_t *pevAttacker, int iGib ) +{ + // Holster weapon immediately, to allow it to cleanup + if ( m_pActiveItem ) + m_pActiveItem->Holster( ); + + g_irunninggausspred = false; +} + +/* +===================== +CBasePlayer::Spawn + +===================== +*/ +void CBasePlayer::Spawn( void ) +{ + if (m_pActiveItem) + m_pActiveItem->Deploy( ); + + g_irunninggausspred = false; +} + +/* +===================== +UTIL_TraceLine + +Don't actually trace, but act like the trace didn't hit anything. +===================== +*/ +void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, edict_t *pentIgnore, TraceResult *ptr ) +{ + memset( ptr, 0, sizeof( *ptr ) ); + ptr->flFraction = 1.0; +} + +/* +===================== +UTIL_ParticleBox + +For debugging, draw a box around a player made out of particles +===================== +*/ +void UTIL_ParticleBox( CBasePlayer *player, float *mins, float *maxs, float life, unsigned char r, unsigned char g, unsigned char b ) +{ + int i; + vec3_t mmin, mmax; + + for ( i = 0; i < 3; i++ ) + { + mmin[ i ] = player->pev->origin[ i ] + mins[ i ]; + mmax[ i ] = player->pev->origin[ i ] + maxs[ i ]; + } + + gEngfuncs.pEfxAPI->R_ParticleBox( (float *)&mmin, (float *)&mmax, 5.0, 0, 255, 0 ); +} + +/* +===================== +UTIL_ParticleBoxes + +For debugging, draw boxes for other collidable players +===================== +*/ +void UTIL_ParticleBoxes( void ) +{ + int idx; + physent_t *pe; + cl_entity_t *player; + vec3_t mins, maxs; + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + player = gEngfuncs.GetLocalPlayer(); + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( player->index - 1 ); + + for ( idx = 1; idx < 100; idx++ ) + { + pe = gEngfuncs.pEventAPI->EV_GetPhysent( idx ); + if ( !pe ) + break; + + if ( pe->info >= 1 && pe->info <= gEngfuncs.GetMaxClients() ) + { + mins = pe->origin + pe->mins; + maxs = pe->origin + pe->maxs; + + gEngfuncs.pEfxAPI->R_ParticleBox( (float *)&mins, (float *)&maxs, 0, 0, 255, 2.0 ); + } + } + + gEngfuncs.pEventAPI->EV_PopPMStates(); +} + +/* +===================== +UTIL_ParticleLine + +For debugging, draw a line made out of particles +===================== +*/ +void UTIL_ParticleLine( CBasePlayer *player, float *start, float *end, float life, unsigned char r, unsigned char g, unsigned char b ) +{ + gEngfuncs.pEfxAPI->R_ParticleLine( start, end, r, g, b, life ); +} + +/* +===================== +HUD_InitClientWeapons + +Set up weapons, player and functions needed to run weapons code client-side. +===================== +*/ +void HUD_InitClientWeapons( void ) +{ + static int initialized = 0; + if ( initialized ) + return; + + initialized = 1; + + // Set up pointer ( dummy object ) + gpGlobals = &Globals; + + // Fill in current time ( probably not needed ) + gpGlobals->time = gEngfuncs.GetClientTime(); + + // Fake functions + g_engfuncs.pfnPrecacheModel = stub_PrecacheModel; + g_engfuncs.pfnPrecacheSound = stub_PrecacheSound; + g_engfuncs.pfnPrecacheEvent = stub_PrecacheEvent; + g_engfuncs.pfnNameForFunction = stub_NameForFunction; + g_engfuncs.pfnSetModel = stub_SetModel; + g_engfuncs.pfnSetClientMaxspeed = HUD_SetMaxSpeed; + + // Handled locally + g_engfuncs.pfnPlaybackEvent = HUD_PlaybackEvent; + g_engfuncs.pfnAlertMessage = AlertMessage; + + // Pass through to engine + g_engfuncs.pfnPrecacheEvent = gEngfuncs.pfnPrecacheEvent; + g_engfuncs.pfnRandomFloat = gEngfuncs.pfnRandomFloat; + g_engfuncs.pfnRandomLong = gEngfuncs.pfnRandomLong; + + // Allocate a slot for the local player + HUD_PrepEntity( &player , NULL ); + + // Allocate slot(s) for each weapon that we are going to be predicting + HUD_PrepEntity( &g_Glock , &player ); + HUD_PrepEntity( &g_Crowbar , &player ); + HUD_PrepEntity( &g_Python , &player ); + HUD_PrepEntity( &g_Mp5 , &player ); + HUD_PrepEntity( &g_Crossbow , &player ); + HUD_PrepEntity( &g_Shotgun , &player ); + HUD_PrepEntity( &g_Rpg , &player ); + HUD_PrepEntity( &g_Gauss , &player ); + HUD_PrepEntity( &g_Egon , &player ); + HUD_PrepEntity( &g_HGun , &player ); + HUD_PrepEntity( &g_HandGren , &player ); + HUD_PrepEntity( &g_Satchel , &player ); + HUD_PrepEntity( &g_Tripmine , &player ); + HUD_PrepEntity( &g_Snark , &player ); +} + +/* +===================== +HUD_GetLastOrg + +Retruns the last position that we stored for egon beam endpoint. +===================== +*/ +void HUD_GetLastOrg( float *org ) +{ + int i; + + // Return last origin + for ( i = 0; i < 3; i++ ) + { + org[i] = previousorigin[i]; + } +} + +/* +===================== +HUD_SetLastOrg + +Remember our exact predicted origin so we can draw the egon to the right position. +===================== +*/ +void HUD_SetLastOrg( void ) +{ + int i; + + // Offset final origin by view_offset + for ( i = 0; i < 3; i++ ) + { + previousorigin[i] = g_finalstate->playerstate.origin[i] + g_finalstate->client.view_ofs[ i ]; + } +} + +/* +===================== +HUD_WeaponsPostThink + +Run Weapon firing code on client +===================== +*/ +void HUD_WeaponsPostThink( local_state_s *from, local_state_s *to, usercmd_t *cmd, double time, unsigned int random_seed ) +{ + int i; + int buttonsChanged; + CBasePlayerWeapon *pWeapon = NULL; + CBasePlayerWeapon *pCurrent; + weapon_data_t nulldata, *pfrom, *pto; + static int lasthealth; + + memset( &nulldata, 0, sizeof( nulldata ) ); + + HUD_InitClientWeapons(); + + // Get current clock + gpGlobals->time = time; + + // Fill in data based on selected weapon + // FIXME, make this a method in each weapon? where you pass in an entity_state_t *? + switch ( from->client.m_iId ) + { + case WEAPON_CROWBAR: + pWeapon = &g_Crowbar; + break; + + case WEAPON_GLOCK: + pWeapon = &g_Glock; + break; + + case WEAPON_PYTHON: + pWeapon = &g_Python; + break; + + case WEAPON_MP5: + pWeapon = &g_Mp5; + break; + + case WEAPON_CROSSBOW: + pWeapon = &g_Crossbow; + break; + + case WEAPON_SHOTGUN: + pWeapon = &g_Shotgun; + break; + + case WEAPON_RPG: + pWeapon = &g_Rpg; + break; + + case WEAPON_GAUSS: + pWeapon = &g_Gauss; + break; + + case WEAPON_EGON: + pWeapon = &g_Egon; + break; + + case WEAPON_HORNETGUN: + pWeapon = &g_HGun; + break; + + case WEAPON_HANDGRENADE: + pWeapon = &g_HandGren; + break; + + case WEAPON_SATCHEL: + pWeapon = &g_Satchel; + break; + + case WEAPON_TRIPMINE: + pWeapon = &g_Tripmine; + break; + + case WEAPON_SNARK: + pWeapon = &g_Snark; + break; + } + + // Store pointer to our destination entity_state_t so we can get our origin, etc. from it + // for setting up events on the client + g_finalstate = to; + + // If we are running events/etc. go ahead and see if we + // managed to die between last frame and this one + // If so, run the appropriate player killed or spawn function + if ( g_runfuncs ) + { + if ( to->client.health <= 0 && lasthealth > 0 ) + { + player.Killed( NULL, 0 ); + + } + else if ( to->client.health > 0 && lasthealth <= 0 ) + { + player.Spawn(); + } + + lasthealth = to->client.health; + } + + // We are not predicting the current weapon, just bow out here. + if ( !pWeapon ) + return; + + for ( i = 0; i < 32; i++ ) + { + pCurrent = g_pWpns[ i ]; + if ( !pCurrent ) + { + continue; + } + + pfrom = &from->weapondata[ i ]; + + pCurrent->m_fInReload = pfrom->m_fInReload; + pCurrent->m_fInSpecialReload = pfrom->m_fInSpecialReload; +// pCurrent->m_flPumpTime = pfrom->m_flPumpTime; + pCurrent->m_iClip = pfrom->m_iClip; + pCurrent->m_flNextPrimaryAttack = pfrom->m_flNextPrimaryAttack; + pCurrent->m_flNextSecondaryAttack = pfrom->m_flNextSecondaryAttack; + pCurrent->m_flTimeWeaponIdle = pfrom->m_flTimeWeaponIdle; + pCurrent->pev->fuser1 = pfrom->fuser1; + pCurrent->m_flStartThrow = pfrom->fuser2; + pCurrent->m_flReleaseThrow = pfrom->fuser3; + pCurrent->m_chargeReady = pfrom->iuser1; + pCurrent->m_fInAttack = pfrom->iuser2; + pCurrent->m_fireState = pfrom->iuser3; + + pCurrent->m_iSecondaryAmmoType = (int)from->client.vuser3[ 2 ]; + pCurrent->m_iPrimaryAmmoType = (int)from->client.vuser4[ 0 ]; + player.m_rgAmmo[ pCurrent->m_iPrimaryAmmoType ] = (int)from->client.vuser4[ 1 ]; + player.m_rgAmmo[ pCurrent->m_iSecondaryAmmoType ] = (int)from->client.vuser4[ 2 ]; + } + + // For random weapon events, use this seed to seed random # generator + player.random_seed = random_seed; + + // Get old buttons from previous state. + player.m_afButtonLast = from->playerstate.oldbuttons; + + // Which buttsons chave changed + buttonsChanged = (player.m_afButtonLast ^ cmd->buttons); // These buttons have changed this frame + + // Debounced button codes for pressed/released + // The changed ones still down are "pressed" + player.m_afButtonPressed = buttonsChanged & cmd->buttons; + // The ones not down are "released" + player.m_afButtonReleased = buttonsChanged & (~cmd->buttons); + + // Set player variables that weapons code might check/alter + player.pev->button = cmd->buttons; + + player.pev->velocity = from->client.velocity; + player.pev->flags = from->client.flags; + + player.pev->deadflag = from->client.deadflag; + player.pev->waterlevel = from->client.waterlevel; + player.pev->maxspeed = from->client.maxspeed; + player.pev->fov = from->client.fov; + player.pev->weaponanim = from->client.weaponanim; + player.pev->viewmodel = from->client.viewmodel; + player.m_flNextAttack = from->client.m_flNextAttack; + player.m_flNextAmmoBurn = from->client.fuser2; + player.m_flAmmoStartCharge = from->client.fuser3; + + //Stores all our ammo info, so the client side weapons can use them. + player.ammo_9mm = (int)from->client.vuser1[0]; + player.ammo_357 = (int)from->client.vuser1[1]; + player.ammo_argrens = (int)from->client.vuser1[2]; + player.ammo_bolts = (int)from->client.ammo_nails; //is an int anyways... + player.ammo_buckshot = (int)from->client.ammo_shells; + player.ammo_uranium = (int)from->client.ammo_cells; + player.ammo_hornets = (int)from->client.vuser2[0]; + player.ammo_rockets = (int)from->client.ammo_rockets; + + + // Point to current weapon object + if ( from->client.m_iId ) + { + player.m_pActiveItem = g_pWpns[ from->client.m_iId ]; + } + + if ( player.m_pActiveItem->m_iId == WEAPON_RPG ) + { + ( ( CRpg * )player.m_pActiveItem)->m_fSpotActive = (int)from->client.vuser2[ 1 ]; + ( ( CRpg * )player.m_pActiveItem)->m_cActiveRockets = (int)from->client.vuser2[ 2 ]; + } + + // Don't go firing anything if we have died or are spectating + // Or if we don't have a weapon model deployed + if ( ( player.pev->deadflag != ( DEAD_DISCARDBODY + 1 ) ) && + !CL_IsDead() && player.pev->viewmodel && !g_iUser1 ) + { + if ( player.m_flNextAttack <= 0 ) + { + pWeapon->ItemPostFrame(); + } + } + + // Assume that we are not going to switch weapons + to->client.m_iId = from->client.m_iId; + + // Now see if we issued a changeweapon command ( and we're not dead ) + if ( cmd->weaponselect && ( player.pev->deadflag != ( DEAD_DISCARDBODY + 1 ) ) ) + { + // Switched to a different weapon? + if ( from->weapondata[ cmd->weaponselect ].m_iId == cmd->weaponselect ) + { + CBasePlayerWeapon *pNew = g_pWpns[ cmd->weaponselect ]; + if ( pNew && ( pNew != pWeapon ) ) + { + // Put away old weapon + if (player.m_pActiveItem) + player.m_pActiveItem->Holster( ); + + player.m_pLastItem = player.m_pActiveItem; + player.m_pActiveItem = pNew; + + // Deploy new weapon + if (player.m_pActiveItem) + { + player.m_pActiveItem->Deploy( ); + } + + // Update weapon id so we can predict things correctly. + to->client.m_iId = cmd->weaponselect; + } + } + } + + // Copy in results of prediction code + to->client.viewmodel = player.pev->viewmodel; + to->client.fov = player.pev->fov; + to->client.weaponanim = player.pev->weaponanim; + to->client.m_flNextAttack = player.m_flNextAttack; + to->client.fuser2 = player.m_flNextAmmoBurn; + to->client.fuser3 = player.m_flAmmoStartCharge; + to->client.maxspeed = player.pev->maxspeed; + + //HL Weapons + to->client.vuser1[0] = player.ammo_9mm; + to->client.vuser1[1] = player.ammo_357; + to->client.vuser1[2] = player.ammo_argrens; + + to->client.ammo_nails = player.ammo_bolts; + to->client.ammo_shells = player.ammo_buckshot; + to->client.ammo_cells = player.ammo_uranium; + to->client.vuser2[0] = player.ammo_hornets; + to->client.ammo_rockets = player.ammo_rockets; + + if ( player.m_pActiveItem->m_iId == WEAPON_RPG ) + { + from->client.vuser2[ 1 ] = ( ( CRpg * )player.m_pActiveItem)->m_fSpotActive; + from->client.vuser2[ 2 ] = ( ( CRpg * )player.m_pActiveItem)->m_cActiveRockets; + } + + // Make sure that weapon animation matches what the game .dll is telling us + // over the wire ( fixes some animation glitches ) + if ( g_runfuncs && ( HUD_GetWeaponAnim() != to->client.weaponanim ) ) + { + int body = 2; + + //Pop the model to body 0. + if ( pWeapon == &g_Tripmine ) + body = 0; + + //Show laser sight/scope combo + if ( pWeapon == &g_Python && bIsMultiplayer() ) + body = 1; + + // Force a fixed anim down to viewmodel + HUD_SendWeaponAnim( to->client.weaponanim, body, 1 ); + } + + for ( i = 0; i < 32; i++ ) + { + pCurrent = g_pWpns[ i ]; + + pto = &to->weapondata[ i ]; + + if ( !pCurrent ) + { + memset( pto, 0, sizeof( weapon_data_t ) ); + continue; + } + + pto->m_fInReload = pCurrent->m_fInReload; + pto->m_fInSpecialReload = pCurrent->m_fInSpecialReload; +// pto->m_flPumpTime = pCurrent->m_flPumpTime; + pto->m_iClip = pCurrent->m_iClip; + pto->m_flNextPrimaryAttack = pCurrent->m_flNextPrimaryAttack; + pto->m_flNextSecondaryAttack = pCurrent->m_flNextSecondaryAttack; + pto->m_flTimeWeaponIdle = pCurrent->m_flTimeWeaponIdle; + pto->fuser1 = pCurrent->pev->fuser1; + pto->fuser2 = pCurrent->m_flStartThrow; + pto->fuser3 = pCurrent->m_flReleaseThrow; + pto->iuser1 = pCurrent->m_chargeReady; + pto->iuser2 = pCurrent->m_fInAttack; + pto->iuser3 = pCurrent->m_fireState; + + // Decrement weapon counters, server does this at same time ( during post think, after doing everything else ) + pto->m_flNextReload -= cmd->msec / 1000.0; + pto->m_fNextAimBonus -= cmd->msec / 1000.0; + pto->m_flNextPrimaryAttack -= cmd->msec / 1000.0; + pto->m_flNextSecondaryAttack -= cmd->msec / 1000.0; + pto->m_flTimeWeaponIdle -= cmd->msec / 1000.0; + pto->fuser1 -= cmd->msec / 1000.0; + + to->client.vuser3[2] = pCurrent->m_iSecondaryAmmoType; + to->client.vuser4[0] = pCurrent->m_iPrimaryAmmoType; + to->client.vuser4[1] = player.m_rgAmmo[ pCurrent->m_iPrimaryAmmoType ]; + to->client.vuser4[2] = player.m_rgAmmo[ pCurrent->m_iSecondaryAmmoType ]; + +/* if ( pto->m_flPumpTime != -9999 ) + { + pto->m_flPumpTime -= cmd->msec / 1000.0; + if ( pto->m_flPumpTime < -0.001 ) + pto->m_flPumpTime = -0.001; + }*/ + + if ( pto->m_fNextAimBonus < -1.0 ) + { + pto->m_fNextAimBonus = -1.0; + } + + if ( pto->m_flNextPrimaryAttack < -1.0 ) + { + pto->m_flNextPrimaryAttack = -1.0; + } + + if ( pto->m_flNextSecondaryAttack < -0.001 ) + { + pto->m_flNextSecondaryAttack = -0.001; + } + + if ( pto->m_flTimeWeaponIdle < -0.001 ) + { + pto->m_flTimeWeaponIdle = -0.001; + } + + if ( pto->m_flNextReload < -0.001 ) + { + pto->m_flNextReload = -0.001; + } + + if ( pto->fuser1 < -0.001 ) + { + pto->fuser1 = -0.001; + } + } + + // m_flNextAttack is now part of the weapons, but is part of the player instead + to->client.m_flNextAttack -= cmd->msec / 1000.0; + if ( to->client.m_flNextAttack < -0.001 ) + { + to->client.m_flNextAttack = -0.001; + } + + to->client.fuser2 -= cmd->msec / 1000.0; + if ( to->client.fuser2 < -0.001 ) + { + to->client.fuser2 = -0.001; + } + + to->client.fuser3 -= cmd->msec / 1000.0; + if ( to->client.fuser3 < -0.001 ) + { + to->client.fuser3 = -0.001; + } + + // Store off the last position from the predicted state. + HUD_SetLastOrg(); + + // Wipe it so we can't use it after this frame + g_finalstate = NULL; +} + +/* +===================== +HUD_PostRunCmd + +Client calls this during prediction, after it has moved the player and updated any info changed into to-> +time is the current client clock based on prediction +cmd is the command that caused the movement, etc +runfuncs is 1 if this is the first time we've predicted this command. If so, sounds and effects should play, otherwise, they should +be ignored +===================== +*/ +void CL_DLLEXPORT HUD_PostRunCmd( struct local_state_s *from, struct local_state_s *to, struct usercmd_s *cmd, int runfuncs, double time, unsigned int random_seed ) +{ +// RecClPostRunCmd(from, to, cmd, runfuncs, time, random_seed); + + g_runfuncs = runfuncs; + +#if defined( CLIENT_WEAPONS ) + if ( cl_lw && cl_lw->value ) + { + HUD_WeaponsPostThink( from, to, cmd, time, random_seed ); + } + else +#endif + { + to->client.fov = g_lastFOV; + } + + if ( g_irunninggausspred == 1 ) + { + Vector forward; + gEngfuncs.pfnAngleVectors( v_angles, forward, NULL, NULL ); + to->client.velocity = to->client.velocity - forward * g_flApplyVel * 5; + g_irunninggausspred = false; + } + + // All games can use FOV state + g_lastFOV = to->client.fov; +} diff --git a/cl_dll/hud.cpp b/cl_dll/hud.cpp new file mode 100644 index 0000000..d376e0e --- /dev/null +++ b/cl_dll/hud.cpp @@ -0,0 +1,698 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud.cpp +// +// implementation of CHud class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" +#include "hud_servers.h" +#include "vgui_int.h" +#include "vgui_TeamFortressViewport.h" + +#include "demo.h" +#include "demo_api.h" +#include "vgui_ScorePanel.h" + +hud_player_info_t g_PlayerInfoList[MAX_PLAYERS+1]; // player info from the engine +extra_player_info_t g_PlayerExtraInfo[MAX_PLAYERS+1]; // additional player info sent directly to the client dll + +class CHLVoiceStatusHelper : public IVoiceStatusHelper +{ +public: + virtual void GetPlayerTextColor(int entindex, int color[3]) + { + color[0] = color[1] = color[2] = 255; + + if( entindex >= 0 && entindex < sizeof(g_PlayerExtraInfo)/sizeof(g_PlayerExtraInfo[0]) ) + { + int iTeam = g_PlayerExtraInfo[entindex].teamnumber; + + if ( iTeam < 0 ) + { + iTeam = 0; + } + + iTeam = iTeam % iNumberOfTeamColors; + + color[0] = iTeamColors[iTeam][0]; + color[1] = iTeamColors[iTeam][1]; + color[2] = iTeamColors[iTeam][2]; + } + } + + virtual void UpdateCursorState() + { + gViewPort->UpdateCursorState(); + } + + virtual int GetAckIconHeight() + { + return ScreenHeight - gHUD.m_iFontHeight*3 - 6; + } + + virtual bool CanShowSpeakerLabels() + { + if( gViewPort && gViewPort->m_pScoreBoard ) + return !gViewPort->m_pScoreBoard->isVisible(); + else + return false; + } +}; +static CHLVoiceStatusHelper g_VoiceStatusHelper; + + +extern client_sprite_t *GetSpriteList(client_sprite_t *pList, const char *psz, int iRes, int iCount); + +extern cvar_t *sensitivity; +cvar_t *cl_lw = NULL; + +void ShutdownInput (void); + +//DECLARE_MESSAGE(m_Logo, Logo) +int __MsgFunc_Logo(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_Logo(pszName, iSize, pbuf ); +} + +//DECLARE_MESSAGE(m_Logo, Logo) +int __MsgFunc_ResetHUD(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_ResetHUD(pszName, iSize, pbuf ); +} + +int __MsgFunc_InitHUD(const char *pszName, int iSize, void *pbuf) +{ + gHUD.MsgFunc_InitHUD( pszName, iSize, pbuf ); + return 1; +} + +int __MsgFunc_ViewMode(const char *pszName, int iSize, void *pbuf) +{ + gHUD.MsgFunc_ViewMode( pszName, iSize, pbuf ); + return 1; +} + +int __MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_SetFOV( pszName, iSize, pbuf ); +} + +int __MsgFunc_Concuss(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_Concuss( pszName, iSize, pbuf ); +} + +int __MsgFunc_GameMode(const char *pszName, int iSize, void *pbuf ) +{ + return gHUD.MsgFunc_GameMode( pszName, iSize, pbuf ); +} + +// TFFree Command Menu +void __CmdFunc_OpenCommandMenu(void) +{ + if ( gViewPort ) + { + gViewPort->ShowCommandMenu( gViewPort->m_StandardMenu ); + } +} + +// TFC "special" command +void __CmdFunc_InputPlayerSpecial(void) +{ + if ( gViewPort ) + { + gViewPort->InputPlayerSpecial(); + } +} + +void __CmdFunc_CloseCommandMenu(void) +{ + if ( gViewPort ) + { + gViewPort->InputSignalHideCommandMenu(); + } +} + +void __CmdFunc_ForceCloseCommandMenu( void ) +{ + if ( gViewPort ) + { + gViewPort->HideCommandMenu(); + } +} + +void __CmdFunc_ToggleServerBrowser( void ) +{ + if ( gViewPort ) + { + gViewPort->ToggleServerBrowser(); + } +} + +// TFFree Command Menu Message Handlers +int __MsgFunc_ValClass(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_ValClass( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_TeamNames(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_TeamNames( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_Feign(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_Feign( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_Detpack(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_Detpack( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_VGUIMenu(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_VGUIMenu( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_MOTD(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_MOTD( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_BuildSt(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_BuildSt( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_RandomPC(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_RandomPC( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_ServerName(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_ServerName( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_ScoreInfo(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_ScoreInfo( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_TeamScore(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_TeamScore( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_TeamInfo(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_TeamInfo( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_Spectator(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_Spectator( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_SpecFade(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_SpecFade( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_ResetFade(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_ResetFade( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_AllowSpec(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_AllowSpec( pszName, iSize, pbuf ); + return 0; +} + +// This is called every time the DLL is loaded +void CHud :: Init( void ) +{ + HOOK_MESSAGE( Logo ); + HOOK_MESSAGE( ResetHUD ); + HOOK_MESSAGE( GameMode ); + HOOK_MESSAGE( InitHUD ); + HOOK_MESSAGE( ViewMode ); + HOOK_MESSAGE( SetFOV ); + HOOK_MESSAGE( Concuss ); + + // TFFree CommandMenu + HOOK_COMMAND( "+commandmenu", OpenCommandMenu ); + HOOK_COMMAND( "-commandmenu", CloseCommandMenu ); + HOOK_COMMAND( "ForceCloseCommandMenu", ForceCloseCommandMenu ); + HOOK_COMMAND( "special", InputPlayerSpecial ); + HOOK_COMMAND( "togglebrowser", ToggleServerBrowser ); + + HOOK_MESSAGE( ValClass ); + HOOK_MESSAGE( TeamNames ); + HOOK_MESSAGE( Feign ); + HOOK_MESSAGE( Detpack ); + HOOK_MESSAGE( MOTD ); + HOOK_MESSAGE( BuildSt ); + HOOK_MESSAGE( RandomPC ); + HOOK_MESSAGE( ServerName ); + HOOK_MESSAGE( ScoreInfo ); + HOOK_MESSAGE( TeamScore ); + HOOK_MESSAGE( TeamInfo ); + + HOOK_MESSAGE( Spectator ); + HOOK_MESSAGE( AllowSpec ); + + HOOK_MESSAGE( SpecFade ); + HOOK_MESSAGE( ResetFade ); + + // VGUI Menus + HOOK_MESSAGE( VGUIMenu ); + + CVAR_CREATE( "hud_classautokill", "1", FCVAR_ARCHIVE | FCVAR_USERINFO ); // controls whether or not to suicide immediately on TF class switch + CVAR_CREATE( "hud_takesshots", "0", FCVAR_ARCHIVE ); // controls whether or not to automatically take screenshots at the end of a round + + + m_iLogo = 0; + m_iFOV = 0; + + CVAR_CREATE( "zoom_sensitivity_ratio", "1.2", 0 ); + default_fov = CVAR_CREATE( "default_fov", "90", 0 ); + m_pCvarStealMouse = CVAR_CREATE( "hud_capturemouse", "1", FCVAR_ARCHIVE ); + m_pCvarDraw = CVAR_CREATE( "hud_draw", "1", FCVAR_ARCHIVE ); + cl_lw = gEngfuncs.pfnGetCvarPointer( "cl_lw" ); + + m_pSpriteList = NULL; + + // Clear any old HUD list + if ( m_pHudList ) + { + HUDLIST *pList; + while ( m_pHudList ) + { + pList = m_pHudList; + m_pHudList = m_pHudList->pNext; + free( pList ); + } + m_pHudList = NULL; + } + + // In case we get messages before the first update -- time will be valid + m_flTime = 1.0; + + m_Ammo.Init(); + m_Health.Init(); + m_SayText.Init(); + m_Spectator.Init(); + m_Geiger.Init(); + m_Train.Init(); + m_Battery.Init(); + m_Flash.Init(); + m_Message.Init(); + m_StatusBar.Init(); + m_DeathNotice.Init(); + m_AmmoSecondary.Init(); + m_TextMessage.Init(); + m_StatusIcons.Init(); + GetClientVoiceMgr()->Init(&g_VoiceStatusHelper, (vgui::Panel**)&gViewPort); + + m_Menu.Init(); + + ServersInit(); + + MsgFunc_ResetHUD(0, 0, NULL ); +} + +// CHud destructor +// cleans up memory allocated for m_rg* arrays +CHud :: ~CHud() +{ + delete [] m_rghSprites; + delete [] m_rgrcRects; + delete [] m_rgszSpriteNames; + + if ( m_pHudList ) + { + HUDLIST *pList; + while ( m_pHudList ) + { + pList = m_pHudList; + m_pHudList = m_pHudList->pNext; + free( pList ); + } + m_pHudList = NULL; + } + + ServersShutdown(); +} + +// GetSpriteIndex() +// searches through the sprite list loaded from hud.txt for a name matching SpriteName +// returns an index into the gHUD.m_rghSprites[] array +// returns 0 if sprite not found +int CHud :: GetSpriteIndex( const char *SpriteName ) +{ + // look through the loaded sprite name list for SpriteName + for ( int i = 0; i < m_iSpriteCount; i++ ) + { + if ( strncmp( SpriteName, m_rgszSpriteNames + (i * MAX_SPRITE_NAME_LENGTH), MAX_SPRITE_NAME_LENGTH ) == 0 ) + return i; + } + + return -1; // invalid sprite +} + +void CHud :: VidInit( void ) +{ + m_scrinfo.iSize = sizeof(m_scrinfo); + GetScreenInfo(&m_scrinfo); + + // ---------- + // Load Sprites + // --------- +// m_hsprFont = LoadSprite("sprites/%d_font.spr"); + + m_hsprLogo = 0; + m_hsprCursor = 0; + + if (ScreenWidth < 640) + m_iRes = 320; + else + m_iRes = 640; + + // Only load this once + if ( !m_pSpriteList ) + { + // we need to load the hud.txt, and all sprites within + m_pSpriteList = SPR_GetList("sprites/hud.txt", &m_iSpriteCountAllRes); + + if (m_pSpriteList) + { + // count the number of sprites of the appropriate res + m_iSpriteCount = 0; + client_sprite_t *p = m_pSpriteList; + int j; + for ( j = 0; j < m_iSpriteCountAllRes; j++ ) + { + if ( p->iRes == m_iRes ) + m_iSpriteCount++; + p++; + } + + // allocated memory for sprite handle arrays + m_rghSprites = new HSPRITE[m_iSpriteCount]; + m_rgrcRects = new wrect_t[m_iSpriteCount]; + m_rgszSpriteNames = new char[m_iSpriteCount * MAX_SPRITE_NAME_LENGTH]; + + p = m_pSpriteList; + int index = 0; + for ( j = 0; j < m_iSpriteCountAllRes; j++ ) + { + if ( p->iRes == m_iRes ) + { + char sz[256]; + sprintf(sz, "sprites/%s.spr", p->szSprite); + m_rghSprites[index] = SPR_Load(sz); + m_rgrcRects[index] = p->rc; + strncpy( &m_rgszSpriteNames[index * MAX_SPRITE_NAME_LENGTH], p->szName, MAX_SPRITE_NAME_LENGTH ); + + index++; + } + + p++; + } + } + } + else + { + // we have already have loaded the sprite reference from hud.txt, but + // we need to make sure all the sprites have been loaded (we've gone through a transition, or loaded a save game) + client_sprite_t *p = m_pSpriteList; + int index = 0; + for ( int j = 0; j < m_iSpriteCountAllRes; j++ ) + { + if ( p->iRes == m_iRes ) + { + char sz[256]; + sprintf( sz, "sprites/%s.spr", p->szSprite ); + m_rghSprites[index] = SPR_Load(sz); + index++; + } + + p++; + } + } + + // assumption: number_1, number_2, etc, are all listed and loaded sequentially + m_HUD_number_0 = GetSpriteIndex( "number_0" ); + + m_iFontHeight = m_rgrcRects[m_HUD_number_0].bottom - m_rgrcRects[m_HUD_number_0].top; + + m_Ammo.VidInit(); + m_Health.VidInit(); + m_Spectator.VidInit(); + m_Geiger.VidInit(); + m_Train.VidInit(); + m_Battery.VidInit(); + m_Flash.VidInit(); + m_Message.VidInit(); + m_StatusBar.VidInit(); + m_DeathNotice.VidInit(); + m_SayText.VidInit(); + m_Menu.VidInit(); + m_AmmoSecondary.VidInit(); + m_TextMessage.VidInit(); + m_StatusIcons.VidInit(); + GetClientVoiceMgr()->VidInit(); +} + +int CHud::MsgFunc_Logo(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + // update Train data + m_iLogo = READ_BYTE(); + + return 1; +} + +float g_lastFOV = 0.0; + +/* +============ +COM_FileBase +============ +*/ +// Extracts the base name of a file (no path, no extension, assumes '/' as path separator) +void COM_FileBase ( const char *in, char *out) +{ + int len, start, end; + + len = strlen( in ); + + // scan backward for '.' + end = len - 1; + while ( end && in[end] != '.' && in[end] != '/' && in[end] != '\\' ) + end--; + + if ( in[end] != '.' ) // no '.', copy to end + end = len-1; + else + end--; // Found ',', copy to left of '.' + + + // Scan backward for '/' + start = len-1; + while ( start >= 0 && in[start] != '/' && in[start] != '\\' ) + start--; + + if ( in[start] != '/' && in[start] != '\\' ) + start = 0; + else + start++; + + // Length of new sting + len = end - start + 1; + + // Copy partial string + strncpy( out, &in[start], len ); + // Terminate it + out[len] = 0; +} + +/* +================= +HUD_IsGame + +================= +*/ +int HUD_IsGame( const char *game ) +{ + const char *gamedir; + char gd[ 1024 ]; + + gamedir = gEngfuncs.pfnGetGameDirectory(); + if ( gamedir && gamedir[0] ) + { + COM_FileBase( gamedir, gd ); + if ( !stricmp( gd, game ) ) + return 1; + } + return 0; +} + +/* +===================== +HUD_GetFOV + +Returns last FOV +===================== +*/ +float HUD_GetFOV( void ) +{ + if ( gEngfuncs.pDemoAPI->IsRecording() ) + { + // Write it + int i = 0; + unsigned char buf[ 100 ]; + + // Active + *( float * )&buf[ i ] = g_lastFOV; + i += sizeof( float ); + + Demo_WriteBuffer( TYPE_ZOOM, i, buf ); + } + + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + { + g_lastFOV = g_demozoom; + } + return g_lastFOV; +} + +int CHud::MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + int newfov = READ_BYTE(); + int def_fov = CVAR_GET_FLOAT( "default_fov" ); + + //Weapon prediction already takes care of changing the fog. ( g_lastFOV ). + if ( cl_lw && cl_lw->value ) + return 1; + + g_lastFOV = newfov; + + if ( newfov == 0 ) + { + m_iFOV = def_fov; + } + else + { + m_iFOV = newfov; + } + + // the clients fov is actually set in the client data update section of the hud + + // Set a new sensitivity + if ( m_iFOV == def_fov ) + { + // reset to saved sensitivity + m_flMouseSensitivity = 0; + } + else + { + // set a new sensitivity that is proportional to the change from the FOV default + m_flMouseSensitivity = sensitivity->value * ((float)newfov / (float)def_fov) * CVAR_GET_FLOAT("zoom_sensitivity_ratio"); + } + + return 1; +} + + +void CHud::AddHudElem(CHudBase *phudelem) +{ + HUDLIST *pdl, *ptemp; + +//phudelem->Think(); + + if (!phudelem) + return; + + pdl = (HUDLIST *)malloc(sizeof(HUDLIST)); + if (!pdl) + return; + + memset(pdl, 0, sizeof(HUDLIST)); + pdl->p = phudelem; + + if (!m_pHudList) + { + m_pHudList = pdl; + return; + } + + ptemp = m_pHudList; + + while (ptemp->pNext) + ptemp = ptemp->pNext; + + ptemp->pNext = pdl; +} + +float CHud::GetSensitivity( void ) +{ + return m_flMouseSensitivity; +} + + diff --git a/cl_dll/hud.h b/cl_dll/hud.h new file mode 100644 index 0000000..96e0df2 --- /dev/null +++ b/cl_dll/hud.h @@ -0,0 +1,658 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud.h +// +// class CHud declaration +// +// CHud handles the message, calculation, and drawing the HUD +// + + +#define RGB_YELLOWISH 0x00FFA000 //255,160,0 +#define RGB_REDISH 0x00FF1010 //255,160,0 +#define RGB_GREENISH 0x0000A000 //0,160,0 + +#ifndef _WIN32 +#define _cdecl +#endif + +#include "wrect.h" +#include "cl_dll.h" +#include "ammo.h" + +#define DHN_DRAWZERO 1 +#define DHN_2DIGITS 2 +#define DHN_3DIGITS 4 +#define MIN_ALPHA 100 + +#define HUDELEM_ACTIVE 1 + +typedef struct { + int x, y; +} POSITION; + +#include "global_consts.h" + +typedef struct { + unsigned char r,g,b,a; +} RGBA; + +typedef struct cvar_s cvar_t; + + +#define HUD_ACTIVE 1 +#define HUD_INTERMISSION 2 + +#define MAX_PLAYER_NAME_LENGTH 32 + +#define MAX_MOTD_LENGTH 1536 + +// +//----------------------------------------------------- +// +class CHudBase +{ +public: + POSITION m_pos; + int m_type; + int m_iFlags; // active, moving, + virtual ~CHudBase() {} + virtual int Init( void ) {return 0;} + virtual int VidInit( void ) {return 0;} + virtual int Draw(float flTime) {return 0;} + virtual void Think(void) {return;} + virtual void Reset(void) {return;} + virtual void InitHUDData( void ) {} // called every time a server is connected to + +}; + +struct HUDLIST { + CHudBase *p; + HUDLIST *pNext; +}; + + + +// +//----------------------------------------------------- +// +#include "voice_status.h" // base voice handling class +#include "hud_spectator.h" + + +// +//----------------------------------------------------- +// +class CHudAmmo: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + void Think(void); + void Reset(void); + int DrawWList(float flTime); + int MsgFunc_CurWeapon(const char *pszName, int iSize, void *pbuf); + int MsgFunc_WeaponList(const char *pszName, int iSize, void *pbuf); + int MsgFunc_AmmoX(const char *pszName, int iSize, void *pbuf); + int MsgFunc_AmmoPickup( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_WeapPickup( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_ItemPickup( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_HideWeapon( const char *pszName, int iSize, void *pbuf ); + + void SlotInput( int iSlot ); + void _cdecl UserCmd_Slot1( void ); + void _cdecl UserCmd_Slot2( void ); + void _cdecl UserCmd_Slot3( void ); + void _cdecl UserCmd_Slot4( void ); + void _cdecl UserCmd_Slot5( void ); + void _cdecl UserCmd_Slot6( void ); + void _cdecl UserCmd_Slot7( void ); + void _cdecl UserCmd_Slot8( void ); + void _cdecl UserCmd_Slot9( void ); + void _cdecl UserCmd_Slot10( void ); + void _cdecl UserCmd_Close( void ); + void _cdecl UserCmd_NextWeapon( void ); + void _cdecl UserCmd_PrevWeapon( void ); + +private: + float m_fFade; + RGBA m_rgba; + WEAPON *m_pWeapon; + int m_HUD_bucket0; + int m_HUD_selection; + +}; + +// +//----------------------------------------------------- +// + +class CHudAmmoSecondary: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + void Reset( void ); + int Draw(float flTime); + + int MsgFunc_SecAmmoVal( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_SecAmmoIcon( const char *pszName, int iSize, void *pbuf ); + +private: + enum { + MAX_SEC_AMMO_VALUES = 4 + }; + + int m_HUD_ammoicon; // sprite indices + int m_iAmmoAmounts[MAX_SEC_AMMO_VALUES]; + float m_fFade; +}; + + +#include "health.h" + + +#define FADE_TIME 100 + + +// +//----------------------------------------------------- +// +class CHudGeiger: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_Geiger(const char *pszName, int iSize, void *pbuf); + +private: + int m_iGeigerRange; + +}; + +// +//----------------------------------------------------- +// +class CHudTrain: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_Train(const char *pszName, int iSize, void *pbuf); + +private: + HSPRITE m_hSprite; + int m_iPos; + +}; + +// +//----------------------------------------------------- +// +class CHudStatusBar : public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw( float flTime ); + void Reset( void ); + void ParseStatusString( int line_num ); + + int MsgFunc_StatusText( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_StatusValue( const char *pszName, int iSize, void *pbuf ); + +protected: + enum { + MAX_STATUSTEXT_LENGTH = 128, + MAX_STATUSBAR_VALUES = 8, + MAX_STATUSBAR_LINES = 3, + }; + + char m_szStatusText[MAX_STATUSBAR_LINES][MAX_STATUSTEXT_LENGTH]; // a text string describing how the status bar is to be drawn + char m_szStatusBar[MAX_STATUSBAR_LINES][MAX_STATUSTEXT_LENGTH]; // the constructed bar that is drawn + int m_iStatusValues[MAX_STATUSBAR_VALUES]; // an array of values for use in the status bar + + int m_bReparseString; // set to TRUE whenever the m_szStatusBar needs to be recalculated + + // an array of colors...one color for each line + float *m_pflNameColors[MAX_STATUSBAR_LINES]; +}; + +struct extra_player_info_t +{ + short frags; + short deaths; + short playerclass; + short health; // UNUSED currently, spectator UI would like this + bool dead; // UNUSED currently, spectator UI would like this + short teamnumber; + char teamname[MAX_TEAM_NAME]; +}; + +struct team_info_t +{ + char name[MAX_TEAM_NAME]; + short frags; + short deaths; + short ping; + short packetloss; + short ownteam; + short players; + int already_drawn; + int scores_overriden; + int teamnumber; +}; + +#include "player_info.h" + +// +//----------------------------------------------------- +// +class CHudDeathNotice : public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + int Draw( float flTime ); + int MsgFunc_DeathMsg( const char *pszName, int iSize, void *pbuf ); + +private: + int m_HUD_d_skull; // sprite index of skull icon +}; + +// +//----------------------------------------------------- +// +class CHudMenu : public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + void Reset( void ); + int Draw( float flTime ); + int MsgFunc_ShowMenu( const char *pszName, int iSize, void *pbuf ); + + void SelectMenuItem( int menu_item ); + + int m_fMenuDisplayed; + int m_bitsValidSlots; + float m_flShutoffTime; + int m_fWaitingForMore; +}; + +// +//----------------------------------------------------- +// +class CHudSayText : public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + int Draw( float flTime ); + int MsgFunc_SayText( const char *pszName, int iSize, void *pbuf ); + void SayTextPrint( const char *pszBuf, int iBufSize, int clientIndex = -1 ); + void EnsureTextFitsInOneLineAndWrapIfHaveTo( int line ); +friend class CHudSpectator; + +private: + + struct cvar_s * m_HUD_saytext; + struct cvar_s * m_HUD_saytext_time; +}; + +// +//----------------------------------------------------- +// +class CHudBattery: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_Battery(const char *pszName, int iSize, void *pbuf ); + +private: + HSPRITE m_hSprite1; + HSPRITE m_hSprite2; + wrect_t *m_prc1; + wrect_t *m_prc2; + int m_iBat; + int m_iBatMax; + float m_fFade; + int m_iHeight; // width of the battery innards +}; + + +// +//----------------------------------------------------- +// +class CHudFlashlight: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + void Reset( void ); + int MsgFunc_Flashlight(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_FlashBat(const char *pszName, int iSize, void *pbuf ); + +private: + HSPRITE m_hSprite1; + HSPRITE m_hSprite2; + HSPRITE m_hBeam; + wrect_t *m_prc1; + wrect_t *m_prc2; + wrect_t *m_prcBeam; + float m_flBat; + int m_iBat; + int m_fOn; + float m_fFade; + int m_iWidth; // width of the battery innards +}; + +// +//----------------------------------------------------- +// +const int maxHUDMessages = 16; +struct message_parms_t +{ + client_textmessage_t *pMessage; + float time; + int x, y; + int totalWidth, totalHeight; + int width; + int lines; + int lineLength; + int length; + int r, g, b; + int text; + int fadeBlend; + float charTime; + float fadeTime; +}; + +// +//----------------------------------------------------- +// + +class CHudTextMessage: public CHudBase +{ +public: + int Init( void ); + static char *LocaliseTextString( const char *msg, char *dst_buffer, int buffer_size ); + static char *BufferedLocaliseTextString( const char *msg ); + char *LookupString( const char *msg_name, int *msg_dest = NULL ); + int MsgFunc_TextMsg(const char *pszName, int iSize, void *pbuf); +}; + +// +//----------------------------------------------------- +// + +class CHudMessage: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_HudText(const char *pszName, int iSize, void *pbuf); + int MsgFunc_HudTextPro(const char *pszName, int iSize, void *pbuf); + int MsgFunc_GameTitle(const char *pszName, int iSize, void *pbuf); + + float FadeBlend( float fadein, float fadeout, float hold, float localTime ); + int XPosition( float x, int width, int lineWidth ); + int YPosition( float y, int height ); + + void MessageAdd( const char *pName, float time ); + void MessageAdd(client_textmessage_t * newMessage ); + void MessageDrawScan( client_textmessage_t *pMessage, float time ); + void MessageScanStart( void ); + void MessageScanNextChar( void ); + void Reset( void ); + +private: + client_textmessage_t *m_pMessages[maxHUDMessages]; + float m_startTime[maxHUDMessages]; + message_parms_t m_parms; + float m_gameTitleTime; + client_textmessage_t *m_pGameTitle; + + int m_HUD_title_life; + int m_HUD_title_half; +}; + +// +//----------------------------------------------------- +// +#define MAX_SPRITE_NAME_LENGTH 24 + +class CHudStatusIcons: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + void Reset( void ); + int Draw(float flTime); + int MsgFunc_StatusIcon(const char *pszName, int iSize, void *pbuf); + + enum { + MAX_ICONSPRITENAME_LENGTH = MAX_SPRITE_NAME_LENGTH, + MAX_ICONSPRITES = 4, + }; + + + //had to make these public so CHud could access them (to enable concussion icon) + //could use a friend declaration instead... + void EnableIcon( char *pszIconName, unsigned char red, unsigned char green, unsigned char blue ); + void DisableIcon( char *pszIconName ); + +private: + + typedef struct + { + char szSpriteName[MAX_ICONSPRITENAME_LENGTH]; + HSPRITE spr; + wrect_t rc; + unsigned char r, g, b; + } icon_sprite_t; + + icon_sprite_t m_IconList[MAX_ICONSPRITES]; + +}; + +// +//----------------------------------------------------- +// +class CHudBenchmark : public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw( float flTime ); + + void SetScore( float score ); + + void Think( void ); + + void StartNextSection( int section ); + + int MsgFunc_Bench(const char *pszName, int iSize, void *pbuf); + + void CountFrame( float dt ); + + int GetObjects( void ) { return m_nObjects; }; + + void SetCompositeScore( void ); + + void Restart( void ); + + int Bench_ScoreForValue( int stage, float raw ); + +private: + float m_fDrawTime; + float m_fDrawScore; + float m_fAvgScore; + + float m_fSendTime; + float m_fReceiveTime; + + int m_nFPSCount; + float m_fAverageFT; + float m_fAvgFrameRate; + + int m_nSentFinish; + float m_fStageStarted; + + float m_StoredLatency; + float m_StoredPacketLoss; + int m_nStoredHopCount; + int m_nTraceDone; + + int m_nObjects; + + int m_nScoreComputed; + int m_nCompositeScore; +}; + +// +//----------------------------------------------------- +// + + +class CHud +{ +private: + HUDLIST *m_pHudList; + HSPRITE m_hsprLogo; + int m_iLogo; + client_sprite_t *m_pSpriteList; + int m_iSpriteCount; + int m_iSpriteCountAllRes; + float m_flMouseSensitivity; + int m_iConcussionEffect; + +public: + + HSPRITE m_hsprCursor; + float m_flTime; // the current client time + float m_fOldTime; // the time at which the HUD was last redrawn + double m_flTimeDelta; // the difference between flTime and fOldTime + Vector m_vecOrigin; + Vector m_vecAngles; + int m_iKeyBits; + int m_iHideHUDDisplay; + int m_iFOV; + int m_Teamplay; + int m_iRes; + cvar_t *m_pCvarStealMouse; + cvar_t *m_pCvarDraw; + + int m_iFontHeight; + int DrawHudNumber(int x, int y, int iFlags, int iNumber, int r, int g, int b ); + int DrawHudString(int x, int y, int iMaxX, char *szString, int r, int g, int b ); + int DrawHudStringReverse( int xpos, int ypos, int iMinX, char *szString, int r, int g, int b ); + int DrawHudNumberString( int xpos, int ypos, int iMinX, int iNumber, int r, int g, int b ); + int GetNumWidth(int iNumber, int iFlags); + +private: + // the memory for these arrays are allocated in the first call to CHud::VidInit(), when the hud.txt and associated sprites are loaded. + // freed in ~CHud() + HSPRITE *m_rghSprites; /*[HUD_SPRITE_COUNT]*/ // the sprites loaded from hud.txt + wrect_t *m_rgrcRects; /*[HUD_SPRITE_COUNT]*/ + char *m_rgszSpriteNames; /*[HUD_SPRITE_COUNT][MAX_SPRITE_NAME_LENGTH]*/ + + struct cvar_s *default_fov; +public: + HSPRITE GetSprite( int index ) + { + return (index < 0) ? 0 : m_rghSprites[index]; + } + + wrect_t& GetSpriteRect( int index ) + { + return m_rgrcRects[index]; + } + + + int GetSpriteIndex( const char *SpriteName ); // gets a sprite index, for use in the m_rghSprites[] array + + CHudAmmo m_Ammo; + CHudHealth m_Health; + CHudSpectator m_Spectator; + CHudGeiger m_Geiger; + CHudBattery m_Battery; + CHudTrain m_Train; + CHudFlashlight m_Flash; + CHudMessage m_Message; + CHudStatusBar m_StatusBar; + CHudDeathNotice m_DeathNotice; + CHudSayText m_SayText; + CHudMenu m_Menu; + CHudAmmoSecondary m_AmmoSecondary; + CHudTextMessage m_TextMessage; + CHudStatusIcons m_StatusIcons; + CHudBenchmark m_Benchmark; + + void Init( void ); + void VidInit( void ); + void Think(void); + int Redraw( float flTime, int intermission ); + int UpdateClientData( client_data_t *cdata, float time ); + + CHud() : m_iSpriteCount(0), m_pHudList(NULL) {} + ~CHud(); // destructor, frees allocated memory + + // user messages + int _cdecl MsgFunc_Damage(const char *pszName, int iSize, void *pbuf ); + int _cdecl MsgFunc_GameMode(const char *pszName, int iSize, void *pbuf ); + int _cdecl MsgFunc_Logo(const char *pszName, int iSize, void *pbuf); + int _cdecl MsgFunc_ResetHUD(const char *pszName, int iSize, void *pbuf); + void _cdecl MsgFunc_InitHUD( const char *pszName, int iSize, void *pbuf ); + void _cdecl MsgFunc_ViewMode( const char *pszName, int iSize, void *pbuf ); + int _cdecl MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf); + int _cdecl MsgFunc_Concuss( const char *pszName, int iSize, void *pbuf ); + + // Screen information + SCREENINFO m_scrinfo; + + int m_iWeaponBits; + int m_fPlayerDead; + int m_iIntermission; + + // sprite indexes + int m_HUD_number_0; + + + void AddHudElem(CHudBase *p); + + float GetSensitivity(); + +}; + +extern CHud gHUD; + +extern int g_iPlayerClass; +extern int g_iTeamNumber; +extern int g_iUser1; +extern int g_iUser2; +extern int g_iUser3; + diff --git a/cl_dll/hud_bench.cpp b/cl_dll/hud_bench.cpp new file mode 100644 index 0000000..71dd87e --- /dev/null +++ b/cl_dll/hud_bench.cpp @@ -0,0 +1,1129 @@ +// +//----------------------------------------------------- +// +#define BENCH_TIME 10.0 + +#include "hud.h" +#include "cl_util.h" + +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "usercmd.h" +#include "pm_defs.h" +#include "event_api.h" + +#include "bench.h" + +#include +#include +#include "parsemsg.h" + +#include "con_nprint.h" + +#include "netadr.h" +#include "hud_benchtrace.h" + +#include "net_api.h" + +#include "entity_types.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +#define NUM_BENCH_OBJ 12 +#define BENCH_CYCLE_TIME 10.0 +#define BENCH_INNER_CYCLE_TIME 4.0 +#define BENCH_VIEW_CYCLE_TIME 7.1 +#define BENCH_SWEEP 360.0 +#define BENCH_RADIUS 80.0 +#define BENCH_VIEW_OFFSET 250.0 +#define BLEND_IN_SPEED 150.0 +#define BENCH_BALLHEIGHT 72.0 +#define BENCH_BALL_VIEWDRIFT 60.0; +#define BENCH_RANGE 60.0 +// Scale: +// 0 - 100 +// 0 is worst +// 100 is best +// PP has 40 - 100 range +// Non-pp has 0 - 60 range +const float weights[3] = { 0.2, 0.3, 0.5 }; + +const char *g_title = "PowerPlay QoS Test"; //uality of Service Test"; +const char *pp_strings[2] = +{ + " PowerPlay Detected", + " PowerPlay Not Detected" , +}; +const char *g_stage1[2] = +{ + " Stage 1: Testing System Connectivity...", + " Stage 1: %i", +}; +const char *g_stage2[2] = +{ + " Stage 2: Testing System Performance...", + " Stage 2: %i", +}; +const char *g_stage3[2] = +{ + " Stage 3: Testing Tracking Accuracy...", + " Stage 3: %i", +}; +const char *g_stage4 = " Composite Score: %i"; + +extern vec3_t v_origin; + +static int g_isPowerPlay = 0; +static int g_currentstage = 0; +static int g_renderedBenchmarkDot = 0; +static float g_benchSwitchTime = 0.0; +static float g_benchSwitchTimes[ LAST_STAGE + 1 ] = { 0.0, 10.0, 12.0, 10.0, 5.0 }; + +#define SCORE_TIME_UP 1.5 + +DECLARE_MESSAGE(m_Benchmark, Bench); + +void VectorAngles( const float *forward, float *angles ); + +void Bench_SetStage( int stage ) +{ + g_currentstage = stage; +} + +int Bench_GetStage( void ) +{ + return g_currentstage; +} + +float Bench_GetSwitchTime( void ) +{ + return g_benchSwitchTimes[ min( Bench_GetStage(), LAST_STAGE ) ]; +} + +int Bench_InStage( int stage ) +{ + return ( Bench_GetStage() == stage ) ? 1 : 0; +} + +void Bench_SetPowerPlay( int set ) +{ + g_isPowerPlay = set ? 1 : 0; +} + +int Bench_GetPowerPlay( void ) +{ + return g_isPowerPlay; +} + +int Bench_Active( void ) +{ + return g_currentstage != 0 ? 1 : 0; +} + +void __CmdFunc_BenchMark( void ) +{ + gHUD.m_Benchmark.Restart(); +} + + +void CHudBenchmark::Restart( void ) +{ + Bench_SetStage( FIRST_STAGE ); + g_benchSwitchTime = gHUD.m_flTime + g_benchSwitchTimes[ FIRST_STAGE ]; + StartNextSection( FIRST_STAGE ); + + gHUD.m_Benchmark.m_iFlags |= HUD_ACTIVE; + gHUD.m_Benchmark.m_fDrawTime = gHUD.m_flTime + BENCH_TIME; +} + +int CHudBenchmark::MsgFunc_Bench(const char *pszName, int iSize, void *pbuf) +{ + int section = READ_BYTE(); + + m_fReceiveTime = gHUD.m_flTime; + m_StoredLatency = ( m_fReceiveTime - m_fSendTime ); + + m_StoredLatency = min( 1.0, m_StoredLatency ); + m_StoredLatency = max( 0.0, m_StoredLatency ); + + m_StoredPacketLoss = 0.0; + + { + char sz[ 256 ]; + netadr_t adr; + net_status_t status; + + gEngfuncs.pNetAPI->Status( &status ); + + if ( status.connected ) + { + adr = status.remote_address; + + sprintf( sz, "%i.%i.%i.%i", + adr.ip[ 0 ], adr.ip[ 1 ], adr.ip[ 2 ], adr.ip[ 3 ] ); + + if ( adr.type == NA_IP ) + { + Trace_StartTrace( &m_nStoredHopCount, &m_nTraceDone, (const char *)sz ); + } + else + { + m_nStoredHopCount = 0; + } + } + } + + return 1; +} + +void CHudBenchmark::StartNextSection( int section ) +{ + net_status_t status; + + switch ( section ) + { + case 1: + // Stage 2 requires that we tell the server to "drop" an item + m_fSendTime = gHUD.m_flTime; + m_fReceiveTime = 0.0; + m_StoredLatency = 0.0; + m_StoredPacketLoss = 0.0; + m_nStoredHopCount = 0; + m_nTraceDone = 0; + ServerCmd( "ppdemo 1 start\n" ); + break; + case 2: + if ( m_nTraceDone ) + { + gEngfuncs.pNetAPI->Status( &status ); + + gEngfuncs.Con_Printf( "Hops == %i\n", m_nStoredHopCount ); + m_StoredPacketLoss = status.packet_loss; + gEngfuncs.Con_Printf( "PL == %i\n", (int)m_StoredPacketLoss ); + + } + m_nSentFinish = 0; // added by minman + ServerCmd( "ppdemo 2\n" ); + break; + case 3: + m_nSentFinish = 0; // added by minman + ServerCmd( "ppdemo 3\n" ); + break; + default: + break; + } + + m_fStageStarted = gHUD.m_flTime; + g_benchSwitchTime = gHUD.m_flTime + Bench_GetSwitchTime(); +} + +void CHudBenchmark::CountFrame( float dt ) +{ + m_nFPSCount++; + m_fAverageFT += dt; +} + + +static int started = 0; + +void Bench_CheckStart( void ) +{ + const char *level; + if ( !started && !Bench_Active() ) + { + level = gEngfuncs.pfnGetLevelName(); + if ( level && level[0] && !stricmp( level, "maps/ppdemo.bsp" ) ) + { + started = 1; + EngineClientCmd( "ppdemostart\n" ); + } + } +} + +void CHudBenchmark::Think( void ) +{ + if ( !Bench_Active() ) + return; + + Trace_Think(); + + if ( started ) + { + started = 0; + + // Clear variable + m_fReceiveTime = 0.0; + m_nFPSCount = 0; + m_fAverageFT = 0.0; + m_nSentFinish = 0; + m_StoredLatency = 0.0; + m_StoredPacketLoss = 0.0; + m_nStoredHopCount = 0; + m_nTraceDone = 0; + m_nObjects = 0; + m_nScoreComputed = 0; + m_nCompositeScore = 0; + m_fAvgScore = 0; + m_fDrawScore = 0.0; + m_fAvgFrameRate = 0.0; + } + + if ( gHUD.m_flTime > g_benchSwitchTime ) + { + Bench_SetStage( Bench_GetStage() + 1 ); + StartNextSection( Bench_GetStage() ); + } + + if ( Bench_InStage( FIRST_STAGE ) ) + { + // Assume 1000 ms lag is the max and that would take all but 2 seconds of this interval to traverse + if ( m_fReceiveTime ) + { + float latency = 2.0 * m_StoredLatency; + float switch_time; + float total_time; + + latency = max( 0.0, latency ); + latency = min( 1.0, latency ); + + total_time = Bench_GetSwitchTime(); + total_time -= 2.0; + + switch_time = m_fStageStarted + latency * total_time; + switch_time += 1.0; + + if ( gHUD.m_flTime >= switch_time ) + { + if ( !m_nSentFinish ) + { + g_benchSwitchTime = gHUD.m_flTime + 1.0 + SCORE_TIME_UP; + + ServerCmd( "ppdemo 1 finish\n" ); + m_nSentFinish = 1; + } + } + else + { + g_benchSwitchTime = gHUD.m_flTime + 10.0; + } + } + } + + if ( Bench_InStage( SECOND_STAGE ) ) + { + // frametime + static float lasttime; + float elapsed; + float total; + float frac; + float switch_time; // added by minman + + if ( lasttime ) + { + float dt; + + dt = gHUD.m_flTime - lasttime; + if ( dt > 0 ) + { + CountFrame( dt ); + } + } + lasttime = gHUD.m_flTime; + + elapsed = gHUD.m_flTime - m_fStageStarted; + total = Bench_GetSwitchTime(); + if ( total ) + { + frac = elapsed / total; + + // Only takes 1/2 time to get up to maximum speed + frac *= 2.0; + frac = max( 0.0, frac ); + frac = min( 1.0, frac ); + + m_nObjects = (int)(NUM_BENCH_OBJ * frac); + } + switch_time = m_fStageStarted + total; + + /* BELOW ADDED BY minman */ + if (gHUD.m_flTime >= switch_time) + { + if ( !m_nSentFinish) + { + g_benchSwitchTime = gHUD.m_flTime + SCORE_TIME_UP; + m_nSentFinish = 1; + } + } + else + g_benchSwitchTime = gHUD.m_flTime + 10.0; + } + + /* BELOW ADDED BY minman */ + if ( Bench_InStage (THIRD_STAGE)) + { + float switch_time = m_fStageStarted + Bench_GetSwitchTime(); + + if (gHUD.m_flTime >= switch_time) + { + if ( !m_nSentFinish) + { + g_benchSwitchTime = gHUD.m_flTime + SCORE_TIME_UP; + m_nSentFinish = 1; + } + } + else + g_benchSwitchTime = gHUD.m_flTime + 10.0; + } + + if ( Bench_InStage( FOURTH_STAGE ) ) + { + if ( !m_nScoreComputed ) + { + m_nScoreComputed = 1; + gHUD.m_Benchmark.SetCompositeScore(); + } + } + + if ( Bench_GetStage() > LAST_STAGE ) + { + m_iFlags &= ~HUD_ACTIVE; + EngineClientCmd( "quit\n" ); + } +} + + +int CHudBenchmark::Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_COMMAND( "ppdemostart", BenchMark ); + + HOOK_MESSAGE(Bench); + + return 1; +} + +int CHudBenchmark::VidInit( void ) +{ + return 1; +} + +int CHudBenchmark::Bench_ScoreForValue( int stage, float raw ) +{ + int score = 100.0; + int power_play = Bench_GetPowerPlay() ? 1 : 0; + + switch ( stage ) + { + case 1: // ping + score = 100.0 * ( m_StoredLatency ); + score = max( score, 0 ); + score = min( score, 100 ); + + // score is inverted + score = 100 - score; + + break; + case 2: // framerate/performance + score = (int)( 100 * m_fAvgFrameRate ) / 72; + score = min( score, 100 ); + score = max( score, 0 ); + + score *= BENCH_RANGE/100.0; + if ( power_play ) + { + score += ( 100 - BENCH_RANGE ); + } + break; + case 3: // tracking + score = (100 * m_fAvgScore) / 40; + score = max( score, 0 ); + score = min( score, 100 ); + + // score is inverted + score = 100 - score; + + score *= BENCH_RANGE/100.0; + if ( power_play ) + { + score += ( 100 - BENCH_RANGE ); + } + break; + } + + return score; +} + +void CHudBenchmark::SetCompositeScore( void ) +{ + int tracking_score = Bench_ScoreForValue( THIRD_STAGE, m_fAvgScore ); + int ping_score = Bench_ScoreForValue( FIRST_STAGE, m_StoredLatency ); + int frame_score = Bench_ScoreForValue( SECOND_STAGE, m_fAvgFrameRate ); + + int composite = ( ping_score * weights[ 0 ] + frame_score * weights[ 1 ] + tracking_score * weights[ 2 ] ); + + composite = min( 100, composite ); + composite = max( 0, composite ); + + m_nCompositeScore = composite; +} + +int CHudBenchmark::Draw( float flTime ) +{ + char sz[ 256 ]; + int x, y; + + if ( m_fDrawTime < flTime || !Bench_Active() ) + { + m_iFlags &= ~HUD_ACTIVE; + return 1; + } + + x = 10; + y = 25; //480 - 150; + + sprintf( sz, "%s: %s", g_title , pp_strings[ Bench_GetPowerPlay() ? 0 : 1]); + + gHUD.DrawHudString( x, y, 320, sz, 251, 237, 7);// , 200, 200); //255, 255, 255 ); + + y += 20; + +// sprintf( sz, pp_strings[ Bench_GetPowerPlay() ? 0 : 1 ] ); + +// gHUD.DrawHudString( x, y, 320, sz, 31, 200, 200 ); + +// y += 20; + + + if ( Bench_InStage( FIRST_STAGE) /*|| Bench_InStage( SECOND_STAGE ) || Bench_InStage( THIRD_STAGE )*/ || Bench_InStage( FOURTH_STAGE ) ) + { + if ( m_fReceiveTime && m_nSentFinish ) + { + sprintf( sz, g_stage1[1], Bench_ScoreForValue( FIRST_STAGE, m_StoredLatency )); + } + else + { + sprintf( sz, g_stage1[0] ); + } + gHUD.DrawHudString( x, y, 320, sz, 255, 255, 255 ); + + y += 20; + + } + + + if ( Bench_InStage( SECOND_STAGE )/* || Bench_InStage( THIRD_STAGE )*/ || Bench_InStage( FOURTH_STAGE ) ) + { + float avg = 0.0; + + if ( m_nFPSCount > 0 ) + { + avg = m_fAverageFT / (float)m_nFPSCount; + m_fAvgFrameRate = 1.0 / avg; + } + + if ( m_nSentFinish /* Bench_InStage( THIRD_STAGE ) */|| Bench_InStage( FOURTH_STAGE ) ) + { + sprintf( sz, g_stage2[1], Bench_ScoreForValue( SECOND_STAGE, m_fAvgFrameRate ) ); + } + else + { + sprintf( sz, g_stage2[0] ); + } + gHUD.DrawHudString( x, y, 320, sz, 255, 255, 255 ); + y += 20; + } + + + if ( Bench_InStage( THIRD_STAGE ) || Bench_InStage( FOURTH_STAGE ) ) + { + if ( m_nSentFinish || Bench_InStage( FOURTH_STAGE ) ) + { + sprintf( sz, g_stage3[1], Bench_ScoreForValue( THIRD_STAGE, m_fAvgScore ) ); + } + else + { + sprintf( sz, g_stage3[0] ); + } + + gHUD.DrawHudString( x, y, 320, sz, 255, 255, 255 ); + + y += 20; + } + + if ( Bench_InStage( FOURTH_STAGE ) ) + { + sprintf( sz, g_stage4, m_nCompositeScore ); + gHUD.DrawHudString( x, y, 320, sz, 31, 200, 200 ); + } + + m_fDrawTime = gHUD.m_flTime + BENCH_TIME; + + return 1; +} + +#define SCORE_AVG 0.9 + +void CHudBenchmark::SetScore( float score ) +{ + // added by minman + if (m_nSentFinish) + return; + + m_fDrawScore = score; + m_fDrawTime = gHUD.m_flTime + BENCH_TIME; + + m_fAvgScore = ( SCORE_AVG ) * m_fAvgScore + ( 1.0 - SCORE_AVG ) * m_fDrawScore; +} + +void Bench_SetDotAdded( int dot ) +{ + g_renderedBenchmarkDot = dot; +} + +int Bench_GetDotAdded( void ) +{ + return g_renderedBenchmarkDot; +} + +void Bench_SpotPosition( vec3_t dot, vec3_t target ) +{ + // Compute new score + vec3_t delta; + + VectorSubtract( target, dot, delta ); + + gHUD.m_Benchmark.SetScore( delta.Length() ); +} + +typedef struct model_s +{ + char name[64]; + qboolean needload; // bmodels and sprites don't cache normally + + int type; + int numframes; + int synctype; + + int flags; + +// +// volume occupied by the model +// + vec3_t mins, maxs; +} model_t; + +static vec3_t g_dotorg; +vec3_t g_aimorg; +float g_fZAdjust = 0.0; + +void Bench_CheckEntity( int type, struct cl_entity_s *ent, const char *modelname ) +{ + if ( Bench_InStage( THIRD_STAGE ) && !stricmp( modelname, "*3" ) ) + { + model_t *pmod; + vec3_t v; + pmod = (model_t *)( ent->model ); + + VectorAdd( pmod->mins, pmod->maxs, v ); + VectorScale( v, 0.5, v ); + + VectorAdd( v, ent->origin, g_aimorg ); + } + + if ( Bench_InStage( THIRD_STAGE ) && strstr( modelname, "ppdemodot" ) ) + { + Bench_SetDotAdded( 1 ); + VectorCopy( ent->origin, g_dotorg ); + + // Adjust end position + if ( Bench_Active() && Bench_InStage( THIRD_STAGE ) ) + { + static float fZAdjust = 0.0; + static float fLastTime; + float dt; + float fRate = Bench_GetPowerPlay() ? 4.0 : 8.0; + float fBounds = Bench_GetPowerPlay() ? 8.0 : 15.0; + + dt = gHUD.m_flTime - fLastTime; + if ( dt > 0.0 && dt < 1.0 ) + { + fZAdjust += gEngfuncs.pfnRandomFloat( -fRate, fRate ); + fZAdjust = min( fBounds, fZAdjust ); + fZAdjust = max( -fBounds, fZAdjust ); + + ent->origin[2] += fZAdjust; + + g_fZAdjust = fZAdjust; + } + fLastTime = gHUD.m_flTime; + } + } +} + + +void NormalizeVector( vec3_t v ) +{ + int i; + for ( i = 0; i < 3; i++ ) + { + while ( v[i] < -180.0 ) + { + v[i] += 360.0; + } + + while ( v[i] > 180.0 ) + { + v[i] -= 360.0; + } + } +} + +float g_flStartTime; +int HUD_SetupBenchObjects( cl_entity_t *bench, int plindex, vec3_t origin ) +{ + int i, j; + vec3_t ang; + float offset; + struct model_s *mdl; + int index; + vec3_t forward, right, up; + vec3_t farpoint; + vec3_t centerspot; + pmtrace_t tr; + + ang = vec3_origin; + //ang[1] = 90.0; + + // Determine forward vector + AngleVectors ( ang, forward, right, up ); + + // Try to find the laserdot sprite model and retrieve the modelindex for it + mdl = gEngfuncs.CL_LoadModel( "models/spikeball.mdl", &index ); + if ( !mdl ) + return 0; + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( plindex ); + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + + centerspot = origin; + centerspot[2] -= 512; + + gEngfuncs.pEventAPI->EV_PlayerTrace( (float *)&origin, (float *)¢erspot, PM_NORMAL, -1, &tr ); + + centerspot = tr.endpos; + centerspot[2] += BENCH_BALLHEIGHT; + + // Move center out from here + centerspot = centerspot + BENCH_VIEW_OFFSET * forward; + + g_flStartTime = gHUD.m_flTime; + + for ( i = 0; i < NUM_BENCH_OBJ; i++ ) + { + offset = ( float ) i / (float) ( NUM_BENCH_OBJ - 1 ); + + ang[ 0 ] = 0; + ang[ 2 ] = 0; + ang[ 1 ] = BENCH_SWEEP * offset; + + // normalize + NormalizeVector( ang ); + + // Determine forward vector + AngleVectors ( ang, forward, right, up ); + + bench[ i ].model = mdl; + + bench[ i ].curstate.modelindex = index; + + // Set up dot info. + bench[ i ].curstate.movetype = MOVETYPE_NONE; + bench[ i ].curstate.solid = SOLID_NOT; + + // Get a far point for ray trace + farpoint = centerspot + BENCH_RADIUS * forward; + + gEngfuncs.pEventAPI->EV_PlayerTrace( (float *)¢erspot, (float *)&farpoint, PM_NORMAL, -1, &tr ); + + // Move dot to trace endpoint + bench[ i ].origin = tr.endpos; + bench[ i ].curstate.origin = bench[ i ].origin; + //bench[ i ].curstate.gravity = 0.5; + for ( j = 0; j < 2; j++ ) + { + // bench[ i ].curstate.velocity[ j ] = gEngfuncs.pfnRandomLong( -300, 300 ); + } + //bench[ i ].curstate.velocity[ 2 ] = gEngfuncs.pfnRandomLong( 0, 50 ); + bench[ i ].curstate.velocity = vec3_origin; + + bench[ i ].curstate.angles[ 2 ] = 0.0; + bench[ i ].curstate.angles[ 0 ] = 0.0; + bench[ i ].curstate.angles[ 1 ] = 0.0; // gEngfuncs.pfnRandomLong( -180, 180 ); + + for ( j = 0; j < 3; j++ ) + { + // angular velocity + bench[ i ].baseline.angles[ 0 ] = gEngfuncs.pfnRandomLong( -300, 300 ); + //bench[ i ].baseline.angles[ 0 ] = 0; + bench[ i ].baseline.angles[ 2 ] = 0; + bench[ i ].baseline.angles[ 1 ] = gEngfuncs.pfnRandomLong( -300, 300 ); + } + + bench[ i ].curstate.renderamt = 0; + + // Force no interpolation, etc., probably not necessary + bench[ i ].prevstate = bench[ i ].curstate; + } + + gEngfuncs.pEventAPI->EV_PopPMStates(); + + return 1; +} + +void HUD_CreateBenchObjects( vec3_t origin ) +{ + static cl_entity_t bench[ NUM_BENCH_OBJ ]; + cl_entity_t *player; + vec3_t forward, right, up; + vec3_t farpoint; + vec3_t centerspot; + static int first = true; + static int failed = false; + static float last_time; + float frametime; + float frac; + float frac2; + float dt; + + pmtrace_t tr; + int i = 0; + + if ( gHUD.m_flTime == last_time ) + return; + + frametime = gHUD.m_flTime - last_time; + last_time = gHUD.m_flTime; + + if ( frametime <= 0.0 ) + return; + + if ( failed ) + return; + + player = gEngfuncs.GetLocalPlayer(); + if ( !player ) + { + failed = true; + return; + } + + if ( first ) + { + first = false; + + if ( !HUD_SetupBenchObjects( bench, player->index - 1, origin ) ) + { + failed = true; + return; + } + } + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( player->index - 1 ); + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + + dt = gHUD.m_flTime - g_flStartTime; + if ( dt < 0 ) + return; + + frac = dt / BENCH_CYCLE_TIME; + if ( frac > 1.0 ) + { + frac = frac - (float)(int)frac; + } + + frac2 = dt /BENCH_INNER_CYCLE_TIME; + if ( frac2 > 1.0 ) + { + frac2 = frac2 - (float)(int)frac2; + } + + // Determine forward vector + AngleVectors ( vec3_origin, forward, right, up ); + + centerspot = origin; + centerspot[2] -= 512; + + gEngfuncs.pEventAPI->EV_PlayerTrace( (float *)&origin, (float *)¢erspot, PM_NORMAL, -1, &tr ); + + centerspot = tr.endpos; + centerspot[2] += BENCH_BALLHEIGHT; + + // Move center out from here + centerspot = centerspot + BENCH_VIEW_OFFSET * forward; + + for ( i = 0; i < NUM_BENCH_OBJ; i++ ) + { + int j; + float jitter = 0.0; + float jfrac; + float offset; + float ofs_radius = 5.0; + + vec3_t ang; + offset = ( float ) i / (float) ( NUM_BENCH_OBJ - 1 ); + + ang[ 0 ] = 0; + ang[ 2 ] = 0; + ang[ 1 ] = BENCH_SWEEP * offset + frac * 360.0; + // normalize + NormalizeVector( ang ); + + // Determine forward vector + AngleVectors ( ang, forward, right, up ); + + // Get a far point for ray trace + farpoint = centerspot + ( BENCH_RADIUS + ofs_radius * sin( BENCH_SWEEP * offset + frac2 * 2 * M_PI ) ) * forward; + farpoint[2] += 10 * cos( BENCH_SWEEP * offset + frac2 * 2 * M_PI ); + + gEngfuncs.pEventAPI->EV_PlayerTrace( (float *)¢erspot, (float *)&farpoint, PM_NORMAL, -1, &tr ); + + // Add angular velocity + VectorMA( bench[ i ].curstate.angles, frametime, bench[ i ].baseline.angles, bench[ i ].curstate.angles ); + + NormalizeVector( bench[ i ].curstate.angles ); + + jfrac = ( (float)gHUD.m_Benchmark.GetObjects() / (float)NUM_BENCH_OBJ ); + + // Adjust velocity + //bench[ i ].curstate.velocity[ 2 ] -= bench[ i ].curstate.gravity * frametime * 800; + + /* + // Did we hit something? + if ( tr.fraction != 1.0 && !tr.inwater ) + { + float damp; + float proj; + vec3_t traceNormal; + int j; + + traceNormal = tr.plane.normal; + + damp = 0.9; + + // Reflect velocity + if ( damp != 0 ) + { + proj = DotProduct( bench[ i ].curstate.velocity, traceNormal ); + VectorMA( bench[ i ].curstate.velocity, -proj*2, traceNormal, bench[ i ].curstate.velocity ); + // Reflect rotation (fake) + + for ( j = 0 ; j < 3; j++ ) + { + if ( bench[ i ].curstate.velocity[ j ] > 1000.0 ) + { + bench[ i ].curstate.velocity[ j ] = 1000.0; + } + else if ( bench[ i ].curstate.velocity[ j ] < -1000.0 ) + { + bench[ i ].curstate.velocity[ j ] = -1000.0; + } + } + + bench[ i ].baseline.angles[1] = -bench[ i ].baseline.angles[1]; + + VectorScale( bench[ i ].curstate.velocity, damp, bench[ i ].curstate.velocity ); + } + } + */ + + if ( i == ( NUM_BENCH_OBJ - 1 ) ) + { + g_aimorg = tr.endpos; + } + + if ( Bench_GetPowerPlay() ) + { + jitter = 0.5; + } + else + { + jitter = 8.0; + } + + jitter *= jfrac; + + for ( j = 0; j < 2; j++ ) + { + tr.endpos[ j ] += gEngfuncs.pfnRandomFloat( -jitter, jitter ); + } + + // Add to visedicts list for rendering + // Move dot to trace endpoint + bench[ i ].origin = tr.endpos; + bench[ i ].curstate.origin = bench[ i ].origin; + bench[ i ].angles = bench[ i ].curstate.angles; + + // Force no interpolation, etc., probably not necessary + bench[ i ].prevstate = bench[ i ].curstate; + + if ( ( NUM_BENCH_OBJ - i - 1 ) < gHUD.m_Benchmark.GetObjects() ) + { + if ( bench[ i ].curstate.renderamt == 255 ) + { + bench[i].curstate.rendermode = kRenderNormal; + } + else + { + bench[i].curstate.renderamt += BLEND_IN_SPEED * frametime; + bench[i].curstate.renderamt = min( 255, bench[i].curstate.renderamt ); + bench[i].curstate.rendermode = kRenderTransAlpha; + } + + gEngfuncs.CL_CreateVisibleEntity( ET_NORMAL, &bench[ i ] ); + } + } + + gEngfuncs.pEventAPI->EV_PopPMStates(); +} + +void Bench_AddObjects( void ) +{ + if ( Bench_GetDotAdded() ) + { + Bench_SpotPosition( g_dotorg, g_aimorg ); + Bench_SetDotAdded( 0 ); + } + + if ( Bench_InStage( SECOND_STAGE ) ) + { + HUD_CreateBenchObjects( v_origin ); + } +} + + +static vec3_t v_stochastic; + +void Bench_SetViewAngles( int recalc_wander, float *viewangles, float frametime, struct usercmd_s *cmd ) +{ + if ( !Bench_Active() ) + return; + + int i; + vec3_t lookdir; + + // Clear stochastic offset between runs + if ( Bench_InStage( FIRST_STAGE ) ) + { + VectorCopy( vec3_origin, v_stochastic ); + } + + if ( Bench_InStage( SECOND_STAGE ) || Bench_InStage( THIRD_STAGE ) ) + { + VectorSubtract( g_aimorg, v_origin, lookdir ); + VectorNormalize( lookdir ); + VectorAngles( (float *)&lookdir, viewangles ); + + viewangles[0] = -viewangles[0]; + + /* + if ( recalc_wander ) + { + float fmag = 2.0; + if ( Bench_GetPowerPlay() ) + { + fmag = 10.0; + } + + for ( i = 0; i < 2; i++ ) + { + v_stochastic[ i ] += frametime * gEngfuncs.pfnRandomFloat( -fmag, fmag ); + v_stochastic[ i ] = max( -15.0, v_stochastic[ i ] ); + v_stochastic[ i ] = min( 15.0, v_stochastic[ i ] ); + } + + v_stochastic[ 2 ] = 0.0; + } + */ + + VectorAdd( viewangles, v_stochastic, viewangles ); + + for ( i = 0; i < 3; i++ ) + { + if ( viewangles[ i ] > 180 ) + viewangles[ i ] -= 360; + if ( viewangles[ i ] < -180 ) + viewangles[ i ] += 360; + } + } + else + { + VectorCopy( vec3_origin, viewangles ) + + if ( Bench_InStage( FIRST_STAGE ) ) + { + viewangles[ 1 ] = -90; + } + } + + if ( cmd ) + { + if ( Bench_InStage( THIRD_STAGE ) ) + { + cmd->buttons = IN_ATTACK; + } + else + { + cmd->buttons = 0; + } + } +} + +void Bench_SetViewOrigin( float *vieworigin, float frametime ) +{ + float dt; + float frac; + float offset_amt = BENCH_BALL_VIEWDRIFT; + float drift; + vec3_t ang, right; + vec3_t move; + + if ( !Bench_InStage( SECOND_STAGE ) ) + return; + + dt = gHUD.m_flTime - g_flStartTime; + if ( dt < 0 ) + return; + + frac = dt / BENCH_VIEW_CYCLE_TIME; + frac *= 2 * M_PI; + + drift = sin( frac ) * offset_amt; + + ang = vec3_origin; + + AngleVectors( ang, NULL, right, NULL ); + + // offset along right axis + move = right * drift; + + VectorAdd( vieworigin, move, vieworigin ); +} + diff --git a/cl_dll/hud_benchtrace.cpp b/cl_dll/hud_benchtrace.cpp new file mode 100644 index 0000000..c1f9ec6 --- /dev/null +++ b/cl_dll/hud_benchtrace.cpp @@ -0,0 +1,234 @@ +// hud_benchtrace.cpp +// Functions for spawning a thread to get a hopcount to a particular ip address and returning the result in a specified +// variable + +#ifdef _WIN32 +#include +#else +#include "port.h" +#include +#endif + +// For tracking the trace threads +typedef struct +{ + // Inputs + char server[ 256 ]; + + // Outputs + int *p_nresults; + int *p_ndone; + + // Local variables + DWORD hThreadId; + HANDLE hThread; + HANDLE hEventDone; +} trace_params_t; + +// Static forces it to be zeroed out +static trace_params_t tp; + +// For doing the actual traceroute +struct trace_options_s +{ + unsigned char ucTTL; + unsigned char a[7]; +}; + +struct +{ + DWORD dwAddress; + unsigned long ulStatus, ulRoundTripTime; + unsigned char a[8]; + struct trace_options_s Options; +} traceReturn; + +/* +============== +Trace_GetHopCount + +Performs a synchronous hopcount on the specified server +============== +*/ +int Trace_GetHopCount( char *pServer, int nMaxHops ) +{ +#ifdef _WIN32 + HMODULE hICMP; // Handle to ICMP .dll + HANDLE hIP; // Handle to icmp session + DWORD *dwIPAddr; // remote IP Address as a DWORD + struct hostent *pHostEnt; // Name of remote host + struct trace_options_s traceOptions; // Input options + int c; // Hop counter + + // Prototypes + HANDLE ( WINAPI *pfnICMPCreateFile ) ( VOID ); + BOOL ( WINAPI *pfnICMPCloseFile ) ( HANDLE ); + DWORD (WINAPI *pfnICMPSendEcho) ( HANDLE, DWORD, LPVOID, WORD, LPVOID, LPVOID, DWORD, DWORD ); + + hICMP = ::LoadLibrary( "ICMP.DLL" ); + + pfnICMPCreateFile = ( HANDLE ( WINAPI *)(VOID ) )::GetProcAddress( hICMP,"IcmpCreateFile"); + pfnICMPCloseFile = ( BOOL ( WINAPI *) ( HANDLE ) )::GetProcAddress( hICMP,"IcmpCloseHandle"); + pfnICMPSendEcho = ( DWORD ( WINAPI * ) ( HANDLE, DWORD, LPVOID, WORD, LPVOID, LPVOID, DWORD,DWORD ) )::GetProcAddress( hICMP,"IcmpSendEcho" ); + + if ( !pfnICMPCreateFile || + !pfnICMPCloseFile || + !pfnICMPSendEcho ) + { + return -1; + } + + hIP = pfnICMPCreateFile(); + if ( !hIP ) + { + return -1; + } + + // DNS lookup on remote host + pHostEnt = gethostbyname( pServer ); + if ( !pHostEnt ) + { + return -1; + } + + // Take first IP address returned + dwIPAddr = ( DWORD * )( *pHostEnt->h_addr_list ); + + // Fixme: If not tracing, can use a "binary search" method to do the trace route + for ( c = 1; c <= nMaxHops ; c++) + { + // Set TTL correctly + traceOptions.ucTTL = (unsigned char)c; + + // Clear out return structure + memset( &traceReturn, 0, sizeof( traceReturn ) ); + + // Send echo request, 2000 milliseconds maximum waiting time + pfnICMPSendEcho ( hIP, *dwIPAddr, 0, 0, &traceOptions, &traceReturn, sizeof(traceReturn), 2000 ); + + // Found requrested remote address, c contains the correct hopcount + if ( traceReturn.dwAddress == *dwIPAddr ) + break; + } + + /* + // This is how you do a raw ping + npings = 1; + pfnICMPSendEcho( hIP, *dwIPAddr, 0, 0, NULL, &E, sizeof( E ), 2000 ); + *ping = (double)E.RoundTripTime / 1000.0; + */ + + // Clean up file and dll handles + pfnICMPCloseFile( hIP ); + + ::FreeLibrary( hICMP ); + + // Failure? + if ( c > nMaxHops ) + { + return -1; + } + + return c; +#else + return -1; +#endif +} + +/* +============== +Trace_Cleanup + +Destroys thread and event handle when trace is done, or when restarting a new trace +============== +*/ +void Trace_Cleanup( void ) +{ +#ifdef _WIN32 + if ( tp.hThread ) + { + TerminateThread( tp.hThread, 0 ); + CloseHandle( tp.hThread ); + tp.hThread = (HANDLE)0; + } + + if ( tp.hEventDone ) + { + CloseHandle( tp.hEventDone ); + tp.hEventDone = (HANDLE)0; + } +#endif +} + +#ifdef _WIN32 + +/* +============== +Trace_ThreadFunction + +Performs a trace, sets finish event and exits +============== +*/ +DWORD WINAPI Trace_ThreadFunction( LPVOID p ) +{ + int *results; + + results = ( int * )p; + + *results = Trace_GetHopCount( tp.server, 30 ); + SetEvent( tp.hEventDone ); + + return 0; +} +#endif + +/* +============== +Trace_StartTrace + +Create finish event, sets up data, and starts thread to do a traceroute. +============== +*/ +void Trace_StartTrace( int *results, int *finished, const char *server ) +{ +#ifdef _WIN32 + tp.p_nresults = results; + strcpy( tp.server, server ); + + *results = -1; + + Trace_Cleanup(); + + tp.hEventDone = CreateEvent( NULL, TRUE, FALSE, NULL ); + if ( !tp.hEventDone ) + { + return; + } + + tp.p_ndone = finished; + *tp.p_ndone = 0; + + tp.hThread = CreateThread( NULL, 0, Trace_ThreadFunction, results, 0, &tp.hThreadId ); +#endif +} + +/* +============== +Trace_Think + +Invoked by general frame loop on client to periodically check if the traceroute thread has completed. +============== +*/ +void Trace_Think( void ) +{ +#ifdef _WIN32 + if ( !tp.hEventDone ) + return; + + if ( WaitForSingleObject( tp.hEventDone, 0 ) == WAIT_OBJECT_0 ) + { + Trace_Cleanup(); + *tp.p_ndone = 1; + } +#endif +} \ No newline at end of file diff --git a/cl_dll/hud_benchtrace.h b/cl_dll/hud_benchtrace.h new file mode 100644 index 0000000..8609321 --- /dev/null +++ b/cl_dll/hud_benchtrace.h @@ -0,0 +1,8 @@ +#if !defined( HUD_BENCHTRACEH ) +#define HUD_BENCHTRACEH +#pragma once + +void Trace_StartTrace( int *results, int *finished, const char *pszServer ); +void Trace_Think( void ); + +#endif // !HUD_BENCHTRACEH \ No newline at end of file diff --git a/cl_dll/hud_iface.h b/cl_dll/hud_iface.h new file mode 100644 index 0000000..adf89b9 --- /dev/null +++ b/cl_dll/hud_iface.h @@ -0,0 +1,17 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( HUD_IFACEH ) +#define HUD_IFACEH +#pragma once + +typedef int (*pfnUserMsgHook)(const char *pszName, int iSize, void *pbuf); +#include "wrect.h" +#include "../engine/cdll_int.h" +extern cl_enginefunc_t gEngfuncs; + +#endif \ No newline at end of file diff --git a/cl_dll/hud_msg.cpp b/cl_dll/hud_msg.cpp new file mode 100644 index 0000000..ce72cc3 --- /dev/null +++ b/cl_dll/hud_msg.cpp @@ -0,0 +1,141 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud_msg.cpp +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" +#include "r_efx.h" + +#include "particleman.h" +extern IParticleMan *g_pParticleMan; + +#define MAX_CLIENTS 32 + +#if !defined( _TFC ) +extern BEAM *pBeam; +extern BEAM *pBeam2; +#endif + +#if defined( _TFC ) +void ClearEventList( void ); +#endif + +/// USER-DEFINED SERVER MESSAGE HANDLERS + +int CHud :: MsgFunc_ResetHUD(const char *pszName, int iSize, void *pbuf ) +{ + ASSERT( iSize == 0 ); + + // clear all hud data + HUDLIST *pList = m_pHudList; + + while ( pList ) + { + if ( pList->p ) + pList->p->Reset(); + pList = pList->pNext; + } + + // reset sensitivity + m_flMouseSensitivity = 0; + + // reset concussion effect + m_iConcussionEffect = 0; + + return 1; +} + +void CAM_ToFirstPerson(void); + +void CHud :: MsgFunc_ViewMode( const char *pszName, int iSize, void *pbuf ) +{ + CAM_ToFirstPerson(); +} + +void CHud :: MsgFunc_InitHUD( const char *pszName, int iSize, void *pbuf ) +{ + // prepare all hud data + HUDLIST *pList = m_pHudList; + + while (pList) + { + if ( pList->p ) + pList->p->InitHUDData(); + pList = pList->pNext; + } + +#if defined( _TFC ) + ClearEventList(); + + // catch up on any building events that are going on + gEngfuncs.pfnServerCmd("sendevents"); +#endif + + if ( g_pParticleMan ) + g_pParticleMan->ResetParticles(); + +#if !defined( _TFC ) + //Probably not a good place to put this. + pBeam = pBeam2 = NULL; +#endif +} + + +int CHud :: MsgFunc_GameMode(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + m_Teamplay = READ_BYTE(); + + return 1; +} + + +int CHud :: MsgFunc_Damage(const char *pszName, int iSize, void *pbuf ) +{ + int armor, blood; + Vector from; + int i; + float count; + + BEGIN_READ( pbuf, iSize ); + armor = READ_BYTE(); + blood = READ_BYTE(); + + for (i=0 ; i<3 ; i++) + from[i] = READ_COORD(); + + count = (blood * 0.5) + (armor * 0.5); + + if (count < 10) + count = 10; + + // TODO: kick viewangles, show damage visually + + return 1; +} + +int CHud :: MsgFunc_Concuss( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + m_iConcussionEffect = READ_BYTE(); + if (m_iConcussionEffect) + this->m_StatusIcons.EnableIcon("dmg_concuss",255,160,0); + else + this->m_StatusIcons.DisableIcon("dmg_concuss"); + return 1; +} diff --git a/cl_dll/hud_redraw.cpp b/cl_dll/hud_redraw.cpp new file mode 100644 index 0000000..ecba3a1 --- /dev/null +++ b/cl_dll/hud_redraw.cpp @@ -0,0 +1,346 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud_redraw.cpp +// +#include +#include "hud.h" +#include "cl_util.h" +#include "bench.h" + +#include "vgui_TeamFortressViewport.h" + +#define MAX_LOGO_FRAMES 56 + +int grgLogoFrame[MAX_LOGO_FRAMES] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 13, 13, 13, 13, 13, 12, 11, 10, 9, 8, 14, 15, + 16, 17, 18, 19, 20, 20, 20, 20, 20, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 29, 29, 29, 29, 29, 28, 27, 26, 25, 24, 30, 31 +}; + + +extern int g_iVisibleMouse; + +float HUD_GetFOV( void ); + +extern cvar_t *sensitivity; + +// Think +void CHud::Think(void) +{ + m_scrinfo.iSize = sizeof(m_scrinfo); + GetScreenInfo(&m_scrinfo); + + int newfov; + HUDLIST *pList = m_pHudList; + + while (pList) + { + if (pList->p->m_iFlags & HUD_ACTIVE) + pList->p->Think(); + pList = pList->pNext; + } + + newfov = HUD_GetFOV(); + if ( newfov == 0 ) + { + m_iFOV = default_fov->value; + } + else + { + m_iFOV = newfov; + } + + // the clients fov is actually set in the client data update section of the hud + + // Set a new sensitivity + if ( m_iFOV == default_fov->value ) + { + // reset to saved sensitivity + m_flMouseSensitivity = 0; + } + else + { + // set a new sensitivity that is proportional to the change from the FOV default + m_flMouseSensitivity = sensitivity->value * ((float)newfov / (float)default_fov->value) * CVAR_GET_FLOAT("zoom_sensitivity_ratio"); + } + + // think about default fov + if ( m_iFOV == 0 ) + { // only let players adjust up in fov, and only if they are not overriden by something else + m_iFOV = max( default_fov->value, 90 ); + } + + if ( gEngfuncs.IsSpectateOnly() ) + { + m_iFOV = gHUD.m_Spectator.GetFOV(); // default_fov->value; + } + + Bench_CheckStart(); +} + +// Redraw +// step through the local data, placing the appropriate graphics & text as appropriate +// returns 1 if they've changed, 0 otherwise +int CHud :: Redraw( float flTime, int intermission ) +{ + m_fOldTime = m_flTime; // save time of previous redraw + m_flTime = flTime; + m_flTimeDelta = (double)m_flTime - m_fOldTime; + static float m_flShotTime = 0; + + // Clock was reset, reset delta + if ( m_flTimeDelta < 0 ) + m_flTimeDelta = 0; + + // Bring up the scoreboard during intermission + if (gViewPort) + { + if ( m_iIntermission && !intermission ) + { + // Have to do this here so the scoreboard goes away + m_iIntermission = intermission; + gViewPort->HideCommandMenu(); + gViewPort->HideScoreBoard(); + gViewPort->UpdateSpectatorPanel(); + } + else if ( !m_iIntermission && intermission ) + { + m_iIntermission = intermission; + gViewPort->HideCommandMenu(); + gViewPort->HideVGUIMenu(); + gViewPort->ShowScoreBoard(); + gViewPort->UpdateSpectatorPanel(); + + // Take a screenshot if the client's got the cvar set + if ( CVAR_GET_FLOAT( "hud_takesshots" ) != 0 ) + m_flShotTime = flTime + 1.0; // Take a screenshot in a second + } + } + + if (m_flShotTime && m_flShotTime < flTime) + { + gEngfuncs.pfnClientCmd("snapshot\n"); + m_flShotTime = 0; + } + + m_iIntermission = intermission; + + // if no redrawing is necessary + // return 0; + + // draw all registered HUD elements + if ( m_pCvarDraw->value ) + { + HUDLIST *pList = m_pHudList; + + while (pList) + { + if ( !Bench_Active() ) + { + if ( !intermission ) + { + if ( (pList->p->m_iFlags & HUD_ACTIVE) && !(m_iHideHUDDisplay & HIDEHUD_ALL) ) + pList->p->Draw(flTime); + } + else + { // it's an intermission, so only draw hud elements that are set to draw during intermissions + if ( pList->p->m_iFlags & HUD_INTERMISSION ) + pList->p->Draw( flTime ); + } + } + else + { + if ( ( pList->p == &m_Benchmark ) && + ( pList->p->m_iFlags & HUD_ACTIVE ) && + !( m_iHideHUDDisplay & HIDEHUD_ALL ) ) + { + pList->p->Draw(flTime); + } + } + + pList = pList->pNext; + } + } + + // are we in demo mode? do we need to draw the logo in the top corner? + if (m_iLogo) + { + int x, y, i; + + if (m_hsprLogo == 0) + m_hsprLogo = LoadSprite("sprites/%d_logo.spr"); + + SPR_Set(m_hsprLogo, 250, 250, 250 ); + + x = SPR_Width(m_hsprLogo, 0); + x = ScreenWidth - x; + y = SPR_Height(m_hsprLogo, 0)/2; + + // Draw the logo at 20 fps + int iFrame = (int)(flTime * 20) % MAX_LOGO_FRAMES; + i = grgLogoFrame[iFrame] - 1; + + SPR_DrawAdditive(i, x, y, NULL); + } + + /* + if ( g_iVisibleMouse ) + { + void IN_GetMousePos( int *mx, int *my ); + int mx, my; + + IN_GetMousePos( &mx, &my ); + + if (m_hsprCursor == 0) + { + char sz[256]; + sprintf( sz, "sprites/cursor.spr" ); + m_hsprCursor = SPR_Load( sz ); + } + + SPR_Set(m_hsprCursor, 250, 250, 250 ); + + // Draw the logo at 20 fps + SPR_DrawAdditive( 0, mx, my, NULL ); + } + */ + + return 1; +} + +void ScaleColors( int &r, int &g, int &b, int a ) +{ + float x = (float)a / 255; + r = (int)(r * x); + g = (int)(g * x); + b = (int)(b * x); +} + +int CHud :: DrawHudString(int xpos, int ypos, int iMaxX, char *szIt, int r, int g, int b ) +{ + return xpos + gEngfuncs.pfnDrawString( xpos, ypos, szIt, r, g, b); +} + +int CHud :: DrawHudNumberString( int xpos, int ypos, int iMinX, int iNumber, int r, int g, int b ) +{ + char szString[32]; + sprintf( szString, "%d", iNumber ); + return DrawHudStringReverse( xpos, ypos, iMinX, szString, r, g, b ); + +} + +// draws a string from right to left (right-aligned) +int CHud :: DrawHudStringReverse( int xpos, int ypos, int iMinX, char *szString, int r, int g, int b ) +{ + return xpos - gEngfuncs.pfnDrawStringReverse( xpos, ypos, szString, r, g, b); +} + +int CHud :: DrawHudNumber( int x, int y, int iFlags, int iNumber, int r, int g, int b) +{ + int iWidth = GetSpriteRect(m_HUD_number_0).right - GetSpriteRect(m_HUD_number_0).left; + int k; + + if (iNumber > 0) + { + // SPR_Draw 100's + if (iNumber >= 100) + { + k = iNumber/100; + SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); + SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + x += iWidth; + } + else if (iFlags & (DHN_3DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + // SPR_Draw 10's + if (iNumber >= 10) + { + k = (iNumber % 100)/10; + SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); + SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + x += iWidth; + } + else if (iFlags & (DHN_3DIGITS | DHN_2DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + // SPR_Draw ones + k = iNumber % 10; + SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); + SPR_DrawAdditive(0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + x += iWidth; + } + else if (iFlags & DHN_DRAWZERO) + { + SPR_Set(GetSprite(m_HUD_number_0), r, g, b ); + + // SPR_Draw 100's + if (iFlags & (DHN_3DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + if (iFlags & (DHN_3DIGITS | DHN_2DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + // SPR_Draw ones + + SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0)); + x += iWidth; + } + + return x; +} + + +int CHud::GetNumWidth( int iNumber, int iFlags ) +{ + if (iFlags & (DHN_3DIGITS)) + return 3; + + if (iFlags & (DHN_2DIGITS)) + return 2; + + if (iNumber <= 0) + { + if (iFlags & (DHN_DRAWZERO)) + return 1; + else + return 0; + } + + if (iNumber < 10) + return 1; + + if (iNumber < 100) + return 2; + + return 3; + +} + + diff --git a/cl_dll/hud_servers.cpp b/cl_dll/hud_servers.cpp new file mode 100644 index 0000000..5b359ac --- /dev/null +++ b/cl_dll/hud_servers.cpp @@ -0,0 +1,1234 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// hud_servers.cpp +#include "hud.h" +#include "cl_util.h" +#include "hud_servers_priv.h" +#include "hud_servers.h" +#include "net_api.h" +#include +#ifdef _WIN32 +#include +#else +#define __cdecl +#include +#endif +static int context_id; + +// Default master server address in case we can't read any from valvecomm.lst file +#define VALVE_MASTER_ADDRESS "half-life.east.won.net" +#define PORT_MASTER 27010 +#define PORT_SERVER 27015 + +// File where we really should look for master servers +#define MASTER_PARSE_FILE "valvecomm.lst" + +#define MAX_QUERIES 20 + +#define NET_API gEngfuncs.pNetAPI + +static CHudServers *g_pServers = NULL; + +/* +=================== +ListResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK ListResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->ListResponse( response ); + } +} + +/* +=================== +ServerResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK ServerResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->ServerResponse( response ); + } +} + +/* +=================== +PingResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK PingResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->PingResponse( response ); + } +} + +/* +=================== +RulesResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK RulesResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->RulesResponse( response ); + } +} +/* +=================== +PlayersResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK PlayersResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->PlayersResponse( response ); + } +} +/* +=================== +ListResponse + +=================== +*/ +void CHudServers::ListResponse( struct net_response_s *response ) +{ + request_t *list; + request_t *p; + int c = 0; + + if ( !( response->error == NET_SUCCESS ) ) + return; + + if ( response->type != NETAPI_REQUEST_SERVERLIST ) + return; + + if ( response->response ) + { + list = ( request_t * ) response->response; + while ( list ) + { + c++; + + //if ( c < 40 ) + { + // Copy from parsed stuff + p = new request_t; + p->context = -1; + p->remote_address = list->remote_address; + p->next = m_pServerList; + m_pServerList = p; + } + + // Move on + list = list->next; + } + } + + gEngfuncs.Con_Printf( "got list\n" ); + + m_nQuerying = 1; + m_nActiveQueries = 0; +} + +/* +=================== +ServerResponse + +=================== +*/ +void CHudServers::ServerResponse( struct net_response_s *response ) +{ + char *szresponse; + request_t *p; + server_t *browser; + int len; + char sz[ 32 ]; + + // Remove from active list + p = FindRequest( response->context, m_pActiveList ); + if ( p ) + { + RemoveServerFromList( &m_pActiveList, p ); + m_nActiveQueries--; + } + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_DETAILS: + if ( response->response ) + { + szresponse = (char *)response->response; + len = strlen( szresponse ) + 100 + 1; + sprintf( sz, "%i", (int)( 1000.0 * response->ping ) ); + + browser = new server_t; + browser->remote_address = response->remote_address; + browser->info = new char[ len ]; + browser->ping = (int)( 1000.0 * response->ping ); + strcpy( browser->info, szresponse ); + + NET_API->SetValueForKey( browser->info, "address", gEngfuncs.pNetAPI->AdrToString( &response->remote_address ), len ); + NET_API->SetValueForKey( browser->info, "ping", sz, len ); + + AddServer( &m_pServers, browser ); + } + break; + default: + break; + } +} + +/* +=================== +PingResponse + +=================== +*/ +void CHudServers::PingResponse( struct net_response_s *response ) +{ + char sz[ 32 ]; + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_PING: + sprintf( sz, "%.2f", 1000.0 * response->ping ); + + gEngfuncs.Con_Printf( "ping == %s\n", sz ); + break; + default: + break; + } +} + +/* +=================== +RulesResponse + +=================== +*/ +void CHudServers::RulesResponse( struct net_response_s *response ) +{ + char *szresponse; + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_RULES: + if ( response->response ) + { + szresponse = (char *)response->response; + + gEngfuncs.Con_Printf( "rules %s\n", szresponse ); + } + break; + default: + break; + } +} + +/* +=================== +PlayersResponse + +=================== +*/ +void CHudServers::PlayersResponse( struct net_response_s *response ) +{ + char *szresponse; + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_PLAYERS: + if ( response->response ) + { + szresponse = (char *)response->response; + + gEngfuncs.Con_Printf( "players %s\n", szresponse ); + } + break; + default: + break; + } +} + +/* +=================== +CompareServers + +Return 1 if p1 is "less than" p2, 0 otherwise +=================== +*/ +int CHudServers::CompareServers( server_t *p1, server_t *p2 ) +{ + const char *n1, *n2; + + if ( p1->ping < p2->ping ) + return 1; + + if ( p1->ping == p2->ping ) + { + // Pings equal, sort by second key: hostname + if ( p1->info && p2->info ) + { + n1 = NET_API->ValueForKey( p1->info, "hostname" ); + n2 = NET_API->ValueForKey( p2->info, "hostname" ); + + if ( n1 && n2 ) + { + if ( stricmp( n1, n2 ) < 0 ) + return 1; + } + } + } + + return 0; +} + +/* +=================== +AddServer + +=================== +*/ +void CHudServers::AddServer( server_t **ppList, server_t *p ) +{ +server_t *list; + + if ( !ppList || ! p ) + return; + + m_nServerCount++; + + // What sort key? Ping? + list = *ppList; + + // Head of list? + if ( !list ) + { + p->next = NULL; + *ppList = p; + return; + } + + // Put on head of list + if ( CompareServers( p, list ) ) + { + p->next = *ppList; + *ppList = p; + } + else + { + while ( list->next ) + { + // Insert before list next + if ( CompareServers( p, list->next ) ) + { + p->next = list->next->next; + list->next = p; + return; + } + + list = list->next; + } + + // Just add at end + p->next = NULL; + list->next = p; + } +} + +/* +=================== +Think + +=================== +*/ +void CHudServers::Think( double time ) +{ + m_fElapsed += time; + + if ( !m_nRequesting ) + return; + + if ( !m_nQuerying ) + return; + + QueryThink(); + + if ( ServerListSize() > 0 ) + return; + + m_dStarted = 0.0; + m_nRequesting = 0; + m_nDone = 0; + m_nQuerying = 0; + m_nActiveQueries = 0; +} + +/* +=================== +QueryThink + +=================== +*/ +void CHudServers::QueryThink( void ) +{ + request_t *p; + + if ( !m_nRequesting || m_nDone ) + return; + + if ( !m_nQuerying ) + return; + + if ( m_nActiveQueries > MAX_QUERIES ) + return; + + // Nothing left + if ( !m_pServerList ) + return; + + while ( 1 ) + { + p = m_pServerList; + + // No more in list? + if ( !p ) + break; + + // Move to next + m_pServerList = m_pServerList->next; + + // Setup context_id + p->context = context_id; + + // Start up query on this one + NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, 0, 2.0, &p->remote_address, ::ServerResponse ); + + // Increment active list + m_nActiveQueries++; + + // Add to active list + p->next = m_pActiveList; + m_pActiveList = p; + + // Too many active? + if ( m_nActiveQueries > MAX_QUERIES ) + break; + } +} + +/* +================== +ServerListSize + +# of servers in active query and in pending to be queried lists +================== +*/ +int CHudServers::ServerListSize( void ) +{ + int c = 0; + request_t *p; + + p = m_pServerList; + while ( p ) + { + c++; + p = p->next; + } + + p = m_pActiveList; + while ( p ) + { + c++; + p = p->next; + } + + return c; +} + +/* +=================== +FindRequest + +Look up a request by context id +=================== +*/ +CHudServers::request_t *CHudServers::FindRequest( int context, request_t *pList ) +{ + request_t *p; + p = pList; + while ( p ) + { + if ( context == p->context ) + return p; + + p = p->next; + } + return NULL; +} + +/* +=================== +RemoveServerFromList + +Remote, but don't delete, item from *ppList +=================== +*/ +void CHudServers::RemoveServerFromList( request_t **ppList, request_t *item ) +{ + request_t *p, *n; + request_t *newlist = NULL; + + if ( !ppList ) + return; + + p = *ppList; + while ( p ) + { + n = p->next; + if ( p != item ) + { + p->next = newlist; + newlist = p; + } + p = n; + } + *ppList = newlist; +} + +/* +=================== +ClearRequestList + +=================== +*/ +void CHudServers::ClearRequestList( request_t **ppList ) +{ + request_t *p, *n; + + if ( !ppList ) + return; + + p = *ppList; + while ( p ) + { + n = p->next; + delete p; + p = n; + } + *ppList = NULL; +} + +/* +=================== +ClearServerList + +=================== +*/ +void CHudServers::ClearServerList( server_t **ppList ) +{ + server_t *p, *n; + + if ( !ppList ) + return; + + p = *ppList; + while ( p ) + { + n = p->next; + delete[] p->info; + delete p; + p = n; + } + *ppList = NULL; +} + +int CompareField( CHudServers::server_t *p1, CHudServers::server_t *p2, const char *fieldname, int iSortOrder ) +{ + const char *sz1, *sz2; + float fv1, fv2; + + sz1 = NET_API->ValueForKey( p1->info, fieldname ); + sz2 = NET_API->ValueForKey( p2->info, fieldname ); + + fv1 = atof( sz1 ); + fv2 = atof( sz2 ); + + if ( fv1 && fv2 ) + { + if ( fv1 > fv2 ) + return iSortOrder; + else if ( fv1 < fv2 ) + return -iSortOrder; + else + return 0; + } + + // String compare + return stricmp( sz1, sz2 ); +} + +int ServerListCompareFunc( CHudServers::server_t *p1, CHudServers::server_t *p2, const char *fieldname ) +{ + if (!p1 || !p2) // No meaningful comparison + return 0; + + int iSortOrder = 1; + + int retval = 0; + + retval = CompareField( p1, p2, fieldname, iSortOrder ); + + return retval; +} + +static char g_fieldname[ 256 ]; +int __cdecl FnServerCompare(const void *elem1, const void *elem2 ) +{ + CHudServers::server_t *list1, *list2; + + list1 = *(CHudServers::server_t **)elem1; + list2 = *(CHudServers::server_t **)elem2; + + return ServerListCompareFunc( list1, list2, g_fieldname ); +} + +void CHudServers::SortServers( const char *fieldname ) +{ + server_t *p; + // Create a list + if ( !m_pServers ) + return; + + strcpy( g_fieldname, fieldname ); + + int i; + int c = 0; + + p = m_pServers; + while ( p ) + { + c++; + p = p->next; + } + + server_t **pSortArray; + + pSortArray = new server_t *[ c ]; + memset( pSortArray, 0, c * sizeof( server_t * ) ); + + // Now copy the list into the pSortArray: + p = m_pServers; + i = 0; + while ( p ) + { + pSortArray[ i++ ] = p; + p = p->next; + } + + // Now do that actual sorting. + size_t nCount = c; + size_t nSize = sizeof( server_t * ); + + qsort( + pSortArray, + (size_t)nCount, + (size_t)nSize, + FnServerCompare + ); + + // Now rebuild the list. + m_pServers = pSortArray[0]; + for ( i = 0; i < c - 1; i++ ) + { + pSortArray[ i ]->next = pSortArray[ i + 1 ]; + } + pSortArray[ c - 1 ]->next = NULL; + + // Clean Up. + delete[] pSortArray; +} + +/* +=================== +GetServer + +Return particular server +=================== +*/ +CHudServers::server_t *CHudServers::GetServer( int server ) +{ + int c = 0; + server_t *p; + + p = m_pServers; + while ( p ) + { + if ( c == server ) + return p; + + c++; + p = p->next; + } + return NULL; +} + +/* +=================== +GetServerInfo + +Return info ( key/value ) string for particular server +=================== +*/ +char *CHudServers::GetServerInfo( int server ) +{ + server_t *p = GetServer( server ); + if ( p ) + { + return p->info; + } + return NULL; +} + +/* +=================== +CancelRequest + +Kill all pending requests in engine +=================== +*/ +void CHudServers::CancelRequest( void ) +{ + m_nRequesting = 0; + m_nQuerying = 0; + m_nDone = 1; + + NET_API->CancelAllRequests(); +} + +/* +================== +LoadMasterAddresses + +Loads the master server addresses from file and into the passed in array +================== +*/ +int CHudServers::LoadMasterAddresses( int maxservers, int *count, netadr_t *padr ) +{ + int i; + char szMaster[ 256 ]; + char szMasterFile[256]; + char *pbuffer = NULL; + char *pstart = NULL ; + netadr_t adr; + char szAdr[64]; + int nPort; + int nCount = 0; + bool bIgnore; + int nDefaultPort; + + // Assume default master and master file + strcpy( szMaster, VALVE_MASTER_ADDRESS ); // IP:PORT string + strcpy( szMasterFile, MASTER_PARSE_FILE ); + + // See if there is a command line override + i = gEngfuncs.CheckParm( "-comm", &pstart ); + if ( i && pstart ) + { + strcpy (szMasterFile, pstart ); + } + + // Read them in from proper file + pbuffer = (char *)gEngfuncs.COM_LoadFile( szMasterFile, 5, NULL ); // Use malloc + if ( !pbuffer ) + { + goto finish_master; + } + + pstart = pbuffer; + + while ( nCount < maxservers ) + { + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if ( strlen(m_szToken) <= 0) + break; + + bIgnore = true; + + if ( !stricmp( m_szToken, "Master" ) ) + { + nDefaultPort = PORT_MASTER; + bIgnore = FALSE; + } + + // Now parse all addresses between { } + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + if ( strlen(m_szToken) <= 0 ) + break; + + if ( stricmp ( m_szToken, "{" ) ) + break; + + // Parse addresses until we get to "}" + while ( nCount < maxservers ) + { + char base[256]; + + // Now parse all addresses between { } + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if (strlen(m_szToken) <= 0) + break; + + if ( !stricmp ( m_szToken, "}" ) ) + break; + + sprintf( base, "%s", m_szToken ); + + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if (strlen(m_szToken) <= 0) + break; + + if ( stricmp( m_szToken, ":" ) ) + break; + + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if (strlen(m_szToken) <= 0) + break; + + nPort = atoi ( m_szToken ); + if ( !nPort ) + nPort = nDefaultPort; + + sprintf( szAdr, "%s:%i", base, nPort ); + + // Can we resolve it any better + if ( !NET_API->StringToAdr( szAdr, &adr ) ) + bIgnore = true; + + if ( !bIgnore ) + { + padr[ nCount++ ] = adr; + } + } + } + +finish_master: + if ( !nCount ) + { + sprintf( szMaster, VALVE_MASTER_ADDRESS ); // IP:PORT string + + // Convert to netadr_t + if ( NET_API->StringToAdr ( szMaster, &adr ) ) + { + + padr[ nCount++ ] = adr; + } + } + + *count = nCount; + + if ( pbuffer ) + { + gEngfuncs.COM_FreeFile( pbuffer ); + } + + return ( nCount > 0 ) ? 1 : 0; +} + +/* +=================== +RequestList + +Request list of game servers from master +=================== +*/ +void CHudServers::RequestList( void ) +{ + m_nRequesting = 1; + m_nDone = 0; + m_dStarted = m_fElapsed; + + int count = 0; + netadr_t adr; + + if ( !LoadMasterAddresses( 1, &count, &adr ) ) + { + gEngfuncs.Con_DPrintf( "SendRequest: Unable to read master server addresses\n" ); + return; + } + + ClearRequestList( &m_pActiveList ); + ClearRequestList( &m_pServerList ); + ClearServerList( &m_pServers ); + + m_nServerCount = 0; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Kill off left overs if any + NET_API->CancelAllRequests(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_SERVERLIST, 0, 5.0, &adr, ::ListResponse ); +} + +void CHudServers::RequestBroadcastList( int clearpending ) +{ + m_nRequesting = 1; + m_nDone = 0; + m_dStarted = m_fElapsed; + + netadr_t adr; + memset( &adr, 0, sizeof( adr ) ); + + if ( clearpending ) + { + ClearRequestList( &m_pActiveList ); + ClearRequestList( &m_pServerList ); + ClearServerList( &m_pServers ); + + m_nServerCount = 0; + } + + // Make sure to byte swap server if necessary ( using "host" to "net" conversion + adr.port = htons( PORT_SERVER ); + + // Make sure networking system has started. + NET_API->InitNetworking(); + + if ( clearpending ) + { + // Kill off left overs if any + NET_API->CancelAllRequests(); + } + + adr.type = NA_BROADCAST; + + // Request Servers from LAN via IP + NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, FNETAPI_MULTIPLE_RESPONSE, 5.0, &adr, ::ServerResponse ); + + adr.type = NA_BROADCAST_IPX; + + // Request Servers from LAN via IPX ( if supported ) + NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, FNETAPI_MULTIPLE_RESPONSE, 5.0, &adr, ::ServerResponse ); +} + +void CHudServers::ServerPing( int server ) +{ + server_t *p; + + p = GetServer( server ); + if ( !p ) + return; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_PING, 0, 5.0, &p->remote_address, ::PingResponse ); +} + +void CHudServers::ServerRules( int server ) +{ + server_t *p; + + p = GetServer( server ); + if ( !p ) + return; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_RULES, 0, 5.0, &p->remote_address, ::RulesResponse ); +} + +void CHudServers::ServerPlayers( int server ) +{ + server_t *p; + + p = GetServer( server ); + if ( !p ) + return; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_PLAYERS, 0, 5.0, &p->remote_address, ::PlayersResponse ); +} + +int CHudServers::isQuerying() +{ + return m_nRequesting ? 1 : 0; +} + + +/* +=================== +GetServerCount + +Return number of servers in browser list +=================== +*/ +int CHudServers::GetServerCount( void ) +{ + return m_nServerCount; +} + +/* +=================== +CHudServers + +=================== +*/ +CHudServers::CHudServers( void ) +{ + m_nRequesting = 0; + m_dStarted = 0.0; + m_nDone = 0; + m_pServerList = NULL; + m_pServers = NULL; + m_pActiveList = NULL; + m_nQuerying = 0; + m_nActiveQueries = 0; + + m_fElapsed = 0.0; + + + m_pPingRequest = NULL; + m_pRulesRequest = NULL; + m_pPlayersRequest = NULL; +} + +/* +=================== +~CHudServers + +=================== +*/ +CHudServers::~CHudServers( void ) +{ + ClearRequestList( &m_pActiveList ); + ClearRequestList( &m_pServerList ); + ClearServerList( &m_pServers ); + + m_nServerCount = 0; + + if ( m_pPingRequest ) + { + delete m_pPingRequest; + m_pPingRequest = NULL; + + } + + if ( m_pRulesRequest ) + { + delete m_pRulesRequest; + m_pRulesRequest = NULL; + } + + if ( m_pPlayersRequest ) + { + delete m_pPlayersRequest; + m_pPlayersRequest = NULL; + } +} + +/////////////////////////////// +// +// PUBLIC APIs +// +/////////////////////////////// + +/* +=================== +ServersGetCount + +=================== +*/ +int ServersGetCount( void ) +{ + if ( g_pServers ) + { + return g_pServers->GetServerCount(); + } + return 0; +} + +int ServersIsQuerying( void ) +{ + if ( g_pServers ) + { + return g_pServers->isQuerying(); + } + return 0; +} + +/* +=================== +ServersGetInfo + +=================== +*/ +const char *ServersGetInfo( int server ) +{ + if ( g_pServers ) + { + return g_pServers->GetServerInfo( server ); + } + + return NULL; +} + +void SortServers( const char *fieldname ) +{ + if ( g_pServers ) + { + g_pServers->SortServers( fieldname ); + } +} + +/* +=================== +ServersShutdown + +=================== +*/ +void ServersShutdown( void ) +{ + if ( g_pServers ) + { + delete g_pServers; + g_pServers = NULL; + } +} + +/* +=================== +ServersInit + +=================== +*/ +void ServersInit( void ) +{ + // Kill any previous instance + ServersShutdown(); + + g_pServers = new CHudServers(); +} + +/* +=================== +ServersThink + +=================== +*/ +void ServersThink( double time ) +{ + if ( g_pServers ) + { + g_pServers->Think( time ); + } +} + +/* +=================== +ServersCancel + +=================== +*/ +void ServersCancel( void ) +{ + if ( g_pServers ) + { + g_pServers->CancelRequest(); + } +} + +// Requests +/* +=================== +ServersList + +=================== +*/ +void ServersList( void ) +{ + if ( g_pServers ) + { + g_pServers->RequestList(); + } +} + +void BroadcastServersList( int clearpending ) +{ + if ( g_pServers ) + { + g_pServers->RequestBroadcastList( clearpending ); + } +} + +void ServerPing( int server ) +{ + if ( g_pServers ) + { + g_pServers->ServerPing( server ); + } +} + +void ServerRules( int server ) +{ + if ( g_pServers ) + { + g_pServers->ServerRules( server ); + } +} + +void ServerPlayers( int server ) +{ + if ( g_pServers ) + { + g_pServers->ServerPlayers( server ); + } +} diff --git a/cl_dll/hud_servers.h b/cl_dll/hud_servers.h new file mode 100644 index 0000000..01e9442 --- /dev/null +++ b/cl_dll/hud_servers.h @@ -0,0 +1,41 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( HUD_SERVERSH ) +#define HUD_SERVERSH +#pragma once + +#define NET_CALLBACK /* */ + +// Dispatchers +void NET_CALLBACK ListResponse( struct net_response_s *response ); +void NET_CALLBACK ServerResponse( struct net_response_s *response ); +void NET_CALLBACK PingResponse( struct net_response_s *response ); +void NET_CALLBACK RulesResponse( struct net_response_s *response ); +void NET_CALLBACK PlayersResponse( struct net_response_s *response ); + +void ServersInit( void ); +void ServersShutdown( void ); +void ServersThink( double time ); +void ServersCancel( void ); + +// Get list and get server info from each +void ServersList( void ); + +// Query for IP / IPX LAN servers +void BroadcastServersList( int clearpending ); + +void ServerPing( int server ); +void ServerRules( int server ); +void ServerPlayers( int server ); + +int ServersGetCount( void ); +const char *ServersGetInfo( int server ); +int ServersIsQuerying( void ); +void SortServers( const char *fieldname ); + +#endif // HUD_SERVERSH \ No newline at end of file diff --git a/cl_dll/hud_servers_priv.h b/cl_dll/hud_servers_priv.h new file mode 100644 index 0000000..73692f4 --- /dev/null +++ b/cl_dll/hud_servers_priv.h @@ -0,0 +1,98 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( HUD_SERVERS_PRIVH ) +#define HUD_SERVERS_PRIVH +#pragma once + +#include "netadr.h" + +class CHudServers +{ +public: + typedef struct request_s + { + struct request_s *next; + netadr_t remote_address; + int context; + } request_t; + + typedef struct server_s + { + struct server_s *next; + netadr_t remote_address; + char *info; + int ping; + } server_t; + + CHudServers(); + ~CHudServers(); + + void Think( double time ); + void QueryThink( void ); + int isQuerying( void ); + + int LoadMasterAddresses( int maxservers, int *count, netadr_t *padr ); + + void RequestList( void ); + void RequestBroadcastList( int clearpending ); + + void ServerPing( int server ); + void ServerRules( int server ); + void ServerPlayers( int server ); + + void CancelRequest( void ); + + int CompareServers( server_t *p1, server_t *p2 ); + + void ClearServerList( server_t **ppList ); + void ClearRequestList( request_t **ppList ); + + void AddServer( server_t **ppList, server_t *p ); + + void RemoveServerFromList( request_t **ppList, request_t *item ); + + request_t *FindRequest( int context, request_t *pList ); + + int ServerListSize( void ); + char *GetServerInfo( int server ); + int GetServerCount( void ); + void SortServers( const char *fieldname ); + + void ListResponse( struct net_response_s *response ); + void ServerResponse( struct net_response_s *response ); + void PingResponse( struct net_response_s *response ); + void RulesResponse( struct net_response_s *response ); + void PlayersResponse( struct net_response_s *response ); +private: + + server_t *GetServer( int server ); + + // + char m_szToken[ 1024 ]; + int m_nRequesting; + int m_nDone; + + double m_dStarted; + + request_t *m_pServerList; + request_t *m_pActiveList; + + server_t *m_pServers; + + int m_nServerCount; + + int m_nActiveQueries; + int m_nQuerying; + double m_fElapsed; + + request_t *m_pPingRequest; + request_t *m_pRulesRequest; + request_t *m_pPlayersRequest; +}; + +#endif // HUD_SERVERS_PRIVH \ No newline at end of file diff --git a/cl_dll/hud_spectator.cpp b/cl_dll/hud_spectator.cpp new file mode 100644 index 0000000..6e90a66 --- /dev/null +++ b/cl_dll/hud_spectator.cpp @@ -0,0 +1,1981 @@ +//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "hud.h" +#include "cl_util.h" +#include "cl_entity.h" +#include "triangleapi.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_SpectatorPanel.h" +#include "hltv.h" + +#include "pm_shared.h" +#include "pm_defs.h" +#include "pmtrace.h" +#include "parsemsg.h" +#include "entity_types.h" + +// these are included for the math functions +#include "com_model.h" +#include "demo_api.h" +#include "event_api.h" +#include "studio_util.h" +#include "screenfade.h" + + +#pragma warning(disable: 4244) + +extern "C" int iJumpSpectator; +extern "C" float vJumpOrigin[3]; +extern "C" float vJumpAngles[3]; + + +extern void V_GetInEyePos(int entity, float * origin, float * angles ); +extern void V_ResetChaseCam(); +extern void V_GetChasePos(int target, float * cl_angles, float * origin, float * angles); +extern float * GetClientColor( int clientIndex ); + +extern vec3_t v_origin; // last view origin +extern vec3_t v_angles; // last view angle +extern vec3_t v_cl_angles; // last client/mouse angle +extern vec3_t v_sim_org; // last sim origin + +#if 0 +const char *GetSpectatorLabel ( int iMode ) +{ + switch ( iMode ) + { + case OBS_CHASE_LOCKED: + return "#OBS_CHASE_LOCKED"; + + case OBS_CHASE_FREE: + return "#OBS_CHASE_FREE"; + + case OBS_ROAMING: + return "#OBS_ROAMING"; + + case OBS_IN_EYE: + return "#OBS_IN_EYE"; + + case OBS_MAP_FREE: + return "#OBS_MAP_FREE"; + + case OBS_MAP_CHASE: + return "#OBS_MAP_CHASE"; + + case OBS_NONE: + default: + return "#OBS_NONE"; + } +} + +#endif + +void SpectatorMode(void) +{ + + + if ( gEngfuncs.Cmd_Argc() <= 1 ) + { + gEngfuncs.Con_Printf( "usage: spec_mode
[]\n" ); + return; + } + + // SetModes() will decide if command is executed on server or local + if ( gEngfuncs.Cmd_Argc() == 2 ) + gHUD.m_Spectator.SetModes( atoi( gEngfuncs.Cmd_Argv(1) ), -1 ); + else if ( gEngfuncs.Cmd_Argc() == 3 ) + gHUD.m_Spectator.SetModes( atoi( gEngfuncs.Cmd_Argv(1) ), atoi( gEngfuncs.Cmd_Argv(2) ) ); +} + +void SpectatorSpray(void) +{ + vec3_t forward; + char string[128]; + + if ( !gEngfuncs.IsSpectateOnly() ) + return; + + AngleVectors(v_angles,forward,NULL,NULL); + VectorScale(forward, 128, forward); + VectorAdd(forward, v_origin, forward); + pmtrace_t * trace = gEngfuncs.PM_TraceLine( v_origin, forward, PM_TRACELINE_PHYSENTSONLY, 2, -1 ); + if ( trace->fraction != 1.0 ) + { + sprintf(string, "drc_spray %.2f %.2f %.2f %i", + trace->endpos[0], trace->endpos[1], trace->endpos[2], trace->ent ); + gEngfuncs.pfnServerCmd(string); + } + +} +void SpectatorHelp(void) +{ + if ( gViewPort ) + { + gViewPort->ShowVGUIMenu( MENU_SPECHELP ); + } + else + { + char *text = CHudTextMessage::BufferedLocaliseTextString( "#Spec_Help_Text" ); + + if ( text ) + { + while ( *text ) + { + if ( *text != 13 ) + gEngfuncs.Con_Printf( "%c", *text ); + text++; + } + } + } +} + +void SpectatorMenu( void ) +{ + if ( gEngfuncs.Cmd_Argc() <= 1 ) + { + gEngfuncs.Con_Printf( "usage: spec_menu <0|1>\n" ); + return; + } + + gViewPort->m_pSpectatorPanel->ShowMenu( atoi( gEngfuncs.Cmd_Argv(1))!=0 ); +} + +void ToggleScores( void ) +{ + if ( gViewPort ) + { + if (gViewPort->IsScoreBoardVisible() ) + { + gViewPort->HideScoreBoard(); + } + else + { + gViewPort->ShowScoreBoard(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CHudSpectator::Init() +{ + gHUD.AddHudElem(this); + + m_iFlags |= HUD_ACTIVE; + m_flNextObserverInput = 0.0f; + m_zoomDelta = 0.0f; + m_moveDelta = 0.0f; + m_FOV = 90.0f; + m_chatEnabled = (gHUD.m_SayText.m_HUD_saytext->value!=0); + iJumpSpectator = 0; + + memset( &m_OverviewData, 0, sizeof(m_OverviewData)); + memset( &m_OverviewEntities, 0, sizeof(m_OverviewEntities)); + m_lastPrimaryObject = m_lastSecondaryObject = 0; + + gEngfuncs.pfnAddCommand ("spec_mode", SpectatorMode ); + gEngfuncs.pfnAddCommand ("spec_decal", SpectatorSpray ); + gEngfuncs.pfnAddCommand ("spec_help", SpectatorHelp ); + gEngfuncs.pfnAddCommand ("spec_menu", SpectatorMenu ); + gEngfuncs.pfnAddCommand ("togglescores", ToggleScores ); + + m_drawnames = gEngfuncs.pfnRegisterVariable("spec_drawnames","1",0); + m_drawcone = gEngfuncs.pfnRegisterVariable("spec_drawcone","1",0); + m_drawstatus = gEngfuncs.pfnRegisterVariable("spec_drawstatus","1",0); + m_autoDirector = gEngfuncs.pfnRegisterVariable("spec_autodirector","1",0); + m_pip = gEngfuncs.pfnRegisterVariable("spec_pip","1",0); + + if ( !m_drawnames || !m_drawcone || !m_drawstatus || !m_autoDirector || !m_pip) + { + gEngfuncs.Con_Printf("ERROR! Couldn't register all spectator variables.\n"); + return 0; + } + + return 1; +} + + +//----------------------------------------------------------------------------- +// UTIL_StringToVector originally from ..\dlls\util.cpp, slightly changed +//----------------------------------------------------------------------------- + +void UTIL_StringToVector( float * pVector, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + + strcpy( tempString, pString ); + pstr = pfront = tempString; + + for ( j = 0; j < 3; j++ ) + { + pVector[j] = atof( pfront ); + + while ( *pstr && *pstr != ' ' ) + pstr++; + if (!*pstr) + break; + pstr++; + pfront = pstr; + } + + if (j < 2) + { + for (j = j+1;j < 3; j++) + pVector[j] = 0; + } +} + +int UTIL_FindEntityInMap(char * name, float * origin, float * angle) +{ + int n,found = 0; + char keyname[256]; + char token[1024]; + + cl_entity_t * pEnt = gEngfuncs.GetEntityByIndex( 0 ); // get world model + + if ( !pEnt ) return 0; + + if ( !pEnt->model ) return 0; + + char * data = pEnt->model->entities; + + while (data) + { + data = gEngfuncs.COM_ParseFile(data, token); + + if ( (token[0] == '}') || (token[0]==0) ) + break; + + if (!data) + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: EOF without closing brace\n"); + return 0; + } + + if (token[0] != '{') + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: expected {\n"); + return 0; + } + + // we parse the first { now parse entities properties + + while ( 1 ) + { + // parse key + data = gEngfuncs.COM_ParseFile(data, token); + if (token[0] == '}') + break; // finish parsing this entity + + if (!data) + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: EOF without closing brace\n"); + return 0; + }; + + strcpy (keyname, token); + + // another hack to fix keynames with trailing spaces + n = strlen(keyname); + while (n && keyname[n-1] == ' ') + { + keyname[n-1] = 0; + n--; + } + + // parse value + data = gEngfuncs.COM_ParseFile(data, token); + if (!data) + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: EOF without closing brace\n"); + return 0; + }; + + if (token[0] == '}') + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: closing brace without data"); + return 0; + } + + if (!strcmp(keyname,"classname")) + { + if (!strcmp(token, name )) + { + found = 1; // thats our entity + } + }; + + if( !strcmp( keyname, "angle" ) ) + { + float y = atof( token ); + + if (y >= 0) + { + angle[0] = 0.0f; + angle[1] = y; + } + else if ((int)y == -1) + { + angle[0] = -90.0f; + angle[1] = 0.0f;; + } + else + { + angle[0] = 90.0f; + angle[1] = 0.0f; + } + + angle[2] = 0.0f; + } + + if( !strcmp( keyname, "angles" ) ) + { + UTIL_StringToVector(angle, token); + } + + if (!strcmp(keyname,"origin")) + { + UTIL_StringToVector(origin, token); + + }; + + } // while (1) + + if (found) + return 1; + + } + + return 0; // we search all entities, but didn't found the correct + +} + +//----------------------------------------------------------------------------- +// SetSpectatorStartPosition(): +// Get valid map position and 'beam' spectator to this position +//----------------------------------------------------------------------------- + +void CHudSpectator::SetSpectatorStartPosition() +{ + // search for info_player start + if ( UTIL_FindEntityInMap( "trigger_camera", m_cameraOrigin, m_cameraAngles ) ) + iJumpSpectator = 1; + + else if ( UTIL_FindEntityInMap( "info_player_start", m_cameraOrigin, m_cameraAngles ) ) + iJumpSpectator = 1; + + else if ( UTIL_FindEntityInMap( "info_player_deathmatch", m_cameraOrigin, m_cameraAngles ) ) + iJumpSpectator = 1; + + else if ( UTIL_FindEntityInMap( "info_player_coop", m_cameraOrigin, m_cameraAngles ) ) + iJumpSpectator = 1; + else + { + // jump to 0,0,0 if no better position was found + VectorCopy(vec3_origin, m_cameraOrigin); + VectorCopy(vec3_origin, m_cameraAngles); + } + + VectorCopy(m_cameraOrigin, vJumpOrigin); + VectorCopy(m_cameraAngles, vJumpAngles); + + iJumpSpectator = 1; // jump anyway +} + + +void CHudSpectator::SetCameraView( vec3_t pos, vec3_t angle, float fov) +{ + m_FOV = fov; + VectorCopy(pos, vJumpOrigin); + VectorCopy(angle, vJumpAngles); + gEngfuncs.SetViewAngles( vJumpAngles ); + iJumpSpectator = 1; // jump anyway +} + +void CHudSpectator::AddWaypoint( float time, vec3_t pos, vec3_t angle, float fov, int flags ) +{ + if ( !flags == 0 && time == 0.0f) + { + // switch instantly to this camera view + SetCameraView( pos, angle, fov ); + return; + } + + if ( m_NumWayPoints >= MAX_CAM_WAYPOINTS ) + { + gEngfuncs.Con_Printf( "Too many camera waypoints!\n" ); + return; + } + + VectorCopy( angle, m_CamPath[ m_NumWayPoints ].angle ); + VectorCopy( pos, m_CamPath[ m_NumWayPoints ].position ); + m_CamPath[ m_NumWayPoints ].flags = flags; + m_CamPath[ m_NumWayPoints ].fov = fov; + m_CamPath[ m_NumWayPoints ].time = time; + + gEngfuncs.Con_DPrintf("Added waypoint %i\n", m_NumWayPoints ); + + m_NumWayPoints++; +} + +void CHudSpectator::SetWayInterpolation(cameraWayPoint_t * prev, cameraWayPoint_t * start, cameraWayPoint_t * end, cameraWayPoint_t * next) +{ + m_WayInterpolation.SetViewAngles( start->angle, end->angle ); + + m_WayInterpolation.SetFOVs( start->fov, end->fov ); + + m_WayInterpolation.SetSmoothing( ( start->flags & DRC_FLAG_SLOWSTART ) != 0, + ( start->flags & DRC_FLAG_SLOWEND ) != 0); + + if ( prev && next ) + { + m_WayInterpolation.SetWaypoints(&prev->position, start->position, end->position, &next->position); + } + else if ( prev ) + { + m_WayInterpolation.SetWaypoints(&prev->position, start->position, end->position, NULL ); + } + else if ( next ) + { + m_WayInterpolation.SetWaypoints(NULL, start->position, end->position, &next->position ); + } + else + { + m_WayInterpolation.SetWaypoints(NULL, start->position, end->position, NULL ); + } +} + +bool CHudSpectator::GetDirectorCamera(vec3_t &position, vec3_t &angle) +{ + float now = gHUD.m_flTime; + float fov = 90.0f; + + if ( m_ChaseEntity ) + { + cl_entity_t * ent = gEngfuncs.GetEntityByIndex( m_ChaseEntity ); + + if ( ent ) + { + vec3_t vt = ent->curstate.origin; + + if ( m_ChaseEntity <= gEngfuncs.GetMaxClients() ) + { + if ( ent->curstate.solid == SOLID_NOT ) + { + vt[2]+= -8 ; // PM_DEAD_VIEWHEIGHT + } + else if (ent->curstate.usehull == 1 ) + { + vt[2]+= 12; // VEC_DUCK_VIEW; + } + else + { + vt[2]+= 28; // DEFAULT_VIEWHEIGHT + } + } + + vt = vt - position; + VectorAngles(vt, angle); + angle[0] = -angle[0]; + return true; + } + else + { + return false; + } + } + + if ( !m_IsInterpolating ) + return false; + + if ( m_WayPoint < 0 || m_WayPoint >= (m_NumWayPoints-1) ) + return false; + + cameraWayPoint_t * wp1 = &m_CamPath[m_WayPoint]; + cameraWayPoint_t * wp2 = &m_CamPath[m_WayPoint+1]; + + if ( now < wp1->time ) + return false; + + while ( now > wp2->time ) + { + // go to next waypoint, if possible + m_WayPoint++; + + if ( m_WayPoint >= (m_NumWayPoints-1) ) + { + m_IsInterpolating = false; + return false; // there is no following waypoint + } + + wp1 = wp2; + wp2 = &m_CamPath[m_WayPoint+1]; + + if ( m_WayPoint > 0 ) + { + // we have a predecessor + + if ( m_WayPoint < (m_NumWayPoints-1) ) + { + // we have also a successor + SetWayInterpolation( &m_CamPath[m_WayPoint-1], wp1, wp2, &m_CamPath[m_WayPoint+2] ); + } + else + { + SetWayInterpolation( &m_CamPath[m_WayPoint-1], wp1, wp2, NULL ); + } + } + else if ( m_WayPoint < (m_NumWayPoints-1) ) + { + // we only have a successor + SetWayInterpolation( NULL, wp1, wp2, &m_CamPath[m_WayPoint+2] ); + + } + else + { + // we have only two waypoints + SetWayInterpolation( NULL, wp1, wp2, NULL ); + } + } + + if ( wp2->time <= wp1->time ) + return false; + + float fraction = (now - wp1->time) / (wp2->time - wp1->time); + + if ( fraction < 0.0f ) + fraction = 0.0f; + else if ( fraction > 1.0f ) + fraction = 1.0f; + + m_WayInterpolation.Interpolate( fraction, position, angle, &fov ); + + // gEngfuncs.Con_Printf("Interpolate time: %.2f, fraction %.2f, point : %.2f,%.2f,%.2f\n", now, fraction, position[0], position[1], position[2] ); + + SetCameraView( position, angle, fov ); + + return true; + +} + +//----------------------------------------------------------------------------- +// Purpose: Loads new icons +//----------------------------------------------------------------------------- +int CHudSpectator::VidInit() +{ + m_hsprPlayer = SPR_Load("sprites/iplayer.spr"); + m_hsprPlayerBlue = SPR_Load("sprites/iplayerblue.spr"); + m_hsprPlayerRed = SPR_Load("sprites/iplayerred.spr"); + m_hsprPlayerDead = SPR_Load("sprites/iplayerdead.spr"); + m_hsprUnkownMap = SPR_Load("sprites/tile.spr"); + m_hsprBeam = SPR_Load("sprites/laserbeam.spr"); + m_hsprCamera = SPR_Load("sprites/camera.spr"); + m_hCrosshair = SPR_Load("sprites/crosshairs.spr"); + + m_lastPrimaryObject = m_lastSecondaryObject = 0; + m_flNextObserverInput = 0.0f; + m_lastHudMessage = 0; + m_iSpectatorNumber = 0; + iJumpSpectator = 0; + g_iUser1 = g_iUser2 = 0; + + return 1; +} + +float CHudSpectator::GetFOV() +{ + return m_FOV; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flTime - +// intermission - +//----------------------------------------------------------------------------- +int CHudSpectator::Draw(float flTime) +{ + int lx; + + char string[256]; + float * color; + + // draw only in spectator mode + if ( !g_iUser1 ) + return 0; + + // if user pressed zoom, aplly changes + if ( (m_zoomDelta != 0.0f) && ( g_iUser1 == OBS_MAP_FREE ) ) + { + m_mapZoom += m_zoomDelta; + + if ( m_mapZoom > 3.0f ) + m_mapZoom = 3.0f; + + if ( m_mapZoom < 0.5f ) + m_mapZoom = 0.5f; + } + + // if user moves in map mode, change map origin + if ( (m_moveDelta != 0.0f) && (g_iUser1 != OBS_ROAMING) ) + { + vec3_t right; + AngleVectors(v_angles, NULL, right, NULL); + VectorNormalize(right); + VectorScale(right, m_moveDelta, right ); + + VectorAdd( m_mapOrigin, right, m_mapOrigin ) + + } + + // Only draw the icon names only if map mode is in Main Mode + if ( g_iUser1 < OBS_MAP_FREE ) + return 1; + + if ( !m_drawnames->value ) + return 1; + + // make sure we have player info + gViewPort->GetAllPlayersInfo(); + + + // loop through all the players and draw additional infos to their sprites on the map + for (int i = 0; i < MAX_PLAYERS; i++) + { + + if ( m_vPlayerPos[i][2]<0 ) // marked as invisible ? + continue; + + // check if name would be in inset window + if ( m_pip->value != INSET_OFF ) + { + if ( m_vPlayerPos[i][0] > XRES( m_OverviewData.insetWindowX ) && + m_vPlayerPos[i][1] > YRES( m_OverviewData.insetWindowY ) && + m_vPlayerPos[i][0] < XRES( m_OverviewData.insetWindowX + m_OverviewData.insetWindowWidth ) && + m_vPlayerPos[i][1] < YRES( m_OverviewData.insetWindowY + m_OverviewData.insetWindowHeight) + ) continue; + } + + color = GetClientColor( i+1 ); + + // draw the players name and health underneath + sprintf(string, "%s", g_PlayerInfoList[i+1].name ); + + lx = strlen(string)*3; // 3 is avg. character length :) + + gEngfuncs.pfnDrawSetTextColor( color[0], color[1], color[2] ); + DrawConsoleString( m_vPlayerPos[i][0]-lx,m_vPlayerPos[i][1], string); + + } + + return 1; +} + + +void CHudSpectator::DirectorMessage( int iSize, void *pbuf ) +{ + float f1,f2; + char * string; + vec3_t v1,v2; + int i1,i2,i3; + + BEGIN_READ( pbuf, iSize ); + + int cmd = READ_BYTE(); + + switch ( cmd ) // director command byte + { + case DRC_CMD_START : + // now we have to do some things clientside, since the proxy doesn't know our mod + g_iPlayerClass = 0; + g_iTeamNumber = 0; + + // fake a InitHUD & ResetHUD message + gHUD.MsgFunc_InitHUD(NULL,0, NULL); + gHUD.MsgFunc_ResetHUD(NULL, 0, NULL); + + break; + + case DRC_CMD_EVENT : // old director style message + m_lastPrimaryObject = READ_WORD(); + m_lastSecondaryObject = READ_WORD(); + m_iObserverFlags = READ_LONG(); + + if ( m_autoDirector->value ) + { + if ( (g_iUser2 != m_lastPrimaryObject) || (g_iUser3 != m_lastSecondaryObject) ) + V_ResetChaseCam(); + + g_iUser2 = m_lastPrimaryObject; + g_iUser3 = m_lastSecondaryObject; + m_IsInterpolating = false; + m_ChaseEntity = 0; + } + + // gEngfuncs.Con_Printf("Director Camera: %i %i\n", firstObject, secondObject); + break; + case DRC_CMD_MODE : + if ( m_autoDirector->value ) + { + SetModes( READ_BYTE(), -1 ); + } + break; + + + case DRC_CMD_CAMERA : + v1[0] = READ_COORD(); // position + v1[1] = READ_COORD(); + v1[2] = READ_COORD(); // vJumpOrigin + + v2[0] = READ_COORD(); // view angle + v2[1] = READ_COORD(); // vJumpAngles + v2[2] = READ_COORD(); + f1 = READ_BYTE(); // fov + i1 = READ_WORD(); // target + + if ( m_autoDirector->value ) + { + SetModes( OBS_ROAMING, -1 ); + SetCameraView(v1, v2, f1); + m_ChaseEntity = i1; + } + break; + + case DRC_CMD_MESSAGE: + { + client_textmessage_t * msg = &m_HUDMessages[m_lastHudMessage]; + + msg->effect = READ_BYTE(); // effect + + UnpackRGB( (int&)msg->r1, (int&)msg->g1, (int&)msg->b1, READ_LONG() ); // color + msg->r2 = msg->r1; + msg->g2 = msg->g1; + msg->b2 = msg->b1; + msg->a2 = msg->a1 = 0xFF; // not transparent + + msg->x = READ_FLOAT(); // x pos + msg->y = READ_FLOAT(); // y pos + + msg->fadein = READ_FLOAT(); // fadein + msg->fadeout = READ_FLOAT(); // fadeout + msg->holdtime = READ_FLOAT(); // holdtime + msg->fxtime = READ_FLOAT(); // fxtime; + + strncpy( m_HUDMessageText[m_lastHudMessage], READ_STRING(), 128 ); + m_HUDMessageText[m_lastHudMessage][127]=0; // text + + msg->pMessage = m_HUDMessageText[m_lastHudMessage]; + msg->pName = "HUD_MESSAGE"; + + gHUD.m_Message.MessageAdd( msg ); + + m_lastHudMessage++; + m_lastHudMessage %= MAX_SPEC_HUD_MESSAGES; + + } + + break; + + case DRC_CMD_SOUND : + string = READ_STRING(); + f1 = READ_FLOAT(); + + // gEngfuncs.Con_Printf("DRC_CMD_FX_SOUND: %s %.2f\n", string, value ); + gEngfuncs.pEventAPI->EV_PlaySound(0, v_origin, CHAN_BODY, string, f1, ATTN_NORM, 0, PITCH_NORM ); + + break; + + case DRC_CMD_TIMESCALE : + f1 = READ_FLOAT(); // ignore this command (maybe show slowmo sign) + break; + + + + case DRC_CMD_STATUS: + READ_LONG(); // total number of spectator slots + m_iSpectatorNumber = READ_LONG(); // total number of spectator + READ_WORD(); // total number of relay proxies + + gViewPort->UpdateSpectatorPanel(); + break; + + case DRC_CMD_BANNER: + // gEngfuncs.Con_DPrintf("GUI: Banner %s\n",READ_STRING() ); // name of banner tga eg gfx/temp/7454562234563475.tga + gViewPort->m_pSpectatorPanel->m_TopBanner->LoadImage( READ_STRING() ); + gViewPort->UpdateSpectatorPanel(); + break; + + case DRC_CMD_STUFFTEXT: + EngineClientCmd( READ_STRING() ); + break; + + case DRC_CMD_CAMPATH: + v1[0] = READ_COORD(); // position + v1[1] = READ_COORD(); + v1[2] = READ_COORD(); // vJumpOrigin + + v2[0] = READ_COORD(); // view angle + v2[1] = READ_COORD(); // vJumpAngles + v2[2] = READ_COORD(); + f1 = READ_BYTE(); // FOV + i1 = READ_BYTE(); // flags + + if ( m_autoDirector->value ) + { + SetModes( OBS_ROAMING, -1 ); + SetCameraView(v1, v2, f1); + } + break; + + case DRC_CMD_WAYPOINTS : + i1 = READ_BYTE(); + m_NumWayPoints = 0; + m_WayPoint = 0; + for ( i2=0; i2value ) + { + // ignore waypoints + m_NumWayPoints = 0; + break; + } + + SetModes( OBS_ROAMING, -1 ); + + m_IsInterpolating = true; + + if ( m_NumWayPoints > 2 ) + { + SetWayInterpolation( NULL, &m_CamPath[0], &m_CamPath[1], &m_CamPath[2] ); + } + else + { + SetWayInterpolation( NULL, &m_CamPath[0], &m_CamPath[1], NULL ); + } + break; + + default : gEngfuncs.Con_DPrintf("CHudSpectator::DirectorMessage: unknown command %i.\n", cmd ); + } +} + +void CHudSpectator::FindNextPlayer(bool bReverse) +{ + // MOD AUTHORS: Modify the logic of this function if you want to restrict the observer to watching + // only a subset of the players. e.g. Make it check the target's team. + + int iStart; + cl_entity_t * pEnt = NULL; + + // if we are NOT in HLTV mode, spectator targets are set on server + if ( !gEngfuncs.IsSpectateOnly() ) + { + char cmdstring[32]; + // forward command to server + sprintf(cmdstring,"follownext %i",bReverse?1:0); + gEngfuncs.pfnServerCmd(cmdstring); + return; + } + + if ( g_iUser2 ) + iStart = g_iUser2; + else + iStart = 1; + + g_iUser2 = 0; + + int iCurrent = iStart; + + int iDir = bReverse ? -1 : 1; + + // make sure we have player info + gViewPort->GetAllPlayersInfo(); + + + do + { + iCurrent += iDir; + + // Loop through the clients + if (iCurrent > MAX_PLAYERS) + iCurrent = 1; + if (iCurrent < 1) + iCurrent = MAX_PLAYERS; + + pEnt = gEngfuncs.GetEntityByIndex( iCurrent ); + + if ( !IsActivePlayer( pEnt ) ) + continue; + + // MOD AUTHORS: Add checks on target here. + + g_iUser2 = iCurrent; + break; + + } while ( iCurrent != iStart ); + + // Did we find a target? + if ( !g_iUser2 ) + { + gEngfuncs.Con_DPrintf( "No observer targets.\n" ); + // take save camera position + VectorCopy(m_cameraOrigin, vJumpOrigin); + VectorCopy(m_cameraAngles, vJumpAngles); + } + else + { + // use new entity position for roaming + VectorCopy ( pEnt->origin, vJumpOrigin ); + VectorCopy ( pEnt->angles, vJumpAngles ); + } + + iJumpSpectator = 1; + gViewPort->MsgFunc_ResetFade( NULL, 0, NULL ); +} + + +void CHudSpectator::FindPlayer(const char *name) +{ + // MOD AUTHORS: Modify the logic of this function if you want to restrict the observer to watching + // only a subset of the players. e.g. Make it check the target's team. + + // if we are NOT in HLTV mode, spectator targets are set on server + if ( !gEngfuncs.IsSpectateOnly() ) + { + char cmdstring[32]; + // forward command to server + sprintf(cmdstring,"follow %s",name); + gEngfuncs.pfnServerCmd(cmdstring); + return; + } + + g_iUser2 = 0; + + // make sure we have player info + gViewPort->GetAllPlayersInfo(); + + cl_entity_t * pEnt = NULL; + + for (int i = 1; i < MAX_PLAYERS; i++ ) + { + + pEnt = gEngfuncs.GetEntityByIndex( i ); + + if ( !IsActivePlayer( pEnt ) ) + continue; + + if(!stricmp(g_PlayerInfoList[pEnt->index].name,name)) + { + g_iUser2 = i; + break; + } + + } + + // Did we find a target? + if ( !g_iUser2 ) + { + gEngfuncs.Con_DPrintf( "No observer targets.\n" ); + // take save camera position + VectorCopy(m_cameraOrigin, vJumpOrigin); + VectorCopy(m_cameraAngles, vJumpAngles); + } + else + { + // use new entity position for roaming + VectorCopy ( pEnt->origin, vJumpOrigin ); + VectorCopy ( pEnt->angles, vJumpAngles ); + } + + iJumpSpectator = 1; + gViewPort->MsgFunc_ResetFade( NULL, 0, NULL ); +} + +void CHudSpectator::HandleButtonsDown( int ButtonPressed ) +{ + double time = gEngfuncs.GetClientTime(); + + int newMainMode = g_iUser1; + int newInsetMode = m_pip->value; + + // gEngfuncs.Con_Printf(" HandleButtons:%i\n", ButtonPressed ); + + if ( !gViewPort ) + return; + + //Not in intermission. + if ( gHUD.m_iIntermission ) + return; + + if ( !g_iUser1 ) + return; // dont do anything if not in spectator mode + + // don't handle buttons during normal demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() && !gEngfuncs.IsSpectateOnly() ) + return; + // Slow down mouse clicks. + if ( m_flNextObserverInput > time ) + return; + + // enable spectator screen + if ( ButtonPressed & IN_DUCK ) + gViewPort->m_pSpectatorPanel->ShowMenu(!gViewPort->m_pSpectatorPanel->m_menuVisible); + + // 'Use' changes inset window mode + if ( ButtonPressed & IN_USE ) + { + newInsetMode = ToggleInset(true); + } + + // if not in HLTV mode, buttons are handled server side + if ( gEngfuncs.IsSpectateOnly() ) + { + // changing target or chase mode not in overviewmode without inset window + + // Jump changes main window modes + if ( ButtonPressed & IN_JUMP ) + { + if ( g_iUser1 == OBS_CHASE_LOCKED ) + newMainMode = OBS_CHASE_FREE; + + else if ( g_iUser1 == OBS_CHASE_FREE ) + newMainMode = OBS_IN_EYE; + + else if ( g_iUser1 == OBS_IN_EYE ) + newMainMode = OBS_ROAMING; + + else if ( g_iUser1 == OBS_ROAMING ) + newMainMode = OBS_MAP_FREE; + + else if ( g_iUser1 == OBS_MAP_FREE ) + newMainMode = OBS_MAP_CHASE; + + else + newMainMode = OBS_CHASE_FREE; // don't use OBS_CHASE_LOCKED anymore + } + + // Attack moves to the next player + if ( ButtonPressed & (IN_ATTACK | IN_ATTACK2) ) + { + FindNextPlayer( (ButtonPressed & IN_ATTACK2) ? true:false ); + + if ( g_iUser1 == OBS_ROAMING ) + { + gEngfuncs.SetViewAngles( vJumpAngles ); + iJumpSpectator = 1; + + } + // release directed mode if player wants to see another player + m_autoDirector->value = 0.0f; + } + } + + SetModes(newMainMode, newInsetMode); + + if ( g_iUser1 == OBS_MAP_FREE ) + { + if ( ButtonPressed & IN_FORWARD ) + m_zoomDelta = 0.01f; + + if ( ButtonPressed & IN_BACK ) + m_zoomDelta = -0.01f; + + if ( ButtonPressed & IN_MOVELEFT ) + m_moveDelta = -12.0f; + + if ( ButtonPressed & IN_MOVERIGHT ) + m_moveDelta = 12.0f; + } + + m_flNextObserverInput = time + 0.2; +} + +void CHudSpectator::HandleButtonsUp( int ButtonPressed ) +{ + if ( !gViewPort ) + return; + + if ( !gViewPort->m_pSpectatorPanel->isVisible() ) + return; // dont do anything if not in spectator mode + + if ( ButtonPressed & (IN_FORWARD | IN_BACK) ) + m_zoomDelta = 0.0f; + + if ( ButtonPressed & (IN_MOVELEFT | IN_MOVERIGHT) ) + m_moveDelta = 0.0f; +} + +void CHudSpectator::SetModes(int iNewMainMode, int iNewInsetMode) +{ + // if value == -1 keep old value + if ( iNewMainMode == -1 ) + iNewMainMode = g_iUser1; + + if ( iNewInsetMode == -1 ) + iNewInsetMode = m_pip->value; + + // inset mode is handled only clients side + m_pip->value = iNewInsetMode; + + if ( iNewMainMode < OBS_CHASE_LOCKED || iNewMainMode > OBS_MAP_CHASE ) + { + gEngfuncs.Con_Printf("Invalid spectator mode.\n"); + return; + } + + m_IsInterpolating = false; + m_ChaseEntity = 0; + + // main mode settings will override inset window settings + if ( iNewMainMode != g_iUser1 ) + { + // if we are NOT in HLTV mode, main spectator mode is set on server + if ( !gEngfuncs.IsSpectateOnly() ) + { + char cmdstring[32]; + // forward command to server + sprintf(cmdstring,"specmode %i",iNewMainMode ); + gEngfuncs.pfnServerCmd(cmdstring); + return; + } + + if ( !g_iUser2 && (iNewMainMode != OBS_ROAMING ) ) // make sure we have a target + { + // choose last Director object if still available + if ( IsActivePlayer( gEngfuncs.GetEntityByIndex( m_lastPrimaryObject ) ) ) + { + g_iUser2 = m_lastPrimaryObject; + g_iUser3 = m_lastSecondaryObject; + } + else + FindNextPlayer(false); // find any target + } + + switch ( iNewMainMode ) + { + case OBS_CHASE_LOCKED: g_iUser1 = OBS_CHASE_LOCKED; + break; + + case OBS_CHASE_FREE : g_iUser1 = OBS_CHASE_FREE; + break; + + case OBS_ROAMING : // jump to current vJumpOrigin/angle + g_iUser1 = OBS_ROAMING; + if ( g_iUser2 ) + { + V_GetChasePos( g_iUser2, v_cl_angles, vJumpOrigin, vJumpAngles ); + gEngfuncs.SetViewAngles( vJumpAngles ); + iJumpSpectator = 1; + } + break; + + case OBS_IN_EYE : g_iUser1 = OBS_IN_EYE; + break; + + case OBS_MAP_FREE : g_iUser1 = OBS_MAP_FREE; + // reset user values + m_mapZoom = m_OverviewData.zoom; + m_mapOrigin = m_OverviewData.origin; + break; + + case OBS_MAP_CHASE : g_iUser1 = OBS_MAP_CHASE; + // reset user values + m_mapZoom = m_OverviewData.zoom; + m_mapOrigin = m_OverviewData.origin; + break; + } + + if ( (g_iUser1 == OBS_IN_EYE) || (g_iUser1 == OBS_ROAMING) ) + { + m_crosshairRect.left = 24; + m_crosshairRect.top = 0; + m_crosshairRect.right = 48; + m_crosshairRect.bottom = 24; + + SetCrosshair( m_hCrosshair, m_crosshairRect, 255, 255, 255 ); + } + else + { + memset( &m_crosshairRect,0,sizeof(m_crosshairRect) ); + SetCrosshair( 0, m_crosshairRect, 0, 0, 0 ); + } + + gViewPort->MsgFunc_ResetFade( NULL, 0, NULL ); + + char string[128]; + sprintf(string, "#Spec_Mode%d", g_iUser1 ); + sprintf(string, "%c%s", HUD_PRINTCENTER, CHudTextMessage::BufferedLocaliseTextString( string )); + gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); + } + + gViewPort->UpdateSpectatorPanel(); + +} + +bool CHudSpectator::IsActivePlayer(cl_entity_t * ent) +{ + return ( ent && + ent->player && + ent->curstate.solid != SOLID_NOT && + ent != gEngfuncs.GetLocalPlayer() && + g_PlayerInfoList[ent->index].name != NULL + ); +} + + +bool CHudSpectator::ParseOverviewFile( ) +{ + char filename[255]; + char levelname[255]; + char token[1024]; + float height; + + char *pfile = NULL; + + memset( &m_OverviewData, 0, sizeof(m_OverviewData)); + + // fill in standrd values + m_OverviewData.insetWindowX = 4; // upper left corner + m_OverviewData.insetWindowY = 4; + m_OverviewData.insetWindowHeight = 180; + m_OverviewData.insetWindowWidth = 240; + m_OverviewData.origin[0] = 0.0f; + m_OverviewData.origin[1] = 0.0f; + m_OverviewData.origin[2] = 0.0f; + m_OverviewData.zoom = 1.0f; + m_OverviewData.layers = 0; + m_OverviewData.layersHeights[0] = 0.0f; + strcpy( m_OverviewData.map, gEngfuncs.pfnGetLevelName() ); + + if ( strlen( m_OverviewData.map ) == 0 ) + return false; // not active yet + + strcpy(levelname, m_OverviewData.map + 5); + levelname[strlen(levelname)-4] = 0; + + sprintf(filename, "overviews/%s.txt", levelname ); + + pfile = (char *)gEngfuncs.COM_LoadFile( filename, 5, NULL); + + if (!pfile) + { + gEngfuncs.Con_DPrintf("Couldn't open file %s. Using default values for overiew mode.\n", filename ); + return false; + } + + + while (true) + { + pfile = gEngfuncs.COM_ParseFile(pfile, token); + + if (!pfile) + break; + + if ( !stricmp( token, "global" ) ) + { + // parse the global data + pfile = gEngfuncs.COM_ParseFile(pfile, token); + if ( stricmp( token, "{" ) ) + { + gEngfuncs.Con_Printf("Error parsing overview file %s. (expected { )\n", filename ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); + + while (stricmp( token, "}") ) + { + if ( !stricmp( token, "zoom" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.zoom = atof( token ); + } + else if ( !stricmp( token, "origin" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile, token); + m_OverviewData.origin[0] = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.origin[1] = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile, token); + m_OverviewData.origin[2] = atof( token ); + } + else if ( !stricmp( token, "rotated" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.rotated = atoi( token ); + } + else if ( !stricmp( token, "inset" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowX = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowY = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowWidth = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowHeight = atof( token ); + + } + else + { + gEngfuncs.Con_Printf("Error parsing overview file %s. (%s unkown)\n", filename, token ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); // parse next token + + } + } + else if ( !stricmp( token, "layer" ) ) + { + // parse a layer data + + if ( m_OverviewData.layers == OVERVIEW_MAX_LAYERS ) + { + gEngfuncs.Con_Printf("Error parsing overview file %s. ( too many layers )\n", filename ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); + + + if ( stricmp( token, "{" ) ) + { + gEngfuncs.Con_Printf("Error parsing overview file %s. (expected { )\n", filename ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); + + while (stricmp( token, "}") ) + { + if ( !stricmp( token, "image" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + strcpy(m_OverviewData.layersImages[ m_OverviewData.layers ], token); + + + } + else if ( !stricmp( token, "height" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + height = atof(token); + m_OverviewData.layersHeights[ m_OverviewData.layers ] = height; + } + else + { + gEngfuncs.Con_Printf("Error parsing overview file %s. (%s unkown)\n", filename, token ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); // parse next token + } + + m_OverviewData.layers++; + + } + } + + gEngfuncs.COM_FreeFile( pfile ); + + m_mapZoom = m_OverviewData.zoom; + m_mapOrigin = m_OverviewData.origin; + + return true; + +} + +void CHudSpectator::LoadMapSprites() +{ + // right now only support for one map layer + if (m_OverviewData.layers > 0 ) + { + m_MapSprite = gEngfuncs.LoadMapSprite( m_OverviewData.layersImages[0] ); + } + else + m_MapSprite = NULL; // the standard "unkown map" sprite will be used instead +} + +void CHudSpectator::DrawOverviewLayer() +{ + float screenaspect, xs, ys, xStep, yStep, x,y,z; + int ix,iy,i,xTiles,yTiles,frame; + + qboolean hasMapImage = m_MapSprite?TRUE:FALSE; + model_t * dummySprite = (struct model_s *)gEngfuncs.GetSpritePointer( m_hsprUnkownMap); + + if ( hasMapImage) + { + i = m_MapSprite->numframes / (4*3); + i = sqrt(i); + xTiles = i*4; + yTiles = i*3; + } + else + { + xTiles = 8; + yTiles = 6; + } + + + screenaspect = 4.0f/3.0f; + + + xs = m_OverviewData.origin[0]; + ys = m_OverviewData.origin[1]; + z = ( 90.0f - v_angles[0] ) / 90.0f; + z *= m_OverviewData.layersHeights[0]; // gOverviewData.z_min - 32; + + // i = r_overviewTexture + ( layer*OVERVIEW_X_TILES*OVERVIEW_Y_TILES ); + + gEngfuncs.pTriAPI->RenderMode( kRenderTransTexture ); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); + gEngfuncs.pTriAPI->Color4f( 1.0, 1.0, 1.0, 1.0 ); + + frame = 0; + + + // rotated view ? + if ( m_OverviewData.rotated ) + { + xStep = (2*4096.0f / m_OverviewData.zoom ) / xTiles; + yStep = -(2*4096.0f / (m_OverviewData.zoom* screenaspect) ) / yTiles; + + y = ys + (4096.0f / (m_OverviewData.zoom * screenaspect)); + + for (iy = 0; iy < yTiles; iy++) + { + x = xs - (4096.0f / (m_OverviewData.zoom)); + + for (ix = 0; ix < xTiles; ix++) + { + if (hasMapImage) + gEngfuncs.pTriAPI->SpriteTexture( m_MapSprite, frame ); + else + gEngfuncs.pTriAPI->SpriteTexture( dummySprite, 0 ); + + gEngfuncs.pTriAPI->Begin( TRI_QUADS ); + gEngfuncs.pTriAPI->TexCoord2f( 0, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x, y, z); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x+xStep ,y, z); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x+xStep, y+yStep, z); + + gEngfuncs.pTriAPI->TexCoord2f( 0, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x, y+yStep, z); + gEngfuncs.pTriAPI->End(); + + frame++; + x+= xStep; + } + + y+=yStep; + } + } + else + { + xStep = -(2*4096.0f / m_OverviewData.zoom ) / xTiles; + yStep = -(2*4096.0f / (m_OverviewData.zoom* screenaspect) ) / yTiles; + + + x = xs + (4096.0f / (m_OverviewData.zoom * screenaspect )); + + + + for (ix = 0; ix < yTiles; ix++) + { + + y = ys + (4096.0f / (m_OverviewData.zoom)); + + for (iy = 0; iy < xTiles; iy++) + { + if (hasMapImage) + gEngfuncs.pTriAPI->SpriteTexture( m_MapSprite, frame ); + else + gEngfuncs.pTriAPI->SpriteTexture( dummySprite, 0 ); + + gEngfuncs.pTriAPI->Begin( TRI_QUADS ); + gEngfuncs.pTriAPI->TexCoord2f( 0, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x, y, z); + + gEngfuncs.pTriAPI->TexCoord2f( 0, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x+xStep ,y, z); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x+xStep, y+yStep, z); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x, y+yStep, z); + gEngfuncs.pTriAPI->End(); + + frame++; + + y+=yStep; + } + + x+= xStep; + + } + } +} + +void CHudSpectator::DrawOverviewEntities() +{ + int i,ir,ig,ib; + struct model_s *hSpriteModel; + vec3_t origin, angles, point, forward, right, left, up, world, screen, offset; + float x,y,z, r,g,b, sizeScale = 4.0f; + cl_entity_t * ent; + float rmatrix[3][4]; // transformation matrix + + float zScale = (90.0f - v_angles[0] ) / 90.0f; + + + z = m_OverviewData.layersHeights[0] * zScale; + // get yellow/brown HUD color + UnpackRGB(ir,ig,ib, RGB_YELLOWISH); + r = (float)ir/255.0f; + g = (float)ig/255.0f; + b = (float)ib/255.0f; + + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); + + for (i=0; i < MAX_PLAYERS; i++ ) + m_vPlayerPos[i][2] = -1; // mark as invisible + + // draw all players + for (i=0 ; i < MAX_OVERVIEW_ENTITIES ; i++) + { + if ( !m_OverviewEntities[i].hSprite ) + continue; + + hSpriteModel = (struct model_s *)gEngfuncs.GetSpritePointer( m_OverviewEntities[i].hSprite ); + ent = m_OverviewEntities[i].entity; + + gEngfuncs.pTriAPI->SpriteTexture( hSpriteModel, 0 ); + gEngfuncs.pTriAPI->RenderMode( kRenderTransTexture ); + + // see R_DrawSpriteModel + // draws players sprite + + AngleVectors(ent->angles, right, up, NULL ); + + VectorCopy(ent->origin,origin); + + gEngfuncs.pTriAPI->Begin( TRI_QUADS ); + + gEngfuncs.pTriAPI->Color4f( 1.0, 1.0, 1.0, 1.0 ); + + gEngfuncs.pTriAPI->TexCoord2f (1, 0); + VectorMA (origin, 16.0f * sizeScale, up, point); + VectorMA (point, 16.0f * sizeScale, right, point); + point[2] *= zScale; + gEngfuncs.pTriAPI->Vertex3fv (point); + + gEngfuncs.pTriAPI->TexCoord2f (0, 0); + + VectorMA (origin, 16.0f * sizeScale, up, point); + VectorMA (point, -16.0f * sizeScale, right, point); + point[2] *= zScale; + gEngfuncs.pTriAPI->Vertex3fv (point); + + gEngfuncs.pTriAPI->TexCoord2f (0,1); + VectorMA (origin, -16.0f * sizeScale, up, point); + VectorMA (point, -16.0f * sizeScale, right, point); + point[2] *= zScale; + gEngfuncs.pTriAPI->Vertex3fv (point); + + gEngfuncs.pTriAPI->TexCoord2f (1,1); + VectorMA (origin, -16.0f * sizeScale, up, point); + VectorMA (point, 16.0f * sizeScale, right, point); + point[2] *= zScale; + gEngfuncs.pTriAPI->Vertex3fv (point); + + gEngfuncs.pTriAPI->End (); + + + if ( !ent->player) + continue; + // draw line under player icons + origin[2] *= zScale; + + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + + hSpriteModel = (struct model_s *)gEngfuncs.GetSpritePointer( m_hsprBeam ); + gEngfuncs.pTriAPI->SpriteTexture( hSpriteModel, 0 ); + + gEngfuncs.pTriAPI->Color4f(r, g, b, 0.3); + + gEngfuncs.pTriAPI->Begin ( TRI_QUADS ); + gEngfuncs.pTriAPI->TexCoord2f (1, 0); + gEngfuncs.pTriAPI->Vertex3f (origin[0]+4, origin[1]+4, origin[2]-zScale); + gEngfuncs.pTriAPI->TexCoord2f (0, 0); + gEngfuncs.pTriAPI->Vertex3f (origin[0]-4, origin[1]-4, origin[2]-zScale); + gEngfuncs.pTriAPI->TexCoord2f (0, 1); + gEngfuncs.pTriAPI->Vertex3f (origin[0]-4, origin[1]-4,z); + gEngfuncs.pTriAPI->TexCoord2f (1, 1); + gEngfuncs.pTriAPI->Vertex3f (origin[0]+4, origin[1]+4,z); + gEngfuncs.pTriAPI->End (); + + gEngfuncs.pTriAPI->Begin ( TRI_QUADS ); + gEngfuncs.pTriAPI->TexCoord2f (1, 0); + gEngfuncs.pTriAPI->Vertex3f (origin[0]-4, origin[1]+4, origin[2]-zScale); + gEngfuncs.pTriAPI->TexCoord2f (0, 0); + gEngfuncs.pTriAPI->Vertex3f (origin[0]+4, origin[1]-4, origin[2]-zScale); + gEngfuncs.pTriAPI->TexCoord2f (0, 1); + gEngfuncs.pTriAPI->Vertex3f (origin[0]+4, origin[1]-4,z); + gEngfuncs.pTriAPI->TexCoord2f (1, 1); + gEngfuncs.pTriAPI->Vertex3f (origin[0]-4, origin[1]+4,z); + gEngfuncs.pTriAPI->End (); + + // calculate screen position for name and infromation in hud::draw() + if ( gEngfuncs.pTriAPI->WorldToScreen(origin,screen) ) + continue; // object is behind viewer + + screen[0] = XPROJECT(screen[0]); + screen[1] = YPROJECT(screen[1]); + screen[2] = 0.0f; + + // calculate some offset under the icon + origin[0]+=32.0f; + origin[1]+=32.0f; + + gEngfuncs.pTriAPI->WorldToScreen(origin,offset); + + offset[0] = XPROJECT(offset[0]); + offset[1] = YPROJECT(offset[1]); + offset[2] = 0.0f; + + VectorSubtract(offset, screen, offset ); + + int playerNum = ent->index - 1; + + m_vPlayerPos[playerNum][0] = screen[0]; + m_vPlayerPos[playerNum][1] = screen[1] + Length(offset); + m_vPlayerPos[playerNum][2] = 1; // mark player as visible + } + + if ( !m_pip->value || !m_drawcone->value ) + return; + + // get current camera position and angle + + if ( m_pip->value == INSET_IN_EYE || g_iUser1 == OBS_IN_EYE ) + { + V_GetInEyePos( g_iUser2, origin, angles ); + } + else if ( m_pip->value == INSET_CHASE_FREE || g_iUser1 == OBS_CHASE_FREE ) + { + V_GetChasePos( g_iUser2, v_cl_angles, origin, angles ); + } + else if ( g_iUser1 == OBS_ROAMING ) + { + VectorCopy( v_sim_org, origin ); + VectorCopy( v_cl_angles, angles ); + } + else + V_GetChasePos( g_iUser2, NULL, origin, angles ); + + + // draw camera sprite + + x = origin[0]; + y = origin[1]; + z = origin[2]; + + angles[0] = 0; // always show horizontal camera sprite + + hSpriteModel = (struct model_s *)gEngfuncs.GetSpritePointer( m_hsprCamera ); + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + gEngfuncs.pTriAPI->SpriteTexture( hSpriteModel, 0 ); + + + gEngfuncs.pTriAPI->Color4f( r, g, b, 1.0 ); + + AngleVectors(angles, forward, NULL, NULL ); + VectorScale (forward, 512.0f, forward); + + offset[0] = 0.0f; + offset[1] = 45.0f; + offset[2] = 0.0f; + + AngleMatrix(offset, rmatrix ); + VectorTransform(forward, rmatrix , right ); + + offset[1]= -45.0f; + AngleMatrix(offset, rmatrix ); + VectorTransform(forward, rmatrix , left ); + + gEngfuncs.pTriAPI->Begin (TRI_TRIANGLES); + gEngfuncs.pTriAPI->TexCoord2f( 0, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x+right[0], y+right[1], (z+right[2]) * zScale); + + gEngfuncs.pTriAPI->TexCoord2f( 0, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x, y, z * zScale); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x+left[0], y+left[1], (z+left[2]) * zScale); + gEngfuncs.pTriAPI->End (); + +} + + + +void CHudSpectator::DrawOverview() +{ + // draw only in sepctator mode + if ( !g_iUser1 ) + return; + + // Only draw the overview if Map Mode is selected for this view + if ( m_iDrawCycle == 0 && ( (g_iUser1 != OBS_MAP_FREE) && (g_iUser1 != OBS_MAP_CHASE) ) ) + return; + + if ( m_iDrawCycle == 1 && m_pip->value < INSET_MAP_FREE ) + return; + + DrawOverviewLayer(); + DrawOverviewEntities(); + CheckOverviewEntities(); +} +void CHudSpectator::CheckOverviewEntities() +{ + double time = gEngfuncs.GetClientTime(); + + // removes old entities from list + for ( int i = 0; i< MAX_OVERVIEW_ENTITIES; i++ ) + { + // remove entity from list if it is too old + if ( m_OverviewEntities[i].killTime < time ) + { + memset( &m_OverviewEntities[i], 0, sizeof (overviewEntity_t) ); + } + } +} + +bool CHudSpectator::AddOverviewEntity( int type, struct cl_entity_s *ent, const char *modelname) +{ + HSPRITE hSprite = 0; + double duration = -1.0f; // duration -1 means show it only this frame; + + if ( !ent ) + return false; + + if ( type == ET_PLAYER ) + { + if ( ent->curstate.solid != SOLID_NOT) + { + switch ( g_PlayerExtraInfo[ent->index].teamnumber ) + { + // blue and red teams are swapped in CS and TFC + case 1 : hSprite = m_hsprPlayerBlue; break; + case 2 : hSprite = m_hsprPlayerRed; break; + default : hSprite = m_hsprPlayer; break; + } + } + else + return false; // it's an spectator + } + else if (type == ET_NORMAL) + { + return false; + } + else + return false; + + return AddOverviewEntityToList(hSprite, ent, gEngfuncs.GetClientTime() + duration ); +} + +void CHudSpectator::DeathMessage(int victim) +{ + // find out where the victim is + cl_entity_t *pl = gEngfuncs.GetEntityByIndex(victim); + + if (pl && pl->player) + AddOverviewEntityToList(m_hsprPlayerDead, pl, gEngfuncs.GetClientTime() + 2.0f ); +} + +bool CHudSpectator::AddOverviewEntityToList(HSPRITE sprite, cl_entity_t *ent, double killTime) +{ + for ( int i = 0; i< MAX_OVERVIEW_ENTITIES; i++ ) + { + // find empty entity slot + if ( m_OverviewEntities[i].entity == NULL) + { + m_OverviewEntities[i].entity = ent; + m_OverviewEntities[i].hSprite = sprite; + m_OverviewEntities[i].killTime = killTime; + return true; + } + } + + return false; // maximum overview entities reached +} +void CHudSpectator::CheckSettings() +{ + // disallow same inset mode as main mode: + + m_pip->value = (int)m_pip->value; + + if ( ( g_iUser1 < OBS_MAP_FREE ) && ( m_pip->value == INSET_CHASE_FREE || m_pip->value == INSET_IN_EYE ) ) + { + // otherwise both would show in World picures + m_pip->value = INSET_MAP_FREE; + } + + if ( ( g_iUser1 >= OBS_MAP_FREE ) && ( m_pip->value >= INSET_MAP_FREE ) ) + { + // both would show map views + m_pip->value = INSET_CHASE_FREE; + } + + // disble in intermission screen + if ( gHUD.m_iIntermission ) + m_pip->value = INSET_OFF; + + // check chat mode + if ( m_chatEnabled != (gHUD.m_SayText.m_HUD_saytext->value!=0) ) + { + // hud_saytext changed + m_chatEnabled = (gHUD.m_SayText.m_HUD_saytext->value!=0); + + if ( gEngfuncs.IsSpectateOnly() ) + { + // tell proxy our new chat mode + char chatcmd[32]; + sprintf(chatcmd, "ignoremsg %i", m_chatEnabled?0:1 ); + gEngfuncs.pfnServerCmd(chatcmd); + } + } + + // HL/TFC has no oberserver corsshair, so set it client side + if ( (g_iUser1 == OBS_IN_EYE) || (g_iUser1 == OBS_ROAMING) ) + { + m_crosshairRect.left = 24; + m_crosshairRect.top = 0; + m_crosshairRect.right = 48; + m_crosshairRect.bottom = 24; + + SetCrosshair( m_hCrosshair, m_crosshairRect, 255, 255, 255 ); + } + else + { + memset( &m_crosshairRect,0,sizeof(m_crosshairRect) ); + SetCrosshair( 0, m_crosshairRect, 0, 0, 0 ); + } + + + + // if we are a real player on server don't allow inset window + // in First Person mode since this is our resticted forcecamera mode 2 + // team number 3 = SPECTATOR see player.h + + if ( ( (g_iTeamNumber == 1) || (g_iTeamNumber == 2)) && (g_iUser1 == OBS_IN_EYE) ) + m_pip->value = INSET_OFF; + + // draw small border around inset view, adjust upper black bar + gViewPort->m_pSpectatorPanel->EnableInsetView( m_pip->value != INSET_OFF ); +} + +int CHudSpectator::ToggleInset(bool allowOff) +{ + int newInsetMode = (int)m_pip->value + 1; + + if ( g_iUser1 < OBS_MAP_FREE ) + { + if ( newInsetMode > INSET_MAP_CHASE ) + { + if (allowOff) + newInsetMode = INSET_OFF; + else + newInsetMode = INSET_MAP_FREE; + } + + if ( newInsetMode == INSET_CHASE_FREE ) + newInsetMode = INSET_MAP_FREE; + } + else + { + if ( newInsetMode > INSET_IN_EYE ) + { + if (allowOff) + newInsetMode = INSET_OFF; + else + newInsetMode = INSET_CHASE_FREE; + } + } + + return newInsetMode; +} +void CHudSpectator::Reset() +{ + // Reset HUD + if ( strcmp( m_OverviewData.map, gEngfuncs.pfnGetLevelName() ) ) + { + // update level overview if level changed + ParseOverviewFile(); + LoadMapSprites(); + } + + memset( &m_OverviewEntities, 0, sizeof(m_OverviewEntities)); + + m_FOV = 90.0f; + + m_IsInterpolating = false; + + m_ChaseEntity = 0; + + SetSpectatorStartPosition(); +} + +void CHudSpectator::InitHUDData() +{ + m_lastPrimaryObject = m_lastSecondaryObject = 0; + m_flNextObserverInput = 0.0f; + m_lastHudMessage = 0; + m_iSpectatorNumber = 0; + iJumpSpectator = 0; + g_iUser1 = g_iUser2 = 0; + + memset( &m_OverviewData, 0, sizeof(m_OverviewData)); + memset( &m_OverviewEntities, 0, sizeof(m_OverviewEntities)); + + if ( gEngfuncs.IsSpectateOnly() || gEngfuncs.pDemoAPI->IsPlayingback() ) + m_autoDirector->value = 1.0f; + else + m_autoDirector->value = 0.0f; + + Reset(); + + SetModes( OBS_CHASE_LOCKED, INSET_OFF ); + + g_iUser2 = 0; // fake not target until first camera command + + // reset HUD FOV + gHUD.m_iFOV = CVAR_GET_FLOAT("default_fov"); +} + diff --git a/cl_dll/hud_spectator.h b/cl_dll/hud_spectator.h new file mode 100644 index 0000000..43bc18a --- /dev/null +++ b/cl_dll/hud_spectator.h @@ -0,0 +1,156 @@ +//========= Copyright � 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef SPECTATOR_H +#define SPECTATOR_H +#pragma once + +#include "cl_entity.h" +#include "interpolation.h" + + +#define INSET_OFF 0 +#define INSET_CHASE_FREE 1 +#define INSET_IN_EYE 2 +#define INSET_MAP_FREE 3 +#define INSET_MAP_CHASE 4 + +#define MAX_SPEC_HUD_MESSAGES 8 + +#define OVERVIEW_TILE_SIZE 128 // don't change this +#define OVERVIEW_MAX_LAYERS 1 + +extern void VectorAngles( const float *forward, float *angles ); +extern "C" void NormalizeAngles( float *angles ); + +//----------------------------------------------------------------------------- +// Purpose: Handles the drawing of the spectator stuff (camera & top-down map and all the things on it ) +//----------------------------------------------------------------------------- + +typedef struct overviewInfo_s { + char map[64]; // cl.levelname or empty + vec3_t origin; // center of map + float zoom; // zoom of map images + int layers; // how may layers do we have + float layersHeights[OVERVIEW_MAX_LAYERS]; + char layersImages[OVERVIEW_MAX_LAYERS][255]; + qboolean rotated; // are map images rotated (90 degrees) ? + + int insetWindowX; + int insetWindowY; + int insetWindowHeight; + int insetWindowWidth; +} overviewInfo_t; + +typedef struct overviewEntity_s { + + HSPRITE hSprite; + struct cl_entity_s * entity; + double killTime; +} overviewEntity_t; + +typedef struct cameraWayPoint_s +{ + float time; + vec3_t position; + vec3_t angle; + float fov; + int flags; +} cameraWayPoint_t; + +#define MAX_OVERVIEW_ENTITIES 128 +#define MAX_CAM_WAYPOINTS 32 + +class CHudSpectator : public CHudBase +{ +public: + void Reset(); + int ToggleInset(bool allowOff); + void CheckSettings(); + void InitHUDData( void ); + bool AddOverviewEntityToList( HSPRITE sprite, cl_entity_t * ent, double killTime); + void DeathMessage(int victim); + bool AddOverviewEntity( int type, struct cl_entity_s *ent, const char *modelname ); + void CheckOverviewEntities(); + void DrawOverview(); + void DrawOverviewEntities(); + void GetMapPosition( float * returnvec ); + void DrawOverviewLayer(); + void LoadMapSprites(); + bool ParseOverviewFile(); + bool IsActivePlayer(cl_entity_t * ent); + void SetModes(int iMainMode, int iInsetMode); + void HandleButtonsDown(int ButtonPressed); + void HandleButtonsUp(int ButtonPressed); + void FindNextPlayer( bool bReverse ); + void FindPlayer(const char *name); + void DirectorMessage( int iSize, void *pbuf ); + void SetSpectatorStartPosition(); + int Init(); + int VidInit(); + + int Draw(float flTime); + + void AddWaypoint( float time, vec3_t pos, vec3_t angle, float fov, int flags ); + void SetCameraView( vec3_t pos, vec3_t angle, float fov); + float GetFOV(); + bool GetDirectorCamera(vec3_t &position, vec3_t &angle); + void SetWayInterpolation(cameraWayPoint_t * prev, cameraWayPoint_t * start, cameraWayPoint_t * end, cameraWayPoint_t * next); + + + int m_iDrawCycle; + client_textmessage_t m_HUDMessages[MAX_SPEC_HUD_MESSAGES]; + char m_HUDMessageText[MAX_SPEC_HUD_MESSAGES][128]; + int m_lastHudMessage; + overviewInfo_t m_OverviewData; + overviewEntity_t m_OverviewEntities[MAX_OVERVIEW_ENTITIES]; + int m_iObserverFlags; + int m_iSpectatorNumber; + + float m_mapZoom; // zoom the user currently uses + vec3_t m_mapOrigin; // origin where user rotates around + cvar_t * m_drawnames; + cvar_t * m_drawcone; + cvar_t * m_drawstatus; + cvar_t * m_autoDirector; + cvar_t * m_pip; + qboolean m_chatEnabled; + + qboolean m_IsInterpolating; + int m_ChaseEntity; // if != 0, follow this entity with viewangles + int m_WayPoint; // current waypoint 1 + int m_NumWayPoints; // current number of waypoints + vec3_t m_cameraOrigin; // a help camera + vec3_t m_cameraAngles; // and it's angles + CInterpolation m_WayInterpolation; + +private: + vec3_t m_vPlayerPos[MAX_PLAYERS]; + HSPRITE m_hsprPlayerBlue; + HSPRITE m_hsprPlayerRed; + HSPRITE m_hsprPlayer; + HSPRITE m_hsprCamera; + HSPRITE m_hsprPlayerDead; + HSPRITE m_hsprViewcone; + HSPRITE m_hsprUnkownMap; + HSPRITE m_hsprBeam; + HSPRITE m_hCrosshair; + + wrect_t m_crosshairRect; + + struct model_s * m_MapSprite; // each layer image is saved in one sprite, where each tile is a sprite frame + float m_flNextObserverInput; + float m_FOV; + float m_zoomDelta; + float m_moveDelta; + int m_lastPrimaryObject; + int m_lastSecondaryObject; + + cameraWayPoint_t m_CamPath[MAX_CAM_WAYPOINTS]; +}; + +#endif // SPECTATOR_H diff --git a/cl_dll/hud_update.cpp b/cl_dll/hud_update.cpp new file mode 100644 index 0000000..9facc8c --- /dev/null +++ b/cl_dll/hud_update.cpp @@ -0,0 +1,54 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud_update.cpp +// + +#include +#include "hud.h" +#include "cl_util.h" +#include +#include + +int CL_ButtonBits( int ); +void CL_ResetButtonBits( int bits ); + +extern float v_idlescale; +float in_fov; +extern void HUD_SetCmdBits( int bits ); + +int CHud::UpdateClientData(client_data_t *cdata, float time) +{ + memcpy(m_vecOrigin, cdata->origin, sizeof(vec3_t)); + memcpy(m_vecAngles, cdata->viewangles, sizeof(vec3_t)); + + m_iKeyBits = CL_ButtonBits( 0 ); + m_iWeaponBits = cdata->iWeaponBits; + + in_fov = cdata->fov; + + Think(); + + cdata->fov = m_iFOV; + + v_idlescale = m_iConcussionEffect; + + CL_ResetButtonBits( m_iKeyBits ); + + // return 1 if in anything in the client_data struct has been changed, 0 otherwise + return 1; +} + + diff --git a/cl_dll/in_camera.cpp b/cl_dll/in_camera.cpp new file mode 100644 index 0000000..39bf848 --- /dev/null +++ b/cl_dll/in_camera.cpp @@ -0,0 +1,631 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "Exports.h" + +#include "SDL2/SDL_mouse.h" +#include "port.h" + +float CL_KeyState (kbutton_t *key); + +extern cl_enginefunc_t gEngfuncs; + +//-------------------------------------------------- Constants + +#define CAM_DIST_DELTA 1.0 +#define CAM_ANGLE_DELTA 2.5 +#define CAM_ANGLE_SPEED 2.5 +#define CAM_MIN_DIST 30.0 +#define CAM_ANGLE_MOVE .5 +#define MAX_ANGLE_DIFF 10.0 +#define PITCH_MAX 90.0 +#define PITCH_MIN 0 +#define YAW_MAX 135.0 +#define YAW_MIN -135.0 + +enum ECAM_Command +{ + CAM_COMMAND_NONE = 0, + CAM_COMMAND_TOTHIRDPERSON = 1, + CAM_COMMAND_TOFIRSTPERSON = 2 +}; + +//-------------------------------------------------- Global Variables + +cvar_t *cam_command; +cvar_t *cam_snapto; +cvar_t *cam_idealyaw; +cvar_t *cam_idealpitch; +cvar_t *cam_idealdist; +cvar_t *cam_contain; + +cvar_t *c_maxpitch; +cvar_t *c_minpitch; +cvar_t *c_maxyaw; +cvar_t *c_minyaw; +cvar_t *c_maxdistance; +cvar_t *c_mindistance; + +// pitch, yaw, dist +vec3_t cam_ofs; + + +// In third person +int cam_thirdperson; +int cam_mousemove; //true if we are moving the cam with the mouse, False if not +int iMouseInUse=0; +int cam_distancemove; +extern int mouse_x, mouse_y; //used to determine what the current x and y values are +int cam_old_mouse_x, cam_old_mouse_y; //holds the last ticks mouse movement +POINT cam_mouse; +//-------------------------------------------------- Local Variables + +static kbutton_t cam_pitchup, cam_pitchdown, cam_yawleft, cam_yawright; +static kbutton_t cam_in, cam_out, cam_move; + +//-------------------------------------------------- Prototypes + +void CAM_ToThirdPerson(void); +void CAM_ToFirstPerson(void); +void CAM_StartDistance(void); +void CAM_EndDistance(void); + +void SDL_GetCursorPos( POINT *p ) +{ + gEngfuncs.GetMousePosition( (int *)&p->x, (int *)&p->y ); +// SDL_GetMouseState( (int *)&p->x, (int *)&p->y ); +} + +void SDL_SetCursorPos( const int x, const int y ) +{ +} + +//-------------------------------------------------- Local Functions + +float MoveToward( float cur, float goal, float maxspeed ) +{ + if( cur != goal ) + { + if( abs( cur - goal ) > 180.0 ) + { + if( cur < goal ) + cur += 360.0; + else + cur -= 360.0; + } + + if( cur < goal ) + { + if( cur < goal - 1.0 ) + cur += ( goal - cur ) / 4.0; + else + cur = goal; + } + else + { + if( cur > goal + 1.0 ) + cur -= ( cur - goal ) / 4.0; + else + cur = goal; + } + } + + + // bring cur back into range + if( cur < 0 ) + cur += 360.0; + else if( cur >= 360 ) + cur -= 360; + + return cur; +} + + +//-------------------------------------------------- Gobal Functions + +typedef struct +{ + vec3_t boxmins, boxmaxs;// enclose the test object along entire move + float *mins, *maxs; // size of the moving object + vec3_t mins2, maxs2; // size when clipping against mosnters + float *start, *end; + trace_t trace; + int type; + edict_t *passedict; + qboolean monsterclip; +} moveclip_t; + +extern trace_t SV_ClipMoveToEntity (edict_t *ent, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end); + +void CL_DLLEXPORT CAM_Think( void ) +{ +// RecClCamThink(); + + vec3_t origin; + vec3_t ext, pnt, camForward, camRight, camUp; + moveclip_t clip; + float dist; + vec3_t camAngles; + float flSensitivity; +#ifdef LATER + int i; +#endif + vec3_t viewangles; + + switch( (int) cam_command->value ) + { + case CAM_COMMAND_TOTHIRDPERSON: + CAM_ToThirdPerson(); + break; + + case CAM_COMMAND_TOFIRSTPERSON: + CAM_ToFirstPerson(); + break; + + case CAM_COMMAND_NONE: + default: + break; + } + + if( !cam_thirdperson ) + return; + +#ifdef LATER + if ( cam_contain->value ) + { + gEngfuncs.GetClientOrigin( origin ); + ext[0] = ext[1] = ext[2] = 0.0; + } +#endif + + camAngles[ PITCH ] = cam_idealpitch->value; + camAngles[ YAW ] = cam_idealyaw->value; + dist = cam_idealdist->value; + // + //movement of the camera with the mouse + // + if (cam_mousemove) + { + //get windows cursor position + SDL_GetCursorPos (&cam_mouse); + //check for X delta values and adjust accordingly + //eventually adjust YAW based on amount of movement + //don't do any movement of the cam using YAW/PITCH if we are zooming in/out the camera + if (!cam_distancemove) + { + + //keep the camera within certain limits around the player (ie avoid certain bad viewing angles) + if (cam_mouse.x>gEngfuncs.GetWindowCenterX()) + { + //if ((camAngles[YAW]>=225.0)||(camAngles[YAW]<135.0)) + if (camAngles[YAW]value) + { + camAngles[ YAW ] += (CAM_ANGLE_MOVE)*((cam_mouse.x-gEngfuncs.GetWindowCenterX())/2); + } + if (camAngles[YAW]>c_maxyaw->value) + { + + camAngles[YAW]=c_maxyaw->value; + } + } + else if (cam_mouse.x225.0)) + if (camAngles[YAW]>c_minyaw->value) + { + camAngles[ YAW ] -= (CAM_ANGLE_MOVE)* ((gEngfuncs.GetWindowCenterX()-cam_mouse.x)/2); + + } + if (camAngles[YAW]value) + { + camAngles[YAW]=c_minyaw->value; + + } + } + + //check for y delta values and adjust accordingly + //eventually adjust PITCH based on amount of movement + //also make sure camera is within bounds + if (cam_mouse.y>gEngfuncs.GetWindowCenterY()) + { + if(camAngles[PITCH]value) + { + camAngles[PITCH] +=(CAM_ANGLE_MOVE)* ((cam_mouse.y-gEngfuncs.GetWindowCenterY())/2); + } + if (camAngles[PITCH]>c_maxpitch->value) + { + camAngles[PITCH]=c_maxpitch->value; + } + } + else if (cam_mouse.yc_minpitch->value) + { + camAngles[PITCH] -= (CAM_ANGLE_MOVE)*((gEngfuncs.GetWindowCenterY()-cam_mouse.y)/2); + } + if (camAngles[PITCH]value) + { + camAngles[PITCH]=c_minpitch->value; + } + } + + //set old mouse coordinates to current mouse coordinates + //since we are done with the mouse + + if ( ( flSensitivity = gHUD.GetSensitivity() ) != 0 ) + { + cam_old_mouse_x=cam_mouse.x*flSensitivity; + cam_old_mouse_y=cam_mouse.y*flSensitivity; + } + else + { + cam_old_mouse_x=cam_mouse.x; + cam_old_mouse_y=cam_mouse.y; + } + SDL_SetCursorPos (gEngfuncs.GetWindowCenterX(), gEngfuncs.GetWindowCenterY()); + } + } + + //Nathan code here + if( CL_KeyState( &cam_pitchup ) ) + camAngles[ PITCH ] += CAM_ANGLE_DELTA; + else if( CL_KeyState( &cam_pitchdown ) ) + camAngles[ PITCH ] -= CAM_ANGLE_DELTA; + + if( CL_KeyState( &cam_yawleft ) ) + camAngles[ YAW ] -= CAM_ANGLE_DELTA; + else if( CL_KeyState( &cam_yawright ) ) + camAngles[ YAW ] += CAM_ANGLE_DELTA; + + if( CL_KeyState( &cam_in ) ) + { + dist -= CAM_DIST_DELTA; + if( dist < CAM_MIN_DIST ) + { + // If we go back into first person, reset the angle + camAngles[ PITCH ] = 0; + camAngles[ YAW ] = 0; + dist = CAM_MIN_DIST; + } + + } + else if( CL_KeyState( &cam_out ) ) + dist += CAM_DIST_DELTA; + + if (cam_distancemove) + { + if (cam_mouse.y>gEngfuncs.GetWindowCenterY()) + { + if(distvalue) + { + dist +=CAM_DIST_DELTA * ((cam_mouse.y-gEngfuncs.GetWindowCenterY())/2); + } + if (dist>c_maxdistance->value) + { + dist=c_maxdistance->value; + } + } + else if (cam_mouse.yc_mindistance->value) + { + dist -= (CAM_DIST_DELTA)*((gEngfuncs.GetWindowCenterY()-cam_mouse.y)/2); + } + if (distvalue) + { + dist=c_mindistance->value; + } + } + //set old mouse coordinates to current mouse coordinates + //since we are done with the mouse + cam_old_mouse_x=cam_mouse.x*gHUD.GetSensitivity(); + cam_old_mouse_y=cam_mouse.y*gHUD.GetSensitivity(); + SDL_SetCursorPos (gEngfuncs.GetWindowCenterX(), gEngfuncs.GetWindowCenterY()); + } +#ifdef LATER + if( cam_contain->value ) + { + // check new ideal + VectorCopy( origin, pnt ); + AngleVectors( camAngles, camForward, camRight, camUp ); + for (i=0 ; i<3 ; i++) + pnt[i] += -dist*camForward[i]; + + // check line from r_refdef.vieworg to pnt + memset ( &clip, 0, sizeof ( moveclip_t ) ); + clip.trace = SV_ClipMoveToEntity( sv.edicts, r_refdef.vieworg, ext, ext, pnt ); + if( clip.trace.fraction == 1.0 ) + { + // update ideal + cam_idealpitch->value = camAngles[ PITCH ]; + cam_idealyaw->value = camAngles[ YAW ]; + cam_idealdist->value = dist; + } + } + else +#endif + { + // update ideal + cam_idealpitch->value = camAngles[ PITCH ]; + cam_idealyaw->value = camAngles[ YAW ]; + cam_idealdist->value = dist; + } + + // Move towards ideal + VectorCopy( cam_ofs, camAngles ); + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + if( cam_snapto->value ) + { + camAngles[ YAW ] = cam_idealyaw->value + viewangles[ YAW ]; + camAngles[ PITCH ] = cam_idealpitch->value + viewangles[ PITCH ]; + camAngles[ 2 ] = cam_idealdist->value; + } + else + { + if( camAngles[ YAW ] - viewangles[ YAW ] != cam_idealyaw->value ) + camAngles[ YAW ] = MoveToward( camAngles[ YAW ], cam_idealyaw->value + viewangles[ YAW ], CAM_ANGLE_SPEED ); + + if( camAngles[ PITCH ] - viewangles[ PITCH ] != cam_idealpitch->value ) + camAngles[ PITCH ] = MoveToward( camAngles[ PITCH ], cam_idealpitch->value + viewangles[ PITCH ], CAM_ANGLE_SPEED ); + + if( abs( camAngles[ 2 ] - cam_idealdist->value ) < 2.0 ) + camAngles[ 2 ] = cam_idealdist->value; + else + camAngles[ 2 ] += ( cam_idealdist->value - camAngles[ 2 ] ) / 4.0; + } +#ifdef LATER + if( cam_contain->value ) + { + // Test new position + dist = camAngles[ ROLL ]; + camAngles[ ROLL ] = 0; + + VectorCopy( origin, pnt ); + AngleVectors( camAngles, camForward, camRight, camUp ); + for (i=0 ; i<3 ; i++) + pnt[i] += -dist*camForward[i]; + + // check line from r_refdef.vieworg to pnt + memset ( &clip, 0, sizeof ( moveclip_t ) ); + ext[0] = ext[1] = ext[2] = 0.0; + clip.trace = SV_ClipMoveToEntity( sv.edicts, r_refdef.vieworg, ext, ext, pnt ); + if( clip.trace.fraction != 1.0 ) + return; + } +#endif + cam_ofs[ 0 ] = camAngles[ 0 ]; + cam_ofs[ 1 ] = camAngles[ 1 ]; + cam_ofs[ 2 ] = dist; +} + +extern void KeyDown (kbutton_t *b); // HACK +extern void KeyUp (kbutton_t *b); // HACK + +void CAM_PitchUpDown(void) { KeyDown( &cam_pitchup ); } +void CAM_PitchUpUp(void) { KeyUp( &cam_pitchup ); } +void CAM_PitchDownDown(void) { KeyDown( &cam_pitchdown ); } +void CAM_PitchDownUp(void) { KeyUp( &cam_pitchdown ); } +void CAM_YawLeftDown(void) { KeyDown( &cam_yawleft ); } +void CAM_YawLeftUp(void) { KeyUp( &cam_yawleft ); } +void CAM_YawRightDown(void) { KeyDown( &cam_yawright ); } +void CAM_YawRightUp(void) { KeyUp( &cam_yawright ); } +void CAM_InDown(void) { KeyDown( &cam_in ); } +void CAM_InUp(void) { KeyUp( &cam_in ); } +void CAM_OutDown(void) { KeyDown( &cam_out ); } +void CAM_OutUp(void) { KeyUp( &cam_out ); } + +void CAM_ToThirdPerson(void) +{ + vec3_t viewangles; + +#if !defined( _DEBUG ) + if ( gEngfuncs.GetMaxClients() > 1 ) + { + // no thirdperson in multiplayer. + return; + } +#endif + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + if( !cam_thirdperson ) + { + cam_thirdperson = 1; + + cam_ofs[ YAW ] = viewangles[ YAW ]; + cam_ofs[ PITCH ] = viewangles[ PITCH ]; + cam_ofs[ 2 ] = CAM_MIN_DIST; + } + + gEngfuncs.Cvar_SetValue( "cam_command", 0 ); +} + +void CAM_ToFirstPerson(void) +{ + cam_thirdperson = 0; + + gEngfuncs.Cvar_SetValue( "cam_command", 0 ); +} + +void CAM_ToggleSnapto( void ) +{ + cam_snapto->value = !cam_snapto->value; +} + +void CAM_Init( void ) +{ + gEngfuncs.pfnAddCommand( "+campitchup", CAM_PitchUpDown ); + gEngfuncs.pfnAddCommand( "-campitchup", CAM_PitchUpUp ); + gEngfuncs.pfnAddCommand( "+campitchdown", CAM_PitchDownDown ); + gEngfuncs.pfnAddCommand( "-campitchdown", CAM_PitchDownUp ); + gEngfuncs.pfnAddCommand( "+camyawleft", CAM_YawLeftDown ); + gEngfuncs.pfnAddCommand( "-camyawleft", CAM_YawLeftUp ); + gEngfuncs.pfnAddCommand( "+camyawright", CAM_YawRightDown ); + gEngfuncs.pfnAddCommand( "-camyawright", CAM_YawRightUp ); + gEngfuncs.pfnAddCommand( "+camin", CAM_InDown ); + gEngfuncs.pfnAddCommand( "-camin", CAM_InUp ); + gEngfuncs.pfnAddCommand( "+camout", CAM_OutDown ); + gEngfuncs.pfnAddCommand( "-camout", CAM_OutUp ); + gEngfuncs.pfnAddCommand( "thirdperson", CAM_ToThirdPerson ); + gEngfuncs.pfnAddCommand( "firstperson", CAM_ToFirstPerson ); + gEngfuncs.pfnAddCommand( "+cammousemove",CAM_StartMouseMove); + gEngfuncs.pfnAddCommand( "-cammousemove",CAM_EndMouseMove); + gEngfuncs.pfnAddCommand( "+camdistance", CAM_StartDistance ); + gEngfuncs.pfnAddCommand( "-camdistance", CAM_EndDistance ); + gEngfuncs.pfnAddCommand( "snapto", CAM_ToggleSnapto ); + + cam_command = gEngfuncs.pfnRegisterVariable ( "cam_command", "0", 0 ); // tells camera to go to thirdperson + cam_snapto = gEngfuncs.pfnRegisterVariable ( "cam_snapto", "0", 0 ); // snap to thirdperson view + cam_idealyaw = gEngfuncs.pfnRegisterVariable ( "cam_idealyaw", "90", 0 ); // thirdperson yaw + cam_idealpitch = gEngfuncs.pfnRegisterVariable ( "cam_idealpitch", "0", 0 ); // thirperson pitch + cam_idealdist = gEngfuncs.pfnRegisterVariable ( "cam_idealdist", "64", 0 ); // thirdperson distance + cam_contain = gEngfuncs.pfnRegisterVariable ( "cam_contain", "0", 0 ); // contain camera to world + + c_maxpitch = gEngfuncs.pfnRegisterVariable ( "c_maxpitch", "90.0", 0 ); + c_minpitch = gEngfuncs.pfnRegisterVariable ( "c_minpitch", "0.0", 0 ); + c_maxyaw = gEngfuncs.pfnRegisterVariable ( "c_maxyaw", "135.0", 0 ); + c_minyaw = gEngfuncs.pfnRegisterVariable ( "c_minyaw", "-135.0", 0 ); + c_maxdistance = gEngfuncs.pfnRegisterVariable ( "c_maxdistance", "200.0", 0 ); + c_mindistance = gEngfuncs.pfnRegisterVariable ( "c_mindistance", "30.0", 0 ); +} + +void CAM_ClearStates( void ) +{ + vec3_t viewangles; + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + cam_pitchup.state = 0; + cam_pitchdown.state = 0; + cam_yawleft.state = 0; + cam_yawright.state = 0; + cam_in.state = 0; + cam_out.state = 0; + + cam_thirdperson = 0; + cam_command->value = 0; + cam_mousemove=0; + + cam_snapto->value = 0; + cam_distancemove = 0; + + cam_ofs[ 0 ] = 0.0; + cam_ofs[ 1 ] = 0.0; + cam_ofs[ 2 ] = CAM_MIN_DIST; + + cam_idealpitch->value = viewangles[ PITCH ]; + cam_idealyaw->value = viewangles[ YAW ]; + cam_idealdist->value = CAM_MIN_DIST; +} + +void CAM_StartMouseMove(void) +{ + float flSensitivity; + + //only move the cam with mouse if we are in third person. + if (cam_thirdperson) + { + //set appropriate flags and initialize the old mouse position + //variables for mouse camera movement + if (!cam_mousemove) + { + cam_mousemove=1; + iMouseInUse=1; + SDL_GetCursorPos (&cam_mouse); + + if ( ( flSensitivity = gHUD.GetSensitivity() ) != 0 ) + { + cam_old_mouse_x=cam_mouse.x*flSensitivity; + cam_old_mouse_y=cam_mouse.y*flSensitivity; + } + else + { + cam_old_mouse_x=cam_mouse.x; + cam_old_mouse_y=cam_mouse.y; + } + } + } + //we are not in 3rd person view..therefore do not allow camera movement + else + { + cam_mousemove=0; + iMouseInUse=0; + } +} + +//the key has been released for camera movement +//tell the engine that mouse camera movement is off +void CAM_EndMouseMove(void) +{ + cam_mousemove=0; + iMouseInUse=0; +} + + +//---------------------------------------------------------- +//routines to start the process of moving the cam in or out +//using the mouse +//---------------------------------------------------------- +void CAM_StartDistance(void) +{ + //only move the cam with mouse if we are in third person. + if (cam_thirdperson) + { + //set appropriate flags and initialize the old mouse position + //variables for mouse camera movement + if (!cam_distancemove) + { + cam_distancemove=1; + cam_mousemove=1; + iMouseInUse=1; + SDL_GetCursorPos (&cam_mouse); + cam_old_mouse_x=cam_mouse.x*gHUD.GetSensitivity(); + cam_old_mouse_y=cam_mouse.y*gHUD.GetSensitivity(); + } + } + //we are not in 3rd person view..therefore do not allow camera movement + else + { + cam_distancemove=0; + cam_mousemove=0; + iMouseInUse=0; + } +} + +//the key has been released for camera movement +//tell the engine that mouse camera movement is off +void CAM_EndDistance(void) +{ + cam_distancemove=0; + cam_mousemove=0; + iMouseInUse=0; +} + +int CL_DLLEXPORT CL_IsThirdPerson( void ) +{ +// RecClCL_IsThirdPerson(); + + return (cam_thirdperson ? 1 : 0) || (g_iUser1 && (g_iUser2 == gEngfuncs.GetLocalPlayer()->index) ); +} + +void CL_DLLEXPORT CL_CameraOffset( float *ofs ) +{ +// RecClCL_GetCameraOffsets(ofs); + + VectorCopy( cam_ofs, ofs ); +} diff --git a/cl_dll/in_defs.h b/cl_dll/in_defs.h new file mode 100644 index 0000000..6285044 --- /dev/null +++ b/cl_dll/in_defs.h @@ -0,0 +1,20 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( IN_DEFSH ) +#define IN_DEFSH +#pragma once + +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 + + +#endif \ No newline at end of file diff --git a/cl_dll/input.cpp b/cl_dll/input.cpp new file mode 100644 index 0000000..183f97e --- /dev/null +++ b/cl_dll/input.cpp @@ -0,0 +1,1041 @@ +// cl.input.c -- builds an intended movement command to send to the server + +//xxxxxx Move bob and pitch drifting code here and other stuff from view if needed + +// Quake is a trademark of Id Software, Inc., (c) 1996 Id Software, Inc. All +// rights reserved. +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +extern "C" +{ +#include "kbutton.h" +} +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "view.h" +#include "bench.h" +#include +#include +#include "Exports.h" + +#include "vgui_TeamFortressViewport.h" + + +extern int g_iAlive; + +extern int g_weaponselect; +extern cl_enginefunc_t gEngfuncs; + +// Defined in pm_math.c +extern "C" float anglemod( float a ); + +void IN_Init (void); +void IN_Move ( float frametime, usercmd_t *cmd); +void IN_Shutdown( void ); +void V_Init( void ); +void VectorAngles( const float *forward, float *angles ); +int CL_ButtonBits( int ); + +// xxx need client dll function to get and clear impuse +extern cvar_t *in_joystick; + +int in_impulse = 0; +int in_cancel = 0; + +cvar_t *m_pitch; +cvar_t *m_yaw; +cvar_t *m_forward; +cvar_t *m_side; + +cvar_t *lookstrafe; +cvar_t *lookspring; +cvar_t *cl_pitchup; +cvar_t *cl_pitchdown; +cvar_t *cl_upspeed; +cvar_t *cl_forwardspeed; +cvar_t *cl_backspeed; +cvar_t *cl_sidespeed; +cvar_t *cl_movespeedkey; +cvar_t *cl_yawspeed; +cvar_t *cl_pitchspeed; +cvar_t *cl_anglespeedkey; +cvar_t *cl_vsmoothing; +/* +=============================================================================== + +KEY BUTTONS + +Continuous button event tracking is complicated by the fact that two different +input sources (say, mouse button 1 and the control key) can both press the +same button, but the button should only be released when both of the +pressing key have been released. + +When a key event issues a button command (+forward, +attack, etc), it appends +its key number as a parameter to the command so it can be matched up with +the release. + +state bit 0 is the current state of the key +state bit 1 is edge triggered on the up to down transition +state bit 2 is edge triggered on the down to up transition + +=============================================================================== +*/ + + +kbutton_t in_mlook; +kbutton_t in_klook; +kbutton_t in_jlook; +kbutton_t in_left; +kbutton_t in_right; +kbutton_t in_forward; +kbutton_t in_back; +kbutton_t in_lookup; +kbutton_t in_lookdown; +kbutton_t in_moveleft; +kbutton_t in_moveright; +kbutton_t in_strafe; +kbutton_t in_speed; +kbutton_t in_use; +kbutton_t in_jump; +kbutton_t in_attack; +kbutton_t in_attack2; +kbutton_t in_up; +kbutton_t in_down; +kbutton_t in_duck; +kbutton_t in_reload; +kbutton_t in_alt1; +kbutton_t in_score; +kbutton_t in_break; +kbutton_t in_graph; // Display the netgraph + +typedef struct kblist_s +{ + struct kblist_s *next; + kbutton_t *pkey; + char name[32]; +} kblist_t; + +kblist_t *g_kbkeys = NULL; + +/* +============ +KB_ConvertString + +Removes references to +use and replaces them with the keyname in the output string. If + a binding is unfound, then the original text is retained. +NOTE: Only works for text with +word in it. +============ +*/ +int KB_ConvertString( char *in, char **ppout ) +{ + char sz[ 4096 ]; + char binding[ 64 ]; + char *p; + char *pOut; + char *pEnd; + const char *pBinding; + + if ( !ppout ) + return 0; + + *ppout = NULL; + p = in; + pOut = sz; + while ( *p ) + { + if ( *p == '+' ) + { + pEnd = binding; + while ( *p && ( isalnum( *p ) || ( pEnd == binding ) ) && ( ( pEnd - binding ) < 63 ) ) + { + *pEnd++ = *p++; + } + + *pEnd = '\0'; + + pBinding = NULL; + if ( strlen( binding + 1 ) > 0 ) + { + // See if there is a binding for binding? + pBinding = gEngfuncs.Key_LookupBinding( binding + 1 ); + } + + if ( pBinding ) + { + *pOut++ = '['; + pEnd = (char *)pBinding; + } + else + { + pEnd = binding; + } + + while ( *pEnd ) + { + *pOut++ = *pEnd++; + } + + if ( pBinding ) + { + *pOut++ = ']'; + } + } + else + { + *pOut++ = *p++; + } + } + + *pOut = '\0'; + + pOut = ( char * )malloc( strlen( sz ) + 1 ); + strcpy( pOut, sz ); + *ppout = pOut; + + return 1; +} + +/* +============ +KB_Find + +Allows the engine to get a kbutton_t directly ( so it can check +mlook state, etc ) for saving out to .cfg files +============ +*/ +struct kbutton_s CL_DLLEXPORT *KB_Find( const char *name ) +{ +// RecClFindKey(name); + + kblist_t *p; + p = g_kbkeys; + while ( p ) + { + if ( !stricmp( name, p->name ) ) + return p->pkey; + + p = p->next; + } + return NULL; +} + +/* +============ +KB_Add + +Add a kbutton_t * to the list of pointers the engine can retrieve via KB_Find +============ +*/ +void KB_Add( const char *name, kbutton_t *pkb ) +{ + kblist_t *p; + kbutton_t *kb; + + kb = KB_Find( name ); + + if ( kb ) + return; + + p = ( kblist_t * )malloc( sizeof( kblist_t ) ); + memset( p, 0, sizeof( *p ) ); + + strcpy( p->name, name ); + p->pkey = pkb; + + p->next = g_kbkeys; + g_kbkeys = p; +} + +/* +============ +KB_Init + +Add kbutton_t definitions that the engine can query if needed +============ +*/ +void KB_Init( void ) +{ + g_kbkeys = NULL; + + KB_Add( "in_graph", &in_graph ); + KB_Add( "in_mlook", &in_mlook ); + KB_Add( "in_jlook", &in_jlook ); +} + +/* +============ +KB_Shutdown + +Clear kblist +============ +*/ +void KB_Shutdown( void ) +{ + kblist_t *p, *n; + p = g_kbkeys; + while ( p ) + { + n = p->next; + free( p ); + p = n; + } + g_kbkeys = NULL; +} + +/* +============ +KeyDown +============ +*/ +void KeyDown (kbutton_t *b) +{ + int k; + char *c; + + c = gEngfuncs.Cmd_Argv(1); + if (c[0]) + k = atoi(c); + else + k = -1; // typed manually at the console for continuous down + + if (k == b->down[0] || k == b->down[1]) + return; // repeating key + + if (!b->down[0]) + b->down[0] = k; + else if (!b->down[1]) + b->down[1] = k; + else + { + gEngfuncs.Con_DPrintf ("Three keys down for a button '%c' '%c' '%c'!\n", b->down[0], b->down[1], c); + return; + } + + if (b->state & 1) + return; // still down + b->state |= 1 + 2; // down + impulse down +} + +/* +============ +KeyUp +============ +*/ +void KeyUp (kbutton_t *b) +{ + int k; + char *c; + + c = gEngfuncs.Cmd_Argv(1); + if (c[0]) + k = atoi(c); + else + { // typed manually at the console, assume for unsticking, so clear all + b->down[0] = b->down[1] = 0; + b->state = 4; // impulse up + return; + } + + if (b->down[0] == k) + b->down[0] = 0; + else if (b->down[1] == k) + b->down[1] = 0; + else + return; // key up without coresponding down (menu pass through) + if (b->down[0] || b->down[1]) + { + //Con_Printf ("Keys down for button: '%c' '%c' '%c' (%d,%d,%d)!\n", b->down[0], b->down[1], c, b->down[0], b->down[1], c); + return; // some other key is still holding it down + } + + if (!(b->state & 1)) + return; // still up (this should not happen) + + b->state &= ~1; // now up + b->state |= 4; // impulse up +} + +/* +============ +HUD_Key_Event + +Return 1 to allow engine to process the key, otherwise, act on it as needed +============ +*/ +int CL_DLLEXPORT HUD_Key_Event( int down, int keynum, const char *pszCurrentBinding ) +{ +// RecClKeyEvent(down, keynum, pszCurrentBinding); + + if (gViewPort) + return gViewPort->KeyInput(down, keynum, pszCurrentBinding); + + return 1; +} + +void IN_BreakDown( void ) { KeyDown( &in_break );}; +void IN_BreakUp( void ) { KeyUp( &in_break ); }; +void IN_KLookDown (void) {KeyDown(&in_klook);} +void IN_KLookUp (void) {KeyUp(&in_klook);} +void IN_JLookDown (void) {KeyDown(&in_jlook);} +void IN_JLookUp (void) {KeyUp(&in_jlook);} +void IN_MLookDown (void) {KeyDown(&in_mlook);} +void IN_UpDown(void) {KeyDown(&in_up);} +void IN_UpUp(void) {KeyUp(&in_up);} +void IN_DownDown(void) {KeyDown(&in_down);} +void IN_DownUp(void) {KeyUp(&in_down);} +void IN_LeftDown(void) {KeyDown(&in_left);} +void IN_LeftUp(void) {KeyUp(&in_left);} +void IN_RightDown(void) {KeyDown(&in_right);} +void IN_RightUp(void) {KeyUp(&in_right);} + +void IN_ForwardDown(void) +{ + KeyDown(&in_forward); + gHUD.m_Spectator.HandleButtonsDown( IN_FORWARD ); +} + +void IN_ForwardUp(void) +{ + KeyUp(&in_forward); + gHUD.m_Spectator.HandleButtonsUp( IN_FORWARD ); +} + +void IN_BackDown(void) +{ + KeyDown(&in_back); + gHUD.m_Spectator.HandleButtonsDown( IN_BACK ); +} + +void IN_BackUp(void) +{ + KeyUp(&in_back); + gHUD.m_Spectator.HandleButtonsUp( IN_BACK ); +} +void IN_LookupDown(void) {KeyDown(&in_lookup);} +void IN_LookupUp(void) {KeyUp(&in_lookup);} +void IN_LookdownDown(void) {KeyDown(&in_lookdown);} +void IN_LookdownUp(void) {KeyUp(&in_lookdown);} +void IN_MoveleftDown(void) +{ + KeyDown(&in_moveleft); + gHUD.m_Spectator.HandleButtonsDown( IN_MOVELEFT ); +} + +void IN_MoveleftUp(void) +{ + KeyUp(&in_moveleft); + gHUD.m_Spectator.HandleButtonsUp( IN_MOVELEFT ); +} + +void IN_MoverightDown(void) +{ + KeyDown(&in_moveright); + gHUD.m_Spectator.HandleButtonsDown( IN_MOVERIGHT ); +} + +void IN_MoverightUp(void) +{ + KeyUp(&in_moveright); + gHUD.m_Spectator.HandleButtonsUp( IN_MOVERIGHT ); +} +void IN_SpeedDown(void) {KeyDown(&in_speed);} +void IN_SpeedUp(void) {KeyUp(&in_speed);} +void IN_StrafeDown(void) {KeyDown(&in_strafe);} +void IN_StrafeUp(void) {KeyUp(&in_strafe);} + +// needs capture by hud/vgui also +extern void __CmdFunc_InputPlayerSpecial(void); + +void IN_Attack2Down(void) +{ + KeyDown(&in_attack2); + +#ifdef _TFC + __CmdFunc_InputPlayerSpecial(); +#endif + + gHUD.m_Spectator.HandleButtonsDown( IN_ATTACK2 ); +} + +void IN_Attack2Up(void) {KeyUp(&in_attack2);} +void IN_UseDown (void) +{ + KeyDown(&in_use); + gHUD.m_Spectator.HandleButtonsDown( IN_USE ); +} +void IN_UseUp (void) {KeyUp(&in_use);} +void IN_JumpDown (void) +{ + KeyDown(&in_jump); + gHUD.m_Spectator.HandleButtonsDown( IN_JUMP ); + +} +void IN_JumpUp (void) {KeyUp(&in_jump);} +void IN_DuckDown(void) +{ + KeyDown(&in_duck); + gHUD.m_Spectator.HandleButtonsDown( IN_DUCK ); + +} +void IN_DuckUp(void) {KeyUp(&in_duck);} +void IN_ReloadDown(void) {KeyDown(&in_reload);} +void IN_ReloadUp(void) {KeyUp(&in_reload);} +void IN_Alt1Down(void) {KeyDown(&in_alt1);} +void IN_Alt1Up(void) {KeyUp(&in_alt1);} +void IN_GraphDown(void) {KeyDown(&in_graph);} +void IN_GraphUp(void) {KeyUp(&in_graph);} + +void IN_AttackDown(void) +{ + KeyDown( &in_attack ); + gHUD.m_Spectator.HandleButtonsDown( IN_ATTACK ); +} + +void IN_AttackUp(void) +{ + KeyUp( &in_attack ); + in_cancel = 0; +} + +// Special handling +void IN_Cancel(void) +{ + in_cancel = 1; +} + +void IN_Impulse (void) +{ + in_impulse = atoi( gEngfuncs.Cmd_Argv(1) ); +} + +void IN_ScoreDown(void) +{ + KeyDown(&in_score); + if ( gViewPort ) + { + gViewPort->ShowScoreBoard(); + } +} + +void IN_ScoreUp(void) +{ + KeyUp(&in_score); + if ( gViewPort ) + { + gViewPort->HideScoreBoard(); + } +} + +void IN_MLookUp (void) +{ + KeyUp( &in_mlook ); + if ( !( in_mlook.state & 1 ) && lookspring->value ) + { + V_StartPitchDrift(); + } +} + +/* +=============== +CL_KeyState + +Returns 0.25 if a key was pressed and released during the frame, +0.5 if it was pressed and held +0 if held then released, and +1.0 if held for the entire time +=============== +*/ +float CL_KeyState (kbutton_t *key) +{ + float val = 0.0; + int impulsedown, impulseup, down; + + impulsedown = key->state & 2; + impulseup = key->state & 4; + down = key->state & 1; + + if ( impulsedown && !impulseup ) + { + // pressed and held this frame? + val = down ? 0.5 : 0.0; + } + + if ( impulseup && !impulsedown ) + { + // released this frame? + val = down ? 0.0 : 0.0; + } + + if ( !impulsedown && !impulseup ) + { + // held the entire frame? + val = down ? 1.0 : 0.0; + } + + if ( impulsedown && impulseup ) + { + if ( down ) + { + // released and re-pressed this frame + val = 0.75; + } + else + { + // pressed and released this frame + val = 0.25; + } + } + + // clear impulses + key->state &= 1; + return val; +} + +/* +================ +CL_AdjustAngles + +Moves the local angle positions +================ +*/ +void CL_AdjustAngles ( float frametime, float *viewangles ) +{ + float speed; + float up, down; + + if (in_speed.state & 1) + { + speed = frametime * cl_anglespeedkey->value; + } + else + { + speed = frametime; + } + + if (!(in_strafe.state & 1)) + { + viewangles[YAW] -= speed*cl_yawspeed->value*CL_KeyState (&in_right); + viewangles[YAW] += speed*cl_yawspeed->value*CL_KeyState (&in_left); + viewangles[YAW] = anglemod(viewangles[YAW]); + } + if (in_klook.state & 1) + { + V_StopPitchDrift (); + viewangles[PITCH] -= speed*cl_pitchspeed->value * CL_KeyState (&in_forward); + viewangles[PITCH] += speed*cl_pitchspeed->value * CL_KeyState (&in_back); + } + + up = CL_KeyState (&in_lookup); + down = CL_KeyState(&in_lookdown); + + viewangles[PITCH] -= speed*cl_pitchspeed->value * up; + viewangles[PITCH] += speed*cl_pitchspeed->value * down; + + if (up || down) + V_StopPitchDrift (); + + if (viewangles[PITCH] > cl_pitchdown->value) + viewangles[PITCH] = cl_pitchdown->value; + if (viewangles[PITCH] < -cl_pitchup->value) + viewangles[PITCH] = -cl_pitchup->value; + + if (viewangles[ROLL] > 50) + viewangles[ROLL] = 50; + if (viewangles[ROLL] < -50) + viewangles[ROLL] = -50; +} + +/* +================ +CL_CreateMove + +Send the intended movement message to the server +if active == 1 then we are 1) not playing back demos ( where our commands are ignored ) and +2 ) we have finished signing on to server +================ +*/ +void CL_DLLEXPORT CL_CreateMove ( float frametime, struct usercmd_s *cmd, int active ) +{ +// RecClCL_CreateMove(frametime, cmd, active); + + float spd; + vec3_t viewangles; + static vec3_t oldangles; + + if ( active && !Bench_Active() ) + { + //memset( viewangles, 0, sizeof( vec3_t ) ); + //viewangles[ 0 ] = viewangles[ 1 ] = viewangles[ 2 ] = 0.0; + gEngfuncs.GetViewAngles( (float *)viewangles ); + + CL_AdjustAngles ( frametime, viewangles ); + + memset (cmd, 0, sizeof(*cmd)); + + gEngfuncs.SetViewAngles( (float *)viewangles ); + + if ( in_strafe.state & 1 ) + { + cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_right); + cmd->sidemove -= cl_sidespeed->value * CL_KeyState (&in_left); + } + + cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_moveright); + cmd->sidemove -= cl_sidespeed->value * CL_KeyState (&in_moveleft); + + cmd->upmove += cl_upspeed->value * CL_KeyState (&in_up); + cmd->upmove -= cl_upspeed->value * CL_KeyState (&in_down); + + if ( !(in_klook.state & 1 ) ) + { + cmd->forwardmove += cl_forwardspeed->value * CL_KeyState (&in_forward); + cmd->forwardmove -= cl_backspeed->value * CL_KeyState (&in_back); + } + + // adjust for speed key + if ( in_speed.state & 1 ) + { + cmd->forwardmove *= cl_movespeedkey->value; + cmd->sidemove *= cl_movespeedkey->value; + cmd->upmove *= cl_movespeedkey->value; + } + + // clip to maxspeed + spd = gEngfuncs.GetClientMaxspeed(); + if ( spd != 0.0 ) + { + // scale the 3 speeds so that the total velocity is not > cl.maxspeed + float fmov = sqrt( (cmd->forwardmove*cmd->forwardmove) + (cmd->sidemove*cmd->sidemove) + (cmd->upmove*cmd->upmove) ); + + if ( fmov > spd ) + { + float fratio = spd / fmov; + cmd->forwardmove *= fratio; + cmd->sidemove *= fratio; + cmd->upmove *= fratio; + } + } + + // Allow mice and other controllers to add their inputs + IN_Move ( frametime, cmd ); + } + + cmd->impulse = in_impulse; + in_impulse = 0; + + cmd->weaponselect = g_weaponselect; + g_weaponselect = 0; + // + // set button and flag bits + // + cmd->buttons = CL_ButtonBits( 1 ); + + // If they're in a modal dialog, ignore the attack button. + if(GetClientVoiceMgr()->IsInSquelchMode()) + cmd->buttons &= ~IN_ATTACK; + + // Using joystick? + if ( in_joystick->value ) + { + if ( cmd->forwardmove > 0 ) + { + cmd->buttons |= IN_FORWARD; + } + else if ( cmd->forwardmove < 0 ) + { + cmd->buttons |= IN_BACK; + } + } + + gEngfuncs.GetViewAngles( (float *)viewangles ); + // Set current view angles. + + if ( g_iAlive ) + { + VectorCopy( viewangles, cmd->viewangles ); + VectorCopy( viewangles, oldangles ); + } + else + { + VectorCopy( oldangles, cmd->viewangles ); + } + + Bench_SetViewAngles( 1, (float *)&cmd->viewangles, frametime, cmd ); +} + +/* +============ +CL_IsDead + +Returns 1 if health is <= 0 +============ +*/ +int CL_IsDead( void ) +{ + return ( gHUD.m_Health.m_iHealth <= 0 ) ? 1 : 0; +} + +/* +============ +CL_ButtonBits + +Returns appropriate button info for keyboard and mouse state +Set bResetState to 1 to clear old state info +============ +*/ +int CL_ButtonBits( int bResetState ) +{ + int bits = 0; + + if ( in_attack.state & 3 ) + { + bits |= IN_ATTACK; + } + + if (in_duck.state & 3) + { + bits |= IN_DUCK; + } + + if (in_jump.state & 3) + { + bits |= IN_JUMP; + } + + if ( in_forward.state & 3 ) + { + bits |= IN_FORWARD; + } + + if (in_back.state & 3) + { + bits |= IN_BACK; + } + + if (in_use.state & 3) + { + bits |= IN_USE; + } + + if (in_cancel) + { + bits |= IN_CANCEL; + } + + if ( in_left.state & 3 ) + { + bits |= IN_LEFT; + } + + if (in_right.state & 3) + { + bits |= IN_RIGHT; + } + + if ( in_moveleft.state & 3 ) + { + bits |= IN_MOVELEFT; + } + + if (in_moveright.state & 3) + { + bits |= IN_MOVERIGHT; + } + + if (in_attack2.state & 3) + { + bits |= IN_ATTACK2; + } + + if (in_reload.state & 3) + { + bits |= IN_RELOAD; + } + + if (in_alt1.state & 3) + { + bits |= IN_ALT1; + } + + if ( in_score.state & 3 ) + { + bits |= IN_SCORE; + } + + // Dead or in intermission? Shore scoreboard, too + if ( CL_IsDead() || gHUD.m_iIntermission ) + { + bits |= IN_SCORE; + } + + if ( bResetState ) + { + in_attack.state &= ~2; + in_duck.state &= ~2; + in_jump.state &= ~2; + in_forward.state &= ~2; + in_back.state &= ~2; + in_use.state &= ~2; + in_left.state &= ~2; + in_right.state &= ~2; + in_moveleft.state &= ~2; + in_moveright.state &= ~2; + in_attack2.state &= ~2; + in_reload.state &= ~2; + in_alt1.state &= ~2; + in_score.state &= ~2; + } + + return bits; +} + +/* +============ +CL_ResetButtonBits + +============ +*/ +void CL_ResetButtonBits( int bits ) +{ + int bitsNew = CL_ButtonBits( 0 ) ^ bits; + + // Has the attack button been changed + if ( bitsNew & IN_ATTACK ) + { + // Was it pressed? or let go? + if ( bits & IN_ATTACK ) + { + KeyDown( &in_attack ); + } + else + { + // totally clear state + in_attack.state &= ~7; + } + } +} + +/* +============ +InitInput +============ +*/ +void InitInput (void) +{ + gEngfuncs.pfnAddCommand ("+moveup",IN_UpDown); + gEngfuncs.pfnAddCommand ("-moveup",IN_UpUp); + gEngfuncs.pfnAddCommand ("+movedown",IN_DownDown); + gEngfuncs.pfnAddCommand ("-movedown",IN_DownUp); + gEngfuncs.pfnAddCommand ("+left",IN_LeftDown); + gEngfuncs.pfnAddCommand ("-left",IN_LeftUp); + gEngfuncs.pfnAddCommand ("+right",IN_RightDown); + gEngfuncs.pfnAddCommand ("-right",IN_RightUp); + gEngfuncs.pfnAddCommand ("+forward",IN_ForwardDown); + gEngfuncs.pfnAddCommand ("-forward",IN_ForwardUp); + gEngfuncs.pfnAddCommand ("+back",IN_BackDown); + gEngfuncs.pfnAddCommand ("-back",IN_BackUp); + gEngfuncs.pfnAddCommand ("+lookup", IN_LookupDown); + gEngfuncs.pfnAddCommand ("-lookup", IN_LookupUp); + gEngfuncs.pfnAddCommand ("+lookdown", IN_LookdownDown); + gEngfuncs.pfnAddCommand ("-lookdown", IN_LookdownUp); + gEngfuncs.pfnAddCommand ("+strafe", IN_StrafeDown); + gEngfuncs.pfnAddCommand ("-strafe", IN_StrafeUp); + gEngfuncs.pfnAddCommand ("+moveleft", IN_MoveleftDown); + gEngfuncs.pfnAddCommand ("-moveleft", IN_MoveleftUp); + gEngfuncs.pfnAddCommand ("+moveright", IN_MoverightDown); + gEngfuncs.pfnAddCommand ("-moveright", IN_MoverightUp); + gEngfuncs.pfnAddCommand ("+speed", IN_SpeedDown); + gEngfuncs.pfnAddCommand ("-speed", IN_SpeedUp); + gEngfuncs.pfnAddCommand ("+attack", IN_AttackDown); + gEngfuncs.pfnAddCommand ("-attack", IN_AttackUp); + gEngfuncs.pfnAddCommand ("+attack2", IN_Attack2Down); + gEngfuncs.pfnAddCommand ("-attack2", IN_Attack2Up); + gEngfuncs.pfnAddCommand ("+use", IN_UseDown); + gEngfuncs.pfnAddCommand ("-use", IN_UseUp); + gEngfuncs.pfnAddCommand ("+jump", IN_JumpDown); + gEngfuncs.pfnAddCommand ("-jump", IN_JumpUp); + gEngfuncs.pfnAddCommand ("impulse", IN_Impulse); + gEngfuncs.pfnAddCommand ("+klook", IN_KLookDown); + gEngfuncs.pfnAddCommand ("-klook", IN_KLookUp); + gEngfuncs.pfnAddCommand ("+mlook", IN_MLookDown); + gEngfuncs.pfnAddCommand ("-mlook", IN_MLookUp); + gEngfuncs.pfnAddCommand ("+jlook", IN_JLookDown); + gEngfuncs.pfnAddCommand ("-jlook", IN_JLookUp); + gEngfuncs.pfnAddCommand ("+duck", IN_DuckDown); + gEngfuncs.pfnAddCommand ("-duck", IN_DuckUp); + gEngfuncs.pfnAddCommand ("+reload", IN_ReloadDown); + gEngfuncs.pfnAddCommand ("-reload", IN_ReloadUp); + gEngfuncs.pfnAddCommand ("+alt1", IN_Alt1Down); + gEngfuncs.pfnAddCommand ("-alt1", IN_Alt1Up); + gEngfuncs.pfnAddCommand ("+score", IN_ScoreDown); + gEngfuncs.pfnAddCommand ("-score", IN_ScoreUp); + gEngfuncs.pfnAddCommand ("+showscores", IN_ScoreDown); + gEngfuncs.pfnAddCommand ("-showscores", IN_ScoreUp); + gEngfuncs.pfnAddCommand ("+graph", IN_GraphDown); + gEngfuncs.pfnAddCommand ("-graph", IN_GraphUp); + gEngfuncs.pfnAddCommand ("+break",IN_BreakDown); + gEngfuncs.pfnAddCommand ("-break",IN_BreakUp); + + lookstrafe = gEngfuncs.pfnRegisterVariable ( "lookstrafe", "0", FCVAR_ARCHIVE ); + lookspring = gEngfuncs.pfnRegisterVariable ( "lookspring", "0", FCVAR_ARCHIVE ); + cl_anglespeedkey = gEngfuncs.pfnRegisterVariable ( "cl_anglespeedkey", "0.67", 0 ); + cl_yawspeed = gEngfuncs.pfnRegisterVariable ( "cl_yawspeed", "210", 0 ); + cl_pitchspeed = gEngfuncs.pfnRegisterVariable ( "cl_pitchspeed", "225", 0 ); + cl_upspeed = gEngfuncs.pfnRegisterVariable ( "cl_upspeed", "320", 0 ); + cl_forwardspeed = gEngfuncs.pfnRegisterVariable ( "cl_forwardspeed", "400", FCVAR_ARCHIVE ); + cl_backspeed = gEngfuncs.pfnRegisterVariable ( "cl_backspeed", "400", FCVAR_ARCHIVE ); + cl_sidespeed = gEngfuncs.pfnRegisterVariable ( "cl_sidespeed", "400", 0 ); + cl_movespeedkey = gEngfuncs.pfnRegisterVariable ( "cl_movespeedkey", "0.3", 0 ); + cl_pitchup = gEngfuncs.pfnRegisterVariable ( "cl_pitchup", "89", 0 ); + cl_pitchdown = gEngfuncs.pfnRegisterVariable ( "cl_pitchdown", "89", 0 ); + + cl_vsmoothing = gEngfuncs.pfnRegisterVariable ( "cl_vsmoothing", "0.05", FCVAR_ARCHIVE ); + + m_pitch = gEngfuncs.pfnRegisterVariable ( "m_pitch","0.022", FCVAR_ARCHIVE ); + m_yaw = gEngfuncs.pfnRegisterVariable ( "m_yaw","0.022", FCVAR_ARCHIVE ); + m_forward = gEngfuncs.pfnRegisterVariable ( "m_forward","1", FCVAR_ARCHIVE ); + m_side = gEngfuncs.pfnRegisterVariable ( "m_side","0.8", FCVAR_ARCHIVE ); + + // Initialize third person camera controls. + CAM_Init(); + // Initialize inputs + IN_Init(); + // Initialize keyboard + KB_Init(); + // Initialize view system + V_Init(); +} + +/* +============ +ShutdownInput +============ +*/ +void ShutdownInput (void) +{ + IN_Shutdown(); + KB_Shutdown(); +} + +#include "interface.h" +void CL_UnloadParticleMan( void ); + +#if defined( _TFC ) +void ClearEventList( void ); +#endif + +void CL_DLLEXPORT HUD_Shutdown( void ) +{ +// RecClShutdown(); + + ShutdownInput(); + +#if defined( _TFC ) + ClearEventList(); +#endif + + CL_UnloadParticleMan(); +} diff --git a/cl_dll/inputw32.cpp b/cl_dll/inputw32.cpp new file mode 100644 index 0000000..3c9e886 --- /dev/null +++ b/cl_dll/inputw32.cpp @@ -0,0 +1,1114 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// in_win.c -- windows 95 mouse and joystick code +// 02/21/97 JCB Added extended DirectInput code to support external controllers. + +#include "port.h" + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "../public/keydefs.h" +#include "view.h" +#include "Exports.h" + +#include +#include + +#define MOUSE_BUTTON_COUNT 5 + +// Set this to 1 to show mouse cursor. Experimental +int g_iVisibleMouse = 0; + +extern cl_enginefunc_t gEngfuncs; +extern int iMouseInUse; + +extern kbutton_t in_strafe; +extern kbutton_t in_mlook; +extern kbutton_t in_speed; +extern kbutton_t in_jlook; + +extern cvar_t *m_pitch; +extern cvar_t *m_yaw; +extern cvar_t *m_forward; +extern cvar_t *m_side; + +extern cvar_t *lookstrafe; +extern cvar_t *lookspring; +extern cvar_t *cl_pitchdown; +extern cvar_t *cl_pitchup; +extern cvar_t *cl_yawspeed; +extern cvar_t *cl_sidespeed; +extern cvar_t *cl_forwardspeed; +extern cvar_t *cl_pitchspeed; +extern cvar_t *cl_movespeedkey; + + +static double s_flRawInputUpdateTime = 0.0f; +static bool m_bRawInput = false; +static bool m_bMouseThread = false; +extern globalvars_t *gpGlobals; + +// mouse variables +cvar_t *m_filter; +cvar_t *sensitivity; + +// Custom mouse acceleration (0 disable, 1 to enable, 2 enable with separate yaw/pitch rescale) +static cvar_t *m_customaccel; +//Formula: mousesensitivity = ( rawmousedelta^m_customaccel_exponent ) * m_customaccel_scale + sensitivity +// If mode is 2, then x and y sensitivity are scaled by m_pitch and m_yaw respectively. +// Custom mouse acceleration value. +static cvar_t *m_customaccel_scale; +//Max mouse move scale factor, 0 for no limit +static cvar_t *m_customaccel_max; +//Mouse move is raised to this power before being scaled by scale factor +static cvar_t *m_customaccel_exponent; + +// if threaded mouse is enabled then the time to sleep between polls +static cvar_t *m_mousethread_sleep; + +int mouse_buttons; +int mouse_oldbuttonstate; +POINT current_pos; +int old_mouse_x, old_mouse_y, mx_accum, my_accum; +float mouse_x, mouse_y; + +static int restore_spi; +static int originalmouseparms[3], newmouseparms[3] = {0, 0, 1}; +static int mouseactive = 0; +int mouseinitialized; +static int mouseparmsvalid; +static int mouseshowtoggle = 1; + +// joystick defines and variables +// where should defines be moved? +#define JOY_ABSOLUTE_AXIS 0x00000000 // control like a joystick +#define JOY_RELATIVE_AXIS 0x00000010 // control like a mouse, spinner, trackball +#define JOY_MAX_AXES 6 // X, Y, Z, R, U, V +#define JOY_AXIS_X 0 +#define JOY_AXIS_Y 1 +#define JOY_AXIS_Z 2 +#define JOY_AXIS_R 3 +#define JOY_AXIS_U 4 +#define JOY_AXIS_V 5 + +enum _ControlList +{ + AxisNada = 0, + AxisForward, + AxisLook, + AxisSide, + AxisTurn +}; + + + +DWORD dwAxisMap[ JOY_MAX_AXES ]; +DWORD dwControlMap[ JOY_MAX_AXES ]; +int pdwRawValue[ JOY_MAX_AXES ]; +DWORD joy_oldbuttonstate, joy_oldpovstate; + +int joy_id; +DWORD joy_numbuttons; + +SDL_GameController *s_pJoystick = NULL; + +// none of these cvars are saved over a session +// this means that advanced controller configuration needs to be executed +// each time. this avoids any problems with getting back to a default usage +// or when changing from one controller to another. this way at least something +// works. +cvar_t *in_joystick; +cvar_t *joy_name; +cvar_t *joy_advanced; +cvar_t *joy_advaxisx; +cvar_t *joy_advaxisy; +cvar_t *joy_advaxisz; +cvar_t *joy_advaxisr; +cvar_t *joy_advaxisu; +cvar_t *joy_advaxisv; +cvar_t *joy_forwardthreshold; +cvar_t *joy_sidethreshold; +cvar_t *joy_pitchthreshold; +cvar_t *joy_yawthreshold; +cvar_t *joy_forwardsensitivity; +cvar_t *joy_sidesensitivity; +cvar_t *joy_pitchsensitivity; +cvar_t *joy_yawsensitivity; +cvar_t *joy_wwhack1; +cvar_t *joy_wwhack2; + +int joy_avail, joy_advancedinit, joy_haspov; + +#ifdef _WIN32 +DWORD s_hMouseThreadId = 0; +HANDLE s_hMouseThread = 0; +HANDLE s_hMouseQuitEvent = 0; +HANDLE s_hMouseDoneQuitEvent = 0; +#endif + +/* +=========== +Force_CenterView_f +=========== +*/ +void Force_CenterView_f (void) +{ + vec3_t viewangles; + + if (!iMouseInUse) + { + gEngfuncs.GetViewAngles( (float *)viewangles ); + viewangles[PITCH] = 0; + gEngfuncs.SetViewAngles( (float *)viewangles ); + } +} + +#ifdef _WIN32 +long s_mouseDeltaX = 0; +long s_mouseDeltaY = 0; +POINT old_mouse_pos; + +long ThreadInterlockedExchange( long *pDest, long value ) +{ + return InterlockedExchange( pDest, value ); +} + + +DWORD WINAPI MousePos_ThreadFunction( LPVOID p ) +{ + s_hMouseDoneQuitEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + + while ( 1 ) + { + if ( WaitForSingleObject( s_hMouseQuitEvent, (int)m_mousethread_sleep->value ) == WAIT_OBJECT_0 ) + { + return 0; + } + + if ( mouseactive ) + { + POINT mouse_pos; + GetCursorPos(&mouse_pos); + + volatile int mx = mouse_pos.x - old_mouse_pos.x + s_mouseDeltaX; + volatile int my = mouse_pos.y - old_mouse_pos.y + s_mouseDeltaY; + + ThreadInterlockedExchange( &old_mouse_pos.x, mouse_pos.x ); + ThreadInterlockedExchange( &old_mouse_pos.y, mouse_pos.y ); + + ThreadInterlockedExchange( &s_mouseDeltaX, mx ); + ThreadInterlockedExchange( &s_mouseDeltaY, my ); + } + } + + SetEvent( s_hMouseDoneQuitEvent ); + + return 0; +} +#endif + +/* +=========== +IN_ActivateMouse +=========== +*/ +void CL_DLLEXPORT IN_ActivateMouse (void) +{ + if (mouseinitialized) + { +#ifdef _WIN32 + if (mouseparmsvalid) + restore_spi = SystemParametersInfo (SPI_SETMOUSE, 0, newmouseparms, 0); + +#endif + mouseactive = 1; + } +} + + +/* +=========== +IN_DeactivateMouse +=========== +*/ +void CL_DLLEXPORT IN_DeactivateMouse (void) +{ + if (mouseinitialized) + { +#ifdef _WIN32 + if (restore_spi) + SystemParametersInfo (SPI_SETMOUSE, 0, originalmouseparms, 0); + +#endif + + mouseactive = 0; + } +} + +/* +=========== +IN_StartupMouse +=========== +*/ +void IN_StartupMouse (void) +{ + if ( gEngfuncs.CheckParm ("-nomouse", NULL ) ) + return; + + mouseinitialized = 1; +#ifdef _WIN32 + mouseparmsvalid = SystemParametersInfo (SPI_GETMOUSE, 0, originalmouseparms, 0); + + if (mouseparmsvalid) + { + if ( gEngfuncs.CheckParm ("-noforcemspd", NULL ) ) + newmouseparms[2] = originalmouseparms[2]; + + if ( gEngfuncs.CheckParm ("-noforcemaccel", NULL ) ) + { + newmouseparms[0] = originalmouseparms[0]; + newmouseparms[1] = originalmouseparms[1]; + } + + if ( gEngfuncs.CheckParm ("-noforcemparms", NULL ) ) + { + newmouseparms[0] = originalmouseparms[0]; + newmouseparms[1] = originalmouseparms[1]; + newmouseparms[2] = originalmouseparms[2]; + } + } +#endif + + mouse_buttons = MOUSE_BUTTON_COUNT; +} + +/* +=========== +IN_Shutdown +=========== +*/ +void IN_Shutdown (void) +{ + IN_DeactivateMouse (); + +#ifdef _WIN32 + if ( s_hMouseQuitEvent ) + { + SetEvent( s_hMouseQuitEvent ); + WaitForSingleObject( s_hMouseDoneQuitEvent, 100 ); + } + + if ( s_hMouseThread ) + { + TerminateThread( s_hMouseThread, 0 ); + CloseHandle( s_hMouseThread ); + s_hMouseThread = (HANDLE)0; + } + + if ( s_hMouseQuitEvent ) + { + CloseHandle( s_hMouseQuitEvent ); + s_hMouseQuitEvent = (HANDLE)0; + } + + + if ( s_hMouseDoneQuitEvent ) + { + CloseHandle( s_hMouseDoneQuitEvent ); + s_hMouseDoneQuitEvent = (HANDLE)0; + } +#endif +} + +/* +=========== +IN_GetMousePos + +Ask for mouse position from engine +=========== +*/ +void IN_GetMousePos( int *mx, int *my ) +{ + gEngfuncs.GetMousePosition( mx, my ); +} + +/* +=========== +IN_ResetMouse + +FIXME: Call through to engine? +=========== +*/ +void IN_ResetMouse( void ) +{ + // no work to do in SDL +#ifdef _WIN32 + if ( !m_bRawInput && mouseactive && gEngfuncs.GetWindowCenterX && gEngfuncs.GetWindowCenterY ) + { + + SetCursorPos ( gEngfuncs.GetWindowCenterX(), gEngfuncs.GetWindowCenterY() ); + ThreadInterlockedExchange( &old_mouse_pos.x, gEngfuncs.GetWindowCenterX() ); + ThreadInterlockedExchange( &old_mouse_pos.y, gEngfuncs.GetWindowCenterY() ); + } + + if ( gpGlobals && gpGlobals->time - s_flRawInputUpdateTime > 1.0f ) + { + s_flRawInputUpdateTime = gpGlobals->time; + m_bRawInput = CVAR_GET_FLOAT( "m_rawinput" ) != 0; + } +#endif +} + +/* +=========== +IN_MouseEvent +=========== +*/ +void CL_DLLEXPORT IN_MouseEvent (int mstate) +{ + int i; + + if ( iMouseInUse || g_iVisibleMouse ) + return; + + // perform button actions + for (i=0 ; ivalue; + + // Using special accleration values + if ( m_customaccel->value != 0 ) + { + float raw_mouse_movement_distance = sqrt( mx * mx + my * my ); + float acceleration_scale = m_customaccel_scale->value; + float accelerated_sensitivity_max = m_customaccel_max->value; + float accelerated_sensitivity_exponent = m_customaccel_exponent->value; + float accelerated_sensitivity = ( (float)pow( raw_mouse_movement_distance, accelerated_sensitivity_exponent ) * acceleration_scale + mouse_senstivity ); + + if ( accelerated_sensitivity_max > 0.0001f && + accelerated_sensitivity > accelerated_sensitivity_max ) + { + accelerated_sensitivity = accelerated_sensitivity_max; + } + + *x *= accelerated_sensitivity; + *y *= accelerated_sensitivity; + + // Further re-scale by yaw and pitch magnitude if user requests alternate mode 2 + // This means that they will need to up their value for m_customaccel_scale greatly (>40x) since m_pitch/yaw default + // to 0.022 + if ( m_customaccel->value == 2 ) + { + *x *= m_yaw->value; + *y *= m_pitch->value; + } + } + else + { + // Just apply the default + *x *= mouse_senstivity; + *y *= mouse_senstivity; + } +} + +/* +=========== +IN_MouseMove +=========== +*/ +void IN_MouseMove ( float frametime, usercmd_t *cmd) +{ + int mx, my; + vec3_t viewangles; + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + if ( in_mlook.state & 1) + { + V_StopPitchDrift (); + } + + //jjb - this disbles normal mouse control if the user is trying to + // move the camera, or if the mouse cursor is visible or if we're in intermission + if ( !iMouseInUse && !gHUD.m_iIntermission && !g_iVisibleMouse ) + { + int deltaX, deltaY; +#ifdef _WIN32 + if ( !m_bRawInput ) + { + if ( m_bMouseThread ) + { + ThreadInterlockedExchange( ¤t_pos.x, s_mouseDeltaX ); + ThreadInterlockedExchange( ¤t_pos.y, s_mouseDeltaY ); + ThreadInterlockedExchange( &s_mouseDeltaX, 0 ); + ThreadInterlockedExchange( &s_mouseDeltaY, 0 ); + } + else + { + GetCursorPos (¤t_pos); + } + } + else +#endif + { + SDL_GetRelativeMouseState( &deltaX, &deltaY ); + current_pos.x = deltaX; + current_pos.y = deltaY; + } + +#ifdef _WIN32 + if ( !m_bRawInput ) + { + if ( m_bMouseThread ) + { + mx = current_pos.x; + my = current_pos.y; + } + else + { + mx = current_pos.x - gEngfuncs.GetWindowCenterX() + mx_accum; + my = current_pos.y - gEngfuncs.GetWindowCenterY() + my_accum; + } + } + else +#endif + { + mx = deltaX + mx_accum; + my = deltaY + my_accum; + } + + mx_accum = 0; + my_accum = 0; + + if (m_filter && m_filter->value) + { + mouse_x = (mx + old_mouse_x) * 0.5; + mouse_y = (my + old_mouse_y) * 0.5; + } + else + { + mouse_x = mx; + mouse_y = my; + } + + old_mouse_x = mx; + old_mouse_y = my; + + // Apply custom mouse scaling/acceleration + IN_ScaleMouse( &mouse_x, &mouse_y ); + + // add mouse X/Y movement to cmd + if ( (in_strafe.state & 1) || (lookstrafe->value && (in_mlook.state & 1) )) + cmd->sidemove += m_side->value * mouse_x; + else + viewangles[YAW] -= m_yaw->value * mouse_x; + + if ( (in_mlook.state & 1) && !(in_strafe.state & 1)) + { + viewangles[PITCH] += m_pitch->value * mouse_y; + if (viewangles[PITCH] > cl_pitchdown->value) + viewangles[PITCH] = cl_pitchdown->value; + if (viewangles[PITCH] < -cl_pitchup->value) + viewangles[PITCH] = -cl_pitchup->value; + } + else + { + if ((in_strafe.state & 1) && gEngfuncs.IsNoClipping() ) + { + cmd->upmove -= m_forward->value * mouse_y; + } + else + { + cmd->forwardmove -= m_forward->value * mouse_y; + } + } + + // if the mouse has moved, force it to the center, so there's room to move + if ( mx || my ) + { + IN_ResetMouse(); + } + } + + gEngfuncs.SetViewAngles( (float *)viewangles ); + +/* +//#define TRACE_TEST +#if defined( TRACE_TEST ) + { + int mx, my; + void V_Move( int mx, int my ); + IN_GetMousePos( &mx, &my ); + V_Move( mx, my ); + } +#endif +*/ +} + +/* +=========== +IN_Accumulate +=========== +*/ +void CL_DLLEXPORT IN_Accumulate (void) +{ + //only accumulate mouse if we are not moving the camera with the mouse + if ( !iMouseInUse && !g_iVisibleMouse) + { + if (mouseactive) + { +#ifdef _WIN32 + if ( !m_bRawInput ) + { + if ( !m_bMouseThread ) + { + GetCursorPos (¤t_pos); + + mx_accum += current_pos.x - gEngfuncs.GetWindowCenterX(); + my_accum += current_pos.y - gEngfuncs.GetWindowCenterY(); + } + } + else +#endif + { + int deltaX, deltaY; + SDL_GetRelativeMouseState( &deltaX, &deltaY ); + mx_accum += deltaX; + my_accum += deltaY; + } + // force the mouse to the center, so there's room to move + IN_ResetMouse(); + + } + } + +} + +/* +=================== +IN_ClearStates +=================== +*/ +void CL_DLLEXPORT IN_ClearStates (void) +{ + if ( !mouseactive ) + return; + + mx_accum = 0; + my_accum = 0; + mouse_oldbuttonstate = 0; +} + +/* +=============== +IN_StartupJoystick +=============== +*/ +void IN_StartupJoystick (void) +{ + // abort startup if user requests no joystick + if ( gEngfuncs.CheckParm ("-nojoy", NULL ) ) + return; + + // assume no joystick + joy_avail = 0; + + int nJoysticks = SDL_NumJoysticks(); + if ( nJoysticks > 0 ) + { + for ( int i = 0; i < nJoysticks; i++ ) + { + if ( SDL_IsGameController( i ) ) + { + s_pJoystick = SDL_GameControllerOpen( i ); + if ( s_pJoystick ) + { + //save the joystick's number of buttons and POV status + joy_numbuttons = SDL_CONTROLLER_BUTTON_MAX; + joy_haspov = 0; + + // old button and POV states default to no buttons pressed + joy_oldbuttonstate = joy_oldpovstate = 0; + + // mark the joystick as available and advanced initialization not completed + // this is needed as cvars are not available during initialization + gEngfuncs.Con_Printf ("joystick found\n\n", SDL_GameControllerName(s_pJoystick)); + joy_avail = 1; + joy_advancedinit = 0; + break; + } + + } + } + } + else + { + gEngfuncs.Con_DPrintf ("joystick not found -- driver not present\n\n"); + } + +} + + +int RawValuePointer (int axis) +{ + switch (axis) + { + default: + case JOY_AXIS_X: + return SDL_GameControllerGetAxis( s_pJoystick, SDL_CONTROLLER_AXIS_LEFTX ); + case JOY_AXIS_Y: + return SDL_GameControllerGetAxis( s_pJoystick, SDL_CONTROLLER_AXIS_LEFTY ); + case JOY_AXIS_Z: + return SDL_GameControllerGetAxis( s_pJoystick, SDL_CONTROLLER_AXIS_RIGHTX ); + case JOY_AXIS_R: + return SDL_GameControllerGetAxis( s_pJoystick, SDL_CONTROLLER_AXIS_RIGHTY ); + + } +} + +/* +=========== +Joy_AdvancedUpdate_f +=========== +*/ +void Joy_AdvancedUpdate_f (void) +{ + + // called once by IN_ReadJoystick and by user whenever an update is needed + // cvars are now available + int i; + DWORD dwTemp; + + // initialize all the maps + for (i = 0; i < JOY_MAX_AXES; i++) + { + dwAxisMap[i] = AxisNada; + dwControlMap[i] = JOY_ABSOLUTE_AXIS; + pdwRawValue[i] = RawValuePointer(i); + } + + if( joy_advanced->value == 0.0) + { + // default joystick initialization + // 2 axes only with joystick control + dwAxisMap[JOY_AXIS_X] = AxisTurn; + // dwControlMap[JOY_AXIS_X] = JOY_ABSOLUTE_AXIS; + dwAxisMap[JOY_AXIS_Y] = AxisForward; + // dwControlMap[JOY_AXIS_Y] = JOY_ABSOLUTE_AXIS; + } + else + { + if ( strcmp ( joy_name->string, "joystick") != 0 ) + { + // notify user of advanced controller + gEngfuncs.Con_Printf ("\n%s configured\n\n", joy_name->string); + } + + // advanced initialization here + // data supplied by user via joy_axisn cvars + dwTemp = (DWORD) joy_advaxisx->value; + dwAxisMap[JOY_AXIS_X] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_X] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisy->value; + dwAxisMap[JOY_AXIS_Y] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_Y] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisz->value; + dwAxisMap[JOY_AXIS_Z] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_Z] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisr->value; + dwAxisMap[JOY_AXIS_R] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_R] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisu->value; + dwAxisMap[JOY_AXIS_U] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_U] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisv->value; + dwAxisMap[JOY_AXIS_V] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_V] = dwTemp & JOY_RELATIVE_AXIS; + } +} + + +/* +=========== +IN_Commands +=========== +*/ +void IN_Commands (void) +{ + int i, key_index; + + if (!joy_avail) + { + return; + } + + DWORD buttonstate, povstate; + + // loop through the joystick buttons + // key a joystick event or auxillary event for higher number buttons for each state change + buttonstate = 0; + for ( i = 0; i < SDL_CONTROLLER_BUTTON_MAX; i++ ) + { + if ( SDL_GameControllerGetButton( s_pJoystick, (SDL_GameControllerButton)i ) ) + { + buttonstate |= 1<value) + { + return; + } + + // collect the joystick data, if possible + if (IN_ReadJoystick () != 1) + { + return; + } + + if (in_speed.state & 1) + speed = cl_movespeedkey->value; + else + speed = 1; + + aspeed = speed * frametime; + + // loop through the axes + for (i = 0; i < JOY_MAX_AXES; i++) + { + // get the floating point zero-centered, potentially-inverted data for the current axis + fAxisValue = (float)pdwRawValue[i]; + + if (joy_wwhack2->value != 0.0) + { + if (dwAxisMap[i] == AxisTurn) + { + // this is a special formula for the Logitech WingMan Warrior + // y=ax^b; where a = 300 and b = 1.3 + // also x values are in increments of 800 (so this is factored out) + // then bounds check result to level out excessively high spin rates + fTemp = 300.0 * pow(abs(fAxisValue) / 800.0, 1.3); + if (fTemp > 14000.0) + fTemp = 14000.0; + // restore direction information + fAxisValue = (fAxisValue > 0.0) ? fTemp : -fTemp; + } + } + + // convert range from -32768..32767 to -1..1 + fAxisValue /= 32768.0; + + switch (dwAxisMap[i]) + { + case AxisForward: + if ((joy_advanced->value == 0.0) && (in_jlook.state & 1)) + { + // user wants forward control to become look control + if (fabs(fAxisValue) > joy_pitchthreshold->value) + { + // if mouse invert is on, invert the joystick pitch value + // only absolute control support here (joy_advanced is 0) + if (m_pitch->value < 0.0) + { + viewangles[PITCH] -= (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; + } + else + { + viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; + } + V_StopPitchDrift(); + } + else + { + // no pitch movement + // disable pitch return-to-center unless requested by user + // *** this code can be removed when the lookspring bug is fixed + // *** the bug always has the lookspring feature on + if(lookspring->value == 0.0) + { + V_StopPitchDrift(); + } + } + } + else + { + // user wants forward control to be forward control + if (fabs(fAxisValue) > joy_forwardthreshold->value) + { + cmd->forwardmove += (fAxisValue * joy_forwardsensitivity->value) * speed * cl_forwardspeed->value; + } + } + break; + + case AxisSide: + if (fabs(fAxisValue) > joy_sidethreshold->value) + { + cmd->sidemove += (fAxisValue * joy_sidesensitivity->value) * speed * cl_sidespeed->value; + } + break; + + case AxisTurn: + if ((in_strafe.state & 1) || (lookstrafe->value && (in_jlook.state & 1))) + { + // user wants turn control to become side control + if (fabs(fAxisValue) > joy_sidethreshold->value) + { + cmd->sidemove -= (fAxisValue * joy_sidesensitivity->value) * speed * cl_sidespeed->value; + } + } + else + { + // user wants turn control to be turn control + if (fabs(fAxisValue) > joy_yawthreshold->value) + { + if(dwControlMap[i] == JOY_ABSOLUTE_AXIS) + { + viewangles[YAW] += (fAxisValue * joy_yawsensitivity->value) * aspeed * cl_yawspeed->value; + } + else + { + viewangles[YAW] += (fAxisValue * joy_yawsensitivity->value) * speed * 180.0; + } + + } + } + break; + + case AxisLook: + if (in_jlook.state & 1) + { + if (fabs(fAxisValue) > joy_pitchthreshold->value) + { + // pitch movement detected and pitch movement desired by user + if(dwControlMap[i] == JOY_ABSOLUTE_AXIS) + { + viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; + } + else + { + viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * speed * 180.0; + } + V_StopPitchDrift(); + } + else + { + // no pitch movement + // disable pitch return-to-center unless requested by user + // *** this code can be removed when the lookspring bug is fixed + // *** the bug always has the lookspring feature on + if( lookspring->value == 0.0 ) + { + V_StopPitchDrift(); + } + } + } + break; + + default: + break; + } + } + + // bounds check pitch + if (viewangles[PITCH] > cl_pitchdown->value) + viewangles[PITCH] = cl_pitchdown->value; + if (viewangles[PITCH] < -cl_pitchup->value) + viewangles[PITCH] = -cl_pitchup->value; + + gEngfuncs.SetViewAngles( (float *)viewangles ); +} + +/* +=========== +IN_Move +=========== +*/ +void IN_Move ( float frametime, usercmd_t *cmd) +{ + if ( !iMouseInUse && mouseactive ) + { + IN_MouseMove ( frametime, cmd); + } + + IN_JoyMove ( frametime, cmd); +} + +/* +=========== +IN_Init +=========== +*/ +void IN_Init (void) +{ + m_filter = gEngfuncs.pfnRegisterVariable ( "m_filter","0", FCVAR_ARCHIVE ); + sensitivity = gEngfuncs.pfnRegisterVariable ( "sensitivity","3", FCVAR_ARCHIVE ); // user mouse sensitivity setting. + + in_joystick = gEngfuncs.pfnRegisterVariable ( "joystick","0", FCVAR_ARCHIVE ); + joy_name = gEngfuncs.pfnRegisterVariable ( "joyname", "joystick", 0 ); + joy_advanced = gEngfuncs.pfnRegisterVariable ( "joyadvanced", "0", 0 ); + joy_advaxisx = gEngfuncs.pfnRegisterVariable ( "joyadvaxisx", "0", 0 ); + joy_advaxisy = gEngfuncs.pfnRegisterVariable ( "joyadvaxisy", "0", 0 ); + joy_advaxisz = gEngfuncs.pfnRegisterVariable ( "joyadvaxisz", "0", 0 ); + joy_advaxisr = gEngfuncs.pfnRegisterVariable ( "joyadvaxisr", "0", 0 ); + joy_advaxisu = gEngfuncs.pfnRegisterVariable ( "joyadvaxisu", "0", 0 ); + joy_advaxisv = gEngfuncs.pfnRegisterVariable ( "joyadvaxisv", "0", 0 ); + joy_forwardthreshold = gEngfuncs.pfnRegisterVariable ( "joyforwardthreshold", "0.15", 0 ); + joy_sidethreshold = gEngfuncs.pfnRegisterVariable ( "joysidethreshold", "0.15", 0 ); + joy_pitchthreshold = gEngfuncs.pfnRegisterVariable ( "joypitchthreshold", "0.15", 0 ); + joy_yawthreshold = gEngfuncs.pfnRegisterVariable ( "joyyawthreshold", "0.15", 0 ); + joy_forwardsensitivity = gEngfuncs.pfnRegisterVariable ( "joyforwardsensitivity", "-1.0", 0 ); + joy_sidesensitivity = gEngfuncs.pfnRegisterVariable ( "joysidesensitivity", "-1.0", 0 ); + joy_pitchsensitivity = gEngfuncs.pfnRegisterVariable ( "joypitchsensitivity", "1.0", 0 ); + joy_yawsensitivity = gEngfuncs.pfnRegisterVariable ( "joyyawsensitivity", "-1.0", 0 ); + joy_wwhack1 = gEngfuncs.pfnRegisterVariable ( "joywwhack1", "0.0", 0 ); + joy_wwhack2 = gEngfuncs.pfnRegisterVariable ( "joywwhack2", "0.0", 0 ); + + m_customaccel = gEngfuncs.pfnRegisterVariable ( "m_customaccel", "0", FCVAR_ARCHIVE ); + m_customaccel_scale = gEngfuncs.pfnRegisterVariable ( "m_customaccel_scale", "0.04", FCVAR_ARCHIVE ); + m_customaccel_max = gEngfuncs.pfnRegisterVariable ( "m_customaccel_max", "0", FCVAR_ARCHIVE ); + m_customaccel_exponent = gEngfuncs.pfnRegisterVariable ( "m_customaccel_exponent", "1", FCVAR_ARCHIVE ); + +#ifdef _WIN32 + m_bRawInput = CVAR_GET_FLOAT( "m_rawinput" ) > 0; + m_bMouseThread = gEngfuncs.CheckParm ("-mousethread", NULL ) != NULL; + m_mousethread_sleep = gEngfuncs.pfnRegisterVariable ( "m_mousethread_sleep", "10", FCVAR_ARCHIVE ); + + if ( !m_bRawInput && m_bMouseThread && m_mousethread_sleep ) + { + s_mouseDeltaX = s_mouseDeltaY = 0; + + s_hMouseQuitEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + if ( s_hMouseQuitEvent ) + { + s_hMouseThread = CreateThread( NULL, 0, MousePos_ThreadFunction, NULL, 0, &s_hMouseThreadId ); + } + } +#endif + + gEngfuncs.pfnAddCommand ("force_centerview", Force_CenterView_f); + gEngfuncs.pfnAddCommand ("joyadvancedupdate", Joy_AdvancedUpdate_f); + + IN_StartupMouse (); + IN_StartupJoystick (); +} diff --git a/cl_dll/interpolation.cpp b/cl_dll/interpolation.cpp new file mode 100644 index 0000000..26d2a4a --- /dev/null +++ b/cl_dll/interpolation.cpp @@ -0,0 +1,222 @@ +/************ (C) Copyright 2003 Valve, L.L.C. All rights reserved. *********** +** +** The copyright to the contents herein is the property of Valve, L.L.C. +** The contents may be used and/or copied only with the written permission of +** Valve, L.L.C., or in accordance with the terms and conditions stipulated in +** the agreement/contract under which the contents have been supplied. +** +******************************************************************************* +** +** Contents: +** +** interpolation.cpp: implementation of the interpolation class +** +******************************************************************************/ + +#include "hud.h" +#include "cl_util.h" +#include "interpolation.h" + +// = determinant of matrix a,b,c +#define Determinant(a,b,c) ( (a)[2] * ( (b)[0]*(c)[1] - (b)[1]*(c)[0] ) + \ + (a)[1] * ( (b)[2]*(c)[0] - (b)[0]*(c)[2] ) + \ + (a)[0] * ( (b)[1]*(c)[2] - (b)[2]*(c)[1] ) ) + +// slove 3 vector linear system of equations v0 = x*v1 + y*v2 + z*v3 (if possible) +bool SolveLSE (vec3_t v0, vec3_t v1, vec3_t v2, vec3_t v3, float * x, float * y, float * z) +{ + float d = Determinant(v1,v2,v3); + + if (d==0.0f) + return false; + + if ( x ) + *x = Determinant(v0,v2,v3) / d; + + if ( y ) + *y= Determinant(v1,v0,v3) / d; + + if ( z ) + *z= Determinant(v1,v2,v0) / d; + + return true; +} + +// p = closest point between vector lines a1+x*m1 and a2+x*m2 +bool GetPointBetweenLines(vec3_t &p, vec3_t a1, vec3_t m1, vec3_t a2, vec3_t m2 ) +{ + float x,z; + + vec3_t t1 = CrossProduct(m1, m2); + vec3_t t2 = a2 - a1; + + if ( !SolveLSE( t2, m1, t1, m2, &x , NULL, &z ) ) + return false; + + t1 = a1 + x*m1; + t2 = a2 + (-z)*m2; + + p = ( t1 + t2 ) / 2.0f; + + return true; +} + +// Bernstein Poynom B(u) with n = 2, i = 0 +#define BernsteinPolynom20(u) ((1.0f-u)*(1.0f-u)) +#define BernsteinPolynom21(u) (2.0f*u*(1.0f-u)) +#define BernsteinPolynom22(u) (u*u) + +CInterpolation::CInterpolation() +{ +} + +CInterpolation::~CInterpolation() +{ + m_SmoothStart = m_SmoothEnd = false; +} + +void CInterpolation::SetViewAngles( vec3_t start, vec3_t end ) +{ + m_StartAngle = start; + m_EndAngle = end; + NormalizeAngles( m_StartAngle ); + NormalizeAngles( m_EndAngle ); +} + +void CInterpolation::SetFOVs(float start, float end) +{ + m_StartFov = start; + m_EndFov = end; +} + +void CInterpolation::SetWaypoints( vec3_t * prev, vec3_t start, vec3_t end, vec3_t * next) +{ + m_StartPoint = start; + m_EndPoint = end; + + + vec3_t a,b,c,d; + + if ( !prev && !next ) + { + // no direction given, straight linear interpolation + m_Center = (m_StartPoint + m_EndPoint) / 2.0f; + } + else if ( !prev ) + { + a = start - end; + float dist = a.Length() / 2.0f; + a = a.Normalize(); + b = *next - end; + b = b.Normalize(); + c = a - b; + c = c.Normalize(); + m_Center = end + c*dist; + + } + else if ( !next ) + { + a = *prev - start; + a = a.Normalize(); + b = end - start; + float dist = b.Length() / 2.0f; + b = b.Normalize(); + c = b - a; + c = c.Normalize(); + m_Center = start + c*dist; + } + else + { + // we have a previous and a next point, great! + a = *prev - start; + a = a.Normalize(); + b = end - start; + b = b.Normalize(); + c = b - a; + + a = start - end; + a = a.Normalize(); + b = *next - end; + b = b.Normalize(); + d = a - b; + + GetPointBetweenLines( m_Center, start, c, end, d); + } +} + +void CInterpolation::Interpolate( float t, vec3_t &point, vec3_t &angle, float * fov) +{ + + if ( m_SmoothStart && m_SmoothEnd ) + { + t = (1.0f-t)*(t*t)+t*(1.0f-((t-1.0f)*(t-1.0f))); + } + else if ( m_SmoothStart ) + { + t = t*t; + } + else if ( m_SmoothEnd ) + { + t = t - 1.0f; + t = -(t*t)+1; + } + + if ( point ) + { + BezierInterpolatePoint(t, point); + } + + if ( angle ) + { + InterpolateAngle(t, angle); + } + + if ( fov ) + { + *fov = m_StartFov + (t * (m_EndFov-m_StartFov)); + } +} + +void CInterpolation::BezierInterpolatePoint( float t, vec3_t &point ) +{ + point = m_StartPoint * BernsteinPolynom20(t); + point = point + m_Center * BernsteinPolynom21(t); + point = point + m_EndPoint * BernsteinPolynom22(t); +} + +void CInterpolation::SetSmoothing(bool start, bool end) +{ + m_SmoothStart = start; + m_SmoothEnd = end; + +} + +void CInterpolation::InterpolateAngle( float t, vec3_t &angle ) +{ + int i; + float ang1, ang2; + float d; + + for ( i = 0 ; i < 3 ; i++ ) + { + ang1 = m_StartAngle[i]; + ang2 = m_EndAngle[i]; + + d = ang2 - ang1; + if ( d > 180 ) + { + d -= 360; + } + else if ( d < -180 ) + { + d += 360; + } + + angle[i] = ang1 + d * t; + } + + NormalizeAngles( angle ); +} + + + diff --git a/cl_dll/interpolation.h b/cl_dll/interpolation.h new file mode 100644 index 0000000..4ee5e0d --- /dev/null +++ b/cl_dll/interpolation.h @@ -0,0 +1,56 @@ +/************ (C) Copyright 2003 Valve, L.L.C. All rights reserved. *********** +** +** The copyright to the contents herein is the property of Valve, L.L.C. +** The contents may be used and/or copied only with the written permission of +** Valve, L.L.C., or in accordance with the terms and conditions stipulated in +** the agreement/contract under which the contents have been supplied. +** +******************************************************************************* +** +** Contents: +** +** interpolation.h: Bezier inpolation classes +** +******************************************************************************/ + +#ifndef INTERPOLATION_H +#define INTERPOLATION_H +#ifdef _WIN32 +#pragma once +#endif + + +// interpolation class +class CInterpolation +{ +public: + + CInterpolation(); + virtual ~CInterpolation(); + + void SetWaypoints(vec3_t * prev, vec3_t start, vec3_t end, vec3_t * next); + void SetViewAngles( vec3_t start, vec3_t end ); + void SetFOVs(float start, float end); + void SetSmoothing(bool start, bool end); + + // get interpolated point 0 =< t =< 1, 0 = start, 1 = end + void Interpolate(float t, vec3_t &point, vec3_t &angle, float * fov); + +protected: + + void BezierInterpolatePoint( float t, vec3_t &point ); + void InterpolateAngle( float t, vec3_t &angle ); + + vec3_t m_StartPoint; + vec3_t m_EndPoint; + vec3_t m_StartAngle; + vec3_t m_EndAngle; + vec3_t m_Center; + float m_StartFov; + float m_EndFov; + + bool m_SmoothStart; + bool m_SmoothEnd; +}; + +#endif // INTERPOLATION_H \ No newline at end of file diff --git a/cl_dll/kbutton.h b/cl_dll/kbutton.h new file mode 100644 index 0000000..23cd8ac --- /dev/null +++ b/cl_dll/kbutton.h @@ -0,0 +1,18 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( KBUTTONH ) +#define KBUTTONH +#pragma once + +typedef struct kbutton_s +{ + int down[2]; // key nums holding it down + int state; // low bit is down state +} kbutton_t; + +#endif // !KBUTTONH \ No newline at end of file diff --git a/cl_dll/menu.cpp b/cl_dll/menu.cpp new file mode 100644 index 0000000..82f991a --- /dev/null +++ b/cl_dll/menu.cpp @@ -0,0 +1,283 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// menu.cpp +// +// generic menu handler +// +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "vgui_TeamFortressViewport.h" + +#define MAX_MENU_STRING 512 +char g_szMenuString[MAX_MENU_STRING]; +char g_szPrelocalisedMenuString[MAX_MENU_STRING]; + +int KB_ConvertString( char *in, char **ppout ); + +DECLARE_MESSAGE( m_Menu, ShowMenu ); + +int CHudMenu :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( ShowMenu ); + + InitHUDData(); + + return 1; +} + +void CHudMenu :: InitHUDData( void ) +{ + m_fMenuDisplayed = 0; + m_bitsValidSlots = 0; + Reset(); +} + +void CHudMenu :: Reset( void ) +{ + g_szPrelocalisedMenuString[0] = 0; + m_fWaitingForMore = FALSE; +} + +int CHudMenu :: VidInit( void ) +{ + return 1; +} + + +/*================================= + ParseEscapeToken + + Interprets the given escape token (backslash followed by a letter). The + first character of the token must be a backslash. The second character + specifies the operation to perform: + + \w : White text (this is the default) + \d : Dim (gray) text + \y : Yellow text + \r : Red text + \R : Right-align (just for the remainder of the current line) +=================================*/ + +static int menu_r, menu_g, menu_b, menu_x, menu_ralign; + +static inline const char* ParseEscapeToken( const char* token ) +{ + if ( *token != '\\' ) + return token; + + token++; + + switch ( *token ) + { + case '\0': + return token; + + case 'w': + menu_r = 255; + menu_g = 255; + menu_b = 255; + break; + + case 'd': + menu_r = 100; + menu_g = 100; + menu_b = 100; + break; + + case 'y': + menu_r = 255; + menu_g = 210; + menu_b = 64; + break; + + case 'r': + menu_r = 210; + menu_g = 24; + menu_b = 0; + break; + + case 'R': + menu_x = ScreenWidth/2; + menu_ralign = TRUE; + break; + } + + return ++token; +} + + +int CHudMenu :: Draw( float flTime ) +{ + // check for if menu is set to disappear + if ( m_flShutoffTime > 0 ) + { + if ( m_flShutoffTime <= gHUD.m_flTime ) + { // times up, shutoff + m_fMenuDisplayed = 0; + m_iFlags &= ~HUD_ACTIVE; + return 1; + } + } + + // don't draw the menu if the scoreboard is being shown + if ( gViewPort && gViewPort->IsScoreBoardVisible() ) + return 1; + + // draw the menu, along the left-hand side of the screen + + // count the number of newlines + int nlc = 0; + int i; + for ( i = 0; i < MAX_MENU_STRING && g_szMenuString[i] != '\0'; i++ ) + { + if ( g_szMenuString[i] == '\n' ) + nlc++; + } + + // center it + int y = (ScreenHeight/2) - ((nlc/2)*12) - 40; // make sure it is above the say text + + menu_r = 255; + menu_g = 255; + menu_b = 255; + menu_x = 20; + menu_ralign = FALSE; + + const char* sptr = g_szMenuString; + + while ( *sptr != '\0' ) + { + if ( *sptr == '\\' ) + { + sptr = ParseEscapeToken( sptr ); + } + else if ( *sptr == '\n' ) + { + menu_ralign = FALSE; + menu_x = 20; + y += (12); + + sptr++; + } + else + { + char menubuf[ 80 ]; + const char *ptr = sptr; + while ( *sptr != '\0' && *sptr != '\n' && *sptr != '\\') + { + sptr++; + } + strncpy( menubuf, ptr, min( ( sptr - ptr), (int)sizeof( menubuf ) )); + menubuf[ min( ( sptr - ptr), (int)(sizeof( menubuf )-1) ) ] = '\0'; + + if ( menu_ralign ) + { + // IMPORTANT: Right-to-left rendered text does not parse escape tokens! + menu_x = gHUD.DrawHudStringReverse( menu_x, y, 0, menubuf, menu_r, menu_g, menu_b ); + } + else + { + menu_x = gHUD.DrawHudString( menu_x, y, 320, menubuf, menu_r, menu_g, menu_b ); + } + } + } + + return 1; +} + +// selects an item from the menu +void CHudMenu :: SelectMenuItem( int menu_item ) +{ + // if menu_item is in a valid slot, send a menuselect command to the server + if ( (menu_item > 0) && (m_bitsValidSlots & (1 << (menu_item-1))) ) + { + char szbuf[32]; + sprintf( szbuf, "menuselect %d\n", menu_item ); + EngineClientCmd( szbuf ); + + // remove the menu + m_fMenuDisplayed = 0; + m_iFlags &= ~HUD_ACTIVE; + } +} + + +// Message handler for ShowMenu message +// takes four values: +// short: a bitfield of keys that are valid input +// char : the duration, in seconds, the menu should stay up. -1 means is stays until something is chosen. +// byte : a boolean, TRUE if there is more string yet to be received before displaying the menu, FALSE if it's the last string +// string: menu string to display +// if this message is never received, then scores will simply be the combined totals of the players. +int CHudMenu :: MsgFunc_ShowMenu( const char *pszName, int iSize, void *pbuf ) +{ + char *temp = NULL; + + BEGIN_READ( pbuf, iSize ); + + m_bitsValidSlots = READ_SHORT(); + int DisplayTime = READ_CHAR(); + int NeedMore = READ_BYTE(); + + if ( DisplayTime > 0 ) + m_flShutoffTime = DisplayTime + gHUD.m_flTime; + else + m_flShutoffTime = -1; + + if ( m_bitsValidSlots ) + { + if ( !m_fWaitingForMore ) // this is the start of a new menu + { + strncpy( g_szPrelocalisedMenuString, READ_STRING(), MAX_MENU_STRING ); + } + else + { // append to the current menu string + strncat( g_szPrelocalisedMenuString, READ_STRING(), MAX_MENU_STRING - strlen(g_szPrelocalisedMenuString) ); + } + g_szPrelocalisedMenuString[MAX_MENU_STRING-1] = 0; // ensure null termination (strncat/strncpy does not) + + if ( !NeedMore ) + { // we have the whole string, so we can localise it now + strcpy( g_szMenuString, gHUD.m_TextMessage.BufferedLocaliseTextString( g_szPrelocalisedMenuString ) ); + + // Swap in characters + if ( KB_ConvertString( g_szMenuString, &temp ) ) + { + strcpy( g_szMenuString, temp ); + free( temp ); + } + } + + m_fMenuDisplayed = 1; + m_iFlags |= HUD_ACTIVE; + } + else + { + m_fMenuDisplayed = 0; // no valid slots means that the menu should be turned off + m_iFlags &= ~HUD_ACTIVE; + } + + m_fWaitingForMore = NeedMore; + + return 1; +} diff --git a/cl_dll/message.cpp b/cl_dll/message.cpp new file mode 100644 index 0000000..293e8a8 --- /dev/null +++ b/cl_dll/message.cpp @@ -0,0 +1,536 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Message.cpp +// +// implementation of CHudMessage class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE( m_Message, HudText ) +DECLARE_MESSAGE( m_Message, GameTitle ) + +// 1 Global client_textmessage_t for custom messages that aren't in the titles.txt +client_textmessage_t g_pCustomMessage; +char *g_pCustomName = "Custom"; +char g_pCustomText[1024]; + +int CHudMessage::Init(void) +{ + HOOK_MESSAGE( HudText ); + HOOK_MESSAGE( GameTitle ); + + gHUD.AddHudElem(this); + + Reset(); + + return 1; +}; + +int CHudMessage::VidInit( void ) +{ + m_HUD_title_half = gHUD.GetSpriteIndex( "title_half" ); + m_HUD_title_life = gHUD.GetSpriteIndex( "title_life" ); + + return 1; +}; + + +void CHudMessage::Reset( void ) +{ + memset( m_pMessages, 0, sizeof( m_pMessages[0] ) * maxHUDMessages ); + memset( m_startTime, 0, sizeof( m_startTime[0] ) * maxHUDMessages ); + + m_gameTitleTime = 0; + m_pGameTitle = NULL; +} + + +float CHudMessage::FadeBlend( float fadein, float fadeout, float hold, float localTime ) +{ + float fadeTime = fadein + hold; + float fadeBlend; + + if ( localTime < 0 ) + return 0; + + if ( localTime < fadein ) + { + fadeBlend = 1 - ((fadein - localTime) / fadein); + } + else if ( localTime > fadeTime ) + { + if ( fadeout > 0 ) + fadeBlend = 1 - ((localTime - fadeTime) / fadeout); + else + fadeBlend = 0; + } + else + fadeBlend = 1; + + return fadeBlend; +} + + +int CHudMessage::XPosition( float x, int width, int totalWidth ) +{ + int xPos; + + if ( x == -1 ) + { + xPos = (ScreenWidth - width) / 2; + } + else + { + if ( x < 0 ) + xPos = (1.0 + x) * ScreenWidth - totalWidth; // Alight right + else + xPos = x * ScreenWidth; + } + + if ( xPos + width > ScreenWidth ) + xPos = ScreenWidth - width; + else if ( xPos < 0 ) + xPos = 0; + + return xPos; +} + + +int CHudMessage::YPosition( float y, int height ) +{ + int yPos; + + if ( y == -1 ) // Centered? + yPos = (ScreenHeight - height) * 0.5; + else + { + // Alight bottom? + if ( y < 0 ) + yPos = (1.0 + y) * ScreenHeight - height; // Alight bottom + else // align top + yPos = y * ScreenHeight; + } + + if ( yPos + height > ScreenHeight ) + yPos = ScreenHeight - height; + else if ( yPos < 0 ) + yPos = 0; + + return yPos; +} + + +void CHudMessage::MessageScanNextChar( void ) +{ + int srcRed, srcGreen, srcBlue, destRed, destGreen, destBlue; + int blend; + + srcRed = m_parms.pMessage->r1; + srcGreen = m_parms.pMessage->g1; + srcBlue = m_parms.pMessage->b1; + blend = 0; // Pure source + + switch( m_parms.pMessage->effect ) + { + // Fade-in / Fade-out + case 0: + case 1: + destRed = destGreen = destBlue = 0; + blend = m_parms.fadeBlend; + break; + + case 2: + m_parms.charTime += m_parms.pMessage->fadein; + if ( m_parms.charTime > m_parms.time ) + { + srcRed = srcGreen = srcBlue = 0; + blend = 0; // pure source + } + else + { + float deltaTime = m_parms.time - m_parms.charTime; + + destRed = destGreen = destBlue = 0; + if ( m_parms.time > m_parms.fadeTime ) + { + blend = m_parms.fadeBlend; + } + else if ( deltaTime > m_parms.pMessage->fxtime ) + blend = 0; // pure dest + else + { + destRed = m_parms.pMessage->r2; + destGreen = m_parms.pMessage->g2; + destBlue = m_parms.pMessage->b2; + blend = 255 - (deltaTime * (1.0/m_parms.pMessage->fxtime) * 255.0 + 0.5); + } + } + break; + } + if ( blend > 255 ) + blend = 255; + else if ( blend < 0 ) + blend = 0; + + m_parms.r = ((srcRed * (255-blend)) + (destRed * blend)) >> 8; + m_parms.g = ((srcGreen * (255-blend)) + (destGreen * blend)) >> 8; + m_parms.b = ((srcBlue * (255-blend)) + (destBlue * blend)) >> 8; + + if ( m_parms.pMessage->effect == 1 && m_parms.charTime != 0 ) + { + if ( m_parms.x >= 0 && m_parms.y >= 0 && (m_parms.x + gHUD.m_scrinfo.charWidths[ m_parms.text ]) <= ScreenWidth ) + TextMessageDrawChar( m_parms.x, m_parms.y, m_parms.text, m_parms.pMessage->r2, m_parms.pMessage->g2, m_parms.pMessage->b2 ); + } +} + + +void CHudMessage::MessageScanStart( void ) +{ + switch( m_parms.pMessage->effect ) + { + // Fade-in / out with flicker + case 1: + case 0: + m_parms.fadeTime = m_parms.pMessage->fadein + m_parms.pMessage->holdtime; + + + if ( m_parms.time < m_parms.pMessage->fadein ) + { + m_parms.fadeBlend = ((m_parms.pMessage->fadein - m_parms.time) * (1.0/m_parms.pMessage->fadein) * 255); + } + else if ( m_parms.time > m_parms.fadeTime ) + { + if ( m_parms.pMessage->fadeout > 0 ) + m_parms.fadeBlend = (((m_parms.time - m_parms.fadeTime) / m_parms.pMessage->fadeout) * 255); + else + m_parms.fadeBlend = 255; // Pure dest (off) + } + else + m_parms.fadeBlend = 0; // Pure source (on) + m_parms.charTime = 0; + + if ( m_parms.pMessage->effect == 1 && (rand()%100) < 10 ) + m_parms.charTime = 1; + break; + + case 2: + m_parms.fadeTime = (m_parms.pMessage->fadein * m_parms.length) + m_parms.pMessage->holdtime; + + if ( m_parms.time > m_parms.fadeTime && m_parms.pMessage->fadeout > 0 ) + m_parms.fadeBlend = (((m_parms.time - m_parms.fadeTime) / m_parms.pMessage->fadeout) * 255); + else + m_parms.fadeBlend = 0; + break; + } +} + + +void CHudMessage::MessageDrawScan( client_textmessage_t *pMessage, float time ) +{ + int i, j, length, width; + const char *pText; + unsigned char line[80]; + + pText = pMessage->pMessage; + // Count lines + m_parms.lines = 1; + m_parms.time = time; + m_parms.pMessage = pMessage; + length = 0; + width = 0; + m_parms.totalWidth = 0; + while ( *pText ) + { + if ( *pText == '\n' ) + { + m_parms.lines++; + if ( width > m_parms.totalWidth ) + m_parms.totalWidth = width; + width = 0; + } + else + width += gHUD.m_scrinfo.charWidths[*pText]; + pText++; + length++; + } + m_parms.length = length; + m_parms.totalHeight = (m_parms.lines * gHUD.m_scrinfo.iCharHeight); + + + m_parms.y = YPosition( pMessage->y, m_parms.totalHeight ); + pText = pMessage->pMessage; + + m_parms.charTime = 0; + + MessageScanStart(); + + for ( i = 0; i < m_parms.lines; i++ ) + { + m_parms.lineLength = 0; + m_parms.width = 0; + while ( *pText && *pText != '\n' ) + { + unsigned char c = *pText; + line[m_parms.lineLength] = c; + m_parms.width += gHUD.m_scrinfo.charWidths[c]; + m_parms.lineLength++; + pText++; + } + pText++; // Skip LF + line[m_parms.lineLength] = 0; + + m_parms.x = XPosition( pMessage->x, m_parms.width, m_parms.totalWidth ); + + for ( j = 0; j < m_parms.lineLength; j++ ) + { + m_parms.text = line[j]; + int next = m_parms.x + gHUD.m_scrinfo.charWidths[ m_parms.text ]; + MessageScanNextChar(); + + if ( m_parms.x >= 0 && m_parms.y >= 0 && next <= ScreenWidth ) + TextMessageDrawChar( m_parms.x, m_parms.y, m_parms.text, m_parms.r, m_parms.g, m_parms.b ); + m_parms.x = next; + } + + m_parms.y += gHUD.m_scrinfo.iCharHeight; + } +} + + +int CHudMessage::Draw( float fTime ) +{ + int i, drawn; + client_textmessage_t *pMessage; + float endTime; + + drawn = 0; + + if ( m_gameTitleTime > 0 ) + { + float localTime = gHUD.m_flTime - m_gameTitleTime; + float brightness; + + // Maybe timer isn't set yet + if ( m_gameTitleTime > gHUD.m_flTime ) + m_gameTitleTime = gHUD.m_flTime; + + if ( localTime > (m_pGameTitle->fadein + m_pGameTitle->holdtime + m_pGameTitle->fadeout) ) + m_gameTitleTime = 0; + else + { + brightness = FadeBlend( m_pGameTitle->fadein, m_pGameTitle->fadeout, m_pGameTitle->holdtime, localTime ); + + int halfWidth = gHUD.GetSpriteRect(m_HUD_title_half).right - gHUD.GetSpriteRect(m_HUD_title_half).left; + int fullWidth = halfWidth + gHUD.GetSpriteRect(m_HUD_title_life).right - gHUD.GetSpriteRect(m_HUD_title_life).left; + int fullHeight = gHUD.GetSpriteRect(m_HUD_title_half).bottom - gHUD.GetSpriteRect(m_HUD_title_half).top; + + int x = XPosition( m_pGameTitle->x, fullWidth, fullWidth ); + int y = YPosition( m_pGameTitle->y, fullHeight ); + + + SPR_Set( gHUD.GetSprite(m_HUD_title_half), brightness * m_pGameTitle->r1, brightness * m_pGameTitle->g1, brightness * m_pGameTitle->b1 ); + SPR_DrawAdditive( 0, x, y, &gHUD.GetSpriteRect(m_HUD_title_half) ); + + SPR_Set( gHUD.GetSprite(m_HUD_title_life), brightness * m_pGameTitle->r1, brightness * m_pGameTitle->g1, brightness * m_pGameTitle->b1 ); + SPR_DrawAdditive( 0, x + halfWidth, y, &gHUD.GetSpriteRect(m_HUD_title_life) ); + + drawn = 1; + } + } + // Fixup level transitions + for ( i = 0; i < maxHUDMessages; i++ ) + { + // Assume m_parms.time contains last time + if ( m_pMessages[i] ) + { + pMessage = m_pMessages[i]; + if ( m_startTime[i] > gHUD.m_flTime ) + m_startTime[i] = gHUD.m_flTime + m_parms.time - m_startTime[i] + 0.2; // Server takes 0.2 seconds to spawn, adjust for this + } + } + + for ( i = 0; i < maxHUDMessages; i++ ) + { + if ( m_pMessages[i] ) + { + pMessage = m_pMessages[i]; + + // This is when the message is over + switch( pMessage->effect ) + { + case 0: + case 1: + endTime = m_startTime[i] + pMessage->fadein + pMessage->fadeout + pMessage->holdtime; + break; + + // Fade in is per character in scanning messages + case 2: + endTime = m_startTime[i] + (pMessage->fadein * strlen( pMessage->pMessage )) + pMessage->fadeout + pMessage->holdtime; + break; + } + + if ( fTime <= endTime ) + { + float messageTime = fTime - m_startTime[i]; + + // Draw the message + // effect 0 is fade in/fade out + // effect 1 is flickery credits + // effect 2 is write out (training room) + MessageDrawScan( pMessage, messageTime ); + + drawn++; + } + else + { + // The message is over + m_pMessages[i] = NULL; + } + } + } + + // Remember the time -- to fix up level transitions + m_parms.time = gHUD.m_flTime; + // Don't call until we get another message + if ( !drawn ) + m_iFlags &= ~HUD_ACTIVE; + + return 1; +} + + +void CHudMessage::MessageAdd( const char *pName, float time ) +{ + int i,j; + client_textmessage_t *tempMessage; + + for ( i = 0; i < maxHUDMessages; i++ ) + { + if ( !m_pMessages[i] ) + { + // Trim off a leading # if it's there + if ( pName[0] == '#' ) + tempMessage = TextMessageGet( pName+1 ); + else + tempMessage = TextMessageGet( pName ); + // If we couldnt find it in the titles.txt, just create it + if ( !tempMessage ) + { + g_pCustomMessage.effect = 2; + g_pCustomMessage.r1 = g_pCustomMessage.g1 = g_pCustomMessage.b1 = g_pCustomMessage.a1 = 100; + g_pCustomMessage.r2 = 240; + g_pCustomMessage.g2 = 110; + g_pCustomMessage.b2 = 0; + g_pCustomMessage.a2 = 0; + g_pCustomMessage.x = -1; // Centered + g_pCustomMessage.y = 0.7; + g_pCustomMessage.fadein = 0.01; + g_pCustomMessage.fadeout = 1.5; + g_pCustomMessage.fxtime = 0.25; + g_pCustomMessage.holdtime = 5; + g_pCustomMessage.pName = g_pCustomName; + strcpy( g_pCustomText, pName ); + g_pCustomMessage.pMessage = g_pCustomText; + + tempMessage = &g_pCustomMessage; + } + + for ( j = 0; j < maxHUDMessages; j++ ) + { + if ( m_pMessages[j] ) + { + // is this message already in the list + if ( !strcmp( tempMessage->pMessage, m_pMessages[j]->pMessage ) ) + { + return; + } + + // get rid of any other messages in same location (only one displays at a time) + if ( fabs( tempMessage->y - m_pMessages[j]->y ) < 0.0001 ) + { + if ( fabs( tempMessage->x - m_pMessages[j]->x ) < 0.0001 ) + { + m_pMessages[j] = NULL; + } + } + } + } + + m_pMessages[i] = tempMessage; + m_startTime[i] = time; + return; + } + } +} + + +int CHudMessage::MsgFunc_HudText( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + char *pString = READ_STRING(); + + MessageAdd( pString, gHUD.m_flTime ); + // Remember the time -- to fix up level transitions + m_parms.time = gHUD.m_flTime; + + // Turn on drawing + if ( !(m_iFlags & HUD_ACTIVE) ) + m_iFlags |= HUD_ACTIVE; + + return 1; +} + + +int CHudMessage::MsgFunc_GameTitle( const char *pszName, int iSize, void *pbuf ) +{ + m_pGameTitle = TextMessageGet( "GAMETITLE" ); + if ( m_pGameTitle != NULL ) + { + m_gameTitleTime = gHUD.m_flTime; + + // Turn on drawing + if ( !(m_iFlags & HUD_ACTIVE) ) + m_iFlags |= HUD_ACTIVE; + } + + return 1; +} + +void CHudMessage::MessageAdd(client_textmessage_t * newMessage ) +{ + m_parms.time = gHUD.m_flTime; + + // Turn on drawing + if ( !(m_iFlags & HUD_ACTIVE) ) + m_iFlags |= HUD_ACTIVE; + + for ( int i = 0; i < maxHUDMessages; i++ ) + { + if ( !m_pMessages[i] ) + { + m_pMessages[i] = newMessage; + m_startTime[i] = gHUD.m_flTime; + return; + } + } + +} diff --git a/cl_dll/overview.cpp b/cl_dll/overview.cpp new file mode 100644 index 0000000..e77a887 --- /dev/null +++ b/cl_dll/overview.cpp @@ -0,0 +1,160 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "hud.h" +#include "cl_util.h" +#include "cl_entity.h" +#include "triangleapi.h" +#include "vgui_TeamFortressViewport.h" + +// these are included for the math functions +#include "com_model.h" +#include "studio_util.h" + +#pragma warning(disable: 4244) + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CHudOverview::Init() +{ + gHUD.AddHudElem(this); + + m_iFlags |= HUD_ACTIVE; + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Loads new icons +//----------------------------------------------------------------------------- +int CHudOverview::VidInit() +{ + m_hsprPlayer = gEngfuncs.pfnSPR_Load("sprites/ring.spr"); + m_hsprViewcone = gEngfuncs.pfnSPR_Load("sprites/camera.spr"); + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flTime - +// intermission - +//----------------------------------------------------------------------------- +int CHudOverview::Draw(float flTime) +{ + // only draw in overview mode + if (!gEngfuncs.Overview_GetOverviewState()) + return 1; + + // make sure we have player info + gViewPort->GetAllPlayersInfo(); + + // calculate player size on the overview + int x1, y1, x2, y2; + float v0[3]={0,0,0}, v1[3]={64,64,0}; + gEngfuncs.Overview_WorldToScreen(v0, &x1, &y1); + gEngfuncs.Overview_WorldToScreen(v1, &x2, &y2); + float scale = abs(x2 - x1); + + // loop through all the players and draw them on the map + for (int i = 1; i < MAX_PLAYERS; i++) + { + cl_entity_t *pl = gEngfuncs.GetEntityByIndex(i); + + if (pl && pl->player && pl->curstate.health > 0 && pl->curstate.solid != SOLID_NOT) + { + int x, y, z = 0; + float v[3]={pl->origin[0], pl->origin[1], 0}; + gEngfuncs.Overview_WorldToScreen(v, &x, &y); + + // hack in some team colors + float r, g, bc; + if (g_PlayerExtraInfo[i].teamnumber == 1) + { + r = 0.0f; g = 0.0f; bc = 1.0f; + } + else if (g_PlayerExtraInfo[i].teamnumber == 2) + { + r = 1.0f; g = 0.0f; bc = 0.0f; + } + else + { + // just use the default orange color if the team isn't set + r = 1.0f; g = 0.7f; bc = 0.0f; + } + + // set the current texture + gEngfuncs.pTriAPI->SpriteTexture((struct model_s *)gEngfuncs.GetSpritePointer(m_hsprPlayer), 0); + + // additive render mode + gEngfuncs.pTriAPI->RenderMode(kRenderTransAdd); + + // no culling + gEngfuncs.pTriAPI->CullFace(TRI_NONE); + + // draw a square + gEngfuncs.pTriAPI->Begin(TRI_QUADS); + + // set the color to be that of the team + gEngfuncs.pTriAPI->Color4f(r, g, bc, 1.0f); + + // calculate rotational matrix + vec3_t a, b, angles; + float rmatrix[3][4]; // transformation matrix + VectorCopy(pl->angles, angles); + angles[0] = 0.0f; + angles[1] += 90.f; + angles[1] = -angles[1]; + angles[2] = 0.0f; + AngleMatrix(angles, rmatrix); + a[2] = 0; + + a[0] = -scale; a[1] = -scale; + VectorTransform(a, rmatrix , b ); + gEngfuncs.pTriAPI->TexCoord2f( 0, 0 ); + gEngfuncs.pTriAPI->Vertex3f(x + b[0], y + b[1], z); + + a[0]=-scale; a[1] = scale; + VectorTransform(a, rmatrix , b ); + gEngfuncs.pTriAPI->TexCoord2f( 0, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x + b[0], y + b[1], z); + + a[0]=scale; a[1] = scale; + VectorTransform(a, rmatrix , b ); + gEngfuncs.pTriAPI->TexCoord2f( 1, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x + b[0], y + b[1], z); + + a[0]=scale; a[1] = -scale; + VectorTransform(a, rmatrix , b ); + gEngfuncs.pTriAPI->TexCoord2f( 1, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x + b[0], y + b[1], z); + + // finish up + gEngfuncs.pTriAPI->End(); + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + + // draw the players name and health underneath + char string[256]; + sprintf(string, "%s (%i%%)", g_PlayerInfoList[i].name, pl->curstate.health); + DrawConsoleString(x, y + (1.1 * scale), string); + } + } + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: called every time a server is connected to +//----------------------------------------------------------------------------- +void CHudOverview::InitHUDData() +{ +// this block would force the spectator view to be on +// gEngfuncs.Overview_SetDrawOverview( 1 ); +// gEngfuncs.Overview_SetDrawInset( 0 ); +} + diff --git a/cl_dll/overview.h b/cl_dll/overview.h new file mode 100644 index 0000000..4de9ae7 --- /dev/null +++ b/cl_dll/overview.h @@ -0,0 +1,31 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef OVERVIEW_H +#define OVERVIEW_H +#pragma once + + +//----------------------------------------------------------------------------- +// Purpose: Handles the drawing of the top-down map and all the things on it +//----------------------------------------------------------------------------- +class CHudOverview : public CHudBase +{ +public: + int Init(); + int VidInit(); + + int Draw(float flTime); + void InitHUDData( void ); + +private: + HSPRITE m_hsprPlayer; + HSPRITE m_hsprViewcone; +}; + + +#endif // OVERVIEW_H diff --git a/cl_dll/player_info.h b/cl_dll/player_info.h new file mode 100644 index 0000000..474d9c6 --- /dev/null +++ b/cl_dll/player_info.h @@ -0,0 +1,20 @@ +/*** +* +* Copyright (c) 2003', Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +extern hud_player_info_t g_PlayerInfoList[MAX_PLAYERS+1]; // player info from the engine +extern extra_player_info_t g_PlayerExtraInfo[MAX_PLAYERS+1]; // additional player info sent directly to the client dll +extern team_info_t g_TeamInfo[MAX_TEAMS+1]; +extern int g_IsSpectator[MAX_PLAYERS+1]; + diff --git a/cl_dll/readme.txt b/cl_dll/readme.txt new file mode 100644 index 0000000..249205c --- /dev/null +++ b/cl_dll/readme.txt @@ -0,0 +1,107 @@ + client dll readme.txt +------------------------- + +This file details the structure of the half-life client dll, and +how it communicates with the half-life game engine. + + +Engine callback functions: + +Drawing functions: + HSPRITE SPR_Load( char *picname ); + Loads a sprite into memory, and returns a handle to it. + + int SPR_Frames( HSPRITE sprite ); + Returns the number of frames stored in the specified sprite. + + int SPR_Height( HSPRITE x, int frame ) + Returns the height, in pixels, of a sprite at the specified frame. + Returns 0 is the frame number or the sprite handle is invalid. + + int SPR_Width( HSPRITE x, int f ) + Returns the width, in pixels, of a sprite at the specified frame. + Returns 0 is the frame number or the sprite handle is invalid. + + int SPR_Set( HSPRITE sprite, int r, int g, int b ); + Prepares a sprite about to be drawn. RBG color values are applied to the sprite at this time. + + + void SPR_Draw( int frame, int x, int y ); + Precondition: SPR_Set has already been called for a sprite. + Draws the currently active sprite to the screen, at position (x,y), where (0,0) is + the top left-hand corner of the screen. + + + void SPR_DrawHoles( int frame, int x, int y ); + Precondition: SPR_Set has already been called for a sprite. + Draws the currently active sprite to the screen. Color index #255 is treated as transparent. + + void SPR_DrawAdditive( int frame, int x, int y ); + Precondition: SPR_Set has already been called for a sprite. + Draws the currently active sprite to the screen, adding it's color values to the background. + + void SPR_EnableScissor( int x, int y, int width, int height ); + Creates a clipping rectangle. No pixels will be drawn outside the specified area. Will + stay in effect until either the next frame, or SPR_DisableScissor is called. + + void SPR_DisableScissor( void ); + Disables the effect of an SPR_EnableScissor call. + + int IsHighRes( void ); + returns 1 if the res mode is 640x480 or higher; 0 otherwise. + + int ScreenWidth( void ); + returns the screen width, in pixels. + + int ScreenHeight( void ); + returns the screen height, in pixels. + +// Sound functions + void PlaySound( char *szSound, int volume ) + plays the sound 'szSound' at the specified volume. Loads the sound if it hasn't been cached. + If it can't find the sound, it displays an error message and plays no sound. + + void PlaySound( int iSound, int volume ) + Precondition: iSound has been precached. + Plays the sound, from the precache list. + + +// Communication functions + void SendClientCmd( char *szCmdString ); + sends a command to the server, just as if the client had typed the szCmdString at the console. + + char *GetPlayerName( int entity_number ); + returns a pointer to a string, that contains the name of the specified client. + Returns NULL if the entity_number is not a client. + + + DECLARE_MESSAGE(), HOOK_MESSAGE() + These two macros bind the message sending between the entity DLL and the client DLL to + the CHud object. + + HOOK_MESSAGE( message_name ) + This is used inside CHud::Init(). It calls into the engine to hook that message + from the incoming message stream. + Precondition: There must be a function of name UserMsg_message_name declared + for CHud. Eg, CHud::UserMsg_Health() must be declared if you want to + use HOOK_MESSAGE( Health ); + + DECLARE_MESSAGE( message_name ) + For each HOOK_MESSAGE you must have an equivalent DECLARE_MESSAGE. This creates + a function which passes the hooked messages into the CHud object. + + + HOOK_COMMAND(), DECLARE_COMMAND() + These two functions declare and hook console commands into the client dll. + + HOOK_COMMAND( char *command, command_name ) + Whenever the user types the 'command' at the console, the function 'command_name' + will be called. + Precondition: There must be a function of the name UserCmd_command_name declared + for CHud. Eg, CHud::UserMsg_ShowScores() must be declared if you want to + use HOOK_COMMAND( "+showscores", ShowScores ); + + DECLARE_COMMAND( command_name ) + For each HOOK_COMMAND you must have an equivelant DECLARE_COMMAND. This creates + a function which passes the hooked commands into the CHud object. + diff --git a/cl_dll/saytext.cpp b/cl_dll/saytext.cpp new file mode 100644 index 0000000..8069d86 --- /dev/null +++ b/cl_dll/saytext.cpp @@ -0,0 +1,326 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// saytext.cpp +// +// implementation of CHudSayText class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include +#include // _alloca + +#include "vgui_TeamFortressViewport.h" + +extern float *GetClientColor( int clientIndex ); + +#define MAX_LINES 5 +#define MAX_CHARS_PER_LINE 256 /* it can be less than this, depending on char size */ + +// allow 20 pixels on either side of the text +#define MAX_LINE_WIDTH ( ScreenWidth - 40 ) +#define LINE_START 10 +static float SCROLL_SPEED = 5; + +static char g_szLineBuffer[ MAX_LINES + 1 ][ MAX_CHARS_PER_LINE ]; +static float *g_pflNameColors[ MAX_LINES + 1 ]; +static int g_iNameLengths[ MAX_LINES + 1 ]; +static float flScrollTime = 0; // the time at which the lines next scroll up + +static int Y_START = 0; +static int line_height = 0; + +DECLARE_MESSAGE( m_SayText, SayText ); + +int CHudSayText :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( SayText ); + + InitHUDData(); + + m_HUD_saytext = gEngfuncs.pfnRegisterVariable( "hud_saytext", "1", 0 ); + m_HUD_saytext_time = gEngfuncs.pfnRegisterVariable( "hud_saytext_time", "5", 0 ); + + m_iFlags |= HUD_INTERMISSION; // is always drawn during an intermission + + return 1; +} + + +void CHudSayText :: InitHUDData( void ) +{ + memset( g_szLineBuffer, 0, sizeof g_szLineBuffer ); + memset( g_pflNameColors, 0, sizeof g_pflNameColors ); + memset( g_iNameLengths, 0, sizeof g_iNameLengths ); +} + +int CHudSayText :: VidInit( void ) +{ + return 1; +} + + +int ScrollTextUp( void ) +{ + ConsolePrint( g_szLineBuffer[0] ); // move the first line into the console buffer + g_szLineBuffer[MAX_LINES][0] = 0; + memmove( g_szLineBuffer[0], g_szLineBuffer[1], sizeof(g_szLineBuffer) - sizeof(g_szLineBuffer[0]) ); // overwrite the first line + memmove( &g_pflNameColors[0], &g_pflNameColors[1], sizeof(g_pflNameColors) - sizeof(g_pflNameColors[0]) ); + memmove( &g_iNameLengths[0], &g_iNameLengths[1], sizeof(g_iNameLengths) - sizeof(g_iNameLengths[0]) ); + g_szLineBuffer[MAX_LINES-1][0] = 0; + + if ( g_szLineBuffer[0][0] == ' ' ) // also scroll up following lines + { + g_szLineBuffer[0][0] = 2; + return 1 + ScrollTextUp(); + } + + return 1; +} + +int CHudSayText :: Draw( float flTime ) +{ + int y = Y_START; + + if ( ( gViewPort && gViewPort->AllowedToPrintText() == FALSE) || !m_HUD_saytext->value ) + return 1; + + // make sure the scrolltime is within reasonable bounds, to guard against the clock being reset + flScrollTime = min( flScrollTime, flTime + m_HUD_saytext_time->value ); + + // make sure the scrolltime is within reasonable bounds, to guard against the clock being reset + flScrollTime = min( flScrollTime, flTime + m_HUD_saytext_time->value ); + + if ( flScrollTime <= flTime ) + { + if ( *g_szLineBuffer[0] ) + { + flScrollTime = flTime + m_HUD_saytext_time->value; + // push the console up + ScrollTextUp(); + } + else + { // buffer is empty, just disable drawing of this section + m_iFlags &= ~HUD_ACTIVE; + } + } + + for ( int i = 0; i < MAX_LINES; i++ ) + { + if ( *g_szLineBuffer[i] ) + { + if ( *g_szLineBuffer[i] == 2 && g_pflNameColors[i] ) + { + // it's a saytext string + char *buf = static_cast( _alloca( strlen( g_szLineBuffer[i] ) ) ); + if ( buf ) + { + //char buf[MAX_PLAYER_NAME_LENGTH+32]; + + // draw the first x characters in the player color + strncpy( buf, g_szLineBuffer[i], min(g_iNameLengths[i], MAX_PLAYER_NAME_LENGTH+32) ); + buf[ min(g_iNameLengths[i], MAX_PLAYER_NAME_LENGTH+31) ] = 0; + gEngfuncs.pfnDrawSetTextColor( g_pflNameColors[i][0], g_pflNameColors[i][1], g_pflNameColors[i][2] ); + int x = DrawConsoleString( LINE_START, y, buf + 1 ); // don't draw the control code at the start + strncpy( buf, g_szLineBuffer[i] + g_iNameLengths[i], strlen( g_szLineBuffer[i] )); + buf[ strlen( g_szLineBuffer[i] + g_iNameLengths[i] ) - 1 ] = '\0'; + // color is reset after each string draw + DrawConsoleString( x, y, buf ); + } + else + { + assert( "Not able to alloca chat buffer!\n"); + } + } + else + { + // normal draw + DrawConsoleString( LINE_START, y, g_szLineBuffer[i] ); + } + } + + y += line_height; + } + + return 1; +} + +int CHudSayText :: MsgFunc_SayText( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int client_index = READ_BYTE(); // the client who spoke the message + SayTextPrint( READ_STRING(), iSize - 1, client_index ); + + return 1; +} + +void CHudSayText :: SayTextPrint( const char *pszBuf, int iBufSize, int clientIndex ) +{ + if ( gViewPort && gViewPort->AllowedToPrintText() == FALSE ) + { + // Print it straight to the console + ConsolePrint( pszBuf ); + return; + } + + int i; + // find an empty string slot + for ( i = 0; i < MAX_LINES; i++ ) + { + if ( ! *g_szLineBuffer[i] ) + break; + } + if ( i == MAX_LINES ) + { + // force scroll buffer up + ScrollTextUp(); + i = MAX_LINES - 1; + } + + g_iNameLengths[i] = 0; + g_pflNameColors[i] = NULL; + + // if it's a say message, search for the players name in the string + if ( *pszBuf == 2 && clientIndex > 0 ) + { + gEngfuncs.pfnGetPlayerInfo( clientIndex, &g_PlayerInfoList[clientIndex] ); + const char *pName = g_PlayerInfoList[clientIndex].name; + + if ( pName ) + { + const char *nameInString = strstr( pszBuf, pName ); + + if ( nameInString ) + { + g_iNameLengths[i] = strlen( pName ) + (nameInString - pszBuf); + g_pflNameColors[i] = GetClientColor( clientIndex ); + } + } + } + + strncpy( g_szLineBuffer[i], pszBuf, max(iBufSize , MAX_CHARS_PER_LINE) ); + + // make sure the text fits in one line + EnsureTextFitsInOneLineAndWrapIfHaveTo( i ); + + // Set scroll time + if ( i == 0 ) + { + flScrollTime = gHUD.m_flTime + m_HUD_saytext_time->value; + } + + m_iFlags |= HUD_ACTIVE; + PlaySound( "misc/talk.wav", 1 ); + + Y_START = ScreenHeight - 60 - ( line_height * (MAX_LINES+2) ); +} + +void CHudSayText :: EnsureTextFitsInOneLineAndWrapIfHaveTo( int line ) +{ + int line_width = 0; + GetConsoleStringSize( g_szLineBuffer[line], &line_width, &line_height ); + + if ( (line_width + LINE_START) > MAX_LINE_WIDTH ) + { // string is too long to fit on line + // scan the string until we find what word is too long, and wrap the end of the sentence after the word + int length = LINE_START; + int tmp_len = 0; + char *last_break = NULL; + for ( char *x = g_szLineBuffer[line]; *x != 0; x++ ) + { + // check for a color change, if so skip past it + if ( x[0] == '/' && x[1] == '(' ) + { + x += 2; + // skip forward until past mode specifier + while ( *x != 0 && *x != ')' ) + x++; + + if ( *x != 0 ) + x++; + + if ( *x == 0 ) + break; + } + + char buf[2]; + buf[1] = 0; + + if ( *x == ' ' && x != g_szLineBuffer[line] ) // store each line break, except for the very first character + last_break = x; + + buf[0] = *x; // get the length of the current character + GetConsoleStringSize( buf, &tmp_len, &line_height ); + length += tmp_len; + + if ( length > MAX_LINE_WIDTH ) + { // needs to be broken up + if ( !last_break ) + last_break = x-1; + + x = last_break; + + // find an empty string slot + int j; + do + { + for ( j = 0; j < MAX_LINES; j++ ) + { + if ( ! *g_szLineBuffer[j] ) + break; + } + if ( j == MAX_LINES ) + { + // need to make more room to display text, scroll stuff up then fix the pointers + int linesmoved = ScrollTextUp(); + line -= linesmoved; + last_break = last_break - (sizeof(g_szLineBuffer[0]) * linesmoved); + } + } + while ( j == MAX_LINES ); + + // copy remaining string into next buffer, making sure it starts with a space character + if ( (char)*last_break == (char)' ' ) + { + int linelen = strlen(g_szLineBuffer[j]); + int remaininglen = strlen(last_break); + + if ( (linelen - remaininglen) <= MAX_CHARS_PER_LINE ) + strcat( g_szLineBuffer[j], last_break ); + } + else + { + if ( (strlen(g_szLineBuffer[j]) - strlen(last_break) - 2) < MAX_CHARS_PER_LINE ) + { + strcat( g_szLineBuffer[j], " " ); + strcat( g_szLineBuffer[j], last_break ); + } + } + + *last_break = 0; // cut off the last string + + EnsureTextFitsInOneLineAndWrapIfHaveTo( j ); + break; + } + } + } +} diff --git a/cl_dll/scoreboard.cpp b/cl_dll/scoreboard.cpp new file mode 100644 index 0000000..9bb8d2c --- /dev/null +++ b/cl_dll/scoreboard.cpp @@ -0,0 +1,586 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Scoreboard.cpp +// +// implementation of CHudScoreboard class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include +#include "vgui_TeamFortressViewport.h" + +DECLARE_COMMAND( m_Scoreboard, ShowScores ); +DECLARE_COMMAND( m_Scoreboard, HideScores ); + +DECLARE_MESSAGE( m_Scoreboard, ScoreInfo ); +DECLARE_MESSAGE( m_Scoreboard, TeamInfo ); +DECLARE_MESSAGE( m_Scoreboard, TeamScore ); + +int CHudScoreboard :: Init( void ) +{ + gHUD.AddHudElem( this ); + + // Hook messages & commands here + //HOOK_COMMAND( "+showscores", ShowScores ); + //HOOK_COMMAND( "-showscores", HideScores ); + + HOOK_MESSAGE( ScoreInfo ); + HOOK_MESSAGE( TeamScore ); + HOOK_MESSAGE( TeamInfo ); + + InitHUDData(); + + cl_showpacketloss = CVAR_CREATE( "cl_showpacketloss", "0", FCVAR_ARCHIVE ); + + return 1; +} + + +int CHudScoreboard :: VidInit( void ) +{ + // Load sprites here + + return 1; +} + +void CHudScoreboard :: InitHUDData( void ) +{ + memset( g_PlayerExtraInfo, 0, sizeof g_PlayerExtraInfo ); + m_iLastKilledBy = 0; + m_fLastKillTime = 0; + m_iPlayerNum = 0; + m_iNumTeams = 0; + memset( g_TeamInfo, 0, sizeof g_TeamInfo ); + + m_iFlags &= ~HUD_ACTIVE; // starts out inactive + + m_iFlags |= HUD_INTERMISSION; // is always drawn during an intermission +} + +/* The scoreboard +We have a minimum width of 1-320 - we could have the field widths scale with it? +*/ + +// X positions +// relative to the side of the scoreboard +#define NAME_RANGE_MIN 20 +#define NAME_RANGE_MAX 145 +#define KILLS_RANGE_MIN 130 +#define KILLS_RANGE_MAX 170 +#define DIVIDER_POS 180 +#define DEATHS_RANGE_MIN 185 +#define DEATHS_RANGE_MAX 210 +#define PING_RANGE_MIN 245 +#define PING_RANGE_MAX 295 +#define PL_RANGE_MIN 315 +#define PL_RANGE_MAX 375 + +int SCOREBOARD_WIDTH = 320; + + +// Y positions +#define ROW_GAP 13 +#define ROW_RANGE_MIN 15 +#define ROW_RANGE_MAX ( ScreenHeight - 50 ) + +int CHudScoreboard :: Draw( float fTime ) +{ + int can_show_packetloss = 0; + int FAR_RIGHT; + + if ( !m_iShowscoresHeld && gHUD.m_Health.m_iHealth > 0 && !gHUD.m_iIntermission ) + return 1; + + GetAllPlayersInfo(); + + // Packetloss removed on Kelly 'shipping nazi' Bailey's orders + if ( cl_showpacketloss && cl_showpacketloss->value && ( ScreenWidth >= 400 ) ) + { + can_show_packetloss = 1; + SCOREBOARD_WIDTH = 400; + } + else + { + SCOREBOARD_WIDTH = 320; + } + + // just sort the list on the fly + // list is sorted first by frags, then by deaths + float list_slot = 0; + int xpos_rel = (ScreenWidth - SCOREBOARD_WIDTH) / 2; + + // print the heading line + int ypos = ROW_RANGE_MIN + (list_slot * ROW_GAP); + int xpos = NAME_RANGE_MIN + xpos_rel; + + if ( !gHUD.m_Teamplay ) + gHUD.DrawHudString( xpos, ypos, NAME_RANGE_MAX + xpos_rel, "Player", 255, 140, 0 ); + else + gHUD.DrawHudString( xpos, ypos, NAME_RANGE_MAX + xpos_rel, "Teams", 255, 140, 0 ); + + gHUD.DrawHudStringReverse( KILLS_RANGE_MAX + xpos_rel, ypos, 0, "kills", 255, 140, 0 ); + gHUD.DrawHudString( DIVIDER_POS + xpos_rel, ypos, ScreenWidth, "/", 255, 140, 0 ); + gHUD.DrawHudString( DEATHS_RANGE_MIN + xpos_rel + 5, ypos, ScreenWidth, "deaths", 255, 140, 0 ); + gHUD.DrawHudString( PING_RANGE_MAX + xpos_rel - 35, ypos, ScreenWidth, "latency", 255, 140, 0 ); + + if ( can_show_packetloss ) + { + gHUD.DrawHudString( PL_RANGE_MAX + xpos_rel - 35, ypos, ScreenWidth, "pkt loss", 255, 140, 0 ); + } + + FAR_RIGHT = can_show_packetloss ? PL_RANGE_MAX : PING_RANGE_MAX; + FAR_RIGHT += 5; + + list_slot += 1.2; + ypos = ROW_RANGE_MIN + (list_slot * ROW_GAP); + xpos = NAME_RANGE_MIN + xpos_rel; + FillRGBA( xpos - 5, ypos, FAR_RIGHT, 1, 255, 140, 0, 255); // draw the seperator line + + list_slot += 0.8; + + if ( !gHUD.m_Teamplay ) + { + // it's not teamplay, so just draw a simple player list + DrawPlayers( xpos_rel, list_slot ); + return 1; + } + + // clear out team scores + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + if ( !g_TeamInfo[i].scores_overriden ) + g_TeamInfo[i].frags = g_TeamInfo[i].deaths = 0; + g_TeamInfo[i].ping = g_TeamInfo[i].packetloss = 0; + } + + // recalc the team scores, then draw them + for ( i = 1; i < MAX_PLAYERS; i++ ) + { + if ( g_PlayerInfoList[i].name == NULL ) + continue; // empty player slot, skip + + if ( g_PlayerExtraInfo[i].teamname[0] == 0 ) + continue; // skip over players who are not in a team + + // find what team this player is in + for ( int j = 1; j <= m_iNumTeams; j++ ) + { + if ( !stricmp( g_PlayerExtraInfo[i].teamname, g_TeamInfo[j].name ) ) + break; + } + if ( j > m_iNumTeams ) // player is not in a team, skip to the next guy + continue; + + if ( !g_TeamInfo[j].scores_overriden ) + { + g_TeamInfo[j].frags += g_PlayerExtraInfo[i].frags; + g_TeamInfo[j].deaths += g_PlayerExtraInfo[i].deaths; + } + + g_TeamInfo[j].ping += g_PlayerInfoList[i].ping; + g_TeamInfo[j].packetloss += g_PlayerInfoList[i].packetloss; + + if ( g_PlayerInfoList[i].thisplayer ) + g_TeamInfo[j].ownteam = TRUE; + else + g_TeamInfo[j].ownteam = FALSE; + } + + // find team ping/packetloss averages + for ( i = 1; i <= m_iNumTeams; i++ ) + { + g_TeamInfo[i].already_drawn = FALSE; + + if ( g_TeamInfo[i].players > 0 ) + { + g_TeamInfo[i].ping /= g_TeamInfo[i].players; // use the average ping of all the players in the team as the teams ping + g_TeamInfo[i].packetloss /= g_TeamInfo[i].players; // use the average ping of all the players in the team as the teams ping + } + } + + // Draw the teams + while ( 1 ) + { + int highest_frags = -99999; int lowest_deaths = 99999; + int best_team = 0; + + for ( i = 1; i <= m_iNumTeams; i++ ) + { + if ( g_TeamInfo[i].players < 0 ) + continue; + + if ( !g_TeamInfo[i].already_drawn && g_TeamInfo[i].frags >= highest_frags ) + { + if ( g_TeamInfo[i].frags > highest_frags || g_TeamInfo[i].deaths < lowest_deaths ) + { + best_team = i; + lowest_deaths = g_TeamInfo[i].deaths; + highest_frags = g_TeamInfo[i].frags; + } + } + } + + // draw the best team on the scoreboard + if ( !best_team ) + break; + + // draw out the best team + team_info_t *team_info = &g_TeamInfo[best_team]; + + ypos = ROW_RANGE_MIN + (list_slot * ROW_GAP); + + // check we haven't drawn too far down + if ( ypos > ROW_RANGE_MAX ) // don't draw to close to the lower border + break; + + xpos = NAME_RANGE_MIN + xpos_rel; + int r = 255, g = 225, b = 55; // draw the stuff kinda yellowish + + if ( team_info->ownteam ) // if it is their team, draw the background different color + { + // overlay the background in blue, then draw the score text over it + FillRGBA( NAME_RANGE_MIN + xpos_rel - 5, ypos, FAR_RIGHT, ROW_GAP, 0, 0, 255, 70 ); + } + + // draw their name (left to right) + gHUD.DrawHudString( xpos, ypos, NAME_RANGE_MAX + xpos_rel, team_info->name, r, g, b ); + + // draw kills (right to left) + xpos = KILLS_RANGE_MAX + xpos_rel; + gHUD.DrawHudNumberString( xpos, ypos, KILLS_RANGE_MIN + xpos_rel, team_info->frags, r, g, b ); + + // draw divider + xpos = DIVIDER_POS + xpos_rel; + gHUD.DrawHudString( xpos, ypos, xpos + 20, "/", r, g, b ); + + // draw deaths + xpos = DEATHS_RANGE_MAX + xpos_rel; + gHUD.DrawHudNumberString( xpos, ypos, DEATHS_RANGE_MIN + xpos_rel, team_info->deaths, r, g, b ); + + // draw ping + // draw ping & packetloss + static char buf[64]; + sprintf( buf, "%d", team_info->ping ); + xpos = ((PING_RANGE_MAX - PING_RANGE_MIN) / 2) + PING_RANGE_MIN + xpos_rel + 25; + UnpackRGB( r, g, b, RGB_YELLOWISH ); + gHUD.DrawHudStringReverse( xpos, ypos, xpos - 50, buf, r, g, b ); + + // Packetloss removed on Kelly 'shipping nazi' Bailey's orders + if ( can_show_packetloss ) + { + xpos = ((PL_RANGE_MAX - PL_RANGE_MIN) / 2) + PL_RANGE_MIN + xpos_rel + 25; + + sprintf( buf, " %d", team_info->packetloss ); + gHUD.DrawHudString( xpos, ypos, xpos+50, buf, r, g, b ); + } + + team_info->already_drawn = TRUE; // set the already_drawn to be TRUE, so this team won't get drawn again + list_slot++; + + // draw all the players that belong to this team, indented slightly + list_slot = DrawPlayers( xpos_rel, list_slot, 10, team_info->name ); + } + + // draw all the players who are not in a team + list_slot += 0.5; + DrawPlayers( xpos_rel, list_slot, 0, "" ); + + return 1; +} + +// returns the ypos where it finishes drawing +int CHudScoreboard :: DrawPlayers( int xpos_rel, float list_slot, int nameoffset, char *team ) +{ + int can_show_packetloss = 0; + int FAR_RIGHT; + + // Packetloss removed on Kelly 'shipping nazi' Bailey's orders + if ( cl_showpacketloss && cl_showpacketloss->value && ( ScreenWidth >= 400 ) ) + { + can_show_packetloss = 1; + SCOREBOARD_WIDTH = 400; + } + else + { + SCOREBOARD_WIDTH = 320; + } + + FAR_RIGHT = can_show_packetloss ? PL_RANGE_MAX : PING_RANGE_MAX; + FAR_RIGHT += 5; + + // draw the players, in order, and restricted to team if set + while ( 1 ) + { + // Find the top ranking player + int highest_frags = -99999; int lowest_deaths = 99999; + int best_player = 0; + + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + if ( g_PlayerInfoList[i].name && g_PlayerExtraInfo[i].frags >= highest_frags ) + { + if ( !(team && stricmp(g_PlayerExtraInfo[i].teamname, team)) ) // make sure it is the specified team + { + extra_player_info_t *pl_info = &g_PlayerExtraInfo[i]; + if ( pl_info->frags > highest_frags || pl_info->deaths < lowest_deaths ) + { + best_player = i; + lowest_deaths = pl_info->deaths; + highest_frags = pl_info->frags; + } + } + } + } + + if ( !best_player ) + break; + + // draw out the best player + hud_player_info_t *pl_info = &g_PlayerInfoList[best_player]; + + int ypos = ROW_RANGE_MIN + (list_slot * ROW_GAP); + + // check we haven't drawn too far down + if ( ypos > ROW_RANGE_MAX ) // don't draw to close to the lower border + break; + + int xpos = NAME_RANGE_MIN + xpos_rel; + int r = 255, g = 255, b = 255; + if ( best_player == m_iLastKilledBy && m_fLastKillTime && m_fLastKillTime > gHUD.m_flTime ) + { + if ( pl_info->thisplayer ) + { // green is the suicide color? i wish this could do grey... + FillRGBA( NAME_RANGE_MIN + xpos_rel - 5, ypos, FAR_RIGHT, ROW_GAP, 80, 155, 0, 70 ); + } + else + { // Highlight the killers name - overlay the background in red, then draw the score text over it + FillRGBA( NAME_RANGE_MIN + xpos_rel - 5, ypos, FAR_RIGHT, ROW_GAP, 255, 0, 0, ((float)15 * (float)(m_fLastKillTime - gHUD.m_flTime)) ); + } + } + else if ( pl_info->thisplayer ) // if it is their name, draw it a different color + { + // overlay the background in blue, then draw the score text over it + FillRGBA( NAME_RANGE_MIN + xpos_rel - 5, ypos, FAR_RIGHT, ROW_GAP, 0, 0, 255, 70 ); + } + + // draw their name (left to right) + gHUD.DrawHudString( xpos + nameoffset, ypos, NAME_RANGE_MAX + xpos_rel, pl_info->name, r, g, b ); + + // draw kills (right to left) + xpos = KILLS_RANGE_MAX + xpos_rel; + gHUD.DrawHudNumberString( xpos, ypos, KILLS_RANGE_MIN + xpos_rel, g_PlayerExtraInfo[best_player].frags, r, g, b ); + + // draw divider + xpos = DIVIDER_POS + xpos_rel; + gHUD.DrawHudString( xpos, ypos, xpos + 20, "/", r, g, b ); + + // draw deaths + xpos = DEATHS_RANGE_MAX + xpos_rel; + gHUD.DrawHudNumberString( xpos, ypos, DEATHS_RANGE_MIN + xpos_rel, g_PlayerExtraInfo[best_player].deaths, r, g, b ); + + // draw ping & packetloss + static char buf[64]; + sprintf( buf, "%d", g_PlayerInfoList[best_player].ping ); + xpos = ((PING_RANGE_MAX - PING_RANGE_MIN) / 2) + PING_RANGE_MIN + xpos_rel + 25; + gHUD.DrawHudStringReverse( xpos, ypos, xpos - 50, buf, r, g, b ); + + // Packetloss removed on Kelly 'shipping nazi' Bailey's orders + if ( can_show_packetloss ) + { + if ( g_PlayerInfoList[best_player].packetloss >= 63 ) + { + UnpackRGB( r, g, b, RGB_REDISH ); + sprintf( buf, " !!!!" ); + } + else + { + sprintf( buf, " %d", g_PlayerInfoList[best_player].packetloss ); + } + + xpos = ((PL_RANGE_MAX - PL_RANGE_MIN) / 2) + PL_RANGE_MIN + xpos_rel + 25; + + gHUD.DrawHudString( xpos, ypos, xpos+50, buf, r, g, b ); + } + + pl_info->name = NULL; // set the name to be NULL, so this client won't get drawn again + list_slot++; + } + + return list_slot; +} + + +void CHudScoreboard :: GetAllPlayersInfo( void ) +{ + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + GetPlayerInfo( i, &g_PlayerInfoList[i] ); + + if ( g_PlayerInfoList[i].thisplayer ) + m_iPlayerNum = i; // !!!HACK: this should be initialized elsewhere... maybe gotten from the engine + } +} + +int CHudScoreboard :: MsgFunc_ScoreInfo( const char *pszName, int iSize, void *pbuf ) +{ + m_iFlags |= HUD_ACTIVE; + + BEGIN_READ( pbuf, iSize ); + short cl = READ_BYTE(); + short frags = READ_SHORT(); + short deaths = READ_SHORT(); + short playerclass = READ_SHORT(); + short teamnumber = READ_SHORT(); + + if ( cl > 0 && cl <= MAX_PLAYERS ) + { + g_PlayerExtraInfo[cl].frags = frags; + g_PlayerExtraInfo[cl].deaths = deaths; + g_PlayerExtraInfo[cl].playerclass = playerclass; + g_PlayerExtraInfo[cl].teamnumber = teamnumber; + + gViewPort->UpdateOnPlayerInfo(); + } + + return 1; +} + +// Message handler for TeamInfo message +// accepts two values: +// byte: client number +// string: client team name +int CHudScoreboard :: MsgFunc_TeamInfo( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + short cl = READ_BYTE(); + + if ( cl > 0 && cl <= MAX_PLAYERS ) + { // set the players team + strncpy( g_PlayerExtraInfo[cl].teamname, READ_STRING(), MAX_TEAM_NAME ); + } + + // rebuild the list of teams + + // clear out player counts from teams + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + g_TeamInfo[i].players = 0; + } + + // rebuild the team list + GetAllPlayersInfo(); + m_iNumTeams = 0; + for ( i = 1; i < MAX_PLAYERS; i++ ) + { + if ( g_PlayerInfoList[i].name == NULL ) + continue; + + if ( g_PlayerExtraInfo[i].teamname[0] == 0 ) + continue; // skip over players who are not in a team + + // is this player in an existing team? + for ( int j = 1; j <= m_iNumTeams; j++ ) + { + if ( g_TeamInfo[j].name[0] == '\0' ) + break; + + if ( !stricmp( g_PlayerExtraInfo[i].teamname, g_TeamInfo[j].name ) ) + break; + } + + if ( j > m_iNumTeams ) + { // they aren't in a listed team, so make a new one + // search through for an empty team slot + for ( int j = 1; j <= m_iNumTeams; j++ ) + { + if ( g_TeamInfo[j].name[0] == '\0' ) + break; + } + m_iNumTeams = max( j, m_iNumTeams ); + + strncpy( g_TeamInfo[j].name, g_PlayerExtraInfo[i].teamname, MAX_TEAM_NAME ); + g_TeamInfo[j].players = 0; + } + + g_TeamInfo[j].players++; + } + + // clear out any empty teams + for ( i = 1; i <= m_iNumTeams; i++ ) + { + if ( g_TeamInfo[i].players < 1 ) + memset( &g_TeamInfo[i], 0, sizeof(team_info_t) ); + } + + return 1; +} + +// Message handler for TeamScore message +// accepts three values: +// string: team name +// short: teams kills +// short: teams deaths +// if this message is never received, then scores will simply be the combined totals of the players. +int CHudScoreboard :: MsgFunc_TeamScore( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + char *TeamName = READ_STRING(); + + // find the team matching the name + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + if ( !stricmp( TeamName, g_TeamInfo[i].name ) ) + break; + } + if ( i > m_iNumTeams ) + return 1; + + // use this new score data instead of combined player scores + g_TeamInfo[i].scores_overriden = TRUE; + g_TeamInfo[i].frags = READ_SHORT(); + g_TeamInfo[i].deaths = READ_SHORT(); + + return 1; +} + +void CHudScoreboard :: DeathMsg( int killer, int victim ) +{ + // if we were the one killed, or the world killed us, set the scoreboard to indicate suicide + if ( victim == m_iPlayerNum || killer == 0 ) + { + m_iLastKilledBy = killer ? killer : m_iPlayerNum; + m_fLastKillTime = gHUD.m_flTime + 10; // display who we were killed by for 10 seconds + + if ( killer == m_iPlayerNum ) + m_iLastKilledBy = m_iPlayerNum; + } +} + + + +void CHudScoreboard :: UserCmd_ShowScores( void ) +{ + m_iShowscoresHeld = TRUE; +} + +void CHudScoreboard :: UserCmd_HideScores( void ) +{ + m_iShowscoresHeld = FALSE; +} diff --git a/cl_dll/soundsystem.cpp b/cl_dll/soundsystem.cpp new file mode 100644 index 0000000..d368109 --- /dev/null +++ b/cl_dll/soundsystem.cpp @@ -0,0 +1,162 @@ +//======== (C) Copyright 1996-2002 Valve, L.L.C. All rights reserved. ======== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= +#include +#include +#include +#include "r_studioint.h" + +extern engine_studio_api_t IEngineStudio; + +#define RENDERTYPE_UNDEFINED 0 +#define RENDERTYPE_SOFTWARE 1 +#define RENDERTYPE_HARDWARE 2 + +#define ENGINE_LAUNCHER_API_VERSION 1 + +LPDIRECTSOUND lpDS = NULL; +LPDIRECTSOUNDBUFFER lpDSBuf = NULL; +LPHWAVEOUT lpHW = NULL; + +static HMODULE hEngine = 0; + +typedef struct engine_api_s +{ + int version; + int rendertype; + int size; + + // Functions + void ( *unused1 ) ( void ); + void ( *unused2 ) ( void ); + void ( *unused3 ) ( void ); + void ( *unused4 ) ( void ); + void ( *unused5 ) ( void ); + void ( *unused6 ) ( void ); + void ( *unused7 ) ( void ); + void ( *unused8 ) ( void ); + void ( *unused9 ) ( void ); + void ( *unused10 ) ( void ); + void ( *unused11 ) ( void ); + void ( *unused12 ) ( void ); + void ( *unused13 ) ( void ); + void ( *unused14 ) ( void ); + void ( *unused15 ) ( void ); + void ( *unused16 ) ( void ); + void ( *unused17 ) ( void ); + void ( *unused18 ) ( void ); + void ( *unused19 ) ( void ); + void ( *unused20 ) ( void ); + void ( *unused21 ) ( void ); + void ( *unused22 ) ( void ); + void ( *unused23 ) ( void ); + void ( *unused24 ) ( void ); + void ( *unused25 ) ( void ); + void ( *unused26 ) ( void ); + void ( *unused27 ) ( void ); + void ( *unused28 ) ( void ); + void ( *unused29 ) ( void ); + void ( *unused30 ) ( void ); + void ( *unused31 ) ( void ); + void ( *unused32 ) ( void ); + void ( *unused33 ) ( void ); + void ( *unused34 ) ( void ); + + void ( *S_GetDSPointer ) ( struct IDirectSound **lpDS, struct IDirectSoundBuffer **lpDSBuf ); + void *( *S_GetWAVPointer ) ( void ); + + void ( *unused35 ) ( void ); + void ( *unused36 ) ( void ); + void ( *unused37 ) ( void ); + void ( *unused38 ) ( void ); + void ( *unused39 ) ( void ); + void ( *unused40 ) ( void ); + void ( *unused41 ) ( void ); + void ( *unused42 ) ( void ); + void ( *unused43 ) ( void ); + void ( *unused44 ) ( void ); + void ( *unused45 ) ( void ); + void ( *unused46 ) ( void ); + void ( *unused47 ) ( void ); + void ( *unused48 ) ( void ); + void ( *unused49 ) ( void ); + void ( *unused50 ) ( void ); + void ( *unused51 ) ( void ); + void ( *unused52 ) ( void ); + void ( *unused53 ) ( void ); + void ( *unused54 ) ( void ); + void ( *unused55 ) ( void ); +} engine_api_t; + +static engine_api_t engineapi; + +typedef int (*engine_api_func)( int version, int size, struct engine_api_s *api ); + +//----------------------------------------------------------------------------- +// Purpose: Get launcher/engine interface from engine module +// Input : hMod - +// Output : int +//----------------------------------------------------------------------------- +int Eng_LoadFunctions( HMODULE hMod ) +{ + engine_api_func pfnEngineAPI; + + pfnEngineAPI = ( engine_api_func )GetProcAddress( hMod, "Sys_EngineAPI" ); + if ( !pfnEngineAPI ) + return 0; + + if ( !(*pfnEngineAPI)( ENGINE_LAUNCHER_API_VERSION, sizeof( engine_api_t ), &engineapi ) ) + return 0; + + // All is okay + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Load proper engine .dll and get pointer to either DSound and primary buffer or HWAVEOUT ( NT 4.0, e.g. ) +//----------------------------------------------------------------------------- +void LoadSoundAPIs( void ) +{ + hEngine = ::LoadLibrary( IEngineStudio.IsHardware() ? "hw.dll" : "sw.dll" ); + if ( hEngine ) + { + if ( Eng_LoadFunctions( hEngine ) ) + { + if ( engineapi.S_GetDSPointer && engineapi.S_GetWAVPointer ) + { + engineapi.S_GetDSPointer(&lpDS, &lpDSBuf); + lpHW = (HWAVEOUT FAR *)engineapi.S_GetWAVPointer(); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Close engine library, release sound pointers +//----------------------------------------------------------------------------- +void ShutdownSoundAPIs( void ) +{ + if( hEngine ) + { + FreeLibrary( hEngine ); + hEngine = 0; + } + + lpDS = 0; + lpDSBuf = 0; + lpHW = 0; +} diff --git a/cl_dll/status_icons.cpp b/cl_dll/status_icons.cpp new file mode 100644 index 0000000..e6fcbeb --- /dev/null +++ b/cl_dll/status_icons.cpp @@ -0,0 +1,163 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// status_icons.cpp +// +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include +#include +#include "parsemsg.h" +#include "event_api.h" + +DECLARE_MESSAGE( m_StatusIcons, StatusIcon ); + +int CHudStatusIcons::Init( void ) +{ + HOOK_MESSAGE( StatusIcon ); + + gHUD.AddHudElem( this ); + + Reset(); + + return 1; +} + +int CHudStatusIcons::VidInit( void ) +{ + + return 1; +} + +void CHudStatusIcons::Reset( void ) +{ + memset( m_IconList, 0, sizeof m_IconList ); + m_iFlags &= ~HUD_ACTIVE; +} + +// Draw status icons along the left-hand side of the screen +int CHudStatusIcons::Draw( float flTime ) +{ + if (gEngfuncs.IsSpectateOnly()) + return 1; + // find starting position to draw from, along right-hand side of screen + int x = 5; + int y = ScreenHeight / 2; + + // loop through icon list, and draw any valid icons drawing up from the middle of screen + for ( int i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( m_IconList[i].spr ) + { + y -= ( m_IconList[i].rc.bottom - m_IconList[i].rc.top ) + 5; + + SPR_Set( m_IconList[i].spr, m_IconList[i].r, m_IconList[i].g, m_IconList[i].b ); + SPR_DrawAdditive( 0, x, y, &m_IconList[i].rc ); + } + } + + return 1; +} + +// Message handler for StatusIcon message +// accepts five values: +// byte : TRUE = ENABLE icon, FALSE = DISABLE icon +// string : the sprite name to display +// byte : red +// byte : green +// byte : blue +int CHudStatusIcons::MsgFunc_StatusIcon( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int ShouldEnable = READ_BYTE(); + char *pszIconName = READ_STRING(); + if ( ShouldEnable ) + { + int r = READ_BYTE(); + int g = READ_BYTE(); + int b = READ_BYTE(); + EnableIcon( pszIconName, r, g, b ); + m_iFlags |= HUD_ACTIVE; + } + else + { + DisableIcon( pszIconName ); + } + + return 1; +} + +// add the icon to the icon list, and set it's drawing color +void CHudStatusIcons::EnableIcon( char *pszIconName, unsigned char red, unsigned char green, unsigned char blue ) +{ + int i; + // check to see if the sprite is in the current list + for ( i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( !stricmp( m_IconList[i].szSpriteName, pszIconName ) ) + break; + } + + if ( i == MAX_ICONSPRITES ) + { + // icon not in list, so find an empty slot to add to + for ( i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( !m_IconList[i].spr ) + break; + } + } + + // if we've run out of space in the list, overwrite the first icon + if ( i == MAX_ICONSPRITES ) + { + i = 0; + } + + // Load the sprite and add it to the list + // the sprite must be listed in hud.txt + int spr_index = gHUD.GetSpriteIndex( pszIconName ); + m_IconList[i].spr = gHUD.GetSprite( spr_index ); + m_IconList[i].rc = gHUD.GetSpriteRect( spr_index ); + m_IconList[i].r = red; + m_IconList[i].g = green; + m_IconList[i].b = blue; + strcpy( m_IconList[i].szSpriteName, pszIconName ); + + // Hack: Play Timer sound when a grenade icon is played (in 0.8 seconds) + if ( strstr(m_IconList[i].szSpriteName, "grenade") ) + { + cl_entity_t *pthisplayer = gEngfuncs.GetLocalPlayer(); + gEngfuncs.pEventAPI->EV_PlaySound( pthisplayer->index, pthisplayer->origin, CHAN_STATIC, "weapons/timer.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); + } +} + +void CHudStatusIcons::DisableIcon( char *pszIconName ) +{ + // find the sprite is in the current list + for ( int i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( !stricmp( m_IconList[i].szSpriteName, pszIconName ) ) + { + // clear the item from the list + memset( &m_IconList[i], 0, sizeof(icon_sprite_t) ); + return; + } + } +} diff --git a/cl_dll/statusbar.cpp b/cl_dll/statusbar.cpp new file mode 100644 index 0000000..a83e58a --- /dev/null +++ b/cl_dll/statusbar.cpp @@ -0,0 +1,265 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// statusbar.cpp +// +// generic text status bar, set by game dll +// runs across bottom of screen +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +DECLARE_MESSAGE( m_StatusBar, StatusText ); +DECLARE_MESSAGE( m_StatusBar, StatusValue ); + +#ifdef _TFC +#define STATUSBAR_ID_LINE 2 +#else +#define STATUSBAR_ID_LINE 1 +#endif + +float *GetClientColor( int clientIndex ); +extern float g_ColorYellow[3]; + +int CHudStatusBar :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( StatusText ); + HOOK_MESSAGE( StatusValue ); + + Reset(); + + CVAR_CREATE( "hud_centerid", "0", FCVAR_ARCHIVE ); + + return 1; +} + +int CHudStatusBar :: VidInit( void ) +{ + // Load sprites here + + return 1; +} + +void CHudStatusBar :: Reset( void ) +{ + int i = 0; + + m_iFlags &= ~HUD_ACTIVE; // start out inactive + for ( i = 0; i < MAX_STATUSBAR_LINES; i++ ) + m_szStatusText[i][0] = 0; + memset( m_iStatusValues, 0, sizeof m_iStatusValues ); + + m_iStatusValues[0] = 1; // 0 is the special index, which always returns true + + // reset our colors for the status bar lines (yellow is default) + for ( i = 0; i < MAX_STATUSBAR_LINES; i++ ) + m_pflNameColors[i] = g_ColorYellow; +} + +void CHudStatusBar :: ParseStatusString( int line_num ) +{ + // localise string first + char szBuffer[MAX_STATUSTEXT_LENGTH]; + memset( szBuffer, 0, sizeof szBuffer ); + gHUD.m_TextMessage.LocaliseTextString( m_szStatusText[line_num], szBuffer, MAX_STATUSTEXT_LENGTH ); + + // parse m_szStatusText & m_iStatusValues into m_szStatusBar + memset( m_szStatusBar[line_num], 0, MAX_STATUSTEXT_LENGTH ); + char *src = szBuffer; + char *dst = m_szStatusBar[line_num]; + + char *src_start = src, *dst_start = dst; + + while ( *src != 0 ) + { + while ( *src == '\n' ) + src++; // skip over any newlines + + if ( ((src - src_start) >= MAX_STATUSTEXT_LENGTH) || ((dst - dst_start) >= MAX_STATUSTEXT_LENGTH) ) + break; + + int index = atoi( src ); + // should we draw this line? + if ( (index >= 0 && index < MAX_STATUSBAR_VALUES) && (m_iStatusValues[index] != 0) ) + { // parse this line and append result to the status bar + while ( *src >= '0' && *src <= '9' ) + src++; + + if ( *src == '\n' || *src == 0 ) + continue; // no more left in this text line + + // copy the text, char by char, until we hit a % or a \n + while ( *src != '\n' && *src != 0 ) + { + if ( *src != '%' ) + { // just copy the character + *dst = *src; + dst++, src++; + } + else + { + // get the descriptor + char valtype = *(++src); // move over % + + // if it's a %, draw a % sign + if ( valtype == '%' ) + { + *dst = valtype; + dst++, src++; + continue; + } + + // move over descriptor, then get and move over the index + index = atoi( ++src ); + while ( *src >= '0' && *src <= '9' ) + src++; + + if ( index >= 0 && index < MAX_STATUSBAR_VALUES ) + { + int indexval = m_iStatusValues[index]; + + // get the string to substitute in place of the %XX + char szRepString[MAX_PLAYER_NAME_LENGTH]; + switch ( valtype ) + { + case 'p': // player name + gEngfuncs.pfnGetPlayerInfo( indexval, &g_PlayerInfoList[indexval] ); + if ( g_PlayerInfoList[indexval].name != NULL ) + { + strncpy( szRepString, g_PlayerInfoList[indexval].name, MAX_PLAYER_NAME_LENGTH ); + m_pflNameColors[line_num] = GetClientColor( indexval ); + } + else + { + strcpy( szRepString, "******" ); + } + + break; + case 'i': // number + sprintf( szRepString, "%d", indexval ); + break; + default: + szRepString[0] = 0; + } + + for ( char *cp = szRepString; *cp != 0 && ((dst - dst_start) < MAX_STATUSTEXT_LENGTH); cp++, dst++ ) + *dst = *cp; + } + } + } + } + else + { + // skip to next line of text + while ( *src != 0 && *src != '\n' ) + src++; + } + } +} + +int CHudStatusBar :: Draw( float fTime ) +{ + if ( m_bReparseString ) + { + for ( int i = 0; i < MAX_STATUSBAR_LINES; i++ ) + { + m_pflNameColors[i] = g_ColorYellow; + ParseStatusString( i ); + } + m_bReparseString = FALSE; + } + + int Y_START = ScreenHeight - 52; + + // Draw the status bar lines + for ( int i = 0; i < MAX_STATUSBAR_LINES; i++ ) + { + int TextHeight, TextWidth; + GetConsoleStringSize( m_szStatusBar[i], &TextWidth, &TextHeight ); + + int x = 8; + int y = Y_START - ( 4 + TextHeight * i ); // draw along bottom of screen + + // let user set status ID bar centering + if ( (i == STATUSBAR_ID_LINE) && CVAR_GET_FLOAT("hud_centerid") ) + { + x = max( 0, max(2, (ScreenWidth - TextWidth)) / 2 ); + y = (ScreenHeight / 2) + (TextHeight*CVAR_GET_FLOAT("hud_centerid")); + } + + if ( m_pflNameColors[i] ) + gEngfuncs.pfnDrawSetTextColor( m_pflNameColors[i][0], m_pflNameColors[i][1], m_pflNameColors[i][2] ); + + DrawConsoleString( x, y, m_szStatusBar[i] ); + } + + return 1; +} + +// Message handler for StatusText message +// accepts two values: +// byte: line number of status bar text +// string: status bar text +// this string describes how the status bar should be drawn +// a semi-regular expression: +// ( slotnum ([a..z] [%pX] [%iX])*)* +// where slotnum is an index into the Value table (see below) +// if slotnum is 0, the string is always drawn +// if StatusValue[slotnum] != 0, the following string is drawn, upto the next newline - otherwise the text is skipped upto next newline +// %pX, where X is an integer, will substitute a player name here, getting the player index from StatusValue[X] +// %iX, where X is an integer, will substitute a number here, getting the number from StatusValue[X] +int CHudStatusBar :: MsgFunc_StatusText( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int line = READ_BYTE(); + + if ( line < 0 || line > MAX_STATUSBAR_LINES ) + return 1; + + strncpy( m_szStatusText[line], READ_STRING(), MAX_STATUSTEXT_LENGTH ); + m_szStatusText[line][MAX_STATUSTEXT_LENGTH-1] = 0; // ensure it's null terminated ( strncpy() won't null terminate if read string too long) + + m_iFlags |= HUD_ACTIVE; + m_bReparseString = TRUE; + + return 1; +} + +// Message handler for StatusText message +// accepts two values: +// byte: index into the status value array +// short: value to store +int CHudStatusBar :: MsgFunc_StatusValue( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int index = READ_BYTE(); + if ( index < 1 || index > MAX_STATUSBAR_VALUES ) + return 1; // index out of range + + m_iStatusValues[index] = READ_SHORT(); + + m_bReparseString = TRUE; + + return 1; +} \ No newline at end of file diff --git a/cl_dll/studio_util.cpp b/cl_dll/studio_util.cpp new file mode 100644 index 0000000..df5fc4b --- /dev/null +++ b/cl_dll/studio_util.cpp @@ -0,0 +1,251 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include "studio_util.h" + +/* +==================== +AngleMatrix + +==================== +*/ +void AngleMatrix (const float *angles, float (*matrix)[4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + // matrix = (YAW * PITCH) * ROLL + matrix[0][0] = cp*cy; + matrix[1][0] = cp*sy; + matrix[2][0] = -sp; + matrix[0][1] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[2][1] = sr*cp; + matrix[0][2] = (cr*sp*cy+-sr*-sy); + matrix[1][2] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + +/* +==================== +VectorCompare + +==================== +*/ +int VectorCompare (const float *v1, const float *v2) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (v1[i] != v2[i]) + return 0; + + return 1; +} + +/* +==================== +CrossProduct + +==================== +*/ +void CrossProduct (const float *v1, const float *v2, float *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]; +} + +/* +==================== +VectorTransform + +==================== +*/ +void VectorTransform (const float *in1, float in2[3][4], float *out) +{ + out[0] = DotProduct(in1, in2[0]) + in2[0][3]; + out[1] = DotProduct(in1, in2[1]) + in2[1][3]; + out[2] = DotProduct(in1, in2[2]) + in2[2][3]; +} + +/* +================ +ConcatTransforms + +================ +*/ +void ConcatTransforms (float in1[3][4], 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]; +} + +// angles index are not the same as ROLL, PITCH, YAW + +/* +==================== +AngleQuaternion + +==================== +*/ +void AngleQuaternion( float *angles, vec4_t quaternion ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + // FIXME: rescale the inputs to 1/2 angle + angle = angles[2] * 0.5; + sy = sin(angle); + cy = cos(angle); + angle = angles[1] * 0.5; + sp = sin(angle); + cp = cos(angle); + angle = angles[0] * 0.5; + sr = sin(angle); + cr = cos(angle); + + quaternion[0] = sr*cp*cy-cr*sp*sy; // X + quaternion[1] = cr*sp*cy+sr*cp*sy; // Y + quaternion[2] = cr*cp*sy-sr*sp*cy; // Z + quaternion[3] = cr*cp*cy+sr*sp*sy; // W +} + +/* +==================== +QuaternionSlerp + +==================== +*/ +void QuaternionSlerp( vec4_t p, vec4_t q, float t, vec4_t qt ) +{ + int i; + float omega, cosom, sinom, sclp, sclq; + + // decide if one of the quaternions is backwards + float a = 0; + float b = 0; + + for (i = 0; i < 4; i++) + { + a += (p[i]-q[i])*(p[i]-q[i]); + b += (p[i]+q[i])*(p[i]+q[i]); + } + if (a > b) + { + for (i = 0; i < 4; i++) + { + q[i] = -q[i]; + } + } + + cosom = p[0]*q[0] + p[1]*q[1] + p[2]*q[2] + p[3]*q[3]; + + if ((1.0 + cosom) > 0.000001) + { + if ((1.0 - cosom) > 0.000001) + { + omega = acos( cosom ); + sinom = sin( omega ); + sclp = sin( (1.0 - t)*omega) / sinom; + sclq = sin( t*omega ) / sinom; + } + else + { + sclp = 1.0 - t; + sclq = t; + } + for (i = 0; i < 4; i++) { + qt[i] = sclp * p[i] + sclq * q[i]; + } + } + else + { + qt[0] = -q[1]; + qt[1] = q[0]; + qt[2] = -q[3]; + qt[3] = q[2]; + sclp = sin( (1.0 - t) * (0.5 * M_PI)); + sclq = sin( t * (0.5 * M_PI)); + for (i = 0; i < 3; i++) + { + qt[i] = sclp * p[i] + sclq * qt[i]; + } + } +} + +/* +==================== +QuaternionMatrix + +==================== +*/ +void QuaternionMatrix( vec4_t quaternion, float (*matrix)[4] ) +{ + matrix[0][0] = 1.0 - 2.0 * quaternion[1] * quaternion[1] - 2.0 * quaternion[2] * quaternion[2]; + matrix[1][0] = 2.0 * quaternion[0] * quaternion[1] + 2.0 * quaternion[3] * quaternion[2]; + matrix[2][0] = 2.0 * quaternion[0] * quaternion[2] - 2.0 * quaternion[3] * quaternion[1]; + + matrix[0][1] = 2.0 * quaternion[0] * quaternion[1] - 2.0 * quaternion[3] * quaternion[2]; + matrix[1][1] = 1.0 - 2.0 * quaternion[0] * quaternion[0] - 2.0 * quaternion[2] * quaternion[2]; + matrix[2][1] = 2.0 * quaternion[1] * quaternion[2] + 2.0 * quaternion[3] * quaternion[0]; + + matrix[0][2] = 2.0 * quaternion[0] * quaternion[2] + 2.0 * quaternion[3] * quaternion[1]; + matrix[1][2] = 2.0 * quaternion[1] * quaternion[2] - 2.0 * quaternion[3] * quaternion[0]; + matrix[2][2] = 1.0 - 2.0 * quaternion[0] * quaternion[0] - 2.0 * quaternion[1] * quaternion[1]; +} + +/* +==================== +MatrixCopy + +==================== +*/ +void MatrixCopy( float in[3][4], float out[3][4] ) +{ + memcpy( out, in, sizeof( float ) * 3 * 4 ); +} \ No newline at end of file diff --git a/cl_dll/studio_util.h b/cl_dll/studio_util.h new file mode 100644 index 0000000..aa8dcf6 --- /dev/null +++ b/cl_dll/studio_util.h @@ -0,0 +1,40 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( STUDIO_UTIL_H ) +#define STUDIO_UTIL_H +#if defined( WIN32 ) +#pragma once +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +#ifndef PITCH +// MOVEMENT INFO +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 +#endif + +#define FDotProduct( a, b ) (fabs((a[0])*(b[0])) + fabs((a[1])*(b[1])) + fabs((a[2])*(b[2]))) + +void AngleMatrix (const float *angles, float (*matrix)[4] ); +int VectorCompare (const float *v1, const float *v2); +void CrossProduct (const float *v1, const float *v2, float *cross); +void VectorTransform (const float *in1, float in2[3][4], float *out); +void ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]); +void MatrixCopy( float in[3][4], float out[3][4] ); +void QuaternionMatrix( vec4_t quaternion, float (*matrix)[4] ); +void QuaternionSlerp( vec4_t p, vec4_t q, float t, vec4_t qt ); +void AngleQuaternion( float *angles, vec4_t quaternion ); + +#endif // STUDIO_UTIL_H \ No newline at end of file diff --git a/cl_dll/text_message.cpp b/cl_dll/text_message.cpp new file mode 100644 index 0000000..646c43e --- /dev/null +++ b/cl_dll/text_message.cpp @@ -0,0 +1,214 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// text_message.cpp +// +// implementation of CHudTextMessage class +// +// this class routes messages through titles.txt for localisation +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +#include "vgui_TeamFortressViewport.h" + +DECLARE_MESSAGE( m_TextMessage, TextMsg ); + +int CHudTextMessage::Init(void) +{ + HOOK_MESSAGE( TextMsg ); + + gHUD.AddHudElem( this ); + + Reset(); + + return 1; +}; + +// Searches through the string for any msg names (indicated by a '#') +// any found are looked up in titles.txt and the new message substituted +// the new value is pushed into dst_buffer +char *CHudTextMessage::LocaliseTextString( const char *msg, char *dst_buffer, int buffer_size ) +{ + char *dst = dst_buffer; + for ( char *src = (char*)msg; *src != 0 && buffer_size > 0; buffer_size-- ) + { + if ( *src == '#' ) + { + // cut msg name out of string + static char word_buf[255]; + char *wdst = word_buf, *word_start = src; + for ( ++src ; (*src >= 'A' && *src <= 'z') || (*src >= '0' && *src <= '9'); wdst++, src++ ) + { + *wdst = *src; + } + *wdst = 0; + + // lookup msg name in titles.txt + client_textmessage_t *clmsg = TextMessageGet( word_buf ); + if ( !clmsg || !(clmsg->pMessage) ) + { + src = word_start; + *dst = *src; + dst++, src++; + continue; + } + + // copy string into message over the msg name + for ( char *wsrc = (char*)clmsg->pMessage; *wsrc != 0; wsrc++, dst++ ) + { + *dst = *wsrc; + } + *dst = 0; + } + else + { + *dst = *src; + dst++, src++; + *dst = 0; + } + } + + dst_buffer[buffer_size-1] = 0; // ensure null termination + return dst_buffer; +} + +// As above, but with a local static buffer +char *CHudTextMessage::BufferedLocaliseTextString( const char *msg ) +{ + static char dst_buffer[1024]; + LocaliseTextString( msg, dst_buffer, 1024 ); + return dst_buffer; +} + +// Simplified version of LocaliseTextString; assumes string is only one word +char *CHudTextMessage::LookupString( const char *msg, int *msg_dest ) +{ + if ( !msg ) + return ""; + + // '#' character indicates this is a reference to a string in titles.txt, and not the string itself + if ( msg[0] == '#' ) + { + // this is a message name, so look up the real message + client_textmessage_t *clmsg = TextMessageGet( msg+1 ); + + if ( !clmsg || !(clmsg->pMessage) ) + return (char*)msg; // lookup failed, so return the original string + + if ( msg_dest ) + { + // check to see if titles.txt info overrides msg destination + // if clmsg->effect is less than 0, then clmsg->effect holds -1 * message_destination + if ( clmsg->effect < 0 ) // + *msg_dest = -clmsg->effect; + } + + return (char*)clmsg->pMessage; + } + else + { // nothing special about this message, so just return the same string + return (char*)msg; + } +} + +void StripEndNewlineFromString( char *str ) +{ + int s = strlen( str ) - 1; + if ( str[s] == '\n' || str[s] == '\r' ) + str[s] = 0; +} + +// converts all '\r' characters to '\n', so that the engine can deal with the properly +// returns a pointer to str +char* ConvertCRtoNL( char *str ) +{ + for ( char *ch = str; *ch != 0; ch++ ) + if ( *ch == '\r' ) + *ch = '\n'; + return str; +} + +// Message handler for text messages +// displays a string, looking them up from the titles.txt file, which can be localised +// parameters: +// byte: message direction ( HUD_PRINTCONSOLE, HUD_PRINTNOTIFY, HUD_PRINTCENTER, HUD_PRINTTALK ) +// string: message +// optional parameters: +// string: message parameter 1 +// string: message parameter 2 +// string: message parameter 3 +// string: message parameter 4 +// any string that starts with the character '#' is a message name, and is used to look up the real message in titles.txt +// the next (optional) one to four strings are parameters for that string (which can also be message names if they begin with '#') +int CHudTextMessage::MsgFunc_TextMsg( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int msg_dest = READ_BYTE(); + +#define MSG_BUF_SIZE 128 + static char szBuf[6][MSG_BUF_SIZE]; + char *msg_text = LookupString( READ_STRING(), &msg_dest ); + msg_text = safe_strcpy( szBuf[0], msg_text , MSG_BUF_SIZE); + + // keep reading strings and using C format strings for subsituting the strings into the localised text string + char *sstr1 = LookupString( READ_STRING() ); + sstr1 = safe_strcpy( szBuf[1], sstr1 , MSG_BUF_SIZE); + StripEndNewlineFromString( sstr1 ); // these strings are meant for subsitution into the main strings, so cull the automatic end newlines + char *sstr2 = LookupString( READ_STRING() ); + sstr2 = safe_strcpy( szBuf[2], sstr2 , MSG_BUF_SIZE); + StripEndNewlineFromString( sstr2 ); + char *sstr3 = LookupString( READ_STRING() ); + sstr3 = safe_strcpy( szBuf[3], sstr3 , MSG_BUF_SIZE); + StripEndNewlineFromString( sstr3 ); + char *sstr4 = LookupString( READ_STRING() ); + sstr4 = safe_strcpy( szBuf[4], sstr4 , MSG_BUF_SIZE); + StripEndNewlineFromString( sstr4 ); + char *psz = szBuf[5]; + + if ( gViewPort && gViewPort->AllowedToPrintText() == FALSE ) + return 1; + + switch ( msg_dest ) + { + case HUD_PRINTCENTER: + safe_sprintf( psz, MSG_BUF_SIZE, msg_text, sstr1, sstr2, sstr3, sstr4 ); + CenterPrint( ConvertCRtoNL( psz ) ); + break; + + case HUD_PRINTNOTIFY: + psz[0] = 1; // mark this message to go into the notify buffer + safe_sprintf( psz+1, MSG_BUF_SIZE, msg_text, sstr1, sstr2, sstr3, sstr4 ); + ConsolePrint( ConvertCRtoNL( psz ) ); + break; + + case HUD_PRINTTALK: + safe_sprintf( psz, MSG_BUF_SIZE, msg_text, sstr1, sstr2, sstr3, sstr4 ); + gHUD.m_SayText.SayTextPrint( ConvertCRtoNL( psz ), 128 ); + break; + + case HUD_PRINTCONSOLE: + safe_sprintf( psz, MSG_BUF_SIZE, msg_text, sstr1, sstr2, sstr3, sstr4 ); + ConsolePrint( ConvertCRtoNL( psz ) ); + break; + } + + return 1; +} diff --git a/cl_dll/tf_defs.h b/cl_dll/tf_defs.h new file mode 100644 index 0000000..06d9af0 --- /dev/null +++ b/cl_dll/tf_defs.h @@ -0,0 +1,1389 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#ifndef __TF_DEFS_H +#define __TF_DEFS_H + +//=========================================================================== +// OLD OPTIONS.QC +//=========================================================================== +#define DEFAULT_AUTOZOOM FALSE +#define WEINER_SNIPER // autoaiming for sniper rifle +#define FLAME_MAXWORLDNUM 20 // maximum number of flames in the world. DO NOT PUT BELOW 20. + +//#define MAX_WORLD_PIPEBOMBS 15 // This is divided between teams - this is the most you should have on a net server +#define MAX_PLAYER_PIPEBOMBS 8 // maximum number of pipebombs any 1 player can have active +#define MAX_PLAYER_AMMOBOXES 3 // maximum number of ammoboxes any 1 player can have active + +//#define MAX_WORLD_FLARES 9 // This is the total number of flares allowed in the world at one time +//#define MAX_WORLD_AMMOBOXES 20 // This is divided between teams - this is the most you should have on a net server +#define GR_TYPE_MIRV_NO 4 // Number of Mirvs a Mirv Grenade breaks into +#define GR_TYPE_NAPALM_NO 8 // Number of flames napalm grenade breaks into (unused if net server) +#define MEDIKIT_IS_BIOWEAPON // Medikit acts as a bioweapon against enemies + +#define TEAM_HELP_RATE 60 // used only if teamplay bit 64 (help team with lower score) is set. + // 60 is a mild setting, and won't make too much difference + // increasing it _decreases_ the amount of help the losing team gets + // Minimum setting is 1, which would really help the losing team + +#define DISPLAY_CLASS_HELP TRUE // Change this to #OFF if you don't want the class help to + // appear whenever a player connects +#define NEVER_TEAMFRAGS FALSE // teamfrags options always off +#define ALWAYS_TEAMFRAGS FALSE // teamfrags options always on +#define CHECK_SPEEDS TRUE // makes sure players aren't moving too fast +#define SNIPER_RIFLE_RELOAD_TIME 1.5 // seconds + +#define MAPBRIEFING_MAXTEXTLENGTH 512 +#define PLAYER_PUSH_VELOCITY 50 // Players push teammates if they're moving under this speed + +// Debug Options +//#define MAP_DEBUG // Debug for Map code. I suggest running in a hi-res + // mode and/or piping the output from the server to a file. +#ifdef MAP_DEBUG + #define MDEBUG(x) x +#else + #define MDEBUG(x) +#endif +//#define VERBOSE // Verbose Debugging on/off + +//=========================================================================== +// OLD QUAKE Defs +//=========================================================================== +// items +#define IT_AXE 4096 +#define IT_SHOTGUN 1 +#define IT_SUPER_SHOTGUN 2 +#define IT_NAILGUN 4 +#define IT_SUPER_NAILGUN 8 +#define IT_GRENADE_LAUNCHER 16 +#define IT_ROCKET_LAUNCHER 32 +#define IT_LIGHTNING 64 +#define IT_EXTRA_WEAPON 128 + +#define IT_SHELLS 256 +#define IT_NAILS 512 +#define IT_ROCKETS 1024 +#define IT_CELLS 2048 + +#define IT_ARMOR1 8192 +#define IT_ARMOR2 16384 +#define IT_ARMOR3 32768 +#define IT_SUPERHEALTH 65536 + +#define IT_KEY1 131072 +#define IT_KEY2 262144 + +#define IT_INVISIBILITY 524288 +#define IT_INVULNERABILITY 1048576 +#define IT_SUIT 2097152 +#define IT_QUAD 4194304 +#define IT_HOOK 8388608 + +#define IT_KEY3 16777216 // Stomp invisibility +#define IT_KEY4 33554432 // Stomp invulnerability + +//=========================================================================== +// TEAMFORTRESS Defs +//=========================================================================== +// TeamFortress State Flags +#define TFSTATE_GRENPRIMED 1 // Whether the player has a primed grenade +#define TFSTATE_RELOADING 2 // Whether the player is reloading +#define TFSTATE_ALTKILL 4 // #TRUE if killed with a weapon not in self.weapon: NOT USED ANYMORE +#define TFSTATE_RANDOMPC 8 // Whether Playerclass is random, new one each respawn +#define TFSTATE_INFECTED 16 // set when player is infected by the bioweapon +#define TFSTATE_INVINCIBLE 32 // Player has permanent Invincibility (Usually by GoalItem) +#define TFSTATE_INVISIBLE 64 // Player has permanent Invisibility (Usually by GoalItem) +#define TFSTATE_QUAD 128 // Player has permanent Quad Damage (Usually by GoalItem) +#define TFSTATE_RADSUIT 256 // Player has permanent Radsuit (Usually by GoalItem) +#define TFSTATE_BURNING 512 // Is on fire +#define TFSTATE_GRENTHROWING 1024 // is throwing a grenade +#define TFSTATE_AIMING 2048 // is using the laser sight +#define TFSTATE_ZOOMOFF 4096 // doesn't want the FOV changed when zooming +#define TFSTATE_RESPAWN_READY 8192 // is waiting for respawn, and has pressed fire +#define TFSTATE_HALLUCINATING 16384 // set when player is hallucinating +#define TFSTATE_TRANQUILISED 32768 // set when player is tranquilised +#define TFSTATE_CANT_MOVE 65536 // set when player is setting a detpack +#define TFSTATE_RESET_FLAMETIME 131072 // set when the player has to have his flames increased in health + +// Defines used by TF_T_Damage (see combat.qc) +#define TF_TD_IGNOREARMOUR 1 // Bypasses the armour of the target +#define TF_TD_NOTTEAM 2 // Doesn't damage a team member (indicates direct fire weapon) +#define TF_TD_NOTSELF 4 // Doesn't damage self + +#define TF_TD_OTHER 0 // Ignore armorclass +#define TF_TD_SHOT 1 // Bullet damage +#define TF_TD_NAIL 2 // Nail damage +#define TF_TD_EXPLOSION 4 // Explosion damage +#define TF_TD_ELECTRICITY 8 // Electric damage +#define TF_TD_FIRE 16 // Fire damage +#define TF_TD_NOSOUND 256 // Special damage. Makes no sound/painframe, etc + +/*==================================================*/ +/* Toggleable Game Settings */ +/*==================================================*/ +#define TF_RESPAWNDELAY1 5 // seconds of waiting before player can respawn +#define TF_RESPAWNDELAY2 10 // seconds of waiting before player can respawn +#define TF_RESPAWNDELAY3 20 // seconds of waiting before player can respawn + +#define TEAMPLAY_NORMAL 1 +#define TEAMPLAY_HALFDIRECT 2 +#define TEAMPLAY_NODIRECT 4 +#define TEAMPLAY_HALFEXPLOSIVE 8 +#define TEAMPLAY_NOEXPLOSIVE 16 +#define TEAMPLAY_LESSPLAYERSHELP 32 +#define TEAMPLAY_LESSSCOREHELP 64 +#define TEAMPLAY_HALFDIRECTARMOR 128 +#define TEAMPLAY_NODIRECTARMOR 256 +#define TEAMPLAY_HALFEXPARMOR 512 +#define TEAMPLAY_NOEXPARMOR 1024 +#define TEAMPLAY_HALFDIRMIRROR 2048 +#define TEAMPLAY_FULLDIRMIRROR 4096 +#define TEAMPLAY_HALFEXPMIRROR 8192 +#define TEAMPLAY_FULLEXPMIRROR 16384 + +#define TEAMPLAY_TEAMDAMAGE (TEAMPLAY_NODIRECT | TEAMPLAY_HALFDIRECT | TEAMPLAY_HALFEXPLOSIVE | TEAMPLAY_NOEXPLOSIVE) +// FortressMap stuff +#define TEAM1_CIVILIANS 1 +#define TEAM2_CIVILIANS 2 +#define TEAM3_CIVILIANS 4 +#define TEAM4_CIVILIANS 8 + +// Defines for the playerclass +#define PC_UNDEFINED 0 + +#define PC_SCOUT 1 +#define PC_SNIPER 2 +#define PC_SOLDIER 3 +#define PC_DEMOMAN 4 +#define PC_MEDIC 5 +#define PC_HVYWEAP 6 +#define PC_PYRO 7 +#define PC_SPY 8 +#define PC_ENGINEER 9 + +// Insert new class definitions here + +// PC_RANDOM _MUST_ be the third last class +#define PC_RANDOM 10 // Random playerclass +#define PC_CIVILIAN 11 // Civilians are a special class. They cannot + // be chosen by players, only enforced by maps +#define PC_LASTCLASS 12 // Use this as the high-boundary for any loops + // through the playerclass. + +#define SENTRY_COLOR 10 // will be in the PC_RANDOM slot for team colors + +// These are just for the scanner +#define SCAN_SENTRY 13 +#define SCAN_GOALITEM 14 + +// Values returned by CheckArea +enum +{ + CAREA_CLEAR, + CAREA_BLOCKED, + CAREA_NOBUILD +}; + +/*==================================================*/ +/* Impulse Defines */ +/*==================================================*/ +// Alias check to see whether they already have the aliases +#define TF_ALIAS_CHECK 13 + +// CTF Support Impulses +#define HOOK_IMP1 22 +#define FLAG_INFO 23 +#define HOOK_IMP2 39 + +// Axe +#define AXE_IMP 40 + +// Camera Impulse +#define TF_CAM_TARGET 50 +#define TF_CAM_ZOOM 51 +#define TF_CAM_ANGLE 52 +#define TF_CAM_VEC 53 +#define TF_CAM_PROJECTILE 54 +#define TF_CAM_PROJECTILE_Z 55 +#define TF_CAM_REVANGLE 56 +#define TF_CAM_OFFSET 57 +#define TF_CAM_DROP 58 +#define TF_CAM_FADETOBLACK 59 +#define TF_CAM_FADEFROMBLACK 60 +#define TF_CAM_FADETOWHITE 61 +#define TF_CAM_FADEFROMWHITE 62 + +// Last Weapon impulse +#define TF_LAST_WEAPON 69 + +// Status Bar Resolution Settings. Same as CTF to maintain ease of use. +#define TF_STATUSBAR_RES_START 71 +#define TF_STATUSBAR_RES_END 81 + +// Clan Messages +#define TF_MESSAGE_1 82 +#define TF_MESSAGE_2 83 +#define TF_MESSAGE_3 84 +#define TF_MESSAGE_4 85 +#define TF_MESSAGE_5 86 + +#define TF_CHANGE_CLASS 99 // Bring up the Class Change menu + +// Added to PC_??? to get impulse to use if this clashes with your +// own impulses, just change this value, not the PC_?? +#define TF_CHANGEPC 100 +// The next few impulses are all the class selections +//PC_SCOUT 101 +//PC_SNIPER 102 +//PC_SOLDIER 103 +//PC_DEMOMAN 104 +//PC_MEDIC 105 +//PC_HVYWEAP 106 +//PC_PYRO 107 +//PC_RANDOM 108 +//PC_CIVILIAN 109 // Cannot be used +//PC_SPY 110 +//PC_ENGINEER 111 + +// Help impulses +#define TF_DISPLAYLOCATION 118 +#define TF_STATUS_QUERY 119 + +#define TF_HELP_MAP 131 + +// Information impulses +#define TF_INVENTORY 135 +#define TF_SHOWTF 136 +#define TF_SHOWLEGALCLASSES 137 + +// Team Impulses +#define TF_TEAM_1 140 // Join Team 1 +#define TF_TEAM_2 141 // Join Team 2 +#define TF_TEAM_3 142 // Join Team 3 +#define TF_TEAM_4 143 // Join Team 4 +#define TF_TEAM_CLASSES 144 // Impulse to display team classes +#define TF_TEAM_SCORES 145 // Impulse to display team scores +#define TF_TEAM_LIST 146 // Impulse to display the players in each team. + +// Grenade Impulses +#define TF_GRENADE_1 150 // Prime grenade type 1 +#define TF_GRENADE_2 151 // Prime grenade type 2 +#define TF_GRENADE_T 152 // Throw primed grenade + +// Impulses for new items +//#define TF_SCAN 159 // Scanner Pre-Impulse +#define TF_AUTO_SCAN 159 // Scanner On/Off +#define TF_SCAN_ENEMY 160 // Impulses to toggle scanning of enemies +#define TF_SCAN_FRIENDLY 161 // Impulses to toggle scanning of friendlies +//#define TF_SCAN_10 162 // Scan using 10 enery (1 cell) +#define TF_SCAN_SOUND 162 // Scanner sounds on/off +#define TF_SCAN_30 163 // Scan using 30 energy (2 cells) +#define TF_SCAN_100 164 // Scan using 100 energy (5 cells) +#define TF_DETPACK_5 165 // Detpack set to 5 seconds +#define TF_DETPACK_20 166 // Detpack set to 20 seconds +#define TF_DETPACK_50 167 // Detpack set to 50 seconds +#define TF_DETPACK 168 // Detpack Pre-Impulse +#define TF_DETPACK_STOP 169 // Impulse to stop setting detpack +#define TF_PB_DETONATE 170 // Detonate Pipebombs + +// Special skill +#define TF_SPECIAL_SKILL 171 + +// Ammo Drop impulse +#define TF_DROP_AMMO 172 + +// Reload impulse +#define TF_RELOAD 173 + +// auto-zoom toggle +#define TF_AUTOZOOM 174 + +// drop/pass commands +#define TF_DROPKEY 175 + +// Select Medikit +#define TF_MEDIKIT 176 + +// Spy Impulses +#define TF_SPY_SPY 177 // On net, go invisible, on LAN, change skin/color +#define TF_SPY_DIE 178 // Feign Death + +// Engineer Impulses +#define TF_ENGINEER_BUILD 179 +#define TF_ENGINEER_SANDBAG 180 + +// Medic +#define TF_MEDIC_HELPME 181 + +// Status bar +#define TF_STATUSBAR_ON 182 +#define TF_STATUSBAR_OFF 183 + +// Discard impulse +#define TF_DISCARD 184 + +// ID Player impulse +#define TF_ID 185 + +// Clan Battle impulses +#define TF_SHOWIDS 186 + +// More Engineer Impulses +#define TF_ENGINEER_DETDISP 187 +#define TF_ENGINEER_DETSENT 188 + +// Admin Commands +#define TF_ADMIN_DEAL_CYCLE 189 +#define TF_ADMIN_KICK 190 +#define TF_ADMIN_BAN 191 +#define TF_ADMIN_COUNTPLAYERS 192 +#define TF_ADMIN_CEASEFIRE 193 + +// Drop Goal Items +#define TF_DROPGOALITEMS 194 + +// More Admin Commands +#define TF_ADMIN_NEXT 195 + +// More Engineer Impulses +#define TF_ENGINEER_DETEXIT 196 +#define TF_ENGINEER_DETENTRANCE 197 + +// Yet MORE Admin Commands +#define TF_ADMIN_LISTIPS 198 + +// Silent Spy Feign +#define TF_SPY_SILENTDIE 199 + + +/*==================================================*/ +/* Defines for the ENGINEER's Building ability */ +/*==================================================*/ +// Ammo costs +#define AMMO_COST_SHELLS 2 // Metal needed to make 1 shell +#define AMMO_COST_NAILS 1 +#define AMMO_COST_ROCKETS 2 +#define AMMO_COST_CELLS 2 + +// Building types +#define BUILD_DISPENSER 1 +#define BUILD_SENTRYGUN 2 +#define BUILD_MORTAR 3 +#define BUILD_TELEPORTER_ENTRANCE 4 +#define BUILD_TELEPORTER_EXIT 5 + +// Building metal costs +#define BUILD_COST_DISPENSER 100 // Metal needed to built +#define BUILD_COST_SENTRYGUN 130 +#define BUILD_COST_MORTAR 150 +#define BUILD_COST_TELEPORTER 125 + +#define BUILD_COST_SANDBAG 20 // Built with a separate alias + +// Building times +#define BUILD_TIME_DISPENSER 2 // seconds to build +#define BUILD_TIME_SENTRYGUN 5 +#define BUILD_TIME_MORTAR 5 +#define BUILD_TIME_TELEPORTER 4 + +// Building health levels +#define BUILD_HEALTH_DISPENSER 150 // Health of the building +#define BUILD_HEALTH_SENTRYGUN 150 +#define BUILD_HEALTH_MORTAR 200 +#define BUILD_HEALTH_TELEPORTER 80 + +// Dispenser's maximum carrying capability +#define BUILD_DISPENSER_MAX_SHELLS 400 +#define BUILD_DISPENSER_MAX_NAILS 600 +#define BUILD_DISPENSER_MAX_ROCKETS 300 +#define BUILD_DISPENSER_MAX_CELLS 400 +#define BUILD_DISPENSER_MAX_ARMOR 500 + +// Build state sent down to client +#define BS_BUILDING (1<<0) +#define BS_HAS_DISPENSER (1<<1) +#define BS_HAS_SENTRYGUN (1<<2) +#define BS_CANB_DISPENSER (1<<3) +#define BS_CANB_SENTRYGUN (1<<4) +/*==================================================*/ +/* Ammo quantities for dropping & dispenser use */ +/*==================================================*/ +#define DROP_SHELLS 20 +#define DROP_NAILS 20 +#define DROP_ROCKETS 10 +#define DROP_CELLS 10 +#define DROP_ARMOR 40 + +/*==================================================*/ +/* Team Defines */ +/*==================================================*/ +#define TM_MAX_NO 4 // Max number of teams. Simply changing this value isn't enough. + // A new global to hold new team colors is needed, and more flags + // in the spawnpoint spawnflags may need to be used. + // Basically, don't change this unless you know what you're doing :) + +/*==================================================*/ +/* New Weapon Defines */ +/*==================================================*/ +#define WEAP_HOOK 1 +#define WEAP_BIOWEAPON 2 +#define WEAP_MEDIKIT 4 +#define WEAP_SPANNER 8 +#define WEAP_AXE 16 +#define WEAP_SNIPER_RIFLE 32 +#define WEAP_AUTO_RIFLE 64 +#define WEAP_SHOTGUN 128 +#define WEAP_SUPER_SHOTGUN 256 +#define WEAP_NAILGUN 512 +#define WEAP_SUPER_NAILGUN 1024 +#define WEAP_GRENADE_LAUNCHER 2048 +#define WEAP_FLAMETHROWER 4096 +#define WEAP_ROCKET_LAUNCHER 8192 +#define WEAP_INCENDIARY 16384 +#define WEAP_ASSAULT_CANNON 32768 +#define WEAP_LIGHTNING 65536 +#define WEAP_DETPACK 131072 +#define WEAP_TRANQ 262144 +#define WEAP_LASER 524288 +// still room for 12 more weapons +// but we can remove some by giving the weapons +// a weapon mode (like the rifle) + +// HL-compatible weapon numbers +#define WEAPON_HOOK 1 +#define WEAPON_BIOWEAPON (WEAPON_HOOK+1) +#define WEAPON_MEDIKIT (WEAPON_HOOK+2) +#define WEAPON_SPANNER (WEAPON_HOOK+3) +#define WEAPON_AXE (WEAPON_HOOK+4) +#define WEAPON_SNIPER_RIFLE (WEAPON_HOOK+5) +#define WEAPON_AUTO_RIFLE (WEAPON_HOOK+6) +#define WEAPON_TF_SHOTGUN (WEAPON_HOOK+7) +#define WEAPON_SUPER_SHOTGUN (WEAPON_HOOK+8) +#define WEAPON_NAILGUN (WEAPON_HOOK+9) +#define WEAPON_SUPER_NAILGUN (WEAPON_HOOK+10) +#define WEAPON_GRENADE_LAUNCHER (WEAPON_HOOK+11) +#define WEAPON_FLAMETHROWER (WEAPON_HOOK+12) +#define WEAPON_ROCKET_LAUNCHER (WEAPON_HOOK+13) +#define WEAPON_INCENDIARY (WEAPON_HOOK+14) +#define WEAPON_ASSAULT_CANNON (WEAPON_HOOK+16) +#define WEAPON_LIGHTNING (WEAPON_HOOK+17) +#define WEAPON_DETPACK (WEAPON_HOOK+18) +#define WEAPON_TRANQ (WEAPON_HOOK+19) +#define WEAPON_LASER (WEAPON_HOOK+20) +#define WEAPON_PIPEBOMB_LAUNCHER (WEAPON_HOOK+21) +#define WEAPON_KNIFE (WEAPON_HOOK+22) +#define WEAPON_BENCHMARK (WEAPON_HOOK+23) + +/*==================================================*/ +/* New Weapon Related Defines */ +/*==================================================*/ +// shots per reload +#define RE_SHOTGUN 8 +#define RE_SUPER_SHOTGUN 16 // 8 shots +#define RE_GRENADE_LAUNCHER 6 +#define RE_ROCKET_LAUNCHER 4 + +// reload times +#define RE_SHOTGUN_TIME 2 +#define RE_SUPER_SHOTGUN_TIME 3 +#define RE_GRENADE_LAUNCHER_TIME 4 +#define RE_ROCKET_LAUNCHER_TIME 5 + +// Maximum velocity you can move and fire the Sniper Rifle +#define WEAP_SNIPER_RIFLE_MAX_MOVE 50 + +// Medikit +#define WEAP_MEDIKIT_HEAL 200 // Amount medikit heals per hit +#define WEAP_MEDIKIT_OVERHEAL 50 // Amount of superhealth over max_health the medikit will dispense + +// Spanner +#define WEAP_SPANNER_REPAIR 10 + +// Detpack +#define WEAP_DETPACK_DISARMTIME 3 // Time it takes to disarm a Detpack +#define WEAP_DETPACK_SETTIME 3 // Time it takes to set a Detpack +#define WEAP_DETPACK_SIZE 700 // Explosion Size +#define WEAP_DETPACK_GOAL_SIZE 1500 // Explosion Size for goal triggering +#define WEAP_DETPACK_BITS_NO 12 // Bits that detpack explodes into + +// Tranquiliser Gun +#define TRANQ_TIME 15 + +// Grenades +#define GR_PRIMETIME 3 +#define GR_CALTROP_PRIME 0.5 +#define GR_TYPE_NONE 0 +#define GR_TYPE_NORMAL 1 +#define GR_TYPE_CONCUSSION 2 +#define GR_TYPE_NAIL 3 +#define GR_TYPE_MIRV 4 +#define GR_TYPE_NAPALM 5 +//#define GR_TYPE_FLARE 6 +#define GR_TYPE_GAS 7 +#define GR_TYPE_EMP 8 +#define GR_TYPE_CALTROP 9 +//#define GR_TYPE_FLASH 10 + +// Defines for WeaponMode +#define GL_NORMAL 0 +#define GL_PIPEBOMB 1 + +// Defines for OLD Concussion Grenade +#define GR_OLD_CONCUSS_TIME 5 +#define GR_OLD_CONCUSS_DEC 20 + +// Defines for Concussion Grenade +#define GR_CONCUSS_TIME 0.25 +#define GR_CONCUSS_DEC 10 +#define MEDIUM_PING 150 +#define HIGH_PING 200 + +// Defines for the Gas Grenade +#define GR_HALLU_TIME 0.3 +#define GR_OLD_HALLU_TIME 0.5 +#define GR_HALLU_DEC 2.5 + +// Defines for the BioInfection +#define BIO_JUMP_RADIUS 128 // The distance the bioinfection can jump between players + +/*==================================================*/ +/* New Items */ +/*==================================================*/ +#define NIT_SCANNER 1 + +#define NIT_SILVER_DOOR_OPENED #IT_KEY1 // 131072 +#define NIT_GOLD_DOOR_OPENED #IT_KEY2 // 262144 + +/*==================================================*/ +/* New Item Flags */ +/*==================================================*/ +#define NIT_SCANNER_ENEMY 1 // Detect enemies +#define NIT_SCANNER_FRIENDLY 2 // Detect friendlies (team members) +#define NIT_SCANNER_SOUND 4 // Motion detection. Only report moving entities. + +/*==================================================*/ +/* New Item Related Defines */ +/*==================================================*/ +#define NIT_SCANNER_POWER 25 // The amount of power spent on a scan with the scanner + // is multiplied by this to get the scanrange. +#define NIT_SCANNER_MAXCELL 50 // The maximum number of cells than can be used in one scan +#define NIT_SCANNER_MIN_MOVEMENT 50 // The minimum velocity an entity must have to be detected + // by scanners that only detect movement + +/*==================================================*/ +/* Variables used for New Weapons and Reloading */ +/*==================================================*/ +// Armor Classes : Bitfields. Use the "armorclass" of armor for the Armor Type. +#define AT_SAVESHOT 1 // Kevlar : Reduces bullet damage by 15% +#define AT_SAVENAIL 2 // Wood :) : Reduces nail damage by 15% +#define AT_SAVEEXPLOSION 4 // Blast : Reduces explosion damage by 15% +#define AT_SAVEELECTRICITY 8 // Shock : Reduces electricity damage by 15% +#define AT_SAVEFIRE 16 // Asbestos : Reduces fire damage by 15% + +/*==========================================================================*/ +/* TEAMFORTRESS CLASS DETAILS */ +/*==========================================================================*/ +// Class Details for SCOUT +#define PC_SCOUT_SKIN 4 // Skin for this class when Classkin is on. +#define PC_SCOUT_MAXHEALTH 75 // Maximum Health Level +#define PC_SCOUT_MAXSPEED 400 // Maximum movement speed +#define PC_SCOUT_MAXSTRAFESPEED 400 // Maximum strafing movement speed +#define PC_SCOUT_MAXARMOR 50 // Maximum Armor Level, of any armor class +#define PC_SCOUT_INITARMOR 25 // Armor level when respawned +#define PC_SCOUT_MAXARMORTYPE 0.3 // Maximum level of Armor absorption +#define PC_SCOUT_INITARMORTYPE 0.3 // Absorption Level of armor when respawned +#define PC_SCOUT_ARMORCLASSES 3 // #AT_SAVESHOT | #AT_SAVENAIL <-Armor Classes allowed for this class +#define PC_SCOUT_INITARMORCLASS 0 // Armorclass worn when respawned +#define PC_SCOUT_WEAPONS WEAP_AXE | WEAP_SHOTGUN | WEAP_NAILGUN +#define PC_SCOUT_MAXAMMO_SHOT 50 // Maximum amount of shot ammo this class can carry +#define PC_SCOUT_MAXAMMO_NAIL 200 // Maximum amount of nail ammo this class can carry +#define PC_SCOUT_MAXAMMO_CELL 100 // Maximum amount of cell ammo this class can carry +#define PC_SCOUT_MAXAMMO_ROCKET 25 // Maximum amount of rocket ammo this class can carry +#define PC_SCOUT_INITAMMO_SHOT 25 // Amount of shot ammo this class has when respawned +#define PC_SCOUT_INITAMMO_NAIL 100 // Amount of nail ammo this class has when respawned +#define PC_SCOUT_INITAMMO_CELL 50 // Amount of cell ammo this class has when respawned +#define PC_SCOUT_INITAMMO_ROCKET 0 // Amount of rocket ammo this class has when respawned +#define PC_SCOUT_GRENADE_TYPE_1 GR_TYPE_CALTROP // <- 1st Type of Grenade this class has +#define PC_SCOUT_GRENADE_TYPE_2 GR_TYPE_CONCUSSION // <- 2nd Type of Grenade this class has +#define PC_SCOUT_GRENADE_INIT_1 2 // Number of grenades of Type 1 this class has when respawned +#define PC_SCOUT_GRENADE_INIT_2 3 // Number of grenades of Type 2 this class has when respawned +#define PC_SCOUT_TF_ITEMS NIT_SCANNER // <- TeamFortress Items this class has + +#define PC_SCOUT_MOTION_MIN_I 0.5 // < Short range +#define PC_SCOUT_MOTION_MIN_MOVE 50 // Minimum vlen of player velocity to be picked up by motion detector +#define PC_SCOUT_SCAN_TIME 2 // # of seconds between each scan pulse +#define PC_SCOUT_SCAN_RANGE 100 // Default scanner range +#define PC_SCOUT_SCAN_COST 2 // Default scanner cell useage per scan + +// Class Details for SNIPER +#define PC_SNIPER_SKIN 5 +#define PC_SNIPER_MAXHEALTH 90 +#define PC_SNIPER_MAXSPEED 300 +#define PC_SNIPER_MAXSTRAFESPEED 300 +#define PC_SNIPER_MAXARMOR 50 +#define PC_SNIPER_INITARMOR 0 +#define PC_SNIPER_MAXARMORTYPE 0.3 +#define PC_SNIPER_INITARMORTYPE 0.3 +#define PC_SNIPER_ARMORCLASSES 3 // #AT_SAVESHOT | #AT_SAVENAIL +#define PC_SNIPER_INITARMORCLASS 0 +#define PC_SNIPER_WEAPONS WEAP_SNIPER_RIFLE | WEAP_AUTO_RIFLE | WEAP_AXE | WEAP_NAILGUN +#define PC_SNIPER_MAXAMMO_SHOT 75 +#define PC_SNIPER_MAXAMMO_NAIL 100 +#define PC_SNIPER_MAXAMMO_CELL 50 +#define PC_SNIPER_MAXAMMO_ROCKET 25 +#define PC_SNIPER_INITAMMO_SHOT 60 +#define PC_SNIPER_INITAMMO_NAIL 50 +#define PC_SNIPER_INITAMMO_CELL 0 +#define PC_SNIPER_INITAMMO_ROCKET 0 +#define PC_SNIPER_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_SNIPER_GRENADE_TYPE_2 GR_TYPE_NONE +#define PC_SNIPER_GRENADE_INIT_1 2 +#define PC_SNIPER_GRENADE_INIT_2 0 +#define PC_SNIPER_TF_ITEMS 0 + +// Class Details for SOLDIER +#define PC_SOLDIER_SKIN 6 +#define PC_SOLDIER_MAXHEALTH 100 +#define PC_SOLDIER_MAXSPEED 240 +#define PC_SOLDIER_MAXSTRAFESPEED 240 +#define PC_SOLDIER_MAXARMOR 200 +#define PC_SOLDIER_INITARMOR 100 +#define PC_SOLDIER_MAXARMORTYPE 0.8 +#define PC_SOLDIER_INITARMORTYPE 0.8 +#define PC_SOLDIER_ARMORCLASSES 31 // ALL +#define PC_SOLDIER_INITARMORCLASS 0 +#define PC_SOLDIER_WEAPONS WEAP_AXE | WEAP_SHOTGUN | WEAP_SUPER_SHOTGUN | WEAP_ROCKET_LAUNCHER +#define PC_SOLDIER_MAXAMMO_SHOT 100 +#define PC_SOLDIER_MAXAMMO_NAIL 100 +#define PC_SOLDIER_MAXAMMO_CELL 50 +#define PC_SOLDIER_MAXAMMO_ROCKET 50 +#define PC_SOLDIER_INITAMMO_SHOT 50 +#define PC_SOLDIER_INITAMMO_NAIL 0 +#define PC_SOLDIER_INITAMMO_CELL 0 +#define PC_SOLDIER_INITAMMO_ROCKET 10 +#define PC_SOLDIER_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_SOLDIER_GRENADE_TYPE_2 GR_TYPE_NAIL +#define PC_SOLDIER_GRENADE_INIT_1 2 +#define PC_SOLDIER_GRENADE_INIT_2 1 +#define PC_SOLDIER_TF_ITEMS 0 + +#define MAX_NAIL_GRENS 2 // Can only have 2 Nail grens active +#define MAX_NAPALM_GRENS 2 // Can only have 2 Napalm grens active +#define MAX_GAS_GRENS 2 // Can only have 2 Gas grenades active +#define MAX_MIRV_GRENS 2 // Can only have 2 Mirv's +#define MAX_CONCUSSION_GRENS 3 +#define MAX_CALTROP_CANS 3 + +// Class Details for DEMOLITION MAN +#define PC_DEMOMAN_SKIN 1 +#define PC_DEMOMAN_MAXHEALTH 90 +#define PC_DEMOMAN_MAXSPEED 280 +#define PC_DEMOMAN_MAXSTRAFESPEED 280 +#define PC_DEMOMAN_MAXARMOR 120 +#define PC_DEMOMAN_INITARMOR 50 +#define PC_DEMOMAN_MAXARMORTYPE 0.6 +#define PC_DEMOMAN_INITARMORTYPE 0.6 +#define PC_DEMOMAN_ARMORCLASSES 31 // ALL +#define PC_DEMOMAN_INITARMORCLASS 0 +#define PC_DEMOMAN_WEAPONS WEAP_AXE | WEAP_SHOTGUN | WEAP_GRENADE_LAUNCHER | WEAP_DETPACK +#define PC_DEMOMAN_MAXAMMO_SHOT 75 +#define PC_DEMOMAN_MAXAMMO_NAIL 50 +#define PC_DEMOMAN_MAXAMMO_CELL 50 +#define PC_DEMOMAN_MAXAMMO_ROCKET 50 +#define PC_DEMOMAN_MAXAMMO_DETPACK 1 +#define PC_DEMOMAN_INITAMMO_SHOT 30 +#define PC_DEMOMAN_INITAMMO_NAIL 0 +#define PC_DEMOMAN_INITAMMO_CELL 0 +#define PC_DEMOMAN_INITAMMO_ROCKET 20 +#define PC_DEMOMAN_INITAMMO_DETPACK 1 +#define PC_DEMOMAN_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_DEMOMAN_GRENADE_TYPE_2 GR_TYPE_MIRV +#define PC_DEMOMAN_GRENADE_INIT_1 2 +#define PC_DEMOMAN_GRENADE_INIT_2 2 +#define PC_DEMOMAN_TF_ITEMS 0 + +// Class Details for COMBAT MEDIC +#define PC_MEDIC_SKIN 3 +#define PC_MEDIC_MAXHEALTH 90 +#define PC_MEDIC_MAXSPEED 320 +#define PC_MEDIC_MAXSTRAFESPEED 320 +#define PC_MEDIC_MAXARMOR 100 +#define PC_MEDIC_INITARMOR 50 +#define PC_MEDIC_MAXARMORTYPE 0.6 +#define PC_MEDIC_INITARMORTYPE 0.3 +#define PC_MEDIC_ARMORCLASSES 11 // ALL except EXPLOSION +#define PC_MEDIC_INITARMORCLASS 0 +#define PC_MEDIC_WEAPONS WEAP_BIOWEAPON | WEAP_MEDIKIT | WEAP_SHOTGUN | WEAP_SUPER_SHOTGUN | WEAP_SUPER_NAILGUN +#define PC_MEDIC_MAXAMMO_SHOT 75 +#define PC_MEDIC_MAXAMMO_NAIL 150 +#define PC_MEDIC_MAXAMMO_CELL 50 +#define PC_MEDIC_MAXAMMO_ROCKET 25 +#define PC_MEDIC_MAXAMMO_MEDIKIT 100 +#define PC_MEDIC_INITAMMO_SHOT 50 +#define PC_MEDIC_INITAMMO_NAIL 50 +#define PC_MEDIC_INITAMMO_CELL 0 +#define PC_MEDIC_INITAMMO_ROCKET 0 +#define PC_MEDIC_INITAMMO_MEDIKIT 50 +#define PC_MEDIC_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_MEDIC_GRENADE_TYPE_2 GR_TYPE_CONCUSSION +#define PC_MEDIC_GRENADE_INIT_1 2 +#define PC_MEDIC_GRENADE_INIT_2 2 +#define PC_MEDIC_TF_ITEMS 0 +#define PC_MEDIC_REGEN_TIME 3 // Number of seconds between each regen. +#define PC_MEDIC_REGEN_AMOUNT 2 // Amount of health regenerated each regen. + +// Class Details for HVYWEAP +#define PC_HVYWEAP_SKIN 2 +#define PC_HVYWEAP_MAXHEALTH 100 +#define PC_HVYWEAP_MAXSPEED 230 +#define PC_HVYWEAP_MAXSTRAFESPEED 230 +#define PC_HVYWEAP_MAXARMOR 300 +#define PC_HVYWEAP_INITARMOR 150 +#define PC_HVYWEAP_MAXARMORTYPE 0.8 +#define PC_HVYWEAP_INITARMORTYPE 0.8 +#define PC_HVYWEAP_ARMORCLASSES 31 // ALL +#define PC_HVYWEAP_INITARMORCLASS 0 +#define PC_HVYWEAP_WEAPONS WEAP_ASSAULT_CANNON | WEAP_AXE | WEAP_SHOTGUN | WEAP_SUPER_SHOTGUN +#define PC_HVYWEAP_MAXAMMO_SHOT 200 +#define PC_HVYWEAP_MAXAMMO_NAIL 200 +#define PC_HVYWEAP_MAXAMMO_CELL 50 +#define PC_HVYWEAP_MAXAMMO_ROCKET 25 +#define PC_HVYWEAP_INITAMMO_SHOT 200 +#define PC_HVYWEAP_INITAMMO_NAIL 0 +#define PC_HVYWEAP_INITAMMO_CELL 30 +#define PC_HVYWEAP_INITAMMO_ROCKET 0 +#define PC_HVYWEAP_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_HVYWEAP_GRENADE_TYPE_2 GR_TYPE_MIRV +#define PC_HVYWEAP_GRENADE_INIT_1 2 +#define PC_HVYWEAP_GRENADE_INIT_2 1 +#define PC_HVYWEAP_TF_ITEMS 0 +#define PC_HVYWEAP_CELL_USAGE 7 // Amount of cells spent to power up assault cannon + + + +// Class Details for PYRO +#define PC_PYRO_SKIN 21 +#define PC_PYRO_MAXHEALTH 100 +#define PC_PYRO_MAXSPEED 300 +#define PC_PYRO_MAXSTRAFESPEED 300 +#define PC_PYRO_MAXARMOR 150 +#define PC_PYRO_INITARMOR 50 +#define PC_PYRO_MAXARMORTYPE 0.6 +#define PC_PYRO_INITARMORTYPE 0.6 +#define PC_PYRO_ARMORCLASSES 27 // ALL except EXPLOSION +#define PC_PYRO_INITARMORCLASS 16 // #AT_SAVEFIRE +#define PC_PYRO_WEAPONS WEAP_INCENDIARY | WEAP_FLAMETHROWER | WEAP_AXE | WEAP_SHOTGUN +#define PC_PYRO_MAXAMMO_SHOT 40 +#define PC_PYRO_MAXAMMO_NAIL 50 +#define PC_PYRO_MAXAMMO_CELL 200 +#define PC_PYRO_MAXAMMO_ROCKET 20 +#define PC_PYRO_INITAMMO_SHOT 20 +#define PC_PYRO_INITAMMO_NAIL 0 +#define PC_PYRO_INITAMMO_CELL 120 +#define PC_PYRO_INITAMMO_ROCKET 5 +#define PC_PYRO_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_PYRO_GRENADE_TYPE_2 GR_TYPE_NAPALM +#define PC_PYRO_GRENADE_INIT_1 2 +#define PC_PYRO_GRENADE_INIT_2 4 +#define PC_PYRO_TF_ITEMS 0 +#define PC_PYRO_ROCKET_USAGE 3 // Number of rockets per incendiary cannon shot + +// Class Details for SPY +#define PC_SPY_SKIN 22 +#define PC_SPY_MAXHEALTH 90 +#define PC_SPY_MAXSPEED 300 +#define PC_SPY_MAXSTRAFESPEED 300 +#define PC_SPY_MAXARMOR 100 +#define PC_SPY_INITARMOR 25 +#define PC_SPY_MAXARMORTYPE 0.6 // Was 0.3 +#define PC_SPY_INITARMORTYPE 0.6 // Was 0.3 +#define PC_SPY_ARMORCLASSES 27 // ALL except EXPLOSION +#define PC_SPY_INITARMORCLASS 0 +#define PC_SPY_WEAPONS WEAP_AXE | WEAP_TRANQ | WEAP_SUPER_SHOTGUN | WEAP_NAILGUN +#define PC_SPY_MAXAMMO_SHOT 40 +#define PC_SPY_MAXAMMO_NAIL 100 +#define PC_SPY_MAXAMMO_CELL 30 +#define PC_SPY_MAXAMMO_ROCKET 15 +#define PC_SPY_INITAMMO_SHOT 40 +#define PC_SPY_INITAMMO_NAIL 50 +#define PC_SPY_INITAMMO_CELL 10 +#define PC_SPY_INITAMMO_ROCKET 0 +#define PC_SPY_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_SPY_GRENADE_TYPE_2 GR_TYPE_GAS +#define PC_SPY_GRENADE_INIT_1 2 +#define PC_SPY_GRENADE_INIT_2 2 +#define PC_SPY_TF_ITEMS 0 +#define PC_SPY_CELL_REGEN_TIME 5 +#define PC_SPY_CELL_REGEN_AMOUNT 1 +#define PC_SPY_CELL_USAGE 3 // Amount of cells spent while invisible +#define PC_SPY_GO_UNDERCOVER_TIME 4 // Time it takes to go undercover + +// Class Details for ENGINEER +#define PC_ENGINEER_SKIN 22 // Not used anymore +#define PC_ENGINEER_MAXHEALTH 80 +#define PC_ENGINEER_MAXSPEED 300 +#define PC_ENGINEER_MAXSTRAFESPEED 300 +#define PC_ENGINEER_MAXARMOR 50 +#define PC_ENGINEER_INITARMOR 25 +#define PC_ENGINEER_MAXARMORTYPE 0.6 +#define PC_ENGINEER_INITARMORTYPE 0.3 +#define PC_ENGINEER_ARMORCLASSES 31 // ALL +#define PC_ENGINEER_INITARMORCLASS 0 +#define PC_ENGINEER_WEAPONS WEAP_SPANNER | WEAP_LASER | WEAP_SUPER_SHOTGUN +#define PC_ENGINEER_MAXAMMO_SHOT 50 +#define PC_ENGINEER_MAXAMMO_NAIL 50 +#define PC_ENGINEER_MAXAMMO_CELL 200 // synonymous with metal +#define PC_ENGINEER_MAXAMMO_ROCKET 30 +#define PC_ENGINEER_INITAMMO_SHOT 20 +#define PC_ENGINEER_INITAMMO_NAIL 25 +#define PC_ENGINEER_INITAMMO_CELL 100 // synonymous with metal +#define PC_ENGINEER_INITAMMO_ROCKET 0 +#define PC_ENGINEER_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_ENGINEER_GRENADE_TYPE_2 GR_TYPE_EMP +#define PC_ENGINEER_GRENADE_INIT_1 2 +#define PC_ENGINEER_GRENADE_INIT_2 2 +#define PC_ENGINEER_TF_ITEMS 0 + +// Class Details for CIVILIAN +#define PC_CIVILIAN_SKIN 22 +#define PC_CIVILIAN_MAXHEALTH 50 +#define PC_CIVILIAN_MAXSPEED 240 +#define PC_CIVILIAN_MAXSTRAFESPEED 240 +#define PC_CIVILIAN_MAXARMOR 0 +#define PC_CIVILIAN_INITARMOR 0 +#define PC_CIVILIAN_MAXARMORTYPE 0 +#define PC_CIVILIAN_INITARMORTYPE 0 +#define PC_CIVILIAN_ARMORCLASSES 0 +#define PC_CIVILIAN_INITARMORCLASS 0 +#define PC_CIVILIAN_WEAPONS WEAP_AXE +#define PC_CIVILIAN_MAXAMMO_SHOT 0 +#define PC_CIVILIAN_MAXAMMO_NAIL 0 +#define PC_CIVILIAN_MAXAMMO_CELL 0 +#define PC_CIVILIAN_MAXAMMO_ROCKET 0 +#define PC_CIVILIAN_INITAMMO_SHOT 0 +#define PC_CIVILIAN_INITAMMO_NAIL 0 +#define PC_CIVILIAN_INITAMMO_CELL 0 +#define PC_CIVILIAN_INITAMMO_ROCKET 0 +#define PC_CIVILIAN_GRENADE_TYPE_1 0 +#define PC_CIVILIAN_GRENADE_TYPE_2 0 +#define PC_CIVILIAN_GRENADE_INIT_1 0 +#define PC_CIVILIAN_GRENADE_INIT_2 0 +#define PC_CIVILIAN_TF_ITEMS 0 + + +/*==========================================================================*/ +/* TEAMFORTRESS GOALS */ +/*==========================================================================*/ +// For all these defines, see the tfortmap.txt that came with the zip +// for complete descriptions. +// Defines for Goal Activation types : goal_activation (in goals) +#define TFGA_TOUCH 1 // Activated when touched +#define TFGA_TOUCH_DETPACK 2 // Activated when touched by a detpack explosion +#define TFGA_REVERSE_AP 4 // Activated when AP details are _not_ met +#define TFGA_SPANNER 8 // Activated when hit by an engineer's spanner +#define TFGA_DROPTOGROUND 2048 // Drop to Ground when spawning + +// Defines for Goal Effects types : goal_effect +#define TFGE_AP 1 // AP is affected. Default. +#define TFGE_AP_TEAM 2 // All of the AP's team. +#define TFGE_NOT_AP_TEAM 4 // All except AP's team. +#define TFGE_NOT_AP 8 // All except AP. +#define TFGE_WALL 16 // If set, walls stop the Radius effects +#define TFGE_SAME_ENVIRONMENT 32 // If set, players in a different environment to the Goal are not affected +#define TFGE_TIMER_CHECK_AP 64 // If set, Timer Goals check their critera for all players fitting their effects + +// Defines for Goal Result types : goal_result +#define TFGR_SINGLE 1 // Goal can only be activated once +#define TFGR_ADD_BONUSES 2 // Any Goals activated by this one give their bonuses +#define TFGR_ENDGAME 4 // Goal fires Intermission, displays scores, and ends level +#define TFGR_NO_ITEM_RESULTS 8 // GoalItems given by this Goal don't do results +#define TFGR_REMOVE_DISGUISE 16 // Prevent/Remove undercover from any Spy +#define TFGR_FORCE_RESPAWN 32 // Forces the player to teleport to a respawn point +#define TFGR_DESTROY_BUILDINGS 64 // Destroys this player's buildings, if anys + +// Defines for Goal Group Result types : goal_group +// None! +// But I'm leaving this variable in there, since it's fairly likely +// that some will show up sometime. + +// Defines for Goal Item types, : goal_activation (in items) +#define TFGI_GLOW 1 // Players carrying this GoalItem will glow +#define TFGI_SLOW 2 // Players carrying this GoalItem will move at half-speed +#define TFGI_DROP 4 // Players dying with this item will drop it +#define TFGI_RETURN_DROP 8 // Return if a player with it dies +#define TFGI_RETURN_GOAL 16 // Return if a player with it has it removed by a goal's activation +#define TFGI_RETURN_REMOVE 32 // Return if it is removed by TFGI_REMOVE +#define TFGI_REVERSE_AP 64 // Only pickup if the player _doesn't_ match AP Details +#define TFGI_REMOVE 128 // Remove if left untouched for 2 minutes after being dropped +#define TFGI_KEEP 256 // Players keep this item even when they die +#define TFGI_ITEMGLOWS 512 // Item glows when on the ground +#define TFGI_DONTREMOVERES 1024 // Don't remove results when the item is removed +#define TFGI_DROPTOGROUND 2048 // Drop To Ground when spawning +#define TFGI_CANBEDROPPED 4096 // Can be voluntarily dropped by players +#define TFGI_SOLID 8192 // Is solid... blocks bullets, etc + +// Defines for methods of GoalItem returning +#define GI_RET_DROP_DEAD 0 // Dropped by a dead player +#define GI_RET_DROP_LIVING 1 // Dropped by a living player +#define GI_RET_GOAL 2 // Returned by a Goal +#define GI_RET_TIME 3 // Returned due to timeout + +// Defines for TeamSpawnpoints : goal_activation (in teamspawns) +#define TFSP_MULTIPLEITEMS 1 // Give out the GoalItem multiple times +#define TFSP_MULTIPLEMSGS 2 // Display the message multiple times + +// Defines for TeamSpawnpoints : goal_effects (in teamspawns) +#define TFSP_REMOVESELF 1 // Remove itself after being spawned on + +// Defines for Goal States +#define TFGS_ACTIVE 1 +#define TFGS_INACTIVE 2 +#define TFGS_REMOVED 3 +#define TFGS_DELAYED 4 + +// Defines for GoalItem Removing from Player Methods +#define GI_DROP_PLAYERDEATH 0 // Dropped by a dying player +#define GI_DROP_REMOVEGOAL 1 // Removed by a Goal +#define GI_DROP_PLAYERDROP 2 // Dropped by a player + +// Legal Playerclass Handling +#define TF_ILL_SCOUT 1 +#define TF_ILL_SNIPER 2 +#define TF_ILL_SOLDIER 4 +#define TF_ILL_DEMOMAN 8 +#define TF_ILL_MEDIC 16 +#define TF_ILL_HVYWEP 32 +#define TF_ILL_PYRO 64 +#define TF_ILL_RANDOMPC 128 +#define TF_ILL_SPY 256 +#define TF_ILL_ENGINEER 512 + +// Addition classes +#define CLASS_TFGOAL 128 +#define CLASS_TFGOAL_TIMER 129 +#define CLASS_TFGOAL_ITEM 130 +#define CLASS_TFSPAWN 131 + +/*==========================================================================*/ +/* Flamethrower */ +/*==========================================================================*/ +#define FLAME_PLYRMAXTIME 5.0 // lifetime in seconds of a flame on a player +#define FLAME_MAXBURNTIME 8 // lifetime in seconds of a flame on the world (big ones) +#define NAPALM_MAXBURNTIME 20 // lifetime in seconds of flame from a napalm grenade +#define FLAME_MAXPLYRFLAMES 4 // maximum number of flames on a player +#define FLAME_NUMLIGHTS 1 // maximum number of light flame +#define FLAME_BURNRATIO 0.3 // the chance of a flame not 'sticking' +#define GR_TYPE_FLAMES_NO 15 // number of flames spawned when a grenade explode +#define FLAME_DAMAGE_TIME 1 // Interval between damage burns from flames +#define FLAME_EFFECT_TIME 0.2 // frequency at which we display flame effects. +#define FLAME_THINK_TIME 0.1 // Seconds between times the flame checks burn +#define PER_FLAME_DAMAGE 2 // Damage taken per second per flame by burning players + +/*==================================================*/ +/* CTF Support defines */ +/*==================================================*/ +#define CTF_FLAG1 1 +#define CTF_FLAG2 2 +#define CTF_DROPOFF1 3 +#define CTF_DROPOFF2 4 +#define CTF_SCORE1 5 +#define CTF_SCORE2 6 + +//.float hook_out; + +/*==================================================*/ +/* Camera defines */ +/*==================================================*/ +/* +float live_camera; +.float camdist; +.vector camangle; +.entity camera_list; +*/ + +/*==================================================*/ +/* QuakeWorld defines */ +/*==================================================*/ +/* +float already_chosen_map; + +// grappling hook variables +.entity hook; +.float on_hook; +.float fire_held_down;// flag - TRUE if player is still holding down the + // fire button after throwing a hook. +*/ +/*==================================================*/ +/* Server Settings */ +/*==================================================*/ +// Admin modes +#define ADMIN_MODE_NONE 0 +#define ADMIN_MODE_DEAL 1 + +/*==================================================*/ +/* Death Message defines */ +/*==================================================*/ +#define DMSG_SHOTGUN 1 +#define DMSG_SSHOTGUN 2 +#define DMSG_NAILGUN 3 +#define DMSG_SNAILGUN 4 +#define DMSG_GRENADEL 5 +#define DMSG_ROCKETL 6 +#define DMSG_LIGHTNING 7 +#define DMSG_GREN_HAND 8 +#define DMSG_GREN_NAIL 9 +#define DMSG_GREN_MIRV 10 +#define DMSG_GREN_PIPE 11 +#define DMSG_DETPACK 12 +#define DMSG_BIOWEAPON 13 +#define DMSG_BIOWEAPON_ATT 14 +#define DMSG_FLAME 15 +#define DMSG_DETPACK_DIS 16 +#define DMSG_AXE 17 +#define DMSG_SNIPERRIFLE 18 +#define DMSG_AUTORIFLE 19 +#define DMSG_ASSAULTCANNON 20 +#define DMSG_HOOK 21 +#define DMSG_BACKSTAB 22 +#define DMSG_MEDIKIT 23 +#define DMSG_GREN_GAS 24 +#define DMSG_TRANQ 25 +#define DMSG_LASERBOLT 26 +#define DMSG_SENTRYGUN_BULLET 27 +#define DMSG_SNIPERLEGSHOT 28 +#define DMSG_SNIPERHEADSHOT 29 +#define DMSG_GREN_EMP 30 +#define DMSG_GREN_EMP_AMMO 31 +#define DMSG_SPANNER 32 +#define DMSG_INCENDIARY 33 +#define DMSG_SENTRYGUN_ROCKET 34 +#define DMSG_GREN_FLASH 35 +#define DMSG_TRIGGER 36 +#define DMSG_MIRROR 37 +#define DMSG_SENTRYDEATH 38 +#define DMSG_DISPENSERDEATH 39 +#define DMSG_GREN_AIRPIPE 40 +#define DMSG_CALTROP 41 + +/*==================================================*/ +// TOGGLEFLAGS +/*==================================================*/ +// Some of the toggleflags aren't used anymore, but the bits are still +// there to provide compatability with old maps +#define TFLAG_CLASS_PERSIST (1 << 0) // Persistent Classes Bit +#define TFLAG_CHEATCHECK (1 << 1) // Cheatchecking Bit +#define TFLAG_RESPAWNDELAY (1 << 2) // RespawnDelay bit +//#define TFLAG_UN (1 << 3) // NOT USED ANYMORE +#define TFLAG_OLD_GRENS (1 << 3) // Use old concussion grenade and flash grenade +#define TFLAG_UN2 (1 << 4) // NOT USED ANYMORE +#define TFLAG_UN3 (1 << 5) // NOT USED ANYMORE +#define TFLAG_UN4 (1 << 6) // NOT USED ANYMORE: Was Autoteam. CVAR tfc_autoteam used now. +#define TFLAG_TEAMFRAGS (1 << 7) // Individual Frags, or Frags = TeamScore +#define TFLAG_FIRSTENTRY (1 << 8) // Used to determine the first time toggleflags is set + // In a map. Cannot be toggled by players. +#define TFLAG_SPYINVIS (1 << 9) // Spy invisible only +#define TFLAG_GRAPPLE (1 << 10) // Grapple on/off +//#define TFLAG_FULLTEAMSCORE (1 << 11) // Each Team's score is TeamScore + Frags +#define TFLAG_FLAGEMULATION (1 << 12) // Flag emulation on for old TF maps +#define TFLAG_USE_STANDARD (1 << 13) // Use the TF War standard for Flag emulation + +#define TFLAG_FRAGSCORING (1 << 14) // Use frag scoring only + +/*======================*/ +// Menu stuff // +/*======================*/ + +#define MENU_DEFAULT 1 +#define MENU_TEAM 2 +#define MENU_CLASS 3 +#define MENU_MAPBRIEFING 4 +#define MENU_INTRO 5 +#define MENU_CLASSHELP 6 +#define MENU_CLASSHELP2 7 +#define MENU_REPEATHELP 8 + +#define MENU_SPECHELP 9 + + +#define MENU_SPY 12 +#define MENU_SPY_SKIN 13 +#define MENU_SPY_COLOR 14 +#define MENU_ENGINEER 15 +#define MENU_ENGINEER_FIX_DISPENSER 16 +#define MENU_ENGINEER_FIX_SENTRYGUN 17 +#define MENU_ENGINEER_FIX_MORTAR 18 +#define MENU_DISPENSER 19 +#define MENU_CLASS_CHANGE 20 +#define MENU_TEAM_CHANGE 21 + +#define MENU_REFRESH_RATE 25 + +#define MENU_VOICETWEAK 50 + +//============================ +// Timer Types +#define TF_TIMER_ANY 0 +#define TF_TIMER_CONCUSSION 1 +#define TF_TIMER_INFECTION 2 +#define TF_TIMER_HALLUCINATION 3 +#define TF_TIMER_TRANQUILISATION 4 +#define TF_TIMER_ROTHEALTH 5 +#define TF_TIMER_REGENERATION 6 +#define TF_TIMER_GRENPRIME 7 +#define TF_TIMER_CELLREGENERATION 8 +#define TF_TIMER_DETPACKSET 9 +#define TF_TIMER_DETPACKDISARM 10 +#define TF_TIMER_BUILD 11 +#define TF_TIMER_CHECKBUILDDISTANCE 12 +#define TF_TIMER_DISGUISE 13 +#define TF_TIMER_DISPENSERREFILL 14 + +// Non Player timers +#define TF_TIMER_RETURNITEM 100 +#define TF_TIMER_DELAYEDGOAL 101 +#define TF_TIMER_ENDROUND 102 + +//============================ +// Teamscore printing +#define TS_PRINT_SHORT 1 +#define TS_PRINT_LONG 2 +#define TS_PRINT_LONG_TO_ALL 3 + +#ifndef TF_DEFS_ONLY + +typedef struct +{ + int topColor; + int bottomColor; +} team_color_t; + + +/*==================================================*/ +/* GLOBAL VARIABLES */ +/*==================================================*/ +// FortressMap stuff +extern float number_of_teams; // number of teams supported by the map +extern int illegalclasses[5]; // Illegal playerclasses for all teams +extern int civilianteams; // Bitfield holding Civilian teams +extern Vector rgbcolors[5]; // RGB colors for each of the 4 teams + +extern team_color_t teamcolors[5][PC_LASTCLASS]; // Colors for each of the 4 teams + +extern int teamscores[5]; // Goal Score of each team +extern int g_iOrderedTeams[5]; // Teams ordered into order of winners->losers +extern int teamfrags[5]; // Total Frags for each team +extern int teamlives[5]; // Number of lives each team's players have +extern int teammaxplayers[5]; // Max number of players allowed in each team +extern float teamadvantage[5]; // only used if the teamplay equalisation bits are set + // stores the damage ratio players take/give +extern int teamallies[5]; // Keeps track of which teams are allied +extern string_t team_names[5]; + +extern BOOL CTF_Map; +extern BOOL birthday; +extern BOOL christmas; + +extern float num_world_flames; + +// Clan Battle stuff +extern float clan_scores_dumped; +extern float cb_prematch_time; +extern float fOldPrematch; +extern float fOldCeaseFire; +extern float cb_ceasefire_time; +extern float last_id; +extern float spy_off; +extern float old_grens; +extern float flagem_checked; +extern float flNextEqualisationCalc; +extern BOOL cease_fire; +extern BOOL no_cease_fire_text; +extern BOOL initial_cease_fire; +extern BOOL last_cease_fire; +// Autokick stuff +extern float autokick_kills; + +extern float deathmsg; // Global, which is set before every T_Damage, to indicate + // the death message that should be used. + +extern char *sTeamSpawnNames[]; +extern char *sClassNames[]; +extern char *sNewClassModelFiles[]; +extern char *sOldClassModelFiles[]; +extern char *sClassModels[]; +extern char *sClassCfgs[]; +extern char *sGrenadeNames[]; +extern string_t team_menu_string; + +extern int toggleflags; // toggleable flags + +extern CBaseEntity* g_pLastSpawns[5]; +extern BOOL g_bFirstClient; + +extern float g_fNextPrematchAlert; + +typedef struct +{ + int ip; + edict_t *pEdict; +} ip_storage_t; + +extern ip_storage_t g_IpStorage[32]; + +class CGhost; +/*==========================================================================*/ +BOOL ClassIsRestricted(float tno, int pc); +char* GetTeamName(int tno); +int TeamFortress_GetNoPlayers(); +void DestroyBuilding(CBaseEntity *eng, char *bld); +void teamsprint( int tno, CBaseEntity *ignore, int msg_dest, const char *st, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL ); +float anglemod( float v ); + +// Team Funcs +BOOL TeamFortress_TeamIsCivilian(float tno); +void TeamFortress_TeamShowScores(BOOL bLong, CBasePlayer *pPlayer); +BOOL TeamFortress_TeamPutPlayerInTeam(); +void TeamFortress_TeamSetColor(int tno); +void TeamFortress_TeamIncreaseScore(int tno, int scoretoadd); +int TeamFortress_TeamGetScoreFrags(int tno); +int TeamFortress_TeamGetNoPlayers(int tno); +float TeamEqualiseDamage(CBaseEntity *targ, CBaseEntity *attacker, float damage); +BOOL IsSpawnPointValid( Vector &pos ); +BOOL TeamFortress_SortTeams( void ); +void DumpClanScores( void ); +void CalculateTeamEqualiser(); + +// mapscript funcs +void ParseTFServerSettings(); +void ParseTFMapSettings(); +CBaseEntity* Finditem(int ino); +CBaseEntity* Findgoal(int gno); +CBaseEntity* Findteamspawn(int gno); +void RemoveGoal(CBaseEntity *Goal); +void tfgoalitem_GiveToPlayer(CBaseEntity *Item, CBasePlayer *AP, CBaseEntity *Goal); +void dremove( CBaseEntity *te ); +void tfgoalitem_RemoveFromPlayer(CBaseEntity *Item, CBasePlayer *AP, int iMethod); +void tfgoalitem_drop(CBaseEntity *Item, BOOL PAlive, CBasePlayer *P); +void DisplayItemStatus(CBaseEntity *Goal, CBasePlayer *Player, CBaseEntity *Item); +void tfgoalitem_checkgoalreturn(CBaseEntity *Item); +void DoGoalWork(CBaseEntity *Goal, CBasePlayer *AP); +void DoResults(CBaseEntity *Goal, CBasePlayer *AP, BOOL bAddBonuses); +void DoGroupWork(CBaseEntity *Goal, CBasePlayer *AP); +// hooks into the mapscript for all entities +BOOL ActivateDoResults(CBaseEntity *Goal, CBasePlayer *AP, CBaseEntity *ActivatingGoal); +BOOL ActivationSucceeded(CBaseEntity *Goal, CBasePlayer *AP, CBaseEntity *ActivatingGoal); + +// prematch & ceasefire +void Display_Prematch(); +void Check_Ceasefire(); + +// admin +void KickPlayer( CBaseEntity *pTarget ); +void BanPlayer( CBaseEntity *pTarget ); +CGhost *FindGhost( int iGhostID ); +int GetBattleID( edict_t *pEntity ); + +extern cvar_t tfc_spam_penalty1;// the initial gag penalty for a spammer (seconds) +extern cvar_t tfc_spam_penalty2;// incremental gag penalty (seconds) for each time gagged spammer continues to speak. +extern cvar_t tfc_spam_limit; // at this many points, gag the spammer +extern cvar_t tfc_clanbattle, tfc_clanbattle_prematch, tfc_prematch, tfc_clanbattle_ceasefire, tfc_balance_teams, tfc_balance_scores; +extern cvar_t tfc_clanbattle_locked, tfc_birthday, tfc_autokick_kills, tfc_fragscoring, tfc_autokick_time, tfc_adminpwd; +extern cvar_t weaponstay, footsteps, flashlight, aimcrosshair, falldamage, teamplay; +extern cvar_t allow_spectators; + +/*==========================================================================*/ +class CTFFlame : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT FlameThink( void ); + static CTFFlame *FlameSpawn( CBaseEntity *pOwner, CBaseEntity *pTarget ); + void FlameDestroy( void ); + + float m_flNextDamageTime; +}; + +/*==========================================================================*/ +// MAPSCRIPT CLASSES +class CTFGoal : public CBaseAnimating +{ +public: + void Spawn( void ); + void StartGoal( void ); + void EXPORT PlaceGoal( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int Classify ( void ) { return CLASS_TFGOAL; } + + void SetObjectCollisionBox( void ); +}; + +class CTFGoalItem : public CTFGoal +{ +public: + void Spawn( void ); + void StartItem( void ); + void EXPORT PlaceItem( void ); + int Classify ( void ) { return CLASS_TFGOAL_ITEM; } + + float m_flDroppedAt; +}; + +class CTFTimerGoal : public CTFGoal +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_TFGOAL_TIMER; } +}; + +class CTFSpawn : public CBaseEntity +{ +public: + void Spawn( void ); + void Activate( void ); + int Classify ( void ) { return CLASS_TFSPAWN; } + BOOL CheckTeam( int iTeamNo ); + + EHANDLE m_pTeamCheck; +}; + +class CTFDetect : public CBaseEntity +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_TFGOAL; } +}; + +class CTelefragDeath : public CBaseEntity +{ +public: + void Spawn( void ); + void EXPORT DeathTouch( CBaseEntity *pOther ); +}; + +class CTeamCheck : public CBaseDelay +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + BOOL TeamMatches( int iTeam ); +}; + +class CTeamSet : public CBaseDelay +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; + +#endif // TF_DEFS_ONLY +#endif // __TF_DEFS_H + + diff --git a/cl_dll/train.cpp b/cl_dll/train.cpp new file mode 100644 index 0000000..0286c68 --- /dev/null +++ b/cl_dll/train.cpp @@ -0,0 +1,85 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Train.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE(m_Train, Train ) + + +int CHudTrain::Init(void) +{ + HOOK_MESSAGE( Train ); + + m_iPos = 0; + m_iFlags = 0; + gHUD.AddHudElem(this); + + return 1; +}; + +int CHudTrain::VidInit(void) +{ + m_hSprite = 0; + + return 1; +}; + +int CHudTrain::Draw(float fTime) +{ + if ( !m_hSprite ) + m_hSprite = LoadSprite("sprites/%d_train.spr"); + + if (m_iPos) + { + int r, g, b, x, y; + + UnpackRGB(r,g,b, RGB_YELLOWISH); + SPR_Set(m_hSprite, r, g, b ); + + // This should show up to the right and part way up the armor number + y = ScreenHeight - SPR_Height(m_hSprite,0) - gHUD.m_iFontHeight; + x = ScreenWidth/3 + SPR_Width(m_hSprite,0)/4; + + SPR_DrawAdditive( m_iPos - 1, x, y, NULL); + + } + + return 1; +} + + +int CHudTrain::MsgFunc_Train(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + // update Train data + m_iPos = READ_BYTE(); + + if (m_iPos) + m_iFlags |= HUD_ACTIVE; + else + m_iFlags &= ~HUD_ACTIVE; + + return 1; +} diff --git a/cl_dll/tri.cpp b/cl_dll/tri.cpp new file mode 100644 index 0000000..5332eb8 --- /dev/null +++ b/cl_dll/tri.cpp @@ -0,0 +1,60 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// Triangle rendering, if any + +#include "hud.h" +#include "cl_util.h" + +// Triangle rendering apis are in gEngfuncs.pTriAPI + +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "triangleapi.h" +#include "Exports.h" + +#include "particleman.h" +#include "tri.h" +extern IParticleMan *g_pParticleMan; + +/* +================= +HUD_DrawNormalTriangles + +Non-transparent triangles-- add them here +================= +*/ +void CL_DLLEXPORT HUD_DrawNormalTriangles( void ) +{ +// RecClDrawNormalTriangles(); + + gHUD.m_Spectator.DrawOverview(); +} + +#if defined( _TFC ) +void RunEventList( void ); +#endif + +/* +================= +HUD_DrawTransparentTriangles + +Render any triangles with transparent rendermode needs here +================= +*/ +void CL_DLLEXPORT HUD_DrawTransparentTriangles( void ) +{ +// RecClDrawTransparentTriangles(); + +#if defined( _TFC ) + RunEventList(); +#endif + + if ( g_pParticleMan ) + g_pParticleMan->Update(); +} diff --git a/cl_dll/tri.h b/cl_dll/tri.h new file mode 100644 index 0000000..3977cd1 --- /dev/null +++ b/cl_dll/tri.h @@ -0,0 +1,13 @@ +#ifndef TRI_H +#define TRI_H + +#include "particleman.h" + +extern IParticleMan *g_pParticleMan; + + + + + + +#endif //TRI_H \ No newline at end of file diff --git a/cl_dll/util.cpp b/cl_dll/util.cpp new file mode 100644 index 0000000..68566eb --- /dev/null +++ b/cl_dll/util.cpp @@ -0,0 +1,133 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// util.cpp +// +// implementation of class-less helper functions +// + +#include "stdio.h" +#include "stdlib.h" +#include "math.h" + +#include "hud.h" +#include "cl_util.h" +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +vec3_t vec3_origin( 0, 0, 0 ); + +double sqrt(double x); + +float Length(const float *v) +{ + int i; + float length; + + length = 0; + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); // FIXME + + return length; +} + +void VectorAngles( const float *forward, float *angles ) +{ + float tmp, yaw, pitch; + + if (forward[1] == 0 && forward[0] == 0) + { + yaw = 0; + if (forward[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + yaw = (atan2(forward[1], forward[0]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + + tmp = sqrt (forward[0]*forward[0] + forward[1]*forward[1]); + pitch = (atan2(forward[2], tmp) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + angles[0] = pitch; + angles[1] = yaw; + angles[2] = 0; +} + +float VectorNormalize (float *v) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; + +} + +void VectorInverse ( float *v ) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void VectorScale (const float *in, float scale, float *out) +{ + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; +} + +void VectorMA (const float *veca, float scale, const float *vecb, float *vecc) +{ + vecc[0] = veca[0] + scale*vecb[0]; + vecc[1] = veca[1] + scale*vecb[1]; + vecc[2] = veca[2] + scale*vecb[2]; +} + +HSPRITE LoadSprite(const char *pszName) +{ + int i; + char sz[256]; + + if (ScreenWidth < 640) + i = 320; + else + i = 640; + + sprintf(sz, pszName, i); + + return SPR_Load(sz); +} + diff --git a/cl_dll/util_vector.h b/cl_dll/util_vector.h new file mode 100644 index 0000000..2a63537 --- /dev/null +++ b/cl_dll/util_vector.h @@ -0,0 +1,125 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Vector.h +// A subset of the extdll.h in the project HL Entity DLL +// + +// Misc C-runtime library headers +#include "stdio.h" +#include "stdlib.h" +#include "math.h" + +// Header file containing definition of globalvars_t and entvars_t +typedef unsigned int func_t; // +typedef unsigned int string_t; // from engine's pr_comp.h; +typedef float vec_t; // needed before including progdefs.h + +//========================================================= +// 2DVector - used for many pathfinding and many other +// operations that are treated as planar rather than 3d. +//========================================================= +class Vector2D +{ +public: + inline Vector2D(void) { } + inline Vector2D(float X, float Y) { x = X; y = Y; } + inline Vector2D operator+(const Vector2D& v) const { return Vector2D(x+v.x, y+v.y); } + inline Vector2D operator-(const Vector2D& v) const { return Vector2D(x-v.x, y-v.y); } + inline Vector2D operator*(float fl) const { return Vector2D(x*fl, y*fl); } + inline Vector2D operator/(float fl) const { return Vector2D(x/fl, y/fl); } + + inline float Length(void) const { return (float)sqrt(x*x + y*y ); } + + inline Vector2D Normalize ( void ) const + { + Vector2D vec2; + + float flLen = Length(); + if ( flLen == 0 ) + { + return Vector2D( (float)0, (float)0 ); + } + else + { + flLen = 1 / flLen; + return Vector2D( x * flLen, y * flLen ); + } + } + + vec_t x, y; +}; + +#undef DotProduct +inline float DotProduct(const Vector2D& a, const Vector2D& b) { return( a.x*b.x + a.y*b.y ); } +inline Vector2D operator*(float fl, const Vector2D& v) { return v * fl; } + +//========================================================= +// 3D Vector +//========================================================= +class Vector // same data-layout as engine's vec3_t, +{ // which is a vec_t[3] +public: + // Construction/destruction + inline Vector(void) { } + inline Vector(float X, float Y, float Z) { x = X; y = Y; z = Z; } + inline Vector(double X, double Y, double Z) { x = (float)X; y = (float)Y; z = (float)Z; } + inline Vector(int X, int Y, int Z) { x = (float)X; y = (float)Y; z = (float)Z; } + inline Vector(const Vector& v) { x = v.x; y = v.y; z = v.z; } + inline Vector(float rgfl[3]) { x = rgfl[0]; y = rgfl[1]; z = rgfl[2]; } + + // Operators + inline Vector operator-(void) const { return Vector(-x,-y,-z); } + inline int operator==(const Vector& v) const { return x==v.x && y==v.y && z==v.z; } + inline int operator!=(const Vector& v) const { return !(*this==v); } + inline Vector operator+(const Vector& v) const { return Vector(x+v.x, y+v.y, z+v.z); } + inline Vector operator-(const Vector& v) const { return Vector(x-v.x, y-v.y, z-v.z); } + inline Vector operator*(float fl) const { return Vector(x*fl, y*fl, z*fl); } + inline Vector operator/(float fl) const { return Vector(x/fl, y/fl, z/fl); } + + // Methods + inline void CopyToArray(float* rgfl) const { rgfl[0] = x, rgfl[1] = y, rgfl[2] = z; } + inline float Length(void) const { return (float)sqrt(x*x + y*y + z*z); } + operator float *() { return &x; } // Vectors will now automatically convert to float * when needed + operator const float *() const { return &x; } // Vectors will now automatically convert to float * when needed + inline Vector Normalize(void) const + { + float flLen = Length(); + if (flLen == 0) return Vector(0,0,1); // ???? + flLen = 1 / flLen; + return Vector(x * flLen, y * flLen, z * flLen); + } + + inline Vector2D Make2D ( void ) const + { + Vector2D Vec2; + + Vec2.x = x; + Vec2.y = y; + + return Vec2; + } + inline float Length2D(void) const { return (float)sqrt(x*x + y*y); } + + // Members + vec_t x, y, z; +}; +inline Vector operator*(float fl, const Vector& v) { return v * fl; } +inline float DotProduct(const Vector& a, const Vector& b) { return(a.x*b.x+a.y*b.y+a.z*b.z); } +inline Vector CrossProduct(const Vector& a, const Vector& b) { return Vector( a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x ); } + +#ifndef DID_VEC3_T_DEFINE +#define DID_VEC3_T_DEFINE +#define vec3_t Vector +#endif diff --git a/cl_dll/vgui_ClassMenu.cpp b/cl_dll/vgui_ClassMenu.cpp new file mode 100644 index 0000000..e91d72c --- /dev/null +++ b/cl_dll/vgui_ClassMenu.cpp @@ -0,0 +1,437 @@ +//=========== (C) Copyright 1996-2002 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: TFC Class Menu +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "VGUI_Font.h" +#include + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "parsemsg.h" + +#include "vgui_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" + +// Class Menu Dimensions +#define CLASSMENU_TITLE_X XRES(40) +#define CLASSMENU_TITLE_Y YRES(32) +#define CLASSMENU_TOPLEFT_BUTTON_X XRES(40) +#define CLASSMENU_TOPLEFT_BUTTON_Y YRES(80) +#define CLASSMENU_BUTTON_SIZE_X XRES(124) +#define CLASSMENU_BUTTON_SIZE_Y YRES(24) +#define CLASSMENU_BUTTON_SPACER_Y YRES(8) +#define CLASSMENU_WINDOW_X XRES(176) +#define CLASSMENU_WINDOW_Y YRES(80) +#define CLASSMENU_WINDOW_SIZE_X XRES(424) +#define CLASSMENU_WINDOW_SIZE_Y YRES(312) +#define CLASSMENU_WINDOW_TEXT_X XRES(150) +#define CLASSMENU_WINDOW_TEXT_Y YRES(80) +#define CLASSMENU_WINDOW_NAME_X XRES(150) +#define CLASSMENU_WINDOW_NAME_Y YRES(8) +#define CLASSMENU_WINDOW_PLAYERS_Y YRES(42) + +// Creation +CClassMenuPanel::CClassMenuPanel(int iTrans, int iRemoveMe, int x,int y,int wide,int tall) : CMenuPanel(iTrans, iRemoveMe, x,y,wide,tall) +{ + // don't show class graphics at below 640x480 resolution + bool bShowClassGraphic = true; + if ( ScreenWidth < 640 ) + { + bShowClassGraphic = false; + } + + memset( m_pClassImages, 0, sizeof(m_pClassImages) ); + + // Get the scheme used for the Titles + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + + // schemes + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle( "Title Font" ); + SchemeHandle_t hClassWindowText = pSchemes->getSchemeHandle( "Briefing Text" ); + + // color schemes + int r, g, b, a; + + // Create the title + Label *pLabel = new Label( "", CLASSMENU_TITLE_X, CLASSMENU_TITLE_Y ); + pLabel->setParent( this ); + pLabel->setFont( pSchemes->getFont(hTitleScheme) ); + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + pLabel->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + pLabel->setBgColor( r, g, b, a ); + pLabel->setContentAlignment( vgui::Label::a_west ); + pLabel->setText(gHUD.m_TextMessage.BufferedLocaliseTextString("#Title_SelectYourClass")); + + // Create the Scroll panel + m_pScrollPanel = new CTFScrollPanel( CLASSMENU_WINDOW_X, CLASSMENU_WINDOW_Y, CLASSMENU_WINDOW_SIZE_X, CLASSMENU_WINDOW_SIZE_Y ); + m_pScrollPanel->setParent(this); + //force the scrollbars on, so after the validate clientClip will be smaller + m_pScrollPanel->setScrollBarAutoVisible(false, false); + m_pScrollPanel->setScrollBarVisible(true, true); + m_pScrollPanel->setBorder( new LineBorder( Color(255 * 0.7,170 * 0.7,0,0) ) ); + m_pScrollPanel->validate(); + + int clientWide=m_pScrollPanel->getClient()->getWide(); + + //turn scrollpanel back into auto show scrollbar mode and validate + m_pScrollPanel->setScrollBarAutoVisible(false,true); + m_pScrollPanel->setScrollBarVisible(false,false); + m_pScrollPanel->validate(); + + // Create the Class buttons +#ifdef _TFC + for (int i = 0; i <= PC_RANDOM; i++) + { + char sz[256]; + int iYPos = CLASSMENU_TOPLEFT_BUTTON_Y + ( (CLASSMENU_BUTTON_SIZE_Y + CLASSMENU_BUTTON_SPACER_Y) * i ); + + ActionSignal *pASignal = new CMenuHandler_StringCommandClassSelect( sTFClassSelection[i], true ); + + // Class button + sprintf(sz, "%s", CHudTextMessage::BufferedLocaliseTextString( sLocalisedClasses[i] ) ); + m_pButtons[i] = new ClassButton( i, sz, CLASSMENU_TOPLEFT_BUTTON_X, iYPos, CLASSMENU_BUTTON_SIZE_X, CLASSMENU_BUTTON_SIZE_Y, true); + // RandomPC uses '0' + if ( i >= 1 && i <= 9 ) + { + sprintf(sz,"%d",i); + } + else + { + sprintf(sz,"0"); + } + m_pButtons[i]->setBoundKey( sz[0] ); + m_pButtons[i]->setContentAlignment( vgui::Label::a_west ); + m_pButtons[i]->addActionSignal( pASignal ); + m_pButtons[i]->addInputSignal( new CHandler_MenuButtonOver(this, i) ); + m_pButtons[i]->setParent( this ); + + // Create the Class Info Window + //m_pClassInfoPanel[i] = new CTransparentPanel( 255, CLASSMENU_WINDOW_X, CLASSMENU_WINDOW_Y, CLASSMENU_WINDOW_SIZE_X, CLASSMENU_WINDOW_SIZE_Y ); + m_pClassInfoPanel[i] = new CTransparentPanel( 255, 0, 0, clientWide, CLASSMENU_WINDOW_SIZE_Y ); + m_pClassInfoPanel[i]->setParent( m_pScrollPanel->getClient() ); + //m_pClassInfoPanel[i]->setVisible( false ); + + // don't show class pic in lower resolutions + int textOffs = XRES(8); + + if ( bShowClassGraphic ) + { + textOffs = CLASSMENU_WINDOW_NAME_X; + } + + // Create the Class Name Label + sprintf(sz, "#Title_%s", sTFClassSelection[i]); + char* localName=CHudTextMessage::BufferedLocaliseTextString( sz ); + Label *pNameLabel = new Label( "", textOffs, CLASSMENU_WINDOW_NAME_Y ); + pNameLabel->setFont( pSchemes->getFont(hTitleScheme) ); + pNameLabel->setParent( m_pClassInfoPanel[i] ); + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + pNameLabel->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + pNameLabel->setBgColor( r, g, b, a ); + pNameLabel->setContentAlignment( vgui::Label::a_west ); + //pNameLabel->setBorder(new LineBorder()); + pNameLabel->setText( "%s", localName); + + // Create the Class Image + if ( bShowClassGraphic ) + { + for ( int team = 0; team < 2; team++ ) + { + if ( team == 1 ) + { + sprintf( sz, "%sred", sTFClassSelection[i] ); + } + else + { + sprintf( sz, "%sblue", sTFClassSelection[i] ); + } + + m_pClassImages[team][i] = new CImageLabel( sz, 0, 0, CLASSMENU_WINDOW_TEXT_X, CLASSMENU_WINDOW_TEXT_Y ); + + CImageLabel *pLabel = m_pClassImages[team][i]; + pLabel->setParent( m_pClassInfoPanel[i] ); + //pLabel->setBorder(new LineBorder()); + + if ( team != 1 ) + { + pLabel->setVisible( false ); + } + + // Reposition it based upon it's size + int xOut, yOut; + pNameLabel->getTextSize( xOut, yOut ); + pLabel->setPos( (CLASSMENU_WINDOW_TEXT_X - pLabel->getWide()) / 2, yOut /2 ); + } + } + + // Create the Player count string + gHUD.m_TextMessage.LocaliseTextString( "#Title_CurrentlyOnYourTeam", m_sPlayersOnTeamString, STRLENMAX_PLAYERSONTEAM ); + m_pPlayers[i] = new Label( "", textOffs, CLASSMENU_WINDOW_PLAYERS_Y ); + m_pPlayers[i]->setParent( m_pClassInfoPanel[i] ); + m_pPlayers[i]->setBgColor( 0, 0, 0, 255 ); + m_pPlayers[i]->setContentAlignment( vgui::Label::a_west ); + m_pPlayers[i]->setFont( pSchemes->getFont(hClassWindowText) ); + + // Open up the Class Briefing File + sprintf(sz, "classes/short_%s.txt", sTFClassSelection[i]); + char *cText = "Class Description not available."; + char *pfile = (char *)gEngfuncs.COM_LoadFile( sz, 5, NULL ); + if (pfile) + { + cText = pfile; + } + + // Create the Text info window + TextPanel *pTextWindow = new TextPanel(cText, textOffs, CLASSMENU_WINDOW_TEXT_Y, (CLASSMENU_WINDOW_SIZE_X - textOffs)-5, CLASSMENU_WINDOW_SIZE_Y - CLASSMENU_WINDOW_TEXT_Y); + pTextWindow->setParent( m_pClassInfoPanel[i] ); + pTextWindow->setFont( pSchemes->getFont(hClassWindowText) ); + pSchemes->getFgColor( hClassWindowText, r, g, b, a ); + pTextWindow->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hClassWindowText, r, g, b, a ); + pTextWindow->setBgColor( r, g, b, a ); + + // Resize the Info panel to fit it all + int wide,tall; + pTextWindow->getTextImage()->getTextSizeWrapped( wide,tall); + pTextWindow->setSize(wide,tall); + + int xx,yy; + pTextWindow->getPos(xx,yy); + int maxX=xx+wide; + int maxY=yy+tall; + + //check to see if the image goes lower than the text + //just use the red teams [0] images + if(m_pClassImages[0][i]!=null) + { + m_pClassImages[0][i]->getPos(xx,yy); + if((yy+m_pClassImages[0][i]->getTall())>maxY) + { + maxY=yy+m_pClassImages[0][i]->getTall(); + } + } + + m_pClassInfoPanel[i]->setSize( maxX , maxY ); + if (pfile) gEngfuncs.COM_FreeFile( pfile ); + //m_pClassInfoPanel[i]->setBorder(new LineBorder()); + + } +#endif + // Create the Cancel button + m_pCancelButton = new CommandButton( gHUD.m_TextMessage.BufferedLocaliseTextString( "#Menu_Cancel" ), CLASSMENU_TOPLEFT_BUTTON_X, 0, CLASSMENU_BUTTON_SIZE_X, CLASSMENU_BUTTON_SIZE_Y); + m_pCancelButton->setParent( this ); + m_pCancelButton->addActionSignal( new CMenuHandler_TextWindow(HIDE_TEXTWINDOW) ); + + m_iCurrentInfo = 0; + +} + + +// Update +void CClassMenuPanel::Update() +{ + // Don't allow the player to join a team if they're not in a team + if (!g_iTeamNumber) + return; + + int iYPos = CLASSMENU_TOPLEFT_BUTTON_Y; + + // Cycle through the rest of the buttons +#ifdef _TFC + for (int i = 0; i <= PC_RANDOM; i++) + { + bool bCivilian = (gViewPort->GetValidClasses(g_iTeamNumber) == -1); + + if ( bCivilian ) + { + // If this team can only be civilians, only the civilian button's visible + if (i == 0) + { + m_pButtons[0]->setVisible( true ); + SetActiveInfo( 0 ); + iYPos += CLASSMENU_BUTTON_SIZE_Y + CLASSMENU_BUTTON_SPACER_Y; + } + else + { + m_pButtons[i]->setVisible( false ); + } + } + else + { + if ( m_pButtons[i]->IsNotValid() || i == 0 ) + { + m_pButtons[i]->setVisible( false ); + } + else + { + m_pButtons[i]->setVisible( true ); + m_pButtons[i]->setPos( CLASSMENU_TOPLEFT_BUTTON_X, iYPos ); + iYPos += CLASSMENU_BUTTON_SIZE_Y + CLASSMENU_BUTTON_SPACER_Y; + + // Start with the first option up + if (!m_iCurrentInfo) + SetActiveInfo( i ); + } + } + + // Now count the number of teammembers of this class + int iTotal = 0; + for ( int j = 1; j < MAX_PLAYERS; j++ ) + { + if ( g_PlayerInfoList[j].name == NULL ) + continue; // empty player slot, skip + if ( g_PlayerExtraInfo[j].teamname[0] == 0 ) + continue; // skip over players who are not in a team + if ( g_PlayerInfoList[j].thisplayer ) + continue; // skip this player + if ( g_PlayerExtraInfo[j].teamnumber != g_iTeamNumber ) + continue; // skip over players in other teams + + // If this team is forced to be civilians, just count the number of teammates + if ( g_PlayerExtraInfo[j].playerclass != i && !bCivilian ) + continue; + + iTotal++; + } + + char sz[256]; + sprintf(sz, m_sPlayersOnTeamString, iTotal); + m_pPlayers[i]->setText( "%s", sz ); + + // Set the text color to the teamcolor + m_pPlayers[i]->setFgColor( iTeamColors[g_iTeamNumber % iNumberOfTeamColors][0], + iTeamColors[g_iTeamNumber % iNumberOfTeamColors][1], + iTeamColors[g_iTeamNumber % iNumberOfTeamColors][2], + 0 ); + + // set the graphic to be the team pick + for ( int team = 0; team < MAX_TEAMS; team++ ) + { + // unset all the other images + if ( m_pClassImages[team][i] ) + { + m_pClassImages[team][i]->setVisible( false ); + } + + // set the current team image + if ( m_pClassImages[g_iTeamNumber-1][i] != NULL ) + { + m_pClassImages[g_iTeamNumber-1][i]->setVisible( true ); + } + else if ( m_pClassImages[0][i] ) + { + m_pClassImages[0][i]->setVisible( true ); + } + } + } +#endif + + // If the player already has a class, make the cancel button visible + if ( g_iPlayerClass ) + { + m_pCancelButton->setPos( CLASSMENU_TOPLEFT_BUTTON_X, iYPos ); + m_pCancelButton->setVisible( true ); + } + else + { + m_pCancelButton->setVisible( false ); + } +} + +//====================================== +// Key inputs for the Class Menu +bool CClassMenuPanel::SlotInput( int iSlot ) +{ + if ( (iSlot < 0) || (iSlot > 9) ) + return false; + if ( !m_pButtons[ iSlot ] ) + return false; + + // Is the button pushable? (0 is special case) + if (iSlot == 0) + { + // Selects Civilian and RandomPC + if ( gViewPort->GetValidClasses(g_iTeamNumber) == -1 ) + { + m_pButtons[ 0 ]->fireActionSignal(); + return true; + } + + // Select RandomPC + iSlot = 10; + } + + if ( !(m_pButtons[ iSlot ]->IsNotValid()) ) + { + m_pButtons[ iSlot ]->fireActionSignal(); + return true; + } + + return false; +} + +//====================================== +// Update the Class menu before opening it +void CClassMenuPanel::Open( void ) +{ + Update(); + CMenuPanel::Open(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called each time a new level is started. +//----------------------------------------------------------------------------- +void CClassMenuPanel::Initialize( void ) +{ + setVisible( false ); + m_pScrollPanel->setScrollValue( 0, 0 ); +} + +//====================================== +// Mouse is over a class button, bring up the class info +void CClassMenuPanel::SetActiveInfo( int iInput ) +{ + // Remove all the Info panels and bring up the specified one +#ifdef _TFC + for (int i = 0; i <= PC_RANDOM; i++) + { + m_pButtons[i]->setArmed( false ); + m_pClassInfoPanel[i]->setVisible( false ); + } + + if ( iInput > PC_RANDOM || iInput < 0 ) +#endif + iInput = 0; + + m_pButtons[iInput]->setArmed( true ); + m_pClassInfoPanel[iInput]->setVisible( true ); + m_iCurrentInfo = iInput; + + m_pScrollPanel->setScrollValue(0,0); + m_pScrollPanel->validate(); +} + diff --git a/cl_dll/vgui_ConsolePanel.cpp b/cl_dll/vgui_ConsolePanel.cpp new file mode 100644 index 0000000..39ad9c5 --- /dev/null +++ b/cl_dll/vgui_ConsolePanel.cpp @@ -0,0 +1,101 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include"vgui_ConsolePanel.h" +#include"hud.h" +#include +#include +#include +#include +#include + +using namespace vgui; + + +namespace +{ + +class Handler : public ActionSignal +{ +private: + + ConsolePanel* _consolePanel; + +public: + + Handler(ConsolePanel* consolePanel) + { + _consolePanel=consolePanel; + } + +public: + + virtual void actionPerformed(Panel* panel) + { + _consolePanel->doExecCommand(); + } + +}; + +} + + + +ConsolePanel::ConsolePanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) +{ + setBorder(new EtchedBorder()); + + _textGrid=new TextGrid(80,21,5,5,200,100); + _textGrid->setBorder(new LoweredBorder()); + _textGrid->setParent(this); + + _textEntry=new TextEntry("",5,5,200,20); + _textEntry->setParent(this); + _textEntry->addActionSignal(new Handler(this)); +} + +int ConsolePanel::print(const char* text) +{ + return _textGrid->printf("%s",text); +} + +int ConsolePanel::vprintf(const char* format,va_list argList) +{ + return _textGrid->vprintf(format,argList); +} + +int ConsolePanel::printf(const char* format,...) +{ + va_list argList; + va_start(argList,format); + int ret=vprintf(format,argList); + va_end(argList); + return ret; +} + +void ConsolePanel::doExecCommand() +{ + char buf[2048]; + _textEntry->getText(0,buf,2048); + _textEntry->setText(null,0); + gEngfuncs.pfnClientCmd(buf); +} + +void ConsolePanel::setSize(int wide,int tall) +{ + Panel::setSize(wide,tall); + + getPaintSize(wide,tall); + + _textGrid->setBounds(5,5,wide-10,tall-35); + _textEntry->setBounds(5,tall-25,wide-10,20); +} + + + + + diff --git a/cl_dll/vgui_ConsolePanel.h b/cl_dll/vgui_ConsolePanel.h new file mode 100644 index 0000000..ec6672e --- /dev/null +++ b/cl_dll/vgui_ConsolePanel.h @@ -0,0 +1,38 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef CONSOLEPANEL_H +#define CONSOLEPANEL_H + +#include +#include + +namespace vgui +{ +class TextGrid; +class TextEntry; +} + + +class ConsolePanel : public vgui::Panel +{ +private: + vgui::TextGrid* _textGrid; + vgui::TextEntry* _textEntry; +public: + ConsolePanel(int x,int y,int wide,int tall); +public: + virtual void setSize(int wide,int tall); + virtual int print(const char* text); + virtual int vprintf(const char* format,va_list argList); + virtual int printf(const char* format,...); + virtual void doExecCommand(); +}; + + + +#endif \ No newline at end of file diff --git a/cl_dll/vgui_ControlConfigPanel.cpp b/cl_dll/vgui_ControlConfigPanel.cpp new file mode 100644 index 0000000..37c60d1 --- /dev/null +++ b/cl_dll/vgui_ControlConfigPanel.cpp @@ -0,0 +1,212 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include"vgui_ControlConfigPanel.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace vgui; + +namespace +{ +class FooTablePanel : public TablePanel +{ +private: + Label* _label; + TextEntry* _textEntry; + ControlConfigPanel* _controlConfigPanel; +public: + FooTablePanel(ControlConfigPanel* controlConfigPanel,int x,int y,int wide,int tall,int columnCount) : TablePanel(x,y,wide,tall,columnCount) + { + _controlConfigPanel=controlConfigPanel; + _label=new Label("You are a dumb monkey",0,0,100,20); + _label->setBgColor(Scheme::sc_primary3); + _label->setFgColor(Scheme::sc_primary1); + _label->setFont(Scheme::sf_primary3); + + _textEntry=new TextEntry("",0,0,100,20); + //_textEntry->setFont(Scheme::sf_primary3); + } +public: + virtual int getRowCount() + { + return _controlConfigPanel->GetCVarCount(); + } + virtual int getCellTall(int row) + { + return 12; + } + virtual Panel* getCellRenderer(int column,int row,bool columnSelected,bool rowSelected,bool cellSelected) + { + char cvar[128],desc[128],bind[128],bindAlt[128]; + _controlConfigPanel->GetCVar(row,cvar,128,desc,128); + + if(cellSelected) + { + _label->setBgColor(Scheme::sc_primary1); + _label->setFgColor(Scheme::sc_primary3); + } + else + if(rowSelected) + { + _label->setBgColor(Scheme::sc_primary2); + _label->setFgColor(Scheme::sc_primary1); + } + else + { + _label->setBgColor(Scheme::sc_primary3); + _label->setFgColor(Scheme::sc_primary1); + } + + switch(column) + { + case 0: + { + _label->setText(desc); + _label->setContentAlignment(Label::a_west); + break; + } + case 1: + { + _controlConfigPanel->GetCVarBind(cvar,bind,128,bindAlt,128); + _label->setText(bind); + _label->setContentAlignment(Label::a_center); + break; + } + case 2: + { + _controlConfigPanel->GetCVarBind(cvar,bind,128,bindAlt,128); + _label->setText(bindAlt); + _label->setContentAlignment(Label::a_center); + break; + } + default: + { + _label->setText(""); + break; + } + } + + return _label; + } + virtual Panel* startCellEditing(int column,int row) + { + _textEntry->setText("Goat",strlen("Goat")); + _textEntry->requestFocus(); + return _textEntry; + } +}; +} + +ControlConfigPanel::ControlConfigPanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) +{ + setPaintBorderEnabled(false); + setPaintBackgroundEnabled(false); + setPaintEnabled(false); + + _actionLabel=new Label("Action"); + _actionLabel->setBgColor(Scheme::sc_primary3); + _actionLabel->setFgColor(Scheme::sc_primary3); + + _keyButtonLabel=new Label("Key / Button"); + _keyButtonLabel->setBgColor(Scheme::sc_primary3); + _keyButtonLabel->setFgColor(Scheme::sc_primary3); + + _alternateLabel=new Label("Alternate"); + _alternateLabel->setBgColor(Scheme::sc_primary3); + _alternateLabel->setFgColor(Scheme::sc_primary3); + + _headerPanel=new HeaderPanel(0,0,wide,20); + _headerPanel->setParent(this); + + _headerPanel->addSectionPanel(_actionLabel); + _headerPanel->addSectionPanel(_keyButtonLabel); + _headerPanel->addSectionPanel(_alternateLabel); + + _headerPanel->setSliderPos( 0, wide/2 ); + _headerPanel->setSliderPos( 1, (wide/2) + (wide/4) ); + _headerPanel->setSliderPos( 2, wide ); + + _scrollPanel=new ScrollPanel(0,20,wide,tall-20); + _scrollPanel->setParent(this); + _scrollPanel->setPaintBorderEnabled(false); + _scrollPanel->setPaintBackgroundEnabled(false); + _scrollPanel->setPaintEnabled(false); + _scrollPanel->getClient()->setPaintBorderEnabled(false); + _scrollPanel->getClient()->setPaintBackgroundEnabled(false); + _scrollPanel->getClient()->setPaintEnabled(false); + _scrollPanel->setScrollBarVisible(false,true); + + _tablePanel=new FooTablePanel(this,0,0,_scrollPanel->getClient()->getWide(),800, 3); + _tablePanel->setParent(_scrollPanel->getClient()); + _tablePanel->setHeaderPanel(_headerPanel); + _tablePanel->setBgColor(Color(200,0,0,255)); + _tablePanel->setFgColor(Color(Scheme::sc_primary2)); + _tablePanel->setGridVisible(true,true); + _tablePanel->setGridSize(1,1); +} + +void ControlConfigPanel::AddCVar(const char* cvar,const char* desc) +{ + _cvarDar.addElement(vgui_strdup(cvar)); + _descDar.addElement(vgui_strdup(desc)); +} + +int ControlConfigPanel::GetCVarCount() +{ + return _cvarDar.getCount(); +} + +void ControlConfigPanel::GetCVar(int index,char* cvar,int cvarLen,char* desc,int descLen) +{ + vgui_strcpy(cvar,cvarLen,_cvarDar[index]); + vgui_strcpy(desc,descLen,_descDar[index]); +} + +void ControlConfigPanel::AddCVarFromInputStream(InputStream* is) +{ + if(is==null) + { + return; + } + + DataInputStream dis(is); + + bool success; + + while(1) + { + char buf[256],cvar[128],desc[128]; + dis.readLine(buf,256,success); + if(!success) + { + break; + } + if(sscanf(buf,"\"%[^\"]\" \"%[^\"]\"",cvar,desc)==2) + { + AddCVar(cvar,desc); + } + } +} + +void ControlConfigPanel::GetCVarBind(const char* cvar,char* bind,int bindLen,char* bindAlt,int bindAltLen) +{ + sprintf(bind,"%s : Bind",cvar); + sprintf(bindAlt,"%s : BindAlt",cvar); +} + +void ControlConfigPanel::SetCVarBind(const char* cvar,const char* bind,const char* bindAlt) +{ +} + diff --git a/cl_dll/vgui_ControlConfigPanel.h b/cl_dll/vgui_ControlConfigPanel.h new file mode 100644 index 0000000..4c413a0 --- /dev/null +++ b/cl_dll/vgui_ControlConfigPanel.h @@ -0,0 +1,47 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef CONTROLCONFIGPANEL_H +#define CONTROLCONFIGPANEL_H + +#include +#include + +namespace vgui +{ +class HeaderPanel; +class TablePanel; +class ScrollPanel; +class InputStream; +class Label; +} + +class ControlConfigPanel : public vgui::Panel +{ +private: + vgui::HeaderPanel* _headerPanel; + vgui::TablePanel* _tablePanel; + vgui::ScrollPanel* _scrollPanel; + vgui::Dar _cvarDar; + vgui::Dar _descDar; + vgui::Label* _actionLabel; + vgui::Label* _keyButtonLabel; + vgui::Label* _alternateLabel; +public: + ControlConfigPanel(int x,int y,int wide,int tall); +public: + void AddCVar(const char* cvar,const char* desc); + void AddCVarFromInputStream(vgui::InputStream* is); + int GetCVarCount(); + void GetCVar(int index,char* cvar,int cvarLen,char* desc,int descLen); + void GetCVarBind(const char* cvar,char* bind,int bindLen,char* bindAlt,int bindAltLen); + void SetCVarBind(const char* cvar,const char* bind,const char* bindAlt); +}; + + + +#endif \ No newline at end of file diff --git a/cl_dll/vgui_CustomObjects.cpp b/cl_dll/vgui_CustomObjects.cpp new file mode 100644 index 0000000..03d4026 --- /dev/null +++ b/cl_dll/vgui_CustomObjects.cpp @@ -0,0 +1,547 @@ +//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: Contains implementation of various VGUI-derived objects +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "VGUI_Font.h" + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "parsemsg.h" + +#include "vgui_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" +#include "vgui_loadtga.h" + +// Arrow filenames +char *sArrowFilenames[] = +{ + "arrowup", + "arrowdn", + "arrowlt", + "arrowrt", +}; + +// Get the name of TGA file, without a gamedir +char *GetTGANameForRes(const char *pszName) +{ + int i; + char sz[256]; + static char gd[256]; + if (ScreenWidth < 640) + i = 320; + else + i = 640; + sprintf(sz, pszName, i); + sprintf(gd, "gfx/vgui/%s.tga", sz); + return gd; +} + +//----------------------------------------------------------------------------- +// Purpose: Loads a .tga file and returns a pointer to the VGUI tga object +//----------------------------------------------------------------------------- +BitmapTGA *LoadTGAForRes( const char* pImageName ) +{ + BitmapTGA *pTGA; + + char sz[256]; + sprintf(sz, "%%d_%s", pImageName); + pTGA = vgui_LoadTGA(GetTGANameForRes(sz)); + + return pTGA; +} + +//=========================================================== +// All TFC Hud buttons are derived from this one. +CommandButton::CommandButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight) : Button("",x,y,wide,tall) +{ + m_iPlayerClass = 0; + m_bNoHighlight = bNoHighlight; + m_bFlat = false; + Init(); + setText( text ); +} + +CommandButton::CommandButton( int iPlayerClass, const char* text,int x,int y,int wide,int tall, bool bFlat) : Button("",x,y,wide,tall) +{ + m_iPlayerClass = iPlayerClass; + m_bNoHighlight = false; + m_bFlat = bFlat; + Init(); + setText( text ); +} + +CommandButton::CommandButton(const char *text, int x, int y, int wide, int tall, bool bNoHighlight, bool bFlat) : Button("",x,y,wide,tall) +{ + m_iPlayerClass = 0; + m_bFlat = bFlat; + m_bNoHighlight = bNoHighlight; + Init(); + setText( text ); +} + +void CommandButton::Init( void ) +{ + m_pSubMenu = NULL; + m_pSubLabel = NULL; + m_pParentMenu = NULL; + + // Set text color to orange + setFgColor(Scheme::sc_primary1); + + // left align + setContentAlignment( vgui::Label::a_west ); + + // Add the Highlight signal + if (!m_bNoHighlight) + addInputSignal( new CHandler_CommandButtonHighlight(this) ); + + // not bound to any button yet + m_cBoundKey = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Prepends the button text with the current bound key +// if no bound key, then a clear space ' ' instead +//----------------------------------------------------------------------------- +void CommandButton::RecalculateText( void ) +{ + char szBuf[128]; + + if ( m_cBoundKey != 0 ) + { + if ( m_cBoundKey == (char)255 ) + { + strcpy( szBuf, m_sMainText ); + } + else + { + sprintf( szBuf, " %c %s", m_cBoundKey, m_sMainText ); + } + szBuf[MAX_BUTTON_SIZE-1] = 0; + } + else + { + // just draw a space if no key bound + sprintf( szBuf, " %s", m_sMainText ); + szBuf[MAX_BUTTON_SIZE-1] = 0; + } + + Button::setText( szBuf ); +} + +void CommandButton::setText( const char *text ) +{ + strncpy( m_sMainText, text, MAX_BUTTON_SIZE ); + m_sMainText[MAX_BUTTON_SIZE-1] = 0; + + RecalculateText(); +} + +void CommandButton::setBoundKey( char boundKey ) +{ + m_cBoundKey = boundKey; + RecalculateText(); +} + +char CommandButton::getBoundKey( void ) +{ + return m_cBoundKey; +} + +void CommandButton::AddSubMenu( CCommandMenu *pNewMenu ) +{ + m_pSubMenu = pNewMenu; + + // Prevent this button from being pushed + setMouseClickEnabled( MOUSE_LEFT, false ); +} + +void CommandButton::UpdateSubMenus( int iAdjustment ) +{ + if ( m_pSubMenu ) + m_pSubMenu->RecalculatePositions( iAdjustment ); +} + +void CommandButton::paint() +{ + // Make the sub label paint the same as the button + if ( m_pSubLabel ) + { + if ( isSelected() ) + m_pSubLabel->PushDown(); + else + m_pSubLabel->PushUp(); + } + + // draw armed button text in white + if ( isArmed() ) + { + setFgColor( Scheme::sc_secondary2 ); + } + else + { + setFgColor( Scheme::sc_primary1 ); + } + + Button::paint(); +} + +void CommandButton::paintBackground() +{ + if ( m_bFlat ) + { + if ( isArmed() ) + { + // Orange Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect(0,0,_size[0],_size[1]); + } + } + else + { + if ( isArmed() ) + { + // Orange highlight background + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect(0,0,_size[0],_size[1]); + } + + // Orange Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect(0,0,_size[0],_size[1]); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Highlights the current button, and all it's parent menus +//----------------------------------------------------------------------------- +void CommandButton::cursorEntered( void ) +{ + // unarm all the other buttons in this menu + CCommandMenu *containingMenu = getParentMenu(); + if ( containingMenu ) + { + containingMenu->ClearButtonsOfArmedState(); + + // make all our higher buttons armed + CCommandMenu *pCParent = containingMenu->GetParentMenu(); + if ( pCParent ) + { + CommandButton *pParentButton = pCParent->FindButtonWithSubmenu( containingMenu ); + + pParentButton->cursorEntered(); + } + } + + // arm ourselves + setArmed( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CommandButton::cursorExited( void ) +{ + // only clear ourselves if we have do not have a containing menu + // only stay armed if we have a sub menu + // the buttons only unarm themselves when another button is armed instead + if ( !getParentMenu() || !GetSubMenu() ) + { + setArmed( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the command menu that the button is part of, if any +// Output : CCommandMenu * +//----------------------------------------------------------------------------- +CCommandMenu *CommandButton::getParentMenu( void ) +{ + return m_pParentMenu; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the menu that contains this button +// Input : *pParentMenu - +//----------------------------------------------------------------------------- +void CommandButton::setParentMenu( CCommandMenu *pParentMenu ) +{ + m_pParentMenu = pParentMenu; +} + + +//=========================================================== +int ClassButton::IsNotValid() +{ + // If this is the main ChangeClass button, remove it if the player's only able to be civilians + if ( m_iPlayerClass == -1 ) + { + if (gViewPort->GetValidClasses(g_iTeamNumber) == -1) + return true; + + return false; + } + + // Is it an illegal class? +#ifdef _TFC + if ((gViewPort->GetValidClasses(0) & sTFValidClassInts[ m_iPlayerClass ]) || (gViewPort->GetValidClasses(g_iTeamNumber) & sTFValidClassInts[ m_iPlayerClass ])) + return true; +#endif + + // Only check current class if they've got autokill on + bool bAutoKill = CVAR_GET_FLOAT( "hud_classautokill" ) != 0; + if ( bAutoKill ) + { + // Is it the player's current class? + if ( +#ifdef _TFC + (gViewPort->IsRandomPC() && m_iPlayerClass == PC_RANDOM) || +#endif + (!gViewPort->IsRandomPC() && (m_iPlayerClass == g_iPlayerClass)) ) + return true; + } + + return false; +} + +//=========================================================== +// Button with Class image beneath it +CImageLabel::CImageLabel( const char* pImageName,int x,int y ) : Label( "", x,y ) +{ + setContentFitted(true); + m_pTGA = LoadTGAForRes(pImageName); + setImage( m_pTGA ); +} + +CImageLabel::CImageLabel( const char* pImageName,int x,int y,int wide,int tall ) : Label( "", x,y,wide,tall ) +{ + setContentFitted(true); + m_pTGA = LoadTGAForRes(pImageName); + setImage( m_pTGA ); +} + +//=========================================================== +// Image size +int CImageLabel::getImageWide( void ) +{ + if( m_pTGA ) + { + int iXSize, iYSize; + m_pTGA->getSize( iXSize, iYSize ); + return iXSize; + } + else + { + return 1; + } +} + +int CImageLabel::getImageTall( void ) +{ + if( m_pTGA ) + { + int iXSize, iYSize; + m_pTGA->getSize( iXSize, iYSize ); + return iYSize; + } + else + { + return 1; + } +} + +void CImageLabel::LoadImage(const char * pImageName) +{ + if ( m_pTGA ) + delete m_pTGA; + + // Load the Image + m_pTGA = LoadTGAForRes(pImageName); + + if ( m_pTGA == NULL ) + { + // we didn't find a matching image file for this resolution + // try to load file resolution independent + + char sz[256]; + sprintf(sz, "%s/%s",gEngfuncs.pfnGetGameDirectory(), pImageName ); + FileInputStream* fis = new FileInputStream( sz, false ); + m_pTGA = new BitmapTGA(fis,true); + fis->close(); + } + + if ( m_pTGA == NULL ) + return; // unable to load image + + int w,t; + + m_pTGA->getSize( w, t ); + + setSize( XRES (w),YRES (t) ); + setImage( m_pTGA ); +} + +//=========================================================== +// Various overloaded paint functions for Custom VGUI objects +void CCommandMenu::paintBackground() +{ + // Transparent black background + + if ( m_iSpectCmdMenu ) + drawSetColor( 0, 0, 0, 64 ); + else + drawSetColor(Scheme::sc_primary3); + + drawFilledRect(0,0,_size[0],_size[1]); +} + +//================================================================================= +// CUSTOM SCROLLPANEL +//================================================================================= +CTFScrollButton::CTFScrollButton(int iArrow, const char* text,int x,int y,int wide,int tall) : CommandButton(text,x,y,wide,tall) +{ + // Set text color to orange + setFgColor(Scheme::sc_primary1); + + // Load in the arrow + m_pTGA = LoadTGAForRes( sArrowFilenames[iArrow] ); + setImage( m_pTGA ); + + // Highlight signal + InputSignal *pISignal = new CHandler_CommandButtonHighlight(this); + addInputSignal(pISignal); +} + +void CTFScrollButton::paint( void ) +{ + if (!m_pTGA) + return; + + // draw armed button text in white + if ( isArmed() ) + { + m_pTGA->setColor( Color(255,255,255, 0) ); + } + else + { + m_pTGA->setColor( Color(255,255,255, 128) ); + } + + m_pTGA->doPaint(this); +} + +void CTFScrollButton::paintBackground( void ) +{ +/* + if ( isArmed() ) + { + // Orange highlight background + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect(0,0,_size[0],_size[1]); + } + + // Orange Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect(0,0,_size[0]-1,_size[1]); +*/ +} + +void CTFSlider::paintBackground( void ) +{ + int wide,tall,nobx,noby; + getPaintSize(wide,tall); + getNobPos(nobx,noby); + + // Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect( 0,0,wide,tall ); + + if( isVertical() ) + { + // Nob Fill + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect( 0,nobx,wide,noby ); + + // Nob Outline + drawSetColor( Scheme::sc_primary1 ); + drawOutlinedRect( 0,nobx,wide,noby ); + } + else + { + // Nob Fill + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect( nobx,0,noby,tall ); + + // Nob Outline + drawSetColor( Scheme::sc_primary1 ); + drawOutlinedRect( nobx,0,noby,tall ); + } +} + +CTFScrollPanel::CTFScrollPanel(int x,int y,int wide,int tall) : ScrollPanel(x,y,wide,tall) +{ + ScrollBar *pScrollBar = getVerticalScrollBar(); + pScrollBar->setButton( new CTFScrollButton( ARROW_UP, "", 0,0,16,16 ), 0 ); + pScrollBar->setButton( new CTFScrollButton( ARROW_DOWN, "", 0,0,16,16 ), 1 ); + pScrollBar->setSlider( new CTFSlider(0,wide-1,wide,(tall-(wide*2))+2,true) ); + pScrollBar->setPaintBorderEnabled(false); + pScrollBar->setPaintBackgroundEnabled(false); + pScrollBar->setPaintEnabled(false); + + pScrollBar = getHorizontalScrollBar(); + pScrollBar->setButton( new CTFScrollButton( ARROW_LEFT, "", 0,0,16,16 ), 0 ); + pScrollBar->setButton( new CTFScrollButton( ARROW_RIGHT, "", 0,0,16,16 ), 1 ); + pScrollBar->setSlider( new CTFSlider(tall,0,wide-(tall*2),tall,false) ); + pScrollBar->setPaintBorderEnabled(false); + pScrollBar->setPaintBackgroundEnabled(false); + pScrollBar->setPaintEnabled(false); +} + + +//================================================================================= +// CUSTOM HANDLERS +//================================================================================= +void CHandler_MenuButtonOver::cursorEntered(Panel *panel) +{ + if ( gViewPort && m_pMenuPanel ) + { + m_pMenuPanel->SetActiveInfo( m_iButton ); + } +} + +void CMenuHandler_StringCommandClassSelect::actionPerformed(Panel* panel) +{ + CMenuHandler_StringCommand::actionPerformed( panel ); + + // THIS IS NOW BEING DONE ON THE TFC SERVER TO AVOID KILLING SOMEONE THEN + // HAVE THE SERVER SAY "SORRY...YOU CAN'T BE THAT CLASS". + +#if !defined _TFC + bool bAutoKill = CVAR_GET_FLOAT( "hud_classautokill" ) != 0; + if ( bAutoKill && g_iPlayerClass != 0 ) + gEngfuncs.pfnClientCmd("kill"); +#endif +} + diff --git a/cl_dll/vgui_MOTDWindow.cpp b/cl_dll/vgui_MOTDWindow.cpp new file mode 100644 index 0000000..b71145d --- /dev/null +++ b/cl_dll/vgui_MOTDWindow.cpp @@ -0,0 +1,154 @@ +//=========== (C) Copyright 1996-2002 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "VGUI_Font.h" +#include "VGUI_ScrollPanel.h" +#include "VGUI_TextImage.h" + +#include + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "const.h" + +#include "vgui_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" + +#define MOTD_TITLE_X XRES(16) +#define MOTD_TITLE_Y YRES(16) + +#define MOTD_WINDOW_X XRES(112) +#define MOTD_WINDOW_Y YRES(80) +#define MOTD_WINDOW_SIZE_X XRES(424) +#define MOTD_WINDOW_SIZE_Y YRES(312) + +//----------------------------------------------------------------------------- +// Purpose: Displays the MOTD and basic server information +//----------------------------------------------------------------------------- +class CMessageWindowPanel : public CMenuPanel +{ +public: + CMessageWindowPanel( const char *szMOTD, const char *szTitle, int iShadeFullScreen, int iRemoveMe, int x, int y, int wide, int tall ); + +private: + CTransparentPanel *m_pBackgroundPanel; + +}; + +//----------------------------------------------------------------------------- +// Purpose: Creates a new CMessageWindowPanel +// Output : CMenuPanel - interface to the panel +//----------------------------------------------------------------------------- +CMenuPanel *CMessageWindowPanel_Create( const char *szMOTD, const char *szTitle, int iShadeFullscreen, int iRemoveMe, int x, int y, int wide, int tall ) +{ + return new CMessageWindowPanel( szMOTD, szTitle, iShadeFullscreen, iRemoveMe, x, y, wide, tall ); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructs a message panel +//----------------------------------------------------------------------------- +CMessageWindowPanel::CMessageWindowPanel( const char *szMOTD, const char *szTitle, int iShadeFullscreen, int iRemoveMe, int x, int y, int wide, int tall ) : CMenuPanel( iShadeFullscreen ? 100 : 255, iRemoveMe, x, y, wide, tall ) +{ + // Get the scheme used for the Titles + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + + // schemes + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle( "Title Font" ); + SchemeHandle_t hMOTDText = pSchemes->getSchemeHandle( "Briefing Text" ); + + // color schemes + int r, g, b, a; + + // Create the window + m_pBackgroundPanel = new CTransparentPanel( iShadeFullscreen ? 255 : 100, MOTD_WINDOW_X, MOTD_WINDOW_Y, MOTD_WINDOW_SIZE_X, MOTD_WINDOW_SIZE_Y ); + m_pBackgroundPanel->setParent( this ); + m_pBackgroundPanel->setBorder( new LineBorder( Color(255 * 0.7,170 * 0.7,0,0)) ); + m_pBackgroundPanel->setVisible( true ); + + int iXSize,iYSize,iXPos,iYPos; + m_pBackgroundPanel->getPos( iXPos,iYPos ); + m_pBackgroundPanel->getSize( iXSize,iYSize ); + + // Create the title + Label *pLabel = new Label( "", iXPos + MOTD_TITLE_X, iYPos + MOTD_TITLE_Y ); + pLabel->setParent( this ); + pLabel->setFont( pSchemes->getFont(hTitleScheme) ); + pLabel->setFont( Scheme::sf_primary1 ); + + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + pLabel->setFgColor( r, g, b, a ); + pLabel->setFgColor( Scheme::sc_primary1 ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + pLabel->setBgColor( r, g, b, a ); + pLabel->setContentAlignment( vgui::Label::a_west ); + pLabel->setText( "%s", szTitle); + + // Create the Scroll panel + ScrollPanel *pScrollPanel = new CTFScrollPanel( iXPos + XRES(16), iYPos + MOTD_TITLE_Y*2 + YRES(16), iXSize - XRES(32), iYSize - (YRES(48) + BUTTON_SIZE_Y*2) ); + pScrollPanel->setParent(this); + + //force the scrollbars on so clientClip will take them in account after the validate + pScrollPanel->setScrollBarAutoVisible(false, false); + pScrollPanel->setScrollBarVisible(true, true); + pScrollPanel->validate(); + + // Create the text panel + TextPanel *pText = new TextPanel( "", 0,0, 64,64); + pText->setParent( pScrollPanel->getClient() ); + + // get the font and colors from the scheme + pText->setFont( pSchemes->getFont(hMOTDText) ); + pSchemes->getFgColor( hMOTDText, r, g, b, a ); + pText->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hMOTDText, r, g, b, a ); + pText->setBgColor( r, g, b, a ); + pText->setText( szMOTD); + + // Get the total size of the MOTD text and resize the text panel + int iScrollSizeX, iScrollSizeY; + + // First, set the size so that the client's wdith is correct at least because the + // width is critical for getting the "wrapped" size right. + // You'll see a horizontal scroll bar if there is a single word that won't wrap in the + // specified width. + pText->getTextImage()->setSize(pScrollPanel->getClientClip()->getWide(), pScrollPanel->getClientClip()->getTall()); + pText->getTextImage()->getTextSizeWrapped( iScrollSizeX, iScrollSizeY ); + + // Now resize the textpanel to fit the scrolled size + pText->setSize( iScrollSizeX , iScrollSizeY ); + + //turn the scrollbars back into automode + pScrollPanel->setScrollBarAutoVisible(true, true); + pScrollPanel->setScrollBarVisible(false, false); + + pScrollPanel->validate(); + + CommandButton *pButton = new CommandButton( CHudTextMessage::BufferedLocaliseTextString( "#Menu_OK" ), iXPos + XRES(16), iYPos + iYSize - YRES(16) - BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_TextWindow(HIDE_TEXTWINDOW)); + pButton->setParent(this); + +} + + + + + + diff --git a/cl_dll/vgui_SchemeManager.cpp b/cl_dll/vgui_SchemeManager.cpp new file mode 100644 index 0000000..dddd268 --- /dev/null +++ b/cl_dll/vgui_SchemeManager.cpp @@ -0,0 +1,556 @@ +//=========== (C) Copyright 1996-2002 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "hud.h" +#include "vgui_SchemeManager.h" +#include "cvardef.h" + +#include + + +cvar_t *g_CV_BitmapFonts; + + +void Scheme_Init() +{ + g_CV_BitmapFonts = gEngfuncs.pfnRegisterVariable("bitmapfonts", "1", 0); +} + + + +//----------------------------------------------------------------------------- +// Purpose: Scheme managers data container +//----------------------------------------------------------------------------- +class CSchemeManager::CScheme +{ +public: + enum { + SCHEME_NAME_LENGTH = 32, + FONT_NAME_LENGTH = 48, + FONT_FILENAME_LENGTH = 64, + }; + + // name + char schemeName[SCHEME_NAME_LENGTH]; + + // font + char fontName[FONT_NAME_LENGTH]; + + int fontSize; + int fontWeight; + + vgui::Font *font; + int ownFontPointer; // true if the font is ours to delete + + // scheme + byte fgColor[4]; + byte bgColor[4]; + byte armedFgColor[4]; + byte armedBgColor[4]; + byte mousedownFgColor[4]; + byte mousedownBgColor[4]; + byte borderColor[4]; + + // construction/destruction + CScheme(); + ~CScheme(); +}; + +CSchemeManager::CScheme::CScheme() +{ + schemeName[0] = 0; + fontName[0] = 0; + fontSize = 0; + fontWeight = 0; + font = NULL; + ownFontPointer = false; +} + +CSchemeManager::CScheme::~CScheme() +{ + // only delete our font pointer if we own it + if ( ownFontPointer ) + { + delete font; + } +} + +//----------------------------------------------------------------------------- +// Purpose: resolution information +// !! needs to be shared out +//----------------------------------------------------------------------------- +static int g_ResArray[] = +{ + 320, + 400, + 512, + 640, + 800, + 1024, + 1152, + 1280, + 1600 +}; +static int g_NumReses = sizeof(g_ResArray) / sizeof(int); + +static byte *LoadFileByResolution( const char *filePrefix, int xRes, const char *filePostfix ) +{ + // find our resolution in the res array + int resNum = g_NumReses - 1; + while ( g_ResArray[resNum] > xRes ) + { + resNum--; + + if ( resNum < 0 ) + return NULL; + } + + // try open the file + byte *pFile = NULL; + while ( 1 ) + { + + // try load + char fname[256]; + sprintf( fname, "%s%d%s", filePrefix, g_ResArray[resNum], filePostfix ); + pFile = gEngfuncs.COM_LoadFile( fname, 5, NULL ); + + if ( pFile ) + break; + + if ( resNum == 0 ) + return NULL; + + resNum--; + }; + + return pFile; +} + +static void ParseRGBAFromString( byte colorArray[4], const char *colorVector ) +{ + int r, g, b, a; + sscanf( colorVector, "%d %d %d %d", &r, &g, &b, &a ); + colorArray[0] = r; + colorArray[1] = g; + colorArray[2] = b; + colorArray[3] = a; +} + +//----------------------------------------------------------------------------- +// Purpose: initializes the scheme manager +// loading the scheme files for the current resolution +// Input : xRes - +// yRes - dimensions of output window +//----------------------------------------------------------------------------- +CSchemeManager::CSchemeManager( int xRes, int yRes ) +{ + // basic setup + m_pSchemeList = NULL; + m_iNumSchemes = 0; + + // find the closest matching scheme file to our resolution + char token[1024]; + char *pFile = (char*)LoadFileByResolution( "", xRes, "_textscheme.txt" ); + m_xRes = xRes; + + char *pFileStart = pFile; + + byte *pFontData; + int fontFileLength; + char fontFilename[512]; + + // + // Read the scheme descriptions from the text file, into a temporary array + // format is simply: + // = + // + // a of "SchemeName" signals a new scheme is being described + // + + const static int numTmpSchemes = 64; + static CScheme tmpSchemes[numTmpSchemes]; + memset( tmpSchemes, 0, sizeof(tmpSchemes) ); + int currentScheme = -1; + CScheme *pScheme = NULL; + + if ( !pFile ) + { + gEngfuncs.Con_DPrintf( "Unable to find *_textscheme.txt\n"); + goto buildDefaultFont; + } + + // record what has been entered so we can create defaults from the different values + bool hasFgColor, hasBgColor, hasArmedFgColor, hasArmedBgColor, hasMouseDownFgColor, hasMouseDownBgColor; + + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + while ( strlen(token) > 0 && (currentScheme < numTmpSchemes) ) + { + // get the paramName name + static const int tokenSize = 64; + char paramName[tokenSize], paramValue[tokenSize]; + + strncpy( paramName, token, tokenSize ); + paramName[tokenSize-1] = 0; // ensure null termination + + // get the '=' character + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + if ( stricmp( token, "=" ) ) + { + if ( currentScheme < 0 ) + { + gEngfuncs.Con_Printf( "error parsing font scheme text file at file start - expected '=', found '%s''\n", token ); + } + else + { + gEngfuncs.Con_Printf( "error parsing font scheme text file at scheme '%s' - expected '=', found '%s''\n", tmpSchemes[currentScheme].schemeName, token ); + } + break; + } + + // get paramValue + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + strncpy( paramValue, token, tokenSize ); + paramValue[tokenSize-1] = 0; // ensure null termination + + // is this a new scheme? + if ( !stricmp(paramName, "SchemeName") ) + { + // setup the defaults for the current scheme + if ( pScheme ) + { + // foreground color defaults (normal -> armed -> mouse down) + if ( !hasFgColor ) + { + pScheme->fgColor[0] = pScheme->fgColor[1] = pScheme->fgColor[2] = pScheme->fgColor[3] = 255; + } + if ( !hasArmedFgColor ) + { + memcpy( pScheme->armedFgColor, pScheme->fgColor, sizeof(pScheme->armedFgColor) ); + } + if ( !hasMouseDownFgColor ) + { + memcpy( pScheme->mousedownFgColor, pScheme->armedFgColor, sizeof(pScheme->mousedownFgColor) ); + } + + // background color (normal -> armed -> mouse down) + if ( !hasBgColor ) + { + pScheme->bgColor[0] = pScheme->bgColor[1] = pScheme->bgColor[2] = pScheme->bgColor[3] = 0; + } + if ( !hasArmedBgColor ) + { + memcpy( pScheme->armedBgColor, pScheme->bgColor, sizeof(pScheme->armedBgColor) ); + } + if ( !hasMouseDownBgColor ) + { + memcpy( pScheme->mousedownBgColor, pScheme->armedBgColor, sizeof(pScheme->mousedownBgColor) ); + } + + // font size + if ( !pScheme->fontSize ) + { + pScheme->fontSize = 17; + } + if ( !pScheme->fontName[0] ) + { + strcpy( pScheme->fontName, "Arial" ); + } + } + + // create the new scheme + currentScheme++; + pScheme = &tmpSchemes[currentScheme]; + hasFgColor = hasBgColor = hasArmedFgColor = hasArmedBgColor = hasMouseDownFgColor = hasMouseDownBgColor = false; + + strncpy( pScheme->schemeName, paramValue, CScheme::SCHEME_NAME_LENGTH ); + pScheme->schemeName[CScheme::SCHEME_NAME_LENGTH-1] = '\0'; // ensure null termination of string + } + + if ( !pScheme ) + { + gEngfuncs.Con_Printf( "font scheme text file MUST start with a 'SchemeName'\n"); + break; + } + + // pull the data out into the scheme + if ( !stricmp(paramName, "FontName") ) + { + strncpy( pScheme->fontName, paramValue, CScheme::FONT_NAME_LENGTH ); + pScheme->fontName[CScheme::FONT_NAME_LENGTH-1] = 0; + } + else if ( !stricmp(paramName, "FontSize") ) + { + pScheme->fontSize = atoi( paramValue ); + } + else if ( !stricmp(paramName, "FontWeight") ) + { + pScheme->fontWeight = atoi( paramValue ); + } + else if ( !stricmp(paramName, "FgColor") ) + { + ParseRGBAFromString( pScheme->fgColor, paramValue ); + hasFgColor = true; + } + else if ( !stricmp(paramName, "BgColor") ) + { + ParseRGBAFromString( pScheme->bgColor, paramValue ); + hasBgColor = true; + } + else if ( !stricmp(paramName, "FgColorArmed") ) + { + ParseRGBAFromString( pScheme->armedFgColor, paramValue ); + hasArmedFgColor = true; + } + else if ( !stricmp(paramName, "BgColorArmed") ) + { + ParseRGBAFromString( pScheme->armedBgColor, paramValue ); + hasArmedBgColor = true; + } + else if ( !stricmp(paramName, "FgColorMousedown") ) + { + ParseRGBAFromString( pScheme->mousedownFgColor, paramValue ); + hasMouseDownFgColor = true; + } + else if ( !stricmp(paramName, "BgColorMousedown") ) + { + ParseRGBAFromString( pScheme->mousedownBgColor, paramValue ); + hasMouseDownBgColor = true; + } + else if ( !stricmp(paramName, "BorderColor") ) + { + ParseRGBAFromString( pScheme->borderColor, paramValue ); + hasMouseDownBgColor = true; + } + + // get the new token last, so we now if the loop needs to be continued or not + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + } + + // free the file + gEngfuncs.COM_FreeFile( pFileStart ); + + +buildDefaultFont: + + // make sure we have at least 1 valid font + if ( currentScheme < 0 ) + { + currentScheme = 0; + strcpy( tmpSchemes[0].schemeName, "Default Scheme" ); + strcpy( tmpSchemes[0].fontName, "Arial" ); + tmpSchemes[0].fontSize = 0; + tmpSchemes[0].fgColor[0] = tmpSchemes[0].fgColor[1] = tmpSchemes[0].fgColor[2] = tmpSchemes[0].fgColor[3] = 255; + tmpSchemes[0].armedFgColor[0] = tmpSchemes[0].armedFgColor[1] = tmpSchemes[0].armedFgColor[2] = tmpSchemes[0].armedFgColor[3] = 255; + tmpSchemes[0].mousedownFgColor[0] = tmpSchemes[0].mousedownFgColor[1] = tmpSchemes[0].mousedownFgColor[2] = tmpSchemes[0].mousedownFgColor[3] = 255; + } + + // we have the full list of schemes in the tmpSchemes array + // now allocate the correct sized list + m_iNumSchemes = currentScheme + 1; // 0-based index + m_pSchemeList = new CScheme[ m_iNumSchemes ]; + + // copy in the data + memcpy( m_pSchemeList, tmpSchemes, sizeof(CScheme) * m_iNumSchemes ); + + // create the fonts + for ( int i = 0; i < m_iNumSchemes; i++ ) + { + m_pSchemeList[i].font = NULL; + + // see if the current font values exist in a previously loaded font + for ( int j = 0; j < i; j++ ) + { + // check if the font name, size, and weight are the same + if ( !stricmp(m_pSchemeList[i].fontName, m_pSchemeList[j].fontName) + && m_pSchemeList[i].fontSize == m_pSchemeList[j].fontSize + && m_pSchemeList[i].fontWeight == m_pSchemeList[j].fontWeight ) + { + // copy the pointer, but mark i as not owning it + m_pSchemeList[i].font = m_pSchemeList[j].font; + m_pSchemeList[i].ownFontPointer = false; + } + } + + // if we haven't found the font already, load it ourselves + if ( !m_pSchemeList[i].font ) + { + fontFileLength = -1; + pFontData = NULL; + + if(g_CV_BitmapFonts && g_CV_BitmapFonts->value) + { + int fontRes = 640; + if ( m_xRes >= 1600 ) + fontRes = 1600; + else if ( m_xRes >= 1280 ) + fontRes = 1280; + else if ( m_xRes >= 1152 ) + fontRes = 1152; + else if ( m_xRes >= 1024 ) + fontRes = 1024; + else if ( m_xRes >= 800 ) + fontRes = 800; + + sprintf(fontFilename, "gfx\\vgui\\fonts\\%d_%s.tga", fontRes, m_pSchemeList[i].schemeName); + pFontData = gEngfuncs.COM_LoadFile( fontFilename, 5, &fontFileLength ); + if(!pFontData) + gEngfuncs.Con_Printf("Missing bitmap font: %s\n", fontFilename); + } + + m_pSchemeList[i].font = new vgui::Font( + m_pSchemeList[i].fontName, + pFontData, + fontFileLength, + m_pSchemeList[i].fontSize, + 0, + 0, + m_pSchemeList[i].fontWeight, + false, + false, + false, + false); + + m_pSchemeList[i].ownFontPointer = true; + } + + // fix up alpha values; VGUI uses 1-A (A=0 being solid, A=255 transparent) + m_pSchemeList[i].fgColor[3] = 255 - m_pSchemeList[i].fgColor[3]; + m_pSchemeList[i].bgColor[3] = 255 - m_pSchemeList[i].bgColor[3]; + m_pSchemeList[i].armedFgColor[3] = 255 - m_pSchemeList[i].armedFgColor[3]; + m_pSchemeList[i].armedBgColor[3] = 255 - m_pSchemeList[i].armedBgColor[3]; + m_pSchemeList[i].mousedownFgColor[3] = 255 - m_pSchemeList[i].mousedownFgColor[3]; + m_pSchemeList[i].mousedownBgColor[3] = 255 - m_pSchemeList[i].mousedownBgColor[3]; + } +} + +//----------------------------------------------------------------------------- +// Purpose: frees all the memory used by the scheme manager +//----------------------------------------------------------------------------- +CSchemeManager::~CSchemeManager() +{ + delete [] m_pSchemeList; + m_iNumSchemes = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Finds a scheme in the list, by name +// Input : char *schemeName - string name of the scheme +// Output : SchemeHandle_t handle to the scheme +//----------------------------------------------------------------------------- +SchemeHandle_t CSchemeManager::getSchemeHandle( const char *schemeName ) +{ + // iterate through the list + for ( int i = 0; i < m_iNumSchemes; i++ ) + { + if ( !stricmp(schemeName, m_pSchemeList[i].schemeName) ) + return i; + } + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: always returns a valid scheme handle +// Input : schemeHandle - +// Output : CScheme +//----------------------------------------------------------------------------- +CSchemeManager::CScheme *CSchemeManager::getSafeScheme( SchemeHandle_t schemeHandle ) +{ + if ( schemeHandle < m_iNumSchemes ) + return m_pSchemeList + schemeHandle; + + return m_pSchemeList; +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns the schemes pointer to a font +// Input : schemeHandle - +// Output : vgui::Font +//----------------------------------------------------------------------------- +vgui::Font *CSchemeManager::getFont( SchemeHandle_t schemeHandle ) +{ + return getSafeScheme( schemeHandle )->font; +} + +void CSchemeManager::getFgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->fgColor[0]; + g = pScheme->fgColor[1]; + b = pScheme->fgColor[2]; + a = pScheme->fgColor[3]; +} + +void CSchemeManager::getBgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->bgColor[0]; + g = pScheme->bgColor[1]; + b = pScheme->bgColor[2]; + a = pScheme->bgColor[3]; +} + +void CSchemeManager::getFgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->armedFgColor[0]; + g = pScheme->armedFgColor[1]; + b = pScheme->armedFgColor[2]; + a = pScheme->armedFgColor[3]; +} + +void CSchemeManager::getBgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->armedBgColor[0]; + g = pScheme->armedBgColor[1]; + b = pScheme->armedBgColor[2]; + a = pScheme->armedBgColor[3]; +} + +void CSchemeManager::getFgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->mousedownFgColor[0]; + g = pScheme->mousedownFgColor[1]; + b = pScheme->mousedownFgColor[2]; + a = pScheme->mousedownFgColor[3]; +} + +void CSchemeManager::getBgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->mousedownBgColor[0]; + g = pScheme->mousedownBgColor[1]; + b = pScheme->mousedownBgColor[2]; + a = pScheme->mousedownBgColor[3]; +} + +void CSchemeManager::getBorderColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->borderColor[0]; + g = pScheme->borderColor[1]; + b = pScheme->borderColor[2]; + a = pScheme->borderColor[3]; +} + + + diff --git a/cl_dll/vgui_SchemeManager.h b/cl_dll/vgui_SchemeManager.h new file mode 100644 index 0000000..a66caec --- /dev/null +++ b/cl_dll/vgui_SchemeManager.h @@ -0,0 +1,54 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include + + +// handle to an individual scheme +typedef int SchemeHandle_t; + + +// Register console variables, etc.. +void Scheme_Init(); + + +//----------------------------------------------------------------------------- +// Purpose: Handles the loading of text scheme description from disk +// supports different font/color/size schemes at different resolutions +//----------------------------------------------------------------------------- +class CSchemeManager +{ +public: + // initialization + CSchemeManager( int xRes, int yRes ); + virtual ~CSchemeManager(); + + // scheme handling + SchemeHandle_t getSchemeHandle( const char *schemeName ); + + // getting info from schemes + vgui::Font *getFont( SchemeHandle_t schemeHandle ); + void getFgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getFgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getFgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBorderColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + +private: + class CScheme; + CScheme *m_pSchemeList; + int m_iNumSchemes; + + // Resolution we were initted at. + int m_xRes; + + CScheme *getSafeScheme( SchemeHandle_t schemeHandle ); +}; + + diff --git a/cl_dll/vgui_ScorePanel.cpp b/cl_dll/vgui_ScorePanel.cpp new file mode 100644 index 0000000..dbe4607 --- /dev/null +++ b/cl_dll/vgui_ScorePanel.cpp @@ -0,0 +1,1151 @@ +//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: VGUI scoreboard +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + + +#include + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ScorePanel.h" +#include "vgui_helpers.h" +#include "vgui_loadtga.h" +#include "voice_status.h" +#include "vgui_SpectatorPanel.h" + +extern hud_player_info_t g_PlayerInfoList[MAX_PLAYERS+1]; // player info from the engine +extern extra_player_info_t g_PlayerExtraInfo[MAX_PLAYERS+1]; // additional player info sent directly to the client dll +team_info_t g_TeamInfo[MAX_TEAMS+1]; +int g_IsSpectator[MAX_PLAYERS+1]; + +int HUD_IsGame( const char *game ); +int EV_TFC_IsAllyTeam( int iTeam1, int iTeam2 ); + +// Scoreboard dimensions +#define SBOARD_TITLE_SIZE_Y YRES(22) + +#define X_BORDER XRES(4) + +// Column sizes +class SBColumnInfo +{ +public: + char *m_pTitle; // If null, ignore, if starts with #, it's localized, otherwise use the string directly. + int m_Width; // Based on 640 width. Scaled to fit other resolutions. + Label::Alignment m_Alignment; +}; + +// grid size is marked out for 640x480 screen + +SBColumnInfo g_ColumnInfo[NUM_COLUMNS] = +{ + {NULL, 24, Label::a_east}, // tracker column + {NULL, 140, Label::a_east}, // name + {NULL, 56, Label::a_east}, // class + {"#SCORE", 40, Label::a_east}, + {"#DEATHS", 46, Label::a_east}, + {"#LATENCY", 46, Label::a_east}, + {"#VOICE", 40, Label::a_east}, + {NULL, 2, Label::a_east}, // blank column to take up the slack +}; + + +#define TEAM_NO 0 +#define TEAM_YES 1 +#define TEAM_SPECTATORS 2 +#define TEAM_BLANK 3 + + +//----------------------------------------------------------------------------- +// ScorePanel::HitTestPanel. +//----------------------------------------------------------------------------- + +void ScorePanel::HitTestPanel::internalMousePressed(MouseCode code) +{ + for(int i=0;i<_inputSignalDar.getCount();i++) + { + _inputSignalDar[i]->mousePressed(code,this); + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: Create the ScoreBoard panel +//----------------------------------------------------------------------------- +ScorePanel::ScorePanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) +{ + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle("Scoreboard Title Text"); + SchemeHandle_t hSmallScheme = pSchemes->getSchemeHandle("Scoreboard Small Text"); + Font *tfont = pSchemes->getFont(hTitleScheme); + Font *smallfont = pSchemes->getFont(hSmallScheme); + + setBgColor(0, 0, 0, 96); + m_pCurrentHighlightLabel = NULL; + m_iHighlightRow = -1; + + //m_pTrackerIcon = vgui_LoadTGANoInvertAlpha("gfx/vgui/640_scoreboardtracker.tga"); + + // Initialize the top title. + m_TitleLabel.setFont(tfont); + m_TitleLabel.setText(""); + m_TitleLabel.setBgColor( 0, 0, 0, 255 ); + m_TitleLabel.setFgColor( Scheme::sc_primary1 ); + m_TitleLabel.setContentAlignment( vgui::Label::a_west ); + + LineBorder *border = new LineBorder(Color(60, 60, 60, 128)); + setBorder(border); + setPaintBorderEnabled(true); + + int xpos = g_ColumnInfo[0].m_Width + 3; + if (ScreenWidth >= 640) + { + // only expand column size for res greater than 640 + xpos = XRES(xpos); + } + m_TitleLabel.setBounds(xpos, 4, wide, SBOARD_TITLE_SIZE_Y); + m_TitleLabel.setContentFitted(false); + m_TitleLabel.setParent(this); + + // Setup the header (labels like "name", "class", etc..). + m_HeaderGrid.SetDimensions(NUM_COLUMNS, 1); + m_HeaderGrid.SetSpacing(0, 0); + + for(int i=0; i < NUM_COLUMNS; i++) + { + if (g_ColumnInfo[i].m_pTitle && g_ColumnInfo[i].m_pTitle[0] == '#') + m_HeaderLabels[i].setText(CHudTextMessage::BufferedLocaliseTextString(g_ColumnInfo[i].m_pTitle)); + else if(g_ColumnInfo[i].m_pTitle) + m_HeaderLabels[i].setText(g_ColumnInfo[i].m_pTitle); + + int xwide = g_ColumnInfo[i].m_Width; + if (ScreenWidth >= 640) + { + xwide = XRES(xwide); + } + else if (ScreenWidth == 400) + { + // hack to make 400x300 resolution scoreboard fit + if (i == 1) + { + // reduces size of player name cell + xwide -= 28; + } + else if (i == 0) + { + // tracker icon cell + xwide -= 8; + } + } + + m_HeaderGrid.SetColumnWidth(i, xwide); + m_HeaderGrid.SetEntry(i, 0, &m_HeaderLabels[i]); + + m_HeaderLabels[i].setBgColor(0,0,0,255); + m_HeaderLabels[i].setFgColor(Scheme::sc_primary1); + m_HeaderLabels[i].setFont(smallfont); + m_HeaderLabels[i].setContentAlignment(g_ColumnInfo[i].m_Alignment); + + int yres = 12; + if (ScreenHeight >= 480) + { + yres = YRES(yres); + } + m_HeaderLabels[i].setSize(50, yres); + } + + // Set the width of the last column to be the remaining space. + int ex, ey, ew, eh; + m_HeaderGrid.GetEntryBox(NUM_COLUMNS - 2, 0, ex, ey, ew, eh); + m_HeaderGrid.SetColumnWidth(NUM_COLUMNS - 1, (wide - X_BORDER) - (ex + ew)); + + m_HeaderGrid.AutoSetRowHeights(); + m_HeaderGrid.setBounds(X_BORDER, SBOARD_TITLE_SIZE_Y, wide - X_BORDER*2, m_HeaderGrid.GetRowHeight(0)); + m_HeaderGrid.setParent(this); + m_HeaderGrid.setBgColor(0,0,0,255); + + + // Now setup the listbox with the actual player data in it. + int headerX, headerY, headerWidth, headerHeight; + m_HeaderGrid.getBounds(headerX, headerY, headerWidth, headerHeight); + m_PlayerList.setBounds(headerX, headerY+headerHeight, headerWidth, tall - headerY - headerHeight - 6); + m_PlayerList.setBgColor(0,0,0,255); + m_PlayerList.setParent(this); + + for(int row=0; row < NUM_ROWS; row++) + { + CGrid *pGridRow = &m_PlayerGrids[row]; + + pGridRow->SetDimensions(NUM_COLUMNS, 1); + + for(int col=0; col < NUM_COLUMNS; col++) + { + m_PlayerEntries[col][row].setContentFitted(false); + m_PlayerEntries[col][row].setRow(row); + m_PlayerEntries[col][row].addInputSignal(this); + pGridRow->SetEntry(col, 0, &m_PlayerEntries[col][row]); + } + + pGridRow->setBgColor(0,0,0,255); + pGridRow->SetSpacing(0, 0); + pGridRow->CopyColumnWidths(&m_HeaderGrid); + pGridRow->AutoSetRowHeights(); + pGridRow->setSize(PanelWidth(pGridRow), pGridRow->CalcDrawHeight()); + pGridRow->RepositionContents(); + + m_PlayerList.AddItem(pGridRow); + } + + + // Add the hit test panel. It is invisible and traps mouse clicks so we can go into squelch mode. + m_HitTestPanel.setBgColor(0,0,0,255); + m_HitTestPanel.setParent(this); + m_HitTestPanel.setBounds(0, 0, wide, tall); + m_HitTestPanel.addInputSignal(this); + + m_pCloseButton = new CommandButton( "x", wide-XRES(12 + 4), YRES(2), XRES( 12 ) , YRES( 12 ) ); + m_pCloseButton->setParent( this ); + m_pCloseButton->addActionSignal( new CMenuHandler_StringCommandWatch( "-showscores", true ) ); + m_pCloseButton->setBgColor(0,0,0,255); + m_pCloseButton->setFgColor( 255, 255, 255, 0 ); + m_pCloseButton->setFont(tfont); + m_pCloseButton->setBoundKey( (char)255 ); + m_pCloseButton->setContentAlignment(Label::a_center); + + + Initialize(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Called each time a new level is started. +//----------------------------------------------------------------------------- +void ScorePanel::Initialize( void ) +{ + // Clear out scoreboard data + m_iLastKilledBy = 0; + m_fLastKillTime = 0; + m_iPlayerNum = 0; + m_iNumTeams = 0; + memset( g_PlayerExtraInfo, 0, sizeof g_PlayerExtraInfo ); + memset( g_TeamInfo, 0, sizeof g_TeamInfo ); +} + +bool HACK_GetPlayerUniqueID( int iPlayer, char playerID[16] ) +{ + return !!gEngfuncs.GetPlayerUniqueID( iPlayer, playerID ); // TODO remove after testing +} + +//----------------------------------------------------------------------------- +// Purpose: Recalculate the internal scoreboard data +//----------------------------------------------------------------------------- +void ScorePanel::Update() +{ + int i; + + // Set the title + if (gViewPort->m_szServerName) + { + char sz[MAX_SERVERNAME_LENGTH + 16]; + sprintf(sz, "%s", gViewPort->m_szServerName ); + m_TitleLabel.setText(sz); + } + + m_iRows = 0; + gViewPort->GetAllPlayersInfo(); + + // Clear out sorts + for (i = 0; i < NUM_ROWS; i++) + { + m_iSortedRows[i] = 0; + m_iIsATeam[i] = TEAM_NO; + } + for (i = 0; i < MAX_PLAYERS; i++) + { + m_bHasBeenSorted[i] = false; + } + + // If it's not teamplay, sort all the players. Otherwise, sort the teams. + if ( !gHUD.m_Teamplay ) + SortPlayers( 0, NULL ); + else + SortTeams(); + + // set scrollbar range + m_PlayerList.SetScrollRange(m_iRows); + + FillGrid(); + + if ( gViewPort->m_pSpectatorPanel->m_menuVisible ) + { + m_pCloseButton->setVisible ( true ); + } + else + { + m_pCloseButton->setVisible ( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Sort all the teams +//----------------------------------------------------------------------------- +void ScorePanel::SortTeams() +{ + // clear out team scores + int i; + for ( i = 1; i <= m_iNumTeams; i++ ) + { + if ( !g_TeamInfo[i].scores_overriden ) + g_TeamInfo[i].frags = g_TeamInfo[i].deaths = 0; + g_TeamInfo[i].ping = g_TeamInfo[i].packetloss = 0; + } + + // recalc the team scores, then draw them + for ( i = 1; i < MAX_PLAYERS; i++ ) + { + if ( g_PlayerInfoList[i].name == NULL ) + continue; // empty player slot, skip + + if ( g_PlayerExtraInfo[i].teamname[0] == 0 ) + continue; // skip over players who are not in a team + + // find what team this player is in + int j; + for ( j = 1; j <= m_iNumTeams; j++ ) + { + if ( !stricmp( g_PlayerExtraInfo[i].teamname, g_TeamInfo[j].name ) ) + break; + } + if ( j > m_iNumTeams ) // player is not in a team, skip to the next guy + continue; + + if ( !g_TeamInfo[j].scores_overriden ) + { + g_TeamInfo[j].frags += g_PlayerExtraInfo[i].frags; + g_TeamInfo[j].deaths += g_PlayerExtraInfo[i].deaths; + } + + g_TeamInfo[j].ping += g_PlayerInfoList[i].ping; + g_TeamInfo[j].packetloss += g_PlayerInfoList[i].packetloss; + + if ( g_PlayerInfoList[i].thisplayer ) + g_TeamInfo[j].ownteam = TRUE; + else + g_TeamInfo[j].ownteam = FALSE; + + // Set the team's number (used for team colors) + g_TeamInfo[j].teamnumber = g_PlayerExtraInfo[i].teamnumber; + } + + // find team ping/packetloss averages + for ( i = 1; i <= m_iNumTeams; i++ ) + { + g_TeamInfo[i].already_drawn = FALSE; + + if ( g_TeamInfo[i].players > 0 ) + { + g_TeamInfo[i].ping /= g_TeamInfo[i].players; // use the average ping of all the players in the team as the teams ping + g_TeamInfo[i].packetloss /= g_TeamInfo[i].players; // use the average ping of all the players in the team as the teams ping + } + } + + // Draw the teams + while ( 1 ) + { + int highest_frags = -99999; int lowest_deaths = 99999; + int best_team = 0; + + for ( i = 1; i <= m_iNumTeams; i++ ) + { + if ( g_TeamInfo[i].players < 1 ) + continue; + + if ( !g_TeamInfo[i].already_drawn && g_TeamInfo[i].frags >= highest_frags ) + { + if ( g_TeamInfo[i].frags > highest_frags || g_TeamInfo[i].deaths < lowest_deaths ) + { + best_team = i; + lowest_deaths = g_TeamInfo[i].deaths; + highest_frags = g_TeamInfo[i].frags; + } + } + } + + // draw the best team on the scoreboard + if ( !best_team ) + break; + + // Put this team in the sorted list + m_iSortedRows[ m_iRows ] = best_team; + m_iIsATeam[ m_iRows ] = TEAM_YES; + g_TeamInfo[best_team].already_drawn = TRUE; // set the already_drawn to be TRUE, so this team won't get sorted again + m_iRows++; + + // Now sort all the players on this team + SortPlayers( 0, g_TeamInfo[best_team].name ); + } + + // Add all the players who aren't in a team yet into spectators + SortPlayers( TEAM_SPECTATORS, NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sort a list of players +//----------------------------------------------------------------------------- +void ScorePanel::SortPlayers( int iTeam, char *team ) +{ + bool bCreatedTeam = false; + + // draw the players, in order, and restricted to team if set + while ( 1 ) + { + // Find the top ranking player + int highest_frags = -99999; int lowest_deaths = 99999; + int best_player; + best_player = 0; + + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + if ( m_bHasBeenSorted[i] == false && g_PlayerInfoList[i].name && g_PlayerExtraInfo[i].frags >= highest_frags ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( i ); + + if ( ent && !(team && stricmp(g_PlayerExtraInfo[i].teamname, team)) ) + { + extra_player_info_t *pl_info = &g_PlayerExtraInfo[i]; + if ( pl_info->frags > highest_frags || pl_info->deaths < lowest_deaths ) + { + best_player = i; + lowest_deaths = pl_info->deaths; + highest_frags = pl_info->frags; + } + } + } + } + + if ( !best_player ) + break; + + // If we haven't created the Team yet, do it first + if (!bCreatedTeam && iTeam) + { + m_iIsATeam[ m_iRows ] = iTeam; + m_iRows++; + + bCreatedTeam = true; + } + + // Put this player in the sorted list + m_iSortedRows[ m_iRows ] = best_player; + m_bHasBeenSorted[ best_player ] = true; + m_iRows++; + } + + if (team) + { + m_iIsATeam[m_iRows++] = TEAM_BLANK; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Recalculate the existing teams in the match +//----------------------------------------------------------------------------- +void ScorePanel::RebuildTeams() +{ + // clear out player counts from teams + int i; + for ( i = 1; i <= m_iNumTeams; i++ ) + { + g_TeamInfo[i].players = 0; + } + + // rebuild the team list + gViewPort->GetAllPlayersInfo(); + m_iNumTeams = 0; + for ( i = 1; i < MAX_PLAYERS; i++ ) + { + if ( g_PlayerInfoList[i].name == NULL ) + continue; + + if ( g_PlayerExtraInfo[i].teamname[0] == 0 ) + continue; // skip over players who are not in a team + + // is this player in an existing team? + int j; + for ( j = 1; j <= m_iNumTeams; j++ ) + { + if ( g_TeamInfo[j].name[0] == '\0' ) + break; + + if ( !stricmp( g_PlayerExtraInfo[i].teamname, g_TeamInfo[j].name ) ) + break; + } + + if ( j > m_iNumTeams ) + { // they aren't in a listed team, so make a new one + // search through for an empty team slot + for ( int j = 1; j <= m_iNumTeams; j++ ) + { + if ( g_TeamInfo[j].name[0] == '\0' ) + break; + } + m_iNumTeams = max( j, m_iNumTeams ); + + strncpy( g_TeamInfo[j].name, g_PlayerExtraInfo[i].teamname, MAX_TEAM_NAME ); + g_TeamInfo[j].players = 0; + } + + g_TeamInfo[j].players++; + } + + // clear out any empty teams + for ( i = 1; i <= m_iNumTeams; i++ ) + { + if ( g_TeamInfo[i].players < 1 ) + memset( &g_TeamInfo[i], 0, sizeof(team_info_t) ); + } + + // Update the scoreboard + Update(); +} + + +void ScorePanel::FillGrid() +{ + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + SchemeHandle_t hScheme = pSchemes->getSchemeHandle("Scoreboard Text"); + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle("Scoreboard Title Text"); + SchemeHandle_t hSmallScheme = pSchemes->getSchemeHandle("Scoreboard Small Text"); + + Font *sfont = pSchemes->getFont(hScheme); + Font *tfont = pSchemes->getFont(hTitleScheme); + Font *smallfont = pSchemes->getFont(hSmallScheme); + + // update highlight position + int x, y; + getApp()->getCursorPos(x, y); + cursorMoved(x, y, this); + + // remove highlight row if we're not in squelch mode + if (!GetClientVoiceMgr()->IsInSquelchMode()) + { + m_iHighlightRow = -1; + } + + bool bNextRowIsGap = false; + int row; + for(row=0; row < NUM_ROWS; row++) + { + CGrid *pGridRow = &m_PlayerGrids[row]; + pGridRow->SetRowUnderline(0, false, 0, 0, 0, 0, 0); + + if(row >= m_iRows) + { + for(int col=0; col < NUM_COLUMNS; col++) + m_PlayerEntries[col][row].setVisible(false); + + continue; + } + + bool bRowIsGap = false; + if (bNextRowIsGap) + { + bNextRowIsGap = false; + bRowIsGap = true; + } + + for(int col=0; col < NUM_COLUMNS; col++) + { + CLabelHeader *pLabel = &m_PlayerEntries[col][row]; + + pLabel->setVisible(true); + pLabel->setText2(""); + pLabel->setImage(NULL); + pLabel->setFont(sfont); + pLabel->setTextOffset(0, 0); + + int rowheight = 13; + if (ScreenHeight > 480) + { + rowheight = YRES(rowheight); + } + else + { + // more tweaking, make sure icons fit at low res + rowheight = 15; + } + pLabel->setSize(pLabel->getWide(), rowheight); + pLabel->setBgColor(0, 0, 0, 255); + + char sz[128]; + hud_player_info_t *pl_info = NULL; + team_info_t *team_info = NULL; + + if (m_iIsATeam[row] == TEAM_BLANK) + { + pLabel->setText(" "); + continue; + } + else if ( m_iIsATeam[row] == TEAM_YES ) + { + // Get the team's data + team_info = &g_TeamInfo[ m_iSortedRows[row] ]; + + // team color text for team names + pLabel->setFgColor( iTeamColors[team_info->teamnumber % iNumberOfTeamColors][0], + iTeamColors[team_info->teamnumber % iNumberOfTeamColors][1], + iTeamColors[team_info->teamnumber % iNumberOfTeamColors][2], + 0 ); + + // different height for team header rows + rowheight = 20; + if (ScreenHeight >= 480) + { + rowheight = YRES(rowheight); + } + pLabel->setSize(pLabel->getWide(), rowheight); + pLabel->setFont(tfont); + + pGridRow->SetRowUnderline( 0, + true, + YRES(3), + iTeamColors[team_info->teamnumber % iNumberOfTeamColors][0], + iTeamColors[team_info->teamnumber % iNumberOfTeamColors][1], + iTeamColors[team_info->teamnumber % iNumberOfTeamColors][2], + 0 ); + } + else if ( m_iIsATeam[row] == TEAM_SPECTATORS ) + { + // grey text for spectators + pLabel->setFgColor(100, 100, 100, 0); + + // different height for team header rows + rowheight = 20; + if (ScreenHeight >= 480) + { + rowheight = YRES(rowheight); + } + pLabel->setSize(pLabel->getWide(), rowheight); + pLabel->setFont(tfont); + + pGridRow->SetRowUnderline(0, true, YRES(3), 100, 100, 100, 0); + } + else + { + // team color text for player names + pLabel->setFgColor( iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][0], + iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][1], + iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][2], + 0 ); + + // Get the player's data + pl_info = &g_PlayerInfoList[ m_iSortedRows[row] ]; + + // Set background color + if ( pl_info->thisplayer ) // if it is their name, draw it a different color + { + // Highlight this player + pLabel->setFgColor(Scheme::sc_white); + pLabel->setBgColor( iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][0], + iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][1], + iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][2], + 196 ); + } + else if ( m_iSortedRows[row] == m_iLastKilledBy && m_fLastKillTime && m_fLastKillTime > gHUD.m_flTime ) + { + // Killer's name + pLabel->setBgColor( 255,0,0, 255 - ((float)15 * (float)(m_fLastKillTime - gHUD.m_flTime)) ); + } + } + + // Align + if (col == COLUMN_NAME || col == COLUMN_CLASS) + { + pLabel->setContentAlignment( vgui::Label::a_west ); + } + else if (col == COLUMN_TRACKER) + { + pLabel->setContentAlignment( vgui::Label::a_center ); + } + else + { + pLabel->setContentAlignment( vgui::Label::a_east ); + } + + // Fill out with the correct data + strcpy(sz, ""); + if ( m_iIsATeam[row] ) + { + char sz2[128]; + + switch (col) + { + case COLUMN_NAME: + if ( m_iIsATeam[row] == TEAM_SPECTATORS ) + { + sprintf( sz2, CHudTextMessage::BufferedLocaliseTextString( "#Spectators" ) ); + } + else + { + sprintf( sz2, gViewPort->GetTeamName(team_info->teamnumber) ); + } + + strcpy(sz, sz2); + + // Append the number of players + if ( m_iIsATeam[row] == TEAM_YES ) + { + if (team_info->players == 1) + { + sprintf(sz2, "(%d %s)", team_info->players, CHudTextMessage::BufferedLocaliseTextString( "#Player" ) ); + } + else + { + sprintf(sz2, "(%d %s)", team_info->players, CHudTextMessage::BufferedLocaliseTextString( "#Player_plural" ) ); + } + + pLabel->setText2(sz2); + pLabel->setFont2(smallfont); + } + break; + case COLUMN_VOICE: + break; + case COLUMN_CLASS: + break; + case COLUMN_KILLS: + if ( m_iIsATeam[row] == TEAM_YES ) + sprintf(sz, "%d", team_info->frags ); + break; + case COLUMN_DEATHS: + if ( m_iIsATeam[row] == TEAM_YES ) + sprintf(sz, "%d", team_info->deaths ); + break; + case COLUMN_LATENCY: + if ( m_iIsATeam[row] == TEAM_YES ) + sprintf(sz, "%d", team_info->ping ); + break; + default: + break; + } + } + else + { + bool bShowClass = false; + + switch (col) + { + case COLUMN_NAME: + /* + if (g_pTrackerUser) + { + int playerSlot = m_iSortedRows[row]; + int trackerID = gEngfuncs.GetTrackerIDForPlayer(playerSlot); + const char *trackerName = g_pTrackerUser->GetUserName(trackerID); + if (trackerName && *trackerName) + { + sprintf(sz, " (%s)", trackerName); + pLabel->setText2(sz); + } + } + */ + sprintf(sz, "%s ", pl_info->name); + break; + case COLUMN_VOICE: + sz[0] = 0; + GetClientVoiceMgr()->UpdateSpeakerImage(pLabel, m_iSortedRows[row]); + break; + case COLUMN_CLASS: + // No class for other team's members (unless allied or spectator) + if ( gViewPort && EV_TFC_IsAllyTeam( g_iTeamNumber, g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ) ) + bShowClass = true; + // Don't show classes if this client hasnt picked a team yet + if ( g_iTeamNumber == 0 ) + bShowClass = false; +#ifdef _TFC + // in TFC show all classes in spectator mode + if ( g_iUser1 ) + bShowClass = true; +#endif + + if (bShowClass) + { + // Only print Civilian if this team are all civilians + bool bNoClass = false; + if ( g_PlayerExtraInfo[ m_iSortedRows[row] ].playerclass == 0 ) + { + if ( gViewPort->GetValidClasses( g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ) != -1 ) + bNoClass = true; + } + + if (bNoClass) + sprintf(sz, ""); + else + sprintf( sz, "%s", CHudTextMessage::BufferedLocaliseTextString( sLocalisedClasses[ g_PlayerExtraInfo[ m_iSortedRows[row] ].playerclass ] ) ); + } + else + { + strcpy(sz, ""); + } + break; + + case COLUMN_TRACKER: + /* + if (g_pTrackerUser) + { + int playerSlot = m_iSortedRows[row]; + int trackerID = gEngfuncs.GetTrackerIDForPlayer(playerSlot); + + if (g_pTrackerUser->IsFriend(trackerID) && trackerID != g_pTrackerUser->GetTrackerID()) + { + pLabel->setImage(m_pTrackerIcon); + pLabel->setFgColorAsImageColor(false); + m_pTrackerIcon->setColor(Color(255, 255, 255, 0)); + } + } + */ + break; + +#ifdef _TFC + case COLUMN_KILLS: + if (g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber) + sprintf(sz, "%d", g_PlayerExtraInfo[ m_iSortedRows[row] ].frags ); + break; + case COLUMN_DEATHS: + if (g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber) + sprintf(sz, "%d", g_PlayerExtraInfo[ m_iSortedRows[row] ].deaths ); + break; + case COLUMN_LATENCY: + if (g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber) + sprintf(sz, "%d", g_PlayerInfoList[ m_iSortedRows[row] ].ping ); + break; +#else + case COLUMN_KILLS: + sprintf(sz, "%d", g_PlayerExtraInfo[ m_iSortedRows[row] ].frags ); + break; + case COLUMN_DEATHS: + sprintf(sz, "%d", g_PlayerExtraInfo[ m_iSortedRows[row] ].deaths ); + break; + case COLUMN_LATENCY: + sprintf(sz, "%d", g_PlayerInfoList[ m_iSortedRows[row] ].ping ); + break; +#endif + default: + break; + } + } + + pLabel->setText(sz); + } + } + + for(row=0; row < NUM_ROWS; row++) + { + CGrid *pGridRow = &m_PlayerGrids[row]; + + pGridRow->AutoSetRowHeights(); + pGridRow->setSize(PanelWidth(pGridRow), pGridRow->CalcDrawHeight()); + pGridRow->RepositionContents(); + } + + // hack, for the thing to resize + m_PlayerList.getSize(x, y); + m_PlayerList.setSize(x, y); +} + + +//----------------------------------------------------------------------------- +// Purpose: Setup highlights for player names in scoreboard +//----------------------------------------------------------------------------- +void ScorePanel::DeathMsg( int killer, int victim ) +{ + // if we were the one killed, or the world killed us, set the scoreboard to indicate suicide + if ( victim == m_iPlayerNum || killer == 0 ) + { + m_iLastKilledBy = killer ? killer : m_iPlayerNum; + m_fLastKillTime = gHUD.m_flTime + 10; // display who we were killed by for 10 seconds + + if ( killer == m_iPlayerNum ) + m_iLastKilledBy = m_iPlayerNum; + } +} + + +void ScorePanel::Open( void ) +{ + RebuildTeams(); + setVisible(true); + m_HitTestPanel.setVisible(true); +} + + +void ScorePanel::mousePressed(MouseCode code, Panel* panel) +{ + if(gHUD.m_iIntermission) + return; + + if (!GetClientVoiceMgr()->IsInSquelchMode()) + { + GetClientVoiceMgr()->StartSquelchMode(); + m_HitTestPanel.setVisible(false); + } + else if (m_iHighlightRow >= 0) + { + // mouse has been pressed, toggle mute state + int iPlayer = m_iSortedRows[m_iHighlightRow]; + if (iPlayer > 0) + { + // print text message + hud_player_info_t *pl_info = &g_PlayerInfoList[iPlayer]; + + if (pl_info && pl_info->name && pl_info->name[0]) + { + char string[256]; + if (GetClientVoiceMgr()->IsPlayerBlocked(iPlayer)) + { + char string1[1024]; + + // remove mute + GetClientVoiceMgr()->SetPlayerBlockedState(iPlayer, false); + + sprintf( string1, CHudTextMessage::BufferedLocaliseTextString( "#Unmuted" ), pl_info->name ); + sprintf( string, "%c** %s\n", HUD_PRINTTALK, string1 ); + + gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); + } + else + { + char string1[1024]; + char string2[1024]; + + // mute the player + GetClientVoiceMgr()->SetPlayerBlockedState(iPlayer, true); + + sprintf( string1, CHudTextMessage::BufferedLocaliseTextString( "#Muted" ), pl_info->name ); + sprintf( string2, CHudTextMessage::BufferedLocaliseTextString( "#No_longer_hear_that_player" ) ); + sprintf( string, "%c** %s %s\n", HUD_PRINTTALK, string1, string2 ); + + gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); + } + } + } + } +} + +void ScorePanel::cursorMoved(int x, int y, Panel *panel) +{ + if (GetClientVoiceMgr()->IsInSquelchMode()) + { + // look for which cell the mouse is currently over + for (int i = 0; i < NUM_ROWS; i++) + { + int row, col; + if (m_PlayerGrids[i].getCellAtPoint(x, y, row, col)) + { + MouseOverCell(i, col); + return; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handles mouse movement over a cell +// Input : row - +// col - +//----------------------------------------------------------------------------- +void ScorePanel::MouseOverCell(int row, int col) +{ + CLabelHeader *label = &m_PlayerEntries[col][row]; + + // clear the previously highlighted label + if (m_pCurrentHighlightLabel != label) + { + m_pCurrentHighlightLabel = NULL; + m_iHighlightRow = -1; + } + if (!label) + return; + + // don't act on teams + if (m_iIsATeam[row] != TEAM_NO) + return; + + // don't act on disconnected players or ourselves + hud_player_info_t *pl_info = &g_PlayerInfoList[ m_iSortedRows[row] ]; + if (!pl_info->name || !pl_info->name[0]) + return; + + if (pl_info->thisplayer && !gEngfuncs.IsSpectateOnly() ) + return; + + // setup the new highlight + m_pCurrentHighlightLabel = label; + m_iHighlightRow = row; +} + +//----------------------------------------------------------------------------- +// Purpose: Label paint functions - take into account current highligh status +//----------------------------------------------------------------------------- +void CLabelHeader::paintBackground() +{ + Color oldBg; + getBgColor(oldBg); + + if (gViewPort->GetScoreBoard()->m_iHighlightRow == _row) + { + setBgColor(134, 91, 19, 0); + } + + Panel::paintBackground(); + + setBgColor(oldBg); +} + + +//----------------------------------------------------------------------------- +// Purpose: Label paint functions - take into account current highligh status +//----------------------------------------------------------------------------- +void CLabelHeader::paint() +{ + Color oldFg; + getFgColor(oldFg); + + if (gViewPort->GetScoreBoard()->m_iHighlightRow == _row) + { + setFgColor(255, 255, 255, 0); + } + + // draw text + int x, y, iwide, itall; + getTextSize(iwide, itall); + calcAlignment(iwide, itall, x, y); + _dualImage->setPos(x, y); + + int x1, y1; + _dualImage->GetImage(1)->getPos(x1, y1); + _dualImage->GetImage(1)->setPos(_gap, y1); + + _dualImage->doPaint(this); + + // get size of the panel and the image + if (_image) + { + Color imgColor; + getFgColor( imgColor ); + if( _useFgColorAsImageColor ) + { + _image->setColor( imgColor ); + } + + _image->getSize(iwide, itall); + calcAlignment(iwide, itall, x, y); + _image->setPos(x, y); + _image->doPaint(this); + } + + setFgColor(oldFg[0], oldFg[1], oldFg[2], oldFg[3]); +} + + +void CLabelHeader::calcAlignment(int iwide, int itall, int &x, int &y) +{ + // calculate alignment ourselves, since vgui is so broken + int wide, tall; + getSize(wide, tall); + + x = 0, y = 0; + + // align left/right + switch (_contentAlignment) + { + // left + case Label::a_northwest: + case Label::a_west: + case Label::a_southwest: + { + x = 0; + break; + } + + // center + case Label::a_north: + case Label::a_center: + case Label::a_south: + { + x = (wide - iwide) / 2; + break; + } + + // right + case Label::a_northeast: + case Label::a_east: + case Label::a_southeast: + { + x = wide - iwide; + break; + } + } + + // top/down + switch (_contentAlignment) + { + // top + case Label::a_northwest: + case Label::a_north: + case Label::a_northeast: + { + y = 0; + break; + } + + // center + case Label::a_west: + case Label::a_center: + case Label::a_east: + { + y = (tall - itall) / 2; + break; + } + + // south + case Label::a_southwest: + case Label::a_south: + case Label::a_southeast: + { + y = tall - itall; + break; + } + } + +// don't clip to Y +// if (y < 0) +// { +// y = 0; +// } + if (x < 0) + { + x = 0; + } + + x += _offset[0]; + y += _offset[1]; +} diff --git a/cl_dll/vgui_ScorePanel.h b/cl_dll/vgui_ScorePanel.h new file mode 100644 index 0000000..b9870fd --- /dev/null +++ b/cl_dll/vgui_ScorePanel.h @@ -0,0 +1,310 @@ +//========= Copyright � 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef SCOREPANEL_H +#define SCOREPANEL_H + +#include +#include +#include +#include +#include +#include +#include "../game_shared/vgui_listbox.h" + +#include + +#define MAX_SCORES 10 +#define MAX_SCOREBOARD_TEAMS 5 + +// Scoreboard cells +#define COLUMN_TRACKER 0 +#define COLUMN_NAME 1 +#define COLUMN_CLASS 2 +#define COLUMN_KILLS 3 +#define COLUMN_DEATHS 4 +#define COLUMN_LATENCY 5 +#define COLUMN_VOICE 6 +#define COLUMN_BLANK 7 +#define NUM_COLUMNS 8 +#define NUM_ROWS (MAX_PLAYERS + (MAX_SCOREBOARD_TEAMS * 2)) + +using namespace vgui; + +class CTextImage2 : public Image +{ +public: + CTextImage2() + { + _image[0] = new TextImage(""); + _image[1] = new TextImage(""); + } + + ~CTextImage2() + { + delete _image[0]; + delete _image[1]; + } + + TextImage *GetImage(int image) + { + return _image[image]; + } + + void getSize(int &wide, int &tall) + { + int w1, w2, t1, t2; + _image[0]->getTextSize(w1, t1); + _image[1]->getTextSize(w2, t2); + + wide = w1 + w2; + tall = max(t1, t2); + setSize(wide, tall); + } + + void doPaint(Panel *panel) + { + _image[0]->doPaint(panel); + _image[1]->doPaint(panel); + } + + void setPos(int x, int y) + { + _image[0]->setPos(x, y); + + int swide, stall; + _image[0]->getSize(swide, stall); + + int wide, tall; + _image[1]->getSize(wide, tall); + _image[1]->setPos(x + wide, y + (stall * 0.9) - tall); + } + + void setColor(Color color) + { + _image[0]->setColor(color); + } + + void setColor2(Color color) + { + _image[1]->setColor(color); + } + +private: + TextImage *_image[2]; + +}; + +//----------------------------------------------------------------------------- +// Purpose: Custom label for cells in the Scoreboard's Table Header +//----------------------------------------------------------------------------- +class CLabelHeader : public Label +{ +public: + CLabelHeader() : Label("") + { + _dualImage = new CTextImage2(); + _dualImage->setColor2(Color(255, 170, 0, 0)); + _row = -2; + _useFgColorAsImageColor = true; + _offset[0] = 0; + _offset[1] = 0; + } + + ~CLabelHeader() + { + delete _dualImage; + } + + void setRow(int row) + { + _row = row; + } + + void setFgColorAsImageColor(bool state) + { + _useFgColorAsImageColor = state; + } + + virtual void setText(int textBufferLen, const char* text) + { + _dualImage->GetImage(0)->setText(text); + + // calculate the text size + Font *font = _dualImage->GetImage(0)->getFont(); + _gap = 0; + for (const char *ch = text; *ch != 0; ch++) + { + int a, b, c; + font->getCharABCwide(*ch, a, b, c); + _gap += (a + b + c); + } + + _gap += XRES(5); + } + + virtual void setText(const char* text) + { + // strip any non-alnum characters from the end + char buf[512]; + strcpy(buf, text); + + int len = strlen(buf); + while (len && isspace(buf[--len])) + { + buf[len] = 0; + } + + CLabelHeader::setText(0, buf); + } + + void setText2(const char *text) + { + _dualImage->GetImage(1)->setText(text); + } + + void getTextSize(int &wide, int &tall) + { + _dualImage->getSize(wide, tall); + } + + void setFgColor(int r,int g,int b,int a) + { + Label::setFgColor(r,g,b,a); + Color color(r,g,b,a); + _dualImage->setColor(color); + _dualImage->setColor2(color); + repaint(); + } + + void setFgColor(Scheme::SchemeColor sc) + { + int r, g, b, a; + Label::setFgColor(sc); + Label::getFgColor( r, g, b, a ); + + // Call the r,g,b,a version so it sets the color in the dualImage.. + setFgColor( r, g, b, a ); + } + + void setFont(Font *font) + { + _dualImage->GetImage(0)->setFont(font); + } + + void setFont2(Font *font) + { + _dualImage->GetImage(1)->setFont(font); + } + + // this adjust the absolute position of the text after alignment is calculated + void setTextOffset(int x, int y) + { + _offset[0] = x; + _offset[1] = y; + } + + void paint(); + void paintBackground(); + void calcAlignment(int iwide, int itall, int &x, int &y); + +private: + CTextImage2 *_dualImage; + int _row; + int _gap; + int _offset[2]; + bool _useFgColorAsImageColor; +}; + +class ScoreTablePanel; + +#include "../game_shared/vgui_grid.h" +#include "../game_shared/vgui_defaultinputsignal.h" + +//----------------------------------------------------------------------------- +// Purpose: Scoreboard back panel +//----------------------------------------------------------------------------- +class ScorePanel : public Panel, public vgui::CDefaultInputSignal +{ +private: + // Default panel implementation doesn't forward mouse messages when there is no cursor and we need them. + class HitTestPanel : public Panel + { + public: + virtual void internalMousePressed(MouseCode code); + }; + + +private: + + Label m_TitleLabel; + + // Here is how these controls are arranged hierarchically. + // m_HeaderGrid + // m_HeaderLabels + + // m_PlayerGridScroll + // m_PlayerGrid + // m_PlayerEntries + + CGrid m_HeaderGrid; + CLabelHeader m_HeaderLabels[NUM_COLUMNS]; // Labels above the + CLabelHeader *m_pCurrentHighlightLabel; + int m_iHighlightRow; + + vgui::CListBox m_PlayerList; + CGrid m_PlayerGrids[NUM_ROWS]; // The grid with player and team info. + CLabelHeader m_PlayerEntries[NUM_COLUMNS][NUM_ROWS]; // Labels for the grid entries. + + ScorePanel::HitTestPanel m_HitTestPanel; + CommandButton *m_pCloseButton; + CLabelHeader* GetPlayerEntry(int x, int y) {return &m_PlayerEntries[x][y];} + +public: + + int m_iNumTeams; + int m_iPlayerNum; + int m_iShowscoresHeld; + + int m_iRows; + int m_iSortedRows[NUM_ROWS]; + int m_iIsATeam[NUM_ROWS]; + bool m_bHasBeenSorted[MAX_PLAYERS]; + int m_iLastKilledBy; + int m_fLastKillTime; + + +public: + + ScorePanel(int x,int y,int wide,int tall); + + void Update( void ); + + void SortTeams( void ); + void SortPlayers( int iTeam, char *team ); + void RebuildTeams( void ); + + void FillGrid(); + + void DeathMsg( int killer, int victim ); + + void Initialize( void ); + + void Open( void ); + + void MouseOverCell(int row, int col); + +// InputSignal overrides. +public: + + virtual void mousePressed(MouseCode code, Panel* panel); + virtual void cursorMoved(int x, int y, Panel *panel); + + friend class CLabelHeader; +}; + +#endif diff --git a/cl_dll/vgui_ServerBrowser.cpp b/cl_dll/vgui_ServerBrowser.cpp new file mode 100644 index 0000000..48040f3 --- /dev/null +++ b/cl_dll/vgui_ServerBrowser.cpp @@ -0,0 +1,623 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include +#include +#include +#include +#include + +#include "hud.h" +#include "cl_util.h" +#include "hud_servers.h" +#include "net_api.h" + +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" + +using namespace vgui; + +namespace +{ + +#define MAX_SB_ROWS 24 + +#define NUM_COLUMNS 5 + +#define HEADER_SIZE_Y YRES(18) + +// Column sizes +#define CSIZE_ADDRESS XRES(200) +#define CSIZE_SERVER XRES(400) +#define CSIZE_MAP XRES(500) +#define CSIZE_CURRENT XRES(570) +#define CSIZE_PING XRES(640) + +#define CELL_HEIGHT YRES(15) + +class ServerBrowserTablePanel; + +class CBrowser_InputSignal : public InputSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; +public: + CBrowser_InputSignal( ServerBrowserTablePanel *pBrowser ) + { + m_pBrowser = pBrowser; + } + + virtual void cursorMoved(int x,int y,Panel* panel) {}; + virtual void cursorEntered(Panel* panel){}; + virtual void cursorExited(Panel* Panel) {}; + + virtual void mousePressed(MouseCode code,Panel* panel); + + virtual void mouseDoublePressed(MouseCode code,Panel* panel); + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; +}; + +class ServerBrowserTablePanel : public TablePanel +{ +private: + Label *m_pLabel; + int m_nMouseOverRow; + +public: + + ServerBrowserTablePanel( int x,int y,int wide,int tall,int columnCount) : TablePanel( x,y,wide,tall,columnCount) + { + m_pLabel = new Label( "", 0, 0 /*,wide, tall*/ ); + + m_nMouseOverRow = 0; + } + +public: + void setMouseOverRow( int row ) + { + m_nMouseOverRow = row; + } + + void DoSort( char *sortkey ) + { + // Request server list and refresh servers... + SortServers( sortkey ); + } + + void DoRefresh( void ) + { + // Request server list and refresh servers... + ServersList(); + BroadcastServersList( 0 ); + } + + void DoBroadcastRefresh( void ) + { + // Request server list and refresh servers... + BroadcastServersList( 1 ); + } + + void DoStop( void ) + { + // Stop requesting + ServersCancel(); + } + + void DoCancel( void ) + { + EngineClientCmd( "togglebrowser\n" ); + } + + void DoConnect( void ) + { + const char *info; + const char *address; + char sz[ 256 ]; + + info = ServersGetInfo( m_nMouseOverRow ); + if ( !info ) + return; + + address = gEngfuncs.pNetAPI->ValueForKey( info, "address" ); + //gEngfuncs.Con_Printf( "Connecting to %s\n", address ); + + sprintf( sz, "connect %s\n", address ); + + EngineClientCmd( sz ); + + DoCancel(); + } + + void DoPing( void ) + { + ServerPing( 0 ); + ServerRules( 0 ); + ServerPlayers( 0 ); + } + + virtual int getRowCount() + { + int rowcount; + int height, width; + + getSize( width, height ); + + // Space for buttons + height -= YRES(20); + height = max( 0, height ); + + rowcount = height / CELL_HEIGHT; + + return rowcount; + } + + virtual int getCellTall(int row) + { + return CELL_HEIGHT - 2; + } + + virtual Panel* getCellRenderer(int column,int row,bool columnSelected,bool rowSelected,bool cellSelected) + { + const char *info; + const char *val, *val2; + char sz[ 32 ]; + + info = ServersGetInfo( row ); + + if ( row == m_nMouseOverRow ) + { + m_pLabel->setFgColor( 200, 240, 63, 100 ); + } + else + { + m_pLabel->setFgColor( 255, 255, 255, 0 ); + } + m_pLabel->setBgColor( 0, 0, 0, 200 ); + m_pLabel->setContentAlignment( vgui::Label::a_west ); + m_pLabel->setFont( Scheme::sf_primary2 ); + + if ( info ) + { + // Fill out with the correct data + switch ( column ) + { + case 0: + val = gEngfuncs.pNetAPI->ValueForKey( info, "address" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Name; + m_pLabel->setText( sz ); + } + break; + case 1: + val = gEngfuncs.pNetAPI->ValueForKey( info, "hostname" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Map; + m_pLabel->setText( sz ); + } + break; + case 2: + val = gEngfuncs.pNetAPI->ValueForKey( info, "map" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Name; + m_pLabel->setText( sz ); + } + break; + case 3: + val = gEngfuncs.pNetAPI->ValueForKey( info, "current" ); + val2 = gEngfuncs.pNetAPI->ValueForKey( info, "max" ); + if ( val && val2 ) + { + sprintf( sz, "%s/%s", val, val2 ); + sz[ 31 ] = '\0'; + // Server Map; + m_pLabel->setText( sz ); + } + break; + case 4: + val = gEngfuncs.pNetAPI->ValueForKey( info, "ping" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Name; + m_pLabel->setText( sz ); + } + break; + default: + break; + } + } + else + { + if ( !row && !column ) + { + if ( ServersIsQuerying() ) + { + m_pLabel->setText( "Waiting for servers to respond..." ); + } + else + { + m_pLabel->setText( "Press 'Refresh' to search for servers..." ); + } + } + else + { + m_pLabel->setText( "" ); + } + } + + return m_pLabel; + } + + virtual Panel* startCellEditing(int column,int row) + { + return null; + } + +}; + +class ConnectHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + ConnectHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoConnect(); + } +}; + +class RefreshHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + RefreshHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoRefresh(); + } +}; + +class BroadcastRefreshHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + BroadcastRefreshHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoBroadcastRefresh(); + } +}; + +class StopHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + StopHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoStop(); + } +}; + +class CancelHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + CancelHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoCancel(); + } +}; + +class PingHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + PingHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoPing(); + } +}; + +class SortHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + SortHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoSort( "map" ); + } +}; + +} + +class LabelSortInputHandler : public InputSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + char m_szSortKey[ 64 ]; + +public: + LabelSortInputHandler( ServerBrowserTablePanel *pBrowser, char *name ) + { + m_pBrowser = pBrowser; + strcpy( m_szSortKey, name ); + } + + virtual void cursorMoved(int x,int y,Panel* panel) {}; + virtual void cursorEntered(Panel* panel){}; + virtual void cursorExited(Panel* Panel) {}; + + virtual void mousePressed(MouseCode code,Panel* panel) + { + m_pBrowser->DoSort( m_szSortKey ); + } + + virtual void mouseDoublePressed(MouseCode code,Panel* panel) + { + m_pBrowser->DoSort( m_szSortKey ); + } + + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; +}; + +class CSBLabel : public Label +{ + +private: + char m_szSortKey[ 64 ]; + ServerBrowserTablePanel *m_pBrowser; + +public: + CSBLabel( char *name, char *sortkey ) : Label( name ) + { + m_pBrowser = NULL; + + strcpy( m_szSortKey, sortkey ); + + int label_bg_r = 120, + label_bg_g = 75, + label_bg_b = 32, + label_bg_a = 200; + + int label_fg_r = 255, + label_fg_g = 0, + label_fg_b = 0, + label_fg_a = 0; + + setContentAlignment( vgui::Label::a_west ); + setFgColor( label_fg_r, label_fg_g, label_fg_b, label_fg_a ); + setBgColor( label_bg_r, label_bg_g, label_bg_b, label_bg_a ); + setFont( Scheme::sf_primary2 ); + + } + + void setTable( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + + addInputSignal( new LabelSortInputHandler( (ServerBrowserTablePanel * )m_pBrowser, m_szSortKey ) ); + } +}; + +ServerBrowser::ServerBrowser(int x,int y,int wide,int tall) : CTransparentPanel( 100, x,y,wide,tall ) +{ + int i; + + _headerPanel = new HeaderPanel(0,0,wide,HEADER_SIZE_Y); + _headerPanel->setParent(this); + _headerPanel->setFgColor( 100,100,100, 100 ); + _headerPanel->setBgColor( 0, 0, 0, 100 ); + + CSBLabel *pLabel[5]; + + pLabel[0] = new CSBLabel( "Address", "address" ); + pLabel[1] = new CSBLabel( "Server", "hostname" ); + pLabel[2] = new CSBLabel( "Map", "map" ); + pLabel[3] = new CSBLabel( "Current", "current" ); + pLabel[4] = new CSBLabel( "Latency", "ping" ); + + for ( i = 0; i < 5; i++ ) + { + _headerPanel->addSectionPanel( pLabel[i] ); + } + + // _headerPanel->setFont( Scheme::sf_primary1 ); + + _headerPanel->setSliderPos( 0, CSIZE_ADDRESS ); + _headerPanel->setSliderPos( 1, CSIZE_SERVER ); + _headerPanel->setSliderPos( 2, CSIZE_MAP ); + _headerPanel->setSliderPos( 3, CSIZE_CURRENT ); + _headerPanel->setSliderPos( 4, CSIZE_PING ); + + _tablePanel = new ServerBrowserTablePanel( 0, HEADER_SIZE_Y, wide, tall - HEADER_SIZE_Y, NUM_COLUMNS ); + _tablePanel->setParent(this); + _tablePanel->setHeaderPanel(_headerPanel); + _tablePanel->setFgColor( 100,100,100, 100 ); + _tablePanel->setBgColor( 0, 0, 0, 100 ); + + _tablePanel->addInputSignal( new CBrowser_InputSignal( (ServerBrowserTablePanel *)_tablePanel ) ); + + for ( i = 0; i < 5; i++ ) + { + pLabel[i]->setTable( (ServerBrowserTablePanel * )_tablePanel ); + } + + int bw = 80, bh = 15; + int by = tall - HEADER_SIZE_Y; + + int btnx = 10; + + _connectButton = new CommandButton( "Connect", btnx, by, bw, bh ); + _connectButton->setParent( this ); + _connectButton->addActionSignal( new ConnectHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + btnx += bw; + + _refreshButton = new CommandButton( "Refresh", btnx, by, bw, bh ); + _refreshButton->setParent( this ); + _refreshButton->addActionSignal( new RefreshHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + /* + btnx += bw; + + _broadcastRefreshButton = new CommandButton( "LAN", btnx, by, bw, bh ); + _broadcastRefreshButton->setParent( this ); + _broadcastRefreshButton->addActionSignal( new BroadcastRefreshHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + */ + + btnx += bw; + + _stopButton = new CommandButton( "Stop", btnx, by, bw, bh ); + _stopButton->setParent( this ); + _stopButton->addActionSignal( new StopHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + /* + btnx += bw; + + _pingButton = new CommandButton( "Test", btnx, by, bw, bh ); + _pingButton->setParent( this ); + _pingButton->addActionSignal( new PingHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + btnx += bw; + + _sortButton = new CommandButton( "Sort", btnx, by, bw, bh ); + _sortButton->setParent( this ); + _sortButton->addActionSignal( new SortHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + */ + + btnx += bw; + + _cancelButton = new CommandButton( "Close", btnx, by, bw, bh ); + _cancelButton->setParent( this ); + _cancelButton->addActionSignal( new CancelHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + setPaintBorderEnabled(false); + setPaintBackgroundEnabled(false); + setPaintEnabled(false); + +} + +void ServerBrowser::setSize(int wide,int tall) +{ + Panel::setSize(wide,tall); + + _headerPanel->setBounds(0,0,wide,HEADER_SIZE_Y); + _tablePanel->setBounds(0,HEADER_SIZE_Y,wide,tall - HEADER_SIZE_Y); + + _connectButton->setBounds( 5, tall - HEADER_SIZE_Y, 75, 15 ); + _refreshButton->setBounds( 85, tall - HEADER_SIZE_Y, 75, 15 ); + /* + _broadcastRefreshButton->setBounds( 165, tall - HEADER_SIZE_Y, 75, 15 ); + */ + _stopButton->setBounds( 165, tall - HEADER_SIZE_Y, 75, 15 ); + /* + _pingButton->setBounds( 325, tall - HEADER_SIZE_Y, 75, 15 ); + */ + _cancelButton->setBounds( 245, tall - HEADER_SIZE_Y, 75, 15 ); +} + +void CBrowser_InputSignal::mousePressed(MouseCode code,Panel* panel) +{ + int x, y; + int therow = 2; + + if ( code != MOUSE_LEFT ) + return; + + panel->getApp()->getCursorPos(x,y); + panel->screenToLocal( x, y ); + + therow = y / CELL_HEIGHT; + + // Figure out which row it's on + m_pBrowser->setMouseOverRow( therow ); +} + +void CBrowser_InputSignal::mouseDoublePressed(MouseCode code,Panel* panel) +{ + int x, y; + int therow = 2; + + if ( code != MOUSE_LEFT ) + return; + + panel->getApp()->getCursorPos(x,y); + panel->screenToLocal( x, y ); + + therow = y / CELL_HEIGHT; + + // Figure out which row it's on + m_pBrowser->setMouseOverRow( therow ); + m_pBrowser->DoConnect(); +} diff --git a/cl_dll/vgui_ServerBrowser.h b/cl_dll/vgui_ServerBrowser.h new file mode 100644 index 0000000..0f51d72 --- /dev/null +++ b/cl_dll/vgui_ServerBrowser.h @@ -0,0 +1,50 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef ServerBrowser_H +#define ServerBrowser_H + +#include + +namespace vgui +{ +class Button; +class TablePanel; +class HeaderPanel; +} + +class CTransparentPanel; +class CommandButton; + +// Scoreboard positions +#define SB_X_INDENT (20 * ((float)ScreenHeight / 640)) +#define SB_Y_INDENT (20 * ((float)ScreenHeight / 480)) + +class ServerBrowser : public CTransparentPanel +{ +private: + HeaderPanel * _headerPanel; + TablePanel* _tablePanel; + + CommandButton* _connectButton; + CommandButton* _refreshButton; + CommandButton* _broadcastRefreshButton; + CommandButton* _stopButton; + CommandButton* _sortButton; + CommandButton* _cancelButton; + + CommandButton* _pingButton; + +public: + ServerBrowser(int x,int y,int wide,int tall); +public: + virtual void setSize(int wide,int tall); +}; + + + +#endif \ No newline at end of file diff --git a/cl_dll/vgui_SpectatorPanel.cpp b/cl_dll/vgui_SpectatorPanel.cpp new file mode 100644 index 0000000..1bd0517 --- /dev/null +++ b/cl_dll/vgui_SpectatorPanel.cpp @@ -0,0 +1,426 @@ +// vgui_SpectatorPanel.cpp: implementation of the SpectatorPanel class. +// +////////////////////////////////////////////////////////////////////// + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "pm_shared.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_SpectatorPanel.h" +#include "vgui_ScorePanel.h" + +#include "Exports.h" + +/* +========================== +HUD_ChatInputPosition + +Sets the location of the input for chat text +========================== +*/ + +void CL_DLLEXPORT HUD_ChatInputPosition( int *x, int *y ) +{ +// RecClChatInputPosition( x, y ); + + if ( g_iUser1 != 0 || gEngfuncs.IsSpectateOnly() ) + { + if ( gHUD.m_Spectator.m_pip->value == INSET_OFF ) + { + *y = YRES( PANEL_HEIGHT ); + } + else + { + *y = YRES( gHUD.m_Spectator.m_OverviewData.insetWindowHeight + 5 ); + } + } +} + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +SpectatorPanel::SpectatorPanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) +{ +} + +SpectatorPanel::~SpectatorPanel() +{ + +} + +void SpectatorPanel::ActionSignal(int cmd) +{ + switch (cmd) + { + case SPECTATOR_PANEL_CMD_NONE : break; + + case SPECTATOR_PANEL_CMD_OPTIONS : gViewPort->ShowCommandMenu( gViewPort->m_SpectatorOptionsMenu ); + break; + + case SPECTATOR_PANEL_CMD_NEXTPLAYER : gHUD.m_Spectator.FindNextPlayer(true); + break; + + case SPECTATOR_PANEL_CMD_PREVPLAYER : gHUD.m_Spectator.FindNextPlayer(false); + break; + + case SPECTATOR_PANEL_CMD_PLAYERS : gViewPort->ShowCommandMenu( gViewPort->m_PlayerMenu ); + break; + + case SPECTATOR_PANEL_CMD_HIDEMENU : ShowMenu(false); + break; + + case SPECTATOR_PANEL_CMD_CAMERA : gViewPort->ShowCommandMenu( gViewPort->m_SpectatorCameraMenu ); + break; + + case SPECTATOR_PANEL_CMD_TOGGLE_INSET : gHUD.m_Spectator.SetModes( -1, + gHUD.m_Spectator.ToggleInset(false) ); + break; + + + default : gEngfuncs.Con_DPrintf("Unknown SpectatorPanel ActionSingal %i.\n",cmd); break; + } + +} + + +void SpectatorPanel::Initialize() +{ + int x,y,wide,tall; + + getBounds(x,y,wide,tall); + + CSchemeManager * pSchemes = gViewPort->GetSchemeManager(); + + SchemeHandle_t hSmallScheme = pSchemes->getSchemeHandle( "Team Info Text" ); + + m_TopBorder = new CTransparentPanel(64, 0, 0, ScreenWidth, PANEL_HEIGHT); + m_TopBorder->setParent(this); + + m_BottomBorder = new CTransparentPanel(64, 0, ScreenHeight - PANEL_HEIGHT, ScreenWidth, PANEL_HEIGHT); + m_BottomBorder->setParent(this); + + setPaintBackgroundEnabled(false); + + m_ExtraInfo = new Label( "Extra Info", 0, 0, wide, PANEL_HEIGHT ); + m_ExtraInfo->setParent(m_TopBorder); + m_ExtraInfo->setFont( pSchemes->getFont(hSmallScheme) ); + + m_ExtraInfo->setPaintBackgroundEnabled(false); + m_ExtraInfo->setFgColor( 143, 143, 54, 0 ); + m_ExtraInfo->setContentAlignment( vgui::Label::a_west ); + + + + m_TimerImage = new CImageLabel( "timer", 0, 0, 14, 14 ); + m_TimerImage->setParent(m_TopBorder); + + m_TopBanner = new CImageLabel( "banner", 0, 0, XRES(BANNER_WIDTH), YRES(BANNER_HEIGHT) ); + m_TopBanner->setParent(this); + + m_CurrentTime = new Label( "00:00", 0, 0, wide, PANEL_HEIGHT ); + m_CurrentTime->setParent(m_TopBorder); + m_CurrentTime->setFont( pSchemes->getFont(hSmallScheme) ); + m_CurrentTime->setPaintBackgroundEnabled(false); + m_CurrentTime->setFgColor( 143, 143, 54, 0 ); + m_CurrentTime->setContentAlignment( vgui::Label::a_west ); + + m_Separator = new Panel( 0, 0, XRES( 64 ), YRES( 96 )); + m_Separator->setParent( m_TopBorder ); + m_Separator->setFgColor( 59, 58, 34, 48 ); + m_Separator->setBgColor( 59, 58, 34, 48 ); + + for ( int j= 0; j < TEAM_NUMBER; j++ ) + { + m_TeamScores[j] = new Label( " ", 0, 0, wide, PANEL_HEIGHT ); + m_TeamScores[j]->setParent( m_TopBorder ); + m_TeamScores[j]->setFont( pSchemes->getFont(hSmallScheme) ); + m_TeamScores[j]->setPaintBackgroundEnabled(false); + m_TeamScores[j]->setFgColor( 143, 143, 54, 0 ); + m_TeamScores[j]->setContentAlignment( vgui::Label::a_west ); + m_TeamScores[j]->setVisible ( false ); + } + + + // Initialize command buttons. +// m_OptionButton = new ColorButton( CHudTextMessage::BufferedLocaliseTextString( "#SPECT_OPTIONS" ), XRES(15), YRES(6), XRES(OPTIONS_BUTTON_X), YRES(20), false, false ); + m_OptionButton = new DropDownButton( CHudTextMessage::BufferedLocaliseTextString( "#SPECT_OPTIONS" ), XRES(15), YRES(6), XRES(OPTIONS_BUTTON_X), YRES(20), false, false ); + m_OptionButton->setParent( m_BottomBorder ); + m_OptionButton->setContentAlignment( vgui::Label::a_center ); + m_OptionButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_OptionButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_OPTIONS) ); + m_OptionButton->setUnArmedBorderColor ( 59, 58, 34, 48 ); + m_OptionButton->setArmedBorderColor ( 194, 202, 54, 0 ); + m_OptionButton->setUnArmedColor ( 143, 143, 54, 0 ); + m_OptionButton->setArmedColor ( 194, 202, 54, 0 ); + + m_CamButton = new DropDownButton( CHudTextMessage::BufferedLocaliseTextString( "#CAM_OPTIONS" ), ScreenWidth - ( XRES ( CAMOPTIONS_BUTTON_X ) + 15 ), YRES(6), XRES ( CAMOPTIONS_BUTTON_X ), YRES(20), false, false ); + m_CamButton->setParent( m_BottomBorder ); + m_CamButton->setContentAlignment( vgui::Label::a_center ); + m_CamButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_CamButton->addActionSignal( new CSpectatorHandler_Command( this, SPECTATOR_PANEL_CMD_CAMERA ) ); + m_CamButton->setUnArmedBorderColor ( 59, 58, 34, 48 ); + m_CamButton->setArmedBorderColor ( 194, 202, 54, 0 ); + m_CamButton->setUnArmedColor ( 143, 143, 54, 0 ); + m_CamButton->setArmedColor ( 194, 202, 54, 0 ); + +// m_PrevPlayerButton= new ColorButton("<", XRES( 15 + OPTIONS_BUTTON_X + 15 ), YRES(6), XRES(24), YRES(20), false, false ); + m_PrevPlayerButton= new CImageButton("arrowleft", XRES( 15 + OPTIONS_BUTTON_X + 15 ), YRES(6), XRES(24), YRES(20), false, false ); + m_PrevPlayerButton->setParent( m_BottomBorder ); + m_PrevPlayerButton->setContentAlignment( vgui::Label::a_center ); + m_PrevPlayerButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_PrevPlayerButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_PREVPLAYER) ); + m_PrevPlayerButton->setUnArmedBorderColor ( 59, 58, 34, 48 ); + m_PrevPlayerButton->setArmedBorderColor ( 194, 202, 54, 0 ); + m_PrevPlayerButton->setUnArmedColor ( 143, 143, 54, 0 ); + m_PrevPlayerButton->setArmedColor ( 194, 202, 54, 0 ); + +// m_NextPlayerButton= new ColorButton(">", (ScreenWidth - (XRES ( CAMOPTIONS_BUTTON_X ) + 15)) - XRES ( 24 + 15 ), YRES(6), XRES(24), YRES(20),false, false ); + m_NextPlayerButton= new CImageButton("arrowright", (ScreenWidth - (XRES ( CAMOPTIONS_BUTTON_X ) + 15)) - XRES ( 24 + 15 ), YRES(6), XRES(24), YRES(20),false, false ); + m_NextPlayerButton->setParent( m_BottomBorder ); + m_NextPlayerButton->setContentAlignment( vgui::Label::a_center ); + m_NextPlayerButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_NextPlayerButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_NEXTPLAYER) ); + m_NextPlayerButton->setUnArmedBorderColor ( 59, 58, 34, 48 ); + m_NextPlayerButton->setArmedBorderColor ( 194, 202, 54, 0 ); + m_NextPlayerButton->setUnArmedColor ( 143, 143, 54, 0 ); + m_NextPlayerButton->setArmedColor ( 194, 202, 54, 0 ); + + // Initialize the bottom title. + + float flLabelSize = ( (ScreenWidth - (XRES ( CAMOPTIONS_BUTTON_X ) + 15)) - XRES ( 24 + 15 ) ) - XRES( (15 + OPTIONS_BUTTON_X + 15) + 38 ); + + m_BottomMainButton = new DropDownButton("Spectator Bottom", + XRES( ( 15 + OPTIONS_BUTTON_X + 15 ) + 31 ), YRES(6), flLabelSize, YRES(20), + false, false ); + + m_BottomMainButton->setParent(m_BottomBorder); + m_BottomMainButton->setPaintBackgroundEnabled(false); + m_BottomMainButton->setFgColor( Scheme::sc_primary1 ); + m_BottomMainButton->setContentAlignment( vgui::Label::a_center ); + m_BottomMainButton->setBorder( new LineBorder( Color( 59, 58, 34, 48 ) ) ); + m_BottomMainButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_BottomMainButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_PLAYERS) ); + m_BottomMainButton->setUnArmedBorderColor ( 59, 58, 34, 48 ); + m_BottomMainButton->setArmedBorderColor ( 194, 202, 54, 0 ); + m_BottomMainButton->setUnArmedColor ( 143, 143, 54, 0 ); + m_BottomMainButton->setArmedColor ( 194, 202, 54, 0 ); + + + m_BottomMainLabel = new Label("Spectator Bottom", + XRES( ( 15 + OPTIONS_BUTTON_X + 15 ) + 31 ), YRES(6), flLabelSize, YRES(20)); + + m_BottomMainLabel->setParent(m_BottomBorder); + m_BottomMainLabel->setPaintBackgroundEnabled(false); + m_BottomMainLabel->setFgColor( Scheme::sc_primary1 ); + m_BottomMainLabel->setContentAlignment( vgui::Label::a_center ); + m_BottomMainLabel->setBorder( NULL ); + m_BottomMainLabel->setVisible(false); + + m_InsetViewButton = new ColorButton("", XRES(2), YRES(2), XRES(240), YRES(180), false, false ); + m_InsetViewButton->setParent( this ); + m_InsetViewButton->setBoundKey( (char)255 ); + m_InsetViewButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_TOGGLE_INSET) ); + m_InsetViewButton->setUnArmedBorderColor ( 59, 58, 34, 48 ); + m_InsetViewButton->setArmedBorderColor ( 194, 202, 54, 0 ); + m_InsetViewButton->setUnArmedColor ( 143, 143, 54, 0 ); + m_InsetViewButton->setArmedColor ( 194, 202, 54, 0 ); + + + m_menuVisible = false; + m_insetVisible = false; +// m_HideButton->setVisible(false); + m_CamButton->setVisible(false); + m_OptionButton->setVisible(false); + m_NextPlayerButton->setVisible(false); + m_PrevPlayerButton->setVisible(false); + m_TopBanner->setVisible( false ); + m_ExtraInfo->setVisible( false ); + m_Separator->setVisible( false ); + m_TimerImage->setVisible( false ); + +} + +void SpectatorPanel::ShowMenu(bool isVisible) +{ +// m_HideButton->setVisible(isVisible); m_HideButton->setArmed( false ); + m_OptionButton->setVisible(isVisible); m_OptionButton->setArmed( false ); + m_CamButton->setVisible(isVisible); m_CamButton->setArmed( false ); + m_NextPlayerButton->setVisible(isVisible); m_NextPlayerButton->setArmed( false ); + m_PrevPlayerButton->setVisible(isVisible); m_PrevPlayerButton->setArmed( false ); + + if ( !isVisible ) + { + int iLabelSizeX, iLabelSizeY; + m_BottomMainLabel->setVisible(true); + m_BottomMainButton->setVisible(false); + + m_BottomMainLabel->getSize( iLabelSizeX, iLabelSizeY ); + m_BottomMainLabel->setPos( ( ScreenWidth / 2 ) - (iLabelSizeX/2), YRES(6) ); + } + else + { + m_BottomMainButton->setPos( XRES( ( 15 + OPTIONS_BUTTON_X + 15 ) + 31 ), YRES(6) ); + m_BottomMainLabel->setVisible(false); + m_BottomMainButton->setVisible(true); + } + + if ( !isVisible ) + { + gViewPort->HideCommandMenu(); + + // if switching from visible menu to invisible menu, show help text + if ( m_menuVisible && this->isVisible() ) + { + char string[ 64 ]; + + _snprintf( string, sizeof( string ) - 1, "%c%s", HUD_PRINTCENTER, CHudTextMessage::BufferedLocaliseTextString( "#Spec_Duck" ) ); + string[ sizeof( string ) - 1 ] = '\0'; + + gHUD.m_TextMessage.MsgFunc_TextMsg( NULL, strlen( string ) + 1, string ); + } + } + + m_menuVisible = isVisible; + + gViewPort->UpdateCursorState(); +} + + +const char *GetSpectatorLabel ( int iMode ) +{ + switch ( iMode ) + { + case OBS_CHASE_LOCKED: + return "#OBS_CHASE_LOCKED"; + + case OBS_CHASE_FREE: + return "#OBS_CHASE_FREE"; + + case OBS_ROAMING: + return "#OBS_ROAMING"; + + case OBS_IN_EYE: + return "#OBS_IN_EYE"; + + case OBS_MAP_FREE: + return "#OBS_MAP_FREE"; + + case OBS_MAP_CHASE: + return "#OBS_MAP_CHASE"; + + case OBS_NONE: + default: + return "#OBS_NONE"; + } + + return ""; +} + +void SpectatorPanel::EnableInsetView(bool isEnabled) +{ + int x = gHUD.m_Spectator.m_OverviewData.insetWindowX; + int y = gHUD.m_Spectator.m_OverviewData.insetWindowY; + int wide = gHUD.m_Spectator.m_OverviewData.insetWindowWidth; + int tall = gHUD.m_Spectator.m_OverviewData.insetWindowHeight; + int offset = x + wide + 2; + + if ( isEnabled ) + { + // short black bar to see full inset + m_TopBorder->setBounds( XRES(offset), 0, XRES(640 - offset ), PANEL_HEIGHT ); + + if ( gEngfuncs.IsSpectateOnly() ) + { + m_TopBanner->setVisible( true ); + m_TopBanner->setPos( XRES(offset), 0 ); + } + else + m_TopBanner->setVisible( false ); + + m_InsetViewButton->setBounds( XRES( x -1 ), YRES( y ), + XRES( wide +2), YRES( tall ) ); + m_InsetViewButton->setVisible(true); + } + else + { + // full black bar, no inset border + // show banner only in real HLTV mode + if ( gEngfuncs.IsSpectateOnly() ) + { + m_TopBanner->setVisible( true ); + m_TopBanner->setPos( 0,0 ); + } + else + m_TopBanner->setVisible( false ); + + m_TopBorder->setBounds( 0, 0, ScreenWidth, PANEL_HEIGHT ); + + m_InsetViewButton->setVisible(false); + } + + m_insetVisible = isEnabled; + + Update(); + + m_CamButton->setText( CHudTextMessage::BufferedLocaliseTextString( GetSpectatorLabel( g_iUser1 ) ) ); +} + + + + +void SpectatorPanel::Update() +{ + int iTextWidth, iTextHeight; + int iTimeHeight, iTimeWidth; + int offset,j; + + if ( m_insetVisible ) + offset = gHUD.m_Spectator.m_OverviewData.insetWindowX + gHUD.m_Spectator.m_OverviewData.insetWindowWidth + 2; + else + offset = 0; + + bool visible = gHUD.m_Spectator.m_drawstatus->value != 0; + + m_ExtraInfo->setVisible( visible ); + m_TimerImage->setVisible( visible ); + m_CurrentTime->setVisible( visible ); + m_Separator->setVisible( visible ); + + for ( j= 0; j < TEAM_NUMBER; j++ ) + m_TeamScores[j]->setVisible( visible ); + + if ( !visible ) + return; + + m_ExtraInfo->getTextSize( iTextWidth, iTextHeight ); + m_CurrentTime->getTextSize( iTimeWidth, iTimeHeight ); + + iTimeWidth += XRES ( SEPERATOR_WIDTH*2 + 1 ); // +timer icon + iTimeWidth += ( SEPERATOR_WIDTH-(iTimeWidth%SEPERATOR_WIDTH) ); + + if ( iTimeWidth > iTextWidth ) + iTextWidth = iTimeWidth; + + int xPos = ScreenWidth - ( iTextWidth + XRES ( SEPERATOR_WIDTH + offset ) ); + + m_ExtraInfo->setBounds( xPos, YRES( SEPERATOR_HEIGHT ), iTextWidth, iTextHeight ); + + m_TimerImage->setBounds( xPos, YRES( SEPERATOR_HEIGHT ) + iTextHeight , XRES(SEPERATOR_WIDTH*2 + 1), YRES(SEPERATOR_HEIGHT + 1) ); + + m_CurrentTime->setBounds( xPos + XRES ( SEPERATOR_WIDTH*2 + 1 ), YRES( SEPERATOR_HEIGHT ) + iTextHeight , iTimeWidth, iTimeHeight ); + + m_Separator->setPos( ScreenWidth - ( iTextWidth + XRES ( 2*SEPERATOR_WIDTH+SEPERATOR_WIDTH/2+offset ) ) , YRES( 5 ) ); + m_Separator->setSize( XRES( 1 ), PANEL_HEIGHT - 10 ); + + for ( j= 0; j < TEAM_NUMBER; j++ ) + { + int iwidth, iheight; + + m_TeamScores[j]->getTextSize( iwidth, iheight ); + m_TeamScores[j]->setBounds( ScreenWidth - ( iTextWidth + XRES ( 2*SEPERATOR_WIDTH+2*SEPERATOR_WIDTH/2+offset ) + iwidth ), YRES( SEPERATOR_HEIGHT ) + ( iheight * j ), iwidth, iheight ); + } +} diff --git a/cl_dll/vgui_SpectatorPanel.h b/cl_dll/vgui_SpectatorPanel.h new file mode 100644 index 0000000..1bd6326 --- /dev/null +++ b/cl_dll/vgui_SpectatorPanel.h @@ -0,0 +1,112 @@ +// vgui_SpectatorPanel.h: interface for the SpectatorPanel class. +// +////////////////////////////////////////////////////////////////////// + +#ifndef SPECTATORPANEL_H +#define SPECTATORPANEL_H + +#include +#include +#include + +using namespace vgui; + +#define SPECTATOR_PANEL_CMD_NONE 0 + +#define SPECTATOR_PANEL_CMD_OPTIONS 1 +#define SPECTATOR_PANEL_CMD_PREVPLAYER 2 +#define SPECTATOR_PANEL_CMD_NEXTPLAYER 3 +#define SPECTATOR_PANEL_CMD_HIDEMENU 4 +#define SPECTATOR_PANEL_CMD_TOGGLE_INSET 5 +#define SPECTATOR_PANEL_CMD_CAMERA 6 +#define SPECTATOR_PANEL_CMD_PLAYERS 7 + +// spectator panel sizes +#define PANEL_HEIGHT 64 + +#define BANNER_WIDTH 256 +#define BANNER_HEIGHT 64 + +#define OPTIONS_BUTTON_X 96 +#define CAMOPTIONS_BUTTON_X 200 + + +#define SEPERATOR_WIDTH 15 +#define SEPERATOR_HEIGHT 15 + + +#define TEAM_NUMBER 2 + +class SpectatorPanel : public Panel //, public vgui::CDefaultInputSignal +{ + +public: + SpectatorPanel(int x,int y,int wide,int tall); + virtual ~SpectatorPanel(); + + void ActionSignal(int cmd); + + // InputSignal overrides. +public: + void Initialize(); + void Update(); + + + +public: + + void EnableInsetView(bool isEnabled); + void ShowMenu(bool isVisible); + + DropDownButton * m_OptionButton; +// CommandButton * m_HideButton; + //ColorButton * m_PrevPlayerButton; + //ColorButton * m_NextPlayerButton; + CImageButton * m_PrevPlayerButton; + CImageButton * m_NextPlayerButton; + DropDownButton * m_CamButton; + + CTransparentPanel * m_TopBorder; + CTransparentPanel * m_BottomBorder; + + ColorButton *m_InsetViewButton; + + DropDownButton *m_BottomMainButton; + CImageLabel *m_TimerImage; + Label *m_BottomMainLabel; + Label *m_CurrentTime; + Label *m_ExtraInfo; + Panel *m_Separator; + + Label *m_TeamScores[TEAM_NUMBER]; + + CImageLabel *m_TopBanner; + + bool m_menuVisible; + bool m_insetVisible; +}; + + + +class CSpectatorHandler_Command : public ActionSignal +{ + +private: + SpectatorPanel * m_pFather; + int m_cmd; + +public: + CSpectatorHandler_Command( SpectatorPanel * panel, int cmd ) + { + m_pFather = panel; + m_cmd = cmd; + } + + virtual void actionPerformed( Panel * panel ) + { + m_pFather->ActionSignal(m_cmd); + } +}; + + +#endif // !defined SPECTATORPANEL_H diff --git a/cl_dll/vgui_TeamFortressViewport.cpp b/cl_dll/vgui_TeamFortressViewport.cpp new file mode 100644 index 0000000..b0cdd28 --- /dev/null +++ b/cl_dll/vgui_TeamFortressViewport.cpp @@ -0,0 +1,2644 @@ +//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: Client DLL VGUI Viewport +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "parsemsg.h" +#include "pm_shared.h" +#include "keydefs.h" +#include "demo.h" +#include "demo_api.h" + +#include "vgui_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" +#include "vgui_ScorePanel.h" +#include "vgui_SpectatorPanel.h" + +#include "shake.h" +#include "screenfade.h" + +extern int g_iVisibleMouse; +class CCommandMenu; +int g_iPlayerClass; +int g_iTeamNumber; +int g_iUser1 = 0; +int g_iUser2 = 0; +int g_iUser3 = 0; + +// Scoreboard positions +#define SBOARD_INDENT_X XRES(104) +#define SBOARD_INDENT_Y YRES(40) + +// low-res scoreboard indents +#define SBOARD_INDENT_X_512 30 +#define SBOARD_INDENT_Y_512 30 + +#define SBOARD_INDENT_X_400 0 +#define SBOARD_INDENT_Y_400 20 + +void IN_ResetMouse( void ); +extern CMenuPanel *CMessageWindowPanel_Create( const char *szMOTD, const char *szTitle, int iShadeFullscreen, int iRemoveMe, int x, int y, int wide, int tall ); +extern float * GetClientColor( int clientIndex ); + +using namespace vgui; + +// Team Colors +int iNumberOfTeamColors = 5; +int iTeamColors[5][3] = +{ + { 255, 170, 0 }, // HL orange (default) + { 125, 165, 210 }, // Blue + { 200, 90, 70 }, // Red + { 225, 205, 45 }, // Yellow + { 145, 215, 140 }, // Green +}; + + +// Used for Class specific buttons +char *sTFClasses[] = +{ + "", + "SCOUT", + "SNIPER", + "SOLDIER", + "DEMOMAN", + "MEDIC", + "HWGUY", + "PYRO", + "SPY", + "ENGINEER", + "CIVILIAN", +}; + +char *sLocalisedClasses[] = +{ + "#Civilian", + "#Scout", + "#Sniper", + "#Soldier", + "#Demoman", + "#Medic", + "#HWGuy", + "#Pyro", + "#Spy", + "#Engineer", + "#Random", + "#Civilian", +}; + +char *sTFClassSelection[] = +{ + "civilian", + "scout", + "sniper", + "soldier", + "demoman", + "medic", + "hwguy", + "pyro", + "spy", + "engineer", + "randompc", + "civilian", +}; + +#ifdef _TFC +int iBuildingCosts[] = +{ + BUILD_COST_DISPENSER, + BUILD_COST_SENTRYGUN, + BUILD_COST_TELEPORTER +}; + +// This maps class numbers to the Invalid Class bit. +// This is needed for backwards compatability in maps that were finished before +// all the classes were in TF. Hence the wacky sequence. +int sTFValidClassInts[] = +{ + 0, + TF_ILL_SCOUT, + TF_ILL_SNIPER, + TF_ILL_SOLDIER, + TF_ILL_DEMOMAN, + TF_ILL_MEDIC, + TF_ILL_HVYWEP, + TF_ILL_PYRO, + TF_ILL_SPY, + TF_ILL_ENGINEER, + TF_ILL_RANDOMPC, +}; +#endif + +// Get the name of TGA file, based on GameDir +char* GetVGUITGAName(const char *pszName) +{ + int i; + char sz[256]; + static char gd[256]; + const char *gamedir; + + if (ScreenWidth < 640) + i = 320; + else + i = 640; + sprintf(sz, pszName, i); + + gamedir = gEngfuncs.pfnGetGameDirectory(); + sprintf(gd, "%s/gfx/vgui/%s.tga",gamedir,sz); + + return gd; +} + +//================================================================ +// COMMAND MENU +//================================================================ +void CCommandMenu::AddButton( CommandButton *pButton ) +{ + if (m_iButtons >= MAX_BUTTONS) + return; + + m_aButtons[m_iButtons] = pButton; + m_iButtons++; + pButton->setParent( this ); + pButton->setFont( Scheme::sf_primary3 ); + + // give the button a default key binding + if ( m_iButtons < 10 ) + { + pButton->setBoundKey( m_iButtons + '0' ); + } + else if ( m_iButtons == 10 ) + { + pButton->setBoundKey( '0' ); + } +} + +void CCommandMenu::RemoveAllButtons(void) +{ + /* + for(int i=0;iIsNotValid() ) + { + if ( m_aButtons[i]->getBoundKey() == keyNum ) + { + // hit the button + if ( m_aButtons[i]->GetSubMenu() ) + { + // open the sub menu + gViewPort->SetCurrentCommandMenu( m_aButtons[i]->GetSubMenu() ); + return false; + } + else + { + // run the bound command + m_aButtons[i]->fireActionSignal(); + return true; + } + } + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: clears the current menus buttons of any armed (highlighted) +// state, and all their sub buttons +//----------------------------------------------------------------------------- +void CCommandMenu::ClearButtonsOfArmedState( void ) +{ + for ( int i = 0; i < GetNumButtons(); i++ ) + { + m_aButtons[i]->setArmed( false ); + + if ( m_aButtons[i]->GetSubMenu() ) + { + m_aButtons[i]->GetSubMenu()->ClearButtonsOfArmedState(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pSubMenu - +// Output : CommandButton +//----------------------------------------------------------------------------- +CommandButton *CCommandMenu::FindButtonWithSubmenu( CCommandMenu *pSubMenu ) +{ + for ( int i = 0; i < GetNumButtons(); i++ ) + { + if ( m_aButtons[i]->GetSubMenu() == pSubMenu ) + return m_aButtons[i]; + } + + return NULL; +} + +// Recalculate the visible buttons +bool CCommandMenu::RecalculateVisibles( int iYOffset, bool bHideAll ) +{ + int i, iCurrentY = 0; + int iVisibleButtons = 0; + + // Cycle through all the buttons in this menu, and see which will be visible + for (i = 0; i < m_iButtons; i++) + { + int iClass = m_aButtons[i]->GetPlayerClass(); + + if ( (iClass && iClass != g_iPlayerClass ) || ( m_aButtons[i]->IsNotValid() ) || bHideAll ) + { + m_aButtons[i]->setVisible( false ); + if ( m_aButtons[i]->GetSubMenu() != NULL ) + { + (m_aButtons[i]->GetSubMenu())->RecalculateVisibles( 0, true ); + } + } + else + { + // If it's got a submenu, force it to check visibilities + if ( m_aButtons[i]->GetSubMenu() != NULL ) + { + if ( !(m_aButtons[i]->GetSubMenu())->RecalculateVisibles( 0 , false ) ) + { + // The submenu had no visible buttons, so don't display this button + m_aButtons[i]->setVisible( false ); + continue; + } + } + + m_aButtons[i]->setVisible( true ); + iVisibleButtons++; + } + } + + // Set Size + setSize( _size[0], (iVisibleButtons * (m_flButtonSizeY-1)) + 1 ); + + if ( iYOffset ) + { + m_iYOffset = iYOffset; + } + + for (i = 0; i < m_iButtons; i++) + { + if ( m_aButtons[i]->isVisible() ) + { + if ( m_aButtons[i]->GetSubMenu() != NULL ) + (m_aButtons[i]->GetSubMenu())->RecalculateVisibles( iCurrentY + m_iYOffset, false ); + + + // Make sure it's at the right Y position + // m_aButtons[i]->getPos( iXPos, iYPos ); + + if ( m_iDirection ) + { + m_aButtons[i]->setPos( 0, (iVisibleButtons-1) * (m_flButtonSizeY-1) - iCurrentY ); + } + else + { + m_aButtons[i]->setPos( 0, iCurrentY ); + } + + iCurrentY += (m_flButtonSizeY-1); + } + } + + return iVisibleButtons?true:false; +} + +// Make sure all submenus can fit on the screen +void CCommandMenu::RecalculatePositions( int iYOffset ) +{ + int iTop; + int iAdjust = 0; + + m_iYOffset+= iYOffset; + + if ( m_iDirection ) + iTop = ScreenHeight - (m_iYOffset + _size[1] ); + else + iTop = m_iYOffset; + + if ( iTop < 0 ) + iTop = 0; + + // Calculate if this is going to fit onscreen, and shuffle it up if it won't + int iBottom = iTop + _size[1]; + + if ( iBottom > ScreenHeight ) + { + // Move in increments of button sizes + while (iAdjust < (iBottom - ScreenHeight)) + { + iAdjust += m_flButtonSizeY - 1; + } + + iTop -= iAdjust; + + // Make sure it doesn't move off the top of the screen (the menu's too big to fit it all) + if ( iTop < 0 ) + { + iAdjust -= (0 - iTop); + iTop = 0; + } + } + + setPos( _pos[0], iTop ); + + // We need to force all menus below this one to update their positions now, because they + // might have submenus riding off buttons in this menu that have just shifted. + for (int i = 0; i < m_iButtons; i++) + m_aButtons[i]->UpdateSubMenus( iAdjust ); +} + + +// Make this menu and all menus above it in the chain visible +void CCommandMenu::MakeVisible( CCommandMenu *pChildMenu ) +{ +/* + // Push down the button leading to the child menu + for (int i = 0; i < m_iButtons; i++) + { + if ( (pChildMenu != NULL) && (m_aButtons[i]->GetSubMenu() == pChildMenu) ) + { + m_aButtons[i]->setArmed( true ); + } + else + { + m_aButtons[i]->setArmed( false ); + } + } +*/ + + setVisible(true); + if (m_pParentMenu) + m_pParentMenu->MakeVisible( this ); +} + +//================================================================ +// CreateSubMenu +CCommandMenu *TeamFortressViewport::CreateSubMenu( CommandButton *pButton, CCommandMenu *pParentMenu, int iYOffset, int iXOffset ) +{ + int iXPos = 0; + int iYPos = 0; + int iWide = CMENU_SIZE_X; + int iTall = 0; + int iDirection = 0; + + if (pParentMenu) + { + iXPos = m_pCurrentCommandMenu->GetXOffset() + (CMENU_SIZE_X - 1) + iXOffset; + iYPos = m_pCurrentCommandMenu->GetYOffset() + iYOffset; + iDirection = pParentMenu->GetDirection(); + } + + CCommandMenu *pMenu = new CCommandMenu(pParentMenu, iDirection, iXPos, iYPos, iWide, iTall ); + pMenu->setParent(this); + pButton->AddSubMenu( pMenu ); + pButton->setFont( Scheme::sf_primary3 ); + pMenu->m_flButtonSizeY = m_pCurrentCommandMenu->m_flButtonSizeY; + + // Create the Submenu-open signal + InputSignal *pISignal = new CMenuHandler_PopupSubMenuInput(pButton, pMenu); + pButton->addInputSignal(pISignal); + + // Put a > to show it's a submenu + CImageLabel *pLabel = new CImageLabel( "arrowright", XRES(CMENU_SIZE_X - SUBMENU_SIZE_X), YRES(SUBMENU_SIZE_Y) ); + pLabel->setParent(pButton); + pLabel->addInputSignal(pISignal); + + // Reposition + pLabel->getPos( iXPos, iYPos ); + pLabel->setPos( pButton->getWide() - pLabel->getImageWide()-4, -4 ); + + // Create the mouse off signal for the Label too + if (!pButton->m_bNoHighlight) + pLabel->addInputSignal( new CHandler_CommandButtonHighlight(pButton) ); + + return pMenu; +} + +//----------------------------------------------------------------------------- +// Purpose: Makes sure the memory allocated for TeamFortressViewport is nulled out +// Input : stAllocateBlock - +// Output : void * +//----------------------------------------------------------------------------- +void *TeamFortressViewport::operator new( size_t stAllocateBlock ) +{ +// void *mem = Panel::operator new( stAllocateBlock ); + void *mem = ::operator new( stAllocateBlock ); + memset( mem, 0, stAllocateBlock ); + return mem; +} + +//----------------------------------------------------------------------------- +// Purpose: InputSignal handler for the main viewport +//----------------------------------------------------------------------------- +class CViewPortInputHandler : public InputSignal +{ +public: + bool bPressed; + + CViewPortInputHandler() + { + } + + virtual void cursorMoved(int x,int y,Panel* panel) {} + virtual void cursorEntered(Panel* panel) {} + virtual void cursorExited(Panel* panel) {} + virtual void mousePressed(MouseCode code,Panel* panel) + { + if ( code != MOUSE_LEFT ) + { + // send a message to close the command menu + // this needs to be a message, since a direct call screws the timing + gEngfuncs.pfnClientCmd( "ForceCloseCommandMenu\n" ); + } + } + virtual void mouseReleased(MouseCode code,Panel* panel) + { + } + + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {} + virtual void mouseWheeled(int delta,Panel* panel) {} + virtual void keyPressed(KeyCode code,Panel* panel) {} + virtual void keyTyped(KeyCode code,Panel* panel) {} + virtual void keyReleased(KeyCode code,Panel* panel) {} + virtual void keyFocusTicked(Panel* panel) {} +}; + + +//================================================================ +TeamFortressViewport::TeamFortressViewport(int x,int y,int wide,int tall) : Panel(x,y,wide,tall), m_SchemeManager(wide,tall) +{ + gViewPort = this; + m_iInitialized = false; + m_pTeamMenu = NULL; + m_pClassMenu = NULL; + m_pScoreBoard = NULL; + m_pSpectatorPanel = NULL; + m_pCurrentMenu = NULL; + m_pCurrentCommandMenu = NULL; + + Initialize(); + addInputSignal( new CViewPortInputHandler ); + + int r, g, b, a; + + Scheme* pScheme = App::getInstance()->getScheme(); + + // primary text color + // Get the colors + //!! two different types of scheme here, need to integrate + SchemeHandle_t hPrimaryScheme = m_SchemeManager.getSchemeHandle( "Primary Button Text" ); + { + // font + pScheme->setFont( Scheme::sf_primary1, m_SchemeManager.getFont(hPrimaryScheme) ); + + // text color + m_SchemeManager.getFgColor( hPrimaryScheme, r, g, b, a ); + pScheme->setColor(Scheme::sc_primary1, r, g, b, a ); // sc_primary1 is non-transparent orange + + // background color (transparent black) + m_SchemeManager.getBgColor( hPrimaryScheme, r, g, b, a ); + pScheme->setColor(Scheme::sc_primary3, r, g, b, a ); + + // armed foreground color + m_SchemeManager.getFgArmedColor( hPrimaryScheme, r, g, b, a ); + pScheme->setColor(Scheme::sc_secondary2, r, g, b, a ); + + // armed background color + m_SchemeManager.getBgArmedColor( hPrimaryScheme, r, g, b, a ); + pScheme->setColor(Scheme::sc_primary2, r, g, b, a ); + + //!! need to get this color from scheme file + // used for orange borders around buttons + m_SchemeManager.getBorderColor( hPrimaryScheme, r, g, b, a ); + // pScheme->setColor(Scheme::sc_secondary1, r, g, b, a ); + pScheme->setColor(Scheme::sc_secondary1, 255*0.7, 170*0.7, 0, 0); + } + + // Change the second primary font (used in the scoreboard) + SchemeHandle_t hScoreboardScheme = m_SchemeManager.getSchemeHandle( "Scoreboard Text" ); + { + pScheme->setFont(Scheme::sf_primary2, m_SchemeManager.getFont(hScoreboardScheme) ); + } + + // Change the third primary font (used in command menu) + SchemeHandle_t hCommandMenuScheme = m_SchemeManager.getSchemeHandle( "CommandMenu Text" ); + { + pScheme->setFont(Scheme::sf_primary3, m_SchemeManager.getFont(hCommandMenuScheme) ); + } + + App::getInstance()->setScheme(pScheme); + + // VGUI MENUS + CreateTeamMenu(); + CreateClassMenu(); + CreateSpectatorMenu(); + CreateScoreBoard(); + // Init command menus + m_iNumMenus = 0; + m_iCurrentTeamNumber = m_iUser1 = m_iUser2 = m_iUser3 = 0; + + m_StandardMenu = CreateCommandMenu("commandmenu.txt", 0, CMENU_TOP, false, CMENU_SIZE_X, BUTTON_SIZE_Y, 0 ); + m_SpectatorOptionsMenu = CreateCommandMenu("spectatormenu.txt", 1, PANEL_HEIGHT, true, CMENU_SIZE_X, BUTTON_SIZE_Y / 2, 0 ); // above bottom bar, flat design + m_SpectatorCameraMenu = CreateCommandMenu("spectcammenu.txt", 1, PANEL_HEIGHT, true, XRES( 200 ), BUTTON_SIZE_Y / 2, ScreenWidth - ( XRES ( 200 ) + 15 ) ); // above bottom bar, flat design + + m_PlayerMenu = m_iNumMenus; + m_iNumMenus++; + + float flLabelSize = ( (ScreenWidth - (XRES ( CAMOPTIONS_BUTTON_X ) + 15)) - XRES ( 24 + 15 ) ) - XRES( (15 + OPTIONS_BUTTON_X + 15) + 38 ); + + m_pCommandMenus[m_PlayerMenu] = new CCommandMenu(NULL, 1, + XRES( ( 15 + OPTIONS_BUTTON_X + 15 ) + 31 ),PANEL_HEIGHT, flLabelSize,300); + m_pCommandMenus[m_PlayerMenu]->setParent(this); + m_pCommandMenus[m_PlayerMenu]->setVisible(false); + m_pCommandMenus[m_PlayerMenu]->m_flButtonSizeY = BUTTON_SIZE_Y /2; + m_pCommandMenus[m_PlayerMenu]->m_iSpectCmdMenu = 1; + + UpdatePlayerMenu(m_PlayerMenu); + + CreateServerBrowser(); + + +} + +//----------------------------------------------------------------------------- +// Purpose: Called everytime a new level is started. Viewport clears out it's data. +//----------------------------------------------------------------------------- +void TeamFortressViewport::Initialize( void ) +{ + // Force each menu to Initialize + if (m_pTeamMenu) + { + m_pTeamMenu->Initialize(); + } + if (m_pClassMenu) + { + m_pClassMenu->Initialize(); + } + if (m_pScoreBoard) + { + m_pScoreBoard->Initialize(); + HideScoreBoard(); + } + if (m_pSpectatorPanel) + { + // Spectator menu doesn't need initializing + m_pSpectatorPanel->setVisible( false ); + } + + // Make sure all menus are hidden + HideVGUIMenu(); + HideCommandMenu(); + + // Clear out some data + m_iGotAllMOTD = true; + m_iRandomPC = false; + m_flScoreBoardLastUpdated = 0; + m_flSpectatorPanelLastUpdated = 0; + + // reset player info + g_iPlayerClass = 0; + g_iTeamNumber = 0; + + strcpy(m_sMapName, ""); + strcpy(m_szServerName, ""); + for (int i = 0; i < 5; i++) + { + m_iValidClasses[i] = 0; + strcpy(m_sTeamNames[i], ""); + } + + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::scu_none) ); +} + +class CException; +//----------------------------------------------------------------------------- +// Purpose: Read the Command Menu structure from the txt file and create the menu. +// Returns Index of menu in m_pCommandMenus +//----------------------------------------------------------------------------- +int TeamFortressViewport::CreateCommandMenu( char * menuFile, int direction, int yOffset, bool flatDesign, float flButtonSizeX, float flButtonSizeY, int xOffset ) +{ + // COMMAND MENU + // Create the root of this new Command Menu + + int newIndex = m_iNumMenus; + + m_pCommandMenus[newIndex] = new CCommandMenu(NULL, direction, xOffset, yOffset, flButtonSizeX, 300); // This will be resized once we know how many items are in it + m_pCommandMenus[newIndex]->setParent(this); + m_pCommandMenus[newIndex]->setVisible(false); + m_pCommandMenus[newIndex]->m_flButtonSizeY = flButtonSizeY; + m_pCommandMenus[newIndex]->m_iSpectCmdMenu = direction; + + m_iNumMenus++; + + // Read Command Menu from the txt file + char token[1024]; + char *pfile = (char*)gEngfuncs.COM_LoadFile( menuFile, 5, NULL); + if (!pfile) + { + gEngfuncs.Con_DPrintf( "Unable to open %s\n", menuFile); + SetCurrentCommandMenu( NULL ); + return newIndex; + } + +#ifdef _WIN32 +try +{ +#endif + // First, read in the localisation strings + + // Detpack strings + gHUD.m_TextMessage.LocaliseTextString( "#DetpackSet_For5Seconds", m_sDetpackStrings[0], MAX_BUTTON_SIZE ); + gHUD.m_TextMessage.LocaliseTextString( "#DetpackSet_For20Seconds", m_sDetpackStrings[1], MAX_BUTTON_SIZE ); + gHUD.m_TextMessage.LocaliseTextString( "#DetpackSet_For50Seconds", m_sDetpackStrings[2], MAX_BUTTON_SIZE ); + + // Now start parsing the menu structure + m_pCurrentCommandMenu = m_pCommandMenus[newIndex]; + char szLastButtonText[32] = "file start"; + pfile = gEngfuncs.COM_ParseFile(pfile, token); + while ( ( strlen ( token ) > 0 ) && ( m_iNumMenus < MAX_MENUS ) ) + { + // Keep looping until we hit the end of this menu + while ( token[0] != '}' && ( strlen( token ) > 0 ) ) + { + char cText[32] = ""; + char cBoundKey[32] = ""; + char cCustom[32] = ""; + static const int cCommandLength = 128; + char cCommand[cCommandLength] = ""; + char szMap[MAX_MAPNAME] = ""; + int iPlayerClass = 0; + int iCustom = false; + int iTeamOnly = -1; + int iToggle = 0; + int iButtonY; + bool bGetExtraToken = true; + CommandButton *pButton = NULL; + + // We should never be here without a Command Menu + if (!m_pCurrentCommandMenu) + { + gEngfuncs.Con_Printf("Error in %s file after '%s'.\n",menuFile, szLastButtonText ); + m_iInitialized = false; + return newIndex; + } + + // token should already be the bound key, or the custom name + strncpy( cCustom, token, 32 ); + cCustom[31] = '\0'; + + // See if it's a custom button + if (!strcmp(cCustom, "CUSTOM") ) + { + iCustom = true; + + // Get the next token + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + // See if it's a map + else if (!strcmp(cCustom, "MAP") ) + { + // Get the mapname + pfile = gEngfuncs.COM_ParseFile(pfile, token); + strncpy( szMap, token, MAX_MAPNAME ); + szMap[MAX_MAPNAME-1] = '\0'; + + // Get the next token + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + else if ( !strncmp(cCustom, "TEAM", 4) ) // TEAM1, TEAM2, TEAM3, TEAM4 + { + // make it a team only button + iTeamOnly = atoi( cCustom + 4 ); + + // Get the next token + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + else if ( !strncmp(cCustom, "TOGGLE", 6) ) + { + iToggle = true; + // Get the next token + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + else + { + // See if it's a Class +#ifdef _TFC + for (int i = 1; i <= PC_ENGINEER; i++) + { + if ( !strcmp(token, sTFClasses[i]) ) + { + // Save it off + iPlayerClass = i; + + // Get the button text + pfile = gEngfuncs.COM_ParseFile(pfile, token); + break; + } + } +#endif + } + + // Get the button bound key + strncpy( cBoundKey, token, 32 ); + cText[31] = '\0'; + + // Get the button text + pfile = gEngfuncs.COM_ParseFile(pfile, token); + strncpy( cText, token, 32 ); + cText[31] = '\0'; + + // save off the last button text we've come across (for error reporting) + strcpy( szLastButtonText, cText ); + + // Get the button command + pfile = gEngfuncs.COM_ParseFile(pfile, token); + strncpy( cCommand, token, cCommandLength ); + cCommand[cCommandLength - 1] = '\0'; + + iButtonY = (BUTTON_SIZE_Y-1) * m_pCurrentCommandMenu->GetNumButtons(); + + // Custom button handling + if ( iCustom ) + { + pButton = CreateCustomButton( cText, cCommand, iButtonY ); + + // Get the next token to see if we're a menu + pfile = gEngfuncs.COM_ParseFile(pfile, token); + + if ( token[0] == '{' ) + { + strcpy( cCommand, token ); + } + else + { + bGetExtraToken = false; + } + } + else if ( szMap[0] != '\0' ) + { + // create a map button + pButton = new MapButton(szMap, cText, xOffset, iButtonY, flButtonSizeX, flButtonSizeY ); + } + else if ( iTeamOnly != -1) + { + // button that only shows up if the player is on team iTeamOnly + pButton = new TeamOnlyCommandButton( iTeamOnly, cText, xOffset, iButtonY, flButtonSizeX, flButtonSizeY, flatDesign ); + } + else if ( iToggle && direction == 0 ) + { + pButton = new ToggleCommandButton( cCommand, cText, xOffset, iButtonY, flButtonSizeX, flButtonSizeY, flatDesign ); + } + else if ( direction == 1 ) + { + if ( iToggle ) + pButton = new SpectToggleButton( cCommand, cText, xOffset, iButtonY, flButtonSizeX, flButtonSizeY, flatDesign ); + else + pButton = new SpectButton( iPlayerClass, cText, xOffset, iButtonY, flButtonSizeX, flButtonSizeY ); + } + else + { + // normal button + pButton = new CommandButton( iPlayerClass, cText, xOffset, iButtonY, flButtonSizeX, flButtonSizeY, flatDesign ); + } + + // add the button into the command menu + if ( pButton ) + { + m_pCurrentCommandMenu->AddButton( pButton ); + pButton->setBoundKey( cBoundKey[0] ); + pButton->setParentMenu( m_pCurrentCommandMenu ); + + // Override font in CommandMenu + pButton->setFont( Scheme::sf_primary3 ); + } + + // Find out if it's a submenu or a button we're dealing with + if ( cCommand[0] == '{' ) + { + if ( m_iNumMenus >= MAX_MENUS ) + { + gEngfuncs.Con_Printf( "Too many menus in %s past '%s'\n",menuFile, szLastButtonText ); + } + else + { + // Create the menu + m_pCommandMenus[m_iNumMenus] = CreateSubMenu(pButton, m_pCurrentCommandMenu, iButtonY ); + m_pCurrentCommandMenu = m_pCommandMenus[m_iNumMenus]; + m_iNumMenus++; + } + } + else if ( !iCustom ) + { + // Create the button and attach it to the current menu + if ( iToggle ) + pButton->addActionSignal(new CMenuHandler_ToggleCvar(cCommand)); + else + pButton->addActionSignal(new CMenuHandler_StringCommand(cCommand)); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + + // Get the next token + if ( bGetExtraToken ) + { + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + } + + // Move back up a menu + m_pCurrentCommandMenu = m_pCurrentCommandMenu->GetParentMenu(); + + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } +#ifdef _WIN32 +} +catch( CException *e ) +{ + e; + //e->Delete(); + e = NULL; + m_iInitialized = false; + return newIndex; +} +#endif + + SetCurrentMenu( NULL ); + SetCurrentCommandMenu( NULL ); + gEngfuncs.COM_FreeFile( pfile ); + + m_iInitialized = true; + return newIndex; +} + +//----------------------------------------------------------------------------- +// Purpose: Creates all the class choices under a spy's disguise menus, and +// maps a command to them +// Output : CCommandMenu +//----------------------------------------------------------------------------- +CCommandMenu *TeamFortressViewport::CreateDisguiseSubmenu( CommandButton *pButton, CCommandMenu *pParentMenu, const char *commandText, int iYOffset, int iXOffset ) +{ + // create the submenu, under which the class choices will be listed + CCommandMenu *pMenu = CreateSubMenu( pButton, pParentMenu, iYOffset, iXOffset ); + m_pCommandMenus[m_iNumMenus] = pMenu; + m_iNumMenus++; + + // create the class choice buttons +#ifdef _TFC + for ( int i = PC_SCOUT; i <= PC_ENGINEER; i++ ) + { + CommandButton *pDisguiseButton = new CommandButton( CHudTextMessage::BufferedLocaliseTextString( sLocalisedClasses[i] ), 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y ); + + char sz[256]; + sprintf(sz, "%s %d", commandText, i ); + pDisguiseButton->addActionSignal(new CMenuHandler_StringCommand(sz)); + + pMenu->AddButton( pDisguiseButton ); + } +#endif + + return pMenu; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pButtonText - +// *pButtonName - +// Output : CommandButton +//----------------------------------------------------------------------------- +CommandButton *TeamFortressViewport::CreateCustomButton( char *pButtonText, char *pButtonName, int iYOffset ) +{ + CommandButton *pButton = NULL; + CCommandMenu *pMenu = NULL; + + // ChangeTeam + if ( !strcmp( pButtonName, "!CHANGETEAM" ) ) + { + // ChangeTeam Submenu + pButton = new CommandButton(pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + + // Create the submenu + pMenu = CreateSubMenu(pButton, m_pCurrentCommandMenu, iYOffset ); + m_pCommandMenus[m_iNumMenus] = pMenu; + m_iNumMenus++; + + // ChangeTeam buttons + for (int i = 0; i < 4; i++) + { + char sz[256]; + sprintf(sz, "jointeam %d", i+1); + m_pTeamButtons[i] = new TeamButton(i+1, "teamname", 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + m_pTeamButtons[i]->addActionSignal(new CMenuHandler_StringCommandWatch( sz )); + pMenu->AddButton( m_pTeamButtons[i] ); + } + + // Auto Assign button + m_pTeamButtons[4] = new TeamButton(5, gHUD.m_TextMessage.BufferedLocaliseTextString( "#Team_AutoAssign" ), 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + m_pTeamButtons[4]->addActionSignal(new CMenuHandler_StringCommand( "jointeam 5" )); + pMenu->AddButton( m_pTeamButtons[4] ); + + // Spectate button + m_pTeamButtons[5] = new SpectateButton( CHudTextMessage::BufferedLocaliseTextString( "#Menu_Spectate" ), 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y, false); + m_pTeamButtons[5]->addActionSignal(new CMenuHandler_StringCommand( "spectate" )); + pMenu->AddButton( m_pTeamButtons[5] ); + } + // ChangeClass + else if ( !strcmp( pButtonName, "!CHANGECLASS" ) ) + { + // Create the Change class menu + pButton = new ClassButton(-1, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y, false); + + // ChangeClass Submenu + pMenu = CreateSubMenu(pButton, m_pCurrentCommandMenu, iYOffset ); + m_pCommandMenus[m_iNumMenus] = pMenu; + m_iNumMenus++; + +#ifdef _TFC + for (int i = PC_SCOUT; i <= PC_RANDOM; i++ ) + { + char sz[256]; + + // ChangeClass buttons + CHudTextMessage::LocaliseTextString( sLocalisedClasses[i], sz, 256 ); + ClassButton *pClassButton = new ClassButton( i, sz, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y, false); + + sprintf(sz, "%s", sTFClassSelection[i]); + pClassButton->addActionSignal(new CMenuHandler_StringCommandClassSelect(sz)); + pMenu->AddButton( pClassButton ); + } +#endif + } +#ifdef _TFC + // Map Briefing + else if ( !strcmp( pButtonName, "!MAPBRIEFING" ) ) + { + pButton = new CommandButton(pButtonText, 0, BUTTON_SIZE_Y * m_pCurrentCommandMenu->GetNumButtons(), CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_TextWindow(MENU_MAPBRIEFING)); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Class Descriptions + else if ( !strcmp( pButtonName, "!CLASSDESC" ) ) + { + pButton = new ClassButton(0, pButtonText, 0, BUTTON_SIZE_Y * m_pCurrentCommandMenu->GetNumButtons(), CMENU_SIZE_X, BUTTON_SIZE_Y, false); + pButton->addActionSignal(new CMenuHandler_TextWindow(MENU_CLASSHELP)); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!SERVERINFO" ) ) + { + pButton = new ClassButton(0, pButtonText, 0, BUTTON_SIZE_Y * m_pCurrentCommandMenu->GetNumButtons(), CMENU_SIZE_X, BUTTON_SIZE_Y, false); + pButton->addActionSignal(new CMenuHandler_TextWindow(MENU_INTRO)); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Spy abilities + else if ( !strcmp( pButtonName, "!SPY" ) ) + { + pButton = new DisguiseButton( 0, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y ); + } + // Feign + else if ( !strcmp( pButtonName, "!FEIGN" ) ) + { + pButton = new FeignButton(FALSE, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand( "feign" )); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Feign Silently + else if ( !strcmp( pButtonName, "!FEIGNSILENT" ) ) + { + pButton = new FeignButton(FALSE, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand( "sfeign" )); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Stop Feigning + else if ( !strcmp( pButtonName, "!FEIGNSTOP" ) ) + { + pButton = new FeignButton(TRUE, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand( "feign" )); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Disguise + else if ( !strcmp( pButtonName, "!DISGUISEENEMY" ) ) + { + // Create the disguise enemy button, which active only if there are 2 teams + pButton = new DisguiseButton(DISGUISE_TEAM2, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + CreateDisguiseSubmenu( pButton, m_pCurrentCommandMenu, "disguise_enemy", iYOffset); + } + else if ( !strcmp( pButtonName, "!DISGUISEFRIENDLY" ) ) + { + // Create the disguise friendly button, which active only if there are 1 or 2 teams + pButton = new DisguiseButton(DISGUISE_TEAM1 | DISGUISE_TEAM2, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + CreateDisguiseSubmenu( pButton, m_pCurrentCommandMenu, "disguise_friendly", iYOffset ); + } + else if ( !strcmp( pButtonName, "!DISGUISE" ) ) + { + // Create the Disguise button + pButton = new DisguiseButton( DISGUISE_TEAM3 | DISGUISE_TEAM4, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + CCommandMenu *pDisguiseMenu = CreateSubMenu( pButton, m_pCurrentCommandMenu, iYOffset ); + m_pCommandMenus[m_iNumMenus] = pDisguiseMenu; + m_iNumMenus++; + + // Disguise Enemy submenu buttons + for ( int i = 1; i <= 4; i++ ) + { + // only show the 4th disguise button if we have 4 teams + m_pDisguiseButtons[i] = new DisguiseButton( ((i < 4) ? DISGUISE_TEAM3 : 0) | DISGUISE_TEAM4, "Disguise", 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + + pDisguiseMenu->AddButton( m_pDisguiseButtons[i] ); + m_pDisguiseButtons[i]->setParentMenu( pDisguiseMenu ); + + char sz[256]; + sprintf( sz, "disguise %d", i ); + CreateDisguiseSubmenu( m_pDisguiseButtons[i], pDisguiseMenu, sz, iYOffset, CMENU_SIZE_X - 1 ); + } + } + // Start setting a Detpack + else if ( !strcmp( pButtonName, "!DETPACKSTART" ) ) + { + // Detpack Submenu + pButton = new DetpackButton(2, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + + // Create the submenu + pMenu = CreateSubMenu(pButton, m_pCurrentCommandMenu, iYOffset ); + m_pCommandMenus[m_iNumMenus] = pMenu; + m_iNumMenus++; + + // Set detpack buttons + CommandButton *pDetButton; + pDetButton = new CommandButton(m_sDetpackStrings[0], 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pDetButton->addActionSignal(new CMenuHandler_StringCommand("detstart 5")); + pMenu->AddButton( pDetButton ); + pDetButton = new CommandButton(m_sDetpackStrings[1], 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pDetButton->addActionSignal(new CMenuHandler_StringCommand("detstart 20")); + pMenu->AddButton( pDetButton ); + pDetButton = new CommandButton(m_sDetpackStrings[2], 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pDetButton->addActionSignal(new CMenuHandler_StringCommand("detstart 50")); + pMenu->AddButton( pDetButton ); + } + // Stop setting a Detpack + else if ( !strcmp( pButtonName, "!DETPACKSTOP" ) ) + { + pButton = new DetpackButton(1, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand( "detstop" )); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Engineer building + else if ( !strcmp( pButtonName, "!BUILD" ) ) + { + // only appears if the player is an engineer, and either they have built something or have enough metal to build + pButton = new BuildButton( BUILDSTATE_BASE, 0, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + } + else if ( !strcmp( pButtonName, "!BUILDSENTRY" ) ) + { + pButton = new BuildButton( BUILDSTATE_CANBUILD, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("build 2")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!BUILDDISPENSER" ) ) + { + pButton = new BuildButton( BUILDSTATE_CANBUILD, BuildButton::DISPENSER, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("build 1")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!ROTATESENTRY180" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("rotatesentry180")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!ROTATESENTRY" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("rotatesentry")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DISMANTLEDISPENSER" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::DISPENSER, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("dismantle 1")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DISMANTLESENTRY" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("dismantle 2")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DETONATEDISPENSER" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::DISPENSER, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("detdispenser")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DETONATESENTRY" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("detsentry")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!BUILDENTRYTELEPORTER" ) ) + { + pButton = new BuildButton( BUILDSTATE_CANBUILD, BuildButton::ENTRY_TELEPORTER, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("build 4")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DISMANTLEENTRYTELEPORTER" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::ENTRY_TELEPORTER, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("dismantle 4")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DETONATEENTRYTELEPORTER" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::ENTRY_TELEPORTER, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("detentryteleporter")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!BUILDEXITTELEPORTER" ) ) + { + pButton = new BuildButton( BUILDSTATE_CANBUILD, BuildButton::EXIT_TELEPORTER, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("build 5")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DISMANTLEEXITTELEPORTER" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::EXIT_TELEPORTER, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("dismantle 5")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DETONATEEXITTELEPORTER" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::EXIT_TELEPORTER, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("detexitteleporter")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Stop building + else if ( !strcmp( pButtonName, "!BUILDSTOP" ) ) + { + pButton = new BuildButton( BUILDSTATE_BUILDING, 0, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("build")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } +#endif + + return pButton; +} + +void TeamFortressViewport::ToggleServerBrowser() +{ + if (!m_iInitialized) + return; + + if ( !m_pServerBrowser ) + return; + + if ( m_pServerBrowser->isVisible() ) + { + m_pServerBrowser->setVisible( false ); + } + else + { + m_pServerBrowser->setVisible( true ); + } + + UpdateCursorState(); +} + +//======================================================================= +void TeamFortressViewport::ShowCommandMenu(int menuIndex) +{ + if (!m_iInitialized) + return; + + //Already have a menu open. + if ( m_pCurrentMenu ) + return; + + // is the command menu open? + if ( m_pCurrentCommandMenu == m_pCommandMenus[menuIndex] ) + { + HideCommandMenu(); + return; + } + + // Not visible while in intermission + if ( gHUD.m_iIntermission ) + return; + + // Recalculate visible menus + UpdateCommandMenu( menuIndex ); + HideVGUIMenu(); + + SetCurrentCommandMenu( m_pCommandMenus[menuIndex] ); + m_flMenuOpenTime = gHUD.m_flTime; + UpdateCursorState(); + + // get command menu parameters + for ( int i = 2; i < gEngfuncs.Cmd_Argc(); i++ ) + { + const char *param = gEngfuncs.Cmd_Argv( i - 1 ); + if ( param ) + { + if ( m_pCurrentCommandMenu->KeyInput(param[0]) ) + { + // kill the menu open time, since the key input is final + HideCommandMenu(); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handles the key input of "-commandmenu" +// Input : +//----------------------------------------------------------------------------- +void TeamFortressViewport::InputSignalHideCommandMenu() +{ + if (!m_iInitialized) + return; + + // if they've just tapped the command menu key, leave it open + if ( (m_flMenuOpenTime + 0.3) > gHUD.m_flTime ) + return; + + HideCommandMenu(); +} + +//----------------------------------------------------------------------------- +// Purpose: Hides the command menu +//----------------------------------------------------------------------------- +void TeamFortressViewport::HideCommandMenu() +{ + if (!m_iInitialized) + return; + + if ( m_pCommandMenus[m_StandardMenu] ) + { + m_pCommandMenus[m_StandardMenu]->ClearButtonsOfArmedState(); + } + + if ( m_pCommandMenus[m_SpectatorOptionsMenu] ) + { + m_pCommandMenus[m_SpectatorOptionsMenu]->ClearButtonsOfArmedState(); + } + + if ( m_pCommandMenus[m_SpectatorCameraMenu] ) + { + m_pCommandMenus[m_SpectatorCameraMenu]->ClearButtonsOfArmedState(); + } + + if ( m_pCommandMenus[m_PlayerMenu] ) + { + m_pCommandMenus[m_PlayerMenu]->ClearButtonsOfArmedState(); + } + + + m_flMenuOpenTime = 0.0f; + SetCurrentCommandMenu( NULL ); + UpdateCursorState(); +} + +//----------------------------------------------------------------------------- +// Purpose: Bring up the scoreboard +//----------------------------------------------------------------------------- +void TeamFortressViewport::ShowScoreBoard( void ) +{ + if (m_pScoreBoard) + { + // No Scoreboard in single-player + if ( gEngfuncs.GetMaxClients() > 1 ) + { + m_pScoreBoard->Open(); + UpdateCursorState(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if the scoreboard is up +//----------------------------------------------------------------------------- +bool TeamFortressViewport::IsScoreBoardVisible( void ) +{ + if (m_pScoreBoard) + return m_pScoreBoard->isVisible(); + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Hide the scoreboard +//----------------------------------------------------------------------------- +void TeamFortressViewport::HideScoreBoard( void ) +{ + // Prevent removal of scoreboard during intermission + if ( gHUD.m_iIntermission ) + return; + + if (m_pScoreBoard) + { + m_pScoreBoard->setVisible(false); + + GetClientVoiceMgr()->StopSquelchMode(); + + UpdateCursorState(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Activate's the player special ability +// called when the player hits their "special" key +//----------------------------------------------------------------------------- +void TeamFortressViewport::InputPlayerSpecial( void ) +{ + if (!m_iInitialized) + return; + +#ifdef _TFC + if ( g_iPlayerClass == PC_ENGINEER || g_iPlayerClass == PC_SPY ) + { + ShowCommandMenu( gViewPort->m_StandardMenu ); + + if ( m_pCurrentCommandMenu ) + { + m_pCurrentCommandMenu->KeyInput( '7' ); + } + } + else +#endif + { + // if it's any other class, just send the command down to the server + EngineClientCmd( "_special" ); + } +} + +// Set the submenu of the Command Menu +void TeamFortressViewport::SetCurrentCommandMenu( CCommandMenu *pNewMenu ) +{ + for (int i = 0; i < m_iNumMenus; i++) + m_pCommandMenus[i]->setVisible(false); + + m_pCurrentCommandMenu = pNewMenu; + + if (m_pCurrentCommandMenu) + m_pCurrentCommandMenu->MakeVisible( NULL ); +} + +void TeamFortressViewport::UpdateCommandMenu(int menuIndex) +{ + // if its the player menu update the player list + if(menuIndex==m_PlayerMenu) + { + m_pCommandMenus[m_PlayerMenu]->RemoveAllButtons(); + UpdatePlayerMenu(m_PlayerMenu); + } + + m_pCommandMenus[menuIndex]->RecalculateVisibles( 0, false ); + m_pCommandMenus[menuIndex]->RecalculatePositions( 0 ); + +} + +void TeamFortressViewport::UpdatePlayerMenu(int menuIndex) +{ + + cl_entity_t * pEnt = NULL; + float flLabelSize = ( (ScreenWidth - (XRES ( CAMOPTIONS_BUTTON_X ) + 15)) - XRES ( 24 + 15 ) ) - XRES( (15 + OPTIONS_BUTTON_X + 15) + 38 ); + gViewPort->GetAllPlayersInfo(); + + + for (int i = 1; i < MAX_PLAYERS; i++ ) + { + //if ( g_PlayerInfoList[i].name == NULL ) + // continue; // empty player slot, skip + + pEnt = gEngfuncs.GetEntityByIndex( i ); + + if ( !gHUD.m_Spectator.IsActivePlayer( pEnt ) ) + continue; + + //if ( g_PlayerExtraInfo[i].teamname[0] == 0 ) + // continue; // skip over players who are not in a team + + SpectButton *pButton = new SpectButton(1 , g_PlayerInfoList[pEnt->index].name , + XRES( ( 15 + OPTIONS_BUTTON_X + 15 ) + 31 ),PANEL_HEIGHT+(i-1)*CMENU_SIZE_X, flLabelSize, BUTTON_SIZE_Y /2 ); + + pButton->setBoundKey( (char)255 ); + pButton->setContentAlignment( vgui::Label::a_center ); + m_pCommandMenus[menuIndex]->AddButton( pButton ); + pButton->setParentMenu( m_pCommandMenus[menuIndex] ); + + // Override font in CommandMenu + pButton->setFont( Scheme::sf_primary3 ); + + pButton->addActionSignal(new CMenuHandler_SpectateFollow( g_PlayerInfoList[pEnt->index].name)); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCommandMenus[menuIndex]) ); + + } + +} + + + +void COM_FileBase ( const char *in, char *out); + +void TeamFortressViewport::UpdateSpectatorPanel() +{ + m_iUser1 = g_iUser1; + m_iUser2 = g_iUser2; + m_iUser3 = g_iUser3; + + if (!m_pSpectatorPanel) + return; + + if ( g_iUser1 && gHUD.m_pCvarDraw->value && !gHUD.m_iIntermission) // don't draw in dev_overview mode + { + char bottomText[128]; + char helpString2[128]; + char tempString[128]; + char * name; + char *pBottomText = NULL; + int player = 0; + + // check if spectator combinations are still valid + gHUD.m_Spectator.CheckSettings(); + + if ( !m_pSpectatorPanel->isVisible() ) + { + m_pSpectatorPanel->setVisible( true ); // show spectator panel, but + m_pSpectatorPanel->ShowMenu( false ); // dsiable all menus/buttons + + _snprintf( tempString, sizeof( tempString ) - 1, "%c%s", HUD_PRINTCENTER, CHudTextMessage::BufferedLocaliseTextString( "#Spec_Duck" ) ); + tempString[ sizeof( tempString ) - 1 ] = '\0'; + + gHUD.m_TextMessage.MsgFunc_TextMsg( NULL, strlen( tempString ) + 1, tempString ); + } + + sprintf(bottomText,"#Spec_Mode%d", g_iUser1 ); + sprintf(helpString2,"#Spec_Mode%d", g_iUser1 ); + + if ( gEngfuncs.IsSpectateOnly() ) + strcat(helpString2, " - HLTV"); + + // check if we're locked onto a target, show the player's name + if ( (g_iUser2 > 0) && (g_iUser2 <= gEngfuncs.GetMaxClients()) && (g_iUser1 != OBS_ROAMING) ) + { + player = g_iUser2; + } + + // special case in free map and inset off, don't show names + if ( (g_iUser1 == OBS_MAP_FREE) && !gHUD.m_Spectator.m_pip->value ) + name = NULL; + else + name = g_PlayerInfoList[player].name; + + // create player & health string + if ( player && name ) + { + strncpy( bottomText, name, sizeof(bottomText) ); + bottomText[ sizeof(bottomText) - 1 ] = 0; + pBottomText = bottomText; + } + else + { + pBottomText = CHudTextMessage::BufferedLocaliseTextString( bottomText ); + } + + // in first person mode colorize player names + if ( (g_iUser1 == OBS_IN_EYE) && player ) + { + float * color = GetClientColor( player ); + int r = color[0]*255; + int g = color[1]*255; + int b = color[2]*255; + + // set team color, a bit transparent + m_pSpectatorPanel->m_BottomMainLabel->setFgColor(r,g,b,0); + m_pSpectatorPanel->m_BottomMainButton->setFgColor(r,g,b,0); + } + else + { // restore GUI color + m_pSpectatorPanel->m_BottomMainLabel->setFgColor( 143, 143, 54, 0 ); + m_pSpectatorPanel->m_BottomMainButton->setFgColor( 143, 143, 54, 0 ); + } + + // add sting auto if we are in auto directed mode + if ( gHUD.m_Spectator.m_autoDirector->value ) + { + char tempString[128]; + sprintf(tempString, "#Spec_Auto %s", helpString2); + strcpy( helpString2, tempString ); + } + + m_pSpectatorPanel->m_BottomMainLabel->setText( "%s", pBottomText ); + m_pSpectatorPanel->m_BottomMainButton->setText( pBottomText ); + + + // update extra info field + char szText[64]; + + if ( gEngfuncs.IsSpectateOnly() ) + { + // in HLTV mode show number of spectators + _snprintf( szText, 63, "%s: %d", CHudTextMessage::BufferedLocaliseTextString( "#Spectators" ), gHUD.m_Spectator.m_iSpectatorNumber ); + } + else + { + // otherwise show map name + char szMapName[64]; + COM_FileBase( gEngfuncs.pfnGetLevelName(), szMapName ); + + _snprintf ( szText, 63, "%s: %s",CHudTextMessage::BufferedLocaliseTextString( "#Spec_Map" ), szMapName ); + } + + szText[63] = 0; + + m_pSpectatorPanel->m_ExtraInfo->setText ( szText ); + + /* + int timer = (int)( gHUD.m_roundTimer.m_flTimeEnd - gHUD.m_flTime ); + + if ( timer < 0 ) + timer = 0; + + _snprintf ( szText, 63, "%d:%02d\n", (timer / 60), (timer % 60) ); + + szText[63] = 0; + + m_pSpectatorPanel->m_CurrentTime->setText( szText ); */ + + // update spectator panel + gViewPort->m_pSpectatorPanel->Update(); + } + else + { + if ( m_pSpectatorPanel->isVisible() ) + { + m_pSpectatorPanel->setVisible( false ); + m_pSpectatorPanel->ShowMenu( false ); // dsiable all menus/buttons + } + } + + m_flSpectatorPanelLastUpdated = gHUD.m_flTime + 1.0; // update every second +} + +//====================================================================== +void TeamFortressViewport::CreateScoreBoard( void ) +{ + int xdent = SBOARD_INDENT_X, ydent = SBOARD_INDENT_Y; + if (ScreenWidth == 512) + { + xdent = SBOARD_INDENT_X_512; + ydent = SBOARD_INDENT_Y_512; + } + else if (ScreenWidth == 400) + { + xdent = SBOARD_INDENT_X_400; + ydent = SBOARD_INDENT_Y_400; + } + + m_pScoreBoard = new ScorePanel(xdent, ydent, ScreenWidth - (xdent * 2), ScreenHeight - (ydent * 2)); + m_pScoreBoard->setParent(this); + m_pScoreBoard->setVisible(false); +} + +void TeamFortressViewport::CreateServerBrowser( void ) +{ + m_pServerBrowser = new ServerBrowser( 0, 0, ScreenWidth, ScreenHeight ); + m_pServerBrowser->setParent(this); + m_pServerBrowser->setVisible(false); +} + + +//====================================================================== +// Set the VGUI Menu +void TeamFortressViewport::SetCurrentMenu( CMenuPanel *pMenu ) +{ + m_pCurrentMenu = pMenu; + if ( m_pCurrentMenu ) + { + // Don't open menus in demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + return; + + m_pCurrentMenu->Open(); + } + else + { + gEngfuncs.pfnClientCmd( "closemenus;" ); + } +} + +//================================================================ +// Text Window +CMenuPanel* TeamFortressViewport::CreateTextWindow( int iTextToShow ) +{ + char sz[256]; + char *cText; + char *pfile = NULL; + static const int MAX_TITLE_LENGTH = 64; + char cTitle[MAX_TITLE_LENGTH]; + + if ( iTextToShow == SHOW_MOTD ) + { + if (!m_szServerName || !m_szServerName[0]) + strcpy( cTitle, "Half-Life" ); + else + strncpy( cTitle, m_szServerName, sizeof(cTitle) ); + cTitle[sizeof(cTitle)-1] = 0; + cText = m_szMOTD; + } + else if ( iTextToShow == SHOW_MAPBRIEFING ) + { + // Get the current mapname, and open it's map briefing text + if (m_sMapName && m_sMapName[0]) + { + strcpy( sz, "maps/"); + strcat( sz, m_sMapName ); + strcat( sz, ".txt" ); + } + else + { + const char *level = gEngfuncs.pfnGetLevelName(); + if (!level) + return NULL; + + strcpy( sz, level ); + char *ch = strchr( sz, '.' ); + *ch = '\0'; + strcat( sz, ".txt" ); + + // pull out the map name + strcpy( m_sMapName, level ); + ch = strchr( m_sMapName, '.' ); + if ( ch ) + { + *ch = 0; + } + + ch = strchr( m_sMapName, '/' ); + if ( ch ) + { + // move the string back over the '/' + memmove( m_sMapName, ch+1, strlen(ch)+1 ); + } + } + + pfile = (char*)gEngfuncs.COM_LoadFile( sz, 5, NULL ); + + if (!pfile) + return NULL; + + cText = pfile; + + strncpy( cTitle, m_sMapName, MAX_TITLE_LENGTH ); + cTitle[MAX_TITLE_LENGTH-1] = 0; + } +#ifdef _TFC + else if ( iTextToShow == SHOW_CLASSDESC ) + { + switch ( g_iPlayerClass ) + { + case PC_SCOUT: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_scout" ); + CHudTextMessage::LocaliseTextString( "#Title_scout", cTitle, MAX_TITLE_LENGTH ); break; + case PC_SNIPER: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_sniper" ); + CHudTextMessage::LocaliseTextString( "#Title_sniper", cTitle, MAX_TITLE_LENGTH ); break; + case PC_SOLDIER: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_soldier" ); + CHudTextMessage::LocaliseTextString( "#Title_soldier", cTitle, MAX_TITLE_LENGTH ); break; + case PC_DEMOMAN: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_demoman" ); + CHudTextMessage::LocaliseTextString( "#Title_demoman", cTitle, MAX_TITLE_LENGTH ); break; + case PC_MEDIC: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_medic" ); + CHudTextMessage::LocaliseTextString( "#Title_medic", cTitle, MAX_TITLE_LENGTH ); break; + case PC_HVYWEAP: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_hwguy" ); + CHudTextMessage::LocaliseTextString( "#Title_hwguy", cTitle, MAX_TITLE_LENGTH ); break; + case PC_PYRO: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_pyro" ); + CHudTextMessage::LocaliseTextString( "#Title_pyro", cTitle, MAX_TITLE_LENGTH ); break; + case PC_SPY: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_spy" ); + CHudTextMessage::LocaliseTextString( "#Title_spy", cTitle, MAX_TITLE_LENGTH ); break; + case PC_ENGINEER: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_engineer" ); + CHudTextMessage::LocaliseTextString( "#Title_engineer", cTitle, MAX_TITLE_LENGTH ); break; + case PC_CIVILIAN: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_civilian" ); + CHudTextMessage::LocaliseTextString( "#Title_civilian", cTitle, MAX_TITLE_LENGTH ); break; + default: + return NULL; + } + + if ( g_iPlayerClass == PC_CIVILIAN ) + { + sprintf(sz, "classes/long_civilian.txt"); + } + else + { + sprintf(sz, "classes/long_%s.txt", sTFClassSelection[ g_iPlayerClass ]); + } + char *pfile = (char*)gEngfuncs.COM_LoadFile( sz, 5, NULL ); + if (pfile) + { + cText = pfile; + } + } +#endif + else if ( iTextToShow == SHOW_SPECHELP ) + { + CHudTextMessage::LocaliseTextString( "#Spec_Help_Title", cTitle, MAX_TITLE_LENGTH ); + cTitle[MAX_TITLE_LENGTH-1] = 0; + + char *pfile = CHudTextMessage::BufferedLocaliseTextString( "#Spec_Help_Text" ); + if ( pfile ) + { + cText = pfile; + } + } + + // if we're in the game (ie. have selected a class), flag the menu to be only grayed in the dialog box, instead of full screen + CMenuPanel *pMOTDPanel = CMessageWindowPanel_Create( cText, cTitle, g_iPlayerClass == PC_UNDEFINED, false, 0, 0, ScreenWidth, ScreenHeight ); + pMOTDPanel->setParent( this ); + + if ( pfile ) + gEngfuncs.COM_FreeFile( pfile ); + + return pMOTDPanel; +} + +//================================================================ +// VGUI Menus +void TeamFortressViewport::ShowVGUIMenu( int iMenu ) +{ + CMenuPanel *pNewMenu = NULL; + + // Don't open menus in demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + return; + + // Don't open any menus except the MOTD during intermission + // MOTD needs to be accepted because it's sent down to the client + // after map change, before intermission's turned off + if ( gHUD.m_iIntermission && iMenu != MENU_INTRO ) + return; + + // Don't create one if it's already in the list + if (m_pCurrentMenu) + { + CMenuPanel *pMenu = m_pCurrentMenu; + while (pMenu != NULL) + { + if (pMenu->GetMenuID() == iMenu) + return; + pMenu = pMenu->GetNextMenu(); + } + } + + switch ( iMenu ) + { + case MENU_TEAM: + pNewMenu = ShowTeamMenu(); + break; + + // Map Briefing removed now that it appears in the team menu + case MENU_MAPBRIEFING: + pNewMenu = CreateTextWindow( SHOW_MAPBRIEFING ); + break; + + case MENU_INTRO: + pNewMenu = CreateTextWindow( SHOW_MOTD ); + break; + + case MENU_CLASSHELP: + pNewMenu = CreateTextWindow( SHOW_CLASSDESC ); + break; + + case MENU_SPECHELP: + pNewMenu = CreateTextWindow( SHOW_SPECHELP ); + break; + case MENU_CLASS: + pNewMenu = ShowClassMenu(); + break; + + default: + break; + } + + if (!pNewMenu) + return; + + // Close the Command Menu if it's open + HideCommandMenu(); + + pNewMenu->SetMenuID( iMenu ); + pNewMenu->SetActive( true ); + pNewMenu->setParent(this); + + // See if another menu is visible, and if so, cache this one for display once the other one's finished + if (m_pCurrentMenu) + { + if ( m_pCurrentMenu->GetMenuID() == MENU_CLASS && iMenu == MENU_TEAM ) + { + CMenuPanel *temp = m_pCurrentMenu; + m_pCurrentMenu->Close(); + m_pCurrentMenu = pNewMenu; + m_pCurrentMenu->SetNextMenu( temp ); + m_pCurrentMenu->Open(); + UpdateCursorState(); + } + else + { + m_pCurrentMenu->SetNextMenu( pNewMenu ); + } + } + else + { + m_pCurrentMenu = pNewMenu; + m_pCurrentMenu->Open(); + UpdateCursorState(); + } +} + +// Removes all VGUI Menu's onscreen +void TeamFortressViewport::HideVGUIMenu() +{ + while (m_pCurrentMenu) + { + HideTopMenu(); + } +} + +// Remove the top VGUI menu, and bring up the next one +void TeamFortressViewport::HideTopMenu() +{ + if (m_pCurrentMenu) + { + // Close the top one + m_pCurrentMenu->Close(); + + // Bring up the next one + gViewPort->SetCurrentMenu( m_pCurrentMenu->GetNextMenu() ); + } + + UpdateCursorState(); +} + +// Return TRUE if the HUD's allowed to print text messages +bool TeamFortressViewport::AllowedToPrintText( void ) +{ + // Prevent text messages when fullscreen menus are up + if ( m_pCurrentMenu && g_iPlayerClass == 0 ) + { + int iId = m_pCurrentMenu->GetMenuID(); + if ( iId == MENU_TEAM || iId == MENU_CLASS || iId == MENU_INTRO || iId == MENU_CLASSHELP ) + return FALSE; + } + + return TRUE; +} + +//====================================================================================== +// TEAM MENU +//====================================================================================== +// Bring up the Team selection Menu +CMenuPanel* TeamFortressViewport::ShowTeamMenu() +{ + // Don't open menus in demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + return NULL; + + m_pTeamMenu->Reset(); + return m_pTeamMenu; +} + +void TeamFortressViewport::CreateTeamMenu() +{ + // Create the panel + m_pTeamMenu = new CTeamMenuPanel(100, false, 0, 0, ScreenWidth, ScreenHeight); + m_pTeamMenu->setParent( this ); + m_pTeamMenu->setVisible( false ); +} + +//====================================================================================== +// CLASS MENU +//====================================================================================== +// Bring up the Class selection Menu +CMenuPanel* TeamFortressViewport::ShowClassMenu() +{ + // Don't open menus in demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + return NULL; + + m_pClassMenu->Reset(); + return m_pClassMenu; +} + +void TeamFortressViewport::CreateClassMenu() +{ + // Create the panel + m_pClassMenu = new CClassMenuPanel(100, false, 0, 0, ScreenWidth, ScreenHeight); + m_pClassMenu->setParent(this); + m_pClassMenu->setVisible( false ); +} + +//====================================================================================== +//====================================================================================== +// SPECTATOR MENU +//====================================================================================== +// Spectator "Menu" explaining the Spectator buttons +void TeamFortressViewport::CreateSpectatorMenu() +{ + // Create the Panel + m_pSpectatorPanel = new SpectatorPanel(0, 0, ScreenWidth, ScreenHeight); + m_pSpectatorPanel->setParent(this); + m_pSpectatorPanel->setVisible(false); + m_pSpectatorPanel->Initialize(); +} + +//====================================================================================== +// UPDATE HUD SECTIONS +//====================================================================================== +// We've got an update on player info +// Recalculate any menus that use it. +void TeamFortressViewport::UpdateOnPlayerInfo() +{ + if (m_pTeamMenu) + m_pTeamMenu->Update(); + if (m_pClassMenu) + m_pClassMenu->Update(); + if (m_pScoreBoard) + m_pScoreBoard->Update(); +} + +void TeamFortressViewport::UpdateCursorState() +{ + // Need cursor if any VGUI window is up + if ( m_pSpectatorPanel->m_menuVisible || m_pCurrentMenu || m_pTeamMenu->isVisible() || m_pServerBrowser->isVisible() || GetClientVoiceMgr()->IsInSquelchMode() ) + { + g_iVisibleMouse = true; + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::scu_arrow) ); + return; + } + else if ( m_pCurrentCommandMenu ) + { + // commandmenu doesn't have cursor if hud_capturemouse is turned off + if ( gHUD.m_pCvarStealMouse->value != 0.0f ) + { + g_iVisibleMouse = true; + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::scu_arrow) ); + return; + } + } + + // Don't reset mouse in demo playback + if ( !gEngfuncs.pDemoAPI->IsPlayingback() ) + { + IN_ResetMouse(); + } + + g_iVisibleMouse = false; + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::scu_none) ); +} + +void TeamFortressViewport::UpdateHighlights() +{ + if (m_pCurrentCommandMenu) + m_pCurrentCommandMenu->MakeVisible( NULL ); +} + +void TeamFortressViewport::GetAllPlayersInfo( void ) +{ + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + gEngfuncs.pfnGetPlayerInfo( i, &g_PlayerInfoList[i] ); + + if ( g_PlayerInfoList[i].thisplayer ) + m_pScoreBoard->m_iPlayerNum = i; // !!!HACK: this should be initialized elsewhere... maybe gotten from the engine + } +} + +void TeamFortressViewport::paintBackground() +{ + int wide, tall; + getParent()->getSize( wide, tall ); + setSize( wide, tall ); + if (m_pScoreBoard) + { + int x, y; + getApp()->getCursorPos(x, y); + m_pScoreBoard->cursorMoved(x, y, m_pScoreBoard); + } + + // See if the command menu is visible and needs recalculating due to some external change + if ( g_iTeamNumber != m_iCurrentTeamNumber ) + { + UpdateCommandMenu( m_StandardMenu ); + + if ( m_pClassMenu ) + { + m_pClassMenu->Update(); + } + + m_iCurrentTeamNumber = g_iTeamNumber; + } + + if ( g_iPlayerClass != m_iCurrentPlayerClass ) + { + UpdateCommandMenu( m_StandardMenu ); + + m_iCurrentPlayerClass = g_iPlayerClass; + } + + // See if the Spectator Menu needs to be update + if ( ( g_iUser1 != m_iUser1 || g_iUser2 != m_iUser2 ) || + ( m_flSpectatorPanelLastUpdated < gHUD.m_flTime ) ) + { + UpdateSpectatorPanel(); + } + + // Update the Scoreboard, if it's visible + if ( m_pScoreBoard->isVisible() && (m_flScoreBoardLastUpdated < gHUD.m_flTime) ) + { + m_pScoreBoard->Update(); + m_flScoreBoardLastUpdated = gHUD.m_flTime + 0.5; + } + + int extents[4]; + getAbsExtents(extents[0],extents[1],extents[2],extents[3]); + VGui_ViewportPaintBackground(extents); +} + +//================================================================ +// Input Handler for Drag N Drop panels +void CDragNDropHandler::cursorMoved(int x,int y,Panel* panel) +{ + if(m_bDragging) + { + App::getInstance()->getCursorPos(x,y); + m_pPanel->setPos(m_iaDragOrgPos[0]+(x-m_iaDragStart[0]),m_iaDragOrgPos[1]+(y-m_iaDragStart[1])); + + if(m_pPanel->getParent()!=null) + { + m_pPanel->getParent()->repaint(); + } + } +} + +void CDragNDropHandler::mousePressed(MouseCode code,Panel* panel) +{ + int x,y; + App::getInstance()->getCursorPos(x,y); + m_bDragging=true; + m_iaDragStart[0]=x; + m_iaDragStart[1]=y; + m_pPanel->getPos(m_iaDragOrgPos[0],m_iaDragOrgPos[1]); + App::getInstance()->setMouseCapture(panel); + + m_pPanel->setDragged(m_bDragging); + m_pPanel->requestFocus(); +} + +void CDragNDropHandler::mouseReleased(MouseCode code,Panel* panel) +{ + m_bDragging=false; + m_pPanel->setDragged(m_bDragging); + App::getInstance()->setMouseCapture(null); +} + +//================================================================ +// Number Key Input +bool TeamFortressViewport::SlotInput( int iSlot ) +{ + // If there's a menu up, give it the input + if ( m_pCurrentMenu ) + return m_pCurrentMenu->SlotInput( iSlot ); + + return FALSE; +} + +// Direct Key Input +int TeamFortressViewport::KeyInput( int down, int keynum, const char *pszCurrentBinding ) +{ + // Enter gets out of Spectator Mode by bringing up the Team Menu + if (m_iUser1 && gEngfuncs.Con_IsVisible() == false ) + { + if ( down && (keynum == K_ENTER || keynum == K_KP_ENTER) ) + ShowVGUIMenu( MENU_TEAM ); + } + + // Open Text Window? + if (m_pCurrentMenu && gEngfuncs.Con_IsVisible() == false) + { + int iMenuID = m_pCurrentMenu->GetMenuID(); + + // Get number keys as Input for Team/Class menus + if (iMenuID == MENU_TEAM || iMenuID == MENU_CLASS) + { + // Escape gets you out of Team/Class menus if the Cancel button is visible + if ( keynum == K_ESCAPE ) + { + if ( (iMenuID == MENU_TEAM && g_iTeamNumber) || (iMenuID == MENU_CLASS && g_iPlayerClass) ) + { + HideTopMenu(); + return 0; + } + } + + for (int i = '0'; i <= '9'; i++) + { + if ( down && (keynum == i) ) + { + SlotInput( i - '0' ); + return 0; + } + } + } + + // Grab enter keys to close TextWindows + if ( down && (keynum == K_ENTER || keynum == K_KP_ENTER || keynum == K_SPACE || keynum == K_ESCAPE) ) + { + if ( iMenuID == MENU_MAPBRIEFING || iMenuID == MENU_INTRO || iMenuID == MENU_CLASSHELP ) + { + HideTopMenu(); + return 0; + } + } + + // Grab jump key on Team Menu as autoassign + if ( pszCurrentBinding && down && !strcmp(pszCurrentBinding, "+jump") ) + { + if (iMenuID == MENU_TEAM) + { + m_pTeamMenu->SlotInput(5); + return 0; + } + } + + } + + // if we're in a command menu, try hit one of it's buttons + if ( down && m_pCurrentCommandMenu ) + { + // Escape hides the command menu + if ( keynum == K_ESCAPE ) + { + HideCommandMenu(); + return 0; + } + + // only trap the number keys + if ( keynum >= '0' && keynum <= '9' ) + { + if ( m_pCurrentCommandMenu->KeyInput(keynum) ) + { + // a final command has been issued, so close the command menu + HideCommandMenu(); + } + + return 0; + } + } + + return 1; +} + +//================================================================ +// Message Handlers +int TeamFortressViewport::MsgFunc_ValClass(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + for (int i = 0; i < 5; i++) + m_iValidClasses[i] = READ_SHORT(); + + // Force the menu to update + UpdateCommandMenu( m_StandardMenu ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_TeamNames(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iNumberOfTeams = READ_BYTE(); + + for (int i = 0; i < m_iNumberOfTeams; i++) + { + int teamNum = i + 1; + + gHUD.m_TextMessage.LocaliseTextString( READ_STRING(), m_sTeamNames[teamNum], MAX_TEAMNAME_SIZE ); + + // Set the team name buttons + if (m_pTeamButtons[i]) + m_pTeamButtons[i]->setText( m_sTeamNames[teamNum] ); + + // range check this value...m_pDisguiseButtons[5]; + if ( teamNum < 5 ) + { + // Set the disguise buttons + if ( m_pDisguiseButtons[teamNum] ) + m_pDisguiseButtons[teamNum]->setText( m_sTeamNames[teamNum] ); + } + } + + // Update the Team Menu + if (m_pTeamMenu) + m_pTeamMenu->Update(); + + return 1; +} + +int TeamFortressViewport::MsgFunc_Feign(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iIsFeigning = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu( m_StandardMenu ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_Detpack(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iIsSettingDetpack = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu( m_StandardMenu ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_VGUIMenu(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int iMenu = READ_BYTE(); + + // Map briefing includes the name of the map (because it's sent down before the client knows what map it is) + if (iMenu == MENU_MAPBRIEFING) + { + strncpy( m_sMapName, READ_STRING(), sizeof(m_sMapName) ); + m_sMapName[ sizeof(m_sMapName) - 1 ] = '\0'; + } + + // Bring up the menu6 + ShowVGUIMenu( iMenu ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_MOTD( const char *pszName, int iSize, void *pbuf ) +{ + if (m_iGotAllMOTD) + m_szMOTD[0] = 0; + + BEGIN_READ( pbuf, iSize ); + + m_iGotAllMOTD = READ_BYTE(); + + int roomInArray = sizeof(m_szMOTD) - strlen(m_szMOTD) - 1; + + strncat( m_szMOTD, READ_STRING(), roomInArray >= 0 ? roomInArray : 0 ); + m_szMOTD[ sizeof(m_szMOTD)-1 ] = '\0'; + + // don't show MOTD for HLTV spectators + if ( m_iGotAllMOTD && !gEngfuncs.IsSpectateOnly() ) + { + ShowVGUIMenu( MENU_INTRO ); + } + + return 1; +} + +int TeamFortressViewport::MsgFunc_BuildSt( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iBuildState = READ_SHORT(); + + // Force the menu to update + UpdateCommandMenu( m_StandardMenu ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_RandomPC( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iRandomPC = READ_BYTE(); + + return 1; +} + +int TeamFortressViewport::MsgFunc_ServerName( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + strncpy( m_szServerName, READ_STRING(), sizeof(m_szServerName) ); + m_szServerName[sizeof(m_szServerName) - 1] = 0; + + return 1; +} + +int TeamFortressViewport::MsgFunc_ScoreInfo( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + short cl = READ_BYTE(); + short frags = READ_SHORT(); + short deaths = READ_SHORT(); + short playerclass = READ_SHORT(); + short teamnumber = READ_SHORT(); + + if ( cl > 0 && cl <= MAX_PLAYERS ) + { + g_PlayerExtraInfo[cl].frags = frags; + g_PlayerExtraInfo[cl].deaths = deaths; + g_PlayerExtraInfo[cl].playerclass = playerclass; + g_PlayerExtraInfo[cl].teamnumber = teamnumber; + + //Dont go bellow 0! + if ( g_PlayerExtraInfo[cl].teamnumber < 0 ) + g_PlayerExtraInfo[cl].teamnumber = 0; + + UpdateOnPlayerInfo(); + } + + return 1; +} + +// Message handler for TeamScore message +// accepts three values: +// string: team name +// short: teams kills +// short: teams deaths +// if this message is never received, then scores will simply be the combined totals of the players. +int TeamFortressViewport::MsgFunc_TeamScore( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + char *TeamName = READ_STRING(); + + // find the team matching the name + int i; + for ( i = 1; i <= m_pScoreBoard->m_iNumTeams; i++ ) + { + if ( !stricmp( TeamName, g_TeamInfo[i].name ) ) + break; + } + + if ( i > m_pScoreBoard->m_iNumTeams ) + return 1; + + // use this new score data instead of combined player scoresw + g_TeamInfo[i].scores_overriden = TRUE; + g_TeamInfo[i].frags = READ_SHORT(); + g_TeamInfo[i].deaths = READ_SHORT(); + + return 1; +} + +// Message handler for TeamInfo message +// accepts two values: +// byte: client number +// string: client team name +int TeamFortressViewport::MsgFunc_TeamInfo( const char *pszName, int iSize, void *pbuf ) +{ + if (!m_pScoreBoard) + return 1; + + BEGIN_READ( pbuf, iSize ); + short cl = READ_BYTE(); + + if ( cl > 0 && cl <= MAX_PLAYERS ) + { + // set the players team + strncpy( g_PlayerExtraInfo[cl].teamname, READ_STRING(), MAX_TEAM_NAME ); + } + + // rebuild the list of teams + m_pScoreBoard->RebuildTeams(); + + return 1; +} + +void TeamFortressViewport::DeathMsg( int killer, int victim ) +{ + m_pScoreBoard->DeathMsg(killer,victim); +} + +int TeamFortressViewport::MsgFunc_Spectator( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + short cl = READ_BYTE(); + if ( cl > 0 && cl <= MAX_PLAYERS ) + { + g_IsSpectator[cl] = READ_BYTE(); + } + + return 1; +} + +int TeamFortressViewport::MsgFunc_AllowSpec( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iAllowSpectators = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu( m_StandardMenu ); + + // If the team menu is up, update it too + if (m_pTeamMenu) + m_pTeamMenu->Update(); + + return 1; +} + +#if defined( _TFC ) +const Vector& GetTeamColor( int team_no ); +extern globalvars_t *gpGlobals; +#endif + +// used to reset the player's screen immediately +int TeamFortressViewport::MsgFunc_ResetFade( const char *pszName, int iSize, void *pbuf ) +{ +#if defined( _TFC ) + if ( !gpGlobals ) + return 0; + + screenfade_t sf; + gEngfuncs.pfnGetScreenFade( &sf ); + + sf.fader = 0; + sf.fadeg = 0; + sf.fadeb = 0; + sf.fadealpha = 0; + + sf.fadeEnd = 0.1; + sf.fadeReset = 0.0; + sf.fadeSpeed = 0.0; + + sf.fadeFlags = FFADE_IN; + + sf.fadeReset += gpGlobals->time; + sf.fadeEnd += sf.fadeReset; + + gEngfuncs.pfnSetScreenFade( &sf ); +#endif + + return 1; +} + +// used to fade a player's screen out/in when they're spectating someone who is teleported +int TeamFortressViewport::MsgFunc_SpecFade( const char *pszName, int iSize, void *pbuf ) +{ +#if defined( _TFC ) + BEGIN_READ( pbuf, iSize ); + + int iIndex = READ_BYTE(); + + // we're in first-person spectator mode (...not first-person in the PIP) + if ( g_iUser1 == OBS_IN_EYE ) + { + // this is the person we're watching + if ( g_iUser2 == iIndex ) + { + int iFade = READ_BYTE(); + int iTeam = READ_BYTE(); + float flTime = ( (float)READ_SHORT() / 100.0 ); + int iAlpha = READ_BYTE(); + + Vector team = GetTeamColor( iTeam ); + + screenfade_t sf; + gEngfuncs.pfnGetScreenFade( &sf ); + + sf.fader = team[0]; + sf.fadeg = team[1]; + sf.fadeb = team[2]; + sf.fadealpha = iAlpha; + + sf.fadeEnd = flTime; + sf.fadeReset = 0.0; + sf.fadeSpeed = 0.0; + + if ( iFade == BUILD_TELEPORTER_FADE_OUT ) + { + sf.fadeFlags = FFADE_OUT; + sf.fadeReset = flTime; + + if ( sf.fadeEnd ) + sf.fadeSpeed = -(float)sf.fadealpha / sf.fadeEnd; + + sf.fadeTotalEnd = sf.fadeEnd += gpGlobals->time; + sf.fadeReset += sf.fadeEnd; + } + else + { + sf.fadeFlags = FFADE_IN; + + if ( sf.fadeEnd ) + sf.fadeSpeed = (float)sf.fadealpha / sf.fadeEnd; + + sf.fadeReset += gpGlobals->time; + sf.fadeEnd += sf.fadeReset; + } + + gEngfuncs.pfnSetScreenFade( &sf ); + } + } +#endif + + return 1; +} diff --git a/cl_dll/vgui_TeamFortressViewport.h b/cl_dll/vgui_TeamFortressViewport.h new file mode 100644 index 0000000..05128cd --- /dev/null +++ b/cl_dll/vgui_TeamFortressViewport.h @@ -0,0 +1,1818 @@ + +#ifndef TEAMFORTRESSVIEWPORT_H +#define TEAMFORTRESSVIEWPORT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// custom scheme handling +#include "vgui_SchemeManager.h" + +#define TF_DEFS_ONLY +#ifdef _TFC +#include "../tfc/tf_defs.h" +#else +#define PC_LASTCLASS 10 +#define PC_UNDEFINED 0 +#define MENU_DEFAULT 1 +#define MENU_TEAM 2 +#define MENU_CLASS 3 +#define MENU_MAPBRIEFING 4 +#define MENU_INTRO 5 +#define MENU_CLASSHELP 6 +#define MENU_CLASSHELP2 7 +#define MENU_REPEATHELP 8 +#define MENU_SPECHELP 9 +#endif +using namespace vgui; + +class Cursor; +class ScorePanel; +class SpectatorPanel; +class CCommandMenu; +class CommandLabel; +class CommandButton; +class BuildButton; +class ClassButton; +class CMenuPanel; +class ServerBrowser; +class DragNDropPanel; +class CTransparentPanel; +class CClassMenuPanel; +class CTeamMenuPanel; +class TeamFortressViewport; + +char* GetVGUITGAName(const char *pszName); +BitmapTGA *LoadTGAForRes(const char* pImageName); +void ScaleColors( int &r, int &g, int &b, int a ); +extern char *sTFClassSelection[]; +extern int sTFValidClassInts[]; +extern char *sLocalisedClasses[]; +extern int iTeamColors[5][3]; +extern int iNumberOfTeamColors; +extern TeamFortressViewport *gViewPort; + + +// Command Menu positions +#define MAX_MENUS 80 +#define MAX_BUTTONS 100 + +#define BUTTON_SIZE_Y YRES(30) +#define CMENU_SIZE_X XRES(160) + +#define SUBMENU_SIZE_X (CMENU_SIZE_X / 8) +#define SUBMENU_SIZE_Y (BUTTON_SIZE_Y / 6) + +#define CMENU_TOP (BUTTON_SIZE_Y * 4) + +//#define MAX_TEAMNAME_SIZE 64 +#define MAX_BUTTON_SIZE 32 + +// Map Briefing Window +#define MAPBRIEF_INDENT 30 + +// Team Menu +#define TMENU_INDENT_X (30 * ((float)ScreenHeight / 640)) +#define TMENU_HEADER 100 +#define TMENU_SIZE_X (ScreenWidth - (TMENU_INDENT_X * 2)) +#define TMENU_SIZE_Y (TMENU_HEADER + BUTTON_SIZE_Y * 7) +#define TMENU_PLAYER_INDENT (((float)TMENU_SIZE_X / 3) * 2) +#define TMENU_INDENT_Y (((float)ScreenHeight - TMENU_SIZE_Y) / 2) + +// Class Menu +#define CLMENU_INDENT_X (30 * ((float)ScreenHeight / 640)) +#define CLMENU_HEADER 100 +#define CLMENU_SIZE_X (ScreenWidth - (CLMENU_INDENT_X * 2)) +#define CLMENU_SIZE_Y (CLMENU_HEADER + BUTTON_SIZE_Y * 11) +#define CLMENU_PLAYER_INDENT (((float)CLMENU_SIZE_X / 3) * 2) +#define CLMENU_INDENT_Y (((float)ScreenHeight - CLMENU_SIZE_Y) / 2) + +// Arrows +enum +{ + ARROW_UP, + ARROW_DOWN, + ARROW_LEFT, + ARROW_RIGHT, +}; + +//============================================================================== +// VIEWPORT PIECES +//============================================================ +// Wrapper for an Image Label without a background +class CImageLabel : public Label +{ +public: + BitmapTGA *m_pTGA; + +public: + void LoadImage(const char * pImageName); + CImageLabel( const char* pImageName,int x,int y ); + CImageLabel( const char* pImageName,int x,int y,int wide,int tall ); + + virtual int getImageTall(); + virtual int getImageWide(); + + virtual void paintBackground() + { + // Do nothing, so the background's left transparent. + } +}; + +// Command Label +// Overridden label so we can darken it when submenus open +class CommandLabel : public Label +{ +private: + int m_iState; + +public: + CommandLabel(const char* text,int x,int y,int wide,int tall) : Label(text,x,y,wide,tall) + { + m_iState = false; + } + + void PushUp() + { + m_iState = false; + repaint(); + } + + void PushDown() + { + m_iState = true; + repaint(); + } +}; + +//============================================================ +// Command Buttons +class CommandButton : public Button +{ +private: + int m_iPlayerClass; + bool m_bFlat; + + // Submenus under this button + CCommandMenu *m_pSubMenu; + CCommandMenu *m_pParentMenu; + CommandLabel *m_pSubLabel; + + char m_sMainText[MAX_BUTTON_SIZE]; + char m_cBoundKey; + + SchemeHandle_t m_hTextScheme; + + void RecalculateText( void ); + +public: + bool m_bNoHighlight; + +public: + CommandButton(const char* text,int x,int y,int wide,int tall, bool bNoHighlight, bool bFlat); + // Constructors + CommandButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight = false); + CommandButton( int iPlayerClass, const char* text,int x,int y,int wide,int tall, bool bFlat ); + + void Init( void ); + + // Menu Handling + void AddSubMenu( CCommandMenu *pNewMenu ); + void AddSubLabel( CommandLabel *pSubLabel ) + { + m_pSubLabel = pSubLabel; + } + + virtual int IsNotValid( void ) + { + return false; + } + + void UpdateSubMenus( int iAdjustment ); + int GetPlayerClass() { return m_iPlayerClass; }; + CCommandMenu *GetSubMenu() { return m_pSubMenu; }; + + CCommandMenu *getParentMenu( void ); + void setParentMenu( CCommandMenu *pParentMenu ); + + // Overloaded vgui functions + virtual void paint(); + virtual void setText( const char *text ); + virtual void paintBackground(); + + void cursorEntered( void ); + void cursorExited( void ); + + void setBoundKey( char boundKey ); + char getBoundKey( void ); +}; + +class ColorButton : public CommandButton +{ +private: + + Color *ArmedColor; + Color *UnArmedColor; + + Color *ArmedBorderColor; + Color *UnArmedBorderColor; + +public: + ColorButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight, bool bFlat ) : + CommandButton( text, x, y, wide, tall, bNoHighlight, bFlat ) + { + ArmedColor = NULL; + UnArmedColor = NULL; + ArmedBorderColor = NULL; + UnArmedBorderColor = NULL; + } + + + virtual void paintBackground() + { + int r, g, b, a; + Color bgcolor; + + if ( isArmed() ) + { + // Highlight background + /* getBgColor(bgcolor); + bgcolor.getColor(r, g, b, a); + drawSetColor( r,g,b,a ); + drawFilledRect(0,0,_size[0],_size[1]);*/ + + if ( ArmedBorderColor ) + { + ArmedBorderColor->getColor( r, g, b, a); + drawSetColor( r, g, b, a ); + drawOutlinedRect(0,0,_size[0],_size[1]); + } + } + else + { + if ( UnArmedBorderColor ) + { + UnArmedBorderColor->getColor( r, g, b, a); + drawSetColor( r, g, b, a ); + drawOutlinedRect(0,0,_size[0],_size[1]); + } + } + } + void paint() + { + int r, g, b, a; + if ( isArmed() ) + { + if (ArmedColor) + { + ArmedColor->getColor(r, g, b, a); + setFgColor(r, g, b, a); + } + else + setFgColor( Scheme::sc_secondary2 ); + } + else + { + if (UnArmedColor) + { + UnArmedColor->getColor(r, g, b, a); + setFgColor(r, g, b, a); + } + else + setFgColor( Scheme::sc_primary1 ); + } + + Button::paint(); + } + + void setArmedColor ( int r, int g, int b, int a ) + { + ArmedColor = new Color( r, g, b, a ); + } + + void setUnArmedColor ( int r, int g, int b, int a ) + { + UnArmedColor = new Color( r, g, b, a ); + } + + void setArmedBorderColor ( int r, int g, int b, int a ) + { + ArmedBorderColor = new Color( r, g, b, a ); + } + + void setUnArmedBorderColor ( int r, int g, int b, int a ) + { + UnArmedBorderColor = new Color( r, g, b, a ); + } +}; + +class SpectButton : public CommandButton +{ +private: + +public: + SpectButton( int iPlayerClass, const char* text,int x,int y,int wide,int tall ) : + CommandButton( text, x, y, wide, tall, false) + { + Init(); + + setText( text ); + } + + virtual void paintBackground() + { + if ( isArmed()) + { + drawSetColor( 143,143, 54, 125 ); + drawFilledRect( 5, 0,_size[0] - 5,_size[1]); + } + } + + virtual void paint() + { + + if ( isArmed() ) + { + setFgColor( 194, 202, 54, 0 ); + } + else + { + setFgColor( 143, 143, 54, 15 ); + } + + Button::paint(); + } +}; +//============================================================ +// Command Menus +class CCommandMenu : public Panel +{ +private: + CCommandMenu *m_pParentMenu; + int m_iXOffset; + int m_iYOffset; + + // Buttons in this menu + CommandButton *m_aButtons[ MAX_BUTTONS ]; + int m_iButtons; + + // opens menu from top to bottom (0 = default), or from bottom to top (1)? + int m_iDirection; +public: + CCommandMenu( CCommandMenu *pParentMenu, int x,int y,int wide,int tall ) : Panel(x,y,wide,tall) + { + m_pParentMenu = pParentMenu; + m_iXOffset = x; + m_iYOffset = y; + m_iButtons = 0; + m_iDirection = 0; + } + + + CCommandMenu( CCommandMenu *pParentMenu, int direction, int x,int y,int wide,int tall ) : Panel(x,y,wide,tall) + { + m_pParentMenu = pParentMenu; + m_iXOffset = x; + m_iYOffset = y; + m_iButtons = 0; + m_iDirection = direction; + } + + float m_flButtonSizeY; + int m_iSpectCmdMenu; + void AddButton( CommandButton *pButton ); + bool RecalculateVisibles( int iNewYPos, bool bHideAll ); + void RecalculatePositions( int iYOffset ); + void MakeVisible( CCommandMenu *pChildMenu ); + + CCommandMenu *GetParentMenu() { return m_pParentMenu; }; + int GetXOffset() { return m_iXOffset; }; + int GetYOffset() { return m_iYOffset; }; + int GetDirection() { return m_iDirection; }; + int GetNumButtons() { return m_iButtons; }; + CommandButton *FindButtonWithSubmenu( CCommandMenu *pSubMenu ); + + void ClearButtonsOfArmedState( void ); + + void RemoveAllButtons(void); + + + bool KeyInput( int keyNum ); + + virtual void paintBackground(); +}; + +//============================================================================== +// Command menu root button (drop down box style) + +class DropDownButton : public ColorButton +{ +private: + CImageLabel *m_pOpenButton; + +public: + + DropDownButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight, bool bFlat ) : + ColorButton( text, x, y, wide, tall, bNoHighlight, bFlat ) + { + // Put a > to show it's a submenu + m_pOpenButton = new CImageLabel( "arrowup", XRES( CMENU_SIZE_X-2 ) , YRES( BUTTON_SIZE_Y-2 ) ); + m_pOpenButton->setParent(this); + + int textwide, texttall; + getSize( textwide, texttall); + + // Reposition + m_pOpenButton->setPos( textwide-(m_pOpenButton->getImageWide()+6), -2 /*(tall - m_pOpenButton->getImageTall()*2) / 2*/ ); + m_pOpenButton->setVisible(true); + + } + + virtual void setVisible(bool state) + { + m_pOpenButton->setVisible(state); + ColorButton::setVisible(state); + } + + +}; + +//============================================================================== +// Button with image instead of text + +class CImageButton : public ColorButton +{ +private: + CImageLabel *m_pOpenButton; + +public: + + CImageButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight, bool bFlat ) : + ColorButton( " ", x, y, wide, tall, bNoHighlight, bFlat ) + { + m_pOpenButton = new CImageLabel( text,1,1,wide-2 , tall-2 ); + m_pOpenButton->setParent(this); + + // Reposition + // m_pOpenButton->setPos( x+1,y+1 ); + // m_pOpenButton->setSize(wide-2,tall-2); + + m_pOpenButton->setVisible(true); + } + + virtual void setVisible(bool state) + { + m_pOpenButton->setVisible(state); + ColorButton::setVisible(state); + } + + +}; + +//============================================================================== +class TeamFortressViewport : public Panel +{ +private: + vgui::Cursor* _cursorNone; + vgui::Cursor* _cursorArrow; + + int m_iInitialized; + + CCommandMenu *m_pCommandMenus[ MAX_MENUS ]; + CCommandMenu *m_pCurrentCommandMenu; + float m_flMenuOpenTime; + float m_flScoreBoardLastUpdated; + float m_flSpectatorPanelLastUpdated; + int m_iNumMenus; + int m_iCurrentTeamNumber; + int m_iCurrentPlayerClass; + int m_iUser1; + int m_iUser2; + int m_iUser3; + + // VGUI Menus + void CreateTeamMenu( void ); + CMenuPanel* ShowTeamMenu( void ); + void CreateClassMenu( void ); + CMenuPanel* ShowClassMenu( void ); + void CreateSpectatorMenu( void ); + + // Scheme handler + CSchemeManager m_SchemeManager; + + // MOTD + int m_iGotAllMOTD; + char m_szMOTD[ MAX_MOTD_LENGTH ]; + + // Command Menu Team buttons + CommandButton *m_pTeamButtons[6]; + CommandButton *m_pDisguiseButtons[5]; + BuildButton *m_pBuildButtons[3]; + BuildButton *m_pBuildActiveButtons[3]; + + // Server Browser + ServerBrowser *m_pServerBrowser; + + int m_iAllowSpectators; + + // Data for specific sections of the Command Menu + int m_iValidClasses[5]; + int m_iIsFeigning; + int m_iIsSettingDetpack; + int m_iNumberOfTeams; + int m_iBuildState; + int m_iRandomPC; + char m_sTeamNames[5][MAX_TEAMNAME_SIZE]; + + // Localisation strings + char m_sDetpackStrings[3][MAX_BUTTON_SIZE]; + + char m_sMapName[64]; + + // helper function to update the player menu entries + void UpdatePlayerMenu(int menuIndex); + +public: + TeamFortressViewport(int x,int y,int wide,int tall); + void Initialize( void ); + + int CreateCommandMenu( char * menuFile, int direction, int yOffset, bool flatDesign, float flButtonSizeX, float flButtonSizeY, int xOffset ); + void CreateScoreBoard( void ); + void CreateServerBrowser( void ); + CommandButton * CreateCustomButton( char *pButtonText, char * pButtonName, int iYOffset ); + CCommandMenu * CreateDisguiseSubmenu( CommandButton *pButton, CCommandMenu *pParentMenu, const char *commandText, int iYOffset, int iXOffset = 0 ); + + void UpdateCursorState( void ); + void UpdateCommandMenu(int menuIndex); + void UpdateOnPlayerInfo( void ); + void UpdateHighlights( void ); + void UpdateSpectatorPanel( void ); + + int KeyInput( int down, int keynum, const char *pszCurrentBinding ); + void InputPlayerSpecial( void ); + void GetAllPlayersInfo( void ); + void DeathMsg( int killer, int victim ); + + void ShowCommandMenu(int menuIndex); + void InputSignalHideCommandMenu( void ); + void HideCommandMenu( void ); + void SetCurrentCommandMenu( CCommandMenu *pNewMenu ); + void SetCurrentMenu( CMenuPanel *pMenu ); + + void ShowScoreBoard( void ); + void HideScoreBoard( void ); + bool IsScoreBoardVisible( void ); + + bool AllowedToPrintText( void ); + + void ShowVGUIMenu( int iMenu ); + void HideVGUIMenu( void ); + void HideTopMenu( void ); + + void ToggleServerBrowser( void ); + + CMenuPanel* CreateTextWindow( int iTextToShow ); + + CCommandMenu *CreateSubMenu( CommandButton *pButton, CCommandMenu *pParentMenu, int iYOffset, int iXOffset = 0 ); + + // Data Handlers + int GetValidClasses(int iTeam) { return m_iValidClasses[iTeam]; }; + int GetNumberOfTeams() { return m_iNumberOfTeams; }; + int GetIsFeigning() { return m_iIsFeigning; }; + int GetIsSettingDetpack() { return m_iIsSettingDetpack; }; + int GetBuildState() { return m_iBuildState; }; + int IsRandomPC() { return m_iRandomPC; }; + char *GetTeamName( int iTeam ) { return m_sTeamNames[iTeam]; }; + int GetAllowSpectators() { return m_iAllowSpectators; }; + + // Message Handlers + int MsgFunc_ValClass(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_TeamNames(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Feign(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Detpack(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_VGUIMenu(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_MOTD( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_BuildSt( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_RandomPC( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_ServerName( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_ScoreInfo( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_TeamScore( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_TeamInfo( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Spectator( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_AllowSpec( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_SpecFade( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_ResetFade( const char *pszName, int iSize, void *pbuf ); + + // Input + bool SlotInput( int iSlot ); + + virtual void paintBackground(); + + CSchemeManager *GetSchemeManager( void ) { return &m_SchemeManager; } + ScorePanel *GetScoreBoard( void ) { return m_pScoreBoard; } + + void *operator new( size_t stAllocateBlock ); + +public: + // VGUI Menus + CMenuPanel *m_pCurrentMenu; + CTeamMenuPanel *m_pTeamMenu; + int m_StandardMenu; // indexs in m_pCommandMenus + int m_SpectatorOptionsMenu; + int m_SpectatorCameraMenu; + int m_PlayerMenu; // a list of current player + CClassMenuPanel *m_pClassMenu; + ScorePanel *m_pScoreBoard; + SpectatorPanel * m_pSpectatorPanel; + char m_szServerName[ MAX_SERVERNAME_LENGTH ]; +}; + +//============================================================ +// Command Menu Button Handlers +#define MAX_COMMAND_SIZE 256 + +class CMenuHandler_StringCommand : public ActionSignal +{ +protected: + char m_pszCommand[MAX_COMMAND_SIZE]; + int m_iCloseVGUIMenu; +public: + CMenuHandler_StringCommand( char *pszCommand ) + { + strncpy( m_pszCommand, pszCommand, MAX_COMMAND_SIZE); + m_pszCommand[MAX_COMMAND_SIZE-1] = '\0'; + m_iCloseVGUIMenu = false; + } + + CMenuHandler_StringCommand( char *pszCommand, int iClose ) + { + strncpy( m_pszCommand, pszCommand, MAX_COMMAND_SIZE); + m_pszCommand[MAX_COMMAND_SIZE-1] = '\0'; + m_iCloseVGUIMenu = true; + } + + virtual void actionPerformed(Panel* panel) + { + gEngfuncs.pfnClientCmd(m_pszCommand); + + if (m_iCloseVGUIMenu) + gViewPort->HideTopMenu(); + else + gViewPort->HideCommandMenu(); + } +}; + +// This works the same as CMenuHandler_StringCommand, except it watches the string command +// for specific commands, and modifies client vars based upon them. +class CMenuHandler_StringCommandWatch : public CMenuHandler_StringCommand +{ +private: +public: + CMenuHandler_StringCommandWatch( char *pszCommand ) : CMenuHandler_StringCommand( pszCommand ) + { + } + + CMenuHandler_StringCommandWatch( char *pszCommand, int iClose ) : CMenuHandler_StringCommand( pszCommand, iClose ) + { + } + + virtual void actionPerformed(Panel* panel) + { + CMenuHandler_StringCommand::actionPerformed( panel ); + + // Try to guess the player's new team (it'll be corrected if it's wrong) + if ( !strcmp( m_pszCommand, "jointeam 1" ) ) + g_iTeamNumber = 1; + else if ( !strcmp( m_pszCommand, "jointeam 2" ) ) + g_iTeamNumber = 2; + else if ( !strcmp( m_pszCommand, "jointeam 3" ) ) + g_iTeamNumber = 3; + else if ( !strcmp( m_pszCommand, "jointeam 4" ) ) + g_iTeamNumber = 4; + } +}; + +// Used instead of CMenuHandler_StringCommand for Class Selection buttons. +// Checks the state of hud_classautokill and kills the player if set +class CMenuHandler_StringCommandClassSelect : public CMenuHandler_StringCommand +{ +private: +public: + CMenuHandler_StringCommandClassSelect( char *pszCommand ) : CMenuHandler_StringCommand( pszCommand ) + { + } + + CMenuHandler_StringCommandClassSelect( char *pszCommand, int iClose ) : CMenuHandler_StringCommand( pszCommand, iClose ) + { + } + + virtual void actionPerformed(Panel* panel); +}; + +class CMenuHandler_PopupSubMenuInput : public InputSignal +{ +private: + CCommandMenu *m_pSubMenu; + Button *m_pButton; +public: + CMenuHandler_PopupSubMenuInput( Button *pButton, CCommandMenu *pSubMenu ) + { + m_pSubMenu = pSubMenu; + m_pButton = pButton; + } + + virtual void cursorMoved(int x,int y,Panel* panel) + { + //gViewPort->SetCurrentCommandMenu( m_pSubMenu ); + } + + virtual void cursorEntered(Panel* panel) + { + gViewPort->SetCurrentCommandMenu( m_pSubMenu ); + + if (m_pButton) + m_pButton->setArmed(true); + }; + virtual void cursorExited(Panel* Panel) {}; + virtual void mousePressed(MouseCode code,Panel* panel) {}; + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {}; + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; +}; + +class CMenuHandler_LabelInput : public InputSignal +{ +private: + ActionSignal *m_pActionSignal; +public: + CMenuHandler_LabelInput( ActionSignal *pSignal ) + { + m_pActionSignal = pSignal; + } + + virtual void mousePressed(MouseCode code,Panel* panel) + { + m_pActionSignal->actionPerformed( panel ); + } + + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void cursorEntered(Panel* panel) {}; + virtual void cursorExited(Panel* Panel) {}; + virtual void cursorMoved(int x,int y,Panel* panel) {}; + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; +}; + +#define HIDE_TEXTWINDOW 0 +#define SHOW_MAPBRIEFING 1 +#define SHOW_CLASSDESC 2 +#define SHOW_MOTD 3 +#define SHOW_SPECHELP 4 + +class CMenuHandler_TextWindow : public ActionSignal +{ +private: + int m_iState; +public: + CMenuHandler_TextWindow( int iState ) + { + m_iState = iState; + } + + virtual void actionPerformed(Panel* panel) + { + if (m_iState == HIDE_TEXTWINDOW) + { + gViewPort->HideTopMenu(); + } + else + { + gViewPort->HideCommandMenu(); + gViewPort->ShowVGUIMenu( m_iState ); + } + } +}; + +class CMenuHandler_ToggleCvar : public ActionSignal +{ +private: + struct cvar_s * m_cvar; + +public: + CMenuHandler_ToggleCvar( char * cvarname ) + { + m_cvar = gEngfuncs.pfnGetCvarPointer( cvarname ); + } + + virtual void actionPerformed(Panel* panel) + { + if ( m_cvar->value ) + m_cvar->value = 0.0f; + else + m_cvar->value = 1.0f; + + // hide the menu + gViewPort->HideCommandMenu(); + + gViewPort->UpdateSpectatorPanel(); + } + + +}; + + + +class CMenuHandler_SpectateFollow : public ActionSignal +{ +protected: + char m_szplayer[MAX_COMMAND_SIZE]; +public: + CMenuHandler_SpectateFollow( char *player ) + { + strncpy( m_szplayer, player, MAX_COMMAND_SIZE); + m_szplayer[MAX_COMMAND_SIZE-1] = '\0'; + } + + virtual void actionPerformed(Panel* panel) + { + gHUD.m_Spectator.FindPlayer(m_szplayer); + gViewPort->HideCommandMenu(); + } +}; + + + +class CDragNDropHandler : public InputSignal +{ +private: + DragNDropPanel* m_pPanel; + bool m_bDragging; + int m_iaDragOrgPos[2]; + int m_iaDragStart[2]; + +public: + CDragNDropHandler(DragNDropPanel* pPanel) + { + m_pPanel = pPanel; + m_bDragging = false; + } + + void cursorMoved(int x,int y,Panel* panel); + void mousePressed(MouseCode code,Panel* panel); + void mouseReleased(MouseCode code,Panel* panel); + + void mouseDoublePressed(MouseCode code,Panel* panel) {}; + void cursorEntered(Panel* panel) {}; + void cursorExited(Panel* panel) {}; + void mouseWheeled(int delta,Panel* panel) {}; + void keyPressed(KeyCode code,Panel* panel) {}; + void keyTyped(KeyCode code,Panel* panel) {}; + void keyReleased(KeyCode code,Panel* panel) {}; + void keyFocusTicked(Panel* panel) {}; +}; + +class CHandler_MenuButtonOver : public InputSignal +{ +private: + int m_iButton; + CMenuPanel *m_pMenuPanel; +public: + CHandler_MenuButtonOver( CMenuPanel *pPanel, int iButton ) + { + m_iButton = iButton; + m_pMenuPanel = pPanel; + } + + void cursorEntered(Panel *panel); + + void cursorMoved(int x,int y,Panel* panel) {}; + void mousePressed(MouseCode code,Panel* panel) {}; + void mouseReleased(MouseCode code,Panel* panel) {}; + void mouseDoublePressed(MouseCode code,Panel* panel) {}; + void cursorExited(Panel* panel) {}; + void mouseWheeled(int delta,Panel* panel) {}; + void keyPressed(KeyCode code,Panel* panel) {}; + void keyTyped(KeyCode code,Panel* panel) {}; + void keyReleased(KeyCode code,Panel* panel) {}; + void keyFocusTicked(Panel* panel) {}; +}; + +class CHandler_ButtonHighlight : public InputSignal +{ +private: + Button *m_pButton; +public: + CHandler_ButtonHighlight( Button *pButton ) + { + m_pButton = pButton; + } + + virtual void cursorEntered(Panel* panel) + { + m_pButton->setArmed(true); + }; + virtual void cursorExited(Panel* Panel) + { + m_pButton->setArmed(false); + }; + virtual void mousePressed(MouseCode code,Panel* panel) {}; + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void cursorMoved(int x,int y,Panel* panel) {}; + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; +}; + +//----------------------------------------------------------------------------- +// Purpose: Special handler for highlighting of command menu buttons +//----------------------------------------------------------------------------- +class CHandler_CommandButtonHighlight : public CHandler_ButtonHighlight +{ +private: + CommandButton *m_pCommandButton; +public: + CHandler_CommandButtonHighlight( CommandButton *pButton ) : CHandler_ButtonHighlight( pButton ) + { + m_pCommandButton = pButton; + } + + virtual void cursorEntered( Panel *panel ) + { + m_pCommandButton->cursorEntered(); + } + + virtual void cursorExited( Panel *panel ) + { + m_pCommandButton->cursorExited(); + } +}; + + +//================================================================ +// Overidden Command Buttons for special visibilities +class ClassButton : public CommandButton +{ +protected: + int m_iPlayerClass; + +public: + ClassButton( int iClass, const char* text,int x,int y,int wide,int tall, bool bNoHighlight ) : CommandButton( text,x,y,wide,tall, bNoHighlight) + { + m_iPlayerClass = iClass; + } + + virtual int IsNotValid(); +}; + +class TeamButton : public CommandButton +{ +private: + int m_iTeamNumber; +public: + TeamButton( int iTeam, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iTeamNumber = iTeam; + } + + virtual int IsNotValid() + { + int iTeams = gViewPort->GetNumberOfTeams(); + // Never valid if there's only 1 team + if (iTeams == 1) + return true; + + // Auto Team's always visible + if (m_iTeamNumber == 5) + return false; + + if (iTeams >= m_iTeamNumber && m_iTeamNumber != g_iTeamNumber) + return false; + + return true; + } +}; + +class FeignButton : public CommandButton +{ +private: + int m_iFeignState; +public: + FeignButton( int iState, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iFeignState = iState; + } + + virtual int IsNotValid() + { + // Only visible for spies +#ifdef _TFC + if (g_iPlayerClass != PC_SPY) + return true; +#endif + + if (m_iFeignState == gViewPort->GetIsFeigning()) + return false; + return true; + } +}; + +class SpectateButton : public CommandButton +{ +public: + SpectateButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight ) : CommandButton( text,x,y,wide,tall, bNoHighlight) + { + } + + virtual int IsNotValid() + { + // Only visible if the server allows it + if ( gViewPort->GetAllowSpectators() != 0 ) + return false; + + return true; + } +}; + +#define DISGUISE_TEAM1 (1<<0) +#define DISGUISE_TEAM2 (1<<1) +#define DISGUISE_TEAM3 (1<<2) +#define DISGUISE_TEAM4 (1<<3) + +class DisguiseButton : public CommandButton +{ +private: + int m_iValidTeamsBits; + int m_iThisTeam; +public: + DisguiseButton( int iValidTeamNumsBits, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall,false ) + { + m_iValidTeamsBits = iValidTeamNumsBits; + } + + virtual int IsNotValid() + { +#ifdef _TFC + // Only visible for spies + if ( g_iPlayerClass != PC_SPY ) + return true; +#endif + + // if it's not tied to a specific team, then always show (for spies) + if ( !m_iValidTeamsBits ) + return false; + + // if we're tied to a team make sure we can change to that team + int iTmp = 1 << (gViewPort->GetNumberOfTeams() - 1); + if ( m_iValidTeamsBits & iTmp ) + return false; + return true; + } +}; + +class DetpackButton : public CommandButton +{ +private: + int m_iDetpackState; +public: + DetpackButton( int iState, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iDetpackState = iState; + } + + virtual int IsNotValid() + { +#ifdef _TFC + // Only visible for demomen + if (g_iPlayerClass != PC_DEMOMAN) + return true; +#endif + + if (m_iDetpackState == gViewPort->GetIsSettingDetpack()) + return false; + + return true; + } +}; + +extern int iBuildingCosts[]; +#define BUILDSTATE_HASBUILDING (1<<0) // Data is building ID (1 = Dispenser, 2 = Sentry, 3 = Entry Teleporter, 4 = Exit Teleporter) +#define BUILDSTATE_BUILDING (1<<1) +#define BUILDSTATE_BASE (1<<2) +#define BUILDSTATE_CANBUILD (1<<3) // Data is building ID (1 = Dispenser, 2 = Sentry, 3 = Entry Teleporter, 4 = Exit Teleporter) + +class BuildButton : public CommandButton +{ +private: + int m_iBuildState; + int m_iBuildData; + +public: + enum Buildings + { + DISPENSER = 0, + SENTRYGUN = 1, + ENTRY_TELEPORTER = 2, + EXIT_TELEPORTER = 3 + }; + + BuildButton( int iState, int iData, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iBuildState = iState; + m_iBuildData = iData; + } + + virtual int IsNotValid() + { +#ifdef _TFC + // Only visible for engineers + if (g_iPlayerClass != PC_ENGINEER) + return true; + + // If this isn't set, it's only active when they're not building + if (m_iBuildState & BUILDSTATE_BUILDING) + { + // Make sure the player's building + if ( !(gViewPort->GetBuildState() & BS_BUILDING) ) + return true; + } + else + { + // Make sure the player's not building + if ( gViewPort->GetBuildState() & BS_BUILDING ) + return true; + } + + if (m_iBuildState & BUILDSTATE_BASE) + { + // Only appear if we've got enough metal to build something, or something already built + if ( gViewPort->GetBuildState() & (BS_HAS_SENTRYGUN | BS_HAS_DISPENSER | BS_CANB_SENTRYGUN | BS_CANB_DISPENSER | BS_HAS_ENTRY_TELEPORTER | BS_HAS_EXIT_TELEPORTER | BS_CANB_ENTRY_TELEPORTER | BS_CANB_EXIT_TELEPORTER) ) + return false; + + return true; + } + + // Must have a building + if (m_iBuildState & BUILDSTATE_HASBUILDING) + { + if ( m_iBuildData == BuildButton::DISPENSER && !(gViewPort->GetBuildState() & BS_HAS_DISPENSER) ) + return true; + if ( m_iBuildData == BuildButton::SENTRYGUN && !(gViewPort->GetBuildState() & BS_HAS_SENTRYGUN) ) + return true; + if ( m_iBuildData == BuildButton::ENTRY_TELEPORTER && !(gViewPort->GetBuildState() & BS_HAS_ENTRY_TELEPORTER) ) + return true; + if ( m_iBuildData == BuildButton::EXIT_TELEPORTER && !(gViewPort->GetBuildState() & BS_HAS_EXIT_TELEPORTER) ) + return true; + } + + // Can build something + if (m_iBuildState & BUILDSTATE_CANBUILD) + { + // Make sure they've got the ammo and don't have one already + if ( m_iBuildData == BuildButton::DISPENSER && (gViewPort->GetBuildState() & BS_CANB_DISPENSER) ) + return false; + if ( m_iBuildData == BuildButton::SENTRYGUN && (gViewPort->GetBuildState() & BS_CANB_SENTRYGUN) ) + return false; + if ( m_iBuildData == BuildButton::ENTRY_TELEPORTER && (gViewPort->GetBuildState() & BS_CANB_ENTRY_TELEPORTER) ) + return false; + if ( m_iBuildData == BuildButton::EXIT_TELEPORTER && (gViewPort->GetBuildState() & BS_CANB_EXIT_TELEPORTER) ) + return false; + + return true; + } +#endif + return false; + } +}; + +#define MAX_MAPNAME 256 + +class MapButton : public CommandButton +{ +private: + char m_szMapName[ MAX_MAPNAME ]; + +public: + MapButton( const char *pMapName, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + sprintf( m_szMapName, "maps/%s.bsp", pMapName ); + } + + virtual int IsNotValid() + { + const char *level = gEngfuncs.pfnGetLevelName(); + if (!level) + return true; + + // Does it match the current map name? + if ( strcmp(m_szMapName, level) ) + return true; + + return false; + } +}; + +//----------------------------------------------------------------------------- +// Purpose: CommandButton which is only displayed if the player is on team X +//----------------------------------------------------------------------------- +class TeamOnlyCommandButton : public CommandButton +{ +private: + int m_iTeamNum; + +public: + TeamOnlyCommandButton( int iTeamNum, const char* text,int x,int y,int wide,int tall, bool flat ) : + CommandButton( text, x, y, wide, tall, false, flat ), m_iTeamNum(iTeamNum) {} + + virtual int IsNotValid() + { + if ( g_iTeamNumber != m_iTeamNum ) + return true; + + return CommandButton::IsNotValid(); + } +}; + +//----------------------------------------------------------------------------- +// Purpose: CommandButton which is only displayed if the player is on team X +//----------------------------------------------------------------------------- +class ToggleCommandButton : public CommandButton, public InputSignal +{ +private: + struct cvar_s * m_cvar; + CImageLabel * pLabelOn; + CImageLabel * pLabelOff; + + +public: + ToggleCommandButton( const char* cvarname, const char* text,int x,int y,int wide,int tall, bool flat ) : + CommandButton( text, x, y, wide, tall, false, flat ) + { + m_cvar = gEngfuncs.pfnGetCvarPointer( cvarname ); + + // Put a > to show it's a submenu + pLabelOn = new CImageLabel( "checked", 0, 0 ); + pLabelOn->setParent(this); + pLabelOn->addInputSignal(this); + + pLabelOff = new CImageLabel( "unchecked", 0, 0 ); + pLabelOff->setParent(this); + pLabelOff->setEnabled(true); + pLabelOff->addInputSignal(this); + + int textwide, texttall; + getTextSize( textwide, texttall); + + // Reposition + pLabelOn->setPos( textwide, (tall - pLabelOn->getTall()) / 2 ); + + pLabelOff->setPos( textwide, (tall - pLabelOff->getTall()) / 2 ); + + // Set text color to orange + setFgColor(Scheme::sc_primary1); + } + + virtual void cursorEntered(Panel* panel) + { + CommandButton::cursorEntered(); + } + + virtual void cursorExited(Panel* panel) + { + CommandButton::cursorExited(); + } + + virtual void mousePressed(MouseCode code,Panel* panel) + { + doClick(); + }; + + virtual void cursorMoved(int x,int y,Panel* panel) {}; + + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {}; + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; + + virtual void paint( void ) + { + if ( !m_cvar ) + { + pLabelOff->setVisible(false); + pLabelOn->setVisible(false); + } + else if ( m_cvar->value ) + { + pLabelOff->setVisible(false); + pLabelOn->setVisible(true); + } + else + { + pLabelOff->setVisible(true); + pLabelOn->setVisible(false); + } + + CommandButton::paint(); + + } +}; +class SpectToggleButton : public CommandButton, public InputSignal +{ +private: + struct cvar_s * m_cvar; + CImageLabel * pLabelOn; + +public: + SpectToggleButton( const char* cvarname, const char* text,int x,int y,int wide,int tall, bool flat ) : + CommandButton( text, x, y, wide, tall, false, flat ) + { + m_cvar = gEngfuncs.pfnGetCvarPointer( cvarname ); + + // Put a > to show it's a submenu + pLabelOn = new CImageLabel( "checked", 0, 0 ); + pLabelOn->setParent(this); + pLabelOn->addInputSignal(this); + + + int textwide, texttall; + getTextSize( textwide, texttall); + + // Reposition + pLabelOn->setPos( textwide, (tall - pLabelOn->getTall()) / 2 ); + } + + virtual void cursorEntered(Panel* panel) + { + CommandButton::cursorEntered(); + } + + virtual void cursorExited(Panel* panel) + { + CommandButton::cursorExited(); + } + + virtual void mousePressed(MouseCode code,Panel* panel) + { + doClick(); + }; + + virtual void cursorMoved(int x,int y,Panel* panel) {}; + + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {}; + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; + + virtual void paintBackground() + { + if ( isArmed() ) + { + drawSetColor( 143,143, 54, 125 ); + drawFilledRect( 5, 0,_size[0] - 5,_size[1]); + } + } + + virtual void paint( void ) + { + if ( isArmed() ) + { + setFgColor( 194, 202, 54, 0 ); + } + else + { + setFgColor( 143, 143, 54, 15 ); + } + + if ( !m_cvar ) + { + pLabelOn->setVisible(false); + } + else if ( m_cvar->value ) + { + pLabelOn->setVisible(true); + } + else + { + pLabelOn->setVisible(false); + } + + Button::paint(); + } +}; + +/* +class SpectToggleButton : public ToggleCommandButton +{ +private: + struct cvar_s * m_cvar; + CImageLabel * pLabelOn; + CImageLabel * pLabelOff; + +public: + + SpectToggleButton( const char* cvarname, const char* text,int x,int y,int wide,int tall, bool flat ) : + ToggleCommandButton( cvarname, text, x, y, wide, tall, flat, TRUE ) + { + m_cvar = gEngfuncs.pfnGetCvarPointer( cvarname ); + + // Put a > to show it's a submenu + pLabelOn = new CImageLabel( "checked", 0, 0 ); + pLabelOn->setParent(this); + pLabelOn->addInputSignal(this); + + pLabelOff = new CImageLabel( "unchecked", 0, 0 ); + pLabelOff->setParent(this); + pLabelOff->setEnabled(true); + pLabelOff->addInputSignal(this); + + int textwide, texttall; + getTextSize( textwide, texttall); + + // Reposition + pLabelOn->setPos( textwide, (tall - pLabelOn->getTall()) / 2 ); + + pLabelOff->setPos( textwide, (tall - pLabelOff->getTall()) / 2 ); + + // Set text color to orange + setFgColor(Scheme::sc_primary1); + } + + virtual void paintBackground() + { + if ( isArmed()) + { + drawSetColor( 143,143, 54, 125 ); + drawFilledRect( 5, 0,_size[0] - 5,_size[1]); + } + } + + virtual void paint() + { + + if ( isArmed() ) + { + setFgColor( 194, 202, 54, 0 ); + } + else + { + setFgColor( 143, 143, 54, 15 ); + } + + if ( !m_cvar ) + { + pLabelOff->setVisible(false); + pLabelOn->setVisible(false); + } + else if ( m_cvar->value ) + { + pLabelOff->setVisible(false); + pLabelOn->setVisible(true); + } + else + { + pLabelOff->setVisible(true); + pLabelOn->setVisible(false); + } + + Button::paint(); + } +}; +*/ +//============================================================ +// Panel that can be dragged around +class DragNDropPanel : public Panel +{ +private: + bool m_bBeingDragged; + LineBorder *m_pBorder; +public: + DragNDropPanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) + { + m_bBeingDragged = false; + + // Create the Drag Handler + addInputSignal( new CDragNDropHandler(this) ); + + // Create the border (for dragging) + m_pBorder = new LineBorder(); + } + + virtual void setDragged( bool bState ) + { + m_bBeingDragged = bState; + + if (m_bBeingDragged) + setBorder(m_pBorder); + else + setBorder(NULL); + } +}; + +//================================================================ +// Panel that draws itself with a transparent black background +class CTransparentPanel : public Panel +{ +private: + int m_iTransparency; +public: + CTransparentPanel(int iTrans, int x,int y,int wide,int tall) : Panel(x,y,wide,tall) + { + m_iTransparency = iTrans; + } + + virtual void paintBackground() + { + if (m_iTransparency) + { + // Transparent black background + drawSetColor( 0,0,0, m_iTransparency ); + drawFilledRect(0,0,_size[0],_size[1]); + } + } +}; + +//================================================================ +// Menu Panel that supports buffering of menus +class CMenuPanel : public CTransparentPanel +{ +private: + CMenuPanel *m_pNextMenu; + int m_iMenuID; + int m_iRemoveMe; + int m_iIsActive; + float m_flOpenTime; +public: + CMenuPanel(int iRemoveMe, int x,int y,int wide,int tall) : CTransparentPanel(100, x,y,wide,tall) + { + Reset(); + m_iRemoveMe = iRemoveMe; + } + + CMenuPanel(int iTrans, int iRemoveMe, int x,int y,int wide,int tall) : CTransparentPanel(iTrans, x,y,wide,tall) + { + Reset(); + m_iRemoveMe = iRemoveMe; + } + + virtual void Reset( void ) + { + m_pNextMenu = NULL; + m_iIsActive = false; + m_flOpenTime = 0; + } + + void SetNextMenu( CMenuPanel *pNextPanel ) + { + if (m_pNextMenu) + m_pNextMenu->SetNextMenu( pNextPanel ); + else + m_pNextMenu = pNextPanel; + } + + void SetMenuID( int iID ) + { + m_iMenuID = iID; + } + + void SetActive( int iState ) + { + m_iIsActive = iState; + } + + virtual void Open( void ) + { + setVisible( true ); + + // Note the open time, so we can delay input for a bit + m_flOpenTime = gHUD.m_flTime; + } + + virtual void Close( void ) + { + setVisible( false ); + m_iIsActive = false; + + if ( m_iRemoveMe ) + gViewPort->removeChild( this ); + + // This MenuPanel has now been deleted. Don't append code here. + } + + int ShouldBeRemoved() { return m_iRemoveMe; }; + CMenuPanel* GetNextMenu() { return m_pNextMenu; }; + int GetMenuID() { return m_iMenuID; }; + int IsActive() { return m_iIsActive; }; + float GetOpenTime() { return m_flOpenTime; }; + + // Numeric input + virtual bool SlotInput( int iSlot ) { return false; }; + virtual void SetActiveInfo( int iInput ) {}; +}; + +//================================================================ +// Custom drawn scroll bars +class CTFScrollButton : public CommandButton +{ +private: + BitmapTGA *m_pTGA; + +public: + CTFScrollButton(int iArrow, const char* text,int x,int y,int wide,int tall); + + virtual void paint( void ); + virtual void paintBackground( void ); +}; + +// Custom drawn slider bar +class CTFSlider : public Slider +{ +public: + CTFSlider(int x,int y,int wide,int tall,bool vertical) : Slider(x,y,wide,tall,vertical) + { + }; + + virtual void paintBackground( void ); +}; + +// Custom drawn scrollpanel +class CTFScrollPanel : public ScrollPanel +{ +public: + CTFScrollPanel(int x,int y,int wide,int tall); +}; + +//================================================================ +// Menu Panels that take key input +//============================================================ +class CClassMenuPanel : public CMenuPanel +{ +private: + CTransparentPanel *m_pClassInfoPanel[PC_LASTCLASS]; + Label *m_pPlayers[PC_LASTCLASS]; + ClassButton *m_pButtons[PC_LASTCLASS]; + CommandButton *m_pCancelButton; + ScrollPanel *m_pScrollPanel; + + CImageLabel *m_pClassImages[MAX_TEAMS][PC_LASTCLASS]; + + int m_iCurrentInfo; + + enum { STRLENMAX_PLAYERSONTEAM = 128 }; + char m_sPlayersOnTeamString[STRLENMAX_PLAYERSONTEAM]; + +public: + CClassMenuPanel(int iTrans, int iRemoveMe, int x,int y,int wide,int tall); + + virtual bool SlotInput( int iSlot ); + virtual void Open( void ); + virtual void Update( void ); + virtual void SetActiveInfo( int iInput ); + virtual void Initialize( void ); + + virtual void Reset( void ) + { + CMenuPanel::Reset(); + m_iCurrentInfo = 0; + } +}; + +class CTeamMenuPanel : public CMenuPanel +{ +public: + ScrollPanel *m_pScrollPanel; + CTransparentPanel *m_pTeamWindow; + Label *m_pMapTitle; + TextPanel *m_pBriefing; + TextPanel *m_pTeamInfoPanel[6]; + CommandButton *m_pButtons[6]; + bool m_bUpdatedMapName; + CommandButton *m_pCancelButton; + CommandButton *m_pSpectateButton; + + int m_iCurrentInfo; + +public: + CTeamMenuPanel(int iTrans, int iRemoveMe, int x,int y,int wide,int tall); + + virtual bool SlotInput( int iSlot ); + virtual void Open( void ); + virtual void Update( void ); + virtual void SetActiveInfo( int iInput ); + virtual void paintBackground( void ); + + virtual void Initialize( void ); + + virtual void Reset( void ) + { + CMenuPanel::Reset(); + m_iCurrentInfo = 0; + } +}; + +//========================================================= +// Specific Menus to handle old HUD sections +class CHealthPanel : public DragNDropPanel +{ +private: + BitmapTGA *m_pHealthTGA; + Label *m_pHealthLabel; +public: + CHealthPanel(int x,int y,int wide,int tall) : DragNDropPanel(x,y,wide,tall) + { + // Load the Health icon + FileInputStream* fis = new FileInputStream( GetVGUITGAName("%d_hud_health"), false); + m_pHealthTGA = new BitmapTGA(fis,true); + fis->close(); + + // Create the Health Label + int iXSize,iYSize; + m_pHealthTGA->getSize(iXSize,iYSize); + m_pHealthLabel = new Label("",0,0,iXSize,iYSize); + m_pHealthLabel->setImage(m_pHealthTGA); + m_pHealthLabel->setParent(this); + + // Set panel dimension + // Shouldn't be needed once Billy's fized setImage not recalculating the size + //setSize( iXSize + 100, gHUD.m_iFontHeight + 10 ); + //m_pHealthLabel->setPos( 10, (getTall() - iYSize) / 2 ); + } + + virtual void paintBackground() + { + } + + void paint() + { + // Get the paint color + int r,g,b,a; + // Has health changed? Flash the health # + if (gHUD.m_Health.m_fFade) + { + gHUD.m_Health.m_fFade -= (gHUD.m_flTimeDelta * 20); + if (gHUD.m_Health.m_fFade <= 0) + { + a = MIN_ALPHA; + gHUD.m_Health.m_fFade = 0; + } + + // Fade the health number back to dim + a = MIN_ALPHA + (gHUD.m_Health.m_fFade/FADE_TIME) * 128; + } + else + a = MIN_ALPHA; + + gHUD.m_Health.GetPainColor( r, g, b ); + ScaleColors(r, g, b, a ); + + // If health is getting low, make it bright red + if (gHUD.m_Health.m_iHealth <= 15) + a = 255; + + int iXSize,iYSize, iXPos, iYPos; + m_pHealthTGA->getSize(iXSize,iYSize); + m_pHealthTGA->getPos(iXPos, iYPos); + + // Paint the player's health + int x = gHUD.DrawHudNumber( iXPos + iXSize + 5, iYPos + 5, DHN_3DIGITS | DHN_DRAWZERO, gHUD.m_Health.m_iHealth, r, g, b); + + // Draw the vertical line + int HealthWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; + x += HealthWidth / 2; + FillRGBA(x, iYPos + 5, HealthWidth / 10, gHUD.m_iFontHeight, 255, 160, 0, a); + } +}; + +#endif diff --git a/cl_dll/vgui_int.cpp b/cl_dll/vgui_int.cpp new file mode 100644 index 0000000..e684fa0 --- /dev/null +++ b/cl_dll/vgui_int.cpp @@ -0,0 +1,128 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include"vgui_int.h" +#include +#include +#include +#include +#include +#include +#include +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ControlConfigPanel.h" + +namespace +{ + +class TexturePanel : public Panel , public ActionSignal +{ +private: + int _bindIndex; + TextEntry* _textEntry; +public: + TexturePanel() : Panel(0,0,256,276) + { + _bindIndex=2700; + _textEntry=new TextEntry("2700",0,0,128,20); + _textEntry->setParent(this); + _textEntry->addActionSignal(this); + } +public: + virtual bool isWithin(int x,int y) + { + return _textEntry->isWithin(x,y); + } +public: + virtual void actionPerformed(Panel* panel) + { + char buf[256]; + _textEntry->getText(0,buf,256); + sscanf(buf,"%d",&_bindIndex); + } +protected: + virtual void paintBackground() + { + Panel::paintBackground(); + + int wide,tall; + getPaintSize(wide,tall); + + drawSetColor(0,0,255,0); + drawSetTexture(_bindIndex); + drawTexturedRect(0,19,257,257); + } + +}; + +} + +using namespace vgui; + +void VGui_ViewportPaintBackground(int extents[4]) +{ + gEngfuncs.VGui_ViewportPaintBackground(extents); +} + +void* VGui_GetPanel() +{ + return (Panel*)gEngfuncs.VGui_GetPanel(); +} + +void VGui_Startup() +{ + Panel* root=(Panel*)VGui_GetPanel(); + root->setBgColor(128,128,0,0); + //root->setNonPainted(false); + //root->setBorder(new LineBorder()); + root->setLayout(new BorderLayout(0)); + + + //root->getSurfaceBase()->setEmulatedCursorVisible(true); + + if (gViewPort != NULL) + { +// root->removeChild(gViewPort); + + // free the memory +// delete gViewPort; +// gViewPort = NULL; + + gViewPort->Initialize(); + } + else + { + gViewPort = new TeamFortressViewport(0,0,root->getWide(),root->getTall()); + gViewPort->setParent(root); + } + + /* + TexturePanel* texturePanel=new TexturePanel(); + texturePanel->setParent(gViewPort); + */ + +} + +void VGui_Shutdown() +{ + delete gViewPort; + gViewPort = NULL; +} + + + + + diff --git a/cl_dll/vgui_int.h b/cl_dll/vgui_int.h new file mode 100644 index 0000000..6510853 --- /dev/null +++ b/cl_dll/vgui_int.h @@ -0,0 +1,21 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_INT_H +#define VGUI_INT_H + +extern "C" +{ +void VGui_Startup(); +void VGui_Shutdown(); + +//Only safe to call from inside subclass of Panel::paintBackground +void VGui_ViewportPaintBackground(int extents[4]); +} + + +#endif \ No newline at end of file diff --git a/cl_dll/vgui_teammenu.cpp b/cl_dll/vgui_teammenu.cpp new file mode 100644 index 0000000..96430f5 --- /dev/null +++ b/cl_dll/vgui_teammenu.cpp @@ -0,0 +1,393 @@ +//=========== (C) Copyright 1996-2002 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: TFC Team Menu +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "vgui_int.h" +#include "VGUI_Font.h" +#include "VGUI_ScrollPanel.h" +#include "VGUI_TextImage.h" + +#include "hud.h" +#include "cl_util.h" +#include "vgui_TeamFortressViewport.h" + +// Team Menu Dimensions +#define TEAMMENU_TITLE_X XRES(40) +#define TEAMMENU_TITLE_Y YRES(32) +#define TEAMMENU_TOPLEFT_BUTTON_X XRES(40) +#define TEAMMENU_TOPLEFT_BUTTON_Y YRES(80) +#define TEAMMENU_BUTTON_SIZE_X XRES(124) +#define TEAMMENU_BUTTON_SIZE_Y YRES(24) +#define TEAMMENU_BUTTON_SPACER_Y YRES(8) +#define TEAMMENU_WINDOW_X XRES(176) +#define TEAMMENU_WINDOW_Y YRES(80) +#define TEAMMENU_WINDOW_SIZE_X XRES(424) +#define TEAMMENU_WINDOW_SIZE_Y YRES(312) +#define TEAMMENU_WINDOW_TITLE_X XRES(16) +#define TEAMMENU_WINDOW_TITLE_Y YRES(16) +#define TEAMMENU_WINDOW_TEXT_X XRES(16) +#define TEAMMENU_WINDOW_TEXT_Y YRES(48) +#define TEAMMENU_WINDOW_TEXT_SIZE_Y YRES(178) +#define TEAMMENU_WINDOW_INFO_X XRES(16) +#define TEAMMENU_WINDOW_INFO_Y YRES(234) + +// Creation +CTeamMenuPanel::CTeamMenuPanel(int iTrans, int iRemoveMe, int x,int y,int wide,int tall) : CMenuPanel(iTrans, iRemoveMe, x,y,wide,tall) +{ + // Get the scheme used for the Titles + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + + // schemes + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle( "Title Font" ); + SchemeHandle_t hTeamWindowText = pSchemes->getSchemeHandle( "Briefing Text" ); + SchemeHandle_t hTeamInfoText = pSchemes->getSchemeHandle( "Team Info Text" ); + + // get the Font used for the Titles + Font *pTitleFont = pSchemes->getFont( hTitleScheme ); + int r, g, b, a; + + // Create the title + Label *pLabel = new Label( "", TEAMMENU_TITLE_X, TEAMMENU_TITLE_Y ); + pLabel->setParent( this ); + pLabel->setFont( pTitleFont ); + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + pLabel->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + pLabel->setBgColor( r, g, b, a ); + pLabel->setContentAlignment( vgui::Label::a_west ); + pLabel->setText( "%s", gHUD.m_TextMessage.BufferedLocaliseTextString("#Title_SelectYourTeam")); + + // Create the Info Window + m_pTeamWindow = new CTransparentPanel( 255, TEAMMENU_WINDOW_X, TEAMMENU_WINDOW_Y, TEAMMENU_WINDOW_SIZE_X, TEAMMENU_WINDOW_SIZE_Y ); + m_pTeamWindow->setParent( this ); + m_pTeamWindow->setBorder( new LineBorder( Color(255*0.7,170*0.7,0,0 )) ); + + // Create the Map Name Label + m_pMapTitle = new Label( "", TEAMMENU_WINDOW_TITLE_X, TEAMMENU_WINDOW_TITLE_Y ); + m_pMapTitle->setFont( pTitleFont ); + m_pMapTitle->setParent( m_pTeamWindow ); + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + m_pMapTitle->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + m_pMapTitle->setBgColor( r, g, b, a ); + m_pMapTitle->setContentAlignment( vgui::Label::a_west ); + + // Create the Scroll panel + m_pScrollPanel = new CTFScrollPanel( TEAMMENU_WINDOW_TEXT_X, TEAMMENU_WINDOW_TEXT_Y, TEAMMENU_WINDOW_SIZE_X - (TEAMMENU_WINDOW_TEXT_X * 2), TEAMMENU_WINDOW_TEXT_SIZE_Y ); + m_pScrollPanel->setParent(m_pTeamWindow); + m_pScrollPanel->setScrollBarVisible(false, false); + + // Create the Map Briefing panel + m_pBriefing = new TextPanel("", 0,0, TEAMMENU_WINDOW_SIZE_X - TEAMMENU_WINDOW_TEXT_X, TEAMMENU_WINDOW_TEXT_SIZE_Y ); + m_pBriefing->setParent( m_pScrollPanel->getClient() ); + m_pBriefing->setFont( pSchemes->getFont(hTeamWindowText) ); + pSchemes->getFgColor( hTeamWindowText, r, g, b, a ); + m_pBriefing->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hTeamWindowText, r, g, b, a ); + m_pBriefing->setBgColor( r, g, b, a ); + + m_pBriefing->setText( gHUD.m_TextMessage.BufferedLocaliseTextString("#Map_Description_not_available") ); + + // Team Menu buttons + for (int i = 1; i <= 5; i++) + { + char sz[256]; + + int iYPos = TEAMMENU_TOPLEFT_BUTTON_Y + ( (TEAMMENU_BUTTON_SIZE_Y + TEAMMENU_BUTTON_SPACER_Y) * i ); + + // Team button + m_pButtons[i] = new CommandButton( "", TEAMMENU_TOPLEFT_BUTTON_X, iYPos, TEAMMENU_BUTTON_SIZE_X, TEAMMENU_BUTTON_SIZE_Y, true); + m_pButtons[i]->setParent( this ); + m_pButtons[i]->setContentAlignment( vgui::Label::a_west ); + m_pButtons[i]->setVisible( false ); + + // AutoAssign button uses special case + if (i == 5) + { + m_pButtons[5]->setBoundKey( '5' ); + m_pButtons[5]->setText( gHUD.m_TextMessage.BufferedLocaliseTextString("#Team_AutoAssign") ); + m_pButtons[5]->setVisible( true ); + } + + // Create the Signals + sprintf(sz, "jointeam %d", i); + m_pButtons[i]->addActionSignal( new CMenuHandler_StringCommandWatch( sz, true ) ); + m_pButtons[i]->addInputSignal( new CHandler_MenuButtonOver(this, i) ); + + // Create the Team Info panel + m_pTeamInfoPanel[i] = new TextPanel("", TEAMMENU_WINDOW_INFO_X, TEAMMENU_WINDOW_INFO_Y, TEAMMENU_WINDOW_SIZE_X - TEAMMENU_WINDOW_INFO_X, TEAMMENU_WINDOW_SIZE_X - TEAMMENU_WINDOW_INFO_Y ); + m_pTeamInfoPanel[i]->setParent( m_pTeamWindow ); + m_pTeamInfoPanel[i]->setFont( pSchemes->getFont(hTeamInfoText) ); + m_pTeamInfoPanel[i]->setFgColor( iTeamColors[i % iNumberOfTeamColors][0], + iTeamColors[i % iNumberOfTeamColors][1], + iTeamColors[i % iNumberOfTeamColors][2], + 0 ); + m_pTeamInfoPanel[i]->setBgColor( 0,0,0, 255 ); + } + + // Create the Cancel button + m_pCancelButton = new CommandButton( CHudTextMessage::BufferedLocaliseTextString( "#Menu_Cancel" ), TEAMMENU_TOPLEFT_BUTTON_X, 0, TEAMMENU_BUTTON_SIZE_X, TEAMMENU_BUTTON_SIZE_Y); + m_pCancelButton->setParent( this ); + m_pCancelButton->addActionSignal( new CMenuHandler_TextWindow(HIDE_TEXTWINDOW) ); + + // Create the Spectate button + m_pSpectateButton = new SpectateButton( CHudTextMessage::BufferedLocaliseTextString( "#Menu_Spectate" ), TEAMMENU_TOPLEFT_BUTTON_X, 0, TEAMMENU_BUTTON_SIZE_X, TEAMMENU_BUTTON_SIZE_Y, true); + m_pSpectateButton->setParent( this ); + m_pSpectateButton->addActionSignal( new CMenuHandler_StringCommand( "spectate", true ) ); + m_pSpectateButton->setBoundKey( '6' ); + m_pSpectateButton->addInputSignal( new CHandler_MenuButtonOver(this, 6) ); + + Initialize(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called each time a new level is started. +//----------------------------------------------------------------------------- +void CTeamMenuPanel::Initialize( void ) +{ + m_bUpdatedMapName = false; + m_iCurrentInfo = 0; + m_pScrollPanel->setScrollValue( 0, 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Called everytime the Team Menu is displayed +//----------------------------------------------------------------------------- +void CTeamMenuPanel::Update( void ) +{ + int iYPos = TEAMMENU_TOPLEFT_BUTTON_Y; + + // Set the team buttons + for (int i = 1; i <= 4; i++) + { + if (m_pButtons[i]) + { + if ( i <= gViewPort->GetNumberOfTeams() ) + { + m_pButtons[i]->setText( gViewPort->GetTeamName(i) ); + + // bound key replacement + char sz[32]; + sprintf( sz, "%d", i ); + m_pButtons[i]->setBoundKey( sz[0] ); + + m_pButtons[i]->setVisible( true ); + m_pButtons[i]->setPos( TEAMMENU_TOPLEFT_BUTTON_X, iYPos ); + iYPos += TEAMMENU_BUTTON_SIZE_Y + TEAMMENU_BUTTON_SPACER_Y; + + // Start with the first option up + if (!m_iCurrentInfo) + SetActiveInfo( i ); + + char szPlayerList[ (MAX_PLAYER_NAME_LENGTH + 3) * 31 ]; // name + ", " + strcpy(szPlayerList, "\n"); + // Update the Team Info + // Now count the number of teammembers of this class + int iTotal = 0; + for ( int j = 1; j < MAX_PLAYERS; j++ ) + { + if ( g_PlayerInfoList[j].name == NULL ) + continue; // empty player slot, skip + if ( g_PlayerInfoList[j].thisplayer ) + continue; // skip this player + if ( g_PlayerExtraInfo[j].teamnumber != i ) + continue; // skip over players in other teams + + iTotal++; + if (iTotal > 1) + strncat( szPlayerList, ", ", sizeof(szPlayerList) - strlen(szPlayerList) ); + strncat( szPlayerList, g_PlayerInfoList[j].name, sizeof(szPlayerList) - strlen(szPlayerList) ); + szPlayerList[ sizeof(szPlayerList) - 1 ] = '\0'; + } + + if (iTotal > 0) + { + // Set the text of the info Panel + char szText[ ((MAX_PLAYER_NAME_LENGTH + 3) * 31) + 256 ]; + if (iTotal == 1) + sprintf(szText, "%s: %d Player (%d points)", gViewPort->GetTeamName(i), iTotal, g_TeamInfo[i].frags ); + else + sprintf(szText, "%s: %d Players (%d points)", gViewPort->GetTeamName(i), iTotal, g_TeamInfo[i].frags ); + strncat( szText, szPlayerList, sizeof(szText) - strlen(szText) ); + szText[ sizeof(szText) - 1 ] = '\0'; + + m_pTeamInfoPanel[i]->setText( szText ); + } + else + { + m_pTeamInfoPanel[i]->setText( "" ); + } + } + else + { + // Hide the button (may be visible from previous maps) + m_pButtons[i]->setVisible( false ); + } + } + } + + // Move the AutoAssign button into place + m_pButtons[5]->setPos( TEAMMENU_TOPLEFT_BUTTON_X, iYPos ); + iYPos += TEAMMENU_BUTTON_SIZE_Y + TEAMMENU_BUTTON_SPACER_Y; + + // Spectate button + if (m_pSpectateButton->IsNotValid()) + { + m_pSpectateButton->setVisible( false ); + } + else + { + m_pSpectateButton->setPos( TEAMMENU_TOPLEFT_BUTTON_X, iYPos ); + m_pSpectateButton->setVisible( true ); + iYPos += TEAMMENU_BUTTON_SIZE_Y + TEAMMENU_BUTTON_SPACER_Y; + } + + // If the player is already in a team, make the cancel button visible + if ( g_iTeamNumber ) + { + m_pCancelButton->setPos( TEAMMENU_TOPLEFT_BUTTON_X, iYPos ); + iYPos += TEAMMENU_BUTTON_SIZE_Y + TEAMMENU_BUTTON_SPACER_Y; + m_pCancelButton->setVisible( true ); + } + else + { + m_pCancelButton->setVisible( false ); + } + + // Set the Map Title + if (!m_bUpdatedMapName) + { + const char *level = gEngfuncs.pfnGetLevelName(); + if (level && level[0]) + { + char sz[256]; + char szTitle[256]; + char *ch; + + // Update the level name + strcpy( sz, level ); + ch = strchr( sz, '/' ); + if (!ch) + ch = strchr( sz, '\\' ); + strcpy( szTitle, ch+1 ); + ch = strchr( szTitle, '.' ); + *ch = '\0'; + m_pMapTitle->setText( "%s", szTitle ); + *ch = '.'; + + // Update the map briefing + strcpy( sz, level ); + ch = strchr( sz, '.' ); + *ch = '\0'; + strcat( sz, ".txt" ); + char *pfile = (char*)gEngfuncs.COM_LoadFile( sz, 5, NULL ); + if (pfile) + { + m_pBriefing->setText( pfile ); + + // Get the total size of the Briefing text and resize the text panel + int iXSize, iYSize; + m_pBriefing->getTextImage()->getTextSize( iXSize, iYSize ); + m_pBriefing->setSize( iXSize, iYSize ); + gEngfuncs.COM_FreeFile( pfile ); + } + + m_bUpdatedMapName = true; + } + } + + m_pScrollPanel->validate(); +} + +//===================================== +// Key inputs +bool CTeamMenuPanel::SlotInput( int iSlot ) +{ + // Check for AutoAssign + if ( iSlot == 5) + { + m_pButtons[5]->fireActionSignal(); + return true; + } + + // Spectate + if ( iSlot == 6) + { + m_pSpectateButton->fireActionSignal(); + return true; + } + + // Otherwise, see if a particular team is selectable + if ( (iSlot < 1) || (iSlot > gViewPort->GetNumberOfTeams()) ) + return false; + if ( !m_pButtons[ iSlot ] ) + return false; + + // Is the button pushable? + if ( m_pButtons[ iSlot ]->isVisible() ) + { + m_pButtons[ iSlot ]->fireActionSignal(); + return true; + } + + return false; +} + +//====================================== +// Update the Team menu before opening it +void CTeamMenuPanel::Open( void ) +{ + Update(); + CMenuPanel::Open(); +} + +void CTeamMenuPanel::paintBackground() +{ + // make sure we get the map briefing up + if ( !m_bUpdatedMapName ) + Update(); + + CMenuPanel::paintBackground(); +} + +//====================================== +// Mouse is over a team button, bring up the class info +void CTeamMenuPanel::SetActiveInfo( int iInput ) +{ + // Remove all the Info panels and bring up the specified one + m_pSpectateButton->setArmed( false ); + for (int i = 1; i <= 5; i++) + { + m_pButtons[i]->setArmed( false ); + m_pTeamInfoPanel[i]->setVisible( false ); + } + + // 6 is Spectate + if (iInput == 6) + { + m_pSpectateButton->setArmed( true ); + } + else + { + m_pButtons[iInput]->setArmed( true ); + m_pTeamInfoPanel[iInput]->setVisible( true ); + } + + m_iCurrentInfo = iInput; + + m_pScrollPanel->validate(); +} diff --git a/cl_dll/view.cpp b/cl_dll/view.cpp new file mode 100644 index 0000000..796be73 --- /dev/null +++ b/cl_dll/view.cpp @@ -0,0 +1,1802 @@ +// view/refresh setup functions + +#include "hud.h" +#include "cl_util.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" + +#include "entity_state.h" +#include "cl_entity.h" +#include "ref_params.h" +#include "in_defs.h" // PITCH YAW ROLL +#include "pm_movevars.h" +#include "pm_shared.h" +#include "pm_defs.h" +#include "event_api.h" +#include "pmtrace.h" +#include "bench.h" +#include "screenfade.h" +#include "shake.h" +#include "hltv.h" +#include "Exports.h" + + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + + int CL_IsThirdPerson( void ); + void CL_CameraOffset( float *ofs ); + + void CL_DLLEXPORT V_CalcRefdef( struct ref_params_s *pparams ); + + void PM_ParticleLine( float *start, float *end, int pcolor, float life, float vert); + int PM_GetVisEntInfo( int ent ); + extern "C" int PM_GetPhysEntInfo( int ent ); + void InterpolateAngles( float * start, float * end, float * output, float frac ); + void NormalizeAngles( float * angles ); + extern "C" float Distance(const float * v1, const float * v2); + float AngleBetweenVectors( const float * v1, const float * v2 ); + + float vJumpOrigin[3]; + float vJumpAngles[3]; + + +void V_DropPunchAngle ( float frametime, float *ev_punchangle ); +void VectorAngles( const float *forward, float *angles ); + +#include "r_studioint.h" +#include "com_model.h" +#include "kbutton.h" + +extern engine_studio_api_t IEngineStudio; + +extern kbutton_t in_mlook; + +/* +The view is allowed to move slightly from it's true position for bobbing, +but if it exceeds 8 pixels linear distance (spherical, not box), the list of +entities sent from the server may not include everything in the pvs, especially +when crossing a water boudnary. +*/ + +extern cvar_t *cl_forwardspeed; +extern cvar_t *chase_active; +extern cvar_t *scr_ofsx, *scr_ofsy, *scr_ofsz; +extern cvar_t *cl_vsmoothing; + +#define CAM_MODE_RELAX 1 +#define CAM_MODE_FOCUS 2 + +vec3_t v_origin, v_angles, v_cl_angles, v_sim_org, v_lastAngles; +float v_frametime, v_lastDistance; +float v_cameraRelaxAngle = 5.0f; +float v_cameraFocusAngle = 35.0f; +int v_cameraMode = CAM_MODE_FOCUS; +qboolean v_resetCamera = 1; + +vec3_t ev_punchangle; + +cvar_t *scr_ofsx; +cvar_t *scr_ofsy; +cvar_t *scr_ofsz; + +cvar_t *v_centermove; +cvar_t *v_centerspeed; + +cvar_t *cl_bobcycle; +cvar_t *cl_bob; +cvar_t *cl_bobup; +cvar_t *cl_waterdist; +cvar_t *cl_chasedist; + +// These cvars are not registered (so users can't cheat), so set the ->value field directly +// Register these cvars in V_Init() if needed for easy tweaking +cvar_t v_iyaw_cycle = {"v_iyaw_cycle", "2", 0, 2}; +cvar_t v_iroll_cycle = {"v_iroll_cycle", "0.5", 0, 0.5}; +cvar_t v_ipitch_cycle = {"v_ipitch_cycle", "1", 0, 1}; +cvar_t v_iyaw_level = {"v_iyaw_level", "0.3", 0, 0.3}; +cvar_t v_iroll_level = {"v_iroll_level", "0.1", 0, 0.1}; +cvar_t v_ipitch_level = {"v_ipitch_level", "0.3", 0, 0.3}; + +float v_idlescale; // used by TFC for concussion grenade effect + +//============================================================================= +/* +void V_NormalizeAngles( float *angles ) +{ + int i; + // Normalize angles + for ( i = 0; i < 3; i++ ) + { + if ( angles[i] > 180.0 ) + { + angles[i] -= 360.0; + } + else if ( angles[i] < -180.0 ) + { + angles[i] += 360.0; + } + } +} + +/* +=================== +V_InterpolateAngles + +Interpolate Euler angles. +FIXME: Use Quaternions to avoid discontinuities +Frac is 0.0 to 1.0 ( i.e., should probably be clamped, but doesn't have to be ) +=================== + +void V_InterpolateAngles( float *start, float *end, float *output, float frac ) +{ + int i; + float ang1, ang2; + float d; + + V_NormalizeAngles( start ); + V_NormalizeAngles( end ); + + for ( i = 0 ; i < 3 ; i++ ) + { + ang1 = start[i]; + ang2 = end[i]; + + d = ang2 - ang1; + if ( d > 180 ) + { + d -= 360; + } + else if ( d < -180 ) + { + d += 360; + } + + output[i] = ang1 + d * frac; + } + + V_NormalizeAngles( output ); +} */ + +// Quakeworld bob code, this fixes jitters in the mutliplayer since the clock (pparams->time) isn't quite linear +float V_CalcBob ( struct ref_params_s *pparams ) +{ + static double bobtime; + static float bob; + float cycle; + static float lasttime; + vec3_t vel; + + + if ( pparams->onground == -1 || + pparams->time == lasttime ) + { + // just use old value + return bob; + } + + lasttime = pparams->time; + + bobtime += pparams->frametime; + cycle = bobtime - (int)( bobtime / cl_bobcycle->value ) * cl_bobcycle->value; + cycle /= cl_bobcycle->value; + + if ( cycle < cl_bobup->value ) + { + cycle = M_PI * cycle / cl_bobup->value; + } + else + { + cycle = M_PI + M_PI * ( cycle - cl_bobup->value )/( 1.0 - cl_bobup->value ); + } + + // bob is proportional to simulated velocity in the xy plane + // (don't count Z, or jumping messes it up) + VectorCopy( pparams->simvel, vel ); + vel[2] = 0; + + bob = sqrt( vel[0] * vel[0] + vel[1] * vel[1] ) * cl_bob->value; + bob = bob * 0.3 + bob * 0.7 * sin(cycle); + bob = min( bob, 4 ); + bob = max( bob, -7 ); + return bob; + +} + +/* +=============== +V_CalcRoll +Used by view and sv_user +=============== +*/ +float V_CalcRoll (vec3_t angles, vec3_t velocity, float rollangle, float rollspeed ) +{ + float sign; + float side; + float value; + vec3_t forward, right, up; + + AngleVectors ( angles, forward, right, up ); + + side = DotProduct (velocity, right); + sign = side < 0 ? -1 : 1; + side = fabs( side ); + + value = rollangle; + if (side < rollspeed) + { + side = side * value / rollspeed; + } + else + { + side = value; + } + return side * sign; +} + +typedef struct pitchdrift_s +{ + float pitchvel; + int nodrift; + float driftmove; + double laststop; +} pitchdrift_t; + +static pitchdrift_t pd; + +void V_StartPitchDrift( void ) +{ + if ( pd.laststop == gEngfuncs.GetClientTime() ) + { + return; // something else is keeping it from drifting + } + + if ( pd.nodrift || !pd.pitchvel ) + { + pd.pitchvel = v_centerspeed->value; + pd.nodrift = 0; + pd.driftmove = 0; + } +} + +void V_StopPitchDrift ( void ) +{ + pd.laststop = gEngfuncs.GetClientTime(); + pd.nodrift = 1; + pd.pitchvel = 0; +} + +/* +=============== +V_DriftPitch + +Moves the client pitch angle towards idealpitch sent by the server. + +If the user is adjusting pitch manually, either with lookup/lookdown, +mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped. +=============== +*/ +void V_DriftPitch ( struct ref_params_s *pparams ) +{ + float delta, move; + + if ( gEngfuncs.IsNoClipping() || !pparams->onground || pparams->demoplayback || pparams->spectator ) + { + pd.driftmove = 0; + pd.pitchvel = 0; + return; + } + + // don't count small mouse motion + if ( pd.nodrift) + { + if ( v_centermove->value > 0 && !(in_mlook.state & 1) ) + { + // this is for lazy players. if they stopped, looked around and then continued + // to move the view will be centered automatically if they move more than + // v_centermove units. + + if ( fabs( pparams->cmd->forwardmove ) < cl_forwardspeed->value ) + pd.driftmove = 0; + else + pd.driftmove += pparams->frametime; + + if ( pd.driftmove > v_centermove->value) + { + V_StartPitchDrift (); + } + else + { + return; // player didn't move enough + } + } + + return; // don't drift view + } + + delta = pparams->idealpitch - pparams->cl_viewangles[PITCH]; + + if (!delta) + { + pd.pitchvel = 0; + return; + } + + move = pparams->frametime * pd.pitchvel; + + pd.pitchvel *= (1.0f+(pparams->frametime*0.25f)); // get faster by time + + if (delta > 0) + { + if (move > delta) + { + pd.pitchvel = 0; + move = delta; + } + pparams->cl_viewangles[PITCH] += move; + } + else if (delta < 0) + { + if (move > -delta) + { + pd.pitchvel = 0; + move = -delta; + } + pparams->cl_viewangles[PITCH] -= move; + } +} + +/* +============================================================================== + VIEW RENDERING +============================================================================== +*/ + +/* +================== +V_CalcGunAngle +================== +*/ +void V_CalcGunAngle ( struct ref_params_s *pparams ) +{ + cl_entity_t *viewent; + + viewent = gEngfuncs.GetViewModel(); + if ( !viewent ) + return; + + viewent->angles[YAW] = pparams->viewangles[YAW] + pparams->crosshairangle[YAW]; + viewent->angles[PITCH] = -pparams->viewangles[PITCH] + pparams->crosshairangle[PITCH] * 0.25; + viewent->angles[ROLL] -= v_idlescale * sin(pparams->time*v_iroll_cycle.value) * v_iroll_level.value; + + // don't apply all of the v_ipitch to prevent normally unseen parts of viewmodel from coming into view. + viewent->angles[PITCH] -= v_idlescale * sin(pparams->time*v_ipitch_cycle.value) * (v_ipitch_level.value * 0.5); + viewent->angles[YAW] -= v_idlescale * sin(pparams->time*v_iyaw_cycle.value) * v_iyaw_level.value; + + VectorCopy( viewent->angles, viewent->curstate.angles ); + VectorCopy( viewent->angles, viewent->latched.prevangles ); +} + +/* +============== +V_AddIdle + +Idle swaying +============== +*/ +void V_AddIdle ( struct ref_params_s *pparams ) +{ + pparams->viewangles[ROLL] += v_idlescale * sin(pparams->time*v_iroll_cycle.value) * v_iroll_level.value; + pparams->viewangles[PITCH] += v_idlescale * sin(pparams->time*v_ipitch_cycle.value) * v_ipitch_level.value; + pparams->viewangles[YAW] += v_idlescale * sin(pparams->time*v_iyaw_cycle.value) * v_iyaw_level.value; +} + + +/* +============== +V_CalcViewRoll + +Roll is induced by movement and damage +============== +*/ +void V_CalcViewRoll ( struct ref_params_s *pparams ) +{ + float side; + cl_entity_t *viewentity; + + viewentity = gEngfuncs.GetEntityByIndex( pparams->viewentity ); + if ( !viewentity ) + return; + + side = V_CalcRoll ( viewentity->angles, pparams->simvel, pparams->movevars->rollangle, pparams->movevars->rollspeed ); + + pparams->viewangles[ROLL] += side; + + if ( pparams->health <= 0 && ( pparams->viewheight[2] != 0 ) ) + { + // only roll the view if the player is dead and the viewheight[2] is nonzero + // this is so deadcam in multiplayer will work. + pparams->viewangles[ROLL] = 80; // dead view angle + return; + } +} + + +/* +================== +V_CalcIntermissionRefdef + +================== +*/ +void V_CalcIntermissionRefdef ( struct ref_params_s *pparams ) +{ + cl_entity_t *ent, *view; + float old; + + // ent is the player model ( visible when out of body ) + ent = gEngfuncs.GetLocalPlayer(); + + // view is the weapon model (only visible from inside body ) + view = gEngfuncs.GetViewModel(); + + VectorCopy ( pparams->simorg, pparams->vieworg ); + VectorCopy ( pparams->cl_viewangles, pparams->viewangles ); + + view->model = NULL; + + // allways idle in intermission + old = v_idlescale; + v_idlescale = 1; + + V_AddIdle ( pparams ); + + if ( gEngfuncs.IsSpectateOnly() ) + { + // in HLTV we must go to 'intermission' position by ourself + VectorCopy( gHUD.m_Spectator.m_cameraOrigin, pparams->vieworg ); + VectorCopy( gHUD.m_Spectator.m_cameraAngles, pparams->viewangles ); + } + + v_idlescale = old; + + v_cl_angles = pparams->cl_viewangles; + v_origin = pparams->vieworg; + v_angles = pparams->viewangles; +} + +#define ORIGIN_BACKUP 64 +#define ORIGIN_MASK ( ORIGIN_BACKUP - 1 ) + +typedef struct +{ + float Origins[ ORIGIN_BACKUP ][3]; + float OriginTime[ ORIGIN_BACKUP ]; + + float Angles[ ORIGIN_BACKUP ][3]; + float AngleTime[ ORIGIN_BACKUP ]; + + int CurrentOrigin; + int CurrentAngle; +} viewinterp_t; + +/* +================== +V_CalcRefdef + +================== +*/ +void V_CalcNormalRefdef ( struct ref_params_s *pparams ) +{ + cl_entity_t *ent, *view; + int i; + vec3_t angles; + float bob, waterOffset; + static viewinterp_t ViewInterp; + + static float oldz = 0; + static float lasttime; + + vec3_t camAngles, camForward, camRight, camUp; + cl_entity_t *pwater; + + V_DriftPitch ( pparams ); + + if ( gEngfuncs.IsSpectateOnly() ) + { + ent = gEngfuncs.GetEntityByIndex( g_iUser2 ); + } + else + { + // ent is the player model ( visible when out of body ) + ent = gEngfuncs.GetLocalPlayer(); + } + + // view is the weapon model (only visible from inside body ) + view = gEngfuncs.GetViewModel(); + + // transform the view offset by the model's matrix to get the offset from + // model origin for the view + bob = V_CalcBob ( pparams ); + + // refresh position + VectorCopy ( pparams->simorg, pparams->vieworg ); + pparams->vieworg[2] += ( bob ); + VectorAdd( pparams->vieworg, pparams->viewheight, pparams->vieworg ); + + VectorCopy ( pparams->cl_viewangles, pparams->viewangles ); + + gEngfuncs.V_CalcShake(); + gEngfuncs.V_ApplyShake( pparams->vieworg, pparams->viewangles, 1.0 ); + + // never let view origin sit exactly on a node line, because a water plane can + // dissapear when viewed with the eye exactly on it. + // FIXME, we send origin at 1/128 now, change this? + // the server protocol only specifies to 1/16 pixel, so add 1/32 in each axis + + pparams->vieworg[0] += 1.0/32; + pparams->vieworg[1] += 1.0/32; + pparams->vieworg[2] += 1.0/32; + + // Check for problems around water, move the viewer artificially if necessary + // -- this prevents drawing errors in GL due to waves + + waterOffset = 0; + if ( pparams->waterlevel >= 2 ) + { + int i, contents, waterDist, waterEntity; + vec3_t point; + waterDist = cl_waterdist->value; + + if ( pparams->hardware ) + { + waterEntity = gEngfuncs.PM_WaterEntity( pparams->simorg ); + if ( waterEntity >= 0 && waterEntity < pparams->max_entities ) + { + pwater = gEngfuncs.GetEntityByIndex( waterEntity ); + if ( pwater && ( pwater->model != NULL ) ) + { + waterDist += ( pwater->curstate.scale * 16 ); // Add in wave height + } + } + } + else + { + waterEntity = 0; // Don't need this in software + } + + VectorCopy( pparams->vieworg, point ); + + // Eyes are above water, make sure we're above the waves + if ( pparams->waterlevel == 2 ) + { + point[2] -= waterDist; + for ( i = 0; i < waterDist; i++ ) + { + contents = gEngfuncs.PM_PointContents( point, NULL ); + if ( contents > CONTENTS_WATER ) + break; + point[2] += 1; + } + waterOffset = (point[2] + waterDist) - pparams->vieworg[2]; + } + else + { + // eyes are under water. Make sure we're far enough under + point[2] += waterDist; + + for ( i = 0; i < waterDist; i++ ) + { + contents = gEngfuncs.PM_PointContents( point, NULL ); + if ( contents <= CONTENTS_WATER ) + break; + point[2] -= 1; + } + waterOffset = (point[2] - waterDist) - pparams->vieworg[2]; + } + } + + pparams->vieworg[2] += waterOffset; + + V_CalcViewRoll ( pparams ); + + V_AddIdle ( pparams ); + + // offsets + VectorCopy( pparams->cl_viewangles, angles ); + + AngleVectors ( angles, pparams->forward, pparams->right, pparams->up ); + + // don't allow cheats in multiplayer + if ( pparams->maxclients <= 1 ) + { + for ( i=0 ; i<3 ; i++ ) + { + pparams->vieworg[i] += scr_ofsx->value*pparams->forward[i] + scr_ofsy->value*pparams->right[i] + scr_ofsz->value*pparams->up[i]; + } + } + + // Treating cam_ofs[2] as the distance + if( CL_IsThirdPerson() ) + { + vec3_t ofs; + + ofs[0] = ofs[1] = ofs[2] = 0.0; + + CL_CameraOffset( (float *)&ofs ); + + VectorCopy( ofs, camAngles ); + camAngles[ ROLL ] = 0; + + AngleVectors( camAngles, camForward, camRight, camUp ); + + for ( i = 0; i < 3; i++ ) + { + pparams->vieworg[ i ] += -ofs[2] * camForward[ i ]; + } + } + + // Give gun our viewangles + VectorCopy ( pparams->cl_viewangles, view->angles ); + + // set up gun position + V_CalcGunAngle ( pparams ); + + // Use predicted origin as view origin. + VectorCopy ( pparams->simorg, view->origin ); + view->origin[2] += ( waterOffset ); + VectorAdd( view->origin, pparams->viewheight, view->origin ); + + // Let the viewmodel shake at about 10% of the amplitude + gEngfuncs.V_ApplyShake( view->origin, view->angles, 0.9 ); + + for ( i = 0; i < 3; i++ ) + { + view->origin[ i ] += bob * 0.4 * pparams->forward[ i ]; + } + view->origin[2] += bob; + + // throw in a little tilt. + view->angles[YAW] -= bob * 0.5; + view->angles[ROLL] -= bob * 1; + view->angles[PITCH] -= bob * 0.3; + + // pushing the view origin down off of the same X/Z plane as the ent's origin will give the + // gun a very nice 'shifting' effect when the player looks up/down. If there is a problem + // with view model distortion, this may be a cause. (SJB). + view->origin[2] -= 1; + + // fudge position around to keep amount of weapon visible + // roughly equal with different FOV + if (pparams->viewsize == 110) + { + view->origin[2] += 1; + } + else if (pparams->viewsize == 100) + { + view->origin[2] += 2; + } + else if (pparams->viewsize == 90) + { + view->origin[2] += 1; + } + else if (pparams->viewsize == 80) + { + view->origin[2] += 0.5; + } + + // Add in the punchangle, if any + VectorAdd ( pparams->viewangles, pparams->punchangle, pparams->viewangles ); + + // Include client side punch, too + VectorAdd ( pparams->viewangles, (float *)&ev_punchangle, pparams->viewangles); + + V_DropPunchAngle ( pparams->frametime, (float *)&ev_punchangle ); + + // smooth out stair step ups +#if 1 + if ( !pparams->smoothing && pparams->onground && pparams->simorg[2] - oldz > 0) + { + float steptime; + + steptime = pparams->time - lasttime; + if (steptime < 0) + //FIXME I_Error ("steptime < 0"); + steptime = 0; + + oldz += steptime * 150; + if (oldz > pparams->simorg[2]) + oldz = pparams->simorg[2]; + if (pparams->simorg[2] - oldz > 18) + oldz = pparams->simorg[2]- 18; + pparams->vieworg[2] += oldz - pparams->simorg[2]; + view->origin[2] += oldz - pparams->simorg[2]; + } + else + { + oldz = pparams->simorg[2]; + } +#endif + + { + static float lastorg[3]; + vec3_t delta; + + VectorSubtract( pparams->simorg, lastorg, delta ); + + if ( Length( delta ) != 0.0 ) + { + VectorCopy( pparams->simorg, ViewInterp.Origins[ ViewInterp.CurrentOrigin & ORIGIN_MASK ] ); + ViewInterp.OriginTime[ ViewInterp.CurrentOrigin & ORIGIN_MASK ] = pparams->time; + ViewInterp.CurrentOrigin++; + + VectorCopy( pparams->simorg, lastorg ); + } + } + + // Smooth out whole view in multiplayer when on trains, lifts + if ( cl_vsmoothing && cl_vsmoothing->value && + ( pparams->smoothing && ( pparams->maxclients > 1 ) ) ) + { + int foundidx; + int i; + float t; + + if ( cl_vsmoothing->value < 0.0 ) + { + gEngfuncs.Cvar_SetValue( "cl_vsmoothing", 0.0 ); + } + + t = pparams->time - cl_vsmoothing->value; + + for ( i = 1; i < ORIGIN_MASK; i++ ) + { + foundidx = ViewInterp.CurrentOrigin - 1 - i; + if ( ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ] <= t ) + break; + } + + if ( i < ORIGIN_MASK && ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ] != 0.0 ) + { + // Interpolate + vec3_t delta; + double frac; + double dt; + vec3_t neworg; + + dt = ViewInterp.OriginTime[ (foundidx + 1) & ORIGIN_MASK ] - ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ]; + if ( dt > 0.0 ) + { + frac = ( t - ViewInterp.OriginTime[ foundidx & ORIGIN_MASK] ) / dt; + frac = min( 1.0, frac ); + VectorSubtract( ViewInterp.Origins[ ( foundidx + 1 ) & ORIGIN_MASK ], ViewInterp.Origins[ foundidx & ORIGIN_MASK ], delta ); + VectorMA( ViewInterp.Origins[ foundidx & ORIGIN_MASK ], frac, delta, neworg ); + + // Dont interpolate large changes + if ( Length( delta ) < 64 ) + { + VectorSubtract( neworg, pparams->simorg, delta ); + + VectorAdd( pparams->simorg, delta, pparams->simorg ); + VectorAdd( pparams->vieworg, delta, pparams->vieworg ); + VectorAdd( view->origin, delta, view->origin ); + + } + } + } + } + + // Store off v_angles before munging for third person + v_angles = pparams->viewangles; + v_lastAngles = pparams->viewangles; +// v_cl_angles = pparams->cl_viewangles; // keep old user mouse angles ! + if ( CL_IsThirdPerson() ) + { + VectorCopy( camAngles, pparams->viewangles); + float pitch = camAngles[ 0 ]; + + // Normalize angles + if ( pitch > 180 ) + pitch -= 360.0; + else if ( pitch < -180 ) + pitch += 360; + + // Player pitch is inverted + pitch /= -3.0; + + // Slam local player's pitch value + ent->angles[ 0 ] = pitch; + ent->curstate.angles[ 0 ] = pitch; + ent->prevstate.angles[ 0 ] = pitch; + ent->latched.prevangles[ 0 ] = pitch; + } + + // override all previous settings if the viewent isn't the client + if ( pparams->viewentity > pparams->maxclients ) + { + cl_entity_t *viewentity; + viewentity = gEngfuncs.GetEntityByIndex( pparams->viewentity ); + if ( viewentity ) + { + VectorCopy( viewentity->origin, pparams->vieworg ); + VectorCopy( viewentity->angles, pparams->viewangles ); + + // Store off overridden viewangles + v_angles = pparams->viewangles; + } + } + + lasttime = pparams->time; + + v_origin = pparams->vieworg; +} + +void V_SmoothInterpolateAngles( float * startAngle, float * endAngle, float * finalAngle, float degreesPerSec ) +{ + float absd,frac,d,threshhold; + + NormalizeAngles( startAngle ); + NormalizeAngles( endAngle ); + + for ( int i = 0 ; i < 3 ; i++ ) + { + d = endAngle[i] - startAngle[i]; + + if ( d > 180.0f ) + { + d -= 360.0f; + } + else if ( d < -180.0f ) + { + d += 360.0f; + } + + absd = fabs(d); + + if ( absd > 0.01f ) + { + frac = degreesPerSec * v_frametime; + + threshhold= degreesPerSec / 4; + + if ( absd < threshhold ) + { + float h = absd / threshhold; + h *= h; + frac*= h; // slow down last degrees + } + + if ( frac > absd ) + { + finalAngle[i] = endAngle[i]; + } + else + { + if ( d>0) + finalAngle[i] = startAngle[i] + frac; + else + finalAngle[i] = startAngle[i] - frac; + } + } + else + { + finalAngle[i] = endAngle[i]; + } + + } + + NormalizeAngles( finalAngle ); +} + +// Get the origin of the Observer based around the target's position and angles +void V_GetChaseOrigin( float * angles, float * origin, float distance, float * returnvec ) +{ + vec3_t vecEnd; + vec3_t forward; + vec3_t vecStart; + pmtrace_t * trace; + int maxLoops = 8; + + int ignoreent = -1; // first, ignore no entity + + cl_entity_t * ent = NULL; + + // Trace back from the target using the player's view angles + AngleVectors(angles, forward, NULL, NULL); + + VectorScale(forward,-1,forward); + + VectorCopy( origin, vecStart ); + + VectorMA(vecStart, distance , forward, vecEnd); + + while ( maxLoops > 0) + { + trace = gEngfuncs.PM_TraceLine( vecStart, vecEnd, PM_TRACELINE_PHYSENTSONLY, 2, ignoreent ); + + // WARNING! trace->ent is is the number in physent list not the normal entity number + + if ( trace->ent <= 0) + break; // we hit the world or nothing, stop trace + + ent = gEngfuncs.GetEntityByIndex( PM_GetPhysEntInfo( trace->ent ) ); + + if ( ent == NULL ) + break; + + // hit non-player solid BSP , stop here + if ( ent->curstate.solid == SOLID_BSP && !ent->player ) + break; + + // if close enought to end pos, stop, otherwise continue trace + if( Distance(trace->endpos, vecEnd ) < 1.0f ) + { + break; + } + else + { + ignoreent = trace->ent; // ignore last hit entity + VectorCopy( trace->endpos, vecStart); + } + + maxLoops--; + } + +/* if ( ent ) + { + gEngfuncs.Con_Printf("Trace loops %i , entity %i, model %s, solid %i\n",(8-maxLoops),ent->curstate.number, ent->model->name , ent->curstate.solid ); + } */ + + VectorMA( trace->endpos, 4, trace->plane.normal, returnvec ); + + v_lastDistance = Distance(trace->endpos, origin); // real distance without offset +} + +/*void V_GetDeathCam(cl_entity_t * ent1, cl_entity_t * ent2, float * angle, float * origin) +{ + float newAngle[3]; float newOrigin[3]; + + float distance = 168.0f; + + v_lastDistance+= v_frametime * 96.0f; // move unit per seconds back + + if ( v_resetCamera ) + v_lastDistance = 64.0f; + + if ( distance > v_lastDistance ) + distance = v_lastDistance; + + VectorCopy(ent1->origin, newOrigin); + + if ( ent1->player ) + newOrigin[2]+= 17; // head level of living player + + // get new angle towards second target + if ( ent2 ) + { + VectorSubtract( ent2->origin, ent1->origin, newAngle ); + VectorAngles( newAngle, newAngle ); + newAngle[0] = -newAngle[0]; + } + else + { + // if no second target is given, look down to dead player + newAngle[0] = 90.0f; + newAngle[1] = 0.0f; + newAngle[2] = 0; + } + + // and smooth view + V_SmoothInterpolateAngles( v_lastAngles, newAngle, angle, 120.0f ); + + V_GetChaseOrigin( angle, newOrigin, distance, origin ); + + VectorCopy(angle, v_lastAngles); +}*/ + +void V_GetSingleTargetCam(cl_entity_t * ent1, float * angle, float * origin) +{ + float newAngle[3]; float newOrigin[3]; + + int flags = gHUD.m_Spectator.m_iObserverFlags; + + // see is target is a dead player + qboolean deadPlayer = ent1->player && (ent1->curstate.solid == SOLID_NOT); + + float dfactor = ( flags & DRC_FLAG_DRAMATIC )? -1.0f : 1.0f; + + float distance = 112.0f + ( 16.0f * dfactor ); // get close if dramatic; + + // go away in final scenes or if player just died + if ( flags & DRC_FLAG_FINAL ) + distance*=2.0f; + else if ( deadPlayer ) + distance*=1.5f; + + // let v_lastDistance float smoothly away + v_lastDistance+= v_frametime * 32.0f; // move unit per seconds back + + if ( distance > v_lastDistance ) + distance = v_lastDistance; + + VectorCopy(ent1->origin, newOrigin); + + if ( ent1->player ) + { + if ( deadPlayer ) + newOrigin[2]+= 2; //laying on ground + else + newOrigin[2]+= 17; // head level of living player + + } + else + newOrigin[2]+= 8; // object, tricky, must be above bomb in CS + + // we have no second target, choose view direction based on + // show front of primary target + VectorCopy(ent1->angles, newAngle); + + // show dead players from front, normal players back + if ( flags & DRC_FLAG_FACEPLAYER ) + newAngle[1]+= 180.0f; + + + newAngle[0]+= 12.5f * dfactor; // lower angle if dramatic + + // if final scene (bomb), show from real high pos + if ( flags & DRC_FLAG_FINAL ) + newAngle[0] = 22.5f; + + // choose side of object/player + if ( flags & DRC_FLAG_SIDE ) + newAngle[1]+=22.5f; + else + newAngle[1]-=22.5f; + + V_SmoothInterpolateAngles( v_lastAngles, newAngle, angle, 120.0f ); + + // HACK, if player is dead don't clip against his dead body, can't check this + V_GetChaseOrigin( angle, newOrigin, distance, origin ); +} + +float MaxAngleBetweenAngles( float * a1, float * a2 ) +{ + float d, maxd = 0.0f; + + NormalizeAngles( a1 ); + NormalizeAngles( a2 ); + + for ( int i = 0 ; i < 3 ; i++ ) + { + d = a2[i] - a1[i]; + if ( d > 180 ) + { + d -= 360; + } + else if ( d < -180 ) + { + d += 360; + } + + d = fabs(d); + + if ( d > maxd ) + maxd=d; + } + + return maxd; +} + +void V_GetDoubleTargetsCam(cl_entity_t * ent1, cl_entity_t * ent2,float * angle, float * origin) +{ + float newAngle[3]; float newOrigin[3]; float tempVec[3]; + + int flags = gHUD.m_Spectator.m_iObserverFlags; + + float dfactor = ( flags & DRC_FLAG_DRAMATIC )? -1.0f : 1.0f; + + float distance = 112.0f + ( 16.0f * dfactor ); // get close if dramatic; + + // go away in final scenes or if player just died + if ( flags & DRC_FLAG_FINAL ) + distance*=2.0f; + + // let v_lastDistance float smoothly away + v_lastDistance+= v_frametime * 32.0f; // move unit per seconds back + + if ( distance > v_lastDistance ) + distance = v_lastDistance; + + VectorCopy(ent1->origin, newOrigin); + + if ( ent1->player ) + newOrigin[2]+= 17; // head level of living player + else + newOrigin[2]+= 8; // object, tricky, must be above bomb in CS + + // get new angle towards second target + VectorSubtract( ent2->origin, ent1->origin, newAngle ); + + VectorAngles( newAngle, newAngle ); + newAngle[0] = -newAngle[0]; + + // set angle diffrent in Dramtaic scenes + newAngle[0]+= 12.5f * dfactor; // lower angle if dramatic + + if ( flags & DRC_FLAG_SIDE ) + newAngle[1]+=22.5f; + else + newAngle[1]-=22.5f; + + float d = MaxAngleBetweenAngles( v_lastAngles, newAngle ); + + if ( ( d < v_cameraFocusAngle) && ( v_cameraMode == CAM_MODE_RELAX ) ) + { + // difference is to small and we are in relax camera mode, keep viewangles + VectorCopy(v_lastAngles, newAngle ); + } + else if ( (d < v_cameraRelaxAngle) && (v_cameraMode == CAM_MODE_FOCUS) ) + { + // we catched up with our target, relax again + v_cameraMode = CAM_MODE_RELAX; + } + else + { + // target move too far away, focus camera again + v_cameraMode = CAM_MODE_FOCUS; + } + + // and smooth view, if not a scene cut + if ( v_resetCamera || (v_cameraMode == CAM_MODE_RELAX) ) + { + VectorCopy( newAngle, angle ); + } + else + { + V_SmoothInterpolateAngles( v_lastAngles, newAngle, angle, 180.0f ); + } + + V_GetChaseOrigin( newAngle, newOrigin, distance, origin ); + + // move position up, if very close at target + if ( v_lastDistance < 64.0f ) + origin[2]+= 16.0f*( 1.0f - (v_lastDistance / 64.0f ) ); + + // calculate angle to second target + VectorSubtract( ent2->origin, origin, tempVec ); + VectorAngles( tempVec, tempVec ); + tempVec[0] = -tempVec[0]; + + /* take middle between two viewangles + InterpolateAngles( newAngle, tempVec, newAngle, 0.5f); */ + + + +} + +void V_GetDirectedChasePosition(cl_entity_t * ent1, cl_entity_t * ent2,float * angle, float * origin) +{ + + if ( v_resetCamera ) + { + v_lastDistance = 4096.0f; + // v_cameraMode = CAM_MODE_FOCUS; + } + + if ( ( ent2 == (cl_entity_t*)0xFFFFFFFF ) || ( ent1->player && (ent1->curstate.solid == SOLID_NOT) ) ) + { + // we have no second target or player just died + V_GetSingleTargetCam(ent1, angle, origin); + } + else if ( ent2 ) + { + // keep both target in view + V_GetDoubleTargetsCam( ent1, ent2, angle, origin ); + } + else + { + // second target disappeard somehow (dead) + + // keep last good viewangle + float newOrigin[3]; + + int flags = gHUD.m_Spectator.m_iObserverFlags; + + float dfactor = ( flags & DRC_FLAG_DRAMATIC )? -1.0f : 1.0f; + + float distance = 112.0f + ( 16.0f * dfactor ); // get close if dramatic; + + // go away in final scenes or if player just died + if ( flags & DRC_FLAG_FINAL ) + distance*=2.0f; + + // let v_lastDistance float smoothly away + v_lastDistance+= v_frametime * 32.0f; // move unit per seconds back + + if ( distance > v_lastDistance ) + distance = v_lastDistance; + + VectorCopy(ent1->origin, newOrigin); + + if ( ent1->player ) + newOrigin[2]+= 17; // head level of living player + else + newOrigin[2]+= 8; // object, tricky, must be above bomb in CS + + V_GetChaseOrigin( angle, newOrigin, distance, origin ); + } + + VectorCopy(angle, v_lastAngles); +} + +void V_GetChasePos(int target, float * cl_angles, float * origin, float * angles) +{ + cl_entity_t * ent = NULL; + + if ( target ) + { + ent = gEngfuncs.GetEntityByIndex( target ); + }; + + if (!ent) + { + // just copy a save in-map position + VectorCopy ( vJumpAngles, angles ); + VectorCopy ( vJumpOrigin, origin ); + return; + } + + + + if ( gHUD.m_Spectator.m_autoDirector->value ) + { + if ( g_iUser3 ) + V_GetDirectedChasePosition( ent, gEngfuncs.GetEntityByIndex( g_iUser3 ), + angles, origin ); + else + V_GetDirectedChasePosition( ent, ( cl_entity_t*)0xFFFFFFFF, + angles, origin ); + } + else + { + if ( cl_angles == NULL ) // no mouse angles given, use entity angles ( locked mode ) + { + VectorCopy ( ent->angles, angles); + angles[0]*=-1; + } + else + VectorCopy ( cl_angles, angles); + + + VectorCopy ( ent->origin, origin); + + origin[2]+= 28; // DEFAULT_VIEWHEIGHT - some offset + + V_GetChaseOrigin( angles, origin, cl_chasedist->value, origin ); + } + + v_resetCamera = false; +} + +void V_ResetChaseCam() +{ + v_resetCamera = true; +} + + +void V_GetInEyePos(int target, float * origin, float * angles ) +{ + if ( !target) + { + // just copy a save in-map position + VectorCopy ( vJumpAngles, angles ); + VectorCopy ( vJumpOrigin, origin ); + return; + }; + + + cl_entity_t * ent = gEngfuncs.GetEntityByIndex( target ); + + if ( !ent ) + return; + + VectorCopy ( ent->origin, origin ); + VectorCopy ( ent->angles, angles ); + + angles[PITCH]*=-3.0f; // see CL_ProcessEntityUpdate() + + if ( ent->curstate.solid == SOLID_NOT ) + { + angles[ROLL] = 80; // dead view angle + origin[2]+= -8 ; // PM_DEAD_VIEWHEIGHT + } + else if (ent->curstate.usehull == 1 ) + origin[2]+= 12; // VEC_DUCK_VIEW; + else + // exacty eye position can't be caluculated since it depends on + // client values like cl_bobcycle, this offset matches the default values + origin[2]+= 28; // DEFAULT_VIEWHEIGHT +} + +void V_GetMapFreePosition( float * cl_angles, float * origin, float * angles ) +{ + vec3_t forward; + vec3_t zScaledTarget; + + VectorCopy(cl_angles, angles); + + // modify angles since we don't wanna see map's bottom + angles[0] = 51.25f + 38.75f*(angles[0]/90.0f); + + zScaledTarget[0] = gHUD.m_Spectator.m_mapOrigin[0]; + zScaledTarget[1] = gHUD.m_Spectator.m_mapOrigin[1]; + zScaledTarget[2] = gHUD.m_Spectator.m_mapOrigin[2] * (( 90.0f - angles[0] ) / 90.0f ); + + + AngleVectors(angles, forward, NULL, NULL); + + VectorNormalize(forward); + + VectorMA(zScaledTarget, -( 4096.0f / gHUD.m_Spectator.m_mapZoom ), forward , origin); +} + +void V_GetMapChasePosition(int target, float * cl_angles, float * origin, float * angles) +{ + vec3_t forward; + + if ( target ) + { + cl_entity_t * ent = gEngfuncs.GetEntityByIndex( target ); + + if ( gHUD.m_Spectator.m_autoDirector->value ) + { + // this is done to get the angles made by director mode + V_GetChasePos(target, cl_angles, origin, angles); + VectorCopy(ent->origin, origin); + + // keep fix chase angle horizontal + angles[0] = 45.0f; + } + else + { + VectorCopy(cl_angles, angles); + VectorCopy(ent->origin, origin); + + // modify angles since we don't wanna see map's bottom + angles[0] = 51.25f + 38.75f*(angles[0]/90.0f); + } + } + else + { + // keep out roaming position, but modify angles + VectorCopy(cl_angles, angles); + angles[0] = 51.25f + 38.75f*(angles[0]/90.0f); + } + + origin[2] *= (( 90.0f - angles[0] ) / 90.0f ); + angles[2] = 0.0f; // don't roll angle (if chased player is dead) + + AngleVectors(angles, forward, NULL, NULL); + + VectorNormalize(forward); + + VectorMA(origin, -1536, forward, origin); +} + +int V_FindViewModelByWeaponModel(int weaponindex) +{ + + static char * modelmap[][2] = { + +# ifdef _TFC // TFC models override HL models + { "models/p_mini.mdl", "models/v_tfac.mdl" }, + { "models/p_sniper.mdl", "models/v_tfc_sniper.mdl" }, + { "models/p_umbrella.mdl", "models/v_umbrella.mdl" }, + { "models/p_crowbar.mdl", "models/v_tfc_crowbar.mdl" }, + { "models/p_spanner.mdl", "models/v_tfc_spanner.mdl" }, + { "models/p_knife.mdl", "models/v_tfc_knife.mdl" }, + { "models/p_medkit.mdl", "models/v_tfc_medkit.mdl" }, + { "models/p_egon.mdl", "models/v_flame.mdl" }, + { "models/p_glauncher.mdl", "models/v_tfgl.mdl" }, + { "models/p_rpg.mdl", "models/v_tfc_rpg.mdl" }, + { "models/p_nailgun.mdl", "models/v_tfc_nailgun.mdl" }, + { "models/p_snailgun.mdl", "models/v_tfc_supernailgun.mdl" }, + { "models/p_9mmhandgun.mdl", "models/v_tfc_railgun.mdl" }, + { "models/p_srpg.mdl", "models/v_tfc_rpg.mdl" }, + { "models/p_smallshotgun.mdl", "models/v_tfc_12gauge.mdl" }, + { "models/p_shotgun.mdl", "models/v_tfc_shotgun.mdl" }, + { "models/p_spygun.mdl", "models/v_tfc_pistol.mdl" }, +#endif + { "models/p_crossbow.mdl", "models/v_crossbow.mdl" }, + { "models/p_crowbar.mdl", "models/v_crowbar.mdl" }, + { "models/p_egon.mdl", "models/v_egon.mdl" }, + { "models/p_gauss.mdl", "models/v_gauss.mdl" }, + { "models/p_9mmhandgun.mdl", "models/v_9mmhandgun.mdl" }, + { "models/p_grenade.mdl", "models/v_grenade.mdl" }, + { "models/p_hgun.mdl", "models/v_hgun.mdl" }, + { "models/p_9mmAR.mdl", "models/v_9mmAR.mdl" }, + { "models/p_357.mdl", "models/v_357.mdl" }, + { "models/p_rpg.mdl", "models/v_rpg.mdl" }, + { "models/p_shotgun.mdl", "models/v_shotgun.mdl" }, + { "models/p_squeak.mdl", "models/v_squeak.mdl" }, + { "models/p_tripmine.mdl", "models/v_tripmine.mdl" }, + { "models/p_satchel_radio.mdl", "models/v_satchel_radio.mdl"}, + { "models/p_satchel.mdl", "models/v_satchel.mdl" }, + { NULL, NULL } }; + + struct model_s * weaponModel = IEngineStudio.GetModelByIndex( weaponindex ); + + if ( weaponModel ) + { + int len = strlen( weaponModel->name ); + int i = 0; + + while ( modelmap[i] != NULL ) + { + if ( !strnicmp( weaponModel->name, modelmap[i][0], len ) ) + { + return gEngfuncs.pEventAPI->EV_FindModelIndex( modelmap[i][1] ); + } + i++; + } + + return 0; + } + else + return 0; + +} + + +/* +================== +V_CalcSpectatorRefdef + +================== +*/ +void V_CalcSpectatorRefdef ( struct ref_params_s * pparams ) +{ + static vec3_t velocity ( 0.0f, 0.0f, 0.0f); + + static int lastWeaponModelIndex = 0; + static int lastViewModelIndex = 0; + + cl_entity_t * ent = gEngfuncs.GetEntityByIndex( g_iUser2 ); + + pparams->onlyClientDraw = false; + + // refresh position + VectorCopy ( pparams->simorg, v_sim_org ); + + // get old values + VectorCopy ( pparams->cl_viewangles, v_cl_angles ); + VectorCopy ( pparams->viewangles, v_angles ); + VectorCopy ( pparams->vieworg, v_origin ); + + if ( ( g_iUser1 == OBS_IN_EYE || gHUD.m_Spectator.m_pip->value == INSET_IN_EYE ) && ent ) + { + // calculate player velocity + float timeDiff = ent->curstate.msg_time - ent->prevstate.msg_time; + + if ( timeDiff > 0 ) + { + vec3_t distance; + VectorSubtract(ent->prevstate.origin, ent->curstate.origin, distance); + VectorScale(distance, 1/timeDiff, distance ); + + velocity[0] = velocity[0]*0.9f + distance[0]*0.1f; + velocity[1] = velocity[1]*0.9f + distance[1]*0.1f; + velocity[2] = velocity[2]*0.9f + distance[2]*0.1f; + + VectorCopy(velocity, pparams->simvel); + } + + // predict missing client data and set weapon model ( in HLTV mode or inset in eye mode ) +#ifdef _TFC + if ( gEngfuncs.IsSpectateOnly() || gHUD.m_Spectator.m_pip->value == INSET_IN_EYE ) +#else + if ( gEngfuncs.IsSpectateOnly() ) +#endif + { + V_GetInEyePos( g_iUser2, pparams->simorg, pparams->cl_viewangles ); + + pparams->health = 1; + + cl_entity_t * gunModel = gEngfuncs.GetViewModel(); + + if ( lastWeaponModelIndex != ent->curstate.weaponmodel ) + { + // weapon model changed + + lastWeaponModelIndex = ent->curstate.weaponmodel; + lastViewModelIndex = V_FindViewModelByWeaponModel( lastWeaponModelIndex ); + if ( lastViewModelIndex ) + { + gEngfuncs.pfnWeaponAnim(0,0); // reset weapon animation + } + else + { + // model not found + gunModel->model = NULL; // disable weapon model + lastWeaponModelIndex = lastViewModelIndex = 0; + } + } + + if ( lastViewModelIndex ) + { + gunModel->model = IEngineStudio.GetModelByIndex( lastViewModelIndex ); + gunModel->curstate.modelindex = lastViewModelIndex; + gunModel->curstate.frame = 0; + gunModel->curstate.colormap = 0; + gunModel->index = g_iUser2; + } + else + { + gunModel->model = NULL; // disable weaopn model + } + } + else + { + // only get viewangles from entity + VectorCopy ( ent->angles, pparams->cl_viewangles ); + pparams->cl_viewangles[PITCH]*=-3.0f; // see CL_ProcessEntityUpdate() + } + } + + v_frametime = pparams->frametime; + + if ( pparams->nextView == 0 ) + { + // first renderer cycle, full screen + + switch ( g_iUser1 ) + { + case OBS_CHASE_LOCKED: V_GetChasePos( g_iUser2, NULL, v_origin, v_angles ); + break; + + case OBS_CHASE_FREE: V_GetChasePos( g_iUser2, v_cl_angles, v_origin, v_angles ); + break; + + case OBS_ROAMING : VectorCopy (v_cl_angles, v_angles); + VectorCopy (v_sim_org, v_origin); + + // override values if director is active + gHUD.m_Spectator.GetDirectorCamera(v_origin, v_angles); + break; + + case OBS_IN_EYE : V_CalcNormalRefdef ( pparams ); + break; + + case OBS_MAP_FREE : pparams->onlyClientDraw = true; + V_GetMapFreePosition( v_cl_angles, v_origin, v_angles ); + break; + + case OBS_MAP_CHASE : pparams->onlyClientDraw = true; + V_GetMapChasePosition( g_iUser2, v_cl_angles, v_origin, v_angles ); + break; + } + + if ( gHUD.m_Spectator.m_pip->value ) + pparams->nextView = 1; // force a second renderer view + + gHUD.m_Spectator.m_iDrawCycle = 0; + + } + else + { + // second renderer cycle, inset window + + // set inset parameters + pparams->viewport[0] = XRES(gHUD.m_Spectator.m_OverviewData.insetWindowX); // change viewport to inset window + pparams->viewport[1] = YRES(gHUD.m_Spectator.m_OverviewData.insetWindowY); + pparams->viewport[2] = XRES(gHUD.m_Spectator.m_OverviewData.insetWindowWidth); + pparams->viewport[3] = YRES(gHUD.m_Spectator.m_OverviewData.insetWindowHeight); + pparams->nextView = 0; // on further view + + // override some settings in certain modes + switch ( (int)gHUD.m_Spectator.m_pip->value ) + { + case INSET_CHASE_FREE : V_GetChasePos( g_iUser2, v_cl_angles, v_origin, v_angles ); + break; + + case INSET_IN_EYE : V_CalcNormalRefdef ( pparams ); + break; + + case INSET_MAP_FREE : pparams->onlyClientDraw = true; + V_GetMapFreePosition( v_cl_angles, v_origin, v_angles ); + break; + + case INSET_MAP_CHASE : pparams->onlyClientDraw = true; + + if ( g_iUser1 == OBS_ROAMING ) + V_GetMapChasePosition( 0, v_cl_angles, v_origin, v_angles ); + else + V_GetMapChasePosition( g_iUser2, v_cl_angles, v_origin, v_angles ); + + break; + } + + gHUD.m_Spectator.m_iDrawCycle = 1; + } + + // write back new values into pparams + VectorCopy ( v_cl_angles, pparams->cl_viewangles ); + VectorCopy ( v_angles, pparams->viewangles ) + VectorCopy ( v_origin, pparams->vieworg ); + +} + + + +void CL_DLLEXPORT V_CalcRefdef( struct ref_params_s *pparams ) +{ +// RecClCalcRefdef(pparams); + + // intermission / finale rendering + if ( pparams->intermission ) + { + V_CalcIntermissionRefdef ( pparams ); + } + else if ( pparams->spectator || g_iUser1 ) // g_iUser true if in spectator mode + { + V_CalcSpectatorRefdef ( pparams ); + } + else if ( !pparams->paused ) + { + V_CalcNormalRefdef ( pparams ); + } + +/* +// Example of how to overlay the whole screen with red at 50 % alpha +#define SF_TEST +#if defined SF_TEST + { + screenfade_t sf; + gEngfuncs.pfnGetScreenFade( &sf ); + + sf.fader = 255; + sf.fadeg = 0; + sf.fadeb = 0; + sf.fadealpha = 128; + sf.fadeFlags = FFADE_STAYOUT | FFADE_OUT; + + gEngfuncs.pfnSetScreenFade( &sf ); + } +#endif +*/ +} + +/* +============= +V_DropPunchAngle + +============= +*/ +void V_DropPunchAngle ( float frametime, float *ev_punchangle ) +{ + float len; + + len = VectorNormalize ( ev_punchangle ); + len -= (10.0 + len * 0.5) * frametime; + len = max( len, 0.0 ); + VectorScale ( ev_punchangle, len, ev_punchangle ); +} + +/* +============= +V_PunchAxis + +Client side punch effect +============= +*/ +void V_PunchAxis( int axis, float punch ) +{ + ev_punchangle[ axis ] = punch; +} + +/* +============= +V_Init +============= +*/ +void V_Init (void) +{ + gEngfuncs.pfnAddCommand ("centerview", V_StartPitchDrift ); + + scr_ofsx = gEngfuncs.pfnRegisterVariable( "scr_ofsx","0", 0 ); + scr_ofsy = gEngfuncs.pfnRegisterVariable( "scr_ofsy","0", 0 ); + scr_ofsz = gEngfuncs.pfnRegisterVariable( "scr_ofsz","0", 0 ); + + v_centermove = gEngfuncs.pfnRegisterVariable( "v_centermove", "0.15", 0 ); + v_centerspeed = gEngfuncs.pfnRegisterVariable( "v_centerspeed","500", 0 ); + + cl_bobcycle = gEngfuncs.pfnRegisterVariable( "cl_bobcycle","0.8", 0 );// best default for my experimental gun wag (sjb) + cl_bob = gEngfuncs.pfnRegisterVariable( "cl_bob","0.01", 0 );// best default for my experimental gun wag (sjb) + cl_bobup = gEngfuncs.pfnRegisterVariable( "cl_bobup","0.5", 0 ); + cl_waterdist = gEngfuncs.pfnRegisterVariable( "cl_waterdist","4", 0 ); + cl_chasedist = gEngfuncs.pfnRegisterVariable( "cl_chasedist","112", 0 ); +} + + +//#define TRACE_TEST +#if defined( TRACE_TEST ) + +extern float in_fov; +/* +==================== +CalcFov +==================== +*/ +float CalcFov (float fov_x, float width, float height) +{ + float a; + float x; + + if (fov_x < 1 || fov_x > 179) + fov_x = 90; // error, set to 90 + + x = width/tan(fov_x/360*M_PI); + + a = atan (height/x); + + a = a*360/M_PI; + + return a; +} + +int hitent = -1; + +void V_Move( int mx, int my ) +{ + float fov; + float fx, fy; + float dx, dy; + float c_x, c_y; + float dX, dY; + vec3_t forward, up, right; + vec3_t newangles; + + vec3_t farpoint; + pmtrace_t tr; + + fov = CalcFov( in_fov, (float)ScreenWidth, (float)ScreenHeight ); + + c_x = (float)ScreenWidth / 2.0; + c_y = (float)ScreenHeight / 2.0; + + dx = (float)mx - c_x; + dy = (float)my - c_y; + + // Proportion we moved in each direction + fx = dx / c_x; + fy = dy / c_y; + + dX = fx * in_fov / 2.0 ; + dY = fy * fov / 2.0; + + newangles = v_angles; + + newangles[ YAW ] -= dX; + newangles[ PITCH ] += dY; + + // Now rotate v_forward around that point + AngleVectors ( newangles, forward, right, up ); + + farpoint = v_origin + 8192 * forward; + + // Trace + tr = *(gEngfuncs.PM_TraceLine( (float *)&v_origin, (float *)&farpoint, PM_TRACELINE_PHYSENTSONLY, 2 /*point sized hull*/, -1 )); + + if ( tr.fraction != 1.0 && tr.ent != 0 ) + { + hitent = PM_GetPhysEntInfo( tr.ent ); + PM_ParticleLine( (float *)&v_origin, (float *)&tr.endpos, 5, 1.0, 0.0 ); + } + else + { + hitent = -1; + } +} + +#endif diff --git a/cl_dll/view.h b/cl_dll/view.h new file mode 100644 index 0000000..1afbe38 --- /dev/null +++ b/cl_dll/view.h @@ -0,0 +1,15 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined ( VIEWH ) +#define VIEWH +#pragma once + +void V_StartPitchDrift( void ); +void V_StopPitchDrift( void ); + +#endif // !VIEWH \ No newline at end of file diff --git a/cl_dll/voice_status.cpp b/cl_dll/voice_status.cpp new file mode 100644 index 0000000..468ebc4 --- /dev/null +++ b/cl_dll/voice_status.cpp @@ -0,0 +1,885 @@ +//========= Copyright � 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// There are hud.h's coming out of the woodwork so this ensures that we get the right one. +#if defined(THREEWAVE) || defined(DMC_BUILD) + #include "../dmc/cl_dll/hud.h" +#elif defined(CSTRIKE) + #include "../cstrike/cl_dll/hud.h" +#elif defined(DOD) + #include "../dod/cl_dll/hud.h" +#else + #include "hud.h" +#endif + +#include "cl_util.h" +#include +#include +#include +#include "parsemsg.h" +#include "hud_servers.h" +#include "demo.h" +#include "demo_api.h" +#include "voice_status.h" +#include "r_efx.h" +#include "entity_types.h" +#include "VGUI_ActionSignal.h" +#include "VGUI_Scheme.h" +#include "VGUI_TextImage.h" +#include "vgui_loadtga.h" +#include "vgui_helpers.h" +#include "VGUI_MouseCode.h" + + + +using namespace vgui; + + +extern int cam_thirdperson; + + +#define VOICE_MODEL_INTERVAL 0.3 +#define SCOREBOARD_BLINK_FREQUENCY 0.3 // How often to blink the scoreboard icons. +#define SQUELCHOSCILLATE_PER_SECOND 2.0f + + +extern BitmapTGA *LoadTGA( const char* pImageName ); + + + +// ---------------------------------------------------------------------- // +// The voice manager for the client. +// ---------------------------------------------------------------------- // +CVoiceStatus g_VoiceStatus; + +CVoiceStatus* GetClientVoiceMgr() +{ + return &g_VoiceStatus; +} + + + +// ---------------------------------------------------------------------- // +// CVoiceStatus. +// ---------------------------------------------------------------------- // + +static CVoiceStatus *g_pInternalVoiceStatus = NULL; + +int __MsgFunc_VoiceMask(const char *pszName, int iSize, void *pbuf) +{ + if(g_pInternalVoiceStatus) + g_pInternalVoiceStatus->HandleVoiceMaskMsg(iSize, pbuf); + + return 1; +} + +int __MsgFunc_ReqState(const char *pszName, int iSize, void *pbuf) +{ + if(g_pInternalVoiceStatus) + g_pInternalVoiceStatus->HandleReqStateMsg(iSize, pbuf); + + return 1; +} + + +int g_BannedPlayerPrintCount; +void ForEachBannedPlayer(char id[16]) +{ + char str[256]; + sprintf(str, "Ban %d: %2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x\n", + g_BannedPlayerPrintCount++, + id[0], id[1], id[2], id[3], + id[4], id[5], id[6], id[7], + id[8], id[9], id[10], id[11], + id[12], id[13], id[14], id[15] + ); +#ifdef _WIN32 + strupr(str); +#endif + gEngfuncs.pfnConsolePrint(str); +} + + +void ShowBannedCallback() +{ + if(g_pInternalVoiceStatus) + { + g_BannedPlayerPrintCount = 0; + gEngfuncs.pfnConsolePrint("------- BANNED PLAYERS -------\n"); + g_pInternalVoiceStatus->m_BanMgr.ForEachBannedPlayer(ForEachBannedPlayer); + gEngfuncs.pfnConsolePrint("------------------------------\n"); + } +} + + +// ---------------------------------------------------------------------- // +// CVoiceStatus. +// ---------------------------------------------------------------------- // + +CVoiceStatus::CVoiceStatus() +{ + m_bBanMgrInitialized = false; + m_LastUpdateServerState = 0; + + m_pSpeakerLabelIcon = NULL; + m_pScoreboardNeverSpoken = NULL; + m_pScoreboardNotSpeaking = NULL; + m_pScoreboardSpeaking = NULL; + m_pScoreboardSpeaking2 = NULL; + m_pScoreboardSquelch = NULL; + m_pScoreboardBanned = NULL; + + m_pLocalBitmap = NULL; + m_pAckBitmap = NULL; + + m_bTalking = m_bServerAcked = false; + + memset(m_pBanButtons, 0, sizeof(m_pBanButtons)); + + m_pParentPanel = NULL; + + m_bServerModEnable = -1; + + m_pchGameDir = NULL; +} + + +CVoiceStatus::~CVoiceStatus() +{ + g_pInternalVoiceStatus = NULL; + + for(int i=0; i < MAX_VOICE_SPEAKERS; i++) + { + delete m_Labels[i].m_pLabel; + m_Labels[i].m_pLabel = NULL; + + delete m_Labels[i].m_pIcon; + m_Labels[i].m_pIcon = NULL; + + delete m_Labels[i].m_pBackground; + m_Labels[i].m_pBackground = NULL; + } + + delete m_pLocalLabel; + m_pLocalLabel = NULL; + + FreeBitmaps(); + + if(m_pchGameDir) + { + if(m_bBanMgrInitialized) + { + m_BanMgr.SaveState(m_pchGameDir); + } + + free(m_pchGameDir); + } +} + + +int CVoiceStatus::Init( + IVoiceStatusHelper *pHelper, + Panel **pParentPanel) +{ + // Setup the voice_modenable cvar. + gEngfuncs.pfnRegisterVariable("voice_modenable", "1", FCVAR_ARCHIVE); + + gEngfuncs.pfnRegisterVariable("voice_clientdebug", "0", 0); + + gEngfuncs.pfnAddCommand("voice_showbanned", ShowBannedCallback); + + if(gEngfuncs.pfnGetGameDirectory()) + { + m_BanMgr.Init(gEngfuncs.pfnGetGameDirectory()); + m_bBanMgrInitialized = true; + } + + assert(!g_pInternalVoiceStatus); + g_pInternalVoiceStatus = this; + + m_BlinkTimer = 0; + m_VoiceHeadModel = NULL; + memset(m_Labels, 0, sizeof(m_Labels)); + + for(int i=0; i < MAX_VOICE_SPEAKERS; i++) + { + CVoiceLabel *pLabel = &m_Labels[i]; + + pLabel->m_pBackground = new Label(""); + + if(pLabel->m_pLabel = new Label("")) + { + pLabel->m_pLabel->setVisible( true ); + pLabel->m_pLabel->setFont( Scheme::sf_primary2 ); + pLabel->m_pLabel->setTextAlignment( Label::a_east ); + pLabel->m_pLabel->setContentAlignment( Label::a_east ); + pLabel->m_pLabel->setParent( pLabel->m_pBackground ); + } + + if( pLabel->m_pIcon = new ImagePanel( NULL ) ) + { + pLabel->m_pIcon->setVisible( true ); + pLabel->m_pIcon->setParent( pLabel->m_pBackground ); + } + + pLabel->m_clientindex = -1; + } + + m_pLocalLabel = new ImagePanel(NULL); + + m_bInSquelchMode = false; + + m_pHelper = pHelper; + m_pParentPanel = pParentPanel; + gHUD.AddHudElem(this); + m_iFlags = HUD_ACTIVE; + HOOK_MESSAGE(VoiceMask); + HOOK_MESSAGE(ReqState); + + // Cache the game directory for use when we shut down + const char *pchGameDirT = gEngfuncs.pfnGetGameDirectory(); + m_pchGameDir = (char *)malloc(strlen(pchGameDirT) + 1); + strcpy(m_pchGameDir, pchGameDirT); + + return 1; +} + + +int CVoiceStatus::VidInit() +{ + FreeBitmaps(); + + + if( m_pLocalBitmap = vgui_LoadTGA("gfx/vgui/icntlk_pl.tga") ) + { + m_pLocalBitmap->setColor(Color(255,255,255,135)); + } + + if( m_pAckBitmap = vgui_LoadTGA("gfx/vgui/icntlk_sv.tga") ) + { + m_pAckBitmap->setColor(Color(255,255,255,135)); // Give just a tiny bit of translucency so software draws correctly. + } + + m_pLocalLabel->setImage( m_pLocalBitmap ); + m_pLocalLabel->setVisible( false ); + + + if( m_pSpeakerLabelIcon = vgui_LoadTGANoInvertAlpha("gfx/vgui/speaker4.tga" ) ) + m_pSpeakerLabelIcon->setColor( Color(255,255,255,1) ); // Give just a tiny bit of translucency so software draws correctly. + + if (m_pScoreboardNeverSpoken = vgui_LoadTGANoInvertAlpha("gfx/vgui/640_speaker1.tga")) + m_pScoreboardNeverSpoken->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardNotSpeaking = vgui_LoadTGANoInvertAlpha("gfx/vgui/640_speaker2.tga")) + m_pScoreboardNotSpeaking->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardSpeaking = vgui_LoadTGANoInvertAlpha("gfx/vgui/640_speaker3.tga")) + m_pScoreboardSpeaking->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardSpeaking2 = vgui_LoadTGANoInvertAlpha("gfx/vgui/640_speaker4.tga")) + m_pScoreboardSpeaking2->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardSquelch = vgui_LoadTGA("gfx/vgui/icntlk_squelch.tga")) + m_pScoreboardSquelch->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardBanned = vgui_LoadTGA("gfx/vgui/640_voiceblocked.tga")) + m_pScoreboardBanned->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + // Figure out the voice head model height. + m_VoiceHeadModelHeight = 45; + char *pFile = (char *)gEngfuncs.COM_LoadFile("scripts/voicemodel.txt", 5, NULL); + if(pFile) + { + char token[4096]; + gEngfuncs.COM_ParseFile(pFile, token); + if(token[0] >= '0' && token[0] <= '9') + { + m_VoiceHeadModelHeight = (float)atof(token); + } + + gEngfuncs.COM_FreeFile(pFile); + } + + m_VoiceHeadModel = gEngfuncs.pfnSPR_Load("sprites/voiceicon.spr"); + return TRUE; +} + + +void CVoiceStatus::Frame(double frametime) +{ + // check server banned players once per second + if(gEngfuncs.GetClientTime() - m_LastUpdateServerState > 1) + { + UpdateServerState(false); + } + + m_BlinkTimer += frametime; + + // Update speaker labels. + if( m_pHelper->CanShowSpeakerLabels() ) + { + for( int i=0; i < MAX_VOICE_SPEAKERS; i++ ) + m_Labels[i].m_pBackground->setVisible( m_Labels[i].m_clientindex != -1 ); + } + else + { + for( int i=0; i < MAX_VOICE_SPEAKERS; i++ ) + m_Labels[i].m_pBackground->setVisible( false ); + } + + for(int i=0; i < VOICE_MAX_PLAYERS; i++) + UpdateBanButton(i); +} + + +void CVoiceStatus::CreateEntities() +{ + if(!m_VoiceHeadModel) + return; + + cl_entity_t *localPlayer = gEngfuncs.GetLocalPlayer(); + + int iOutModel = 0; + for(int i=0; i < VOICE_MAX_PLAYERS; i++) + { + if(!m_VoicePlayers[i]) + continue; + + cl_entity_s *pClient = gEngfuncs.GetEntityByIndex(i+1); + + // Don't show an icon if the player is not in our PVS. + if(!pClient || pClient->curstate.messagenum < localPlayer->curstate.messagenum) + continue; + + // Don't show an icon for dead or spectating players (ie: invisible entities). + if(pClient->curstate.effects & EF_NODRAW) + continue; + + // Don't show an icon for the local player unless we're in thirdperson mode. + if(pClient == localPlayer && !cam_thirdperson) + continue; + + cl_entity_s *pEnt = &m_VoiceHeadModels[iOutModel]; + ++iOutModel; + + memset(pEnt, 0, sizeof(*pEnt)); + + pEnt->curstate.rendermode = kRenderTransAdd; + pEnt->curstate.renderamt = 255; + pEnt->baseline.renderamt = 255; + pEnt->curstate.renderfx = kRenderFxNoDissipation; + pEnt->curstate.framerate = 1; + pEnt->curstate.frame = 0; + pEnt->model = (struct model_s*)gEngfuncs.GetSpritePointer(m_VoiceHeadModel); + pEnt->angles[0] = pEnt->angles[1] = pEnt->angles[2] = 0; + pEnt->curstate.scale = 0.5f; + + pEnt->origin[0] = pEnt->origin[1] = 0; + pEnt->origin[2] = 45; + + VectorAdd(pEnt->origin, pClient->origin, pEnt->origin); + + // Tell the engine. + gEngfuncs.CL_CreateVisibleEntity(ET_NORMAL, pEnt); + } +} + + +void CVoiceStatus::UpdateSpeakerStatus( int entindex, qboolean bTalking ) +{ + cvar_t *pVoiceLoopback = NULL; + + if ( !m_pParentPanel || !*m_pParentPanel ) + { + return; + } + + if ( gEngfuncs.pfnGetCvarFloat( "voice_clientdebug" ) ) + { + char msg[256]; + _snprintf( msg, sizeof( msg ), "CVoiceStatus::UpdateSpeakerStatus: ent %d talking = %d\n", entindex, bTalking ); + gEngfuncs.pfnConsolePrint( msg ); + } + + int iLocalPlayerIndex = gEngfuncs.GetLocalPlayer()->index; + + // Is it the local player talking? + if ( entindex == -1 ) + { + m_bTalking = !!bTalking; + if( bTalking ) + { + // Enable voice for them automatically if they try to talk. + gEngfuncs.pfnClientCmd( "voice_modenable 1" ); + } + + // now set the player index to the correct index for the local player + // this will allow us to have the local player's icon flash in the scoreboard + entindex = iLocalPlayerIndex; + + pVoiceLoopback = gEngfuncs.pfnGetCvarPointer( "voice_loopback" ); + } + else if ( entindex == -2 ) + { + m_bServerAcked = !!bTalking; + } + + if ( entindex >= 0 && entindex <= VOICE_MAX_PLAYERS ) + { + int iClient = entindex - 1; + if ( iClient < 0 ) + { + return; + } + + CVoiceLabel *pLabel = FindVoiceLabel( iClient ); + if ( bTalking ) + { + m_VoicePlayers[iClient] = true; + m_VoiceEnabledPlayers[iClient] = true; + + // If we don't have a label for this guy yet, then create one. + if ( !pLabel ) + { + // if this isn't the local player (unless they have voice_loopback on) + if ( ( entindex != iLocalPlayerIndex ) || ( pVoiceLoopback && pVoiceLoopback->value ) ) + { + if ( pLabel = GetFreeVoiceLabel() ) + { + // Get the name from the engine. + hud_player_info_t info; + memset( &info, 0, sizeof( info ) ); + gEngfuncs.pfnGetPlayerInfo( entindex, &info ); + + char paddedName[512]; + _snprintf( paddedName, sizeof( paddedName ), "%s ", info.name ); + + int color[3]; + m_pHelper->GetPlayerTextColor( entindex, color ); + + if ( pLabel->m_pBackground ) + { + pLabel->m_pBackground->setBgColor( color[0], color[1], color[2], 135 ); + pLabel->m_pBackground->setParent( *m_pParentPanel ); + pLabel->m_pBackground->setVisible( m_pHelper->CanShowSpeakerLabels() ); + } + + if ( pLabel->m_pLabel ) + { + pLabel->m_pLabel->setFgColor( 255, 255, 255, 0 ); + pLabel->m_pLabel->setBgColor( 0, 0, 0, 255 ); + pLabel->m_pLabel->setText( "%s", paddedName ); + } + + pLabel->m_clientindex = iClient; + } + } + } + } + else + { + m_VoicePlayers[iClient] = false; + + // If we have a label for this guy, kill it. + if ( pLabel ) + { + pLabel->m_pBackground->setVisible( false ); + pLabel->m_clientindex = -1; + } + } + } + + RepositionLabels(); +} + + +void CVoiceStatus::UpdateServerState(bool bForce) +{ + // Can't do anything when we're not in a level. + char const *pLevelName = gEngfuncs.pfnGetLevelName(); + if( pLevelName[0] == 0 ) + { + if( gEngfuncs.pfnGetCvarFloat("voice_clientdebug") ) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::UpdateServerState: pLevelName[0]==0\n" ); + } + + return; + } + + int bCVarModEnable = !!gEngfuncs.pfnGetCvarFloat("voice_modenable"); + if(bForce || m_bServerModEnable != bCVarModEnable) + { + m_bServerModEnable = bCVarModEnable; + + char str[256]; + _snprintf(str, sizeof(str), "VModEnable %d", m_bServerModEnable); + ServerCmd(str); + + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char msg[256]; + sprintf(msg, "CVoiceStatus::UpdateServerState: Sending '%s'\n", str); + gEngfuncs.pfnConsolePrint(msg); + } + } + + char str[2048]; + sprintf(str, "vban"); + bool bChange = false; + + for(unsigned long dw=0; dw < VOICE_MAX_PLAYERS_DW; dw++) + { + unsigned long serverBanMask = 0; + unsigned long banMask = 0; + for(unsigned long i=0; i < 32; i++) + { + char playerID[16]; + if(!gEngfuncs.GetPlayerUniqueID(i+1, playerID)) + continue; + + if(m_BanMgr.GetPlayerBan(playerID)) + banMask |= 1 << i; + + if(m_ServerBannedPlayers[dw*32 + i]) + serverBanMask |= 1 << i; + } + + if(serverBanMask != banMask) + bChange = true; + + // Ok, the server needs to be updated. + char numStr[512]; + sprintf(numStr, " %x", banMask); + strcat(str, numStr); + } + + if(bChange || bForce) + { + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char msg[256]; + sprintf(msg, "CVoiceStatus::UpdateServerState: Sending '%s'\n", str); + gEngfuncs.pfnConsolePrint(msg); + } + + gEngfuncs.pfnServerCmdUnreliable(str); // Tell the server.. + } + else + { + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::UpdateServerState: no change\n" ); + } + } + + m_LastUpdateServerState = gEngfuncs.GetClientTime(); +} + +void CVoiceStatus::UpdateSpeakerImage(Label *pLabel, int iPlayer) +{ + m_pBanButtons[iPlayer-1] = pLabel; + UpdateBanButton(iPlayer-1); +} + +void CVoiceStatus::UpdateBanButton(int iClient) +{ + Label *pPanel = m_pBanButtons[iClient]; + + if (!pPanel) + return; + + char playerID[16]; + extern bool HACK_GetPlayerUniqueID( int iPlayer, char playerID[16] ); + if(!HACK_GetPlayerUniqueID(iClient+1, playerID)) + return; + + // Figure out if it's blinking or not. + bool bBlink = fmod(m_BlinkTimer, SCOREBOARD_BLINK_FREQUENCY*2) < SCOREBOARD_BLINK_FREQUENCY; + bool bTalking = !!m_VoicePlayers[iClient]; + bool bBanned = m_BanMgr.GetPlayerBan(playerID); + bool bNeverSpoken = !m_VoiceEnabledPlayers[iClient]; + + // Get the appropriate image to display on the panel. + if (bBanned) + { + pPanel->setImage(m_pScoreboardBanned); + } + else if (bTalking) + { + if (bBlink) + { + pPanel->setImage(m_pScoreboardSpeaking2); + } + else + { + pPanel->setImage(m_pScoreboardSpeaking); + } + pPanel->setFgColor(255, 170, 0, 1); + } + else if (bNeverSpoken) + { + pPanel->setImage(m_pScoreboardNeverSpoken); + pPanel->setFgColor(100, 100, 100, 1); + } + else + { + pPanel->setImage(m_pScoreboardNotSpeaking); + } +} + + +void CVoiceStatus::HandleVoiceMaskMsg(int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + unsigned long dw; + for(dw=0; dw < VOICE_MAX_PLAYERS_DW; dw++) + { + m_AudiblePlayers.SetDWord(dw, (unsigned long)READ_LONG()); + m_ServerBannedPlayers.SetDWord(dw, (unsigned long)READ_LONG()); + + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char str[256]; + gEngfuncs.pfnConsolePrint("CVoiceStatus::HandleVoiceMaskMsg\n"); + + sprintf(str, " - m_AudiblePlayers[%d] = %lu\n", dw, m_AudiblePlayers.GetDWord(dw)); + gEngfuncs.pfnConsolePrint(str); + + sprintf(str, " - m_ServerBannedPlayers[%d] = %lu\n", dw, m_ServerBannedPlayers.GetDWord(dw)); + gEngfuncs.pfnConsolePrint(str); + } + } + + m_bServerModEnable = READ_BYTE(); +} + +void CVoiceStatus::HandleReqStateMsg(int iSize, void *pbuf) +{ + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint("CVoiceStatus::HandleReqStateMsg\n"); + } + + UpdateServerState(true); +} + +void CVoiceStatus::StartSquelchMode() +{ + if(m_bInSquelchMode) + return; + + m_bInSquelchMode = true; + m_pHelper->UpdateCursorState(); +} + +void CVoiceStatus::StopSquelchMode() +{ + m_bInSquelchMode = false; + m_pHelper->UpdateCursorState(); +} + +bool CVoiceStatus::IsInSquelchMode() +{ + return m_bInSquelchMode; +} + +CVoiceLabel* CVoiceStatus::FindVoiceLabel(int clientindex) +{ + for(int i=0; i < MAX_VOICE_SPEAKERS; i++) + { + if(m_Labels[i].m_clientindex == clientindex) + return &m_Labels[i]; + } + + return NULL; +} + + +CVoiceLabel* CVoiceStatus::GetFreeVoiceLabel() +{ + return FindVoiceLabel(-1); +} + + +void CVoiceStatus::RepositionLabels() +{ + // find starting position to draw from, along right-hand side of screen + int y = ScreenHeight / 2; + + int iconWide = 8, iconTall = 8; + if( m_pSpeakerLabelIcon ) + { + m_pSpeakerLabelIcon->getSize( iconWide, iconTall ); + } + + // Reposition active labels. + for(int i = 0; i < MAX_VOICE_SPEAKERS; i++) + { + CVoiceLabel *pLabel = &m_Labels[i]; + + if( pLabel->m_clientindex == -1 || !pLabel->m_pLabel ) + { + if( pLabel->m_pBackground ) + pLabel->m_pBackground->setVisible( false ); + + continue; + } + + int textWide, textTall; + pLabel->m_pLabel->getContentSize( textWide, textTall ); + + // Don't let it stretch too far across their screen. + if( textWide > (ScreenWidth*2)/3 ) + textWide = (ScreenWidth*2)/3; + + // Setup the background label to fit everything in. + int border = 2; + int bgWide = textWide + iconWide + border*3; + int bgTall = max( textTall, iconTall ) + border*2; + pLabel->m_pBackground->setBounds( ScreenWidth - bgWide - 8, y, bgWide, bgTall ); + + // Put the text at the left. + pLabel->m_pLabel->setBounds( border, (bgTall - textTall) / 2, textWide, textTall ); + + // Put the icon at the right. + int iconLeft = border + textWide + border; + int iconTop = (bgTall - iconTall) / 2; + if( pLabel->m_pIcon ) + { + pLabel->m_pIcon->setImage( m_pSpeakerLabelIcon ); + pLabel->m_pIcon->setBounds( iconLeft, iconTop, iconWide, iconTall ); + } + + y += bgTall + 2; + } + + if( m_pLocalBitmap && m_pAckBitmap && m_pLocalLabel && (m_bTalking || m_bServerAcked) ) + { + m_pLocalLabel->setParent(*m_pParentPanel); + m_pLocalLabel->setVisible( true ); + + if( m_bServerAcked && !!gEngfuncs.pfnGetCvarFloat("voice_clientdebug") ) + m_pLocalLabel->setImage( m_pAckBitmap ); + else + m_pLocalLabel->setImage( m_pLocalBitmap ); + + int sizeX, sizeY; + m_pLocalBitmap->getSize(sizeX, sizeY); + + int local_xPos = ScreenWidth - sizeX - 10; + int local_yPos = m_pHelper->GetAckIconHeight() - sizeY; + + m_pLocalLabel->setPos( local_xPos, local_yPos ); + } + else + { + m_pLocalLabel->setVisible( false ); + } +} + + +void CVoiceStatus::FreeBitmaps() +{ + // Delete all the images we have loaded. + delete m_pLocalBitmap; + m_pLocalBitmap = NULL; + + delete m_pAckBitmap; + m_pAckBitmap = NULL; + + delete m_pSpeakerLabelIcon; + m_pSpeakerLabelIcon = NULL; + + delete m_pScoreboardNeverSpoken; + m_pScoreboardNeverSpoken = NULL; + + delete m_pScoreboardNotSpeaking; + m_pScoreboardNotSpeaking = NULL; + + delete m_pScoreboardSpeaking; + m_pScoreboardSpeaking = NULL; + + delete m_pScoreboardSpeaking2; + m_pScoreboardSpeaking2 = NULL; + + delete m_pScoreboardSquelch; + m_pScoreboardSquelch = NULL; + + delete m_pScoreboardBanned; + m_pScoreboardBanned = NULL; + + // Clear references to the images in panels. + for(int i=0; i < VOICE_MAX_PLAYERS; i++) + { + if (m_pBanButtons[i]) + { + m_pBanButtons[i]->setImage(NULL); + } + } + + if(m_pLocalLabel) + m_pLocalLabel->setImage(NULL); +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the target client has been banned +// Input : playerID - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CVoiceStatus::IsPlayerBlocked(int iPlayer) +{ + char playerID[16]; + if (!gEngfuncs.GetPlayerUniqueID(iPlayer, playerID)) + return false; + + return m_BanMgr.GetPlayerBan(playerID); +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the player can't hear the other client due to game rules (eg. the other team) +// Input : playerID - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CVoiceStatus::IsPlayerAudible(int iPlayer) +{ + return !!m_AudiblePlayers[iPlayer-1]; +} + +//----------------------------------------------------------------------------- +// Purpose: blocks/unblocks the target client from being heard +// Input : playerID - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +void CVoiceStatus::SetPlayerBlockedState(int iPlayer, bool blocked) +{ + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::SetPlayerBlockedState part 1\n" ); + } + + char playerID[16]; + if (!gEngfuncs.GetPlayerUniqueID(iPlayer, playerID)) + return; + + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::SetPlayerBlockedState part 2\n" ); + } + + // Squelch or (try to) unsquelch this player. + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char str[256]; + sprintf(str, "CVoiceStatus::SetPlayerBlockedState: setting player %d ban to %d\n", iPlayer, !m_BanMgr.GetPlayerBan(playerID)); + gEngfuncs.pfnConsolePrint(str); + } + + m_BanMgr.SetPlayerBan( playerID, blocked ); + UpdateServerState(false); +} diff --git a/cl_dll/voice_status.h b/cl_dll/voice_status.h new file mode 100644 index 0000000..8edf58c --- /dev/null +++ b/cl_dll/voice_status.h @@ -0,0 +1,228 @@ +//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VOICE_STATUS_H +#define VOICE_STATUS_H +#pragma once + + +#include "VGUI_Label.h" +#include "VGUI_LineBorder.h" +#include "VGUI_ImagePanel.h" +#include "VGUI_BitmapTGA.h" +#include "VGUI_InputSignal.h" +#include "VGUI_Button.h" +#include "voice_common.h" +#include "cl_entity.h" +#include "voice_banmgr.h" +#include "vgui_checkbutton2.h" +#include "vgui_defaultinputsignal.h" + + +class CVoiceStatus; + + +class CVoiceLabel +{ +public: + vgui::Label *m_pLabel; + vgui::Label *m_pBackground; + vgui::ImagePanel *m_pIcon; // Voice icon next to player name. + int m_clientindex; // Client index of the speaker. -1 if this label isn't being used. +}; + + +// This is provided by each mod to access data that may not be the same across mods. +class IVoiceStatusHelper +{ +public: + virtual ~IVoiceStatusHelper() {} + + // Get RGB color for voice status text about this player. + virtual void GetPlayerTextColor(int entindex, int color[3]) = 0; + + // Force it to update the cursor state. + virtual void UpdateCursorState() = 0; + + // Return the height above the bottom that the voice ack icons should be drawn at. + virtual int GetAckIconHeight() = 0; + + // Return true if the voice manager is allowed to show speaker labels + // (mods usually return false when the scoreboard is up). + virtual bool CanShowSpeakerLabels() = 0; +}; + +//----------------------------------------------------------------------------- +// Purpose: Holds a color for the shared image +//----------------------------------------------------------------------------- +class VoiceImagePanel : public vgui::ImagePanel +{ + virtual void paintBackground() + { + if (_image!=null) + { + vgui::Color col; + getFgColor(col); + _image->setColor(col); + _image->doPaint(this); + } + } +}; + + +class CVoiceStatus : public CHudBase, public vgui::CDefaultInputSignal +{ +public: + CVoiceStatus(); + virtual ~CVoiceStatus(); + +// CHudBase overrides. +public: + + // Initialize the cl_dll's voice manager. + virtual int Init( + IVoiceStatusHelper *m_pHelper, + vgui::Panel **pParentPanel); + + // ackPosition is the bottom position of where CVoiceStatus will draw the voice acknowledgement labels. + virtual int VidInit(); + + +public: + + // Call from HUD_Frame each frame. + void Frame(double frametime); + + // Called when a player starts or stops talking. + // entindex is -1 to represent the local client talking (before the data comes back from the server). + // When the server acknowledges that the local client is talking, then entindex will be gEngfuncs.GetLocalPlayer(). + // entindex is -2 to represent the local client's voice being acked by the server. + void UpdateSpeakerStatus(int entindex, qboolean bTalking); + + // sets the correct image in the label for the player + void UpdateSpeakerImage(vgui::Label *pLabel, int iPlayer); + + // Call from the HUD_CreateEntities function so it can add sprites above player heads. + void CreateEntities(); + + // Called when the server registers a change to who this client can hear. + void HandleVoiceMaskMsg(int iSize, void *pbuf); + + // The server sends this message initially to tell the client to send their state. + void HandleReqStateMsg(int iSize, void *pbuf); + + +// Squelch mode functions. +public: + + // When you enter squelch mode, pass in + void StartSquelchMode(); + void StopSquelchMode(); + bool IsInSquelchMode(); + + // returns true if the target client has been banned + // playerIndex is of range 1..maxplayers + bool IsPlayerBlocked(int iPlayerIndex); + + // returns false if the player can't hear the other client due to game rules (eg. the other team) + bool IsPlayerAudible(int iPlayerIndex); + + // blocks the target client from being heard + void SetPlayerBlockedState(int iPlayerIndex, bool blocked); + +public: + + CVoiceLabel* FindVoiceLabel(int clientindex); // Find a CVoiceLabel representing the specified speaker. + // Returns NULL if none. + // entindex can be -1 if you want a currently-unused voice label. + CVoiceLabel* GetFreeVoiceLabel(); // Get an unused voice label. Returns NULL if none. + + void RepositionLabels(); + + void FreeBitmaps(); + + void UpdateServerState(bool bForce); + + // Update the button artwork to reflect the client's current state. + void UpdateBanButton(int iClient); + + +public: + + enum {MAX_VOICE_SPEAKERS=7}; + + float m_LastUpdateServerState; // Last time we called this function. + int m_bServerModEnable; // What we've sent to the server about our "voice_modenable" cvar. + + vgui::Panel **m_pParentPanel; + CPlayerBitVec m_VoicePlayers; // Who is currently talking. Indexed by client index. + + // This is the gamerules-defined list of players that you can hear. It is based on what teams people are on + // and is totally separate from the ban list. Indexed by client index. + CPlayerBitVec m_AudiblePlayers; + + // Players who have spoken at least once in the game so far + CPlayerBitVec m_VoiceEnabledPlayers; + + // This is who the server THINKS we have banned (it can become incorrect when a new player arrives on the server). + // It is checked periodically, and the server is told to squelch or unsquelch the appropriate players. + CPlayerBitVec m_ServerBannedPlayers; + + cl_entity_s m_VoiceHeadModels[VOICE_MAX_PLAYERS]; // These aren't necessarily in the order of players. They are just + // a place for it to put data in during CreateEntities. + + IVoiceStatusHelper *m_pHelper; // Each mod provides an implementation of this. + + + // Scoreboard icons. + double m_BlinkTimer; // Blink scoreboard icons.. + vgui::BitmapTGA *m_pScoreboardNeverSpoken; + vgui::BitmapTGA *m_pScoreboardNotSpeaking; + vgui::BitmapTGA *m_pScoreboardSpeaking; + vgui::BitmapTGA *m_pScoreboardSpeaking2; + vgui::BitmapTGA *m_pScoreboardSquelch; + vgui::BitmapTGA *m_pScoreboardBanned; + + vgui::Label *m_pBanButtons[VOICE_MAX_PLAYERS]; // scoreboard buttons. + + // Squelch mode stuff. + bool m_bInSquelchMode; + + HSPRITE m_VoiceHeadModel; // Voice head model (goes above players who are speaking). + float m_VoiceHeadModelHeight; // Height above their head to place the model. + + vgui::Image *m_pSpeakerLabelIcon; // Icon next to speaker labels. + + // Lower-right icons telling when the local player is talking.. + vgui::BitmapTGA *m_pLocalBitmap; // Represents the local client talking. + vgui::BitmapTGA *m_pAckBitmap; // Represents the server ack'ing the client talking. + vgui::ImagePanel *m_pLocalLabel; // Represents the local client talking. + + bool m_bTalking; // Set to true when the client thinks it's talking. + bool m_bServerAcked; // Set to true when the server knows the client is talking. + +public: + + CVoiceBanMgr m_BanMgr; // Tracks which users we have squelched and don't want to hear. + +public: + + bool m_bBanMgrInitialized; + + // Labels telling who is speaking. + CVoiceLabel m_Labels[MAX_VOICE_SPEAKERS]; + + // Cache the game directory for use when we shut down + char * m_pchGameDir; +}; + + +// Get the (global) voice manager. +CVoiceStatus* GetClientVoiceMgr(); + + +#endif // VOICE_STATUS_H diff --git a/cl_dll/wrect.h b/cl_dll/wrect.h new file mode 100644 index 0000000..a3494ae --- /dev/null +++ b/cl_dll/wrect.h @@ -0,0 +1,16 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( WRECTH ) +#define WRECTH + +typedef struct rect_s +{ + int left, right, top, bottom; +} wrect_t; + +#endif \ No newline at end of file diff --git a/common/Sequence.h b/common/Sequence.h new file mode 100644 index 0000000..8df553d --- /dev/null +++ b/common/Sequence.h @@ -0,0 +1,201 @@ +//--------------------------------------------------------------------------- +// +// S c r i p t e d S e q u e n c e s +// +//--------------------------------------------------------------------------- +#ifndef _INCLUDE_SEQUENCE_H_ +#define _INCLUDE_SEQUENCE_H_ + + +#ifndef _DEF_BYTE_ +typedef unsigned char byte; +#endif + +//--------------------------------------------------------------------------- +// client_textmessage_t +//--------------------------------------------------------------------------- +typedef struct client_textmessage_s +{ + int effect; + byte r1, g1, b1, a1; // 2 colors for effects + byte r2, g2, b2, a2; + float x; + float y; + float fadein; + float fadeout; + float holdtime; + float fxtime; + const char *pName; + const char *pMessage; +} client_textmessage_t; + + +//-------------------------------------------------------------------------- +// sequenceDefaultBits_e +// +// Enumerated list of possible modifiers for a command. This enumeration +// is used in a bitarray controlling what modifiers are specified for a command. +//--------------------------------------------------------------------------- +enum sequenceModifierBits +{ + SEQUENCE_MODIFIER_EFFECT_BIT = (1 << 1), + SEQUENCE_MODIFIER_POSITION_BIT = (1 << 2), + SEQUENCE_MODIFIER_COLOR_BIT = (1 << 3), + SEQUENCE_MODIFIER_COLOR2_BIT = (1 << 4), + SEQUENCE_MODIFIER_FADEIN_BIT = (1 << 5), + SEQUENCE_MODIFIER_FADEOUT_BIT = (1 << 6), + SEQUENCE_MODIFIER_HOLDTIME_BIT = (1 << 7), + SEQUENCE_MODIFIER_FXTIME_BIT = (1 << 8), + SEQUENCE_MODIFIER_SPEAKER_BIT = (1 << 9), + SEQUENCE_MODIFIER_LISTENER_BIT = (1 << 10), + SEQUENCE_MODIFIER_TEXTCHANNEL_BIT = (1 << 11), +}; +typedef enum sequenceModifierBits sequenceModifierBits_e ; + + +//--------------------------------------------------------------------------- +// sequenceCommandEnum_e +// +// Enumerated sequence command types. +//--------------------------------------------------------------------------- +enum sequenceCommandEnum_ +{ + SEQUENCE_COMMAND_ERROR = -1, + SEQUENCE_COMMAND_PAUSE = 0, + SEQUENCE_COMMAND_FIRETARGETS, + SEQUENCE_COMMAND_KILLTARGETS, + SEQUENCE_COMMAND_TEXT, + SEQUENCE_COMMAND_SOUND, + SEQUENCE_COMMAND_GOSUB, + SEQUENCE_COMMAND_SENTENCE, + SEQUENCE_COMMAND_REPEAT, + SEQUENCE_COMMAND_SETDEFAULTS, + SEQUENCE_COMMAND_MODIFIER, + SEQUENCE_COMMAND_POSTMODIFIER, + SEQUENCE_COMMAND_NOOP, + + SEQUENCE_MODIFIER_EFFECT, + SEQUENCE_MODIFIER_POSITION, + SEQUENCE_MODIFIER_COLOR, + SEQUENCE_MODIFIER_COLOR2, + SEQUENCE_MODIFIER_FADEIN, + SEQUENCE_MODIFIER_FADEOUT, + SEQUENCE_MODIFIER_HOLDTIME, + SEQUENCE_MODIFIER_FXTIME, + SEQUENCE_MODIFIER_SPEAKER, + SEQUENCE_MODIFIER_LISTENER, + SEQUENCE_MODIFIER_TEXTCHANNEL, +}; +typedef enum sequenceCommandEnum_ sequenceCommandEnum_e; + + +//--------------------------------------------------------------------------- +// sequenceCommandType_e +// +// Typeerated sequence command types. +//--------------------------------------------------------------------------- +enum sequenceCommandType_ +{ + SEQUENCE_TYPE_COMMAND, + SEQUENCE_TYPE_MODIFIER, +}; +typedef enum sequenceCommandType_ sequenceCommandType_e; + + +//--------------------------------------------------------------------------- +// sequenceCommandMapping_s +// +// A mapping of a command enumerated-value to its name. +//--------------------------------------------------------------------------- +typedef struct sequenceCommandMapping_ sequenceCommandMapping_s; +struct sequenceCommandMapping_ +{ + sequenceCommandEnum_e commandEnum; + const char* commandName; + sequenceCommandType_e commandType; +}; + + +//--------------------------------------------------------------------------- +// sequenceCommandLine_s +// +// Structure representing a single command (usually 1 line) from a +// .SEQ file entry. +//--------------------------------------------------------------------------- +typedef struct sequenceCommandLine_ sequenceCommandLine_s; +struct sequenceCommandLine_ +{ + int commandType; // Specifies the type of command + client_textmessage_t clientMessage; // Text HUD message struct + char* speakerName; // Targetname of speaking entity + char* listenerName; // Targetname of entity being spoken to + char* soundFileName; // Name of sound file to play + char* sentenceName; // Name of sentences.txt to play + char* fireTargetNames; // List of targetnames to fire + char* killTargetNames; // List of targetnames to remove + float delay; // Seconds 'till next command + int repeatCount; // If nonzero, reset execution pointer to top of block (N times, -1 = infinite) + int textChannel; // Display channel on which text message is sent + int modifierBitField; // Bit field to specify what clientmessage fields are valid + sequenceCommandLine_s* nextCommandLine; // Next command (linked list) +}; + + +//--------------------------------------------------------------------------- +// sequenceEntry_s +// +// Structure representing a single command (usually 1 line) from a +// .SEQ file entry. +//--------------------------------------------------------------------------- +typedef struct sequenceEntry_ sequenceEntry_s; +struct sequenceEntry_ +{ + char* fileName; // Name of sequence file without .SEQ extension + char* entryName; // Name of entry label in file + sequenceCommandLine_s* firstCommand; // Linked list of commands in entry + sequenceEntry_s* nextEntry; // Next loaded entry + qboolean isGlobal; // Is entry retained over level transitions? +}; + + + +//--------------------------------------------------------------------------- +// sentenceEntry_s +// Structure representing a single sentence of a group from a .SEQ +// file entry. Sentences are identical to entries in sentences.txt, but +// can be unique per level and are loaded/unloaded with the level. +//--------------------------------------------------------------------------- +typedef struct sentenceEntry_ sentenceEntry_s; +struct sentenceEntry_ +{ + char* data; // sentence data (ie "We have hostiles" ) + sentenceEntry_s* nextEntry; // Next loaded entry + qboolean isGlobal; // Is entry retained over level transitions? + unsigned int index; // this entry's position in the file. +}; + +//-------------------------------------------------------------------------- +// sentenceGroupEntry_s +// Structure representing a group of sentences found in a .SEQ file. +// A sentence group is defined by all sentences with the same name, ignoring +// the number at the end of the sentence name. Groups enable a sentence +// to be picked at random across a group. +//-------------------------------------------------------------------------- +typedef struct sentenceGroupEntry_ sentenceGroupEntry_s; +struct sentenceGroupEntry_ +{ + char* groupName; // name of the group (ie CT_ALERT ) + unsigned int numSentences; // number of sentences in group + sentenceEntry_s* firstSentence; // head of linked list of sentences in group + sentenceGroupEntry_s* nextEntry; // next loaded group +}; + +//--------------------------------------------------------------------------- +// Function declarations +//--------------------------------------------------------------------------- +sequenceEntry_s* SequenceGet( const char* fileName, const char* entryName ); +void Sequence_ParseFile( const char* fileName, qboolean isGlobal ); +void Sequence_OnLevelLoad( const char* mapName ); +sentenceEntry_s* SequencePickSentence( const char *groupName, int pickMethod, int *picked ); + +#endif // _INCLUDE_SEQUENCE_H_ diff --git a/common/beamdef.h b/common/beamdef.h new file mode 100644 index 0000000..fd77a76 --- /dev/null +++ b/common/beamdef.h @@ -0,0 +1,62 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined ( BEAMDEFH ) +#define BEAMDEFH +#ifdef _WIN32 +#pragma once +#endif + +#define FBEAM_STARTENTITY 0x00000001 +#define FBEAM_ENDENTITY 0x00000002 +#define FBEAM_FADEIN 0x00000004 +#define FBEAM_FADEOUT 0x00000008 +#define FBEAM_SINENOISE 0x00000010 +#define FBEAM_SOLID 0x00000020 +#define FBEAM_SHADEIN 0x00000040 +#define FBEAM_SHADEOUT 0x00000080 +#define FBEAM_STARTVISIBLE 0x10000000 // Has this client actually seen this beam's start entity yet? +#define FBEAM_ENDVISIBLE 0x20000000 // Has this client actually seen this beam's end entity yet? +#define FBEAM_ISACTIVE 0x40000000 +#define FBEAM_FOREVER 0x80000000 + +typedef struct beam_s BEAM; +struct beam_s +{ + BEAM *next; + int type; + int flags; + vec3_t source; + vec3_t target; + vec3_t delta; + float t; // 0 .. 1 over lifetime of beam + float freq; + float die; + float width; + float amplitude; + float r, g, b; + float brightness; + float speed; + float frameRate; + float frame; + int segments; + int startEntity; + int endEntity; + int modelIndex; + int frameCount; + struct model_s *pFollowModel; + struct particle_s *particles; +}; + +#endif diff --git a/common/cl_entity.h b/common/cl_entity.h new file mode 100644 index 0000000..a7cd472 --- /dev/null +++ b/common/cl_entity.h @@ -0,0 +1,115 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// cl_entity.h +#if !defined( CL_ENTITYH ) +#define CL_ENTITYH +#ifdef _WIN32 +#pragma once +#endif + +typedef struct efrag_s +{ + struct mleaf_s *leaf; + struct efrag_s *leafnext; + struct cl_entity_s *entity; + struct efrag_s *entnext; +} efrag_t; + +typedef struct +{ + byte mouthopen; // 0 = mouth closed, 255 = mouth agape + byte sndcount; // counter for running average + int sndavg; // running average +} mouth_t; + +typedef struct +{ + float prevanimtime; + float sequencetime; + byte prevseqblending[2]; + vec3_t prevorigin; + vec3_t prevangles; + + int prevsequence; + float prevframe; + + byte prevcontroller[4]; + byte prevblending[2]; +} latchedvars_t; + +typedef struct +{ + // Time stamp for this movement + float animtime; + + vec3_t origin; + vec3_t angles; +} position_history_t; + +typedef struct cl_entity_s cl_entity_t; + +#define HISTORY_MAX 64 // Must be power of 2 +#define HISTORY_MASK ( HISTORY_MAX - 1 ) + + +#if !defined( ENTITY_STATEH ) +#include "entity_state.h" +#endif + +#if !defined( PROGS_H ) +#include "progs.h" +#endif + +struct cl_entity_s +{ + int index; // Index into cl_entities ( should match actual slot, but not necessarily ) + + qboolean player; // True if this entity is a "player" + + entity_state_t baseline; // The original state from which to delta during an uncompressed message + entity_state_t prevstate; // The state information from the penultimate message received from the server + entity_state_t curstate; // The state information from the last message received from server + + int current_position; // Last received history update index + position_history_t ph[ HISTORY_MAX ]; // History of position and angle updates for this player + + mouth_t mouth; // For synchronizing mouth movements. + + latchedvars_t latched; // Variables used by studio model rendering routines + + // Information based on interplocation, extrapolation, prediction, or just copied from last msg received. + // + float lastmove; + + // Actual render position and angles + vec3_t origin; + vec3_t angles; + + // Attachment points + vec3_t attachment[4]; + + // Other entity local information + int trivial_accept; + + struct model_s *model; // cl.model_precache[ curstate.modelindes ]; all visible entities have a model + struct efrag_s *efrag; // linked list of efrags + struct mnode_s *topnode; // for bmodels, first world node that splits bmodel, or NULL if not split + + float syncbase; // for client-side animations -- used by obsolete alias animation system, remove? + int visframe; // last frame this entity was found in an active leaf + colorVec cvFloorColor; +}; + +#endif // !CL_ENTITYH diff --git a/common/com_model.h b/common/com_model.h new file mode 100644 index 0000000..c014a39 --- /dev/null +++ b/common/com_model.h @@ -0,0 +1,351 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// com_model.h +#if !defined( COM_MODEL_H ) +#define COM_MODEL_H +#if defined( _WIN32 ) +#pragma once +#endif + +#define STUDIO_RENDER 1 +#define STUDIO_EVENTS 2 + +#define MAX_CLIENTS 32 +#define MAX_EDICTS 900 + +#define MAX_MODEL_NAME 64 +#define MAX_MAP_HULLS 4 +#define MIPLEVELS 4 +#define NUM_AMBIENTS 4 // automatic ambient sounds +#define MAXLIGHTMAPS 4 +#define PLANE_ANYZ 5 + +#define ALIAS_Z_CLIP_PLANE 5 + +// flags in finalvert_t.flags +#define ALIAS_LEFT_CLIP 0x0001 +#define ALIAS_TOP_CLIP 0x0002 +#define ALIAS_RIGHT_CLIP 0x0004 +#define ALIAS_BOTTOM_CLIP 0x0008 +#define ALIAS_Z_CLIP 0x0010 +#define ALIAS_ONSEAM 0x0020 +#define ALIAS_XY_CLIP_MASK 0x000F + +#define ZISCALE ((float)0x8000) + +#define CACHE_SIZE 32 // used to align key data structures + +typedef enum +{ + mod_brush, + mod_sprite, + mod_alias, + mod_studio +} modtype_t; + +// must match definition in modelgen.h +#ifndef SYNCTYPE_T +#define SYNCTYPE_T + +typedef enum +{ + ST_SYNC=0, + ST_RAND +} synctype_t; + +#endif + +typedef struct +{ + float mins[3], maxs[3]; + float origin[3]; + int headnode[MAX_MAP_HULLS]; + int visleafs; // not including the solid leaf 0 + int firstface, numfaces; +} dmodel_t; + +// plane_t structure +typedef struct mplane_s +{ + vec3_t normal; // surface normal + float dist; // closest appoach to origin + byte type; // for texture axis selection and fast side tests + byte signbits; // signx + signy<<1 + signz<<1 + byte pad[2]; +} mplane_t; + +typedef struct +{ + vec3_t position; +} mvertex_t; + +typedef struct +{ + unsigned short v[2]; + unsigned int cachededgeoffset; +} medge_t; + +typedef struct texture_s +{ + char name[16]; + unsigned width, height; + int anim_total; // total tenths in sequence ( 0 = no) + int anim_min, anim_max; // time for this frame min <=time< max + struct texture_s *anim_next; // in the animation sequence + struct texture_s *alternate_anims; // bmodels in frame 1 use these + unsigned offsets[MIPLEVELS]; // four mip maps stored + unsigned paloffset; +} texture_t; + +typedef struct +{ + float vecs[2][4]; // [s/t] unit vectors in world space. + // [i][3] is the s/t offset relative to the origin. + // s or t = dot(3Dpoint,vecs[i])+vecs[i][3] + float mipadjust; // ?? mipmap limits for very small surfaces + texture_t *texture; + int flags; // sky or slime, no lightmap or 256 subdivision +} mtexinfo_t; + +typedef struct mnode_s +{ +// common with leaf + int contents; // 0, to differentiate from leafs + int visframe; // node needs to be traversed if current + + short minmaxs[6]; // for bounding box culling + + struct mnode_s *parent; + +// node specific + mplane_t *plane; + struct mnode_s *children[2]; + + unsigned short firstsurface; + unsigned short numsurfaces; +} mnode_t; + +typedef struct msurface_s msurface_t; +typedef struct decal_s decal_t; + +// JAY: Compress this as much as possible +struct decal_s +{ + decal_t *pnext; // linked list for each surface + msurface_t *psurface; // Surface id for persistence / unlinking + short dx; // Offsets into surface texture (in texture coordinates, so we don't need floats) + short dy; + short texture; // Decal texture + byte scale; // Pixel scale + byte flags; // Decal flags + + short entityIndex; // Entity this is attached to +}; + +typedef struct mleaf_s +{ +// common with node + int contents; // wil be a negative contents number + int visframe; // node needs to be traversed if current + + short minmaxs[6]; // for bounding box culling + + struct mnode_s *parent; + +// leaf specific + byte *compressed_vis; + struct efrag_s *efrags; + + msurface_t **firstmarksurface; + int nummarksurfaces; + int key; // BSP sequence number for leaf's contents + byte ambient_sound_level[NUM_AMBIENTS]; +} mleaf_t; + +struct msurface_s +{ + int visframe; // should be drawn when node is crossed + + int dlightframe; // last frame the surface was checked by an animated light + int dlightbits; // dynamically generated. Indicates if the surface illumination + // is modified by an animated light. + + mplane_t *plane; // pointer to shared plane + int flags; // see SURF_ #defines + + int firstedge; // look up in model->surfedges[], negative numbers + int numedges; // are backwards edges + +// surface generation data + struct surfcache_s *cachespots[MIPLEVELS]; + + short texturemins[2]; // smallest s/t position on the surface. + short extents[2]; // ?? s/t texture size, 1..256 for all non-sky surfaces + + mtexinfo_t *texinfo; + +// lighting info + byte styles[MAXLIGHTMAPS]; // index into d_lightstylevalue[] for animated lights + // no one surface can be effected by more than 4 + // animated lights. + color24 *samples; + + decal_t *pdecals; +}; + +typedef struct +{ + int planenum; + short children[2]; // negative numbers are contents +} dclipnode_t; + +typedef struct hull_s +{ + dclipnode_t *clipnodes; + mplane_t *planes; + int firstclipnode; + int lastclipnode; + vec3_t clip_mins; + vec3_t clip_maxs; +} hull_t; + +#if !defined( CACHE_USER ) && !defined( QUAKEDEF_H ) +#define CACHE_USER +typedef struct cache_user_s +{ + void *data; +} cache_user_t; +#endif + +typedef struct model_s +{ + char name[ MAX_MODEL_NAME ]; + qboolean needload; // bmodels and sprites don't cache normally + + modtype_t type; + int numframes; + synctype_t synctype; + + int flags; + +// +// volume occupied by the model +// + vec3_t mins, maxs; + float radius; + +// +// brush model +// + int firstmodelsurface, nummodelsurfaces; + + int numsubmodels; + dmodel_t *submodels; + + int numplanes; + mplane_t *planes; + + int numleafs; // number of visible leafs, not counting 0 + struct mleaf_s *leafs; + + int numvertexes; + mvertex_t *vertexes; + + int numedges; + medge_t *edges; + + int numnodes; + mnode_t *nodes; + + int numtexinfo; + mtexinfo_t *texinfo; + + int numsurfaces; + msurface_t *surfaces; + + int numsurfedges; + int *surfedges; + + int numclipnodes; + dclipnode_t *clipnodes; + + int nummarksurfaces; + msurface_t **marksurfaces; + + hull_t hulls[MAX_MAP_HULLS]; + + int numtextures; + texture_t **textures; + + byte *visdata; + + color24 *lightdata; + + char *entities; + +// +// additional model data +// + cache_user_t cache; // only access through Mod_Extradata + +} model_t; + +typedef vec_t vec4_t[4]; + +typedef struct alight_s +{ + int ambientlight; // clip at 128 + int shadelight; // clip at 192 - ambientlight + vec3_t color; + float *plightvec; +} alight_t; + +typedef struct auxvert_s +{ + float fv[3]; // viewspace x, y +} auxvert_t; + +#include "custom.h" + +#define MAX_INFO_STRING 256 +#define MAX_SCOREBOARDNAME 32 +typedef struct player_info_s +{ + // User id on server + int userid; + + // User info string + char userinfo[ MAX_INFO_STRING ]; + + // Name + char name[ MAX_SCOREBOARDNAME ]; + + // Spectator or not, unused + int spectator; + + int ping; + int packet_loss; + + // skin information + char model[MAX_QPATH]; + int topcolor; + int bottomcolor; + + // last frame rendered + int renderframe; + + // Gait frame estimation + int gaitsequence; + float gaitframe; + float gaityaw; + vec3_t prevgaitorigin; + + customization_t customdata; +} player_info_t; + +#endif // #define COM_MODEL_H diff --git a/common/con_nprint.h b/common/con_nprint.h new file mode 100644 index 0000000..64f5dac --- /dev/null +++ b/common/con_nprint.h @@ -0,0 +1,38 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( CON_NPRINTH ) +#define CON_NPRINTH +#ifdef _WIN32 +#pragma once +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct con_nprint_s +{ + int index; // Row # + float time_to_live; // # of seconds before it dissappears + float color[ 3 ]; // RGB colors ( 0.0 -> 1.0 scale ) +} con_nprint_t; + +void Con_NPrintf( int idx, char *fmt, ... ); +void Con_NXPrintf( struct con_nprint_s *info, char *fmt, ... ); +#ifdef __cplusplus +} +#endif + +#endif diff --git a/common/const.h b/common/const.h new file mode 100644 index 0000000..6c5f35b --- /dev/null +++ b/common/const.h @@ -0,0 +1,783 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef CONST_H +#define CONST_H +// +// Constants shared by the engine and dlls +// This header file included by engine files and DLL files. +// Most came from server.h + +// edict->flags +#define FL_FLY (1<<0) // Changes the SV_Movestep() behavior to not need to be on ground +#define FL_SWIM (1<<1) // Changes the SV_Movestep() behavior to not need to be on ground (but stay in water) +#define FL_CONVEYOR (1<<2) +#define FL_CLIENT (1<<3) +#define FL_INWATER (1<<4) +#define FL_MONSTER (1<<5) +#define FL_GODMODE (1<<6) +#define FL_NOTARGET (1<<7) +#define FL_SKIPLOCALHOST (1<<8) // Don't send entity to local host, it's predicting this entity itself +#define FL_ONGROUND (1<<9) // At rest / on the ground +#define FL_PARTIALGROUND (1<<10) // not all corners are valid +#define FL_WATERJUMP (1<<11) // player jumping out of water +#define FL_FROZEN (1<<12) // Player is frozen for 3rd person camera +#define FL_FAKECLIENT (1<<13) // JAC: fake client, simulated server side; don't send network messages to them +#define FL_DUCKING (1<<14) // Player flag -- Player is fully crouched +#define FL_FLOAT (1<<15) // Apply floating force to this entity when in water +#define FL_GRAPHED (1<<16) // worldgraph has this ent listed as something that blocks a connection + +// UNDONE: Do we need these? +#define FL_IMMUNE_WATER (1<<17) +#define FL_IMMUNE_SLIME (1<<18) +#define FL_IMMUNE_LAVA (1<<19) + +#define FL_PROXY (1<<20) // This is a spectator proxy +#define FL_ALWAYSTHINK (1<<21) // Brush model flag -- call think every frame regardless of nextthink - ltime (for constantly changing velocity/path) +#define FL_BASEVELOCITY (1<<22) // Base velocity has been applied this frame (used to convert base velocity into momentum) +#define FL_MONSTERCLIP (1<<23) // Only collide in with monsters who have FL_MONSTERCLIP set +#define FL_ONTRAIN (1<<24) // Player is _controlling_ a train, so movement commands should be ignored on client during prediction. +#define FL_WORLDBRUSH (1<<25) // Not moveable/removeable brush entity (really part of the world, but represented as an entity for transparency or something) +#define FL_SPECTATOR (1<<26) // This client is a spectator, don't run touch functions, etc. +#define FL_CUSTOMENTITY (1<<29) // This is a custom entity +#define FL_KILLME (1<<30) // This entity is marked for death -- This allows the engine to kill ents at the appropriate time +#define FL_DORMANT (1<<31) // Entity is dormant, no updates to client + + +// Goes into globalvars_t.trace_flags +#define FTRACE_SIMPLEBOX (1<<0) // Traceline with a simple box + + +// walkmove modes +#define WALKMOVE_NORMAL 0 // normal walkmove +#define WALKMOVE_WORLDONLY 1 // doesn't hit ANY entities, no matter what the solid type +#define WALKMOVE_CHECKONLY 2 // move, but don't touch triggers + +// edict->movetype values +#define MOVETYPE_NONE 0 // never moves +//#define MOVETYPE_ANGLENOCLIP 1 +//#define MOVETYPE_ANGLECLIP 2 +#define MOVETYPE_WALK 3 // Player only - moving on the ground +#define MOVETYPE_STEP 4 // gravity, special edge handling -- monsters use this +#define MOVETYPE_FLY 5 // No gravity, but still collides with stuff +#define MOVETYPE_TOSS 6 // gravity/collisions +#define MOVETYPE_PUSH 7 // no clip to world, push and crush +#define MOVETYPE_NOCLIP 8 // No gravity, no collisions, still do velocity/avelocity +#define MOVETYPE_FLYMISSILE 9 // extra size to monsters +#define MOVETYPE_BOUNCE 10 // Just like Toss, but reflect velocity when contacting surfaces +#define MOVETYPE_BOUNCEMISSILE 11 // bounce w/o gravity +#define MOVETYPE_FOLLOW 12 // track movement of aiment +#define MOVETYPE_PUSHSTEP 13 // BSP model that needs physics/world collisions (uses nearest hull for world collision) + +// edict->solid values +// NOTE: Some movetypes will cause collisions independent of SOLID_NOT/SOLID_TRIGGER when the entity moves +// SOLID only effects OTHER entities colliding with this one when they move - UGH! +#define SOLID_NOT 0 // no interaction with other objects +#define SOLID_TRIGGER 1 // touch on edge, but not blocking +#define SOLID_BBOX 2 // touch on edge, block +#define SOLID_SLIDEBOX 3 // touch on edge, but not an onground +#define SOLID_BSP 4 // bsp clip, touch on edge, block + +// edict->deadflag values +#define DEAD_NO 0 // alive +#define DEAD_DYING 1 // playing death animation or still falling off of a ledge waiting to hit ground +#define DEAD_DEAD 2 // dead. lying still. +#define DEAD_RESPAWNABLE 3 +#define DEAD_DISCARDBODY 4 + +#define DAMAGE_NO 0 +#define DAMAGE_YES 1 +#define DAMAGE_AIM 2 + +// entity effects +#define EF_BRIGHTFIELD 1 // swirling cloud of particles +#define EF_MUZZLEFLASH 2 // single frame ELIGHT on entity attachment 0 +#define EF_BRIGHTLIGHT 4 // DLIGHT centered at entity origin +#define EF_DIMLIGHT 8 // player flashlight +#define EF_INVLIGHT 16 // get lighting from ceiling +#define EF_NOINTERP 32 // don't interpolate the next frame +#define EF_LIGHT 64 // rocket flare glow sprite +#define EF_NODRAW 128 // don't draw entity +#define EF_NIGHTVISION 256 // player nightvision +#define EF_SNIPERLASER 512 // sniper laser effect +#define EF_FIBERCAMERA 1024// fiber camera + + +// entity flags +#define EFLAG_SLERP 1 // do studio interpolation of this entity + +// +// temp entity events +// +#define TE_BEAMPOINTS 0 // beam effect between two points +// coord coord coord (start position) +// coord coord coord (end position) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMENTPOINT 1 // beam effect between point and entity +// short (start entity) +// coord coord coord (end position) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_GUNSHOT 2 // particle effect plus ricochet sound +// coord coord coord (position) + +#define TE_EXPLOSION 3 // additive sprite, 2 dynamic lights, flickering particles, explosion sound, move vertically 8 pps +// coord coord coord (position) +// short (sprite index) +// byte (scale in 0.1's) +// byte (framerate) +// byte (flags) +// +// The Explosion effect has some flags to control performance/aesthetic features: +#define TE_EXPLFLAG_NONE 0 // all flags clear makes default Half-Life explosion +#define TE_EXPLFLAG_NOADDITIVE 1 // sprite will be drawn opaque (ensure that the sprite you send is a non-additive sprite) +#define TE_EXPLFLAG_NODLIGHTS 2 // do not render dynamic lights +#define TE_EXPLFLAG_NOSOUND 4 // do not play client explosion sound +#define TE_EXPLFLAG_NOPARTICLES 8 // do not draw particles + + +#define TE_TAREXPLOSION 4 // Quake1 "tarbaby" explosion with sound +// coord coord coord (position) + +#define TE_SMOKE 5 // alphablend sprite, move vertically 30 pps +// coord coord coord (position) +// short (sprite index) +// byte (scale in 0.1's) +// byte (framerate) + +#define TE_TRACER 6 // tracer effect from point to point +// coord, coord, coord (start) +// coord, coord, coord (end) + +#define TE_LIGHTNING 7 // TE_BEAMPOINTS with simplified parameters +// coord, coord, coord (start) +// coord, coord, coord (end) +// byte (life in 0.1's) +// byte (width in 0.1's) +// byte (amplitude in 0.01's) +// short (sprite model index) + +#define TE_BEAMENTS 8 +// short (start entity) +// short (end entity) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_SPARKS 9 // 8 random tracers with gravity, ricochet sprite +// coord coord coord (position) + +#define TE_LAVASPLASH 10 // Quake1 lava splash +// coord coord coord (position) + +#define TE_TELEPORT 11 // Quake1 teleport splash +// coord coord coord (position) + +#define TE_EXPLOSION2 12 // Quake1 colormaped (base palette) particle explosion with sound +// coord coord coord (position) +// byte (starting color) +// byte (num colors) + +#define TE_BSPDECAL 13 // Decal from the .BSP file +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// short (texture index of precached decal texture name) +// short (entity index) +// [optional - only included if previous short is non-zero (not the world)] short (index of model of above entity) + +#define TE_IMPLOSION 14 // tracers moving toward a point +// coord, coord, coord (position) +// byte (radius) +// byte (count) +// byte (life in 0.1's) + +#define TE_SPRITETRAIL 15 // line of moving glow sprites with gravity, fadeout, and collisions +// coord, coord, coord (start) +// coord, coord, coord (end) +// short (sprite index) +// byte (count) +// byte (life in 0.1's) +// byte (scale in 0.1's) +// byte (velocity along vector in 10's) +// byte (randomness of velocity in 10's) + +#define TE_BEAM 16 // obsolete + +#define TE_SPRITE 17 // additive sprite, plays 1 cycle +// coord, coord, coord (position) +// short (sprite index) +// byte (scale in 0.1's) +// byte (brightness) + +#define TE_BEAMSPRITE 18 // A beam with a sprite at the end +// coord, coord, coord (start position) +// coord, coord, coord (end position) +// short (beam sprite index) +// short (end sprite index) + +#define TE_BEAMTORUS 19 // screen aligned beam ring, expands to max radius over lifetime +// coord coord coord (center position) +// coord coord coord (axis and radius) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMDISK 20 // disk that expands to max radius over lifetime +// coord coord coord (center position) +// coord coord coord (axis and radius) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMCYLINDER 21 // cylinder that expands to max radius over lifetime +// coord coord coord (center position) +// coord coord coord (axis and radius) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMFOLLOW 22 // create a line of decaying beam segments until entity stops moving +// short (entity:attachment to follow) +// short (sprite index) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte,byte,byte (color) +// byte (brightness) + +#define TE_GLOWSPRITE 23 +// coord, coord, coord (pos) short (model index) byte (scale / 10) + +#define TE_BEAMRING 24 // connect a beam ring to two entities +// short (start entity) +// short (end entity) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_STREAK_SPLASH 25 // oriented shower of tracers +// coord coord coord (start position) +// coord coord coord (direction vector) +// byte (color) +// short (count) +// short (base speed) +// short (ramdon velocity) + +#define TE_BEAMHOSE 26 // obsolete + +#define TE_DLIGHT 27 // dynamic light, effect world, minor entity effect +// coord, coord, coord (pos) +// byte (radius in 10's) +// byte byte byte (color) +// byte (brightness) +// byte (life in 10's) +// byte (decay rate in 10's) + +#define TE_ELIGHT 28 // point entity light, no world effect +// short (entity:attachment to follow) +// coord coord coord (initial position) +// coord (radius) +// byte byte byte (color) +// byte (life in 0.1's) +// coord (decay rate) + +#define TE_TEXTMESSAGE 29 +// short 1.2.13 x (-1 = center) +// short 1.2.13 y (-1 = center) +// byte Effect 0 = fade in/fade out + // 1 is flickery credits + // 2 is write out (training room) + +// 4 bytes r,g,b,a color1 (text color) +// 4 bytes r,g,b,a color2 (effect color) +// ushort 8.8 fadein time +// ushort 8.8 fadeout time +// ushort 8.8 hold time +// optional ushort 8.8 fxtime (time the highlight lags behing the leading text in effect 2) +// string text message (512 chars max sz string) +#define TE_LINE 30 +// coord, coord, coord startpos +// coord, coord, coord endpos +// short life in 0.1 s +// 3 bytes r, g, b + +#define TE_BOX 31 +// coord, coord, coord boxmins +// coord, coord, coord boxmaxs +// short life in 0.1 s +// 3 bytes r, g, b + +#define TE_KILLBEAM 99 // kill all beams attached to entity +// short (entity) + +#define TE_LARGEFUNNEL 100 +// coord coord coord (funnel position) +// short (sprite index) +// short (flags) + +#define TE_BLOODSTREAM 101 // particle spray +// coord coord coord (start position) +// coord coord coord (spray vector) +// byte (color) +// byte (speed) + +#define TE_SHOWLINE 102 // line of particles every 5 units, dies in 30 seconds +// coord coord coord (start position) +// coord coord coord (end position) + +#define TE_BLOOD 103 // particle spray +// coord coord coord (start position) +// coord coord coord (spray vector) +// byte (color) +// byte (speed) + +#define TE_DECAL 104 // Decal applied to a brush entity (not the world) +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name) +// short (entity index) + +#define TE_FIZZ 105 // create alpha sprites inside of entity, float upwards +// short (entity) +// short (sprite index) +// byte (density) + +#define TE_MODEL 106 // create a moving model that bounces and makes a sound when it hits +// coord, coord, coord (position) +// coord, coord, coord (velocity) +// angle (initial yaw) +// short (model index) +// byte (bounce sound type) +// byte (life in 0.1's) + +#define TE_EXPLODEMODEL 107 // spherical shower of models, picks from set +// coord, coord, coord (origin) +// coord (velocity) +// short (model index) +// short (count) +// byte (life in 0.1's) + +#define TE_BREAKMODEL 108 // box of models or sprites +// coord, coord, coord (position) +// coord, coord, coord (size) +// coord, coord, coord (velocity) +// byte (random velocity in 10's) +// short (sprite or model index) +// byte (count) +// byte (life in 0.1 secs) +// byte (flags) + +#define TE_GUNSHOTDECAL 109 // decal and ricochet sound +// coord, coord, coord (position) +// short (entity index???) +// byte (decal???) + +#define TE_SPRITE_SPRAY 110 // spay of alpha sprites +// coord, coord, coord (position) +// coord, coord, coord (velocity) +// short (sprite index) +// byte (count) +// byte (speed) +// byte (noise) + +#define TE_ARMOR_RICOCHET 111 // quick spark sprite, client ricochet sound. +// coord, coord, coord (position) +// byte (scale in 0.1's) + +#define TE_PLAYERDECAL 112 // ??? +// byte (playerindex) +// coord, coord, coord (position) +// short (entity???) +// byte (decal number???) +// [optional] short (model index???) + +#define TE_BUBBLES 113 // create alpha sprites inside of box, float upwards +// coord, coord, coord (min start position) +// coord, coord, coord (max start position) +// coord (float height) +// short (model index) +// byte (count) +// coord (speed) + +#define TE_BUBBLETRAIL 114 // create alpha sprites along a line, float upwards +// coord, coord, coord (min start position) +// coord, coord, coord (max start position) +// coord (float height) +// short (model index) +// byte (count) +// coord (speed) + +#define TE_BLOODSPRITE 115 // spray of opaque sprite1's that fall, single sprite2 for 1..2 secs (this is a high-priority tent) +// coord, coord, coord (position) +// short (sprite1 index) +// short (sprite2 index) +// byte (color) +// byte (scale) + +#define TE_WORLDDECAL 116 // Decal applied to the world brush +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name) + +#define TE_WORLDDECALHIGH 117 // Decal (with texture index > 256) applied to world brush +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name - 256) + +#define TE_DECALHIGH 118 // Same as TE_DECAL, but the texture index was greater than 256 +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name - 256) +// short (entity index) + +#define TE_PROJECTILE 119 // Makes a projectile (like a nail) (this is a high-priority tent) +// coord, coord, coord (position) +// coord, coord, coord (velocity) +// short (modelindex) +// byte (life) +// byte (owner) projectile won't collide with owner (if owner == 0, projectile will hit any client). + +#define TE_SPRAY 120 // Throws a shower of sprites or models +// coord, coord, coord (position) +// coord, coord, coord (direction) +// short (modelindex) +// byte (count) +// byte (speed) +// byte (noise) +// byte (rendermode) + +#define TE_PLAYERSPRITES 121 // sprites emit from a player's bounding box (ONLY use for players!) +// byte (playernum) +// short (sprite modelindex) +// byte (count) +// byte (variance) (0 = no variance in size) (10 = 10% variance in size) + +#define TE_PARTICLEBURST 122 // very similar to lavasplash. +// coord (origin) +// short (radius) +// byte (particle color) +// byte (duration * 10) (will be randomized a bit) + +#define TE_FIREFIELD 123 // makes a field of fire. +// coord (origin) +// short (radius) (fire is made in a square around origin. -radius, -radius to radius, radius) +// short (modelindex) +// byte (count) +// byte (flags) +// byte (duration (in seconds) * 10) (will be randomized a bit) +// +// to keep network traffic low, this message has associated flags that fit into a byte: +#define TEFIRE_FLAG_ALLFLOAT 1 // all sprites will drift upwards as they animate +#define TEFIRE_FLAG_SOMEFLOAT 2 // some of the sprites will drift upwards. (50% chance) +#define TEFIRE_FLAG_LOOP 4 // if set, sprite plays at 15 fps, otherwise plays at whatever rate stretches the animation over the sprite's duration. +#define TEFIRE_FLAG_ALPHA 8 // if set, sprite is rendered alpha blended at 50% else, opaque +#define TEFIRE_FLAG_PLANAR 16 // if set, all fire sprites have same initial Z instead of randomly filling a cube. +#define TEFIRE_FLAG_ADDITIVE 32 // if set, sprite is rendered non-opaque with additive + +#define TE_PLAYERATTACHMENT 124 // attaches a TENT to a player (this is a high-priority tent) +// byte (entity index of player) +// coord (vertical offset) ( attachment origin.z = player origin.z + vertical offset ) +// short (model index) +// short (life * 10 ); + +#define TE_KILLPLAYERATTACHMENTS 125 // will expire all TENTS attached to a player. +// byte (entity index of player) + +#define TE_MULTIGUNSHOT 126 // much more compact shotgun message +// This message is used to make a client approximate a 'spray' of gunfire. +// Any weapon that fires more than one bullet per frame and fires in a bit of a spread is +// a good candidate for MULTIGUNSHOT use. (shotguns) +// +// NOTE: This effect makes the client do traces for each bullet, these client traces ignore +// entities that have studio models.Traces are 4096 long. +// +// coord (origin) +// coord (origin) +// coord (origin) +// coord (direction) +// coord (direction) +// coord (direction) +// coord (x noise * 100) +// coord (y noise * 100) +// byte (count) +// byte (bullethole decal texture index) + +#define TE_USERTRACER 127 // larger message than the standard tracer, but allows some customization. +// coord (origin) +// coord (origin) +// coord (origin) +// coord (velocity) +// coord (velocity) +// coord (velocity) +// byte ( life * 10 ) +// byte ( color ) this is an index into an array of color vectors in the engine. (0 - ) +// byte ( length * 10 ) + + + +#define MSG_BROADCAST 0 // unreliable to all +#define MSG_ONE 1 // reliable to one (msg_entity) +#define MSG_ALL 2 // reliable to all +#define MSG_INIT 3 // write to the init string +#define MSG_PVS 4 // Ents in PVS of org +#define MSG_PAS 5 // Ents in PAS of org +#define MSG_PVS_R 6 // Reliable to PVS +#define MSG_PAS_R 7 // Reliable to PAS +#define MSG_ONE_UNRELIABLE 8 // Send to one client, but don't put in reliable stream, put in unreliable datagram ( could be dropped ) +#define MSG_SPEC 9 // Sends to all spectator proxies + +// contents of a spot in the world +#define CONTENTS_EMPTY -1 +#define CONTENTS_SOLID -2 +#define CONTENTS_WATER -3 +#define CONTENTS_SLIME -4 +#define CONTENTS_LAVA -5 +#define CONTENTS_SKY -6 +/* These additional contents constants are defined in bspfile.h +#define CONTENTS_ORIGIN -7 // removed at csg time +#define CONTENTS_CLIP -8 // changed to contents_solid +#define CONTENTS_CURRENT_0 -9 +#define CONTENTS_CURRENT_90 -10 +#define CONTENTS_CURRENT_180 -11 +#define CONTENTS_CURRENT_270 -12 +#define CONTENTS_CURRENT_UP -13 +#define CONTENTS_CURRENT_DOWN -14 + +#define CONTENTS_TRANSLUCENT -15 +*/ +#define CONTENTS_LADDER -16 + +#define CONTENT_FLYFIELD -17 +#define CONTENT_GRAVITY_FLYFIELD -18 +#define CONTENT_FOG -19 + +#define CONTENT_EMPTY -1 +#define CONTENT_SOLID -2 +#define CONTENT_WATER -3 +#define CONTENT_SLIME -4 +#define CONTENT_LAVA -5 +#define CONTENT_SKY -6 + +// channels +#define CHAN_AUTO 0 +#define CHAN_WEAPON 1 +#define CHAN_VOICE 2 +#define CHAN_ITEM 3 +#define CHAN_BODY 4 +#define CHAN_STREAM 5 // allocate stream channel from the static or dynamic area +#define CHAN_STATIC 6 // allocate channel from the static area +#define CHAN_NETWORKVOICE_BASE 7 // voice data coming across the network +#define CHAN_NETWORKVOICE_END 500 // network voice data reserves slots (CHAN_NETWORKVOICE_BASE through CHAN_NETWORKVOICE_END). +#define CHAN_BOT 501 // channel used for bot chatter. + +// attenuation values +#define ATTN_NONE 0 +#define ATTN_NORM (float)0.8 +#define ATTN_IDLE (float)2 +#define ATTN_STATIC (float)1.25 + +// pitch values +#define PITCH_NORM 100 // non-pitch shifted +#define PITCH_LOW 95 // other values are possible - 0-255, where 255 is very high +#define PITCH_HIGH 120 + +// volume values +#define VOL_NORM 1.0 + +// plats +#define PLAT_LOW_TRIGGER 1 + +// Trains +#define SF_TRAIN_WAIT_RETRIGGER 1 +#define SF_TRAIN_START_ON 4 // Train is initially moving +#define SF_TRAIN_PASSABLE 8 // Train is not solid -- used to make water trains + +// buttons +#ifndef IN_BUTTONS_H +#include "in_buttons.h" +#endif + +// Break Model Defines + +#define BREAK_TYPEMASK 0x4F +#define BREAK_GLASS 0x01 +#define BREAK_METAL 0x02 +#define BREAK_FLESH 0x04 +#define BREAK_WOOD 0x08 + +#define BREAK_SMOKE 0x10 +#define BREAK_TRANS 0x20 +#define BREAK_CONCRETE 0x40 +#define BREAK_2 0x80 + +// Colliding temp entity sounds + +#define BOUNCE_GLASS BREAK_GLASS +#define BOUNCE_METAL BREAK_METAL +#define BOUNCE_FLESH BREAK_FLESH +#define BOUNCE_WOOD BREAK_WOOD +#define BOUNCE_SHRAP 0x10 +#define BOUNCE_SHELL 0x20 +#define BOUNCE_CONCRETE BREAK_CONCRETE +#define BOUNCE_SHOTSHELL 0x80 + +// Temp entity bounce sound types +#define TE_BOUNCE_NULL 0 +#define TE_BOUNCE_SHELL 1 +#define TE_BOUNCE_SHOTSHELL 2 + +// Rendering constants +enum +{ + kRenderNormal, // src + kRenderTransColor, // c*a+dest*(1-a) + kRenderTransTexture, // src*a+dest*(1-a) + kRenderGlow, // src*a+dest -- No Z buffer checks + kRenderTransAlpha, // src*srca+dest*(1-srca) + kRenderTransAdd, // src*a+dest +}; + +enum +{ + kRenderFxNone = 0, + kRenderFxPulseSlow, + kRenderFxPulseFast, + kRenderFxPulseSlowWide, + kRenderFxPulseFastWide, + kRenderFxFadeSlow, + kRenderFxFadeFast, + kRenderFxSolidSlow, + kRenderFxSolidFast, + kRenderFxStrobeSlow, + kRenderFxStrobeFast, + kRenderFxStrobeFaster, + kRenderFxFlickerSlow, + kRenderFxFlickerFast, + kRenderFxNoDissipation, + kRenderFxDistort, // Distort/scale/translate flicker + kRenderFxHologram, // kRenderFxDistort + distance fade + kRenderFxDeadPlayer, // kRenderAmt is the player index + kRenderFxExplode, // Scale up really big! + kRenderFxGlowShell, // Glowing Shell + kRenderFxClampMinScale, // Keep this sprite from getting very small (SPRITES only!) + kRenderFxLightMultiplier, //CTM !!!CZERO added to tell the studiorender that the value in iuser2 is a lightmultiplier +}; + + +typedef unsigned int func_t; +typedef unsigned int string_t; + +typedef unsigned char byte; +typedef unsigned short word; +#define _DEF_BYTE_ + +#undef true +#undef false + +#ifndef __cplusplus +typedef enum {false, true} qboolean; +#else +typedef int qboolean; +#endif + +typedef struct +{ + byte r, g, b; +} color24; + +typedef struct +{ + unsigned r, g, b, a; +} colorVec; + +#ifdef _WIN32 +#pragma pack(push,2) +#endif + +typedef struct +{ + unsigned short r, g, b, a; +} PackedColorVec; + +#ifdef _WIN32 +#pragma pack(pop) +#endif +typedef struct link_s +{ + struct link_s *prev, *next; +} link_t; + +typedef struct edict_s edict_t; + +typedef struct +{ + vec3_t normal; + float dist; +} plane_t; + +typedef struct +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + qboolean inopen, inwater; + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + plane_t plane; // surface normal at impact + edict_t *ent; // entity the surface is on + int hitgroup; // 0 == generic, non zero is specific body part +} trace_t; + +#endif + diff --git a/common/crc.h b/common/crc.h new file mode 100644 index 0000000..eba4ab8 --- /dev/null +++ b/common/crc.h @@ -0,0 +1,65 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* crc.h */ +#ifndef CRC_H +#define CRC_H +#ifdef _WIN32 +#pragma once +#endif + +#include "archtypes.h" // DAL + +// MD5 Hash +typedef struct +{ + unsigned int buf[4]; + unsigned int bits[2]; + unsigned char in[64]; +} MD5Context_t; + + +#ifdef _WIN32 +typedef uint32 CRC32_t; +#else +typedef uint32 CRC32_t; +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif +void CRC32_Init(CRC32_t *pulCRC); +CRC32_t CRC32_Final(CRC32_t pulCRC); +void CRC32_ProcessBuffer(CRC32_t *pulCRC, void *p, int len); +void CRC32_ProcessByte(CRC32_t *pulCRC, unsigned char ch); +int CRC_File(CRC32_t *crcvalue, char *pszFileName); +#ifdef __cplusplus +} +#endif +unsigned char COM_BlockSequenceCRCByte (unsigned char *base, int length, int sequence); + +void MD5Init(MD5Context_t *context); +void MD5Update(MD5Context_t *context, unsigned char const *buf, + unsigned int len); +void MD5Final(unsigned char digest[16], MD5Context_t *context); +void Transform(unsigned int buf[4], unsigned int const in[16]); + +int MD5_Hash_File(unsigned char digest[16], char *pszFileName, int bUsefopen, int bSeed, unsigned int seed[4]); +char *MD5_Print(unsigned char hash[16]); +int MD5_Hash_CachedFile(unsigned char digest[16], unsigned char *pCache, int nFileSize, int bSeed, unsigned int seed[4]); + +int CRC_MapFile(CRC32_t *crcvalue, char *pszFileName); + +#endif diff --git a/common/cvardef.h b/common/cvardef.h new file mode 100644 index 0000000..16044ab --- /dev/null +++ b/common/cvardef.h @@ -0,0 +1,37 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef CVARDEF_H +#define CVARDEF_H + +#define FCVAR_ARCHIVE (1<<0) // set to cause it to be saved to vars.rc +#define FCVAR_USERINFO (1<<1) // changes the client's info string +#define FCVAR_SERVER (1<<2) // notifies players when changed +#define FCVAR_EXTDLL (1<<3) // defined by external DLL +#define FCVAR_CLIENTDLL (1<<4) // defined by the client dll +#define FCVAR_PROTECTED (1<<5) // It's a server cvar, but we don't send the data since it's a password, etc. Sends 1 if it's not bland/zero, 0 otherwise as value +#define FCVAR_SPONLY (1<<6) // This cvar cannot be changed by clients connected to a multiplayer server. +#define FCVAR_PRINTABLEONLY (1<<7) // This cvar's string cannot contain unprintable characters ( e.g., used for player name etc ). +#define FCVAR_UNLOGGED (1<<8) // If this is a FCVAR_SERVER, don't log changes to the log file / console if we are creating a log +#define FCVAR_NOEXTRAWHITEPACE (1<<9) // strip trailing/leading white space from this cvar + +typedef struct cvar_s +{ + char *name; + char *string; + int flags; + float value; + struct cvar_s *next; +} cvar_t; +#endif diff --git a/common/demo_api.h b/common/demo_api.h new file mode 100644 index 0000000..8284a81 --- /dev/null +++ b/common/demo_api.h @@ -0,0 +1,31 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined ( DEMO_APIH ) +#define DEMO_APIH +#ifdef _WIN32 +#pragma once +#endif + +typedef struct demo_api_s +{ + int ( *IsRecording ) ( void ); + int ( *IsPlayingback ) ( void ); + int ( *IsTimeDemo ) ( void ); + void ( *WriteBuffer ) ( int size, unsigned char *buffer ); +} demo_api_t; + +extern demo_api_t demoapi; + +#endif diff --git a/common/director_cmds.h b/common/director_cmds.h new file mode 100644 index 0000000..4c8fdd5 --- /dev/null +++ b/common/director_cmds.h @@ -0,0 +1,38 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// director_cmds.h +// sub commands for svc_director + +#define DRC_ACTIVE 0 // tells client that he's an spectator and will get director command +#define DRC_STATUS 1 // send status infos about proxy +#define DRC_CAMERA 2 // set the actual director camera position +#define DRC_EVENT 3 // informs the dircetor about ann important game event + + +#define DRC_FLAG_PRIO_MASK 0x0F // priorities between 0 and 15 (15 most important) +#define DRC_FLAG_SIDE (1<<4) +#define DRC_FLAG_DRAMATIC (1<<5) + + + +// commands of the director API function CallDirectorProc(...) + +#define DRCAPI_NOP 0 // no operation +#define DRCAPI_ACTIVE 1 // de/acivates director mode in engine +#define DRCAPI_STATUS 2 // request proxy information +#define DRCAPI_SETCAM 3 // set camera n to given position and angle +#define DRCAPI_GETCAM 4 // request camera n position and angle +#define DRCAPI_DIRPLAY 5 // set director time and play with normal speed +#define DRCAPI_DIRFREEZE 6 // freeze directo at this time +#define DRCAPI_SETVIEWMODE 7 // overview or 4 cameras +#define DRCAPI_SETOVERVIEWPARAMS 8 // sets parameter for overview mode +#define DRCAPI_SETFOCUS 9 // set the camera which has the input focus +#define DRCAPI_GETTARGETS 10 // queries engine for player list +#define DRCAPI_SETVIEWPOINTS 11 // gives engine all waypoints + + diff --git a/common/dlight.h b/common/dlight.h new file mode 100644 index 0000000..f869c98 --- /dev/null +++ b/common/dlight.h @@ -0,0 +1,33 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined ( DLIGHTH ) +#define DLIGHTH +#ifdef _WIN32 +#pragma once +#endif + +typedef struct dlight_s +{ + vec3_t origin; + float radius; + color24 color; + float die; // stop lighting after this time + float decay; // drop this each second + float minlight; // don't add when contributing less + int key; + qboolean dark; // subtracts light instead of adding +} dlight_t; + +#endif diff --git a/common/dll_state.h b/common/dll_state.h new file mode 100644 index 0000000..4065162 --- /dev/null +++ b/common/dll_state.h @@ -0,0 +1,23 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +//DLL State Flags + +#define DLL_INACTIVE 0 // no dll +#define DLL_ACTIVE 1 // dll is running +#define DLL_PAUSED 2 // dll is paused +#define DLL_CLOSE 3 // closing down dll +#define DLL_TRANS 4 // Level Transition + +// DLL Pause reasons + +#define DLL_NORMAL 0 // User hit Esc or something. +#define DLL_QUIT 4 // Quit now +#define DLL_RESTART 6 // Switch to launcher for linux, does a quit but returns 1 + +// DLL Substate info ( not relevant ) +#define ENG_NORMAL (1<<0) diff --git a/common/entity_state.h b/common/entity_state.h new file mode 100644 index 0000000..3d2d44a --- /dev/null +++ b/common/entity_state.h @@ -0,0 +1,193 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( ENTITY_STATEH ) +#define ENTITY_STATEH +#ifdef _WIN32 +#pragma once +#endif + +// For entityType below +#define ENTITY_NORMAL (1<<0) +#define ENTITY_BEAM (1<<1) + +// Entity state is used for the baseline and for delta compression of a packet of +// entities that is sent to a client. +typedef struct entity_state_s entity_state_t; + +struct entity_state_s +{ +// Fields which are filled in by routines outside of delta compression + int entityType; + // Index into cl_entities array for this entity. + int number; + float msg_time; + + // Message number last time the player/entity state was updated. + int messagenum; + + // Fields which can be transitted and reconstructed over the network stream + vec3_t origin; + vec3_t angles; + + int modelindex; + int sequence; + float frame; + int colormap; + short skin; + short solid; + int effects; + float scale; + + byte eflags; + + // Render information + int rendermode; + int renderamt; + color24 rendercolor; + int renderfx; + + int movetype; + float animtime; + float framerate; + int body; + byte controller[4]; + byte blending[4]; + vec3_t velocity; + + // Send bbox down to client for use during prediction. + vec3_t mins; + vec3_t maxs; + + int aiment; + // If owned by a player, the index of that player ( for projectiles ). + int owner; + + // Friction, for prediction. + float friction; + // Gravity multiplier + float gravity; + +// PLAYER SPECIFIC + int team; + int playerclass; + int health; + qboolean spectator; + int weaponmodel; + int gaitsequence; + // If standing on conveyor, e.g. + vec3_t basevelocity; + // Use the crouched hull, or the regular player hull. + int usehull; + // Latched buttons last time state updated. + int oldbuttons; + // -1 = in air, else pmove entity number + int onground; + int iStepLeft; + // How fast we are falling + float flFallVelocity; + + float fov; + int weaponanim; + + // Parametric movement overrides + vec3_t startpos; + vec3_t endpos; + float impacttime; + float starttime; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; +}; + +#include "pm_info.h" + +typedef struct clientdata_s +{ + vec3_t origin; + vec3_t velocity; + + int viewmodel; + vec3_t punchangle; + int flags; + int waterlevel; + int watertype; + vec3_t view_ofs; + float health; + + int bInDuck; + + int weapons; // remove? + + int flTimeStepSound; + int flDuckTime; + int flSwimTime; + int waterjumptime; + + float maxspeed; + + float fov; + int weaponanim; + + int m_iId; + int ammo_shells; + int ammo_nails; + int ammo_cells; + int ammo_rockets; + float m_flNextAttack; + + int tfstate; + + int pushmsec; + + int deadflag; + + char physinfo[ MAX_PHYSINFO_STRING ]; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; +} clientdata_t; + +#include "weaponinfo.h" + +typedef struct local_state_s +{ + entity_state_t playerstate; + clientdata_t client; + weapon_data_t weapondata[ 64 ]; +} local_state_t; + +#endif // !ENTITY_STATEH diff --git a/common/entity_types.h b/common/entity_types.h new file mode 100644 index 0000000..ff783df --- /dev/null +++ b/common/entity_types.h @@ -0,0 +1,26 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// entity_types.h +#if !defined( ENTITY_TYPESH ) +#define ENTITY_TYPESH + +#define ET_NORMAL 0 +#define ET_PLAYER 1 +#define ET_TEMPENTITY 2 +#define ET_BEAM 3 +// BMODEL or SPRITE that was split across BSP nodes +#define ET_FRAGMENTED 4 + +#endif // !ENTITY_TYPESH diff --git a/common/enums.h b/common/enums.h new file mode 100644 index 0000000..1590191 --- /dev/null +++ b/common/enums.h @@ -0,0 +1,27 @@ +/*** + * + * Copyright (c) 2009, Valve LLC. All rights reserved. + * + * This product contains software technology licensed from Id + * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. + * All Rights Reserved. + * + * Use, distribution, and modification of this source code and/or resulting + * object code is restricted to non-commercial enhancements to products from + * Valve LLC. All other use, distribution, or modification is prohibited + * without written permission from Valve LLC. + * + ****/ + +#ifndef ENUMS_H +#define ENUMS_H + +typedef enum netsrc_s + { + NS_CLIENT, + NS_SERVER, + NS_MULTICAST // xxxMO + } netsrc_t; + +#endif + diff --git a/common/event_api.h b/common/event_api.h new file mode 100644 index 0000000..722dfe2 --- /dev/null +++ b/common/event_api.h @@ -0,0 +1,51 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined ( EVENT_APIH ) +#define EVENT_APIH +#ifdef _WIN32 +#pragma once +#endif + +#define EVENT_API_VERSION 1 + +typedef struct event_api_s +{ + int version; + void ( *EV_PlaySound ) ( int ent, float *origin, int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch ); + void ( *EV_StopSound ) ( int ent, int channel, const char *sample ); + int ( *EV_FindModelIndex )( const char *pmodel ); + int ( *EV_IsLocal ) ( int playernum ); + int ( *EV_LocalPlayerDucking ) ( void ); + void ( *EV_LocalPlayerViewheight ) ( float * ); + void ( *EV_LocalPlayerBounds ) ( int hull, float *mins, float *maxs ); + int ( *EV_IndexFromTrace) ( struct pmtrace_s *pTrace ); + struct physent_s *( *EV_GetPhysent ) ( int idx ); + void ( *EV_SetUpPlayerPrediction ) ( int dopred, int bIncludeLocalClient ); + void ( *EV_PushPMStates ) ( void ); + void ( *EV_PopPMStates ) ( void ); + void ( *EV_SetSolidPlayers ) (int playernum); + void ( *EV_SetTraceHull ) ( int hull ); + void ( *EV_PlayerTrace ) ( float *start, float *end, int traceFlags, int ignore_pe, struct pmtrace_s *tr ); + void ( *EV_WeaponAnimation ) ( int sequence, int body ); + unsigned short ( *EV_PrecacheEvent ) ( int type, const char* psz ); + void ( *EV_PlaybackEvent ) ( int flags, const struct edict_s *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); + const char *( *EV_TraceTexture ) ( int ground, float *vstart, float *vend ); + void ( *EV_StopAllSounds ) ( int entnum, int entchannel ); + void ( *EV_KillEvents ) ( int entnum, const char *eventname ); +} event_api_t; + +extern event_api_t eventapi; + +#endif diff --git a/common/event_args.h b/common/event_args.h new file mode 100644 index 0000000..99dd49a --- /dev/null +++ b/common/event_args.h @@ -0,0 +1,50 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( EVENT_ARGSH ) +#define EVENT_ARGSH +#ifdef _WIN32 +#pragma once +#endif + +// Event was invoked with stated origin +#define FEVENT_ORIGIN ( 1<<0 ) + +// Event was invoked with stated angles +#define FEVENT_ANGLES ( 1<<1 ) + +typedef struct event_args_s +{ + int flags; + + // Transmitted + int entindex; + + float origin[3]; + float angles[3]; + float velocity[3]; + + int ducking; + + float fparam1; + float fparam2; + + int iparam1; + int iparam2; + + int bparam1; + int bparam2; +} event_args_t; + +#endif diff --git a/common/event_flags.h b/common/event_flags.h new file mode 100644 index 0000000..43f804f --- /dev/null +++ b/common/event_flags.h @@ -0,0 +1,47 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( EVENT_FLAGSH ) +#define EVENT_FLAGSH +#ifdef _WIN32 +#pragma once +#endif + +// Skip local host for event send. +#define FEV_NOTHOST (1<<0) + +// Send the event reliably. You must specify the origin and angles and use +// PLAYBACK_EVENT_FULL for this to work correctly on the server for anything +// that depends on the event origin/angles. I.e., the origin/angles are not +// taken from the invoking edict for reliable events. +#define FEV_RELIABLE (1<<1) + +// Don't restrict to PAS/PVS, send this event to _everybody_ on the server ( useful for stopping CHAN_STATIC +// sounds started by client event when client is not in PVS anymore ( hwguy in TFC e.g. ). +#define FEV_GLOBAL (1<<2) + +// If this client already has one of these events in its queue, just update the event instead of sending it as a duplicate +// +#define FEV_UPDATE (1<<3) + +// Only send to entity specified as the invoker +#define FEV_HOSTONLY (1<<4) + +// Only send if the event was created on the server. +#define FEV_SERVER (1<<5) + +// Only issue event client side ( from shared code ) +#define FEV_CLIENT (1<<6) + +#endif diff --git a/common/hltv.h b/common/hltv.h new file mode 100644 index 0000000..ab7184c --- /dev/null +++ b/common/hltv.h @@ -0,0 +1,54 @@ +// hltv.h +// all shared consts between server, clients and proxy + +#ifndef HLTV_H +#define HLTV_H + +#define TYPE_CLIENT 0 // client is a normal HL client (default) +#define TYPE_PROXY 1 // client is another proxy +#define TYPE_COMMENTATOR 3 // client is a commentator +#define TYPE_DEMO 4 // client is a demo file + +// sub commands of svc_hltv: +#define HLTV_ACTIVE 0 // tells client that he's an spectator and will get director commands +#define HLTV_STATUS 1 // send status infos about proxy +#define HLTV_LISTEN 2 // tell client to listen to a multicast stream + +// director command types: +#define DRC_CMD_NONE 0 // NULL director command +#define DRC_CMD_START 1 // start director mode +#define DRC_CMD_EVENT 2 // informs about director command +#define DRC_CMD_MODE 3 // switches camera modes +#define DRC_CMD_CAMERA 4 // set fixed camera +#define DRC_CMD_TIMESCALE 5 // sets time scale +#define DRC_CMD_MESSAGE 6 // send HUD centerprint +#define DRC_CMD_SOUND 7 // plays a particular sound +#define DRC_CMD_STATUS 8 // HLTV broadcast status +#define DRC_CMD_BANNER 9 // set GUI banner +#define DRC_CMD_STUFFTEXT 10 // like the normal svc_stufftext but as director command +#define DRC_CMD_CHASE 11 // chase a certain player +#define DRC_CMD_INEYE 12 // view player through own eyes +#define DRC_CMD_MAP 13 // show overview map +#define DRC_CMD_CAMPATH 14 // define camera waypoint +#define DRC_CMD_WAYPOINTS 15 // start moving camera, inetranl message + +#define DRC_CMD_LAST 15 + + +// DRC_CMD_EVENT event flags +#define DRC_FLAG_PRIO_MASK 0x0F // priorities between 0 and 15 (15 most important) +#define DRC_FLAG_SIDE (1<<4) // +#define DRC_FLAG_DRAMATIC (1<<5) // is a dramatic scene +#define DRC_FLAG_SLOWMOTION (1<<6) // would look good in SloMo +#define DRC_FLAG_FACEPLAYER (1<<7) // player is doning something (reload/defuse bomb etc) +#define DRC_FLAG_INTRO (1<<8) // is a introduction scene +#define DRC_FLAG_FINAL (1<<9) // is a final scene +#define DRC_FLAG_NO_RANDOM (1<<10) // don't randomize event data + + +// DRC_CMD_WAYPOINT flags +#define DRC_FLAG_STARTPATH 1 // end with speed 0.0 +#define DRC_FLAG_SLOWSTART 2 // start with speed 0.0 +#define DRC_FLAG_SLOWEND 4 // end with speed 0.0 + +#endif // HLTV_H diff --git a/common/in_buttons.h b/common/in_buttons.h new file mode 100644 index 0000000..4196a2d --- /dev/null +++ b/common/in_buttons.h @@ -0,0 +1,38 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef IN_BUTTONS_H +#define IN_BUTTONS_H +#ifdef _WIN32 +#pragma once +#endif + +#define IN_ATTACK (1 << 0) +#define IN_JUMP (1 << 1) +#define IN_DUCK (1 << 2) +#define IN_FORWARD (1 << 3) +#define IN_BACK (1 << 4) +#define IN_USE (1 << 5) +#define IN_CANCEL (1 << 6) +#define IN_LEFT (1 << 7) +#define IN_RIGHT (1 << 8) +#define IN_MOVELEFT (1 << 9) +#define IN_MOVERIGHT (1 << 10) +#define IN_ATTACK2 (1 << 11) +#define IN_RUN (1 << 12) +#define IN_RELOAD (1 << 13) +#define IN_ALT1 (1 << 14) +#define IN_SCORE (1 << 15) // Used by client.dll for when scoreboard is held down + +#endif // IN_BUTTONS_H diff --git a/common/interface.cpp b/common/interface.cpp new file mode 100644 index 0000000..69c5345 --- /dev/null +++ b/common/interface.cpp @@ -0,0 +1,150 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include +#include "interface.h" + +#ifndef _WIN32 // LINUX +#include +#include // getcwd +#include // sprintf +#endif + + +// ------------------------------------------------------------------------------------ // +// InterfaceReg. +// ------------------------------------------------------------------------------------ // +InterfaceReg *InterfaceReg::s_pInterfaceRegs = NULL; + + +InterfaceReg::InterfaceReg( InstantiateInterfaceFn fn, const char *pName ) : + m_pName(pName) +{ + m_CreateFn = fn; + m_pNext = s_pInterfaceRegs; + s_pInterfaceRegs = this; +} + + + +// ------------------------------------------------------------------------------------ // +// CreateInterface. +// ------------------------------------------------------------------------------------ // +EXPORT_FUNCTION IBaseInterface *CreateInterface( const char *pName, int *pReturnCode ) +{ + InterfaceReg *pCur; + + for(pCur=InterfaceReg::s_pInterfaceRegs; pCur; pCur=pCur->m_pNext) + { + if(strcmp(pCur->m_pName, pName) == 0) + { + if ( pReturnCode ) + { + *pReturnCode = IFACE_OK; + } + return pCur->m_CreateFn(); + } + } + + if ( pReturnCode ) + { + *pReturnCode = IFACE_FAILED; + } + return NULL; +} + + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include "windows.h" +#endif + + +#ifdef _WIN32 +HINTERFACEMODULE Sys_LoadModule(const char *pModuleName) +{ + return (HINTERFACEMODULE)LoadLibrary(pModuleName); +} + +#else // LINUX +HINTERFACEMODULE Sys_LoadModule(const char *pModuleName) +{ + // Linux dlopen() doesn't look in the current directory for libraries. + // We tell it to, so people don't have to 'install' libraries as root. + + char szCwd[1024]; + char szAbsoluteLibFilename[1024]; + + getcwd( szCwd, sizeof( szCwd ) ); + if ( szCwd[ strlen( szCwd ) - 1 ] == '/' ) + szCwd[ strlen( szCwd ) - 1 ] = 0; + + sprintf( szAbsoluteLibFilename, "%s/%s", szCwd, pModuleName ); + + return (HINTERFACEMODULE)dlopen( szAbsoluteLibFilename, RTLD_NOW ); +} + +#endif + + +#ifdef _WIN32 +void Sys_FreeModule(HINTERFACEMODULE hModule) +{ + if(!hModule) + return; + + FreeLibrary((HMODULE)hModule); +} + +#else // LINUX +void Sys_FreeModule(HINTERFACEMODULE hModule) +{ + if(!hModule) + return; + + dlclose( (void *)hModule ); +} + +#endif + + +//----------------------------------------------------------------------------- +// Purpose: returns the instance of this module +// Output : interface_instance_t +//----------------------------------------------------------------------------- +CreateInterfaceFn Sys_GetFactoryThis( void ) +{ + return CreateInterface; +} + + +//----------------------------------------------------------------------------- +// Purpose: returns the instance of the named module +// Input : *pModuleName - name of the module +// Output : interface_instance_t - instance of that module +//----------------------------------------------------------------------------- + +#ifdef _WIN32 +CreateInterfaceFn Sys_GetFactory( HINTERFACEMODULE hModule ) +{ + if(!hModule) + return NULL; + + return (CreateInterfaceFn)GetProcAddress((HMODULE)hModule, CREATEINTERFACE_PROCNAME); +} + +#else // LINUX +CreateInterfaceFn Sys_GetFactory( HINTERFACEMODULE hModule ) +{ + if(!hModule) + return NULL; + + return (CreateInterfaceFn)dlsym( (void *)hModule, CREATEINTERFACE_PROCNAME ); +} + +#endif diff --git a/common/interface.h b/common/interface.h new file mode 100644 index 0000000..b49d087 --- /dev/null +++ b/common/interface.h @@ -0,0 +1,129 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// This header defines the interface convention used in the valve engine. +// To make an interface and expose it: +// 1. Derive from IBaseInterface. +// 2. The interface must be ALL pure virtuals, and have no data members. +// 3. Define a name for it. +// 4. In its implementation file, use EXPOSE_INTERFACE or EXPOSE_SINGLE_INTERFACE. + +// Versioning +// There are two versioning cases that are handled by this: +// 1. You add functions to the end of an interface, so it is binary compatible with the previous interface. In this case, +// you need two EXPOSE_INTERFACEs: one to expose your class as the old interface and one to expose it as the new interface. +// 2. You update an interface so it's not compatible anymore (but you still want to be able to expose the old interface +// for legacy code). In this case, you need to make a new version name for your new interface, and make a wrapper interface and +// expose it for the old interface. + +#ifndef INTERFACE_H +#define INTERFACE_H + +#ifdef __cplusplus + +// All interfaces derive from this. +class IBaseInterface +{ +public: + + virtual ~IBaseInterface() {} +}; + + +#define CREATEINTERFACE_PROCNAME "CreateInterface" +typedef IBaseInterface* (*CreateInterfaceFn)(const char *pName, int *pReturnCode); + + +typedef IBaseInterface* (*InstantiateInterfaceFn)(); + + +// Used internally to register classes. +class InterfaceReg +{ +public: + InterfaceReg(InstantiateInterfaceFn fn, const char *pName); + +public: + + InstantiateInterfaceFn m_CreateFn; + const char *m_pName; + + InterfaceReg *m_pNext; // For the global list. + static InterfaceReg *s_pInterfaceRegs; +}; + + +// Use this to expose an interface that can have multiple instances. +// e.g.: +// EXPOSE_INTERFACE( CInterfaceImp, IInterface, "MyInterface001" ) +// This will expose a class called CInterfaceImp that implements IInterface (a pure class) +// clients can receive a pointer to this class by calling CreateInterface( "MyInterface001" ) +// +// In practice, the shared header file defines the interface (IInterface) and version name ("MyInterface001") +// so that each component can use these names/vtables to communicate +// +// A single class can support multiple interfaces through multiple inheritance +// +#define EXPOSE_INTERFACE_FN(functionName, interfaceName, versionName) \ + static InterfaceReg __g_Create##className##_reg(functionName, versionName); + +#define EXPOSE_INTERFACE(className, interfaceName, versionName) \ + static IBaseInterface* __Create##className##_interface() {return (interfaceName *)new className;}\ + static InterfaceReg __g_Create##className##_reg(__Create##className##_interface, versionName ); + +// Use this to expose a singleton interface with a global variable you've created. +#define EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, globalVarName) \ + static IBaseInterface* __Create##className##interfaceName##_interface() {return (interfaceName *)&globalVarName;}\ + static InterfaceReg __g_Create##className##interfaceName##_reg(__Create##className##interfaceName##_interface, versionName); + +// Use this to expose a singleton interface. This creates the global variable for you automatically. +#define EXPOSE_SINGLE_INTERFACE(className, interfaceName, versionName) \ + static className __g_##className##_singleton;\ + EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, __g_##className##_singleton) + + +#ifdef WIN32 + #define EXPORT_FUNCTION __declspec(dllexport) +#else + #define EXPORT_FUNCTION +#endif + + +// This function is automatically exported and allows you to access any interfaces exposed with the above macros. +// if pReturnCode is set, it will return one of the following values +// extend this for other error conditions/code +enum +{ + IFACE_OK = 0, + IFACE_FAILED +}; + + +extern "C" +{ + EXPORT_FUNCTION IBaseInterface* CreateInterface(const char *pName, int *pReturnCode); +}; + + +// Handle to an interface (HInterfaceModule_t* is just there for type safety). +typedef struct HInterfaceModule_t* HINTERFACEMODULE; + + +// Use these to load and unload a module. +extern HINTERFACEMODULE Sys_LoadModule(const char *pModuleName); +extern void Sys_FreeModule(HINTERFACEMODULE hModule); + +// Use these to get the factory function from either a loaded module or the current module. +extern CreateInterfaceFn Sys_GetFactory( HINTERFACEMODULE hModule ); +extern CreateInterfaceFn Sys_GetFactoryThis( void ); + +#endif // __cplusplus + +#endif + + + diff --git a/common/ivoicetweak.h b/common/ivoicetweak.h new file mode 100644 index 0000000..d5e6b3e --- /dev/null +++ b/common/ivoicetweak.h @@ -0,0 +1,38 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef IVOICETWEAK_H +#define IVOICETWEAK_H +#ifdef _WIN32 +#pragma once +#endif + +// These provide access to the voice controls. +typedef enum +{ + MicrophoneVolume=0, // values 0-1. + OtherSpeakerScale, // values 0-1. Scales how loud other players are. + MicBoost, // 20 db gain to voice input +} VoiceTweakControl; + + +typedef struct IVoiceTweak_s +{ + // These turn voice tweak mode on and off. While in voice tweak mode, the user's voice is echoed back + // without sending to the server. + int (*StartVoiceTweakMode)(); // Returns 0 on error. + void (*EndVoiceTweakMode)(); + + // Get/set control values. + void (*SetControlFloat)(VoiceTweakControl iControl, float value); + float (*GetControlFloat)(VoiceTweakControl iControl); + + int (*GetSpeakingVolume)(); +} IVoiceTweak; + + +#endif // IVOICETWEAK_H diff --git a/common/mathlib.h b/common/mathlib.h new file mode 100644 index 0000000..f5a0c37 --- /dev/null +++ b/common/mathlib.h @@ -0,0 +1,158 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// mathlib.h + +typedef float vec_t; +#ifndef DID_VEC3_T_DEFINE +#define DID_VEC3_T_DEFINE +typedef vec_t vec3_t[3]; +#endif +typedef vec_t vec4_t[4]; // x,y,z,w +typedef vec_t vec5_t[5]; + +typedef short vec_s_t; +typedef vec_s_t vec3s_t[3]; +typedef vec_s_t vec4s_t[4]; // x,y,z,w +typedef vec_s_t vec5s_t[5]; + +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 + +struct mplane_s; + +extern vec3_t vec3_origin; +extern int nanmask; + +#define IS_NAN(x) (((*(int *)&x)&nanmask)==nanmask) + +#ifndef VECTOR_H + #define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) +#endif + +#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 VectorClear(a) {(a)[0]=0.0;(a)[1]=0.0;(a)[2]=0.0;} + +void VectorMA (const vec3_t veca, float scale, const vec3_t vecb, vec3_t vecc); + +vec_t _DotProduct (vec3_t v1, vec3_t v2); +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out); +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out); +void _VectorCopy (vec3_t in, vec3_t out); + +int VectorCompare (const vec3_t v1, const vec3_t v2); +float Length (const vec3_t v); +void CrossProduct (const vec3_t v1, const vec3_t v2, vec3_t cross); +float VectorNormalize (vec3_t v); // returns vector length +void VectorInverse (vec3_t v); +void VectorScale (const vec3_t in, vec_t scale, vec3_t out); +int Q_log2(int val); + +void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]); +void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]); + +// Here are some "manual" INLINE routines for doing floating point to integer conversions +extern short new_cw, old_cw; + +typedef union DLONG { + int i[2]; + double d; + float f; + } DLONG; + +extern DLONG dlong; + +#ifdef _WIN32 +void __inline set_fpu_cw(void) +{ +_asm + { wait + fnstcw old_cw + wait + mov ax, word ptr old_cw + or ah, 0xc + mov word ptr new_cw,ax + fldcw new_cw + } +} + +int __inline quick_ftol(float f) +{ + _asm { + // Assumes that we are already in chop mode, and only need a 32-bit int + fld DWORD PTR f + fistp DWORD PTR dlong + } + return dlong.i[0]; +} + +void __inline restore_fpu_cw(void) +{ + _asm fldcw old_cw +} +#else +#define set_fpu_cw() /* */ +#define quick_ftol(f) ftol(f) +#define restore_fpu_cw() /* */ +#endif + +void FloorDivMod (double numer, double denom, int *quotient, + int *rem); +fixed16_t Invert24To16(fixed16_t val); +int GreatestCommonDivisor (int i1, int i2); + +void AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); +void AngleVectorsTranspose (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); +#define AngleIVectors AngleVectorsTranspose + +void AngleMatrix (const vec3_t angles, float (*matrix)[4] ); +void AngleIMatrix (const vec3_t angles, float (*matrix)[4] ); +void VectorTransform (const vec3_t in1, float in2[3][4], vec3_t out); + +void NormalizeAngles( vec3_t angles ); +void InterpolateAngles( vec3_t start, vec3_t end, vec3_t output, float frac ); +float AngleBetweenVectors( const vec3_t v1, const vec3_t v2 ); + + +void VectorMatrix( vec3_t forward, vec3_t right, vec3_t up); +void VectorAngles( const vec3_t forward, vec3_t angles ); + +int InvertMatrix( const float * m, float *out ); + +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct mplane_s *plane); +float anglemod(float a); + + + +#define BOX_ON_PLANE_SIDE(emins, emaxs, p) \ + (((p)->type < 3)? \ + ( \ + ((p)->dist <= (emins)[(p)->type])? \ + 1 \ + : \ + ( \ + ((p)->dist >= (emaxs)[(p)->type])?\ + 2 \ + : \ + 3 \ + ) \ + ) \ + : \ + BoxOnPlaneSide( (emins), (emaxs), (p))) diff --git a/common/net_api.h b/common/net_api.h new file mode 100644 index 0000000..49c39e7 --- /dev/null +++ b/common/net_api.h @@ -0,0 +1,99 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( NET_APIH ) +#define NET_APIH +#ifdef _WIN32 +#pragma once +#endif + +#if !defined ( NETADRH ) +#include "netadr.h" +#endif + +#define NETAPI_REQUEST_SERVERLIST ( 0 ) // Doesn't need a remote address +#define NETAPI_REQUEST_PING ( 1 ) +#define NETAPI_REQUEST_RULES ( 2 ) +#define NETAPI_REQUEST_PLAYERS ( 3 ) +#define NETAPI_REQUEST_DETAILS ( 4 ) + +// Set this flag for things like broadcast requests, etc. where the engine should not +// kill the request hook after receiving the first response +#define FNETAPI_MULTIPLE_RESPONSE ( 1<<0 ) + +typedef void ( *net_api_response_func_t ) ( struct net_response_s *response ); + +#define NET_SUCCESS ( 0 ) +#define NET_ERROR_TIMEOUT ( 1<<0 ) +#define NET_ERROR_PROTO_UNSUPPORTED ( 1<<1 ) +#define NET_ERROR_UNDEFINED ( 1<<2 ) + +typedef struct net_adrlist_s +{ + struct net_adrlist_s *next; + netadr_t remote_address; +} net_adrlist_t; + +typedef struct net_response_s +{ + // NET_SUCCESS or an error code + int error; + + // Context ID + int context; + // Type + int type; + + // Server that is responding to the request + netadr_t remote_address; + + // Response RTT ping time + double ping; + // Key/Value pair string ( separated by backlash \ characters ) + // WARNING: You must copy this buffer in the callback function, because it is freed + // by the engine right after the call!!!! + // ALSO: For NETAPI_REQUEST_SERVERLIST requests, this will be a pointer to a linked list of net_adrlist_t's + void *response; +} net_response_t; + +typedef struct net_status_s +{ + // Connected to remote server? 1 == yes, 0 otherwise + int connected; + // Client's IP address + netadr_t local_address; + // Address of remote server + netadr_t remote_address; + // Packet Loss ( as a percentage ) + int packet_loss; + // Latency, in seconds ( multiply by 1000.0 to get milliseconds ) + double latency; + // Connection time, in seconds + double connection_time; + // Rate setting ( for incoming data ) + double rate; +} net_status_t; + +typedef struct net_api_s +{ + // APIs + void ( *InitNetworking )( void ); + void ( *Status ) ( struct net_status_s *status ); + void ( *SendRequest) ( int context, int request, int flags, double timeout, struct netadr_s *remote_address, net_api_response_func_t response ); + void ( *CancelRequest ) ( int context ); + void ( *CancelAllRequests ) ( void ); + char *( *AdrToString ) ( struct netadr_s *a ); + int ( *CompareAdr ) ( struct netadr_s *a, struct netadr_s *b ); + int ( *StringToAdr ) ( char *s, struct netadr_s *a ); + const char *( *ValueForKey ) ( const char *s, const char *key ); + void ( *RemoveKey ) ( char *s, const char *key ); + void ( *SetValueForKey ) (char *s, const char *key, const char *value, int maxsize ); +} net_api_t; + +extern net_api_t netapi; + +#endif // NET_APIH \ No newline at end of file diff --git a/common/netadr.h b/common/netadr.h new file mode 100644 index 0000000..304073c --- /dev/null +++ b/common/netadr.h @@ -0,0 +1,40 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// netadr.h +#ifndef NETADR_H +#define NETADR_H +#ifdef _WIN32 +#pragma once +#endif + +typedef enum +{ + NA_UNUSED, + NA_LOOPBACK, + NA_BROADCAST, + NA_IP, + NA_IPX, + NA_BROADCAST_IPX, +} netadrtype_t; + +typedef struct netadr_s +{ + netadrtype_t type; + unsigned char ip[4]; + unsigned char ipx[10]; + unsigned short port; +} netadr_t; + +#endif // NETADR_H diff --git a/common/nowin.h b/common/nowin.h new file mode 100644 index 0000000..cecd2c1 --- /dev/null +++ b/common/nowin.h @@ -0,0 +1,16 @@ +//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef INC_NOWIN_H +#define INC_NOWIN_H +#ifndef _WIN32 + +#include +#include + +#endif //!_WIN32 +#endif //INC_NOWIN_H diff --git a/common/parsemsg.cpp b/common/parsemsg.cpp new file mode 100644 index 0000000..6742db2 --- /dev/null +++ b/common/parsemsg.cpp @@ -0,0 +1,259 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// parsemsg.cpp +// +//-------------------------------------------------------------------------------------------------------------- +#include "parsemsg.h" +#include + +typedef unsigned char byte; +#define true 1 + +static byte *gpBuf; +static int giSize; +static int giRead; +static int giBadRead; + +int READ_OK( void ) +{ + return !giBadRead; +} + +void BEGIN_READ( void *buf, int size ) +{ + giRead = 0; + giBadRead = 0; + giSize = size; + gpBuf = (byte*)buf; +} + + +int READ_CHAR( void ) +{ + int c; + + if (giRead + 1 > giSize) + { + giBadRead = true; + return -1; + } + + c = (signed char)gpBuf[giRead]; + giRead++; + + return c; +} + +int READ_BYTE( void ) +{ + int c; + + if (giRead+1 > giSize) + { + giBadRead = true; + return -1; + } + + c = (unsigned char)gpBuf[giRead]; + giRead++; + + return c; +} + +int READ_SHORT( void ) +{ + int c; + + if (giRead+2 > giSize) + { + giBadRead = true; + return -1; + } + + c = (short)( gpBuf[giRead] + ( gpBuf[giRead+1] << 8 ) ); + + giRead += 2; + + return c; +} + +int READ_WORD( void ) +{ + return READ_SHORT(); +} + + +int READ_LONG( void ) +{ + int c; + + if (giRead+4 > giSize) + { + giBadRead = true; + return -1; + } + + c = gpBuf[giRead] + (gpBuf[giRead + 1] << 8) + (gpBuf[giRead + 2] << 16) + (gpBuf[giRead + 3] << 24); + + giRead += 4; + + return c; +} + +float READ_FLOAT( void ) +{ + union + { + byte b[4]; + float f; + int l; + } dat; + + dat.b[0] = gpBuf[giRead]; + dat.b[1] = gpBuf[giRead+1]; + dat.b[2] = gpBuf[giRead+2]; + dat.b[3] = gpBuf[giRead+3]; + giRead += 4; + +// dat.l = LittleLong (dat.l); + + return dat.f; +} + +char* READ_STRING( void ) +{ + static char string[2048]; + int l,c; + + string[0] = 0; + + l = 0; + do + { + if ( giRead+1 > giSize ) + break; // no more characters + + c = READ_CHAR(); + if (c == -1 || c == 0) + break; + string[l] = c; + l++; + } while (l < sizeof(string)-1); + + string[l] = 0; + + return string; +} + +float READ_COORD( void ) +{ + return (float)(READ_SHORT() * (1.0/8)); +} + +float READ_ANGLE( void ) +{ + return (float)(READ_CHAR() * (360.0/256)); +} + +float READ_HIRESANGLE( void ) +{ + return (float)(READ_SHORT() * (360.0/65536)); +} + +//-------------------------------------------------------------------------------------------------------------- +BufferWriter::BufferWriter() +{ + Init( NULL, 0 ); +} + +//-------------------------------------------------------------------------------------------------------------- +BufferWriter::BufferWriter( unsigned char *buffer, int bufferLen ) +{ + Init( buffer, bufferLen ); +} + +//-------------------------------------------------------------------------------------------------------------- +void BufferWriter::Init( unsigned char *buffer, int bufferLen ) +{ + m_overflow = false; + m_buffer = buffer; + m_remaining = bufferLen; + m_overallLength = bufferLen; +} + +//-------------------------------------------------------------------------------------------------------------- +void BufferWriter::WriteByte( unsigned char data ) +{ + if (!m_buffer || !m_remaining) + { + m_overflow = true; + return; + } + + *m_buffer = data; + ++m_buffer; + --m_remaining; +} + +//-------------------------------------------------------------------------------------------------------------- +void BufferWriter::WriteLong( int data ) +{ + if (!m_buffer || m_remaining < 4) + { + m_overflow = true; + return; + } + + m_buffer[0] = data&0xff; + m_buffer[1] = (data>>8)&0xff; + m_buffer[2] = (data>>16)&0xff; + m_buffer[3] = data>>24; + m_buffer += 4; + m_remaining -= 4; +} + +//-------------------------------------------------------------------------------------------------------------- +void BufferWriter::WriteString( const char *str ) +{ + if (!m_buffer || !m_remaining) + { + m_overflow = true; + return; + } + + if (!str) + str = ""; + + int len = strlen(str)+1; + if ( len > m_remaining ) + { + m_overflow = true; + str = ""; + len = 1; + } + + strcpy((char *)m_buffer, str); + m_remaining -= len; + m_buffer += len; +} + +//-------------------------------------------------------------------------------------------------------------- +int BufferWriter::GetSpaceUsed() +{ + return m_overallLength - m_remaining; +} + +//-------------------------------------------------------------------------------------------------------------- diff --git a/common/parsemsg.h b/common/parsemsg.h new file mode 100644 index 0000000..be7affc --- /dev/null +++ b/common/parsemsg.h @@ -0,0 +1,66 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// parsemsg.h +// MDC - copying from cstrike\cl_dll so career-mode stuff can catch messages +// in this dll. (and C++ifying it) +// + +#ifndef PARSEMSG_H +#define PARSEMSG_H + +#define ASSERT( x ) +//-------------------------------------------------------------------------------------------------------------- +void BEGIN_READ( void *buf, int size ); +int READ_CHAR( void ); +int READ_BYTE( void ); +int READ_SHORT( void ); +int READ_WORD( void ); +int READ_LONG( void ); +float READ_FLOAT( void ); +char* READ_STRING( void ); +float READ_COORD( void ); +float READ_ANGLE( void ); +float READ_HIRESANGLE( void ); +int READ_OK( void ); + +//-------------------------------------------------------------------------------------------------------------- +class BufferWriter +{ +public: + BufferWriter(); + BufferWriter( unsigned char *buffer, int bufferLen ); + void Init( unsigned char *buffer, int bufferLen ); + + void WriteByte( unsigned char data ); + void WriteLong( int data ); + void WriteString( const char *str ); + + bool HasOverflowed(); + int GetSpaceUsed(); + +protected: + unsigned char *m_buffer; + int m_remaining; + bool m_overflow; + int m_overallLength; +}; + +//-------------------------------------------------------------------------------------------------------------- + +#endif // PARSEMSG_H + + + diff --git a/common/particledef.h b/common/particledef.h new file mode 100644 index 0000000..7e4043a --- /dev/null +++ b/common/particledef.h @@ -0,0 +1,57 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( PARTICLEDEFH ) +#define PARTICLEDEFH +#ifdef _WIN32 +#pragma once +#endif + +typedef enum { + pt_static, + pt_grav, + pt_slowgrav, + pt_fire, + pt_explode, + pt_explode2, + pt_blob, + pt_blob2, + pt_vox_slowgrav, + pt_vox_grav, + pt_clientcustom // Must have callback function specified +} ptype_t; + +// !!! if this is changed, it must be changed in d_ifacea.h too !!! +typedef struct particle_s +{ +// driver-usable fields + vec3_t org; + short color; + short packedColor; +// drivers never touch the following fields + struct particle_s *next; + vec3_t vel; + float ramp; + float die; + ptype_t type; + void (*deathfunc)( struct particle_s *particle ); + + // for pt_clientcusttom, we'll call this function each frame + void (*callback)( struct particle_s *particle, float frametime ); + + // For deathfunc, etc. + unsigned char context; +} particle_t; + +#endif diff --git a/common/pmtrace.h b/common/pmtrace.h new file mode 100644 index 0000000..1784e9c --- /dev/null +++ b/common/pmtrace.h @@ -0,0 +1,43 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( PMTRACEH ) +#define PMTRACEH +#ifdef _WIN32 +#pragma once +#endif + +typedef struct +{ + vec3_t normal; + float dist; +} pmplane_t; + +typedef struct pmtrace_s pmtrace_t; + +struct pmtrace_s +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + qboolean inopen, inwater; // End point is in empty space or in water + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + pmplane_t plane; // surface normal at impact + int ent; // entity at impact + vec3_t deltavelocity; // Change in player's velocity caused by impact. + // Only run on server. + int hitgroup; +}; + +#endif diff --git a/common/port.h b/common/port.h new file mode 100644 index 0000000..c3b2eca --- /dev/null +++ b/common/port.h @@ -0,0 +1,122 @@ +// port.h: portability helper +// +////////////////////////////////////////////////////////////////////// + +#if !defined PORT_H +#define PORT_H + +#include "archtypes.h" // DAL + +#ifdef _WIN32 + +// Insert your headers here +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#define WIN32_EXTRA_LEAN + +#include + +#include +#include +#include + +#define snprintf _snprintf +#define vsnprintf _vsnprintf + +#else // _WIN32 + +#include +#include +#include // exit() +#include // strncpy() +#include // tolower() +#include +#include +#include +#include + +typedef unsigned char BYTE; +typedef short int WORD; +typedef unsigned int DWORD; +typedef int32 LONG; +//typedef uint32 ULONG; +#ifndef ARCHTYPES_H +typedef uint32 ULONG; +#endif +typedef void *HANDLE; +#ifndef HMODULE +typedef void *HMODULE; +#endif +typedef char * LPSTR; + +#define __cdecl + + +//const int MAX_PATH = PATH_MAX; +#define MAX_PATH PATH_MAX + +#ifdef LINUX +typedef struct POINT_s +{ + int x; + int y; +} POINT; +typedef void *HINSTANCE; +typedef void *HWND; +typedef void *HDC; +typedef void *HGLRC; + +typedef struct RECT_s +{ + int left; + int right; + int top; + int bottom; +} RECT; +#endif + + +#ifdef __cplusplus + +//#undef FALSE +//#undef TRUE + +#ifdef OSX +//#else +//const bool FALSE = false; +//const bool TRUE = true; +#endif +#endif + +#ifndef NULL + #ifdef __cplusplus + #define NULL 0 + #else + #define NULL ((void *)0) + #endif +#endif + +#ifdef __cplusplus +inline int ioctlsocket( int d, int cmd, uint32 *argp ) { return ioctl( d, cmd, argp ); } +inline int closesocket( int fd ) { return close( fd ); } +inline char * GetCurrentDirectory( size_t size, char * buf ) { return getcwd( buf, size ); } +inline int WSAGetLastError() { return errno; } + +inline void DebugBreak( void ) { exit( 1 ); } +#endif + +extern char g_szEXEName[ 4096 ]; + +#define _snprintf snprintf + +#if defined(OSX) +#define SO_ARCH_SUFFIX ".dylib" +#else +#if defined ( __x86_64__ ) +#define SO_ARCH_SUFFIX "_amd64.so" +#else +#define SO_ARCH_SUFFIX ".so" +#endif +#endif +#endif + +#endif // PORT_H diff --git a/common/qfont.h b/common/qfont.h new file mode 100644 index 0000000..d6bbb87 --- /dev/null +++ b/common/qfont.h @@ -0,0 +1,41 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( QFONTH ) +#define QFONTH +#ifdef _WIN32 +#pragma once +#endif + +// Font stuff + +#define NUM_GLYPHS 256 +#include "basetypes.h" + +typedef struct +{ + short startoffset; + short charwidth; +} charinfo; + +typedef struct qfont_s +{ + int width, height; + int rowcount; + int rowheight; + charinfo fontinfo[ NUM_GLYPHS ]; + byte data[4]; +} qfont_t; + +#endif // qfont.h diff --git a/common/r_efx.h b/common/r_efx.h new file mode 100644 index 0000000..e57a70c --- /dev/null +++ b/common/r_efx.h @@ -0,0 +1,197 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined ( R_EFXH ) +#define R_EFXH +#ifdef _WIN32 +#pragma once +#endif + +// particle_t +#if !defined( PARTICLEDEFH ) +#include "particledef.h" +#endif + +// BEAM +#if !defined( BEAMDEFH ) +#include "beamdef.h" +#endif + +// dlight_t +#if !defined ( DLIGHTH ) +#include "dlight.h" +#endif + +// cl_entity_t +#if !defined( CL_ENTITYH ) +#include "cl_entity.h" +#endif + +/* +// FOR REFERENCE, These are the built-in tracer colors. Note, color 4 is the one +// that uses the tracerred/tracergreen/tracerblue and traceralpha cvar settings +color24 gTracerColors[] = +{ + { 255, 255, 255 }, // White + { 255, 0, 0 }, // Red + { 0, 255, 0 }, // Green + { 0, 0, 255 }, // Blue + { 0, 0, 0 }, // Tracer default, filled in from cvars, etc. + { 255, 167, 17 }, // Yellow-orange sparks + { 255, 130, 90 }, // Yellowish streaks (garg) + { 55, 60, 144 }, // Blue egon streak + { 255, 130, 90 }, // More Yellowish streaks (garg) + { 255, 140, 90 }, // More Yellowish streaks (garg) + { 200, 130, 90 }, // More red streaks (garg) + { 255, 120, 70 }, // Darker red streaks (garg) +}; +*/ + +// Temporary entity array +#define TENTPRIORITY_LOW 0 +#define TENTPRIORITY_HIGH 1 + +// TEMPENTITY flags +#define FTENT_NONE 0x00000000 +#define FTENT_SINEWAVE 0x00000001 +#define FTENT_GRAVITY 0x00000002 +#define FTENT_ROTATE 0x00000004 +#define FTENT_SLOWGRAVITY 0x00000008 +#define FTENT_SMOKETRAIL 0x00000010 +#define FTENT_COLLIDEWORLD 0x00000020 +#define FTENT_FLICKER 0x00000040 +#define FTENT_FADEOUT 0x00000080 +#define FTENT_SPRANIMATE 0x00000100 +#define FTENT_HITSOUND 0x00000200 +#define FTENT_SPIRAL 0x00000400 +#define FTENT_SPRCYCLE 0x00000800 +#define FTENT_COLLIDEALL 0x00001000 // will collide with world and slideboxes +#define FTENT_PERSIST 0x00002000 // tent is not removed when unable to draw +#define FTENT_COLLIDEKILL 0x00004000 // tent is removed upon collision with anything +#define FTENT_PLYRATTACHMENT 0x00008000 // tent is attached to a player (owner) +#define FTENT_SPRANIMATELOOP 0x00010000 // animating sprite doesn't die when last frame is displayed +#define FTENT_SPARKSHOWER 0x00020000 +#define FTENT_NOMODEL 0x00040000 // Doesn't have a model, never try to draw ( it just triggers other things ) +#define FTENT_CLIENTCUSTOM 0x00080000 // Must specify callback. Callback function is responsible for killing tempent and updating fields ( unless other flags specify how to do things ) + +typedef struct tempent_s +{ + int flags; + float die; + float frameMax; + float x; + float y; + float z; + float fadeSpeed; + float bounceFactor; + int hitSound; + void ( *hitcallback ) ( struct tempent_s *ent, struct pmtrace_s *ptr ); + void ( *callback ) ( struct tempent_s *ent, float frametime, float currenttime ); + struct tempent_s *next; + int priority; + short clientIndex; // if attached, this is the index of the client to stick to + // if COLLIDEALL, this is the index of the client to ignore + // TENTS with FTENT_PLYRATTACHMENT MUST set the clientindex! + + vec3_t tentOffset; // if attached, client origin + tentOffset = tent origin. + cl_entity_t entity; + + // baseline.origin - velocity + // baseline.renderamt - starting fadeout intensity + // baseline.angles - angle velocity +} TEMPENTITY; + +typedef struct efx_api_s efx_api_t; + +struct efx_api_s +{ + particle_t *( *R_AllocParticle ) ( void ( *callback ) ( struct particle_s *particle, float frametime ) ); + void ( *R_BlobExplosion ) ( float * org ); + void ( *R_Blood ) ( float * org, float * dir, int pcolor, int speed ); + void ( *R_BloodSprite ) ( float * org, int colorindex, int modelIndex, int modelIndex2, float size ); + void ( *R_BloodStream ) ( float * org, float * dir, int pcolor, int speed ); + void ( *R_BreakModel ) ( float *pos, float *size, float *dir, float random, float life, int count, int modelIndex, char flags ); + void ( *R_Bubbles ) ( float * mins, float * maxs, float height, int modelIndex, int count, float speed ); + void ( *R_BubbleTrail ) ( float * start, float * end, float height, int modelIndex, int count, float speed ); + void ( *R_BulletImpactParticles ) ( float * pos ); + void ( *R_EntityParticles ) ( struct cl_entity_s *ent ); + void ( *R_Explosion ) ( float *pos, int model, float scale, float framerate, int flags ); + void ( *R_FizzEffect ) ( struct cl_entity_s *pent, int modelIndex, int density ); + void ( *R_FireField ) ( float * org, int radius, int modelIndex, int count, int flags, float life ); + void ( *R_FlickerParticles ) ( float * org ); + void ( *R_FunnelSprite ) ( float *org, int modelIndex, int reverse ); + void ( *R_Implosion ) ( float * end, float radius, int count, float life ); + void ( *R_LargeFunnel ) ( float * org, int reverse ); + void ( *R_LavaSplash ) ( float * org ); + void ( *R_MultiGunshot ) ( float * org, float * dir, float * noise, int count, int decalCount, int *decalIndices ); + void ( *R_MuzzleFlash ) ( float *pos1, int type ); + void ( *R_ParticleBox ) ( float *mins, float *maxs, unsigned char r, unsigned char g, unsigned char b, float life ); + void ( *R_ParticleBurst ) ( float * pos, int size, int color, float life ); + void ( *R_ParticleExplosion ) ( float * org ); + void ( *R_ParticleExplosion2 ) ( float * org, int colorStart, int colorLength ); + void ( *R_ParticleLine ) ( float * start, float *end, unsigned char r, unsigned char g, unsigned char b, float life ); + void ( *R_PlayerSprites ) ( int client, int modelIndex, int count, int size ); + void ( *R_Projectile ) ( float * origin, float * velocity, int modelIndex, int life, int owner, void (*hitcallback)( struct tempent_s *ent, struct pmtrace_s *ptr ) ); + void ( *R_RicochetSound ) ( float * pos ); + void ( *R_RicochetSprite ) ( float *pos, struct model_s *pmodel, float duration, float scale ); + void ( *R_RocketFlare ) ( float *pos ); + void ( *R_RocketTrail ) ( float * start, float * end, int type ); + void ( *R_RunParticleEffect ) ( float * org, float * dir, int color, int count ); + void ( *R_ShowLine ) ( float * start, float * end ); + void ( *R_SparkEffect ) ( float *pos, int count, int velocityMin, int velocityMax ); + void ( *R_SparkShower ) ( float *pos ); + void ( *R_SparkStreaks ) ( float * pos, int count, int velocityMin, int velocityMax ); + void ( *R_Spray ) ( float * pos, float * dir, int modelIndex, int count, int speed, int spread, int rendermode ); + void ( *R_Sprite_Explode ) ( TEMPENTITY *pTemp, float scale, int flags ); + void ( *R_Sprite_Smoke ) ( TEMPENTITY *pTemp, float scale ); + void ( *R_Sprite_Spray ) ( float * pos, float * dir, int modelIndex, int count, int speed, int iRand ); + void ( *R_Sprite_Trail ) ( int type, float * start, float * end, int modelIndex, int count, float life, float size, float amplitude, int renderamt, float speed ); + void ( *R_Sprite_WallPuff ) ( TEMPENTITY *pTemp, float scale ); + void ( *R_StreakSplash ) ( float * pos, float * dir, int color, int count, float speed, int velocityMin, int velocityMax ); + void ( *R_TracerEffect ) ( float * start, float * end ); + void ( *R_UserTracerParticle ) ( float * org, float * vel, float life, int colorIndex, float length, unsigned char deathcontext, void ( *deathfunc)( struct particle_s *particle ) ); + particle_t *( *R_TracerParticles ) ( float * org, float * vel, float life ); + void ( *R_TeleportSplash ) ( float * org ); + void ( *R_TempSphereModel ) ( float *pos, float speed, float life, int count, int modelIndex ); + TEMPENTITY *( *R_TempModel ) ( float *pos, float *dir, float *angles, float life, int modelIndex, int soundtype ); + TEMPENTITY *( *R_DefaultSprite ) ( float *pos, int spriteIndex, float framerate ); + TEMPENTITY *( *R_TempSprite ) ( float *pos, float *dir, float scale, int modelIndex, int rendermode, int renderfx, float a, float life, int flags ); + int ( *Draw_DecalIndex ) ( int id ); + int ( *Draw_DecalIndexFromName ) ( char *name ); + void ( *R_DecalShoot ) ( int textureIndex, int entity, int modelIndex, float * position, int flags ); + void ( *R_AttachTentToPlayer ) ( int client, int modelIndex, float zoffset, float life ); + void ( *R_KillAttachedTents ) ( int client ); + BEAM *( *R_BeamCirclePoints ) ( int type, float * start, float * end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); + BEAM *( *R_BeamEntPoint ) ( int startEnt, float * end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); + BEAM *( *R_BeamEnts ) ( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); + BEAM *( *R_BeamFollow ) ( int startEnt, int modelIndex, float life, float width, float r, float g, float b, float brightness ); + void ( *R_BeamKill ) ( int deadEntity ); + BEAM *( *R_BeamLightning ) ( float * start, float * end, int modelIndex, float life, float width, float amplitude, float brightness, float speed ); + BEAM *( *R_BeamPoints ) ( float * start, float * end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); + BEAM *( *R_BeamRing ) ( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); + dlight_t *( *CL_AllocDlight ) ( int key ); + dlight_t *( *CL_AllocElight ) ( int key ); + TEMPENTITY *( *CL_TempEntAlloc ) ( float * org, struct model_s *model ); + TEMPENTITY *( *CL_TempEntAllocNoModel ) ( float * org ); + TEMPENTITY *( *CL_TempEntAllocHigh ) ( float * org, struct model_s *model ); + TEMPENTITY *( *CL_TentEntAllocCustom ) ( float *origin, struct model_s *model, int high, void ( *callback ) ( struct tempent_s *ent, float frametime, float currenttime ) ); + void ( *R_GetPackedColor ) ( short *packed, short color ); + short ( *R_LookupColor ) ( unsigned char r, unsigned char g, unsigned char b ); + void ( *R_DecalRemoveAll ) ( int textureIndex ); //textureIndex points to the decal index in the array, not the actual texture index. + void ( *R_FireCustomDecal ) ( int textureIndex, int entity, int modelIndex, float * position, int flags, float scale ); +}; + +extern efx_api_t efx; + +#endif diff --git a/common/r_studioint.h b/common/r_studioint.h new file mode 100644 index 0000000..3570993 --- /dev/null +++ b/common/r_studioint.h @@ -0,0 +1,144 @@ +#if !defined( R_STUDIOINT_H ) +#define R_STUDIOINT_H +#if defined( _WIN32 ) +#pragma once +#endif + +#define STUDIO_INTERFACE_VERSION 1 + +typedef struct engine_studio_api_s +{ + // Allocate number*size bytes and zero it + void *( *Mem_Calloc ) ( int number, size_t size ); + // Check to see if pointer is in the cache + void *( *Cache_Check ) ( struct cache_user_s *c ); + // Load file into cache ( can be swapped out on demand ) + void ( *LoadCacheFile ) ( char *path, struct cache_user_s *cu ); + // Retrieve model pointer for the named model + struct model_s *( *Mod_ForName ) ( const char *name, int crash_if_missing ); + // Retrieve pointer to studio model data block from a model + void *( *Mod_Extradata ) ( struct model_s *mod ); + // Retrieve indexed model from client side model precache list + struct model_s *( *GetModelByIndex ) ( int index ); + // Get entity that is set for rendering + struct cl_entity_s * ( *GetCurrentEntity ) ( void ); + // Get referenced player_info_t + struct player_info_s *( *PlayerInfo ) ( int index ); + // Get most recently received player state data from network system + struct entity_state_s *( *GetPlayerState ) ( int index ); + // Get viewentity + struct cl_entity_s * ( *GetViewEntity ) ( void ); + // Get current frame count, and last two timestampes on client + void ( *GetTimes ) ( int *framecount, double *current, double *old ); + // Get a pointer to a cvar by name + struct cvar_s *( *GetCvar ) ( const char *name ); + // Get current render origin and view vectors ( up, right and vpn ) + void ( *GetViewInfo ) ( float *origin, float *upv, float *rightv, float *vpnv ); + // Get sprite model used for applying chrome effect + struct model_s *( *GetChromeSprite ) ( void ); + // Get model counters so we can incement instrumentation + void ( *GetModelCounters ) ( int **s, int **a ); + // Get software scaling coefficients + void ( *GetAliasScale ) ( float *x, float *y ); + + // Get bone, light, alias, and rotation matrices + float ****( *StudioGetBoneTransform ) ( void ); + float ****( *StudioGetLightTransform )( void ); + float ***( *StudioGetAliasTransform ) ( void ); + float ***( *StudioGetRotationMatrix ) ( void ); + + // Set up body part, and get submodel pointers + void ( *StudioSetupModel ) ( int bodypart, void **ppbodypart, void **ppsubmodel ); + // Check if entity's bbox is in the view frustum + int ( *StudioCheckBBox ) ( void ); + // Apply lighting effects to model + void ( *StudioDynamicLight ) ( struct cl_entity_s *ent, struct alight_s *plight ); + void ( *StudioEntityLight ) ( struct alight_s *plight ); + void ( *StudioSetupLighting ) ( struct alight_s *plighting ); + + // Draw mesh vertices + void ( *StudioDrawPoints ) ( void ); + + // Draw hulls around bones + void ( *StudioDrawHulls ) ( void ); + // Draw bbox around studio models + void ( *StudioDrawAbsBBox ) ( void ); + // Draws bones + void ( *StudioDrawBones ) ( void ); + // Loads in appropriate texture for model + void ( *StudioSetupSkin ) ( void *ptexturehdr, int index ); + // Sets up for remapped colors + void ( *StudioSetRemapColors ) ( int top, int bottom ); + // Set's player model and returns model pointer + struct model_s *( *SetupPlayerModel ) ( int index ); + // Fires any events embedded in animation + void ( *StudioClientEvents ) ( void ); + // Retrieve/set forced render effects flags + int ( *GetForceFaceFlags ) ( void ); + void ( *SetForceFaceFlags ) ( int flags ); + // Tell engine the value of the studio model header + void ( *StudioSetHeader ) ( void *header ); + // Tell engine which model_t * is being renderered + void ( *SetRenderModel ) ( struct model_s *model ); + + // Final state setup and restore for rendering + void ( *SetupRenderer ) ( int rendermode ); + void ( *RestoreRenderer ) ( void ); + + // Set render origin for applying chrome effect + void ( *SetChromeOrigin ) ( void ); + + // True if using D3D/OpenGL + int ( *IsHardware ) ( void ); + + // Only called by hardware interface + void ( *GL_StudioDrawShadow ) ( void ); + void ( *GL_SetRenderMode ) ( int mode ); + + void ( *StudioSetRenderamt ) (int iRenderamt); //!!!CZERO added for rendering glass on viewmodels + void ( *StudioSetCullState ) ( int iCull ); + void ( *StudioRenderShadow ) ( int iSprite, float *p1, float *p2, float *p3, float *p4 ); +} engine_studio_api_t; + +typedef struct server_studio_api_s +{ + // Allocate number*size bytes and zero it + void *( *Mem_Calloc ) ( int number, size_t size ); + // Check to see if pointer is in the cache + void *( *Cache_Check ) ( struct cache_user_s *c ); + // Load file into cache ( can be swapped out on demand ) + void ( *LoadCacheFile ) ( char *path, struct cache_user_s *cu ); + // Retrieve pointer to studio model data block from a model + void *( *Mod_Extradata ) ( struct model_s *mod ); +} server_studio_api_t; + + +// client blending +typedef struct r_studio_interface_s +{ + int version; + int ( *StudioDrawModel ) ( int flags ); + int ( *StudioDrawPlayer ) ( int flags, struct entity_state_s *pplayer ); +} r_studio_interface_t; + +extern r_studio_interface_t *pStudioAPI; + +// server blending +#define SV_BLENDING_INTERFACE_VERSION 1 + +typedef struct sv_blending_interface_s +{ + int version; + + void ( *SV_StudioSetupBones ) ( struct model_s *pModel, + float frame, + int sequence, + const vec3_t angles, + const vec3_t origin, + const byte *pcontroller, + const byte *pblending, + int iBone, + const edict_t *pEdict ); +} sv_blending_interface_t; + +#endif // R_STUDIOINT_H diff --git a/common/ref_params.h b/common/ref_params.h new file mode 100644 index 0000000..90eb03f --- /dev/null +++ b/common/ref_params.h @@ -0,0 +1,75 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( REF_PARAMSH ) +#define REF_PARAMSH + +typedef struct ref_params_s +{ + // Output + float vieworg[3]; + float viewangles[3]; + + float forward[3]; + float right[3]; + float up[3]; + + // Client frametime; + float frametime; + // Client time + float time; + + // Misc + int intermission; + int paused; + int spectator; + int onground; + int waterlevel; + + float simvel[3]; + float simorg[3]; + + float viewheight[3]; + float idealpitch; + + float cl_viewangles[3]; + + int health; + float crosshairangle[3]; + float viewsize; + + float punchangle[3]; + int maxclients; + int viewentity; + int playernum; + int max_entities; + int demoplayback; + int hardware; + + int smoothing; + + // Last issued usercmd + struct usercmd_s *cmd; + + // Movevars + struct movevars_s *movevars; + + int viewport[4]; // the viewport coordinates x ,y , width, height + + int nextView; // the renderer calls ClientDLL_CalcRefdef() and Renderview + // so long in cycles until this value is 0 (multiple views) + int onlyClientDraw; // if !=0 nothing is drawn by the engine except clientDraw functions +} ref_params_t; + +#endif // !REF_PARAMSH diff --git a/common/screenfade.h b/common/screenfade.h new file mode 100644 index 0000000..62c0d25 --- /dev/null +++ b/common/screenfade.h @@ -0,0 +1,24 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( SCREENFADEH ) +#define SCREENFADEH +#ifdef _WIN32 +#pragma once +#endif + +typedef struct screenfade_s +{ + float fadeSpeed; // How fast to fade (tics / second) (+ fade in, - fade out) + float fadeEnd; // When the fading hits maximum + float fadeTotalEnd; // Total End Time of the fade (used for FFADE_OUT) + float fadeReset; // When to reset to not fading (for fadeout and hold) + byte fader, fadeg, fadeb, fadealpha; // Fade color + int fadeFlags; // Fading flags +} screenfade_t; + +#endif // !SCREENFADEH diff --git a/common/studio_event.h b/common/studio_event.h new file mode 100644 index 0000000..c79c210 --- /dev/null +++ b/common/studio_event.h @@ -0,0 +1,29 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( STUDIO_EVENTH ) +#define STUDIO_EVENTH +#ifdef _WIN32 +#pragma once +#endif + +typedef struct mstudioevent_s +{ + int frame; + int event; + int type; + char options[64]; +} mstudioevent_t; + +#endif // STUDIO_EVENTH diff --git a/common/triangleapi.h b/common/triangleapi.h new file mode 100644 index 0000000..069a4d6 --- /dev/null +++ b/common/triangleapi.h @@ -0,0 +1,64 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( TRIANGLEAPIH ) +#define TRIANGLEAPIH +#ifdef _WIN32 +#pragma once +#endif + +typedef enum +{ + TRI_FRONT = 0, + TRI_NONE = 1, +} TRICULLSTYLE; + +#define TRI_API_VERSION 1 + +#define TRI_TRIANGLES 0 +#define TRI_TRIANGLE_FAN 1 +#define TRI_QUADS 2 +#define TRI_POLYGON 3 +#define TRI_LINES 4 +#define TRI_TRIANGLE_STRIP 5 +#define TRI_QUAD_STRIP 6 + +typedef struct triangleapi_s +{ + int version; + + void ( *RenderMode )( int mode ); + void ( *Begin )( int primitiveCode ); + void ( *End ) ( void ); + + void ( *Color4f ) ( float r, float g, float b, float a ); + void ( *Color4ub ) ( unsigned char r, unsigned char g, unsigned char b, unsigned char a ); + void ( *TexCoord2f ) ( float u, float v ); + void ( *Vertex3fv ) ( float *worldPnt ); + void ( *Vertex3f ) ( float x, float y, float z ); + void ( *Brightness ) ( float brightness ); + void ( *CullFace ) ( TRICULLSTYLE style ); + int ( *SpriteTexture ) ( struct model_s *pSpriteModel, int frame ); + int ( *WorldToScreen ) ( float *world, float *screen ); // Returns 1 if it's z clipped + void ( *Fog ) ( float flFogColor[3], float flStart, float flEnd, int bOn ); // Works just like GL_FOG, flFogColor is r/g/b. + void ( *ScreenToWorld ) ( float *screen, float *world ); + void ( *GetMatrix ) ( const int pname, float *matrix ); + int ( *BoxInPVS ) ( float *mins, float *maxs ); + void ( *LightAtPoint ) ( float *pos, float *value ); + void ( *Color4fRendermode ) ( float r, float g, float b, float a, int rendermode ); + void ( *FogParams ) ( float flDensity, int iFogSkybox ); // Used with Fog()...sets fog density and whether the fog should be applied to the skybox + +} triangleapi_t; + +#endif // !TRIANGLEAPIH diff --git a/common/usercmd.h b/common/usercmd.h new file mode 100644 index 0000000..7cdcfe2 --- /dev/null +++ b/common/usercmd.h @@ -0,0 +1,41 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef USERCMD_H +#define USERCMD_H +#ifdef _WIN32 +#pragma once +#endif + +typedef struct usercmd_s +{ + short lerp_msec; // Interpolation time on client + byte msec; // Duration in ms of command + vec3_t viewangles; // Command view angles. + +// intended velocities + float forwardmove; // Forward velocity. + float sidemove; // Sideways velocity. + float upmove; // Upward velocity. + byte lightlevel; // Light level at spot where we are standing. + unsigned short buttons; // Attack buttons + byte impulse; // Impulse command issued. + byte weaponselect; // Current weapon id + +// Experimental player impact stuff. + int impact_index; + vec3_t impact_position; +} usercmd_t; + +#endif // USERCMD_H diff --git a/common/weaponinfo.h b/common/weaponinfo.h new file mode 100644 index 0000000..b648652 --- /dev/null +++ b/common/weaponinfo.h @@ -0,0 +1,52 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined ( WEAPONINFOH ) +#define WEAPONINFOH +#ifdef _WIN32 +#pragma once +#endif + +// Info about weapons player might have in his/her possession +typedef struct weapon_data_s +{ + int m_iId; + int m_iClip; + + float m_flNextPrimaryAttack; + float m_flNextSecondaryAttack; + float m_flTimeWeaponIdle; + + int m_fInReload; + int m_fInSpecialReload; + float m_flNextReload; + float m_flPumpTime; + float m_fReloadTime; + + float m_fAimedDamage; + float m_fNextAimBonus; + int m_fInZoom; + int m_iWeaponState; + + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; +} weapon_data_t; + +#endif diff --git a/dlls/AI_BaseNPC_Schedule.cpp b/dlls/AI_BaseNPC_Schedule.cpp new file mode 100644 index 0000000..1f83d07 --- /dev/null +++ b/dlls/AI_BaseNPC_Schedule.cpp @@ -0,0 +1,1514 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// schedule.cpp - functions and data pertaining to the +// monsters' AI scheduling system. +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "animation.h" +#include "scripted.h" +#include "nodes.h" +#include "defaultai.h" +#include "soundent.h" + +extern CGraph WorldGraph; + +//========================================================= +// FHaveSchedule - Returns TRUE if monster's m_pSchedule +// is anything other than NULL. +//========================================================= +BOOL CBaseMonster :: FHaveSchedule( void ) +{ + if ( m_pSchedule == NULL ) + { + return FALSE; + } + + return TRUE; +} + +//========================================================= +// ClearSchedule - blanks out the caller's schedule pointer +// and index. +//========================================================= +void CBaseMonster :: ClearSchedule( void ) +{ + m_iTaskStatus = TASKSTATUS_NEW; + m_pSchedule = NULL; + m_iScheduleIndex = 0; +} + +//========================================================= +// FScheduleDone - Returns TRUE if the caller is on the +// last task in the schedule +//========================================================= +BOOL CBaseMonster :: FScheduleDone ( void ) +{ + ASSERT( m_pSchedule != NULL ); + + if ( m_iScheduleIndex == m_pSchedule->cTasks ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// ChangeSchedule - replaces the monster's schedule pointer +// with the passed pointer, and sets the ScheduleIndex back +// to 0 +//========================================================= +void CBaseMonster :: ChangeSchedule ( Schedule_t *pNewSchedule ) +{ + ASSERT( pNewSchedule != NULL ); + + m_pSchedule = pNewSchedule; + m_iScheduleIndex = 0; + m_iTaskStatus = TASKSTATUS_NEW; + m_afConditions = 0;// clear all of the conditions + m_failSchedule = SCHED_NONE; + + if ( m_pSchedule->iInterruptMask & bits_COND_HEAR_SOUND && !(m_pSchedule->iSoundMask) ) + { + ALERT ( at_aiconsole, "COND_HEAR_SOUND with no sound mask!\n" ); + } + else if ( m_pSchedule->iSoundMask && !(m_pSchedule->iInterruptMask & bits_COND_HEAR_SOUND) ) + { + ALERT ( at_aiconsole, "Sound mask without COND_HEAR_SOUND!\n" ); + } + +#if _DEBUG + if ( !ScheduleFromName( pNewSchedule->pName ) ) + { + ALERT( at_console, "Schedule %s not in table!!!\n", pNewSchedule->pName ); + } +#endif + +// this is very useful code if you can isolate a test case in a level with a single monster. It will notify +// you of every schedule selection the monster makes. +#if 0 + if ( FClassnameIs( pev, "monster_human_grunt" ) ) + { + Task_t *pTask = GetTask(); + + if ( pTask ) + { + const char *pName = NULL; + + if ( m_pSchedule ) + { + pName = m_pSchedule->pName; + } + else + { + pName = "No Schedule"; + } + + if ( !pName ) + { + pName = "Unknown"; + } + + ALERT( at_aiconsole, "%s: picked schedule %s\n", STRING( pev->classname ), pName ); + } + } +#endif// 0 + +} + +//========================================================= +// NextScheduledTask - increments the ScheduleIndex +//========================================================= +void CBaseMonster :: NextScheduledTask ( void ) +{ + ASSERT( m_pSchedule != NULL ); + + m_iTaskStatus = TASKSTATUS_NEW; + m_iScheduleIndex++; + + if ( FScheduleDone() ) + { + // just completed last task in schedule, so make it invalid by clearing it. + SetConditions( bits_COND_SCHEDULE_DONE ); + //ClearSchedule(); + } +} + +//========================================================= +// IScheduleFlags - returns an integer with all Conditions +// bits that are currently set and also set in the current +// schedule's Interrupt mask. +//========================================================= +int CBaseMonster :: IScheduleFlags ( void ) +{ + if( !m_pSchedule ) + { + return 0; + } + + // strip off all bits excepts the ones capable of breaking this schedule. + return m_afConditions & m_pSchedule->iInterruptMask; +} + +//========================================================= +// FScheduleValid - returns TRUE as long as the current +// schedule is still the proper schedule to be executing, +// taking into account all conditions +//========================================================= +BOOL CBaseMonster :: FScheduleValid ( void ) +{ + if ( m_pSchedule == NULL ) + { + // schedule is empty, and therefore not valid. + return FALSE; + } + + if ( HasConditions( m_pSchedule->iInterruptMask | bits_COND_SCHEDULE_DONE | bits_COND_TASK_FAILED ) ) + { +#ifdef DEBUG + if ( HasConditions ( bits_COND_TASK_FAILED ) && m_failSchedule == SCHED_NONE ) + { + // fail! Send a visual indicator. + ALERT ( at_aiconsole, "Schedule: %s Failed\n", m_pSchedule->pName ); + + Vector tmp = pev->origin; + tmp.z = pev->absmax.z + 16; + UTIL_Sparks( tmp ); + } +#endif // DEBUG + + // some condition has interrupted the schedule, or the schedule is done + return FALSE; + } + + return TRUE; +} + +//========================================================= +// MaintainSchedule - does all the per-think schedule maintenance. +// ensures that the monster leaves this function with a valid +// schedule! +//========================================================= +void CBaseMonster :: MaintainSchedule ( void ) +{ + Schedule_t *pNewSchedule; + int i; + + // UNDONE: Tune/fix this 10... This is just here so infinite loops are impossible + for ( i = 0; i < 10; i++ ) + { + if ( m_pSchedule != NULL && TaskIsComplete() ) + { + NextScheduledTask(); + } + + // validate existing schedule + if ( !FScheduleValid() || m_MonsterState != m_IdealMonsterState ) + { + // if we come into this block of code, the schedule is going to have to be changed. + // if the previous schedule was interrupted by a condition, GetIdealState will be + // called. Else, a schedule finished normally. + + // Notify the monster that his schedule is changing + ScheduleChange(); + + // Call GetIdealState if we're not dead and one or more of the following... + // - in COMBAT state with no enemy (it died?) + // - conditions bits (excluding SCHEDULE_DONE) indicate interruption, + // - schedule is done but schedule indicates it wants GetIdealState called + // after successful completion (by setting bits_COND_SCHEDULE_DONE in iInterruptMask) + // DEAD & SCRIPT are not suggestions, they are commands! + if ( m_IdealMonsterState != MONSTERSTATE_DEAD && + (m_IdealMonsterState != MONSTERSTATE_SCRIPT || m_IdealMonsterState == m_MonsterState) ) + { + if ( (m_afConditions && !HasConditions(bits_COND_SCHEDULE_DONE)) || + (m_pSchedule && (m_pSchedule->iInterruptMask & bits_COND_SCHEDULE_DONE)) || + ((m_MonsterState == MONSTERSTATE_COMBAT) && (m_hEnemy == NULL)) ) + { + GetIdealState(); + } + } + if ( HasConditions( bits_COND_TASK_FAILED ) && m_MonsterState == m_IdealMonsterState ) + { + if ( m_failSchedule != SCHED_NONE ) + pNewSchedule = GetScheduleOfType( m_failSchedule ); + else + pNewSchedule = GetScheduleOfType( SCHED_FAIL ); + // schedule was invalid because the current task failed to start or complete + ALERT ( at_aiconsole, "Schedule Failed at %d!\n", m_iScheduleIndex ); + ChangeSchedule( pNewSchedule ); + } + else + { + SetState( m_IdealMonsterState ); + if ( m_MonsterState == MONSTERSTATE_SCRIPT || m_MonsterState == MONSTERSTATE_DEAD ) + pNewSchedule = CBaseMonster::GetSchedule(); + else + pNewSchedule = GetSchedule(); + ChangeSchedule( pNewSchedule ); + } + } + + if ( m_iTaskStatus == TASKSTATUS_NEW ) + { + Task_t *pTask = GetTask(); + ASSERT( pTask != NULL ); + TaskBegin(); + StartTask( pTask ); + } + + // UNDONE: Twice?!!! + if ( m_Activity != m_IdealActivity ) + { + SetActivity ( m_IdealActivity ); + } + + if ( !TaskIsComplete() && m_iTaskStatus != TASKSTATUS_NEW ) + break; + } + + if ( TaskIsRunning() ) + { + Task_t *pTask = GetTask(); + ASSERT( pTask != NULL ); + RunTask( pTask ); + } + + // UNDONE: We have to do this so that we have an animation set to blend to if RunTask changes the animation + // RunTask() will always change animations at the end of a script! + // Don't do this twice + if ( m_Activity != m_IdealActivity ) + { + SetActivity ( m_IdealActivity ); + } +} + +//========================================================= +// RunTask +//========================================================= +void CBaseMonster :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_TURN_RIGHT: + case TASK_TURN_LEFT: + { + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + TaskComplete(); + } + break; + } + + case TASK_PLAY_SEQUENCE_FACE_ENEMY: + case TASK_PLAY_SEQUENCE_FACE_TARGET: + { + CBaseEntity *pTarget; + + if ( pTask->iTask == TASK_PLAY_SEQUENCE_FACE_TARGET ) + pTarget = m_hTargetEnt; + else + pTarget = m_hEnemy; + if ( pTarget ) + { + pev->ideal_yaw = UTIL_VecToYaw( pTarget->pev->origin - pev->origin ); + ChangeYaw( pev->yaw_speed ); + } + if ( m_fSequenceFinished ) + TaskComplete(); + } + break; + + case TASK_PLAY_SEQUENCE: + case TASK_PLAY_ACTIVE_IDLE: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + break; + } + + + case TASK_FACE_ENEMY: + { + MakeIdealYaw( m_vecEnemyLKP ); + + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + TaskComplete(); + } + break; + } + case TASK_FACE_HINTNODE: + case TASK_FACE_LASTPOSITION: + case TASK_FACE_TARGET: + case TASK_FACE_IDEAL: + case TASK_FACE_ROUTE: + { + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + TaskComplete(); + } + break; + } + case TASK_WAIT_PVS: + { + if ( !FNullEnt(FIND_CLIENT_IN_PVS(edict())) ) + { + TaskComplete(); + } + break; + } + case TASK_WAIT_INDEFINITE: + { + // don't do anything. + break; + } + case TASK_WAIT: + case TASK_WAIT_RANDOM: + { + if ( gpGlobals->time >= m_flWaitFinished ) + { + TaskComplete(); + } + break; + } + case TASK_WAIT_FACE_ENEMY: + { + MakeIdealYaw ( m_vecEnemyLKP ); + ChangeYaw( pev->yaw_speed ); + + if ( gpGlobals->time >= m_flWaitFinished ) + { + TaskComplete(); + } + break; + } + case TASK_MOVE_TO_TARGET_RANGE: + { + float distance; + + if ( m_hTargetEnt == NULL ) + TaskFail(); + else + { + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + // Re-evaluate when you think your finished, or the target has moved too far + if ( (distance < pTask->flData) || (m_vecMoveGoal - m_hTargetEnt->pev->origin).Length() > pTask->flData * 0.5 ) + { + m_vecMoveGoal = m_hTargetEnt->pev->origin; + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + FRefreshRoute(); + } + + // Set the appropriate activity based on an overlapping range + // overlap the range to prevent oscillation + if ( distance < pTask->flData ) + { + TaskComplete(); + RouteClear(); // Stop moving + } + else if ( distance < 190 && m_movementActivity != ACT_WALK ) + m_movementActivity = ACT_WALK; + else if ( distance >= 270 && m_movementActivity != ACT_RUN ) + m_movementActivity = ACT_RUN; + } + + break; + } + case TASK_WAIT_FOR_MOVEMENT: + { + if (MovementIsComplete()) + { + TaskComplete(); + RouteClear(); // Stop moving + } + break; + } + case TASK_DIE: + { + if ( m_fSequenceFinished && pev->frame >= 255 ) + { + pev->deadflag = DEAD_DEAD; + + SetThink ( NULL ); + StopAnimation(); + + if ( !BBoxFlat() ) + { + // a bit of a hack. If a corpses' bbox is positioned such that being left solid so that it can be attacked will + // block the player on a slope or stairs, the corpse is made nonsolid. +// pev->solid = SOLID_NOT; + UTIL_SetSize ( pev, Vector ( -4, -4, 0 ), Vector ( 4, 4, 1 ) ); + } + else // !!!HACKHACK - put monster in a thin, wide bounding box until we fix the solid type/bounding volume problem + UTIL_SetSize ( pev, Vector ( pev->mins.x, pev->mins.y, pev->mins.z ), Vector ( pev->maxs.x, pev->maxs.y, pev->mins.z + 1 ) ); + + if ( ShouldFadeOnDeath() ) + { + // this monster was created by a monstermaker... fade the corpse out. + SUB_StartFadeOut(); + } + else + { + // body is gonna be around for a while, so have it stink for a bit. + CSoundEnt::InsertSound ( bits_SOUND_CARCASS, pev->origin, 384, 30 ); + } + } + break; + } + case TASK_RANGE_ATTACK1_NOTURN: + case TASK_MELEE_ATTACK1_NOTURN: + case TASK_MELEE_ATTACK2_NOTURN: + case TASK_RANGE_ATTACK2_NOTURN: + case TASK_RELOAD_NOTURN: + { + if ( m_fSequenceFinished ) + { + m_Activity = ACT_RESET; + TaskComplete(); + } + break; + } + case TASK_RANGE_ATTACK1: + case TASK_MELEE_ATTACK1: + case TASK_MELEE_ATTACK2: + case TASK_RANGE_ATTACK2: + case TASK_SPECIAL_ATTACK1: + case TASK_SPECIAL_ATTACK2: + case TASK_RELOAD: + { + MakeIdealYaw ( m_vecEnemyLKP ); + ChangeYaw ( pev->yaw_speed ); + + if ( m_fSequenceFinished ) + { + m_Activity = ACT_RESET; + TaskComplete(); + } + break; + } + case TASK_SMALL_FLINCH: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + } + break; + case TASK_WAIT_FOR_SCRIPT: + { + if ( m_pCine->m_iDelay <= 0 && gpGlobals->time >= m_pCine->m_startTime ) + { + TaskComplete(); + m_pCine->StartSequence( (CBaseMonster *)this, m_pCine->m_iszPlay, TRUE ); + if ( m_fSequenceFinished ) + ClearSchedule(); + pev->framerate = 1.0; + //ALERT( at_aiconsole, "Script %s has begun for %s\n", STRING( m_pCine->m_iszPlay ), STRING(pev->classname) ); + } + break; + } + case TASK_PLAY_SCRIPT: + { + if (m_fSequenceFinished) + { + m_pCine->SequenceDone( this ); + } + break; + } + } +} + +//========================================================= +// SetTurnActivity - measures the difference between the way +// the monster is facing and determines whether or not to +// select one of the 180 turn animations. +//========================================================= +void CBaseMonster :: SetTurnActivity ( void ) +{ + float flYD; + flYD = FlYawDiff(); + + if ( flYD <= -45 && LookupActivity ( ACT_TURN_RIGHT ) != ACTIVITY_NOT_AVAILABLE ) + {// big right turn + m_IdealActivity = ACT_TURN_RIGHT; + } + else if ( flYD > 45 && LookupActivity ( ACT_TURN_LEFT ) != ACTIVITY_NOT_AVAILABLE ) + {// big left turn + m_IdealActivity = ACT_TURN_LEFT; + } +} + +//========================================================= +// Start task - selects the correct activity and performs +// any necessary calculations to start the next task on the +// schedule. +//========================================================= +void CBaseMonster :: StartTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_TURN_RIGHT: + { + float flCurrentYaw; + + flCurrentYaw = UTIL_AngleMod( pev->angles.y ); + pev->ideal_yaw = UTIL_AngleMod( flCurrentYaw - pTask->flData ); + SetTurnActivity(); + break; + } + case TASK_TURN_LEFT: + { + float flCurrentYaw; + + flCurrentYaw = UTIL_AngleMod( pev->angles.y ); + pev->ideal_yaw = UTIL_AngleMod( flCurrentYaw + pTask->flData ); + SetTurnActivity(); + break; + } + case TASK_REMEMBER: + { + Remember ( (int)pTask->flData ); + TaskComplete(); + break; + } + case TASK_FORGET: + { + Forget ( (int)pTask->flData ); + TaskComplete(); + break; + } + case TASK_FIND_HINTNODE: + { + m_iHintNode = FindHintNode(); + + if ( m_iHintNode != NO_NODE ) + { + TaskComplete(); + } + else + { + TaskFail(); + } + break; + } + case TASK_STORE_LASTPOSITION: + { + m_vecLastPosition = pev->origin; + TaskComplete(); + break; + } + case TASK_CLEAR_LASTPOSITION: + { + m_vecLastPosition = g_vecZero; + TaskComplete(); + break; + } + case TASK_CLEAR_HINTNODE: + { + m_iHintNode = NO_NODE; + TaskComplete(); + break; + } + case TASK_STOP_MOVING: + { + if ( m_IdealActivity == m_movementActivity ) + { + m_IdealActivity = GetStoppedActivity(); + } + + RouteClear(); + TaskComplete(); + break; + } + case TASK_PLAY_SEQUENCE_FACE_ENEMY: + case TASK_PLAY_SEQUENCE_FACE_TARGET: + case TASK_PLAY_SEQUENCE: + { + m_IdealActivity = ( Activity )( int )pTask->flData; + break; + } + case TASK_PLAY_ACTIVE_IDLE: + { + // monsters verify that they have a sequence for the node's activity BEFORE + // moving towards the node, so it's ok to just set the activity without checking here. + m_IdealActivity = ( Activity )WorldGraph.m_pNodes[ m_iHintNode ].m_sHintActivity; + break; + } + case TASK_SET_SCHEDULE: + { + Schedule_t *pNewSchedule; + + pNewSchedule = GetScheduleOfType( (int)pTask->flData ); + + if ( pNewSchedule ) + { + ChangeSchedule( pNewSchedule ); + } + else + { + TaskFail(); + } + + break; + } + case TASK_FIND_NEAR_NODE_COVER_FROM_ENEMY: + { + if ( m_hEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, 0, pTask->flData ) ) + { + // try for cover farther than the FLData from the schedule. + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_FAR_NODE_COVER_FROM_ENEMY: + { + if ( m_hEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, pTask->flData, CoverRadius() ) ) + { + // try for cover farther than the FLData from the schedule. + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_NODE_COVER_FROM_ENEMY: + { + if ( m_hEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, 0, CoverRadius() ) ) + { + // try for cover farther than the FLData from the schedule. + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_COVER_FROM_ENEMY: + { + entvars_t *pevCover; + + if ( m_hEnemy == NULL ) + { + // Find cover from self if no enemy available + pevCover = pev; +// TaskFail(); +// return; + } + else + pevCover = m_hEnemy->pev; + + if ( FindLateralCover( pevCover->origin, pevCover->view_ofs ) ) + { + // try lateral first + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else if ( FindCover( pevCover->origin, pevCover->view_ofs, 0, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_COVER_FROM_ORIGIN: + { + if ( FindCover( pev->origin, pev->view_ofs, 0, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else + { + // no cover! + TaskFail(); + } + } + break; + case TASK_FIND_COVER_FROM_BEST_SOUND: + { + CSound *pBestSound; + + pBestSound = PBestSound(); + + ASSERT( pBestSound != NULL ); + /* + if ( pBestSound && FindLateralCover( pBestSound->m_vecOrigin, g_vecZero ) ) + { + // try lateral first + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + */ + + if ( pBestSound && FindCover( pBestSound->m_vecOrigin, g_vecZero, pBestSound->m_iVolume, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else + { + // no coverwhatsoever. or no sound in list + TaskFail(); + } + break; + } + case TASK_FACE_HINTNODE: + { + pev->ideal_yaw = WorldGraph.m_pNodes[ m_iHintNode ].m_flHintYaw; + SetTurnActivity(); + break; + } + + case TASK_FACE_LASTPOSITION: + MakeIdealYaw ( m_vecLastPosition ); + SetTurnActivity(); + break; + + case TASK_FACE_TARGET: + if ( m_hTargetEnt != NULL ) + { + MakeIdealYaw ( m_hTargetEnt->pev->origin ); + SetTurnActivity(); + } + else + TaskFail(); + break; + case TASK_FACE_ENEMY: + { + MakeIdealYaw ( m_vecEnemyLKP ); + SetTurnActivity(); + break; + } + case TASK_FACE_IDEAL: + { + SetTurnActivity(); + break; + } + case TASK_FACE_ROUTE: + { + if (FRouteClear()) + { + ALERT(at_aiconsole, "No route to face!\n"); + TaskFail(); + } + else + { + MakeIdealYaw(m_Route[m_iRouteIndex].vecLocation); + SetTurnActivity(); + } + break; + } + case TASK_WAIT_PVS: + case TASK_WAIT_INDEFINITE: + { + // don't do anything. + break; + } + case TASK_WAIT: + case TASK_WAIT_FACE_ENEMY: + {// set a future time that tells us when the wait is over. + m_flWaitFinished = gpGlobals->time + pTask->flData; + break; + } + case TASK_WAIT_RANDOM: + {// set a future time that tells us when the wait is over. + m_flWaitFinished = gpGlobals->time + RANDOM_FLOAT( 0.1, pTask->flData ); + break; + } + case TASK_MOVE_TO_TARGET_RANGE: + { + if ( (m_hTargetEnt->pev->origin - pev->origin).Length() < 1 ) + TaskComplete(); + else + { + m_vecMoveGoal = m_hTargetEnt->pev->origin; + if ( !MoveToTarget( ACT_WALK, 2 ) ) + TaskFail(); + } + break; + } + case TASK_RUN_TO_TARGET: + case TASK_WALK_TO_TARGET: + { + Activity newActivity; + + if ( (m_hTargetEnt->pev->origin - pev->origin).Length() < 1 ) + TaskComplete(); + else + { + if ( pTask->iTask == TASK_WALK_TO_TARGET ) + newActivity = ACT_WALK; + else + newActivity = ACT_RUN; + // This monster can't do this! + if ( LookupActivity( newActivity ) == ACTIVITY_NOT_AVAILABLE ) + TaskComplete(); + else + { + if ( m_hTargetEnt == NULL || !MoveToTarget( newActivity, 2 ) ) + { + TaskFail(); + ALERT( at_aiconsole, "%s Failed to reach target!!!\n", STRING(pev->classname) ); + RouteClear(); + } + } + } + TaskComplete(); + break; + } + case TASK_CLEAR_MOVE_WAIT: + { + m_flMoveWaitFinished = gpGlobals->time; + TaskComplete(); + break; + } + case TASK_MELEE_ATTACK1_NOTURN: + case TASK_MELEE_ATTACK1: + { + m_IdealActivity = ACT_MELEE_ATTACK1; + break; + } + case TASK_MELEE_ATTACK2_NOTURN: + case TASK_MELEE_ATTACK2: + { + m_IdealActivity = ACT_MELEE_ATTACK2; + break; + } + case TASK_RANGE_ATTACK1_NOTURN: + case TASK_RANGE_ATTACK1: + { + m_IdealActivity = ACT_RANGE_ATTACK1; + break; + } + case TASK_RANGE_ATTACK2_NOTURN: + case TASK_RANGE_ATTACK2: + { + m_IdealActivity = ACT_RANGE_ATTACK2; + break; + } + case TASK_RELOAD_NOTURN: + case TASK_RELOAD: + { + m_IdealActivity = ACT_RELOAD; + break; + } + case TASK_SPECIAL_ATTACK1: + { + m_IdealActivity = ACT_SPECIAL_ATTACK1; + break; + } + case TASK_SPECIAL_ATTACK2: + { + m_IdealActivity = ACT_SPECIAL_ATTACK2; + break; + } + case TASK_SET_ACTIVITY: + { + m_IdealActivity = (Activity)(int)pTask->flData; + TaskComplete(); + break; + } + case TASK_GET_PATH_TO_ENEMY_LKP: + { + if ( BuildRoute ( m_vecEnemyLKP, bits_MF_TO_LOCATION, NULL ) ) + { + TaskComplete(); + } + else if (BuildNearestRoute( m_vecEnemyLKP, pev->view_ofs, 0, (m_vecEnemyLKP - pev->origin).Length() )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemyLKP failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_ENEMY: + { + CBaseEntity *pEnemy = m_hEnemy; + + if ( pEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( BuildRoute ( pEnemy->pev->origin, bits_MF_TO_ENEMY, pEnemy ) ) + { + TaskComplete(); + } + else if (BuildNearestRoute( pEnemy->pev->origin, pEnemy->pev->view_ofs, 0, (pEnemy->pev->origin - pev->origin).Length() )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemy failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_ENEMY_CORPSE: + { + UTIL_MakeVectors( pev->angles ); + if ( BuildRoute ( m_vecEnemyLKP - gpGlobals->v_forward * 64, bits_MF_TO_LOCATION, NULL ) ) + { + TaskComplete(); + } + else + { + ALERT ( at_aiconsole, "GetPathToEnemyCorpse failed!!\n" ); + TaskFail(); + } + } + break; + case TASK_GET_PATH_TO_SPOT: + { + CBaseEntity *pPlayer = CBaseEntity::Instance( FIND_ENTITY_BY_CLASSNAME( NULL, "player" ) ); + if ( BuildRoute ( m_vecMoveGoal, bits_MF_TO_LOCATION, pPlayer ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToSpot failed!!\n" ); + TaskFail(); + } + break; + } + + case TASK_GET_PATH_TO_TARGET: + { + RouteClear(); + if ( m_hTargetEnt != NULL && MoveToTarget( m_movementActivity, 1 ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToSpot failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_HINTNODE:// for active idles! + { + if ( MoveToLocation( m_movementActivity, 2, WorldGraph.m_pNodes[ m_iHintNode ].m_vecOrigin ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToHintNode failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_LASTPOSITION: + { + m_vecMoveGoal = m_vecLastPosition; + + if ( MoveToLocation( m_movementActivity, 2, m_vecMoveGoal ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToLastPosition failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_BESTSOUND: + { + CSound *pSound; + + pSound = PBestSound(); + + if ( pSound && MoveToLocation( m_movementActivity, 2, pSound->m_vecOrigin ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToBestSound failed!!\n" ); + TaskFail(); + } + break; + } +case TASK_GET_PATH_TO_BESTSCENT: + { + CSound *pScent; + + pScent = PBestScent(); + + if ( pScent && MoveToLocation( m_movementActivity, 2, pScent->m_vecOrigin ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToBestScent failed!!\n" ); + + TaskFail(); + } + break; + } + case TASK_RUN_PATH: + { + // UNDONE: This is in some default AI and some monsters can't run? -- walk instead? + if ( LookupActivity( ACT_RUN ) != ACTIVITY_NOT_AVAILABLE ) + { + m_movementActivity = ACT_RUN; + } + else + { + m_movementActivity = ACT_WALK; + } + TaskComplete(); + break; + } + case TASK_WALK_PATH: + { + if ( pev->movetype == MOVETYPE_FLY ) + { + m_movementActivity = ACT_FLY; + } + if ( LookupActivity( ACT_WALK ) != ACTIVITY_NOT_AVAILABLE ) + { + m_movementActivity = ACT_WALK; + } + else + { + m_movementActivity = ACT_RUN; + } + TaskComplete(); + break; + } + case TASK_STRAFE_PATH: + { + Vector2D vec2DirToPoint; + Vector2D vec2RightSide; + + // to start strafing, we have to first figure out if the target is on the left side or right side + UTIL_MakeVectors ( pev->angles ); + + vec2DirToPoint = ( m_Route[ 0 ].vecLocation - pev->origin ).Make2D().Normalize(); + vec2RightSide = gpGlobals->v_right.Make2D().Normalize(); + + if ( DotProduct ( vec2DirToPoint, vec2RightSide ) > 0 ) + { + // strafe right + m_movementActivity = ACT_STRAFE_RIGHT; + } + else + { + // strafe left + m_movementActivity = ACT_STRAFE_LEFT; + } + TaskComplete(); + break; + } + + + case TASK_WAIT_FOR_MOVEMENT: + { + if (FRouteClear()) + { + TaskComplete(); + } + break; + } + + case TASK_EAT: + { + Eat( pTask->flData ); + TaskComplete(); + break; + } + case TASK_SMALL_FLINCH: + { + m_IdealActivity = GetSmallFlinchActivity(); + break; + } + case TASK_DIE: + { + RouteClear(); + + m_IdealActivity = GetDeathActivity(); + + pev->deadflag = DEAD_DYING; + break; + } + case TASK_SOUND_WAKE: + { + AlertSound(); + TaskComplete(); + break; + } + case TASK_SOUND_DIE: + { + DeathSound(); + TaskComplete(); + break; + } + case TASK_SOUND_IDLE: + { + IdleSound(); + TaskComplete(); + break; + } + case TASK_SOUND_PAIN: + { + PainSound(); + TaskComplete(); + break; + } + case TASK_SOUND_DEATH: + { + DeathSound(); + TaskComplete(); + break; + } + case TASK_SOUND_ANGRY: + { + // sounds are complete as soon as we get here, cause we've already played them. + ALERT ( at_aiconsole, "SOUND\n" ); + TaskComplete(); + break; + } + case TASK_WAIT_FOR_SCRIPT: + { + if (m_pCine->m_iszIdle) + { + m_pCine->StartSequence( (CBaseMonster *)this, m_pCine->m_iszIdle, FALSE ); + if (FStrEq( STRING(m_pCine->m_iszIdle), STRING(m_pCine->m_iszPlay))) + { + pev->framerate = 0; + } + } + else + m_IdealActivity = ACT_IDLE; + + break; + } + case TASK_PLAY_SCRIPT: + { + pev->movetype = MOVETYPE_FLY; + ClearBits(pev->flags, FL_ONGROUND); + m_scriptState = SCRIPT_PLAYING; + break; + } + case TASK_ENABLE_SCRIPT: + { + m_pCine->DelayStart( 0 ); + TaskComplete(); + break; + } + case TASK_PLANT_ON_SCRIPT: + { + if ( m_hTargetEnt != NULL ) + { + pev->origin = m_hTargetEnt->pev->origin; // Plant on target + } + + TaskComplete(); + break; + } + case TASK_FACE_SCRIPT: + { + if ( m_hTargetEnt != NULL ) + { + pev->ideal_yaw = UTIL_AngleMod( m_hTargetEnt->pev->angles.y ); + } + + TaskComplete(); + m_IdealActivity = ACT_IDLE; + RouteClear(); + break; + } + + case TASK_SUGGEST_STATE: + { + m_IdealMonsterState = (MONSTERSTATE)(int)pTask->flData; + TaskComplete(); + break; + } + + case TASK_SET_FAIL_SCHEDULE: + m_failSchedule = (int)pTask->flData; + TaskComplete(); + break; + + case TASK_CLEAR_FAIL_SCHEDULE: + m_failSchedule = SCHED_NONE; + TaskComplete(); + break; + + default: + { + ALERT ( at_aiconsole, "No StartTask entry for %d\n", (SHARED_TASKS)pTask->iTask ); + break; + } + } +} + +//========================================================= +// GetTask - returns a pointer to the current +// scheduled task. NULL if there's a problem. +//========================================================= +Task_t *CBaseMonster :: GetTask ( void ) +{ + if ( m_iScheduleIndex < 0 || m_iScheduleIndex >= m_pSchedule->cTasks ) + { + // m_iScheduleIndex is not within valid range for the monster's current schedule. + return NULL; + } + else + { + return &m_pSchedule->pTasklist[ m_iScheduleIndex ]; + } +} + +//========================================================= +// GetSchedule - Decides which type of schedule best suits +// the monster's current state and conditions. Then calls +// monster's member function to get a pointer to a schedule +// of the proper type. +//========================================================= +Schedule_t *CBaseMonster :: GetSchedule ( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_PRONE: + { + return GetScheduleOfType( SCHED_BARNACLE_VICTIM_GRAB ); + break; + } + case MONSTERSTATE_NONE: + { + ALERT ( at_aiconsole, "MONSTERSTATE IS NONE!\n" ); + break; + } + case MONSTERSTATE_IDLE: + { + if ( HasConditions ( bits_COND_HEAR_SOUND ) ) + { + return GetScheduleOfType( SCHED_ALERT_FACE ); + } + else if ( FRouteClear() ) + { + // no valid route! + return GetScheduleOfType( SCHED_IDLE_STAND ); + } + else + { + // valid route. Get moving + return GetScheduleOfType( SCHED_IDLE_WALK ); + } + break; + } + case MONSTERSTATE_ALERT: + { + if ( HasConditions( bits_COND_ENEMY_DEAD ) && LookupActivity( ACT_VICTORY_DANCE ) != ACTIVITY_NOT_AVAILABLE ) + { + return GetScheduleOfType ( SCHED_VICTORY_DANCE ); + } + + if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE) ) + { + if ( fabs( FlYawDiff() ) < (1.0 - m_flFieldOfView) * 60 ) // roughly in the correct direction + { + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ORIGIN ); + } + else + { + return GetScheduleOfType( SCHED_ALERT_SMALL_FLINCH ); + } + } + + else if ( HasConditions ( bits_COND_HEAR_SOUND ) ) + { + return GetScheduleOfType( SCHED_ALERT_FACE ); + } + else + { + return GetScheduleOfType( SCHED_ALERT_STAND ); + } + break; + } + case MONSTERSTATE_COMBAT: + { + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // clear the current (dead) enemy and try to find another. + m_hEnemy = NULL; + + if ( GetEnemy() ) + { + ClearConditions( bits_COND_ENEMY_DEAD ); + return GetSchedule(); + } + else + { + SetState( MONSTERSTATE_ALERT ); + return GetSchedule(); + } + } + + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + return GetScheduleOfType ( SCHED_WAKE_ANGRY ); + } + else if (HasConditions(bits_COND_LIGHT_DAMAGE) && !HasMemory( bits_MEMORY_FLINCHED) ) + { + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + else if ( !HasConditions(bits_COND_SEE_ENEMY) ) + { + // we can't see the enemy + if ( !HasConditions(bits_COND_ENEMY_OCCLUDED) ) + { + // enemy is unseen, but not occluded! + // turn to face enemy + return GetScheduleOfType( SCHED_COMBAT_FACE ); + } + else + { + // chase! + return GetScheduleOfType( SCHED_CHASE_ENEMY ); + } + } + else + { + // we can see the enemy + if ( HasConditions(bits_COND_CAN_RANGE_ATTACK1) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + if ( HasConditions(bits_COND_CAN_RANGE_ATTACK2) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + if ( HasConditions(bits_COND_CAN_MELEE_ATTACK1) ) + { + return GetScheduleOfType( SCHED_MELEE_ATTACK1 ); + } + if ( HasConditions(bits_COND_CAN_MELEE_ATTACK2) ) + { + return GetScheduleOfType( SCHED_MELEE_ATTACK2 ); + } + if ( !HasConditions(bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_MELEE_ATTACK1) ) + { + // if we can see enemy but can't use either attack type, we must need to get closer to enemy + return GetScheduleOfType( SCHED_CHASE_ENEMY ); + } + else if ( !FacingIdeal() ) + { + //turn + return GetScheduleOfType( SCHED_COMBAT_FACE ); + } + else + { + ALERT ( at_aiconsole, "No suitable combat schedule!\n" ); + } + } + break; + } + case MONSTERSTATE_DEAD: + { + return GetScheduleOfType( SCHED_DIE ); + break; + } + case MONSTERSTATE_SCRIPT: + { + ASSERT( m_pCine != NULL ); + if ( !m_pCine ) + { + ALERT( at_aiconsole, "Script failed for %s\n", STRING(pev->classname) ); + CineCleanup(); + return GetScheduleOfType( SCHED_IDLE_STAND ); + } + + return GetScheduleOfType( SCHED_AISCRIPT ); + } + default: + { + ALERT ( at_aiconsole, "Invalid State for GetSchedule!\n" ); + break; + } + } + + return &slError[ 0 ]; +} diff --git a/dlls/Makefile b/dlls/Makefile new file mode 100644 index 0000000..a02dc28 --- /dev/null +++ b/dlls/Makefile @@ -0,0 +1,186 @@ +# +# Half-Life Full SDK 2.3 hl_i386.so Makefile for x86 Linux +# +# October 2002 by Leon Hartwig (hartwig@valvesoftware.com) +# + +DLLNAME=hl + +ARCH=i386 + +#make sure this is the correct compiler for your system +CC=gcc + +DLL_SRCDIR=. +ENGINE_SRCDIR=../engine +COMMON_SRCDIR=../common +WPN_SHARED_SRCDIR=./wpn_shared +PM_SHARED_SRCDIR=../pm_shared +GAME_SHARED_SRCDIR=../game_shared + +DLL_OBJDIR=$(DLL_SRCDIR)/obj +WPN_SHARED_OBJDIR=$(WPN_SHARED_SRCDIR)/obj +PM_SHARED_OBJDIR=$(PM_SHARED_SRCDIR)/obj +GAME_SHARED_OBJDIR=$(GAME_SHARED_SRCDIR)/obj + +BASE_CFLAGS= -Dstricmp=strcasecmp -D_strnicmp=strncasecmp -Dstrnicmp=strncasecmp \ + -DCLIENT_WEAPONS + +#safe optimization +CFLAGS=$(BASE_CFLAGS) -w -m486 -O1 + +#full optimization +#CFLAGS=$(BASE_CFLAGS) -w -O1 -m486 -ffast-math -funroll-loops \ + -fomit-frame-pointer -fexpensive-optimizations \ + -malign-loops=2 -malign-jumps=2 -malign-functions=2 + +#use these when debugging +#CFLAGS=$(BASE_CFLAGS) -g + +INCLUDEDIRS=-I. -I$(ENGINE_SRCDIR) -I$(COMMON_SRCDIR) -I$(PM_SHARED_SRCDIR) -I$(GAME_SHARED_SRCDIR) + +LDFLAGS= + +SHLIBEXT=so +SHLIBCFLAGS=-fPIC +SHLIBLDFLAGS=-shared + +DO_CC=$(CC) $(CFLAGS) $(SHLIBCFLAGS) $(INCLUDEDIRS) -o $@ -c $< + +############################################################################# +# SETUP AND BUILD +# GAME +############################################################################# + +$(DLL_OBJDIR)/%.o: $(DLL_SRCDIR)/%.cpp + $(DO_CC) + +$(WPN_SHARED_OBJDIR)/%.o: $(WPN_SHARED_SRCDIR)/%.cpp + $(DO_CC) + +$(GAME_SHARED_OBJDIR)/%.o: $(GAME_SHARED_SRCDIR)/%.cpp + $(DO_CC) + +$(PM_SHARED_OBJDIR)/%.o: $(PM_SHARED_SRCDIR)/%.c + $(DO_CC) + +OBJ = \ + $(DLL_OBJDIR)/aflock.o \ + $(DLL_OBJDIR)/agrunt.o \ + $(DLL_OBJDIR)/airtank.o \ + $(DLL_OBJDIR)/animating.o \ + $(DLL_OBJDIR)/animation.o \ + $(DLL_OBJDIR)/apache.o \ + $(DLL_OBJDIR)/barnacle.o \ + $(DLL_OBJDIR)/barney.o \ + $(DLL_OBJDIR)/bigmomma.o \ + $(DLL_OBJDIR)/bloater.o \ + $(DLL_OBJDIR)/bmodels.o \ + $(DLL_OBJDIR)/bullsquid.o \ + $(DLL_OBJDIR)/buttons.o \ + $(DLL_OBJDIR)/cbase.o \ + $(DLL_OBJDIR)/client.o \ + $(DLL_OBJDIR)/combat.o \ + $(DLL_OBJDIR)/controller.o \ + $(DLL_OBJDIR)/crossbow.o \ + $(DLL_OBJDIR)/crowbar.o \ + $(DLL_OBJDIR)/defaultai.o \ + $(DLL_OBJDIR)/doors.o \ + $(DLL_OBJDIR)/effects.o \ + $(DLL_OBJDIR)/egon.o \ + $(DLL_OBJDIR)/explode.o \ + $(DLL_OBJDIR)/flyingmonster.o \ + $(DLL_OBJDIR)/func_break.o \ + $(DLL_OBJDIR)/func_tank.o \ + $(DLL_OBJDIR)/game.o \ + $(DLL_OBJDIR)/gamerules.o \ + $(DLL_OBJDIR)/gargantua.o \ + $(DLL_OBJDIR)/gauss.o \ + $(DLL_OBJDIR)/genericmonster.o \ + $(DLL_OBJDIR)/ggrenade.o \ + $(DLL_OBJDIR)/globals.o \ + $(DLL_OBJDIR)/gman.o \ + $(DLL_OBJDIR)/h_ai.o \ + $(DLL_OBJDIR)/h_battery.o \ + $(DLL_OBJDIR)/h_cine.o \ + $(DLL_OBJDIR)/h_cycler.o \ + $(DLL_OBJDIR)/h_export.o \ + $(DLL_OBJDIR)/handgrenade.o \ + $(DLL_OBJDIR)/hassassin.o \ + $(DLL_OBJDIR)/headcrab.o \ + $(DLL_OBJDIR)/healthkit.o \ + $(DLL_OBJDIR)/hgrunt.o \ + $(DLL_OBJDIR)/hornet.o \ + $(DLL_OBJDIR)/hornetgun.o \ + $(DLL_OBJDIR)/houndeye.o \ + $(DLL_OBJDIR)/ichthyosaur.o \ + $(DLL_OBJDIR)/islave.o \ + $(DLL_OBJDIR)/items.o \ + $(DLL_OBJDIR)/leech.o \ + $(DLL_OBJDIR)/lights.o \ + $(DLL_OBJDIR)/maprules.o \ + $(DLL_OBJDIR)/monstermaker.o \ + $(DLL_OBJDIR)/monsters.o \ + $(DLL_OBJDIR)/monsterstate.o \ + $(DLL_OBJDIR)/mortar.o \ + $(DLL_OBJDIR)/mp5.o \ + $(DLL_OBJDIR)/multiplay_gamerules.o \ + $(DLL_OBJDIR)/nihilanth.o \ + $(DLL_OBJDIR)/nodes.o \ + $(DLL_OBJDIR)/osprey.o \ + $(DLL_OBJDIR)/pathcorner.o \ + $(DLL_OBJDIR)/plane.o \ + $(DLL_OBJDIR)/plats.o \ + $(DLL_OBJDIR)/player.o \ + $(DLL_OBJDIR)/python.o \ + $(DLL_OBJDIR)/rat.o \ + $(DLL_OBJDIR)/roach.o \ + $(DLL_OBJDIR)/rpg.o \ + $(DLL_OBJDIR)/satchel.o \ + $(DLL_OBJDIR)/schedule.o \ + $(DLL_OBJDIR)/scientist.o \ + $(DLL_OBJDIR)/scripted.o \ + $(DLL_OBJDIR)/shotgun.o \ + $(DLL_OBJDIR)/singleplay_gamerules.o \ + $(DLL_OBJDIR)/skill.o \ + $(DLL_OBJDIR)/sound.o \ + $(DLL_OBJDIR)/soundent.o \ + $(DLL_OBJDIR)/spectator.o \ + $(DLL_OBJDIR)/squadmonster.o \ + $(DLL_OBJDIR)/squeakgrenade.o \ + $(DLL_OBJDIR)/subs.o \ + $(DLL_OBJDIR)/talkmonster.o \ + $(DLL_OBJDIR)/teamplay_gamerules.o \ + $(DLL_OBJDIR)/tempmonster.o \ + $(DLL_OBJDIR)/tentacle.o \ + $(DLL_OBJDIR)/triggers.o \ + $(DLL_OBJDIR)/tripmine.o \ + $(DLL_OBJDIR)/turret.o \ + $(DLL_OBJDIR)/util.o \ + $(DLL_OBJDIR)/weapons.o \ + $(DLL_OBJDIR)/world.o \ + $(DLL_OBJDIR)/xen.o \ + $(DLL_OBJDIR)/zombie.o \ + $(WPN_SHARED_OBJDIR)/hl_wpn_glock.o \ + $(GAME_SHARED_OBJDIR)/voice_gamemgr.o \ + $(PM_SHARED_OBJDIR)/pm_debug.o \ + $(PM_SHARED_OBJDIR)/pm_math.o \ + $(PM_SHARED_OBJDIR)/pm_shared.o + +$(DLLNAME)_$(ARCH).$(SHLIBEXT) : neat $(OBJ) + $(CC) $(CFLAGS) $(SHLIBLDFLAGS) $(LDFLAGS) -o $@ $(OBJ) + +neat: + -mkdir $(DLL_OBJDIR) + -mkdir $(WPN_SHARED_OBJDIR) + -mkdir $(GAME_SHARED_OBJDIR) + -mkdir $(PM_SHARED_OBJDIR) +clean: + -rm -f $(OBJ) + -rm -f $(DLLNAME)_$(ARCH).$(SHLIBEXT) +spotless: clean + -rm -r $(DLL_OBJDIR) + -rm -r $(WPN_SHARED_OBJDIR) + -rm -r $(GAME_SHARED_OBJDIR) + -rm -r $(PM_SHARED_OBJDIR) + diff --git a/dlls/Wxdebug.cpp b/dlls/Wxdebug.cpp new file mode 100644 index 0000000..26d0d38 --- /dev/null +++ b/dlls/Wxdebug.cpp @@ -0,0 +1,395 @@ +//==========================================================================; +// +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR +// PURPOSE. +// +// Copyright (c) 1992 - 1997 Microsoft Corporation. All Rights Reserved. +// +//--------------------------------------------------------------------------; + + +// For every module and executable we store a debugging level and flags +// for the types of output that are desired. Constants for the types are +// defined in WXDEBUG.H and more can be added. +// The keys are stored in the registry under the +// HKEY_LOCAL_MACHINE\SOFTWARE\Debug\\Type and +// HKEY_LOCAL_MACHINE\SOFTWARE\Debug\\Level key values +// +// There are also global values under SOFTWARE\Debug\Global which are loaded +// after the module-specific values. The Types specified there are OR'ed with +// the module specific types and m_dwLevel is set to the greater of the global +// and the module specific settings. + +#include +#include + +#include "extdll.h" +#include "util.h" +#include "wxdebug.h" + +#include + +#ifdef _DEBUG + +void WINAPI DbgInitModuleName(void); +void WINAPI DbgInitModuleSettings(void); +void WINAPI DbgInitGlobalSettings(void); +void WINAPI DbgInitLogTo(HKEY hKey); +void WINAPI DbgInitKeyLevels(HKEY hKey, DWORD *pdwTypes, DWORD *pdwLevel); + + + +const INT iDEBUGINFO = 512; // Used to format strings + +HINSTANCE m_hInst; // Module instance handle +TCHAR m_ModuleName[iDEBUGINFO]; // Cut down module name +//CRITICAL_SECTION m_CSDebug; // Controls access to list +BOOL m_bInit = FALSE; // Have we been initialised +HANDLE m_hOutput = INVALID_HANDLE_VALUE; // Optional output written here +DWORD m_dwTypes = 0; +DWORD m_dwLevel = 0; + +const TCHAR *m_pBaseKey = TEXT("SOFTWARE\\Debug"); +const TCHAR *m_pGlobalKey = TEXT("GLOBAL"); +TCHAR *pKeyNames[] = +{ + TEXT("Types"), + TEXT("Level") +}; + + +// DbgInitialize +// This sets the instance handle that the debug library uses to find +// the module's file name from the Win32 GetModuleFileName function +void WINAPI DbgInitialise(HINSTANCE hInst) +{ + if (!m_bInit) + { + //InitializeCriticalSection(&m_CSDebug); + m_bInit = TRUE; + m_hInst = hInst; + DbgInitModuleName(); + DbgInitModuleSettings(); + DbgInitGlobalSettings(); + } +} + + +// DbgTerminate +// This is called to clear up any resources the debug library uses - at the +// moment we delete our critical section and the handle of the output file. +void WINAPI DbgTerminate() +{ + if (m_bInit) + { + if (m_hOutput != INVALID_HANDLE_VALUE) + { + DBGASSERTEXECUTE(CloseHandle(m_hOutput)); + m_hOutput = INVALID_HANDLE_VALUE; + } + //DeleteCriticalSection(&m_CSDebug); + m_bInit = FALSE; + } +} + + +// DbgInitModuleName +// Initialise the module file name +void WINAPI DbgInitModuleName() +{ + TCHAR FullName[iDEBUGINFO]; // Load the full path and module name + TCHAR *pName; // Searches from the end for a backslash + + GetModuleFileName(m_hInst,FullName,iDEBUGINFO); + pName = _tcsrchr(FullName,'\\'); + if (pName == NULL) + { + pName = FullName; + } + else + { + pName++; + } + lstrcpy(m_ModuleName,pName); +} + + +// DbgInitModuleSettings +// Retrieve the module-specific settings +void WINAPI DbgInitModuleSettings() +{ + LONG lReturn; // Create key return value + TCHAR szInfo[iDEBUGINFO]; // Constructs key names + HKEY hModuleKey; // Module key handle + + // Construct the base key name + wsprintf(szInfo,TEXT("%s\\%s"),m_pBaseKey,m_ModuleName); + + // Create or open the key for this module + lReturn = RegCreateKeyEx(HKEY_LOCAL_MACHINE, // Handle of an open key + szInfo, // Address of subkey name + (DWORD)0, // Reserved value + NULL, // Address of class name + (DWORD)0, // Special options flags + KEY_ALL_ACCESS, // Desired security access + NULL, // Key security descriptor + &hModuleKey, // Opened handle buffer + NULL); // What really happened + + if (lReturn != ERROR_SUCCESS) + { + DbgLogInfo(LOG_ERROR, 0, TEXT("Could not access module key")); + return; + } + + DbgInitLogTo(hModuleKey); + DbgInitKeyLevels(hModuleKey, &m_dwTypes, &m_dwLevel); + RegCloseKey(hModuleKey); +} + + +// DbgInitGlobalSettings +// This is called by DbgInitialize to read the global debug settings for +// Level and Type from the registry. The Types are OR'ed together and m_dwLevel +// is set to the greater of the global and module-specific values. +void WINAPI DbgInitGlobalSettings() +{ + LONG lReturn; // Create key return value + TCHAR szInfo[iDEBUGINFO]; // Constructs key names + HKEY hGlobalKey; // Global override key + DWORD dwTypes = 0; + DWORD dwLevel = 0; + + // Construct the global base key name + wsprintf(szInfo,TEXT("%s\\%s"),m_pBaseKey,m_pGlobalKey); + + // Create or open the key for this module + lReturn = RegCreateKeyEx(HKEY_LOCAL_MACHINE, // Handle of an open key + szInfo, // Address of subkey name + (DWORD)0, // Reserved value + NULL, // Address of class name + (DWORD)0, // Special options flags + KEY_ALL_ACCESS, // Desired security access + NULL, // Key security descriptor + &hGlobalKey, // Opened handle buffer + NULL); // What really happened + + if (lReturn != ERROR_SUCCESS) + { + DbgLogInfo(LOG_ERROR, 0, TEXT("Could not access GLOBAL module key")); + return; + } + + DbgInitKeyLevels(hGlobalKey, &dwTypes, &dwLevel); + RegCloseKey(hGlobalKey); + + m_dwTypes |= dwTypes; + if (dwLevel > m_dwLevel) + m_dwLevel = dwLevel; +} + + +// DbgInitLogTo +// Called by DbgInitModuleSettings to setup alternate logging destinations +void WINAPI DbgInitLogTo(HKEY hKey) +{ + LONG lReturn; + DWORD dwKeyType; + DWORD dwKeySize; + TCHAR szFile[MAX_PATH] = {0}; + static const TCHAR cszKey[] = TEXT("LogToFile"); + + dwKeySize = MAX_PATH; + lReturn = RegQueryValueEx( + hKey, // Handle to an open key + cszKey, // Subkey name derivation + NULL, // Reserved field + &dwKeyType, // Returns the field type + (LPBYTE) szFile, // Returns the field's value + &dwKeySize); // Number of bytes transferred + + // create an empty key if it does not already exist + if (lReturn != ERROR_SUCCESS || dwKeyType != REG_SZ) + { + dwKeySize = 1; + lReturn = RegSetValueEx( + hKey, // Handle of an open key + cszKey, // Address of subkey name + (DWORD) 0, // Reserved field + REG_SZ, // Type of the key field + (PBYTE)szFile, // Value for the field + dwKeySize); // Size of the field buffer + } + + // if an output-to was specified. try to open it. + if (m_hOutput != INVALID_HANDLE_VALUE) + { + DBGASSERTEXECUTE(CloseHandle(m_hOutput)); + m_hOutput = INVALID_HANDLE_VALUE; + } + if (szFile[0] != 0) + { + if (!lstrcmpi(szFile, TEXT("Console"))) + { + m_hOutput = GetStdHandle(STD_OUTPUT_HANDLE); + if (m_hOutput == INVALID_HANDLE_VALUE) + { + AllocConsole(); + m_hOutput = GetStdHandle(STD_OUTPUT_HANDLE); + } + SetConsoleTitle (TEXT("Valve Debug Output")); + } else if (szFile[0] && + lstrcmpi(szFile, TEXT("Debug")) && + lstrcmpi(szFile, TEXT("Debugger")) && + lstrcmpi(szFile, TEXT("Deb"))) + { + m_hOutput = CreateFile(szFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (INVALID_HANDLE_VALUE != m_hOutput) + { + static const TCHAR cszBar[] = TEXT("\r\n\r\n=====DbgInitialize()=====\r\n\r\n"); + SetFilePointer (m_hOutput, 0, NULL, FILE_END); + DbgOutString (cszBar); + } + } + } +} + + +// DbgInitKeyLevels +// This is called by DbgInitModuleSettings and DbgInitGlobalSettings to read +// settings for Types and Level from the registry +void WINAPI DbgInitKeyLevels(HKEY hKey, DWORD *pdwTypes, DWORD *pdwLevel) +{ + LONG lReturn; // Create key return value + DWORD dwKeySize; // Size of the key value + DWORD dwKeyType; // Receives it's type + + // Get the Types value + dwKeySize = sizeof(DWORD); + lReturn = RegQueryValueEx( + hKey, // Handle to an open key + pKeyNames[0], // Subkey name derivation + NULL, // Reserved field + &dwKeyType, // Returns the field type + (LPBYTE)pdwTypes, // Returns the field's value + &dwKeySize ); // Number of bytes transferred + + // If either the key was not available or it was not a DWORD value + // then we ensure only the high priority debug logging is output + // but we try and update the field to a zero filled DWORD value + + if (lReturn != ERROR_SUCCESS || dwKeyType != REG_DWORD) + { + *pdwTypes = 0; + lReturn = RegSetValueEx( + hKey, // Handle of an open key + pKeyNames[0], // Address of subkey name + (DWORD)0, // Reserved field + REG_DWORD, // Type of the key field + (PBYTE)pdwTypes, // Value for the field + sizeof(DWORD)); // Size of the field buffer + + if (lReturn != ERROR_SUCCESS) + { + DbgLogInfo(LOG_ERROR, 0, TEXT("Could not create subkey %s"),pKeyNames[0]); + *pdwTypes = 0; + } + } + + // Get the Level value + dwKeySize = sizeof(DWORD); + lReturn = RegQueryValueEx( + hKey, // Handle to an open key + pKeyNames[1], // Subkey name derivation + NULL, // Reserved field + &dwKeyType, // Returns the field type + (LPBYTE)pdwLevel, // Returns the field's value + &dwKeySize ); // Number of bytes transferred + + // If either the key was not available or it was not a DWORD value + // then we ensure only the high priority debug logging is output + // but we try and update the field to a zero filled DWORD value + + if (lReturn != ERROR_SUCCESS || dwKeyType != REG_DWORD) + { + *pdwLevel = 0; + lReturn = RegSetValueEx( + hKey, // Handle of an open key + pKeyNames[1], // Address of subkey name + (DWORD)0, // Reserved field + REG_DWORD, // Type of the key field + (PBYTE)pdwLevel, // Value for the field + sizeof(DWORD)); // Size of the field buffer + + if (lReturn != ERROR_SUCCESS) + { + DbgLogInfo(LOG_ERROR, 0, TEXT("Could not create subkey %s"),pKeyNames[1]); + *pdwLevel = 0; + } + } +} + + +// DbgOutString +void WINAPI DbgOutString(LPCTSTR psz) +{ + if (!m_bInit) + return; + if (m_hOutput != INVALID_HANDLE_VALUE) { + UINT cb = lstrlen(psz); + DWORD dw; + WriteFile (m_hOutput, psz, cb, &dw, NULL); + } else { + OutputDebugString (psz); + } +} + + +// DbgLogInfo +// Print a formatted string to the debugger prefixed with this module's name +// Because the debug code is linked statically every module loaded will +// have its own copy of this code. It therefore helps if the module name is +// included on the output so that the offending code can be easily found +void WINAPI DbgLogInfo(DWORD Type, DWORD Level, const TCHAR *pFormat,...) +{ + if (!m_bInit) + return; + // Check the current level for this type combination */ + if (((Type & m_dwTypes) == 0) || (m_dwLevel < Level)) + return; + + TCHAR szInfo[2000]; + + // Format the variable length parameter list + + va_list va; + va_start(va, pFormat); + + //lstrcpy(szInfo, m_ModuleName); + //lstrcat(szInfo, TEXT(": ")); + wvsprintf(szInfo /* + lstrlen(szInfo) */, pFormat, va); + //lstrcat(szInfo, TEXT("\r\n")); + DbgOutString(szInfo); + + va_end(va); +} + + +// DbgKernelAssert +// If we are executing as a pure kernel filter we cannot display message +// boxes to the user, this provides an alternative which puts the error +// condition on the debugger output with a suitable eye catching message +void WINAPI DbgKernelAssert(const TCHAR *pCondition, const TCHAR *pFileName, INT iLine) +{ + if (!m_bInit) + return; + DbgLogInfo(LOG_ERROR, 0, TEXT(m_ModuleName)); + DbgLogInfo(LOG_ERROR, 0, TEXT(": Assertion FAILED (%s) at line %d in file %s\r\n"), pCondition, iLine, pFileName); + DebugBreak(); +} + +#endif // _DEBUG + + diff --git a/dlls/activity.h b/dlls/activity.h new file mode 100644 index 0000000..c091712 --- /dev/null +++ b/dlls/activity.h @@ -0,0 +1,109 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef ACTIVITY_H +#define ACTIVITY_H + + +typedef enum { + ACT_RESET = 0, // Set m_Activity to this invalid value to force a reset to m_IdealActivity + ACT_IDLE = 1, + ACT_GUARD, + ACT_WALK, + ACT_RUN, + ACT_FLY, // Fly (and flap if appropriate) + ACT_SWIM, + ACT_HOP, // vertical jump + ACT_LEAP, // long forward jump + ACT_FALL, + ACT_LAND, + ACT_STRAFE_LEFT, + ACT_STRAFE_RIGHT, + ACT_ROLL_LEFT, // tuck and roll, left + ACT_ROLL_RIGHT, // tuck and roll, right + ACT_TURN_LEFT, // turn quickly left (stationary) + ACT_TURN_RIGHT, // turn quickly right (stationary) + ACT_CROUCH, // the act of crouching down from a standing position + ACT_CROUCHIDLE, // holding body in crouched position (loops) + ACT_STAND, // the act of standing from a crouched position + ACT_USE, + ACT_SIGNAL1, + ACT_SIGNAL2, + ACT_SIGNAL3, + ACT_TWITCH, + ACT_COWER, + ACT_SMALL_FLINCH, + ACT_BIG_FLINCH, + ACT_RANGE_ATTACK1, + ACT_RANGE_ATTACK2, + ACT_MELEE_ATTACK1, + ACT_MELEE_ATTACK2, + ACT_RELOAD, + ACT_ARM, // pull out gun, for instance + ACT_DISARM, // reholster gun + ACT_EAT, // monster chowing on a large food item (loop) + ACT_DIESIMPLE, + ACT_DIEBACKWARD, + ACT_DIEFORWARD, + ACT_DIEVIOLENT, + ACT_BARNACLE_HIT, // barnacle tongue hits a monster + ACT_BARNACLE_PULL, // barnacle is lifting the monster ( loop ) + ACT_BARNACLE_CHOMP, // barnacle latches on to the monster + ACT_BARNACLE_CHEW, // barnacle is holding the monster in its mouth ( loop ) + ACT_SLEEP, + ACT_INSPECT_FLOOR, // for active idles, look at something on or near the floor + ACT_INSPECT_WALL, // for active idles, look at something directly ahead of you ( doesn't HAVE to be a wall or on a wall ) + ACT_IDLE_ANGRY, // alternate idle animation in which the monster is clearly agitated. (loop) + ACT_WALK_HURT, // limp (loop) + ACT_RUN_HURT, // limp (loop) + ACT_HOVER, // Idle while in flight + ACT_GLIDE, // Fly (don't flap) + ACT_FLY_LEFT, // Turn left in flight + ACT_FLY_RIGHT, // Turn right in flight + ACT_DETECT_SCENT, // this means the monster smells a scent carried by the air + ACT_SNIFF, // this is the act of actually sniffing an item in front of the monster + ACT_BITE, // some large monsters can eat small things in one bite. This plays one time, EAT loops. + ACT_THREAT_DISPLAY, // without attacking, monster demonstrates that it is angry. (Yell, stick out chest, etc ) + ACT_FEAR_DISPLAY, // monster just saw something that it is afraid of + ACT_EXCITED, // for some reason, monster is excited. Sees something he really likes to eat, or whatever. + ACT_SPECIAL_ATTACK1, // very monster specific special attacks. + ACT_SPECIAL_ATTACK2, + ACT_COMBAT_IDLE, // agitated idle. + ACT_WALK_SCARED, + ACT_RUN_SCARED, + ACT_VICTORY_DANCE, // killed a player, do a victory dance. + ACT_DIE_HEADSHOT, // die, hit in head. + ACT_DIE_CHESTSHOT, // die, hit in chest + ACT_DIE_GUTSHOT, // die, hit in gut + ACT_DIE_BACKSHOT, // die, hit in back + ACT_FLINCH_HEAD, + ACT_FLINCH_CHEST, + ACT_FLINCH_STOMACH, + ACT_FLINCH_LEFTARM, + ACT_FLINCH_RIGHTARM, + ACT_FLINCH_LEFTLEG, + ACT_FLINCH_RIGHTLEG, +} Activity; + + +typedef struct { + int type; + char *name; +} activity_map_t; + +extern activity_map_t activity_map[]; + + +#endif //ACTIVITY_H diff --git a/dlls/activitymap.h b/dlls/activitymap.h new file mode 100644 index 0000000..0748551 --- /dev/null +++ b/dlls/activitymap.h @@ -0,0 +1,97 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#define _A( a ) { a, #a } + +activity_map_t activity_map[] = +{ +_A( ACT_IDLE ), +_A( ACT_GUARD ), +_A( ACT_WALK ), +_A( ACT_RUN ), +_A( ACT_FLY ), +_A( ACT_SWIM ), +_A( ACT_HOP ), +_A( ACT_LEAP ), +_A( ACT_FALL ), +_A( ACT_LAND ), +_A( ACT_STRAFE_LEFT ), +_A( ACT_STRAFE_RIGHT ), +_A( ACT_ROLL_LEFT ), +_A( ACT_ROLL_RIGHT ), +_A( ACT_TURN_LEFT ), +_A( ACT_TURN_RIGHT ), +_A( ACT_CROUCH ), +_A( ACT_CROUCHIDLE ), +_A( ACT_STAND ), +_A( ACT_USE ), +_A( ACT_SIGNAL1 ), +_A( ACT_SIGNAL2 ), +_A( ACT_SIGNAL3 ), +_A( ACT_TWITCH ), +_A( ACT_COWER ), +_A( ACT_SMALL_FLINCH ), +_A( ACT_BIG_FLINCH ), +_A( ACT_RANGE_ATTACK1 ), +_A( ACT_RANGE_ATTACK2 ), +_A( ACT_MELEE_ATTACK1 ), +_A( ACT_MELEE_ATTACK2 ), +_A( ACT_RELOAD ), +_A( ACT_ARM ), +_A( ACT_DISARM ), +_A( ACT_EAT ), +_A( ACT_DIESIMPLE ), +_A( ACT_DIEBACKWARD ), +_A( ACT_DIEFORWARD ), +_A( ACT_DIEVIOLENT ), +_A( ACT_BARNACLE_HIT ), +_A( ACT_BARNACLE_PULL ), +_A( ACT_BARNACLE_CHOMP ), +_A( ACT_BARNACLE_CHEW ), +_A( ACT_SLEEP ), +_A( ACT_INSPECT_FLOOR ), +_A( ACT_INSPECT_WALL ), +_A( ACT_IDLE_ANGRY ), +_A( ACT_WALK_HURT ), +_A( ACT_RUN_HURT ), +_A( ACT_HOVER ), +_A( ACT_GLIDE ), +_A( ACT_FLY_LEFT ), +_A( ACT_FLY_RIGHT ), +_A( ACT_DETECT_SCENT ), +_A( ACT_SNIFF ), +_A( ACT_BITE ), +_A( ACT_THREAT_DISPLAY ), +_A( ACT_FEAR_DISPLAY ), +_A( ACT_EXCITED ), +_A( ACT_SPECIAL_ATTACK1 ), +_A( ACT_SPECIAL_ATTACK2 ), +_A( ACT_COMBAT_IDLE ), +_A( ACT_WALK_SCARED ), +_A( ACT_RUN_SCARED ), +_A( ACT_VICTORY_DANCE ), +_A( ACT_DIE_HEADSHOT ), +_A( ACT_DIE_CHESTSHOT ), +_A( ACT_DIE_GUTSHOT ), +_A( ACT_DIE_BACKSHOT ), +_A( ACT_FLINCH_HEAD ), +_A( ACT_FLINCH_CHEST ), +_A( ACT_FLINCH_STOMACH ), +_A( ACT_FLINCH_LEFTARM ), +_A( ACT_FLINCH_RIGHTARM ), +_A( ACT_FLINCH_LEFTLEG ), +_A( ACT_FLINCH_RIGHTLEG ), +0, NULL +}; diff --git a/dlls/aflock.cpp b/dlls/aflock.cpp new file mode 100644 index 0000000..147dc91 --- /dev/null +++ b/dlls/aflock.cpp @@ -0,0 +1,912 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +//========================================================= +#include "archtypes.h" // DAL + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "squadmonster.h" + +#define AFLOCK_MAX_RECRUIT_RADIUS 1024 +#define AFLOCK_FLY_SPEED 125 +#define AFLOCK_TURN_RATE 75 +#define AFLOCK_ACCELERATE 10 +#define AFLOCK_CHECK_DIST 192 +#define AFLOCK_TOO_CLOSE 100 +#define AFLOCK_TOO_FAR 256 + +//========================================================= +//========================================================= +class CFlockingFlyerFlock : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void SpawnFlock( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + // Sounds are shared by the flock + static void PrecacheFlockSounds( void ); + + int m_cFlockSize; + float m_flFlockRadius; +}; + +TYPEDESCRIPTION CFlockingFlyerFlock::m_SaveData[] = +{ + DEFINE_FIELD( CFlockingFlyerFlock, m_cFlockSize, FIELD_INTEGER ), + DEFINE_FIELD( CFlockingFlyerFlock, m_flFlockRadius, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CFlockingFlyerFlock, CBaseMonster ); + +//========================================================= +//========================================================= +class CFlockingFlyer : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SpawnCommonCode( void ); + void EXPORT IdleThink( void ); + void BoidAdvanceFrame( void ); + void EXPORT FormFlock( void ); + void EXPORT Start( void ); + void EXPORT FlockLeaderThink( void ); + void EXPORT FlockFollowerThink( void ); + void EXPORT FallHack( void ); + void MakeSound( void ); + void AlertFlock( void ); + void SpreadFlock( void ); + void SpreadFlock2( void ); + void Killed( entvars_t *pevAttacker, int iGib ); + void Poop ( void ); + BOOL FPathBlocked( void ); + //void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int IsLeader( void ) { return m_pSquadLeader == this; } + int InSquad( void ) { return m_pSquadLeader != NULL; } + int SquadCount( void ); + void SquadRemove( CFlockingFlyer *pRemove ); + void SquadUnlink( void ); + void SquadAdd( CFlockingFlyer *pAdd ); + void SquadDisband( void ); + + CFlockingFlyer *m_pSquadLeader; + CFlockingFlyer *m_pSquadNext; + BOOL m_fTurning;// is this boid turning? + BOOL m_fCourseAdjust;// followers set this flag TRUE to override flocking while they avoid something + BOOL m_fPathBlocked;// TRUE if there is an obstacle ahead + Vector m_vecReferencePoint;// last place we saw leader + Vector m_vecAdjustedVelocity;// adjusted velocity (used when fCourseAdjust is TRUE) + float m_flGoalSpeed; + float m_flLastBlockedTime; + float m_flFakeBlockedTime; + float m_flAlertTime; + float m_flFlockNextSoundTime; +}; +LINK_ENTITY_TO_CLASS( monster_flyer, CFlockingFlyer ); +LINK_ENTITY_TO_CLASS( monster_flyer_flock, CFlockingFlyerFlock ); + +TYPEDESCRIPTION CFlockingFlyer::m_SaveData[] = +{ + DEFINE_FIELD( CFlockingFlyer, m_pSquadLeader, FIELD_CLASSPTR ), + DEFINE_FIELD( CFlockingFlyer, m_pSquadNext, FIELD_CLASSPTR ), + DEFINE_FIELD( CFlockingFlyer, m_fTurning, FIELD_BOOLEAN ), + DEFINE_FIELD( CFlockingFlyer, m_fCourseAdjust, FIELD_BOOLEAN ), + DEFINE_FIELD( CFlockingFlyer, m_fPathBlocked, FIELD_BOOLEAN ), + DEFINE_FIELD( CFlockingFlyer, m_vecReferencePoint, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CFlockingFlyer, m_vecAdjustedVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CFlockingFlyer, m_flGoalSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CFlockingFlyer, m_flLastBlockedTime, FIELD_TIME ), + DEFINE_FIELD( CFlockingFlyer, m_flFakeBlockedTime, FIELD_TIME ), + DEFINE_FIELD( CFlockingFlyer, m_flAlertTime, FIELD_TIME ), +// DEFINE_FIELD( CFlockingFlyer, m_flFlockNextSoundTime, FIELD_TIME ), // don't need to save +}; + +IMPLEMENT_SAVERESTORE( CFlockingFlyer, CBaseMonster ); + +//========================================================= +//========================================================= +void CFlockingFlyerFlock :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "iFlockSize")) + { + m_cFlockSize = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "flFlockRadius")) + { + m_flFlockRadius = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } +} + +//========================================================= +//========================================================= +void CFlockingFlyerFlock :: Spawn( ) +{ + Precache( ); + SpawnFlock(); + + REMOVE_ENTITY(ENT(pev)); // dump the spawn ent +} + +//========================================================= +//========================================================= +void CFlockingFlyerFlock :: Precache( ) +{ + //PRECACHE_MODEL("models/aflock.mdl"); + PRECACHE_MODEL("models/boid.mdl"); + + PrecacheFlockSounds(); +} + + +void CFlockingFlyerFlock :: PrecacheFlockSounds( void ) +{ + PRECACHE_SOUND("boid/boid_alert1.wav" ); + PRECACHE_SOUND("boid/boid_alert2.wav" ); + + PRECACHE_SOUND("boid/boid_idle1.wav" ); + PRECACHE_SOUND("boid/boid_idle2.wav" ); +} + +//========================================================= +//========================================================= +void CFlockingFlyerFlock :: SpawnFlock( void ) +{ + float R = m_flFlockRadius; + int iCount; + Vector vecSpot; + CFlockingFlyer *pBoid, *pLeader; + + pLeader = pBoid = NULL; + + for ( iCount = 0 ; iCount < m_cFlockSize ; iCount++ ) + { + pBoid = GetClassPtr( (CFlockingFlyer *)NULL ); + + if ( !pLeader ) + { + // make this guy the leader. + pLeader = pBoid; + + pLeader->m_pSquadLeader = pLeader; + pLeader->m_pSquadNext = NULL; + } + + vecSpot.x = RANDOM_FLOAT( -R, R ); + vecSpot.y = RANDOM_FLOAT( -R, R ); + vecSpot.z = RANDOM_FLOAT( 0, 16 ); + vecSpot = pev->origin + vecSpot; + + UTIL_SetOrigin(pBoid->pev, vecSpot); + pBoid->pev->movetype = MOVETYPE_FLY; + pBoid->SpawnCommonCode(); + pBoid->pev->flags &= ~FL_ONGROUND; + pBoid->pev->velocity = g_vecZero; + pBoid->pev->angles = pev->angles; + + pBoid->pev->frame = 0; + pBoid->pev->nextthink = gpGlobals->time + 0.2; + pBoid->SetThink( &CFlockingFlyer :: IdleThink ); + + if ( pBoid != pLeader ) + { + pLeader->SquadAdd( pBoid ); + } + } +} + +//========================================================= +//========================================================= +void CFlockingFlyer :: Spawn( ) +{ + Precache( ); + SpawnCommonCode(); + + pev->frame = 0; + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CFlockingFlyer::IdleThink ); +} + +//========================================================= +//========================================================= +void CFlockingFlyer :: Precache( ) +{ + //PRECACHE_MODEL("models/aflock.mdl"); + PRECACHE_MODEL("models/boid.mdl"); + CFlockingFlyerFlock::PrecacheFlockSounds(); +} + +//========================================================= +//========================================================= +void CFlockingFlyer :: MakeSound( void ) +{ + if ( m_flAlertTime > gpGlobals->time ) + { + // make agitated sounds + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "boid/boid_alert1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "boid/boid_alert2.wav", 1, ATTN_NORM ); break; + } + + return; + } + + // make normal sound + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "boid/boid_idle1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "boid/boid_idle2.wav", 1, ATTN_NORM ); break; + } +} + +//========================================================= +//========================================================= +void CFlockingFlyer :: Killed( entvars_t *pevAttacker, int iGib ) +{ + CFlockingFlyer *pSquad; + + pSquad = (CFlockingFlyer *)m_pSquadLeader; + + while ( pSquad ) + { + pSquad->m_flAlertTime = gpGlobals->time + 15; + pSquad = (CFlockingFlyer *)pSquad->m_pSquadNext; + } + + if ( m_pSquadLeader ) + { + m_pSquadLeader->SquadRemove( this ); + } + + pev->deadflag = DEAD_DEAD; + + pev->framerate = 0; + pev->effects = EF_NOINTERP; + + UTIL_SetSize( pev, Vector(0,0,0), Vector(0,0,0) ); + pev->movetype = MOVETYPE_TOSS; + + SetThink ( &CFlockingFlyer::FallHack ); + pev->nextthink = gpGlobals->time + 0.1; +} + +void CFlockingFlyer :: FallHack( void ) +{ + if ( pev->flags & FL_ONGROUND ) + { + if ( !FClassnameIs ( pev->groundentity, "worldspawn" ) ) + { + pev->flags &= ~FL_ONGROUND; + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + pev->velocity = g_vecZero; + SetThink( NULL ); + } + } +} + +//========================================================= +//========================================================= +void CFlockingFlyer :: SpawnCommonCode( ) +{ + pev->deadflag = DEAD_NO; + pev->classname = MAKE_STRING("monster_flyer"); + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_FLY; + pev->takedamage = DAMAGE_NO; + pev->health = 1; + + m_fPathBlocked = FALSE;// obstacles will be detected + m_flFieldOfView = 0.2; + + //SET_MODEL(ENT(pev), "models/aflock.mdl"); + SET_MODEL(ENT(pev), "models/boid.mdl"); + +// UTIL_SetSize(pev, Vector(0,0,0), Vector(0,0,0)); + UTIL_SetSize(pev, Vector(-5,-5,0), Vector(5,5,2)); +} + +//========================================================= +//========================================================= +void CFlockingFlyer :: BoidAdvanceFrame ( ) +{ + float flapspeed = (pev->speed - pev->armorvalue) / AFLOCK_ACCELERATE; + pev->armorvalue = pev->armorvalue * .8 + pev->speed * .2; + + if (flapspeed < 0) flapspeed = -flapspeed; + if (flapspeed < 0.25) flapspeed = 0.25; + if (flapspeed > 1.9) flapspeed = 1.9; + + pev->framerate = flapspeed; + + // lean + pev->avelocity.x = - (pev->angles.x + flapspeed * 5); + + // bank + pev->avelocity.z = - (pev->angles.z + pev->avelocity.y); + + // pev->framerate = flapspeed; + StudioFrameAdvance( 0.1 ); +} + +//========================================================= +//========================================================= +void CFlockingFlyer :: IdleThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.2; + + // see if there's a client in the same pvs as the monster + if ( !FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) + { + SetThink( &CFlockingFlyer::Start ); + pev->nextthink = gpGlobals->time + 0.1; + } +} + +//========================================================= +// Start - player enters the pvs, so get things going. +//========================================================= +void CFlockingFlyer :: Start( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if ( IsLeader() ) + { + SetThink( &CFlockingFlyer::FlockLeaderThink ); + } + else + { + SetThink( &CFlockingFlyer::FlockFollowerThink ); + } + +/* + Vector vecTakeOff; + vecTakeOff = Vector ( 0 , 0 , 0 ); + + vecTakeOff.z = 50 + RANDOM_FLOAT ( 0, 100 ); + vecTakeOff.x = 20 - RANDOM_FLOAT ( 0, 40); + vecTakeOff.y = 20 - RANDOM_FLOAT ( 0, 40); + + pev->velocity = vecTakeOff; + + + pev->speed = pev->velocity.Length(); + pev->sequence = 0; +*/ + SetActivity ( ACT_FLY ); + ResetSequenceInfo( ); + BoidAdvanceFrame( ); + + pev->speed = AFLOCK_FLY_SPEED;// no delay! +} + +//========================================================= +// Leader boid calls this to form a flock from surrounding boids +//========================================================= +void CFlockingFlyer :: FormFlock( void ) +{ + if ( !InSquad() ) + { + // I am my own leader + m_pSquadLeader = this; + m_pSquadNext = NULL; + int squadCount = 1; + + CBaseEntity *pEntity = NULL; + + while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, AFLOCK_MAX_RECRUIT_RADIUS )) != NULL) + { + CBaseMonster *pRecruit = pEntity->MyMonsterPointer( ); + + if ( pRecruit && pRecruit != this && pRecruit->IsAlive() && !pRecruit->m_pCine ) + { + // Can we recruit this guy? + if ( FClassnameIs ( pRecruit->pev, "monster_flyer" ) ) + { + squadCount++; + SquadAdd( (CFlockingFlyer *)pRecruit ); + } + } + } + } + + SetThink( &CFlockingFlyer::IdleThink );// now that flock is formed, go to idle and wait for a player to come along. + pev->nextthink = gpGlobals->time; +} + +//========================================================= +// Searches for boids that are too close and pushes them away +//========================================================= +void CFlockingFlyer :: SpreadFlock( ) +{ + Vector vecDir; + float flSpeed;// holds vector magnitude while we fiddle with the direction + + CFlockingFlyer *pList = m_pSquadLeader; + while ( pList ) + { + if ( pList != this && ( pev->origin - pList->pev->origin ).Length() <= AFLOCK_TOO_CLOSE ) + { + // push the other away + vecDir = ( pList->pev->origin - pev->origin ); + vecDir = vecDir.Normalize(); + + // store the magnitude of the other boid's velocity, and normalize it so we + // can average in a course that points away from the leader. + flSpeed = pList->pev->velocity.Length(); + pList->pev->velocity = pList->pev->velocity.Normalize(); + pList->pev->velocity = ( pList->pev->velocity + vecDir ) * 0.5; + pList->pev->velocity = pList->pev->velocity * flSpeed; + } + + pList = pList->m_pSquadNext; + } +} + +//========================================================= +// Alters the caller's course if he's too close to others +// +// This function should **ONLY** be called when Caller's velocity is normalized!! +//========================================================= +void CFlockingFlyer :: SpreadFlock2 ( ) +{ + Vector vecDir; + + CFlockingFlyer *pList = m_pSquadLeader; + while ( pList ) + { + if ( pList != this && ( pev->origin - pList->pev->origin ).Length() <= AFLOCK_TOO_CLOSE ) + { + vecDir = ( pev->origin - pList->pev->origin ); + vecDir = vecDir.Normalize(); + + pev->velocity = (pev->velocity + vecDir); + } + + pList = pList->m_pSquadNext; + } +} + +//========================================================= +// FBoidPathBlocked - returns TRUE if there is an obstacle ahead +//========================================================= +BOOL CFlockingFlyer :: FPathBlocked( ) +{ + TraceResult tr; + Vector vecDist;// used for general measurements + Vector vecDir;// used for general measurements + BOOL fBlocked; + + if ( m_flFakeBlockedTime > gpGlobals->time ) + { + m_flLastBlockedTime = gpGlobals->time; + return TRUE; + } + + // use VELOCITY, not angles, not all boids point the direction they are flying + //vecDir = UTIL_VecToAngles( pevBoid->velocity ); + UTIL_MakeVectors ( pev->angles ); + + fBlocked = FALSE;// assume the way ahead is clear + + // check for obstacle ahead + UTIL_TraceLine(pev->origin, pev->origin + gpGlobals->v_forward * AFLOCK_CHECK_DIST, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + m_flLastBlockedTime = gpGlobals->time; + fBlocked = TRUE; + } + + // extra wide checks + UTIL_TraceLine(pev->origin + gpGlobals->v_right * 12, pev->origin + gpGlobals->v_right * 12 + gpGlobals->v_forward * AFLOCK_CHECK_DIST, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + m_flLastBlockedTime = gpGlobals->time; + fBlocked = TRUE; + } + + UTIL_TraceLine(pev->origin - gpGlobals->v_right * 12, pev->origin - gpGlobals->v_right * 12 + gpGlobals->v_forward * AFLOCK_CHECK_DIST, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + m_flLastBlockedTime = gpGlobals->time; + fBlocked = TRUE; + } + + if ( !fBlocked && gpGlobals->time - m_flLastBlockedTime > 6 ) + { + // not blocked, and it's been a few seconds since we've actually been blocked. + m_flFakeBlockedTime = gpGlobals->time + RANDOM_LONG(1, 3); + } + + return fBlocked; +} + + +//========================================================= +// Leader boids use this think every tenth +//========================================================= +void CFlockingFlyer :: FlockLeaderThink( void ) +{ + TraceResult tr; + Vector vecDist;// used for general measurements + Vector vecDir;// used for general measurements + int cProcessed = 0;// keep track of how many other boids we've processed + float flLeftSide; + float flRightSide; + + + pev->nextthink = gpGlobals->time + 0.1; + + UTIL_MakeVectors ( pev->angles ); + + // is the way ahead clear? + if ( !FPathBlocked () ) + { + // if the boid is turning, stop the trend. + if ( m_fTurning ) + { + m_fTurning = FALSE; + pev->avelocity.y = 0; + } + + m_fPathBlocked = FALSE; + + if (pev->speed <= AFLOCK_FLY_SPEED ) + pev->speed+= 5; + + pev->velocity = gpGlobals->v_forward * pev->speed; + + BoidAdvanceFrame( ); + + return; + } + + // IF we get this far in the function, the leader's path is blocked! + m_fPathBlocked = TRUE; + + if ( !m_fTurning)// something in the way and boid is not already turning to avoid + { + // measure clearance on left and right to pick the best dir to turn + UTIL_TraceLine(pev->origin, pev->origin + gpGlobals->v_right * AFLOCK_CHECK_DIST, ignore_monsters, ENT(pev), &tr); + vecDist = (tr.vecEndPos - pev->origin); + flRightSide = vecDist.Length(); + + UTIL_TraceLine(pev->origin, pev->origin - gpGlobals->v_right * AFLOCK_CHECK_DIST, ignore_monsters, ENT(pev), &tr); + vecDist = (tr.vecEndPos - pev->origin); + flLeftSide = vecDist.Length(); + + // turn right if more clearance on right side + if ( flRightSide > flLeftSide ) + { + pev->avelocity.y = -AFLOCK_TURN_RATE; + m_fTurning = TRUE; + } + // default to left turn :) + else if ( flLeftSide > flRightSide ) + { + pev->avelocity.y = AFLOCK_TURN_RATE; + m_fTurning = TRUE; + } + else + { + // equidistant. Pick randomly between left and right. + m_fTurning = TRUE; + + if ( RANDOM_LONG( 0, 1 ) == 0 ) + { + pev->avelocity.y = AFLOCK_TURN_RATE; + } + else + { + pev->avelocity.y = -AFLOCK_TURN_RATE; + } + } + } + SpreadFlock( ); + + pev->velocity = gpGlobals->v_forward * pev->speed; + + // check and make sure we aren't about to plow into the ground, don't let it happen + UTIL_TraceLine(pev->origin, pev->origin - gpGlobals->v_up * 16, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0 && pev->velocity.z < 0 ) + pev->velocity.z = 0; + + // maybe it did, though. + if ( FBitSet (pev->flags, FL_ONGROUND) ) + { + UTIL_SetOrigin (pev, pev->origin + Vector ( 0 , 0 , 1 ) ); + pev->velocity.z = 0; + } + + if ( m_flFlockNextSoundTime < gpGlobals->time ) + { + MakeSound(); + m_flFlockNextSoundTime = gpGlobals->time + RANDOM_FLOAT( 1, 3 ); + } + + BoidAdvanceFrame( ); + + return; +} + +//========================================================= +// follower boids execute this code when flocking +//========================================================= +void CFlockingFlyer :: FlockFollowerThink( void ) +{ + TraceResult tr; + Vector vecDist; + Vector vecDir; + Vector vecDirToLeader; + float flDistToLeader; + + pev->nextthink = gpGlobals->time + 0.1; + + if ( IsLeader() || !InSquad() ) + { + // the leader has been killed and this flyer suddenly finds himself the leader. + SetThink ( &CFlockingFlyer::FlockLeaderThink ); + return; + } + + vecDirToLeader = ( m_pSquadLeader->pev->origin - pev->origin ); + flDistToLeader = vecDirToLeader.Length(); + + // match heading with leader + pev->angles = m_pSquadLeader->pev->angles; + + // + // We can see the leader, so try to catch up to it + // + if ( FInViewCone ( m_pSquadLeader ) ) + { + // if we're too far away, speed up + if ( flDistToLeader > AFLOCK_TOO_FAR ) + { + m_flGoalSpeed = m_pSquadLeader->pev->velocity.Length() * 1.5; + } + + // if we're too close, slow down + else if ( flDistToLeader < AFLOCK_TOO_CLOSE ) + { + m_flGoalSpeed = m_pSquadLeader->pev->velocity.Length() * 0.5; + } + } + else + { + // wait up! the leader isn't out in front, so we slow down to let him pass + m_flGoalSpeed = m_pSquadLeader->pev->velocity.Length() * 0.5; + } + + SpreadFlock2(); + + pev->speed = pev->velocity.Length(); + pev->velocity = pev->velocity.Normalize(); + + // if we are too far from leader, average a vector towards it into our current velocity + if ( flDistToLeader > AFLOCK_TOO_FAR ) + { + vecDirToLeader = vecDirToLeader.Normalize(); + pev->velocity = (pev->velocity + vecDirToLeader) * 0.5; + } + + // clamp speeds and handle acceleration + if ( m_flGoalSpeed > AFLOCK_FLY_SPEED * 2 ) + { + m_flGoalSpeed = AFLOCK_FLY_SPEED * 2; + } + + if ( pev->speed < m_flGoalSpeed ) + { + pev->speed += AFLOCK_ACCELERATE; + } + else if ( pev->speed > m_flGoalSpeed ) + { + pev->speed -= AFLOCK_ACCELERATE; + } + + pev->velocity = pev->velocity * pev->speed; + + BoidAdvanceFrame( ); +} + +/* + // Is this boid's course blocked? + if ( FBoidPathBlocked (pev) ) + { + // course is still blocked from last time. Just keep flying along adjusted + // velocity + if ( m_fCourseAdjust ) + { + pev->velocity = m_vecAdjustedVelocity * pev->speed; + return; + } + else // set course adjust flag and calculate adjusted velocity + { + m_fCourseAdjust = TRUE; + + // use VELOCITY, not angles, not all boids point the direction they are flying + //vecDir = UTIL_VecToAngles( pev->velocity ); + //UTIL_MakeVectors ( vecDir ); + + UTIL_MakeVectors ( pev->angles ); + + // measure clearance on left and right to pick the best dir to turn + UTIL_TraceLine(pev->origin, pev->origin + gpGlobals->v_right * AFLOCK_CHECK_DIST, ignore_monsters, ENT(pev), &tr); + vecDist = (tr.vecEndPos - pev->origin); + flRightSide = vecDist.Length(); + + UTIL_TraceLine(pev->origin, pev->origin - gpGlobals->v_right * AFLOCK_CHECK_DIST, ignore_monsters, ENT(pev), &tr); + vecDist = (tr.vecEndPos - pev->origin); + flLeftSide = vecDist.Length(); + + // slide right if more clearance on right side + if ( flRightSide > flLeftSide ) + { + m_vecAdjustedVelocity = gpGlobals->v_right; + } + // else slide left + else + { + m_vecAdjustedVelocity = gpGlobals->v_right * -1; + } + } + return; + } + + // if we make it this far, boids path is CLEAR! + m_fCourseAdjust = FALSE; +*/ + + +//========================================================= +// +// SquadUnlink(), Unlink the squad pointers. +// +//========================================================= +void CFlockingFlyer :: SquadUnlink( void ) +{ + m_pSquadLeader = NULL; + m_pSquadNext = NULL; +} + +//========================================================= +// +// SquadAdd(), add pAdd to my squad +// +//========================================================= +void CFlockingFlyer :: SquadAdd( CFlockingFlyer *pAdd ) +{ + ASSERT( pAdd!=NULL ); + ASSERT( !pAdd->InSquad() ); + ASSERT( this->IsLeader() ); + + pAdd->m_pSquadNext = m_pSquadNext; + m_pSquadNext = pAdd; + pAdd->m_pSquadLeader = this; +} +//========================================================= +// +// SquadRemove(), remove pRemove from my squad. +// If I am pRemove, promote m_pSquadNext to leader +// +//========================================================= +void CFlockingFlyer :: SquadRemove( CFlockingFlyer *pRemove ) +{ + ASSERT( pRemove!=NULL ); + ASSERT( this->IsLeader() ); + ASSERT( pRemove->m_pSquadLeader == this ); + + if ( SquadCount() > 2 ) + { + // Removing the leader, promote m_pSquadNext to leader + if ( pRemove == this ) + { + CFlockingFlyer *pLeader = m_pSquadNext; + + // copy the enemy LKP to the new leader + pLeader->m_vecEnemyLKP = m_vecEnemyLKP; + + if ( pLeader ) + { + CFlockingFlyer *pList = pLeader; + + while ( pList ) + { + pList->m_pSquadLeader = pLeader; + pList = pList->m_pSquadNext; + } + + } + SquadUnlink(); + } + else // removing a node + { + CFlockingFlyer *pList = this; + + // Find the node before pRemove + while ( pList->m_pSquadNext != pRemove ) + { + // assert to test valid list construction + ASSERT( pList->m_pSquadNext != NULL ); + pList = pList->m_pSquadNext; + } + // List validity + ASSERT( pList->m_pSquadNext == pRemove ); + + // Relink without pRemove + pList->m_pSquadNext = pRemove->m_pSquadNext; + + // Unlink pRemove + pRemove->SquadUnlink(); + } + } + else + SquadDisband(); +} +//========================================================= +// +// SquadCount(), return the number of members of this squad +// callable from leaders & followers +// +//========================================================= +int CFlockingFlyer :: SquadCount( void ) +{ + CFlockingFlyer *pList = m_pSquadLeader; + int squadCount = 0; + while ( pList ) + { + squadCount++; + pList = pList->m_pSquadNext; + } + + return squadCount; +} + +//========================================================= +// +// SquadDisband(), Unlink all squad members +// +//========================================================= +void CFlockingFlyer :: SquadDisband( void ) +{ + CFlockingFlyer *pList = m_pSquadLeader; + CFlockingFlyer *pNext; + + while ( pList ) + { + pNext = pList->m_pSquadNext; + pList->SquadUnlink(); + pList = pNext; + } +} diff --git a/dlls/agrunt.cpp b/dlls/agrunt.cpp new file mode 100644 index 0000000..411e718 --- /dev/null +++ b/dlls/agrunt.cpp @@ -0,0 +1,1186 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Agrunt - Dominant, warlike alien grunt monster +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "squadmonster.h" +#include "weapons.h" +#include "soundent.h" +#include "hornet.h" + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_AGRUNT_SUPPRESS = LAST_COMMON_SCHEDULE + 1, + SCHED_AGRUNT_THREAT_DISPLAY, +}; + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_AGRUNT_SETUP_HIDE_ATTACK = LAST_COMMON_TASK + 1, + TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE, +}; + +int iAgruntMuzzleFlash; + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define AGRUNT_AE_HORNET1 ( 1 ) +#define AGRUNT_AE_HORNET2 ( 2 ) +#define AGRUNT_AE_HORNET3 ( 3 ) +#define AGRUNT_AE_HORNET4 ( 4 ) +#define AGRUNT_AE_HORNET5 ( 5 ) +// some events are set up in the QC file that aren't recognized by the code yet. +#define AGRUNT_AE_PUNCH ( 6 ) +#define AGRUNT_AE_BITE ( 7 ) + +#define AGRUNT_AE_LEFT_FOOT ( 10 ) +#define AGRUNT_AE_RIGHT_FOOT ( 11 ) + +#define AGRUNT_AE_LEFT_PUNCH ( 12 ) +#define AGRUNT_AE_RIGHT_PUNCH ( 13 ) + + + +#define AGRUNT_MELEE_DIST 100 + +class CAGrunt : public CSquadMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed ( void ); + int Classify ( void ); + int ISoundMask ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector( -32, -32, 0 ); + pev->absmax = pev->origin + Vector( 32, 32, 85 ); + } + + Schedule_t* GetSchedule ( void ); + Schedule_t* GetScheduleOfType ( int Type ); + BOOL FCanCheckAttacks ( void ); + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + void StartTask ( Task_t *pTask ); + void AlertSound( void ); + void DeathSound ( void ); + void PainSound ( void ); + void AttackSound ( void ); + void PrescheduleThink ( void ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + int IRelationship( CBaseEntity *pTarget ); + void StopTalking ( void ); + BOOL ShouldSpeak( void ); + CUSTOM_SCHEDULES; + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + static const char *pAttackHitSounds[]; + static const char *pAttackMissSounds[]; + static const char *pAttackSounds[]; + static const char *pDieSounds[]; + static const char *pPainSounds[]; + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + + BOOL m_fCanHornetAttack; + float m_flNextHornetAttackCheck; + + float m_flNextPainTime; + + // three hacky fields for speech stuff. These don't really need to be saved. + float m_flNextSpeakTime; + float m_flNextWordTime; + int m_iLastWord; +}; +LINK_ENTITY_TO_CLASS( monster_alien_grunt, CAGrunt ); + +TYPEDESCRIPTION CAGrunt::m_SaveData[] = +{ + DEFINE_FIELD( CAGrunt, m_fCanHornetAttack, FIELD_BOOLEAN ), + DEFINE_FIELD( CAGrunt, m_flNextHornetAttackCheck, FIELD_TIME ), + DEFINE_FIELD( CAGrunt, m_flNextPainTime, FIELD_TIME ), + DEFINE_FIELD( CAGrunt, m_flNextSpeakTime, FIELD_TIME ), + DEFINE_FIELD( CAGrunt, m_flNextWordTime, FIELD_TIME ), + DEFINE_FIELD( CAGrunt, m_iLastWord, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CAGrunt, CSquadMonster ); + +const char *CAGrunt::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CAGrunt::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CAGrunt::pAttackSounds[] = +{ + "agrunt/ag_attack1.wav", + "agrunt/ag_attack2.wav", + "agrunt/ag_attack3.wav", +}; + +const char *CAGrunt::pDieSounds[] = +{ + "agrunt/ag_die1.wav", + "agrunt/ag_die4.wav", + "agrunt/ag_die5.wav", +}; + +const char *CAGrunt::pPainSounds[] = +{ + "agrunt/ag_pain1.wav", + "agrunt/ag_pain2.wav", + "agrunt/ag_pain3.wav", + "agrunt/ag_pain4.wav", + "agrunt/ag_pain5.wav", +}; + +const char *CAGrunt::pIdleSounds[] = +{ + "agrunt/ag_idle1.wav", + "agrunt/ag_idle2.wav", + "agrunt/ag_idle3.wav", + "agrunt/ag_idle4.wav", +}; + +const char *CAGrunt::pAlertSounds[] = +{ + "agrunt/ag_alert1.wav", + "agrunt/ag_alert3.wav", + "agrunt/ag_alert4.wav", + "agrunt/ag_alert5.wav", +}; + +//========================================================= +// IRelationship - overridden because Human Grunts are +// Alien Grunt's nemesis. +//========================================================= +int CAGrunt::IRelationship ( CBaseEntity *pTarget ) +{ + if ( FClassnameIs( pTarget->pev, "monster_human_grunt" ) ) + { + return R_NM; + } + + return CSquadMonster :: IRelationship( pTarget ); +} + +//========================================================= +// ISoundMask +//========================================================= +int CAGrunt :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER | + bits_SOUND_DANGER; +} + +//========================================================= +// TraceAttack +//========================================================= +void CAGrunt :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( ptr->iHitgroup == 10 && (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_CLUB))) + { + // hit armor + if ( pev->dmgtime != gpGlobals->time || (RANDOM_LONG(0,10) < 1) ) + { + UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT( 1, 2) ); + pev->dmgtime = gpGlobals->time; + } + + if ( RANDOM_LONG( 0, 1 ) == 0 ) + { + Vector vecTracerDir = vecDir; + + vecTracerDir.x += RANDOM_FLOAT( -0.3, 0.3 ); + vecTracerDir.y += RANDOM_FLOAT( -0.3, 0.3 ); + vecTracerDir.z += RANDOM_FLOAT( -0.3, 0.3 ); + + vecTracerDir = vecTracerDir * -512; + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, ptr->vecEndPos ); + WRITE_BYTE( TE_TRACER ); + WRITE_COORD( ptr->vecEndPos.x ); + WRITE_COORD( ptr->vecEndPos.y ); + WRITE_COORD( ptr->vecEndPos.z ); + + WRITE_COORD( vecTracerDir.x ); + WRITE_COORD( vecTracerDir.y ); + WRITE_COORD( vecTracerDir.z ); + MESSAGE_END(); + } + + flDamage -= 20; + if (flDamage <= 0) + flDamage = 0.1;// don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated + } + else + { + SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage);// a little surface blood. + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + } + + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); +} + +//========================================================= +// StopTalking - won't speak again for 10-20 seconds. +//========================================================= +void CAGrunt::StopTalking( void ) +{ + m_flNextWordTime = m_flNextSpeakTime = gpGlobals->time + 10 + RANDOM_LONG(0, 10); +} + +//========================================================= +// ShouldSpeak - Should this agrunt be talking? +//========================================================= +BOOL CAGrunt::ShouldSpeak( void ) +{ + if ( m_flNextSpeakTime > gpGlobals->time ) + { + // my time to talk is still in the future. + return FALSE; + } + + if ( pev->spawnflags & SF_MONSTER_GAG ) + { + if ( m_MonsterState != MONSTERSTATE_COMBAT ) + { + // if gagged, don't talk outside of combat. + // if not going to talk because of this, put the talk time + // into the future a bit, so we don't talk immediately after + // going into combat + m_flNextSpeakTime = gpGlobals->time + 3; + return FALSE; + } + } + + return TRUE; +} + +//========================================================= +// PrescheduleThink +//========================================================= +void CAGrunt :: PrescheduleThink ( void ) +{ + if ( ShouldSpeak() ) + { + if ( m_flNextWordTime < gpGlobals->time ) + { + int num = -1; + + do + { + num = RANDOM_LONG(0,ARRAYSIZE(pIdleSounds)-1); + } while( num == m_iLastWord ); + + m_iLastWord = num; + + // play a new sound + EMIT_SOUND ( ENT(pev), CHAN_VOICE, pIdleSounds[ num ], 1.0, ATTN_NORM ); + + // is this word our last? + if ( RANDOM_LONG( 1, 10 ) <= 1 ) + { + // stop talking. + StopTalking(); + } + else + { + m_flNextWordTime = gpGlobals->time + RANDOM_FLOAT( 0.5, 1 ); + } + } + } +} + +//========================================================= +// DieSound +//========================================================= +void CAGrunt :: DeathSound ( void ) +{ + StopTalking(); + + EMIT_SOUND ( ENT(pev), CHAN_VOICE, pDieSounds[RANDOM_LONG(0,ARRAYSIZE(pDieSounds)-1)], 1.0, ATTN_NORM ); +} + +//========================================================= +// AlertSound +//========================================================= +void CAGrunt :: AlertSound ( void ) +{ + StopTalking(); + + EMIT_SOUND ( ENT(pev), CHAN_VOICE, pAlertSounds[RANDOM_LONG(0,ARRAYSIZE(pAlertSounds)-1)], 1.0, ATTN_NORM ); +} + +//========================================================= +// AttackSound +//========================================================= +void CAGrunt :: AttackSound ( void ) +{ + StopTalking(); + + EMIT_SOUND ( ENT(pev), CHAN_VOICE, pAttackSounds[RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1)], 1.0, ATTN_NORM ); +} + +//========================================================= +// PainSound +//========================================================= +void CAGrunt :: PainSound ( void ) +{ + if ( m_flNextPainTime > gpGlobals->time ) + { + return; + } + + m_flNextPainTime = gpGlobals->time + 0.6; + + StopTalking(); + + EMIT_SOUND ( ENT(pev), CHAN_VOICE, pPainSounds[RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1)], 1.0, ATTN_NORM ); +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CAGrunt :: Classify ( void ) +{ + return CLASS_ALIEN_MILITARY; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CAGrunt :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 110; + break; + default: ys = 100; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CAGrunt :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case AGRUNT_AE_HORNET1: + case AGRUNT_AE_HORNET2: + case AGRUNT_AE_HORNET3: + case AGRUNT_AE_HORNET4: + case AGRUNT_AE_HORNET5: + { + // m_vecEnemyLKP should be center of enemy body + Vector vecArmPos, vecArmDir; + Vector vecDirToEnemy; + Vector angDir; + + if (HasConditions( bits_COND_SEE_ENEMY)) + { + vecDirToEnemy = ( ( m_vecEnemyLKP ) - pev->origin ); + angDir = UTIL_VecToAngles( vecDirToEnemy ); + vecDirToEnemy = vecDirToEnemy.Normalize(); + } + else + { + angDir = pev->angles; + UTIL_MakeAimVectors( angDir ); + vecDirToEnemy = gpGlobals->v_forward; + } + + pev->effects = EF_MUZZLEFLASH; + + // make angles +-180 + if (angDir.x > 180) + { + angDir.x = angDir.x - 360; + } + + SetBlending( 0, angDir.x ); + GetAttachment( 0, vecArmPos, vecArmDir ); + + vecArmPos = vecArmPos + vecDirToEnemy * 32; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecArmPos ); + WRITE_BYTE( TE_SPRITE ); + WRITE_COORD( vecArmPos.x ); // pos + WRITE_COORD( vecArmPos.y ); + WRITE_COORD( vecArmPos.z ); + WRITE_SHORT( iAgruntMuzzleFlash ); // model + WRITE_BYTE( 6 ); // size * 10 + WRITE_BYTE( 128 ); // brightness + MESSAGE_END(); + + CBaseEntity *pHornet = CBaseEntity::Create( "hornet", vecArmPos, UTIL_VecToAngles( vecDirToEnemy ), edict() ); + UTIL_MakeVectors ( pHornet->pev->angles ); + pHornet->pev->velocity = gpGlobals->v_forward * 300; + + + + switch ( RANDOM_LONG ( 0 , 2 ) ) + { + case 0: EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, "agrunt/ag_fire1.wav", 1.0, ATTN_NORM, 0, 100 ); break; + case 1: EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, "agrunt/ag_fire2.wav", 1.0, ATTN_NORM, 0, 100 ); break; + case 2: EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, "agrunt/ag_fire3.wav", 1.0, ATTN_NORM, 0, 100 ); break; + } + + CBaseMonster *pHornetMonster = pHornet->MyMonsterPointer(); + + if ( pHornetMonster ) + { + pHornetMonster->m_hEnemy = m_hEnemy; + } + } + break; + + case AGRUNT_AE_LEFT_FOOT: + switch (RANDOM_LONG(0,1)) + { + // left foot + case 0: EMIT_SOUND_DYN ( ENT(pev), CHAN_BODY, "player/pl_ladder2.wav", 1, ATTN_NORM, 0, 70 ); break; + case 1: EMIT_SOUND_DYN ( ENT(pev), CHAN_BODY, "player/pl_ladder4.wav", 1, ATTN_NORM, 0, 70 ); break; + } + break; + case AGRUNT_AE_RIGHT_FOOT: + // right foot + switch (RANDOM_LONG(0,1)) + { + case 0: EMIT_SOUND_DYN ( ENT(pev), CHAN_BODY, "player/pl_ladder1.wav", 1, ATTN_NORM, 0, 70 ); break; + case 1: EMIT_SOUND_DYN ( ENT(pev), CHAN_BODY, "player/pl_ladder3.wav", 1, ATTN_NORM, 0 ,70); break; + } + break; + + case AGRUNT_AE_LEFT_PUNCH: + { + CBaseEntity *pHurt = CheckTraceHullAttack( AGRUNT_MELEE_DIST, gSkillData.agruntDmgPunch, DMG_CLUB ); + + if ( pHurt ) + { + pHurt->pev->punchangle.y = -25; + pHurt->pev->punchangle.x = 8; + + // OK to use gpGlobals without calling MakeVectors, cause CheckTraceHullAttack called it above. + if ( pHurt->IsPlayer() ) + { + // this is a player. Knock him around. + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_right * 250; + } + + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + + Vector vecArmPos, vecArmAng; + GetAttachment( 0, vecArmPos, vecArmAng ); + SpawnBlood(vecArmPos, pHurt->BloodColor(), 25);// a little surface blood. + } + else + { + // Play a random attack miss sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + } + } + break; + + case AGRUNT_AE_RIGHT_PUNCH: + { + CBaseEntity *pHurt = CheckTraceHullAttack( AGRUNT_MELEE_DIST, gSkillData.agruntDmgPunch, DMG_CLUB ); + + if ( pHurt ) + { + pHurt->pev->punchangle.y = 25; + pHurt->pev->punchangle.x = 8; + + // OK to use gpGlobals without calling MakeVectors, cause CheckTraceHullAttack called it above. + if ( pHurt->IsPlayer() ) + { + // this is a player. Knock him around. + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_right * -250; + } + + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + + Vector vecArmPos, vecArmAng; + GetAttachment( 0, vecArmPos, vecArmAng ); + SpawnBlood(vecArmPos, pHurt->BloodColor(), 25);// a little surface blood. + } + else + { + // Play a random attack miss sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + } + } + break; + + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CAGrunt :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/agrunt.mdl"); + UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 64)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->effects = 0; + pev->health = gSkillData.agruntHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_afCapability = 0; + m_afCapability |= bits_CAP_SQUAD; + + m_HackedGunPos = Vector( 24, 64, 48 ); + + m_flNextSpeakTime = m_flNextWordTime = gpGlobals->time + 10 + RANDOM_LONG(0, 10); + + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CAGrunt :: Precache() +{ + int i; + + PRECACHE_MODEL("models/agrunt.mdl"); + + for ( i = 0; i < ARRAYSIZE( pAttackHitSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackHitSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackMissSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackMissSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pIdleSounds ); i++ ) + PRECACHE_SOUND((char *)pIdleSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pDieSounds ); i++ ) + PRECACHE_SOUND((char *)pDieSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pPainSounds ); i++ ) + PRECACHE_SOUND((char *)pPainSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAlertSounds ); i++ ) + PRECACHE_SOUND((char *)pAlertSounds[i]); + + + PRECACHE_SOUND( "hassault/hw_shoot1.wav" ); + + iAgruntMuzzleFlash = PRECACHE_MODEL( "sprites/muz4.spr" ); + + UTIL_PrecacheOther( "hornet" ); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +//========================================================= +// Fail Schedule +//========================================================= +Task_t tlAGruntFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slAGruntFail[] = +{ + { + tlAGruntFail, + ARRAYSIZE ( tlAGruntFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1, + 0, + "AGrunt Fail" + }, +}; + +//========================================================= +// Combat Fail Schedule +//========================================================= +Task_t tlAGruntCombatFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slAGruntCombatFail[] = +{ + { + tlAGruntCombatFail, + ARRAYSIZE ( tlAGruntCombatFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1, + 0, + "AGrunt Combat Fail" + }, +}; + +//========================================================= +// Standoff schedule. Used in combat when a monster is +// hiding in cover or the enemy has moved out of sight. +// Should we look around in this schedule? +//========================================================= +Task_t tlAGruntStandoff[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, +}; + +Schedule_t slAGruntStandoff[] = +{ + { + tlAGruntStandoff, + ARRAYSIZE ( tlAGruntStandoff ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Agrunt Standoff" + } +}; + +//========================================================= +// Suppress +//========================================================= +Task_t tlAGruntSuppressHornet[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slAGruntSuppress[] = +{ + { + tlAGruntSuppressHornet, + ARRAYSIZE ( tlAGruntSuppressHornet ), + 0, + 0, + "AGrunt Suppress Hornet", + }, +}; + +//========================================================= +// primary range attacks +//========================================================= +Task_t tlAGruntRangeAttack1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slAGruntRangeAttack1[] = +{ + { + tlAGruntRangeAttack1, + ARRAYSIZE ( tlAGruntRangeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE, + + 0, + "AGrunt Range Attack1" + }, +}; + + +Task_t tlAGruntHiddenRangeAttack1[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_STANDOFF }, + { TASK_AGRUNT_SETUP_HIDE_ATTACK, 0 }, + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, 0 }, + { TASK_RANGE_ATTACK1_NOTURN, (float)0 }, +}; + +Schedule_t slAGruntHiddenRangeAttack[] = +{ + { + tlAGruntHiddenRangeAttack1, + ARRAYSIZE ( tlAGruntHiddenRangeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "AGrunt Hidden Range Attack1" + }, +}; + +//========================================================= +// Take cover from enemy! Tries lateral cover before node +// cover! +//========================================================= +Task_t tlAGruntTakeCoverFromEnemy[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, +}; + +Schedule_t slAGruntTakeCoverFromEnemy[] = +{ + { + tlAGruntTakeCoverFromEnemy, + ARRAYSIZE ( tlAGruntTakeCoverFromEnemy ), + bits_COND_NEW_ENEMY, + 0, + "AGruntTakeCoverFromEnemy" + }, +}; + +//========================================================= +// Victory dance! +//========================================================= +Task_t tlAGruntVictoryDance[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_AGRUNT_THREAT_DISPLAY }, + { TASK_WAIT, (float)0.2 }, + { TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_CROUCH }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_STAND }, + { TASK_PLAY_SEQUENCE, (float)ACT_THREAT_DISPLAY }, + { TASK_PLAY_SEQUENCE, (float)ACT_CROUCH }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_STAND }, +}; + +Schedule_t slAGruntVictoryDance[] = +{ + { + tlAGruntVictoryDance, + ARRAYSIZE ( tlAGruntVictoryDance ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "AGruntVictoryDance" + }, +}; + +//========================================================= +//========================================================= +Task_t tlAGruntThreatDisplay[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_THREAT_DISPLAY }, +}; + +Schedule_t slAGruntThreatDisplay[] = +{ + { + tlAGruntThreatDisplay, + ARRAYSIZE ( tlAGruntThreatDisplay ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + + bits_SOUND_PLAYER | + bits_SOUND_COMBAT | + bits_SOUND_WORLD, + "AGruntThreatDisplay" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CAGrunt ) +{ + slAGruntFail, + slAGruntCombatFail, + slAGruntStandoff, + slAGruntSuppress, + slAGruntRangeAttack1, + slAGruntHiddenRangeAttack, + slAGruntTakeCoverFromEnemy, + slAGruntVictoryDance, + slAGruntThreatDisplay, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CAGrunt, CSquadMonster ); + +//========================================================= +// FCanCheckAttacks - this is overridden for alien grunts +// because they can use their smart weapons against unseen +// enemies. Base class doesn't attack anyone it can't see. +//========================================================= +BOOL CAGrunt :: FCanCheckAttacks ( void ) +{ + if ( !HasConditions( bits_COND_ENEMY_TOOFAR ) ) + { + return TRUE; + } + else + { + return FALSE; + } +} + +//========================================================= +// CheckMeleeAttack1 - alien grunts zap the crap out of +// any enemy that gets too close. +//========================================================= +BOOL CAGrunt :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + if ( HasConditions ( bits_COND_SEE_ENEMY ) && flDist <= AGRUNT_MELEE_DIST && flDot >= 0.6 && m_hEnemy != NULL ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack1 +// +// !!!LATER - we may want to load balance this. Several +// tracelines are done, so we may not want to do this every +// server frame. Definitely not while firing. +//========================================================= +BOOL CAGrunt :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( gpGlobals->time < m_flNextHornetAttackCheck ) + { + return m_fCanHornetAttack; + } + + if ( HasConditions( bits_COND_SEE_ENEMY ) && flDist >= AGRUNT_MELEE_DIST && flDist <= 1024 && flDot >= 0.5 && NoFriendlyFire() ) + { + TraceResult tr; + Vector vecArmPos, vecArmDir; + + // verify that a shot fired from the gun will hit the enemy before the world. + // !!!LATER - we may wish to do something different for projectile weapons as opposed to instant-hit + UTIL_MakeVectors( pev->angles ); + GetAttachment( 0, vecArmPos, vecArmDir ); +// UTIL_TraceLine( vecArmPos, vecArmPos + gpGlobals->v_forward * 256, ignore_monsters, ENT(pev), &tr); + UTIL_TraceLine( vecArmPos, m_hEnemy->BodyTarget(vecArmPos), dont_ignore_monsters, ENT(pev), &tr); + + if ( tr.flFraction == 1.0 || tr.pHit == m_hEnemy->edict() ) + { + m_flNextHornetAttackCheck = gpGlobals->time + RANDOM_FLOAT( 2, 5 ); + m_fCanHornetAttack = TRUE; + return m_fCanHornetAttack; + } + } + + m_flNextHornetAttackCheck = gpGlobals->time + 0.2;// don't check for half second if this check wasn't successful + m_fCanHornetAttack = FALSE; + return m_fCanHornetAttack; +} + +//========================================================= +// StartTask +//========================================================= +void CAGrunt :: StartTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE: + { + UTIL_MakeVectors( pev->angles ); + if ( BuildRoute ( m_vecEnemyLKP - gpGlobals->v_forward * 50, bits_MF_TO_LOCATION, NULL ) ) + { + TaskComplete(); + } + else + { + ALERT ( at_aiconsole, "AGruntGetPathToEnemyCorpse failed!!\n" ); + TaskFail(); + } + } + break; + + case TASK_AGRUNT_SETUP_HIDE_ATTACK: + // alien grunt shoots hornets back out into the open from a concealed location. + // try to find a spot to throw that gives the smart weapon a good chance of finding the enemy. + // ideally, this spot is along a line that is perpendicular to a line drawn from the agrunt to the enemy. + + CBaseMonster *pEnemyMonsterPtr; + + pEnemyMonsterPtr = m_hEnemy->MyMonsterPointer(); + + if ( pEnemyMonsterPtr ) + { + Vector vecCenter; + TraceResult tr; + BOOL fSkip; + + fSkip = FALSE; + vecCenter = Center(); + + UTIL_VecToAngles( m_vecEnemyLKP - pev->origin ); + + UTIL_TraceLine( Center() + gpGlobals->v_forward * 128, m_vecEnemyLKP, ignore_monsters, ENT(pev), &tr); + if ( tr.flFraction == 1.0 ) + { + MakeIdealYaw ( pev->origin + gpGlobals->v_right * 128 ); + fSkip = TRUE; + TaskComplete(); + } + + if ( !fSkip ) + { + UTIL_TraceLine( Center() - gpGlobals->v_forward * 128, m_vecEnemyLKP, ignore_monsters, ENT(pev), &tr); + if ( tr.flFraction == 1.0 ) + { + MakeIdealYaw ( pev->origin - gpGlobals->v_right * 128 ); + fSkip = TRUE; + TaskComplete(); + } + } + + if ( !fSkip ) + { + UTIL_TraceLine( Center() + gpGlobals->v_forward * 256, m_vecEnemyLKP, ignore_monsters, ENT(pev), &tr); + if ( tr.flFraction == 1.0 ) + { + MakeIdealYaw ( pev->origin + gpGlobals->v_right * 256 ); + fSkip = TRUE; + TaskComplete(); + } + } + + if ( !fSkip ) + { + UTIL_TraceLine( Center() - gpGlobals->v_forward * 256, m_vecEnemyLKP, ignore_monsters, ENT(pev), &tr); + if ( tr.flFraction == 1.0 ) + { + MakeIdealYaw ( pev->origin - gpGlobals->v_right * 256 ); + fSkip = TRUE; + TaskComplete(); + } + } + + if ( !fSkip ) + { + TaskFail(); + } + } + else + { + ALERT ( at_aiconsole, "AGRunt - no enemy monster ptr!!!\n" ); + TaskFail(); + } + break; + + default: + CSquadMonster :: StartTask ( pTask ); + break; + } +} + +//========================================================= +// GetSchedule - Decides which type of schedule best suits +// the monster's current state and conditions. Then calls +// monster's member function to get a pointer to a schedule +// of the proper type. +//========================================================= +Schedule_t *CAGrunt :: GetSchedule ( void ) +{ + if ( HasConditions(bits_COND_HEAR_SOUND) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) + { + // dangerous sound nearby! + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + } + + switch ( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + return GetScheduleOfType( SCHED_WAKE_ANGRY ); + } + + // zap player! + if ( HasConditions ( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + AttackSound();// this is a total hack. Should be parto f the schedule + return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); + } + + if ( HasConditions ( bits_COND_HEAVY_DAMAGE ) ) + { + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + + // can attack + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) && OccupySlot ( bits_SLOTS_AGRUNT_HORNET ) ) + { + return GetScheduleOfType ( SCHED_RANGE_ATTACK1 ); + } + + if ( OccupySlot ( bits_SLOT_AGRUNT_CHASE ) ) + { + return GetScheduleOfType ( SCHED_CHASE_ENEMY ); + } + + return GetScheduleOfType ( SCHED_STANDOFF ); + } + } + + return CSquadMonster :: GetSchedule(); +} + +//========================================================= +//========================================================= +Schedule_t* CAGrunt :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_TAKE_COVER_FROM_ENEMY: + return &slAGruntTakeCoverFromEnemy[ 0 ]; + break; + + case SCHED_RANGE_ATTACK1: + if ( HasConditions( bits_COND_SEE_ENEMY ) ) + { + //normal attack + return &slAGruntRangeAttack1[ 0 ]; + } + else + { + // attack an unseen enemy + // return &slAGruntHiddenRangeAttack[ 0 ]; + return &slAGruntRangeAttack1[ 0 ]; + } + break; + + case SCHED_AGRUNT_THREAT_DISPLAY: + return &slAGruntThreatDisplay[ 0 ]; + break; + + case SCHED_AGRUNT_SUPPRESS: + return &slAGruntSuppress[ 0 ]; + break; + + case SCHED_STANDOFF: + return &slAGruntStandoff[ 0 ]; + break; + + case SCHED_VICTORY_DANCE: + return &slAGruntVictoryDance[ 0 ]; + break; + + case SCHED_FAIL: + // no fail schedule specified, so pick a good generic one. + { + if ( m_hEnemy != NULL ) + { + // I have an enemy + // !!!LATER - what if this enemy is really far away and i'm chasing him? + // this schedule will make me stop, face his last known position for 2 + // seconds, and then try to move again + return &slAGruntCombatFail[ 0 ]; + } + + return &slAGruntFail[ 0 ]; + } + break; + + } + + return CSquadMonster :: GetScheduleOfType( Type ); +} + diff --git a/dlls/airtank.cpp b/dlls/airtank.cpp new file mode 100644 index 0000000..d0cfc02 --- /dev/null +++ b/dlls/airtank.cpp @@ -0,0 +1,118 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" + +class CAirtank : public CGrenade +{ + void Spawn( void ); + void Precache( void ); + void EXPORT TankThink( void ); + void EXPORT TankTouch( CBaseEntity *pOther ); + int BloodColor( void ) { return DONT_BLEED; }; + void Killed( entvars_t *pevAttacker, int iGib ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_state; +}; + + +LINK_ENTITY_TO_CLASS( item_airtank, CAirtank ); +TYPEDESCRIPTION CAirtank::m_SaveData[] = +{ + DEFINE_FIELD( CAirtank, m_state, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CAirtank, CGrenade ); + + +void CAirtank :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/w_oxygen.mdl"); + UTIL_SetSize(pev, Vector( -16, -16, 0), Vector(16, 16, 36)); + UTIL_SetOrigin( pev, pev->origin ); + + SetTouch( &CAirtank::TankTouch ); + SetThink( &CAirtank::TankThink ); + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_YES; + pev->health = 20; + pev->dmg = 50; + m_state = 1; +} + +void CAirtank::Precache( void ) +{ + PRECACHE_MODEL("models/w_oxygen.mdl"); + PRECACHE_SOUND("doors/aliendoor3.wav"); +} + + +void CAirtank :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->owner = ENT( pevAttacker ); + + // UNDONE: this should make a big bubble cloud, not an explosion + + Explode( pev->origin, Vector( 0, 0, -1 ) ); +} + + +void CAirtank::TankThink( void ) +{ + // Fire trigger + m_state = 1; + SUB_UseTargets( this, USE_TOGGLE, 0 ); +} + + +void CAirtank::TankTouch( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + return; + + if (!m_state) + { + // "no oxygen" sound + EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_swim2.wav", 1.0, ATTN_NORM ); + return; + } + + // give player 12 more seconds of air + pOther->pev->air_finished = gpGlobals->time + 12; + + // suit recharge sound + EMIT_SOUND( ENT(pev), CHAN_VOICE, "doors/aliendoor3.wav", 1.0, ATTN_NORM ); + + // recharge airtank in 30 seconds + pev->nextthink = gpGlobals->time + 30; + m_state = 0; + SUB_UseTargets( this, USE_TOGGLE, 1 ); +} diff --git a/dlls/animating.cpp b/dlls/animating.cpp new file mode 100644 index 0000000..caecb32 --- /dev/null +++ b/dlls/animating.cpp @@ -0,0 +1,318 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== monsters.cpp ======================================================== + + Monster-related utility code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "animation.h" +#include "saverestore.h" + +TYPEDESCRIPTION CBaseAnimating::m_SaveData[] = +{ + DEFINE_FIELD( CBaseMonster, m_flFrameRate, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flGroundSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flLastEventCheck, FIELD_TIME ), + DEFINE_FIELD( CBaseMonster, m_fSequenceFinished, FIELD_BOOLEAN ), + DEFINE_FIELD( CBaseMonster, m_fSequenceLoops, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CBaseAnimating, CBaseDelay ); + + +//========================================================= +// StudioFrameAdvance - advance the animation frame up to the current time +// if an flInterval is passed in, only advance animation that number of seconds +//========================================================= +float CBaseAnimating :: StudioFrameAdvance ( float flInterval ) +{ + if (flInterval == 0.0) + { + flInterval = (gpGlobals->time - pev->animtime); + if (flInterval <= 0.001) + { + pev->animtime = gpGlobals->time; + return 0.0; + } + } + if (! pev->animtime) + flInterval = 0.0; + + pev->frame += flInterval * m_flFrameRate * pev->framerate; + pev->animtime = gpGlobals->time; + + if (pev->frame < 0.0 || pev->frame >= 256.0) + { + if (m_fSequenceLoops) + pev->frame -= (int)(pev->frame / 256.0) * 256.0; + else + pev->frame = (pev->frame < 0.0) ? 0 : 255; + m_fSequenceFinished = TRUE; // just in case it wasn't caught in GetEvents + } + + return flInterval; +} + +//========================================================= +// LookupActivity +//========================================================= +int CBaseAnimating :: LookupActivity ( int activity ) +{ + ASSERT( activity != 0 ); + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupActivity( pmodel, pev, activity ); +} + +//========================================================= +// LookupActivityHeaviest +// +// Get activity with highest 'weight' +// +//========================================================= +int CBaseAnimating :: LookupActivityHeaviest ( int activity ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupActivityHeaviest( pmodel, pev, activity ); +} + +//========================================================= +//========================================================= +int CBaseAnimating :: LookupSequence ( const char *label ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupSequence( pmodel, label ); +} + + +//========================================================= +//========================================================= +void CBaseAnimating :: ResetSequenceInfo ( ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + GetSequenceInfo( pmodel, pev, &m_flFrameRate, &m_flGroundSpeed ); + m_fSequenceLoops = ((GetSequenceFlags() & STUDIO_LOOPING) != 0); + pev->animtime = gpGlobals->time; + pev->framerate = 1.0; + m_fSequenceFinished = FALSE; + m_flLastEventCheck = gpGlobals->time; +} + + + +//========================================================= +//========================================================= +BOOL CBaseAnimating :: GetSequenceFlags( ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::GetSequenceFlags( pmodel, pev ); +} + +//========================================================= +// DispatchAnimEvents +//========================================================= +void CBaseAnimating :: DispatchAnimEvents ( float flInterval ) +{ + MonsterEvent_t event; + + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + if ( !pmodel ) + { + ALERT( at_aiconsole, "Gibbed monster is thinking!\n" ); + return; + } + + // FIXME: I have to do this or some events get missed, and this is probably causing the problem below + flInterval = 0.1; + + // FIX: this still sometimes hits events twice + float flStart = pev->frame + (m_flLastEventCheck - pev->animtime) * m_flFrameRate * pev->framerate; + float flEnd = pev->frame + flInterval * m_flFrameRate * pev->framerate; + m_flLastEventCheck = pev->animtime + flInterval; + + m_fSequenceFinished = FALSE; + if (flEnd >= 256 || flEnd <= 0.0) + m_fSequenceFinished = TRUE; + + int index = 0; + + while ( (index = GetAnimationEvent( pmodel, pev, &event, flStart, flEnd, index ) ) != 0 ) + { + HandleAnimEvent( &event ); + } +} + + +//========================================================= +//========================================================= +float CBaseAnimating :: SetBoneController ( int iController, float flValue ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return SetController( pmodel, pev, iController, flValue ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: InitBoneControllers ( void ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + SetController( pmodel, pev, 0, 0.0 ); + SetController( pmodel, pev, 1, 0.0 ); + SetController( pmodel, pev, 2, 0.0 ); + SetController( pmodel, pev, 3, 0.0 ); +} + +//========================================================= +//========================================================= +float CBaseAnimating :: SetBlending ( int iBlender, float flValue ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::SetBlending( pmodel, pev, iBlender, flValue ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: GetBonePosition ( int iBone, Vector &origin, Vector &angles ) +{ + GET_BONE_POSITION( ENT(pev), iBone, origin, angles ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: GetAttachment ( int iAttachment, Vector &origin, Vector &angles ) +{ + GET_ATTACHMENT( ENT(pev), iAttachment, origin, angles ); +} + +//========================================================= +//========================================================= +int CBaseAnimating :: FindTransition( int iEndingSequence, int iGoalSequence, int *piDir ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + if (piDir == NULL) + { + int iDir; + int sequence = ::FindTransition( pmodel, iEndingSequence, iGoalSequence, &iDir ); + if (iDir != 1) + return -1; + else + return sequence; + } + + return ::FindTransition( pmodel, iEndingSequence, iGoalSequence, piDir ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: GetAutomovement( Vector &origin, Vector &angles, float flInterval ) +{ + +} + +void CBaseAnimating :: SetBodygroup( int iGroup, int iValue ) +{ + ::SetBodygroup( GET_MODEL_PTR( ENT(pev) ), pev, iGroup, iValue ); +} + +int CBaseAnimating :: GetBodygroup( int iGroup ) +{ + return ::GetBodygroup( GET_MODEL_PTR( ENT(pev) ), pev, iGroup ); +} + + +int CBaseAnimating :: ExtractBbox( int sequence, float *mins, float *maxs ) +{ + return ::ExtractBbox( GET_MODEL_PTR( ENT(pev) ), sequence, mins, maxs ); +} + +//========================================================= +//========================================================= + +void CBaseAnimating :: SetSequenceBox( void ) +{ + Vector mins, maxs; + + // Get sequence bbox + if ( ExtractBbox( pev->sequence, mins, maxs ) ) + { + // expand box for rotation + // find min / max for rotations + float yaw = pev->angles.y * (M_PI / 180.0); + + Vector xvector, yvector; + xvector.x = cos(yaw); + xvector.y = sin(yaw); + yvector.x = -sin(yaw); + yvector.y = cos(yaw); + Vector bounds[2]; + + bounds[0] = mins; + bounds[1] = maxs; + + Vector rmin( 9999, 9999, 9999 ); + Vector rmax( -9999, -9999, -9999 ); + Vector base, transformed; + + for (int i = 0; i <= 1; i++ ) + { + base.x = bounds[i].x; + for ( int j = 0; j <= 1; j++ ) + { + base.y = bounds[j].y; + for ( int k = 0; k <= 1; k++ ) + { + base.z = bounds[k].z; + + // transform the point + transformed.x = xvector.x*base.x + yvector.x*base.y; + transformed.y = xvector.y*base.x + yvector.y*base.y; + transformed.z = base.z; + + if (transformed.x < rmin.x) + rmin.x = transformed.x; + if (transformed.x > rmax.x) + rmax.x = transformed.x; + if (transformed.y < rmin.y) + rmin.y = transformed.y; + if (transformed.y > rmax.y) + rmax.y = transformed.y; + if (transformed.z < rmin.z) + rmin.z = transformed.z; + if (transformed.z > rmax.z) + rmax.z = transformed.z; + } + } + } + rmin.z = 0; + rmax.z = rmin.z + 1; + UTIL_SetSize( pev, rmin, rmax ); + } +} + diff --git a/dlls/animation.cpp b/dlls/animation.cpp new file mode 100644 index 0000000..0a46292 --- /dev/null +++ b/dlls/animation.cpp @@ -0,0 +1,528 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include +#include +#include + +#include "../common/nowin.h" + +typedef int BOOL; +#define TRUE 1 +#define FALSE 0 + +// hack into header files that we can ship +typedef int qboolean; +typedef unsigned char byte; +#include "../utils/common/mathlib.h" +#include "const.h" +#include "progdefs.h" +#include "edict.h" +#include "eiface.h" + +#include "studio.h" + +#ifndef ACTIVITY_H +#include "activity.h" +#endif + +#include "activitymap.h" + +#ifndef ANIMATION_H +#include "animation.h" +#endif + +#ifndef SCRIPTEVENT_H +#include "scriptevent.h" +#endif + +#ifndef ENGINECALLBACK_H +#include "enginecallback.h" +#endif + +extern globalvars_t *gpGlobals; + +#pragma warning( disable : 4244 ) + + + +int ExtractBbox( void *pmodel, int sequence, float *mins, float *maxs ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + mins[0] = pseqdesc[ sequence ].bbmin[0]; + mins[1] = pseqdesc[ sequence ].bbmin[1]; + mins[2] = pseqdesc[ sequence ].bbmin[2]; + + maxs[0] = pseqdesc[ sequence ].bbmax[0]; + maxs[1] = pseqdesc[ sequence ].bbmax[1]; + maxs[2] = pseqdesc[ sequence ].bbmax[2]; + + return 1; +} + + +int LookupActivity( void *pmodel, entvars_t *pev, int activity ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + int weighttotal = 0; + int seq = ACTIVITY_NOT_AVAILABLE; + for (int i = 0; i < pstudiohdr->numseq; i++) + { + if (pseqdesc[i].activity == activity) + { + weighttotal += pseqdesc[i].actweight; + if (!weighttotal || RANDOM_LONG(0,weighttotal-1) < pseqdesc[i].actweight) + seq = i; + } + } + + return seq; +} + + +int LookupActivityHeaviest( void *pmodel, entvars_t *pev, int activity ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr ) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + int weight = 0; + int seq = ACTIVITY_NOT_AVAILABLE; + for (int i = 0; i < pstudiohdr->numseq; i++) + { + if (pseqdesc[i].activity == activity) + { + if ( pseqdesc[i].actweight > weight ) + { + weight = pseqdesc[i].actweight; + seq = i; + } + } + } + + return seq; +} + +void GetEyePosition ( void *pmodel, float *vecEyePosition ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + + if ( !pstudiohdr ) + { + ALERT ( at_console, "GetEyePosition() Can't get pstudiohdr ptr!\n" ); + return; + } + + VectorCopy ( pstudiohdr->eyeposition, vecEyePosition ); +} + +int LookupSequence( void *pmodel, const char *label ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + for (int i = 0; i < pstudiohdr->numseq; i++) + { + if (stricmp( pseqdesc[i].label, label ) == 0) + return i; + } + + return -1; +} + + +int IsSoundEvent( int eventNumber ) +{ + if ( eventNumber == SCRIPT_EVENT_SOUND || eventNumber == SCRIPT_EVENT_SOUND_VOICE ) + return 1; + return 0; +} + + +void SequencePrecache( void *pmodel, const char *pSequenceName ) +{ + int index = LookupSequence( pmodel, pSequenceName ); + if ( index >= 0 ) + { + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || index >= pstudiohdr->numseq ) + return; + + mstudioseqdesc_t *pseqdesc; + mstudioevent_t *pevent; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + index; + pevent = (mstudioevent_t *)((byte *)pstudiohdr + pseqdesc->eventindex); + + for (int i = 0; i < pseqdesc->numevents; i++) + { + // Don't send client-side events to the server AI + if ( pevent[i].event >= EVENT_CLIENT ) + continue; + + // UNDONE: Add a callback to check to see if a sound is precached yet and don't allocate a copy + // of it's name if it is. + if ( IsSoundEvent( pevent[i].event ) ) + { + if ( !strlen(pevent[i].options) ) + { + ALERT( at_error, "Bad sound event %d in sequence %s :: %s (sound is \"%s\")\n", pevent[i].event, pstudiohdr->name, pSequenceName, pevent[i].options ); + } + + PRECACHE_SOUND( (char *)(gpGlobals->pStringBase + ALLOC_STRING(pevent[i].options) ) ); + } + } + } +} + + + +void GetSequenceInfo( void *pmodel, entvars_t *pev, float *pflFrameRate, float *pflGroundSpeed ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return; + + mstudioseqdesc_t *pseqdesc; + + if (pev->sequence >= pstudiohdr->numseq) + { + *pflFrameRate = 0.0; + *pflGroundSpeed = 0.0; + return; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + if (pseqdesc->numframes > 1) + { + *pflFrameRate = 256 * pseqdesc->fps / (pseqdesc->numframes - 1); + *pflGroundSpeed = sqrt( pseqdesc->linearmovement[0]*pseqdesc->linearmovement[0]+ pseqdesc->linearmovement[1]*pseqdesc->linearmovement[1]+ pseqdesc->linearmovement[2]*pseqdesc->linearmovement[2] ); + *pflGroundSpeed = *pflGroundSpeed * pseqdesc->fps / (pseqdesc->numframes - 1); + } + else + { + *pflFrameRate = 256.0; + *pflGroundSpeed = 0.0; + } +} + + +int GetSequenceFlags( void *pmodel, entvars_t *pev ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq ) + return 0; + + mstudioseqdesc_t *pseqdesc; + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + return pseqdesc->flags; +} + + +int GetAnimationEvent( void *pmodel, entvars_t *pev, MonsterEvent_t *pMonsterEvent, float flStart, float flEnd, int index ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq || !pMonsterEvent ) + return 0; + + int events = 0; + + mstudioseqdesc_t *pseqdesc; + mstudioevent_t *pevent; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + pevent = (mstudioevent_t *)((byte *)pstudiohdr + pseqdesc->eventindex); + + if (pseqdesc->numevents == 0 || index > pseqdesc->numevents ) + return 0; + + if (pseqdesc->numframes > 1) + { + flStart *= (pseqdesc->numframes - 1) / 256.0; + flEnd *= (pseqdesc->numframes - 1) / 256.0; + } + else + { + flStart = 0; + flEnd = 1.0; + } + + for (; index < pseqdesc->numevents; index++) + { + // Don't send client-side events to the server AI + if ( pevent[index].event >= EVENT_CLIENT ) + continue; + + if ( (pevent[index].frame >= flStart && pevent[index].frame < flEnd) || + ((pseqdesc->flags & STUDIO_LOOPING) && flEnd >= pseqdesc->numframes - 1 && pevent[index].frame < flEnd - pseqdesc->numframes + 1) ) + { + pMonsterEvent->event = pevent[index].event; + pMonsterEvent->options = pevent[index].options; + return index + 1; + } + } + return 0; +} + +float SetController( void *pmodel, entvars_t *pev, int iController, float flValue ) +{ + studiohdr_t *pstudiohdr; + int i; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return flValue; + + mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)pstudiohdr + pstudiohdr->bonecontrollerindex); + + // find first controller that matches the index + for ( i = 0; i < pstudiohdr->numbonecontrollers; i++, pbonecontroller++) + { + if (pbonecontroller->index == iController) + break; + } + if (i >= pstudiohdr->numbonecontrollers) + return flValue; + + // wrap 0..360 if it's a rotational controller + + if (pbonecontroller->type & (STUDIO_XR | STUDIO_YR | STUDIO_ZR)) + { + // ugly hack, invert value if end < start + if (pbonecontroller->end < pbonecontroller->start) + flValue = -flValue; + + // does the controller not wrap? + if (pbonecontroller->start + 359.0 >= pbonecontroller->end) + { + if (flValue > ((pbonecontroller->start + pbonecontroller->end) / 2.0) + 180) + flValue = flValue - 360; + if (flValue < ((pbonecontroller->start + pbonecontroller->end) / 2.0) - 180) + flValue = flValue + 360; + } + else + { + if (flValue > 360) + flValue = flValue - (int)(flValue / 360.0) * 360.0; + else if (flValue < 0) + flValue = flValue + (int)((flValue / -360.0) + 1) * 360.0; + } + } + + int setting = 255 * (flValue - pbonecontroller->start) / (pbonecontroller->end - pbonecontroller->start); + + if (setting < 0) setting = 0; + if (setting > 255) setting = 255; + pev->controller[iController] = setting; + + return setting * (1.0 / 255.0) * (pbonecontroller->end - pbonecontroller->start) + pbonecontroller->start; +} + + +float SetBlending( void *pmodel, entvars_t *pev, int iBlender, float flValue ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return flValue; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + if (pseqdesc->blendtype[iBlender] == 0) + return flValue; + + if (pseqdesc->blendtype[iBlender] & (STUDIO_XR | STUDIO_YR | STUDIO_ZR)) + { + // ugly hack, invert value if end < start + if (pseqdesc->blendend[iBlender] < pseqdesc->blendstart[iBlender]) + flValue = -flValue; + + // does the controller not wrap? + if (pseqdesc->blendstart[iBlender] + 359.0 >= pseqdesc->blendend[iBlender]) + { + if (flValue > ((pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender]) / 2.0) + 180) + flValue = flValue - 360; + if (flValue < ((pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender]) / 2.0) - 180) + flValue = flValue + 360; + } + } + + int setting = 255 * (flValue - pseqdesc->blendstart[iBlender]) / (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender]); + + if (setting < 0) setting = 0; + if (setting > 255) setting = 255; + + pev->blending[iBlender] = setting; + + return setting * (1.0 / 255.0) * (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender]) + pseqdesc->blendstart[iBlender]; +} + + + + +int FindTransition( void *pmodel, int iEndingAnim, int iGoalAnim, int *piDir ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return iGoalAnim; + + mstudioseqdesc_t *pseqdesc; + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + // bail if we're going to or from a node 0 + if (pseqdesc[iEndingAnim].entrynode == 0 || pseqdesc[iGoalAnim].entrynode == 0) + { + return iGoalAnim; + } + + int iEndNode; + + // ALERT( at_console, "from %d to %d: ", pEndNode->iEndNode, pGoalNode->iStartNode ); + + if (*piDir > 0) + { + iEndNode = pseqdesc[iEndingAnim].exitnode; + } + else + { + iEndNode = pseqdesc[iEndingAnim].entrynode; + } + + if (iEndNode == pseqdesc[iGoalAnim].entrynode) + { + *piDir = 1; + return iGoalAnim; + } + + byte *pTransition = ((byte *)pstudiohdr + pstudiohdr->transitionindex); + + int iInternNode = pTransition[(iEndNode-1)*pstudiohdr->numtransitions + (pseqdesc[iGoalAnim].entrynode-1)]; + + if (iInternNode == 0) + return iGoalAnim; + + int i; + + // look for someone going + for (i = 0; i < pstudiohdr->numseq; i++) + { + if (pseqdesc[i].entrynode == iEndNode && pseqdesc[i].exitnode == iInternNode) + { + *piDir = 1; + return i; + } + if (pseqdesc[i].nodeflags) + { + if (pseqdesc[i].exitnode == iEndNode && pseqdesc[i].entrynode == iInternNode) + { + *piDir = -1; + return i; + } + } + } + + ALERT( at_console, "error in transition graph" ); + return iGoalAnim; +} + +void SetBodygroup( void *pmodel, entvars_t *pev, int iGroup, int iValue ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return; + + if (iGroup > pstudiohdr->numbodyparts) + return; + + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex) + iGroup; + + if (iValue >= pbodypart->nummodels) + return; + + int iCurrent = (pev->body / pbodypart->base) % pbodypart->nummodels; + + pev->body = (pev->body - (iCurrent * pbodypart->base) + (iValue * pbodypart->base)); +} + + +int GetBodygroup( void *pmodel, entvars_t *pev, int iGroup ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + if (iGroup > pstudiohdr->numbodyparts) + return 0; + + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex) + iGroup; + + if (pbodypart->nummodels <= 1) + return 0; + + int iCurrent = (pev->body / pbodypart->base) % pbodypart->nummodels; + + return iCurrent; +} diff --git a/dlls/animation.h b/dlls/animation.h new file mode 100644 index 0000000..ec00ca4 --- /dev/null +++ b/dlls/animation.h @@ -0,0 +1,47 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef ANIMATION_H +#define ANIMATION_H + +#define ACTIVITY_NOT_AVAILABLE -1 + +#ifndef MONSTEREVENT_H +#include "monsterevent.h" +#endif + +extern int IsSoundEvent( int eventNumber ); + +int LookupActivity( void *pmodel, entvars_t *pev, int activity ); +int LookupActivityHeaviest( void *pmodel, entvars_t *pev, int activity ); +int LookupSequence( void *pmodel, const char *label ); +void GetSequenceInfo( void *pmodel, entvars_t *pev, float *pflFrameRate, float *pflGroundSpeed ); +int GetSequenceFlags( void *pmodel, entvars_t *pev ); +int LookupAnimationEvents( void *pmodel, entvars_t *pev, float flStart, float flEnd ); +float SetController( void *pmodel, entvars_t *pev, int iController, float flValue ); +float SetBlending( void *pmodel, entvars_t *pev, int iBlender, float flValue ); +void GetEyePosition( void *pmodel, float *vecEyePosition ); +void SequencePrecache( void *pmodel, const char *pSequenceName ); +int FindTransition( void *pmodel, int iEndingAnim, int iGoalAnim, int *piDir ); +void SetBodygroup( void *pmodel, entvars_t *pev, int iGroup, int iValue ); +int GetBodygroup( void *pmodel, entvars_t *pev, int iGroup ); + +int GetAnimationEvent( void *pmodel, entvars_t *pev, MonsterEvent_t *pMonsterEvent, float flStart, float flEnd, int index ); +int ExtractBbox( void *pmodel, int sequence, float *mins, float *maxs ); + +// From /engine/studio.h +#define STUDIO_LOOPING 0x0001 + + +#endif //ANIMATION_H diff --git a/dlls/apache.cpp b/dlls/apache.cpp new file mode 100644 index 0000000..693184a --- /dev/null +++ b/dlls/apache.cpp @@ -0,0 +1,1050 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef OEM_BUILD + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "effects.h" + +extern DLL_GLOBAL int g_iSkillLevel; + +#define SF_WAITFORTRIGGER (0x04 | 0x40) // UNDONE: Fix! +#define SF_NOWRECKAGE 0x08 + +class CApache : public CBaseMonster +{ + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + int Classify( void ) { return CLASS_HUMAN_MILITARY; }; + int BloodColor( void ) { return DONT_BLEED; } + void Killed( entvars_t *pevAttacker, int iGib ); + void GibMonster( void ); + + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector( -300, -300, -172); + pev->absmax = pev->origin + Vector(300, 300, 8); + } + + void EXPORT HuntThink( void ); + void EXPORT FlyTouch( CBaseEntity *pOther ); + void EXPORT CrashTouch( CBaseEntity *pOther ); + void EXPORT DyingThink( void ); + void EXPORT StartupUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT NullThink( void ); + + void ShowDamage( void ); + void Flight( void ); + void FireRocket( void ); + BOOL FireGun( void ); + + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + + int m_iRockets; + float m_flForce; + float m_flNextRocket; + + Vector m_vecTarget; + Vector m_posTarget; + + Vector m_vecDesired; + Vector m_posDesired; + + Vector m_vecGoal; + + Vector m_angGun; + float m_flLastSeen; + float m_flPrevSeen; + + int m_iSoundState; // don't save this + + int m_iSpriteTexture; + int m_iExplode; + int m_iBodyGibs; + + float m_flGoalSpeed; + + int m_iDoSmokePuff; + CBeam *m_pBeam; +}; +LINK_ENTITY_TO_CLASS( monster_apache, CApache ); + +TYPEDESCRIPTION CApache::m_SaveData[] = +{ + DEFINE_FIELD( CApache, m_iRockets, FIELD_INTEGER ), + DEFINE_FIELD( CApache, m_flForce, FIELD_FLOAT ), + DEFINE_FIELD( CApache, m_flNextRocket, FIELD_TIME ), + DEFINE_FIELD( CApache, m_vecTarget, FIELD_VECTOR ), + DEFINE_FIELD( CApache, m_posTarget, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CApache, m_vecDesired, FIELD_VECTOR ), + DEFINE_FIELD( CApache, m_posDesired, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CApache, m_vecGoal, FIELD_VECTOR ), + DEFINE_FIELD( CApache, m_angGun, FIELD_VECTOR ), + DEFINE_FIELD( CApache, m_flLastSeen, FIELD_TIME ), + DEFINE_FIELD( CApache, m_flPrevSeen, FIELD_TIME ), +// DEFINE_FIELD( CApache, m_iSoundState, FIELD_INTEGER ), // Don't save, precached +// DEFINE_FIELD( CApache, m_iSpriteTexture, FIELD_INTEGER ), +// DEFINE_FIELD( CApache, m_iExplode, FIELD_INTEGER ), +// DEFINE_FIELD( CApache, m_iBodyGibs, FIELD_INTEGER ), + DEFINE_FIELD( CApache, m_pBeam, FIELD_CLASSPTR ), + DEFINE_FIELD( CApache, m_flGoalSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CApache, m_iDoSmokePuff, FIELD_INTEGER ), +}; +IMPLEMENT_SAVERESTORE( CApache, CBaseMonster ); + + +void CApache :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/apache.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, -64 ), Vector( 32, 32, 0 ) ); + UTIL_SetOrigin( pev, pev->origin ); + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_AIM; + pev->health = gSkillData.apacheHealth; + + m_flFieldOfView = -0.707; // 270 degrees + + pev->sequence = 0; + ResetSequenceInfo( ); + pev->frame = RANDOM_LONG(0, 0xFF); + + InitBoneControllers(); + + if (pev->spawnflags & SF_WAITFORTRIGGER) + { + SetUse( &CApache::StartupUse ); + } + else + { + SetThink( &CApache::HuntThink ); + SetTouch( &CApache::FlyTouch ); + pev->nextthink = gpGlobals->time + 1.0; + } + + m_iRockets = 10; +} + + +void CApache::Precache( void ) +{ + PRECACHE_MODEL("models/apache.mdl"); + + PRECACHE_SOUND("apache/ap_rotor1.wav"); + PRECACHE_SOUND("apache/ap_rotor2.wav"); + PRECACHE_SOUND("apache/ap_rotor3.wav"); + PRECACHE_SOUND("apache/ap_whine1.wav"); + + PRECACHE_SOUND("weapons/mortarhit.wav"); + + m_iSpriteTexture = PRECACHE_MODEL( "sprites/white.spr" ); + + PRECACHE_SOUND("turret/tu_fire1.wav"); + + PRECACHE_MODEL("sprites/lgtning.spr"); + + m_iExplode = PRECACHE_MODEL( "sprites/fexplo.spr" ); + m_iBodyGibs = PRECACHE_MODEL( "models/metalplategibs_green.mdl" ); + + UTIL_PrecacheOther( "hvr_rocket" ); +} + + + +void CApache::NullThink( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.5; +} + + +void CApache::StartupUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( &CApache::HuntThink ); + SetTouch( &CApache::FlyTouch ); + pev->nextthink = gpGlobals->time + 0.1; + SetUse( NULL ); +} + +void CApache :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->gravity = 0.3; + + STOP_SOUND( ENT(pev), CHAN_STATIC, "apache/ap_rotor2.wav" ); + + UTIL_SetSize( pev, Vector( -32, -32, -64), Vector( 32, 32, 0) ); + SetThink( &CApache::DyingThink ); + SetTouch( &CApache::CrashTouch ); + pev->nextthink = gpGlobals->time + 0.1; + pev->health = 0; + pev->takedamage = DAMAGE_NO; + + if (pev->spawnflags & SF_NOWRECKAGE) + { + m_flNextRocket = gpGlobals->time + 4.0; + } + else + { + m_flNextRocket = gpGlobals->time + 15.0; + } +} + +void CApache :: DyingThink( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + pev->avelocity = pev->avelocity * 1.02; + + // still falling? + if (m_flNextRocket > gpGlobals->time ) + { + // random explosions + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION); // This just makes a dynamic light now + WRITE_COORD( pev->origin.x + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( pev->origin.y + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( pev->origin.z + RANDOM_FLOAT( -150, -50 )); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( RANDOM_LONG(0,29) + 30 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + // lots of smoke + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( pev->origin.x + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( pev->origin.y + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( pev->origin.z + RANDOM_FLOAT( -150, -50 )); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 100 ); // scale * 10 + WRITE_BYTE( 10 ); // framerate + MESSAGE_END(); + + Vector vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z ); + + // size + WRITE_COORD( 400 ); + WRITE_COORD( 400 ); + WRITE_COORD( 132 ); + + // velocity + WRITE_COORD( pev->velocity.x ); + WRITE_COORD( pev->velocity.y ); + WRITE_COORD( pev->velocity.z ); + + // randomization + WRITE_BYTE( 50 ); + + // Model + WRITE_SHORT( m_iBodyGibs ); //model id# + + // # of shards + WRITE_BYTE( 4 ); // let client decide + + // duration + WRITE_BYTE( 30 );// 3.0 seconds + + // flags + + WRITE_BYTE( BREAK_METAL ); + MESSAGE_END(); + + // don't stop it we touch a entity + pev->flags &= ~FL_ONGROUND; + pev->nextthink = gpGlobals->time + 0.2; + return; + } + else + { + Vector vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + + /* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_EXPLOSION); // This just makes a dynamic light now + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 300 ); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( 250 ); // scale * 10 + WRITE_BYTE( 8 ); // framerate + MESSAGE_END(); + */ + + // fireball + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_SPRITE ); + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 256 ); + WRITE_SHORT( m_iExplode ); + WRITE_BYTE( 120 ); // scale * 10 + WRITE_BYTE( 255 ); // brightness + MESSAGE_END(); + + // big smoke + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 512 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 250 ); // scale * 10 + WRITE_BYTE( 5 ); // framerate + MESSAGE_END(); + + // blast circle + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_BEAMCYLINDER ); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 2000 ); // reach damage radius over .2 seconds + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 4 ); // life + WRITE_BYTE( 32 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 192 ); // r, g, b + WRITE_BYTE( 128 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + EMIT_SOUND(ENT(pev), CHAN_STATIC, "weapons/mortarhit.wav", 1.0, 0.3); + + RadiusDamage( pev->origin, pev, pev, 300, CLASS_NONE, DMG_BLAST ); + + if (/*!(pev->spawnflags & SF_NOWRECKAGE) && */(pev->flags & FL_ONGROUND)) + { + CBaseEntity *pWreckage = Create( "cycler_wreckage", pev->origin, pev->angles ); + // SET_MODEL( ENT(pWreckage->pev), STRING(pev->model) ); + UTIL_SetSize( pWreckage->pev, Vector( -200, -200, -128 ), Vector( 200, 200, -32 ) ); + pWreckage->pev->frame = pev->frame; + pWreckage->pev->sequence = pev->sequence; + pWreckage->pev->framerate = 0; + pWreckage->pev->dmgtime = gpGlobals->time + 5; + } + + // gibs + vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 64); + + // size + WRITE_COORD( 400 ); + WRITE_COORD( 400 ); + WRITE_COORD( 128 ); + + // velocity + WRITE_COORD( 0 ); + WRITE_COORD( 0 ); + WRITE_COORD( 200 ); + + // randomization + WRITE_BYTE( 30 ); + + // Model + WRITE_SHORT( m_iBodyGibs ); //model id# + + // # of shards + WRITE_BYTE( 200 ); + + // duration + WRITE_BYTE( 200 );// 10.0 seconds + + // flags + + WRITE_BYTE( BREAK_METAL ); + MESSAGE_END(); + + SetThink( &CApache::SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; + } +} + + +void CApache::FlyTouch( CBaseEntity *pOther ) +{ + // bounce if we hit something solid + if ( pOther->pev->solid == SOLID_BSP) + { + TraceResult tr = UTIL_GetGlobalTrace( ); + + // UNDONE, do a real bounce + pev->velocity = pev->velocity + tr.vecPlaneNormal * (pev->velocity.Length() + 200); + } +} + + +void CApache::CrashTouch( CBaseEntity *pOther ) +{ + // only crash if we hit something solid + if ( pOther->pev->solid == SOLID_BSP) + { + SetTouch( NULL ); + m_flNextRocket = gpGlobals->time; + pev->nextthink = gpGlobals->time; + } +} + + + +void CApache :: GibMonster( void ) +{ + // EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "common/bodysplat.wav", 0.75, ATTN_NORM, 0, 200); +} + + +void CApache :: HuntThink( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + ShowDamage( ); + + if ( m_pGoalEnt == NULL && !FStringNull(pev->target) )// this monster has a target + { + m_pGoalEnt = UTIL_FindEntityByTargetname( NULL, STRING( pev->target ) ); + if (m_pGoalEnt) + { + m_posDesired = m_pGoalEnt->pev->origin; + UTIL_MakeAimVectors( m_pGoalEnt->pev->angles ); + m_vecGoal = gpGlobals->v_forward; + } + } + + // if (m_hEnemy == NULL) + { + Look( 4092 ); + m_hEnemy = BestVisibleEnemy( ); + } + + // generic speed up + if (m_flGoalSpeed < 800) + m_flGoalSpeed += 5; + + if (m_hEnemy != NULL) + { + // ALERT( at_console, "%s\n", STRING( m_hEnemy->pev->classname ) ); + if (FVisible( m_hEnemy )) + { + if (m_flLastSeen < gpGlobals->time - 5) + m_flPrevSeen = gpGlobals->time; + m_flLastSeen = gpGlobals->time; + m_posTarget = m_hEnemy->Center( ); + } + else + { + m_hEnemy = NULL; + } + } + + m_vecTarget = (m_posTarget - pev->origin).Normalize(); + + float flLength = (pev->origin - m_posDesired).Length(); + + if (m_pGoalEnt) + { + // ALERT( at_console, "%.0f\n", flLength ); + + if (flLength < 128) + { + m_pGoalEnt = UTIL_FindEntityByTargetname( NULL, STRING( m_pGoalEnt->pev->target ) ); + if (m_pGoalEnt) + { + m_posDesired = m_pGoalEnt->pev->origin; + UTIL_MakeAimVectors( m_pGoalEnt->pev->angles ); + m_vecGoal = gpGlobals->v_forward; + flLength = (pev->origin - m_posDesired).Length(); + } + } + } + else + { + m_posDesired = pev->origin; + } + + if (flLength > 250) // 500 + { + // float flLength2 = (m_posTarget - pev->origin).Length() * (1.5 - DotProduct((m_posTarget - pev->origin).Normalize(), pev->velocity.Normalize() )); + // if (flLength2 < flLength) + if (m_flLastSeen + 90 > gpGlobals->time && DotProduct( (m_posTarget - pev->origin).Normalize(), (m_posDesired - pev->origin).Normalize( )) > 0.25) + { + m_vecDesired = (m_posTarget - pev->origin).Normalize( ); + } + else + { + m_vecDesired = (m_posDesired - pev->origin).Normalize( ); + } + } + else + { + m_vecDesired = m_vecGoal; + } + + Flight( ); + + // ALERT( at_console, "%.0f %.0f %.0f\n", gpGlobals->time, m_flLastSeen, m_flPrevSeen ); + if ((m_flLastSeen + 1 > gpGlobals->time) && (m_flPrevSeen + 2 < gpGlobals->time)) + { + if (FireGun( )) + { + // slow down if we're fireing + if (m_flGoalSpeed > 400) + m_flGoalSpeed = 400; + } + + // don't fire rockets and gun on easy mode + if (g_iSkillLevel == SKILL_EASY) + m_flNextRocket = gpGlobals->time + 10.0; + } + + UTIL_MakeAimVectors( pev->angles ); + Vector vecEst = (gpGlobals->v_forward * 800 + pev->velocity).Normalize( ); + // ALERT( at_console, "%d %d %d %4.2f\n", pev->angles.x < 0, DotProduct( pev->velocity, gpGlobals->v_forward ) > -100, m_flNextRocket < gpGlobals->time, DotProduct( m_vecTarget, vecEst ) ); + + if ((m_iRockets % 2) == 1) + { + FireRocket( ); + m_flNextRocket = gpGlobals->time + 0.5; + if (m_iRockets <= 0) + { + m_flNextRocket = gpGlobals->time + 10; + m_iRockets = 10; + } + } + else if (pev->angles.x < 0 && DotProduct( pev->velocity, gpGlobals->v_forward ) > -100 && m_flNextRocket < gpGlobals->time) + { + if (m_flLastSeen + 60 > gpGlobals->time) + { + if (m_hEnemy != NULL) + { + // make sure it's a good shot + if (DotProduct( m_vecTarget, vecEst) > .965) + { + TraceResult tr; + + UTIL_TraceLine( pev->origin, pev->origin + vecEst * 4096, ignore_monsters, edict(), &tr ); + if ((tr.vecEndPos - m_posTarget).Length() < 512) + FireRocket( ); + } + } + else + { + TraceResult tr; + + UTIL_TraceLine( pev->origin, pev->origin + vecEst * 4096, dont_ignore_monsters, edict(), &tr ); + // just fire when close + if ((tr.vecEndPos - m_posTarget).Length() < 512) + FireRocket( ); + } + } + } +} + + +void CApache :: Flight( void ) +{ + // tilt model 5 degrees + Vector vecAdj = Vector( 5.0, 0, 0 ); + + // estimate where I'll be facing in one seconds + UTIL_MakeAimVectors( pev->angles + pev->avelocity * 2 + vecAdj); + // Vector vecEst1 = pev->origin + pev->velocity + gpGlobals->v_up * m_flForce - Vector( 0, 0, 384 ); + // float flSide = DotProduct( m_posDesired - vecEst1, gpGlobals->v_right ); + + float flSide = DotProduct( m_vecDesired, gpGlobals->v_right ); + + if (flSide < 0) + { + if (pev->avelocity.y < 60) + { + pev->avelocity.y += 8; // 9 * (3.0/2.0); + } + } + else + { + if (pev->avelocity.y > -60) + { + pev->avelocity.y -= 8; // 9 * (3.0/2.0); + } + } + pev->avelocity.y *= 0.98; + + // estimate where I'll be in two seconds + UTIL_MakeAimVectors( pev->angles + pev->avelocity * 1 + vecAdj); + Vector vecEst = pev->origin + pev->velocity * 2.0 + gpGlobals->v_up * m_flForce * 20 - Vector( 0, 0, 384 * 2 ); + + // add immediate force + UTIL_MakeAimVectors( pev->angles + vecAdj); + pev->velocity.x += gpGlobals->v_up.x * m_flForce; + pev->velocity.y += gpGlobals->v_up.y * m_flForce; + pev->velocity.z += gpGlobals->v_up.z * m_flForce; + // add gravity + pev->velocity.z -= 38.4; // 32ft/sec + + + float flSpeed = pev->velocity.Length(); + float flDir = DotProduct( Vector( gpGlobals->v_forward.x, gpGlobals->v_forward.y, 0 ), Vector( pev->velocity.x, pev->velocity.y, 0 ) ); + if (flDir < 0) + flSpeed = -flSpeed; + + float flDist = DotProduct( m_posDesired - vecEst, gpGlobals->v_forward ); + + // float flSlip = DotProduct( pev->velocity, gpGlobals->v_right ); + float flSlip = -DotProduct( m_posDesired - vecEst, gpGlobals->v_right ); + + // fly sideways + if (flSlip > 0) + { + if (pev->angles.z > -30 && pev->avelocity.z > -15) + pev->avelocity.z -= 4; + else + pev->avelocity.z += 2; + } + else + { + + if (pev->angles.z < 30 && pev->avelocity.z < 15) + pev->avelocity.z += 4; + else + pev->avelocity.z -= 2; + } + + // sideways drag + pev->velocity.x = pev->velocity.x * (1.0 - fabs( gpGlobals->v_right.x ) * 0.05); + pev->velocity.y = pev->velocity.y * (1.0 - fabs( gpGlobals->v_right.y ) * 0.05); + pev->velocity.z = pev->velocity.z * (1.0 - fabs( gpGlobals->v_right.z ) * 0.05); + + // general drag + pev->velocity = pev->velocity * 0.995; + + // apply power to stay correct height + if (m_flForce < 80 && vecEst.z < m_posDesired.z) + { + m_flForce += 12; + } + else if (m_flForce > 30) + { + if (vecEst.z > m_posDesired.z) + m_flForce -= 8; + } + + // pitch forward or back to get to target + if (flDist > 0 && flSpeed < m_flGoalSpeed /* && flSpeed < flDist */ && pev->angles.x + pev->avelocity.x > -40) + { + // ALERT( at_console, "F " ); + // lean forward + pev->avelocity.x -= 12.0; + } + else if (flDist < 0 && flSpeed > -50 && pev->angles.x + pev->avelocity.x < 20) + { + // ALERT( at_console, "B " ); + // lean backward + pev->avelocity.x += 12.0; + } + else if (pev->angles.x + pev->avelocity.x > 0) + { + // ALERT( at_console, "f " ); + pev->avelocity.x -= 4.0; + } + else if (pev->angles.x + pev->avelocity.x < 0) + { + // ALERT( at_console, "b " ); + pev->avelocity.x += 4.0; + } + + // ALERT( at_console, "%.0f %.0f : %.0f %.0f : %.0f %.0f : %.0f\n", pev->origin.x, pev->velocity.x, flDist, flSpeed, pev->angles.x, pev->avelocity.x, m_flForce ); + // ALERT( at_console, "%.0f %.0f : %.0f %0.f : %.0f\n", pev->origin.z, pev->velocity.z, vecEst.z, m_posDesired.z, m_flForce ); + + // make rotor, engine sounds + if (m_iSoundState == 0) + { + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_rotor2.wav", 1.0, 0.3, 0, 110 ); + // EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_whine1.wav", 0.5, 0.2, 0, 110 ); + + m_iSoundState = SND_CHANGE_PITCH; // hack for going through level transitions + } + else + { + CBaseEntity *pPlayer = NULL; + + pPlayer = UTIL_FindEntityByClassname( NULL, "player" ); + // UNDONE: this needs to send different sounds to every player for multiplayer. + if (pPlayer) + { + + float pitch = DotProduct( pev->velocity - pPlayer->pev->velocity, (pPlayer->pev->origin - pev->origin).Normalize() ); + + pitch = (int)(100 + pitch / 50.0); + + if (pitch > 250) + pitch = 250; + if (pitch < 50) + pitch = 50; + if (pitch == 100) + pitch = 101; + + float flVol = (m_flForce / 100.0) + .1; + if (flVol > 1.0) + flVol = 1.0; + + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_rotor2.wav", 1.0, 0.3, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch); + } + // EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_whine1.wav", flVol, 0.2, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch); + + // ALERT( at_console, "%.0f %.2f\n", pitch, flVol ); + } +} + + +void CApache :: FireRocket( void ) +{ + static float side = 1.0; + static int count; + + if (m_iRockets <= 0) + return; + + UTIL_MakeAimVectors( pev->angles ); + Vector vecSrc = pev->origin + 1.5 * (gpGlobals->v_forward * 21 + gpGlobals->v_right * 70 * side + gpGlobals->v_up * -79); + + switch( m_iRockets % 5) + { + case 0: vecSrc = vecSrc + gpGlobals->v_right * 10; break; + case 1: vecSrc = vecSrc - gpGlobals->v_right * 10; break; + case 2: vecSrc = vecSrc + gpGlobals->v_up * 10; break; + case 3: vecSrc = vecSrc - gpGlobals->v_up * 10; break; + case 4: break; + } + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSrc ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSrc.x ); + WRITE_COORD( vecSrc.y ); + WRITE_COORD( vecSrc.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 20 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + + CBaseEntity *pRocket = CBaseEntity::Create( "hvr_rocket", vecSrc, pev->angles, edict() ); + if (pRocket) + pRocket->pev->velocity = pev->velocity + gpGlobals->v_forward * 100; + + m_iRockets--; + + side = - side; +} + + + +BOOL CApache :: FireGun( ) +{ + UTIL_MakeAimVectors( pev->angles ); + + Vector posGun, angGun; + GetAttachment( 1, posGun, angGun ); + + Vector vecTarget = (m_posTarget - posGun).Normalize( ); + + Vector vecOut; + + vecOut.x = DotProduct( gpGlobals->v_forward, vecTarget ); + vecOut.y = -DotProduct( gpGlobals->v_right, vecTarget ); + vecOut.z = DotProduct( gpGlobals->v_up, vecTarget ); + + Vector angles = UTIL_VecToAngles (vecOut); + + angles.x = -angles.x; + if (angles.y > 180) + angles.y = angles.y - 360; + if (angles.y < -180) + angles.y = angles.y + 360; + if (angles.x > 180) + angles.x = angles.x - 360; + if (angles.x < -180) + angles.x = angles.x + 360; + + if (angles.x > m_angGun.x) + m_angGun.x = min( angles.x, m_angGun.x + 12 ); + if (angles.x < m_angGun.x) + m_angGun.x = max( angles.x, m_angGun.x - 12 ); + if (angles.y > m_angGun.y) + m_angGun.y = min( angles.y, m_angGun.y + 12 ); + if (angles.y < m_angGun.y) + m_angGun.y = max( angles.y, m_angGun.y - 12 ); + + m_angGun.y = SetBoneController( 0, m_angGun.y ); + m_angGun.x = SetBoneController( 1, m_angGun.x ); + + Vector posBarrel, angBarrel; + GetAttachment( 0, posBarrel, angBarrel ); + Vector vecGun = (posBarrel - posGun).Normalize( ); + + if (DotProduct( vecGun, vecTarget ) > 0.98) + { +#if 1 + FireBullets( 1, posGun, vecGun, VECTOR_CONE_4DEGREES, 8192, BULLET_MONSTER_12MM, 1 ); + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "turret/tu_fire1.wav", 1, 0.3); +#else + static float flNext; + TraceResult tr; + UTIL_TraceLine( posGun, posGun + vecGun * 8192, dont_ignore_monsters, ENT( pev ), &tr ); + + if (!m_pBeam) + { + m_pBeam = CBeam::BeamCreate( "sprites/lgtning.spr", 80 ); + m_pBeam->PointEntInit( pev->origin, entindex( ) ); + m_pBeam->SetEndAttachment( 1 ); + m_pBeam->SetColor( 255, 180, 96 ); + m_pBeam->SetBrightness( 192 ); + } + + if (flNext < gpGlobals->time) + { + flNext = gpGlobals->time + 0.5; + m_pBeam->SetStartPos( tr.vecEndPos ); + } +#endif + return TRUE; + } + else + { + if (m_pBeam) + { + UTIL_Remove( m_pBeam ); + m_pBeam = NULL; + } + } + return FALSE; +} + + + +void CApache :: ShowDamage( void ) +{ + if (m_iDoSmokePuff > 0 || RANDOM_LONG(0,99) > pev->health) + { + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z - 32 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( RANDOM_LONG(0,9) + 20 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + } + if (m_iDoSmokePuff > 0) + m_iDoSmokePuff--; +} + + +int CApache :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if (pevInflictor->owner == edict()) + return 0; + + if (bitsDamageType & DMG_BLAST) + { + flDamage *= 2; + } + + /* + if ( (bitsDamageType & DMG_BULLET) && flDamage > 50) + { + // clip bullet damage at 50 + flDamage = 50; + } + */ + + // ALERT( at_console, "%.0f\n", flDamage ); + return CBaseEntity::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + + + +void CApache::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + // ALERT( at_console, "%d %.0f\n", ptr->iHitgroup, flDamage ); + + // ignore blades + if (ptr->iHitgroup == 6 && (bitsDamageType & (DMG_ENERGYBEAM|DMG_BULLET|DMG_CLUB))) + return; + + // hit hard, hits cockpit, hits engines + if (flDamage > 50 || ptr->iHitgroup == 1 || ptr->iHitgroup == 2) + { + // ALERT( at_console, "%.0f\n", flDamage ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + m_iDoSmokePuff = 3 + (flDamage / 5.0); + } + else + { + // do half damage in the body + // AddMultiDamage( pevAttacker, this, flDamage / 2.0, bitsDamageType ); + UTIL_Ricochet( ptr->vecEndPos, 2.0 ); + } +} + + + + + +class CApacheHVR : public CGrenade +{ + void Spawn( void ); + void Precache( void ); + void EXPORT IgniteThink( void ); + void EXPORT AccelerateThink( void ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iTrail; + Vector m_vecForward; +}; +LINK_ENTITY_TO_CLASS( hvr_rocket, CApacheHVR ); + +TYPEDESCRIPTION CApacheHVR::m_SaveData[] = +{ +// DEFINE_FIELD( CApacheHVR, m_iTrail, FIELD_INTEGER ), // Dont' save, precache + DEFINE_FIELD( CApacheHVR, m_vecForward, FIELD_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CApacheHVR, CGrenade ); + +void CApacheHVR :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/HVR.mdl"); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( pev, pev->origin ); + + SetThink( &CApacheHVR::IgniteThink ); + SetTouch( &CApacheHVR::ExplodeTouch ); + + UTIL_MakeAimVectors( pev->angles ); + m_vecForward = gpGlobals->v_forward; + pev->gravity = 0.5; + + pev->nextthink = gpGlobals->time + 0.1; + + pev->dmg = 150; +} + + +void CApacheHVR :: Precache( void ) +{ + PRECACHE_MODEL("models/HVR.mdl"); + m_iTrail = PRECACHE_MODEL("sprites/smoke.spr"); + PRECACHE_SOUND ("weapons/rocket1.wav"); +} + + +void CApacheHVR :: IgniteThink( void ) +{ + // pev->movetype = MOVETYPE_TOSS; + + // pev->movetype = MOVETYPE_FLY; + pev->effects |= EF_LIGHT; + + // make rocket sound + EMIT_SOUND( ENT(pev), CHAN_VOICE, "weapons/rocket1.wav", 1, 0.5 ); + + // rocket trail + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + + WRITE_BYTE( TE_BEAMFOLLOW ); + WRITE_SHORT(entindex()); // entity + WRITE_SHORT(m_iTrail ); // model + WRITE_BYTE( 15 ); // life + WRITE_BYTE( 5 ); // width + WRITE_BYTE( 224 ); // r, g, b + WRITE_BYTE( 224 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + + MESSAGE_END(); // move PHS/PVS data sending into here (SEND_ALL, SEND_PVS, SEND_PHS) + + // set to accelerate + SetThink( &CApacheHVR::AccelerateThink ); + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CApacheHVR :: AccelerateThink( void ) +{ + // check world boundaries + if (pev->origin.x < -4096 || pev->origin.x > 4096 || pev->origin.y < -4096 || pev->origin.y > 4096 || pev->origin.z < -4096 || pev->origin.z > 4096) + { + UTIL_Remove( this ); + return; + } + + // accelerate + float flSpeed = pev->velocity.Length(); + if (flSpeed < 1800) + { + pev->velocity = pev->velocity + m_vecForward * 200; + } + + // re-aim + pev->angles = UTIL_VecToAngles( pev->velocity ); + + pev->nextthink = gpGlobals->time + 0.1; +} + + +#endif diff --git a/dlls/barnacle.cpp b/dlls/barnacle.cpp new file mode 100644 index 0000000..18b1f58 --- /dev/null +++ b/dlls/barnacle.cpp @@ -0,0 +1,428 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// barnacle - stationary ceiling mounted 'fishing' monster +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" + +#define BARNACLE_BODY_HEIGHT 44 // how 'tall' the barnacle's model is. +#define BARNACLE_PULL_SPEED 8 +#define BARNACLE_KILL_VICTIM_DELAY 5 // how many seconds after pulling prey in to gib them. + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define BARNACLE_AE_PUKEGIB 2 + +class CBarnacle : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + CBaseEntity *TongueTouchEnt ( float *pflLength ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void EXPORT BarnacleThink ( void ); + void EXPORT WaitTillDead ( void ); + void Killed( entvars_t *pevAttacker, int iGib ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + float m_flAltitude; + float m_flKillVictimTime; + int m_cGibs;// barnacle loads up on gibs each time it kills something. + BOOL m_fTongueExtended; + BOOL m_fLiftingPrey; + float m_flTongueAdj; +}; +LINK_ENTITY_TO_CLASS( monster_barnacle, CBarnacle ); + +TYPEDESCRIPTION CBarnacle::m_SaveData[] = +{ + DEFINE_FIELD( CBarnacle, m_flAltitude, FIELD_FLOAT ), + DEFINE_FIELD( CBarnacle, m_flKillVictimTime, FIELD_TIME ), + DEFINE_FIELD( CBarnacle, m_cGibs, FIELD_INTEGER ),// barnacle loads up on gibs each time it kills something. + DEFINE_FIELD( CBarnacle, m_fTongueExtended, FIELD_BOOLEAN ), + DEFINE_FIELD( CBarnacle, m_fLiftingPrey, FIELD_BOOLEAN ), + DEFINE_FIELD( CBarnacle, m_flTongueAdj, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CBarnacle, CBaseMonster ); + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CBarnacle :: Classify ( void ) +{ + return CLASS_ALIEN_MONSTER; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CBarnacle :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case BARNACLE_AE_PUKEGIB: + CGib::SpawnRandomGibs( pev, 1, 1 ); + break; + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CBarnacle :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/barnacle.mdl"); + UTIL_SetSize( pev, Vector(-16, -16, -32), Vector(16, 16, 0) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_NONE; + pev->takedamage = DAMAGE_AIM; + m_bloodColor = BLOOD_COLOR_RED; + pev->effects = EF_INVLIGHT; // take light from the ceiling + pev->health = 25; + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_flKillVictimTime = 0; + m_cGibs = 0; + m_fLiftingPrey = FALSE; + m_flTongueAdj = -100; + + InitBoneControllers(); + + SetActivity ( ACT_IDLE ); + + SetThink ( &CBarnacle::BarnacleThink ); + pev->nextthink = gpGlobals->time + 0.5; + + UTIL_SetOrigin ( pev, pev->origin ); +} + +int CBarnacle::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( bitsDamageType & DMG_CLUB ) + { + flDamage = pev->health; + } + + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +//========================================================= +void CBarnacle :: BarnacleThink ( void ) +{ + CBaseEntity *pTouchEnt; + CBaseMonster *pVictim; + float flLength; + + pev->nextthink = gpGlobals->time + 0.1; + + if ( m_hEnemy != NULL ) + { +// barnacle has prey. + + if ( !m_hEnemy->IsAlive() ) + { + // someone (maybe even the barnacle) killed the prey. Reset barnacle. + m_fLiftingPrey = FALSE;// indicate that we're not lifting prey. + m_hEnemy = NULL; + return; + } + + if ( m_fLiftingPrey ) + { + if ( m_hEnemy != NULL && m_hEnemy->pev->deadflag != DEAD_NO ) + { + // crap, someone killed the prey on the way up. + m_hEnemy = NULL; + m_fLiftingPrey = FALSE; + return; + } + + // still pulling prey. + Vector vecNewEnemyOrigin = m_hEnemy->pev->origin; + vecNewEnemyOrigin.x = pev->origin.x; + vecNewEnemyOrigin.y = pev->origin.y; + + // guess as to where their neck is + vecNewEnemyOrigin.x -= 6 * cos(m_hEnemy->pev->angles.y * M_PI/180.0); + vecNewEnemyOrigin.y -= 6 * sin(m_hEnemy->pev->angles.y * M_PI/180.0); + + m_flAltitude -= BARNACLE_PULL_SPEED; + vecNewEnemyOrigin.z += BARNACLE_PULL_SPEED; + + if ( fabs( pev->origin.z - ( vecNewEnemyOrigin.z + m_hEnemy->pev->view_ofs.z - 8 ) ) < BARNACLE_BODY_HEIGHT ) + { + // prey has just been lifted into position ( if the victim origin + eye height + 8 is higher than the bottom of the barnacle, it is assumed that the head is within barnacle's body ) + m_fLiftingPrey = FALSE; + + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_bite3.wav", 1, ATTN_NORM ); + + pVictim = m_hEnemy->MyMonsterPointer(); + + m_flKillVictimTime = gpGlobals->time + 10;// now that the victim is in place, the killing bite will be administered in 10 seconds. + + if ( pVictim ) + { + pVictim->BarnacleVictimBitten( pev ); + SetActivity ( ACT_EAT ); + } + } + + UTIL_SetOrigin ( m_hEnemy->pev, vecNewEnemyOrigin ); + } + else + { + // prey is lifted fully into feeding position and is dangling there. + + pVictim = m_hEnemy->MyMonsterPointer(); + + if ( m_flKillVictimTime != -1 && gpGlobals->time > m_flKillVictimTime ) + { + // kill! + if ( pVictim ) + { + pVictim->TakeDamage ( pev, pev, pVictim->pev->health, DMG_SLASH | DMG_ALWAYSGIB ); + m_cGibs = 3; + } + + return; + } + + // bite prey every once in a while + if ( pVictim && ( RANDOM_LONG(0,49) == 0 ) ) + { + switch ( RANDOM_LONG(0,2) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew2.wav", 1, ATTN_NORM ); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew3.wav", 1, ATTN_NORM ); break; + } + + pVictim->BarnacleVictimBitten( pev ); + } + + } + } + else + { +// barnacle has no prey right now, so just idle and check to see if anything is touching the tongue. + + // If idle and no nearby client, don't think so often + if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(1,1.5); // Stagger a bit to keep barnacles from thinking on the same frame + + if ( m_fSequenceFinished ) + {// this is done so barnacle will fidget. + SetActivity ( ACT_IDLE ); + m_flTongueAdj = -100; + } + + if ( m_cGibs && RANDOM_LONG(0,99) == 1 ) + { + // cough up a gib. + CGib::SpawnRandomGibs( pev, 1, 1 ); + m_cGibs--; + + switch ( RANDOM_LONG(0,2) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew2.wav", 1, ATTN_NORM ); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew3.wav", 1, ATTN_NORM ); break; + } + } + + pTouchEnt = TongueTouchEnt( &flLength ); + + if ( pTouchEnt != NULL && m_fTongueExtended ) + { + // tongue is fully extended, and is touching someone. + if ( pTouchEnt->FBecomeProne() ) + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_alert2.wav", 1, ATTN_NORM ); + + SetSequenceByName ( "attack1" ); + m_flTongueAdj = -20; + + m_hEnemy = pTouchEnt; + + pTouchEnt->pev->movetype = MOVETYPE_FLY; + pTouchEnt->pev->velocity = g_vecZero; + pTouchEnt->pev->basevelocity = g_vecZero; + pTouchEnt->pev->origin.x = pev->origin.x; + pTouchEnt->pev->origin.y = pev->origin.y; + + m_fLiftingPrey = TRUE;// indicate that we should be lifting prey. + m_flKillVictimTime = -1;// set this to a bogus time while the victim is lifted. + + m_flAltitude = (pev->origin.z - pTouchEnt->EyePosition().z); + } + } + else + { + // calculate a new length for the tongue to be clear of anything else that moves under it. + if ( m_flAltitude < flLength ) + { + // if tongue is higher than is should be, lower it kind of slowly. + m_flAltitude += BARNACLE_PULL_SPEED; + m_fTongueExtended = FALSE; + } + else + { + m_flAltitude = flLength; + m_fTongueExtended = TRUE; + } + + } + + } + + // ALERT( at_console, "tounge %f\n", m_flAltitude + m_flTongueAdj ); + SetBoneController( 0, -(m_flAltitude + m_flTongueAdj) ); + StudioFrameAdvance( 0.1 ); +} + +//========================================================= +// Killed. +//========================================================= +void CBarnacle :: Killed( entvars_t *pevAttacker, int iGib ) +{ + CBaseMonster *pVictim; + + pev->solid = SOLID_NOT; + pev->takedamage = DAMAGE_NO; + + if ( m_hEnemy != NULL ) + { + pVictim = m_hEnemy->MyMonsterPointer(); + + if ( pVictim ) + { + pVictim->BarnacleVictimReleased(); + } + } + +// CGib::SpawnRandomGibs( pev, 4, 1 ); + + switch ( RANDOM_LONG ( 0, 1 ) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_die1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_die3.wav", 1, ATTN_NORM ); break; + } + + SetActivity ( ACT_DIESIMPLE ); + SetBoneController( 0, 0 ); + + StudioFrameAdvance( 0.1 ); + + pev->nextthink = gpGlobals->time + 0.1; + SetThink ( &CBarnacle::WaitTillDead ); +} + +//========================================================= +//========================================================= +void CBarnacle :: WaitTillDead ( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + float flInterval = StudioFrameAdvance( 0.1 ); + DispatchAnimEvents ( flInterval ); + + if ( m_fSequenceFinished ) + { + // death anim finished. + StopAnimation(); + SetThink ( NULL ); + } +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CBarnacle :: Precache() +{ + PRECACHE_MODEL("models/barnacle.mdl"); + + PRECACHE_SOUND("barnacle/bcl_alert2.wav");//happy, lifting food up + PRECACHE_SOUND("barnacle/bcl_bite3.wav");//just got food to mouth + PRECACHE_SOUND("barnacle/bcl_chew1.wav"); + PRECACHE_SOUND("barnacle/bcl_chew2.wav"); + PRECACHE_SOUND("barnacle/bcl_chew3.wav"); + PRECACHE_SOUND("barnacle/bcl_die1.wav" ); + PRECACHE_SOUND("barnacle/bcl_die3.wav" ); +} + +//========================================================= +// TongueTouchEnt - does a trace along the barnacle's tongue +// to see if any entity is touching it. Also stores the length +// of the trace in the int pointer provided. +//========================================================= +#define BARNACLE_CHECK_SPACING 8 +CBaseEntity *CBarnacle :: TongueTouchEnt ( float *pflLength ) +{ + TraceResult tr; + float length; + + // trace once to hit architecture and see if the tongue needs to change position. + UTIL_TraceLine ( pev->origin, pev->origin - Vector ( 0 , 0 , 2048 ), ignore_monsters, ENT(pev), &tr ); + length = fabs( pev->origin.z - tr.vecEndPos.z ); + if ( pflLength ) + { + *pflLength = length; + } + + Vector delta = Vector( BARNACLE_CHECK_SPACING, BARNACLE_CHECK_SPACING, 0 ); + Vector mins = pev->origin - delta; + Vector maxs = pev->origin + delta; + maxs.z = pev->origin.z; + mins.z -= length; + + CBaseEntity *pList[10]; + int count = UTIL_EntitiesInBox( pList, 10, mins, maxs, (FL_CLIENT|FL_MONSTER) ); + if ( count ) + { + for ( int i = 0; i < count; i++ ) + { + // only clients and monsters + if ( pList[i] != this && IRelationship( pList[i] ) > R_NO && pList[ i ]->pev->deadflag == DEAD_NO ) // this ent is one of our enemies. Barnacle tries to eat it. + { + return pList[i]; + } + } + } + + return NULL; +} diff --git a/dlls/barney.cpp b/dlls/barney.cpp new file mode 100644 index 0000000..20b39e0 --- /dev/null +++ b/dlls/barney.cpp @@ -0,0 +1,841 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// monster template +//========================================================= +// UNDONE: Holster weapon? + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "talkmonster.h" +#include "schedule.h" +#include "defaultai.h" +#include "scripted.h" +#include "weapons.h" +#include "soundent.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +// first flag is barney dying for scripted sequences? +#define BARNEY_AE_DRAW ( 2 ) +#define BARNEY_AE_SHOOT ( 3 ) +#define BARNEY_AE_HOLSTER ( 4 ) + +#define BARNEY_BODY_GUNHOLSTERED 0 +#define BARNEY_BODY_GUNDRAWN 1 +#define BARNEY_BODY_GUNGONE 2 + +class CBarney : public CTalkMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int ISoundMask( void ); + void BarneyFirePistol( void ); + void AlertSound( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + + void RunTask( Task_t *pTask ); + void StartTask( Task_t *pTask ); + virtual int ObjectCaps( void ) { return CTalkMonster :: ObjectCaps() | FCAP_IMPULSE_USE; } + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + + void DeclineFollowing( void ); + + // Override these to set behavior + Schedule_t *GetScheduleOfType ( int Type ); + Schedule_t *GetSchedule ( void ); + MONSTERSTATE GetIdealState ( void ); + + void DeathSound( void ); + void PainSound( void ); + + void TalkInit( void ); + + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + void Killed( entvars_t *pevAttacker, int iGib ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + BOOL m_fGunDrawn; + float m_painTime; + float m_checkAttackTime; + BOOL m_lastAttackCheck; + + // UNDONE: What is this for? It isn't used? + float m_flPlayerDamage;// how much pain has the player inflicted on me? + + CUSTOM_SCHEDULES; +}; + +LINK_ENTITY_TO_CLASS( monster_barney, CBarney ); + +TYPEDESCRIPTION CBarney::m_SaveData[] = +{ + DEFINE_FIELD( CBarney, m_fGunDrawn, FIELD_BOOLEAN ), + DEFINE_FIELD( CBarney, m_painTime, FIELD_TIME ), + DEFINE_FIELD( CBarney, m_checkAttackTime, FIELD_TIME ), + DEFINE_FIELD( CBarney, m_lastAttackCheck, FIELD_BOOLEAN ), + DEFINE_FIELD( CBarney, m_flPlayerDamage, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CBarney, CTalkMonster ); + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +Task_t tlBaFollow[] = +{ + { TASK_MOVE_TO_TARGET_RANGE,(float)128 }, // Move within 128 of target ent (client) + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE }, +}; + +Schedule_t slBaFollow[] = +{ + { + tlBaFollow, + ARRAYSIZE ( tlBaFollow ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_PROVOKED, + bits_SOUND_DANGER, + "Follow" + }, +}; + +//========================================================= +// BarneyDraw- much better looking draw schedule for when +// barney knows who he's gonna attack. +//========================================================= +Task_t tlBarneyEnemyDraw[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, 0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float) ACT_ARM }, +}; + +Schedule_t slBarneyEnemyDraw[] = +{ + { + tlBarneyEnemyDraw, + ARRAYSIZE ( tlBarneyEnemyDraw ), + 0, + 0, + "Barney Enemy Draw" + } +}; + +Task_t tlBaFaceTarget[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_TARGET, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE }, +}; + +Schedule_t slBaFaceTarget[] = +{ + { + tlBaFaceTarget, + ARRAYSIZE ( tlBaFaceTarget ), + bits_COND_CLIENT_PUSH | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_PROVOKED, + bits_SOUND_DANGER, + "FaceTarget" + }, +}; + + +Task_t tlIdleBaStand[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, // repick IDLESTAND every two seconds. + { TASK_TLK_HEADRESET, (float)0 }, // reset head position +}; + +Schedule_t slIdleBaStand[] = +{ + { + tlIdleBaStand, + ARRAYSIZE ( tlIdleBaStand ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags - change these, and you'll break the talking code. + //bits_SOUND_PLAYER | + //bits_SOUND_WORLD | + + bits_SOUND_DANGER | + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "IdleStand" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CBarney ) +{ + slBaFollow, + slBarneyEnemyDraw, + slBaFaceTarget, + slIdleBaStand, +}; + + +IMPLEMENT_CUSTOM_SCHEDULES( CBarney, CTalkMonster ); + +void CBarney :: StartTask( Task_t *pTask ) +{ + CTalkMonster::StartTask( pTask ); +} + +void CBarney :: RunTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK1: + if (m_hEnemy != NULL && (m_hEnemy->IsPlayer())) + { + pev->framerate = 1.5; + } + CTalkMonster::RunTask( pTask ); + break; + default: + CTalkMonster::RunTask( pTask ); + break; + } +} + + + + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. +//========================================================= +int CBarney :: ISoundMask ( void) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_CARCASS | + bits_SOUND_MEAT | + bits_SOUND_GARBAGE | + bits_SOUND_DANGER | + bits_SOUND_PLAYER; +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CBarney :: Classify ( void ) +{ + return CLASS_PLAYER_ALLY; +} + +//========================================================= +// ALertSound - barney says "Freeze!" +//========================================================= +void CBarney :: AlertSound( void ) +{ + if ( m_hEnemy != NULL ) + { + if ( FOkToSpeak() ) + { + PlaySentence( "BA_ATTACK", RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + } + } + +} +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CBarney :: SetYawSpeed ( void ) +{ + int ys; + + ys = 0; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 70; + break; + case ACT_WALK: + ys = 70; + break; + case ACT_RUN: + ys = 90; + break; + default: + ys = 70; + break; + } + + pev->yaw_speed = ys; +} + + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CBarney :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDist <= 1024 && flDot >= 0.5 ) + { + if ( gpGlobals->time > m_checkAttackTime ) + { + TraceResult tr; + + Vector shootOrigin = pev->origin + Vector( 0, 0, 55 ); + CBaseEntity *pEnemy = m_hEnemy; + Vector shootTarget = ( (pEnemy->BodyTarget( shootOrigin ) - pEnemy->pev->origin) + m_vecEnemyLKP ); + UTIL_TraceLine( shootOrigin, shootTarget, dont_ignore_monsters, ENT(pev), &tr ); + m_checkAttackTime = gpGlobals->time + 1; + if ( tr.flFraction == 1.0 || (tr.pHit != NULL && CBaseEntity::Instance(tr.pHit) == pEnemy) ) + m_lastAttackCheck = TRUE; + else + m_lastAttackCheck = FALSE; + m_checkAttackTime = gpGlobals->time + 1.5; + } + return m_lastAttackCheck; + } + return FALSE; +} + + +//========================================================= +// BarneyFirePistol - shoots one round from the pistol at +// the enemy barney is facing. +//========================================================= +void CBarney :: BarneyFirePistol ( void ) +{ + Vector vecShootOrigin; + + UTIL_MakeVectors(pev->angles); + vecShootOrigin = pev->origin + Vector( 0, 0, 55 ); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); + pev->effects = EF_MUZZLEFLASH; + + FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_2DEGREES, 1024, BULLET_MONSTER_9MM ); + + int pitchShift = RANDOM_LONG( 0, 20 ); + + // Only shift about half the time + if ( pitchShift > 10 ) + pitchShift = 0; + else + pitchShift -= 5; + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "barney/ba_attack2.wav", 1, ATTN_NORM, 0, 100 + pitchShift ); + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 384, 0.3 ); + + // UNDONE: Reload? + m_cAmmoLoaded--;// take away a bullet! +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CBarney :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case BARNEY_AE_SHOOT: + BarneyFirePistol(); + break; + + case BARNEY_AE_DRAW: + // barney's bodygroup switches here so he can pull gun from holster + pev->body = BARNEY_BODY_GUNDRAWN; + m_fGunDrawn = TRUE; + break; + + case BARNEY_AE_HOLSTER: + // change bodygroup to replace gun in holster + pev->body = BARNEY_BODY_GUNHOLSTERED; + m_fGunDrawn = FALSE; + break; + + default: + CTalkMonster::HandleAnimEvent( pEvent ); + } +} + +//========================================================= +// Spawn +//========================================================= +void CBarney :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/barney.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->health = gSkillData.barneyHealth; + pev->view_ofs = Vector ( 0, 0, 50 );// position of the eyes relative to monster's origin. + m_flFieldOfView = VIEW_FIELD_WIDE; // NOTE: we need a wide field of view so npc will notice player and say hello + m_MonsterState = MONSTERSTATE_NONE; + + pev->body = 0; // gun in holster + m_fGunDrawn = FALSE; + + m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; + + MonsterInit(); + SetUse( &CBarney::FollowerUse ); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CBarney :: Precache() +{ + PRECACHE_MODEL("models/barney.mdl"); + + PRECACHE_SOUND("barney/ba_attack1.wav" ); + PRECACHE_SOUND("barney/ba_attack2.wav" ); + + PRECACHE_SOUND("barney/ba_pain1.wav"); + PRECACHE_SOUND("barney/ba_pain2.wav"); + PRECACHE_SOUND("barney/ba_pain3.wav"); + + PRECACHE_SOUND("barney/ba_die1.wav"); + PRECACHE_SOUND("barney/ba_die2.wav"); + PRECACHE_SOUND("barney/ba_die3.wav"); + + // every new barney must call this, otherwise + // when a level is loaded, nobody will talk (time is reset to 0) + TalkInit(); + CTalkMonster::Precache(); +} + +// Init talk data +void CBarney :: TalkInit() +{ + + CTalkMonster::TalkInit(); + + // scientists speach group names (group names are in sentences.txt) + + m_szGrp[TLK_ANSWER] = "BA_ANSWER"; + m_szGrp[TLK_QUESTION] = "BA_QUESTION"; + m_szGrp[TLK_IDLE] = "BA_IDLE"; + m_szGrp[TLK_STARE] = "BA_STARE"; + m_szGrp[TLK_USE] = "BA_OK"; + m_szGrp[TLK_UNUSE] = "BA_WAIT"; + m_szGrp[TLK_STOP] = "BA_STOP"; + + m_szGrp[TLK_NOSHOOT] = "BA_SCARED"; + m_szGrp[TLK_HELLO] = "BA_HELLO"; + + m_szGrp[TLK_PLHURT1] = "!BA_CUREA"; + m_szGrp[TLK_PLHURT2] = "!BA_CUREB"; + m_szGrp[TLK_PLHURT3] = "!BA_CUREC"; + + m_szGrp[TLK_PHELLO] = NULL; //"BA_PHELLO"; // UNDONE + m_szGrp[TLK_PIDLE] = NULL; //"BA_PIDLE"; // UNDONE + m_szGrp[TLK_PQUESTION] = "BA_PQUEST"; // UNDONE + + m_szGrp[TLK_SMELL] = "BA_SMELL"; + + m_szGrp[TLK_WOUND] = "BA_WOUND"; + m_szGrp[TLK_MORTAL] = "BA_MORTAL"; + + // get voice for head - just one barney voice for now + m_voicePitch = 100; +} + + +static BOOL IsFacing( entvars_t *pevTest, const Vector &reference ) +{ + Vector vecDir = (reference - pevTest->origin); + vecDir.z = 0; + vecDir = vecDir.Normalize(); + Vector forward, angle; + angle = pevTest->v_angle; + angle.x = 0; + UTIL_MakeVectorsPrivate( angle, forward, NULL, NULL ); + // He's facing me, he meant it + if ( DotProduct( forward, vecDir ) > 0.96 ) // +/- 15 degrees or so + { + return TRUE; + } + return FALSE; +} + + +int CBarney :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType) +{ + // make sure friends talk about it if player hurts talkmonsters... + int ret = CTalkMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); + if ( !IsAlive() || pev->deadflag == DEAD_DYING ) + return ret; + + if ( m_MonsterState != MONSTERSTATE_PRONE && (pevAttacker->flags & FL_CLIENT) ) + { + m_flPlayerDamage += flDamage; + + // This is a heurstic to determine if the player intended to harm me + // If I have an enemy, we can't establish intent (may just be crossfire) + if ( m_hEnemy == NULL ) + { + // If the player was facing directly at me, or I'm already suspicious, get mad + if ( (m_afMemory & bits_MEMORY_SUSPICIOUS) || IsFacing( pevAttacker, pev->origin ) ) + { + // Alright, now I'm pissed! + PlaySentence( "BA_MAD", 4, VOL_NORM, ATTN_NORM ); + + Remember( bits_MEMORY_PROVOKED ); + StopFollowing( TRUE ); + } + else + { + // Hey, be careful with that + PlaySentence( "BA_SHOT", 4, VOL_NORM, ATTN_NORM ); + Remember( bits_MEMORY_SUSPICIOUS ); + } + } + else if ( !(m_hEnemy->IsPlayer()) && pev->deadflag == DEAD_NO ) + { + PlaySentence( "BA_SHOT", 4, VOL_NORM, ATTN_NORM ); + } + } + + return ret; +} + + +//========================================================= +// PainSound +//========================================================= +void CBarney :: PainSound ( void ) +{ + if (gpGlobals->time < m_painTime) + return; + + m_painTime = gpGlobals->time + RANDOM_FLOAT(0.5, 0.75); + + switch (RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_pain1.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 1: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_pain2.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 2: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_pain3.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CBarney :: DeathSound ( void ) +{ + switch (RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_die1.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 1: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_die2.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 2: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_die3.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + } +} + + +void CBarney::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + switch( ptr->iHitgroup) + { + case HITGROUP_CHEST: + case HITGROUP_STOMACH: + if (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_BLAST)) + { + flDamage = flDamage / 2; + } + break; + case 10: + if (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_CLUB)) + { + flDamage -= 20; + if (flDamage <= 0) + { + UTIL_Ricochet( ptr->vecEndPos, 1.0 ); + flDamage = 0.01; + } + } + // always a head shot + ptr->iHitgroup = HITGROUP_HEAD; + break; + } + + CTalkMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + + +void CBarney::Killed( entvars_t *pevAttacker, int iGib ) +{ + if ( pev->body < BARNEY_BODY_GUNGONE ) + {// drop the gun! + Vector vecGunPos; + Vector vecGunAngles; + + pev->body = BARNEY_BODY_GUNGONE; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + CBaseEntity *pGun = DropItem( "weapon_9mmhandgun", vecGunPos, vecGunAngles ); + } + + SetUse( NULL ); + CTalkMonster::Killed( pevAttacker, iGib ); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +Schedule_t* CBarney :: GetScheduleOfType ( int Type ) +{ + Schedule_t *psched; + + switch( Type ) + { + case SCHED_ARM_WEAPON: + if ( m_hEnemy != NULL ) + { + // face enemy, then draw. + return slBarneyEnemyDraw; + } + break; + + // Hook these to make a looping schedule + case SCHED_TARGET_FACE: + // call base class default so that barney will talk + // when 'used' + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + return slBaFaceTarget; // override this for different target face behavior + else + return psched; + + case SCHED_TARGET_CHASE: + return slBaFollow; + + case SCHED_IDLE_STAND: + // call base class default so that scientist will talk + // when standing during idle + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + { + // just look straight ahead. + return slIdleBaStand; + } + else + return psched; + } + + return CTalkMonster::GetScheduleOfType( Type ); +} + +//========================================================= +// GetSchedule - Decides which type of schedule best suits +// the monster's current state and conditions. Then calls +// monster's member function to get a pointer to a schedule +// of the proper type. +//========================================================= +Schedule_t *CBarney :: GetSchedule ( void ) +{ + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + if ( HasConditions( bits_COND_ENEMY_DEAD ) && FOkToSpeak() ) + { + PlaySentence( "BA_KILL", 4, VOL_NORM, ATTN_NORM ); + } + + switch( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + + // always act surprized with a new enemy + if ( HasConditions( bits_COND_NEW_ENEMY ) && HasConditions( bits_COND_LIGHT_DAMAGE) ) + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + + // wait for one schedule to draw gun + if (!m_fGunDrawn ) + return GetScheduleOfType( SCHED_ARM_WEAPON ); + + if ( HasConditions( bits_COND_HEAVY_DAMAGE ) ) + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + break; + + case MONSTERSTATE_ALERT: + case MONSTERSTATE_IDLE: + if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE)) + { + // flinch if hurt + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + + if ( m_hEnemy == NULL && IsFollowing() ) + { + if ( !m_hTargetEnt->IsAlive() ) + { + // UNDONE: Comment about the recently dead player here? + StopFollowing( FALSE ); + break; + } + else + { + if ( HasConditions( bits_COND_CLIENT_PUSH ) ) + { + return GetScheduleOfType( SCHED_MOVE_AWAY_FOLLOW ); + } + return GetScheduleOfType( SCHED_TARGET_FACE ); + } + } + + if ( HasConditions( bits_COND_CLIENT_PUSH ) ) + { + return GetScheduleOfType( SCHED_MOVE_AWAY ); + } + + // try to say something about smells + TrySmellTalk(); + break; + } + + return CTalkMonster::GetSchedule(); +} + +MONSTERSTATE CBarney :: GetIdealState ( void ) +{ + return CTalkMonster::GetIdealState(); +} + + + +void CBarney::DeclineFollowing( void ) +{ + PlaySentence( "BA_POK", 2, VOL_NORM, ATTN_NORM ); +} + + + + + +//========================================================= +// DEAD BARNEY PROP +// +// Designer selects a pose in worldcraft, 0 through num_poses-1 +// this value is added to what is selected as the 'first dead pose' +// among the monster's normal animations. All dead poses must +// appear sequentially in the model file. Be sure and set +// the m_iFirstPose properly! +// +//========================================================= +class CDeadBarney : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_PLAYER_ALLY; } + + void KeyValue( KeyValueData *pkvd ); + + int m_iPose;// which sequence to display -- temporary, don't need to save + static char *m_szPoses[3]; +}; + +char *CDeadBarney::m_szPoses[] = { "lying_on_back", "lying_on_side", "lying_on_stomach" }; + +void CDeadBarney::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "pose")) + { + m_iPose = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +LINK_ENTITY_TO_CLASS( monster_barney_dead, CDeadBarney ); + +//========================================================= +// ********** DeadBarney SPAWN ********** +//========================================================= +void CDeadBarney :: Spawn( ) +{ + PRECACHE_MODEL("models/barney.mdl"); + SET_MODEL(ENT(pev), "models/barney.mdl"); + + pev->effects = 0; + pev->yaw_speed = 8; + pev->sequence = 0; + m_bloodColor = BLOOD_COLOR_RED; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + if (pev->sequence == -1) + { + ALERT ( at_console, "Dead barney with bad pose\n" ); + } + // Corpses have less health + pev->health = 8;//gSkillData.barneyHealth; + + MonsterInitDead(); +} + + diff --git a/dlls/basemonster.h b/dlls/basemonster.h new file mode 100644 index 0000000..bd29f34 --- /dev/null +++ b/dlls/basemonster.h @@ -0,0 +1,339 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ + +#ifndef BASEMONSTER_H +#define BASEMONSTER_H + +// +// generic Monster +// +class CBaseMonster : public CBaseToggle +{ +private: + int m_afConditions; + +public: + typedef enum + { + SCRIPT_PLAYING = 0, // Playing the sequence + SCRIPT_WAIT, // Waiting on everyone in the script to be ready + SCRIPT_CLEANUP, // Cancelling the script / cleaning up + SCRIPT_WALK_TO_MARK, + SCRIPT_RUN_TO_MARK, + } SCRIPTSTATE; + + + + // these fields have been added in the process of reworking the state machine. (sjb) + EHANDLE m_hEnemy; // the entity that the monster is fighting. + EHANDLE m_hTargetEnt; // the entity that the monster is trying to reach + EHANDLE m_hOldEnemy[ MAX_OLD_ENEMIES ]; + Vector m_vecOldEnemy[ MAX_OLD_ENEMIES ]; + + float m_flFieldOfView;// width of monster's field of view ( dot product ) + float m_flWaitFinished;// if we're told to wait, this is the time that the wait will be over. + float m_flMoveWaitFinished; + + Activity m_Activity;// what the monster is doing (animation) + Activity m_IdealActivity;// monster should switch to this activity + + int m_LastHitGroup; // the last body region that took damage + + MONSTERSTATE m_MonsterState;// monster's current state + MONSTERSTATE m_IdealMonsterState;// monster should change to this state + + int m_iTaskStatus; + Schedule_t *m_pSchedule; + int m_iScheduleIndex; + + WayPoint_t m_Route[ ROUTE_SIZE ]; // Positions of movement + int m_movementGoal; // Goal that defines route + int m_iRouteIndex; // index into m_Route[] + float m_moveWaitTime; // How long I should wait for something to move + + Vector m_vecMoveGoal; // kept around for node graph moves, so we know our ultimate goal + Activity m_movementActivity; // When moving, set this activity + + int m_iAudibleList; // first index of a linked list of sounds that the monster can hear. + int m_afSoundTypes; + + Vector m_vecLastPosition;// monster sometimes wants to return to where it started after an operation. + + int m_iHintNode; // this is the hint node that the monster is moving towards or performing active idle on. + + int m_afMemory; + + int m_iMaxHealth;// keeps track of monster's maximum health value (for re-healing, etc) + + Vector m_vecEnemyLKP;// last known position of enemy. (enemy's origin) + + int m_cAmmoLoaded; // how much ammo is in the weapon (used to trigger reload anim sequences) + + int m_afCapability;// tells us what a monster can/can't do. + + float m_flNextAttack; // cannot attack again until this time + + int m_bitsDamageType; // what types of damage has monster (player) taken + BYTE m_rgbTimeBasedDamage[CDMG_TIMEBASED]; + + int m_lastDamageAmount;// how much damage did monster (player) last take + // time based damage counters, decr. 1 per 2 seconds + int m_bloodColor; // color of blood particless + + int m_failSchedule; // Schedule type to choose if current schedule fails + + float m_flHungryTime;// set this is a future time to stop the monster from eating for a while. + + float m_flDistTooFar; // if enemy farther away than this, bits_COND_ENEMY_TOOFAR set in CheckEnemy + float m_flDistLook; // distance monster sees (Default 2048) + + int m_iTriggerCondition;// for scripted AI, this is the condition that will cause the activation of the monster's TriggerTarget + string_t m_iszTriggerTarget;// name of target that should be fired. + + Vector m_HackedGunPos; // HACK until we can query end of gun + +// Scripted sequence Info + SCRIPTSTATE m_scriptState; // internal cinematic state + CCineMonster *m_pCine; + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + void KeyValue( KeyValueData *pkvd ); + +// monster use function + void EXPORT MonsterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT CorpseUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +// overrideable Monster member functions + + virtual int BloodColor( void ) { return m_bloodColor; } + + virtual CBaseMonster *MyMonsterPointer( void ) { return this; } + virtual void Look ( int iDistance );// basic sight function for monsters + virtual void RunAI ( void );// core ai function! + void Listen ( void ); + + virtual BOOL IsAlive( void ) { return (pev->deadflag != DEAD_DEAD); } + virtual BOOL ShouldFadeOnDeath( void ); + +// Basic Monster AI functions + virtual float ChangeYaw ( int speed ); + float VecToYaw( Vector vecDir ); + float FlYawDiff ( void ); + + float DamageForce( float damage ); + +// stuff written for new state machine + virtual void MonsterThink( void ); + void EXPORT CallMonsterThink( void ) { this->MonsterThink(); } + virtual int IRelationship ( CBaseEntity *pTarget ); + virtual void MonsterInit ( void ); + virtual void MonsterInitDead( void ); // Call after animation/pose is set up + virtual void BecomeDead( void ); + void EXPORT CorpseFallThink( void ); + + void EXPORT MonsterInitThink ( void ); + virtual void StartMonster ( void ); + virtual CBaseEntity* BestVisibleEnemy ( void );// finds best visible enemy for attack + virtual BOOL FInViewCone ( CBaseEntity *pEntity );// see if pEntity is in monster's view cone + virtual BOOL FInViewCone ( Vector *pOrigin );// see if given location is in monster's view cone + virtual void HandleAnimEvent( MonsterEvent_t *pEvent ); + + virtual int CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist );// check validity of a straight move through space + virtual void Move( float flInterval = 0.1 ); + virtual void MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ); + virtual BOOL ShouldAdvanceRoute( float flWaypointDist ); + + virtual Activity GetStoppedActivity( void ) { return ACT_IDLE; } + virtual void Stop( void ) { m_IdealActivity = GetStoppedActivity(); } + + // This will stop animation until you call ResetSequenceInfo() at some point in the future + inline void StopAnimation( void ) { pev->framerate = 0; } + + // these functions will survey conditions and set appropriate conditions bits for attack types. + virtual BOOL CheckRangeAttack1( float flDot, float flDist ); + virtual BOOL CheckRangeAttack2( float flDot, float flDist ); + virtual BOOL CheckMeleeAttack1( float flDot, float flDist ); + virtual BOOL CheckMeleeAttack2( float flDot, float flDist ); + + BOOL FHaveSchedule( void ); + BOOL FScheduleValid ( void ); + void ClearSchedule( void ); + BOOL FScheduleDone ( void ); + void ChangeSchedule ( Schedule_t *pNewSchedule ); + void NextScheduledTask ( void ); + Schedule_t *ScheduleInList( const char *pName, Schedule_t **pList, int listCount ); + + virtual Schedule_t *ScheduleFromName( const char *pName ); + static Schedule_t *m_scheduleList[]; + + void MaintainSchedule ( void ); + virtual void StartTask ( Task_t *pTask ); + virtual void RunTask ( Task_t *pTask ); + virtual Schedule_t *GetScheduleOfType( int Type ); + virtual Schedule_t *GetSchedule( void ); + virtual void ScheduleChange( void ) {} + // virtual int CanPlaySequence( void ) { return ((m_pCine == NULL) && (m_MonsterState == MONSTERSTATE_NONE || m_MonsterState == MONSTERSTATE_IDLE || m_IdealMonsterState == MONSTERSTATE_IDLE)); } + virtual int CanPlaySequence( BOOL fDisregardState, int interruptLevel ); + virtual int CanPlaySentence( BOOL fDisregardState ) { return IsAlive(); } + virtual void PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ); + virtual void PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ); + + virtual void SentenceStop( void ); + + Task_t *GetTask ( void ); + virtual MONSTERSTATE GetIdealState ( void ); + virtual void SetActivity ( Activity NewActivity ); + void SetSequenceByName ( char *szSequence ); + void SetState ( MONSTERSTATE State ); + virtual void ReportAIState( void ); + + void CheckAttacks ( CBaseEntity *pTarget, float flDist ); + virtual int CheckEnemy ( CBaseEntity *pEnemy ); + void PushEnemy( CBaseEntity *pEnemy, Vector &vecLastKnownPos ); + BOOL PopEnemy( void ); + + BOOL FGetNodeRoute ( Vector vecDest ); + + inline void TaskComplete( void ) { if ( !HasConditions(bits_COND_TASK_FAILED) ) m_iTaskStatus = TASKSTATUS_COMPLETE; } + void MovementComplete( void ); + inline void TaskFail( void ) { SetConditions(bits_COND_TASK_FAILED); } + inline void TaskBegin( void ) { m_iTaskStatus = TASKSTATUS_RUNNING; } + int TaskIsRunning( void ); + inline int TaskIsComplete( void ) { return (m_iTaskStatus == TASKSTATUS_COMPLETE); } + inline int MovementIsComplete( void ) { return (m_movementGoal == MOVEGOAL_NONE); } + + int IScheduleFlags ( void ); + BOOL FRefreshRoute( void ); + BOOL FRouteClear ( void ); + void RouteSimplify( CBaseEntity *pTargetEnt ); + void AdvanceRoute ( float distance ); + virtual BOOL FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ); + void MakeIdealYaw( Vector vecTarget ); + virtual void SetYawSpeed ( void ) { return; };// allows different yaw_speeds for each activity + BOOL BuildRoute ( const Vector &vecGoal, int iMoveFlag, CBaseEntity *pTarget ); + virtual BOOL BuildNearestRoute ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ); + int RouteClassify( int iMoveFlag ); + void InsertWaypoint ( Vector vecLocation, int afMoveFlags ); + + BOOL FindLateralCover ( const Vector &vecThreat, const Vector &vecViewOffset ); + virtual BOOL FindCover ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ); + virtual BOOL FValidateCover ( const Vector &vecCoverLocation ) { return TRUE; }; + virtual float CoverRadius( void ) { return 784; } // Default cover radius + + virtual BOOL FCanCheckAttacks ( void ); + virtual void CheckAmmo( void ) { return; }; + virtual int IgnoreConditions ( void ); + + inline void SetConditions( int iConditions ) { m_afConditions |= iConditions; } + inline void ClearConditions( int iConditions ) { m_afConditions &= ~iConditions; } + inline BOOL HasConditions( int iConditions ) { if ( m_afConditions & iConditions ) return TRUE; return FALSE; } + inline BOOL HasAllConditions( int iConditions ) { if ( (m_afConditions & iConditions) == iConditions ) return TRUE; return FALSE; } + + virtual BOOL FValidateHintType( short sHint ); + int FindHintNode ( void ); + virtual BOOL FCanActiveIdle ( void ); + void SetTurnActivity ( void ); + float FLSoundVolume ( CSound *pSound ); + + BOOL MoveToNode( Activity movementAct, float waitTime, const Vector &goal ); + BOOL MoveToTarget( Activity movementAct, float waitTime ); + BOOL MoveToLocation( Activity movementAct, float waitTime, const Vector &goal ); + BOOL MoveToEnemy( Activity movementAct, float waitTime ); + + // Returns the time when the door will be open + float OpenDoorAndWait( entvars_t *pevDoor ); + + virtual int ISoundMask( void ); + virtual CSound* PBestSound ( void ); + virtual CSound* PBestScent ( void ); + virtual float HearingSensitivity( void ) { return 1.0; }; + + BOOL FBecomeProne ( void ); + virtual void BarnacleVictimBitten( entvars_t *pevBarnacle ); + virtual void BarnacleVictimReleased( void ); + + void SetEyePosition ( void ); + + BOOL FShouldEat( void );// see if a monster is 'hungry' + void Eat ( float flFullDuration );// make the monster 'full' for a while. + + CBaseEntity *CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ); + BOOL FacingIdeal( void ); + + BOOL FCheckAITrigger( void );// checks and, if necessary, fires the monster's trigger target. + BOOL NoFriendlyFire( void ); + + BOOL BBoxFlat( void ); + + // PrescheduleThink + virtual void PrescheduleThink( void ) { return; }; + + BOOL GetEnemy ( void ); + void MakeDamageBloodDecal ( int cCount, float flNoise, TraceResult *ptr, const Vector &vecDir ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + + // combat functions + float UpdateTarget ( entvars_t *pevTarget ); + virtual Activity GetDeathActivity ( void ); + Activity GetSmallFlinchActivity( void ); + virtual void Killed( entvars_t *pevAttacker, int iGib ); + virtual void GibMonster( void ); + BOOL ShouldGibMonster( int iGib ); + void CallGibMonster( void ); + virtual BOOL HasHumanGibs( void ); + virtual BOOL HasAlienGibs( void ); + virtual void FadeMonster( void ); // Called instead of GibMonster() when gibs are disabled + + Vector ShootAtEnemy( const Vector &shootOrigin ); + virtual Vector BodyTarget( const Vector &posSrc ) { return Center( ) * 0.75 + EyePosition() * 0.25; }; // position to shoot at + + virtual Vector GetGunPosition( void ); + + virtual int TakeHealth( float flHealth, int bitsDamageType ); + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + int DeadTakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + void RadiusDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ); + void RadiusDamage(Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ); + virtual int IsMoving( void ) { return m_movementGoal != MOVEGOAL_NONE; } + + void RouteClear( void ); + void RouteNew( void ); + + virtual void DeathSound ( void ) { return; }; + virtual void AlertSound ( void ) { return; }; + virtual void IdleSound ( void ) { return; }; + virtual void PainSound ( void ) { return; }; + + virtual void StopFollowing( BOOL clearSchedule ) {} + + inline void Remember( int iMemory ) { m_afMemory |= iMemory; } + inline void Forget( int iMemory ) { m_afMemory &= ~iMemory; } + inline BOOL HasMemory( int iMemory ) { if ( m_afMemory & iMemory ) return TRUE; return FALSE; } + inline BOOL HasAllMemories( int iMemory ) { if ( (m_afMemory & iMemory) == iMemory ) return TRUE; return FALSE; } + + BOOL ExitScriptedSequence( ); + BOOL CineCleanup( ); + + CBaseEntity* DropItem ( char *pszItemName, const Vector &vecPos, const Vector &vecAng );// drop an item. +}; + + + +#endif // BASEMONSTER_H diff --git a/dlls/bigmomma.cpp b/dlls/bigmomma.cpp new file mode 100644 index 0000000..096676f --- /dev/null +++ b/dlls/bigmomma.cpp @@ -0,0 +1,1251 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +//========================================================= +// monster template +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "decals.h" +#include "weapons.h" +#include "game.h" + +#define SF_INFOBM_RUN 0x0001 +#define SF_INFOBM_WAIT 0x0002 + +// AI Nodes for Big Momma +class CInfoBM : public CPointEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData* pkvd ); + + // name in pev->targetname + // next in pev->target + // radius in pev->scale + // health in pev->health + // Reach target in pev->message + // Reach delay in pev->speed + // Reach sequence in pev->netname + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_preSequence; +}; + +LINK_ENTITY_TO_CLASS( info_bigmomma, CInfoBM ); + +TYPEDESCRIPTION CInfoBM::m_SaveData[] = +{ + DEFINE_FIELD( CInfoBM, m_preSequence, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CInfoBM, CPointEntity ); + +void CInfoBM::Spawn( void ) +{ +} + + +void CInfoBM::KeyValue( KeyValueData* pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "radius")) + { + pev->scale = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "reachdelay")) + { + pev->speed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "reachtarget")) + { + pev->message = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "reachsequence")) + { + pev->netname = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "presequence")) + { + m_preSequence = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +//========================================================= +// Mortar shot entity +//========================================================= +class CBMortar : public CBaseEntity +{ +public: + void Spawn( void ); + + static CBMortar *Shoot( edict_t *pOwner, Vector vecStart, Vector vecVelocity ); + void Touch( CBaseEntity *pOther ); + void EXPORT Animate( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_maxFrame; +}; + +LINK_ENTITY_TO_CLASS( bmortar, CBMortar ); + +TYPEDESCRIPTION CBMortar::m_SaveData[] = +{ + DEFINE_FIELD( CBMortar, m_maxFrame, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CBMortar, CBaseEntity ); + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define BIG_AE_STEP1 1 // Footstep left +#define BIG_AE_STEP2 2 // Footstep right +#define BIG_AE_STEP3 3 // Footstep back left +#define BIG_AE_STEP4 4 // Footstep back right +#define BIG_AE_SACK 5 // Sack slosh +#define BIG_AE_DEATHSOUND 6 // Death sound + +#define BIG_AE_MELEE_ATTACKBR 8 // Leg attack +#define BIG_AE_MELEE_ATTACKBL 9 // Leg attack +#define BIG_AE_MELEE_ATTACK1 10 // Leg attack +#define BIG_AE_MORTAR_ATTACK1 11 // Launch a mortar +#define BIG_AE_LAY_CRAB 12 // Lay a headcrab +#define BIG_AE_JUMP_FORWARD 13 // Jump up and forward +#define BIG_AE_SCREAM 14 // alert sound +#define BIG_AE_PAIN_SOUND 15 // pain sound +#define BIG_AE_ATTACK_SOUND 16 // attack sound +#define BIG_AE_BIRTH_SOUND 17 // birth sound +#define BIG_AE_EARLY_TARGET 50 // Fire target early + + + +// User defined conditions +#define bits_COND_NODE_SEQUENCE ( bits_COND_SPECIAL1 ) // pev->netname contains the name of a sequence to play + +// Attack distance constants +#define BIG_ATTACKDIST 170 +#define BIG_MORTARDIST 800 +#define BIG_MAXCHILDREN 20 // Max # of live headcrab children + + +#define bits_MEMORY_CHILDPAIR (bits_MEMORY_CUSTOM1) +#define bits_MEMORY_ADVANCE_NODE (bits_MEMORY_CUSTOM2) +#define bits_MEMORY_COMPLETED_NODE (bits_MEMORY_CUSTOM3) +#define bits_MEMORY_FIRED_NODE (bits_MEMORY_CUSTOM4) + +int gSpitSprite, gSpitDebrisSprite; +Vector VecCheckSplatToss( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float maxHeight ); +void MortarSpray( const Vector &position, const Vector &direction, int spriteModel, int count ); + + +// UNDONE: +// +#define BIG_CHILDCLASS "monster_babycrab" + +class CBigMomma : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void Activate( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + void RunTask( Task_t *pTask ); + void StartTask( Task_t *pTask ); + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType( int Type ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); + + void NodeStart( int iszNextNode ); + void NodeReach( void ); + BOOL ShouldGoToNode( void ); + + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void LayHeadcrab( void ); + + int GetNodeSequence( void ) + { + CBaseEntity *pTarget = m_hTargetEnt; + if ( pTarget ) + { + return pTarget->pev->netname; // netname holds node sequence + } + return 0; + } + + + int GetNodePresequence( void ) + { + CInfoBM *pTarget = (CInfoBM *)(CBaseEntity *)m_hTargetEnt; + if ( pTarget ) + { + return pTarget->m_preSequence; + } + return 0; + } + + float GetNodeDelay( void ) + { + CBaseEntity *pTarget = m_hTargetEnt; + if ( pTarget ) + { + return pTarget->pev->speed; // Speed holds node delay + } + return 0; + } + + float GetNodeRange( void ) + { + CBaseEntity *pTarget = m_hTargetEnt; + if ( pTarget ) + { + return pTarget->pev->scale; // Scale holds node delay + } + return 1e6; + } + + float GetNodeYaw( void ) + { + CBaseEntity *pTarget = m_hTargetEnt; + if ( pTarget ) + { + if ( pTarget->pev->angles.y != 0 ) + return pTarget->pev->angles.y; + } + return pev->angles.y; + } + + // Restart the crab count on each new level + void OverrideReset( void ) + { + m_crabCount = 0; + } + + void DeathNotice( entvars_t *pevChild ); + + BOOL CanLayCrab( void ) + { + if ( m_crabTime < gpGlobals->time && m_crabCount < BIG_MAXCHILDREN ) + { + // Don't spawn crabs inside each other + Vector mins = pev->origin - Vector( 32, 32, 0 ); + Vector maxs = pev->origin + Vector( 32, 32, 0 ); + + CBaseEntity *pList[2]; + int count = UTIL_EntitiesInBox( pList, 2, mins, maxs, FL_MONSTER ); + for ( int i = 0; i < count; i++ ) + { + if ( pList[i] != this ) // Don't hurt yourself! + return FALSE; + } + return TRUE; + } + + return FALSE; + } + + void LaunchMortar( void ); + + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector( -95, -95, 0 ); + pev->absmax = pev->origin + Vector( 95, 95, 190 ); + } + + BOOL CheckMeleeAttack1( float flDot, float flDist ); // Slash + BOOL CheckMeleeAttack2( float flDot, float flDist ); // Lay a crab + BOOL CheckRangeAttack1( float flDot, float flDist ); // Mortar launch + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + static const char *pChildDieSounds[]; + static const char *pSackSounds[]; + static const char *pDeathSounds[]; + static const char *pAttackSounds[]; + static const char *pAttackHitSounds[]; + static const char *pBirthSounds[]; + static const char *pAlertSounds[]; + static const char *pPainSounds[]; + static const char *pFootSounds[]; + + CUSTOM_SCHEDULES; + +private: + float m_nodeTime; + float m_crabTime; + float m_mortarTime; + float m_painSoundTime; + int m_crabCount; +}; +LINK_ENTITY_TO_CLASS( monster_bigmomma, CBigMomma ); + +TYPEDESCRIPTION CBigMomma::m_SaveData[] = +{ + DEFINE_FIELD( CBigMomma, m_nodeTime, FIELD_TIME ), + DEFINE_FIELD( CBigMomma, m_crabTime, FIELD_TIME ), + DEFINE_FIELD( CBigMomma, m_mortarTime, FIELD_TIME ), + DEFINE_FIELD( CBigMomma, m_painSoundTime, FIELD_TIME ), + DEFINE_FIELD( CBigMomma, m_crabCount, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CBigMomma, CBaseMonster ); + +const char *CBigMomma::pChildDieSounds[] = +{ + "gonarch/gon_childdie1.wav", + "gonarch/gon_childdie2.wav", + "gonarch/gon_childdie3.wav", +}; + +const char *CBigMomma::pSackSounds[] = +{ + "gonarch/gon_sack1.wav", + "gonarch/gon_sack2.wav", + "gonarch/gon_sack3.wav", +}; + +const char *CBigMomma::pDeathSounds[] = +{ + "gonarch/gon_die1.wav", +}; + +const char *CBigMomma::pAttackSounds[] = +{ + "gonarch/gon_attack1.wav", + "gonarch/gon_attack2.wav", + "gonarch/gon_attack3.wav", +}; +const char *CBigMomma::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CBigMomma::pBirthSounds[] = +{ + "gonarch/gon_birth1.wav", + "gonarch/gon_birth2.wav", + "gonarch/gon_birth3.wav", +}; + +const char *CBigMomma::pAlertSounds[] = +{ + "gonarch/gon_alert1.wav", + "gonarch/gon_alert2.wav", + "gonarch/gon_alert3.wav", +}; + +const char *CBigMomma::pPainSounds[] = +{ + "gonarch/gon_pain2.wav", + "gonarch/gon_pain4.wav", + "gonarch/gon_pain5.wav", +}; + +const char *CBigMomma::pFootSounds[] = +{ + "gonarch/gon_step1.wav", + "gonarch/gon_step2.wav", + "gonarch/gon_step3.wav", +}; + + + +void CBigMomma :: KeyValue( KeyValueData *pkvd ) +{ +#if 0 + if (FStrEq(pkvd->szKeyName, "volume")) + { + m_volume = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else +#endif + CBaseMonster::KeyValue( pkvd ); +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CBigMomma :: Classify ( void ) +{ + return CLASS_ALIEN_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CBigMomma :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 100; + break; + default: + ys = 90; + } + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CBigMomma :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case BIG_AE_MELEE_ATTACKBR: + case BIG_AE_MELEE_ATTACKBL: + case BIG_AE_MELEE_ATTACK1: + { + Vector forward, right; + + UTIL_MakeVectorsPrivate( pev->angles, forward, right, NULL ); + + Vector center = pev->origin + forward * 128; + Vector mins = center - Vector( 64, 64, 0 ); + Vector maxs = center + Vector( 64, 64, 64 ); + + CBaseEntity *pList[8]; + int count = UTIL_EntitiesInBox( pList, 8, mins, maxs, FL_MONSTER|FL_CLIENT ); + CBaseEntity *pHurt = NULL; + + for ( int i = 0; i < count && !pHurt; i++ ) + { + if ( pList[i] != this ) + { + if ( pList[i]->pev->owner != edict() ) + pHurt = pList[i]; + } + } + + if ( pHurt ) + { + pHurt->TakeDamage( pev, pev, gSkillData.bigmommaDmgSlash, DMG_CRUSH | DMG_SLASH ); + pHurt->pev->punchangle.x = 15; + switch( pEvent->event ) + { + case BIG_AE_MELEE_ATTACKBR: + pHurt->pev->velocity = pHurt->pev->velocity + (forward * 150) + Vector(0,0,250) - (right * 200); + break; + + case BIG_AE_MELEE_ATTACKBL: + pHurt->pev->velocity = pHurt->pev->velocity + (forward * 150) + Vector(0,0,250) + (right * 200); + break; + + case BIG_AE_MELEE_ATTACK1: + pHurt->pev->velocity = pHurt->pev->velocity + (forward * 220) + Vector(0,0,200); + break; + } + + pHurt->pev->flags &= ~FL_ONGROUND; + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pAttackHitSounds), 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + } + } + break; + + case BIG_AE_SCREAM: + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pAlertSounds ); + break; + + case BIG_AE_PAIN_SOUND: + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pPainSounds ); + break; + + case BIG_AE_ATTACK_SOUND: + EMIT_SOUND_ARRAY_DYN( CHAN_WEAPON, pAttackSounds ); + break; + + case BIG_AE_BIRTH_SOUND: + EMIT_SOUND_ARRAY_DYN( CHAN_BODY, pBirthSounds ); + break; + + case BIG_AE_SACK: + if ( RANDOM_LONG(0,100) < 30 ) + EMIT_SOUND_ARRAY_DYN( CHAN_BODY, pSackSounds ); + break; + + case BIG_AE_DEATHSOUND: + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pDeathSounds ); + break; + + case BIG_AE_STEP1: // Footstep left + case BIG_AE_STEP3: // Footstep back left + EMIT_SOUND_ARRAY_DYN( CHAN_ITEM, pFootSounds ); + break; + + case BIG_AE_STEP4: // Footstep back right + case BIG_AE_STEP2: // Footstep right + EMIT_SOUND_ARRAY_DYN( CHAN_BODY, pFootSounds ); + break; + + case BIG_AE_MORTAR_ATTACK1: + LaunchMortar(); + break; + + case BIG_AE_LAY_CRAB: + LayHeadcrab(); + break; + + case BIG_AE_JUMP_FORWARD: + ClearBits( pev->flags, FL_ONGROUND ); + + UTIL_SetOrigin (pev, pev->origin + Vector ( 0 , 0 , 1) );// take him off ground so engine doesn't instantly reset onground + UTIL_MakeVectors ( pev->angles ); + + pev->velocity = (gpGlobals->v_forward * 200) + gpGlobals->v_up * 500; + break; + + case BIG_AE_EARLY_TARGET: + { + CBaseEntity *pTarget = m_hTargetEnt; + if ( pTarget && pTarget->pev->message ) + FireTargets( STRING(pTarget->pev->message), this, this, USE_TOGGLE, 0 ); + Remember( bits_MEMORY_FIRED_NODE ); + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +void CBigMomma :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + if ( ptr->iHitgroup != 1 ) + { + // didn't hit the sack? + + if ( pev->dmgtime != gpGlobals->time || (RANDOM_LONG(0,10) < 1) ) + { + UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT( 1, 2) ); + pev->dmgtime = gpGlobals->time; + } + + flDamage = 0.1;// don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated + } + else if ( gpGlobals->time > m_painSoundTime ) + { + m_painSoundTime = gpGlobals->time + RANDOM_LONG(1, 3); + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pPainSounds ); + } + + + CBaseMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + + +int CBigMomma :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // Don't take any acid damage -- BigMomma's mortar is acid + if ( bitsDamageType & DMG_ACID ) + flDamage = 0; + + if ( !HasMemory(bits_MEMORY_PATH_FINISHED) ) + { + if ( pev->health <= flDamage ) + { + pev->health = flDamage + 1; + Remember( bits_MEMORY_ADVANCE_NODE | bits_MEMORY_COMPLETED_NODE ); + ALERT( at_aiconsole, "BM: Finished node health!!!\n" ); + } + } + + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CBigMomma :: LayHeadcrab( void ) +{ + CBaseEntity *pChild = CBaseEntity::Create( BIG_CHILDCLASS, pev->origin, pev->angles, edict() ); + + pChild->pev->spawnflags |= SF_MONSTER_FALL_TO_GROUND; + + // Is this the second crab in a pair? + if ( HasMemory( bits_MEMORY_CHILDPAIR ) ) + { + m_crabTime = gpGlobals->time + RANDOM_FLOAT( 5, 10 ); + Forget( bits_MEMORY_CHILDPAIR ); + } + else + { + m_crabTime = gpGlobals->time + RANDOM_FLOAT( 0.5, 2.5 ); + Remember( bits_MEMORY_CHILDPAIR ); + } + + TraceResult tr; + UTIL_TraceLine( pev->origin, pev->origin - Vector(0,0,100), ignore_monsters, edict(), &tr); + UTIL_DecalTrace( &tr, DECAL_MOMMABIRTH ); + + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pBirthSounds), 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + m_crabCount++; +} + + + +void CBigMomma::DeathNotice( entvars_t *pevChild ) +{ + if ( m_crabCount > 0 ) // Some babies may cross a transition, but we reset the count then + m_crabCount--; + if ( IsAlive() ) + { + // Make the "my baby's dead" noise! + EMIT_SOUND_ARRAY_DYN( CHAN_WEAPON, pChildDieSounds ); + } +} + + +void CBigMomma::LaunchMortar( void ) +{ + m_mortarTime = gpGlobals->time + RANDOM_FLOAT( 2, 15 ); + + Vector startPos = pev->origin; + startPos.z += 180; + + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pSackSounds), 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + CBMortar *pBomb = CBMortar::Shoot( edict(), startPos, pev->movedir ); + pBomb->pev->gravity = 1.0; + MortarSpray( startPos, Vector(0,0,1), gSpitSprite, 24 ); +} + +//========================================================= +// Spawn +//========================================================= +void CBigMomma :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/big_mom.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->health = 150 * gSkillData.bigmommaHealthFactor; + pev->view_ofs = Vector ( 0, 0, 128 );// position of the eyes relative to monster's origin. + m_flFieldOfView = 0.3;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CBigMomma :: Precache() +{ + PRECACHE_MODEL("models/big_mom.mdl"); + + PRECACHE_SOUND_ARRAY( pChildDieSounds ); + PRECACHE_SOUND_ARRAY( pSackSounds ); + PRECACHE_SOUND_ARRAY( pDeathSounds ); + PRECACHE_SOUND_ARRAY( pAttackSounds ); + PRECACHE_SOUND_ARRAY( pAttackHitSounds ); + PRECACHE_SOUND_ARRAY( pBirthSounds ); + PRECACHE_SOUND_ARRAY( pAlertSounds ); + PRECACHE_SOUND_ARRAY( pPainSounds ); + PRECACHE_SOUND_ARRAY( pFootSounds ); + + UTIL_PrecacheOther( BIG_CHILDCLASS ); + + // TEMP: Squid + PRECACHE_MODEL("sprites/mommaspit.spr");// spit projectile. + gSpitSprite = PRECACHE_MODEL("sprites/mommaspout.spr");// client side spittle. + gSpitDebrisSprite = PRECACHE_MODEL("sprites/mommablob.spr" ); + + PRECACHE_SOUND( "bullchicken/bc_acid1.wav" ); + PRECACHE_SOUND( "bullchicken/bc_spithit1.wav" ); + PRECACHE_SOUND( "bullchicken/bc_spithit2.wav" ); +} + + +void CBigMomma::Activate( void ) +{ + if ( m_hTargetEnt == NULL ) + Remember( bits_MEMORY_ADVANCE_NODE ); // Start 'er up +} + + +void CBigMomma::NodeStart( int iszNextNode ) +{ + pev->netname = iszNextNode; + + CBaseEntity *pTarget = NULL; + + if ( pev->netname ) + { + edict_t *pentTarget = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(pev->netname) ); + + if ( !FNullEnt(pentTarget) ) + pTarget = Instance( pentTarget ); + } + + + if ( !pTarget ) + { + ALERT( at_aiconsole, "BM: Finished the path!!\n" ); + Remember( bits_MEMORY_PATH_FINISHED ); + return; + } + Remember( bits_MEMORY_ON_PATH ); + m_hTargetEnt = pTarget; +} + + +void CBigMomma::NodeReach( void ) +{ + CBaseEntity *pTarget = m_hTargetEnt; + + Forget( bits_MEMORY_ADVANCE_NODE ); + + if ( !pTarget ) + return; + + if ( pTarget->pev->health ) + pev->max_health = pev->health = pTarget->pev->health * gSkillData.bigmommaHealthFactor; + + if ( !HasMemory( bits_MEMORY_FIRED_NODE ) ) + { + if ( pTarget->pev->message ) + FireTargets( STRING(pTarget->pev->message), this, this, USE_TOGGLE, 0 ); + } + Forget( bits_MEMORY_FIRED_NODE ); + + pev->netname = pTarget->pev->target; + if ( pTarget->pev->health == 0 ) + Remember( bits_MEMORY_ADVANCE_NODE ); // Move on if no health at this node +} + + + // Slash +BOOL CBigMomma::CheckMeleeAttack1( float flDot, float flDist ) +{ + if (flDot >= 0.7) + { + if ( flDist <= BIG_ATTACKDIST ) + return TRUE; + } + return FALSE; +} + + +// Lay a crab +BOOL CBigMomma::CheckMeleeAttack2( float flDot, float flDist ) +{ + return CanLayCrab(); +} + + +// Mortar launch +BOOL CBigMomma::CheckRangeAttack1( float flDot, float flDist ) +{ + if ( flDist <= BIG_MORTARDIST && m_mortarTime < gpGlobals->time ) + { + CBaseEntity *pEnemy = m_hEnemy; + + if ( pEnemy ) + { + Vector startPos = pev->origin; + startPos.z += 180; + pev->movedir = VecCheckSplatToss( pev, startPos, pEnemy->BodyTarget( pev->origin ), RANDOM_FLOAT( 150, 500 ) ); + if ( pev->movedir != g_vecZero ) + return TRUE; + } + } + return FALSE; +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +enum +{ + SCHED_BIG_NODE = LAST_COMMON_SCHEDULE + 1, + SCHED_NODE_FAIL, +}; + +enum +{ + TASK_MOVE_TO_NODE_RANGE = LAST_COMMON_TASK + 1, // Move within node range + TASK_FIND_NODE, // Find my next node + TASK_PLAY_NODE_PRESEQUENCE, // Play node pre-script + TASK_PLAY_NODE_SEQUENCE, // Play node script + TASK_PROCESS_NODE, // Fire targets, etc. + TASK_WAIT_NODE, // Wait at the node + TASK_NODE_DELAY, // Delay walking toward node for a bit. You've failed to get there + TASK_NODE_YAW, // Get the best facing direction for this node +}; + + +Task_t tlBigNode[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_NODE_FAIL }, + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_NODE, (float)0 }, // Find my next node + { TASK_PLAY_NODE_PRESEQUENCE,(float)0 }, // Play the pre-approach sequence if any + { TASK_MOVE_TO_NODE_RANGE, (float)0 }, // Move within node range + { TASK_STOP_MOVING, (float)0 }, + { TASK_NODE_YAW, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_WAIT_NODE, (float)0 }, // Wait for node delay + { TASK_PLAY_NODE_SEQUENCE, (float)0 }, // Play the sequence if one exists + { TASK_PROCESS_NODE, (float)0 }, // Fire targets, etc. + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slBigNode[] = +{ + { + tlBigNode, + ARRAYSIZE ( tlBigNode ), + 0, + 0, + "Big Node" + }, +}; + + +Task_t tlNodeFail[] = +{ + { TASK_NODE_DELAY, (float)10 }, // Try to do something else for 10 seconds + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slNodeFail[] = +{ + { + tlNodeFail, + ARRAYSIZE ( tlNodeFail ), + 0, + 0, + "NodeFail" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CBigMomma ) +{ + slBigNode, + slNodeFail, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CBigMomma, CBaseMonster ); + + + + +Schedule_t *CBigMomma::GetScheduleOfType( int Type ) +{ + switch( Type ) + { + case SCHED_BIG_NODE: + return slBigNode; + break; + + case SCHED_NODE_FAIL: + return slNodeFail; + break; + } + + return CBaseMonster::GetScheduleOfType( Type ); +} + + +BOOL CBigMomma::ShouldGoToNode( void ) +{ + if ( HasMemory( bits_MEMORY_ADVANCE_NODE ) ) + { + if ( m_nodeTime < gpGlobals->time ) + return TRUE; + } + return FALSE; +} + + + +Schedule_t *CBigMomma::GetSchedule( void ) +{ + if ( ShouldGoToNode() ) + { + return GetScheduleOfType( SCHED_BIG_NODE ); + } + + return CBaseMonster::GetSchedule(); +} + + +void CBigMomma::StartTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_FIND_NODE: + { + CBaseEntity *pTarget = m_hTargetEnt; + if ( !HasMemory( bits_MEMORY_ADVANCE_NODE ) ) + { + if ( pTarget ) + pev->netname = m_hTargetEnt->pev->target; + } + NodeStart( pev->netname ); + TaskComplete(); + ALERT( at_aiconsole, "BM: Found node %s\n", STRING(pev->netname) ); + } + break; + + case TASK_NODE_DELAY: + m_nodeTime = gpGlobals->time + pTask->flData; + TaskComplete(); + ALERT( at_aiconsole, "BM: FAIL! Delay %.2f\n", pTask->flData ); + break; + + case TASK_PROCESS_NODE: + ALERT( at_aiconsole, "BM: Reached node %s\n", STRING(pev->netname) ); + NodeReach(); + TaskComplete(); + break; + + case TASK_PLAY_NODE_PRESEQUENCE: + case TASK_PLAY_NODE_SEQUENCE: + { + int sequence; + if ( pTask->iTask == TASK_PLAY_NODE_SEQUENCE ) + sequence = GetNodeSequence(); + else + sequence = GetNodePresequence(); + + ALERT( at_aiconsole, "BM: Playing node sequence %s\n", STRING(sequence) ); + if ( sequence ) + { + sequence = LookupSequence( STRING( sequence ) ); + if ( sequence != -1 ) + { + pev->sequence = sequence; + pev->frame = 0; + ResetSequenceInfo( ); + ALERT( at_aiconsole, "BM: Sequence %s\n", STRING(GetNodeSequence()) ); + return; + } + } + TaskComplete(); + } + break; + + case TASK_NODE_YAW: + pev->ideal_yaw = GetNodeYaw(); + TaskComplete(); + break; + + case TASK_WAIT_NODE: + m_flWait = gpGlobals->time + GetNodeDelay(); + if ( m_hTargetEnt->pev->spawnflags & SF_INFOBM_WAIT ) + ALERT( at_aiconsole, "BM: Wait at node %s forever\n", STRING(pev->netname) ); + else + ALERT( at_aiconsole, "BM: Wait at node %s for %.2f\n", STRING(pev->netname), GetNodeDelay() ); + break; + + + case TASK_MOVE_TO_NODE_RANGE: + { + CBaseEntity *pTarget = m_hTargetEnt; + if ( !pTarget ) + TaskFail(); + else + { + if ( (pTarget->pev->origin - pev->origin).Length() < GetNodeRange() ) + TaskComplete(); + else + { + Activity act = ACT_WALK; + if ( pTarget->pev->spawnflags & SF_INFOBM_RUN ) + act = ACT_RUN; + + m_vecMoveGoal = pTarget->pev->origin; + if ( !MoveToTarget( act, 2 ) ) + { + TaskFail(); + } + } + } + } + ALERT( at_aiconsole, "BM: Moving to node %s\n", STRING(pev->netname) ); + + break; + + case TASK_MELEE_ATTACK1: + // Play an attack sound here + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pAttackSounds), 1.0, ATTN_NORM, 0, PITCH_NORM ); + CBaseMonster::StartTask( pTask ); + break; + + default: + CBaseMonster::StartTask( pTask ); + break; + } +} + +//========================================================= +// RunTask +//========================================================= +void CBigMomma::RunTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_MOVE_TO_NODE_RANGE: + { + float distance; + + if ( m_hTargetEnt == NULL ) + TaskFail(); + else + { + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + // Set the appropriate activity based on an overlapping range + // overlap the range to prevent oscillation + if ( (distance < GetNodeRange()) || MovementIsComplete() ) + { + ALERT( at_aiconsole, "BM: Reached node!\n" ); + TaskComplete(); + RouteClear(); // Stop moving + } + } + } + + break; + + case TASK_WAIT_NODE: + if ( m_hTargetEnt != NULL && (m_hTargetEnt->pev->spawnflags & SF_INFOBM_WAIT) ) + return; + + if ( gpGlobals->time > m_flWaitFinished ) + TaskComplete(); + ALERT( at_aiconsole, "BM: The WAIT is over!\n" ); + break; + + case TASK_PLAY_NODE_PRESEQUENCE: + case TASK_PLAY_NODE_SEQUENCE: + if ( m_fSequenceFinished ) + { + m_Activity = ACT_RESET; + TaskComplete(); + } + break; + + default: + CBaseMonster::RunTask( pTask ); + break; + } +} + + + +Vector VecCheckSplatToss( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float maxHeight ) +{ + TraceResult tr; + Vector vecMidPoint;// halfway point between Spot1 and Spot2 + Vector vecApex;// highest point + Vector vecScale; + Vector vecGrenadeVel; + Vector vecTemp; + float flGravity = g_psv_gravity->value; + + // calculate the midpoint and apex of the 'triangle' + vecMidPoint = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5; + UTIL_TraceLine(vecMidPoint, vecMidPoint + Vector(0,0,maxHeight), ignore_monsters, ENT(pev), &tr); + vecApex = tr.vecEndPos; + + UTIL_TraceLine(vecSpot1, vecApex, dont_ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + // Don't worry about actually hitting the target, this won't hurt us! + + // How high should the grenade travel (subtract 15 so the grenade doesn't hit the ceiling)? + float height = (vecApex.z - vecSpot1.z) - 15; + // How fast does the grenade need to travel to reach that height given gravity? + float speed = sqrt( 2 * flGravity * height ); + + // How much time does it take to get there? + float time = speed / flGravity; + vecGrenadeVel = (vecSpot2 - vecSpot1); + vecGrenadeVel.z = 0; + float distance = vecGrenadeVel.Length(); + + // Travel half the distance to the target in that time (apex is at the midpoint) + vecGrenadeVel = vecGrenadeVel * ( 0.5 / time ); + // Speed to offset gravity at the desired height + vecGrenadeVel.z = speed; + + return vecGrenadeVel; +} + + + + +// --------------------------------- +// +// Mortar +// +// --------------------------------- +void MortarSpray( const Vector &position, const Vector &direction, int spriteModel, int count ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, position ); + WRITE_BYTE( TE_SPRITE_SPRAY ); + WRITE_COORD( position.x); // pos + WRITE_COORD( position.y); + WRITE_COORD( position.z); + WRITE_COORD( direction.x); // dir + WRITE_COORD( direction.y); + WRITE_COORD( direction.z); + WRITE_SHORT( spriteModel ); // model + WRITE_BYTE ( count ); // count + WRITE_BYTE ( 130 ); // speed + WRITE_BYTE ( 80 ); // noise ( client will divide by 100 ) + MESSAGE_END(); +} + + +// UNDONE: right now this is pretty much a copy of the squid spit with minor changes to the way it does damage +void CBMortar:: Spawn( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->classname = MAKE_STRING( "bmortar" ); + + pev->solid = SOLID_BBOX; + pev->rendermode = kRenderTransAlpha; + pev->renderamt = 255; + + SET_MODEL(ENT(pev), "sprites/mommaspit.spr"); + pev->frame = 0; + pev->scale = 0.5; + + UTIL_SetSize( pev, Vector( 0, 0, 0), Vector(0, 0, 0) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; + pev->dmgtime = gpGlobals->time + 0.4; +} + +void CBMortar::Animate( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if ( gpGlobals->time > pev->dmgtime ) + { + pev->dmgtime = gpGlobals->time + 0.2; + MortarSpray( pev->origin, -pev->velocity.Normalize(), gSpitSprite, 3 ); + } + if ( pev->frame++ ) + { + if ( pev->frame > m_maxFrame ) + { + pev->frame = 0; + } + } +} + +CBMortar *CBMortar::Shoot( edict_t *pOwner, Vector vecStart, Vector vecVelocity ) +{ + CBMortar *pSpit = GetClassPtr( (CBMortar *)NULL ); + pSpit->Spawn(); + + UTIL_SetOrigin( pSpit->pev, vecStart ); + pSpit->pev->velocity = vecVelocity; + pSpit->pev->owner = pOwner; + pSpit->pev->scale = 2.5; + pSpit->SetThink ( &CBMortar::Animate ); + pSpit->pev->nextthink = gpGlobals->time + 0.1; + + return pSpit; +} + + +void CBMortar::Touch( CBaseEntity *pOther ) +{ + TraceResult tr; + int iPitch; + + // splat sound + iPitch = RANDOM_FLOAT( 90, 110 ); + + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_acid1.wav", 1, ATTN_NORM, 0, iPitch ); + + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_spithit1.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_spithit2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } + + if ( pOther->IsBSPModel() ) + { + + // make a splat on the wall + UTIL_TraceLine( pev->origin, pev->origin + pev->velocity * 10, dont_ignore_monsters, ENT( pev ), &tr ); + UTIL_DecalTrace(&tr, DECAL_MOMMASPLAT); + } + else + { + tr.vecEndPos = pev->origin; + tr.vecPlaneNormal = -1 * pev->velocity.Normalize(); + } + // make some flecks + MortarSpray( tr.vecEndPos, tr.vecPlaneNormal, gSpitSprite, 24 ); + + entvars_t *pevOwner = NULL; + if ( pev->owner ) + pevOwner = VARS(pev->owner); + + RadiusDamage( pev->origin, pev, pevOwner, gSkillData.bigmommaDmgBlast, gSkillData.bigmommaRadiusBlast, CLASS_NONE, DMG_ACID ); + UTIL_Remove( this ); +} + +#endif diff --git a/dlls/bloater.cpp b/dlls/bloater.cpp new file mode 100644 index 0000000..eb0c734 --- /dev/null +++ b/dlls/bloater.cpp @@ -0,0 +1,219 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Bloater +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define BLOATER_AE_ATTACK_MELEE1 0x01 + + +class CBloater : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + + void PainSound( void ); + void AlertSound( void ); + void IdleSound( void ); + void AttackSnd( void ); + + // No range attacks + BOOL CheckRangeAttack1 ( float flDot, float flDist ) { return FALSE; } + BOOL CheckRangeAttack2 ( float flDot, float flDist ) { return FALSE; } + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); +}; + +LINK_ENTITY_TO_CLASS( monster_bloater, CBloater ); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CBloater :: Classify ( void ) +{ + return CLASS_ALIEN_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CBloater :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + +#if 0 + switch ( m_Activity ) + { + } +#endif + + pev->yaw_speed = ys; +} + +int CBloater :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + PainSound(); + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CBloater :: PainSound( void ) +{ +#if 0 + int pitch = 95 + RANDOM_LONG(0,9); + + switch (RANDOM_LONG(0,5)) + { + case 0: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_pain1.wav", 1.0, ATTN_NORM, 0, pitch); + break; + case 1: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_pain2.wav", 1.0, ATTN_NORM, 0, pitch); + break; + default: + break; + } +#endif +} + +void CBloater :: AlertSound( void ) +{ +#if 0 + int pitch = 95 + RANDOM_LONG(0,9); + + switch (RANDOM_LONG(0,2)) + { + case 0: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_alert10.wav", 1.0, ATTN_NORM, 0, pitch); + break; + case 1: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_alert20.wav", 1.0, ATTN_NORM, 0, pitch); + break; + case 2: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_alert30.wav", 1.0, ATTN_NORM, 0, pitch); + break; + } +#endif +} + +void CBloater :: IdleSound( void ) +{ +#if 0 + int pitch = 95 + RANDOM_LONG(0,9); + + switch (RANDOM_LONG(0,2)) + { + case 0: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_idle1.wav", 1.0, ATTN_NORM, 0, pitch); + break; + case 1: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_idle2.wav", 1.0, ATTN_NORM, 0, pitch); + break; + case 2: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_idle3.wav", 1.0, ATTN_NORM, 0, pitch); + break; + } +#endif +} + +void CBloater :: AttackSnd( void ) +{ +#if 0 + int pitch = 95 + RANDOM_LONG(0,9); + + switch (RANDOM_LONG(0,1)) + { + case 0: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_attack1.wav", 1.0, ATTN_NORM, 0, pitch); + break; + case 1: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_attack2.wav", 1.0, ATTN_NORM, 0, pitch); + break; + } +#endif +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CBloater :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case BLOATER_AE_ATTACK_MELEE1: + { + // do stuff for this event. + AttackSnd(); + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CBloater :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/floater.mdl"); + UTIL_SetSize( pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_FLY; + pev->spawnflags |= FL_FLY; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->health = 40; + pev->view_ofs = VEC_VIEW;// position of the eyes relative to monster's origin. + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CBloater :: Precache() +{ + PRECACHE_MODEL("models/floater.mdl"); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + diff --git a/dlls/bmodels.cpp b/dlls/bmodels.cpp new file mode 100644 index 0000000..a585402 --- /dev/null +++ b/dlls/bmodels.cpp @@ -0,0 +1,958 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== bmodels.cpp ======================================================== + + spawn, think, and use functions for entities that use brush models + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "doors.h" + +extern DLL_GLOBAL Vector g_vecAttackDir; + +#define SF_BRUSH_ACCDCC 16// brush should accelerate and decelerate when toggled +#define SF_BRUSH_HURT 32// rotating brush that inflicts pain based on rotation speed +#define SF_ROTATING_NOT_SOLID 64 // some special rotating objects are not solid. + +// covering cheesy noise1, noise2, & noise3 fields so they make more sense (for rotating fans) +#define noiseStart noise1 +#define noiseStop noise2 +#define noiseRunning noise3 + +#define SF_PENDULUM_SWING 2 // spawnflag that makes a pendulum a rope swing. +// +// BModelOrigin - calculates origin of a bmodel from absmin/size because all bmodel origins are 0 0 0 +// +Vector VecBModelOrigin( entvars_t* pevBModel ) +{ + return pevBModel->absmin + ( pevBModel->size * 0.5 ); +} + +// =================== FUNC_WALL ============================================== + +/*QUAKED func_wall (0 .5 .8) ? +This is just a solid wall if not inhibited +*/ +class CFuncWall : public CBaseEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + // Bmodels don't go across transitions + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +LINK_ENTITY_TO_CLASS( func_wall, CFuncWall ); + +void CFuncWall :: Spawn( void ) +{ + pev->angles = g_vecZero; + pev->movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + pev->solid = SOLID_BSP; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + // If it can't move/go away, it's really part of the world + pev->flags |= FL_WORLDBRUSH; +} + + +void CFuncWall :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( ShouldToggle( useType, (int)(pev->frame)) ) + pev->frame = 1 - pev->frame; +} + + +#define SF_WALL_START_OFF 0x0001 + +class CFuncWallToggle : public CFuncWall +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void TurnOff( void ); + void TurnOn( void ); + BOOL IsOn( void ); +}; + +LINK_ENTITY_TO_CLASS( func_wall_toggle, CFuncWallToggle ); + +void CFuncWallToggle :: Spawn( void ) +{ + CFuncWall::Spawn(); + if ( pev->spawnflags & SF_WALL_START_OFF ) + TurnOff(); +} + + +void CFuncWallToggle :: TurnOff( void ) +{ + pev->solid = SOLID_NOT; + pev->effects |= EF_NODRAW; + UTIL_SetOrigin( pev, pev->origin ); +} + + +void CFuncWallToggle :: TurnOn( void ) +{ + pev->solid = SOLID_BSP; + pev->effects &= ~EF_NODRAW; + UTIL_SetOrigin( pev, pev->origin ); +} + + +BOOL CFuncWallToggle :: IsOn( void ) +{ + if ( pev->solid == SOLID_NOT ) + return FALSE; + return TRUE; +} + + +void CFuncWallToggle :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int status = IsOn(); + + if ( ShouldToggle( useType, status ) ) + { + if ( status ) + TurnOff(); + else + TurnOn(); + } +} + + +#define SF_CONVEYOR_VISUAL 0x0001 +#define SF_CONVEYOR_NOTSOLID 0x0002 + +class CFuncConveyor : public CFuncWall +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void UpdateSpeed( float speed ); +}; + +LINK_ENTITY_TO_CLASS( func_conveyor, CFuncConveyor ); +void CFuncConveyor :: Spawn( void ) +{ + SetMovedir( pev ); + CFuncWall::Spawn(); + + if ( !(pev->spawnflags & SF_CONVEYOR_VISUAL) ) + SetBits( pev->flags, FL_CONVEYOR ); + + // HACKHACK - This is to allow for some special effects + if ( pev->spawnflags & SF_CONVEYOR_NOTSOLID ) + { + pev->solid = SOLID_NOT; + pev->skin = 0; // Don't want the engine thinking we've got special contents on this brush + } + + if ( pev->speed == 0 ) + pev->speed = 100; + + UpdateSpeed( pev->speed ); +} + + +// HACKHACK -- This is ugly, but encode the speed in the rendercolor to avoid adding more data to the network stream +void CFuncConveyor :: UpdateSpeed( float speed ) +{ + // Encode it as an integer with 4 fractional bits + int speedCode = (int)(fabs(speed) * 16.0); + + if ( speed < 0 ) + pev->rendercolor.x = 1; + else + pev->rendercolor.x = 0; + + pev->rendercolor.y = (speedCode >> 8); + pev->rendercolor.z = (speedCode & 0xFF); +} + + +void CFuncConveyor :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + pev->speed = -pev->speed; + UpdateSpeed( pev->speed ); +} + + + +// =================== FUNC_ILLUSIONARY ============================================== + + +/*QUAKED func_illusionary (0 .5 .8) ? +A simple entity that looks solid but lets you walk through it. +*/ +class CFuncIllusionary : public CBaseToggle +{ +public: + void Spawn( void ); + void EXPORT SloshTouch( CBaseEntity *pOther ); + void KeyValue( KeyValueData *pkvd ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +LINK_ENTITY_TO_CLASS( func_illusionary, CFuncIllusionary ); + +void CFuncIllusionary :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "skin"))//skin is used for content type + { + pev->skin = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CFuncIllusionary :: Spawn( void ) +{ + pev->angles = g_vecZero; + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT;// always solid_not + SET_MODEL( ENT(pev), STRING(pev->model) ); + + // I'd rather eat the network bandwidth of this than figure out how to save/restore + // these entities after they have been moved to the client, or respawn them ala Quake + // Perhaps we can do this in deathmatch only. + // MAKE_STATIC(ENT(pev)); +} + + +// ------------------------------------------------------------------------------- +// +// Monster only clip brush +// +// This brush will be solid for any entity who has the FL_MONSTERCLIP flag set +// in pev->flags +// +// otherwise it will be invisible and not solid. This can be used to keep +// specific monsters out of certain areas +// +// ------------------------------------------------------------------------------- +class CFuncMonsterClip : public CFuncWall +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) {} // Clear out func_wall's use function +}; + +LINK_ENTITY_TO_CLASS( func_monsterclip, CFuncMonsterClip ); + +void CFuncMonsterClip::Spawn( void ) +{ + CFuncWall::Spawn(); + if ( CVAR_GET_FLOAT("showtriggers") == 0 ) + pev->effects = EF_NODRAW; + pev->flags |= FL_MONSTERCLIP; +} + + +// =================== FUNC_ROTATING ============================================== +class CFuncRotating : public CBaseEntity +{ +public: + // basic functions + void Spawn( void ); + void Precache( void ); + void EXPORT SpinUp ( void ); + void EXPORT SpinDown ( void ); + void KeyValue( KeyValueData* pkvd); + void EXPORT HurtTouch ( CBaseEntity *pOther ); + void EXPORT RotatingUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Rotate( void ); + void RampPitchVol (int fUp ); + void Blocked( CBaseEntity *pOther ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flFanFriction; + float m_flAttenuation; + float m_flVolume; + float m_pitch; + int m_sounds; +}; + +TYPEDESCRIPTION CFuncRotating::m_SaveData[] = +{ + DEFINE_FIELD( CFuncRotating, m_flFanFriction, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_flAttenuation, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_flVolume, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_pitch, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_sounds, FIELD_INTEGER ) +}; + +IMPLEMENT_SAVERESTORE( CFuncRotating, CBaseEntity ); + + +LINK_ENTITY_TO_CLASS( func_rotating, CFuncRotating ); + +void CFuncRotating :: KeyValue( KeyValueData* pkvd) +{ + if (FStrEq(pkvd->szKeyName, "fanfriction")) + { + m_flFanFriction = atof(pkvd->szValue)/100; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "Volume")) + { + m_flVolume = atof(pkvd->szValue)/10.0; + + if (m_flVolume > 1.0) + m_flVolume = 1.0; + if (m_flVolume < 0.0) + m_flVolume = 0.0; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spawnorigin")) + { + Vector tmp; + UTIL_StringToVector( (float *)tmp, pkvd->szValue ); + if ( tmp != g_vecZero ) + pev->origin = tmp; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +/*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS +You need to have an origin brush as part of this entity. The +center of that brush will be +the point around which it is rotated. It will rotate around the Z +axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"speed" determines how fast it moves; default value is 100. +"dmg" damage to inflict when blocked (2 default) + +REVERSE will cause the it to rotate in the opposite direction. +*/ + + +void CFuncRotating :: Spawn( ) +{ + // set final pitch. Must not be PITCH_NORM, since we + // plan on pitch shifting later. + + m_pitch = PITCH_NORM - 1; + + // maintain compatibility with previous maps + if (m_flVolume == 0.0) + m_flVolume = 1.0; + + // if the designer didn't set a sound attenuation, default to one. + m_flAttenuation = ATTN_NORM; + + if ( FBitSet ( pev->spawnflags, SF_BRUSH_ROTATE_SMALLRADIUS) ) + { + m_flAttenuation = ATTN_IDLE; + } + else if ( FBitSet ( pev->spawnflags, SF_BRUSH_ROTATE_MEDIUMRADIUS) ) + { + m_flAttenuation = ATTN_STATIC; + } + else if ( FBitSet ( pev->spawnflags, SF_BRUSH_ROTATE_LARGERADIUS) ) + { + m_flAttenuation = ATTN_NORM; + } + + // prevent divide by zero if level designer forgets friction! + if ( m_flFanFriction == 0 ) + { + m_flFanFriction = 1; + } + + if ( FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_Z_AXIS) ) + pev->movedir = Vector(0,0,1); + else if ( FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_X_AXIS) ) + pev->movedir = Vector(1,0,0); + else + pev->movedir = Vector(0,1,0); // y-axis + + // check for reverse rotation + if ( FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_BACKWARDS) ) + pev->movedir = pev->movedir * -1; + + // some rotating objects like fake volumetric lights will not be solid. + if ( FBitSet(pev->spawnflags, SF_ROTATING_NOT_SOLID) ) + { + pev->solid = SOLID_NOT; + pev->skin = CONTENTS_EMPTY; + pev->movetype = MOVETYPE_PUSH; + } + else + { + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + } + + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + SetUse( &CFuncRotating::RotatingUse ); + // did level designer forget to assign speed? + if (pev->speed <= 0) + pev->speed = 0; + + // Removed this per level designers request. -- JAY + // if (pev->dmg == 0) + // pev->dmg = 2; + + // instant-use brush? + if ( FBitSet( pev->spawnflags, SF_BRUSH_ROTATE_INSTANT) ) + { + SetThink( &CFuncRotating::SUB_CallUseToggle ); + pev->nextthink = pev->ltime + 1.5; // leave a magic delay for client to start up + } + // can this brush inflict pain? + if ( FBitSet (pev->spawnflags, SF_BRUSH_HURT) ) + { + SetTouch( &CFuncRotating::HurtTouch ); + } + + Precache( ); +} + + +void CFuncRotating :: Precache( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + + // set up fan sounds + + if (!FStringNull( pev->message ) && strlen( szSoundFile ) > 0) + { + // if a path is set for a wave, use it + + PRECACHE_SOUND(szSoundFile); + + pev->noiseRunning = ALLOC_STRING(szSoundFile); + } else + { + // otherwise use preset sound + switch (m_sounds) + { + case 1: + PRECACHE_SOUND ("fans/fan1.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan1.wav"); + break; + case 2: + PRECACHE_SOUND ("fans/fan2.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan2.wav"); + break; + case 3: + PRECACHE_SOUND ("fans/fan3.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan3.wav"); + break; + case 4: + PRECACHE_SOUND ("fans/fan4.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan4.wav"); + break; + case 5: + PRECACHE_SOUND ("fans/fan5.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan5.wav"); + break; + + case 0: + default: + if (!FStringNull( pev->message ) && strlen( szSoundFile ) > 0) + { + PRECACHE_SOUND(szSoundFile); + + pev->noiseRunning = ALLOC_STRING(szSoundFile); + break; + } else + { + pev->noiseRunning = ALLOC_STRING("common/null.wav"); + break; + } + } + } + + if (pev->avelocity != g_vecZero ) + { + // if fan was spinning, and we went through transition or save/restore, + // make sure we restart the sound. 1.5 sec delay is magic number. KDB + + SetThink ( &CFuncRotating::SpinUp ); + pev->nextthink = pev->ltime + 1.5; + } +} + + + +// +// Touch - will hurt others based on how fast the brush is spinning +// +void CFuncRotating :: HurtTouch ( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + // we can't hurt this thing, so we're not concerned with it + if ( !pevOther->takedamage ) + return; + + // calculate damage based on rotation speed + pev->dmg = pev->avelocity.Length() / 10; + + pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH); + + pevOther->velocity = (pevOther->origin - VecBModelOrigin(pev) ).Normalize() * pev->dmg; +} + +// +// RampPitchVol - ramp pitch and volume up to final values, based on difference +// between how fast we're going vs how fast we plan to go +// +#define FANPITCHMIN 30 +#define FANPITCHMAX 100 + +void CFuncRotating :: RampPitchVol (int fUp) +{ + + Vector vecAVel = pev->avelocity; + vec_t vecCur; + vec_t vecFinal; + float fpct; + float fvol; + float fpitch; + int pitch; + + // get current angular velocity + + vecCur = abs(vecAVel.x != 0 ? vecAVel.x : (vecAVel.y != 0 ? vecAVel.y : vecAVel.z)); + + // get target angular velocity + + vecFinal = (pev->movedir.x != 0 ? pev->movedir.x : (pev->movedir.y != 0 ? pev->movedir.y : pev->movedir.z)); + vecFinal *= pev->speed; + vecFinal = abs(vecFinal); + + // calc volume and pitch as % of final vol and pitch + + fpct = vecCur / vecFinal; +// if (fUp) +// fvol = m_flVolume * (0.5 + fpct/2.0); // spinup volume ramps up from 50% max vol +// else + fvol = m_flVolume * fpct; // slowdown volume ramps down to 0 + + fpitch = FANPITCHMIN + (FANPITCHMAX - FANPITCHMIN) * fpct; + + pitch = (int) fpitch; + if (pitch == PITCH_NORM) + pitch = PITCH_NORM-1; + + // change the fan's vol and pitch + + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + fvol, m_flAttenuation, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch); + +} + +// +// SpinUp - accelerates a non-moving func_rotating up to it's speed +// +void CFuncRotating :: SpinUp( void ) +{ + Vector vecAVel;//rotational velocity + + pev->nextthink = pev->ltime + 0.1; + pev->avelocity = pev->avelocity + ( pev->movedir * ( pev->speed * m_flFanFriction ) ); + + vecAVel = pev->avelocity;// cache entity's rotational velocity + + // if we've met or exceeded target speed, set target speed and stop thinking + if ( abs(vecAVel.x) >= abs(pev->movedir.x * pev->speed) && + abs(vecAVel.y) >= abs(pev->movedir.y * pev->speed) && + abs(vecAVel.z) >= abs(pev->movedir.z * pev->speed) ) + { + pev->avelocity = pev->movedir * pev->speed;// set speed in case we overshot + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + m_flVolume, m_flAttenuation, SND_CHANGE_PITCH | SND_CHANGE_VOL, FANPITCHMAX); + + SetThink( &CFuncRotating::Rotate ); + Rotate(); + } + else + { + RampPitchVol(TRUE); + } +} + +// +// SpinDown - decelerates a moving func_rotating to a standstill. +// +void CFuncRotating :: SpinDown( void ) +{ + Vector vecAVel;//rotational velocity + vec_t vecdir; + + pev->nextthink = pev->ltime + 0.1; + + pev->avelocity = pev->avelocity - ( pev->movedir * ( pev->speed * m_flFanFriction ) );//spin down slower than spinup + + vecAVel = pev->avelocity;// cache entity's rotational velocity + + if (pev->movedir.x != 0) + vecdir = pev->movedir.x; + else if (pev->movedir.y != 0) + vecdir = pev->movedir.y; + else + vecdir = pev->movedir.z; + + // if we've met or exceeded target speed, set target speed and stop thinking + // (note: must check for movedir > 0 or < 0) + if (((vecdir > 0) && (vecAVel.x <= 0 && vecAVel.y <= 0 && vecAVel.z <= 0)) || + ((vecdir < 0) && (vecAVel.x >= 0 && vecAVel.y >= 0 && vecAVel.z >= 0))) + { + pev->avelocity = g_vecZero;// set speed in case we overshot + + // stop sound, we're done + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning /* Stop */), + 0, 0, SND_STOP, m_pitch); + + SetThink( &CFuncRotating::Rotate ); + Rotate(); + } + else + { + RampPitchVol(FALSE); + } +} + +void CFuncRotating :: Rotate( void ) +{ + pev->nextthink = pev->ltime + 10; +} + +//========================================================= +// Rotating Use - when a rotating brush is triggered +//========================================================= +void CFuncRotating :: RotatingUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // is this a brush that should accelerate and decelerate when turned on/off (fan)? + if ( FBitSet ( pev->spawnflags, SF_BRUSH_ACCDCC ) ) + { + // fan is spinning, so stop it. + if ( pev->avelocity != g_vecZero ) + { + SetThink ( &CFuncRotating::SpinDown ); + //EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, (char *)STRING(pev->noiseStop), + // m_flVolume, m_flAttenuation, 0, m_pitch); + + pev->nextthink = pev->ltime + 0.1; + } + else// fan is not moving, so start it + { + SetThink ( &CFuncRotating::SpinUp ); + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + 0.01, m_flAttenuation, 0, FANPITCHMIN); + + pev->nextthink = pev->ltime + 0.1; + } + } + else if ( !FBitSet ( pev->spawnflags, SF_BRUSH_ACCDCC ) )//this is a normal start/stop brush. + { + if ( pev->avelocity != g_vecZero ) + { + // play stopping sound here + SetThink ( &CFuncRotating::SpinDown ); + + // EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, (char *)STRING(pev->noiseStop), + // m_flVolume, m_flAttenuation, 0, m_pitch); + + pev->nextthink = pev->ltime + 0.1; + // pev->avelocity = g_vecZero; + } + else + { + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + m_flVolume, m_flAttenuation, 0, FANPITCHMAX); + pev->avelocity = pev->movedir * pev->speed; + + SetThink( &CFuncRotating::Rotate ); + Rotate(); + } + } +} + + +// +// RotatingBlocked - An entity has blocked the brush +// +void CFuncRotating :: Blocked( CBaseEntity *pOther ) + +{ + pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH); +} + + + + + + +//#endif + + +class CPendulum : public CBaseEntity +{ +public: + void Spawn ( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT Swing( void ); + void EXPORT PendulumUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Stop( void ); + void Touch( CBaseEntity *pOther ); + void EXPORT RopeTouch ( CBaseEntity *pOther );// this touch func makes the pendulum a rope + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + void Blocked( CBaseEntity *pOther ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_accel; // Acceleration + float m_distance; // + float m_time; + float m_damp; + float m_maxSpeed; + float m_dampSpeed; + vec3_t m_center; + vec3_t m_start; +}; + +LINK_ENTITY_TO_CLASS( func_pendulum, CPendulum ); + +TYPEDESCRIPTION CPendulum::m_SaveData[] = +{ + DEFINE_FIELD( CPendulum, m_accel, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_distance, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_time, FIELD_TIME ), + DEFINE_FIELD( CPendulum, m_damp, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_maxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_dampSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_center, FIELD_VECTOR ), + DEFINE_FIELD( CPendulum, m_start, FIELD_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CPendulum, CBaseEntity ); + + + +void CPendulum :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "distance")) + { + m_distance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damp")) + { + m_damp = atof(pkvd->szValue) * 0.001; + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +void CPendulum :: Spawn( void ) +{ + // set the axis of rotation + CBaseToggle :: AxisDir( pev ); + + if ( FBitSet (pev->spawnflags, SF_DOOR_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + if ( m_distance == 0 ) + return; + + if (pev->speed == 0) + pev->speed = 100; + + m_accel = (pev->speed * pev->speed) / (2 * fabs(m_distance)); // Calculate constant acceleration from speed and distance + m_maxSpeed = pev->speed; + m_start = pev->angles; + m_center = pev->angles + (m_distance * 0.5) * pev->movedir; + + if ( FBitSet( pev->spawnflags, SF_BRUSH_ROTATE_INSTANT) ) + { + SetThink( &CPendulum::SUB_CallUseToggle ); + pev->nextthink = gpGlobals->time + 0.1; + } + pev->speed = 0; + SetUse( &CPendulum::PendulumUse ); + + if ( FBitSet( pev->spawnflags, SF_PENDULUM_SWING ) ) + { + SetTouch ( &CPendulum::RopeTouch ); + } +} + + +void CPendulum :: PendulumUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->speed ) // Pendulum is moving, stop it and auto-return if necessary + { + if ( FBitSet( pev->spawnflags, SF_PENDULUM_AUTO_RETURN ) ) + { + float delta; + + delta = CBaseToggle :: AxisDelta( pev->spawnflags, pev->angles, m_start ); + + pev->avelocity = m_maxSpeed * pev->movedir; + pev->nextthink = pev->ltime + (delta / m_maxSpeed); + SetThink( &CPendulum::Stop ); + } + else + { + pev->speed = 0; // Dead stop + SetThink( NULL ); + pev->avelocity = g_vecZero; + } + } + else + { + pev->nextthink = pev->ltime + 0.1; // Start the pendulum moving + m_time = gpGlobals->time; // Save time to calculate dt + SetThink( &CPendulum::Swing ); + m_dampSpeed = m_maxSpeed; + } +} + + +void CPendulum :: Stop( void ) +{ + pev->angles = m_start; + pev->speed = 0; + SetThink( NULL ); + pev->avelocity = g_vecZero; +} + + +void CPendulum::Blocked( CBaseEntity *pOther ) +{ + m_time = gpGlobals->time; +} + + +void CPendulum :: Swing( void ) +{ + float delta, dt; + + delta = CBaseToggle :: AxisDelta( pev->spawnflags, pev->angles, m_center ); + dt = gpGlobals->time - m_time; // How much time has passed? + m_time = gpGlobals->time; // Remember the last time called + + if ( delta > 0 && m_accel > 0 ) + pev->speed -= m_accel * dt; // Integrate velocity + else + pev->speed += m_accel * dt; + + if ( pev->speed > m_maxSpeed ) + pev->speed = m_maxSpeed; + else if ( pev->speed < -m_maxSpeed ) + pev->speed = -m_maxSpeed; + // scale the destdelta vector by the time spent traveling to get velocity + pev->avelocity = pev->speed * pev->movedir; + + // Call this again + pev->nextthink = pev->ltime + 0.1; + + if ( m_damp ) + { + m_dampSpeed -= m_damp * m_dampSpeed * dt; + if ( m_dampSpeed < 30.0 ) + { + pev->angles = m_center; + pev->speed = 0; + SetThink( NULL ); + pev->avelocity = g_vecZero; + } + else if ( pev->speed > m_dampSpeed ) + pev->speed = m_dampSpeed; + else if ( pev->speed < -m_dampSpeed ) + pev->speed = -m_dampSpeed; + + } +} + + +void CPendulum :: Touch ( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + if ( pev->dmg <= 0 ) + return; + + // we can't hurt this thing, so we're not concerned with it + if ( !pevOther->takedamage ) + return; + + // calculate damage based on rotation speed + float damage = pev->dmg * pev->speed * 0.01; + + if ( damage < 0 ) + damage = -damage; + + pOther->TakeDamage( pev, pev, damage, DMG_CRUSH ); + + pevOther->velocity = (pevOther->origin - VecBModelOrigin(pev) ).Normalize() * damage; +} + +void CPendulum :: RopeTouch ( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + if ( !pOther->IsPlayer() ) + {// not a player! + ALERT ( at_console, "Not a client\n" ); + return; + } + + if ( ENT(pevOther) == pev->enemy ) + {// this player already on the rope. + return; + } + + pev->enemy = pOther->edict(); + pevOther->velocity = g_vecZero; + pevOther->movetype = MOVETYPE_NONE; +} + + diff --git a/dlls/bullsquid.cpp b/dlls/bullsquid.cpp new file mode 100644 index 0000000..b2a7ead --- /dev/null +++ b/dlls/bullsquid.cpp @@ -0,0 +1,1275 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// bullsquid - big, spotty tentacle-mouthed meanie. +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "nodes.h" +#include "effects.h" +#include "decals.h" +#include "soundent.h" +#include "game.h" + +#define SQUID_SPRINT_DIST 256 // how close the squid has to get before starting to sprint and refusing to swerve + +int iSquidSpitSprite; + + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_SQUID_HURTHOP = LAST_COMMON_SCHEDULE + 1, + SCHED_SQUID_SMELLFOOD, + SCHED_SQUID_SEECRAB, + SCHED_SQUID_EAT, + SCHED_SQUID_SNIFF_AND_EAT, + SCHED_SQUID_WALLOW, +}; + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_SQUID_HOPTURN = LAST_COMMON_TASK + 1, +}; + +//========================================================= +// Bullsquid's spit projectile +//========================================================= +class CSquidSpit : public CBaseEntity +{ +public: + void Spawn( void ); + + static void Shoot( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + void Touch( CBaseEntity *pOther ); + void EXPORT Animate( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_maxFrame; +}; + +LINK_ENTITY_TO_CLASS( squidspit, CSquidSpit ); + +TYPEDESCRIPTION CSquidSpit::m_SaveData[] = +{ + DEFINE_FIELD( CSquidSpit, m_maxFrame, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CSquidSpit, CBaseEntity ); + +void CSquidSpit:: Spawn( void ) +{ + pev->movetype = MOVETYPE_FLY; + pev->classname = MAKE_STRING( "squidspit" ); + + pev->solid = SOLID_BBOX; + pev->rendermode = kRenderTransAlpha; + pev->renderamt = 255; + + SET_MODEL(ENT(pev), "sprites/bigspit.spr"); + pev->frame = 0; + pev->scale = 0.5; + + UTIL_SetSize( pev, Vector( 0, 0, 0), Vector(0, 0, 0) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; +} + +void CSquidSpit::Animate( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if ( pev->frame++ ) + { + if ( pev->frame > m_maxFrame ) + { + pev->frame = 0; + } + } +} + +void CSquidSpit::Shoot( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ) +{ + CSquidSpit *pSpit = GetClassPtr( (CSquidSpit *)NULL ); + pSpit->Spawn(); + + UTIL_SetOrigin( pSpit->pev, vecStart ); + pSpit->pev->velocity = vecVelocity; + pSpit->pev->owner = ENT(pevOwner); + + pSpit->SetThink ( &CSquidSpit::Animate ); + pSpit->pev->nextthink = gpGlobals->time + 0.1; +} + +void CSquidSpit :: Touch ( CBaseEntity *pOther ) +{ + TraceResult tr; + int iPitch; + + // splat sound + iPitch = RANDOM_FLOAT( 90, 110 ); + + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_acid1.wav", 1, ATTN_NORM, 0, iPitch ); + + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_spithit1.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_spithit2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } + + if ( !pOther->pev->takedamage ) + { + + // make a splat on the wall + UTIL_TraceLine( pev->origin, pev->origin + pev->velocity * 10, dont_ignore_monsters, ENT( pev ), &tr ); + UTIL_DecalTrace(&tr, DECAL_SPIT1 + RANDOM_LONG(0,1)); + + // make some flecks + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, tr.vecEndPos ); + WRITE_BYTE( TE_SPRITE_SPRAY ); + WRITE_COORD( tr.vecEndPos.x); // pos + WRITE_COORD( tr.vecEndPos.y); + WRITE_COORD( tr.vecEndPos.z); + WRITE_COORD( tr.vecPlaneNormal.x); // dir + WRITE_COORD( tr.vecPlaneNormal.y); + WRITE_COORD( tr.vecPlaneNormal.z); + WRITE_SHORT( iSquidSpitSprite ); // model + WRITE_BYTE ( 5 ); // count + WRITE_BYTE ( 30 ); // speed + WRITE_BYTE ( 80 ); // noise ( client will divide by 100 ) + MESSAGE_END(); + } + else + { + pOther->TakeDamage ( pev, pev, gSkillData.bullsquidDmgSpit, DMG_GENERIC ); + } + + SetThink ( &CSquidSpit::SUB_Remove ); + pev->nextthink = gpGlobals->time; +} + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define BSQUID_AE_SPIT ( 1 ) +#define BSQUID_AE_BITE ( 2 ) +#define BSQUID_AE_BLINK ( 3 ) +#define BSQUID_AE_TAILWHIP ( 4 ) +#define BSQUID_AE_HOP ( 5 ) +#define BSQUID_AE_THROW ( 6 ) + +class CBullsquid : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int ISoundMask( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void IdleSound( void ); + void PainSound( void ); + void DeathSound( void ); + void AlertSound ( void ); + void AttackSound( void ); + void StartTask ( Task_t *pTask ); + void RunTask ( Task_t *pTask ); + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); + BOOL CheckMeleeAttack2 ( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + void RunAI( void ); + BOOL FValidateHintType ( short sHint ); + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType ( int Type ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + int IRelationship ( CBaseEntity *pTarget ); + int IgnoreConditions ( void ); + MONSTERSTATE GetIdealState ( void ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + + CUSTOM_SCHEDULES; + static TYPEDESCRIPTION m_SaveData[]; + + BOOL m_fCanThreatDisplay;// this is so the squid only does the "I see a headcrab!" dance one time. + + float m_flLastHurtTime;// we keep track of this, because if something hurts a squid, it will forget about its love of headcrabs for a while. + float m_flNextSpitTime;// last time the bullsquid used the spit attack. +}; +LINK_ENTITY_TO_CLASS( monster_bullchicken, CBullsquid ); + +TYPEDESCRIPTION CBullsquid::m_SaveData[] = +{ + DEFINE_FIELD( CBullsquid, m_fCanThreatDisplay, FIELD_BOOLEAN ), + DEFINE_FIELD( CBullsquid, m_flLastHurtTime, FIELD_TIME ), + DEFINE_FIELD( CBullsquid, m_flNextSpitTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CBullsquid, CBaseMonster ); + +//========================================================= +// IgnoreConditions +//========================================================= +int CBullsquid::IgnoreConditions ( void ) +{ + int iIgnore = CBaseMonster::IgnoreConditions(); + + if ( gpGlobals->time - m_flLastHurtTime <= 20 ) + { + // haven't been hurt in 20 seconds, so let the squid care about stink. + iIgnore = bits_COND_SMELL | bits_COND_SMELL_FOOD; + } + + if ( m_hEnemy != NULL ) + { + if ( FClassnameIs( m_hEnemy->pev, "monster_headcrab" ) ) + { + // (Unless after a tasty headcrab) + iIgnore = bits_COND_SMELL | bits_COND_SMELL_FOOD; + } + } + + + return iIgnore; +} + +//========================================================= +// IRelationship - overridden for bullsquid so that it can +// be made to ignore its love of headcrabs for a while. +//========================================================= +int CBullsquid::IRelationship ( CBaseEntity *pTarget ) +{ + if ( gpGlobals->time - m_flLastHurtTime < 5 && FClassnameIs ( pTarget->pev, "monster_headcrab" ) ) + { + // if squid has been hurt in the last 5 seconds, and is getting relationship for a headcrab, + // tell squid to disregard crab. + return R_NO; + } + + return CBaseMonster :: IRelationship ( pTarget ); +} + +//========================================================= +// TakeDamage - overridden for bullsquid so we can keep track +// of how much time has passed since it was last injured +//========================================================= +int CBullsquid :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + float flDist; + Vector vecApex; + + // if the squid is running, has an enemy, was hurt by the enemy, hasn't been hurt in the last 3 seconds, and isn't too close to the enemy, + // it will swerve. (whew). + if ( m_hEnemy != NULL && IsMoving() && pevAttacker == m_hEnemy->pev && gpGlobals->time - m_flLastHurtTime > 3 ) + { + flDist = ( pev->origin - m_hEnemy->pev->origin ).Length2D(); + + if ( flDist > SQUID_SPRINT_DIST ) + { + flDist = ( pev->origin - m_Route[ m_iRouteIndex ].vecLocation ).Length2D();// reusing flDist. + + if ( FTriangulate( pev->origin, m_Route[ m_iRouteIndex ].vecLocation, flDist * 0.5, m_hEnemy, &vecApex ) ) + { + InsertWaypoint( vecApex, bits_MF_TO_DETOUR | bits_MF_DONT_SIMPLIFY ); + } + } + } + + if ( !FClassnameIs ( pevAttacker, "monster_headcrab" ) ) + { + // don't forget about headcrabs if it was a headcrab that hurt the squid. + m_flLastHurtTime = gpGlobals->time; + } + + return CBaseMonster :: TakeDamage ( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CBullsquid :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( IsMoving() && flDist >= 512 ) + { + // squid will far too far behind if he stops running to spit at this distance from the enemy. + return FALSE; + } + + if ( flDist > 64 && flDist <= 784 && flDot >= 0.5 && gpGlobals->time >= m_flNextSpitTime ) + { + if ( m_hEnemy != NULL ) + { + if ( fabs( pev->origin.z - m_hEnemy->pev->origin.z ) > 256 ) + { + // don't try to spit at someone up really high or down really low. + return FALSE; + } + } + + if ( IsMoving() ) + { + // don't spit again for a long time, resume chasing enemy. + m_flNextSpitTime = gpGlobals->time + 5; + } + else + { + // not moving, so spit again pretty soon. + m_flNextSpitTime = gpGlobals->time + 0.5; + } + + return TRUE; + } + + return FALSE; +} + +//========================================================= +// CheckMeleeAttack1 - bullsquid is a big guy, so has a longer +// melee range than most monsters. This is the tailwhip attack +//========================================================= +BOOL CBullsquid :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + if ( m_hEnemy->pev->health <= gSkillData.bullsquidDmgWhip && flDist <= 85 && flDot >= 0.7 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckMeleeAttack2 - bullsquid is a big guy, so has a longer +// melee range than most monsters. This is the bite attack. +// this attack will not be performed if the tailwhip attack +// is valid. +//========================================================= +BOOL CBullsquid :: CheckMeleeAttack2 ( float flDot, float flDist ) +{ + if ( flDist <= 85 && flDot >= 0.7 && !HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) // The player & bullsquid can be as much as their bboxes + { // apart (48 * sqrt(3)) and he can still attack (85 is a little more than 48*sqrt(3)) + return TRUE; + } + return FALSE; +} + +//========================================================= +// FValidateHintType +//========================================================= +BOOL CBullsquid :: FValidateHintType ( short sHint ) +{ + int i; + + static short sSquidHints[] = + { + HINT_WORLD_HUMAN_BLOOD, + }; + + for ( i = 0 ; i < ARRAYSIZE ( sSquidHints ) ; i++ ) + { + if ( sSquidHints[ i ] == sHint ) + { + return TRUE; + } + } + + ALERT ( at_aiconsole, "Couldn't validate hint type" ); + return FALSE; +} + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. In the base class implementation, +// monsters care about all sounds, but no scents. +//========================================================= +int CBullsquid :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_CARCASS | + bits_SOUND_MEAT | + bits_SOUND_GARBAGE | + bits_SOUND_PLAYER; +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CBullsquid :: Classify ( void ) +{ + return CLASS_ALIEN_PREDATOR; +} + +//========================================================= +// IdleSound +//========================================================= +#define SQUID_ATTN_IDLE (float)1.5 +void CBullsquid :: IdleSound ( void ) +{ + switch ( RANDOM_LONG(0,4) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle1.wav", 1, SQUID_ATTN_IDLE ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle2.wav", 1, SQUID_ATTN_IDLE ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle3.wav", 1, SQUID_ATTN_IDLE ); + break; + case 3: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle4.wav", 1, SQUID_ATTN_IDLE ); + break; + case 4: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle5.wav", 1, SQUID_ATTN_IDLE ); + break; + } +} + +//========================================================= +// PainSound +//========================================================= +void CBullsquid :: PainSound ( void ) +{ + int iPitch = RANDOM_LONG( 85, 120 ); + + switch ( RANDOM_LONG(0,3) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_pain1.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_pain2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 2: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_pain3.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 3: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_pain4.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } +} + +//========================================================= +// AlertSound +//========================================================= +void CBullsquid :: AlertSound ( void ) +{ + int iPitch = RANDOM_LONG( 140, 160 ); + + switch ( RANDOM_LONG ( 0, 1 ) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle1.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CBullsquid :: SetYawSpeed ( void ) +{ + int ys; + + ys = 0; + + switch ( m_Activity ) + { + case ACT_WALK: ys = 90; break; + case ACT_RUN: ys = 90; break; + case ACT_IDLE: ys = 90; break; + case ACT_RANGE_ATTACK1: ys = 90; break; + default: + ys = 90; + break; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CBullsquid :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case BSQUID_AE_SPIT: + { + Vector vecSpitOffset; + Vector vecSpitDir; + + UTIL_MakeVectors ( pev->angles ); + + // !!!HACKHACK - the spot at which the spit originates (in front of the mouth) was measured in 3ds and hardcoded here. + // we should be able to read the position of bones at runtime for this info. + vecSpitOffset = ( gpGlobals->v_right * 8 + gpGlobals->v_forward * 37 + gpGlobals->v_up * 23 ); + vecSpitOffset = ( pev->origin + vecSpitOffset ); + vecSpitDir = ( ( m_hEnemy->pev->origin + m_hEnemy->pev->view_ofs ) - vecSpitOffset ).Normalize(); + + vecSpitDir.x += RANDOM_FLOAT( -0.05, 0.05 ); + vecSpitDir.y += RANDOM_FLOAT( -0.05, 0.05 ); + vecSpitDir.z += RANDOM_FLOAT( -0.05, 0 ); + + + // do stuff for this event. + AttackSound(); + + // spew the spittle temporary ents. + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpitOffset ); + WRITE_BYTE( TE_SPRITE_SPRAY ); + WRITE_COORD( vecSpitOffset.x); // pos + WRITE_COORD( vecSpitOffset.y); + WRITE_COORD( vecSpitOffset.z); + WRITE_COORD( vecSpitDir.x); // dir + WRITE_COORD( vecSpitDir.y); + WRITE_COORD( vecSpitDir.z); + WRITE_SHORT( iSquidSpitSprite ); // model + WRITE_BYTE ( 15 ); // count + WRITE_BYTE ( 210 ); // speed + WRITE_BYTE ( 25 ); // noise ( client will divide by 100 ) + MESSAGE_END(); + + CSquidSpit::Shoot( pev, vecSpitOffset, vecSpitDir * 900 ); + } + break; + + case BSQUID_AE_BITE: + { + // SOUND HERE! + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.bullsquidDmgBite, DMG_SLASH ); + + if ( pHurt ) + { + //pHurt->pev->punchangle.z = -15; + //pHurt->pev->punchangle.x = -45; + pHurt->pev->velocity = pHurt->pev->velocity - gpGlobals->v_forward * 100; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_up * 100; + } + } + break; + + case BSQUID_AE_TAILWHIP: + { + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.bullsquidDmgWhip, DMG_CLUB | DMG_ALWAYSGIB ); + if ( pHurt ) + { + pHurt->pev->punchangle.z = -20; + pHurt->pev->punchangle.x = 20; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_right * 200; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_up * 100; + } + } + break; + + case BSQUID_AE_BLINK: + { + // close eye. + pev->skin = 1; + } + break; + + case BSQUID_AE_HOP: + { + float flGravity = g_psv_gravity->value; + + // throw the squid up into the air on this frame. + if ( FBitSet ( pev->flags, FL_ONGROUND ) ) + { + pev->flags -= FL_ONGROUND; + } + + // jump into air for 0.8 (24/30) seconds +// pev->velocity.z += (0.875 * flGravity) * 0.5; + pev->velocity.z += (0.625 * flGravity) * 0.5; + } + break; + + case BSQUID_AE_THROW: + { + int iPitch; + + // squid throws its prey IF the prey is a client. + CBaseEntity *pHurt = CheckTraceHullAttack( 70, 0, 0 ); + + + if ( pHurt ) + { + // croonchy bite sound + iPitch = RANDOM_FLOAT( 90, 110 ); + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_bite2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_bite3.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } + + + //pHurt->pev->punchangle.x = RANDOM_LONG(0,34) - 5; + //pHurt->pev->punchangle.z = RANDOM_LONG(0,49) - 25; + //pHurt->pev->punchangle.y = RANDOM_LONG(0,89) - 45; + + // screeshake transforms the viewmodel as well as the viewangle. No problems with seeing the ends of the viewmodels. + UTIL_ScreenShake( pHurt->pev->origin, 25.0, 1.5, 0.7, 2 ); + + if ( pHurt->IsPlayer() ) + { + UTIL_MakeVectors( pev->angles ); + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 300 + gpGlobals->v_up * 300; + } + } + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + } +} + +//========================================================= +// Spawn +//========================================================= +void CBullsquid :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/bullsquid.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->effects = 0; + pev->health = gSkillData.bullsquidHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + m_fCanThreatDisplay = TRUE; + m_flNextSpitTime = gpGlobals->time; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CBullsquid :: Precache() +{ + PRECACHE_MODEL("models/bullsquid.mdl"); + + PRECACHE_MODEL("sprites/bigspit.spr");// spit projectile. + + iSquidSpitSprite = PRECACHE_MODEL("sprites/tinyspit.spr");// client side spittle. + + PRECACHE_SOUND("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event + + PRECACHE_SOUND("bullchicken/bc_attack2.wav"); + PRECACHE_SOUND("bullchicken/bc_attack3.wav"); + + PRECACHE_SOUND("bullchicken/bc_die1.wav"); + PRECACHE_SOUND("bullchicken/bc_die2.wav"); + PRECACHE_SOUND("bullchicken/bc_die3.wav"); + + PRECACHE_SOUND("bullchicken/bc_idle1.wav"); + PRECACHE_SOUND("bullchicken/bc_idle2.wav"); + PRECACHE_SOUND("bullchicken/bc_idle3.wav"); + PRECACHE_SOUND("bullchicken/bc_idle4.wav"); + PRECACHE_SOUND("bullchicken/bc_idle5.wav"); + + PRECACHE_SOUND("bullchicken/bc_pain1.wav"); + PRECACHE_SOUND("bullchicken/bc_pain2.wav"); + PRECACHE_SOUND("bullchicken/bc_pain3.wav"); + PRECACHE_SOUND("bullchicken/bc_pain4.wav"); + + PRECACHE_SOUND("bullchicken/bc_attackgrowl.wav"); + PRECACHE_SOUND("bullchicken/bc_attackgrowl2.wav"); + PRECACHE_SOUND("bullchicken/bc_attackgrowl3.wav"); + + PRECACHE_SOUND("bullchicken/bc_acid1.wav"); + + PRECACHE_SOUND("bullchicken/bc_bite2.wav"); + PRECACHE_SOUND("bullchicken/bc_bite3.wav"); + + PRECACHE_SOUND("bullchicken/bc_spithit1.wav"); + PRECACHE_SOUND("bullchicken/bc_spithit2.wav"); + +} + +//========================================================= +// DeathSound +//========================================================= +void CBullsquid :: DeathSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_die1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_die2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_die3.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// AttackSound +//========================================================= +void CBullsquid :: AttackSound ( void ) +{ + switch ( RANDOM_LONG(0,1) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "bullchicken/bc_attack2.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "bullchicken/bc_attack3.wav", 1, ATTN_NORM ); + break; + } +} + + +//======================================================== +// RunAI - overridden for bullsquid because there are things +// that need to be checked every think. +//======================================================== +void CBullsquid :: RunAI ( void ) +{ + // first, do base class stuff + CBaseMonster :: RunAI(); + + if ( pev->skin != 0 ) + { + // close eye if it was open. + pev->skin = 0; + } + + if ( RANDOM_LONG(0,39) == 0 ) + { + pev->skin = 1; + } + + if ( m_hEnemy != NULL && m_Activity == ACT_RUN ) + { + // chasing enemy. Sprint for last bit + if ( (pev->origin - m_hEnemy->pev->origin).Length2D() < SQUID_SPRINT_DIST ) + { + pev->framerate = 1.25; + } + } + +} + +//======================================================== +// AI Schedules Specific to this monster +//========================================================= + +// primary range attack +Task_t tlSquidRangeAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slSquidRangeAttack1[] = +{ + { + tlSquidRangeAttack1, + ARRAYSIZE ( tlSquidRangeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED, + 0, + "Squid Range Attack1" + }, +}; + +// Chase enemy schedule +Task_t tlSquidChaseEnemy1[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RANGE_ATTACK1 },// !!!OEM - this will stop nasty squid oscillation. + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slSquidChaseEnemy[] = +{ + { + tlSquidChaseEnemy1, + ARRAYSIZE ( tlSquidChaseEnemy1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_SMELL_FOOD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_TASK_FAILED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER | + bits_SOUND_MEAT, + "Squid Chase Enemy" + }, +}; + +Task_t tlSquidHurtHop[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SOUND_WAKE, (float)0 }, + { TASK_SQUID_HOPTURN, (float)0 }, + { TASK_FACE_ENEMY, (float)0 },// in case squid didn't turn all the way in the air. +}; + +Schedule_t slSquidHurtHop[] = +{ + { + tlSquidHurtHop, + ARRAYSIZE ( tlSquidHurtHop ), + 0, + 0, + "SquidHurtHop" + } +}; + +Task_t tlSquidSeeCrab[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SOUND_WAKE, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_EXCITED }, + { TASK_FACE_ENEMY, (float)0 }, +}; + +Schedule_t slSquidSeeCrab[] = +{ + { + tlSquidSeeCrab, + ARRAYSIZE ( tlSquidSeeCrab ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "SquidSeeCrab" + } +}; + +// squid walks to something tasty and eats it. +Task_t tlSquidEat[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_EAT, (float)10 },// this is in case the squid can't get to the food + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_GET_PATH_TO_BESTSCENT, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_EAT, (float)50 }, + { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, +}; + +Schedule_t slSquidEat[] = +{ + { + tlSquidEat, + ARRAYSIZE( tlSquidEat ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_NEW_ENEMY , + + // even though HEAR_SOUND/SMELL FOOD doesn't break this schedule, we need this mask + // here or the monster won't detect these sounds at ALL while running this schedule. + bits_SOUND_MEAT | + bits_SOUND_CARCASS, + "SquidEat" + } +}; + +// this is a bit different than just Eat. We use this schedule when the food is far away, occluded, or behind +// the squid. This schedule plays a sniff animation before going to the source of food. +Task_t tlSquidSniffAndEat[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_EAT, (float)10 },// this is in case the squid can't get to the food + { TASK_PLAY_SEQUENCE, (float)ACT_DETECT_SCENT }, + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_GET_PATH_TO_BESTSCENT, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_EAT, (float)50 }, + { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, +}; + +Schedule_t slSquidSniffAndEat[] = +{ + { + tlSquidSniffAndEat, + ARRAYSIZE( tlSquidSniffAndEat ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_NEW_ENEMY , + + // even though HEAR_SOUND/SMELL FOOD doesn't break this schedule, we need this mask + // here or the monster won't detect these sounds at ALL while running this schedule. + bits_SOUND_MEAT | + bits_SOUND_CARCASS, + "SquidSniffAndEat" + } +}; + +// squid does this to stinky things. +Task_t tlSquidWallow[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_EAT, (float)10 },// this is in case the squid can't get to the stinkiness + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_GET_PATH_TO_BESTSCENT, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_INSPECT_FLOOR}, + { TASK_EAT, (float)50 },// keeps squid from eating or sniffing anything else for a while. + { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, +}; + +Schedule_t slSquidWallow[] = +{ + { + tlSquidWallow, + ARRAYSIZE( tlSquidWallow ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_NEW_ENEMY , + + // even though HEAR_SOUND/SMELL FOOD doesn't break this schedule, we need this mask + // here or the monster won't detect these sounds at ALL while running this schedule. + bits_SOUND_GARBAGE, + + "SquidWallow" + } +}; + +DEFINE_CUSTOM_SCHEDULES( CBullsquid ) +{ + slSquidRangeAttack1, + slSquidChaseEnemy, + slSquidHurtHop, + slSquidSeeCrab, + slSquidEat, + slSquidSniffAndEat, + slSquidWallow +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CBullsquid, CBaseMonster ); + +//========================================================= +// GetSchedule +//========================================================= +Schedule_t *CBullsquid :: GetSchedule( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_ALERT: + { + if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE) ) + { + return GetScheduleOfType ( SCHED_SQUID_HURTHOP ); + } + + if ( HasConditions(bits_COND_SMELL_FOOD) ) + { + CSound *pSound; + + pSound = PBestScent(); + + if ( pSound && (!FInViewCone ( &pSound->m_vecOrigin ) || !FVisible ( pSound->m_vecOrigin )) ) + { + // scent is behind or occluded + return GetScheduleOfType( SCHED_SQUID_SNIFF_AND_EAT ); + } + + // food is right out in the open. Just go get it. + return GetScheduleOfType( SCHED_SQUID_EAT ); + } + + if ( HasConditions(bits_COND_SMELL) ) + { + // there's something stinky. + CSound *pSound; + + pSound = PBestScent(); + if ( pSound ) + return GetScheduleOfType( SCHED_SQUID_WALLOW); + } + + break; + } + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + if ( m_fCanThreatDisplay && IRelationship( m_hEnemy ) == R_HT ) + { + // this means squid sees a headcrab! + m_fCanThreatDisplay = FALSE;// only do the headcrab dance once per lifetime. + return GetScheduleOfType ( SCHED_SQUID_SEECRAB ); + } + else + { + return GetScheduleOfType ( SCHED_WAKE_ANGRY ); + } + } + + if ( HasConditions(bits_COND_SMELL_FOOD) ) + { + CSound *pSound; + + pSound = PBestScent(); + + if ( pSound && (!FInViewCone ( &pSound->m_vecOrigin ) || !FVisible ( pSound->m_vecOrigin )) ) + { + // scent is behind or occluded + return GetScheduleOfType( SCHED_SQUID_SNIFF_AND_EAT ); + } + + // food is right out in the open. Just go get it. + return GetScheduleOfType( SCHED_SQUID_EAT ); + } + + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_RANGE_ATTACK1 ); + } + + if ( HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); + } + + if ( HasConditions( bits_COND_CAN_MELEE_ATTACK2 ) ) + { + return GetScheduleOfType ( SCHED_MELEE_ATTACK2 ); + } + + return GetScheduleOfType ( SCHED_CHASE_ENEMY ); + + break; + } + } + + return CBaseMonster :: GetSchedule(); +} + +//========================================================= +// GetScheduleOfType +//========================================================= +Schedule_t* CBullsquid :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_RANGE_ATTACK1: + return &slSquidRangeAttack1[ 0 ]; + break; + case SCHED_SQUID_HURTHOP: + return &slSquidHurtHop[ 0 ]; + break; + case SCHED_SQUID_SEECRAB: + return &slSquidSeeCrab[ 0 ]; + break; + case SCHED_SQUID_EAT: + return &slSquidEat[ 0 ]; + break; + case SCHED_SQUID_SNIFF_AND_EAT: + return &slSquidSniffAndEat[ 0 ]; + break; + case SCHED_SQUID_WALLOW: + return &slSquidWallow[ 0 ]; + break; + case SCHED_CHASE_ENEMY: + return &slSquidChaseEnemy[ 0 ]; + break; + } + + return CBaseMonster :: GetScheduleOfType ( Type ); +} + +//========================================================= +// Start task - selects the correct activity and performs +// any necessary calculations to start the next task on the +// schedule. OVERRIDDEN for bullsquid because it needs to +// know explicitly when the last attempt to chase the enemy +// failed, since that impacts its attack choices. +//========================================================= +void CBullsquid :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_MELEE_ATTACK2: + { + switch ( RANDOM_LONG ( 0, 2 ) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_attackgrowl.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_attackgrowl2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_attackgrowl3.wav", 1, ATTN_NORM ); + break; + } + + CBaseMonster :: StartTask ( pTask ); + break; + } + case TASK_SQUID_HOPTURN: + { + SetActivity ( ACT_HOP ); + MakeIdealYaw ( m_vecEnemyLKP ); + break; + } + case TASK_GET_PATH_TO_ENEMY: + { + if ( BuildRoute ( m_hEnemy->pev->origin, bits_MF_TO_ENEMY, m_hEnemy ) ) + { + m_iTaskStatus = TASKSTATUS_COMPLETE; + } + else + { + ALERT ( at_aiconsole, "GetPathToEnemy failed!!\n" ); + TaskFail(); + } + break; + } + default: + { + CBaseMonster :: StartTask ( pTask ); + break; + } + } +} + +//========================================================= +// RunTask +//========================================================= +void CBullsquid :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_SQUID_HOPTURN: + { + MakeIdealYaw( m_vecEnemyLKP ); + ChangeYaw( pev->yaw_speed ); + + if ( m_fSequenceFinished ) + { + m_iTaskStatus = TASKSTATUS_COMPLETE; + } + break; + } + default: + { + CBaseMonster :: RunTask( pTask ); + break; + } + } +} + + +//========================================================= +// GetIdealState - Overridden for Bullsquid to deal with +// the feature that makes it lose interest in headcrabs for +// a while if something injures it. +//========================================================= +MONSTERSTATE CBullsquid :: GetIdealState ( void ) +{ + int iConditions; + + iConditions = IScheduleFlags(); + + // If no schedule conditions, the new ideal state is probably the reason we're in here. + switch ( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + /* + COMBAT goes to ALERT upon death of enemy + */ + { + if ( m_hEnemy != NULL && ( iConditions & bits_COND_LIGHT_DAMAGE || iConditions & bits_COND_HEAVY_DAMAGE ) && FClassnameIs( m_hEnemy->pev, "monster_headcrab" ) ) + { + // if the squid has a headcrab enemy and something hurts it, it's going to forget about the crab for a while. + m_hEnemy = NULL; + m_IdealMonsterState = MONSTERSTATE_ALERT; + } + break; + } + } + + m_IdealMonsterState = CBaseMonster :: GetIdealState(); + + return m_IdealMonsterState; +} + diff --git a/dlls/buttons.cpp b/dlls/buttons.cpp new file mode 100644 index 0000000..f8dae82 --- /dev/null +++ b/dlls/buttons.cpp @@ -0,0 +1,1284 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== buttons.cpp ======================================================== + + button-related code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "doors.h" + +#if !defined ( _WIN32 ) +#include // memset()))) +#endif + +#define SF_BUTTON_DONTMOVE 1 +#define SF_ROTBUTTON_NOTSOLID 1 +#define SF_BUTTON_TOGGLE 32 // button stays pushed until reactivated +#define SF_BUTTON_SPARK_IF_OFF 64 // button sparks in OFF state +#define SF_BUTTON_TOUCH_ONLY 256 // button only fires as a result of USE key. + +#define SF_GLOBAL_SET 1 // Set global state to initial state on spawn + +class CEnvGlobal : public CPointEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + string_t m_globalstate; + int m_triggermode; + int m_initialstate; +}; + +TYPEDESCRIPTION CEnvGlobal::m_SaveData[] = +{ + DEFINE_FIELD( CEnvGlobal, m_globalstate, FIELD_STRING ), + DEFINE_FIELD( CEnvGlobal, m_triggermode, FIELD_INTEGER ), + DEFINE_FIELD( CEnvGlobal, m_initialstate, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CEnvGlobal, CBaseEntity ); + +LINK_ENTITY_TO_CLASS( env_global, CEnvGlobal ); + +void CEnvGlobal::KeyValue( KeyValueData *pkvd ) +{ + pkvd->fHandled = TRUE; + + if ( FStrEq(pkvd->szKeyName, "globalstate") ) // State name + m_globalstate = ALLOC_STRING( pkvd->szValue ); + else if ( FStrEq(pkvd->szKeyName, "triggermode") ) + m_triggermode = atoi( pkvd->szValue ); + else if ( FStrEq(pkvd->szKeyName, "initialstate") ) + m_initialstate = atoi( pkvd->szValue ); + else + CPointEntity::KeyValue( pkvd ); +} + +void CEnvGlobal::Spawn( void ) +{ + if ( !m_globalstate ) + { + REMOVE_ENTITY( ENT(pev) ); + return; + } + if ( FBitSet( pev->spawnflags, SF_GLOBAL_SET ) ) + { + if ( !gGlobalState.EntityInTable( m_globalstate ) ) + gGlobalState.EntityAdd( m_globalstate, gpGlobals->mapname, (GLOBALESTATE)m_initialstate ); + } +} + + +void CEnvGlobal::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + GLOBALESTATE oldState = gGlobalState.EntityGetState( m_globalstate ); + GLOBALESTATE newState; + + switch( m_triggermode ) + { + case 0: + newState = GLOBAL_OFF; + break; + + case 1: + newState = GLOBAL_ON; + break; + + case 2: + newState = GLOBAL_DEAD; + break; + + default: + case 3: + if ( oldState == GLOBAL_ON ) + newState = GLOBAL_OFF; + else if ( oldState == GLOBAL_OFF ) + newState = GLOBAL_ON; + else + newState = oldState; + } + + if ( gGlobalState.EntityInTable( m_globalstate ) ) + gGlobalState.EntitySetState( m_globalstate, newState ); + else + gGlobalState.EntityAdd( m_globalstate, gpGlobals->mapname, newState ); +} + + + +TYPEDESCRIPTION CMultiSource::m_SaveData[] = +{ + //!!!BUGBUG FIX + DEFINE_ARRAY( CMultiSource, m_rgEntities, FIELD_EHANDLE, MS_MAX_TARGETS ), + DEFINE_ARRAY( CMultiSource, m_rgTriggered, FIELD_INTEGER, MS_MAX_TARGETS ), + DEFINE_FIELD( CMultiSource, m_iTotal, FIELD_INTEGER ), + DEFINE_FIELD( CMultiSource, m_globalstate, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CMultiSource, CBaseEntity ); + +LINK_ENTITY_TO_CLASS( multisource, CMultiSource ); +// +// Cache user-entity-field values until spawn is called. +// + +void CMultiSource::KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "killtarget") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + pkvd->fHandled = TRUE; + else if ( FStrEq(pkvd->szKeyName, "globalstate") ) + { + m_globalstate = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +#define SF_MULTI_INIT 1 + +void CMultiSource::Spawn() +{ + // set up think for later registration + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->nextthink = gpGlobals->time + 0.1; + pev->spawnflags |= SF_MULTI_INIT; // Until it's initialized + SetThink(&CMultiSource::Register); +} + +void CMultiSource::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int i = 0; + + // Find the entity in our list + while (i < m_iTotal) + if ( m_rgEntities[i++] == pCaller ) + break; + + // if we didn't find it, report error and leave + if (i > m_iTotal) + { + ALERT(at_console, "MultiSrc:Used by non member %s.\n", STRING(pCaller->pev->classname)); + return; + } + + // CONSIDER: a Use input to the multisource always toggles. Could check useType for ON/OFF/TOGGLE + + m_rgTriggered[i-1] ^= 1; + + // + if ( IsTriggered( pActivator ) ) + { + ALERT( at_aiconsole, "Multisource %s enabled (%d inputs)\n", STRING(pev->targetname), m_iTotal ); + USE_TYPE useType = USE_TOGGLE; + if ( m_globalstate ) + useType = USE_ON; + SUB_UseTargets( NULL, useType, 0 ); + } +} + + +BOOL CMultiSource::IsTriggered( CBaseEntity * ) +{ + // Is everything triggered? + int i = 0; + + // Still initializing? + if ( pev->spawnflags & SF_MULTI_INIT ) + return 0; + + while (i < m_iTotal) + { + if (m_rgTriggered[i] == 0) + break; + i++; + } + + if (i == m_iTotal) + { + if ( !m_globalstate || gGlobalState.EntityGetState( m_globalstate ) == GLOBAL_ON ) + return 1; + } + + return 0; +} + +void CMultiSource::Register(void) +{ + edict_t *pentTarget = NULL; + + m_iTotal = 0; + memset( m_rgEntities, 0, MS_MAX_TARGETS * sizeof(EHANDLE) ); + + SetThink(&CMultiSource::SUB_DoNothing); + + // search for all entities which target this multisource (pev->targetname) + + pentTarget = FIND_ENTITY_BY_STRING(NULL, "target", STRING(pev->targetname)); + + while (!FNullEnt(pentTarget) && (m_iTotal < MS_MAX_TARGETS)) + { + CBaseEntity *pTarget = CBaseEntity::Instance(pentTarget); + if ( pTarget ) + m_rgEntities[m_iTotal++] = pTarget; + + pentTarget = FIND_ENTITY_BY_STRING( pentTarget, "target", STRING(pev->targetname)); + } + + pentTarget = FIND_ENTITY_BY_STRING(NULL, "classname", "multi_manager"); + while (!FNullEnt(pentTarget) && (m_iTotal < MS_MAX_TARGETS)) + { + CBaseEntity *pTarget = CBaseEntity::Instance(pentTarget); + if ( pTarget && pTarget->HasTarget(pev->targetname) ) + m_rgEntities[m_iTotal++] = pTarget; + + pentTarget = FIND_ENTITY_BY_STRING( pentTarget, "classname", "multi_manager" ); + } + + pev->spawnflags &= ~SF_MULTI_INIT; +} + +// CBaseButton +TYPEDESCRIPTION CBaseButton::m_SaveData[] = +{ + DEFINE_FIELD( CBaseButton, m_fStayPushed, FIELD_BOOLEAN ), + DEFINE_FIELD( CBaseButton, m_fRotating, FIELD_BOOLEAN ), + + DEFINE_FIELD( CBaseButton, m_sounds, FIELD_INTEGER ), + DEFINE_FIELD( CBaseButton, m_bLockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_bLockedSentence, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_bUnlockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_bUnlockedSentence, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_strChangeTarget, FIELD_STRING ), +// DEFINE_FIELD( CBaseButton, m_ls, FIELD_??? ), // This is restored in Precache() +}; + + +IMPLEMENT_SAVERESTORE( CBaseButton, CBaseToggle ); + +void CBaseButton::Precache( void ) +{ + char *pszSound; + + if ( FBitSet ( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) )// this button should spark in OFF state + { + PRECACHE_SOUND ("buttons/spark1.wav"); + PRECACHE_SOUND ("buttons/spark2.wav"); + PRECACHE_SOUND ("buttons/spark3.wav"); + PRECACHE_SOUND ("buttons/spark4.wav"); + PRECACHE_SOUND ("buttons/spark5.wav"); + PRECACHE_SOUND ("buttons/spark6.wav"); + } + + // get door button sounds, for doors which require buttons to open + + if (m_bLockedSound) + { + pszSound = ButtonSound( (int)m_bLockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sLockedSound = ALLOC_STRING(pszSound); + } + + if (m_bUnlockedSound) + { + pszSound = ButtonSound( (int)m_bUnlockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sUnlockedSound = ALLOC_STRING(pszSound); + } + + // get sentence group names, for doors which are directly 'touched' to open + + switch (m_bLockedSentence) + { + case 1: m_ls.sLockedSentence = MAKE_STRING("NA"); break; // access denied + case 2: m_ls.sLockedSentence = MAKE_STRING("ND"); break; // security lockout + case 3: m_ls.sLockedSentence = MAKE_STRING("NF"); break; // blast door + case 4: m_ls.sLockedSentence = MAKE_STRING("NFIRE"); break; // fire door + case 5: m_ls.sLockedSentence = MAKE_STRING("NCHEM"); break; // chemical door + case 6: m_ls.sLockedSentence = MAKE_STRING("NRAD"); break; // radiation door + case 7: m_ls.sLockedSentence = MAKE_STRING("NCON"); break; // gen containment + case 8: m_ls.sLockedSentence = MAKE_STRING("NH"); break; // maintenance door + case 9: m_ls.sLockedSentence = MAKE_STRING("NG"); break; // broken door + + default: m_ls.sLockedSentence = 0; break; + } + + switch (m_bUnlockedSentence) + { + case 1: m_ls.sUnlockedSentence = MAKE_STRING("EA"); break; // access granted + case 2: m_ls.sUnlockedSentence = MAKE_STRING("ED"); break; // security door + case 3: m_ls.sUnlockedSentence = MAKE_STRING("EF"); break; // blast door + case 4: m_ls.sUnlockedSentence = MAKE_STRING("EFIRE"); break; // fire door + case 5: m_ls.sUnlockedSentence = MAKE_STRING("ECHEM"); break; // chemical door + case 6: m_ls.sUnlockedSentence = MAKE_STRING("ERAD"); break; // radiation door + case 7: m_ls.sUnlockedSentence = MAKE_STRING("ECON"); break; // gen containment + case 8: m_ls.sUnlockedSentence = MAKE_STRING("EH"); break; // maintenance door + + default: m_ls.sUnlockedSentence = 0; break; + } +} + +// +// Cache user-entity-field values until spawn is called. +// + +void CBaseButton::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "changetarget")) + { + m_strChangeTarget = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sound")) + { + m_bLockedSound = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sentence")) + { + m_bLockedSentence = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sound")) + { + m_bUnlockedSound = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sentence")) + { + m_bUnlockedSentence = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +// +// ButtonShot +// +int CBaseButton::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + BUTTON_CODE code = ButtonResponseToTouch(); + + if ( code == BUTTON_NOTHING ) + return 0; + // Temporarily disable the touch function, until movement is finished. + SetTouch( NULL ); + + m_hActivator = CBaseEntity::Instance( pevAttacker ); + if ( m_hActivator == NULL ) + return 0; + + if ( code == BUTTON_RETURN ) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + // Toggle buttons fire when they get back to their "home" position + if ( !(pev->spawnflags & SF_BUTTON_TOGGLE) ) + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + ButtonReturn(); + } + else // code == BUTTON_ACTIVATE + ButtonActivate( ); + + return 0; +} + +/*QUAKED func_button (0 .5 .8) ? +When a button is touched, it moves some distance in the direction of it's angle, +triggers all of it's targets, waits some time, then returns to it's original position +where it can be triggered again. + +"angle" determines the opening direction +"target" all entities with a matching targetname will be used +"speed" override the default 40 speed +"wait" override the default 1 second wait (-1 = never return) +"lip" override the default 4 pixel lip remaining at end of move +"health" if set, the button must be killed instead of touched +"sounds" +0) steam metal +1) wooden clunk +2) metallic click +3) in-out +*/ +LINK_ENTITY_TO_CLASS( func_button, CBaseButton ); + + +void CBaseButton::Spawn( ) +{ + char *pszSound; + + //---------------------------------------------------- + //determine sounds for buttons + //a sound of 0 should not make a sound + //---------------------------------------------------- + pszSound = ButtonSound( m_sounds ); + PRECACHE_SOUND(pszSound); + pev->noise = ALLOC_STRING(pszSound); + + Precache(); + + if ( FBitSet ( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) )// this button should spark in OFF state + { + SetThink ( &CBaseButton::ButtonSpark ); + pev->nextthink = gpGlobals->time + 0.5;// no hurry, make sure everything else spawns + } + + SetMovedir(pev); + + pev->movetype = MOVETYPE_PUSH; + pev->solid = SOLID_BSP; + SET_MODEL(ENT(pev), STRING(pev->model)); + + if (pev->speed == 0) + pev->speed = 40; + + if (pev->health > 0) + { + pev->takedamage = DAMAGE_YES; + } + + if (m_flWait == 0) + m_flWait = 1; + if (m_flLip == 0) + m_flLip = 4; + + m_toggle_state = TS_AT_BOTTOM; + m_vecPosition1 = pev->origin; + // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big + m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); + + + // Is this a non-moving button? + if ( ((m_vecPosition2 - m_vecPosition1).Length() < 1) || (pev->spawnflags & SF_BUTTON_DONTMOVE) ) + m_vecPosition2 = m_vecPosition1; + + m_fStayPushed = (m_flWait == -1 ? TRUE : FALSE); + m_fRotating = FALSE; + + // if the button is flagged for USE button activation only, take away it's touch function and add a use function + + if ( FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // touchable button + { + SetTouch( &CBaseButton::ButtonTouch ); + } + else + { + SetTouch ( NULL ); + SetUse ( &CBaseButton::ButtonUse ); + } +} + + +// Button sound table. +// Also used by CBaseDoor to get 'touched' door lock/unlock sounds + +char *ButtonSound( int sound ) +{ + char *pszSound; + + switch ( sound ) + { + case 0: pszSound = "common/null.wav"; break; + case 1: pszSound = "buttons/button1.wav"; break; + case 2: pszSound = "buttons/button2.wav"; break; + case 3: pszSound = "buttons/button3.wav"; break; + case 4: pszSound = "buttons/button4.wav"; break; + case 5: pszSound = "buttons/button5.wav"; break; + case 6: pszSound = "buttons/button6.wav"; break; + case 7: pszSound = "buttons/button7.wav"; break; + case 8: pszSound = "buttons/button8.wav"; break; + case 9: pszSound = "buttons/button9.wav"; break; + case 10: pszSound = "buttons/button10.wav"; break; + case 11: pszSound = "buttons/button11.wav"; break; + case 12: pszSound = "buttons/latchlocked1.wav"; break; + case 13: pszSound = "buttons/latchunlocked1.wav"; break; + case 14: pszSound = "buttons/lightswitch2.wav";break; + +// next 6 slots reserved for any additional sliding button sounds we may add + + case 21: pszSound = "buttons/lever1.wav"; break; + case 22: pszSound = "buttons/lever2.wav"; break; + case 23: pszSound = "buttons/lever3.wav"; break; + case 24: pszSound = "buttons/lever4.wav"; break; + case 25: pszSound = "buttons/lever5.wav"; break; + + default:pszSound = "buttons/button9.wav"; break; + } + + return pszSound; +} + +// +// Makes flagged buttons spark when turned off +// + +void DoSpark(entvars_t *pev, const Vector &location ) +{ + Vector tmp = location + pev->size * 0.5; + UTIL_Sparks( tmp ); + + float flVolume = RANDOM_FLOAT ( 0.25 , 0.75 ) * 0.4;//random volume range + switch ( (int)(RANDOM_FLOAT(0,1) * 6) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark1.wav", flVolume, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark2.wav", flVolume, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark3.wav", flVolume, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark4.wav", flVolume, ATTN_NORM); break; + case 4: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM); break; + case 5: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM); break; + } +} + +void CBaseButton::ButtonSpark ( void ) +{ + SetThink ( &CBaseButton::ButtonSpark ); + pev->nextthink = gpGlobals->time + ( 0.1 + RANDOM_FLOAT ( 0, 1.5 ) );// spark again at random interval + + DoSpark( pev, pev->mins ); +} + + +// +// Button's Use function +// +void CBaseButton::ButtonUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Ignore touches if button is moving, or pushed-in and waiting to auto-come-out. + // UNDONE: Should this use ButtonResponseToTouch() too? + if (m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN ) + return; + + m_hActivator = pActivator; + if ( m_toggle_state == TS_AT_TOP) + { + if (!m_fStayPushed && FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE)) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + //SUB_UseTargets( m_eoActivator ); + ButtonReturn(); + } + } + else + ButtonActivate( ); +} + + +CBaseButton::BUTTON_CODE CBaseButton::ButtonResponseToTouch( void ) +{ + // Ignore touches if button is moving, or pushed-in and waiting to auto-come-out. + if (m_toggle_state == TS_GOING_UP || + m_toggle_state == TS_GOING_DOWN || + (m_toggle_state == TS_AT_TOP && !m_fStayPushed && !FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE) ) ) + return BUTTON_NOTHING; + + if (m_toggle_state == TS_AT_TOP) + { + if((FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE) ) && !m_fStayPushed) + { + return BUTTON_RETURN; + } + } + else + return BUTTON_ACTIVATE; + + return BUTTON_NOTHING; +} + + +// +// Touching a button simply "activates" it. +// +void CBaseButton:: ButtonTouch( CBaseEntity *pOther ) +{ + // Ignore touches by anything but players + if (!FClassnameIs(pOther->pev, "player")) + return; + + m_hActivator = pOther; + + BUTTON_CODE code = ButtonResponseToTouch(); + + if ( code == BUTTON_NOTHING ) + return; + + if (!UTIL_IsMasterTriggered(m_sMaster, pOther)) + { + // play button locked sound + PlayLockSounds(pev, &m_ls, TRUE, TRUE); + return; + } + + // Temporarily disable the touch function, until movement is finished. + SetTouch( NULL ); + + if ( code == BUTTON_RETURN ) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + ButtonReturn(); + } + else // code == BUTTON_ACTIVATE + ButtonActivate( ); +} + +// +// Starts the button moving "in/up". +// +void CBaseButton::ButtonActivate( ) +{ + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + { + // button is locked, play locked sound + PlayLockSounds(pev, &m_ls, TRUE, TRUE); + return; + } + else + { + // button is unlocked, play unlocked sound + PlayLockSounds(pev, &m_ls, FALSE, TRUE); + } + + ASSERT(m_toggle_state == TS_AT_BOTTOM); + m_toggle_state = TS_GOING_UP; + + SetMoveDone( &CBaseButton::TriggerAndWait ); + if (!m_fRotating) + LinearMove( m_vecPosition2, pev->speed); + else + AngularMove( m_vecAngle2, pev->speed); +} + +// +// Button has reached the "in/up" position. Activate its "targets", and pause before "popping out". +// +void CBaseButton::TriggerAndWait( void ) +{ + ASSERT(m_toggle_state == TS_GOING_UP); + + if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + return; + + m_toggle_state = TS_AT_TOP; + + // If button automatically comes back out, start it moving out. + // Else re-instate touch method + if (m_fStayPushed || FBitSet ( pev->spawnflags, SF_BUTTON_TOGGLE ) ) + { + if ( !FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // this button only works if USED, not touched! + { + // ALL buttons are now use only + SetTouch ( NULL ); + } + else + SetTouch( &CBaseButton::ButtonTouch ); + } + else + { + pev->nextthink = pev->ltime + m_flWait; + SetThink( &CBaseButton::ButtonReturn ); + } + + pev->frame = 1; // use alternate textures + + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); +} + + +// +// Starts the button moving "out/down". +// +void CBaseButton::ButtonReturn( void ) +{ + ASSERT(m_toggle_state == TS_AT_TOP); + m_toggle_state = TS_GOING_DOWN; + + SetMoveDone( &CBaseButton::ButtonBackHome ); + if (!m_fRotating) + LinearMove( m_vecPosition1, pev->speed); + else + AngularMove( m_vecAngle1, pev->speed); + + pev->frame = 0; // use normal textures +} + + +// +// Button has returned to start state. Quiesce it. +// +void CBaseButton::ButtonBackHome( void ) +{ + ASSERT(m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_AT_BOTTOM; + + if ( FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE) ) + { + //EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + } + + + if (!FStringNull(pev->target)) + { + edict_t* pentTarget = NULL; + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->target)); + + if (FNullEnt(pentTarget)) + break; + + if (!FClassnameIs(pentTarget, "multisource")) + continue; + CBaseEntity *pTarget = CBaseEntity::Instance( pentTarget ); + + if ( pTarget ) + pTarget->Use( m_hActivator, this, USE_TOGGLE, 0 ); + } + } + +// Re-instate touch method, movement cycle is complete. + if ( !FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // this button only works if USED, not touched! + { + // All buttons are now use only + SetTouch ( NULL ); + } + else + SetTouch( &CBaseButton::ButtonTouch ); + +// reset think for a sparking button + if ( FBitSet ( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) ) + { + SetThink ( &CBaseButton::ButtonSpark ); + pev->nextthink = gpGlobals->time + 0.5;// no hurry. + } +} + + + +// +// Rotating button (aka "lever") +// +class CRotButton : public CBaseButton +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( func_rot_button, CRotButton ); + +void CRotButton::Spawn( void ) +{ + char *pszSound; + //---------------------------------------------------- + //determine sounds for buttons + //a sound of 0 should not make a sound + //---------------------------------------------------- + pszSound = ButtonSound( m_sounds ); + PRECACHE_SOUND(pszSound); + pev->noise = ALLOC_STRING(pszSound); + + // set the axis of rotation + CBaseToggle::AxisDir( pev ); + + // check for clockwise rotation + if ( FBitSet (pev->spawnflags, SF_DOOR_ROTATE_BACKWARDS) ) + pev->movedir = pev->movedir * -1; + + pev->movetype = MOVETYPE_PUSH; + + if ( pev->spawnflags & SF_ROTBUTTON_NOTSOLID ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + + SET_MODEL(ENT(pev), STRING(pev->model)); + + if (pev->speed == 0) + pev->speed = 40; + + if (m_flWait == 0) + m_flWait = 1; + + if (pev->health > 0) + { + pev->takedamage = DAMAGE_YES; + } + + m_toggle_state = TS_AT_BOTTOM; + m_vecAngle1 = pev->angles; + m_vecAngle2 = pev->angles + pev->movedir * m_flMoveDistance; + ASSERTSZ(m_vecAngle1 != m_vecAngle2, "rotating button start/end positions are equal"); + + m_fStayPushed = (m_flWait == -1 ? TRUE : FALSE); + m_fRotating = TRUE; + + // if the button is flagged for USE button activation only, take away it's touch function and add a use function + if ( !FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) + { + SetTouch ( NULL ); + SetUse ( &CRotButton::ButtonUse ); + } + else // touchable button + SetTouch( &CRotButton::ButtonTouch ); + + //SetTouch( ButtonTouch ); +} + + +// Make this button behave like a door (HACKHACK) +// This will disable use and make the button solid +// rotating buttons were made SOLID_NOT by default since their were some +// collision problems with them... +#define SF_MOMENTARY_DOOR 0x0001 + +class CMomentaryRotButton : public CBaseToggle +{ +public: + void Spawn ( void ); + void KeyValue( KeyValueData *pkvd ); + virtual int ObjectCaps( void ) + { + int flags = CBaseToggle :: ObjectCaps() & (~FCAP_ACROSS_TRANSITION); + if ( pev->spawnflags & SF_MOMENTARY_DOOR ) + return flags; + return flags | FCAP_CONTINUOUS_USE; + } + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Off( void ); + void EXPORT Return( void ); + void UpdateSelf( float value ); + void UpdateSelfReturn( float value ); + void UpdateAllButtons( float value, int start ); + + void PlaySound( void ); + void UpdateTarget( float value ); + + static CMomentaryRotButton *Instance( edict_t *pent ) { return (CMomentaryRotButton *)GET_PRIVATE(pent);}; + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_lastUsed; + int m_direction; + float m_returnSpeed; + vec3_t m_start; + vec3_t m_end; + int m_sounds; +}; +TYPEDESCRIPTION CMomentaryRotButton::m_SaveData[] = +{ + DEFINE_FIELD( CMomentaryRotButton, m_lastUsed, FIELD_INTEGER ), + DEFINE_FIELD( CMomentaryRotButton, m_direction, FIELD_INTEGER ), + DEFINE_FIELD( CMomentaryRotButton, m_returnSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CMomentaryRotButton, m_start, FIELD_VECTOR ), + DEFINE_FIELD( CMomentaryRotButton, m_end, FIELD_VECTOR ), + DEFINE_FIELD( CMomentaryRotButton, m_sounds, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CMomentaryRotButton, CBaseToggle ); + +LINK_ENTITY_TO_CLASS( momentary_rot_button, CMomentaryRotButton ); + +void CMomentaryRotButton::Spawn( void ) +{ + CBaseToggle::AxisDir( pev ); + + if ( pev->speed == 0 ) + pev->speed = 100; + + if ( m_flMoveDistance < 0 ) + { + m_start = pev->angles + pev->movedir * m_flMoveDistance; + m_end = pev->angles; + m_direction = 1; // This will toggle to -1 on the first use() + m_flMoveDistance = -m_flMoveDistance; + } + else + { + m_start = pev->angles; + m_end = pev->angles + pev->movedir * m_flMoveDistance; + m_direction = -1; // This will toggle to +1 on the first use() + } + + if ( pev->spawnflags & SF_MOMENTARY_DOOR ) + pev->solid = SOLID_BSP; + else + pev->solid = SOLID_NOT; + + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + char *pszSound = ButtonSound( m_sounds ); + PRECACHE_SOUND(pszSound); + pev->noise = ALLOC_STRING(pszSound); + m_lastUsed = 0; +} + +void CMomentaryRotButton::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "returnspeed")) + { + m_returnSpeed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CMomentaryRotButton::PlaySound( void ) +{ + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); +} + +// BUGBUG: This design causes a latentcy. When the button is retriggered, the first impulse +// will send the target in the wrong direction because the parameter is calculated based on the +// current, not future position. +void CMomentaryRotButton::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + pev->ideal_yaw = CBaseToggle::AxisDelta( pev->spawnflags, pev->angles, m_start ) / m_flMoveDistance; + + UpdateAllButtons( pev->ideal_yaw, 1 ); + + // Calculate destination angle and use it to predict value, this prevents sending target in wrong direction on retriggering + Vector dest = pev->angles + pev->avelocity * (pev->nextthink - pev->ltime); + float value1 = CBaseToggle::AxisDelta( pev->spawnflags, dest, m_start ) / m_flMoveDistance; + UpdateTarget( value1 ); + +} + +void CMomentaryRotButton::UpdateAllButtons( float value, int start ) +{ + // Update all rot buttons attached to the same target + edict_t *pentTarget = NULL; + for (;;) + { + + pentTarget = FIND_ENTITY_BY_STRING(pentTarget, "target", STRING(pev->target)); + if (FNullEnt(pentTarget)) + break; + + if ( FClassnameIs( VARS(pentTarget), "momentary_rot_button" ) ) + { + CMomentaryRotButton *pEntity = CMomentaryRotButton::Instance(pentTarget); + if ( pEntity ) + { + if ( start ) + pEntity->UpdateSelf( value ); + else + pEntity->UpdateSelfReturn( value ); + } + } + } +} + +void CMomentaryRotButton::UpdateSelf( float value ) +{ + BOOL fplaysound = FALSE; + + if ( !m_lastUsed ) + { + fplaysound = TRUE; + m_direction = -m_direction; + } + m_lastUsed = 1; + + pev->nextthink = pev->ltime + 0.1; + if ( m_direction > 0 && value >= 1.0 ) + { + pev->avelocity = g_vecZero; + pev->angles = m_end; + return; + } + else if ( m_direction < 0 && value <= 0 ) + { + pev->avelocity = g_vecZero; + pev->angles = m_start; + return; + } + + if (fplaysound) + PlaySound(); + + // HACKHACK -- If we're going slow, we'll get multiple player packets per frame, bump nexthink on each one to avoid stalling + if ( pev->nextthink < pev->ltime ) + pev->nextthink = pev->ltime + 0.1; + else + pev->nextthink += 0.1; + + pev->avelocity = (m_direction * pev->speed) * pev->movedir; + SetThink( &CMomentaryRotButton::Off ); +} + +void CMomentaryRotButton::UpdateTarget( float value ) +{ + if (!FStringNull(pev->target)) + { + edict_t* pentTarget = NULL; + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->target)); + if (FNullEnt(pentTarget)) + break; + CBaseEntity *pEntity = CBaseEntity::Instance(pentTarget); + if ( pEntity ) + { + pEntity->Use( this, this, USE_SET, value ); + } + } + } +} + +void CMomentaryRotButton::Off( void ) +{ + pev->avelocity = g_vecZero; + m_lastUsed = 0; + if ( FBitSet( pev->spawnflags, SF_PENDULUM_AUTO_RETURN ) && m_returnSpeed > 0 ) + { + SetThink( &CMomentaryRotButton::Return ); + pev->nextthink = pev->ltime + 0.1; + m_direction = -1; + } + else + SetThink( NULL ); +} + +void CMomentaryRotButton::Return( void ) +{ + float value = CBaseToggle::AxisDelta( pev->spawnflags, pev->angles, m_start ) / m_flMoveDistance; + + UpdateAllButtons( value, 0 ); // This will end up calling UpdateSelfReturn() n times, but it still works right + if ( value > 0 ) + UpdateTarget( value ); +} + + +void CMomentaryRotButton::UpdateSelfReturn( float value ) +{ + if ( value <= 0 ) + { + pev->avelocity = g_vecZero; + pev->angles = m_start; + pev->nextthink = -1; + SetThink( NULL ); + } + else + { + pev->avelocity = -m_returnSpeed * pev->movedir; + pev->nextthink = pev->ltime + 0.1; + } +} + + +//---------------------------------------------------------------- +// Spark +//---------------------------------------------------------------- + +class CEnvSpark : public CBaseEntity +{ +public: + void Spawn(void); + void Precache(void); + void EXPORT SparkThink(void); + void EXPORT SparkStart(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT SparkStop(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue(KeyValueData *pkvd); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flDelay; +}; + + +TYPEDESCRIPTION CEnvSpark::m_SaveData[] = +{ + DEFINE_FIELD( CEnvSpark, m_flDelay, FIELD_FLOAT), +}; + +IMPLEMENT_SAVERESTORE( CEnvSpark, CBaseEntity ); + +LINK_ENTITY_TO_CLASS(env_spark, CEnvSpark); +LINK_ENTITY_TO_CLASS(env_debris, CEnvSpark); + +void CEnvSpark::Spawn(void) +{ + SetThink( NULL ); + SetUse( NULL ); + + if (FBitSet(pev->spawnflags, 32)) // Use for on/off + { + if (FBitSet(pev->spawnflags, 64)) // Start on + { + SetThink(&CEnvSpark::SparkThink); // start sparking + SetUse(&CEnvSpark::SparkStop); // set up +USE to stop sparking + } + else + SetUse(&CEnvSpark::SparkStart); + } + else + SetThink(&CEnvSpark::SparkThink); + + pev->nextthink = gpGlobals->time + ( 0.1 + RANDOM_FLOAT ( 0, 1.5 ) ); + + if (m_flDelay <= 0) + m_flDelay = 1.5; + + Precache( ); +} + + +void CEnvSpark::Precache(void) +{ + PRECACHE_SOUND( "buttons/spark1.wav" ); + PRECACHE_SOUND( "buttons/spark2.wav" ); + PRECACHE_SOUND( "buttons/spark3.wav" ); + PRECACHE_SOUND( "buttons/spark4.wav" ); + PRECACHE_SOUND( "buttons/spark5.wav" ); + PRECACHE_SOUND( "buttons/spark6.wav" ); +} + +void CEnvSpark::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "MaxDelay")) + { + m_flDelay = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "killtarget") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + pkvd->fHandled = TRUE; + else + CBaseEntity::KeyValue( pkvd ); +} + +void EXPORT CEnvSpark::SparkThink(void) +{ + pev->nextthink = gpGlobals->time + 0.1 + RANDOM_FLOAT (0, m_flDelay); + DoSpark( pev, pev->origin ); +} + +void EXPORT CEnvSpark::SparkStart(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetUse(&CEnvSpark::SparkStop); + SetThink(&CEnvSpark::SparkThink); + pev->nextthink = gpGlobals->time + (0.1 + RANDOM_FLOAT ( 0, m_flDelay)); +} + +void EXPORT CEnvSpark::SparkStop(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetUse(&CEnvSpark::SparkStart); + SetThink(NULL); +} + +#define SF_BTARGET_USE 0x0001 +#define SF_BTARGET_ON 0x0002 + +class CButtonTarget : public CBaseEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + int ObjectCaps( void ); + +}; + +LINK_ENTITY_TO_CLASS( button_target, CButtonTarget ); + +void CButtonTarget::Spawn( void ) +{ + pev->movetype = MOVETYPE_PUSH; + pev->solid = SOLID_BSP; + SET_MODEL(ENT(pev), STRING(pev->model)); + pev->takedamage = DAMAGE_YES; + + if ( FBitSet( pev->spawnflags, SF_BTARGET_ON ) ) + pev->frame = 1; +} + +void CButtonTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, (int)pev->frame ) ) + return; + pev->frame = 1-pev->frame; + if ( pev->frame ) + SUB_UseTargets( pActivator, USE_ON, 0 ); + else + SUB_UseTargets( pActivator, USE_OFF, 0 ); +} + + +int CButtonTarget :: ObjectCaps( void ) +{ + int caps = CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; + + if ( FBitSet(pev->spawnflags, SF_BTARGET_USE) ) + return caps | FCAP_IMPULSE_USE; + else + return caps; +} + + +int CButtonTarget::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + Use( Instance(pevAttacker), this, USE_TOGGLE, 0 ); + + return 1; +} diff --git a/dlls/cbase.cpp b/dlls/cbase.cpp new file mode 100644 index 0000000..a77605d --- /dev/null +++ b/dlls/cbase.cpp @@ -0,0 +1,771 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "client.h" +#include "decals.h" +#include "gamerules.h" +#include "game.h" + +void EntvarsKeyvalue( entvars_t *pev, KeyValueData *pkvd ); + +extern "C" void PM_Move ( struct playermove_s *ppmove, int server ); +extern "C" void PM_Init ( struct playermove_s *ppmove ); +extern "C" char PM_FindTextureType( char *name ); + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); +extern DLL_GLOBAL Vector g_vecAttackDir; +extern DLL_GLOBAL int g_iSkillLevel; + +static DLL_FUNCTIONS gFunctionTable = +{ + GameDLLInit, //pfnGameInit + DispatchSpawn, //pfnSpawn + DispatchThink, //pfnThink + DispatchUse, //pfnUse + DispatchTouch, //pfnTouch + DispatchBlocked, //pfnBlocked + DispatchKeyValue, //pfnKeyValue + DispatchSave, //pfnSave + DispatchRestore, //pfnRestore + DispatchObjectCollsionBox, //pfnAbsBox + + SaveWriteFields, //pfnSaveWriteFields + SaveReadFields, //pfnSaveReadFields + + SaveGlobalState, //pfnSaveGlobalState + RestoreGlobalState, //pfnRestoreGlobalState + ResetGlobalState, //pfnResetGlobalState + + ClientConnect, //pfnClientConnect + ClientDisconnect, //pfnClientDisconnect + ClientKill, //pfnClientKill + ClientPutInServer, //pfnClientPutInServer + ClientCommand, //pfnClientCommand + ClientUserInfoChanged, //pfnClientUserInfoChanged + ServerActivate, //pfnServerActivate + ServerDeactivate, //pfnServerDeactivate + + PlayerPreThink, //pfnPlayerPreThink + PlayerPostThink, //pfnPlayerPostThink + + StartFrame, //pfnStartFrame + ParmsNewLevel, //pfnParmsNewLevel + ParmsChangeLevel, //pfnParmsChangeLevel + + GetGameDescription, //pfnGetGameDescription Returns string describing current .dll game. + PlayerCustomization, //pfnPlayerCustomization Notifies .dll of new customization for player. + + SpectatorConnect, //pfnSpectatorConnect Called when spectator joins server + SpectatorDisconnect, //pfnSpectatorDisconnect Called when spectator leaves the server + SpectatorThink, //pfnSpectatorThink Called when spectator sends a command packet (usercmd_t) + + Sys_Error, //pfnSys_Error Called when engine has encountered an error + + PM_Move, //pfnPM_Move + PM_Init, //pfnPM_Init Server version of player movement initialization + PM_FindTextureType, //pfnPM_FindTextureType + + SetupVisibility, //pfnSetupVisibility Set up PVS and PAS for networking for this client + UpdateClientData, //pfnUpdateClientData Set up data sent only to specific client + AddToFullPack, //pfnAddToFullPack + CreateBaseline, //pfnCreateBaseline Tweak entity baseline for network encoding, allows setup of player baselines, too. + RegisterEncoders, //pfnRegisterEncoders Callbacks for network encoding + GetWeaponData, //pfnGetWeaponData + CmdStart, //pfnCmdStart + CmdEnd, //pfnCmdEnd + ConnectionlessPacket, //pfnConnectionlessPacket + GetHullBounds, //pfnGetHullBounds + CreateInstancedBaselines, //pfnCreateInstancedBaselines + InconsistentFile, //pfnInconsistentFile + AllowLagCompensation, //pfnAllowLagCompensation +}; + +static void SetObjectCollisionBox( entvars_t *pev ); + +extern "C" { + + int GetEntityAPI( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion ) +{ + if ( !pFunctionTable || interfaceVersion != INTERFACE_VERSION ) + { + return FALSE; + } + + memcpy( pFunctionTable, &gFunctionTable, sizeof( DLL_FUNCTIONS ) ); + return TRUE; +} + +int GetEntityAPI2( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ) +{ + if ( !pFunctionTable || *interfaceVersion != INTERFACE_VERSION ) + { + // Tell engine what version we had, so it can figure out who is out of date. + *interfaceVersion = INTERFACE_VERSION; + return FALSE; + } + + memcpy( pFunctionTable, &gFunctionTable, sizeof( DLL_FUNCTIONS ) ); + return TRUE; +} + +} + + +int DispatchSpawn( edict_t *pent ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if (pEntity) + { + // Initialize these or entities who don't link to the world won't have anything in here + pEntity->pev->absmin = pEntity->pev->origin - Vector(1,1,1); + pEntity->pev->absmax = pEntity->pev->origin + Vector(1,1,1); + + pEntity->Spawn(); + + // Try to get the pointer again, in case the spawn function deleted the entity. + // UNDONE: Spawn() should really return a code to ask that the entity be deleted, but + // that would touch too much code for me to do that right now. + pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if ( pEntity ) + { + if ( g_pGameRules && !g_pGameRules->IsAllowedToSpawn( pEntity ) ) + return -1; // return that this entity should be deleted + if ( pEntity->pev->flags & FL_KILLME ) + return -1; + } + + + // Handle global stuff here + if ( pEntity && pEntity->pev->globalname ) + { + const globalentity_t *pGlobal = gGlobalState.EntityFromTable( pEntity->pev->globalname ); + if ( pGlobal ) + { + // Already dead? delete + if ( pGlobal->state == GLOBAL_DEAD ) + return -1; + else if ( !FStrEq( STRING(gpGlobals->mapname), pGlobal->levelName ) ) + pEntity->MakeDormant(); // Hasn't been moved to this level yet, wait but stay alive + // In this level & not dead, continue on as normal + } + else + { + // Spawned entities default to 'On' + gGlobalState.EntityAdd( pEntity->pev->globalname, gpGlobals->mapname, GLOBAL_ON ); +// ALERT( at_console, "Added global entity %s (%s)\n", STRING(pEntity->pev->classname), STRING(pEntity->pev->globalname) ); + } + } + + } + + return 0; +} + +void DispatchKeyValue( edict_t *pentKeyvalue, KeyValueData *pkvd ) +{ + if ( !pkvd || !pentKeyvalue ) + return; + + EntvarsKeyvalue( VARS(pentKeyvalue), pkvd ); + + // If the key was an entity variable, or there's no class set yet, don't look for the object, it may + // not exist yet. + if ( pkvd->fHandled || pkvd->szClassName == NULL ) + return; + + // Get the actualy entity object + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pentKeyvalue); + + if ( !pEntity ) + return; + + pEntity->KeyValue( pkvd ); +} + + +// HACKHACK -- this is a hack to keep the node graph entity from "touching" things (like triggers) +// while it builds the graph +BOOL gTouchDisabled = FALSE; +void DispatchTouch( edict_t *pentTouched, edict_t *pentOther ) +{ + if ( gTouchDisabled ) + return; + + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pentTouched); + CBaseEntity *pOther = (CBaseEntity *)GET_PRIVATE( pentOther ); + + if ( pEntity && pOther && ! ((pEntity->pev->flags | pOther->pev->flags) & FL_KILLME) ) + pEntity->Touch( pOther ); +} + + +void DispatchUse( edict_t *pentUsed, edict_t *pentOther ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pentUsed); + CBaseEntity *pOther = (CBaseEntity *)GET_PRIVATE(pentOther); + + if (pEntity && !(pEntity->pev->flags & FL_KILLME) ) + pEntity->Use( pOther, pOther, USE_TOGGLE, 0 ); +} + +void DispatchThink( edict_t *pent ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + if (pEntity) + { + if ( FBitSet( pEntity->pev->flags, FL_DORMANT ) ) + ALERT( at_error, "Dormant entity %s is thinking!!\n", STRING(pEntity->pev->classname) ); + + pEntity->Think(); + } +} + +void DispatchBlocked( edict_t *pentBlocked, edict_t *pentOther ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE( pentBlocked ); + CBaseEntity *pOther = (CBaseEntity *)GET_PRIVATE( pentOther ); + + if (pEntity) + pEntity->Blocked( pOther ); +} + +void DispatchSave( edict_t *pent, SAVERESTOREDATA *pSaveData ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if ( pEntity && pSaveData ) + { + ENTITYTABLE *pTable = &pSaveData->pTable[ pSaveData->currentIndex ]; + + if ( pTable->pent != pent ) + ALERT( at_error, "ENTITY TABLE OR INDEX IS WRONG!!!!\n" ); + + if ( pEntity->ObjectCaps() & FCAP_DONT_SAVE ) + return; + + // These don't use ltime & nextthink as times really, but we'll fudge around it. + if ( pEntity->pev->movetype == MOVETYPE_PUSH ) + { + float delta = pEntity->pev->nextthink - pEntity->pev->ltime; + pEntity->pev->ltime = gpGlobals->time; + pEntity->pev->nextthink = pEntity->pev->ltime + delta; + } + + pTable->location = pSaveData->size; // Remember entity position for file I/O + pTable->classname = pEntity->pev->classname; // Remember entity class for respawn + + CSave saveHelper( pSaveData ); + pEntity->Save( saveHelper ); + + pTable->size = pSaveData->size - pTable->location; // Size of entity block is data size written to block + } +} + + +// Find the matching global entity. Spit out an error if the designer made entities of +// different classes with the same global name +CBaseEntity *FindGlobalEntity( string_t classname, string_t globalname ) +{ + edict_t *pent = FIND_ENTITY_BY_STRING( NULL, "globalname", STRING(globalname) ); + CBaseEntity *pReturn = CBaseEntity::Instance( pent ); + if ( pReturn ) + { + if ( !FClassnameIs( pReturn->pev, STRING(classname) ) ) + { + ALERT( at_console, "Global entity found %s, wrong class %s\n", STRING(globalname), STRING(pReturn->pev->classname) ); + pReturn = NULL; + } + } + + return pReturn; +} + + +int DispatchRestore( edict_t *pent, SAVERESTOREDATA *pSaveData, int globalEntity ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if ( pEntity && pSaveData ) + { + entvars_t tmpVars; + Vector oldOffset; + + CRestore restoreHelper( pSaveData ); + if ( globalEntity ) + { + CRestore tmpRestore( pSaveData ); + tmpRestore.PrecacheMode( 0 ); + tmpRestore.ReadEntVars( "ENTVARS", &tmpVars ); + + // HACKHACK - reset the save pointers, we're going to restore for real this time + pSaveData->size = pSaveData->pTable[pSaveData->currentIndex].location; + pSaveData->pCurrentData = pSaveData->pBaseData + pSaveData->size; + // ------------------- + + + const globalentity_t *pGlobal = gGlobalState.EntityFromTable( tmpVars.globalname ); + + // Don't overlay any instance of the global that isn't the latest + // pSaveData->szCurrentMapName is the level this entity is coming from + // pGlobla->levelName is the last level the global entity was active in. + // If they aren't the same, then this global update is out of date. + if ( !FStrEq( pSaveData->szCurrentMapName, pGlobal->levelName ) ) + return 0; + + // Compute the new global offset + oldOffset = pSaveData->vecLandmarkOffset; + CBaseEntity *pNewEntity = FindGlobalEntity( tmpVars.classname, tmpVars.globalname ); + if ( pNewEntity ) + { +// ALERT( at_console, "Overlay %s with %s\n", STRING(pNewEntity->pev->classname), STRING(tmpVars.classname) ); + // Tell the restore code we're overlaying a global entity from another level + restoreHelper.SetGlobalMode( 1 ); // Don't overwrite global fields + pSaveData->vecLandmarkOffset = (pSaveData->vecLandmarkOffset - pNewEntity->pev->mins) + tmpVars.mins; + pEntity = pNewEntity;// we're going to restore this data OVER the old entity + pent = ENT( pEntity->pev ); + // Update the global table to say that the global definition of this entity should come from this level + gGlobalState.EntityUpdate( pEntity->pev->globalname, gpGlobals->mapname ); + } + else + { + // This entity will be freed automatically by the engine. If we don't do a restore on a matching entity (below) + // or call EntityUpdate() to move it to this level, we haven't changed global state at all. + return 0; + } + + } + + if ( pEntity->ObjectCaps() & FCAP_MUST_SPAWN ) + { + pEntity->Restore( restoreHelper ); + pEntity->Spawn(); + } + else + { + pEntity->Restore( restoreHelper ); + pEntity->Precache( ); + } + + // Again, could be deleted, get the pointer again. + pEntity = (CBaseEntity *)GET_PRIVATE(pent); + +#if 0 + if ( pEntity && pEntity->pev->globalname && globalEntity ) + { + ALERT( at_console, "Global %s is %s\n", STRING(pEntity->pev->globalname), STRING(pEntity->pev->model) ); + } +#endif + + // Is this an overriding global entity (coming over the transition), or one restoring in a level + if ( globalEntity ) + { +// ALERT( at_console, "After: %f %f %f %s\n", pEntity->pev->origin.x, pEntity->pev->origin.y, pEntity->pev->origin.z, STRING(pEntity->pev->model) ); + pSaveData->vecLandmarkOffset = oldOffset; + if ( pEntity ) + { + UTIL_SetOrigin( pEntity->pev, pEntity->pev->origin ); + pEntity->OverrideReset(); + } + } + else if ( pEntity && pEntity->pev->globalname ) + { + const globalentity_t *pGlobal = gGlobalState.EntityFromTable( pEntity->pev->globalname ); + if ( pGlobal ) + { + // Already dead? delete + if ( pGlobal->state == GLOBAL_DEAD ) + return -1; + else if ( !FStrEq( STRING(gpGlobals->mapname), pGlobal->levelName ) ) + { + pEntity->MakeDormant(); // Hasn't been moved to this level yet, wait but stay alive + } + // In this level & not dead, continue on as normal + } + else + { + ALERT( at_error, "Global Entity %s (%s) not in table!!!\n", STRING(pEntity->pev->globalname), STRING(pEntity->pev->classname) ); + // Spawned entities default to 'On' + gGlobalState.EntityAdd( pEntity->pev->globalname, gpGlobals->mapname, GLOBAL_ON ); + } + } + } + return 0; +} + + +void DispatchObjectCollsionBox( edict_t *pent ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + if (pEntity) + { + pEntity->SetObjectCollisionBox(); + } + else + SetObjectCollisionBox( &pent->v ); +} + + +void SaveWriteFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + CSave saveHelper( pSaveData ); + saveHelper.WriteFields( pname, pBaseData, pFields, fieldCount ); +} + + +void SaveReadFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + CRestore restoreHelper( pSaveData ); + restoreHelper.ReadFields( pname, pBaseData, pFields, fieldCount ); +} + + +edict_t * EHANDLE::Get( void ) +{ + if (m_pent) + { + if (m_pent->serialnumber == m_serialnumber) + return m_pent; + else + return NULL; + } + return NULL; +}; + +edict_t * EHANDLE::Set( edict_t *pent ) +{ + m_pent = pent; + if (pent) + m_serialnumber = m_pent->serialnumber; + return pent; +}; + + +EHANDLE :: operator CBaseEntity *() +{ + return (CBaseEntity *)GET_PRIVATE( Get( ) ); +}; + + +CBaseEntity * EHANDLE :: operator = (CBaseEntity *pEntity) +{ + if (pEntity) + { + m_pent = ENT( pEntity->pev ); + if (m_pent) + m_serialnumber = m_pent->serialnumber; + } + else + { + m_pent = NULL; + m_serialnumber = 0; + } + return pEntity; +} + +EHANDLE :: operator int () +{ + return Get() != NULL; +} + +CBaseEntity * EHANDLE :: operator -> () +{ + return (CBaseEntity *)GET_PRIVATE( Get( ) ); +} + + +// give health +int CBaseEntity :: TakeHealth( float flHealth, int bitsDamageType ) +{ + if (!pev->takedamage) + return 0; + +// heal + if ( pev->health >= pev->max_health ) + return 0; + + pev->health += flHealth; + + if (pev->health > pev->max_health) + pev->health = pev->max_health; + + return 1; +} + +// inflict damage on this entity. bitsDamageType indicates type of damage inflicted, ie: DMG_CRUSH + +int CBaseEntity :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecTemp; + + if (!pev->takedamage) + return 0; + + // UNDONE: some entity types may be immune or resistant to some bitsDamageType + + // if Attacker == Inflictor, the attack was a melee or other instant-hit attack. + // (that is, no actual entity projectile was involved in the attack so use the shooter's origin). + if ( pevAttacker == pevInflictor ) + { + vecTemp = pevInflictor->origin - ( VecBModelOrigin(pev) ); + } + else + // an actual missile was involved. + { + vecTemp = pevInflictor->origin - ( VecBModelOrigin(pev) ); + } + +// this global is still used for glass and other non-monster killables, along with decals. + g_vecAttackDir = vecTemp.Normalize(); + +// save damage based on the target's armor level + +// figure momentum add (don't let hurt brushes or other triggers move player) + if ((!FNullEnt(pevInflictor)) && (pev->movetype == MOVETYPE_WALK || pev->movetype == MOVETYPE_STEP) && (pevAttacker->solid != SOLID_TRIGGER) ) + { + Vector vecDir = pev->origin - (pevInflictor->absmin + pevInflictor->absmax) * 0.5; + vecDir = vecDir.Normalize(); + + float flForce = flDamage * ((32 * 32 * 72.0) / (pev->size.x * pev->size.y * pev->size.z)) * 5; + + if (flForce > 1000.0) + flForce = 1000.0; + pev->velocity = pev->velocity + vecDir * flForce; + } + +// do the damage + pev->health -= flDamage; + if (pev->health <= 0) + { + Killed( pevAttacker, GIB_NORMAL ); + return 0; + } + + return 1; +} + + +void CBaseEntity :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->takedamage = DAMAGE_NO; + pev->deadflag = DEAD_DEAD; + UTIL_Remove( this ); +} + + +CBaseEntity *CBaseEntity::GetNextTarget( void ) +{ + if ( FStringNull( pev->target ) ) + return NULL; + edict_t *pTarget = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(pev->target) ); + if ( FNullEnt(pTarget) ) + return NULL; + + return Instance( pTarget ); +} + +// Global Savedata for Delay +TYPEDESCRIPTION CBaseEntity::m_SaveData[] = +{ + DEFINE_FIELD( CBaseEntity, m_pGoalEnt, FIELD_CLASSPTR ), + + DEFINE_FIELD( CBaseEntity, m_pfnThink, FIELD_FUNCTION ), // UNDONE: Build table of these!!! + DEFINE_FIELD( CBaseEntity, m_pfnTouch, FIELD_FUNCTION ), + DEFINE_FIELD( CBaseEntity, m_pfnUse, FIELD_FUNCTION ), + DEFINE_FIELD( CBaseEntity, m_pfnBlocked, FIELD_FUNCTION ), +}; + + +int CBaseEntity::Save( CSave &save ) +{ + if ( save.WriteEntVars( "ENTVARS", pev ) ) + return save.WriteFields( "BASE", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + + return 0; +} + +int CBaseEntity::Restore( CRestore &restore ) +{ + int status; + + status = restore.ReadEntVars( "ENTVARS", pev ); + if ( status ) + status = restore.ReadFields( "BASE", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + + if ( pev->modelindex != 0 && !FStringNull(pev->model) ) + { + Vector mins, maxs; + mins = pev->mins; // Set model is about to destroy these + maxs = pev->maxs; + + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL(ENT(pev), STRING(pev->model)); + UTIL_SetSize(pev, mins, maxs); // Reset them + } + + return status; +} + + +// Initialize absmin & absmax to the appropriate box +void SetObjectCollisionBox( entvars_t *pev ) +{ + if ( (pev->solid == SOLID_BSP) && + (pev->angles.x || pev->angles.y|| pev->angles.z) ) + { // expand for rotation + float max, v; + int i; + + max = 0; + for (i=0 ; i<3 ; i++) + { + v = fabs( ((float *)pev->mins)[i]); + if (v > max) + max = v; + v = fabs( ((float *)pev->maxs)[i]); + if (v > max) + max = v; + } + for (i=0 ; i<3 ; i++) + { + ((float *)pev->absmin)[i] = ((float *)pev->origin)[i] - max; + ((float *)pev->absmax)[i] = ((float *)pev->origin)[i] + max; + } + } + else + { + pev->absmin = pev->origin + pev->mins; + pev->absmax = pev->origin + pev->maxs; + } + + pev->absmin.x -= 1; + pev->absmin.y -= 1; + pev->absmin.z -= 1; + pev->absmax.x += 1; + pev->absmax.y += 1; + pev->absmax.z += 1; +} + + +void CBaseEntity::SetObjectCollisionBox( void ) +{ + ::SetObjectCollisionBox( pev ); +} + + +int CBaseEntity :: Intersects( CBaseEntity *pOther ) +{ + if ( pOther->pev->absmin.x > pev->absmax.x || + pOther->pev->absmin.y > pev->absmax.y || + pOther->pev->absmin.z > pev->absmax.z || + pOther->pev->absmax.x < pev->absmin.x || + pOther->pev->absmax.y < pev->absmin.y || + pOther->pev->absmax.z < pev->absmin.z ) + return 0; + return 1; +} + +void CBaseEntity :: MakeDormant( void ) +{ + SetBits( pev->flags, FL_DORMANT ); + + // Don't touch + pev->solid = SOLID_NOT; + // Don't move + pev->movetype = MOVETYPE_NONE; + // Don't draw + SetBits( pev->effects, EF_NODRAW ); + // Don't think + pev->nextthink = 0; + // Relink + UTIL_SetOrigin( pev, pev->origin ); +} + +int CBaseEntity :: IsDormant( void ) +{ + return FBitSet( pev->flags, FL_DORMANT ); +} + +BOOL CBaseEntity :: IsInWorld( void ) +{ + // position + if (pev->origin.x >= 4096) return FALSE; + if (pev->origin.y >= 4096) return FALSE; + if (pev->origin.z >= 4096) return FALSE; + if (pev->origin.x <= -4096) return FALSE; + if (pev->origin.y <= -4096) return FALSE; + if (pev->origin.z <= -4096) return FALSE; + // speed + if (pev->velocity.x >= 2000) return FALSE; + if (pev->velocity.y >= 2000) return FALSE; + if (pev->velocity.z >= 2000) return FALSE; + if (pev->velocity.x <= -2000) return FALSE; + if (pev->velocity.y <= -2000) return FALSE; + if (pev->velocity.z <= -2000) return FALSE; + + return TRUE; +} + +int CBaseEntity::ShouldToggle( USE_TYPE useType, BOOL currentState ) +{ + if ( useType != USE_TOGGLE && useType != USE_SET ) + { + if ( (currentState && useType == USE_ON) || (!currentState && useType == USE_OFF) ) + return 0; + } + return 1; +} + + +int CBaseEntity :: DamageDecal( int bitsDamageType ) +{ + if ( pev->rendermode == kRenderTransAlpha ) + return -1; + + if ( pev->rendermode != kRenderNormal ) + return DECAL_BPROOF1; + + return DECAL_GUNSHOT1 + RANDOM_LONG(0,4); +} + + + +// NOTE: szName must be a pointer to constant memory, e.g. "monster_class" because the entity +// will keep a pointer to it after this call. +CBaseEntity * CBaseEntity::Create( char *szName, const Vector &vecOrigin, const Vector &vecAngles, edict_t *pentOwner ) +{ + edict_t *pent; + CBaseEntity *pEntity; + + pent = CREATE_NAMED_ENTITY( MAKE_STRING( szName )); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in Create!\n" ); + return NULL; + } + pEntity = Instance( pent ); + pEntity->pev->owner = pentOwner; + pEntity->pev->origin = vecOrigin; + pEntity->pev->angles = vecAngles; + DispatchSpawn( pEntity->edict() ); + return pEntity; +} + + diff --git a/dlls/cbase.h b/dlls/cbase.h new file mode 100644 index 0000000..3c7090b --- /dev/null +++ b/dlls/cbase.h @@ -0,0 +1,802 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +Class Hierachy + +CBaseEntity + CBaseDelay + CBaseToggle + CBaseItem + CBaseMonster + CBaseCycler + CBasePlayer + CBaseGroup +*/ + +#define MAX_PATH_SIZE 10 // max number of nodes available for a path. + +// These are caps bits to indicate what an object's capabilities (currently used for save/restore and level transitions) +#define FCAP_CUSTOMSAVE 0x00000001 +#define FCAP_ACROSS_TRANSITION 0x00000002 // should transfer between transitions +#define FCAP_MUST_SPAWN 0x00000004 // Spawn after restore +#define FCAP_DONT_SAVE 0x80000000 // Don't save this +#define FCAP_IMPULSE_USE 0x00000008 // can be used by the player +#define FCAP_CONTINUOUS_USE 0x00000010 // can be used by the player +#define FCAP_ONOFF_USE 0x00000020 // can be used by the player +#define FCAP_DIRECTIONAL_USE 0x00000040 // Player sends +/- 1 when using (currently only tracktrains) +#define FCAP_MASTER 0x00000080 // Can be used to "master" other entities (like multisource) + +// UNDONE: This will ignore transition volumes (trigger_transition), but not the PVS!!! +#define FCAP_FORCE_TRANSITION 0x00000080 // ALWAYS goes across transitions + +#include "archtypes.h" // DAL +#include "saverestore.h" +#include "schedule.h" + +#ifndef MONSTEREVENT_H +#include "monsterevent.h" +#endif + +// C functions for external declarations that call the appropriate C++ methods + +#ifndef CBASE_DLLEXPORT +#ifdef _WIN32 +#define CBASE_DLLEXPORT _declspec( dllexport ) +#else +#define CBASE_DLLEXPORT __attribute__ ((visibility("default"))) +#endif +#endif + +#define EXPORT CBASE_DLLEXPORT + +extern "C" CBASE_DLLEXPORT int GetEntityAPI( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion ); +extern "C" CBASE_DLLEXPORT int GetEntityAPI2( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ); + +extern int DispatchSpawn( edict_t *pent ); +extern void DispatchKeyValue( edict_t *pentKeyvalue, KeyValueData *pkvd ); +extern void DispatchTouch( edict_t *pentTouched, edict_t *pentOther ); +extern void DispatchUse( edict_t *pentUsed, edict_t *pentOther ); +extern void DispatchThink( edict_t *pent ); +extern void DispatchBlocked( edict_t *pentBlocked, edict_t *pentOther ); +extern void DispatchSave( edict_t *pent, SAVERESTOREDATA *pSaveData ); +extern int DispatchRestore( edict_t *pent, SAVERESTOREDATA *pSaveData, int globalEntity ); +extern void DispatchObjectCollsionBox( edict_t *pent ); +extern void SaveWriteFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); +extern void SaveReadFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); +extern void SaveGlobalState( SAVERESTOREDATA *pSaveData ); +extern void RestoreGlobalState( SAVERESTOREDATA *pSaveData ); +extern void ResetGlobalState( void ); + +typedef enum { USE_OFF = 0, USE_ON = 1, USE_SET = 2, USE_TOGGLE = 3 } USE_TYPE; + +extern void FireTargets( const char *targetName, CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +typedef void (CBaseEntity::*BASEPTR)(void); +typedef void (CBaseEntity::*ENTITYFUNCPTR)(CBaseEntity *pOther ); +typedef void (CBaseEntity::*USEPTR)( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +// For CLASSIFY +#define CLASS_NONE 0 +#define CLASS_MACHINE 1 +#define CLASS_PLAYER 2 +#define CLASS_HUMAN_PASSIVE 3 +#define CLASS_HUMAN_MILITARY 4 +#define CLASS_ALIEN_MILITARY 5 +#define CLASS_ALIEN_PASSIVE 6 +#define CLASS_ALIEN_MONSTER 7 +#define CLASS_ALIEN_PREY 8 +#define CLASS_ALIEN_PREDATOR 9 +#define CLASS_INSECT 10 +#define CLASS_PLAYER_ALLY 11 +#define CLASS_PLAYER_BIOWEAPON 12 // hornets and snarks.launched by players +#define CLASS_ALIEN_BIOWEAPON 13 // hornets and snarks.launched by the alien menace +#define CLASS_BARNACLE 99 // special because no one pays attention to it, and it eats a wide cross-section of creatures. + +class CBaseEntity; +class CBaseMonster; +class CBasePlayerItem; +class CSquadMonster; + + +#define SF_NORESPAWN ( 1 << 30 )// !!!set this bit on guns and stuff that should never respawn. + +// +// EHANDLE. Safe way to point to CBaseEntities who may die between frames +// +class EHANDLE +{ +private: + edict_t *m_pent; + int m_serialnumber; +public: + edict_t *Get( void ); + edict_t *Set( edict_t *pent ); + + operator int (); + + operator CBaseEntity *(); + + CBaseEntity * operator = (CBaseEntity *pEntity); + CBaseEntity * operator ->(); +}; + + +// +// Base Entity. All entity types derive from this +// +class CBaseEntity +{ +public: + // Constructor. Set engine to use C/C++ callback functions + // pointers to engine data + entvars_t *pev; // Don't need to save/restore this pointer, the engine resets it + + // path corners + CBaseEntity *m_pGoalEnt;// path corner we are heading towards + CBaseEntity *m_pLink;// used for temporary link-list operations. + + // initialization functions + virtual void Spawn( void ) { return; } + virtual void Precache( void ) { return; } + virtual void KeyValue( KeyValueData* pkvd) { pkvd->fHandled = FALSE; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return FCAP_ACROSS_TRANSITION; } + virtual void Activate( void ) {} + + // Setup the object->object collision box (pev->mins / pev->maxs is the object->world collision box) + virtual void SetObjectCollisionBox( void ); + +// Classify - returns the type of group (i.e, "houndeye", or "human military" so that monsters with different classnames +// still realize that they are teammates. (overridden for monsters that form groups) + virtual int Classify ( void ) { return CLASS_NONE; }; + virtual void DeathNotice ( entvars_t *pevChild ) {}// monster maker children use this to tell the monster maker that they have died. + + + static TYPEDESCRIPTION m_SaveData[]; + + virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + virtual int TakeHealth( float flHealth, int bitsDamageType ); + virtual void Killed( entvars_t *pevAttacker, int iGib ); + virtual int BloodColor( void ) { return DONT_BLEED; } + virtual void TraceBleed( float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); + virtual BOOL IsTriggered( CBaseEntity *pActivator ) {return TRUE;} + virtual CBaseMonster *MyMonsterPointer( void ) { return NULL;} + virtual CSquadMonster *MySquadMonsterPointer( void ) { return NULL;} + virtual int GetToggleState( void ) { return TS_AT_TOP; } + virtual void AddPoints( int score, BOOL bAllowNegativeScore ) {} + virtual void AddPointsToTeam( int score, BOOL bAllowNegativeScore ) {} + virtual BOOL AddPlayerItem( CBasePlayerItem *pItem ) { return 0; } + virtual BOOL RemovePlayerItem( CBasePlayerItem *pItem ) { return 0; } + virtual int GiveAmmo( int iAmount, char *szName, int iMax ) { return -1; }; + virtual float GetDelay( void ) { return 0; } + virtual int IsMoving( void ) { return pev->velocity != g_vecZero; } + virtual void OverrideReset( void ) {} + virtual int DamageDecal( int bitsDamageType ); + // This is ONLY used by the node graph to test movement through a door + virtual void SetToggleState( int state ) {} + virtual void StartSneaking( void ) {} + virtual void StopSneaking( void ) {} + virtual BOOL OnControls( entvars_t *pev ) { return FALSE; } + virtual BOOL IsSneaking( void ) { return FALSE; } + virtual BOOL IsAlive( void ) { return (pev->deadflag == DEAD_NO) && pev->health > 0; } + virtual BOOL IsBSPModel( void ) { return pev->solid == SOLID_BSP || pev->movetype == MOVETYPE_PUSHSTEP; } + virtual BOOL ReflectGauss( void ) { return ( IsBSPModel() && !pev->takedamage ); } + virtual BOOL HasTarget( string_t targetname ) { return FStrEq(STRING(targetname), STRING(pev->targetname) ); } + virtual BOOL IsInWorld( void ); + virtual BOOL IsPlayer( void ) { return FALSE; } + virtual BOOL IsNetClient( void ) { return FALSE; } + virtual const char *TeamID( void ) { return ""; } + + +// virtual void SetActivator( CBaseEntity *pActivator ) {} + virtual CBaseEntity *GetNextTarget( void ); + + // fundamental callbacks + void (CBaseEntity ::*m_pfnThink)(void); + void (CBaseEntity ::*m_pfnTouch)( CBaseEntity *pOther ); + void (CBaseEntity ::*m_pfnUse)( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void (CBaseEntity ::*m_pfnBlocked)( CBaseEntity *pOther ); + + virtual void Think( void ) { if (m_pfnThink) (this->*m_pfnThink)(); }; + virtual void Touch( CBaseEntity *pOther ) { if (m_pfnTouch) (this->*m_pfnTouch)( pOther ); }; + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) + { + if (m_pfnUse) + (this->*m_pfnUse)( pActivator, pCaller, useType, value ); + } + virtual void Blocked( CBaseEntity *pOther ) { if (m_pfnBlocked) (this->*m_pfnBlocked)( pOther ); }; + + // allow engine to allocate instance data + void *operator new( size_t stAllocateBlock, entvars_t *pev ) + { + return (void *)ALLOC_PRIVATE(ENT(pev), stAllocateBlock); + }; + + // don't use this. +#if _MSC_VER >= 1200 // only build this code if MSVC++ 6.0 or higher + void operator delete(void *pMem, entvars_t *pev) + { + pev->flags |= FL_KILLME; + }; +#endif + + void UpdateOnRemove( void ); + + // common member functions + void EXPORT SUB_Remove( void ); + void EXPORT SUB_DoNothing( void ); + void EXPORT SUB_StartFadeOut ( void ); + void EXPORT SUB_FadeOut ( void ); + void EXPORT SUB_CallUseToggle( void ) { this->Use( this, this, USE_TOGGLE, 0 ); } + int ShouldToggle( USE_TYPE useType, BOOL currentState ); + void FireBullets( ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq = 4, int iDamage = 0, entvars_t *pevAttacker = NULL ); + Vector FireBulletsPlayer( ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq = 4, int iDamage = 0, entvars_t *pevAttacker = NULL, int shared_rand = 0 ); + + virtual CBaseEntity *Respawn( void ) { return NULL; } + + void SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ); + // Do the bounding boxes of these two intersect? + int Intersects( CBaseEntity *pOther ); + void MakeDormant( void ); + int IsDormant( void ); + BOOL IsLockedByMaster( void ) { return FALSE; } + + static CBaseEntity *Instance( edict_t *pent ) + { + if ( !pent ) + pent = ENT(0); + CBaseEntity *pEnt = (CBaseEntity *)GET_PRIVATE(pent); + return pEnt; + } + + static CBaseEntity *Instance( entvars_t *pev ) { return Instance( ENT( pev ) ); } + static CBaseEntity *Instance( int eoffset) { return Instance( ENT( eoffset) ); } + + CBaseMonster *GetMonsterPointer( entvars_t *pevMonster ) + { + CBaseEntity *pEntity = Instance( pevMonster ); + if ( pEntity ) + return pEntity->MyMonsterPointer(); + return NULL; + } + CBaseMonster *GetMonsterPointer( edict_t *pentMonster ) + { + CBaseEntity *pEntity = Instance( pentMonster ); + if ( pEntity ) + return pEntity->MyMonsterPointer(); + return NULL; + } + + + // Ugly code to lookup all functions to make sure they are exported when set. +#ifdef _DEBUG + void FunctionCheck( void *pFunction, char *name ) + { + if (pFunction && !NAME_FOR_FUNCTION((uint32)pFunction) ) + ALERT( at_error, "No EXPORT: %s:%s (%08lx)\n", STRING(pev->classname), name, (uint32)pFunction ); + } + + BASEPTR ThinkSet( BASEPTR func, char *name ) + { + m_pfnThink = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnThink)))), name ); + return func; + } + ENTITYFUNCPTR TouchSet( ENTITYFUNCPTR func, char *name ) + { + m_pfnTouch = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnTouch)))), name ); + return func; + } + USEPTR UseSet( USEPTR func, char *name ) + { + m_pfnUse = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnUse)))), name ); + return func; + } + ENTITYFUNCPTR BlockedSet( ENTITYFUNCPTR func, char *name ) + { + m_pfnBlocked = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnBlocked)))), name ); + return func; + } + +#endif + + + // virtual functions used by a few classes + + // used by monsters that are created by the MonsterMaker + virtual void UpdateOwner( void ) { return; }; + + + // + static CBaseEntity *Create( char *szName, const Vector &vecOrigin, const Vector &vecAngles, edict_t *pentOwner = NULL ); + + virtual BOOL FBecomeProne( void ) {return FALSE;}; + edict_t *edict() { return ENT( pev ); }; + EOFFSET eoffset( ) { return OFFSET( pev ); }; + int entindex( ) { return ENTINDEX( edict() ); }; + + virtual Vector Center( ) { return (pev->absmax + pev->absmin) * 0.5; }; // center point of entity + virtual Vector EyePosition( ) { return pev->origin + pev->view_ofs; }; // position of eyes + virtual Vector EarPosition( ) { return pev->origin + pev->view_ofs; }; // position of ears + virtual Vector BodyTarget( const Vector &posSrc ) { return Center( ); }; // position to shoot at + + virtual int Illumination( ) { return GETENTITYILLUM( ENT( pev ) ); }; + + virtual BOOL FVisible ( CBaseEntity *pEntity ); + virtual BOOL FVisible ( const Vector &vecOrigin ); + + //We use this variables to store each ammo count. + int ammo_9mm; + int ammo_357; + int ammo_bolts; + int ammo_buckshot; + int ammo_rockets; + int ammo_uranium; + int ammo_hornets; + int ammo_argrens; + //Special stuff for grenades and satchels. + float m_flStartThrow; + float m_flReleaseThrow; + int m_chargeReady; + int m_fInAttack; + + enum EGON_FIRESTATE { FIRE_OFF, FIRE_CHARGE }; + int m_fireState; +}; + + + +// Ugly technique to override base member functions +// Normally it's illegal to cast a pointer to a member function of a derived class to a pointer to a +// member function of a base class. static_cast is a sleezy way around that problem. + +#ifdef _DEBUG + +#define SetThink( a ) ThinkSet( static_cast (a), #a ) +#define SetTouch( a ) TouchSet( static_cast (a), #a ) +#define SetUse( a ) UseSet( static_cast (a), #a ) +#define SetBlocked( a ) BlockedSet( static_cast (a), #a ) + +#else + +#define SetThink( a ) m_pfnThink = static_cast (a) +#define SetTouch( a ) m_pfnTouch = static_cast (a) +#define SetUse( a ) m_pfnUse = static_cast (a) +#define SetBlocked( a ) m_pfnBlocked = static_cast (a) + +#endif + + +class CPointEntity : public CBaseEntity +{ +public: + void Spawn( void ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +private: +}; + + +typedef struct locksounds // sounds that doors and buttons make when locked/unlocked +{ + string_t sLockedSound; // sound a door makes when it's locked + string_t sLockedSentence; // sentence group played when door is locked + string_t sUnlockedSound; // sound a door makes when it's unlocked + string_t sUnlockedSentence; // sentence group played when door is unlocked + + int iLockedSentence; // which sentence in sentence group to play next + int iUnlockedSentence; // which sentence in sentence group to play next + + float flwaitSound; // time delay between playing consecutive 'locked/unlocked' sounds + float flwaitSentence; // time delay between playing consecutive sentences + BYTE bEOFLocked; // true if hit end of list of locked sentences + BYTE bEOFUnlocked; // true if hit end of list of unlocked sentences +} locksound_t; + +void PlayLockSounds(entvars_t *pev, locksound_t *pls, int flocked, int fbutton); + +// +// MultiSouce +// + +#define MAX_MULTI_TARGETS 16 // maximum number of targets a single multi_manager entity may be assigned. +#define MS_MAX_TARGETS 32 + +class CMultiSource : public CPointEntity +{ +public: + void Spawn( ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int ObjectCaps( void ) { return (CPointEntity::ObjectCaps() | FCAP_MASTER); } + BOOL IsTriggered( CBaseEntity *pActivator ); + void EXPORT Register( void ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + EHANDLE m_rgEntities[MS_MAX_TARGETS]; + int m_rgTriggered[MS_MAX_TARGETS]; + + int m_iTotal; + string_t m_globalstate; +}; + + +// +// generic Delay entity. +// +class CBaseDelay : public CBaseEntity +{ +public: + float m_flDelay; + int m_iszKillTarget; + + virtual void KeyValue( KeyValueData* pkvd); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + // common member functions + void SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ); + void EXPORT DelayThink( void ); +}; + + +class CBaseAnimating : public CBaseDelay +{ +public: + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + // Basic Monster Animation functions + float StudioFrameAdvance( float flInterval = 0.0 ); // accumulate animation frame time from last time called until now + int GetSequenceFlags( void ); + int LookupActivity ( int activity ); + int LookupActivityHeaviest ( int activity ); + int LookupSequence ( const char *label ); + void ResetSequenceInfo ( ); + void DispatchAnimEvents ( float flFutureInterval = 0.1 ); // Handle events that have happend since last time called up until X seconds into the future + virtual void HandleAnimEvent( MonsterEvent_t *pEvent ) { return; }; + float SetBoneController ( int iController, float flValue ); + void InitBoneControllers ( void ); + float SetBlending ( int iBlender, float flValue ); + void GetBonePosition ( int iBone, Vector &origin, Vector &angles ); + void GetAutomovement( Vector &origin, Vector &angles, float flInterval = 0.1 ); + int FindTransition( int iEndingSequence, int iGoalSequence, int *piDir ); + void GetAttachment ( int iAttachment, Vector &origin, Vector &angles ); + void SetBodygroup( int iGroup, int iValue ); + int GetBodygroup( int iGroup ); + int ExtractBbox( int sequence, float *mins, float *maxs ); + void SetSequenceBox( void ); + + // animation needs + float m_flFrameRate; // computed FPS for current sequence + float m_flGroundSpeed; // computed linear movement rate for current sequence + float m_flLastEventCheck; // last time the event list was checked + BOOL m_fSequenceFinished;// flag set when StudioAdvanceFrame moves across a frame boundry + BOOL m_fSequenceLoops; // true if the sequence loops +}; + + +// +// generic Toggle entity. +// +#define SF_ITEM_USE_ONLY 256 // ITEM_USE_ONLY = BUTTON_USE_ONLY = DOOR_USE_ONLY!!! + +class CBaseToggle : public CBaseAnimating +{ +public: + void KeyValue( KeyValueData *pkvd ); + + TOGGLE_STATE m_toggle_state; + float m_flActivateFinished;//like attack_finished, but for doors + float m_flMoveDistance;// how far a door should slide or rotate + float m_flWait; + float m_flLip; + float m_flTWidth;// for plats + float m_flTLength;// for plats + + Vector m_vecPosition1; + Vector m_vecPosition2; + Vector m_vecAngle1; + Vector m_vecAngle2; + + int m_cTriggersLeft; // trigger_counter only, # of activations remaining + float m_flHeight; + EHANDLE m_hActivator; + void (CBaseToggle::*m_pfnCallWhenMoveDone)(void); + Vector m_vecFinalDest; + Vector m_vecFinalAngle; + + int m_bitsDamageInflict; // DMG_ damage type that the door or tigger does + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + virtual int GetToggleState( void ) { return m_toggle_state; } + virtual float GetDelay( void ) { return m_flWait; } + + // common member functions + void LinearMove( Vector vecDest, float flSpeed ); + void EXPORT LinearMoveDone( void ); + void AngularMove( Vector vecDestAngle, float flSpeed ); + void EXPORT AngularMoveDone( void ); + BOOL IsLockedByMaster( void ); + + static float AxisValue( int flags, const Vector &angles ); + static void AxisDir( entvars_t *pev ); + static float AxisDelta( int flags, const Vector &angle1, const Vector &angle2 ); + + string_t m_sMaster; // If this button has a master switch, this is the targetname. + // A master switch must be of the multisource type. If all + // of the switches in the multisource have been triggered, then + // the button will be allowed to operate. Otherwise, it will be + // deactivated. +}; +#define SetMoveDone( a ) m_pfnCallWhenMoveDone = static_cast (a) + + +// people gib if their health is <= this at the time of death +#define GIB_HEALTH_VALUE -30 + +#define ROUTE_SIZE 8 // how many waypoints a monster can store at one time +#define MAX_OLD_ENEMIES 4 // how many old enemies to remember + +#define bits_CAP_DUCK ( 1 << 0 )// crouch +#define bits_CAP_JUMP ( 1 << 1 )// jump/leap +#define bits_CAP_STRAFE ( 1 << 2 )// strafe ( walk/run sideways) +#define bits_CAP_SQUAD ( 1 << 3 )// can form squads +#define bits_CAP_SWIM ( 1 << 4 )// proficiently navigate in water +#define bits_CAP_CLIMB ( 1 << 5 )// climb ladders/ropes +#define bits_CAP_USE ( 1 << 6 )// open doors/push buttons/pull levers +#define bits_CAP_HEAR ( 1 << 7 )// can hear forced sounds +#define bits_CAP_AUTO_DOORS ( 1 << 8 )// can trigger auto doors +#define bits_CAP_OPEN_DOORS ( 1 << 9 )// can open manual doors +#define bits_CAP_TURN_HEAD ( 1 << 10)// can turn head, always bone controller 0 + +#define bits_CAP_RANGE_ATTACK1 ( 1 << 11)// can do a range attack 1 +#define bits_CAP_RANGE_ATTACK2 ( 1 << 12)// can do a range attack 2 +#define bits_CAP_MELEE_ATTACK1 ( 1 << 13)// can do a melee attack 1 +#define bits_CAP_MELEE_ATTACK2 ( 1 << 14)// can do a melee attack 2 + +#define bits_CAP_FLY ( 1 << 15)// can fly, move all around + +#define bits_CAP_DOORS_GROUP (bits_CAP_USE | bits_CAP_AUTO_DOORS | bits_CAP_OPEN_DOORS) + +// used by suit voice to indicate damage sustained and repaired type to player + +// instant damage + +#define DMG_GENERIC 0 // generic damage was done +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FREEZE (1 << 4) // frozen +#define DMG_FALL (1 << 5) // fell too far +#define DMG_BLAST (1 << 6) // explosive blast damage +#define DMG_CLUB (1 << 7) // crowbar, punch, headbutt +#define DMG_SHOCK (1 << 8) // electric shock +#define DMG_SONIC (1 << 9) // sound pulse shockwave +#define DMG_ENERGYBEAM (1 << 10) // laser or other high energy beam +#define DMG_NEVERGIB (1 << 12) // with this bit OR'd in, no damage type will be able to gib victims upon death +#define DMG_ALWAYSGIB (1 << 13) // with this bit OR'd in, any damage type can be made to gib victims upon death. +#define DMG_DROWN (1 << 14) // Drowning +// time-based damage +#define DMG_TIMEBASED (~(0x3fff)) // mask for time-based damage + +#define DMG_PARALYZE (1 << 15) // slows affected creature down +#define DMG_NERVEGAS (1 << 16) // nerve toxins, very bad +#define DMG_POISON (1 << 17) // blood poisioning +#define DMG_RADIATION (1 << 18) // radiation exposure +#define DMG_DROWNRECOVER (1 << 19) // drowning recovery +#define DMG_ACID (1 << 20) // toxic chemicals or acid burns +#define DMG_SLOWBURN (1 << 21) // in an oven +#define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer +#define DMG_MORTAR (1 << 23) // Hit by air raid (done to distinguish grenade from mortar) + +// these are the damage types that are allowed to gib corpses +#define DMG_GIB_CORPSE ( DMG_CRUSH | DMG_FALL | DMG_BLAST | DMG_SONIC | DMG_CLUB ) + +// these are the damage types that have client hud art +#define DMG_SHOWNHUD (DMG_POISON | DMG_ACID | DMG_FREEZE | DMG_SLOWFREEZE | DMG_DROWN | DMG_BURN | DMG_SLOWBURN | DMG_NERVEGAS | DMG_RADIATION | DMG_SHOCK) + +// NOTE: tweak these values based on gameplay feedback: + +#define PARALYZE_DURATION 2 // number of 2 second intervals to take damage +#define PARALYZE_DAMAGE 1.0 // damage to take each 2 second interval + +#define NERVEGAS_DURATION 2 +#define NERVEGAS_DAMAGE 5.0 + +#define POISON_DURATION 5 +#define POISON_DAMAGE 2.0 + +#define RADIATION_DURATION 2 +#define RADIATION_DAMAGE 1.0 + +#define ACID_DURATION 2 +#define ACID_DAMAGE 5.0 + +#define SLOWBURN_DURATION 2 +#define SLOWBURN_DAMAGE 1.0 + +#define SLOWFREEZE_DURATION 2 +#define SLOWFREEZE_DAMAGE 1.0 + + +#define itbd_Paralyze 0 +#define itbd_NerveGas 1 +#define itbd_Poison 2 +#define itbd_Radiation 3 +#define itbd_DrownRecover 4 +#define itbd_Acid 5 +#define itbd_SlowBurn 6 +#define itbd_SlowFreeze 7 +#define CDMG_TIMEBASED 8 + +// when calling KILLED(), a value that governs gib behavior is expected to be +// one of these three values +#define GIB_NORMAL 0// gib if entity was overkilled +#define GIB_NEVER 1// never gib, no matter how much death damage is done ( freezing, etc ) +#define GIB_ALWAYS 2// always gib ( Houndeye Shock, Barnacle Bite ) + +class CBaseMonster; +class CCineMonster; +class CSound; + +#include "basemonster.h" + + +char *ButtonSound( int sound ); // get string of button sound number + + +// +// Generic Button +// +class CBaseButton : public CBaseToggle +{ +public: + void Spawn( void ); + virtual void Precache( void ); + void RotSpawn( void ); + virtual void KeyValue( KeyValueData* pkvd); + + void ButtonActivate( ); + void SparkSoundCache( void ); + + void EXPORT ButtonShot( void ); + void EXPORT ButtonTouch( CBaseEntity *pOther ); + void EXPORT ButtonSpark ( void ); + void EXPORT TriggerAndWait( void ); + void EXPORT ButtonReturn( void ); + void EXPORT ButtonBackHome( void ); + void EXPORT ButtonUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + enum BUTTON_CODE { BUTTON_NOTHING, BUTTON_ACTIVATE, BUTTON_RETURN }; + BUTTON_CODE ButtonResponseToTouch( void ); + + static TYPEDESCRIPTION m_SaveData[]; + // Buttons that don't take damage can be IMPULSE used + virtual int ObjectCaps( void ) { return (CBaseToggle:: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | (pev->takedamage?0:FCAP_IMPULSE_USE); } + + BOOL m_fStayPushed; // button stays pushed in until touched again? + BOOL m_fRotating; // a rotating button? default is a sliding button. + + string_t m_strChangeTarget; // if this field is not null, this is an index into the engine string array. + // when this button is touched, it's target entity's TARGET field will be set + // to the button's ChangeTarget. This allows you to make a func_train switch paths, etc. + + locksound_t m_ls; // door lock sounds + + BYTE m_bLockedSound; // ordinals from entity selection + BYTE m_bLockedSentence; + BYTE m_bUnlockedSound; + BYTE m_bUnlockedSentence; + int m_sounds; +}; + +// +// Weapons +// + +#define BAD_WEAPON 0x00007FFF + +// +// Converts a entvars_t * to a class pointer +// It will allocate the class and entity if necessary +// +template T * GetClassPtr( T *a ) +{ + entvars_t *pev = (entvars_t *)a; + + // allocate entity if necessary + if (pev == NULL) + pev = VARS(CREATE_ENTITY()); + + // get the private data + a = (T *)GET_PRIVATE(ENT(pev)); + + if (a == NULL) + { + // allocate private data + a = new(pev) T; + a->pev = pev; + } + return a; +} + + +/* +bit_PUSHBRUSH_DATA | bit_TOGGLE_DATA +bit_MONSTER_DATA +bit_DELAY_DATA +bit_TOGGLE_DATA | bit_DELAY_DATA | bit_MONSTER_DATA +bit_PLAYER_DATA | bit_MONSTER_DATA +bit_MONSTER_DATA | CYCLER_DATA +bit_LIGHT_DATA +path_corner_data +bit_MONSTER_DATA | wildcard_data +bit_MONSTER_DATA | bit_GROUP_DATA +boid_flock_data +boid_data +CYCLER_DATA +bit_ITEM_DATA +bit_ITEM_DATA | func_hud_data +bit_TOGGLE_DATA | bit_ITEM_DATA +EOFFSET +env_sound_data +env_sound_data +push_trigger_data +*/ + +#define TRACER_FREQ 4 // Tracers fire every 4 bullets + +typedef struct _SelAmmo +{ + BYTE Ammo1Type; + BYTE Ammo1; + BYTE Ammo2Type; + BYTE Ammo2; +} SelAmmo; + + +// this moved here from world.cpp, to allow classes to be derived from it +//======================= +// CWorld +// +// This spawns first when each level begins. +//======================= +class CWorld : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); +}; diff --git a/dlls/cdll_dll.h b/dlls/cdll_dll.h new file mode 100644 index 0000000..a5e62de --- /dev/null +++ b/dlls/cdll_dll.h @@ -0,0 +1,46 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cdll_dll.h + +// this file is included by both the game-dll and the client-dll, + +#ifndef CDLL_DLL_H +#define CDLL_DLL_H + +#define MAX_WEAPONS 32 // ??? + +#define MAX_WEAPON_SLOTS 5 // hud item selection slots +#define MAX_ITEM_TYPES 6 // hud item selection slots + +#define MAX_ITEMS 5 // hard coded item types + +#define HIDEHUD_WEAPONS ( 1<<0 ) +#define HIDEHUD_FLASHLIGHT ( 1<<1 ) +#define HIDEHUD_ALL ( 1<<2 ) +#define HIDEHUD_HEALTH ( 1<<3 ) + +#define MAX_AMMO_TYPES 32 // ??? +#define MAX_AMMO_SLOTS 32 // not really slots + +#define HUD_PRINTNOTIFY 1 +#define HUD_PRINTCONSOLE 2 +#define HUD_PRINTTALK 3 +#define HUD_PRINTCENTER 4 + + +#define WEAPON_SUIT 31 + +#endif diff --git a/dlls/client.cpp b/dlls/client.cpp new file mode 100644 index 0000000..c1e5fb6 --- /dev/null +++ b/dlls/client.cpp @@ -0,0 +1,1925 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Robin, 4-22-98: Moved set_suicide_frame() here from player.cpp to allow us to +// have one without a hardcoded player.mdl in tf_client.cpp + +/* + +===== client.cpp ======================================================== + + client/server game specific stuff + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "player.h" +#include "spectator.h" +#include "client.h" +#include "soundent.h" +#include "gamerules.h" +#include "game.h" +#include "customentity.h" +#include "weapons.h" +#include "weaponinfo.h" +#include "usercmd.h" +#include "netadr.h" +#include "pm_shared.h" + +#if !defined ( _WIN32 ) +#include +#endif + +extern DLL_GLOBAL ULONG g_ulModelIndexPlayer; +extern DLL_GLOBAL BOOL g_fGameOver; +extern DLL_GLOBAL int g_iSkillLevel; +extern DLL_GLOBAL ULONG g_ulFrameCount; + +extern void CopyToBodyQue(entvars_t* pev); +extern int giPrecacheGrunt; +extern int gmsgSayText; + +extern cvar_t allow_spectators; + +extern int g_teamplay; + +void LinkUserMessages( void ); + +/* + * used by kill command and disconnect command + * ROBIN: Moved here from player.cpp, to allow multiple player models + */ +void set_suicide_frame(entvars_t* pev) +{ + if (!FStrEq(STRING(pev->model), "models/player.mdl")) + return; // allready gibbed + +// pev->frame = $deatha11; + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_TOSS; + pev->deadflag = DEAD_DEAD; + pev->nextthink = -1; +} + + +/* +=========== +ClientConnect + +called when a player connects to a server +============ +*/ +BOOL ClientConnect( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) +{ + return g_pGameRules->ClientConnected( pEntity, pszName, pszAddress, szRejectReason ); + +// a client connecting during an intermission can cause problems +// if (intermission_running) +// ExitIntermission (); + +} + + +/* +=========== +ClientDisconnect + +called when a player disconnects from a server + +GLOBALS ASSUMED SET: g_fGameOver +============ +*/ +void ClientDisconnect( edict_t *pEntity ) +{ + if (g_fGameOver) + return; + + char text[256] = "" ; + if ( pEntity->v.netname ) + _snprintf( text, sizeof(text), "- %s has left the game\n", STRING(pEntity->v.netname) ); + text[ sizeof(text) - 1 ] = 0; + MESSAGE_BEGIN( MSG_ALL, gmsgSayText, NULL ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + + CSound *pSound; + pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( pEntity ) ); + { + // since this client isn't around to think anymore, reset their sound. + if ( pSound ) + { + pSound->Reset(); + } + } + +// since the edict doesn't get deleted, fix it so it doesn't interfere. + pEntity->v.takedamage = DAMAGE_NO;// don't attract autoaim + pEntity->v.solid = SOLID_NOT;// nonsolid + UTIL_SetOrigin ( &pEntity->v, pEntity->v.origin ); + + g_pGameRules->ClientDisconnected( pEntity ); +} + + +// called by ClientKill and DeadThink +void respawn(entvars_t* pev, BOOL fCopyCorpse) +{ + if (gpGlobals->coop || gpGlobals->deathmatch) + { + if ( fCopyCorpse ) + { + // make a copy of the dead body for appearances sake + CopyToBodyQue(pev); + } + + // respawn player + GetClassPtr( (CBasePlayer *)pev)->Spawn( ); + } + else + { // restart the entire server + SERVER_COMMAND("reload\n"); + } +} + +/* +============ +ClientKill + +Player entered the suicide command + +GLOBALS ASSUMED SET: g_ulModelIndexPlayer +============ +*/ +void ClientKill( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + + CBasePlayer *pl = (CBasePlayer*) CBasePlayer::Instance( pev ); + + if ( pl->m_fNextSuicideTime > gpGlobals->time ) + return; // prevent suiciding too ofter + + pl->m_fNextSuicideTime = gpGlobals->time + 1; // don't let them suicide for 5 seconds after suiciding + + // have the player kill themself + pev->health = 0; + pl->Killed( pev, GIB_NEVER ); + +// pev->modelindex = g_ulModelIndexPlayer; +// pev->frags -= 2; // extra penalty +// respawn( pev ); +} + +/* +=========== +ClientPutInServer + +called each time a player is spawned +============ +*/ +void ClientPutInServer( edict_t *pEntity ) +{ + CBasePlayer *pPlayer; + + entvars_t *pev = &pEntity->v; + + pPlayer = GetClassPtr((CBasePlayer *)pev); + pPlayer->SetCustomDecalFrames(-1); // Assume none; + + // Allocate a CBasePlayer for pev, and call spawn + pPlayer->Spawn() ; + + // Reset interpolation during first frame + pPlayer->pev->effects |= EF_NOINTERP; + + pPlayer->pev->iuser1 = 0; // disable any spec modes + pPlayer->pev->iuser2 = 0; +} + +#include "voice_gamemgr.h" +extern CVoiceGameMgr g_VoiceGameMgr; + + + +#if defined( _MSC_VER ) || defined( WIN32 ) +typedef wchar_t uchar16; +typedef unsigned int uchar32; +#else +typedef unsigned short uchar16; +typedef wchar_t uchar32; +#endif + +//----------------------------------------------------------------------------- +// Purpose: determine if a uchar32 represents a valid Unicode code point +//----------------------------------------------------------------------------- +bool Q_IsValidUChar32( uchar32 uVal ) +{ + // Values > 0x10FFFF are explicitly invalid; ditto for UTF-16 surrogate halves, + // values ending in FFFE or FFFF, or values in the 0x00FDD0-0x00FDEF reserved range + return ( uVal < 0x110000u ) && ( (uVal - 0x00D800u) > 0x7FFu ) && ( (uVal & 0xFFFFu) < 0xFFFEu ) && ( ( uVal - 0x00FDD0u ) > 0x1Fu ); +} + + +// Decode one character from a UTF-8 encoded string. Treats 6-byte CESU-8 sequences +// as a single character, as if they were a correctly-encoded 4-byte UTF-8 sequence. +int Q_UTF8ToUChar32( const char *pUTF8_, uchar32 &uValueOut, bool &bErrorOut ) +{ + const uint8 *pUTF8 = (const uint8 *)pUTF8_; + + int nBytes = 1; + uint32 uValue = pUTF8[0]; + uint32 uMinValue = 0; + + // 0....... single byte + if ( uValue < 0x80 ) + goto decodeFinishedNoCheck; + + // Expecting at least a two-byte sequence with 0xC0 <= first <= 0xF7 (110...... and 11110...) + if ( (uValue - 0xC0u) > 0x37u || ( pUTF8[1] & 0xC0 ) != 0x80 ) + goto decodeError; + + uValue = (uValue << 6) - (0xC0 << 6) + pUTF8[1] - 0x80; + nBytes = 2; + uMinValue = 0x80; + + // 110..... two-byte lead byte + if ( !( uValue & (0x20 << 6) ) ) + goto decodeFinished; + + // Expecting at least a three-byte sequence + if ( ( pUTF8[2] & 0xC0 ) != 0x80 ) + goto decodeError; + + uValue = (uValue << 6) - (0x20 << 12) + pUTF8[2] - 0x80; + nBytes = 3; + uMinValue = 0x800; + + // 1110.... three-byte lead byte + if ( !( uValue & (0x10 << 12) ) ) + goto decodeFinishedMaybeCESU8; + + // Expecting a four-byte sequence, longest permissible in UTF-8 + if ( ( pUTF8[3] & 0xC0 ) != 0x80 ) + goto decodeError; + + uValue = (uValue << 6) - (0x10 << 18) + pUTF8[3] - 0x80; + nBytes = 4; + uMinValue = 0x10000; + + // 11110... four-byte lead byte. fall through to finished. + +decodeFinished: + if ( uValue >= uMinValue && Q_IsValidUChar32( uValue ) ) + { +decodeFinishedNoCheck: + uValueOut = uValue; + bErrorOut = false; + return nBytes; + } +decodeError: + uValueOut = '?'; + bErrorOut = true; + return nBytes; + +decodeFinishedMaybeCESU8: + // Do we have a full UTF-16 surrogate pair that's been UTF-8 encoded afterwards? + // That is, do we have 0xD800-0xDBFF followed by 0xDC00-0xDFFF? If so, decode it all. + if ( ( uValue - 0xD800u ) < 0x400u && pUTF8[3] == 0xED && (uint8)( pUTF8[4] - 0xB0 ) < 0x10 && ( pUTF8[5] & 0xC0 ) == 0x80 ) + { + uValue = 0x10000 + ( ( uValue - 0xD800u ) << 10 ) + ( (uint8)( pUTF8[4] - 0xB0 ) << 6 ) + pUTF8[5] - 0x80; + nBytes = 6; + uMinValue = 0x10000; + } + goto decodeFinished; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Returns true if UTF-8 string contains invalid sequences. +//----------------------------------------------------------------------------- +bool Q_UnicodeValidate( const char *pUTF8 ) +{ + bool bError = false; + while ( *pUTF8 ) + { + uchar32 uVal; + // Our UTF-8 decoder silently fixes up 6-byte CESU-8 (improperly re-encoded UTF-16) sequences. + // However, these are technically not valid UTF-8. So if we eat 6 bytes at once, it's an error. + int nCharSize = Q_UTF8ToUChar32( pUTF8, uVal, bError ); + if ( bError || nCharSize == 6 ) + return false; + pUTF8 += nCharSize; + } + return true; +} + +//// HOST_SAY +// String comes in as +// say blah blah blah +// or as +// blah blah blah +// +void Host_Say( edict_t *pEntity, int teamonly ) +{ + CBasePlayer *client; + int j; + char *p; + char text[128]; + char szTemp[256]; + const char *cpSay = "say"; + const char *cpSayTeam = "say_team"; + const char *pcmd = CMD_ARGV(0); + + // We can get a raw string now, without the "say " prepended + if ( CMD_ARGC() == 0 ) + return; + + entvars_t *pev = &pEntity->v; + CBasePlayer* player = GetClassPtr((CBasePlayer *)pev); + + //Not yet. + if ( player->m_flNextChatTime > gpGlobals->time ) + return; + + if ( !stricmp( pcmd, cpSay) || !stricmp( pcmd, cpSayTeam ) ) + { + if ( CMD_ARGC() >= 2 ) + { + p = (char *)CMD_ARGS(); + } + else + { + // say with a blank message, nothing to do + return; + } + } + else // Raw text, need to prepend argv[0] + { + if ( CMD_ARGC() >= 2 ) + { + sprintf( szTemp, "%s %s", ( char * )pcmd, (char *)CMD_ARGS() ); + } + else + { + // Just a one word command, use the first word...sigh + sprintf( szTemp, "%s", ( char * )pcmd ); + } + p = szTemp; + } + +// remove quotes if present + if (*p == '"') + { + p++; + p[strlen(p)-1] = 0; + } + +// make sure the text has content + + if ( !p || !p[0] || !Q_UnicodeValidate ( p ) ) + return; // no character found, so say nothing + +// turn on color set 2 (color on, no sound) + // turn on color set 2 (color on, no sound) + if ( player->IsObserver() && ( teamonly ) ) + sprintf( text, "%c(SPEC) %s: ", 2, STRING( pEntity->v.netname ) ); + else if ( teamonly ) + sprintf( text, "%c(TEAM) %s: ", 2, STRING( pEntity->v.netname ) ); + else + sprintf( text, "%c%s: ", 2, STRING( pEntity->v.netname ) ); + + j = sizeof(text) - 2 - strlen(text); // -2 for /n and null terminator + if ( (int)strlen(p) > j ) + p[j] = 0; + + strcat( text, p ); + strcat( text, "\n" ); + + + player->m_flNextChatTime = gpGlobals->time + CHAT_INTERVAL; + + // loop through all players + // Start with the first player. + // This may return the world in single player if the client types something between levels or during spawn + // so check it, or it will infinite loop + + client = NULL; + while ( ((client = (CBasePlayer*)UTIL_FindEntityByClassname( client, "player" )) != NULL) && (!FNullEnt(client->edict())) ) + { + if ( !client->pev ) + continue; + + if ( client->edict() == pEntity ) + continue; + + if ( !(client->IsNetClient()) ) // Not a client ? (should never be true) + continue; + + // can the receiver hear the sender? or has he muted him? + if ( g_VoiceGameMgr.PlayerHasBlockedPlayer( client, player ) ) + continue; + + if ( !player->IsObserver() && teamonly && g_pGameRules->PlayerRelationship(client, CBaseEntity::Instance(pEntity)) != GR_TEAMMATE ) + continue; + + // Spectators can only talk to other specs + if ( player->IsObserver() && teamonly ) + if ( !client->IsObserver() ) + continue; + + MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, client->pev ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + + } + + // print to the sending client + MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, &pEntity->v ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + + // echo to server console + g_engfuncs.pfnServerPrint( text ); + + char * temp; + if ( teamonly ) + temp = "say_team"; + else + temp = "say"; + + // team match? + if ( g_teamplay ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" %s \"%s\"\n", + STRING( pEntity->v.netname ), + GETPLAYERUSERID( pEntity ), + GETPLAYERAUTHID( pEntity ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pEntity ), "model" ), + temp, + p ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" %s \"%s\"\n", + STRING( pEntity->v.netname ), + GETPLAYERUSERID( pEntity ), + GETPLAYERAUTHID( pEntity ), + GETPLAYERUSERID( pEntity ), + temp, + p ); + } +} + + +/* +=========== +ClientCommand +called each time a player uses a "cmd" command +============ +*/ +extern float g_flWeaponCheat; + +// Use CMD_ARGV, CMD_ARGV, and CMD_ARGC to get pointers the character string command. +void ClientCommand( edict_t *pEntity ) +{ + const char *pcmd = CMD_ARGV(0); + const char *pstr; + + // Is the client spawned yet? + if ( !pEntity->pvPrivateData ) + return; + + entvars_t *pev = &pEntity->v; + + if ( FStrEq(pcmd, "say" ) ) + { + Host_Say( pEntity, 0 ); + } + else if ( FStrEq(pcmd, "say_team" ) ) + { + Host_Say( pEntity, 1 ); + } + else if ( FStrEq(pcmd, "fullupdate" ) ) + { + GetClassPtr((CBasePlayer *)pev)->ForceClientDllUpdate(); + } + else if ( FStrEq(pcmd, "give" ) ) + { + if ( g_flWeaponCheat != 0.0) + { + int iszItem = ALLOC_STRING( CMD_ARGV(1) ); // Make a copy of the classname + GetClassPtr((CBasePlayer *)pev)->GiveNamedItem( STRING(iszItem) ); + } + } + + else if ( FStrEq(pcmd, "drop" ) ) + { + // player is dropping an item. + GetClassPtr((CBasePlayer *)pev)->DropPlayerItem((char *)CMD_ARGV(1)); + } + else if ( FStrEq(pcmd, "fov" ) ) + { + if ( g_flWeaponCheat && CMD_ARGC() > 1) + { + GetClassPtr((CBasePlayer *)pev)->m_iFOV = atoi( CMD_ARGV(1) ); + } + else + { + CLIENT_PRINTF( pEntity, print_console, UTIL_VarArgs( "\"fov\" is \"%d\"\n", (int)GetClassPtr((CBasePlayer *)pev)->m_iFOV ) ); + } + } + else if ( FStrEq(pcmd, "use" ) ) + { + GetClassPtr((CBasePlayer *)pev)->SelectItem((char *)CMD_ARGV(1)); + } + else if (((pstr = strstr(pcmd, "weapon_")) != NULL) && (pstr == pcmd)) + { + GetClassPtr((CBasePlayer *)pev)->SelectItem(pcmd); + } + else if (FStrEq(pcmd, "lastinv" )) + { + GetClassPtr((CBasePlayer *)pev)->SelectLastItem(); + } + else if ( FStrEq( pcmd, "spectate" ) ) // clients wants to become a spectator + { + // always allow proxies to become a spectator + if ( (pev->flags & FL_PROXY) || allow_spectators.value ) + { + CBasePlayer * pPlayer = GetClassPtr((CBasePlayer *)pev); + + edict_t *pentSpawnSpot = g_pGameRules->GetPlayerSpawnSpot( pPlayer ); + pPlayer->StartObserver( pev->origin, VARS(pentSpawnSpot)->angles); + + // notify other clients of player switching to spectator mode + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, UTIL_VarArgs( "%s switched to spectator mode\n", + ( pev->netname && STRING(pev->netname)[0] != 0 ) ? STRING(pev->netname) : "unconnected" ) ); + } + else + ClientPrint( pev, HUD_PRINTCONSOLE, "Spectator mode is disabled.\n" ); + + } + else if ( FStrEq( pcmd, "specmode" ) ) // new spectator mode + { + CBasePlayer * pPlayer = GetClassPtr((CBasePlayer *)pev); + + if ( pPlayer->IsObserver() ) + pPlayer->Observer_SetMode( atoi( CMD_ARGV(1) ) ); + } + else if ( FStrEq(pcmd, "closemenus" ) ) + { + // just ignore it + } + else if ( FStrEq( pcmd, "follownext" ) ) // follow next player + { + CBasePlayer * pPlayer = GetClassPtr((CBasePlayer *)pev); + + if ( pPlayer->IsObserver() ) + pPlayer->Observer_FindNextPlayer( atoi( CMD_ARGV(1) )?true:false ); + } + else if ( g_pGameRules->ClientCommand( GetClassPtr((CBasePlayer *)pev), pcmd ) ) + { + // MenuSelect returns true only if the command is properly handled, so don't print a warning + } + else + { + // tell the user they entered an unknown command + char command[128]; + + // check the length of the command (prevents crash) + // max total length is 192 ...and we're adding a string below ("Unknown command: %s\n") + strncpy( command, pcmd, 127 ); + command[127] = '\0'; + + // tell the user they entered an unknown command + ClientPrint( &pEntity->v, HUD_PRINTCONSOLE, UTIL_VarArgs( "Unknown command: %s\n", command ) ); + } +} + + +/* +======================== +ClientUserInfoChanged + +called after the player changes +userinfo - gives dll a chance to modify it before +it gets sent into the rest of the engine. +======================== +*/ +void ClientUserInfoChanged( edict_t *pEntity, char *infobuffer ) +{ + // Is the client spawned yet? + if ( !pEntity->pvPrivateData ) + return; + + // msg everyone if someone changes their name, and it isn't the first time (changing no name to current name) + if ( pEntity->v.netname && STRING(pEntity->v.netname)[0] != 0 && !FStrEq( STRING(pEntity->v.netname), g_engfuncs.pfnInfoKeyValue( infobuffer, "name" )) ) + { + char sName[256]; + char *pName = g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ); + strncpy( sName, pName, sizeof(sName) - 1 ); + sName[ sizeof(sName) - 1 ] = '\0'; + + // First parse the name and remove any %'s + for ( char *pApersand = sName; pApersand != NULL && *pApersand != 0; pApersand++ ) + { + // Replace it with a space + if ( *pApersand == '%' ) + *pApersand = ' '; + } + + // Set the name + g_engfuncs.pfnSetClientKeyValue( ENTINDEX(pEntity), infobuffer, "name", sName ); + + if (gpGlobals->maxClients > 1) + { + char text[256]; + sprintf( text, "* %s changed name to %s\n", STRING(pEntity->v.netname), g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ) ); + MESSAGE_BEGIN( MSG_ALL, gmsgSayText, NULL ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + } + + // team match? + if ( g_teamplay ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" changed name to \"%s\"\n", + STRING( pEntity->v.netname ), + GETPLAYERUSERID( pEntity ), + GETPLAYERAUTHID( pEntity ), + g_engfuncs.pfnInfoKeyValue( infobuffer, "model" ), + g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ) ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" changed name to \"%s\"\n", + STRING( pEntity->v.netname ), + GETPLAYERUSERID( pEntity ), + GETPLAYERAUTHID( pEntity ), + GETPLAYERUSERID( pEntity ), + g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ) ); + } + } + + g_pGameRules->ClientUserInfoChanged( GetClassPtr((CBasePlayer *)&pEntity->v), infobuffer ); +} + +static int g_serveractive = 0; + +void ServerDeactivate( void ) +{ + // It's possible that the engine will call this function more times than is necessary + // Therefore, only run it one time for each call to ServerActivate + if ( g_serveractive != 1 ) + { + return; + } + + g_serveractive = 0; + + // Peform any shutdown operations here... + // +} + +void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) +{ + int i; + CBaseEntity *pClass; + + // Every call to ServerActivate should be matched by a call to ServerDeactivate + g_serveractive = 1; + + // Clients have not been initialized yet + for ( i = 0; i < edictCount; i++ ) + { + if ( pEdictList[i].free ) + continue; + + // Clients aren't necessarily initialized until ClientPutInServer() + if ( i < clientMax || !pEdictList[i].pvPrivateData ) + continue; + + pClass = CBaseEntity::Instance( &pEdictList[i] ); + // Activate this entity if it's got a class & isn't dormant + if ( pClass && !(pClass->pev->flags & FL_DORMANT) ) + { + pClass->Activate(); + } + else + { + ALERT( at_console, "Can't instance %s\n", STRING(pEdictList[i].v.classname) ); + } + } + + // Link user messages here to make sure first client can get them... + LinkUserMessages(); +} + + +/* +================ +PlayerPreThink + +Called every frame before physics are run +================ +*/ +void PlayerPreThink( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + CBasePlayer *pPlayer = (CBasePlayer *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->PreThink( ); +} + +/* +================ +PlayerPostThink + +Called every frame after physics are run +================ +*/ +void PlayerPostThink( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + CBasePlayer *pPlayer = (CBasePlayer *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->PostThink( ); +} + + + +void ParmsNewLevel( void ) +{ +} + + +void ParmsChangeLevel( void ) +{ + // retrieve the pointer to the save data + SAVERESTOREDATA *pSaveData = (SAVERESTOREDATA *)gpGlobals->pSaveData; + + if ( pSaveData ) + pSaveData->connectionCount = BuildChangeList( pSaveData->levelList, MAX_LEVEL_CONNECTIONS ); +} + + +// +// GLOBALS ASSUMED SET: g_ulFrameCount +// +void StartFrame( void ) +{ + if ( g_pGameRules ) + g_pGameRules->Think(); + + if ( g_fGameOver ) + return; + + gpGlobals->teamplay = teamplay.value; + g_ulFrameCount++; +} + + +void ClientPrecache( void ) +{ + // setup precaches always needed + PRECACHE_SOUND("player/sprayer.wav"); // spray paint sound for PreAlpha + + // PRECACHE_SOUND("player/pl_jumpland2.wav"); // UNDONE: play 2x step sound + + PRECACHE_SOUND("player/pl_fallpain2.wav"); + PRECACHE_SOUND("player/pl_fallpain3.wav"); + + PRECACHE_SOUND("player/pl_step1.wav"); // walk on concrete + PRECACHE_SOUND("player/pl_step2.wav"); + PRECACHE_SOUND("player/pl_step3.wav"); + PRECACHE_SOUND("player/pl_step4.wav"); + + PRECACHE_SOUND("common/npc_step1.wav"); // NPC walk on concrete + PRECACHE_SOUND("common/npc_step2.wav"); + PRECACHE_SOUND("common/npc_step3.wav"); + PRECACHE_SOUND("common/npc_step4.wav"); + + PRECACHE_SOUND("player/pl_metal1.wav"); // walk on metal + PRECACHE_SOUND("player/pl_metal2.wav"); + PRECACHE_SOUND("player/pl_metal3.wav"); + PRECACHE_SOUND("player/pl_metal4.wav"); + + PRECACHE_SOUND("player/pl_dirt1.wav"); // walk on dirt + PRECACHE_SOUND("player/pl_dirt2.wav"); + PRECACHE_SOUND("player/pl_dirt3.wav"); + PRECACHE_SOUND("player/pl_dirt4.wav"); + + PRECACHE_SOUND("player/pl_duct1.wav"); // walk in duct + PRECACHE_SOUND("player/pl_duct2.wav"); + PRECACHE_SOUND("player/pl_duct3.wav"); + PRECACHE_SOUND("player/pl_duct4.wav"); + + PRECACHE_SOUND("player/pl_grate1.wav"); // walk on grate + PRECACHE_SOUND("player/pl_grate2.wav"); + PRECACHE_SOUND("player/pl_grate3.wav"); + PRECACHE_SOUND("player/pl_grate4.wav"); + + PRECACHE_SOUND("player/pl_slosh1.wav"); // walk in shallow water + PRECACHE_SOUND("player/pl_slosh2.wav"); + PRECACHE_SOUND("player/pl_slosh3.wav"); + PRECACHE_SOUND("player/pl_slosh4.wav"); + + PRECACHE_SOUND("player/pl_tile1.wav"); // walk on tile + PRECACHE_SOUND("player/pl_tile2.wav"); + PRECACHE_SOUND("player/pl_tile3.wav"); + PRECACHE_SOUND("player/pl_tile4.wav"); + PRECACHE_SOUND("player/pl_tile5.wav"); + + PRECACHE_SOUND("player/pl_swim1.wav"); // breathe bubbles + PRECACHE_SOUND("player/pl_swim2.wav"); + PRECACHE_SOUND("player/pl_swim3.wav"); + PRECACHE_SOUND("player/pl_swim4.wav"); + + PRECACHE_SOUND("player/pl_ladder1.wav"); // climb ladder rung + PRECACHE_SOUND("player/pl_ladder2.wav"); + PRECACHE_SOUND("player/pl_ladder3.wav"); + PRECACHE_SOUND("player/pl_ladder4.wav"); + + PRECACHE_SOUND("player/pl_wade1.wav"); // wade in water + PRECACHE_SOUND("player/pl_wade2.wav"); + PRECACHE_SOUND("player/pl_wade3.wav"); + PRECACHE_SOUND("player/pl_wade4.wav"); + + PRECACHE_SOUND("debris/wood1.wav"); // hit wood texture + PRECACHE_SOUND("debris/wood2.wav"); + PRECACHE_SOUND("debris/wood3.wav"); + + PRECACHE_SOUND("plats/train_use1.wav"); // use a train + + PRECACHE_SOUND("buttons/spark5.wav"); // hit computer texture + PRECACHE_SOUND("buttons/spark6.wav"); + PRECACHE_SOUND("debris/glass1.wav"); + PRECACHE_SOUND("debris/glass2.wav"); + PRECACHE_SOUND("debris/glass3.wav"); + + PRECACHE_SOUND( SOUND_FLASHLIGHT_ON ); + PRECACHE_SOUND( SOUND_FLASHLIGHT_OFF ); + +// player gib sounds + PRECACHE_SOUND("common/bodysplat.wav"); + +// player pain sounds + PRECACHE_SOUND("player/pl_pain2.wav"); + PRECACHE_SOUND("player/pl_pain4.wav"); + PRECACHE_SOUND("player/pl_pain5.wav"); + PRECACHE_SOUND("player/pl_pain6.wav"); + PRECACHE_SOUND("player/pl_pain7.wav"); + + PRECACHE_MODEL("models/player.mdl"); + + // hud sounds + + PRECACHE_SOUND("common/wpn_hudoff.wav"); + PRECACHE_SOUND("common/wpn_hudon.wav"); + PRECACHE_SOUND("common/wpn_moveselect.wav"); + PRECACHE_SOUND("common/wpn_select.wav"); + PRECACHE_SOUND("common/wpn_denyselect.wav"); + + + // geiger sounds + + PRECACHE_SOUND("player/geiger6.wav"); + PRECACHE_SOUND("player/geiger5.wav"); + PRECACHE_SOUND("player/geiger4.wav"); + PRECACHE_SOUND("player/geiger3.wav"); + PRECACHE_SOUND("player/geiger2.wav"); + PRECACHE_SOUND("player/geiger1.wav"); + + if (giPrecacheGrunt) + UTIL_PrecacheOther("monster_human_grunt"); +} + +/* +=============== +GetGameDescription + +Returns the descriptive name of this .dll. E.g., Half-Life, or Team Fortress 2 +=============== +*/ +const char *GetGameDescription() +{ + if ( g_pGameRules ) // this function may be called before the world has spawned, and the game rules initialized + return g_pGameRules->GetGameDescription(); + else + return "Half-Life"; +} + +/* +================ +Sys_Error + +Engine is going to shut down, allows setting a breakpoint in game .dll to catch that occasion +================ +*/ +void Sys_Error( const char *error_string ) +{ + // Default case, do nothing. MOD AUTHORS: Add code ( e.g., _asm { int 3 }; here to cause a breakpoint for debugging your game .dlls +} + +/* +================ +PlayerCustomization + +A new player customization has been registered on the server +UNDONE: This only sets the # of frames of the spray can logo +animation right now. +================ +*/ +void PlayerCustomization( edict_t *pEntity, customization_t *pCust ) +{ + entvars_t *pev = &pEntity->v; + CBasePlayer *pPlayer = (CBasePlayer *)GET_PRIVATE(pEntity); + + if (!pPlayer) + { + ALERT(at_console, "PlayerCustomization: Couldn't get player!\n"); + return; + } + + if (!pCust) + { + ALERT(at_console, "PlayerCustomization: NULL customization!\n"); + return; + } + + switch (pCust->resource.type) + { + case t_decal: + pPlayer->SetCustomDecalFrames(pCust->nUserData2); // Second int is max # of frames. + break; + case t_sound: + case t_skin: + case t_model: + // Ignore for now. + break; + default: + ALERT(at_console, "PlayerCustomization: Unknown customization type!\n"); + break; + } +} + +/* +================ +SpectatorConnect + +A spectator has joined the game +================ +*/ +void SpectatorConnect( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + CBaseSpectator *pPlayer = (CBaseSpectator *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->SpectatorConnect( ); +} + +/* +================ +SpectatorConnect + +A spectator has left the game +================ +*/ +void SpectatorDisconnect( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + CBaseSpectator *pPlayer = (CBaseSpectator *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->SpectatorDisconnect( ); +} + +/* +================ +SpectatorConnect + +A spectator has sent a usercmd +================ +*/ +void SpectatorThink( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + CBaseSpectator *pPlayer = (CBaseSpectator *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->SpectatorThink( ); +} + +//////////////////////////////////////////////////////// +// PAS and PVS routines for client messaging +// + +/* +================ +SetupVisibility + +A client can have a separate "view entity" indicating that his/her view should depend on the origin of that +view entity. If that's the case, then pViewEntity will be non-NULL and will be used. Otherwise, the current +entity's origin is used. Either is offset by the view_ofs to get the eye position. + +From the eye position, we set up the PAS and PVS to use for filtering network messages to the client. At this point, we could + override the actual PAS or PVS values, or use a different origin. + +NOTE: Do not cache the values of pas and pvs, as they depend on reusable memory in the engine, they are only good for this one frame +================ +*/ +void SetupVisibility( edict_t *pViewEntity, edict_t *pClient, unsigned char **pvs, unsigned char **pas ) +{ + Vector org; + edict_t *pView = pClient; + + // Find the client's PVS + if ( pViewEntity ) + { + pView = pViewEntity; + } + + if ( pClient->v.flags & FL_PROXY ) + { + *pvs = NULL; // the spectator proxy sees + *pas = NULL; // and hears everything + return; + } + + org = pView->v.origin + pView->v.view_ofs; + if ( pView->v.flags & FL_DUCKING ) + { + org = org + ( VEC_HULL_MIN - VEC_DUCK_HULL_MIN ); + } + + *pvs = ENGINE_SET_PVS ( (float *)&org ); + *pas = ENGINE_SET_PAS ( (float *)&org ); +} + +#include "entity_state.h" + +/* +AddToFullPack + +Return 1 if the entity state has been filled in for the ent and the entity will be propagated to the client, 0 otherwise + +state is the server maintained copy of the state info that is transmitted to the client +a MOD could alter values copied into state to send the "host" a different look for a particular entity update, etc. +e and ent are the entity that is being added to the update, if 1 is returned +host is the player's edict of the player whom we are sending the update to +player is 1 if the ent/e is a player and 0 otherwise +pSet is either the PAS or PVS that we previous set up. We can use it to ask the engine to filter the entity against the PAS or PVS. +we could also use the pas/ pvs that we set in SetupVisibility, if we wanted to. Caching the value is valid in that case, but still only for the current frame +*/ +int AddToFullPack( struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet ) +{ + int i; + + // don't send if flagged for NODRAW and it's not the host getting the message + if ( ( ent->v.effects & EF_NODRAW ) && + ( ent != host ) ) + return 0; + + // Ignore ents without valid / visible models + if ( !ent->v.modelindex || !STRING( ent->v.model ) ) + return 0; + + // Don't send spectators to other players + if ( ( ent->v.flags & FL_SPECTATOR ) && ( ent != host ) ) + { + return 0; + } + + // Ignore if not the host and not touching a PVS/PAS leaf + // If pSet is NULL, then the test will always succeed and the entity will be added to the update + if ( ent != host ) + { + if ( !ENGINE_CHECK_VISIBILITY( (const struct edict_s *)ent, pSet ) ) + { + return 0; + } + } + + + // Don't send entity to local client if the client says it's predicting the entity itself. + if ( ent->v.flags & FL_SKIPLOCALHOST ) + { + if ( ( hostflags & 1 ) && ( ent->v.owner == host ) ) + return 0; + } + + if ( host->v.groupinfo ) + { + UTIL_SetGroupTrace( host->v.groupinfo, GROUP_OP_AND ); + + // Should always be set, of course + if ( ent->v.groupinfo ) + { + if ( g_groupop == GROUP_OP_AND ) + { + if ( !(ent->v.groupinfo & host->v.groupinfo ) ) + return 0; + } + else if ( g_groupop == GROUP_OP_NAND ) + { + if ( ent->v.groupinfo & host->v.groupinfo ) + return 0; + } + } + + UTIL_UnsetGroupTrace(); + } + + memset( state, 0, sizeof( *state ) ); + + // Assign index so we can track this entity from frame to frame and + // delta from it. + state->number = e; + state->entityType = ENTITY_NORMAL; + + // Flag custom entities. + if ( ent->v.flags & FL_CUSTOMENTITY ) + { + state->entityType = ENTITY_BEAM; + } + + // + // Copy state data + // + + // Round animtime to nearest millisecond + state->animtime = (int)(1000.0 * ent->v.animtime ) / 1000.0; + + memcpy( state->origin, ent->v.origin, 3 * sizeof( float ) ); + memcpy( state->angles, ent->v.angles, 3 * sizeof( float ) ); + memcpy( state->mins, ent->v.mins, 3 * sizeof( float ) ); + memcpy( state->maxs, ent->v.maxs, 3 * sizeof( float ) ); + + memcpy( state->startpos, ent->v.startpos, 3 * sizeof( float ) ); + memcpy( state->endpos, ent->v.endpos, 3 * sizeof( float ) ); + + state->impacttime = ent->v.impacttime; + state->starttime = ent->v.starttime; + + state->modelindex = ent->v.modelindex; + + state->frame = ent->v.frame; + + state->skin = ent->v.skin; + state->effects = ent->v.effects; + + // This non-player entity is being moved by the game .dll and not the physics simulation system + // make sure that we interpolate it's position on the client if it moves + if ( !player && + ent->v.animtime && + ent->v.velocity[ 0 ] == 0 && + ent->v.velocity[ 1 ] == 0 && + ent->v.velocity[ 2 ] == 0 ) + { + state->eflags |= EFLAG_SLERP; + } + + state->scale = ent->v.scale; + state->solid = ent->v.solid; + state->colormap = ent->v.colormap; + + state->movetype = ent->v.movetype; + state->sequence = ent->v.sequence; + state->framerate = ent->v.framerate; + state->body = ent->v.body; + + for (i = 0; i < 4; i++) + { + state->controller[i] = ent->v.controller[i]; + } + + for (i = 0; i < 2; i++) + { + state->blending[i] = ent->v.blending[i]; + } + + state->rendermode = ent->v.rendermode; + state->renderamt = ent->v.renderamt; + state->renderfx = ent->v.renderfx; + state->rendercolor.r = ent->v.rendercolor.x; + state->rendercolor.g = ent->v.rendercolor.y; + state->rendercolor.b = ent->v.rendercolor.z; + + state->aiment = 0; + if ( ent->v.aiment ) + { + state->aiment = ENTINDEX( ent->v.aiment ); + } + + state->owner = 0; + if ( ent->v.owner ) + { + int owner = ENTINDEX( ent->v.owner ); + + // Only care if owned by a player + if ( owner >= 1 && owner <= gpGlobals->maxClients ) + { + state->owner = owner; + } + } + + // HACK: Somewhat... + // Class is overridden for non-players to signify a breakable glass object ( sort of a class? ) + if ( !player ) + { + state->playerclass = ent->v.playerclass; + } + + // Special stuff for players only + if ( player ) + { + memcpy( state->basevelocity, ent->v.basevelocity, 3 * sizeof( float ) ); + + state->weaponmodel = MODEL_INDEX( STRING( ent->v.weaponmodel ) ); + state->gaitsequence = ent->v.gaitsequence; + state->spectator = ent->v.flags & FL_SPECTATOR; + state->friction = ent->v.friction; + + state->gravity = ent->v.gravity; +// state->team = ent->v.team; +// + state->usehull = ( ent->v.flags & FL_DUCKING ) ? 1 : 0; + state->health = ent->v.health; + } + + return 1; +} + +// defaults for clientinfo messages +#define DEFAULT_VIEWHEIGHT 28 + +/* +=================== +CreateBaseline + +Creates baselines used for network encoding, especially for player data since players are not spawned until connect time. +=================== +*/ +void CreateBaseline( int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, vec3_t player_mins, vec3_t player_maxs ) +{ + baseline->origin = entity->v.origin; + baseline->angles = entity->v.angles; + baseline->frame = entity->v.frame; + baseline->skin = (short)entity->v.skin; + + // render information + baseline->rendermode = (byte)entity->v.rendermode; + baseline->renderamt = (byte)entity->v.renderamt; + baseline->rendercolor.r = (byte)entity->v.rendercolor.x; + baseline->rendercolor.g = (byte)entity->v.rendercolor.y; + baseline->rendercolor.b = (byte)entity->v.rendercolor.z; + baseline->renderfx = (byte)entity->v.renderfx; + + if ( player ) + { + baseline->mins = player_mins; + baseline->maxs = player_maxs; + + baseline->colormap = eindex; + baseline->modelindex = playermodelindex; + baseline->friction = 1.0; + baseline->movetype = MOVETYPE_WALK; + + baseline->scale = entity->v.scale; + baseline->solid = SOLID_SLIDEBOX; + baseline->framerate = 1.0; + baseline->gravity = 1.0; + + } + else + { + baseline->mins = entity->v.mins; + baseline->maxs = entity->v.maxs; + + baseline->colormap = 0; + baseline->modelindex = entity->v.modelindex;//SV_ModelIndex(pr_strings + entity->v.model); + baseline->movetype = entity->v.movetype; + + baseline->scale = entity->v.scale; + baseline->solid = entity->v.solid; + baseline->framerate = entity->v.framerate; + baseline->gravity = entity->v.gravity; + } +} + +typedef struct +{ + char name[32]; + int field; +} entity_field_alias_t; + +#define FIELD_ORIGIN0 0 +#define FIELD_ORIGIN1 1 +#define FIELD_ORIGIN2 2 +#define FIELD_ANGLES0 3 +#define FIELD_ANGLES1 4 +#define FIELD_ANGLES2 5 + +static entity_field_alias_t entity_field_alias[]= +{ + { "origin[0]", 0 }, + { "origin[1]", 0 }, + { "origin[2]", 0 }, + { "angles[0]", 0 }, + { "angles[1]", 0 }, + { "angles[2]", 0 }, +}; + +void Entity_FieldInit( struct delta_s *pFields ) +{ + entity_field_alias[ FIELD_ORIGIN0 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ORIGIN0 ].name ); + entity_field_alias[ FIELD_ORIGIN1 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ORIGIN1 ].name ); + entity_field_alias[ FIELD_ORIGIN2 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ORIGIN2 ].name ); + entity_field_alias[ FIELD_ANGLES0 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ANGLES0 ].name ); + entity_field_alias[ FIELD_ANGLES1 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ANGLES1 ].name ); + entity_field_alias[ FIELD_ANGLES2 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ANGLES2 ].name ); +} + +/* +================== +Entity_Encode + +Callback for sending entity_state_t info over network. +FIXME: Move to script +================== +*/ +void Entity_Encode( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) +{ + entity_state_t *f, *t; + int localplayer = 0; + static int initialized = 0; + + if ( !initialized ) + { + Entity_FieldInit( pFields ); + initialized = 1; + } + + f = (entity_state_t *)from; + t = (entity_state_t *)to; + + // Never send origin to local player, it's sent with more resolution in clientdata_t structure + localplayer = ( t->number - 1 ) == ENGINE_CURRENT_PLAYER(); + if ( localplayer ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + + if ( ( t->impacttime != 0 ) && ( t->starttime != 0 ) ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ANGLES0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ANGLES1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ANGLES2 ].field ); + } + + if ( ( t->movetype == MOVETYPE_FOLLOW ) && + ( t->aiment != 0 ) ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + else if ( t->aiment != f->aiment ) + { + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } +} + +static entity_field_alias_t player_field_alias[]= +{ + { "origin[0]", 0 }, + { "origin[1]", 0 }, + { "origin[2]", 0 }, +}; + +void Player_FieldInit( struct delta_s *pFields ) +{ + player_field_alias[ FIELD_ORIGIN0 ].field = DELTA_FINDFIELD( pFields, player_field_alias[ FIELD_ORIGIN0 ].name ); + player_field_alias[ FIELD_ORIGIN1 ].field = DELTA_FINDFIELD( pFields, player_field_alias[ FIELD_ORIGIN1 ].name ); + player_field_alias[ FIELD_ORIGIN2 ].field = DELTA_FINDFIELD( pFields, player_field_alias[ FIELD_ORIGIN2 ].name ); +} + +/* +================== +Player_Encode + +Callback for sending entity_state_t for players info over network. +================== +*/ +void Player_Encode( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) +{ + entity_state_t *f, *t; + int localplayer = 0; + static int initialized = 0; + + if ( !initialized ) + { + Player_FieldInit( pFields ); + initialized = 1; + } + + f = (entity_state_t *)from; + t = (entity_state_t *)to; + + // Never send origin to local player, it's sent with more resolution in clientdata_t structure + localplayer = ( t->number - 1 ) == ENGINE_CURRENT_PLAYER(); + if ( localplayer ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + + if ( ( t->movetype == MOVETYPE_FOLLOW ) && + ( t->aiment != 0 ) ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + else if ( t->aiment != f->aiment ) + { + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } +} + +#define CUSTOMFIELD_ORIGIN0 0 +#define CUSTOMFIELD_ORIGIN1 1 +#define CUSTOMFIELD_ORIGIN2 2 +#define CUSTOMFIELD_ANGLES0 3 +#define CUSTOMFIELD_ANGLES1 4 +#define CUSTOMFIELD_ANGLES2 5 +#define CUSTOMFIELD_SKIN 6 +#define CUSTOMFIELD_SEQUENCE 7 +#define CUSTOMFIELD_ANIMTIME 8 + +entity_field_alias_t custom_entity_field_alias[]= +{ + { "origin[0]", 0 }, + { "origin[1]", 0 }, + { "origin[2]", 0 }, + { "angles[0]", 0 }, + { "angles[1]", 0 }, + { "angles[2]", 0 }, + { "skin", 0 }, + { "sequence", 0 }, + { "animtime", 0 }, +}; + +void Custom_Entity_FieldInit( struct delta_s *pFields ) +{ + custom_entity_field_alias[ CUSTOMFIELD_ORIGIN0 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN0 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ORIGIN1 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN1 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ORIGIN2 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN2 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANGLES0 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES0 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANGLES1 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES1 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANGLES2 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES2 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_SKIN ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_SKIN ].name ); + custom_entity_field_alias[ CUSTOMFIELD_SEQUENCE ].field= DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_SEQUENCE ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANIMTIME ].field= DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANIMTIME ].name ); +} + +/* +================== +Custom_Encode + +Callback for sending entity_state_t info ( for custom entities ) over network. +FIXME: Move to script +================== +*/ +void Custom_Encode( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) +{ + entity_state_t *f, *t; + int beamType; + static int initialized = 0; + + if ( !initialized ) + { + Custom_Entity_FieldInit( pFields ); + initialized = 1; + } + + f = (entity_state_t *)from; + t = (entity_state_t *)to; + + beamType = t->rendermode & 0x0f; + + if ( beamType != BEAM_POINTS && beamType != BEAM_ENTPOINT ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN2 ].field ); + } + + if ( beamType != BEAM_POINTS ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES0 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES1 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES2 ].field ); + } + + if ( beamType != BEAM_ENTS && beamType != BEAM_ENTPOINT ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_SKIN ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_SEQUENCE ].field ); + } + + // animtime is compared by rounding first + // see if we really shouldn't actually send it + if ( (int)f->animtime == (int)t->animtime ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANIMTIME ].field ); + } +} + +/* +================= +RegisterEncoders + +Allows game .dll to override network encoding of certain types of entities and tweak values, etc. +================= +*/ +void RegisterEncoders( void ) +{ + DELTA_ADDENCODER( "Entity_Encode", Entity_Encode ); + DELTA_ADDENCODER( "Custom_Encode", Custom_Encode ); + DELTA_ADDENCODER( "Player_Encode", Player_Encode ); +} + +int GetWeaponData( struct edict_s *player, struct weapon_data_s *info ) +{ +#if defined( CLIENT_WEAPONS ) + int i; + weapon_data_t *item; + entvars_t *pev = &player->v; + CBasePlayer *pl = dynamic_cast< CBasePlayer *>( CBasePlayer::Instance( pev ) ); + CBasePlayerWeapon *gun; + + ItemInfo II; + + memset( info, 0, 32 * sizeof( weapon_data_t ) ); + + if ( !pl ) + return 1; + + // go through all of the weapons and make a list of the ones to pack + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( pl->m_rgpPlayerItems[ i ] ) + { + // there's a weapon here. Should I pack it? + CBasePlayerItem *pPlayerItem = pl->m_rgpPlayerItems[ i ]; + + while ( pPlayerItem ) + { + gun = dynamic_cast( pPlayerItem->GetWeaponPtr() ); + if ( gun && gun->UseDecrement() ) + { + // Get The ID. + memset( &II, 0, sizeof( II ) ); + gun->GetItemInfo( &II ); + + if ( II.iId >= 0 && II.iId < 32 ) + { + item = &info[ II.iId ]; + + item->m_iId = II.iId; + item->m_iClip = gun->m_iClip; + + item->m_flTimeWeaponIdle = max( gun->m_flTimeWeaponIdle, -0.001 ); + item->m_flNextPrimaryAttack = max( gun->m_flNextPrimaryAttack, -0.001 ); + item->m_flNextSecondaryAttack = max( gun->m_flNextSecondaryAttack, -0.001 ); + item->m_fInReload = gun->m_fInReload; + item->m_fInSpecialReload = gun->m_fInSpecialReload; + item->fuser1 = max( gun->pev->fuser1, -0.001 ); + item->fuser2 = gun->m_flStartThrow; + item->fuser3 = gun->m_flReleaseThrow; + item->iuser1 = gun->m_chargeReady; + item->iuser2 = gun->m_fInAttack; + item->iuser3 = gun->m_fireState; + + +// item->m_flPumpTime = max( gun->m_flPumpTime, -0.001 ); + } + } + pPlayerItem = pPlayerItem->m_pNext; + } + } + } +#else + memset( info, 0, 32 * sizeof( weapon_data_t ) ); +#endif + return 1; +} + +/* +================= +UpdateClientData + +Data sent to current client only +engine sets cd to 0 before calling. +================= +*/ +void UpdateClientData ( const edict_t *ent, int sendweapons, struct clientdata_s *cd ) +{ + if ( !ent || !ent->pvPrivateData ) + return; + entvars_t * pev = (entvars_t *)&ent->v; + CBasePlayer * pl = dynamic_cast< CBasePlayer *>(CBasePlayer::Instance( pev )); + entvars_t * pevOrg = NULL; + + // if user is spectating different player in First person, override some vars + if ( pl && pl->pev->iuser1 == OBS_IN_EYE ) + { + if ( pl->m_hObserverTarget ) + { + pevOrg = pev; + pev = pl->m_hObserverTarget->pev; + pl = dynamic_cast< CBasePlayer *>(CBasePlayer::Instance( pev ) ); + } + } + + cd->flags = pev->flags; + cd->health = pev->health; + + cd->viewmodel = MODEL_INDEX( STRING( pev->viewmodel ) ); + + cd->waterlevel = pev->waterlevel; + cd->watertype = pev->watertype; + cd->weapons = pev->weapons; + + // Vectors + cd->origin = pev->origin; + cd->velocity = pev->velocity; + cd->view_ofs = pev->view_ofs; + cd->punchangle = pev->punchangle; + + cd->bInDuck = pev->bInDuck; + cd->flTimeStepSound = pev->flTimeStepSound; + cd->flDuckTime = pev->flDuckTime; + cd->flSwimTime = pev->flSwimTime; + cd->waterjumptime = pev->teleport_time; + + strcpy( cd->physinfo, ENGINE_GETPHYSINFO( ent ) ); + + cd->maxspeed = pev->maxspeed; + cd->fov = pev->fov; + cd->weaponanim = pev->weaponanim; + + cd->pushmsec = pev->pushmsec; + + //Spectator mode + if ( pevOrg != NULL ) + { + // don't use spec vars from chased player + cd->iuser1 = pevOrg->iuser1; + cd->iuser2 = pevOrg->iuser2; + } + else + { + cd->iuser1 = pev->iuser1; + cd->iuser2 = pev->iuser2; + } + + + +#if defined( CLIENT_WEAPONS ) + if ( sendweapons ) + { + if ( pl ) + { + cd->m_flNextAttack = pl->m_flNextAttack; + cd->fuser2 = pl->m_flNextAmmoBurn; + cd->fuser3 = pl->m_flAmmoStartCharge; + cd->vuser1.x = pl->ammo_9mm; + cd->vuser1.y = pl->ammo_357; + cd->vuser1.z = pl->ammo_argrens; + cd->ammo_nails = pl->ammo_bolts; + cd->ammo_shells = pl->ammo_buckshot; + cd->ammo_rockets = pl->ammo_rockets; + cd->ammo_cells = pl->ammo_uranium; + cd->vuser2.x = pl->ammo_hornets; + + + if ( pl->m_pActiveItem ) + { + CBasePlayerWeapon *gun; + gun = (CBasePlayerWeapon *)pl->m_pActiveItem->GetWeaponPtr(); + if ( gun && gun->UseDecrement() ) + { + ItemInfo II; + memset( &II, 0, sizeof( II ) ); + gun->GetItemInfo( &II ); + + cd->m_iId = II.iId; + + cd->vuser3.z = gun->m_iSecondaryAmmoType; + cd->vuser4.x = gun->m_iPrimaryAmmoType; + cd->vuser4.y = pl->m_rgAmmo[gun->m_iPrimaryAmmoType]; + cd->vuser4.z = pl->m_rgAmmo[gun->m_iSecondaryAmmoType]; + + if ( pl->m_pActiveItem->m_iId == WEAPON_RPG ) + { + cd->vuser2.y = ( ( CRpg * )pl->m_pActiveItem)->m_fSpotActive; + cd->vuser2.z = ( ( CRpg * )pl->m_pActiveItem)->m_cActiveRockets; + } + } + } + } + } +#endif +} + +/* +================= +CmdStart + +We're about to run this usercmd for the specified player. We can set up groupinfo and masking here, etc. +This is the time to examine the usercmd for anything extra. This call happens even if think does not. +================= +*/ +void CmdStart( const edict_t *player, const struct usercmd_s *cmd, unsigned int random_seed ) +{ + entvars_t *pev = (entvars_t *)&player->v; + CBasePlayer *pl = dynamic_cast< CBasePlayer *>( CBasePlayer::Instance( pev ) ); + + if( !pl ) + return; + + if ( pl->pev->groupinfo != 0 ) + { + UTIL_SetGroupTrace( pl->pev->groupinfo, GROUP_OP_AND ); + } + + pl->random_seed = random_seed; +} + +/* +================= +CmdEnd + +Each cmdstart is exactly matched with a cmd end, clean up any group trace flags, etc. here +================= +*/ +void CmdEnd ( const edict_t *player ) +{ + entvars_t *pev = (entvars_t *)&player->v; + CBasePlayer *pl = dynamic_cast< CBasePlayer *>( CBasePlayer::Instance( pev ) ); + + if( !pl ) + return; + if ( pl->pev->groupinfo != 0 ) + { + UTIL_UnsetGroupTrace(); + } +} + +/* +================================ +ConnectionlessPacket + + Return 1 if the packet is valid. Set response_buffer_size if you want to send a response packet. Incoming, it holds the max + size of the response_buffer, so you must zero it out if you choose not to respond. +================================ +*/ +int ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ) +{ + // Parse stuff from args + int max_buffer_size = *response_buffer_size; + + // Zero it out since we aren't going to respond. + // If we wanted to response, we'd write data into response_buffer + *response_buffer_size = 0; + + // Since we don't listen for anything here, just respond that it's a bogus message + // If we didn't reject the message, we'd return 1 for success instead. + return 0; +} + +/* +================================ +GetHullBounds + + Engine calls this to enumerate player collision hulls, for prediction. Return 0 if the hullnumber doesn't exist. +================================ +*/ +int GetHullBounds( int hullnumber, float *mins, float *maxs ) +{ + int iret = 0; + + switch ( hullnumber ) + { + case 0: // Normal player + mins = VEC_HULL_MIN; + maxs = VEC_HULL_MAX; + iret = 1; + break; + case 1: // Crouched player + mins = VEC_DUCK_HULL_MIN; + maxs = VEC_DUCK_HULL_MAX; + iret = 1; + break; + case 2: // Point based hull + mins = Vector( 0, 0, 0 ); + maxs = Vector( 0, 0, 0 ); + iret = 1; + break; + } + + return iret; +} + +/* +================================ +CreateInstancedBaselines + +Create pseudo-baselines for items that aren't placed in the map at spawn time, but which are likely +to be created during play ( e.g., grenades, ammo packs, projectiles, corpses, etc. ) +================================ +*/ +void CreateInstancedBaselines ( void ) +{ + int iret = 0; + entity_state_t state; + + memset( &state, 0, sizeof( state ) ); + + // Create any additional baselines here for things like grendates, etc. + // iret = ENGINE_INSTANCE_BASELINE( pc->pev->classname, &state ); + + // Destroy objects. + //UTIL_Remove( pc ); +} + +/* +================================ +InconsistentFile + +One of the ENGINE_FORCE_UNMODIFIED files failed the consistency check for the specified player + Return 0 to allow the client to continue, 1 to force immediate disconnection ( with an optional disconnect message of up to 256 characters ) +================================ +*/ +int InconsistentFile( const edict_t *player, const char *filename, char *disconnect_message ) +{ + // Server doesn't care? + if ( CVAR_GET_FLOAT( "mp_consistency" ) != 1 ) + return 0; + + // Default behavior is to kick the player + sprintf( disconnect_message, "Server is enforcing file consistency for %s\n", filename ); + + // Kick now with specified disconnect message. + return 1; +} + +/* +================================ +AllowLagCompensation + + The game .dll should return 1 if lag compensation should be allowed ( could also just set + the sv_unlag cvar. + Most games right now should return 0, until client-side weapon prediction code is written + and tested for them ( note you can predict weapons, but not do lag compensation, too, + if you want. +================================ +*/ +int AllowLagCompensation( void ) +{ + return 1; +} diff --git a/dlls/client.h b/dlls/client.h new file mode 100644 index 0000000..395afac --- /dev/null +++ b/dlls/client.h @@ -0,0 +1,65 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef CLIENT_H +#define CLIENT_H + +extern void respawn( entvars_t* pev, BOOL fCopyCorpse ); +extern BOOL ClientConnect( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); +extern void ClientDisconnect( edict_t *pEntity ); +extern void ClientKill( edict_t *pEntity ); +extern void ClientPutInServer( edict_t *pEntity ); +extern void ClientCommand( edict_t *pEntity ); +extern void ClientUserInfoChanged( edict_t *pEntity, char *infobuffer ); +extern void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ); +extern void ServerDeactivate( void ); +extern void StartFrame( void ); +extern void PlayerPostThink( edict_t *pEntity ); +extern void PlayerPreThink( edict_t *pEntity ); +extern void ParmsNewLevel( void ); +extern void ParmsChangeLevel( void ); + +extern void ClientPrecache( void ); + +extern const char *GetGameDescription( void ); +extern void PlayerCustomization( edict_t *pEntity, customization_t *pCust ); + +extern void SpectatorConnect ( edict_t *pEntity ); +extern void SpectatorDisconnect ( edict_t *pEntity ); +extern void SpectatorThink ( edict_t *pEntity ); + +extern void Sys_Error( const char *error_string ); + +extern void SetupVisibility( edict_t *pViewEntity, edict_t *pClient, unsigned char **pvs, unsigned char **pas ); +extern void UpdateClientData ( const struct edict_s *ent, int sendweapons, struct clientdata_s *cd ); +extern int AddToFullPack( struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet ); +extern void CreateBaseline( int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, vec3_t player_mins, vec3_t player_maxs ); +extern void RegisterEncoders( void ); + +extern int GetWeaponData( struct edict_s *player, struct weapon_data_s *info ); + +extern void CmdStart( const edict_t *player, const struct usercmd_s *cmd, unsigned int random_seed ); +extern void CmdEnd ( const edict_t *player ); + +extern int ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); + +extern int GetHullBounds( int hullnumber, float *mins, float *maxs ); + +extern void CreateInstancedBaselines ( void ); + +extern int InconsistentFile( const edict_t *player, const char *filename, char *disconnect_message ); + +extern int AllowLagCompensation( void ); + +#endif // CLIENT_H diff --git a/dlls/combat.cpp b/dlls/combat.cpp new file mode 100644 index 0000000..5acd32b --- /dev/null +++ b/dlls/combat.cpp @@ -0,0 +1,1706 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== combat.cpp ======================================================== + + functions dealing with damage infliction & death + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "soundent.h" +#include "decals.h" +#include "animation.h" +#include "weapons.h" +#include "func_break.h" + +extern DLL_GLOBAL Vector g_vecAttackDir; +extern DLL_GLOBAL int g_iSkillLevel; + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); +extern entvars_t *g_pevLastInflictor; + +#define GERMAN_GIB_COUNT 4 +#define HUMAN_GIB_COUNT 6 +#define ALIEN_GIB_COUNT 4 + + +// HACKHACK -- The gib velocity equations don't work +void CGib :: LimitVelocity( void ) +{ + float length = pev->velocity.Length(); + + // ceiling at 1500. The gib velocity equation is not bounded properly. Rather than tune it + // in 3 separate places again, I'll just limit it here. + if ( length > 1500.0 ) + pev->velocity = pev->velocity.Normalize() * 1500; // This should really be sv_maxvelocity * 0.75 or something +} + + +void CGib :: SpawnStickyGibs( entvars_t *pevVictim, Vector vecOrigin, int cGibs ) +{ + int i; + + if ( g_Language == LANGUAGE_GERMAN ) + { + // no sticky gibs in germany right now! + return; + } + + for ( i = 0 ; i < cGibs ; i++ ) + { + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + pGib->Spawn( "models/stickygib.mdl" ); + pGib->pev->body = RANDOM_LONG(0,2); + + if ( pevVictim ) + { + pGib->pev->origin.x = vecOrigin.x + RANDOM_FLOAT( -3, 3 ); + pGib->pev->origin.y = vecOrigin.y + RANDOM_FLOAT( -3, 3 ); + pGib->pev->origin.z = vecOrigin.z + RANDOM_FLOAT( -3, 3 ); + + /* + pGib->pev->origin.x = pevVictim->absmin.x + pevVictim->size.x * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.y = pevVictim->absmin.y + pevVictim->size.y * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.z = pevVictim->absmin.z + pevVictim->size.z * (RANDOM_FLOAT ( 0 , 1 ) ); + */ + + // make the gib fly away from the attack vector + pGib->pev->velocity = g_vecAttackDir * -1; + + // mix in some noise + pGib->pev->velocity.x += RANDOM_FLOAT ( -0.15, 0.15 ); + pGib->pev->velocity.y += RANDOM_FLOAT ( -0.15, 0.15 ); + pGib->pev->velocity.z += RANDOM_FLOAT ( -0.15, 0.15 ); + + pGib->pev->velocity = pGib->pev->velocity * 900; + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 250, 400 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 250, 400 ); + + // copy owner's blood color + pGib->m_bloodColor = (CBaseEntity::Instance(pevVictim))->BloodColor(); + + if ( pevVictim->health > -50) + { + pGib->pev->velocity = pGib->pev->velocity * 0.7; + } + else if ( pevVictim->health > -200) + { + pGib->pev->velocity = pGib->pev->velocity * 2; + } + else + { + pGib->pev->velocity = pGib->pev->velocity * 4; + } + + + pGib->pev->movetype = MOVETYPE_TOSS; + pGib->pev->solid = SOLID_BBOX; + UTIL_SetSize ( pGib->pev, Vector ( 0, 0 ,0 ), Vector ( 0, 0, 0 ) ); + pGib->SetTouch ( &CGib::StickyGibTouch ); + pGib->SetThink (NULL); + } + pGib->LimitVelocity(); + } +} + +void CGib :: SpawnHeadGib( entvars_t *pevVictim ) +{ + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + if ( g_Language == LANGUAGE_GERMAN ) + { + pGib->Spawn( "models/germangibs.mdl" );// throw one head + pGib->pev->body = 0; + } + else + { + pGib->Spawn( "models/hgibs.mdl" );// throw one head + pGib->pev->body = 0; + } + + if ( pevVictim ) + { + pGib->pev->origin = pevVictim->origin + pevVictim->view_ofs; + + edict_t *pentPlayer = FIND_CLIENT_IN_PVS( pGib->edict() ); + + if ( RANDOM_LONG ( 0, 100 ) <= 5 && pentPlayer ) + { + // 5% chance head will be thrown at player's face. + entvars_t *pevPlayer; + + pevPlayer = VARS( pentPlayer ); + pGib->pev->velocity = ( ( pevPlayer->origin + pevPlayer->view_ofs ) - pGib->pev->origin ).Normalize() * 300; + pGib->pev->velocity.z += 100; + } + else + { + pGib->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + } + + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + // copy owner's blood color + pGib->m_bloodColor = (CBaseEntity::Instance(pevVictim))->BloodColor(); + + if ( pevVictim->health > -50) + { + pGib->pev->velocity = pGib->pev->velocity * 0.7; + } + else if ( pevVictim->health > -200) + { + pGib->pev->velocity = pGib->pev->velocity * 2; + } + else + { + pGib->pev->velocity = pGib->pev->velocity * 4; + } + } + pGib->LimitVelocity(); +} + +void CGib :: SpawnRandomGibs( entvars_t *pevVictim, int cGibs, int human ) +{ + int cSplat; + + for ( cSplat = 0 ; cSplat < cGibs ; cSplat++ ) + { + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + if ( g_Language == LANGUAGE_GERMAN ) + { + pGib->Spawn( "models/germangibs.mdl" ); + pGib->pev->body = RANDOM_LONG(0,GERMAN_GIB_COUNT-1); + } + else + { + if ( human ) + { + // human pieces + pGib->Spawn( "models/hgibs.mdl" ); + pGib->pev->body = RANDOM_LONG(1,HUMAN_GIB_COUNT-1);// start at one to avoid throwing random amounts of skulls (0th gib) + } + else + { + // aliens + pGib->Spawn( "models/agibs.mdl" ); + pGib->pev->body = RANDOM_LONG(0,ALIEN_GIB_COUNT-1); + } + } + + if ( pevVictim ) + { + // spawn the gib somewhere in the monster's bounding volume + pGib->pev->origin.x = pevVictim->absmin.x + pevVictim->size.x * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.y = pevVictim->absmin.y + pevVictim->size.y * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.z = pevVictim->absmin.z + pevVictim->size.z * (RANDOM_FLOAT ( 0 , 1 ) ) + 1; // absmin.z is in the floor because the engine subtracts 1 to enlarge the box + + // make the gib fly away from the attack vector + pGib->pev->velocity = g_vecAttackDir * -1; + + // mix in some noise + pGib->pev->velocity.x += RANDOM_FLOAT ( -0.25, 0.25 ); + pGib->pev->velocity.y += RANDOM_FLOAT ( -0.25, 0.25 ); + pGib->pev->velocity.z += RANDOM_FLOAT ( -0.25, 0.25 ); + + pGib->pev->velocity = pGib->pev->velocity * RANDOM_FLOAT ( 300, 400 ); + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + // copy owner's blood color + pGib->m_bloodColor = (CBaseEntity::Instance(pevVictim))->BloodColor(); + + if ( pevVictim->health > -50) + { + pGib->pev->velocity = pGib->pev->velocity * 0.7; + } + else if ( pevVictim->health > -200) + { + pGib->pev->velocity = pGib->pev->velocity * 2; + } + else + { + pGib->pev->velocity = pGib->pev->velocity * 4; + } + + pGib->pev->solid = SOLID_BBOX; + UTIL_SetSize ( pGib->pev, Vector( 0 , 0 , 0 ), Vector ( 0, 0, 0 ) ); + } + pGib->LimitVelocity(); + } +} + + +BOOL CBaseMonster :: HasHumanGibs( void ) +{ + int myClass = Classify(); + + if ( myClass == CLASS_HUMAN_MILITARY || + myClass == CLASS_PLAYER_ALLY || + myClass == CLASS_HUMAN_PASSIVE || + myClass == CLASS_PLAYER ) + + return TRUE; + + return FALSE; +} + + +BOOL CBaseMonster :: HasAlienGibs( void ) +{ + int myClass = Classify(); + + if ( myClass == CLASS_ALIEN_MILITARY || + myClass == CLASS_ALIEN_MONSTER || + myClass == CLASS_ALIEN_PASSIVE || + myClass == CLASS_INSECT || + myClass == CLASS_ALIEN_PREDATOR || + myClass == CLASS_ALIEN_PREY ) + + return TRUE; + + return FALSE; +} + + +void CBaseMonster::FadeMonster( void ) +{ + StopAnimation(); + pev->velocity = g_vecZero; + pev->movetype = MOVETYPE_NONE; + pev->avelocity = g_vecZero; + pev->animtime = gpGlobals->time; + pev->effects |= EF_NOINTERP; + SUB_StartFadeOut(); +} + +//========================================================= +// GibMonster - create some gore and get rid of a monster's +// model. +//========================================================= +void CBaseMonster :: GibMonster( void ) +{ + TraceResult tr; + BOOL gibbed = FALSE; + + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "common/bodysplat.wav", 1, ATTN_NORM); + + // only humans throw skulls !!!UNDONE - eventually monsters will have their own sets of gibs + if ( HasHumanGibs() ) + { + if ( CVAR_GET_FLOAT("violence_hgibs") != 0 ) // Only the player will ever get here + { + CGib::SpawnHeadGib( pev ); + CGib::SpawnRandomGibs( pev, 4, 1 ); // throw some human gibs. + } + gibbed = TRUE; + } + else if ( HasAlienGibs() ) + { + if ( CVAR_GET_FLOAT("violence_agibs") != 0 ) // Should never get here, but someone might call it directly + { + CGib::SpawnRandomGibs( pev, 4, 0 ); // Throw alien gibs + } + gibbed = TRUE; + } + + if ( !IsPlayer() ) + { + if ( gibbed ) + { + // don't remove players! + SetThink ( &CBaseMonster::SUB_Remove ); + pev->nextthink = gpGlobals->time; + } + else + { + FadeMonster(); + } + } +} + +//========================================================= +// GetDeathActivity - determines the best type of death +// anim to play. +//========================================================= +Activity CBaseMonster :: GetDeathActivity ( void ) +{ + Activity deathActivity; + BOOL fTriedDirection; + float flDot; + TraceResult tr; + Vector vecSrc; + + if ( pev->deadflag != DEAD_NO ) + { + // don't run this while dying. + return m_IdealActivity; + } + + vecSrc = Center(); + + fTriedDirection = FALSE; + deathActivity = ACT_DIESIMPLE;// in case we can't find any special deaths to do. + + UTIL_MakeVectors ( pev->angles ); + flDot = DotProduct ( gpGlobals->v_forward, g_vecAttackDir * -1 ); + + switch ( m_LastHitGroup ) + { + // try to pick a region-specific death. + case HITGROUP_HEAD: + deathActivity = ACT_DIE_HEADSHOT; + break; + + case HITGROUP_STOMACH: + deathActivity = ACT_DIE_GUTSHOT; + break; + + case HITGROUP_GENERIC: + // try to pick a death based on attack direction + fTriedDirection = TRUE; + + if ( flDot > 0.3 ) + { + deathActivity = ACT_DIEFORWARD; + } + else if ( flDot <= -0.3 ) + { + deathActivity = ACT_DIEBACKWARD; + } + break; + + default: + // try to pick a death based on attack direction + fTriedDirection = TRUE; + + if ( flDot > 0.3 ) + { + deathActivity = ACT_DIEFORWARD; + } + else if ( flDot <= -0.3 ) + { + deathActivity = ACT_DIEBACKWARD; + } + break; + } + + + // can we perform the prescribed death? + if ( LookupActivity ( deathActivity ) == ACTIVITY_NOT_AVAILABLE ) + { + // no! did we fail to perform a directional death? + if ( fTriedDirection ) + { + // if yes, we're out of options. Go simple. + deathActivity = ACT_DIESIMPLE; + } + else + { + // cannot perform the ideal region-specific death, so try a direction. + if ( flDot > 0.3 ) + { + deathActivity = ACT_DIEFORWARD; + } + else if ( flDot <= -0.3 ) + { + deathActivity = ACT_DIEBACKWARD; + } + } + } + + if ( LookupActivity ( deathActivity ) == ACTIVITY_NOT_AVAILABLE ) + { + // if we're still invalid, simple is our only option. + deathActivity = ACT_DIESIMPLE; + } + + if ( deathActivity == ACT_DIEFORWARD ) + { + // make sure there's room to fall forward + UTIL_TraceHull ( vecSrc, vecSrc + gpGlobals->v_forward * 64, dont_ignore_monsters, head_hull, edict(), &tr ); + + if ( tr.flFraction != 1.0 ) + { + deathActivity = ACT_DIESIMPLE; + } + } + + if ( deathActivity == ACT_DIEBACKWARD ) + { + // make sure there's room to fall backward + UTIL_TraceHull ( vecSrc, vecSrc - gpGlobals->v_forward * 64, dont_ignore_monsters, head_hull, edict(), &tr ); + + if ( tr.flFraction != 1.0 ) + { + deathActivity = ACT_DIESIMPLE; + } + } + + return deathActivity; +} + +//========================================================= +// GetSmallFlinchActivity - determines the best type of flinch +// anim to play. +//========================================================= +Activity CBaseMonster :: GetSmallFlinchActivity ( void ) +{ + Activity flinchActivity; + BOOL fTriedDirection; + float flDot; + + fTriedDirection = FALSE; + UTIL_MakeVectors ( pev->angles ); + flDot = DotProduct ( gpGlobals->v_forward, g_vecAttackDir * -1 ); + + switch ( m_LastHitGroup ) + { + // pick a region-specific flinch + case HITGROUP_HEAD: + flinchActivity = ACT_FLINCH_HEAD; + break; + case HITGROUP_STOMACH: + flinchActivity = ACT_FLINCH_STOMACH; + break; + case HITGROUP_LEFTARM: + flinchActivity = ACT_FLINCH_LEFTARM; + break; + case HITGROUP_RIGHTARM: + flinchActivity = ACT_FLINCH_RIGHTARM; + break; + case HITGROUP_LEFTLEG: + flinchActivity = ACT_FLINCH_LEFTLEG; + break; + case HITGROUP_RIGHTLEG: + flinchActivity = ACT_FLINCH_RIGHTLEG; + break; + case HITGROUP_GENERIC: + default: + // just get a generic flinch. + flinchActivity = ACT_SMALL_FLINCH; + break; + } + + + // do we have a sequence for the ideal activity? + if ( LookupActivity ( flinchActivity ) == ACTIVITY_NOT_AVAILABLE ) + { + flinchActivity = ACT_SMALL_FLINCH; + } + + return flinchActivity; +} + + +void CBaseMonster::BecomeDead( void ) +{ + pev->takedamage = DAMAGE_YES;// don't let autoaim aim at corpses. + + // give the corpse half of the monster's original maximum health. + pev->health = pev->max_health / 2; + pev->max_health = 5; // max_health now becomes a counter for how many blood decals the corpse can place. + + // make the corpse fly away from the attack vector + pev->movetype = MOVETYPE_TOSS; + //pev->flags &= ~FL_ONGROUND; + //pev->origin.z += 2; + //pev->velocity = g_vecAttackDir * -1; + //pev->velocity = pev->velocity * RANDOM_FLOAT( 300, 400 ); +} + + +BOOL CBaseMonster::ShouldGibMonster( int iGib ) +{ + if ( ( iGib == GIB_NORMAL && pev->health < GIB_HEALTH_VALUE ) || ( iGib == GIB_ALWAYS ) ) + return TRUE; + + return FALSE; +} + + +void CBaseMonster::CallGibMonster( void ) +{ + BOOL fade = FALSE; + + if ( HasHumanGibs() ) + { + if ( CVAR_GET_FLOAT("violence_hgibs") == 0 ) + fade = TRUE; + } + else if ( HasAlienGibs() ) + { + if ( CVAR_GET_FLOAT("violence_agibs") == 0 ) + fade = TRUE; + } + + pev->takedamage = DAMAGE_NO; + pev->solid = SOLID_NOT;// do something with the body. while monster blows up + + if ( fade ) + { + FadeMonster(); + } + else + { + pev->effects = EF_NODRAW; // make the model invisible. + GibMonster(); + } + + pev->deadflag = DEAD_DEAD; + FCheckAITrigger(); + + // don't let the status bar glitch for players.with <0 health. + if (pev->health < -99) + { + pev->health = 0; + } + + if ( ShouldFadeOnDeath() && !fade ) + UTIL_Remove(this); +} + + +/* +============ +Killed +============ +*/ +void CBaseMonster :: Killed( entvars_t *pevAttacker, int iGib ) +{ + unsigned int cCount = 0; + BOOL fDone = FALSE; + + if ( HasMemory( bits_MEMORY_KILLED ) ) + { + if ( ShouldGibMonster( iGib ) ) + CallGibMonster(); + return; + } + + Remember( bits_MEMORY_KILLED ); + + // clear the deceased's sound channels.(may have been firing or reloading when killed) + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "common/null.wav", 1, ATTN_NORM); + m_IdealMonsterState = MONSTERSTATE_DEAD; + // Make sure this condition is fired too (TakeDamage breaks out before this happens on death) + SetConditions( bits_COND_LIGHT_DAMAGE ); + + // tell owner ( if any ) that we're dead.This is mostly for MonsterMaker functionality. + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + if ( pOwner ) + { + pOwner->DeathNotice( pev ); + } + + if ( ShouldGibMonster( iGib ) ) + { + CallGibMonster(); + return; + } + else if ( pev->flags & FL_MONSTER ) + { + SetTouch( NULL ); + BecomeDead(); + } + + // don't let the status bar glitch for players.with <0 health. + if (pev->health < -99) + { + pev->health = 0; + } + + //pev->enemy = ENT( pevAttacker );//why? (sjb) + + m_IdealMonsterState = MONSTERSTATE_DEAD; +} + +// +// fade out - slowly fades a entity out, then removes it. +// +// DON'T USE ME FOR GIBS AND STUFF IN MULTIPLAYER! +// SET A FUTURE THINK AND A RENDERMODE!! +void CBaseEntity :: SUB_StartFadeOut ( void ) +{ + if (pev->rendermode == kRenderNormal) + { + pev->renderamt = 255; + pev->rendermode = kRenderTransTexture; + } + + pev->solid = SOLID_NOT; + pev->avelocity = g_vecZero; + + pev->nextthink = gpGlobals->time + 0.1; + SetThink ( &CBaseEntity::SUB_FadeOut ); +} + +void CBaseEntity :: SUB_FadeOut ( void ) +{ + if ( pev->renderamt > 7 ) + { + pev->renderamt -= 7; + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + pev->renderamt = 0; + pev->nextthink = gpGlobals->time + 0.2; + SetThink ( &CBaseEntity::SUB_Remove ); + } +} + +//========================================================= +// WaitTillLand - in order to emit their meaty scent from +// the proper location, gibs should wait until they stop +// bouncing to emit their scent. That's what this function +// does. +//========================================================= +void CGib :: WaitTillLand ( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + if ( pev->velocity == g_vecZero ) + { + SetThink (&CGib::SUB_StartFadeOut); + pev->nextthink = gpGlobals->time + m_lifeTime; + + // If you bleed, you stink! + if ( m_bloodColor != DONT_BLEED ) + { + // ok, start stinkin! + CSoundEnt::InsertSound ( bits_SOUND_MEAT, pev->origin, 384, 25 ); + } + } + else + { + // wait and check again in another half second. + pev->nextthink = gpGlobals->time + 0.5; + } +} + +// +// Gib bounces on the ground or wall, sponges some blood down, too! +// +void CGib :: BounceGibTouch ( CBaseEntity *pOther ) +{ + Vector vecSpot; + TraceResult tr; + + //if ( RANDOM_LONG(0,1) ) + // return;// don't bleed everytime + + if (pev->flags & FL_ONGROUND) + { + pev->velocity = pev->velocity * 0.9; + pev->angles.x = 0; + pev->angles.z = 0; + pev->avelocity.x = 0; + pev->avelocity.z = 0; + } + else + { + if ( g_Language != LANGUAGE_GERMAN && m_cBloodDecals > 0 && m_bloodColor != DONT_BLEED ) + { + vecSpot = pev->origin + Vector ( 0 , 0 , 8 );//move up a bit, and trace down. + UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -24 ), ignore_monsters, ENT(pev), & tr); + + UTIL_BloodDecalTrace( &tr, m_bloodColor ); + + m_cBloodDecals--; + } + + if ( m_material != matNone && RANDOM_LONG(0,2) == 0 ) + { + float volume; + float zvel = fabs(pev->velocity.z); + + volume = 0.8 * min(1.0, ((float)zvel) / 450.0); + + CBreakable::MaterialSoundRandom( edict(), (Materials)m_material, volume ); + } + } +} + +// +// Sticky gib puts blood on the wall and stays put. +// +void CGib :: StickyGibTouch ( CBaseEntity *pOther ) +{ + Vector vecSpot; + TraceResult tr; + + SetThink ( &CGib::SUB_Remove ); + pev->nextthink = gpGlobals->time + 10; + + if ( !FClassnameIs( pOther->pev, "worldspawn" ) ) + { + pev->nextthink = gpGlobals->time; + return; + } + + UTIL_TraceLine ( pev->origin, pev->origin + pev->velocity * 32, ignore_monsters, ENT(pev), & tr); + + UTIL_BloodDecalTrace( &tr, m_bloodColor ); + + pev->velocity = tr.vecPlaneNormal * -1; + pev->angles = UTIL_VecToAngles ( pev->velocity ); + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + pev->movetype = MOVETYPE_NONE; +} + +// +// Throw a chunk +// +void CGib :: Spawn( const char *szGibModel ) +{ + pev->movetype = MOVETYPE_BOUNCE; + pev->friction = 0.55; // deading the bounce a bit + + // sometimes an entity inherits the edict from a former piece of glass, + // and will spawn using the same render FX or rendermode! bad! + pev->renderamt = 255; + pev->rendermode = kRenderNormal; + pev->renderfx = kRenderFxNone; + pev->solid = SOLID_SLIDEBOX;/// hopefully this will fix the VELOCITY TOO LOW crap + pev->classname = MAKE_STRING("gib"); + + SET_MODEL(ENT(pev), szGibModel); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + + pev->nextthink = gpGlobals->time + 4; + m_lifeTime = 25; + SetThink ( &CGib::WaitTillLand ); + SetTouch ( &CGib::BounceGibTouch ); + + m_material = matNone; + m_cBloodDecals = 5;// how many blood decals this gib can place (1 per bounce until none remain). +} + +// take health +int CBaseMonster :: TakeHealth (float flHealth, int bitsDamageType) +{ + if (!pev->takedamage) + return 0; + + // clear out any damage types we healed. + // UNDONE: generic health should not heal any + // UNDONE: time-based damage + + m_bitsDamageType &= ~(bitsDamageType & ~DMG_TIMEBASED); + + return CBaseEntity::TakeHealth(flHealth, bitsDamageType); +} + +/* +============ +TakeDamage + +The damage is coming from inflictor, but get mad at attacker +This should be the only function that ever reduces health. +bitsDamageType indicates the type of damage sustained, ie: DMG_SHOCK + +Time-based damage: only occurs while the monster is within the trigger_hurt. +When a monster is poisoned via an arrow etc it takes all the poison damage at once. + + + +GLOBALS ASSUMED SET: g_iSkillLevel +============ +*/ +int CBaseMonster :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + float flTake; + Vector vecDir; + + if (!pev->takedamage) + return 0; + + if ( !IsAlive() ) + { + return DeadTakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); + } + + if ( pev->deadflag == DEAD_NO ) + { + // no pain sound during death animation. + PainSound();// "Ouch!" + } + + //!!!LATER - make armor consideration here! + flTake = flDamage; + + // set damage type sustained + m_bitsDamageType |= bitsDamageType; + + // grab the vector of the incoming attack. ( pretend that the inflictor is a little lower than it really is, so the body will tend to fly upward a bit). + vecDir = Vector( 0, 0, 0 ); + if (!FNullEnt( pevInflictor )) + { + CBaseEntity *pInflictor = CBaseEntity :: Instance( pevInflictor ); + if (pInflictor) + { + vecDir = ( pInflictor->Center() - Vector ( 0, 0, 10 ) - Center() ).Normalize(); + vecDir = g_vecAttackDir = vecDir.Normalize(); + } + } + + // add to the damage total for clients, which will be sent as a single + // message at the end of the frame + // todo: remove after combining shotgun blasts? + if ( IsPlayer() ) + { + if ( pevInflictor ) + pev->dmg_inflictor = ENT(pevInflictor); + + pev->dmg_take += flTake; + + // check for godmode or invincibility + if ( pev->flags & FL_GODMODE ) + { + return 0; + } + } + + // if this is a player, move him around! + if ( ( !FNullEnt( pevInflictor ) ) && (pev->movetype == MOVETYPE_WALK) && (!pevAttacker || pevAttacker->solid != SOLID_TRIGGER) ) + { + pev->velocity = pev->velocity + vecDir * -DamageForce( flDamage ); + } + + // do the damage + pev->health -= flTake; + + + // HACKHACK Don't kill monsters in a script. Let them break their scripts first + if ( m_MonsterState == MONSTERSTATE_SCRIPT ) + { + SetConditions( bits_COND_LIGHT_DAMAGE ); + return 0; + } + + if ( pev->health <= 0 ) + { + g_pevLastInflictor = pevInflictor; + + if ( bitsDamageType & DMG_ALWAYSGIB ) + { + Killed( pevAttacker, GIB_ALWAYS ); + } + else if ( bitsDamageType & DMG_NEVERGIB ) + { + Killed( pevAttacker, GIB_NEVER ); + } + else + { + Killed( pevAttacker, GIB_NORMAL ); + } + + g_pevLastInflictor = NULL; + + return 0; + } + + // react to the damage (get mad) + if ( (pev->flags & FL_MONSTER) && !FNullEnt(pevAttacker) ) + { + if ( pevAttacker->flags & (FL_MONSTER | FL_CLIENT) ) + {// only if the attack was a monster or client! + + // enemy's last known position is somewhere down the vector that the attack came from. + if (pevInflictor) + { + if (m_hEnemy == NULL || pevInflictor == m_hEnemy->pev || !HasConditions(bits_COND_SEE_ENEMY)) + { + m_vecEnemyLKP = pevInflictor->origin; + } + } + else + { + m_vecEnemyLKP = pev->origin + ( g_vecAttackDir * 64 ); + } + + MakeIdealYaw( m_vecEnemyLKP ); + + // add pain to the conditions + // !!!HACKHACK - fudged for now. Do we want to have a virtual function to determine what is light and + // heavy damage per monster class? + if ( flDamage > 0 ) + { + SetConditions(bits_COND_LIGHT_DAMAGE); + } + + if ( flDamage >= 20 ) + { + SetConditions(bits_COND_HEAVY_DAMAGE); + } + } + } + + return 1; +} + +//========================================================= +// DeadTakeDamage - takedamage function called when a monster's +// corpse is damaged. +//========================================================= +int CBaseMonster :: DeadTakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecDir; + + // grab the vector of the incoming attack. ( pretend that the inflictor is a little lower than it really is, so the body will tend to fly upward a bit). + vecDir = Vector( 0, 0, 0 ); + if (!FNullEnt( pevInflictor )) + { + CBaseEntity *pInflictor = CBaseEntity :: Instance( pevInflictor ); + if (pInflictor) + { + vecDir = ( pInflictor->Center() - Vector ( 0, 0, 10 ) - Center() ).Normalize(); + vecDir = g_vecAttackDir = vecDir.Normalize(); + } + } + +#if 0// turn this back on when the bounding box issues are resolved. + + pev->flags &= ~FL_ONGROUND; + pev->origin.z += 1; + + // let the damage scoot the corpse around a bit. + if ( !FNullEnt(pevInflictor) && (pevAttacker->solid != SOLID_TRIGGER) ) + { + pev->velocity = pev->velocity + vecDir * -DamageForce( flDamage ); + } + +#endif + + // kill the corpse if enough damage was done to destroy the corpse and the damage is of a type that is allowed to destroy the corpse. + if ( bitsDamageType & DMG_GIB_CORPSE ) + { + if ( pev->health <= flDamage ) + { + pev->health = -50; + Killed( pevAttacker, GIB_ALWAYS ); + return 0; + } + // Accumulate corpse gibbing damage, so you can gib with multiple hits + pev->health -= flDamage * 0.1; + } + + return 1; +} + + +float CBaseMonster :: DamageForce( float damage ) +{ + float force = damage * ((32 * 32 * 72.0) / (pev->size.x * pev->size.y * pev->size.z)) * 5; + + if ( force > 1000.0) + { + force = 1000.0; + } + + return force; +} + +// +// RadiusDamage - this entity is exploding, or otherwise needs to inflict damage upon entities within a certain range. +// +// only damage ents that can clearly be seen by the explosion! + + +void RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, float flRadius, int iClassIgnore, int bitsDamageType ) +{ + CBaseEntity *pEntity = NULL; + TraceResult tr; + float flAdjustedDamage, falloff; + Vector vecSpot; + + if ( flRadius ) + falloff = flDamage / flRadius; + else + falloff = 1.0; + + int bInWater = (UTIL_PointContents ( vecSrc ) == CONTENTS_WATER); + + vecSrc.z += 1;// in case grenade is lying on the ground + + if ( !pevAttacker ) + pevAttacker = pevInflictor; + + // iterate on all entities in the vicinity. + while ((pEntity = UTIL_FindEntityInSphere( pEntity, vecSrc, flRadius )) != NULL) + { + if ( pEntity->pev->takedamage != DAMAGE_NO ) + { + // UNDONE: this should check a damage mask, not an ignore + if ( iClassIgnore != CLASS_NONE && pEntity->Classify() == iClassIgnore ) + {// houndeyes don't hurt other houndeyes with their attack + continue; + } + + // blast's don't tavel into or out of water + if (bInWater && pEntity->pev->waterlevel == 0) + continue; + if (!bInWater && pEntity->pev->waterlevel == 3) + continue; + + vecSpot = pEntity->BodyTarget( vecSrc ); + + UTIL_TraceLine ( vecSrc, vecSpot, dont_ignore_monsters, ENT(pevInflictor), &tr ); + + if ( tr.flFraction == 1.0 || tr.pHit == pEntity->edict() ) + {// the explosion can 'see' this entity, so hurt them! + if (tr.fStartSolid) + { + // if we're stuck inside them, fixup the position and distance + tr.vecEndPos = vecSrc; + tr.flFraction = 0.0; + } + + // decrease damage for an ent that's farther from the bomb. + flAdjustedDamage = ( vecSrc - tr.vecEndPos ).Length() * falloff; + flAdjustedDamage = flDamage - flAdjustedDamage; + + if ( flAdjustedDamage < 0 ) + { + flAdjustedDamage = 0; + } + + // ALERT( at_console, "hit %s\n", STRING( pEntity->pev->classname ) ); + if (tr.flFraction != 1.0) + { + ClearMultiDamage( ); + pEntity->TraceAttack( pevInflictor, flAdjustedDamage, (tr.vecEndPos - vecSrc).Normalize( ), &tr, bitsDamageType ); + ApplyMultiDamage( pevInflictor, pevAttacker ); + } + else + { + pEntity->TakeDamage ( pevInflictor, pevAttacker, flAdjustedDamage, bitsDamageType ); + } + } + } + } +} + + +void CBaseMonster :: RadiusDamage(entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) +{ + ::RadiusDamage( pev->origin, pevInflictor, pevAttacker, flDamage, flDamage * 2.5, iClassIgnore, bitsDamageType ); +} + + +void CBaseMonster :: RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) +{ + ::RadiusDamage( vecSrc, pevInflictor, pevAttacker, flDamage, flDamage * 2.5, iClassIgnore, bitsDamageType ); +} + + +//========================================================= +// CheckTraceHullAttack - expects a length to trace, amount +// of damage to do, and damage type. Returns a pointer to +// the damaged entity in case the monster wishes to do +// other stuff to the victim (punchangle, etc) +// +// Used for many contact-range melee attacks. Bites, claws, etc. +//========================================================= +CBaseEntity* CBaseMonster :: CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ) +{ + TraceResult tr; + + if (IsPlayer()) + UTIL_MakeVectors( pev->angles ); + else + UTIL_MakeAimVectors( pev->angles ); + + Vector vecStart = pev->origin; + vecStart.z += pev->size.z * 0.5; + Vector vecEnd = vecStart + (gpGlobals->v_forward * flDist ); + + UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr ); + + if ( tr.pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + + if ( iDamage > 0 ) + { + pEntity->TakeDamage( pev, pev, iDamage, iDmgType ); + } + + return pEntity; + } + + return NULL; +} + + +//========================================================= +// FInViewCone - returns true is the passed ent is in +// the caller's forward view cone. The dot product is performed +// in 2d, making the view cone infinitely tall. +//========================================================= +BOOL CBaseMonster :: FInViewCone ( CBaseEntity *pEntity ) +{ + Vector2D vec2LOS; + float flDot; + + UTIL_MakeVectors ( pev->angles ); + + vec2LOS = ( pEntity->pev->origin - pev->origin ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() ); + + if ( flDot > m_flFieldOfView ) + { + return TRUE; + } + else + { + return FALSE; + } +} + +//========================================================= +// FInViewCone - returns true is the passed vector is in +// the caller's forward view cone. The dot product is performed +// in 2d, making the view cone infinitely tall. +//========================================================= +BOOL CBaseMonster :: FInViewCone ( Vector *pOrigin ) +{ + Vector2D vec2LOS; + float flDot; + + UTIL_MakeVectors ( pev->angles ); + + vec2LOS = ( *pOrigin - pev->origin ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() ); + + if ( flDot > m_flFieldOfView ) + { + return TRUE; + } + else + { + return FALSE; + } +} + +//========================================================= +// FVisible - returns true if a line can be traced from +// the caller's eyes to the target +//========================================================= +BOOL CBaseEntity :: FVisible ( CBaseEntity *pEntity ) +{ + TraceResult tr; + Vector vecLookerOrigin; + Vector vecTargetOrigin; + + if (FBitSet( pEntity->pev->flags, FL_NOTARGET )) + return FALSE; + + // don't look through water + if ((pev->waterlevel != 3 && pEntity->pev->waterlevel == 3) + || (pev->waterlevel == 3 && pEntity->pev->waterlevel == 0)) + return FALSE; + + vecLookerOrigin = pev->origin + pev->view_ofs;//look through the caller's 'eyes' + vecTargetOrigin = pEntity->EyePosition(); + + UTIL_TraceLine(vecLookerOrigin, vecTargetOrigin, ignore_monsters, ignore_glass, ENT(pev)/*pentIgnore*/, &tr); + + if (tr.flFraction != 1.0) + { + return FALSE;// Line of sight is not established + } + else + { + return TRUE;// line of sight is valid. + } +} + +//========================================================= +// FVisible - returns true if a line can be traced from +// the caller's eyes to the target vector +//========================================================= +BOOL CBaseEntity :: FVisible ( const Vector &vecOrigin ) +{ + TraceResult tr; + Vector vecLookerOrigin; + + vecLookerOrigin = EyePosition();//look through the caller's 'eyes' + + UTIL_TraceLine(vecLookerOrigin, vecOrigin, ignore_monsters, ignore_glass, ENT(pev)/*pentIgnore*/, &tr); + + if (tr.flFraction != 1.0) + { + return FALSE;// Line of sight is not established + } + else + { + return TRUE;// line of sight is valid. + } +} + +/* +================ +TraceAttack +================ +*/ +void CBaseEntity::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + Vector vecOrigin = ptr->vecEndPos - vecDir * 4; + + if ( pev->takedamage ) + { + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + + int blood = BloodColor(); + + if ( blood != DONT_BLEED ) + { + SpawnBlood(vecOrigin, blood, flDamage);// a little surface blood. + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + } + } +} + + +/* +//========================================================= +// TraceAttack +//========================================================= +void CBaseMonster::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + Vector vecOrigin = ptr->vecEndPos - vecDir * 4; + + ALERT ( at_console, "%d\n", ptr->iHitgroup ); + + + if ( pev->takedamage ) + { + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + + int blood = BloodColor(); + + if ( blood != DONT_BLEED ) + { + SpawnBlood(vecOrigin, blood, flDamage);// a little surface blood. + } + } +} +*/ + +//========================================================= +// TraceAttack +//========================================================= +void CBaseMonster :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( pev->takedamage ) + { + m_LastHitGroup = ptr->iHitgroup; + + switch ( ptr->iHitgroup ) + { + case HITGROUP_GENERIC: + break; + case HITGROUP_HEAD: + flDamage *= gSkillData.monHead; + break; + case HITGROUP_CHEST: + flDamage *= gSkillData.monChest; + break; + case HITGROUP_STOMACH: + flDamage *= gSkillData.monStomach; + break; + case HITGROUP_LEFTARM: + case HITGROUP_RIGHTARM: + flDamage *= gSkillData.monArm; + break; + case HITGROUP_LEFTLEG: + case HITGROUP_RIGHTLEG: + flDamage *= gSkillData.monLeg; + break; + default: + break; + } + + SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage);// a little surface blood. + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + } +} + +/* +================ +FireBullets + +Go to the trouble of combining multiple pellets into a single damage call. + +This version is used by Monsters. +================ +*/ +void CBaseEntity::FireBullets(ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq, int iDamage, entvars_t *pevAttacker ) +{ + static int tracerCount; + int tracer; + TraceResult tr; + Vector vecRight = gpGlobals->v_right; + Vector vecUp = gpGlobals->v_up; + + if ( pevAttacker == NULL ) + pevAttacker = pev; // the default attacker is ourselves + + ClearMultiDamage(); + gMultiDamage.type = DMG_BULLET | DMG_NEVERGIB; + + for (ULONG iShot = 1; iShot <= cShots; iShot++) + { + // get circular gaussian spread + float x, y, z; + do { + x = RANDOM_FLOAT(-0.5,0.5) + RANDOM_FLOAT(-0.5,0.5); + y = RANDOM_FLOAT(-0.5,0.5) + RANDOM_FLOAT(-0.5,0.5); + z = x*x+y*y; + } while (z > 1); + + Vector vecDir = vecDirShooting + + x * vecSpread.x * vecRight + + y * vecSpread.y * vecUp; + Vector vecEnd; + + vecEnd = vecSrc + vecDir * flDistance; + UTIL_TraceLine(vecSrc, vecEnd, dont_ignore_monsters, ENT(pev)/*pentIgnore*/, &tr); + + tracer = 0; + if (iTracerFreq != 0 && (tracerCount++ % iTracerFreq) == 0) + { + Vector vecTracerSrc; + + if ( IsPlayer() ) + {// adjust tracer position for player + vecTracerSrc = vecSrc + Vector ( 0 , 0 , -4 ) + gpGlobals->v_right * 2 + gpGlobals->v_forward * 16; + } + else + { + vecTracerSrc = vecSrc; + } + + if ( iTracerFreq != 1 ) // guns that always trace also always decal + tracer = 1; + switch( iBulletType ) + { + case BULLET_MONSTER_MP5: + case BULLET_MONSTER_9MM: + case BULLET_MONSTER_12MM: + default: + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, vecTracerSrc ); + WRITE_BYTE( TE_TRACER ); + WRITE_COORD( vecTracerSrc.x ); + WRITE_COORD( vecTracerSrc.y ); + WRITE_COORD( vecTracerSrc.z ); + WRITE_COORD( tr.vecEndPos.x ); + WRITE_COORD( tr.vecEndPos.y ); + WRITE_COORD( tr.vecEndPos.z ); + MESSAGE_END(); + break; + } + } + // do damage, paint decals + if (tr.flFraction != 1.0) + { + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + if ( iDamage ) + { + pEntity->TraceAttack(pevAttacker, iDamage, vecDir, &tr, DMG_BULLET | ((iDamage > 16) ? DMG_ALWAYSGIB : DMG_NEVERGIB) ); + + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + DecalGunshot( &tr, iBulletType ); + } + else switch(iBulletType) + { + default: + case BULLET_MONSTER_9MM: + pEntity->TraceAttack(pevAttacker, gSkillData.monDmg9MM, vecDir, &tr, DMG_BULLET); + + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + DecalGunshot( &tr, iBulletType ); + + break; + + case BULLET_MONSTER_MP5: + pEntity->TraceAttack(pevAttacker, gSkillData.monDmgMP5, vecDir, &tr, DMG_BULLET); + + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + DecalGunshot( &tr, iBulletType ); + + break; + + case BULLET_MONSTER_12MM: + pEntity->TraceAttack(pevAttacker, gSkillData.monDmg12MM, vecDir, &tr, DMG_BULLET); + if ( !tracer ) + { + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + DecalGunshot( &tr, iBulletType ); + } + break; + + case BULLET_NONE: // FIX + pEntity->TraceAttack(pevAttacker, 50, vecDir, &tr, DMG_CLUB); + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + // only decal glass + if ( !FNullEnt(tr.pHit) && VARS(tr.pHit)->rendermode != 0) + { + UTIL_DecalTrace( &tr, DECAL_GLASSBREAK1 + RANDOM_LONG(0,2) ); + } + + break; + } + } + // make bullet trails + UTIL_BubbleTrail( vecSrc, tr.vecEndPos, (flDistance * tr.flFraction) / 64.0 ); + } + ApplyMultiDamage(pev, pevAttacker); +} + + +/* +================ +FireBullets + +Go to the trouble of combining multiple pellets into a single damage call. + +This version is used by Players, uses the random seed generator to sync client and server side shots. +================ +*/ +Vector CBaseEntity::FireBulletsPlayer ( ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq, int iDamage, entvars_t *pevAttacker, int shared_rand ) +{ + static int tracerCount; + TraceResult tr; + Vector vecRight = gpGlobals->v_right; + Vector vecUp = gpGlobals->v_up; + float x, y, z; + + if ( pevAttacker == NULL ) + pevAttacker = pev; // the default attacker is ourselves + + ClearMultiDamage(); + gMultiDamage.type = DMG_BULLET | DMG_NEVERGIB; + + for ( ULONG iShot = 1; iShot <= cShots; iShot++ ) + { + //Use player's random seed. + // get circular gaussian spread + x = UTIL_SharedRandomFloat( shared_rand + iShot, -0.5, 0.5 ) + UTIL_SharedRandomFloat( shared_rand + ( 1 + iShot ) , -0.5, 0.5 ); + y = UTIL_SharedRandomFloat( shared_rand + ( 2 + iShot ), -0.5, 0.5 ) + UTIL_SharedRandomFloat( shared_rand + ( 3 + iShot ), -0.5, 0.5 ); + z = x * x + y * y; + + Vector vecDir = vecDirShooting + + x * vecSpread.x * vecRight + + y * vecSpread.y * vecUp; + Vector vecEnd; + + vecEnd = vecSrc + vecDir * flDistance; + UTIL_TraceLine(vecSrc, vecEnd, dont_ignore_monsters, ENT(pev)/*pentIgnore*/, &tr); + + // do damage, paint decals + if (tr.flFraction != 1.0) + { + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + if ( iDamage ) + { + pEntity->TraceAttack(pevAttacker, iDamage, vecDir, &tr, DMG_BULLET | ((iDamage > 16) ? DMG_ALWAYSGIB : DMG_NEVERGIB) ); + + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + DecalGunshot( &tr, iBulletType ); + } + else switch(iBulletType) + { + default: + case BULLET_PLAYER_9MM: + pEntity->TraceAttack(pevAttacker, gSkillData.plrDmg9MM, vecDir, &tr, DMG_BULLET); + break; + + case BULLET_PLAYER_MP5: + pEntity->TraceAttack(pevAttacker, gSkillData.plrDmgMP5, vecDir, &tr, DMG_BULLET); + break; + + case BULLET_PLAYER_BUCKSHOT: + // make distance based! + pEntity->TraceAttack(pevAttacker, gSkillData.plrDmgBuckshot, vecDir, &tr, DMG_BULLET); + break; + + case BULLET_PLAYER_357: + pEntity->TraceAttack(pevAttacker, gSkillData.plrDmg357, vecDir, &tr, DMG_BULLET); + break; + + case BULLET_NONE: // FIX + pEntity->TraceAttack(pevAttacker, 50, vecDir, &tr, DMG_CLUB); + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + // only decal glass + if ( !FNullEnt(tr.pHit) && VARS(tr.pHit)->rendermode != 0) + { + UTIL_DecalTrace( &tr, DECAL_GLASSBREAK1 + RANDOM_LONG(0,2) ); + } + + break; + } + } + // make bullet trails + UTIL_BubbleTrail( vecSrc, tr.vecEndPos, (flDistance * tr.flFraction) / 64.0 ); + } + ApplyMultiDamage(pev, pevAttacker); + + return Vector( x * vecSpread.x, y * vecSpread.y, 0.0 ); +} + +void CBaseEntity :: TraceBleed( float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + if (BloodColor() == DONT_BLEED) + return; + + if (flDamage == 0) + return; + + if (! (bitsDamageType & (DMG_CRUSH | DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB | DMG_MORTAR))) + return; + + // make blood decal on the wall! + TraceResult Bloodtr; + Vector vecTraceDir; + float flNoise; + int cCount; + int i; + +/* + if ( !IsAlive() ) + { + // dealing with a dead monster. + if ( pev->max_health <= 0 ) + { + // no blood decal for a monster that has already decalled its limit. + return; + } + else + { + pev->max_health--; + } + } +*/ + + if (flDamage < 10) + { + flNoise = 0.1; + cCount = 1; + } + else if (flDamage < 25) + { + flNoise = 0.2; + cCount = 2; + } + else + { + flNoise = 0.3; + cCount = 4; + } + + for ( i = 0 ; i < cCount ; i++ ) + { + vecTraceDir = vecDir * -1;// trace in the opposite direction the shot came from (the direction the shot is going) + + vecTraceDir.x += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.y += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.z += RANDOM_FLOAT( -flNoise, flNoise ); + + UTIL_TraceLine( ptr->vecEndPos, ptr->vecEndPos + vecTraceDir * -172, ignore_monsters, ENT(pev), &Bloodtr); + + if ( Bloodtr.flFraction != 1.0 ) + { + UTIL_BloodDecalTrace( &Bloodtr, BloodColor() ); + } + } +} + +//========================================================= +//========================================================= +void CBaseMonster :: MakeDamageBloodDecal ( int cCount, float flNoise, TraceResult *ptr, const Vector &vecDir ) +{ + // make blood decal on the wall! + TraceResult Bloodtr; + Vector vecTraceDir; + int i; + + if ( !IsAlive() ) + { + // dealing with a dead monster. + if ( pev->max_health <= 0 ) + { + // no blood decal for a monster that has already decalled its limit. + return; + } + else + { + pev->max_health--; + } + } + + for ( i = 0 ; i < cCount ; i++ ) + { + vecTraceDir = vecDir; + + vecTraceDir.x += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.y += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.z += RANDOM_FLOAT( -flNoise, flNoise ); + + UTIL_TraceLine( ptr->vecEndPos, ptr->vecEndPos + vecTraceDir * 172, ignore_monsters, ENT(pev), &Bloodtr); + +/* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + WRITE_COORD( ptr->vecEndPos.x ); + WRITE_COORD( ptr->vecEndPos.y ); + WRITE_COORD( ptr->vecEndPos.z ); + + WRITE_COORD( Bloodtr.vecEndPos.x ); + WRITE_COORD( Bloodtr.vecEndPos.y ); + WRITE_COORD( Bloodtr.vecEndPos.z ); + MESSAGE_END(); +*/ + + if ( Bloodtr.flFraction != 1.0 ) + { + UTIL_BloodDecalTrace( &Bloodtr, BloodColor() ); + } + } +} diff --git a/dlls/controller.cpp b/dlls/controller.cpp new file mode 100644 index 0000000..34253c0 --- /dev/null +++ b/dlls/controller.cpp @@ -0,0 +1,1427 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +//========================================================= +// CONTROLLER +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "effects.h" +#include "schedule.h" +#include "weapons.h" +#include "squadmonster.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define CONTROLLER_AE_HEAD_OPEN 1 +#define CONTROLLER_AE_BALL_SHOOT 2 +#define CONTROLLER_AE_SMALL_SHOOT 3 +#define CONTROLLER_AE_POWERUP_FULL 4 +#define CONTROLLER_AE_POWERUP_HALF 5 + +#define CONTROLLER_FLINCH_DELAY 2 // at most one flinch every n secs + +class CController : public CSquadMonster +{ +public: + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + + void RunAI( void ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); // balls + BOOL CheckRangeAttack2 ( float flDot, float flDist ); // head + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); // block, throw + Schedule_t* GetSchedule ( void ); + Schedule_t* GetScheduleOfType ( int Type ); + void StartTask ( Task_t *pTask ); + void RunTask ( Task_t *pTask ); + CUSTOM_SCHEDULES; + + void Stop( void ); + void Move ( float flInterval ); + int CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ); + void MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ); + void SetActivity ( Activity NewActivity ); + BOOL ShouldAdvanceRoute( float flWaypointDist ); + int LookupFloat( ); + + float m_flNextFlinch; + + float m_flShootTime; + float m_flShootEnd; + + void PainSound( void ); + void AlertSound( void ); + void IdleSound( void ); + void AttackSound( void ); + void DeathSound( void ); + + static const char *pAttackSounds[]; + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + static const char *pPainSounds[]; + static const char *pDeathSounds[]; + + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + void Killed( entvars_t *pevAttacker, int iGib ); + void GibMonster( void ); + + CSprite *m_pBall[2]; // hand balls + int m_iBall[2]; // how bright it should be + float m_iBallTime[2]; // when it should be that color + int m_iBallCurrent[2]; // current brightness + + Vector m_vecEstVelocity; + + Vector m_velocity; + int m_fInCombat; +}; + +LINK_ENTITY_TO_CLASS( monster_alien_controller, CController ); + +TYPEDESCRIPTION CController::m_SaveData[] = +{ + DEFINE_ARRAY( CController, m_pBall, FIELD_CLASSPTR, 2 ), + DEFINE_ARRAY( CController, m_iBall, FIELD_INTEGER, 2 ), + DEFINE_ARRAY( CController, m_iBallTime, FIELD_TIME, 2 ), + DEFINE_ARRAY( CController, m_iBallCurrent, FIELD_INTEGER, 2 ), + DEFINE_FIELD( CController, m_vecEstVelocity, FIELD_VECTOR ), +}; +IMPLEMENT_SAVERESTORE( CController, CSquadMonster ); + + +const char *CController::pAttackSounds[] = +{ + "controller/con_attack1.wav", + "controller/con_attack2.wav", + "controller/con_attack3.wav", +}; + +const char *CController::pIdleSounds[] = +{ + "controller/con_idle1.wav", + "controller/con_idle2.wav", + "controller/con_idle3.wav", + "controller/con_idle4.wav", + "controller/con_idle5.wav", +}; + +const char *CController::pAlertSounds[] = +{ + "controller/con_alert1.wav", + "controller/con_alert2.wav", + "controller/con_alert3.wav", +}; + +const char *CController::pPainSounds[] = +{ + "controller/con_pain1.wav", + "controller/con_pain2.wav", + "controller/con_pain3.wav", +}; + +const char *CController::pDeathSounds[] = +{ + "controller/con_die1.wav", + "controller/con_die2.wav", +}; + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CController :: Classify ( void ) +{ + return CLASS_ALIEN_MILITARY; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CController :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + +#if 0 + switch ( m_Activity ) + { + } +#endif + + pev->yaw_speed = ys; +} + +int CController :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // HACK HACK -- until we fix this. + if ( IsAlive() ) + PainSound(); + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + + +void CController::Killed( entvars_t *pevAttacker, int iGib ) +{ + // shut off balls + /* + m_iBall[0] = 0; + m_iBallTime[0] = gpGlobals->time + 4.0; + m_iBall[1] = 0; + m_iBallTime[1] = gpGlobals->time + 4.0; + */ + + // fade balls + if (m_pBall[0]) + { + m_pBall[0]->SUB_StartFadeOut(); + m_pBall[0] = NULL; + } + if (m_pBall[1]) + { + m_pBall[1]->SUB_StartFadeOut(); + m_pBall[1] = NULL; + } + + CSquadMonster::Killed( pevAttacker, iGib ); +} + + +void CController::GibMonster( void ) +{ + // delete balls + if (m_pBall[0]) + { + UTIL_Remove( m_pBall[0] ); + m_pBall[0] = NULL; + } + if (m_pBall[1]) + { + UTIL_Remove( m_pBall[1] ); + m_pBall[1] = NULL; + } + CSquadMonster::GibMonster( ); +} + + + + +void CController :: PainSound( void ) +{ + if (RANDOM_LONG(0,5) < 2) + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pPainSounds ); +} + +void CController :: AlertSound( void ) +{ + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pAlertSounds ); +} + +void CController :: IdleSound( void ) +{ + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pIdleSounds ); +} + +void CController :: AttackSound( void ) +{ + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pAttackSounds ); +} + +void CController :: DeathSound( void ) +{ + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pDeathSounds ); +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CController :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case CONTROLLER_AE_HEAD_OPEN: + { + Vector vecStart, angleGun; + + GetAttachment( 0, vecStart, angleGun ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x1000 ); // entity, attachment + WRITE_COORD( vecStart.x ); // origin + WRITE_COORD( vecStart.y ); + WRITE_COORD( vecStart.z ); + WRITE_COORD( 1 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 192 ); // G + WRITE_BYTE( 64 ); // B + WRITE_BYTE( 20 ); // life * 10 + WRITE_COORD( -32 ); // decay + MESSAGE_END(); + + m_iBall[0] = 192; + m_iBallTime[0] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + m_iBall[1] = 255; + m_iBallTime[1] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + + } + break; + + case CONTROLLER_AE_BALL_SHOOT: + { + Vector vecStart, angleGun; + + GetAttachment( 0, vecStart, angleGun ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x1000 ); // entity, attachment + WRITE_COORD( 0 ); // origin + WRITE_COORD( 0 ); + WRITE_COORD( 0 ); + WRITE_COORD( 32 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 192 ); // G + WRITE_BYTE( 64 ); // B + WRITE_BYTE( 10 ); // life * 10 + WRITE_COORD( 32 ); // decay + MESSAGE_END(); + + CBaseMonster *pBall = (CBaseMonster*)Create( "controller_head_ball", vecStart, pev->angles, edict() ); + + pBall->pev->velocity = Vector( 0, 0, 32 ); + pBall->m_hEnemy = m_hEnemy; + + m_iBall[0] = 0; + m_iBall[1] = 0; + } + break; + + case CONTROLLER_AE_SMALL_SHOOT: + { + AttackSound( ); + m_flShootTime = gpGlobals->time; + m_flShootEnd = m_flShootTime + atoi( pEvent->options ) / 15.0; + } + break; + case CONTROLLER_AE_POWERUP_FULL: + { + m_iBall[0] = 255; + m_iBallTime[0] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + m_iBall[1] = 255; + m_iBallTime[1] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + } + break; + case CONTROLLER_AE_POWERUP_HALF: + { + m_iBall[0] = 192; + m_iBallTime[0] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + m_iBall[1] = 192; + m_iBallTime[1] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + } + break; + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CController :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/controller.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 )); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_FLY; + pev->flags |= FL_FLY; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->health = gSkillData.controllerHealth; + pev->view_ofs = Vector( 0, 0, -2 );// position of the eyes relative to monster's origin. + m_flFieldOfView = VIEW_FIELD_FULL;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CController :: Precache() +{ + PRECACHE_MODEL("models/controller.mdl"); + + PRECACHE_SOUND_ARRAY( pAttackSounds ); + PRECACHE_SOUND_ARRAY( pIdleSounds ); + PRECACHE_SOUND_ARRAY( pAlertSounds ); + PRECACHE_SOUND_ARRAY( pPainSounds ); + PRECACHE_SOUND_ARRAY( pDeathSounds ); + + PRECACHE_MODEL( "sprites/xspark4.spr"); + + UTIL_PrecacheOther( "controller_energy_ball" ); + UTIL_PrecacheOther( "controller_head_ball" ); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + +// Chase enemy schedule +Task_t tlControllerChaseEnemy[] = +{ + { TASK_GET_PATH_TO_ENEMY, (float)128 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + +}; + +Schedule_t slControllerChaseEnemy[] = +{ + { + tlControllerChaseEnemy, + ARRAYSIZE ( tlControllerChaseEnemy ), + bits_COND_NEW_ENEMY | + bits_COND_TASK_FAILED, + 0, + "ControllerChaseEnemy" + }, +}; + + + +Task_t tlControllerStrafe[] = +{ + { TASK_WAIT, (float)0.2 }, + { TASK_GET_PATH_TO_ENEMY, (float)128 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slControllerStrafe[] = +{ + { + tlControllerStrafe, + ARRAYSIZE ( tlControllerStrafe ), + bits_COND_NEW_ENEMY, + 0, + "ControllerStrafe" + }, +}; + + +Task_t tlControllerTakeCover[] = +{ + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slControllerTakeCover[] = +{ + { + tlControllerTakeCover, + ARRAYSIZE ( tlControllerTakeCover ), + bits_COND_NEW_ENEMY, + 0, + "ControllerTakeCover" + }, +}; + + +Task_t tlControllerFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slControllerFail[] = +{ + { + tlControllerFail, + ARRAYSIZE ( tlControllerFail ), + 0, + 0, + "ControllerFail" + }, +}; + + + +DEFINE_CUSTOM_SCHEDULES( CController ) +{ + slControllerChaseEnemy, + slControllerStrafe, + slControllerTakeCover, + slControllerFail, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CController, CSquadMonster ); + + + +//========================================================= +// StartTask +//========================================================= +void CController :: StartTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK1: + CSquadMonster :: StartTask ( pTask ); + break; + case TASK_GET_PATH_TO_ENEMY_LKP: + { + if (BuildNearestRoute( m_vecEnemyLKP, pev->view_ofs, pTask->flData, (m_vecEnemyLKP - pev->origin).Length() + 1024 )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemyLKP failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_ENEMY: + { + CBaseEntity *pEnemy = m_hEnemy; + + if ( pEnemy == NULL ) + { + TaskFail(); + return; + } + + if (BuildNearestRoute( pEnemy->pev->origin, pEnemy->pev->view_ofs, pTask->flData, (pEnemy->pev->origin - pev->origin).Length() + 1024 )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemy failed!!\n" ); + TaskFail(); + } + break; + } + default: + CSquadMonster :: StartTask ( pTask ); + break; + } +} + + +Vector Intersect( Vector vecSrc, Vector vecDst, Vector vecMove, float flSpeed ) +{ + Vector vecTo = vecDst - vecSrc; + + float a = DotProduct( vecMove, vecMove ) - flSpeed * flSpeed; + float b = 0 * DotProduct(vecTo, vecMove); // why does this work? + float c = DotProduct( vecTo, vecTo ); + + float t; + if (a == 0) + { + t = c / (flSpeed * flSpeed); + } + else + { + t = b * b - 4 * a * c; + t = sqrt( t ) / (2.0 * a); + float t1 = -b +t; + float t2 = -b -t; + + if (t1 < 0 || t2 < t1) + t = t2; + else + t = t1; + } + + // ALERT( at_console, "Intersect %f\n", t ); + + if (t < 0.1) + t = 0.1; + if (t > 10.0) + t = 10.0; + + Vector vecHit = vecTo + vecMove * t; + return vecHit.Normalize( ) * flSpeed; +} + + +int CController::LookupFloat( ) +{ + if (m_velocity.Length( ) < 32.0) + { + return LookupSequence( "up" ); + } + + UTIL_MakeAimVectors( pev->angles ); + float x = DotProduct( gpGlobals->v_forward, m_velocity ); + float y = DotProduct( gpGlobals->v_right, m_velocity ); + float z = DotProduct( gpGlobals->v_up, m_velocity ); + + if (fabs(x) > fabs(y) && fabs(x) > fabs(z)) + { + if (x > 0) + return LookupSequence( "forward"); + else + return LookupSequence( "backward"); + } + else if (fabs(y) > fabs(z)) + { + if (y > 0) + return LookupSequence( "right"); + else + return LookupSequence( "left"); + } + else + { + if (z > 0) + return LookupSequence( "up"); + else + return LookupSequence( "down"); + } +} + + +//========================================================= +// RunTask +//========================================================= +void CController :: RunTask ( Task_t *pTask ) +{ + + if (m_flShootEnd > gpGlobals->time) + { + Vector vecHand, vecAngle; + + GetAttachment( 2, vecHand, vecAngle ); + + while (m_flShootTime < m_flShootEnd && m_flShootTime < gpGlobals->time) + { + Vector vecSrc = vecHand + pev->velocity * (m_flShootTime - gpGlobals->time); + Vector vecDir; + + if (m_hEnemy != NULL) + { + if (HasConditions( bits_COND_SEE_ENEMY )) + { + m_vecEstVelocity = m_vecEstVelocity * 0.5 + m_hEnemy->pev->velocity * 0.5; + } + else + { + m_vecEstVelocity = m_vecEstVelocity * 0.8; + } + vecDir = Intersect( vecSrc, m_hEnemy->BodyTarget( pev->origin ), m_vecEstVelocity, gSkillData.controllerSpeedBall ); + float delta = 0.03490; // +-2 degree + vecDir = vecDir + Vector( RANDOM_FLOAT( -delta, delta ), RANDOM_FLOAT( -delta, delta ), RANDOM_FLOAT( -delta, delta ) ) * gSkillData.controllerSpeedBall; + + vecSrc = vecSrc + vecDir * (gpGlobals->time - m_flShootTime); + CBaseMonster *pBall = (CBaseMonster*)Create( "controller_energy_ball", vecSrc, pev->angles, edict() ); + pBall->pev->velocity = vecDir; + } + m_flShootTime += 0.2; + } + + if (m_flShootTime > m_flShootEnd) + { + m_iBall[0] = 64; + m_iBallTime[0] = m_flShootEnd; + m_iBall[1] = 64; + m_iBallTime[1] = m_flShootEnd; + m_fInCombat = FALSE; + } + } + + switch ( pTask->iTask ) + { + case TASK_WAIT_FOR_MOVEMENT: + case TASK_WAIT: + case TASK_WAIT_FACE_ENEMY: + case TASK_WAIT_PVS: + MakeIdealYaw( m_vecEnemyLKP ); + ChangeYaw( pev->yaw_speed ); + + if (m_fSequenceFinished) + { + m_fInCombat = FALSE; + } + + CSquadMonster :: RunTask ( pTask ); + + if (!m_fInCombat) + { + if (HasConditions ( bits_COND_CAN_RANGE_ATTACK1 )) + { + pev->sequence = LookupActivity( ACT_RANGE_ATTACK1 ); + pev->frame = 0; + ResetSequenceInfo( ); + m_fInCombat = TRUE; + } + else if (HasConditions ( bits_COND_CAN_RANGE_ATTACK2 )) + { + pev->sequence = LookupActivity( ACT_RANGE_ATTACK2 ); + pev->frame = 0; + ResetSequenceInfo( ); + m_fInCombat = TRUE; + } + else + { + int iFloat = LookupFloat( ); + if (m_fSequenceFinished || iFloat != pev->sequence) + { + pev->sequence = iFloat; + pev->frame = 0; + ResetSequenceInfo( ); + } + } + } + break; + default: + CSquadMonster :: RunTask ( pTask ); + break; + } +} + + +//========================================================= +// GetSchedule - Decides which type of schedule best suits +// the monster's current state and conditions. Then calls +// monster's member function to get a pointer to a schedule +// of the proper type. +//========================================================= +Schedule_t *CController :: GetSchedule ( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: + break; + + case MONSTERSTATE_ALERT: + break; + + case MONSTERSTATE_COMBAT: + { + Vector vecTmp = Intersect( Vector( 0, 0, 0 ), Vector( 100, 4, 7 ), Vector( 2, 10, -3 ), 20.0 ); + + // dead enemy + if ( HasConditions ( bits_COND_LIGHT_DAMAGE ) ) + { + // m_iFrustration++; + } + if ( HasConditions ( bits_COND_HEAVY_DAMAGE ) ) + { + // m_iFrustration++; + } + } + break; + } + + return CSquadMonster :: GetSchedule(); +} + + + +//========================================================= +//========================================================= +Schedule_t* CController :: GetScheduleOfType ( int Type ) +{ + // ALERT( at_console, "%d\n", m_iFrustration ); + switch ( Type ) + { + case SCHED_CHASE_ENEMY: + return slControllerChaseEnemy; + case SCHED_RANGE_ATTACK1: + return slControllerStrafe; + case SCHED_RANGE_ATTACK2: + case SCHED_MELEE_ATTACK1: + case SCHED_MELEE_ATTACK2: + case SCHED_TAKE_COVER_FROM_ENEMY: + return slControllerTakeCover; + case SCHED_FAIL: + return slControllerFail; + } + + return CBaseMonster :: GetScheduleOfType( Type ); +} + + + + + +//========================================================= +// CheckRangeAttack1 - shoot a bigass energy ball out of their head +// +//========================================================= +BOOL CController :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDot > 0.5 && flDist > 256 && flDist <= 2048 ) + { + return TRUE; + } + return FALSE; +} + + +BOOL CController :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + if ( flDot > 0.5 && flDist > 64 && flDist <= 2048 ) + { + return TRUE; + } + return FALSE; +} + + +BOOL CController :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + return FALSE; +} + + +void CController :: SetActivity ( Activity NewActivity ) +{ + CBaseMonster::SetActivity( NewActivity ); + + switch ( m_Activity) + { + case ACT_WALK: + m_flGroundSpeed = 100; + break; + default: + m_flGroundSpeed = 100; + break; + } +} + + + +//========================================================= +// RunAI +//========================================================= +void CController :: RunAI( void ) +{ + CBaseMonster :: RunAI(); + Vector vecStart, angleGun; + + if ( HasMemory( bits_MEMORY_KILLED ) ) + return; + + for (int i = 0; i < 2; i++) + { + if (m_pBall[i] == NULL) + { + m_pBall[i] = CSprite::SpriteCreate( "sprites/xspark4.spr", pev->origin, TRUE ); + m_pBall[i]->SetTransparency( kRenderGlow, 255, 255, 255, 255, kRenderFxNoDissipation ); + m_pBall[i]->SetAttachment( edict(), (i + 3) ); + m_pBall[i]->SetScale( 1.0 ); + } + + float t = m_iBallTime[i] - gpGlobals->time; + if (t > 0.1) + t = 0.1 / t; + else + t = 1.0; + + m_iBallCurrent[i] += (m_iBall[i] - m_iBallCurrent[i]) * t; + + m_pBall[i]->SetBrightness( m_iBallCurrent[i] ); + + GetAttachment( i + 2, vecStart, angleGun ); + UTIL_SetOrigin( m_pBall[i]->pev, vecStart ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x1000 * (i + 3) ); // entity, attachment + WRITE_COORD( vecStart.x ); // origin + WRITE_COORD( vecStart.y ); + WRITE_COORD( vecStart.z ); + WRITE_COORD( m_iBallCurrent[i] / 8 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 192 ); // G + WRITE_BYTE( 64 ); // B + WRITE_BYTE( 5 ); // life * 10 + WRITE_COORD( 0 ); // decay + MESSAGE_END(); + } +} + + +extern void DrawRoute( entvars_t *pev, WayPoint_t *m_Route, int m_iRouteIndex, int r, int g, int b ); + +void CController::Stop( void ) +{ + m_IdealActivity = GetStoppedActivity(); +} + + +#define DIST_TO_CHECK 200 +void CController :: Move ( float flInterval ) +{ + float flWaypointDist; + float flCheckDist; + float flDist;// how far the lookahead check got before hitting an object. + float flMoveDist; + Vector vecDir; + Vector vecApex; + CBaseEntity *pTargetEnt; + + // Don't move if no valid route + if ( FRouteClear() ) + { + ALERT( at_aiconsole, "Tried to move with no route!\n" ); + TaskFail(); + return; + } + + if ( m_flMoveWaitFinished > gpGlobals->time ) + return; + +// Debug, test movement code +#if 0 +// if ( CVAR_GET_FLOAT("stopmove" ) != 0 ) + { + if ( m_movementGoal == MOVEGOAL_ENEMY ) + RouteSimplify( m_hEnemy ); + else + RouteSimplify( m_hTargetEnt ); + FRefreshRoute(); + return; + } +#else +// Debug, draw the route +// DrawRoute( pev, m_Route, m_iRouteIndex, 0, 0, 255 ); +#endif + + // if the monster is moving directly towards an entity (enemy for instance), we'll set this pointer + // to that entity for the CheckLocalMove and Triangulate functions. + pTargetEnt = NULL; + + if (m_flGroundSpeed == 0) + { + m_flGroundSpeed = 100; + // TaskFail( ); + // return; + } + + flMoveDist = m_flGroundSpeed * flInterval; + + do + { + // local move to waypoint. + vecDir = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Normalize(); + flWaypointDist = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Length(); + + // MakeIdealYaw ( m_Route[ m_iRouteIndex ].vecLocation ); + // ChangeYaw ( pev->yaw_speed ); + + // if the waypoint is closer than CheckDist, CheckDist is the dist to waypoint + if ( flWaypointDist < DIST_TO_CHECK ) + { + flCheckDist = flWaypointDist; + } + else + { + flCheckDist = DIST_TO_CHECK; + } + + if ( (m_Route[ m_iRouteIndex ].iType & (~bits_MF_NOT_TO_MASK)) == bits_MF_TO_ENEMY ) + { + // only on a PURE move to enemy ( i.e., ONLY MF_TO_ENEMY set, not MF_TO_ENEMY and DETOUR ) + pTargetEnt = m_hEnemy; + } + else if ( (m_Route[ m_iRouteIndex ].iType & ~bits_MF_NOT_TO_MASK) == bits_MF_TO_TARGETENT ) + { + pTargetEnt = m_hTargetEnt; + } + + // !!!BUGBUG - CheckDist should be derived from ground speed. + // If this fails, it should be because of some dynamic entity blocking this guy. + // We've already checked this path, so we should wait and time out if the entity doesn't move + flDist = 0; + if ( CheckLocalMove ( pev->origin, pev->origin + vecDir * flCheckDist, pTargetEnt, &flDist ) != LOCALMOVE_VALID ) + { + CBaseEntity *pBlocker; + + // Can't move, stop + Stop(); + // Blocking entity is in global trace_ent + pBlocker = CBaseEntity::Instance( gpGlobals->trace_ent ); + if (pBlocker) + { + DispatchBlocked( edict(), pBlocker->edict() ); + } + if ( pBlocker && m_moveWaitTime > 0 && pBlocker->IsMoving() && !pBlocker->IsPlayer() && (gpGlobals->time-m_flMoveWaitFinished) > 3.0 ) + { + // Can we still move toward our target? + if ( flDist < m_flGroundSpeed ) + { + // Wait for a second + m_flMoveWaitFinished = gpGlobals->time + m_moveWaitTime; + // ALERT( at_aiconsole, "Move %s!!!\n", STRING( pBlocker->pev->classname ) ); + return; + } + } + else + { + // try to triangulate around whatever is in the way. + if ( FTriangulate( pev->origin, m_Route[ m_iRouteIndex ].vecLocation, flDist, pTargetEnt, &vecApex ) ) + { + InsertWaypoint( vecApex, bits_MF_TO_DETOUR ); + RouteSimplify( pTargetEnt ); + } + else + { + ALERT ( at_aiconsole, "Couldn't Triangulate\n" ); + Stop(); + if ( m_moveWaitTime > 0 ) + { + FRefreshRoute(); + m_flMoveWaitFinished = gpGlobals->time + m_moveWaitTime * 0.5; + } + else + { + TaskFail(); + ALERT( at_aiconsole, "Failed to move!\n" ); + //ALERT( at_aiconsole, "%f, %f, %f\n", pev->origin.z, (pev->origin + (vecDir * flCheckDist)).z, m_Route[m_iRouteIndex].vecLocation.z ); + } + return; + } + } + } + + // UNDONE: this is a hack to quit moving farther than it has looked ahead. + if (flCheckDist < flMoveDist) + { + MoveExecute( pTargetEnt, vecDir, flCheckDist / m_flGroundSpeed ); + + // ALERT( at_console, "%.02f\n", flInterval ); + AdvanceRoute( flWaypointDist ); + flMoveDist -= flCheckDist; + } + else + { + MoveExecute( pTargetEnt, vecDir, flMoveDist / m_flGroundSpeed ); + + if ( ShouldAdvanceRoute( flWaypointDist - flMoveDist ) ) + { + AdvanceRoute( flWaypointDist ); + } + flMoveDist = 0; + } + + if ( MovementIsComplete() ) + { + Stop(); + RouteClear(); + } + } while (flMoveDist > 0 && flCheckDist > 0); + + // cut corner? + if (flWaypointDist < 128) + { + if ( m_movementGoal == MOVEGOAL_ENEMY ) + RouteSimplify( m_hEnemy ); + else + RouteSimplify( m_hTargetEnt ); + FRefreshRoute(); + + if (m_flGroundSpeed > 100) + m_flGroundSpeed -= 40; + } + else + { + if (m_flGroundSpeed < 400) + m_flGroundSpeed += 10; + } +} + + + +BOOL CController:: ShouldAdvanceRoute( float flWaypointDist ) +{ + if ( flWaypointDist <= 32 ) + { + return TRUE; + } + + return FALSE; +} + + +int CController :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ) +{ + TraceResult tr; + + UTIL_TraceHull( vecStart + Vector( 0, 0, 32), vecEnd + Vector( 0, 0, 32), dont_ignore_monsters, large_hull, edict(), &tr ); + + // ALERT( at_console, "%.0f %.0f %.0f : ", vecStart.x, vecStart.y, vecStart.z ); + // ALERT( at_console, "%.0f %.0f %.0f\n", vecEnd.x, vecEnd.y, vecEnd.z ); + + if (pflDist) + { + *pflDist = ( (tr.vecEndPos - Vector( 0, 0, 32 )) - vecStart ).Length();// get the distance. + } + + // ALERT( at_console, "check %d %d %f\n", tr.fStartSolid, tr.fAllSolid, tr.flFraction ); + if (tr.fStartSolid || tr.flFraction < 1.0) + { + if ( pTarget && pTarget->edict() == gpGlobals->trace_ent ) + return LOCALMOVE_VALID; + return LOCALMOVE_INVALID; + } + + return LOCALMOVE_VALID; +} + + +void CController::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) +{ + if ( m_IdealActivity != m_movementActivity ) + m_IdealActivity = m_movementActivity; + + // ALERT( at_console, "move %.4f %.4f %.4f : %f\n", vecDir.x, vecDir.y, vecDir.z, flInterval ); + + // float flTotal = m_flGroundSpeed * pev->framerate * flInterval; + // UTIL_MoveToOrigin ( ENT(pev), m_Route[ m_iRouteIndex ].vecLocation, flTotal, MOVE_STRAFE ); + + m_velocity = m_velocity * 0.8 + m_flGroundSpeed * vecDir * 0.2; + + UTIL_MoveToOrigin ( ENT(pev), pev->origin + m_velocity, m_velocity.Length() * flInterval, MOVE_STRAFE ); + +} + + + + +//========================================================= +// Controller bouncy ball attack +//========================================================= +class CControllerHeadBall : public CBaseMonster +{ + void Spawn( void ); + void Precache( void ); + void EXPORT HuntThink( void ); + void EXPORT DieThink( void ); + void EXPORT BounceTouch( CBaseEntity *pOther ); + void MovetoTarget( Vector vecTarget ); + void Crawl( void ); + int m_iTrail; + int m_flNextAttack; + Vector m_vecIdeal; + EHANDLE m_hOwner; +}; +LINK_ENTITY_TO_CLASS( controller_head_ball, CControllerHeadBall ); + + + +void CControllerHeadBall :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "sprites/xspark4.spr"); + pev->rendermode = kRenderTransAdd; + pev->rendercolor.x = 255; + pev->rendercolor.y = 255; + pev->rendercolor.z = 255; + pev->renderamt = 255; + pev->scale = 2.0; + + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( pev, pev->origin ); + + SetThink( &CControllerHeadBall::HuntThink ); + SetTouch( &CControllerHeadBall::BounceTouch ); + + m_vecIdeal = Vector( 0, 0, 0 ); + + pev->nextthink = gpGlobals->time + 0.1; + + m_hOwner = Instance( pev->owner ); + pev->dmgtime = gpGlobals->time; +} + + +void CControllerHeadBall :: Precache( void ) +{ + PRECACHE_MODEL("sprites/xspark1.spr"); + PRECACHE_SOUND("debris/zap4.wav"); + PRECACHE_SOUND("weapons/electro4.wav"); +} + + +void CControllerHeadBall :: HuntThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + pev->renderamt -= 5; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( pev->renderamt / 16 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 255 ); // G + WRITE_BYTE( 255 ); // B + WRITE_BYTE( 2 ); // life * 10 + WRITE_COORD( 0 ); // decay + MESSAGE_END(); + + // check world boundaries + if (gpGlobals->time - pev->dmgtime > 5 || pev->renderamt < 64 || m_hEnemy == NULL || m_hOwner == NULL || pev->origin.x < -4096 || pev->origin.x > 4096 || pev->origin.y < -4096 || pev->origin.y > 4096 || pev->origin.z < -4096 || pev->origin.z > 4096) + { + SetTouch( NULL ); + UTIL_Remove( this ); + return; + } + + MovetoTarget( m_hEnemy->Center( ) ); + + if ((m_hEnemy->Center() - pev->origin).Length() < 64) + { + TraceResult tr; + + UTIL_TraceLine( pev->origin, m_hEnemy->Center(), dont_ignore_monsters, ENT(pev), &tr ); + + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + if (pEntity != NULL && pEntity->pev->takedamage) + { + ClearMultiDamage( ); + pEntity->TraceAttack( m_hOwner->pev, gSkillData.controllerDmgZap, pev->velocity, &tr, DMG_SHOCK ); + ApplyMultiDamage( pev, m_hOwner->pev ); + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( entindex() ); + WRITE_COORD( tr.vecEndPos.x ); + WRITE_COORD( tr.vecEndPos.y ); + WRITE_COORD( tr.vecEndPos.z ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 3 ); // life + WRITE_BYTE( 20 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); + + UTIL_EmitAmbientSound( ENT(pev), tr.vecEndPos, "weapons/electro4.wav", 0.5, ATTN_NORM, 0, RANDOM_LONG( 140, 160 ) ); + + m_flNextAttack = gpGlobals->time + 3.0; + + SetThink( &CControllerHeadBall::DieThink ); + pev->nextthink = gpGlobals->time + 0.3; + } + + // Crawl( ); +} + + +void CControllerHeadBall :: DieThink( void ) +{ + UTIL_Remove( this ); +} + + +void CControllerHeadBall :: MovetoTarget( Vector vecTarget ) +{ + // accelerate + float flSpeed = m_vecIdeal.Length(); + if (flSpeed == 0) + { + m_vecIdeal = pev->velocity; + flSpeed = m_vecIdeal.Length(); + } + + if (flSpeed > 400) + { + m_vecIdeal = m_vecIdeal.Normalize( ) * 400; + } + m_vecIdeal = m_vecIdeal + (vecTarget - pev->origin).Normalize() * 100; + pev->velocity = m_vecIdeal; +} + + + +void CControllerHeadBall :: Crawl( void ) +{ + + Vector vecAim = Vector( RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ) ).Normalize( ); + Vector vecPnt = pev->origin + pev->velocity * 0.3 + vecAim * 64; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( entindex() ); + WRITE_COORD( vecPnt.x); + WRITE_COORD( vecPnt.y); + WRITE_COORD( vecPnt.z); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 3 ); // life + WRITE_BYTE( 20 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); +} + + +void CControllerHeadBall::BounceTouch( CBaseEntity *pOther ) +{ + Vector vecDir = m_vecIdeal.Normalize( ); + + TraceResult tr = UTIL_GetGlobalTrace( ); + + float n = -DotProduct(tr.vecPlaneNormal, vecDir); + + vecDir = 2.0 * tr.vecPlaneNormal * n + vecDir; + + m_vecIdeal = vecDir * m_vecIdeal.Length(); +} + + + + +class CControllerZapBall : public CBaseMonster +{ + void Spawn( void ); + void Precache( void ); + void EXPORT AnimateThink( void ); + void EXPORT ExplodeTouch( CBaseEntity *pOther ); + + EHANDLE m_hOwner; +}; +LINK_ENTITY_TO_CLASS( controller_energy_ball, CControllerZapBall ); + + +void CControllerZapBall :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "sprites/xspark4.spr"); + pev->rendermode = kRenderTransAdd; + pev->rendercolor.x = 255; + pev->rendercolor.y = 255; + pev->rendercolor.z = 255; + pev->renderamt = 255; + pev->scale = 0.5; + + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( pev, pev->origin ); + + SetThink( &CControllerZapBall::AnimateThink ); + SetTouch( &CControllerZapBall::ExplodeTouch ); + + m_hOwner = Instance( pev->owner ); + pev->dmgtime = gpGlobals->time; // keep track of when ball spawned + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CControllerZapBall :: Precache( void ) +{ + PRECACHE_MODEL("sprites/xspark4.spr"); + // PRECACHE_SOUND("debris/zap4.wav"); + // PRECACHE_SOUND("weapons/electro4.wav"); +} + + +void CControllerZapBall :: AnimateThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + pev->frame = ((int)pev->frame + 1) % 11; + + if (gpGlobals->time - pev->dmgtime > 5 || pev->velocity.Length() < 10) + { + SetTouch( NULL ); + UTIL_Remove( this ); + } +} + + +void CControllerZapBall::ExplodeTouch( CBaseEntity *pOther ) +{ + if (pOther->pev->takedamage) + { + TraceResult tr = UTIL_GetGlobalTrace( ); + + entvars_t *pevOwner; + if (m_hOwner != NULL) + { + pevOwner = m_hOwner->pev; + } + else + { + pevOwner = pev; + } + + ClearMultiDamage( ); + pOther->TraceAttack(pevOwner, gSkillData.controllerDmgBall, pev->velocity.Normalize(), &tr, DMG_ENERGYBEAM ); + ApplyMultiDamage( pevOwner, pevOwner ); + + UTIL_EmitAmbientSound( ENT(pev), tr.vecEndPos, "weapons/electro4.wav", 0.3, ATTN_NORM, 0, RANDOM_LONG( 90, 99 ) ); + + } + + UTIL_Remove( this ); +} + + + +#endif // !OEM && !HLDEMO diff --git a/dlls/crossbow.cpp b/dlls/crossbow.cpp new file mode 100644 index 0000000..08dbd93 --- /dev/null +++ b/dlls/crossbow.cpp @@ -0,0 +1,548 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "gamerules.h" + +#ifndef CLIENT_DLL +#define BOLT_AIR_VELOCITY 2000 +#define BOLT_WATER_VELOCITY 1000 + +// UNDONE: Save/restore this? Don't forget to set classname and LINK_ENTITY_TO_CLASS() +// +// OVERLOADS SOME ENTVARS: +// +// speed - the ideal magnitude of my velocity +class CCrossbowBolt : public CBaseEntity +{ + void Spawn( void ); + void Precache( void ); + int Classify ( void ); + void EXPORT BubbleThink( void ); + void EXPORT BoltTouch( CBaseEntity *pOther ); + void EXPORT ExplodeThink( void ); + + int m_iTrail; + +public: + static CCrossbowBolt *BoltCreate( void ); +}; +LINK_ENTITY_TO_CLASS( crossbow_bolt, CCrossbowBolt ); + +CCrossbowBolt *CCrossbowBolt::BoltCreate( void ) +{ + // Create a new entity with CCrossbowBolt private data + CCrossbowBolt *pBolt = GetClassPtr( (CCrossbowBolt *)NULL ); + pBolt->pev->classname = MAKE_STRING("bolt"); + pBolt->Spawn(); + + return pBolt; +} + +void CCrossbowBolt::Spawn( ) +{ + Precache( ); + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + pev->gravity = 0.5; + + SET_MODEL(ENT(pev), "models/crossbow_bolt.mdl"); + + UTIL_SetOrigin( pev, pev->origin ); + UTIL_SetSize(pev, Vector(0, 0, 0), Vector(0, 0, 0)); + + SetTouch( &CCrossbowBolt::BoltTouch ); + SetThink( &CCrossbowBolt::BubbleThink ); + pev->nextthink = gpGlobals->time + 0.2; +} + + +void CCrossbowBolt::Precache( ) +{ + PRECACHE_MODEL ("models/crossbow_bolt.mdl"); + PRECACHE_SOUND("weapons/xbow_hitbod1.wav"); + PRECACHE_SOUND("weapons/xbow_hitbod2.wav"); + PRECACHE_SOUND("weapons/xbow_fly1.wav"); + PRECACHE_SOUND("weapons/xbow_hit1.wav"); + PRECACHE_SOUND("fvox/beep.wav"); + m_iTrail = PRECACHE_MODEL("sprites/streak.spr"); +} + + +int CCrossbowBolt :: Classify ( void ) +{ + return CLASS_NONE; +} + +void CCrossbowBolt::BoltTouch( CBaseEntity *pOther ) +{ + SetTouch( NULL ); + SetThink( NULL ); + + if (pOther->pev->takedamage) + { + TraceResult tr = UTIL_GetGlobalTrace( ); + entvars_t *pevOwner; + + pevOwner = VARS( pev->owner ); + + // UNDONE: this needs to call TraceAttack instead + ClearMultiDamage( ); + + if ( pOther->IsPlayer() ) + { + pOther->TraceAttack(pevOwner, gSkillData.plrDmgCrossbowClient, pev->velocity.Normalize(), &tr, DMG_NEVERGIB ); + } + else + { + pOther->TraceAttack(pevOwner, gSkillData.plrDmgCrossbowMonster, pev->velocity.Normalize(), &tr, DMG_BULLET | DMG_NEVERGIB ); + } + + ApplyMultiDamage( pev, pevOwner ); + + pev->velocity = Vector( 0, 0, 0 ); + // play body "thwack" sound + switch( RANDOM_LONG(0,1) ) + { + case 0: + EMIT_SOUND(ENT(pev), CHAN_BODY, "weapons/xbow_hitbod1.wav", 1, ATTN_NORM); break; + case 1: + EMIT_SOUND(ENT(pev), CHAN_BODY, "weapons/xbow_hitbod2.wav", 1, ATTN_NORM); break; + } + + if ( !g_pGameRules->IsMultiplayer() ) + { + Killed( pev, GIB_NEVER ); + } + } + else + { + EMIT_SOUND_DYN(ENT(pev), CHAN_BODY, "weapons/xbow_hit1.wav", RANDOM_FLOAT(0.95, 1.0), ATTN_NORM, 0, 98 + RANDOM_LONG(0,7)); + + SetThink( &CCrossbowBolt::SUB_Remove ); + pev->nextthink = gpGlobals->time;// this will get changed below if the bolt is allowed to stick in what it hit. + + if ( FClassnameIs( pOther->pev, "worldspawn" ) ) + { + // if what we hit is static architecture, can stay around for a while. + Vector vecDir = pev->velocity.Normalize( ); + UTIL_SetOrigin( pev, pev->origin - vecDir * 12 ); + pev->angles = UTIL_VecToAngles( vecDir ); + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_FLY; + pev->velocity = Vector( 0, 0, 0 ); + pev->avelocity.z = 0; + pev->angles.z = RANDOM_LONG(0,360); + pev->nextthink = gpGlobals->time + 10.0; + } + + if (UTIL_PointContents(pev->origin) != CONTENTS_WATER) + { + UTIL_Sparks( pev->origin ); + } + } + + if ( g_pGameRules->IsMultiplayer() ) + { + SetThink( &CCrossbowBolt::ExplodeThink ); + pev->nextthink = gpGlobals->time + 0.1; + } +} + +void CCrossbowBolt::BubbleThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if (pev->waterlevel == 0) + return; + + UTIL_BubbleTrail( pev->origin - pev->velocity * 0.1, pev->origin, 1 ); +} + +void CCrossbowBolt::ExplodeThink( void ) +{ + int iContents = UTIL_PointContents ( pev->origin ); + int iScale; + + pev->dmg = 40; + iScale = 10; + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + if (iContents != CONTENTS_WATER) + { + WRITE_SHORT( g_sModelIndexFireball ); + } + else + { + WRITE_SHORT( g_sModelIndexWExplosion ); + } + WRITE_BYTE( iScale ); // scale * 10 + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + entvars_t *pevOwner; + + if ( pev->owner ) + pevOwner = VARS( pev->owner ); + else + pevOwner = NULL; + + pev->owner = NULL; // can't traceline attack owner if this is set + + ::RadiusDamage( pev->origin, pev, pevOwner, pev->dmg, 128, CLASS_NONE, DMG_BLAST | DMG_ALWAYSGIB ); + + UTIL_Remove(this); +} +#endif + +enum crossbow_e { + CROSSBOW_IDLE1 = 0, // full + CROSSBOW_IDLE2, // empty + CROSSBOW_FIDGET1, // full + CROSSBOW_FIDGET2, // empty + CROSSBOW_FIRE1, // full + CROSSBOW_FIRE2, // reload + CROSSBOW_FIRE3, // empty + CROSSBOW_RELOAD, // from empty + CROSSBOW_DRAW1, // full + CROSSBOW_DRAW2, // empty + CROSSBOW_HOLSTER1, // full + CROSSBOW_HOLSTER2, // empty +}; + +LINK_ENTITY_TO_CLASS( weapon_crossbow, CCrossbow ); + +void CCrossbow::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_CROSSBOW; + SET_MODEL(ENT(pev), "models/w_crossbow.mdl"); + + m_iDefaultAmmo = CROSSBOW_DEFAULT_GIVE; + + FallInit();// get ready to fall down. +} + +int CCrossbow::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + +void CCrossbow::Precache( void ) +{ + PRECACHE_MODEL("models/w_crossbow.mdl"); + PRECACHE_MODEL("models/v_crossbow.mdl"); + PRECACHE_MODEL("models/p_crossbow.mdl"); + + PRECACHE_SOUND("weapons/xbow_fire1.wav"); + PRECACHE_SOUND("weapons/xbow_reload1.wav"); + + UTIL_PrecacheOther( "crossbow_bolt" ); + + m_usCrossbow = PRECACHE_EVENT( 1, "events/crossbow1.sc" ); + m_usCrossbow2 = PRECACHE_EVENT( 1, "events/crossbow2.sc" ); +} + + +int CCrossbow::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "bolts"; + p->iMaxAmmo1 = BOLT_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = CROSSBOW_MAX_CLIP; + p->iSlot = 2; + p->iPosition = 2; + p->iId = WEAPON_CROSSBOW; + p->iFlags = 0; + p->iWeight = CROSSBOW_WEIGHT; + return 1; +} + + +BOOL CCrossbow::Deploy( ) +{ + if (m_iClip) + return DefaultDeploy( "models/v_crossbow.mdl", "models/p_crossbow.mdl", CROSSBOW_DRAW1, "bow" ); + return DefaultDeploy( "models/v_crossbow.mdl", "models/p_crossbow.mdl", CROSSBOW_DRAW2, "bow" ); +} + +void CCrossbow::Holster( int skiplocal /* = 0 */ ) +{ + m_fInReload = FALSE;// cancel any reload in progress. + + if ( m_fInZoom ) + { + SecondaryAttack( ); + } + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + if (m_iClip) + SendWeaponAnim( CROSSBOW_HOLSTER1 ); + else + SendWeaponAnim( CROSSBOW_HOLSTER2 ); +} + +void CCrossbow::PrimaryAttack( void ) +{ + +#ifdef CLIENT_DLL + if ( m_fInZoom && bIsMultiplayer() ) +#else + if ( m_fInZoom && g_pGameRules->IsMultiplayer() ) +#endif + { + FireSniperBolt(); + return; + } + + FireBolt(); +} + +// this function only gets called in multiplayer +void CCrossbow::FireSniperBolt() +{ + m_flNextPrimaryAttack = GetNextAttackDelay(0.75); + + if (m_iClip == 0) + { + PlayEmptySound( ); + return; + } + + TraceResult tr; + + m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME; + m_iClip--; + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usCrossbow2, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0, 0, m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType], 0, 0 ); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + Vector anglesAim = m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle; + UTIL_MakeVectors( anglesAim ); + Vector vecSrc = m_pPlayer->GetGunPosition( ) - gpGlobals->v_up * 2; + Vector vecDir = gpGlobals->v_forward; + + UTIL_TraceLine(vecSrc, vecSrc + vecDir * 8192, dont_ignore_monsters, m_pPlayer->edict(), &tr); + +#ifndef CLIENT_DLL + if ( tr.pHit->v.takedamage ) + { + ClearMultiDamage( ); + CBaseEntity::Instance(tr.pHit)->TraceAttack(m_pPlayer->pev, 120, vecDir, &tr, DMG_BULLET | DMG_NEVERGIB ); + ApplyMultiDamage( pev, m_pPlayer->pev ); + } +#endif +} + +void CCrossbow::FireBolt() +{ + TraceResult tr; + + if (m_iClip == 0) + { + PlayEmptySound( ); + return; + } + + m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME; + + m_iClip--; + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usCrossbow, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0, 0, m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType], 0, 0 ); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + Vector anglesAim = m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle; + UTIL_MakeVectors( anglesAim ); + + anglesAim.x = -anglesAim.x; + Vector vecSrc = m_pPlayer->GetGunPosition( ) - gpGlobals->v_up * 2; + Vector vecDir = gpGlobals->v_forward; + +#ifndef CLIENT_DLL + CCrossbowBolt *pBolt = CCrossbowBolt::BoltCreate(); + pBolt->pev->origin = vecSrc; + pBolt->pev->angles = anglesAim; + pBolt->pev->owner = m_pPlayer->edict(); + + if (m_pPlayer->pev->waterlevel == 3) + { + pBolt->pev->velocity = vecDir * BOLT_WATER_VELOCITY; + pBolt->pev->speed = BOLT_WATER_VELOCITY; + } + else + { + pBolt->pev->velocity = vecDir * BOLT_AIR_VELOCITY; + pBolt->pev->speed = BOLT_AIR_VELOCITY; + } + pBolt->pev->avelocity.z = 10; +#endif + + if (!m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + // HEV suit - indicate out of ammo condition + m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + + m_flNextPrimaryAttack = GetNextAttackDelay(0.75); + + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.75; + + if (m_iClip != 0) + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 5.0; + else + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.75; +} + + +void CCrossbow::SecondaryAttack() +{ + if ( m_pPlayer->pev->fov != 0 ) + { + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 0; // 0 means reset to default fov + m_fInZoom = 0; + } + else if ( m_pPlayer->pev->fov != 20 ) + { + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 20; + m_fInZoom = 1; + } + + pev->nextthink = UTIL_WeaponTimeBase() + 0.1; + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1.0; +} + + +void CCrossbow::Reload( void ) +{ + if ( m_pPlayer->ammo_bolts <= 0 ) + return; + + if ( m_pPlayer->pev->fov != 0 ) + { + SecondaryAttack(); + } + + if ( DefaultReload( 5, CROSSBOW_RELOAD, 4.5 ) ) + { + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/xbow_reload1.wav", RANDOM_FLOAT(0.95, 1.0), ATTN_NORM, 0, 93 + RANDOM_LONG(0,0xF)); + } +} + + +void CCrossbow::WeaponIdle( void ) +{ + m_pPlayer->GetAutoaimVector( AUTOAIM_2DEGREES ); // get the autoaim vector but ignore it; used for autoaim crosshair in DM + + ResetEmptySound( ); + + if ( m_flTimeWeaponIdle < UTIL_WeaponTimeBase() ) + { + float flRand = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0, 1 ); + if (flRand <= 0.75) + { + if (m_iClip) + { + SendWeaponAnim( CROSSBOW_IDLE1 ); + } + else + { + SendWeaponAnim( CROSSBOW_IDLE2 ); + } + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + } + else + { + if (m_iClip) + { + SendWeaponAnim( CROSSBOW_FIDGET1 ); + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 90.0 / 30.0; + } + else + { + SendWeaponAnim( CROSSBOW_FIDGET2 ); + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 80.0 / 30.0; + } + } + } +} + + + +class CCrossbowAmmo : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_crossbow_clip.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_crossbow_clip.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + if (pOther->GiveAmmo( AMMO_CROSSBOWCLIP_GIVE, "bolts", BOLT_MAX_CARRY ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + return TRUE; + } + return FALSE; + } +}; +LINK_ENTITY_TO_CLASS( ammo_crossbow, CCrossbowAmmo ); + + + +#endif diff --git a/dlls/crowbar.cpp b/dlls/crowbar.cpp new file mode 100644 index 0000000..88d09df --- /dev/null +++ b/dlls/crowbar.cpp @@ -0,0 +1,318 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "gamerules.h" + + +#define CROWBAR_BODYHIT_VOLUME 128 +#define CROWBAR_WALLHIT_VOLUME 512 + +LINK_ENTITY_TO_CLASS( weapon_crowbar, CCrowbar ); + + + +enum gauss_e { + CROWBAR_IDLE = 0, + CROWBAR_DRAW, + CROWBAR_HOLSTER, + CROWBAR_ATTACK1HIT, + CROWBAR_ATTACK1MISS, + CROWBAR_ATTACK2MISS, + CROWBAR_ATTACK2HIT, + CROWBAR_ATTACK3MISS, + CROWBAR_ATTACK3HIT +}; + + +void CCrowbar::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_CROWBAR; + SET_MODEL(ENT(pev), "models/w_crowbar.mdl"); + m_iClip = -1; + + FallInit();// get ready to fall down. +} + + +void CCrowbar::Precache( void ) +{ + PRECACHE_MODEL("models/v_crowbar.mdl"); + PRECACHE_MODEL("models/w_crowbar.mdl"); + PRECACHE_MODEL("models/p_crowbar.mdl"); + PRECACHE_SOUND("weapons/cbar_hit1.wav"); + PRECACHE_SOUND("weapons/cbar_hit2.wav"); + PRECACHE_SOUND("weapons/cbar_hitbod1.wav"); + PRECACHE_SOUND("weapons/cbar_hitbod2.wav"); + PRECACHE_SOUND("weapons/cbar_hitbod3.wav"); + PRECACHE_SOUND("weapons/cbar_miss1.wav"); + + m_usCrowbar = PRECACHE_EVENT ( 1, "events/crowbar.sc" ); +} + +int CCrowbar::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = NULL; + p->iMaxAmmo1 = -1; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 0; + p->iPosition = 0; + p->iId = WEAPON_CROWBAR; + p->iWeight = CROWBAR_WEIGHT; + return 1; +} + + + +BOOL CCrowbar::Deploy( ) +{ + return DefaultDeploy( "models/v_crowbar.mdl", "models/p_crowbar.mdl", CROWBAR_DRAW, "crowbar" ); +} + +void CCrowbar::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + SendWeaponAnim( CROWBAR_HOLSTER ); +} + + +void FindHullIntersection( const Vector &vecSrc, TraceResult &tr, float *mins, float *maxs, edict_t *pEntity ) +{ + int i, j, k; + float distance; + float *minmaxs[2] = {mins, maxs}; + TraceResult tmpTrace; + Vector vecHullEnd = tr.vecEndPos; + Vector vecEnd; + + distance = 1e6f; + + vecHullEnd = vecSrc + ((vecHullEnd - vecSrc)*2); + UTIL_TraceLine( vecSrc, vecHullEnd, dont_ignore_monsters, pEntity, &tmpTrace ); + if ( tmpTrace.flFraction < 1.0 ) + { + tr = tmpTrace; + return; + } + + for ( i = 0; i < 2; i++ ) + { + for ( j = 0; j < 2; j++ ) + { + for ( k = 0; k < 2; k++ ) + { + vecEnd.x = vecHullEnd.x + minmaxs[i][0]; + vecEnd.y = vecHullEnd.y + minmaxs[j][1]; + vecEnd.z = vecHullEnd.z + minmaxs[k][2]; + + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, pEntity, &tmpTrace ); + if ( tmpTrace.flFraction < 1.0 ) + { + float thisDistance = (tmpTrace.vecEndPos - vecSrc).Length(); + if ( thisDistance < distance ) + { + tr = tmpTrace; + distance = thisDistance; + } + } + } + } + } +} + + +void CCrowbar::PrimaryAttack() +{ + if (! Swing( 1 )) + { + SetThink( &CCrowbar::SwingAgain ); + pev->nextthink = gpGlobals->time + 0.1; + } +} + + +void CCrowbar::Smack( ) +{ + DecalGunshot( &m_trHit, BULLET_PLAYER_CROWBAR ); +} + + +void CCrowbar::SwingAgain( void ) +{ + Swing( 0 ); +} + + +int CCrowbar::Swing( int fFirst ) +{ + int fDidHit = FALSE; + + TraceResult tr; + + UTIL_MakeVectors (m_pPlayer->pev->v_angle); + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecEnd = vecSrc + gpGlobals->v_forward * 32; + + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, ENT( m_pPlayer->pev ), &tr ); + +#ifndef CLIENT_DLL + if ( tr.flFraction >= 1.0 ) + { + UTIL_TraceHull( vecSrc, vecEnd, dont_ignore_monsters, head_hull, ENT( m_pPlayer->pev ), &tr ); + if ( tr.flFraction < 1.0 ) + { + // Calculate the point of intersection of the line (or hull) and the object we hit + // This is and approximation of the "best" intersection + CBaseEntity *pHit = CBaseEntity::Instance( tr.pHit ); + if ( !pHit || pHit->IsBSPModel() ) + FindHullIntersection( vecSrc, tr, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, m_pPlayer->edict() ); + vecEnd = tr.vecEndPos; // This is the point on the actual surface (the hull could have hit space) + } + } +#endif + + PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_usCrowbar, + 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0, 0, 0, + 0.0, 0, 0.0 ); + + + if ( tr.flFraction >= 1.0 ) + { + if (fFirst) + { + // miss + m_flNextPrimaryAttack = GetNextAttackDelay(0.5); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + } + } + else + { + switch( ((m_iSwing++) % 2) + 1 ) + { + case 0: + SendWeaponAnim( CROWBAR_ATTACK1HIT ); break; + case 1: + SendWeaponAnim( CROWBAR_ATTACK2HIT ); break; + case 2: + SendWeaponAnim( CROWBAR_ATTACK3HIT ); break; + } + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + +#ifndef CLIENT_DLL + + // hit + fDidHit = TRUE; + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + ClearMultiDamage( ); + + if ( (m_flNextPrimaryAttack + 1 < UTIL_WeaponTimeBase() ) || g_pGameRules->IsMultiplayer() ) + { + // first swing does full damage + pEntity->TraceAttack(m_pPlayer->pev, gSkillData.plrDmgCrowbar, gpGlobals->v_forward, &tr, DMG_CLUB ); + } + else + { + // subsequent swings do half + pEntity->TraceAttack(m_pPlayer->pev, gSkillData.plrDmgCrowbar / 2, gpGlobals->v_forward, &tr, DMG_CLUB ); + } + ApplyMultiDamage( m_pPlayer->pev, m_pPlayer->pev ); + + // play thwack, smack, or dong sound + float flVol = 1.0; + int fHitWorld = TRUE; + + if (pEntity) + { + if ( pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE ) + { + // play thwack or smack sound + switch( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hitbod1.wav", 1, ATTN_NORM); break; + case 1: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hitbod2.wav", 1, ATTN_NORM); break; + case 2: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hitbod3.wav", 1, ATTN_NORM); break; + } + m_pPlayer->m_iWeaponVolume = CROWBAR_BODYHIT_VOLUME; + if ( !pEntity->IsAlive() ) + return TRUE; + else + flVol = 0.1; + + fHitWorld = FALSE; + } + } + + // play texture hit sound + // UNDONE: Calculate the correct point of intersection when we hit with the hull instead of the line + + if (fHitWorld) + { + float fvolbar = TEXTURETYPE_PlaySound(&tr, vecSrc, vecSrc + (vecEnd-vecSrc)*2, BULLET_PLAYER_CROWBAR); + + if ( g_pGameRules->IsMultiplayer() ) + { + // override the volume here, cause we don't play texture sounds in multiplayer, + // and fvolbar is going to be 0 from the above call. + + fvolbar = 1; + } + + // also play crowbar strike + switch( RANDOM_LONG(0,1) ) + { + case 0: + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hit1.wav", fvolbar, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + break; + case 1: + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hit2.wav", fvolbar, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + break; + } + + // delay the decal a bit + m_trHit = tr; + } + + m_pPlayer->m_iWeaponVolume = flVol * CROWBAR_WALLHIT_VOLUME; +#endif + m_flNextPrimaryAttack = GetNextAttackDelay(0.25); + + SetThink( &CCrowbar::Smack ); + pev->nextthink = UTIL_WeaponTimeBase() + 0.2; + + + } + return fDidHit; +} + + + diff --git a/dlls/decals.h b/dlls/decals.h new file mode 100644 index 0000000..e18ab71 --- /dev/null +++ b/dlls/decals.h @@ -0,0 +1,75 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef DECALS_H +#define DECALS_H + +// +// Dynamic Decals +// +enum decal_e +{ + DECAL_GUNSHOT1 = 0, + DECAL_GUNSHOT2, + DECAL_GUNSHOT3, + DECAL_GUNSHOT4, + DECAL_GUNSHOT5, + DECAL_LAMBDA1, + DECAL_LAMBDA2, + DECAL_LAMBDA3, + DECAL_LAMBDA4, + DECAL_LAMBDA5, + DECAL_LAMBDA6, + DECAL_SCORCH1, + DECAL_SCORCH2, + DECAL_BLOOD1, + DECAL_BLOOD2, + DECAL_BLOOD3, + DECAL_BLOOD4, + DECAL_BLOOD5, + DECAL_BLOOD6, + DECAL_YBLOOD1, + DECAL_YBLOOD2, + DECAL_YBLOOD3, + DECAL_YBLOOD4, + DECAL_YBLOOD5, + DECAL_YBLOOD6, + DECAL_GLASSBREAK1, + DECAL_GLASSBREAK2, + DECAL_GLASSBREAK3, + DECAL_BIGSHOT1, + DECAL_BIGSHOT2, + DECAL_BIGSHOT3, + DECAL_BIGSHOT4, + DECAL_BIGSHOT5, + DECAL_SPIT1, + DECAL_SPIT2, + DECAL_BPROOF1, // Bulletproof glass decal + DECAL_GARGSTOMP1, // Gargantua stomp crack + DECAL_SMALLSCORCH1, // Small scorch mark + DECAL_SMALLSCORCH2, // Small scorch mark + DECAL_SMALLSCORCH3, // Small scorch mark + DECAL_MOMMABIRTH, // Big momma birth splatter + DECAL_MOMMASPLAT, +}; + +typedef struct +{ + char *name; + int index; +} DLL_DECALLIST; + +extern DLL_DECALLIST gDecals[]; + +#endif // DECALS_H diff --git a/dlls/defaultai.cpp b/dlls/defaultai.cpp new file mode 100644 index 0000000..6f231ad --- /dev/null +++ b/dlls/defaultai.cpp @@ -0,0 +1,1232 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Default behaviors. +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "defaultai.h" +#include "soundent.h" +#include "nodes.h" +#include "scripted.h" + +//========================================================= +// Fail +//========================================================= +Task_t tlFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slFail[] = +{ + { + tlFail, + ARRAYSIZE ( tlFail ), + bits_COND_CAN_ATTACK, + 0, + "Fail" + }, +}; + +//========================================================= +// Idle Schedules +//========================================================= +Task_t tlIdleStand1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)5 },// repick IDLESTAND every five seconds. gives us a chance to pick an active idle, fidget, etc. +}; + +Schedule_t slIdleStand[] = +{ + { + tlIdleStand1, + ARRAYSIZE ( tlIdleStand1 ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_FEAR | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL_FOOD | + bits_COND_SMELL | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags + bits_SOUND_WORLD | + bits_SOUND_PLAYER | + bits_SOUND_DANGER | + + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "IdleStand" + }, +}; + +Schedule_t slIdleTrigger[] = +{ + { + tlIdleStand1, + ARRAYSIZE ( tlIdleStand1 ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "Idle Trigger" + }, +}; + + +Task_t tlIdleWalk1[] = +{ + { TASK_WALK_PATH, (float)9999 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slIdleWalk[] = +{ + { + tlIdleWalk1, + ARRAYSIZE ( tlIdleWalk1 ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL_FOOD | + bits_COND_SMELL | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags + + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "Idle Walk" + }, +}; + +//========================================================= +// Ambush - monster stands in place and waits for a new +// enemy, or chance to attack an existing enemy. +//========================================================= +Task_t tlAmbush[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_INDEFINITE, (float)0 }, +}; + +Schedule_t slAmbush[] = +{ + { + tlAmbush, + ARRAYSIZE ( tlAmbush ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED, + + 0, + "Ambush" + }, +}; + +//========================================================= +// ActiveIdle schedule - !!!BUGBUG - if this schedule doesn't +// complete on its own, the monster's HintNode will not be +// cleared, and the rest of the monster's group will avoid +// that node because they think the group member that was +// previously interrupted is still using that node to active +// idle. +///========================================================= +Task_t tlActiveIdle[] = +{ + { TASK_FIND_HINTNODE, (float)0 }, + { TASK_GET_PATH_TO_HINTNODE, (float)0 }, + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_FACE_HINTNODE, (float)0 }, + { TASK_PLAY_ACTIVE_IDLE, (float)0 }, + { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, + { TASK_CLEAR_HINTNODE, (float)0 }, +}; + +Schedule_t slActiveIdle[] = +{ + { + tlActiveIdle, + ARRAYSIZE( tlActiveIdle ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED | + bits_COND_HEAR_SOUND, + + bits_SOUND_COMBAT | + bits_SOUND_WORLD | + bits_SOUND_PLAYER | + bits_SOUND_DANGER, + "Active Idle" + } +}; + +//========================================================= +// Wake Schedules +//========================================================= +Task_t tlWakeAngry1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_SOUND_WAKE, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, +}; + +Schedule_t slWakeAngry[] = +{ + { + tlWakeAngry1, + ARRAYSIZE ( tlWakeAngry1 ), + 0, + 0, + "Wake Angry" + } +}; + +//========================================================= +// AlertFace Schedules +//========================================================= +Task_t tlAlertFace1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_IDEAL, (float)0 }, +}; + +Schedule_t slAlertFace[] = +{ + { + tlAlertFace1, + ARRAYSIZE ( tlAlertFace1 ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_FEAR | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED, + 0, + "Alert Face" + }, +}; + +//========================================================= +// AlertSmallFlinch Schedule - shot, but didn't see attacker, +// flinch then face +//========================================================= +Task_t tlAlertSmallFlinch[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_REMEMBER, (float)bits_MEMORY_FLINCHED }, + { TASK_SMALL_FLINCH, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_ALERT_FACE }, +}; + +Schedule_t slAlertSmallFlinch[] = +{ + { + tlAlertSmallFlinch, + ARRAYSIZE ( tlAlertSmallFlinch ), + 0, + 0, + "Alert Small Flinch" + }, +}; + +//========================================================= +// AlertIdle Schedules +//========================================================= +Task_t tlAlertStand1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)20 }, + { TASK_SUGGEST_STATE, (float)MONSTERSTATE_IDLE }, +}; + +Schedule_t slAlertStand[] = +{ + { + tlAlertStand1, + ARRAYSIZE ( tlAlertStand1 ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_ENEMY | + bits_COND_SEE_FEAR | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED | + bits_COND_SMELL | + bits_COND_SMELL_FOOD | + bits_COND_HEAR_SOUND, + + bits_SOUND_COMBAT |// sound flags + bits_SOUND_WORLD | + bits_SOUND_PLAYER | + bits_SOUND_DANGER | + + bits_SOUND_MEAT |// scent flags + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "Alert Stand" + }, +}; + +//========================================================= +// InvestigateSound - sends a monster to the location of the +// sound that was just heard, to check things out. +//========================================================= +Task_t tlInvestigateSound[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_GET_PATH_TO_BESTSOUND, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_IDLE }, + { TASK_WAIT, (float)10 }, + { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, +}; + +Schedule_t slInvestigateSound[] = +{ + { + tlInvestigateSound, + ARRAYSIZE ( tlInvestigateSound ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_FEAR | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "InvestigateSound" + }, +}; + +//========================================================= +// CombatIdle Schedule +//========================================================= +Task_t tlCombatStand1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_INDEFINITE, (float)0 }, +}; + +Schedule_t slCombatStand[] = +{ + { + tlCombatStand1, + ARRAYSIZE ( tlCombatStand1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_CAN_ATTACK, + 0, + "Combat Stand" + }, +}; + +//========================================================= +// CombatFace Schedule +//========================================================= +Task_t tlCombatFace1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_ENEMY, (float)0 }, +}; + +Schedule_t slCombatFace[] = +{ + { + tlCombatFace1, + ARRAYSIZE ( tlCombatFace1 ), + bits_COND_CAN_ATTACK | + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD, + 0, + "Combat Face" + }, +}; + +//========================================================= +// Standoff schedule. Used in combat when a monster is +// hiding in cover or the enemy has moved out of sight. +// Should we look around in this schedule? +//========================================================= +Task_t tlStandoff[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, +}; + +Schedule_t slStandoff[] = +{ + { + tlStandoff, + ARRAYSIZE ( tlStandoff ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_ENEMY_DEAD | + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Standoff" + } +}; + +//========================================================= +// Arm weapon (draw gun) +//========================================================= +Task_t tlArmWeapon[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, (float) ACT_ARM } +}; + +Schedule_t slArmWeapon[] = +{ + { + tlArmWeapon, + ARRAYSIZE ( tlArmWeapon ), + 0, + 0, + "Arm Weapon" + } +}; + +//========================================================= +// reload schedule +//========================================================= +Task_t tlReload[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, float(ACT_RELOAD) }, +}; + +Schedule_t slReload[] = +{ + { + tlReload, + ARRAYSIZE ( tlReload ), + bits_COND_HEAVY_DAMAGE, + 0, + "Reload" + } +}; + +//========================================================= +// Attack Schedules +//========================================================= + +// primary range attack +Task_t tlRangeAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slRangeAttack1[] = +{ + { + tlRangeAttack1, + ARRAYSIZE ( tlRangeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Range Attack1" + }, +}; + +// secondary range attack +Task_t tlRangeAttack2[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK2, (float)0 }, +}; + +Schedule_t slRangeAttack2[] = +{ + { + tlRangeAttack2, + ARRAYSIZE ( tlRangeAttack2 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Range Attack2" + }, +}; + +// primary melee attack +Task_t tlPrimaryMeleeAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MELEE_ATTACK1, (float)0 }, +}; + +Schedule_t slPrimaryMeleeAttack[] = +{ + { + tlPrimaryMeleeAttack1, + ARRAYSIZE ( tlPrimaryMeleeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED, + 0, + "Primary Melee Attack" + }, +}; + +// secondary melee attack +Task_t tlSecondaryMeleeAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MELEE_ATTACK2, (float)0 }, +}; + +Schedule_t slSecondaryMeleeAttack[] = +{ + { + tlSecondaryMeleeAttack1, + ARRAYSIZE ( tlSecondaryMeleeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED, + 0, + "Secondary Melee Attack" + }, +}; + +// special attack1 +Task_t tlSpecialAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SPECIAL_ATTACK1, (float)0 }, +}; + +Schedule_t slSpecialAttack1[] = +{ + { + tlSpecialAttack1, + ARRAYSIZE ( tlSpecialAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Special Attack1" + }, +}; + +// special attack2 +Task_t tlSpecialAttack2[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SPECIAL_ATTACK2, (float)0 }, +}; + +Schedule_t slSpecialAttack2[] = +{ + { + tlSpecialAttack2, + ARRAYSIZE ( tlSpecialAttack2 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Special Attack2" + }, +}; + +// Chase enemy schedule +Task_t tlChaseEnemy1[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_CHASE_ENEMY_FAILED }, + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slChaseEnemy[] = +{ + { + tlChaseEnemy1, + ARRAYSIZE ( tlChaseEnemy1 ), + bits_COND_NEW_ENEMY | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_TASK_FAILED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Chase Enemy" + }, +}; + + +// Chase enemy failure schedule +Task_t tlChaseEnemyFailed[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, +// { TASK_TURN_LEFT, (float)179 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slChaseEnemyFailed[] = +{ + { + tlChaseEnemyFailed, + ARRAYSIZE ( tlChaseEnemyFailed ), + bits_COND_NEW_ENEMY | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "tlChaseEnemyFailed" + }, +}; + + +//========================================================= +// small flinch, played when minor damage is taken. +//========================================================= +Task_t tlSmallFlinch[] = +{ + { TASK_REMEMBER, (float)bits_MEMORY_FLINCHED }, + { TASK_STOP_MOVING, 0 }, + { TASK_SMALL_FLINCH, 0 }, +}; + +Schedule_t slSmallFlinch[] = +{ + { + tlSmallFlinch, + ARRAYSIZE ( tlSmallFlinch ), + 0, + 0, + "Small Flinch" + }, +}; + +//========================================================= +// Die! +//========================================================= +Task_t tlDie1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SOUND_DIE, (float)0 }, + { TASK_DIE, (float)0 }, +}; + +Schedule_t slDie[] = +{ + { + tlDie1, + ARRAYSIZE( tlDie1 ), + 0, + 0, + "Die" + }, +}; + +//========================================================= +// Victory Dance +//========================================================= +Task_t tlVictoryDance[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_WAIT, (float)0 }, +}; + +Schedule_t slVictoryDance[] = +{ + { + tlVictoryDance, + ARRAYSIZE( tlVictoryDance ), + 0, + 0, + "Victory Dance" + }, +}; + +//========================================================= +// BarnacleVictimGrab - barnacle tongue just hit the monster, +// so play a hit animation, then play a cycling pull animation +// as the creature is hoisting the monster. +//========================================================= +Task_t tlBarnacleVictimGrab[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_BARNACLE_HIT }, + { TASK_SET_ACTIVITY, (float)ACT_BARNACLE_PULL }, + { TASK_WAIT_INDEFINITE, (float)0 },// just cycle barnacle pull anim while barnacle hoists. +}; + +Schedule_t slBarnacleVictimGrab[] = +{ + { + tlBarnacleVictimGrab, + ARRAYSIZE ( tlBarnacleVictimGrab ), + 0, + 0, + "Barnacle Victim" + } +}; + +//========================================================= +// BarnacleVictimChomp - barnacle has pulled the prey to its +// mouth. Victim should play the BARNCLE_CHOMP animation +// once, then loop the BARNACLE_CHEW animation indefinitely +//========================================================= +Task_t tlBarnacleVictimChomp[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_BARNACLE_CHOMP }, + { TASK_SET_ACTIVITY, (float)ACT_BARNACLE_CHEW }, + { TASK_WAIT_INDEFINITE, (float)0 },// just cycle barnacle pull anim while barnacle hoists. +}; + +Schedule_t slBarnacleVictimChomp[] = +{ + { + tlBarnacleVictimChomp, + ARRAYSIZE ( tlBarnacleVictimChomp ), + 0, + 0, + "Barnacle Chomp" + } +}; + + +// Universal Error Schedule +Task_t tlError[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_WAIT_INDEFINITE, (float)0 }, +}; + +Schedule_t slError[] = +{ + { + tlError, + ARRAYSIZE ( tlError ), + 0, + 0, + "Error" + }, +}; + +Task_t tlScriptedWalk[] = +{ + { TASK_WALK_TO_TARGET, (float)TARGET_MOVE_SCRIPTED }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLANT_ON_SCRIPT, (float)0 }, + { TASK_FACE_SCRIPT, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_ENABLE_SCRIPT, (float)0 }, + { TASK_WAIT_FOR_SCRIPT, (float)0 }, + { TASK_PLAY_SCRIPT, (float)0 }, +}; + +Schedule_t slWalkToScript[] = +{ + { + tlScriptedWalk, + ARRAYSIZE ( tlScriptedWalk ), + SCRIPT_BREAK_CONDITIONS, + 0, + "WalkToScript" + }, +}; + + +Task_t tlScriptedRun[] = +{ + { TASK_RUN_TO_TARGET, (float)TARGET_MOVE_SCRIPTED }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLANT_ON_SCRIPT, (float)0 }, + { TASK_FACE_SCRIPT, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_ENABLE_SCRIPT, (float)0 }, + { TASK_WAIT_FOR_SCRIPT, (float)0 }, + { TASK_PLAY_SCRIPT, (float)0 }, +}; + +Schedule_t slRunToScript[] = +{ + { + tlScriptedRun, + ARRAYSIZE ( tlScriptedRun ), + SCRIPT_BREAK_CONDITIONS, + 0, + "RunToScript" + }, +}; + +Task_t tlScriptedWait[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_WAIT_FOR_SCRIPT, (float)0 }, + { TASK_PLAY_SCRIPT, (float)0 }, +}; + +Schedule_t slWaitScript[] = +{ + { + tlScriptedWait, + ARRAYSIZE ( tlScriptedWait ), + SCRIPT_BREAK_CONDITIONS, + 0, + "WaitForScript" + }, +}; + +Task_t tlScriptedFace[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_SCRIPT, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_WAIT_FOR_SCRIPT, (float)0 }, + { TASK_PLAY_SCRIPT, (float)0 }, +}; + +Schedule_t slFaceScript[] = +{ + { + tlScriptedFace, + ARRAYSIZE ( tlScriptedFace ), + SCRIPT_BREAK_CONDITIONS, + 0, + "FaceScript" + }, +}; + +//========================================================= +// Cower - this is what is usually done when attempts +// to escape danger fail. +//========================================================= +Task_t tlCower[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_COWER }, +}; + +Schedule_t slCower[] = +{ + { + tlCower, + ARRAYSIZE ( tlCower ), + 0, + 0, + "Cower" + }, +}; + +//========================================================= +// move away from where you're currently standing. +//========================================================= +Task_t tlTakeCoverFromOrigin[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_ORIGIN, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_TURN_LEFT, (float)179 }, +}; + +Schedule_t slTakeCoverFromOrigin[] = +{ + { + tlTakeCoverFromOrigin, + ARRAYSIZE ( tlTakeCoverFromOrigin ), + bits_COND_NEW_ENEMY, + 0, + "TakeCoverFromOrigin" + }, +}; + +//========================================================= +// hide from the loudest sound source +//========================================================= +Task_t tlTakeCoverFromBestSound[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_BEST_SOUND, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_TURN_LEFT, (float)179 }, +}; + +Schedule_t slTakeCoverFromBestSound[] = +{ + { + tlTakeCoverFromBestSound, + ARRAYSIZE ( tlTakeCoverFromBestSound ), + bits_COND_NEW_ENEMY, + 0, + "TakeCoverFromBestSound" + }, +}; + +//========================================================= +// Take cover from enemy! Tries lateral cover before node +// cover! +//========================================================= +Task_t tlTakeCoverFromEnemy[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, +// { TASK_TURN_LEFT, (float)179 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slTakeCoverFromEnemy[] = +{ + { + tlTakeCoverFromEnemy, + ARRAYSIZE ( tlTakeCoverFromEnemy ), + bits_COND_NEW_ENEMY, + 0, + "tlTakeCoverFromEnemy" + }, +}; + +Schedule_t *CBaseMonster::m_scheduleList[] = +{ + slIdleStand, + slIdleTrigger, + slIdleWalk, + slAmbush, + slActiveIdle, + slWakeAngry, + slAlertFace, + slAlertSmallFlinch, + slAlertStand, + slInvestigateSound, + slCombatStand, + slCombatFace, + slStandoff, + slArmWeapon, + slReload, + slRangeAttack1, + slRangeAttack2, + slPrimaryMeleeAttack, + slSecondaryMeleeAttack, + slSpecialAttack1, + slSpecialAttack2, + slChaseEnemy, + slChaseEnemyFailed, + slSmallFlinch, + slDie, + slVictoryDance, + slBarnacleVictimGrab, + slBarnacleVictimChomp, + slError, + slWalkToScript, + slRunToScript, + slWaitScript, + slFaceScript, + slCower, + slTakeCoverFromOrigin, + slTakeCoverFromBestSound, + slTakeCoverFromEnemy, + slFail +}; + +Schedule_t *CBaseMonster::ScheduleFromName( const char *pName ) +{ + return ScheduleInList( pName, m_scheduleList, ARRAYSIZE(m_scheduleList) ); +} + + +Schedule_t *CBaseMonster :: ScheduleInList( const char *pName, Schedule_t **pList, int listCount ) +{ + int i; + + if ( !pName ) + { + ALERT( at_console, "%s set to unnamed schedule!\n", STRING(pev->classname) ); + return NULL; + } + + + for ( i = 0; i < listCount; i++ ) + { + if ( !pList[i]->pName ) + { + ALERT( at_console, "Unnamed schedule!\n" ); + continue; + } + if ( stricmp( pName, pList[i]->pName ) == 0 ) + return pList[i]; + } + return NULL; +} + +//========================================================= +// GetScheduleOfType - returns a pointer to one of the +// monster's available schedules of the indicated type. +//========================================================= +Schedule_t* CBaseMonster :: GetScheduleOfType ( int Type ) +{ +// ALERT ( at_console, "Sched Type:%d\n", Type ); + switch ( Type ) + { + // This is the schedule for scripted sequences AND scripted AI + case SCHED_AISCRIPT: + { + ASSERT( m_pCine != NULL ); + if ( !m_pCine ) + { + ALERT( at_aiconsole, "Script failed for %s\n", STRING(pev->classname) ); + CineCleanup(); + return GetScheduleOfType( SCHED_IDLE_STAND ); + } +// else +// ALERT( at_aiconsole, "Starting script %s for %s\n", STRING( m_pCine->m_iszPlay ), STRING(pev->classname) ); + + switch ( m_pCine->m_fMoveTo ) + { + case 0: + case 4: + return slWaitScript; + case 1: + return slWalkToScript; + case 2: + return slRunToScript; + case 5: + return slFaceScript; + } + break; + } + case SCHED_IDLE_STAND: + { + if ( RANDOM_LONG(0,14) == 0 && FCanActiveIdle() ) + { + return &slActiveIdle[ 0 ]; + } + + return &slIdleStand[ 0 ]; + } + case SCHED_IDLE_WALK: + { + return &slIdleWalk[ 0 ]; + } + case SCHED_WAIT_TRIGGER: + { + return &slIdleTrigger[ 0 ]; + } + case SCHED_WAKE_ANGRY: + { + return &slWakeAngry[ 0 ]; + } + case SCHED_ALERT_FACE: + { + return &slAlertFace[ 0 ]; + } + case SCHED_ALERT_STAND: + { + return &slAlertStand[ 0 ]; + } + case SCHED_COMBAT_STAND: + { + return &slCombatStand[ 0 ]; + } + case SCHED_COMBAT_FACE: + { + return &slCombatFace[ 0 ]; + } + case SCHED_CHASE_ENEMY: + { + return &slChaseEnemy[ 0 ]; + } + case SCHED_CHASE_ENEMY_FAILED: + { + return &slFail[ 0 ]; + } + case SCHED_SMALL_FLINCH: + { + return &slSmallFlinch[ 0 ]; + } + case SCHED_ALERT_SMALL_FLINCH: + { + return &slAlertSmallFlinch[ 0 ]; + } + case SCHED_RELOAD: + { + return &slReload[ 0 ]; + } + case SCHED_ARM_WEAPON: + { + return &slArmWeapon[ 0 ]; + } + case SCHED_STANDOFF: + { + return &slStandoff[ 0 ]; + } + case SCHED_RANGE_ATTACK1: + { + return &slRangeAttack1[ 0 ]; + } + case SCHED_RANGE_ATTACK2: + { + return &slRangeAttack2[ 0 ]; + } + case SCHED_MELEE_ATTACK1: + { + return &slPrimaryMeleeAttack[ 0 ]; + } + case SCHED_MELEE_ATTACK2: + { + return &slSecondaryMeleeAttack[ 0 ]; + } + case SCHED_SPECIAL_ATTACK1: + { + return &slSpecialAttack1[ 0 ]; + } + case SCHED_SPECIAL_ATTACK2: + { + return &slSpecialAttack2[ 0 ]; + } + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + { + return &slTakeCoverFromBestSound[ 0 ]; + } + case SCHED_TAKE_COVER_FROM_ENEMY: + { + return &slTakeCoverFromEnemy[ 0 ]; + } + case SCHED_COWER: + { + return &slCower[ 0 ]; + } + case SCHED_AMBUSH: + { + return &slAmbush[ 0 ]; + } + case SCHED_BARNACLE_VICTIM_GRAB: + { + return &slBarnacleVictimGrab[ 0 ]; + } + case SCHED_BARNACLE_VICTIM_CHOMP: + { + return &slBarnacleVictimChomp[ 0 ]; + } + case SCHED_INVESTIGATE_SOUND: + { + return &slInvestigateSound[ 0 ]; + } + case SCHED_DIE: + { + return &slDie[ 0 ]; + } + case SCHED_TAKE_COVER_FROM_ORIGIN: + { + return &slTakeCoverFromOrigin[ 0 ]; + } + case SCHED_VICTORY_DANCE: + { + return &slVictoryDance[ 0 ]; + } + case SCHED_FAIL: + { + return slFail; + } + default: + { + ALERT ( at_console, "GetScheduleOfType()\nNo CASE for Schedule Type %d!\n", Type ); + + return &slIdleStand[ 0 ]; + break; + } + } + + return NULL; +} diff --git a/dlls/defaultai.h b/dlls/defaultai.h new file mode 100644 index 0000000..32657ab --- /dev/null +++ b/dlls/defaultai.h @@ -0,0 +1,98 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef DEFAULTAI_H +#define DEFAULTAI_H + +//========================================================= +// Failed +//========================================================= +extern Schedule_t slFail[]; + +//========================================================= +// Idle Schedules +//========================================================= +extern Schedule_t slIdleStand[]; +extern Schedule_t slIdleTrigger[]; +extern Schedule_t slIdleWalk[]; + +//========================================================= +// Wake Schedules +//========================================================= +extern Schedule_t slWakeAngry[]; + +//========================================================= +// AlertTurn Schedules +//========================================================= +extern Schedule_t slAlertFace[]; + +//========================================================= +// AlertIdle Schedules +//========================================================= +extern Schedule_t slAlertStand[]; + +//========================================================= +// CombatIdle Schedule +//========================================================= +extern Schedule_t slCombatStand[]; + +//========================================================= +// CombatFace Schedule +//========================================================= +extern Schedule_t slCombatFace[]; + +//========================================================= +// reload schedule +//========================================================= +extern Schedule_t slReload[]; + +//========================================================= +// Attack Schedules +//========================================================= + +extern Schedule_t slRangeAttack1[]; +extern Schedule_t slRangeAttack2[]; + +extern Schedule_t slTakeCoverFromBestSound[]; + +// primary melee attack +extern Schedule_t slMeleeAttack[]; + +// Chase enemy schedule +extern Schedule_t slChaseEnemy[]; + +//========================================================= +// small flinch, used when a relatively minor bit of damage +// is inflicted. +//========================================================= +extern Schedule_t slSmallFlinch[]; + +//========================================================= +// Die! +//========================================================= +extern Schedule_t slDie[]; + +//========================================================= +// Universal Error Schedule +//========================================================= +extern Schedule_t slError[]; + +//========================================================= +// Scripted sequences +//========================================================= +extern Schedule_t slWalkToScript[]; +extern Schedule_t slRunToScript[]; +extern Schedule_t slWaitScript[]; + +#endif // DEFAULTAI_H diff --git a/dlls/doors.cpp b/dlls/doors.cpp new file mode 100644 index 0000000..e8eeb1c --- /dev/null +++ b/dlls/doors.cpp @@ -0,0 +1,1052 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== doors.cpp ======================================================== + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "doors.h" + + +extern void SetMovedir(entvars_t* ev); + +#define noiseMoving noise1 +#define noiseArrived noise2 + +class CBaseDoor : public CBaseToggle +{ +public: + void Spawn( void ); + void Precache( void ); + virtual void KeyValue( KeyValueData *pkvd ); + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void Blocked( CBaseEntity *pOther ); + + + virtual int ObjectCaps( void ) + { + if (pev->spawnflags & SF_ITEM_USE_ONLY) + return (CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_IMPULSE_USE; + else + return (CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION); + }; + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + virtual void SetToggleState( int state ); + + // used to selectivly override defaults + void EXPORT DoorTouch( CBaseEntity *pOther ); + + // local functions + int DoorActivate( ); + void EXPORT DoorGoUp( void ); + void EXPORT DoorGoDown( void ); + void EXPORT DoorHitTop( void ); + void EXPORT DoorHitBottom( void ); + + BYTE m_bHealthValue;// some doors are medi-kit doors, they give players health + + BYTE m_bMoveSnd; // sound a door makes while moving + BYTE m_bStopSnd; // sound a door makes when it stops + + locksound_t m_ls; // door lock sounds + + BYTE m_bLockedSound; // ordinals from entity selection + BYTE m_bLockedSentence; + BYTE m_bUnlockedSound; + BYTE m_bUnlockedSentence; +}; + + +TYPEDESCRIPTION CBaseDoor::m_SaveData[] = +{ + DEFINE_FIELD( CBaseDoor, m_bHealthValue, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bMoveSnd, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bStopSnd, FIELD_CHARACTER ), + + DEFINE_FIELD( CBaseDoor, m_bLockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bLockedSentence, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bUnlockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bUnlockedSentence, FIELD_CHARACTER ), + +}; + +IMPLEMENT_SAVERESTORE( CBaseDoor, CBaseToggle ); + + +#define DOOR_SENTENCEWAIT 6 +#define DOOR_SOUNDWAIT 3 +#define BUTTON_SOUNDWAIT 0.5 + +// play door or button locked or unlocked sounds. +// pass in pointer to valid locksound struct. +// if flocked is true, play 'door is locked' sound, +// otherwise play 'door is unlocked' sound +// NOTE: this routine is shared by doors and buttons + +void PlayLockSounds(entvars_t *pev, locksound_t *pls, int flocked, int fbutton) +{ + // LOCKED SOUND + + // CONSIDER: consolidate the locksound_t struct (all entries are duplicates for lock/unlock) + // CONSIDER: and condense this code. + float flsoundwait; + + if (fbutton) + flsoundwait = BUTTON_SOUNDWAIT; + else + flsoundwait = DOOR_SOUNDWAIT; + + if (flocked) + { + int fplaysound = (pls->sLockedSound && gpGlobals->time > pls->flwaitSound); + int fplaysentence = (pls->sLockedSentence && !pls->bEOFLocked && gpGlobals->time > pls->flwaitSentence); + float fvol; + + if (fplaysound && fplaysentence) + fvol = 0.25; + else + fvol = 1.0; + + // if there is a locked sound, and we've debounced, play sound + if (fplaysound) + { + // play 'door locked' sound + EMIT_SOUND(ENT(pev), CHAN_ITEM, (char*)STRING(pls->sLockedSound), fvol, ATTN_NORM); + pls->flwaitSound = gpGlobals->time + flsoundwait; + } + + // if there is a sentence, we've not played all in list, and we've debounced, play sound + if (fplaysentence) + { + // play next 'door locked' sentence in group + int iprev = pls->iLockedSentence; + + pls->iLockedSentence = SENTENCEG_PlaySequentialSz(ENT(pev), STRING(pls->sLockedSentence), + 0.85, ATTN_NORM, 0, 100, pls->iLockedSentence, FALSE); + pls->iUnlockedSentence = 0; + + // make sure we don't keep calling last sentence in list + pls->bEOFLocked = (iprev == pls->iLockedSentence); + + pls->flwaitSentence = gpGlobals->time + DOOR_SENTENCEWAIT; + } + } + else + { + // UNLOCKED SOUND + + int fplaysound = (pls->sUnlockedSound && gpGlobals->time > pls->flwaitSound); + int fplaysentence = (pls->sUnlockedSentence && !pls->bEOFUnlocked && gpGlobals->time > pls->flwaitSentence); + float fvol; + + // if playing both sentence and sound, lower sound volume so we hear sentence + if (fplaysound && fplaysentence) + fvol = 0.25; + else + fvol = 1.0; + + // play 'door unlocked' sound if set + if (fplaysound) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, (char*)STRING(pls->sUnlockedSound), fvol, ATTN_NORM); + pls->flwaitSound = gpGlobals->time + flsoundwait; + } + + // play next 'door unlocked' sentence in group + if (fplaysentence) + { + int iprev = pls->iUnlockedSentence; + + pls->iUnlockedSentence = SENTENCEG_PlaySequentialSz(ENT(pev), STRING(pls->sUnlockedSentence), + 0.85, ATTN_NORM, 0, 100, pls->iUnlockedSentence, FALSE); + pls->iLockedSentence = 0; + + // make sure we don't keep calling last sentence in list + pls->bEOFUnlocked = (iprev == pls->iUnlockedSentence); + pls->flwaitSentence = gpGlobals->time + DOOR_SENTENCEWAIT; + } + } +} + +// +// Cache user-entity-field values until spawn is called. +// + +void CBaseDoor::KeyValue( KeyValueData *pkvd ) +{ + + if (FStrEq(pkvd->szKeyName, "skin"))//skin is used for content type + { + pev->skin = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "movesnd")) + { + m_bMoveSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "stopsnd")) + { + m_bStopSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "healthvalue")) + { + m_bHealthValue = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sound")) + { + m_bLockedSound = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sentence")) + { + m_bLockedSentence = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sound")) + { + m_bUnlockedSound = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sentence")) + { + m_bUnlockedSentence = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "WaveHeight")) + { + pev->scale = atof(pkvd->szValue) * (1.0/8.0); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +/*QUAKED func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK TOGGLE +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. + +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). + +"angle" determines the opening direction +"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" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (2 default) +"sounds" +0) no sound +1) stone +2) base +3) stone chain +4) screechy metal +*/ + +LINK_ENTITY_TO_CLASS( func_door, CBaseDoor ); +// +// func_water - same as a door. +// +LINK_ENTITY_TO_CLASS( func_water, CBaseDoor ); + + +void CBaseDoor::Spawn( ) +{ + Precache(); + SetMovedir (pev); + + if ( pev->skin == 0 ) + {//normal door + if ( FBitSet (pev->spawnflags, SF_DOOR_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + } + else + {// special contents + pev->solid = SOLID_NOT; + SetBits( pev->spawnflags, SF_DOOR_SILENT ); // water is silent for now + } + + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + if (pev->speed == 0) + pev->speed = 100; + + m_vecPosition1 = pev->origin; + // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big + m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); + ASSERTSZ(m_vecPosition1 != m_vecPosition2, "door start/end positions are equal"); + if ( FBitSet (pev->spawnflags, SF_DOOR_START_OPEN) ) + { // swap pos1 and pos2, put door at pos2 + UTIL_SetOrigin(pev, m_vecPosition2); + m_vecPosition2 = m_vecPosition1; + m_vecPosition1 = pev->origin; + } + + m_toggle_state = TS_AT_BOTTOM; + + // if the door is flagged for USE button activation only, use NULL touch function + if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + { + SetTouch ( NULL ); + } + else // touchable button + SetTouch( &CBaseDoor::DoorTouch ); +} + + +void CBaseDoor :: SetToggleState( int state ) +{ + if ( state == TS_AT_TOP ) + UTIL_SetOrigin( pev, m_vecPosition2 ); + else + UTIL_SetOrigin( pev, m_vecPosition1 ); +} + + +void CBaseDoor::Precache( void ) +{ + char *pszSound; + +// set the door's "in-motion" sound + switch (m_bMoveSnd) + { + case 0: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("doors/doormove1.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove1.wav"); + break; + case 2: + PRECACHE_SOUND ("doors/doormove2.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove2.wav"); + break; + case 3: + PRECACHE_SOUND ("doors/doormove3.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove3.wav"); + break; + case 4: + PRECACHE_SOUND ("doors/doormove4.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove4.wav"); + break; + case 5: + PRECACHE_SOUND ("doors/doormove5.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove5.wav"); + break; + case 6: + PRECACHE_SOUND ("doors/doormove6.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove6.wav"); + break; + case 7: + PRECACHE_SOUND ("doors/doormove7.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove7.wav"); + break; + case 8: + PRECACHE_SOUND ("doors/doormove8.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove8.wav"); + break; + case 9: + PRECACHE_SOUND ("doors/doormove9.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove9.wav"); + break; + case 10: + PRECACHE_SOUND ("doors/doormove10.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove10.wav"); + break; + default: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + } + +// set the door's 'reached destination' stop sound + switch (m_bStopSnd) + { + case 0: + pev->noiseArrived = ALLOC_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("doors/doorstop1.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop1.wav"); + break; + case 2: + PRECACHE_SOUND ("doors/doorstop2.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop2.wav"); + break; + case 3: + PRECACHE_SOUND ("doors/doorstop3.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop3.wav"); + break; + case 4: + PRECACHE_SOUND ("doors/doorstop4.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop4.wav"); + break; + case 5: + PRECACHE_SOUND ("doors/doorstop5.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop5.wav"); + break; + case 6: + PRECACHE_SOUND ("doors/doorstop6.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop6.wav"); + break; + case 7: + PRECACHE_SOUND ("doors/doorstop7.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop7.wav"); + break; + case 8: + PRECACHE_SOUND ("doors/doorstop8.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop8.wav"); + break; + default: + pev->noiseArrived = ALLOC_STRING("common/null.wav"); + break; + } + + // get door button sounds, for doors which are directly 'touched' to open + + if (m_bLockedSound) + { + pszSound = ButtonSound( (int)m_bLockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sLockedSound = ALLOC_STRING(pszSound); + } + + if (m_bUnlockedSound) + { + pszSound = ButtonSound( (int)m_bUnlockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sUnlockedSound = ALLOC_STRING(pszSound); + } + + // get sentence group names, for doors which are directly 'touched' to open + + switch (m_bLockedSentence) + { + case 1: m_ls.sLockedSentence = ALLOC_STRING("NA"); break; // access denied + case 2: m_ls.sLockedSentence = ALLOC_STRING("ND"); break; // security lockout + case 3: m_ls.sLockedSentence = ALLOC_STRING("NF"); break; // blast door + case 4: m_ls.sLockedSentence = ALLOC_STRING("NFIRE"); break; // fire door + case 5: m_ls.sLockedSentence = ALLOC_STRING("NCHEM"); break; // chemical door + case 6: m_ls.sLockedSentence = ALLOC_STRING("NRAD"); break; // radiation door + case 7: m_ls.sLockedSentence = ALLOC_STRING("NCON"); break; // gen containment + case 8: m_ls.sLockedSentence = ALLOC_STRING("NH"); break; // maintenance door + case 9: m_ls.sLockedSentence = ALLOC_STRING("NG"); break; // broken door + + default: m_ls.sLockedSentence = 0; break; + } + + switch (m_bUnlockedSentence) + { + case 1: m_ls.sUnlockedSentence = ALLOC_STRING("EA"); break; // access granted + case 2: m_ls.sUnlockedSentence = ALLOC_STRING("ED"); break; // security door + case 3: m_ls.sUnlockedSentence = ALLOC_STRING("EF"); break; // blast door + case 4: m_ls.sUnlockedSentence = ALLOC_STRING("EFIRE"); break; // fire door + case 5: m_ls.sUnlockedSentence = ALLOC_STRING("ECHEM"); break; // chemical door + case 6: m_ls.sUnlockedSentence = ALLOC_STRING("ERAD"); break; // radiation door + case 7: m_ls.sUnlockedSentence = ALLOC_STRING("ECON"); break; // gen containment + case 8: m_ls.sUnlockedSentence = ALLOC_STRING("EH"); break; // maintenance door + + default: m_ls.sUnlockedSentence = 0; break; + } +} + +// +// Doors not tied to anything (e.g. button, another door) can be touched, to make them activate. +// +void CBaseDoor::DoorTouch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + + // Ignore touches by anything but players + if (!FClassnameIs(pevToucher, "player")) + return; + + // If door has master, and it's not ready to trigger, + // play 'locked' sound + + if (m_sMaster && !UTIL_IsMasterTriggered(m_sMaster, pOther)) + PlayLockSounds(pev, &m_ls, TRUE, FALSE); + + // If door is somebody's target, then touching does nothing. + // You have to activate the owner (e.g. button). + + if (!FStringNull(pev->targetname)) + { + // play locked sound + PlayLockSounds(pev, &m_ls, TRUE, FALSE); + return; + } + + m_hActivator = pOther;// remember who activated the door + + if (DoorActivate( )) + SetTouch( NULL ); // Temporarily disable the touch function, until movement is finished. +} + + +// +// Used by SUB_UseTargets, when a door is the target of a button. +// +void CBaseDoor::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_hActivator = pActivator; + // if not ready to be used, ignore "use" command. + if (m_toggle_state == TS_AT_BOTTOM || FBitSet(pev->spawnflags, SF_DOOR_NO_AUTO_RETURN) && m_toggle_state == TS_AT_TOP) + DoorActivate(); +} + +// +// Causes the door to "do its thing", i.e. start moving, and cascade activation. +// +int CBaseDoor::DoorActivate( ) +{ + if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + return 0; + + if (FBitSet(pev->spawnflags, SF_DOOR_NO_AUTO_RETURN) && m_toggle_state == TS_AT_TOP) + {// door should close + DoorGoDown(); + } + else + {// door should open + + if ( m_hActivator != NULL && m_hActivator->IsPlayer() ) + {// give health if player opened the door (medikit) + // VARS( m_eoActivator )->health += m_bHealthValue; + + m_hActivator->TakeHealth( m_bHealthValue, DMG_GENERIC ); + + } + + // play door unlock sounds + PlayLockSounds(pev, &m_ls, FALSE, FALSE); + + DoorGoUp(); + } + + return 1; +} + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); + +// +// Starts the door going to its "up" position (simply ToggleData->vecPosition2). +// +void CBaseDoor::DoorGoUp( void ) +{ + entvars_t *pevActivator; + + // It could be going-down, if blocked. + ASSERT(m_toggle_state == TS_AT_BOTTOM || m_toggle_state == TS_GOING_DOWN); + + // emit door moving and stop sounds on CHAN_STATIC so that the multicast doesn't + // filter them out and leave a client stuck with looping door sounds! + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + { + if ( m_toggle_state != TS_GOING_UP && m_toggle_state != TS_GOING_DOWN ) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving), 1, ATTN_NORM); + } + + m_toggle_state = TS_GOING_UP; + + SetMoveDone( &CBaseDoor::DoorHitTop ); + if ( FClassnameIs(pev, "func_door_rotating")) // !!! BUGBUG Triggered doors don't work with this yet + { + float sign = 1.0; + + if ( m_hActivator != NULL ) + { + pevActivator = m_hActivator->pev; + + if ( !FBitSet( pev->spawnflags, SF_DOOR_ONEWAY ) && pev->movedir.y ) // Y axis rotation, move away from the player + { + Vector vec = pevActivator->origin - pev->origin; + Vector angles = pevActivator->angles; + angles.x = 0; + angles.z = 0; + UTIL_MakeVectors (angles); + // Vector vnext = (pevToucher->origin + (pevToucher->velocity * 10)) - pev->origin; + UTIL_MakeVectors ( pevActivator->angles ); + Vector vnext = (pevActivator->origin + (gpGlobals->v_forward * 10)) - pev->origin; + if ( (vec.x*vnext.y - vec.y*vnext.x) < 0 ) + sign = -1.0; + } + } + AngularMove(m_vecAngle2*sign, pev->speed); + } + else + LinearMove(m_vecPosition2, pev->speed); +} + + +// +// The door has reached the "up" position. Either go back down, or wait for another activation. +// +void CBaseDoor::DoorHitTop( void ) +{ + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + { + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving) ); + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseArrived), 1, ATTN_NORM); + } + + ASSERT(m_toggle_state == TS_GOING_UP); + m_toggle_state = TS_AT_TOP; + + // toggle-doors don't come down automatically, they wait for refire. + if (FBitSet(pev->spawnflags, SF_DOOR_NO_AUTO_RETURN)) + { + // Re-instate touch method, movement is complete + if ( !FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + SetTouch( &CBaseDoor::DoorTouch ); + } + else + { + // In flWait seconds, DoorGoDown will fire, unless wait is -1, then door stays open + pev->nextthink = pev->ltime + m_flWait; + SetThink( &CBaseDoor::DoorGoDown ); + + if ( m_flWait == -1 ) + { + pev->nextthink = -1; + } + } + + // Fire the close target (if startopen is set, then "top" is closed) - netname is the close target + if ( pev->netname && (pev->spawnflags & SF_DOOR_START_OPEN) ) + FireTargets( STRING(pev->netname), m_hActivator, this, USE_TOGGLE, 0 ); + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); // this isn't finished +} + + +// +// Starts the door going to its "down" position (simply ToggleData->vecPosition1). +// +void CBaseDoor::DoorGoDown( void ) +{ + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + { + if ( m_toggle_state != TS_GOING_UP && m_toggle_state != TS_GOING_DOWN ) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving), 1, ATTN_NORM); + } + +#ifdef DOOR_ASSERT + ASSERT(m_toggle_state == TS_AT_TOP); +#endif // DOOR_ASSERT + m_toggle_state = TS_GOING_DOWN; + + SetMoveDone( &CBaseDoor::DoorHitBottom ); + if ( FClassnameIs(pev, "func_door_rotating"))//rotating door + AngularMove( m_vecAngle1, pev->speed); + else + LinearMove( m_vecPosition1, pev->speed); +} + +// +// The door has reached the "down" position. Back to quiescence. +// +void CBaseDoor::DoorHitBottom( void ) +{ + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + { + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving) ); + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseArrived), 1, ATTN_NORM); + } + + ASSERT(m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_AT_BOTTOM; + + // Re-instate touch method, cycle is complete + if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + {// use only door + SetTouch ( NULL ); + } + else // touchable door + SetTouch( &CBaseDoor::DoorTouch ); + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); // this isn't finished + + // Fire the close target (if startopen is set, then "top" is closed) - netname is the close target + if ( pev->netname && !(pev->spawnflags & SF_DOOR_START_OPEN) ) + FireTargets( STRING(pev->netname), m_hActivator, this, USE_TOGGLE, 0 ); +} + +void CBaseDoor::Blocked( CBaseEntity *pOther ) +{ + edict_t *pentTarget = NULL; + CBaseDoor *pDoor = NULL; + + + // Hurt the blocker a little. + if ( pev->dmg ) + pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH ); + + // if a door has a negative wait, it would never come back if blocked, + // so let it just squash the object to death real fast + + if (m_flWait >= 0) + { + if (m_toggle_state == TS_GOING_DOWN) + { + DoorGoUp(); + } + else + { + DoorGoDown(); + } + } + + // Block all door pieces with the same targetname here. + if ( !FStringNull ( pev->targetname ) ) + { + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->targetname)); + + if ( VARS( pentTarget ) != pev ) + { + if (FNullEnt(pentTarget)) + break; + + if ( FClassnameIs ( pentTarget, "func_door" ) || FClassnameIs ( pentTarget, "func_door_rotating" ) ) + { + + pDoor = GetClassPtr( (CBaseDoor *) VARS(pentTarget) ); + + if ( pDoor->m_flWait >= 0) + { + if (pDoor->pev->velocity == pev->velocity && pDoor->pev->avelocity == pev->velocity) + { + // this is the most hacked, evil, bastardized thing I've ever seen. kjb + if ( FClassnameIs ( pentTarget, "func_door" ) ) + {// set origin to realign normal doors + pDoor->pev->origin = pev->origin; + pDoor->pev->velocity = g_vecZero;// stop! + } + else + {// set angles to realign rotating doors + pDoor->pev->angles = pev->angles; + pDoor->pev->avelocity = g_vecZero; + } + } + + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving) ); + + if ( pDoor->m_toggle_state == TS_GOING_DOWN) + pDoor->DoorGoUp(); + else + pDoor->DoorGoDown(); + } + } + } + } + } +} + + +/*QUAKED FuncRotDoorSpawn (0 .5 .8) ? START_OPEN REVERSE +DOOR_DONT_LINK TOGGLE X_AXIS Y_AXIS +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. + +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). + +You need to have an origin brush as part of this entity. The +center of that brush will be +the point around which it is rotated. It will rotate around the Z +axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"distance" is how many degrees the door will be rotated. +"speed" determines how fast the door moves; default value is 100. + +REVERSE will cause the door to rotate in the opposite direction. + +"angle" determines the opening direction +"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" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"dmg" damage to inflict when blocked (2 default) +"sounds" +0) no sound +1) stone +2) base +3) stone chain +4) screechy metal +*/ +class CRotDoor : public CBaseDoor +{ +public: + void Spawn( void ); + virtual void SetToggleState( int state ); +}; + +LINK_ENTITY_TO_CLASS( func_door_rotating, CRotDoor ); + + +void CRotDoor::Spawn( void ) +{ + Precache(); + // set the axis of rotation + CBaseToggle::AxisDir( pev ); + + // check for clockwise rotation + if ( FBitSet (pev->spawnflags, SF_DOOR_ROTATE_BACKWARDS) ) + pev->movedir = pev->movedir * -1; + + //m_flWait = 2; who the hell did this? (sjb) + m_vecAngle1 = pev->angles; + m_vecAngle2 = pev->angles + pev->movedir * m_flMoveDistance; + + ASSERTSZ(m_vecAngle1 != m_vecAngle2, "rotating door start/end positions are equal"); + + if ( FBitSet (pev->spawnflags, SF_DOOR_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + if (pev->speed == 0) + pev->speed = 100; + +// DOOR_START_OPEN is to allow an entity to be lighted in the closed position +// but spawn in the open position + if ( FBitSet (pev->spawnflags, SF_DOOR_START_OPEN) ) + { // swap pos1 and pos2, put door at pos2, invert movement direction + pev->angles = m_vecAngle2; + Vector vecSav = m_vecAngle1; + m_vecAngle2 = m_vecAngle1; + m_vecAngle1 = vecSav; + pev->movedir = pev->movedir * -1; + } + + m_toggle_state = TS_AT_BOTTOM; + + if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + { + SetTouch ( NULL ); + } + else // touchable button + SetTouch( &CRotDoor::DoorTouch ); +} + + +void CRotDoor :: SetToggleState( int state ) +{ + if ( state == TS_AT_TOP ) + pev->angles = m_vecAngle2; + else + pev->angles = m_vecAngle1; + + UTIL_SetOrigin( pev, pev->origin ); +} + + +class CMomentaryDoor : public CBaseToggle +{ +public: + void Spawn( void ); + void Precache( void ); + + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void EXPORT DoorMoveDone( void ); + + BYTE m_bMoveSnd; // sound a door makes while moving +}; + +LINK_ENTITY_TO_CLASS( momentary_door, CMomentaryDoor ); + +TYPEDESCRIPTION CMomentaryDoor::m_SaveData[] = +{ + DEFINE_FIELD( CMomentaryDoor, m_bMoveSnd, FIELD_CHARACTER ), +}; + +IMPLEMENT_SAVERESTORE( CMomentaryDoor, CBaseToggle ); + +void CMomentaryDoor::Spawn( void ) +{ + SetMovedir (pev); + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + if (pev->speed == 0) + pev->speed = 100; + if (pev->dmg == 0) + pev->dmg = 2; + + m_vecPosition1 = pev->origin; + // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big + m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); + ASSERTSZ(m_vecPosition1 != m_vecPosition2, "door start/end positions are equal"); + + if ( FBitSet (pev->spawnflags, SF_DOOR_START_OPEN) ) + { // swap pos1 and pos2, put door at pos2 + UTIL_SetOrigin(pev, m_vecPosition2); + m_vecPosition2 = m_vecPosition1; + m_vecPosition1 = pev->origin; + } + SetTouch( NULL ); + + Precache(); +} + +void CMomentaryDoor::Precache( void ) +{ + +// set the door's "in-motion" sound + switch (m_bMoveSnd) + { + case 0: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("doors/doormove1.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove1.wav"); + break; + case 2: + PRECACHE_SOUND ("doors/doormove2.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove2.wav"); + break; + case 3: + PRECACHE_SOUND ("doors/doormove3.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove3.wav"); + break; + case 4: + PRECACHE_SOUND ("doors/doormove4.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove4.wav"); + break; + case 5: + PRECACHE_SOUND ("doors/doormove5.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove5.wav"); + break; + case 6: + PRECACHE_SOUND ("doors/doormove6.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove6.wav"); + break; + case 7: + PRECACHE_SOUND ("doors/doormove7.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove7.wav"); + break; + case 8: + PRECACHE_SOUND ("doors/doormove8.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove8.wav"); + break; + default: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + } +} + +void CMomentaryDoor::KeyValue( KeyValueData *pkvd ) +{ + + if (FStrEq(pkvd->szKeyName, "movesnd")) + { + m_bMoveSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "stopsnd")) + { +// m_bStopSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "healthvalue")) + { +// m_bHealthValue = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CMomentaryDoor::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( useType != USE_SET ) // Momentary buttons will pass down a float in here + return; + + if ( value > 1.0 ) + value = 1.0; + if ( value < 0.0 ) + value = 0.0; + + Vector move = m_vecPosition1 + (value * (m_vecPosition2 - m_vecPosition1)); + + Vector delta = move - pev->origin; + //float speed = delta.Length() * 10; + float speed = delta.Length() / 0.1; // move there in 0.1 sec + if ( speed == 0 ) + return; + + // This entity only thinks when it moves, so if it's thinking, it's in the process of moving + // play the sound when it starts moving (not yet thinking) + if ( pev->nextthink < pev->ltime || pev->nextthink == 0 ) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving), 1, ATTN_NORM); + // If we already moving to designated point, return + else if (move == m_vecFinalDest) + return; + + SetMoveDone( &CMomentaryDoor::DoorMoveDone ); + LinearMove( move, speed ); +} + +// +// The door has reached needed position. +// +void CMomentaryDoor::DoorMoveDone( void ) +{ + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving) ); + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseArrived), 1, ATTN_NORM); +} diff --git a/dlls/doors.h b/dlls/doors.h new file mode 100644 index 0000000..c9752c1 --- /dev/null +++ b/dlls/doors.h @@ -0,0 +1,33 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef DOORS_H +#define DOORS_H + +// doors +#define SF_DOOR_ROTATE_Y 0 +#define SF_DOOR_START_OPEN 1 +#define SF_DOOR_ROTATE_BACKWARDS 2 +#define SF_DOOR_PASSABLE 8 +#define SF_DOOR_ONEWAY 16 +#define SF_DOOR_NO_AUTO_RETURN 32 +#define SF_DOOR_ROTATE_Z 64 +#define SF_DOOR_ROTATE_X 128 +#define SF_DOOR_USE_ONLY 256 // door must be opened by player's use button. +#define SF_DOOR_NOMONSTERS 512 // Monster can't open +#define SF_DOOR_SILENT 0x80000000 + + + +#endif //DOORS_H diff --git a/dlls/effects.cpp b/dlls/effects.cpp new file mode 100644 index 0000000..cebe5d9 --- /dev/null +++ b/dlls/effects.cpp @@ -0,0 +1,2268 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "customentity.h" +#include "effects.h" +#include "weapons.h" +#include "decals.h" +#include "func_break.h" +#include "shake.h" + +#define SF_GIBSHOOTER_REPEATABLE 1 // allows a gibshooter to be refired + +#define SF_FUNNEL_REVERSE 1 // funnel effect repels particles instead of attracting them. + + +// Lightning target, just alias landmark +LINK_ENTITY_TO_CLASS( info_target, CPointEntity ); + + +class CBubbling : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + void EXPORT FizzThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + static TYPEDESCRIPTION m_SaveData[]; + + int m_density; + int m_frequency; + int m_bubbleModel; + int m_state; +}; + +LINK_ENTITY_TO_CLASS( env_bubbles, CBubbling ); + +TYPEDESCRIPTION CBubbling::m_SaveData[] = +{ + DEFINE_FIELD( CBubbling, m_density, FIELD_INTEGER ), + DEFINE_FIELD( CBubbling, m_frequency, FIELD_INTEGER ), + DEFINE_FIELD( CBubbling, m_state, FIELD_INTEGER ), + // Let spawn restore this! + // DEFINE_FIELD( CBubbling, m_bubbleModel, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CBubbling, CBaseEntity ); + + +#define SF_BUBBLES_STARTOFF 0x0001 + +void CBubbling::Spawn( void ) +{ + Precache( ); + SET_MODEL( ENT(pev), STRING(pev->model) ); // Set size + + pev->solid = SOLID_NOT; // Remove model & collisions + pev->renderamt = 0; // The engine won't draw this model if this is set to 0 and blending is on + pev->rendermode = kRenderTransTexture; + int speed = pev->speed > 0 ? pev->speed : -pev->speed; + + // HACKHACK!!! - Speed in rendercolor + pev->rendercolor.x = speed >> 8; + pev->rendercolor.y = speed & 255; + pev->rendercolor.z = (pev->speed < 0) ? 1 : 0; + + + if ( !(pev->spawnflags & SF_BUBBLES_STARTOFF) ) + { + SetThink( &CBubbling::FizzThink ); + pev->nextthink = gpGlobals->time + 2.0; + m_state = 1; + } + else + m_state = 0; +} + +void CBubbling::Precache( void ) +{ + m_bubbleModel = PRECACHE_MODEL("sprites/bubble.spr"); // Precache bubble sprite +} + + +void CBubbling::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( ShouldToggle( useType, m_state ) ) + m_state = !m_state; + + if ( m_state ) + { + SetThink( & CBubbling::FizzThink ); + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + SetThink( NULL ); + pev->nextthink = 0; + } +} + + +void CBubbling::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "density")) + { + m_density = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "frequency")) + { + m_frequency = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "current")) + { + pev->speed = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +void CBubbling::FizzThink( void ) +{ + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, VecBModelOrigin(pev) ); + WRITE_BYTE( TE_FIZZ ); + WRITE_SHORT( (short)ENTINDEX( edict() ) ); + WRITE_SHORT( (short)m_bubbleModel ); + WRITE_BYTE( m_density ); + MESSAGE_END(); + + if ( m_frequency > 19 ) + pev->nextthink = gpGlobals->time + 0.5; + else + pev->nextthink = gpGlobals->time + 2.5 - (0.1 * m_frequency); +} + +// -------------------------------------------------- +// +// Beams +// +// -------------------------------------------------- + +LINK_ENTITY_TO_CLASS( beam, CBeam ); + +void CBeam::Spawn( void ) +{ + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); +} + +void CBeam::Precache( void ) +{ + if ( pev->owner ) + SetStartEntity( ENTINDEX( pev->owner ) ); + if ( pev->aiment ) + SetEndEntity( ENTINDEX( pev->aiment ) ); +} + +void CBeam::SetStartEntity( int entityIndex ) +{ + pev->sequence = (entityIndex & 0x0FFF) | ((pev->sequence&0xF000)<<12); + pev->owner = g_engfuncs.pfnPEntityOfEntIndex( entityIndex ); +} + +void CBeam::SetEndEntity( int entityIndex ) +{ + pev->skin = (entityIndex & 0x0FFF) | ((pev->skin&0xF000)<<12); + pev->aiment = g_engfuncs.pfnPEntityOfEntIndex( entityIndex ); +} + + +// These don't take attachments into account +const Vector &CBeam::GetStartPos( void ) +{ + if ( GetType() == BEAM_ENTS ) + { + edict_t *pent = g_engfuncs.pfnPEntityOfEntIndex( GetStartEntity() ); + return pent->v.origin; + } + return pev->origin; +} + + +const Vector &CBeam::GetEndPos( void ) +{ + int type = GetType(); + if ( type == BEAM_POINTS || type == BEAM_HOSE ) + { + return pev->angles; + } + + edict_t *pent = g_engfuncs.pfnPEntityOfEntIndex( GetEndEntity() ); + if ( pent ) + return pent->v.origin; + return pev->angles; +} + + +CBeam *CBeam::BeamCreate( const char *pSpriteName, int width ) +{ + // Create a new entity with CBeam private data + CBeam *pBeam = GetClassPtr( (CBeam *)NULL ); + pBeam->pev->classname = MAKE_STRING("beam"); + + pBeam->BeamInit( pSpriteName, width ); + + return pBeam; +} + + +void CBeam::BeamInit( const char *pSpriteName, int width ) +{ + pev->flags |= FL_CUSTOMENTITY; + SetColor( 255, 255, 255 ); + SetBrightness( 255 ); + SetNoise( 0 ); + SetFrame( 0 ); + SetScrollRate( 0 ); + pev->model = MAKE_STRING( pSpriteName ); + SetTexture( PRECACHE_MODEL( (char *)pSpriteName ) ); + SetWidth( width ); + pev->skin = 0; + pev->sequence = 0; + pev->rendermode = 0; +} + + +void CBeam::PointsInit( const Vector &start, const Vector &end ) +{ + SetType( BEAM_POINTS ); + SetStartPos( start ); + SetEndPos( end ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CBeam::HoseInit( const Vector &start, const Vector &direction ) +{ + SetType( BEAM_HOSE ); + SetStartPos( start ); + SetEndPos( direction ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CBeam::PointEntInit( const Vector &start, int endIndex ) +{ + SetType( BEAM_ENTPOINT ); + SetStartPos( start ); + SetEndEntity( endIndex ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + +void CBeam::EntsInit( int startIndex, int endIndex ) +{ + SetType( BEAM_ENTS ); + SetStartEntity( startIndex ); + SetEndEntity( endIndex ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CBeam::RelinkBeam( void ) +{ + const Vector &startPos = GetStartPos(), &endPos = GetEndPos(); + + pev->mins.x = min( startPos.x, endPos.x ); + pev->mins.y = min( startPos.y, endPos.y ); + pev->mins.z = min( startPos.z, endPos.z ); + pev->maxs.x = max( startPos.x, endPos.x ); + pev->maxs.y = max( startPos.y, endPos.y ); + pev->maxs.z = max( startPos.z, endPos.z ); + pev->mins = pev->mins - pev->origin; + pev->maxs = pev->maxs - pev->origin; + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); +} + +#if 0 +void CBeam::SetObjectCollisionBox( void ) +{ + const Vector &startPos = GetStartPos(), &endPos = GetEndPos(); + + pev->absmin.x = min( startPos.x, endPos.x ); + pev->absmin.y = min( startPos.y, endPos.y ); + pev->absmin.z = min( startPos.z, endPos.z ); + pev->absmax.x = max( startPos.x, endPos.x ); + pev->absmax.y = max( startPos.y, endPos.y ); + pev->absmax.z = max( startPos.z, endPos.z ); +} +#endif + + +void CBeam::TriggerTouch( CBaseEntity *pOther ) +{ + if ( pOther->pev->flags & (FL_CLIENT | FL_MONSTER) ) + { + if ( pev->owner ) + { + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + pOwner->Use( pOther, this, USE_TOGGLE, 0 ); + } + ALERT( at_console, "Firing targets!!!\n" ); + } +} + + +CBaseEntity *CBeam::RandomTargetname( const char *szName ) +{ + int total = 0; + + CBaseEntity *pEntity = NULL; + CBaseEntity *pNewEntity = NULL; + while ((pNewEntity = UTIL_FindEntityByTargetname( pNewEntity, szName )) != NULL) + { + total++; + if (RANDOM_LONG(0,total-1) < 1) + pEntity = pNewEntity; + } + return pEntity; +} + + +void CBeam::DoSparks( const Vector &start, const Vector &end ) +{ + if ( pev->spawnflags & (SF_BEAM_SPARKSTART|SF_BEAM_SPARKEND) ) + { + if ( pev->spawnflags & SF_BEAM_SPARKSTART ) + { + UTIL_Sparks( start ); + } + if ( pev->spawnflags & SF_BEAM_SPARKEND ) + { + UTIL_Sparks( end ); + } + } +} + + +class CLightning : public CBeam +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void Activate( void ); + + void EXPORT StrikeThink( void ); + void EXPORT DamageThink( void ); + void RandomArea( void ); + void RandomPoint( Vector &vecSrc ); + void Zap( const Vector &vecSrc, const Vector &vecDest ); + void EXPORT StrikeUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + inline BOOL ServerSide( void ) + { + if ( m_life == 0 && !(pev->spawnflags & SF_BEAM_RING) ) + return TRUE; + return FALSE; + } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void BeamUpdateVars( void ); + + int m_active; + int m_iszStartEntity; + int m_iszEndEntity; + float m_life; + int m_boltWidth; + int m_noiseAmplitude; + int m_brightness; + int m_speed; + float m_restrike; + int m_spriteTexture; + int m_iszSpriteName; + int m_frameStart; + + float m_radius; +}; + +LINK_ENTITY_TO_CLASS( env_lightning, CLightning ); +LINK_ENTITY_TO_CLASS( env_beam, CLightning ); + +// UNDONE: Jay -- This is only a test +#if _DEBUG +class CTripBeam : public CLightning +{ + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( trip_beam, CTripBeam ); + +void CTripBeam::Spawn( void ) +{ + CLightning::Spawn(); + SetTouch( TriggerTouch ); + pev->solid = SOLID_TRIGGER; + RelinkBeam(); +} +#endif + + + +TYPEDESCRIPTION CLightning::m_SaveData[] = +{ + DEFINE_FIELD( CLightning, m_active, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_iszStartEntity, FIELD_STRING ), + DEFINE_FIELD( CLightning, m_iszEndEntity, FIELD_STRING ), + DEFINE_FIELD( CLightning, m_life, FIELD_FLOAT ), + DEFINE_FIELD( CLightning, m_boltWidth, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_noiseAmplitude, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_brightness, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_speed, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_restrike, FIELD_FLOAT ), + DEFINE_FIELD( CLightning, m_spriteTexture, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_iszSpriteName, FIELD_STRING ), + DEFINE_FIELD( CLightning, m_frameStart, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_radius, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CLightning, CBeam ); + + +void CLightning::Spawn( void ) +{ + if ( FStringNull( m_iszSpriteName ) ) + { + SetThink( &CLightning::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); + + pev->dmgtime = gpGlobals->time; + + if ( ServerSide() ) + { + SetThink( NULL ); + if ( pev->dmg > 0 ) + { + SetThink( &CLightning::DamageThink ); + pev->nextthink = gpGlobals->time + 0.1; + } + if ( pev->targetname ) + { + if ( !(pev->spawnflags & SF_BEAM_STARTON) ) + { + pev->effects = EF_NODRAW; + m_active = 0; + pev->nextthink = 0; + } + else + m_active = 1; + + SetUse( &CLightning::ToggleUse ); + } + } + else + { + m_active = 0; + if ( !FStringNull(pev->targetname) ) + { + SetUse( &CLightning::StrikeUse ); + } + if ( FStringNull(pev->targetname) || FBitSet(pev->spawnflags, SF_BEAM_STARTON) ) + { + SetThink( &CLightning::StrikeThink ); + pev->nextthink = gpGlobals->time + 1.0; + } + } +} + +void CLightning::Precache( void ) +{ + m_spriteTexture = PRECACHE_MODEL( (char *)STRING(m_iszSpriteName) ); + CBeam::Precache(); +} + + +void CLightning::Activate( void ) +{ + if ( ServerSide() ) + BeamUpdateVars(); +} + + +void CLightning::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "LightningStart")) + { + m_iszStartEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "LightningEnd")) + { + m_iszEndEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "life")) + { + m_life = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "BoltWidth")) + { + m_boltWidth = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "NoiseAmplitude")) + { + m_noiseAmplitude = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "TextureScroll")) + { + m_speed = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "StrikeTime")) + { + m_restrike = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "texture")) + { + m_iszSpriteName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "framestart")) + { + m_frameStart = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "Radius")) + { + m_radius = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damage")) + { + pev->dmg = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBeam::KeyValue( pkvd ); +} + + +void CLightning::ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_active ) ) + return; + if ( m_active ) + { + m_active = 0; + pev->effects |= EF_NODRAW; + pev->nextthink = 0; + } + else + { + m_active = 1; + pev->effects &= ~EF_NODRAW; + DoSparks( GetStartPos(), GetEndPos() ); + if ( pev->dmg > 0 ) + { + pev->nextthink = gpGlobals->time; + pev->dmgtime = gpGlobals->time; + } + } +} + + +void CLightning::StrikeUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_active ) ) + return; + + if ( m_active ) + { + m_active = 0; + SetThink( NULL ); + } + else + { + SetThink( &CLightning::StrikeThink ); + pev->nextthink = gpGlobals->time + 0.1; + } + + if ( !FBitSet( pev->spawnflags, SF_BEAM_TOGGLE ) ) + SetUse( NULL ); +} + + +int IsPointEntity( CBaseEntity *pEnt ) +{ + if ( !pEnt->pev->modelindex ) + return 1; + if ( FClassnameIs( pEnt->pev, "info_target" ) || FClassnameIs( pEnt->pev, "info_landmark" ) || FClassnameIs( pEnt->pev, "path_corner" ) ) + return 1; + + return 0; +} + + +void CLightning::StrikeThink( void ) +{ + if ( m_life != 0 ) + { + if ( pev->spawnflags & SF_BEAM_RANDOM ) + pev->nextthink = gpGlobals->time + m_life + RANDOM_FLOAT( 0, m_restrike ); + else + pev->nextthink = gpGlobals->time + m_life + m_restrike; + } + m_active = 1; + + if (FStringNull(m_iszEndEntity)) + { + if (FStringNull(m_iszStartEntity)) + { + RandomArea( ); + } + else + { + CBaseEntity *pStart = RandomTargetname( STRING(m_iszStartEntity) ); + if (pStart != NULL) + RandomPoint( pStart->pev->origin ); + else + ALERT( at_console, "env_beam: unknown entity \"%s\"\n", STRING(m_iszStartEntity) ); + } + return; + } + + CBaseEntity *pStart = RandomTargetname( STRING(m_iszStartEntity) ); + CBaseEntity *pEnd = RandomTargetname( STRING(m_iszEndEntity) ); + + if ( pStart != NULL && pEnd != NULL ) + { + if ( IsPointEntity( pStart ) || IsPointEntity( pEnd ) ) + { + if ( pev->spawnflags & SF_BEAM_RING) + { + // don't work + return; + } + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + if ( IsPointEntity( pStart ) || IsPointEntity( pEnd ) ) + { + if ( !IsPointEntity( pEnd ) ) // One point entity must be in pEnd + { + CBaseEntity *pTemp; + pTemp = pStart; + pStart = pEnd; + pEnd = pTemp; + } + if ( !IsPointEntity( pStart ) ) // One sided + { + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( pStart->entindex() ); + WRITE_COORD( pEnd->pev->origin.x); + WRITE_COORD( pEnd->pev->origin.y); + WRITE_COORD( pEnd->pev->origin.z); + } + else + { + WRITE_BYTE( TE_BEAMPOINTS); + WRITE_COORD( pStart->pev->origin.x); + WRITE_COORD( pStart->pev->origin.y); + WRITE_COORD( pStart->pev->origin.z); + WRITE_COORD( pEnd->pev->origin.x); + WRITE_COORD( pEnd->pev->origin.y); + WRITE_COORD( pEnd->pev->origin.z); + } + + + } + else + { + if ( pev->spawnflags & SF_BEAM_RING) + WRITE_BYTE( TE_BEAMRING ); + else + WRITE_BYTE( TE_BEAMENTS ); + WRITE_SHORT( pStart->entindex() ); + WRITE_SHORT( pEnd->entindex() ); + } + + WRITE_SHORT( m_spriteTexture ); + WRITE_BYTE( m_frameStart ); // framestart + WRITE_BYTE( (int)pev->framerate); // framerate + WRITE_BYTE( (int)(m_life*10.0) ); // life + WRITE_BYTE( m_boltWidth ); // width + WRITE_BYTE( m_noiseAmplitude ); // noise + WRITE_BYTE( (int)pev->rendercolor.x ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.y ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.z ); // r, g, b + WRITE_BYTE( pev->renderamt ); // brightness + WRITE_BYTE( m_speed ); // speed + MESSAGE_END(); + DoSparks( pStart->pev->origin, pEnd->pev->origin ); + if ( pev->dmg > 0 ) + { + TraceResult tr; + UTIL_TraceLine( pStart->pev->origin, pEnd->pev->origin, dont_ignore_monsters, NULL, &tr ); + BeamDamageInstant( &tr, pev->dmg ); + } + } +} + + +void CBeam::BeamDamage( TraceResult *ptr ) +{ + RelinkBeam(); + if ( ptr->flFraction != 1.0 && ptr->pHit != NULL ) + { + CBaseEntity *pHit = CBaseEntity::Instance(ptr->pHit); + if ( pHit ) + { + ClearMultiDamage(); + pHit->TraceAttack( pev, pev->dmg * (gpGlobals->time - pev->dmgtime), (ptr->vecEndPos - pev->origin).Normalize(), ptr, DMG_ENERGYBEAM ); + ApplyMultiDamage( pev, pev ); + if ( pev->spawnflags & SF_BEAM_DECALS ) + { + if ( pHit->IsBSPModel() ) + UTIL_DecalTrace( ptr, DECAL_BIGSHOT1 + RANDOM_LONG(0,4) ); + } + } + } + pev->dmgtime = gpGlobals->time; +} + + +void CLightning::DamageThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + TraceResult tr; + UTIL_TraceLine( GetStartPos(), GetEndPos(), dont_ignore_monsters, NULL, &tr ); + BeamDamage( &tr ); +} + + + +void CLightning::Zap( const Vector &vecSrc, const Vector &vecDest ) +{ +#if 1 + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS); + WRITE_COORD(vecSrc.x); + WRITE_COORD(vecSrc.y); + WRITE_COORD(vecSrc.z); + WRITE_COORD(vecDest.x); + WRITE_COORD(vecDest.y); + WRITE_COORD(vecDest.z); + WRITE_SHORT( m_spriteTexture ); + WRITE_BYTE( m_frameStart ); // framestart + WRITE_BYTE( (int)pev->framerate); // framerate + WRITE_BYTE( (int)(m_life*10.0) ); // life + WRITE_BYTE( m_boltWidth ); // width + WRITE_BYTE( m_noiseAmplitude ); // noise + WRITE_BYTE( (int)pev->rendercolor.x ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.y ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.z ); // r, g, b + WRITE_BYTE( pev->renderamt ); // brightness + WRITE_BYTE( m_speed ); // speed + MESSAGE_END(); +#else + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE(TE_LIGHTNING); + WRITE_COORD(vecSrc.x); + WRITE_COORD(vecSrc.y); + WRITE_COORD(vecSrc.z); + WRITE_COORD(vecDest.x); + WRITE_COORD(vecDest.y); + WRITE_COORD(vecDest.z); + WRITE_BYTE(10); + WRITE_BYTE(50); + WRITE_BYTE(40); + WRITE_SHORT(m_spriteTexture); + MESSAGE_END(); +#endif + DoSparks( vecSrc, vecDest ); +} + +void CLightning::RandomArea( void ) +{ + int iLoops = 0; + + for (iLoops = 0; iLoops < 10; iLoops++) + { + Vector vecSrc = pev->origin; + + Vector vecDir1 = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + vecDir1 = vecDir1.Normalize(); + TraceResult tr1; + UTIL_TraceLine( vecSrc, vecSrc + vecDir1 * m_radius, ignore_monsters, ENT(pev), &tr1 ); + + if (tr1.flFraction == 1.0) + continue; + + Vector vecDir2; + do { + vecDir2 = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + } while (DotProduct(vecDir1, vecDir2 ) > 0); + vecDir2 = vecDir2.Normalize(); + TraceResult tr2; + UTIL_TraceLine( vecSrc, vecSrc + vecDir2 * m_radius, ignore_monsters, ENT(pev), &tr2 ); + + if (tr2.flFraction == 1.0) + continue; + + if ((tr1.vecEndPos - tr2.vecEndPos).Length() < m_radius * 0.1) + continue; + + UTIL_TraceLine( tr1.vecEndPos, tr2.vecEndPos, ignore_monsters, ENT(pev), &tr2 ); + + if (tr2.flFraction != 1.0) + continue; + + Zap( tr1.vecEndPos, tr2.vecEndPos ); + + break; + } +} + + +void CLightning::RandomPoint( Vector &vecSrc ) +{ + int iLoops = 0; + + for (iLoops = 0; iLoops < 10; iLoops++) + { + Vector vecDir1 = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + vecDir1 = vecDir1.Normalize(); + TraceResult tr1; + UTIL_TraceLine( vecSrc, vecSrc + vecDir1 * m_radius, ignore_monsters, ENT(pev), &tr1 ); + + if ((tr1.vecEndPos - vecSrc).Length() < m_radius * 0.1) + continue; + + if (tr1.flFraction == 1.0) + continue; + + Zap( vecSrc, tr1.vecEndPos ); + break; + } +} + + + +void CLightning::BeamUpdateVars( void ) +{ + int beamType; + int pointStart, pointEnd; + + edict_t *pStart = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(m_iszStartEntity) ); + edict_t *pEnd = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(m_iszEndEntity) ); + pointStart = IsPointEntity( CBaseEntity::Instance(pStart) ); + pointEnd = IsPointEntity( CBaseEntity::Instance(pEnd) ); + + pev->skin = 0; + pev->sequence = 0; + pev->rendermode = 0; + pev->flags |= FL_CUSTOMENTITY; + pev->model = m_iszSpriteName; + SetTexture( m_spriteTexture ); + + beamType = BEAM_ENTS; + if ( pointStart || pointEnd ) + { + if ( !pointStart ) // One point entity must be in pStart + { + edict_t *pTemp; + // Swap start & end + pTemp = pStart; + pStart = pEnd; + pEnd = pTemp; + int swap = pointStart; + pointStart = pointEnd; + pointEnd = swap; + } + if ( !pointEnd ) + beamType = BEAM_ENTPOINT; + else + beamType = BEAM_POINTS; + } + + SetType( beamType ); + if ( beamType == BEAM_POINTS || beamType == BEAM_ENTPOINT || beamType == BEAM_HOSE ) + { + SetStartPos( pStart->v.origin ); + if ( beamType == BEAM_POINTS || beamType == BEAM_HOSE ) + SetEndPos( pEnd->v.origin ); + else + SetEndEntity( ENTINDEX(pEnd) ); + } + else + { + SetStartEntity( ENTINDEX(pStart) ); + SetEndEntity( ENTINDEX(pEnd) ); + } + + RelinkBeam(); + + SetWidth( m_boltWidth ); + SetNoise( m_noiseAmplitude ); + SetFrame( m_frameStart ); + SetScrollRate( m_speed ); + if ( pev->spawnflags & SF_BEAM_SHADEIN ) + SetFlags( BEAM_FSHADEIN ); + else if ( pev->spawnflags & SF_BEAM_SHADEOUT ) + SetFlags( BEAM_FSHADEOUT ); +} + + +LINK_ENTITY_TO_CLASS( env_laser, CLaser ); + +TYPEDESCRIPTION CLaser::m_SaveData[] = +{ + DEFINE_FIELD( CLaser, m_pSprite, FIELD_CLASSPTR ), + DEFINE_FIELD( CLaser, m_iszSpriteName, FIELD_STRING ), + DEFINE_FIELD( CLaser, m_firePosition, FIELD_POSITION_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CLaser, CBeam ); + +void CLaser::Spawn( void ) +{ + if ( FStringNull( pev->model ) ) + { + SetThink( &CLaser::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); + + SetThink( &CLaser::StrikeThink ); + pev->flags |= FL_CUSTOMENTITY; + + PointsInit( pev->origin, pev->origin ); + + if ( !m_pSprite && m_iszSpriteName ) + m_pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteName), pev->origin, TRUE ); + else + m_pSprite = NULL; + + if ( m_pSprite ) + m_pSprite->SetTransparency( kRenderGlow, pev->rendercolor.x, pev->rendercolor.y, pev->rendercolor.z, pev->renderamt, pev->renderfx ); + + if ( pev->targetname && !(pev->spawnflags & SF_BEAM_STARTON) ) + TurnOff(); + else + TurnOn(); +} + +void CLaser::Precache( void ) +{ + pev->modelindex = PRECACHE_MODEL( (char *)STRING(pev->model) ); + if ( m_iszSpriteName ) + PRECACHE_MODEL( (char *)STRING(m_iszSpriteName) ); +} + + +void CLaser::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "LaserTarget")) + { + pev->message = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "width")) + { + SetWidth( (int) atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "NoiseAmplitude")) + { + SetNoise( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "TextureScroll")) + { + SetScrollRate( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "texture")) + { + pev->model = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "EndSprite")) + { + m_iszSpriteName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "framestart")) + { + pev->frame = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damage")) + { + pev->dmg = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBeam::KeyValue( pkvd ); +} + + +int CLaser::IsOn( void ) +{ + if (pev->effects & EF_NODRAW) + return 0; + return 1; +} + + +void CLaser::TurnOff( void ) +{ + pev->effects |= EF_NODRAW; + pev->nextthink = 0; + if ( m_pSprite ) + m_pSprite->TurnOff(); +} + + +void CLaser::TurnOn( void ) +{ + pev->effects &= ~EF_NODRAW; + if ( m_pSprite ) + m_pSprite->TurnOn(); + pev->dmgtime = gpGlobals->time; + pev->nextthink = gpGlobals->time; +} + + +void CLaser::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int active = IsOn(); + + if ( !ShouldToggle( useType, active ) ) + return; + if ( active ) + { + TurnOff(); + } + else + { + TurnOn(); + } +} + + +void CLaser::FireAtPoint( TraceResult &tr ) +{ + SetEndPos( tr.vecEndPos ); + if ( m_pSprite ) + UTIL_SetOrigin( m_pSprite->pev, tr.vecEndPos ); + + BeamDamage( &tr ); + DoSparks( GetStartPos(), tr.vecEndPos ); +} + +void CLaser::StrikeThink( void ) +{ + CBaseEntity *pEnd = RandomTargetname( STRING(pev->message) ); + + if ( pEnd ) + m_firePosition = pEnd->pev->origin; + + TraceResult tr; + + UTIL_TraceLine( pev->origin, m_firePosition, dont_ignore_monsters, NULL, &tr ); + FireAtPoint( tr ); + pev->nextthink = gpGlobals->time + 0.1; +} + + + +class CGlow : public CPointEntity +{ +public: + void Spawn( void ); + void Think( void ); + void Animate( float frames ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + float m_lastTime; + float m_maxFrame; +}; + +LINK_ENTITY_TO_CLASS( env_glow, CGlow ); + +TYPEDESCRIPTION CGlow::m_SaveData[] = +{ + DEFINE_FIELD( CGlow, m_lastTime, FIELD_TIME ), + DEFINE_FIELD( CGlow, m_maxFrame, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CGlow, CPointEntity ); + +void CGlow::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; + if ( m_maxFrame > 1.0 && pev->framerate != 0 ) + pev->nextthink = gpGlobals->time + 0.1; + + m_lastTime = gpGlobals->time; +} + + +void CGlow::Think( void ) +{ + Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); + + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; +} + + +void CGlow::Animate( float frames ) +{ + if ( m_maxFrame > 0 ) + pev->frame = fmod( pev->frame + frames, m_maxFrame ); +} + + +LINK_ENTITY_TO_CLASS( env_sprite, CSprite ); + +TYPEDESCRIPTION CSprite::m_SaveData[] = +{ + DEFINE_FIELD( CSprite, m_lastTime, FIELD_TIME ), + DEFINE_FIELD( CSprite, m_maxFrame, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CSprite, CPointEntity ); + +void CSprite::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + + Precache(); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; + if ( pev->targetname && !(pev->spawnflags & SF_SPRITE_STARTON) ) + TurnOff(); + else + TurnOn(); + + // Worldcraft only sets y rotation, copy to Z + if ( pev->angles.y != 0 && pev->angles.z == 0 ) + { + pev->angles.z = pev->angles.y; + pev->angles.y = 0; + } +} + + +void CSprite::Precache( void ) +{ + PRECACHE_MODEL( (char *)STRING(pev->model) ); + + // Reset attachment after save/restore + if ( pev->aiment ) + SetAttachment( pev->aiment, pev->body ); + else + { + // Clear attachment + pev->skin = 0; + pev->body = 0; + } +} + + +void CSprite::SpriteInit( const char *pSpriteName, const Vector &origin ) +{ + pev->model = MAKE_STRING(pSpriteName); + pev->origin = origin; + Spawn(); +} + +CSprite *CSprite::SpriteCreate( const char *pSpriteName, const Vector &origin, BOOL animate ) +{ + CSprite *pSprite = GetClassPtr( (CSprite *)NULL ); + pSprite->SpriteInit( pSpriteName, origin ); + pSprite->pev->classname = MAKE_STRING("env_sprite"); + pSprite->pev->solid = SOLID_NOT; + pSprite->pev->movetype = MOVETYPE_NOCLIP; + if ( animate ) + pSprite->TurnOn(); + + return pSprite; +} + + +void CSprite::AnimateThink( void ) +{ + Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); + + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; +} + +void CSprite::AnimateUntilDead( void ) +{ + if ( gpGlobals->time > pev->dmgtime ) + UTIL_Remove(this); + else + { + AnimateThink(); + pev->nextthink = gpGlobals->time; + } +} + +void CSprite::Expand( float scaleSpeed, float fadeSpeed ) +{ + pev->speed = scaleSpeed; + pev->health = fadeSpeed; + SetThink( &CSprite::ExpandThink ); + + pev->nextthink = gpGlobals->time; + m_lastTime = gpGlobals->time; +} + + +void CSprite::ExpandThink( void ) +{ + float frametime = gpGlobals->time - m_lastTime; + pev->scale += pev->speed * frametime; + pev->renderamt -= pev->health * frametime; + if ( pev->renderamt <= 0 ) + { + pev->renderamt = 0; + UTIL_Remove( this ); + } + else + { + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; + } +} + + +void CSprite::Animate( float frames ) +{ + pev->frame += frames; + if ( pev->frame > m_maxFrame ) + { + if ( pev->spawnflags & SF_SPRITE_ONCE ) + { + TurnOff(); + } + else + { + if ( m_maxFrame > 0 ) + pev->frame = fmod( pev->frame, m_maxFrame ); + } + } +} + + +void CSprite::TurnOff( void ) +{ + pev->effects = EF_NODRAW; + pev->nextthink = 0; +} + + +void CSprite::TurnOn( void ) +{ + pev->effects = 0; + if ( (pev->framerate && m_maxFrame > 1.0) || (pev->spawnflags & SF_SPRITE_ONCE) ) + { + SetThink( &CSprite::AnimateThink ); + pev->nextthink = gpGlobals->time; + m_lastTime = gpGlobals->time; + } + pev->frame = 0; +} + + +void CSprite::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int on = pev->effects != EF_NODRAW; + if ( ShouldToggle( useType, on ) ) + { + if ( on ) + { + TurnOff(); + } + else + { + TurnOn(); + } + } +} + + +class CGibShooter : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT ShootThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual CGib *CreateGib( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iGibs; + int m_iGibCapacity; + int m_iGibMaterial; + int m_iGibModelIndex; + float m_flGibVelocity; + float m_flVariance; + float m_flGibLife; +}; + +TYPEDESCRIPTION CGibShooter::m_SaveData[] = +{ + DEFINE_FIELD( CGibShooter, m_iGibs, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_iGibCapacity, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_iGibMaterial, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_iGibModelIndex, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_flGibVelocity, FIELD_FLOAT ), + DEFINE_FIELD( CGibShooter, m_flVariance, FIELD_FLOAT ), + DEFINE_FIELD( CGibShooter, m_flGibLife, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CGibShooter, CBaseDelay ); +LINK_ENTITY_TO_CLASS( gibshooter, CGibShooter ); + + +void CGibShooter :: Precache ( void ) +{ + if ( g_Language == LANGUAGE_GERMAN ) + { + m_iGibModelIndex = PRECACHE_MODEL ("models/germanygibs.mdl"); + } + else + { + m_iGibModelIndex = PRECACHE_MODEL ("models/hgibs.mdl"); + } +} + + +void CGibShooter::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iGibs")) + { + m_iGibs = m_iGibCapacity = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flVelocity")) + { + m_flGibVelocity = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flVariance")) + { + m_flVariance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flGibLife")) + { + m_flGibLife = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + { + CBaseDelay::KeyValue( pkvd ); + } +} + +void CGibShooter::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( &CGibShooter::ShootThink ); + pev->nextthink = gpGlobals->time; +} + +void CGibShooter::Spawn( void ) +{ + Precache(); + + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + + if ( m_flDelay == 0 ) + { + m_flDelay = 0.1; + } + + if ( m_flGibLife == 0 ) + { + m_flGibLife = 25; + } + + SetMovedir ( pev ); + pev->body = MODEL_FRAMES( m_iGibModelIndex ); +} + + +CGib *CGibShooter :: CreateGib ( void ) +{ + if ( CVAR_GET_FLOAT("violence_hgibs") == 0 ) + return NULL; + + CGib *pGib = GetClassPtr( (CGib *)NULL ); + pGib->Spawn( "models/hgibs.mdl" ); + pGib->m_bloodColor = BLOOD_COLOR_RED; + + if ( pev->body <= 1 ) + { + ALERT ( at_aiconsole, "GibShooter Body is <= 1!\n" ); + } + + pGib->pev->body = RANDOM_LONG ( 1, pev->body - 1 );// avoid throwing random amounts of the 0th gib. (skull). + + return pGib; +} + + +void CGibShooter :: ShootThink ( void ) +{ + pev->nextthink = gpGlobals->time + m_flDelay; + + Vector vecShootDir; + + vecShootDir = pev->movedir; + + vecShootDir = vecShootDir + gpGlobals->v_right * RANDOM_FLOAT( -1, 1) * m_flVariance;; + vecShootDir = vecShootDir + gpGlobals->v_forward * RANDOM_FLOAT( -1, 1) * m_flVariance;; + vecShootDir = vecShootDir + gpGlobals->v_up * RANDOM_FLOAT( -1, 1) * m_flVariance;; + + vecShootDir = vecShootDir.Normalize(); + CGib *pGib = CreateGib(); + + if ( pGib ) + { + pGib->pev->origin = pev->origin; + pGib->pev->velocity = vecShootDir * m_flGibVelocity; + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + float thinkTime = pGib->pev->nextthink - gpGlobals->time; + + pGib->m_lifeTime = (m_flGibLife * RANDOM_FLOAT( 0.95, 1.05 )); // +/- 5% + if ( pGib->m_lifeTime < thinkTime ) + { + pGib->pev->nextthink = gpGlobals->time + pGib->m_lifeTime; + pGib->m_lifeTime = 0; + } + + } + + if ( --m_iGibs <= 0 ) + { + if ( pev->spawnflags & SF_GIBSHOOTER_REPEATABLE ) + { + m_iGibs = m_iGibCapacity; + SetThink ( NULL ); + pev->nextthink = gpGlobals->time; + } + else + { + SetThink ( &CGibShooter::SUB_Remove ); + pev->nextthink = gpGlobals->time; + } + } +} + + +class CEnvShooter : public CGibShooter +{ + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + CGib *CreateGib( void ); +}; + +LINK_ENTITY_TO_CLASS( env_shooter, CEnvShooter ); + +void CEnvShooter :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "shootmodel")) + { + pev->model = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "shootsounds")) + { + int iNoise = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + switch( iNoise ) + { + case 0: + m_iGibMaterial = matGlass; + break; + case 1: + m_iGibMaterial = matWood; + break; + case 2: + m_iGibMaterial = matMetal; + break; + case 3: + m_iGibMaterial = matFlesh; + break; + case 4: + m_iGibMaterial = matRocks; + break; + + default: + case -1: + m_iGibMaterial = matNone; + break; + } + } + else + { + CGibShooter::KeyValue( pkvd ); + } +} + + +void CEnvShooter :: Precache ( void ) +{ + m_iGibModelIndex = PRECACHE_MODEL( (char *)STRING(pev->model) ); + CBreakable::MaterialSoundPrecache( (Materials)m_iGibMaterial ); +} + + +CGib *CEnvShooter :: CreateGib ( void ) +{ + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + pGib->Spawn( STRING(pev->model) ); + + int bodyPart = 0; + + if ( pev->body > 1 ) + bodyPart = RANDOM_LONG( 0, pev->body-1 ); + + pGib->pev->body = bodyPart; + pGib->m_bloodColor = DONT_BLEED; + pGib->m_material = m_iGibMaterial; + + pGib->pev->rendermode = pev->rendermode; + pGib->pev->renderamt = pev->renderamt; + pGib->pev->rendercolor = pev->rendercolor; + pGib->pev->renderfx = pev->renderfx; + pGib->pev->scale = pev->scale; + pGib->pev->skin = pev->skin; + + return pGib; +} + + + + +class CTestEffect : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + // void KeyValue( KeyValueData *pkvd ); + void EXPORT TestThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int m_iLoop; + int m_iBeam; + CBeam *m_pBeam[24]; + float m_flBeamTime[24]; + float m_flStartTime; +}; + + +LINK_ENTITY_TO_CLASS( test_effect, CTestEffect ); + +void CTestEffect::Spawn( void ) +{ + Precache( ); +} + +void CTestEffect::Precache( void ) +{ + PRECACHE_MODEL( "sprites/lgtning.spr" ); +} + +void CTestEffect::TestThink( void ) +{ + int i; + float t = (gpGlobals->time - m_flStartTime); + + if (m_iBeam < 24) + { + CBeam *pbeam = CBeam::BeamCreate( "sprites/lgtning.spr", 100 ); + + TraceResult tr; + + Vector vecSrc = pev->origin; + Vector vecDir = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + vecDir = vecDir.Normalize(); + UTIL_TraceLine( vecSrc, vecSrc + vecDir * 128, ignore_monsters, ENT(pev), &tr); + + pbeam->PointsInit( vecSrc, tr.vecEndPos ); + // pbeam->SetColor( 80, 100, 255 ); + pbeam->SetColor( 255, 180, 100 ); + pbeam->SetWidth( 100 ); + pbeam->SetScrollRate( 12 ); + + m_flBeamTime[m_iBeam] = gpGlobals->time; + m_pBeam[m_iBeam] = pbeam; + m_iBeam++; + +#if 0 + Vector vecMid = (vecSrc + tr.vecEndPos) * 0.5; + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE(TE_DLIGHT); + WRITE_COORD(vecMid.x); // X + WRITE_COORD(vecMid.y); // Y + WRITE_COORD(vecMid.z); // Z + WRITE_BYTE( 20 ); // radius * 0.1 + WRITE_BYTE( 255 ); // r + WRITE_BYTE( 180 ); // g + WRITE_BYTE( 100 ); // b + WRITE_BYTE( 20 ); // time * 10 + WRITE_BYTE( 0 ); // decay * 0.1 + MESSAGE_END( ); +#endif + } + + if (t < 3.0) + { + for (i = 0; i < m_iBeam; i++) + { + t = (gpGlobals->time - m_flBeamTime[i]) / ( 3 + m_flStartTime - m_flBeamTime[i]); + m_pBeam[i]->SetBrightness( 255 * t ); + // m_pBeam[i]->SetScrollRate( 20 * t ); + } + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + for (i = 0; i < m_iBeam; i++) + { + UTIL_Remove( m_pBeam[i] ); + } + m_flStartTime = gpGlobals->time; + m_iBeam = 0; + // pev->nextthink = gpGlobals->time; + SetThink( NULL ); + } +} + + +void CTestEffect::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( &CTestEffect::TestThink ); + pev->nextthink = gpGlobals->time + 0.1; + m_flStartTime = gpGlobals->time; +} + + + +// Blood effects +class CBlood : public CPointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline int Color( void ) { return pev->impulse; } + inline float BloodAmount( void ) { return pev->dmg; } + + inline void SetColor( int color ) { pev->impulse = color; } + inline void SetBloodAmount( float amount ) { pev->dmg = amount; } + + Vector Direction( void ); + Vector BloodPosition( CBaseEntity *pActivator ); + +private: +}; + +LINK_ENTITY_TO_CLASS( env_blood, CBlood ); + + + +#define SF_BLOOD_RANDOM 0x0001 +#define SF_BLOOD_STREAM 0x0002 +#define SF_BLOOD_PLAYER 0x0004 +#define SF_BLOOD_DECAL 0x0008 + +void CBlood::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + SetMovedir( pev ); +} + + +void CBlood::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "color")) + { + int color = atoi(pkvd->szValue); + switch( color ) + { + case 1: + SetColor( BLOOD_COLOR_YELLOW ); + break; + default: + SetColor( BLOOD_COLOR_RED ); + break; + } + + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "amount")) + { + SetBloodAmount( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +Vector CBlood::Direction( void ) +{ + if ( pev->spawnflags & SF_BLOOD_RANDOM ) + return UTIL_RandomBloodVector(); + + return pev->movedir; +} + + +Vector CBlood::BloodPosition( CBaseEntity *pActivator ) +{ + if ( pev->spawnflags & SF_BLOOD_PLAYER ) + { + edict_t *pPlayer; + + if ( pActivator && pActivator->IsPlayer() ) + { + pPlayer = pActivator->edict(); + } + else + pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + if ( pPlayer ) + return (pPlayer->v.origin + pPlayer->v.view_ofs) + Vector( RANDOM_FLOAT(-10,10), RANDOM_FLOAT(-10,10), RANDOM_FLOAT(-10,10) ); + } + + return pev->origin; +} + + +void CBlood::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->spawnflags & SF_BLOOD_STREAM ) + UTIL_BloodStream( BloodPosition(pActivator), Direction(), (Color() == BLOOD_COLOR_RED) ? 70 : Color(), BloodAmount() ); + else + UTIL_BloodDrips( BloodPosition(pActivator), Direction(), Color(), BloodAmount() ); + + if ( pev->spawnflags & SF_BLOOD_DECAL ) + { + Vector forward = Direction(); + Vector start = BloodPosition( pActivator ); + TraceResult tr; + + UTIL_TraceLine( start, start + forward * BloodAmount() * 2, ignore_monsters, NULL, &tr ); + if ( tr.flFraction != 1.0 ) + UTIL_BloodDecalTrace( &tr, Color() ); + } +} + + + +// Screen shake +class CShake : public CPointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline float Amplitude( void ) { return pev->scale; } + inline float Frequency( void ) { return pev->dmg_save; } + inline float Duration( void ) { return pev->dmg_take; } + inline float Radius( void ) { return pev->dmg; } + + inline void SetAmplitude( float amplitude ) { pev->scale = amplitude; } + inline void SetFrequency( float frequency ) { pev->dmg_save = frequency; } + inline void SetDuration( float duration ) { pev->dmg_take = duration; } + inline void SetRadius( float radius ) { pev->dmg = radius; } +private: +}; + +LINK_ENTITY_TO_CLASS( env_shake, CShake ); + +// pev->scale is amplitude +// pev->dmg_save is frequency +// pev->dmg_take is duration +// pev->dmg is radius +// radius of 0 means all players +// NOTE: UTIL_ScreenShake() will only shake players who are on the ground + +#define SF_SHAKE_EVERYONE 0x0001 // Don't check radius +// UNDONE: These don't work yet +#define SF_SHAKE_DISRUPT 0x0002 // Disrupt controls +#define SF_SHAKE_INAIR 0x0004 // Shake players in air + +void CShake::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + + if ( pev->spawnflags & SF_SHAKE_EVERYONE ) + pev->dmg = 0; +} + + +void CShake::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "amplitude")) + { + SetAmplitude( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "frequency")) + { + SetFrequency( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "duration")) + { + SetDuration( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "radius")) + { + SetRadius( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CShake::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + UTIL_ScreenShake( pev->origin, Amplitude(), Frequency(), Duration(), Radius() ); +} + + +class CFade : public CPointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline float Duration( void ) { return pev->dmg_take; } + inline float HoldTime( void ) { return pev->dmg_save; } + + inline void SetDuration( float duration ) { pev->dmg_take = duration; } + inline void SetHoldTime( float hold ) { pev->dmg_save = hold; } +private: +}; + +LINK_ENTITY_TO_CLASS( env_fade, CFade ); + +// pev->dmg_take is duration +// pev->dmg_save is hold duration +#define SF_FADE_IN 0x0001 // Fade in, not out +#define SF_FADE_MODULATE 0x0002 // Modulate, don't blend +#define SF_FADE_ONLYONE 0x0004 + +void CFade::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; +} + + +void CFade::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "duration")) + { + SetDuration( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "holdtime")) + { + SetHoldTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CFade::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int fadeFlags = 0; + + if ( !(pev->spawnflags & SF_FADE_IN) ) + fadeFlags |= FFADE_OUT; + + if ( pev->spawnflags & SF_FADE_MODULATE ) + fadeFlags |= FFADE_MODULATE; + + if ( pev->spawnflags & SF_FADE_ONLYONE ) + { + if ( pActivator->IsNetClient() ) + { + UTIL_ScreenFade( pActivator, pev->rendercolor, Duration(), HoldTime(), pev->renderamt, fadeFlags ); + } + } + else + { + UTIL_ScreenFadeAll( pev->rendercolor, Duration(), HoldTime(), pev->renderamt, fadeFlags ); + } + SUB_UseTargets( this, USE_TOGGLE, 0 ); +} + + +class CMessage : public CPointEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); +private: +}; + +LINK_ENTITY_TO_CLASS( env_message, CMessage ); + + +void CMessage::Spawn( void ) +{ + Precache(); + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + switch( pev->impulse ) + { + case 1: // Medium radius + pev->speed = ATTN_STATIC; + break; + + case 2: // Large radius + pev->speed = ATTN_NORM; + break; + + case 3: //EVERYWHERE + pev->speed = ATTN_NONE; + break; + + default: + case 0: // Small radius + pev->speed = ATTN_IDLE; + break; + } + pev->impulse = 0; + + // No volume, use normal + if ( pev->scale <= 0 ) + pev->scale = 1.0; +} + + +void CMessage::Precache( void ) +{ + if ( pev->noise ) + PRECACHE_SOUND( (char *)STRING(pev->noise) ); +} + +void CMessage::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "messagesound")) + { + pev->noise = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "messagevolume")) + { + pev->scale = atof(pkvd->szValue) * 0.1; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "messageattenuation")) + { + pev->impulse = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CMessage::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity *pPlayer = NULL; + + if ( pev->spawnflags & SF_MESSAGE_ALL ) + UTIL_ShowMessageAll( STRING(pev->message) ); + else + { + if ( pActivator && pActivator->IsPlayer() ) + pPlayer = pActivator; + else + { + pPlayer = CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) ); + } + if ( pPlayer ) + UTIL_ShowMessage( STRING(pev->message), pPlayer ); + } + if ( pev->noise ) + { + EMIT_SOUND( edict(), CHAN_BODY, STRING(pev->noise), pev->scale, pev->speed ); + } + if ( pev->spawnflags & SF_MESSAGE_ONCE ) + UTIL_Remove( this ); + + SUB_UseTargets( this, USE_TOGGLE, 0 ); +} + + + +//========================================================= +// FunnelEffect +//========================================================= +class CEnvFunnel : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int m_iSprite; // Don't save, precache +}; + +void CEnvFunnel :: Precache ( void ) +{ + m_iSprite = PRECACHE_MODEL ( "sprites/flare6.spr" ); +} + +LINK_ENTITY_TO_CLASS( env_funnel, CEnvFunnel ); + +void CEnvFunnel::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_LARGEFUNNEL ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( m_iSprite ); + + if ( pev->spawnflags & SF_FUNNEL_REVERSE )// funnel flows in reverse? + { + WRITE_SHORT( 1 ); + } + else + { + WRITE_SHORT( 0 ); + } + + + MESSAGE_END(); + + SetThink( &CEnvFunnel::SUB_Remove ); + pev->nextthink = gpGlobals->time; +} + +void CEnvFunnel::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; +} + +//========================================================= +// Beverage Dispenser +// overloaded pev->frags, is now a flag for whether or not a can is stuck in the dispenser. +// overloaded pev->health, is now how many cans remain in the machine. +//========================================================= +class CEnvBeverage : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; + +void CEnvBeverage :: Precache ( void ) +{ + PRECACHE_MODEL( "models/can.mdl" ); + PRECACHE_SOUND( "weapons/g_bounce3.wav" ); +} + +LINK_ENTITY_TO_CLASS( env_beverage, CEnvBeverage ); + +void CEnvBeverage::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->frags != 0 || pev->health <= 0 ) + { + // no more cans while one is waiting in the dispenser, or if I'm out of cans. + return; + } + + CBaseEntity *pCan = CBaseEntity::Create( "item_sodacan", pev->origin, pev->angles, edict() ); + + if ( pev->skin == 6 ) + { + // random + pCan->pev->skin = RANDOM_LONG( 0, 5 ); + } + else + { + pCan->pev->skin = pev->skin; + } + + pev->frags = 1; + pev->health--; + + //SetThink (SUB_Remove); + //pev->nextthink = gpGlobals->time; +} + +void CEnvBeverage::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + pev->frags = 0; + + if ( pev->health == 0 ) + { + pev->health = 10; + } +} + +//========================================================= +// Soda can +//========================================================= +class CItemSoda : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT CanThink ( void ); + void EXPORT CanTouch ( CBaseEntity *pOther ); +}; + +void CItemSoda :: Precache ( void ) +{ +} + +LINK_ENTITY_TO_CLASS( item_sodacan, CItemSoda ); + +void CItemSoda::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_TOSS; + + SET_MODEL ( ENT(pev), "models/can.mdl" ); + UTIL_SetSize ( pev, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ) ); + + SetThink (&CItemSoda::CanThink); + pev->nextthink = gpGlobals->time + 0.5; +} + +void CItemSoda::CanThink ( void ) +{ + EMIT_SOUND (ENT(pev), CHAN_WEAPON, "weapons/g_bounce3.wav", 1, ATTN_NORM ); + + pev->solid = SOLID_TRIGGER; + UTIL_SetSize ( pev, Vector ( -8, -8, 0 ), Vector ( 8, 8, 8 ) ); + SetThink ( NULL ); + SetTouch ( &CItemSoda::CanTouch ); +} + +void CItemSoda::CanTouch ( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + { + return; + } + + // spoit sound here + + pOther->TakeHealth( 1, DMG_GENERIC );// a bit of health. + + if ( !FNullEnt( pev->owner ) ) + { + // tell the machine the can was taken + pev->owner->v.frags = 0; + } + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = EF_NODRAW; + SetTouch ( NULL ); + SetThink ( &CItemSoda::SUB_Remove ); + pev->nextthink = gpGlobals->time; +} diff --git a/dlls/effects.h b/dlls/effects.h new file mode 100644 index 0000000..d3debd9 --- /dev/null +++ b/dlls/effects.h @@ -0,0 +1,209 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef EFFECTS_H +#define EFFECTS_H + +#define SF_BEAM_STARTON 0x0001 +#define SF_BEAM_TOGGLE 0x0002 +#define SF_BEAM_RANDOM 0x0004 +#define SF_BEAM_RING 0x0008 +#define SF_BEAM_SPARKSTART 0x0010 +#define SF_BEAM_SPARKEND 0x0020 +#define SF_BEAM_DECALS 0x0040 +#define SF_BEAM_SHADEIN 0x0080 +#define SF_BEAM_SHADEOUT 0x0100 +#define SF_BEAM_TEMPORARY 0x8000 + +#define SF_SPRITE_STARTON 0x0001 +#define SF_SPRITE_ONCE 0x0002 +#define SF_SPRITE_TEMPORARY 0x8000 + +class CSprite : public CPointEntity +{ +public: + void Spawn( void ); + void Precache( void ); + + int ObjectCaps( void ) + { + int flags = 0; + if ( pev->spawnflags & SF_SPRITE_TEMPORARY ) + flags = FCAP_DONT_SAVE; + return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | flags; + } + void EXPORT AnimateThink( void ); + void EXPORT ExpandThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Animate( float frames ); + void Expand( float scaleSpeed, float fadeSpeed ); + void SpriteInit( const char *pSpriteName, const Vector &origin ); + + inline void SetAttachment( edict_t *pEntity, int attachment ) + { + if ( pEntity ) + { + pev->skin = ENTINDEX(pEntity); + pev->body = attachment; + pev->aiment = pEntity; + pev->movetype = MOVETYPE_FOLLOW; + } + } + void TurnOff( void ); + void TurnOn( void ); + inline float Frames( void ) { return m_maxFrame; } + inline void SetTransparency( int rendermode, int r, int g, int b, int a, int fx ) + { + pev->rendermode = rendermode; + pev->rendercolor.x = r; + pev->rendercolor.y = g; + pev->rendercolor.z = b; + pev->renderamt = a; + pev->renderfx = fx; + } + inline void SetTexture( int spriteIndex ) { pev->modelindex = spriteIndex; } + inline void SetScale( float scale ) { pev->scale = scale; } + inline void SetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline void SetBrightness( int brightness ) { pev->renderamt = brightness; } + + inline void AnimateAndDie( float framerate ) + { + SetThink(&CSprite::AnimateUntilDead); + pev->framerate = framerate; + pev->dmgtime = gpGlobals->time + (m_maxFrame / framerate); + pev->nextthink = gpGlobals->time; + } + + void EXPORT AnimateUntilDead( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + static CSprite *SpriteCreate( const char *pSpriteName, const Vector &origin, BOOL animate ); + +private: + + float m_lastTime; + float m_maxFrame; +}; + + +class CBeam : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + int ObjectCaps( void ) + { + int flags = 0; + if ( pev->spawnflags & SF_BEAM_TEMPORARY ) + flags = FCAP_DONT_SAVE; + return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | flags; + } + + void EXPORT TriggerTouch( CBaseEntity *pOther ); + + // These functions are here to show the way beams are encoded as entities. + // Encoding beams as entities simplifies their management in the client/server architecture + inline void SetType( int type ) { pev->rendermode = (pev->rendermode & 0xF0) | (type&0x0F); } + inline void SetFlags( int flags ) { pev->rendermode = (pev->rendermode & 0x0F) | (flags&0xF0); } + inline void SetStartPos( const Vector& pos ) { pev->origin = pos; } + inline void SetEndPos( const Vector& pos ) { pev->angles = pos; } + void SetStartEntity( int entityIndex ); + void SetEndEntity( int entityIndex ); + + inline void SetStartAttachment( int attachment ) { pev->sequence = (pev->sequence & 0x0FFF) | ((attachment&0xF)<<12); } + inline void SetEndAttachment( int attachment ) { pev->skin = (pev->skin & 0x0FFF) | ((attachment&0xF)<<12); } + + inline void SetTexture( int spriteIndex ) { pev->modelindex = spriteIndex; } + inline void SetWidth( int width ) { pev->scale = width; } + inline void SetNoise( int amplitude ) { pev->body = amplitude; } + inline void SetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline void SetBrightness( int brightness ) { pev->renderamt = brightness; } + inline void SetFrame( float frame ) { pev->frame = frame; } + inline void SetScrollRate( int speed ) { pev->animtime = speed; } + + inline int GetType( void ) { return pev->rendermode & 0x0F; } + inline int GetFlags( void ) { return pev->rendermode & 0xF0; } + inline int GetStartEntity( void ) { return pev->sequence & 0xFFF; } + inline int GetEndEntity( void ) { return pev->skin & 0xFFF; } + + const Vector &GetStartPos( void ); + const Vector &GetEndPos( void ); + + Vector Center( void ) { return (GetStartPos() + GetEndPos()) * 0.5; }; // center point of beam + + inline int GetTexture( void ) { return pev->modelindex; } + inline int GetWidth( void ) { return pev->scale; } + inline int GetNoise( void ) { return pev->body; } + // inline void GetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline int GetBrightness( void ) { return pev->renderamt; } + inline int GetFrame( void ) { return pev->frame; } + inline int GetScrollRate( void ) { return pev->animtime; } + + // Call after you change start/end positions + void RelinkBeam( void ); +// void SetObjectCollisionBox( void ); + + void DoSparks( const Vector &start, const Vector &end ); + CBaseEntity *RandomTargetname( const char *szName ); + void BeamDamage( TraceResult *ptr ); + // Init after BeamCreate() + void BeamInit( const char *pSpriteName, int width ); + void PointsInit( const Vector &start, const Vector &end ); + void PointEntInit( const Vector &start, int endIndex ); + void EntsInit( int startIndex, int endIndex ); + void HoseInit( const Vector &start, const Vector &direction ); + + static CBeam *BeamCreate( const char *pSpriteName, int width ); + + inline void LiveForTime( float time ) { SetThink(&CBeam::SUB_Remove); pev->nextthink = gpGlobals->time + time; } + inline void BeamDamageInstant( TraceResult *ptr, float damage ) + { + pev->dmg = damage; + pev->dmgtime = gpGlobals->time - 1; + BeamDamage(ptr); + } +}; + + +#define SF_MESSAGE_ONCE 0x0001 // Fade in, not out +#define SF_MESSAGE_ALL 0x0002 // Send to all clients + + +class CLaser : public CBeam +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + void TurnOn( void ); + void TurnOff( void ); + int IsOn( void ); + + void FireAtPoint( TraceResult &point ); + + void EXPORT StrikeThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + CSprite *m_pSprite; + int m_iszSpriteName; + Vector m_firePosition; +}; + +#endif //EFFECTS_H diff --git a/dlls/egon.cpp b/dlls/egon.cpp new file mode 100644 index 0000000..c1cdf9a --- /dev/null +++ b/dlls/egon.cpp @@ -0,0 +1,568 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "effects.h" +#include "customentity.h" +#include "gamerules.h" + +#define EGON_PRIMARY_VOLUME 450 +#define EGON_BEAM_SPRITE "sprites/xbeam1.spr" +#define EGON_FLARE_SPRITE "sprites/XSpark1.spr" +#define EGON_SOUND_OFF "weapons/egon_off1.wav" +#define EGON_SOUND_RUN "weapons/egon_run3.wav" +#define EGON_SOUND_STARTUP "weapons/egon_windup2.wav" + +#define EGON_SWITCH_NARROW_TIME 0.75 // Time it takes to switch fire modes +#define EGON_SWITCH_WIDE_TIME 1.5 + +enum egon_e { + EGON_IDLE1 = 0, + EGON_FIDGET1, + EGON_ALTFIREON, + EGON_ALTFIRECYCLE, + EGON_ALTFIREOFF, + EGON_FIRE1, + EGON_FIRE2, + EGON_FIRE3, + EGON_FIRE4, + EGON_DRAW, + EGON_HOLSTER +}; + +LINK_ENTITY_TO_CLASS( weapon_egon, CEgon ); + +void CEgon::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_EGON; + SET_MODEL(ENT(pev), "models/w_egon.mdl"); + + m_iDefaultAmmo = EGON_DEFAULT_GIVE; + + FallInit();// get ready to fall down. +} + + +void CEgon::Precache( void ) +{ + PRECACHE_MODEL("models/w_egon.mdl"); + PRECACHE_MODEL("models/v_egon.mdl"); + PRECACHE_MODEL("models/p_egon.mdl"); + + PRECACHE_MODEL("models/w_9mmclip.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + + PRECACHE_SOUND( EGON_SOUND_OFF ); + PRECACHE_SOUND( EGON_SOUND_RUN ); + PRECACHE_SOUND( EGON_SOUND_STARTUP ); + + PRECACHE_MODEL( EGON_BEAM_SPRITE ); + PRECACHE_MODEL( EGON_FLARE_SPRITE ); + + PRECACHE_SOUND ("weapons/357_cock1.wav"); + + m_usEgonFire = PRECACHE_EVENT ( 1, "events/egon_fire.sc" ); + m_usEgonStop = PRECACHE_EVENT ( 1, "events/egon_stop.sc" ); +} + + +BOOL CEgon::Deploy( void ) +{ + m_deployed = FALSE; + m_fireState = FIRE_OFF; + return DefaultDeploy( "models/v_egon.mdl", "models/p_egon.mdl", EGON_DRAW, "egon" ); +} + +int CEgon::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + + + +void CEgon::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + SendWeaponAnim( EGON_HOLSTER ); + + EndAttack(); +} + +int CEgon::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "uranium"; + p->iMaxAmmo1 = URANIUM_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 3; + p->iPosition = 2; + p->iId = m_iId = WEAPON_EGON; + p->iFlags = 0; + p->iWeight = EGON_WEIGHT; + + return 1; +} + +#define EGON_PULSE_INTERVAL 0.1 +#define EGON_DISCHARGE_INTERVAL 0.1 + +float CEgon::GetPulseInterval( void ) +{ + return EGON_PULSE_INTERVAL; +} + +float CEgon::GetDischargeInterval( void ) +{ + return EGON_DISCHARGE_INTERVAL; +} + +BOOL CEgon::HasAmmo( void ) +{ + if ( m_pPlayer->ammo_uranium <= 0 ) + return FALSE; + + return TRUE; +} + +void CEgon::UseAmmo( int count ) +{ + if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] >= count ) + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= count; + else + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] = 0; +} + +void CEgon::Attack( void ) +{ + // don't fire underwater + if ( m_pPlayer->pev->waterlevel == 3 ) + { + + if ( m_fireState != FIRE_OFF || m_pBeam ) + { + EndAttack(); + } + else + { + PlayEmptySound( ); + } + return; + } + + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + Vector vecAiming = gpGlobals->v_forward; + Vector vecSrc = m_pPlayer->GetGunPosition( ); + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + switch( m_fireState ) + { + case FIRE_OFF: + { + if ( !HasAmmo() ) + { + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.25; + PlayEmptySound( ); + return; + } + + m_flAmmoUseTime = gpGlobals->time;// start using ammo ASAP. + + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usEgonFire, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, m_fireState, m_fireMode, 1, 0 ); + + m_shakeTime = 0; + + m_pPlayer->m_iWeaponVolume = EGON_PRIMARY_VOLUME; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.1; + pev->fuser1 = UTIL_WeaponTimeBase() + 2; + + pev->dmgtime = gpGlobals->time + GetPulseInterval(); + m_fireState = FIRE_CHARGE; + } + break; + + case FIRE_CHARGE: + { + Fire( vecSrc, vecAiming ); + m_pPlayer->m_iWeaponVolume = EGON_PRIMARY_VOLUME; + + if ( pev->fuser1 <= UTIL_WeaponTimeBase() ) + { + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usEgonFire, 0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, m_fireState, m_fireMode, 0, 0 ); + pev->fuser1 = 1000; + } + + if ( !HasAmmo() ) + { + EndAttack(); + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1.0; + } + + } + break; + } +} + +void CEgon::PrimaryAttack( void ) +{ + m_fireMode = FIRE_WIDE; + Attack(); + +} + +void CEgon::Fire( const Vector &vecOrigSrc, const Vector &vecDir ) +{ + Vector vecDest = vecOrigSrc + vecDir * 2048; + edict_t *pentIgnore; + TraceResult tr; + + pentIgnore = m_pPlayer->edict(); + Vector tmpSrc = vecOrigSrc + gpGlobals->v_up * -8 + gpGlobals->v_right * 3; + + // ALERT( at_console, "." ); + + UTIL_TraceLine( vecOrigSrc, vecDest, dont_ignore_monsters, pentIgnore, &tr ); + + if (tr.fAllSolid) + return; + +#ifndef CLIENT_DLL + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + if (pEntity == NULL) + return; + + if ( g_pGameRules->IsMultiplayer() ) + { + if ( m_pSprite && pEntity->pev->takedamage ) + { + m_pSprite->pev->effects &= ~EF_NODRAW; + } + else if ( m_pSprite ) + { + m_pSprite->pev->effects |= EF_NODRAW; + } + } + + +#endif + + float timedist; + + switch ( m_fireMode ) + { + case FIRE_NARROW: +#ifndef CLIENT_DLL + if ( pev->dmgtime < gpGlobals->time ) + { + // Narrow mode only does damage to the entity it hits + ClearMultiDamage(); + if (pEntity->pev->takedamage) + { + pEntity->TraceAttack( m_pPlayer->pev, gSkillData.plrDmgEgonNarrow, vecDir, &tr, DMG_ENERGYBEAM ); + } + ApplyMultiDamage(m_pPlayer->pev, m_pPlayer->pev); + + if ( g_pGameRules->IsMultiplayer() ) + { + // multiplayer uses 1 ammo every 1/10th second + if ( gpGlobals->time >= m_flAmmoUseTime ) + { + UseAmmo( 1 ); + m_flAmmoUseTime = gpGlobals->time + 0.1; + } + } + else + { + // single player, use 3 ammo/second + if ( gpGlobals->time >= m_flAmmoUseTime ) + { + UseAmmo( 1 ); + m_flAmmoUseTime = gpGlobals->time + 0.166; + } + } + + pev->dmgtime = gpGlobals->time + GetPulseInterval(); + } +#endif + timedist = ( pev->dmgtime - gpGlobals->time ) / GetPulseInterval(); + break; + + case FIRE_WIDE: +#ifndef CLIENT_DLL + if ( pev->dmgtime < gpGlobals->time ) + { + // wide mode does damage to the ent, and radius damage + ClearMultiDamage(); + if (pEntity->pev->takedamage) + { + pEntity->TraceAttack( m_pPlayer->pev, gSkillData.plrDmgEgonWide, vecDir, &tr, DMG_ENERGYBEAM | DMG_ALWAYSGIB); + } + ApplyMultiDamage(m_pPlayer->pev, m_pPlayer->pev); + + if ( g_pGameRules->IsMultiplayer() ) + { + // radius damage a little more potent in multiplayer. + ::RadiusDamage( tr.vecEndPos, pev, m_pPlayer->pev, gSkillData.plrDmgEgonWide/4, 128, CLASS_NONE, DMG_ENERGYBEAM | DMG_BLAST | DMG_ALWAYSGIB ); + } + + if ( !m_pPlayer->IsAlive() ) + return; + + if ( g_pGameRules->IsMultiplayer() ) + { + //multiplayer uses 5 ammo/second + if ( gpGlobals->time >= m_flAmmoUseTime ) + { + UseAmmo( 1 ); + m_flAmmoUseTime = gpGlobals->time + 0.2; + } + } + else + { + // Wide mode uses 10 charges per second in single player + if ( gpGlobals->time >= m_flAmmoUseTime ) + { + UseAmmo( 1 ); + m_flAmmoUseTime = gpGlobals->time + 0.1; + } + } + + pev->dmgtime = gpGlobals->time + GetDischargeInterval(); + if ( m_shakeTime < gpGlobals->time ) + { + UTIL_ScreenShake( tr.vecEndPos, 5.0, 150.0, 0.75, 250.0 ); + m_shakeTime = gpGlobals->time + 1.5; + } + } +#endif + timedist = ( pev->dmgtime - gpGlobals->time ) / GetDischargeInterval(); + break; + } + + if ( timedist < 0 ) + timedist = 0; + else if ( timedist > 1 ) + timedist = 1; + timedist = 1-timedist; + + UpdateEffect( tmpSrc, tr.vecEndPos, timedist ); +} + + +void CEgon::UpdateEffect( const Vector &startPoint, const Vector &endPoint, float timeBlend ) +{ +#ifndef CLIENT_DLL + if ( !m_pBeam ) + { + CreateEffect(); + } + + m_pBeam->SetStartPos( endPoint ); + m_pBeam->SetBrightness( 255 - (timeBlend*180) ); + m_pBeam->SetWidth( 40 - (timeBlend*20) ); + + if ( m_fireMode == FIRE_WIDE ) + m_pBeam->SetColor( 30 + (25*timeBlend), 30 + (30*timeBlend), 64 + 80*fabs(sin(gpGlobals->time*10)) ); + else + m_pBeam->SetColor( 60 + (25*timeBlend), 120 + (30*timeBlend), 64 + 80*fabs(sin(gpGlobals->time*10)) ); + + + UTIL_SetOrigin( m_pSprite->pev, endPoint ); + m_pSprite->pev->frame += 8 * gpGlobals->frametime; + if ( m_pSprite->pev->frame > m_pSprite->Frames() ) + m_pSprite->pev->frame = 0; + + m_pNoise->SetStartPos( endPoint ); + +#endif + +} + +void CEgon::CreateEffect( void ) +{ + +#ifndef CLIENT_DLL + DestroyEffect(); + + m_pBeam = CBeam::BeamCreate( EGON_BEAM_SPRITE, 40 ); + m_pBeam->PointEntInit( pev->origin, m_pPlayer->entindex() ); + m_pBeam->SetFlags( BEAM_FSINE ); + m_pBeam->SetEndAttachment( 1 ); + m_pBeam->pev->spawnflags |= SF_BEAM_TEMPORARY; // Flag these to be destroyed on save/restore or level transition + m_pBeam->pev->flags |= FL_SKIPLOCALHOST; + m_pBeam->pev->owner = m_pPlayer->edict(); + + m_pNoise = CBeam::BeamCreate( EGON_BEAM_SPRITE, 55 ); + m_pNoise->PointEntInit( pev->origin, m_pPlayer->entindex() ); + m_pNoise->SetScrollRate( 25 ); + m_pNoise->SetBrightness( 100 ); + m_pNoise->SetEndAttachment( 1 ); + m_pNoise->pev->spawnflags |= SF_BEAM_TEMPORARY; + m_pNoise->pev->flags |= FL_SKIPLOCALHOST; + m_pNoise->pev->owner = m_pPlayer->edict(); + + m_pSprite = CSprite::SpriteCreate( EGON_FLARE_SPRITE, pev->origin, FALSE ); + m_pSprite->pev->scale = 1.0; + m_pSprite->SetTransparency( kRenderGlow, 255, 255, 255, 255, kRenderFxNoDissipation ); + m_pSprite->pev->spawnflags |= SF_SPRITE_TEMPORARY; + m_pSprite->pev->flags |= FL_SKIPLOCALHOST; + m_pSprite->pev->owner = m_pPlayer->edict(); + + if ( m_fireMode == FIRE_WIDE ) + { + m_pBeam->SetScrollRate( 50 ); + m_pBeam->SetNoise( 20 ); + m_pNoise->SetColor( 50, 50, 255 ); + m_pNoise->SetNoise( 8 ); + } + else + { + m_pBeam->SetScrollRate( 110 ); + m_pBeam->SetNoise( 5 ); + m_pNoise->SetColor( 80, 120, 255 ); + m_pNoise->SetNoise( 2 ); + } +#endif + +} + + +void CEgon::DestroyEffect( void ) +{ + +#ifndef CLIENT_DLL + if ( m_pBeam ) + { + UTIL_Remove( m_pBeam ); + m_pBeam = NULL; + } + if ( m_pNoise ) + { + UTIL_Remove( m_pNoise ); + m_pNoise = NULL; + } + if ( m_pSprite ) + { + if ( m_fireMode == FIRE_WIDE ) + m_pSprite->Expand( 10, 500 ); + else + UTIL_Remove( m_pSprite ); + m_pSprite = NULL; + } +#endif + +} + + + +void CEgon::WeaponIdle( void ) +{ + ResetEmptySound( ); + + if ( m_flTimeWeaponIdle > gpGlobals->time ) + return; + + if ( m_fireState != FIRE_OFF ) + EndAttack(); + + int iAnim; + + float flRand = RANDOM_FLOAT(0,1); + + if ( flRand <= 0.5 ) + { + iAnim = EGON_IDLE1; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + } + else + { + iAnim = EGON_FIDGET1; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 3; + } + + SendWeaponAnim( iAnim ); + m_deployed = TRUE; +} + + + +void CEgon::EndAttack( void ) +{ + bool bMakeNoise = false; + + if ( m_fireState != FIRE_OFF ) //Checking the button just in case!. + bMakeNoise = true; + + PLAYBACK_EVENT_FULL( FEV_GLOBAL | FEV_RELIABLE, m_pPlayer->edict(), m_usEgonStop, 0, (float *)&m_pPlayer->pev->origin, (float *)&m_pPlayer->pev->angles, 0.0, 0.0, bMakeNoise, 0, 0, 0 ); + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 2.0; + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.5; + + m_fireState = FIRE_OFF; + + DestroyEffect(); +} + + + +class CEgonAmmo : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_chainammo.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_chainammo.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + if (pOther->GiveAmmo( AMMO_URANIUMBOX_GIVE, "uranium", URANIUM_MAX_CARRY ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + return TRUE; + } + return FALSE; + } +}; +LINK_ENTITY_TO_CLASS( ammo_egonclip, CEgonAmmo ); + +#endif \ No newline at end of file diff --git a/dlls/enginecallback.h b/dlls/enginecallback.h new file mode 100644 index 0000000..842fe34 --- /dev/null +++ b/dlls/enginecallback.h @@ -0,0 +1,158 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef ENGINECALLBACK_H +#define ENGINECALLBACK_H +#pragma once + +#include "event_flags.h" + +// Must be provided by user of this code +extern enginefuncs_t g_engfuncs; + +// The actual engine callbacks +#define GETPLAYERUSERID (*g_engfuncs.pfnGetPlayerUserId) +#define PRECACHE_MODEL (*g_engfuncs.pfnPrecacheModel) +#define PRECACHE_SOUND (*g_engfuncs.pfnPrecacheSound) +#define PRECACHE_GENERIC (*g_engfuncs.pfnPrecacheGeneric) +#define SET_MODEL (*g_engfuncs.pfnSetModel) +#define MODEL_INDEX (*g_engfuncs.pfnModelIndex) +#define MODEL_FRAMES (*g_engfuncs.pfnModelFrames) +#define SET_SIZE (*g_engfuncs.pfnSetSize) +#define CHANGE_LEVEL (*g_engfuncs.pfnChangeLevel) +#define GET_SPAWN_PARMS (*g_engfuncs.pfnGetSpawnParms) +#define SAVE_SPAWN_PARMS (*g_engfuncs.pfnSaveSpawnParms) +#define VEC_TO_YAW (*g_engfuncs.pfnVecToYaw) +#define VEC_TO_ANGLES (*g_engfuncs.pfnVecToAngles) +#define MOVE_TO_ORIGIN (*g_engfuncs.pfnMoveToOrigin) +#define oldCHANGE_YAW (*g_engfuncs.pfnChangeYaw) +#define CHANGE_PITCH (*g_engfuncs.pfnChangePitch) +#define MAKE_VECTORS (*g_engfuncs.pfnMakeVectors) +#define CREATE_ENTITY (*g_engfuncs.pfnCreateEntity) +#define REMOVE_ENTITY (*g_engfuncs.pfnRemoveEntity) +#define CREATE_NAMED_ENTITY (*g_engfuncs.pfnCreateNamedEntity) +#define MAKE_STATIC (*g_engfuncs.pfnMakeStatic) +#define ENT_IS_ON_FLOOR (*g_engfuncs.pfnEntIsOnFloor) +#define DROP_TO_FLOOR (*g_engfuncs.pfnDropToFloor) +#define WALK_MOVE (*g_engfuncs.pfnWalkMove) +#define SET_ORIGIN (*g_engfuncs.pfnSetOrigin) +#define EMIT_SOUND_DYN2 (*g_engfuncs.pfnEmitSound) +#define BUILD_SOUND_MSG (*g_engfuncs.pfnBuildSoundMsg) +#define TRACE_LINE (*g_engfuncs.pfnTraceLine) +#define TRACE_TOSS (*g_engfuncs.pfnTraceToss) +#define TRACE_MONSTER_HULL (*g_engfuncs.pfnTraceMonsterHull) +#define TRACE_HULL (*g_engfuncs.pfnTraceHull) +#define GET_AIM_VECTOR (*g_engfuncs.pfnGetAimVector) +#define SERVER_COMMAND (*g_engfuncs.pfnServerCommand) +#define SERVER_EXECUTE (*g_engfuncs.pfnServerExecute) +#define CLIENT_COMMAND (*g_engfuncs.pfnClientCommand) +#define PARTICLE_EFFECT (*g_engfuncs.pfnParticleEffect) +#define LIGHT_STYLE (*g_engfuncs.pfnLightStyle) +#define DECAL_INDEX (*g_engfuncs.pfnDecalIndex) +#define POINT_CONTENTS (*g_engfuncs.pfnPointContents) +#define CRC32_INIT (*g_engfuncs.pfnCRC32_Init) +#define CRC32_PROCESS_BUFFER (*g_engfuncs.pfnCRC32_ProcessBuffer) +#define CRC32_PROCESS_BYTE (*g_engfuncs.pfnCRC32_ProcessByte) +#define CRC32_FINAL (*g_engfuncs.pfnCRC32_Final) +#define RANDOM_LONG (*g_engfuncs.pfnRandomLong) +#define RANDOM_FLOAT (*g_engfuncs.pfnRandomFloat) +#define GETPLAYERAUTHID (*g_engfuncs.pfnGetPlayerAuthId) + +inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin = NULL, edict_t *ed = NULL ) { + (*g_engfuncs.pfnMessageBegin)(msg_dest, msg_type, pOrigin, ed); +} +#define MESSAGE_END (*g_engfuncs.pfnMessageEnd) +#define WRITE_BYTE (*g_engfuncs.pfnWriteByte) +#define WRITE_CHAR (*g_engfuncs.pfnWriteChar) +#define WRITE_SHORT (*g_engfuncs.pfnWriteShort) +#define WRITE_LONG (*g_engfuncs.pfnWriteLong) +#define WRITE_ANGLE (*g_engfuncs.pfnWriteAngle) +#define WRITE_COORD (*g_engfuncs.pfnWriteCoord) +#define WRITE_STRING (*g_engfuncs.pfnWriteString) +#define WRITE_ENTITY (*g_engfuncs.pfnWriteEntity) +#define CVAR_REGISTER (*g_engfuncs.pfnCVarRegister) +#define CVAR_GET_FLOAT (*g_engfuncs.pfnCVarGetFloat) +#define CVAR_GET_STRING (*g_engfuncs.pfnCVarGetString) +#define CVAR_SET_FLOAT (*g_engfuncs.pfnCVarSetFloat) +#define CVAR_SET_STRING (*g_engfuncs.pfnCVarSetString) +#define CVAR_GET_POINTER (*g_engfuncs.pfnCVarGetPointer) +#define ALERT (*g_engfuncs.pfnAlertMessage) +#define ENGINE_FPRINTF (*g_engfuncs.pfnEngineFprintf) +#define ALLOC_PRIVATE (*g_engfuncs.pfnPvAllocEntPrivateData) +inline void *GET_PRIVATE( edict_t *pent ) +{ + if ( pent ) + return pent->pvPrivateData; + return NULL; +} + +#define FREE_PRIVATE (*g_engfuncs.pfnFreeEntPrivateData) +//#define STRING (*g_engfuncs.pfnSzFromIndex) +#define ALLOC_STRING (*g_engfuncs.pfnAllocString) +#define FIND_ENTITY_BY_STRING (*g_engfuncs.pfnFindEntityByString) +#define GETENTITYILLUM (*g_engfuncs.pfnGetEntityIllum) +#define FIND_ENTITY_IN_SPHERE (*g_engfuncs.pfnFindEntityInSphere) +#define FIND_CLIENT_IN_PVS (*g_engfuncs.pfnFindClientInPVS) +#define EMIT_AMBIENT_SOUND (*g_engfuncs.pfnEmitAmbientSound) +#define GET_MODEL_PTR (*g_engfuncs.pfnGetModelPtr) +#define REG_USER_MSG (*g_engfuncs.pfnRegUserMsg) +#define GET_BONE_POSITION (*g_engfuncs.pfnGetBonePosition) +#define FUNCTION_FROM_NAME (*g_engfuncs.pfnFunctionFromName) +#define NAME_FOR_FUNCTION (*g_engfuncs.pfnNameForFunction) +#define TRACE_TEXTURE (*g_engfuncs.pfnTraceTexture) +#define CLIENT_PRINTF (*g_engfuncs.pfnClientPrintf) +#define CMD_ARGS (*g_engfuncs.pfnCmd_Args) +#define CMD_ARGC (*g_engfuncs.pfnCmd_Argc) +#define CMD_ARGV (*g_engfuncs.pfnCmd_Argv) +#define GET_ATTACHMENT (*g_engfuncs.pfnGetAttachment) +#define SET_VIEW (*g_engfuncs.pfnSetView) +#define SET_CROSSHAIRANGLE (*g_engfuncs.pfnCrosshairAngle) +#define LOAD_FILE_FOR_ME (*g_engfuncs.pfnLoadFileForMe) +#define FREE_FILE (*g_engfuncs.pfnFreeFile) +#define COMPARE_FILE_TIME (*g_engfuncs.pfnCompareFileTime) +#define GET_GAME_DIR (*g_engfuncs.pfnGetGameDir) +#define IS_MAP_VALID (*g_engfuncs.pfnIsMapValid) +#define NUMBER_OF_ENTITIES (*g_engfuncs.pfnNumberOfEntities) +#define IS_DEDICATED_SERVER (*g_engfuncs.pfnIsDedicatedServer) + +#define PRECACHE_EVENT (*g_engfuncs.pfnPrecacheEvent) +#define PLAYBACK_EVENT_FULL (*g_engfuncs.pfnPlaybackEvent) + +#define ENGINE_SET_PVS (*g_engfuncs.pfnSetFatPVS) +#define ENGINE_SET_PAS (*g_engfuncs.pfnSetFatPAS) + +#define ENGINE_CHECK_VISIBILITY (*g_engfuncs.pfnCheckVisibility) + +#define DELTA_SET ( *g_engfuncs.pfnDeltaSetField ) +#define DELTA_UNSET ( *g_engfuncs.pfnDeltaUnsetField ) +#define DELTA_ADDENCODER ( *g_engfuncs.pfnDeltaAddEncoder ) +#define ENGINE_CURRENT_PLAYER ( *g_engfuncs.pfnGetCurrentPlayer ) + +#define ENGINE_CANSKIP ( *g_engfuncs.pfnCanSkipPlayer ) + +#define DELTA_FINDFIELD ( *g_engfuncs.pfnDeltaFindField ) +#define DELTA_SETBYINDEX ( *g_engfuncs.pfnDeltaSetFieldByIndex ) +#define DELTA_UNSETBYINDEX ( *g_engfuncs.pfnDeltaUnsetFieldByIndex ) + +#define ENGINE_GETPHYSINFO ( *g_engfuncs.pfnGetPhysicsInfoString ) + +#define ENGINE_SETGROUPMASK ( *g_engfuncs.pfnSetGroupMask ) + +#define ENGINE_INSTANCE_BASELINE ( *g_engfuncs.pfnCreateInstancedBaseline ) + +#define ENGINE_FORCE_UNMODIFIED ( *g_engfuncs.pfnForceUnmodified ) + +#define PLAYER_CNX_STATS ( *g_engfuncs.pfnGetPlayerStats ) + +#endif //ENGINECALLBACK_H diff --git a/dlls/explode.cpp b/dlls/explode.cpp new file mode 100644 index 0000000..4e5f1c1 --- /dev/null +++ b/dlls/explode.cpp @@ -0,0 +1,273 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== explode.cpp ======================================================== + + Explosion-related code + +*/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "decals.h" +#include "explode.h" + +// Spark Shower +class CShower : public CBaseEntity +{ + void Spawn( void ); + void Think( void ); + void Touch( CBaseEntity *pOther ); + int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +LINK_ENTITY_TO_CLASS( spark_shower, CShower ); + +void CShower::Spawn( void ) +{ + pev->velocity = RANDOM_FLOAT( 200, 300 ) * pev->angles; + pev->velocity.x += RANDOM_FLOAT(-100.f,100.f); + pev->velocity.y += RANDOM_FLOAT(-100.f,100.f); + if ( pev->velocity.z >= 0 ) + pev->velocity.z += 200; + else + pev->velocity.z -= 200; + pev->movetype = MOVETYPE_BOUNCE; + pev->gravity = 0.5; + pev->nextthink = gpGlobals->time + 0.1; + pev->solid = SOLID_NOT; + SET_MODEL( edict(), "models/grenade.mdl"); // Need a model, just use the grenade, we don't draw it anyway + UTIL_SetSize(pev, g_vecZero, g_vecZero ); + pev->effects |= EF_NODRAW; + pev->speed = RANDOM_FLOAT( 0.5, 1.5 ); + + pev->angles = g_vecZero; +} + + +void CShower::Think( void ) +{ + UTIL_Sparks( pev->origin ); + + pev->speed -= 0.1; + if ( pev->speed > 0 ) + pev->nextthink = gpGlobals->time + 0.1; + else + UTIL_Remove( this ); + pev->flags &= ~FL_ONGROUND; +} + +void CShower::Touch( CBaseEntity *pOther ) +{ + if ( pev->flags & FL_ONGROUND ) + pev->velocity = pev->velocity * 0.1; + else + pev->velocity = pev->velocity * 0.6; + + if ( (pev->velocity.x*pev->velocity.x+pev->velocity.y*pev->velocity.y) < 10.0 ) + pev->speed = 0; +} + +class CEnvExplosion : public CBaseMonster +{ +public: + void Spawn( ); + void EXPORT Smoke ( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iMagnitude;// how large is the fireball? how much damage? + int m_spriteScale; // what's the exact fireball sprite scale? +}; + +TYPEDESCRIPTION CEnvExplosion::m_SaveData[] = +{ + DEFINE_FIELD( CEnvExplosion, m_iMagnitude, FIELD_INTEGER ), + DEFINE_FIELD( CEnvExplosion, m_spriteScale, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CEnvExplosion, CBaseMonster ); +LINK_ENTITY_TO_CLASS( env_explosion, CEnvExplosion ); + +void CEnvExplosion::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "iMagnitude")) + { + m_iMagnitude = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CEnvExplosion::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + + pev->movetype = MOVETYPE_NONE; + /* + if ( m_iMagnitude > 250 ) + { + m_iMagnitude = 250; + } + */ + + float flSpriteScale; + flSpriteScale = ( m_iMagnitude - 50) * 0.6; + + /* + if ( flSpriteScale > 50 ) + { + flSpriteScale = 50; + } + */ + if ( flSpriteScale < 10 ) + { + flSpriteScale = 10; + } + + m_spriteScale = (int)flSpriteScale; +} + +void CEnvExplosion::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + TraceResult tr; + + pev->model = iStringNull;//invisible + pev->solid = SOLID_NOT;// intangible + + Vector vecSpot;// trace starts here! + + vecSpot = pev->origin + Vector ( 0 , 0 , 8 ); + + UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -40 ), ignore_monsters, ENT(pev), & tr); + + // Pull out of the wall a bit + if ( tr.flFraction != 1.0 ) + { + pev->origin = tr.vecEndPos + (tr.vecPlaneNormal * (m_iMagnitude - 24) * 0.6); + } + else + { + pev->origin = pev->origin; + } + + // draw decal + if (! ( pev->spawnflags & SF_ENVEXPLOSION_NODECAL)) + { + if ( RANDOM_FLOAT( 0 , 1 ) < 0.5 ) + { + UTIL_DecalTrace( &tr, DECAL_SCORCH1 ); + } + else + { + UTIL_DecalTrace( &tr, DECAL_SCORCH2 ); + } + } + + // draw fireball + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NOFIREBALL ) ) + { + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( (BYTE)m_spriteScale ); // scale * 10 + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + } + else + { + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( 0 ); // no sprite + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + } + + // do damage + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NODAMAGE ) ) + { + RadiusDamage ( pev, pev, m_iMagnitude, CLASS_NONE, DMG_BLAST ); + } + + SetThink( &CEnvExplosion::Smoke ); + pev->nextthink = gpGlobals->time + 0.3; + + // draw sparks + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NOSPARKS ) ) + { + int sparkCount = RANDOM_LONG(0,3); + + for ( int i = 0; i < sparkCount; i++ ) + { + Create( "spark_shower", pev->origin, tr.vecPlaneNormal, NULL ); + } + } +} + +void CEnvExplosion::Smoke( void ) +{ + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NOSMOKE ) ) + { + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( (BYTE)m_spriteScale ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + } + + if ( !(pev->spawnflags & SF_ENVEXPLOSION_REPEATABLE) ) + { + UTIL_Remove( this ); + } +} + + +// HACKHACK -- create one of these and fake a keyvalue to get the right explosion setup +void ExplosionCreate( const Vector ¢er, const Vector &angles, edict_t *pOwner, int magnitude, BOOL doDamage ) +{ + KeyValueData kvd; + char buf[128]; + + CBaseEntity *pExplosion = CBaseEntity::Create( "env_explosion", center, angles, pOwner ); + sprintf( buf, "%3d", magnitude ); + kvd.szKeyName = "iMagnitude"; + kvd.szValue = buf; + pExplosion->KeyValue( &kvd ); + if ( !doDamage ) + pExplosion->pev->spawnflags |= SF_ENVEXPLOSION_NODAMAGE; + + pExplosion->Spawn(); + pExplosion->Use( NULL, NULL, USE_TOGGLE, 0 ); +} diff --git a/dlls/explode.h b/dlls/explode.h new file mode 100644 index 0000000..f179a13 --- /dev/null +++ b/dlls/explode.h @@ -0,0 +1,32 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef EXPLODE_H +#define EXPLODE_H + + +#define SF_ENVEXPLOSION_NODAMAGE ( 1 << 0 ) // when set, ENV_EXPLOSION will not actually inflict damage +#define SF_ENVEXPLOSION_REPEATABLE ( 1 << 1 ) // can this entity be refired? +#define SF_ENVEXPLOSION_NOFIREBALL ( 1 << 2 ) // don't draw the fireball +#define SF_ENVEXPLOSION_NOSMOKE ( 1 << 3 ) // don't draw the smoke +#define SF_ENVEXPLOSION_NODECAL ( 1 << 4 ) // don't make a scorch mark +#define SF_ENVEXPLOSION_NOSPARKS ( 1 << 5 ) // don't make a scorch mark + +extern DLL_GLOBAL short g_sModelIndexFireball; +extern DLL_GLOBAL short g_sModelIndexSmoke; + + +extern void ExplosionCreate( const Vector ¢er, const Vector &angles, edict_t *pOwner, int magnitude, BOOL doDamage ); + +#endif //EXPLODE_H diff --git a/dlls/extdll.h b/dlls/extdll.h new file mode 100644 index 0000000..87ae7f7 --- /dev/null +++ b/dlls/extdll.h @@ -0,0 +1,91 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef EXTDLL_H +#define EXTDLL_H + + +// +// Global header file for extension DLLs +// + +// Allow "DEBUG" in addition to default "_DEBUG" +#ifdef _DEBUG +#define DEBUG 1 +#endif + +// Silence certain warnings +#pragma warning(disable : 4244) // int or float down-conversion +#pragma warning(disable : 4305) // int or float data truncation +#pragma warning(disable : 4201) // nameless struct/union +#pragma warning(disable : 4514) // unreferenced inline function removed +#pragma warning(disable : 4100) // unreferenced formal parameter + +#include "archtypes.h" // DAL + +// Prevent tons of unused windows definitions +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOWINRES +#define NOSERVICE +#define NOMCX +#define NOIME +#include "windows.h" +#else // _WIN32 +#define FALSE 0 +#define TRUE (!FALSE) +typedef uint32 ULONG; +typedef unsigned char BYTE; +typedef int BOOL; +#define MAX_PATH PATH_MAX +#include +#include +#include // memset +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#define _vsnprintf(a,b,c,d) vsnprintf(a,b,c,d) +#endif +#endif //_WIN32 + +// Misc C-runtime library headers +#include "stdio.h" +#include "stdlib.h" +#include "math.h" + +// Header file containing definition of globalvars_t and entvars_t +typedef unsigned int func_t; // +typedef unsigned int string_t; // from engine's pr_comp.h; +typedef float vec_t; // needed before including progdefs.h + +// Vector class +#include "vector.h" + +// Defining it as a (bogus) struct helps enforce type-checking +#define vec3_t Vector + +// Shared engine/DLL constants +#include "const.h" +#include "progdefs.h" +#include "edict.h" + +// Shared header describing protocol between engine and DLLs +#include "eiface.h" + +// Shared header between the client DLL and the game DLLs +#include "cdll_dll.h" + +#endif //EXTDLL_H diff --git a/dlls/flyingmonster.cpp b/dlls/flyingmonster.cpp new file mode 100644 index 0000000..4fae59e --- /dev/null +++ b/dlls/flyingmonster.cpp @@ -0,0 +1,281 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "flyingmonster.h" + +#define FLYING_AE_FLAP (8) +#define FLYING_AE_FLAPSOUND (9) + + +extern DLL_GLOBAL edict_t *g_pBodyQueueHead; + +int CFlyingMonster :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ) +{ + // UNDONE: need to check more than the endpoint + if (FBitSet(pev->flags, FL_SWIM) && (UTIL_PointContents(vecEnd) != CONTENTS_WATER)) + { + // ALERT(at_aiconsole, "can't swim out of water\n"); + return FALSE; + } + + TraceResult tr; + + UTIL_TraceHull( vecStart + Vector( 0, 0, 32 ), vecEnd + Vector( 0, 0, 32 ), dont_ignore_monsters, large_hull, edict(), &tr ); + + // ALERT( at_console, "%.0f %.0f %.0f : ", vecStart.x, vecStart.y, vecStart.z ); + // ALERT( at_console, "%.0f %.0f %.0f\n", vecEnd.x, vecEnd.y, vecEnd.z ); + + if (pflDist) + { + *pflDist = ( (tr.vecEndPos - Vector( 0, 0, 32 )) - vecStart ).Length();// get the distance. + } + + // ALERT( at_console, "check %d %d %f\n", tr.fStartSolid, tr.fAllSolid, tr.flFraction ); + if (tr.fStartSolid || tr.flFraction < 1.0) + { + if ( pTarget && pTarget->edict() == gpGlobals->trace_ent ) + return LOCALMOVE_VALID; + return LOCALMOVE_INVALID; + } + + return LOCALMOVE_VALID; +} + + +BOOL CFlyingMonster :: FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ) +{ + return CBaseMonster::FTriangulate( vecStart, vecEnd, flDist, pTargetEnt, pApex ); +} + + +Activity CFlyingMonster :: GetStoppedActivity( void ) +{ + if ( pev->movetype != MOVETYPE_FLY ) // UNDONE: Ground idle here, IDLE may be something else + return ACT_IDLE; + + return ACT_HOVER; +} + + +void CFlyingMonster :: Stop( void ) +{ + Activity stopped = GetStoppedActivity(); + if ( m_IdealActivity != stopped ) + { + m_flightSpeed = 0; + m_IdealActivity = stopped; + } + pev->angles.z = 0; + pev->angles.x = 0; + m_vecTravel = g_vecZero; +} + + +float CFlyingMonster :: ChangeYaw( int speed ) +{ + if ( pev->movetype == MOVETYPE_FLY ) + { + float diff = FlYawDiff(); + float target = 0; + + if ( m_IdealActivity != GetStoppedActivity() ) + { + if ( diff < -20 ) + target = 90; + else if ( diff > 20 ) + target = -90; + } + pev->angles.z = UTIL_Approach( target, pev->angles.z, 220.0 * gpGlobals->frametime ); + } + return CBaseMonster::ChangeYaw( speed ); +} + + +void CFlyingMonster :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->movetype = MOVETYPE_STEP; + ClearBits( pev->flags, FL_ONGROUND ); + pev->angles.z = 0; + pev->angles.x = 0; + CBaseMonster::Killed( pevAttacker, iGib ); +} + + +void CFlyingMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case FLYING_AE_FLAP: + m_flightSpeed = 400; + break; + + case FLYING_AE_FLAPSOUND: + if ( m_pFlapSound ) + EMIT_SOUND( edict(), CHAN_BODY, m_pFlapSound, 1, ATTN_NORM ); + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + + +void CFlyingMonster :: Move( float flInterval ) +{ + if ( pev->movetype == MOVETYPE_FLY ) + m_flGroundSpeed = m_flightSpeed; + CBaseMonster::Move( flInterval ); +} + + +BOOL CFlyingMonster:: ShouldAdvanceRoute( float flWaypointDist ) +{ + // Get true 3D distance to the goal so we actually reach the correct height + if ( m_Route[ m_iRouteIndex ].iType & bits_MF_IS_GOAL ) + flWaypointDist = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Length(); + + if ( flWaypointDist <= 64 + (m_flGroundSpeed * gpGlobals->frametime) ) + return TRUE; + + return FALSE; +} + + +void CFlyingMonster::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) +{ + if ( pev->movetype == MOVETYPE_FLY ) + { + if ( gpGlobals->time - m_stopTime > 1.0 ) + { + if ( m_IdealActivity != m_movementActivity ) + { + m_IdealActivity = m_movementActivity; + m_flGroundSpeed = m_flightSpeed = 200; + } + } + Vector vecMove = pev->origin + (( vecDir + (m_vecTravel * m_momentum) ).Normalize() * (m_flGroundSpeed * flInterval)); + + if ( m_IdealActivity != m_movementActivity ) + { + m_flightSpeed = UTIL_Approach( 100, m_flightSpeed, 75 * gpGlobals->frametime ); + if ( m_flightSpeed < 100 ) + m_stopTime = gpGlobals->time; + } + else + m_flightSpeed = UTIL_Approach( 20, m_flightSpeed, 300 * gpGlobals->frametime ); + + if ( CheckLocalMove ( pev->origin, vecMove, pTargetEnt, NULL ) ) + { + m_vecTravel = (vecMove - pev->origin); + m_vecTravel = m_vecTravel.Normalize(); + UTIL_MoveToOrigin(ENT(pev), vecMove, (m_flGroundSpeed * flInterval), MOVE_STRAFE); + } + else + { + m_IdealActivity = GetStoppedActivity(); + m_stopTime = gpGlobals->time; + m_vecTravel = g_vecZero; + } + } + else + CBaseMonster::MoveExecute( pTargetEnt, vecDir, flInterval ); +} + + +float CFlyingMonster::CeilingZ( const Vector &position ) +{ + TraceResult tr; + + Vector minUp = position; + Vector maxUp = position; + maxUp.z += 4096.0; + + UTIL_TraceLine(position, maxUp, ignore_monsters, NULL, &tr); + if (tr.flFraction != 1.0) + maxUp.z = tr.vecEndPos.z; + + if ((pev->flags) & FL_SWIM) + { + return UTIL_WaterLevel( position, minUp.z, maxUp.z ); + } + return maxUp.z; +} + +BOOL CFlyingMonster::ProbeZ( const Vector &position, const Vector &probe, float *pFraction) +{ + int conPosition = UTIL_PointContents(position); + if ( (((pev->flags) & FL_SWIM) == FL_SWIM) ^ (conPosition == CONTENTS_WATER)) + { + // SWIMING & !WATER + // or FLYING & WATER + // + *pFraction = 0.0; + return TRUE; // We hit a water boundary because we are where we don't belong. + } + int conProbe = UTIL_PointContents(probe); + if (conProbe == conPosition) + { + // The probe is either entirely inside the water (for fish) or entirely + // outside the water (for birds). + // + *pFraction = 1.0; + return FALSE; + } + + Vector ProbeUnit = (probe-position).Normalize(); + float ProbeLength = (probe-position).Length(); + float maxProbeLength = ProbeLength; + float minProbeLength = 0; + + float diff = maxProbeLength - minProbeLength; + while (diff > 1.0) + { + float midProbeLength = minProbeLength + diff/2.0; + Vector midProbeVec = midProbeLength * ProbeUnit; + if (UTIL_PointContents(position+midProbeVec) == conPosition) + { + minProbeLength = midProbeLength; + } + else + { + maxProbeLength = midProbeLength; + } + diff = maxProbeLength - minProbeLength; + } + *pFraction = minProbeLength/ProbeLength; + + return TRUE; +} + +float CFlyingMonster::FloorZ( const Vector &position ) +{ + TraceResult tr; + + Vector down = position; + down.z -= 2048; + + UTIL_TraceLine( position, down, ignore_monsters, NULL, &tr ); + + if ( tr.flFraction != 1.0 ) + return tr.vecEndPos.z; + + return down.z; +} + diff --git a/dlls/flyingmonster.h b/dlls/flyingmonster.h new file mode 100644 index 0000000..85ef61a --- /dev/null +++ b/dlls/flyingmonster.h @@ -0,0 +1,53 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +// Base class for flying monsters. This overrides the movement test & execution code from CBaseMonster + +#ifndef FLYINGMONSTER_H +#define FLYINGMONSTER_H + +class CFlyingMonster : public CBaseMonster +{ +public: + int CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist );// check validity of a straight move through space + BOOL FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ); + Activity GetStoppedActivity( void ); + void Killed( entvars_t *pevAttacker, int iGib ); + void Stop( void ); + float ChangeYaw( int speed ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ); + void Move( float flInterval = 0.1 ); + BOOL ShouldAdvanceRoute( float flWaypointDist ); + + inline void SetFlyingMomentum( float momentum ) { m_momentum = momentum; } + inline void SetFlyingFlapSound( const char *pFlapSound ) { m_pFlapSound = pFlapSound; } + inline void SetFlyingSpeed( float speed ) { m_flightSpeed = speed; } + float CeilingZ( const Vector &position ); + float FloorZ( const Vector &position ); + BOOL ProbeZ( const Vector &position, const Vector &probe, float *pFraction ); + + + // UNDONE: Save/restore this stuff!!! +protected: + Vector m_vecTravel; // Current direction + float m_flightSpeed; // Current flight speed (decays when not flapping or gliding) + float m_stopTime; // Last time we stopped (to avoid switching states too soon) + float m_momentum; // Weight for desired vs. momentum velocity + const char *m_pFlapSound; +}; + + +#endif //FLYINGMONSTER_H + diff --git a/dlls/func_break.cpp b/dlls/func_break.cpp new file mode 100644 index 0000000..fb33522 --- /dev/null +++ b/dlls/func_break.cpp @@ -0,0 +1,1004 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== bmodels.cpp ======================================================== + + spawn, think, and use functions for entities that use brush models + +*/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "func_break.h" +#include "decals.h" +#include "explode.h" + +extern DLL_GLOBAL Vector g_vecAttackDir; + +// =================== FUNC_Breakable ============================================== + +// Just add more items to the bottom of this array and they will automagically be supported +// This is done instead of just a classname in the FGD so we can control which entities can +// be spawned, and still remain fairly flexible +const char *CBreakable::pSpawnObjects[] = +{ + NULL, // 0 + "item_battery", // 1 + "item_healthkit", // 2 + "weapon_9mmhandgun",// 3 + "ammo_9mmclip", // 4 + "weapon_9mmAR", // 5 + "ammo_9mmAR", // 6 + "ammo_ARgrenades", // 7 + "weapon_shotgun", // 8 + "ammo_buckshot", // 9 + "weapon_crossbow", // 10 + "ammo_crossbow", // 11 + "weapon_357", // 12 + "ammo_357", // 13 + "weapon_rpg", // 14 + "ammo_rpgclip", // 15 + "ammo_gaussclip", // 16 + "weapon_handgrenade",// 17 + "weapon_tripmine", // 18 + "weapon_satchel", // 19 + "weapon_snark", // 20 + "weapon_hornetgun", // 21 +}; + +void CBreakable::KeyValue( KeyValueData* pkvd ) +{ + // UNDONE_WC: explicitly ignoring these fields, but they shouldn't be in the map file! + if (FStrEq(pkvd->szKeyName, "explosion")) + { + if (!stricmp(pkvd->szValue, "directed")) + m_Explosion = expDirected; + else if (!stricmp(pkvd->szValue, "random")) + m_Explosion = expRandom; + else + m_Explosion = expRandom; + + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "material")) + { + int i = atoi( pkvd->szValue); + + // 0:glass, 1:metal, 2:flesh, 3:wood + + if ((i < 0) || (i >= matLastMaterial)) + m_Material = matWood; + else + m_Material = (Materials)i; + + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "deadmodel")) + { + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "shards")) + { +// m_iShards = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "gibmodel") ) + { + m_iszGibModel = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spawnobject") ) + { + int object = atoi( pkvd->szValue ); + if ( object > 0 && object < ARRAYSIZE(pSpawnObjects) ) + m_iszSpawnObject = MAKE_STRING( pSpawnObjects[object] ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "explodemagnitude") ) + { + ExplosionSetMagnitude( atoi( pkvd->szValue ) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "lip") ) + pkvd->fHandled = TRUE; + else + CBaseDelay::KeyValue( pkvd ); +} + + +// +// func_breakable - bmodel that breaks into pieces after taking damage +// +LINK_ENTITY_TO_CLASS( func_breakable, CBreakable ); +TYPEDESCRIPTION CBreakable::m_SaveData[] = +{ + DEFINE_FIELD( CBreakable, m_Material, FIELD_INTEGER ), + DEFINE_FIELD( CBreakable, m_Explosion, FIELD_INTEGER ), + +// Don't need to save/restore these because we precache after restore +// DEFINE_FIELD( CBreakable, m_idShard, FIELD_INTEGER ), + + DEFINE_FIELD( CBreakable, m_angle, FIELD_FLOAT ), + DEFINE_FIELD( CBreakable, m_iszGibModel, FIELD_STRING ), + DEFINE_FIELD( CBreakable, m_iszSpawnObject, FIELD_STRING ), + + // Explosion magnitude is stored in pev->impulse +}; + +IMPLEMENT_SAVERESTORE( CBreakable, CBaseEntity ); + +void CBreakable::Spawn( void ) +{ + Precache( ); + + if ( FBitSet( pev->spawnflags, SF_BREAK_TRIGGER_ONLY ) ) + pev->takedamage = DAMAGE_NO; + else + pev->takedamage = DAMAGE_YES; + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + m_angle = pev->angles.y; + pev->angles.y = 0; + + // HACK: matGlass can receive decals, we need the client to know about this + // so use class to store the material flag + if ( m_Material == matGlass ) + { + pev->playerclass = 1; + } + + SET_MODEL(ENT(pev), STRING(pev->model) );//set size and link into world. + + SetTouch( &CBreakable::BreakTouch ); + if ( FBitSet( pev->spawnflags, SF_BREAK_TRIGGER_ONLY ) ) // Only break on trigger + SetTouch( NULL ); + + // Flag unbreakable glass as "worldbrush" so it will block ALL tracelines + if ( !IsBreakable() && pev->rendermode != kRenderNormal ) + pev->flags |= FL_WORLDBRUSH; +} + + +const char *CBreakable::pSoundsWood[] = +{ + "debris/wood1.wav", + "debris/wood2.wav", + "debris/wood3.wav", +}; + +const char *CBreakable::pSoundsFlesh[] = +{ + "debris/flesh1.wav", + "debris/flesh2.wav", + "debris/flesh3.wav", + "debris/flesh5.wav", + "debris/flesh6.wav", + "debris/flesh7.wav", +}; + +const char *CBreakable::pSoundsMetal[] = +{ + "debris/metal1.wav", + "debris/metal2.wav", + "debris/metal3.wav", +}; + +const char *CBreakable::pSoundsConcrete[] = +{ + "debris/concrete1.wav", + "debris/concrete2.wav", + "debris/concrete3.wav", +}; + + +const char *CBreakable::pSoundsGlass[] = +{ + "debris/glass1.wav", + "debris/glass2.wav", + "debris/glass3.wav", +}; + +const char **CBreakable::MaterialSoundList( Materials precacheMaterial, int &soundCount ) +{ + const char **pSoundList = NULL; + + switch ( precacheMaterial ) + { + case matWood: + pSoundList = pSoundsWood; + soundCount = ARRAYSIZE(pSoundsWood); + break; + case matFlesh: + pSoundList = pSoundsFlesh; + soundCount = ARRAYSIZE(pSoundsFlesh); + break; + case matComputer: + case matUnbreakableGlass: + case matGlass: + pSoundList = pSoundsGlass; + soundCount = ARRAYSIZE(pSoundsGlass); + break; + + case matMetal: + pSoundList = pSoundsMetal; + soundCount = ARRAYSIZE(pSoundsMetal); + break; + + case matCinderBlock: + case matRocks: + pSoundList = pSoundsConcrete; + soundCount = ARRAYSIZE(pSoundsConcrete); + break; + + + case matCeilingTile: + case matNone: + default: + soundCount = 0; + break; + } + + return pSoundList; +} + +void CBreakable::MaterialSoundPrecache( Materials precacheMaterial ) +{ + const char **pSoundList; + int i, soundCount = 0; + + pSoundList = MaterialSoundList( precacheMaterial, soundCount ); + + for ( i = 0; i < soundCount; i++ ) + { + PRECACHE_SOUND( (char *)pSoundList[i] ); + } +} + +void CBreakable::MaterialSoundRandom( edict_t *pEdict, Materials soundMaterial, float volume ) +{ + const char **pSoundList; + int soundCount = 0; + + pSoundList = MaterialSoundList( soundMaterial, soundCount ); + + if ( soundCount ) + EMIT_SOUND( pEdict, CHAN_BODY, pSoundList[ RANDOM_LONG(0,soundCount-1) ], volume, 1.0 ); +} + + +void CBreakable::Precache( void ) +{ + const char *pGibName; + + switch (m_Material) + { + case matWood: + pGibName = "models/woodgibs.mdl"; + + PRECACHE_SOUND("debris/bustcrate1.wav"); + PRECACHE_SOUND("debris/bustcrate2.wav"); + break; + case matFlesh: + pGibName = "models/fleshgibs.mdl"; + + PRECACHE_SOUND("debris/bustflesh1.wav"); + PRECACHE_SOUND("debris/bustflesh2.wav"); + break; + case matComputer: + PRECACHE_SOUND("buttons/spark5.wav"); + PRECACHE_SOUND("buttons/spark6.wav"); + pGibName = "models/computergibs.mdl"; + + PRECACHE_SOUND("debris/bustmetal1.wav"); + PRECACHE_SOUND("debris/bustmetal2.wav"); + break; + + case matUnbreakableGlass: + case matGlass: + pGibName = "models/glassgibs.mdl"; + + PRECACHE_SOUND("debris/bustglass1.wav"); + PRECACHE_SOUND("debris/bustglass2.wav"); + break; + case matMetal: + pGibName = "models/metalplategibs.mdl"; + + PRECACHE_SOUND("debris/bustmetal1.wav"); + PRECACHE_SOUND("debris/bustmetal2.wav"); + break; + case matCinderBlock: + pGibName = "models/cindergibs.mdl"; + + PRECACHE_SOUND("debris/bustconcrete1.wav"); + PRECACHE_SOUND("debris/bustconcrete2.wav"); + break; + case matRocks: + pGibName = "models/rockgibs.mdl"; + + PRECACHE_SOUND("debris/bustconcrete1.wav"); + PRECACHE_SOUND("debris/bustconcrete2.wav"); + break; + case matCeilingTile: + pGibName = "models/ceilinggibs.mdl"; + + PRECACHE_SOUND ("debris/bustceiling.wav"); + break; + } + MaterialSoundPrecache( m_Material ); + if ( m_iszGibModel ) + pGibName = STRING(m_iszGibModel); + + m_idShard = PRECACHE_MODEL( (char *)pGibName ); + + // Precache the spawn item's data + if ( m_iszSpawnObject ) + UTIL_PrecacheOther( (char *)STRING( m_iszSpawnObject ) ); +} + +// play shard sound when func_breakable takes damage. +// the more damage, the louder the shard sound. + + +void CBreakable::DamageSound( void ) +{ + int pitch; + float fvol; + char *rgpsz[6]; + int i; + int material = m_Material; + +// if (RANDOM_LONG(0,1)) +// return; + + if (RANDOM_LONG(0,2)) + pitch = PITCH_NORM; + else + pitch = 95 + RANDOM_LONG(0,34); + + fvol = RANDOM_FLOAT(0.75, 1.0); + + if (material == matComputer && RANDOM_LONG(0,1)) + material = matMetal; + + switch (material) + { + case matComputer: + case matGlass: + case matUnbreakableGlass: + rgpsz[0] = "debris/glass1.wav"; + rgpsz[1] = "debris/glass2.wav"; + rgpsz[2] = "debris/glass3.wav"; + i = 3; + break; + + case matWood: + rgpsz[0] = "debris/wood1.wav"; + rgpsz[1] = "debris/wood2.wav"; + rgpsz[2] = "debris/wood3.wav"; + i = 3; + break; + + case matMetal: + rgpsz[0] = "debris/metal1.wav"; + rgpsz[1] = "debris/metal3.wav"; + rgpsz[2] = "debris/metal2.wav"; + i = 2; + break; + + case matFlesh: + rgpsz[0] = "debris/flesh1.wav"; + rgpsz[1] = "debris/flesh2.wav"; + rgpsz[2] = "debris/flesh3.wav"; + rgpsz[3] = "debris/flesh5.wav"; + rgpsz[4] = "debris/flesh6.wav"; + rgpsz[5] = "debris/flesh7.wav"; + i = 6; + break; + + case matRocks: + case matCinderBlock: + rgpsz[0] = "debris/concrete1.wav"; + rgpsz[1] = "debris/concrete2.wav"; + rgpsz[2] = "debris/concrete3.wav"; + i = 3; + break; + + case matCeilingTile: + // UNDONE: no ceiling tile shard sound yet + i = 0; + break; + } + + if (i) + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, rgpsz[RANDOM_LONG(0,i-1)], fvol, ATTN_NORM, 0, pitch); +} + +void CBreakable::BreakTouch( CBaseEntity *pOther ) +{ + float flDamage; + entvars_t* pevToucher = pOther->pev; + + // only players can break these right now + if ( !pOther->IsPlayer() || !IsBreakable() ) + { + return; + } + + if ( FBitSet ( pev->spawnflags, SF_BREAK_TOUCH ) ) + {// can be broken when run into + flDamage = pevToucher->velocity.Length() * 0.01; + + if (flDamage >= pev->health) + { + SetTouch( NULL ); + TakeDamage(pevToucher, pevToucher, flDamage, DMG_CRUSH); + + // do a little damage to player if we broke glass or computer + pOther->TakeDamage( pev, pev, flDamage/4, DMG_SLASH ); + } + } + + if ( FBitSet ( pev->spawnflags, SF_BREAK_PRESSURE ) && pevToucher->absmin.z >= pev->maxs.z - 2 ) + {// can be broken when stood upon + + // play creaking sound here. + DamageSound(); + + SetThink ( &CBreakable::Die ); + SetTouch( NULL ); + + if ( m_flDelay == 0 ) + {// !!!BUGBUG - why doesn't zero delay work? + m_flDelay = 0.1; + } + + pev->nextthink = pev->ltime + m_flDelay; + + } + +} + + +// +// Smash the our breakable object +// + +// Break when triggered +void CBreakable::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( IsBreakable() ) + { + pev->angles.y = m_angle; + UTIL_MakeVectors(pev->angles); + g_vecAttackDir = gpGlobals->v_forward; + + Die(); + } +} + + +void CBreakable::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + // random spark if this is a 'computer' object + if (RANDOM_LONG(0,1) ) + { + switch( m_Material ) + { + case matComputer: + { + UTIL_Sparks( ptr->vecEndPos ); + + float flVolume = RANDOM_FLOAT ( 0.7 , 1.0 );//random volume range + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM); break; + } + } + break; + + case matUnbreakableGlass: + UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT(0.5,1.5) ); + break; + } + } + + CBaseDelay::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + +//========================================================= +// Special takedamage for func_breakable. Allows us to make +// exceptions that are breakable-specific +// bitsDamageType indicates the type of damage sustained ie: DMG_CRUSH +//========================================================= +int CBreakable :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecTemp; + + // if Attacker == Inflictor, the attack was a melee or other instant-hit attack. + // (that is, no actual entity projectile was involved in the attack so use the shooter's origin). + if ( pevAttacker == pevInflictor ) + { + vecTemp = pevInflictor->origin - ( pev->absmin + ( pev->size * 0.5 ) ); + + // if a client hit the breakable with a crowbar, and breakable is crowbar-sensitive, break it now. + if ( FBitSet ( pevAttacker->flags, FL_CLIENT ) && + FBitSet ( pev->spawnflags, SF_BREAK_CROWBAR ) && (bitsDamageType & DMG_CLUB)) + flDamage = pev->health; + } + else + // an actual missile was involved. + { + vecTemp = pevInflictor->origin - ( pev->absmin + ( pev->size * 0.5 ) ); + } + + if (!IsBreakable()) + return 0; + + // Breakables take double damage from the crowbar + if ( bitsDamageType & DMG_CLUB ) + flDamage *= 2; + + // Boxes / glass / etc. don't take much poison damage, just the impact of the dart - consider that 10% + if ( bitsDamageType & DMG_POISON ) + flDamage *= 0.1; + +// this global is still used for glass and other non-monster killables, along with decals. + g_vecAttackDir = vecTemp.Normalize(); + +// do the damage + pev->health -= flDamage; + if (pev->health <= 0) + { + Killed( pevAttacker, GIB_NORMAL ); + Die(); + return 0; + } + + // Make a shard noise each time func breakable is hit. + // Don't play shard noise if cbreakable actually died. + + DamageSound(); + + return 1; +} + + +void CBreakable::Die( void ) +{ + Vector vecSpot;// shard origin + Vector vecVelocity;// shard velocity + CBaseEntity *pEntity = NULL; + char cFlag = 0; + int pitch; + float fvol; + + pitch = 95 + RANDOM_LONG(0,29); + + if (pitch > 97 && pitch < 103) + pitch = 100; + + // The more negative pev->health, the louder + // the sound should be. + + fvol = RANDOM_FLOAT(0.85, 1.0) + (abs(pev->health) / 100.0); + + if (fvol > 1.0) + fvol = 1.0; + + + switch (m_Material) + { + case matGlass: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustglass1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustglass2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_GLASS; + break; + + case matWood: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustcrate1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustcrate2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_WOOD; + break; + + case matComputer: + case matMetal: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustmetal1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustmetal2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_METAL; + break; + + case matFlesh: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustflesh1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustflesh2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_FLESH; + break; + + case matRocks: + case matCinderBlock: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustconcrete1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustconcrete2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_CONCRETE; + break; + + case matCeilingTile: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustceiling.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + + + if (m_Explosion == expDirected) + vecVelocity = g_vecAttackDir * 200; + else + { + vecVelocity.x = 0; + vecVelocity.y = 0; + vecVelocity.z = 0; + } + + vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z ); + + // size + WRITE_COORD( pev->size.x); + WRITE_COORD( pev->size.y); + WRITE_COORD( pev->size.z); + + // velocity + WRITE_COORD( vecVelocity.x ); + WRITE_COORD( vecVelocity.y ); + WRITE_COORD( vecVelocity.z ); + + // randomization + WRITE_BYTE( 10 ); + + // Model + WRITE_SHORT( m_idShard ); //model id# + + // # of shards + WRITE_BYTE( 0 ); // let client decide + + // duration + WRITE_BYTE( 25 );// 2.5 seconds + + // flags + WRITE_BYTE( cFlag ); + MESSAGE_END(); + + float size = pev->size.x; + if ( size < pev->size.y ) + size = pev->size.y; + if ( size < pev->size.z ) + size = pev->size.z; + + // !!! HACK This should work! + // Build a box above the entity that looks like an 8 pixel high sheet + Vector mins = pev->absmin; + Vector maxs = pev->absmax; + mins.z = pev->absmax.z; + maxs.z += 8; + + // BUGBUG -- can only find 256 entities on a breakable -- should be enough + CBaseEntity *pList[256]; + int count = UTIL_EntitiesInBox( pList, 256, mins, maxs, FL_ONGROUND ); + if ( count ) + { + for ( int i = 0; i < count; i++ ) + { + ClearBits( pList[i]->pev->flags, FL_ONGROUND ); + pList[i]->pev->groundentity = NULL; + } + } + + // Don't fire something that could fire myself + pev->targetname = 0; + + pev->solid = SOLID_NOT; + // Fire targets on break + SUB_UseTargets( NULL, USE_TOGGLE, 0 ); + + SetThink( &CBreakable::SUB_Remove ); + pev->nextthink = pev->ltime + 0.1; + if ( m_iszSpawnObject ) + CBaseEntity::Create( (char *)STRING(m_iszSpawnObject), VecBModelOrigin(pev), pev->angles, edict() ); + + + if ( Explodable() ) + { + ExplosionCreate( Center(), pev->angles, edict(), ExplosionMagnitude(), TRUE ); + } +} + + + +BOOL CBreakable :: IsBreakable( void ) +{ + return m_Material != matUnbreakableGlass; +} + + +int CBreakable :: DamageDecal( int bitsDamageType ) +{ + if ( m_Material == matGlass ) + return DECAL_GLASSBREAK1 + RANDOM_LONG(0,2); + + if ( m_Material == matUnbreakableGlass ) + return DECAL_BPROOF1; + + return CBaseEntity::DamageDecal( bitsDamageType ); +} + + +class CPushable : public CBreakable +{ +public: + void Spawn ( void ); + void Precache( void ); + void Touch ( CBaseEntity *pOther ); + void Move( CBaseEntity *pMover, int push ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT StopSound( void ); +// virtual void SetActivator( CBaseEntity *pActivator ) { m_pPusher = pActivator; } + + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_CONTINUOUS_USE; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + inline float MaxSpeed( void ) { return m_maxSpeed; } + + // breakables use an overridden takedamage + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + + static TYPEDESCRIPTION m_SaveData[]; + + static char *m_soundNames[3]; + int m_lastSound; // no need to save/restore, just keeps the same sound from playing twice in a row + float m_maxSpeed; + float m_soundTime; +}; + +TYPEDESCRIPTION CPushable::m_SaveData[] = +{ + DEFINE_FIELD( CPushable, m_maxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CPushable, m_soundTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CPushable, CBreakable ); + +LINK_ENTITY_TO_CLASS( func_pushable, CPushable ); + +char *CPushable :: m_soundNames[3] = { "debris/pushbox1.wav", "debris/pushbox2.wav", "debris/pushbox3.wav" }; + + +void CPushable :: Spawn( void ) +{ + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + CBreakable::Spawn(); + else + Precache( ); + + pev->movetype = MOVETYPE_PUSHSTEP; + pev->solid = SOLID_BBOX; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + if ( pev->friction > 399 ) + pev->friction = 399; + + m_maxSpeed = 400 - pev->friction; + SetBits( pev->flags, FL_FLOAT ); + pev->friction = 0; + + pev->origin.z += 1; // Pick up off of the floor + UTIL_SetOrigin( pev, pev->origin ); + + // Multiply by area of the box's cross-section (assume 1000 units^3 standard volume) + pev->skin = ( pev->skin * (pev->maxs.x - pev->mins.x) * (pev->maxs.y - pev->mins.y) ) * 0.0005; + m_soundTime = 0; +} + + +void CPushable :: Precache( void ) +{ + for ( int i = 0; i < 3; i++ ) + PRECACHE_SOUND( m_soundNames[i] ); + + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + CBreakable::Precache( ); +} + + +void CPushable :: KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "size") ) + { + int bbox = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + switch( bbox ) + { + case 0: // Point + UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8)); + break; + + case 2: // Big Hull!?!? !!!BUGBUG Figure out what this hull really is + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN*2, VEC_DUCK_HULL_MAX*2); + break; + + case 3: // Player duck + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + break; + + default: + case 1: // Player + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + break; + } + + } + else if ( FStrEq(pkvd->szKeyName, "buoyancy") ) + { + pev->skin = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBreakable::KeyValue( pkvd ); +} + + +// Pull the func_pushable +void CPushable :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !pActivator || !pActivator->IsPlayer() ) + { + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + this->CBreakable::Use( pActivator, pCaller, useType, value ); + return; + } + + if ( pActivator->pev->velocity != g_vecZero ) + Move( pActivator, 0 ); +} + + +void CPushable :: Touch( CBaseEntity *pOther ) +{ + if ( FClassnameIs( pOther->pev, "worldspawn" ) ) + return; + + Move( pOther, 1 ); +} + + +void CPushable :: Move( CBaseEntity *pOther, int push ) +{ + entvars_t* pevToucher = pOther->pev; + int playerTouch = 0; + + // Is entity standing on this pushable ? + if ( FBitSet(pevToucher->flags,FL_ONGROUND) && pevToucher->groundentity && VARS(pevToucher->groundentity) == pev ) + { + // Only push if floating + if ( pev->waterlevel > 0 ) + pev->velocity.z += pevToucher->velocity.z * 0.1; + + return; + } + + + if ( pOther->IsPlayer() ) + { + if ( push && !(pevToucher->button & (IN_FORWARD|IN_USE)) ) // Don't push unless the player is pushing forward and NOT use (pull) + return; + playerTouch = 1; + } + + float factor; + + if ( playerTouch ) + { + if ( !(pevToucher->flags & FL_ONGROUND) ) // Don't push away from jumping/falling players unless in water + { + if ( pev->waterlevel < 1 ) + return; + else + factor = 0.1; + } + else + factor = 1; + } + else + factor = 0.25; + + pev->velocity.x += pevToucher->velocity.x * factor; + pev->velocity.y += pevToucher->velocity.y * factor; + + float length = sqrt( pev->velocity.x * pev->velocity.x + pev->velocity.y * pev->velocity.y ); + if ( push && (length > MaxSpeed()) ) + { + pev->velocity.x = (pev->velocity.x * MaxSpeed() / length ); + pev->velocity.y = (pev->velocity.y * MaxSpeed() / length ); + } + if ( playerTouch ) + { + pevToucher->velocity.x = pev->velocity.x; + pevToucher->velocity.y = pev->velocity.y; + if ( (gpGlobals->time - m_soundTime) > 0.7 ) + { + m_soundTime = gpGlobals->time; + if ( length > 0 && FBitSet(pev->flags,FL_ONGROUND) ) + { + m_lastSound = RANDOM_LONG(0,2); + EMIT_SOUND(ENT(pev), CHAN_WEAPON, m_soundNames[m_lastSound], 0.5, ATTN_NORM); + // SetThink( StopSound ); + // pev->nextthink = pev->ltime + 0.1; + } + else + STOP_SOUND( ENT(pev), CHAN_WEAPON, m_soundNames[m_lastSound] ); + } + } +} + +#if 0 +void CPushable::StopSound( void ) +{ + Vector dist = pev->oldorigin - pev->origin; + if ( dist.Length() <= 0 ) + STOP_SOUND( ENT(pev), CHAN_WEAPON, m_soundNames[m_lastSound] ); +} +#endif + +int CPushable::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + return CBreakable::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); + + return 1; +} + diff --git a/dlls/func_break.h b/dlls/func_break.h new file mode 100644 index 0000000..9f7fde8 --- /dev/null +++ b/dlls/func_break.h @@ -0,0 +1,74 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef FUNC_BREAK_H +#define FUNC_BREAK_H + +typedef enum { expRandom, expDirected} Explosions; +typedef enum { matGlass = 0, matWood, matMetal, matFlesh, matCinderBlock, matCeilingTile, matComputer, matUnbreakableGlass, matRocks, matNone, matLastMaterial } Materials; + +#define NUM_SHARDS 6 // this many shards spawned when breakable objects break; + +class CBreakable : public CBaseDelay +{ +public: + // basic functions + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData* pkvd); + void EXPORT BreakTouch( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void DamageSound( void ); + + // breakables use an overridden takedamage + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + // To spark when hit + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); + + BOOL IsBreakable( void ); + BOOL SparkWhenHit( void ); + + int DamageDecal( int bitsDamageType ); + + void EXPORT Die( void ); + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + inline BOOL Explodable( void ) { return ExplosionMagnitude() > 0; } + inline int ExplosionMagnitude( void ) { return pev->impulse; } + inline void ExplosionSetMagnitude( int magnitude ) { pev->impulse = magnitude; } + + static void MaterialSoundPrecache( Materials precacheMaterial ); + static void MaterialSoundRandom( edict_t *pEdict, Materials soundMaterial, float volume ); + static const char **MaterialSoundList( Materials precacheMaterial, int &soundCount ); + + static const char *pSoundsWood[]; + static const char *pSoundsFlesh[]; + static const char *pSoundsGlass[]; + static const char *pSoundsMetal[]; + static const char *pSoundsConcrete[]; + static const char *pSpawnObjects[]; + + static TYPEDESCRIPTION m_SaveData[]; + + Materials m_Material; + Explosions m_Explosion; + int m_idShard; + float m_angle; + int m_iszGibModel; + int m_iszSpawnObject; +}; + +#endif // FUNC_BREAK_H diff --git a/dlls/func_tank.cpp b/dlls/func_tank.cpp new file mode 100644 index 0000000..2076285 --- /dev/null +++ b/dlls/func_tank.cpp @@ -0,0 +1,1039 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "effects.h" +#include "weapons.h" +#include "explode.h" + +#include "player.h" + + +#define SF_TANK_ACTIVE 0x0001 +#define SF_TANK_PLAYER 0x0002 +#define SF_TANK_HUMANS 0x0004 +#define SF_TANK_ALIENS 0x0008 +#define SF_TANK_LINEOFSIGHT 0x0010 +#define SF_TANK_CANCONTROL 0x0020 +#define SF_TANK_SOUNDON 0x8000 + +enum TANKBULLET +{ + TANK_BULLET_NONE = 0, + TANK_BULLET_9MM = 1, + TANK_BULLET_MP5 = 2, + TANK_BULLET_12MM = 3, +}; + +// Custom damage +// env_laser (duration is 0.5 rate of fire) +// rockets +// explosion? + +class CFuncTank : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + void TrackTarget( void ); + + virtual void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); + virtual Vector UpdateTargetPosition( CBaseEntity *pTarget ) + { + return pTarget->BodyTarget( pev->origin ); + } + + void StartRotSound( void ); + void StopRotSound( void ); + + // Bmodels don't go across transitions + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + inline BOOL IsActive( void ) { return (pev->spawnflags & SF_TANK_ACTIVE)?TRUE:FALSE; } + inline void TankActivate( void ) { pev->spawnflags |= SF_TANK_ACTIVE; pev->nextthink = pev->ltime + 0.1; m_fireLast = 0; } + inline void TankDeactivate( void ) { pev->spawnflags &= ~SF_TANK_ACTIVE; m_fireLast = 0; StopRotSound(); } + inline BOOL CanFire( void ) { return (gpGlobals->time - m_lastSightTime) < m_persist; } + BOOL InRange( float range ); + + // Acquire a target. pPlayer is a player in the PVS + edict_t *FindTarget( edict_t *pPlayer ); + + void TankTrace( const Vector &vecStart, const Vector &vecForward, const Vector &vecSpread, TraceResult &tr ); + + Vector BarrelPosition( void ) + { + Vector forward, right, up; + UTIL_MakeVectorsPrivate( pev->angles, forward, right, up ); + return pev->origin + (forward * m_barrelPos.x) + (right * m_barrelPos.y) + (up * m_barrelPos.z); + } + + void AdjustAnglesForBarrel( Vector &angles, float distance ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + BOOL OnControls( entvars_t *pevTest ); + BOOL StartControl( CBasePlayer* pController ); + void StopControl( void ); + void ControllerPostFrame( void ); + + +protected: + CBasePlayer* m_pController; + float m_flNextAttack; + Vector m_vecControllerUsePos; + + float m_yawCenter; // "Center" yaw + float m_yawRate; // Max turn rate to track targets + float m_yawRange; // Range of turning motion (one-sided: 30 is +/- 30 degress from center) + // Zero is full rotation + float m_yawTolerance; // Tolerance angle + + float m_pitchCenter; // "Center" pitch + float m_pitchRate; // Max turn rate on pitch + float m_pitchRange; // Range of pitch motion as above + float m_pitchTolerance; // Tolerance angle + + float m_fireLast; // Last time I fired + float m_fireRate; // How many rounds/second + float m_lastSightTime;// Last time I saw target + float m_persist; // Persistence of firing (how long do I shoot when I can't see) + float m_minRange; // Minimum range to aim/track + float m_maxRange; // Max range to aim/track + + Vector m_barrelPos; // Length of the freakin barrel + float m_spriteScale; // Scale of any sprites we shoot + int m_iszSpriteSmoke; + int m_iszSpriteFlash; + TANKBULLET m_bulletType; // Bullet type + int m_iBulletDamage; // 0 means use Bullet type's default damage + + Vector m_sightOrigin; // Last sight of target + int m_spread; // firing spread + int m_iszMaster; // Master entity (game_team_master or multisource) +}; + + +TYPEDESCRIPTION CFuncTank::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTank, m_yawCenter, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_yawRate, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_yawRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_yawTolerance, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchCenter, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchRate, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchTolerance, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_fireLast, FIELD_TIME ), + DEFINE_FIELD( CFuncTank, m_fireRate, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_lastSightTime, FIELD_TIME ), + DEFINE_FIELD( CFuncTank, m_persist, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_minRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_maxRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_barrelPos, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTank, m_spriteScale, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_iszSpriteSmoke, FIELD_STRING ), + DEFINE_FIELD( CFuncTank, m_iszSpriteFlash, FIELD_STRING ), + DEFINE_FIELD( CFuncTank, m_bulletType, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTank, m_sightOrigin, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTank, m_spread, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTank, m_pController, FIELD_CLASSPTR ), + DEFINE_FIELD( CFuncTank, m_vecControllerUsePos, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTank, m_flNextAttack, FIELD_TIME ), + DEFINE_FIELD( CFuncTank, m_iBulletDamage, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTank, m_iszMaster, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTank, CBaseEntity ); + +static Vector gTankSpread[] = +{ + Vector( 0, 0, 0 ), // perfect + Vector( 0.025, 0.025, 0.025 ), // small cone + Vector( 0.05, 0.05, 0.05 ), // medium cone + Vector( 0.1, 0.1, 0.1 ), // large cone + Vector( 0.25, 0.25, 0.25 ), // extra-large cone +}; +#define MAX_FIRING_SPREADS ARRAYSIZE(gTankSpread) + + +void CFuncTank :: Spawn( void ) +{ + Precache(); + + pev->movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + pev->solid = SOLID_BSP; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_yawCenter = pev->angles.y; + m_pitchCenter = pev->angles.x; + + if ( IsActive() ) + pev->nextthink = pev->ltime + 1.0; + + m_sightOrigin = BarrelPosition(); // Point at the end of the barrel + + if ( m_fireRate <= 0 ) + m_fireRate = 1; + if ( m_spread > MAX_FIRING_SPREADS ) + m_spread = 0; + + pev->oldorigin = pev->origin; +} + + +void CFuncTank :: Precache( void ) +{ + if ( m_iszSpriteSmoke ) + PRECACHE_MODEL( (char *)STRING(m_iszSpriteSmoke) ); + if ( m_iszSpriteFlash ) + PRECACHE_MODEL( (char *)STRING(m_iszSpriteFlash) ); + + if ( pev->noise ) + PRECACHE_SOUND( (char *)STRING(pev->noise) ); +} + + +void CFuncTank :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "yawrate")) + { + m_yawRate = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "yawrange")) + { + m_yawRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "yawtolerance")) + { + m_yawTolerance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitchrange")) + { + m_pitchRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitchrate")) + { + m_pitchRate = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitchtolerance")) + { + m_pitchTolerance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "firerate")) + { + m_fireRate = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "barrel")) + { + m_barrelPos.x = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "barrely")) + { + m_barrelPos.y = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "barrelz")) + { + m_barrelPos.z = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spritescale")) + { + m_spriteScale = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spritesmoke")) + { + m_iszSpriteSmoke = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spriteflash")) + { + m_iszSpriteFlash = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "rotatesound")) + { + pev->noise = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "persistence")) + { + m_persist = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "bullet")) + { + m_bulletType = (TANKBULLET)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "bullet_damage" )) + { + m_iBulletDamage = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "firespread")) + { + m_spread = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "minRange")) + { + m_minRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "maxRange")) + { + m_maxRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "master")) + { + m_iszMaster = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +////////////// START NEW STUFF ////////////// + +//================================================================================== +// TANK CONTROLLING +BOOL CFuncTank :: OnControls( entvars_t *pevTest ) +{ + if ( !(pev->spawnflags & SF_TANK_CANCONTROL) ) + return FALSE; + + Vector offset = pevTest->origin - pev->origin; + + if ( (m_vecControllerUsePos - pevTest->origin).Length() < 30 ) + return TRUE; + + return FALSE; +} + +BOOL CFuncTank :: StartControl( CBasePlayer *pController ) +{ + if ( m_pController != NULL ) + return FALSE; + + // Team only or disabled? + if ( m_iszMaster ) + { + if ( !UTIL_IsMasterTriggered( m_iszMaster, pController ) ) + return FALSE; + } + + ALERT( at_console, "using TANK!\n"); + + m_pController = pController; + if ( m_pController->m_pActiveItem ) + { + m_pController->m_pActiveItem->Holster(); + m_pController->pev->weaponmodel = 0; + m_pController->pev->viewmodel = 0; + + } + + m_pController->m_iHideHUD |= HIDEHUD_WEAPONS; + m_vecControllerUsePos = m_pController->pev->origin; + + pev->nextthink = pev->ltime + 0.1; + + return TRUE; +} + +void CFuncTank :: StopControl() +{ + // TODO: bring back the controllers current weapon + if ( !m_pController ) + return; + + if ( m_pController->m_pActiveItem ) + m_pController->m_pActiveItem->Deploy(); + + ALERT( at_console, "stopped using TANK\n"); + + m_pController->m_iHideHUD &= ~HIDEHUD_WEAPONS; + + pev->nextthink = 0; + m_pController = NULL; + + if ( IsActive() ) + pev->nextthink = pev->ltime + 1.0; +} + +// Called each frame by the player's ItemPostFrame +void CFuncTank :: ControllerPostFrame( void ) +{ + ASSERT(m_pController != NULL); + + if ( gpGlobals->time < m_flNextAttack ) + return; + + if ( m_pController->pev->button & IN_ATTACK ) + { + Vector vecForward; + UTIL_MakeVectorsPrivate( pev->angles, vecForward, NULL, NULL ); + + m_fireLast = gpGlobals->time - (1/m_fireRate) - 0.01; // to make sure the gun doesn't fire too many bullets + + Fire( BarrelPosition(), vecForward, m_pController->pev ); + + // HACKHACK -- make some noise (that the AI can hear) + if ( m_pController && m_pController->IsPlayer() ) + ((CBasePlayer *)m_pController)->m_iWeaponVolume = LOUD_GUN_VOLUME; + + m_flNextAttack = gpGlobals->time + (1/m_fireRate); + } +} +////////////// END NEW STUFF ////////////// + + +void CFuncTank :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->spawnflags & SF_TANK_CANCONTROL ) + { // player controlled turret + + if ( pActivator->Classify() != CLASS_PLAYER ) + return; + + if ( value == 2 && useType == USE_SET ) + { + ControllerPostFrame(); + } + else if ( !m_pController && useType != USE_OFF ) + { + ((CBasePlayer*)pActivator)->m_pTank = this; + StartControl( (CBasePlayer*)pActivator ); + } + else + { + StopControl(); + } + } + else + { + if ( !ShouldToggle( useType, IsActive() ) ) + return; + + if ( IsActive() ) + TankDeactivate(); + else + TankActivate(); + } +} + + +edict_t *CFuncTank :: FindTarget( edict_t *pPlayer ) +{ + return pPlayer; +} + + + +BOOL CFuncTank :: InRange( float range ) +{ + if ( range < m_minRange ) + return FALSE; + if ( m_maxRange > 0 && range > m_maxRange ) + return FALSE; + + return TRUE; +} + + +void CFuncTank :: Think( void ) +{ + pev->avelocity = g_vecZero; + TrackTarget(); + + if ( fabs(pev->avelocity.x) > 1 || fabs(pev->avelocity.y) > 1 ) + StartRotSound(); + else + StopRotSound(); +} + +void CFuncTank::TrackTarget( void ) +{ + TraceResult tr; + edict_t *pPlayer = FIND_CLIENT_IN_PVS( edict() ); + BOOL updateTime = FALSE, lineOfSight; + Vector angles, direction, targetPosition, barrelEnd; + edict_t *pTarget; + + // Get a position to aim for + if (m_pController) + { + // Tanks attempt to mirror the player's angles + angles = m_pController->pev->v_angle; + angles[0] = 0 - angles[0]; + pev->nextthink = pev->ltime + 0.05; + } + else + { + if ( IsActive() ) + pev->nextthink = pev->ltime + 0.1; + else + return; + + if ( FNullEnt( pPlayer ) ) + { + if ( IsActive() ) + pev->nextthink = pev->ltime + 2; // Wait 2 secs + return; + } + pTarget = FindTarget( pPlayer ); + if ( !pTarget ) + return; + + // Calculate angle needed to aim at target + barrelEnd = BarrelPosition(); + targetPosition = pTarget->v.origin + pTarget->v.view_ofs; + float range = (targetPosition - barrelEnd).Length(); + + if ( !InRange( range ) ) + return; + + UTIL_TraceLine( barrelEnd, targetPosition, dont_ignore_monsters, edict(), &tr ); + + lineOfSight = FALSE; + // No line of sight, don't track + if ( tr.flFraction == 1.0 || tr.pHit == pTarget ) + { + lineOfSight = TRUE; + + CBaseEntity *pInstance = CBaseEntity::Instance(pTarget); + if ( InRange( range ) && pInstance && pInstance->IsAlive() ) + { + updateTime = TRUE; + m_sightOrigin = UpdateTargetPosition( pInstance ); + } + } + + // Track sight origin + +// !!! I'm not sure what i changed + direction = m_sightOrigin - pev->origin; +// direction = m_sightOrigin - barrelEnd; + angles = UTIL_VecToAngles( direction ); + + // Calculate the additional rotation to point the end of the barrel at the target (not the gun's center) + AdjustAnglesForBarrel( angles, direction.Length() ); + } + + angles.x = -angles.x; + + // Force the angles to be relative to the center position + angles.y = m_yawCenter + UTIL_AngleDistance( angles.y, m_yawCenter ); + angles.x = m_pitchCenter + UTIL_AngleDistance( angles.x, m_pitchCenter ); + + // Limit against range in y + if ( angles.y > m_yawCenter + m_yawRange ) + { + angles.y = m_yawCenter + m_yawRange; + updateTime = FALSE; // Don't update if you saw the player, but out of range + } + else if ( angles.y < (m_yawCenter - m_yawRange) ) + { + angles.y = (m_yawCenter - m_yawRange); + updateTime = FALSE; // Don't update if you saw the player, but out of range + } + + if ( updateTime ) + m_lastSightTime = gpGlobals->time; + + // Move toward target at rate or less + float distY = UTIL_AngleDistance( angles.y, pev->angles.y ); + pev->avelocity.y = distY * 10; + if ( pev->avelocity.y > m_yawRate ) + pev->avelocity.y = m_yawRate; + else if ( pev->avelocity.y < -m_yawRate ) + pev->avelocity.y = -m_yawRate; + + // Limit against range in x + if ( angles.x > m_pitchCenter + m_pitchRange ) + angles.x = m_pitchCenter + m_pitchRange; + else if ( angles.x < m_pitchCenter - m_pitchRange ) + angles.x = m_pitchCenter - m_pitchRange; + + // Move toward target at rate or less + float distX = UTIL_AngleDistance( angles.x, pev->angles.x ); + pev->avelocity.x = distX * 10; + + if ( pev->avelocity.x > m_pitchRate ) + pev->avelocity.x = m_pitchRate; + else if ( pev->avelocity.x < -m_pitchRate ) + pev->avelocity.x = -m_pitchRate; + + if ( m_pController ) + return; + + if ( CanFire() && ( (fabs(distX) < m_pitchTolerance && fabs(distY) < m_yawTolerance) || (pev->spawnflags & SF_TANK_LINEOFSIGHT) ) ) + { + BOOL fire = FALSE; + Vector forward; + UTIL_MakeVectorsPrivate( pev->angles, forward, NULL, NULL ); + + if ( pev->spawnflags & SF_TANK_LINEOFSIGHT ) + { + float length = direction.Length(); + UTIL_TraceLine( barrelEnd, barrelEnd + forward * length, dont_ignore_monsters, edict(), &tr ); + if ( tr.pHit == pTarget ) + fire = TRUE; + } + else + fire = TRUE; + + if ( fire ) + { + Fire( BarrelPosition(), forward, pev ); + } + else + m_fireLast = 0; + } + else + m_fireLast = 0; +} + + +// If barrel is offset, add in additional rotation +void CFuncTank::AdjustAnglesForBarrel( Vector &angles, float distance ) +{ + float r2, d2; + + + if ( m_barrelPos.y != 0 || m_barrelPos.z != 0 ) + { + distance -= m_barrelPos.z; + d2 = distance * distance; + if ( m_barrelPos.y ) + { + r2 = m_barrelPos.y * m_barrelPos.y; + angles.y += (180.0 / M_PI) * atan2( m_barrelPos.y, sqrt( d2 - r2 ) ); + } + if ( m_barrelPos.z ) + { + r2 = m_barrelPos.z * m_barrelPos.z; + angles.x += (180.0 / M_PI) * atan2( -m_barrelPos.z, sqrt( d2 - r2 ) ); + } + } +} + + +// Fire targets and spawn sprites +void CFuncTank::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + if ( m_fireLast != 0 ) + { + if ( m_iszSpriteSmoke ) + { + CSprite *pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteSmoke), barrelEnd, TRUE ); + pSprite->AnimateAndDie( RANDOM_FLOAT( 15.0, 20.0 ) ); + pSprite->SetTransparency( kRenderTransAlpha, pev->rendercolor.x, pev->rendercolor.y, pev->rendercolor.z, 255, kRenderFxNone ); + pSprite->pev->velocity.z = RANDOM_FLOAT(40, 80); + pSprite->SetScale( m_spriteScale ); + } + if ( m_iszSpriteFlash ) + { + CSprite *pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteFlash), barrelEnd, TRUE ); + pSprite->AnimateAndDie( 60 ); + pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); + pSprite->SetScale( m_spriteScale ); + + // Hack Hack, make it stick around for at least 100 ms. + pSprite->pev->nextthink += 0.1; + } + SUB_UseTargets( this, USE_TOGGLE, 0 ); + } + m_fireLast = gpGlobals->time; +} + + +void CFuncTank::TankTrace( const Vector &vecStart, const Vector &vecForward, const Vector &vecSpread, TraceResult &tr ) +{ + // get circular gaussian spread + float x, y, z; + do { + x = RANDOM_FLOAT(-0.5,0.5) + RANDOM_FLOAT(-0.5,0.5); + y = RANDOM_FLOAT(-0.5,0.5) + RANDOM_FLOAT(-0.5,0.5); + z = x*x+y*y; + } while (z > 1); + Vector vecDir = vecForward + + x * vecSpread.x * gpGlobals->v_right + + y * vecSpread.y * gpGlobals->v_up; + Vector vecEnd; + + vecEnd = vecStart + vecDir * 4096; + UTIL_TraceLine( vecStart, vecEnd, dont_ignore_monsters, edict(), &tr ); +} + + +void CFuncTank::StartRotSound( void ) +{ + if ( !pev->noise || (pev->spawnflags & SF_TANK_SOUNDON) ) + return; + pev->spawnflags |= SF_TANK_SOUNDON; + EMIT_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noise), 0.85, ATTN_NORM); +} + + +void CFuncTank::StopRotSound( void ) +{ + if ( pev->spawnflags & SF_TANK_SOUNDON ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noise) ); + pev->spawnflags &= ~SF_TANK_SOUNDON; +} + +class CFuncTankGun : public CFuncTank +{ +public: + void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); +}; +LINK_ENTITY_TO_CLASS( func_tank, CFuncTankGun ); + +void CFuncTankGun::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + int i; + + if ( m_fireLast != 0 ) + { + // FireBullets needs gpGlobals->v_up, etc. + UTIL_MakeAimVectors(pev->angles); + + int bulletCount = (gpGlobals->time - m_fireLast) * m_fireRate; + if ( bulletCount > 0 ) + { + for ( i = 0; i < bulletCount; i++ ) + { + switch( m_bulletType ) + { + case TANK_BULLET_9MM: + FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096, BULLET_MONSTER_9MM, 1, m_iBulletDamage, pevAttacker ); + break; + + case TANK_BULLET_MP5: + FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096, BULLET_MONSTER_MP5, 1, m_iBulletDamage, pevAttacker ); + break; + + case TANK_BULLET_12MM: + FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096, BULLET_MONSTER_12MM, 1, m_iBulletDamage, pevAttacker ); + break; + + default: + case TANK_BULLET_NONE: + break; + } + } + CFuncTank::Fire( barrelEnd, forward, pevAttacker ); + } + } + else + CFuncTank::Fire( barrelEnd, forward, pevAttacker ); +} + + + +class CFuncTankLaser : public CFuncTank +{ +public: + void Activate( void ); + void KeyValue( KeyValueData *pkvd ); + void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); + void Think( void ); + CLaser *GetLaser( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + CLaser *m_pLaser; + float m_laserTime; +}; +LINK_ENTITY_TO_CLASS( func_tanklaser, CFuncTankLaser ); + +TYPEDESCRIPTION CFuncTankLaser::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTankLaser, m_pLaser, FIELD_CLASSPTR ), + DEFINE_FIELD( CFuncTankLaser, m_laserTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTankLaser, CFuncTank ); + +void CFuncTankLaser::Activate( void ) +{ + if ( !GetLaser() ) + { + UTIL_Remove(this); + ALERT( at_error, "Laser tank with no env_laser!\n" ); + } + else + { + m_pLaser->TurnOff(); + } +} + + +void CFuncTankLaser::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "laserentity")) + { + pev->message = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CFuncTank::KeyValue( pkvd ); +} + + +CLaser *CFuncTankLaser::GetLaser( void ) +{ + if ( m_pLaser ) + return m_pLaser; + + edict_t *pentLaser; + + pentLaser = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->message) ); + while ( !FNullEnt( pentLaser ) ) + { + // Found the landmark + if ( FClassnameIs( pentLaser, "env_laser" ) ) + { + m_pLaser = (CLaser *)CBaseEntity::Instance(pentLaser); + break; + } + else + pentLaser = FIND_ENTITY_BY_TARGETNAME( pentLaser, STRING(pev->message) ); + } + + return m_pLaser; +} + + +void CFuncTankLaser::Think( void ) +{ + if ( m_pLaser && (gpGlobals->time > m_laserTime) ) + m_pLaser->TurnOff(); + + CFuncTank::Think(); +} + + +void CFuncTankLaser::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + int i; + TraceResult tr; + + if ( m_fireLast != 0 && GetLaser() ) + { + // TankTrace needs gpGlobals->v_up, etc. + UTIL_MakeAimVectors(pev->angles); + + int bulletCount = (gpGlobals->time - m_fireLast) * m_fireRate; + if ( bulletCount ) + { + for ( i = 0; i < bulletCount; i++ ) + { + m_pLaser->pev->origin = barrelEnd; + TankTrace( barrelEnd, forward, gTankSpread[m_spread], tr ); + + m_laserTime = gpGlobals->time; + m_pLaser->TurnOn(); + m_pLaser->pev->dmgtime = gpGlobals->time - 1.0; + m_pLaser->FireAtPoint( tr ); + m_pLaser->pev->nextthink = 0; + } + CFuncTank::Fire( barrelEnd, forward, pev ); + } + } + else + { + CFuncTank::Fire( barrelEnd, forward, pev ); + } +} + +class CFuncTankRocket : public CFuncTank +{ +public: + void Precache( void ); + void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); +}; +LINK_ENTITY_TO_CLASS( func_tankrocket, CFuncTankRocket ); + +void CFuncTankRocket::Precache( void ) +{ + UTIL_PrecacheOther( "rpg_rocket" ); + CFuncTank::Precache(); +} + + + +void CFuncTankRocket::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + int i; + + if ( m_fireLast != 0 ) + { + int bulletCount = (gpGlobals->time - m_fireLast) * m_fireRate; + if ( bulletCount > 0 ) + { + for ( i = 0; i < bulletCount; i++ ) + { + CBaseEntity *pRocket = CBaseEntity::Create( "rpg_rocket", barrelEnd, pev->angles, edict() ); + } + CFuncTank::Fire( barrelEnd, forward, pev ); + } + } + else + CFuncTank::Fire( barrelEnd, forward, pev ); +} + + +class CFuncTankMortar : public CFuncTank +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); +}; +LINK_ENTITY_TO_CLASS( func_tankmortar, CFuncTankMortar ); + + +void CFuncTankMortar::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "iMagnitude")) + { + pev->impulse = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CFuncTank::KeyValue( pkvd ); +} + + +void CFuncTankMortar::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + if ( m_fireLast != 0 ) + { + int bulletCount = (gpGlobals->time - m_fireLast) * m_fireRate; + // Only create 1 explosion + if ( bulletCount > 0 ) + { + TraceResult tr; + + // TankTrace needs gpGlobals->v_up, etc. + UTIL_MakeAimVectors(pev->angles); + + TankTrace( barrelEnd, forward, gTankSpread[m_spread], tr ); + + ExplosionCreate( tr.vecEndPos, pev->angles, edict(), pev->impulse, TRUE ); + + CFuncTank::Fire( barrelEnd, forward, pev ); + } + } + else + CFuncTank::Fire( barrelEnd, forward, pev ); +} + + + +//============================================================================ +// FUNC TANK CONTROLS +//============================================================================ +class CFuncTankControls : public CBaseEntity +{ +public: + virtual int ObjectCaps( void ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + CFuncTank *m_pTank; +}; +LINK_ENTITY_TO_CLASS( func_tankcontrols, CFuncTankControls ); + +TYPEDESCRIPTION CFuncTankControls::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTankControls, m_pTank, FIELD_CLASSPTR ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTankControls, CBaseEntity ); + +int CFuncTankControls :: ObjectCaps( void ) +{ + return (CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_IMPULSE_USE; +} + + +void CFuncTankControls :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ // pass the Use command onto the controls + if ( m_pTank ) + m_pTank->Use( pActivator, pCaller, useType, value ); + + ASSERT( m_pTank != NULL ); // if this fails, most likely means save/restore hasn't worked properly +} + + +void CFuncTankControls :: Think( void ) +{ + edict_t *pTarget = NULL; + + do + { + pTarget = FIND_ENTITY_BY_TARGETNAME( pTarget, STRING(pev->target) ); + } while ( !FNullEnt(pTarget) && strncmp( STRING(pTarget->v.classname), "func_tank", 9 ) ); + + if ( FNullEnt( pTarget ) ) + { + ALERT( at_console, "No tank %s\n", STRING(pev->target) ); + return; + } + + m_pTank = (CFuncTank*)Instance(pTarget); +} + +void CFuncTankControls::Spawn( void ) +{ + pev->solid = SOLID_TRIGGER; + pev->movetype = MOVETYPE_NONE; + pev->effects |= EF_NODRAW; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); + + pev->nextthink = gpGlobals->time + 0.3; // After all the func_tank's have spawned + + CBaseEntity::Spawn(); +} diff --git a/dlls/game.cpp b/dlls/game.cpp new file mode 100644 index 0000000..77624bd --- /dev/null +++ b/dlls/game.cpp @@ -0,0 +1,890 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "eiface.h" +#include "util.h" +#include "game.h" + +cvar_t displaysoundlist = {"displaysoundlist","0"}; + +// multiplayer server rules +cvar_t fragsleft = {"mp_fragsleft","0", FCVAR_SERVER | FCVAR_UNLOGGED }; // Don't spam console/log files/users with this changing +cvar_t timeleft = {"mp_timeleft","0" , FCVAR_SERVER | FCVAR_UNLOGGED }; // " " + +// multiplayer server rules +cvar_t teamplay = {"mp_teamplay","0", FCVAR_SERVER }; +cvar_t fraglimit = {"mp_fraglimit","0", FCVAR_SERVER }; +cvar_t timelimit = {"mp_timelimit","0", FCVAR_SERVER }; +cvar_t friendlyfire= {"mp_friendlyfire","0", FCVAR_SERVER }; +cvar_t falldamage = {"mp_falldamage","0", FCVAR_SERVER }; +cvar_t weaponstay = {"mp_weaponstay","0", FCVAR_SERVER }; +cvar_t forcerespawn= {"mp_forcerespawn","1", FCVAR_SERVER }; +cvar_t flashlight = {"mp_flashlight","0", FCVAR_SERVER }; +cvar_t aimcrosshair= {"mp_autocrosshair","1", FCVAR_SERVER }; +cvar_t decalfrequency = {"decalfrequency","30", FCVAR_SERVER }; +cvar_t teamlist = {"mp_teamlist","hgrunt;scientist", FCVAR_SERVER }; +cvar_t teamoverride = {"mp_teamoverride","1" }; +cvar_t defaultteam = {"mp_defaultteam","0" }; +cvar_t allowmonsters={"mp_allowmonsters","0", FCVAR_SERVER }; + +cvar_t allow_spectators = { "allow_spectators", "0.0", FCVAR_SERVER }; // 0 prevents players from being spectators + +cvar_t mp_chattime = {"mp_chattime","10", FCVAR_SERVER }; + +// Engine Cvars +cvar_t *g_psv_gravity = NULL; +cvar_t *g_psv_aim = NULL; +cvar_t *g_footsteps = NULL; + +//CVARS FOR SKILL LEVEL SETTINGS +// Agrunt +cvar_t sk_agrunt_health1 = {"sk_agrunt_health1","0"}; +cvar_t sk_agrunt_health2 = {"sk_agrunt_health2","0"}; +cvar_t sk_agrunt_health3 = {"sk_agrunt_health3","0"}; + +cvar_t sk_agrunt_dmg_punch1 = {"sk_agrunt_dmg_punch1","0"}; +cvar_t sk_agrunt_dmg_punch2 = {"sk_agrunt_dmg_punch2","0"}; +cvar_t sk_agrunt_dmg_punch3 = {"sk_agrunt_dmg_punch3","0"}; + +// Apache +cvar_t sk_apache_health1 = {"sk_apache_health1","0"}; +cvar_t sk_apache_health2 = {"sk_apache_health2","0"}; +cvar_t sk_apache_health3 = {"sk_apache_health3","0"}; + +// Barney +cvar_t sk_barney_health1 = {"sk_barney_health1","0"}; +cvar_t sk_barney_health2 = {"sk_barney_health2","0"}; +cvar_t sk_barney_health3 = {"sk_barney_health3","0"}; + +// Bullsquid +cvar_t sk_bullsquid_health1 = {"sk_bullsquid_health1","0"}; +cvar_t sk_bullsquid_health2 = {"sk_bullsquid_health2","0"}; +cvar_t sk_bullsquid_health3 = {"sk_bullsquid_health3","0"}; + +cvar_t sk_bullsquid_dmg_bite1 = {"sk_bullsquid_dmg_bite1","0"}; +cvar_t sk_bullsquid_dmg_bite2 = {"sk_bullsquid_dmg_bite2","0"}; +cvar_t sk_bullsquid_dmg_bite3 = {"sk_bullsquid_dmg_bite3","0"}; + +cvar_t sk_bullsquid_dmg_whip1 = {"sk_bullsquid_dmg_whip1","0"}; +cvar_t sk_bullsquid_dmg_whip2 = {"sk_bullsquid_dmg_whip2","0"}; +cvar_t sk_bullsquid_dmg_whip3 = {"sk_bullsquid_dmg_whip3","0"}; + +cvar_t sk_bullsquid_dmg_spit1 = {"sk_bullsquid_dmg_spit1","0"}; +cvar_t sk_bullsquid_dmg_spit2 = {"sk_bullsquid_dmg_spit2","0"}; +cvar_t sk_bullsquid_dmg_spit3 = {"sk_bullsquid_dmg_spit3","0"}; + + +// Big Momma +cvar_t sk_bigmomma_health_factor1 = {"sk_bigmomma_health_factor1","1.0"}; +cvar_t sk_bigmomma_health_factor2 = {"sk_bigmomma_health_factor2","1.0"}; +cvar_t sk_bigmomma_health_factor3 = {"sk_bigmomma_health_factor3","1.0"}; + +cvar_t sk_bigmomma_dmg_slash1 = {"sk_bigmomma_dmg_slash1","50"}; +cvar_t sk_bigmomma_dmg_slash2 = {"sk_bigmomma_dmg_slash2","50"}; +cvar_t sk_bigmomma_dmg_slash3 = {"sk_bigmomma_dmg_slash3","50"}; + +cvar_t sk_bigmomma_dmg_blast1 = {"sk_bigmomma_dmg_blast1","100"}; +cvar_t sk_bigmomma_dmg_blast2 = {"sk_bigmomma_dmg_blast2","100"}; +cvar_t sk_bigmomma_dmg_blast3 = {"sk_bigmomma_dmg_blast3","100"}; + +cvar_t sk_bigmomma_radius_blast1 = {"sk_bigmomma_radius_blast1","250"}; +cvar_t sk_bigmomma_radius_blast2 = {"sk_bigmomma_radius_blast2","250"}; +cvar_t sk_bigmomma_radius_blast3 = {"sk_bigmomma_radius_blast3","250"}; + +// Gargantua +cvar_t sk_gargantua_health1 = {"sk_gargantua_health1","0"}; +cvar_t sk_gargantua_health2 = {"sk_gargantua_health2","0"}; +cvar_t sk_gargantua_health3 = {"sk_gargantua_health3","0"}; + +cvar_t sk_gargantua_dmg_slash1 = {"sk_gargantua_dmg_slash1","0"}; +cvar_t sk_gargantua_dmg_slash2 = {"sk_gargantua_dmg_slash2","0"}; +cvar_t sk_gargantua_dmg_slash3 = {"sk_gargantua_dmg_slash3","0"}; + +cvar_t sk_gargantua_dmg_fire1 = {"sk_gargantua_dmg_fire1","0"}; +cvar_t sk_gargantua_dmg_fire2 = {"sk_gargantua_dmg_fire2","0"}; +cvar_t sk_gargantua_dmg_fire3 = {"sk_gargantua_dmg_fire3","0"}; + +cvar_t sk_gargantua_dmg_stomp1 = {"sk_gargantua_dmg_stomp1","0"}; +cvar_t sk_gargantua_dmg_stomp2 = {"sk_gargantua_dmg_stomp2","0"}; +cvar_t sk_gargantua_dmg_stomp3 = {"sk_gargantua_dmg_stomp3","0"}; + + +// Hassassin +cvar_t sk_hassassin_health1 = {"sk_hassassin_health1","0"}; +cvar_t sk_hassassin_health2 = {"sk_hassassin_health2","0"}; +cvar_t sk_hassassin_health3 = {"sk_hassassin_health3","0"}; + + +// Headcrab +cvar_t sk_headcrab_health1 = {"sk_headcrab_health1","0"}; +cvar_t sk_headcrab_health2 = {"sk_headcrab_health2","0"}; +cvar_t sk_headcrab_health3 = {"sk_headcrab_health3","0"}; + +cvar_t sk_headcrab_dmg_bite1 = {"sk_headcrab_dmg_bite1","0"}; +cvar_t sk_headcrab_dmg_bite2 = {"sk_headcrab_dmg_bite2","0"}; +cvar_t sk_headcrab_dmg_bite3 = {"sk_headcrab_dmg_bite3","0"}; + + +// Hgrunt +cvar_t sk_hgrunt_health1 = {"sk_hgrunt_health1","0"}; +cvar_t sk_hgrunt_health2 = {"sk_hgrunt_health2","0"}; +cvar_t sk_hgrunt_health3 = {"sk_hgrunt_health3","0"}; + +cvar_t sk_hgrunt_kick1 = {"sk_hgrunt_kick1","0"}; +cvar_t sk_hgrunt_kick2 = {"sk_hgrunt_kick2","0"}; +cvar_t sk_hgrunt_kick3 = {"sk_hgrunt_kick3","0"}; + +cvar_t sk_hgrunt_pellets1 = {"sk_hgrunt_pellets1","0"}; +cvar_t sk_hgrunt_pellets2 = {"sk_hgrunt_pellets2","0"}; +cvar_t sk_hgrunt_pellets3 = {"sk_hgrunt_pellets3","0"}; + +cvar_t sk_hgrunt_gspeed1 = {"sk_hgrunt_gspeed1","0"}; +cvar_t sk_hgrunt_gspeed2 = {"sk_hgrunt_gspeed2","0"}; +cvar_t sk_hgrunt_gspeed3 = {"sk_hgrunt_gspeed3","0"}; + +// Houndeye +cvar_t sk_houndeye_health1 = {"sk_houndeye_health1","0"}; +cvar_t sk_houndeye_health2 = {"sk_houndeye_health2","0"}; +cvar_t sk_houndeye_health3 = {"sk_houndeye_health3","0"}; + +cvar_t sk_houndeye_dmg_blast1 = {"sk_houndeye_dmg_blast1","0"}; +cvar_t sk_houndeye_dmg_blast2 = {"sk_houndeye_dmg_blast2","0"}; +cvar_t sk_houndeye_dmg_blast3 = {"sk_houndeye_dmg_blast3","0"}; + + +// ISlave +cvar_t sk_islave_health1 = {"sk_islave_health1","0"}; +cvar_t sk_islave_health2 = {"sk_islave_health2","0"}; +cvar_t sk_islave_health3 = {"sk_islave_health3","0"}; + +cvar_t sk_islave_dmg_claw1 = {"sk_islave_dmg_claw1","0"}; +cvar_t sk_islave_dmg_claw2 = {"sk_islave_dmg_claw2","0"}; +cvar_t sk_islave_dmg_claw3 = {"sk_islave_dmg_claw3","0"}; + +cvar_t sk_islave_dmg_clawrake1 = {"sk_islave_dmg_clawrake1","0"}; +cvar_t sk_islave_dmg_clawrake2 = {"sk_islave_dmg_clawrake2","0"}; +cvar_t sk_islave_dmg_clawrake3 = {"sk_islave_dmg_clawrake3","0"}; + +cvar_t sk_islave_dmg_zap1 = {"sk_islave_dmg_zap1","0"}; +cvar_t sk_islave_dmg_zap2 = {"sk_islave_dmg_zap2","0"}; +cvar_t sk_islave_dmg_zap3 = {"sk_islave_dmg_zap3","0"}; + + +// Icthyosaur +cvar_t sk_ichthyosaur_health1 = {"sk_ichthyosaur_health1","0"}; +cvar_t sk_ichthyosaur_health2 = {"sk_ichthyosaur_health2","0"}; +cvar_t sk_ichthyosaur_health3 = {"sk_ichthyosaur_health3","0"}; + +cvar_t sk_ichthyosaur_shake1 = {"sk_ichthyosaur_shake1","0"}; +cvar_t sk_ichthyosaur_shake2 = {"sk_ichthyosaur_shake2","0"}; +cvar_t sk_ichthyosaur_shake3 = {"sk_ichthyosaur_shake3","0"}; + + +// Leech +cvar_t sk_leech_health1 = {"sk_leech_health1","0"}; +cvar_t sk_leech_health2 = {"sk_leech_health2","0"}; +cvar_t sk_leech_health3 = {"sk_leech_health3","0"}; + +cvar_t sk_leech_dmg_bite1 = {"sk_leech_dmg_bite1","0"}; +cvar_t sk_leech_dmg_bite2 = {"sk_leech_dmg_bite2","0"}; +cvar_t sk_leech_dmg_bite3 = {"sk_leech_dmg_bite3","0"}; + +// Controller +cvar_t sk_controller_health1 = {"sk_controller_health1","0"}; +cvar_t sk_controller_health2 = {"sk_controller_health2","0"}; +cvar_t sk_controller_health3 = {"sk_controller_health3","0"}; + +cvar_t sk_controller_dmgzap1 = {"sk_controller_dmgzap1","0"}; +cvar_t sk_controller_dmgzap2 = {"sk_controller_dmgzap2","0"}; +cvar_t sk_controller_dmgzap3 = {"sk_controller_dmgzap3","0"}; + +cvar_t sk_controller_speedball1 = {"sk_controller_speedball1","0"}; +cvar_t sk_controller_speedball2 = {"sk_controller_speedball2","0"}; +cvar_t sk_controller_speedball3 = {"sk_controller_speedball3","0"}; + +cvar_t sk_controller_dmgball1 = {"sk_controller_dmgball1","0"}; +cvar_t sk_controller_dmgball2 = {"sk_controller_dmgball2","0"}; +cvar_t sk_controller_dmgball3 = {"sk_controller_dmgball3","0"}; + +// Nihilanth +cvar_t sk_nihilanth_health1 = {"sk_nihilanth_health1","0"}; +cvar_t sk_nihilanth_health2 = {"sk_nihilanth_health2","0"}; +cvar_t sk_nihilanth_health3 = {"sk_nihilanth_health3","0"}; + +cvar_t sk_nihilanth_zap1 = {"sk_nihilanth_zap1","0"}; +cvar_t sk_nihilanth_zap2 = {"sk_nihilanth_zap2","0"}; +cvar_t sk_nihilanth_zap3 = {"sk_nihilanth_zap3","0"}; + +// Scientist +cvar_t sk_scientist_health1 = {"sk_scientist_health1","0"}; +cvar_t sk_scientist_health2 = {"sk_scientist_health2","0"}; +cvar_t sk_scientist_health3 = {"sk_scientist_health3","0"}; + + +// Snark +cvar_t sk_snark_health1 = {"sk_snark_health1","0"}; +cvar_t sk_snark_health2 = {"sk_snark_health2","0"}; +cvar_t sk_snark_health3 = {"sk_snark_health3","0"}; + +cvar_t sk_snark_dmg_bite1 = {"sk_snark_dmg_bite1","0"}; +cvar_t sk_snark_dmg_bite2 = {"sk_snark_dmg_bite2","0"}; +cvar_t sk_snark_dmg_bite3 = {"sk_snark_dmg_bite3","0"}; + +cvar_t sk_snark_dmg_pop1 = {"sk_snark_dmg_pop1","0"}; +cvar_t sk_snark_dmg_pop2 = {"sk_snark_dmg_pop2","0"}; +cvar_t sk_snark_dmg_pop3 = {"sk_snark_dmg_pop3","0"}; + + + +// Zombie +cvar_t sk_zombie_health1 = {"sk_zombie_health1","0"}; +cvar_t sk_zombie_health2 = {"sk_zombie_health2","0"}; +cvar_t sk_zombie_health3 = {"sk_zombie_health3","0"}; + +cvar_t sk_zombie_dmg_one_slash1 = {"sk_zombie_dmg_one_slash1","0"}; +cvar_t sk_zombie_dmg_one_slash2 = {"sk_zombie_dmg_one_slash2","0"}; +cvar_t sk_zombie_dmg_one_slash3 = {"sk_zombie_dmg_one_slash3","0"}; + +cvar_t sk_zombie_dmg_both_slash1 = {"sk_zombie_dmg_both_slash1","0"}; +cvar_t sk_zombie_dmg_both_slash2 = {"sk_zombie_dmg_both_slash2","0"}; +cvar_t sk_zombie_dmg_both_slash3 = {"sk_zombie_dmg_both_slash3","0"}; + + +//Turret +cvar_t sk_turret_health1 = {"sk_turret_health1","0"}; +cvar_t sk_turret_health2 = {"sk_turret_health2","0"}; +cvar_t sk_turret_health3 = {"sk_turret_health3","0"}; + + +// MiniTurret +cvar_t sk_miniturret_health1 = {"sk_miniturret_health1","0"}; +cvar_t sk_miniturret_health2 = {"sk_miniturret_health2","0"}; +cvar_t sk_miniturret_health3 = {"sk_miniturret_health3","0"}; + + +// Sentry Turret +cvar_t sk_sentry_health1 = {"sk_sentry_health1","0"}; +cvar_t sk_sentry_health2 = {"sk_sentry_health2","0"}; +cvar_t sk_sentry_health3 = {"sk_sentry_health3","0"}; + + +// PLAYER WEAPONS + +// Crowbar whack +cvar_t sk_plr_crowbar1 = {"sk_plr_crowbar1","0"}; +cvar_t sk_plr_crowbar2 = {"sk_plr_crowbar2","0"}; +cvar_t sk_plr_crowbar3 = {"sk_plr_crowbar3","0"}; + +// Glock Round +cvar_t sk_plr_9mm_bullet1 = {"sk_plr_9mm_bullet1","0"}; +cvar_t sk_plr_9mm_bullet2 = {"sk_plr_9mm_bullet2","0"}; +cvar_t sk_plr_9mm_bullet3 = {"sk_plr_9mm_bullet3","0"}; + +// 357 Round +cvar_t sk_plr_357_bullet1 = {"sk_plr_357_bullet1","0"}; +cvar_t sk_plr_357_bullet2 = {"sk_plr_357_bullet2","0"}; +cvar_t sk_plr_357_bullet3 = {"sk_plr_357_bullet3","0"}; + +// MP5 Round +cvar_t sk_plr_9mmAR_bullet1 = {"sk_plr_9mmAR_bullet1","0"}; +cvar_t sk_plr_9mmAR_bullet2 = {"sk_plr_9mmAR_bullet2","0"}; +cvar_t sk_plr_9mmAR_bullet3 = {"sk_plr_9mmAR_bullet3","0"}; + + +// M203 grenade +cvar_t sk_plr_9mmAR_grenade1 = {"sk_plr_9mmAR_grenade1","0"}; +cvar_t sk_plr_9mmAR_grenade2 = {"sk_plr_9mmAR_grenade2","0"}; +cvar_t sk_plr_9mmAR_grenade3 = {"sk_plr_9mmAR_grenade3","0"}; + + +// Shotgun buckshot +cvar_t sk_plr_buckshot1 = {"sk_plr_buckshot1","0"}; +cvar_t sk_plr_buckshot2 = {"sk_plr_buckshot2","0"}; +cvar_t sk_plr_buckshot3 = {"sk_plr_buckshot3","0"}; + + +// Crossbow +cvar_t sk_plr_xbow_bolt_client1 = {"sk_plr_xbow_bolt_client1","0"}; +cvar_t sk_plr_xbow_bolt_client2 = {"sk_plr_xbow_bolt_client2","0"}; +cvar_t sk_plr_xbow_bolt_client3 = {"sk_plr_xbow_bolt_client3","0"}; + +cvar_t sk_plr_xbow_bolt_monster1 = {"sk_plr_xbow_bolt_monster1","0"}; +cvar_t sk_plr_xbow_bolt_monster2 = {"sk_plr_xbow_bolt_monster2","0"}; +cvar_t sk_plr_xbow_bolt_monster3 = {"sk_plr_xbow_bolt_monster3","0"}; + + +// RPG +cvar_t sk_plr_rpg1 = {"sk_plr_rpg1","0"}; +cvar_t sk_plr_rpg2 = {"sk_plr_rpg2","0"}; +cvar_t sk_plr_rpg3 = {"sk_plr_rpg3","0"}; + + +// Zero Point Generator +cvar_t sk_plr_gauss1 = {"sk_plr_gauss1","0"}; +cvar_t sk_plr_gauss2 = {"sk_plr_gauss2","0"}; +cvar_t sk_plr_gauss3 = {"sk_plr_gauss3","0"}; + + +// Tau Cannon +cvar_t sk_plr_egon_narrow1 = {"sk_plr_egon_narrow1","0"}; +cvar_t sk_plr_egon_narrow2 = {"sk_plr_egon_narrow2","0"}; +cvar_t sk_plr_egon_narrow3 = {"sk_plr_egon_narrow3","0"}; + +cvar_t sk_plr_egon_wide1 = {"sk_plr_egon_wide1","0"}; +cvar_t sk_plr_egon_wide2 = {"sk_plr_egon_wide2","0"}; +cvar_t sk_plr_egon_wide3 = {"sk_plr_egon_wide3","0"}; + + +// Hand Grendade +cvar_t sk_plr_hand_grenade1 = {"sk_plr_hand_grenade1","0"}; +cvar_t sk_plr_hand_grenade2 = {"sk_plr_hand_grenade2","0"}; +cvar_t sk_plr_hand_grenade3 = {"sk_plr_hand_grenade3","0"}; + + +// Satchel Charge +cvar_t sk_plr_satchel1 = {"sk_plr_satchel1","0"}; +cvar_t sk_plr_satchel2 = {"sk_plr_satchel2","0"}; +cvar_t sk_plr_satchel3 = {"sk_plr_satchel3","0"}; + + +// Tripmine +cvar_t sk_plr_tripmine1 = {"sk_plr_tripmine1","0"}; +cvar_t sk_plr_tripmine2 = {"sk_plr_tripmine2","0"}; +cvar_t sk_plr_tripmine3 = {"sk_plr_tripmine3","0"}; + + +// WORLD WEAPONS +cvar_t sk_12mm_bullet1 = {"sk_12mm_bullet1","0"}; +cvar_t sk_12mm_bullet2 = {"sk_12mm_bullet2","0"}; +cvar_t sk_12mm_bullet3 = {"sk_12mm_bullet3","0"}; + +cvar_t sk_9mmAR_bullet1 = {"sk_9mmAR_bullet1","0"}; +cvar_t sk_9mmAR_bullet2 = {"sk_9mmAR_bullet2","0"}; +cvar_t sk_9mmAR_bullet3 = {"sk_9mmAR_bullet3","0"}; + +cvar_t sk_9mm_bullet1 = {"sk_9mm_bullet1","0"}; +cvar_t sk_9mm_bullet2 = {"sk_9mm_bullet2","0"}; +cvar_t sk_9mm_bullet3 = {"sk_9mm_bullet3","0"}; + + +// HORNET +cvar_t sk_hornet_dmg1 = {"sk_hornet_dmg1","0"}; +cvar_t sk_hornet_dmg2 = {"sk_hornet_dmg2","0"}; +cvar_t sk_hornet_dmg3 = {"sk_hornet_dmg3","0"}; + +// HEALTH/CHARGE +cvar_t sk_suitcharger1 = { "sk_suitcharger1","0" }; +cvar_t sk_suitcharger2 = { "sk_suitcharger2","0" }; +cvar_t sk_suitcharger3 = { "sk_suitcharger3","0" }; + +cvar_t sk_battery1 = { "sk_battery1","0" }; +cvar_t sk_battery2 = { "sk_battery2","0" }; +cvar_t sk_battery3 = { "sk_battery3","0" }; + +cvar_t sk_healthcharger1 = { "sk_healthcharger1","0" }; +cvar_t sk_healthcharger2 = { "sk_healthcharger2","0" }; +cvar_t sk_healthcharger3 = { "sk_healthcharger3","0" }; + +cvar_t sk_healthkit1 = { "sk_healthkit1","0" }; +cvar_t sk_healthkit2 = { "sk_healthkit2","0" }; +cvar_t sk_healthkit3 = { "sk_healthkit3","0" }; + +cvar_t sk_scientist_heal1 = { "sk_scientist_heal1","0" }; +cvar_t sk_scientist_heal2 = { "sk_scientist_heal2","0" }; +cvar_t sk_scientist_heal3 = { "sk_scientist_heal3","0" }; + + +// monster damage adjusters +cvar_t sk_monster_head1 = { "sk_monster_head1","2" }; +cvar_t sk_monster_head2 = { "sk_monster_head2","2" }; +cvar_t sk_monster_head3 = { "sk_monster_head3","2" }; + +cvar_t sk_monster_chest1 = { "sk_monster_chest1","1" }; +cvar_t sk_monster_chest2 = { "sk_monster_chest2","1" }; +cvar_t sk_monster_chest3 = { "sk_monster_chest3","1" }; + +cvar_t sk_monster_stomach1 = { "sk_monster_stomach1","1" }; +cvar_t sk_monster_stomach2 = { "sk_monster_stomach2","1" }; +cvar_t sk_monster_stomach3 = { "sk_monster_stomach3","1" }; + +cvar_t sk_monster_arm1 = { "sk_monster_arm1","1" }; +cvar_t sk_monster_arm2 = { "sk_monster_arm2","1" }; +cvar_t sk_monster_arm3 = { "sk_monster_arm3","1" }; + +cvar_t sk_monster_leg1 = { "sk_monster_leg1","1" }; +cvar_t sk_monster_leg2 = { "sk_monster_leg2","1" }; +cvar_t sk_monster_leg3 = { "sk_monster_leg3","1" }; + +// player damage adjusters +cvar_t sk_player_head1 = { "sk_player_head1","2" }; +cvar_t sk_player_head2 = { "sk_player_head2","2" }; +cvar_t sk_player_head3 = { "sk_player_head3","2" }; + +cvar_t sk_player_chest1 = { "sk_player_chest1","1" }; +cvar_t sk_player_chest2 = { "sk_player_chest2","1" }; +cvar_t sk_player_chest3 = { "sk_player_chest3","1" }; + +cvar_t sk_player_stomach1 = { "sk_player_stomach1","1" }; +cvar_t sk_player_stomach2 = { "sk_player_stomach2","1" }; +cvar_t sk_player_stomach3 = { "sk_player_stomach3","1" }; + +cvar_t sk_player_arm1 = { "sk_player_arm1","1" }; +cvar_t sk_player_arm2 = { "sk_player_arm2","1" }; +cvar_t sk_player_arm3 = { "sk_player_arm3","1" }; + +cvar_t sk_player_leg1 = { "sk_player_leg1","1" }; +cvar_t sk_player_leg2 = { "sk_player_leg2","1" }; +cvar_t sk_player_leg3 = { "sk_player_leg3","1" }; + +// END Cvars for Skill Level settings + +// Register your console variables here +// This gets called one time when the game is initialied +void GameDLLInit( void ) +{ + // Register cvars here: + + g_psv_gravity = CVAR_GET_POINTER( "sv_gravity" ); + g_psv_aim = CVAR_GET_POINTER( "sv_aim" ); + g_footsteps = CVAR_GET_POINTER( "mp_footsteps" ); + + CVAR_REGISTER (&displaysoundlist); + CVAR_REGISTER( &allow_spectators ); + + CVAR_REGISTER (&teamplay); + CVAR_REGISTER (&fraglimit); + CVAR_REGISTER (&timelimit); + + CVAR_REGISTER (&fragsleft); + CVAR_REGISTER (&timeleft); + + CVAR_REGISTER (&friendlyfire); + CVAR_REGISTER (&falldamage); + CVAR_REGISTER (&weaponstay); + CVAR_REGISTER (&forcerespawn); + CVAR_REGISTER (&flashlight); + CVAR_REGISTER (&aimcrosshair); + CVAR_REGISTER (&decalfrequency); + CVAR_REGISTER (&teamlist); + CVAR_REGISTER (&teamoverride); + CVAR_REGISTER (&defaultteam); + CVAR_REGISTER (&allowmonsters); + + CVAR_REGISTER (&mp_chattime); + +// REGISTER CVARS FOR SKILL LEVEL STUFF + // Agrunt + CVAR_REGISTER ( &sk_agrunt_health1 );// {"sk_agrunt_health1","0"}; + CVAR_REGISTER ( &sk_agrunt_health2 );// {"sk_agrunt_health2","0"}; + CVAR_REGISTER ( &sk_agrunt_health3 );// {"sk_agrunt_health3","0"}; + + CVAR_REGISTER ( &sk_agrunt_dmg_punch1 );// {"sk_agrunt_dmg_punch1","0"}; + CVAR_REGISTER ( &sk_agrunt_dmg_punch2 );// {"sk_agrunt_dmg_punch2","0"}; + CVAR_REGISTER ( &sk_agrunt_dmg_punch3 );// {"sk_agrunt_dmg_punch3","0"}; + + // Apache + CVAR_REGISTER ( &sk_apache_health1 );// {"sk_apache_health1","0"}; + CVAR_REGISTER ( &sk_apache_health2 );// {"sk_apache_health2","0"}; + CVAR_REGISTER ( &sk_apache_health3 );// {"sk_apache_health3","0"}; + + // Barney + CVAR_REGISTER ( &sk_barney_health1 );// {"sk_barney_health1","0"}; + CVAR_REGISTER ( &sk_barney_health2 );// {"sk_barney_health2","0"}; + CVAR_REGISTER ( &sk_barney_health3 );// {"sk_barney_health3","0"}; + + // Bullsquid + CVAR_REGISTER ( &sk_bullsquid_health1 );// {"sk_bullsquid_health1","0"}; + CVAR_REGISTER ( &sk_bullsquid_health2 );// {"sk_bullsquid_health2","0"}; + CVAR_REGISTER ( &sk_bullsquid_health3 );// {"sk_bullsquid_health3","0"}; + + CVAR_REGISTER ( &sk_bullsquid_dmg_bite1 );// {"sk_bullsquid_dmg_bite1","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_bite2 );// {"sk_bullsquid_dmg_bite2","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_bite3 );// {"sk_bullsquid_dmg_bite3","0"}; + + CVAR_REGISTER ( &sk_bullsquid_dmg_whip1 );// {"sk_bullsquid_dmg_whip1","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_whip2 );// {"sk_bullsquid_dmg_whip2","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_whip3 );// {"sk_bullsquid_dmg_whip3","0"}; + + CVAR_REGISTER ( &sk_bullsquid_dmg_spit1 );// {"sk_bullsquid_dmg_spit1","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_spit2 );// {"sk_bullsquid_dmg_spit2","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_spit3 );// {"sk_bullsquid_dmg_spit3","0"}; + + + CVAR_REGISTER ( &sk_bigmomma_health_factor1 );// {"sk_bigmomma_health_factor1","1.0"}; + CVAR_REGISTER ( &sk_bigmomma_health_factor2 );// {"sk_bigmomma_health_factor2","1.0"}; + CVAR_REGISTER ( &sk_bigmomma_health_factor3 );// {"sk_bigmomma_health_factor3","1.0"}; + + CVAR_REGISTER ( &sk_bigmomma_dmg_slash1 );// {"sk_bigmomma_dmg_slash1","50"}; + CVAR_REGISTER ( &sk_bigmomma_dmg_slash2 );// {"sk_bigmomma_dmg_slash2","50"}; + CVAR_REGISTER ( &sk_bigmomma_dmg_slash3 );// {"sk_bigmomma_dmg_slash3","50"}; + + CVAR_REGISTER ( &sk_bigmomma_dmg_blast1 );// {"sk_bigmomma_dmg_blast1","100"}; + CVAR_REGISTER ( &sk_bigmomma_dmg_blast2 );// {"sk_bigmomma_dmg_blast2","100"}; + CVAR_REGISTER ( &sk_bigmomma_dmg_blast3 );// {"sk_bigmomma_dmg_blast3","100"}; + + CVAR_REGISTER ( &sk_bigmomma_radius_blast1 );// {"sk_bigmomma_radius_blast1","250"}; + CVAR_REGISTER ( &sk_bigmomma_radius_blast2 );// {"sk_bigmomma_radius_blast2","250"}; + CVAR_REGISTER ( &sk_bigmomma_radius_blast3 );// {"sk_bigmomma_radius_blast3","250"}; + + // Gargantua + CVAR_REGISTER ( &sk_gargantua_health1 );// {"sk_gargantua_health1","0"}; + CVAR_REGISTER ( &sk_gargantua_health2 );// {"sk_gargantua_health2","0"}; + CVAR_REGISTER ( &sk_gargantua_health3 );// {"sk_gargantua_health3","0"}; + + CVAR_REGISTER ( &sk_gargantua_dmg_slash1 );// {"sk_gargantua_dmg_slash1","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_slash2 );// {"sk_gargantua_dmg_slash2","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_slash3 );// {"sk_gargantua_dmg_slash3","0"}; + + CVAR_REGISTER ( &sk_gargantua_dmg_fire1 );// {"sk_gargantua_dmg_fire1","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_fire2 );// {"sk_gargantua_dmg_fire2","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_fire3 );// {"sk_gargantua_dmg_fire3","0"}; + + CVAR_REGISTER ( &sk_gargantua_dmg_stomp1 );// {"sk_gargantua_dmg_stomp1","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_stomp2 );// {"sk_gargantua_dmg_stomp2","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_stomp3 );// {"sk_gargantua_dmg_stomp3","0"}; + + + // Hassassin + CVAR_REGISTER ( &sk_hassassin_health1 );// {"sk_hassassin_health1","0"}; + CVAR_REGISTER ( &sk_hassassin_health2 );// {"sk_hassassin_health2","0"}; + CVAR_REGISTER ( &sk_hassassin_health3 );// {"sk_hassassin_health3","0"}; + + + // Headcrab + CVAR_REGISTER ( &sk_headcrab_health1 );// {"sk_headcrab_health1","0"}; + CVAR_REGISTER ( &sk_headcrab_health2 );// {"sk_headcrab_health2","0"}; + CVAR_REGISTER ( &sk_headcrab_health3 );// {"sk_headcrab_health3","0"}; + + CVAR_REGISTER ( &sk_headcrab_dmg_bite1 );// {"sk_headcrab_dmg_bite1","0"}; + CVAR_REGISTER ( &sk_headcrab_dmg_bite2 );// {"sk_headcrab_dmg_bite2","0"}; + CVAR_REGISTER ( &sk_headcrab_dmg_bite3 );// {"sk_headcrab_dmg_bite3","0"}; + + + // Hgrunt + CVAR_REGISTER ( &sk_hgrunt_health1 );// {"sk_hgrunt_health1","0"}; + CVAR_REGISTER ( &sk_hgrunt_health2 );// {"sk_hgrunt_health2","0"}; + CVAR_REGISTER ( &sk_hgrunt_health3 );// {"sk_hgrunt_health3","0"}; + + CVAR_REGISTER ( &sk_hgrunt_kick1 );// {"sk_hgrunt_kick1","0"}; + CVAR_REGISTER ( &sk_hgrunt_kick2 );// {"sk_hgrunt_kick2","0"}; + CVAR_REGISTER ( &sk_hgrunt_kick3 );// {"sk_hgrunt_kick3","0"}; + + CVAR_REGISTER ( &sk_hgrunt_pellets1 ); + CVAR_REGISTER ( &sk_hgrunt_pellets2 ); + CVAR_REGISTER ( &sk_hgrunt_pellets3 ); + + CVAR_REGISTER ( &sk_hgrunt_gspeed1 ); + CVAR_REGISTER ( &sk_hgrunt_gspeed2 ); + CVAR_REGISTER ( &sk_hgrunt_gspeed3 ); + + // Houndeye + CVAR_REGISTER ( &sk_houndeye_health1 );// {"sk_houndeye_health1","0"}; + CVAR_REGISTER ( &sk_houndeye_health2 );// {"sk_houndeye_health2","0"}; + CVAR_REGISTER ( &sk_houndeye_health3 );// {"sk_houndeye_health3","0"}; + + CVAR_REGISTER ( &sk_houndeye_dmg_blast1 );// {"sk_houndeye_dmg_blast1","0"}; + CVAR_REGISTER ( &sk_houndeye_dmg_blast2 );// {"sk_houndeye_dmg_blast2","0"}; + CVAR_REGISTER ( &sk_houndeye_dmg_blast3 );// {"sk_houndeye_dmg_blast3","0"}; + + + // ISlave + CVAR_REGISTER ( &sk_islave_health1 );// {"sk_islave_health1","0"}; + CVAR_REGISTER ( &sk_islave_health2 );// {"sk_islave_health2","0"}; + CVAR_REGISTER ( &sk_islave_health3 );// {"sk_islave_health3","0"}; + + CVAR_REGISTER ( &sk_islave_dmg_claw1 );// {"sk_islave_dmg_claw1","0"}; + CVAR_REGISTER ( &sk_islave_dmg_claw2 );// {"sk_islave_dmg_claw2","0"}; + CVAR_REGISTER ( &sk_islave_dmg_claw3 );// {"sk_islave_dmg_claw3","0"}; + + CVAR_REGISTER ( &sk_islave_dmg_clawrake1 );// {"sk_islave_dmg_clawrake1","0"}; + CVAR_REGISTER ( &sk_islave_dmg_clawrake2 );// {"sk_islave_dmg_clawrake2","0"}; + CVAR_REGISTER ( &sk_islave_dmg_clawrake3 );// {"sk_islave_dmg_clawrake3","0"}; + + CVAR_REGISTER ( &sk_islave_dmg_zap1 );// {"sk_islave_dmg_zap1","0"}; + CVAR_REGISTER ( &sk_islave_dmg_zap2 );// {"sk_islave_dmg_zap2","0"}; + CVAR_REGISTER ( &sk_islave_dmg_zap3 );// {"sk_islave_dmg_zap3","0"}; + + + // Icthyosaur + CVAR_REGISTER ( &sk_ichthyosaur_health1 );// {"sk_ichthyosaur_health1","0"}; + CVAR_REGISTER ( &sk_ichthyosaur_health2 );// {"sk_ichthyosaur_health2","0"}; + CVAR_REGISTER ( &sk_ichthyosaur_health3 );// {"sk_ichthyosaur_health3","0"}; + + CVAR_REGISTER ( &sk_ichthyosaur_shake1 );// {"sk_ichthyosaur_health3","0"}; + CVAR_REGISTER ( &sk_ichthyosaur_shake2 );// {"sk_ichthyosaur_health3","0"}; + CVAR_REGISTER ( &sk_ichthyosaur_shake3 );// {"sk_ichthyosaur_health3","0"}; + + + + // Leech + CVAR_REGISTER ( &sk_leech_health1 );// {"sk_leech_health1","0"}; + CVAR_REGISTER ( &sk_leech_health2 );// {"sk_leech_health2","0"}; + CVAR_REGISTER ( &sk_leech_health3 );// {"sk_leech_health3","0"}; + + CVAR_REGISTER ( &sk_leech_dmg_bite1 );// {"sk_leech_dmg_bite1","0"}; + CVAR_REGISTER ( &sk_leech_dmg_bite2 );// {"sk_leech_dmg_bite2","0"}; + CVAR_REGISTER ( &sk_leech_dmg_bite3 );// {"sk_leech_dmg_bite3","0"}; + + + // Controller + CVAR_REGISTER ( &sk_controller_health1 ); + CVAR_REGISTER ( &sk_controller_health2 ); + CVAR_REGISTER ( &sk_controller_health3 ); + + CVAR_REGISTER ( &sk_controller_dmgzap1 ); + CVAR_REGISTER ( &sk_controller_dmgzap2 ); + CVAR_REGISTER ( &sk_controller_dmgzap3 ); + + CVAR_REGISTER ( &sk_controller_speedball1 ); + CVAR_REGISTER ( &sk_controller_speedball2 ); + CVAR_REGISTER ( &sk_controller_speedball3 ); + + CVAR_REGISTER ( &sk_controller_dmgball1 ); + CVAR_REGISTER ( &sk_controller_dmgball2 ); + CVAR_REGISTER ( &sk_controller_dmgball3 ); + + // Nihilanth + CVAR_REGISTER ( &sk_nihilanth_health1 );// {"sk_nihilanth_health1","0"}; + CVAR_REGISTER ( &sk_nihilanth_health2 );// {"sk_nihilanth_health2","0"}; + CVAR_REGISTER ( &sk_nihilanth_health3 );// {"sk_nihilanth_health3","0"}; + + CVAR_REGISTER ( &sk_nihilanth_zap1 ); + CVAR_REGISTER ( &sk_nihilanth_zap2 ); + CVAR_REGISTER ( &sk_nihilanth_zap3 ); + + // Scientist + CVAR_REGISTER ( &sk_scientist_health1 );// {"sk_scientist_health1","0"}; + CVAR_REGISTER ( &sk_scientist_health2 );// {"sk_scientist_health2","0"}; + CVAR_REGISTER ( &sk_scientist_health3 );// {"sk_scientist_health3","0"}; + + + // Snark + CVAR_REGISTER ( &sk_snark_health1 );// {"sk_snark_health1","0"}; + CVAR_REGISTER ( &sk_snark_health2 );// {"sk_snark_health2","0"}; + CVAR_REGISTER ( &sk_snark_health3 );// {"sk_snark_health3","0"}; + + CVAR_REGISTER ( &sk_snark_dmg_bite1 );// {"sk_snark_dmg_bite1","0"}; + CVAR_REGISTER ( &sk_snark_dmg_bite2 );// {"sk_snark_dmg_bite2","0"}; + CVAR_REGISTER ( &sk_snark_dmg_bite3 );// {"sk_snark_dmg_bite3","0"}; + + CVAR_REGISTER ( &sk_snark_dmg_pop1 );// {"sk_snark_dmg_pop1","0"}; + CVAR_REGISTER ( &sk_snark_dmg_pop2 );// {"sk_snark_dmg_pop2","0"}; + CVAR_REGISTER ( &sk_snark_dmg_pop3 );// {"sk_snark_dmg_pop3","0"}; + + + + // Zombie + CVAR_REGISTER ( &sk_zombie_health1 );// {"sk_zombie_health1","0"}; + CVAR_REGISTER ( &sk_zombie_health2 );// {"sk_zombie_health3","0"}; + CVAR_REGISTER ( &sk_zombie_health3 );// {"sk_zombie_health3","0"}; + + CVAR_REGISTER ( &sk_zombie_dmg_one_slash1 );// {"sk_zombie_dmg_one_slash1","0"}; + CVAR_REGISTER ( &sk_zombie_dmg_one_slash2 );// {"sk_zombie_dmg_one_slash2","0"}; + CVAR_REGISTER ( &sk_zombie_dmg_one_slash3 );// {"sk_zombie_dmg_one_slash3","0"}; + + CVAR_REGISTER ( &sk_zombie_dmg_both_slash1 );// {"sk_zombie_dmg_both_slash1","0"}; + CVAR_REGISTER ( &sk_zombie_dmg_both_slash2 );// {"sk_zombie_dmg_both_slash2","0"}; + CVAR_REGISTER ( &sk_zombie_dmg_both_slash3 );// {"sk_zombie_dmg_both_slash3","0"}; + + + //Turret + CVAR_REGISTER ( &sk_turret_health1 );// {"sk_turret_health1","0"}; + CVAR_REGISTER ( &sk_turret_health2 );// {"sk_turret_health2","0"}; + CVAR_REGISTER ( &sk_turret_health3 );// {"sk_turret_health3","0"}; + + + // MiniTurret + CVAR_REGISTER ( &sk_miniturret_health1 );// {"sk_miniturret_health1","0"}; + CVAR_REGISTER ( &sk_miniturret_health2 );// {"sk_miniturret_health2","0"}; + CVAR_REGISTER ( &sk_miniturret_health3 );// {"sk_miniturret_health3","0"}; + + + // Sentry Turret + CVAR_REGISTER ( &sk_sentry_health1 );// {"sk_sentry_health1","0"}; + CVAR_REGISTER ( &sk_sentry_health2 );// {"sk_sentry_health2","0"}; + CVAR_REGISTER ( &sk_sentry_health3 );// {"sk_sentry_health3","0"}; + + + // PLAYER WEAPONS + + // Crowbar whack + CVAR_REGISTER ( &sk_plr_crowbar1 );// {"sk_plr_crowbar1","0"}; + CVAR_REGISTER ( &sk_plr_crowbar2 );// {"sk_plr_crowbar2","0"}; + CVAR_REGISTER ( &sk_plr_crowbar3 );// {"sk_plr_crowbar3","0"}; + + // Glock Round + CVAR_REGISTER ( &sk_plr_9mm_bullet1 );// {"sk_plr_9mm_bullet1","0"}; + CVAR_REGISTER ( &sk_plr_9mm_bullet2 );// {"sk_plr_9mm_bullet2","0"}; + CVAR_REGISTER ( &sk_plr_9mm_bullet3 );// {"sk_plr_9mm_bullet3","0"}; + + // 357 Round + CVAR_REGISTER ( &sk_plr_357_bullet1 );// {"sk_plr_357_bullet1","0"}; + CVAR_REGISTER ( &sk_plr_357_bullet2 );// {"sk_plr_357_bullet2","0"}; + CVAR_REGISTER ( &sk_plr_357_bullet3 );// {"sk_plr_357_bullet3","0"}; + + // MP5 Round + CVAR_REGISTER ( &sk_plr_9mmAR_bullet1 );// {"sk_plr_9mmAR_bullet1","0"}; + CVAR_REGISTER ( &sk_plr_9mmAR_bullet2 );// {"sk_plr_9mmAR_bullet2","0"}; + CVAR_REGISTER ( &sk_plr_9mmAR_bullet3 );// {"sk_plr_9mmAR_bullet3","0"}; + + + // M203 grenade + CVAR_REGISTER ( &sk_plr_9mmAR_grenade1 );// {"sk_plr_9mmAR_grenade1","0"}; + CVAR_REGISTER ( &sk_plr_9mmAR_grenade2 );// {"sk_plr_9mmAR_grenade2","0"}; + CVAR_REGISTER ( &sk_plr_9mmAR_grenade3 );// {"sk_plr_9mmAR_grenade3","0"}; + + + // Shotgun buckshot + CVAR_REGISTER ( &sk_plr_buckshot1 );// {"sk_plr_buckshot1","0"}; + CVAR_REGISTER ( &sk_plr_buckshot2 );// {"sk_plr_buckshot2","0"}; + CVAR_REGISTER ( &sk_plr_buckshot3 );// {"sk_plr_buckshot3","0"}; + + + // Crossbow + CVAR_REGISTER ( &sk_plr_xbow_bolt_monster1 );// {"sk_plr_xbow_bolt1","0"}; + CVAR_REGISTER ( &sk_plr_xbow_bolt_monster2 );// {"sk_plr_xbow_bolt2","0"}; + CVAR_REGISTER ( &sk_plr_xbow_bolt_monster3 );// {"sk_plr_xbow_bolt3","0"}; + + CVAR_REGISTER ( &sk_plr_xbow_bolt_client1 );// {"sk_plr_xbow_bolt1","0"}; + CVAR_REGISTER ( &sk_plr_xbow_bolt_client2 );// {"sk_plr_xbow_bolt2","0"}; + CVAR_REGISTER ( &sk_plr_xbow_bolt_client3 );// {"sk_plr_xbow_bolt3","0"}; + + + // RPG + CVAR_REGISTER ( &sk_plr_rpg1 );// {"sk_plr_rpg1","0"}; + CVAR_REGISTER ( &sk_plr_rpg2 );// {"sk_plr_rpg2","0"}; + CVAR_REGISTER ( &sk_plr_rpg3 );// {"sk_plr_rpg3","0"}; + + + // Gauss Gun + CVAR_REGISTER ( &sk_plr_gauss1 );// {"sk_plr_gauss1","0"}; + CVAR_REGISTER ( &sk_plr_gauss2 );// {"sk_plr_gauss2","0"}; + CVAR_REGISTER ( &sk_plr_gauss3 );// {"sk_plr_gauss3","0"}; + + + // Egon Gun + CVAR_REGISTER ( &sk_plr_egon_narrow1 );// {"sk_plr_egon_narrow1","0"}; + CVAR_REGISTER ( &sk_plr_egon_narrow2 );// {"sk_plr_egon_narrow2","0"}; + CVAR_REGISTER ( &sk_plr_egon_narrow3 );// {"sk_plr_egon_narrow3","0"}; + + CVAR_REGISTER ( &sk_plr_egon_wide1 );// {"sk_plr_egon_wide1","0"}; + CVAR_REGISTER ( &sk_plr_egon_wide2 );// {"sk_plr_egon_wide2","0"}; + CVAR_REGISTER ( &sk_plr_egon_wide3 );// {"sk_plr_egon_wide3","0"}; + + + // Hand Grendade + CVAR_REGISTER ( &sk_plr_hand_grenade1 );// {"sk_plr_hand_grenade1","0"}; + CVAR_REGISTER ( &sk_plr_hand_grenade2 );// {"sk_plr_hand_grenade2","0"}; + CVAR_REGISTER ( &sk_plr_hand_grenade3 );// {"sk_plr_hand_grenade3","0"}; + + + // Satchel Charge + CVAR_REGISTER ( &sk_plr_satchel1 );// {"sk_plr_satchel1","0"}; + CVAR_REGISTER ( &sk_plr_satchel2 );// {"sk_plr_satchel2","0"}; + CVAR_REGISTER ( &sk_plr_satchel3 );// {"sk_plr_satchel3","0"}; + + + // Tripmine + CVAR_REGISTER ( &sk_plr_tripmine1 );// {"sk_plr_tripmine1","0"}; + CVAR_REGISTER ( &sk_plr_tripmine2 );// {"sk_plr_tripmine2","0"}; + CVAR_REGISTER ( &sk_plr_tripmine3 );// {"sk_plr_tripmine3","0"}; + + + // WORLD WEAPONS + CVAR_REGISTER ( &sk_12mm_bullet1 );// {"sk_12mm_bullet1","0"}; + CVAR_REGISTER ( &sk_12mm_bullet2 );// {"sk_12mm_bullet2","0"}; + CVAR_REGISTER ( &sk_12mm_bullet3 );// {"sk_12mm_bullet3","0"}; + + CVAR_REGISTER ( &sk_9mmAR_bullet1 );// {"sk_9mm_bullet1","0"}; + CVAR_REGISTER ( &sk_9mmAR_bullet2 );// {"sk_9mm_bullet1","0"}; + CVAR_REGISTER ( &sk_9mmAR_bullet3 );// {"sk_9mm_bullet1","0"}; + + CVAR_REGISTER ( &sk_9mm_bullet1 );// {"sk_9mm_bullet1","0"}; + CVAR_REGISTER ( &sk_9mm_bullet2 );// {"sk_9mm_bullet2","0"}; + CVAR_REGISTER ( &sk_9mm_bullet3 );// {"sk_9mm_bullet3","0"}; + + + // HORNET + CVAR_REGISTER ( &sk_hornet_dmg1 );// {"sk_hornet_dmg1","0"}; + CVAR_REGISTER ( &sk_hornet_dmg2 );// {"sk_hornet_dmg2","0"}; + CVAR_REGISTER ( &sk_hornet_dmg3 );// {"sk_hornet_dmg3","0"}; + + // HEALTH/SUIT CHARGE DISTRIBUTION + CVAR_REGISTER ( &sk_suitcharger1 ); + CVAR_REGISTER ( &sk_suitcharger2 ); + CVAR_REGISTER ( &sk_suitcharger3 ); + + CVAR_REGISTER ( &sk_battery1 ); + CVAR_REGISTER ( &sk_battery2 ); + CVAR_REGISTER ( &sk_battery3 ); + + CVAR_REGISTER ( &sk_healthcharger1 ); + CVAR_REGISTER ( &sk_healthcharger2 ); + CVAR_REGISTER ( &sk_healthcharger3 ); + + CVAR_REGISTER ( &sk_healthkit1 ); + CVAR_REGISTER ( &sk_healthkit2 ); + CVAR_REGISTER ( &sk_healthkit3 ); + + CVAR_REGISTER ( &sk_scientist_heal1 ); + CVAR_REGISTER ( &sk_scientist_heal2 ); + CVAR_REGISTER ( &sk_scientist_heal3 ); + +// monster damage adjusters + CVAR_REGISTER ( &sk_monster_head1 ); + CVAR_REGISTER ( &sk_monster_head2 ); + CVAR_REGISTER ( &sk_monster_head3 ); + + CVAR_REGISTER ( &sk_monster_chest1 ); + CVAR_REGISTER ( &sk_monster_chest2 ); + CVAR_REGISTER ( &sk_monster_chest3 ); + + CVAR_REGISTER ( &sk_monster_stomach1 ); + CVAR_REGISTER ( &sk_monster_stomach2 ); + CVAR_REGISTER ( &sk_monster_stomach3 ); + + CVAR_REGISTER ( &sk_monster_arm1 ); + CVAR_REGISTER ( &sk_monster_arm2 ); + CVAR_REGISTER ( &sk_monster_arm3 ); + + CVAR_REGISTER ( &sk_monster_leg1 ); + CVAR_REGISTER ( &sk_monster_leg2 ); + CVAR_REGISTER ( &sk_monster_leg3 ); + +// player damage adjusters + CVAR_REGISTER ( &sk_player_head1 ); + CVAR_REGISTER ( &sk_player_head2 ); + CVAR_REGISTER ( &sk_player_head3 ); + + CVAR_REGISTER ( &sk_player_chest1 ); + CVAR_REGISTER ( &sk_player_chest2 ); + CVAR_REGISTER ( &sk_player_chest3 ); + + CVAR_REGISTER ( &sk_player_stomach1 ); + CVAR_REGISTER ( &sk_player_stomach2 ); + CVAR_REGISTER ( &sk_player_stomach3 ); + + CVAR_REGISTER ( &sk_player_arm1 ); + CVAR_REGISTER ( &sk_player_arm2 ); + CVAR_REGISTER ( &sk_player_arm3 ); + + CVAR_REGISTER ( &sk_player_leg1 ); + CVAR_REGISTER ( &sk_player_leg2 ); + CVAR_REGISTER ( &sk_player_leg3 ); +// END REGISTER CVARS FOR SKILL LEVEL STUFF + + SERVER_COMMAND( "exec skill.cfg\n" ); +} + diff --git a/dlls/game.h b/dlls/game.h new file mode 100644 index 0000000..df82f00 --- /dev/null +++ b/dlls/game.h @@ -0,0 +1,45 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef GAME_H +#define GAME_H + +extern void GameDLLInit( void ); + + +extern cvar_t displaysoundlist; + +// multiplayer server rules +extern cvar_t teamplay; +extern cvar_t fraglimit; +extern cvar_t timelimit; +extern cvar_t friendlyfire; +extern cvar_t falldamage; +extern cvar_t weaponstay; +extern cvar_t forcerespawn; +extern cvar_t flashlight; +extern cvar_t aimcrosshair; +extern cvar_t decalfrequency; +extern cvar_t teamlist; +extern cvar_t teamoverride; +extern cvar_t defaultteam; +extern cvar_t allowmonsters; + +// Engine Cvars +extern cvar_t *g_psv_gravity; +extern cvar_t *g_psv_aim; +extern cvar_t *g_footsteps; + +#endif // GAME_H diff --git a/dlls/gamerules.cpp b/dlls/gamerules.cpp new file mode 100644 index 0000000..dd406c0 --- /dev/null +++ b/dlls/gamerules.cpp @@ -0,0 +1,347 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// GameRules.cpp +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "teamplay_gamerules.h" +#include "skill.h" +#include "game.h" + +extern edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ); + +DLL_GLOBAL CGameRules* g_pGameRules = NULL; +extern DLL_GLOBAL BOOL g_fGameOver; +extern int gmsgDeathMsg; // client dll messages +extern int gmsgMOTD; + +int g_teamplay = 0; + +//========================================================= +//========================================================= +BOOL CGameRules::CanHaveAmmo( CBasePlayer *pPlayer, const char *pszAmmoName, int iMaxCarry ) +{ + int iAmmoIndex; + + if ( pszAmmoName ) + { + iAmmoIndex = pPlayer->GetAmmoIndex( pszAmmoName ); + + if ( iAmmoIndex > -1 ) + { + if ( pPlayer->AmmoInventory( iAmmoIndex ) < iMaxCarry ) + { + // player has room for more of this type of ammo + return TRUE; + } + } + } + + return FALSE; +} + +//========================================================= +//========================================================= +edict_t *CGameRules :: GetPlayerSpawnSpot( CBasePlayer *pPlayer ) +{ + edict_t *pentSpawnSpot = EntSelectSpawnPoint( pPlayer ); + + pPlayer->pev->origin = VARS(pentSpawnSpot)->origin + Vector(0,0,1); + pPlayer->pev->v_angle = g_vecZero; + pPlayer->pev->velocity = g_vecZero; + pPlayer->pev->angles = VARS(pentSpawnSpot)->angles; + pPlayer->pev->punchangle = g_vecZero; + pPlayer->pev->fixangle = TRUE; + + return pentSpawnSpot; +} + +//========================================================= +//========================================================= +BOOL CGameRules::CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ + // only living players can have items + if ( pPlayer->pev->deadflag != DEAD_NO ) + return FALSE; + + if ( pWeapon->pszAmmo1() ) + { + if ( !CanHaveAmmo( pPlayer, pWeapon->pszAmmo1(), pWeapon->iMaxAmmo1() ) ) + { + // we can't carry anymore ammo for this gun. We can only + // have the gun if we aren't already carrying one of this type + if ( pPlayer->HasPlayerItem( pWeapon ) ) + { + return FALSE; + } + } + } + else + { + // weapon doesn't use ammo, don't take another if you already have it. + if ( pPlayer->HasPlayerItem( pWeapon ) ) + { + return FALSE; + } + } + + // note: will fall through to here if GetItemInfo doesn't fill the struct! + return TRUE; +} + +//========================================================= +// load the SkillData struct with the proper values based on the skill level. +//========================================================= +void CGameRules::RefreshSkillData ( void ) +{ + int iSkill; + + iSkill = (int)CVAR_GET_FLOAT("skill"); + g_iSkillLevel = iSkill; + + if ( iSkill < 1 ) + { + iSkill = 1; + } + else if ( iSkill > 3 ) + { + iSkill = 3; + } + + gSkillData.iSkillLevel = iSkill; + + ALERT ( at_console, "\nGAME SKILL LEVEL:%d\n",iSkill ); + + //Agrunt + gSkillData.agruntHealth = GetSkillCvar( "sk_agrunt_health" ); + gSkillData.agruntDmgPunch = GetSkillCvar( "sk_agrunt_dmg_punch"); + + // Apache + gSkillData.apacheHealth = GetSkillCvar( "sk_apache_health"); + + // Barney + gSkillData.barneyHealth = GetSkillCvar( "sk_barney_health"); + + // Big Momma + gSkillData.bigmommaHealthFactor = GetSkillCvar( "sk_bigmomma_health_factor" ); + gSkillData.bigmommaDmgSlash = GetSkillCvar( "sk_bigmomma_dmg_slash" ); + gSkillData.bigmommaDmgBlast = GetSkillCvar( "sk_bigmomma_dmg_blast" ); + gSkillData.bigmommaRadiusBlast = GetSkillCvar( "sk_bigmomma_radius_blast" ); + + // Bullsquid + gSkillData.bullsquidHealth = GetSkillCvar( "sk_bullsquid_health"); + gSkillData.bullsquidDmgBite = GetSkillCvar( "sk_bullsquid_dmg_bite"); + gSkillData.bullsquidDmgWhip = GetSkillCvar( "sk_bullsquid_dmg_whip"); + gSkillData.bullsquidDmgSpit = GetSkillCvar( "sk_bullsquid_dmg_spit"); + + // Gargantua + gSkillData.gargantuaHealth = GetSkillCvar( "sk_gargantua_health"); + gSkillData.gargantuaDmgSlash = GetSkillCvar( "sk_gargantua_dmg_slash"); + gSkillData.gargantuaDmgFire = GetSkillCvar( "sk_gargantua_dmg_fire"); + gSkillData.gargantuaDmgStomp = GetSkillCvar( "sk_gargantua_dmg_stomp"); + + // Hassassin + gSkillData.hassassinHealth = GetSkillCvar( "sk_hassassin_health"); + + // Headcrab + gSkillData.headcrabHealth = GetSkillCvar( "sk_headcrab_health"); + gSkillData.headcrabDmgBite = GetSkillCvar( "sk_headcrab_dmg_bite"); + + // Hgrunt + gSkillData.hgruntHealth = GetSkillCvar( "sk_hgrunt_health"); + gSkillData.hgruntDmgKick = GetSkillCvar( "sk_hgrunt_kick"); + gSkillData.hgruntShotgunPellets = GetSkillCvar( "sk_hgrunt_pellets"); + gSkillData.hgruntGrenadeSpeed = GetSkillCvar( "sk_hgrunt_gspeed"); + + // Houndeye + gSkillData.houndeyeHealth = GetSkillCvar( "sk_houndeye_health"); + gSkillData.houndeyeDmgBlast = GetSkillCvar( "sk_houndeye_dmg_blast"); + + // ISlave + gSkillData.slaveHealth = GetSkillCvar( "sk_islave_health"); + gSkillData.slaveDmgClaw = GetSkillCvar( "sk_islave_dmg_claw"); + gSkillData.slaveDmgClawrake = GetSkillCvar( "sk_islave_dmg_clawrake"); + gSkillData.slaveDmgZap = GetSkillCvar( "sk_islave_dmg_zap"); + + // Icthyosaur + gSkillData.ichthyosaurHealth = GetSkillCvar( "sk_ichthyosaur_health"); + gSkillData.ichthyosaurDmgShake = GetSkillCvar( "sk_ichthyosaur_shake"); + + // Leech + gSkillData.leechHealth = GetSkillCvar( "sk_leech_health"); + + gSkillData.leechDmgBite = GetSkillCvar( "sk_leech_dmg_bite"); + + // Controller + gSkillData.controllerHealth = GetSkillCvar( "sk_controller_health"); + gSkillData.controllerDmgZap = GetSkillCvar( "sk_controller_dmgzap"); + gSkillData.controllerSpeedBall = GetSkillCvar( "sk_controller_speedball"); + gSkillData.controllerDmgBall = GetSkillCvar( "sk_controller_dmgball"); + + // Nihilanth + gSkillData.nihilanthHealth = GetSkillCvar( "sk_nihilanth_health"); + gSkillData.nihilanthZap = GetSkillCvar( "sk_nihilanth_zap"); + + // Scientist + gSkillData.scientistHealth = GetSkillCvar( "sk_scientist_health"); + + // Snark + gSkillData.snarkHealth = GetSkillCvar( "sk_snark_health"); + gSkillData.snarkDmgBite = GetSkillCvar( "sk_snark_dmg_bite"); + gSkillData.snarkDmgPop = GetSkillCvar( "sk_snark_dmg_pop"); + + // Zombie + gSkillData.zombieHealth = GetSkillCvar( "sk_zombie_health"); + gSkillData.zombieDmgOneSlash = GetSkillCvar( "sk_zombie_dmg_one_slash"); + gSkillData.zombieDmgBothSlash = GetSkillCvar( "sk_zombie_dmg_both_slash"); + + //Turret + gSkillData.turretHealth = GetSkillCvar( "sk_turret_health"); + + // MiniTurret + gSkillData.miniturretHealth = GetSkillCvar( "sk_miniturret_health"); + + // Sentry Turret + gSkillData.sentryHealth = GetSkillCvar( "sk_sentry_health"); + +// PLAYER WEAPONS + + // Crowbar whack + gSkillData.plrDmgCrowbar = GetSkillCvar( "sk_plr_crowbar"); + + // Glock Round + gSkillData.plrDmg9MM = GetSkillCvar( "sk_plr_9mm_bullet"); + + // 357 Round + gSkillData.plrDmg357 = GetSkillCvar( "sk_plr_357_bullet"); + + // MP5 Round + gSkillData.plrDmgMP5 = GetSkillCvar( "sk_plr_9mmAR_bullet"); + + // M203 grenade + gSkillData.plrDmgM203Grenade = GetSkillCvar( "sk_plr_9mmAR_grenade"); + + // Shotgun buckshot + gSkillData.plrDmgBuckshot = GetSkillCvar( "sk_plr_buckshot"); + + // Crossbow + gSkillData.plrDmgCrossbowClient = GetSkillCvar( "sk_plr_xbow_bolt_client"); + gSkillData.plrDmgCrossbowMonster = GetSkillCvar( "sk_plr_xbow_bolt_monster"); + + // RPG + gSkillData.plrDmgRPG = GetSkillCvar( "sk_plr_rpg"); + + // Gauss gun + gSkillData.plrDmgGauss = GetSkillCvar( "sk_plr_gauss"); + + // Egon Gun + gSkillData.plrDmgEgonNarrow = GetSkillCvar( "sk_plr_egon_narrow"); + gSkillData.plrDmgEgonWide = GetSkillCvar( "sk_plr_egon_wide"); + + // Hand Grendade + gSkillData.plrDmgHandGrenade = GetSkillCvar( "sk_plr_hand_grenade"); + + // Satchel Charge + gSkillData.plrDmgSatchel = GetSkillCvar( "sk_plr_satchel"); + + // Tripmine + gSkillData.plrDmgTripmine = GetSkillCvar( "sk_plr_tripmine"); + + // MONSTER WEAPONS + gSkillData.monDmg12MM = GetSkillCvar( "sk_12mm_bullet"); + gSkillData.monDmgMP5 = GetSkillCvar ("sk_9mmAR_bullet" ); + gSkillData.monDmg9MM = GetSkillCvar( "sk_9mm_bullet"); + + // MONSTER HORNET + gSkillData.monDmgHornet = GetSkillCvar( "sk_hornet_dmg"); + + // PLAYER HORNET +// Up to this point, player hornet damage and monster hornet damage were both using +// monDmgHornet to determine how much damage to do. In tuning the hivehand, we now need +// to separate player damage and monster hivehand damage. Since it's so late in the project, we've +// added plrDmgHornet to the SKILLDATA struct, but not to the engine CVar list, so it's inaccesible +// via SKILLS.CFG. Any player hivehand tuning must take place in the code. (sjb) + gSkillData.plrDmgHornet = 7; + + + // HEALTH/CHARGE + gSkillData.suitchargerCapacity = GetSkillCvar( "sk_suitcharger" ); + gSkillData.batteryCapacity = GetSkillCvar( "sk_battery" ); + gSkillData.healthchargerCapacity = GetSkillCvar ( "sk_healthcharger" ); + gSkillData.healthkitCapacity = GetSkillCvar ( "sk_healthkit" ); + gSkillData.scientistHeal = GetSkillCvar ( "sk_scientist_heal" ); + + // monster damage adj + gSkillData.monHead = GetSkillCvar( "sk_monster_head" ); + gSkillData.monChest = GetSkillCvar( "sk_monster_chest" ); + gSkillData.monStomach = GetSkillCvar( "sk_monster_stomach" ); + gSkillData.monLeg = GetSkillCvar( "sk_monster_leg" ); + gSkillData.monArm = GetSkillCvar( "sk_monster_arm" ); + + // player damage adj + gSkillData.plrHead = GetSkillCvar( "sk_player_head" ); + gSkillData.plrChest = GetSkillCvar( "sk_player_chest" ); + gSkillData.plrStomach = GetSkillCvar( "sk_player_stomach" ); + gSkillData.plrLeg = GetSkillCvar( "sk_player_leg" ); + gSkillData.plrArm = GetSkillCvar( "sk_player_arm" ); +} + +//========================================================= +// instantiate the proper game rules object +//========================================================= + +CGameRules *InstallGameRules( void ) +{ + SERVER_COMMAND( "exec game.cfg\n" ); + SERVER_EXECUTE( ); + + if ( !gpGlobals->deathmatch ) + { + // generic half-life + g_teamplay = 0; + return new CHalfLifeRules; + } + else + { + if ( teamplay.value > 0 ) + { + // teamplay + + g_teamplay = 1; + return new CHalfLifeTeamplay; + } + if ((int)gpGlobals->deathmatch == 1) + { + // vanilla deathmatch + g_teamplay = 0; + return new CHalfLifeMultiplay; + } + else + { + // vanilla deathmatch?? + g_teamplay = 0; + return new CHalfLifeMultiplay; + } + } +} + + + diff --git a/dlls/gamerules.h b/dlls/gamerules.h new file mode 100644 index 0000000..37a0e5f --- /dev/null +++ b/dlls/gamerules.h @@ -0,0 +1,360 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// GameRules +//========================================================= + +//#include "weapons.h" +//#include "items.h" +class CBasePlayerItem; +class CBasePlayer; +class CItem; +class CBasePlayerAmmo; + +// weapon respawning return codes +enum +{ + GR_NONE = 0, + + GR_WEAPON_RESPAWN_YES, + GR_WEAPON_RESPAWN_NO, + + GR_AMMO_RESPAWN_YES, + GR_AMMO_RESPAWN_NO, + + GR_ITEM_RESPAWN_YES, + GR_ITEM_RESPAWN_NO, + + GR_PLR_DROP_GUN_ALL, + GR_PLR_DROP_GUN_ACTIVE, + GR_PLR_DROP_GUN_NO, + + GR_PLR_DROP_AMMO_ALL, + GR_PLR_DROP_AMMO_ACTIVE, + GR_PLR_DROP_AMMO_NO, +}; + +// Player relationship return codes +enum +{ + GR_NOTTEAMMATE = 0, + GR_TEAMMATE, + GR_ENEMY, + GR_ALLY, + GR_NEUTRAL, +}; + +class CGameRules +{ +public: + virtual void RefreshSkillData( void );// fill skill data struct with proper values + virtual void Think( void ) = 0;// GR_Think - runs every server frame, should handle any timer tasks, periodic events, etc. + virtual BOOL IsAllowedToSpawn( CBaseEntity *pEntity ) = 0; // Can this item spawn (eg monsters don't spawn in deathmatch). + + virtual BOOL FAllowFlashlight( void ) = 0;// Are players allowed to switch on their flashlight? + virtual BOOL FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) = 0;// should the player switch to this weapon? + virtual BOOL GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) = 0;// I can't use this weapon anymore, get me the next best one. + +// Functions to verify the single/multiplayer status of a game + virtual BOOL IsMultiplayer( void ) = 0;// is this a multiplayer game? (either coop or deathmatch) + virtual BOOL IsDeathmatch( void ) = 0;//is this a deathmatch game? + virtual BOOL IsTeamplay( void ) { return FALSE; };// is this deathmatch game being played with team rules? + virtual BOOL IsCoOp( void ) = 0;// is this a coop game? + virtual const char *GetGameDescription( void ) { return "Half-Life"; } // this is the game name that gets seen in the server browser + +// Client connection/disconnection + virtual BOOL ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) = 0;// a client just connected to the server (player hasn't spawned yet) + virtual void InitHUD( CBasePlayer *pl ) = 0; // the client dll is ready for updating + virtual void ClientDisconnected( edict_t *pClient ) = 0;// a client just disconnected from the server + virtual void UpdateGameMode( CBasePlayer *pPlayer ) {} // the client needs to be informed of the current game mode + +// Client damage rules + virtual float FlPlayerFallDamage( CBasePlayer *pPlayer ) = 0;// this client just hit the ground after a fall. How much damage? + virtual BOOL FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ) {return TRUE;};// can this player take damage from this attacker? + virtual BOOL ShouldAutoAim( CBasePlayer *pPlayer, edict_t *target ) { return TRUE; } + +// Client spawn/respawn control + virtual void PlayerSpawn( CBasePlayer *pPlayer ) = 0;// called by CBasePlayer::Spawn just before releasing player into the game + virtual void PlayerThink( CBasePlayer *pPlayer ) = 0; // called by CBasePlayer::PreThink every frame, before physics are run and after keys are accepted + virtual BOOL FPlayerCanRespawn( CBasePlayer *pPlayer ) = 0;// is this player allowed to respawn now? + virtual float FlPlayerSpawnTime( CBasePlayer *pPlayer ) = 0;// When in the future will this player be able to spawn? + virtual edict_t *GetPlayerSpawnSpot( CBasePlayer *pPlayer );// Place this player on their spawnspot and face them the proper direction. + + virtual BOOL AllowAutoTargetCrosshair( void ) { return TRUE; }; + virtual BOOL ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) { return FALSE; }; // handles the user commands; returns TRUE if command handled properly + virtual void ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ) {} // the player has changed userinfo; can change it now + +// Client kills/scoring + virtual int IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) = 0;// how many points do I award whoever kills this player? + virtual void PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) = 0;// Called each time a player dies + virtual void DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor )= 0;// Call this from within a GameRules class to report an obituary. +// Weapon retrieval + virtual BOOL CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon );// The player is touching an CBasePlayerItem, do I give it to him? + virtual void PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) = 0;// Called each time a player picks up a weapon from the ground + +// Weapon spawn/respawn control + virtual int WeaponShouldRespawn( CBasePlayerItem *pWeapon ) = 0;// should this weapon respawn? + virtual float FlWeaponRespawnTime( CBasePlayerItem *pWeapon ) = 0;// when may this weapon respawn? + virtual float FlWeaponTryRespawn( CBasePlayerItem *pWeapon ) = 0; // can i respawn now, and if not, when should i try again? + virtual Vector VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ) = 0;// where in the world should this weapon respawn? + +// Item retrieval + virtual BOOL CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ) = 0;// is this player allowed to take this item? + virtual void PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ) = 0;// call each time a player picks up an item (battery, healthkit, longjump) + +// Item spawn/respawn control + virtual int ItemShouldRespawn( CItem *pItem ) = 0;// Should this item respawn? + virtual float FlItemRespawnTime( CItem *pItem ) = 0;// when may this item respawn? + virtual Vector VecItemRespawnSpot( CItem *pItem ) = 0;// where in the world should this item respawn? + +// Ammo retrieval + virtual BOOL CanHaveAmmo( CBasePlayer *pPlayer, const char *pszAmmoName, int iMaxCarry );// can this player take more of this ammo? + virtual void PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ) = 0;// called each time a player picks up some ammo in the world + +// Ammo spawn/respawn control + virtual int AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ) = 0;// should this ammo item respawn? + virtual float FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ) = 0;// when should this ammo item respawn? + virtual Vector VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ) = 0;// where in the world should this ammo item respawn? + // by default, everything spawns + +// Healthcharger respawn control + virtual float FlHealthChargerRechargeTime( void ) = 0;// how long until a depleted HealthCharger recharges itself? + virtual float FlHEVChargerRechargeTime( void ) { return 0; }// how long until a depleted HealthCharger recharges itself? + +// What happens to a dead player's weapons + virtual int DeadPlayerWeapons( CBasePlayer *pPlayer ) = 0;// what do I do with a player's weapons when he's killed? + +// What happens to a dead player's ammo + virtual int DeadPlayerAmmo( CBasePlayer *pPlayer ) = 0;// Do I drop ammo when the player dies? How much? + +// Teamplay stuff + virtual const char *GetTeamID( CBaseEntity *pEntity ) = 0;// what team is this entity on? + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) = 0;// What is the player's relationship with this entity? + virtual int GetTeamIndex( const char *pTeamName ) { return -1; } + virtual const char *GetIndexedTeamName( int teamIndex ) { return ""; } + virtual BOOL IsValidTeam( const char *pTeamName ) { return TRUE; } + virtual void ChangePlayerTeam( CBasePlayer *pPlayer, const char *pTeamName, BOOL bKill, BOOL bGib ) {} + virtual const char *SetDefaultPlayerTeam( CBasePlayer *pPlayer ) { return ""; } + +// Sounds + virtual BOOL PlayTextureSounds( void ) { return TRUE; } + virtual BOOL PlayFootstepSounds( CBasePlayer *pl, float fvol ) { return TRUE; } + +// Monsters + virtual BOOL FAllowMonsters( void ) = 0;//are monsters allowed + + // Immediately end a multiplayer game + virtual void EndMultiplayerGame( void ) {} +}; + +extern CGameRules *InstallGameRules( void ); + + +//========================================================= +// CHalfLifeRules - rules for the single player Half-Life +// game. +//========================================================= +class CHalfLifeRules : public CGameRules +{ +public: + CHalfLifeRules ( void ); + +// GR_Think + virtual void Think( void ); + virtual BOOL IsAllowedToSpawn( CBaseEntity *pEntity ); + virtual BOOL FAllowFlashlight( void ) { return TRUE; }; + + virtual BOOL FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + virtual BOOL GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ); + +// Functions to verify the single/multiplayer status of a game + virtual BOOL IsMultiplayer( void ); + virtual BOOL IsDeathmatch( void ); + virtual BOOL IsCoOp( void ); + +// Client connection/disconnection + virtual BOOL ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); + virtual void InitHUD( CBasePlayer *pl ); // the client dll is ready for updating + virtual void ClientDisconnected( edict_t *pClient ); + +// Client damage rules + virtual float FlPlayerFallDamage( CBasePlayer *pPlayer ); + +// Client spawn/respawn control + virtual void PlayerSpawn( CBasePlayer *pPlayer ); + virtual void PlayerThink( CBasePlayer *pPlayer ); + virtual BOOL FPlayerCanRespawn( CBasePlayer *pPlayer ); + virtual float FlPlayerSpawnTime( CBasePlayer *pPlayer ); + + virtual BOOL AllowAutoTargetCrosshair( void ); + +// Client kills/scoring + virtual int IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ); + virtual void PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); + virtual void DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); + +// Weapon retrieval + virtual void PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + +// Weapon spawn/respawn control + virtual int WeaponShouldRespawn( CBasePlayerItem *pWeapon ); + virtual float FlWeaponRespawnTime( CBasePlayerItem *pWeapon ); + virtual float FlWeaponTryRespawn( CBasePlayerItem *pWeapon ); + virtual Vector VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ); + +// Item retrieval + virtual BOOL CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ); + virtual void PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ); + +// Item spawn/respawn control + virtual int ItemShouldRespawn( CItem *pItem ); + virtual float FlItemRespawnTime( CItem *pItem ); + virtual Vector VecItemRespawnSpot( CItem *pItem ); + +// Ammo retrieval + virtual void PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ); + +// Ammo spawn/respawn control + virtual int AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ); + virtual float FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ); + virtual Vector VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ); + +// Healthcharger respawn control + virtual float FlHealthChargerRechargeTime( void ); + +// What happens to a dead player's weapons + virtual int DeadPlayerWeapons( CBasePlayer *pPlayer ); + +// What happens to a dead player's ammo + virtual int DeadPlayerAmmo( CBasePlayer *pPlayer ); + +// Monsters + virtual BOOL FAllowMonsters( void ); + +// Teamplay stuff + virtual const char *GetTeamID( CBaseEntity *pEntity ) {return "";}; + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); +}; + +//========================================================= +// CHalfLifeMultiplay - rules for the basic half life multiplayer +// competition +//========================================================= +class CHalfLifeMultiplay : public CGameRules +{ +public: + CHalfLifeMultiplay(); + +// GR_Think + virtual void Think( void ); + virtual void RefreshSkillData( void ); + virtual BOOL IsAllowedToSpawn( CBaseEntity *pEntity ); + virtual BOOL FAllowFlashlight( void ); + + virtual BOOL FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + virtual BOOL GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ); + +// Functions to verify the single/multiplayer status of a game + virtual BOOL IsMultiplayer( void ); + virtual BOOL IsDeathmatch( void ); + virtual BOOL IsCoOp( void ); + +// Client connection/disconnection + // If ClientConnected returns FALSE, the connection is rejected and the user is provided the reason specified in + // svRejectReason + // Only the client's name and remote address are provided to the dll for verification. + virtual BOOL ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); + virtual void InitHUD( CBasePlayer *pl ); // the client dll is ready for updating + virtual void ClientDisconnected( edict_t *pClient ); + virtual void UpdateGameMode( CBasePlayer *pPlayer ); // the client needs to be informed of the current game mode + +// Client damage rules + virtual float FlPlayerFallDamage( CBasePlayer *pPlayer ); + virtual BOOL FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ); + +// Client spawn/respawn control + virtual void PlayerSpawn( CBasePlayer *pPlayer ); + virtual void PlayerThink( CBasePlayer *pPlayer ); + virtual BOOL FPlayerCanRespawn( CBasePlayer *pPlayer ); + virtual float FlPlayerSpawnTime( CBasePlayer *pPlayer ); + virtual edict_t *GetPlayerSpawnSpot( CBasePlayer *pPlayer ); + + virtual BOOL AllowAutoTargetCrosshair( void ); + virtual BOOL ClientCommand( CBasePlayer *pPlayer, const char *pcmd ); + +// Client kills/scoring + virtual int IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ); + virtual void PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); + virtual void DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); + +// Weapon retrieval + virtual void PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + virtual BOOL CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon );// The player is touching an CBasePlayerItem, do I give it to him? + +// Weapon spawn/respawn control + virtual int WeaponShouldRespawn( CBasePlayerItem *pWeapon ); + virtual float FlWeaponRespawnTime( CBasePlayerItem *pWeapon ); + virtual float FlWeaponTryRespawn( CBasePlayerItem *pWeapon ); + virtual Vector VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ); + +// Item retrieval + virtual BOOL CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ); + virtual void PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ); + +// Item spawn/respawn control + virtual int ItemShouldRespawn( CItem *pItem ); + virtual float FlItemRespawnTime( CItem *pItem ); + virtual Vector VecItemRespawnSpot( CItem *pItem ); + +// Ammo retrieval + virtual void PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ); + +// Ammo spawn/respawn control + virtual int AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ); + virtual float FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ); + virtual Vector VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ); + +// Healthcharger respawn control + virtual float FlHealthChargerRechargeTime( void ); + virtual float FlHEVChargerRechargeTime( void ); + +// What happens to a dead player's weapons + virtual int DeadPlayerWeapons( CBasePlayer *pPlayer ); + +// What happens to a dead player's ammo + virtual int DeadPlayerAmmo( CBasePlayer *pPlayer ); + +// Teamplay stuff + virtual const char *GetTeamID( CBaseEntity *pEntity ) {return "";} + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); + + virtual BOOL PlayTextureSounds( void ) { return FALSE; } + virtual BOOL PlayFootstepSounds( CBasePlayer *pl, float fvol ); + +// Monsters + virtual BOOL FAllowMonsters( void ); + + // Immediately end a multiplayer game + virtual void EndMultiplayerGame( void ) { GoToIntermission(); } + +protected: + virtual void ChangeLevel( void ); + virtual void GoToIntermission( void ); + float m_flIntermissionEndTime; + BOOL m_iEndIntermissionButtonHit; + void SendMOTDToClient( edict_t *client ); +}; + +extern DLL_GLOBAL CGameRules* g_pGameRules; diff --git a/dlls/gargantua.cpp b/dlls/gargantua.cpp new file mode 100644 index 0000000..e8103e2 --- /dev/null +++ b/dlls/gargantua.cpp @@ -0,0 +1,1368 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef OEM_BUILD + +//========================================================= +// Gargantua +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "nodes.h" +#include "monsters.h" +#include "schedule.h" +#include "customentity.h" +#include "weapons.h" +#include "effects.h" +#include "soundent.h" +#include "decals.h" +#include "explode.h" +#include "func_break.h" + +//========================================================= +// Gargantua Monster +//========================================================= +const float GARG_ATTACKDIST = 80.0; + +// Garg animation events +#define GARG_AE_SLASH_LEFT 1 +//#define GARG_AE_BEAM_ATTACK_RIGHT 2 // No longer used +#define GARG_AE_LEFT_FOOT 3 +#define GARG_AE_RIGHT_FOOT 4 +#define GARG_AE_STOMP 5 +#define GARG_AE_BREATHE 6 + + +// Gargantua is immune to any damage but this +#define GARG_DAMAGE (DMG_ENERGYBEAM|DMG_CRUSH|DMG_MORTAR|DMG_BLAST) +#define GARG_EYE_SPRITE_NAME "sprites/gargeye1.spr" +#define GARG_BEAM_SPRITE_NAME "sprites/xbeam3.spr" +#define GARG_BEAM_SPRITE2 "sprites/xbeam3.spr" +#define GARG_STOMP_SPRITE_NAME "sprites/gargeye1.spr" +#define GARG_STOMP_BUZZ_SOUND "weapons/mine_charge.wav" +#define GARG_FLAME_LENGTH 330 +#define GARG_GIB_MODEL "models/metalplategibs.mdl" + +#define ATTN_GARG (ATTN_NORM) + +#define STOMP_SPRITE_COUNT 10 + +int gStompSprite = 0, gGargGibModel = 0; +void SpawnExplosion( Vector center, float randomRange, float time, int magnitude ); + +class CSmoker; + +// Spiral Effect +class CSpiral : public CBaseEntity +{ +public: + void Spawn( void ); + void Think( void ); + int ObjectCaps( void ) { return FCAP_DONT_SAVE; } + static CSpiral *Create( const Vector &origin, float height, float radius, float duration ); +}; +LINK_ENTITY_TO_CLASS( streak_spiral, CSpiral ); + + +class CStomp : public CBaseEntity +{ +public: + void Spawn( void ); + void Think( void ); + static CStomp *StompCreate( const Vector &origin, const Vector &end, float speed ); + +private: +// UNDONE: re-use this sprite list instead of creating new ones all the time +// CSprite *m_pSprites[ STOMP_SPRITE_COUNT ]; +}; + +LINK_ENTITY_TO_CLASS( garg_stomp, CStomp ); +CStomp *CStomp::StompCreate( const Vector &origin, const Vector &end, float speed ) +{ + CStomp *pStomp = GetClassPtr( (CStomp *)NULL ); + + pStomp->pev->origin = origin; + Vector dir = (end - origin); + pStomp->pev->scale = dir.Length(); + pStomp->pev->movedir = dir.Normalize(); + pStomp->pev->speed = speed; + pStomp->Spawn(); + + return pStomp; +} + +void CStomp::Spawn( void ) +{ + pev->nextthink = gpGlobals->time; + pev->classname = MAKE_STRING("garg_stomp"); + pev->dmgtime = gpGlobals->time; + + pev->framerate = 30; + pev->model = MAKE_STRING(GARG_STOMP_SPRITE_NAME); + pev->rendermode = kRenderTransTexture; + pev->renderamt = 0; + EMIT_SOUND_DYN( edict(), CHAN_BODY, GARG_STOMP_BUZZ_SOUND, 1, ATTN_NORM, 0, PITCH_NORM * 0.55); +} + + +#define STOMP_INTERVAL 0.025 + +void CStomp::Think( void ) +{ + TraceResult tr; + + pev->nextthink = gpGlobals->time + 0.1; + + // Do damage for this frame + Vector vecStart = pev->origin; + vecStart.z += 30; + Vector vecEnd = vecStart + (pev->movedir * pev->speed * gpGlobals->frametime); + + UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr ); + + if ( tr.pHit && tr.pHit != pev->owner ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + entvars_t *pevOwner = pev; + if ( pev->owner ) + pevOwner = VARS(pev->owner); + + if ( pEntity ) + pEntity->TakeDamage( pev, pevOwner, gSkillData.gargantuaDmgStomp, DMG_SONIC ); + } + + // Accelerate the effect + pev->speed = pev->speed + (gpGlobals->frametime) * pev->framerate; + pev->framerate = pev->framerate + (gpGlobals->frametime) * 1500; + + // Move and spawn trails + while ( gpGlobals->time - pev->dmgtime > STOMP_INTERVAL ) + { + pev->origin = pev->origin + pev->movedir * pev->speed * STOMP_INTERVAL; + for ( int i = 0; i < 2; i++ ) + { + CSprite *pSprite = CSprite::SpriteCreate( GARG_STOMP_SPRITE_NAME, pev->origin, TRUE ); + if ( pSprite ) + { + UTIL_TraceLine( pev->origin, pev->origin - Vector(0,0,500), ignore_monsters, edict(), &tr ); + pSprite->pev->origin = tr.vecEndPos; + pSprite->pev->velocity = Vector(RANDOM_FLOAT(-200,200),RANDOM_FLOAT(-200,200),175); + // pSprite->AnimateAndDie( RANDOM_FLOAT( 8.0, 12.0 ) ); + pSprite->pev->nextthink = gpGlobals->time + 0.3; + pSprite->SetThink( &CSprite::SUB_Remove ); + pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxFadeFast ); + } + } + pev->dmgtime += STOMP_INTERVAL; + // Scale has the "life" of this effect + pev->scale -= STOMP_INTERVAL * pev->speed; + if ( pev->scale <= 0 ) + { + // Life has run out + UTIL_Remove(this); + STOP_SOUND( edict(), CHAN_BODY, GARG_STOMP_BUZZ_SOUND ); + } + + } +} + + +void StreakSplash( const Vector &origin, const Vector &direction, int color, int count, int speed, int velocityRange ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, origin ); + WRITE_BYTE( TE_STREAK_SPLASH ); + WRITE_COORD( origin.x ); // origin + WRITE_COORD( origin.y ); + WRITE_COORD( origin.z ); + WRITE_COORD( direction.x ); // direction + WRITE_COORD( direction.y ); + WRITE_COORD( direction.z ); + WRITE_BYTE( color ); // Streak color 6 + WRITE_SHORT( count ); // count + WRITE_SHORT( speed ); + WRITE_SHORT( velocityRange ); // Random velocity modifier + MESSAGE_END(); +} + + +class CGargantua : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + + BOOL CheckMeleeAttack1( float flDot, float flDist ); // Swipe + BOOL CheckMeleeAttack2( float flDot, float flDist ); // Flames + BOOL CheckRangeAttack1( float flDot, float flDist ); // Stomp attack + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector( -80, -80, 0 ); + pev->absmax = pev->origin + Vector( 80, 80, 214 ); + } + + Schedule_t *GetScheduleOfType( int Type ); + void StartTask( Task_t *pTask ); + void RunTask( Task_t *pTask ); + + void PrescheduleThink( void ); + + void Killed( entvars_t *pevAttacker, int iGib ); + void DeathEffect( void ); + + void EyeOff( void ); + void EyeOn( int level ); + void EyeUpdate( void ); + void Leap( void ); + void StompAttack( void ); + void FlameCreate( void ); + void FlameUpdate( void ); + void FlameControls( float angleX, float angleY ); + void FlameDestroy( void ); + inline BOOL FlameIsOn( void ) { return m_pFlame[0] != NULL; } + + void FlameDamage( Vector vecStart, Vector vecEnd, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + CUSTOM_SCHEDULES; + +private: + static const char *pAttackHitSounds[]; + static const char *pBeamAttackSounds[]; + static const char *pAttackMissSounds[]; + static const char *pRicSounds[]; + static const char *pFootSounds[]; + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + static const char *pPainSounds[]; + static const char *pAttackSounds[]; + static const char *pStompSounds[]; + static const char *pBreatheSounds[]; + + CBaseEntity* GargantuaCheckTraceHullAttack(float flDist, int iDamage, int iDmgType); + + CSprite *m_pEyeGlow; // Glow around the eyes + CBeam *m_pFlame[4]; // Flame beams + + int m_eyeBrightness; // Brightness target + float m_seeTime; // Time to attack (when I see the enemy, I set this) + float m_flameTime; // Time of next flame attack + float m_painSoundTime; // Time of next pain sound + float m_streakTime; // streak timer (don't send too many) + float m_flameX; // Flame thrower aim + float m_flameY; +}; + +LINK_ENTITY_TO_CLASS( monster_gargantua, CGargantua ); + +TYPEDESCRIPTION CGargantua::m_SaveData[] = +{ + DEFINE_FIELD( CGargantua, m_pEyeGlow, FIELD_CLASSPTR ), + DEFINE_FIELD( CGargantua, m_eyeBrightness, FIELD_INTEGER ), + DEFINE_FIELD( CGargantua, m_seeTime, FIELD_TIME ), + DEFINE_FIELD( CGargantua, m_flameTime, FIELD_TIME ), + DEFINE_FIELD( CGargantua, m_streakTime, FIELD_TIME ), + DEFINE_FIELD( CGargantua, m_painSoundTime, FIELD_TIME ), + DEFINE_ARRAY( CGargantua, m_pFlame, FIELD_CLASSPTR, 4 ), + DEFINE_FIELD( CGargantua, m_flameX, FIELD_FLOAT ), + DEFINE_FIELD( CGargantua, m_flameY, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CGargantua, CBaseMonster ); + +const char *CGargantua::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CGargantua::pBeamAttackSounds[] = +{ + "garg/gar_flameoff1.wav", + "garg/gar_flameon1.wav", + "garg/gar_flamerun1.wav", +}; + + +const char *CGargantua::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CGargantua::pRicSounds[] = +{ +#if 0 + "weapons/ric1.wav", + "weapons/ric2.wav", + "weapons/ric3.wav", + "weapons/ric4.wav", + "weapons/ric5.wav", +#else + "debris/metal4.wav", + "debris/metal6.wav", + "weapons/ric4.wav", + "weapons/ric5.wav", +#endif +}; + +const char *CGargantua::pFootSounds[] = +{ + "garg/gar_step1.wav", + "garg/gar_step2.wav", +}; + + +const char *CGargantua::pIdleSounds[] = +{ + "garg/gar_idle1.wav", + "garg/gar_idle2.wav", + "garg/gar_idle3.wav", + "garg/gar_idle4.wav", + "garg/gar_idle5.wav", +}; + + +const char *CGargantua::pAttackSounds[] = +{ + "garg/gar_attack1.wav", + "garg/gar_attack2.wav", + "garg/gar_attack3.wav", +}; + +const char *CGargantua::pAlertSounds[] = +{ + "garg/gar_alert1.wav", + "garg/gar_alert2.wav", + "garg/gar_alert3.wav", +}; + +const char *CGargantua::pPainSounds[] = +{ + "garg/gar_pain1.wav", + "garg/gar_pain2.wav", + "garg/gar_pain3.wav", +}; + +const char *CGargantua::pStompSounds[] = +{ + "garg/gar_stomp1.wav", +}; + +const char *CGargantua::pBreatheSounds[] = +{ + "garg/gar_breathe1.wav", + "garg/gar_breathe2.wav", + "garg/gar_breathe3.wav", +}; +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +#if 0 +enum +{ + SCHED_ = LAST_COMMON_SCHEDULE + 1, +}; +#endif + +enum +{ + TASK_SOUND_ATTACK = LAST_COMMON_TASK + 1, + TASK_FLAME_SWEEP, +}; + +Task_t tlGargFlame[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SOUND_ATTACK, (float)0 }, + // { TASK_PLAY_SEQUENCE, (float)ACT_SIGNAL1 }, + { TASK_SET_ACTIVITY, (float)ACT_MELEE_ATTACK2 }, + { TASK_FLAME_SWEEP, (float)4.5 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slGargFlame[] = +{ + { + tlGargFlame, + ARRAYSIZE ( tlGargFlame ), + 0, + 0, + "GargFlame" + }, +}; + + +// primary melee attack +Task_t tlGargSwipe[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MELEE_ATTACK1, (float)0 }, +}; + +Schedule_t slGargSwipe[] = +{ + { + tlGargSwipe, + ARRAYSIZE ( tlGargSwipe ), + bits_COND_CAN_MELEE_ATTACK2, + 0, + "GargSwipe" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CGargantua ) +{ + slGargFlame, + slGargSwipe, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CGargantua, CBaseMonster ); + + +void CGargantua::EyeOn( int level ) +{ + m_eyeBrightness = level; +} + + +void CGargantua::EyeOff( void ) +{ + m_eyeBrightness = 0; +} + + +void CGargantua::EyeUpdate( void ) +{ + if ( m_pEyeGlow ) + { + m_pEyeGlow->pev->renderamt = UTIL_Approach( m_eyeBrightness, m_pEyeGlow->pev->renderamt, 26 ); + if ( m_pEyeGlow->pev->renderamt == 0 ) + m_pEyeGlow->pev->effects |= EF_NODRAW; + else + m_pEyeGlow->pev->effects &= ~EF_NODRAW; + UTIL_SetOrigin( m_pEyeGlow->pev, pev->origin ); + } +} + + +void CGargantua::StompAttack( void ) +{ + TraceResult trace; + + UTIL_MakeVectors( pev->angles ); + Vector vecStart = pev->origin + Vector(0,0,60) + 35 * gpGlobals->v_forward; + Vector vecAim = ShootAtEnemy( vecStart ); + Vector vecEnd = (vecAim * 1024) + vecStart; + + UTIL_TraceLine( vecStart, vecEnd, ignore_monsters, edict(), &trace ); + CStomp::StompCreate( vecStart, trace.vecEndPos, 0 ); + UTIL_ScreenShake( pev->origin, 12.0, 100.0, 2.0, 1000 ); + EMIT_SOUND_DYN ( edict(), CHAN_WEAPON, pStompSounds[ RANDOM_LONG(0,ARRAYSIZE(pStompSounds)-1) ], 1.0, ATTN_GARG, 0, PITCH_NORM + RANDOM_LONG(-10,10) ); + + UTIL_TraceLine( pev->origin, pev->origin - Vector(0,0,20), ignore_monsters, edict(), &trace ); + if ( trace.flFraction < 1.0 ) + UTIL_DecalTrace( &trace, DECAL_GARGSTOMP1 ); +} + + +void CGargantua :: FlameCreate( void ) +{ + int i; + Vector posGun, angleGun; + TraceResult trace; + + UTIL_MakeVectors( pev->angles ); + + for ( i = 0; i < 4; i++ ) + { + if ( i < 2 ) + m_pFlame[i] = CBeam::BeamCreate( GARG_BEAM_SPRITE_NAME, 240 ); + else + m_pFlame[i] = CBeam::BeamCreate( GARG_BEAM_SPRITE2, 140 ); + if ( m_pFlame[i] ) + { + int attach = i%2; + // attachment is 0 based in GetAttachment + GetAttachment( attach+1, posGun, angleGun ); + + Vector vecEnd = (gpGlobals->v_forward * GARG_FLAME_LENGTH) + posGun; + UTIL_TraceLine( posGun, vecEnd, dont_ignore_monsters, edict(), &trace ); + + m_pFlame[i]->PointEntInit( trace.vecEndPos, entindex() ); + if ( i < 2 ) + m_pFlame[i]->SetColor( 255, 130, 90 ); + else + m_pFlame[i]->SetColor( 0, 120, 255 ); + m_pFlame[i]->SetBrightness( 190 ); + m_pFlame[i]->SetFlags( BEAM_FSHADEIN ); + m_pFlame[i]->SetScrollRate( 20 ); + // attachment is 1 based in SetEndAttachment + m_pFlame[i]->SetEndAttachment( attach + 2 ); + CSoundEnt::InsertSound( bits_SOUND_COMBAT, posGun, 384, 0.3 ); + } + } + EMIT_SOUND_DYN ( edict(), CHAN_BODY, pBeamAttackSounds[ 1 ], 1.0, ATTN_NORM, 0, PITCH_NORM ); + EMIT_SOUND_DYN ( edict(), CHAN_WEAPON, pBeamAttackSounds[ 2 ], 1.0, ATTN_NORM, 0, PITCH_NORM ); +} + + +void CGargantua :: FlameControls( float angleX, float angleY ) +{ + if ( angleY < -180 ) + angleY += 360; + else if ( angleY > 180 ) + angleY -= 360; + + if ( angleY < -45 ) + angleY = -45; + else if ( angleY > 45 ) + angleY = 45; + + m_flameX = UTIL_ApproachAngle( angleX, m_flameX, 4 ); + m_flameY = UTIL_ApproachAngle( angleY, m_flameY, 8 ); + SetBoneController( 0, m_flameY ); + SetBoneController( 1, m_flameX ); +} + + +void CGargantua :: FlameUpdate( void ) +{ + int i; + static float offset[2] = { 60, -60 }; + TraceResult trace; + Vector vecStart, angleGun; + BOOL streaks = FALSE; + + for ( i = 0; i < 2; i++ ) + { + if ( m_pFlame[i] ) + { + Vector vecAim = pev->angles; + vecAim.x += m_flameX; + vecAim.y += m_flameY; + + UTIL_MakeVectors( vecAim ); + + GetAttachment( i+1, vecStart, angleGun ); + Vector vecEnd = vecStart + (gpGlobals->v_forward * GARG_FLAME_LENGTH); // - offset[i] * gpGlobals->v_right; + + UTIL_TraceLine( vecStart, vecEnd, dont_ignore_monsters, edict(), &trace ); + + m_pFlame[i]->SetStartPos( trace.vecEndPos ); + m_pFlame[i+2]->SetStartPos( (vecStart * 0.6) + (trace.vecEndPos * 0.4) ); + + if ( trace.flFraction != 1.0 && gpGlobals->time > m_streakTime ) + { + StreakSplash( trace.vecEndPos, trace.vecPlaneNormal, 6, 20, 50, 400 ); + streaks = TRUE; + UTIL_DecalTrace( &trace, DECAL_SMALLSCORCH1 + RANDOM_LONG(0,2) ); + } + // RadiusDamage( trace.vecEndPos, pev, pev, gSkillData.gargantuaDmgFire, CLASS_ALIEN_MONSTER, DMG_BURN ); + FlameDamage( vecStart, trace.vecEndPos, pev, pev, gSkillData.gargantuaDmgFire, CLASS_ALIEN_MONSTER, DMG_BURN ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x1000 * (i + 2) ); // entity, attachment + WRITE_COORD( vecStart.x ); // origin + WRITE_COORD( vecStart.y ); + WRITE_COORD( vecStart.z ); + WRITE_COORD( RANDOM_FLOAT( 32, 48 ) ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 255 ); // G + WRITE_BYTE( 255 ); // B + WRITE_BYTE( 2 ); // life * 10 + WRITE_COORD( 0 ); // decay + MESSAGE_END(); + } + } + if ( streaks ) + m_streakTime = gpGlobals->time; +} + + + +void CGargantua :: FlameDamage( Vector vecStart, Vector vecEnd, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) +{ + CBaseEntity *pEntity = NULL; + TraceResult tr; + float flAdjustedDamage; + Vector vecSpot; + + Vector vecMid = (vecStart + vecEnd) * 0.5; + + float searchRadius = (vecStart - vecMid).Length(); + + Vector vecAim = (vecEnd - vecStart).Normalize( ); + + // iterate on all entities in the vicinity. + while ((pEntity = UTIL_FindEntityInSphere( pEntity, vecMid, searchRadius )) != NULL) + { + if ( pEntity->pev->takedamage != DAMAGE_NO ) + { + // UNDONE: this should check a damage mask, not an ignore + if ( iClassIgnore != CLASS_NONE && pEntity->Classify() == iClassIgnore ) + {// houndeyes don't hurt other houndeyes with their attack + continue; + } + + vecSpot = pEntity->BodyTarget( vecMid ); + + float dist = DotProduct( vecAim, vecSpot - vecMid ); + if (dist > searchRadius) + dist = searchRadius; + else if (dist < -searchRadius) + dist = searchRadius; + + Vector vecSrc = vecMid + dist * vecAim; + + UTIL_TraceLine ( vecSrc, vecSpot, dont_ignore_monsters, ENT(pev), &tr ); + + if ( tr.flFraction == 1.0 || tr.pHit == pEntity->edict() ) + {// the explosion can 'see' this entity, so hurt them! + // decrease damage for an ent that's farther from the flame. + dist = ( vecSrc - tr.vecEndPos ).Length(); + + if (dist > 64) + { + flAdjustedDamage = flDamage - (dist - 64) * 0.4; + if (flAdjustedDamage <= 0) + continue; + } + else + { + flAdjustedDamage = flDamage; + } + + // ALERT( at_console, "hit %s\n", STRING( pEntity->pev->classname ) ); + if (tr.flFraction != 1.0) + { + ClearMultiDamage( ); + pEntity->TraceAttack( pevInflictor, flAdjustedDamage, (tr.vecEndPos - vecSrc).Normalize( ), &tr, bitsDamageType ); + ApplyMultiDamage( pevInflictor, pevAttacker ); + } + else + { + pEntity->TakeDamage ( pevInflictor, pevAttacker, flAdjustedDamage, bitsDamageType ); + } + } + } + } +} + + +void CGargantua :: FlameDestroy( void ) +{ + int i; + + EMIT_SOUND_DYN ( edict(), CHAN_WEAPON, pBeamAttackSounds[ 0 ], 1.0, ATTN_NORM, 0, PITCH_NORM ); + for ( i = 0; i < 4; i++ ) + { + if ( m_pFlame[i] ) + { + UTIL_Remove( m_pFlame[i] ); + m_pFlame[i] = NULL; + } + } +} + + +void CGargantua :: PrescheduleThink( void ) +{ + if ( !HasConditions( bits_COND_SEE_ENEMY ) ) + { + m_seeTime = gpGlobals->time + 5; + EyeOff(); + } + else + EyeOn( 200 ); + + EyeUpdate(); +} + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CGargantua :: Classify ( void ) +{ + return CLASS_ALIEN_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CGargantua :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 60; + break; + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 180; + break; + case ACT_WALK: + case ACT_RUN: + ys = 60; + break; + + default: + ys = 60; + break; + } + + pev->yaw_speed = ys; +} + + +//========================================================= +// Spawn +//========================================================= +void CGargantua :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/garg.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->health = gSkillData.gargantuaHealth; + //pev->view_ofs = Vector ( 0, 0, 96 );// taken from mdl file + m_flFieldOfView = -0.2;// width of forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); + + m_pEyeGlow = CSprite::SpriteCreate( GARG_EYE_SPRITE_NAME, pev->origin, FALSE ); + m_pEyeGlow->SetTransparency( kRenderGlow, 255, 255, 255, 0, kRenderFxNoDissipation ); + m_pEyeGlow->SetAttachment( edict(), 1 ); + EyeOff(); + m_seeTime = gpGlobals->time + 5; + m_flameTime = gpGlobals->time + 2; +} + + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CGargantua :: Precache() +{ + int i; + + PRECACHE_MODEL("models/garg.mdl"); + PRECACHE_MODEL( GARG_EYE_SPRITE_NAME ); + PRECACHE_MODEL( GARG_BEAM_SPRITE_NAME ); + PRECACHE_MODEL( GARG_BEAM_SPRITE2 ); + gStompSprite = PRECACHE_MODEL( GARG_STOMP_SPRITE_NAME ); + gGargGibModel = PRECACHE_MODEL( GARG_GIB_MODEL ); + PRECACHE_SOUND( GARG_STOMP_BUZZ_SOUND ); + + for ( i = 0; i < ARRAYSIZE( pAttackHitSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackHitSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pBeamAttackSounds ); i++ ) + PRECACHE_SOUND((char *)pBeamAttackSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackMissSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackMissSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pRicSounds ); i++ ) + PRECACHE_SOUND((char *)pRicSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pFootSounds ); i++ ) + PRECACHE_SOUND((char *)pFootSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pIdleSounds ); i++ ) + PRECACHE_SOUND((char *)pIdleSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAlertSounds ); i++ ) + PRECACHE_SOUND((char *)pAlertSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pPainSounds ); i++ ) + PRECACHE_SOUND((char *)pPainSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pStompSounds ); i++ ) + PRECACHE_SOUND((char *)pStompSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pBreatheSounds ); i++ ) + PRECACHE_SOUND((char *)pBreatheSounds[i]); +} + + +void CGargantua::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + ALERT( at_aiconsole, "CGargantua::TraceAttack\n"); + + if ( !IsAlive() ) + { + CBaseMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); + return; + } + + // UNDONE: Hit group specific damage? + if ( bitsDamageType & (GARG_DAMAGE|DMG_BLAST) ) + { + if ( m_painSoundTime < gpGlobals->time ) + { + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, pPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1) ], 1.0, ATTN_GARG, 0, PITCH_NORM ); + m_painSoundTime = gpGlobals->time + RANDOM_FLOAT( 2.5, 4 ); + } + } + + bitsDamageType &= GARG_DAMAGE; + + if ( bitsDamageType == 0) + { + if ( pev->dmgtime != gpGlobals->time || (RANDOM_LONG(0,100) < 20) ) + { + UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT(0.5,1.5) ); + pev->dmgtime = gpGlobals->time; +// if ( RANDOM_LONG(0,100) < 25 ) +// EMIT_SOUND_DYN( ENT(pev), CHAN_BODY, pRicSounds[ RANDOM_LONG(0,ARRAYSIZE(pRicSounds)-1) ], 1.0, ATTN_NORM, 0, PITCH_NORM ); + } + flDamage = 0; + } + + CBaseMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); + +} + + + +int CGargantua::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + ALERT( at_aiconsole, "CGargantua::TakeDamage\n"); + + if ( IsAlive() ) + { + if ( !(bitsDamageType & GARG_DAMAGE) ) + flDamage *= 0.01; + if ( bitsDamageType & DMG_BLAST ) + SetConditions( bits_COND_LIGHT_DAMAGE ); + } + + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + + +void CGargantua::DeathEffect( void ) +{ + int i; + UTIL_MakeVectors(pev->angles); + Vector deathPos = pev->origin + gpGlobals->v_forward * 100; + + // Create a spiral of streaks + CSpiral::Create( deathPos, (pev->absmax.z - pev->absmin.z) * 0.6, 125, 1.5 ); + + Vector position = pev->origin; + position.z += 32; + for ( i = 0; i < 7; i+=2 ) + { + SpawnExplosion( position, 70, (i * 0.3), 60 + (i*20) ); + position.z += 15; + } + + CBaseEntity *pSmoker = CBaseEntity::Create( "env_smoker", pev->origin, g_vecZero, NULL ); + pSmoker->pev->health = 1; // 1 smoke balls + pSmoker->pev->scale = 46; // 4.6X normal size + pSmoker->pev->dmg = 0; // 0 radial distribution + pSmoker->pev->nextthink = gpGlobals->time + 2.5; // Start in 2.5 seconds +} + + +void CGargantua::Killed( entvars_t *pevAttacker, int iGib ) +{ + EyeOff(); + UTIL_Remove( m_pEyeGlow ); + m_pEyeGlow = NULL; + CBaseMonster::Killed( pevAttacker, GIB_NEVER ); +} + +//========================================================= +// CheckMeleeAttack1 +// Garg swipe attack +// +//========================================================= +BOOL CGargantua::CheckMeleeAttack1( float flDot, float flDist ) +{ +// ALERT(at_aiconsole, "CheckMelee(%f, %f)\n", flDot, flDist); + + if (flDot >= 0.7) + { + if (flDist <= GARG_ATTACKDIST) + return TRUE; + } + return FALSE; +} + + +// Flame thrower madness! +BOOL CGargantua::CheckMeleeAttack2( float flDot, float flDist ) +{ +// ALERT(at_aiconsole, "CheckMelee(%f, %f)\n", flDot, flDist); + + if ( gpGlobals->time > m_flameTime ) + { + if (flDot >= 0.8 && flDist > GARG_ATTACKDIST) + { + if ( flDist <= GARG_FLAME_LENGTH ) + return TRUE; + } + } + return FALSE; +} + + +//========================================================= +// CheckRangeAttack1 +// flDot is the cos of the angle of the cone within which +// the attack can occur. +//========================================================= +// +// Stomp attack +// +//========================================================= +BOOL CGargantua::CheckRangeAttack1( float flDot, float flDist ) +{ + if ( gpGlobals->time > m_seeTime ) + { + if (flDot >= 0.7 && flDist > GARG_ATTACKDIST) + { + return TRUE; + } + } + return FALSE; +} + + + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CGargantua::HandleAnimEvent(MonsterEvent_t *pEvent) +{ + switch( pEvent->event ) + { + case GARG_AE_SLASH_LEFT: + { + // HACKHACK!!! + CBaseEntity *pHurt = GargantuaCheckTraceHullAttack( GARG_ATTACKDIST + 10.0, gSkillData.gargantuaDmgSlash, DMG_SLASH ); + if (pHurt) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.x = -30; // pitch + pHurt->pev->punchangle.y = -30; // yaw + pHurt->pev->punchangle.z = 30; // roll + //UTIL_MakeVectors(pev->angles); // called by CheckTraceHullAttack + pHurt->pev->velocity = pHurt->pev->velocity - gpGlobals->v_right * 100; + } + EMIT_SOUND_DYN ( edict(), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, 50 + RANDOM_LONG(0,15) ); + } + else // Play a random attack miss sound + EMIT_SOUND_DYN ( edict(), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, 50 + RANDOM_LONG(0,15) ); + + Vector forward; + UTIL_MakeVectorsPrivate( pev->angles, forward, NULL, NULL ); + } + break; + + case GARG_AE_RIGHT_FOOT: + case GARG_AE_LEFT_FOOT: + UTIL_ScreenShake( pev->origin, 4.0, 3.0, 1.0, 750 ); + EMIT_SOUND_DYN ( edict(), CHAN_BODY, pFootSounds[ RANDOM_LONG(0,ARRAYSIZE(pFootSounds)-1) ], 1.0, ATTN_GARG, 0, PITCH_NORM + RANDOM_LONG(-10,10) ); + break; + + case GARG_AE_STOMP: + StompAttack(); + m_seeTime = gpGlobals->time + 12; + break; + + case GARG_AE_BREATHE: + EMIT_SOUND_DYN ( edict(), CHAN_VOICE, pBreatheSounds[ RANDOM_LONG(0,ARRAYSIZE(pBreatheSounds)-1) ], 1.0, ATTN_GARG, 0, PITCH_NORM + RANDOM_LONG(-10,10) ); + break; + + default: + CBaseMonster::HandleAnimEvent(pEvent); + break; + } +} + + +//========================================================= +// CheckTraceHullAttack - expects a length to trace, amount +// of damage to do, and damage type. Returns a pointer to +// the damaged entity in case the monster wishes to do +// other stuff to the victim (punchangle, etc) +// Used for many contact-range melee attacks. Bites, claws, etc. + +// Overridden for Gargantua because his swing starts lower as +// a percentage of his height (otherwise he swings over the +// players head) +//========================================================= +CBaseEntity* CGargantua::GargantuaCheckTraceHullAttack(float flDist, int iDamage, int iDmgType) +{ + TraceResult tr; + + UTIL_MakeVectors( pev->angles ); + Vector vecStart = pev->origin; + vecStart.z += 64; + Vector vecEnd = vecStart + (gpGlobals->v_forward * flDist) - (gpGlobals->v_up * flDist * 0.3); + + UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr ); + + if ( tr.pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + + if ( iDamage > 0 ) + { + pEntity->TakeDamage( pev, pev, iDamage, iDmgType ); + } + + return pEntity; + } + + return NULL; +} + + +Schedule_t *CGargantua::GetScheduleOfType( int Type ) +{ + // HACKHACK - turn off the flames if they are on and garg goes scripted / dead + if ( FlameIsOn() ) + FlameDestroy(); + + switch( Type ) + { + case SCHED_MELEE_ATTACK2: + return slGargFlame; + case SCHED_MELEE_ATTACK1: + return slGargSwipe; + break; + } + + return CBaseMonster::GetScheduleOfType( Type ); +} + + +void CGargantua::StartTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_FLAME_SWEEP: + FlameCreate(); + m_flWaitFinished = gpGlobals->time + pTask->flData; + m_flameTime = gpGlobals->time + 6; + m_flameX = 0; + m_flameY = 0; + break; + + case TASK_SOUND_ATTACK: + if ( RANDOM_LONG(0,100) < 30 ) + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, pAttackSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1) ], 1.0, ATTN_GARG, 0, PITCH_NORM ); + TaskComplete(); + break; + + case TASK_DIE: + m_flWaitFinished = gpGlobals->time + 1.6; + DeathEffect(); + // FALL THROUGH + default: + CBaseMonster::StartTask( pTask ); + break; + } +} + +//========================================================= +// RunTask +//========================================================= +void CGargantua::RunTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_DIE: + if ( gpGlobals->time > m_flWaitFinished ) + { + pev->renderfx = kRenderFxExplode; + pev->rendercolor.x = 255; + pev->rendercolor.y = 0; + pev->rendercolor.z = 0; + StopAnimation(); + pev->nextthink = gpGlobals->time + 0.15; + SetThink( &CGargantua::SUB_Remove ); + int i; + int parts = MODEL_FRAMES( gGargGibModel ); + for ( i = 0; i < 10; i++ ) + { + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + pGib->Spawn( GARG_GIB_MODEL ); + + int bodyPart = 0; + if ( parts > 1 ) + bodyPart = RANDOM_LONG( 0, pev->body-1 ); + + pGib->pev->body = bodyPart; + pGib->m_bloodColor = BLOOD_COLOR_YELLOW; + pGib->m_material = matNone; + pGib->pev->origin = pev->origin; + pGib->pev->velocity = UTIL_RandomBloodVector() * RANDOM_FLOAT( 300, 500 ); + pGib->pev->nextthink = gpGlobals->time + 1.25; + pGib->SetThink( &CGib::SUB_FadeOut ); + } + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + + // size + WRITE_COORD( 200 ); + WRITE_COORD( 200 ); + WRITE_COORD( 128 ); + + // velocity + WRITE_COORD( 0 ); + WRITE_COORD( 0 ); + WRITE_COORD( 0 ); + + // randomization + WRITE_BYTE( 200 ); + + // Model + WRITE_SHORT( gGargGibModel ); //model id# + + // # of shards + WRITE_BYTE( 50 ); + + // duration + WRITE_BYTE( 20 );// 3.0 seconds + + // flags + + WRITE_BYTE( BREAK_FLESH ); + MESSAGE_END(); + + return; + } + else + CBaseMonster::RunTask(pTask); + break; + + case TASK_FLAME_SWEEP: + if ( gpGlobals->time > m_flWaitFinished ) + { + FlameDestroy(); + TaskComplete(); + FlameControls( 0, 0 ); + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + } + else + { + BOOL cancel = FALSE; + + Vector angles = g_vecZero; + + FlameUpdate(); + CBaseEntity *pEnemy = m_hEnemy; + if ( pEnemy ) + { + Vector org = pev->origin; + org.z += 64; + Vector dir = pEnemy->BodyTarget(org) - org; + angles = UTIL_VecToAngles( dir ); + angles.x = -angles.x; + angles.y -= pev->angles.y; + if ( dir.Length() > 400 ) + cancel = TRUE; + } + if ( fabs(angles.y) > 60 ) + cancel = TRUE; + + if ( cancel ) + { + m_flWaitFinished -= 0.5; + m_flameTime -= 0.5; + } + // FlameControls( angles.x + 2 * sin(gpGlobals->time*8), angles.y + 28 * sin(gpGlobals->time*8.5) ); + FlameControls( angles.x, angles.y ); + } + break; + + default: + CBaseMonster::RunTask( pTask ); + break; + } +} + + +class CSmoker : public CBaseEntity +{ +public: + void Spawn( void ); + void Think( void ); +}; + +LINK_ENTITY_TO_CLASS( env_smoker, CSmoker ); + +void CSmoker::Spawn( void ) +{ + pev->movetype = MOVETYPE_NONE; + pev->nextthink = gpGlobals->time; + pev->solid = SOLID_NOT; + UTIL_SetSize(pev, g_vecZero, g_vecZero ); + pev->effects |= EF_NODRAW; + pev->angles = g_vecZero; +} + + +void CSmoker::Think( void ) +{ + // lots of smoke + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( pev->origin.x + RANDOM_FLOAT( -pev->dmg, pev->dmg )); + WRITE_COORD( pev->origin.y + RANDOM_FLOAT( -pev->dmg, pev->dmg )); + WRITE_COORD( pev->origin.z); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( RANDOM_LONG(pev->scale, pev->scale * 1.1) ); + WRITE_BYTE( RANDOM_LONG(8,14) ); // framerate + MESSAGE_END(); + + pev->health--; + if ( pev->health > 0 ) + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(0.1, 0.2); + else + UTIL_Remove( this ); +} + + +void CSpiral::Spawn( void ) +{ + pev->movetype = MOVETYPE_NONE; + pev->nextthink = gpGlobals->time; + pev->solid = SOLID_NOT; + UTIL_SetSize(pev, g_vecZero, g_vecZero ); + pev->effects |= EF_NODRAW; + pev->angles = g_vecZero; +} + + +CSpiral *CSpiral::Create( const Vector &origin, float height, float radius, float duration ) +{ + if ( duration <= 0 ) + return NULL; + + CSpiral *pSpiral = GetClassPtr( (CSpiral *)NULL ); + pSpiral->Spawn(); + pSpiral->pev->dmgtime = pSpiral->pev->nextthink; + pSpiral->pev->origin = origin; + pSpiral->pev->scale = radius; + pSpiral->pev->dmg = height; + pSpiral->pev->speed = duration; + pSpiral->pev->health = 0; + pSpiral->pev->angles = g_vecZero; + + return pSpiral; +} + +#define SPIRAL_INTERVAL 0.1 //025 + +void CSpiral::Think( void ) +{ + float time = gpGlobals->time - pev->dmgtime; + + while ( time > SPIRAL_INTERVAL ) + { + Vector position = pev->origin; + Vector direction = Vector(0,0,1); + + float fraction = 1.0 / pev->speed; + + float radius = (pev->scale * pev->health) * fraction; + + position.z += (pev->health * pev->dmg) * fraction; + pev->angles.y = (pev->health * 360 * 8) * fraction; + UTIL_MakeVectors( pev->angles ); + position = position + gpGlobals->v_forward * radius; + direction = (direction + gpGlobals->v_forward).Normalize(); + + StreakSplash( position, Vector(0,0,1), RANDOM_LONG(8,11), 20, RANDOM_LONG(50,150), 400 ); + + // Jeez, how many counters should this take ? :) + pev->dmgtime += SPIRAL_INTERVAL; + pev->health += SPIRAL_INTERVAL; + time -= SPIRAL_INTERVAL; + } + + pev->nextthink = gpGlobals->time; + + if ( pev->health >= pev->speed ) + UTIL_Remove( this ); +} + + +// HACKHACK Cut and pasted from explode.cpp +void SpawnExplosion( Vector center, float randomRange, float time, int magnitude ) +{ + KeyValueData kvd; + char buf[128]; + + center.x += RANDOM_FLOAT( -randomRange, randomRange ); + center.y += RANDOM_FLOAT( -randomRange, randomRange ); + + CBaseEntity *pExplosion = CBaseEntity::Create( "env_explosion", center, g_vecZero, NULL ); + sprintf( buf, "%3d", magnitude ); + kvd.szKeyName = "iMagnitude"; + kvd.szValue = buf; + pExplosion->KeyValue( &kvd ); + pExplosion->pev->spawnflags |= SF_ENVEXPLOSION_NODAMAGE; + + pExplosion->Spawn(); + pExplosion->SetThink( &CBaseEntity::SUB_CallUseToggle ); + pExplosion->pev->nextthink = gpGlobals->time + time; +} + + + +#endif diff --git a/dlls/gauss.cpp b/dlls/gauss.cpp new file mode 100644 index 0000000..213ae5d --- /dev/null +++ b/dlls/gauss.cpp @@ -0,0 +1,623 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "soundent.h" +#include "shake.h" +#include "gamerules.h" + + +#define GAUSS_PRIMARY_CHARGE_VOLUME 256// how loud gauss is while charging +#define GAUSS_PRIMARY_FIRE_VOLUME 450// how loud gauss is when discharged + +enum gauss_e { + GAUSS_IDLE = 0, + GAUSS_IDLE2, + GAUSS_FIDGET, + GAUSS_SPINUP, + GAUSS_SPIN, + GAUSS_FIRE, + GAUSS_FIRE2, + GAUSS_HOLSTER, + GAUSS_DRAW +}; + +LINK_ENTITY_TO_CLASS( weapon_gauss, CGauss ); + +float CGauss::GetFullChargeTime( void ) +{ +#ifdef CLIENT_DLL + if ( bIsMultiplayer() ) +#else + if ( g_pGameRules->IsMultiplayer() ) +#endif + { + return 1.5; + } + + return 4; +} + +#ifdef CLIENT_DLL +extern int g_irunninggausspred; +#endif + +void CGauss::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_GAUSS; + SET_MODEL(ENT(pev), "models/w_gauss.mdl"); + + m_iDefaultAmmo = GAUSS_DEFAULT_GIVE; + + FallInit();// get ready to fall down. +} + + +void CGauss::Precache( void ) +{ + PRECACHE_MODEL("models/w_gauss.mdl"); + PRECACHE_MODEL("models/v_gauss.mdl"); + PRECACHE_MODEL("models/p_gauss.mdl"); + + PRECACHE_SOUND("items/9mmclip1.wav"); + + PRECACHE_SOUND("weapons/gauss2.wav"); + PRECACHE_SOUND("weapons/electro4.wav"); + PRECACHE_SOUND("weapons/electro5.wav"); + PRECACHE_SOUND("weapons/electro6.wav"); + PRECACHE_SOUND("ambience/pulsemachine.wav"); + + m_iGlow = PRECACHE_MODEL( "sprites/hotglow.spr" ); + m_iBalls = PRECACHE_MODEL( "sprites/hotglow.spr" ); + m_iBeam = PRECACHE_MODEL( "sprites/smoke.spr" ); + + m_usGaussFire = PRECACHE_EVENT( 1, "events/gauss.sc" ); + m_usGaussSpin = PRECACHE_EVENT( 1, "events/gaussspin.sc" ); +} + +int CGauss::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + +int CGauss::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "uranium"; + p->iMaxAmmo1 = URANIUM_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 3; + p->iPosition = 1; + p->iId = m_iId = WEAPON_GAUSS; + p->iFlags = 0; + p->iWeight = GAUSS_WEIGHT; + + return 1; +} + +BOOL CGauss::Deploy( ) +{ + m_pPlayer->m_flPlayAftershock = 0.0; + return DefaultDeploy( "models/v_gauss.mdl", "models/p_gauss.mdl", GAUSS_DRAW, "gauss" ); +} + +void CGauss::Holster( int skiplocal /* = 0 */ ) +{ + PLAYBACK_EVENT_FULL( FEV_RELIABLE | FEV_GLOBAL, m_pPlayer->edict(), m_usGaussFire, 0.01, (float *)&m_pPlayer->pev->origin, (float *)&m_pPlayer->pev->angles, 0.0, 0.0, 0, 0, 0, 1 ); + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + + SendWeaponAnim( GAUSS_HOLSTER ); + m_fInAttack = 0; +} + + +void CGauss::PrimaryAttack() +{ + // don't fire underwater + if ( m_pPlayer->pev->waterlevel == 3 ) + { + PlayEmptySound( ); + m_flNextSecondaryAttack = m_flNextPrimaryAttack = GetNextAttackDelay(0.15); + return; + } + + if ( m_pPlayer->m_rgAmmo[ m_iPrimaryAmmoType ] < 2 ) + { + PlayEmptySound( ); + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + return; + } + + m_pPlayer->m_iWeaponVolume = GAUSS_PRIMARY_FIRE_VOLUME; + m_fPrimaryFire = TRUE; + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= 2; + + StartFire(); + m_fInAttack = 0; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.0; + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.2; +} + +void CGauss::SecondaryAttack() +{ + // don't fire underwater + if ( m_pPlayer->pev->waterlevel == 3 ) + { + if ( m_fInAttack != 0 ) + { + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/electro4.wav", 1.0, ATTN_NORM, 0, 80 + RANDOM_LONG(0,0x3f)); + SendWeaponAnim( GAUSS_IDLE ); + m_fInAttack = 0; + } + else + { + PlayEmptySound( ); + } + + m_flNextSecondaryAttack = m_flNextPrimaryAttack = GetNextAttackDelay(0.5); + return; + } + + if ( m_fInAttack == 0 ) + { + if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0 ) + { + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/357_cock1.wav", 0.8, ATTN_NORM); + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + return; + } + + m_fPrimaryFire = FALSE; + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--;// take one ammo just to start the spin + m_pPlayer->m_flNextAmmoBurn = UTIL_WeaponTimeBase(); + + // spin up + m_pPlayer->m_iWeaponVolume = GAUSS_PRIMARY_CHARGE_VOLUME; + + SendWeaponAnim( GAUSS_SPINUP ); + m_fInAttack = 1; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.5; + m_pPlayer->m_flStartCharge = gpGlobals->time; + m_pPlayer->m_flAmmoStartCharge = UTIL_WeaponTimeBase() + GetFullChargeTime(); + + PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_usGaussSpin, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 110, 0, 0, 0 ); + + m_iSoundState = SND_CHANGE_PITCH; + } + else if (m_fInAttack == 1) + { + if (m_flTimeWeaponIdle < UTIL_WeaponTimeBase()) + { + SendWeaponAnim( GAUSS_SPIN ); + m_fInAttack = 2; + } + } + else + { + // during the charging process, eat one bit of ammo every once in a while + if ( UTIL_WeaponTimeBase() >= m_pPlayer->m_flNextAmmoBurn && m_pPlayer->m_flNextAmmoBurn != 1000 ) + { +#ifdef CLIENT_DLL + if ( bIsMultiplayer() ) +#else + if ( g_pGameRules->IsMultiplayer() ) +#endif + { + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + m_pPlayer->m_flNextAmmoBurn = UTIL_WeaponTimeBase() + 0.1; + } + else + { + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + m_pPlayer->m_flNextAmmoBurn = UTIL_WeaponTimeBase() + 0.3; + } + } + + if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0 ) + { + // out of ammo! force the gun to fire + StartFire(); + m_fInAttack = 0; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.0; + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1; + return; + } + + if ( UTIL_WeaponTimeBase() >= m_pPlayer->m_flAmmoStartCharge ) + { + // don't eat any more ammo after gun is fully charged. + m_pPlayer->m_flNextAmmoBurn = 1000; + } + + int pitch = ( gpGlobals->time - m_pPlayer->m_flStartCharge ) * ( 150 / GetFullChargeTime() ) + 100; + if ( pitch > 250 ) + pitch = 250; + + // ALERT( at_console, "%d %d %d\n", m_fInAttack, m_iSoundState, pitch ); + + if ( m_iSoundState == 0 ) + ALERT( at_console, "sound state %d\n", m_iSoundState ); + + PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_usGaussSpin, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, pitch, 0, ( m_iSoundState == SND_CHANGE_PITCH ) ? 1 : 0, 0 ); + + m_iSoundState = SND_CHANGE_PITCH; // hack for going through level transitions + + m_pPlayer->m_iWeaponVolume = GAUSS_PRIMARY_CHARGE_VOLUME; + + // m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.1; + if ( m_pPlayer->m_flStartCharge < gpGlobals->time - 10 ) + { + // Player charged up too long. Zap him. + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/electro4.wav", 1.0, ATTN_NORM, 0, 80 + RANDOM_LONG(0,0x3f)); + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/electro6.wav", 1.0, ATTN_NORM, 0, 75 + RANDOM_LONG(0,0x3f)); + + m_fInAttack = 0; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.0; + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.0; + +#ifndef CLIENT_DLL + m_pPlayer->TakeDamage( VARS(eoNullEntity), VARS(eoNullEntity), 50, DMG_SHOCK ); + UTIL_ScreenFade( m_pPlayer, Vector(255,128,0), 2, 0.5, 128, FFADE_IN ); +#endif + SendWeaponAnim( GAUSS_IDLE ); + + // Player may have been killed and this weapon dropped, don't execute any more code after this! + return; + } + } +} + +//========================================================= +// StartFire- since all of this code has to run and then +// call Fire(), it was easier at this point to rip it out +// of weaponidle() and make its own function then to try to +// merge this into Fire(), which has some identical variable names +//========================================================= +void CGauss::StartFire( void ) +{ + float flDamage; + + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + Vector vecAiming = gpGlobals->v_forward; + Vector vecSrc = m_pPlayer->GetGunPosition( ); // + gpGlobals->v_up * -8 + gpGlobals->v_right * 8; + + if ( gpGlobals->time - m_pPlayer->m_flStartCharge > GetFullChargeTime() ) + { + flDamage = 200; + } + else + { + flDamage = 200 * (( gpGlobals->time - m_pPlayer->m_flStartCharge) / GetFullChargeTime() ); + } + + if ( m_fPrimaryFire ) + { + // fixed damage on primary attack +#ifdef CLIENT_DLL + flDamage = 20; +#else + flDamage = gSkillData.plrDmgGauss; +#endif + } + + if (m_fInAttack != 3) + { + //ALERT ( at_console, "Time:%f Damage:%f\n", gpGlobals->time - m_pPlayer->m_flStartCharge, flDamage ); + +#ifndef CLIENT_DLL + float flZVel = m_pPlayer->pev->velocity.z; + + if ( !m_fPrimaryFire ) + { + m_pPlayer->pev->velocity = m_pPlayer->pev->velocity - gpGlobals->v_forward * flDamage * 5; + } + + if ( !g_pGameRules->IsMultiplayer() ) + + { + // in deathmatch, gauss can pop you up into the air. Not in single play. + m_pPlayer->pev->velocity.z = flZVel; + } +#endif + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + } + + // time until aftershock 'static discharge' sound + m_pPlayer->m_flPlayAftershock = gpGlobals->time + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0.3, 0.8 ); + + Fire( vecSrc, vecAiming, flDamage ); +} + +void CGauss::Fire( Vector vecOrigSrc, Vector vecDir, float flDamage ) +{ + m_pPlayer->m_iWeaponVolume = GAUSS_PRIMARY_FIRE_VOLUME; + + Vector vecSrc = vecOrigSrc; + Vector vecDest = vecSrc + vecDir * 8192; + edict_t *pentIgnore; + TraceResult tr, beam_tr; + float flMaxFrac = 1.0; + int nTotal = 0; + int fHasPunched = 0; + int fFirstBeam = 1; + int nMaxHits = 10; + + pentIgnore = ENT( m_pPlayer->pev ); + +#ifdef CLIENT_DLL + if ( m_fPrimaryFire == false ) + g_irunninggausspred = true; +#endif + + // The main firing event is sent unreliably so it won't be delayed. + PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_usGaussFire, 0.0, (float *)&m_pPlayer->pev->origin, (float *)&m_pPlayer->pev->angles, flDamage, 0.0, 0, 0, m_fPrimaryFire ? 1 : 0, 0 ); + + // This reliable event is used to stop the spinning sound + // It's delayed by a fraction of second to make sure it is delayed by 1 frame on the client + // It's sent reliably anyway, which could lead to other delays + + PLAYBACK_EVENT_FULL( FEV_NOTHOST | FEV_RELIABLE, m_pPlayer->edict(), m_usGaussFire, 0.01, (float *)&m_pPlayer->pev->origin, (float *)&m_pPlayer->pev->angles, 0.0, 0.0, 0, 0, 0, 1 ); + + + /*ALERT( at_console, "%f %f %f\n%f %f %f\n", + vecSrc.x, vecSrc.y, vecSrc.z, + vecDest.x, vecDest.y, vecDest.z );*/ + + +// ALERT( at_console, "%f %f\n", tr.flFraction, flMaxFrac ); + +#ifndef CLIENT_DLL + while (flDamage > 10 && nMaxHits > 0) + { + nMaxHits--; + + // ALERT( at_console, "." ); + UTIL_TraceLine(vecSrc, vecDest, dont_ignore_monsters, pentIgnore, &tr); + + if (tr.fAllSolid) + break; + + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + if (pEntity == NULL) + break; + + if ( fFirstBeam ) + { + m_pPlayer->pev->effects |= EF_MUZZLEFLASH; + fFirstBeam = 0; + + nTotal += 26; + } + + if (pEntity->pev->takedamage) + { + ClearMultiDamage(); + pEntity->TraceAttack( m_pPlayer->pev, flDamage, vecDir, &tr, DMG_BULLET ); + ApplyMultiDamage(m_pPlayer->pev, m_pPlayer->pev); + } + + if ( pEntity->ReflectGauss() ) + { + float n; + + pentIgnore = NULL; + + n = -DotProduct(tr.vecPlaneNormal, vecDir); + + if (n < 0.5) // 60 degrees + { + // ALERT( at_console, "reflect %f\n", n ); + // reflect + Vector r; + + r = 2.0 * tr.vecPlaneNormal * n + vecDir; + flMaxFrac = flMaxFrac - tr.flFraction; + vecDir = r; + vecSrc = tr.vecEndPos + vecDir * 8; + vecDest = vecSrc + vecDir * 8192; + + // explode a bit + m_pPlayer->RadiusDamage( tr.vecEndPos, pev, m_pPlayer->pev, flDamage * n, CLASS_NONE, DMG_BLAST ); + + nTotal += 34; + + // lose energy + if (n == 0) n = 0.1; + flDamage = flDamage * (1 - n); + } + else + { + nTotal += 13; + + // limit it to one hole punch + if (fHasPunched) + break; + fHasPunched = 1; + + // try punching through wall if secondary attack (primary is incapable of breaking through) + if ( !m_fPrimaryFire ) + { + UTIL_TraceLine( tr.vecEndPos + vecDir * 8, vecDest, dont_ignore_monsters, pentIgnore, &beam_tr); + if (!beam_tr.fAllSolid) + { + // trace backwards to find exit point + UTIL_TraceLine( beam_tr.vecEndPos, tr.vecEndPos, dont_ignore_monsters, pentIgnore, &beam_tr); + + float n = (beam_tr.vecEndPos - tr.vecEndPos).Length( ); + + if (n < flDamage) + { + if (n == 0) n = 1; + flDamage -= n; + + // ALERT( at_console, "punch %f\n", n ); + nTotal += 21; + + // exit blast damage + //m_pPlayer->RadiusDamage( beam_tr.vecEndPos + vecDir * 8, pev, m_pPlayer->pev, flDamage, CLASS_NONE, DMG_BLAST ); + float damage_radius; + + + if ( g_pGameRules->IsMultiplayer() ) + { + damage_radius = flDamage * 1.75; // Old code == 2.5 + } + else + { + damage_radius = flDamage * 2.5; + } + + ::RadiusDamage( beam_tr.vecEndPos + vecDir * 8, pev, m_pPlayer->pev, flDamage, damage_radius, CLASS_NONE, DMG_BLAST ); + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, NORMAL_EXPLOSION_VOLUME, 3.0 ); + + nTotal += 53; + + vecSrc = beam_tr.vecEndPos + vecDir; + } + } + else + { + //ALERT( at_console, "blocked %f\n", n ); + flDamage = 0; + } + } + else + { + //ALERT( at_console, "blocked solid\n" ); + + flDamage = 0; + } + + } + } + else + { + vecSrc = tr.vecEndPos + vecDir; + pentIgnore = ENT( pEntity->pev ); + } + } +#endif + // ALERT( at_console, "%d bytes\n", nTotal ); +} + + + + +void CGauss::WeaponIdle( void ) +{ + ResetEmptySound( ); + + // play aftershock static discharge + if ( m_pPlayer->m_flPlayAftershock && m_pPlayer->m_flPlayAftershock < gpGlobals->time ) + { + switch (RANDOM_LONG(0,3)) + { + case 0: EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/electro4.wav", RANDOM_FLOAT(0.7, 0.8), ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/electro5.wav", RANDOM_FLOAT(0.7, 0.8), ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/electro6.wav", RANDOM_FLOAT(0.7, 0.8), ATTN_NORM); break; + case 3: break; // no sound + } + m_pPlayer->m_flPlayAftershock = 0.0; + } + + if (m_flTimeWeaponIdle > UTIL_WeaponTimeBase()) + return; + + if (m_fInAttack != 0) + { + StartFire(); + m_fInAttack = 0; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 2.0; + } + else + { + int iAnim; + float flRand = RANDOM_FLOAT(0, 1); + if (flRand <= 0.5) + { + iAnim = GAUSS_IDLE; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + } + else if (flRand <= 0.75) + { + iAnim = GAUSS_IDLE2; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + } + else + { + iAnim = GAUSS_FIDGET; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 3; + } + + return; + SendWeaponAnim( iAnim ); + + } +} + + + + + + +class CGaussAmmo : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_gaussammo.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_gaussammo.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + if (pOther->GiveAmmo( AMMO_URANIUMBOX_GIVE, "uranium", URANIUM_MAX_CARRY ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + return TRUE; + } + return FALSE; + } +}; +LINK_ENTITY_TO_CLASS( ammo_gaussclip, CGaussAmmo ); + +#endif diff --git a/dlls/genericmonster.cpp b/dlls/genericmonster.cpp new file mode 100644 index 0000000..69bb130 --- /dev/null +++ b/dlls/genericmonster.cpp @@ -0,0 +1,140 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Generic Monster - purely for scripted sequence work. +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" + +// For holograms, make them not solid so the player can walk through them +#define SF_GENERICMONSTER_NOTSOLID 4 + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +class CGenericMonster : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int ISoundMask ( void ); +}; +LINK_ENTITY_TO_CLASS( monster_generic, CGenericMonster ); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CGenericMonster :: Classify ( void ) +{ + return CLASS_PLAYER_ALLY; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CGenericMonster :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + default: + ys = 90; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CGenericMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case 0: + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// ISoundMask - generic monster can't hear. +//========================================================= +int CGenericMonster :: ISoundMask ( void ) +{ + return NULL; +} + +//========================================================= +// Spawn +//========================================================= +void CGenericMonster :: Spawn() +{ + Precache(); + + SET_MODEL( ENT(pev), STRING(pev->model) ); + +/* + if ( FStrEq( STRING(pev->model), "models/player.mdl" ) ) + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + else + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); +*/ + + if ( FStrEq( STRING(pev->model), "models/player.mdl" ) || FStrEq( STRING(pev->model), "models/holo.mdl" ) ) + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + else + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->health = 8; + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); + + if ( pev->spawnflags & SF_GENERICMONSTER_NOTSOLID ) + { + pev->solid = SOLID_NOT; + pev->takedamage = DAMAGE_NO; + } +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CGenericMonster :: Precache() +{ + PRECACHE_MODEL( (char *)STRING(pev->model) ); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= diff --git a/dlls/ggrenade.cpp b/dlls/ggrenade.cpp new file mode 100644 index 0000000..35bf556 --- /dev/null +++ b/dlls/ggrenade.cpp @@ -0,0 +1,488 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== generic grenade.cpp ======================================================== + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "soundent.h" +#include "decals.h" + + +//===================grenade + + +LINK_ENTITY_TO_CLASS( grenade, CGrenade ); + +// Grenades flagged with this will be triggered when the owner calls detonateSatchelCharges +#define SF_DETONATE 0x0001 + +// +// Grenade Explode +// +void CGrenade::Explode( Vector vecSrc, Vector vecAim ) +{ + TraceResult tr; + UTIL_TraceLine ( pev->origin, pev->origin + Vector ( 0, 0, -32 ), ignore_monsters, ENT(pev), & tr); + + Explode( &tr, DMG_BLAST ); +} + +// UNDONE: temporary scorching for PreAlpha - find a less sleazy permenant solution. +void CGrenade::Explode( TraceResult *pTrace, int bitsDamageType ) +{ + float flRndSound;// sound randomizer + + pev->model = iStringNull;//invisible + pev->solid = SOLID_NOT;// intangible + + pev->takedamage = DAMAGE_NO; + + // Pull out of the wall a bit + if ( pTrace->flFraction != 1.0 ) + { + pev->origin = pTrace->vecEndPos + (pTrace->vecPlaneNormal * (pev->dmg - 24) * 0.6); + } + + int iContents = UTIL_PointContents ( pev->origin ); + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION ); // This makes a dynamic light and the explosion sprites/sound + WRITE_COORD( pev->origin.x ); // Send to PAS because of the sound + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + if (iContents != CONTENTS_WATER) + { + WRITE_SHORT( g_sModelIndexFireball ); + } + else + { + WRITE_SHORT( g_sModelIndexWExplosion ); + } + WRITE_BYTE( (pev->dmg - 50) * .60 ); // scale * 10 + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, NORMAL_EXPLOSION_VOLUME, 3.0 ); + entvars_t *pevOwner; + if ( pev->owner ) + pevOwner = VARS( pev->owner ); + else + pevOwner = NULL; + + pev->owner = NULL; // can't traceline attack owner if this is set + + RadiusDamage ( pev, pevOwner, pev->dmg, CLASS_NONE, bitsDamageType ); + + if ( RANDOM_FLOAT( 0 , 1 ) < 0.5 ) + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH1 ); + } + else + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH2 ); + } + + flRndSound = RANDOM_FLOAT( 0 , 1 ); + + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris1.wav", 0.55, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris2.wav", 0.55, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris3.wav", 0.55, ATTN_NORM); break; + } + + pev->effects |= EF_NODRAW; + SetThink( &CGrenade::Smoke ); + pev->velocity = g_vecZero; + pev->nextthink = gpGlobals->time + 0.3; + + if (iContents != CONTENTS_WATER) + { + int sparkCount = RANDOM_LONG(0,3); + for ( int i = 0; i < sparkCount; i++ ) + Create( "spark_shower", pev->origin, pTrace->vecPlaneNormal, NULL ); + } +} + + +void CGrenade::Smoke( void ) +{ + if (UTIL_PointContents ( pev->origin ) == CONTENTS_WATER) + { + UTIL_Bubbles( pev->origin - Vector( 64, 64, 64 ), pev->origin + Vector( 64, 64, 64 ), 100 ); + } + else + { + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( (pev->dmg - 50) * 0.80 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + } + UTIL_Remove( this ); +} + +void CGrenade::Killed( entvars_t *pevAttacker, int iGib ) +{ + Detonate( ); +} + + +// Timed grenade, this think is called when time runs out. +void CGrenade::DetonateUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( &CGrenade::Detonate ); + pev->nextthink = gpGlobals->time; +} + +void CGrenade::PreDetonate( void ) +{ + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin, 400, 0.3 ); + + SetThink( &CGrenade::Detonate ); + pev->nextthink = gpGlobals->time + 1; +} + + +void CGrenade::Detonate( void ) +{ + TraceResult tr; + Vector vecSpot;// trace starts here! + + vecSpot = pev->origin + Vector ( 0 , 0 , 8 ); + UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -40 ), ignore_monsters, ENT(pev), & tr); + + Explode( &tr, DMG_BLAST ); +} + + +// +// Contact grenade, explode when it touches something +// +void CGrenade::ExplodeTouch( CBaseEntity *pOther ) +{ + TraceResult tr; + Vector vecSpot;// trace starts here! + + pev->enemy = pOther->edict(); + + vecSpot = pev->origin - pev->velocity.Normalize() * 32; + UTIL_TraceLine( vecSpot, vecSpot + pev->velocity.Normalize() * 64, ignore_monsters, ENT(pev), &tr ); + + Explode( &tr, DMG_BLAST ); +} + + +void CGrenade::DangerSoundThink( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin + pev->velocity * 0.5, pev->velocity.Length( ), 0.2 ); + pev->nextthink = gpGlobals->time + 0.2; + + if (pev->waterlevel != 0) + { + pev->velocity = pev->velocity * 0.5; + } +} + + +void CGrenade::BounceTouch( CBaseEntity *pOther ) +{ + // don't hit the guy that launched this grenade + if ( pOther->edict() == pev->owner ) + return; + + // only do damage if we're moving fairly fast + if (m_flNextAttack < gpGlobals->time && pev->velocity.Length() > 100) + { + entvars_t *pevOwner = VARS( pev->owner ); + if (pevOwner) + { + TraceResult tr = UTIL_GetGlobalTrace( ); + ClearMultiDamage( ); + pOther->TraceAttack(pevOwner, 1, gpGlobals->v_forward, &tr, DMG_CLUB ); + ApplyMultiDamage( pev, pevOwner); + } + m_flNextAttack = gpGlobals->time + 1.0; // debounce + } + + Vector vecTestVelocity; + // pev->avelocity = Vector (300, 300, 300); + + // this is my heuristic for modulating the grenade velocity because grenades dropped purely vertical + // or thrown very far tend to slow down too quickly for me to always catch just by testing velocity. + // trimming the Z velocity a bit seems to help quite a bit. + vecTestVelocity = pev->velocity; + vecTestVelocity.z *= 0.45; + + if ( !m_fRegisteredSound && vecTestVelocity.Length() <= 60 ) + { + //ALERT( at_console, "Grenade Registered!: %f\n", vecTestVelocity.Length() ); + + // grenade is moving really slow. It's probably very close to where it will ultimately stop moving. + // go ahead and emit the danger sound. + + // register a radius louder than the explosion, so we make sure everyone gets out of the way + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin, pev->dmg / 0.4, 0.3 ); + m_fRegisteredSound = TRUE; + } + + if (pev->flags & FL_ONGROUND) + { + // add a bit of static friction + pev->velocity = pev->velocity * 0.8; + + pev->sequence = RANDOM_LONG( 1, 1 ); + } + else + { + // play bounce sound + BounceSound(); + } + pev->framerate = pev->velocity.Length() / 200.0; + if (pev->framerate > 1.0) + pev->framerate = 1; + else if (pev->framerate < 0.5) + pev->framerate = 0; + +} + + + +void CGrenade::SlideTouch( CBaseEntity *pOther ) +{ + // don't hit the guy that launched this grenade + if ( pOther->edict() == pev->owner ) + return; + + // pev->avelocity = Vector (300, 300, 300); + + if (pev->flags & FL_ONGROUND) + { + // add a bit of static friction + pev->velocity = pev->velocity * 0.95; + + if (pev->velocity.x != 0 || pev->velocity.y != 0) + { + // maintain sliding sound + } + } + else + { + BounceSound(); + } +} + +void CGrenade :: BounceSound( void ) +{ + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit1.wav", 0.25, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit2.wav", 0.25, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit3.wav", 0.25, ATTN_NORM); break; + } +} + +void CGrenade :: TumbleThink( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + if (pev->dmgtime - 1 < gpGlobals->time) + { + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin + pev->velocity * (pev->dmgtime - gpGlobals->time), 400, 0.1 ); + } + + if (pev->dmgtime <= gpGlobals->time) + { + SetThink( &CGrenade::Detonate ); + } + if (pev->waterlevel != 0) + { + pev->velocity = pev->velocity * 0.5; + pev->framerate = 0.2; + } +} + + +void CGrenade:: Spawn( void ) +{ + pev->movetype = MOVETYPE_BOUNCE; + pev->classname = MAKE_STRING( "grenade" ); + + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/grenade.mdl"); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + + pev->dmg = 100; + m_fRegisteredSound = FALSE; +} + + +CGrenade *CGrenade::ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ) +{ + CGrenade *pGrenade = GetClassPtr( (CGrenade *)NULL ); + pGrenade->Spawn(); + // contact grenades arc lower + pGrenade->pev->gravity = 0.5;// lower gravity since grenade is aerodynamic and engine doesn't know it. + UTIL_SetOrigin( pGrenade->pev, vecStart ); + pGrenade->pev->velocity = vecVelocity; + pGrenade->pev->angles = UTIL_VecToAngles (pGrenade->pev->velocity); + pGrenade->pev->owner = ENT(pevOwner); + + // make monsters afaid of it while in the air + pGrenade->SetThink( &CGrenade::DangerSoundThink ); + pGrenade->pev->nextthink = gpGlobals->time; + + // Tumble in air + pGrenade->pev->avelocity.x = RANDOM_FLOAT ( -100, -500 ); + + // Explode on contact + pGrenade->SetTouch( &CGrenade::ExplodeTouch ); + + pGrenade->pev->dmg = gSkillData.plrDmgM203Grenade; + + return pGrenade; +} + + +CGrenade * CGrenade:: ShootTimed( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time ) +{ + CGrenade *pGrenade = GetClassPtr( (CGrenade *)NULL ); + pGrenade->Spawn(); + UTIL_SetOrigin( pGrenade->pev, vecStart ); + pGrenade->pev->velocity = vecVelocity; + pGrenade->pev->angles = UTIL_VecToAngles(pGrenade->pev->velocity); + pGrenade->pev->owner = ENT(pevOwner); + + pGrenade->SetTouch( &CGrenade::BounceTouch ); // Bounce if touched + + // Take one second off of the desired detonation time and set the think to PreDetonate. PreDetonate + // will insert a DANGER sound into the world sound list and delay detonation for one second so that + // the grenade explodes after the exact amount of time specified in the call to ShootTimed(). + + pGrenade->pev->dmgtime = gpGlobals->time + time; + pGrenade->SetThink( &CGrenade::TumbleThink ); + pGrenade->pev->nextthink = gpGlobals->time + 0.1; + if (time < 0.1) + { + pGrenade->pev->nextthink = gpGlobals->time; + pGrenade->pev->velocity = Vector( 0, 0, 0 ); + } + + pGrenade->pev->sequence = RANDOM_LONG( 3, 6 ); + pGrenade->pev->framerate = 1.0; + + // Tumble through the air + // pGrenade->pev->avelocity.x = -400; + + pGrenade->pev->gravity = 0.5; + pGrenade->pev->friction = 0.8; + + SET_MODEL(ENT(pGrenade->pev), "models/w_grenade.mdl"); + pGrenade->pev->dmg = 100; + + return pGrenade; +} + + +CGrenade * CGrenade :: ShootSatchelCharge( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ) +{ + CGrenade *pGrenade = GetClassPtr( (CGrenade *)NULL ); + pGrenade->pev->movetype = MOVETYPE_BOUNCE; + pGrenade->pev->classname = MAKE_STRING( "grenade" ); + + pGrenade->pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pGrenade->pev), "models/grenade.mdl"); // Change this to satchel charge model + + UTIL_SetSize(pGrenade->pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + + pGrenade->pev->dmg = 200; + UTIL_SetOrigin( pGrenade->pev, vecStart ); + pGrenade->pev->velocity = vecVelocity; + pGrenade->pev->angles = g_vecZero; + pGrenade->pev->owner = ENT(pevOwner); + + // Detonate in "time" seconds + pGrenade->SetThink( &CGrenade::SUB_DoNothing ); + pGrenade->SetUse( &CGrenade::DetonateUse ); + pGrenade->SetTouch( &CGrenade::SlideTouch ); + pGrenade->pev->spawnflags = SF_DETONATE; + + pGrenade->pev->friction = 0.9; + + return pGrenade; +} + + + +void CGrenade :: UseSatchelCharges( entvars_t *pevOwner, SATCHELCODE code ) +{ + edict_t *pentFind; + edict_t *pentOwner; + + if ( !pevOwner ) + return; + + CBaseEntity *pOwner = CBaseEntity::Instance( pevOwner ); + + pentOwner = pOwner->edict(); + + pentFind = FIND_ENTITY_BY_CLASSNAME( NULL, "grenade" ); + while ( !FNullEnt( pentFind ) ) + { + CBaseEntity *pEnt = Instance( pentFind ); + if ( pEnt ) + { + if ( FBitSet( pEnt->pev->spawnflags, SF_DETONATE ) && pEnt->pev->owner == pentOwner ) + { + if ( code == SATCHEL_DETONATE ) + pEnt->Use( pOwner, pOwner, USE_ON, 0 ); + else // SATCHEL_RELEASE + pEnt->pev->owner = NULL; + } + } + pentFind = FIND_ENTITY_BY_CLASSNAME( pentFind, "grenade" ); + } +} + +//======================end grenade + diff --git a/dlls/globals.cpp b/dlls/globals.cpp new file mode 100644 index 0000000..b3f5d7c --- /dev/null +++ b/dlls/globals.cpp @@ -0,0 +1,39 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== globals.cpp ======================================================== + + DLL-wide global variable definitions. + They're all defined here, for convenient centralization. + Source files that need them should "extern ..." declare each + variable, to better document what globals they care about. + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "soundent.h" + +DLL_GLOBAL ULONG g_ulFrameCount; +DLL_GLOBAL ULONG g_ulModelIndexEyes; +DLL_GLOBAL ULONG g_ulModelIndexPlayer; +DLL_GLOBAL Vector g_vecAttackDir; +DLL_GLOBAL int g_iSkillLevel; +DLL_GLOBAL int gDisplayTitle; +DLL_GLOBAL BOOL g_fGameOver; +DLL_GLOBAL const Vector g_vecZero = Vector(0,0,0); +DLL_GLOBAL int g_Language; diff --git a/dlls/glock.cpp b/dlls/glock.cpp new file mode 100644 index 0000000..9799c2b --- /dev/null +++ b/dlls/glock.cpp @@ -0,0 +1,325 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" + +enum glock_e { + GLOCK_IDLE1 = 0, + GLOCK_IDLE2, + GLOCK_IDLE3, + GLOCK_SHOOT, + GLOCK_SHOOT_EMPTY, + GLOCK_RELOAD, + GLOCK_RELOAD_NOT_EMPTY, + GLOCK_DRAW, + GLOCK_HOLSTER, + GLOCK_ADD_SILENCER +}; + +class CGlock : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 2; } + int GetItemInfo(ItemInfo *p); + + void PrimaryAttack( void ); + void SecondaryAttack( void ); + void GlockFire( float flSpread, float flCycleTime, BOOL fUseAutoAim ); + BOOL Deploy( void ); + void Reload( void ); + void WeaponIdle( void ); + +private: + int m_iShell; + + + unsigned short m_usFireGlock1; + unsigned short m_usFireGlock2; +}; +LINK_ENTITY_TO_CLASS( weapon_glock, CGlock ); +LINK_ENTITY_TO_CLASS( weapon_9mmhandgun, CGlock ); + + +void CGlock::Spawn( ) +{ + pev->classname = MAKE_STRING("weapon_9mmhandgun"); // hack to allow for old names + Precache( ); + m_iId = WEAPON_GLOCK; + SET_MODEL(ENT(pev), "models/w_9mmhandgun.mdl"); + + m_iDefaultAmmo = GLOCK_DEFAULT_GIVE; + + FallInit();// get ready to fall down. +} + + +void CGlock::Precache( void ) +{ + PRECACHE_MODEL("models/v_9mmhandgun.mdl"); + PRECACHE_MODEL("models/w_9mmhandgun.mdl"); + PRECACHE_MODEL("models/p_9mmhandgun.mdl"); + + m_iShell = PRECACHE_MODEL ("models/shell.mdl");// brass shell + + PRECACHE_SOUND("items/9mmclip1.wav"); + PRECACHE_SOUND("items/9mmclip2.wav"); + + PRECACHE_SOUND ("weapons/pl_gun1.wav");//silenced handgun + PRECACHE_SOUND ("weapons/pl_gun2.wav");//silenced handgun + PRECACHE_SOUND ("weapons/pl_gun3.wav");//handgun + + m_usFireGlock1 = PRECACHE_EVENT( 1, "events/glock1.sc" ); + m_usFireGlock2 = PRECACHE_EVENT( 1, "events/glock2.sc" ); +} + +int CGlock::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "9mm"; + p->iMaxAmmo1 = _9MM_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = GLOCK_MAX_CLIP; + p->iSlot = 1; + p->iPosition = 0; + p->iFlags = 0; + p->iId = m_iId = WEAPON_GLOCK; + p->iWeight = GLOCK_WEIGHT; + + return 1; +} + +BOOL CGlock::Deploy( ) +{ + // pev->body = 1; + return DefaultDeploy( "models/v_9mmhandgun.mdl", "models/p_9mmhandgun.mdl", GLOCK_DRAW, "onehanded" ); +} + +void CGlock::SecondaryAttack( void ) +{ + GlockFire( 0.1, 0.2, FALSE ); +} + +void CGlock::PrimaryAttack( void ) +{ + GlockFire( 0.01, 0.3, TRUE ); +} + +void CGlock::GlockFire( float flSpread , float flCycleTime, BOOL fUseAutoAim ) +{ + if (m_iClip <= 0) + { + if (m_fFireOnEmpty) + { + PlayEmptySound(); + m_flNextPrimaryAttack = gpGlobals->time + 0.2; + } + + return; + } + + m_iClip--; + + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + +#if defined ( OLD_WEAPONS ) + if (m_iClip != 0) + SendWeaponAnim( GLOCK_SHOOT ); + else + SendWeaponAnim( GLOCK_SHOOT_EMPTY ); +#endif + + if ( fUseAutoAim ) + { + PLAYBACK_EVENT_FULL( 0, m_pPlayer->edict(), m_usFireGlock1, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, ( m_iClip == 0 ) ? 1 : 0, 0 ); + } + else + { + PLAYBACK_EVENT_FULL( 0, m_pPlayer->edict(), m_usFireGlock2, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, ( m_iClip == 0 ) ? 1 : 0, 0 ); + } + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + +#if defined ( OLD_WEAPONS ) + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + + Vector vecShellVelocity = m_pPlayer->pev->velocity + + gpGlobals->v_right * RANDOM_FLOAT(50,70) + + gpGlobals->v_up * RANDOM_FLOAT(100,150) + + gpGlobals->v_forward * 25; + EjectBrass ( pev->origin + m_pPlayer->pev->view_ofs + gpGlobals->v_up * -12 + gpGlobals->v_forward * 32 + gpGlobals->v_right * 6 , vecShellVelocity, pev->angles.y, m_iShell, TE_BOUNCE_SHELL ); +#endif + + // silenced + if (pev->body == 1) + { + m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = DIM_GUN_FLASH; +#if defined ( OLD_WEAPONS ) + switch(RANDOM_LONG(0,1)) + { + case 0: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/pl_gun1.wav", RANDOM_FLOAT(0.9, 1.0), ATTN_NORM); + break; + case 1: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/pl_gun2.wav", RANDOM_FLOAT(0.9, 1.0), ATTN_NORM); + break; + } +#endif + } + else + { + // non-silenced + m_pPlayer->m_iWeaponVolume = NORMAL_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; +#if defined ( OLD_WEAPONS ) + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/pl_gun3.wav", RANDOM_FLOAT(0.92, 1.0), ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); +#endif + } + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming; + + if ( fUseAutoAim ) + { + vecAiming = m_pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES ); + } + else + { + vecAiming = gpGlobals->v_forward; + } + + m_pPlayer->FireBullets( 1, vecSrc, vecAiming, Vector( flSpread, flSpread, flSpread ), 8192, BULLET_PLAYER_9MM, 0 ); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = gpGlobals->time + flCycleTime; + + if (!m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + // HEV suit - indicate out of ammo condition + m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); + +#if defined ( OLD_WEAPONS ) + m_pPlayer->pev->punchangle.x -= 2; +#endif +} + + +void CGlock::Reload( void ) +{ + int iResult; + + if (m_iClip == 0) + iResult = DefaultReload( 17, GLOCK_RELOAD, 1.5 ); + else + iResult = DefaultReload( 18, GLOCK_RELOAD_NOT_EMPTY, 1.5 ); + + if (iResult) + { + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); + } +} + + + +void CGlock::WeaponIdle( void ) +{ + ResetEmptySound( ); + + m_pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES ); + + if (m_flTimeWeaponIdle > gpGlobals->time) + return; + + // only idle if the slid isn't back + if (m_iClip != 0) + { + int iAnim; + float flRand = RANDOM_FLOAT(0, 1); + if (flRand <= 0.3 + 0 * 0.75) + { + iAnim = GLOCK_IDLE3; + m_flTimeWeaponIdle = gpGlobals->time + 49.0 / 16; + } + else if (flRand <= 0.6 + 0 * 0.875) + { + iAnim = GLOCK_IDLE1; + m_flTimeWeaponIdle = gpGlobals->time + 60.0 / 16.0; + } + else + { + iAnim = GLOCK_IDLE2; + m_flTimeWeaponIdle = gpGlobals->time + 40.0 / 16.0; + } + SendWeaponAnim( iAnim ); + } +} + + + + + + + + +class CGlockAmmo : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_9mmclip.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_9mmclip.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + if (pOther->GiveAmmo( AMMO_GLOCKCLIP_GIVE, "9mm", _9MM_MAX_CARRY ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + return TRUE; + } + return FALSE; + } +}; +LINK_ENTITY_TO_CLASS( ammo_glockclip, CGlockAmmo ); +LINK_ENTITY_TO_CLASS( ammo_9mmclip, CGlockAmmo ); + + + + + + + + + + + + + + + diff --git a/dlls/gman.cpp b/dlls/gman.cpp new file mode 100644 index 0000000..7549fac --- /dev/null +++ b/dlls/gman.cpp @@ -0,0 +1,237 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// GMan - misunderstood servant of the people +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "weapons.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +class CGMan : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int ISoundMask ( void ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void StartTask( Task_t *pTask ); + void RunTask( Task_t *pTask ); + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + + void PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ); + + EHANDLE m_hPlayer; + EHANDLE m_hTalkTarget; + float m_flTalkTime; +}; +LINK_ENTITY_TO_CLASS( monster_gman, CGMan ); + + +TYPEDESCRIPTION CGMan::m_SaveData[] = +{ + DEFINE_FIELD( CGMan, m_hTalkTarget, FIELD_EHANDLE ), + DEFINE_FIELD( CGMan, m_flTalkTime, FIELD_TIME ), +}; +IMPLEMENT_SAVERESTORE( CGMan, CBaseMonster ); + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CGMan :: Classify ( void ) +{ + return CLASS_NONE; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CGMan :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + default: + ys = 90; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CGMan :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case 0: + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// ISoundMask - generic monster can't hear. +//========================================================= +int CGMan :: ISoundMask ( void ) +{ + return NULL; +} + +//========================================================= +// Spawn +//========================================================= +void CGMan :: Spawn() +{ + Precache(); + + SET_MODEL( ENT(pev), "models/gman.mdl" ); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = DONT_BLEED; + pev->health = 100; + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CGMan :: Precache() +{ + PRECACHE_MODEL( "models/gman.mdl" ); +} + + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + +void CGMan :: StartTask( Task_t *pTask ) +{ + switch( pTask->iTask ) + { + case TASK_WAIT: + if (m_hPlayer == NULL) + { + m_hPlayer = UTIL_FindEntityByClassname( NULL, "player" ); + } + break; + } + CBaseMonster::StartTask( pTask ); +} + +void CGMan :: RunTask( Task_t *pTask ) +{ + switch( pTask->iTask ) + { + case TASK_WAIT: + // look at who I'm talking to + if (m_flTalkTime > gpGlobals->time && m_hTalkTarget != NULL) + { + float yaw = VecToYaw(m_hTalkTarget->pev->origin - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + // turn towards vector + SetBoneController( 0, yaw ); + } + // look at player, but only if playing a "safe" idle animation + else if (m_hPlayer != NULL && pev->sequence == 0) + { + float yaw = VecToYaw(m_hPlayer->pev->origin - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + // turn towards vector + SetBoneController( 0, yaw ); + } + else + { + SetBoneController( 0, 0 ); + } + CBaseMonster::RunTask( pTask ); + break; + default: + SetBoneController( 0, 0 ); + CBaseMonster::RunTask( pTask ); + break; + } +} + + +//========================================================= +// Override all damage +//========================================================= +int CGMan :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + pev->health = pev->max_health / 2; // always trigger the 50% damage aitrigger + + if ( flDamage > 0 ) + { + SetConditions(bits_COND_LIGHT_DAMAGE); + } + + if ( flDamage >= 20 ) + { + SetConditions(bits_COND_HEAVY_DAMAGE); + } + return TRUE; +} + + +void CGMan::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + UTIL_Ricochet( ptr->vecEndPos, 1.0 ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); +} + + +void CGMan::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ) +{ + CBaseMonster::PlayScriptedSentence( pszSentence, duration, volume, attenuation, bConcurrent, pListener ); + + m_flTalkTime = gpGlobals->time + duration; + m_hTalkTarget = pListener; +} diff --git a/dlls/h_ai.cpp b/dlls/h_ai.cpp new file mode 100644 index 0000000..7414312 --- /dev/null +++ b/dlls/h_ai.cpp @@ -0,0 +1,199 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + + h_ai.cpp - halflife specific ai code + +*/ + + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "game.h" + +#define NUM_LATERAL_CHECKS 13 // how many checks are made on each side of a monster looking for lateral cover +#define NUM_LATERAL_LOS_CHECKS 6 // how many checks are made on each side of a monster looking for lateral cover + +//float flRandom = RANDOM_FLOAT(0,1); + +DLL_GLOBAL BOOL g_fDrawLines = FALSE; + +//========================================================= +// +// AI UTILITY FUNCTIONS +// +// !!!UNDONE - move CBaseMonster functions to monsters.cpp +//========================================================= + +//========================================================= +// FBoxVisible - a more accurate ( and slower ) version +// of FVisible. +// +// !!!UNDONE - make this CBaseMonster? +//========================================================= +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget, Vector &vecTargetOrigin, float flSize ) +{ + // don't look through water + if ((pevLooker->waterlevel != 3 && pevTarget->waterlevel == 3) + || (pevLooker->waterlevel == 3 && pevTarget->waterlevel == 0)) + return FALSE; + + TraceResult tr; + Vector vecLookerOrigin = pevLooker->origin + pevLooker->view_ofs;//look through the monster's 'eyes' + for (int i = 0; i < 5; i++) + { + Vector vecTarget = pevTarget->origin; + vecTarget.x += RANDOM_FLOAT( pevTarget->mins.x + flSize, pevTarget->maxs.x - flSize); + vecTarget.y += RANDOM_FLOAT( pevTarget->mins.y + flSize, pevTarget->maxs.y - flSize); + vecTarget.z += RANDOM_FLOAT( pevTarget->mins.z + flSize, pevTarget->maxs.z - flSize); + + UTIL_TraceLine(vecLookerOrigin, vecTarget, ignore_monsters, ignore_glass, ENT(pevLooker)/*pentIgnore*/, &tr); + + if (tr.flFraction == 1.0) + { + vecTargetOrigin = vecTarget; + return TRUE;// line of sight is valid. + } + } + return FALSE;// Line of sight is not established +} + +// +// VecCheckToss - returns the velocity at which an object should be lobbed from vecspot1 to land near vecspot2. +// returns g_vecZero if toss is not feasible. +// +Vector VecCheckToss ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flGravityAdj ) +{ + TraceResult tr; + Vector vecMidPoint;// halfway point between Spot1 and Spot2 + Vector vecApex;// highest point + Vector vecScale; + Vector vecGrenadeVel; + Vector vecTemp; + float flGravity = g_psv_gravity->value * flGravityAdj; + + if (vecSpot2.z - vecSpot1.z > 500) + { + // to high, fail + return g_vecZero; + } + + UTIL_MakeVectors (pev->angles); + + // toss a little bit to the left or right, not right down on the enemy's bean (head). + vecSpot2 = vecSpot2 + gpGlobals->v_right * ( RANDOM_FLOAT(-8,8) + RANDOM_FLOAT(-16,16) ); + vecSpot2 = vecSpot2 + gpGlobals->v_forward * ( RANDOM_FLOAT(-8,8) + RANDOM_FLOAT(-16,16) ); + + // calculate the midpoint and apex of the 'triangle' + // UNDONE: normalize any Z position differences between spot1 and spot2 so that triangle is always RIGHT + + // How much time does it take to get there? + + // get a rough idea of how high it can be thrown + vecMidPoint = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5; + UTIL_TraceLine(vecMidPoint, vecMidPoint + Vector(0,0,500), ignore_monsters, ENT(pev), &tr); + vecMidPoint = tr.vecEndPos; + // (subtract 15 so the grenade doesn't hit the ceiling) + vecMidPoint.z -= 15; + + if (vecMidPoint.z < vecSpot1.z || vecMidPoint.z < vecSpot2.z) + { + // to not enough space, fail + return g_vecZero; + } + + // How high should the grenade travel to reach the apex + float distance1 = (vecMidPoint.z - vecSpot1.z); + float distance2 = (vecMidPoint.z - vecSpot2.z); + + // How long will it take for the grenade to travel this distance + float time1 = sqrt( distance1 / (0.5 * flGravity) ); + float time2 = sqrt( distance2 / (0.5 * flGravity) ); + + if (time1 < 0.1) + { + // too close + return g_vecZero; + } + + // how hard to throw sideways to get there in time. + vecGrenadeVel = (vecSpot2 - vecSpot1) / (time1 + time2); + // how hard upwards to reach the apex at the right time. + vecGrenadeVel.z = flGravity * time1; + + // find the apex + vecApex = vecSpot1 + vecGrenadeVel * time1; + vecApex.z = vecMidPoint.z; + + UTIL_TraceLine(vecSpot1, vecApex, dont_ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + // UNDONE: either ignore monsters or change it to not care if we hit our enemy + UTIL_TraceLine(vecSpot2, vecApex, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + return vecGrenadeVel; +} + + +// +// VecCheckThrow - returns the velocity vector at which an object should be thrown from vecspot1 to hit vecspot2. +// returns g_vecZero if throw is not feasible. +// +Vector VecCheckThrow ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flSpeed, float flGravityAdj ) +{ + float flGravity = g_psv_gravity->value * flGravityAdj; + + Vector vecGrenadeVel = (vecSpot2 - vecSpot1); + + // throw at a constant time + float time = vecGrenadeVel.Length( ) / flSpeed; + vecGrenadeVel = vecGrenadeVel * (1.0 / time); + + // adjust upward toss to compensate for gravity loss + vecGrenadeVel.z += flGravity * time * 0.5; + + Vector vecApex = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5; + vecApex.z += 0.5 * flGravity * (time * 0.5) * (time * 0.5); + + TraceResult tr; + UTIL_TraceLine(vecSpot1, vecApex, dont_ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + UTIL_TraceLine(vecSpot2, vecApex, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + return vecGrenadeVel; +} + + diff --git a/dlls/h_battery.cpp b/dlls/h_battery.cpp new file mode 100644 index 0000000..a464d03 --- /dev/null +++ b/dlls/h_battery.cpp @@ -0,0 +1,200 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== h_battery.cpp ======================================================== + + battery-related code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "skill.h" +#include "gamerules.h" + +class CRecharge : public CBaseToggle +{ +public: + void Spawn( ); + void Precache( void ); + void EXPORT Off(void); + void EXPORT Recharge(void); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return (CBaseToggle :: ObjectCaps() | FCAP_CONTINUOUS_USE) & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flNextCharge; + int m_iReactivate ; // DeathMatch Delay until reactvated + int m_iJuice; + int m_iOn; // 0 = off, 1 = startup, 2 = going + float m_flSoundTime; +}; + +TYPEDESCRIPTION CRecharge::m_SaveData[] = +{ + DEFINE_FIELD( CRecharge, m_flNextCharge, FIELD_TIME ), + DEFINE_FIELD( CRecharge, m_iReactivate, FIELD_INTEGER), + DEFINE_FIELD( CRecharge, m_iJuice, FIELD_INTEGER), + DEFINE_FIELD( CRecharge, m_iOn, FIELD_INTEGER), + DEFINE_FIELD( CRecharge, m_flSoundTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CRecharge, CBaseEntity ); + +LINK_ENTITY_TO_CLASS(func_recharge, CRecharge); + + +void CRecharge::KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + { + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "dmdelay")) + { + m_iReactivate = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CRecharge::Spawn() +{ + Precache( ); + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); // set size and link into world + UTIL_SetSize(pev, pev->mins, pev->maxs); + SET_MODEL(ENT(pev), STRING(pev->model) ); + m_iJuice = gSkillData.suitchargerCapacity; + pev->frame = 0; +} + +void CRecharge::Precache() +{ + PRECACHE_SOUND("items/suitcharge1.wav"); + PRECACHE_SOUND("items/suitchargeno1.wav"); + PRECACHE_SOUND("items/suitchargeok1.wav"); +} + + +void CRecharge::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // if it's not a player, ignore + if (!FClassnameIs(pActivator->pev, "player")) + return; + + // if there is no juice left, turn it off + if (m_iJuice <= 0) + { + pev->frame = 1; + Off(); + } + + // if the player doesn't have the suit, or there is no juice left, make the deny noise + if ((m_iJuice <= 0) || (!(pActivator->pev->weapons & (1<time) + { + m_flSoundTime = gpGlobals->time + 0.62; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/suitchargeno1.wav", 0.85, ATTN_NORM ); + } + return; + } + + pev->nextthink = pev->ltime + 0.25; + SetThink(&CRecharge::Off); + + // Time to recharge yet? + + if (m_flNextCharge >= gpGlobals->time) + return; + + // Make sure that we have a caller + if (!pActivator) + return; + + m_hActivator = pActivator; + + //only recharge the player + + if (!m_hActivator->IsPlayer() ) + return; + + // Play the on sound or the looping charging sound + if (!m_iOn) + { + m_iOn++; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/suitchargeok1.wav", 0.85, ATTN_NORM ); + m_flSoundTime = 0.56 + gpGlobals->time; + } + if ((m_iOn == 1) && (m_flSoundTime <= gpGlobals->time)) + { + m_iOn++; + EMIT_SOUND(ENT(pev), CHAN_STATIC, "items/suitcharge1.wav", 0.85, ATTN_NORM ); + } + + + // charge the player + if (m_hActivator->pev->armorvalue < 100) + { + m_iJuice--; + m_hActivator->pev->armorvalue += 1; + + if (m_hActivator->pev->armorvalue > 100) + m_hActivator->pev->armorvalue = 100; + } + + // govern the rate of charge + m_flNextCharge = gpGlobals->time + 0.1; +} + +void CRecharge::Recharge(void) +{ + m_iJuice = gSkillData.suitchargerCapacity; + pev->frame = 0; + SetThink( &CRecharge::SUB_DoNothing ); +} + +void CRecharge::Off(void) +{ + // Stop looping sound. + if (m_iOn > 1) + STOP_SOUND( ENT(pev), CHAN_STATIC, "items/suitcharge1.wav" ); + + m_iOn = 0; + + if ((!m_iJuice) && ( ( m_iReactivate = g_pGameRules->FlHEVChargerRechargeTime() ) > 0) ) + { + pev->nextthink = pev->ltime + m_iReactivate; + SetThink(&CRecharge::Recharge); + } + else + SetThink( &CRecharge::SUB_DoNothing ); +} diff --git a/dlls/h_cine.cpp b/dlls/h_cine.cpp new file mode 100644 index 0000000..d13aba6 --- /dev/null +++ b/dlls/h_cine.cpp @@ -0,0 +1,241 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +/* + +===== h_cine.cpp ======================================================== + + The Halflife hard coded "scripted sequence". + + I'm pretty sure all this code is obsolete + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "decals.h" + + +class CLegacyCineMonster : public CBaseMonster +{ +public: + void CineSpawn( char *szModel ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT CineThink( void ); + void Pain( void ); + void Die( void ); +}; + +class CCineScientist : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine-scientist.mdl"); } +}; +class CCine2Scientist : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine2-scientist.mdl"); } +}; +class CCinePanther : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine-panther.mdl"); } +}; + +class CCineBarney : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine-barney.mdl"); } +}; + +class CCine2HeavyWeapons : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine2_hvyweapons.mdl"); } +}; + +class CCine2Slave : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine2_slave.mdl"); } +}; + +class CCine3Scientist : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine3-scientist.mdl"); } +}; + +class CCine3Barney : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine3-barney.mdl"); } +}; + +// +// ********** Scientist SPAWN ********** +// + +LINK_ENTITY_TO_CLASS( monster_cine_scientist, CCineScientist ); +LINK_ENTITY_TO_CLASS( monster_cine_panther, CCinePanther ); +LINK_ENTITY_TO_CLASS( monster_cine_barney, CCineBarney ); +LINK_ENTITY_TO_CLASS( monster_cine2_scientist, CCine2Scientist ); +LINK_ENTITY_TO_CLASS( monster_cine2_hvyweapons, CCine2HeavyWeapons ); +LINK_ENTITY_TO_CLASS( monster_cine2_slave, CCine2Slave ); +LINK_ENTITY_TO_CLASS( monster_cine3_scientist, CCine3Scientist ); +LINK_ENTITY_TO_CLASS( monster_cine3_barney, CCine3Barney ); + +// +// ********** Scientist SPAWN ********** +// + +void CLegacyCineMonster :: CineSpawn( char *szModel ) +{ + PRECACHE_MODEL(szModel); + SET_MODEL(ENT(pev), szModel); + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 64)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + pev->effects = 0; + pev->health = 1; + pev->yaw_speed = 10; + + // ugly alpha hack, can't set ints from the bsp. + pev->sequence = (int)pev->impulse; + ResetSequenceInfo( ); + pev->framerate = 0.0; + + m_bloodColor = BLOOD_COLOR_RED; + + // if no targetname, start now + if ( FStringNull(pev->targetname) ) + { + SetThink( &CLegacyCineMonster::CineThink ); + pev->nextthink += 1.0; + } +} + + +// +// CineStart +// +void CLegacyCineMonster :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + pev->animtime = 0; // reset the sequence + SetThink( &CLegacyCineMonster::CineThink ); + pev->nextthink = gpGlobals->time; +} + +// +// ********** Scientist DIE ********** +// +void CLegacyCineMonster :: Die( void ) +{ + SetThink( &CLegacyCineMonster::SUB_Remove ); +} + +// +// ********** Scientist PAIN ********** +// +void CLegacyCineMonster :: Pain( void ) +{ + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pain3.wav", 1, ATTN_NORM); +} + +void CLegacyCineMonster :: CineThink( void ) +{ + // DBG_CheckMonsterData(pev); + + // Emit particles from origin (double check animator's placement of model) + // THIS is a test feature + //UTIL_ParticleEffect(pev->origin, g_vecZero, 255, 20); + + if (!pev->animtime) + ResetSequenceInfo( ); + + pev->nextthink = gpGlobals->time + 1.0; + + if (pev->spawnflags != 0 && m_fSequenceFinished) + { + Die(); + return; + } + + StudioFrameAdvance ( ); +} + +// +// cine_blood +// +// e3/prealpha only. +class CCineBlood : public CBaseEntity +{ +public: + void Spawn( void ); + void EXPORT BloodStart ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT BloodGush ( void ); +}; + +LINK_ENTITY_TO_CLASS( cine_blood, CCineBlood ); + + +void CCineBlood :: BloodGush ( void ) +{ + Vector vecSplatDir; + TraceResult tr; + pev->nextthink = gpGlobals->time + 0.1; + + UTIL_MakeVectors(pev->angles); + if ( pev->health-- < 0 ) + REMOVE_ENTITY(ENT(pev)); +// CHANGE_METHOD ( ENT(pev), em_think, SUB_Remove ); + + if ( RANDOM_FLOAT ( 0 , 1 ) < 0.7 )// larger chance of globs + { + UTIL_BloodDrips( pev->origin, UTIL_RandomBloodVector(), BLOOD_COLOR_RED, 10 ); + } + else// slim chance of geyser + { + UTIL_BloodStream( pev->origin, UTIL_RandomBloodVector(), BLOOD_COLOR_RED, RANDOM_LONG(50, 150) ); + } + + if ( RANDOM_FLOAT ( 0, 1 ) < 0.75 ) + {// decals the floor with blood. + vecSplatDir = Vector ( 0 , 0 , -1 ); + vecSplatDir = vecSplatDir + (RANDOM_FLOAT(-1,1) * 0.6 * gpGlobals->v_right) + (RANDOM_FLOAT(-1,1) * 0.6 * gpGlobals->v_forward);// randomize a bit + UTIL_TraceLine( pev->origin + Vector ( 0, 0 , 64) , pev->origin + vecSplatDir * 256, ignore_monsters, ENT(pev), &tr); + if ( tr.flFraction != 1.0 ) + { + // Decal with a bloodsplat + UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_RED ); + } + } +} + +void CCineBlood :: BloodStart ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( &CCineBlood::BloodGush ); + pev->nextthink = gpGlobals->time;// now! +} + +void CCineBlood :: Spawn ( void ) +{ + pev->solid = SOLID_NOT; + SetUse ( &CCineBlood::BloodStart ); + pev->health = 20;//hacked health to count iterations +} + diff --git a/dlls/h_cycler.cpp b/dlls/h_cycler.cpp new file mode 100644 index 0000000..4449805 --- /dev/null +++ b/dlls/h_cycler.cpp @@ -0,0 +1,471 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== h_cycler.cpp ======================================================== + + The Halflife Cycler Monsters + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "animation.h" +#include "weapons.h" +#include "player.h" + + +#define TEMP_FOR_SCREEN_SHOTS +#ifdef TEMP_FOR_SCREEN_SHOTS //=================================================== + +class CCycler : public CBaseMonster +{ +public: + void GenericCyclerSpawn(char *szModel, Vector vecMin, Vector vecMax); + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() | FCAP_IMPULSE_USE); } + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void Spawn( void ); + void Think( void ); + //void Pain( float flDamage ); + void Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + // Don't treat as a live target + virtual BOOL IsAlive( void ) { return FALSE; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_animate; +}; + +TYPEDESCRIPTION CCycler::m_SaveData[] = +{ + DEFINE_FIELD( CCycler, m_animate, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CCycler, CBaseMonster ); + + +// +// we should get rid of all the other cyclers and replace them with this. +// +class CGenericCycler : public CCycler +{ +public: + void Spawn( void ) { GenericCyclerSpawn( (char *)STRING(pev->model), Vector(-16, -16, 0), Vector(16, 16, 72) ); } +}; +LINK_ENTITY_TO_CLASS( cycler, CGenericCycler ); + + + +// Probe droid imported for tech demo compatibility +// +// PROBE DROID +// +class CCyclerProbe : public CCycler +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( cycler_prdroid, CCyclerProbe ); +void CCyclerProbe :: Spawn( void ) +{ + pev->origin = pev->origin + Vector ( 0, 0, 16 ); + GenericCyclerSpawn( "models/prdroid.mdl", Vector(-16,-16,-16), Vector(16,16,16)); +} + + + +// Cycler member functions + +void CCycler :: GenericCyclerSpawn(char *szModel, Vector vecMin, Vector vecMax) +{ + if (!szModel || !*szModel) + { + ALERT(at_error, "cycler at %.0f %.0f %0.f missing modelname", pev->origin.x, pev->origin.y, pev->origin.z ); + REMOVE_ENTITY(ENT(pev)); + return; + } + + pev->classname = MAKE_STRING("cycler"); + PRECACHE_MODEL( szModel ); + SET_MODEL(ENT(pev), szModel); + + CCycler::Spawn( ); + + UTIL_SetSize(pev, vecMin, vecMax); +} + + +void CCycler :: Spawn( ) +{ + InitBoneControllers(); + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_NONE; + pev->takedamage = DAMAGE_YES; + pev->effects = 0; + pev->health = 80000;// no cycler should die + pev->yaw_speed = 5; + pev->ideal_yaw = pev->angles.y; + ChangeYaw( 360 ); + + m_flFrameRate = 75; + m_flGroundSpeed = 0; + + pev->nextthink += 1.0; + + ResetSequenceInfo( ); + + if (pev->sequence != 0 || pev->frame != 0) + { + m_animate = 0; + pev->framerate = 0; + } + else + { + m_animate = 1; + } +} + + + + +// +// cycler think +// +void CCycler :: Think( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if (m_animate) + { + StudioFrameAdvance ( ); + } + if (m_fSequenceFinished && !m_fSequenceLoops) + { + // ResetSequenceInfo(); + // hack to avoid reloading model every frame + pev->animtime = gpGlobals->time; + pev->framerate = 1.0; + m_fSequenceFinished = FALSE; + m_flLastEventCheck = gpGlobals->time; + pev->frame = 0; + if (!m_animate) + pev->framerate = 0.0; // FIX: don't reset framerate + } +} + +// +// CyclerUse - starts a rotation trend +// +void CCycler :: Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_animate = !m_animate; + if (m_animate) + pev->framerate = 1.0; + else + pev->framerate = 0.0; +} + +// +// CyclerPain , changes sequences when shot +// +//void CCycler :: Pain( float flDamage ) +int CCycler :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if (m_animate) + { + pev->sequence++; + + ResetSequenceInfo( ); + + if (m_flFrameRate == 0.0) + { + pev->sequence = 0; + ResetSequenceInfo( ); + } + pev->frame = 0; + } + else + { + pev->framerate = 1.0; + StudioFrameAdvance ( 0.1 ); + pev->framerate = 0; + ALERT( at_console, "sequence: %d, frame %.0f\n", pev->sequence, pev->frame ); + } + + return 0; +} + +#endif + + +class CCyclerSprite : public CBaseEntity +{ +public: + void Spawn( void ); + void Think( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() | FCAP_DONT_SAVE | FCAP_IMPULSE_USE); } + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void Animate( float frames ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + inline int ShouldAnimate( void ) { return m_animate && m_maxFrame > 1.0; } + int m_animate; + float m_lastTime; + float m_maxFrame; +}; + +LINK_ENTITY_TO_CLASS( cycler_sprite, CCyclerSprite ); + +TYPEDESCRIPTION CCyclerSprite::m_SaveData[] = +{ + DEFINE_FIELD( CCyclerSprite, m_animate, FIELD_INTEGER ), + DEFINE_FIELD( CCyclerSprite, m_lastTime, FIELD_TIME ), + DEFINE_FIELD( CCyclerSprite, m_maxFrame, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CCyclerSprite, CBaseEntity ); + + +void CCyclerSprite::Spawn( void ) +{ + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_NONE; + pev->takedamage = DAMAGE_YES; + pev->effects = 0; + + pev->frame = 0; + pev->nextthink = gpGlobals->time + 0.1; + m_animate = 1; + m_lastTime = gpGlobals->time; + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; +} + + +void CCyclerSprite::Think( void ) +{ + if ( ShouldAnimate() ) + Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); + + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; +} + + +void CCyclerSprite::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_animate = !m_animate; + ALERT( at_console, "Sprite: %s\n", STRING(pev->model) ); +} + + +int CCyclerSprite::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( m_maxFrame > 1.0 ) + { + Animate( 1.0 ); + } + return 1; +} + +void CCyclerSprite::Animate( float frames ) +{ + pev->frame += frames; + if ( m_maxFrame > 0 ) + pev->frame = fmod( pev->frame, m_maxFrame ); +} + + + + + + + +class CWeaponCycler : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + int iItemSlot( void ) { return 1; } + int GetItemInfo(ItemInfo *p) {return 0; } + + void PrimaryAttack( void ); + void SecondaryAttack( void ); + BOOL Deploy( void ); + void Holster( int skiplocal = 0 ); + int m_iszModel; + int m_iModel; +}; +LINK_ENTITY_TO_CLASS( cycler_weapon, CWeaponCycler ); + + +void CWeaponCycler::Spawn( ) +{ + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_NONE; + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL( ENT(pev), STRING(pev->model) ); + m_iszModel = pev->model; + m_iModel = pev->modelindex; + + UTIL_SetOrigin( pev, pev->origin ); + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 16)); + SetTouch( &CWeaponCycler::DefaultTouch ); +} + + + +BOOL CWeaponCycler::Deploy( ) +{ + m_pPlayer->pev->viewmodel = m_iszModel; + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.0; + SendWeaponAnim( 0 ); + m_iClip = 0; + return TRUE; +} + + +void CWeaponCycler::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; +} + + +void CWeaponCycler::PrimaryAttack() +{ + + SendWeaponAnim( pev->sequence ); + + m_flNextPrimaryAttack = gpGlobals->time + 0.3; +} + + +void CWeaponCycler::SecondaryAttack( void ) +{ + float flFrameRate, flGroundSpeed; + + pev->sequence = (pev->sequence + 1) % 8; + + pev->modelindex = m_iModel; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + GetSequenceInfo( pmodel, pev, &flFrameRate, &flGroundSpeed ); + pev->modelindex = 0; + + if (flFrameRate == 0.0) + { + pev->sequence = 0; + } + + SendWeaponAnim( pev->sequence ); + + m_flNextSecondaryAttack = gpGlobals->time + 0.3; +} + + + +// Flaming Wreakage +class CWreckage : public CBaseMonster +{ + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + void Think( void ); + + int m_flStartTime; +}; +TYPEDESCRIPTION CWreckage::m_SaveData[] = +{ + DEFINE_FIELD( CWreckage, m_flStartTime, FIELD_TIME ), +}; +IMPLEMENT_SAVERESTORE( CWreckage, CBaseMonster ); + + +LINK_ENTITY_TO_CLASS( cycler_wreckage, CWreckage ); + +void CWreckage::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->takedamage = 0; + pev->effects = 0; + + pev->frame = 0; + pev->nextthink = gpGlobals->time + 0.1; + + if (pev->model) + { + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL( ENT(pev), STRING(pev->model) ); + } + // pev->scale = 5.0; + + m_flStartTime = gpGlobals->time; +} + +void CWreckage::Precache( ) +{ + if ( pev->model ) + PRECACHE_MODEL( (char *)STRING(pev->model) ); +} + +void CWreckage::Think( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.2; + + if (pev->dmgtime) + { + if (pev->dmgtime < gpGlobals->time) + { + UTIL_Remove( this ); + return; + } + else if (RANDOM_FLOAT( 0, pev->dmgtime - m_flStartTime ) > pev->dmgtime - gpGlobals->time) + { + return; + } + } + + Vector VecSrc; + + VecSrc.x = RANDOM_FLOAT( pev->absmin.x, pev->absmax.x ); + VecSrc.y = RANDOM_FLOAT( pev->absmin.y, pev->absmax.y ); + VecSrc.z = RANDOM_FLOAT( pev->absmin.z, pev->absmax.z ); + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, VecSrc ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( VecSrc.x ); + WRITE_COORD( VecSrc.y ); + WRITE_COORD( VecSrc.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( RANDOM_LONG(0,49) + 50 ); // scale * 10 + WRITE_BYTE( RANDOM_LONG(0, 3) + 8 ); // framerate + MESSAGE_END(); +} diff --git a/dlls/h_export.cpp b/dlls/h_export.cpp new file mode 100644 index 0000000..a4d1f03 --- /dev/null +++ b/dlls/h_export.cpp @@ -0,0 +1,63 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== h_export.cpp ======================================================== + + Entity classes exported by Halflife. + +*/ + +#include "extdll.h" +#include "util.h" + +#include "cbase.h" + +// Holds engine functionality callbacks +enginefuncs_t g_engfuncs; +globalvars_t *gpGlobals; + +#undef DLLEXPORT +#ifdef _WIN32 +#define DLLEXPORT __stdcall +#else +#define DLLEXPORT __attribute__ ((visibility("default"))) +#endif + +#ifdef _WIN32 + +// Required DLL entry point +BOOL WINAPI DllMain( + HINSTANCE hinstDLL, + DWORD fdwReason, + LPVOID lpvReserved) +{ + if (fdwReason == DLL_PROCESS_ATTACH) + { + } + else if (fdwReason == DLL_PROCESS_DETACH) + { + } + return TRUE; +} +#endif + +extern "C" void DLLEXPORT GiveFnptrsToDll( enginefuncs_t* pengfuncsFromEngine, globalvars_t *pGlobals ) +{ + memcpy(&g_engfuncs, pengfuncsFromEngine, sizeof(enginefuncs_t)); + gpGlobals = pGlobals; +} + + diff --git a/dlls/handgrenade.cpp b/dlls/handgrenade.cpp new file mode 100644 index 0000000..a854763 --- /dev/null +++ b/dlls/handgrenade.cpp @@ -0,0 +1,233 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" + + +#define HANDGRENADE_PRIMARY_VOLUME 450 + +enum handgrenade_e { + HANDGRENADE_IDLE = 0, + HANDGRENADE_FIDGET, + HANDGRENADE_PINPULL, + HANDGRENADE_THROW1, // toss + HANDGRENADE_THROW2, // medium + HANDGRENADE_THROW3, // hard + HANDGRENADE_HOLSTER, + HANDGRENADE_DRAW +}; + + +LINK_ENTITY_TO_CLASS( weapon_handgrenade, CHandGrenade ); + + +void CHandGrenade::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_HANDGRENADE; + SET_MODEL(ENT(pev), "models/w_grenade.mdl"); + +#ifndef CLIENT_DLL + pev->dmg = gSkillData.plrDmgHandGrenade; +#endif + + m_iDefaultAmmo = HANDGRENADE_DEFAULT_GIVE; + + FallInit();// get ready to fall down. +} + + +void CHandGrenade::Precache( void ) +{ + PRECACHE_MODEL("models/w_grenade.mdl"); + PRECACHE_MODEL("models/v_grenade.mdl"); + PRECACHE_MODEL("models/p_grenade.mdl"); +} + +int CHandGrenade::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "Hand Grenade"; + p->iMaxAmmo1 = HANDGRENADE_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 4; + p->iPosition = 0; + p->iId = m_iId = WEAPON_HANDGRENADE; + p->iWeight = HANDGRENADE_WEIGHT; + p->iFlags = ITEM_FLAG_LIMITINWORLD | ITEM_FLAG_EXHAUSTIBLE; + + return 1; +} + + +BOOL CHandGrenade::Deploy( ) +{ + m_flReleaseThrow = -1; + return DefaultDeploy( "models/v_grenade.mdl", "models/p_grenade.mdl", HANDGRENADE_DRAW, "crowbar" ); +} + +BOOL CHandGrenade::CanHolster( void ) +{ + // can only holster hand grenades when not primed! + return ( m_flStartThrow == 0 ); +} + +void CHandGrenade::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + + if ( m_pPlayer->m_rgAmmo[ m_iPrimaryAmmoType ] ) + { + SendWeaponAnim( HANDGRENADE_HOLSTER ); + } + else + { + // no more grenades! + m_pPlayer->pev->weapons &= ~(1<nextthink = gpGlobals->time + 0.1; + } + + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM); +} + +void CHandGrenade::PrimaryAttack() +{ + if ( !m_flStartThrow && m_pPlayer->m_rgAmmo[ m_iPrimaryAmmoType ] > 0 ) + { + m_flStartThrow = gpGlobals->time; + m_flReleaseThrow = 0; + + SendWeaponAnim( HANDGRENADE_PINPULL ); + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.5; + } +} + + +void CHandGrenade::WeaponIdle( void ) +{ + if ( m_flReleaseThrow == 0 && m_flStartThrow ) + m_flReleaseThrow = gpGlobals->time; + + if ( m_flTimeWeaponIdle > UTIL_WeaponTimeBase() ) + return; + + if ( m_flStartThrow ) + { + Vector angThrow = m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle; + + if ( angThrow.x < 0 ) + angThrow.x = -10 + angThrow.x * ( ( 90 - 10 ) / 90.0 ); + else + angThrow.x = -10 + angThrow.x * ( ( 90 + 10 ) / 90.0 ); + + float flVel = ( 90 - angThrow.x ) * 4; + if ( flVel > 500 ) + flVel = 500; + + UTIL_MakeVectors( angThrow ); + + Vector vecSrc = m_pPlayer->pev->origin + m_pPlayer->pev->view_ofs + gpGlobals->v_forward * 16; + + Vector vecThrow = gpGlobals->v_forward * flVel + m_pPlayer->pev->velocity; + + // alway explode 3 seconds after the pin was pulled + float time = m_flStartThrow - gpGlobals->time + 3.0; + if (time < 0) + time = 0; + + CGrenade::ShootTimed( m_pPlayer->pev, vecSrc, vecThrow, time ); + + if ( flVel < 500 ) + { + SendWeaponAnim( HANDGRENADE_THROW1 ); + } + else if ( flVel < 1000 ) + { + SendWeaponAnim( HANDGRENADE_THROW2 ); + } + else + { + SendWeaponAnim( HANDGRENADE_THROW3 ); + } + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_flReleaseThrow = 0; + m_flStartThrow = 0; + m_flNextPrimaryAttack = GetNextAttackDelay(0.5); + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.5; + + m_pPlayer->m_rgAmmo[ m_iPrimaryAmmoType ]--; + + if ( !m_pPlayer->m_rgAmmo[ m_iPrimaryAmmoType ] ) + { + // just threw last grenade + // set attack times in the future, and weapon idle in the future so we can see the whole throw + // animation, weapon idle will automatically retire the weapon for us. + m_flTimeWeaponIdle = m_flNextSecondaryAttack = m_flNextPrimaryAttack = GetNextAttackDelay(0.5);// ensure that the animation can finish playing + } + return; + } + else if ( m_flReleaseThrow > 0 ) + { + // we've finished the throw, restart. + m_flStartThrow = 0; + + if ( m_pPlayer->m_rgAmmo[ m_iPrimaryAmmoType ] ) + { + SendWeaponAnim( HANDGRENADE_DRAW ); + } + else + { + RetireWeapon(); + return; + } + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + m_flReleaseThrow = -1; + return; + } + + if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] ) + { + int iAnim; + float flRand = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0, 1 ); + if (flRand <= 0.75) + { + iAnim = HANDGRENADE_IDLE; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 );// how long till we do this again. + } + else + { + iAnim = HANDGRENADE_FIDGET; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 75.0 / 30.0; + } + + SendWeaponAnim( iAnim ); + } +} + + + + diff --git a/dlls/hassassin.cpp b/dlls/hassassin.cpp new file mode 100644 index 0000000..0ab2591 --- /dev/null +++ b/dlls/hassassin.cpp @@ -0,0 +1,1015 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +//========================================================= +// hassassin - Human assassin, fast and stealthy +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "squadmonster.h" +#include "weapons.h" +#include "soundent.h" +#include "game.h" + +extern DLL_GLOBAL int g_iSkillLevel; + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_ASSASSIN_EXPOSED = LAST_COMMON_SCHEDULE + 1,// cover was blown. + SCHED_ASSASSIN_JUMP, // fly through the air + SCHED_ASSASSIN_JUMP_ATTACK, // fly through the air and shoot + SCHED_ASSASSIN_JUMP_LAND, // hit and run away +}; + +//========================================================= +// monster-specific tasks +//========================================================= + +enum +{ + TASK_ASSASSIN_FALL_TO_GROUND = LAST_COMMON_TASK + 1, // falling and waiting to hit ground +}; + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define ASSASSIN_AE_SHOOT1 1 +#define ASSASSIN_AE_TOSS1 2 +#define ASSASSIN_AE_JUMP 3 + + +#define bits_MEMORY_BADJUMP (bits_MEMORY_CUSTOM1) + +class CHAssassin : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed ( void ); + int Classify ( void ); + int ISoundMask ( void); + void Shoot( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + Schedule_t* GetSchedule ( void ); + Schedule_t* GetScheduleOfType ( int Type ); + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); // jump + // BOOL CheckMeleeAttack2 ( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); // shoot + BOOL CheckRangeAttack2 ( float flDot, float flDist ); // throw grenade + void StartTask ( Task_t *pTask ); + void RunAI( void ); + void RunTask ( Task_t *pTask ); + void DeathSound ( void ); + void IdleSound ( void ); + CUSTOM_SCHEDULES; + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + float m_flLastShot; + float m_flDiviation; + + float m_flNextJump; + Vector m_vecJumpVelocity; + + float m_flNextGrenadeCheck; + Vector m_vecTossVelocity; + BOOL m_fThrowGrenade; + + int m_iTargetRanderamt; + + int m_iFrustration; + + int m_iShell; +}; +LINK_ENTITY_TO_CLASS( monster_human_assassin, CHAssassin ); + + +TYPEDESCRIPTION CHAssassin::m_SaveData[] = +{ + DEFINE_FIELD( CHAssassin, m_flLastShot, FIELD_TIME ), + DEFINE_FIELD( CHAssassin, m_flDiviation, FIELD_FLOAT ), + + DEFINE_FIELD( CHAssassin, m_flNextJump, FIELD_TIME ), + DEFINE_FIELD( CHAssassin, m_vecJumpVelocity, FIELD_VECTOR ), + + DEFINE_FIELD( CHAssassin, m_flNextGrenadeCheck, FIELD_TIME ), + DEFINE_FIELD( CHAssassin, m_vecTossVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CHAssassin, m_fThrowGrenade, FIELD_BOOLEAN ), + + DEFINE_FIELD( CHAssassin, m_iTargetRanderamt, FIELD_INTEGER ), + DEFINE_FIELD( CHAssassin, m_iFrustration, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CHAssassin, CBaseMonster ); + + +//========================================================= +// DieSound +//========================================================= +void CHAssassin :: DeathSound ( void ) +{ +} + +//========================================================= +// IdleSound +//========================================================= +void CHAssassin :: IdleSound ( void ) +{ +} + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. +//========================================================= +int CHAssassin :: ISoundMask ( void) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_DANGER | + bits_SOUND_PLAYER; +} + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CHAssassin :: Classify ( void ) +{ + return CLASS_HUMAN_MILITARY; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CHAssassin :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 360; + break; + default: + ys = 360; + break; + } + + pev->yaw_speed = ys; +} + + +//========================================================= +// Shoot +//========================================================= +void CHAssassin :: Shoot ( void ) +{ + if (m_hEnemy == NULL) + { + return; + } + + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + if (m_flLastShot + 2 < gpGlobals->time) + { + m_flDiviation = 0.10; + } + else + { + m_flDiviation -= 0.01; + if (m_flDiviation < 0.02) + m_flDiviation = 0.02; + } + m_flLastShot = gpGlobals->time; + + UTIL_MakeVectors ( pev->angles ); + + Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT(40,90) + gpGlobals->v_up * RANDOM_FLOAT(75,200) + gpGlobals->v_forward * RANDOM_FLOAT(-40, 40); + EjectBrass ( pev->origin + gpGlobals->v_up * 32 + gpGlobals->v_forward * 12, vecShellVelocity, pev->angles.y, m_iShell, TE_BOUNCE_SHELL); + FireBullets(1, vecShootOrigin, vecShootDir, Vector( m_flDiviation, m_flDiviation, m_flDiviation ), 2048, BULLET_MONSTER_9MM ); // shoot +-8 degrees + + switch(RANDOM_LONG(0,1)) + { + case 0: + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/pl_gun1.wav", RANDOM_FLOAT(0.6, 0.8), ATTN_NORM); + break; + case 1: + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/pl_gun2.wav", RANDOM_FLOAT(0.6, 0.8), ATTN_NORM); + break; + } + + pev->effects |= EF_MUZZLEFLASH; + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); + + m_cAmmoLoaded--; +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CHAssassin :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case ASSASSIN_AE_SHOOT1: + Shoot( ); + break; + case ASSASSIN_AE_TOSS1: + { + UTIL_MakeVectors( pev->angles ); + CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 34 + Vector (0, 0, 32), m_vecTossVelocity, 2.0 ); + + m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. + m_fThrowGrenade = FALSE; + // !!!LATER - when in a group, only try to throw grenade if ordered. + } + break; + case ASSASSIN_AE_JUMP: + { + // ALERT( at_console, "jumping"); + UTIL_MakeAimVectors( pev->angles ); + pev->movetype = MOVETYPE_TOSS; + pev->flags &= ~FL_ONGROUND; + pev->velocity = m_vecJumpVelocity; + m_flNextJump = gpGlobals->time + 3.0; + } + return; + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CHAssassin :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/hassassin.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->effects = 0; + pev->health = gSkillData.hassassinHealth; + m_flFieldOfView = VIEW_FIELD_WIDE; // indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_afCapability = bits_CAP_MELEE_ATTACK1 | bits_CAP_DOORS_GROUP; + pev->friction = 1; + + m_HackedGunPos = Vector( 0, 24, 48 ); + + m_iTargetRanderamt = 20; + pev->renderamt = 20; + pev->rendermode = kRenderTransTexture; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CHAssassin :: Precache() +{ + PRECACHE_MODEL("models/hassassin.mdl"); + + PRECACHE_SOUND("weapons/pl_gun1.wav"); + PRECACHE_SOUND("weapons/pl_gun2.wav"); + + PRECACHE_SOUND("debris/beamstart1.wav"); + + m_iShell = PRECACHE_MODEL ("models/shell.mdl");// brass shell +} + + + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +//========================================================= +// Fail Schedule +//========================================================= +Task_t tlAssassinFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + // { TASK_WAIT_PVS, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_CHASE_ENEMY }, +}; + +Schedule_t slAssassinFail[] = +{ + { + tlAssassinFail, + ARRAYSIZE ( tlAssassinFail ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER | + bits_SOUND_PLAYER, + "AssassinFail" + }, +}; + + +//========================================================= +// Enemy exposed Agrunt's cover +//========================================================= +Task_t tlAssassinExposed[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_ASSASSIN_JUMP }, + { TASK_SET_SCHEDULE, (float)SCHED_TAKE_COVER_FROM_ENEMY }, +}; + +Schedule_t slAssassinExposed[] = +{ + { + tlAssassinExposed, + ARRAYSIZE ( tlAssassinExposed ), + bits_COND_CAN_MELEE_ATTACK1, + 0, + "AssassinExposed", + }, +}; + + +//========================================================= +// Take cover from enemy! Tries lateral cover before node +// cover! +//========================================================= +Task_t tlAssassinTakeCoverFromEnemy[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_WAIT, (float)0.2 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RANGE_ATTACK1 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, +}; + +Schedule_t slAssassinTakeCoverFromEnemy[] = +{ + { + tlAssassinTakeCoverFromEnemy, + ARRAYSIZE ( tlAssassinTakeCoverFromEnemy ), + bits_COND_NEW_ENEMY | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "AssassinTakeCoverFromEnemy" + }, +}; + + +//========================================================= +// Take cover from enemy! Tries lateral cover before node +// cover! +//========================================================= +Task_t tlAssassinTakeCoverFromEnemy2[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_WAIT, (float)0.2 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RANGE_ATTACK2 }, + { TASK_FIND_FAR_NODE_COVER_FROM_ENEMY, (float)384 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, +}; + +Schedule_t slAssassinTakeCoverFromEnemy2[] = +{ + { + tlAssassinTakeCoverFromEnemy2, + ARRAYSIZE ( tlAssassinTakeCoverFromEnemy2 ), + bits_COND_NEW_ENEMY | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "AssassinTakeCoverFromEnemy2" + }, +}; + + +//========================================================= +// hide from the loudest sound source +//========================================================= +Task_t tlAssassinTakeCoverFromBestSound[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_MELEE_ATTACK1 }, + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_BEST_SOUND, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_TURN_LEFT, (float)179 }, +}; + +Schedule_t slAssassinTakeCoverFromBestSound[] = +{ + { + tlAssassinTakeCoverFromBestSound, + ARRAYSIZE ( tlAssassinTakeCoverFromBestSound ), + bits_COND_NEW_ENEMY, + 0, + "AssassinTakeCoverFromBestSound" + }, +}; + + + + + +//========================================================= +// AlertIdle Schedules +//========================================================= +Task_t tlAssassinHide[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_SET_SCHEDULE, (float)SCHED_CHASE_ENEMY }, +}; + +Schedule_t slAssassinHide[] = +{ + { + tlAssassinHide, + ARRAYSIZE ( tlAssassinHide ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_ENEMY | + bits_COND_SEE_FEAR | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "AssassinHide" + }, +}; + + + +//========================================================= +// HUNT Schedules +//========================================================= +Task_t tlAssassinHunt[] = +{ + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slAssassinHunt[] = +{ + { + tlAssassinHunt, + ARRAYSIZE ( tlAssassinHunt ), + bits_COND_NEW_ENEMY | + // bits_COND_SEE_ENEMY | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "AssassinHunt" + }, +}; + + +//========================================================= +// Jumping Schedules +//========================================================= +Task_t tlAssassinJump[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_HOP }, + { TASK_SET_SCHEDULE, (float)SCHED_ASSASSIN_JUMP_ATTACK }, +}; + +Schedule_t slAssassinJump[] = +{ + { + tlAssassinJump, + ARRAYSIZE ( tlAssassinJump ), + 0, + 0, + "AssassinJump" + }, +}; + + +//========================================================= +// repel +//========================================================= +Task_t tlAssassinJumpAttack[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_ASSASSIN_JUMP_LAND }, + // { TASK_SET_ACTIVITY, (float)ACT_FLY }, + { TASK_ASSASSIN_FALL_TO_GROUND, (float)0 }, +}; + + +Schedule_t slAssassinJumpAttack[] = +{ + { + tlAssassinJumpAttack, + ARRAYSIZE ( tlAssassinJumpAttack ), + 0, + 0, + "AssassinJumpAttack" + }, +}; + + +//========================================================= +// repel +//========================================================= +Task_t tlAssassinJumpLand[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_ASSASSIN_EXPOSED }, + // { TASK_SET_FAIL_SCHEDULE, (float)SCHED_MELEE_ATTACK1 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_REMEMBER, (float)bits_MEMORY_BADJUMP }, + { TASK_FIND_NODE_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_FORGET, (float)bits_MEMORY_BADJUMP }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RANGE_ATTACK1 }, +}; + +Schedule_t slAssassinJumpLand[] = +{ + { + tlAssassinJumpLand, + ARRAYSIZE ( tlAssassinJumpLand ), + 0, + 0, + "AssassinJumpLand" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CHAssassin ) +{ + slAssassinFail, + slAssassinExposed, + slAssassinTakeCoverFromEnemy, + slAssassinTakeCoverFromEnemy2, + slAssassinTakeCoverFromBestSound, + slAssassinHide, + slAssassinHunt, + slAssassinJump, + slAssassinJumpAttack, + slAssassinJumpLand, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CHAssassin, CBaseMonster ); + + +//========================================================= +// CheckMeleeAttack1 - jump like crazy if the enemy gets too close. +//========================================================= +BOOL CHAssassin :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + if ( m_flNextJump < gpGlobals->time && (flDist <= 128 || HasMemory( bits_MEMORY_BADJUMP )) && m_hEnemy != NULL ) + { + TraceResult tr; + + Vector vecDest = pev->origin + Vector( RANDOM_FLOAT( -64, 64), RANDOM_FLOAT( -64, 64 ), 160 ); + + UTIL_TraceHull( pev->origin + Vector( 0, 0, 36 ), vecDest + Vector( 0, 0, 36 ), dont_ignore_monsters, human_hull, ENT(pev), &tr); + + if ( tr.fStartSolid || tr.flFraction < 1.0) + { + return FALSE; + } + + float flGravity = g_psv_gravity->value; + + float time = sqrt( 160 / (0.5 * flGravity)); + float speed = flGravity * time / 160; + m_vecJumpVelocity = (vecDest - pev->origin) * speed; + + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack1 - drop a cap in their ass +// +//========================================================= +BOOL CHAssassin :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( !HasConditions( bits_COND_ENEMY_OCCLUDED ) && flDist > 64 && flDist <= 2048 /* && flDot >= 0.5 */ /* && NoFriendlyFire() */ ) + { + TraceResult tr; + + Vector vecSrc = GetGunPosition(); + + // verify that a bullet fired from the gun will hit the enemy before the world. + UTIL_TraceLine( vecSrc, m_hEnemy->BodyTarget(vecSrc), dont_ignore_monsters, ENT(pev), &tr); + + if ( tr.flFraction == 1 || tr.pHit == m_hEnemy->edict() ) + { + return TRUE; + } + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 - toss grenade is enemy gets in the way and is too close. +//========================================================= +BOOL CHAssassin :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + m_fThrowGrenade = FALSE; + if ( !FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ) ) + { + // don't throw grenades at anything that isn't on the ground! + return FALSE; + } + + // don't get grenade happy unless the player starts to piss you off + if ( m_iFrustration <= 2) + return FALSE; + + if ( m_flNextGrenadeCheck < gpGlobals->time && !HasConditions( bits_COND_ENEMY_OCCLUDED ) && flDist <= 512 /* && flDot >= 0.5 */ /* && NoFriendlyFire() */ ) + { + Vector vecToss = VecCheckThrow( pev, GetGunPosition( ), m_hEnemy->Center(), flDist, 0.5 ); // use dist as speed to get there in 1 second + + if ( vecToss != g_vecZero ) + { + m_vecTossVelocity = vecToss; + + // throw a hand grenade + m_fThrowGrenade = TRUE; + + return TRUE; + } + } + + return FALSE; +} + + +//========================================================= +// RunAI +//========================================================= +void CHAssassin :: RunAI( void ) +{ + CBaseMonster :: RunAI(); + + // always visible if moving + // always visible is not on hard + if (g_iSkillLevel != SKILL_HARD || m_hEnemy == NULL || pev->deadflag != DEAD_NO || m_Activity == ACT_RUN || m_Activity == ACT_WALK || !(pev->flags & FL_ONGROUND)) + m_iTargetRanderamt = 255; + else + m_iTargetRanderamt = 20; + + if (pev->renderamt > m_iTargetRanderamt) + { + if (pev->renderamt == 255) + { + EMIT_SOUND (ENT(pev), CHAN_BODY, "debris/beamstart1.wav", 0.2, ATTN_NORM ); + } + + pev->renderamt = max( pev->renderamt - 50, m_iTargetRanderamt ); + pev->rendermode = kRenderTransTexture; + } + else if (pev->renderamt < m_iTargetRanderamt) + { + pev->renderamt = min( pev->renderamt + 50, m_iTargetRanderamt ); + if (pev->renderamt == 255) + pev->rendermode = kRenderNormal; + } + + if (m_Activity == ACT_RUN || m_Activity == ACT_WALK) + { + static int iStep = 0; + iStep = ! iStep; + if (iStep) + { + switch( RANDOM_LONG( 0, 3 ) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step1.wav", 0.5, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step3.wav", 0.5, ATTN_NORM); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step2.wav", 0.5, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step4.wav", 0.5, ATTN_NORM); break; + } + } + } +} + + +//========================================================= +// StartTask +//========================================================= +void CHAssassin :: StartTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK2: + if (!m_fThrowGrenade) + { + TaskComplete( ); + } + else + { + CBaseMonster :: StartTask ( pTask ); + } + break; + case TASK_ASSASSIN_FALL_TO_GROUND: + break; + default: + CBaseMonster :: StartTask ( pTask ); + break; + } +} + + +//========================================================= +// RunTask +//========================================================= +void CHAssassin :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_ASSASSIN_FALL_TO_GROUND: + MakeIdealYaw( m_vecEnemyLKP ); + ChangeYaw( pev->yaw_speed ); + + if (m_fSequenceFinished) + { + if (pev->velocity.z > 0) + { + pev->sequence = LookupSequence( "fly_up" ); + } + else if (HasConditions ( bits_COND_SEE_ENEMY )) + { + pev->sequence = LookupSequence( "fly_attack" ); + pev->frame = 0; + } + else + { + pev->sequence = LookupSequence( "fly_down" ); + pev->frame = 0; + } + + ResetSequenceInfo( ); + SetYawSpeed(); + } + if (pev->flags & FL_ONGROUND) + { + // ALERT( at_console, "on ground\n"); + TaskComplete( ); + } + break; + default: + CBaseMonster :: RunTask ( pTask ); + break; + } +} + +//========================================================= +// GetSchedule - Decides which type of schedule best suits +// the monster's current state and conditions. Then calls +// monster's member function to get a pointer to a schedule +// of the proper type. +//========================================================= +Schedule_t *CHAssassin :: GetSchedule ( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: + case MONSTERSTATE_ALERT: + { + if ( HasConditions ( bits_COND_HEAR_SOUND )) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) + { + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + if ( pSound && (pSound->m_iType & bits_SOUND_COMBAT) ) + { + return GetScheduleOfType( SCHED_INVESTIGATE_SOUND ); + } + } + } + break; + + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + + // flying? + if ( pev->movetype == MOVETYPE_TOSS) + { + if (pev->flags & FL_ONGROUND) + { + // ALERT( at_console, "landed\n"); + // just landed + pev->movetype = MOVETYPE_STEP; + return GetScheduleOfType ( SCHED_ASSASSIN_JUMP_LAND ); + } + else + { + // ALERT( at_console, "jump\n"); + // jump or jump/shoot + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + return GetScheduleOfType ( SCHED_ASSASSIN_JUMP ); + else + return GetScheduleOfType ( SCHED_ASSASSIN_JUMP_ATTACK ); + } + } + + if ( HasConditions ( bits_COND_HEAR_SOUND )) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) + { + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + } + + if ( HasConditions ( bits_COND_LIGHT_DAMAGE ) ) + { + m_iFrustration++; + } + if ( HasConditions ( bits_COND_HEAVY_DAMAGE ) ) + { + m_iFrustration++; + } + + // jump player! + if ( HasConditions ( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + // ALERT( at_console, "melee attack 1\n"); + return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); + } + + // throw grenade + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK2 ) ) + { + // ALERT( at_console, "range attack 2\n"); + return GetScheduleOfType ( SCHED_RANGE_ATTACK2 ); + } + + // spotted + if ( HasConditions ( bits_COND_SEE_ENEMY ) && HasConditions ( bits_COND_ENEMY_FACING_ME ) ) + { + // ALERT( at_console, "exposed\n"); + m_iFrustration++; + return GetScheduleOfType ( SCHED_ASSASSIN_EXPOSED ); + } + + // can attack + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + // ALERT( at_console, "range attack 1\n"); + m_iFrustration = 0; + return GetScheduleOfType ( SCHED_RANGE_ATTACK1 ); + } + + if ( HasConditions ( bits_COND_SEE_ENEMY ) ) + { + // ALERT( at_console, "face\n"); + return GetScheduleOfType ( SCHED_COMBAT_FACE ); + } + + // new enemy + if ( HasConditions ( bits_COND_NEW_ENEMY ) ) + { + // ALERT( at_console, "take cover\n"); + return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); + } + + // ALERT( at_console, "stand\n"); + return GetScheduleOfType ( SCHED_ALERT_STAND ); + } + break; + } + + return CBaseMonster :: GetSchedule(); +} + +//========================================================= +//========================================================= +Schedule_t* CHAssassin :: GetScheduleOfType ( int Type ) +{ + // ALERT( at_console, "%d\n", m_iFrustration ); + switch ( Type ) + { + case SCHED_TAKE_COVER_FROM_ENEMY: + if (pev->health > 30) + return slAssassinTakeCoverFromEnemy; + else + return slAssassinTakeCoverFromEnemy2; + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + return slAssassinTakeCoverFromBestSound; + case SCHED_ASSASSIN_EXPOSED: + return slAssassinExposed; + case SCHED_FAIL: + if (m_MonsterState == MONSTERSTATE_COMBAT) + return slAssassinFail; + break; + case SCHED_ALERT_STAND: + if (m_MonsterState == MONSTERSTATE_COMBAT) + return slAssassinHide; + break; + case SCHED_CHASE_ENEMY: + return slAssassinHunt; + case SCHED_MELEE_ATTACK1: + if (pev->flags & FL_ONGROUND) + { + if (m_flNextJump > gpGlobals->time) + { + // can't jump yet, go ahead and fail + return slAssassinFail; + } + else + { + return slAssassinJump; + } + } + else + { + return slAssassinJumpAttack; + } + case SCHED_ASSASSIN_JUMP: + case SCHED_ASSASSIN_JUMP_ATTACK: + return slAssassinJumpAttack; + case SCHED_ASSASSIN_JUMP_LAND: + return slAssassinJumpLand; + } + + return CBaseMonster :: GetScheduleOfType( Type ); +} + +#endif \ No newline at end of file diff --git a/dlls/headcrab.cpp b/dlls/headcrab.cpp new file mode 100644 index 0000000..41f72fa --- /dev/null +++ b/dlls/headcrab.cpp @@ -0,0 +1,555 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// headcrab.cpp - tiny, jumpy alien parasite +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "game.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define HC_AE_JUMPATTACK ( 2 ) + +Task_t tlHCRangeAttack1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_WAIT_RANDOM, (float)0.5 }, +}; + +Schedule_t slHCRangeAttack1[] = +{ + { + tlHCRangeAttack1, + ARRAYSIZE ( tlHCRangeAttack1 ), + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED, + 0, + "HCRangeAttack1" + }, +}; + +Task_t tlHCRangeAttack1Fast[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slHCRangeAttack1Fast[] = +{ + { + tlHCRangeAttack1Fast, + ARRAYSIZE ( tlHCRangeAttack1Fast ), + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED, + 0, + "HCRAFast" + }, +}; + +class CHeadCrab : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void RunTask ( Task_t *pTask ); + void StartTask ( Task_t *pTask ); + void SetYawSpeed ( void ); + void EXPORT LeapTouch ( CBaseEntity *pOther ); + Vector Center( void ); + Vector BodyTarget( const Vector &posSrc ); + void PainSound( void ); + void DeathSound( void ); + void IdleSound( void ); + void AlertSound( void ); + void PrescheduleThink( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack2 ( float flDot, float flDist ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + virtual float GetDamageAmount( void ) { return gSkillData.headcrabDmgBite; } + virtual int GetVoicePitch( void ) { return 100; } + virtual float GetSoundVolue( void ) { return 1.0; } + Schedule_t* GetScheduleOfType ( int Type ); + + CUSTOM_SCHEDULES; + + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + static const char *pPainSounds[]; + static const char *pAttackSounds[]; + static const char *pDeathSounds[]; + static const char *pBiteSounds[]; +}; +LINK_ENTITY_TO_CLASS( monster_headcrab, CHeadCrab ); + +DEFINE_CUSTOM_SCHEDULES( CHeadCrab ) +{ + slHCRangeAttack1, + slHCRangeAttack1Fast, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CHeadCrab, CBaseMonster ); + +const char *CHeadCrab::pIdleSounds[] = +{ + "headcrab/hc_idle1.wav", + "headcrab/hc_idle2.wav", + "headcrab/hc_idle3.wav", +}; +const char *CHeadCrab::pAlertSounds[] = +{ + "headcrab/hc_alert1.wav", +}; +const char *CHeadCrab::pPainSounds[] = +{ + "headcrab/hc_pain1.wav", + "headcrab/hc_pain2.wav", + "headcrab/hc_pain3.wav", +}; +const char *CHeadCrab::pAttackSounds[] = +{ + "headcrab/hc_attack1.wav", + "headcrab/hc_attack2.wav", + "headcrab/hc_attack3.wav", +}; + +const char *CHeadCrab::pDeathSounds[] = +{ + "headcrab/hc_die1.wav", + "headcrab/hc_die2.wav", +}; + +const char *CHeadCrab::pBiteSounds[] = +{ + "headcrab/hc_headbite.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CHeadCrab :: Classify ( void ) +{ + return CLASS_ALIEN_PREY; +} + +//========================================================= +// Center - returns the real center of the headcrab. The +// bounding box is much larger than the actual creature so +// this is needed for targeting +//========================================================= +Vector CHeadCrab :: Center ( void ) +{ + return Vector( pev->origin.x, pev->origin.y, pev->origin.z + 6 ); +} + + +Vector CHeadCrab :: BodyTarget( const Vector &posSrc ) +{ + return Center( ); +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CHeadCrab :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 30; + break; + case ACT_RUN: + case ACT_WALK: + ys = 20; + break; + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 60; + break; + case ACT_RANGE_ATTACK1: + ys = 30; + break; + default: + ys = 30; + break; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CHeadCrab :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case HC_AE_JUMPATTACK: + { + ClearBits( pev->flags, FL_ONGROUND ); + + UTIL_SetOrigin (pev, pev->origin + Vector ( 0 , 0 , 1) );// take him off ground so engine doesn't instantly reset onground + UTIL_MakeVectors ( pev->angles ); + + Vector vecJumpDir; + if (m_hEnemy != NULL) + { + float gravity = g_psv_gravity->value; + if (gravity <= 1) + gravity = 1; + + // How fast does the headcrab need to travel to reach that height given gravity? + float height = (m_hEnemy->pev->origin.z + m_hEnemy->pev->view_ofs.z - pev->origin.z); + if (height < 16) + height = 16; + float speed = sqrt( 2 * gravity * height ); + float time = speed / gravity; + + // Scale the sideways velocity to get there at the right time + vecJumpDir = (m_hEnemy->pev->origin + m_hEnemy->pev->view_ofs - pev->origin); + vecJumpDir = vecJumpDir * ( 1.0 / time ); + + // Speed to offset gravity at the desired height + vecJumpDir.z = speed; + + // Don't jump too far/fast + float distance = vecJumpDir.Length(); + + if (distance > 650) + { + vecJumpDir = vecJumpDir * ( 650.0 / distance ); + } + } + else + { + // jump hop, don't care where + vecJumpDir = Vector( gpGlobals->v_forward.x, gpGlobals->v_forward.y, gpGlobals->v_up.z ) * 350; + } + + int iSound = RANDOM_LONG(0,2); + if ( iSound != 0 ) + EMIT_SOUND_DYN( edict(), CHAN_VOICE, pAttackSounds[iSound], GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); + + pev->velocity = vecJumpDir; + m_flNextAttack = gpGlobals->time + 2; + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CHeadCrab :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/headcrab.mdl"); + UTIL_SetSize(pev, Vector(-12, -12, 0), Vector(12, 12, 24)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->effects = 0; + pev->health = gSkillData.headcrabHealth; + pev->view_ofs = Vector ( 0, 0, 20 );// position of the eyes relative to monster's origin. + pev->yaw_speed = 5;//!!! should we put this in the monster's changeanim function since turn rates may vary with state/anim? + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CHeadCrab :: Precache() +{ + PRECACHE_SOUND_ARRAY(pIdleSounds); + PRECACHE_SOUND_ARRAY(pAlertSounds); + PRECACHE_SOUND_ARRAY(pPainSounds); + PRECACHE_SOUND_ARRAY(pAttackSounds); + PRECACHE_SOUND_ARRAY(pDeathSounds); + PRECACHE_SOUND_ARRAY(pBiteSounds); + + PRECACHE_MODEL("models/headcrab.mdl"); +} + + +//========================================================= +// RunTask +//========================================================= +void CHeadCrab :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK1: + case TASK_RANGE_ATTACK2: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + SetTouch( NULL ); + m_IdealActivity = ACT_IDLE; + } + break; + } + default: + { + CBaseMonster :: RunTask(pTask); + } + } +} + +//========================================================= +// LeapTouch - this is the headcrab's touch function when it +// is in the air +//========================================================= +void CHeadCrab :: LeapTouch ( CBaseEntity *pOther ) +{ + if ( !pOther->pev->takedamage ) + { + return; + } + + if ( pOther->Classify() == Classify() ) + { + return; + } + + // Don't hit if back on ground + if ( !FBitSet( pev->flags, FL_ONGROUND ) ) + { + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pBiteSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); + + pOther->TakeDamage( pev, pev, GetDamageAmount(), DMG_SLASH ); + } + + SetTouch( NULL ); +} + +//========================================================= +// PrescheduleThink +//========================================================= +void CHeadCrab :: PrescheduleThink ( void ) +{ + // make the crab coo a little bit in combat state + if ( m_MonsterState == MONSTERSTATE_COMBAT && RANDOM_FLOAT( 0, 5 ) < 0.1 ) + { + IdleSound(); + } +} + +void CHeadCrab :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK1: + { + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, pAttackSounds[0], GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); + m_IdealActivity = ACT_RANGE_ATTACK1; + SetTouch ( &CHeadCrab::LeapTouch ); + break; + } + default: + { + CBaseMonster :: StartTask( pTask ); + } + } +} + + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CHeadCrab :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( FBitSet( pev->flags, FL_ONGROUND ) && flDist <= 256 && flDot >= 0.65 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 +//========================================================= +BOOL CHeadCrab :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + return FALSE; + // BUGBUG: Why is this code here? There is no ACT_RANGE_ATTACK2 animation. I've disabled it for now. +#if 0 + if ( FBitSet( pev->flags, FL_ONGROUND ) && flDist > 64 && flDist <= 256 && flDot >= 0.5 ) + { + return TRUE; + } + return FALSE; +#endif +} + +int CHeadCrab :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // Don't take any acid damage -- BigMomma's mortar is acid + if ( bitsDamageType & DMG_ACID ) + flDamage = 0; + + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +// IdleSound +//========================================================= +#define CRAB_ATTN_IDLE (float)1.5 +void CHeadCrab :: IdleSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pIdleSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +//========================================================= +// AlertSound +//========================================================= +void CHeadCrab :: AlertSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pAlertSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +//========================================================= +// AlertSound +//========================================================= +void CHeadCrab :: PainSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pPainSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +//========================================================= +// DeathSound +//========================================================= +void CHeadCrab :: DeathSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pDeathSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +Schedule_t* CHeadCrab :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_RANGE_ATTACK1: + { + return &slHCRangeAttack1[ 0 ]; + } + break; + } + + return CBaseMonster::GetScheduleOfType( Type ); +} + + +class CBabyCrab : public CHeadCrab +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed ( void ); + float GetDamageAmount( void ) { return gSkillData.headcrabDmgBite * 0.3; } + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + Schedule_t* GetScheduleOfType ( int Type ); + virtual int GetVoicePitch( void ) { return PITCH_NORM + RANDOM_LONG(40,50); } + virtual float GetSoundVolue( void ) { return 0.8; } +}; +LINK_ENTITY_TO_CLASS( monster_babycrab, CBabyCrab ); + +void CBabyCrab :: Spawn( void ) +{ + CHeadCrab::Spawn(); + SET_MODEL(ENT(pev), "models/baby_headcrab.mdl"); + pev->rendermode = kRenderTransTexture; + pev->renderamt = 192; + UTIL_SetSize(pev, Vector(-12, -12, 0), Vector(12, 12, 24)); + + pev->health = gSkillData.headcrabHealth * 0.25; // less health than full grown +} + +void CBabyCrab :: Precache( void ) +{ + PRECACHE_MODEL( "models/baby_headcrab.mdl" ); + CHeadCrab::Precache(); +} + + +void CBabyCrab :: SetYawSpeed ( void ) +{ + pev->yaw_speed = 120; +} + + +BOOL CBabyCrab :: CheckRangeAttack1( float flDot, float flDist ) +{ + if ( pev->flags & FL_ONGROUND ) + { + if ( pev->groundentity && (pev->groundentity->v.flags & (FL_CLIENT|FL_MONSTER)) ) + return TRUE; + + // A little less accurate, but jump from closer + if ( flDist <= 180 && flDot >= 0.55 ) + return TRUE; + } + + return FALSE; +} + + +Schedule_t* CBabyCrab :: GetScheduleOfType ( int Type ) +{ + switch( Type ) + { + case SCHED_FAIL: // If you fail, try to jump! + if ( m_hEnemy != NULL ) + return slHCRangeAttack1Fast; + break; + + case SCHED_RANGE_ATTACK1: + { + return slHCRangeAttack1Fast; + } + break; + } + + return CHeadCrab::GetScheduleOfType( Type ); +} diff --git a/dlls/healthkit.cpp b/dlls/healthkit.cpp new file mode 100644 index 0000000..5a74889 --- /dev/null +++ b/dlls/healthkit.cpp @@ -0,0 +1,264 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "items.h" +#include "gamerules.h" + +extern int gmsgItemPickup; + +class CHealthKit : public CItem +{ + void Spawn( void ); + void Precache( void ); + BOOL MyTouch( CBasePlayer *pPlayer ); + +/* + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; +*/ + +}; + + +LINK_ENTITY_TO_CLASS( item_healthkit, CHealthKit ); + +/* +TYPEDESCRIPTION CHealthKit::m_SaveData[] = +{ + +}; + + +IMPLEMENT_SAVERESTORE( CHealthKit, CItem); +*/ + +void CHealthKit :: Spawn( void ) +{ + Precache( ); + SET_MODEL(ENT(pev), "models/w_medkit.mdl"); + + CItem::Spawn(); +} + +void CHealthKit::Precache( void ) +{ + PRECACHE_MODEL("models/w_medkit.mdl"); + PRECACHE_SOUND("items/smallmedkit1.wav"); +} + +BOOL CHealthKit::MyTouch( CBasePlayer *pPlayer ) +{ + if ( pPlayer->pev->deadflag != DEAD_NO ) + { + return FALSE; + } + + if ( pPlayer->TakeHealth( gSkillData.healthkitCapacity, DMG_GENERIC ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgItemPickup, NULL, pPlayer->pev ); + WRITE_STRING( STRING(pev->classname) ); + MESSAGE_END(); + + EMIT_SOUND(ENT(pPlayer->pev), CHAN_ITEM, "items/smallmedkit1.wav", 1, ATTN_NORM); + + if ( g_pGameRules->ItemShouldRespawn( this ) ) + { + Respawn(); + } + else + { + UTIL_Remove(this); + } + + return TRUE; + } + + return FALSE; +} + + + +//------------------------------------------------------------- +// Wall mounted health kit +//------------------------------------------------------------- +class CWallHealth : public CBaseToggle +{ +public: + void Spawn( ); + void Precache( void ); + void EXPORT Off(void); + void EXPORT Recharge(void); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return (CBaseToggle :: ObjectCaps() | FCAP_CONTINUOUS_USE) & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flNextCharge; + int m_iReactivate ; // DeathMatch Delay until reactvated + int m_iJuice; + int m_iOn; // 0 = off, 1 = startup, 2 = going + float m_flSoundTime; +}; + +TYPEDESCRIPTION CWallHealth::m_SaveData[] = +{ + DEFINE_FIELD( CWallHealth, m_flNextCharge, FIELD_TIME), + DEFINE_FIELD( CWallHealth, m_iReactivate, FIELD_INTEGER), + DEFINE_FIELD( CWallHealth, m_iJuice, FIELD_INTEGER), + DEFINE_FIELD( CWallHealth, m_iOn, FIELD_INTEGER), + DEFINE_FIELD( CWallHealth, m_flSoundTime, FIELD_TIME), +}; + +IMPLEMENT_SAVERESTORE( CWallHealth, CBaseEntity ); + +LINK_ENTITY_TO_CLASS(func_healthcharger, CWallHealth); + + +void CWallHealth::KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + { + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "dmdelay")) + { + m_iReactivate = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CWallHealth::Spawn() +{ + Precache( ); + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); // set size and link into world + UTIL_SetSize(pev, pev->mins, pev->maxs); + SET_MODEL(ENT(pev), STRING(pev->model) ); + m_iJuice = gSkillData.healthchargerCapacity; + pev->frame = 0; + +} + +void CWallHealth::Precache() +{ + PRECACHE_SOUND("items/medshot4.wav"); + PRECACHE_SOUND("items/medshotno1.wav"); + PRECACHE_SOUND("items/medcharge4.wav"); +} + + +void CWallHealth::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Make sure that we have a caller + if (!pActivator) + return; + // if it's not a player, ignore + if ( !pActivator->IsPlayer() ) + return; + + // if there is no juice left, turn it off + if (m_iJuice <= 0) + { + pev->frame = 1; + Off(); + } + + // if the player doesn't have the suit, or there is no juice left, make the deny noise + if ((m_iJuice <= 0) || (!(pActivator->pev->weapons & (1<time) + { + m_flSoundTime = gpGlobals->time + 0.62; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/medshotno1.wav", 1.0, ATTN_NORM ); + } + return; + } + + pev->nextthink = pev->ltime + 0.25; + SetThink(&CWallHealth::Off); + + // Time to recharge yet? + + if (m_flNextCharge >= gpGlobals->time) + return; + + // Play the on sound or the looping charging sound + if (!m_iOn) + { + m_iOn++; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/medshot4.wav", 1.0, ATTN_NORM ); + m_flSoundTime = 0.56 + gpGlobals->time; + } + if ((m_iOn == 1) && (m_flSoundTime <= gpGlobals->time)) + { + m_iOn++; + EMIT_SOUND(ENT(pev), CHAN_STATIC, "items/medcharge4.wav", 1.0, ATTN_NORM ); + } + + + // charge the player + if ( pActivator->TakeHealth( 1, DMG_GENERIC ) ) + { + m_iJuice--; + } + + // govern the rate of charge + m_flNextCharge = gpGlobals->time + 0.1; +} + +void CWallHealth::Recharge(void) +{ + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/medshot4.wav", 1.0, ATTN_NORM ); + m_iJuice = gSkillData.healthchargerCapacity; + pev->frame = 0; + SetThink( &CWallHealth::SUB_DoNothing ); +} + +void CWallHealth::Off(void) +{ + // Stop looping sound. + if (m_iOn > 1) + STOP_SOUND( ENT(pev), CHAN_STATIC, "items/medcharge4.wav" ); + + m_iOn = 0; + + if ((!m_iJuice) && ( ( m_iReactivate = g_pGameRules->FlHealthChargerRechargeTime() ) > 0) ) + { + pev->nextthink = pev->ltime + m_iReactivate; + SetThink(&CWallHealth::Recharge); + } + else + SetThink( &CWallHealth::SUB_DoNothing ); +} diff --git a/dlls/hgrunt.cpp b/dlls/hgrunt.cpp new file mode 100644 index 0000000..e58bbef --- /dev/null +++ b/dlls/hgrunt.cpp @@ -0,0 +1,2517 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// hgrunt +//========================================================= + +//========================================================= +// Hit groups! +//========================================================= +/* + + 1 - Head + 2 - Stomach + 3 - Gun + +*/ + + +#include "extdll.h" +#include "plane.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "animation.h" +#include "squadmonster.h" +#include "weapons.h" +#include "talkmonster.h" +#include "soundent.h" +#include "effects.h" +#include "customentity.h" + +int g_fGruntQuestion; // true if an idle grunt asked a question. Cleared when someone answers. + +extern DLL_GLOBAL int g_iSkillLevel; + +//========================================================= +// monster-specific DEFINE's +//========================================================= +#define GRUNT_CLIP_SIZE 36 // how many bullets in a clip? - NOTE: 3 round burst sound, so keep as 3 * x! +#define GRUNT_VOL 0.35 // volume of grunt sounds +#define GRUNT_ATTN ATTN_NORM // attenutation of grunt sentences +#define HGRUNT_LIMP_HEALTH 20 +#define HGRUNT_DMG_HEADSHOT ( DMG_BULLET | DMG_CLUB ) // damage types that can kill a grunt with a single headshot. +#define HGRUNT_NUM_HEADS 2 // how many grunt heads are there? +#define HGRUNT_MINIMUM_HEADSHOT_DAMAGE 15 // must do at least this much damage in one shot to head to score a headshot kill +#define HGRUNT_SENTENCE_VOLUME (float)0.35 // volume of grunt sentences + +#define HGRUNT_9MMAR ( 1 << 0) +#define HGRUNT_HANDGRENADE ( 1 << 1) +#define HGRUNT_GRENADELAUNCHER ( 1 << 2) +#define HGRUNT_SHOTGUN ( 1 << 3) + +#define HEAD_GROUP 1 +#define HEAD_GRUNT 0 +#define HEAD_COMMANDER 1 +#define HEAD_SHOTGUN 2 +#define HEAD_M203 3 +#define GUN_GROUP 2 +#define GUN_MP5 0 +#define GUN_SHOTGUN 1 +#define GUN_NONE 2 + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define HGRUNT_AE_RELOAD ( 2 ) +#define HGRUNT_AE_KICK ( 3 ) +#define HGRUNT_AE_BURST1 ( 4 ) +#define HGRUNT_AE_BURST2 ( 5 ) +#define HGRUNT_AE_BURST3 ( 6 ) +#define HGRUNT_AE_GREN_TOSS ( 7 ) +#define HGRUNT_AE_GREN_LAUNCH ( 8 ) +#define HGRUNT_AE_GREN_DROP ( 9 ) +#define HGRUNT_AE_CAUGHT_ENEMY ( 10) // grunt established sight with an enemy (player only) that had previously eluded the squad. +#define HGRUNT_AE_DROP_GUN ( 11) // grunt (probably dead) is dropping his mp5. + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_GRUNT_SUPPRESS = LAST_COMMON_SCHEDULE + 1, + SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE,// move to a location to set up an attack against the enemy. (usually when a friendly is in the way). + SCHED_GRUNT_COVER_AND_RELOAD, + SCHED_GRUNT_SWEEP, + SCHED_GRUNT_FOUND_ENEMY, + SCHED_GRUNT_REPEL, + SCHED_GRUNT_REPEL_ATTACK, + SCHED_GRUNT_REPEL_LAND, + SCHED_GRUNT_WAIT_FACE_ENEMY, + SCHED_GRUNT_TAKECOVER_FAILED,// special schedule type that forces analysis of conditions and picks the best possible schedule to recover from this type of failure. + SCHED_GRUNT_ELOF_FAIL, +}; + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_GRUNT_FACE_TOSS_DIR = LAST_COMMON_TASK + 1, + TASK_GRUNT_SPEAK_SENTENCE, + TASK_GRUNT_CHECK_FIRE, +}; + +//========================================================= +// monster-specific conditions +//========================================================= +#define bits_COND_GRUNT_NOFIRE ( bits_COND_SPECIAL1 ) + +class CHGrunt : public CSquadMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed ( void ); + int Classify ( void ); + int ISoundMask ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + BOOL FCanCheckAttacks ( void ); + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack2 ( float flDot, float flDist ); + void CheckAmmo ( void ); + void SetActivity ( Activity NewActivity ); + void StartTask ( Task_t *pTask ); + void RunTask ( Task_t *pTask ); + void DeathSound( void ); + void PainSound( void ); + void IdleSound ( void ); + Vector GetGunPosition( void ); + void Shoot ( void ); + void Shotgun ( void ); + void PrescheduleThink ( void ); + void GibMonster( void ); + void SpeakSentence( void ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + + CBaseEntity *Kick( void ); + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType ( int Type ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + int IRelationship ( CBaseEntity *pTarget ); + + BOOL FOkToSpeak( void ); + void JustSpoke( void ); + + CUSTOM_SCHEDULES; + static TYPEDESCRIPTION m_SaveData[]; + + // checking the feasibility of a grenade toss is kind of costly, so we do it every couple of seconds, + // not every server frame. + float m_flNextGrenadeCheck; + float m_flNextPainTime; + float m_flLastEnemySightTime; + + Vector m_vecTossVelocity; + + BOOL m_fThrowGrenade; + BOOL m_fStanding; + BOOL m_fFirstEncounter;// only put on the handsign show in the squad's first encounter. + int m_cClipSize; + + int m_voicePitch; + + int m_iBrassShell; + int m_iShotgunShell; + + int m_iSentence; + + static const char *pGruntSentences[]; +}; + +LINK_ENTITY_TO_CLASS( monster_human_grunt, CHGrunt ); + +TYPEDESCRIPTION CHGrunt::m_SaveData[] = +{ + DEFINE_FIELD( CHGrunt, m_flNextGrenadeCheck, FIELD_TIME ), + DEFINE_FIELD( CHGrunt, m_flNextPainTime, FIELD_TIME ), +// DEFINE_FIELD( CHGrunt, m_flLastEnemySightTime, FIELD_TIME ), // don't save, go to zero + DEFINE_FIELD( CHGrunt, m_vecTossVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CHGrunt, m_fThrowGrenade, FIELD_BOOLEAN ), + DEFINE_FIELD( CHGrunt, m_fStanding, FIELD_BOOLEAN ), + DEFINE_FIELD( CHGrunt, m_fFirstEncounter, FIELD_BOOLEAN ), + DEFINE_FIELD( CHGrunt, m_cClipSize, FIELD_INTEGER ), + DEFINE_FIELD( CHGrunt, m_voicePitch, FIELD_INTEGER ), +// DEFINE_FIELD( CShotgun, m_iBrassShell, FIELD_INTEGER ), +// DEFINE_FIELD( CShotgun, m_iShotgunShell, FIELD_INTEGER ), + DEFINE_FIELD( CHGrunt, m_iSentence, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CHGrunt, CSquadMonster ); + +const char *CHGrunt::pGruntSentences[] = +{ + "HG_GREN", // grenade scared grunt + "HG_ALERT", // sees player + "HG_MONSTER", // sees monster + "HG_COVER", // running to cover + "HG_THROW", // about to throw grenade + "HG_CHARGE", // running out to get the enemy + "HG_TAUNT", // say rude things +}; + +enum +{ + HGRUNT_SENT_NONE = -1, + HGRUNT_SENT_GREN = 0, + HGRUNT_SENT_ALERT, + HGRUNT_SENT_MONSTER, + HGRUNT_SENT_COVER, + HGRUNT_SENT_THROW, + HGRUNT_SENT_CHARGE, + HGRUNT_SENT_TAUNT, +} HGRUNT_SENTENCE_TYPES; + +//========================================================= +// Speak Sentence - say your cued up sentence. +// +// Some grunt sentences (take cover and charge) rely on actually +// being able to execute the intended action. It's really lame +// when a grunt says 'COVER ME' and then doesn't move. The problem +// is that the sentences were played when the decision to TRY +// to move to cover was made. Now the sentence is played after +// we know for sure that there is a valid path. The schedule +// may still fail but in most cases, well after the grunt has +// started moving. +//========================================================= +void CHGrunt :: SpeakSentence( void ) +{ + if ( m_iSentence == HGRUNT_SENT_NONE ) + { + // no sentence cued up. + return; + } + + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), pGruntSentences[ m_iSentence ], HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } +} + +//========================================================= +// IRelationship - overridden because Alien Grunts are +// Human Grunt's nemesis. +//========================================================= +int CHGrunt::IRelationship ( CBaseEntity *pTarget ) +{ + if ( FClassnameIs( pTarget->pev, "monster_alien_grunt" ) || ( FClassnameIs( pTarget->pev, "monster_gargantua" ) ) ) + { + return R_NM; + } + + return CSquadMonster::IRelationship( pTarget ); +} + +//========================================================= +// GibMonster - make gun fly through the air. +//========================================================= +void CHGrunt :: GibMonster ( void ) +{ + Vector vecGunPos; + Vector vecGunAngles; + + if ( GetBodygroup( 2 ) != 2 ) + {// throw a gun if the grunt has one + GetAttachment( 0, vecGunPos, vecGunAngles ); + + CBaseEntity *pGun; + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) + { + pGun = DropItem( "weapon_shotgun", vecGunPos, vecGunAngles ); + } + else + { + pGun = DropItem( "weapon_9mmAR", vecGunPos, vecGunAngles ); + } + if ( pGun ) + { + pGun->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + pGun->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 200, 400 ), 0 ); + } + + if (FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER )) + { + pGun = DropItem( "ammo_ARgrenades", vecGunPos, vecGunAngles ); + if ( pGun ) + { + pGun->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + pGun->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 200, 400 ), 0 ); + } + } + } + + CBaseMonster :: GibMonster(); +} + +//========================================================= +// ISoundMask - Overidden for human grunts because they +// hear the DANGER sound that is made by hand grenades and +// other dangerous items. +//========================================================= +int CHGrunt :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER | + bits_SOUND_DANGER; +} + +//========================================================= +// someone else is talking - don't speak +//========================================================= +BOOL CHGrunt :: FOkToSpeak( void ) +{ +// if someone else is talking, don't speak + if (gpGlobals->time <= CTalkMonster::g_talkWaitTime) + return FALSE; + + if ( pev->spawnflags & SF_MONSTER_GAG ) + { + if ( m_MonsterState != MONSTERSTATE_COMBAT ) + { + // no talking outside of combat if gagged. + return FALSE; + } + } + + // if player is not in pvs, don't speak +// if (FNullEnt(FIND_CLIENT_IN_PVS(edict()))) +// return FALSE; + + return TRUE; +} + +//========================================================= +//========================================================= +void CHGrunt :: JustSpoke( void ) +{ + CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(1.5, 2.0); + m_iSentence = HGRUNT_SENT_NONE; +} + +//========================================================= +// PrescheduleThink - this function runs after conditions +// are collected and before scheduling code is run. +//========================================================= +void CHGrunt :: PrescheduleThink ( void ) +{ + if ( InSquad() && m_hEnemy != NULL ) + { + if ( HasConditions ( bits_COND_SEE_ENEMY ) ) + { + // update the squad's last enemy sighting time. + MySquadLeader()->m_flLastEnemySightTime = gpGlobals->time; + } + else + { + if ( gpGlobals->time - MySquadLeader()->m_flLastEnemySightTime > 5 ) + { + // been a while since we've seen the enemy + MySquadLeader()->m_fEnemyEluded = TRUE; + } + } + } +} + +//========================================================= +// FCanCheckAttacks - this is overridden for human grunts +// because they can throw/shoot grenades when they can't see their +// target and the base class doesn't check attacks if the monster +// cannot see its enemy. +// +// !!!BUGBUG - this gets called before a 3-round burst is fired +// which means that a friendly can still be hit with up to 2 rounds. +// ALSO, grenades will not be tossed if there is a friendly in front, +// this is a bad bug. Friendly machine gun fire avoidance +// will unecessarily prevent the throwing of a grenade as well. +//========================================================= +BOOL CHGrunt :: FCanCheckAttacks ( void ) +{ + if ( !HasConditions( bits_COND_ENEMY_TOOFAR ) ) + { + return TRUE; + } + else + { + return FALSE; + } +} + + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL CHGrunt :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + CBaseMonster *pEnemy; + + if ( m_hEnemy != NULL ) + { + pEnemy = m_hEnemy->MyMonsterPointer(); + + if ( !pEnemy ) + { + return FALSE; + } + } + + if ( flDist <= 64 && flDot >= 0.7 && + pEnemy->Classify() != CLASS_ALIEN_BIOWEAPON && + pEnemy->Classify() != CLASS_PLAYER_BIOWEAPON ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack1 - overridden for HGrunt, cause +// FCanCheckAttacks() doesn't disqualify all attacks based +// on whether or not the enemy is occluded because unlike +// the base class, the HGrunt can attack when the enemy is +// occluded (throw grenade over wall, etc). We must +// disqualify the machine gun attack if the enemy is occluded. +//========================================================= +BOOL CHGrunt :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( !HasConditions( bits_COND_ENEMY_OCCLUDED ) && flDist <= 2048 && flDot >= 0.5 && NoFriendlyFire() ) + { + TraceResult tr; + + if ( !m_hEnemy->IsPlayer() && flDist <= 64 ) + { + // kick nonclients, but don't shoot at them. + return FALSE; + } + + Vector vecSrc = GetGunPosition(); + + // verify that a bullet fired from the gun will hit the enemy before the world. + UTIL_TraceLine( vecSrc, m_hEnemy->BodyTarget(vecSrc), ignore_monsters, ignore_glass, ENT(pev), &tr); + + if ( tr.flFraction == 1.0 ) + { + return TRUE; + } + } + + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 - this checks the Grunt's grenade +// attack. +//========================================================= +BOOL CHGrunt :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + if (! FBitSet(pev->weapons, (HGRUNT_HANDGRENADE | HGRUNT_GRENADELAUNCHER))) + { + return FALSE; + } + + // if the grunt isn't moving, it's ok to check. + if ( m_flGroundSpeed != 0 ) + { + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; + } + + // assume things haven't changed too much since last time + if (gpGlobals->time < m_flNextGrenadeCheck ) + { + return m_fThrowGrenade; + } + + if ( !FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ) && m_hEnemy->pev->waterlevel == 0 && m_vecEnemyLKP.z > pev->absmax.z ) + { + //!!!BUGBUG - we should make this check movetype and make sure it isn't FLY? Players who jump a lot are unlikely to + // be grenaded. + // don't throw grenades at anything that isn't on the ground! + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; + } + + Vector vecTarget; + + if (FBitSet( pev->weapons, HGRUNT_HANDGRENADE)) + { + // find feet + if (RANDOM_LONG(0,1)) + { + // magically know where they are + vecTarget = Vector( m_hEnemy->pev->origin.x, m_hEnemy->pev->origin.y, m_hEnemy->pev->absmin.z ); + } + else + { + // toss it to where you last saw them + vecTarget = m_vecEnemyLKP; + } + // vecTarget = m_vecEnemyLKP + (m_hEnemy->BodyTarget( pev->origin ) - m_hEnemy->pev->origin); + // estimate position + // vecTarget = vecTarget + m_hEnemy->pev->velocity * 2; + } + else + { + // find target + // vecTarget = m_hEnemy->BodyTarget( pev->origin ); + vecTarget = m_vecEnemyLKP + (m_hEnemy->BodyTarget( pev->origin ) - m_hEnemy->pev->origin); + // estimate position + if (HasConditions( bits_COND_SEE_ENEMY)) + vecTarget = vecTarget + ((vecTarget - pev->origin).Length() / gSkillData.hgruntGrenadeSpeed) * m_hEnemy->pev->velocity; + } + + // are any of my squad members near the intended grenade impact area? + if ( InSquad() ) + { + if (SquadMemberInRange( vecTarget, 256 )) + { + // crap, I might blow my own guy up. Don't throw a grenade and don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + m_fThrowGrenade = FALSE; + } + } + + if ( ( vecTarget - pev->origin ).Length2D() <= 256 ) + { + // crap, I don't want to blow myself up + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; + } + + + if (FBitSet( pev->weapons, HGRUNT_HANDGRENADE)) + { + Vector vecToss = VecCheckToss( pev, GetGunPosition(), vecTarget, 0.5 ); + + if ( vecToss != g_vecZero ) + { + m_vecTossVelocity = vecToss; + + // throw a hand grenade + m_fThrowGrenade = TRUE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time; // 1/3 second. + } + else + { + // don't throw + m_fThrowGrenade = FALSE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + } + } + else + { + Vector vecToss = VecCheckThrow( pev, GetGunPosition(), vecTarget, gSkillData.hgruntGrenadeSpeed, 0.5 ); + + if ( vecToss != g_vecZero ) + { + m_vecTossVelocity = vecToss; + + // throw a hand grenade + m_fThrowGrenade = TRUE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 0.3; // 1/3 second. + } + else + { + // don't throw + m_fThrowGrenade = FALSE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + } + } + + + + return m_fThrowGrenade; +} + + +//========================================================= +// TraceAttack - make sure we're not taking it in the helmet +//========================================================= +void CHGrunt :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + // check for helmet shot + if (ptr->iHitgroup == 11) + { + // make sure we're wearing one + if (GetBodygroup( 1 ) == HEAD_GRUNT && (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB))) + { + // absorb damage + flDamage -= 20; + if (flDamage <= 0) + { + UTIL_Ricochet( ptr->vecEndPos, 1.0 ); + flDamage = 0.01; + } + } + // it's head shot anyways + ptr->iHitgroup = HITGROUP_HEAD; + } + CSquadMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + + +//========================================================= +// TakeDamage - overridden for the grunt because the grunt +// needs to forget that he is in cover if he's hurt. (Obviously +// not in a safe place anymore). +//========================================================= +int CHGrunt :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + Forget( bits_MEMORY_INCOVER ); + + return CSquadMonster :: TakeDamage ( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CHGrunt :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 150; + break; + case ACT_RUN: + ys = 150; + break; + case ACT_WALK: + ys = 180; + break; + case ACT_RANGE_ATTACK1: + ys = 120; + break; + case ACT_RANGE_ATTACK2: + ys = 120; + break; + case ACT_MELEE_ATTACK1: + ys = 120; + break; + case ACT_MELEE_ATTACK2: + ys = 120; + break; + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 180; + break; + case ACT_GLIDE: + case ACT_FLY: + ys = 30; + break; + default: + ys = 90; + break; + } + + pev->yaw_speed = ys; +} + +void CHGrunt :: IdleSound( void ) +{ + if (FOkToSpeak() && (g_fGruntQuestion || RANDOM_LONG(0,1))) + { + if (!g_fGruntQuestion) + { + // ask question or make statement + switch (RANDOM_LONG(0,2)) + { + case 0: // check in + SENTENCEG_PlayRndSz(ENT(pev), "HG_CHECK", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + g_fGruntQuestion = 1; + break; + case 1: // question + SENTENCEG_PlayRndSz(ENT(pev), "HG_QUEST", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + g_fGruntQuestion = 2; + break; + case 2: // statement + SENTENCEG_PlayRndSz(ENT(pev), "HG_IDLE", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + } + } + else + { + switch (g_fGruntQuestion) + { + case 1: // check in + SENTENCEG_PlayRndSz(ENT(pev), "HG_CLEAR", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + case 2: // question + SENTENCEG_PlayRndSz(ENT(pev), "HG_ANSWER", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + } + g_fGruntQuestion = 0; + } + JustSpoke(); + } +} + +//========================================================= +// CheckAmmo - overridden for the grunt because he actually +// uses ammo! (base class doesn't) +//========================================================= +void CHGrunt :: CheckAmmo ( void ) +{ + if ( m_cAmmoLoaded <= 0 ) + { + SetConditions(bits_COND_NO_AMMO_LOADED); + } +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CHGrunt :: Classify ( void ) +{ + return CLASS_HUMAN_MILITARY; +} + +//========================================================= +//========================================================= +CBaseEntity *CHGrunt :: Kick( void ) +{ + TraceResult tr; + + UTIL_MakeVectors( pev->angles ); + Vector vecStart = pev->origin; + vecStart.z += pev->size.z * 0.5; + Vector vecEnd = vecStart + (gpGlobals->v_forward * 70); + + UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr ); + + if ( tr.pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + return pEntity; + } + + return NULL; +} + +//========================================================= +// GetGunPosition return the end of the barrel +//========================================================= + +Vector CHGrunt :: GetGunPosition( ) +{ + if (m_fStanding ) + { + return pev->origin + Vector( 0, 0, 60 ); + } + else + { + return pev->origin + Vector( 0, 0, 48 ); + } +} + +//========================================================= +// Shoot +//========================================================= +void CHGrunt :: Shoot ( void ) +{ + if (m_hEnemy == NULL) + { + return; + } + + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + UTIL_MakeVectors ( pev->angles ); + + Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT(40,90) + gpGlobals->v_up * RANDOM_FLOAT(75,200) + gpGlobals->v_forward * RANDOM_FLOAT(-40, 40); + EjectBrass ( vecShootOrigin - vecShootDir * 24, vecShellVelocity, pev->angles.y, m_iBrassShell, TE_BOUNCE_SHELL); + FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_10DEGREES, 2048, BULLET_MONSTER_MP5 ); // shoot +-5 degrees + + pev->effects |= EF_MUZZLEFLASH; + + m_cAmmoLoaded--;// take away a bullet! + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); +} + +//========================================================= +// Shoot +//========================================================= +void CHGrunt :: Shotgun ( void ) +{ + if (m_hEnemy == NULL) + { + return; + } + + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + UTIL_MakeVectors ( pev->angles ); + + Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT(40,90) + gpGlobals->v_up * RANDOM_FLOAT(75,200) + gpGlobals->v_forward * RANDOM_FLOAT(-40, 40); + EjectBrass ( vecShootOrigin - vecShootDir * 24, vecShellVelocity, pev->angles.y, m_iShotgunShell, TE_BOUNCE_SHOTSHELL); + FireBullets(gSkillData.hgruntShotgunPellets, vecShootOrigin, vecShootDir, VECTOR_CONE_15DEGREES, 2048, BULLET_PLAYER_BUCKSHOT, 0 ); // shoot +-7.5 degrees + + pev->effects |= EF_MUZZLEFLASH; + + m_cAmmoLoaded--;// take away a bullet! + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CHGrunt :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + Vector vecShootDir; + Vector vecShootOrigin; + + switch( pEvent->event ) + { + case HGRUNT_AE_DROP_GUN: + { + Vector vecGunPos; + Vector vecGunAngles; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + // switch to body group with no gun. + SetBodygroup( GUN_GROUP, GUN_NONE ); + + // now spawn a gun. + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) + { + DropItem( "weapon_shotgun", vecGunPos, vecGunAngles ); + } + else + { + DropItem( "weapon_9mmAR", vecGunPos, vecGunAngles ); + } + if (FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER )) + { + DropItem( "ammo_ARgrenades", BodyTarget( pev->origin ), vecGunAngles ); + } + + } + break; + + case HGRUNT_AE_RELOAD: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "hgrunt/gr_reload1.wav", 1, ATTN_NORM ); + m_cAmmoLoaded = m_cClipSize; + ClearConditions(bits_COND_NO_AMMO_LOADED); + break; + + case HGRUNT_AE_GREN_TOSS: + { + UTIL_MakeVectors( pev->angles ); + // CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 34 + Vector (0, 0, 32), m_vecTossVelocity, 3.5 ); + CGrenade::ShootTimed( pev, GetGunPosition(), m_vecTossVelocity, 3.5 ); + + m_fThrowGrenade = FALSE; + m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. + // !!!LATER - when in a group, only try to throw grenade if ordered. + } + break; + + case HGRUNT_AE_GREN_LAUNCH: + { + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/glauncher.wav", 0.8, ATTN_NORM); + CGrenade::ShootContact( pev, GetGunPosition(), m_vecTossVelocity ); + m_fThrowGrenade = FALSE; + if (g_iSkillLevel == SKILL_HARD) + m_flNextGrenadeCheck = gpGlobals->time + RANDOM_FLOAT( 2, 5 );// wait a random amount of time before shooting again + else + m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. + } + break; + + case HGRUNT_AE_GREN_DROP: + { + UTIL_MakeVectors( pev->angles ); + CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 17 - gpGlobals->v_right * 27 + gpGlobals->v_up * 6, g_vecZero, 3 ); + } + break; + + case HGRUNT_AE_BURST1: + { + if ( FBitSet( pev->weapons, HGRUNT_9MMAR )) + { + Shoot(); + + // the first round of the three round burst plays the sound and puts a sound in the world sound list. + if ( RANDOM_LONG(0,1) ) + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "hgrunt/gr_mgun1.wav", 1, ATTN_NORM ); + } + else + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "hgrunt/gr_mgun2.wav", 1, ATTN_NORM ); + } + } + else + { + Shotgun( ); + + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/sbarrel1.wav", 1, ATTN_NORM ); + } + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 384, 0.3 ); + } + break; + + case HGRUNT_AE_BURST2: + case HGRUNT_AE_BURST3: + Shoot(); + break; + + case HGRUNT_AE_KICK: + { + CBaseEntity *pHurt = Kick(); + + if ( pHurt ) + { + // SOUND HERE! + UTIL_MakeVectors( pev->angles ); + pHurt->pev->punchangle.x = 15; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 100 + gpGlobals->v_up * 50; + pHurt->TakeDamage( pev, pev, gSkillData.hgruntDmgKick, DMG_CLUB ); + } + } + break; + + case HGRUNT_AE_CAUGHT_ENEMY: + { + if ( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz(ENT(pev), "HG_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + + } + + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CHGrunt :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/hgrunt.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->effects = 0; + pev->health = gSkillData.hgruntHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_flNextGrenadeCheck = gpGlobals->time + 1; + m_flNextPainTime = gpGlobals->time; + m_iSentence = HGRUNT_SENT_NONE; + + m_afCapability = bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; + + m_fEnemyEluded = FALSE; + m_fFirstEncounter = TRUE;// this is true when the grunt spawns, because he hasn't encountered an enemy yet. + + m_HackedGunPos = Vector ( 0, 0, 55 ); + + if (pev->weapons == 0) + { + // initialize to original values + pev->weapons = HGRUNT_9MMAR | HGRUNT_HANDGRENADE; + // pev->weapons = HGRUNT_SHOTGUN; + // pev->weapons = HGRUNT_9MMAR | HGRUNT_GRENADELAUNCHER; + } + + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) + { + SetBodygroup( GUN_GROUP, GUN_SHOTGUN ); + m_cClipSize = 8; + } + else + { + m_cClipSize = GRUNT_CLIP_SIZE; + } + m_cAmmoLoaded = m_cClipSize; + + if (RANDOM_LONG( 0, 99 ) < 80) + pev->skin = 0; // light skin + else + pev->skin = 1; // dark skin + + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) + { + SetBodygroup( HEAD_GROUP, HEAD_SHOTGUN); + } + else if (FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER )) + { + SetBodygroup( HEAD_GROUP, HEAD_M203 ); + pev->skin = 1; // alway dark skin + } + + CTalkMonster::g_talkWaitTime = 0; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CHGrunt :: Precache() +{ + PRECACHE_MODEL("models/hgrunt.mdl"); + + PRECACHE_SOUND( "hgrunt/gr_mgun1.wav" ); + PRECACHE_SOUND( "hgrunt/gr_mgun2.wav" ); + + PRECACHE_SOUND( "hgrunt/gr_die1.wav" ); + PRECACHE_SOUND( "hgrunt/gr_die2.wav" ); + PRECACHE_SOUND( "hgrunt/gr_die3.wav" ); + + PRECACHE_SOUND( "hgrunt/gr_pain1.wav" ); + PRECACHE_SOUND( "hgrunt/gr_pain2.wav" ); + PRECACHE_SOUND( "hgrunt/gr_pain3.wav" ); + PRECACHE_SOUND( "hgrunt/gr_pain4.wav" ); + PRECACHE_SOUND( "hgrunt/gr_pain5.wav" ); + + PRECACHE_SOUND( "hgrunt/gr_reload1.wav" ); + + PRECACHE_SOUND( "weapons/glauncher.wav" ); + + PRECACHE_SOUND( "weapons/sbarrel1.wav" ); + + PRECACHE_SOUND("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event + + // get voice pitch + if (RANDOM_LONG(0,1)) + m_voicePitch = 109 + RANDOM_LONG(0,7); + else + m_voicePitch = 100; + + m_iBrassShell = PRECACHE_MODEL ("models/shell.mdl");// brass shell + m_iShotgunShell = PRECACHE_MODEL ("models/shotgunshell.mdl"); +} + +//========================================================= +// start task +//========================================================= +void CHGrunt :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_GRUNT_CHECK_FIRE: + if ( !NoFriendlyFire() ) + { + SetConditions( bits_COND_GRUNT_NOFIRE ); + } + TaskComplete(); + break; + + case TASK_GRUNT_SPEAK_SENTENCE: + SpeakSentence(); + TaskComplete(); + break; + + case TASK_WALK_PATH: + case TASK_RUN_PATH: + // grunt no longer assumes he is covered if he moves + Forget( bits_MEMORY_INCOVER ); + CSquadMonster ::StartTask( pTask ); + break; + + case TASK_RELOAD: + m_IdealActivity = ACT_RELOAD; + break; + + case TASK_GRUNT_FACE_TOSS_DIR: + break; + + case TASK_FACE_IDEAL: + case TASK_FACE_ENEMY: + CSquadMonster :: StartTask( pTask ); + if (pev->movetype == MOVETYPE_FLY) + { + m_IdealActivity = ACT_GLIDE; + } + break; + + default: + CSquadMonster :: StartTask( pTask ); + break; + } +} + +//========================================================= +// RunTask +//========================================================= +void CHGrunt :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_GRUNT_FACE_TOSS_DIR: + { + // project a point along the toss vector and turn to face that point. + MakeIdealYaw( pev->origin + m_vecTossVelocity * 64 ); + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + m_iTaskStatus = TASKSTATUS_COMPLETE; + } + break; + } + default: + { + CSquadMonster :: RunTask( pTask ); + break; + } + } +} + +//========================================================= +// PainSound +//========================================================= +void CHGrunt :: PainSound ( void ) +{ + if ( gpGlobals->time > m_flNextPainTime ) + { +#if 0 + if ( RANDOM_LONG(0,99) < 5 ) + { + // pain sentences are rare + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz(ENT(pev), "HG_PAIN", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, PITCH_NORM); + JustSpoke(); + return; + } + } +#endif + switch ( RANDOM_LONG(0,6) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_pain3.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_pain4.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_pain5.wav", 1, ATTN_NORM ); + break; + case 3: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_pain1.wav", 1, ATTN_NORM ); + break; + case 4: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_pain2.wav", 1, ATTN_NORM ); + break; + } + + m_flNextPainTime = gpGlobals->time + 1; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CHGrunt :: DeathSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_die1.wav", 1, ATTN_IDLE ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_die2.wav", 1, ATTN_IDLE ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_die3.wav", 1, ATTN_IDLE ); + break; + } +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +//========================================================= +// GruntFail +//========================================================= +Task_t tlGruntFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slGruntFail[] = +{ + { + tlGruntFail, + ARRAYSIZE ( tlGruntFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + 0, + "Grunt Fail" + }, +}; + +//========================================================= +// Grunt Combat Fail +//========================================================= +Task_t tlGruntCombatFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slGruntCombatFail[] = +{ + { + tlGruntCombatFail, + ARRAYSIZE ( tlGruntCombatFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Grunt Combat Fail" + }, +}; + +//========================================================= +// Victory dance! +//========================================================= +Task_t tlGruntVictoryDance[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1.5 }, + { TASK_GET_PATH_TO_ENEMY_CORPSE, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, +}; + +Schedule_t slGruntVictoryDance[] = +{ + { + tlGruntVictoryDance, + ARRAYSIZE ( tlGruntVictoryDance ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "GruntVictoryDance" + }, +}; + +//========================================================= +// Establish line of fire - move to a position that allows +// the grunt to attack. +//========================================================= +Task_t tlGruntEstablishLineOfFire[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_GRUNT_ELOF_FAIL }, + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_GRUNT_SPEAK_SENTENCE,(float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slGruntEstablishLineOfFire[] = +{ + { + tlGruntEstablishLineOfFire, + ARRAYSIZE ( tlGruntEstablishLineOfFire ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "GruntEstablishLineOfFire" + }, +}; + +//========================================================= +// GruntFoundEnemy - grunt established sight with an enemy +// that was hiding from the squad. +//========================================================= +Task_t tlGruntFoundEnemy[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY,(float)ACT_SIGNAL1 }, +}; + +Schedule_t slGruntFoundEnemy[] = +{ + { + tlGruntFoundEnemy, + ARRAYSIZE ( tlGruntFoundEnemy ), + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "GruntFoundEnemy" + }, +}; + +//========================================================= +// GruntCombatFace Schedule +//========================================================= +Task_t tlGruntCombatFace1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1.5 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_SWEEP }, +}; + +Schedule_t slGruntCombatFace[] = +{ + { + tlGruntCombatFace1, + ARRAYSIZE ( tlGruntCombatFace1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Combat Face" + }, +}; + +//========================================================= +// Suppressing fire - don't stop shooting until the clip is +// empty or grunt gets hurt. +//========================================================= +Task_t tlGruntSignalSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_SIGNAL2 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntSignalSuppress[] = +{ + { + tlGruntSignalSuppress, + ARRAYSIZE ( tlGruntSignalSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "SignalSuppress" + }, +}; + +Task_t tlGruntSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntSuppress[] = +{ + { + tlGruntSuppress, + ARRAYSIZE ( tlGruntSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "Suppress" + }, +}; + + +//========================================================= +// grunt wait in cover - we don't allow danger or the ability +// to attack to break a grunt's run to cover schedule, but +// when a grunt is in cover, we do want them to attack if they can. +//========================================================= +Task_t tlGruntWaitInCover[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)1 }, +}; + +Schedule_t slGruntWaitInCover[] = +{ + { + tlGruntWaitInCover, + ARRAYSIZE ( tlGruntWaitInCover ), + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + + bits_SOUND_DANGER, + "GruntWaitInCover" + }, +}; + +//========================================================= +// run to cover. +// !!!BUGBUG - set a decent fail schedule here. +//========================================================= +Task_t tlGruntTakeCover1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_GRUNT_TAKECOVER_FAILED }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_GRUNT_SPEAK_SENTENCE, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY }, +}; + +Schedule_t slGruntTakeCover[] = +{ + { + tlGruntTakeCover1, + ARRAYSIZE ( tlGruntTakeCover1 ), + 0, + 0, + "TakeCover" + }, +}; + +//========================================================= +// drop grenade then run to cover. +//========================================================= +Task_t tlGruntGrenadeCover1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)99 }, + { TASK_FIND_FAR_NODE_COVER_FROM_ENEMY, (float)384 }, + { TASK_PLAY_SEQUENCE, (float)ACT_SPECIAL_ATTACK1 }, + { TASK_CLEAR_MOVE_WAIT, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY }, +}; + +Schedule_t slGruntGrenadeCover[] = +{ + { + tlGruntGrenadeCover1, + ARRAYSIZE ( tlGruntGrenadeCover1 ), + 0, + 0, + "GrenadeCover" + }, +}; + + +//========================================================= +// drop grenade then run to cover. +//========================================================= +Task_t tlGruntTossGrenadeCover1[] = +{ + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK2, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_TAKE_COVER_FROM_ENEMY }, +}; + +Schedule_t slGruntTossGrenadeCover[] = +{ + { + tlGruntTossGrenadeCover1, + ARRAYSIZE ( tlGruntTossGrenadeCover1 ), + 0, + 0, + "TossGrenadeCover" + }, +}; + +//========================================================= +// hide from the loudest sound source (to run from grenade) +//========================================================= +Task_t tlGruntTakeCoverFromBestSound[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_COWER },// duck and cover if cannot move from explosion + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_BEST_SOUND, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_TURN_LEFT, (float)179 }, +}; + +Schedule_t slGruntTakeCoverFromBestSound[] = +{ + { + tlGruntTakeCoverFromBestSound, + ARRAYSIZE ( tlGruntTakeCoverFromBestSound ), + 0, + 0, + "GruntTakeCoverFromBestSound" + }, +}; + +//========================================================= +// Grunt reload schedule +//========================================================= +Task_t tlGruntHideReload[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RELOAD }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_RELOAD }, +}; + +Schedule_t slGruntHideReload[] = +{ + { + tlGruntHideReload, + ARRAYSIZE ( tlGruntHideReload ), + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "GruntHideReload" + } +}; + +//========================================================= +// Do a turning sweep of the area +//========================================================= +Task_t tlGruntSweep[] = +{ + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slGruntSweep[] = +{ + { + tlGruntSweep, + ARRAYSIZE ( tlGruntSweep ), + + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_HEAR_SOUND, + + bits_SOUND_WORLD |// sound flags + bits_SOUND_DANGER | + bits_SOUND_PLAYER, + + "Grunt Sweep" + }, +}; + +//========================================================= +// primary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlGruntRangeAttack1A[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCH }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntRangeAttack1A[] = +{ + { + tlGruntRangeAttack1A, + ARRAYSIZE ( tlGruntRangeAttack1A ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "Range Attack1A" + }, +}; + + +//========================================================= +// primary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlGruntRangeAttack1B[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY,(float)ACT_IDLE_ANGRY }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntRangeAttack1B[] = +{ + { + tlGruntRangeAttack1B, + ARRAYSIZE ( tlGruntRangeAttack1B ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_GRUNT_NOFIRE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Range Attack1B" + }, +}; + +//========================================================= +// secondary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlGruntRangeAttack2[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_GRUNT_FACE_TOSS_DIR, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_RANGE_ATTACK2 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY },// don't run immediately after throwing grenade. +}; + +Schedule_t slGruntRangeAttack2[] = +{ + { + tlGruntRangeAttack2, + ARRAYSIZE ( tlGruntRangeAttack2 ), + 0, + 0, + "RangeAttack2" + }, +}; + + +//========================================================= +// repel +//========================================================= +Task_t tlGruntRepel[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_GLIDE }, +}; + +Schedule_t slGruntRepel[] = +{ + { + tlGruntRepel, + ARRAYSIZE ( tlGruntRepel ), + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER, + "Repel" + }, +}; + + +//========================================================= +// repel +//========================================================= +Task_t tlGruntRepelAttack[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_FLY }, +}; + +Schedule_t slGruntRepelAttack[] = +{ + { + tlGruntRepelAttack, + ARRAYSIZE ( tlGruntRepelAttack ), + bits_COND_ENEMY_OCCLUDED, + 0, + "Repel Attack" + }, +}; + +//========================================================= +// repel land +//========================================================= +Task_t tlGruntRepelLand[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_LAND }, + { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, +}; + +Schedule_t slGruntRepelLand[] = +{ + { + tlGruntRepelLand, + ARRAYSIZE ( tlGruntRepelLand ), + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER, + "Repel Land" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CHGrunt ) +{ + slGruntFail, + slGruntCombatFail, + slGruntVictoryDance, + slGruntEstablishLineOfFire, + slGruntFoundEnemy, + slGruntCombatFace, + slGruntSignalSuppress, + slGruntSuppress, + slGruntWaitInCover, + slGruntTakeCover, + slGruntGrenadeCover, + slGruntTossGrenadeCover, + slGruntTakeCoverFromBestSound, + slGruntHideReload, + slGruntSweep, + slGruntRangeAttack1A, + slGruntRangeAttack1B, + slGruntRangeAttack2, + slGruntRepel, + slGruntRepelAttack, + slGruntRepelLand, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CHGrunt, CSquadMonster ); + +//========================================================= +// SetActivity +//========================================================= +void CHGrunt :: SetActivity ( Activity NewActivity ) +{ + int iSequence = ACTIVITY_NOT_AVAILABLE; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + switch ( NewActivity) + { + case ACT_RANGE_ATTACK1: + // grunt is either shooting standing or shooting crouched + if (FBitSet( pev->weapons, HGRUNT_9MMAR)) + { + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "standing_mp5" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouching_mp5" ); + } + } + else + { + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "standing_shotgun" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouching_shotgun" ); + } + } + break; + case ACT_RANGE_ATTACK2: + // grunt is going to a secondary long range attack. This may be a thrown + // grenade or fired grenade, we must determine which and pick proper sequence + if ( pev->weapons & HGRUNT_HANDGRENADE ) + { + // get toss anim + iSequence = LookupSequence( "throwgrenade" ); + } + else + { + // get launch anim + iSequence = LookupSequence( "launchgrenade" ); + } + break; + case ACT_RUN: + if ( pev->health <= HGRUNT_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_RUN_HURT ); + } + else + { + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_WALK: + if ( pev->health <= HGRUNT_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_WALK_HURT ); + } + else + { + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_IDLE: + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + { + NewActivity = ACT_IDLE_ANGRY; + } + iSequence = LookupActivity ( NewActivity ); + break; + default: + iSequence = LookupActivity ( NewActivity ); + break; + } + + m_Activity = NewActivity; // Go ahead and set this so it doesn't keep trying when the anim is not present + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + if ( pev->sequence != iSequence || !m_fSequenceLoops ) + { + pev->frame = 0; + } + + pev->sequence = iSequence; // Set to the reset anim (if it's there) + ResetSequenceInfo( ); + SetYawSpeed(); + } + else + { + // Not available try to get default anim + ALERT ( at_console, "%s has no sequence for act:%d\n", STRING(pev->classname), NewActivity ); + pev->sequence = 0; // Set to the reset anim (if it's there) + } +} + +//========================================================= +// Get Schedule! +//========================================================= +Schedule_t *CHGrunt :: GetSchedule( void ) +{ + + // clear old sentence + m_iSentence = HGRUNT_SENT_NONE; + + // flying? If PRONE, barnacle has me. IF not, it's assumed I am rapelling. + if ( pev->movetype == MOVETYPE_FLY && m_MonsterState != MONSTERSTATE_PRONE ) + { + if (pev->flags & FL_ONGROUND) + { + // just landed + pev->movetype = MOVETYPE_STEP; + return GetScheduleOfType ( SCHED_GRUNT_REPEL_LAND ); + } + else + { + // repel down a rope, + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + return GetScheduleOfType ( SCHED_GRUNT_REPEL_ATTACK ); + else + return GetScheduleOfType ( SCHED_GRUNT_REPEL ); + } + } + + // grunts place HIGH priority on running away from danger sounds. + if ( HasConditions(bits_COND_HEAR_SOUND) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound) + { + if (pSound->m_iType & bits_SOUND_DANGER) + { + // dangerous sound nearby! + + //!!!KELLY - currently, this is the grunt's signal that a grenade has landed nearby, + // and the grunt should find cover from the blast + // good place for "SHIT!" or some other colorful verbal indicator of dismay. + // It's not safe to play a verbal order here "Scatter", etc cause + // this may only affect a single individual in a squad. + + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), "HG_GREN", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + /* + if (!HasConditions( bits_COND_SEE_ENEMY ) && ( pSound->m_iType & (bits_SOUND_PLAYER | bits_SOUND_COMBAT) )) + { + MakeIdealYaw( pSound->m_vecOrigin ); + } + */ + } + } + switch ( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + +// new enemy + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + if ( InSquad() ) + { + MySquadLeader()->m_fEnemyEluded = FALSE; + + if ( !IsLeader() ) + { + return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); + } + else + { + //!!!KELLY - the leader of a squad of grunts has just seen the player or a + // monster and has made it the squad's enemy. You + // can check pev->flags for FL_CLIENT to determine whether this is the player + // or a monster. He's going to immediately start + // firing, though. If you'd like, we can make an alternate "first sight" + // schedule where the leader plays a handsign anim + // that gives us enough time to hear a short sentence or spoken command + // before he starts pluggin away. + if (FOkToSpeak())// && RANDOM_LONG(0,1)) + { + if ((m_hEnemy != NULL) && m_hEnemy->IsPlayer()) + // player + SENTENCEG_PlayRndSz( ENT(pev), "HG_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + else if ((m_hEnemy != NULL) && + (m_hEnemy->Classify() != CLASS_PLAYER_ALLY) && + (m_hEnemy->Classify() != CLASS_HUMAN_PASSIVE) && + (m_hEnemy->Classify() != CLASS_MACHINE)) + // monster + SENTENCEG_PlayRndSz( ENT(pev), "HG_MONST", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + + JustSpoke(); + } + + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_GRUNT_SUPPRESS ); + } + else + { + return GetScheduleOfType ( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + } + } + } +// no ammo + else if ( HasConditions ( bits_COND_NO_AMMO_LOADED ) ) + { + //!!!KELLY - this individual just realized he's out of bullet ammo. + // He's going to try to find cover to run to and reload, but rarely, if + // none is available, he'll drop and reload in the open here. + return GetScheduleOfType ( SCHED_GRUNT_COVER_AND_RELOAD ); + } + +// damaged just a little + else if ( HasConditions( bits_COND_LIGHT_DAMAGE ) ) + { + // if hurt: + // 90% chance of taking cover + // 10% chance of flinch. + int iPercent = RANDOM_LONG(0,99); + + if ( iPercent <= 90 && m_hEnemy != NULL ) + { + // only try to take cover if we actually have an enemy! + + //!!!KELLY - this grunt was hit and is going to run to cover. + if (FOkToSpeak()) // && RANDOM_LONG(0,1)) + { + //SENTENCEG_PlayRndSz( ENT(pev), "HG_COVER", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + m_iSentence = HGRUNT_SENT_COVER; + //JustSpoke(); + } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + else + { + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + } +// can kick + else if ( HasConditions ( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); + } +// can grenade launch + + else if ( FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER) && HasConditions ( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + // shoot a grenade if you can + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } +// can shoot + else if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + if ( InSquad() ) + { + // if the enemy has eluded the squad and a squad member has just located the enemy + // and the enemy does not see the squad member, issue a call to the squad to waste a + // little time and give the player a chance to turn. + if ( MySquadLeader()->m_fEnemyEluded && !HasConditions ( bits_COND_ENEMY_FACING_ME ) ) + { + MySquadLeader()->m_fEnemyEluded = FALSE; + return GetScheduleOfType ( SCHED_GRUNT_FOUND_ENEMY ); + } + } + + if ( OccupySlot ( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + // try to take an available ENGAGE slot + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + else if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + // throw a grenade if can and no engage slots are available + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + else + { + // hide! + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + } +// can't see enemy + else if ( HasConditions( bits_COND_ENEMY_OCCLUDED ) ) + { + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + //!!!KELLY - this grunt is about to throw or fire a grenade at the player. Great place for "fire in the hole" "frag out" etc + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), "HG_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + else if ( OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + //!!!KELLY - grunt cannot see the enemy and has just decided to + // charge the enemy's position. + if (FOkToSpeak())// && RANDOM_LONG(0,1)) + { + //SENTENCEG_PlayRndSz( ENT(pev), "HG_CHARGE", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + m_iSentence = HGRUNT_SENT_CHARGE; + //JustSpoke(); + } + + return GetScheduleOfType( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + else + { + //!!!KELLY - grunt is going to stay put for a couple seconds to see if + // the enemy wanders back out into the open, or approaches the + // grunt's covered position. Good place for a taunt, I guess? + if (FOkToSpeak() && RANDOM_LONG(0,1)) + { + SENTENCEG_PlayRndSz( ENT(pev), "HG_TAUNT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return GetScheduleOfType( SCHED_STANDOFF ); + } + } + + if ( HasConditions( bits_COND_SEE_ENEMY ) && !HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + } + } + + // no special cases here, call the base class + return CSquadMonster :: GetSchedule(); +} + +//========================================================= +//========================================================= +Schedule_t* CHGrunt :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_TAKE_COVER_FROM_ENEMY: + { + if ( InSquad() ) + { + if ( g_iSkillLevel == SKILL_HARD && HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), "HG_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return slGruntTossGrenadeCover; + } + else + { + return &slGruntTakeCover[ 0 ]; + } + } + else + { + if ( RANDOM_LONG(0,1) ) + { + return &slGruntTakeCover[ 0 ]; + } + else + { + return &slGruntGrenadeCover[ 0 ]; + } + } + } + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + { + return &slGruntTakeCoverFromBestSound[ 0 ]; + } + case SCHED_GRUNT_TAKECOVER_FAILED: + { + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) && OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + + return GetScheduleOfType ( SCHED_FAIL ); + } + break; + case SCHED_GRUNT_ELOF_FAIL: + { + // human grunt is unable to move to a position that allows him to attack the enemy. + return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); + } + break; + case SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE: + { + return &slGruntEstablishLineOfFire[ 0 ]; + } + break; + case SCHED_RANGE_ATTACK1: + { + // randomly stand or crouch + if (RANDOM_LONG(0,9) == 0) + m_fStanding = RANDOM_LONG(0,1); + + if (m_fStanding) + return &slGruntRangeAttack1B[ 0 ]; + else + return &slGruntRangeAttack1A[ 0 ]; + } + case SCHED_RANGE_ATTACK2: + { + return &slGruntRangeAttack2[ 0 ]; + } + case SCHED_COMBAT_FACE: + { + return &slGruntCombatFace[ 0 ]; + } + case SCHED_GRUNT_WAIT_FACE_ENEMY: + { + return &slGruntWaitInCover[ 0 ]; + } + case SCHED_GRUNT_SWEEP: + { + return &slGruntSweep[ 0 ]; + } + case SCHED_GRUNT_COVER_AND_RELOAD: + { + return &slGruntHideReload[ 0 ]; + } + case SCHED_GRUNT_FOUND_ENEMY: + { + return &slGruntFoundEnemy[ 0 ]; + } + case SCHED_VICTORY_DANCE: + { + if ( InSquad() ) + { + if ( !IsLeader() ) + { + return &slGruntFail[ 0 ]; + } + } + + return &slGruntVictoryDance[ 0 ]; + } + case SCHED_GRUNT_SUPPRESS: + { + if ( m_hEnemy->IsPlayer() && m_fFirstEncounter ) + { + m_fFirstEncounter = FALSE;// after first encounter, leader won't issue handsigns anymore when he has a new enemy + return &slGruntSignalSuppress[ 0 ]; + } + else + { + return &slGruntSuppress[ 0 ]; + } + } + case SCHED_FAIL: + { + if ( m_hEnemy != NULL ) + { + // grunt has an enemy, so pick a different default fail schedule most likely to help recover. + return &slGruntCombatFail[ 0 ]; + } + + return &slGruntFail[ 0 ]; + } + case SCHED_GRUNT_REPEL: + { + if (pev->velocity.z > -128) + pev->velocity.z -= 32; + return &slGruntRepel[ 0 ]; + } + case SCHED_GRUNT_REPEL_ATTACK: + { + if (pev->velocity.z > -128) + pev->velocity.z -= 32; + return &slGruntRepelAttack[ 0 ]; + } + case SCHED_GRUNT_REPEL_LAND: + { + return &slGruntRepelLand[ 0 ]; + } + default: + { + return CSquadMonster :: GetScheduleOfType ( Type ); + } + } +} + + +//========================================================= +// CHGruntRepel - when triggered, spawns a monster_human_grunt +// repelling down a line. +//========================================================= + +class CHGruntRepel : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT RepelUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int m_iSpriteTexture; // Don't save, precache +}; + +LINK_ENTITY_TO_CLASS( monster_grunt_repel, CHGruntRepel ); + +void CHGruntRepel::Spawn( void ) +{ + Precache( ); + pev->solid = SOLID_NOT; + + SetUse( &CHGruntRepel::RepelUse ); +} + +void CHGruntRepel::Precache( void ) +{ + UTIL_PrecacheOther( "monster_human_grunt" ); + m_iSpriteTexture = PRECACHE_MODEL( "sprites/rope.spr" ); +} + +void CHGruntRepel::RepelUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + TraceResult tr; + UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, -4096.0), dont_ignore_monsters, ENT(pev), &tr); + /* + if ( tr.pHit && Instance( tr.pHit )->pev->solid != SOLID_BSP) + return NULL; + */ + + CBaseEntity *pEntity = Create( "monster_human_grunt", pev->origin, pev->angles ); + CBaseMonster *pGrunt = pEntity->MyMonsterPointer( ); + pGrunt->pev->movetype = MOVETYPE_FLY; + pGrunt->pev->velocity = Vector( 0, 0, RANDOM_FLOAT( -196, -128 ) ); + pGrunt->SetActivity( ACT_GLIDE ); + // UNDONE: position? + pGrunt->m_vecLastPosition = tr.vecEndPos; + + CBeam *pBeam = CBeam::BeamCreate( "sprites/rope.spr", 10 ); + pBeam->PointEntInit( pev->origin + Vector(0,0,112), pGrunt->entindex() ); + pBeam->SetFlags( BEAM_FSOLID ); + pBeam->SetColor( 255, 255, 255 ); + pBeam->SetThink( &CBeam::SUB_Remove ); + pBeam->pev->nextthink = gpGlobals->time + -4096.0 * tr.flFraction / pGrunt->pev->velocity.z + 0.5; + + UTIL_Remove( this ); +} + + + +//========================================================= +// DEAD HGRUNT PROP +//========================================================= +class CDeadHGrunt : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_HUMAN_MILITARY; } + + void KeyValue( KeyValueData *pkvd ); + + int m_iPose;// which sequence to display -- temporary, don't need to save + static char *m_szPoses[3]; +}; + +char *CDeadHGrunt::m_szPoses[] = { "deadstomach", "deadside", "deadsitting" }; + +void CDeadHGrunt::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "pose")) + { + m_iPose = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +LINK_ENTITY_TO_CLASS( monster_hgrunt_dead, CDeadHGrunt ); + +//========================================================= +// ********** DeadHGrunt SPAWN ********** +//========================================================= +void CDeadHGrunt :: Spawn( void ) +{ + PRECACHE_MODEL("models/hgrunt.mdl"); + SET_MODEL(ENT(pev), "models/hgrunt.mdl"); + + pev->effects = 0; + pev->yaw_speed = 8; + pev->sequence = 0; + m_bloodColor = BLOOD_COLOR_RED; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + + if (pev->sequence == -1) + { + ALERT ( at_console, "Dead hgrunt with bad pose\n" ); + } + + // Corpses have less health + pev->health = 8; + + // map old bodies onto new bodies + switch( pev->body ) + { + case 0: // Grunt with Gun + pev->body = 0; + pev->skin = 0; + SetBodygroup( HEAD_GROUP, HEAD_GRUNT ); + SetBodygroup( GUN_GROUP, GUN_MP5 ); + break; + case 1: // Commander with Gun + pev->body = 0; + pev->skin = 0; + SetBodygroup( HEAD_GROUP, HEAD_COMMANDER ); + SetBodygroup( GUN_GROUP, GUN_MP5 ); + break; + case 2: // Grunt no Gun + pev->body = 0; + pev->skin = 0; + SetBodygroup( HEAD_GROUP, HEAD_GRUNT ); + SetBodygroup( GUN_GROUP, GUN_NONE ); + break; + case 3: // Commander no Gun + pev->body = 0; + pev->skin = 0; + SetBodygroup( HEAD_GROUP, HEAD_COMMANDER ); + SetBodygroup( GUN_GROUP, GUN_NONE ); + break; + } + + MonsterInitDead(); +} diff --git a/dlls/hl.def b/dlls/hl.def new file mode 100644 index 0000000..b4211ab --- /dev/null +++ b/dlls/hl.def @@ -0,0 +1,5 @@ +LIBRARY hl +EXPORTS + GiveFnptrsToDll @1 +SECTIONS + .data READ WRITE diff --git a/dlls/hl.dsp b/dlls/hl.dsp new file mode 100644 index 0000000..13ef157 --- /dev/null +++ b/dlls/hl.dsp @@ -0,0 +1,1637 @@ +# Microsoft Developer Studio Project File - Name="hl" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=hl - 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 "hl.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 "hl.mak" CFG="hl - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "hl - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "hl - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "hl - 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 ".\Releasehl" +# PROP Intermediate_Dir ".\Releasehl" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /G5 /MT /W3 /GR /Zi /O2 /I "..\dlls" /I "..\engine" /I "..\common" /I "..\pm_shared" /I "..\game_shared" /I "..\public" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "QUIVER" /D "VOXEL" /D "QUAKE2" /D "VALVE_DLL" /D "CLIENT_WEAPONS" /Fr /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /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 /subsystem:windows /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /map /debug /machine:I386 /def:".\hl.def" +# SUBTRACT LINK32 /profile +# Begin Custom Build +InputDir=.\Releasehl +ProjDir=. +InputPath=.\Releasehl\hl.dll +InputName=hl +SOURCE="$(InputPath)" + +BuildCmds= \ + call ..\filecopy.bat $(InputPath) $(ProjDir)\..\..\game\mod\dlls\$(InputName).dll \ + call ..\filecopy.bat $(InputDir)\$(InputName).pdb $(ProjDir)\..\..\game\moddlls\$(InputName).pdb \ + + +"$(ProjDir)\..\..\game\mod\dlls\$(InputName).dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"$(ProjDir)\..\..\game\mod\dlls\$(InputName).pdb" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) +# End Custom Build + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\hl___Win" +# PROP BASE Intermediate_Dir ".\hl___Win" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir ".\debughl" +# PROP Intermediate_Dir ".\debughl" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /G5 /MTd /W3 /Gm /ZI /Od /I "..\\" /I "..\dlls" /I "..\engine" /I "..\common" /I "..\pm_shared" /I "..\game_shared" /I "..\public" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "QUIVER" /D "VOXEL" /D "QUAKE2" /D "VALVE_DLL" /D "CLIENT_WEAPONS" /FR /YX /FD /c +# ADD BASE MTL /nologo /D "_DEBUG" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /i "..\engine" /d "_DEBUG" +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 /subsystem:windows /dll /debug /machine:I386 +# ADD LINK32 user32.lib advapi32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /def:".\hl.def" /implib:".\Debug\hl.lib" +# SUBTRACT LINK32 /profile +# Begin Custom Build +ProjDir=. +InputPath=.\debughl\hl.dll +InputName=hl +SOURCE="$(InputPath)" + +"$(ProjDir)\..\..\game\mod\dlls\$(InputName).dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + call ..\filecopy.bat $(InputPath) $(ProjDir)\..\..\game\mod\dlls\$(InputName).dll + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "hl - Win32 Release" +# Name "hl - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=.\aflock.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\agrunt.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\airtank.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\animating.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\animation.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\apache.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\barnacle.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\barney.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\bigmomma.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\bloater.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\bmodels.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\bullsquid.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\buttons.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cbase.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\client.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\combat.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\controller.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\crossbow.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\crowbar.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\defaultai.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\doors.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\effects.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\egon.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\explode.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\flyingmonster.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\func_break.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\func_tank.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gamerules.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gargantua.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gauss.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\genericmonster.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ggrenade.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\globals.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gman.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\h_ai.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\h_battery.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\h_cine.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\h_cycler.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\h_export.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\handgrenade.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\hassassin.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\headcrab.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\healthkit.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\hgrunt.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\wpn_shared\hl_wpn_glock.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\hornet.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\hornetgun.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\houndeye.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ichthyosaur.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\islave.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\items.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\leech.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\lights.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\maprules.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\monstermaker.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\monsters.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\monsterstate.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\mortar.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\mp5.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\multiplay_gamerules.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\nihilanth.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\nodes.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\observer.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\osprey.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\pathcorner.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\plane.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\plats.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\player.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_debug.c + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_math.c + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_shared.c + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\python.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\rat.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\roach.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\rpg.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\satchel.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\schedule.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\scientist.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\scripted.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\shotgun.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\singleplay_gamerules.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\skill.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sound.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\soundent.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\spectator.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\squadmonster.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\squeakgrenade.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\subs.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\talkmonster.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\teamplay_gamerules.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\tempmonster.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\tentacle.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\triggers.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\tripmine.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\turret.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\util.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\game_shared\voice_gamemgr.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\weapons.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\world.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\xen.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\zombie.cpp + +!IF "$(CFG)" == "hl - Win32 Release" + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# ADD CPP /GR + +!ENDIF + +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=.\activity.h +# End Source File +# Begin Source File + +SOURCE=.\activitymap.h +# End Source File +# Begin Source File + +SOURCE=.\animation.h +# End Source File +# Begin Source File + +SOURCE=.\basemonster.h +# End Source File +# Begin Source File + +SOURCE=.\cbase.h +# End Source File +# Begin Source File + +SOURCE=.\cdll_dll.h +# End Source File +# Begin Source File + +SOURCE=.\client.h +# End Source File +# Begin Source File + +SOURCE=.\decals.h +# End Source File +# Begin Source File + +SOURCE=.\defaultai.h +# End Source File +# Begin Source File + +SOURCE=.\doors.h +# End Source File +# Begin Source File + +SOURCE=.\effects.h +# End Source File +# Begin Source File + +SOURCE=..\engine\eiface.h +# End Source File +# Begin Source File + +SOURCE=.\enginecallback.h +# End Source File +# Begin Source File + +SOURCE=.\explode.h +# End Source File +# Begin Source File + +SOURCE=.\extdll.h +# End Source File +# Begin Source File + +SOURCE=.\flyingmonster.h +# End Source File +# Begin Source File + +SOURCE=.\func_break.h +# End Source File +# Begin Source File + +SOURCE=.\gamerules.h +# End Source File +# Begin Source File + +SOURCE=.\hornet.h +# End Source File +# Begin Source File + +SOURCE=.\items.h +# End Source File +# Begin Source File + +SOURCE=.\monsterevent.h +# End Source File +# Begin Source File + +SOURCE=.\monsters.h +# End Source File +# Begin Source File + +SOURCE=.\nodes.h +# End Source File +# Begin Source File + +SOURCE=.\plane.h +# End Source File +# Begin Source File + +SOURCE=.\player.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_debug.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_defs.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_info.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_materials.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_movevars.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_shared.h +# End Source File +# Begin Source File + +SOURCE=.\saverestore.h +# End Source File +# Begin Source File + +SOURCE=.\schedule.h +# End Source File +# Begin Source File + +SOURCE=.\scripted.h +# End Source File +# Begin Source File + +SOURCE=.\scriptevent.h +# End Source File +# Begin Source File + +SOURCE=.\skill.h +# End Source File +# Begin Source File + +SOURCE=.\soundent.h +# End Source File +# Begin Source File + +SOURCE=.\spectator.h +# End Source File +# Begin Source File + +SOURCE=.\squadmonster.h +# End Source File +# Begin Source File + +SOURCE=.\talkmonster.h +# End Source File +# Begin Source File + +SOURCE=.\teamplay_gamerules.h +# End Source File +# Begin Source File + +SOURCE=.\trains.h +# End Source File +# Begin Source File + +SOURCE=.\util.h +# End Source File +# Begin Source File + +SOURCE=.\vector.h +# End Source File +# Begin Source File + +SOURCE=.\weapons.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/dlls/hlgl.def b/dlls/hlgl.def new file mode 100644 index 0000000..b94aa63 --- /dev/null +++ b/dlls/hlgl.def @@ -0,0 +1,15 @@ +LIBRARY hlgl +EXPORTS + GiveFnptrsToDll @1 + GetEntityInterfaces @2 + SetChangeParms @3 + SetNewParms @4 + ClientKill @5 + PutClientInServer @6 + PlayerPreThink @7 + PlayerPostThink @8 + ClientConnect @9 + ClientDisconnect @10 + StartFrame @11 +SECTIONS + .data READ WRITE diff --git a/dlls/hornet.cpp b/dlls/hornet.cpp new file mode 100644 index 0000000..7efeaa6 --- /dev/null +++ b/dlls/hornet.cpp @@ -0,0 +1,419 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// Hornets +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "soundent.h" +#include "hornet.h" +#include "gamerules.h" + + +int iHornetTrail; +int iHornetPuff; + +LINK_ENTITY_TO_CLASS( hornet, CHornet ); + +//========================================================= +// Save/Restore +//========================================================= +TYPEDESCRIPTION CHornet::m_SaveData[] = +{ + DEFINE_FIELD( CHornet, m_flStopAttack, FIELD_TIME ), + DEFINE_FIELD( CHornet, m_iHornetType, FIELD_INTEGER ), + DEFINE_FIELD( CHornet, m_flFlySpeed, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CHornet, CBaseMonster ); + +//========================================================= +// don't let hornets gib, ever. +//========================================================= +int CHornet :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // filter these bits a little. + bitsDamageType &= ~ ( DMG_ALWAYSGIB ); + bitsDamageType |= DMG_NEVERGIB; + + return CBaseMonster :: TakeDamage ( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +//========================================================= +void CHornet :: Spawn( void ) +{ + Precache(); + + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + pev->takedamage = DAMAGE_YES; + pev->flags |= FL_MONSTER; + pev->health = 1;// weak! + + if ( g_pGameRules->IsMultiplayer() ) + { + // hornets don't live as long in multiplayer + m_flStopAttack = gpGlobals->time + 3.5; + } + else + { + m_flStopAttack = gpGlobals->time + 5.0; + } + + m_flFieldOfView = 0.9; // +- 25 degrees + + if ( RANDOM_LONG ( 1, 5 ) <= 2 ) + { + m_iHornetType = HORNET_TYPE_RED; + m_flFlySpeed = HORNET_RED_SPEED; + } + else + { + m_iHornetType = HORNET_TYPE_ORANGE; + m_flFlySpeed = HORNET_ORANGE_SPEED; + } + + SET_MODEL(ENT( pev ), "models/hornet.mdl"); + UTIL_SetSize( pev, Vector( -4, -4, -4 ), Vector( 4, 4, 4 ) ); + + SetTouch( &CHornet::DieTouch ); + SetThink( &CHornet::StartTrack ); + + edict_t *pSoundEnt = pev->owner; + if ( !pSoundEnt ) + pSoundEnt = edict(); + + if ( !FNullEnt(pev->owner) && (pev->owner->v.flags & FL_CLIENT) ) + { + pev->dmg = gSkillData.plrDmgHornet; + } + else + { + // no real owner, or owner isn't a client. + pev->dmg = gSkillData.monDmgHornet; + } + + pev->nextthink = gpGlobals->time + 0.1; + ResetSequenceInfo( ); +} + + +void CHornet :: Precache() +{ + PRECACHE_MODEL("models/hornet.mdl"); + + PRECACHE_SOUND( "agrunt/ag_fire1.wav" ); + PRECACHE_SOUND( "agrunt/ag_fire2.wav" ); + PRECACHE_SOUND( "agrunt/ag_fire3.wav" ); + + PRECACHE_SOUND( "hornet/ag_buzz1.wav" ); + PRECACHE_SOUND( "hornet/ag_buzz2.wav" ); + PRECACHE_SOUND( "hornet/ag_buzz3.wav" ); + + PRECACHE_SOUND( "hornet/ag_hornethit1.wav" ); + PRECACHE_SOUND( "hornet/ag_hornethit2.wav" ); + PRECACHE_SOUND( "hornet/ag_hornethit3.wav" ); + + iHornetPuff = PRECACHE_MODEL( "sprites/muz1.spr" ); + iHornetTrail = PRECACHE_MODEL("sprites/laserbeam.spr"); +} + +//========================================================= +// hornets will never get mad at each other, no matter who the owner is. +//========================================================= +int CHornet::IRelationship ( CBaseEntity *pTarget ) +{ + if ( pTarget->pev->modelindex == pev->modelindex ) + { + return R_NO; + } + + return CBaseMonster :: IRelationship( pTarget ); +} + +//========================================================= +// ID's Hornet as their owner +//========================================================= +int CHornet::Classify ( void ) +{ + + if ( pev->owner && pev->owner->v.flags & FL_CLIENT) + { + return CLASS_PLAYER_BIOWEAPON; + } + + return CLASS_ALIEN_BIOWEAPON; +} + +//========================================================= +// StartTrack - starts a hornet out tracking its target +//========================================================= +void CHornet :: StartTrack ( void ) +{ + IgniteTrail(); + + SetTouch( &CHornet::TrackTouch ); + SetThink( &CHornet::TrackTarget ); + + pev->nextthink = gpGlobals->time + 0.1; +} + +//========================================================= +// StartDart - starts a hornet out just flying straight. +//========================================================= +void CHornet :: StartDart ( void ) +{ + IgniteTrail(); + + SetTouch( &CHornet::DartTouch ); + + SetThink( &CHornet::SUB_Remove ); + pev->nextthink = gpGlobals->time + 4; +} + +void CHornet::IgniteTrail( void ) +{ +/* + + ted's suggested trail colors: + +r161 +g25 +b97 + +r173 +g39 +b14 + +old colors + case HORNET_TYPE_RED: + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 0 ); // r, g, b + break; + case HORNET_TYPE_ORANGE: + WRITE_BYTE( 0 ); // r, g, b + WRITE_BYTE( 100 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + break; + +*/ + + // trail + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMFOLLOW ); + WRITE_SHORT( entindex() ); // entity + WRITE_SHORT( iHornetTrail ); // model + WRITE_BYTE( 10 ); // life + WRITE_BYTE( 2 ); // width + + switch ( m_iHornetType ) + { + case HORNET_TYPE_RED: + WRITE_BYTE( 179 ); // r, g, b + WRITE_BYTE( 39 ); // r, g, b + WRITE_BYTE( 14 ); // r, g, b + break; + case HORNET_TYPE_ORANGE: + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 0 ); // r, g, b + break; + } + + WRITE_BYTE( 128 ); // brightness + + MESSAGE_END(); +} + +//========================================================= +// Hornet is flying, gently tracking target +//========================================================= +void CHornet :: TrackTarget ( void ) +{ + Vector vecFlightDir; + Vector vecDirToEnemy; + float flDelta; + + StudioFrameAdvance( ); + + if (gpGlobals->time > m_flStopAttack) + { + SetTouch( NULL ); + SetThink( &CHornet::SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; + return; + } + + // UNDONE: The player pointer should come back after returning from another level + if ( m_hEnemy == NULL ) + {// enemy is dead. + Look( 512 ); + m_hEnemy = BestVisibleEnemy( ); + } + + if ( m_hEnemy != NULL && FVisible( m_hEnemy )) + { + m_vecEnemyLKP = m_hEnemy->BodyTarget( pev->origin ); + } + else + { + m_vecEnemyLKP = m_vecEnemyLKP + pev->velocity * m_flFlySpeed * 0.1; + } + + vecDirToEnemy = ( m_vecEnemyLKP - pev->origin ).Normalize(); + + if (pev->velocity.Length() < 0.1) + vecFlightDir = vecDirToEnemy; + else + vecFlightDir = pev->velocity.Normalize(); + + // measure how far the turn is, the wider the turn, the slow we'll go this time. + flDelta = DotProduct ( vecFlightDir, vecDirToEnemy ); + + if ( flDelta < 0.5 ) + {// hafta turn wide again. play sound + switch (RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_buzz1.wav", HORNET_BUZZ_VOLUME, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_buzz2.wav", HORNET_BUZZ_VOLUME, ATTN_NORM); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_buzz3.wav", HORNET_BUZZ_VOLUME, ATTN_NORM); break; + } + } + + if ( flDelta <= 0 && m_iHornetType == HORNET_TYPE_RED ) + {// no flying backwards, but we don't want to invert this, cause we'd go fast when we have to turn REAL far. + flDelta = 0.25; + } + + pev->velocity = ( vecFlightDir + vecDirToEnemy).Normalize(); + + if ( pev->owner && (pev->owner->v.flags & FL_MONSTER) ) + { + // random pattern only applies to hornets fired by monsters, not players. + + pev->velocity.x += RANDOM_FLOAT ( -0.10, 0.10 );// scramble the flight dir a bit. + pev->velocity.y += RANDOM_FLOAT ( -0.10, 0.10 ); + pev->velocity.z += RANDOM_FLOAT ( -0.10, 0.10 ); + } + + switch ( m_iHornetType ) + { + case HORNET_TYPE_RED: + pev->velocity = pev->velocity * ( m_flFlySpeed * flDelta );// scale the dir by the ( speed * width of turn ) + pev->nextthink = gpGlobals->time + RANDOM_FLOAT( 0.1, 0.3 ); + break; + case HORNET_TYPE_ORANGE: + pev->velocity = pev->velocity * m_flFlySpeed;// do not have to slow down to turn. + pev->nextthink = gpGlobals->time + 0.1;// fixed think time + break; + } + + pev->angles = UTIL_VecToAngles (pev->velocity); + + pev->solid = SOLID_BBOX; + + // if hornet is close to the enemy, jet in a straight line for a half second. + // (only in the single player game) + if ( m_hEnemy != NULL && !g_pGameRules->IsMultiplayer() ) + { + if ( flDelta >= 0.4 && ( pev->origin - m_vecEnemyLKP ).Length() <= 300 ) + { + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SPRITE ); + WRITE_COORD( pev->origin.x); // pos + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z); + WRITE_SHORT( iHornetPuff ); // model + // WRITE_BYTE( 0 ); // life * 10 + WRITE_BYTE( 2 ); // size * 10 + WRITE_BYTE( 128 ); // brightness + MESSAGE_END(); + + switch (RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_buzz1.wav", HORNET_BUZZ_VOLUME, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_buzz2.wav", HORNET_BUZZ_VOLUME, ATTN_NORM); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_buzz3.wav", HORNET_BUZZ_VOLUME, ATTN_NORM); break; + } + pev->velocity = pev->velocity * 2; + pev->nextthink = gpGlobals->time + 1.0; + // don't attack again + m_flStopAttack = gpGlobals->time; + } + } +} + +//========================================================= +// Tracking Hornet hit something +//========================================================= +void CHornet :: TrackTouch ( CBaseEntity *pOther ) +{ + if ( pOther->edict() == pev->owner || pOther->pev->modelindex == pev->modelindex ) + {// bumped into the guy that shot it. + pev->solid = SOLID_NOT; + return; + } + + if ( IRelationship( pOther ) <= R_NO ) + { + // hit something we don't want to hurt, so turn around. + + pev->velocity = pev->velocity.Normalize(); + + pev->velocity.x *= -1; + pev->velocity.y *= -1; + + pev->origin = pev->origin + pev->velocity * 4; // bounce the hornet off a bit. + pev->velocity = pev->velocity * m_flFlySpeed; + + return; + } + + DieTouch( pOther ); +} + +void CHornet::DartTouch( CBaseEntity *pOther ) +{ + DieTouch( pOther ); +} + +void CHornet::DieTouch ( CBaseEntity *pOther ) +{ + if ( pOther && pOther->pev->takedamage ) + {// do the damage + + switch (RANDOM_LONG(0,2)) + {// buzz when you plug someone + case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_hornethit1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_hornethit2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_hornethit3.wav", 1, ATTN_NORM); break; + } + + pOther->TakeDamage( pev, VARS( pev->owner ), pev->dmg, DMG_BULLET ); + } + + pev->modelindex = 0;// so will disappear for the 0.1 secs we wait until NEXTTHINK gets rid + pev->solid = SOLID_NOT; + + SetThink ( &CHornet::SUB_Remove ); + pev->nextthink = gpGlobals->time + 1;// stick around long enough for the sound to finish! +} + diff --git a/dlls/hornet.h b/dlls/hornet.h new file mode 100644 index 0000000..da8ab78 --- /dev/null +++ b/dlls/hornet.h @@ -0,0 +1,58 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// Hornets +//========================================================= + +//========================================================= +// Hornet Defines +//========================================================= +#define HORNET_TYPE_RED 0 +#define HORNET_TYPE_ORANGE 1 +#define HORNET_RED_SPEED (float)600 +#define HORNET_ORANGE_SPEED (float)800 +#define HORNET_BUZZ_VOLUME (float)0.8 + +extern int iHornetPuff; + +//========================================================= +// Hornet - this is the projectile that the Alien Grunt fires. +//========================================================= +class CHornet : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + int Classify ( void ); + int IRelationship ( CBaseEntity *pTarget ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void IgniteTrail( void ); + void EXPORT StartTrack ( void ); + void EXPORT StartDart ( void ); + void EXPORT TrackTarget ( void ); + void EXPORT TrackTouch ( CBaseEntity *pOther ); + void EXPORT DartTouch( CBaseEntity *pOther ); + void EXPORT DieTouch ( CBaseEntity *pOther ); + + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + float m_flStopAttack; + int m_iHornetType; + float m_flFlySpeed; +}; + diff --git a/dlls/hornetgun.cpp b/dlls/hornetgun.cpp new file mode 100644 index 0000000..a23aa41 --- /dev/null +++ b/dlls/hornetgun.cpp @@ -0,0 +1,305 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "hornet.h" +#include "gamerules.h" + + +enum hgun_e { + HGUN_IDLE1 = 0, + HGUN_FIDGETSWAY, + HGUN_FIDGETSHAKE, + HGUN_DOWN, + HGUN_UP, + HGUN_SHOOT +}; + +enum firemode_e +{ + FIREMODE_TRACK = 0, + FIREMODE_FAST +}; + + +LINK_ENTITY_TO_CLASS( weapon_hornetgun, CHgun ); + +BOOL CHgun::IsUseable( void ) +{ + return TRUE; +} + +void CHgun::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_HORNETGUN; + SET_MODEL(ENT(pev), "models/w_hgun.mdl"); + + m_iDefaultAmmo = HIVEHAND_DEFAULT_GIVE; + m_iFirePhase = 0; + + FallInit();// get ready to fall down. +} + + +void CHgun::Precache( void ) +{ + PRECACHE_MODEL("models/v_hgun.mdl"); + PRECACHE_MODEL("models/w_hgun.mdl"); + PRECACHE_MODEL("models/p_hgun.mdl"); + + m_usHornetFire = PRECACHE_EVENT ( 1, "events/firehornet.sc" ); + + UTIL_PrecacheOther("hornet"); +} + +int CHgun::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + +#ifndef CLIENT_DLL + if ( g_pGameRules->IsMultiplayer() ) + { + // in multiplayer, all hivehands come full. + pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] = HORNET_MAX_CARRY; + } +#endif + + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + +int CHgun::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "Hornets"; + p->iMaxAmmo1 = HORNET_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 3; + p->iPosition = 3; + p->iId = m_iId = WEAPON_HORNETGUN; + p->iFlags = ITEM_FLAG_NOAUTOSWITCHEMPTY | ITEM_FLAG_NOAUTORELOAD; + p->iWeight = HORNETGUN_WEIGHT; + + return 1; +} + + +BOOL CHgun::Deploy( ) +{ + return DefaultDeploy( "models/v_hgun.mdl", "models/p_hgun.mdl", HGUN_UP, "hive" ); +} + +void CHgun::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + SendWeaponAnim( HGUN_DOWN ); + + //!!!HACKHACK - can't select hornetgun if it's empty! no way to get ammo for it, either. + if ( !m_pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] ) + { + m_pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] = 1; + } +} + + +void CHgun::PrimaryAttack() +{ + Reload( ); + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + { + return; + } + +#ifndef CLIENT_DLL + UTIL_MakeVectors( m_pPlayer->pev->v_angle ); + + CBaseEntity *pHornet = CBaseEntity::Create( "hornet", m_pPlayer->GetGunPosition( ) + gpGlobals->v_forward * 16 + gpGlobals->v_right * 8 + gpGlobals->v_up * -12, m_pPlayer->pev->v_angle, m_pPlayer->edict() ); + pHornet->pev->velocity = gpGlobals->v_forward * 300; + + m_flRechargeTime = gpGlobals->time + 0.5; +#endif + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + + m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = DIM_GUN_FLASH; + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usHornetFire, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, FIREMODE_TRACK, 0, 0, 0 ); + + + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_flNextPrimaryAttack = GetNextAttackDelay(0.25); + + if (m_flNextPrimaryAttack < UTIL_WeaponTimeBase() ) + { + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.25; + } + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); +} + + + +void CHgun::SecondaryAttack( void ) +{ + Reload(); + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + { + return; + } + + //Wouldn't be a bad idea to completely predict these, since they fly so fast... +#ifndef CLIENT_DLL + CBaseEntity *pHornet; + Vector vecSrc; + + UTIL_MakeVectors( m_pPlayer->pev->v_angle ); + + vecSrc = m_pPlayer->GetGunPosition( ) + gpGlobals->v_forward * 16 + gpGlobals->v_right * 8 + gpGlobals->v_up * -12; + + m_iFirePhase++; + switch ( m_iFirePhase ) + { + case 1: + vecSrc = vecSrc + gpGlobals->v_up * 8; + break; + case 2: + vecSrc = vecSrc + gpGlobals->v_up * 8; + vecSrc = vecSrc + gpGlobals->v_right * 8; + break; + case 3: + vecSrc = vecSrc + gpGlobals->v_right * 8; + break; + case 4: + vecSrc = vecSrc + gpGlobals->v_up * -8; + vecSrc = vecSrc + gpGlobals->v_right * 8; + break; + case 5: + vecSrc = vecSrc + gpGlobals->v_up * -8; + break; + case 6: + vecSrc = vecSrc + gpGlobals->v_up * -8; + vecSrc = vecSrc + gpGlobals->v_right * -8; + break; + case 7: + vecSrc = vecSrc + gpGlobals->v_right * -8; + break; + case 8: + vecSrc = vecSrc + gpGlobals->v_up * 8; + vecSrc = vecSrc + gpGlobals->v_right * -8; + m_iFirePhase = 0; + break; + } + + pHornet = CBaseEntity::Create( "hornet", vecSrc, m_pPlayer->pev->v_angle, m_pPlayer->edict() ); + pHornet->pev->velocity = gpGlobals->v_forward * 1200; + pHornet->pev->angles = UTIL_VecToAngles( pHornet->pev->velocity ); + + pHornet->SetThink( &CHornet::StartDart ); + + m_flRechargeTime = gpGlobals->time + 0.5; +#endif + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usHornetFire, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, FIREMODE_FAST, 0, 0, 0 ); + + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + m_pPlayer->m_iWeaponVolume = NORMAL_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = DIM_GUN_FLASH; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.1; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); +} + + +void CHgun::Reload( void ) +{ + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] >= HORNET_MAX_CARRY) + return; + + while (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] < HORNET_MAX_CARRY && m_flRechargeTime < gpGlobals->time) + { + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]++; + m_flRechargeTime += 0.5; + } +} + + +void CHgun::WeaponIdle( void ) +{ + Reload( ); + + if (m_flTimeWeaponIdle > UTIL_WeaponTimeBase()) + return; + + int iAnim; + float flRand = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0, 1 ); + if (flRand <= 0.75) + { + iAnim = HGUN_IDLE1; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 30.0 / 16 * (2); + } + else if (flRand <= 0.875) + { + iAnim = HGUN_FIDGETSWAY; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 40.0 / 16.0; + } + else + { + iAnim = HGUN_FIDGETSHAKE; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 35.0 / 16.0; + } + SendWeaponAnim( iAnim ); +} + +#endif diff --git a/dlls/houndeye.cpp b/dlls/houndeye.cpp new file mode 100644 index 0000000..9b7cbe0 --- /dev/null +++ b/dlls/houndeye.cpp @@ -0,0 +1,1304 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Houndeye - spooky sonic dog. +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "animation.h" +#include "nodes.h" +#include "squadmonster.h" +#include "soundent.h" +#include "game.h" + +extern CGraph WorldGraph; + +// houndeye does 20 points of damage spread over a sphere 384 units in diameter, and each additional +// squad member increases the BASE damage by 110%, per the spec. +#define HOUNDEYE_MAX_SQUAD_SIZE 4 +#define HOUNDEYE_MAX_ATTACK_RADIUS 384 +#define HOUNDEYE_SQUAD_BONUS (float)1.1 + +#define HOUNDEYE_EYE_FRAMES 4 // how many different switchable maps for the eye + +#define HOUNDEYE_SOUND_STARTLE_VOLUME 128 // how loud a sound has to be to badly scare a sleeping houndeye + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_HOUND_CLOSE_EYE = LAST_COMMON_TASK + 1, + TASK_HOUND_OPEN_EYE, + TASK_HOUND_THREAT_DISPLAY, + TASK_HOUND_FALL_ASLEEP, + TASK_HOUND_WAKE_UP, + TASK_HOUND_HOP_BACK +}; + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_HOUND_AGITATED = LAST_COMMON_SCHEDULE + 1, + SCHED_HOUND_HOP_RETREAT, + SCHED_HOUND_FAIL, +}; + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define HOUND_AE_WARN 1 +#define HOUND_AE_STARTATTACK 2 +#define HOUND_AE_THUMP 3 +#define HOUND_AE_ANGERSOUND1 4 +#define HOUND_AE_ANGERSOUND2 5 +#define HOUND_AE_HOPBACK 6 +#define HOUND_AE_CLOSE_EYE 7 + +class CHoundeye : public CSquadMonster +{ +public: + void Spawn( void ); + void Precache( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void SetYawSpeed ( void ); + void WarmUpSound ( void ); + void AlertSound( void ); + void DeathSound( void ); + void WarnSound( void ); + void PainSound( void ); + void IdleSound( void ); + void StartTask( Task_t *pTask ); + void RunTask ( Task_t *pTask ); + void SonicAttack( void ); + void PrescheduleThink( void ); + void SetActivity ( Activity NewActivity ); + void WriteBeamColor ( void ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL FValidateHintType ( short sHint ); + BOOL FCanActiveIdle ( void ); + Schedule_t *GetScheduleOfType ( int Type ); + Schedule_t *GetSchedule( void ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + + CUSTOM_SCHEDULES; + static TYPEDESCRIPTION m_SaveData[]; + + int m_iSpriteTexture; + BOOL m_fAsleep;// some houndeyes sleep in idle mode if this is set, the houndeye is lying down + BOOL m_fDontBlink;// don't try to open/close eye if this bit is set! + Vector m_vecPackCenter; // the center of the pack. The leader maintains this by averaging the origins of all pack members. +}; +LINK_ENTITY_TO_CLASS( monster_houndeye, CHoundeye ); + +TYPEDESCRIPTION CHoundeye::m_SaveData[] = +{ + DEFINE_FIELD( CHoundeye, m_iSpriteTexture, FIELD_INTEGER ), + DEFINE_FIELD( CHoundeye, m_fAsleep, FIELD_BOOLEAN ), + DEFINE_FIELD( CHoundeye, m_fDontBlink, FIELD_BOOLEAN ), + DEFINE_FIELD( CHoundeye, m_vecPackCenter, FIELD_POSITION_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CHoundeye, CSquadMonster ); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CHoundeye :: Classify ( void ) +{ + return CLASS_ALIEN_MONSTER; +} + +//========================================================= +// FValidateHintType +//========================================================= +BOOL CHoundeye :: FValidateHintType ( short sHint ) +{ + int i; + + static short sHoundHints[] = + { + HINT_WORLD_MACHINERY, + HINT_WORLD_BLINKING_LIGHT, + HINT_WORLD_HUMAN_BLOOD, + HINT_WORLD_ALIEN_BLOOD, + }; + + for ( i = 0 ; i < ARRAYSIZE ( sHoundHints ) ; i++ ) + { + if ( sHoundHints[ i ] == sHint ) + { + return TRUE; + } + } + + ALERT ( at_aiconsole, "Couldn't validate hint type" ); + return FALSE; +} + + +//========================================================= +// FCanActiveIdle +//========================================================= +BOOL CHoundeye :: FCanActiveIdle ( void ) +{ + if ( InSquad() ) + { + CSquadMonster *pSquadLeader = MySquadLeader(); + + for (int i = 0; i < MAX_SQUAD_MEMBERS;i++) + { + CSquadMonster *pMember = pSquadLeader->MySquadMember(i); + + if ( pMember != NULL && pMember != this && pMember->m_iHintNode != NO_NODE ) + { + // someone else in the group is active idling right now! + return FALSE; + } + } + + return TRUE; + } + + return TRUE; +} + + +//========================================================= +// CheckRangeAttack1 - overridden for houndeyes so that they +// try to get within half of their max attack radius before +// attacking, so as to increase their chances of doing damage. +//========================================================= +BOOL CHoundeye :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDist <= ( HOUNDEYE_MAX_ATTACK_RADIUS * 0.5 ) && flDot >= 0.3 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CHoundeye :: SetYawSpeed ( void ) +{ + int ys; + + ys = 90; + + switch ( m_Activity ) + { + case ACT_CROUCHIDLE://sleeping! + ys = 0; + break; + case ACT_IDLE: + ys = 60; + break; + case ACT_WALK: + ys = 90; + break; + case ACT_RUN: + ys = 90; + break; + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 90; + break; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// SetActivity +//========================================================= +void CHoundeye :: SetActivity ( Activity NewActivity ) +{ + int iSequence; + + if ( NewActivity == m_Activity ) + return; + + if ( m_MonsterState == MONSTERSTATE_COMBAT && NewActivity == ACT_IDLE && RANDOM_LONG(0,1) ) + { + // play pissed idle. + iSequence = LookupSequence( "madidle" ); + + m_Activity = NewActivity; // Go ahead and set this so it doesn't keep trying when the anim is not present + + // In case someone calls this with something other than the ideal activity + m_IdealActivity = m_Activity; + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + pev->sequence = iSequence; // Set to the reset anim (if it's there) + pev->frame = 0; // FIX: frame counter shouldn't be reset when its the same activity as before + ResetSequenceInfo(); + SetYawSpeed(); + } + } + else + { + CSquadMonster :: SetActivity ( NewActivity ); + } +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CHoundeye :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch ( pEvent->event ) + { + case HOUND_AE_WARN: + // do stuff for this event. + WarnSound(); + break; + + case HOUND_AE_STARTATTACK: + WarmUpSound(); + break; + + case HOUND_AE_HOPBACK: + { + float flGravity = g_psv_gravity->value; + + pev->flags &= ~FL_ONGROUND; + + pev->velocity = gpGlobals->v_forward * -200; + pev->velocity.z += (0.6 * flGravity) * 0.5; + + break; + } + + case HOUND_AE_THUMP: + // emit the shockwaves + SonicAttack(); + break; + + case HOUND_AE_ANGERSOUND1: + EMIT_SOUND(ENT(pev), CHAN_VOICE, "houndeye/he_pain3.wav", 1, ATTN_NORM); + break; + + case HOUND_AE_ANGERSOUND2: + EMIT_SOUND(ENT(pev), CHAN_VOICE, "houndeye/he_pain1.wav", 1, ATTN_NORM); + break; + + case HOUND_AE_CLOSE_EYE: + if ( !m_fDontBlink ) + { + pev->skin = HOUNDEYE_EYE_FRAMES - 1; + } + break; + + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CHoundeye :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/houndeye.mdl"); + UTIL_SetSize(pev, Vector ( -16, -16, 0 ), Vector ( 16, 16, 36 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_YELLOW; + pev->effects = 0; + pev->health = gSkillData.houndeyeHealth; + pev->yaw_speed = 5;//!!! should we put this in the monster's changeanim function since turn rates may vary with state/anim? + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_fAsleep = FALSE; // everyone spawns awake + m_fDontBlink = FALSE; + m_afCapability |= bits_CAP_SQUAD; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CHoundeye :: Precache() +{ + PRECACHE_MODEL("models/houndeye.mdl"); + + PRECACHE_SOUND("houndeye/he_alert1.wav"); + PRECACHE_SOUND("houndeye/he_alert2.wav"); + PRECACHE_SOUND("houndeye/he_alert3.wav"); + + PRECACHE_SOUND("houndeye/he_die1.wav"); + PRECACHE_SOUND("houndeye/he_die2.wav"); + PRECACHE_SOUND("houndeye/he_die3.wav"); + + PRECACHE_SOUND("houndeye/he_idle1.wav"); + PRECACHE_SOUND("houndeye/he_idle2.wav"); + PRECACHE_SOUND("houndeye/he_idle3.wav"); + + PRECACHE_SOUND("houndeye/he_hunt1.wav"); + PRECACHE_SOUND("houndeye/he_hunt2.wav"); + PRECACHE_SOUND("houndeye/he_hunt3.wav"); + + PRECACHE_SOUND("houndeye/he_pain1.wav"); + PRECACHE_SOUND("houndeye/he_pain3.wav"); + PRECACHE_SOUND("houndeye/he_pain4.wav"); + PRECACHE_SOUND("houndeye/he_pain5.wav"); + + PRECACHE_SOUND("houndeye/he_attack1.wav"); + PRECACHE_SOUND("houndeye/he_attack3.wav"); + + PRECACHE_SOUND("houndeye/he_blast1.wav"); + PRECACHE_SOUND("houndeye/he_blast2.wav"); + PRECACHE_SOUND("houndeye/he_blast3.wav"); + + m_iSpriteTexture = PRECACHE_MODEL( "sprites/shockwave.spr" ); +} + +//========================================================= +// IdleSound +//========================================================= +void CHoundeye :: IdleSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_idle1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_idle2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_idle3.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// IdleSound +//========================================================= +void CHoundeye :: WarmUpSound ( void ) +{ + switch ( RANDOM_LONG(0,1) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "houndeye/he_attack1.wav", 0.7, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "houndeye/he_attack3.wav", 0.7, ATTN_NORM ); + break; + } +} + +//========================================================= +// WarnSound +//========================================================= +void CHoundeye :: WarnSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_hunt1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_hunt2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_hunt3.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// AlertSound +//========================================================= +void CHoundeye :: AlertSound ( void ) +{ + + if ( InSquad() && !IsLeader() ) + { + return; // only leader makes ALERT sound. + } + + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_alert1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_alert2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_alert3.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CHoundeye :: DeathSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_die1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_die2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_die3.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// PainSound +//========================================================= +void CHoundeye :: PainSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_pain3.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_pain4.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_pain5.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// WriteBeamColor - writes a color vector to the network +// based on the size of the group. +//========================================================= +void CHoundeye :: WriteBeamColor ( void ) +{ + BYTE bRed, bGreen, bBlue; + + if ( InSquad() ) + { + switch ( SquadCount() ) + { + case 2: + // no case for 0 or 1, cause those are impossible for monsters in Squads. + bRed = 101; + bGreen = 133; + bBlue = 221; + break; + case 3: + bRed = 67; + bGreen = 85; + bBlue = 255; + break; + case 4: + bRed = 62; + bGreen = 33; + bBlue = 211; + break; + default: + ALERT ( at_aiconsole, "Unsupported Houndeye SquadSize!\n" ); + bRed = 188; + bGreen = 220; + bBlue = 255; + break; + } + } + else + { + // solo houndeye - weakest beam + bRed = 188; + bGreen = 220; + bBlue = 255; + } + + WRITE_BYTE( bRed ); + WRITE_BYTE( bGreen ); + WRITE_BYTE( bBlue ); +} + + +//========================================================= +// SonicAttack +//========================================================= +void CHoundeye :: SonicAttack ( void ) +{ + float flAdjustedDamage; + float flDist; + + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "houndeye/he_blast1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "houndeye/he_blast2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "houndeye/he_blast3.wav", 1, ATTN_NORM); break; + } + + // blast circles + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_BEAMCYLINDER ); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 16); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 16 + HOUNDEYE_MAX_ATTACK_RADIUS / .2); // reach damage radius over .3 seconds + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 2 ); // life + WRITE_BYTE( 16 ); // width + WRITE_BYTE( 0 ); // noise + + WriteBeamColor(); + + WRITE_BYTE( 255 ); //brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_BEAMCYLINDER ); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 16); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 16 + ( HOUNDEYE_MAX_ATTACK_RADIUS / 2 ) / .2); // reach damage radius over .3 seconds + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 2 ); // life + WRITE_BYTE( 16 ); // width + WRITE_BYTE( 0 ); // noise + + WriteBeamColor(); + + WRITE_BYTE( 255 ); //brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + + CBaseEntity *pEntity = NULL; + // iterate on all entities in the vicinity. + while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, HOUNDEYE_MAX_ATTACK_RADIUS )) != NULL) + { + if ( pEntity->pev->takedamage != DAMAGE_NO ) + { + if ( !FClassnameIs(pEntity->pev, "monster_houndeye") ) + {// houndeyes don't hurt other houndeyes with their attack + + // houndeyes do FULL damage if the ent in question is visible. Half damage otherwise. + // This means that you must get out of the houndeye's attack range entirely to avoid damage. + // Calculate full damage first + + if ( SquadCount() > 1 ) + { + // squad gets attack bonus. + flAdjustedDamage = gSkillData.houndeyeDmgBlast + gSkillData.houndeyeDmgBlast * ( HOUNDEYE_SQUAD_BONUS * ( SquadCount() - 1 ) ); + } + else + { + // solo + flAdjustedDamage = gSkillData.houndeyeDmgBlast; + } + + flDist = (pEntity->Center() - pev->origin).Length(); + + flAdjustedDamage -= ( flDist / HOUNDEYE_MAX_ATTACK_RADIUS ) * flAdjustedDamage; + + if ( !FVisible( pEntity ) ) + { + if ( pEntity->IsPlayer() ) + { + // if this entity is a client, and is not in full view, inflict half damage. We do this so that players still + // take the residual damage if they don't totally leave the houndeye's effective radius. We restrict it to clients + // so that monsters in other parts of the level don't take the damage and get pissed. + flAdjustedDamage *= 0.5; + } + else if ( !FClassnameIs( pEntity->pev, "func_breakable" ) && !FClassnameIs( pEntity->pev, "func_pushable" ) ) + { + // do not hurt nonclients through walls, but allow damage to be done to breakables + flAdjustedDamage = 0; + } + } + + //ALERT ( at_aiconsole, "Damage: %f\n", flAdjustedDamage ); + + if (flAdjustedDamage > 0 ) + { + pEntity->TakeDamage ( pev, pev, flAdjustedDamage, DMG_SONIC | DMG_ALWAYSGIB ); + } + } + } + } +} + +//========================================================= +// start task +//========================================================= +void CHoundeye :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_HOUND_FALL_ASLEEP: + { + m_fAsleep = TRUE; // signal that hound is lying down (must stand again before doing anything else!) + m_iTaskStatus = TASKSTATUS_COMPLETE; + break; + } + case TASK_HOUND_WAKE_UP: + { + m_fAsleep = FALSE; // signal that hound is standing again + m_iTaskStatus = TASKSTATUS_COMPLETE; + break; + } + case TASK_HOUND_OPEN_EYE: + { + m_fDontBlink = FALSE; // turn blinking back on and that code will automatically open the eye + m_iTaskStatus = TASKSTATUS_COMPLETE; + break; + } + case TASK_HOUND_CLOSE_EYE: + { + pev->skin = 0; + m_fDontBlink = TRUE; // tell blink code to leave the eye alone. + break; + } + case TASK_HOUND_THREAT_DISPLAY: + { + m_IdealActivity = ACT_IDLE_ANGRY; + break; + } + case TASK_HOUND_HOP_BACK: + { + m_IdealActivity = ACT_LEAP; + break; + } + case TASK_RANGE_ATTACK1: + { + m_IdealActivity = ACT_RANGE_ATTACK1; + +/* + if ( InSquad() ) + { + // see if there is a battery to connect to. + CSquadMonster *pSquad = m_pSquadLeader; + + while ( pSquad ) + { + if ( pSquad->m_iMySlot == bits_SLOT_HOUND_BATTERY ) + { + // draw a beam. + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTS ); + WRITE_SHORT( ENTINDEX( this->edict() ) ); + WRITE_SHORT( ENTINDEX( pSquad->edict() ) ); + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // framestart + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 10 ); // life + WRITE_BYTE( 40 ); // width + WRITE_BYTE( 10 ); // noise + WRITE_BYTE( 0 ); // r, g, b + WRITE_BYTE( 50 ); // r, g, b + WRITE_BYTE( 250); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 30 ); // speed + MESSAGE_END(); + break; + } + + pSquad = pSquad->m_pSquadNext; + } + } +*/ + + break; + } + case TASK_SPECIAL_ATTACK1: + { + m_IdealActivity = ACT_SPECIAL_ATTACK1; + break; + } + case TASK_GUARD: + { + m_IdealActivity = ACT_GUARD; + break; + } + default: + { + CSquadMonster :: StartTask(pTask); + break; + } + } +} + +//========================================================= +// RunTask +//========================================================= +void CHoundeye :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_HOUND_THREAT_DISPLAY: + { + MakeIdealYaw ( m_vecEnemyLKP ); + ChangeYaw ( pev->yaw_speed ); + + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + + break; + } + case TASK_HOUND_CLOSE_EYE: + { + if ( pev->skin < HOUNDEYE_EYE_FRAMES - 1 ) + { + pev->skin++; + } + break; + } + case TASK_HOUND_HOP_BACK: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + break; + } + case TASK_SPECIAL_ATTACK1: + { + pev->skin = RANDOM_LONG(0, HOUNDEYE_EYE_FRAMES - 1); + + MakeIdealYaw ( m_vecEnemyLKP ); + ChangeYaw ( pev->yaw_speed ); + + float life; + life = ((255 - pev->frame) / (pev->framerate * m_flFrameRate)); + if (life < 0.1) life = 0.1; + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_IMPLOSION); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 16); + WRITE_BYTE( 50 * life + 100); // radius + WRITE_BYTE( pev->frame / 25.0 ); // count + WRITE_BYTE( life * 10 ); // life + MESSAGE_END(); + + if ( m_fSequenceFinished ) + { + SonicAttack(); + TaskComplete(); + } + + break; + } + default: + { + CSquadMonster :: RunTask(pTask); + break; + } + } +} + +//========================================================= +// PrescheduleThink +//========================================================= +void CHoundeye::PrescheduleThink ( void ) +{ + // if the hound is mad and is running, make hunt noises. + if ( m_MonsterState == MONSTERSTATE_COMBAT && m_Activity == ACT_RUN && RANDOM_FLOAT( 0, 1 ) < 0.2 ) + { + WarnSound(); + } + + // at random, initiate a blink if not already blinking or sleeping + if ( !m_fDontBlink ) + { + if ( ( pev->skin == 0 ) && RANDOM_LONG(0,0x7F) == 0 ) + {// start blinking! + pev->skin = HOUNDEYE_EYE_FRAMES - 1; + } + else if ( pev->skin != 0 ) + {// already blinking + pev->skin--; + } + } + + // if you are the leader, average the origins of each pack member to get an approximate center. + if ( IsLeader() ) + { + CSquadMonster *pSquadMember; + int iSquadCount = 0; + + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + pSquadMember = MySquadMember(i); + + if (pSquadMember) + { + iSquadCount++; + m_vecPackCenter = m_vecPackCenter + pSquadMember->pev->origin; + } + } + + m_vecPackCenter = m_vecPackCenter / iSquadCount; + } +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +Task_t tlHoundGuardPack[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_GUARD, (float)0 }, +}; + +Schedule_t slHoundGuardPack[] = +{ + { + tlHoundGuardPack, + ARRAYSIZE ( tlHoundGuardPack ), + bits_COND_SEE_HATE | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED | + bits_COND_HEAR_SOUND, + + bits_SOUND_COMBAT |// sound flags + bits_SOUND_WORLD | + bits_SOUND_MEAT | + bits_SOUND_PLAYER, + "GuardPack" + }, +}; + +// primary range attack +Task_t tlHoundYell1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_HOUND_AGITATED }, +}; + +Task_t tlHoundYell2[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slHoundRangeAttack[] = +{ + { + tlHoundYell1, + ARRAYSIZE ( tlHoundYell1 ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "HoundRangeAttack1" + }, + { + tlHoundYell2, + ARRAYSIZE ( tlHoundYell2 ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "HoundRangeAttack2" + }, +}; + +// lie down and fall asleep +Task_t tlHoundSleep[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_RANDOM, (float)5 }, + { TASK_PLAY_SEQUENCE, (float)ACT_CROUCH }, + { TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE }, + { TASK_HOUND_FALL_ASLEEP, (float)0 }, + { TASK_WAIT_RANDOM, (float)25 }, + { TASK_HOUND_CLOSE_EYE, (float)0 }, + //{ TASK_WAIT, (float)10 }, + //{ TASK_WAIT_RANDOM, (float)10 }, +}; + +Schedule_t slHoundSleep[] = +{ + { + tlHoundSleep, + ARRAYSIZE ( tlHoundSleep ), + bits_COND_HEAR_SOUND | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_NEW_ENEMY, + + bits_SOUND_COMBAT | + bits_SOUND_PLAYER | + bits_SOUND_WORLD, + "Hound Sleep" + }, +}; + +// wake and stand up lazily +Task_t tlHoundWakeLazy[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_HOUND_OPEN_EYE, (float)0 }, + { TASK_WAIT_RANDOM, (float)2.5 }, + { TASK_PLAY_SEQUENCE, (float)ACT_STAND }, + { TASK_HOUND_WAKE_UP, (float)0 }, +}; + +Schedule_t slHoundWakeLazy[] = +{ + { + tlHoundWakeLazy, + ARRAYSIZE ( tlHoundWakeLazy ), + 0, + 0, + "WakeLazy" + }, +}; + +// wake and stand up with great urgency! +Task_t tlHoundWakeUrgent[] = +{ + { TASK_HOUND_OPEN_EYE, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_HOP }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_HOUND_WAKE_UP, (float)0 }, +}; + +Schedule_t slHoundWakeUrgent[] = +{ + { + tlHoundWakeUrgent, + ARRAYSIZE ( tlHoundWakeUrgent ), + 0, + 0, + "WakeUrgent" + }, +}; + + +Task_t tlHoundSpecialAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_SPECIAL_ATTACK1, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_IDLE_ANGRY }, +}; + +Schedule_t slHoundSpecialAttack1[] = +{ + { + tlHoundSpecialAttack1, + ARRAYSIZE ( tlHoundSpecialAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED, + + 0, + "Hound Special Attack1" + }, +}; + +Task_t tlHoundAgitated[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_HOUND_THREAT_DISPLAY, 0 }, +}; + +Schedule_t slHoundAgitated[] = +{ + { + tlHoundAgitated, + ARRAYSIZE ( tlHoundAgitated ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "Hound Agitated" + }, +}; + +Task_t tlHoundHopRetreat[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_HOUND_HOP_BACK, 0 }, + { TASK_SET_SCHEDULE, (float)SCHED_TAKE_COVER_FROM_ENEMY }, +}; + +Schedule_t slHoundHopRetreat[] = +{ + { + tlHoundHopRetreat, + ARRAYSIZE ( tlHoundHopRetreat ), + 0, + 0, + "Hound Hop Retreat" + }, +}; + +// hound fails in combat with client in the PVS +Task_t tlHoundCombatFailPVS[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_HOUND_THREAT_DISPLAY, 0 }, + { TASK_WAIT_FACE_ENEMY, (float)1 }, +}; + +Schedule_t slHoundCombatFailPVS[] = +{ + { + tlHoundCombatFailPVS, + ARRAYSIZE ( tlHoundCombatFailPVS ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "HoundCombatFailPVS" + }, +}; + +// hound fails in combat with no client in the PVS. Don't keep peeping! +Task_t tlHoundCombatFailNoPVS[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_HOUND_THREAT_DISPLAY, 0 }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_PVS, 0 }, +}; + +Schedule_t slHoundCombatFailNoPVS[] = +{ + { + tlHoundCombatFailNoPVS, + ARRAYSIZE ( tlHoundCombatFailNoPVS ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "HoundCombatFailNoPVS" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CHoundeye ) +{ + slHoundGuardPack, + slHoundRangeAttack, + &slHoundRangeAttack[ 1 ], + slHoundSleep, + slHoundWakeLazy, + slHoundWakeUrgent, + slHoundSpecialAttack1, + slHoundAgitated, + slHoundHopRetreat, + slHoundCombatFailPVS, + slHoundCombatFailNoPVS, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CHoundeye, CSquadMonster ); + +//========================================================= +// GetScheduleOfType +//========================================================= +Schedule_t* CHoundeye :: GetScheduleOfType ( int Type ) +{ + if ( m_fAsleep ) + { + // if the hound is sleeping, must wake and stand! + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + { + CSound *pWakeSound; + + pWakeSound = PBestSound(); + ASSERT( pWakeSound != NULL ); + if ( pWakeSound ) + { + MakeIdealYaw ( pWakeSound->m_vecOrigin ); + + if ( FLSoundVolume ( pWakeSound ) >= HOUNDEYE_SOUND_STARTLE_VOLUME ) + { + // awakened by a loud sound + return &slHoundWakeUrgent[ 0 ]; + } + } + // sound was not loud enough to scare the bejesus out of houndeye + return &slHoundWakeLazy[ 0 ]; + } + else if ( HasConditions( bits_COND_NEW_ENEMY ) ) + { + // get up fast, to fight. + return &slHoundWakeUrgent[ 0 ]; + } + + else + { + // hound is waking up on its own + return &slHoundWakeLazy[ 0 ]; + } + } + switch ( Type ) + { + case SCHED_IDLE_STAND: + { + // we may want to sleep instead of stand! + if ( InSquad() && !IsLeader() && !m_fAsleep && RANDOM_LONG(0,29) < 1 ) + { + return &slHoundSleep[ 0 ]; + } + else + { + return CSquadMonster :: GetScheduleOfType( Type ); + } + } + case SCHED_RANGE_ATTACK1: + { + return &slHoundRangeAttack[ 0 ]; +/* + if ( InSquad() ) + { + return &slHoundRangeAttack[ RANDOM_LONG( 0, 1 ) ]; + } + + return &slHoundRangeAttack[ 1 ]; +*/ + } + case SCHED_SPECIAL_ATTACK1: + { + return &slHoundSpecialAttack1[ 0 ]; + } + case SCHED_GUARD: + { + return &slHoundGuardPack[ 0 ]; + } + case SCHED_HOUND_AGITATED: + { + return &slHoundAgitated[ 0 ]; + } + case SCHED_HOUND_HOP_RETREAT: + { + return &slHoundHopRetreat[ 0 ]; + } + case SCHED_FAIL: + { + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + { + if ( !FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) + { + // client in PVS + return &slHoundCombatFailPVS[ 0 ]; + } + else + { + // client has taken off! + return &slHoundCombatFailNoPVS[ 0 ]; + } + } + else + { + return CSquadMonster :: GetScheduleOfType ( Type ); + } + } + default: + { + return CSquadMonster :: GetScheduleOfType ( Type ); + } + } +} + +//========================================================= +// GetSchedule +//========================================================= +Schedule_t *CHoundeye :: GetSchedule( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + + if ( HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) + { + if ( RANDOM_FLOAT( 0 , 1 ) <= 0.4 ) + { + TraceResult tr; + UTIL_MakeVectors( pev->angles ); + UTIL_TraceHull( pev->origin, pev->origin + gpGlobals->v_forward * -128, dont_ignore_monsters, head_hull, ENT( pev ), &tr ); + + if ( tr.flFraction == 1.0 ) + { + // it's clear behind, so the hound will jump + return GetScheduleOfType ( SCHED_HOUND_HOP_RETREAT ); + } + } + + return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); + } + + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + if ( OccupySlot ( bits_SLOTS_HOUND_ATTACK ) ) + { + return GetScheduleOfType ( SCHED_RANGE_ATTACK1 ); + } + + return GetScheduleOfType ( SCHED_HOUND_AGITATED ); + } + break; + } + } + + return CSquadMonster :: GetSchedule(); +} diff --git a/dlls/ichthyosaur.cpp b/dlls/ichthyosaur.cpp new file mode 100644 index 0000000..0a7ade5 --- /dev/null +++ b/dlls/ichthyosaur.cpp @@ -0,0 +1,1108 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +//========================================================= +// icthyosaur - evin, satan fish monster +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "flyingmonster.h" +#include "nodes.h" +#include "soundent.h" +#include "animation.h" +#include "effects.h" +#include "weapons.h" + +#define SEARCH_RETRY 16 + +#define ICHTHYOSAUR_SPEED 150 + +extern CGraph WorldGraph; + +#define EYE_MAD 0 +#define EYE_BASE 1 +#define EYE_CLOSED 2 +#define EYE_BACK 3 +#define EYE_LOOK 4 + + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +// UNDONE: Save/restore here +class CIchthyosaur : public CFlyingMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + CUSTOM_SCHEDULES; + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType ( int Type ); + + void Killed( entvars_t *pevAttacker, int iGib ); + void BecomeDead( void ); + + void EXPORT CombatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT BiteTouch( CBaseEntity *pOther ); + + void StartTask( Task_t *pTask ); + void RunTask( Task_t *pTask ); + + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + + float ChangeYaw( int speed ); + Activity GetStoppedActivity( void ); + + void Move( float flInterval ); + void MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ); + void MonsterThink( void ); + void Stop( void ); + void Swim( void ); + Vector DoProbe(const Vector &Probe); + + float VectorToPitch( const Vector &vec); + float FlPitchDiff( void ); + float ChangePitch( int speed ); + + Vector m_SaveVelocity; + float m_idealDist; + + float m_flBlink; + + float m_flEnemyTouched; + BOOL m_bOnAttack; + + float m_flMaxSpeed; + float m_flMinSpeed; + float m_flMaxDist; + + CBeam *m_pBeam; + + float m_flNextAlert; + + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + static const char *pAttackSounds[]; + static const char *pBiteSounds[]; + static const char *pDieSounds[]; + static const char *pPainSounds[]; + + void IdleSound( void ); + void AlertSound( void ); + void AttackSound( void ); + void BiteSound( void ); + void DeathSound( void ); + void PainSound( void ); +}; + +LINK_ENTITY_TO_CLASS( monster_ichthyosaur, CIchthyosaur ); + +TYPEDESCRIPTION CIchthyosaur::m_SaveData[] = +{ + DEFINE_FIELD( CIchthyosaur, m_SaveVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CIchthyosaur, m_idealDist, FIELD_FLOAT ), + DEFINE_FIELD( CIchthyosaur, m_flBlink, FIELD_FLOAT ), + DEFINE_FIELD( CIchthyosaur, m_flEnemyTouched, FIELD_FLOAT ), + DEFINE_FIELD( CIchthyosaur, m_bOnAttack, FIELD_BOOLEAN ), + DEFINE_FIELD( CIchthyosaur, m_flMaxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CIchthyosaur, m_flMinSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CIchthyosaur, m_flMaxDist, FIELD_FLOAT ), + DEFINE_FIELD( CIchthyosaur, m_flNextAlert, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CIchthyosaur, CFlyingMonster ); + + +const char *CIchthyosaur::pIdleSounds[] = +{ + "ichy/ichy_idle1.wav", + "ichy/ichy_idle2.wav", + "ichy/ichy_idle3.wav", + "ichy/ichy_idle4.wav", +}; + +const char *CIchthyosaur::pAlertSounds[] = +{ + "ichy/ichy_alert2.wav", + "ichy/ichy_alert3.wav", +}; + +const char *CIchthyosaur::pAttackSounds[] = +{ + "ichy/ichy_attack1.wav", + "ichy/ichy_attack2.wav", +}; + +const char *CIchthyosaur::pBiteSounds[] = +{ + "ichy/ichy_bite1.wav", + "ichy/ichy_bite2.wav", +}; + +const char *CIchthyosaur::pPainSounds[] = +{ + "ichy/ichy_pain2.wav", + "ichy/ichy_pain3.wav", + "ichy/ichy_pain5.wav", +}; + +const char *CIchthyosaur::pDieSounds[] = +{ + "ichy/ichy_die2.wav", + "ichy/ichy_die4.wav", +}; + +#define EMIT_ICKY_SOUND( chan, array ) \ + EMIT_SOUND_DYN ( ENT(pev), chan , array [ RANDOM_LONG(0,ARRAYSIZE( array )-1) ], 1.0, 0.6, 0, RANDOM_LONG(95,105) ); + + +void CIchthyosaur :: IdleSound( void ) +{ + EMIT_ICKY_SOUND( CHAN_VOICE, pIdleSounds ); +} + +void CIchthyosaur :: AlertSound( void ) +{ + EMIT_ICKY_SOUND( CHAN_VOICE, pAlertSounds ); +} + +void CIchthyosaur :: AttackSound( void ) +{ + EMIT_ICKY_SOUND( CHAN_VOICE, pAttackSounds ); +} + +void CIchthyosaur :: BiteSound( void ) +{ + EMIT_ICKY_SOUND( CHAN_WEAPON, pBiteSounds ); +} + +void CIchthyosaur :: DeathSound( void ) +{ + EMIT_ICKY_SOUND( CHAN_VOICE, pDieSounds ); +} + +void CIchthyosaur :: PainSound( void ) +{ + EMIT_ICKY_SOUND( CHAN_VOICE, pPainSounds ); +} + +//========================================================= +// monster-specific tasks and states +//========================================================= +enum +{ + TASK_ICHTHYOSAUR_CIRCLE_ENEMY = LAST_COMMON_TASK + 1, + TASK_ICHTHYOSAUR_SWIM, + TASK_ICHTHYOSAUR_FLOAT, +}; + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +static Task_t tlSwimAround[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_WALK }, + { TASK_ICHTHYOSAUR_SWIM, 0.0 }, +}; + +static Schedule_t slSwimAround[] = +{ + { + tlSwimAround, + ARRAYSIZE(tlSwimAround), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND, + bits_SOUND_PLAYER | + bits_SOUND_COMBAT, + "SwimAround" + }, +}; + +static Task_t tlSwimAgitated[] = +{ + { TASK_STOP_MOVING, (float) 0 }, + { TASK_SET_ACTIVITY, (float)ACT_RUN }, + { TASK_WAIT, (float)2.0 }, +}; + +static Schedule_t slSwimAgitated[] = +{ + { + tlSwimAgitated, + ARRAYSIZE(tlSwimAgitated), + 0, + 0, + "SwimAgitated" + }, +}; + + +static Task_t tlCircleEnemy[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_WALK }, + { TASK_ICHTHYOSAUR_CIRCLE_ENEMY, 0.0 }, +}; + +static Schedule_t slCircleEnemy[] = +{ + { + tlCircleEnemy, + ARRAYSIZE(tlCircleEnemy), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK1, + 0, + "CircleEnemy" + }, +}; + + +Task_t tlTwitchDie[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SOUND_DIE, (float)0 }, + { TASK_DIE, (float)0 }, + { TASK_ICHTHYOSAUR_FLOAT, (float)0 }, +}; + +Schedule_t slTwitchDie[] = +{ + { + tlTwitchDie, + ARRAYSIZE( tlTwitchDie ), + 0, + 0, + "Die" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES(CIchthyosaur) +{ + slSwimAround, + slSwimAgitated, + slCircleEnemy, + slTwitchDie, +}; +IMPLEMENT_CUSTOM_SCHEDULES(CIchthyosaur, CFlyingMonster); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CIchthyosaur :: Classify ( void ) +{ + return CLASS_ALIEN_MONSTER; +} + + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL CIchthyosaur :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + if ( flDot >= 0.7 && m_flEnemyTouched > gpGlobals->time - 0.2 ) + { + return TRUE; + } + return FALSE; +} + +void CIchthyosaur::BiteTouch( CBaseEntity *pOther ) +{ + // bite if we hit who we want to eat + if ( pOther == m_hEnemy ) + { + m_flEnemyTouched = gpGlobals->time; + m_bOnAttack = TRUE; + } +} + +void CIchthyosaur::CombatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_bOnAttack ) ) + return; + + if (m_bOnAttack) + { + m_bOnAttack = 0; + } + else + { + m_bOnAttack = 1; + } +} + +//========================================================= +// CheckRangeAttack1 - swim in for a chomp +// +//========================================================= +BOOL CIchthyosaur :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDot > -0.7 && (m_bOnAttack || ( flDist <= 192 && m_idealDist <= 192))) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CIchthyosaur :: SetYawSpeed ( void ) +{ + pev->yaw_speed = 100; +} + + + +//========================================================= +// Killed - overrides CFlyingMonster. +// +void CIchthyosaur :: Killed( entvars_t *pevAttacker, int iGib ) +{ + CBaseMonster::Killed( pevAttacker, iGib ); + pev->velocity = Vector( 0, 0, 0 ); +} + +void CIchthyosaur::BecomeDead( void ) +{ + pev->takedamage = DAMAGE_YES;// don't let autoaim aim at corpses. + + // give the corpse half of the monster's original maximum health. + pev->health = pev->max_health / 2; + pev->max_health = 5; // max_health now becomes a counter for how many blood decals the corpse can place. +} + +#define ICHTHYOSAUR_AE_SHAKE_RIGHT 1 +#define ICHTHYOSAUR_AE_SHAKE_LEFT 2 + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CIchthyosaur :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + int bDidAttack = FALSE; + switch( pEvent->event ) + { + case ICHTHYOSAUR_AE_SHAKE_RIGHT: + case ICHTHYOSAUR_AE_SHAKE_LEFT: + { + if (m_hEnemy != NULL && FVisible( m_hEnemy )) + { + CBaseEntity *pHurt = m_hEnemy; + + if (m_flEnemyTouched < gpGlobals->time - 0.2 && (m_hEnemy->BodyTarget( pev->origin ) - pev->origin).Length() > (32+16+32)) + break; + + Vector vecShootDir = ShootAtEnemy( pev->origin ); + UTIL_MakeAimVectors ( pev->angles ); + + if (DotProduct( vecShootDir, gpGlobals->v_forward ) > 0.707) + { + m_bOnAttack = TRUE; + pHurt->pev->punchangle.z = -18; + pHurt->pev->punchangle.x = 5; + pHurt->pev->velocity = pHurt->pev->velocity - gpGlobals->v_right * 300; + if (pHurt->IsPlayer()) + { + pHurt->pev->angles.x += RANDOM_FLOAT( -35, 35 ); + pHurt->pev->angles.y += RANDOM_FLOAT( -90, 90 ); + pHurt->pev->angles.z = 0; + pHurt->pev->fixangle = TRUE; + } + pHurt->TakeDamage( pev, pev, gSkillData.ichthyosaurDmgShake, DMG_SLASH ); + } + } + BiteSound(); + + bDidAttack = TRUE; + } + break; + default: + CFlyingMonster::HandleAnimEvent( pEvent ); + break; + } + + if (bDidAttack) + { + Vector vecSrc = pev->origin + gpGlobals->v_forward * 32; + UTIL_Bubbles( vecSrc - Vector( 8, 8, 8 ), vecSrc + Vector( 8, 8, 8 ), 16 ); + } +} + +//========================================================= +// Spawn +//========================================================= +void CIchthyosaur :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/icky.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, -32 ), Vector( 32, 32, 32 ) ); + + pev->solid = SOLID_BBOX; + pev->movetype = MOVETYPE_FLY; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->health = gSkillData.ichthyosaurHealth; + pev->view_ofs = Vector ( 0, 0, 16 ); + m_flFieldOfView = VIEW_FIELD_WIDE; + m_MonsterState = MONSTERSTATE_NONE; + SetBits(pev->flags, FL_SWIM); + SetFlyingSpeed( ICHTHYOSAUR_SPEED ); + SetFlyingMomentum( 2.5 ); // Set momentum constant + + m_afCapability = bits_CAP_RANGE_ATTACK1 | bits_CAP_SWIM; + + MonsterInit(); + + SetTouch( &CIchthyosaur::BiteTouch ); + SetUse( &CIchthyosaur::CombatUse ); + + m_idealDist = 384; + m_flMinSpeed = 80; + m_flMaxSpeed = 300; + m_flMaxDist = 384; + + Vector Forward; + UTIL_MakeVectorsPrivate(pev->angles, Forward, 0, 0); + pev->velocity = m_flightSpeed * Forward.Normalize(); + m_SaveVelocity = pev->velocity; +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CIchthyosaur :: Precache() +{ + PRECACHE_MODEL("models/icky.mdl"); + + PRECACHE_SOUND_ARRAY( pIdleSounds ); + PRECACHE_SOUND_ARRAY( pAlertSounds ); + PRECACHE_SOUND_ARRAY( pAttackSounds ); + PRECACHE_SOUND_ARRAY( pBiteSounds ); + PRECACHE_SOUND_ARRAY( pDieSounds ); + PRECACHE_SOUND_ARRAY( pPainSounds ); +} + +//========================================================= +// GetSchedule +//========================================================= +Schedule_t* CIchthyosaur::GetSchedule() +{ + // ALERT( at_console, "GetSchedule( )\n" ); + switch(m_MonsterState) + { + case MONSTERSTATE_IDLE: + m_flightSpeed = 80; + return GetScheduleOfType( SCHED_IDLE_WALK ); + + case MONSTERSTATE_ALERT: + m_flightSpeed = 150; + return GetScheduleOfType( SCHED_IDLE_WALK ); + + case MONSTERSTATE_COMBAT: + m_flMaxSpeed = 400; + // eat them + if ( HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_MELEE_ATTACK1 ); + } + // chase them down and eat them + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_CHASE_ENEMY ); + } + if ( HasConditions( bits_COND_HEAVY_DAMAGE ) ) + { + m_bOnAttack = TRUE; + } + if ( pev->health < pev->max_health - 20 ) + { + m_bOnAttack = TRUE; + } + + return GetScheduleOfType( SCHED_STANDOFF ); + } + + return CFlyingMonster :: GetSchedule(); +} + + +//========================================================= +//========================================================= +Schedule_t* CIchthyosaur :: GetScheduleOfType ( int Type ) +{ + // ALERT( at_console, "GetScheduleOfType( %d ) %d\n", Type, m_bOnAttack ); + switch ( Type ) + { + case SCHED_IDLE_WALK: + return slSwimAround; + case SCHED_STANDOFF: + return slCircleEnemy; + case SCHED_FAIL: + return slSwimAgitated; + case SCHED_DIE: + return slTwitchDie; + case SCHED_CHASE_ENEMY: + AttackSound( ); + } + + return CBaseMonster :: GetScheduleOfType( Type ); +} + + + +//========================================================= +// Start task - selects the correct activity and performs +// any necessary calculations to start the next task on the +// schedule. +//========================================================= +void CIchthyosaur::StartTask(Task_t *pTask) +{ + switch (pTask->iTask) + { + case TASK_ICHTHYOSAUR_CIRCLE_ENEMY: + break; + case TASK_ICHTHYOSAUR_SWIM: + break; + case TASK_SMALL_FLINCH: + if (m_idealDist > 128) + { + m_flMaxDist = 512; + m_idealDist = 512; + } + else + { + m_bOnAttack = TRUE; + } + CFlyingMonster::StartTask(pTask); + break; + + case TASK_ICHTHYOSAUR_FLOAT: + pev->skin = EYE_BASE; + SetSequenceByName( "bellyup" ); + break; + + default: + CFlyingMonster::StartTask(pTask); + break; + } +} + +void CIchthyosaur :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_ICHTHYOSAUR_CIRCLE_ENEMY: + if (m_hEnemy == NULL) + { + TaskComplete( ); + } + else if (FVisible( m_hEnemy )) + { + Vector vecFrom = m_hEnemy->EyePosition( ); + + Vector vecDelta = (pev->origin - vecFrom).Normalize( ); + Vector vecSwim = CrossProduct( vecDelta, Vector( 0, 0, 1 ) ).Normalize( ); + + if (DotProduct( vecSwim, m_SaveVelocity ) < 0) + vecSwim = vecSwim * -1.0; + + Vector vecPos = vecFrom + vecDelta * m_idealDist + vecSwim * 32; + + // ALERT( at_console, "vecPos %.0f %.0f %.0f\n", vecPos.x, vecPos.y, vecPos.z ); + + TraceResult tr; + + UTIL_TraceHull( vecFrom, vecPos, ignore_monsters, large_hull, m_hEnemy->edict(), &tr ); + + if (tr.flFraction > 0.5) + vecPos = tr.vecEndPos; + + m_SaveVelocity = m_SaveVelocity * 0.8 + 0.2 * (vecPos - pev->origin).Normalize() * m_flightSpeed; + + // ALERT( at_console, "m_SaveVelocity %.2f %.2f %.2f\n", m_SaveVelocity.x, m_SaveVelocity.y, m_SaveVelocity.z ); + + if (HasConditions( bits_COND_ENEMY_FACING_ME ) && m_hEnemy->FVisible( this )) + { + m_flNextAlert -= 0.1; + + if (m_idealDist < m_flMaxDist) + { + m_idealDist += 4; + } + if (m_flightSpeed > m_flMinSpeed) + { + m_flightSpeed -= 2; + } + else if (m_flightSpeed < m_flMinSpeed) + { + m_flightSpeed += 2; + } + if (m_flMinSpeed < m_flMaxSpeed) + { + m_flMinSpeed += 0.5; + } + } + else + { + m_flNextAlert += 0.1; + + if (m_idealDist > 128) + { + m_idealDist -= 4; + } + if (m_flightSpeed < m_flMaxSpeed) + { + m_flightSpeed += 4; + } + } + // ALERT( at_console, "%.0f\n", m_idealDist ); + } + else + { + m_flNextAlert = gpGlobals->time + 0.2; + } + + if (m_flNextAlert < gpGlobals->time) + { + // ALERT( at_console, "AlertSound()\n"); + AlertSound( ); + m_flNextAlert = gpGlobals->time + RANDOM_FLOAT( 3, 5 ); + } + + break; + case TASK_ICHTHYOSAUR_SWIM: + if (m_fSequenceFinished) + { + TaskComplete( ); + } + break; + case TASK_DIE: + if ( m_fSequenceFinished ) + { + pev->deadflag = DEAD_DEAD; + + TaskComplete( ); + } + break; + + case TASK_ICHTHYOSAUR_FLOAT: + pev->angles.x = UTIL_ApproachAngle( 0, pev->angles.x, 20 ); + pev->velocity = pev->velocity * 0.8; + if (pev->waterlevel > 1 && pev->velocity.z < 64) + { + pev->velocity.z += 8; + } + else + { + pev->velocity.z -= 8; + } + // ALERT( at_console, "%f\n", pev->velocity.z ); + break; + + default: + CFlyingMonster :: RunTask ( pTask ); + break; + } +} + + + +float CIchthyosaur::VectorToPitch( const Vector &vec ) +{ + float pitch; + if (vec.z == 0 && vec.x == 0) + pitch = 0; + else + { + pitch = (int) (atan2(vec.z, sqrt(vec.x*vec.x+vec.y*vec.y)) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + return pitch; +} + +//========================================================= +void CIchthyosaur::Move(float flInterval) +{ + CFlyingMonster::Move( flInterval ); +} + +float CIchthyosaur::FlPitchDiff( void ) +{ + float flPitchDiff; + float flCurrentPitch; + + flCurrentPitch = UTIL_AngleMod( pev->angles.z ); + + if ( flCurrentPitch == pev->idealpitch ) + { + return 0; + } + + flPitchDiff = pev->idealpitch - flCurrentPitch; + + if ( pev->idealpitch > flCurrentPitch ) + { + if (flPitchDiff >= 180) + flPitchDiff = flPitchDiff - 360; + } + else + { + if (flPitchDiff <= -180) + flPitchDiff = flPitchDiff + 360; + } + return flPitchDiff; +} + +float CIchthyosaur :: ChangePitch( int speed ) +{ + if ( pev->movetype == MOVETYPE_FLY ) + { + float diff = FlPitchDiff(); + float target = 0; + if ( m_IdealActivity != GetStoppedActivity() ) + { + if (diff < -20) + target = 45; + else if (diff > 20) + target = -45; + } + pev->angles.x = UTIL_Approach(target, pev->angles.x, 220.0 * 0.1 ); + } + return 0; +} + +float CIchthyosaur::ChangeYaw( int speed ) +{ + if ( pev->movetype == MOVETYPE_FLY ) + { + float diff = FlYawDiff(); + float target = 0; + + if ( m_IdealActivity != GetStoppedActivity() ) + { + if ( diff < -20 ) + target = 20; + else if ( diff > 20 ) + target = -20; + } + pev->angles.z = UTIL_Approach( target, pev->angles.z, 220.0 * 0.1 ); + } + return CFlyingMonster::ChangeYaw( speed ); +} + + +Activity CIchthyosaur:: GetStoppedActivity( void ) +{ + if ( pev->movetype != MOVETYPE_FLY ) // UNDONE: Ground idle here, IDLE may be something else + return ACT_IDLE; + return ACT_WALK; +} + +void CIchthyosaur::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) +{ + m_SaveVelocity = vecDir * m_flightSpeed; +} + + +void CIchthyosaur::MonsterThink ( void ) +{ + CFlyingMonster::MonsterThink( ); + + if (pev->deadflag == DEAD_NO) + { + if (m_MonsterState != MONSTERSTATE_SCRIPT) + { + Swim( ); + + // blink the eye + if (m_flBlink < gpGlobals->time) + { + pev->skin = EYE_CLOSED; + if (m_flBlink + 0.2 < gpGlobals->time) + { + m_flBlink = gpGlobals->time + RANDOM_FLOAT( 3, 4 ); + if (m_bOnAttack) + pev->skin = EYE_MAD; + else + pev->skin = EYE_BASE; + } + } + } + } +} + +void CIchthyosaur :: Stop( void ) +{ + if (!m_bOnAttack) + m_flightSpeed = 80.0; +} + +void CIchthyosaur::Swim( ) +{ + int retValue = 0; + + Vector start = pev->origin; + + Vector Angles; + Vector Forward, Right, Up; + + if (FBitSet( pev->flags, FL_ONGROUND)) + { + pev->angles.x = 0; + pev->angles.y += RANDOM_FLOAT( -45, 45 ); + ClearBits( pev->flags, FL_ONGROUND ); + + Angles = Vector( -pev->angles.x, pev->angles.y, pev->angles.z ); + UTIL_MakeVectorsPrivate(Angles, Forward, Right, Up); + + pev->velocity = Forward * 200 + Up * 200; + + return; + } + + if (m_bOnAttack && m_flightSpeed < m_flMaxSpeed) + { + m_flightSpeed += 40; + } + if (m_flightSpeed < 180) + { + if (m_IdealActivity == ACT_RUN) + SetActivity( ACT_WALK ); + if (m_IdealActivity == ACT_WALK) + pev->framerate = m_flightSpeed / 150.0; + // ALERT( at_console, "walk %.2f\n", pev->framerate ); + } + else + { + if (m_IdealActivity == ACT_WALK) + SetActivity( ACT_RUN ); + if (m_IdealActivity == ACT_RUN) + pev->framerate = m_flightSpeed / 150.0; + // ALERT( at_console, "run %.2f\n", pev->framerate ); + } + +/* + if (!m_pBeam) + { + m_pBeam = CBeam::BeamCreate( "sprites/laserbeam.spr", 80 ); + m_pBeam->PointEntInit( pev->origin + m_SaveVelocity, entindex( ) ); + m_pBeam->SetEndAttachment( 1 ); + m_pBeam->SetColor( 255, 180, 96 ); + m_pBeam->SetBrightness( 192 ); + } +*/ +#define PROBE_LENGTH 150 + Angles = UTIL_VecToAngles( m_SaveVelocity ); + Angles.x = -Angles.x; + UTIL_MakeVectorsPrivate(Angles, Forward, Right, Up); + + Vector f, u, l, r, d; + f = DoProbe(start + PROBE_LENGTH * Forward); + r = DoProbe(start + PROBE_LENGTH/3 * Forward+Right); + l = DoProbe(start + PROBE_LENGTH/3 * Forward-Right); + u = DoProbe(start + PROBE_LENGTH/3 * Forward+Up); + d = DoProbe(start + PROBE_LENGTH/3 * Forward-Up); + + Vector SteeringVector = f+r+l+u+d; + m_SaveVelocity = (m_SaveVelocity + SteeringVector/2).Normalize(); + + Angles = Vector( -pev->angles.x, pev->angles.y, pev->angles.z ); + UTIL_MakeVectorsPrivate(Angles, Forward, Right, Up); + // ALERT( at_console, "%f : %f\n", Angles.x, Forward.z ); + + float flDot = DotProduct( Forward, m_SaveVelocity ); + if (flDot > 0.5) + pev->velocity = m_SaveVelocity = m_SaveVelocity * m_flightSpeed; + else if (flDot > 0) + pev->velocity = m_SaveVelocity = m_SaveVelocity * m_flightSpeed * (flDot + 0.5); + else + pev->velocity = m_SaveVelocity = m_SaveVelocity * 80; + + // ALERT( at_console, "%.0f %.0f\n", m_flightSpeed, pev->velocity.Length() ); + + + // ALERT( at_console, "Steer %f %f %f\n", SteeringVector.x, SteeringVector.y, SteeringVector.z ); + +/* + m_pBeam->SetStartPos( pev->origin + pev->velocity ); + m_pBeam->RelinkBeam( ); +*/ + + // ALERT( at_console, "speed %f\n", m_flightSpeed ); + + Angles = UTIL_VecToAngles( m_SaveVelocity ); + + // Smooth Pitch + // + if (Angles.x > 180) + Angles.x = Angles.x - 360; + pev->angles.x = UTIL_Approach(Angles.x, pev->angles.x, 50 * 0.1 ); + if (pev->angles.x < -80) pev->angles.x = -80; + if (pev->angles.x > 80) pev->angles.x = 80; + + // Smooth Yaw and generate Roll + // + float turn = 360; + // ALERT( at_console, "Y %.0f %.0f\n", Angles.y, pev->angles.y ); + + if (fabs(Angles.y - pev->angles.y) < fabs(turn)) + { + turn = Angles.y - pev->angles.y; + } + if (fabs(Angles.y - pev->angles.y + 360) < fabs(turn)) + { + turn = Angles.y - pev->angles.y + 360; + } + if (fabs(Angles.y - pev->angles.y - 360) < fabs(turn)) + { + turn = Angles.y - pev->angles.y - 360; + } + + float speed = m_flightSpeed * 0.1; + + // ALERT( at_console, "speed %.0f %f\n", turn, speed ); + if (fabs(turn) > speed) + { + if (turn < 0.0) + { + turn = -speed; + } + else + { + turn = speed; + } + } + pev->angles.y += turn; + pev->angles.z -= turn; + pev->angles.y = fmod((pev->angles.y + 360.0), 360.0); + + static float yaw_adj; + + yaw_adj = yaw_adj * 0.8 + turn; + + // ALERT( at_console, "yaw %f : %f\n", turn, yaw_adj ); + + SetBoneController( 0, -yaw_adj / 4.0 ); + + // Roll Smoothing + // + turn = 360; + if (fabs(Angles.z - pev->angles.z) < fabs(turn)) + { + turn = Angles.z - pev->angles.z; + } + if (fabs(Angles.z - pev->angles.z + 360) < fabs(turn)) + { + turn = Angles.z - pev->angles.z + 360; + } + if (fabs(Angles.z - pev->angles.z - 360) < fabs(turn)) + { + turn = Angles.z - pev->angles.z - 360; + } + speed = m_flightSpeed/2 * 0.1; + if (fabs(turn) < speed) + { + pev->angles.z += turn; + } + else + { + if (turn < 0.0) + { + pev->angles.z -= speed; + } + else + { + pev->angles.z += speed; + } + } + if (pev->angles.z < -20) pev->angles.z = -20; + if (pev->angles.z > 20) pev->angles.z = 20; + + UTIL_MakeVectorsPrivate( Vector( -Angles.x, Angles.y, Angles.z ), Forward, Right, Up); + + // UTIL_MoveToOrigin ( ENT(pev), pev->origin + Forward * speed, speed, MOVE_STRAFE ); +} + + +Vector CIchthyosaur::DoProbe(const Vector &Probe) +{ + Vector WallNormal = Vector(0,0,-1); // WATER normal is Straight Down for fish. + float frac; + BOOL bBumpedSomething = ProbeZ(pev->origin, Probe, &frac); + + TraceResult tr; + TRACE_MONSTER_HULL(edict(), pev->origin, Probe, dont_ignore_monsters, edict(), &tr); + if ( tr.fAllSolid || tr.flFraction < 0.99 ) + { + if (tr.flFraction < 0.0) tr.flFraction = 0.0; + if (tr.flFraction > 1.0) tr.flFraction = 1.0; + if (tr.flFraction < frac) + { + frac = tr.flFraction; + bBumpedSomething = TRUE; + WallNormal = tr.vecPlaneNormal; + } + } + + if (bBumpedSomething && (m_hEnemy == NULL || tr.pHit != m_hEnemy->edict())) + { + Vector ProbeDir = Probe - pev->origin; + + Vector NormalToProbeAndWallNormal = CrossProduct(ProbeDir, WallNormal); + Vector SteeringVector = CrossProduct( NormalToProbeAndWallNormal, ProbeDir); + + float SteeringForce = m_flightSpeed * (1-frac) * (DotProduct(WallNormal.Normalize(), m_SaveVelocity.Normalize())); + if (SteeringForce < 0.0) + { + SteeringForce = -SteeringForce; + } + SteeringVector = SteeringForce * SteeringVector.Normalize(); + + return SteeringVector; + } + return Vector(0, 0, 0); +} + +#endif diff --git a/dlls/islave.cpp b/dlls/islave.cpp new file mode 100644 index 0000000..53e6446 --- /dev/null +++ b/dlls/islave.cpp @@ -0,0 +1,866 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Alien slave monster +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "squadmonster.h" +#include "schedule.h" +#include "effects.h" +#include "weapons.h" +#include "soundent.h" + +extern DLL_GLOBAL int g_iSkillLevel; + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define ISLAVE_AE_CLAW ( 1 ) +#define ISLAVE_AE_CLAWRAKE ( 2 ) +#define ISLAVE_AE_ZAP_POWERUP ( 3 ) +#define ISLAVE_AE_ZAP_SHOOT ( 4 ) +#define ISLAVE_AE_ZAP_DONE ( 5 ) + +#define ISLAVE_MAX_BEAMS 8 + +class CISlave : public CSquadMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int ISoundMask( void ); + int Classify ( void ); + int IRelationship( CBaseEntity *pTarget ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack2 ( float flDot, float flDist ); + void CallForHelp( char *szClassname, float flDist, EHANDLE hEnemy, Vector &vecLocation ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + + void DeathSound( void ); + void PainSound( void ); + void AlertSound( void ); + void IdleSound( void ); + + void Killed( entvars_t *pevAttacker, int iGib ); + + void StartTask ( Task_t *pTask ); + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType ( int Type ); + CUSTOM_SCHEDULES; + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void ClearBeams( ); + void ArmBeam( int side ); + void WackBeam( int side, CBaseEntity *pEntity ); + void ZapBeam( int side ); + void BeamGlow( void ); + + int m_iBravery; + + CBeam *m_pBeam[ISLAVE_MAX_BEAMS]; + + int m_iBeams; + float m_flNextAttack; + + int m_voicePitch; + + EHANDLE m_hDead; + + static const char *pAttackHitSounds[]; + static const char *pAttackMissSounds[]; + static const char *pPainSounds[]; + static const char *pDeathSounds[]; +}; +LINK_ENTITY_TO_CLASS( monster_alien_slave, CISlave ); +LINK_ENTITY_TO_CLASS( monster_vortigaunt, CISlave ); + + +TYPEDESCRIPTION CISlave::m_SaveData[] = +{ + DEFINE_FIELD( CISlave, m_iBravery, FIELD_INTEGER ), + + DEFINE_ARRAY( CISlave, m_pBeam, FIELD_CLASSPTR, ISLAVE_MAX_BEAMS ), + DEFINE_FIELD( CISlave, m_iBeams, FIELD_INTEGER ), + DEFINE_FIELD( CISlave, m_flNextAttack, FIELD_TIME ), + + DEFINE_FIELD( CISlave, m_voicePitch, FIELD_INTEGER ), + + DEFINE_FIELD( CISlave, m_hDead, FIELD_EHANDLE ), + +}; + +IMPLEMENT_SAVERESTORE( CISlave, CSquadMonster ); + + + + +const char *CISlave::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CISlave::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CISlave::pPainSounds[] = +{ + "aslave/slv_pain1.wav", + "aslave/slv_pain2.wav", +}; + +const char *CISlave::pDeathSounds[] = +{ + "aslave/slv_die1.wav", + "aslave/slv_die2.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CISlave :: Classify ( void ) +{ + return CLASS_ALIEN_MILITARY; +} + + +int CISlave::IRelationship( CBaseEntity *pTarget ) +{ + if ( (pTarget->IsPlayer()) ) + if ( (pev->spawnflags & SF_MONSTER_WAIT_UNTIL_PROVOKED ) && ! (m_afMemory & bits_MEMORY_PROVOKED )) + return R_NO; + return CBaseMonster::IRelationship( pTarget ); +} + + +void CISlave :: CallForHelp( char *szClassname, float flDist, EHANDLE hEnemy, Vector &vecLocation ) +{ + // ALERT( at_aiconsole, "help " ); + + // skip ones not on my netname + if ( FStringNull( pev->netname )) + return; + + CBaseEntity *pEntity = NULL; + + while ((pEntity = UTIL_FindEntityByString( pEntity, "netname", STRING( pev->netname ))) != NULL) + { + float d = (pev->origin - pEntity->pev->origin).Length(); + if (d < flDist) + { + CBaseMonster *pMonster = pEntity->MyMonsterPointer( ); + if (pMonster) + { + pMonster->m_afMemory |= bits_MEMORY_PROVOKED; + pMonster->PushEnemy( hEnemy, vecLocation ); + } + } + } +} + + +//========================================================= +// ALertSound - scream +//========================================================= +void CISlave :: AlertSound( void ) +{ + if ( m_hEnemy != NULL ) + { + SENTENCEG_PlayRndSz(ENT(pev), "SLV_ALERT", 0.85, ATTN_NORM, 0, m_voicePitch); + + CallForHelp( "monster_alien_slave", 512, m_hEnemy, m_vecEnemyLKP ); + } +} + +//========================================================= +// IdleSound +//========================================================= +void CISlave :: IdleSound( void ) +{ + if (RANDOM_LONG( 0, 2 ) == 0) + { + SENTENCEG_PlayRndSz(ENT(pev), "SLV_IDLE", 0.85, ATTN_NORM, 0, m_voicePitch); + } + +#if 0 + int side = RANDOM_LONG( 0, 1 ) * 2 - 1; + + ClearBeams( ); + ArmBeam( side ); + + UTIL_MakeAimVectors( pev->angles ); + Vector vecSrc = pev->origin + gpGlobals->v_right * 2 * side; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSrc ); + WRITE_BYTE(TE_DLIGHT); + WRITE_COORD(vecSrc.x); // X + WRITE_COORD(vecSrc.y); // Y + WRITE_COORD(vecSrc.z); // Z + WRITE_BYTE( 8 ); // radius * 0.1 + WRITE_BYTE( 255 ); // r + WRITE_BYTE( 180 ); // g + WRITE_BYTE( 96 ); // b + WRITE_BYTE( 10 ); // time * 10 + WRITE_BYTE( 0 ); // decay * 0.1 + MESSAGE_END( ); + + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "debris/zap1.wav", 1, ATTN_NORM, 0, 100 ); +#endif +} + +//========================================================= +// PainSound +//========================================================= +void CISlave :: PainSound( void ) +{ + if (RANDOM_LONG( 0, 2 ) == 0) + { + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); + } +} + +//========================================================= +// DieSound +//========================================================= + +void CISlave :: DeathSound( void ) +{ + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pDeathSounds[ RANDOM_LONG(0,ARRAYSIZE(pDeathSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); +} + + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. +//========================================================= +int CISlave :: ISoundMask ( void) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_DANGER | + bits_SOUND_PLAYER; +} + + +void CISlave::Killed( entvars_t *pevAttacker, int iGib ) +{ + ClearBeams( ); + CSquadMonster::Killed( pevAttacker, iGib ); +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CISlave :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_WALK: + ys = 50; + break; + case ACT_RUN: + ys = 70; + break; + case ACT_IDLE: + ys = 50; + break; + default: + ys = 90; + break; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CISlave :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + // ALERT( at_console, "event %d : %f\n", pEvent->event, pev->frame ); + switch( pEvent->event ) + { + case ISLAVE_AE_CLAW: + { + // SOUND HERE! + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.slaveDmgClaw, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.z = -18; + pHurt->pev->punchangle.x = 5; + } + // Play a random attack hit sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); + } + else + { + // Play a random attack miss sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); + } + } + break; + + case ISLAVE_AE_CLAWRAKE: + { + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.slaveDmgClawrake, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.z = -18; + pHurt->pev->punchangle.x = 5; + } + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); + } + else + { + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); + } + } + break; + + case ISLAVE_AE_ZAP_POWERUP: + { + // speed up attack when on hard + if (g_iSkillLevel == SKILL_HARD) + pev->framerate = 1.5; + + UTIL_MakeAimVectors( pev->angles ); + + if (m_iBeams == 0) + { + Vector vecSrc = pev->origin + gpGlobals->v_forward * 2; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSrc ); + WRITE_BYTE(TE_DLIGHT); + WRITE_COORD(vecSrc.x); // X + WRITE_COORD(vecSrc.y); // Y + WRITE_COORD(vecSrc.z); // Z + WRITE_BYTE( 12 ); // radius * 0.1 + WRITE_BYTE( 255 ); // r + WRITE_BYTE( 180 ); // g + WRITE_BYTE( 96 ); // b + WRITE_BYTE( 20 / pev->framerate ); // time * 10 + WRITE_BYTE( 0 ); // decay * 0.1 + MESSAGE_END( ); + + } + if (m_hDead != NULL) + { + WackBeam( -1, m_hDead ); + WackBeam( 1, m_hDead ); + } + else + { + ArmBeam( -1 ); + ArmBeam( 1 ); + BeamGlow( ); + } + + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "debris/zap4.wav", 1, ATTN_NORM, 0, 100 + m_iBeams * 10 ); + pev->skin = m_iBeams / 2; + } + break; + + case ISLAVE_AE_ZAP_SHOOT: + { + ClearBeams( ); + + if (m_hDead != NULL) + { + Vector vecDest = m_hDead->pev->origin + Vector( 0, 0, 38 ); + TraceResult trace; + UTIL_TraceHull( vecDest, vecDest, dont_ignore_monsters, human_hull, m_hDead->edict(), &trace ); + + if ( !trace.fStartSolid ) + { + CBaseEntity *pNew = Create( "monster_alien_slave", m_hDead->pev->origin, m_hDead->pev->angles ); + CBaseMonster *pNewMonster = pNew->MyMonsterPointer( ); + pNew->pev->spawnflags |= 1; + WackBeam( -1, pNew ); + WackBeam( 1, pNew ); + UTIL_Remove( m_hDead ); + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "hassault/hw_shoot1.wav", 1, ATTN_NORM, 0, RANDOM_LONG( 130, 160 ) ); + + /* + CBaseEntity *pEffect = Create( "test_effect", pNew->Center(), pev->angles ); + pEffect->Use( this, this, USE_ON, 1 ); + */ + break; + } + } + ClearMultiDamage(); + + UTIL_MakeAimVectors( pev->angles ); + + ZapBeam( -1 ); + ZapBeam( 1 ); + + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "hassault/hw_shoot1.wav", 1, ATTN_NORM, 0, RANDOM_LONG( 130, 160 ) ); + // STOP_SOUND( ENT(pev), CHAN_WEAPON, "debris/zap4.wav" ); + ApplyMultiDamage(pev, pev); + + m_flNextAttack = gpGlobals->time + RANDOM_FLOAT( 0.5, 4.0 ); + } + break; + + case ISLAVE_AE_ZAP_DONE: + { + ClearBeams( ); + } + break; + + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// CheckRangeAttack1 - normal beam attack +//========================================================= +BOOL CISlave :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if (m_flNextAttack > gpGlobals->time) + { + return FALSE; + } + + return CSquadMonster::CheckRangeAttack1( flDot, flDist ); +} + +//========================================================= +// CheckRangeAttack2 - check bravery and try to resurect dead comrades +//========================================================= +BOOL CISlave :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + return FALSE; + + if (m_flNextAttack > gpGlobals->time) + { + return FALSE; + } + + m_hDead = NULL; + m_iBravery = 0; + + CBaseEntity *pEntity = NULL; + while ((pEntity = UTIL_FindEntityByClassname( pEntity, "monster_alien_slave" )) != NULL) + { + TraceResult tr; + + UTIL_TraceLine( EyePosition( ), pEntity->EyePosition( ), ignore_monsters, ENT(pev), &tr ); + if (tr.flFraction == 1.0 || tr.pHit == pEntity->edict()) + { + if (pEntity->pev->deadflag == DEAD_DEAD) + { + float d = (pev->origin - pEntity->pev->origin).Length(); + if (d < flDist) + { + m_hDead = pEntity; + flDist = d; + } + m_iBravery--; + } + else + { + m_iBravery++; + } + } + } + if (m_hDead != NULL) + return TRUE; + else + return FALSE; +} + + +//========================================================= +// StartTask +//========================================================= +void CISlave :: StartTask ( Task_t *pTask ) +{ + ClearBeams( ); + + CSquadMonster :: StartTask ( pTask ); +} + + +//========================================================= +// Spawn +//========================================================= +void CISlave :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/islave.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->effects = 0; + pev->health = gSkillData.slaveHealth; + pev->view_ofs = Vector ( 0, 0, 64 );// position of the eyes relative to monster's origin. + m_flFieldOfView = VIEW_FIELD_WIDE; // NOTE: we need a wide field of view so npc will notice player and say hello + m_MonsterState = MONSTERSTATE_NONE; + m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD | bits_CAP_RANGE_ATTACK2 | bits_CAP_DOORS_GROUP; + + m_voicePitch = RANDOM_LONG( 85, 110 ); + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CISlave :: Precache() +{ + int i; + + PRECACHE_MODEL("models/islave.mdl"); + PRECACHE_MODEL("sprites/lgtning.spr"); + PRECACHE_SOUND("debris/zap1.wav"); + PRECACHE_SOUND("debris/zap4.wav"); + PRECACHE_SOUND("weapons/electro4.wav"); + PRECACHE_SOUND("hassault/hw_shoot1.wav"); + PRECACHE_SOUND("zombie/zo_pain2.wav"); + PRECACHE_SOUND("headcrab/hc_headbite.wav"); + PRECACHE_SOUND("weapons/cbar_miss1.wav"); + + for ( i = 0; i < ARRAYSIZE( pAttackHitSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackHitSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackMissSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackMissSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pPainSounds ); i++ ) + PRECACHE_SOUND((char *)pPainSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pDeathSounds ); i++ ) + PRECACHE_SOUND((char *)pDeathSounds[i]); + + UTIL_PrecacheOther( "test_effect" ); +} + + +//========================================================= +// TakeDamage - get provoked when injured +//========================================================= + +int CISlave :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType) +{ + // don't slash one of your own + if ((bitsDamageType & DMG_SLASH) && pevAttacker && IRelationship( Instance(pevAttacker) ) < R_DL) + return 0; + + m_afMemory |= bits_MEMORY_PROVOKED; + return CSquadMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); +} + + +void CISlave::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if (bitsDamageType & DMG_SHOCK) + return; + + CSquadMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + + +// primary range attack +Task_t tlSlaveAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slSlaveAttack1[] = +{ + { + tlSlaveAttack1, + ARRAYSIZE ( tlSlaveAttack1 ), + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_HEAR_SOUND | + bits_COND_HEAVY_DAMAGE, + + bits_SOUND_DANGER, + "Slave Range Attack1" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CISlave ) +{ + slSlaveAttack1, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CISlave, CSquadMonster ); + + +//========================================================= +//========================================================= +Schedule_t *CISlave :: GetSchedule( void ) +{ + ClearBeams( ); + +/* + if (pev->spawnflags) + { + pev->spawnflags = 0; + return GetScheduleOfType( SCHED_RELOAD ); + } +*/ + + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + + if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + if ( pSound->m_iType & bits_SOUND_COMBAT ) + m_afMemory |= bits_MEMORY_PROVOKED; + } + + switch (m_MonsterState) + { + case MONSTERSTATE_COMBAT: +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + + if (pev->health < 20 || m_iBravery < 0) + { + if (!HasConditions( bits_COND_CAN_MELEE_ATTACK1 )) + { + m_failSchedule = SCHED_CHASE_ENEMY; + if (HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE)) + { + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + if ( HasConditions ( bits_COND_SEE_ENEMY ) && HasConditions ( bits_COND_ENEMY_FACING_ME ) ) + { + // ALERT( at_console, "exposed\n"); + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + } + } + break; + } + return CSquadMonster::GetSchedule( ); +} + + +Schedule_t *CISlave :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_FAIL: + if (HasConditions( bits_COND_CAN_MELEE_ATTACK1 )) + { + return CSquadMonster :: GetScheduleOfType( SCHED_MELEE_ATTACK1 ); ; + } + break; + case SCHED_RANGE_ATTACK1: + return slSlaveAttack1; + case SCHED_RANGE_ATTACK2: + return slSlaveAttack1; + } + return CSquadMonster :: GetScheduleOfType( Type ); +} + + +//========================================================= +// ArmBeam - small beam from arm to nearby geometry +//========================================================= + +void CISlave :: ArmBeam( int side ) +{ + TraceResult tr; + float flDist = 1.0; + + if (m_iBeams >= ISLAVE_MAX_BEAMS) + return; + + UTIL_MakeAimVectors( pev->angles ); + Vector vecSrc = pev->origin + gpGlobals->v_up * 36 + gpGlobals->v_right * side * 16 + gpGlobals->v_forward * 32; + + for (int i = 0; i < 3; i++) + { + Vector vecAim = gpGlobals->v_right * side * RANDOM_FLOAT( 0, 1 ) + gpGlobals->v_up * RANDOM_FLOAT( -1, 1 ); + TraceResult tr1; + UTIL_TraceLine ( vecSrc, vecSrc + vecAim * 512, dont_ignore_monsters, ENT( pev ), &tr1); + if (flDist > tr1.flFraction) + { + tr = tr1; + flDist = tr.flFraction; + } + } + + // Couldn't find anything close enough + if ( flDist == 1.0 ) + return; + + DecalGunshot( &tr, BULLET_PLAYER_CROWBAR ); + + m_pBeam[m_iBeams] = CBeam::BeamCreate( "sprites/lgtning.spr", 30 ); + if (!m_pBeam[m_iBeams]) + return; + + m_pBeam[m_iBeams]->PointEntInit( tr.vecEndPos, entindex( ) ); + m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? 2 : 1 ); + // m_pBeam[m_iBeams]->SetColor( 180, 255, 96 ); + m_pBeam[m_iBeams]->SetColor( 96, 128, 16 ); + m_pBeam[m_iBeams]->SetBrightness( 64 ); + m_pBeam[m_iBeams]->SetNoise( 80 ); + m_iBeams++; +} + + +//========================================================= +// BeamGlow - brighten all beams +//========================================================= +void CISlave :: BeamGlow( ) +{ + int b = m_iBeams * 32; + if (b > 255) + b = 255; + + for (int i = 0; i < m_iBeams; i++) + { + if (m_pBeam[i]->GetBrightness() != 255) + { + m_pBeam[i]->SetBrightness( b ); + } + } +} + + +//========================================================= +// WackBeam - regenerate dead colleagues +//========================================================= +void CISlave :: WackBeam( int side, CBaseEntity *pEntity ) +{ + Vector vecDest; + float flDist = 1.0; + + if (m_iBeams >= ISLAVE_MAX_BEAMS) + return; + + if (pEntity == NULL) + return; + + m_pBeam[m_iBeams] = CBeam::BeamCreate( "sprites/lgtning.spr", 30 ); + if (!m_pBeam[m_iBeams]) + return; + + m_pBeam[m_iBeams]->PointEntInit( pEntity->Center(), entindex( ) ); + m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? 2 : 1 ); + m_pBeam[m_iBeams]->SetColor( 180, 255, 96 ); + m_pBeam[m_iBeams]->SetBrightness( 255 ); + m_pBeam[m_iBeams]->SetNoise( 80 ); + m_iBeams++; +} + +//========================================================= +// ZapBeam - heavy damage directly forward +//========================================================= +void CISlave :: ZapBeam( int side ) +{ + Vector vecSrc, vecAim; + TraceResult tr; + CBaseEntity *pEntity; + + if (m_iBeams >= ISLAVE_MAX_BEAMS) + return; + + vecSrc = pev->origin + gpGlobals->v_up * 36; + vecAim = ShootAtEnemy( vecSrc ); + float deflection = 0.01; + vecAim = vecAim + side * gpGlobals->v_right * RANDOM_FLOAT( 0, deflection ) + gpGlobals->v_up * RANDOM_FLOAT( -deflection, deflection ); + UTIL_TraceLine ( vecSrc, vecSrc + vecAim * 1024, dont_ignore_monsters, ENT( pev ), &tr); + + m_pBeam[m_iBeams] = CBeam::BeamCreate( "sprites/lgtning.spr", 50 ); + if (!m_pBeam[m_iBeams]) + return; + + m_pBeam[m_iBeams]->PointEntInit( tr.vecEndPos, entindex( ) ); + m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? 2 : 1 ); + m_pBeam[m_iBeams]->SetColor( 180, 255, 96 ); + m_pBeam[m_iBeams]->SetBrightness( 255 ); + m_pBeam[m_iBeams]->SetNoise( 20 ); + m_iBeams++; + + pEntity = CBaseEntity::Instance(tr.pHit); + if (pEntity != NULL && pEntity->pev->takedamage) + { + pEntity->TraceAttack( pev, gSkillData.slaveDmgZap, vecAim, &tr, DMG_SHOCK ); + } + UTIL_EmitAmbientSound( ENT(pev), tr.vecEndPos, "weapons/electro4.wav", 0.5, ATTN_NORM, 0, RANDOM_LONG( 140, 160 ) ); +} + + +//========================================================= +// ClearBeams - remove all beams +//========================================================= +void CISlave :: ClearBeams( ) +{ + for (int i = 0; i < ISLAVE_MAX_BEAMS; i++) + { + if (m_pBeam[i]) + { + UTIL_Remove( m_pBeam[i] ); + m_pBeam[i] = NULL; + } + } + m_iBeams = 0; + pev->skin = 0; + + STOP_SOUND( ENT(pev), CHAN_WEAPON, "debris/zap4.wav" ); +} diff --git a/dlls/items.cpp b/dlls/items.cpp new file mode 100644 index 0000000..00825a8 --- /dev/null +++ b/dlls/items.cpp @@ -0,0 +1,342 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== items.cpp ======================================================== + + functions governing the selection/use of weapons for players + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "player.h" +#include "skill.h" +#include "items.h" +#include "gamerules.h" + +extern int gmsgItemPickup; + +class CWorldItem : public CBaseEntity +{ +public: + void KeyValue(KeyValueData *pkvd ); + void Spawn( void ); + int m_iType; +}; + +LINK_ENTITY_TO_CLASS(world_items, CWorldItem); + +void CWorldItem::KeyValue(KeyValueData *pkvd) +{ + if (FStrEq(pkvd->szKeyName, "type")) + { + m_iType = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CWorldItem::Spawn( void ) +{ + CBaseEntity *pEntity = NULL; + + switch (m_iType) + { + case 44: // ITEM_BATTERY: + pEntity = CBaseEntity::Create( "item_battery", pev->origin, pev->angles ); + break; + case 42: // ITEM_ANTIDOTE: + pEntity = CBaseEntity::Create( "item_antidote", pev->origin, pev->angles ); + break; + case 43: // ITEM_SECURITY: + pEntity = CBaseEntity::Create( "item_security", pev->origin, pev->angles ); + break; + case 45: // ITEM_SUIT: + pEntity = CBaseEntity::Create( "item_suit", pev->origin, pev->angles ); + break; + } + + if (!pEntity) + { + ALERT( at_console, "unable to create world_item %d\n", m_iType ); + } + else + { + pEntity->pev->target = pev->target; + pEntity->pev->targetname = pev->targetname; + pEntity->pev->spawnflags = pev->spawnflags; + } + + REMOVE_ENTITY(edict()); +} + + +void CItem::Spawn( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + UTIL_SetOrigin( pev, pev->origin ); + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 16)); + SetTouch(&CItem::ItemTouch); + + if (DROP_TO_FLOOR(ENT(pev)) == 0) + { + ALERT(at_error, "Item %s fell out of level at %f,%f,%f", STRING( pev->classname ), pev->origin.x, pev->origin.y, pev->origin.z); + UTIL_Remove( this ); + return; + } +} + +extern int gEvilImpulse101; + +void CItem::ItemTouch( CBaseEntity *pOther ) +{ + // if it's not a player, ignore + if ( !pOther->IsPlayer() ) + { + return; + } + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + + // ok, a player is touching this item, but can he have it? + if ( !g_pGameRules->CanHaveItem( pPlayer, this ) ) + { + // no? Ignore the touch. + return; + } + + if (MyTouch( pPlayer )) + { + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); + SetTouch( NULL ); + + // player grabbed the item. + g_pGameRules->PlayerGotItem( pPlayer, this ); + if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_YES ) + { + Respawn(); + } + else + { + UTIL_Remove( this ); + } + } + else if (gEvilImpulse101) + { + UTIL_Remove( this ); + } +} + +CBaseEntity* CItem::Respawn( void ) +{ + SetTouch( NULL ); + pev->effects |= EF_NODRAW; + + UTIL_SetOrigin( pev, g_pGameRules->VecItemRespawnSpot( this ) );// blip to whereever you should respawn. + + SetThink ( &CItem::Materialize ); + pev->nextthink = g_pGameRules->FlItemRespawnTime( this ); + return this; +} + +void CItem::Materialize( void ) +{ + if ( pev->effects & EF_NODRAW ) + { + // changing from invisible state to visible. + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "items/suitchargeok1.wav", 1, ATTN_NORM, 0, 150 ); + pev->effects &= ~EF_NODRAW; + pev->effects |= EF_MUZZLEFLASH; + } + + SetTouch( &CItem::ItemTouch ); +} + +#define SF_SUIT_SHORTLOGON 0x0001 + +class CItemSuit : public CItem +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_suit.mdl"); + CItem::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_suit.mdl"); + } + BOOL MyTouch( CBasePlayer *pPlayer ) + { + if ( pPlayer->pev->weapons & (1<spawnflags & SF_SUIT_SHORTLOGON ) + EMIT_SOUND_SUIT(pPlayer->edict(), "!HEV_A0"); // short version of suit logon, + else + EMIT_SOUND_SUIT(pPlayer->edict(), "!HEV_AAx"); // long version of suit logon + + pPlayer->pev->weapons |= (1<pev->deadflag != DEAD_NO ) + { + return FALSE; + } + + if ((pPlayer->pev->armorvalue < MAX_NORMAL_BATTERY) && + (pPlayer->pev->weapons & (1<pev->armorvalue += gSkillData.batteryCapacity; + pPlayer->pev->armorvalue = min(pPlayer->pev->armorvalue, MAX_NORMAL_BATTERY); + + EMIT_SOUND( pPlayer->edict(), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM ); + + MESSAGE_BEGIN( MSG_ONE, gmsgItemPickup, NULL, pPlayer->pev ); + WRITE_STRING( STRING(pev->classname) ); + MESSAGE_END(); + + + // Suit reports new power level + // For some reason this wasn't working in release build -- round it. + pct = (int)( (float)(pPlayer->pev->armorvalue * 100.0) * (1.0/MAX_NORMAL_BATTERY) + 0.5); + pct = (pct / 5); + if (pct > 0) + pct--; + + sprintf( szcharge,"!HEV_%1dP", pct ); + + //EMIT_SOUND_SUIT(ENT(pev), szcharge); + pPlayer->SetSuitUpdate(szcharge, FALSE, SUIT_NEXT_IN_30SEC); + return TRUE; + } + return FALSE; + } +}; + +LINK_ENTITY_TO_CLASS(item_battery, CItemBattery); + + +class CItemAntidote : public CItem +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_antidote.mdl"); + CItem::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_antidote.mdl"); + } + BOOL MyTouch( CBasePlayer *pPlayer ) + { + pPlayer->SetSuitUpdate("!HEV_DET4", FALSE, SUIT_NEXT_IN_1MIN); + + pPlayer->m_rgItems[ITEM_ANTIDOTE] += 1; + return TRUE; + } +}; + +LINK_ENTITY_TO_CLASS(item_antidote, CItemAntidote); + + +class CItemSecurity : public CItem +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_security.mdl"); + CItem::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_security.mdl"); + } + BOOL MyTouch( CBasePlayer *pPlayer ) + { + pPlayer->m_rgItems[ITEM_SECURITY] += 1; + return TRUE; + } +}; + +LINK_ENTITY_TO_CLASS(item_security, CItemSecurity); + +class CItemLongJump : public CItem +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_longjump.mdl"); + CItem::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_longjump.mdl"); + } + BOOL MyTouch( CBasePlayer *pPlayer ) + { + if ( pPlayer->m_fLongJump ) + { + return FALSE; + } + + if ( ( pPlayer->pev->weapons & (1<m_fLongJump = TRUE;// player now has longjump module + + g_engfuncs.pfnSetPhysicsKeyValue( pPlayer->edict(), "slj", "1" ); + + MESSAGE_BEGIN( MSG_ONE, gmsgItemPickup, NULL, pPlayer->pev ); + WRITE_STRING( STRING(pev->classname) ); + MESSAGE_END(); + + EMIT_SOUND_SUIT( pPlayer->edict(), "!HEV_A1" ); // Play the longjump sound UNDONE: Kelly? correct sound? + return TRUE; + } + return FALSE; + } +}; + +LINK_ENTITY_TO_CLASS( item_longjump, CItemLongJump ); diff --git a/dlls/items.h b/dlls/items.h new file mode 100644 index 0000000..abd55df --- /dev/null +++ b/dlls/items.h @@ -0,0 +1,29 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef ITEMS_H +#define ITEMS_H + + +class CItem : public CBaseEntity +{ +public: + void Spawn( void ); + CBaseEntity* Respawn( void ); + void EXPORT ItemTouch( CBaseEntity *pOther ); + void EXPORT Materialize( void ); + virtual BOOL MyTouch( CBasePlayer *pPlayer ) { return FALSE; }; +}; + +#endif // ITEMS_H diff --git a/dlls/leech.cpp b/dlls/leech.cpp new file mode 100644 index 0000000..9201a9d --- /dev/null +++ b/dlls/leech.cpp @@ -0,0 +1,723 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// leech - basic little swimming monster +//========================================================= +// +// UNDONE: +// DONE:Steering force model for attack +// DONE:Attack animation control / damage +// DONE:Establish range of up/down motion and steer around vertical obstacles +// DONE:Re-evaluate height periodically +// DONE:Fall (MOVETYPE_TOSS) and play different anim if out of water +// Test in complex room (c2a3?) +// DONE:Sounds? - Kelly will fix +// Blood cloud? Hurt effect? +// Group behavior? +// DONE:Save/restore +// Flop animation - just bind to ACT_TWITCH +// Fix fatal push into wall case +// +// Try this on a bird +// Try this on a model with hulls/tracehull? +// + + +#include "float.h" +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" + + + + +// Animation events +#define LEECH_AE_ATTACK 1 +#define LEECH_AE_FLOP 2 + + +// Movement constants + +#define LEECH_ACCELERATE 10 +#define LEECH_CHECK_DIST 45 +#define LEECH_SWIM_SPEED 50 +#define LEECH_SWIM_ACCEL 80 +#define LEECH_SWIM_DECEL 10 +#define LEECH_TURN_RATE 90 +#define LEECH_SIZEX 10 +#define LEECH_FRAMETIME 0.1 + + + +#define DEBUG_BEAMS 0 + +#if DEBUG_BEAMS +#include "effects.h" +#endif + + +class CLeech : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + + void EXPORT SwimThink( void ); + void EXPORT DeadThink( void ); + void Touch( CBaseEntity *pOther ) + { + if ( pOther->IsPlayer() ) + { + // If the client is pushing me, give me some base velocity + if ( gpGlobals->trace_ent && gpGlobals->trace_ent == edict() ) + { + pev->basevelocity = pOther->pev->velocity; + pev->flags |= FL_BASEVELOCITY; + } + } + } + + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector(-8,-8,0); + pev->absmax = pev->origin + Vector(8,8,2); + } + + void AttackSound( void ); + void AlertSound( void ); + void UpdateMotion( void ); + float ObstacleDistance( CBaseEntity *pTarget ); + void MakeVectors( void ); + void RecalculateWaterlevel( void ); + void SwitchLeechState( void ); + + // Base entity functions + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int BloodColor( void ) { return DONT_BLEED; } + void Killed( entvars_t *pevAttacker, int iGib ); + void Activate( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + int Classify( void ) { return CLASS_INSECT; } + int IRelationship( CBaseEntity *pTarget ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + static const char *pAttackSounds[]; + static const char *pAlertSounds[]; + +private: + // UNDONE: Remove unused boid vars, do group behavior + float m_flTurning;// is this boid turning? + BOOL m_fPathBlocked;// TRUE if there is an obstacle ahead + float m_flAccelerate; + float m_obstacle; + float m_top; + float m_bottom; + float m_height; + float m_waterTime; + float m_sideTime; // Timer to randomly check clearance on sides + float m_zTime; + float m_stateTime; + float m_attackSoundTime; + +#if DEBUG_BEAMS + CBeam *m_pb; + CBeam *m_pt; +#endif +}; + + + +LINK_ENTITY_TO_CLASS( monster_leech, CLeech ); + +TYPEDESCRIPTION CLeech::m_SaveData[] = +{ + DEFINE_FIELD( CLeech, m_flTurning, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_fPathBlocked, FIELD_BOOLEAN ), + DEFINE_FIELD( CLeech, m_flAccelerate, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_obstacle, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_top, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_bottom, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_height, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_waterTime, FIELD_TIME ), + DEFINE_FIELD( CLeech, m_sideTime, FIELD_TIME ), + DEFINE_FIELD( CLeech, m_zTime, FIELD_TIME ), + DEFINE_FIELD( CLeech, m_stateTime, FIELD_TIME ), + DEFINE_FIELD( CLeech, m_attackSoundTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CLeech, CBaseMonster ); + + +const char *CLeech::pAttackSounds[] = +{ + "leech/leech_bite1.wav", + "leech/leech_bite2.wav", + "leech/leech_bite3.wav", +}; + +const char *CLeech::pAlertSounds[] = +{ + "leech/leech_alert1.wav", + "leech/leech_alert2.wav", +}; + + +void CLeech::Spawn( void ) +{ + Precache(); + SET_MODEL(ENT(pev), "models/leech.mdl"); + // Just for fun + // SET_MODEL(ENT(pev), "models/icky.mdl"); + +// UTIL_SetSize( pev, g_vecZero, g_vecZero ); + UTIL_SetSize( pev, Vector(-1,-1,0), Vector(1,1,2)); + // Don't push the minz down too much or the water check will fail because this entity is really point-sized + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_FLY; + SetBits(pev->flags, FL_SWIM); + pev->health = gSkillData.leechHealth; + + m_flFieldOfView = -0.5; // 180 degree FOV + m_flDistLook = 750; + MonsterInit(); + SetThink( &CLeech::SwimThink ); + SetUse( NULL ); + SetTouch( NULL ); + pev->view_ofs = g_vecZero; + + m_flTurning = 0; + m_fPathBlocked = FALSE; + SetActivity( ACT_SWIM ); + SetState( MONSTERSTATE_IDLE ); + m_stateTime = gpGlobals->time + RANDOM_FLOAT( 1, 5 ); +} + + +void CLeech::Activate( void ) +{ + RecalculateWaterlevel(); +} + + + +void CLeech::RecalculateWaterlevel( void ) +{ + // Calculate boundaries + Vector vecTest = pev->origin - Vector(0,0,400); + + TraceResult tr; + + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + if ( tr.flFraction != 1.0 ) + m_bottom = tr.vecEndPos.z + 1; + else + m_bottom = vecTest.z; + + m_top = UTIL_WaterLevel( pev->origin, pev->origin.z, pev->origin.z + 400 ) - 1; + + // Chop off 20% of the outside range + float newBottom = m_bottom * 0.8 + m_top * 0.2; + m_top = m_bottom * 0.2 + m_top * 0.8; + m_bottom = newBottom; + m_height = RANDOM_FLOAT( m_bottom, m_top ); + m_waterTime = gpGlobals->time + RANDOM_FLOAT( 5, 7 ); +} + + +void CLeech::SwitchLeechState( void ) +{ + m_stateTime = gpGlobals->time + RANDOM_FLOAT( 3, 6 ); + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + { + m_hEnemy = NULL; + SetState( MONSTERSTATE_IDLE ); + // We may be up against the player, so redo the side checks + m_sideTime = 0; + } + else + { + Look( m_flDistLook ); + CBaseEntity *pEnemy = BestVisibleEnemy(); + if ( pEnemy && pEnemy->pev->waterlevel != 0 ) + { + m_hEnemy = pEnemy; + SetState( MONSTERSTATE_COMBAT ); + m_stateTime = gpGlobals->time + RANDOM_FLOAT( 18, 25 ); + AlertSound(); + } + } +} + + +int CLeech::IRelationship( CBaseEntity *pTarget ) +{ + if ( pTarget->IsPlayer() ) + return R_DL; + return CBaseMonster::IRelationship( pTarget ); +} + + + +void CLeech::AttackSound( void ) +{ + if ( gpGlobals->time > m_attackSoundTime ) + { + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAttackSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1) ], 1.0, ATTN_NORM, 0, PITCH_NORM ); + m_attackSoundTime = gpGlobals->time + 0.5; + } +} + + +void CLeech::AlertSound( void ) +{ + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAlertSounds[ RANDOM_LONG(0,ARRAYSIZE(pAlertSounds)-1) ], 1.0, ATTN_NORM * 0.5, 0, PITCH_NORM ); +} + + +void CLeech::Precache( void ) +{ + int i; + + //PRECACHE_MODEL("models/icky.mdl"); + PRECACHE_MODEL("models/leech.mdl"); + + for ( i = 0; i < ARRAYSIZE( pAttackSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackSounds[i]); + for ( i = 0; i < ARRAYSIZE( pAlertSounds ); i++ ) + PRECACHE_SOUND((char *)pAlertSounds[i]); +} + + +int CLeech::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + pev->velocity = g_vecZero; + + // Nudge the leech away from the damage + if ( pevInflictor ) + { + pev->velocity = (pev->origin - pevInflictor->origin).Normalize() * 25; + } + + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + + +void CLeech::HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case LEECH_AE_ATTACK: + AttackSound(); + CBaseEntity *pEnemy; + + pEnemy = m_hEnemy; + if ( pEnemy != NULL ) + { + Vector dir, face; + + UTIL_MakeVectorsPrivate( pev->angles, face, NULL, NULL ); + face.z = 0; + dir = (pEnemy->pev->origin - pev->origin); + dir.z = 0; + dir = dir.Normalize(); + face = face.Normalize(); + + + if ( DotProduct(dir, face) > 0.9 ) // Only take damage if the leech is facing the prey + pEnemy->TakeDamage( pev, pev, gSkillData.leechDmgBite, DMG_SLASH ); + } + m_stateTime -= 2; + break; + + case LEECH_AE_FLOP: + // Play flop sound + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + + +void CLeech::MakeVectors( void ) +{ + Vector tmp = pev->angles; + tmp.x = -tmp.x; + UTIL_MakeVectors ( tmp ); +} + + +// +// ObstacleDistance - returns normalized distance to obstacle +// +float CLeech::ObstacleDistance( CBaseEntity *pTarget ) +{ + TraceResult tr; + Vector vecTest; + + // use VELOCITY, not angles, not all boids point the direction they are flying + //Vector vecDir = UTIL_VecToAngles( pev->velocity ); + MakeVectors(); + + // check for obstacle ahead + vecTest = pev->origin + gpGlobals->v_forward * LEECH_CHECK_DIST; + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + + if ( tr.fStartSolid ) + { + pev->speed = -LEECH_SWIM_SPEED * 0.5; +// ALERT( at_console, "Stuck from (%f %f %f) to (%f %f %f)\n", pev->oldorigin.x, pev->oldorigin.y, pev->oldorigin.z, pev->origin.x, pev->origin.y, pev->origin.z ); +// UTIL_SetOrigin( pev, pev->oldorigin ); + } + + if ( tr.flFraction != 1.0 ) + { + if ( (pTarget == NULL || tr.pHit != pTarget->edict()) ) + { + return tr.flFraction; + } + else + { + if ( fabs(m_height - pev->origin.z) > 10 ) + return tr.flFraction; + } + } + + if ( m_sideTime < gpGlobals->time ) + { + // extra wide checks + vecTest = pev->origin + gpGlobals->v_right * LEECH_SIZEX * 2 + gpGlobals->v_forward * LEECH_CHECK_DIST; + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + if (tr.flFraction != 1.0) + return tr.flFraction; + + vecTest = pev->origin - gpGlobals->v_right * LEECH_SIZEX * 2 + gpGlobals->v_forward * LEECH_CHECK_DIST; + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + if (tr.flFraction != 1.0) + return tr.flFraction; + + // Didn't hit either side, so stop testing for another 0.5 - 1 seconds + m_sideTime = gpGlobals->time + RANDOM_FLOAT(0.5,1); + } + return 1.0; +} + + +void CLeech::DeadThink( void ) +{ + if ( m_fSequenceFinished ) + { + if ( m_Activity == ACT_DIEFORWARD ) + { + SetThink( NULL ); + StopAnimation(); + return; + } + else if ( pev->flags & FL_ONGROUND ) + { + pev->solid = SOLID_NOT; + SetActivity(ACT_DIEFORWARD); + } + } + StudioFrameAdvance(); + pev->nextthink = gpGlobals->time + 0.1; + + // Apply damage velocity, but keep out of the walls + if ( pev->velocity.x != 0 || pev->velocity.y != 0 ) + { + TraceResult tr; + + // Look 0.5 seconds ahead + UTIL_TraceLine(pev->origin, pev->origin + pev->velocity * 0.5, missile, edict(), &tr); + if (tr.flFraction != 1.0) + { + pev->velocity.x = 0; + pev->velocity.y = 0; + } + } +} + + + +void CLeech::UpdateMotion( void ) +{ + float flapspeed = (pev->speed - m_flAccelerate) / LEECH_ACCELERATE; + m_flAccelerate = m_flAccelerate * 0.8 + pev->speed * 0.2; + + if (flapspeed < 0) + flapspeed = -flapspeed; + flapspeed += 1.0; + if (flapspeed < 0.5) + flapspeed = 0.5; + if (flapspeed > 1.9) + flapspeed = 1.9; + + pev->framerate = flapspeed; + + if ( !m_fPathBlocked ) + pev->avelocity.y = pev->ideal_yaw; + else + pev->avelocity.y = pev->ideal_yaw * m_obstacle; + + if ( pev->avelocity.y > 150 ) + m_IdealActivity = ACT_TURN_LEFT; + else if ( pev->avelocity.y < -150 ) + m_IdealActivity = ACT_TURN_RIGHT; + else + m_IdealActivity = ACT_SWIM; + + // lean + float targetPitch, delta; + delta = m_height - pev->origin.z; + + if ( delta < -10 ) + targetPitch = -30; + else if ( delta > 10 ) + targetPitch = 30; + else + targetPitch = 0; + + pev->angles.x = UTIL_Approach( targetPitch, pev->angles.x, 60 * LEECH_FRAMETIME ); + + // bank + pev->avelocity.z = - (pev->angles.z + (pev->avelocity.y * 0.25)); + + if ( m_MonsterState == MONSTERSTATE_COMBAT && HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) + m_IdealActivity = ACT_MELEE_ATTACK1; + + // Out of water check + if ( !pev->waterlevel ) + { + pev->movetype = MOVETYPE_TOSS; + m_IdealActivity = ACT_TWITCH; + pev->velocity = g_vecZero; + + // Animation will intersect the floor if either of these is non-zero + pev->angles.z = 0; + pev->angles.x = 0; + + if ( pev->framerate < 1.0 ) + pev->framerate = 1.0; + } + else if ( pev->movetype == MOVETYPE_TOSS ) + { + pev->movetype = MOVETYPE_FLY; + pev->flags &= ~FL_ONGROUND; + RecalculateWaterlevel(); + m_waterTime = gpGlobals->time + 2; // Recalc again soon, water may be rising + } + + if ( m_Activity != m_IdealActivity ) + { + SetActivity ( m_IdealActivity ); + } + float flInterval = StudioFrameAdvance(); + DispatchAnimEvents ( flInterval ); + +#if DEBUG_BEAMS + if ( !m_pb ) + m_pb = CBeam::BeamCreate( "sprites/laserbeam.spr", 5 ); + if ( !m_pt ) + m_pt = CBeam::BeamCreate( "sprites/laserbeam.spr", 5 ); + m_pb->PointsInit( pev->origin, pev->origin + gpGlobals->v_forward * LEECH_CHECK_DIST ); + m_pt->PointsInit( pev->origin, pev->origin - gpGlobals->v_right * (pev->avelocity.y*0.25) ); + if ( m_fPathBlocked ) + { + float color = m_obstacle * 30; + if ( m_obstacle == 1.0 ) + color = 0; + if ( color > 255 ) + color = 255; + m_pb->SetColor( 255, (int)color, (int)color ); + } + else + m_pb->SetColor( 255, 255, 0 ); + m_pt->SetColor( 0, 0, 255 ); +#endif +} + + +void CLeech::SwimThink( void ) +{ + TraceResult tr; + float flLeftSide; + float flRightSide; + float targetSpeed; + float targetYaw = 0; + CBaseEntity *pTarget; + + if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) + { + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(1,1.5); + pev->velocity = g_vecZero; + return; + } + else + pev->nextthink = gpGlobals->time + 0.1; + + targetSpeed = LEECH_SWIM_SPEED; + + if ( m_waterTime < gpGlobals->time ) + RecalculateWaterlevel(); + + if ( m_stateTime < gpGlobals->time ) + SwitchLeechState(); + + ClearConditions( bits_COND_CAN_MELEE_ATTACK1 ); + switch( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + pTarget = m_hEnemy; + if ( !pTarget ) + SwitchLeechState(); + else + { + // Chase the enemy's eyes + m_height = pTarget->pev->origin.z + pTarget->pev->view_ofs.z - 5; + // Clip to viable water area + if ( m_height < m_bottom ) + m_height = m_bottom; + else if ( m_height > m_top ) + m_height = m_top; + Vector location = pTarget->pev->origin - pev->origin; + location.z += (pTarget->pev->view_ofs.z); + if ( location.Length() < 40 ) + SetConditions( bits_COND_CAN_MELEE_ATTACK1 ); + // Turn towards target ent + targetYaw = UTIL_VecToYaw( location ); + + targetYaw = UTIL_AngleDiff( targetYaw, UTIL_AngleMod( pev->angles.y ) ); + + if ( targetYaw < (-LEECH_TURN_RATE*0.75) ) + targetYaw = (-LEECH_TURN_RATE*0.75); + else if ( targetYaw > (LEECH_TURN_RATE*0.75) ) + targetYaw = (LEECH_TURN_RATE*0.75); + else + targetSpeed *= 2; + } + + break; + + default: + if ( m_zTime < gpGlobals->time ) + { + float newHeight = RANDOM_FLOAT( m_bottom, m_top ); + m_height = 0.5 * m_height + 0.5 * newHeight; + m_zTime = gpGlobals->time + RANDOM_FLOAT( 1, 4 ); + } + if ( RANDOM_LONG( 0, 100 ) < 10 ) + targetYaw = RANDOM_LONG( -30, 30 ); + pTarget = NULL; + // oldorigin test + if ( (pev->origin - pev->oldorigin).Length() < 1 ) + { + // If leech didn't move, there must be something blocking it, so try to turn + m_sideTime = 0; + } + + break; + } + + m_obstacle = ObstacleDistance( pTarget ); + pev->oldorigin = pev->origin; + if ( m_obstacle < 0.1 ) + m_obstacle = 0.1; + + // is the way ahead clear? + if ( m_obstacle == 1.0 ) + { + // if the leech is turning, stop the trend. + if ( m_flTurning != 0 ) + { + m_flTurning = 0; + } + + m_fPathBlocked = FALSE; + pev->speed = UTIL_Approach( targetSpeed, pev->speed, LEECH_SWIM_ACCEL * LEECH_FRAMETIME ); + pev->velocity = gpGlobals->v_forward * pev->speed; + + } + else + { + m_obstacle = 1.0 / m_obstacle; + // IF we get this far in the function, the leader's path is blocked! + m_fPathBlocked = TRUE; + + if ( m_flTurning == 0 )// something in the way and leech is not already turning to avoid + { + Vector vecTest; + // measure clearance on left and right to pick the best dir to turn + vecTest = pev->origin + (gpGlobals->v_right * LEECH_SIZEX) + (gpGlobals->v_forward * LEECH_CHECK_DIST); + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + flRightSide = tr.flFraction; + + vecTest = pev->origin + (gpGlobals->v_right * -LEECH_SIZEX) + (gpGlobals->v_forward * LEECH_CHECK_DIST); + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + flLeftSide = tr.flFraction; + + // turn left, right or random depending on clearance ratio + float delta = (flRightSide - flLeftSide); + if ( delta > 0.1 || (delta > -0.1 && RANDOM_LONG(0,100)<50) ) + m_flTurning = -LEECH_TURN_RATE; + else + m_flTurning = LEECH_TURN_RATE; + } + pev->speed = UTIL_Approach( -(LEECH_SWIM_SPEED*0.5), pev->speed, LEECH_SWIM_DECEL * LEECH_FRAMETIME * m_obstacle ); + pev->velocity = gpGlobals->v_forward * pev->speed; + } + pev->ideal_yaw = m_flTurning + targetYaw; + UpdateMotion(); +} + + +void CLeech::Killed(entvars_t *pevAttacker, int iGib) +{ + Vector vecSplatDir; + TraceResult tr; + + //ALERT(at_aiconsole, "Leech: killed\n"); + // tell owner ( if any ) that we're dead.This is mostly for MonsterMaker functionality. + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + if (pOwner) + pOwner->DeathNotice(pev); + + // When we hit the ground, play the "death_end" activity + if ( pev->waterlevel ) + { + pev->angles.z = 0; + pev->angles.x = 0; + pev->origin.z += 1; + pev->avelocity = g_vecZero; + if ( RANDOM_LONG( 0, 99 ) < 70 ) + pev->avelocity.y = RANDOM_LONG( -720, 720 ); + + pev->gravity = 0.02; + ClearBits(pev->flags, FL_ONGROUND); + SetActivity( ACT_DIESIMPLE ); + } + else + SetActivity( ACT_DIEFORWARD ); + + pev->movetype = MOVETYPE_TOSS; + pev->takedamage = DAMAGE_NO; + SetThink( &CLeech::DeadThink ); +} + + diff --git a/dlls/lights.cpp b/dlls/lights.cpp new file mode 100644 index 0000000..43b60b4 --- /dev/null +++ b/dlls/lights.cpp @@ -0,0 +1,199 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== lights.cpp ======================================================== + + spawn and think functions for editor-placed lights + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" + + + +class CLight : public CPointEntity +{ +public: + virtual void KeyValue( KeyValueData* pkvd ); + virtual void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_iStyle; + int m_iszPattern; +}; +LINK_ENTITY_TO_CLASS( light, CLight ); + +TYPEDESCRIPTION CLight::m_SaveData[] = +{ + DEFINE_FIELD( CLight, m_iStyle, FIELD_INTEGER ), + DEFINE_FIELD( CLight, m_iszPattern, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CLight, CPointEntity ); + + +// +// Cache user-entity-field values until spawn is called. +// +void CLight :: KeyValue( KeyValueData* pkvd) +{ + if (FStrEq(pkvd->szKeyName, "style")) + { + m_iStyle = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitch")) + { + pev->angles.x = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pattern")) + { + m_iszPattern = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CPointEntity::KeyValue( pkvd ); + } +} + +/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) LIGHT_START_OFF +Non-displayed light. +Default light value is 300 +Default style is 0 +If targeted, it will toggle between on or off. +*/ + +void CLight :: Spawn( void ) +{ + if (FStringNull(pev->targetname)) + { // inert light + REMOVE_ENTITY(ENT(pev)); + return; + } + + if (m_iStyle >= 32) + { +// CHANGE_METHOD(ENT(pev), em_use, light_use); + if (FBitSet(pev->spawnflags, SF_LIGHT_START_OFF)) + LIGHT_STYLE(m_iStyle, "a"); + else if (m_iszPattern) + LIGHT_STYLE(m_iStyle, (char *)STRING( m_iszPattern )); + else + LIGHT_STYLE(m_iStyle, "m"); + } +} + + +void CLight :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (m_iStyle >= 32) + { + if ( !ShouldToggle( useType, !FBitSet(pev->spawnflags, SF_LIGHT_START_OFF) ) ) + return; + + if (FBitSet(pev->spawnflags, SF_LIGHT_START_OFF)) + { + if (m_iszPattern) + LIGHT_STYLE(m_iStyle, (char *)STRING( m_iszPattern )); + else + LIGHT_STYLE(m_iStyle, "m"); + ClearBits(pev->spawnflags, SF_LIGHT_START_OFF); + } + else + { + LIGHT_STYLE(m_iStyle, "a"); + SetBits(pev->spawnflags, SF_LIGHT_START_OFF); + } + } +} + +// +// shut up spawn functions for new spotlights +// +LINK_ENTITY_TO_CLASS( light_spot, CLight ); + + +class CEnvLight : public CLight +{ +public: + void KeyValue( KeyValueData* pkvd ); + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( light_environment, CEnvLight ); + +void CEnvLight::KeyValue( KeyValueData* pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "_light")) + { + int r, g, b, v, j; + char szColor[64]; + j = sscanf( pkvd->szValue, "%d %d %d %d\n", &r, &g, &b, &v ); + if (j == 1) + { + g = b = r; + } + else if (j == 4) + { + r = r * (v / 255.0); + g = g * (v / 255.0); + b = b * (v / 255.0); + } + + // simulate qrad direct, ambient,and gamma adjustments, as well as engine scaling + r = pow( r / 114.0, 0.6 ) * 264; + g = pow( g / 114.0, 0.6 ) * 264; + b = pow( b / 114.0, 0.6 ) * 264; + + pkvd->fHandled = TRUE; + sprintf( szColor, "%d", r ); + CVAR_SET_STRING( "sv_skycolor_r", szColor ); + sprintf( szColor, "%d", g ); + CVAR_SET_STRING( "sv_skycolor_g", szColor ); + sprintf( szColor, "%d", b ); + CVAR_SET_STRING( "sv_skycolor_b", szColor ); + } + else + { + CLight::KeyValue( pkvd ); + } +} + + +void CEnvLight :: Spawn( void ) +{ + char szVector[64]; + UTIL_MakeAimVectors( pev->angles ); + + sprintf( szVector, "%f", gpGlobals->v_forward.x ); + CVAR_SET_STRING( "sv_skyvec_x", szVector ); + sprintf( szVector, "%f", gpGlobals->v_forward.y ); + CVAR_SET_STRING( "sv_skyvec_y", szVector ); + sprintf( szVector, "%f", gpGlobals->v_forward.z ); + CVAR_SET_STRING( "sv_skyvec_z", szVector ); + + CLight::Spawn( ); +} diff --git a/dlls/maprules.cpp b/dlls/maprules.cpp new file mode 100644 index 0000000..c57905a --- /dev/null +++ b/dlls/maprules.cpp @@ -0,0 +1,918 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +// ------------------------------------------- +// +// maprules.cpp +// +// This module contains entities for implementing/changing game +// rules dynamically within each map (.BSP) +// +// ------------------------------------------- + +#include "extdll.h" +#include "eiface.h" +#include "util.h" +#include "gamerules.h" +#include "maprules.h" +#include "cbase.h" +#include "player.h" + +class CRuleEntity : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void SetMaster( int iszMaster ) { m_iszMaster = iszMaster; } + +protected: + BOOL CanFireForActivator( CBaseEntity *pActivator ); + +private: + string_t m_iszMaster; +}; + +TYPEDESCRIPTION CRuleEntity::m_SaveData[] = +{ + DEFINE_FIELD( CRuleEntity, m_iszMaster, FIELD_STRING), +}; + +IMPLEMENT_SAVERESTORE( CRuleEntity, CBaseEntity ); + + +void CRuleEntity::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = EF_NODRAW; +} + + +void CRuleEntity::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "master")) + { + SetMaster( ALLOC_STRING(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +BOOL CRuleEntity::CanFireForActivator( CBaseEntity *pActivator ) +{ + if ( m_iszMaster ) + { + if ( UTIL_IsMasterTriggered( m_iszMaster, pActivator ) ) + return TRUE; + else + return FALSE; + } + + return TRUE; +} + +// +// CRulePointEntity -- base class for all rule "point" entities (not brushes) +// +class CRulePointEntity : public CRuleEntity +{ +public: + void Spawn( void ); +}; + +void CRulePointEntity::Spawn( void ) +{ + CRuleEntity::Spawn(); + pev->frame = 0; + pev->model = 0; +} + +// +// CRuleBrushEntity -- base class for all rule "brush" entities (not brushes) +// Default behavior is to set up like a trigger, invisible, but keep the model for volume testing +// +class CRuleBrushEntity : public CRuleEntity +{ +public: + void Spawn( void ); + +private: +}; + +void CRuleBrushEntity::Spawn( void ) +{ + SET_MODEL( edict(), STRING(pev->model) ); + CRuleEntity::Spawn(); +} + + +// CGameScore / game_score -- award points to player / team +// Points +/- total +// Flag: Allow negative scores SF_SCORE_NEGATIVE +// Flag: Award points to team in teamplay SF_SCORE_TEAM + +#define SF_SCORE_NEGATIVE 0x0001 +#define SF_SCORE_TEAM 0x0002 + +class CGameScore : public CRulePointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline int Points( void ) { return pev->frags; } + inline BOOL AllowNegativeScore( void ) { return pev->spawnflags & SF_SCORE_NEGATIVE; } + inline BOOL AwardToTeam( void ) { return pev->spawnflags & SF_SCORE_TEAM; } + + inline void SetPoints( int points ) { pev->frags = points; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_score, CGameScore ); + + +void CGameScore::Spawn( void ) +{ + CRulePointEntity::Spawn(); +} + + +void CGameScore::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "points")) + { + SetPoints( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CRulePointEntity::KeyValue( pkvd ); +} + + + +void CGameScore::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + // Only players can use this + if ( pActivator->IsPlayer() ) + { + if ( AwardToTeam() ) + { + pActivator->AddPointsToTeam( Points(), AllowNegativeScore() ); + } + else + { + pActivator->AddPoints( Points(), AllowNegativeScore() ); + } + } +} + + +// CGameEnd / game_end -- Ends the game in MP + +class CGameEnd : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +private: +}; + +LINK_ENTITY_TO_CLASS( game_end, CGameEnd ); + + +void CGameEnd::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + g_pGameRules->EndMultiplayerGame(); +} + + +// +// CGameText / game_text -- NON-Localized HUD Message (use env_message to display a titles.txt message) +// Flag: All players SF_ENVTEXT_ALLPLAYERS +// + + +#define SF_ENVTEXT_ALLPLAYERS 0x0001 + + +class CGameText : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + inline BOOL MessageToAll( void ) { return (pev->spawnflags & SF_ENVTEXT_ALLPLAYERS); } + inline void MessageSet( const char *pMessage ) { pev->message = ALLOC_STRING(pMessage); } + inline const char *MessageGet( void ) { return STRING(pev->message); } + +private: + + hudtextparms_t m_textParms; +}; + +LINK_ENTITY_TO_CLASS( game_text, CGameText ); + +// Save parms as a block. Will break save/restore if the structure changes, but this entity didn't ship with Half-Life, so +// it can't impact saved Half-Life games. +TYPEDESCRIPTION CGameText::m_SaveData[] = +{ + DEFINE_ARRAY( CGameText, m_textParms, FIELD_CHARACTER, sizeof(hudtextparms_t) ), +}; + +IMPLEMENT_SAVERESTORE( CGameText, CRulePointEntity ); + + +void CGameText::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "channel")) + { + m_textParms.channel = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "x")) + { + m_textParms.x = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "y")) + { + m_textParms.y = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "effect")) + { + m_textParms.effect = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "color")) + { + int color[4]; + UTIL_StringToIntArray( color, 4, pkvd->szValue ); + m_textParms.r1 = color[0]; + m_textParms.g1 = color[1]; + m_textParms.b1 = color[2]; + m_textParms.a1 = color[3]; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "color2")) + { + int color[4]; + UTIL_StringToIntArray( color, 4, pkvd->szValue ); + m_textParms.r2 = color[0]; + m_textParms.g2 = color[1]; + m_textParms.b2 = color[2]; + m_textParms.a2 = color[3]; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "fadein")) + { + m_textParms.fadeinTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "fadeout")) + { + m_textParms.fadeoutTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "holdtime")) + { + m_textParms.holdTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "fxtime")) + { + m_textParms.fxTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CRulePointEntity::KeyValue( pkvd ); +} + + +void CGameText::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( MessageToAll() ) + { + UTIL_HudMessageAll( m_textParms, MessageGet() ); + } + else + { + if ( pActivator->IsNetClient() ) + { + UTIL_HudMessage( pActivator, m_textParms, MessageGet() ); + } + } +} + + +// +// CGameTeamMaster / game_team_master -- "Masters" like multisource, but based on the team of the activator +// Only allows mastered entity to fire if the team matches my team +// +// team index (pulled from server team list "mp_teamlist" +// Flag: Remove on Fire +// Flag: Any team until set? -- Any team can use this until the team is set (otherwise no teams can use it) +// + +#define SF_TEAMMASTER_FIREONCE 0x0001 +#define SF_TEAMMASTER_ANYTEAM 0x0002 + +class CGameTeamMaster : public CRulePointEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int ObjectCaps( void ) { return CRulePointEntity:: ObjectCaps() | FCAP_MASTER; } + + BOOL IsTriggered( CBaseEntity *pActivator ); + const char *TeamID( void ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_TEAMMASTER_FIREONCE) ? TRUE : FALSE; } + inline BOOL AnyTeam( void ) { return (pev->spawnflags & SF_TEAMMASTER_ANYTEAM) ? TRUE : FALSE; } + +private: + BOOL TeamMatch( CBaseEntity *pActivator ); + + int m_teamIndex; + USE_TYPE triggerType; +}; + +LINK_ENTITY_TO_CLASS( game_team_master, CGameTeamMaster ); + +void CGameTeamMaster::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "teamindex")) + { + m_teamIndex = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "triggerstate")) + { + int type = atoi( pkvd->szValue ); + switch( type ) + { + case 0: + triggerType = USE_OFF; + break; + case 2: + triggerType = USE_TOGGLE; + break; + default: + triggerType = USE_ON; + break; + } + pkvd->fHandled = TRUE; + } + else + CRulePointEntity::KeyValue( pkvd ); +} + + +void CGameTeamMaster::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( useType == USE_SET ) + { + if ( value < 0 ) + { + m_teamIndex = -1; + } + else + { + m_teamIndex = g_pGameRules->GetTeamIndex( pActivator->TeamID() ); + } + return; + } + + if ( TeamMatch( pActivator ) ) + { + SUB_UseTargets( pActivator, triggerType, value ); + if ( RemoveOnFire() ) + UTIL_Remove( this ); + } +} + + +BOOL CGameTeamMaster::IsTriggered( CBaseEntity *pActivator ) +{ + return TeamMatch( pActivator ); +} + + +const char *CGameTeamMaster::TeamID( void ) +{ + if ( m_teamIndex < 0 ) // Currently set to "no team" + return ""; + + return g_pGameRules->GetIndexedTeamName( m_teamIndex ); // UNDONE: Fill this in with the team from the "teamlist" +} + + +BOOL CGameTeamMaster::TeamMatch( CBaseEntity *pActivator ) +{ + if ( m_teamIndex < 0 && AnyTeam() ) + return TRUE; + + if ( !pActivator ) + return FALSE; + + return UTIL_TeamsMatch( pActivator->TeamID(), TeamID() ); +} + + +// +// CGameTeamSet / game_team_set -- Changes the team of the entity it targets to the activator's team +// Flag: Fire once +// Flag: Clear team -- Sets the team to "NONE" instead of activator + +#define SF_TEAMSET_FIREONCE 0x0001 +#define SF_TEAMSET_CLEARTEAM 0x0002 + +class CGameTeamSet : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_TEAMSET_FIREONCE) ? TRUE : FALSE; } + inline BOOL ShouldClearTeam( void ) { return (pev->spawnflags & SF_TEAMSET_CLEARTEAM) ? TRUE : FALSE; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_team_set, CGameTeamSet ); + + +void CGameTeamSet::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( ShouldClearTeam() ) + { + SUB_UseTargets( pActivator, USE_SET, -1 ); + } + else + { + SUB_UseTargets( pActivator, USE_SET, 0 ); + } + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + +// +// CGamePlayerZone / game_player_zone -- players in the zone fire my target when I'm fired +// +// Needs master? +class CGamePlayerZone : public CRuleBrushEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + string_t m_iszInTarget; + string_t m_iszOutTarget; + string_t m_iszInCount; + string_t m_iszOutCount; +}; + +LINK_ENTITY_TO_CLASS( game_zone_player, CGamePlayerZone ); +TYPEDESCRIPTION CGamePlayerZone::m_SaveData[] = +{ + DEFINE_FIELD( CGamePlayerZone, m_iszInTarget, FIELD_STRING ), + DEFINE_FIELD( CGamePlayerZone, m_iszOutTarget, FIELD_STRING ), + DEFINE_FIELD( CGamePlayerZone, m_iszInCount, FIELD_STRING ), + DEFINE_FIELD( CGamePlayerZone, m_iszOutCount, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CGamePlayerZone, CRuleBrushEntity ); + +void CGamePlayerZone::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "intarget")) + { + m_iszInTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "outtarget")) + { + m_iszOutTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "incount")) + { + m_iszInCount = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "outcount")) + { + m_iszOutCount = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CRuleBrushEntity::KeyValue( pkvd ); +} + +void CGamePlayerZone::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int playersInCount = 0; + int playersOutCount = 0; + + if ( !CanFireForActivator( pActivator ) ) + return; + + CBaseEntity *pPlayer = NULL; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + { + TraceResult trace; + int hullNumber; + + hullNumber = human_hull; + if ( pPlayer->pev->flags & FL_DUCKING ) + { + hullNumber = head_hull; + } + + UTIL_TraceModel( pPlayer->pev->origin, pPlayer->pev->origin, hullNumber, edict(), &trace ); + + if ( trace.fStartSolid ) + { + playersInCount++; + if ( m_iszInTarget ) + { + FireTargets( STRING(m_iszInTarget), pPlayer, pActivator, useType, value ); + } + } + else + { + playersOutCount++; + if ( m_iszOutTarget ) + { + FireTargets( STRING(m_iszOutTarget), pPlayer, pActivator, useType, value ); + } + } + } + } + + if ( m_iszInCount ) + { + FireTargets( STRING(m_iszInCount), pActivator, this, USE_SET, playersInCount ); + } + + if ( m_iszOutCount ) + { + FireTargets( STRING(m_iszOutCount), pActivator, this, USE_SET, playersOutCount ); + } +} + + + +// +// CGamePlayerHurt / game_player_hurt -- Damages the player who fires it +// Flag: Fire once + +#define SF_PKILL_FIREONCE 0x0001 +class CGamePlayerHurt : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_PKILL_FIREONCE) ? TRUE : FALSE; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_player_hurt, CGamePlayerHurt ); + + +void CGamePlayerHurt::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( pActivator->IsPlayer() ) + { + if ( pev->dmg < 0 ) + pActivator->TakeHealth( -pev->dmg, DMG_GENERIC ); + else + pActivator->TakeDamage( pev, pev, pev->dmg, DMG_GENERIC ); + } + + SUB_UseTargets( pActivator, useType, value ); + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + + +// +// CGameCounter / game_counter -- Counts events and fires target +// Flag: Fire once +// Flag: Reset on Fire + +#define SF_GAMECOUNT_FIREONCE 0x0001 +#define SF_GAMECOUNT_RESET 0x0002 + +class CGameCounter : public CRulePointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_GAMECOUNT_FIREONCE) ? TRUE : FALSE; } + inline BOOL ResetOnFire( void ) { return (pev->spawnflags & SF_GAMECOUNT_RESET) ? TRUE : FALSE; } + + inline void CountUp( void ) { pev->frags++; } + inline void CountDown( void ) { pev->frags--; } + inline void ResetCount( void ) { pev->frags = pev->dmg; } + inline int CountValue( void ) { return pev->frags; } + inline int LimitValue( void ) { return pev->health; } + + inline BOOL HitLimit( void ) { return CountValue() == LimitValue(); } + +private: + + inline void SetCountValue( int value ) { pev->frags = value; } + inline void SetInitialValue( int value ) { pev->dmg = value; } +}; + +LINK_ENTITY_TO_CLASS( game_counter, CGameCounter ); + +void CGameCounter::Spawn( void ) +{ + // Save off the initial count + SetInitialValue( CountValue() ); + CRulePointEntity::Spawn(); +} + + +void CGameCounter::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + switch( useType ) + { + case USE_ON: + case USE_TOGGLE: + CountUp(); + break; + + case USE_OFF: + CountDown(); + break; + + case USE_SET: + SetCountValue( (int)value ); + break; + } + + if ( HitLimit() ) + { + SUB_UseTargets( pActivator, USE_TOGGLE, 0 ); + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } + + if ( ResetOnFire() ) + { + ResetCount(); + } + } +} + + + +// +// CGameCounterSet / game_counter_set -- Sets the counter's value +// Flag: Fire once + +#define SF_GAMECOUNTSET_FIREONCE 0x0001 + +class CGameCounterSet : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_GAMECOUNTSET_FIREONCE) ? TRUE : FALSE; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_counter_set, CGameCounterSet ); + + +void CGameCounterSet::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + SUB_UseTargets( pActivator, USE_SET, pev->frags ); + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + +// +// CGamePlayerEquip / game_playerequip -- Sets the default player equipment +// Flag: USE Only + +#define SF_PLAYEREQUIP_USEONLY 0x0001 +#define MAX_EQUIP 32 + +class CGamePlayerEquip : public CRulePointEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Touch( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + inline BOOL UseOnly( void ) { return (pev->spawnflags & SF_PLAYEREQUIP_USEONLY) ? TRUE : FALSE; } + +private: + + void EquipPlayer( CBaseEntity *pPlayer ); + + string_t m_weaponNames[MAX_EQUIP]; + int m_weaponCount[MAX_EQUIP]; +}; + +LINK_ENTITY_TO_CLASS( game_player_equip, CGamePlayerEquip ); + + +void CGamePlayerEquip::KeyValue( KeyValueData *pkvd ) +{ + CRulePointEntity::KeyValue( pkvd ); + + if ( !pkvd->fHandled ) + { + for ( int i = 0; i < MAX_EQUIP; i++ ) + { + if ( !m_weaponNames[i] ) + { + char tmp[128]; + + UTIL_StripToken( pkvd->szKeyName, tmp ); + + m_weaponNames[i] = ALLOC_STRING(tmp); + m_weaponCount[i] = atoi(pkvd->szValue); + m_weaponCount[i] = max(1,m_weaponCount[i]); + pkvd->fHandled = TRUE; + break; + } + } + } +} + + +void CGamePlayerEquip::Touch( CBaseEntity *pOther ) +{ + if ( !CanFireForActivator( pOther ) ) + return; + + if ( UseOnly() ) + return; + + EquipPlayer( pOther ); +} + +void CGamePlayerEquip::EquipPlayer( CBaseEntity *pEntity ) +{ + CBasePlayer *pPlayer = NULL; + + if ( pEntity->IsPlayer() ) + { + pPlayer = (CBasePlayer *)pEntity; + } + + if ( !pPlayer ) + return; + + for ( int i = 0; i < MAX_EQUIP; i++ ) + { + if ( !m_weaponNames[i] ) + break; + for ( int j = 0; j < m_weaponCount[i]; j++ ) + { + pPlayer->GiveNamedItem( STRING(m_weaponNames[i]) ); + } + } +} + + +void CGamePlayerEquip::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + EquipPlayer( pActivator ); +} + + +// +// CGamePlayerTeam / game_player_team -- Changes the team of the player who fired it +// Flag: Fire once +// Flag: Kill Player +// Flag: Gib Player + +#define SF_PTEAM_FIREONCE 0x0001 +#define SF_PTEAM_KILL 0x0002 +#define SF_PTEAM_GIB 0x0004 + +class CGamePlayerTeam : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +private: + + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_PTEAM_FIREONCE) ? TRUE : FALSE; } + inline BOOL ShouldKillPlayer( void ) { return (pev->spawnflags & SF_PTEAM_KILL) ? TRUE : FALSE; } + inline BOOL ShouldGibPlayer( void ) { return (pev->spawnflags & SF_PTEAM_GIB) ? TRUE : FALSE; } + + const char *TargetTeamName( const char *pszTargetName ); +}; + +LINK_ENTITY_TO_CLASS( game_player_team, CGamePlayerTeam ); + + +const char *CGamePlayerTeam::TargetTeamName( const char *pszTargetName ) +{ + CBaseEntity *pTeamEntity = NULL; + + while ((pTeamEntity = UTIL_FindEntityByTargetname( pTeamEntity, pszTargetName )) != NULL) + { + if ( FClassnameIs( pTeamEntity->pev, "game_team_master" ) ) + return pTeamEntity->TeamID(); + } + + return NULL; +} + + +void CGamePlayerTeam::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( pActivator->IsPlayer() ) + { + const char *pszTargetTeam = TargetTeamName( STRING(pev->target) ); + if ( pszTargetTeam ) + { + CBasePlayer *pPlayer = (CBasePlayer *)pActivator; + g_pGameRules->ChangePlayerTeam( pPlayer, pszTargetTeam, ShouldKillPlayer(), ShouldGibPlayer() ); + } + } + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + diff --git a/dlls/maprules.h b/dlls/maprules.h new file mode 100644 index 0000000..6ce8dfd --- /dev/null +++ b/dlls/maprules.h @@ -0,0 +1,22 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef MAPRULES_H +#define MAPRULES_H + + + +#endif // MAPRULES_H + diff --git a/dlls/monsterevent.h b/dlls/monsterevent.h new file mode 100644 index 0000000..0193b69 --- /dev/null +++ b/dlls/monsterevent.h @@ -0,0 +1,34 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef MONSTEREVENT_H +#define MONSTEREVENT_H + +typedef struct +{ + int event; + char *options; +} MonsterEvent_t; + +#define EVENT_SPECIFIC 0 +#define EVENT_SCRIPTED 1000 +#define EVENT_SHARED 2000 +#define EVENT_CLIENT 5000 + +#define MONSTER_EVENT_BODYDROP_LIGHT 2001 +#define MONSTER_EVENT_BODYDROP_HEAVY 2002 + +#define MONSTER_EVENT_SWISHSOUND 2010 + +#endif // MONSTEREVENT_H diff --git a/dlls/monstermaker.cpp b/dlls/monstermaker.cpp new file mode 100644 index 0000000..8fa7e44 --- /dev/null +++ b/dlls/monstermaker.cpp @@ -0,0 +1,292 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// Monster Maker - this is an entity that creates monsters +// in the game. +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "saverestore.h" + +// Monstermaker spawnflags +#define SF_MONSTERMAKER_START_ON 1 // start active ( if has targetname ) +#define SF_MONSTERMAKER_CYCLIC 4 // drop one monster every time fired. +#define SF_MONSTERMAKER_MONSTERCLIP 8 // Children are blocked by monsterclip + +//========================================================= +// MonsterMaker - this ent creates monsters during the game. +//========================================================= +class CMonsterMaker : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData* pkvd); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT CyclicUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT MakerThink ( void ); + void DeathNotice ( entvars_t *pevChild );// monster maker children use this to tell the monster maker that they have died. + void MakeMonster( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + string_t m_iszMonsterClassname;// classname of the monster(s) that will be created. + + int m_cNumMonsters;// max number of monsters this ent can create + + + int m_cLiveChildren;// how many monsters made by this monster maker that are currently alive + int m_iMaxLiveChildren;// max number of monsters that this maker may have out at one time. + + float m_flGround; // z coord of the ground under me, used to make sure no monsters are under the maker when it drops a new child + + BOOL m_fActive; + BOOL m_fFadeChildren;// should we make the children fadeout? +}; + +LINK_ENTITY_TO_CLASS( monstermaker, CMonsterMaker ); + +TYPEDESCRIPTION CMonsterMaker::m_SaveData[] = +{ + DEFINE_FIELD( CMonsterMaker, m_iszMonsterClassname, FIELD_STRING ), + DEFINE_FIELD( CMonsterMaker, m_cNumMonsters, FIELD_INTEGER ), + DEFINE_FIELD( CMonsterMaker, m_cLiveChildren, FIELD_INTEGER ), + DEFINE_FIELD( CMonsterMaker, m_flGround, FIELD_FLOAT ), + DEFINE_FIELD( CMonsterMaker, m_iMaxLiveChildren, FIELD_INTEGER ), + DEFINE_FIELD( CMonsterMaker, m_fActive, FIELD_BOOLEAN ), + DEFINE_FIELD( CMonsterMaker, m_fFadeChildren, FIELD_BOOLEAN ), +}; + + +IMPLEMENT_SAVERESTORE( CMonsterMaker, CBaseMonster ); + +void CMonsterMaker :: KeyValue( KeyValueData *pkvd ) +{ + + if ( FStrEq(pkvd->szKeyName, "monstercount") ) + { + m_cNumMonsters = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "m_imaxlivechildren") ) + { + m_iMaxLiveChildren = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "monstertype") ) + { + m_iszMonsterClassname = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + + +void CMonsterMaker :: Spawn( ) +{ + pev->solid = SOLID_NOT; + + m_cLiveChildren = 0; + Precache(); + if ( !FStringNull ( pev->targetname ) ) + { + if ( pev->spawnflags & SF_MONSTERMAKER_CYCLIC ) + { + SetUse ( &CMonsterMaker::CyclicUse );// drop one monster each time we fire + } + else + { + SetUse ( &CMonsterMaker::ToggleUse );// so can be turned on/off + } + + if ( FBitSet ( pev->spawnflags, SF_MONSTERMAKER_START_ON ) ) + {// start making monsters as soon as monstermaker spawns + m_fActive = TRUE; + SetThink ( &CMonsterMaker::MakerThink ); + } + else + {// wait to be activated. + m_fActive = FALSE; + SetThink ( &CMonsterMaker::SUB_DoNothing ); + } + } + else + {// no targetname, just start. + pev->nextthink = gpGlobals->time + m_flDelay; + m_fActive = TRUE; + SetThink ( &CMonsterMaker::MakerThink ); + } + + if ( m_cNumMonsters == 1 ) + { + m_fFadeChildren = FALSE; + } + else + { + m_fFadeChildren = TRUE; + } + + m_flGround = 0; +} + +void CMonsterMaker :: Precache( void ) +{ + CBaseMonster::Precache(); + + UTIL_PrecacheOther( STRING( m_iszMonsterClassname ) ); +} + +//========================================================= +// MakeMonster- this is the code that drops the monster +//========================================================= +void CMonsterMaker::MakeMonster( void ) +{ + edict_t *pent; + entvars_t *pevCreate; + + if ( m_iMaxLiveChildren > 0 && m_cLiveChildren >= m_iMaxLiveChildren ) + {// not allowed to make a new one yet. Too many live ones out right now. + return; + } + + if ( !m_flGround ) + { + // set altitude. Now that I'm activated, any breakables, etc should be out from under me. + TraceResult tr; + + UTIL_TraceLine ( pev->origin, pev->origin - Vector ( 0, 0, 2048 ), ignore_monsters, ENT(pev), &tr ); + m_flGround = tr.vecEndPos.z; + } + + Vector mins = pev->origin - Vector( 34, 34, 0 ); + Vector maxs = pev->origin + Vector( 34, 34, 0 ); + maxs.z = pev->origin.z; + mins.z = m_flGround; + + CBaseEntity *pList[2]; + int count = UTIL_EntitiesInBox( pList, 2, mins, maxs, FL_CLIENT|FL_MONSTER ); + if ( count ) + { + // don't build a stack of monsters! + return; + } + + pent = CREATE_NAMED_ENTITY( m_iszMonsterClassname ); + + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in MonsterMaker!\n" ); + return; + } + + // If I have a target, fire! + if ( !FStringNull ( pev->target ) ) + { + // delay already overloaded for this entity, so can't call SUB_UseTargets() + FireTargets( STRING(pev->target), this, this, USE_TOGGLE, 0 ); + } + + pevCreate = VARS( pent ); + pevCreate->origin = pev->origin; + pevCreate->angles = pev->angles; + SetBits( pevCreate->spawnflags, SF_MONSTER_FALL_TO_GROUND ); + + // Children hit monsterclip brushes + if ( pev->spawnflags & SF_MONSTERMAKER_MONSTERCLIP ) + SetBits( pevCreate->spawnflags, SF_MONSTER_HITMONSTERCLIP ); + + DispatchSpawn( ENT( pevCreate ) ); + pevCreate->owner = edict(); + + if ( !FStringNull( pev->netname ) ) + { + // if I have a netname (overloaded), give the child monster that name as a targetname + pevCreate->targetname = pev->netname; + } + + m_cLiveChildren++;// count this monster + m_cNumMonsters--; + + if ( m_cNumMonsters == 0 ) + { + // Disable this forever. Don't kill it because it still gets death notices + SetThink( NULL ); + SetUse( NULL ); + } +} + +//========================================================= +// CyclicUse - drops one monster from the monstermaker +// each time we call this. +//========================================================= +void CMonsterMaker::CyclicUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + MakeMonster(); +} + +//========================================================= +// ToggleUse - activates/deactivates the monster maker +//========================================================= +void CMonsterMaker :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_fActive ) ) + return; + + if ( m_fActive ) + { + m_fActive = FALSE; + SetThink ( NULL ); + } + else + { + m_fActive = TRUE; + SetThink ( &CMonsterMaker::MakerThink ); + } + + pev->nextthink = gpGlobals->time; +} + +//========================================================= +// MakerThink - creates a new monster every so often +//========================================================= +void CMonsterMaker :: MakerThink ( void ) +{ + pev->nextthink = gpGlobals->time + m_flDelay; + + MakeMonster(); +} + + +//========================================================= +//========================================================= +void CMonsterMaker :: DeathNotice ( entvars_t *pevChild ) +{ + // ok, we've gotten the deathnotice from our child, now clear out its owner if we don't want it to fade. + m_cLiveChildren--; + + if ( !m_fFadeChildren ) + { + pevChild->owner = NULL; + } +} + + diff --git a/dlls/monsters.cpp b/dlls/monsters.cpp new file mode 100644 index 0000000..7395c45 --- /dev/null +++ b/dlls/monsters.cpp @@ -0,0 +1,3448 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +/* + +===== monsters.cpp ======================================================== + + Monster-related utility code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "nodes.h" +#include "monsters.h" +#include "animation.h" +#include "saverestore.h" +#include "weapons.h" +#include "scripted.h" +#include "squadmonster.h" +#include "decals.h" +#include "soundent.h" +#include "gamerules.h" + +#define MONSTER_CUT_CORNER_DIST 8 // 8 means the monster's bounding box is contained without the box of the node in WC + + +Vector VecBModelOrigin( entvars_t* pevBModel ); + +extern DLL_GLOBAL BOOL g_fDrawLines; +extern DLL_GLOBAL short g_sModelIndexLaser;// holds the index for the laser beam +extern DLL_GLOBAL short g_sModelIndexLaserDot;// holds the index for the laser beam dot + +extern CGraph WorldGraph;// the world node graph + + + +// Global Savedata for monster +// UNDONE: Save schedule data? Can this be done? We may +// lose our enemy pointer or other data (goal ent, target, etc) +// that make the current schedule invalid, perhaps it's best +// to just pick a new one when we start up again. +TYPEDESCRIPTION CBaseMonster::m_SaveData[] = +{ + DEFINE_FIELD( CBaseMonster, m_hEnemy, FIELD_EHANDLE ), + DEFINE_FIELD( CBaseMonster, m_hTargetEnt, FIELD_EHANDLE ), + DEFINE_ARRAY( CBaseMonster, m_hOldEnemy, FIELD_EHANDLE, MAX_OLD_ENEMIES ), + DEFINE_ARRAY( CBaseMonster, m_vecOldEnemy, FIELD_POSITION_VECTOR, MAX_OLD_ENEMIES ), + DEFINE_FIELD( CBaseMonster, m_flFieldOfView, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flWaitFinished, FIELD_TIME ), + DEFINE_FIELD( CBaseMonster, m_flMoveWaitFinished, FIELD_TIME ), + + DEFINE_FIELD( CBaseMonster, m_Activity, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_IdealActivity, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_LastHitGroup, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_MonsterState, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_IdealMonsterState, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_iTaskStatus, FIELD_INTEGER ), + + //Schedule_t *m_pSchedule; + + DEFINE_FIELD( CBaseMonster, m_iScheduleIndex, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_afConditions, FIELD_INTEGER ), + //WayPoint_t m_Route[ ROUTE_SIZE ]; +// DEFINE_FIELD( CBaseMonster, m_movementGoal, FIELD_INTEGER ), +// DEFINE_FIELD( CBaseMonster, m_iRouteIndex, FIELD_INTEGER ), +// DEFINE_FIELD( CBaseMonster, m_moveWaitTime, FIELD_FLOAT ), + + DEFINE_FIELD( CBaseMonster, m_vecMoveGoal, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseMonster, m_movementActivity, FIELD_INTEGER ), + + // int m_iAudibleList; // first index of a linked list of sounds that the monster can hear. +// DEFINE_FIELD( CBaseMonster, m_afSoundTypes, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_vecLastPosition, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseMonster, m_iHintNode, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_afMemory, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_iMaxHealth, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseMonster, m_vecEnemyLKP, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseMonster, m_cAmmoLoaded, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_afCapability, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseMonster, m_flNextAttack, FIELD_TIME ), + DEFINE_FIELD( CBaseMonster, m_bitsDamageType, FIELD_INTEGER ), + DEFINE_ARRAY( CBaseMonster, m_rgbTimeBasedDamage, FIELD_CHARACTER, CDMG_TIMEBASED ), + DEFINE_FIELD( CBaseMonster, m_bloodColor, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_failSchedule, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseMonster, m_flHungryTime, FIELD_TIME ), + DEFINE_FIELD( CBaseMonster, m_flDistTooFar, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flDistLook, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_iTriggerCondition, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_iszTriggerTarget, FIELD_STRING ), + + DEFINE_FIELD( CBaseMonster, m_HackedGunPos, FIELD_VECTOR ), + + DEFINE_FIELD( CBaseMonster, m_scriptState, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_pCine, FIELD_CLASSPTR ), +}; + +//IMPLEMENT_SAVERESTORE( CBaseMonster, CBaseToggle ); +int CBaseMonster::Save( CSave &save ) +{ + if ( !CBaseToggle::Save(save) ) + return 0; + return save.WriteFields( "CBaseMonster", this, m_SaveData, ARRAYSIZE(m_SaveData) ); +} + +int CBaseMonster::Restore( CRestore &restore ) +{ + if ( !CBaseToggle::Restore(restore) ) + return 0; + int status = restore.ReadFields( "CBaseMonster", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + + // We don't save/restore routes yet + RouteClear(); + + // We don't save/restore schedules yet + m_pSchedule = NULL; + m_iTaskStatus = TASKSTATUS_NEW; + + // Reset animation + m_Activity = ACT_RESET; + + // If we don't have an enemy, clear conditions like see enemy, etc. + if ( m_hEnemy == NULL ) + m_afConditions = 0; + + return status; +} + + +//========================================================= +// Eat - makes a monster full for a little while. +//========================================================= +void CBaseMonster :: Eat ( float flFullDuration ) +{ + m_flHungryTime = gpGlobals->time + flFullDuration; +} + +//========================================================= +// FShouldEat - returns true if a monster is hungry. +//========================================================= +BOOL CBaseMonster :: FShouldEat ( void ) +{ + if ( m_flHungryTime > gpGlobals->time ) + { + return FALSE; + } + + return TRUE; +} + +//========================================================= +// BarnacleVictimBitten - called +// by Barnacle victims when the barnacle pulls their head +// into its mouth +//========================================================= +void CBaseMonster :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) +{ + Schedule_t *pNewSchedule; + + pNewSchedule = GetScheduleOfType( SCHED_BARNACLE_VICTIM_CHOMP ); + + if ( pNewSchedule ) + { + ChangeSchedule( pNewSchedule ); + } +} + +//========================================================= +// BarnacleVictimReleased - called by barnacle victims when +// the host barnacle is killed. +//========================================================= +void CBaseMonster :: BarnacleVictimReleased ( void ) +{ + m_IdealMonsterState = MONSTERSTATE_IDLE; + + pev->velocity = g_vecZero; + pev->movetype = MOVETYPE_STEP; +} + +//========================================================= +// Listen - monsters dig through the active sound list for +// any sounds that may interest them. (smells, too!) +//========================================================= +void CBaseMonster :: Listen ( void ) +{ + int iSound; + int iMySounds; + float hearingSensitivity; + CSound *pCurrentSound; + + m_iAudibleList = SOUNDLIST_EMPTY; + ClearConditions(bits_COND_HEAR_SOUND | bits_COND_SMELL | bits_COND_SMELL_FOOD); + m_afSoundTypes = 0; + + iMySounds = ISoundMask(); + + if ( m_pSchedule ) + { + //!!!WATCH THIS SPOT IF YOU ARE HAVING SOUND RELATED BUGS! + // Make sure your schedule AND personal sound masks agree! + iMySounds &= m_pSchedule->iSoundMask; + } + + iSound = CSoundEnt::ActiveList(); + + // UNDONE: Clear these here? + ClearConditions( bits_COND_HEAR_SOUND | bits_COND_SMELL_FOOD | bits_COND_SMELL ); + hearingSensitivity = HearingSensitivity( ); + + while ( iSound != SOUNDLIST_EMPTY ) + { + pCurrentSound = CSoundEnt::SoundPointerForIndex( iSound ); + + if ( pCurrentSound && + ( pCurrentSound->m_iType & iMySounds ) && + ( pCurrentSound->m_vecOrigin - EarPosition() ).Length() <= pCurrentSound->m_iVolume * hearingSensitivity ) + + //if ( ( g_pSoundEnt->m_SoundPool[ iSound ].m_iType & iMySounds ) && ( g_pSoundEnt->m_SoundPool[ iSound ].m_vecOrigin - EarPosition()).Length () <= g_pSoundEnt->m_SoundPool[ iSound ].m_iVolume * hearingSensitivity ) + { + // the monster cares about this sound, and it's close enough to hear. + //g_pSoundEnt->m_SoundPool[ iSound ].m_iNextAudible = m_iAudibleList; + pCurrentSound->m_iNextAudible = m_iAudibleList; + + if ( pCurrentSound->FIsSound() ) + { + // this is an audible sound. + SetConditions( bits_COND_HEAR_SOUND ); + } + else + { + // if not a sound, must be a smell - determine if it's just a scent, or if it's a food scent +// if ( g_pSoundEnt->m_SoundPool[ iSound ].m_iType & ( bits_SOUND_MEAT | bits_SOUND_CARCASS ) ) + if ( pCurrentSound->m_iType & ( bits_SOUND_MEAT | bits_SOUND_CARCASS ) ) + { + // the detected scent is a food item, so set both conditions. + // !!!BUGBUG - maybe a virtual function to determine whether or not the scent is food? + SetConditions( bits_COND_SMELL_FOOD ); + SetConditions( bits_COND_SMELL ); + } + else + { + // just a normal scent. + SetConditions( bits_COND_SMELL ); + } + } + +// m_afSoundTypes |= g_pSoundEnt->m_SoundPool[ iSound ].m_iType; + m_afSoundTypes |= pCurrentSound->m_iType; + + m_iAudibleList = iSound; + } + +// iSound = g_pSoundEnt->m_SoundPool[ iSound ].m_iNext; + iSound = pCurrentSound->m_iNext; + } +} + +//========================================================= +// FLSoundVolume - subtracts the volume of the given sound +// from the distance the sound source is from the caller, +// and returns that value, which is considered to be the 'local' +// volume of the sound. +//========================================================= +float CBaseMonster :: FLSoundVolume ( CSound *pSound ) +{ + return ( pSound->m_iVolume - ( ( pSound->m_vecOrigin - pev->origin ).Length() ) ); +} + +//========================================================= +// FValidateHintType - tells use whether or not the monster cares +// about the type of Hint Node given +//========================================================= +BOOL CBaseMonster :: FValidateHintType ( short sHint ) +{ + return FALSE; +} + +//========================================================= +// Look - Base class monster function to find enemies or +// food by sight. iDistance is distance ( in units ) that the +// monster can see. +// +// Sets the sight bits of the m_afConditions mask to indicate +// which types of entities were sighted. +// Function also sets the Looker's m_pLink +// to the head of a link list that contains all visible ents. +// (linked via each ent's m_pLink field) +// +//========================================================= +void CBaseMonster :: Look ( int iDistance ) +{ + int iSighted = 0; + + // DON'T let visibility information from last frame sit around! + ClearConditions(bits_COND_SEE_HATE | bits_COND_SEE_DISLIKE | bits_COND_SEE_ENEMY | bits_COND_SEE_FEAR | bits_COND_SEE_NEMESIS | bits_COND_SEE_CLIENT); + + m_pLink = NULL; + + CBaseEntity *pSightEnt = NULL;// the current visible entity that we're dealing with + + // See no evil if prisoner is set + if ( !FBitSet( pev->spawnflags, SF_MONSTER_PRISONER ) ) + { + CBaseEntity *pList[100]; + + Vector delta = Vector( iDistance, iDistance, iDistance ); + + // Find only monsters/clients in box, NOT limited to PVS + int count = UTIL_EntitiesInBox( pList, 100, pev->origin - delta, pev->origin + delta, FL_CLIENT|FL_MONSTER ); + for ( int i = 0; i < count; i++ ) + { + pSightEnt = pList[i]; + // !!!temporarily only considering other monsters and clients, don't see prisoners + if ( pSightEnt != this && + !FBitSet( pSightEnt->pev->spawnflags, SF_MONSTER_PRISONER ) && + pSightEnt->pev->health > 0 ) + { + // the looker will want to consider this entity + // don't check anything else about an entity that can't be seen, or an entity that you don't care about. + if ( IRelationship( pSightEnt ) != R_NO && FInViewCone( pSightEnt ) && !FBitSet( pSightEnt->pev->flags, FL_NOTARGET ) && FVisible( pSightEnt ) ) + { + if ( pSightEnt->IsPlayer() ) + { + if ( pev->spawnflags & SF_MONSTER_WAIT_TILL_SEEN ) + { + CBaseMonster *pClient; + + pClient = pSightEnt->MyMonsterPointer(); + // don't link this client in the list if the monster is wait till seen and the player isn't facing the monster + if ( pSightEnt && !pClient->FInViewCone( this ) ) + { + // we're not in the player's view cone. + continue; + } + else + { + // player sees us, become normal now. + pev->spawnflags &= ~SF_MONSTER_WAIT_TILL_SEEN; + } + } + + // if we see a client, remember that (mostly for scripted AI) + iSighted |= bits_COND_SEE_CLIENT; + } + + pSightEnt->m_pLink = m_pLink; + m_pLink = pSightEnt; + + if ( pSightEnt == m_hEnemy ) + { + // we know this ent is visible, so if it also happens to be our enemy, store that now. + iSighted |= bits_COND_SEE_ENEMY; + } + + // don't add the Enemy's relationship to the conditions. We only want to worry about conditions when + // we see monsters other than the Enemy. + switch ( IRelationship ( pSightEnt ) ) + { + case R_NM: + iSighted |= bits_COND_SEE_NEMESIS; + break; + case R_HT: + iSighted |= bits_COND_SEE_HATE; + break; + case R_DL: + iSighted |= bits_COND_SEE_DISLIKE; + break; + case R_FR: + iSighted |= bits_COND_SEE_FEAR; + break; + case R_AL: + break; + default: + ALERT ( at_aiconsole, "%s can't assess %s\n", STRING(pev->classname), STRING(pSightEnt->pev->classname ) ); + break; + } + } + } + } + } + + SetConditions( iSighted ); +} + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. In the base class implementation, +// monsters care about all sounds, but no scents. +//========================================================= +int CBaseMonster :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER; +} + +//========================================================= +// PBestSound - returns a pointer to the sound the monster +// should react to. Right now responds only to nearest sound. +//========================================================= +CSound* CBaseMonster :: PBestSound ( void ) +{ + int iThisSound; + int iBestSound = -1; + float flBestDist = 8192;// so first nearby sound will become best so far. + float flDist; + CSound *pSound; + + iThisSound = m_iAudibleList; + + if ( iThisSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_aiconsole, "ERROR! monster %s has no audible sounds!\n", STRING(pev->classname) ); +#if _DEBUG + ALERT( at_error, "NULL Return from PBestSound\n" ); +#endif + return NULL; + } + + while ( iThisSound != SOUNDLIST_EMPTY ) + { + pSound = CSoundEnt::SoundPointerForIndex( iThisSound ); + + if ( pSound && pSound->FIsSound() ) + { + flDist = ( pSound->m_vecOrigin - EarPosition()).Length(); + + if ( flDist < flBestDist ) + { + iBestSound = iThisSound; + flBestDist = flDist; + } + } + + iThisSound = pSound->m_iNextAudible; + } + if ( iBestSound >= 0 ) + { + pSound = CSoundEnt::SoundPointerForIndex( iBestSound ); + return pSound; + } +#if _DEBUG + ALERT( at_error, "NULL Return from PBestSound\n" ); +#endif + return NULL; +} + +//========================================================= +// PBestScent - returns a pointer to the scent the monster +// should react to. Right now responds only to nearest scent +//========================================================= +CSound* CBaseMonster :: PBestScent ( void ) +{ + int iThisScent; + int iBestScent = -1; + float flBestDist = 8192;// so first nearby smell will become best so far. + float flDist; + CSound *pSound; + + iThisScent = m_iAudibleList;// smells are in the sound list. + + if ( iThisScent == SOUNDLIST_EMPTY ) + { + ALERT ( at_aiconsole, "ERROR! PBestScent() has empty soundlist!\n" ); +#if _DEBUG + ALERT( at_error, "NULL Return from PBestSound\n" ); +#endif + return NULL; + } + + while ( iThisScent != SOUNDLIST_EMPTY ) + { + pSound = CSoundEnt::SoundPointerForIndex( iThisScent ); + + if ( pSound->FIsScent() ) + { + flDist = ( pSound->m_vecOrigin - pev->origin ).Length(); + + if ( flDist < flBestDist ) + { + iBestScent = iThisScent; + flBestDist = flDist; + } + } + + iThisScent = pSound->m_iNextAudible; + } + if ( iBestScent >= 0 ) + { + pSound = CSoundEnt::SoundPointerForIndex( iBestScent ); + + return pSound; + } +#if _DEBUG + ALERT( at_error, "NULL Return from PBestScent\n" ); +#endif + return NULL; +} + + + +//========================================================= +// Monster Think - calls out to core AI functions and handles this +// monster's specific animation events +//========================================================= +void CBaseMonster :: MonsterThink ( void ) +{ + pev->nextthink = gpGlobals->time + 0.1;// keep monster thinking. + + + RunAI(); + + float flInterval = StudioFrameAdvance( ); // animate +// start or end a fidget +// This needs a better home -- switching animations over time should be encapsulated on a per-activity basis +// perhaps MaintainActivity() or a ShiftAnimationOverTime() or something. + if ( m_MonsterState != MONSTERSTATE_SCRIPT && m_MonsterState != MONSTERSTATE_DEAD && m_Activity == ACT_IDLE && m_fSequenceFinished ) + { + int iSequence; + + if ( m_fSequenceLoops ) + { + // animation does loop, which means we're playing subtle idle. Might need to + // fidget. + iSequence = LookupActivity ( m_Activity ); + } + else + { + // animation that just ended doesn't loop! That means we just finished a fidget + // and should return to our heaviest weighted idle (the subtle one) + iSequence = LookupActivityHeaviest ( m_Activity ); + } + if ( iSequence != ACTIVITY_NOT_AVAILABLE ) + { + pev->sequence = iSequence; // Set to new anim (if it's there) + ResetSequenceInfo( ); + } + } + + DispatchAnimEvents( flInterval ); + + if ( !MovementIsComplete() ) + { + Move( flInterval ); + } +#if _DEBUG + else + { + if ( !TaskIsRunning() && !TaskIsComplete() ) + ALERT( at_error, "Schedule stalled!!\n" ); + } +#endif +} + +//========================================================= +// CBaseMonster - USE - will make a monster angry at whomever +// activated it. +//========================================================= +void CBaseMonster :: MonsterUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_IdealMonsterState = MONSTERSTATE_ALERT; +} + +//========================================================= +// Ignore conditions - before a set of conditions is allowed +// to interrupt a monster's schedule, this function removes +// conditions that we have flagged to interrupt the current +// schedule, but may not want to interrupt the schedule every +// time. (Pain, for instance) +//========================================================= +int CBaseMonster :: IgnoreConditions ( void ) +{ + int iIgnoreConditions = 0; + + if ( !FShouldEat() ) + { + // not hungry? Ignore food smell. + iIgnoreConditions |= bits_COND_SMELL_FOOD; + } + + if ( m_MonsterState == MONSTERSTATE_SCRIPT && m_pCine ) + iIgnoreConditions |= m_pCine->IgnoreConditions(); + + return iIgnoreConditions; +} + +//========================================================= +// RouteClear - zeroes out the monster's route array and goal +//========================================================= +void CBaseMonster :: RouteClear ( void ) +{ + RouteNew(); + m_movementGoal = MOVEGOAL_NONE; + m_movementActivity = ACT_IDLE; + Forget( bits_MEMORY_MOVE_FAILED ); +} + +//========================================================= +// Route New - clears out a route to be changed, but keeps +// goal intact. +//========================================================= +void CBaseMonster :: RouteNew ( void ) +{ + m_Route[ 0 ].iType = 0; + m_iRouteIndex = 0; +} + +//========================================================= +// FRouteClear - returns TRUE is the Route is cleared out +// ( invalid ) +//========================================================= +BOOL CBaseMonster :: FRouteClear ( void ) +{ + if ( m_Route[ m_iRouteIndex ].iType == 0 || m_movementGoal == MOVEGOAL_NONE ) + return TRUE; + + return FALSE; +} + +//========================================================= +// FRefreshRoute - after calculating a path to the monster's +// target, this function copies as many waypoints as possible +// from that path to the monster's Route array +//========================================================= +BOOL CBaseMonster :: FRefreshRoute ( void ) +{ + CBaseEntity *pPathCorner; + int i; + BOOL returnCode; + + RouteNew(); + + returnCode = FALSE; + + switch( m_movementGoal ) + { + case MOVEGOAL_PATHCORNER: + { + // monster is on a path_corner loop + pPathCorner = m_pGoalEnt; + i = 0; + + while ( pPathCorner && i < ROUTE_SIZE ) + { + m_Route[ i ].iType = bits_MF_TO_PATHCORNER; + m_Route[ i ].vecLocation = pPathCorner->pev->origin; + + pPathCorner = pPathCorner->GetNextTarget(); + + // Last path_corner in list? + if ( !pPathCorner ) + m_Route[i].iType |= bits_MF_IS_GOAL; + + i++; + } + } + returnCode = TRUE; + break; + + case MOVEGOAL_ENEMY: + returnCode = BuildRoute( m_vecEnemyLKP, bits_MF_TO_ENEMY, m_hEnemy ); + break; + + case MOVEGOAL_LOCATION: + returnCode = BuildRoute( m_vecMoveGoal, bits_MF_TO_LOCATION, NULL ); + break; + + case MOVEGOAL_TARGETENT: + if (m_hTargetEnt != NULL) + { + returnCode = BuildRoute( m_hTargetEnt->pev->origin, bits_MF_TO_TARGETENT, m_hTargetEnt ); + } + break; + + case MOVEGOAL_NODE: + returnCode = FGetNodeRoute( m_vecMoveGoal ); +// if ( returnCode ) +// RouteSimplify( NULL ); + break; + } + + return returnCode; +} + + +BOOL CBaseMonster::MoveToEnemy( Activity movementAct, float waitTime ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_ENEMY; + return FRefreshRoute(); +} + + +BOOL CBaseMonster::MoveToLocation( Activity movementAct, float waitTime, const Vector &goal ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_LOCATION; + m_vecMoveGoal = goal; + return FRefreshRoute(); +} + + +BOOL CBaseMonster::MoveToTarget( Activity movementAct, float waitTime ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_TARGETENT; + return FRefreshRoute(); +} + + +BOOL CBaseMonster::MoveToNode( Activity movementAct, float waitTime, const Vector &goal ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_NODE; + m_vecMoveGoal = goal; + return FRefreshRoute(); +} + + +#ifdef _DEBUG +void DrawRoute( entvars_t *pev, WayPoint_t *m_Route, int m_iRouteIndex, int r, int g, int b ) +{ + int i; + + if ( m_Route[m_iRouteIndex].iType == 0 ) + { + ALERT( at_aiconsole, "Can't draw route!\n" ); + return; + } + +// UTIL_ParticleEffect ( m_Route[ m_iRouteIndex ].vecLocation, g_vecZero, 255, 25 ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( m_Route[ m_iRouteIndex ].vecLocation.x ); + WRITE_COORD( m_Route[ m_iRouteIndex ].vecLocation.y ); + WRITE_COORD( m_Route[ m_iRouteIndex ].vecLocation.z ); + + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 1 ); // life + WRITE_BYTE( 16 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( r ); // r, g, b + WRITE_BYTE( g ); // r, g, b + WRITE_BYTE( b ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); + + for ( i = m_iRouteIndex ; i < ROUTE_SIZE - 1; i++ ) + { + if ( (m_Route[ i ].iType & bits_MF_IS_GOAL) || (m_Route[ i+1 ].iType == 0) ) + break; + + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS ); + WRITE_COORD( m_Route[ i ].vecLocation.x ); + WRITE_COORD( m_Route[ i ].vecLocation.y ); + WRITE_COORD( m_Route[ i ].vecLocation.z ); + WRITE_COORD( m_Route[ i + 1 ].vecLocation.x ); + WRITE_COORD( m_Route[ i + 1 ].vecLocation.y ); + WRITE_COORD( m_Route[ i + 1 ].vecLocation.z ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 1 ); // life + WRITE_BYTE( 8 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( r ); // r, g, b + WRITE_BYTE( g ); // r, g, b + WRITE_BYTE( b ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); + +// UTIL_ParticleEffect ( m_Route[ i ].vecLocation, g_vecZero, 255, 25 ); + } +} +#endif + + +int ShouldSimplify( int routeType ) +{ + routeType &= ~bits_MF_IS_GOAL; + + if ( (routeType == bits_MF_TO_PATHCORNER) || (routeType & bits_MF_DONT_SIMPLIFY) ) + return FALSE; + return TRUE; +} + +//========================================================= +// RouteSimplify +// +// Attempts to make the route more direct by cutting out +// unnecessary nodes & cutting corners. +// +//========================================================= +void CBaseMonster :: RouteSimplify( CBaseEntity *pTargetEnt ) +{ + // BUGBUG: this doesn't work 100% yet + int i, count, outCount; + Vector vecStart; + WayPoint_t outRoute[ ROUTE_SIZE * 2 ]; // Any points except the ends can turn into 2 points in the simplified route + + count = 0; + + for ( i = m_iRouteIndex; i < ROUTE_SIZE; i++ ) + { + if ( !m_Route[i].iType ) + break; + else + count++; + if ( m_Route[i].iType & bits_MF_IS_GOAL ) + break; + } + // Can't simplify a direct route! + if ( count < 2 ) + { +// DrawRoute( pev, m_Route, m_iRouteIndex, 0, 0, 255 ); + return; + } + + outCount = 0; + vecStart = pev->origin; + for ( i = 0; i < count-1; i++ ) + { + // Don't eliminate path_corners + if ( !ShouldSimplify( m_Route[m_iRouteIndex+i].iType ) ) + { + outRoute[outCount] = m_Route[ m_iRouteIndex + i ]; + outCount++; + } + else if ( CheckLocalMove ( vecStart, m_Route[m_iRouteIndex+i+1].vecLocation, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + // Skip vert + continue; + } + else + { + Vector vecTest, vecSplit; + + // Halfway between this and next + vecTest = (m_Route[m_iRouteIndex+i+1].vecLocation + m_Route[m_iRouteIndex+i].vecLocation) * 0.5; + + // Halfway between this and previous + vecSplit = (m_Route[m_iRouteIndex+i].vecLocation + vecStart) * 0.5; + + int iType = (m_Route[m_iRouteIndex+i].iType | bits_MF_TO_DETOUR) & ~bits_MF_NOT_TO_MASK; + if ( CheckLocalMove ( vecStart, vecTest, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + outRoute[outCount].iType = iType; + outRoute[outCount].vecLocation = vecTest; + } + else if ( CheckLocalMove ( vecSplit, vecTest, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + outRoute[outCount].iType = iType; + outRoute[outCount].vecLocation = vecSplit; + outRoute[outCount+1].iType = iType; + outRoute[outCount+1].vecLocation = vecTest; + outCount++; // Adding an extra point + } + else + { + outRoute[outCount] = m_Route[ m_iRouteIndex + i ]; + } + } + // Get last point + vecStart = outRoute[ outCount ].vecLocation; + outCount++; + } + ASSERT( i < count ); + outRoute[outCount] = m_Route[ m_iRouteIndex + i ]; + outCount++; + + // Terminate + outRoute[outCount].iType = 0; + ASSERT( outCount < (ROUTE_SIZE*2) ); + +// Copy the simplified route, disable for testing + m_iRouteIndex = 0; + for ( i = 0; i < ROUTE_SIZE && i < outCount; i++ ) + { + m_Route[i] = outRoute[i]; + } + + // Terminate route + if ( i < ROUTE_SIZE ) + m_Route[i].iType = 0; + +// Debug, test movement code +#if 0 +// if ( CVAR_GET_FLOAT( "simplify" ) != 0 ) + DrawRoute( pev, outRoute, 0, 255, 0, 0 ); +// else + DrawRoute( pev, m_Route, m_iRouteIndex, 0, 255, 0 ); +#endif +} + +//========================================================= +// FBecomeProne - tries to send a monster into PRONE state. +// right now only used when a barnacle snatches someone, so +// may have some special case stuff for that. +//========================================================= +BOOL CBaseMonster :: FBecomeProne ( void ) +{ + if ( FBitSet ( pev->flags, FL_ONGROUND ) ) + { + pev->flags -= FL_ONGROUND; + } + + m_IdealMonsterState = MONSTERSTATE_PRONE; + return TRUE; +} + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CBaseMonster :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDist > 64 && flDist <= 784 && flDot >= 0.5 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 +//========================================================= +BOOL CBaseMonster :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + if ( flDist > 64 && flDist <= 512 && flDot >= 0.5 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL CBaseMonster :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + // Decent fix to keep folks from kicking/punching hornets and snarks is to check the onground flag(sjb) + if ( flDist <= 64 && flDot >= 0.7 && m_hEnemy != NULL && FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ) ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckMeleeAttack2 +//========================================================= +BOOL CBaseMonster :: CheckMeleeAttack2 ( float flDot, float flDist ) +{ + if ( flDist <= 64 && flDot >= 0.7 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckAttacks - sets all of the bits for attacks that the +// monster is capable of carrying out on the passed entity. +//========================================================= +void CBaseMonster :: CheckAttacks ( CBaseEntity *pTarget, float flDist ) +{ + Vector2D vec2LOS; + float flDot; + + UTIL_MakeVectors ( pev->angles ); + + vec2LOS = ( pTarget->pev->origin - pev->origin ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() ); + + // we know the enemy is in front now. We'll find which attacks the monster is capable of by + // checking for corresponding Activities in the model file, then do the simple checks to validate + // those attack types. + + // Clear all attack conditions + ClearConditions( bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_RANGE_ATTACK2 | bits_COND_CAN_MELEE_ATTACK1 |bits_COND_CAN_MELEE_ATTACK2 ); + + if ( m_afCapability & bits_CAP_RANGE_ATTACK1 ) + { + if ( CheckRangeAttack1 ( flDot, flDist ) ) + SetConditions( bits_COND_CAN_RANGE_ATTACK1 ); + } + if ( m_afCapability & bits_CAP_RANGE_ATTACK2 ) + { + if ( CheckRangeAttack2 ( flDot, flDist ) ) + SetConditions( bits_COND_CAN_RANGE_ATTACK2 ); + } + if ( m_afCapability & bits_CAP_MELEE_ATTACK1 ) + { + if ( CheckMeleeAttack1 ( flDot, flDist ) ) + SetConditions( bits_COND_CAN_MELEE_ATTACK1 ); + } + if ( m_afCapability & bits_CAP_MELEE_ATTACK2 ) + { + if ( CheckMeleeAttack2 ( flDot, flDist ) ) + SetConditions( bits_COND_CAN_MELEE_ATTACK2 ); + } +} + +//========================================================= +// CanCheckAttacks - prequalifies a monster to do more fine +// checking of potential attacks. +//========================================================= +BOOL CBaseMonster :: FCanCheckAttacks ( void ) +{ + if ( HasConditions(bits_COND_SEE_ENEMY) && !HasConditions( bits_COND_ENEMY_TOOFAR ) ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// CheckEnemy - part of the Condition collection process, +// gets and stores data and conditions pertaining to a monster's +// enemy. Returns TRUE if Enemy LKP was updated. +//========================================================= +int CBaseMonster :: CheckEnemy ( CBaseEntity *pEnemy ) +{ + float flDistToEnemy; + int iUpdatedLKP;// set this to TRUE if you update the EnemyLKP in this function. + + iUpdatedLKP = FALSE; + ClearConditions ( bits_COND_ENEMY_FACING_ME ); + + if ( !FVisible( pEnemy ) ) + { + ASSERT(!HasConditions(bits_COND_SEE_ENEMY)); + SetConditions( bits_COND_ENEMY_OCCLUDED ); + } + else + ClearConditions( bits_COND_ENEMY_OCCLUDED ); + + if ( !pEnemy->IsAlive() ) + { + SetConditions ( bits_COND_ENEMY_DEAD ); + ClearConditions( bits_COND_SEE_ENEMY | bits_COND_ENEMY_OCCLUDED ); + return FALSE; + } + + Vector vecEnemyPos = pEnemy->pev->origin; + // distance to enemy's origin + flDistToEnemy = ( vecEnemyPos - pev->origin ).Length(); + vecEnemyPos.z += pEnemy->pev->size.z * 0.5; + // distance to enemy's head + float flDistToEnemy2 = (vecEnemyPos - pev->origin).Length(); + if (flDistToEnemy2 < flDistToEnemy) + flDistToEnemy = flDistToEnemy2; + else + { + // distance to enemy's feet + vecEnemyPos.z -= pEnemy->pev->size.z; + float flDistToEnemy2 = (vecEnemyPos - pev->origin).Length(); + if (flDistToEnemy2 < flDistToEnemy) + flDistToEnemy = flDistToEnemy2; + } + + if ( HasConditions( bits_COND_SEE_ENEMY ) ) + { + CBaseMonster *pEnemyMonster; + + iUpdatedLKP = TRUE; + m_vecEnemyLKP = pEnemy->pev->origin; + + pEnemyMonster = pEnemy->MyMonsterPointer(); + + if ( pEnemyMonster ) + { + if ( pEnemyMonster->FInViewCone ( this ) ) + { + SetConditions ( bits_COND_ENEMY_FACING_ME ); + } + else + ClearConditions( bits_COND_ENEMY_FACING_ME ); + } + + if (pEnemy->pev->velocity != Vector( 0, 0, 0)) + { + // trail the enemy a bit + m_vecEnemyLKP = m_vecEnemyLKP - pEnemy->pev->velocity * RANDOM_FLOAT( -0.05, 0 ); + } + else + { + // UNDONE: use pev->oldorigin? + } + } + else if ( !HasConditions(bits_COND_ENEMY_OCCLUDED|bits_COND_SEE_ENEMY) && ( flDistToEnemy <= 256 ) ) + { + // if the enemy is not occluded, and unseen, that means it is behind or beside the monster. + // if the enemy is near enough the monster, we go ahead and let the monster know where the + // enemy is. + iUpdatedLKP = TRUE; + m_vecEnemyLKP = pEnemy->pev->origin; + } + + if ( flDistToEnemy >= m_flDistTooFar ) + { + // enemy is very far away from monster + SetConditions( bits_COND_ENEMY_TOOFAR ); + } + else + ClearConditions( bits_COND_ENEMY_TOOFAR ); + + if ( FCanCheckAttacks() ) + { + CheckAttacks ( m_hEnemy, flDistToEnemy ); + } + + if ( m_movementGoal == MOVEGOAL_ENEMY ) + { + for ( int i = m_iRouteIndex; i < ROUTE_SIZE; i++ ) + { + if ( m_Route[ i ].iType == (bits_MF_IS_GOAL|bits_MF_TO_ENEMY) ) + { + // UNDONE: Should we allow monsters to override this distance (80?) + if ( (m_Route[ i ].vecLocation - m_vecEnemyLKP).Length() > 80 ) + { + // Refresh + FRefreshRoute(); + return iUpdatedLKP; + } + } + } + } + + return iUpdatedLKP; +} + +//========================================================= +// PushEnemy - remember the last few enemies, always remember the player +//========================================================= +void CBaseMonster :: PushEnemy( CBaseEntity *pEnemy, Vector &vecLastKnownPos ) +{ + int i; + + if (pEnemy == NULL) + return; + + // UNDONE: blah, this is bad, we should use a stack but I'm too lazy to code one. + for (i = 0; i < MAX_OLD_ENEMIES; i++) + { + if (m_hOldEnemy[i] == pEnemy) + return; + if (m_hOldEnemy[i] == NULL) // someone died, reuse their slot + break; + } + if (i >= MAX_OLD_ENEMIES) + return; + + m_hOldEnemy[i] = pEnemy; + m_vecOldEnemy[i] = vecLastKnownPos; +} + +//========================================================= +// PopEnemy - try remembering the last few enemies +//========================================================= +BOOL CBaseMonster :: PopEnemy( ) +{ + // UNDONE: blah, this is bad, we should use a stack but I'm too lazy to code one. + for (int i = MAX_OLD_ENEMIES - 1; i >= 0; i--) + { + if (m_hOldEnemy[i] != NULL) + { + if (m_hOldEnemy[i]->IsAlive( )) // cheat and know when they die + { + m_hEnemy = m_hOldEnemy[i]; + m_vecEnemyLKP = m_vecOldEnemy[i]; + // ALERT( at_console, "remembering\n"); + return TRUE; + } + else + { + m_hOldEnemy[i] = NULL; + } + } + } + return FALSE; +} + +//========================================================= +// SetActivity +//========================================================= +void CBaseMonster :: SetActivity ( Activity NewActivity ) +{ + int iSequence; + + iSequence = LookupActivity ( NewActivity ); + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + if ( pev->sequence != iSequence || !m_fSequenceLoops ) + { + // don't reset frame between walk and run + if ( !(m_Activity == ACT_WALK || m_Activity == ACT_RUN) || !(NewActivity == ACT_WALK || NewActivity == ACT_RUN)) + pev->frame = 0; + } + + pev->sequence = iSequence; // Set to the reset anim (if it's there) + ResetSequenceInfo( ); + SetYawSpeed(); + } + else + { + // Not available try to get default anim + ALERT ( at_aiconsole, "%s has no sequence for act:%d\n", STRING(pev->classname), NewActivity ); + pev->sequence = 0; // Set to the reset anim (if it's there) + } + + m_Activity = NewActivity; // Go ahead and set this so it doesn't keep trying when the anim is not present + + // In case someone calls this with something other than the ideal activity + m_IdealActivity = m_Activity; + + +} + +//========================================================= +// SetSequenceByName +//========================================================= +void CBaseMonster :: SetSequenceByName ( char *szSequence ) +{ + int iSequence; + + iSequence = LookupSequence ( szSequence ); + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + if ( pev->sequence != iSequence || !m_fSequenceLoops ) + { + pev->frame = 0; + } + + pev->sequence = iSequence; // Set to the reset anim (if it's there) + ResetSequenceInfo( ); + SetYawSpeed(); + } + else + { + // Not available try to get default anim + ALERT ( at_aiconsole, "%s has no sequence named:%f\n", STRING(pev->classname), szSequence ); + pev->sequence = 0; // Set to the reset anim (if it's there) + } +} + +//========================================================= +// CheckLocalMove - returns TRUE if the caller can walk a +// straight line from its current origin to the given +// location. If so, don't use the node graph! +// +// if a valid pointer to a int is passed, the function +// will fill that int with the distance that the check +// reached before hitting something. THIS ONLY HAPPENS +// IF THE LOCAL MOVE CHECK FAILS! +// +// !!!PERFORMANCE - should we try to load balance this? +// DON"T USE SETORIGIN! +//========================================================= +#define LOCAL_STEP_SIZE 16 +int CBaseMonster :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ) +{ + Vector vecStartPos;// record monster's position before trying the move + float flYaw; + float flDist; + float flStep, stepSize; + int iReturn; + + vecStartPos = pev->origin; + + + flYaw = UTIL_VecToYaw ( vecEnd - vecStart );// build a yaw that points to the goal. + flDist = ( vecEnd - vecStart ).Length2D();// get the distance. + iReturn = LOCALMOVE_VALID;// assume everything will be ok. + + // move the monster to the start of the local move that's to be checked. + UTIL_SetOrigin( pev, vecStart );// !!!BUGBUG - won't this fire triggers? - nope, SetOrigin doesn't fire + + if ( !(pev->flags & (FL_FLY|FL_SWIM)) ) + { + DROP_TO_FLOOR( ENT( pev ) );//make sure monster is on the floor! + } + + //pev->origin.z = vecStartPos.z;//!!!HACKHACK + +// pev->origin = vecStart; + +/* + if ( flDist > 1024 ) + { + // !!!PERFORMANCE - this operation may be too CPU intensive to try checks this large. + // We don't lose much here, because a distance this great is very likely + // to have something in the way. + + // since we've actually moved the monster during the check, undo the move. + pev->origin = vecStartPos; + return FALSE; + } +*/ + // this loop takes single steps to the goal. + for ( flStep = 0 ; flStep < flDist ; flStep += LOCAL_STEP_SIZE ) + { + stepSize = LOCAL_STEP_SIZE; + + if ( (flStep + LOCAL_STEP_SIZE) >= (flDist-1) ) + stepSize = (flDist - flStep) - 1; + +// UTIL_ParticleEffect ( pev->origin, g_vecZero, 255, 25 ); + + if ( !WALK_MOVE( ENT(pev), flYaw, stepSize, WALKMOVE_CHECKONLY ) ) + {// can't take the next step, fail! + + if ( pflDist != NULL ) + { + *pflDist = flStep; + } + if ( pTarget && pTarget->edict() == gpGlobals->trace_ent ) + { + // if this step hits target ent, the move is legal. + iReturn = LOCALMOVE_VALID; + break; + } + else + { + // If we're going toward an entity, and we're almost getting there, it's OK. +// if ( pTarget && fabs( flDist - iStep ) < LOCAL_STEP_SIZE ) +// fReturn = TRUE; +// else + iReturn = LOCALMOVE_INVALID; + break; + } + + } + } + + if ( iReturn == LOCALMOVE_VALID && !(pev->flags & (FL_FLY|FL_SWIM) ) && (!pTarget || (pTarget->pev->flags & FL_ONGROUND)) ) + { + // The monster can move to a spot UNDER the target, but not to it. Don't try to triangulate, go directly to the node graph. + // UNDONE: Magic # 64 -- this used to be pev->size.z but that won't work for small creatures like the headcrab + if ( fabs(vecEnd.z - pev->origin.z) > 64 ) + { + iReturn = LOCALMOVE_INVALID_DONT_TRIANGULATE; + } + } + /* + // uncommenting this block will draw a line representing the nearest legal move. + WRITE_BYTE(MSG_BROADCAST, SVC_TEMPENTITY); + WRITE_BYTE(MSG_BROADCAST, TE_SHOWLINE); + WRITE_COORD(MSG_BROADCAST, pev->origin.x); + WRITE_COORD(MSG_BROADCAST, pev->origin.y); + WRITE_COORD(MSG_BROADCAST, pev->origin.z); + WRITE_COORD(MSG_BROADCAST, vecStart.x); + WRITE_COORD(MSG_BROADCAST, vecStart.y); + WRITE_COORD(MSG_BROADCAST, vecStart.z); + */ + + // since we've actually moved the monster during the check, undo the move. + UTIL_SetOrigin( pev, vecStartPos ); + + return iReturn; +} + + +float CBaseMonster :: OpenDoorAndWait( entvars_t *pevDoor ) +{ + float flTravelTime = 0; + + //ALERT(at_aiconsole, "A door. "); + CBaseEntity *pcbeDoor = CBaseEntity::Instance(pevDoor); + if (pcbeDoor && !pcbeDoor->IsLockedByMaster()) + { + //ALERT(at_aiconsole, "unlocked! "); + pcbeDoor->Use(this, this, USE_ON, 0.0); + //ALERT(at_aiconsole, "pevDoor->nextthink = %d ms\n", (int)(1000*pevDoor->nextthink)); + //ALERT(at_aiconsole, "pevDoor->ltime = %d ms\n", (int)(1000*pevDoor->ltime)); + //ALERT(at_aiconsole, "pev-> nextthink = %d ms\n", (int)(1000*pev->nextthink)); + //ALERT(at_aiconsole, "pev->ltime = %d ms\n", (int)(1000*pev->ltime)); + flTravelTime = pevDoor->nextthink - pevDoor->ltime; + //ALERT(at_aiconsole, "Waiting %d ms\n", (int)(1000*flTravelTime)); + if ( pcbeDoor->pev->targetname ) + { + edict_t *pentTarget = NULL; + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME( pentTarget, STRING(pcbeDoor->pev->targetname)); + + if ( VARS( pentTarget ) != pcbeDoor->pev ) + { + if (FNullEnt(pentTarget)) + break; + + if ( FClassnameIs ( pentTarget, STRING(pcbeDoor->pev->classname) ) ) + { + CBaseEntity *pDoor = Instance(pentTarget); + if ( pDoor ) + pDoor->Use(this, this, USE_ON, 0.0); + } + } + } + } + } + + return gpGlobals->time + flTravelTime; +} + + +//========================================================= +// AdvanceRoute - poorly named function that advances the +// m_iRouteIndex. If it goes beyond ROUTE_SIZE, the route +// is refreshed. +//========================================================= +void CBaseMonster :: AdvanceRoute ( float distance ) +{ + + if ( m_iRouteIndex == ROUTE_SIZE - 1 ) + { + // time to refresh the route. + if ( !FRefreshRoute() ) + { + ALERT ( at_aiconsole, "Can't Refresh Route!!\n" ); + } + } + else + { + if ( ! (m_Route[ m_iRouteIndex ].iType & bits_MF_IS_GOAL) ) + { + // If we've just passed a path_corner, advance m_pGoalEnt + if ( (m_Route[ m_iRouteIndex ].iType & ~bits_MF_NOT_TO_MASK) == bits_MF_TO_PATHCORNER ) + m_pGoalEnt = m_pGoalEnt->GetNextTarget(); + + // IF both waypoints are nodes, then check for a link for a door and operate it. + // + if ( (m_Route[m_iRouteIndex].iType & bits_MF_TO_NODE) == bits_MF_TO_NODE + && (m_Route[m_iRouteIndex+1].iType & bits_MF_TO_NODE) == bits_MF_TO_NODE) + { + //ALERT(at_aiconsole, "SVD: Two nodes. "); + + int iSrcNode = WorldGraph.FindNearestNode(m_Route[m_iRouteIndex].vecLocation, this ); + int iDestNode = WorldGraph.FindNearestNode(m_Route[m_iRouteIndex+1].vecLocation, this ); + + int iLink; + WorldGraph.HashSearch(iSrcNode, iDestNode, iLink); + + if ( iLink >= 0 && WorldGraph.m_pLinkPool[iLink].m_pLinkEnt != NULL ) + { + //ALERT(at_aiconsole, "A link. "); + if ( WorldGraph.HandleLinkEnt ( iSrcNode, WorldGraph.m_pLinkPool[iLink].m_pLinkEnt, m_afCapability, CGraph::NODEGRAPH_DYNAMIC ) ) + { + //ALERT(at_aiconsole, "usable."); + entvars_t *pevDoor = WorldGraph.m_pLinkPool[iLink].m_pLinkEnt; + if (pevDoor) + { + m_flMoveWaitFinished = OpenDoorAndWait( pevDoor ); +// ALERT( at_aiconsole, "Wating for door %.2f\n", m_flMoveWaitFinished-gpGlobals->time ); + } + } + } + //ALERT(at_aiconsole, "\n"); + } + m_iRouteIndex++; + } + else // At goal!!! + { + if ( distance < m_flGroundSpeed * 0.2 /* FIX */ ) + { + MovementComplete(); + } + } + } +} + + +int CBaseMonster :: RouteClassify( int iMoveFlag ) +{ + int movementGoal; + + movementGoal = MOVEGOAL_NONE; + + if ( iMoveFlag & bits_MF_TO_TARGETENT ) + movementGoal = MOVEGOAL_TARGETENT; + else if ( iMoveFlag & bits_MF_TO_ENEMY ) + movementGoal = MOVEGOAL_ENEMY; + else if ( iMoveFlag & bits_MF_TO_PATHCORNER ) + movementGoal = MOVEGOAL_PATHCORNER; + else if ( iMoveFlag & bits_MF_TO_NODE ) + movementGoal = MOVEGOAL_NODE; + else if ( iMoveFlag & bits_MF_TO_LOCATION ) + movementGoal = MOVEGOAL_LOCATION; + + return movementGoal; +} + +//========================================================= +// BuildRoute +//========================================================= +BOOL CBaseMonster :: BuildRoute ( const Vector &vecGoal, int iMoveFlag, CBaseEntity *pTarget ) +{ + float flDist; + Vector vecApex; + int iLocalMove; + + RouteNew(); + m_movementGoal = RouteClassify( iMoveFlag ); + +// so we don't end up with no moveflags + m_Route[ 0 ].vecLocation = vecGoal; + m_Route[ 0 ].iType = iMoveFlag | bits_MF_IS_GOAL; + +// check simple local move + iLocalMove = CheckLocalMove( pev->origin, vecGoal, pTarget, &flDist ); + + if ( iLocalMove == LOCALMOVE_VALID ) + { + // monster can walk straight there! + return TRUE; + } +// try to triangulate around any obstacles. + else if ( iLocalMove != LOCALMOVE_INVALID_DONT_TRIANGULATE && FTriangulate( pev->origin, vecGoal, flDist, pTarget, &vecApex ) ) + { + // there is a slightly more complicated path that allows the monster to reach vecGoal + m_Route[ 0 ].vecLocation = vecApex; + m_Route[ 0 ].iType = (iMoveFlag | bits_MF_TO_DETOUR); + + m_Route[ 1 ].vecLocation = vecGoal; + m_Route[ 1 ].iType = iMoveFlag | bits_MF_IS_GOAL; + + /* + WRITE_BYTE(MSG_BROADCAST, SVC_TEMPENTITY); + WRITE_BYTE(MSG_BROADCAST, TE_SHOWLINE); + WRITE_COORD(MSG_BROADCAST, vecApex.x ); + WRITE_COORD(MSG_BROADCAST, vecApex.y ); + WRITE_COORD(MSG_BROADCAST, vecApex.z ); + WRITE_COORD(MSG_BROADCAST, vecApex.x ); + WRITE_COORD(MSG_BROADCAST, vecApex.y ); + WRITE_COORD(MSG_BROADCAST, vecApex.z + 128 ); + */ + + RouteSimplify( pTarget ); + return TRUE; + } + +// last ditch, try nodes + if ( FGetNodeRoute( vecGoal ) ) + { +// ALERT ( at_console, "Can get there on nodes\n" ); + m_vecMoveGoal = vecGoal; + RouteSimplify( pTarget ); + return TRUE; + } + + // b0rk + return FALSE; +} + + +//========================================================= +// InsertWaypoint - Rebuilds the existing route so that the +// supplied vector and moveflags are the first waypoint in +// the route, and fills the rest of the route with as much +// of the pre-existing route as possible +//========================================================= +void CBaseMonster :: InsertWaypoint ( Vector vecLocation, int afMoveFlags ) +{ + int i, type; + + + // we have to save some Index and Type information from the real + // path_corner or node waypoint that the monster was trying to reach. This makes sure that data necessary + // to refresh the original path exists even in the new waypoints that don't correspond directy to a path_corner + // or node. + type = afMoveFlags | (m_Route[ m_iRouteIndex ].iType & ~bits_MF_NOT_TO_MASK); + + for ( i = ROUTE_SIZE-1; i > 0; i-- ) + m_Route[i] = m_Route[i-1]; + + m_Route[ m_iRouteIndex ].vecLocation = vecLocation; + m_Route[ m_iRouteIndex ].iType = type; +} + +//========================================================= +// FTriangulate - tries to overcome local obstacles by +// triangulating a path around them. +// +// iApexDist is how far the obstruction that we are trying +// to triangulate around is from the monster. +//========================================================= +BOOL CBaseMonster :: FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ) +{ + Vector vecDir; + Vector vecForward; + Vector vecLeft;// the spot we'll try to triangulate to on the left + Vector vecRight;// the spot we'll try to triangulate to on the right + Vector vecTop;// the spot we'll try to triangulate to on the top + Vector vecBottom;// the spot we'll try to triangulate to on the bottom + Vector vecFarSide;// the spot that we'll move to after hitting the triangulated point, before moving on to our normal goal. + int i; + float sizeX, sizeZ; + + // If the hull width is less than 24, use 24 because CheckLocalMove uses a min of + // 24. + sizeX = pev->size.x; + if (sizeX < 24.0) + sizeX = 24.0; + else if (sizeX > 48.0) + sizeX = 48.0; + sizeZ = pev->size.z; + //if (sizeZ < 24.0) + // sizeZ = 24.0; + + vecForward = ( vecEnd - vecStart ).Normalize(); + + Vector vecDirUp(0,0,1); + vecDir = CrossProduct ( vecForward, vecDirUp); + + // start checking right about where the object is, picking two equidistant starting points, one on + // the left, one on the right. As we progress through the loop, we'll push these away from the obstacle, + // hoping to find a way around on either side. pev->size.x is added to the ApexDist in order to help select + // an apex point that insures that the monster is sufficiently past the obstacle before trying to turn back + // onto its original course. + + vecLeft = pev->origin + ( vecForward * ( flDist + sizeX ) ) - vecDir * ( sizeX * 3 ); + vecRight = pev->origin + ( vecForward * ( flDist + sizeX ) ) + vecDir * ( sizeX * 3 ); + if (pev->movetype == MOVETYPE_FLY) + { + vecTop = pev->origin + (vecForward * flDist) + (vecDirUp * sizeZ * 3); + vecBottom = pev->origin + (vecForward * flDist) - (vecDirUp * sizeZ * 3); + } + + vecFarSide = m_Route[ m_iRouteIndex ].vecLocation; + + vecDir = vecDir * sizeX * 2; + if (pev->movetype == MOVETYPE_FLY) + vecDirUp = vecDirUp * sizeZ * 2; + + for ( i = 0 ; i < 8; i++ ) + { +// Debug, Draw the triangulation +#if 0 + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( vecRight.x ); + WRITE_COORD( vecRight.y ); + WRITE_COORD( vecRight.z ); + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( vecLeft.x ); + WRITE_COORD( vecLeft.y ); + WRITE_COORD( vecLeft.z ); + MESSAGE_END(); +#endif + +#if 0 + if (pev->movetype == MOVETYPE_FLY) + { + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( vecTop.x ); + WRITE_COORD( vecTop.y ); + WRITE_COORD( vecTop.z ); + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( vecBottom.x ); + WRITE_COORD( vecBottom.y ); + WRITE_COORD( vecBottom.z ); + MESSAGE_END(); + } +#endif + + if ( CheckLocalMove( pev->origin, vecRight, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( CheckLocalMove ( vecRight, vecFarSide, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( pApex ) + { + *pApex = vecRight; + } + + return TRUE; + } + } + if ( CheckLocalMove( pev->origin, vecLeft, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( CheckLocalMove ( vecLeft, vecFarSide, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( pApex ) + { + *pApex = vecLeft; + } + + return TRUE; + } + } + + if (pev->movetype == MOVETYPE_FLY) + { + if ( CheckLocalMove( pev->origin, vecTop, pTargetEnt, NULL ) == LOCALMOVE_VALID) + { + if ( CheckLocalMove ( vecTop, vecFarSide, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( pApex ) + { + *pApex = vecTop; + //ALERT(at_aiconsole, "triangulate over\n"); + } + + return TRUE; + } + } +#if 1 + if ( CheckLocalMove( pev->origin, vecBottom, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( CheckLocalMove ( vecBottom, vecFarSide, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( pApex ) + { + *pApex = vecBottom; + //ALERT(at_aiconsole, "triangulate under\n"); + } + + return TRUE; + } + } +#endif + } + + vecRight = vecRight + vecDir; + vecLeft = vecLeft - vecDir; + if (pev->movetype == MOVETYPE_FLY) + { + vecTop = vecTop + vecDirUp; + vecBottom = vecBottom - vecDirUp; + } + } + + return FALSE; +} + +//========================================================= +// Move - take a single step towards the next ROUTE location +//========================================================= +#define DIST_TO_CHECK 200 + +void CBaseMonster :: Move ( float flInterval ) +{ + float flWaypointDist; + float flCheckDist; + float flDist;// how far the lookahead check got before hitting an object. + Vector vecDir; + Vector vecApex; + CBaseEntity *pTargetEnt; + + // Don't move if no valid route + if ( FRouteClear() ) + { + // If we still have a movement goal, then this is probably a route truncated by SimplifyRoute() + // so refresh it. + if ( m_movementGoal == MOVEGOAL_NONE || !FRefreshRoute() ) + { + ALERT( at_aiconsole, "Tried to move with no route!\n" ); + TaskFail(); + return; + } + } + + if ( m_flMoveWaitFinished > gpGlobals->time ) + return; + +// Debug, test movement code +#if 0 +// if ( CVAR_GET_FLOAT("stopmove" ) != 0 ) + { + if ( m_movementGoal == MOVEGOAL_ENEMY ) + RouteSimplify( m_hEnemy ); + else + RouteSimplify( m_hTargetEnt ); + FRefreshRoute(); + return; + } +#else +// Debug, draw the route +// DrawRoute( pev, m_Route, m_iRouteIndex, 0, 200, 0 ); +#endif + + // if the monster is moving directly towards an entity (enemy for instance), we'll set this pointer + // to that entity for the CheckLocalMove and Triangulate functions. + pTargetEnt = NULL; + + // local move to waypoint. + vecDir = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Normalize(); + flWaypointDist = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Length2D(); + + MakeIdealYaw ( m_Route[ m_iRouteIndex ].vecLocation ); + ChangeYaw ( pev->yaw_speed ); + + // if the waypoint is closer than CheckDist, CheckDist is the dist to waypoint + if ( flWaypointDist < DIST_TO_CHECK ) + { + flCheckDist = flWaypointDist; + } + else + { + flCheckDist = DIST_TO_CHECK; + } + + if ( (m_Route[ m_iRouteIndex ].iType & (~bits_MF_NOT_TO_MASK)) == bits_MF_TO_ENEMY ) + { + // only on a PURE move to enemy ( i.e., ONLY MF_TO_ENEMY set, not MF_TO_ENEMY and DETOUR ) + pTargetEnt = m_hEnemy; + } + else if ( (m_Route[ m_iRouteIndex ].iType & ~bits_MF_NOT_TO_MASK) == bits_MF_TO_TARGETENT ) + { + pTargetEnt = m_hTargetEnt; + } + + // !!!BUGBUG - CheckDist should be derived from ground speed. + // If this fails, it should be because of some dynamic entity blocking this guy. + // We've already checked this path, so we should wait and time out if the entity doesn't move + flDist = 0; + if ( CheckLocalMove ( pev->origin, pev->origin + vecDir * flCheckDist, pTargetEnt, &flDist ) != LOCALMOVE_VALID ) + { + CBaseEntity *pBlocker; + + // Can't move, stop + Stop(); + // Blocking entity is in global trace_ent + pBlocker = CBaseEntity::Instance( gpGlobals->trace_ent ); + if (pBlocker) + { + DispatchBlocked( edict(), pBlocker->edict() ); + } + + if ( pBlocker && m_moveWaitTime > 0 && pBlocker->IsMoving() && !pBlocker->IsPlayer() && (gpGlobals->time-m_flMoveWaitFinished) > 3.0 ) + { + // Can we still move toward our target? + if ( flDist < m_flGroundSpeed ) + { + // No, Wait for a second + m_flMoveWaitFinished = gpGlobals->time + m_moveWaitTime; + return; + } + // Ok, still enough room to take a step + } + else + { + // try to triangulate around whatever is in the way. + if ( FTriangulate( pev->origin, m_Route[ m_iRouteIndex ].vecLocation, flDist, pTargetEnt, &vecApex ) ) + { + InsertWaypoint( vecApex, bits_MF_TO_DETOUR ); + RouteSimplify( pTargetEnt ); + } + else + { +// ALERT ( at_aiconsole, "Couldn't Triangulate\n" ); + Stop(); + // Only do this once until your route is cleared + if ( m_moveWaitTime > 0 && !(m_afMemory & bits_MEMORY_MOVE_FAILED) ) + { + FRefreshRoute(); + if ( FRouteClear() ) + { + TaskFail(); + } + else + { + // Don't get stuck + if ( (gpGlobals->time - m_flMoveWaitFinished) < 0.2 ) + Remember( bits_MEMORY_MOVE_FAILED ); + + m_flMoveWaitFinished = gpGlobals->time + 0.1; + } + } + else + { + TaskFail(); + ALERT( at_aiconsole, "%s Failed to move (%d)!\n", STRING(pev->classname), HasMemory( bits_MEMORY_MOVE_FAILED ) ); + //ALERT( at_aiconsole, "%f, %f, %f\n", pev->origin.z, (pev->origin + (vecDir * flCheckDist)).z, m_Route[m_iRouteIndex].vecLocation.z ); + } + return; + } + } + } + + // close enough to the target, now advance to the next target. This is done before actually reaching + // the target so that we get a nice natural turn while moving. + if ( ShouldAdvanceRoute( flWaypointDist ) )///!!!BUGBUG- magic number + { + AdvanceRoute( flWaypointDist ); + } + + // Might be waiting for a door + if ( m_flMoveWaitFinished > gpGlobals->time ) + { + Stop(); + return; + } + + // UNDONE: this is a hack to quit moving farther than it has looked ahead. + if (flCheckDist < m_flGroundSpeed * flInterval) + { + flInterval = flCheckDist / m_flGroundSpeed; + // ALERT( at_console, "%.02f\n", flInterval ); + } + MoveExecute( pTargetEnt, vecDir, flInterval ); + + if ( MovementIsComplete() ) + { + Stop(); + RouteClear(); + } +} + + +BOOL CBaseMonster:: ShouldAdvanceRoute( float flWaypointDist ) +{ + if ( flWaypointDist <= MONSTER_CUT_CORNER_DIST ) + { + // ALERT( at_console, "cut %f\n", flWaypointDist ); + return TRUE; + } + + return FALSE; +} + + +void CBaseMonster::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) +{ +// float flYaw = UTIL_VecToYaw ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin );// build a yaw that points to the goal. +// WALK_MOVE( ENT(pev), flYaw, m_flGroundSpeed * flInterval, WALKMOVE_NORMAL ); + if ( m_IdealActivity != m_movementActivity ) + m_IdealActivity = m_movementActivity; + + float flTotal = m_flGroundSpeed * pev->framerate * flInterval; + float flStep; + while (flTotal > 0.001) + { + // don't walk more than 16 units or stairs stop working + flStep = min( 16.0, flTotal ); + UTIL_MoveToOrigin ( ENT(pev), m_Route[ m_iRouteIndex ].vecLocation, flStep, MOVE_NORMAL ); + flTotal -= flStep; + } + // ALERT( at_console, "dist %f\n", m_flGroundSpeed * pev->framerate * flInterval ); +} + + +//========================================================= +// MonsterInit - after a monster is spawned, it needs to +// be dropped into the world, checked for mobility problems, +// and put on the proper path, if any. This function does +// all of those things after the monster spawns. Any +// initialization that should take place for all monsters +// goes here. +//========================================================= +void CBaseMonster :: MonsterInit ( void ) +{ + if (!g_pGameRules->FAllowMonsters()) + { + pev->flags |= FL_KILLME; // Post this because some monster code modifies class data after calling this function +// REMOVE_ENTITY(ENT(pev)); + return; + } + + // Set fields common to all monsters + pev->effects = 0; + pev->takedamage = DAMAGE_AIM; + pev->ideal_yaw = pev->angles.y; + pev->max_health = pev->health; + pev->deadflag = DEAD_NO; + m_IdealMonsterState = MONSTERSTATE_IDLE;// Assume monster will be idle, until proven otherwise + + m_IdealActivity = ACT_IDLE; + + SetBits (pev->flags, FL_MONSTER); + if ( pev->spawnflags & SF_MONSTER_HITMONSTERCLIP ) + pev->flags |= FL_MONSTERCLIP; + + ClearSchedule(); + RouteClear(); + InitBoneControllers( ); // FIX: should be done in Spawn + + m_iHintNode = NO_NODE; + + m_afMemory = MEMORY_CLEAR; + + m_hEnemy = NULL; + + m_flDistTooFar = 1024.0; + m_flDistLook = 2048.0; + + // set eye position + SetEyePosition(); + + SetThink( &CBaseMonster::MonsterInitThink ); + pev->nextthink = gpGlobals->time + 0.1; + SetUse ( &CBaseMonster::MonsterUse ); +} + +//========================================================= +// MonsterInitThink - Calls StartMonster. Startmonster is +// virtual, but this function cannot be +//========================================================= +void CBaseMonster :: MonsterInitThink ( void ) +{ + StartMonster(); +} + +//========================================================= +// StartMonster - final bit of initization before a monster +// is turned over to the AI. +//========================================================= +void CBaseMonster :: StartMonster ( void ) +{ + // update capabilities + if ( LookupActivity ( ACT_RANGE_ATTACK1 ) != ACTIVITY_NOT_AVAILABLE ) + { + m_afCapability |= bits_CAP_RANGE_ATTACK1; + } + if ( LookupActivity ( ACT_RANGE_ATTACK2 ) != ACTIVITY_NOT_AVAILABLE ) + { + m_afCapability |= bits_CAP_RANGE_ATTACK2; + } + if ( LookupActivity ( ACT_MELEE_ATTACK1 ) != ACTIVITY_NOT_AVAILABLE ) + { + m_afCapability |= bits_CAP_MELEE_ATTACK1; + } + if ( LookupActivity ( ACT_MELEE_ATTACK2 ) != ACTIVITY_NOT_AVAILABLE ) + { + m_afCapability |= bits_CAP_MELEE_ATTACK2; + } + + // Raise monster off the floor one unit, then drop to floor + if ( pev->movetype != MOVETYPE_FLY && !FBitSet( pev->spawnflags, SF_MONSTER_FALL_TO_GROUND ) ) + { + pev->origin.z += 1; + DROP_TO_FLOOR ( ENT(pev) ); + // Try to move the monster to make sure it's not stuck in a brush. + if (!WALK_MOVE ( ENT(pev), 0, 0, WALKMOVE_NORMAL ) ) + { + ALERT(at_error, "Monster %s stuck in wall--level design error", STRING(pev->classname)); + pev->effects = EF_BRIGHTFIELD; + } + } + else + { + pev->flags &= ~FL_ONGROUND; + } + + if ( !FStringNull(pev->target) )// this monster has a target + { + // Find the monster's initial target entity, stash it + m_pGoalEnt = CBaseEntity::Instance( FIND_ENTITY_BY_TARGETNAME ( NULL, STRING( pev->target ) ) ); + + if ( !m_pGoalEnt ) + { + ALERT(at_error, "ReadyMonster()--%s couldn't find target %s", STRING(pev->classname), STRING(pev->target)); + } + else + { + // Monster will start turning towards his destination + MakeIdealYaw ( m_pGoalEnt->pev->origin ); + + // JAY: How important is this error message? Big Momma doesn't obey this rule, so I took it out. +#if 0 + // At this point, we expect only a path_corner as initial goal + if (!FClassnameIs( m_pGoalEnt->pev, "path_corner")) + { + ALERT(at_warning, "ReadyMonster--monster's initial goal '%s' is not a path_corner", STRING(pev->target)); + } +#endif + + // set the monster up to walk a path corner path. + // !!!BUGBUG - this is a minor bit of a hack. + // JAYJAY + m_movementGoal = MOVEGOAL_PATHCORNER; + + if ( pev->movetype == MOVETYPE_FLY ) + m_movementActivity = ACT_FLY; + else + m_movementActivity = ACT_WALK; + + if ( !FRefreshRoute() ) + { + ALERT ( at_aiconsole, "Can't Create Route!\n" ); + } + SetState( MONSTERSTATE_IDLE ); + ChangeSchedule( GetScheduleOfType( SCHED_IDLE_WALK ) ); + } + } + + //SetState ( m_IdealMonsterState ); + //SetActivity ( m_IdealActivity ); + + // Delay drop to floor to make sure each door in the level has had its chance to spawn + // Spread think times so that they don't all happen at the same time (Carmack) + SetThink ( &CBaseMonster::CallMonsterThink ); + pev->nextthink += RANDOM_FLOAT(0.1, 0.4); // spread think times. + + if ( !FStringNull(pev->targetname) )// wait until triggered + { + SetState( MONSTERSTATE_IDLE ); + // UNDONE: Some scripted sequence monsters don't have an idle? + SetActivity( ACT_IDLE ); + ChangeSchedule( GetScheduleOfType( SCHED_WAIT_TRIGGER ) ); + } +} + + +void CBaseMonster :: MovementComplete( void ) +{ + switch( m_iTaskStatus ) + { + case TASKSTATUS_NEW: + case TASKSTATUS_RUNNING: + m_iTaskStatus = TASKSTATUS_RUNNING_TASK; + break; + + case TASKSTATUS_RUNNING_MOVEMENT: + TaskComplete(); + break; + + case TASKSTATUS_RUNNING_TASK: + ALERT( at_error, "Movement completed twice!\n" ); + break; + + case TASKSTATUS_COMPLETE: + break; + } + m_movementGoal = MOVEGOAL_NONE; +} + + +int CBaseMonster::TaskIsRunning( void ) +{ + if ( m_iTaskStatus != TASKSTATUS_COMPLETE && + m_iTaskStatus != TASKSTATUS_RUNNING_MOVEMENT ) + return 1; + + return 0; +} + +//========================================================= +// IRelationship - returns an integer that describes the +// relationship between two types of monster. +//========================================================= +int CBaseMonster::IRelationship ( CBaseEntity *pTarget ) +{ + static int iEnemy[14][14] = + { // NONE MACH PLYR HPASS HMIL AMIL APASS AMONST APREY APRED INSECT PLRALY PBWPN ABWPN + /*NONE*/ { R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO, R_NO, R_NO }, + /*MACHINE*/ { R_NO ,R_NO ,R_DL ,R_DL ,R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_DL, R_DL, R_DL }, + /*PLAYER*/ { R_NO ,R_DL ,R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO, R_DL, R_DL }, + /*HUMANPASSIVE*/{ R_NO ,R_NO ,R_AL ,R_AL ,R_HT ,R_FR ,R_NO ,R_HT ,R_DL ,R_FR ,R_NO ,R_AL, R_NO, R_NO }, + /*HUMANMILITAR*/{ R_NO ,R_NO ,R_HT ,R_DL ,R_NO ,R_HT ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_HT, R_NO, R_NO }, + /*ALIENMILITAR*/{ R_NO ,R_DL ,R_HT ,R_DL ,R_HT ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_DL, R_NO, R_NO }, + /*ALIENPASSIVE*/{ R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO, R_NO, R_NO }, + /*ALIENMONSTER*/{ R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_DL, R_NO, R_NO }, + /*ALIENPREY */{ R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO ,R_NO ,R_NO ,R_FR ,R_NO ,R_DL, R_NO, R_NO }, + /*ALIENPREDATO*/{ R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO ,R_NO ,R_HT ,R_DL ,R_NO ,R_DL, R_NO, R_NO }, + /*INSECT*/ { R_FR ,R_FR ,R_FR ,R_FR ,R_FR ,R_NO ,R_FR ,R_FR ,R_FR ,R_FR ,R_NO ,R_FR, R_NO, R_NO }, + /*PLAYERALLY*/ { R_NO ,R_DL ,R_AL ,R_AL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO, R_NO, R_NO }, + /*PBIOWEAPON*/ { R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_DL, R_NO, R_DL }, + /*ABIOWEAPON*/ { R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_AL ,R_NO ,R_DL ,R_DL ,R_NO ,R_NO ,R_DL, R_DL, R_NO } + }; + + return iEnemy[ Classify() ][ pTarget->Classify() ]; +} + +//========================================================= +// FindCover - tries to find a nearby node that will hide +// the caller from its enemy. +// +// If supplied, search will return a node at least as far +// away as MinDist, but no farther than MaxDist. +// if MaxDist isn't supplied, it defaults to a reasonable +// value +//========================================================= +// UNDONE: Should this find the nearest node? + +//float CGraph::PathLength( int iStart, int iDest, int iHull, int afCapMask ) + +BOOL CBaseMonster :: FindCover ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ) +{ + int i; + int iMyHullIndex; + int iMyNode; + int iThreatNode; + float flDist; + Vector vecLookersOffset; + TraceResult tr; + + if ( !flMaxDist ) + { + // user didn't supply a MaxDist, so work up a crazy one. + flMaxDist = 784; + } + + if ( flMinDist > 0.5 * flMaxDist) + { +#if _DEBUG + ALERT ( at_console, "FindCover MinDist (%.0f) too close to MaxDist (%.0f)\n", flMinDist, flMaxDist ); +#endif + flMinDist = 0.5 * flMaxDist; + } + + if ( !WorldGraph.m_fGraphPresent || !WorldGraph.m_fGraphPointersSet ) + { + ALERT ( at_aiconsole, "Graph not ready for findcover!\n" ); + return FALSE; + } + + iMyNode = WorldGraph.FindNearestNode( pev->origin, this ); + iThreatNode = WorldGraph.FindNearestNode ( vecThreat, this ); + iMyHullIndex = WorldGraph.HullIndex( this ); + + if ( iMyNode == NO_NODE ) + { + ALERT ( at_aiconsole, "FindCover() - %s has no nearest node!\n", STRING(pev->classname)); + return FALSE; + } + if ( iThreatNode == NO_NODE ) + { + // ALERT ( at_aiconsole, "FindCover() - Threat has no nearest node!\n" ); + iThreatNode = iMyNode; + // return FALSE; + } + + vecLookersOffset = vecThreat + vecViewOffset;// calculate location of enemy's eyes + + // we'll do a rough sample to find nodes that are relatively nearby + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + int nodeNumber = (i + WorldGraph.m_iLastCoverSearch) % WorldGraph.m_cNodes; + + CNode &node = WorldGraph.Node( nodeNumber ); + WorldGraph.m_iLastCoverSearch = nodeNumber + 1; // next monster that searches for cover node will start where we left off here. + + // could use an optimization here!! + flDist = ( pev->origin - node.m_vecOrigin ).Length(); + + // DON'T do the trace check on a node that is farther away than a node that we've already found to + // provide cover! Also make sure the node is within the mins/maxs of the search. + if ( flDist >= flMinDist && flDist < flMaxDist ) + { + UTIL_TraceLine ( node.m_vecOrigin + vecViewOffset, vecLookersOffset, ignore_monsters, ignore_glass, ENT(pev), &tr ); + + // if this node will block the threat's line of sight to me... + if ( tr.flFraction != 1.0 ) + { + // ..and is also closer to me than the threat, or the same distance from myself and the threat the node is good. + if ( ( iMyNode == iThreatNode ) || WorldGraph.PathLength( iMyNode, nodeNumber, iMyHullIndex, m_afCapability ) <= WorldGraph.PathLength( iThreatNode, nodeNumber, iMyHullIndex, m_afCapability ) ) + { + if ( FValidateCover ( node.m_vecOrigin ) && MoveToLocation( ACT_RUN, 0, node.m_vecOrigin ) ) + { + /* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( node.m_vecOrigin.x ); + WRITE_COORD( node.m_vecOrigin.y ); + WRITE_COORD( node.m_vecOrigin.z ); + + WRITE_COORD( vecLookersOffset.x ); + WRITE_COORD( vecLookersOffset.y ); + WRITE_COORD( vecLookersOffset.z ); + MESSAGE_END(); + */ + + return TRUE; + } + } + } + } + } + return FALSE; +} + + +//========================================================= +// BuildNearestRoute - tries to build a route as close to the target +// as possible, even if there isn't a path to the final point. +// +// If supplied, search will return a node at least as far +// away as MinDist from vecThreat, but no farther than MaxDist. +// if MaxDist isn't supplied, it defaults to a reasonable +// value +//========================================================= +BOOL CBaseMonster :: BuildNearestRoute ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ) +{ + int i; + int iMyHullIndex; + int iMyNode; + float flDist; + Vector vecLookersOffset; + TraceResult tr; + + if ( !flMaxDist ) + { + // user didn't supply a MaxDist, so work up a crazy one. + flMaxDist = 784; + } + + if ( flMinDist > 0.5 * flMaxDist) + { +#if _DEBUG + ALERT ( at_console, "FindCover MinDist (%.0f) too close to MaxDist (%.0f)\n", flMinDist, flMaxDist ); +#endif + flMinDist = 0.5 * flMaxDist; + } + + if ( !WorldGraph.m_fGraphPresent || !WorldGraph.m_fGraphPointersSet ) + { + ALERT ( at_aiconsole, "Graph not ready for BuildNearestRoute!\n" ); + return FALSE; + } + + iMyNode = WorldGraph.FindNearestNode( pev->origin, this ); + iMyHullIndex = WorldGraph.HullIndex( this ); + + if ( iMyNode == NO_NODE ) + { + ALERT ( at_aiconsole, "BuildNearestRoute() - %s has no nearest node!\n", STRING(pev->classname)); + return FALSE; + } + + vecLookersOffset = vecThreat + vecViewOffset;// calculate location of enemy's eyes + + // we'll do a rough sample to find nodes that are relatively nearby + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + int nodeNumber = (i + WorldGraph.m_iLastCoverSearch) % WorldGraph.m_cNodes; + + CNode &node = WorldGraph.Node( nodeNumber ); + WorldGraph.m_iLastCoverSearch = nodeNumber + 1; // next monster that searches for cover node will start where we left off here. + + // can I get there? + if (WorldGraph.NextNodeInRoute( iMyNode, nodeNumber, iMyHullIndex, 0 ) != iMyNode) + { + flDist = ( vecThreat - node.m_vecOrigin ).Length(); + + // is it close? + if ( flDist > flMinDist && flDist < flMaxDist) + { + // can I see where I want to be from there? + UTIL_TraceLine( node.m_vecOrigin + pev->view_ofs, vecLookersOffset, ignore_monsters, edict(), &tr ); + + if (tr.flFraction == 1.0) + { + // try to actually get there + if ( BuildRoute ( node.m_vecOrigin, bits_MF_TO_LOCATION, NULL ) ) + { + flMaxDist = flDist; + m_vecMoveGoal = node.m_vecOrigin; + return TRUE; // UNDONE: keep looking for something closer! + } + } + } + } + } + + return FALSE; +} + + + +//========================================================= +// BestVisibleEnemy - this functions searches the link +// list whose head is the caller's m_pLink field, and returns +// a pointer to the enemy entity in that list that is nearest the +// caller. +// +// !!!UNDONE - currently, this only returns the closest enemy. +// we'll want to consider distance, relationship, attack types, back turned, etc. +//========================================================= +CBaseEntity *CBaseMonster :: BestVisibleEnemy ( void ) +{ + CBaseEntity *pReturn; + CBaseEntity *pNextEnt; + int iNearest; + int iDist; + int iBestRelationship; + + iNearest = 8192;// so first visible entity will become the closest. + pNextEnt = m_pLink; + pReturn = NULL; + iBestRelationship = R_NO; + + while ( pNextEnt != NULL ) + { + if ( pNextEnt->IsAlive() ) + { + if ( IRelationship( pNextEnt) > iBestRelationship ) + { + // this entity is disliked MORE than the entity that we + // currently think is the best visible enemy. No need to do + // a distance check, just get mad at this one for now. + iBestRelationship = IRelationship ( pNextEnt ); + iNearest = ( pNextEnt->pev->origin - pev->origin ).Length(); + pReturn = pNextEnt; + } + else if ( IRelationship( pNextEnt) == iBestRelationship ) + { + // this entity is disliked just as much as the entity that + // we currently think is the best visible enemy, so we only + // get mad at it if it is closer. + iDist = ( pNextEnt->pev->origin - pev->origin ).Length(); + + if ( iDist <= iNearest ) + { + iNearest = iDist; + iBestRelationship = IRelationship ( pNextEnt ); + pReturn = pNextEnt; + } + } + } + + pNextEnt = pNextEnt->m_pLink; + } + + return pReturn; +} + + +//========================================================= +// MakeIdealYaw - gets a yaw value for the caller that would +// face the supplied vector. Value is stuffed into the monster's +// ideal_yaw +//========================================================= +void CBaseMonster :: MakeIdealYaw( Vector vecTarget ) +{ + Vector vecProjection; + + // strafing monster needs to face 90 degrees away from its goal + if ( m_movementActivity == ACT_STRAFE_LEFT ) + { + vecProjection.x = -vecTarget.y; + vecProjection.y = vecTarget.x; + + pev->ideal_yaw = UTIL_VecToYaw( vecProjection - pev->origin ); + } + else if ( m_movementActivity == ACT_STRAFE_RIGHT ) + { + vecProjection.x = vecTarget.y; + vecProjection.y = vecTarget.x; + + pev->ideal_yaw = UTIL_VecToYaw( vecProjection - pev->origin ); + } + else + { + pev->ideal_yaw = UTIL_VecToYaw ( vecTarget - pev->origin ); + } +} + +//========================================================= +// FlYawDiff - returns the difference ( in degrees ) between +// monster's current yaw and ideal_yaw +// +// Positive result is left turn, negative is right turn +//========================================================= +float CBaseMonster::FlYawDiff ( void ) +{ + float flCurrentYaw; + + flCurrentYaw = UTIL_AngleMod( pev->angles.y ); + + if ( flCurrentYaw == pev->ideal_yaw ) + { + return 0; + } + + + return UTIL_AngleDiff( pev->ideal_yaw, flCurrentYaw ); +} + + +//========================================================= +// Changeyaw - turns a monster towards its ideal_yaw +//========================================================= +float CBaseMonster::ChangeYaw ( int yawSpeed ) +{ + float ideal, current, move, speed; + + current = UTIL_AngleMod( pev->angles.y ); + ideal = pev->ideal_yaw; + if (current != ideal) + { + speed = (float)yawSpeed * gpGlobals->frametime * 10; + move = ideal - current; + + if (ideal > current) + { + if (move >= 180) + move = move - 360; + } + else + { + if (move <= -180) + move = move + 360; + } + + if (move > 0) + {// turning to the monster's left + if (move > speed) + move = speed; + } + else + {// turning to the monster's right + if (move < -speed) + move = -speed; + } + + pev->angles.y = UTIL_AngleMod (current + move); + + // turn head in desired direction only if they have a turnable head + if (m_afCapability & bits_CAP_TURN_HEAD) + { + float yaw = pev->ideal_yaw - pev->angles.y; + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + // yaw *= 0.8; + SetBoneController( 0, yaw ); + } + } + else + move = 0; + + return move; +} + +//========================================================= +// VecToYaw - turns a directional vector into a yaw value +// that points down that vector. +//========================================================= +float CBaseMonster::VecToYaw ( Vector vecDir ) +{ + if (vecDir.x == 0 && vecDir.y == 0 && vecDir.z == 0) + return pev->angles.y; + + return UTIL_VecToYaw( vecDir ); +} + + +//========================================================= +// SetEyePosition +// +// queries the monster's model for $eyeposition and copies +// that vector to the monster's view_ofs +// +//========================================================= +void CBaseMonster :: SetEyePosition ( void ) +{ + Vector vecEyePosition; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + GetEyePosition( pmodel, vecEyePosition ); + + pev->view_ofs = vecEyePosition; + + if ( pev->view_ofs == g_vecZero ) + { + ALERT ( at_aiconsole, "%s has no view_ofs!\n", STRING ( pev->classname ) ); + } +} + +void CBaseMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case SCRIPT_EVENT_DEAD: + if ( m_MonsterState == MONSTERSTATE_SCRIPT ) + { + pev->deadflag = DEAD_DYING; + // Kill me now! (and fade out when CineCleanup() is called) +#if _DEBUG + ALERT( at_aiconsole, "Death event: %s\n", STRING(pev->classname) ); +#endif + pev->health = 0; + } +#if _DEBUG + else + ALERT( at_aiconsole, "INVALID death event:%s\n", STRING(pev->classname) ); +#endif + break; + case SCRIPT_EVENT_NOT_DEAD: + if ( m_MonsterState == MONSTERSTATE_SCRIPT ) + { + pev->deadflag = DEAD_NO; + // This is for life/death sequences where the player can determine whether a character is dead or alive after the script + pev->health = pev->max_health; + } + break; + + case SCRIPT_EVENT_SOUND: // Play a named wave file + EMIT_SOUND( edict(), CHAN_BODY, pEvent->options, 1.0, ATTN_IDLE ); + break; + + case SCRIPT_EVENT_SOUND_VOICE: + EMIT_SOUND( edict(), CHAN_VOICE, pEvent->options, 1.0, ATTN_IDLE ); + break; + + case SCRIPT_EVENT_SENTENCE_RND1: // Play a named sentence group 33% of the time + if (RANDOM_LONG(0,2) == 0) + break; + // fall through... + case SCRIPT_EVENT_SENTENCE: // Play a named sentence group + SENTENCEG_PlayRndSz( edict(), pEvent->options, 1.0, ATTN_IDLE, 0, 100 ); + break; + + case SCRIPT_EVENT_FIREEVENT: // Fire a trigger + FireTargets( pEvent->options, this, this, USE_TOGGLE, 0 ); + break; + + case SCRIPT_EVENT_NOINTERRUPT: // Can't be interrupted from now on + if ( m_pCine ) + m_pCine->AllowInterrupt( FALSE ); + break; + + case SCRIPT_EVENT_CANINTERRUPT: // OK to interrupt now + if ( m_pCine ) + m_pCine->AllowInterrupt( TRUE ); + break; + +#if 0 + case SCRIPT_EVENT_INAIR: // Don't DROP_TO_FLOOR() + case SCRIPT_EVENT_ENDANIMATION: // Set ending animation sequence to + break; +#endif + + case MONSTER_EVENT_BODYDROP_HEAVY: + if ( pev->flags & FL_ONGROUND ) + { + if ( RANDOM_LONG( 0, 1 ) == 0 ) + { + EMIT_SOUND_DYN( ENT(pev), CHAN_BODY, "common/bodydrop3.wav", 1, ATTN_NORM, 0, 90 ); + } + else + { + EMIT_SOUND_DYN( ENT(pev), CHAN_BODY, "common/bodydrop4.wav", 1, ATTN_NORM, 0, 90 ); + } + } + break; + + case MONSTER_EVENT_BODYDROP_LIGHT: + if ( pev->flags & FL_ONGROUND ) + { + if ( RANDOM_LONG( 0, 1 ) == 0 ) + { + EMIT_SOUND( ENT(pev), CHAN_BODY, "common/bodydrop3.wav", 1, ATTN_NORM ); + } + else + { + EMIT_SOUND( ENT(pev), CHAN_BODY, "common/bodydrop4.wav", 1, ATTN_NORM ); + } + } + break; + + case MONSTER_EVENT_SWISHSOUND: + { + // NO MONSTER may use this anim event unless that monster's precache precaches this sound!!! + EMIT_SOUND( ENT(pev), CHAN_BODY, "zombie/claw_miss2.wav", 1, ATTN_NORM ); + break; + } + + default: + ALERT( at_aiconsole, "Unhandled animation event %d for %s\n", pEvent->event, STRING(pev->classname) ); + break; + + } +} + + +// Combat + +Vector CBaseMonster :: GetGunPosition( ) +{ + UTIL_MakeVectors(pev->angles); + + // Vector vecSrc = pev->origin + gpGlobals->v_forward * 10; + //vecSrc.z = pevShooter->absmin.z + pevShooter->size.z * 0.7; + //vecSrc.z = pev->origin.z + (pev->view_ofs.z - 4); + Vector vecSrc = pev->origin + + gpGlobals->v_forward * m_HackedGunPos.y + + gpGlobals->v_right * m_HackedGunPos.x + + gpGlobals->v_up * m_HackedGunPos.z; + + return vecSrc; +} + + + + + +//========================================================= +// NODE GRAPH +//========================================================= + + + + + +//========================================================= +// FGetNodeRoute - tries to build an entire node path from +// the callers origin to the passed vector. If this is +// possible, ROUTE_SIZE waypoints will be copied into the +// callers m_Route. TRUE is returned if the operation +// succeeds (path is valid) or FALSE if failed (no path +// exists ) +//========================================================= +BOOL CBaseMonster :: FGetNodeRoute ( Vector vecDest ) +{ + int iPath[ MAX_PATH_SIZE ]; + int iSrcNode, iDestNode; + int iResult; + int i; + int iNumToCopy; + + iSrcNode = WorldGraph.FindNearestNode ( pev->origin, this ); + iDestNode = WorldGraph.FindNearestNode ( vecDest, this ); + + if ( iSrcNode == -1 ) + { + // no node nearest self +// ALERT ( at_aiconsole, "FGetNodeRoute: No valid node near self!\n" ); + return FALSE; + } + else if ( iDestNode == -1 ) + { + // no node nearest target +// ALERT ( at_aiconsole, "FGetNodeRoute: No valid node near target!\n" ); + return FALSE; + } + + // valid src and dest nodes were found, so it's safe to proceed with + // find shortest path + int iNodeHull = WorldGraph.HullIndex( this ); // make this a monster virtual function + iResult = WorldGraph.FindShortestPath ( iPath, iSrcNode, iDestNode, iNodeHull, m_afCapability ); + + if ( !iResult ) + { +#if 1 + ALERT ( at_aiconsole, "No Path from %d to %d!\n", iSrcNode, iDestNode ); + return FALSE; +#else + BOOL bRoutingSave = WorldGraph.m_fRoutingComplete; + WorldGraph.m_fRoutingComplete = FALSE; + iResult = WorldGraph.FindShortestPath(iPath, iSrcNode, iDestNode, iNodeHull, m_afCapability); + WorldGraph.m_fRoutingComplete = bRoutingSave; + if ( !iResult ) + { + ALERT ( at_aiconsole, "No Path from %d to %d!\n", iSrcNode, iDestNode ); + return FALSE; + } + else + { + ALERT ( at_aiconsole, "Routing is inconsistent!" ); + } +#endif + } + + // there's a valid path within iPath now, so now we will fill the route array + // up with as many of the waypoints as it will hold. + + // don't copy ROUTE_SIZE entries if the path returned is shorter + // than ROUTE_SIZE!!! + if ( iResult < ROUTE_SIZE ) + { + iNumToCopy = iResult; + } + else + { + iNumToCopy = ROUTE_SIZE; + } + + for ( i = 0 ; i < iNumToCopy; i++ ) + { + m_Route[ i ].vecLocation = WorldGraph.m_pNodes[ iPath[ i ] ].m_vecOrigin; + m_Route[ i ].iType = bits_MF_TO_NODE; + } + + if ( iNumToCopy < ROUTE_SIZE ) + { + m_Route[ iNumToCopy ].vecLocation = vecDest; + m_Route[ iNumToCopy ].iType |= bits_MF_IS_GOAL; + } + + return TRUE; +} + +//========================================================= +// FindHintNode +//========================================================= +int CBaseMonster :: FindHintNode ( void ) +{ + int i; + TraceResult tr; + + if ( !WorldGraph.m_fGraphPresent ) + { + ALERT ( at_aiconsole, "find_hintnode: graph not ready!\n" ); + return NO_NODE; + } + + if ( WorldGraph.m_iLastActiveIdleSearch >= WorldGraph.m_cNodes ) + { + WorldGraph.m_iLastActiveIdleSearch = 0; + } + + for ( i = 0; i < WorldGraph.m_cNodes ; i++ ) + { + int nodeNumber = (i + WorldGraph.m_iLastActiveIdleSearch) % WorldGraph.m_cNodes; + CNode &node = WorldGraph.Node( nodeNumber ); + + if ( node.m_sHintType ) + { + // this node has a hint. Take it if it is visible, the monster likes it, and the monster has an animation to match the hint's activity. + if ( FValidateHintType ( node.m_sHintType ) ) + { + if ( !node.m_sHintActivity || LookupActivity ( node.m_sHintActivity ) != ACTIVITY_NOT_AVAILABLE ) + { + UTIL_TraceLine ( pev->origin + pev->view_ofs, node.m_vecOrigin + pev->view_ofs, ignore_monsters, ENT(pev), &tr ); + + if ( tr.flFraction == 1.0 ) + { + WorldGraph.m_iLastActiveIdleSearch = nodeNumber + 1; // next monster that searches for hint nodes will start where we left off. + return nodeNumber;// take it! + } + } + } + } + } + + WorldGraph.m_iLastActiveIdleSearch = 0;// start at the top of the list for the next search. + + return NO_NODE; +} + + +void CBaseMonster::ReportAIState( void ) +{ + ALERT_TYPE level = at_console; + + static const char *pStateNames[] = { "None", "Idle", "Combat", "Alert", "Hunt", "Prone", "Scripted", "Dead" }; + + ALERT( level, "%s: ", STRING(pev->classname) ); + if ( (int)m_MonsterState < ARRAYSIZE(pStateNames) ) + ALERT( level, "State: %s, ", pStateNames[m_MonsterState] ); + int i = 0; + while ( activity_map[i].type != 0 ) + { + if ( activity_map[i].type == (int)m_Activity ) + { + ALERT( level, "Activity %s, ", activity_map[i].name ); + break; + } + i++; + } + + if ( m_pSchedule ) + { + const char *pName = NULL; + pName = m_pSchedule->pName; + if ( !pName ) + pName = "Unknown"; + ALERT( level, "Schedule %s, ", pName ); + Task_t *pTask = GetTask(); + if ( pTask ) + ALERT( level, "Task %d (#%d), ", pTask->iTask, m_iScheduleIndex ); + } + else + ALERT( level, "No Schedule, " ); + + if ( m_hEnemy != NULL ) + ALERT( level, "\nEnemy is %s", STRING(m_hEnemy->pev->classname) ); + else + ALERT( level, "No enemy" ); + + if ( IsMoving() ) + { + ALERT( level, " Moving " ); + if ( m_flMoveWaitFinished > gpGlobals->time ) + ALERT( level, ": Stopped for %.2f. ", m_flMoveWaitFinished - gpGlobals->time ); + else if ( m_IdealActivity == GetStoppedActivity() ) + ALERT( level, ": In stopped anim. " ); + } + + CSquadMonster *pSquadMonster = MySquadMonsterPointer(); + + if ( pSquadMonster ) + { + if ( !pSquadMonster->InSquad() ) + { + ALERT ( level, "not " ); + } + + ALERT ( level, "In Squad, " ); + + if ( !pSquadMonster->IsLeader() ) + { + ALERT ( level, "not " ); + } + + ALERT ( level, "Leader." ); + } + + ALERT( level, "\n" ); + ALERT( level, "Yaw speed:%3.1f,Health: %3.1f\n", pev->yaw_speed, pev->health ); + if ( pev->spawnflags & SF_MONSTER_PRISONER ) + ALERT( level, " PRISONER! " ); + if ( pev->spawnflags & SF_MONSTER_PREDISASTER ) + ALERT( level, " Pre-Disaster! " ); + ALERT( level, "\n" ); +} + +//========================================================= +// KeyValue +// +// !!! netname entvar field is used in squadmonster for groupname!!! +//========================================================= +void CBaseMonster :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "TriggerTarget")) + { + m_iszTriggerTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "TriggerCondition") ) + { + m_iTriggerCondition = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CBaseToggle::KeyValue( pkvd ); + } +} + +//========================================================= +// FCheckAITrigger - checks the monster's AI Trigger Conditions, +// if there is a condition, then checks to see if condition is +// met. If yes, the monster's TriggerTarget is fired. +// +// Returns TRUE if the target is fired. +//========================================================= +BOOL CBaseMonster :: FCheckAITrigger ( void ) +{ + BOOL fFireTarget; + + if ( m_iTriggerCondition == AITRIGGER_NONE ) + { + // no conditions, so this trigger is never fired. + return FALSE; + } + + fFireTarget = FALSE; + + switch ( m_iTriggerCondition ) + { + case AITRIGGER_SEEPLAYER_ANGRY_AT_PLAYER: + if ( m_hEnemy != NULL && m_hEnemy->IsPlayer() && HasConditions ( bits_COND_SEE_ENEMY ) ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_SEEPLAYER_UNCONDITIONAL: + if ( HasConditions ( bits_COND_SEE_CLIENT ) ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_SEEPLAYER_NOT_IN_COMBAT: + if ( HasConditions ( bits_COND_SEE_CLIENT ) && + m_MonsterState != MONSTERSTATE_COMBAT && + m_MonsterState != MONSTERSTATE_PRONE && + m_MonsterState != MONSTERSTATE_SCRIPT) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_TAKEDAMAGE: + if ( m_afConditions & ( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_DEATH: + if ( pev->deadflag != DEAD_NO ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_HALFHEALTH: + if ( IsAlive() && pev->health <= ( pev->max_health / 2 ) ) + { + fFireTarget = TRUE; + } + break; +/* + + // !!!UNDONE - no persistant game state that allows us to track these two. + + case AITRIGGER_SQUADMEMBERDIE: + break; + case AITRIGGER_SQUADLEADERDIE: + break; +*/ + case AITRIGGER_HEARWORLD: + if ( m_afConditions & bits_COND_HEAR_SOUND && m_afSoundTypes & bits_SOUND_WORLD ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_HEARPLAYER: + if ( m_afConditions & bits_COND_HEAR_SOUND && m_afSoundTypes & bits_SOUND_PLAYER ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_HEARCOMBAT: + if ( m_afConditions & bits_COND_HEAR_SOUND && m_afSoundTypes & bits_SOUND_COMBAT ) + { + fFireTarget = TRUE; + } + break; + } + + if ( fFireTarget ) + { + // fire the target, then set the trigger conditions to NONE so we don't fire again + ALERT ( at_aiconsole, "AI Trigger Fire Target\n" ); + FireTargets( STRING( m_iszTriggerTarget ), this, this, USE_TOGGLE, 0 ); + m_iTriggerCondition = AITRIGGER_NONE; + return TRUE; + } + + return FALSE; +} + +//========================================================= +// CanPlaySequence - determines whether or not the monster +// can play the scripted sequence or AI sequence that is +// trying to possess it. If DisregardState is set, the monster +// will be sucked into the script no matter what state it is +// in. ONLY Scripted AI ents should allow this. +//========================================================= +int CBaseMonster :: CanPlaySequence( BOOL fDisregardMonsterState, int interruptLevel ) +{ + if ( m_pCine || !IsAlive() || m_MonsterState == MONSTERSTATE_PRONE ) + { + // monster is already running a scripted sequence or dead! + return FALSE; + } + + if ( fDisregardMonsterState ) + { + // ok to go, no matter what the monster state. (scripted AI) + return TRUE; + } + + if ( m_MonsterState == MONSTERSTATE_NONE || m_MonsterState == MONSTERSTATE_IDLE || m_IdealMonsterState == MONSTERSTATE_IDLE ) + { + // ok to go, but only in these states + return TRUE; + } + + if ( m_MonsterState == MONSTERSTATE_ALERT && interruptLevel >= SS_INTERRUPT_BY_NAME ) + return TRUE; + + // unknown situation + return FALSE; +} + + +//========================================================= +// FindLateralCover - attempts to locate a spot in the world +// directly to the left or right of the caller that will +// conceal them from view of pSightEnt +//========================================================= +#define COVER_CHECKS 5// how many checks are made +#define COVER_DELTA 48// distance between checks + +BOOL CBaseMonster :: FindLateralCover ( const Vector &vecThreat, const Vector &vecViewOffset ) +{ + TraceResult tr; + Vector vecBestOnLeft; + Vector vecBestOnRight; + Vector vecLeftTest; + Vector vecRightTest; + Vector vecStepRight; + int i; + + UTIL_MakeVectors ( pev->angles ); + vecStepRight = gpGlobals->v_right * COVER_DELTA; + vecStepRight.z = 0; + + vecLeftTest = vecRightTest = pev->origin; + + for ( i = 0 ; i < COVER_CHECKS ; i++ ) + { + vecLeftTest = vecLeftTest - vecStepRight; + vecRightTest = vecRightTest + vecStepRight; + + // it's faster to check the SightEnt's visibility to the potential spot than to check the local move, so we do that first. + UTIL_TraceLine( vecThreat + vecViewOffset, vecLeftTest + pev->view_ofs, ignore_monsters, ignore_glass, ENT(pev)/*pentIgnore*/, &tr); + + if (tr.flFraction != 1.0) + { + if ( FValidateCover ( vecLeftTest ) && CheckLocalMove( pev->origin, vecLeftTest, NULL, NULL ) == LOCALMOVE_VALID ) + { + if ( MoveToLocation( ACT_RUN, 0, vecLeftTest ) ) + { + return TRUE; + } + } + } + + // it's faster to check the SightEnt's visibility to the potential spot than to check the local move, so we do that first. + UTIL_TraceLine(vecThreat + vecViewOffset, vecRightTest + pev->view_ofs, ignore_monsters, ignore_glass, ENT(pev)/*pentIgnore*/, &tr); + + if ( tr.flFraction != 1.0 ) + { + if ( FValidateCover ( vecRightTest ) && CheckLocalMove( pev->origin, vecRightTest, NULL, NULL ) == LOCALMOVE_VALID ) + { + if ( MoveToLocation( ACT_RUN, 0, vecRightTest ) ) + { + return TRUE; + } + } + } + } + + return FALSE; +} + + +Vector CBaseMonster :: ShootAtEnemy( const Vector &shootOrigin ) +{ + CBaseEntity *pEnemy = m_hEnemy; + + if ( pEnemy ) + { + return ( (pEnemy->BodyTarget( shootOrigin ) - pEnemy->pev->origin) + m_vecEnemyLKP - shootOrigin ).Normalize(); + } + else + return gpGlobals->v_forward; +} + + + +//========================================================= +// FacingIdeal - tells us if a monster is facing its ideal +// yaw. Created this function because many spots in the +// code were checking the yawdiff against this magic +// number. Nicer to have it in one place if we're gonna +// be stuck with it. +//========================================================= +BOOL CBaseMonster :: FacingIdeal( void ) +{ + if ( fabs( FlYawDiff() ) <= 0.006 )//!!!BUGBUG - no magic numbers!!! + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// FCanActiveIdle +//========================================================= +BOOL CBaseMonster :: FCanActiveIdle ( void ) +{ + /* + if ( m_MonsterState == MONSTERSTATE_IDLE && m_IdealMonsterState == MONSTERSTATE_IDLE && !IsMoving() ) + { + return TRUE; + } + */ + return FALSE; +} + + +void CBaseMonster::PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ) +{ + if ( pszSentence && IsAlive() ) + { + if ( pszSentence[0] == '!' ) + EMIT_SOUND_DYN( edict(), CHAN_VOICE, pszSentence, volume, attenuation, 0, PITCH_NORM ); + else + SENTENCEG_PlayRndSz( edict(), pszSentence, volume, attenuation, 0, PITCH_NORM ); + } +} + + +void CBaseMonster::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ) +{ + PlaySentence( pszSentence, duration, volume, attenuation ); +} + + +void CBaseMonster::SentenceStop( void ) +{ + EMIT_SOUND( edict(), CHAN_VOICE, "common/null.wav", 1.0, ATTN_IDLE ); +} + + +void CBaseMonster::CorpseFallThink( void ) +{ + if ( pev->flags & FL_ONGROUND ) + { + SetThink ( NULL ); + + SetSequenceBox( ); + UTIL_SetOrigin( pev, pev->origin );// link into world. + } + else + pev->nextthink = gpGlobals->time + 0.1; +} + +// Call after animation/pose is set up +void CBaseMonster :: MonsterInitDead( void ) +{ + InitBoneControllers(); + + pev->solid = SOLID_BBOX; + pev->movetype = MOVETYPE_TOSS;// so he'll fall to ground + + pev->frame = 0; + ResetSequenceInfo( ); + pev->framerate = 0; + + // Copy health + pev->max_health = pev->health; + pev->deadflag = DEAD_DEAD; + + UTIL_SetSize(pev, g_vecZero, g_vecZero ); + UTIL_SetOrigin( pev, pev->origin ); + + // Setup health counters, etc. + BecomeDead(); + SetThink( &CBaseMonster::CorpseFallThink ); + pev->nextthink = gpGlobals->time + 0.5; +} + +//========================================================= +// BBoxIsFlat - check to see if the monster's bounding box +// is lying flat on a surface (traces from all four corners +// are same length.) +//========================================================= +BOOL CBaseMonster :: BBoxFlat ( void ) +{ + TraceResult tr; + Vector vecPoint; + float flXSize, flYSize; + float flLength; + float flLength2; + + flXSize = pev->size.x / 2; + flYSize = pev->size.y / 2; + + vecPoint.x = pev->origin.x + flXSize; + vecPoint.y = pev->origin.y + flYSize; + vecPoint.z = pev->origin.z; + + UTIL_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), ignore_monsters, ENT(pev), &tr ); + flLength = (vecPoint - tr.vecEndPos).Length(); + + vecPoint.x = pev->origin.x - flXSize; + vecPoint.y = pev->origin.y - flYSize; + + UTIL_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), ignore_monsters, ENT(pev), &tr ); + flLength2 = (vecPoint - tr.vecEndPos).Length(); + if ( flLength2 > flLength ) + { + return FALSE; + } + flLength = flLength2; + + vecPoint.x = pev->origin.x - flXSize; + vecPoint.y = pev->origin.y + flYSize; + UTIL_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), ignore_monsters, ENT(pev), &tr ); + flLength2 = (vecPoint - tr.vecEndPos).Length(); + if ( flLength2 > flLength ) + { + return FALSE; + } + flLength = flLength2; + + vecPoint.x = pev->origin.x + flXSize; + vecPoint.y = pev->origin.y - flYSize; + UTIL_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), ignore_monsters, ENT(pev), &tr ); + flLength2 = (vecPoint - tr.vecEndPos).Length(); + if ( flLength2 > flLength ) + { + return FALSE; + } + flLength = flLength2; + + return TRUE; +} + +//========================================================= +// Get Enemy - tries to find the best suitable enemy for the monster. +//========================================================= +BOOL CBaseMonster :: GetEnemy ( void ) +{ + CBaseEntity *pNewEnemy; + + if ( HasConditions(bits_COND_SEE_HATE | bits_COND_SEE_DISLIKE | bits_COND_SEE_NEMESIS) ) + { + pNewEnemy = BestVisibleEnemy(); + + if ( pNewEnemy != m_hEnemy && pNewEnemy != NULL) + { + // DO NOT mess with the monster's m_hEnemy pointer unless the schedule the monster is currently running will be interrupted + // by COND_NEW_ENEMY. This will eliminate the problem of monsters getting a new enemy while they are in a schedule that doesn't care, + // and then not realizing it by the time they get to a schedule that does. I don't feel this is a good permanent fix. + + if ( m_pSchedule ) + { + if ( m_pSchedule->iInterruptMask & bits_COND_NEW_ENEMY ) + { + PushEnemy( m_hEnemy, m_vecEnemyLKP ); + SetConditions(bits_COND_NEW_ENEMY); + m_hEnemy = pNewEnemy; + m_vecEnemyLKP = m_hEnemy->pev->origin; + } + // if the new enemy has an owner, take that one as well + if (pNewEnemy->pev->owner != NULL) + { + CBaseEntity *pOwner = GetMonsterPointer( pNewEnemy->pev->owner ); + if ( pOwner && (pOwner->pev->flags & FL_MONSTER) && IRelationship( pOwner ) != R_NO ) + PushEnemy( pOwner, m_vecEnemyLKP ); + } + } + } + } + + // remember old enemies + if (m_hEnemy == NULL && PopEnemy( )) + { + if ( m_pSchedule ) + { + if ( m_pSchedule->iInterruptMask & bits_COND_NEW_ENEMY ) + { + SetConditions(bits_COND_NEW_ENEMY); + } + } + } + + if ( m_hEnemy != NULL ) + { + // monster has an enemy. + return TRUE; + } + + return FALSE;// monster has no enemy +} + + +//========================================================= +// DropItem - dead monster drops named item +//========================================================= +CBaseEntity* CBaseMonster :: DropItem ( char *pszItemName, const Vector &vecPos, const Vector &vecAng ) +{ + if ( !pszItemName ) + { + ALERT ( at_console, "DropItem() - No item name!\n" ); + return NULL; + } + + CBaseEntity *pItem = CBaseEntity::Create( pszItemName, vecPos, vecAng, edict() ); + + if ( pItem ) + { + // do we want this behavior to be default?! (sjb) + pItem->pev->velocity = pev->velocity; + pItem->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 0, 100 ), 0 ); + return pItem; + } + else + { + ALERT ( at_console, "DropItem() - Didn't create!\n" ); + return FALSE; + } + +} + + +BOOL CBaseMonster :: ShouldFadeOnDeath( void ) +{ + // if flagged to fade out or I have an owner (I came from a monster spawner) + if ( (pev->spawnflags & SF_MONSTER_FADECORPSE) || !FNullEnt( pev->owner ) ) + return TRUE; + + return FALSE; +} diff --git a/dlls/monsters.h b/dlls/monsters.h new file mode 100644 index 0000000..591ea52 --- /dev/null +++ b/dlls/monsters.h @@ -0,0 +1,183 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef MONSTERS_H +#include "skill.h" +#define MONSTERS_H + +/* + +===== monsters.h ======================================================== + + Header file for monster-related utility code + +*/ + +// CHECKLOCALMOVE result types +#define LOCALMOVE_INVALID 0 // move is not possible +#define LOCALMOVE_INVALID_DONT_TRIANGULATE 1 // move is not possible, don't try to triangulate +#define LOCALMOVE_VALID 2 // move is possible + +// Hit Group standards +#define HITGROUP_GENERIC 0 +#define HITGROUP_HEAD 1 +#define HITGROUP_CHEST 2 +#define HITGROUP_STOMACH 3 +#define HITGROUP_LEFTARM 4 +#define HITGROUP_RIGHTARM 5 +#define HITGROUP_LEFTLEG 6 +#define HITGROUP_RIGHTLEG 7 + + +// Monster Spawnflags +#define SF_MONSTER_WAIT_TILL_SEEN 1// spawnflag that makes monsters wait until player can see them before attacking. +#define SF_MONSTER_GAG 2 // no idle noises from this monster +#define SF_MONSTER_HITMONSTERCLIP 4 +// 8 +#define SF_MONSTER_PRISONER 16 // monster won't attack anyone, no one will attacke him. +// 32 +// 64 +#define SF_MONSTER_WAIT_FOR_SCRIPT 128 //spawnflag that makes monsters wait to check for attacking until the script is done or they've been attacked +#define SF_MONSTER_PREDISASTER 256 //this is a predisaster scientist or barney. Influences how they speak. +#define SF_MONSTER_FADECORPSE 512 // Fade out corpse after death +#define SF_MONSTER_FALL_TO_GROUND 0x80000000 + +// specialty spawnflags +#define SF_MONSTER_TURRET_AUTOACTIVATE 32 +#define SF_MONSTER_TURRET_STARTINACTIVE 64 +#define SF_MONSTER_WAIT_UNTIL_PROVOKED 64 // don't attack the player unless provoked + + + +// MoveToOrigin stuff +#define MOVE_START_TURN_DIST 64 // when this far away from moveGoal, start turning to face next goal +#define MOVE_STUCK_DIST 32 // if a monster can't step this far, it is stuck. + + +// MoveToOrigin stuff +#define MOVE_NORMAL 0// normal move in the direction monster is facing +#define MOVE_STRAFE 1// moves in direction specified, no matter which way monster is facing + +// spawn flags 256 and above are already taken by the engine +extern void UTIL_MoveToOrigin( edict_t* pent, const Vector &vecGoal, float flDist, int iMoveType ); + +Vector VecCheckToss ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flGravityAdj = 1.0 ); +Vector VecCheckThrow ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flSpeed, float flGravityAdj = 1.0 ); +extern DLL_GLOBAL Vector g_vecAttackDir; +extern DLL_GLOBAL CONSTANT float g_flMeleeRange; +extern DLL_GLOBAL CONSTANT float g_flMediumRange; +extern DLL_GLOBAL CONSTANT float g_flLongRange; +extern void EjectBrass (const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int model, int soundtype ); +extern void ExplodeModel( const Vector &vecOrigin, float speed, int model, int count ); + +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget ); +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget, Vector &vecTargetOrigin, float flSize = 0.0 ); + +// monster to monster relationship types +#define R_AL -2 // (ALLY) pals. Good alternative to R_NO when applicable. +#define R_FR -1// (FEAR)will run +#define R_NO 0// (NO RELATIONSHIP) disregard +#define R_DL 1// (DISLIKE) will attack +#define R_HT 2// (HATE)will attack this character instead of any visible DISLIKEd characters +#define R_NM 3// (NEMESIS) A monster Will ALWAYS attack its nemsis, no matter what + + +// these bits represent the monster's memory +#define MEMORY_CLEAR 0 +#define bits_MEMORY_PROVOKED ( 1 << 0 )// right now only used for houndeyes. +#define bits_MEMORY_INCOVER ( 1 << 1 )// monster knows it is in a covered position. +#define bits_MEMORY_SUSPICIOUS ( 1 << 2 )// Ally is suspicious of the player, and will move to provoked more easily +#define bits_MEMORY_PATH_FINISHED ( 1 << 3 )// Finished monster path (just used by big momma for now) +#define bits_MEMORY_ON_PATH ( 1 << 4 )// Moving on a path +#define bits_MEMORY_MOVE_FAILED ( 1 << 5 )// Movement has already failed +#define bits_MEMORY_FLINCHED ( 1 << 6 )// Has already flinched +#define bits_MEMORY_KILLED ( 1 << 7 )// HACKHACK -- remember that I've already called my Killed() +#define bits_MEMORY_CUSTOM4 ( 1 << 28 ) // Monster-specific memory +#define bits_MEMORY_CUSTOM3 ( 1 << 29 ) // Monster-specific memory +#define bits_MEMORY_CUSTOM2 ( 1 << 30 ) // Monster-specific memory +#define bits_MEMORY_CUSTOM1 ( 1 << 31 ) // Monster-specific memory + +// trigger conditions for scripted AI +// these MUST match the CHOICES interface in halflife.fgd for the base monster +enum +{ + AITRIGGER_NONE = 0, + AITRIGGER_SEEPLAYER_ANGRY_AT_PLAYER, + AITRIGGER_TAKEDAMAGE, + AITRIGGER_HALFHEALTH, + AITRIGGER_DEATH, + AITRIGGER_SQUADMEMBERDIE, + AITRIGGER_SQUADLEADERDIE, + AITRIGGER_HEARWORLD, + AITRIGGER_HEARPLAYER, + AITRIGGER_HEARCOMBAT, + AITRIGGER_SEEPLAYER_UNCONDITIONAL, + AITRIGGER_SEEPLAYER_NOT_IN_COMBAT, +}; +/* + 0 : "No Trigger" + 1 : "See Player" + 2 : "Take Damage" + 3 : "50% Health Remaining" + 4 : "Death" + 5 : "Squad Member Dead" + 6 : "Squad Leader Dead" + 7 : "Hear World" + 8 : "Hear Player" + 9 : "Hear Combat" +*/ + +// +// A gib is a chunk of a body, or a piece of wood/metal/rocks/etc. +// +class CGib : public CBaseEntity +{ +public: + void Spawn( const char *szGibModel ); + void EXPORT BounceGibTouch ( CBaseEntity *pOther ); + void EXPORT StickyGibTouch ( CBaseEntity *pOther ); + void EXPORT WaitTillLand( void ); + void LimitVelocity( void ); + + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DONT_SAVE; } + static void SpawnHeadGib( entvars_t *pevVictim ); + static void SpawnRandomGibs( entvars_t *pevVictim, int cGibs, int human ); + static void SpawnStickyGibs( entvars_t *pevVictim, Vector vecOrigin, int cGibs ); + + int m_bloodColor; + int m_cBloodDecals; + int m_material; + float m_lifeTime; +}; + + +#define CUSTOM_SCHEDULES\ + virtual Schedule_t *ScheduleFromName( const char *pName );\ + static Schedule_t *m_scheduleList[]; + +#define DEFINE_CUSTOM_SCHEDULES(derivedClass)\ + Schedule_t *derivedClass::m_scheduleList[] = + +#define IMPLEMENT_CUSTOM_SCHEDULES(derivedClass, baseClass)\ + Schedule_t *derivedClass::ScheduleFromName( const char *pName )\ + {\ + Schedule_t *pSchedule = ScheduleInList( pName, m_scheduleList, ARRAYSIZE(m_scheduleList) );\ + if ( !pSchedule )\ + return baseClass::ScheduleFromName(pName);\ + return pSchedule;\ + } + + + +#endif //MONSTERS_H diff --git a/dlls/monsterstate.cpp b/dlls/monsterstate.cpp new file mode 100644 index 0000000..8b2fd8f --- /dev/null +++ b/dlls/monsterstate.cpp @@ -0,0 +1,234 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// monsterstate.cpp - base class monster functions for +// controlling core AI. +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "nodes.h" +#include "monsters.h" +#include "animation.h" +#include "saverestore.h" +#include "soundent.h" + +//========================================================= +// SetState +//========================================================= +void CBaseMonster :: SetState ( MONSTERSTATE State ) +{ +/* + if ( State != m_MonsterState ) + { + ALERT ( at_aiconsole, "State Changed to %d\n", State ); + } +*/ + + switch( State ) + { + + // Drop enemy pointers when going to idle + case MONSTERSTATE_IDLE: + + if ( m_hEnemy != NULL ) + { + m_hEnemy = NULL;// not allowed to have an enemy anymore. + ALERT ( at_aiconsole, "Stripped\n" ); + } + break; + } + + m_MonsterState = State; + m_IdealMonsterState = State; +} + +//========================================================= +// RunAI +//========================================================= +void CBaseMonster :: RunAI ( void ) +{ + // to test model's eye height + //UTIL_ParticleEffect ( pev->origin + pev->view_ofs, g_vecZero, 255, 10 ); + + // IDLE sound permitted in ALERT state is because monsters were silent in ALERT state. Only play IDLE sound in IDLE state + // once we have sounds for that state. + if ( ( m_MonsterState == MONSTERSTATE_IDLE || m_MonsterState == MONSTERSTATE_ALERT ) && RANDOM_LONG(0,99) == 0 && !(pev->flags & SF_MONSTER_GAG) ) + { + IdleSound(); + } + + if ( m_MonsterState != MONSTERSTATE_NONE && + m_MonsterState != MONSTERSTATE_PRONE && + m_MonsterState != MONSTERSTATE_DEAD )// don't bother with this crap if monster is prone. + { + // collect some sensory Condition information. + // don't let monsters outside of the player's PVS act up, or most of the interesting + // things will happen before the player gets there! + // UPDATE: We now let COMBAT state monsters think and act fully outside of player PVS. This allows the player to leave + // an area where monsters are fighting, and the fight will continue. + if ( !FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) || ( m_MonsterState == MONSTERSTATE_COMBAT ) ) + { + Look( m_flDistLook ); + Listen();// check for audible sounds. + + // now filter conditions. + ClearConditions( IgnoreConditions() ); + + GetEnemy(); + } + + // do these calculations if monster has an enemy. + if ( m_hEnemy != NULL ) + { + CheckEnemy( m_hEnemy ); + } + + CheckAmmo(); + } + + FCheckAITrigger(); + + PrescheduleThink(); + + MaintainSchedule(); + + // if the monster didn't use these conditions during the above call to MaintainSchedule() or CheckAITrigger() + // we throw them out cause we don't want them sitting around through the lifespan of a schedule + // that doesn't use them. + m_afConditions &= ~( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ); +} + +//========================================================= +// GetIdealState - surveys the Conditions information available +// and finds the best new state for a monster. +//========================================================= +MONSTERSTATE CBaseMonster :: GetIdealState ( void ) +{ + int iConditions; + + iConditions = IScheduleFlags(); + + // If no schedule conditions, the new ideal state is probably the reason we're in here. + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: + + /* + IDLE goes to ALERT upon hearing a sound + -IDLE goes to ALERT upon being injured + IDLE goes to ALERT upon seeing food + -IDLE goes to COMBAT upon sighting an enemy + IDLE goes to HUNT upon smelling food + */ + { + if ( iConditions & bits_COND_NEW_ENEMY ) + { + // new enemy! This means an idle monster has seen someone it dislikes, or + // that a monster in combat has found a more suitable target to attack + m_IdealMonsterState = MONSTERSTATE_COMBAT; + } + else if ( iConditions & bits_COND_LIGHT_DAMAGE ) + { + MakeIdealYaw ( m_vecEnemyLKP ); + m_IdealMonsterState = MONSTERSTATE_ALERT; + } + else if ( iConditions & bits_COND_HEAVY_DAMAGE ) + { + MakeIdealYaw ( m_vecEnemyLKP ); + m_IdealMonsterState = MONSTERSTATE_ALERT; + } + else if ( iConditions & bits_COND_HEAR_SOUND ) + { + CSound *pSound; + + pSound = PBestSound(); + ASSERT( pSound != NULL ); + if ( pSound ) + { + MakeIdealYaw ( pSound->m_vecOrigin ); + if ( pSound->m_iType & (bits_SOUND_COMBAT|bits_SOUND_DANGER) ) + m_IdealMonsterState = MONSTERSTATE_ALERT; + } + } + else if ( iConditions & (bits_COND_SMELL | bits_COND_SMELL_FOOD) ) + { + m_IdealMonsterState = MONSTERSTATE_ALERT; + } + + break; + } + case MONSTERSTATE_ALERT: + /* + ALERT goes to IDLE upon becoming bored + -ALERT goes to COMBAT upon sighting an enemy + ALERT goes to HUNT upon hearing a noise + */ + { + if ( iConditions & (bits_COND_NEW_ENEMY|bits_COND_SEE_ENEMY) ) + { + // see an enemy we MUST attack + m_IdealMonsterState = MONSTERSTATE_COMBAT; + } + else if ( iConditions & bits_COND_HEAR_SOUND ) + { + m_IdealMonsterState = MONSTERSTATE_ALERT; + CSound *pSound = PBestSound(); + ASSERT( pSound != NULL ); + if ( pSound ) + MakeIdealYaw ( pSound->m_vecOrigin ); + } + break; + } + case MONSTERSTATE_COMBAT: + /* + COMBAT goes to HUNT upon losing sight of enemy + COMBAT goes to ALERT upon death of enemy + */ + { + if ( m_hEnemy == NULL ) + { + m_IdealMonsterState = MONSTERSTATE_ALERT; + // pev->effects = EF_BRIGHTFIELD; + ALERT ( at_aiconsole, "***Combat state with no enemy!\n" ); + } + break; + } + case MONSTERSTATE_HUNT: + /* + HUNT goes to ALERT upon seeing food + HUNT goes to ALERT upon being injured + HUNT goes to IDLE if goal touched + HUNT goes to COMBAT upon seeing enemy + */ + { + break; + } + case MONSTERSTATE_SCRIPT: + if ( iConditions & (bits_COND_TASK_FAILED|bits_COND_LIGHT_DAMAGE|bits_COND_HEAVY_DAMAGE) ) + { + ExitScriptedSequence(); // This will set the ideal state + } + break; + + case MONSTERSTATE_DEAD: + m_IdealMonsterState = MONSTERSTATE_DEAD; + break; + } + + return m_IdealMonsterState; +} + diff --git a/dlls/mortar.cpp b/dlls/mortar.cpp new file mode 100644 index 0000000..d275f38 --- /dev/null +++ b/dlls/mortar.cpp @@ -0,0 +1,323 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== mortar.cpp ======================================================== + + the "LaBuznik" mortar device + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "weapons.h" +#include "decals.h" +#include "soundent.h" + +class CFuncMortarField : public CBaseToggle +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + // Bmodels don't go across transitions + virtual int ObjectCaps( void ) { return CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + void EXPORT FieldUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int m_iszXController; + int m_iszYController; + float m_flSpread; + float m_flDelay; + int m_iCount; + int m_fControl; +}; + +LINK_ENTITY_TO_CLASS( func_mortar_field, CFuncMortarField ); + +TYPEDESCRIPTION CFuncMortarField::m_SaveData[] = +{ + DEFINE_FIELD( CFuncMortarField, m_iszXController, FIELD_STRING ), + DEFINE_FIELD( CFuncMortarField, m_iszYController, FIELD_STRING ), + DEFINE_FIELD( CFuncMortarField, m_flSpread, FIELD_FLOAT ), + DEFINE_FIELD( CFuncMortarField, m_flDelay, FIELD_FLOAT ), + DEFINE_FIELD( CFuncMortarField, m_iCount, FIELD_INTEGER ), + DEFINE_FIELD( CFuncMortarField, m_fControl, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CFuncMortarField, CBaseToggle ); + + +void CFuncMortarField :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszXController")) + { + m_iszXController = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszYController")) + { + m_iszYController = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flSpread")) + { + m_flSpread = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fControl")) + { + m_fControl = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iCount")) + { + m_iCount = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } +} + + +// Drop bombs from above +void CFuncMortarField :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->movetype = MOVETYPE_NONE; + SetBits( pev->effects, EF_NODRAW ); + SetUse( & CFuncMortarField::FieldUse ); + Precache(); +} + + +void CFuncMortarField :: Precache( void ) +{ + PRECACHE_SOUND ("weapons/mortar.wav"); + PRECACHE_SOUND ("weapons/mortarhit.wav"); + PRECACHE_MODEL( "sprites/lgtning.spr" ); +} + + +// If connected to a table, then use the table controllers, else hit where the trigger is. +void CFuncMortarField :: FieldUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + Vector vecStart; + + vecStart.x = RANDOM_FLOAT( pev->mins.x, pev->maxs.x ); + vecStart.y = RANDOM_FLOAT( pev->mins.y, pev->maxs.y ); + vecStart.z = pev->maxs.z; + + switch( m_fControl ) + { + case 0: // random + break; + case 1: // Trigger Activator + if (pActivator != NULL) + { + vecStart.x = pActivator->pev->origin.x; + vecStart.y = pActivator->pev->origin.y; + } + break; + case 2: // table + { + CBaseEntity *pController; + + if (!FStringNull(m_iszXController)) + { + pController = UTIL_FindEntityByTargetname( NULL, STRING(m_iszXController)); + if (pController != NULL) + { + vecStart.x = pev->mins.x + pController->pev->ideal_yaw * (pev->size.x); + } + } + if (!FStringNull(m_iszYController)) + { + pController = UTIL_FindEntityByTargetname( NULL, STRING(m_iszYController)); + if (pController != NULL) + { + vecStart.y = pev->mins.y + pController->pev->ideal_yaw * (pev->size.y); + } + } + } + break; + } + + int pitch = RANDOM_LONG(95,124); + + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "weapons/mortar.wav", 1.0, ATTN_NONE, 0, pitch); + + float t = 2.5; + for (int i = 0; i < m_iCount; i++) + { + Vector vecSpot = vecStart; + vecSpot.x += RANDOM_FLOAT( -m_flSpread, m_flSpread ); + vecSpot.y += RANDOM_FLOAT( -m_flSpread, m_flSpread ); + + TraceResult tr; + UTIL_TraceLine( vecSpot, vecSpot + Vector( 0, 0, -1 ) * 4096, ignore_monsters, ENT(pev), &tr ); + + edict_t *pentOwner = NULL; + if (pActivator) pentOwner = pActivator->edict(); + + CBaseEntity *pMortar = Create("monster_mortar", tr.vecEndPos, Vector( 0, 0, 0 ), pentOwner ); + pMortar->pev->nextthink = gpGlobals->time + t; + t += RANDOM_FLOAT( 0.2, 0.5 ); + + if (i == 0) + CSoundEnt::InsertSound ( bits_SOUND_DANGER, tr.vecEndPos, 400, 0.3 ); + } +} + + +class CMortar : public CGrenade +{ +public: + void Spawn( void ); + void Precache( void ); + + void EXPORT MortarExplode( void ); + + int m_spriteTexture; +}; + +LINK_ENTITY_TO_CLASS( monster_mortar, CMortar ); + +void CMortar::Spawn( ) +{ + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT; + + pev->dmg = 200; + + SetThink( &CMortar::MortarExplode ); + pev->nextthink = 0; + + Precache( ); + + +} + + +void CMortar::Precache( ) +{ + m_spriteTexture = PRECACHE_MODEL( "sprites/lgtning.spr" ); +} + +void CMortar::MortarExplode( void ) +{ +#if 1 + // mortar beam + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS ); + WRITE_COORD(pev->origin.x); + WRITE_COORD(pev->origin.y); + WRITE_COORD(pev->origin.z); + WRITE_COORD(pev->origin.x); + WRITE_COORD(pev->origin.y); + WRITE_COORD(pev->origin.z + 1024); + WRITE_SHORT(m_spriteTexture ); + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 1 ); // life + WRITE_BYTE( 40 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 160 ); // r, g, b + WRITE_BYTE( 100 ); // r, g, b + WRITE_BYTE( 128 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); +#endif + +#if 0 + // blast circle + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMTORUS); + WRITE_COORD(pev->origin.x); + WRITE_COORD(pev->origin.y); + WRITE_COORD(pev->origin.z + 32); + WRITE_COORD(pev->origin.x); + WRITE_COORD(pev->origin.y); + WRITE_COORD(pev->origin.z + 32 + pev->dmg * 2 / .2); // reach damage radius over .3 seconds + WRITE_SHORT(m_spriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 2 ); // life + WRITE_BYTE( 12 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 160 ); // r, g, b + WRITE_BYTE( 100 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); +#endif + + TraceResult tr; + UTIL_TraceLine( pev->origin + Vector( 0, 0, 1024 ), pev->origin - Vector( 0, 0, 1024 ), dont_ignore_monsters, ENT(pev), &tr ); + + Explode( &tr, DMG_BLAST | DMG_MORTAR ); + UTIL_ScreenShake( tr.vecEndPos, 25.0, 150.0, 1.0, 750 ); + +#if 0 + int pitch = RANDOM_LONG(95,124); + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "weapons/mortarhit.wav", 1.0, 0.55, 0, pitch); + + // ForceSound( SNDRADIUS_MP5, bits_SOUND_COMBAT ); + + // ExplodeModel( pev->origin, 400, g_sModelIndexShrapnel, 30 ); + + RadiusDamage ( pev, VARS(pev->owner), pev->dmg, CLASS_NONE, DMG_BLAST ); + + /* + if ( RANDOM_FLOAT ( 0 , 1 ) < 0.5 ) + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH1 ); + } + else + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH2 ); + } + */ + + SetThink( &CMortar::SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; +#endif + +} + + +#if 0 +void CMortar::ShootTimed( EVARS *pevOwner, Vector vecStart, float time ) +{ + CMortar *pMortar = GetClassPtr( (CMortar *)NULL ); + pMortar->Spawn(); + + TraceResult tr; + UTIL_TraceLine( vecStart, vecStart + Vector( 0, 0, -1 ) * 4096, ignore_monsters, ENT(pMortar->pev), &tr ); + + pMortar->pev->nextthink = gpGlobals->time + time; + + UTIL_SetOrigin( pMortar->pev, tr.vecEndPos ); +} +#endif diff --git a/dlls/mp5.cpp b/dlls/mp5.cpp new file mode 100644 index 0000000..6771f82 --- /dev/null +++ b/dlls/mp5.cpp @@ -0,0 +1,385 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "soundent.h" +#include "gamerules.h" + +enum mp5_e +{ + MP5_LONGIDLE = 0, + MP5_IDLE1, + MP5_LAUNCH, + MP5_RELOAD, + MP5_DEPLOY, + MP5_FIRE1, + MP5_FIRE2, + MP5_FIRE3, +}; + + + +LINK_ENTITY_TO_CLASS( weapon_mp5, CMP5 ); +LINK_ENTITY_TO_CLASS( weapon_9mmAR, CMP5 ); + + +//========================================================= +//========================================================= +int CMP5::SecondaryAmmoIndex( void ) +{ + return m_iSecondaryAmmoType; +} + +void CMP5::Spawn( ) +{ + pev->classname = MAKE_STRING("weapon_9mmAR"); // hack to allow for old names + Precache( ); + SET_MODEL(ENT(pev), "models/w_9mmAR.mdl"); + m_iId = WEAPON_MP5; + + m_iDefaultAmmo = MP5_DEFAULT_GIVE; + + FallInit();// get ready to fall down. +} + + +void CMP5::Precache( void ) +{ + PRECACHE_MODEL("models/v_9mmAR.mdl"); + PRECACHE_MODEL("models/w_9mmAR.mdl"); + PRECACHE_MODEL("models/p_9mmAR.mdl"); + + m_iShell = PRECACHE_MODEL ("models/shell.mdl");// brass shellTE_MODEL + + PRECACHE_MODEL("models/grenade.mdl"); // grenade + + PRECACHE_MODEL("models/w_9mmARclip.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + + PRECACHE_SOUND("items/clipinsert1.wav"); + PRECACHE_SOUND("items/cliprelease1.wav"); + + PRECACHE_SOUND ("weapons/hks1.wav");// H to the K + PRECACHE_SOUND ("weapons/hks2.wav");// H to the K + PRECACHE_SOUND ("weapons/hks3.wav");// H to the K + + PRECACHE_SOUND( "weapons/glauncher.wav" ); + PRECACHE_SOUND( "weapons/glauncher2.wav" ); + + PRECACHE_SOUND ("weapons/357_cock1.wav"); + + m_usMP5 = PRECACHE_EVENT( 1, "events/mp5.sc" ); + m_usMP52 = PRECACHE_EVENT( 1, "events/mp52.sc" ); +} + +int CMP5::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "9mm"; + p->iMaxAmmo1 = _9MM_MAX_CARRY; + p->pszAmmo2 = "ARgrenades"; + p->iMaxAmmo2 = M203_GRENADE_MAX_CARRY; + p->iMaxClip = MP5_MAX_CLIP; + p->iSlot = 2; + p->iPosition = 0; + p->iFlags = 0; + p->iId = m_iId = WEAPON_MP5; + p->iWeight = MP5_WEIGHT; + + return 1; +} + +int CMP5::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + +BOOL CMP5::Deploy( ) +{ + return DefaultDeploy( "models/v_9mmAR.mdl", "models/p_9mmAR.mdl", MP5_DEPLOY, "mp5" ); +} + + +void CMP5::PrimaryAttack() +{ + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = 0.15; + return; + } + + if (m_iClip <= 0) + { + PlayEmptySound(); + m_flNextPrimaryAttack = 0.15; + return; + } + + m_pPlayer->m_iWeaponVolume = NORMAL_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + + m_iClip--; + + + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = m_pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); + Vector vecDir; + +#ifdef CLIENT_DLL + if ( !bIsMultiplayer() ) +#else + if ( !g_pGameRules->IsMultiplayer() ) +#endif + { + // optimized multiplayer. Widened to make it easier to hit a moving player + vecDir = m_pPlayer->FireBulletsPlayer( 1, vecSrc, vecAiming, VECTOR_CONE_6DEGREES, 8192, BULLET_PLAYER_MP5, 2, 0, m_pPlayer->pev, m_pPlayer->random_seed ); + } + else + { + // single player spread + vecDir = m_pPlayer->FireBulletsPlayer( 1, vecSrc, vecAiming, VECTOR_CONE_3DEGREES, 8192, BULLET_PLAYER_MP5, 2, 0, m_pPlayer->pev, m_pPlayer->random_seed ); + } + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usMP5, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, vecDir.x, vecDir.y, 0, 0, 0, 0 ); + + if (!m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + // HEV suit - indicate out of ammo condition + m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + + m_flNextPrimaryAttack = GetNextAttackDelay(0.1); + + if ( m_flNextPrimaryAttack < UTIL_WeaponTimeBase() ) + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.1; + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); +} + + + +void CMP5::SecondaryAttack( void ) +{ + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = 0.15; + return; + } + + if (m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] == 0) + { + PlayEmptySound( ); + return; + } + + m_pPlayer->m_iWeaponVolume = NORMAL_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = BRIGHT_GUN_FLASH; + + m_pPlayer->m_iExtraSoundTypes = bits_SOUND_DANGER; + m_pPlayer->m_flStopExtraSoundTime = UTIL_WeaponTimeBase() + 0.2; + + m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType]--; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + + // we don't add in player velocity anymore. + CGrenade::ShootContact( m_pPlayer->pev, + m_pPlayer->pev->origin + m_pPlayer->pev->view_ofs + gpGlobals->v_forward * 16, + gpGlobals->v_forward * 800 ); + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT( flags, m_pPlayer->edict(), m_usMP52 ); + + m_flNextPrimaryAttack = GetNextAttackDelay(1); + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 5;// idle pretty soon after shooting. + + if (!m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType]) + // HEV suit - indicate out of ammo condition + m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); +} + +void CMP5::Reload( void ) +{ + if ( m_pPlayer->ammo_9mm <= 0 ) + return; + + DefaultReload( MP5_MAX_CLIP, MP5_RELOAD, 1.5 ); +} + + +void CMP5::WeaponIdle( void ) +{ + ResetEmptySound( ); + + m_pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); + + if ( m_flTimeWeaponIdle > UTIL_WeaponTimeBase() ) + return; + + int iAnim; + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: + iAnim = MP5_LONGIDLE; + break; + + default: + case 1: + iAnim = MP5_IDLE1; + break; + } + + SendWeaponAnim( iAnim ); + + m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); // how long till we do this again. +} + + + +class CMP5AmmoClip : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_9mmARclip.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_9mmARclip.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + int bResult = (pOther->GiveAmmo( AMMO_MP5CLIP_GIVE, "9mm", _9MM_MAX_CARRY) != -1); + if (bResult) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + } + return bResult; + } +}; +LINK_ENTITY_TO_CLASS( ammo_mp5clip, CMP5AmmoClip ); +LINK_ENTITY_TO_CLASS( ammo_9mmAR, CMP5AmmoClip ); + + + +class CMP5Chainammo : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_chainammo.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_chainammo.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + int bResult = (pOther->GiveAmmo( AMMO_CHAINBOX_GIVE, "9mm", _9MM_MAX_CARRY) != -1); + if (bResult) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + } + return bResult; + } +}; +LINK_ENTITY_TO_CLASS( ammo_9mmbox, CMP5Chainammo ); + + +class CMP5AmmoGrenade : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_ARgrenade.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_ARgrenade.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + int bResult = (pOther->GiveAmmo( AMMO_M203BOX_GIVE, "ARgrenades", M203_GRENADE_MAX_CARRY ) != -1); + + if (bResult) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + } + return bResult; + } +}; +LINK_ENTITY_TO_CLASS( ammo_mp5grenades, CMP5AmmoGrenade ); +LINK_ENTITY_TO_CLASS( ammo_ARgrenades, CMP5AmmoGrenade ); + + + + + + + + + + + + + + + + + + diff --git a/dlls/mpstubb.cpp b/dlls/mpstubb.cpp new file mode 100644 index 0000000..bbe51e7 --- /dev/null +++ b/dlls/mpstubb.cpp @@ -0,0 +1,264 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "soundent.h" +#include "nodes.h" +#include "talkmonster.h" + + +float CTalkMonster::g_talkWaitTime = 0; // time delay until it's ok to speak: used so that two NPCs don't talk at once + +/*********************************************************/ + + +CGraph WorldGraph; +void CGraph :: InitGraph( void ) { } +int CGraph :: FLoadGraph ( char *szMapName ) { return FALSE; } +int CGraph :: AllocNodes ( void ) { return FALSE; } +int CGraph :: CheckNODFile ( char *szMapName ) { return FALSE; } +int CGraph :: FSetGraphPointers ( void ) { return 0; } +void CGraph :: ShowNodeConnections ( int iNode ) { } +int CGraph :: FindNearestNode ( const Vector &vecOrigin, int afNodeTypes ) { return 0; } + + +/*********************************************************/ + + +void CBaseMonster :: ReportAIState( void ) { } +float CBaseMonster :: ChangeYaw ( int speed ) { return 0; } +void CBaseMonster :: MakeIdealYaw( Vector vecTarget ) { } + + +void CBaseMonster::CorpseFallThink( void ) +{ + if ( pev->flags & FL_ONGROUND ) + { + SetThink ( NULL ); + + SetSequenceBox( ); + UTIL_SetOrigin( pev, pev->origin );// link into world. + } + else + pev->nextthink = gpGlobals->time + 0.1; +} +// Call after animation/pose is set up +void CBaseMonster :: MonsterInitDead( void ) +{ + InitBoneControllers(); + + pev->solid = SOLID_BBOX; + pev->movetype = MOVETYPE_TOSS;// so he'll fall to ground + + pev->frame = 0; + ResetSequenceInfo( ); + pev->framerate = 0; + + // Copy health + pev->max_health = pev->health; + pev->deadflag = DEAD_DEAD; + + UTIL_SetSize(pev, g_vecZero, g_vecZero ); + UTIL_SetOrigin( pev, pev->origin ); + + // Setup health counters, etc. + BecomeDead(); + SetThink( CorpseFallThink ); + pev->nextthink = gpGlobals->time + 0.5; +} + + +BOOL CBaseMonster :: ShouldFadeOnDeath( void ) +{ + return FALSE; +} + +BOOL CBaseMonster :: FCheckAITrigger ( void ) +{ + return FALSE; +} + +void CBaseMonster :: KeyValue( KeyValueData *pkvd ) +{ + CBaseToggle::KeyValue( pkvd ); +} + +int CBaseMonster::IRelationship ( CBaseEntity *pTarget ) +{ + static int iEnemy[14][14] = + { // NONE MACH PLYR HPASS HMIL AMIL APASS AMONST APREY APRED INSECT PLRALY PBWPN ABWPN + /*NONE*/ { R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO, R_NO, R_NO }, + /*MACHINE*/ { R_NO ,R_NO ,R_DL ,R_DL ,R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_DL, R_DL, R_DL }, + /*PLAYER*/ { R_NO ,R_DL ,R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO, R_DL, R_DL }, + /*HUMANPASSIVE*/{ R_NO ,R_NO ,R_AL ,R_AL ,R_HT ,R_FR ,R_NO ,R_HT ,R_DL ,R_FR ,R_NO ,R_AL, R_NO, R_NO }, + /*HUMANMILITAR*/{ R_NO ,R_NO ,R_HT ,R_DL ,R_NO ,R_HT ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_HT, R_NO, R_NO }, + /*ALIENMILITAR*/{ R_NO ,R_DL ,R_HT ,R_DL ,R_HT ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_DL, R_NO, R_NO }, + /*ALIENPASSIVE*/{ R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO, R_NO, R_NO }, + /*ALIENMONSTER*/{ R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_DL, R_NO, R_NO }, + /*ALIENPREY */{ R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO ,R_NO ,R_NO ,R_FR ,R_NO ,R_DL, R_NO, R_NO }, + /*ALIENPREDATO*/{ R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO ,R_NO ,R_HT ,R_DL ,R_NO ,R_DL, R_NO, R_NO }, + /*INSECT*/ { R_FR ,R_FR ,R_FR ,R_FR ,R_FR ,R_NO ,R_FR ,R_FR ,R_FR ,R_FR ,R_NO ,R_FR, R_NO, R_NO }, + /*PLAYERALLY*/ { R_NO ,R_DL ,R_AL ,R_AL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO, R_NO, R_NO }, + /*PBIOWEAPON*/ { R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_DL, R_NO, R_DL }, + /*ABIOWEAPON*/ { R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_AL ,R_NO ,R_DL ,R_DL ,R_NO ,R_NO ,R_DL, R_DL, R_NO } + }; + + return iEnemy[ Classify() ][ pTarget->Classify() ]; +} + + +//========================================================= +// Look - Base class monster function to find enemies or +// food by sight. iDistance is distance ( in units ) that the +// monster can see. +// +// Sets the sight bits of the m_afConditions mask to indicate +// which types of entities were sighted. +// Function also sets the Looker's m_pLink +// to the head of a link list that contains all visible ents. +// (linked via each ent's m_pLink field) +// +//========================================================= +void CBaseMonster :: Look ( int iDistance ) +{ + int iSighted = 0; + + // DON'T let visibility information from last frame sit around! + ClearConditions(bits_COND_SEE_HATE | bits_COND_SEE_DISLIKE | bits_COND_SEE_ENEMY | bits_COND_SEE_FEAR | bits_COND_SEE_NEMESIS | bits_COND_SEE_CLIENT); + + m_pLink = NULL; + + CBaseEntity *pSightEnt = NULL;// the current visible entity that we're dealing with + + CBaseEntity *pList[100]; + + Vector delta = Vector( iDistance, iDistance, iDistance ); + + // Find only monsters/clients in box, NOT limited to PVS + int count = UTIL_EntitiesInBox( pList, 100, pev->origin - delta, pev->origin + delta, FL_CLIENT|FL_MONSTER ); + for ( int i = 0; i < count; i++ ) + { + pSightEnt = pList[i]; + if ( pSightEnt != this && pSightEnt->pev->health > 0 ) + { + // the looker will want to consider this entity + // don't check anything else about an entity that can't be seen, or an entity that you don't care about. + if ( IRelationship( pSightEnt ) != R_NO && FInViewCone( pSightEnt ) && !FBitSet( pSightEnt->pev->flags, FL_NOTARGET ) && FVisible( pSightEnt ) ) + { + if ( pSightEnt->IsPlayer() ) + { + // if we see a client, remember that (mostly for scripted AI) + iSighted |= bits_COND_SEE_CLIENT; + } + + pSightEnt->m_pLink = m_pLink; + m_pLink = pSightEnt; + + if ( pSightEnt == m_hEnemy ) + { + // we know this ent is visible, so if it also happens to be our enemy, store that now. + iSighted |= bits_COND_SEE_ENEMY; + } + + // don't add the Enemy's relationship to the conditions. We only want to worry about conditions when + // we see monsters other than the Enemy. + switch ( IRelationship ( pSightEnt ) ) + { + case R_NM: + iSighted |= bits_COND_SEE_NEMESIS; + break; + case R_HT: + iSighted |= bits_COND_SEE_HATE; + break; + case R_DL: + iSighted |= bits_COND_SEE_DISLIKE; + break; + case R_FR: + iSighted |= bits_COND_SEE_FEAR; + break; + case R_AL: + break; + default: + ALERT ( at_aiconsole, "%s can't assess %s\n", STRING(pev->classname), STRING(pSightEnt->pev->classname ) ); + break; + } + } + } + } + + SetConditions( iSighted ); +} + + +//========================================================= +// BestVisibleEnemy - this functions searches the link +// list whose head is the caller's m_pLink field, and returns +// a pointer to the enemy entity in that list that is nearest the +// caller. +// +// !!!UNDONE - currently, this only returns the closest enemy. +// we'll want to consider distance, relationship, attack types, back turned, etc. +//========================================================= +CBaseEntity *CBaseMonster :: BestVisibleEnemy ( void ) +{ + CBaseEntity *pReturn; + CBaseEntity *pNextEnt; + int iNearest; + int iDist; + int iBestRelationship; + + iNearest = 8192;// so first visible entity will become the closest. + pNextEnt = m_pLink; + pReturn = NULL; + iBestRelationship = R_NO; + + while ( pNextEnt != NULL ) + { + if ( pNextEnt->IsAlive() ) + { + if ( IRelationship( pNextEnt) > iBestRelationship ) + { + // this entity is disliked MORE than the entity that we + // currently think is the best visible enemy. No need to do + // a distance check, just get mad at this one for now. + iBestRelationship = IRelationship ( pNextEnt ); + iNearest = ( pNextEnt->pev->origin - pev->origin ).Length(); + pReturn = pNextEnt; + } + else if ( IRelationship( pNextEnt) == iBestRelationship ) + { + // this entity is disliked just as much as the entity that + // we currently think is the best visible enemy, so we only + // get mad at it if it is closer. + iDist = ( pNextEnt->pev->origin - pev->origin ).Length(); + + if ( iDist <= iNearest ) + { + iNearest = iDist; + iBestRelationship = IRelationship ( pNextEnt ); + pReturn = pNextEnt; + } + } + } + + pNextEnt = pNextEnt->m_pLink; + } + + return pReturn; +} diff --git a/dlls/multiplay_gamerules.cpp b/dlls/multiplay_gamerules.cpp new file mode 100644 index 0000000..9362067 --- /dev/null +++ b/dlls/multiplay_gamerules.cpp @@ -0,0 +1,1692 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.cpp +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" + +#include "skill.h" +#include "game.h" +#include "items.h" +#include "voice_gamemgr.h" +#include "hltv.h" + +#if !defined ( _WIN32 ) +#include +#endif + +extern DLL_GLOBAL CGameRules *g_pGameRules; +extern DLL_GLOBAL BOOL g_fGameOver; +extern int gmsgDeathMsg; // client dll messages +extern int gmsgScoreInfo; +extern int gmsgMOTD; +extern int gmsgServerName; + +extern int g_teamplay; + +#define ITEM_RESPAWN_TIME 30 +#define WEAPON_RESPAWN_TIME 20 +#define AMMO_RESPAWN_TIME 20 + +float g_flIntermissionStartTime = 0; + +CVoiceGameMgr g_VoiceGameMgr; + +class CMultiplayGameMgrHelper : public IVoiceGameMgrHelper +{ +public: + virtual bool CanPlayerHearPlayer(CBasePlayer *pListener, CBasePlayer *pTalker) + { + if ( g_teamplay ) + { + if ( g_pGameRules->PlayerRelationship( pListener, pTalker ) != GR_TEAMMATE ) + { + return false; + } + } + + return true; + } +}; +static CMultiplayGameMgrHelper g_GameMgrHelper; + +//********************************************************* +// Rules for the half-life multiplayer game. +//********************************************************* + +CHalfLifeMultiplay :: CHalfLifeMultiplay() +{ + g_VoiceGameMgr.Init(&g_GameMgrHelper, gpGlobals->maxClients); + + RefreshSkillData(); + m_flIntermissionEndTime = 0; + g_flIntermissionStartTime = 0; + + // 11/8/98 + // Modified by YWB: Server .cfg file is now a cvar, so that + // server ops can run multiple game servers, with different server .cfg files, + // from a single installed directory. + // Mapcyclefile is already a cvar. + + // 3/31/99 + // Added lservercfg file cvar, since listen and dedicated servers should not + // share a single config file. (sjb) + if ( IS_DEDICATED_SERVER() ) + { + // this code has been moved into engine, to only run server.cfg once + } + else + { + // listen server + char *lservercfgfile = (char *)CVAR_GET_STRING( "lservercfgfile" ); + + if ( lservercfgfile && lservercfgfile[0] ) + { + char szCommand[256]; + + ALERT( at_console, "Executing listen server config file\n" ); + sprintf( szCommand, "exec %s\n", lservercfgfile ); + SERVER_COMMAND( szCommand ); + } + } +} + +BOOL CHalfLifeMultiplay::ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) +{ + if(g_VoiceGameMgr.ClientCommand(pPlayer, pcmd)) + return TRUE; + + return CGameRules::ClientCommand(pPlayer, pcmd); +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay::RefreshSkillData( void ) +{ +// load all default values + CGameRules::RefreshSkillData(); + +// override some values for multiplay. + + // suitcharger + gSkillData.suitchargerCapacity = 30; + + // Crowbar whack + gSkillData.plrDmgCrowbar = 25; + + // Glock Round + gSkillData.plrDmg9MM = 12; + + // 357 Round + gSkillData.plrDmg357 = 40; + + // MP5 Round + gSkillData.plrDmgMP5 = 12; + + // M203 grenade + gSkillData.plrDmgM203Grenade = 100; + + // Shotgun buckshot + gSkillData.plrDmgBuckshot = 20;// fewer pellets in deathmatch + + // Crossbow + gSkillData.plrDmgCrossbowClient = 20; + + // RPG + gSkillData.plrDmgRPG = 120; + + // Egon + gSkillData.plrDmgEgonWide = 20; + gSkillData.plrDmgEgonNarrow = 10; + + // Hand Grendade + gSkillData.plrDmgHandGrenade = 100; + + // Satchel Charge + gSkillData.plrDmgSatchel = 120; + + // Tripmine + gSkillData.plrDmgTripmine = 150; + + // hornet + gSkillData.plrDmgHornet = 10; +} + +// longest the intermission can last, in seconds +#define MAX_INTERMISSION_TIME 120 + +extern cvar_t timeleft, fragsleft; + +extern cvar_t mp_chattime; + +//========================================================= +//========================================================= +void CHalfLifeMultiplay :: Think ( void ) +{ + g_VoiceGameMgr.Update(gpGlobals->frametime); + + ///// Check game rules ///// + static int last_frags; + static int last_time; + + int frags_remaining = 0; + int time_remaining = 0; + + if ( g_fGameOver ) // someone else quit the game already + { + // bounds check + int time = (int)CVAR_GET_FLOAT( "mp_chattime" ); + if ( time < 1 ) + CVAR_SET_STRING( "mp_chattime", "1" ); + else if ( time > MAX_INTERMISSION_TIME ) + CVAR_SET_STRING( "mp_chattime", UTIL_dtos1( MAX_INTERMISSION_TIME ) ); + + m_flIntermissionEndTime = g_flIntermissionStartTime + mp_chattime.value; + + // check to see if we should change levels now + if ( m_flIntermissionEndTime < gpGlobals->time ) + { + if ( m_iEndIntermissionButtonHit // check that someone has pressed a key, or the max intermission time is over + || ( ( g_flIntermissionStartTime + MAX_INTERMISSION_TIME ) < gpGlobals->time) ) + ChangeLevel(); // intermission is over + } + + return; + } + + float flTimeLimit = timelimit.value * 60; + float flFragLimit = fraglimit.value; + + time_remaining = (int)(flTimeLimit ? ( flTimeLimit - gpGlobals->time ) : 0); + + if ( flTimeLimit != 0 && gpGlobals->time >= flTimeLimit ) + { + GoToIntermission(); + return; + } + + if ( flFragLimit ) + { + int bestfrags = 9999; + int remain; + + // check if any player is over the frag limit + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + if ( pPlayer && pPlayer->pev->frags >= flFragLimit ) + { + GoToIntermission(); + return; + } + + + if ( pPlayer ) + { + remain = flFragLimit - pPlayer->pev->frags; + if ( remain < bestfrags ) + { + bestfrags = remain; + } + } + + } + frags_remaining = bestfrags; + } + + // Updates when frags change + if ( frags_remaining != last_frags ) + { + g_engfuncs.pfnCvar_DirectSet( &fragsleft, UTIL_VarArgs( "%i", frags_remaining ) ); + } + + // Updates once per second + if ( timeleft.value != last_time ) + { + g_engfuncs.pfnCvar_DirectSet( &timeleft, UTIL_VarArgs( "%i", time_remaining ) ); + } + + last_frags = frags_remaining; + last_time = time_remaining; +} + + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsMultiplayer( void ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsDeathmatch( void ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsCoOp( void ) +{ + return gpGlobals->coop; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ + if ( !pWeapon->CanDeploy() ) + { + // that weapon can't deploy anyway. + return FALSE; + } + + if ( !pPlayer->m_pActiveItem ) + { + // player doesn't have an active item! + return TRUE; + } + + if ( !pPlayer->m_pActiveItem->CanHolster() ) + { + // can't put away the active item. + return FALSE; + } + + if ( pWeapon->iWeight() > pPlayer->m_pActiveItem->iWeight() ) + { + return TRUE; + } + + return FALSE; +} + +BOOL CHalfLifeMultiplay :: GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) +{ + + CBasePlayerItem *pCheck; + CBasePlayerItem *pBest;// this will be used in the event that we don't find a weapon in the same category. + int iBestWeight; + int i; + + iBestWeight = -1;// no weapon lower than -1 can be autoswitched to + pBest = NULL; + + if ( !pCurrentWeapon->CanHolster() ) + { + // can't put this gun away right now, so can't switch. + return FALSE; + } + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pCheck = pPlayer->m_rgpPlayerItems[ i ]; + + while ( pCheck ) + { + if ( pCheck->iWeight() > -1 && pCheck->iWeight() == pCurrentWeapon->iWeight() && pCheck != pCurrentWeapon ) + { + // this weapon is from the same category. + if ( pCheck->CanDeploy() ) + { + if ( pPlayer->SwitchWeapon( pCheck ) ) + { + return TRUE; + } + } + } + else if ( pCheck->iWeight() > iBestWeight && pCheck != pCurrentWeapon )// don't reselect the weapon we're trying to get rid of + { + //ALERT ( at_console, "Considering %s\n", STRING( pCheck->pev->classname ) ); + // we keep updating the 'best' weapon just in case we can't find a weapon of the same weight + // that the player was using. This will end up leaving the player with his heaviest-weighted + // weapon. + if ( pCheck->CanDeploy() ) + { + // if this weapon is useable, flag it as the best + iBestWeight = pCheck->iWeight(); + pBest = pCheck; + } + } + + pCheck = pCheck->m_pNext; + } + } + + // if we make it here, we've checked all the weapons and found no useable + // weapon in the same catagory as the current weapon. + + // if pBest is null, we didn't find ANYTHING. Shouldn't be possible- should always + // at least get the crowbar, but ya never know. + if ( !pBest ) + { + return FALSE; + } + + pPlayer->SwitchWeapon( pBest ); + + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay :: ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) +{ + g_VoiceGameMgr.ClientConnected(pEntity); + return TRUE; +} + +extern int gmsgSayText; +extern int gmsgGameMode; + +void CHalfLifeMultiplay :: UpdateGameMode( CBasePlayer *pPlayer ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgGameMode, NULL, pPlayer->edict() ); + WRITE_BYTE( 0 ); // game mode none + MESSAGE_END(); +} + +void CHalfLifeMultiplay :: InitHUD( CBasePlayer *pl ) +{ + // notify other clients of player joining the game + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, UTIL_VarArgs( "%s has joined the game\n", + ( pl->pev->netname && STRING(pl->pev->netname)[0] != 0 ) ? STRING(pl->pev->netname) : "unconnected" ) ); + + // team match? + if ( g_teamplay ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" entered the game\n", + STRING( pl->pev->netname ), + GETPLAYERUSERID( pl->edict() ), + GETPLAYERAUTHID( pl->edict() ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pl->edict() ), "model" ) ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" entered the game\n", + STRING( pl->pev->netname ), + GETPLAYERUSERID( pl->edict() ), + GETPLAYERAUTHID( pl->edict() ), + GETPLAYERUSERID( pl->edict() ) ); + } + + UpdateGameMode( pl ); + + // sending just one score makes the hud scoreboard active; otherwise + // it is just disabled for single play + MESSAGE_BEGIN( MSG_ONE, gmsgScoreInfo, NULL, pl->edict() ); + WRITE_BYTE( ENTINDEX(pl->edict()) ); + WRITE_SHORT( 0 ); + WRITE_SHORT( 0 ); + WRITE_SHORT( 0 ); + WRITE_SHORT( 0 ); + MESSAGE_END(); + + SendMOTDToClient( pl->edict() ); + + // loop through all active players and send their score info to the new client + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + // FIXME: Probably don't need to cast this just to read m_iDeaths + CBasePlayer *plr = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if ( plr ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgScoreInfo, NULL, pl->edict() ); + WRITE_BYTE( i ); // client number + WRITE_SHORT( plr->pev->frags ); + WRITE_SHORT( plr->m_iDeaths ); + WRITE_SHORT( 0 ); + WRITE_SHORT( GetTeamIndex( plr->m_szTeamName ) + 1 ); + MESSAGE_END(); + } + } + + if ( g_fGameOver ) + { + MESSAGE_BEGIN( MSG_ONE, SVC_INTERMISSION, NULL, pl->edict() ); + MESSAGE_END(); + } +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay :: ClientDisconnected( edict_t *pClient ) +{ + if ( pClient ) + { + CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pClient ); + + if ( pPlayer ) + { + FireTargets( "game_playerleave", pPlayer, pPlayer, USE_TOGGLE, 0 ); + + // team match? + if ( g_teamplay ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" disconnected\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model" ) ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" disconnected\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + GETPLAYERUSERID( pPlayer->edict() ) ); + } + + pPlayer->RemoveAllItems( TRUE );// destroy all of the players weapons and items + } + } +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay :: FlPlayerFallDamage( CBasePlayer *pPlayer ) +{ + int iFallDamage = (int)falldamage.value; + + switch ( iFallDamage ) + { + case 1://progressive + pPlayer->m_flFallVelocity -= PLAYER_MAX_SAFE_FALL_SPEED; + return pPlayer->m_flFallVelocity * DAMAGE_FOR_FALL_SPEED; + break; + default: + case 0:// fixed + return 10; + break; + } +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay :: PlayerThink( CBasePlayer *pPlayer ) +{ + if ( g_fGameOver ) + { + // check for button presses + if ( pPlayer->m_afButtonPressed & ( IN_DUCK | IN_ATTACK | IN_ATTACK2 | IN_USE | IN_JUMP ) ) + m_iEndIntermissionButtonHit = TRUE; + + // clear attack/use commands from player + pPlayer->m_afButtonPressed = 0; + pPlayer->pev->button = 0; + pPlayer->m_afButtonReleased = 0; + } +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay :: PlayerSpawn( CBasePlayer *pPlayer ) +{ + BOOL addDefault; + CBaseEntity *pWeaponEntity = NULL; + + pPlayer->pev->weapons |= (1<Touch( pPlayer ); + addDefault = FALSE; + } + + if ( addDefault ) + { + pPlayer->GiveNamedItem( "weapon_crowbar" ); + pPlayer->GiveNamedItem( "weapon_9mmhandgun" ); + pPlayer->GiveAmmo( 68, "9mm", _9MM_MAX_CARRY );// 4 full reloads + } +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay :: FPlayerCanRespawn( CBasePlayer *pPlayer ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay :: FlPlayerSpawnTime( CBasePlayer *pPlayer ) +{ + return gpGlobals->time;//now! +} + +BOOL CHalfLifeMultiplay :: AllowAutoTargetCrosshair( void ) +{ + return ( aimcrosshair.value != 0 ); +} + +//========================================================= +// IPointsForKill - how many points awarded to anyone +// that kills this player? +//========================================================= +int CHalfLifeMultiplay :: IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) +{ + return 1; +} + + +//========================================================= +// PlayerKilled - someone/something killed this player +//========================================================= +void CHalfLifeMultiplay :: PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) +{ + DeathNotice( pVictim, pKiller, pInflictor ); + + pVictim->m_iDeaths += 1; + + + FireTargets( "game_playerdie", pVictim, pVictim, USE_TOGGLE, 0 ); + CBasePlayer *peKiller = NULL; + CBaseEntity *ktmp = CBaseEntity::Instance( pKiller ); + if ( ktmp && (ktmp->Classify() == CLASS_PLAYER) ) + peKiller = (CBasePlayer*)ktmp; + + if ( pVictim->pev == pKiller ) + { // killed self + pKiller->frags -= 1; + } + else if ( ktmp && ktmp->IsPlayer() ) + { + // if a player dies in a deathmatch game and the killer is a client, award the killer some points + pKiller->frags += IPointsForKill( peKiller, pVictim ); + + FireTargets( "game_playerkill", ktmp, ktmp, USE_TOGGLE, 0 ); + } + else + { // killed by the world + pKiller->frags -= 1; + } + + // update the scores + // killed scores + MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo ); + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); + WRITE_SHORT( pVictim->pev->frags ); + WRITE_SHORT( pVictim->m_iDeaths ); + WRITE_SHORT( 0 ); + WRITE_SHORT( GetTeamIndex( pVictim->m_szTeamName ) + 1 ); + MESSAGE_END(); + + // killers score, if it's a player + CBaseEntity *ep = CBaseEntity::Instance( pKiller ); + if ( ep && ep->Classify() == CLASS_PLAYER ) + { + CBasePlayer *PK = (CBasePlayer*)ep; + + MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo ); + WRITE_BYTE( ENTINDEX(PK->edict()) ); + WRITE_SHORT( PK->pev->frags ); + WRITE_SHORT( PK->m_iDeaths ); + WRITE_SHORT( 0 ); + WRITE_SHORT( GetTeamIndex( PK->m_szTeamName) + 1 ); + MESSAGE_END(); + + // let the killer paint another decal as soon as he'd like. + PK->m_flNextDecalTime = gpGlobals->time; + } +#ifndef HLDEMO_BUILD + if ( pVictim->HasNamedPlayerItem("weapon_satchel") ) + { + DeactivateSatchels( pVictim ); + } +#endif +} + +//========================================================= +// Deathnotice. +//========================================================= +void CHalfLifeMultiplay::DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pevInflictor ) +{ + // Work out what killed the player, and send a message to all clients about it + CBaseEntity *Killer = CBaseEntity::Instance( pKiller ); + + const char *killer_weapon_name = "world"; // by default, the player is killed by the world + int killer_index = 0; + + // Hack to fix name change + char *tau = "tau_cannon"; + char *gluon = "gluon gun"; + + if ( pKiller->flags & FL_CLIENT ) + { + killer_index = ENTINDEX(ENT(pKiller)); + + if ( pevInflictor ) + { + if ( pevInflictor == pKiller ) + { + // If the inflictor is the killer, then it must be their current weapon doing the damage + CBasePlayer *pPlayer = (CBasePlayer*)CBaseEntity::Instance( pKiller ); + + if ( pPlayer->m_pActiveItem ) + { + killer_weapon_name = pPlayer->m_pActiveItem->pszName(); + } + } + else + { + killer_weapon_name = STRING( pevInflictor->classname ); // it's just that easy + } + } + } + else + { + killer_weapon_name = STRING( pevInflictor->classname ); + } + + // strip the monster_* or weapon_* from the inflictor's classname + if ( strncmp( killer_weapon_name, "weapon_", 7 ) == 0 ) + killer_weapon_name += 7; + else if ( strncmp( killer_weapon_name, "monster_", 8 ) == 0 ) + killer_weapon_name += 8; + else if ( strncmp( killer_weapon_name, "func_", 5 ) == 0 ) + killer_weapon_name += 5; + + MESSAGE_BEGIN( MSG_ALL, gmsgDeathMsg ); + WRITE_BYTE( killer_index ); // the killer + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); // the victim + WRITE_STRING( killer_weapon_name ); // what they were killed by (should this be a string?) + MESSAGE_END(); + + // replace the code names with the 'real' names + if ( !strcmp( killer_weapon_name, "egon" ) ) + killer_weapon_name = gluon; + else if ( !strcmp( killer_weapon_name, "gauss" ) ) + killer_weapon_name = tau; + + if ( pVictim->pev == pKiller ) + { + // killed self + + // team match? + if ( g_teamplay ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" committed suicide with \"%s\"\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pVictim->edict() ), "model" ), + killer_weapon_name ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" committed suicide with \"%s\"\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + } + } + else if ( pKiller->flags & FL_CLIENT ) + { + // team match? + if ( g_teamplay ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" killed \"%s<%i><%s><%s>\" with \"%s\"\n", + STRING( pKiller->netname ), + GETPLAYERUSERID( ENT(pKiller) ), + GETPLAYERAUTHID( ENT(pKiller) ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( ENT(pKiller) ), "model" ), + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pVictim->edict() ), "model" ), + killer_weapon_name ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" killed \"%s<%i><%s><%i>\" with \"%s\"\n", + STRING( pKiller->netname ), + GETPLAYERUSERID( ENT(pKiller) ), + GETPLAYERAUTHID( ENT(pKiller) ), + GETPLAYERUSERID( ENT(pKiller) ), + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + } + } + else + { + // killed by the world + + // team match? + if ( g_teamplay ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" committed suicide with \"%s\" (world)\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pVictim->edict() ), "model" ), + killer_weapon_name ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" committed suicide with \"%s\" (world)\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + } + } + + MESSAGE_BEGIN( MSG_SPEC, SVC_DIRECTOR ); + WRITE_BYTE ( 9 ); // command length in bytes + WRITE_BYTE ( DRC_CMD_EVENT ); // player killed + WRITE_SHORT( ENTINDEX(pVictim->edict()) ); // index number of primary entity + if (pevInflictor) + WRITE_SHORT( ENTINDEX(ENT(pevInflictor)) ); // index number of secondary entity + else + WRITE_SHORT( ENTINDEX(ENT(pKiller)) ); // index number of secondary entity + WRITE_LONG( 7 | DRC_FLAG_DRAMATIC); // eventflags (priority and flags) + MESSAGE_END(); + +// Print a standard message + // TODO: make this go direct to console + return; // just remove for now +/* + char szText[ 128 ]; + + if ( pKiller->flags & FL_MONSTER ) + { + // killed by a monster + strcpy ( szText, STRING( pVictim->pev->netname ) ); + strcat ( szText, " was killed by a monster.\n" ); + return; + } + + if ( pKiller == pVictim->pev ) + { + strcpy ( szText, STRING( pVictim->pev->netname ) ); + strcat ( szText, " commited suicide.\n" ); + } + else if ( pKiller->flags & FL_CLIENT ) + { + strcpy ( szText, STRING( pKiller->netname ) ); + + strcat( szText, " : " ); + strcat( szText, killer_weapon_name ); + strcat( szText, " : " ); + + strcat ( szText, STRING( pVictim->pev->netname ) ); + strcat ( szText, "\n" ); + } + else if ( FClassnameIs ( pKiller, "worldspawn" ) ) + { + strcpy ( szText, STRING( pVictim->pev->netname ) ); + strcat ( szText, " fell or drowned or something.\n" ); + } + else if ( pKiller->solid == SOLID_BSP ) + { + strcpy ( szText, STRING( pVictim->pev->netname ) ); + strcat ( szText, " was mooshed.\n" ); + } + else + { + strcpy ( szText, STRING( pVictim->pev->netname ) ); + strcat ( szText, " died mysteriously.\n" ); + } + + UTIL_ClientPrintAll( szText ); +*/ +} + +//========================================================= +// PlayerGotWeapon - player has grabbed a weapon that was +// sitting in the world +//========================================================= +void CHalfLifeMultiplay :: PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ +} + +//========================================================= +// FlWeaponRespawnTime - what is the time in the future +// at which this weapon may spawn? +//========================================================= +float CHalfLifeMultiplay :: FlWeaponRespawnTime( CBasePlayerItem *pWeapon ) +{ + if ( weaponstay.value > 0 ) + { + // make sure it's only certain weapons + if ( !(pWeapon->iFlags() & ITEM_FLAG_LIMITINWORLD) ) + { + return gpGlobals->time + 0; // weapon respawns almost instantly + } + } + + return gpGlobals->time + WEAPON_RESPAWN_TIME; +} + +// when we are within this close to running out of entities, items +// marked with the ITEM_FLAG_LIMITINWORLD will delay their respawn +#define ENTITY_INTOLERANCE 100 + +//========================================================= +// FlWeaponRespawnTime - Returns 0 if the weapon can respawn +// now, otherwise it returns the time at which it can try +// to spawn again. +//========================================================= +float CHalfLifeMultiplay :: FlWeaponTryRespawn( CBasePlayerItem *pWeapon ) +{ + if ( pWeapon && pWeapon->m_iId && (pWeapon->iFlags() & ITEM_FLAG_LIMITINWORLD) ) + { + if ( NUMBER_OF_ENTITIES() < (gpGlobals->maxEntities - ENTITY_INTOLERANCE) ) + return 0; + + // we're past the entity tolerance level, so delay the respawn + return FlWeaponRespawnTime( pWeapon ); + } + + return 0; +} + +//========================================================= +// VecWeaponRespawnSpot - where should this weapon spawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHalfLifeMultiplay :: VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ) +{ + return pWeapon->pev->origin; +} + +//========================================================= +// WeaponShouldRespawn - any conditions inhibiting the +// respawning of this weapon? +//========================================================= +int CHalfLifeMultiplay :: WeaponShouldRespawn( CBasePlayerItem *pWeapon ) +{ + if ( pWeapon->pev->spawnflags & SF_NORESPAWN ) + { + return GR_WEAPON_RESPAWN_NO; + } + + return GR_WEAPON_RESPAWN_YES; +} + +//========================================================= +// CanHaveWeapon - returns FALSE if the player is not allowed +// to pick up this weapon +//========================================================= +BOOL CHalfLifeMultiplay::CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pItem ) +{ + if ( weaponstay.value > 0 ) + { + if ( pItem->iFlags() & ITEM_FLAG_LIMITINWORLD ) + return CGameRules::CanHavePlayerItem( pPlayer, pItem ); + + // check if the player already has this weapon + for ( int i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + CBasePlayerItem *it = pPlayer->m_rgpPlayerItems[i]; + + while ( it != NULL ) + { + if ( it->m_iId == pItem->m_iId ) + { + return FALSE; + } + + it = it->m_pNext; + } + } + } + + return CGameRules::CanHavePlayerItem( pPlayer, pItem ); +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay::PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ) +{ +} + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::ItemShouldRespawn( CItem *pItem ) +{ + if ( pItem->pev->spawnflags & SF_NORESPAWN ) + { + return GR_ITEM_RESPAWN_NO; + } + + return GR_ITEM_RESPAWN_YES; +} + + +//========================================================= +// At what time in the future may this Item respawn? +//========================================================= +float CHalfLifeMultiplay::FlItemRespawnTime( CItem *pItem ) +{ + return gpGlobals->time + ITEM_RESPAWN_TIME; +} + +//========================================================= +// Where should this item respawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHalfLifeMultiplay::VecItemRespawnSpot( CItem *pItem ) +{ + return pItem->pev->origin; +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay::PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ) +{ +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsAllowedToSpawn( CBaseEntity *pEntity ) +{ +// if ( pEntity->pev->flags & FL_MONSTER ) +// return FALSE; + + return TRUE; +} + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ) +{ + if ( pAmmo->pev->spawnflags & SF_NORESPAWN ) + { + return GR_AMMO_RESPAWN_NO; + } + + return GR_AMMO_RESPAWN_YES; +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay::FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ) +{ + return gpGlobals->time + AMMO_RESPAWN_TIME; +} + +//========================================================= +//========================================================= +Vector CHalfLifeMultiplay::VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ) +{ + return pAmmo->pev->origin; +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay::FlHealthChargerRechargeTime( void ) +{ + return 60; +} + + +float CHalfLifeMultiplay::FlHEVChargerRechargeTime( void ) +{ + return 30; +} + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::DeadPlayerWeapons( CBasePlayer *pPlayer ) +{ + return GR_PLR_DROP_GUN_ACTIVE; +} + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::DeadPlayerAmmo( CBasePlayer *pPlayer ) +{ + return GR_PLR_DROP_AMMO_ACTIVE; +} + +edict_t *CHalfLifeMultiplay::GetPlayerSpawnSpot( CBasePlayer *pPlayer ) +{ + edict_t *pentSpawnSpot = CGameRules::GetPlayerSpawnSpot( pPlayer ); + if ( IsMultiplayer() && pentSpawnSpot->v.target ) + { + FireTargets( STRING(pentSpawnSpot->v.target), pPlayer, pPlayer, USE_TOGGLE, 0 ); + } + + return pentSpawnSpot; +} + + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) +{ + // half life deathmatch has only enemies + return GR_NOTTEAMMATE; +} + +BOOL CHalfLifeMultiplay :: PlayFootstepSounds( CBasePlayer *pl, float fvol ) +{ + if ( g_footsteps && g_footsteps->value == 0 ) + return FALSE; + + if ( pl->IsOnLadder() || pl->pev->velocity.Length2D() > 220 ) + return TRUE; // only make step sounds in multiplayer if the player is moving fast enough + + return FALSE; +} + +BOOL CHalfLifeMultiplay :: FAllowFlashlight( void ) +{ + return flashlight.value != 0; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay :: FAllowMonsters( void ) +{ + return ( allowmonsters.value != 0 ); +} + +//========================================================= +//======== CHalfLifeMultiplay private functions =========== +#define INTERMISSION_TIME 6 + +void CHalfLifeMultiplay :: GoToIntermission( void ) +{ + if ( g_fGameOver ) + return; // intermission has already been triggered, so ignore. + + MESSAGE_BEGIN(MSG_ALL, SVC_INTERMISSION); + MESSAGE_END(); + + // bounds check + int time = (int)CVAR_GET_FLOAT( "mp_chattime" ); + if ( time < 1 ) + CVAR_SET_STRING( "mp_chattime", "1" ); + else if ( time > MAX_INTERMISSION_TIME ) + CVAR_SET_STRING( "mp_chattime", UTIL_dtos1( MAX_INTERMISSION_TIME ) ); + + m_flIntermissionEndTime = gpGlobals->time + ( (int)mp_chattime.value ); + g_flIntermissionStartTime = gpGlobals->time; + + g_fGameOver = TRUE; + m_iEndIntermissionButtonHit = FALSE; +} + +#define MAX_RULE_BUFFER 1024 + +typedef struct mapcycle_item_s +{ + struct mapcycle_item_s *next; + + char mapname[ 32 ]; + int minplayers, maxplayers; + char rulebuffer[ MAX_RULE_BUFFER ]; +} mapcycle_item_t; + +typedef struct mapcycle_s +{ + struct mapcycle_item_s *items; + struct mapcycle_item_s *next_item; +} mapcycle_t; + +/* +============== +DestroyMapCycle + +Clean up memory used by mapcycle when switching it +============== +*/ +void DestroyMapCycle( mapcycle_t *cycle ) +{ + mapcycle_item_t *p, *n, *start; + p = cycle->items; + if ( p ) + { + start = p; + p = p->next; + while ( p != start ) + { + n = p->next; + delete p; + p = n; + } + + delete cycle->items; + } + cycle->items = NULL; + cycle->next_item = NULL; +} + +static char com_token[ 1500 ]; + +/* +============== +COM_Parse + +Parse a token out of a string +============== +*/ +char *COM_Parse (char *data) +{ + int c; + int len; + + len = 0; + com_token[0] = 0; + + if (!data) + return NULL; + +// skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + return NULL; // end of file; + data++; + } + +// skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + + +// handle quoted strings specially + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if (c=='\"' || !c) + { + com_token[len] = 0; + return data; + } + com_token[len] = c; + len++; + } + } + +// parse single characters + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c == ',' ) + { + com_token[len] = c; + len++; + com_token[len] = 0; + return data+1; + } + +// parse a regular word + do + { + com_token[len] = c; + data++; + len++; + c = *data; + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c == ',' ) + break; + } while (c>32); + + com_token[len] = 0; + return data; +} + +/* +============== +COM_TokenWaiting + +Returns 1 if additional data is waiting to be processed on this line +============== +*/ +int COM_TokenWaiting( char *buffer ) +{ + char *p; + + p = buffer; + while ( *p && *p!='\n') + { + if ( !isspace( *p ) || isalnum( *p ) ) + return 1; + + p++; + } + + return 0; +} + + + +/* +============== +ReloadMapCycleFile + + +Parses mapcycle.txt file into mapcycle_t structure +============== +*/ +int ReloadMapCycleFile( char *filename, mapcycle_t *cycle ) +{ + char szBuffer[ MAX_RULE_BUFFER ]; + char szMap[ 32 ]; + int length; + char *pFileList; + char *aFileList = pFileList = (char*)LOAD_FILE_FOR_ME( filename, &length ); + int hasbuffer; + mapcycle_item_s *item, *newlist = NULL, *next; + + if ( pFileList && length ) + { + // the first map name in the file becomes the default + while ( 1 ) + { + hasbuffer = 0; + memset( szBuffer, 0, MAX_RULE_BUFFER ); + + pFileList = COM_Parse( pFileList ); + if ( strlen( com_token ) <= 0 ) + break; + + strcpy( szMap, com_token ); + + // Any more tokens on this line? + if ( COM_TokenWaiting( pFileList ) ) + { + pFileList = COM_Parse( pFileList ); + if ( strlen( com_token ) > 0 ) + { + hasbuffer = 1; + strcpy( szBuffer, com_token ); + } + } + + // Check map + if ( IS_MAP_VALID( szMap ) ) + { + // Create entry + char *s; + + item = new mapcycle_item_s; + + strcpy( item->mapname, szMap ); + + item->minplayers = 0; + item->maxplayers = 0; + + memset( item->rulebuffer, 0, MAX_RULE_BUFFER ); + + if ( hasbuffer ) + { + s = g_engfuncs.pfnInfoKeyValue( szBuffer, "minplayers" ); + if ( s && s[0] ) + { + item->minplayers = atoi( s ); + item->minplayers = max( item->minplayers, 0 ); + item->minplayers = min( item->minplayers, gpGlobals->maxClients ); + } + s = g_engfuncs.pfnInfoKeyValue( szBuffer, "maxplayers" ); + if ( s && s[0] ) + { + item->maxplayers = atoi( s ); + item->maxplayers = max( item->maxplayers, 0 ); + item->maxplayers = min( item->maxplayers, gpGlobals->maxClients ); + } + + // Remove keys + // + g_engfuncs.pfnInfo_RemoveKey( szBuffer, "minplayers" ); + g_engfuncs.pfnInfo_RemoveKey( szBuffer, "maxplayers" ); + + strcpy( item->rulebuffer, szBuffer ); + } + + item->next = cycle->items; + cycle->items = item; + } + else + { + ALERT( at_console, "Skipping %s from mapcycle, not a valid map\n", szMap ); + } + + } + + FREE_FILE( aFileList ); + } + + // Fixup circular list pointer + item = cycle->items; + + // Reverse it to get original order + while ( item ) + { + next = item->next; + item->next = newlist; + newlist = item; + item = next; + } + cycle->items = newlist; + item = cycle->items; + + // Didn't parse anything + if ( !item ) + { + return 0; + } + + while ( item->next ) + { + item = item->next; + } + item->next = cycle->items; + + cycle->next_item = item->next; + + return 1; +} + +/* +============== +CountPlayers + +Determine the current # of active players on the server for map cycling logic +============== +*/ +int CountPlayers( void ) +{ + int num = 0; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pEnt = UTIL_PlayerByIndex( i ); + + if ( pEnt ) + { + num = num + 1; + } + } + + return num; +} + +/* +============== +ExtractCommandString + +Parse commands/key value pairs to issue right after map xxx command is issued on server + level transition +============== +*/ +void ExtractCommandString( char *s, char *szCommand ) +{ + // Now make rules happen + char pkey[512]; + char value[512]; // use two buffers so compares + // work without stomping on each other + char *o; + + if ( *s == '\\' ) + s++; + + while (1) + { + o = pkey; + while ( *s != '\\' ) + { + if ( !*s ) + return; + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + + while (*s != '\\' && *s) + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + + strcat( szCommand, pkey ); + if ( strlen( value ) > 0 ) + { + strcat( szCommand, " " ); + strcat( szCommand, value ); + } + strcat( szCommand, "\n" ); + + if (!*s) + return; + s++; + } +} + +/* +============== +ChangeLevel + +Server is changing to a new level, check mapcycle.txt for map name and setup info +============== +*/ +void CHalfLifeMultiplay :: ChangeLevel( void ) +{ + static char szPreviousMapCycleFile[ 256 ]; + static mapcycle_t mapcycle; + + char szNextMap[32]; + char szFirstMapInList[32]; + char szCommands[ 1500 ]; + char szRules[ 1500 ]; + int minplayers = 0, maxplayers = 0; + strcpy( szFirstMapInList, "hldm1" ); // the absolute default level is hldm1 + + int curplayers; + BOOL do_cycle = TRUE; + + // find the map to change to + char *mapcfile = (char*)CVAR_GET_STRING( "mapcyclefile" ); + ASSERT( mapcfile != NULL ); + + szCommands[ 0 ] = '\0'; + szRules[ 0 ] = '\0'; + + curplayers = CountPlayers(); + + // Has the map cycle filename changed? + if ( stricmp( mapcfile, szPreviousMapCycleFile ) ) + { + strcpy( szPreviousMapCycleFile, mapcfile ); + + DestroyMapCycle( &mapcycle ); + + if ( !ReloadMapCycleFile( mapcfile, &mapcycle ) || ( !mapcycle.items ) ) + { + ALERT( at_console, "Unable to load map cycle file %s\n", mapcfile ); + do_cycle = FALSE; + } + } + + if ( do_cycle && mapcycle.items ) + { + BOOL keeplooking = FALSE; + BOOL found = FALSE; + mapcycle_item_s *item; + + // Assume current map + strcpy( szNextMap, STRING(gpGlobals->mapname) ); + strcpy( szFirstMapInList, STRING(gpGlobals->mapname) ); + + // Traverse list + for ( item = mapcycle.next_item; item->next != mapcycle.next_item; item = item->next ) + { + keeplooking = FALSE; + + ASSERT( item != NULL ); + + if ( item->minplayers != 0 ) + { + if ( curplayers >= item->minplayers ) + { + found = TRUE; + minplayers = item->minplayers; + } + else + { + keeplooking = TRUE; + } + } + + if ( item->maxplayers != 0 ) + { + if ( curplayers <= item->maxplayers ) + { + found = TRUE; + maxplayers = item->maxplayers; + } + else + { + keeplooking = TRUE; + } + } + + if ( keeplooking ) + continue; + + found = TRUE; + break; + } + + if ( !found ) + { + item = mapcycle.next_item; + } + + // Increment next item pointer + mapcycle.next_item = item->next; + + // Perform logic on current item + strcpy( szNextMap, item->mapname ); + + ExtractCommandString( item->rulebuffer, szCommands ); + strcpy( szRules, item->rulebuffer ); + } + + if ( !IS_MAP_VALID(szNextMap) ) + { + strcpy( szNextMap, szFirstMapInList ); + } + + g_fGameOver = TRUE; + + ALERT( at_console, "CHANGE LEVEL: %s\n", szNextMap ); + if ( minplayers || maxplayers ) + { + ALERT( at_console, "PLAYER COUNT: min %i max %i current %i\n", minplayers, maxplayers, curplayers ); + } + if ( strlen( szRules ) > 0 ) + { + ALERT( at_console, "RULES: %s\n", szRules ); + } + + CHANGE_LEVEL( szNextMap, NULL ); + if ( strlen( szCommands ) > 0 ) + { + SERVER_COMMAND( szCommands ); + } +} + +#define MAX_MOTD_CHUNK 60 +#define MAX_MOTD_LENGTH 1536 // (MAX_MOTD_CHUNK * 4) + +void CHalfLifeMultiplay :: SendMOTDToClient( edict_t *client ) +{ + // read from the MOTD.txt file + int length, char_count = 0; + char *pFileList; + char *aFileList = pFileList = (char*)LOAD_FILE_FOR_ME( (char *)CVAR_GET_STRING( "motdfile" ), &length ); + + // send the server name + MESSAGE_BEGIN( MSG_ONE, gmsgServerName, NULL, client ); + WRITE_STRING( CVAR_GET_STRING("hostname") ); + MESSAGE_END(); + + // Send the message of the day + // read it chunk-by-chunk, and send it in parts + + while ( pFileList && *pFileList && char_count < MAX_MOTD_LENGTH ) + { + char chunk[MAX_MOTD_CHUNK+1]; + + if ( strlen( pFileList ) < MAX_MOTD_CHUNK ) + { + strcpy( chunk, pFileList ); + } + else + { + strncpy( chunk, pFileList, MAX_MOTD_CHUNK ); + chunk[MAX_MOTD_CHUNK] = 0; // strncpy doesn't always append the null terminator + } + + char_count += strlen( chunk ); + if ( char_count < MAX_MOTD_LENGTH ) + pFileList = aFileList + char_count; + else + *pFileList = 0; + + MESSAGE_BEGIN( MSG_ONE, gmsgMOTD, NULL, client ); + WRITE_BYTE( *pFileList ? FALSE : TRUE ); // FALSE means there is still more message to come + WRITE_STRING( chunk ); + MESSAGE_END(); + } + + FREE_FILE( aFileList ); +} + + diff --git a/dlls/nihilanth.cpp b/dlls/nihilanth.cpp new file mode 100644 index 0000000..6c0a500 --- /dev/null +++ b/dlls/nihilanth.cpp @@ -0,0 +1,1852 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "effects.h" + +#define N_SCALE 15 +#define N_SPHERES 20 + +class CNihilanth : public CBaseMonster +{ +public: + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + int Classify( void ) { return CLASS_ALIEN_MILITARY; }; + int BloodColor( void ) { return BLOOD_COLOR_YELLOW; } + void Killed( entvars_t *pevAttacker, int iGib ); + void GibMonster( void ); + + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector( -16 * N_SCALE, -16 * N_SCALE, -48 * N_SCALE ); + pev->absmax = pev->origin + Vector( 16 * N_SCALE, 16 * N_SCALE, 28 * N_SCALE ); + } + + void HandleAnimEvent( MonsterEvent_t *pEvent ); + + void EXPORT StartupThink( void ); + void EXPORT HuntThink( void ); + void EXPORT CrashTouch( CBaseEntity *pOther ); + void EXPORT DyingThink( void ); + void EXPORT StartupUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT NullThink( void ); + void EXPORT CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + void FloatSequence( void ); + void NextActivity( void ); + + void Flight( void ); + + BOOL AbsorbSphere( void ); + BOOL EmitSphere( void ); + void TargetSphere( USE_TYPE useType, float value ); + CBaseEntity *RandomTargetname( const char *szName ); + void ShootBalls( void ); + void MakeFriend( Vector vecPos ); + + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + + void PainSound( void ); + void DeathSound( void ); + + static const char *pAttackSounds[]; // vocalization: play sometimes when he launches an attack + static const char *pBallSounds[]; // the sound of the lightening ball launch + static const char *pShootSounds[]; // grunting vocalization: play sometimes when he launches an attack + static const char *pRechargeSounds[]; // vocalization: play when he recharges + static const char *pLaughSounds[]; // vocalization: play sometimes when hit and still has lots of health + static const char *pPainSounds[]; // vocalization: play sometimes when hit and has much less health and no more chargers + static const char *pDeathSounds[]; // vocalization: play as he dies + + // x_teleattack1.wav the looping sound of the teleport attack ball. + + float m_flForce; + + float m_flNextPainSound; + + Vector m_velocity; + Vector m_avelocity; + + Vector m_vecTarget; + Vector m_posTarget; + + Vector m_vecDesired; + Vector m_posDesired; + + float m_flMinZ; + float m_flMaxZ; + + Vector m_vecGoal; + + float m_flLastSeen; + float m_flPrevSeen; + + int m_irritation; + + int m_iLevel; + int m_iTeleport; + + EHANDLE m_hRecharger; + + EHANDLE m_hSphere[N_SPHERES]; + int m_iActiveSpheres; + + float m_flAdj; + + CSprite *m_pBall; + + char m_szRechargerTarget[64]; + char m_szDrawUse[64]; + char m_szTeleportUse[64]; + char m_szTeleportTouch[64]; + char m_szDeadUse[64]; + char m_szDeadTouch[64]; + + float m_flShootEnd; + float m_flShootTime; + + EHANDLE m_hFriend[3]; +}; + +LINK_ENTITY_TO_CLASS( monster_nihilanth, CNihilanth ); + +TYPEDESCRIPTION CNihilanth::m_SaveData[] = +{ + DEFINE_FIELD( CNihilanth, m_flForce, FIELD_FLOAT ), + DEFINE_FIELD( CNihilanth, m_flNextPainSound, FIELD_TIME ), + DEFINE_FIELD( CNihilanth, m_velocity, FIELD_VECTOR ), + DEFINE_FIELD( CNihilanth, m_avelocity, FIELD_VECTOR ), + DEFINE_FIELD( CNihilanth, m_vecTarget, FIELD_VECTOR ), + DEFINE_FIELD( CNihilanth, m_posTarget, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CNihilanth, m_vecDesired, FIELD_VECTOR ), + DEFINE_FIELD( CNihilanth, m_posDesired, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CNihilanth, m_flMinZ, FIELD_FLOAT ), + DEFINE_FIELD( CNihilanth, m_flMaxZ, FIELD_FLOAT ), + DEFINE_FIELD( CNihilanth, m_vecGoal, FIELD_VECTOR ), + DEFINE_FIELD( CNihilanth, m_flLastSeen, FIELD_TIME ), + DEFINE_FIELD( CNihilanth, m_flPrevSeen, FIELD_TIME ), + DEFINE_FIELD( CNihilanth, m_irritation, FIELD_INTEGER ), + DEFINE_FIELD( CNihilanth, m_iLevel, FIELD_INTEGER ), + DEFINE_FIELD( CNihilanth, m_iTeleport, FIELD_INTEGER ), + DEFINE_FIELD( CNihilanth, m_hRecharger, FIELD_EHANDLE ), + DEFINE_ARRAY( CNihilanth, m_hSphere, FIELD_EHANDLE, N_SPHERES ), + DEFINE_FIELD( CNihilanth, m_iActiveSpheres, FIELD_INTEGER ), + DEFINE_FIELD( CNihilanth, m_flAdj, FIELD_FLOAT ), + DEFINE_FIELD( CNihilanth, m_pBall, FIELD_CLASSPTR ), + DEFINE_ARRAY( CNihilanth, m_szRechargerTarget, FIELD_CHARACTER, 64 ), + DEFINE_ARRAY( CNihilanth, m_szDrawUse, FIELD_CHARACTER, 64 ), + DEFINE_ARRAY( CNihilanth, m_szTeleportUse, FIELD_CHARACTER, 64 ), + DEFINE_ARRAY( CNihilanth, m_szTeleportTouch, FIELD_CHARACTER, 64 ), + DEFINE_ARRAY( CNihilanth, m_szDeadUse, FIELD_CHARACTER, 64 ), + DEFINE_ARRAY( CNihilanth, m_szDeadTouch, FIELD_CHARACTER, 64 ), + DEFINE_FIELD( CNihilanth, m_flShootEnd, FIELD_TIME ), + DEFINE_FIELD( CNihilanth, m_flShootTime, FIELD_TIME ), + DEFINE_ARRAY( CNihilanth, m_hFriend, FIELD_EHANDLE, 3 ), +}; + +IMPLEMENT_SAVERESTORE( CNihilanth, CBaseMonster ); + +class CNihilanthHVR : public CBaseMonster +{ +public: + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + + void CircleInit( CBaseEntity *pTarget ); + void AbsorbInit( void ); + void TeleportInit( CNihilanth *pOwner, CBaseEntity *pEnemy, CBaseEntity *pTarget, CBaseEntity *pTouch ); + void GreenBallInit( void ); + void ZapInit( CBaseEntity *pEnemy ); + + void EXPORT HoverThink( void ); + BOOL CircleTarget( Vector vecTarget ); + void EXPORT DissipateThink( void ); + + void EXPORT ZapThink( void ); + void EXPORT TeleportThink( void ); + void EXPORT TeleportTouch( CBaseEntity *pOther ); + + void EXPORT RemoveTouch( CBaseEntity *pOther ); + void EXPORT BounceTouch( CBaseEntity *pOther ); + void EXPORT ZapTouch( CBaseEntity *pOther ); + + CBaseEntity *RandomClassname( const char *szName ); + + // void EXPORT SphereUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + void MovetoTarget( Vector vecTarget ); + virtual void Crawl( void ); + + void Zap( void ); + void Teleport( void ); + + float m_flIdealVel; + Vector m_vecIdeal; + CNihilanth *m_pNihilanth; + EHANDLE m_hTouch; + int m_nFrames; +}; + +LINK_ENTITY_TO_CLASS( nihilanth_energy_ball, CNihilanthHVR ); + + +TYPEDESCRIPTION CNihilanthHVR::m_SaveData[] = +{ + DEFINE_FIELD( CNihilanthHVR, m_flIdealVel, FIELD_FLOAT ), + DEFINE_FIELD( CNihilanthHVR, m_vecIdeal, FIELD_VECTOR ), + DEFINE_FIELD( CNihilanthHVR, m_pNihilanth, FIELD_CLASSPTR ), + DEFINE_FIELD( CNihilanthHVR, m_hTouch, FIELD_EHANDLE ), + DEFINE_FIELD( CNihilanthHVR, m_nFrames, FIELD_INTEGER ), +}; + + +IMPLEMENT_SAVERESTORE( CNihilanthHVR, CBaseMonster ); + + +//========================================================= +// Nihilanth, final Boss monster +//========================================================= + +const char *CNihilanth::pAttackSounds[] = +{ + "X/x_attack1.wav", + "X/x_attack2.wav", + "X/x_attack3.wav", +}; + +const char *CNihilanth::pBallSounds[] = +{ + "X/x_ballattack1.wav", +}; + +const char *CNihilanth::pShootSounds[] = +{ + "X/x_shoot1.wav", +}; + +const char *CNihilanth::pRechargeSounds[] = +{ + "X/x_recharge1.wav", + "X/x_recharge2.wav", + "X/x_recharge3.wav", +}; + +const char *CNihilanth::pLaughSounds[] = +{ + "X/x_laugh1.wav", + "X/x_laugh2.wav", +}; + +const char *CNihilanth::pPainSounds[] = +{ + "X/x_pain1.wav", + "X/x_pain2.wav", +}; + +const char *CNihilanth::pDeathSounds[] = +{ + "X/x_die1.wav", +}; + + +void CNihilanth :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(edict(), "models/nihilanth.mdl"); + // UTIL_SetSize(pev, Vector( -300, -300, 0), Vector(300, 300, 512)); + UTIL_SetSize(pev, Vector( -32, -32, 0), Vector(32, 32, 64)); + UTIL_SetOrigin( pev, pev->origin ); + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_AIM; + pev->health = gSkillData.nihilanthHealth; + pev->view_ofs = Vector( 0, 0, 300 ); + + m_flFieldOfView = -1; // 360 degrees + + pev->sequence = 0; + ResetSequenceInfo( ); + + InitBoneControllers(); + + SetThink( &CNihilanth::StartupThink ); + pev->nextthink = gpGlobals->time + 0.1; + + m_vecDesired = Vector( 1, 0, 0 ); + m_posDesired = Vector( pev->origin.x, pev->origin.y, 512 ); + + m_iLevel = 1; + m_iTeleport = 1; + + if (m_szRechargerTarget[0] == '\0') strcpy( m_szRechargerTarget, "n_recharger" ); + if (m_szDrawUse[0] == '\0') strcpy( m_szDrawUse, "n_draw" ); + if (m_szTeleportUse[0] == '\0') strcpy( m_szTeleportUse, "n_leaving" ); + if (m_szTeleportTouch[0] == '\0') strcpy( m_szTeleportTouch, "n_teleport" ); + if (m_szDeadUse[0] == '\0') strcpy( m_szDeadUse, "n_dead" ); + if (m_szDeadTouch[0] == '\0') strcpy( m_szDeadTouch, "n_ending" ); + + // near death + /* + m_iTeleport = 10; + m_iLevel = 10; + m_irritation = 2; + pev->health = 100; + */ +} + + +void CNihilanth::Precache( void ) +{ + PRECACHE_MODEL("models/nihilanth.mdl"); + PRECACHE_MODEL("sprites/lgtning.spr"); + UTIL_PrecacheOther( "nihilanth_energy_ball" ); + UTIL_PrecacheOther( "monster_alien_controller" ); + UTIL_PrecacheOther( "monster_alien_slave" ); + + PRECACHE_SOUND_ARRAY( pAttackSounds ); + PRECACHE_SOUND_ARRAY( pBallSounds ); + PRECACHE_SOUND_ARRAY( pShootSounds ); + PRECACHE_SOUND_ARRAY( pRechargeSounds ); + PRECACHE_SOUND_ARRAY( pLaughSounds ); + PRECACHE_SOUND_ARRAY( pPainSounds ); + PRECACHE_SOUND_ARRAY( pDeathSounds ); + PRECACHE_SOUND("debris/beamstart7.wav"); +} + + + +void CNihilanth :: PainSound( void ) +{ + if (m_flNextPainSound > gpGlobals->time) + return; + + m_flNextPainSound = gpGlobals->time + RANDOM_FLOAT( 2, 5 ); + + if (pev->health > gSkillData.nihilanthHealth / 2) + { + EMIT_SOUND( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY( pLaughSounds ), 1.0, 0.2 ); + } + else if (m_irritation >= 2) + { + EMIT_SOUND( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY( pPainSounds ), 1.0, 0.2 ); + } +} + +void CNihilanth :: DeathSound( void ) +{ + EMIT_SOUND( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY( pDeathSounds ), 1.0, 0.1 ); +} + + +void CNihilanth::NullThink( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.5; +} + + +void CNihilanth::StartupUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( &CNihilanth::HuntThink ); + pev->nextthink = gpGlobals->time + 0.1; + SetUse( &CNihilanth::CommandUse ); +} + + +void CNihilanth::StartupThink( void ) +{ + m_irritation = 0; + m_flAdj = 512; + + CBaseEntity *pEntity; + + pEntity = UTIL_FindEntityByTargetname( NULL, "n_min"); + if (pEntity) + m_flMinZ = pEntity->pev->origin.z; + else + m_flMinZ = -4096; + + pEntity = UTIL_FindEntityByTargetname( NULL, "n_max"); + if (pEntity) + m_flMaxZ = pEntity->pev->origin.z; + else + m_flMaxZ = 4096; + + m_hRecharger = this; + for (int i = 0; i < N_SPHERES; i++) + { + EmitSphere( ); + } + m_hRecharger = NULL; + + SetThink( &CNihilanth::HuntThink); + SetUse( &CNihilanth::CommandUse ); + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CNihilanth :: Killed( entvars_t *pevAttacker, int iGib ) +{ + CBaseMonster::Killed( pevAttacker, iGib ); +} + +void CNihilanth :: DyingThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + DispatchAnimEvents( ); + StudioFrameAdvance( ); + + if (pev->deadflag == DEAD_NO) + { + DeathSound( ); + pev->deadflag = DEAD_DYING; + + m_posDesired.z = m_flMaxZ; + } + + if (pev->deadflag == DEAD_DYING) + { + Flight( ); + + if (fabs( pev->origin.z - m_flMaxZ ) < 16) + { + pev->velocity = Vector( 0, 0, 0 ); + FireTargets( m_szDeadUse, this, this, USE_ON, 1.0 ); + pev->deadflag = DEAD_DEAD; + } + } + + if (m_fSequenceFinished) + { + pev->avelocity.y += RANDOM_FLOAT( -100, 100 ); + if (pev->avelocity.y < -100) + pev->avelocity.y = -100; + if (pev->avelocity.y > 100) + pev->avelocity.y = 100; + + pev->sequence = LookupSequence( "die1" ); + } + + if (m_pBall) + { + if (m_pBall->pev->renderamt > 0) + { + m_pBall->pev->renderamt = max( 0, m_pBall->pev->renderamt - 2); + } + else + { + UTIL_Remove( m_pBall ); + m_pBall = NULL; + } + } + + Vector vecDir, vecSrc, vecAngles; + + UTIL_MakeAimVectors( pev->angles ); + int iAttachment = RANDOM_LONG( 1, 4 ); + + do { + vecDir = Vector( RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 )); + } while (DotProduct( vecDir, vecDir) > 1.0); + + switch( RANDOM_LONG( 1, 4 )) + { + case 1: // head + vecDir.z = fabs( vecDir.z ) * 0.5; + vecDir = vecDir + 2 * gpGlobals->v_up; + break; + case 2: // eyes + if (DotProduct( vecDir, gpGlobals->v_forward ) < 0) + vecDir = vecDir * -1; + + vecDir = vecDir + 2 * gpGlobals->v_forward; + break; + case 3: // left hand + if (DotProduct( vecDir, gpGlobals->v_right ) > 0) + vecDir = vecDir * -1; + vecDir = vecDir - 2 * gpGlobals->v_right; + break; + case 4: // right hand + if (DotProduct( vecDir, gpGlobals->v_right ) < 0) + vecDir = vecDir * -1; + vecDir = vecDir + 2 * gpGlobals->v_right; + break; + } + + GetAttachment( iAttachment - 1, vecSrc, vecAngles ); + + TraceResult tr; + + UTIL_TraceLine( vecSrc, vecSrc + vecDir * 4096, ignore_monsters, ENT(pev), &tr ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( entindex() + 0x1000 * iAttachment ); + WRITE_COORD( tr.vecEndPos.x); + WRITE_COORD( tr.vecEndPos.y); + WRITE_COORD( tr.vecEndPos.z); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 5 ); // life + WRITE_BYTE( 100 ); // width + WRITE_BYTE( 120 ); // noise + WRITE_BYTE( 64 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 255); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); + + GetAttachment( 0, vecSrc, vecAngles ); + CNihilanthHVR *pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); + pEntity->pev->velocity = Vector ( RANDOM_FLOAT( -0.7, 0.7 ), RANDOM_FLOAT( -0.7, 0.7 ), 1.0 ) * 600.0; + pEntity->GreenBallInit( ); + + return; +} + + + +void CNihilanth::CrashTouch( CBaseEntity *pOther ) +{ + // only crash if we hit something solid + if ( pOther->pev->solid == SOLID_BSP) + { + SetTouch( NULL ); + pev->nextthink = gpGlobals->time; + } +} + + + +void CNihilanth :: GibMonster( void ) +{ + // EMIT_SOUND_DYN(edict(), CHAN_VOICE, "common/bodysplat.wav", 0.75, ATTN_NORM, 0, 200); +} + + + +void CNihilanth :: FloatSequence( void ) +{ + if (m_irritation >= 2) + { + pev->sequence = LookupSequence( "float_open" ); + } + else if (m_avelocity.y > 30) + { + pev->sequence = LookupSequence( "walk_r" ); + } + else if (m_avelocity.y < -30) + { + pev->sequence = LookupSequence( "walk_l" ); + } + else if (m_velocity.z > 30) + { + pev->sequence = LookupSequence( "walk_u" ); + } + else if (m_velocity.z < -30) + { + pev->sequence = LookupSequence( "walk_d" ); + } + else + { + pev->sequence = LookupSequence( "float" ); + } +} + + +void CNihilanth :: ShootBalls( void ) +{ + if (m_flShootEnd > gpGlobals->time) + { + Vector vecHand, vecAngle; + + while (m_flShootTime < m_flShootEnd && m_flShootTime < gpGlobals->time) + { + if (m_hEnemy != NULL) + { + Vector vecSrc, vecDir; + CNihilanthHVR *pEntity; + + GetAttachment( 2, vecHand, vecAngle ); + vecSrc = vecHand + pev->velocity * (m_flShootTime - gpGlobals->time); + // vecDir = (m_posTarget - vecSrc).Normalize( ); + vecDir = (m_posTarget - pev->origin).Normalize( ); + vecSrc = vecSrc + vecDir * (gpGlobals->time - m_flShootTime); + pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); + pEntity->pev->velocity = vecDir * 200.0; + pEntity->ZapInit( m_hEnemy ); + + GetAttachment( 3, vecHand, vecAngle ); + vecSrc = vecHand + pev->velocity * (m_flShootTime - gpGlobals->time); + // vecDir = (m_posTarget - vecSrc).Normalize( ); + vecDir = (m_posTarget - pev->origin).Normalize( ); + vecSrc = vecSrc + vecDir * (gpGlobals->time - m_flShootTime); + pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); + pEntity->pev->velocity = vecDir * 200.0; + pEntity->ZapInit( m_hEnemy ); + } + m_flShootTime += 0.2; + } + } +} + + +void CNihilanth :: MakeFriend( Vector vecStart ) +{ + int i; + + for (i = 0; i < 3; i++) + { + if (m_hFriend[i] != NULL && !m_hFriend[i]->IsAlive()) + { + if (pev->rendermode == kRenderNormal) // don't do it if they are already fading + m_hFriend[i]->MyMonsterPointer()->FadeMonster( ); + m_hFriend[i] = NULL; + } + + if (m_hFriend[i] == NULL) + { + if (RANDOM_LONG(0, 1) == 0) + { + int iNode = WorldGraph.FindNearestNode ( vecStart, bits_NODE_AIR ); + if (iNode != NO_NODE) + { + CNode &node = WorldGraph.Node( iNode ); + TraceResult tr; + UTIL_TraceHull( node.m_vecOrigin + Vector( 0, 0, 32 ), node.m_vecOrigin + Vector( 0, 0, 32 ), dont_ignore_monsters, large_hull, NULL, &tr ); + if (tr.fStartSolid == 0) + m_hFriend[i] = Create("monster_alien_controller", node.m_vecOrigin, pev->angles ); + } + } + else + { + int iNode = WorldGraph.FindNearestNode ( vecStart, bits_NODE_LAND | bits_NODE_WATER ); + if (iNode != NO_NODE) + { + CNode &node = WorldGraph.Node( iNode ); + TraceResult tr; + UTIL_TraceHull( node.m_vecOrigin + Vector( 0, 0, 36 ), node.m_vecOrigin + Vector( 0, 0, 36 ), dont_ignore_monsters, human_hull, NULL, &tr ); + if (tr.fStartSolid == 0) + m_hFriend[i] = Create("monster_alien_slave", node.m_vecOrigin, pev->angles ); + } + } + if (m_hFriend[i] != NULL) + { + EMIT_SOUND( m_hFriend[i]->edict(), CHAN_WEAPON, "debris/beamstart7.wav", 1.0, ATTN_NORM ); + } + + return; + } + } +} + + +void CNihilanth :: NextActivity( ) +{ + UTIL_MakeAimVectors( pev->angles ); + + if (m_irritation >= 2) + { + if (m_pBall == NULL) + { + m_pBall = CSprite::SpriteCreate( "sprites/tele1.spr", pev->origin, TRUE ); + if (m_pBall) + { + m_pBall->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); + m_pBall->SetAttachment( edict(), 1 ); + m_pBall->SetScale( 4.0 ); + m_pBall->pev->framerate = 10.0; + m_pBall->TurnOn( ); + } + } + + if (m_pBall) + { + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x1000 ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( 256 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 192 ); // G + WRITE_BYTE( 64 ); // B + WRITE_BYTE( 200 ); // life * 10 + WRITE_COORD( 0 ); // decay + MESSAGE_END(); + } + } + + if ((pev->health < gSkillData.nihilanthHealth / 2 || m_iActiveSpheres < N_SPHERES / 2) && m_hRecharger == NULL && m_iLevel <= 9) + { + char szName[64]; + + CBaseEntity *pEnt = NULL; + CBaseEntity *pRecharger = NULL; + float flDist = 8192; + + sprintf(szName, "%s%d", m_szRechargerTarget, m_iLevel ); + + while ((pEnt = UTIL_FindEntityByTargetname( pEnt, szName )) != NULL) + { + float flLocal = (pEnt->pev->origin - pev->origin).Length(); + if (flLocal < flDist) + { + flDist = flLocal; + pRecharger = pEnt; + } + } + + if (pRecharger) + { + m_hRecharger = pRecharger; + m_posDesired = Vector( pev->origin.x, pev->origin.y, pRecharger->pev->origin.z ); + m_vecDesired = (pRecharger->pev->origin - m_posDesired).Normalize( ); + m_vecDesired.z = 0; + m_vecDesired = m_vecDesired.Normalize(); + } + else + { + m_hRecharger = NULL; + ALERT( at_aiconsole, "nihilanth can't find %s\n", szName ); + m_iLevel++; + if (m_iLevel > 9) + m_irritation = 2; + } + } + + float flDist = (m_posDesired - pev->origin).Length(); + float flDot = DotProduct( m_vecDesired, gpGlobals->v_forward ); + + if (m_hRecharger != NULL) + { + // at we at power up yet? + if (flDist < 128.0) + { + int iseq = LookupSequence( "recharge" ); + + if (iseq != pev->sequence) + { + char szText[64]; + + sprintf( szText, "%s%d", m_szDrawUse, m_iLevel ); + FireTargets( szText, this, this, USE_ON, 1.0 ); + + ALERT( at_console, "fireing %s\n", szText ); + } + pev->sequence = LookupSequence( "recharge" ); + } + else + { + FloatSequence( ); + } + return; + } + + if (m_hEnemy != NULL && !m_hEnemy->IsAlive()) + { + m_hEnemy = NULL; + } + + if (m_flLastSeen + 15 < gpGlobals->time) + { + m_hEnemy = NULL; + } + + if (m_hEnemy == NULL) + { + Look( 4096 ); + m_hEnemy = BestVisibleEnemy( ); + } + + if (m_hEnemy != NULL && m_irritation != 0) + { + if (m_flLastSeen + 5 > gpGlobals->time && flDist < 256 && flDot > 0) + { + if (m_irritation >= 2 && pev->health < gSkillData.nihilanthHealth / 2.0) + { + pev->sequence = LookupSequence( "attack1_open" ); + } + else + { + if (RANDOM_LONG(0, 1 ) == 0) + { + pev->sequence = LookupSequence( "attack1" ); // zap + } + else + { + char szText[64]; + + sprintf( szText, "%s%d", m_szTeleportTouch, m_iTeleport ); + CBaseEntity *pTouch = UTIL_FindEntityByTargetname( NULL, szText ); + + sprintf( szText, "%s%d", m_szTeleportUse, m_iTeleport ); + CBaseEntity *pTrigger = UTIL_FindEntityByTargetname( NULL, szText ); + + if (pTrigger != NULL || pTouch != NULL) + { + pev->sequence = LookupSequence( "attack2" ); // teleport + } + else + { + m_iTeleport++; + pev->sequence = LookupSequence( "attack1" ); // zap + } + } + } + return; + } + } + + FloatSequence( ); +} + +void CNihilanth :: HuntThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + DispatchAnimEvents( ); + StudioFrameAdvance( ); + + ShootBalls( ); + + // if dead, force cancelation of current animation + if (pev->health <= 0) + { + SetThink( &CNihilanth::DyingThink ); + m_fSequenceFinished = TRUE; + return; + } + + // ALERT( at_console, "health %.0f\n", pev->health ); + + // if damaged, try to abosorb some spheres + if (pev->health < gSkillData.nihilanthHealth && AbsorbSphere( )) + { + pev->health += gSkillData.nihilanthHealth / N_SPHERES; + } + + // get new sequence + if (m_fSequenceFinished) + { + // if (!m_fSequenceLoops) + pev->frame = 0; + NextActivity( ); + ResetSequenceInfo( ); + pev->framerate = 2.0 - 1.0 * (pev->health / gSkillData.nihilanthHealth); + } + + // look for current enemy + if (m_hEnemy != NULL && m_hRecharger == NULL) + { + if (FVisible( m_hEnemy )) + { + if (m_flLastSeen < gpGlobals->time - 5) + m_flPrevSeen = gpGlobals->time; + m_flLastSeen = gpGlobals->time; + m_posTarget = m_hEnemy->pev->origin; + m_vecTarget = (m_posTarget - pev->origin).Normalize(); + m_vecDesired = m_vecTarget; + m_posDesired = Vector( pev->origin.x, pev->origin.y, m_posTarget.z + m_flAdj ); + } + else + { + m_flAdj = min( m_flAdj + 10, 1000 ); + } + } + + // don't go too high + if (m_posDesired.z > m_flMaxZ) + m_posDesired.z = m_flMaxZ; + + // don't go too low + if (m_posDesired.z < m_flMinZ) + m_posDesired.z = m_flMinZ; + + Flight( ); +} + + + +void CNihilanth :: Flight( void ) +{ + // estimate where I'll be facing in one seconds + UTIL_MakeAimVectors( pev->angles + m_avelocity ); + // Vector vecEst1 = pev->origin + m_velocity + gpGlobals->v_up * m_flForce - Vector( 0, 0, 384 ); + // float flSide = DotProduct( m_posDesired - vecEst1, gpGlobals->v_right ); + + float flSide = DotProduct( m_vecDesired, gpGlobals->v_right ); + + if (flSide < 0) + { + if (m_avelocity.y < 180) + { + m_avelocity.y += 6; // 9 * (3.0/2.0); + } + } + else + { + if (m_avelocity.y > -180) + { + m_avelocity.y -= 6; // 9 * (3.0/2.0); + } + } + m_avelocity.y *= 0.98; + + // estimate where I'll be in two seconds + Vector vecEst = pev->origin + m_velocity * 2.0 + gpGlobals->v_up * m_flForce * 20; + + // add immediate force + UTIL_MakeAimVectors( pev->angles ); + m_velocity.x += gpGlobals->v_up.x * m_flForce; + m_velocity.y += gpGlobals->v_up.y * m_flForce; + m_velocity.z += gpGlobals->v_up.z * m_flForce; + + + float flSpeed = m_velocity.Length(); + float flDir = DotProduct( Vector( gpGlobals->v_forward.x, gpGlobals->v_forward.y, 0 ), Vector( m_velocity.x, m_velocity.y, 0 ) ); + if (flDir < 0) + flSpeed = -flSpeed; + + float flDist = DotProduct( m_posDesired - vecEst, gpGlobals->v_forward ); + + // sideways drag + m_velocity.x = m_velocity.x * (1.0 - fabs( gpGlobals->v_right.x ) * 0.05); + m_velocity.y = m_velocity.y * (1.0 - fabs( gpGlobals->v_right.y ) * 0.05); + m_velocity.z = m_velocity.z * (1.0 - fabs( gpGlobals->v_right.z ) * 0.05); + + // general drag + m_velocity = m_velocity * 0.995; + + // apply power to stay correct height + if (m_flForce < 100 && vecEst.z < m_posDesired.z) + { + m_flForce += 10; + } + else if (m_flForce > -100 && vecEst.z > m_posDesired.z) + { + if (vecEst.z > m_posDesired.z) + m_flForce -= 10; + } + + UTIL_SetOrigin( pev, pev->origin + m_velocity * 0.1 ); + pev->angles = pev->angles + m_avelocity * 0.1; + + // ALERT( at_console, "%5.0f %5.0f : %4.0f : %3.0f : %2.0f\n", m_posDesired.z, pev->origin.z, m_velocity.z, m_avelocity.y, m_flForce ); +} + + +BOOL CNihilanth :: AbsorbSphere( void ) +{ + for (int i = 0; i < N_SPHERES; i++) + { + if (m_hSphere[i] != NULL) + { + CNihilanthHVR *pSphere = (CNihilanthHVR *)((CBaseEntity *)m_hSphere[i]); + pSphere->AbsorbInit( ); + m_hSphere[i] = NULL; + m_iActiveSpheres--; + return TRUE; + } + } + return FALSE; +} + + +BOOL CNihilanth :: EmitSphere( void ) +{ + m_iActiveSpheres = 0; + int empty = 0; + + for (int i = 0; i < N_SPHERES; i++) + { + if (m_hSphere[i] != NULL) + { + m_iActiveSpheres++; + } + else + { + empty = i; + } + } + + if (m_iActiveSpheres >= N_SPHERES) + return FALSE; + + Vector vecSrc = m_hRecharger->pev->origin; + CNihilanthHVR *pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); + pEntity->pev->velocity = pev->origin - vecSrc; + pEntity->CircleInit( this ); + + m_hSphere[empty] = pEntity; + return TRUE; +} + + +void CNihilanth :: TargetSphere( USE_TYPE useType, float value ) +{ + CBaseMonster *pSphere; + int i; + for (i = 0; i < N_SPHERES; i++) + { + if (m_hSphere[i] != NULL) + { + pSphere = m_hSphere[i]->MyMonsterPointer(); + if (pSphere->m_hEnemy == NULL) + break; + } + } + if (i == N_SPHERES) + { + return; + } + + Vector vecSrc, vecAngles; + GetAttachment( 2, vecSrc, vecAngles ); + UTIL_SetOrigin( pSphere->pev, vecSrc ); + pSphere->Use( this, this, useType, value ); + pSphere->pev->velocity = m_vecDesired * RANDOM_FLOAT( 50, 100 ) + Vector( RANDOM_FLOAT( -50, 50 ), RANDOM_FLOAT( -50, 50 ), RANDOM_FLOAT( -50, 50 ) ); +} + + + +void CNihilanth :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case 1: // shoot + break; + case 2: // zen + if (m_hEnemy != NULL) + { + if (RANDOM_LONG(0,4) == 0) + EMIT_SOUND( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY( pAttackSounds ), 1.0, 0.2 ); + + EMIT_SOUND( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY( pBallSounds ), 1.0, 0.2 ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x3000 ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( 256 ); // radius + WRITE_BYTE( 128 ); // R + WRITE_BYTE( 128 ); // G + WRITE_BYTE( 255 ); // B + WRITE_BYTE( 10 ); // life * 10 + WRITE_COORD( 128 ); // decay + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x4000 ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( 256 ); // radius + WRITE_BYTE( 128 ); // R + WRITE_BYTE( 128 ); // G + WRITE_BYTE( 255 ); // B + WRITE_BYTE( 10 ); // life * 10 + WRITE_COORD( 128 ); // decay + MESSAGE_END(); + + m_flShootTime = gpGlobals->time; + m_flShootEnd = gpGlobals->time + 1.0; + } + break; + case 3: // prayer + if (m_hEnemy != NULL) + { + char szText[32]; + + sprintf( szText, "%s%d", m_szTeleportTouch, m_iTeleport ); + CBaseEntity *pTouch = UTIL_FindEntityByTargetname( NULL, szText ); + + sprintf( szText, "%s%d", m_szTeleportUse, m_iTeleport ); + CBaseEntity *pTrigger = UTIL_FindEntityByTargetname( NULL, szText ); + + if (pTrigger != NULL || pTouch != NULL) + { + EMIT_SOUND( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY( pAttackSounds ), 1.0, 0.2 ); + + Vector vecSrc, vecAngles; + GetAttachment( 2, vecSrc, vecAngles ); + CNihilanthHVR *pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); + pEntity->pev->velocity = pev->origin - vecSrc; + pEntity->TeleportInit( this, m_hEnemy, pTrigger, pTouch ); + } + else + { + m_iTeleport++; // unexpected failure + + EMIT_SOUND( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY( pBallSounds ), 1.0, 0.2 ); + + ALERT( at_aiconsole, "nihilanth can't target %s\n", szText ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x3000 ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( 256 ); // radius + WRITE_BYTE( 128 ); // R + WRITE_BYTE( 128 ); // G + WRITE_BYTE( 255 ); // B + WRITE_BYTE( 10 ); // life * 10 + WRITE_COORD( 128 ); // decay + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x4000 ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( 256 ); // radius + WRITE_BYTE( 128 ); // R + WRITE_BYTE( 128 ); // G + WRITE_BYTE( 255 ); // B + WRITE_BYTE( 10 ); // life * 10 + WRITE_COORD( 128 ); // decay + MESSAGE_END(); + + m_flShootTime = gpGlobals->time; + m_flShootEnd = gpGlobals->time + 1.0; + } + } + break; + case 4: // get a sphere + { + if (m_hRecharger != NULL) + { + if (!EmitSphere( )) + { + m_hRecharger = NULL; + } + } + } + break; + case 5: // start up sphere machine + { + EMIT_SOUND( edict(), CHAN_VOICE , RANDOM_SOUND_ARRAY( pRechargeSounds ), 1.0, 0.2 ); + } + break; + case 6: + if (m_hEnemy != NULL) + { + Vector vecSrc, vecAngles; + GetAttachment( 2, vecSrc, vecAngles ); + CNihilanthHVR *pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); + pEntity->pev->velocity = pev->origin - vecSrc; + pEntity->ZapInit( m_hEnemy ); + } + break; + case 7: + /* + Vector vecSrc, vecAngles; + GetAttachment( 0, vecSrc, vecAngles ); + CNihilanthHVR *pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); + pEntity->pev->velocity = Vector ( RANDOM_FLOAT( -0.7, 0.7 ), RANDOM_FLOAT( -0.7, 0.7 ), 1.0 ) * 600.0; + pEntity->GreenBallInit( ); + */ + break; + } +} + + + +void CNihilanth::CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + switch (useType) + { + case USE_OFF: + { + CBaseEntity *pTouch = UTIL_FindEntityByTargetname( NULL, m_szDeadTouch ); + + if ( pTouch ) + { + if ( m_hEnemy != NULL ) + { + pTouch->Touch( m_hEnemy ); + } + // if the player is using "notarget", the ending sequence won't fire unless we catch it here + else + { + CBaseEntity *pEntity = UTIL_FindEntityByClassname( NULL, "player" ); + if ( pEntity != NULL && pEntity->IsAlive() ) + { + pTouch->Touch( pEntity ); + } + } + } + } + break; + case USE_ON: + if (m_irritation == 0) + { + m_irritation = 1; + } + break; + case USE_SET: + break; + case USE_TOGGLE: + break; + } +} + + +int CNihilanth :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if (pevInflictor->owner == edict()) + return 0; + + if (flDamage >= pev->health) + { + pev->health = 1; + if (m_irritation != 3) + return 0; + } + + PainSound( ); + + pev->health -= flDamage; + return 0; +} + + + +void CNihilanth::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if (m_irritation == 3) + m_irritation = 2; + + if (m_irritation == 2 && ptr->iHitgroup == 2 && flDamage > 2) + m_irritation = 3; + + if (m_irritation != 3) + { + Vector vecBlood = (ptr->vecEndPos - pev->origin).Normalize( ); + + UTIL_BloodStream( ptr->vecEndPos, vecBlood, BloodColor(), flDamage + (100 - 100 * (pev->health / gSkillData.nihilanthHealth))); + } + + // SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage * 5.0);// a little surface blood. + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); +} + + + +CBaseEntity *CNihilanth::RandomTargetname( const char *szName ) +{ + int total = 0; + + CBaseEntity *pEntity = NULL; + CBaseEntity *pNewEntity = NULL; + while ((pNewEntity = UTIL_FindEntityByTargetname( pNewEntity, szName )) != NULL) + { + total++; + if (RANDOM_LONG(0,total-1) < 1) + pEntity = pNewEntity; + } + return pEntity; +} + + + + + + + + + +//========================================================= +// Controller bouncy ball attack +//========================================================= + + + +void CNihilanthHVR :: Spawn( void ) +{ + Precache( ); + + pev->rendermode = kRenderTransAdd; + pev->renderamt = 255; + pev->scale = 3.0; +} + + +void CNihilanthHVR :: Precache( void ) +{ + PRECACHE_MODEL("sprites/flare6.spr"); + PRECACHE_MODEL("sprites/nhth1.spr"); + PRECACHE_MODEL("sprites/exit1.spr"); + PRECACHE_MODEL("sprites/tele1.spr"); + PRECACHE_MODEL("sprites/animglow01.spr"); + PRECACHE_MODEL("sprites/xspark4.spr"); + PRECACHE_MODEL("sprites/muzzleflash3.spr"); + PRECACHE_SOUND("debris/zap4.wav"); + PRECACHE_SOUND("weapons/electro4.wav"); + PRECACHE_SOUND("x/x_teleattack1.wav"); +} + + + +void CNihilanthHVR :: CircleInit( CBaseEntity *pTarget ) +{ + pev->movetype = MOVETYPE_NOCLIP; + pev->solid = SOLID_NOT; + + // SET_MODEL(edict(), "sprites/flare6.spr"); + // pev->scale = 3.0; + // SET_MODEL(edict(), "sprites/xspark4.spr"); + SET_MODEL(edict(), "sprites/muzzleflash3.spr"); + pev->rendercolor.x = 255; + pev->rendercolor.y = 224; + pev->rendercolor.z = 192; + pev->scale = 2.0; + m_nFrames = 1; + pev->renderamt = 255; + + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( pev, pev->origin ); + + SetThink( &CNihilanthHVR::HoverThink ); + SetTouch( &CNihilanthHVR::BounceTouch ); + pev->nextthink = gpGlobals->time + 0.1; + + m_hTargetEnt = pTarget; +} + + +CBaseEntity *CNihilanthHVR::RandomClassname( const char *szName ) +{ + int total = 0; + + CBaseEntity *pEntity = NULL; + CBaseEntity *pNewEntity = NULL; + while ((pNewEntity = UTIL_FindEntityByClassname( pNewEntity, szName )) != NULL) + { + total++; + if (RANDOM_LONG(0,total-1) < 1) + pEntity = pNewEntity; + } + return pEntity; +} + +void CNihilanthHVR :: HoverThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if (m_hTargetEnt != NULL) + { + CircleTarget( m_hTargetEnt->pev->origin + Vector( 0, 0, 16 * N_SCALE ) ); + } + else + { + UTIL_Remove( this ); + } + + + if (RANDOM_LONG( 0, 99 ) < 5) + { +/* + CBaseEntity *pOther = RandomClassname( STRING(pev->classname) ); + + if (pOther && pOther != this) + { + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTS ); + WRITE_SHORT( this->entindex() ); + WRITE_SHORT( pOther->entindex() ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // framestart + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 10 ); // life + WRITE_BYTE( 80 ); // width + WRITE_BYTE( 80 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 64 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 30 ); // speed + MESSAGE_END(); + } +*/ +/* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTS ); + WRITE_SHORT( this->entindex() ); + WRITE_SHORT( m_hTargetEnt->entindex() + 0x1000 ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // framestart + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 10 ); // life + WRITE_BYTE( 80 ); // width + WRITE_BYTE( 80 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 64 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 30 ); // speed + MESSAGE_END(); +*/ + } + + pev->frame = ((int)pev->frame + 1) % m_nFrames; +} + + + + +void CNihilanthHVR :: ZapInit( CBaseEntity *pEnemy ) +{ + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(edict(), "sprites/nhth1.spr"); + + pev->rendercolor.x = 255; + pev->rendercolor.y = 255; + pev->rendercolor.z = 255; + pev->scale = 2.0; + + pev->velocity = (pEnemy->pev->origin - pev->origin).Normalize() * 200; + + m_hEnemy = pEnemy; + SetThink( &CNihilanthHVR::ZapThink ); + SetTouch( &CNihilanthHVR::ZapTouch ); + pev->nextthink = gpGlobals->time + 0.1; + + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, "debris/zap4.wav", 1, ATTN_NORM, 0, 100 ); +} + +void CNihilanthHVR :: ZapThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.05; + + // check world boundaries + if (m_hEnemy == NULL || pev->origin.x < -4096 || pev->origin.x > 4096 || pev->origin.y < -4096 || pev->origin.y > 4096 || pev->origin.z < -4096 || pev->origin.z > 4096) + { + SetTouch( NULL ); + UTIL_Remove( this ); + return; + } + + if (pev->velocity.Length() < 2000) + { + pev->velocity = pev->velocity * 1.2; + } + + + // MovetoTarget( m_hEnemy->Center( ) ); + + if ((m_hEnemy->Center() - pev->origin).Length() < 256) + { + TraceResult tr; + + UTIL_TraceLine( pev->origin, m_hEnemy->Center(), dont_ignore_monsters, edict(), &tr ); + + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + if (pEntity != NULL && pEntity->pev->takedamage) + { + ClearMultiDamage( ); + pEntity->TraceAttack( pev, gSkillData.nihilanthZap, pev->velocity, &tr, DMG_SHOCK ); + ApplyMultiDamage( pev, pev ); + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( entindex() ); + WRITE_COORD( tr.vecEndPos.x ); + WRITE_COORD( tr.vecEndPos.y ); + WRITE_COORD( tr.vecEndPos.z ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 3 ); // life + WRITE_BYTE( 20 ); // width + WRITE_BYTE( 20 ); // noise + WRITE_BYTE( 64 ); // r, g, b + WRITE_BYTE( 196 ); // r, g, b + WRITE_BYTE( 255); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); + + UTIL_EmitAmbientSound( edict(), tr.vecEndPos, "weapons/electro4.wav", 0.5, ATTN_NORM, 0, RANDOM_LONG( 140, 160 ) ); + + SetTouch( NULL ); + UTIL_Remove( this ); + pev->nextthink = gpGlobals->time + 0.2; + return; + } + + pev->frame = (int)(pev->frame + 1) % 11; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( 128 ); // radius + WRITE_BYTE( 128 ); // R + WRITE_BYTE( 128 ); // G + WRITE_BYTE( 255 ); // B + WRITE_BYTE( 10 ); // life * 10 + WRITE_COORD( 128 ); // decay + MESSAGE_END(); + + // Crawl( ); +} + + +void CNihilanthHVR::ZapTouch( CBaseEntity *pOther ) +{ + UTIL_EmitAmbientSound( edict(), pev->origin, "weapons/electro4.wav", 1.0, ATTN_NORM, 0, RANDOM_LONG( 90, 95 ) ); + + RadiusDamage( pev, pev, 50, CLASS_NONE, DMG_SHOCK ); + pev->velocity = pev->velocity * 0; + + /* + for (int i = 0; i < 10; i++) + { + Crawl( ); + } + */ + + SetTouch( NULL ); + UTIL_Remove( this ); + pev->nextthink = gpGlobals->time + 0.2; +} + + + +void CNihilanthHVR :: TeleportInit( CNihilanth *pOwner, CBaseEntity *pEnemy, CBaseEntity *pTarget, CBaseEntity *pTouch ) +{ + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + pev->rendercolor.x = 255; + pev->rendercolor.y = 255; + pev->rendercolor.z = 255; + pev->velocity.z *= 0.2; + + SET_MODEL(edict(), "sprites/exit1.spr"); + + m_pNihilanth = pOwner; + m_hEnemy = pEnemy; + m_hTargetEnt = pTarget; + m_hTouch = pTouch; + + SetThink( &CNihilanthHVR::TeleportThink ); + SetTouch( &CNihilanthHVR::TeleportTouch ); + pev->nextthink = gpGlobals->time + 0.1; + + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, "x/x_teleattack1.wav", 1, 0.2, 0, 100 ); +} + + +void CNihilanthHVR :: GreenBallInit( ) +{ + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + pev->rendercolor.x = 255; + pev->rendercolor.y = 255; + pev->rendercolor.z = 255; + pev->scale = 1.0; + + SET_MODEL(edict(), "sprites/exit1.spr"); + + SetTouch( &CNihilanthHVR::RemoveTouch ); +} + + +void CNihilanthHVR :: TeleportThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + // check world boundaries + if (m_hEnemy == NULL || !m_hEnemy->IsAlive() || pev->origin.x < -4096 || pev->origin.x > 4096 || pev->origin.y < -4096 || pev->origin.y > 4096 || pev->origin.z < -4096 || pev->origin.z > 4096) + { + STOP_SOUND(edict(), CHAN_WEAPON, "x/x_teleattack1.wav" ); + UTIL_Remove( this ); + return; + } + + if ((m_hEnemy->Center() - pev->origin).Length() < 128) + { + STOP_SOUND(edict(), CHAN_WEAPON, "x/x_teleattack1.wav" ); + UTIL_Remove( this ); + + if (m_hTargetEnt != NULL) + m_hTargetEnt->Use( m_hEnemy, m_hEnemy, USE_ON, 1.0 ); + + if ( m_hTouch != NULL && m_hEnemy != NULL ) + m_hTouch->Touch( m_hEnemy ); + } + else + { + MovetoTarget( m_hEnemy->Center( ) ); + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( 256 ); // radius + WRITE_BYTE( 0 ); // R + WRITE_BYTE( 255 ); // G + WRITE_BYTE( 0 ); // B + WRITE_BYTE( 10 ); // life * 10 + WRITE_COORD( 256 ); // decay + MESSAGE_END(); + + pev->frame = (int)(pev->frame + 1) % 20; +} + + +void CNihilanthHVR :: AbsorbInit( void ) +{ + SetThink( &CNihilanthHVR::DissipateThink ); + pev->renderamt = 255; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTS ); + WRITE_SHORT( this->entindex() ); + WRITE_SHORT( m_hTargetEnt->entindex() + 0x1000 ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // framestart + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 50 ); // life + WRITE_BYTE( 80 ); // width + WRITE_BYTE( 80 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 64 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 30 ); // speed + MESSAGE_END(); +} + +void CNihilanthHVR::TeleportTouch( CBaseEntity *pOther ) +{ + CBaseEntity *pEnemy = m_hEnemy; + + if (pOther == pEnemy) + { + if (m_hTargetEnt != NULL) + m_hTargetEnt->Use( pEnemy, pEnemy, USE_ON, 1.0 ); + + if (m_hTouch != NULL && pEnemy != NULL ) + m_hTouch->Touch( pEnemy ); + } + else + { + m_pNihilanth->MakeFriend( pev->origin ); + } + + SetTouch( NULL ); + STOP_SOUND(edict(), CHAN_WEAPON, "x/x_teleattack1.wav" ); + UTIL_Remove( this ); +} + + +void CNihilanthHVR :: DissipateThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if (pev->scale > 5.0) + UTIL_Remove( this ); + + pev->renderamt -= 2; + pev->scale += 0.1; + + if (m_hTargetEnt != NULL) + { + CircleTarget( m_hTargetEnt->pev->origin + Vector( 0, 0, 4096 ) ); + } + else + { + UTIL_Remove( this ); + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( pev->renderamt ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 192 ); // G + WRITE_BYTE( 64 ); // B + WRITE_BYTE( 2 ); // life * 10 + WRITE_COORD( 0 ); // decay + MESSAGE_END(); +} + + +BOOL CNihilanthHVR :: CircleTarget( Vector vecTarget ) +{ + BOOL fClose = FALSE; + + Vector vecDest = vecTarget; + Vector vecEst = pev->origin + pev->velocity * 0.5; + Vector vecSrc = pev->origin; + vecDest.z = 0; + vecEst.z = 0; + vecSrc.z = 0; + float d1 = (vecDest - vecSrc).Length() - 24 * N_SCALE; + float d2 = (vecDest - vecEst).Length() - 24 * N_SCALE; + + if (m_vecIdeal == Vector( 0, 0, 0 )) + { + m_vecIdeal = pev->velocity; + } + + if (d1 < 0 && d2 <= d1) + { + // ALERT( at_console, "too close\n"); + m_vecIdeal = m_vecIdeal - (vecDest - vecSrc).Normalize() * 50; + } + else if (d1 > 0 && d2 >= d1) + { + // ALERT( at_console, "too far\n"); + m_vecIdeal = m_vecIdeal + (vecDest - vecSrc).Normalize() * 50; + } + pev->avelocity.z = d1 * 20; + + if (d1 < 32) + { + fClose = TRUE; + } + + m_vecIdeal = m_vecIdeal + Vector( RANDOM_FLOAT( -2, 2 ), RANDOM_FLOAT( -2, 2 ), RANDOM_FLOAT( -2, 2 )); + m_vecIdeal = Vector( m_vecIdeal.x, m_vecIdeal.y, 0 ).Normalize( ) * 200 + /* + Vector( -m_vecIdeal.y, m_vecIdeal.x, 0 ).Normalize( ) * 32 */ + + Vector( 0, 0, m_vecIdeal.z ); + // m_vecIdeal = m_vecIdeal + Vector( -m_vecIdeal.y, m_vecIdeal.x, 0 ).Normalize( ) * 2; + + // move up/down + d1 = vecTarget.z - pev->origin.z; + if (d1 > 0 && m_vecIdeal.z < 200) + m_vecIdeal.z += 20; + else if (d1 < 0 && m_vecIdeal.z > -200) + m_vecIdeal.z -= 20; + + pev->velocity = m_vecIdeal; + + // ALERT( at_console, "%.0f %.0f %.0f\n", m_vecIdeal.x, m_vecIdeal.y, m_vecIdeal.z ); + return fClose; +} + + +void CNihilanthHVR :: MovetoTarget( Vector vecTarget ) +{ + if (m_vecIdeal == Vector( 0, 0, 0 )) + { + m_vecIdeal = pev->velocity; + } + + // accelerate + float flSpeed = m_vecIdeal.Length(); + if (flSpeed > 300) + { + m_vecIdeal = m_vecIdeal.Normalize( ) * 300; + } + m_vecIdeal = m_vecIdeal + (vecTarget - pev->origin).Normalize() * 300; + pev->velocity = m_vecIdeal; +} + + + + +void CNihilanthHVR :: Crawl( void ) +{ + + Vector vecAim = Vector( RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ) ).Normalize( ); + Vector vecPnt = pev->origin + pev->velocity * 0.2 + vecAim * 128; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( entindex() ); + WRITE_COORD( vecPnt.x); + WRITE_COORD( vecPnt.y); + WRITE_COORD( vecPnt.z); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 3 ); // life + WRITE_BYTE( 20 ); // width + WRITE_BYTE( 80 ); // noise + WRITE_BYTE( 64 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 255); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); +} + + +void CNihilanthHVR::RemoveTouch( CBaseEntity *pOther ) +{ + STOP_SOUND(edict(), CHAN_WEAPON, "x/x_teleattack1.wav" ); + UTIL_Remove( this ); +} + +void CNihilanthHVR::BounceTouch( CBaseEntity *pOther ) +{ + Vector vecDir = m_vecIdeal.Normalize( ); + + TraceResult tr = UTIL_GetGlobalTrace( ); + + float n = -DotProduct(tr.vecPlaneNormal, vecDir); + + vecDir = 2.0 * tr.vecPlaneNormal * n + vecDir; + + m_vecIdeal = vecDir * m_vecIdeal.Length(); +} + + + +#endif diff --git a/dlls/nodes.cpp b/dlls/nodes.cpp new file mode 100644 index 0000000..2755590 --- /dev/null +++ b/dlls/nodes.cpp @@ -0,0 +1,3657 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// nodes.cpp - AI node tree stuff. +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "nodes.h" +#include "animation.h" +#include "doors.h" + +#if !defined ( _WIN32 ) +#include +#include +#include +#include // mkdir +#endif + +#define HULL_STEP_SIZE 16// how far the test hull moves on each step +#define NODE_HEIGHT 8 // how high to lift nodes off the ground after we drop them all (make stair/ramp mapping easier) + +// to help eliminate node clutter by level designers, this is used to cap how many other nodes +// any given node is allowed to 'see' in the first stage of graph creation "LinkVisibleNodes()". +#define MAX_NODE_INITIAL_LINKS 128 +#define MAX_NODES 1024 + +extern DLL_GLOBAL edict_t *g_pBodyQueueHead; + +Vector VecBModelOrigin( entvars_t* pevBModel ); + +CGraph WorldGraph; + +LINK_ENTITY_TO_CLASS( info_node, CNodeEnt ); +LINK_ENTITY_TO_CLASS( info_node_air, CNodeEnt ); +#ifdef _LINUX +#include +#define CreateDirectory(p, n) mkdir(p, 0777) +#endif +//========================================================= +// CGraph - InitGraph - prepares the graph for use. Frees any +// memory currently in use by the world graph, NULLs +// all pointers, and zeros the node count. +//========================================================= +void CGraph :: InitGraph( void) +{ + + // Make the graph unavailable + // + m_fGraphPresent = FALSE; + m_fGraphPointersSet = FALSE; + m_fRoutingComplete = FALSE; + + // Free the link pool + // + if ( m_pLinkPool ) + { + free ( m_pLinkPool ); + m_pLinkPool = NULL; + } + + // Free the node info + // + if ( m_pNodes ) + { + free ( m_pNodes ); + m_pNodes = NULL; + } + + if ( m_di ) + { + free ( m_di ); + m_di = NULL; + } + + // Free the routing info. + // + if ( m_pRouteInfo ) + { + free ( m_pRouteInfo ); + m_pRouteInfo = NULL; + } + + if (m_pHashLinks) + { + free(m_pHashLinks); + m_pHashLinks = NULL; + } + + // Zero node and link counts + // + m_cNodes = 0; + m_cLinks = 0; + m_nRouteInfo = 0; + + m_iLastActiveIdleSearch = 0; + m_iLastCoverSearch = 0; +} + +//========================================================= +// CGraph - AllocNodes - temporary function that mallocs a +// reasonable number of nodes so we can build the path which +// will be saved to disk. +//========================================================= +int CGraph :: AllocNodes ( void ) +{ +// malloc all of the nodes + WorldGraph.m_pNodes = (CNode *)calloc ( sizeof ( CNode ), MAX_NODES ); + +// could not malloc space for all the nodes! + if ( !WorldGraph.m_pNodes ) + { + ALERT ( at_aiconsole, "**ERROR**\nCouldn't malloc %d nodes!\n", WorldGraph.m_cNodes ); + return FALSE; + } + + return TRUE; +} + +//========================================================= +// CGraph - LinkEntForLink - sometimes the ent that blocks +// a path is a usable door, in which case the monster just +// needs to face the door and fire it. In other cases, the +// monster needs to operate a button or lever to get the +// door to open. This function will return a pointer to the +// button if the monster needs to hit a button to open the +// door, or returns a pointer to the door if the monster +// need only use the door. +// +// pNode is the node the monster will be standing on when it +// will need to stop and trigger the ent. +//========================================================= +entvars_t* CGraph :: LinkEntForLink ( CLink *pLink, CNode *pNode ) +{ + edict_t *pentSearch; + edict_t *pentTrigger; + entvars_t *pevTrigger; + entvars_t *pevLinkEnt; + TraceResult tr; + + pevLinkEnt = pLink->m_pLinkEnt; + if ( !pevLinkEnt ) + return NULL; + + pentSearch = NULL;// start search at the top of the ent list. + + if ( FClassnameIs ( pevLinkEnt, "func_door" ) || FClassnameIs ( pevLinkEnt, "func_door_rotating" ) ) + { + + ///!!!UNDONE - check for TOGGLE or STAY open doors here. If a door is in the way, and is + // TOGGLE or STAY OPEN, even monsters that can't open doors can go that way. + + if ( ( pevLinkEnt->spawnflags & SF_DOOR_USE_ONLY ) ) + {// door is use only, so the door is all the monster has to worry about + return pevLinkEnt; + } + + while ( 1 ) + { + pentTrigger = FIND_ENTITY_BY_TARGET ( pentSearch, STRING( pevLinkEnt->targetname ) );// find the button or trigger + + if ( FNullEnt( pentTrigger ) ) + {// no trigger found + + // right now this is a problem among auto-open doors, or any door that opens through the use + // of a trigger brush. Trigger brushes have no models, and don't show up in searches. Just allow + // monsters to open these sorts of doors for now. + return pevLinkEnt; + } + + pentSearch = pentTrigger; + pevTrigger = VARS( pentTrigger ); + + if ( FClassnameIs(pevTrigger, "func_button") || FClassnameIs(pevTrigger, "func_rot_button" ) ) + {// only buttons are handled right now. + + // trace from the node to the trigger, make sure it's one we can see from the node. + // !!!HACKHACK Use bodyqueue here cause there are no ents we really wish to ignore! + UTIL_TraceLine ( pNode->m_vecOrigin, VecBModelOrigin( pevTrigger ), ignore_monsters, g_pBodyQueueHead, &tr ); + + + if ( VARS(tr.pHit) == pevTrigger ) + {// good to go! + return VARS( tr.pHit ); + } + } + } + } + else + { + ALERT ( at_aiconsole, "Unsupported PathEnt:\n'%s'\n", STRING ( pevLinkEnt->classname ) ); + return NULL; + } +} + +//========================================================= +// CGraph - HandleLinkEnt - a brush ent is between two +// nodes that would otherwise be able to see each other. +// Given the monster's capability, determine whether +// or not the monster can go this way. +//========================================================= +int CGraph :: HandleLinkEnt ( int iNode, entvars_t *pevLinkEnt, int afCapMask, NODEQUERY queryType ) +{ + edict_t *pentWorld; + CBaseEntity *pDoor; + TraceResult tr; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return FALSE; + } + + if ( FNullEnt ( pevLinkEnt ) ) + { + ALERT ( at_aiconsole, "dead path ent!\n" ); + return TRUE; + } + pentWorld = NULL; + +// func_door + if ( FClassnameIs( pevLinkEnt, "func_door" ) || FClassnameIs( pevLinkEnt, "func_door_rotating" ) ) + {// ent is a door. + + pDoor = ( CBaseEntity::Instance( pevLinkEnt ) ); + + if ( ( pevLinkEnt->spawnflags & SF_DOOR_USE_ONLY ) ) + {// door is use only. + + if ( ( afCapMask & bits_CAP_OPEN_DOORS ) ) + {// let monster right through if he can open doors + return TRUE; + } + else + { + // monster should try for it if the door is open and looks as if it will stay that way + if ( pDoor->GetToggleState()== TS_AT_TOP && ( pevLinkEnt->spawnflags & SF_DOOR_NO_AUTO_RETURN ) ) + { + return TRUE; + } + + return FALSE; + } + } + else + {// door must be opened with a button or trigger field. + + // monster should try for it if the door is open and looks as if it will stay that way + if ( pDoor->GetToggleState() == TS_AT_TOP && ( pevLinkEnt->spawnflags & SF_DOOR_NO_AUTO_RETURN ) ) + { + return TRUE; + } + if ( ( afCapMask & bits_CAP_OPEN_DOORS ) ) + { + if ( !( pevLinkEnt->spawnflags & SF_DOOR_NOMONSTERS ) || queryType == NODEGRAPH_STATIC ) + return TRUE; + } + + return FALSE; + } + } +// func_breakable + else if ( FClassnameIs( pevLinkEnt, "func_breakable" ) && queryType == NODEGRAPH_STATIC ) + { + return TRUE; + } + else + { + ALERT ( at_aiconsole, "Unhandled Ent in Path %s\n", STRING( pevLinkEnt->classname ) ); + return FALSE; + } + + return FALSE; +} + +#if 0 +//========================================================= +// FindNearestLink - finds the connection (line) nearest +// the given point. Returns FALSE if fails, or TRUE if it +// has stuffed the index into the nearest link pool connection +// into the passed int pointer, and a BOOL telling whether or +// not the point is along the line into the passed BOOL pointer. +//========================================================= +int CGraph :: FindNearestLink ( const Vector &vecTestPoint, int *piNearestLink, BOOL *pfAlongLine ) +{ + int i, j;// loops + + int iNearestLink;// index into the link pool, this is the nearest node at any time. + float flMinDist;// the distance of of the nearest case so far + float flDistToLine;// the distance of the current test case + + BOOL fCurrentAlongLine; + BOOL fSuccess; + + //float flConstant;// line constant + Vector vecSpot1, vecSpot2; + Vector2D vec2Spot1, vec2Spot2, vec2TestPoint; + Vector2D vec2Normal;// line normal + Vector2D vec2Line; + + TraceResult tr; + + iNearestLink = -1;// prepare for failure + fSuccess = FALSE; + + flMinDist = 9999;// anything will be closer than this + +// go through all of the nodes, and each node's connections + int cSkip = 0;// how many links proper pairing allowed us to skip + int cChecked = 0;// how many links were checked + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + vecSpot1 = m_pNodes[ i ].m_vecOrigin; + + if ( m_pNodes[ i ].m_cNumLinks <= 0 ) + {// this shouldn't happen! + ALERT ( at_aiconsole, "**Node %d has no links\n", i ); + continue; + } + + for ( j = 0 ; j < m_pNodes[ i ].m_cNumLinks ; j++ ) + { + /* + !!!This optimization only works when the node graph consists of properly linked pairs. + if ( INodeLink ( i, j ) <= i ) + { + // since we're going through the nodes in order, don't check + // any connections whose second node is lower in the list + // than the node we're currently working with. This eliminates + // redundant checks. + cSkip++; + continue; + } + */ + + vecSpot2 = PNodeLink ( i, j )->m_vecOrigin; + + // these values need a little attention now and then, or sometimes ramps cause trouble. + if ( fabs ( vecSpot1.z - vecTestPoint.z ) > 48 && fabs ( vecSpot2.z - vecTestPoint.z ) > 48 ) + { + // if both endpoints of the line are 32 units or more above or below the monster, + // the monster won't be able to get to them, so we do a bit of trivial rejection here. + // this may change if monsters are allowed to jump down. + // + // !!!LATER: some kind of clever X/Y hashing should be used here, too + continue; + } + +// now we have two endpoints for a line segment that we've not already checked. +// since all lines that make it this far are within -/+ 32 units of the test point's +// Z Plane, we can get away with doing the point->line check in 2d. + + cChecked++; + + vec2Spot1 = vecSpot1.Make2D(); + vec2Spot2 = vecSpot2.Make2D(); + vec2TestPoint = vecTestPoint.Make2D(); + + // get the line normal. + vec2Line = ( vec2Spot1 - vec2Spot2 ).Normalize(); + vec2Normal.x = -vec2Line.y; + vec2Normal.y = vec2Line.x; + + if ( DotProduct ( vec2Line, ( vec2TestPoint - vec2Spot1 ) ) > 0 ) + {// point outside of line + flDistToLine = ( vec2TestPoint - vec2Spot1 ).Length(); + fCurrentAlongLine = FALSE; + } + else if ( DotProduct ( vec2Line, ( vec2TestPoint - vec2Spot2 ) ) < 0 ) + {// point outside of line + flDistToLine = ( vec2TestPoint - vec2Spot2 ).Length(); + fCurrentAlongLine = FALSE; + } + else + {// point inside line + flDistToLine = fabs( DotProduct ( vec2TestPoint - vec2Spot2, vec2Normal ) ); + fCurrentAlongLine = TRUE; + } + + if ( flDistToLine < flMinDist ) + {// just found a line nearer than any other so far + + UTIL_TraceLine ( vecTestPoint, SourceNode( i, j ).m_vecOrigin, ignore_monsters, g_pBodyQueueHead, &tr ); + + if ( tr.flFraction != 1.0 ) + {// crap. can't see the first node of this link, try to see the other + + UTIL_TraceLine ( vecTestPoint, DestNode( i, j ).m_vecOrigin, ignore_monsters, g_pBodyQueueHead, &tr ); + if ( tr.flFraction != 1.0 ) + {// can't use this link, cause can't see either node! + continue; + } + + } + + fSuccess = TRUE;// we know there will be something to return. + flMinDist = flDistToLine; + iNearestLink = m_pNodes [ i ].m_iFirstLink + j; + *piNearestLink = m_pNodes[ i ].m_iFirstLink + j; + *pfAlongLine = fCurrentAlongLine; + } + } + } + +/* + if ( fSuccess ) + { + WRITE_BYTE(MSG_BROADCAST, SVC_TEMPENTITY); + WRITE_BYTE(MSG_BROADCAST, TE_SHOWLINE); + + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iSrcNode ].m_vecOrigin.x ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iSrcNode ].m_vecOrigin.y ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iSrcNode ].m_vecOrigin.z + NODE_HEIGHT); + + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iDestNode ].m_vecOrigin.x ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iDestNode ].m_vecOrigin.y ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iDestNode ].m_vecOrigin.z + NODE_HEIGHT); + } +*/ + + ALERT ( at_aiconsole, "%d Checked\n", cChecked ); + return fSuccess; +} + +#endif + +int CGraph::HullIndex( const CBaseEntity *pEntity ) +{ + if ( pEntity->pev->movetype == MOVETYPE_FLY) + return NODE_FLY_HULL; + + if ( pEntity->pev->mins == Vector( -12, -12, 0 ) ) + return NODE_SMALL_HULL; + else if ( pEntity->pev->mins == VEC_HUMAN_HULL_MIN ) + return NODE_HUMAN_HULL; + else if ( pEntity->pev->mins == Vector ( -32, -32, 0 ) ) + return NODE_LARGE_HULL; + +// ALERT ( at_aiconsole, "Unknown Hull Mins!\n" ); + return NODE_HUMAN_HULL; +} + + +int CGraph::NodeType( const CBaseEntity *pEntity ) +{ + if ( pEntity->pev->movetype == MOVETYPE_FLY) + { + if (pEntity->pev->waterlevel != 0) + { + return bits_NODE_WATER; + } + else + { + return bits_NODE_AIR; + } + } + return bits_NODE_LAND; +} + + +// Sum up graph weights on the path from iStart to iDest to determine path length +float CGraph::PathLength( int iStart, int iDest, int iHull, int afCapMask ) +{ + float distance = 0; + int iNext; + + int iMaxLoop = m_cNodes; + + int iCurrentNode = iStart; + int iCap = CapIndex( afCapMask ); + + while (iCurrentNode != iDest) + { + if (iMaxLoop-- <= 0) + { + ALERT( at_console, "Route Failure\n" ); + return 0; + } + + iNext = NextNodeInRoute( iCurrentNode, iDest, iHull, iCap ); + if (iCurrentNode == iNext) + { + //ALERT(at_aiconsole, "SVD: Can't get there from here..\n"); + return 0; + } + + int iLink; + HashSearch(iCurrentNode, iNext, iLink); + if (iLink < 0) + { + ALERT(at_console, "HashLinks is broken from %d to %d.\n", iCurrentNode, iDest); + return 0; + } + CLink &link = Link(iLink); + distance += link.m_flWeight; + + iCurrentNode = iNext; + } + + return distance; +} + + +// Parse the routing table at iCurrentNode for the next node on the shortest path to iDest +int CGraph::NextNodeInRoute( int iCurrentNode, int iDest, int iHull, int iCap ) +{ + int iNext = iCurrentNode; + int nCount = iDest+1; + char *pRoute = m_pRouteInfo + m_pNodes[ iCurrentNode ].m_pNextBestNode[iHull][iCap]; + + // Until we decode the next best node + // + while (nCount > 0) + { + char ch = *pRoute++; + //ALERT(at_aiconsole, "C(%d)", ch); + if (ch < 0) + { + // Sequence phrase + // + ch = -ch; + if (nCount <= ch) + { + iNext = iDest; + nCount = 0; + //ALERT(at_aiconsole, "SEQ: iNext/iDest=%d\n", iNext); + } + else + { + //ALERT(at_aiconsole, "SEQ: nCount + ch (%d + %d)\n", nCount, ch); + nCount = nCount - ch; + } + } + else + { + //ALERT(at_aiconsole, "C(%d)", *pRoute); + + // Repeat phrase + // + if (nCount <= ch+1) + { + iNext = iCurrentNode + *pRoute; + if (iNext >= m_cNodes) iNext -= m_cNodes; + else if (iNext < 0) iNext += m_cNodes; + nCount = 0; + //ALERT(at_aiconsole, "REP: iNext=%d\n", iNext); + } + else + { + //ALERT(at_aiconsole, "REP: nCount - ch+1 (%d - %d+1)\n", nCount, ch); + nCount = nCount - ch - 1; + } + pRoute++; + } + } + + return iNext; +} + + +//========================================================= +// CGraph - FindShortestPath +// +// accepts a capability mask (afCapMask), and will only +// find a path usable by a monster with those capabilities +// returns the number of nodes copied into supplied array +//========================================================= +int CGraph :: FindShortestPath ( int *piPath, int iStart, int iDest, int iHull, int afCapMask) +{ + int iVisitNode; + int iCurrentNode; + int iNumPathNodes; + int iHullMask; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available or built + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return FALSE; + } + + if ( iStart < 0 || iStart > m_cNodes ) + {// The start node is bad? + ALERT ( at_aiconsole, "Can't build a path, iStart is %d!\n", iStart ); + return FALSE; + } + + if (iStart == iDest) + { + piPath[0] = iStart; + piPath[1] = iDest; + return 2; + } + + // Is routing information present. + // + if (m_fRoutingComplete) + { + int iCap = CapIndex( afCapMask ); + + iNumPathNodes = 0; + piPath[iNumPathNodes++] = iStart; + iCurrentNode = iStart; + int iNext; + + //ALERT(at_aiconsole, "GOAL: %d to %d\n", iStart, iDest); + + // Until we arrive at the destination + // + while (iCurrentNode != iDest) + { + iNext = NextNodeInRoute( iCurrentNode, iDest, iHull, iCap ); + if (iCurrentNode == iNext) + { + //ALERT(at_aiconsole, "SVD: Can't get there from here..\n"); + return 0; + break; + } + if (iNumPathNodes >= MAX_PATH_SIZE) + { + //ALERT(at_aiconsole, "SVD: Don't return the entire path.\n"); + break; + } + piPath[iNumPathNodes++] = iNext; + iCurrentNode = iNext; + } + //ALERT( at_aiconsole, "SVD: Path with %d nodes.\n", iNumPathNodes); + } + else + { + CQueuePriority queue; + + switch( iHull ) + { + case NODE_SMALL_HULL: + iHullMask = bits_LINK_SMALL_HULL; + break; + case NODE_HUMAN_HULL: + iHullMask = bits_LINK_HUMAN_HULL; + break; + case NODE_LARGE_HULL: + iHullMask = bits_LINK_LARGE_HULL; + break; + case NODE_FLY_HULL: + iHullMask = bits_LINK_FLY_HULL; + break; + } + + // Mark all the nodes as unvisited. + // + int i; + for ( i = 0; i < m_cNodes; i++) + { + m_pNodes[ i ].m_flClosestSoFar = -1.0; + } + + m_pNodes[ iStart ].m_flClosestSoFar = 0.0; + m_pNodes[ iStart ].m_iPreviousNode = iStart;// tag this as the origin node + queue.Insert( iStart, 0.0 );// insert start node + + while ( !queue.Empty() ) + { + // now pull a node out of the queue + float flCurrentDistance; + iCurrentNode = queue.Remove(flCurrentDistance); + + // For straight-line weights, the following Shortcut works. For arbitrary weights, + // it doesn't. + // + if (iCurrentNode == iDest) break; + + CNode *pCurrentNode = &m_pNodes[ iCurrentNode ]; + + for ( i = 0 ; i < pCurrentNode->m_cNumLinks ; i++ ) + {// run through all of this node's neighbors + + iVisitNode = INodeLink ( iCurrentNode, i ); + if ( ( m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_afLinkInfo & iHullMask ) != iHullMask ) + {// monster is too large to walk this connection + //ALERT ( at_aiconsole, "fat ass %d/%d\n",m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_afLinkInfo, iMonsterHull ); + continue; + } + // check the connection from the current node to the node we're about to mark visited and push into the queue + if ( m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_pLinkEnt != NULL ) + {// there's a brush ent in the way! Don't mark this node or put it into the queue unless the monster can negotiate it + + if ( !HandleLinkEnt ( iCurrentNode, m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_pLinkEnt, afCapMask, NODEGRAPH_STATIC ) ) + {// monster should not try to go this way. + continue; + } + } + float flOurDistance = flCurrentDistance + m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i].m_flWeight; + if ( m_pNodes[ iVisitNode ].m_flClosestSoFar < -0.5 + || flOurDistance < m_pNodes[ iVisitNode ].m_flClosestSoFar - 0.001 ) + { + m_pNodes[iVisitNode].m_flClosestSoFar = flOurDistance; + m_pNodes[iVisitNode].m_iPreviousNode = iCurrentNode; + + queue.Insert ( iVisitNode, flOurDistance ); + } + } + } + if ( m_pNodes[iDest].m_flClosestSoFar < -0.5 ) + {// Destination is unreachable, no path found. + return 0; + } + + // the queue is not empty + + // now we must walk backwards through the m_iPreviousNode field, and count how many connections there are in the path + iCurrentNode = iDest; + iNumPathNodes = 1;// count the dest + + while ( iCurrentNode != iStart ) + { + iNumPathNodes++; + iCurrentNode = m_pNodes[ iCurrentNode ].m_iPreviousNode; + } + + iCurrentNode = iDest; + for ( i = iNumPathNodes - 1 ; i >= 0 ; i-- ) + { + piPath[ i ] = iCurrentNode; + iCurrentNode = m_pNodes [ iCurrentNode ].m_iPreviousNode; + } + } + +#if 0 + + if (m_fRoutingComplete) + { + // This will draw the entire path that was generated for the monster. + + for ( int i = 0 ; i < iNumPathNodes - 1 ; i++ ) + { + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( m_pNodes[ piPath[ i ] ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ piPath[ i ] ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ piPath[ i ] ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( m_pNodes[ piPath[ i + 1 ] ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ piPath[ i + 1 ] ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ piPath[ i + 1 ] ].m_vecOrigin.z + NODE_HEIGHT ); + MESSAGE_END(); + } + } + +#endif +#if 0 // MAZE map + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( m_pNodes[ 4 ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ 4 ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ 4 ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( m_pNodes[ 9 ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ 9 ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ 9 ].m_vecOrigin.z + NODE_HEIGHT ); + MESSAGE_END(); +#endif + + return iNumPathNodes; +} + +inline ULONG Hash(void *p, int len) +{ + CRC32_t ulCrc; + CRC32_INIT(&ulCrc); + CRC32_PROCESS_BUFFER(&ulCrc, p, len); + return CRC32_FINAL(ulCrc); +} + +void inline CalcBounds(int &Lower, int &Upper, int Goal, int Best) +{ + int Temp = 2*Goal - Best; + if (Best > Goal) + { + Lower = max(0, Temp); + Upper = Best; + } + else + { + Upper = min(255, Temp); + Lower = Best; + } +} + +// Convert from [-8192,8192] to [0, 255] +// +inline int CALC_RANGE(int x, int lower, int upper) +{ + return NUM_RANGES*(x-lower)/((upper-lower+1)); +} + + +void inline UpdateRange(int &minValue, int &maxValue, int Goal, int Best) +{ + int Lower, Upper; + CalcBounds(Lower, Upper, Goal, Best); + if (Upper < maxValue) maxValue = Upper; + if (minValue < Lower) minValue = Lower; +} + +void CGraph :: CheckNode(Vector vecOrigin, int iNode) +{ + // Have we already seen this point before?. + // + if (m_di[iNode].m_CheckedEvent == m_CheckedCounter) return; + m_di[iNode].m_CheckedEvent = m_CheckedCounter; + + float flDist = ( vecOrigin - m_pNodes[ iNode ].m_vecOriginPeek ).Length(); + + if ( flDist < m_flShortest ) + { + TraceResult tr; + + // make sure that vecOrigin can trace to this node! + UTIL_TraceLine ( vecOrigin, m_pNodes[ iNode ].m_vecOriginPeek, ignore_monsters, 0, &tr ); + + if ( tr.flFraction == 1.0 ) + { + m_iNearest = iNode; + m_flShortest = flDist; + + UpdateRange(m_minX, m_maxX, CALC_RANGE(vecOrigin.x, m_RegionMin[0], m_RegionMax[0]), m_pNodes[iNode].m_Region[0]); + UpdateRange(m_minY, m_maxY, CALC_RANGE(vecOrigin.y, m_RegionMin[1], m_RegionMax[1]), m_pNodes[iNode].m_Region[1]); + UpdateRange(m_minZ, m_maxZ, CALC_RANGE(vecOrigin.z, m_RegionMin[2], m_RegionMax[2]), m_pNodes[iNode].m_Region[2]); + + // From maxCircle, calculate maximum bounds box. All points must be + // simultaneously inside all bounds of the box. + // + m_minBoxX = CALC_RANGE(vecOrigin.x - flDist, m_RegionMin[0], m_RegionMax[0]); + m_maxBoxX = CALC_RANGE(vecOrigin.x + flDist, m_RegionMin[0], m_RegionMax[0]); + m_minBoxY = CALC_RANGE(vecOrigin.y - flDist, m_RegionMin[1], m_RegionMax[1]); + m_maxBoxY = CALC_RANGE(vecOrigin.y + flDist, m_RegionMin[1], m_RegionMax[1]); + m_minBoxZ = CALC_RANGE(vecOrigin.z - flDist, m_RegionMin[2], m_RegionMax[2]); + m_maxBoxZ = CALC_RANGE(vecOrigin.z + flDist, m_RegionMin[2], m_RegionMax[2]); + } + } +} + +//========================================================= +// CGraph - FindNearestNode - returns the index of the node nearest +// the given vector -1 is failure (couldn't find a valid +// near node ) +//========================================================= +int CGraph :: FindNearestNode ( const Vector &vecOrigin, CBaseEntity *pEntity ) +{ + return FindNearestNode( vecOrigin, NodeType( pEntity ) ); +} + +int CGraph :: FindNearestNode ( const Vector &vecOrigin, int afNodeTypes ) +{ + int i; + TraceResult tr; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return -1; + } + + // Check with the cache + // + ULONG iHash = (CACHE_SIZE-1) & Hash((void *)(const float *)vecOrigin, sizeof(vecOrigin)); + if (m_Cache[iHash].v == vecOrigin) + { + //ALERT(at_aiconsole, "Cache Hit.\n"); + return m_Cache[iHash].n; + } + else + { + //ALERT(at_aiconsole, "Cache Miss.\n"); + } + + // Mark all points as unchecked. + // + m_CheckedCounter++; + if (m_CheckedCounter == 0) + { + for (int i = 0; i < m_cNodes; i++) + { + m_di[i].m_CheckedEvent = 0; + } + m_CheckedCounter++; + } + + m_iNearest = -1; + m_flShortest = 999999.0; // just a big number. + + // If we can find a visible point, then let CalcBounds set the limits, but if + // we have no visible point at all to start with, then don't restrict the limits. + // +#if 1 + m_minX = 0; m_maxX = 255; + m_minY = 0; m_maxY = 255; + m_minZ = 0; m_maxZ = 255; + m_minBoxX = 0; m_maxBoxX = 255; + m_minBoxY = 0; m_maxBoxY = 255; + m_minBoxZ = 0; m_maxBoxZ = 255; +#else + m_minBoxX = CALC_RANGE(vecOrigin.x - flDist, m_RegionMin[0], m_RegionMax[0]); + m_maxBoxX = CALC_RANGE(vecOrigin.x + flDist, m_RegionMin[0], m_RegionMax[0]); + m_minBoxY = CALC_RANGE(vecOrigin.y - flDist, m_RegionMin[1], m_RegionMax[1]); + m_maxBoxY = CALC_RANGE(vecOrigin.y + flDist, m_RegionMin[1], m_RegionMax[1]); + m_minBoxZ = CALC_RANGE(vecOrigin.z - flDist, m_RegionMin[2], m_RegionMax[2]); + m_maxBoxZ = CALC_RANGE(vecOrigin.z + flDist, m_RegionMin[2], m_RegionMax[2]) + CalcBounds(m_minX, m_maxX, CALC_RANGE(vecOrigin.x, m_RegionMin[0], m_RegionMax[0]), m_pNodes[m_iNearest].m_Region[0]); + CalcBounds(m_minY, m_maxY, CALC_RANGE(vecOrigin.y, m_RegionMin[1], m_RegionMax[1]), m_pNodes[m_iNearest].m_Region[1]); + CalcBounds(m_minZ, m_maxZ, CALC_RANGE(vecOrigin.z, m_RegionMin[2], m_RegionMax[2]), m_pNodes[m_iNearest].m_Region[2]); +#endif + + int halfX = (m_minX+m_maxX)/2; + int halfY = (m_minY+m_maxY)/2; + int halfZ = (m_minZ+m_maxZ)/2; + + int j; + + for (i = halfX; i >= m_minX; i--) + { + for (j = m_RangeStart[0][i]; j <= m_RangeEnd[0][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[0]].m_afNodeInfo & afNodeTypes)) continue; + + int rgY = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[1]; + if (rgY > m_maxBoxY) break; + if (rgY < m_minBoxY) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[2]; + if (rgZ < m_minBoxZ) continue; + if (rgZ > m_maxBoxZ) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[0]); + } + } + + for (i = max(m_minY,halfY+1); i <= m_maxY; i++) + { + for (j = m_RangeStart[1][i]; j <= m_RangeEnd[1][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[1]].m_afNodeInfo & afNodeTypes)) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[2]; + if (rgZ > m_maxBoxZ) break; + if (rgZ < m_minBoxZ) continue; + int rgX = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[0]; + if (rgX < m_minBoxX) continue; + if (rgX > m_maxBoxX) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[1]); + } + } + + for (i = min(m_maxZ,halfZ); i >= m_minZ; i--) + { + for (j = m_RangeStart[2][i]; j <= m_RangeEnd[2][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[2]].m_afNodeInfo & afNodeTypes)) continue; + + int rgX = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[0]; + if (rgX > m_maxBoxX) break; + if (rgX < m_minBoxX) continue; + int rgY = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[1]; + if (rgY < m_minBoxY) continue; + if (rgY > m_maxBoxY) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[2]); + } + } + + for (i = max(m_minX,halfX+1); i <= m_maxX; i++) + { + for (j = m_RangeStart[0][i]; j <= m_RangeEnd[0][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[0]].m_afNodeInfo & afNodeTypes)) continue; + + int rgY = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[1]; + if (rgY > m_maxBoxY) break; + if (rgY < m_minBoxY) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[2]; + if (rgZ < m_minBoxZ) continue; + if (rgZ > m_maxBoxZ) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[0]); + } + } + + for (i = min(m_maxY,halfY); i >= m_minY; i--) + { + for (j = m_RangeStart[1][i]; j <= m_RangeEnd[1][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[1]].m_afNodeInfo & afNodeTypes)) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[2]; + if (rgZ > m_maxBoxZ) break; + if (rgZ < m_minBoxZ) continue; + int rgX = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[0]; + if (rgX < m_minBoxX) continue; + if (rgX > m_maxBoxX) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[1]); + } + } + + for (i = max(m_minZ,halfZ+1); i <= m_maxZ; i++) + { + for (j = m_RangeStart[2][i]; j <= m_RangeEnd[2][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[2]].m_afNodeInfo & afNodeTypes)) continue; + + int rgX = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[0]; + if (rgX > m_maxBoxX) break; + if (rgX < m_minBoxX) continue; + int rgY = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[1]; + if (rgY < m_minBoxY) continue; + if (rgY > m_maxBoxY) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[2]); + } + } + +#if 0 + // Verify our answers. + // + int iNearestCheck = -1; + m_flShortest = 8192;// find nodes within this radius + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + float flDist = ( vecOrigin - m_pNodes[ i ].m_vecOriginPeek ).Length(); + + if ( flDist < m_flShortest ) + { + // make sure that vecOrigin can trace to this node! + UTIL_TraceLine ( vecOrigin, m_pNodes[ i ].m_vecOriginPeek, ignore_monsters, 0, &tr ); + + if ( tr.flFraction == 1.0 ) + { + iNearestCheck = i; + m_flShortest = flDist; + } + } + } + + if (iNearestCheck != m_iNearest) + { + ALERT( at_aiconsole, "NOT closest %d(%f,%f,%f) %d(%f,%f,%f).\n", + iNearestCheck, + m_pNodes[iNearestCheck].m_vecOriginPeek.x, + m_pNodes[iNearestCheck].m_vecOriginPeek.y, + m_pNodes[iNearestCheck].m_vecOriginPeek.z, + m_iNearest, + (m_iNearest == -1?0.0:m_pNodes[m_iNearest].m_vecOriginPeek.x), + (m_iNearest == -1?0.0:m_pNodes[m_iNearest].m_vecOriginPeek.y), + (m_iNearest == -1?0.0:m_pNodes[m_iNearest].m_vecOriginPeek.z)); + } + if (m_iNearest == -1) + { + ALERT(at_aiconsole, "All that work for nothing.\n"); + } +#endif + m_Cache[iHash].v = vecOrigin; + m_Cache[iHash].n = m_iNearest; + return m_iNearest; +} + +//========================================================= +// CGraph - ShowNodeConnections - draws a line from the given node +// to all connected nodes +//========================================================= +void CGraph :: ShowNodeConnections ( int iNode ) +{ + Vector vecSpot; + CNode *pNode; + CNode *pLinkNode; + int i; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available or built + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return; + } + + if ( iNode < 0 ) + { + ALERT( at_aiconsole, "Can't show connections for node %d\n", iNode ); + return; + } + + pNode = &m_pNodes[ iNode ]; + + UTIL_ParticleEffect( pNode->m_vecOrigin, g_vecZero, 255, 20 );// show node position + + if ( pNode->m_cNumLinks <= 0 ) + {// no connections! + ALERT ( at_aiconsole, "**No Connections!\n" ); + } + + for ( i = 0 ; i < pNode->m_cNumLinks ; i++ ) + { + + pLinkNode = &Node( NodeLink( iNode, i).m_iDestNode ); + vecSpot = pLinkNode->m_vecOrigin; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( m_pNodes[ iNode ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ iNode ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ iNode ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + NODE_HEIGHT ); + MESSAGE_END(); + + } +} + +//========================================================= +// CGraph - LinkVisibleNodes - the first, most basic +// function of node graph creation, this connects every +// node to every other node that it can see. Expects a +// pointer to an empty connection pool and a file pointer +// to write progress to. Returns the total number of initial +// links. +// +// If there's a problem with this process, the index +// of the offending node will be written to piBadNode +//========================================================= +int CGraph :: LinkVisibleNodes ( CLink *pLinkPool, FILE *file, int *piBadNode ) +{ + int i,j,z; + edict_t *pTraceEnt; + int cTotalLinks, cLinksThisNode, cMaxInitialLinks; + TraceResult tr; + + // !!!BUGBUG - this function returns 0 if there is a problem in the middle of connecting the graph + // it also returns 0 if none of the nodes in a level can see each other. piBadNode is ALWAYS read + // by BuildNodeGraph() if this function returns a 0, so make sure that it doesn't get some random + // number back. + *piBadNode = 0; + + + if ( m_cNodes <= 0 ) + { + ALERT ( at_aiconsole, "No Nodes!\n" ); + return FALSE; + } + + // if the file pointer is bad, don't blow up, just don't write the + // file. + if ( !file ) + { + ALERT ( at_aiconsole, "**LinkVisibleNodes:\ncan't write to file." ); + } + else + { + fprintf ( file, "----------------------------------------------------------------------------\n" ); + fprintf ( file, "LinkVisibleNodes - Initial Connections\n" ); + fprintf ( file, "----------------------------------------------------------------------------\n" ); + } + + cTotalLinks = 0;// start with no connections + + // to keep track of the maximum number of initial links any node had so far. + // this lets us keep an eye on MAX_NODE_INITIAL_LINKS to ensure that we are + // being generous enough. + cMaxInitialLinks = 0; + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + cLinksThisNode = 0;// reset this count for each node. + + if ( file ) + { + fprintf ( file, "Node #%4d:\n\n", i ); + } + + for ( z = 0 ; z < MAX_NODE_INITIAL_LINKS ; z++ ) + {// clear out the important fields in the link pool for this node + pLinkPool [ cTotalLinks + z ].m_iSrcNode = i;// so each link knows which node it originates from + pLinkPool [ cTotalLinks + z ].m_iDestNode = 0; + pLinkPool [ cTotalLinks + z ].m_pLinkEnt = NULL; + } + + m_pNodes [ i ].m_iFirstLink = cTotalLinks; + + // now build a list of every other node that this node can see + for ( j = 0 ; j < m_cNodes ; j++ ) + { + if ( j == i ) + {// don't connect to self! + continue; + } + +#if 0 + + if ( (m_pNodes[ i ].m_afNodeInfo & bits_NODE_WATER) != (m_pNodes[ j ].m_afNodeInfo & bits_NODE_WATER) ) + { + // don't connect water nodes to air nodes or land nodes. It just wouldn't be prudent at this juncture. + continue; + } +#else + if ( (m_pNodes[ i ].m_afNodeInfo & bits_NODE_GROUP_REALM) != (m_pNodes[ j ].m_afNodeInfo & bits_NODE_GROUP_REALM) ) + { + // don't connect air nodes to water nodes to land nodes. It just wouldn't be prudent at this juncture. + continue; + } +#endif + + tr.pHit = NULL;// clear every time so we don't get stuck with last trace's hit ent + pTraceEnt = 0; + + UTIL_TraceLine ( m_pNodes[ i ].m_vecOrigin, + m_pNodes[ j ].m_vecOrigin, + ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &tr ); + + + if ( tr.fStartSolid ) + continue; + + if ( tr.flFraction != 1.0 ) + {// trace hit a brush ent, trace backwards to make sure that this ent is the only thing in the way. + + pTraceEnt = tr.pHit;// store the ent that the trace hit, for comparison + + UTIL_TraceLine ( m_pNodes[ j ].m_vecOrigin, + m_pNodes[ i ].m_vecOrigin, + ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &tr ); + + +// there is a solid_bsp ent in the way of these two nodes, so we must record several things about in order to keep +// track of it in the pathfinding code, as well as through save and restore of the node graph. ANY data that is manipulated +// as part of the process of adding a LINKENT to a connection here must also be done in CGraph::SetGraphPointers, where reloaded +// graphs are prepared for use. + if ( tr.pHit == pTraceEnt && !FClassnameIs( tr.pHit, "worldspawn" ) ) + { + // get a pointer + pLinkPool [ cTotalLinks ].m_pLinkEnt = VARS( tr.pHit ); + + // record the modelname, so that we can save/load node trees + memcpy( pLinkPool [ cTotalLinks ].m_szLinkEntModelname, STRING( VARS(tr.pHit)->model ), 4 ); + + // set the flag for this ent that indicates that it is attached to the world graph + // if this ent is removed from the world, it must also be removed from the connections + // that it formerly blocked. + if ( !FBitSet( VARS( tr.pHit )->flags, FL_GRAPHED ) ) + { + VARS( tr.pHit )->flags += FL_GRAPHED; + } + } + else + {// even if the ent wasn't there, these nodes couldn't be connected. Skip. + continue; + } + } + + if ( file ) + { + fprintf ( file, "%4d", j ); + + if ( !FNullEnt( pLinkPool[ cTotalLinks ].m_pLinkEnt ) ) + {// record info about the ent in the way, if any. + fprintf ( file, " Entity on connection: %s, name: %s Model: %s", STRING( VARS( pTraceEnt )->classname ), STRING ( VARS( pTraceEnt )->targetname ), STRING ( VARS(tr.pHit)->model ) ); + } + + fprintf ( file, "\n", j ); + } + + pLinkPool [ cTotalLinks ].m_iDestNode = j; + cLinksThisNode++; + cTotalLinks++; + + // If we hit this, either a level designer is placing too many nodes in the same area, or + // we need to allow for a larger initial link pool. + if ( cLinksThisNode == MAX_NODE_INITIAL_LINKS ) + { + ALERT ( at_aiconsole, "**LinkVisibleNodes:\nNode %d has NodeLinks > MAX_NODE_INITIAL_LINKS", i ); + fprintf ( file, "** NODE %d HAS NodeLinks > MAX_NODE_INITIAL_LINKS **\n", i ); + *piBadNode = i; + return FALSE; + } + else if ( cTotalLinks > MAX_NODE_INITIAL_LINKS * m_cNodes ) + {// this is paranoia + ALERT ( at_aiconsole, "**LinkVisibleNodes:\nTotalLinks > MAX_NODE_INITIAL_LINKS * NUMNODES" ); + *piBadNode = i; + return FALSE; + } + + if ( cLinksThisNode == 0 ) + { + fprintf ( file, "**NO INITIAL LINKS**\n" ); + } + + // record the connection info in the link pool + WorldGraph.m_pNodes [ i ].m_cNumLinks = cLinksThisNode; + + // keep track of the most initial links ANY node had, so we can figure out + // if we have a large enough default link pool + if ( cLinksThisNode > cMaxInitialLinks ) + { + cMaxInitialLinks = cLinksThisNode; + } + } + + + if ( file ) + { + fprintf ( file, "----------------------------------------------------------------------------\n" ); + } + } + + fprintf ( file, "\n%4d Total Initial Connections - %4d Maximum connections for a single node.\n", cTotalLinks, cMaxInitialLinks ); + fprintf ( file, "----------------------------------------------------------------------------\n\n\n" ); + + return cTotalLinks; +} + +//========================================================= +// CGraph - RejectInlineLinks - expects a pointer to a link +// pool, and a pointer to and already-open file ( if you +// want status reports written to disk ). RETURNS the number +// of connections that were rejected +//========================================================= +int CGraph :: RejectInlineLinks ( CLink *pLinkPool, FILE *file ) +{ + int i,j,k; + + int cRejectedLinks; + + BOOL fRestartLoop;// have to restart the J loop if we eliminate a link. + + CNode *pSrcNode; + CNode *pCheckNode;// the node we are testing for (one of pSrcNode's connections) + CNode *pTestNode;// the node we are checking against ( also one of pSrcNode's connections) + + float flDistToTestNode, flDistToCheckNode; + + Vector2D vec2DirToTestNode, vec2DirToCheckNode; + + if ( file ) + { + fprintf ( file, "----------------------------------------------------------------------------\n" ); + fprintf ( file, "InLine Rejection:\n" ); + fprintf ( file, "----------------------------------------------------------------------------\n" ); + } + + cRejectedLinks = 0; + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + pSrcNode = &m_pNodes[ i ]; + + if ( file ) + { + fprintf ( file, "Node %3d:\n", i ); + } + + for ( j = 0 ; j < pSrcNode->m_cNumLinks ; j++ ) + { + pCheckNode = &m_pNodes[ pLinkPool[ pSrcNode->m_iFirstLink + j ].m_iDestNode ]; + + vec2DirToCheckNode = ( pCheckNode->m_vecOrigin - pSrcNode->m_vecOrigin ).Make2D(); + flDistToCheckNode = vec2DirToCheckNode.Length(); + vec2DirToCheckNode = vec2DirToCheckNode.Normalize(); + + pLinkPool[ pSrcNode->m_iFirstLink + j ].m_flWeight = flDistToCheckNode; + + fRestartLoop = FALSE; + for ( k = 0 ; k < pSrcNode->m_cNumLinks && !fRestartLoop ; k++ ) + { + if ( k == j ) + {// don't check against same node + continue; + } + + pTestNode = &m_pNodes [ pLinkPool[ pSrcNode->m_iFirstLink + k ].m_iDestNode ]; + + vec2DirToTestNode = ( pTestNode->m_vecOrigin - pSrcNode->m_vecOrigin ).Make2D(); + + flDistToTestNode = vec2DirToTestNode.Length(); + vec2DirToTestNode = vec2DirToTestNode.Normalize(); + + if ( DotProduct ( vec2DirToCheckNode, vec2DirToTestNode ) >= 0.998 ) + { + // there's a chance that TestNode intersects the line to CheckNode. If so, we should disconnect the link to CheckNode. + if ( flDistToTestNode < flDistToCheckNode ) + { + if ( file ) + { + fprintf ( file, "REJECTED NODE %3d through Node %3d, Dot = %8f\n", pLinkPool[ pSrcNode->m_iFirstLink + j ].m_iDestNode, pLinkPool[ pSrcNode->m_iFirstLink + k ].m_iDestNode, DotProduct ( vec2DirToCheckNode, vec2DirToTestNode ) ); + } + + pLinkPool[ pSrcNode->m_iFirstLink + j ] = pLinkPool[ pSrcNode->m_iFirstLink + ( pSrcNode->m_cNumLinks - 1 ) ]; + pSrcNode->m_cNumLinks--; + j--; + + cRejectedLinks++;// keeping track of how many links are cut, so that we can return that value. + + fRestartLoop = TRUE; + } + } + } + } + + if ( file ) + { + fprintf ( file, "----------------------------------------------------------------------------\n\n" ); + } + } + + return cRejectedLinks; +} + +//========================================================= +// TestHull is a modelless clip hull that verifies reachable +// nodes by walking from every node to each of it's connections +//========================================================= +class CTestHull : public CBaseMonster +{ + +public: + void Spawn( entvars_t *pevMasterNode ); + virtual int ObjectCaps( void ) { return CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + void EXPORT CallBuildNodeGraph ( void ); + void BuildNodeGraph ( void ); + void EXPORT ShowBadNode ( void ); + void EXPORT DropDelay ( void ); + void EXPORT PathFind ( void ); + + Vector vecBadNodeOrigin; +}; + +LINK_ENTITY_TO_CLASS( testhull, CTestHull ); + +//========================================================= +// CTestHull::Spawn +//========================================================= +void CTestHull :: Spawn( entvars_t *pevMasterNode ) +{ + SET_MODEL(ENT(pev), "models/player.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + pev->effects = 0; + pev->health = 50; + pev->yaw_speed = 8; + + if ( WorldGraph.m_fGraphPresent ) + {// graph loaded from disk, so we don't need the test hull + SetThink ( &CTestHull::SUB_Remove ); + pev->nextthink = gpGlobals->time; + } + else + { + SetThink ( &CTestHull::DropDelay ); + pev->nextthink = gpGlobals->time + 1; + } + + // Make this invisible + // UNDONE: Shouldn't we just use EF_NODRAW? This doesn't need to go to the client. + pev->rendermode = kRenderTransTexture; + pev->renderamt = 0; +} + +//========================================================= +// TestHull::DropDelay - spawns TestHull on top of +// the 0th node and drops it to the ground. +//========================================================= +void CTestHull::DropDelay ( void ) +{ +// UTIL_CenterPrintAll( "Node Graph out of Date. Rebuilding..." ); + + UTIL_SetOrigin ( VARS(pev), WorldGraph.m_pNodes[ 0 ].m_vecOrigin ); + + SetThink ( &CTestHull::CallBuildNodeGraph ); + + pev->nextthink = gpGlobals->time + 1; +} + +//========================================================= +// nodes start out as ents in the world. As they are spawned, +// the node info is recorded then the ents are discarded. +//========================================================= +void CNodeEnt :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "hinttype")) + { + m_sHintType = (short)atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + + if (FStrEq(pkvd->szKeyName, "activity")) + { + m_sHintActivity = (short)atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +//========================================================= +//========================================================= +void CNodeEnt :: Spawn( void ) +{ + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT;// always solid_not + + if ( WorldGraph.m_fGraphPresent ) + {// graph loaded from disk, so discard all these node ents as soon as they spawn + REMOVE_ENTITY( edict() ); + return; + } + + if ( WorldGraph.m_cNodes == 0 ) + {// this is the first node to spawn, spawn the test hull entity that builds and walks the node tree + CTestHull *pHull = GetClassPtr((CTestHull *)NULL); + pHull->Spawn( pev ); + } + + if ( WorldGraph.m_cNodes >= MAX_NODES ) + { + ALERT ( at_aiconsole, "cNodes > MAX_NODES\n" ); + return; + } + + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_vecOriginPeek = + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_vecOrigin = pev->origin; + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_flHintYaw = pev->angles.y; + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_sHintType = m_sHintType; + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_sHintActivity = m_sHintActivity; + + if (FClassnameIs( pev, "info_node_air" )) + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_afNodeInfo = bits_NODE_AIR; + else + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_afNodeInfo = 0; + + WorldGraph.m_cNodes++; + + REMOVE_ENTITY( edict() ); +} + +//========================================================= +// CTestHull - ShowBadNode - makes a bad node fizzle. When +// there's a problem with node graph generation, the test +// hull will be placed up the bad node's location and will generate +// particles +//========================================================= +void CTestHull :: ShowBadNode( void ) +{ + pev->movetype = MOVETYPE_FLY; + pev->angles.y = pev->angles.y + 4; + + UTIL_MakeVectors ( pev->angles ); + + UTIL_ParticleEffect ( pev->origin, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin + gpGlobals->v_forward * 64, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin - gpGlobals->v_forward * 64, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin + gpGlobals->v_right * 64, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin - gpGlobals->v_right * 64, g_vecZero, 255, 25 ); + + pev->nextthink = gpGlobals->time + 0.1; +} + +extern BOOL gTouchDisabled; +void CTestHull::CallBuildNodeGraph( void ) +{ + // TOUCH HACK -- Don't allow this entity to call anyone's "touch" function + gTouchDisabled = TRUE; + BuildNodeGraph(); + gTouchDisabled = FALSE; + // Undo TOUCH HACK +} + +//========================================================= +// BuildNodeGraph - think function called by the empty walk +// hull that is spawned by the first node to spawn. This +// function links all nodes that can see each other, then +// eliminates all inline links, then uses a monster-sized +// hull that walks between each node and each of its links +// to ensure that a monster can actually fit through the space +//========================================================= +void CTestHull :: BuildNodeGraph( void ) +{ + TraceResult tr; + FILE *file; + + char szNrpFilename [MAX_PATH];// text node report filename + + CLink *pTempPool; // temporary link pool + + CNode *pSrcNode;// node we're currently working with + CNode *pDestNode;// the other node in comparison operations + + BOOL fSkipRemainingHulls;//if smallest hull can't fit, don't check any others + BOOL fPairsValid;// are all links in the graph evenly paired? + + int i, j, hull; + + int iBadNode;// this is the node that caused graph generation to fail + + int cMaxInitialLinks = 0; + int cMaxValidLinks = 0; + + int iPoolIndex = 0; + int cPoolLinks;// number of links in the pool. + + Vector vecDirToCheckNode; + Vector vecDirToTestNode; + Vector vecStepCheckDir; + Vector vecTraceSpot; + Vector vecSpot; + + Vector2D vec2DirToCheckNode; + Vector2D vec2DirToTestNode; + Vector2D vec2StepCheckDir; + Vector2D vec2TraceSpot; + Vector2D vec2Spot; + + float flYaw;// use this stuff to walk the hull between nodes + float flDist; + int step; + + SetThink ( &CTestHull::SUB_Remove );// no matter what happens, the hull gets rid of itself. + pev->nextthink = gpGlobals->time; + +// malloc a swollen temporary connection pool that we trim down after we know exactly how many connections there are. + pTempPool = (CLink *)calloc ( sizeof ( CLink ) , ( WorldGraph.m_cNodes * MAX_NODE_INITIAL_LINKS ) ); + if ( !pTempPool ) + { + ALERT ( at_aiconsole, "**Could not malloc TempPool!\n" ); + return; + } + + + // make sure directories have been made + GET_GAME_DIR( szNrpFilename ); + strcat( szNrpFilename, "/maps" ); + CreateDirectory( szNrpFilename, NULL ); + strcat( szNrpFilename, "/graphs" ); + CreateDirectory( szNrpFilename, NULL ); + + strcat( szNrpFilename, "/" ); + strcat( szNrpFilename, STRING( gpGlobals->mapname ) ); + strcat( szNrpFilename, ".nrp" ); + + file = fopen ( szNrpFilename, "w+" ); + + if ( !file ) + {// file error + ALERT ( at_aiconsole, "Couldn't create %s!\n", szNrpFilename ); + + if ( pTempPool ) + { + free ( pTempPool ); + } + + return; + } + + fprintf( file, "Node Graph Report for map: %s.bsp\n", STRING(gpGlobals->mapname) ); + fprintf ( file, "%d Total Nodes\n\n", WorldGraph.m_cNodes ); + + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + {// print all node numbers and their locations to the file. + WorldGraph.m_pNodes[ i ].m_cNumLinks = 0; + WorldGraph.m_pNodes[ i ].m_iFirstLink = 0; + memset(WorldGraph.m_pNodes[ i ].m_pNextBestNode, 0, sizeof(WorldGraph.m_pNodes[ i ].m_pNextBestNode)); + + fprintf ( file, "Node# %4d\n", i ); + fprintf ( file, "Location %4d,%4d,%4d\n",(int)WorldGraph.m_pNodes[ i ].m_vecOrigin.x, (int)WorldGraph.m_pNodes[ i ].m_vecOrigin.y, (int)WorldGraph.m_pNodes[ i ].m_vecOrigin.z ); + fprintf ( file, "HintType: %4d\n", WorldGraph.m_pNodes[ i ].m_sHintType ); + fprintf ( file, "HintActivity: %4d\n", WorldGraph.m_pNodes[ i ].m_sHintActivity ); + fprintf ( file, "HintYaw: %4f\n", WorldGraph.m_pNodes[ i ].m_flHintYaw ); + fprintf ( file, "-------------------------------------------------------------------------------\n" ); + } + fprintf ( file, "\n\n" ); + + + // Automatically recognize WATER nodes and drop the LAND nodes to the floor. + // + for ( i = 0; i < WorldGraph.m_cNodes; i++) + { + if (WorldGraph.m_pNodes[ i ].m_afNodeInfo & bits_NODE_AIR) + { + // do nothing + } + else if (UTIL_PointContents(WorldGraph.m_pNodes[ i ].m_vecOrigin) == CONTENTS_WATER) + { + WorldGraph.m_pNodes[ i ].m_afNodeInfo |= bits_NODE_WATER; + } + else + { + WorldGraph.m_pNodes[ i ].m_afNodeInfo |= bits_NODE_LAND; + + // trace to the ground, then pop up 8 units and place node there to make it + // easier for them to connect (think stairs, chairs, and bumps in the floor). + // After the routing is done, push them back down. + // + TraceResult tr; + + UTIL_TraceLine ( WorldGraph.m_pNodes[i].m_vecOrigin, + WorldGraph.m_pNodes[i].m_vecOrigin - Vector ( 0, 0, 384 ), + ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &tr ); + + // This trace is ONLY used if we hit an entity flagged with FL_WORLDBRUSH + TraceResult trEnt; + UTIL_TraceLine ( WorldGraph.m_pNodes[i].m_vecOrigin, + WorldGraph.m_pNodes[i].m_vecOrigin - Vector ( 0, 0, 384 ), + dont_ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &trEnt ); + + + // Did we hit something closer than the floor? + if ( trEnt.flFraction < tr.flFraction ) + { + // If it was a world brush entity, copy the node location + if ( trEnt.pHit && (trEnt.pHit->v.flags & FL_WORLDBRUSH) ) + tr.vecEndPos = trEnt.vecEndPos; + } + + WorldGraph.m_pNodes[i].m_vecOriginPeek.z = + WorldGraph.m_pNodes[i].m_vecOrigin.z = tr.vecEndPos.z + NODE_HEIGHT; + } + } + + cPoolLinks = WorldGraph.LinkVisibleNodes( pTempPool, file, &iBadNode ); + + if ( !cPoolLinks ) + { + ALERT ( at_aiconsole, "**ConnectVisibleNodes FAILED!\n" ); + + SetThink ( &CTestHull::ShowBadNode );// send the hull off to show the offending node. + //pev->solid = SOLID_NOT; + pev->origin = WorldGraph.m_pNodes[ iBadNode ].m_vecOrigin; + + if ( pTempPool ) + { + free ( pTempPool ); + } + + if ( file ) + {// close the file + fclose ( file ); + } + + return; + } + +// send the walkhull to all of this node's connections now. We'll do this here since +// so much of it relies on being able to control the test hull. + fprintf ( file, "----------------------------------------------------------------------------\n" ); + fprintf ( file, "Walk Rejection:\n"); + + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + pSrcNode = &WorldGraph.m_pNodes[ i ]; + + fprintf ( file, "-------------------------------------------------------------------------------\n"); + fprintf ( file, "Node %4d:\n\n", i ); + + for ( j = 0 ; j < pSrcNode->m_cNumLinks ; j++ ) + { + // assume that all hulls can walk this link, then eliminate the ones that can't. + pTempPool [ pSrcNode->m_iFirstLink + j ].m_afLinkInfo = bits_LINK_SMALL_HULL | bits_LINK_HUMAN_HULL | bits_LINK_LARGE_HULL | bits_LINK_FLY_HULL; + + + // do a check for each hull size. + + // if we can't fit a tiny hull through a connection, no other hulls with fit either, so we + // should just fall out of the loop. Do so by setting the SkipRemainingHulls flag. + fSkipRemainingHulls = FALSE; + for ( hull = 0 ; hull < MAX_NODE_HULLS; hull++ ) + { + if (fSkipRemainingHulls && (hull == NODE_HUMAN_HULL || hull == NODE_LARGE_HULL)) // skip the remaining walk hulls + continue; + + switch ( hull ) + { + case NODE_SMALL_HULL: + UTIL_SetSize(pev, Vector(-12, -12, 0), Vector(12, 12, 24)); + break; + case NODE_HUMAN_HULL: + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX ); + break; + case NODE_LARGE_HULL: + UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 64)); + break; + case NODE_FLY_HULL: + UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 64)); + // UTIL_SetSize(pev, Vector(0, 0, 0), Vector(0, 0, 0)); + break; + } + + UTIL_SetOrigin ( pev, pSrcNode->m_vecOrigin );// place the hull on the node + + if ( !FBitSet ( pev->flags, FL_ONGROUND ) ) + { + ALERT ( at_aiconsole, "OFFGROUND!\n" ); + } + + // now build a yaw that points to the dest node, and get the distance. + if ( j < 0 ) + { + ALERT ( at_aiconsole, "**** j = %d ****\n", j ); + if ( pTempPool ) + { + free ( pTempPool ); + } + + if ( file ) + {// close the file + fclose ( file ); + } + return; + } + + pDestNode = &WorldGraph.m_pNodes [ pTempPool[ pSrcNode->m_iFirstLink + j ].m_iDestNode ]; + + vecSpot = pDestNode->m_vecOrigin; + //vecSpot.z = pev->origin.z; + + if (hull < NODE_FLY_HULL) + { + int SaveFlags = pev->flags; + int MoveMode = WALKMOVE_WORLDONLY; + if (pSrcNode->m_afNodeInfo & bits_NODE_WATER) + { + pev->flags |= FL_SWIM; + MoveMode = WALKMOVE_NORMAL; + } + + flYaw = UTIL_VecToYaw ( pDestNode->m_vecOrigin - pev->origin ); + + flDist = ( vecSpot - pev->origin ).Length2D(); + + int fWalkFailed = FALSE; + + // in this loop we take tiny steps from the current node to the nodes that it links to, one at a time. + // pev->angles.y = flYaw; + for ( step = 0 ; step < flDist && !fWalkFailed ; step += HULL_STEP_SIZE ) + { + float stepSize = HULL_STEP_SIZE; + + if ( (step + stepSize) >= (flDist-1) ) + stepSize = (flDist - step) - 1; + + if ( !WALK_MOVE( ENT(pev), flYaw, stepSize, MoveMode ) ) + {// can't take the next step + + fWalkFailed = TRUE; + break; + } + } + + if (!fWalkFailed && (pev->origin - vecSpot).Length() > 64) + { + // ALERT( at_console, "bogus walk\n"); + // we thought we + fWalkFailed = TRUE; + } + + if (fWalkFailed) + { + + //pTempPool[ pSrcNode->m_iFirstLink + j ] = pTempPool [ pSrcNode->m_iFirstLink + ( pSrcNode->m_cNumLinks - 1 ) ]; + + // now me must eliminate the hull that couldn't walk this connection + switch ( hull ) + { + case NODE_SMALL_HULL: // if this hull can't fit, nothing can, so drop the connection + fprintf ( file, "NODE_SMALL_HULL step %f\n", step ); + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~(bits_LINK_SMALL_HULL | bits_LINK_HUMAN_HULL | bits_LINK_LARGE_HULL); + fSkipRemainingHulls = TRUE;// don't bother checking larger hulls + break; + case NODE_HUMAN_HULL: + fprintf ( file, "NODE_HUMAN_HULL step %f\n", step ); + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~(bits_LINK_HUMAN_HULL | bits_LINK_LARGE_HULL); + fSkipRemainingHulls = TRUE;// don't bother checking larger hulls + break; + case NODE_LARGE_HULL: + fprintf ( file, "NODE_LARGE_HULL step %f\n", step ); + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~bits_LINK_LARGE_HULL; + break; + } + } + pev->flags = SaveFlags; + } + else + { + TraceResult tr; + + UTIL_TraceHull( pSrcNode->m_vecOrigin + Vector( 0, 0, 32 ), pDestNode->m_vecOriginPeek + Vector( 0, 0, 32 ), ignore_monsters, large_hull, ENT( pev ), &tr ); + if (tr.fStartSolid || tr.flFraction < 1.0) + { + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~bits_LINK_FLY_HULL; + } + } + } + + if (pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo == 0) + { + fprintf ( file, "Rejected Node %3d - Unreachable by ", pTempPool [ pSrcNode->m_iFirstLink + j ].m_iDestNode ); + pTempPool[ pSrcNode->m_iFirstLink + j ] = pTempPool [ pSrcNode->m_iFirstLink + ( pSrcNode->m_cNumLinks - 1 ) ]; + fprintf ( file, "Any Hull\n" ); + + pSrcNode->m_cNumLinks--; + cPoolLinks--;// we just removed a link, so decrement the total number of links in the pool. + j--; + } + + } + } + fprintf ( file, "-------------------------------------------------------------------------------\n\n\n"); + + cPoolLinks -= WorldGraph.RejectInlineLinks ( pTempPool, file ); + +// now malloc a pool just large enough to hold the links that are actually used + WorldGraph.m_pLinkPool = (CLink *) calloc ( sizeof ( CLink ), cPoolLinks ); + + if ( !WorldGraph.m_pLinkPool ) + {// couldn't make the link pool! + ALERT ( at_aiconsole, "Couldn't malloc LinkPool!\n" ); + if ( pTempPool ) + { + free ( pTempPool ); + } + if ( file ) + {// close the file + fclose ( file ); + } + + return; + } + WorldGraph.m_cLinks = cPoolLinks; + +//copy only the used portions of the TempPool into the graph's link pool + int iFinalPoolIndex = 0; + int iOldFirstLink; + + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + iOldFirstLink = WorldGraph.m_pNodes[ i ].m_iFirstLink;// store this, because we have to re-assign it before entering the copy loop + + WorldGraph.m_pNodes[ i ].m_iFirstLink = iFinalPoolIndex; + + for ( j = 0 ; j < WorldGraph.m_pNodes[ i ].m_cNumLinks ; j++ ) + { + WorldGraph.m_pLinkPool[ iFinalPoolIndex++ ] = pTempPool[ iOldFirstLink + j ]; + } + } + + + // Node sorting numbers linked nodes close to each other + // + WorldGraph.SortNodes(); + + // This is used for HashSearch + // + WorldGraph.BuildLinkLookups(); + + fPairsValid = TRUE; // assume that the connection pairs are all valid to start + + fprintf ( file, "\n\n-------------------------------------------------------------------------------\n"); + fprintf ( file, "Link Pairings:\n"); + +// link integrity check. The idea here is that if Node A links to Node B, node B should +// link to node A. If not, we have a situation that prevents us from using a basic +// optimization in the FindNearestLink function. + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + for ( j = 0 ; j < WorldGraph.m_pNodes[ i ].m_cNumLinks ; j++ ) + { + int iLink; + WorldGraph.HashSearch(WorldGraph.INodeLink(i,j), i, iLink); + if (iLink < 0) + { + fPairsValid = FALSE;// unmatched link pair. + fprintf ( file, "WARNING: Node %3d does not connect back to Node %3d\n", WorldGraph.INodeLink(i, j), i); + } + } + } + + // !!!LATER - if all connections are properly paired, when can enable an optimization in the pathfinding code + // (in the find nearest line function) + if ( fPairsValid ) + { + fprintf ( file, "\nAll Connections are Paired!\n"); + } + + fprintf ( file, "-------------------------------------------------------------------------------\n"); + fprintf ( file, "\n\n-------------------------------------------------------------------------------\n"); + fprintf ( file, "Total Number of Connections in Pool: %d\n", cPoolLinks ); + fprintf ( file, "-------------------------------------------------------------------------------\n"); + fprintf ( file, "Connection Pool: %d bytes\n", sizeof ( CLink ) * cPoolLinks ); + fprintf ( file, "-------------------------------------------------------------------------------\n"); + + + ALERT ( at_aiconsole, "%d Nodes, %d Connections\n", WorldGraph.m_cNodes, cPoolLinks ); + + // This is used for FindNearestNode + // + WorldGraph.BuildRegionTables(); + + + // Push all of the LAND nodes down to the ground now. Leave the water and air nodes alone. + // + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + if ((WorldGraph.m_pNodes[ i ].m_afNodeInfo & bits_NODE_LAND)) + { + WorldGraph.m_pNodes[ i ].m_vecOrigin.z -= NODE_HEIGHT; + } + } + + + if ( pTempPool ) + {// free the temp pool + free ( pTempPool ); + } + + if ( file ) + { + fclose ( file ); + } + + // We now have some graphing capabilities. + // + WorldGraph.m_fGraphPresent = TRUE;//graph is in memory. + WorldGraph.m_fGraphPointersSet = TRUE;// since the graph was generated, the pointers are ready + WorldGraph.m_fRoutingComplete = FALSE; // Optimal routes aren't computed, yet. + + // Compute and compress the routing information. + // + WorldGraph.ComputeStaticRoutingTables(); + +// save the node graph for this level + WorldGraph.FSaveGraph( (char *)STRING( gpGlobals->mapname ) ); + ALERT( at_console, "Done.\n"); +} + + +//========================================================= +// returns a hardcoded path. +//========================================================= +void CTestHull :: PathFind ( void ) +{ + int iPath[ 50 ]; + int iPathSize; + int i; + CNode *pNode, *pNextNode; + + if ( !WorldGraph.m_fGraphPresent || !WorldGraph.m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return; + } + + iPathSize = WorldGraph.FindShortestPath ( iPath, 0, 19, 0, 0 ); // UNDONE use hull constant + + if ( !iPathSize ) + { + ALERT ( at_aiconsole, "No Path!\n" ); + return; + } + + ALERT ( at_aiconsole, "%d\n", iPathSize ); + + pNode = &WorldGraph.m_pNodes[ iPath [ 0 ] ]; + + for ( i = 0 ; i < iPathSize - 1 ; i++ ) + { + + pNextNode = &WorldGraph.m_pNodes[ iPath [ i + 1 ] ]; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( pNode->m_vecOrigin.x ); + WRITE_COORD( pNode->m_vecOrigin.y ); + WRITE_COORD( pNode->m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( pNextNode->m_vecOrigin.x); + WRITE_COORD( pNextNode->m_vecOrigin.y); + WRITE_COORD( pNextNode->m_vecOrigin.z + NODE_HEIGHT); + MESSAGE_END(); + + pNode = pNextNode; + } + +} + + +//========================================================= +// CStack Constructor +//========================================================= +CStack :: CStack( void ) +{ + m_level = 0; +} + +//========================================================= +// pushes a value onto the stack +//========================================================= +void CStack :: Push( int value ) +{ + if ( m_level >= MAX_STACK_NODES ) + { + printf("Error!\n"); + return; + } + m_stack[m_level] = value; + m_level++; +} + +//========================================================= +// pops a value off of the stack +//========================================================= +int CStack :: Pop( void ) +{ + if ( m_level <= 0 ) + return -1; + + m_level--; + return m_stack[ m_level ]; +} + +//========================================================= +// returns the value on the top of the stack +//========================================================= +int CStack :: Top ( void ) +{ + return m_stack[ m_level - 1 ]; +} + +//========================================================= +// copies every element on the stack into an array LIFO +//========================================================= +void CStack :: CopyToArray ( int *piArray ) +{ + int i; + + for ( i = 0 ; i < m_level ; i++ ) + { + piArray[ i ] = m_stack[ i ]; + } +} + +//========================================================= +// CQueue constructor +//========================================================= +CQueue :: CQueue( void ) +{ + m_cSize = 0; + m_head = 0; + m_tail = -1; +} + +//========================================================= +// inserts a value into the queue +//========================================================= +void CQueue :: Insert ( int iValue, float fPriority ) +{ + + if ( Full() ) + { + printf ( "Queue is full!\n" ); + return; + } + + m_tail++; + + if ( m_tail == MAX_STACK_NODES ) + {//wrap around + m_tail = 0; + } + + m_queue[ m_tail ].Id = iValue; + m_queue[ m_tail ].Priority = fPriority; + m_cSize++; +} + +//========================================================= +// removes a value from the queue (FIFO) +//========================================================= +int CQueue :: Remove ( float &fPriority ) +{ + if ( m_head == MAX_STACK_NODES ) + {// wrap + m_head = 0; + } + + m_cSize--; + fPriority = m_queue[ m_head ].Priority; + return m_queue[ m_head++ ].Id; +} + +//========================================================= +// CQueue constructor +//========================================================= +CQueuePriority :: CQueuePriority( void ) +{ + m_cSize = 0; +} + +//========================================================= +// inserts a value into the priority queue +//========================================================= +void CQueuePriority :: Insert( int iValue, float fPriority ) +{ + + if ( Full() ) + { + printf ( "Queue is full!\n" ); + return; + } + + m_heap[ m_cSize ].Priority = fPriority; + m_heap[ m_cSize ].Id = iValue; + m_cSize++; + Heap_SiftUp(); +} + +//========================================================= +// removes the smallest item from the priority queue +// +//========================================================= +int CQueuePriority :: Remove( float &fPriority ) +{ + int iReturn = m_heap[ 0 ].Id; + fPriority = m_heap[ 0 ].Priority; + + m_cSize--; + + m_heap[ 0 ] = m_heap[ m_cSize ]; + + Heap_SiftDown(0); + return iReturn; +} + +#define HEAP_LEFT_CHILD(x) (2*(x)+1) +#define HEAP_RIGHT_CHILD(x) (2*(x)+2) +#define HEAP_PARENT(x) (((x)-1)/2) + +void CQueuePriority::Heap_SiftDown(int iSubRoot) +{ + int parent = iSubRoot; + int child = HEAP_LEFT_CHILD(parent); + + struct tag_HEAP_NODE Ref = m_heap[ parent ]; + + while (child < m_cSize) + { + int rightchild = HEAP_RIGHT_CHILD(parent); + if (rightchild < m_cSize) + { + if ( m_heap[ rightchild ].Priority < m_heap[ child ].Priority ) + { + child = rightchild; + } + } + if ( Ref.Priority <= m_heap[ child ].Priority ) + break; + + m_heap[ parent ] = m_heap[ child ]; + parent = child; + child = HEAP_LEFT_CHILD(parent); + } + m_heap[ parent ] = Ref; +} + +void CQueuePriority::Heap_SiftUp(void) +{ + int child = m_cSize-1; + while (child) + { + int parent = HEAP_PARENT(child); + if ( m_heap[ parent ].Priority <= m_heap[ child ].Priority ) + break; + + struct tag_HEAP_NODE Tmp; + Tmp = m_heap[ child ]; + m_heap[ child ] = m_heap[ parent ]; + m_heap[ parent ] = Tmp; + + child = parent; + } +} + +//========================================================= +// CGraph - FLoadGraph - attempts to load a node graph from disk. +// if the current level is maps/snar.bsp, maps/graphs/snar.nod +// will be loaded. If file cannot be loaded, the node tree +// will be created and saved to disk. +//========================================================= +int CGraph :: FLoadGraph ( char *szMapName ) +{ + char szFilename[MAX_PATH]; + int iVersion; + int length; + byte *aMemFile; + byte *pMemFile; + + // make sure the directories have been made + char szDirName[MAX_PATH]; + GET_GAME_DIR( szDirName ); + strcat( szDirName, "/maps" ); + CreateDirectory( szDirName, NULL ); + strcat( szDirName, "/graphs" ); + CreateDirectory( szDirName, NULL ); + + strcpy ( szFilename, "maps/graphs/" ); + strcat ( szFilename, szMapName ); + strcat( szFilename, ".nod" ); + + pMemFile = aMemFile = LOAD_FILE_FOR_ME(szFilename, &length); + + if ( !aMemFile ) + { + return FALSE; + } + else + { + // Read the graph version number + // + length -= sizeof(int); + if (length < 0) goto ShortFile; + memcpy(&iVersion, pMemFile, sizeof(int)); + pMemFile += sizeof(int); + + if ( iVersion != GRAPH_VERSION ) + { + // This file was written by a different build of the dll! + // + ALERT ( at_aiconsole, "**ERROR** Graph version is %d, expected %d\n",iVersion, GRAPH_VERSION ); + goto ShortFile; + } + + // Read the graph class + // + length -= sizeof(CGraph); + if (length < 0) goto ShortFile; + memcpy(this, pMemFile, sizeof(CGraph)); + pMemFile += sizeof(CGraph); + + // Set the pointers to zero, just in case we run out of memory. + // + m_pNodes = NULL; + m_pLinkPool = NULL; + m_di = NULL; + m_pRouteInfo = NULL; + m_pHashLinks = NULL; + + + // Malloc for the nodes + // + m_pNodes = ( CNode * )calloc ( sizeof ( CNode ), m_cNodes ); + + if ( !m_pNodes ) + { + ALERT ( at_aiconsole, "**ERROR**\nCouldn't malloc %d nodes!\n", m_cNodes ); + goto NoMemory; + } + + // Read in all the nodes + // + length -= sizeof(CNode) * m_cNodes; + if (length < 0) goto ShortFile; + memcpy(m_pNodes, pMemFile, sizeof(CNode)*m_cNodes); + pMemFile += sizeof(CNode) * m_cNodes; + + + // Malloc for the link pool + // + m_pLinkPool = ( CLink * )calloc ( sizeof ( CLink ), m_cLinks ); + + if ( !m_pLinkPool ) + { + ALERT ( at_aiconsole, "**ERROR**\nCouldn't malloc %d link!\n", m_cLinks ); + goto NoMemory; + } + + // Read in all the links + // + length -= sizeof(CLink)*m_cLinks; + if (length < 0) goto ShortFile; + memcpy(m_pLinkPool, pMemFile, sizeof(CLink)*m_cLinks); + pMemFile += sizeof(CLink)*m_cLinks; + + // Malloc for the sorting info. + // + m_di = (DIST_INFO *)calloc( sizeof(DIST_INFO), m_cNodes ); + if ( !m_di ) + { + ALERT ( at_aiconsole, "***ERROR**\nCouldn't malloc %d entries sorting nodes!\n", m_cNodes ); + goto NoMemory; + } + + // Read it in. + // + length -= sizeof(DIST_INFO)*m_cNodes; + if (length < 0) goto ShortFile; + memcpy(m_di, pMemFile, sizeof(DIST_INFO)*m_cNodes); + pMemFile += sizeof(DIST_INFO)*m_cNodes; + + // Malloc for the routing info. + // + m_fRoutingComplete = FALSE; + m_pRouteInfo = (char *)calloc( sizeof(char), m_nRouteInfo ); + if ( !m_pRouteInfo ) + { + ALERT ( at_aiconsole, "***ERROR**\nCounldn't malloc %d route bytes!\n", m_nRouteInfo ); + goto NoMemory; + } + m_CheckedCounter = 0; + for (int i = 0; i < m_cNodes; i++) + { + m_di[i].m_CheckedEvent = 0; + } + + // Read in the route information. + // + length -= sizeof(char)*m_nRouteInfo; + if (length < 0) goto ShortFile; + memcpy(m_pRouteInfo, pMemFile, sizeof(char)*m_nRouteInfo); + pMemFile += sizeof(char)*m_nRouteInfo; + m_fRoutingComplete = TRUE;; + + // malloc for the hash links + // + m_pHashLinks = (short *)calloc(sizeof(short), m_nHashLinks); + if (!m_pHashLinks) + { + ALERT ( at_aiconsole, "***ERROR**\nCounldn't malloc %d hash link bytes!\n", m_nHashLinks ); + goto NoMemory; + } + + // Read in the hash link information + // + length -= sizeof(short)*m_nHashLinks; + if (length < 0) goto ShortFile; + memcpy(m_pHashLinks, pMemFile, sizeof(short)*m_nHashLinks); + pMemFile += sizeof(short)*m_nHashLinks; + + // Set the graph present flag, clear the pointers set flag + // + m_fGraphPresent = TRUE; + m_fGraphPointersSet = FALSE; + + FREE_FILE(aMemFile); + + if (length != 0) + { + ALERT ( at_aiconsole, "***WARNING***:Node graph was longer than expected by %d bytes.!\n", length); + } + + return TRUE; + } + +ShortFile: +NoMemory: + FREE_FILE(aMemFile); + return FALSE; +} + +//========================================================= +// CGraph - FSaveGraph - It's not rocket science. +// this WILL overwrite existing files. +//========================================================= +int CGraph :: FSaveGraph ( char *szMapName ) +{ + + int iVersion = GRAPH_VERSION; + char szFilename[MAX_PATH]; + FILE *file; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available or built + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return FALSE; + } + + // make sure directories have been made + GET_GAME_DIR( szFilename ); + strcat( szFilename, "/maps" ); + CreateDirectory( szFilename, NULL ); + strcat( szFilename, "/graphs" ); + CreateDirectory( szFilename, NULL ); + + strcat( szFilename, "/" ); + strcat( szFilename, szMapName ); + strcat( szFilename, ".nod" ); + + file = fopen ( szFilename, "wb" ); + + ALERT ( at_aiconsole, "Created: %s\n", szFilename ); + + if ( !file ) + {// couldn't create + ALERT ( at_aiconsole, "Couldn't Create: %s\n", szFilename ); + return FALSE; + } + else + { + // write the version + fwrite ( &iVersion, sizeof ( int ), 1, file ); + + // write the CGraph class + fwrite ( this, sizeof ( CGraph ), 1, file ); + + // write the nodes + fwrite ( m_pNodes, sizeof ( CNode ), m_cNodes, file ); + + // write the links + fwrite ( m_pLinkPool, sizeof ( CLink ), m_cLinks, file ); + + fwrite ( m_di, sizeof(DIST_INFO), m_cNodes, file ); + + // Write the route info. + // + if ( m_pRouteInfo && m_nRouteInfo ) + { + fwrite ( m_pRouteInfo, sizeof( char ), m_nRouteInfo, file ); + } + + if (m_pHashLinks && m_nHashLinks) + { + fwrite(m_pHashLinks, sizeof(short), m_nHashLinks, file); + } + fclose ( file ); + return TRUE; + } +} + +//========================================================= +// CGraph - FSetGraphPointers - Takes the modelnames of +// all of the brush ents that block connections in the node +// graph and resolves them into pointers to those entities. +// this is done after loading the graph from disk, whereupon +// the pointers are not valid. +//========================================================= +int CGraph :: FSetGraphPointers ( void ) +{ + int i; + edict_t *pentLinkEnt; + + for ( i = 0 ; i < m_cLinks ; i++ ) + {// go through all of the links + + if ( m_pLinkPool[ i ].m_pLinkEnt != NULL ) + { + char name[5]; + // when graphs are saved, any valid pointers are will be non-zero, signifying that we should + // reset those pointers upon reloading. Any pointers that were NULL when the graph was saved + // will be NULL when reloaded, and will ignored by this function. + + // m_szLinkEntModelname is not necessarily NULL terminated (so we can store it in a more alignment-friendly 4 bytes) + memcpy( name, m_pLinkPool[ i ].m_szLinkEntModelname, 4 ); + name[4] = 0; + pentLinkEnt = FIND_ENTITY_BY_STRING( NULL, "model", name ); + + if ( FNullEnt ( pentLinkEnt ) ) + { + // the ent isn't around anymore? Either there is a major problem, or it was removed from the world + // ( like a func_breakable that's been destroyed or something ). Make sure that LinkEnt is null. + ALERT ( at_aiconsole, "**Could not find model %s\n", name ); + m_pLinkPool[ i ].m_pLinkEnt = NULL; + } + else + { + m_pLinkPool[ i ].m_pLinkEnt = VARS( pentLinkEnt ); + + if ( !FBitSet( m_pLinkPool[ i ].m_pLinkEnt->flags, FL_GRAPHED ) ) + { + m_pLinkPool[ i ].m_pLinkEnt->flags += FL_GRAPHED; + } + } + } + } + + // the pointers are now set. + m_fGraphPointersSet = TRUE; + return TRUE; +} + +//========================================================= +// CGraph - CheckNODFile - this function checks the date of +// the BSP file that was just loaded and the date of the a +// ssociated .NOD file. If the NOD file is not present, or +// is older than the BSP file, we rebuild it. +// +// returns FALSE if the .NOD file doesn't qualify and needs +// to be rebuilt. +// +// !!!BUGBUG - the file times we get back are 20 hours ahead! +// since this happens consistantly, we can still correctly +// determine which of the 2 files is newer. This needs fixed, +// though. ( I now suspect that we are getting GMT back from +// these functions and must compensate for local time ) (sjb) +//========================================================= +int CGraph :: CheckNODFile ( char *szMapName ) +{ + int retValue; + + char szBspFilename[MAX_PATH]; + char szGraphFilename[MAX_PATH]; + + + strcpy ( szBspFilename, "maps/" ); + strcat ( szBspFilename, szMapName ); + strcat ( szBspFilename, ".bsp" ); + + strcpy ( szGraphFilename, "maps/graphs/" ); + strcat ( szGraphFilename, szMapName ); + strcat ( szGraphFilename, ".nod" ); + + retValue = TRUE; + + int iCompare; + if (COMPARE_FILE_TIME(szBspFilename, szGraphFilename, &iCompare)) + { + if ( iCompare > 0 ) + {// BSP file is newer. + ALERT ( at_aiconsole, ".NOD File will be updated\n\n" ); + retValue = FALSE; + } + } + else + { + retValue = FALSE; + } + + return retValue; +} + +#define ENTRY_STATE_EMPTY -1 + +struct tagNodePair +{ + short iSrc; + short iDest; +}; + +void CGraph::HashInsert(int iSrcNode, int iDestNode, int iKey) +{ + struct tagNodePair np; + + np.iSrc = iSrcNode; + np.iDest = iDestNode; + CRC32_t dwHash; + CRC32_INIT(&dwHash); + CRC32_PROCESS_BUFFER(&dwHash, &np, sizeof(np)); + dwHash = CRC32_FINAL(dwHash); + + int di = m_HashPrimes[dwHash&15]; + int i = (dwHash >> 4) % m_nHashLinks; + while (m_pHashLinks[i] != ENTRY_STATE_EMPTY) + { + i += di; + if (i >= m_nHashLinks) i -= m_nHashLinks; + } + m_pHashLinks[i] = iKey; +} + +void CGraph::HashSearch(int iSrcNode, int iDestNode, int &iKey) +{ + struct tagNodePair np; + + np.iSrc = iSrcNode; + np.iDest = iDestNode; + CRC32_t dwHash; + CRC32_INIT(&dwHash); + CRC32_PROCESS_BUFFER(&dwHash, &np, sizeof(np)); + dwHash = CRC32_FINAL(dwHash); + + int di = m_HashPrimes[dwHash&15]; + int i = (dwHash >> 4) % m_nHashLinks; + while (m_pHashLinks[i] != ENTRY_STATE_EMPTY) + { + CLink &link = Link(m_pHashLinks[i]); + if (iSrcNode == link.m_iSrcNode && iDestNode == link.m_iDestNode) + { + break; + } + else + { + i += di; + if (i >= m_nHashLinks) i -= m_nHashLinks; + } + } + iKey = m_pHashLinks[i]; +} + +#define NUMBER_OF_PRIMES 177 + +int Primes[NUMBER_OF_PRIMES] = +{ 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, +71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, +157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, +241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, +347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, +439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, +547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, +643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, +751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, +859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, +977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 0 }; + +void CGraph::HashChoosePrimes(int TableSize) +{ + int LargestPrime = TableSize/2; + if (LargestPrime > Primes[NUMBER_OF_PRIMES-2]) + { + LargestPrime = Primes[NUMBER_OF_PRIMES-2]; + } + int Spacing = LargestPrime/16; + + // Pick a set primes that are evenly spaced from (0 to LargestPrime) + // We divide this interval into 16 equal sized zones. We want to find + // one prime number that best represents that zone. + // + int iPrime,iZone;; + for (iZone = 1, iPrime = 0; iPrime < 16; iZone += Spacing) + { + // Search for a prime number that is less than the target zone + // number given by iZone. + // + int Lower = Primes[0]; + for (int jPrime = 0; Primes[jPrime] != 0; jPrime++) + { + if (jPrime != 0 && TableSize % Primes[jPrime] == 0) continue; + int Upper = Primes[jPrime]; + if (Lower <= iZone && iZone <= Upper) + { + // Choose the closest lower prime number. + // + if (iZone - Lower <= Upper - iZone) + { + m_HashPrimes[iPrime++] = Lower; + } + else + { + m_HashPrimes[iPrime++] = Upper; + } + break; + } + Lower = Upper; + } + } + + // Alternate negative and positive numbers + // + for (iPrime = 0; iPrime < 16; iPrime += 2) + { + m_HashPrimes[iPrime] = TableSize-m_HashPrimes[iPrime]; + } + + // Shuffle the set of primes to reduce correlation with bits in + // hash key. + // + for (iPrime = 0; iPrime < 16-1; iPrime++) + { + int Pick = RANDOM_LONG(0, 15-iPrime); + int Temp = m_HashPrimes[Pick]; + m_HashPrimes[Pick] = m_HashPrimes[15-iPrime]; + m_HashPrimes[15-iPrime] = Temp; + } +} + +// Renumber nodes so that nodes that link together are together. +// +#define UNNUMBERED_NODE -1 +void CGraph::SortNodes(void) +{ + // We are using m_iPreviousNode to be the new node number. + // After assigning new node numbers to everything, we move + // things and patchup the links. + // + int iNodeCnt = 0; + int i; + m_pNodes[0].m_iPreviousNode = iNodeCnt++; + + for (i = 1; i < m_cNodes; i++) + { + m_pNodes[i].m_iPreviousNode = UNNUMBERED_NODE; + } + + for (i = 0; i < m_cNodes; i++) + { + // Run through all of this node's neighbors + // + for (int j = 0 ; j < m_pNodes[i].m_cNumLinks; j++ ) + { + int iDestNode = INodeLink(i, j); + if (m_pNodes[iDestNode].m_iPreviousNode == UNNUMBERED_NODE) + { + m_pNodes[iDestNode].m_iPreviousNode = iNodeCnt++; + } + } + } + + // Assign remaining node numbers to unlinked nodes. + // + for (i = 0; i < m_cNodes; i++) + { + if (m_pNodes[i].m_iPreviousNode == UNNUMBERED_NODE) + { + m_pNodes[i].m_iPreviousNode = iNodeCnt++; + } + } + + // Alter links to reflect new node numbers. + // + for (i = 0; i < m_cLinks; i++) + { + m_pLinkPool[i].m_iSrcNode = m_pNodes[m_pLinkPool[i].m_iSrcNode].m_iPreviousNode; + m_pLinkPool[i].m_iDestNode = m_pNodes[m_pLinkPool[i].m_iDestNode].m_iPreviousNode; + } + + // Rearrange nodes to reflect new node numbering. + // + for (i = 0; i < m_cNodes; i++) + { + while (m_pNodes[i].m_iPreviousNode != i) + { + // Move current node off to where it should be, and bring + // that other node back into the current slot. + // + int iDestNode = m_pNodes[i].m_iPreviousNode; + CNode TempNode = m_pNodes[iDestNode]; + m_pNodes[iDestNode] = m_pNodes[i]; + m_pNodes[i] = TempNode; + } + } +} + +void CGraph::BuildLinkLookups(void) +{ + m_nHashLinks = 3*m_cLinks/2 + 3; + + HashChoosePrimes(m_nHashLinks); + m_pHashLinks = (short *)calloc(sizeof(short), m_nHashLinks); + if (!m_pHashLinks) + { + ALERT(at_aiconsole, "Couldn't allocated Link Lookup Table.\n"); + return; + } + int i; + for (i = 0; i < m_nHashLinks; i++) + { + m_pHashLinks[i] = ENTRY_STATE_EMPTY; + } + + for (i = 0; i < m_cLinks; i++) + { + CLink &link = Link(i); + HashInsert(link.m_iSrcNode, link.m_iDestNode, i); + } +#if 0 + for (i = 0; i < m_cLinks; i++) + { + CLink &link = Link(i); + int iKey; + HashSearch(link.m_iSrcNode, link.m_iDestNode, iKey); + if (iKey != i) + { + ALERT(at_aiconsole, "HashLinks don't match (%d versus %d)\n", i, iKey); + } + } +#endif +} + +void CGraph::BuildRegionTables(void) +{ + if (m_di) free(m_di); + + // Go ahead and setup for range searching the nodes for FindNearestNodes + // + m_di = (DIST_INFO *)calloc(sizeof(DIST_INFO), m_cNodes); + if (!m_di) + { + ALERT(at_aiconsole, "Couldn't allocated node ordering array.\n"); + return; + } + + // Calculate regions for all the nodes. + // + // + int i; + for (i = 0; i < 3; i++) + { + m_RegionMin[i] = 999999999.0; // just a big number out there; + m_RegionMax[i] = -999999999.0; // just a big number out there; + } + for (i = 0; i < m_cNodes; i++) + { + if (m_pNodes[i].m_vecOrigin.x < m_RegionMin[0]) + m_RegionMin[0] = m_pNodes[i].m_vecOrigin.x; + if (m_pNodes[i].m_vecOrigin.y < m_RegionMin[1]) + m_RegionMin[1] = m_pNodes[i].m_vecOrigin.y; + if (m_pNodes[i].m_vecOrigin.z < m_RegionMin[2]) + m_RegionMin[2] = m_pNodes[i].m_vecOrigin.z; + + if (m_pNodes[i].m_vecOrigin.x > m_RegionMax[0]) + m_RegionMax[0] = m_pNodes[i].m_vecOrigin.x; + if (m_pNodes[i].m_vecOrigin.y > m_RegionMax[1]) + m_RegionMax[1] = m_pNodes[i].m_vecOrigin.y; + if (m_pNodes[i].m_vecOrigin.z > m_RegionMax[2]) + m_RegionMax[2] = m_pNodes[i].m_vecOrigin.z; + } + for (i = 0; i < m_cNodes; i++) + { + m_pNodes[i].m_Region[0] = CALC_RANGE(m_pNodes[i].m_vecOrigin.x, m_RegionMin[0], m_RegionMax[0]); + m_pNodes[i].m_Region[1] = CALC_RANGE(m_pNodes[i].m_vecOrigin.y, m_RegionMin[1], m_RegionMax[1]); + m_pNodes[i].m_Region[2] = CALC_RANGE(m_pNodes[i].m_vecOrigin.z, m_RegionMin[2], m_RegionMax[2]); + } + + for (i = 0; i < 3; i++) + { + int j; + for (j = 0; j < NUM_RANGES; j++) + { + m_RangeStart[i][j] = 255; + m_RangeEnd[i][j] = 0; + } + for (j = 0; j < m_cNodes; j++) + { + m_di[j].m_SortedBy[i] = j; + } + + for (j = 0; j < m_cNodes - 1; j++) + { + int jNode = m_di[j].m_SortedBy[i]; + int jCodeX = m_pNodes[jNode].m_Region[0]; + int jCodeY = m_pNodes[jNode].m_Region[1]; + int jCodeZ = m_pNodes[jNode].m_Region[2]; + int jCode; + switch (i) + { + case 0: + jCode = (jCodeX << 16) + (jCodeY << 8) + jCodeZ; + break; + case 1: + jCode = (jCodeY << 16) + (jCodeZ << 8) + jCodeX; + break; + case 2: + jCode = (jCodeZ << 16) + (jCodeX << 8) + jCodeY; + break; + } + + for (int k = j+1; k < m_cNodes; k++) + { + int kNode = m_di[k].m_SortedBy[i]; + int kCodeX = m_pNodes[kNode].m_Region[0]; + int kCodeY = m_pNodes[kNode].m_Region[1]; + int kCodeZ = m_pNodes[kNode].m_Region[2]; + int kCode; + switch (i) + { + case 0: + kCode = (kCodeX << 16) + (kCodeY << 8) + kCodeZ; + break; + case 1: + kCode = (kCodeY << 16) + (kCodeZ << 8) + kCodeX; + break; + case 2: + kCode = (kCodeZ << 16) + (kCodeX << 8) + kCodeY; + break; + } + + if (kCode < jCode) + { + // Swap j and k entries. + // + int Tmp = m_di[j].m_SortedBy[i]; + m_di[j].m_SortedBy[i] = m_di[k].m_SortedBy[i]; + m_di[k].m_SortedBy[i] = Tmp; + } + } + } + } + + // Generate lookup tables. + // + for (i = 0; i < m_cNodes; i++) + { + int CodeX = m_pNodes[m_di[i].m_SortedBy[0]].m_Region[0]; + int CodeY = m_pNodes[m_di[i].m_SortedBy[1]].m_Region[1]; + int CodeZ = m_pNodes[m_di[i].m_SortedBy[2]].m_Region[2]; + + if (i < m_RangeStart[0][CodeX]) + { + m_RangeStart[0][CodeX] = i; + } + if (i < m_RangeStart[1][CodeY]) + { + m_RangeStart[1][CodeY] = i; + } + if (i < m_RangeStart[2][CodeZ]) + { + m_RangeStart[2][CodeZ] = i; + } + if (m_RangeEnd[0][CodeX] < i) + { + m_RangeEnd[0][CodeX] = i; + } + if (m_RangeEnd[1][CodeY] < i) + { + m_RangeEnd[1][CodeY] = i; + } + if (m_RangeEnd[2][CodeZ] < i) + { + m_RangeEnd[2][CodeZ] = i; + } + } + + // Initialize the cache. + // + memset(m_Cache, 0, sizeof(m_Cache)); +} + +void CGraph :: ComputeStaticRoutingTables( void ) +{ + int nRoutes = m_cNodes*m_cNodes; +#define FROM_TO(x,y) ((x)*m_cNodes+(y)) + short *Routes = new short[nRoutes]; + + int *pMyPath = new int[m_cNodes]; + unsigned short *BestNextNodes = new unsigned short[m_cNodes]; + char *pRoute = new char[m_cNodes*2]; + + + if (Routes && pMyPath && BestNextNodes && pRoute) + { + int nTotalCompressedSize = 0; + for (int iHull = 0; iHull < MAX_NODE_HULLS; iHull++) + { + for (int iCap = 0; iCap < 2; iCap++) + { + int iCapMask; + switch (iCap) + { + case 0: + iCapMask = 0; + break; + + case 1: + iCapMask = bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE; + break; + } + + + // Initialize Routing table to uncalculated. + // + int iFrom; + for (iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = 0; iTo < m_cNodes; iTo++) + { + Routes[FROM_TO(iFrom, iTo)] = -1; + } + } + + for (iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = m_cNodes-1; iTo >= 0; iTo--) + { + if (Routes[FROM_TO(iFrom, iTo)] != -1) continue; + + int cPathSize = FindShortestPath(pMyPath, iFrom, iTo, iHull, iCapMask); + + // Use the computed path to update the routing table. + // + if (cPathSize > 1) + { + for (int iNode = 0; iNode < cPathSize-1; iNode++) + { + int iStart = pMyPath[iNode]; + int iNext = pMyPath[iNode+1]; + for (int iNode1 = iNode+1; iNode1 < cPathSize; iNode1++) + { + int iEnd = pMyPath[iNode1]; + Routes[FROM_TO(iStart, iEnd)] = iNext; + } + } +#if 0 + // Well, at first glance, this should work, but actually it's safer + // to be told explictly that you can take a series of node in a + // particular direction. Some links don't appear to have links in + // the opposite direction. + // + for (iNode = cPathSize-1; iNode >= 1; iNode--) + { + int iStart = pMyPath[iNode]; + int iNext = pMyPath[iNode-1]; + for (int iNode1 = iNode-1; iNode1 >= 0; iNode1--) + { + int iEnd = pMyPath[iNode1]; + Routes[FROM_TO(iStart, iEnd)] = iNext; + } + } +#endif + } + else + { + Routes[FROM_TO(iFrom, iTo)] = iFrom; + Routes[FROM_TO(iTo, iFrom)] = iTo; + } + } + } + + for (iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = 0; iTo < m_cNodes; iTo++) + { + BestNextNodes[iTo] = Routes[FROM_TO(iFrom, iTo)]; + } + + // Compress this node's routing table. + // + int iLastNode = 9999999; // just really big. + int cSequence = 0; + int cRepeats = 0; + int CompressedSize = 0; + char *p = pRoute; + for (int i = 0; i < m_cNodes; i++) + { + BOOL CanRepeat = ((BestNextNodes[i] == iLastNode) && cRepeats < 127); + BOOL CanSequence = (BestNextNodes[i] == i && cSequence < 128); + + if (cRepeats) + { + if (CanRepeat) + { + cRepeats++; + } + else + { + // Emit the repeat phrase. + // + CompressedSize += 2; // (count-1, iLastNode-i) + *p++ = cRepeats - 1; + int a = iLastNode - iFrom; + int b = iLastNode - iFrom + m_cNodes; + int c = iLastNode - iFrom - m_cNodes; + if (-128 <= a && a <= 127) + { + *p++ = a; + } + else if (-128 <= b && b <= 127) + { + *p++ = b; + } + else if (-128 <= c && c <= 127) + { + *p++ = c; + } + else + { + ALERT( at_aiconsole, "Nodes need sorting (%d,%d)!\n", iLastNode, iFrom); + } + cRepeats = 0; + + if (CanSequence) + { + // Start a sequence. + // + cSequence++; + } + else + { + // Start another repeat. + // + cRepeats++; + } + } + } + else if (cSequence) + { + if (CanSequence) + { + cSequence++; + } + else + { + // It may be advantageous to combine + // a single-entry sequence phrase with the + // next repeat phrase. + // + if (cSequence == 1 && CanRepeat) + { + // Combine with repeat phrase. + // + cRepeats = 2; + cSequence = 0; + } + else + { + // Emit the sequence phrase. + // + CompressedSize += 1; // (-count) + *p++ = -cSequence; + cSequence = 0; + + // Start a repeat sequence. + // + cRepeats++; + } + } + } + else + { + if (CanSequence) + { + // Start a sequence phrase. + // + cSequence++; + } + else + { + // Start a repeat sequence. + // + cRepeats++; + } + } + iLastNode = BestNextNodes[i]; + } + if (cRepeats) + { + // Emit the repeat phrase. + // + CompressedSize += 2; + *p++ = cRepeats - 1; +#if 0 + iLastNode = iFrom + *pRoute; + if (iLastNode >= m_cNodes) iLastNode -= m_cNodes; + else if (iLastNode < 0) iLastNode += m_cNodes; +#endif + int a = iLastNode - iFrom; + int b = iLastNode - iFrom + m_cNodes; + int c = iLastNode - iFrom - m_cNodes; + if (-128 <= a && a <= 127) + { + *p++ = a; + } + else if (-128 <= b && b <= 127) + { + *p++ = b; + } + else if (-128 <= c && c <= 127) + { + *p++ = c; + } + else + { + ALERT( at_aiconsole, "Nodes need sorting (%d,%d)!\n", iLastNode, iFrom); + } + } + if (cSequence) + { + // Emit the Sequence phrase. + // + CompressedSize += 1; + *p++ = -cSequence; + } + + // Go find a place to store this thing and point to it. + // + int nRoute = p - pRoute; + if (m_pRouteInfo) + { + int i; + for (i = 0; i < m_nRouteInfo - nRoute; i++) + { + if (memcmp(m_pRouteInfo + i, pRoute, nRoute) == 0) + { + break; + } + } + if (i < m_nRouteInfo - nRoute) + { + m_pNodes[ iFrom ].m_pNextBestNode[iHull][iCap] = i; + } + else + { + char *Tmp = (char *)calloc(sizeof(char), (m_nRouteInfo + nRoute)); + memcpy(Tmp, m_pRouteInfo, m_nRouteInfo); + free(m_pRouteInfo); + m_pRouteInfo = Tmp; + memcpy(m_pRouteInfo + m_nRouteInfo, pRoute, nRoute); + m_pNodes[ iFrom ].m_pNextBestNode[iHull][iCap] = m_nRouteInfo; + m_nRouteInfo += nRoute; + nTotalCompressedSize += CompressedSize; + } + } + else + { + m_nRouteInfo = nRoute; + m_pRouteInfo = (char *)calloc(sizeof(char), nRoute); + memcpy(m_pRouteInfo, pRoute, nRoute); + m_pNodes[ iFrom ].m_pNextBestNode[iHull][iCap] = 0; + nTotalCompressedSize += CompressedSize; + } + } + } + } + ALERT( at_aiconsole, "Size of Routes = %d\n", nTotalCompressedSize); + } + if (Routes) delete Routes; + if (BestNextNodes) delete BestNextNodes; + if (pRoute) delete pRoute; + if (pMyPath) delete pMyPath; + Routes = 0; + BestNextNodes = 0; + pRoute = 0; + pMyPath = 0; + +#if 0 + TestRoutingTables(); +#endif + m_fRoutingComplete = TRUE; +} + +// Test those routing tables. Doesn't really work, yet. +// +void CGraph :: TestRoutingTables( void ) +{ + int *pMyPath = new int[m_cNodes]; + int *pMyPath2 = new int[m_cNodes]; + if (pMyPath && pMyPath2) + { + for (int iHull = 0; iHull < MAX_NODE_HULLS; iHull++) + { + for (int iCap = 0; iCap < 2; iCap++) + { + int iCapMask; + switch (iCap) + { + case 0: + iCapMask = 0; + break; + + case 1: + iCapMask = bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE; + break; + } + + for (int iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = 0; iTo < m_cNodes; iTo++) + { + m_fRoutingComplete = FALSE; + int cPathSize1 = FindShortestPath(pMyPath, iFrom, iTo, iHull, iCapMask); + m_fRoutingComplete = TRUE; + int cPathSize2 = FindShortestPath(pMyPath2, iFrom, iTo, iHull, iCapMask); + + // Unless we can look at the entire path, we can verify that it's correct. + // + if (cPathSize2 == MAX_PATH_SIZE) continue; + + // Compare distances. + // +#if 1 + float flDistance1 = 0.0; + int i; + for (i = 0; i < cPathSize1-1; i++) + { + // Find the link from pMyPath[i] to pMyPath[i+1] + // + if (pMyPath[i] == pMyPath[i+1]) continue; + int iVisitNode; + BOOL bFound = FALSE; + for (int iLink = 0; iLink < m_pNodes[pMyPath[i]].m_cNumLinks; iLink++) + { + iVisitNode = INodeLink ( pMyPath[i], iLink ); + if (iVisitNode == pMyPath[i+1]) + { + flDistance1 += m_pLinkPool[ m_pNodes[ pMyPath[i] ].m_iFirstLink + iLink].m_flWeight; + bFound = TRUE; + break; + } + } + if (!bFound) + { + ALERT(at_aiconsole, "No link.\n"); + } + } + + float flDistance2 = 0.0; + for (i = 0; i < cPathSize2-1; i++) + { + // Find the link from pMyPath2[i] to pMyPath2[i+1] + // + if (pMyPath2[i] == pMyPath2[i+1]) continue; + int iVisitNode; + BOOL bFound = FALSE; + for (int iLink = 0; iLink < m_pNodes[pMyPath2[i]].m_cNumLinks; iLink++) + { + iVisitNode = INodeLink ( pMyPath2[i], iLink ); + if (iVisitNode == pMyPath2[i+1]) + { + flDistance2 += m_pLinkPool[ m_pNodes[ pMyPath2[i] ].m_iFirstLink + iLink].m_flWeight; + bFound = TRUE; + break; + } + } + if (!bFound) + { + ALERT(at_aiconsole, "No link.\n"); + } + } + if (fabs(flDistance1 - flDistance2) > 0.10) + { +#else + if (cPathSize1 != cPathSize2 || memcmp(pMyPath, pMyPath2, sizeof(int)*cPathSize1) != 0) + { +#endif + ALERT(at_aiconsole, "Routing is inconsistent!!!\n"); + ALERT(at_aiconsole, "(%d to %d |%d/%d)1:", iFrom, iTo, iHull, iCap); + for (int i = 0; i < cPathSize1; i++) + { + ALERT(at_aiconsole, "%d ", pMyPath[i]); + } + ALERT(at_aiconsole, "\n(%d to %d |%d/%d)2:", iFrom, iTo, iHull, iCap); + for (i = 0; i < cPathSize2; i++) + { + ALERT(at_aiconsole, "%d ", pMyPath2[i]); + } + ALERT(at_aiconsole, "\n"); + m_fRoutingComplete = FALSE; + cPathSize1 = FindShortestPath(pMyPath, iFrom, iTo, iHull, iCapMask); + m_fRoutingComplete = TRUE; + cPathSize2 = FindShortestPath(pMyPath2, iFrom, iTo, iHull, iCapMask); + goto EnoughSaid; + } + } + } + } + } + } + +EnoughSaid: + + if (pMyPath) delete pMyPath; + if (pMyPath2) delete pMyPath2; + pMyPath = 0; + pMyPath2 = 0; +} + + + + + + + + + +//========================================================= +// CNodeViewer - Draws a graph of the shorted path from all nodes +// to current location (typically the player). It then draws +// as many connects as it can per frame, trying not to overflow the buffer +//========================================================= +class CNodeViewer : public CBaseEntity +{ +public: + void Spawn( void ); + + int m_iBaseNode; + int m_iDraw; + int m_nVisited; + int m_aFrom[128]; + int m_aTo[128]; + int m_iHull; + int m_afNodeType; + Vector m_vecColor; + + void FindNodeConnections( int iNode ); + void AddNode( int iFrom, int iTo ); + void EXPORT DrawThink( void ); + +}; +LINK_ENTITY_TO_CLASS( node_viewer, CNodeViewer ); +LINK_ENTITY_TO_CLASS( node_viewer_human, CNodeViewer ); +LINK_ENTITY_TO_CLASS( node_viewer_fly, CNodeViewer ); +LINK_ENTITY_TO_CLASS( node_viewer_large, CNodeViewer ); + +void CNodeViewer::Spawn( ) +{ + if ( !WorldGraph.m_fGraphPresent || !WorldGraph.m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available or built + ALERT ( at_console, "Graph not ready!\n" ); + UTIL_Remove( this ); + return; + } + + + if (FClassnameIs( pev, "node_viewer_fly")) + { + m_iHull = NODE_FLY_HULL; + m_afNodeType = bits_NODE_AIR; + m_vecColor = Vector( 160, 100, 255 ); + } + else if (FClassnameIs( pev, "node_viewer_large")) + { + m_iHull = NODE_LARGE_HULL; + m_afNodeType = bits_NODE_LAND | bits_NODE_WATER; + m_vecColor = Vector( 100, 255, 160 ); + } + else + { + m_iHull = NODE_HUMAN_HULL; + m_afNodeType = bits_NODE_LAND | bits_NODE_WATER; + m_vecColor = Vector( 255, 160, 100 ); + } + + + m_iBaseNode = WorldGraph.FindNearestNode ( pev->origin, m_afNodeType ); + + if ( m_iBaseNode < 0 ) + { + ALERT( at_console, "No nearby node\n" ); + return; + } + + m_nVisited = 0; + + ALERT( at_aiconsole, "basenode %d\n", m_iBaseNode ); + + if (WorldGraph.m_cNodes < 128) + { + for (int i = 0; i < WorldGraph.m_cNodes; i++) + { + AddNode( i, WorldGraph.NextNodeInRoute( i, m_iBaseNode, m_iHull, 0 )); + } + } + else + { + // do a depth traversal + FindNodeConnections( m_iBaseNode ); + + int start = 0; + int end; + do { + end = m_nVisited; + // ALERT( at_console, "%d :", m_nVisited ); + for (end = m_nVisited; start < end; start++) + { + FindNodeConnections( m_aFrom[start] ); + FindNodeConnections( m_aTo[start] ); + } + } while (end != m_nVisited); + } + + ALERT( at_aiconsole, "%d nodes\n", m_nVisited ); + + m_iDraw = 0; + SetThink( &CNodeViewer::DrawThink ); + pev->nextthink = gpGlobals->time; +} + + +void CNodeViewer :: FindNodeConnections ( int iNode ) +{ + AddNode( iNode, WorldGraph.NextNodeInRoute( iNode, m_iBaseNode, m_iHull, 0 )); + for ( int i = 0 ; i < WorldGraph.m_pNodes[ iNode ].m_cNumLinks ; i++ ) + { + CLink *pToLink = &WorldGraph.NodeLink( iNode, i); + AddNode( pToLink->m_iDestNode, WorldGraph.NextNodeInRoute( pToLink->m_iDestNode, m_iBaseNode, m_iHull, 0 )); + } +} + +void CNodeViewer::AddNode( int iFrom, int iTo ) +{ + if (m_nVisited >= 128) + { + return; + } + else + { + if (iFrom == iTo) + return; + + for (int i = 0; i < m_nVisited; i++) + { + if (m_aFrom[i] == iFrom && m_aTo[i] == iTo) + return; + if (m_aFrom[i] == iTo && m_aTo[i] == iFrom) + return; + } + m_aFrom[m_nVisited] = iFrom; + m_aTo[m_nVisited] = iTo; + m_nVisited++; + } +} + + +void CNodeViewer :: DrawThink( void ) +{ + pev->nextthink = gpGlobals->time; + + for (int i = 0; i < 10; i++) + { + if (m_iDraw == m_nVisited) + { + UTIL_Remove( this ); + return; + } + + extern short g_sModelIndexLaser; + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aFrom[m_iDraw] ].m_vecOrigin.x ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aFrom[m_iDraw] ].m_vecOrigin.y ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aFrom[m_iDraw] ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( WorldGraph.m_pNodes[ m_aTo[m_iDraw] ].m_vecOrigin.x ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aTo[m_iDraw] ].m_vecOrigin.y ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aTo[m_iDraw] ].m_vecOrigin.z + NODE_HEIGHT ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 250 ); // life + WRITE_BYTE( 40 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( m_vecColor.x ); // r, g, b + WRITE_BYTE( m_vecColor.y ); // r, g, b + WRITE_BYTE( m_vecColor.z ); // r, g, b + WRITE_BYTE( 128 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + m_iDraw++; + } +} + + diff --git a/dlls/nodes.h b/dlls/nodes.h new file mode 100644 index 0000000..9213d8b --- /dev/null +++ b/dlls/nodes.h @@ -0,0 +1,374 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// nodes.h +//========================================================= + +//========================================================= +// DEFINE +//========================================================= +#define MAX_STACK_NODES 100 +#define NO_NODE -1 +#define MAX_NODE_HULLS 4 + +#define bits_NODE_LAND ( 1 << 0 ) // Land node, so nudge if necessary. +#define bits_NODE_AIR ( 1 << 1 ) // Air node, don't nudge. +#define bits_NODE_WATER ( 1 << 2 ) // Water node, don't nudge. +#define bits_NODE_GROUP_REALM (bits_NODE_LAND | bits_NODE_AIR | bits_NODE_WATER) + +//========================================================= +// Instance of a node. +//========================================================= +class CNode +{ +public: + Vector m_vecOrigin;// location of this node in space + Vector m_vecOriginPeek; // location of this node (LAND nodes are NODE_HEIGHT higher). + BYTE m_Region[3]; // Which of 256 regions do each of the coordinate belong? + int m_afNodeInfo;// bits that tell us more about this location + + int m_cNumLinks; // how many links this node has + int m_iFirstLink;// index of this node's first link in the link pool. + + // Where to start looking in the compressed routing table (offset into m_pRouteInfo). + // (4 hull sizes -- smallest to largest + fly/swim), and secondly, door capability. + // + int m_pNextBestNode[MAX_NODE_HULLS][2]; + + // Used in finding the shortest path. m_fClosestSoFar is -1 if not visited. + // Then it is the distance to the source. If another path uses this node + // and has a closer distance, then m_iPreviousNode is also updated. + // + float m_flClosestSoFar; // Used in finding the shortest path. + int m_iPreviousNode; + + short m_sHintType;// there is something interesting in the world at this node's position + short m_sHintActivity;// there is something interesting in the world at this node's position + float m_flHintYaw;// monster on this node should face this yaw to face the hint. +}; + +//========================================================= +// CLink - A link between 2 nodes +//========================================================= +#define bits_LINK_SMALL_HULL ( 1 << 0 )// headcrab box can fit through this connection +#define bits_LINK_HUMAN_HULL ( 1 << 1 )// player box can fit through this connection +#define bits_LINK_LARGE_HULL ( 1 << 2 )// big box can fit through this connection +#define bits_LINK_FLY_HULL ( 1 << 3 )// a flying big box can fit through this connection +#define bits_LINK_DISABLED ( 1 << 4 )// link is not valid when the set + +#define NODE_SMALL_HULL 0 +#define NODE_HUMAN_HULL 1 +#define NODE_LARGE_HULL 2 +#define NODE_FLY_HULL 3 + +class CLink +{ +public: + int m_iSrcNode;// the node that 'owns' this link ( keeps us from having to make reverse lookups ) + int m_iDestNode;// the node on the other end of the link. + + entvars_t *m_pLinkEnt;// the entity that blocks this connection (doors, etc) + + // m_szLinkEntModelname is not necessarily NULL terminated (so we can store it in a more alignment-friendly 4 bytes) + char m_szLinkEntModelname[ 4 ];// the unique name of the brush model that blocks the connection (this is kept for save/restore) + + int m_afLinkInfo;// information about this link + float m_flWeight;// length of the link line segment +}; + + +typedef struct +{ + int m_SortedBy[3]; + int m_CheckedEvent; +} DIST_INFO; + +typedef struct +{ + Vector v; + short n; // Nearest node or -1 if no node found. +} CACHE_ENTRY; + +//========================================================= +// CGraph +//========================================================= +#define GRAPH_VERSION (int)16// !!!increment this whever graph/node/link classes change, to obsolesce older disk files. +class CGraph +{ +public: + +// the graph has two flags, and should not be accessed unless both flags are TRUE! + BOOL m_fGraphPresent;// is the graph in memory? + BOOL m_fGraphPointersSet;// are the entity pointers for the graph all set? + BOOL m_fRoutingComplete; // are the optimal routes computed, yet? + + CNode *m_pNodes;// pointer to the memory block that contains all node info + CLink *m_pLinkPool;// big list of all node connections + char *m_pRouteInfo; // compressed routing information the nodes use. + + int m_cNodes;// total number of nodes + int m_cLinks;// total number of links + int m_nRouteInfo; // size of m_pRouteInfo in bytes. + + // Tables for making nearest node lookup faster. SortedBy provided nodes in a + // order of a particular coordinate. Instead of doing a binary search, RangeStart + // and RangeEnd let you get to the part of SortedBy that you are interested in. + // + // Once you have a point of interest, the only way you'll find a closer point is + // if at least one of the coordinates is closer than the ones you have now. So we + // search each range. After the search is exhausted, we know we have the closest + // node. + // +#define CACHE_SIZE 128 +#define NUM_RANGES 256 + DIST_INFO *m_di; // This is m_cNodes long, but the entries don't correspond to CNode entries. + int m_RangeStart[3][NUM_RANGES]; + int m_RangeEnd[3][NUM_RANGES]; + float m_flShortest; + int m_iNearest; + int m_minX, m_minY, m_minZ, m_maxX, m_maxY, m_maxZ; + int m_minBoxX, m_minBoxY, m_minBoxZ, m_maxBoxX, m_maxBoxY, m_maxBoxZ; + int m_CheckedCounter; + float m_RegionMin[3], m_RegionMax[3]; // The range of nodes. + CACHE_ENTRY m_Cache[CACHE_SIZE]; + + + int m_HashPrimes[16]; + short *m_pHashLinks; + int m_nHashLinks; + + + // kinda sleazy. In order to allow variety in active idles for monster groups in a room with more than one node, + // we keep track of the last node we searched from and store it here. Subsequent searches by other monsters will pick + // up where the last search stopped. + int m_iLastActiveIdleSearch; + + // another such system used to track the search for cover nodes, helps greatly with two monsters trying to get to the same node. + int m_iLastCoverSearch; + + // functions to create the graph + int LinkVisibleNodes ( CLink *pLinkPool, FILE *file, int *piBadNode ); + int RejectInlineLinks ( CLink *pLinkPool, FILE *file ); + int FindShortestPath ( int *piPath, int iStart, int iDest, int iHull, int afCapMask); + int FindNearestNode ( const Vector &vecOrigin, CBaseEntity *pEntity ); + int FindNearestNode ( const Vector &vecOrigin, int afNodeTypes ); + //int FindNearestLink ( const Vector &vecTestPoint, int *piNearestLink, BOOL *pfAlongLine ); + float PathLength( int iStart, int iDest, int iHull, int afCapMask ); + int NextNodeInRoute( int iCurrentNode, int iDest, int iHull, int iCap ); + + enum NODEQUERY { NODEGRAPH_DYNAMIC, NODEGRAPH_STATIC }; + // A static query means we're asking about the possiblity of handling this entity at ANY time + // A dynamic query means we're asking about it RIGHT NOW. So we should query the current state + int HandleLinkEnt ( int iNode, entvars_t *pevLinkEnt, int afCapMask, NODEQUERY queryType ); + entvars_t* LinkEntForLink ( CLink *pLink, CNode *pNode ); + void ShowNodeConnections ( int iNode ); + void InitGraph( void ); + int AllocNodes ( void ); + + int CheckNODFile(char *szMapName); + int FLoadGraph(char *szMapName); + int FSaveGraph(char *szMapName); + int FSetGraphPointers(void); + void CheckNode(Vector vecOrigin, int iNode); + + void BuildRegionTables(void); + void ComputeStaticRoutingTables(void); + void TestRoutingTables(void); + + void HashInsert(int iSrcNode, int iDestNode, int iKey); + void HashSearch(int iSrcNode, int iDestNode, int &iKey); + void HashChoosePrimes(int TableSize); + void BuildLinkLookups(void); + + void SortNodes(void); + + int HullIndex( const CBaseEntity *pEntity ); // what hull the monster uses + int NodeType( const CBaseEntity *pEntity ); // what node type the monster uses + inline int CapIndex( int afCapMask ) + { + if (afCapMask & (bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE)) + return 1; + return 0; + } + + + inline CNode &Node( int i ) + { +#ifdef _DEBUG + if ( !m_pNodes || i < 0 || i > m_cNodes ) + ALERT( at_error, "Bad Node!\n" ); +#endif + return m_pNodes[i]; + } + + inline CLink &Link( int i ) + { +#ifdef _DEBUG + if ( !m_pLinkPool || i < 0 || i > m_cLinks ) + ALERT( at_error, "Bad link!\n" ); +#endif + return m_pLinkPool[i]; + } + + inline CLink &NodeLink( int iNode, int iLink ) + { + return Link( Node( iNode ).m_iFirstLink + iLink ); + } + + inline CLink &NodeLink( const CNode &node, int iLink ) + { + return Link( node.m_iFirstLink + iLink ); + } + + inline int INodeLink ( int iNode, int iLink ) + { + return NodeLink( iNode, iLink ).m_iDestNode; + } + +#if 0 + inline CNode &SourceNode( int iNode, int iLink ) + { + return Node( NodeLink( iNode, iLink ).m_iSrcNode ); + } + + inline CNode &DestNode( int iNode, int iLink ) + { + return Node( NodeLink( iNode, iLink ).m_iDestNode ); + } + + inline CNode *PNodeLink ( int iNode, int iLink ) + { + return &DestNode( iNode, iLink ); + } +#endif +}; + +//========================================================= +// Nodes start out as ents in the level. The node graph +// is built, then these ents are discarded. +//========================================================= +class CNodeEnt : public CBaseEntity +{ + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + short m_sHintType; + short m_sHintActivity; +}; + + +//========================================================= +// CStack - last in, first out. +//========================================================= +class CStack +{ +public: + CStack( void ); + void Push( int value ); + int Pop( void ); + int Top( void ); + int Empty( void ) { return m_level==0; } + int Size( void ) { return m_level; } + void CopyToArray ( int *piArray ); + +private: + int m_stack[ MAX_STACK_NODES ]; + int m_level; +}; + + +//========================================================= +// CQueue - first in, first out. +//========================================================= +class CQueue +{ +public: + + CQueue( void );// constructor + inline int Full ( void ) { return ( m_cSize == MAX_STACK_NODES ); } + inline int Empty ( void ) { return ( m_cSize == 0 ); } + //inline int Tail ( void ) { return ( m_queue[ m_tail ] ); } + inline int Size ( void ) { return ( m_cSize ); } + void Insert( int, float ); + int Remove( float & ); + +private: + int m_cSize; + struct tag_QUEUE_NODE + { + int Id; + float Priority; + } m_queue[ MAX_STACK_NODES ]; + int m_head; + int m_tail; +}; + +//========================================================= +// CQueuePriority - Priority queue (smallest item out first). +// +//========================================================= +class CQueuePriority +{ +public: + + CQueuePriority( void );// constructor + inline int Full ( void ) { return ( m_cSize == MAX_STACK_NODES ); } + inline int Empty ( void ) { return ( m_cSize == 0 ); } + //inline int Tail ( float & ) { return ( m_queue[ m_tail ].Id ); } + inline int Size ( void ) { return ( m_cSize ); } + void Insert( int, float ); + int Remove( float &); + +private: + int m_cSize; + struct tag_HEAP_NODE + { + int Id; + float Priority; + } m_heap[ MAX_STACK_NODES ]; + void Heap_SiftDown(int); + void Heap_SiftUp(void); + +}; + +//========================================================= +// hints - these MUST coincide with the HINTS listed under +// info_node in the FGD file! +//========================================================= +enum +{ + HINT_NONE = 0, + HINT_WORLD_DOOR, + HINT_WORLD_WINDOW, + HINT_WORLD_BUTTON, + HINT_WORLD_MACHINERY, + HINT_WORLD_LEDGE, + HINT_WORLD_LIGHT_SOURCE, + HINT_WORLD_HEAT_SOURCE, + HINT_WORLD_BLINKING_LIGHT, + HINT_WORLD_BRIGHT_COLORS, + HINT_WORLD_HUMAN_BLOOD, + HINT_WORLD_ALIEN_BLOOD, + + HINT_TACTICAL_EXIT = 100, + HINT_TACTICAL_VANTAGE, + HINT_TACTICAL_AMBUSH, + + HINT_STUKA_PERCH = 300, + HINT_STUKA_LANDING, +}; + +extern CGraph WorldGraph; diff --git a/dlls/observer.cpp b/dlls/observer.cpp new file mode 100644 index 0000000..0ca466d --- /dev/null +++ b/dlls/observer.cpp @@ -0,0 +1,280 @@ +//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: Functionality for the observer chase camera +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "pm_shared.h" + +extern int gmsgCurWeapon; +extern int gmsgSetFOV; +// Find the next client in the game for this player to spectate +void CBasePlayer::Observer_FindNextPlayer( bool bReverse ) +{ + // MOD AUTHORS: Modify the logic of this function if you want to restrict the observer to watching + // only a subset of the players. e.g. Make it check the target's team. + + int iStart; + if ( m_hObserverTarget ) + iStart = ENTINDEX( m_hObserverTarget->edict() ); + else + iStart = ENTINDEX( edict() ); + int iCurrent = iStart; + m_hObserverTarget = NULL; + int iDir = bReverse ? -1 : 1; + + do + { + iCurrent += iDir; + + // Loop through the clients + if (iCurrent > gpGlobals->maxClients) + iCurrent = 1; + if (iCurrent < 1) + iCurrent = gpGlobals->maxClients; + + CBaseEntity *pEnt = UTIL_PlayerByIndex( iCurrent ); + if ( !pEnt ) + continue; + if ( pEnt == this ) + continue; + // Don't spec observers or players who haven't picked a class yet + if ( ((CBasePlayer*)pEnt)->IsObserver() || (pEnt->pev->effects & EF_NODRAW) ) + continue; + + // MOD AUTHORS: Add checks on target here. + + m_hObserverTarget = pEnt; + break; + + } while ( iCurrent != iStart ); + + // Did we find a target? + if ( m_hObserverTarget ) + { + // Move to the target + UTIL_SetOrigin( pev, m_hObserverTarget->pev->origin ); + + // ALERT( at_console, "Now Tracking %s\n", STRING( m_hObserverTarget->pev->netname ) ); + + // Store the target in pev so the physics DLL can get to it + if (pev->iuser1 != OBS_ROAMING) + pev->iuser2 = ENTINDEX( m_hObserverTarget->edict() ); + + + + } +} + +// Handle buttons in observer mode +void CBasePlayer::Observer_HandleButtons() +{ + // Slow down mouse clicks + if ( m_flNextObserverInput > gpGlobals->time ) + return; + + // Jump changes from modes: Chase to Roaming + if ( m_afButtonPressed & IN_JUMP ) + { + if ( pev->iuser1 == OBS_CHASE_LOCKED ) + Observer_SetMode( OBS_CHASE_FREE ); + + else if ( pev->iuser1 == OBS_CHASE_FREE ) + Observer_SetMode( OBS_IN_EYE ); + + else if ( pev->iuser1 == OBS_IN_EYE ) + Observer_SetMode( OBS_ROAMING ); + + else if ( pev->iuser1 == OBS_ROAMING ) + Observer_SetMode( OBS_MAP_FREE ); + + else if ( pev->iuser1 == OBS_MAP_FREE ) + Observer_SetMode( OBS_MAP_CHASE ); + + else + Observer_SetMode( OBS_CHASE_FREE ); // don't use OBS_CHASE_LOCKED anymore + + m_flNextObserverInput = gpGlobals->time + 0.2; + } + + // Attack moves to the next player + if ( m_afButtonPressed & IN_ATTACK )//&& pev->iuser1 != OBS_ROAMING ) + { + Observer_FindNextPlayer( false ); + + m_flNextObserverInput = gpGlobals->time + 0.2; + } + + // Attack2 moves to the prev player + if ( m_afButtonPressed & IN_ATTACK2)// && pev->iuser1 != OBS_ROAMING ) + { + Observer_FindNextPlayer( true ); + + m_flNextObserverInput = gpGlobals->time + 0.2; + } +} + +void CBasePlayer::Observer_CheckTarget() +{ + if( pev->iuser1 == OBS_ROAMING ) + return; + + // try to find a traget if we have no current one + if ( m_hObserverTarget == NULL) + { + Observer_FindNextPlayer( false ); + + if (m_hObserverTarget == NULL) + { + // no target found at all + + int lastMode = pev->iuser1; + + Observer_SetMode( OBS_ROAMING ); + + m_iObserverLastMode = lastMode; // don't overwrite users lastmode + + return; // we still have np target return + } + } + + CBasePlayer* target = (CBasePlayer*)(UTIL_PlayerByIndex( ENTINDEX(m_hObserverTarget->edict()))); + + if ( !target ) + { + Observer_FindNextPlayer( false ); + return; + } + + // check taget + if (target->pev->deadflag == DEAD_DEAD) + { + if ( (target->m_fDeadTime + 2.0f ) < gpGlobals->time ) + { + // 3 secs after death change target + Observer_FindNextPlayer( false ); + return; + } + } +} + +void CBasePlayer::Observer_CheckProperties() +{ + // try to find a traget if we have no current one + if ( pev->iuser1 == OBS_IN_EYE && m_hObserverTarget != NULL) + { + CBasePlayer* target = (CBasePlayer*)(UTIL_PlayerByIndex( ENTINDEX(m_hObserverTarget->edict()))); + + if (!target ) + return; + + int weapon = (target->m_pActiveItem!=NULL)?target->m_pActiveItem->m_iId:0; + // use fov of tracked client + if ( m_iFOV != target->m_iFOV || m_iObserverWeapon != weapon ) + { + m_iFOV = target->m_iFOV; + m_iClientFOV = m_iFOV; + // write fov before wepon data, so zoomed crosshair is set correctly + MESSAGE_BEGIN( MSG_ONE, gmsgSetFOV, NULL, pev ); + WRITE_BYTE( m_iFOV ); + MESSAGE_END(); + + + m_iObserverWeapon = weapon; + //send weapon update + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pev ); + WRITE_BYTE( 1 ); // 1 = current weapon, not on target + WRITE_BYTE( m_iObserverWeapon ); + WRITE_BYTE( 0 ); // clip + MESSAGE_END(); + } + } + else + { + m_iFOV = 90; + + if ( m_iObserverWeapon != 0 ) + { + m_iObserverWeapon = 0; + + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pev ); + WRITE_BYTE( 1 ); // 1 = current weapon + WRITE_BYTE( m_iObserverWeapon ); + WRITE_BYTE( 0 ); // clip + MESSAGE_END(); + } + } +} + +// Attempt to change the observer mode +void CBasePlayer::Observer_SetMode( int iMode ) +{ + + // Just abort if we're changing to the mode we're already in + if ( iMode == pev->iuser1 ) + return; + + // is valid mode ? + if ( iMode < OBS_CHASE_LOCKED || iMode > OBS_MAP_CHASE ) + iMode = OBS_IN_EYE; // now it is + // verify observer target again + if ( m_hObserverTarget != NULL) + { + CBaseEntity *pEnt = m_hObserverTarget; + + if ( (pEnt == this) || (pEnt == NULL) ) + m_hObserverTarget = NULL; + else if ( ((CBasePlayer*)pEnt)->IsObserver() || (pEnt->pev->effects & EF_NODRAW) ) + m_hObserverTarget = NULL; + } + + // set spectator mode + pev->iuser1 = iMode; + + // if we are not roaming, we need a valid target to track + if ( (iMode != OBS_ROAMING) && (m_hObserverTarget == NULL) ) + { + Observer_FindNextPlayer( false ); + + // if we didn't find a valid target switch to roaming + if (m_hObserverTarget == NULL) + { + ClientPrint( pev, HUD_PRINTCENTER, "#Spec_NoTarget" ); + pev->iuser1 = OBS_ROAMING; + } + } + + // set target if not roaming + if (pev->iuser1 == OBS_ROAMING) + { + pev->iuser2 = 0; + } + else + pev->iuser2 = ENTINDEX( m_hObserverTarget->edict() ); + + pev->iuser3 = 0; // clear second target from death cam + + // print spepctaor mode on client screen + + char modemsg[16]; + sprintf(modemsg,"#Spec_Mode%i", pev->iuser1 ); + ClientPrint( pev, HUD_PRINTCENTER, modemsg ); + + m_iObserverLastMode = iMode; +} diff --git a/dlls/osprey.cpp b/dlls/osprey.cpp new file mode 100644 index 0000000..8694203 --- /dev/null +++ b/dlls/osprey.cpp @@ -0,0 +1,805 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "soundent.h" +#include "effects.h" +#include "customentity.h" + +typedef struct +{ + int isValid; + EHANDLE hGrunt; + Vector vecOrigin; + Vector vecAngles; +} t_ospreygrunt; + + + +#define SF_WAITFORTRIGGER 0x40 + + +#define MAX_CARRY 24 + +class COsprey : public CBaseMonster +{ +public: + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + int ObjectCaps( void ) { return CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + void Spawn( void ); + void Precache( void ); + int Classify( void ) { return CLASS_MACHINE; }; + int BloodColor( void ) { return DONT_BLEED; } + void Killed( entvars_t *pevAttacker, int iGib ); + + void UpdateGoal( void ); + BOOL HasDead( void ); + void EXPORT FlyThink( void ); + void EXPORT DeployThink( void ); + void Flight( void ); + void EXPORT HitTouch( CBaseEntity *pOther ); + void EXPORT FindAllThink( void ); + void EXPORT HoverThink( void ); + CBaseMonster *MakeGrunt( Vector vecSrc ); + void EXPORT CrashTouch( CBaseEntity *pOther ); + void EXPORT DyingThink( void ); + void EXPORT CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + // int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + void ShowDamage( void ); + + CBaseEntity *m_pGoalEnt; + Vector m_vel1; + Vector m_vel2; + Vector m_pos1; + Vector m_pos2; + Vector m_ang1; + Vector m_ang2; + float m_startTime; + float m_dTime; + + Vector m_velocity; + + float m_flIdealtilt; + float m_flRotortilt; + + float m_flRightHealth; + float m_flLeftHealth; + + int m_iUnits; + EHANDLE m_hGrunt[MAX_CARRY]; + Vector m_vecOrigin[MAX_CARRY]; + EHANDLE m_hRepel[4]; + + int m_iSoundState; + int m_iSpriteTexture; + + int m_iPitch; + + int m_iExplode; + int m_iTailGibs; + int m_iBodyGibs; + int m_iEngineGibs; + + int m_iDoLeftSmokePuff; + int m_iDoRightSmokePuff; +}; + +LINK_ENTITY_TO_CLASS( monster_osprey, COsprey ); + +TYPEDESCRIPTION COsprey::m_SaveData[] = +{ + DEFINE_FIELD( COsprey, m_pGoalEnt, FIELD_CLASSPTR ), + DEFINE_FIELD( COsprey, m_vel1, FIELD_VECTOR ), + DEFINE_FIELD( COsprey, m_vel2, FIELD_VECTOR ), + DEFINE_FIELD( COsprey, m_pos1, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( COsprey, m_pos2, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( COsprey, m_ang1, FIELD_VECTOR ), + DEFINE_FIELD( COsprey, m_ang2, FIELD_VECTOR ), + + DEFINE_FIELD( COsprey, m_startTime, FIELD_TIME ), + DEFINE_FIELD( COsprey, m_dTime, FIELD_FLOAT ), + DEFINE_FIELD( COsprey, m_velocity, FIELD_VECTOR ), + + DEFINE_FIELD( COsprey, m_flIdealtilt, FIELD_FLOAT ), + DEFINE_FIELD( COsprey, m_flRotortilt, FIELD_FLOAT ), + + DEFINE_FIELD( COsprey, m_flRightHealth, FIELD_FLOAT ), + DEFINE_FIELD( COsprey, m_flLeftHealth, FIELD_FLOAT ), + + DEFINE_FIELD( COsprey, m_iUnits, FIELD_INTEGER ), + DEFINE_ARRAY( COsprey, m_hGrunt, FIELD_EHANDLE, MAX_CARRY ), + DEFINE_ARRAY( COsprey, m_vecOrigin, FIELD_POSITION_VECTOR, MAX_CARRY ), + DEFINE_ARRAY( COsprey, m_hRepel, FIELD_EHANDLE, 4 ), + + // DEFINE_FIELD( COsprey, m_iSoundState, FIELD_INTEGER ), + // DEFINE_FIELD( COsprey, m_iSpriteTexture, FIELD_INTEGER ), + // DEFINE_FIELD( COsprey, m_iPitch, FIELD_INTEGER ), + + DEFINE_FIELD( COsprey, m_iDoLeftSmokePuff, FIELD_INTEGER ), + DEFINE_FIELD( COsprey, m_iDoRightSmokePuff, FIELD_INTEGER ), +}; +IMPLEMENT_SAVERESTORE( COsprey, CBaseMonster ); + + +void COsprey :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/osprey.mdl"); + UTIL_SetSize(pev, Vector( -400, -400, -100), Vector(400, 400, 32)); + UTIL_SetOrigin( pev, pev->origin ); + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_YES; + m_flRightHealth = 200; + m_flLeftHealth = 200; + pev->health = 400; + + m_flFieldOfView = 0; // 180 degrees + + pev->sequence = 0; + ResetSequenceInfo( ); + pev->frame = RANDOM_LONG(0,0xFF); + + InitBoneControllers(); + + SetThink( &COsprey::FindAllThink ); + SetUse( &COsprey::CommandUse ); + + if (!(pev->spawnflags & SF_WAITFORTRIGGER)) + { + pev->nextthink = gpGlobals->time + 1.0; + } + + m_pos2 = pev->origin; + m_ang2 = pev->angles; + m_vel2 = pev->velocity; +} + + +void COsprey::Precache( void ) +{ + UTIL_PrecacheOther( "monster_human_grunt" ); + + PRECACHE_MODEL("models/osprey.mdl"); + PRECACHE_MODEL("models/HVR.mdl"); + + PRECACHE_SOUND("apache/ap_rotor4.wav"); + PRECACHE_SOUND("weapons/mortarhit.wav"); + + m_iSpriteTexture = PRECACHE_MODEL( "sprites/rope.spr" ); + + m_iExplode = PRECACHE_MODEL( "sprites/fexplo.spr" ); + m_iTailGibs = PRECACHE_MODEL( "models/osprey_tailgibs.mdl" ); + m_iBodyGibs = PRECACHE_MODEL( "models/osprey_bodygibs.mdl" ); + m_iEngineGibs = PRECACHE_MODEL( "models/osprey_enginegibs.mdl" ); +} + +void COsprey::CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + pev->nextthink = gpGlobals->time + 0.1; +} + +void COsprey :: FindAllThink( void ) +{ + CBaseEntity *pEntity = NULL; + + m_iUnits = 0; + while (m_iUnits < MAX_CARRY && (pEntity = UTIL_FindEntityByClassname( pEntity, "monster_human_grunt" )) != NULL) + { + if (pEntity->IsAlive()) + { + m_hGrunt[m_iUnits] = pEntity; + m_vecOrigin[m_iUnits] = pEntity->pev->origin; + m_iUnits++; + } + } + + if (m_iUnits == 0) + { + ALERT( at_console, "osprey error: no grunts to resupply\n"); + UTIL_Remove( this ); + return; + } + SetThink( &COsprey::FlyThink ); + pev->nextthink = gpGlobals->time + 0.1; + m_startTime = gpGlobals->time; +} + + +void COsprey :: DeployThink( void ) +{ + UTIL_MakeAimVectors( pev->angles ); + + Vector vecForward = gpGlobals->v_forward; + Vector vecRight = gpGlobals->v_right; + Vector vecUp = gpGlobals->v_up; + + Vector vecSrc; + + TraceResult tr; + UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, -4096.0), ignore_monsters, ENT(pev), &tr); + CSoundEnt::InsertSound ( bits_SOUND_DANGER, tr.vecEndPos, 400, 0.3 ); + + vecSrc = pev->origin + vecForward * 32 + vecRight * 100 + vecUp * -96; + m_hRepel[0] = MakeGrunt( vecSrc ); + + vecSrc = pev->origin + vecForward * -64 + vecRight * 100 + vecUp * -96; + m_hRepel[1] = MakeGrunt( vecSrc ); + + vecSrc = pev->origin + vecForward * 32 + vecRight * -100 + vecUp * -96; + m_hRepel[2] = MakeGrunt( vecSrc ); + + vecSrc = pev->origin + vecForward * -64 + vecRight * -100 + vecUp * -96; + m_hRepel[3] = MakeGrunt( vecSrc ); + + SetThink( &COsprey::HoverThink ); + pev->nextthink = gpGlobals->time + 0.1; +} + + + +BOOL COsprey :: HasDead( ) +{ + for (int i = 0; i < m_iUnits; i++) + { + if (m_hGrunt[i] == NULL || !m_hGrunt[i]->IsAlive()) + { + return TRUE; + } + else + { + m_vecOrigin[i] = m_hGrunt[i]->pev->origin; // send them to where they died + } + } + return FALSE; +} + + +CBaseMonster *COsprey :: MakeGrunt( Vector vecSrc ) +{ + CBaseEntity *pEntity; + CBaseMonster *pGrunt; + + TraceResult tr; + UTIL_TraceLine( vecSrc, vecSrc + Vector( 0, 0, -4096.0), dont_ignore_monsters, ENT(pev), &tr); + if ( tr.pHit && Instance( tr.pHit )->pev->solid != SOLID_BSP) + return NULL; + + for (int i = 0; i < m_iUnits; i++) + { + if (m_hGrunt[i] == NULL || !m_hGrunt[i]->IsAlive()) + { + if (m_hGrunt[i] != NULL && m_hGrunt[i]->pev->rendermode == kRenderNormal) + { + m_hGrunt[i]->SUB_StartFadeOut( ); + } + pEntity = Create( "monster_human_grunt", vecSrc, pev->angles ); + pGrunt = pEntity->MyMonsterPointer( ); + pGrunt->pev->movetype = MOVETYPE_FLY; + pGrunt->pev->velocity = Vector( 0, 0, RANDOM_FLOAT( -196, -128 ) ); + pGrunt->SetActivity( ACT_GLIDE ); + + CBeam *pBeam = CBeam::BeamCreate( "sprites/rope.spr", 10 ); + pBeam->PointEntInit( vecSrc + Vector(0,0,112), pGrunt->entindex() ); + pBeam->SetFlags( BEAM_FSOLID ); + pBeam->SetColor( 255, 255, 255 ); + pBeam->SetThink( &CBeam::SUB_Remove ); + pBeam->pev->nextthink = gpGlobals->time + -4096.0 * tr.flFraction / pGrunt->pev->velocity.z + 0.5; + + // ALERT( at_console, "%d at %.0f %.0f %.0f\n", i, m_vecOrigin[i].x, m_vecOrigin[i].y, m_vecOrigin[i].z ); + pGrunt->m_vecLastPosition = m_vecOrigin[i]; + m_hGrunt[i] = pGrunt; + return pGrunt; + } + } + // ALERT( at_console, "none dead\n"); + return NULL; +} + + +void COsprey :: HoverThink( void ) +{ + int i; + for (i = 0; i < 4; i++) + { + if (m_hRepel[i] != NULL && m_hRepel[i]->pev->health > 0 && !(m_hRepel[i]->pev->flags & FL_ONGROUND)) + { + break; + } + } + + if (i == 4) + { + m_startTime = gpGlobals->time; + SetThink( &COsprey::FlyThink ); + } + + pev->nextthink = gpGlobals->time + 0.1; + UTIL_MakeAimVectors( pev->angles ); + ShowDamage( ); +} + + +void COsprey::UpdateGoal( ) +{ + if (m_pGoalEnt) + { + m_pos1 = m_pos2; + m_ang1 = m_ang2; + m_vel1 = m_vel2; + m_pos2 = m_pGoalEnt->pev->origin; + m_ang2 = m_pGoalEnt->pev->angles; + UTIL_MakeAimVectors( Vector( 0, m_ang2.y, 0 ) ); + m_vel2 = gpGlobals->v_forward * m_pGoalEnt->pev->speed; + + m_startTime = m_startTime + m_dTime; + m_dTime = 2.0 * (m_pos1 - m_pos2).Length() / (m_vel1.Length() + m_pGoalEnt->pev->speed); + + if (m_ang1.y - m_ang2.y < -180) + { + m_ang1.y += 360; + } + else if (m_ang1.y - m_ang2.y > 180) + { + m_ang1.y -= 360; + } + + if (m_pGoalEnt->pev->speed < 400) + m_flIdealtilt = 0; + else + m_flIdealtilt = -90; + } + else + { + ALERT( at_console, "osprey missing target"); + } +} + + +void COsprey::FlyThink( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + if ( m_pGoalEnt == NULL && !FStringNull(pev->target) )// this monster has a target + { + m_pGoalEnt = CBaseEntity::Instance( FIND_ENTITY_BY_TARGETNAME ( NULL, STRING( pev->target ) ) ); + UpdateGoal( ); + } + + if (gpGlobals->time > m_startTime + m_dTime) + { + if (m_pGoalEnt->pev->speed == 0) + { + SetThink( &COsprey::DeployThink ); + } + do { + m_pGoalEnt = CBaseEntity::Instance( FIND_ENTITY_BY_TARGETNAME ( NULL, STRING( m_pGoalEnt->pev->target ) ) ); + } while (m_pGoalEnt->pev->speed < 400 && !HasDead()); + UpdateGoal( ); + } + + Flight( ); + ShowDamage( ); +} + + +void COsprey::Flight( ) +{ + float t = (gpGlobals->time - m_startTime); + float scale = 1.0 / m_dTime; + + float f = UTIL_SplineFraction( t * scale, 1.0 ); + + Vector pos = (m_pos1 + m_vel1 * t) * (1.0 - f) + (m_pos2 - m_vel2 * (m_dTime - t)) * f; + Vector ang = (m_ang1) * (1.0 - f) + (m_ang2) * f; + m_velocity = m_vel1 * (1.0 - f) + m_vel2 * f; + + UTIL_SetOrigin( pev, pos ); + pev->angles = ang; + UTIL_MakeAimVectors( pev->angles ); + float flSpeed = DotProduct( gpGlobals->v_forward, m_velocity ); + + // float flSpeed = DotProduct( gpGlobals->v_forward, pev->velocity ); + + float m_flIdealtilt = (160 - flSpeed) / 10.0; + + // ALERT( at_console, "%f %f\n", flSpeed, flIdealtilt ); + if (m_flRotortilt < m_flIdealtilt) + { + m_flRotortilt += 0.5; + if (m_flRotortilt > 0) + m_flRotortilt = 0; + } + if (m_flRotortilt > m_flIdealtilt) + { + m_flRotortilt -= 0.5; + if (m_flRotortilt < -90) + m_flRotortilt = -90; + } + SetBoneController( 0, m_flRotortilt ); + + + if (m_iSoundState == 0) + { + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_rotor4.wav", 1.0, 0.15, 0, 110 ); + // EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_whine1.wav", 0.5, 0.2, 0, 110 ); + + m_iSoundState = SND_CHANGE_PITCH; // hack for going through level transitions + } + else + { + CBaseEntity *pPlayer = NULL; + + pPlayer = UTIL_FindEntityByClassname( NULL, "player" ); + // UNDONE: this needs to send different sounds to every player for multiplayer. + if (pPlayer) + { + float pitch = DotProduct( m_velocity - pPlayer->pev->velocity, (pPlayer->pev->origin - pev->origin).Normalize() ); + + pitch = (int)(100 + pitch / 75.0); + + if (pitch > 250) + pitch = 250; + if (pitch < 50) + pitch = 50; + + if (pitch == 100) + pitch = 101; + + if (pitch != m_iPitch) + { + m_iPitch = pitch; + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_rotor4.wav", 1.0, 0.15, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch); + // ALERT( at_console, "%.0f\n", pitch ); + } + } + // EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_whine1.wav", flVol, 0.2, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch); + } + +} + + +void COsprey::HitTouch( CBaseEntity *pOther ) +{ + pev->nextthink = gpGlobals->time + 2.0; +} + + +/* +int COsprey::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if (m_flRotortilt <= -90) + { + m_flRotortilt = 0; + } + else + { + m_flRotortilt -= 45; + } + SetBoneController( 0, m_flRotortilt ); + return 0; +} +*/ + + + +void COsprey :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->gravity = 0.3; + pev->velocity = m_velocity; + pev->avelocity = Vector( RANDOM_FLOAT( -20, 20 ), 0, RANDOM_FLOAT( -50, 50 ) ); + STOP_SOUND( ENT(pev), CHAN_STATIC, "apache/ap_rotor4.wav" ); + + UTIL_SetSize( pev, Vector( -32, -32, -64), Vector( 32, 32, 0) ); + SetThink( &COsprey::DyingThink ); + SetTouch( &COsprey::CrashTouch ); + pev->nextthink = gpGlobals->time + 0.1; + pev->health = 0; + pev->takedamage = DAMAGE_NO; + + m_startTime = gpGlobals->time + 4.0; +} + +void COsprey::CrashTouch( CBaseEntity *pOther ) +{ + // only crash if we hit something solid + if ( pOther->pev->solid == SOLID_BSP) + { + SetTouch( NULL ); + m_startTime = gpGlobals->time; + pev->nextthink = gpGlobals->time; + m_velocity = pev->velocity; + } +} + + +void COsprey :: DyingThink( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + pev->avelocity = pev->avelocity * 1.02; + + // still falling? + if (m_startTime > gpGlobals->time ) + { + UTIL_MakeAimVectors( pev->angles ); + ShowDamage( ); + + Vector vecSpot = pev->origin + pev->velocity * 0.2; + + // random explosions + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_EXPLOSION); // This just makes a dynamic light now + WRITE_COORD( vecSpot.x + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( vecSpot.y + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( vecSpot.z + RANDOM_FLOAT( -150, -50 )); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( RANDOM_LONG(0,29) + 30 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + // lots of smoke + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSpot.x + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( vecSpot.y + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( vecSpot.z + RANDOM_FLOAT( -150, -50 )); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 100 ); // scale * 10 + WRITE_BYTE( 10 ); // framerate + MESSAGE_END(); + + + vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z ); + + // size + WRITE_COORD( 800 ); + WRITE_COORD( 800 ); + WRITE_COORD( 132 ); + + // velocity + WRITE_COORD( pev->velocity.x ); + WRITE_COORD( pev->velocity.y ); + WRITE_COORD( pev->velocity.z ); + + // randomization + WRITE_BYTE( 50 ); + + // Model + WRITE_SHORT( m_iTailGibs ); //model id# + + // # of shards + WRITE_BYTE( 8 ); // let client decide + + // duration + WRITE_BYTE( 200 );// 10.0 seconds + + // flags + + WRITE_BYTE( BREAK_METAL ); + MESSAGE_END(); + + + + // don't stop it we touch a entity + pev->flags &= ~FL_ONGROUND; + pev->nextthink = gpGlobals->time + 0.2; + return; + } + else + { + Vector vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + + /* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_EXPLOSION); // This just makes a dynamic light now + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 512 ); + WRITE_SHORT( m_iExplode ); + WRITE_BYTE( 250 ); // scale * 10 + WRITE_BYTE( 10 ); // framerate + MESSAGE_END(); + */ + + // gibs + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_SPRITE ); + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 512 ); + WRITE_SHORT( m_iExplode ); + WRITE_BYTE( 250 ); // scale * 10 + WRITE_BYTE( 255 ); // brightness + MESSAGE_END(); + + /* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 300 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 250 ); // scale * 10 + WRITE_BYTE( 6 ); // framerate + MESSAGE_END(); + */ + + // blast circle + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_BEAMCYLINDER ); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 2000 ); // reach damage radius over .2 seconds + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 4 ); // life + WRITE_BYTE( 32 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 192 ); // r, g, b + WRITE_BYTE( 128 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + EMIT_SOUND(ENT(pev), CHAN_STATIC, "weapons/mortarhit.wav", 1.0, 0.3); + + RadiusDamage( pev->origin, pev, pev, 300, CLASS_NONE, DMG_BLAST ); + + // gibs + vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 64); + + // size + WRITE_COORD( 800 ); + WRITE_COORD( 800 ); + WRITE_COORD( 128 ); + + // velocity + WRITE_COORD( m_velocity.x ); + WRITE_COORD( m_velocity.y ); + WRITE_COORD( fabs( m_velocity.z ) * 0.25 ); + + // randomization + WRITE_BYTE( 40 ); + + // Model + WRITE_SHORT( m_iBodyGibs ); //model id# + + // # of shards + WRITE_BYTE( 128 ); + + // duration + WRITE_BYTE( 200 );// 10.0 seconds + + // flags + + WRITE_BYTE( BREAK_METAL ); + MESSAGE_END(); + + UTIL_Remove( this ); + } +} + + +void COsprey :: ShowDamage( void ) +{ + if (m_iDoLeftSmokePuff > 0 || RANDOM_LONG(0,99) > m_flLeftHealth) + { + Vector vecSrc = pev->origin + gpGlobals->v_right * -340; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSrc ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSrc.x ); + WRITE_COORD( vecSrc.y ); + WRITE_COORD( vecSrc.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( RANDOM_LONG(0,9) + 20 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + if (m_iDoLeftSmokePuff > 0) + m_iDoLeftSmokePuff--; + } + if (m_iDoRightSmokePuff > 0 || RANDOM_LONG(0,99) > m_flRightHealth) + { + Vector vecSrc = pev->origin + gpGlobals->v_right * 340; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSrc ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSrc.x ); + WRITE_COORD( vecSrc.y ); + WRITE_COORD( vecSrc.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( RANDOM_LONG(0,9) + 20 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + if (m_iDoRightSmokePuff > 0) + m_iDoRightSmokePuff--; + } +} + + +void COsprey::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + // ALERT( at_console, "%d %.0f\n", ptr->iHitgroup, flDamage ); + + // only so much per engine + if (ptr->iHitgroup == 3) + { + if (m_flRightHealth < 0) + return; + else + m_flRightHealth -= flDamage; + m_iDoLeftSmokePuff = 3 + (flDamage / 5.0); + } + + if (ptr->iHitgroup == 2) + { + if (m_flLeftHealth < 0) + return; + else + m_flLeftHealth -= flDamage; + m_iDoRightSmokePuff = 3 + (flDamage / 5.0); + } + + // hit hard, hits cockpit, hits engines + if (flDamage > 50 || ptr->iHitgroup == 1 || ptr->iHitgroup == 2 || ptr->iHitgroup == 3) + { + // ALERT( at_console, "%.0f\n", flDamage ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + } + else + { + UTIL_Sparks( ptr->vecEndPos ); + } +} + + + + + diff --git a/dlls/pathcorner.cpp b/dlls/pathcorner.cpp new file mode 100644 index 0000000..badebd9 --- /dev/null +++ b/dlls/pathcorner.cpp @@ -0,0 +1,428 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ========================== PATH_CORNER =========================== +// + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "trains.h" +#include "saverestore.h" + +class CPathCorner : public CPointEntity +{ +public: + void Spawn( ); + void KeyValue( KeyValueData* pkvd ); + float GetDelay( void ) { return m_flWait; } +// void Touch( CBaseEntity *pOther ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + float m_flWait; +}; + +LINK_ENTITY_TO_CLASS( path_corner, CPathCorner ); + +// Global Savedata for Delay +TYPEDESCRIPTION CPathCorner::m_SaveData[] = +{ + DEFINE_FIELD( CPathCorner, m_flWait, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CPathCorner, CPointEntity ); + +// +// Cache user-entity-field values until spawn is called. +// +void CPathCorner :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CPathCorner :: Spawn( ) +{ + ASSERTSZ(!FStringNull(pev->targetname), "path_corner without a targetname"); +} + +#if 0 +void CPathCorner :: Touch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + + if ( FBitSet ( pevToucher->flags, FL_MONSTER ) ) + {// monsters don't navigate path corners based on touch anymore + return; + } + + // If OTHER isn't explicitly looking for this path_corner, bail out + if ( pOther->m_pGoalEnt != this ) + { + return; + } + + // If OTHER has an enemy, this touch is incidental, ignore + if ( !FNullEnt(pevToucher->enemy) ) + { + return; // fighting, not following a path + } + + // UNDONE: support non-zero flWait + /* + if (m_flWait != 0) + ALERT(at_warning, "Non-zero path-cornder waits NYI"); + */ + + // Find the next "stop" on the path, make it the goal of the "toucher". + if (FStringNull(pev->target)) + { + ALERT(at_warning, "PathCornerTouch: no next stop specified"); + } + + pOther->m_pGoalEnt = CBaseEntity::Instance( FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(pev->target) ) ); + + // If "next spot" was not found (does not exist - level design error) + if ( !pOther->m_pGoalEnt ) + { + ALERT(at_console, "PathCornerTouch--%s couldn't find next stop in path: %s", STRING(pev->classname), STRING(pev->target)); + return; + } + + // Turn towards the next stop in the path. + pevToucher->ideal_yaw = UTIL_VecToYaw ( pOther->m_pGoalEnt->pev->origin - pevToucher->origin ); +} +#endif + + + +TYPEDESCRIPTION CPathTrack::m_SaveData[] = +{ + DEFINE_FIELD( CPathTrack, m_length, FIELD_FLOAT ), + DEFINE_FIELD( CPathTrack, m_pnext, FIELD_CLASSPTR ), + DEFINE_FIELD( CPathTrack, m_paltpath, FIELD_CLASSPTR ), + DEFINE_FIELD( CPathTrack, m_pprevious, FIELD_CLASSPTR ), + DEFINE_FIELD( CPathTrack, m_altName, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CPathTrack, CBaseEntity ); +LINK_ENTITY_TO_CLASS( path_track, CPathTrack ); + +// +// Cache user-entity-field values until spawn is called. +// +void CPathTrack :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "altpath")) + { + m_altName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CPathTrack :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int on; + + // Use toggles between two paths + if ( m_paltpath ) + { + on = !FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ); + if ( ShouldToggle( useType, on ) ) + { + if ( on ) + SetBits( pev->spawnflags, SF_PATH_ALTERNATE ); + else + ClearBits( pev->spawnflags, SF_PATH_ALTERNATE ); + } + } + else // Use toggles between enabled/disabled + { + on = !FBitSet( pev->spawnflags, SF_PATH_DISABLED ); + + if ( ShouldToggle( useType, on ) ) + { + if ( on ) + SetBits( pev->spawnflags, SF_PATH_DISABLED ); + else + ClearBits( pev->spawnflags, SF_PATH_DISABLED ); + } + } +} + + +void CPathTrack :: Link( void ) +{ + edict_t *pentTarget; + + if ( !FStringNull(pev->target) ) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) ); + if ( !FNullEnt(pentTarget) ) + { + m_pnext = CPathTrack::Instance( pentTarget ); + + if ( m_pnext ) // If no next pointer, this is the end of a path + { + m_pnext->SetPrevious( this ); + } + } + else + ALERT( at_console, "Dead end link %s\n", STRING(pev->target) ); + } + + // Find "alternate" path + if ( m_altName ) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_altName) ); + if ( !FNullEnt(pentTarget) ) + { + m_paltpath = CPathTrack::Instance( pentTarget ); + + if ( m_paltpath ) // If no next pointer, this is the end of a path + { + m_paltpath->SetPrevious( this ); + } + } + } +} + + +void CPathTrack :: Spawn( void ) +{ + pev->solid = SOLID_TRIGGER; + UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8)); + + m_pnext = NULL; + m_pprevious = NULL; +// DEBUGGING CODE +#if PATH_SPARKLE_DEBUG + SetThink( Sparkle ); + pev->nextthink = gpGlobals->time + 0.5; +#endif +} + + +void CPathTrack::Activate( void ) +{ + if ( !FStringNull( pev->targetname ) ) // Link to next, and back-link + Link(); +} + +CPathTrack *CPathTrack :: ValidPath( CPathTrack *ppath, int testFlag ) +{ + if ( !ppath ) + return NULL; + + if ( testFlag && FBitSet( ppath->pev->spawnflags, SF_PATH_DISABLED ) ) + return NULL; + + return ppath; +} + + +void CPathTrack :: Project( CPathTrack *pstart, CPathTrack *pend, Vector *origin, float dist ) +{ + if ( pstart && pend ) + { + Vector dir = (pend->pev->origin - pstart->pev->origin); + dir = dir.Normalize(); + *origin = pend->pev->origin + dir * dist; + } +} + +CPathTrack *CPathTrack::GetNext( void ) +{ + if ( m_paltpath && FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ) && !FBitSet( pev->spawnflags, SF_PATH_ALTREVERSE ) ) + return m_paltpath; + + return m_pnext; +} + + + +CPathTrack *CPathTrack::GetPrevious( void ) +{ + if ( m_paltpath && FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ) && FBitSet( pev->spawnflags, SF_PATH_ALTREVERSE ) ) + return m_paltpath; + + return m_pprevious; +} + + + +void CPathTrack::SetPrevious( CPathTrack *pprev ) +{ + // Only set previous if this isn't my alternate path + if ( pprev && !FStrEq( STRING(pprev->pev->targetname), STRING(m_altName) ) ) + m_pprevious = pprev; +} + + +// Assumes this is ALWAYS enabled +CPathTrack *CPathTrack :: LookAhead( Vector *origin, float dist, int move ) +{ + CPathTrack *pcurrent; + float originalDist = dist; + + pcurrent = this; + Vector currentPos = *origin; + + if ( dist < 0 ) // Travelling backwards through path + { + dist = -dist; + while ( dist > 0 ) + { + Vector dir = pcurrent->pev->origin - currentPos; + float length = dir.Length(); + if ( !length ) + { + if ( !ValidPath(pcurrent->GetPrevious(), move) ) // If there is no previous node, or it's disabled, return now. + { + if ( !move ) + Project( pcurrent->GetNext(), pcurrent, origin, dist ); + return NULL; + } + pcurrent = pcurrent->GetPrevious(); + } + else if ( length > dist ) // enough left in this path to move + { + *origin = currentPos + (dir * (dist / length)); + return pcurrent; + } + else + { + dist -= length; + currentPos = pcurrent->pev->origin; + *origin = currentPos; + if ( !ValidPath(pcurrent->GetPrevious(), move) ) // If there is no previous node, or it's disabled, return now. + return NULL; + + pcurrent = pcurrent->GetPrevious(); + } + } + *origin = currentPos; + return pcurrent; + } + else + { + while ( dist > 0 ) + { + if ( !ValidPath(pcurrent->GetNext(), move) ) // If there is no next node, or it's disabled, return now. + { + if ( !move ) + Project( pcurrent->GetPrevious(), pcurrent, origin, dist ); + return NULL; + } + Vector dir = pcurrent->GetNext()->pev->origin - currentPos; + float length = dir.Length(); + if ( !length && !ValidPath( pcurrent->GetNext()->GetNext(), move ) ) + { + if ( dist == originalDist ) // HACK -- up against a dead end + return NULL; + return pcurrent; + } + if ( length > dist ) // enough left in this path to move + { + *origin = currentPos + (dir * (dist / length)); + return pcurrent; + } + else + { + dist -= length; + currentPos = pcurrent->GetNext()->pev->origin; + pcurrent = pcurrent->GetNext(); + *origin = currentPos; + } + } + *origin = currentPos; + } + + return pcurrent; +} + + +// Assumes this is ALWAYS enabled +CPathTrack *CPathTrack :: Nearest( Vector origin ) +{ + int deadCount; + float minDist, dist; + Vector delta; + CPathTrack *ppath, *pnearest; + + + delta = origin - pev->origin; + delta.z = 0; + minDist = delta.Length(); + pnearest = this; + ppath = GetNext(); + + // Hey, I could use the old 2 racing pointers solution to this, but I'm lazy :) + deadCount = 0; + while ( ppath && ppath != this ) + { + deadCount++; + if ( deadCount > 9999 ) + { + ALERT( at_error, "Bad sequence of path_tracks from %s", STRING(pev->targetname) ); + return NULL; + } + delta = origin - ppath->pev->origin; + delta.z = 0; + dist = delta.Length(); + if ( dist < minDist ) + { + minDist = dist; + pnearest = ppath; + } + ppath = ppath->GetNext(); + } + return pnearest; +} + + +CPathTrack *CPathTrack::Instance( edict_t *pent ) +{ + if ( FClassnameIs( pent, "path_track" ) ) + return (CPathTrack *)GET_PRIVATE(pent); + return NULL; +} + + + // DEBUGGING CODE +#if PATH_SPARKLE_DEBUG +void CPathTrack :: Sparkle( void ) +{ + + pev->nextthink = gpGlobals->time + 0.2; + if ( FBitSet( pev->spawnflags, SF_PATH_DISABLED ) ) + UTIL_ParticleEffect(pev->origin, Vector(0,0,100), 210, 10); + else + UTIL_ParticleEffect(pev->origin, Vector(0,0,100), 84, 10); +} +#endif + diff --git a/dlls/plane.cpp b/dlls/plane.cpp new file mode 100644 index 0000000..fffd80c --- /dev/null +++ b/dlls/plane.cpp @@ -0,0 +1,60 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "plane.h" + +//========================================================= +// Plane +//========================================================= +CPlane :: CPlane ( void ) +{ + m_fInitialized = FALSE; +} + +//========================================================= +// InitializePlane - Takes a normal for the plane and a +// point on the plane and +//========================================================= +void CPlane :: InitializePlane ( const Vector &vecNormal, const Vector &vecPoint ) +{ + m_vecNormal = vecNormal; + m_flDist = DotProduct ( m_vecNormal, vecPoint ); + m_fInitialized = TRUE; +} + + +//========================================================= +// PointInFront - determines whether the given vector is +// in front of the plane. +//========================================================= +BOOL CPlane :: PointInFront ( const Vector &vecPoint ) +{ + float flFace; + + if ( !m_fInitialized ) + { + return FALSE; + } + + flFace = DotProduct ( m_vecNormal, vecPoint ) - m_flDist; + + if ( flFace >= 0 ) + { + return TRUE; + } + + return FALSE; +} + diff --git a/dlls/plane.h b/dlls/plane.h new file mode 100644 index 0000000..10baeee --- /dev/null +++ b/dlls/plane.h @@ -0,0 +1,43 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PLANE_H +#define PLANE_H + +//========================================================= +// Plane +//========================================================= +class CPlane +{ +public: + CPlane ( void ); + + //========================================================= + // InitializePlane - Takes a normal for the plane and a + // point on the plane and + //========================================================= + void InitializePlane ( const Vector &vecNormal, const Vector &vecPoint ); + + //========================================================= + // PointInFront - determines whether the given vector is + // in front of the plane. + //========================================================= + BOOL PointInFront ( const Vector &vecPoint ); + + Vector m_vecNormal; + float m_flDist; + BOOL m_fInitialized; +}; + +#endif // PLANE_H diff --git a/dlls/plats.cpp b/dlls/plats.cpp new file mode 100644 index 0000000..8207b76 --- /dev/null +++ b/dlls/plats.cpp @@ -0,0 +1,2285 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== plats.cpp ======================================================== + + spawn, think, and touch functions for trains, etc + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "trains.h" +#include "saverestore.h" + +static void PlatSpawnInsideTrigger(entvars_t* pevPlatform); + +#define SF_PLAT_TOGGLE 0x0001 + +class CBasePlatTrain : public CBaseToggle +{ +public: + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + void KeyValue( KeyValueData* pkvd); + void Precache( void ); + + // This is done to fix spawn flag collisions between this class and a derived class + virtual BOOL IsTogglePlat( void ) { return (pev->spawnflags & SF_PLAT_TOGGLE) ? TRUE : FALSE; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + BYTE m_bMoveSnd; // sound a plat makes while moving + BYTE m_bStopSnd; // sound a plat makes when it stops + float m_volume; // Sound volume +}; + +TYPEDESCRIPTION CBasePlatTrain::m_SaveData[] = +{ + DEFINE_FIELD( CBasePlatTrain, m_bMoveSnd, FIELD_CHARACTER ), + DEFINE_FIELD( CBasePlatTrain, m_bStopSnd, FIELD_CHARACTER ), + DEFINE_FIELD( CBasePlatTrain, m_volume, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CBasePlatTrain, CBaseToggle ); + +void CBasePlatTrain :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "lip")) + { + m_flLip = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "height")) + { + m_flHeight = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "rotation")) + { + m_vecFinalAngle.x = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "movesnd")) + { + m_bMoveSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "stopsnd")) + { + m_bStopSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "volume")) + { + m_volume = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +#define noiseMoving noise +#define noiseArrived noise1 + +void CBasePlatTrain::Precache( void ) +{ +// set the plat's "in-motion" sound + switch (m_bMoveSnd) + { + case 0: + pev->noiseMoving = MAKE_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("plats/bigmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/bigmove1.wav"); + break; + case 2: + PRECACHE_SOUND ("plats/bigmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/bigmove2.wav"); + break; + case 3: + PRECACHE_SOUND ("plats/elevmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/elevmove1.wav"); + break; + case 4: + PRECACHE_SOUND ("plats/elevmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/elevmove2.wav"); + break; + case 5: + PRECACHE_SOUND ("plats/elevmove3.wav"); + pev->noiseMoving = MAKE_STRING("plats/elevmove3.wav"); + break; + case 6: + PRECACHE_SOUND ("plats/freightmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/freightmove1.wav"); + break; + case 7: + PRECACHE_SOUND ("plats/freightmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/freightmove2.wav"); + break; + case 8: + PRECACHE_SOUND ("plats/heavymove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/heavymove1.wav"); + break; + case 9: + PRECACHE_SOUND ("plats/rackmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/rackmove1.wav"); + break; + case 10: + PRECACHE_SOUND ("plats/railmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/railmove1.wav"); + break; + case 11: + PRECACHE_SOUND ("plats/squeekmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/squeekmove1.wav"); + break; + case 12: + PRECACHE_SOUND ("plats/talkmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/talkmove1.wav"); + break; + case 13: + PRECACHE_SOUND ("plats/talkmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/talkmove2.wav"); + break; + default: + pev->noiseMoving = MAKE_STRING("common/null.wav"); + break; + } + +// set the plat's 'reached destination' stop sound + switch (m_bStopSnd) + { + case 0: + pev->noiseArrived = MAKE_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("plats/bigstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/bigstop1.wav"); + break; + case 2: + PRECACHE_SOUND ("plats/bigstop2.wav"); + pev->noiseArrived = MAKE_STRING("plats/bigstop2.wav"); + break; + case 3: + PRECACHE_SOUND ("plats/freightstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/freightstop1.wav"); + break; + case 4: + PRECACHE_SOUND ("plats/heavystop2.wav"); + pev->noiseArrived = MAKE_STRING("plats/heavystop2.wav"); + break; + case 5: + PRECACHE_SOUND ("plats/rackstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/rackstop1.wav"); + break; + case 6: + PRECACHE_SOUND ("plats/railstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/railstop1.wav"); + break; + case 7: + PRECACHE_SOUND ("plats/squeekstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/squeekstop1.wav"); + break; + case 8: + PRECACHE_SOUND ("plats/talkstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/talkstop1.wav"); + break; + + default: + pev->noiseArrived = MAKE_STRING("common/null.wav"); + break; + } +} + +// +//====================== PLAT code ==================================================== +// + + +#define noiseMovement noise +#define noiseStopMoving noise1 + +class CFuncPlat : public CBasePlatTrain +{ +public: + void Spawn( void ); + void Precache( void ); + void Setup( void ); + + virtual void Blocked( CBaseEntity *pOther ); + + + void EXPORT PlatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + void EXPORT CallGoDown( void ) { GoDown(); } + void EXPORT CallHitTop( void ) { HitTop(); } + void EXPORT CallHitBottom( void ) { HitBottom(); } + + virtual void GoUp( void ); + virtual void GoDown( void ); + virtual void HitTop( void ); + virtual void HitBottom( void ); +}; +LINK_ENTITY_TO_CLASS( func_plat, CFuncPlat ); + + +// UNDONE: Need to save this!!! It needs class & linkage +class CPlatTrigger : public CBaseEntity +{ +public: + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DONT_SAVE; } + void SpawnInsideTrigger( CFuncPlat *pPlatform ); + void Touch( CBaseEntity *pOther ); + CFuncPlat *m_pPlatform; +}; + + + +/*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER +speed default 150 + +Plats are always drawn in the extended position, so they will light correctly. + +If the plat is the target of another trigger or button, it will start out disabled in +the extended position until it is trigger, when it will lower and become a normal plat. + +If the "height" key is set, that will determine the amount the plat moves, instead of +being implicitly determined by the model's height. + +Set "sounds" to one of the following: +1) base fast +2) chain slow +*/ + +void CFuncPlat :: Setup( void ) +{ + //pev->noiseMovement = MAKE_STRING("plats/platmove1.wav"); + //pev->noiseStopMoving = MAKE_STRING("plats/platstop1.wav"); + + if (m_flTLength == 0) + m_flTLength = 80; + if (m_flTWidth == 0) + m_flTWidth = 10; + + pev->angles = g_vecZero; + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); // set size and link into world + UTIL_SetSize(pev, pev->mins, pev->maxs); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + // vecPosition1 is the top position, vecPosition2 is the bottom + m_vecPosition1 = pev->origin; + m_vecPosition2 = pev->origin; + if (m_flHeight != 0) + m_vecPosition2.z = pev->origin.z - m_flHeight; + else + m_vecPosition2.z = pev->origin.z - pev->size.z + 8; + if (pev->speed == 0) + pev->speed = 150; + + if ( m_volume == 0 ) + m_volume = 0.85; +} + + +void CFuncPlat :: Precache( ) +{ + CBasePlatTrain::Precache(); + //PRECACHE_SOUND("plats/platmove1.wav"); + //PRECACHE_SOUND("plats/platstop1.wav"); + if ( !IsTogglePlat() ) + PlatSpawnInsideTrigger( pev ); // the "start moving" trigger +} + + +void CFuncPlat :: Spawn( ) +{ + Setup(); + + Precache(); + + // If this platform is the target of some button, it starts at the TOP position, + // and is brought down by that button. Otherwise, it starts at BOTTOM. + if ( !FStringNull(pev->targetname) ) + { + UTIL_SetOrigin (pev, m_vecPosition1); + m_toggle_state = TS_AT_TOP; + SetUse( &CFuncPlat::PlatUse ); + } + else + { + UTIL_SetOrigin (pev, m_vecPosition2); + m_toggle_state = TS_AT_BOTTOM; + } +} + + + +static void PlatSpawnInsideTrigger(entvars_t* pevPlatform) +{ + GetClassPtr( (CPlatTrigger *)NULL)->SpawnInsideTrigger( GetClassPtr( (CFuncPlat *)pevPlatform ) ); +} + + +// +// Create a trigger entity for a platform. +// +void CPlatTrigger :: SpawnInsideTrigger( CFuncPlat *pPlatform ) +{ + m_pPlatform = pPlatform; + // Create trigger entity, "point" it at the owning platform, give it a touch method + pev->solid = SOLID_TRIGGER; + pev->movetype = MOVETYPE_NONE; + pev->origin = pPlatform->pev->origin; + + // Establish the trigger field's size + Vector vecTMin = m_pPlatform->pev->mins + Vector ( 25 , 25 , 0 ); + Vector vecTMax = m_pPlatform->pev->maxs + Vector ( 25 , 25 , 8 ); + vecTMin.z = vecTMax.z - ( m_pPlatform->m_vecPosition1.z - m_pPlatform->m_vecPosition2.z + 8 ); + if (m_pPlatform->pev->size.x <= 50) + { + vecTMin.x = (m_pPlatform->pev->mins.x + m_pPlatform->pev->maxs.x) / 2; + vecTMax.x = vecTMin.x + 1; + } + if (m_pPlatform->pev->size.y <= 50) + { + vecTMin.y = (m_pPlatform->pev->mins.y + m_pPlatform->pev->maxs.y) / 2; + vecTMax.y = vecTMin.y + 1; + } + UTIL_SetSize ( pev, vecTMin, vecTMax ); +} + + +// +// When the platform's trigger field is touched, the platform ??? +// +void CPlatTrigger :: Touch( CBaseEntity *pOther ) +{ + // Ignore touches by non-players + entvars_t* pevToucher = pOther->pev; + if ( !FClassnameIs (pevToucher, "player") ) + return; + + // Ignore touches by corpses + if (!pOther->IsAlive()||!m_pPlatform||!m_pPlatform->pev) + return; + + // Make linked platform go up/down. + if (m_pPlatform->m_toggle_state == TS_AT_BOTTOM) + m_pPlatform->GoUp(); + else if (m_pPlatform->m_toggle_state == TS_AT_TOP) + m_pPlatform->pev->nextthink = m_pPlatform->pev->ltime + 1;// delay going down +} + + +// +// Used by SUB_UseTargets, when a platform is the target of a button. +// Start bringing platform down. +// +void CFuncPlat :: PlatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( IsTogglePlat() ) + { + // Top is off, bottom is on + BOOL on = (m_toggle_state == TS_AT_BOTTOM) ? TRUE : FALSE; + + if ( !ShouldToggle( useType, on ) ) + return; + + if (m_toggle_state == TS_AT_TOP) + GoDown(); + else if ( m_toggle_state == TS_AT_BOTTOM ) + GoUp(); + } + else + { + SetUse( NULL ); + + if (m_toggle_state == TS_AT_TOP) + GoDown(); + } +} + + +// +// Platform is at top, now starts moving down. +// +void CFuncPlat :: GoDown( void ) +{ + if(pev->noiseMovement) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_AT_TOP || m_toggle_state == TS_GOING_UP); + m_toggle_state = TS_GOING_DOWN; + SetMoveDone(&CFuncPlat::CallHitBottom); + LinearMove(m_vecPosition2, pev->speed); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncPlat :: HitBottom( void ) +{ + if(pev->noiseMovement) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement)); + + if (pev->noiseStopMoving) + EMIT_SOUND(ENT(pev), CHAN_WEAPON, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_AT_BOTTOM; +} + + +// +// Platform is at bottom, now starts moving up +// +void CFuncPlat :: GoUp( void ) +{ + if (pev->noiseMovement) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_AT_BOTTOM || m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_GOING_UP; + SetMoveDone(&CFuncPlat::CallHitTop); + LinearMove(m_vecPosition1, pev->speed); +} + + +// +// Platform has hit top. Pauses, then starts back down again. +// +void CFuncPlat :: HitTop( void ) +{ + if(pev->noiseMovement) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement)); + + if (pev->noiseStopMoving) + EMIT_SOUND(ENT(pev), CHAN_WEAPON, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_GOING_UP); + m_toggle_state = TS_AT_TOP; + + if ( !IsTogglePlat() ) + { + // After a delay, the platform will automatically start going down again. + SetThink( &CFuncPlat::CallGoDown ); + pev->nextthink = pev->ltime + 3; + } +} + + +void CFuncPlat :: Blocked( CBaseEntity *pOther ) +{ + ALERT( at_aiconsole, "%s Blocked by %s\n", STRING(pev->classname), STRING(pOther->pev->classname) ); + // Hurt the blocker a little + pOther->TakeDamage(pev, pev, 1, DMG_CRUSH); + + if(pev->noiseMovement) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement)); + + // Send the platform back where it came from + ASSERT(m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN); + if (m_toggle_state == TS_GOING_UP) + GoDown(); + else if (m_toggle_state == TS_GOING_DOWN) + GoUp (); +} + + +class CFuncPlatRot : public CFuncPlat +{ +public: + void Spawn( void ); + void SetupRotation( void ); + + virtual void GoUp( void ); + virtual void GoDown( void ); + virtual void HitTop( void ); + virtual void HitBottom( void ); + + void RotMove( Vector &destAngle, float time ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + Vector m_end, m_start; +}; +LINK_ENTITY_TO_CLASS( func_platrot, CFuncPlatRot ); +TYPEDESCRIPTION CFuncPlatRot::m_SaveData[] = +{ + DEFINE_FIELD( CFuncPlatRot, m_end, FIELD_VECTOR ), + DEFINE_FIELD( CFuncPlatRot, m_start, FIELD_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CFuncPlatRot, CFuncPlat ); + + +void CFuncPlatRot :: SetupRotation( void ) +{ + if ( m_vecFinalAngle.x != 0 ) // This plat rotates too! + { + CBaseToggle :: AxisDir( pev ); + m_start = pev->angles; + m_end = pev->angles + pev->movedir * m_vecFinalAngle.x; + } + else + { + m_start = g_vecZero; + m_end = g_vecZero; + } + if ( !FStringNull(pev->targetname) ) // Start at top + { + pev->angles = m_end; + } +} + + +void CFuncPlatRot :: Spawn( void ) +{ + CFuncPlat :: Spawn(); + SetupRotation(); +} + +void CFuncPlatRot :: GoDown( void ) +{ + CFuncPlat :: GoDown(); + RotMove( m_start, pev->nextthink - pev->ltime ); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncPlatRot :: HitBottom( void ) +{ + CFuncPlat :: HitBottom(); + pev->avelocity = g_vecZero; + pev->angles = m_start; +} + + +// +// Platform is at bottom, now starts moving up +// +void CFuncPlatRot :: GoUp( void ) +{ + CFuncPlat :: GoUp(); + RotMove( m_end, pev->nextthink - pev->ltime ); +} + + +// +// Platform has hit top. Pauses, then starts back down again. +// +void CFuncPlatRot :: HitTop( void ) +{ + CFuncPlat :: HitTop(); + pev->avelocity = g_vecZero; + pev->angles = m_end; +} + + +void CFuncPlatRot :: RotMove( Vector &destAngle, float time ) +{ + // set destdelta to the vector needed to move + Vector vecDestDelta = destAngle - pev->angles; + + // Travel time is so short, we're practically there already; so make it so. + if ( time >= 0.1) + pev->avelocity = vecDestDelta / time; + else + { + pev->avelocity = vecDestDelta; + pev->nextthink = pev->ltime + 1; + } +} + + +// +//====================== TRAIN code ================================================== +// + +class CFuncTrain : public CBasePlatTrain +{ +public: + void Spawn( void ); + void Precache( void ); + void Activate( void ); + void OverrideReset( void ); + + void Blocked( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + + void EXPORT Wait( void ); + void EXPORT Next( void ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + entvars_t *m_pevCurrentTarget; + int m_sounds; + BOOL m_activated; +}; + +LINK_ENTITY_TO_CLASS( func_train, CFuncTrain ); +TYPEDESCRIPTION CFuncTrain::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTrain, m_sounds, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrain, m_pevCurrentTarget, FIELD_EVARS ), + DEFINE_FIELD( CFuncTrain, m_activated, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTrain, CBasePlatTrain ); + + +void CFuncTrain :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBasePlatTrain::KeyValue( pkvd ); +} + + +void CFuncTrain :: Blocked( CBaseEntity *pOther ) + +{ + if ( gpGlobals->time < m_flActivateFinished) + return; + + m_flActivateFinished = gpGlobals->time + 0.5; + + pOther->TakeDamage(pev, pev, pev->dmg, DMG_CRUSH); +} + + +void CFuncTrain :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->spawnflags & SF_TRAIN_WAIT_RETRIGGER ) + { + // Move toward my target + pev->spawnflags &= ~SF_TRAIN_WAIT_RETRIGGER; + Next(); + } + else + { + pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER; + // Pop back to last target if it's available + if ( pev->enemy ) + pev->target = pev->enemy->v.targetname; + pev->nextthink = 0; + pev->velocity = g_vecZero; + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + } +} + + +void CFuncTrain :: Wait( void ) +{ + // Fire the pass target if there is one + if ( m_pevCurrentTarget->message ) + { + FireTargets( STRING(m_pevCurrentTarget->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( m_pevCurrentTarget->spawnflags, SF_CORNER_FIREONCE ) ) + m_pevCurrentTarget->message = 0; + } + + // need pointer to LAST target. + if ( FBitSet (m_pevCurrentTarget->spawnflags , SF_TRAIN_WAIT_RETRIGGER ) || ( pev->spawnflags & SF_TRAIN_WAIT_RETRIGGER ) ) + { + pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER; + // clear the sound channel. + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + pev->nextthink = 0; + return; + } + + // ALERT ( at_console, "%f\n", m_flWait ); + + if (m_flWait != 0) + {// -1 wait will wait forever! + pev->nextthink = pev->ltime + m_flWait; + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + SetThink( &CFuncTrain::Next ); + } + else + { + Next();// do it RIGHT now! + } +} + + +// +// Train next - path corner needs to change to next target +// +void CFuncTrain :: Next( void ) +{ + CBaseEntity *pTarg; + + + // now find our next target + pTarg = GetNextTarget(); + + if ( !pTarg ) + { + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + // Play stop sound + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + return; + } + + // Save last target in case we need to find it again + pev->message = pev->target; + + pev->target = pTarg->pev->target; + m_flWait = pTarg->GetDelay(); + + if ( m_pevCurrentTarget && m_pevCurrentTarget->speed != 0 ) + {// don't copy speed from target if it is 0 (uninitialized) + pev->speed = m_pevCurrentTarget->speed; + ALERT( at_aiconsole, "Train %s speed to %4.2f\n", STRING(pev->targetname), pev->speed ); + } + m_pevCurrentTarget = pTarg->pev;// keep track of this since path corners change our target for us. + + pev->enemy = pTarg->edict();//hack + + if(FBitSet(m_pevCurrentTarget->spawnflags, SF_CORNER_TELEPORT)) + { + // Path corner has indicated a teleport to the next corner. + SetBits(pev->effects, EF_NOINTERP); + UTIL_SetOrigin(pev, pTarg->pev->origin - (pev->mins + pev->maxs)* 0.5); + Wait(); // Get on with doing the next path corner. + } + else + { + // Normal linear move. + + // CHANGED this from CHAN_VOICE to CHAN_STATIC around OEM beta time because trains should + // use CHAN_STATIC for their movement sounds to prevent sound field problems. + // this is not a hack or temporary fix, this is how things should be. (sjb). + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + if ( pev->noiseMovement ) + EMIT_SOUND (ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement), m_volume, ATTN_NORM); + ClearBits(pev->effects, EF_NOINTERP); + SetMoveDone( &CFuncTrain::Wait ); + LinearMove (pTarg->pev->origin - (pev->mins + pev->maxs)* 0.5, pev->speed); + } +} + + +void CFuncTrain :: Activate( void ) +{ + // Not yet active, so teleport to first target + if ( !m_activated ) + { + m_activated = TRUE; + entvars_t *pevTarg = VARS( FIND_ENTITY_BY_TARGETNAME (NULL, STRING(pev->target) ) ); + + pev->target = pevTarg->target; + m_pevCurrentTarget = pevTarg;// keep track of this since path corners change our target for us. + + UTIL_SetOrigin (pev, pevTarg->origin - (pev->mins + pev->maxs) * 0.5 ); + + if ( FStringNull(pev->targetname) ) + { // not triggered, so start immediately + pev->nextthink = pev->ltime + 0.1; + SetThink( &CFuncTrain::Next ); + } + else + pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER; + } +} + +/*QUAKED func_train (0 .5 .8) ? +Trains are moving platforms that players can ride. +The targets origin specifies the min point of the train at each corner. +The train spawns at the first target it is pointing at. +If the train is the target of a button or trigger, it will not begin moving until activated. +speed default 100 +dmg default 2 +sounds +1) ratchet metal +*/ + +void CFuncTrain :: Spawn( void ) +{ + Precache(); + if (pev->speed == 0) + pev->speed = 100; + + if ( FStringNull(pev->target) ) + ALERT(at_console, "FuncTrain with no target"); + + if (pev->dmg == 0) + pev->dmg = 2; + + pev->movetype = MOVETYPE_PUSH; + + if ( FBitSet (pev->spawnflags, SF_TRACKTRAIN_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + + SET_MODEL( ENT(pev), STRING(pev->model) ); + UTIL_SetSize (pev, pev->mins, pev->maxs); + UTIL_SetOrigin(pev, pev->origin); + + m_activated = FALSE; + + if ( m_volume == 0 ) + m_volume = 0.85; +} + + +void CFuncTrain::Precache( void ) +{ + CBasePlatTrain::Precache(); + +#if 0 // obsolete + // otherwise use preset sound + switch (m_sounds) + { + case 0: + pev->noise = 0; + pev->noise1 = 0; + break; + + case 1: + PRECACHE_SOUND ("plats/train2.wav"); + PRECACHE_SOUND ("plats/train1.wav"); + pev->noise = MAKE_STRING("plats/train2.wav"); + pev->noise1 = MAKE_STRING("plats/train1.wav"); + break; + + case 2: + PRECACHE_SOUND ("plats/platmove1.wav"); + PRECACHE_SOUND ("plats/platstop1.wav"); + pev->noise = MAKE_STRING("plats/platstop1.wav"); + pev->noise1 = MAKE_STRING("plats/platmove1.wav"); + break; + } +#endif +} + + +void CFuncTrain::OverrideReset( void ) +{ + CBaseEntity *pTarg; + + // Are we moving? + if ( pev->velocity != g_vecZero && pev->nextthink != 0 ) + { + pev->target = pev->message; + // now find our next target + pTarg = GetNextTarget(); + if ( !pTarg ) + { + pev->nextthink = 0; + pev->velocity = g_vecZero; + } + else // Keep moving for 0.1 secs, then find path_corner again and restart + { + SetThink( &CFuncTrain::Next ); + pev->nextthink = pev->ltime + 0.1; + } + } +} + + + + +// --------------------------------------------------------------------- +// +// Track Train +// +// --------------------------------------------------------------------- + +TYPEDESCRIPTION CFuncTrackTrain::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTrackTrain, m_ppath, FIELD_CLASSPTR ), + DEFINE_FIELD( CFuncTrackTrain, m_length, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_height, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_speed, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_dir, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_startSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_controlMins, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTrackTrain, m_controlMaxs, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTrackTrain, m_sounds, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrackTrain, m_flVolume, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_flBank, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_oldSpeed, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTrackTrain, CBaseEntity ); +LINK_ENTITY_TO_CLASS( func_tracktrain, CFuncTrackTrain ); + +void CFuncTrackTrain :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "wheels")) + { + m_length = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "height")) + { + m_height = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "startspeed")) + { + m_startSpeed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "volume")) + { + m_flVolume = (float) (atoi(pkvd->szValue)); + m_flVolume *= 0.1; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "bank")) + { + m_flBank = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +void CFuncTrackTrain :: NextThink( float thinkTime, BOOL alwaysThink ) +{ + if ( alwaysThink ) + pev->flags |= FL_ALWAYSTHINK; + else + pev->flags &= ~FL_ALWAYSTHINK; + + pev->nextthink = thinkTime; +} + + +void CFuncTrackTrain :: Blocked( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + // Blocker is on-ground on the train + if ( FBitSet( pevOther->flags, FL_ONGROUND ) && VARS(pevOther->groundentity) == pev ) + { + float deltaSpeed = fabs(pev->speed); + if ( deltaSpeed > 50 ) + deltaSpeed = 50; + if ( !pevOther->velocity.z ) + pevOther->velocity.z += deltaSpeed; + return; + } + else + pevOther->velocity = (pevOther->origin - pev->origin ).Normalize() * pev->dmg; + + ALERT( at_aiconsole, "TRAIN(%s): Blocked by %s (dmg:%.2f)\n", STRING(pev->targetname), STRING(pOther->pev->classname), pev->dmg ); + if ( pev->dmg <= 0 ) + return; + // we can't hurt this thing, so we're not concerned with it + pOther->TakeDamage(pev, pev, pev->dmg, DMG_CRUSH); +} + + +void CFuncTrackTrain :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( useType != USE_SET ) + { + if ( !ShouldToggle( useType, (pev->speed != 0) ) ) + return; + + if ( pev->speed == 0 ) + { + pev->speed = m_speed * m_dir; + + Next(); + } + else + { + pev->speed = 0; + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + StopSound(); + SetThink( NULL ); + } + } + else + { + float delta = value; + + delta = ((int)(pev->speed * 4) / (int)m_speed)*0.25 + 0.25 * delta; + if ( delta > 1 ) + delta = 1; + else if ( delta < -1 ) + delta = -1; + if ( pev->spawnflags & SF_TRACKTRAIN_FORWARDONLY ) + { + if ( delta < 0 ) + delta = 0; + } + pev->speed = m_speed * delta; + Next(); + ALERT( at_aiconsole, "TRAIN(%s), speed to %.2f\n", STRING(pev->targetname), pev->speed ); + } +} + + +static float Fix( float angle ) +{ + while ( angle < 0 ) + angle += 360; + while ( angle > 360 ) + angle -= 360; + + return angle; +} + + +static void FixupAngles( Vector &v ) +{ + v.x = Fix( v.x ); + v.y = Fix( v.y ); + v.z = Fix( v.z ); +} + +#define TRAIN_STARTPITCH 60 +#define TRAIN_MAXPITCH 200 +#define TRAIN_MAXSPEED 1000 // approx max speed for sound pitch calculation + +void CFuncTrackTrain :: StopSound( void ) +{ + // if sound playing, stop it + if (m_soundPlaying && pev->noise) + { + unsigned short us_encode; + unsigned short us_sound = ( ( unsigned short )( m_sounds ) & 0x0007 ) << 12; + + us_encode = us_sound; + + PLAYBACK_EVENT_FULL( FEV_RELIABLE | FEV_UPDATE, edict(), m_usAdjustPitch, 0.0, + (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, us_encode, 0, 1, 0 ); + + /* + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noise)); + */ + EMIT_SOUND_DYN(ENT(pev), CHAN_ITEM, "plats/ttrain_brake1.wav", m_flVolume, ATTN_NORM, 0, 100); + } + + m_soundPlaying = 0; +} + +// update pitch based on speed, start sound if not playing +// NOTE: when train goes through transition, m_soundPlaying should go to 0, +// which will cause the looped sound to restart. + +void CFuncTrackTrain :: UpdateSound( void ) +{ + float flpitch; + + if (!pev->noise) + return; + + flpitch = TRAIN_STARTPITCH + (abs(pev->speed) * (TRAIN_MAXPITCH - TRAIN_STARTPITCH) / TRAIN_MAXSPEED); + + if (!m_soundPlaying) + { + // play startup sound for train + EMIT_SOUND_DYN(ENT(pev), CHAN_ITEM, "plats/ttrain_start1.wav", m_flVolume, ATTN_NORM, 0, 100); + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noise), m_flVolume, ATTN_NORM, 0, (int) flpitch); + m_soundPlaying = 1; + } + else + { +/* + // update pitch + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noise), m_flVolume, ATTN_NORM, SND_CHANGE_PITCH, (int) flpitch); +*/ + // volume 0.0 - 1.0 - 6 bits + // m_sounds 3 bits + // flpitch = 6 bits + // 15 bits total + + unsigned short us_encode; + unsigned short us_sound = ( ( unsigned short )( m_sounds ) & 0x0007 ) << 12; + unsigned short us_pitch = ( ( unsigned short )( flpitch / 10.0 ) & 0x003f ) << 6; + unsigned short us_volume = ( ( unsigned short )( m_flVolume * 40.0 ) & 0x003f ); + + us_encode = us_sound | us_pitch | us_volume; + + PLAYBACK_EVENT_FULL( FEV_RELIABLE | FEV_UPDATE, edict(), m_usAdjustPitch, 0.0, + (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, us_encode, 0, 0, 0 ); + } +} + + +void CFuncTrackTrain :: Next( void ) +{ + float time = 0.5; + + if ( !pev->speed ) + { + ALERT( at_aiconsole, "TRAIN(%s): Speed is 0\n", STRING(pev->targetname) ); + StopSound(); + return; + } + +// if ( !m_ppath ) +// m_ppath = CPathTrack::Instance(FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) )); + if ( !m_ppath ) + { + ALERT( at_aiconsole, "TRAIN(%s): Lost path\n", STRING(pev->targetname) ); + StopSound(); + return; + } + + UpdateSound(); + + Vector nextPos = pev->origin; + + nextPos.z -= m_height; + CPathTrack *pnext = m_ppath->LookAhead( &nextPos, pev->speed * 0.1, 1 ); + nextPos.z += m_height; + + pev->velocity = (nextPos - pev->origin) * 10; + Vector nextFront = pev->origin; + + nextFront.z -= m_height; + if ( m_length > 0 ) + m_ppath->LookAhead( &nextFront, m_length, 0 ); + else + m_ppath->LookAhead( &nextFront, 100, 0 ); + nextFront.z += m_height; + + Vector delta = nextFront - pev->origin; + Vector angles = UTIL_VecToAngles( delta ); + // The train actually points west + angles.y += 180; + + // !!! All of this crap has to be done to make the angles not wrap around, revisit this. + FixupAngles( angles ); + FixupAngles( pev->angles ); + + if ( !pnext || (delta.x == 0 && delta.y == 0) ) + angles = pev->angles; + + float vy, vx; + if ( !(pev->spawnflags & SF_TRACKTRAIN_NOPITCH) ) + vx = UTIL_AngleDistance( angles.x, pev->angles.x ); + else + vx = 0; + vy = UTIL_AngleDistance( angles.y, pev->angles.y ); + + pev->avelocity.y = vy * 10; + pev->avelocity.x = vx * 10; + + if ( m_flBank != 0 ) + { + if ( pev->avelocity.y < -5 ) + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( -m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); + else if ( pev->avelocity.y > 5 ) + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); + else + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( 0, pev->angles.z, m_flBank*4 ), pev->angles.z) * 4; + } + + if ( pnext ) + { + if ( pnext != m_ppath ) + { + CPathTrack *pFire; + if ( pev->speed >= 0 ) + pFire = pnext; + else + pFire = m_ppath; + + m_ppath = pnext; + // Fire the pass target if there is one + if ( pFire->pev->message ) + { + FireTargets( STRING(pFire->pev->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( pFire->pev->spawnflags, SF_PATH_FIREONCE ) ) + pFire->pev->message = 0; + } + + if ( pFire->pev->spawnflags & SF_PATH_DISABLE_TRAIN ) + pev->spawnflags |= SF_TRACKTRAIN_NOCONTROL; + + // Don't override speed if under user control + if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL ) + { + if ( pFire->pev->speed != 0 ) + {// don't copy speed from target if it is 0 (uninitialized) + pev->speed = pFire->pev->speed; + ALERT( at_aiconsole, "TrackTrain %s speed to %4.2f\n", STRING(pev->targetname), pev->speed ); + } + } + + } + SetThink( &CFuncTrackTrain::Next ); + NextThink( pev->ltime + time, TRUE ); + } + else // end of path, stop + { + StopSound(); + pev->velocity = (nextPos - pev->origin); + pev->avelocity = g_vecZero; + float distance = pev->velocity.Length(); + m_oldSpeed = pev->speed; + + + pev->speed = 0; + + // Move to the dead end + + // Are we there yet? + if ( distance > 0 ) + { + // no, how long to get there? + time = distance / m_oldSpeed; + pev->velocity = pev->velocity * (m_oldSpeed / distance); + SetThink( &CFuncTrackTrain::DeadEnd ); + NextThink( pev->ltime + time, FALSE ); + } + else + { + DeadEnd(); + } + } +} + + +void CFuncTrackTrain::DeadEnd( void ) +{ + // Fire the dead-end target if there is one + CPathTrack *pTrack, *pNext; + + pTrack = m_ppath; + + ALERT( at_aiconsole, "TRAIN(%s): Dead end ", STRING(pev->targetname) ); + // Find the dead end path node + // HACKHACK -- This is bugly, but the train can actually stop moving at a different node depending on it's speed + // so we have to traverse the list to it's end. + if ( pTrack ) + { + if ( m_oldSpeed < 0 ) + { + do + { + pNext = pTrack->ValidPath( pTrack->GetPrevious(), TRUE ); + if ( pNext ) + pTrack = pNext; + } while ( pNext ); + } + else + { + do + { + pNext = pTrack->ValidPath( pTrack->GetNext(), TRUE ); + if ( pNext ) + pTrack = pNext; + } while ( pNext ); + } + } + + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + if ( pTrack ) + { + ALERT( at_aiconsole, "at %s\n", STRING(pTrack->pev->targetname) ); + if ( pTrack->pev->netname ) + FireTargets( STRING(pTrack->pev->netname), this, this, USE_TOGGLE, 0 ); + } + else + ALERT( at_aiconsole, "\n" ); +} + + +void CFuncTrackTrain :: SetControls( entvars_t *pevControls ) +{ + Vector offset = pevControls->origin - pev->oldorigin; + + m_controlMins = pevControls->mins + offset; + m_controlMaxs = pevControls->maxs + offset; +} + + +BOOL CFuncTrackTrain :: OnControls( entvars_t *pevTest ) +{ + Vector offset = pevTest->origin - pev->origin; + + if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL ) + return FALSE; + + // Transform offset into local coordinates + UTIL_MakeVectors( pev->angles ); + Vector local; + local.x = DotProduct( offset, gpGlobals->v_forward ); + local.y = -DotProduct( offset, gpGlobals->v_right ); + local.z = DotProduct( offset, gpGlobals->v_up ); + + if ( local.x >= m_controlMins.x && local.y >= m_controlMins.y && local.z >= m_controlMins.z && + local.x <= m_controlMaxs.x && local.y <= m_controlMaxs.y && local.z <= m_controlMaxs.z ) + return TRUE; + + return FALSE; +} + + +void CFuncTrackTrain :: Find( void ) +{ + m_ppath = CPathTrack::Instance(FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) )); + if ( !m_ppath ) + return; + + entvars_t *pevTarget = m_ppath->pev; + if ( !FClassnameIs( pevTarget, "path_track" ) ) + { + ALERT( at_error, "func_track_train must be on a path of path_track\n" ); + m_ppath = NULL; + return; + } + + Vector nextPos = pevTarget->origin; + nextPos.z += m_height; + + Vector look = nextPos; + look.z -= m_height; + m_ppath->LookAhead( &look, m_length, 0 ); + look.z += m_height; + + pev->angles = UTIL_VecToAngles( look - nextPos ); + // The train actually points west + pev->angles.y += 180; + + if ( pev->spawnflags & SF_TRACKTRAIN_NOPITCH ) + pev->angles.x = 0; + UTIL_SetOrigin( pev, nextPos ); + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::Next ); + pev->speed = m_startSpeed; + + UpdateSound(); +} + + +void CFuncTrackTrain :: NearestPath( void ) +{ + CBaseEntity *pTrack = NULL; + CBaseEntity *pNearest = NULL; + float dist, closest; + + closest = 1024; + + while ((pTrack = UTIL_FindEntityInSphere( pTrack, pev->origin, 1024 )) != NULL) + { + // filter out non-tracks + if ( !(pTrack->pev->flags & (FL_CLIENT|FL_MONSTER)) && FClassnameIs( pTrack->pev, "path_track" ) ) + { + dist = (pev->origin - pTrack->pev->origin).Length(); + if ( dist < closest ) + { + closest = dist; + pNearest = pTrack; + } + } + } + + if ( !pNearest ) + { + ALERT( at_console, "Can't find a nearby track !!!\n" ); + SetThink(NULL); + return; + } + + ALERT( at_aiconsole, "TRAIN: %s, Nearest track is %s\n", STRING(pev->targetname), STRING(pNearest->pev->targetname) ); + // If I'm closer to the next path_track on this path, then it's my real path + pTrack = ((CPathTrack *)pNearest)->GetNext(); + if ( pTrack ) + { + if ( (pev->origin - pTrack->pev->origin).Length() < (pev->origin - pNearest->pev->origin).Length() ) + pNearest = pTrack; + } + + m_ppath = (CPathTrack *)pNearest; + + if ( pev->speed != 0 ) + { + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::Next ); + } +} + + +void CFuncTrackTrain::OverrideReset( void ) +{ + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::NearestPath ); +} + + +CFuncTrackTrain *CFuncTrackTrain::Instance( edict_t *pent ) +{ + if ( FClassnameIs( pent, "func_tracktrain" ) ) + return (CFuncTrackTrain *)GET_PRIVATE(pent); + return NULL; +} + +/*QUAKED func_train (0 .5 .8) ? +Trains are moving platforms that players can ride. +The targets origin specifies the min point of the train at each corner. +The train spawns at the first target it is pointing at. +If the train is the target of a button or trigger, it will not begin moving until activated. +speed default 100 +dmg default 2 +sounds +1) ratchet metal +*/ + +void CFuncTrackTrain :: Spawn( void ) +{ + if ( pev->speed == 0 ) + m_speed = 100; + else + m_speed = pev->speed; + + pev->speed = 0; + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + pev->impulse = m_speed; + + m_dir = 1; + + if ( FStringNull(pev->target) ) + ALERT( at_console, "FuncTrain with no target" ); + + if ( pev->spawnflags & SF_TRACKTRAIN_PASSABLE ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + SET_MODEL( ENT(pev), STRING(pev->model) ); + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); + + // Cache off placed origin for train controls + pev->oldorigin = pev->origin; + + m_controlMins = pev->mins; + m_controlMaxs = pev->maxs; + m_controlMaxs.z += 72; +// start trains on the next frame, to make sure their targets have had +// a chance to spawn/activate + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::Find ); + Precache(); +} + +void CFuncTrackTrain :: Precache( void ) +{ + if (m_flVolume == 0.0) + m_flVolume = 1.0; + + switch (m_sounds) + { + default: + // no sound + pev->noise = 0; + break; + case 1: PRECACHE_SOUND("plats/ttrain1.wav"); pev->noise = MAKE_STRING("plats/ttrain1.wav");break; + case 2: PRECACHE_SOUND("plats/ttrain2.wav"); pev->noise = MAKE_STRING("plats/ttrain2.wav");break; + case 3: PRECACHE_SOUND("plats/ttrain3.wav"); pev->noise = MAKE_STRING("plats/ttrain3.wav");break; + case 4: PRECACHE_SOUND("plats/ttrain4.wav"); pev->noise = MAKE_STRING("plats/ttrain4.wav");break; + case 5: PRECACHE_SOUND("plats/ttrain6.wav"); pev->noise = MAKE_STRING("plats/ttrain6.wav");break; + case 6: PRECACHE_SOUND("plats/ttrain7.wav"); pev->noise = MAKE_STRING("plats/ttrain7.wav");break; + } + + PRECACHE_SOUND("plats/ttrain_brake1.wav"); + PRECACHE_SOUND("plats/ttrain_start1.wav"); + + m_usAdjustPitch = PRECACHE_EVENT( 1, "events/train.sc" ); +} + +// This class defines the volume of space that the player must stand in to control the train +class CFuncTrainControls : public CBaseEntity +{ +public: + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + void Spawn( void ); + void EXPORT Find( void ); +}; +LINK_ENTITY_TO_CLASS( func_traincontrols, CFuncTrainControls ); + + +void CFuncTrainControls :: Find( void ) +{ + edict_t *pTarget = NULL; + + do + { + pTarget = FIND_ENTITY_BY_TARGETNAME( pTarget, STRING(pev->target) ); + } while ( !FNullEnt(pTarget) && !FClassnameIs(pTarget, "func_tracktrain") ); + + if ( FNullEnt( pTarget ) ) + { + ALERT( at_console, "No train %s\n", STRING(pev->target) ); + return; + } + + CFuncTrackTrain *ptrain = CFuncTrackTrain::Instance(pTarget); + ptrain->SetControls( pev ); + UTIL_Remove( this ); +} + + +void CFuncTrainControls :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); + + SetThink(&CFuncTrainControls::Find ); + pev->nextthink = gpGlobals->time; +} + + + +// ---------------------------------------------------------------------------- +// +// Track changer / Train elevator +// +// ---------------------------------------------------------------------------- + +#define SF_TRACK_ACTIVATETRAIN 0x00000001 +#define SF_TRACK_RELINK 0x00000002 +#define SF_TRACK_ROTMOVE 0x00000004 +#define SF_TRACK_STARTBOTTOM 0x00000008 +#define SF_TRACK_DONT_MOVE 0x00000010 + +// +// This entity is a rotating/moving platform that will carry a train to a new track. +// It must be larger in X-Y planar area than the train, since it must contain the +// train within these dimensions in order to operate when the train is near it. +// + +typedef enum { TRAIN_SAFE, TRAIN_BLOCKING, TRAIN_FOLLOWING } TRAIN_CODE; + +class CFuncTrackChange : public CFuncPlatRot +{ +public: + void Spawn( void ); + void Precache( void ); + +// virtual void Blocked( void ); + virtual void EXPORT GoUp( void ); + virtual void EXPORT GoDown( void ); + + void KeyValue( KeyValueData* pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Find( void ); + TRAIN_CODE EvaluateTrain( CPathTrack *pcurrent ); + void UpdateTrain( Vector &dest ); + virtual void HitBottom( void ); + virtual void HitTop( void ); + void Touch( CBaseEntity *pOther ); + virtual void UpdateAutoTargets( int toggleState ); + virtual BOOL IsTogglePlat( void ) { return TRUE; } + + void DisableUse( void ) { m_use = 0; } + void EnableUse( void ) { m_use = 1; } + int UseEnabled( void ) { return m_use; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual void OverrideReset( void ); + + + CPathTrack *m_trackTop; + CPathTrack *m_trackBottom; + + CFuncTrackTrain *m_train; + + int m_trackTopName; + int m_trackBottomName; + int m_trainName; + TRAIN_CODE m_code; + int m_targetState; + int m_use; +}; +LINK_ENTITY_TO_CLASS( func_trackchange, CFuncTrackChange ); + +TYPEDESCRIPTION CFuncTrackChange::m_SaveData[] = +{ + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackTop, FIELD_CLASSPTR ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackBottom, FIELD_CLASSPTR ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_train, FIELD_CLASSPTR ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackTopName, FIELD_STRING ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackBottomName, FIELD_STRING ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trainName, FIELD_STRING ), + DEFINE_FIELD( CFuncTrackChange, m_code, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrackChange, m_targetState, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrackChange, m_use, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTrackChange, CFuncPlatRot ); + +void CFuncTrackChange :: Spawn( void ) +{ + Setup(); + if ( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) ) + m_vecPosition2.z = pev->origin.z; + + SetupRotation(); + + if ( FBitSet( pev->spawnflags, SF_TRACK_STARTBOTTOM ) ) + { + UTIL_SetOrigin (pev, m_vecPosition2); + m_toggle_state = TS_AT_BOTTOM; + pev->angles = m_start; + m_targetState = TS_AT_TOP; + } + else + { + UTIL_SetOrigin (pev, m_vecPosition1); + m_toggle_state = TS_AT_TOP; + pev->angles = m_end; + m_targetState = TS_AT_BOTTOM; + } + + EnableUse(); + pev->nextthink = pev->ltime + 2.0; + SetThink( &CFuncTrackChange::Find ); + Precache(); +} + +void CFuncTrackChange :: Precache( void ) +{ + // Can't trigger sound + PRECACHE_SOUND( "buttons/button11.wav" ); + + CFuncPlatRot::Precache(); +} + + +// UNDONE: Filter touches before re-evaluating the train. +void CFuncTrackChange :: Touch( CBaseEntity *pOther ) +{ +#if 0 + TRAIN_CODE code; + entvars_t *pevToucher = pOther->pev; +#endif +} + + + +void CFuncTrackChange :: KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "train") ) + { + m_trainName = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "toptrack") ) + { + m_trackTopName = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "bottomtrack") ) + { + m_trackBottomName = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CFuncPlatRot::KeyValue( pkvd ); // Pass up to base class + } +} + + +void CFuncTrackChange::OverrideReset( void ) +{ + pev->nextthink = pev->ltime + 1.0; + SetThink( &CFuncTrackChange::Find ); +} + +void CFuncTrackChange :: Find( void ) +{ + // Find track entities + edict_t *target; + + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trackTopName) ); + if ( !FNullEnt(target) ) + { + m_trackTop = CPathTrack::Instance( target ); + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trackBottomName) ); + if ( !FNullEnt(target) ) + { + m_trackBottom = CPathTrack::Instance( target ); + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trainName) ); + if ( !FNullEnt(target) ) + { + m_train = CFuncTrackTrain::Instance( FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trainName) ) ); + if ( !m_train ) + { + ALERT( at_error, "Can't find train for track change! %s\n", STRING(m_trainName) ); + return; + } + Vector center = (pev->absmin + pev->absmax) * 0.5; + m_trackBottom = m_trackBottom->Nearest( center ); + m_trackTop = m_trackTop->Nearest( center ); + UpdateAutoTargets( m_toggle_state ); + SetThink( NULL ); + return; + } + else + { + ALERT( at_error, "Can't find train for track change! %s\n", STRING(m_trainName) ); + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trainName) ); + } + } + else + ALERT( at_error, "Can't find bottom track for track change! %s\n", STRING(m_trackBottomName) ); + } + else + ALERT( at_error, "Can't find top track for track change! %s\n", STRING(m_trackTopName) ); +} + + + +TRAIN_CODE CFuncTrackChange :: EvaluateTrain( CPathTrack *pcurrent ) +{ + // Go ahead and work, we don't have anything to switch, so just be an elevator + if ( !pcurrent || !m_train ) + return TRAIN_SAFE; + + if ( m_train->m_ppath == pcurrent || (pcurrent->m_pprevious && m_train->m_ppath == pcurrent->m_pprevious) || + (pcurrent->m_pnext && m_train->m_ppath == pcurrent->m_pnext) ) + { + if ( m_train->pev->speed != 0 ) + return TRAIN_BLOCKING; + + Vector dist = pev->origin - m_train->pev->origin; + float length = dist.Length2D(); + if ( length < m_train->m_length ) // Empirically determined close distance + return TRAIN_FOLLOWING; + else if ( length > (150 + m_train->m_length) ) + return TRAIN_SAFE; + + return TRAIN_BLOCKING; + } + + return TRAIN_SAFE; +} + + +void CFuncTrackChange :: UpdateTrain( Vector &dest ) +{ + float time = (pev->nextthink - pev->ltime); + + m_train->pev->velocity = pev->velocity; + m_train->pev->avelocity = pev->avelocity; + m_train->NextThink( m_train->pev->ltime + time, FALSE ); + + // Attempt at getting the train to rotate properly around the origin of the trackchange + if ( time <= 0 ) + return; + + Vector offset = m_train->pev->origin - pev->origin; + Vector delta = dest - pev->angles; + // Transform offset into local coordinates + UTIL_MakeInvVectors( delta, gpGlobals ); + Vector local; + local.x = DotProduct( offset, gpGlobals->v_forward ); + local.y = DotProduct( offset, gpGlobals->v_right ); + local.z = DotProduct( offset, gpGlobals->v_up ); + + local = local - offset; + m_train->pev->velocity = pev->velocity + (local * (1.0/time)); +} + +void CFuncTrackChange :: GoDown( void ) +{ + if ( m_code == TRAIN_BLOCKING ) + return; + + // HitBottom may get called during CFuncPlat::GoDown(), so set up for that + // before you call GoDown() + + UpdateAutoTargets( TS_GOING_DOWN ); + // If ROTMOVE, move & rotate + if ( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) ) + { + SetMoveDone( &CFuncTrackChange::CallHitBottom ); + m_toggle_state = TS_GOING_DOWN; + AngularMove( m_start, pev->speed ); + } + else + { + CFuncPlat :: GoDown(); + SetMoveDone( &CFuncTrackChange::CallHitBottom ); + RotMove( m_start, pev->nextthink - pev->ltime ); + } + // Otherwise, rotate first, move second + + // If the train is moving with the platform, update it + if ( m_code == TRAIN_FOLLOWING ) + { + UpdateTrain( m_start ); + m_train->m_ppath = NULL; + } +} + + +// +// Platform is at bottom, now starts moving up +// +void CFuncTrackChange :: GoUp( void ) +{ + if ( m_code == TRAIN_BLOCKING ) + return; + + // HitTop may get called during CFuncPlat::GoUp(), so set up for that + // before you call GoUp(); + + UpdateAutoTargets( TS_GOING_UP ); + if ( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) ) + { + m_toggle_state = TS_GOING_UP; + SetMoveDone( &CFuncTrackChange::CallHitTop ); + AngularMove( m_end, pev->speed ); + } + else + { + // If ROTMOVE, move & rotate + CFuncPlat :: GoUp(); + SetMoveDone( &CFuncTrackChange::CallHitTop ); + RotMove( m_end, pev->nextthink - pev->ltime ); + } + + // Otherwise, move first, rotate second + + // If the train is moving with the platform, update it + if ( m_code == TRAIN_FOLLOWING ) + { + UpdateTrain( m_end ); + m_train->m_ppath = NULL; + } +} + + +// Normal track change +void CFuncTrackChange :: UpdateAutoTargets( int toggleState ) +{ + if ( !m_trackTop || !m_trackBottom ) + return; + + if ( toggleState == TS_AT_TOP ) + ClearBits( m_trackTop->pev->spawnflags, SF_PATH_DISABLED ); + else + SetBits( m_trackTop->pev->spawnflags, SF_PATH_DISABLED ); + + if ( toggleState == TS_AT_BOTTOM ) + ClearBits( m_trackBottom->pev->spawnflags, SF_PATH_DISABLED ); + else + SetBits( m_trackBottom->pev->spawnflags, SF_PATH_DISABLED ); +} + + +void CFuncTrackChange :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( m_toggle_state != TS_AT_TOP && m_toggle_state != TS_AT_BOTTOM ) + return; + + // If train is in "safe" area, but not on the elevator, play alarm sound + if ( m_toggle_state == TS_AT_TOP ) + m_code = EvaluateTrain( m_trackTop ); + else if ( m_toggle_state == TS_AT_BOTTOM ) + m_code = EvaluateTrain( m_trackBottom ); + else + m_code = TRAIN_BLOCKING; + if ( m_code == TRAIN_BLOCKING ) + { + // Play alarm and return + EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/button11.wav", 1, ATTN_NORM); + return; + } + + // Otherwise, it's safe to move + // If at top, go down + // at bottom, go up + + DisableUse(); + if (m_toggle_state == TS_AT_TOP) + GoDown(); + else + GoUp(); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncTrackChange :: HitBottom( void ) +{ + CFuncPlatRot :: HitBottom(); + if ( m_code == TRAIN_FOLLOWING ) + { +// UpdateTrain(); + m_train->SetTrack( m_trackBottom ); + } + SetThink( NULL ); + pev->nextthink = -1; + + UpdateAutoTargets( m_toggle_state ); + + EnableUse(); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncTrackChange :: HitTop( void ) +{ + CFuncPlatRot :: HitTop(); + if ( m_code == TRAIN_FOLLOWING ) + { +// UpdateTrain(); + m_train->SetTrack( m_trackTop ); + } + + // Don't let the plat go back down + SetThink( NULL ); + pev->nextthink = -1; + UpdateAutoTargets( m_toggle_state ); + EnableUse(); +} + + + +class CFuncTrackAuto : public CFuncTrackChange +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void UpdateAutoTargets( int toggleState ); +}; + +LINK_ENTITY_TO_CLASS( func_trackautochange, CFuncTrackAuto ); + +// Auto track change +void CFuncTrackAuto :: UpdateAutoTargets( int toggleState ) +{ + CPathTrack *pTarget, *pNextTarget; + + if ( !m_trackTop || !m_trackBottom ) + return; + + if ( m_targetState == TS_AT_TOP ) + { + pTarget = m_trackTop->GetNext(); + pNextTarget = m_trackBottom->GetNext(); + } + else + { + pTarget = m_trackBottom->GetNext(); + pNextTarget = m_trackTop->GetNext(); + } + if ( pTarget ) + { + ClearBits( pTarget->pev->spawnflags, SF_PATH_DISABLED ); + if ( m_code == TRAIN_FOLLOWING && m_train && m_train->pev->speed == 0 ) + m_train->Use( this, this, USE_ON, 0 ); + } + + if ( pNextTarget ) + SetBits( pNextTarget->pev->spawnflags, SF_PATH_DISABLED ); + +} + + +void CFuncTrackAuto :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CPathTrack *pTarget; + + if ( !UseEnabled() ) + return; + + if ( m_toggle_state == TS_AT_TOP ) + pTarget = m_trackTop; + else if ( m_toggle_state == TS_AT_BOTTOM ) + pTarget = m_trackBottom; + else + pTarget = NULL; + + if ( FClassnameIs( pActivator->pev, "func_tracktrain" ) ) + { + m_code = EvaluateTrain( pTarget ); + // Safe to fire? + if ( m_code == TRAIN_FOLLOWING && m_toggle_state != m_targetState ) + { + DisableUse(); + if (m_toggle_state == TS_AT_TOP) + GoDown(); + else + GoUp(); + } + } + else + { + if ( pTarget ) + pTarget = pTarget->GetNext(); + if ( pTarget && m_train->m_ppath != pTarget && ShouldToggle( useType, m_targetState ) ) + { + if ( m_targetState == TS_AT_TOP ) + m_targetState = TS_AT_BOTTOM; + else + m_targetState = TS_AT_TOP; + } + + UpdateAutoTargets( m_targetState ); + } +} + + +// ---------------------------------------------------------- +// +// +// pev->speed is the travel speed +// pev->health is current health +// pev->max_health is the amount to reset to each time it starts + +#define FGUNTARGET_START_ON 0x0001 + +class CGunTarget : public CBaseMonster +{ +public: + void Spawn( void ); + void Activate( void ); + void EXPORT Next( void ); + void EXPORT Start( void ); + void EXPORT Wait( void ); + void Stop( void ); + + int BloodColor( void ) { return DONT_BLEED; } + int Classify( void ) { return CLASS_MACHINE; } + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + Vector BodyTarget( const Vector &posSrc ) { return pev->origin; } + + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + BOOL m_on; +}; + + +LINK_ENTITY_TO_CLASS( func_guntarget, CGunTarget ); + +TYPEDESCRIPTION CGunTarget::m_SaveData[] = +{ + DEFINE_FIELD( CGunTarget, m_on, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CGunTarget, CBaseMonster ); + + +void CGunTarget::Spawn( void ) +{ + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + if ( pev->speed == 0 ) + pev->speed = 100; + + // Don't take damage until "on" + pev->takedamage = DAMAGE_NO; + pev->flags |= FL_MONSTER; + + m_on = FALSE; + pev->max_health = pev->health; + + if ( pev->spawnflags & FGUNTARGET_START_ON ) + { + SetThink( &CGunTarget::Start ); + pev->nextthink = pev->ltime + 0.3; + } +} + + +void CGunTarget::Activate( void ) +{ + CBaseEntity *pTarg; + + // now find our next target + pTarg = GetNextTarget(); + if ( pTarg ) + { + m_hTargetEnt = pTarg; + UTIL_SetOrigin( pev, pTarg->pev->origin - (pev->mins + pev->maxs) * 0.5 ); + } +} + + +void CGunTarget::Start( void ) +{ + Use( this, this, USE_ON, 0 ); +} + + +void CGunTarget::Next( void ) +{ + SetThink( NULL ); + + m_hTargetEnt = GetNextTarget(); + CBaseEntity *pTarget = m_hTargetEnt; + + if ( !pTarget ) + { + Stop(); + return; + } + SetMoveDone( &CGunTarget::Wait ); + LinearMove( pTarget->pev->origin - (pev->mins + pev->maxs) * 0.5, pev->speed ); +} + + +void CGunTarget::Wait( void ) +{ + CBaseEntity *pTarget = m_hTargetEnt; + + if ( !pTarget ) + { + Stop(); + return; + } + + // Fire the pass target if there is one + if ( pTarget->pev->message ) + { + FireTargets( STRING(pTarget->pev->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( pTarget->pev->spawnflags, SF_CORNER_FIREONCE ) ) + pTarget->pev->message = 0; + } + + m_flWait = pTarget->GetDelay(); + + pev->target = pTarget->pev->target; + SetThink( &CGunTarget::Next ); + if (m_flWait != 0) + {// -1 wait will wait forever! + pev->nextthink = pev->ltime + m_flWait; + } + else + { + Next();// do it RIGHT now! + } +} + + +void CGunTarget::Stop( void ) +{ + pev->velocity = g_vecZero; + pev->nextthink = 0; + pev->takedamage = DAMAGE_NO; +} + + +int CGunTarget::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( pev->health > 0 ) + { + pev->health -= flDamage; + if ( pev->health <= 0 ) + { + pev->health = 0; + Stop(); + if ( pev->message ) + FireTargets( STRING(pev->message), this, this, USE_TOGGLE, 0 ); + } + } + return 0; +} + + +void CGunTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_on ) ) + return; + + if ( m_on ) + { + Stop(); + } + else + { + pev->takedamage = DAMAGE_AIM; + m_hTargetEnt = GetNextTarget(); + if ( m_hTargetEnt == NULL ) + return; + pev->health = pev->max_health; + Next(); + } +} + + + diff --git a/dlls/player.cpp b/dlls/player.cpp new file mode 100644 index 0000000..b3d9161 --- /dev/null +++ b/dlls/player.cpp @@ -0,0 +1,4885 @@ + /*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== player.cpp ======================================================== + + functions dealing with the player + +*/ + +#include "extdll.h" +#include "util.h" + +#include "cbase.h" +#include "player.h" +#include "trains.h" +#include "nodes.h" +#include "weapons.h" +#include "soundent.h" +#include "monsters.h" +#include "shake.h" +#include "decals.h" +#include "gamerules.h" +#include "game.h" +#include "pm_shared.h" +#include "hltv.h" + +// #define DUCKFIX + +extern DLL_GLOBAL ULONG g_ulModelIndexPlayer; +extern DLL_GLOBAL BOOL g_fGameOver; +extern DLL_GLOBAL BOOL g_fDrawLines; +int gEvilImpulse101; +extern DLL_GLOBAL int g_iSkillLevel, gDisplayTitle; + + +BOOL gInitHUD = TRUE; + +extern void CopyToBodyQue(entvars_t* pev); +extern void respawn(entvars_t *pev, BOOL fCopyCorpse); +extern Vector VecBModelOrigin(entvars_t *pevBModel ); +extern edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ); + +// the world node graph +extern CGraph WorldGraph; + +#define TRAIN_ACTIVE 0x80 +#define TRAIN_NEW 0xc0 +#define TRAIN_OFF 0x00 +#define TRAIN_NEUTRAL 0x01 +#define TRAIN_SLOW 0x02 +#define TRAIN_MEDIUM 0x03 +#define TRAIN_FAST 0x04 +#define TRAIN_BACK 0x05 + +#define FLASH_DRAIN_TIME 1.2 //100 units/3 minutes +#define FLASH_CHARGE_TIME 0.2 // 100 units/20 seconds (seconds per unit) + +// Global Savedata for player +TYPEDESCRIPTION CBasePlayer::m_playerSaveData[] = +{ + DEFINE_FIELD( CBasePlayer, m_flFlashLightTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_iFlashBattery, FIELD_INTEGER ), + + DEFINE_FIELD( CBasePlayer, m_afButtonLast, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_afButtonPressed, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_afButtonReleased, FIELD_INTEGER ), + + DEFINE_ARRAY( CBasePlayer, m_rgItems, FIELD_INTEGER, MAX_ITEMS ), + DEFINE_FIELD( CBasePlayer, m_afPhysicsFlags, FIELD_INTEGER ), + + DEFINE_FIELD( CBasePlayer, m_flTimeStepSound, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flTimeWeaponIdle, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flSwimTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flDuckTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flWallJumpTime, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_flSuitUpdate, FIELD_TIME ), + DEFINE_ARRAY( CBasePlayer, m_rgSuitPlayList, FIELD_INTEGER, CSUITPLAYLIST ), + DEFINE_FIELD( CBasePlayer, m_iSuitPlayNext, FIELD_INTEGER ), + DEFINE_ARRAY( CBasePlayer, m_rgiSuitNoRepeat, FIELD_INTEGER, CSUITNOREPEAT ), + DEFINE_ARRAY( CBasePlayer, m_rgflSuitNoRepeatTime, FIELD_TIME, CSUITNOREPEAT ), + DEFINE_FIELD( CBasePlayer, m_lastDamageAmount, FIELD_INTEGER ), + + DEFINE_ARRAY( CBasePlayer, m_rgpPlayerItems, FIELD_CLASSPTR, MAX_ITEM_TYPES ), + DEFINE_FIELD( CBasePlayer, m_pActiveItem, FIELD_CLASSPTR ), + DEFINE_FIELD( CBasePlayer, m_pLastItem, FIELD_CLASSPTR ), + + DEFINE_ARRAY( CBasePlayer, m_rgAmmo, FIELD_INTEGER, MAX_AMMO_SLOTS ), + DEFINE_FIELD( CBasePlayer, m_idrowndmg, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_idrownrestored, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_tSneaking, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_iTrain, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_bitsHUDDamage, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_flFallVelocity, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayer, m_iTargetVolume, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iWeaponVolume, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iExtraSoundTypes, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iWeaponFlash, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_fLongJump, FIELD_BOOLEAN ), + DEFINE_FIELD( CBasePlayer, m_fInitHUD, FIELD_BOOLEAN ), + DEFINE_FIELD( CBasePlayer, m_tbdPrev, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_pTank, FIELD_EHANDLE ), + DEFINE_FIELD( CBasePlayer, m_iHideHUD, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iFOV, FIELD_INTEGER ), + + //DEFINE_FIELD( CBasePlayer, m_fDeadTime, FIELD_FLOAT ), // only used in multiplayer games + //DEFINE_FIELD( CBasePlayer, m_fGameHUDInitialized, FIELD_INTEGER ), // only used in multiplayer games + //DEFINE_FIELD( CBasePlayer, m_flStopExtraSoundTime, FIELD_TIME ), + //DEFINE_FIELD( CBasePlayer, m_fKnownItem, FIELD_INTEGER ), // reset to zero on load + //DEFINE_FIELD( CBasePlayer, m_iPlayerSound, FIELD_INTEGER ), // Don't restore, set in Precache() + //DEFINE_FIELD( CBasePlayer, m_pentSndLast, FIELD_EDICT ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_flSndRoomtype, FIELD_FLOAT ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_flSndRange, FIELD_FLOAT ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_fNewAmmo, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_flgeigerRange, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_flgeigerDelay, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_igeigerRangePrev, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_iStepLeft, FIELD_INTEGER ), // Don't need to restore + //DEFINE_ARRAY( CBasePlayer, m_szTextureName, FIELD_CHARACTER, CBTEXTURENAMEMAX ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_chTextureType, FIELD_CHARACTER ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_fNoPlayerSound, FIELD_BOOLEAN ), // Don't need to restore, debug + //DEFINE_FIELD( CBasePlayer, m_iUpdateTime, FIELD_INTEGER ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_iClientHealth, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_iClientBattery, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_iClientHideHUD, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_fWeapon, FIELD_BOOLEAN ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_nCustomSprayFrames, FIELD_INTEGER ), // Don't restore, depends on server message after spawning and only matters in multiplayer + //DEFINE_FIELD( CBasePlayer, m_vecAutoAim, FIELD_VECTOR ), // Don't save/restore - this is recomputed + //DEFINE_ARRAY( CBasePlayer, m_rgAmmoLast, FIELD_INTEGER, MAX_AMMO_SLOTS ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_fOnTarget, FIELD_BOOLEAN ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_nCustomSprayFrames, FIELD_INTEGER ), // Don't need to restore + +}; + + +int giPrecacheGrunt = 0; +int gmsgShake = 0; +int gmsgFade = 0; +int gmsgSelAmmo = 0; +int gmsgFlashlight = 0; +int gmsgFlashBattery = 0; +int gmsgResetHUD = 0; +int gmsgInitHUD = 0; +int gmsgShowGameTitle = 0; +int gmsgCurWeapon = 0; +int gmsgHealth = 0; +int gmsgDamage = 0; +int gmsgBattery = 0; +int gmsgTrain = 0; +int gmsgLogo = 0; +int gmsgWeaponList = 0; +int gmsgAmmoX = 0; +int gmsgHudText = 0; +int gmsgDeathMsg = 0; +int gmsgScoreInfo = 0; +int gmsgTeamInfo = 0; +int gmsgTeamScore = 0; +int gmsgGameMode = 0; +int gmsgMOTD = 0; +int gmsgServerName = 0; +int gmsgAmmoPickup = 0; +int gmsgWeapPickup = 0; +int gmsgItemPickup = 0; +int gmsgHideWeapon = 0; +int gmsgSetCurWeap = 0; +int gmsgSayText = 0; +int gmsgTextMsg = 0; +int gmsgSetFOV = 0; +int gmsgShowMenu = 0; +int gmsgGeigerRange = 0; +int gmsgTeamNames = 0; + +int gmsgStatusText = 0; +int gmsgStatusValue = 0; + + + +void LinkUserMessages( void ) +{ + // Already taken care of? + if ( gmsgSelAmmo ) + { + return; + } + + gmsgSelAmmo = REG_USER_MSG("SelAmmo", sizeof(SelAmmo)); + gmsgCurWeapon = REG_USER_MSG("CurWeapon", 3); + gmsgGeigerRange = REG_USER_MSG("Geiger", 1); + gmsgFlashlight = REG_USER_MSG("Flashlight", 2); + gmsgFlashBattery = REG_USER_MSG("FlashBat", 1); + gmsgHealth = REG_USER_MSG( "Health", 1 ); + gmsgDamage = REG_USER_MSG( "Damage", 12 ); + gmsgBattery = REG_USER_MSG( "Battery", 2); + gmsgTrain = REG_USER_MSG( "Train", 1); + //gmsgHudText = REG_USER_MSG( "HudTextPro", -1 ); + gmsgHudText = REG_USER_MSG( "HudText", -1 ); // we don't use the message but 3rd party addons may! + gmsgSayText = REG_USER_MSG( "SayText", -1 ); + gmsgTextMsg = REG_USER_MSG( "TextMsg", -1 ); + gmsgWeaponList = REG_USER_MSG("WeaponList", -1); + gmsgResetHUD = REG_USER_MSG("ResetHUD", 1); // called every respawn + gmsgInitHUD = REG_USER_MSG("InitHUD", 0 ); // called every time a new player joins the server + gmsgShowGameTitle = REG_USER_MSG("GameTitle", 1); + gmsgDeathMsg = REG_USER_MSG( "DeathMsg", -1 ); + gmsgScoreInfo = REG_USER_MSG( "ScoreInfo", 9 ); + gmsgTeamInfo = REG_USER_MSG( "TeamInfo", -1 ); // sets the name of a player's team + gmsgTeamScore = REG_USER_MSG( "TeamScore", -1 ); // sets the score of a team on the scoreboard + gmsgGameMode = REG_USER_MSG( "GameMode", 1 ); + gmsgMOTD = REG_USER_MSG( "MOTD", -1 ); + gmsgServerName = REG_USER_MSG( "ServerName", -1 ); + gmsgAmmoPickup = REG_USER_MSG( "AmmoPickup", 2 ); + gmsgWeapPickup = REG_USER_MSG( "WeapPickup", 1 ); + gmsgItemPickup = REG_USER_MSG( "ItemPickup", -1 ); + gmsgHideWeapon = REG_USER_MSG( "HideWeapon", 1 ); + gmsgSetFOV = REG_USER_MSG( "SetFOV", 1 ); + gmsgShowMenu = REG_USER_MSG( "ShowMenu", -1 ); + gmsgShake = REG_USER_MSG("ScreenShake", sizeof(ScreenShake)); + gmsgFade = REG_USER_MSG("ScreenFade", sizeof(ScreenFade)); + gmsgAmmoX = REG_USER_MSG("AmmoX", 2); + gmsgTeamNames = REG_USER_MSG( "TeamNames", -1 ); + + gmsgStatusText = REG_USER_MSG("StatusText", -1); + gmsgStatusValue = REG_USER_MSG("StatusValue", 3); + +} + +LINK_ENTITY_TO_CLASS( player, CBasePlayer ); + + + +void CBasePlayer :: Pain( void ) +{ + float flRndSound;//sound randomizer + + flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.33 ) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain5.wav", 1, ATTN_NORM); + else if ( flRndSound <= 0.66 ) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain6.wav", 1, ATTN_NORM); + else + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain7.wav", 1, ATTN_NORM); +} + +/* + * + */ +Vector VecVelocityForDamage(float flDamage) +{ + Vector vec(RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + + if (flDamage > -50) + vec = vec * 0.7; + else if (flDamage > -200) + vec = vec * 2; + else + vec = vec * 10; + + return vec; +} + +#if 0 /* +static void ThrowGib(entvars_t *pev, char *szGibModel, float flDamage) +{ + edict_t *pentNew = CREATE_ENTITY(); + entvars_t *pevNew = VARS(pentNew); + + pevNew->origin = pev->origin; + SET_MODEL(ENT(pevNew), szGibModel); + UTIL_SetSize(pevNew, g_vecZero, g_vecZero); + + pevNew->velocity = VecVelocityForDamage(flDamage); + pevNew->movetype = MOVETYPE_BOUNCE; + pevNew->solid = SOLID_NOT; + pevNew->avelocity.x = RANDOM_FLOAT(0,600); + pevNew->avelocity.y = RANDOM_FLOAT(0,600); + pevNew->avelocity.z = RANDOM_FLOAT(0,600); + CHANGE_METHOD(ENT(pevNew), em_think, SUB_Remove); + pevNew->ltime = gpGlobals->time; + pevNew->nextthink = gpGlobals->time + RANDOM_FLOAT(10,20); + pevNew->frame = 0; + pevNew->flags = 0; +} + + +static void ThrowHead(entvars_t *pev, char *szGibModel, floatflDamage) +{ + SET_MODEL(ENT(pev), szGibModel); + pev->frame = 0; + pev->nextthink = -1; + pev->movetype = MOVETYPE_BOUNCE; + pev->takedamage = DAMAGE_NO; + pev->solid = SOLID_NOT; + pev->view_ofs = Vector(0,0,8); + UTIL_SetSize(pev, Vector(-16,-16,0), Vector(16,16,56)); + pev->velocity = VecVelocityForDamage(flDamage); + pev->avelocity = RANDOM_FLOAT(-1,1) * Vector(0,600,0); + pev->origin.z -= 24; + ClearBits(pev->flags, FL_ONGROUND); +} + + +*/ +#endif + +int TrainSpeed(int iSpeed, int iMax) +{ + float fSpeed, fMax; + int iRet = 0; + + fMax = (float)iMax; + fSpeed = iSpeed; + + fSpeed = fSpeed/fMax; + + if (iSpeed < 0) + iRet = TRAIN_BACK; + else if (iSpeed == 0) + iRet = TRAIN_NEUTRAL; + else if (fSpeed < 0.33) + iRet = TRAIN_SLOW; + else if (fSpeed < 0.66) + iRet = TRAIN_MEDIUM; + else + iRet = TRAIN_FAST; + + return iRet; +} + +void CBasePlayer :: DeathSound( void ) +{ + // water death sounds + /* + if (pev->waterlevel == 3) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/h2odeath.wav", 1, ATTN_NONE); + return; + } + */ + + // temporarily using pain sounds for death sounds + switch (RANDOM_LONG(1,5)) + { + case 1: + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain5.wav", 1, ATTN_NORM); + break; + case 2: + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain6.wav", 1, ATTN_NORM); + break; + case 3: + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain7.wav", 1, ATTN_NORM); + break; + } + + // play one of the suit death alarms + EMIT_GROUPNAME_SUIT(ENT(pev), "HEV_DEAD"); +} + +// override takehealth +// bitsDamageType indicates type of damage healed. + +int CBasePlayer :: TakeHealth( float flHealth, int bitsDamageType ) +{ + return CBaseMonster :: TakeHealth (flHealth, bitsDamageType); + +} + +Vector CBasePlayer :: GetGunPosition( ) +{ +// UTIL_MakeVectors(pev->v_angle); +// m_HackedGunPos = pev->view_ofs; + Vector origin; + + origin = pev->origin + pev->view_ofs; + + return origin; +} + +//========================================================= +// TraceAttack +//========================================================= +void CBasePlayer :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( pev->takedamage ) + { + m_LastHitGroup = ptr->iHitgroup; + + switch ( ptr->iHitgroup ) + { + case HITGROUP_GENERIC: + break; + case HITGROUP_HEAD: + flDamage *= gSkillData.plrHead; + break; + case HITGROUP_CHEST: + flDamage *= gSkillData.plrChest; + break; + case HITGROUP_STOMACH: + flDamage *= gSkillData.plrStomach; + break; + case HITGROUP_LEFTARM: + case HITGROUP_RIGHTARM: + flDamage *= gSkillData.plrArm; + break; + case HITGROUP_LEFTLEG: + case HITGROUP_RIGHTLEG: + flDamage *= gSkillData.plrLeg; + break; + default: + break; + } + + SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage);// a little surface blood. + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + } +} + +/* + Take some damage. + NOTE: each call to TakeDamage with bitsDamageType set to a time-based damage + type will cause the damage time countdown to be reset. Thus the ongoing effects of poison, radiation + etc are implemented with subsequent calls to TakeDamage using DMG_GENERIC. +*/ + +#define ARMOR_RATIO 0.2 // Armor Takes 80% of the damage +#define ARMOR_BONUS 0.5 // Each Point of Armor is work 1/x points of health + +int CBasePlayer :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // have suit diagnose the problem - ie: report damage type + int bitsDamage = bitsDamageType; + int ffound = TRUE; + int fmajor; + int fcritical; + int fTookDamage; + int ftrivial; + float flRatio; + float flBonus; + float flHealthPrev = pev->health; + + flBonus = ARMOR_BONUS; + flRatio = ARMOR_RATIO; + + if ( ( bitsDamageType & DMG_BLAST ) && g_pGameRules->IsMultiplayer() ) + { + // blasts damage armor more. + flBonus *= 2; + } + + // Already dead + if ( !IsAlive() ) + return 0; + // go take the damage first + + + CBaseEntity *pAttacker = CBaseEntity::Instance(pevAttacker); + + if ( !g_pGameRules->FPlayerCanTakeDamage( this, pAttacker ) ) + { + // Refuse the damage + return 0; + } + + // keep track of amount of damage last sustained + m_lastDamageAmount = flDamage; + + // Armor. + if (pev->armorvalue && !(bitsDamageType & (DMG_FALL | DMG_DROWN)) )// armor doesn't protect against fall or drown damage! + { + float flNew = flDamage * flRatio; + + float flArmor; + + flArmor = (flDamage - flNew) * flBonus; + + // Does this use more armor than we have? + if (flArmor > pev->armorvalue) + { + flArmor = pev->armorvalue; + flArmor *= (1/flBonus); + flNew = flDamage - flArmor; + pev->armorvalue = 0; + } + else + pev->armorvalue -= flArmor; + + flDamage = flNew; + } + + // this cast to INT is critical!!! If a player ends up with 0.5 health, the engine will get that + // as an int (zero) and think the player is dead! (this will incite a clientside screentilt, etc) + fTookDamage = CBaseMonster::TakeDamage(pevInflictor, pevAttacker, (int)flDamage, bitsDamageType); + + // reset damage time countdown for each type of time based damage player just sustained + + { + for (int i = 0; i < CDMG_TIMEBASED; i++) + if (bitsDamageType & (DMG_PARALYZE << i)) + m_rgbTimeBasedDamage[i] = 0; + } + + // tell director about it + MESSAGE_BEGIN( MSG_SPEC, SVC_DIRECTOR ); + WRITE_BYTE ( 9 ); // command length in bytes + WRITE_BYTE ( DRC_CMD_EVENT ); // take damage event + WRITE_SHORT( ENTINDEX(this->edict()) ); // index number of primary entity + WRITE_SHORT( ENTINDEX(ENT(pevInflictor)) ); // index number of secondary entity + WRITE_LONG( 5 ); // eventflags (priority and flags) + MESSAGE_END(); + + + // how bad is it, doc? + + ftrivial = (pev->health > 75 || m_lastDamageAmount < 5); + fmajor = (m_lastDamageAmount > 25); + fcritical = (pev->health < 30); + + // handle all bits set in this damage message, + // let the suit give player the diagnosis + + // UNDONE: add sounds for types of damage sustained (ie: burn, shock, slash ) + + // UNDONE: still need to record damage and heal messages for the following types + + // DMG_BURN + // DMG_FREEZE + // DMG_BLAST + // DMG_SHOCK + + m_bitsDamageType |= bitsDamage; // Save this so we can report it to the client + m_bitsHUDDamage = -1; // make sure the damage bits get resent + + while (fTookDamage && (!ftrivial || (bitsDamage & DMG_TIMEBASED)) && ffound && bitsDamage) + { + ffound = FALSE; + + if (bitsDamage & DMG_CLUB) + { + if (fmajor) + SetSuitUpdate("!HEV_DMG4", FALSE, SUIT_NEXT_IN_30SEC); // minor fracture + bitsDamage &= ~DMG_CLUB; + ffound = TRUE; + } + if (bitsDamage & (DMG_FALL | DMG_CRUSH)) + { + if (fmajor) + SetSuitUpdate("!HEV_DMG5", FALSE, SUIT_NEXT_IN_30SEC); // major fracture + else + SetSuitUpdate("!HEV_DMG4", FALSE, SUIT_NEXT_IN_30SEC); // minor fracture + + bitsDamage &= ~(DMG_FALL | DMG_CRUSH); + ffound = TRUE; + } + + if (bitsDamage & DMG_BULLET) + { + if (m_lastDamageAmount > 5) + SetSuitUpdate("!HEV_DMG6", FALSE, SUIT_NEXT_IN_30SEC); // blood loss detected + //else + // SetSuitUpdate("!HEV_DMG0", FALSE, SUIT_NEXT_IN_30SEC); // minor laceration + + bitsDamage &= ~DMG_BULLET; + ffound = TRUE; + } + + if (bitsDamage & DMG_SLASH) + { + if (fmajor) + SetSuitUpdate("!HEV_DMG1", FALSE, SUIT_NEXT_IN_30SEC); // major laceration + else + SetSuitUpdate("!HEV_DMG0", FALSE, SUIT_NEXT_IN_30SEC); // minor laceration + + bitsDamage &= ~DMG_SLASH; + ffound = TRUE; + } + + if (bitsDamage & DMG_SONIC) + { + if (fmajor) + SetSuitUpdate("!HEV_DMG2", FALSE, SUIT_NEXT_IN_1MIN); // internal bleeding + bitsDamage &= ~DMG_SONIC; + ffound = TRUE; + } + + if (bitsDamage & (DMG_POISON | DMG_PARALYZE)) + { + SetSuitUpdate("!HEV_DMG3", FALSE, SUIT_NEXT_IN_1MIN); // blood toxins detected + bitsDamage &= ~(DMG_POISON | DMG_PARALYZE); + ffound = TRUE; + } + + if (bitsDamage & DMG_ACID) + { + SetSuitUpdate("!HEV_DET1", FALSE, SUIT_NEXT_IN_1MIN); // hazardous chemicals detected + bitsDamage &= ~DMG_ACID; + ffound = TRUE; + } + + if (bitsDamage & DMG_NERVEGAS) + { + SetSuitUpdate("!HEV_DET0", FALSE, SUIT_NEXT_IN_1MIN); // biohazard detected + bitsDamage &= ~DMG_NERVEGAS; + ffound = TRUE; + } + + if (bitsDamage & DMG_RADIATION) + { + SetSuitUpdate("!HEV_DET2", FALSE, SUIT_NEXT_IN_1MIN); // radiation detected + bitsDamage &= ~DMG_RADIATION; + ffound = TRUE; + } + if (bitsDamage & DMG_SHOCK) + { + bitsDamage &= ~DMG_SHOCK; + ffound = TRUE; + } + } + + pev->punchangle.x = -2; + + if (fTookDamage && !ftrivial && fmajor && flHealthPrev >= 75) + { + // first time we take major damage... + // turn automedic on if not on + SetSuitUpdate("!HEV_MED1", FALSE, SUIT_NEXT_IN_30MIN); // automedic on + + // give morphine shot if not given recently + SetSuitUpdate("!HEV_HEAL7", FALSE, SUIT_NEXT_IN_30MIN); // morphine shot + } + + if (fTookDamage && !ftrivial && fcritical && flHealthPrev < 75) + { + + // already took major damage, now it's critical... + if (pev->health < 6) + SetSuitUpdate("!HEV_HLTH3", FALSE, SUIT_NEXT_IN_10MIN); // near death + else if (pev->health < 20) + SetSuitUpdate("!HEV_HLTH2", FALSE, SUIT_NEXT_IN_10MIN); // health critical + + // give critical health warnings + if (!RANDOM_LONG(0,3) && flHealthPrev < 50) + SetSuitUpdate("!HEV_DMG7", FALSE, SUIT_NEXT_IN_5MIN); //seek medical attention + } + + // if we're taking time based damage, warn about its continuing effects + if (fTookDamage && (bitsDamageType & DMG_TIMEBASED) && flHealthPrev < 75) + { + if (flHealthPrev < 50) + { + if (!RANDOM_LONG(0,3)) + SetSuitUpdate("!HEV_DMG7", FALSE, SUIT_NEXT_IN_5MIN); //seek medical attention + } + else + SetSuitUpdate("!HEV_HLTH1", FALSE, SUIT_NEXT_IN_10MIN); // health dropping + } + + return fTookDamage; +} + +//========================================================= +// PackDeadPlayerItems - call this when a player dies to +// pack up the appropriate weapons and ammo items, and to +// destroy anything that shouldn't be packed. +// +// This is pretty brute force :( +//========================================================= +void CBasePlayer::PackDeadPlayerItems( void ) +{ + int iWeaponRules; + int iAmmoRules; + int i; + CBasePlayerWeapon *rgpPackWeapons[ 20 ];// 20 hardcoded for now. How to determine exactly how many weapons we have? + int iPackAmmo[ MAX_AMMO_SLOTS + 1]; + int iPW = 0;// index into packweapons array + int iPA = 0;// index into packammo array + + memset(rgpPackWeapons, 0, sizeof(rgpPackWeapons) ); + memset(iPackAmmo, -1, sizeof(iPackAmmo) ); + + // get the game rules + iWeaponRules = g_pGameRules->DeadPlayerWeapons( this ); + iAmmoRules = g_pGameRules->DeadPlayerAmmo( this ); + + if ( iWeaponRules == GR_PLR_DROP_GUN_NO && iAmmoRules == GR_PLR_DROP_AMMO_NO ) + { + // nothing to pack. Remove the weapons and return. Don't call create on the box! + RemoveAllItems( TRUE ); + return; + } + +// go through all of the weapons and make a list of the ones to pack + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + // there's a weapon here. Should I pack it? + CBasePlayerItem *pPlayerItem = m_rgpPlayerItems[ i ]; + + while ( pPlayerItem ) + { + switch( iWeaponRules ) + { + case GR_PLR_DROP_GUN_ACTIVE: + if ( m_pActiveItem && pPlayerItem == m_pActiveItem ) + { + // this is the active item. Pack it. + rgpPackWeapons[ iPW++ ] = (CBasePlayerWeapon *)pPlayerItem; + } + break; + + case GR_PLR_DROP_GUN_ALL: + rgpPackWeapons[ iPW++ ] = (CBasePlayerWeapon *)pPlayerItem; + break; + + default: + break; + } + + pPlayerItem = pPlayerItem->m_pNext; + } + } + } + +// now go through ammo and make a list of which types to pack. + if ( iAmmoRules != GR_PLR_DROP_AMMO_NO ) + { + for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ ) + { + if ( m_rgAmmo[ i ] > 0 ) + { + // player has some ammo of this type. + switch ( iAmmoRules ) + { + case GR_PLR_DROP_AMMO_ALL: + iPackAmmo[ iPA++ ] = i; + break; + + case GR_PLR_DROP_AMMO_ACTIVE: + if ( m_pActiveItem && i == m_pActiveItem->PrimaryAmmoIndex() ) + { + // this is the primary ammo type for the active weapon + iPackAmmo[ iPA++ ] = i; + } + else if ( m_pActiveItem && i == m_pActiveItem->SecondaryAmmoIndex() ) + { + // this is the secondary ammo type for the active weapon + iPackAmmo[ iPA++ ] = i; + } + break; + + default: + break; + } + } + } + } + +// create a box to pack the stuff into. + CWeaponBox *pWeaponBox = (CWeaponBox *)CBaseEntity::Create( "weaponbox", pev->origin, pev->angles, edict() ); + + pWeaponBox->pev->angles.x = 0;// don't let weaponbox tilt. + pWeaponBox->pev->angles.z = 0; + + pWeaponBox->SetThink( &CWeaponBox::Kill ); + pWeaponBox->pev->nextthink = gpGlobals->time + 120; + +// back these two lists up to their first elements + iPA = 0; + iPW = 0; + +// pack the ammo + while ( iPackAmmo[ iPA ] != -1 ) + { + pWeaponBox->PackAmmo( MAKE_STRING( CBasePlayerItem::AmmoInfoArray[ iPackAmmo[ iPA ] ].pszName ), m_rgAmmo[ iPackAmmo[ iPA ] ] ); + iPA++; + } + +// now pack all of the items in the lists + while ( rgpPackWeapons[ iPW ] ) + { + // weapon unhooked from the player. Pack it into der box. + pWeaponBox->PackWeapon( rgpPackWeapons[ iPW ] ); + + iPW++; + } + + pWeaponBox->pev->velocity = pev->velocity * 1.2;// weaponbox has player's velocity, then some. + + RemoveAllItems( TRUE );// now strip off everything that wasn't handled by the code above. +} + +void CBasePlayer::RemoveAllItems( BOOL removeSuit ) +{ + if (m_pActiveItem) + { + ResetAutoaim( ); + m_pActiveItem->Holster( ); + m_pActiveItem = NULL; + } + + m_pLastItem = NULL; + + if ( m_pTank != NULL ) + { + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + } + + int i; + CBasePlayerItem *pPendingItem; + for (i = 0; i < MAX_ITEM_TYPES; i++) + { + m_pActiveItem = m_rgpPlayerItems[i]; + while (m_pActiveItem) + { + pPendingItem = m_pActiveItem->m_pNext; + m_pActiveItem->Drop( ); + m_pActiveItem = pPendingItem; + } + m_rgpPlayerItems[i] = NULL; + } + m_pActiveItem = NULL; + + pev->viewmodel = 0; + pev->weaponmodel = 0; + + if ( removeSuit ) + pev->weapons = 0; + else + pev->weapons &= ~WEAPON_ALLWEAPONS; + + for ( i = 0; i < MAX_AMMO_SLOTS;i++) + m_rgAmmo[i] = 0; + + UpdateClientData(); + // send Selected Weapon Message to our client + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(0); + WRITE_BYTE(0); + MESSAGE_END(); +} + +/* + * GLOBALS ASSUMED SET: g_ulModelIndexPlayer + * + * ENTITY_METHOD(PlayerDie) + */ +entvars_t *g_pevLastInflictor; // Set in combat.cpp. Used to pass the damage inflictor for death messages. + // Better solution: Add as parameter to all Killed() functions. + +void CBasePlayer::Killed( entvars_t *pevAttacker, int iGib ) +{ + CSound *pSound; + + // Holster weapon immediately, to allow it to cleanup + if ( m_pActiveItem ) + m_pActiveItem->Holster( ); + + g_pGameRules->PlayerKilled( this, pevAttacker, g_pevLastInflictor ); + + if ( m_pTank != NULL ) + { + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + } + + // this client isn't going to be thinking for a while, so reset the sound until they respawn + pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( edict() ) ); + { + if ( pSound ) + { + pSound->Reset(); + } + } + + SetAnimation( PLAYER_DIE ); + + m_iRespawnFrames = 0; + + pev->modelindex = g_ulModelIndexPlayer; // don't use eyes + + pev->deadflag = DEAD_DYING; + pev->movetype = MOVETYPE_TOSS; + ClearBits( pev->flags, FL_ONGROUND ); + if (pev->velocity.z < 10) + pev->velocity.z += RANDOM_FLOAT(0,300); + + // clear out the suit message cache so we don't keep chattering + SetSuitUpdate(NULL, FALSE, 0); + + // send "health" update message to zero + m_iClientHealth = 0; + MESSAGE_BEGIN( MSG_ONE, gmsgHealth, NULL, pev ); + WRITE_BYTE( m_iClientHealth ); + MESSAGE_END(); + + // Tell Ammo Hud that the player is dead + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(0XFF); + WRITE_BYTE(0xFF); + MESSAGE_END(); + + // reset FOV + pev->fov = m_iFOV = m_iClientFOV = 0; + + MESSAGE_BEGIN( MSG_ONE, gmsgSetFOV, NULL, pev ); + WRITE_BYTE(0); + MESSAGE_END(); + + + // UNDONE: Put this in, but add FFADE_PERMANENT and make fade time 8.8 instead of 4.12 + // UTIL_ScreenFade( edict(), Vector(128,0,0), 6, 15, 255, FFADE_OUT | FFADE_MODULATE ); + + if ( ( pev->health < -40 && iGib != GIB_NEVER ) || iGib == GIB_ALWAYS ) + { + pev->solid = SOLID_NOT; + GibMonster(); // This clears pev->model + pev->effects |= EF_NODRAW; + return; + } + + DeathSound(); + + pev->angles.x = 0; + pev->angles.z = 0; + + SetThink(&CBasePlayer::PlayerDeathThink); + pev->nextthink = gpGlobals->time + 0.1; +} + + +// Set the activity based on an event or current state +void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) +{ + int animDesired; + float speed; + char szAnim[64]; + + speed = pev->velocity.Length2D(); + + if (pev->flags & FL_FROZEN) + { + speed = 0; + playerAnim = PLAYER_IDLE; + } + + switch (playerAnim) + { + case PLAYER_JUMP: + m_IdealActivity = ACT_HOP; + break; + + case PLAYER_SUPERJUMP: + m_IdealActivity = ACT_LEAP; + break; + + case PLAYER_DIE: + m_IdealActivity = ACT_DIESIMPLE; + m_IdealActivity = GetDeathActivity( ); + break; + + case PLAYER_ATTACK1: + switch( m_Activity ) + { + case ACT_HOVER: + case ACT_SWIM: + case ACT_HOP: + case ACT_LEAP: + case ACT_DIESIMPLE: + m_IdealActivity = m_Activity; + break; + default: + m_IdealActivity = ACT_RANGE_ATTACK1; + break; + } + break; + case PLAYER_IDLE: + case PLAYER_WALK: + if ( !FBitSet( pev->flags, FL_ONGROUND ) && (m_Activity == ACT_HOP || m_Activity == ACT_LEAP) ) // Still jumping + { + m_IdealActivity = m_Activity; + } + else if ( pev->waterlevel > 1 ) + { + if ( speed == 0 ) + m_IdealActivity = ACT_HOVER; + else + m_IdealActivity = ACT_SWIM; + } + else + { + m_IdealActivity = ACT_WALK; + } + break; + } + + switch (m_IdealActivity) + { + case ACT_HOVER: + case ACT_LEAP: + case ACT_SWIM: + case ACT_HOP: + case ACT_DIESIMPLE: + default: + if ( m_Activity == m_IdealActivity) + return; + m_Activity = m_IdealActivity; + + animDesired = LookupActivity( m_Activity ); + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + pev->gaitsequence = 0; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + + case ACT_RANGE_ATTACK1: + if ( FBitSet( pev->flags, FL_DUCKING ) ) // crouching + strcpy( szAnim, "crouch_shoot_" ); + else + strcpy( szAnim, "ref_shoot_" ); + strcat( szAnim, m_szAnimExtention ); + animDesired = LookupSequence( szAnim ); + if (animDesired == -1) + animDesired = 0; + + if ( pev->sequence != animDesired || !m_fSequenceLoops ) + { + pev->frame = 0; + } + + if (!m_fSequenceLoops) + { + pev->effects |= EF_NOINTERP; + } + + m_Activity = m_IdealActivity; + + pev->sequence = animDesired; + ResetSequenceInfo( ); + break; + + case ACT_WALK: + if (m_Activity != ACT_RANGE_ATTACK1 || m_fSequenceFinished) + { + if ( FBitSet( pev->flags, FL_DUCKING ) ) // crouching + strcpy( szAnim, "crouch_aim_" ); + else + strcpy( szAnim, "ref_aim_" ); + strcat( szAnim, m_szAnimExtention ); + animDesired = LookupSequence( szAnim ); + if (animDesired == -1) + animDesired = 0; + m_Activity = ACT_WALK; + } + else + { + animDesired = pev->sequence; + } + } + + if ( FBitSet( pev->flags, FL_DUCKING ) ) + { + if ( speed == 0) + { + pev->gaitsequence = LookupActivity( ACT_CROUCHIDLE ); + // pev->gaitsequence = LookupActivity( ACT_CROUCH ); + } + else + { + pev->gaitsequence = LookupActivity( ACT_CROUCH ); + } + } + else if ( speed > 220 ) + { + pev->gaitsequence = LookupActivity( ACT_RUN ); + } + else if (speed > 0) + { + pev->gaitsequence = LookupActivity( ACT_WALK ); + } + else + { + // pev->gaitsequence = LookupActivity( ACT_WALK ); + pev->gaitsequence = LookupSequence( "deep_idle" ); + } + + + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + //ALERT( at_console, "Set animation to %d\n", animDesired ); + // Reset to first frame of desired animation + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); +} + +/* +=========== +TabulateAmmo +This function is used to find and store +all the ammo we have into the ammo vars. +============ +*/ +void CBasePlayer::TabulateAmmo() +{ + ammo_9mm = AmmoInventory( GetAmmoIndex( "9mm" ) ); + ammo_357 = AmmoInventory( GetAmmoIndex( "357" ) ); + ammo_argrens = AmmoInventory( GetAmmoIndex( "ARgrenades" ) ); + ammo_bolts = AmmoInventory( GetAmmoIndex( "bolts" ) ); + ammo_buckshot = AmmoInventory( GetAmmoIndex( "buckshot" ) ); + ammo_rockets = AmmoInventory( GetAmmoIndex( "rockets" ) ); + ammo_uranium = AmmoInventory( GetAmmoIndex( "uranium" ) ); + ammo_hornets = AmmoInventory( GetAmmoIndex( "Hornets" ) ); +} + + +/* +=========== +WaterMove +============ +*/ +#define AIRTIME 12 // lung full of air lasts this many seconds + +void CBasePlayer::WaterMove() +{ + int air; + + if (pev->movetype == MOVETYPE_NOCLIP) + return; + + if (pev->health < 0) + return; + + // waterlevel 0 - not in water + // waterlevel 1 - feet in water + // waterlevel 2 - waist in water + // waterlevel 3 - head in water + + if (pev->waterlevel != 3) + { + // not underwater + + // play 'up for air' sound + if (pev->air_finished < gpGlobals->time) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_wade1.wav", 1, ATTN_NORM); + else if (pev->air_finished < gpGlobals->time + 9) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_wade2.wav", 1, ATTN_NORM); + + pev->air_finished = gpGlobals->time + AIRTIME; + pev->dmg = 2; + + // if we took drowning damage, give it back slowly + if (m_idrowndmg > m_idrownrestored) + { + // set drowning damage bit. hack - dmg_drownrecover actually + // makes the time based damage code 'give back' health over time. + // make sure counter is cleared so we start count correctly. + + // NOTE: this actually causes the count to continue restarting + // until all drowning damage is healed. + + m_bitsDamageType |= DMG_DROWNRECOVER; + m_bitsDamageType &= ~DMG_DROWN; + m_rgbTimeBasedDamage[itbd_DrownRecover] = 0; + } + + } + else + { // fully under water + // stop restoring damage while underwater + m_bitsDamageType &= ~DMG_DROWNRECOVER; + m_rgbTimeBasedDamage[itbd_DrownRecover] = 0; + + if (pev->air_finished < gpGlobals->time) // drown! + { + if (pev->pain_finished < gpGlobals->time) + { + // take drowning damage + pev->dmg += 1; + if (pev->dmg > 5) + pev->dmg = 5; + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), pev->dmg, DMG_DROWN); + pev->pain_finished = gpGlobals->time + 1; + + // track drowning damage, give it back when + // player finally takes a breath + + m_idrowndmg += pev->dmg; + } + } + else + { + m_bitsDamageType &= ~DMG_DROWN; + } + } + + if (!pev->waterlevel) + { + if (FBitSet(pev->flags, FL_INWATER)) + { + ClearBits(pev->flags, FL_INWATER); + } + return; + } + + // make bubbles + + air = (int)(pev->air_finished - gpGlobals->time); + if (!RANDOM_LONG(0,0x1f) && RANDOM_LONG(0,AIRTIME-1) >= air) + { + switch (RANDOM_LONG(0,3)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim1.wav", 0.8, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim2.wav", 0.8, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim3.wav", 0.8, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim4.wav", 0.8, ATTN_NORM); break; + } + } + + if (pev->watertype == CONTENT_LAVA) // do damage + { + if (pev->dmgtime < gpGlobals->time) + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), 10 * pev->waterlevel, DMG_BURN); + } + else if (pev->watertype == CONTENT_SLIME) // do damage + { + pev->dmgtime = gpGlobals->time + 1; + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), 4 * pev->waterlevel, DMG_ACID); + } + + if (!FBitSet(pev->flags, FL_INWATER)) + { + SetBits(pev->flags, FL_INWATER); + pev->dmgtime = 0; + } +} + + +// TRUE if the player is attached to a ladder +BOOL CBasePlayer::IsOnLadder( void ) +{ + return ( pev->movetype == MOVETYPE_FLY ); +} + +void CBasePlayer::PlayerDeathThink(void) +{ + float flForward; + + if (FBitSet(pev->flags, FL_ONGROUND)) + { + flForward = pev->velocity.Length() - 20; + if (flForward <= 0) + pev->velocity = g_vecZero; + else + pev->velocity = flForward * pev->velocity.Normalize(); + } + + if ( HasWeapons() ) + { + // we drop the guns here because weapons that have an area effect and can kill their user + // will sometimes crash coming back from CBasePlayer::Killed() if they kill their owner because the + // player class sometimes is freed. It's safer to manipulate the weapons once we know + // we aren't calling into any of their code anymore through the player pointer. + PackDeadPlayerItems(); + } + + + if (pev->modelindex && (!m_fSequenceFinished) && (pev->deadflag == DEAD_DYING)) + { + StudioFrameAdvance( ); + + m_iRespawnFrames++; // Note, these aren't necessarily real "frames", so behavior is dependent on # of client movement commands + if ( m_iRespawnFrames < 120 ) // Animations should be no longer than this + return; + } + + // once we're done animating our death and we're on the ground, we want to set movetype to None so our dead body won't do collisions and stuff anymore + // this prevents a bug where the dead body would go to a player's head if he walked over it while the dead player was clicking their button to respawn + if ( pev->movetype != MOVETYPE_NONE && FBitSet(pev->flags, FL_ONGROUND) ) + pev->movetype = MOVETYPE_NONE; + + if (pev->deadflag == DEAD_DYING) + pev->deadflag = DEAD_DEAD; + + StopAnimation(); + + pev->effects |= EF_NOINTERP; + pev->framerate = 0.0; + + BOOL fAnyButtonDown = (pev->button & ~IN_SCORE ); + + // wait for all buttons released + if (pev->deadflag == DEAD_DEAD) + { + if (fAnyButtonDown) + return; + + if ( g_pGameRules->FPlayerCanRespawn( this ) ) + { + m_fDeadTime = gpGlobals->time; + pev->deadflag = DEAD_RESPAWNABLE; + } + + return; + } + +// if the player has been dead for one second longer than allowed by forcerespawn, +// forcerespawn isn't on. Send the player off to an intermission camera until they +// choose to respawn. + if ( g_pGameRules->IsMultiplayer() && ( gpGlobals->time > (m_fDeadTime + 6) ) && !(m_afPhysicsFlags & PFLAG_OBSERVER) ) + { + // go to dead camera. + StartDeathCam(); + } + + if ( pev->iuser1 ) // player is in spectator mode + return; + +// wait for any button down, or mp_forcerespawn is set and the respawn time is up + if (!fAnyButtonDown + && !( g_pGameRules->IsMultiplayer() && forcerespawn.value > 0 && (gpGlobals->time > (m_fDeadTime + 5))) ) + return; + + pev->button = 0; + m_iRespawnFrames = 0; + + //ALERT(at_console, "Respawn\n"); + + respawn(pev, !(m_afPhysicsFlags & PFLAG_OBSERVER) );// don't copy a corpse if we're in deathcam. + pev->nextthink = -1; +} + +//========================================================= +// StartDeathCam - find an intermission spot and send the +// player off into observer mode +//========================================================= +void CBasePlayer::StartDeathCam( void ) +{ + edict_t *pSpot, *pNewSpot; + int iRand; + + if ( pev->view_ofs == g_vecZero ) + { + // don't accept subsequent attempts to StartDeathCam() + return; + } + + pSpot = FIND_ENTITY_BY_CLASSNAME( NULL, "info_intermission"); + + if ( !FNullEnt( pSpot ) ) + { + // at least one intermission spot in the world. + iRand = RANDOM_LONG( 0, 3 ); + + while ( iRand > 0 ) + { + pNewSpot = FIND_ENTITY_BY_CLASSNAME( pSpot, "info_intermission"); + + if ( pNewSpot ) + { + pSpot = pNewSpot; + } + + iRand--; + } + + CopyToBodyQue( pev ); + + UTIL_SetOrigin( pev, pSpot->v.origin ); + pev->angles = pev->v_angle = pSpot->v.v_angle; + } + else + { + // no intermission spot. Push them up in the air, looking down at their corpse + TraceResult tr; + CopyToBodyQue( pev ); + UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, 128 ), ignore_monsters, edict(), &tr ); + + UTIL_SetOrigin( pev, tr.vecEndPos ); + pev->angles = pev->v_angle = UTIL_VecToAngles( tr.vecEndPos - pev->origin ); + } + + // start death cam + + m_afPhysicsFlags |= PFLAG_OBSERVER; + pev->view_ofs = g_vecZero; + pev->fixangle = TRUE; + pev->solid = SOLID_NOT; + pev->takedamage = DAMAGE_NO; + pev->movetype = MOVETYPE_NONE; + pev->modelindex = 0; +} + +void CBasePlayer::StartObserver( Vector vecPosition, Vector vecViewAngle ) +{ + // clear any clientside entities attached to this player + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_KILLPLAYERATTACHMENTS ); + WRITE_BYTE( (BYTE)entindex() ); + MESSAGE_END(); + + // Holster weapon immediately, to allow it to cleanup + if (m_pActiveItem) + m_pActiveItem->Holster( ); + + if ( m_pTank != NULL ) + { + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + } + + // clear out the suit message cache so we don't keep chattering + SetSuitUpdate(NULL, FALSE, 0); + + // Tell Ammo Hud that the player is dead + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(0XFF); + WRITE_BYTE(0xFF); + MESSAGE_END(); + + // reset FOV + m_iFOV = m_iClientFOV = 0; + pev->fov = m_iFOV; + MESSAGE_BEGIN( MSG_ONE, gmsgSetFOV, NULL, pev ); + WRITE_BYTE(0); + MESSAGE_END(); + + // Setup flags + m_iHideHUD = (HIDEHUD_HEALTH | HIDEHUD_WEAPONS); + m_afPhysicsFlags |= PFLAG_OBSERVER; + pev->effects = EF_NODRAW; + pev->view_ofs = g_vecZero; + pev->angles = pev->v_angle = vecViewAngle; + pev->fixangle = TRUE; + pev->solid = SOLID_NOT; + pev->takedamage = DAMAGE_NO; + pev->movetype = MOVETYPE_NONE; + ClearBits( m_afPhysicsFlags, PFLAG_DUCKING ); + ClearBits( pev->flags, FL_DUCKING ); + pev->deadflag = DEAD_RESPAWNABLE; + pev->health = 1; + + // Clear out the status bar + m_fInitHUD = TRUE; + + pev->team = 0; + MESSAGE_BEGIN( MSG_ALL, gmsgTeamInfo ); + WRITE_BYTE( ENTINDEX(edict()) ); + WRITE_STRING( "" ); + MESSAGE_END(); + + // Remove all the player's stuff + RemoveAllItems( FALSE ); + + // Move them to the new position + UTIL_SetOrigin( pev, vecPosition ); + + // Find a player to watch + m_flNextObserverInput = 0; + Observer_SetMode( m_iObserverLastMode ); +} + +// +// PlayerUse - handles USE keypress +// +#define PLAYER_SEARCH_RADIUS (float)64 + +void CBasePlayer::PlayerUse ( void ) +{ + if ( IsObserver() ) + return; + + // Was use pressed or released? + if ( ! ((pev->button | m_afButtonPressed | m_afButtonReleased) & IN_USE) ) + return; + + // Hit Use on a train? + if ( m_afButtonPressed & IN_USE ) + { + if ( m_pTank != NULL ) + { + // Stop controlling the tank + // TODO: Send HUD Update + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + return; + } + else + { + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + { + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + else + { // Start controlling the train! + CBaseEntity *pTrain = CBaseEntity::Instance( pev->groundentity ); + + if ( pTrain && !(pev->button & IN_JUMP) && FBitSet(pev->flags, FL_ONGROUND) && (pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) && pTrain->OnControls(pev) ) + { + m_afPhysicsFlags |= PFLAG_ONTRAIN; + m_iTrain = TrainSpeed(pTrain->pev->speed, pTrain->pev->impulse); + m_iTrain |= TRAIN_NEW; + EMIT_SOUND( ENT(pev), CHAN_ITEM, "plats/train_use1.wav", 0.8, ATTN_NORM); + return; + } + } + } + } + + CBaseEntity *pObject = NULL; + CBaseEntity *pClosest = NULL; + Vector vecLOS; + float flMaxDot = VIEW_FIELD_NARROW; + float flDot; + + UTIL_MakeVectors ( pev->v_angle );// so we know which way we are facing + + while ((pObject = UTIL_FindEntityInSphere( pObject, pev->origin, PLAYER_SEARCH_RADIUS )) != NULL) + { + + if (pObject->ObjectCaps() & (FCAP_IMPULSE_USE | FCAP_CONTINUOUS_USE | FCAP_ONOFF_USE)) + { + // !!!PERFORMANCE- should this check be done on a per case basis AFTER we've determined that + // this object is actually usable? This dot is being done for every object within PLAYER_SEARCH_RADIUS + // when player hits the use key. How many objects can be in that area, anyway? (sjb) + vecLOS = (VecBModelOrigin( pObject->pev ) - (pev->origin + pev->view_ofs)); + + // This essentially moves the origin of the target to the corner nearest the player to test to see + // if it's "hull" is in the view cone + vecLOS = UTIL_ClampVectorToBox( vecLOS, pObject->pev->size * 0.5 ); + + flDot = DotProduct (vecLOS , gpGlobals->v_forward); + if (flDot > flMaxDot ) + {// only if the item is in front of the user + pClosest = pObject; + flMaxDot = flDot; +// ALERT( at_console, "%s : %f\n", STRING( pObject->pev->classname ), flDot ); + } +// ALERT( at_console, "%s : %f\n", STRING( pObject->pev->classname ), flDot ); + } + } + pObject = pClosest; + + // Found an object + if (pObject ) + { + //!!!UNDONE: traceline here to prevent USEing buttons through walls + int caps = pObject->ObjectCaps(); + + if ( m_afButtonPressed & IN_USE ) + EMIT_SOUND( ENT(pev), CHAN_ITEM, "common/wpn_select.wav", 0.4, ATTN_NORM); + + if ( ( (pev->button & IN_USE) && (caps & FCAP_CONTINUOUS_USE) ) || + ( (m_afButtonPressed & IN_USE) && (caps & (FCAP_IMPULSE_USE|FCAP_ONOFF_USE)) ) ) + { + if ( caps & FCAP_CONTINUOUS_USE ) + m_afPhysicsFlags |= PFLAG_USING; + + pObject->Use( this, this, USE_SET, 1 ); + } + // UNDONE: Send different USE codes for ON/OFF. Cache last ONOFF_USE object to send 'off' if you turn away + else if ( (m_afButtonReleased & IN_USE) && (pObject->ObjectCaps() & FCAP_ONOFF_USE) ) // BUGBUG This is an "off" use + { + pObject->Use( this, this, USE_SET, 0 ); + } + } + else + { + if ( m_afButtonPressed & IN_USE ) + EMIT_SOUND( ENT(pev), CHAN_ITEM, "common/wpn_denyselect.wav", 0.4, ATTN_NORM); + } +} + + + +void CBasePlayer::Jump() +{ + Vector vecWallCheckDir;// direction we're tracing a line to find a wall when walljumping + Vector vecAdjustedVelocity; + Vector vecSpot; + TraceResult tr; + + if (FBitSet(pev->flags, FL_WATERJUMP)) + return; + + if (pev->waterlevel >= 2) + { + return; + } + + // jump velocity is sqrt( height * gravity * 2) + + // If this isn't the first frame pressing the jump button, break out. + if ( !FBitSet( m_afButtonPressed, IN_JUMP ) ) + return; // don't pogo stick + + if ( !(pev->flags & FL_ONGROUND) || !pev->groundentity ) + { + return; + } + +// many features in this function use v_forward, so makevectors now. + UTIL_MakeVectors (pev->angles); + + // ClearBits(pev->flags, FL_ONGROUND); // don't stairwalk + + SetAnimation( PLAYER_JUMP ); + + if ( m_fLongJump && + (pev->button & IN_DUCK) && + ( pev->flDuckTime > 0 ) && + pev->velocity.Length() > 50 ) + { + SetAnimation( PLAYER_SUPERJUMP ); + } + + // If you're standing on a conveyor, add it's velocity to yours (for momentum) + entvars_t *pevGround = VARS(pev->groundentity); + if ( pevGround && (pevGround->flags & FL_CONVEYOR) ) + { + pev->velocity = pev->velocity + pev->basevelocity; + } +} + + + +// This is a glorious hack to find free space when you've crouched into some solid space +// Our crouching collisions do not work correctly for some reason and this is easier +// than fixing the problem :( +void FixPlayerCrouchStuck( edict_t *pPlayer ) +{ + TraceResult trace; + + // Move up as many as 18 pixels if the player is stuck. + for ( int i = 0; i < 18; i++ ) + { + UTIL_TraceHull( pPlayer->v.origin, pPlayer->v.origin, dont_ignore_monsters, head_hull, pPlayer, &trace ); + if ( trace.fStartSolid ) + pPlayer->v.origin.z ++; + else + break; + } +} + +void CBasePlayer::Duck( ) +{ + if (pev->button & IN_DUCK) + { + if ( m_IdealActivity != ACT_LEAP ) + { + SetAnimation( PLAYER_WALK ); + } + } +} + +// +// ID's player as such. +// +int CBasePlayer::Classify ( void ) +{ + return CLASS_PLAYER; +} + + +void CBasePlayer::AddPoints( int score, BOOL bAllowNegativeScore ) +{ + // Positive score always adds + if ( score < 0 ) + { + if ( !bAllowNegativeScore ) + { + if ( pev->frags < 0 ) // Can't go more negative + return; + + if ( -score > pev->frags ) // Will this go negative? + { + score = -pev->frags; // Sum will be 0 + } + } + } + + pev->frags += score; + + MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo ); + WRITE_BYTE( ENTINDEX(edict()) ); + WRITE_SHORT( pev->frags ); + WRITE_SHORT( m_iDeaths ); + WRITE_SHORT( 0 ); + WRITE_SHORT( g_pGameRules->GetTeamIndex( m_szTeamName ) + 1 ); + MESSAGE_END(); +} + + +void CBasePlayer::AddPointsToTeam( int score, BOOL bAllowNegativeScore ) +{ + int index = entindex(); + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + if ( pPlayer && i != index ) + { + if ( g_pGameRules->PlayerRelationship( this, pPlayer ) == GR_TEAMMATE ) + { + pPlayer->AddPoints( score, bAllowNegativeScore ); + } + } + } +} + +//Player ID +void CBasePlayer::InitStatusBar() +{ + m_flStatusBarDisappearDelay = 0; + m_SbarString1[0] = m_SbarString0[0] = 0; +} + +void CBasePlayer::UpdateStatusBar() +{ + int newSBarState[ SBAR_END ]; + char sbuf0[ SBAR_STRING_SIZE ]; + char sbuf1[ SBAR_STRING_SIZE ]; + + memset( newSBarState, 0, sizeof(newSBarState) ); + strcpy( sbuf0, m_SbarString0 ); + strcpy( sbuf1, m_SbarString1 ); + + // Find an ID Target + TraceResult tr; + UTIL_MakeVectors( pev->v_angle + pev->punchangle ); + Vector vecSrc = EyePosition(); + Vector vecEnd = vecSrc + (gpGlobals->v_forward * MAX_ID_RANGE); + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, edict(), &tr); + + if (tr.flFraction != 1.0) + { + if ( !FNullEnt( tr.pHit ) ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + + if (pEntity->Classify() == CLASS_PLAYER ) + { + newSBarState[ SBAR_ID_TARGETNAME ] = ENTINDEX( pEntity->edict() ); + strcpy( sbuf1, "1 %p1\n2 Health: %i2%%\n3 Armor: %i3%%" ); + + // allies and medics get to see the targets health + if ( g_pGameRules->PlayerRelationship( this, pEntity ) == GR_TEAMMATE ) + { + newSBarState[ SBAR_ID_TARGETHEALTH ] = 100 * (pEntity->pev->health / pEntity->pev->max_health); + newSBarState[ SBAR_ID_TARGETARMOR ] = pEntity->pev->armorvalue; //No need to get it % based since 100 it's the max. + } + + m_flStatusBarDisappearDelay = gpGlobals->time + 1.0; + } + } + else if ( m_flStatusBarDisappearDelay > gpGlobals->time ) + { + // hold the values for a short amount of time after viewing the object + newSBarState[ SBAR_ID_TARGETNAME ] = m_izSBarState[ SBAR_ID_TARGETNAME ]; + newSBarState[ SBAR_ID_TARGETHEALTH ] = m_izSBarState[ SBAR_ID_TARGETHEALTH ]; + newSBarState[ SBAR_ID_TARGETARMOR ] = m_izSBarState[ SBAR_ID_TARGETARMOR ]; + } + } + + BOOL bForceResend = FALSE; + + if ( strcmp( sbuf0, m_SbarString0 ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgStatusText, NULL, pev ); + WRITE_BYTE( 0 ); + WRITE_STRING( sbuf0 ); + MESSAGE_END(); + + strcpy( m_SbarString0, sbuf0 ); + + // make sure everything's resent + bForceResend = TRUE; + } + + if ( strcmp( sbuf1, m_SbarString1 ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgStatusText, NULL, pev ); + WRITE_BYTE( 1 ); + WRITE_STRING( sbuf1 ); + MESSAGE_END(); + + strcpy( m_SbarString1, sbuf1 ); + + // make sure everything's resent + bForceResend = TRUE; + } + + // Check values and send if they don't match + for (int i = 1; i < SBAR_END; i++) + { + if ( newSBarState[i] != m_izSBarState[i] || bForceResend ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgStatusValue, NULL, pev ); + WRITE_BYTE( i ); + WRITE_SHORT( newSBarState[i] ); + MESSAGE_END(); + + m_izSBarState[i] = newSBarState[i]; + } + } +} + + + + + + + + + +#define CLIMB_SHAKE_FREQUENCY 22 // how many frames in between screen shakes when climbing +#define MAX_CLIMB_SPEED 200 // fastest vertical climbing speed possible +#define CLIMB_SPEED_DEC 15 // climbing deceleration rate +#define CLIMB_PUNCH_X -7 // how far to 'punch' client X axis when climbing +#define CLIMB_PUNCH_Z 7 // how far to 'punch' client Z axis when climbing + +void CBasePlayer::PreThink(void) +{ + int buttonsChanged = (m_afButtonLast ^ pev->button); // These buttons have changed this frame + + // Debounced button codes for pressed/released + // UNDONE: Do we need auto-repeat? + m_afButtonPressed = buttonsChanged & pev->button; // The changed ones still down are "pressed" + m_afButtonReleased = buttonsChanged & (~pev->button); // The ones not down are "released" + + g_pGameRules->PlayerThink( this ); + + if ( g_fGameOver ) + return; // intermission or finale + + UTIL_MakeVectors(pev->v_angle); // is this still used? + + ItemPreFrame( ); + WaterMove(); + + if ( g_pGameRules && g_pGameRules->FAllowFlashlight() ) + m_iHideHUD &= ~HIDEHUD_FLASHLIGHT; + else + m_iHideHUD |= HIDEHUD_FLASHLIGHT; + + + // JOHN: checks if new client data (for HUD and view control) needs to be sent to the client + UpdateClientData(); + + CheckTimeBasedDamage(); + + CheckSuitUpdate(); + + // Observer Button Handling + if ( IsObserver() ) + { + Observer_HandleButtons(); + Observer_CheckTarget(); + Observer_CheckProperties(); + pev->impulse = 0; + return; + } + + if (pev->deadflag >= DEAD_DYING) + { + PlayerDeathThink(); + return; + } + + // So the correct flags get sent to client asap. + // + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + pev->flags |= FL_ONTRAIN; + else + pev->flags &= ~FL_ONTRAIN; + + // Train speed control + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + { + CBaseEntity *pTrain = CBaseEntity::Instance( pev->groundentity ); + float vel; + + if ( !pTrain ) + { + TraceResult trainTrace; + // Maybe this is on the other side of a level transition + UTIL_TraceLine( pev->origin, pev->origin + Vector(0,0,-38), ignore_monsters, ENT(pev), &trainTrace ); + + // HACKHACK - Just look for the func_tracktrain classname + if ( trainTrace.flFraction != 1.0 && trainTrace.pHit ) + pTrain = CBaseEntity::Instance( trainTrace.pHit ); + + + if ( !pTrain || !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) || !pTrain->OnControls(pev) ) + { + //ALERT( at_error, "In train mode with no train!\n" ); + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + } + else if ( !FBitSet( pev->flags, FL_ONGROUND ) || FBitSet( pTrain->pev->spawnflags, SF_TRACKTRAIN_NOCONTROL ) || (pev->button & (IN_MOVELEFT|IN_MOVERIGHT) ) ) + { + // Turn off the train if you jump, strafe, or the train controls go dead + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + + pev->velocity = g_vecZero; + vel = 0; + if ( m_afButtonPressed & IN_FORWARD ) + { + vel = 1; + pTrain->Use( this, this, USE_SET, (float)vel ); + } + else if ( m_afButtonPressed & IN_BACK ) + { + vel = -1; + pTrain->Use( this, this, USE_SET, (float)vel ); + } + + if (vel) + { + m_iTrain = TrainSpeed(pTrain->pev->speed, pTrain->pev->impulse); + m_iTrain |= TRAIN_ACTIVE|TRAIN_NEW; + } + + } else if (m_iTrain & TRAIN_ACTIVE) + m_iTrain = TRAIN_NEW; // turn off train + + if (pev->button & IN_JUMP) + { + // If on a ladder, jump off the ladder + // else Jump + Jump(); + } + + + // If trying to duck, already ducked, or in the process of ducking + if ((pev->button & IN_DUCK) || FBitSet(pev->flags,FL_DUCKING) || (m_afPhysicsFlags & PFLAG_DUCKING) ) + Duck(); + + if ( !FBitSet ( pev->flags, FL_ONGROUND ) ) + { + m_flFallVelocity = -pev->velocity.z; + } + + // StudioFrameAdvance( );//!!!HACKHACK!!! Can't be hit by traceline when not animating? + + // Clear out ladder pointer + m_hEnemy = NULL; + + if ( m_afPhysicsFlags & PFLAG_ONBARNACLE ) + { + pev->velocity = g_vecZero; + } +} +/* Time based Damage works as follows: + 1) There are several types of timebased damage: + + #define DMG_PARALYZE (1 << 14) // slows affected creature down + #define DMG_NERVEGAS (1 << 15) // nerve toxins, very bad + #define DMG_POISON (1 << 16) // blood poisioning + #define DMG_RADIATION (1 << 17) // radiation exposure + #define DMG_DROWNRECOVER (1 << 18) // drown recovery + #define DMG_ACID (1 << 19) // toxic chemicals or acid burns + #define DMG_SLOWBURN (1 << 20) // in an oven + #define DMG_SLOWFREEZE (1 << 21) // in a subzero freezer + + 2) A new hit inflicting tbd restarts the tbd counter - each monster has an 8bit counter, + per damage type. The counter is decremented every second, so the maximum time + an effect will last is 255/60 = 4.25 minutes. Of course, staying within the radius + of a damaging effect like fire, nervegas, radiation will continually reset the counter to max. + + 3) Every second that a tbd counter is running, the player takes damage. The damage + is determined by the type of tdb. + Paralyze - 1/2 movement rate, 30 second duration. + Nervegas - 5 points per second, 16 second duration = 80 points max dose. + Poison - 2 points per second, 25 second duration = 50 points max dose. + Radiation - 1 point per second, 50 second duration = 50 points max dose. + Drown - 5 points per second, 2 second duration. + Acid/Chemical - 5 points per second, 10 second duration = 50 points max. + Burn - 10 points per second, 2 second duration. + Freeze - 3 points per second, 10 second duration = 30 points max. + + 4) Certain actions or countermeasures counteract the damaging effects of tbds: + + Armor/Heater/Cooler - Chemical(acid),burn, freeze all do damage to armor power, then to body + - recharged by suit recharger + Air In Lungs - drowning damage is done to air in lungs first, then to body + - recharged by poking head out of water + - 10 seconds if swiming fast + Air In SCUBA - drowning damage is done to air in tanks first, then to body + - 2 minutes in tanks. Need new tank once empty. + Radiation Syringe - Each syringe full provides protection vs one radiation dosage + Antitoxin Syringe - Each syringe full provides protection vs one poisoning (nervegas or poison). + Health kit - Immediate stop to acid/chemical, fire or freeze damage. + Radiation Shower - Immediate stop to radiation damage, acid/chemical or fire damage. + + +*/ + +// If player is taking time based damage, continue doing damage to player - +// this simulates the effect of being poisoned, gassed, dosed with radiation etc - +// anything that continues to do damage even after the initial contact stops. +// Update all time based damage counters, and shut off any that are done. + +// The m_bitsDamageType bit MUST be set if any damage is to be taken. +// This routine will detect the initial on value of the m_bitsDamageType +// and init the appropriate counter. Only processes damage every second. + +//#define PARALYZE_DURATION 30 // number of 2 second intervals to take damage +//#define PARALYZE_DAMAGE 0.0 // damage to take each 2 second interval + +//#define NERVEGAS_DURATION 16 +//#define NERVEGAS_DAMAGE 5.0 + +//#define POISON_DURATION 25 +//#define POISON_DAMAGE 2.0 + +//#define RADIATION_DURATION 50 +//#define RADIATION_DAMAGE 1.0 + +//#define ACID_DURATION 10 +//#define ACID_DAMAGE 5.0 + +//#define SLOWBURN_DURATION 2 +//#define SLOWBURN_DAMAGE 1.0 + +//#define SLOWFREEZE_DURATION 1.0 +//#define SLOWFREEZE_DAMAGE 3.0 + +/* */ + + +void CBasePlayer::CheckTimeBasedDamage() +{ + int i; + BYTE bDuration = 0; + + static float gtbdPrev = 0.0; + + if (!(m_bitsDamageType & DMG_TIMEBASED)) + return; + + // only check for time based damage approx. every 2 seconds + if (abs(gpGlobals->time - m_tbdPrev) < 2.0) + return; + + m_tbdPrev = gpGlobals->time; + + for (i = 0; i < CDMG_TIMEBASED; i++) + { + // make sure bit is set for damage type + if (m_bitsDamageType & (DMG_PARALYZE << i)) + { + switch (i) + { + case itbd_Paralyze: + // UNDONE - flag movement as half-speed + bDuration = PARALYZE_DURATION; + break; + case itbd_NerveGas: +// TakeDamage(pev, pev, NERVEGAS_DAMAGE, DMG_GENERIC); + bDuration = NERVEGAS_DURATION; + break; + case itbd_Poison: + TakeDamage(pev, pev, POISON_DAMAGE, DMG_GENERIC); + bDuration = POISON_DURATION; + break; + case itbd_Radiation: +// TakeDamage(pev, pev, RADIATION_DAMAGE, DMG_GENERIC); + bDuration = RADIATION_DURATION; + break; + case itbd_DrownRecover: + // NOTE: this hack is actually used to RESTORE health + // after the player has been drowning and finally takes a breath + if (m_idrowndmg > m_idrownrestored) + { + int idif = min(m_idrowndmg - m_idrownrestored, 10); + + TakeHealth(idif, DMG_GENERIC); + m_idrownrestored += idif; + } + bDuration = 4; // get up to 5*10 = 50 points back + break; + case itbd_Acid: +// TakeDamage(pev, pev, ACID_DAMAGE, DMG_GENERIC); + bDuration = ACID_DURATION; + break; + case itbd_SlowBurn: +// TakeDamage(pev, pev, SLOWBURN_DAMAGE, DMG_GENERIC); + bDuration = SLOWBURN_DURATION; + break; + case itbd_SlowFreeze: +// TakeDamage(pev, pev, SLOWFREEZE_DAMAGE, DMG_GENERIC); + bDuration = SLOWFREEZE_DURATION; + break; + default: + bDuration = 0; + } + + if (m_rgbTimeBasedDamage[i]) + { + // use up an antitoxin on poison or nervegas after a few seconds of damage + if (((i == itbd_NerveGas) && (m_rgbTimeBasedDamage[i] < NERVEGAS_DURATION)) || + ((i == itbd_Poison) && (m_rgbTimeBasedDamage[i] < POISON_DURATION))) + { + if (m_rgItems[ITEM_ANTIDOTE]) + { + m_rgbTimeBasedDamage[i] = 0; + m_rgItems[ITEM_ANTIDOTE]--; + SetSuitUpdate("!HEV_HEAL4", FALSE, SUIT_REPEAT_OK); + } + } + + + // decrement damage duration, detect when done. + if (!m_rgbTimeBasedDamage[i] || --m_rgbTimeBasedDamage[i] == 0) + { + m_rgbTimeBasedDamage[i] = 0; + // if we're done, clear damage bits + m_bitsDamageType &= ~(DMG_PARALYZE << i); + } + } + else + // first time taking this damage type - init damage duration + m_rgbTimeBasedDamage[i] = bDuration; + } + } +} + +/* +THE POWER SUIT + +The Suit provides 3 main functions: Protection, Notification and Augmentation. +Some functions are automatic, some require power. +The player gets the suit shortly after getting off the train in C1A0 and it stays +with him for the entire game. + +Protection + + Heat/Cold + When the player enters a hot/cold area, the heating/cooling indicator on the suit + will come on and the battery will drain while the player stays in the area. + After the battery is dead, the player starts to take damage. + This feature is built into the suit and is automatically engaged. + Radiation Syringe + This will cause the player to be immune from the effects of radiation for N seconds. Single use item. + Anti-Toxin Syringe + This will cure the player from being poisoned. Single use item. + Health + Small (1st aid kits, food, etc.) + Large (boxes on walls) + Armor + The armor works using energy to create a protective field that deflects a + percentage of damage projectile and explosive attacks. After the armor has been deployed, + it will attempt to recharge itself to full capacity with the energy reserves from the battery. + It takes the armor N seconds to fully charge. + +Notification (via the HUD) + +x Health +x Ammo +x Automatic Health Care + Notifies the player when automatic healing has been engaged. +x Geiger counter + Classic Geiger counter sound and status bar at top of HUD + alerts player to dangerous levels of radiation. This is not visible when radiation levels are normal. +x Poison + Armor + Displays the current level of armor. + +Augmentation + + Reanimation (w/adrenaline) + Causes the player to come back to life after he has been dead for 3 seconds. + Will not work if player was gibbed. Single use. + Long Jump + Used by hitting the ??? key(s). Caused the player to further than normal. + SCUBA + Used automatically after picked up and after player enters the water. + Works for N seconds. Single use. + +Things powered by the battery + + Armor + Uses N watts for every M units of damage. + Heat/Cool + Uses N watts for every second in hot/cold area. + Long Jump + Uses N watts for every jump. + Alien Cloak + Uses N watts for each use. Each use lasts M seconds. + Alien Shield + Augments armor. Reduces Armor drain by one half + +*/ + +// if in range of radiation source, ping geiger counter + +#define GEIGERDELAY 0.25 + +void CBasePlayer :: UpdateGeigerCounter( void ) +{ + BYTE range; + + // delay per update ie: don't flood net with these msgs + if (gpGlobals->time < m_flgeigerDelay) + return; + + m_flgeigerDelay = gpGlobals->time + GEIGERDELAY; + + // send range to radition source to client + + range = (BYTE) (m_flgeigerRange / 4); + + if (range != m_igeigerRangePrev) + { + m_igeigerRangePrev = range; + + MESSAGE_BEGIN( MSG_ONE, gmsgGeigerRange, NULL, pev ); + WRITE_BYTE( range ); + MESSAGE_END(); + } + + // reset counter and semaphore + if (!RANDOM_LONG(0,3)) + m_flgeigerRange = 1000; + +} + +/* +================ +CheckSuitUpdate + +Play suit update if it's time +================ +*/ + +#define SUITUPDATETIME 3.5 +#define SUITFIRSTUPDATETIME 0.1 + +void CBasePlayer::CheckSuitUpdate() +{ + int i; + int isentence = 0; + int isearch = m_iSuitPlayNext; + + // Ignore suit updates if no suit + if ( !(pev->weapons & (1<IsMultiplayer() ) + { + // don't bother updating HEV voice in multiplayer. + return; + } + + if ( gpGlobals->time >= m_flSuitUpdate && m_flSuitUpdate > 0) + { + // play a sentence off of the end of the queue + for (i = 0; i < CSUITPLAYLIST; i++) + { + if (isentence = m_rgSuitPlayList[isearch]) + break; + + if (++isearch == CSUITPLAYLIST) + isearch = 0; + } + + if (isentence) + { + m_rgSuitPlayList[isearch] = 0; + if (isentence > 0) + { + // play sentence number + + char sentence[CBSENTENCENAME_MAX+1]; + strcpy(sentence, "!"); + strcat(sentence, gszallsentencenames[isentence]); + EMIT_SOUND_SUIT(ENT(pev), sentence); + } + else + { + // play sentence group + EMIT_GROUPID_SUIT(ENT(pev), -isentence); + } + m_flSuitUpdate = gpGlobals->time + SUITUPDATETIME; + } + else + // queue is empty, don't check + m_flSuitUpdate = 0; + } +} + +// add sentence to suit playlist queue. if fgroup is true, then +// name is a sentence group (HEV_AA), otherwise name is a specific +// sentence name ie: !HEV_AA0. If iNoRepeat is specified in +// seconds, then we won't repeat playback of this word or sentence +// for at least that number of seconds. + +void CBasePlayer::SetSuitUpdate(char *name, int fgroup, int iNoRepeatTime) +{ + int i; + int isentence; + int iempty = -1; + + + // Ignore suit updates if no suit + if ( !(pev->weapons & (1<IsMultiplayer() ) + { + // due to static channel design, etc. We don't play HEV sounds in multiplayer right now. + return; + } + + // if name == NULL, then clear out the queue + + if (!name) + { + for (i = 0; i < CSUITPLAYLIST; i++) + m_rgSuitPlayList[i] = 0; + return; + } + // get sentence or group number + if (!fgroup) + { + isentence = SENTENCEG_Lookup(name, NULL); + if (isentence < 0) + return; + } + else + // mark group number as negative + isentence = -SENTENCEG_GetIndex(name); + + // check norepeat list - this list lets us cancel + // the playback of words or sentences that have already + // been played within a certain time. + + for (i = 0; i < CSUITNOREPEAT; i++) + { + if (isentence == m_rgiSuitNoRepeat[i]) + { + // this sentence or group is already in + // the norepeat list + + if (m_rgflSuitNoRepeatTime[i] < gpGlobals->time) + { + // norepeat time has expired, clear it out + m_rgiSuitNoRepeat[i] = 0; + m_rgflSuitNoRepeatTime[i] = 0.0; + iempty = i; + break; + } + else + { + // don't play, still marked as norepeat + return; + } + } + // keep track of empty slot + if (!m_rgiSuitNoRepeat[i]) + iempty = i; + } + + // sentence is not in norepeat list, save if norepeat time was given + + if (iNoRepeatTime) + { + if (iempty < 0) + iempty = RANDOM_LONG(0, CSUITNOREPEAT-1); // pick random slot to take over + m_rgiSuitNoRepeat[iempty] = isentence; + m_rgflSuitNoRepeatTime[iempty] = iNoRepeatTime + gpGlobals->time; + } + + // find empty spot in queue, or overwrite last spot + + m_rgSuitPlayList[m_iSuitPlayNext++] = isentence; + if (m_iSuitPlayNext == CSUITPLAYLIST) + m_iSuitPlayNext = 0; + + if (m_flSuitUpdate <= gpGlobals->time) + { + if (m_flSuitUpdate == 0) + // play queue is empty, don't delay too long before playback + m_flSuitUpdate = gpGlobals->time + SUITFIRSTUPDATETIME; + else + m_flSuitUpdate = gpGlobals->time + SUITUPDATETIME; + } + +} + +/* +================ +CheckPowerups + +Check for turning off powerups + +GLOBALS ASSUMED SET: g_ulModelIndexPlayer +================ +*/ + static void +CheckPowerups(entvars_t *pev) +{ + if (pev->health <= 0) + return; + + pev->modelindex = g_ulModelIndexPlayer; // don't use eyes +} + + +//========================================================= +// UpdatePlayerSound - updates the position of the player's +// reserved sound slot in the sound list. +//========================================================= +void CBasePlayer :: UpdatePlayerSound ( void ) +{ + int iBodyVolume; + int iVolume; + CSound *pSound; + + pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt :: ClientSoundIndex( edict() ) ); + + if ( !pSound ) + { + ALERT ( at_console, "Client lost reserved sound!\n" ); + return; + } + + pSound->m_iType = bits_SOUND_NONE; + + // now calculate the best target volume for the sound. If the player's weapon + // is louder than his body/movement, use the weapon volume, else, use the body volume. + + if ( FBitSet ( pev->flags, FL_ONGROUND ) ) + { + iBodyVolume = pev->velocity.Length(); + + // clamp the noise that can be made by the body, in case a push trigger, + // weapon recoil, or anything shoves the player abnormally fast. + if ( iBodyVolume > 512 ) + { + iBodyVolume = 512; + } + } + else + { + iBodyVolume = 0; + } + + if ( pev->button & IN_JUMP ) + { + iBodyVolume += 100; + } + +// convert player move speed and actions into sound audible by monsters. + if ( m_iWeaponVolume > iBodyVolume ) + { + m_iTargetVolume = m_iWeaponVolume; + + // OR in the bits for COMBAT sound if the weapon is being louder than the player. + pSound->m_iType |= bits_SOUND_COMBAT; + } + else + { + m_iTargetVolume = iBodyVolume; + } + + // decay weapon volume over time so bits_SOUND_COMBAT stays set for a while + m_iWeaponVolume -= 250 * gpGlobals->frametime; + if ( m_iWeaponVolume < 0 ) + { + iVolume = 0; + } + + + // if target volume is greater than the player sound's current volume, we paste the new volume in + // immediately. If target is less than the current volume, current volume is not set immediately to the + // lower volume, rather works itself towards target volume over time. This gives monsters a much better chance + // to hear a sound, especially if they don't listen every frame. + iVolume = pSound->m_iVolume; + + if ( m_iTargetVolume > iVolume ) + { + iVolume = m_iTargetVolume; + } + else if ( iVolume > m_iTargetVolume ) + { + iVolume -= 250 * gpGlobals->frametime; + + if ( iVolume < m_iTargetVolume ) + { + iVolume = 0; + } + } + + if ( m_fNoPlayerSound ) + { + // debugging flag, lets players move around and shoot without monsters hearing. + iVolume = 0; + } + + if ( gpGlobals->time > m_flStopExtraSoundTime ) + { + // since the extra sound that a weapon emits only lasts for one client frame, we keep that sound around for a server frame or two + // after actual emission to make sure it gets heard. + m_iExtraSoundTypes = 0; + } + + if ( pSound ) + { + pSound->m_vecOrigin = pev->origin; + pSound->m_iType |= ( bits_SOUND_PLAYER | m_iExtraSoundTypes ); + pSound->m_iVolume = iVolume; + } + + // keep track of virtual muzzle flash + m_iWeaponFlash -= 256 * gpGlobals->frametime; + if (m_iWeaponFlash < 0) + m_iWeaponFlash = 0; + + //UTIL_MakeVectors ( pev->angles ); + //gpGlobals->v_forward.z = 0; + + // Below are a couple of useful little bits that make it easier to determine just how much noise the + // player is making. + // UTIL_ParticleEffect ( pev->origin + gpGlobals->v_forward * iVolume, g_vecZero, 255, 25 ); + //ALERT ( at_console, "%d/%d\n", iVolume, m_iTargetVolume ); +} + + +void CBasePlayer::PostThink() +{ + if ( g_fGameOver ) + goto pt_end; // intermission or finale + + if (!IsAlive()) + goto pt_end; + + // Handle Tank controlling + if ( m_pTank != NULL ) + { // if they've moved too far from the gun, or selected a weapon, unuse the gun + if ( m_pTank->OnControls( pev ) && !pev->weaponmodel ) + { + m_pTank->Use( this, this, USE_SET, 2 ); // try fire the gun + } + else + { // they've moved off the platform + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + } + } + +// do weapon stuff + ItemPostFrame( ); + +// check to see if player landed hard enough to make a sound +// falling farther than half of the maximum safe distance, but not as far a max safe distance will +// play a bootscrape sound, and no damage will be inflicted. Fallling a distance shorter than half +// of maximum safe distance will make no sound. Falling farther than max safe distance will play a +// fallpain sound, and damage will be inflicted based on how far the player fell + + if ( (FBitSet(pev->flags, FL_ONGROUND)) && (pev->health > 0) && m_flFallVelocity >= PLAYER_FALL_PUNCH_THRESHHOLD ) + { + // ALERT ( at_console, "%f\n", m_flFallVelocity ); + + if (pev->watertype == CONTENT_WATER) + { + // Did he hit the world or a non-moving entity? + // BUG - this happens all the time in water, especially when + // BUG - water has current force + // if ( !pev->groundentity || VARS(pev->groundentity)->velocity.z == 0 ) + // EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM); + } + else if ( m_flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED ) + {// after this point, we start doing damage + + float flFallDamage = g_pGameRules->FlPlayerFallDamage( this ); + + if ( flFallDamage > pev->health ) + {//splat + // note: play on item channel because we play footstep landing on body channel + EMIT_SOUND(ENT(pev), CHAN_ITEM, "common/bodysplat.wav", 1, ATTN_NORM); + } + + if ( flFallDamage > 0 ) + { + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), flFallDamage, DMG_FALL ); + pev->punchangle.x = 0; + } + } + + if ( IsAlive() ) + { + SetAnimation( PLAYER_WALK ); + } + } + + if (FBitSet(pev->flags, FL_ONGROUND)) + { + if (m_flFallVelocity > 64 && !g_pGameRules->IsMultiplayer()) + { + CSoundEnt::InsertSound ( bits_SOUND_PLAYER, pev->origin, m_flFallVelocity, 0.2 ); + // ALERT( at_console, "fall %f\n", m_flFallVelocity ); + } + m_flFallVelocity = 0; + } + + // select the proper animation for the player character + if ( IsAlive() ) + { + if (!pev->velocity.x && !pev->velocity.y) + SetAnimation( PLAYER_IDLE ); + else if ((pev->velocity.x || pev->velocity.y) && (FBitSet(pev->flags, FL_ONGROUND))) + SetAnimation( PLAYER_WALK ); + else if (pev->waterlevel > 1) + SetAnimation( PLAYER_WALK ); + } + + StudioFrameAdvance( ); + CheckPowerups(pev); + + UpdatePlayerSound(); + +pt_end: +#if defined( CLIENT_WEAPONS ) + // Decay timers on weapons + // go through all of the weapons and make a list of the ones to pack + for ( int i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + CBasePlayerItem *pPlayerItem = m_rgpPlayerItems[ i ]; + + while ( pPlayerItem ) + { + CBasePlayerWeapon *gun; + + gun = (CBasePlayerWeapon *)pPlayerItem->GetWeaponPtr(); + + if ( gun && gun->UseDecrement() ) + { + gun->m_flNextPrimaryAttack = max( gun->m_flNextPrimaryAttack - gpGlobals->frametime, -1.0 ); + gun->m_flNextSecondaryAttack = max( gun->m_flNextSecondaryAttack - gpGlobals->frametime, -0.001 ); + + if ( gun->m_flTimeWeaponIdle != 1000 ) + { + gun->m_flTimeWeaponIdle = max( gun->m_flTimeWeaponIdle - gpGlobals->frametime, -0.001 ); + } + + if ( gun->pev->fuser1 != 1000 ) + { + gun->pev->fuser1 = max( gun->pev->fuser1 - gpGlobals->frametime, -0.001 ); + } + + // Only decrement if not flagged as NO_DECREMENT +// if ( gun->m_flPumpTime != 1000 ) + // { + // gun->m_flPumpTime = max( gun->m_flPumpTime - gpGlobals->frametime, -0.001 ); + // } + + } + + pPlayerItem = pPlayerItem->m_pNext; + } + } + } + + m_flNextAttack -= gpGlobals->frametime; + if ( m_flNextAttack < -0.001 ) + m_flNextAttack = -0.001; + + if ( m_flNextAmmoBurn != 1000 ) + { + m_flNextAmmoBurn -= gpGlobals->frametime; + + if ( m_flNextAmmoBurn < -0.001 ) + m_flNextAmmoBurn = -0.001; + } + + if ( m_flAmmoStartCharge != 1000 ) + { + m_flAmmoStartCharge -= gpGlobals->frametime; + + if ( m_flAmmoStartCharge < -0.001 ) + m_flAmmoStartCharge = -0.001; + } +#endif + + // Track button info so we can detect 'pressed' and 'released' buttons next frame + m_afButtonLast = pev->button; +} + + +// checks if the spot is clear of players +BOOL IsSpawnPointValid( CBaseEntity *pPlayer, CBaseEntity *pSpot ) +{ + CBaseEntity *ent = NULL; + + if ( !pSpot->IsTriggered( pPlayer ) ) + { + return FALSE; + } + + while ( (ent = UTIL_FindEntityInSphere( ent, pSpot->pev->origin, 128 )) != NULL ) + { + // if ent is a client, don't spawn on 'em + if ( ent->IsPlayer() && ent != pPlayer ) + return FALSE; + } + + return TRUE; +} + + +DLL_GLOBAL CBaseEntity *g_pLastSpawn; +inline int FNullEnt( CBaseEntity *ent ) { return (ent == NULL) || FNullEnt( ent->edict() ); } + +/* +============ +EntSelectSpawnPoint + +Returns the entity to spawn at + +USES AND SETS GLOBAL g_pLastSpawn +============ +*/ +edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ) +{ + CBaseEntity *pSpot; + edict_t *player; + + player = pPlayer->edict(); + +// choose a info_player_deathmatch point + if (g_pGameRules->IsCoOp()) + { + pSpot = UTIL_FindEntityByClassname( g_pLastSpawn, "info_player_coop"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + pSpot = UTIL_FindEntityByClassname( g_pLastSpawn, "info_player_start"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + else if ( g_pGameRules->IsDeathmatch() ) + { + pSpot = g_pLastSpawn; + // Randomize the start spot + for ( int i = RANDOM_LONG(1,5); i > 0; i-- ) + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + if ( FNullEnt( pSpot ) ) // skip over the null point + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + + CBaseEntity *pFirstSpot = pSpot; + + do + { + if ( pSpot ) + { + // check if pSpot is valid + if ( IsSpawnPointValid( pPlayer, pSpot ) ) + { + if ( pSpot->pev->origin == Vector( 0, 0, 0 ) ) + { + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + continue; + } + + // if so, go to pSpot + goto ReturnSpot; + } + } + // increment pSpot + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + } while ( pSpot != pFirstSpot ); // loop if we're not back to the start + + // we haven't found a place to spawn yet, so kill any guy at the first spawn point and spawn there + if ( !FNullEnt( pSpot ) ) + { + CBaseEntity *ent = NULL; + while ( (ent = UTIL_FindEntityInSphere( ent, pSpot->pev->origin, 128 )) != NULL ) + { + // if ent is a client, kill em (unless they are ourselves) + if ( ent->IsPlayer() && !(ent->edict() == player) ) + ent->TakeDamage( VARS(INDEXENT(0)), VARS(INDEXENT(0)), 300, DMG_GENERIC ); + } + goto ReturnSpot; + } + } + + // If startspot is set, (re)spawn there. + if ( FStringNull( gpGlobals->startspot ) || !strlen(STRING(gpGlobals->startspot))) + { + pSpot = UTIL_FindEntityByClassname(NULL, "info_player_start"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + else + { + pSpot = UTIL_FindEntityByTargetname( NULL, STRING(gpGlobals->startspot) ); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + +ReturnSpot: + if ( FNullEnt( pSpot ) ) + { + ALERT(at_error, "PutClientInServer: no info_player_start on level"); + return INDEXENT(0); + } + + g_pLastSpawn = pSpot; + return pSpot->edict(); +} + +void CBasePlayer::Spawn( void ) +{ + pev->classname = MAKE_STRING("player"); + pev->health = 100; + pev->armorvalue = 0; + pev->takedamage = DAMAGE_AIM; + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_WALK; + pev->max_health = pev->health; + pev->flags &= FL_PROXY; // keep proxy flag sey by engine + pev->flags |= FL_CLIENT; + pev->air_finished = gpGlobals->time + 12; + pev->dmg = 2; // initial water damage + pev->effects = 0; + pev->deadflag = DEAD_NO; + pev->dmg_take = 0; + pev->dmg_save = 0; + pev->friction = 1.0; + pev->gravity = 1.0; + m_bitsHUDDamage = -1; + m_bitsDamageType = 0; + m_afPhysicsFlags = 0; + m_fLongJump = FALSE;// no longjump module. + + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "slj", "0" ); + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "hl", "1" ); + + pev->fov = m_iFOV = 0;// init field of view. + m_iClientFOV = -1; // make sure fov reset is sent + + m_flNextDecalTime = 0;// let this player decal as soon as he spawns. + + m_flgeigerDelay = gpGlobals->time + 2.0; // wait a few seconds until user-defined message registrations + // are recieved by all clients + + m_flTimeStepSound = 0; + m_iStepLeft = 0; + m_flFieldOfView = 0.5;// some monsters use this to determine whether or not the player is looking at them. + + m_bloodColor = BLOOD_COLOR_RED; + m_flNextAttack = UTIL_WeaponTimeBase(); + StartSneaking(); + + m_iFlashBattery = 99; + m_flFlashLightTime = 1; // force first message + +// dont let uninitialized value here hurt the player + m_flFallVelocity = 0; + + g_pGameRules->SetDefaultPlayerTeam( this ); + g_pGameRules->GetPlayerSpawnSpot( this ); + + SET_MODEL(ENT(pev), "models/player.mdl"); + g_ulModelIndexPlayer = pev->modelindex; + pev->sequence = LookupActivity( ACT_IDLE ); + + if ( FBitSet(pev->flags, FL_DUCKING) ) + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + else + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + + pev->view_ofs = VEC_VIEW; + Precache(); + m_HackedGunPos = Vector( 0, 32, 0 ); + + if ( m_iPlayerSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_console, "Couldn't alloc player sound slot!\n" ); + } + + m_fNoPlayerSound = FALSE;// normal sound behavior. + + m_pLastItem = NULL; + m_fInitHUD = TRUE; + m_iClientHideHUD = -1; // force this to be recalculated + m_fWeapon = FALSE; + m_pClientActiveItem = NULL; + m_iClientBattery = -1; + + // reset all ammo values to 0 + for ( int i = 0; i < MAX_AMMO_SLOTS; i++ ) + { + m_rgAmmo[i] = 0; + m_rgAmmoLast[i] = 0; // client ammo values also have to be reset (the death hud clear messages does on the client side) + } + + m_lastx = m_lasty = 0; + + m_flNextChatTime = gpGlobals->time; + + g_pGameRules->PlayerSpawn( this ); +} + + +void CBasePlayer :: Precache( void ) +{ + // in the event that the player JUST spawned, and the level node graph + // was loaded, fix all of the node graph pointers before the game starts. + + // !!!BUGBUG - now that we have multiplayer, this needs to be moved! + if ( WorldGraph.m_fGraphPresent && !WorldGraph.m_fGraphPointersSet ) + { + if ( !WorldGraph.FSetGraphPointers() ) + { + ALERT ( at_console, "**Graph pointers were not set!\n"); + } + else + { + ALERT ( at_console, "**Graph Pointers Set!\n" ); + } + } + + // SOUNDS / MODELS ARE PRECACHED in ClientPrecache() (game specific) + // because they need to precache before any clients have connected + + // init geiger counter vars during spawn and each time + // we cross a level transition + + m_flgeigerRange = 1000; + m_igeigerRangePrev = 1000; + + m_bitsDamageType = 0; + m_bitsHUDDamage = -1; + + m_iClientBattery = -1; + + m_iTrain = TRAIN_NEW; + + // Make sure any necessary user messages have been registered + LinkUserMessages(); + + m_iUpdateTime = 5; // won't update for 1/2 a second + + if ( gInitHUD ) + m_fInitHUD = TRUE; +} + + +int CBasePlayer::Save( CSave &save ) +{ + if ( !CBaseMonster::Save(save) ) + return 0; + + return save.WriteFields( "PLAYER", this, m_playerSaveData, ARRAYSIZE(m_playerSaveData) ); +} + + +// +// Marks everything as new so the player will resend this to the hud. +// +void CBasePlayer::RenewItems(void) +{ + +} + + +int CBasePlayer::Restore( CRestore &restore ) +{ + if ( !CBaseMonster::Restore(restore) ) + return 0; + + int status = restore.ReadFields( "PLAYER", this, m_playerSaveData, ARRAYSIZE(m_playerSaveData) ); + + SAVERESTOREDATA *pSaveData = (SAVERESTOREDATA *)gpGlobals->pSaveData; + // landmark isn't present. + if ( !pSaveData->fUseLandmark ) + { + ALERT( at_console, "No Landmark:%s\n", pSaveData->szLandmarkName ); + + // default to normal spawn + edict_t* pentSpawnSpot = EntSelectSpawnPoint( this ); + pev->origin = VARS(pentSpawnSpot)->origin + Vector(0,0,1); + pev->angles = VARS(pentSpawnSpot)->angles; + } + pev->v_angle.z = 0; // Clear out roll + pev->angles = pev->v_angle; + + pev->fixangle = TRUE; // turn this way immediately + +// Copied from spawn() for now + m_bloodColor = BLOOD_COLOR_RED; + + g_ulModelIndexPlayer = pev->modelindex; + + if ( FBitSet(pev->flags, FL_DUCKING) ) + { + // Use the crouch HACK + //FixPlayerCrouchStuck( edict() ); + // Don't need to do this with new player prediction code. + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + } + else + { + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + } + + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "hl", "1" ); + + if ( m_fLongJump ) + { + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "slj", "1" ); + } + else + { + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "slj", "0" ); + } + + RenewItems(); + +#if defined( CLIENT_WEAPONS ) + // HACK: This variable is saved/restored in CBaseMonster as a time variable, but we're using it + // as just a counter. Ideally, this needs its own variable that's saved as a plain float. + // Barring that, we clear it out here instead of using the incorrect restored time value. + m_flNextAttack = UTIL_WeaponTimeBase(); +#endif + + return status; +} + + + +void CBasePlayer::SelectNextItem( int iItem ) +{ + CBasePlayerItem *pItem; + + pItem = m_rgpPlayerItems[ iItem ]; + + if (!pItem) + return; + + if (pItem == m_pActiveItem) + { + // select the next one in the chain + pItem = m_pActiveItem->m_pNext; + if (! pItem) + { + return; + } + + CBasePlayerItem *pLast; + pLast = pItem; + while (pLast->m_pNext) + pLast = pLast->m_pNext; + + // relink chain + pLast->m_pNext = m_pActiveItem; + m_pActiveItem->m_pNext = NULL; + m_rgpPlayerItems[ iItem ] = pItem; + } + + ResetAutoaim( ); + + // FIX, this needs to queue them up and delay + if (m_pActiveItem) + { + m_pActiveItem->Holster( ); + } + + m_pActiveItem = pItem; + + if (m_pActiveItem) + { + m_pActiveItem->Deploy( ); + m_pActiveItem->UpdateItemInfo( ); + } +} + +void CBasePlayer::SelectItem(const char *pstr) +{ + if (!pstr) + return; + + CBasePlayerItem *pItem = NULL; + + for (int i = 0; i < MAX_ITEM_TYPES; i++) + { + if (m_rgpPlayerItems[i]) + { + pItem = m_rgpPlayerItems[i]; + + while (pItem) + { + if (FClassnameIs(pItem->pev, pstr)) + break; + pItem = pItem->m_pNext; + } + } + + if (pItem) + break; + } + + if (!pItem) + return; + + + if (pItem == m_pActiveItem) + return; + + ResetAutoaim( ); + + // FIX, this needs to queue them up and delay + if (m_pActiveItem) + m_pActiveItem->Holster( ); + + m_pLastItem = m_pActiveItem; + m_pActiveItem = pItem; + + if (m_pActiveItem) + { + m_pActiveItem->Deploy( ); + m_pActiveItem->UpdateItemInfo( ); + } +} + + +void CBasePlayer::SelectLastItem(void) +{ + if (!m_pLastItem) + { + return; + } + + if ( m_pActiveItem && !m_pActiveItem->CanHolster() ) + { + return; + } + + ResetAutoaim( ); + + // FIX, this needs to queue them up and delay + if (m_pActiveItem) + m_pActiveItem->Holster( ); + + CBasePlayerItem *pTemp = m_pActiveItem; + m_pActiveItem = m_pLastItem; + m_pLastItem = pTemp; + m_pActiveItem->Deploy( ); + m_pActiveItem->UpdateItemInfo( ); +} + +//============================================== +// HasWeapons - do I have any weapons at all? +//============================================== +BOOL CBasePlayer::HasWeapons( void ) +{ + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + return TRUE; + } + } + + return FALSE; +} + +void CBasePlayer::SelectPrevItem( int iItem ) +{ +} + + +const char *CBasePlayer::TeamID( void ) +{ + if ( pev == NULL ) // Not fully connected yet + return ""; + + // return their team name + return m_szTeamName; +} + + +//============================================== +// !!!UNDONE:ultra temporary SprayCan entity to apply +// decal frame at a time. For PreAlpha CD +//============================================== +class CSprayCan : public CBaseEntity +{ +public: + void Spawn ( entvars_t *pevOwner ); + void Think( void ); + + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +void CSprayCan::Spawn ( entvars_t *pevOwner ) +{ + pev->origin = pevOwner->origin + Vector ( 0 , 0 , 32 ); + pev->angles = pevOwner->v_angle; + pev->owner = ENT(pevOwner); + pev->frame = 0; + + pev->nextthink = gpGlobals->time + 0.1; + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/sprayer.wav", 1, ATTN_NORM); +} + +void CSprayCan::Think( void ) +{ + TraceResult tr; + int playernum; + int nFrames; + CBasePlayer *pPlayer; + + pPlayer = (CBasePlayer *)GET_PRIVATE(pev->owner); + + if (pPlayer) + nFrames = pPlayer->GetCustomDecalFrames(); + else + nFrames = -1; + + playernum = ENTINDEX(pev->owner); + + // ALERT(at_console, "Spray by player %i, %i of %i\n", playernum, (int)(pev->frame + 1), nFrames); + + UTIL_MakeVectors(pev->angles); + UTIL_TraceLine ( pev->origin, pev->origin + gpGlobals->v_forward * 128, ignore_monsters, pev->owner, & tr); + + // No customization present. + if (nFrames == -1) + { + UTIL_DecalTrace( &tr, DECAL_LAMBDA6 ); + UTIL_Remove( this ); + } + else + { + UTIL_PlayerDecalTrace( &tr, playernum, pev->frame, TRUE ); + // Just painted last custom frame. + if ( pev->frame++ >= (nFrames - 1)) + UTIL_Remove( this ); + } + + pev->nextthink = gpGlobals->time + 0.1; +} + +class CBloodSplat : public CBaseEntity +{ +public: + void Spawn ( entvars_t *pevOwner ); + void Spray ( void ); +}; + +void CBloodSplat::Spawn ( entvars_t *pevOwner ) +{ + pev->origin = pevOwner->origin + Vector ( 0 , 0 , 32 ); + pev->angles = pevOwner->v_angle; + pev->owner = ENT(pevOwner); + + SetThink ( &CBloodSplat::Spray ); + pev->nextthink = gpGlobals->time + 0.1; +} + +void CBloodSplat::Spray ( void ) +{ + TraceResult tr; + + if ( g_Language != LANGUAGE_GERMAN ) + { + UTIL_MakeVectors(pev->angles); + UTIL_TraceLine ( pev->origin, pev->origin + gpGlobals->v_forward * 128, ignore_monsters, pev->owner, & tr); + + UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_RED ); + } + SetThink ( &CBloodSplat::SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; +} + +//============================================== + + + +void CBasePlayer::GiveNamedItem( const char *pszName ) +{ + edict_t *pent; + + int istr = MAKE_STRING(pszName); + + pent = CREATE_NAMED_ENTITY(istr); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in GiveNamedItem!\n" ); + return; + } + VARS( pent )->origin = pev->origin; + pent->v.spawnflags |= SF_NORESPAWN; + + DispatchSpawn( pent ); + DispatchTouch( pent, ENT( pev ) ); +} + + + +CBaseEntity *FindEntityForward( CBaseEntity *pMe ) +{ + TraceResult tr; + + UTIL_MakeVectors(pMe->pev->v_angle); + UTIL_TraceLine(pMe->pev->origin + pMe->pev->view_ofs,pMe->pev->origin + pMe->pev->view_ofs + gpGlobals->v_forward * 8192,dont_ignore_monsters, pMe->edict(), &tr ); + if ( tr.flFraction != 1.0 && !FNullEnt( tr.pHit) ) + { + CBaseEntity *pHit = CBaseEntity::Instance( tr.pHit ); + return pHit; + } + return NULL; +} + + +BOOL CBasePlayer :: FlashlightIsOn( void ) +{ + return FBitSet(pev->effects, EF_DIMLIGHT); +} + + +void CBasePlayer :: FlashlightTurnOn( void ) +{ + if ( !g_pGameRules->FAllowFlashlight() ) + { + return; + } + + if ( (pev->weapons & (1<effects, EF_DIMLIGHT); + MESSAGE_BEGIN( MSG_ONE, gmsgFlashlight, NULL, pev ); + WRITE_BYTE(1); + WRITE_BYTE(m_iFlashBattery); + MESSAGE_END(); + + m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->time; + + } +} + + +void CBasePlayer :: FlashlightTurnOff( void ) +{ + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, SOUND_FLASHLIGHT_OFF, 1.0, ATTN_NORM, 0, PITCH_NORM ); + ClearBits(pev->effects, EF_DIMLIGHT); + MESSAGE_BEGIN( MSG_ONE, gmsgFlashlight, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(m_iFlashBattery); + MESSAGE_END(); + + m_flFlashLightTime = FLASH_CHARGE_TIME + gpGlobals->time; + +} + +/* +=============== +ForceClientDllUpdate + +When recording a demo, we need to have the server tell us the entire client state +so that the client side .dll can behave correctly. +Reset stuff so that the state is transmitted. +=============== +*/ +void CBasePlayer :: ForceClientDllUpdate( void ) +{ + m_iClientHealth = -1; + m_iClientBattery = -1; + m_iTrain |= TRAIN_NEW; // Force new train message. + m_fWeapon = FALSE; // Force weapon send + m_fKnownItem = FALSE; // Force weaponinit messages. + m_fInitHUD = TRUE; // Force HUD gmsgResetHUD message + + // Now force all the necessary messages + // to be sent. + UpdateClientData(); +} + +/* +============ +ImpulseCommands +============ +*/ +extern float g_flWeaponCheat; + +void CBasePlayer::ImpulseCommands( ) +{ + TraceResult tr;// UNDONE: kill me! This is temporary for PreAlpha CDs + + // Handle use events + PlayerUse(); + + int iImpulse = (int)pev->impulse; + switch (iImpulse) + { + case 99: + { + + int iOn; + + if (!gmsgLogo) + { + iOn = 1; + gmsgLogo = REG_USER_MSG("Logo", 1); + } + else + { + iOn = 0; + } + + ASSERT( gmsgLogo > 0 ); + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgLogo, NULL, pev ); + WRITE_BYTE(iOn); + MESSAGE_END(); + + if(!iOn) + gmsgLogo = 0; + break; + } + case 100: + // temporary flashlight for level designers + if ( FlashlightIsOn() ) + { + FlashlightTurnOff(); + } + else + { + FlashlightTurnOn(); + } + break; + + case 201:// paint decal + + if ( gpGlobals->time < m_flNextDecalTime ) + { + // too early! + break; + } + + UTIL_MakeVectors(pev->v_angle); + UTIL_TraceLine ( pev->origin + pev->view_ofs, pev->origin + pev->view_ofs + gpGlobals->v_forward * 128, ignore_monsters, ENT(pev), & tr); + + if ( tr.flFraction != 1.0 ) + {// line hit something, so paint a decal + m_flNextDecalTime = gpGlobals->time + decalfrequency.value; + CSprayCan *pCan = GetClassPtr((CSprayCan *)NULL); + pCan->Spawn( pev ); + } + + break; + + default: + // check all of the cheat impulse commands now + CheatImpulseCommands( iImpulse ); + break; + } + + pev->impulse = 0; +} + +//========================================================= +//========================================================= +void CBasePlayer::CheatImpulseCommands( int iImpulse ) +{ +#if !defined( HLDEMO_BUILD ) + if ( g_flWeaponCheat == 0.0 ) + { + return; + } + + CBaseEntity *pEntity; + TraceResult tr; + + switch ( iImpulse ) + { + case 76: + { + if (!giPrecacheGrunt) + { + giPrecacheGrunt = 1; + ALERT(at_console, "You must now restart to use Grunt-o-matic.\n"); + } + else + { + UTIL_MakeVectors( Vector( 0, pev->v_angle.y, 0 ) ); + Create("monster_human_grunt", pev->origin + gpGlobals->v_forward * 128, pev->angles); + } + break; + } + + + case 101: + gEvilImpulse101 = TRUE; + GiveNamedItem( "item_suit" ); + GiveNamedItem( "item_battery" ); + GiveNamedItem( "weapon_crowbar" ); + GiveNamedItem( "weapon_9mmhandgun" ); + GiveNamedItem( "ammo_9mmclip" ); + GiveNamedItem( "weapon_shotgun" ); + GiveNamedItem( "ammo_buckshot" ); + GiveNamedItem( "weapon_9mmAR" ); + GiveNamedItem( "ammo_9mmAR" ); + GiveNamedItem( "ammo_ARgrenades" ); + GiveNamedItem( "weapon_handgrenade" ); + GiveNamedItem( "weapon_tripmine" ); +#ifndef OEM_BUILD + GiveNamedItem( "weapon_357" ); + GiveNamedItem( "ammo_357" ); + GiveNamedItem( "weapon_crossbow" ); + GiveNamedItem( "ammo_crossbow" ); + GiveNamedItem( "weapon_egon" ); + GiveNamedItem( "weapon_gauss" ); + GiveNamedItem( "ammo_gaussclip" ); + GiveNamedItem( "weapon_rpg" ); + GiveNamedItem( "ammo_rpgclip" ); + GiveNamedItem( "weapon_satchel" ); + GiveNamedItem( "weapon_snark" ); + GiveNamedItem( "weapon_hornetgun" ); +#endif + gEvilImpulse101 = FALSE; + break; + + case 102: + // Gibbage!!! + CGib::SpawnRandomGibs( pev, 1, 1 ); + break; + + case 103: + // What the hell are you doing? + pEntity = FindEntityForward( this ); + if ( pEntity ) + { + CBaseMonster *pMonster = pEntity->MyMonsterPointer(); + if ( pMonster ) + pMonster->ReportAIState(); + } + break; + + case 104: + // Dump all of the global state varaibles (and global entity names) + gGlobalState.DumpGlobals(); + break; + + case 105:// player makes no sound for monsters to hear. + { + if ( m_fNoPlayerSound ) + { + ALERT ( at_console, "Player is audible\n" ); + m_fNoPlayerSound = FALSE; + } + else + { + ALERT ( at_console, "Player is silent\n" ); + m_fNoPlayerSound = TRUE; + } + break; + } + + case 106: + // Give me the classname and targetname of this entity. + pEntity = FindEntityForward( this ); + if ( pEntity ) + { + ALERT ( at_console, "Classname: %s", STRING( pEntity->pev->classname ) ); + + if ( !FStringNull ( pEntity->pev->targetname ) ) + { + ALERT ( at_console, " - Targetname: %s\n", STRING( pEntity->pev->targetname ) ); + } + else + { + ALERT ( at_console, " - TargetName: No Targetname\n" ); + } + + ALERT ( at_console, "Model: %s\n", STRING( pEntity->pev->model ) ); + if ( pEntity->pev->globalname ) + ALERT ( at_console, "Globalname: %s\n", STRING( pEntity->pev->globalname ) ); + } + break; + + case 107: + { + TraceResult tr; + + edict_t *pWorld = g_engfuncs.pfnPEntityOfEntIndex( 0 ); + + Vector start = pev->origin + pev->view_ofs; + Vector end = start + gpGlobals->v_forward * 1024; + UTIL_TraceLine( start, end, ignore_monsters, edict(), &tr ); + if ( tr.pHit ) + pWorld = tr.pHit; + const char *pTextureName = TRACE_TEXTURE( pWorld, start, end ); + if ( pTextureName ) + ALERT( at_console, "Texture: %s\n", pTextureName ); + } + break; + case 195:// show shortest paths for entire level to nearest node + { + Create("node_viewer_fly", pev->origin, pev->angles); + } + break; + case 196:// show shortest paths for entire level to nearest node + { + Create("node_viewer_large", pev->origin, pev->angles); + } + break; + case 197:// show shortest paths for entire level to nearest node + { + Create("node_viewer_human", pev->origin, pev->angles); + } + break; + case 199:// show nearest node and all connections + { + ALERT ( at_console, "%d\n", WorldGraph.FindNearestNode ( pev->origin, bits_NODE_GROUP_REALM ) ); + WorldGraph.ShowNodeConnections ( WorldGraph.FindNearestNode ( pev->origin, bits_NODE_GROUP_REALM ) ); + } + break; + case 202:// Random blood splatter + UTIL_MakeVectors(pev->v_angle); + UTIL_TraceLine ( pev->origin + pev->view_ofs, pev->origin + pev->view_ofs + gpGlobals->v_forward * 128, ignore_monsters, ENT(pev), & tr); + + if ( tr.flFraction != 1.0 ) + {// line hit something, so paint a decal + CBloodSplat *pBlood = GetClassPtr((CBloodSplat *)NULL); + pBlood->Spawn( pev ); + } + break; + case 203:// remove creature. + pEntity = FindEntityForward( this ); + if ( pEntity ) + { + if ( pEntity->pev->takedamage ) + pEntity->SetThink(&CBaseEntity::SUB_Remove); + } + break; + } +#endif // HLDEMO_BUILD +} + +// +// Add a weapon to the player (Item == Weapon == Selectable Object) +// +int CBasePlayer::AddPlayerItem( CBasePlayerItem *pItem ) +{ + CBasePlayerItem *pInsert; + + pInsert = m_rgpPlayerItems[pItem->iItemSlot()]; + + while (pInsert) + { + if (FClassnameIs( pInsert->pev, STRING( pItem->pev->classname) )) + { + if (pItem->AddDuplicate( pInsert )) + { + g_pGameRules->PlayerGotWeapon ( this, pItem ); + pItem->CheckRespawn(); + + // ugly hack to update clip w/o an update clip message + pInsert->UpdateItemInfo( ); + if (m_pActiveItem) + m_pActiveItem->UpdateItemInfo( ); + + pItem->Kill( ); + } + else if (gEvilImpulse101) + { + // FIXME: remove anyway for deathmatch testing + pItem->Kill( ); + } + return FALSE; + } + pInsert = pInsert->m_pNext; + } + + + if (pItem->AddToPlayer( this )) + { + g_pGameRules->PlayerGotWeapon ( this, pItem ); + pItem->CheckRespawn(); + + pItem->m_pNext = m_rgpPlayerItems[pItem->iItemSlot()]; + m_rgpPlayerItems[pItem->iItemSlot()] = pItem; + + // should we switch to this item? + if ( g_pGameRules->FShouldSwitchWeapon( this, pItem ) ) + { + SwitchWeapon( pItem ); + } + + return TRUE; + } + else if (gEvilImpulse101) + { + // FIXME: remove anyway for deathmatch testing + pItem->Kill( ); + } + return FALSE; +} + + + +int CBasePlayer::RemovePlayerItem( CBasePlayerItem *pItem ) +{ + if (m_pActiveItem == pItem) + { + ResetAutoaim( ); + pItem->Holster( ); + pItem->pev->nextthink = 0;// crowbar may be trying to swing again, etc. + pItem->SetThink( NULL ); + m_pActiveItem = NULL; + pev->viewmodel = 0; + pev->weaponmodel = 0; + } + else if ( m_pLastItem == pItem ) + m_pLastItem = NULL; + + CBasePlayerItem *pPrev = m_rgpPlayerItems[pItem->iItemSlot()]; + + if (pPrev == pItem) + { + m_rgpPlayerItems[pItem->iItemSlot()] = pItem->m_pNext; + return TRUE; + } + else + { + while (pPrev && pPrev->m_pNext != pItem) + { + pPrev = pPrev->m_pNext; + } + if (pPrev) + { + pPrev->m_pNext = pItem->m_pNext; + return TRUE; + } + } + return FALSE; +} + + +// +// Returns the unique ID for the ammo, or -1 if error +// +int CBasePlayer :: GiveAmmo( int iCount, char *szName, int iMax ) +{ + if ( !szName ) + { + // no ammo. + return -1; + } + + if ( !g_pGameRules->CanHaveAmmo( this, szName, iMax ) ) + { + // game rules say I can't have any more of this ammo type. + return -1; + } + + int i = 0; + + i = GetAmmoIndex( szName ); + + if ( i < 0 || i >= MAX_AMMO_SLOTS ) + return -1; + + int iAdd = min( iCount, iMax - m_rgAmmo[i] ); + if ( iAdd < 1 ) + return i; + + m_rgAmmo[ i ] += iAdd; + + + if ( gmsgAmmoPickup ) // make sure the ammo messages have been linked first + { + // Send the message that ammo has been picked up + MESSAGE_BEGIN( MSG_ONE, gmsgAmmoPickup, NULL, pev ); + WRITE_BYTE( GetAmmoIndex(szName) ); // ammo ID + WRITE_BYTE( iAdd ); // amount + MESSAGE_END(); + } + + TabulateAmmo(); + + return i; +} + + +/* +============ +ItemPreFrame + +Called every frame by the player PreThink +============ +*/ +void CBasePlayer::ItemPreFrame() +{ +#if defined( CLIENT_WEAPONS ) + if ( m_flNextAttack > 0 ) +#else + if ( gpGlobals->time < m_flNextAttack ) +#endif + { + return; + } + + if (!m_pActiveItem) + return; + + m_pActiveItem->ItemPreFrame( ); +} + + +/* +============ +ItemPostFrame + +Called every frame by the player PostThink +============ +*/ +void CBasePlayer::ItemPostFrame() +{ + static int fInSelect = FALSE; + + // check if the player is using a tank + if ( m_pTank != NULL ) + return; + +#if defined( CLIENT_WEAPONS ) + if ( m_flNextAttack > 0 ) +#else + if ( gpGlobals->time < m_flNextAttack ) +#endif + { + return; + } + + ImpulseCommands(); + + if (!m_pActiveItem) + return; + + m_pActiveItem->ItemPostFrame( ); +} + +int CBasePlayer::AmmoInventory( int iAmmoIndex ) +{ + if (iAmmoIndex == -1) + { + return -1; + } + + return m_rgAmmo[ iAmmoIndex ]; +} + +int CBasePlayer::GetAmmoIndex(const char *psz) +{ + int i; + + if (!psz) + return -1; + + for (i = 1; i < MAX_AMMO_SLOTS; i++) + { + if ( !CBasePlayerItem::AmmoInfoArray[i].pszName ) + continue; + + if (stricmp( psz, CBasePlayerItem::AmmoInfoArray[i].pszName ) == 0) + return i; + } + + return -1; +} + +// Called from UpdateClientData +// makes sure the client has all the necessary ammo info, if values have changed +void CBasePlayer::SendAmmoUpdate(void) +{ + for (int i=0; i < MAX_AMMO_SLOTS;i++) + { + if (m_rgAmmo[i] != m_rgAmmoLast[i]) + { + m_rgAmmoLast[i] = m_rgAmmo[i]; + + ASSERT( m_rgAmmo[i] >= 0 ); + ASSERT( m_rgAmmo[i] < 255 ); + + // send "Ammo" update message + MESSAGE_BEGIN( MSG_ONE, gmsgAmmoX, NULL, pev ); + WRITE_BYTE( i ); + WRITE_BYTE( max( min( m_rgAmmo[i], 254 ), 0 ) ); // clamp the value to one byte + MESSAGE_END(); + } + } +} + +/* +========================================================= + UpdateClientData + +resends any changed player HUD info to the client. +Called every frame by PlayerPreThink +Also called at start of demo recording and playback by +ForceClientDllUpdate to ensure the demo gets messages +reflecting all of the HUD state info. +========================================================= +*/ +void CBasePlayer :: UpdateClientData( void ) +{ + if (m_fInitHUD) + { + m_fInitHUD = FALSE; + gInitHUD = FALSE; + + MESSAGE_BEGIN( MSG_ONE, gmsgResetHUD, NULL, pev ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + + if ( !m_fGameHUDInitialized ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgInitHUD, NULL, pev ); + MESSAGE_END(); + + g_pGameRules->InitHUD( this ); + m_fGameHUDInitialized = TRUE; + + m_iObserverLastMode = OBS_ROAMING; + + if ( g_pGameRules->IsMultiplayer() ) + { + FireTargets( "game_playerjoin", this, this, USE_TOGGLE, 0 ); + } + } + + FireTargets( "game_playerspawn", this, this, USE_TOGGLE, 0 ); + + InitStatusBar(); + } + + if ( m_iHideHUD != m_iClientHideHUD ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgHideWeapon, NULL, pev ); + WRITE_BYTE( m_iHideHUD ); + MESSAGE_END(); + + m_iClientHideHUD = m_iHideHUD; + } + + if ( m_iFOV != m_iClientFOV ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgSetFOV, NULL, pev ); + WRITE_BYTE( m_iFOV ); + MESSAGE_END(); + + // cache FOV change at end of function, so weapon updates can see that FOV has changed + } + + // HACKHACK -- send the message to display the game title + if (gDisplayTitle) + { + MESSAGE_BEGIN( MSG_ONE, gmsgShowGameTitle, NULL, pev ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + gDisplayTitle = 0; + } + + if (pev->health != m_iClientHealth) + { +#define clamp( val, min, max ) ( ((val) > (max)) ? (max) : ( ((val) < (min)) ? (min) : (val) ) ) + int iHealth = clamp( pev->health, 0, 255 ); // make sure that no negative health values are sent + if ( pev->health > 0.0f && pev->health <= 1.0f ) + iHealth = 1; + + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgHealth, NULL, pev ); + WRITE_BYTE( iHealth ); + MESSAGE_END(); + + m_iClientHealth = pev->health; + } + + + if (pev->armorvalue != m_iClientBattery) + { + m_iClientBattery = pev->armorvalue; + + ASSERT( gmsgBattery > 0 ); + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgBattery, NULL, pev ); + WRITE_SHORT( (int)pev->armorvalue); + MESSAGE_END(); + } + + if (pev->dmg_take || pev->dmg_save || m_bitsHUDDamage != m_bitsDamageType) + { + // Comes from inside me if not set + Vector damageOrigin = pev->origin; + // send "damage" message + // causes screen to flash, and pain compass to show direction of damage + edict_t *other = pev->dmg_inflictor; + if ( other ) + { + CBaseEntity *pEntity = CBaseEntity::Instance(other); + if ( pEntity ) + damageOrigin = pEntity->Center(); + } + + // only send down damage type that have hud art + int visibleDamageBits = m_bitsDamageType & DMG_SHOWNHUD; + + MESSAGE_BEGIN( MSG_ONE, gmsgDamage, NULL, pev ); + WRITE_BYTE( pev->dmg_save ); + WRITE_BYTE( pev->dmg_take ); + WRITE_LONG( visibleDamageBits ); + WRITE_COORD( damageOrigin.x ); + WRITE_COORD( damageOrigin.y ); + WRITE_COORD( damageOrigin.z ); + MESSAGE_END(); + + pev->dmg_take = 0; + pev->dmg_save = 0; + m_bitsHUDDamage = m_bitsDamageType; + + // Clear off non-time-based damage indicators + m_bitsDamageType &= DMG_TIMEBASED; + } + + // Update Flashlight + if ((m_flFlashLightTime) && (m_flFlashLightTime <= gpGlobals->time)) + { + if (FlashlightIsOn()) + { + if (m_iFlashBattery) + { + m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->time; + m_iFlashBattery--; + + if (!m_iFlashBattery) + FlashlightTurnOff(); + } + } + else + { + if (m_iFlashBattery < 100) + { + m_flFlashLightTime = FLASH_CHARGE_TIME + gpGlobals->time; + m_iFlashBattery++; + } + else + m_flFlashLightTime = 0; + } + + MESSAGE_BEGIN( MSG_ONE, gmsgFlashBattery, NULL, pev ); + WRITE_BYTE(m_iFlashBattery); + MESSAGE_END(); + } + + + if (m_iTrain & TRAIN_NEW) + { + ASSERT( gmsgTrain > 0 ); + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgTrain, NULL, pev ); + WRITE_BYTE(m_iTrain & 0xF); + MESSAGE_END(); + + m_iTrain &= ~TRAIN_NEW; + } + + // + // New Weapon? + // + if (!m_fKnownItem) + { + m_fKnownItem = TRUE; + + // WeaponInit Message + // byte = # of weapons + // + // for each weapon: + // byte name str length (not including null) + // bytes... name + // byte Ammo Type + // byte Ammo2 Type + // byte bucket + // byte bucket pos + // byte flags + // ???? Icons + + // Send ALL the weapon info now + int i; + + for (i = 0; i < MAX_WEAPONS; i++) + { + ItemInfo& II = CBasePlayerItem::ItemInfoArray[i]; + + if ( !II.iId ) + continue; + + const char *pszName; + if (!II.pszName) + pszName = "Empty"; + else + pszName = II.pszName; + + MESSAGE_BEGIN( MSG_ONE, gmsgWeaponList, NULL, pev ); + WRITE_STRING(pszName); // string weapon name + WRITE_BYTE(GetAmmoIndex(II.pszAmmo1)); // byte Ammo Type + WRITE_BYTE(II.iMaxAmmo1); // byte Max Ammo 1 + WRITE_BYTE(GetAmmoIndex(II.pszAmmo2)); // byte Ammo2 Type + WRITE_BYTE(II.iMaxAmmo2); // byte Max Ammo 2 + WRITE_BYTE(II.iSlot); // byte bucket + WRITE_BYTE(II.iPosition); // byte bucket pos + WRITE_BYTE(II.iId); // byte id (bit index into pev->weapons) + WRITE_BYTE(II.iFlags); // byte Flags + MESSAGE_END(); + } + } + + + SendAmmoUpdate(); + + // Update all the items + for ( int i = 0; i < MAX_ITEM_TYPES; i++ ) + { + if ( m_rgpPlayerItems[i] ) // each item updates it's successors + m_rgpPlayerItems[i]->UpdateClientData( this ); + } + + // Cache and client weapon change + m_pClientActiveItem = m_pActiveItem; + m_iClientFOV = m_iFOV; + + // Update Status Bar + if ( m_flNextSBarUpdateTime < gpGlobals->time ) + { + UpdateStatusBar(); + m_flNextSBarUpdateTime = gpGlobals->time + 0.2; + } +} + + +//========================================================= +// FBecomeProne - Overridden for the player to set the proper +// physics flags when a barnacle grabs player. +//========================================================= +BOOL CBasePlayer :: FBecomeProne ( void ) +{ + m_afPhysicsFlags |= PFLAG_ONBARNACLE; + return TRUE; +} + +//========================================================= +// BarnacleVictimBitten - bad name for a function that is called +// by Barnacle victims when the barnacle pulls their head +// into its mouth. For the player, just die. +//========================================================= +void CBasePlayer :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) +{ + TakeDamage ( pevBarnacle, pevBarnacle, pev->health + pev->armorvalue, DMG_SLASH | DMG_ALWAYSGIB ); +} + +//========================================================= +// BarnacleVictimReleased - overridden for player who has +// physics flags concerns. +//========================================================= +void CBasePlayer :: BarnacleVictimReleased ( void ) +{ + m_afPhysicsFlags &= ~PFLAG_ONBARNACLE; +} + + +//========================================================= +// Illumination +// return player light level plus virtual muzzle flash +//========================================================= +int CBasePlayer :: Illumination( void ) +{ + int iIllum = CBaseEntity::Illumination( ); + + iIllum += m_iWeaponFlash; + if (iIllum > 255) + return 255; + return iIllum; +} + + +void CBasePlayer :: EnableControl(BOOL fControl) +{ + if (!fControl) + pev->flags |= FL_FROZEN; + else + pev->flags &= ~FL_FROZEN; + +} + + +#define DOT_1DEGREE 0.9998476951564 +#define DOT_2DEGREE 0.9993908270191 +#define DOT_3DEGREE 0.9986295347546 +#define DOT_4DEGREE 0.9975640502598 +#define DOT_5DEGREE 0.9961946980917 +#define DOT_6DEGREE 0.9945218953683 +#define DOT_7DEGREE 0.9925461516413 +#define DOT_8DEGREE 0.9902680687416 +#define DOT_9DEGREE 0.9876883405951 +#define DOT_10DEGREE 0.9848077530122 +#define DOT_15DEGREE 0.9659258262891 +#define DOT_20DEGREE 0.9396926207859 +#define DOT_25DEGREE 0.9063077870367 + +//========================================================= +// Autoaim +// set crosshair position to point to enemey +//========================================================= +Vector CBasePlayer :: GetAutoaimVector( float flDelta ) +{ + if (g_iSkillLevel == SKILL_HARD) + { + UTIL_MakeVectors( pev->v_angle + pev->punchangle ); + return gpGlobals->v_forward; + } + + Vector vecSrc = GetGunPosition( ); + float flDist = 8192; + + // always use non-sticky autoaim + // UNDONE: use sever variable to chose! + if (1 || g_iSkillLevel == SKILL_MEDIUM) + { + m_vecAutoAim = Vector( 0, 0, 0 ); + // flDelta *= 0.5; + } + + BOOL m_fOldTargeting = m_fOnTarget; + Vector angles = AutoaimDeflection(vecSrc, flDist, flDelta ); + + // update ontarget if changed + if ( !g_pGameRules->AllowAutoTargetCrosshair() ) + m_fOnTarget = 0; + else if (m_fOldTargeting != m_fOnTarget) + { + m_pActiveItem->UpdateItemInfo( ); + } + + if (angles.x > 180) + angles.x -= 360; + if (angles.x < -180) + angles.x += 360; + if (angles.y > 180) + angles.y -= 360; + if (angles.y < -180) + angles.y += 360; + + if (angles.x > 25) + angles.x = 25; + if (angles.x < -25) + angles.x = -25; + if (angles.y > 12) + angles.y = 12; + if (angles.y < -12) + angles.y = -12; + + + // always use non-sticky autoaim + // UNDONE: use sever variable to chose! + if (0 || g_iSkillLevel == SKILL_EASY) + { + m_vecAutoAim = m_vecAutoAim * 0.67 + angles * 0.33; + } + else + { + m_vecAutoAim = angles * 0.9; + } + + // m_vecAutoAim = m_vecAutoAim * 0.99; + + // Don't send across network if sv_aim is 0 + if ( g_psv_aim->value != 0 ) + { + if ( m_vecAutoAim.x != m_lastx || + m_vecAutoAim.y != m_lasty ) + { + SET_CROSSHAIRANGLE( edict(), -m_vecAutoAim.x, m_vecAutoAim.y ); + + m_lastx = m_vecAutoAim.x; + m_lasty = m_vecAutoAim.y; + } + } + + // ALERT( at_console, "%f %f\n", angles.x, angles.y ); + + UTIL_MakeVectors( pev->v_angle + pev->punchangle + m_vecAutoAim ); + return gpGlobals->v_forward; +} + + +Vector CBasePlayer :: AutoaimDeflection( Vector &vecSrc, float flDist, float flDelta ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + float bestdot; + Vector bestdir; + edict_t *bestent; + TraceResult tr; + + if ( g_psv_aim->value == 0 ) + { + m_fOnTarget = FALSE; + return g_vecZero; + } + + UTIL_MakeVectors( pev->v_angle + pev->punchangle + m_vecAutoAim ); + + // try all possible entities + bestdir = gpGlobals->v_forward; + bestdot = flDelta; // +- 10 degrees + bestent = NULL; + + m_fOnTarget = FALSE; + + UTIL_TraceLine( vecSrc, vecSrc + bestdir * flDist, dont_ignore_monsters, edict(), &tr ); + + + if ( tr.pHit && tr.pHit->v.takedamage != DAMAGE_NO) + { + // don't look through water + if (!((pev->waterlevel != 3 && tr.pHit->v.waterlevel == 3) + || (pev->waterlevel == 3 && tr.pHit->v.waterlevel == 0))) + { + if (tr.pHit->v.takedamage == DAMAGE_AIM) + m_fOnTarget = TRUE; + + return m_vecAutoAim; + } + } + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + Vector center; + Vector dir; + float dot; + + if ( pEdict->free ) // Not in use + continue; + + if (pEdict->v.takedamage != DAMAGE_AIM) + continue; + if (pEdict == edict()) + continue; +// if (pev->team > 0 && pEdict->v.team == pev->team) +// continue; // don't aim at teammate + if ( !g_pGameRules->ShouldAutoAim( this, pEdict ) ) + continue; + + pEntity = Instance( pEdict ); + if (pEntity == NULL) + continue; + + if (!pEntity->IsAlive()) + continue; + + // don't look through water + if ((pev->waterlevel != 3 && pEntity->pev->waterlevel == 3) + || (pev->waterlevel == 3 && pEntity->pev->waterlevel == 0)) + continue; + + center = pEntity->BodyTarget( vecSrc ); + + dir = (center - vecSrc).Normalize( ); + + // make sure it's in front of the player + if (DotProduct (dir, gpGlobals->v_forward ) < 0) + continue; + + dot = fabs( DotProduct (dir, gpGlobals->v_right ) ) + + fabs( DotProduct (dir, gpGlobals->v_up ) ) * 0.5; + + // tweek for distance + dot *= 1.0 + 0.2 * ((center - vecSrc).Length() / flDist); + + if (dot > bestdot) + continue; // to far to turn + + UTIL_TraceLine( vecSrc, center, dont_ignore_monsters, edict(), &tr ); + if (tr.flFraction != 1.0 && tr.pHit != pEdict) + { + // ALERT( at_console, "hit %s, can't see %s\n", STRING( tr.pHit->v.classname ), STRING( pEdict->v.classname ) ); + continue; + } + + // don't shoot at friends + if (IRelationship( pEntity ) < 0) + { + if ( !pEntity->IsPlayer() && !g_pGameRules->IsDeathmatch()) + // ALERT( at_console, "friend\n"); + continue; + } + + // can shoot at this one + bestdot = dot; + bestent = pEdict; + bestdir = dir; + } + + if (bestent) + { + bestdir = UTIL_VecToAngles (bestdir); + bestdir.x = -bestdir.x; + bestdir = bestdir - pev->v_angle - pev->punchangle; + + if (bestent->v.takedamage == DAMAGE_AIM) + m_fOnTarget = TRUE; + + return bestdir; + } + + return Vector( 0, 0, 0 ); +} + + +void CBasePlayer :: ResetAutoaim( ) +{ + if (m_vecAutoAim.x != 0 || m_vecAutoAim.y != 0) + { + m_vecAutoAim = Vector( 0, 0, 0 ); + SET_CROSSHAIRANGLE( edict(), 0, 0 ); + } + m_fOnTarget = FALSE; +} + +/* +============= +SetCustomDecalFrames + + UNDONE: Determine real frame limit, 8 is a placeholder. + Note: -1 means no custom frames present. +============= +*/ +void CBasePlayer :: SetCustomDecalFrames( int nFrames ) +{ + if (nFrames > 0 && + nFrames < 8) + m_nCustomSprayFrames = nFrames; + else + m_nCustomSprayFrames = -1; +} + +/* +============= +GetCustomDecalFrames + + Returns the # of custom frames this player's custom clan logo contains. +============= +*/ +int CBasePlayer :: GetCustomDecalFrames( void ) +{ + return m_nCustomSprayFrames; +} + + +//========================================================= +// DropPlayerItem - drop the named item, or if no name, +// the active item. +//========================================================= +void CBasePlayer::DropPlayerItem ( char *pszItemName ) +{ + if ( !g_pGameRules->IsMultiplayer() || (weaponstay.value > 0) ) + { + // no dropping in single player. + return; + } + + if ( !strlen( pszItemName ) ) + { + // if this string has no length, the client didn't type a name! + // assume player wants to drop the active item. + // make the string null to make future operations in this function easier + pszItemName = NULL; + } + + CBasePlayerItem *pWeapon; + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pWeapon = m_rgpPlayerItems[ i ]; + + while ( pWeapon ) + { + if ( pszItemName ) + { + // try to match by name. + if ( !strcmp( pszItemName, STRING( pWeapon->pev->classname ) ) ) + { + // match! + break; + } + } + else + { + // trying to drop active item + if ( pWeapon == m_pActiveItem ) + { + // active item! + break; + } + } + + pWeapon = pWeapon->m_pNext; + } + + + // if we land here with a valid pWeapon pointer, that's because we found the + // item we want to drop and hit a BREAK; pWeapon is the item. + if ( pWeapon ) + { + if ( !g_pGameRules->GetNextBestWeapon( this, pWeapon ) ) + return; // can't drop the item they asked for, may be our last item or something we can't holster + + UTIL_MakeVectors ( pev->angles ); + + pev->weapons &= ~(1<m_iId);// take item off hud + + CWeaponBox *pWeaponBox = (CWeaponBox *)CBaseEntity::Create( "weaponbox", pev->origin + gpGlobals->v_forward * 10, pev->angles, edict() ); + pWeaponBox->pev->angles.x = 0; + pWeaponBox->pev->angles.z = 0; + pWeaponBox->PackWeapon( pWeapon ); + pWeaponBox->pev->velocity = gpGlobals->v_forward * 300 + gpGlobals->v_forward * 100; + + // drop half of the ammo for this weapon. + int iAmmoIndex; + + iAmmoIndex = GetAmmoIndex ( pWeapon->pszAmmo1() ); // ??? + + if ( iAmmoIndex != -1 ) + { + // this weapon weapon uses ammo, so pack an appropriate amount. + if ( pWeapon->iFlags() & ITEM_FLAG_EXHAUSTIBLE ) + { + // pack up all the ammo, this weapon is its own ammo type + pWeaponBox->PackAmmo( MAKE_STRING(pWeapon->pszAmmo1()), m_rgAmmo[ iAmmoIndex ] ); + m_rgAmmo[ iAmmoIndex ] = 0; + + } + else + { + // pack half of the ammo + pWeaponBox->PackAmmo( MAKE_STRING(pWeapon->pszAmmo1()), m_rgAmmo[ iAmmoIndex ] / 2 ); + m_rgAmmo[ iAmmoIndex ] /= 2; + } + + } + + return;// we're done, so stop searching with the FOR loop. + } + } +} + +//========================================================= +// HasPlayerItem Does the player already have this item? +//========================================================= +BOOL CBasePlayer::HasPlayerItem( CBasePlayerItem *pCheckItem ) +{ + CBasePlayerItem *pItem = m_rgpPlayerItems[pCheckItem->iItemSlot()]; + + while (pItem) + { + if (FClassnameIs( pItem->pev, STRING( pCheckItem->pev->classname) )) + { + return TRUE; + } + pItem = pItem->m_pNext; + } + + return FALSE; +} + +//========================================================= +// HasNamedPlayerItem Does the player already have this item? +//========================================================= +BOOL CBasePlayer::HasNamedPlayerItem( const char *pszItemName ) +{ + CBasePlayerItem *pItem; + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pItem = m_rgpPlayerItems[ i ]; + + while (pItem) + { + if ( !strcmp( pszItemName, STRING( pItem->pev->classname ) ) ) + { + return TRUE; + } + pItem = pItem->m_pNext; + } + } + + return FALSE; +} + +//========================================================= +// +//========================================================= +BOOL CBasePlayer :: SwitchWeapon( CBasePlayerItem *pWeapon ) +{ + if ( !pWeapon->CanDeploy() ) + { + return FALSE; + } + + ResetAutoaim( ); + + if (m_pActiveItem) + { + m_pActiveItem->Holster( ); + } + + m_pActiveItem = pWeapon; + pWeapon->Deploy( ); + + return TRUE; +} + +//========================================================= +// Dead HEV suit prop +//========================================================= +class CDeadHEV : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_HUMAN_MILITARY; } + + void KeyValue( KeyValueData *pkvd ); + + int m_iPose;// which sequence to display -- temporary, don't need to save + static char *m_szPoses[4]; +}; + +char *CDeadHEV::m_szPoses[] = { "deadback", "deadsitting", "deadstomach", "deadtable" }; + +void CDeadHEV::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "pose")) + { + m_iPose = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +LINK_ENTITY_TO_CLASS( monster_hevsuit_dead, CDeadHEV ); + +//========================================================= +// ********** DeadHEV SPAWN ********** +//========================================================= +void CDeadHEV :: Spawn( void ) +{ + PRECACHE_MODEL("models/player.mdl"); + SET_MODEL(ENT(pev), "models/player.mdl"); + + pev->effects = 0; + pev->yaw_speed = 8; + pev->sequence = 0; + pev->body = 1; + m_bloodColor = BLOOD_COLOR_RED; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + + if (pev->sequence == -1) + { + ALERT ( at_console, "Dead hevsuit with bad pose\n" ); + pev->sequence = 0; + pev->effects = EF_BRIGHTFIELD; + } + + // Corpses have less health + pev->health = 8; + + MonsterInitDead(); +} + + +class CStripWeapons : public CPointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +private: +}; + +LINK_ENTITY_TO_CLASS( player_weaponstrip, CStripWeapons ); + +void CStripWeapons :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBasePlayer *pPlayer = NULL; + + if ( pActivator && pActivator->IsPlayer() ) + { + pPlayer = (CBasePlayer *)pActivator; + } + else if ( !g_pGameRules->IsDeathmatch() ) + { + pPlayer = (CBasePlayer *)CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) ); + } + + if ( pPlayer ) + pPlayer->RemoveAllItems( FALSE ); +} + + +class CRevertSaved : public CPointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT MessageThink( void ); + void EXPORT LoadThink( void ); + void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + inline float Duration( void ) { return pev->dmg_take; } + inline float HoldTime( void ) { return pev->dmg_save; } + inline float MessageTime( void ) { return m_messageTime; } + inline float LoadTime( void ) { return m_loadTime; } + + inline void SetDuration( float duration ) { pev->dmg_take = duration; } + inline void SetHoldTime( float hold ) { pev->dmg_save = hold; } + inline void SetMessageTime( float time ) { m_messageTime = time; } + inline void SetLoadTime( float time ) { m_loadTime = time; } + +private: + float m_messageTime; + float m_loadTime; +}; + +LINK_ENTITY_TO_CLASS( player_loadsaved, CRevertSaved ); + +TYPEDESCRIPTION CRevertSaved::m_SaveData[] = +{ + DEFINE_FIELD( CRevertSaved, m_messageTime, FIELD_FLOAT ), // These are not actual times, but durations, so save as floats + DEFINE_FIELD( CRevertSaved, m_loadTime, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CRevertSaved, CPointEntity ); + +void CRevertSaved :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "duration")) + { + SetDuration( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "holdtime")) + { + SetHoldTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "messagetime")) + { + SetMessageTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "loadtime")) + { + SetLoadTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CRevertSaved :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + UTIL_ScreenFadeAll( pev->rendercolor, Duration(), HoldTime(), pev->renderamt, FFADE_OUT ); + pev->nextthink = gpGlobals->time + MessageTime(); + SetThink( &CRevertSaved::MessageThink ); +} + + +void CRevertSaved :: MessageThink( void ) +{ + UTIL_ShowMessageAll( STRING(pev->message) ); + float nextThink = LoadTime() - MessageTime(); + if ( nextThink > 0 ) + { + pev->nextthink = gpGlobals->time + nextThink; + SetThink( &CRevertSaved::LoadThink ); + } + else + LoadThink(); +} + + +void CRevertSaved :: LoadThink( void ) +{ + if ( !gpGlobals->deathmatch ) + { + SERVER_COMMAND("reload\n"); + } +} + + +//========================================================= +// Multiplayer intermission spots. +//========================================================= +class CInfoIntermission:public CPointEntity +{ + void Spawn( void ); + void Think( void ); +}; + +void CInfoIntermission::Spawn( void ) +{ + UTIL_SetOrigin( pev, pev->origin ); + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + pev->v_angle = g_vecZero; + + pev->nextthink = gpGlobals->time + 2;// let targets spawn! + +} + +void CInfoIntermission::Think ( void ) +{ + edict_t *pTarget; + + // find my target + pTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) ); + + if ( !FNullEnt(pTarget) ) + { + pev->v_angle = UTIL_VecToAngles( (pTarget->v.origin - pev->origin).Normalize() ); + pev->v_angle.x = -pev->v_angle.x; + } +} + +LINK_ENTITY_TO_CLASS( info_intermission, CInfoIntermission ); + diff --git a/dlls/player.h b/dlls/player.h new file mode 100644 index 0000000..0181509 --- /dev/null +++ b/dlls/player.h @@ -0,0 +1,337 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PLAYER_H +#define PLAYER_H + + +#include "pm_materials.h" + + +#define PLAYER_FATAL_FALL_SPEED 1024// approx 60 feet +#define PLAYER_MAX_SAFE_FALL_SPEED 580// approx 20 feet +#define DAMAGE_FOR_FALL_SPEED (float) 100 / ( PLAYER_FATAL_FALL_SPEED - PLAYER_MAX_SAFE_FALL_SPEED )// damage per unit per second. +#define PLAYER_MIN_BOUNCE_SPEED 200 +#define PLAYER_FALL_PUNCH_THRESHHOLD (float)350 // won't punch player's screen/make scrape noise unless player falling at least this fast. + +// +// Player PHYSICS FLAGS bits +// +#define PFLAG_ONLADDER ( 1<<0 ) +#define PFLAG_ONSWING ( 1<<0 ) +#define PFLAG_ONTRAIN ( 1<<1 ) +#define PFLAG_ONBARNACLE ( 1<<2 ) +#define PFLAG_DUCKING ( 1<<3 ) // In the process of ducking, but totally squatted yet +#define PFLAG_USING ( 1<<4 ) // Using a continuous entity +#define PFLAG_OBSERVER ( 1<<5 ) // player is locked in stationary cam mode. Spectators can move, observers can't. + +// +// generic player +// +//----------------------------------------------------- +//This is Half-Life player entity +//----------------------------------------------------- +#define CSUITPLAYLIST 4 // max of 4 suit sentences queued up at any time + +#define SUIT_GROUP TRUE +#define SUIT_SENTENCE FALSE + +#define SUIT_REPEAT_OK 0 +#define SUIT_NEXT_IN_30SEC 30 +#define SUIT_NEXT_IN_1MIN 60 +#define SUIT_NEXT_IN_5MIN 300 +#define SUIT_NEXT_IN_10MIN 600 +#define SUIT_NEXT_IN_30MIN 1800 +#define SUIT_NEXT_IN_1HOUR 3600 + +#define CSUITNOREPEAT 32 + +#define SOUND_FLASHLIGHT_ON "items/flashlight1.wav" +#define SOUND_FLASHLIGHT_OFF "items/flashlight1.wav" + +#define TEAM_NAME_LENGTH 16 + +typedef enum +{ + PLAYER_IDLE, + PLAYER_WALK, + PLAYER_JUMP, + PLAYER_SUPERJUMP, + PLAYER_DIE, + PLAYER_ATTACK1, +} PLAYER_ANIM; + +#define MAX_ID_RANGE 2048 +#define SBAR_STRING_SIZE 128 + +enum sbar_data +{ + SBAR_ID_TARGETNAME = 1, + SBAR_ID_TARGETHEALTH, + SBAR_ID_TARGETARMOR, + SBAR_END, +}; + +#define CHAT_INTERVAL 1.0f + +class CBasePlayer : public CBaseMonster +{ +public: + + // Spectator camera + void Observer_FindNextPlayer( bool bReverse ); + void Observer_HandleButtons(); + void Observer_SetMode( int iMode ); + void Observer_CheckTarget(); + void Observer_CheckProperties(); + EHANDLE m_hObserverTarget; + float m_flNextObserverInput; + int m_iObserverWeapon; // weapon of current tracked target + int m_iObserverLastMode;// last used observer mode + int IsObserver() { return pev->iuser1; }; + + int random_seed; // See that is shared between client & server for shared weapons code + + int m_iPlayerSound;// the index of the sound list slot reserved for this player + int m_iTargetVolume;// ideal sound volume. + int m_iWeaponVolume;// how loud the player's weapon is right now. + int m_iExtraSoundTypes;// additional classification for this weapon's sound + int m_iWeaponFlash;// brightness of the weapon flash + float m_flStopExtraSoundTime; + + float m_flFlashLightTime; // Time until next battery draw/Recharge + int m_iFlashBattery; // Flashlight Battery Draw + + int m_afButtonLast; + int m_afButtonPressed; + int m_afButtonReleased; + + edict_t *m_pentSndLast; // last sound entity to modify player room type + float m_flSndRoomtype; // last roomtype set by sound entity + float m_flSndRange; // dist from player to sound entity + + float m_flFallVelocity; + + int m_rgItems[MAX_ITEMS]; + int m_fKnownItem; // True when a new item needs to be added + int m_fNewAmmo; // True when a new item has been added + + unsigned int m_afPhysicsFlags; // physics flags - set when 'normal' physics should be revisited or overriden + float m_fNextSuicideTime; // the time after which the player can next use the suicide command + + +// these are time-sensitive things that we keep track of + float m_flTimeStepSound; // when the last stepping sound was made + float m_flTimeWeaponIdle; // when to play another weapon idle animation. + float m_flSwimTime; // how long player has been underwater + float m_flDuckTime; // how long we've been ducking + float m_flWallJumpTime; // how long until next walljump + + float m_flSuitUpdate; // when to play next suit update + int m_rgSuitPlayList[CSUITPLAYLIST];// next sentencenum to play for suit update + int m_iSuitPlayNext; // next sentence slot for queue storage; + int m_rgiSuitNoRepeat[CSUITNOREPEAT]; // suit sentence no repeat list + float m_rgflSuitNoRepeatTime[CSUITNOREPEAT]; // how long to wait before allowing repeat + int m_lastDamageAmount; // Last damage taken + float m_tbdPrev; // Time-based damage timer + + float m_flgeigerRange; // range to nearest radiation source + float m_flgeigerDelay; // delay per update of range msg to client + int m_igeigerRangePrev; + int m_iStepLeft; // alternate left/right foot stepping sound + char m_szTextureName[CBTEXTURENAMEMAX]; // current texture name we're standing on + char m_chTextureType; // current texture type + + int m_idrowndmg; // track drowning damage taken + int m_idrownrestored; // track drowning damage restored + + int m_bitsHUDDamage; // Damage bits for the current fame. These get sent to + // the hude via the DAMAGE message + BOOL m_fInitHUD; // True when deferred HUD restart msg needs to be sent + BOOL m_fGameHUDInitialized; + int m_iTrain; // Train control position + BOOL m_fWeapon; // Set this to FALSE to force a reset of the current weapon HUD info + + EHANDLE m_pTank; // the tank which the player is currently controlling, NULL if no tank + float m_fDeadTime; // the time at which the player died (used in PlayerDeathThink()) + + BOOL m_fNoPlayerSound; // a debugging feature. Player makes no sound if this is true. + BOOL m_fLongJump; // does this player have the longjump module? + + float m_tSneaking; + int m_iUpdateTime; // stores the number of frame ticks before sending HUD update messages + int m_iClientHealth; // the health currently known by the client. If this changes, send a new + int m_iClientBattery; // the Battery currently known by the client. If this changes, send a new + int m_iHideHUD; // the players hud weapon info is to be hidden + int m_iClientHideHUD; + int m_iFOV; // field of view + int m_iClientFOV; // client's known FOV + // usable player items + CBasePlayerItem *m_rgpPlayerItems[MAX_ITEM_TYPES]; + CBasePlayerItem *m_pActiveItem; + CBasePlayerItem *m_pClientActiveItem; // client version of the active item + CBasePlayerItem *m_pLastItem; + // shared ammo slots + int m_rgAmmo[MAX_AMMO_SLOTS]; + int m_rgAmmoLast[MAX_AMMO_SLOTS]; + + Vector m_vecAutoAim; + BOOL m_fOnTarget; + int m_iDeaths; + float m_iRespawnFrames; // used in PlayerDeathThink() to make sure players can always respawn + + int m_lastx, m_lasty; // These are the previous update's crosshair angles, DON"T SAVE/RESTORE + + int m_nCustomSprayFrames;// Custom clan logo frames for this player + float m_flNextDecalTime;// next time this player can spray a decal + + char m_szTeamName[TEAM_NAME_LENGTH]; + + virtual void Spawn( void ); + void Pain( void ); + +// virtual void Think( void ); + virtual void Jump( void ); + virtual void Duck( void ); + virtual void PreThink( void ); + virtual void PostThink( void ); + virtual Vector GetGunPosition( void ); + virtual int TakeHealth( float flHealth, int bitsDamageType ); + virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + virtual void Killed( entvars_t *pevAttacker, int iGib ); + virtual Vector BodyTarget( const Vector &posSrc ) { return Center( ) + pev->view_ofs * RANDOM_FLOAT( 0.5, 1.1 ); }; // position to shoot at + virtual void StartSneaking( void ) { m_tSneaking = gpGlobals->time - 1; } + virtual void StopSneaking( void ) { m_tSneaking = gpGlobals->time + 30; } + virtual BOOL IsSneaking( void ) { return m_tSneaking <= gpGlobals->time; } + virtual BOOL IsAlive( void ) { return (pev->deadflag == DEAD_NO) && pev->health > 0; } + virtual BOOL ShouldFadeOnDeath( void ) { return FALSE; } + virtual BOOL IsPlayer( void ) { return TRUE; } // Spectators should return FALSE for this, they aren't "players" as far as game logic is concerned + + virtual BOOL IsNetClient( void ) { return TRUE; } // Bots should return FALSE for this, they can't receive NET messages + // Spectators should return TRUE for this + virtual const char *TeamID( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + void RenewItems(void); + void PackDeadPlayerItems( void ); + void RemoveAllItems( BOOL removeSuit ); + BOOL SwitchWeapon( CBasePlayerItem *pWeapon ); + + // JOHN: sends custom messages if player HUD data has changed (eg health, ammo) + virtual void UpdateClientData( void ); + + static TYPEDESCRIPTION m_playerSaveData[]; + + // Player is moved across the transition by other means + virtual int ObjectCaps( void ) { return CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual void Precache( void ); + BOOL IsOnLadder( void ); + BOOL FlashlightIsOn( void ); + void FlashlightTurnOn( void ); + void FlashlightTurnOff( void ); + + void UpdatePlayerSound ( void ); + void DeathSound ( void ); + + int Classify ( void ); + void SetAnimation( PLAYER_ANIM playerAnim ); + void SetWeaponAnimType( const char *szExtention ); + char m_szAnimExtention[32]; + + // custom player functions + virtual void ImpulseCommands( void ); + void CheatImpulseCommands( int iImpulse ); + + void StartDeathCam( void ); + void StartObserver( Vector vecPosition, Vector vecViewAngle ); + + void AddPoints( int score, BOOL bAllowNegativeScore ); + void AddPointsToTeam( int score, BOOL bAllowNegativeScore ); + BOOL AddPlayerItem( CBasePlayerItem *pItem ); + BOOL RemovePlayerItem( CBasePlayerItem *pItem ); + void DropPlayerItem ( char *pszItemName ); + BOOL HasPlayerItem( CBasePlayerItem *pCheckItem ); + BOOL HasNamedPlayerItem( const char *pszItemName ); + BOOL HasWeapons( void );// do I have ANY weapons? + void SelectPrevItem( int iItem ); + void SelectNextItem( int iItem ); + void SelectLastItem(void); + void SelectItem(const char *pstr); + void ItemPreFrame( void ); + void ItemPostFrame( void ); + void GiveNamedItem( const char *szName ); + void EnableControl(BOOL fControl); + + int GiveAmmo( int iAmount, char *szName, int iMax ); + void SendAmmoUpdate(void); + + void WaterMove( void ); + void EXPORT PlayerDeathThink( void ); + void PlayerUse( void ); + + void CheckSuitUpdate(); + void SetSuitUpdate(char *name, int fgroup, int iNoRepeat); + void UpdateGeigerCounter( void ); + void CheckTimeBasedDamage( void ); + + BOOL FBecomeProne ( void ); + void BarnacleVictimBitten ( entvars_t *pevBarnacle ); + void BarnacleVictimReleased ( void ); + static int GetAmmoIndex(const char *psz); + int AmmoInventory( int iAmmoIndex ); + int Illumination( void ); + + void ResetAutoaim( void ); + Vector GetAutoaimVector( float flDelta ); + Vector AutoaimDeflection( Vector &vecSrc, float flDist, float flDelta ); + + void ForceClientDllUpdate( void ); // Forces all client .dll specific data to be resent to client. + + void DeathMessage( entvars_t *pevKiller ); + + void SetCustomDecalFrames( int nFrames ); + int GetCustomDecalFrames( void ); + + void TabulateAmmo( void ); + + float m_flStartCharge; + float m_flAmmoStartCharge; + float m_flPlayAftershock; + float m_flNextAmmoBurn;// while charging, when to absorb another unit of player's ammo? + + //Player ID + void InitStatusBar( void ); + void UpdateStatusBar( void ); + int m_izSBarState[ SBAR_END ]; + float m_flNextSBarUpdateTime; + float m_flStatusBarDisappearDelay; + char m_SbarString0[ SBAR_STRING_SIZE ]; + char m_SbarString1[ SBAR_STRING_SIZE ]; + + float m_flNextChatTime; + +}; + +#define AUTOAIM_2DEGREES 0.0348994967025 +#define AUTOAIM_5DEGREES 0.08715574274766 +#define AUTOAIM_8DEGREES 0.1391731009601 +#define AUTOAIM_10DEGREES 0.1736481776669 + + +extern int gmsgHudText; +extern BOOL gInitHUD; + +#endif // PLAYER_H diff --git a/dlls/playermonster.cpp b/dlls/playermonster.cpp new file mode 100644 index 0000000..798c25d --- /dev/null +++ b/dlls/playermonster.cpp @@ -0,0 +1,122 @@ +//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: New version of the slider bar +// +// $NoKeywords: $ +//============================================================================= + +//========================================================= +// playermonster - for scripted sequence use. +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" + +// For holograms, make them not solid so the player can walk through them +#define SF_MONSTERPLAYER_NOTSOLID 4 + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +class CPlayerMonster : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int ISoundMask ( void ); +}; +LINK_ENTITY_TO_CLASS( monster_player, CPlayerMonster ); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CPlayerMonster :: Classify ( void ) +{ + return CLASS_PLAYER_ALLY; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CPlayerMonster :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + default: + ys = 90; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CPlayerMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case 0: + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// ISoundMask - player monster can't hear. +//========================================================= +int CPlayerMonster :: ISoundMask ( void ) +{ + return NULL; +} + +//========================================================= +// Spawn +//========================================================= +void CPlayerMonster :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/player.mdl"); + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->health = 8; + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + + MonsterInit(); + if ( pev->spawnflags & SF_MONSTERPLAYER_NOTSOLID ) + { + pev->solid = SOLID_NOT; + pev->takedamage = DAMAGE_NO; + } +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CPlayerMonster :: Precache() +{ + PRECACHE_MODEL("models/player.mdl"); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= diff --git a/dlls/python.cpp b/dlls/python.cpp new file mode 100644 index 0000000..4e019ef --- /dev/null +++ b/dlls/python.cpp @@ -0,0 +1,309 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "monsters.h" +#include "player.h" +#include "gamerules.h" + + +enum python_e { + PYTHON_IDLE1 = 0, + PYTHON_FIDGET, + PYTHON_FIRE1, + PYTHON_RELOAD, + PYTHON_HOLSTER, + PYTHON_DRAW, + PYTHON_IDLE2, + PYTHON_IDLE3 +}; + +LINK_ENTITY_TO_CLASS( weapon_python, CPython ); +LINK_ENTITY_TO_CLASS( weapon_357, CPython ); + +int CPython::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "357"; + p->iMaxAmmo1 = _357_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = PYTHON_MAX_CLIP; + p->iFlags = 0; + p->iSlot = 1; + p->iPosition = 1; + p->iId = m_iId = WEAPON_PYTHON; + p->iWeight = PYTHON_WEIGHT; + + return 1; +} + +int CPython::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + +void CPython::Spawn( ) +{ + pev->classname = MAKE_STRING("weapon_357"); // hack to allow for old names + Precache( ); + m_iId = WEAPON_PYTHON; + SET_MODEL(ENT(pev), "models/w_357.mdl"); + + m_iDefaultAmmo = PYTHON_DEFAULT_GIVE; + + FallInit();// get ready to fall down. +} + + +void CPython::Precache( void ) +{ + PRECACHE_MODEL("models/v_357.mdl"); + PRECACHE_MODEL("models/w_357.mdl"); + PRECACHE_MODEL("models/p_357.mdl"); + + PRECACHE_MODEL("models/w_357ammobox.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + + PRECACHE_SOUND ("weapons/357_reload1.wav"); + PRECACHE_SOUND ("weapons/357_cock1.wav"); + PRECACHE_SOUND ("weapons/357_shot1.wav"); + PRECACHE_SOUND ("weapons/357_shot2.wav"); + + m_usFirePython = PRECACHE_EVENT( 1, "events/python.sc" ); +} + +BOOL CPython::Deploy( ) +{ +#ifdef CLIENT_DLL + if ( bIsMultiplayer() ) +#else + if ( g_pGameRules->IsMultiplayer() ) +#endif + { + // enable laser sight geometry. + pev->body = 1; + } + else + { + pev->body = 0; + } + + return DefaultDeploy( "models/v_357.mdl", "models/p_357.mdl", PYTHON_DRAW, "python", UseDecrement(), pev->body ); +} + + +void CPython::Holster( int skiplocal /* = 0 */ ) +{ + m_fInReload = FALSE;// cancel any reload in progress. + + if ( m_fInZoom ) + { + SecondaryAttack(); + } + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.0; + m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + SendWeaponAnim( PYTHON_HOLSTER ); +} + +void CPython::SecondaryAttack( void ) +{ +#ifdef CLIENT_DLL + if ( !bIsMultiplayer() ) +#else + if ( !g_pGameRules->IsMultiplayer() ) +#endif + { + return; + } + + if ( m_pPlayer->pev->fov != 0 ) + { + m_fInZoom = FALSE; + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 0; // 0 means reset to default fov + } + else if ( m_pPlayer->pev->fov != 40 ) + { + m_fInZoom = TRUE; + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 40; + } + + m_flNextSecondaryAttack = 0.5; +} + +void CPython::PrimaryAttack() +{ + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = 0.15; + return; + } + + if (m_iClip <= 0) + { + if (!m_fFireOnEmpty) + Reload( ); + else + { + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/357_cock1.wav", 0.8, ATTN_NORM); + m_flNextPrimaryAttack = 0.15; + } + + return; + } + + m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = BRIGHT_GUN_FLASH; + + m_iClip--; + + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = m_pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES ); + + Vector vecDir; + vecDir = m_pPlayer->FireBulletsPlayer( 1, vecSrc, vecAiming, VECTOR_CONE_1DEGREES, 8192, BULLET_PLAYER_357, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed ); + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usFirePython, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, vecDir.x, vecDir.y, 0, 0, 0, 0 ); + + if (!m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + // HEV suit - indicate out of ammo condition + m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + + m_flNextPrimaryAttack = 0.75; + m_flTimeWeaponIdle = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); +} + + +void CPython::Reload( void ) +{ + if ( m_pPlayer->ammo_357 <= 0 ) + return; + + if ( m_pPlayer->pev->fov != 0 ) + { + m_fInZoom = FALSE; + m_pPlayer->pev->fov = m_pPlayer->m_iFOV = 0; // 0 means reset to default fov + } + + int bUseScope = FALSE; +#ifdef CLIENT_DLL + bUseScope = bIsMultiplayer(); +#else + bUseScope = g_pGameRules->IsMultiplayer(); +#endif + + DefaultReload( 6, PYTHON_RELOAD, 2.0, bUseScope ); +} + + +void CPython::WeaponIdle( void ) +{ + ResetEmptySound( ); + + m_pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES ); + + if (m_flTimeWeaponIdle > UTIL_WeaponTimeBase() ) + return; + + int iAnim; + float flRand = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0, 1 ); + if (flRand <= 0.5) + { + iAnim = PYTHON_IDLE1; + m_flTimeWeaponIdle = (70.0/30.0); + } + else if (flRand <= 0.7) + { + iAnim = PYTHON_IDLE2; + m_flTimeWeaponIdle = (60.0/30.0); + } + else if (flRand <= 0.9) + { + iAnim = PYTHON_IDLE3; + m_flTimeWeaponIdle = (88.0/30.0); + } + else + { + iAnim = PYTHON_FIDGET; + m_flTimeWeaponIdle = (170.0/30.0); + } + + int bUseScope = FALSE; +#ifdef CLIENT_DLL + bUseScope = bIsMultiplayer(); +#else + bUseScope = g_pGameRules->IsMultiplayer(); +#endif + + SendWeaponAnim( iAnim, UseDecrement() ? 1 : 0, bUseScope ); +} + + +class CPythonAmmo : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_357ammobox.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_357ammobox.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + if (pOther->GiveAmmo( AMMO_357BOX_GIVE, "357", _357_MAX_CARRY ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + return TRUE; + } + return FALSE; + } +}; +LINK_ENTITY_TO_CLASS( ammo_357, CPythonAmmo ); + + +#endif \ No newline at end of file diff --git a/dlls/rat.cpp b/dlls/rat.cpp new file mode 100644 index 0000000..ba858eb --- /dev/null +++ b/dlls/rat.cpp @@ -0,0 +1,98 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// rat - environmental monster +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +class CRat : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); +}; +LINK_ENTITY_TO_CLASS( monster_rat, CRat ); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CRat :: Classify ( void ) +{ + return CLASS_INSECT; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CRat :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + default: + ys = 45; + break; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// Spawn +//========================================================= +void CRat :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/bigrat.mdl"); + UTIL_SetSize( pev, Vector( 0, 0, 0 ), Vector( 0, 0, 0 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->health = 8; + pev->view_ofs = Vector ( 0, 0, 6 );// position of the eyes relative to monster's origin. + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CRat :: Precache() +{ + PRECACHE_MODEL("models/bigrat.mdl"); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= diff --git a/dlls/roach.cpp b/dlls/roach.cpp new file mode 100644 index 0000000..6a0aca9 --- /dev/null +++ b/dlls/roach.cpp @@ -0,0 +1,460 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// cockroach +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "soundent.h" +#include "decals.h" + +#define ROACH_IDLE 0 +#define ROACH_BORED 1 +#define ROACH_SCARED_BY_ENT 2 +#define ROACH_SCARED_BY_LIGHT 3 +#define ROACH_SMELL_FOOD 4 +#define ROACH_EAT 5 + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +class CRoach : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + void EXPORT MonsterThink ( void ); + void Move ( float flInterval ); + void PickNewDest ( int iCondition ); + void EXPORT Touch ( CBaseEntity *pOther ); + void Killed( entvars_t *pevAttacker, int iGib ); + + float m_flLastLightLevel; + float m_flNextSmellTime; + int Classify ( void ); + void Look ( int iDistance ); + int ISoundMask ( void ); + + // UNDONE: These don't necessarily need to be save/restored, but if we add more data, it may + BOOL m_fLightHacked; + int m_iMode; + // ----------------------------- +}; +LINK_ENTITY_TO_CLASS( monster_cockroach, CRoach ); + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. In the base class implementation, +// monsters care about all sounds, but no scents. +//========================================================= +int CRoach :: ISoundMask ( void ) +{ + return bits_SOUND_CARCASS | bits_SOUND_MEAT; +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CRoach :: Classify ( void ) +{ + return CLASS_INSECT; +} + +//========================================================= +// Touch +//========================================================= +void CRoach :: Touch ( CBaseEntity *pOther ) +{ + Vector vecSpot; + TraceResult tr; + + if ( pOther->pev->velocity == g_vecZero || !pOther->IsPlayer() ) + { + return; + } + + vecSpot = pev->origin + Vector ( 0 , 0 , 8 );//move up a bit, and trace down. + UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -24 ), ignore_monsters, ENT(pev), & tr); + + // This isn't really blood. So you don't have to screen it out based on violence levels (UTIL_ShouldShowBlood()) + UTIL_DecalTrace( &tr, DECAL_YBLOOD1 +RANDOM_LONG(0,5) ); + + TakeDamage( pOther->pev, pOther->pev, pev->health, DMG_CRUSH ); +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CRoach :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + + pev->yaw_speed = ys; +} + +//========================================================= +// Spawn +//========================================================= +void CRoach :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/roach.mdl"); + UTIL_SetSize( pev, Vector( -1, -1, 0 ), Vector( 1, 1, 2 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_YELLOW; + pev->effects = 0; + pev->health = 1; + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); + SetActivity ( ACT_IDLE ); + + pev->view_ofs = Vector ( 0, 0, 1 );// position of the eyes relative to monster's origin. + pev->takedamage = DAMAGE_YES; + m_fLightHacked = FALSE; + m_flLastLightLevel = -1; + m_iMode = ROACH_IDLE; + m_flNextSmellTime = gpGlobals->time; +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CRoach :: Precache() +{ + PRECACHE_MODEL("models/roach.mdl"); + + PRECACHE_SOUND("roach/rch_die.wav"); + PRECACHE_SOUND("roach/rch_walk.wav"); + PRECACHE_SOUND("roach/rch_smash.wav"); +} + + +//========================================================= +// Killed. +//========================================================= +void CRoach :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->solid = SOLID_NOT; + + //random sound + if ( RANDOM_LONG(0,4) == 1 ) + { + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "roach/rch_die.wav", 0.8, ATTN_NORM, 0, 80 + RANDOM_LONG(0,39) ); + } + else + { + EMIT_SOUND_DYN(ENT(pev), CHAN_BODY, "roach/rch_smash.wav", 0.7, ATTN_NORM, 0, 80 + RANDOM_LONG(0,39) ); + } + + CSoundEnt::InsertSound ( bits_SOUND_WORLD, pev->origin, 128, 1 ); + + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + if ( pOwner ) + { + pOwner->DeathNotice( pev ); + } + UTIL_Remove( this ); +} + +//========================================================= +// MonsterThink, overridden for roaches. +//========================================================= +void CRoach :: MonsterThink( void ) +{ + if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(1,1.5); + else + pev->nextthink = gpGlobals->time + 0.1;// keep monster thinking + + float flInterval = StudioFrameAdvance( ); // animate + + if ( !m_fLightHacked ) + { + // if light value hasn't been collection for the first time yet, + // suspend the creature for a second so the world finishes spawning, then we'll collect the light level. + pev->nextthink = gpGlobals->time + 1; + m_fLightHacked = TRUE; + return; + } + else if ( m_flLastLightLevel < 0 ) + { + // collect light level for the first time, now that all of the lightmaps in the roach's area have been calculated. + m_flLastLightLevel = GETENTITYILLUM( ENT( pev ) ); + } + + switch ( m_iMode ) + { + case ROACH_IDLE: + case ROACH_EAT: + { + // if not moving, sample environment to see if anything scary is around. Do a radius search 'look' at random. + if ( RANDOM_LONG(0,3) == 1 ) + { + Look( 150 ); + if (HasConditions(bits_COND_SEE_FEAR)) + { + // if see something scary + //ALERT ( at_aiconsole, "Scared\n" ); + Eat( 30 + ( RANDOM_LONG(0,14) ) );// roach will ignore food for 30 to 45 seconds + PickNewDest( ROACH_SCARED_BY_ENT ); + SetActivity ( ACT_WALK ); + } + else if ( RANDOM_LONG(0,149) == 1 ) + { + // if roach doesn't see anything, there's still a chance that it will move. (boredom) + //ALERT ( at_aiconsole, "Bored\n" ); + PickNewDest( ROACH_BORED ); + SetActivity ( ACT_WALK ); + + if ( m_iMode == ROACH_EAT ) + { + // roach will ignore food for 30 to 45 seconds if it got bored while eating. + Eat( 30 + ( RANDOM_LONG(0,14) ) ); + } + } + } + + // don't do this stuff if eating! + if ( m_iMode == ROACH_IDLE ) + { + if ( FShouldEat() ) + { + Listen(); + } + + if ( GETENTITYILLUM( ENT(pev) ) > m_flLastLightLevel ) + { + // someone turned on lights! + //ALERT ( at_console, "Lights!\n" ); + PickNewDest( ROACH_SCARED_BY_LIGHT ); + SetActivity ( ACT_WALK ); + } + else if ( HasConditions(bits_COND_SMELL_FOOD) ) + { + CSound *pSound; + + pSound = CSoundEnt::SoundPointerForIndex( m_iAudibleList ); + + // roach smells food and is just standing around. Go to food unless food isn't on same z-plane. + if ( pSound && abs( pSound->m_vecOrigin.z - pev->origin.z ) <= 3 ) + { + PickNewDest( ROACH_SMELL_FOOD ); + SetActivity ( ACT_WALK ); + } + } + } + + break; + } + case ROACH_SCARED_BY_LIGHT: + { + // if roach was scared by light, then stop if we're over a spot at least as dark as where we started! + if ( GETENTITYILLUM( ENT( pev ) ) <= m_flLastLightLevel ) + { + SetActivity ( ACT_IDLE ); + m_flLastLightLevel = GETENTITYILLUM( ENT ( pev ) );// make this our new light level. + } + break; + } + } + + if ( m_flGroundSpeed != 0 ) + { + Move( flInterval ); + } +} + +//========================================================= +// Picks a new spot for roach to run to.( +//========================================================= +void CRoach :: PickNewDest ( int iCondition ) +{ + Vector vecNewDir; + Vector vecDest; + float flDist; + + m_iMode = iCondition; + + if ( m_iMode == ROACH_SMELL_FOOD ) + { + // find the food and go there. + CSound *pSound; + + pSound = CSoundEnt::SoundPointerForIndex( m_iAudibleList ); + + if ( pSound ) + { + m_Route[ 0 ].vecLocation.x = pSound->m_vecOrigin.x + ( 3 - RANDOM_LONG(0,5) ); + m_Route[ 0 ].vecLocation.y = pSound->m_vecOrigin.y + ( 3 - RANDOM_LONG(0,5) ); + m_Route[ 0 ].vecLocation.z = pSound->m_vecOrigin.z; + m_Route[ 0 ].iType = bits_MF_TO_LOCATION; + m_movementGoal = RouteClassify( m_Route[ 0 ].iType ); + return; + } + } + + do + { + // picks a random spot, requiring that it be at least 128 units away + // else, the roach will pick a spot too close to itself and run in + // circles. this is a hack but buys me time to work on the real monsters. + vecNewDir.x = RANDOM_FLOAT( -1, 1 ); + vecNewDir.y = RANDOM_FLOAT( -1, 1 ); + flDist = 256 + ( RANDOM_LONG(0,255) ); + vecDest = pev->origin + vecNewDir * flDist; + + } while ( ( vecDest - pev->origin ).Length2D() < 128 ); + + m_Route[ 0 ].vecLocation.x = vecDest.x; + m_Route[ 0 ].vecLocation.y = vecDest.y; + m_Route[ 0 ].vecLocation.z = pev->origin.z; + m_Route[ 0 ].iType = bits_MF_TO_LOCATION; + m_movementGoal = RouteClassify( m_Route[ 0 ].iType ); + + if ( RANDOM_LONG(0,9) == 1 ) + { + // every once in a while, a roach will play a skitter sound when they decide to run + EMIT_SOUND_DYN(ENT(pev), CHAN_BODY, "roach/rch_walk.wav", 1, ATTN_NORM, 0, 80 + RANDOM_LONG(0,39) ); + } +} + +//========================================================= +// roach's move function +//========================================================= +void CRoach :: Move ( float flInterval ) +{ + float flWaypointDist; + Vector vecApex; + + // local move to waypoint. + flWaypointDist = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Length2D(); + MakeIdealYaw ( m_Route[ m_iRouteIndex ].vecLocation ); + + ChangeYaw ( pev->yaw_speed ); + UTIL_MakeVectors( pev->angles ); + + if ( RANDOM_LONG(0,7) == 1 ) + { + // randomly check for blocked path.(more random load balancing) + if ( !WALK_MOVE( ENT(pev), pev->ideal_yaw, 4, WALKMOVE_NORMAL ) ) + { + // stuck, so just pick a new spot to run off to + PickNewDest( m_iMode ); + } + } + + WALK_MOVE( ENT(pev), pev->ideal_yaw, m_flGroundSpeed * flInterval, WALKMOVE_NORMAL ); + + // if the waypoint is closer than step size, then stop after next step (ok for roach to overshoot) + if ( flWaypointDist <= m_flGroundSpeed * flInterval ) + { + // take truncated step and stop + + SetActivity ( ACT_IDLE ); + m_flLastLightLevel = GETENTITYILLUM( ENT ( pev ) );// this is roach's new comfortable light level + + if ( m_iMode == ROACH_SMELL_FOOD ) + { + m_iMode = ROACH_EAT; + } + else + { + m_iMode = ROACH_IDLE; + } + } + + if ( RANDOM_LONG(0,149) == 1 && m_iMode != ROACH_SCARED_BY_LIGHT && m_iMode != ROACH_SMELL_FOOD ) + { + // random skitter while moving as long as not on a b-line to get out of light or going to food + PickNewDest( FALSE ); + } +} + +//========================================================= +// Look - overriden for the roach, which can virtually see +// 360 degrees. +//========================================================= +void CRoach :: Look ( int iDistance ) +{ + CBaseEntity *pSightEnt = NULL;// the current visible entity that we're dealing with + CBaseEntity *pPreviousEnt;// the last entity added to the link list + int iSighted = 0; + + // DON'T let visibility information from last frame sit around! + ClearConditions( bits_COND_SEE_HATE |bits_COND_SEE_DISLIKE | bits_COND_SEE_ENEMY | bits_COND_SEE_FEAR ); + + // don't let monsters outside of the player's PVS act up, or most of the interesting + // things will happen before the player gets there! + if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) + { + return; + } + + m_pLink = NULL; + pPreviousEnt = this; + + // Does sphere also limit itself to PVS? + // Examine all entities within a reasonable radius + // !!!PERFORMANCE - let's trivially reject the ent list before radius searching! + while ((pSightEnt = UTIL_FindEntityInSphere( pSightEnt, pev->origin, iDistance )) != NULL) + { + // only consider ents that can be damaged. !!!temporarily only considering other monsters and clients + if ( pSightEnt->IsPlayer() || FBitSet ( pSightEnt->pev->flags, FL_MONSTER ) ) + { + if ( /*FVisible( pSightEnt ) &&*/ !FBitSet( pSightEnt->pev->flags, FL_NOTARGET ) && pSightEnt->pev->health > 0 ) + { + // NULL the Link pointer for each ent added to the link list. If other ents follow, the will overwrite + // this value. If this ent happens to be the last, the list will be properly terminated. + pPreviousEnt->m_pLink = pSightEnt; + pSightEnt->m_pLink = NULL; + pPreviousEnt = pSightEnt; + + // don't add the Enemy's relationship to the conditions. We only want to worry about conditions when + // we see monsters other than the Enemy. + switch ( IRelationship ( pSightEnt ) ) + { + case R_FR: + iSighted |= bits_COND_SEE_FEAR; + break; + case R_NO: + break; + default: + ALERT ( at_console, "%s can't asses %s\n", STRING(pev->classname), STRING(pSightEnt->pev->classname ) ); + break; + } + } + } + } + SetConditions( iSighted ); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + diff --git a/dlls/rpg.cpp b/dlls/rpg.cpp new file mode 100644 index 0000000..fe9c53c --- /dev/null +++ b/dlls/rpg.cpp @@ -0,0 +1,617 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "gamerules.h" + + + + +enum rpg_e { + RPG_IDLE = 0, + RPG_FIDGET, + RPG_RELOAD, // to reload + RPG_FIRE2, // to empty + RPG_HOLSTER1, // loaded + RPG_DRAW1, // loaded + RPG_HOLSTER2, // unloaded + RPG_DRAW_UL, // unloaded + RPG_IDLE_UL, // unloaded idle + RPG_FIDGET_UL, // unloaded fidget +}; + +LINK_ENTITY_TO_CLASS( weapon_rpg, CRpg ); + +#ifndef CLIENT_DLL + +LINK_ENTITY_TO_CLASS( laser_spot, CLaserSpot ); + +//========================================================= +//========================================================= +CLaserSpot *CLaserSpot::CreateSpot( void ) +{ + CLaserSpot *pSpot = GetClassPtr( (CLaserSpot *)NULL ); + pSpot->Spawn(); + + pSpot->pev->classname = MAKE_STRING("laser_spot"); + + return pSpot; +} + +//========================================================= +//========================================================= +void CLaserSpot::Spawn( void ) +{ + Precache( ); + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT; + + pev->rendermode = kRenderGlow; + pev->renderfx = kRenderFxNoDissipation; + pev->renderamt = 255; + + SET_MODEL(ENT(pev), "sprites/laserdot.spr"); + UTIL_SetOrigin( pev, pev->origin ); +}; + +//========================================================= +// Suspend- make the laser sight invisible. +//========================================================= +void CLaserSpot::Suspend( float flSuspendTime ) +{ + pev->effects |= EF_NODRAW; + + SetThink( &CLaserSpot::Revive ); + pev->nextthink = gpGlobals->time + flSuspendTime; +} + +//========================================================= +// Revive - bring a suspended laser sight back. +//========================================================= +void CLaserSpot::Revive( void ) +{ + pev->effects &= ~EF_NODRAW; + + SetThink( NULL ); +} + +void CLaserSpot::Precache( void ) +{ + PRECACHE_MODEL("sprites/laserdot.spr"); +}; + +LINK_ENTITY_TO_CLASS( rpg_rocket, CRpgRocket ); + +//========================================================= +//========================================================= +CRpgRocket *CRpgRocket::CreateRpgRocket( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner, CRpg *pLauncher ) +{ + CRpgRocket *pRocket = GetClassPtr( (CRpgRocket *)NULL ); + + UTIL_SetOrigin( pRocket->pev, vecOrigin ); + pRocket->pev->angles = vecAngles; + pRocket->Spawn(); + pRocket->SetTouch( &CRpgRocket::RocketTouch ); + pRocket->m_pLauncher = pLauncher;// remember what RPG fired me. + pRocket->m_pLauncher->m_cActiveRockets++;// register this missile as active for the launcher + pRocket->pev->owner = pOwner->edict(); + + return pRocket; +} + +//========================================================= +//========================================================= +void CRpgRocket :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_BOUNCE; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/rpgrocket.mdl"); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( pev, pev->origin ); + + pev->classname = MAKE_STRING("rpg_rocket"); + + SetThink( &CRpgRocket::IgniteThink ); + SetTouch( &CRpgRocket::ExplodeTouch ); + + pev->angles.x -= 30; + UTIL_MakeVectors( pev->angles ); + pev->angles.x = -(pev->angles.x + 30); + + pev->velocity = gpGlobals->v_forward * 250; + pev->gravity = 0.5; + + pev->nextthink = gpGlobals->time + 0.4; + + pev->dmg = gSkillData.plrDmgRPG; +} + +//========================================================= +//========================================================= +void CRpgRocket :: RocketTouch ( CBaseEntity *pOther ) +{ + if ( m_pLauncher ) + { + // my launcher is still around, tell it I'm dead. + m_pLauncher->m_cActiveRockets--; + } + + STOP_SOUND( edict(), CHAN_VOICE, "weapons/rocket1.wav" ); + ExplodeTouch( pOther ); +} + +//========================================================= +//========================================================= +void CRpgRocket :: Precache( void ) +{ + PRECACHE_MODEL("models/rpgrocket.mdl"); + m_iTrail = PRECACHE_MODEL("sprites/smoke.spr"); + PRECACHE_SOUND ("weapons/rocket1.wav"); +} + + +void CRpgRocket :: IgniteThink( void ) +{ + // pev->movetype = MOVETYPE_TOSS; + + pev->movetype = MOVETYPE_FLY; + pev->effects |= EF_LIGHT; + + // make rocket sound + EMIT_SOUND( ENT(pev), CHAN_VOICE, "weapons/rocket1.wav", 1, 0.5 ); + + // rocket trail + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + + WRITE_BYTE( TE_BEAMFOLLOW ); + WRITE_SHORT(entindex()); // entity + WRITE_SHORT(m_iTrail ); // model + WRITE_BYTE( 40 ); // life + WRITE_BYTE( 5 ); // width + WRITE_BYTE( 224 ); // r, g, b + WRITE_BYTE( 224 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + + MESSAGE_END(); // move PHS/PVS data sending into here (SEND_ALL, SEND_PVS, SEND_PHS) + + m_flIgniteTime = gpGlobals->time; + + // set to follow laser spot + SetThink( &CRpgRocket::FollowThink ); + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CRpgRocket :: FollowThink( void ) +{ + CBaseEntity *pOther = NULL; + Vector vecTarget; + Vector vecDir; + float flDist, flMax, flDot; + TraceResult tr; + + UTIL_MakeAimVectors( pev->angles ); + + vecTarget = gpGlobals->v_forward; + flMax = 4096; + + // Examine all entities within a reasonable radius + while ((pOther = UTIL_FindEntityByClassname( pOther, "laser_spot" )) != NULL) + { + UTIL_TraceLine ( pev->origin, pOther->pev->origin, dont_ignore_monsters, ENT(pev), &tr ); + // ALERT( at_console, "%f\n", tr.flFraction ); + if (tr.flFraction >= 0.90) + { + vecDir = pOther->pev->origin - pev->origin; + flDist = vecDir.Length( ); + vecDir = vecDir.Normalize( ); + flDot = DotProduct( gpGlobals->v_forward, vecDir ); + if ((flDot > 0) && (flDist * (1 - flDot) < flMax)) + { + flMax = flDist * (1 - flDot); + vecTarget = vecDir; + } + } + } + + pev->angles = UTIL_VecToAngles( vecTarget ); + + // this acceleration and turning math is totally wrong, but it seems to respond well so don't change it. + float flSpeed = pev->velocity.Length(); + if (gpGlobals->time - m_flIgniteTime < 1.0) + { + pev->velocity = pev->velocity * 0.2 + vecTarget * (flSpeed * 0.8 + 400); + if (pev->waterlevel == 3) + { + // go slow underwater + if (pev->velocity.Length() > 300) + { + pev->velocity = pev->velocity.Normalize() * 300; + } + UTIL_BubbleTrail( pev->origin - pev->velocity * 0.1, pev->origin, 4 ); + } + else + { + if (pev->velocity.Length() > 2000) + { + pev->velocity = pev->velocity.Normalize() * 2000; + } + } + } + else + { + if (pev->effects & EF_LIGHT) + { + pev->effects = 0; + STOP_SOUND( ENT(pev), CHAN_VOICE, "weapons/rocket1.wav" ); + } + pev->velocity = pev->velocity * 0.2 + vecTarget * flSpeed * 0.798; + if (pev->waterlevel == 0 && pev->velocity.Length() < 1500) + { + Detonate( ); + } + } + // ALERT( at_console, "%.0f\n", flSpeed ); + + pev->nextthink = gpGlobals->time + 0.1; +} +#endif + + + +void CRpg::Reload( void ) +{ + int iResult; + + if ( m_iClip == 1 ) + { + // don't bother with any of this if don't need to reload. + return; + } + + if ( m_pPlayer->ammo_rockets <= 0 ) + return; + + // because the RPG waits to autoreload when no missiles are active while the LTD is on, the + // weapons code is constantly calling into this function, but is often denied because + // a) missiles are in flight, but the LTD is on + // or + // b) player is totally out of ammo and has nothing to switch to, and should be allowed to + // shine the designator around + // + // Set the next attack time into the future so that WeaponIdle will get called more often + // than reload, allowing the RPG LTD to be updated + + m_flNextPrimaryAttack = GetNextAttackDelay(0.5); + + if ( m_cActiveRockets && m_fSpotActive ) + { + // no reloading when there are active missiles tracking the designator. + // ward off future autoreload attempts by setting next attack time into the future for a bit. + return; + } + +#ifndef CLIENT_DLL + if ( m_pSpot && m_fSpotActive ) + { + m_pSpot->Suspend( 2.1 ); + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 2.1; + } +#endif + + if ( m_iClip == 0 ) + iResult = DefaultReload( RPG_MAX_CLIP, RPG_RELOAD, 2 ); + + if ( iResult ) + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + +} + +void CRpg::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_RPG; + + SET_MODEL(ENT(pev), "models/w_rpg.mdl"); + m_fSpotActive = 1; + +#ifdef CLIENT_DLL + if ( bIsMultiplayer() ) +#else + if ( g_pGameRules->IsMultiplayer() ) +#endif + { + // more default ammo in multiplay. + m_iDefaultAmmo = RPG_DEFAULT_GIVE * 2; + } + else + { + m_iDefaultAmmo = RPG_DEFAULT_GIVE; + } + + FallInit();// get ready to fall down. +} + + +void CRpg::Precache( void ) +{ + PRECACHE_MODEL("models/w_rpg.mdl"); + PRECACHE_MODEL("models/v_rpg.mdl"); + PRECACHE_MODEL("models/p_rpg.mdl"); + + PRECACHE_SOUND("items/9mmclip1.wav"); + + UTIL_PrecacheOther( "laser_spot" ); + UTIL_PrecacheOther( "rpg_rocket" ); + + PRECACHE_SOUND("weapons/rocketfire1.wav"); + PRECACHE_SOUND("weapons/glauncher.wav"); // alternative fire sound + + m_usRpg = PRECACHE_EVENT ( 1, "events/rpg.sc" ); +} + + +int CRpg::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "rockets"; + p->iMaxAmmo1 = ROCKET_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = RPG_MAX_CLIP; + p->iSlot = 3; + p->iPosition = 0; + p->iId = m_iId = WEAPON_RPG; + p->iFlags = 0; + p->iWeight = RPG_WEIGHT; + + return 1; +} + +int CRpg::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + +BOOL CRpg::Deploy( ) +{ + if ( m_iClip == 0 ) + { + return DefaultDeploy( "models/v_rpg.mdl", "models/p_rpg.mdl", RPG_DRAW_UL, "rpg" ); + } + + return DefaultDeploy( "models/v_rpg.mdl", "models/p_rpg.mdl", RPG_DRAW1, "rpg" ); +} + + +BOOL CRpg::CanHolster( void ) +{ + if ( m_fSpotActive && m_cActiveRockets ) + { + // can't put away while guiding a missile. + return FALSE; + } + + return TRUE; +} + +void CRpg::Holster( int skiplocal /* = 0 */ ) +{ + m_fInReload = FALSE;// cancel any reload in progress. + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + + SendWeaponAnim( RPG_HOLSTER1 ); + +#ifndef CLIENT_DLL + if (m_pSpot) + { + m_pSpot->Killed( NULL, GIB_NEVER ); + m_pSpot = NULL; + } +#endif + +} + + + +void CRpg::PrimaryAttack() +{ + if ( m_iClip ) + { + m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = BRIGHT_GUN_FLASH; + +#ifndef CLIENT_DLL + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + UTIL_MakeVectors( m_pPlayer->pev->v_angle ); + Vector vecSrc = m_pPlayer->GetGunPosition( ) + gpGlobals->v_forward * 16 + gpGlobals->v_right * 8 + gpGlobals->v_up * -8; + + CRpgRocket *pRocket = CRpgRocket::CreateRpgRocket( vecSrc, m_pPlayer->pev->v_angle, m_pPlayer, this ); + + UTIL_MakeVectors( m_pPlayer->pev->v_angle );// RpgRocket::Create stomps on globals, so remake. + pRocket->pev->velocity = pRocket->pev->velocity + gpGlobals->v_forward * DotProduct( m_pPlayer->pev->velocity, gpGlobals->v_forward ); +#endif + + // firing RPG no longer turns on the designator. ALT fire is a toggle switch for the LTD. + // Ken signed up for this as a global change (sjb) + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT( flags, m_pPlayer->edict(), m_usRpg ); + + m_iClip--; + + m_flNextPrimaryAttack = GetNextAttackDelay(1.5); + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.5; + } + else + { + PlayEmptySound( ); + } + UpdateSpot( ); +} + + +void CRpg::SecondaryAttack() +{ + m_fSpotActive = ! m_fSpotActive; + +#ifndef CLIENT_DLL + if (!m_fSpotActive && m_pSpot) + { + m_pSpot->Killed( NULL, GIB_NORMAL ); + m_pSpot = NULL; + } +#endif + + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.2; +} + + +void CRpg::WeaponIdle( void ) +{ + UpdateSpot( ); + + ResetEmptySound( ); + + if ( m_flTimeWeaponIdle > UTIL_WeaponTimeBase() ) + return; + + if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + int iAnim; + float flRand = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0, 1 ); + if (flRand <= 0.75 || m_fSpotActive) + { + if ( m_iClip == 0 ) + iAnim = RPG_IDLE_UL; + else + iAnim = RPG_IDLE; + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 90.0 / 15.0; + } + else + { + if ( m_iClip == 0 ) + iAnim = RPG_FIDGET_UL; + else + iAnim = RPG_FIDGET; + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 3.0; + } + + SendWeaponAnim( iAnim ); + } + else + { + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1; + } +} + + + +void CRpg::UpdateSpot( void ) +{ + +#ifndef CLIENT_DLL + if (m_fSpotActive) + { + if (!m_pSpot) + { + m_pSpot = CLaserSpot::CreateSpot(); + } + + UTIL_MakeVectors( m_pPlayer->pev->v_angle ); + Vector vecSrc = m_pPlayer->GetGunPosition( );; + Vector vecAiming = gpGlobals->v_forward; + + TraceResult tr; + UTIL_TraceLine ( vecSrc, vecSrc + vecAiming * 8192, dont_ignore_monsters, ENT(m_pPlayer->pev), &tr ); + + UTIL_SetOrigin( m_pSpot->pev, tr.vecEndPos ); + } +#endif + +} + + +class CRpgAmmo : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_rpgammo.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_rpgammo.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + int iGive; + +#ifdef CLIENT_DLL + if ( bIsMultiplayer() ) +#else + if ( g_pGameRules->IsMultiplayer() ) +#endif + { + // hand out more ammo per rocket in multiplayer. + iGive = AMMO_RPGCLIP_GIVE * 2; + } + else + { + iGive = AMMO_RPGCLIP_GIVE; + } + + if (pOther->GiveAmmo( iGive, "rockets", ROCKET_MAX_CARRY ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + return TRUE; + } + return FALSE; + } +}; +LINK_ENTITY_TO_CLASS( ammo_rpgclip, CRpgAmmo ); + +#endif diff --git a/dlls/satchel.cpp b/dlls/satchel.cpp new file mode 100644 index 0000000..983f431 --- /dev/null +++ b/dlls/satchel.cpp @@ -0,0 +1,494 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "gamerules.h" + +enum satchel_e { + SATCHEL_IDLE1 = 0, + SATCHEL_FIDGET1, + SATCHEL_DRAW, + SATCHEL_DROP +}; + +enum satchel_radio_e { + SATCHEL_RADIO_IDLE1 = 0, + SATCHEL_RADIO_FIDGET1, + SATCHEL_RADIO_DRAW, + SATCHEL_RADIO_FIRE, + SATCHEL_RADIO_HOLSTER +}; + + + +class CSatchelCharge : public CGrenade +{ + void Spawn( void ); + void Precache( void ); + void BounceSound( void ); + + void EXPORT SatchelSlide( CBaseEntity *pOther ); + void EXPORT SatchelThink( void ); + +public: + void Deactivate( void ); +}; +LINK_ENTITY_TO_CLASS( monster_satchel, CSatchelCharge ); + +//========================================================= +// Deactivate - do whatever it is we do to an orphaned +// satchel when we don't want it in the world anymore. +//========================================================= +void CSatchelCharge::Deactivate( void ) +{ + pev->solid = SOLID_NOT; + UTIL_Remove( this ); +} + + +void CSatchelCharge :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_BOUNCE; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/w_satchel.mdl"); + //UTIL_SetSize(pev, Vector( -16, -16, -4), Vector(16, 16, 32)); // Old box -- size of headcrab monsters/players get blocked by this + UTIL_SetSize(pev, Vector( -4, -4, -4), Vector(4, 4, 4)); // Uses point-sized, and can be stepped over + UTIL_SetOrigin( pev, pev->origin ); + + SetTouch( &CSatchelCharge::SatchelSlide ); + SetUse( &CSatchelCharge::DetonateUse ); + SetThink( &CSatchelCharge::SatchelThink ); + pev->nextthink = gpGlobals->time + 0.1; + + pev->gravity = 0.5; + pev->friction = 0.8; + + pev->dmg = gSkillData.plrDmgSatchel; + // ResetSequenceInfo( ); + pev->sequence = 1; +} + + +void CSatchelCharge::SatchelSlide( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + // don't hit the guy that launched this grenade + if ( pOther->edict() == pev->owner ) + return; + + // pev->avelocity = Vector (300, 300, 300); + pev->gravity = 1;// normal gravity now + + // HACKHACK - On ground isn't always set, so look for ground underneath + TraceResult tr; + UTIL_TraceLine( pev->origin, pev->origin - Vector(0,0,10), ignore_monsters, edict(), &tr ); + + if ( tr.flFraction < 1.0 ) + { + // add a bit of static friction + pev->velocity = pev->velocity * 0.95; + pev->avelocity = pev->avelocity * 0.9; + // play sliding sound, volume based on velocity + } + if ( !(pev->flags & FL_ONGROUND) && pev->velocity.Length2D() > 10 ) + { + BounceSound(); + } + StudioFrameAdvance( ); +} + + +void CSatchelCharge :: SatchelThink( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + if (pev->waterlevel == 3) + { + pev->movetype = MOVETYPE_FLY; + pev->velocity = pev->velocity * 0.8; + pev->avelocity = pev->avelocity * 0.9; + pev->velocity.z += 8; + } + else if (pev->waterlevel == 0) + { + pev->movetype = MOVETYPE_BOUNCE; + } + else + { + pev->velocity.z -= 8; + } +} + +void CSatchelCharge :: Precache( void ) +{ + PRECACHE_MODEL("models/grenade.mdl"); + PRECACHE_SOUND("weapons/g_bounce1.wav"); + PRECACHE_SOUND("weapons/g_bounce2.wav"); + PRECACHE_SOUND("weapons/g_bounce3.wav"); +} + +void CSatchelCharge :: BounceSound( void ) +{ + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/g_bounce1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/g_bounce2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/g_bounce3.wav", 1, ATTN_NORM); break; + } +} + + +LINK_ENTITY_TO_CLASS( weapon_satchel, CSatchel ); + + +//========================================================= +// CALLED THROUGH the newly-touched weapon's instance. The existing player weapon is pOriginal +//========================================================= +int CSatchel::AddDuplicate( CBasePlayerItem *pOriginal ) +{ + CSatchel *pSatchel; + +#ifdef CLIENT_DLL + if ( bIsMultiplayer() ) +#else + if ( g_pGameRules->IsMultiplayer() ) +#endif + { + pSatchel = (CSatchel *)pOriginal; + + if ( pSatchel->m_chargeReady != 0 ) + { + // player has some satchels deployed. Refuse to add more. + return FALSE; + } + } + + return CBasePlayerWeapon::AddDuplicate ( pOriginal ); +} + +//========================================================= +//========================================================= +int CSatchel::AddToPlayer( CBasePlayer *pPlayer ) +{ + int bResult = CBasePlayerItem::AddToPlayer( pPlayer ); + + pPlayer->pev->weapons |= (1<pszName = STRING(pev->classname); + p->pszAmmo1 = "Satchel Charge"; + p->iMaxAmmo1 = SATCHEL_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 4; + p->iPosition = 1; + p->iFlags = ITEM_FLAG_SELECTONEMPTY | ITEM_FLAG_LIMITINWORLD | ITEM_FLAG_EXHAUSTIBLE; + p->iId = m_iId = WEAPON_SATCHEL; + p->iWeight = SATCHEL_WEIGHT; + + return 1; +} + +//========================================================= +//========================================================= +BOOL CSatchel::IsUseable( void ) +{ + if ( m_pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] > 0 ) + { + // player is carrying some satchels + return TRUE; + } + + if ( m_chargeReady != 0 ) + { + // player isn't carrying any satchels, but has some out + return TRUE; + } + + return FALSE; +} + +BOOL CSatchel::CanDeploy( void ) +{ + if ( m_pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] > 0 ) + { + // player is carrying some satchels + return TRUE; + } + + if ( m_chargeReady != 0 ) + { + // player isn't carrying any satchels, but has some out + return TRUE; + } + + return FALSE; +} + +BOOL CSatchel::Deploy( ) +{ + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.0; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + + if ( m_chargeReady ) + return DefaultDeploy( "models/v_satchel_radio.mdl", "models/p_satchel_radio.mdl", SATCHEL_RADIO_DRAW, "hive" ); + else + return DefaultDeploy( "models/v_satchel.mdl", "models/p_satchel.mdl", SATCHEL_DRAW, "trip" ); + + + return TRUE; +} + + +void CSatchel::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + + if ( m_chargeReady ) + { + SendWeaponAnim( SATCHEL_RADIO_HOLSTER ); + } + else + { + SendWeaponAnim( SATCHEL_DROP ); + } + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM); + + if ( !m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] && !m_chargeReady ) + { + m_pPlayer->pev->weapons &= ~(1<nextthink = gpGlobals->time + 0.1; + } +} + + + +void CSatchel::PrimaryAttack() +{ + switch (m_chargeReady) + { + case 0: + { + Throw( ); + } + break; + case 1: + { + SendWeaponAnim( SATCHEL_RADIO_FIRE ); + + edict_t *pPlayer = m_pPlayer->edict( ); + + CBaseEntity *pSatchel = NULL; + + while ((pSatchel = UTIL_FindEntityInSphere( pSatchel, m_pPlayer->pev->origin, 4096 )) != NULL) + { + if (FClassnameIs( pSatchel->pev, "monster_satchel")) + { + if (pSatchel->pev->owner == pPlayer) + { + pSatchel->Use( m_pPlayer, m_pPlayer, USE_ON, 0 ); + m_chargeReady = 2; + } + } + } + + m_chargeReady = 2; + m_flNextPrimaryAttack = GetNextAttackDelay(0.5); + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.5; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.5; + break; + } + + case 2: + // we're reloading, don't allow fire + { + } + break; + } +} + + +void CSatchel::SecondaryAttack( void ) +{ + if ( m_chargeReady != 2 ) + { + Throw( ); + } +} + + +void CSatchel::Throw( void ) +{ + if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] ) + { + Vector vecSrc = m_pPlayer->pev->origin; + + Vector vecThrow = gpGlobals->v_forward * 274 + m_pPlayer->pev->velocity; + +#ifndef CLIENT_DLL + CBaseEntity *pSatchel = Create( "monster_satchel", vecSrc, Vector( 0, 0, 0), m_pPlayer->edict() ); + pSatchel->pev->velocity = vecThrow; + pSatchel->pev->avelocity.y = 400; + + m_pPlayer->pev->viewmodel = MAKE_STRING("models/v_satchel_radio.mdl"); + m_pPlayer->pev->weaponmodel = MAKE_STRING("models/p_satchel_radio.mdl"); +#else + LoadVModel ( "models/v_satchel_radio.mdl", m_pPlayer ); +#endif + + SendWeaponAnim( SATCHEL_RADIO_DRAW ); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_chargeReady = 1; + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + m_flNextPrimaryAttack = GetNextAttackDelay(1.0); + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.5; + } +} + + +void CSatchel::WeaponIdle( void ) +{ + if ( m_flTimeWeaponIdle > UTIL_WeaponTimeBase() ) + return; + + switch( m_chargeReady ) + { + case 0: + SendWeaponAnim( SATCHEL_FIDGET1 ); + // use tripmine animations + strcpy( m_pPlayer->m_szAnimExtention, "trip" ); + break; + case 1: + SendWeaponAnim( SATCHEL_RADIO_FIDGET1 ); + // use hivehand animations + strcpy( m_pPlayer->m_szAnimExtention, "hive" ); + break; + case 2: + if ( !m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] ) + { + m_chargeReady = 0; + RetireWeapon(); + return; + } + +#ifndef CLIENT_DLL + m_pPlayer->pev->viewmodel = MAKE_STRING("models/v_satchel.mdl"); + m_pPlayer->pev->weaponmodel = MAKE_STRING("models/p_satchel.mdl"); +#else + LoadVModel ( "models/v_satchel.mdl", m_pPlayer ); +#endif + + SendWeaponAnim( SATCHEL_DRAW ); + + // use tripmine animations + strcpy( m_pPlayer->m_szAnimExtention, "trip" ); + + m_flNextPrimaryAttack = GetNextAttackDelay(0.5); + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.5; + m_chargeReady = 0; + break; + } + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 );// how long till we do this again. +} + +//========================================================= +// DeactivateSatchels - removes all satchels owned by +// the provided player. Should only be used upon death. +// +// Made this global on purpose. +//========================================================= +void DeactivateSatchels( CBasePlayer *pOwner ) +{ + edict_t *pFind; + + pFind = FIND_ENTITY_BY_CLASSNAME( NULL, "monster_satchel" ); + + while ( !FNullEnt( pFind ) ) + { + CBaseEntity *pEnt = CBaseEntity::Instance( pFind ); + CSatchelCharge *pSatchel = (CSatchelCharge *)pEnt; + + if ( pSatchel ) + { + if ( pSatchel->pev->owner == pOwner->edict() ) + { + pSatchel->Deactivate(); + } + } + + pFind = FIND_ENTITY_BY_CLASSNAME( pFind, "monster_satchel" ); + } +} + +#endif diff --git a/dlls/saverestore.h b/dlls/saverestore.h new file mode 100644 index 0000000..46ebfbb --- /dev/null +++ b/dlls/saverestore.h @@ -0,0 +1,169 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Implementation in UTIL.CPP +#ifndef SAVERESTORE_H +#define SAVERESTORE_H + +class CBaseEntity; + +class CSaveRestoreBuffer +{ +public: + CSaveRestoreBuffer( void ); + CSaveRestoreBuffer( SAVERESTOREDATA *pdata ); + ~CSaveRestoreBuffer( void ); + + int EntityIndex( entvars_t *pevLookup ); + int EntityIndex( edict_t *pentLookup ); + int EntityIndex( EOFFSET eoLookup ); + int EntityIndex( CBaseEntity *pEntity ); + + int EntityFlags( int entityIndex, int flags ) { return EntityFlagsSet( entityIndex, 0 ); } + int EntityFlagsSet( int entityIndex, int flags ); + + edict_t *EntityFromIndex( int entityIndex ); + + unsigned short TokenHash( const char *pszToken ); + +protected: + SAVERESTOREDATA *m_pdata; + void BufferRewind( int size ); + unsigned int HashString( const char *pszToken ); +}; + + +class CSave : public CSaveRestoreBuffer +{ +public: + CSave( SAVERESTOREDATA *pdata ) : CSaveRestoreBuffer( pdata ) {}; + + void WriteShort( const char *pname, const short *value, int count ); + void WriteInt( const char *pname, const int *value, int count ); // Save an int + void WriteFloat( const char *pname, const float *value, int count ); // Save a float + void WriteTime( const char *pname, const float *value, int count ); // Save a float (timevalue) + void WriteData( const char *pname, int size, const char *pdata ); // Save a binary data block + void WriteString( const char *pname, const char *pstring ); // Save a null-terminated string + void WriteString( const char *pname, const int *stringId, int count ); // Save a null-terminated string (engine string) + void WriteVector( const char *pname, const Vector &value ); // Save a vector + void WriteVector( const char *pname, const float *value, int count ); // Save a vector + void WritePositionVector( const char *pname, const Vector &value ); // Offset for landmark if necessary + void WritePositionVector( const char *pname, const float *value, int count ); // array of pos vectors + void WriteFunction( const char *pname, void **value, int count ); // Save a function pointer + int WriteEntVars( const char *pname, entvars_t *pev ); // Save entvars_t (entvars_t) + int WriteFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); + +private: + int DataEmpty( const char *pdata, int size ); + void BufferField( const char *pname, int size, const char *pdata ); + void BufferString( char *pdata, int len ); + void BufferData( const char *pdata, int size ); + void BufferHeader( const char *pname, int size ); +}; + +typedef struct +{ + unsigned short size; + unsigned short token; + char *pData; +} HEADER; + +class CRestore : public CSaveRestoreBuffer +{ +public: + CRestore( SAVERESTOREDATA *pdata ) : CSaveRestoreBuffer( pdata ) { m_global = 0; m_precache = TRUE; } + int ReadEntVars( const char *pname, entvars_t *pev ); // entvars_t + int ReadFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); + int ReadField( void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount, int startField, int size, char *pName, void *pData ); + int ReadInt( void ); + short ReadShort( void ); + int ReadNamedInt( const char *pName ); + char *ReadNamedString( const char *pName ); + int Empty( void ) { return (m_pdata == NULL) || ((m_pdata->pCurrentData-m_pdata->pBaseData)>=m_pdata->bufferSize); } + inline void SetGlobalMode( int global ) { m_global = global; } + void PrecacheMode( BOOL mode ) { m_precache = mode; } + +private: + char *BufferPointer( void ); + void BufferReadBytes( char *pOutput, int size ); + void BufferSkipBytes( int bytes ); + int BufferSkipZString( void ); + int BufferCheckZString( const char *string ); + + void BufferReadHeader( HEADER *pheader ); + + int m_global; // Restoring a global entity? + BOOL m_precache; +}; + +#define MAX_ENTITYARRAY 64 + +//#define ARRAYSIZE(p) (sizeof(p)/sizeof(p[0])) + +#define IMPLEMENT_SAVERESTORE(derivedClass,baseClass) \ + int derivedClass::Save( CSave &save )\ + {\ + if ( !baseClass::Save(save) )\ + return 0;\ + return save.WriteFields( #derivedClass, this, m_SaveData, ARRAYSIZE(m_SaveData) );\ + }\ + int derivedClass::Restore( CRestore &restore )\ + {\ + if ( !baseClass::Restore(restore) )\ + return 0;\ + return restore.ReadFields( #derivedClass, this, m_SaveData, ARRAYSIZE(m_SaveData) );\ + } + + +typedef enum { GLOBAL_OFF = 0, GLOBAL_ON = 1, GLOBAL_DEAD = 2 } GLOBALESTATE; + +typedef struct globalentity_s globalentity_t; + +struct globalentity_s +{ + char name[64]; + char levelName[32]; + GLOBALESTATE state; + globalentity_t *pNext; +}; + +class CGlobalState +{ +public: + CGlobalState(); + void Reset( void ); + void ClearStates( void ); + void EntityAdd( string_t globalname, string_t mapName, GLOBALESTATE state ); + void EntitySetState( string_t globalname, GLOBALESTATE state ); + void EntityUpdate( string_t globalname, string_t mapname ); + const globalentity_t *EntityFromTable( string_t globalname ); + GLOBALESTATE EntityGetState( string_t globalname ); + int EntityInTable( string_t globalname ) { return (Find( globalname ) != NULL) ? 1 : 0; } + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +//#ifdef _DEBUG + void DumpGlobals( void ); +//#endif + +private: + globalentity_t *Find( string_t globalname ); + globalentity_t *m_pList; + int m_listCount; +}; + +extern CGlobalState gGlobalState; + +#endif //SAVERESTORE_H diff --git a/dlls/schedule.cpp b/dlls/schedule.cpp new file mode 100644 index 0000000..488a770 --- /dev/null +++ b/dlls/schedule.cpp @@ -0,0 +1,1514 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// schedule.cpp - functions and data pertaining to the +// monsters' AI scheduling system. +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "animation.h" +#include "scripted.h" +#include "nodes.h" +#include "defaultai.h" +#include "soundent.h" + +extern CGraph WorldGraph; + +//========================================================= +// FHaveSchedule - Returns TRUE if monster's m_pSchedule +// is anything other than NULL. +//========================================================= +BOOL CBaseMonster :: FHaveSchedule( void ) +{ + if ( m_pSchedule == NULL ) + { + return FALSE; + } + + return TRUE; +} + +//========================================================= +// ClearSchedule - blanks out the caller's schedule pointer +// and index. +//========================================================= +void CBaseMonster :: ClearSchedule( void ) +{ + m_iTaskStatus = TASKSTATUS_NEW; + m_pSchedule = NULL; + m_iScheduleIndex = 0; +} + +//========================================================= +// FScheduleDone - Returns TRUE if the caller is on the +// last task in the schedule +//========================================================= +BOOL CBaseMonster :: FScheduleDone ( void ) +{ + ASSERT( m_pSchedule != NULL ); + + if ( m_iScheduleIndex == m_pSchedule->cTasks ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// ChangeSchedule - replaces the monster's schedule pointer +// with the passed pointer, and sets the ScheduleIndex back +// to 0 +//========================================================= +void CBaseMonster :: ChangeSchedule ( Schedule_t *pNewSchedule ) +{ + ASSERT( pNewSchedule != NULL ); + + m_pSchedule = pNewSchedule; + m_iScheduleIndex = 0; + m_iTaskStatus = TASKSTATUS_NEW; + m_afConditions = 0;// clear all of the conditions + m_failSchedule = SCHED_NONE; + + if ( m_pSchedule->iInterruptMask & bits_COND_HEAR_SOUND && !(m_pSchedule->iSoundMask) ) + { + ALERT ( at_aiconsole, "COND_HEAR_SOUND with no sound mask!\n" ); + } + else if ( m_pSchedule->iSoundMask && !(m_pSchedule->iInterruptMask & bits_COND_HEAR_SOUND) ) + { + ALERT ( at_aiconsole, "Sound mask without COND_HEAR_SOUND!\n" ); + } + +#if _DEBUG + if ( !ScheduleFromName( pNewSchedule->pName ) ) + { + ALERT( at_console, "Schedule %s not in table!!!\n", pNewSchedule->pName ); + } +#endif + +// this is very useful code if you can isolate a test case in a level with a single monster. It will notify +// you of every schedule selection the monster makes. +#if 0 + if ( FClassnameIs( pev, "monster_human_grunt" ) ) + { + Task_t *pTask = GetTask(); + + if ( pTask ) + { + const char *pName = NULL; + + if ( m_pSchedule ) + { + pName = m_pSchedule->pName; + } + else + { + pName = "No Schedule"; + } + + if ( !pName ) + { + pName = "Unknown"; + } + + ALERT( at_aiconsole, "%s: picked schedule %s\n", STRING( pev->classname ), pName ); + } + } +#endif// 0 + +} + +//========================================================= +// NextScheduledTask - increments the ScheduleIndex +//========================================================= +void CBaseMonster :: NextScheduledTask ( void ) +{ + ASSERT( m_pSchedule != NULL ); + + m_iTaskStatus = TASKSTATUS_NEW; + m_iScheduleIndex++; + + if ( FScheduleDone() ) + { + // just completed last task in schedule, so make it invalid by clearing it. + SetConditions( bits_COND_SCHEDULE_DONE ); + //ClearSchedule(); + } +} + +//========================================================= +// IScheduleFlags - returns an integer with all Conditions +// bits that are currently set and also set in the current +// schedule's Interrupt mask. +//========================================================= +int CBaseMonster :: IScheduleFlags ( void ) +{ + if( !m_pSchedule ) + { + return 0; + } + + // strip off all bits excepts the ones capable of breaking this schedule. + return m_afConditions & m_pSchedule->iInterruptMask; +} + +//========================================================= +// FScheduleValid - returns TRUE as long as the current +// schedule is still the proper schedule to be executing, +// taking into account all conditions +//========================================================= +BOOL CBaseMonster :: FScheduleValid ( void ) +{ + if ( m_pSchedule == NULL ) + { + // schedule is empty, and therefore not valid. + return FALSE; + } + + if ( HasConditions( m_pSchedule->iInterruptMask | bits_COND_SCHEDULE_DONE | bits_COND_TASK_FAILED ) ) + { +#ifdef DEBUG + if ( HasConditions ( bits_COND_TASK_FAILED ) && m_failSchedule == SCHED_NONE ) + { + // fail! Send a visual indicator. + ALERT ( at_aiconsole, "Schedule: %s Failed\n", m_pSchedule->pName ); + + Vector tmp = pev->origin; + tmp.z = pev->absmax.z + 16; + UTIL_Sparks( tmp ); + } +#endif // DEBUG + + // some condition has interrupted the schedule, or the schedule is done + return FALSE; + } + + return TRUE; +} + +//========================================================= +// MaintainSchedule - does all the per-think schedule maintenance. +// ensures that the monster leaves this function with a valid +// schedule! +//========================================================= +void CBaseMonster :: MaintainSchedule ( void ) +{ + Schedule_t *pNewSchedule; + int i; + + // UNDONE: Tune/fix this 10... This is just here so infinite loops are impossible + for ( i = 0; i < 10; i++ ) + { + if ( m_pSchedule != NULL && TaskIsComplete() ) + { + NextScheduledTask(); + } + + // validate existing schedule + if ( !FScheduleValid() || m_MonsterState != m_IdealMonsterState ) + { + // if we come into this block of code, the schedule is going to have to be changed. + // if the previous schedule was interrupted by a condition, GetIdealState will be + // called. Else, a schedule finished normally. + + // Notify the monster that his schedule is changing + ScheduleChange(); + + // Call GetIdealState if we're not dead and one or more of the following... + // - in COMBAT state with no enemy (it died?) + // - conditions bits (excluding SCHEDULE_DONE) indicate interruption, + // - schedule is done but schedule indicates it wants GetIdealState called + // after successful completion (by setting bits_COND_SCHEDULE_DONE in iInterruptMask) + // DEAD & SCRIPT are not suggestions, they are commands! + if ( m_IdealMonsterState != MONSTERSTATE_DEAD && + (m_IdealMonsterState != MONSTERSTATE_SCRIPT || m_IdealMonsterState == m_MonsterState) ) + { + if ( (m_afConditions && !HasConditions(bits_COND_SCHEDULE_DONE)) || + (m_pSchedule && (m_pSchedule->iInterruptMask & bits_COND_SCHEDULE_DONE)) || + ((m_MonsterState == MONSTERSTATE_COMBAT) && (m_hEnemy == NULL)) ) + { + GetIdealState(); + } + } + if ( HasConditions( bits_COND_TASK_FAILED ) && m_MonsterState == m_IdealMonsterState ) + { + if ( m_failSchedule != SCHED_NONE ) + pNewSchedule = GetScheduleOfType( m_failSchedule ); + else + pNewSchedule = GetScheduleOfType( SCHED_FAIL ); + // schedule was invalid because the current task failed to start or complete + ALERT ( at_aiconsole, "Schedule Failed at %d!\n", m_iScheduleIndex ); + ChangeSchedule( pNewSchedule ); + } + else + { + SetState( m_IdealMonsterState ); + if ( m_MonsterState == MONSTERSTATE_SCRIPT || m_MonsterState == MONSTERSTATE_DEAD ) + pNewSchedule = CBaseMonster::GetSchedule(); + else + pNewSchedule = GetSchedule(); + ChangeSchedule( pNewSchedule ); + } + } + + if ( m_iTaskStatus == TASKSTATUS_NEW ) + { + Task_t *pTask = GetTask(); + ASSERT( pTask != NULL ); + TaskBegin(); + StartTask( pTask ); + } + + // UNDONE: Twice?!!! + if ( m_Activity != m_IdealActivity ) + { + SetActivity ( m_IdealActivity ); + } + + if ( !TaskIsComplete() && m_iTaskStatus != TASKSTATUS_NEW ) + break; + } + + if ( TaskIsRunning() ) + { + Task_t *pTask = GetTask(); + ASSERT( pTask != NULL ); + RunTask( pTask ); + } + + // UNDONE: We have to do this so that we have an animation set to blend to if RunTask changes the animation + // RunTask() will always change animations at the end of a script! + // Don't do this twice + if ( m_Activity != m_IdealActivity ) + { + SetActivity ( m_IdealActivity ); + } +} + +//========================================================= +// RunTask +//========================================================= +void CBaseMonster :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_TURN_RIGHT: + case TASK_TURN_LEFT: + { + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + TaskComplete(); + } + break; + } + + case TASK_PLAY_SEQUENCE_FACE_ENEMY: + case TASK_PLAY_SEQUENCE_FACE_TARGET: + { + CBaseEntity *pTarget; + + if ( pTask->iTask == TASK_PLAY_SEQUENCE_FACE_TARGET ) + pTarget = m_hTargetEnt; + else + pTarget = m_hEnemy; + if ( pTarget ) + { + pev->ideal_yaw = UTIL_VecToYaw( pTarget->pev->origin - pev->origin ); + ChangeYaw( pev->yaw_speed ); + } + if ( m_fSequenceFinished ) + TaskComplete(); + } + break; + + case TASK_PLAY_SEQUENCE: + case TASK_PLAY_ACTIVE_IDLE: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + break; + } + + + case TASK_FACE_ENEMY: + { + MakeIdealYaw( m_vecEnemyLKP ); + + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + TaskComplete(); + } + break; + } + case TASK_FACE_HINTNODE: + case TASK_FACE_LASTPOSITION: + case TASK_FACE_TARGET: + case TASK_FACE_IDEAL: + case TASK_FACE_ROUTE: + { + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + TaskComplete(); + } + break; + } + case TASK_WAIT_PVS: + { + if ( !FNullEnt(FIND_CLIENT_IN_PVS(edict())) ) + { + TaskComplete(); + } + break; + } + case TASK_WAIT_INDEFINITE: + { + // don't do anything. + break; + } + case TASK_WAIT: + case TASK_WAIT_RANDOM: + { + if ( gpGlobals->time >= m_flWaitFinished ) + { + TaskComplete(); + } + break; + } + case TASK_WAIT_FACE_ENEMY: + { + MakeIdealYaw ( m_vecEnemyLKP ); + ChangeYaw( pev->yaw_speed ); + + if ( gpGlobals->time >= m_flWaitFinished ) + { + TaskComplete(); + } + break; + } + case TASK_MOVE_TO_TARGET_RANGE: + { + float distance; + + if ( m_hTargetEnt == NULL ) + TaskFail(); + else + { + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + // Re-evaluate when you think your finished, or the target has moved too far + if ( (distance < pTask->flData) || (m_vecMoveGoal - m_hTargetEnt->pev->origin).Length() > pTask->flData * 0.5 ) + { + m_vecMoveGoal = m_hTargetEnt->pev->origin; + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + FRefreshRoute(); + } + + // Set the appropriate activity based on an overlapping range + // overlap the range to prevent oscillation + if ( distance < pTask->flData ) + { + TaskComplete(); + RouteClear(); // Stop moving + } + else if ( distance < 190 && m_movementActivity != ACT_WALK ) + m_movementActivity = ACT_WALK; + else if ( distance >= 270 && m_movementActivity != ACT_RUN ) + m_movementActivity = ACT_RUN; + } + + break; + } + case TASK_WAIT_FOR_MOVEMENT: + { + if (MovementIsComplete()) + { + TaskComplete(); + RouteClear(); // Stop moving + } + break; + } + case TASK_DIE: + { + if ( m_fSequenceFinished && pev->frame >= 255 ) + { + pev->deadflag = DEAD_DEAD; + + SetThink ( NULL ); + StopAnimation(); + + if ( !BBoxFlat() ) + { + // a bit of a hack. If a corpses' bbox is positioned such that being left solid so that it can be attacked will + // block the player on a slope or stairs, the corpse is made nonsolid. +// pev->solid = SOLID_NOT; + UTIL_SetSize ( pev, Vector ( -4, -4, 0 ), Vector ( 4, 4, 1 ) ); + } + else // !!!HACKHACK - put monster in a thin, wide bounding box until we fix the solid type/bounding volume problem + UTIL_SetSize ( pev, Vector ( pev->mins.x, pev->mins.y, pev->mins.z ), Vector ( pev->maxs.x, pev->maxs.y, pev->mins.z + 1 ) ); + + if ( ShouldFadeOnDeath() ) + { + // this monster was created by a monstermaker... fade the corpse out. + SUB_StartFadeOut(); + } + else + { + // body is gonna be around for a while, so have it stink for a bit. + CSoundEnt::InsertSound ( bits_SOUND_CARCASS, pev->origin, 384, 30 ); + } + } + break; + } + case TASK_RANGE_ATTACK1_NOTURN: + case TASK_MELEE_ATTACK1_NOTURN: + case TASK_MELEE_ATTACK2_NOTURN: + case TASK_RANGE_ATTACK2_NOTURN: + case TASK_RELOAD_NOTURN: + { + if ( m_fSequenceFinished ) + { + m_Activity = ACT_RESET; + TaskComplete(); + } + break; + } + case TASK_RANGE_ATTACK1: + case TASK_MELEE_ATTACK1: + case TASK_MELEE_ATTACK2: + case TASK_RANGE_ATTACK2: + case TASK_SPECIAL_ATTACK1: + case TASK_SPECIAL_ATTACK2: + case TASK_RELOAD: + { + MakeIdealYaw ( m_vecEnemyLKP ); + ChangeYaw ( pev->yaw_speed ); + + if ( m_fSequenceFinished ) + { + m_Activity = ACT_RESET; + TaskComplete(); + } + break; + } + case TASK_SMALL_FLINCH: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + } + break; + case TASK_WAIT_FOR_SCRIPT: + { + if ( m_pCine->m_iDelay <= 0 && gpGlobals->time >= m_pCine->m_startTime ) + { + TaskComplete(); + m_pCine->StartSequence( (CBaseMonster *)this, m_pCine->m_iszPlay, TRUE ); + if ( m_fSequenceFinished ) + ClearSchedule(); + pev->framerate = 1.0; + //ALERT( at_aiconsole, "Script %s has begun for %s\n", STRING( m_pCine->m_iszPlay ), STRING(pev->classname) ); + } + break; + } + case TASK_PLAY_SCRIPT: + { + if (m_fSequenceFinished) + { + m_pCine->SequenceDone( this ); + } + break; + } + } +} + +//========================================================= +// SetTurnActivity - measures the difference between the way +// the monster is facing and determines whether or not to +// select one of the 180 turn animations. +//========================================================= +void CBaseMonster :: SetTurnActivity ( void ) +{ + float flYD; + flYD = FlYawDiff(); + + if ( flYD <= -45 && LookupActivity ( ACT_TURN_RIGHT ) != ACTIVITY_NOT_AVAILABLE ) + {// big right turn + m_IdealActivity = ACT_TURN_RIGHT; + } + else if ( flYD > 45 && LookupActivity ( ACT_TURN_LEFT ) != ACTIVITY_NOT_AVAILABLE ) + {// big left turn + m_IdealActivity = ACT_TURN_LEFT; + } +} + +//========================================================= +// Start task - selects the correct activity and performs +// any necessary calculations to start the next task on the +// schedule. +//========================================================= +void CBaseMonster :: StartTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_TURN_RIGHT: + { + float flCurrentYaw; + + flCurrentYaw = UTIL_AngleMod( pev->angles.y ); + pev->ideal_yaw = UTIL_AngleMod( flCurrentYaw - pTask->flData ); + SetTurnActivity(); + break; + } + case TASK_TURN_LEFT: + { + float flCurrentYaw; + + flCurrentYaw = UTIL_AngleMod( pev->angles.y ); + pev->ideal_yaw = UTIL_AngleMod( flCurrentYaw + pTask->flData ); + SetTurnActivity(); + break; + } + case TASK_REMEMBER: + { + Remember ( (int)pTask->flData ); + TaskComplete(); + break; + } + case TASK_FORGET: + { + Forget ( (int)pTask->flData ); + TaskComplete(); + break; + } + case TASK_FIND_HINTNODE: + { + m_iHintNode = FindHintNode(); + + if ( m_iHintNode != NO_NODE ) + { + TaskComplete(); + } + else + { + TaskFail(); + } + break; + } + case TASK_STORE_LASTPOSITION: + { + m_vecLastPosition = pev->origin; + TaskComplete(); + break; + } + case TASK_CLEAR_LASTPOSITION: + { + m_vecLastPosition = g_vecZero; + TaskComplete(); + break; + } + case TASK_CLEAR_HINTNODE: + { + m_iHintNode = NO_NODE; + TaskComplete(); + break; + } + case TASK_STOP_MOVING: + { + if ( m_IdealActivity == m_movementActivity ) + { + m_IdealActivity = GetStoppedActivity(); + } + + RouteClear(); + TaskComplete(); + break; + } + case TASK_PLAY_SEQUENCE_FACE_ENEMY: + case TASK_PLAY_SEQUENCE_FACE_TARGET: + case TASK_PLAY_SEQUENCE: + { + m_IdealActivity = ( Activity )( int )pTask->flData; + break; + } + case TASK_PLAY_ACTIVE_IDLE: + { + // monsters verify that they have a sequence for the node's activity BEFORE + // moving towards the node, so it's ok to just set the activity without checking here. + m_IdealActivity = ( Activity )WorldGraph.m_pNodes[ m_iHintNode ].m_sHintActivity; + break; + } + case TASK_SET_SCHEDULE: + { + Schedule_t *pNewSchedule; + + pNewSchedule = GetScheduleOfType( (int)pTask->flData ); + + if ( pNewSchedule ) + { + ChangeSchedule( pNewSchedule ); + } + else + { + TaskFail(); + } + + break; + } + case TASK_FIND_NEAR_NODE_COVER_FROM_ENEMY: + { + if ( m_hEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, 0, pTask->flData ) ) + { + // try for cover farther than the FLData from the schedule. + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_FAR_NODE_COVER_FROM_ENEMY: + { + if ( m_hEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, pTask->flData, CoverRadius() ) ) + { + // try for cover farther than the FLData from the schedule. + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_NODE_COVER_FROM_ENEMY: + { + if ( m_hEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, 0, CoverRadius() ) ) + { + // try for cover farther than the FLData from the schedule. + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_COVER_FROM_ENEMY: + { + entvars_t *pevCover; + + if ( m_hEnemy == NULL ) + { + // Find cover from self if no enemy available + pevCover = pev; +// TaskFail(); +// return; + } + else + pevCover = m_hEnemy->pev; + + if ( FindLateralCover( pevCover->origin, pevCover->view_ofs ) ) + { + // try lateral first + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else if ( FindCover( pevCover->origin, pevCover->view_ofs, 0, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_COVER_FROM_ORIGIN: + { + if ( FindCover( pev->origin, pev->view_ofs, 0, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else + { + // no cover! + TaskFail(); + } + } + break; + case TASK_FIND_COVER_FROM_BEST_SOUND: + { + CSound *pBestSound; + + pBestSound = PBestSound(); + + ASSERT( pBestSound != NULL ); + /* + if ( pBestSound && FindLateralCover( pBestSound->m_vecOrigin, g_vecZero ) ) + { + // try lateral first + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + */ + + if ( pBestSound && FindCover( pBestSound->m_vecOrigin, g_vecZero, pBestSound->m_iVolume, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else + { + // no coverwhatsoever. or no sound in list + TaskFail(); + } + break; + } + case TASK_FACE_HINTNODE: + { + pev->ideal_yaw = WorldGraph.m_pNodes[ m_iHintNode ].m_flHintYaw; + SetTurnActivity(); + break; + } + + case TASK_FACE_LASTPOSITION: + MakeIdealYaw ( m_vecLastPosition ); + SetTurnActivity(); + break; + + case TASK_FACE_TARGET: + if ( m_hTargetEnt != NULL ) + { + MakeIdealYaw ( m_hTargetEnt->pev->origin ); + SetTurnActivity(); + } + else + TaskFail(); + break; + case TASK_FACE_ENEMY: + { + MakeIdealYaw ( m_vecEnemyLKP ); + SetTurnActivity(); + break; + } + case TASK_FACE_IDEAL: + { + SetTurnActivity(); + break; + } + case TASK_FACE_ROUTE: + { + if (FRouteClear()) + { + ALERT(at_aiconsole, "No route to face!\n"); + TaskFail(); + } + else + { + MakeIdealYaw(m_Route[m_iRouteIndex].vecLocation); + SetTurnActivity(); + } + break; + } + case TASK_WAIT_PVS: + case TASK_WAIT_INDEFINITE: + { + // don't do anything. + break; + } + case TASK_WAIT: + case TASK_WAIT_FACE_ENEMY: + {// set a future time that tells us when the wait is over. + m_flWaitFinished = gpGlobals->time + pTask->flData; + break; + } + case TASK_WAIT_RANDOM: + {// set a future time that tells us when the wait is over. + m_flWaitFinished = gpGlobals->time + RANDOM_FLOAT( 0.1, pTask->flData ); + break; + } + case TASK_MOVE_TO_TARGET_RANGE: + { + if ( (m_hTargetEnt->pev->origin - pev->origin).Length() < 1 ) + TaskComplete(); + else + { + m_vecMoveGoal = m_hTargetEnt->pev->origin; + if ( !MoveToTarget( ACT_WALK, 2 ) ) + TaskFail(); + } + break; + } + case TASK_RUN_TO_TARGET: + case TASK_WALK_TO_TARGET: + { + Activity newActivity; + + if ( (m_hTargetEnt->pev->origin - pev->origin).Length() < 1 ) + TaskComplete(); + else + { + if ( pTask->iTask == TASK_WALK_TO_TARGET ) + newActivity = ACT_WALK; + else + newActivity = ACT_RUN; + // This monster can't do this! + if ( LookupActivity( newActivity ) == ACTIVITY_NOT_AVAILABLE ) + TaskComplete(); + else + { + if ( m_hTargetEnt == NULL || !MoveToTarget( newActivity, 2 ) ) + { + TaskFail(); + ALERT( at_aiconsole, "%s Failed to reach target!!!\n", STRING(pev->classname) ); + RouteClear(); + } + } + } + TaskComplete(); + break; + } + case TASK_CLEAR_MOVE_WAIT: + { + m_flMoveWaitFinished = gpGlobals->time; + TaskComplete(); + break; + } + case TASK_MELEE_ATTACK1_NOTURN: + case TASK_MELEE_ATTACK1: + { + m_IdealActivity = ACT_MELEE_ATTACK1; + break; + } + case TASK_MELEE_ATTACK2_NOTURN: + case TASK_MELEE_ATTACK2: + { + m_IdealActivity = ACT_MELEE_ATTACK2; + break; + } + case TASK_RANGE_ATTACK1_NOTURN: + case TASK_RANGE_ATTACK1: + { + m_IdealActivity = ACT_RANGE_ATTACK1; + break; + } + case TASK_RANGE_ATTACK2_NOTURN: + case TASK_RANGE_ATTACK2: + { + m_IdealActivity = ACT_RANGE_ATTACK2; + break; + } + case TASK_RELOAD_NOTURN: + case TASK_RELOAD: + { + m_IdealActivity = ACT_RELOAD; + break; + } + case TASK_SPECIAL_ATTACK1: + { + m_IdealActivity = ACT_SPECIAL_ATTACK1; + break; + } + case TASK_SPECIAL_ATTACK2: + { + m_IdealActivity = ACT_SPECIAL_ATTACK2; + break; + } + case TASK_SET_ACTIVITY: + { + m_IdealActivity = (Activity)(int)pTask->flData; + TaskComplete(); + break; + } + case TASK_GET_PATH_TO_ENEMY_LKP: + { + if ( BuildRoute ( m_vecEnemyLKP, bits_MF_TO_LOCATION, NULL ) ) + { + TaskComplete(); + } + else if (BuildNearestRoute( m_vecEnemyLKP, pev->view_ofs, 0, (m_vecEnemyLKP - pev->origin).Length() )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemyLKP failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_ENEMY: + { + CBaseEntity *pEnemy = m_hEnemy; + + if ( pEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( BuildRoute ( pEnemy->pev->origin, bits_MF_TO_ENEMY, pEnemy ) ) + { + TaskComplete(); + } + else if (BuildNearestRoute( pEnemy->pev->origin, pEnemy->pev->view_ofs, 0, (pEnemy->pev->origin - pev->origin).Length() )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemy failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_ENEMY_CORPSE: + { + UTIL_MakeVectors( pev->angles ); + if ( BuildRoute ( m_vecEnemyLKP - gpGlobals->v_forward * 64, bits_MF_TO_LOCATION, NULL ) ) + { + TaskComplete(); + } + else + { + ALERT ( at_aiconsole, "GetPathToEnemyCorpse failed!!\n" ); + TaskFail(); + } + } + break; + case TASK_GET_PATH_TO_SPOT: + { + CBaseEntity *pPlayer = CBaseEntity::Instance( FIND_ENTITY_BY_CLASSNAME( NULL, "player" ) ); + if ( BuildRoute ( m_vecMoveGoal, bits_MF_TO_LOCATION, pPlayer ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToSpot failed!!\n" ); + TaskFail(); + } + break; + } + + case TASK_GET_PATH_TO_TARGET: + { + RouteClear(); + if ( m_hTargetEnt != NULL && MoveToTarget( m_movementActivity, 1 ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToSpot failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_HINTNODE:// for active idles! + { + if ( MoveToLocation( m_movementActivity, 2, WorldGraph.m_pNodes[ m_iHintNode ].m_vecOrigin ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToHintNode failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_LASTPOSITION: + { + m_vecMoveGoal = m_vecLastPosition; + + if ( MoveToLocation( m_movementActivity, 2, m_vecMoveGoal ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToLastPosition failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_BESTSOUND: + { + CSound *pSound; + + pSound = PBestSound(); + + if ( pSound && MoveToLocation( m_movementActivity, 2, pSound->m_vecOrigin ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToBestSound failed!!\n" ); + TaskFail(); + } + break; + } +case TASK_GET_PATH_TO_BESTSCENT: + { + CSound *pScent; + + pScent = PBestScent(); + + if ( pScent && MoveToLocation( m_movementActivity, 2, pScent->m_vecOrigin ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToBestScent failed!!\n" ); + + TaskFail(); + } + break; + } + case TASK_RUN_PATH: + { + // UNDONE: This is in some default AI and some monsters can't run? -- walk instead? + if ( LookupActivity( ACT_RUN ) != ACTIVITY_NOT_AVAILABLE ) + { + m_movementActivity = ACT_RUN; + } + else + { + m_movementActivity = ACT_WALK; + } + TaskComplete(); + break; + } + case TASK_WALK_PATH: + { + if ( pev->movetype == MOVETYPE_FLY ) + { + m_movementActivity = ACT_FLY; + } + if ( LookupActivity( ACT_WALK ) != ACTIVITY_NOT_AVAILABLE ) + { + m_movementActivity = ACT_WALK; + } + else + { + m_movementActivity = ACT_RUN; + } + TaskComplete(); + break; + } + case TASK_STRAFE_PATH: + { + Vector2D vec2DirToPoint; + Vector2D vec2RightSide; + + // to start strafing, we have to first figure out if the target is on the left side or right side + UTIL_MakeVectors ( pev->angles ); + + vec2DirToPoint = ( m_Route[ 0 ].vecLocation - pev->origin ).Make2D().Normalize(); + vec2RightSide = gpGlobals->v_right.Make2D().Normalize(); + + if ( DotProduct ( vec2DirToPoint, vec2RightSide ) > 0 ) + { + // strafe right + m_movementActivity = ACT_STRAFE_RIGHT; + } + else + { + // strafe left + m_movementActivity = ACT_STRAFE_LEFT; + } + TaskComplete(); + break; + } + + + case TASK_WAIT_FOR_MOVEMENT: + { + if (FRouteClear()) + { + TaskComplete(); + } + break; + } + + case TASK_EAT: + { + Eat( pTask->flData ); + TaskComplete(); + break; + } + case TASK_SMALL_FLINCH: + { + m_IdealActivity = GetSmallFlinchActivity(); + break; + } + case TASK_DIE: + { + RouteClear(); + + m_IdealActivity = GetDeathActivity(); + + pev->deadflag = DEAD_DYING; + break; + } + case TASK_SOUND_WAKE: + { + AlertSound(); + TaskComplete(); + break; + } + case TASK_SOUND_DIE: + { + DeathSound(); + TaskComplete(); + break; + } + case TASK_SOUND_IDLE: + { + IdleSound(); + TaskComplete(); + break; + } + case TASK_SOUND_PAIN: + { + PainSound(); + TaskComplete(); + break; + } + case TASK_SOUND_DEATH: + { + DeathSound(); + TaskComplete(); + break; + } + case TASK_SOUND_ANGRY: + { + // sounds are complete as soon as we get here, cause we've already played them. + ALERT ( at_aiconsole, "SOUND\n" ); + TaskComplete(); + break; + } + case TASK_WAIT_FOR_SCRIPT: + { + if (m_pCine->m_iszIdle) + { + m_pCine->StartSequence( (CBaseMonster *)this, m_pCine->m_iszIdle, FALSE ); + if (FStrEq( STRING(m_pCine->m_iszIdle), STRING(m_pCine->m_iszPlay))) + { + pev->framerate = 0; + } + } + else + m_IdealActivity = ACT_IDLE; + + break; + } + case TASK_PLAY_SCRIPT: + { + pev->movetype = MOVETYPE_FLY; + ClearBits(pev->flags, FL_ONGROUND); + m_scriptState = SCRIPT_PLAYING; + break; + } + case TASK_ENABLE_SCRIPT: + { + m_pCine->DelayStart( 0 ); + TaskComplete(); + break; + } + case TASK_PLANT_ON_SCRIPT: + { + if ( m_hTargetEnt != NULL ) + { + pev->origin = m_hTargetEnt->pev->origin; // Plant on target + } + + TaskComplete(); + break; + } + case TASK_FACE_SCRIPT: + { + if ( m_hTargetEnt != NULL ) + { + pev->ideal_yaw = UTIL_AngleMod( m_hTargetEnt->pev->angles.y ); + } + + TaskComplete(); + m_IdealActivity = ACT_IDLE; + RouteClear(); + break; + } + + case TASK_SUGGEST_STATE: + { + m_IdealMonsterState = (MONSTERSTATE)(int)pTask->flData; + TaskComplete(); + break; + } + + case TASK_SET_FAIL_SCHEDULE: + m_failSchedule = (int)pTask->flData; + TaskComplete(); + break; + + case TASK_CLEAR_FAIL_SCHEDULE: + m_failSchedule = SCHED_NONE; + TaskComplete(); + break; + + default: + { + ALERT ( at_aiconsole, "No StartTask entry for %d\n", (SHARED_TASKS)pTask->iTask ); + break; + } + } +} + +//========================================================= +// GetTask - returns a pointer to the current +// scheduled task. NULL if there's a problem. +//========================================================= +Task_t *CBaseMonster :: GetTask ( void ) +{ + if ( m_iScheduleIndex < 0 || m_iScheduleIndex >= m_pSchedule->cTasks ) + { + // m_iScheduleIndex is not within valid range for the monster's current schedule. + return NULL; + } + else + { + return &m_pSchedule->pTasklist[ m_iScheduleIndex ]; + } +} + +//========================================================= +// GetSchedule - Decides which type of schedule best suits +// the monster's current state and conditions. Then calls +// monster's member function to get a pointer to a schedule +// of the proper type. +//========================================================= +Schedule_t *CBaseMonster :: GetSchedule ( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_PRONE: + { + return GetScheduleOfType( SCHED_BARNACLE_VICTIM_GRAB ); + break; + } + case MONSTERSTATE_NONE: + { + ALERT ( at_aiconsole, "MONSTERSTATE IS NONE!\n" ); + break; + } + case MONSTERSTATE_IDLE: + { + if ( HasConditions ( bits_COND_HEAR_SOUND ) ) + { + return GetScheduleOfType( SCHED_ALERT_FACE ); + } + else if ( FRouteClear() ) + { + // no valid route! + return GetScheduleOfType( SCHED_IDLE_STAND ); + } + else + { + // valid route. Get moving + return GetScheduleOfType( SCHED_IDLE_WALK ); + } + break; + } + case MONSTERSTATE_ALERT: + { + if ( HasConditions( bits_COND_ENEMY_DEAD ) && LookupActivity( ACT_VICTORY_DANCE ) != ACTIVITY_NOT_AVAILABLE ) + { + return GetScheduleOfType ( SCHED_VICTORY_DANCE ); + } + + if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE) ) + { + if ( fabs( FlYawDiff() ) < (1.0 - m_flFieldOfView) * 60 ) // roughly in the correct direction + { + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ORIGIN ); + } + else + { + return GetScheduleOfType( SCHED_ALERT_SMALL_FLINCH ); + } + } + + else if ( HasConditions ( bits_COND_HEAR_SOUND ) ) + { + return GetScheduleOfType( SCHED_ALERT_FACE ); + } + else + { + return GetScheduleOfType( SCHED_ALERT_STAND ); + } + break; + } + case MONSTERSTATE_COMBAT: + { + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // clear the current (dead) enemy and try to find another. + m_hEnemy = NULL; + + if ( GetEnemy() ) + { + ClearConditions( bits_COND_ENEMY_DEAD ); + return GetSchedule(); + } + else + { + SetState( MONSTERSTATE_ALERT ); + return GetSchedule(); + } + } + + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + return GetScheduleOfType ( SCHED_WAKE_ANGRY ); + } + else if (HasConditions(bits_COND_LIGHT_DAMAGE) && !HasMemory( bits_MEMORY_FLINCHED) ) + { + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + else if ( !HasConditions(bits_COND_SEE_ENEMY) ) + { + // we can't see the enemy + if ( !HasConditions(bits_COND_ENEMY_OCCLUDED) ) + { + // enemy is unseen, but not occluded! + // turn to face enemy + return GetScheduleOfType( SCHED_COMBAT_FACE ); + } + else + { + // chase! + return GetScheduleOfType( SCHED_CHASE_ENEMY ); + } + } + else + { + // we can see the enemy + if ( HasConditions(bits_COND_CAN_RANGE_ATTACK1) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + if ( HasConditions(bits_COND_CAN_RANGE_ATTACK2) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + if ( HasConditions(bits_COND_CAN_MELEE_ATTACK1) ) + { + return GetScheduleOfType( SCHED_MELEE_ATTACK1 ); + } + if ( HasConditions(bits_COND_CAN_MELEE_ATTACK2) ) + { + return GetScheduleOfType( SCHED_MELEE_ATTACK2 ); + } + if ( !HasConditions(bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_MELEE_ATTACK1) ) + { + // if we can see enemy but can't use either attack type, we must need to get closer to enemy + return GetScheduleOfType( SCHED_CHASE_ENEMY ); + } + else if ( !FacingIdeal() ) + { + //turn + return GetScheduleOfType( SCHED_COMBAT_FACE ); + } + else + { + ALERT ( at_aiconsole, "No suitable combat schedule!\n" ); + } + } + break; + } + case MONSTERSTATE_DEAD: + { + return GetScheduleOfType( SCHED_DIE ); + break; + } + case MONSTERSTATE_SCRIPT: + { + ASSERT( m_pCine != NULL ); + if ( !m_pCine ) + { + ALERT( at_aiconsole, "Script failed for %s\n", STRING(pev->classname) ); + CineCleanup(); + return GetScheduleOfType( SCHED_IDLE_STAND ); + } + + return GetScheduleOfType( SCHED_AISCRIPT ); + } + default: + { + ALERT ( at_aiconsole, "Invalid State for GetSchedule!\n" ); + break; + } + } + + return &slError[ 0 ]; +} diff --git a/dlls/schedule.h b/dlls/schedule.h new file mode 100644 index 0000000..0c09441 --- /dev/null +++ b/dlls/schedule.h @@ -0,0 +1,290 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Scheduling +//========================================================= + +#ifndef SCHEDULE_H +#define SCHEDULE_H + +#define TASKSTATUS_NEW 0 // Just started +#define TASKSTATUS_RUNNING 1 // Running task & movement +#define TASKSTATUS_RUNNING_MOVEMENT 2 // Just running movement +#define TASKSTATUS_RUNNING_TASK 3 // Just running task +#define TASKSTATUS_COMPLETE 4 // Completed, get next task + + +//========================================================= +// These are the schedule types +//========================================================= +typedef enum +{ + SCHED_NONE = 0, + SCHED_IDLE_STAND, + SCHED_IDLE_WALK, + SCHED_WAKE_ANGRY, + SCHED_WAKE_CALLED, + SCHED_ALERT_FACE, + SCHED_ALERT_SMALL_FLINCH, + SCHED_ALERT_BIG_FLINCH, + SCHED_ALERT_STAND, + SCHED_INVESTIGATE_SOUND, + SCHED_COMBAT_FACE, + SCHED_COMBAT_STAND, + SCHED_CHASE_ENEMY, + SCHED_CHASE_ENEMY_FAILED, + SCHED_VICTORY_DANCE, + SCHED_TARGET_FACE, + SCHED_TARGET_CHASE, + SCHED_SMALL_FLINCH, + SCHED_TAKE_COVER_FROM_ENEMY, + SCHED_TAKE_COVER_FROM_BEST_SOUND, + SCHED_TAKE_COVER_FROM_ORIGIN, + SCHED_COWER, // usually a last resort! + SCHED_MELEE_ATTACK1, + SCHED_MELEE_ATTACK2, + SCHED_RANGE_ATTACK1, + SCHED_RANGE_ATTACK2, + SCHED_SPECIAL_ATTACK1, + SCHED_SPECIAL_ATTACK2, + SCHED_STANDOFF, + SCHED_ARM_WEAPON, + SCHED_RELOAD, + SCHED_GUARD, + SCHED_AMBUSH, + SCHED_DIE, + SCHED_WAIT_TRIGGER, + SCHED_FOLLOW, + SCHED_SLEEP, + SCHED_WAKE, + SCHED_BARNACLE_VICTIM_GRAB, + SCHED_BARNACLE_VICTIM_CHOMP, + SCHED_AISCRIPT, + SCHED_FAIL, + + LAST_COMMON_SCHEDULE // Leave this at the bottom +} SCHEDULE_TYPE; + +//========================================================= +// These are the shared tasks +//========================================================= +typedef enum +{ + TASK_INVALID = 0, + TASK_WAIT, + TASK_WAIT_FACE_ENEMY, + TASK_WAIT_PVS, + TASK_SUGGEST_STATE, + TASK_WALK_TO_TARGET, + TASK_RUN_TO_TARGET, + TASK_MOVE_TO_TARGET_RANGE, + TASK_GET_PATH_TO_ENEMY, + TASK_GET_PATH_TO_ENEMY_LKP, + TASK_GET_PATH_TO_ENEMY_CORPSE, + TASK_GET_PATH_TO_LEADER, + TASK_GET_PATH_TO_SPOT, + TASK_GET_PATH_TO_TARGET, + TASK_GET_PATH_TO_HINTNODE, + TASK_GET_PATH_TO_LASTPOSITION, + TASK_GET_PATH_TO_BESTSOUND, + TASK_GET_PATH_TO_BESTSCENT, + TASK_RUN_PATH, + TASK_WALK_PATH, + TASK_STRAFE_PATH, + TASK_CLEAR_MOVE_WAIT, + TASK_STORE_LASTPOSITION, + TASK_CLEAR_LASTPOSITION, + TASK_PLAY_ACTIVE_IDLE, + TASK_FIND_HINTNODE, + TASK_CLEAR_HINTNODE, + TASK_SMALL_FLINCH, + TASK_FACE_IDEAL, + TASK_FACE_ROUTE, + TASK_FACE_ENEMY, + TASK_FACE_HINTNODE, + TASK_FACE_TARGET, + TASK_FACE_LASTPOSITION, + TASK_RANGE_ATTACK1, + TASK_RANGE_ATTACK2, + TASK_MELEE_ATTACK1, + TASK_MELEE_ATTACK2, + TASK_RELOAD, + TASK_RANGE_ATTACK1_NOTURN, + TASK_RANGE_ATTACK2_NOTURN, + TASK_MELEE_ATTACK1_NOTURN, + TASK_MELEE_ATTACK2_NOTURN, + TASK_RELOAD_NOTURN, + TASK_SPECIAL_ATTACK1, + TASK_SPECIAL_ATTACK2, + TASK_CROUCH, + TASK_STAND, + TASK_GUARD, + TASK_STEP_LEFT, + TASK_STEP_RIGHT, + TASK_STEP_FORWARD, + TASK_STEP_BACK, + TASK_DODGE_LEFT, + TASK_DODGE_RIGHT, + TASK_SOUND_ANGRY, + TASK_SOUND_DEATH, + TASK_SET_ACTIVITY, + TASK_SET_SCHEDULE, + TASK_SET_FAIL_SCHEDULE, + TASK_CLEAR_FAIL_SCHEDULE, + TASK_PLAY_SEQUENCE, + TASK_PLAY_SEQUENCE_FACE_ENEMY, + TASK_PLAY_SEQUENCE_FACE_TARGET, + TASK_SOUND_IDLE, + TASK_SOUND_WAKE, + TASK_SOUND_PAIN, + TASK_SOUND_DIE, + TASK_FIND_COVER_FROM_BEST_SOUND,// tries lateral cover first, then node cover + TASK_FIND_COVER_FROM_ENEMY,// tries lateral cover first, then node cover + TASK_FIND_LATERAL_COVER_FROM_ENEMY, + TASK_FIND_NODE_COVER_FROM_ENEMY, + TASK_FIND_NEAR_NODE_COVER_FROM_ENEMY,// data for this one is the MAXIMUM acceptable distance to the cover. + TASK_FIND_FAR_NODE_COVER_FROM_ENEMY,// data for this one is there MINIMUM aceptable distance to the cover. + TASK_FIND_COVER_FROM_ORIGIN, + TASK_EAT, + TASK_DIE, + TASK_WAIT_FOR_SCRIPT, + TASK_PLAY_SCRIPT, + TASK_ENABLE_SCRIPT, + TASK_PLANT_ON_SCRIPT, + TASK_FACE_SCRIPT, + TASK_WAIT_RANDOM, + TASK_WAIT_INDEFINITE, + TASK_STOP_MOVING, + TASK_TURN_LEFT, + TASK_TURN_RIGHT, + TASK_REMEMBER, + TASK_FORGET, + TASK_WAIT_FOR_MOVEMENT, // wait until MovementIsComplete() + LAST_COMMON_TASK, // LEAVE THIS AT THE BOTTOM!! (sjb) +} SHARED_TASKS; + + +// These go in the flData member of the TASK_WALK_TO_TARGET, TASK_RUN_TO_TARGET +enum +{ + TARGET_MOVE_NORMAL = 0, + TARGET_MOVE_SCRIPTED = 1, +}; + + +// A goal should be used for a task that requires several schedules to complete. +// The goal index should indicate which schedule (ordinally) the monster is running. +// That way, when tasks fail, the AI can make decisions based on the context of the +// current goal and sequence rather than just the current schedule. +enum +{ + GOAL_ATTACK_ENEMY, + GOAL_MOVE, + GOAL_TAKE_COVER, + GOAL_MOVE_TARGET, + GOAL_EAT, +}; + +// an array of tasks is a task list +// an array of schedules is a schedule list +struct Task_t +{ + + int iTask; + float flData; +}; + +struct Schedule_t +{ + + Task_t *pTasklist; + int cTasks; + int iInterruptMask;// a bit mask of conditions that can interrupt this schedule + + // a more specific mask that indicates which TYPES of sounds will interrupt the schedule in the + // event that the schedule is broken by COND_HEAR_SOUND + int iSoundMask; + const char *pName; +}; + +// an array of waypoints makes up the monster's route. +// !!!LATER- this declaration doesn't belong in this file. +struct WayPoint_t +{ + Vector vecLocation; + int iType; +}; + +// these MoveFlag values are assigned to a WayPoint's TYPE in order to demonstrate the +// type of movement the monster should use to get there. +#define bits_MF_TO_TARGETENT ( 1 << 0 ) // local move to targetent. +#define bits_MF_TO_ENEMY ( 1 << 1 ) // local move to enemy +#define bits_MF_TO_COVER ( 1 << 2 ) // local move to a hiding place +#define bits_MF_TO_DETOUR ( 1 << 3 ) // local move to detour point. +#define bits_MF_TO_PATHCORNER ( 1 << 4 ) // local move to a path corner +#define bits_MF_TO_NODE ( 1 << 5 ) // local move to a node +#define bits_MF_TO_LOCATION ( 1 << 6 ) // local move to an arbitrary point +#define bits_MF_IS_GOAL ( 1 << 7 ) // this waypoint is the goal of the whole move. +#define bits_MF_DONT_SIMPLIFY ( 1 << 8 ) // Don't let the route code simplify this waypoint + +// If you define any flags that aren't _TO_ flags, add them here so we can mask +// them off when doing compares. +#define bits_MF_NOT_TO_MASK (bits_MF_IS_GOAL | bits_MF_DONT_SIMPLIFY) + +#define MOVEGOAL_NONE (0) +#define MOVEGOAL_TARGETENT (bits_MF_TO_TARGETENT) +#define MOVEGOAL_ENEMY (bits_MF_TO_ENEMY) +#define MOVEGOAL_PATHCORNER (bits_MF_TO_PATHCORNER) +#define MOVEGOAL_LOCATION (bits_MF_TO_LOCATION) +#define MOVEGOAL_NODE (bits_MF_TO_NODE) + +// these bits represent conditions that may befall the monster, of which some are allowed +// to interrupt certain schedules. +#define bits_COND_NO_AMMO_LOADED ( 1 << 0 ) // weapon needs to be reloaded! +#define bits_COND_SEE_HATE ( 1 << 1 ) // see something that you hate +#define bits_COND_SEE_FEAR ( 1 << 2 ) // see something that you are afraid of +#define bits_COND_SEE_DISLIKE ( 1 << 3 ) // see something that you dislike +#define bits_COND_SEE_ENEMY ( 1 << 4 ) // target entity is in full view. +#define bits_COND_ENEMY_OCCLUDED ( 1 << 5 ) // target entity occluded by the world +#define bits_COND_SMELL_FOOD ( 1 << 6 ) +#define bits_COND_ENEMY_TOOFAR ( 1 << 7 ) +#define bits_COND_LIGHT_DAMAGE ( 1 << 8 ) // hurt a little +#define bits_COND_HEAVY_DAMAGE ( 1 << 9 ) // hurt a lot +#define bits_COND_CAN_RANGE_ATTACK1 ( 1 << 10) +#define bits_COND_CAN_MELEE_ATTACK1 ( 1 << 11) +#define bits_COND_CAN_RANGE_ATTACK2 ( 1 << 12) +#define bits_COND_CAN_MELEE_ATTACK2 ( 1 << 13) +// #define bits_COND_CAN_RANGE_ATTACK3 ( 1 << 14) +#define bits_COND_PROVOKED ( 1 << 15) +#define bits_COND_NEW_ENEMY ( 1 << 16) +#define bits_COND_HEAR_SOUND ( 1 << 17) // there is an interesting sound +#define bits_COND_SMELL ( 1 << 18) // there is an interesting scent +#define bits_COND_ENEMY_FACING_ME ( 1 << 19) // enemy is facing me +#define bits_COND_ENEMY_DEAD ( 1 << 20) // enemy was killed. If you get this in combat, try to find another enemy. If you get it in alert, victory dance. +#define bits_COND_SEE_CLIENT ( 1 << 21) // see a client +#define bits_COND_SEE_NEMESIS ( 1 << 22) // see my nemesis + +#define bits_COND_SPECIAL1 ( 1 << 28) // Defined by individual monster +#define bits_COND_SPECIAL2 ( 1 << 29) // Defined by individual monster + +#define bits_COND_TASK_FAILED ( 1 << 30) +#define bits_COND_SCHEDULE_DONE ( 1 << 31) + + +#define bits_COND_ALL_SPECIAL (bits_COND_SPECIAL1 | bits_COND_SPECIAL2) + +#define bits_COND_CAN_ATTACK (bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_MELEE_ATTACK1 | bits_COND_CAN_RANGE_ATTACK2 | bits_COND_CAN_MELEE_ATTACK2) + +#endif // SCHEDULE_H diff --git a/dlls/scientist.cpp b/dlls/scientist.cpp new file mode 100644 index 0000000..a27e52f --- /dev/null +++ b/dlls/scientist.cpp @@ -0,0 +1,1428 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// human scientist (passive lab worker) +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "talkmonster.h" +#include "schedule.h" +#include "defaultai.h" +#include "scripted.h" +#include "animation.h" +#include "soundent.h" + + +#define NUM_SCIENTIST_HEADS 4 // four heads available for scientist model +enum { HEAD_GLASSES = 0, HEAD_EINSTEIN = 1, HEAD_LUTHER = 2, HEAD_SLICK = 3 }; + +enum +{ + SCHED_HIDE = LAST_TALKMONSTER_SCHEDULE + 1, + SCHED_FEAR, + SCHED_PANIC, + SCHED_STARTLE, + SCHED_TARGET_CHASE_SCARED, + SCHED_TARGET_FACE_SCARED, +}; + +enum +{ + TASK_SAY_HEAL = LAST_TALKMONSTER_TASK + 1, + TASK_HEAL, + TASK_SAY_FEAR, + TASK_RUN_PATH_SCARED, + TASK_SCREAM, + TASK_RANDOM_SCREAM, + TASK_MOVE_TO_TARGET_RANGE_SCARED, +}; + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define SCIENTIST_AE_HEAL ( 1 ) +#define SCIENTIST_AE_NEEDLEON ( 2 ) +#define SCIENTIST_AE_NEEDLEOFF ( 3 ) + +//======================================================= +// Scientist +//======================================================= + +class CScientist : public CTalkMonster +{ +public: + void Spawn( void ); + void Precache( void ); + + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void RunTask( Task_t *pTask ); + void StartTask( Task_t *pTask ); + int ObjectCaps( void ) { return CTalkMonster :: ObjectCaps() | FCAP_IMPULSE_USE; } + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + virtual int FriendNumber( int arrayNumber ); + void SetActivity ( Activity newActivity ); + Activity GetStoppedActivity( void ); + int ISoundMask( void ); + void DeclineFollowing( void ); + + float CoverRadius( void ) { return 1200; } // Need more room for cover because scientists want to get far away! + BOOL DisregardEnemy( CBaseEntity *pEnemy ) { return !pEnemy->IsAlive() || (gpGlobals->time - m_fearTime) > 15; } + + BOOL CanHeal( void ); + void Heal( void ); + void Scream( void ); + + // Override these to set behavior + Schedule_t *GetScheduleOfType ( int Type ); + Schedule_t *GetSchedule ( void ); + MONSTERSTATE GetIdealState ( void ); + + void DeathSound( void ); + void PainSound( void ); + + void TalkInit( void ); + + void Killed( entvars_t *pevAttacker, int iGib ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + CUSTOM_SCHEDULES; + +private: + float m_painTime; + float m_healTime; + float m_fearTime; +}; + +LINK_ENTITY_TO_CLASS( monster_scientist, CScientist ); + +TYPEDESCRIPTION CScientist::m_SaveData[] = +{ + DEFINE_FIELD( CScientist, m_painTime, FIELD_TIME ), + DEFINE_FIELD( CScientist, m_healTime, FIELD_TIME ), + DEFINE_FIELD( CScientist, m_fearTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CScientist, CTalkMonster ); + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +Task_t tlFollow[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_CANT_FOLLOW }, // If you fail, bail out of follow + { TASK_MOVE_TO_TARGET_RANGE,(float)128 }, // Move within 128 of target ent (client) +// { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE }, +}; + +Schedule_t slFollow[] = +{ + { + tlFollow, + ARRAYSIZE ( tlFollow ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + bits_SOUND_COMBAT | + bits_SOUND_DANGER, + "Follow" + }, +}; + +Task_t tlFollowScared[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_TARGET_CHASE },// If you fail, follow normally + { TASK_MOVE_TO_TARGET_RANGE_SCARED,(float)128 }, // Move within 128 of target ent (client) +// { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE_SCARED }, +}; + +Schedule_t slFollowScared[] = +{ + { + tlFollowScared, + ARRAYSIZE ( tlFollowScared ), + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + bits_SOUND_DANGER, + "FollowScared" + }, +}; + +Task_t tlFaceTargetScared[] = +{ + { TASK_FACE_TARGET, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE }, + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE_SCARED }, +}; + +Schedule_t slFaceTargetScared[] = +{ + { + tlFaceTargetScared, + ARRAYSIZE ( tlFaceTargetScared ), + bits_COND_HEAR_SOUND | + bits_COND_NEW_ENEMY, + bits_SOUND_DANGER, + "FaceTargetScared" + }, +}; + +Task_t tlStopFollowing[] = +{ + { TASK_CANT_FOLLOW, (float)0 }, +}; + +Schedule_t slStopFollowing[] = +{ + { + tlStopFollowing, + ARRAYSIZE ( tlStopFollowing ), + 0, + 0, + "StopFollowing" + }, +}; + + +Task_t tlHeal[] = +{ + { TASK_MOVE_TO_TARGET_RANGE,(float)50 }, // Move within 60 of target ent (client) + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_TARGET_CHASE }, // If you fail, catch up with that guy! (change this to put syringe away and then chase) + { TASK_FACE_IDEAL, (float)0 }, + { TASK_SAY_HEAL, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_TARGET, (float)ACT_ARM }, // Whip out the needle + { TASK_HEAL, (float)0 }, // Put it in the player + { TASK_PLAY_SEQUENCE_FACE_TARGET, (float)ACT_DISARM }, // Put away the needle +}; + +Schedule_t slHeal[] = +{ + { + tlHeal, + ARRAYSIZE ( tlHeal ), + 0, // Don't interrupt or he'll end up running around with a needle all the time + 0, + "Heal" + }, +}; + + +Task_t tlFaceTarget[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_TARGET, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE }, +}; + +Schedule_t slFaceTarget[] = +{ + { + tlFaceTarget, + ARRAYSIZE ( tlFaceTarget ), + bits_COND_CLIENT_PUSH | + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND, + bits_SOUND_COMBAT | + bits_SOUND_DANGER, + "FaceTarget" + }, +}; + + +Task_t tlSciPanic[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SCREAM, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_EXCITED }, // This is really fear-stricken excitement + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slSciPanic[] = +{ + { + tlSciPanic, + ARRAYSIZE ( tlSciPanic ), + 0, + 0, + "SciPanic" + }, +}; + + +Task_t tlIdleSciStand[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, // repick IDLESTAND every two seconds. + { TASK_TLK_HEADRESET, (float)0 }, // reset head position +}; + +Schedule_t slIdleSciStand[] = +{ + { + tlIdleSciStand, + ARRAYSIZE ( tlIdleSciStand ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL | + bits_COND_CLIENT_PUSH | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags + //bits_SOUND_PLAYER | + //bits_SOUND_WORLD | + bits_SOUND_DANGER | + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "IdleSciStand" + + }, +}; + + +Task_t tlScientistCover[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_PANIC }, // If you fail, just panic! + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH_SCARED, (float)0 }, + { TASK_TURN_LEFT, (float)179 }, + { TASK_SET_SCHEDULE, (float)SCHED_HIDE }, +}; + +Schedule_t slScientistCover[] = +{ + { + tlScientistCover, + ARRAYSIZE ( tlScientistCover ), + bits_COND_NEW_ENEMY, + 0, + "ScientistCover" + }, +}; + + + +Task_t tlScientistHide[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_PANIC }, // If you fail, just panic! + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_CROUCH }, + { TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE }, // FIXME: This looks lame + { TASK_WAIT_RANDOM, (float)10.0 }, +}; + +Schedule_t slScientistHide[] = +{ + { + tlScientistHide, + ARRAYSIZE ( tlScientistHide ), + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND | + bits_COND_SEE_ENEMY | + bits_COND_SEE_HATE | + bits_COND_SEE_FEAR | + bits_COND_SEE_DISLIKE, + bits_SOUND_DANGER, + "ScientistHide" + }, +}; + + +Task_t tlScientistStartle[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_PANIC }, // If you fail, just panic! + { TASK_RANDOM_SCREAM, (float)0.3 }, // Scream 30% of the time + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCH }, + { TASK_RANDOM_SCREAM, (float)0.1 }, // Scream again 10% of the time + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCHIDLE }, + { TASK_WAIT_RANDOM, (float)1.0 }, +}; + +Schedule_t slScientistStartle[] = +{ + { + tlScientistStartle, + ARRAYSIZE ( tlScientistStartle ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_ENEMY | + bits_COND_SEE_HATE | + bits_COND_SEE_FEAR | + bits_COND_SEE_DISLIKE, + 0, + "ScientistStartle" + }, +}; + + + +Task_t tlFear[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SAY_FEAR, (float)0 }, +// { TASK_PLAY_SEQUENCE, (float)ACT_FEAR_DISPLAY }, +}; + +Schedule_t slFear[] = +{ + { + tlFear, + ARRAYSIZE ( tlFear ), + bits_COND_NEW_ENEMY, + 0, + "Fear" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CScientist ) +{ + slFollow, + slFaceTarget, + slIdleSciStand, + slFear, + slScientistCover, + slScientistHide, + slScientistStartle, + slHeal, + slStopFollowing, + slSciPanic, + slFollowScared, + slFaceTargetScared, +}; + + +IMPLEMENT_CUSTOM_SCHEDULES( CScientist, CTalkMonster ); + + +void CScientist::DeclineFollowing( void ) +{ + Talk( 10 ); + m_hTalkTarget = m_hEnemy; + PlaySentence( "SC_POK", 2, VOL_NORM, ATTN_NORM ); +} + + +void CScientist :: Scream( void ) +{ + if ( FOkToSpeak() ) + { + Talk( 10 ); + m_hTalkTarget = m_hEnemy; + PlaySentence( "SC_SCREAM", RANDOM_FLOAT(3, 6), VOL_NORM, ATTN_NORM ); + } +} + + +Activity CScientist::GetStoppedActivity( void ) +{ + if ( m_hEnemy != NULL ) + return ACT_EXCITED; + return CTalkMonster::GetStoppedActivity(); +} + + +void CScientist :: StartTask( Task_t *pTask ) +{ + switch( pTask->iTask ) + { + case TASK_SAY_HEAL: +// if ( FOkToSpeak() ) + Talk( 2 ); + m_hTalkTarget = m_hTargetEnt; + PlaySentence( "SC_HEAL", 2, VOL_NORM, ATTN_IDLE ); + + TaskComplete(); + break; + + case TASK_SCREAM: + Scream(); + TaskComplete(); + break; + + case TASK_RANDOM_SCREAM: + if ( RANDOM_FLOAT( 0, 1 ) < pTask->flData ) + Scream(); + TaskComplete(); + break; + + case TASK_SAY_FEAR: + if ( FOkToSpeak() ) + { + Talk( 2 ); + m_hTalkTarget = m_hEnemy; + if ( m_hEnemy->IsPlayer() ) + PlaySentence( "SC_PLFEAR", 5, VOL_NORM, ATTN_NORM ); + else + PlaySentence( "SC_FEAR", 5, VOL_NORM, ATTN_NORM ); + } + TaskComplete(); + break; + + case TASK_HEAL: + m_IdealActivity = ACT_MELEE_ATTACK1; + break; + + case TASK_RUN_PATH_SCARED: + m_movementActivity = ACT_RUN_SCARED; + break; + + case TASK_MOVE_TO_TARGET_RANGE_SCARED: + { + if ( (m_hTargetEnt->pev->origin - pev->origin).Length() < 1 ) + TaskComplete(); + else + { + m_vecMoveGoal = m_hTargetEnt->pev->origin; + if ( !MoveToTarget( ACT_WALK_SCARED, 0.5 ) ) + TaskFail(); + } + } + break; + + default: + CTalkMonster::StartTask( pTask ); + break; + } +} + +void CScientist :: RunTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RUN_PATH_SCARED: + if ( MovementIsComplete() ) + TaskComplete(); + if ( RANDOM_LONG(0,31) < 8 ) + Scream(); + break; + + case TASK_MOVE_TO_TARGET_RANGE_SCARED: + { + if ( RANDOM_LONG(0,63)< 8 ) + Scream(); + + if ( m_hEnemy == NULL ) + { + TaskFail(); + } + else + { + float distance; + + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + // Re-evaluate when you think your finished, or the target has moved too far + if ( (distance < pTask->flData) || (m_vecMoveGoal - m_hTargetEnt->pev->origin).Length() > pTask->flData * 0.5 ) + { + m_vecMoveGoal = m_hTargetEnt->pev->origin; + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + FRefreshRoute(); + } + + // Set the appropriate activity based on an overlapping range + // overlap the range to prevent oscillation + if ( distance < pTask->flData ) + { + TaskComplete(); + RouteClear(); // Stop moving + } + else if ( distance < 190 && m_movementActivity != ACT_WALK_SCARED ) + m_movementActivity = ACT_WALK_SCARED; + else if ( distance >= 270 && m_movementActivity != ACT_RUN_SCARED ) + m_movementActivity = ACT_RUN_SCARED; + } + } + break; + + case TASK_HEAL: + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + else + { + if ( TargetDistance() > 90 ) + TaskComplete(); + pev->ideal_yaw = UTIL_VecToYaw( m_hTargetEnt->pev->origin - pev->origin ); + ChangeYaw( pev->yaw_speed ); + } + break; + default: + CTalkMonster::RunTask( pTask ); + break; + } +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CScientist :: Classify ( void ) +{ + return CLASS_HUMAN_PASSIVE; +} + + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CScientist :: SetYawSpeed ( void ) +{ + int ys; + + ys = 90; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 120; + break; + case ACT_WALK: + ys = 180; + break; + case ACT_RUN: + ys = 150; + break; + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 120; + break; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CScientist :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case SCIENTIST_AE_HEAL: // Heal my target (if within range) + Heal(); + break; + case SCIENTIST_AE_NEEDLEON: + { + int oldBody = pev->body; + pev->body = (oldBody % NUM_SCIENTIST_HEADS) + NUM_SCIENTIST_HEADS * 1; + } + break; + case SCIENTIST_AE_NEEDLEOFF: + { + int oldBody = pev->body; + pev->body = (oldBody % NUM_SCIENTIST_HEADS) + NUM_SCIENTIST_HEADS * 0; + } + break; + + default: + CTalkMonster::HandleAnimEvent( pEvent ); + } +} + +//========================================================= +// Spawn +//========================================================= +void CScientist :: Spawn( void ) +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/scientist.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->health = gSkillData.scientistHealth; + pev->view_ofs = Vector ( 0, 0, 50 );// position of the eyes relative to monster's origin. + m_flFieldOfView = VIEW_FIELD_WIDE; // NOTE: we need a wide field of view so scientists will notice player and say hello + m_MonsterState = MONSTERSTATE_NONE; + +// m_flDistTooFar = 256.0; + + m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD | bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE; + + // White hands + pev->skin = 0; + + if ( pev->body == -1 ) + {// -1 chooses a random head + pev->body = RANDOM_LONG(0, NUM_SCIENTIST_HEADS-1);// pick a head, any head + } + + // Luther is black, make his hands black + if ( pev->body == HEAD_LUTHER ) + pev->skin = 1; + + MonsterInit(); + SetUse( &CScientist::FollowerUse ); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CScientist :: Precache( void ) +{ + PRECACHE_MODEL("models/scientist.mdl"); + PRECACHE_SOUND("scientist/sci_pain1.wav"); + PRECACHE_SOUND("scientist/sci_pain2.wav"); + PRECACHE_SOUND("scientist/sci_pain3.wav"); + PRECACHE_SOUND("scientist/sci_pain4.wav"); + PRECACHE_SOUND("scientist/sci_pain5.wav"); + + // every new scientist must call this, otherwise + // when a level is loaded, nobody will talk (time is reset to 0) + TalkInit(); + + CTalkMonster::Precache(); +} + +// Init talk data +void CScientist :: TalkInit() +{ + + CTalkMonster::TalkInit(); + + // scientist will try to talk to friends in this order: + + m_szFriends[0] = "monster_scientist"; + m_szFriends[1] = "monster_sitting_scientist"; + m_szFriends[2] = "monster_barney"; + + // scientists speach group names (group names are in sentences.txt) + + m_szGrp[TLK_ANSWER] = "SC_ANSWER"; + m_szGrp[TLK_QUESTION] = "SC_QUESTION"; + m_szGrp[TLK_IDLE] = "SC_IDLE"; + m_szGrp[TLK_STARE] = "SC_STARE"; + m_szGrp[TLK_USE] = "SC_OK"; + m_szGrp[TLK_UNUSE] = "SC_WAIT"; + m_szGrp[TLK_STOP] = "SC_STOP"; + m_szGrp[TLK_NOSHOOT] = "SC_SCARED"; + m_szGrp[TLK_HELLO] = "SC_HELLO"; + + m_szGrp[TLK_PLHURT1] = "!SC_CUREA"; + m_szGrp[TLK_PLHURT2] = "!SC_CUREB"; + m_szGrp[TLK_PLHURT3] = "!SC_CUREC"; + + m_szGrp[TLK_PHELLO] = "SC_PHELLO"; + m_szGrp[TLK_PIDLE] = "SC_PIDLE"; + m_szGrp[TLK_PQUESTION] = "SC_PQUEST"; + m_szGrp[TLK_SMELL] = "SC_SMELL"; + + m_szGrp[TLK_WOUND] = "SC_WOUND"; + m_szGrp[TLK_MORTAL] = "SC_MORTAL"; + + // get voice for head + switch (pev->body % 3) + { + default: + case HEAD_GLASSES: m_voicePitch = 105; break; //glasses + case HEAD_EINSTEIN: m_voicePitch = 100; break; //einstein + case HEAD_LUTHER: m_voicePitch = 95; break; //luther + case HEAD_SLICK: m_voicePitch = 100; break;//slick + } +} + +int CScientist :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) +{ + + if ( pevInflictor && pevInflictor->flags & FL_CLIENT ) + { + Remember( bits_MEMORY_PROVOKED ); + StopFollowing( TRUE ); + } + + // make sure friends talk about it if player hurts scientist... + return CTalkMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); +} + + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. In the base class implementation, +// monsters care about all sounds, but no scents. +//========================================================= +int CScientist :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_DANGER | + bits_SOUND_PLAYER; +} + +//========================================================= +// PainSound +//========================================================= +void CScientist :: PainSound ( void ) +{ + if (gpGlobals->time < m_painTime ) + return; + + m_painTime = gpGlobals->time + RANDOM_FLOAT(0.5, 0.75); + + switch (RANDOM_LONG(0,4)) + { + case 0: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain1.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 1: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain2.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 2: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain3.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 3: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain4.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 4: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain5.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CScientist :: DeathSound ( void ) +{ + PainSound(); +} + + +void CScientist::Killed( entvars_t *pevAttacker, int iGib ) +{ + SetUse( NULL ); + CTalkMonster::Killed( pevAttacker, iGib ); +} + + +void CScientist :: SetActivity ( Activity newActivity ) +{ + int iSequence; + + iSequence = LookupActivity ( newActivity ); + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence == ACTIVITY_NOT_AVAILABLE ) + newActivity = ACT_IDLE; + CTalkMonster::SetActivity( newActivity ); +} + + +Schedule_t* CScientist :: GetScheduleOfType ( int Type ) +{ + Schedule_t *psched; + + switch( Type ) + { + // Hook these to make a looping schedule + case SCHED_TARGET_FACE: + // call base class default so that scientist will talk + // when 'used' + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + return slFaceTarget; // override this for different target face behavior + else + return psched; + + case SCHED_TARGET_CHASE: + return slFollow; + + case SCHED_CANT_FOLLOW: + return slStopFollowing; + + case SCHED_PANIC: + return slSciPanic; + + case SCHED_TARGET_CHASE_SCARED: + return slFollowScared; + + case SCHED_TARGET_FACE_SCARED: + return slFaceTargetScared; + + case SCHED_IDLE_STAND: + // call base class default so that scientist will talk + // when standing during idle + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + return slIdleSciStand; + else + return psched; + + case SCHED_HIDE: + return slScientistHide; + + case SCHED_STARTLE: + return slScientistStartle; + + case SCHED_FEAR: + return slFear; + } + + return CTalkMonster::GetScheduleOfType( Type ); +} + +Schedule_t *CScientist :: GetSchedule ( void ) +{ + // so we don't keep calling through the EHANDLE stuff + CBaseEntity *pEnemy = m_hEnemy; + + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + + switch( m_MonsterState ) + { + case MONSTERSTATE_ALERT: + case MONSTERSTATE_IDLE: + if ( pEnemy ) + { + if ( HasConditions( bits_COND_SEE_ENEMY ) ) + m_fearTime = gpGlobals->time; + else if ( DisregardEnemy( pEnemy ) ) // After 15 seconds of being hidden, return to alert + { + m_hEnemy = NULL; + pEnemy = NULL; + } + } + + if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE)) + { + // flinch if hurt + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + + // Cower when you hear something scary + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound ) + { + if ( pSound->m_iType & (bits_SOUND_DANGER | bits_SOUND_COMBAT) ) + { + if ( gpGlobals->time - m_fearTime > 3 ) // Only cower every 3 seconds or so + { + m_fearTime = gpGlobals->time; // Update last fear + return GetScheduleOfType( SCHED_STARTLE ); // This will just duck for a second + } + } + } + } + + // Behavior for following the player + if ( IsFollowing() ) + { + if ( !m_hTargetEnt->IsAlive() ) + { + // UNDONE: Comment about the recently dead player here? + StopFollowing( FALSE ); + break; + } + + int relationship = R_NO; + + // Nothing scary, just me and the player + if ( pEnemy != NULL ) + relationship = IRelationship( pEnemy ); + + // UNDONE: Model fear properly, fix R_FR and add multiple levels of fear + if ( relationship != R_DL && relationship != R_HT ) + { + // If I'm already close enough to my target + if ( TargetDistance() <= 128 ) + { + if ( CanHeal() ) // Heal opportunistically + return slHeal; + if ( HasConditions( bits_COND_CLIENT_PUSH ) ) // Player wants me to move + return GetScheduleOfType( SCHED_MOVE_AWAY_FOLLOW ); + } + return GetScheduleOfType( SCHED_TARGET_FACE ); // Just face and follow. + } + else // UNDONE: When afraid, scientist won't move out of your way. Keep This? If not, write move away scared + { + if ( HasConditions( bits_COND_NEW_ENEMY ) ) // I just saw something new and scary, react + return GetScheduleOfType( SCHED_FEAR ); // React to something scary + return GetScheduleOfType( SCHED_TARGET_FACE_SCARED ); // face and follow, but I'm scared! + } + } + + if ( HasConditions( bits_COND_CLIENT_PUSH ) ) // Player wants me to move + return GetScheduleOfType( SCHED_MOVE_AWAY ); + + // try to say something about smells + TrySmellTalk(); + break; + case MONSTERSTATE_COMBAT: + if ( HasConditions( bits_COND_NEW_ENEMY ) ) + return slFear; // Point and scream! + if ( HasConditions( bits_COND_SEE_ENEMY ) ) + return slScientistCover; // Take Cover + + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + return slTakeCoverFromBestSound; // Cower and panic from the scary sound! + + return slScientistCover; // Run & Cower + break; + } + + return CTalkMonster::GetSchedule(); +} + +MONSTERSTATE CScientist :: GetIdealState ( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_ALERT: + case MONSTERSTATE_IDLE: + if ( HasConditions( bits_COND_NEW_ENEMY ) ) + { + if ( IsFollowing() ) + { + int relationship = IRelationship( m_hEnemy ); + if ( relationship != R_FR || relationship != R_HT && !HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) + { + // Don't go to combat if you're following the player + m_IdealMonsterState = MONSTERSTATE_ALERT; + return m_IdealMonsterState; + } + StopFollowing( TRUE ); + } + } + else if ( HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) + { + // Stop following if you take damage + if ( IsFollowing() ) + StopFollowing( TRUE ); + } + break; + + case MONSTERSTATE_COMBAT: + { + CBaseEntity *pEnemy = m_hEnemy; + if ( pEnemy != NULL ) + { + if ( DisregardEnemy( pEnemy ) ) // After 15 seconds of being hidden, return to alert + { + // Strip enemy when going to alert + m_IdealMonsterState = MONSTERSTATE_ALERT; + m_hEnemy = NULL; + return m_IdealMonsterState; + } + // Follow if only scared a little + if ( m_hTargetEnt != NULL ) + { + m_IdealMonsterState = MONSTERSTATE_ALERT; + return m_IdealMonsterState; + } + + if ( HasConditions ( bits_COND_SEE_ENEMY ) ) + { + m_fearTime = gpGlobals->time; + m_IdealMonsterState = MONSTERSTATE_COMBAT; + return m_IdealMonsterState; + } + + } + } + break; + } + + return CTalkMonster::GetIdealState(); +} + + +BOOL CScientist::CanHeal( void ) +{ + if ( (m_healTime > gpGlobals->time) || (m_hTargetEnt == NULL) || (m_hTargetEnt->pev->health > (m_hTargetEnt->pev->max_health * 0.5)) ) + return FALSE; + + return TRUE; +} + +void CScientist::Heal( void ) +{ + if ( !CanHeal() ) + return; + + Vector target = m_hTargetEnt->pev->origin - pev->origin; + if ( target.Length() > 100 ) + return; + + m_hTargetEnt->TakeHealth( gSkillData.scientistHeal, DMG_GENERIC ); + // Don't heal again for 1 minute + m_healTime = gpGlobals->time + 60; +} + +int CScientist::FriendNumber( int arrayNumber ) +{ + static int array[3] = { 1, 2, 0 }; + if ( arrayNumber < 3 ) + return array[ arrayNumber ]; + return arrayNumber; +} + + +//========================================================= +// Dead Scientist PROP +//========================================================= +class CDeadScientist : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_HUMAN_PASSIVE; } + + void KeyValue( KeyValueData *pkvd ); + int m_iPose;// which sequence to display + static char *m_szPoses[7]; +}; +char *CDeadScientist::m_szPoses[] = { "lying_on_back", "lying_on_stomach", "dead_sitting", "dead_hang", "dead_table1", "dead_table2", "dead_table3" }; + +void CDeadScientist::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "pose")) + { + m_iPose = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} +LINK_ENTITY_TO_CLASS( monster_scientist_dead, CDeadScientist ); + +// +// ********** DeadScientist SPAWN ********** +// +void CDeadScientist :: Spawn( ) +{ + PRECACHE_MODEL("models/scientist.mdl"); + SET_MODEL(ENT(pev), "models/scientist.mdl"); + + pev->effects = 0; + pev->sequence = 0; + // Corpses have less health + pev->health = 8;//gSkillData.scientistHealth; + + m_bloodColor = BLOOD_COLOR_RED; + + if ( pev->body == -1 ) + {// -1 chooses a random head + pev->body = RANDOM_LONG(0, NUM_SCIENTIST_HEADS-1);// pick a head, any head + } + // Luther is black, make his hands black + if ( pev->body == HEAD_LUTHER ) + pev->skin = 1; + else + pev->skin = 0; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + if (pev->sequence == -1) + { + ALERT ( at_console, "Dead scientist with bad pose\n" ); + } + + // pev->skin += 2; // use bloody skin -- UNDONE: Turn this back on when we have a bloody skin again! + MonsterInitDead(); +} + + +//========================================================= +// Sitting Scientist PROP +//========================================================= + +class CSittingScientist : public CScientist // kdb: changed from public CBaseMonster so he can speak +{ +public: + void Spawn( void ); + void Precache( void ); + + void EXPORT SittingThink( void ); + int Classify ( void ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual void SetAnswerQuestion( CTalkMonster *pSpeaker ); + int FriendNumber( int arrayNumber ); + + int FIdleSpeak ( void ); + int m_baseSequence; + int m_headTurn; + float m_flResponseDelay; +}; + +LINK_ENTITY_TO_CLASS( monster_sitting_scientist, CSittingScientist ); +TYPEDESCRIPTION CSittingScientist::m_SaveData[] = +{ + // Don't need to save/restore m_baseSequence (recalced) + DEFINE_FIELD( CSittingScientist, m_headTurn, FIELD_INTEGER ), + DEFINE_FIELD( CSittingScientist, m_flResponseDelay, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CSittingScientist, CScientist ); + +// animation sequence aliases +typedef enum +{ +SITTING_ANIM_sitlookleft, +SITTING_ANIM_sitlookright, +SITTING_ANIM_sitscared, +SITTING_ANIM_sitting2, +SITTING_ANIM_sitting3 +} SITTING_ANIM; + + +// +// ********** Scientist SPAWN ********** +// +void CSittingScientist :: Spawn( ) +{ + PRECACHE_MODEL("models/scientist.mdl"); + SET_MODEL(ENT(pev), "models/scientist.mdl"); + Precache(); + InitBoneControllers(); + + UTIL_SetSize(pev, Vector(-14, -14, 0), Vector(14, 14, 36)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + pev->effects = 0; + pev->health = 50; + + m_bloodColor = BLOOD_COLOR_RED; + m_flFieldOfView = VIEW_FIELD_WIDE; // indicates the width of this monster's forward view cone ( as a dotproduct result ) + + m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD; + + SetBits(pev->spawnflags, SF_MONSTER_PREDISASTER); // predisaster only! + + if ( pev->body == -1 ) + {// -1 chooses a random head + pev->body = RANDOM_LONG(0, NUM_SCIENTIST_HEADS-1);// pick a head, any head + } + // Luther is black, make his hands black + if ( pev->body == HEAD_LUTHER ) + pev->skin = 1; + + m_baseSequence = LookupSequence( "sitlookleft" ); + pev->sequence = m_baseSequence + RANDOM_LONG(0,4); + ResetSequenceInfo( ); + + SetThink (&CSittingScientist::SittingThink); + pev->nextthink = gpGlobals->time + 0.1; + + DROP_TO_FLOOR ( ENT(pev) ); +} + +void CSittingScientist :: Precache( void ) +{ + m_baseSequence = LookupSequence( "sitlookleft" ); + TalkInit(); +} + +//========================================================= +// ID as a passive human +//========================================================= +int CSittingScientist :: Classify ( void ) +{ + return CLASS_HUMAN_PASSIVE; +} + + +int CSittingScientist::FriendNumber( int arrayNumber ) +{ + static int array[3] = { 2, 1, 0 }; + if ( arrayNumber < 3 ) + return array[ arrayNumber ]; + return arrayNumber; +} + + + +//========================================================= +// sit, do stuff +//========================================================= +void CSittingScientist :: SittingThink( void ) +{ + CBaseEntity *pent; + + StudioFrameAdvance( ); + + // try to greet player + if (FIdleHello()) + { + pent = FindNearestFriend(TRUE); + if (pent) + { + float yaw = VecToYaw(pent->pev->origin - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + if (yaw > 0) + pev->sequence = m_baseSequence + SITTING_ANIM_sitlookleft; + else + pev->sequence = m_baseSequence + SITTING_ANIM_sitlookright; + + ResetSequenceInfo( ); + pev->frame = 0; + SetBoneController( 0, 0 ); + } + } + else if (m_fSequenceFinished) + { + int i = RANDOM_LONG(0,99); + m_headTurn = 0; + + if (m_flResponseDelay && gpGlobals->time > m_flResponseDelay) + { + // respond to question + IdleRespond(); + pev->sequence = m_baseSequence + SITTING_ANIM_sitscared; + m_flResponseDelay = 0; + } + else if (i < 30) + { + pev->sequence = m_baseSequence + SITTING_ANIM_sitting3; + + // turn towards player or nearest friend and speak + + if (!FBitSet(m_bitsSaid, bit_saidHelloPlayer)) + pent = FindNearestFriend(TRUE); + else + pent = FindNearestFriend(FALSE); + + if (!FIdleSpeak() || !pent) + { + m_headTurn = RANDOM_LONG(0,8) * 10 - 40; + pev->sequence = m_baseSequence + SITTING_ANIM_sitting3; + } + else + { + // only turn head if we spoke + float yaw = VecToYaw(pent->pev->origin - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + if (yaw > 0) + pev->sequence = m_baseSequence + SITTING_ANIM_sitlookleft; + else + pev->sequence = m_baseSequence + SITTING_ANIM_sitlookright; + + //ALERT(at_console, "sitting speak\n"); + } + } + else if (i < 60) + { + pev->sequence = m_baseSequence + SITTING_ANIM_sitting3; + m_headTurn = RANDOM_LONG(0,8) * 10 - 40; + if (RANDOM_LONG(0,99) < 5) + { + //ALERT(at_console, "sitting speak2\n"); + FIdleSpeak(); + } + } + else if (i < 80) + { + pev->sequence = m_baseSequence + SITTING_ANIM_sitting2; + } + else if (i < 100) + { + pev->sequence = m_baseSequence + SITTING_ANIM_sitscared; + } + + ResetSequenceInfo( ); + pev->frame = 0; + SetBoneController( 0, m_headTurn ); + } + pev->nextthink = gpGlobals->time + 0.1; +} + +// prepare sitting scientist to answer a question +void CSittingScientist :: SetAnswerQuestion( CTalkMonster *pSpeaker ) +{ + m_flResponseDelay = gpGlobals->time + RANDOM_FLOAT(3, 4); + m_hTalkTarget = (CBaseMonster *)pSpeaker; +} + + +//========================================================= +// FIdleSpeak +// ask question of nearby friend, or make statement +//========================================================= +int CSittingScientist :: FIdleSpeak ( void ) +{ + // try to start a conversation, or make statement + int pitch; + + if (!FOkToSpeak()) + return FALSE; + + // set global min delay for next conversation + CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(4.8, 5.2); + + pitch = GetVoicePitch(); + + // if there is a friend nearby to speak to, play sentence, set friend's response time, return + + // try to talk to any standing or sitting scientists nearby + CBaseEntity *pentFriend = FindNearestFriend(FALSE); + + if (pentFriend && RANDOM_LONG(0,1)) + { + CTalkMonster *pTalkMonster = GetClassPtr((CTalkMonster *)pentFriend->pev); + pTalkMonster->SetAnswerQuestion( this ); + + IdleHeadTurn(pentFriend->pev->origin); + SENTENCEG_PlayRndSz( ENT(pev), m_szGrp[TLK_PQUESTION], 1.0, ATTN_IDLE, 0, pitch ); + // set global min delay for next conversation + CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(4.8, 5.2); + return TRUE; + } + + // otherwise, play an idle statement + if (RANDOM_LONG(0,1)) + { + SENTENCEG_PlayRndSz( ENT(pev), m_szGrp[TLK_PIDLE], 1.0, ATTN_IDLE, 0, pitch ); + // set global min delay for next conversation + CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(4.8, 5.2); + return TRUE; + } + + // never spoke + CTalkMonster::g_talkWaitTime = 0; + return FALSE; +} diff --git a/dlls/scripted.cpp b/dlls/scripted.cpp new file mode 100644 index 0000000..651af70 --- /dev/null +++ b/dlls/scripted.cpp @@ -0,0 +1,1260 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +/* + + +===== scripted.cpp ======================================================== + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" + +#ifndef ANIMATION_H +#include "animation.h" +#endif + +#ifndef SAVERESTORE_H +#include "saverestore.h" +#endif + +#include "schedule.h" +#include "scripted.h" +#include "defaultai.h" + + + +/* +classname "scripted_sequence" +targetname "me" - there can be more than one with the same name, and they act in concert +target "the_entity_I_want_to_start_playing" or "class entity_classname" will pick the closest inactive scientist +play "name_of_sequence" +idle "name of idle sequence to play before starting" +donetrigger "whatever" - can be any other triggerable entity such as another sequence, train, door, or a special case like "die" or "remove" +moveto - if set the monster first moves to this nodes position +range # - only search this far to find the target +spawnflags - (stop if blocked, stop if player seen) +*/ + + +// +// Cache user-entity-field values until spawn is called. +// + +void CCineMonster :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszIdle")) + { + m_iszIdle = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszPlay")) + { + m_iszPlay = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszEntity")) + { + m_iszEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fMoveTo")) + { + m_fMoveTo = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flRepeat")) + { + m_flRepeat = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flRadius")) + { + m_flRadius = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iFinishSchedule")) + { + m_iFinishSchedule = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CBaseMonster::KeyValue( pkvd ); + } +} + +TYPEDESCRIPTION CCineMonster::m_SaveData[] = +{ + DEFINE_FIELD( CCineMonster, m_iszIdle, FIELD_STRING ), + DEFINE_FIELD( CCineMonster, m_iszPlay, FIELD_STRING ), + DEFINE_FIELD( CCineMonster, m_iszEntity, FIELD_STRING ), + DEFINE_FIELD( CCineMonster, m_fMoveTo, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_flRepeat, FIELD_FLOAT ), + DEFINE_FIELD( CCineMonster, m_flRadius, FIELD_FLOAT ), + + DEFINE_FIELD( CCineMonster, m_iDelay, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_startTime, FIELD_TIME ), + + DEFINE_FIELD( CCineMonster, m_saved_movetype, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_saved_solid, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_saved_effects, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_iFinishSchedule, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_interruptable, FIELD_BOOLEAN ), +}; + + +IMPLEMENT_SAVERESTORE( CCineMonster, CBaseMonster ); + +LINK_ENTITY_TO_CLASS( scripted_sequence, CCineMonster ); +#define CLASSNAME "scripted_sequence" + +LINK_ENTITY_TO_CLASS( aiscripted_sequence, CCineAI ); + + +void CCineMonster :: Spawn( void ) +{ + // pev->solid = SOLID_TRIGGER; + // UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8)); + pev->solid = SOLID_NOT; + + + // REMOVE: The old side-effect +#if 0 + if ( m_iszIdle ) + m_fMoveTo = 4; +#endif + + // if no targetname, start now + if ( FStringNull(pev->targetname) || !FStringNull( m_iszIdle ) ) + { + SetThink( &CCineMonster::CineThink ); + pev->nextthink = gpGlobals->time + 1.0; + // Wait to be used? + if ( pev->targetname ) + m_startTime = gpGlobals->time + 1E6; + } + if ( pev->spawnflags & SF_SCRIPT_NOINTERRUPT ) + m_interruptable = FALSE; + else + m_interruptable = TRUE; +} + +//========================================================= +// FCanOverrideState - returns FALSE, scripted sequences +// cannot possess entities regardless of state. +//========================================================= +BOOL CCineMonster :: FCanOverrideState( void ) +{ + if ( pev->spawnflags & SF_SCRIPT_OVERRIDESTATE ) + return TRUE; + return FALSE; +} + +//========================================================= +// FCanOverrideState - returns true because scripted AI can +// possess entities regardless of their state. +//========================================================= +BOOL CCineAI :: FCanOverrideState( void ) +{ + return TRUE; +} + + +// +// CineStart +// +void CCineMonster :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // do I already know who I should use + CBaseEntity *pEntity = m_hTargetEnt; + CBaseMonster *pTarget = NULL; + + if ( pEntity ) + pTarget = pEntity->MyMonsterPointer(); + + if ( pTarget ) + { + // am I already playing the script? + if ( pTarget->m_scriptState == SCRIPT_PLAYING ) + return; + + m_startTime = gpGlobals->time + 0.05; + } + else + { + // if not, try finding them + SetThink( &CCineMonster::CineThink ); + pev->nextthink = gpGlobals->time; + } +} + + +// This doesn't really make sense since only MOVETYPE_PUSH get 'Blocked' events +void CCineMonster :: Blocked( CBaseEntity *pOther ) +{ + +} + +void CCineMonster :: Touch( CBaseEntity *pOther ) +{ +/* + ALERT( at_aiconsole, "Cine Touch\n" ); + if (m_pentTarget && OFFSET(pOther->pev) == OFFSET(m_pentTarget)) + { + CBaseMonster *pTarget = GetClassPtr((CBaseMonster *)VARS(m_pentTarget)); + pTarget->m_monsterState == MONSTERSTATE_SCRIPT; + } +*/ +} + + +/* + entvars_t *pevOther = VARS( gpGlobals->other ); + + if ( !FBitSet ( pevOther->flags , FL_MONSTER ) ) + {// touched by a non-monster. + return; + } + + pevOther->origin.z += 1; + + if ( FBitSet ( pevOther->flags, FL_ONGROUND ) ) + {// clear the onground so physics don't bitch + pevOther->flags -= FL_ONGROUND; + } + + // toss the monster! + pevOther->velocity = pev->movedir * pev->speed; + pevOther->velocity.z += m_flHeight; + + + pev->solid = SOLID_NOT;// kill the trigger for now !!!UNDONE +} +*/ + + +// +// ********** Cinematic DIE ********** +// +void CCineMonster :: Die( void ) +{ + SetThink( &CCineMonster::SUB_Remove ); +} + +// +// ********** Cinematic PAIN ********** +// +void CCineMonster :: Pain( void ) +{ + +} + +// +// ********** Cinematic Think ********** +// + +// find a viable entity +int CCineMonster :: FindEntity( void ) +{ + edict_t *pentTarget; + + pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(m_iszEntity)); + m_hTargetEnt = NULL; + CBaseMonster *pTarget = NULL; + + while (!FNullEnt(pentTarget)) + { + if ( FBitSet( VARS(pentTarget)->flags, FL_MONSTER )) + { + pTarget = GetMonsterPointer( pentTarget ); + if ( pTarget && pTarget->CanPlaySequence( FCanOverrideState(), SS_INTERRUPT_BY_NAME ) ) + { + m_hTargetEnt = pTarget; + return TRUE; + } + ALERT( at_console, "Found %s, but can't play!\n", STRING(m_iszEntity) ); + } + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(m_iszEntity)); + pTarget = NULL; + } + + if ( !pTarget ) + { + CBaseEntity *pEntity = NULL; + while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, m_flRadius )) != NULL) + { + if (FClassnameIs( pEntity->pev, STRING(m_iszEntity))) + { + if ( FBitSet( pEntity->pev->flags, FL_MONSTER )) + { + pTarget = pEntity->MyMonsterPointer( ); + if ( pTarget && pTarget->CanPlaySequence( FCanOverrideState(), SS_INTERRUPT_IDLE ) ) + { + m_hTargetEnt = pTarget; + return TRUE; + } + } + } + } + } + pTarget = NULL; + m_hTargetEnt = NULL; + return FALSE; +} + +// make the entity enter a scripted sequence +void CCineMonster :: PossessEntity( void ) +{ + CBaseEntity *pEntity = m_hTargetEnt; + CBaseMonster *pTarget = NULL; + if ( pEntity ) + pTarget = pEntity->MyMonsterPointer(); + + if ( pTarget ) + { + + // FindEntity() just checked this! +#if 0 + if ( !pTarget->CanPlaySequence( FCanOverrideState() ) ) + { + ALERT( at_aiconsole, "Can't possess entity %s\n", STRING(pTarget->pev->classname) ); + return; + } +#endif + + pTarget->m_pGoalEnt = this; + pTarget->m_pCine = this; + pTarget->m_hTargetEnt = this; + + m_saved_movetype = pTarget->pev->movetype; + m_saved_solid = pTarget->pev->solid; + m_saved_effects = pTarget->pev->effects; + pTarget->pev->effects |= pev->effects; + + switch (m_fMoveTo) + { + case 0: + pTarget->m_scriptState = SCRIPT_WAIT; + break; + + case 1: + pTarget->m_scriptState = SCRIPT_WALK_TO_MARK; + DelayStart( 1 ); + break; + + case 2: + pTarget->m_scriptState = SCRIPT_RUN_TO_MARK; + DelayStart( 1 ); + break; + + case 4: + UTIL_SetOrigin( pTarget->pev, pev->origin ); + pTarget->pev->ideal_yaw = pev->angles.y; + pTarget->pev->avelocity = Vector( 0, 0, 0 ); + pTarget->pev->velocity = Vector( 0, 0, 0 ); + pTarget->pev->effects |= EF_NOINTERP; + pTarget->pev->angles.y = pev->angles.y; + pTarget->m_scriptState = SCRIPT_WAIT; + m_startTime = gpGlobals->time + 1E6; + // UNDONE: Add a flag to do this so people can fixup physics after teleporting monsters + // pTarget->pev->flags &= ~FL_ONGROUND; + break; + } +// ALERT( at_aiconsole, "\"%s\" found and used (INT: %s)\n", STRING( pTarget->pev->targetname ), FBitSet(pev->spawnflags, SF_SCRIPT_NOINTERRUPT)?"No":"Yes" ); + + pTarget->m_IdealMonsterState = MONSTERSTATE_SCRIPT; + if (m_iszIdle) + { + StartSequence( pTarget, m_iszIdle, FALSE ); + if (FStrEq( STRING(m_iszIdle), STRING(m_iszPlay))) + { + pTarget->pev->framerate = 0; + } + } + } +} + +// make the entity carry out the scripted sequence instructions, but without +// destroying the monster's state. +void CCineAI :: PossessEntity( void ) +{ + Schedule_t *pNewSchedule; + + CBaseEntity *pEntity = m_hTargetEnt; + CBaseMonster *pTarget = NULL; + if ( pEntity ) + pTarget = pEntity->MyMonsterPointer(); + + if ( pTarget ) + { + if ( !pTarget->CanPlaySequence( FCanOverrideState(), SS_INTERRUPT_AI ) ) + { + ALERT( at_aiconsole, "(AI)Can't possess entity %s\n", STRING(pTarget->pev->classname) ); + return; + } + + pTarget->m_pGoalEnt = this; + pTarget->m_pCine = this; + pTarget->m_hTargetEnt = this; + + m_saved_movetype = pTarget->pev->movetype; + m_saved_solid = pTarget->pev->solid; + m_saved_effects = pTarget->pev->effects; + pTarget->pev->effects |= pev->effects; + + switch (m_fMoveTo) + { + case 0: + case 5: + pTarget->m_scriptState = SCRIPT_WAIT; + break; + + case 1: + pTarget->m_scriptState = SCRIPT_WALK_TO_MARK; + break; + + case 2: + pTarget->m_scriptState = SCRIPT_RUN_TO_MARK; + break; + + case 4: + // zap the monster instantly to the site of the script entity. + UTIL_SetOrigin( pTarget->pev, pev->origin ); + pTarget->pev->ideal_yaw = pev->angles.y; + pTarget->pev->avelocity = Vector( 0, 0, 0 ); + pTarget->pev->velocity = Vector( 0, 0, 0 ); + pTarget->pev->effects |= EF_NOINTERP; + pTarget->pev->angles.y = pev->angles.y; + pTarget->m_scriptState = SCRIPT_WAIT; + m_startTime = gpGlobals->time + 1E6; + // UNDONE: Add a flag to do this so people can fixup physics after teleporting monsters + pTarget->pev->flags &= ~FL_ONGROUND; + break; + default: + ALERT ( at_aiconsole, "aiscript: invalid Move To Position value!" ); + break; + } + + ALERT( at_aiconsole, "\"%s\" found and used\n", STRING( pTarget->pev->targetname ) ); + + pTarget->m_IdealMonsterState = MONSTERSTATE_SCRIPT; + +/* + if (m_iszIdle) + { + StartSequence( pTarget, m_iszIdle, FALSE ); + if (FStrEq( STRING(m_iszIdle), STRING(m_iszPlay))) + { + pTarget->pev->framerate = 0; + } + } +*/ + // Already in a scripted state? + if ( pTarget->m_MonsterState == MONSTERSTATE_SCRIPT ) + { + pNewSchedule = pTarget->GetScheduleOfType( SCHED_AISCRIPT ); + pTarget->ChangeSchedule( pNewSchedule ); + } + } +} + +void CCineMonster :: CineThink( void ) +{ + if (FindEntity()) + { + PossessEntity( ); + ALERT( at_aiconsole, "script \"%s\" using monster \"%s\"\n", STRING( pev->targetname ), STRING( m_iszEntity ) ); + } + else + { + CancelScript( ); + ALERT( at_aiconsole, "script \"%s\" can't find monster \"%s\"\n", STRING( pev->targetname ), STRING( m_iszEntity ) ); + pev->nextthink = gpGlobals->time + 1.0; + } +} + + +// lookup a sequence name and setup the target monster to play it +BOOL CCineMonster :: StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL completeOnEmpty ) +{ + if ( !iszSeq && completeOnEmpty ) + { + SequenceDone( pTarget ); + return FALSE; + } + + pTarget->pev->sequence = pTarget->LookupSequence( STRING( iszSeq ) ); + if (pTarget->pev->sequence == -1) + { + ALERT( at_error, "%s: unknown scripted sequence \"%s\"\n", STRING( pTarget->pev->targetname ), STRING( iszSeq) ); + pTarget->pev->sequence = 0; + // return FALSE; + } + +#if 0 + char *s; + if ( pev->spawnflags & SF_SCRIPT_NOINTERRUPT ) + s = "No"; + else + s = "Yes"; + + ALERT( at_console, "%s (%s): started \"%s\":INT:%s\n", STRING( pTarget->pev->targetname ), STRING( pTarget->pev->classname ), STRING( iszSeq), s ); +#endif + + pTarget->pev->frame = 0; + pTarget->ResetSequenceInfo( ); + return TRUE; +} + +// lookup a sequence name and setup the target monster to play it +// overridden for CCineAI because it's ok for them to not have an animation sequence +// for the monster to play. For a regular Scripted Sequence, that situation is an error. +BOOL CCineAI :: StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL completeOnEmpty ) +{ + if ( iszSeq == 0 && completeOnEmpty ) + { + // no sequence was provided. Just let the monster proceed, however, we still have to fire any Sequence target + // and remove any non-repeatable CineAI entities here ( because there is code elsewhere that handles those tasks, but + // not until the animation sequence is finished. We have to manually take care of these things where there is no sequence. + + SequenceDone ( pTarget ); + + return TRUE; + } + + pTarget->pev->sequence = pTarget->LookupSequence( STRING( iszSeq ) ); + + if (pTarget->pev->sequence == -1) + { + ALERT( at_error, "%s: unknown aiscripted sequence \"%s\"\n", STRING( pTarget->pev->targetname ), STRING( iszSeq) ); + pTarget->pev->sequence = 0; + // return FALSE; + } + + pTarget->pev->frame = 0; + pTarget->ResetSequenceInfo( ); + return TRUE; +} + +//========================================================= +// SequenceDone - called when a scripted sequence animation +// sequence is done playing ( or when an AI Scripted Sequence +// doesn't supply an animation sequence to play ). Expects +// the CBaseMonster pointer to the monster that the sequence +// possesses. +//========================================================= +void CCineMonster :: SequenceDone ( CBaseMonster *pMonster ) +{ + //ALERT( at_aiconsole, "Sequence %s finished\n", STRING( m_pCine->m_iszPlay ) ); + + if ( !( pev->spawnflags & SF_SCRIPT_REPEATABLE ) ) + { + SetThink( &CCineMonster::SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; + } + + // This is done so that another sequence can take over the monster when triggered by the first + + pMonster->CineCleanup(); + + FixScriptMonsterSchedule( pMonster ); + + // This may cause a sequence to attempt to grab this guy NOW, so we have to clear him out + // of the existing sequence + SUB_UseTargets( NULL, USE_TOGGLE, 0 ); +} + +//========================================================= +// When a monster finishes a scripted sequence, we have to +// fix up its state and schedule for it to return to a +// normal AI monster. +// +// Scripted sequences just dirty the Schedule and drop the +// monster in Idle State. +//========================================================= +void CCineMonster :: FixScriptMonsterSchedule( CBaseMonster *pMonster ) +{ + if ( pMonster->m_IdealMonsterState != MONSTERSTATE_DEAD ) + pMonster->m_IdealMonsterState = MONSTERSTATE_IDLE; + pMonster->ClearSchedule(); +} + +//========================================================= +// When a monster finishes a scripted sequence, we have to +// fix up its state and schedule for it to return to a +// normal AI monster. +// +// AI Scripted sequences will, depending on what the level +// designer selects: +// +// -Dirty the monster's schedule and drop out of the +// sequence in their current state. +// +// -Select a specific AMBUSH schedule, regardless of state. +//========================================================= +void CCineAI :: FixScriptMonsterSchedule( CBaseMonster *pMonster ) +{ + switch ( m_iFinishSchedule ) + { + case SCRIPT_FINISHSCHED_DEFAULT: + pMonster->ClearSchedule(); + break; + case SCRIPT_FINISHSCHED_AMBUSH: + pMonster->ChangeSchedule( pMonster->GetScheduleOfType( SCHED_AMBUSH ) ); + break; + default: + ALERT ( at_aiconsole, "FixScriptMonsterSchedule - no case!\n" ); + pMonster->ClearSchedule(); + break; + } +} + +BOOL CBaseMonster :: ExitScriptedSequence( ) +{ + if ( pev->deadflag == DEAD_DYING ) + { + // is this legal? + // BUGBUG -- This doesn't call Killed() + m_IdealMonsterState = MONSTERSTATE_DEAD; + return FALSE; + } + + if (m_pCine) + { + m_pCine->CancelScript( ); + } + + return TRUE; +} + + +void CCineMonster::AllowInterrupt( BOOL fAllow ) +{ + if ( pev->spawnflags & SF_SCRIPT_NOINTERRUPT ) + return; + m_interruptable = fAllow; +} + + +BOOL CCineMonster::CanInterrupt( void ) +{ + if ( !m_interruptable ) + return FALSE; + + CBaseEntity *pTarget = m_hTargetEnt; + + if ( pTarget != NULL && pTarget->pev->deadflag == DEAD_NO ) + return TRUE; + + return FALSE; +} + + +int CCineMonster::IgnoreConditions( void ) +{ + if ( CanInterrupt() ) + return 0; + return SCRIPT_BREAK_CONDITIONS; +} + + +void ScriptEntityCancel( edict_t *pentCine ) +{ + // make sure they are a scripted_sequence + if (FClassnameIs( pentCine, CLASSNAME )) + { + CCineMonster *pCineTarget = GetClassPtr((CCineMonster *)VARS(pentCine)); + // make sure they have a monster in mind for the script + CBaseEntity *pEntity = pCineTarget->m_hTargetEnt; + CBaseMonster *pTarget = NULL; + if ( pEntity ) + pTarget = pEntity->MyMonsterPointer(); + + if (pTarget) + { + // make sure their monster is actually playing a script + if ( pTarget->m_MonsterState == MONSTERSTATE_SCRIPT ) + { + // tell them do die + pTarget->m_scriptState = CCineMonster::SCRIPT_CLEANUP; + // do it now + pTarget->CineCleanup( ); + } + } + } +} + + +// find all the cinematic entities with my targetname and stop them from playing +void CCineMonster :: CancelScript( void ) +{ + ALERT( at_aiconsole, "Cancelling script: %s\n", STRING(m_iszPlay) ); + + if ( !pev->targetname ) + { + ScriptEntityCancel( edict() ); + return; + } + + edict_t *pentCineTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(pev->targetname)); + + while (!FNullEnt(pentCineTarget)) + { + ScriptEntityCancel( pentCineTarget ); + pentCineTarget = FIND_ENTITY_BY_TARGETNAME(pentCineTarget, STRING(pev->targetname)); + } +} + + +// find all the cinematic entities with my targetname and tell them to wait before starting +void CCineMonster :: DelayStart( int state ) +{ + edict_t *pentCine = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(pev->targetname)); + + while (!FNullEnt(pentCine)) + { + if (FClassnameIs( pentCine, "scripted_sequence" )) + { + CCineMonster *pTarget = GetClassPtr((CCineMonster *)VARS(pentCine)); + if (state) + { + pTarget->m_iDelay++; + } + else + { + pTarget->m_iDelay--; + if (pTarget->m_iDelay <= 0) + pTarget->m_startTime = gpGlobals->time + 0.05; + } + } + pentCine = FIND_ENTITY_BY_TARGETNAME(pentCine, STRING(pev->targetname)); + } +} + + + +// Find an entity that I'm interested in and precache the sounds he'll need in the sequence. +void CCineMonster :: Activate( void ) +{ + edict_t *pentTarget; + CBaseMonster *pTarget; + + // The entity name could be a target name or a classname + // Check the targetname + pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(m_iszEntity)); + pTarget = NULL; + + while (!pTarget && !FNullEnt(pentTarget)) + { + if ( FBitSet( VARS(pentTarget)->flags, FL_MONSTER )) + { + pTarget = GetMonsterPointer( pentTarget ); + } + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(m_iszEntity)); + } + + // If no entity with that targetname, check the classname + if ( !pTarget ) + { + pentTarget = FIND_ENTITY_BY_CLASSNAME(NULL, STRING(m_iszEntity)); + while (!pTarget && !FNullEnt(pentTarget)) + { + pTarget = GetMonsterPointer( pentTarget ); + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(m_iszEntity)); + } + } + // Found a compatible entity + if ( pTarget ) + { + void *pmodel; + pmodel = GET_MODEL_PTR( pTarget->edict() ); + if ( pmodel ) + { + // Look through the event list for stuff to precache + SequencePrecache( pmodel, STRING( m_iszIdle ) ); + SequencePrecache( pmodel, STRING( m_iszPlay ) ); + } + } +} + + +BOOL CBaseMonster :: CineCleanup( ) +{ + CCineMonster *pOldCine = m_pCine; + + // am I linked to a cinematic? + if (m_pCine) + { + // okay, reset me to what it thought I was before + m_pCine->m_hTargetEnt = NULL; + pev->movetype = m_pCine->m_saved_movetype; + pev->solid = m_pCine->m_saved_solid; + pev->effects = m_pCine->m_saved_effects; + } + else + { + // arg, punt + pev->movetype = MOVETYPE_STEP;// this is evil + pev->solid = SOLID_SLIDEBOX; + } + m_pCine = NULL; + m_hTargetEnt = NULL; + m_pGoalEnt = NULL; + if (pev->deadflag == DEAD_DYING) + { + // last frame of death animation? + pev->health = 0; + pev->framerate = 0.0; + pev->solid = SOLID_NOT; + SetState( MONSTERSTATE_DEAD ); + pev->deadflag = DEAD_DEAD; + UTIL_SetSize( pev, pev->mins, Vector(pev->maxs.x, pev->maxs.y, pev->mins.z + 2) ); + + if ( pOldCine && FBitSet( pOldCine->pev->spawnflags, SF_SCRIPT_LEAVECORPSE ) ) + { + SetUse( NULL ); // BUGBUG -- This doesn't call Killed() + SetThink( NULL ); // This will probably break some stuff + SetTouch( NULL ); + } + else + SUB_StartFadeOut(); // SetThink( SUB_DoNothing ); + // This turns off animation & physics in case their origin ends up stuck in the world or something + StopAnimation(); + pev->movetype = MOVETYPE_NONE; + pev->effects |= EF_NOINTERP; // Don't interpolate either, assume the corpse is positioned in its final resting place + return FALSE; + } + + // If we actually played a sequence + if ( pOldCine && pOldCine->m_iszPlay ) + { + if ( !(pOldCine->pev->spawnflags & SF_SCRIPT_NOSCRIPTMOVEMENT) ) + { + // reset position + Vector new_origin, new_angle; + GetBonePosition( 0, new_origin, new_angle ); + + // Figure out how far they have moved + // We can't really solve this problem because we can't query the movement of the origin relative + // to the sequence. We can get the root bone's position as we do here, but there are + // cases where the root bone is in a different relative position to the entity's origin + // before/after the sequence plays. So we are stuck doing this: + + // !!!HACKHACK: Float the origin up and drop to floor because some sequences have + // irregular motion that can't be properly accounted for. + + // UNDONE: THIS SHOULD ONLY HAPPEN IF WE ACTUALLY PLAYED THE SEQUENCE. + Vector oldOrigin = pev->origin; + + // UNDONE: ugly hack. Don't move monster if they don't "seem" to move + // this really needs to be done with the AX,AY,etc. flags, but that aren't consistantly + // being set, so animations that really do move won't be caught. + if ((oldOrigin - new_origin).Length2D() < 8.0) + new_origin = oldOrigin; + + pev->origin.x = new_origin.x; + pev->origin.y = new_origin.y; + pev->origin.z += 1; + + pev->flags |= FL_ONGROUND; + int drop = DROP_TO_FLOOR( ENT(pev) ); + + // Origin in solid? Set to org at the end of the sequence + if ( drop < 0 ) + pev->origin = oldOrigin; + else if ( drop == 0 ) // Hanging in air? + { + pev->origin.z = new_origin.z; + pev->flags &= ~FL_ONGROUND; + } + // else entity hit floor, leave there + + // pEntity->pev->origin.z = new_origin.z + 5.0; // damn, got to fix this + + UTIL_SetOrigin( pev, pev->origin ); + pev->effects |= EF_NOINTERP; + } + + // We should have some animation to put these guys in, but for now it's idle. + // Due to NOINTERP above, there won't be any blending between this anim & the sequence + m_Activity = ACT_RESET; + } + // set them back into a normal state + pev->enemy = NULL; + if ( pev->health > 0 ) + m_IdealMonsterState = MONSTERSTATE_IDLE; // m_previousState; + else + { + // Dropping out because he got killed + // Can't call killed() no attacker and weirdness (late gibbing) may result + m_IdealMonsterState = MONSTERSTATE_DEAD; + SetConditions( bits_COND_LIGHT_DAMAGE ); + pev->deadflag = DEAD_DYING; + FCheckAITrigger(); + pev->deadflag = DEAD_NO; + } + + + // SetAnimation( m_MonsterState ); + ClearBits(pev->spawnflags, SF_MONSTER_WAIT_FOR_SCRIPT ); + + return TRUE; +} + + + + +class CScriptedSentence : public CBaseToggle +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT FindThink( void ); + void EXPORT DelayThink( void ); + int ObjectCaps( void ) { return (CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + CBaseMonster *FindEntity( void ); + BOOL AcceptableSpeaker( CBaseMonster *pMonster ); + BOOL StartSentence( CBaseMonster *pTarget ); + + +private: + int m_iszSentence; // string index for idle animation + int m_iszEntity; // entity that is wanted for this sentence + float m_flRadius; // range to search + float m_flDuration; // How long the sentence lasts + float m_flRepeat; // repeat rate + float m_flAttenuation; + float m_flVolume; + BOOL m_active; + int m_iszListener; // name of entity to look at while talking +}; + +#define SF_SENTENCE_ONCE 0x0001 +#define SF_SENTENCE_FOLLOWERS 0x0002 // only say if following player +#define SF_SENTENCE_INTERRUPT 0x0004 // force talking except when dead +#define SF_SENTENCE_CONCURRENT 0x0008 // allow other people to keep talking + +TYPEDESCRIPTION CScriptedSentence::m_SaveData[] = +{ + DEFINE_FIELD( CScriptedSentence, m_iszSentence, FIELD_STRING ), + DEFINE_FIELD( CScriptedSentence, m_iszEntity, FIELD_STRING ), + DEFINE_FIELD( CScriptedSentence, m_flRadius, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flDuration, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flRepeat, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flAttenuation, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flVolume, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_active, FIELD_BOOLEAN ), + DEFINE_FIELD( CScriptedSentence, m_iszListener, FIELD_STRING ), +}; + + +IMPLEMENT_SAVERESTORE( CScriptedSentence, CBaseToggle ); + +LINK_ENTITY_TO_CLASS( scripted_sentence, CScriptedSentence ); + +void CScriptedSentence :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "sentence")) + { + m_iszSentence = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "entity")) + { + m_iszEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "duration")) + { + m_flDuration = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "radius")) + { + m_flRadius = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "refire")) + { + m_flRepeat = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if(FStrEq(pkvd->szKeyName, "attenuation")) + { + pev->impulse = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if(FStrEq(pkvd->szKeyName, "volume")) + { + m_flVolume = atof( pkvd->szValue ) * 0.1; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "listener")) + { + m_iszListener = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + + +void CScriptedSentence :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !m_active ) + return; +// ALERT( at_console, "Firing sentence: %s\n", STRING(m_iszSentence) ); + SetThink( &CScriptedSentence::FindThink ); + pev->nextthink = gpGlobals->time; +} + + +void CScriptedSentence :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + + m_active = TRUE; + // if no targetname, start now + if ( !pev->targetname ) + { + SetThink( &CScriptedSentence::FindThink ); + pev->nextthink = gpGlobals->time + 1.0; + } + + switch( pev->impulse ) + { + case 1: // Medium radius + m_flAttenuation = ATTN_STATIC; + break; + + case 2: // Large radius + m_flAttenuation = ATTN_NORM; + break; + + case 3: //EVERYWHERE + m_flAttenuation = ATTN_NONE; + break; + + default: + case 0: // Small radius + m_flAttenuation = ATTN_IDLE; + break; + } + pev->impulse = 0; + + // No volume, use normal + if ( m_flVolume <= 0 ) + m_flVolume = 1.0; +} + + +void CScriptedSentence :: FindThink( void ) +{ + CBaseMonster *pMonster = FindEntity(); + if ( pMonster ) + { + StartSentence( pMonster ); + if ( pev->spawnflags & SF_SENTENCE_ONCE ) + UTIL_Remove( this ); + SetThink( &CScriptedSentence::DelayThink ); + pev->nextthink = gpGlobals->time + m_flDuration + m_flRepeat; + m_active = FALSE; +// ALERT( at_console, "%s: found monster %s\n", STRING(m_iszSentence), STRING(m_iszEntity) ); + } + else + { +// ALERT( at_console, "%s: can't find monster %s\n", STRING(m_iszSentence), STRING(m_iszEntity) ); + pev->nextthink = gpGlobals->time + m_flRepeat + 0.5; + } +} + + +void CScriptedSentence :: DelayThink( void ) +{ + m_active = TRUE; + if ( !pev->targetname ) + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CScriptedSentence::FindThink ); +} + + +BOOL CScriptedSentence :: AcceptableSpeaker( CBaseMonster *pMonster ) +{ + if ( pMonster ) + { + if ( pev->spawnflags & SF_SENTENCE_FOLLOWERS ) + { + if ( pMonster->m_hTargetEnt == NULL || !FClassnameIs(pMonster->m_hTargetEnt->pev, "player") ) + return FALSE; + } + BOOL override; + if ( pev->spawnflags & SF_SENTENCE_INTERRUPT ) + override = TRUE; + else + override = FALSE; + if ( pMonster->CanPlaySentence( override ) ) + return TRUE; + } + return FALSE; +} + + +CBaseMonster *CScriptedSentence :: FindEntity( void ) +{ + edict_t *pentTarget; + CBaseMonster *pMonster; + + + pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(m_iszEntity)); + pMonster = NULL; + + while (!FNullEnt(pentTarget)) + { + pMonster = GetMonsterPointer( pentTarget ); + if ( pMonster != NULL ) + { + if ( AcceptableSpeaker( pMonster ) ) + return pMonster; +// ALERT( at_console, "%s (%s), not acceptable\n", STRING(pMonster->pev->classname), STRING(pMonster->pev->targetname) ); + } + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(m_iszEntity)); + } + + CBaseEntity *pEntity = NULL; + while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, m_flRadius )) != NULL) + { + if (FClassnameIs( pEntity->pev, STRING(m_iszEntity))) + { + if ( FBitSet( pEntity->pev->flags, FL_MONSTER )) + { + pMonster = pEntity->MyMonsterPointer( ); + if ( AcceptableSpeaker( pMonster ) ) + return pMonster; + } + } + } + + return NULL; +} + + +BOOL CScriptedSentence :: StartSentence( CBaseMonster *pTarget ) +{ + if ( !pTarget ) + { + ALERT( at_aiconsole, "Not Playing sentence %s\n", STRING(m_iszSentence) ); + return NULL; + } + + BOOL bConcurrent = FALSE; + if ( !(pev->spawnflags & SF_SENTENCE_CONCURRENT) ) + bConcurrent = TRUE; + + CBaseEntity *pListener = NULL; + if (!FStringNull(m_iszListener)) + { + float radius = m_flRadius; + + if ( FStrEq( STRING(m_iszListener ), "player" ) ) + radius = 4096; // Always find the player + + pListener = UTIL_FindEntityGeneric( STRING( m_iszListener ), pTarget->pev->origin, radius ); + } + + pTarget->PlayScriptedSentence( STRING(m_iszSentence), m_flDuration, m_flVolume, m_flAttenuation, bConcurrent, pListener ); + ALERT( at_aiconsole, "Playing sentence %s (%.1f)\n", STRING(m_iszSentence), m_flDuration ); + SUB_UseTargets( NULL, USE_TOGGLE, 0 ); + return TRUE; +} + + + + + +/* + +*/ + + +//========================================================= +// Furniture - this is the cool comment I cut-and-pasted +//========================================================= +class CFurniture : public CBaseMonster +{ +public: + void Spawn ( void ); + void Die( void ); + int Classify ( void ); + virtual int ObjectCaps( void ) { return (CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } +}; + + +LINK_ENTITY_TO_CLASS( monster_furniture, CFurniture ); + + +//========================================================= +// Furniture is killed +//========================================================= +void CFurniture :: Die ( void ) +{ + SetThink ( &CFurniture::SUB_Remove ); + pev->nextthink = gpGlobals->time; +} + +//========================================================= +// This used to have something to do with bees flying, but +// now it only initializes moving furniture in scripted sequences +//========================================================= +void CFurniture :: Spawn( ) +{ + PRECACHE_MODEL((char *)STRING(pev->model)); + SET_MODEL(ENT(pev), STRING(pev->model)); + + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_BBOX; + pev->health = 80000; + pev->takedamage = DAMAGE_AIM; + pev->effects = 0; + pev->yaw_speed = 0; + pev->sequence = 0; + pev->frame = 0; + +// pev->nextthink += 1.0; +// SetThink (WalkMonsterDelay); + + ResetSequenceInfo( ); + pev->frame = 0; + MonsterInit(); +} + +//========================================================= +// ID's Furniture as neutral (noone will attack it) +//========================================================= +int CFurniture::Classify ( void ) +{ + return CLASS_NONE; +} + + diff --git a/dlls/scripted.h b/dlls/scripted.h new file mode 100644 index 0000000..5a5c9b2 --- /dev/null +++ b/dlls/scripted.h @@ -0,0 +1,107 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef SCRIPTED_H +#define SCRIPTED_H + +#ifndef SCRIPTEVENT_H +#include "scriptevent.h" +#endif + +#define SF_SCRIPT_WAITTILLSEEN 1 +#define SF_SCRIPT_EXITAGITATED 2 +#define SF_SCRIPT_REPEATABLE 4 +#define SF_SCRIPT_LEAVECORPSE 8 +//#define SF_SCRIPT_INTERPOLATE 16 // don't use, old bug +#define SF_SCRIPT_NOINTERRUPT 32 +#define SF_SCRIPT_OVERRIDESTATE 64 +#define SF_SCRIPT_NOSCRIPTMOVEMENT 128 + +#define SCRIPT_BREAK_CONDITIONS (bits_COND_LIGHT_DAMAGE|bits_COND_HEAVY_DAMAGE) + +enum SS_INTERRUPT +{ + SS_INTERRUPT_IDLE = 0, + SS_INTERRUPT_BY_NAME, + SS_INTERRUPT_AI, +}; + +// when a monster finishes an AI scripted sequence, we can choose +// a schedule to place them in. These defines are the aliases to +// resolve worldcraft input to real schedules (sjb) +#define SCRIPT_FINISHSCHED_DEFAULT 0 +#define SCRIPT_FINISHSCHED_AMBUSH 1 + +class CCineMonster : public CBaseMonster +{ +public: + void Spawn( void ); + virtual void KeyValue( KeyValueData *pkvd ); + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void Blocked( CBaseEntity *pOther ); + virtual void Touch( CBaseEntity *pOther ); + virtual int ObjectCaps( void ) { return (CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + virtual void Activate( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + // void EXPORT CineSpawnThink( void ); + void EXPORT CineThink( void ); + void Pain( void ); + void Die( void ); + void DelayStart( int state ); + BOOL FindEntity( void ); + virtual void PossessEntity( void ); + + void ReleaseEntity( CBaseMonster *pEntity ); + void CancelScript( void ); + virtual BOOL StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL completeOnEmpty ); + virtual BOOL FCanOverrideState ( void ); + void SequenceDone ( CBaseMonster *pMonster ); + virtual void FixScriptMonsterSchedule( CBaseMonster *pMonster ); + BOOL CanInterrupt( void ); + void AllowInterrupt( BOOL fAllow ); + int IgnoreConditions( void ); + + int m_iszIdle; // string index for idle animation + int m_iszPlay; // string index for scripted animation + int m_iszEntity; // entity that is wanted for this script + int m_fMoveTo; + int m_iFinishSchedule; + float m_flRadius; // range to search + float m_flRepeat; // repeat rate + + int m_iDelay; + float m_startTime; + + int m_saved_movetype; + int m_saved_solid; + int m_saved_effects; +// Vector m_vecOrigOrigin; + BOOL m_interruptable; +}; + +class CCineAI : public CCineMonster +{ + BOOL StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL completeOnEmpty ); + void PossessEntity( void ); + BOOL FCanOverrideState ( void ); + virtual void FixScriptMonsterSchedule( CBaseMonster *pMonster ); +}; + + +#endif //SCRIPTED_H diff --git a/dlls/scriptevent.h b/dlls/scriptevent.h new file mode 100644 index 0000000..73bd28a --- /dev/null +++ b/dlls/scriptevent.h @@ -0,0 +1,29 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef SCRIPTEVENT_H +#define SCRIPTEVENT_H + +#define SCRIPT_EVENT_DEAD 1000 // character is now dead +#define SCRIPT_EVENT_NOINTERRUPT 1001 // does not allow interrupt +#define SCRIPT_EVENT_CANINTERRUPT 1002 // will allow interrupt +#define SCRIPT_EVENT_FIREEVENT 1003 // event now fires +#define SCRIPT_EVENT_SOUND 1004 // Play named wave file (on CHAN_BODY) +#define SCRIPT_EVENT_SENTENCE 1005 // Play named sentence +#define SCRIPT_EVENT_INAIR 1006 // Leave the character in air at the end of the sequence (don't find the floor) +#define SCRIPT_EVENT_ENDANIMATION 1007 // Set the animation by name after the sequence completes +#define SCRIPT_EVENT_SOUND_VOICE 1008 // Play named wave file (on CHAN_VOICE) +#define SCRIPT_EVENT_SENTENCE_RND1 1009 // Play sentence group 25% of the time +#define SCRIPT_EVENT_NOT_DEAD 1010 // Bring back to life (for life/death sequences) +#endif //SCRIPTEVENT_H diff --git a/dlls/shotgun.cpp b/dlls/shotgun.cpp new file mode 100644 index 0000000..604f12b --- /dev/null +++ b/dlls/shotgun.cpp @@ -0,0 +1,401 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "gamerules.h" + +// special deathmatch shotgun spreads +#define VECTOR_CONE_DM_SHOTGUN Vector( 0.08716, 0.04362, 0.00 )// 10 degrees by 5 degrees +#define VECTOR_CONE_DM_DOUBLESHOTGUN Vector( 0.17365, 0.04362, 0.00 ) // 20 degrees by 5 degrees + +enum shotgun_e { + SHOTGUN_IDLE = 0, + SHOTGUN_FIRE, + SHOTGUN_FIRE2, + SHOTGUN_RELOAD, + SHOTGUN_PUMP, + SHOTGUN_START_RELOAD, + SHOTGUN_DRAW, + SHOTGUN_HOLSTER, + SHOTGUN_IDLE4, + SHOTGUN_IDLE_DEEP +}; + +LINK_ENTITY_TO_CLASS( weapon_shotgun, CShotgun ); + +void CShotgun::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_SHOTGUN; + SET_MODEL(ENT(pev), "models/w_shotgun.mdl"); + + m_iDefaultAmmo = SHOTGUN_DEFAULT_GIVE; + + FallInit();// get ready to fall +} + + +void CShotgun::Precache( void ) +{ + PRECACHE_MODEL("models/v_shotgun.mdl"); + PRECACHE_MODEL("models/w_shotgun.mdl"); + PRECACHE_MODEL("models/p_shotgun.mdl"); + + m_iShell = PRECACHE_MODEL ("models/shotgunshell.mdl");// shotgun shell + + PRECACHE_SOUND("items/9mmclip1.wav"); + + PRECACHE_SOUND ("weapons/dbarrel1.wav");//shotgun + PRECACHE_SOUND ("weapons/sbarrel1.wav");//shotgun + + PRECACHE_SOUND ("weapons/reload1.wav"); // shotgun reload + PRECACHE_SOUND ("weapons/reload3.wav"); // shotgun reload + +// PRECACHE_SOUND ("weapons/sshell1.wav"); // shotgun reload - played on client +// PRECACHE_SOUND ("weapons/sshell3.wav"); // shotgun reload - played on client + + PRECACHE_SOUND ("weapons/357_cock1.wav"); // gun empty sound + PRECACHE_SOUND ("weapons/scock1.wav"); // cock gun + + m_usSingleFire = PRECACHE_EVENT( 1, "events/shotgun1.sc" ); + m_usDoubleFire = PRECACHE_EVENT( 1, "events/shotgun2.sc" ); +} + +int CShotgun::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + + +int CShotgun::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "buckshot"; + p->iMaxAmmo1 = BUCKSHOT_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = SHOTGUN_MAX_CLIP; + p->iSlot = 2; + p->iPosition = 1; + p->iFlags = 0; + p->iId = m_iId = WEAPON_SHOTGUN; + p->iWeight = SHOTGUN_WEIGHT; + + return 1; +} + + + +BOOL CShotgun::Deploy( ) +{ + return DefaultDeploy( "models/v_shotgun.mdl", "models/p_shotgun.mdl", SHOTGUN_DRAW, "shotgun" ); +} + +void CShotgun::PrimaryAttack() +{ + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = GetNextAttackDelay(0.15); + return; + } + + if (m_iClip <= 0) + { + Reload( ); + if (m_iClip == 0) + PlayEmptySound( ); + return; + } + + m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + + m_iClip--; + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = m_pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); + + Vector vecDir; + +#ifdef CLIENT_DLL + if ( bIsMultiplayer() ) +#else + if ( g_pGameRules->IsMultiplayer() ) +#endif + { + vecDir = m_pPlayer->FireBulletsPlayer( 4, vecSrc, vecAiming, VECTOR_CONE_DM_SHOTGUN, 2048, BULLET_PLAYER_BUCKSHOT, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed ); + } + else + { + // regular old, untouched spread. + vecDir = m_pPlayer->FireBulletsPlayer( 6, vecSrc, vecAiming, VECTOR_CONE_10DEGREES, 2048, BULLET_PLAYER_BUCKSHOT, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed ); + } + + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usSingleFire, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, vecDir.x, vecDir.y, 0, 0, 0, 0 ); + + + if (!m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + // HEV suit - indicate out of ammo condition + m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + + if (m_iClip != 0) + m_flPumpTime = gpGlobals->time + 0.5; + + m_flNextPrimaryAttack = GetNextAttackDelay(0.75); + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.75; + if (m_iClip != 0) + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 5.0; + else + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.75; + m_fInSpecialReload = 0; +} + + +void CShotgun::SecondaryAttack( void ) +{ + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = GetNextAttackDelay(0.15); + return; + } + + if (m_iClip <= 1) + { + Reload( ); + PlayEmptySound( ); + return; + } + + m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + + m_iClip -= 2; + + + int flags; +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = m_pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); + + Vector vecDir; + +#ifdef CLIENT_DLL + if ( bIsMultiplayer() ) +#else + if ( g_pGameRules->IsMultiplayer() ) +#endif + { + // tuned for deathmatch + vecDir = m_pPlayer->FireBulletsPlayer( 8, vecSrc, vecAiming, VECTOR_CONE_DM_DOUBLESHOTGUN, 2048, BULLET_PLAYER_BUCKSHOT, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed ); + } + else + { + // untouched default single player + vecDir = m_pPlayer->FireBulletsPlayer( 12, vecSrc, vecAiming, VECTOR_CONE_10DEGREES, 2048, BULLET_PLAYER_BUCKSHOT, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed ); + } + + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usDoubleFire, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, vecDir.x, vecDir.y, 0, 0, 0, 0 ); + + if (!m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + // HEV suit - indicate out of ammo condition + m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + + if (m_iClip != 0) + m_flPumpTime = gpGlobals->time + 0.95; + + m_flNextPrimaryAttack = GetNextAttackDelay(1.5); + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1.5; + if (m_iClip != 0) + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 6.0; + else + m_flTimeWeaponIdle = 1.5; + + m_fInSpecialReload = 0; + +} + + +void CShotgun::Reload( void ) +{ + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0 || m_iClip == SHOTGUN_MAX_CLIP) + return; + + // don't reload until recoil is done + if (m_flNextPrimaryAttack > UTIL_WeaponTimeBase()) + return; + + // check to see if we're ready to reload + if (m_fInSpecialReload == 0) + { + SendWeaponAnim( SHOTGUN_START_RELOAD ); + m_fInSpecialReload = 1; + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.6; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.6; + m_flNextPrimaryAttack = GetNextAttackDelay(1.0); + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1.0; + return; + } + else if (m_fInSpecialReload == 1) + { + if (m_flTimeWeaponIdle > UTIL_WeaponTimeBase()) + return; + // was waiting for gun to move to side + m_fInSpecialReload = 2; + + if (RANDOM_LONG(0,1)) + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/reload1.wav", 1, ATTN_NORM, 0, 85 + RANDOM_LONG(0,0x1f)); + else + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/reload3.wav", 1, ATTN_NORM, 0, 85 + RANDOM_LONG(0,0x1f)); + + SendWeaponAnim( SHOTGUN_RELOAD ); + + m_flNextReload = UTIL_WeaponTimeBase() + 0.5; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.5; + } + else + { + // Add them to the clip + m_iClip += 1; + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= 1; + m_fInSpecialReload = 1; + } +} + + +void CShotgun::WeaponIdle( void ) +{ + ResetEmptySound( ); + + m_pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); + + if ( m_flPumpTime && m_flPumpTime < gpGlobals->time ) + { + // play pumping sound + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/scock1.wav", 1, ATTN_NORM, 0, 95 + RANDOM_LONG(0,0x1f)); + m_flPumpTime = 0; + } + + if (m_flTimeWeaponIdle < UTIL_WeaponTimeBase() ) + { + if (m_iClip == 0 && m_fInSpecialReload == 0 && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + Reload( ); + } + else if (m_fInSpecialReload != 0) + { + if (m_iClip != 8 && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + Reload( ); + } + else + { + // reload debounce has timed out + SendWeaponAnim( SHOTGUN_PUMP ); + + // play cocking sound + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/scock1.wav", 1, ATTN_NORM, 0, 95 + RANDOM_LONG(0,0x1f)); + m_fInSpecialReload = 0; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.5; + } + } + else + { + int iAnim; + float flRand = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0, 1 ); + if (flRand <= 0.8) + { + iAnim = SHOTGUN_IDLE_DEEP; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + (60.0/12.0);// * RANDOM_LONG(2, 5); + } + else if (flRand <= 0.95) + { + iAnim = SHOTGUN_IDLE; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + (20.0/9.0); + } + else + { + iAnim = SHOTGUN_IDLE4; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + (20.0/9.0); + } + SendWeaponAnim( iAnim ); + } + } +} + + + +class CShotgunAmmo : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_shotbox.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_shotbox.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + if (pOther->GiveAmmo( AMMO_BUCKSHOTBOX_GIVE, "buckshot", BUCKSHOT_MAX_CARRY ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + return TRUE; + } + return FALSE; + } +}; +LINK_ENTITY_TO_CLASS( ammo_buckshot, CShotgunAmmo ); + + diff --git a/dlls/singleplay_gamerules.cpp b/dlls/singleplay_gamerules.cpp new file mode 100644 index 0000000..78c86ef --- /dev/null +++ b/dlls/singleplay_gamerules.cpp @@ -0,0 +1,328 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.cpp +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "skill.h" +#include "items.h" + +extern DLL_GLOBAL CGameRules *g_pGameRules; +extern DLL_GLOBAL BOOL g_fGameOver; +extern int gmsgDeathMsg; // client dll messages +extern int gmsgScoreInfo; +extern int gmsgMOTD; + +//========================================================= +//========================================================= +CHalfLifeRules::CHalfLifeRules( void ) +{ + RefreshSkillData(); +} + +//========================================================= +//========================================================= +void CHalfLifeRules::Think ( void ) +{ +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::IsMultiplayer( void ) +{ + return FALSE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::IsDeathmatch ( void ) +{ + return FALSE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::IsCoOp( void ) +{ + return FALSE; +} + + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ + if ( !pPlayer->m_pActiveItem ) + { + // player doesn't have an active item! + return TRUE; + } + + if ( !pPlayer->m_pActiveItem->CanHolster() ) + { + return FALSE; + } + + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) +{ + return FALSE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) +{ + return TRUE; +} + +void CHalfLifeRules :: InitHUD( CBasePlayer *pl ) +{ +} + +//========================================================= +//========================================================= +void CHalfLifeRules :: ClientDisconnected( edict_t *pClient ) +{ +} + +//========================================================= +//========================================================= +float CHalfLifeRules::FlPlayerFallDamage( CBasePlayer *pPlayer ) +{ + // subtract off the speed at which a player is allowed to fall without being hurt, + // so damage will be based on speed beyond that, not the entire fall + pPlayer->m_flFallVelocity -= PLAYER_MAX_SAFE_FALL_SPEED; + return pPlayer->m_flFallVelocity * DAMAGE_FOR_FALL_SPEED; +} + +//========================================================= +//========================================================= +void CHalfLifeRules :: PlayerSpawn( CBasePlayer *pPlayer ) +{ +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: AllowAutoTargetCrosshair( void ) +{ + return ( g_iSkillLevel == SKILL_EASY ); +} + +//========================================================= +//========================================================= +void CHalfLifeRules :: PlayerThink( CBasePlayer *pPlayer ) +{ +} + + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: FPlayerCanRespawn( CBasePlayer *pPlayer ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +float CHalfLifeRules :: FlPlayerSpawnTime( CBasePlayer *pPlayer ) +{ + return gpGlobals->time;//now! +} + +//========================================================= +// IPointsForKill - how many points awarded to anyone +// that kills this player? +//========================================================= +int CHalfLifeRules :: IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) +{ + return 1; +} + +//========================================================= +// PlayerKilled - someone/something killed this player +//========================================================= +void CHalfLifeRules :: PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) +{ +} + +//========================================================= +// Deathnotice +//========================================================= +void CHalfLifeRules::DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) +{ +} + +//========================================================= +// PlayerGotWeapon - player has grabbed a weapon that was +// sitting in the world +//========================================================= +void CHalfLifeRules :: PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ +} + +//========================================================= +// FlWeaponRespawnTime - what is the time in the future +// at which this weapon may spawn? +//========================================================= +float CHalfLifeRules :: FlWeaponRespawnTime( CBasePlayerItem *pWeapon ) +{ + return -1; +} + +//========================================================= +// FlWeaponRespawnTime - Returns 0 if the weapon can respawn +// now, otherwise it returns the time at which it can try +// to spawn again. +//========================================================= +float CHalfLifeRules :: FlWeaponTryRespawn( CBasePlayerItem *pWeapon ) +{ + return 0; +} + +//========================================================= +// VecWeaponRespawnSpot - where should this weapon spawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHalfLifeRules :: VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ) +{ + return pWeapon->pev->origin; +} + +//========================================================= +// WeaponShouldRespawn - any conditions inhibiting the +// respawning of this weapon? +//========================================================= +int CHalfLifeRules :: WeaponShouldRespawn( CBasePlayerItem *pWeapon ) +{ + return GR_WEAPON_RESPAWN_NO; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +void CHalfLifeRules::PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ) +{ +} + +//========================================================= +//========================================================= +int CHalfLifeRules::ItemShouldRespawn( CItem *pItem ) +{ + return GR_ITEM_RESPAWN_NO; +} + + +//========================================================= +// At what time in the future may this Item respawn? +//========================================================= +float CHalfLifeRules::FlItemRespawnTime( CItem *pItem ) +{ + return -1; +} + +//========================================================= +// Where should this item respawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHalfLifeRules::VecItemRespawnSpot( CItem *pItem ) +{ + return pItem->pev->origin; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::IsAllowedToSpawn( CBaseEntity *pEntity ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +void CHalfLifeRules::PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ) +{ +} + +//========================================================= +//========================================================= +int CHalfLifeRules::AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ) +{ + return GR_AMMO_RESPAWN_NO; +} + +//========================================================= +//========================================================= +float CHalfLifeRules::FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ) +{ + return -1; +} + +//========================================================= +//========================================================= +Vector CHalfLifeRules::VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ) +{ + return pAmmo->pev->origin; +} + +//========================================================= +//========================================================= +float CHalfLifeRules::FlHealthChargerRechargeTime( void ) +{ + return 0;// don't recharge +} + +//========================================================= +//========================================================= +int CHalfLifeRules::DeadPlayerWeapons( CBasePlayer *pPlayer ) +{ + return GR_PLR_DROP_GUN_NO; +} + +//========================================================= +//========================================================= +int CHalfLifeRules::DeadPlayerAmmo( CBasePlayer *pPlayer ) +{ + return GR_PLR_DROP_AMMO_NO; +} + +//========================================================= +//========================================================= +int CHalfLifeRules::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) +{ + // why would a single player in half life need this? + return GR_NOTTEAMMATE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: FAllowMonsters( void ) +{ + return TRUE; +} diff --git a/dlls/skill.cpp b/dlls/skill.cpp new file mode 100644 index 0000000..9ed4817 --- /dev/null +++ b/dlls/skill.cpp @@ -0,0 +1,47 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// skill.cpp - code for skill level concerns +//========================================================= +#include "extdll.h" +#include "util.h" +#include "skill.h" + + +skilldata_t gSkillData; + + +//========================================================= +// take the name of a cvar, tack a digit for the skill level +// on, and return the value.of that Cvar +//========================================================= +float GetSkillCvar( char *pName ) +{ + int iCount; + float flValue; + char szBuffer[ 64 ]; + + iCount = sprintf( szBuffer, "%s%d",pName, gSkillData.iSkillLevel ); + + flValue = CVAR_GET_FLOAT ( szBuffer ); + + if ( flValue <= 0 ) + { + ALERT ( at_console, "\n\n** GetSkillCVar Got a zero for %s **\n\n", szBuffer ); + } + + return flValue; +} + diff --git a/dlls/skill.h b/dlls/skill.h new file mode 100644 index 0000000..41bdfd8 --- /dev/null +++ b/dlls/skill.h @@ -0,0 +1,147 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// skill.h - skill level concerns +//========================================================= + +struct skilldata_t +{ + + int iSkillLevel; // game skill level + +// Monster Health & Damage + float agruntHealth; + float agruntDmgPunch; + + float apacheHealth; + + float barneyHealth; + + float bigmommaHealthFactor; // Multiply each node's health by this + float bigmommaDmgSlash; // melee attack damage + float bigmommaDmgBlast; // mortar attack damage + float bigmommaRadiusBlast; // mortar attack radius + + float bullsquidHealth; + float bullsquidDmgBite; + float bullsquidDmgWhip; + float bullsquidDmgSpit; + + float gargantuaHealth; + float gargantuaDmgSlash; + float gargantuaDmgFire; + float gargantuaDmgStomp; + + float hassassinHealth; + + float headcrabHealth; + float headcrabDmgBite; + + float hgruntHealth; + float hgruntDmgKick; + float hgruntShotgunPellets; + float hgruntGrenadeSpeed; + + float houndeyeHealth; + float houndeyeDmgBlast; + + float slaveHealth; + float slaveDmgClaw; + float slaveDmgClawrake; + float slaveDmgZap; + + float ichthyosaurHealth; + float ichthyosaurDmgShake; + + float leechHealth; + float leechDmgBite; + + float controllerHealth; + float controllerDmgZap; + float controllerSpeedBall; + float controllerDmgBall; + + float nihilanthHealth; + float nihilanthZap; + + float scientistHealth; + + float snarkHealth; + float snarkDmgBite; + float snarkDmgPop; + + float zombieHealth; + float zombieDmgOneSlash; + float zombieDmgBothSlash; + + float turretHealth; + float miniturretHealth; + float sentryHealth; + + +// Player Weapons + float plrDmgCrowbar; + float plrDmg9MM; + float plrDmg357; + float plrDmgMP5; + float plrDmgM203Grenade; + float plrDmgBuckshot; + float plrDmgCrossbowClient; + float plrDmgCrossbowMonster; + float plrDmgRPG; + float plrDmgGauss; + float plrDmgEgonNarrow; + float plrDmgEgonWide; + float plrDmgHornet; + float plrDmgHandGrenade; + float plrDmgSatchel; + float plrDmgTripmine; + +// weapons shared by monsters + float monDmg9MM; + float monDmgMP5; + float monDmg12MM; + float monDmgHornet; + +// health/suit charge + float suitchargerCapacity; + float batteryCapacity; + float healthchargerCapacity; + float healthkitCapacity; + float scientistHeal; + +// monster damage adj + float monHead; + float monChest; + float monStomach; + float monLeg; + float monArm; + +// player damage adj + float plrHead; + float plrChest; + float plrStomach; + float plrLeg; + float plrArm; +}; + +extern DLL_GLOBAL skilldata_t gSkillData; +float GetSkillCvar( char *pName ); + +extern DLL_GLOBAL int g_iSkillLevel; + +#define SKILL_EASY 1 +#define SKILL_MEDIUM 2 +#define SKILL_HARD 3 diff --git a/dlls/sound.cpp b/dlls/sound.cpp new file mode 100644 index 0000000..92a4c55 --- /dev/null +++ b/dlls/sound.cpp @@ -0,0 +1,1982 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// sound.cpp +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "player.h" +#include "talkmonster.h" +#include "gamerules.h" + +#if !defined ( _WIN32 ) +#include +#endif + + +static char *memfgets( byte *pMemFile, int fileSize, int &filePos, char *pBuffer, int bufferSize ); + + +// ==================== GENERIC AMBIENT SOUND ====================================== + +// runtime pitch shift and volume fadein/out structure + +// NOTE: IF YOU CHANGE THIS STRUCT YOU MUST CHANGE THE SAVE/RESTORE VERSION NUMBER +// SEE BELOW (in the typedescription for the class) +typedef struct dynpitchvol +{ + // NOTE: do not change the order of these parameters + // NOTE: unless you also change order of rgdpvpreset array elements! + int preset; + + int pitchrun; // pitch shift % when sound is running 0 - 255 + int pitchstart; // pitch shift % when sound stops or starts 0 - 255 + int spinup; // spinup time 0 - 100 + int spindown; // spindown time 0 - 100 + + int volrun; // volume change % when sound is running 0 - 10 + int volstart; // volume change % when sound stops or starts 0 - 10 + int fadein; // volume fade in time 0 - 100 + int fadeout; // volume fade out time 0 - 100 + + // Low Frequency Oscillator + int lfotype; // 0) off 1) square 2) triangle 3) random + int lforate; // 0 - 1000, how fast lfo osciallates + + int lfomodpitch; // 0-100 mod of current pitch. 0 is off. + int lfomodvol; // 0-100 mod of current volume. 0 is off. + + int cspinup; // each trigger hit increments counter and spinup pitch + + + int cspincount; + + int pitch; + int spinupsav; + int spindownsav; + int pitchfrac; + + int vol; + int fadeinsav; + int fadeoutsav; + int volfrac; + + int lfofrac; + int lfomult; + + +} dynpitchvol_t; + +#define CDPVPRESETMAX 27 + +// presets for runtime pitch and vol modulation of ambient sounds + +dynpitchvol_t rgdpvpreset[CDPVPRESETMAX] = +{ +// pitch pstart spinup spindwn volrun volstrt fadein fadeout lfotype lforate modptch modvol cspnup +{1, 255, 75, 95, 95, 10, 1, 50, 95, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{2, 255, 85, 70, 88, 10, 1, 20, 88, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{3, 255, 100, 50, 75, 10, 1, 10, 75, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{4, 100, 100, 0, 0, 10, 1, 90, 90, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{5, 100, 100, 0, 0, 10, 1, 80, 80, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{6, 100, 100, 0, 0, 10, 1, 50, 70, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{7, 100, 100, 0, 0, 5, 1, 40, 50, 1, 50, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{8, 100, 100, 0, 0, 5, 1, 40, 50, 1, 150, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{9, 100, 100, 0, 0, 5, 1, 40, 50, 1, 750, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{10,128, 100, 50, 75, 10, 1, 30, 40, 2, 8, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{11,128, 100, 50, 75, 10, 1, 30, 40, 2, 25, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{12,128, 100, 50, 75, 10, 1, 30, 40, 2, 70, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{13,50, 50, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{14,70, 70, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{15,90, 90, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{16,120, 120, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{17,180, 180, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{18,255, 255, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{19,200, 75, 90, 90, 10, 1, 50, 90, 2, 100, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{20,255, 75, 97, 90, 10, 1, 50, 90, 1, 40, 50, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{21,100, 100, 0, 0, 10, 1, 30, 50, 3, 15, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{22,160, 160, 0, 0, 10, 1, 50, 50, 3, 500, 25, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{23,255, 75, 88, 0, 10, 1, 40, 0, 0, 0, 0, 0, 5, 0,0,0,0,0,0,0,0,0,0}, +{24,200, 20, 95, 70, 10, 1, 70, 70, 3, 20, 50, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{25,180, 100, 50, 60, 10, 1, 40, 60, 2, 90, 100, 100, 0, 0,0,0,0,0,0,0,0,0,0}, +{26,60, 60, 0, 0, 10, 1, 40, 70, 3, 80, 20, 50, 0, 0,0,0,0,0,0,0,0,0,0}, +{27,128, 90, 10, 10, 10, 1, 20, 40, 1, 5, 10, 20, 0, 0,0,0,0,0,0,0,0,0,0} +}; + +class CAmbientGeneric : public CBaseEntity +{ +public: + void KeyValue( KeyValueData* pkvd); + void Spawn( void ); + void Precache( void ); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT RampThink( void ); + void InitModulationParms(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + float m_flAttenuation; // attenuation value + dynpitchvol_t m_dpv; + + BOOL m_fActive; // only TRUE when the entity is playing a looping sound + BOOL m_fLooping; // TRUE when the sound played will loop +}; + +LINK_ENTITY_TO_CLASS( ambient_generic, CAmbientGeneric ); +TYPEDESCRIPTION CAmbientGeneric::m_SaveData[] = +{ + DEFINE_FIELD( CAmbientGeneric, m_flAttenuation, FIELD_FLOAT ), + DEFINE_FIELD( CAmbientGeneric, m_fActive, FIELD_BOOLEAN ), + DEFINE_FIELD( CAmbientGeneric, m_fLooping, FIELD_BOOLEAN ), + + // HACKHACK - This is not really in the spirit of the save/restore design, but save this + // out as a binary data block. If the dynpitchvol_t is changed, old saved games will NOT + // load these correctly, so bump the save/restore version if you change the size of the struct + // The right way to do this is to split the input parms (read in keyvalue) into members and re-init this + // struct in Precache(), but it's unlikely that the struct will change, so it's not worth the time right now. + DEFINE_ARRAY( CAmbientGeneric, m_dpv, FIELD_CHARACTER, sizeof(dynpitchvol_t) ), +}; + +IMPLEMENT_SAVERESTORE( CAmbientGeneric, CBaseEntity ); + +// +// ambient_generic - general-purpose user-defined static sound +// +void CAmbientGeneric :: Spawn( void ) +{ +/* + -1 : "Default" + 0 : "Everywhere" + 200 : "Small Radius" + 125 : "Medium Radius" + 80 : "Large Radius" +*/ + + if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_EVERYWHERE) ) + { + m_flAttenuation = ATTN_NONE; + } + else if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_SMALLRADIUS) ) + { + m_flAttenuation = ATTN_IDLE; + } + else if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_MEDIUMRADIUS) ) + { + m_flAttenuation = ATTN_STATIC; + } + else if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_LARGERADIUS) ) + { + m_flAttenuation = ATTN_NORM; + } + else + {// if the designer didn't set a sound attenuation, default to one. + m_flAttenuation = ATTN_STATIC; + } + + char* szSoundFile = (char*) STRING(pev->message); + + if ( FStringNull( pev->message ) || strlen( szSoundFile ) < 1 ) + { + ALERT( at_error, "EMPTY AMBIENT AT: %f, %f, %f\n", pev->origin.x, pev->origin.y, pev->origin.z ); + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CAmbientGeneric::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + // Set up think function for dynamic modification + // of ambient sound's pitch or volume. Don't + // start thinking yet. + + SetThink(&CAmbientGeneric::RampThink); + pev->nextthink = 0; + + // allow on/off switching via 'use' function. + + SetUse ( &CAmbientGeneric::ToggleUse ); + + m_fActive = FALSE; + + if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_NOT_LOOPING ) ) + m_fLooping = FALSE; + else + m_fLooping = TRUE; + Precache( ); +} + + +void CAmbientGeneric :: Precache( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + + if ( !FStringNull( pev->message ) && strlen( szSoundFile ) > 1 ) + { + if (*szSoundFile != '!') + PRECACHE_SOUND(szSoundFile); + } + // init all dynamic modulation parms + InitModulationParms(); + + if ( !FBitSet (pev->spawnflags, AMBIENT_SOUND_START_SILENT ) ) + { + // start the sound ASAP + if (m_fLooping) + m_fActive = TRUE; + } + if ( m_fActive ) + { + UTIL_EmitAmbientSound ( ENT(pev), pev->origin, szSoundFile, + (m_dpv.vol * 0.01), m_flAttenuation, SND_SPAWNING, m_dpv.pitch); + + pev->nextthink = gpGlobals->time + 0.1; + } +} + +// RampThink - Think at 5hz if we are dynamically modifying +// pitch or volume of the playing sound. This function will +// ramp pitch and/or volume up or down, modify pitch/volume +// with lfo if active. + +void CAmbientGeneric :: RampThink( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + int pitch = m_dpv.pitch; + int vol = m_dpv.vol; + int flags = 0; + int fChanged = 0; // FALSE if pitch and vol remain unchanged this round + int prev; + + if (!m_dpv.spinup && !m_dpv.spindown && !m_dpv.fadein && !m_dpv.fadeout && !m_dpv.lfotype) + return; // no ramps or lfo, stop thinking + + // ============== + // pitch envelope + // ============== + if (m_dpv.spinup || m_dpv.spindown) + { + prev = m_dpv.pitchfrac >> 8; + + if (m_dpv.spinup > 0) + m_dpv.pitchfrac += m_dpv.spinup; + else if (m_dpv.spindown > 0) + m_dpv.pitchfrac -= m_dpv.spindown; + + pitch = m_dpv.pitchfrac >> 8; + + if (pitch > m_dpv.pitchrun) + { + pitch = m_dpv.pitchrun; + m_dpv.spinup = 0; // done with ramp up + } + + if (pitch < m_dpv.pitchstart) + { + pitch = m_dpv.pitchstart; + m_dpv.spindown = 0; // done with ramp down + + // shut sound off + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + + // return without setting nextthink + return; + } + + if (pitch > 255) pitch = 255; + if (pitch < 1) pitch = 1; + + m_dpv.pitch = pitch; + + fChanged |= (prev != pitch); + flags |= SND_CHANGE_PITCH; + } + + // ================== + // amplitude envelope + // ================== + if (m_dpv.fadein || m_dpv.fadeout) + { + prev = m_dpv.volfrac >> 8; + + if (m_dpv.fadein > 0) + m_dpv.volfrac += m_dpv.fadein; + else if (m_dpv.fadeout > 0) + m_dpv.volfrac -= m_dpv.fadeout; + + vol = m_dpv.volfrac >> 8; + + if (vol > m_dpv.volrun) + { + vol = m_dpv.volrun; + m_dpv.fadein = 0; // done with ramp up + } + + if (vol < m_dpv.volstart) + { + vol = m_dpv.volstart; + m_dpv.fadeout = 0; // done with ramp down + + // shut sound off + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + + // return without setting nextthink + return; + } + + if (vol > 100) vol = 100; + if (vol < 1) vol = 1; + + m_dpv.vol = vol; + + fChanged |= (prev != vol); + flags |= SND_CHANGE_VOL; + } + + // =================== + // pitch/amplitude LFO + // =================== + if (m_dpv.lfotype) + { + int pos; + + if (m_dpv.lfofrac > 0x6fffffff) + m_dpv.lfofrac = 0; + + // update lfo, lfofrac/255 makes a triangle wave 0-255 + m_dpv.lfofrac += m_dpv.lforate; + pos = m_dpv.lfofrac >> 8; + + if (m_dpv.lfofrac < 0) + { + m_dpv.lfofrac = 0; + m_dpv.lforate = abs(m_dpv.lforate); + pos = 0; + } + else if (pos > 255) + { + pos = 255; + m_dpv.lfofrac = (255 << 8); + m_dpv.lforate = -abs(m_dpv.lforate); + } + + switch(m_dpv.lfotype) + { + case LFO_SQUARE: + if (pos < 128) + m_dpv.lfomult = 255; + else + m_dpv.lfomult = 0; + + break; + case LFO_RANDOM: + if (pos == 255) + m_dpv.lfomult = RANDOM_LONG(0, 255); + break; + case LFO_TRIANGLE: + default: + m_dpv.lfomult = pos; + break; + } + + if (m_dpv.lfomodpitch) + { + prev = pitch; + + // pitch 0-255 + pitch += ((m_dpv.lfomult - 128) * m_dpv.lfomodpitch) / 100; + + if (pitch > 255) pitch = 255; + if (pitch < 1) pitch = 1; + + + fChanged |= (prev != pitch); + flags |= SND_CHANGE_PITCH; + } + + if (m_dpv.lfomodvol) + { + // vol 0-100 + prev = vol; + + vol += ((m_dpv.lfomult - 128) * m_dpv.lfomodvol) / 100; + + if (vol > 100) vol = 100; + if (vol < 0) vol = 0; + + fChanged |= (prev != vol); + flags |= SND_CHANGE_VOL; + } + + } + + // Send update to playing sound only if we actually changed + // pitch or volume in this routine. + + if (flags && fChanged) + { + if (pitch == PITCH_NORM) + pitch = PITCH_NORM + 1; // don't send 'no pitch' ! + + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + (vol * 0.01), m_flAttenuation, flags, pitch); + } + + // update ramps at 5hz + pev->nextthink = gpGlobals->time + 0.2; + return; +} + +// Init all ramp params in preparation to +// play a new sound + +void CAmbientGeneric :: InitModulationParms(void) +{ + int pitchinc; + + m_dpv.volrun = pev->health * 10; // 0 - 100 + if (m_dpv.volrun > 100) m_dpv.volrun = 100; + if (m_dpv.volrun < 0) m_dpv.volrun = 0; + + // get presets + if (m_dpv.preset != 0 && m_dpv.preset <= CDPVPRESETMAX) + { + // load preset values + m_dpv = rgdpvpreset[m_dpv.preset - 1]; + + // fixup preset values, just like + // fixups in KeyValue routine. + if (m_dpv.spindown > 0) + m_dpv.spindown = (101 - m_dpv.spindown) * 64; + if (m_dpv.spinup > 0) + m_dpv.spinup = (101 - m_dpv.spinup) * 64; + + m_dpv.volstart *= 10; + m_dpv.volrun *= 10; + + if (m_dpv.fadein > 0) + m_dpv.fadein = (101 - m_dpv.fadein) * 64; + if (m_dpv.fadeout > 0) + m_dpv.fadeout = (101 - m_dpv.fadeout) * 64; + + m_dpv.lforate *= 256; + + m_dpv.fadeinsav = m_dpv.fadein; + m_dpv.fadeoutsav = m_dpv.fadeout; + m_dpv.spinupsav = m_dpv.spinup; + m_dpv.spindownsav = m_dpv.spindown; + } + + m_dpv.fadein = m_dpv.fadeinsav; + m_dpv.fadeout = 0; + + if (m_dpv.fadein) + m_dpv.vol = m_dpv.volstart; + else + m_dpv.vol = m_dpv.volrun; + + m_dpv.spinup = m_dpv.spinupsav; + m_dpv.spindown = 0; + + if (m_dpv.spinup) + m_dpv.pitch = m_dpv.pitchstart; + else + m_dpv.pitch = m_dpv.pitchrun; + + if (m_dpv.pitch == 0) + m_dpv.pitch = PITCH_NORM; + + m_dpv.pitchfrac = m_dpv.pitch << 8; + m_dpv.volfrac = m_dpv.vol << 8; + + m_dpv.lfofrac = 0; + m_dpv.lforate = abs(m_dpv.lforate); + + m_dpv.cspincount = 1; + + if (m_dpv.cspinup) + { + pitchinc = (255 - m_dpv.pitchstart) / m_dpv.cspinup; + + m_dpv.pitchrun = m_dpv.pitchstart + pitchinc; + if (m_dpv.pitchrun > 255) m_dpv.pitchrun = 255; + } + + if ((m_dpv.spinupsav || m_dpv.spindownsav || (m_dpv.lfotype && m_dpv.lfomodpitch)) + && (m_dpv.pitch == PITCH_NORM)) + m_dpv.pitch = PITCH_NORM + 1; // must never send 'no pitch' as first pitch + // if we intend to pitch shift later! +} + +// +// ToggleUse - turns an ambient sound on or off. If the +// ambient is a looping sound, mark sound as active (m_fActive) +// if it's playing, innactive if not. If the sound is not +// a looping sound, never mark it as active. +// +void CAmbientGeneric :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + char* szSoundFile = (char*) STRING(pev->message); + float fraction; + + if ( useType != USE_TOGGLE ) + { + if ( (m_fActive && useType == USE_ON) || (!m_fActive && useType == USE_OFF) ) + return; + } + // Directly change pitch if arg passed. Only works if sound is already playing. + + if (useType == USE_SET && m_fActive) // Momentary buttons will pass down a float in here + { + + fraction = value; + + if ( fraction > 1.0 ) + fraction = 1.0; + if (fraction < 0.0) + fraction = 0.01; + + m_dpv.pitch = fraction * 255; + + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_CHANGE_PITCH, m_dpv.pitch); + + return; + } + + // Toggle + + // m_fActive is TRUE only if a looping sound is playing. + + if ( m_fActive ) + {// turn sound off + + if (m_dpv.cspinup) + { + // Don't actually shut off. Each toggle causes + // incremental spinup to max pitch + + if (m_dpv.cspincount <= m_dpv.cspinup) + { + int pitchinc; + + // start a new spinup + m_dpv.cspincount++; + + pitchinc = (255 - m_dpv.pitchstart) / m_dpv.cspinup; + + m_dpv.spinup = m_dpv.spinupsav; + m_dpv.spindown = 0; + + m_dpv.pitchrun = m_dpv.pitchstart + pitchinc * m_dpv.cspincount; + if (m_dpv.pitchrun > 255) m_dpv.pitchrun = 255; + + pev->nextthink = gpGlobals->time + 0.1; + } + + } + else + { + m_fActive = FALSE; + + // HACKHACK - this makes the code in Precache() work properly after a save/restore + pev->spawnflags |= AMBIENT_SOUND_START_SILENT; + + if (m_dpv.spindownsav || m_dpv.fadeoutsav) + { + // spin it down (or fade it) before shutoff if spindown is set + m_dpv.spindown = m_dpv.spindownsav; + m_dpv.spinup = 0; + + m_dpv.fadeout = m_dpv.fadeoutsav; + m_dpv.fadein = 0; + pev->nextthink = gpGlobals->time + 0.1; + } + else + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + } + } + else + {// turn sound on + + // only toggle if this is a looping sound. If not looping, each + // trigger will cause the sound to play. If the sound is still + // playing from a previous trigger press, it will be shut off + // and then restarted. + + if (m_fLooping) + m_fActive = TRUE; + else + // shut sound off now - may be interrupting a long non-looping sound + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + + // init all ramp params for startup + + InitModulationParms(); + + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + (m_dpv.vol * 0.01), m_flAttenuation, 0, m_dpv.pitch); + + pev->nextthink = gpGlobals->time + 0.1; + + } +} +// KeyValue - load keyvalue pairs into member data of the +// ambient generic. NOTE: called BEFORE spawn! + +void CAmbientGeneric :: KeyValue( KeyValueData *pkvd ) +{ + // NOTE: changing any of the modifiers in this code + // NOTE: also requires changing InitModulationParms code. + + // preset + if (FStrEq(pkvd->szKeyName, "preset")) + { + m_dpv.preset = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + + // pitchrun + else if (FStrEq(pkvd->szKeyName, "pitch")) + { + m_dpv.pitchrun = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + if (m_dpv.pitchrun > 255) m_dpv.pitchrun = 255; + if (m_dpv.pitchrun < 0) m_dpv.pitchrun = 0; + } + + // pitchstart + else if (FStrEq(pkvd->szKeyName, "pitchstart")) + { + m_dpv.pitchstart = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + if (m_dpv.pitchstart > 255) m_dpv.pitchstart = 255; + if (m_dpv.pitchstart < 0) m_dpv.pitchstart = 0; + } + + // spinup + else if (FStrEq(pkvd->szKeyName, "spinup")) + { + m_dpv.spinup = atoi(pkvd->szValue); + + if (m_dpv.spinup > 100) m_dpv.spinup = 100; + if (m_dpv.spinup < 0) m_dpv.spinup = 0; + + if (m_dpv.spinup > 0) + m_dpv.spinup = (101 - m_dpv.spinup) * 64; + m_dpv.spinupsav = m_dpv.spinup; + pkvd->fHandled = TRUE; + } + + // spindown + else if (FStrEq(pkvd->szKeyName, "spindown")) + { + m_dpv.spindown = atoi(pkvd->szValue); + + if (m_dpv.spindown > 100) m_dpv.spindown = 100; + if (m_dpv.spindown < 0) m_dpv.spindown = 0; + + if (m_dpv.spindown > 0) + m_dpv.spindown = (101 - m_dpv.spindown) * 64; + m_dpv.spindownsav = m_dpv.spindown; + pkvd->fHandled = TRUE; + } + + // volstart + else if (FStrEq(pkvd->szKeyName, "volstart")) + { + m_dpv.volstart = atoi(pkvd->szValue); + + if (m_dpv.volstart > 10) m_dpv.volstart = 10; + if (m_dpv.volstart < 0) m_dpv.volstart = 0; + + m_dpv.volstart *= 10; // 0 - 100 + + pkvd->fHandled = TRUE; + } + + // fadein + else if (FStrEq(pkvd->szKeyName, "fadein")) + { + m_dpv.fadein = atoi(pkvd->szValue); + + if (m_dpv.fadein > 100) m_dpv.fadein = 100; + if (m_dpv.fadein < 0) m_dpv.fadein = 0; + + if (m_dpv.fadein > 0) + m_dpv.fadein = (101 - m_dpv.fadein) * 64; + m_dpv.fadeinsav = m_dpv.fadein; + pkvd->fHandled = TRUE; + } + + // fadeout + else if (FStrEq(pkvd->szKeyName, "fadeout")) + { + m_dpv.fadeout = atoi(pkvd->szValue); + + if (m_dpv.fadeout > 100) m_dpv.fadeout = 100; + if (m_dpv.fadeout < 0) m_dpv.fadeout = 0; + + if (m_dpv.fadeout > 0) + m_dpv.fadeout = (101 - m_dpv.fadeout) * 64; + m_dpv.fadeoutsav = m_dpv.fadeout; + pkvd->fHandled = TRUE; + } + + // lfotype + else if (FStrEq(pkvd->szKeyName, "lfotype")) + { + m_dpv.lfotype = atoi(pkvd->szValue); + if (m_dpv.lfotype > 4) m_dpv.lfotype = LFO_TRIANGLE; + pkvd->fHandled = TRUE; + } + + // lforate + else if (FStrEq(pkvd->szKeyName, "lforate")) + { + m_dpv.lforate = atoi(pkvd->szValue); + + if (m_dpv.lforate > 1000) m_dpv.lforate = 1000; + if (m_dpv.lforate < 0) m_dpv.lforate = 0; + + m_dpv.lforate *= 256; + + pkvd->fHandled = TRUE; + } + // lfomodpitch + else if (FStrEq(pkvd->szKeyName, "lfomodpitch")) + { + m_dpv.lfomodpitch = atoi(pkvd->szValue); + if (m_dpv.lfomodpitch > 100) m_dpv.lfomodpitch = 100; + if (m_dpv.lfomodpitch < 0) m_dpv.lfomodpitch = 0; + + + pkvd->fHandled = TRUE; + } + + // lfomodvol + else if (FStrEq(pkvd->szKeyName, "lfomodvol")) + { + m_dpv.lfomodvol = atoi(pkvd->szValue); + if (m_dpv.lfomodvol > 100) m_dpv.lfomodvol = 100; + if (m_dpv.lfomodvol < 0) m_dpv.lfomodvol = 0; + + pkvd->fHandled = TRUE; + } + + // cspinup + else if (FStrEq(pkvd->szKeyName, "cspinup")) + { + m_dpv.cspinup = atoi(pkvd->szValue); + if (m_dpv.cspinup > 100) m_dpv.cspinup = 100; + if (m_dpv.cspinup < 0) m_dpv.cspinup = 0; + + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +// =================== ROOM SOUND FX ========================================== + +class CEnvSound : public CPointEntity +{ +public: + void KeyValue( KeyValueData* pkvd); + void Spawn( void ); + + void Think( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + float m_flRadius; + float m_flRoomtype; +}; + +LINK_ENTITY_TO_CLASS( env_sound, CEnvSound ); +TYPEDESCRIPTION CEnvSound::m_SaveData[] = +{ + DEFINE_FIELD( CEnvSound, m_flRadius, FIELD_FLOAT ), + DEFINE_FIELD( CEnvSound, m_flRoomtype, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CEnvSound, CBaseEntity ); + + +void CEnvSound :: KeyValue( KeyValueData *pkvd ) +{ + + if (FStrEq(pkvd->szKeyName, "radius")) + { + m_flRadius = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + if (FStrEq(pkvd->szKeyName, "roomtype")) + { + m_flRoomtype = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } +} + +// returns TRUE if the given sound entity (pev) is in range +// and can see the given player entity (pevTarget) + +BOOL FEnvSoundInRange(entvars_t *pev, entvars_t *pevTarget, float *pflRange) +{ + CEnvSound *pSound = GetClassPtr( (CEnvSound *)pev ); + Vector vecSpot1 = pev->origin + pev->view_ofs; + Vector vecSpot2 = pevTarget->origin + pevTarget->view_ofs; + Vector vecRange; + float flRange; + TraceResult tr; + + UTIL_TraceLine(vecSpot1, vecSpot2, ignore_monsters, ENT(pev), &tr); + + // check if line of sight crosses water boundary, or is blocked + + if ((tr.fInOpen && tr.fInWater) || tr.flFraction != 1) + return FALSE; + + // calc range from sound entity to player + + vecRange = tr.vecEndPos - vecSpot1; + flRange = vecRange.Length(); + + if (pSound->m_flRadius < flRange) + return FALSE; + + if (pflRange) + *pflRange = flRange; + + return TRUE; +} + +// +// A client that is visible and in range of a sound entity will +// have its room_type set by that sound entity. If two or more +// sound entities are contending for a client, then the nearest +// sound entity to the client will set the client's room_type. +// A client's room_type will remain set to its prior value until +// a new in-range, visible sound entity resets a new room_type. +// + +// CONSIDER: if player in water state, autoset roomtype to 14,15 or 16. + +void CEnvSound :: Think( void ) +{ + // get pointer to client if visible; FIND_CLIENT_IN_PVS will + // cycle through visible clients on consecutive calls. + + edict_t *pentPlayer = FIND_CLIENT_IN_PVS(edict()); + CBasePlayer *pPlayer = NULL; + + if (FNullEnt(pentPlayer)) + goto env_sound_Think_slow; // no player in pvs of sound entity, slow it down + + pPlayer = GetClassPtr( (CBasePlayer *)VARS(pentPlayer)); + float flRange; + + // check to see if this is the sound entity that is + // currently affecting this player + + if(!FNullEnt(pPlayer->m_pentSndLast) && (pPlayer->m_pentSndLast == ENT(pev))) { + + // this is the entity currently affecting player, check + // for validity + + if (pPlayer->m_flSndRoomtype != 0 && pPlayer->m_flSndRange != 0) { + + // we're looking at a valid sound entity affecting + // player, make sure it's still valid, update range + + if (FEnvSoundInRange(pev, VARS(pentPlayer), &flRange)) { + pPlayer->m_flSndRange = flRange; + goto env_sound_Think_fast; + } else { + + // current sound entity affecting player is no longer valid, + // flag this state by clearing room_type and range. + // NOTE: we do not actually change the player's room_type + // NOTE: until we have a new valid room_type to change it to. + + pPlayer->m_flSndRange = 0; + pPlayer->m_flSndRoomtype = 0; + goto env_sound_Think_slow; + } + } else { + // entity is affecting player but is out of range, + // wait passively for another entity to usurp it... + goto env_sound_Think_slow; + } + } + + // if we got this far, we're looking at an entity that is contending + // for current player sound. the closest entity to player wins. + + if (FEnvSoundInRange(pev, VARS(pentPlayer), &flRange)) + { + if (flRange < pPlayer->m_flSndRange || pPlayer->m_flSndRange == 0) + { + // new entity is closer to player, so it wins. + pPlayer->m_pentSndLast = ENT(pev); + pPlayer->m_flSndRoomtype = m_flRoomtype; + pPlayer->m_flSndRange = flRange; + + // send room_type command to player's server. + // this should be a rare event - once per change of room_type + // only! + + //CLIENT_COMMAND(pentPlayer, "room_type %f", m_flRoomtype); + + MESSAGE_BEGIN( MSG_ONE, SVC_ROOMTYPE, NULL, pentPlayer ); // use the magic #1 for "one client" + WRITE_SHORT( (short)m_flRoomtype ); // sequence number + MESSAGE_END(); + + // crank up nextthink rate for new active sound entity + // by falling through to think_fast... + } + // player is not closer to the contending sound entity, + // just fall through to think_fast. this effectively + // cranks up the think_rate of entities near the player. + } + + // player is in pvs of sound entity, but either not visible or + // not in range. do nothing, fall through to think_fast... + +env_sound_Think_fast: + pev->nextthink = gpGlobals->time + 0.25; + return; + +env_sound_Think_slow: + pev->nextthink = gpGlobals->time + 0.75; + return; +} + +// +// env_sound - spawn a sound entity that will set player roomtype +// when player moves in range and sight. +// +// +void CEnvSound :: Spawn( ) +{ + // spread think times + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(0.0, 0.5); +} + +// ==================== SENTENCE GROUPS, UTILITY FUNCTIONS ====================================== + +#define CSENTENCE_LRU_MAX 32 // max number of elements per sentence group + +// group of related sentences + +typedef struct sentenceg +{ + char szgroupname[CBSENTENCENAME_MAX]; + int count; + unsigned char rgblru[CSENTENCE_LRU_MAX]; + +} SENTENCEG; + +#define CSENTENCEG_MAX 200 // max number of sentence groups +// globals + +SENTENCEG rgsentenceg[CSENTENCEG_MAX]; +int fSentencesInit = FALSE; + +char gszallsentencenames[CVOXFILESENTENCEMAX][CBSENTENCENAME_MAX]; +int gcallsentences = 0; + +// randomize list of sentence name indices + +void USENTENCEG_InitLRU(unsigned char *plru, int count) +{ + int i, j, k; + unsigned char temp; + + if (!fSentencesInit) + return; + + if (count > CSENTENCE_LRU_MAX) + count = CSENTENCE_LRU_MAX; + + for (i = 0; i < count; i++) + plru[i] = (unsigned char) i; + + // randomize array + for (i = 0; i < (count * 4); i++) + { + j = RANDOM_LONG(0,count-1); + k = RANDOM_LONG(0,count-1); + temp = plru[j]; + plru[j] = plru[k]; + plru[k] = temp; + } +} + +// ignore lru. pick next sentence from sentence group. Go in order until we hit the last sentence, +// then repeat list if freset is true. If freset is false, then repeat last sentence. +// ipick is passed in as the requested sentence ordinal. +// ipick 'next' is returned. +// return of -1 indicates an error. + +int USENTENCEG_PickSequential(int isentenceg, char *szfound, int ipick, int freset) +{ + char *szgroupname; + unsigned char count; + char sznum[8]; + + if (!fSentencesInit) + return -1; + + if (isentenceg < 0) + return -1; + + szgroupname = rgsentenceg[isentenceg].szgroupname; + count = rgsentenceg[isentenceg].count; + + if (count == 0) + return -1; + + if (ipick >= count) + ipick = count-1; + + strcpy(szfound, "!"); + strcat(szfound, szgroupname); + sprintf(sznum, "%d", ipick); + strcat(szfound, sznum); + + if (ipick >= count) + { + if (freset) + // reset at end of list + return 0; + else + return count; + } + + return ipick + 1; +} + + + +// pick a random sentence from rootname0 to rootnameX. +// picks from the rgsentenceg[isentenceg] least +// recently used, modifies lru array. returns the sentencename. +// note, lru must be seeded with 0-n randomized sentence numbers, with the +// rest of the lru filled with -1. The first integer in the lru is +// actually the size of the list. Returns ipick, the ordinal +// of the picked sentence within the group. + +int USENTENCEG_Pick(int isentenceg, char *szfound) +{ + char *szgroupname; + unsigned char *plru; + unsigned char i; + unsigned char count; + char sznum[8]; + unsigned char ipick; + int ffound = FALSE; + + if (!fSentencesInit) + return -1; + + if (isentenceg < 0) + return -1; + + szgroupname = rgsentenceg[isentenceg].szgroupname; + count = rgsentenceg[isentenceg].count; + plru = rgsentenceg[isentenceg].rgblru; + + while (!ffound) + { + for (i = 0; i < count; i++) + if (plru[i] != 0xFF) + { + ipick = plru[i]; + plru[i] = 0xFF; + ffound = TRUE; + break; + } + + if (!ffound) + USENTENCEG_InitLRU(plru, count); + else + { + strcpy(szfound, "!"); + strcat(szfound, szgroupname); + sprintf(sznum, "%d", ipick); + strcat(szfound, sznum); + return ipick; + } + } + return -1; +} + +// ===================== SENTENCE GROUPS, MAIN ROUTINES ======================== + +// Given sentence group rootname (name without number suffix), +// get sentence group index (isentenceg). Returns -1 if no such name. + +int SENTENCEG_GetIndex(const char *szgroupname) +{ + int i; + + if (!fSentencesInit || !szgroupname) + return -1; + + // search rgsentenceg for match on szgroupname + + i = 0; + while (rgsentenceg[i].count) + { + if (!strcmp(szgroupname, rgsentenceg[i].szgroupname)) + return i; + i++; + } + + return -1; +} + +// given sentence group index, play random sentence for given entity. +// returns ipick - which sentence was picked to +// play from the group. Ipick is only needed if you plan on stopping +// the sound before playback is done (see SENTENCEG_Stop). + +int SENTENCEG_PlayRndI(edict_t *entity, int isentenceg, + float volume, float attenuation, int flags, int pitch) +{ + char name[64]; + int ipick; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + ipick = USENTENCEG_Pick(isentenceg, name); + if (ipick > 0 && name) + EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch); + return ipick; +} + +// same as above, but takes sentence group name instead of index + +int SENTENCEG_PlayRndSz(edict_t *entity, const char *szgroupname, + float volume, float attenuation, int flags, int pitch) +{ + char name[64]; + int ipick; + int isentenceg; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + isentenceg = SENTENCEG_GetIndex(szgroupname); + if (isentenceg < 0) + { + ALERT( at_console, "No such sentence group %s\n", szgroupname ); + return -1; + } + + ipick = USENTENCEG_Pick(isentenceg, name); + if (ipick >= 0 && name[0]) + EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch); + + return ipick; +} + +// play sentences in sequential order from sentence group. Reset after last sentence. + +int SENTENCEG_PlaySequentialSz(edict_t *entity, const char *szgroupname, + float volume, float attenuation, int flags, int pitch, int ipick, int freset) +{ + char name[64]; + int ipicknext; + int isentenceg; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + isentenceg = SENTENCEG_GetIndex(szgroupname); + if (isentenceg < 0) + return -1; + + ipicknext = USENTENCEG_PickSequential(isentenceg, name, ipick, freset); + if (ipicknext >= 0 && name[0]) + EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch); + return ipicknext; +} + + +// for this entity, for the given sentence within the sentence group, stop +// the sentence. + +void SENTENCEG_Stop(edict_t *entity, int isentenceg, int ipick) +{ + char buffer[64]; + char sznum[8]; + + if (!fSentencesInit) + return; + + if (isentenceg < 0 || ipick < 0) + return; + + strcpy(buffer, "!"); + strcat(buffer, rgsentenceg[isentenceg].szgroupname); + sprintf(sznum, "%d", ipick); + strcat(buffer, sznum); + + STOP_SOUND(entity, CHAN_VOICE, buffer); +} + +// open sentences.txt, scan for groups, build rgsentenceg +// Should be called from world spawn, only works on the +// first call and is ignored subsequently. + +void SENTENCEG_Init() +{ + char buffer[512]; + char szgroup[64]; + int i, j; + int isentencegs; + + if (fSentencesInit) + return; + + memset(gszallsentencenames, 0, CVOXFILESENTENCEMAX * CBSENTENCENAME_MAX); + gcallsentences = 0; + + memset(rgsentenceg, 0, CSENTENCEG_MAX * sizeof(SENTENCEG)); + memset(buffer, 0, 512); + memset(szgroup, 0, 64); + isentencegs = -1; + + + int filePos = 0, fileSize; + byte *pMemFile = g_engfuncs.pfnLoadFileForMe( "sound/sentences.txt", &fileSize ); + if ( !pMemFile ) + return; + + // for each line in the file... + while ( memfgets(pMemFile, fileSize, filePos, buffer, 511) != NULL ) + { + // skip whitespace + i = 0; + while(buffer[i] && buffer[i] == ' ') + i++; + + if (!buffer[i]) + continue; + + if (buffer[i] == '/' || !isalpha(buffer[i])) + continue; + + // get sentence name + j = i; + while (buffer[j] && buffer[j] != ' ') + j++; + + if (!buffer[j]) + continue; + + if (gcallsentences > CVOXFILESENTENCEMAX) + { + ALERT (at_error, "Too many sentences in sentences.txt!\n"); + break; + } + + // null-terminate name and save in sentences array + buffer[j] = 0; + const char *pString = buffer + i; + + if ( strlen( pString ) >= CBSENTENCENAME_MAX ) + ALERT( at_warning, "Sentence %s longer than %d letters\n", pString, CBSENTENCENAME_MAX-1 ); + + strcpy( gszallsentencenames[gcallsentences++], pString ); + + j--; + if (j <= i) + continue; + if (!isdigit(buffer[j])) + continue; + + // cut out suffix numbers + while (j > i && isdigit(buffer[j])) + j--; + + if (j <= i) + continue; + + buffer[j+1] = 0; + + // if new name doesn't match previous group name, + // make a new group. + + if (strcmp(szgroup, &(buffer[i]))) + { + // name doesn't match with prev name, + // copy name into group, init count to 1 + isentencegs++; + if (isentencegs >= CSENTENCEG_MAX) + { + ALERT (at_error, "Too many sentence groups in sentences.txt!\n"); + break; + } + + strcpy(rgsentenceg[isentencegs].szgroupname, &(buffer[i])); + rgsentenceg[isentencegs].count = 1; + + strcpy(szgroup, &(buffer[i])); + + continue; + } + else + { + //name matches with previous, increment group count + if (isentencegs >= 0) + rgsentenceg[isentencegs].count++; + } + } + + g_engfuncs.pfnFreeFile( pMemFile ); + + fSentencesInit = TRUE; + + // init lru lists + + i = 0; + + while (rgsentenceg[i].count && i < CSENTENCEG_MAX) + { + USENTENCEG_InitLRU(&(rgsentenceg[i].rgblru[0]), rgsentenceg[i].count); + i++; + } + +} + +// convert sentence (sample) name to !sentencenum, return !sentencenum + +int SENTENCEG_Lookup(const char *sample, char *sentencenum) +{ + char sznum[8]; + + int i; + // this is a sentence name; lookup sentence number + // and give to engine as string. + for (i = 0; i < gcallsentences; i++) + if (!stricmp(gszallsentencenames[i], sample+1)) + { + if (sentencenum) + { + strcpy(sentencenum, "!"); + sprintf(sznum, "%d", i); + strcat(sentencenum, sznum); + } + return i; + } + // sentence name not found! + return -1; +} + +void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volume, float attenuation, + int flags, int pitch) +{ + if (sample && *sample == '!') + { + char name[32]; + if (SENTENCEG_Lookup(sample, name) >= 0) + EMIT_SOUND_DYN2(entity, channel, name, volume, attenuation, flags, pitch); + else + ALERT( at_aiconsole, "Unable to find %s in sentences.txt\n", sample ); + } + else + EMIT_SOUND_DYN2(entity, channel, sample, volume, attenuation, flags, pitch); +} + +// play a specific sentence over the HEV suit speaker - just pass player entity, and !sentencename + +void EMIT_SOUND_SUIT(edict_t *entity, const char *sample) +{ + float fvol; + int pitch = PITCH_NORM; + + fvol = CVAR_GET_FLOAT("suitvolume"); + if (RANDOM_LONG(0,1)) + pitch = RANDOM_LONG(0,6) + 98; + + if (fvol > 0.05) + EMIT_SOUND_DYN(entity, CHAN_STATIC, sample, fvol, ATTN_NORM, 0, pitch); +} + +// play a sentence, randomly selected from the passed in group id, over the HEV suit speaker + +void EMIT_GROUPID_SUIT(edict_t *entity, int isentenceg) +{ + float fvol; + int pitch = PITCH_NORM; + + fvol = CVAR_GET_FLOAT("suitvolume"); + if (RANDOM_LONG(0,1)) + pitch = RANDOM_LONG(0,6) + 98; + + if (fvol > 0.05) + SENTENCEG_PlayRndI(entity, isentenceg, fvol, ATTN_NORM, 0, pitch); +} + +// play a sentence, randomly selected from the passed in groupname + +void EMIT_GROUPNAME_SUIT(edict_t *entity, const char *groupname) +{ + float fvol; + int pitch = PITCH_NORM; + + fvol = CVAR_GET_FLOAT("suitvolume"); + if (RANDOM_LONG(0,1)) + pitch = RANDOM_LONG(0,6) + 98; + + if (fvol > 0.05) + SENTENCEG_PlayRndSz(entity, groupname, fvol, ATTN_NORM, 0, pitch); +} + +// ===================== MATERIAL TYPE DETECTION, MAIN ROUTINES ======================== +// +// Used to detect the texture the player is standing on, map the +// texture name to a material type. Play footstep sound based +// on material type. + +int fTextureTypeInit = FALSE; + +#define CTEXTURESMAX 512 // max number of textures loaded + +int gcTextures = 0; +char grgszTextureName[CTEXTURESMAX][CBTEXTURENAMEMAX]; // texture names +char grgchTextureType[CTEXTURESMAX]; // parallel array of texture types + +// open materials.txt, get size, alloc space, +// save in array. Only works first time called, +// ignored on subsequent calls. + +static char *memfgets( byte *pMemFile, int fileSize, int &filePos, char *pBuffer, int bufferSize ) +{ + // Bullet-proofing + if ( !pMemFile || !pBuffer ) + return NULL; + + if ( filePos >= fileSize ) + return NULL; + + int i = filePos; + int last = fileSize; + + // fgets always NULL terminates, so only read bufferSize-1 characters + if ( last - filePos > (bufferSize-1) ) + last = filePos + (bufferSize-1); + + int stop = 0; + + // Stop at the next newline (inclusive) or end of buffer + while ( i < last && !stop ) + { + if ( pMemFile[i] == '\n' ) + stop = 1; + i++; + } + + + // If we actually advanced the pointer, copy it over + if ( i != filePos ) + { + // We read in size bytes + int size = i - filePos; + // copy it out + memcpy( pBuffer, pMemFile + filePos, sizeof(byte)*size ); + + // If the buffer isn't full, terminate (this is always true) + if ( size < bufferSize ) + pBuffer[size] = 0; + + // Update file pointer + filePos = i; + return pBuffer; + } + + // No data read, bail + return NULL; +} + + +void TEXTURETYPE_Init() +{ + char buffer[512]; + int i, j; + byte *pMemFile; + int fileSize, filePos = 0; + + if (fTextureTypeInit) + return; + + memset(&(grgszTextureName[0][0]), 0, CTEXTURESMAX * CBTEXTURENAMEMAX); + memset(grgchTextureType, 0, CTEXTURESMAX); + + gcTextures = 0; + memset(buffer, 0, 512); + + pMemFile = g_engfuncs.pfnLoadFileForMe( "sound/materials.txt", &fileSize ); + if ( !pMemFile ) + return; + + // for each line in the file... + while (memfgets(pMemFile, fileSize, filePos, buffer, 511) != NULL && (gcTextures < CTEXTURESMAX)) + { + // skip whitespace + i = 0; + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // skip comment lines + if (buffer[i] == '/' || !isalpha(buffer[i])) + continue; + + // get texture type + grgchTextureType[gcTextures] = toupper(buffer[i++]); + + // skip whitespace + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // get sentence name + j = i; + while (buffer[j] && !isspace(buffer[j])) + j++; + + if (!buffer[j]) + continue; + + // null-terminate name and save in sentences array + j = min (j, CBTEXTURENAMEMAX-1+i); + buffer[j] = 0; + strcpy(&(grgszTextureName[gcTextures++][0]), &(buffer[i])); + } + + g_engfuncs.pfnFreeFile( pMemFile ); + + fTextureTypeInit = TRUE; +} + +// given texture name, find texture type +// if not found, return type 'concrete' + +// NOTE: this routine should ONLY be called if the +// current texture under the player changes! + +char TEXTURETYPE_Find(char *name) +{ + // CONSIDER: pre-sort texture names and perform faster binary search here + + for (int i = 0; i < gcTextures; i++) + { + if (!strnicmp(name, &(grgszTextureName[i][0]), CBTEXTURENAMEMAX-1)) + return (grgchTextureType[i]); + } + + return CHAR_TEX_CONCRETE; +} + +// play a strike sound based on the texture that was hit by the attack traceline. VecSrc/VecEnd are the +// original traceline endpoints used by the attacker, iBulletType is the type of bullet that hit the texture. +// returns volume of strike instrument (crowbar) to play + +float TEXTURETYPE_PlaySound(TraceResult *ptr, Vector vecSrc, Vector vecEnd, int iBulletType) +{ +// hit the world, try to play sound based on texture material type + + char chTextureType; + float fvol; + float fvolbar; + char szbuffer[64]; + const char *pTextureName; + float rgfl1[3]; + float rgfl2[3]; + char *rgsz[4]; + int cnt; + float fattn = ATTN_NORM; + + if ( !g_pGameRules->PlayTextureSounds() ) + return 0.0; + + CBaseEntity *pEntity = CBaseEntity::Instance(ptr->pHit); + + chTextureType = 0; + + if (pEntity && pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE) + // hit body + chTextureType = CHAR_TEX_FLESH; + else + { + // hit world + + // find texture under strike, get material type + + // copy trace vector into array for trace_texture + + vecSrc.CopyToArray(rgfl1); + vecEnd.CopyToArray(rgfl2); + + // get texture from entity or world (world is ent(0)) + if (pEntity) + pTextureName = TRACE_TEXTURE( ENT(pEntity->pev), rgfl1, rgfl2 ); + else + pTextureName = TRACE_TEXTURE( ENT(0), rgfl1, rgfl2 ); + + if ( pTextureName ) + { + // strip leading '-0' or '+0~' or '{' or '!' + if (*pTextureName == '-' || *pTextureName == '+') + pTextureName += 2; + + if (*pTextureName == '{' || *pTextureName == '!' || *pTextureName == '~' || *pTextureName == ' ') + pTextureName++; + // '}}' + strcpy(szbuffer, pTextureName); + szbuffer[CBTEXTURENAMEMAX - 1] = 0; + + // ALERT ( at_console, "texture hit: %s\n", szbuffer); + + // get texture type + chTextureType = TEXTURETYPE_Find(szbuffer); + } + } + + switch (chTextureType) + { + default: + case CHAR_TEX_CONCRETE: fvol = 0.9; fvolbar = 0.6; + rgsz[0] = "player/pl_step1.wav"; + rgsz[1] = "player/pl_step2.wav"; + cnt = 2; + break; + case CHAR_TEX_METAL: fvol = 0.9; fvolbar = 0.3; + rgsz[0] = "player/pl_metal1.wav"; + rgsz[1] = "player/pl_metal2.wav"; + cnt = 2; + break; + case CHAR_TEX_DIRT: fvol = 0.9; fvolbar = 0.1; + rgsz[0] = "player/pl_dirt1.wav"; + rgsz[1] = "player/pl_dirt2.wav"; + rgsz[2] = "player/pl_dirt3.wav"; + cnt = 3; + break; + case CHAR_TEX_VENT: fvol = 0.5; fvolbar = 0.3; + rgsz[0] = "player/pl_duct1.wav"; + rgsz[1] = "player/pl_duct1.wav"; + cnt = 2; + break; + case CHAR_TEX_GRATE: fvol = 0.9; fvolbar = 0.5; + rgsz[0] = "player/pl_grate1.wav"; + rgsz[1] = "player/pl_grate4.wav"; + cnt = 2; + break; + case CHAR_TEX_TILE: fvol = 0.8; fvolbar = 0.2; + rgsz[0] = "player/pl_tile1.wav"; + rgsz[1] = "player/pl_tile3.wav"; + rgsz[2] = "player/pl_tile2.wav"; + rgsz[3] = "player/pl_tile4.wav"; + cnt = 4; + break; + case CHAR_TEX_SLOSH: fvol = 0.9; fvolbar = 0.0; + rgsz[0] = "player/pl_slosh1.wav"; + rgsz[1] = "player/pl_slosh3.wav"; + rgsz[2] = "player/pl_slosh2.wav"; + rgsz[3] = "player/pl_slosh4.wav"; + cnt = 4; + break; + case CHAR_TEX_WOOD: fvol = 0.9; fvolbar = 0.2; + rgsz[0] = "debris/wood1.wav"; + rgsz[1] = "debris/wood2.wav"; + rgsz[2] = "debris/wood3.wav"; + cnt = 3; + break; + case CHAR_TEX_GLASS: + case CHAR_TEX_COMPUTER: + fvol = 0.8; fvolbar = 0.2; + rgsz[0] = "debris/glass1.wav"; + rgsz[1] = "debris/glass2.wav"; + rgsz[2] = "debris/glass3.wav"; + cnt = 3; + break; + case CHAR_TEX_FLESH: + if (iBulletType == BULLET_PLAYER_CROWBAR) + return 0.0; // crowbar already makes this sound + fvol = 1.0; fvolbar = 0.2; + rgsz[0] = "weapons/bullet_hit1.wav"; + rgsz[1] = "weapons/bullet_hit2.wav"; + fattn = 1.0; + cnt = 2; + break; + } + + // did we hit a breakable? + + if (pEntity && FClassnameIs(pEntity->pev, "func_breakable")) + { + // drop volumes, the object will already play a damaged sound + fvol /= 1.5; + fvolbar /= 2.0; + } + else if (chTextureType == CHAR_TEX_COMPUTER) + { + // play random spark if computer + + if ( ptr->flFraction != 1.0 && RANDOM_LONG(0,1)) + { + UTIL_Sparks( ptr->vecEndPos ); + + float flVolume = RANDOM_FLOAT ( 0.7 , 1.0 );//random volume range + switch ( RANDOM_LONG(0,1) ) + { + case 0: UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, "buttons/spark5.wav", flVolume, ATTN_NORM, 0, 100); break; + case 1: UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, "buttons/spark6.wav", flVolume, ATTN_NORM, 0, 100); break; + // case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM); break; + // case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM); break; + } + } + } + + // play material hit sound + UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, rgsz[RANDOM_LONG(0,cnt-1)], fvol, fattn, 0, 96 + RANDOM_LONG(0,0xf)); + //EMIT_SOUND_DYN( ENT(m_pPlayer->pev), CHAN_WEAPON, rgsz[RANDOM_LONG(0,cnt-1)], fvol, ATTN_NORM, 0, 96 + RANDOM_LONG(0,0xf)); + + return fvolbar; +} + +// =================================================================================== +// +// Speaker class. Used for announcements per level, for door lock/unlock spoken voice. +// + +class CSpeaker : public CBaseEntity +{ +public: + void KeyValue( KeyValueData* pkvd); + void Spawn( void ); + void Precache( void ); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT SpeakerThink( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + int m_preset; // preset number +}; + +LINK_ENTITY_TO_CLASS( speaker, CSpeaker ); +TYPEDESCRIPTION CSpeaker::m_SaveData[] = +{ + DEFINE_FIELD( CSpeaker, m_preset, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CSpeaker, CBaseEntity ); + +// +// ambient_generic - general-purpose user-defined static sound +// +void CSpeaker :: Spawn( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + + if ( !m_preset && (FStringNull( pev->message ) || strlen( szSoundFile ) < 1 )) + { + ALERT( at_error, "SPEAKER with no Level/Sentence! at: %f, %f, %f\n", pev->origin.x, pev->origin.y, pev->origin.z ); + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CSpeaker::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + + SetThink(&CSpeaker::SpeakerThink); + pev->nextthink = 0.0; + + // allow on/off switching via 'use' function. + + SetUse ( &CSpeaker::ToggleUse ); + + Precache( ); +} + +#define ANNOUNCE_MINUTES_MIN 0.25 +#define ANNOUNCE_MINUTES_MAX 2.25 + +void CSpeaker :: Precache( void ) +{ + if ( !FBitSet (pev->spawnflags, SPEAKER_START_SILENT ) ) + // set first announcement time for random n second + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(5.0, 15.0); +} +void CSpeaker :: SpeakerThink( void ) +{ + char* szSoundFile; + float flvolume = pev->health * 0.1; + float flattenuation = 0.3; + int flags = 0; + int pitch = 100; + + + // Wait for the talkmonster to finish first. + if (gpGlobals->time <= CTalkMonster::g_talkWaitTime) + { + pev->nextthink = CTalkMonster::g_talkWaitTime + RANDOM_FLOAT( 5, 10 ); + return; + } + + if (m_preset) + { + // go lookup preset text, assign szSoundFile + switch (m_preset) + { + case 1: szSoundFile = "C1A0_"; break; + case 2: szSoundFile = "C1A1_"; break; + case 3: szSoundFile = "C1A2_"; break; + case 4: szSoundFile = "C1A3_"; break; + case 5: szSoundFile = "C1A4_"; break; + case 6: szSoundFile = "C2A1_"; break; + case 7: szSoundFile = "C2A2_"; break; + case 8: szSoundFile = "C2A3_"; break; + case 9: szSoundFile = "C2A4_"; break; + case 10: szSoundFile = "C2A5_"; break; + case 11: szSoundFile = "C3A1_"; break; + case 12: szSoundFile = "C3A2_"; break; + } + } else + szSoundFile = (char*) STRING(pev->message); + + if (szSoundFile[0] == '!') + { + // play single sentence, one shot + UTIL_EmitAmbientSound ( ENT(pev), pev->origin, szSoundFile, + flvolume, flattenuation, flags, pitch); + + // shut off and reset + pev->nextthink = 0.0; + } + else + { + // make random announcement from sentence group + + if (SENTENCEG_PlayRndSz(ENT(pev), szSoundFile, flvolume, flattenuation, flags, pitch) < 0) + ALERT(at_console, "Level Design Error!\nSPEAKER has bad sentence group name: %s\n",szSoundFile); + + // set next announcement time for random 5 to 10 minute delay + pev->nextthink = gpGlobals->time + + RANDOM_FLOAT(ANNOUNCE_MINUTES_MIN * 60.0, ANNOUNCE_MINUTES_MAX * 60.0); + + CTalkMonster::g_talkWaitTime = gpGlobals->time + 5; // time delay until it's ok to speak: used so that two NPCs don't talk at once + } + + return; +} + + +// +// ToggleUse - if an announcement is pending, cancel it. If no announcement is pending, start one. +// +void CSpeaker :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int fActive = (pev->nextthink > 0.0); + + // fActive is TRUE only if an announcement is pending + + if ( useType != USE_TOGGLE ) + { + // ignore if we're just turning something on that's already on, or + // turning something off that's already off. + if ( (fActive && useType == USE_ON) || (!fActive && useType == USE_OFF) ) + return; + } + + if ( useType == USE_ON ) + { + // turn on announcements + pev->nextthink = gpGlobals->time + 0.1; + return; + } + + if ( useType == USE_OFF ) + { + // turn off announcements + pev->nextthink = 0.0; + return; + + } + + // Toggle announcements + + + if ( fActive ) + { + // turn off announcements + pev->nextthink = 0.0; + } + else + { + // turn on announcements + pev->nextthink = gpGlobals->time + 0.1; + } +} + +// KeyValue - load keyvalue pairs into member data +// NOTE: called BEFORE spawn! + +void CSpeaker :: KeyValue( KeyValueData *pkvd ) +{ + + // preset + if (FStrEq(pkvd->szKeyName, "preset")) + { + m_preset = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} diff --git a/dlls/soundent.cpp b/dlls/soundent.cpp new file mode 100644 index 0000000..86156e5 --- /dev/null +++ b/dlls/soundent.cpp @@ -0,0 +1,379 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "soundent.h" + + +LINK_ENTITY_TO_CLASS( soundent, CSoundEnt ); + +CSoundEnt *pSoundEnt; + +//========================================================= +// CSound - Clear - zeros all fields for a sound +//========================================================= +void CSound :: Clear ( void ) +{ + m_vecOrigin = g_vecZero; + m_iType = 0; + m_iVolume = 0; + m_flExpireTime = 0; + m_iNext = SOUNDLIST_EMPTY; + m_iNextAudible = 0; +} + +//========================================================= +// Reset - clears the volume, origin, and type for a sound, +// but doesn't expire or unlink it. +//========================================================= +void CSound :: Reset ( void ) +{ + m_vecOrigin = g_vecZero; + m_iType = 0; + m_iVolume = 0; + m_iNext = SOUNDLIST_EMPTY; +} + +//========================================================= +// FIsSound - returns TRUE if the sound is an Audible sound +//========================================================= +BOOL CSound :: FIsSound ( void ) +{ + if ( m_iType & ( bits_SOUND_COMBAT | bits_SOUND_WORLD | bits_SOUND_PLAYER | bits_SOUND_DANGER ) ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// FIsScent - returns TRUE if the sound is actually a scent +//========================================================= +BOOL CSound :: FIsScent ( void ) +{ + if ( m_iType & ( bits_SOUND_CARCASS | bits_SOUND_MEAT | bits_SOUND_GARBAGE ) ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// Spawn +//========================================================= +void CSoundEnt :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + Initialize(); + + pev->nextthink = gpGlobals->time + 1; +} + +//========================================================= +// Think - at interval, the entire active sound list is checked +// for sounds that have ExpireTimes less than or equal +// to the current world time, and these sounds are deallocated. +//========================================================= +void CSoundEnt :: Think ( void ) +{ + int iSound; + int iPreviousSound; + + pev->nextthink = gpGlobals->time + 0.3;// how often to check the sound list. + + iPreviousSound = SOUNDLIST_EMPTY; + iSound = m_iActiveSound; + + while ( iSound != SOUNDLIST_EMPTY ) + { + if ( m_SoundPool[ iSound ].m_flExpireTime <= gpGlobals->time && m_SoundPool[ iSound ].m_flExpireTime != SOUND_NEVER_EXPIRE ) + { + int iNext = m_SoundPool[ iSound ].m_iNext; + + // move this sound back into the free list + FreeSound( iSound, iPreviousSound ); + + iSound = iNext; + } + else + { + iPreviousSound = iSound; + iSound = m_SoundPool[ iSound ].m_iNext; + } + } + + if ( m_fShowReport ) + { + ALERT ( at_aiconsole, "Soundlist: %d / %d (%d)\n", ISoundsInList( SOUNDLISTTYPE_ACTIVE ),ISoundsInList( SOUNDLISTTYPE_FREE ), ISoundsInList( SOUNDLISTTYPE_ACTIVE ) - m_cLastActiveSounds ); + m_cLastActiveSounds = ISoundsInList ( SOUNDLISTTYPE_ACTIVE ); + } + +} + +//========================================================= +// Precache - dummy function +//========================================================= +void CSoundEnt :: Precache ( void ) +{ +} + +//========================================================= +// FreeSound - clears the passed active sound and moves it +// to the top of the free list. TAKE CARE to only call this +// function for sounds in the Active list!! +//========================================================= +void CSoundEnt :: FreeSound ( int iSound, int iPrevious ) +{ + if ( !pSoundEnt ) + { + // no sound ent! + return; + } + + if ( iPrevious != SOUNDLIST_EMPTY ) + { + // iSound is not the head of the active list, so + // must fix the index for the Previous sound +// pSoundEnt->m_SoundPool[ iPrevious ].m_iNext = m_SoundPool[ iSound ].m_iNext; + pSoundEnt->m_SoundPool[ iPrevious ].m_iNext = pSoundEnt->m_SoundPool[ iSound ].m_iNext; + } + else + { + // the sound we're freeing IS the head of the active list. + pSoundEnt->m_iActiveSound = pSoundEnt->m_SoundPool [ iSound ].m_iNext; + } + + // make iSound the head of the Free list. + pSoundEnt->m_SoundPool[ iSound ].m_iNext = pSoundEnt->m_iFreeSound; + pSoundEnt->m_iFreeSound = iSound; +} + +//========================================================= +// IAllocSound - moves a sound from the Free list to the +// Active list returns the index of the alloc'd sound +//========================================================= +int CSoundEnt :: IAllocSound( void ) +{ + int iNewSound; + + if ( m_iFreeSound == SOUNDLIST_EMPTY ) + { + // no free sound! + ALERT ( at_console, "Free Sound List is full!\n" ); + return SOUNDLIST_EMPTY; + } + + // there is at least one sound available, so move it to the + // Active sound list, and return its SoundPool index. + + iNewSound = m_iFreeSound;// copy the index of the next free sound + + m_iFreeSound = m_SoundPool[ m_iFreeSound ].m_iNext;// move the index down into the free list. + + m_SoundPool[ iNewSound ].m_iNext = m_iActiveSound;// point the new sound at the top of the active list. + + m_iActiveSound = iNewSound;// now make the new sound the top of the active list. You're done. + + return iNewSound; +} + +//========================================================= +// InsertSound - Allocates a free sound and fills it with +// sound info. +//========================================================= +void CSoundEnt :: InsertSound ( int iType, const Vector &vecOrigin, int iVolume, float flDuration ) +{ + int iThisSound; + + if ( !pSoundEnt ) + { + // no sound ent! + return; + } + + iThisSound = pSoundEnt->IAllocSound(); + + if ( iThisSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_console, "Could not AllocSound() for InsertSound() (DLL)\n" ); + return; + } + + pSoundEnt->m_SoundPool[ iThisSound ].m_vecOrigin = vecOrigin; + pSoundEnt->m_SoundPool[ iThisSound ].m_iType = iType; + pSoundEnt->m_SoundPool[ iThisSound ].m_iVolume = iVolume; + pSoundEnt->m_SoundPool[ iThisSound ].m_flExpireTime = gpGlobals->time + flDuration; +} + +//========================================================= +// Initialize - clears all sounds and moves them into the +// free sound list. +//========================================================= +void CSoundEnt :: Initialize ( void ) +{ + int i; + int iSound; + + m_cLastActiveSounds; + m_iFreeSound = 0; + m_iActiveSound = SOUNDLIST_EMPTY; + + for ( i = 0 ; i < MAX_WORLD_SOUNDS ; i++ ) + {// clear all sounds, and link them into the free sound list. + m_SoundPool[ i ].Clear(); + m_SoundPool[ i ].m_iNext = i + 1; + } + + m_SoundPool[ i - 1 ].m_iNext = SOUNDLIST_EMPTY;// terminate the list here. + + + // now reserve enough sounds for each client + for ( i = 0 ; i < gpGlobals->maxClients ; i++ ) + { + iSound = pSoundEnt->IAllocSound(); + + if ( iSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_console, "Could not AllocSound() for Client Reserve! (DLL)\n" ); + return; + } + + pSoundEnt->m_SoundPool[ iSound ].m_flExpireTime = SOUND_NEVER_EXPIRE; + } + + if ( CVAR_GET_FLOAT("displaysoundlist") == 1 ) + { + m_fShowReport = TRUE; + } + else + { + m_fShowReport = FALSE; + } +} + +//========================================================= +// ISoundsInList - returns the number of sounds in the desired +// sound list. +//========================================================= +int CSoundEnt :: ISoundsInList ( int iListType ) +{ + int i; + int iThisSound; + + if ( iListType == SOUNDLISTTYPE_FREE ) + { + iThisSound = m_iFreeSound; + } + else if ( iListType == SOUNDLISTTYPE_ACTIVE ) + { + iThisSound = m_iActiveSound; + } + else + { + ALERT ( at_console, "Unknown Sound List Type!\n" ); + } + + if ( iThisSound == SOUNDLIST_EMPTY ) + { + return 0; + } + + i = 0; + + while ( iThisSound != SOUNDLIST_EMPTY ) + { + i++; + + iThisSound = m_SoundPool[ iThisSound ].m_iNext; + } + + return i; +} + +//========================================================= +// ActiveList - returns the head of the active sound list +//========================================================= +int CSoundEnt :: ActiveList ( void ) +{ + if ( !pSoundEnt ) + { + return SOUNDLIST_EMPTY; + } + + return pSoundEnt->m_iActiveSound; +} + +//========================================================= +// FreeList - returns the head of the free sound list +//========================================================= +int CSoundEnt :: FreeList ( void ) +{ + if ( !pSoundEnt ) + { + return SOUNDLIST_EMPTY; + } + + return pSoundEnt->m_iFreeSound; +} + +//========================================================= +// SoundPointerForIndex - returns a pointer to the instance +// of CSound at index's position in the sound pool. +//========================================================= +CSound* CSoundEnt :: SoundPointerForIndex( int iIndex ) +{ + if ( !pSoundEnt ) + { + return NULL; + } + + if ( iIndex > ( MAX_WORLD_SOUNDS - 1 ) ) + { + ALERT ( at_console, "SoundPointerForIndex() - Index too large!\n" ); + return NULL; + } + + if ( iIndex < 0 ) + { + ALERT ( at_console, "SoundPointerForIndex() - Index < 0!\n" ); + return NULL; + } + + return &pSoundEnt->m_SoundPool[ iIndex ]; +} + +//========================================================= +// Clients are numbered from 1 to MAXCLIENTS, but the client +// reserved sounds in the soundlist are from 0 to MAXCLIENTS - 1, +// so this function ensures that a client gets the proper index +// to his reserved sound in the soundlist. +//========================================================= +int CSoundEnt :: ClientSoundIndex ( edict_t *pClient ) +{ + int iReturn = ENTINDEX( pClient ) - 1; + +#ifdef _DEBUG + if ( iReturn < 0 || iReturn > gpGlobals->maxClients ) + { + ALERT ( at_console, "** ClientSoundIndex returning a bogus value! **\n" ); + } +#endif // _DEBUG + + return iReturn; +} \ No newline at end of file diff --git a/dlls/soundent.h b/dlls/soundent.h new file mode 100644 index 0000000..0339408 --- /dev/null +++ b/dlls/soundent.h @@ -0,0 +1,95 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// Soundent.h - the entity that spawns when the world +// spawns, and handles the world's active and free sound +// lists. +//========================================================= + +#define MAX_WORLD_SOUNDS 64 // maximum number of sounds handled by the world at one time. + +#define bits_SOUND_NONE 0 +#define bits_SOUND_COMBAT ( 1 << 0 )// gunshots, explosions +#define bits_SOUND_WORLD ( 1 << 1 )// door opening/closing, glass breaking +#define bits_SOUND_PLAYER ( 1 << 2 )// all noises generated by player. walking, shooting, falling, splashing +#define bits_SOUND_CARCASS ( 1 << 3 )// dead body +#define bits_SOUND_MEAT ( 1 << 4 )// gib or pork chop +#define bits_SOUND_DANGER ( 1 << 5 )// pending danger. Grenade that is about to explode, explosive barrel that is damaged, falling crate +#define bits_SOUND_GARBAGE ( 1 << 6 )// trash cans, banana peels, old fast food bags. + +#define bits_ALL_SOUNDS 0xFFFFFFFF + +#define SOUNDLIST_EMPTY -1 + +#define SOUNDLISTTYPE_FREE 1// identifiers passed to functions that can operate on either list, to indicate which list to operate on. +#define SOUNDLISTTYPE_ACTIVE 2 + +#define SOUND_NEVER_EXPIRE -1 // with this set as a sound's ExpireTime, the sound will never expire. + +//========================================================= +// CSound - an instance of a sound in the world. +//========================================================= +class CSound +{ +public: + + void Clear ( void ); + void Reset ( void ); + + Vector m_vecOrigin; // sound's location in space + int m_iType; // what type of sound this is + int m_iVolume; // how loud the sound is + float m_flExpireTime; // when the sound should be purged from the list + int m_iNext; // index of next sound in this list ( Active or Free ) + int m_iNextAudible; // temporary link that monsters use to build a list of audible sounds + + BOOL FIsSound( void ); + BOOL FIsScent( void ); +}; + +//========================================================= +// CSoundEnt - a single instance of this entity spawns when +// the world spawns. The SoundEnt's job is to update the +// world's Free and Active sound lists. +//========================================================= +class CSoundEnt : public CBaseEntity +{ +public: + + void Precache ( void ); + void Spawn( void ); + void Think( void ); + void Initialize ( void ); + + static void InsertSound ( int iType, const Vector &vecOrigin, int iVolume, float flDuration ); + static void FreeSound ( int iSound, int iPrevious ); + static int ActiveList( void );// return the head of the active list + static int FreeList( void );// return the head of the free list + static CSound* SoundPointerForIndex( int iIndex );// return a pointer for this index in the sound list + static int ClientSoundIndex ( edict_t *pClient ); + + BOOL IsEmpty( void ) { return m_iActiveSound == SOUNDLIST_EMPTY; } + int ISoundsInList ( int iListType ); + int IAllocSound ( void ); + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } + + int m_iFreeSound; // index of the first sound in the free sound list + int m_iActiveSound; // indes of the first sound in the active sound list + int m_cLastActiveSounds; // keeps track of the number of active sounds at the last update. (for diagnostic work) + BOOL m_fShowReport; // if true, dump information about free/active sounds. + +private: + CSound m_SoundPool[ MAX_WORLD_SOUNDS ]; +}; diff --git a/dlls/spectator.cpp b/dlls/spectator.cpp new file mode 100644 index 0000000..5bc49c6 --- /dev/null +++ b/dlls/spectator.cpp @@ -0,0 +1,149 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// CBaseSpectator + +// YWB: UNDONE + +// Spectator functions +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "spectator.h" + +/* +=========== +SpectatorConnect + +called when a spectator connects to a server +============ +*/ +void CBaseSpectator::SpectatorConnect(void) +{ + pev->flags = FL_SPECTATOR; + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NOCLIP; + + m_pGoalEnt = NULL; +} + +/* +=========== +SpectatorDisconnect + +called when a spectator disconnects from a server +============ +*/ +void CBaseSpectator::SpectatorDisconnect(void) +{ +} + +/* +================ +SpectatorImpulseCommand + +Called by SpectatorThink if the spectator entered an impulse +================ +*/ +void CBaseSpectator::SpectatorImpulseCommand(void) +{ + static edict_t *pGoal = NULL; + edict_t *pPreviousGoal; + edict_t *pCurrentGoal; + BOOL bFound; + + switch (pev->impulse) + { + case 1: + // teleport the spectator to the next spawn point + // note that if the spectator is tracking, this doesn't do + // much + pPreviousGoal = pGoal; + pCurrentGoal = pGoal; + // Start at the current goal, skip the world, and stop if we looped + // back around + + bFound = FALSE; + while (1) + { + pCurrentGoal = FIND_ENTITY_BY_CLASSNAME(pCurrentGoal, "info_player_deathmatch"); + // Looped around, failure + if (pCurrentGoal == pPreviousGoal) + { + ALERT(at_console, "Could not find a spawn spot.\n"); + break; + } + // Found a non-world entity, set success, otherwise, look for the next one. + if (!FNullEnt(pCurrentGoal)) + { + bFound = TRUE; + break; + } + } + + if (!bFound) // Didn't find a good spot. + break; + + pGoal = pCurrentGoal; + UTIL_SetOrigin( pev, pGoal->v.origin ); + pev->angles = pGoal->v.angles; + pev->fixangle = FALSE; + break; + default: + ALERT(at_console, "Unknown spectator impulse\n"); + break; + } + + pev->impulse = 0; +} + +/* +================ +SpectatorThink + +Called every frame after physics are run +================ +*/ +void CBaseSpectator::SpectatorThink(void) +{ + if (!(pev->flags & FL_SPECTATOR)) + { + pev->flags = FL_SPECTATOR; + } + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NOCLIP; + + if (pev->impulse) + SpectatorImpulseCommand(); +} + +/* +=========== +Spawn + + Called when spectator is initialized: + UNDONE: Is this actually being called because spectators are not allocated in normal fashion? +============ +*/ +void CBaseSpectator::Spawn() +{ + pev->flags = FL_SPECTATOR; + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NOCLIP; + + m_pGoalEnt = NULL; +} diff --git a/dlls/spectator.h b/dlls/spectator.h new file mode 100644 index 0000000..2a4aefc --- /dev/null +++ b/dlls/spectator.h @@ -0,0 +1,27 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Spectator.h + +class CBaseSpectator : public CBaseEntity +{ +public: + void Spawn(); + void SpectatorConnect(void); + void SpectatorDisconnect(void); + void SpectatorThink(void); + +private: + void SpectatorImpulseCommand(void); +}; diff --git a/dlls/squad.h b/dlls/squad.h new file mode 100644 index 0000000..ab34d1d --- /dev/null +++ b/dlls/squad.h @@ -0,0 +1,20 @@ +//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: New version of the slider bar +// +// $NoKeywords: $ +//============================================================================= + +//========================================================= +// squad.h +//========================================================= + +// these are special group roles that are assigned to members when the group is formed. +// the reason these are explicitly assigned and tasks like throwing grenades to flush out +// enemies is that it's bad to have two members trying to flank left at the same time, but +// ok to have two throwing grenades at the same time. When a squad member cannot attack the +// enemy, it will choose to execute its special role. +#define bits_SQUAD_FLANK_LEFT ( 1 << 0 ) +#define bits_SQUAD_FLANK_RIGHT ( 1 << 1 ) +#define bits_SQUAD_ADVANCE ( 1 << 2 ) +#define bits_SQUAD_FLUSH_ATTACK ( 1 << 3 ) diff --git a/dlls/squadmonster.cpp b/dlls/squadmonster.cpp new file mode 100644 index 0000000..a95ea9f --- /dev/null +++ b/dlls/squadmonster.cpp @@ -0,0 +1,623 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Squadmonster functions +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "nodes.h" +#include "monsters.h" +#include "animation.h" +#include "saverestore.h" +#include "squadmonster.h" +#include "plane.h" + +//========================================================= +// Save/Restore +//========================================================= +TYPEDESCRIPTION CSquadMonster::m_SaveData[] = +{ + DEFINE_FIELD( CSquadMonster, m_hSquadLeader, FIELD_EHANDLE ), + DEFINE_ARRAY( CSquadMonster, m_hSquadMember, FIELD_EHANDLE, MAX_SQUAD_MEMBERS - 1 ), + + // DEFINE_FIELD( CSquadMonster, m_afSquadSlots, FIELD_INTEGER ), // these need to be reset after transitions! + DEFINE_FIELD( CSquadMonster, m_fEnemyEluded, FIELD_BOOLEAN ), + DEFINE_FIELD( CSquadMonster, m_flLastEnemySightTime, FIELD_TIME ), + + DEFINE_FIELD( CSquadMonster, m_iMySlot, FIELD_INTEGER ), + + +}; + +IMPLEMENT_SAVERESTORE( CSquadMonster, CBaseMonster ); + + +//========================================================= +// OccupySlot - if any slots of the passed slots are +// available, the monster will be assigned to one. +//========================================================= +BOOL CSquadMonster :: OccupySlot( int iDesiredSlots ) +{ + int i; + int iMask; + int iSquadSlots; + + if ( !InSquad() ) + { + return TRUE; + } + + if ( SquadEnemySplit() ) + { + // if the squad members aren't all fighting the same enemy, slots are disabled + // so that a squad member doesn't get stranded unable to engage his enemy because + // all of the attack slots are taken by squad members fighting other enemies. + m_iMySlot = bits_SLOT_SQUAD_SPLIT; + return TRUE; + } + + CSquadMonster *pSquadLeader = MySquadLeader(); + + if ( !( iDesiredSlots ^ pSquadLeader->m_afSquadSlots ) ) + { + // none of the desired slots are available. + return FALSE; + } + + iSquadSlots = pSquadLeader->m_afSquadSlots; + + for ( i = 0; i < NUM_SLOTS; i++ ) + { + iMask = 1<m_afSquadSlots |= iMask; + m_iMySlot = iMask; +// ALERT ( at_aiconsole, "Took slot %d - %d\n", i, m_hSquadLeader->m_afSquadSlots ); + return TRUE; + } + } + } + + return FALSE; +} + +//========================================================= +// VacateSlot +//========================================================= +void CSquadMonster :: VacateSlot() +{ + if ( m_iMySlot != bits_NO_SLOT && InSquad() ) + { +// ALERT ( at_aiconsole, "Vacated Slot %d - %d\n", m_iMySlot, m_hSquadLeader->m_afSquadSlots ); + MySquadLeader()->m_afSquadSlots &= ~m_iMySlot; + m_iMySlot = bits_NO_SLOT; + } +} + +//========================================================= +// ScheduleChange +//========================================================= +void CSquadMonster :: ScheduleChange ( void ) +{ + VacateSlot(); +} + +//========================================================= +// Killed +//========================================================= +void CSquadMonster :: Killed( entvars_t *pevAttacker, int iGib ) +{ + VacateSlot(); + + if ( InSquad() ) + { + MySquadLeader()->SquadRemove( this ); + } + + CBaseMonster :: Killed ( pevAttacker, iGib ); +} + +// These functions are still awaiting conversion to CSquadMonster + + +//========================================================= +// +// SquadRemove(), remove pRemove from my squad. +// If I am pRemove, promote m_pSquadNext to leader +// +//========================================================= +void CSquadMonster :: SquadRemove( CSquadMonster *pRemove ) +{ + ASSERT( pRemove!=NULL ); + ASSERT( this->IsLeader() ); + ASSERT( pRemove->m_hSquadLeader == this ); + + // If I'm the leader, get rid of my squad + if (pRemove == MySquadLeader()) + { + for (int i = 0; i < MAX_SQUAD_MEMBERS-1;i++) + { + CSquadMonster *pMember = MySquadMember(i); + if (pMember) + { + pMember->m_hSquadLeader = NULL; + m_hSquadMember[i] = NULL; + } + } + } + else + { + CSquadMonster *pSquadLeader = MySquadLeader(); + if (pSquadLeader) + { + for (int i = 0; i < MAX_SQUAD_MEMBERS-1;i++) + { + if (pSquadLeader->m_hSquadMember[i] == this) + { + pSquadLeader->m_hSquadMember[i] = NULL; + break; + } + } + } + } + + pRemove->m_hSquadLeader = NULL; +} + +//========================================================= +// +// SquadAdd(), add pAdd to my squad +// +//========================================================= +BOOL CSquadMonster :: SquadAdd( CSquadMonster *pAdd ) +{ + ASSERT( pAdd!=NULL ); + ASSERT( !pAdd->InSquad() ); + ASSERT( this->IsLeader() ); + + for (int i = 0; i < MAX_SQUAD_MEMBERS-1; i++) + { + if (m_hSquadMember[i] == NULL) + { + m_hSquadMember[i] = pAdd; + pAdd->m_hSquadLeader = this; + return TRUE; + } + } + return FALSE; + // should complain here +} + + +//========================================================= +// +// SquadPasteEnemyInfo - called by squad members that have +// current info on the enemy so that it can be stored for +// members who don't have current info. +// +//========================================================= +void CSquadMonster :: SquadPasteEnemyInfo ( void ) +{ + CSquadMonster *pSquadLeader = MySquadLeader( ); + if (pSquadLeader) + pSquadLeader->m_vecEnemyLKP = m_vecEnemyLKP; +} + +//========================================================= +// +// SquadCopyEnemyInfo - called by squad members who don't +// have current info on the enemy. Reads from the same fields +// in the leader's data that other squad members write to, +// so the most recent data is always available here. +// +//========================================================= +void CSquadMonster :: SquadCopyEnemyInfo ( void ) +{ + CSquadMonster *pSquadLeader = MySquadLeader( ); + if (pSquadLeader) + m_vecEnemyLKP = pSquadLeader->m_vecEnemyLKP; +} + +//========================================================= +// +// SquadMakeEnemy - makes everyone in the squad angry at +// the same entity. +// +//========================================================= +void CSquadMonster :: SquadMakeEnemy ( CBaseEntity *pEnemy ) +{ + if (!InSquad()) + return; + + if ( !pEnemy ) + { + ALERT ( at_console, "ERROR: SquadMakeEnemy() - pEnemy is NULL!\n" ); + return; + } + + CSquadMonster *pSquadLeader = MySquadLeader( ); + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + CSquadMonster *pMember = pSquadLeader->MySquadMember(i); + if (pMember) + { + // reset members who aren't activly engaged in fighting + if (pMember->m_hEnemy != pEnemy && !pMember->HasConditions( bits_COND_SEE_ENEMY)) + { + if ( pMember->m_hEnemy != NULL) + { + // remember their current enemy + pMember->PushEnemy( pMember->m_hEnemy, pMember->m_vecEnemyLKP ); + } + // give them a new enemy + pMember->m_hEnemy = pEnemy; + pMember->m_vecEnemyLKP = pEnemy->pev->origin; + pMember->SetConditions ( bits_COND_NEW_ENEMY ); + } + } + } +} + + +//========================================================= +// +// SquadCount(), return the number of members of this squad +// callable from leaders & followers +// +//========================================================= +int CSquadMonster :: SquadCount( void ) +{ + if (!InSquad()) + return 0; + + CSquadMonster *pSquadLeader = MySquadLeader(); + int squadCount = 0; + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + if (pSquadLeader->MySquadMember(i) != NULL) + squadCount++; + } + + return squadCount; +} + + +//========================================================= +// +// SquadRecruit(), get some monsters of my classification and +// link them as a group. returns the group size +// +//========================================================= +int CSquadMonster :: SquadRecruit( int searchRadius, int maxMembers ) +{ + int squadCount; + int iMyClass = Classify();// cache this monster's class + + + // Don't recruit if I'm already in a group + if ( InSquad() ) + return 0; + + if ( maxMembers < 2 ) + return 0; + + // I am my own leader + m_hSquadLeader = this; + squadCount = 1; + + CBaseEntity *pEntity = NULL; + + if ( !FStringNull( pev->netname ) ) + { + // I have a netname, so unconditionally recruit everyone else with that name. + pEntity = UTIL_FindEntityByString( pEntity, "netname", STRING( pev->netname ) ); + while ( pEntity ) + { + CSquadMonster *pRecruit = pEntity->MySquadMonsterPointer(); + + if ( pRecruit ) + { + if ( !pRecruit->InSquad() && pRecruit->Classify() == iMyClass && pRecruit != this ) + { + // minimum protection here against user error.in worldcraft. + if (!SquadAdd( pRecruit )) + break; + squadCount++; + } + } + + pEntity = UTIL_FindEntityByString( pEntity, "netname", STRING( pev->netname ) ); + } + } + else + { + while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, searchRadius )) != NULL) + { + CSquadMonster *pRecruit = pEntity->MySquadMonsterPointer( ); + + if ( pRecruit && pRecruit != this && pRecruit->IsAlive() && !pRecruit->m_pCine ) + { + // Can we recruit this guy? + if ( !pRecruit->InSquad() && pRecruit->Classify() == iMyClass && + ( (iMyClass != CLASS_ALIEN_MONSTER) || FStrEq(STRING(pev->classname), STRING(pRecruit->pev->classname))) && + FStringNull( pRecruit->pev->netname ) ) + { + TraceResult tr; + UTIL_TraceLine( pev->origin + pev->view_ofs, pRecruit->pev->origin + pev->view_ofs, ignore_monsters, pRecruit->edict(), &tr );// try to hit recruit with a traceline. + if ( tr.flFraction == 1.0 ) + { + if (!SquadAdd( pRecruit )) + break; + + squadCount++; + } + } + } + } + } + + // no single member squads + if (squadCount == 1) + { + m_hSquadLeader = NULL; + } + + return squadCount; +} + +//========================================================= +// CheckEnemy +//========================================================= +int CSquadMonster :: CheckEnemy ( CBaseEntity *pEnemy ) +{ + int iUpdatedLKP; + + iUpdatedLKP = CBaseMonster :: CheckEnemy ( m_hEnemy ); + + // communicate with squad members about the enemy IF this individual has the same enemy as the squad leader. + if ( InSquad() && (CBaseEntity *)m_hEnemy == MySquadLeader()->m_hEnemy ) + { + if ( iUpdatedLKP ) + { + // have new enemy information, so paste to the squad. + SquadPasteEnemyInfo(); + } + else + { + // enemy unseen, copy from the squad knowledge. + SquadCopyEnemyInfo(); + } + } + + return iUpdatedLKP; +} + +//========================================================= +// StartMonster +//========================================================= +void CSquadMonster :: StartMonster( void ) +{ + CBaseMonster :: StartMonster(); + + if ( ( m_afCapability & bits_CAP_SQUAD ) && !InSquad() ) + { + if ( !FStringNull( pev->netname ) ) + { + // if I have a groupname, I can only recruit if I'm flagged as leader + if ( !( pev->spawnflags & SF_SQUADMONSTER_LEADER ) ) + { + return; + } + } + + // try to form squads now. + int iSquadSize = SquadRecruit( 1024, 4 ); + + if ( iSquadSize ) + { + ALERT ( at_aiconsole, "Squad of %d %s formed\n", iSquadSize, STRING( pev->classname ) ); + } + + if ( IsLeader() && FClassnameIs ( pev, "monster_human_grunt" ) ) + { + SetBodygroup( 1, 1 ); // UNDONE: truly ugly hack + pev->skin = 0; + } + + } +} + +//========================================================= +// NoFriendlyFire - checks for possibility of friendly fire +// +// Builds a large box in front of the grunt and checks to see +// if any squad members are in that box. +//========================================================= +BOOL CSquadMonster :: NoFriendlyFire( void ) +{ + if ( !InSquad() ) + { + return TRUE; + } + + CPlane backPlane; + CPlane leftPlane; + CPlane rightPlane; + + Vector vecLeftSide; + Vector vecRightSide; + Vector v_left; + + //!!!BUGBUG - to fix this, the planes must be aligned to where the monster will be firing its gun, not the direction it is facing!!! + + if ( m_hEnemy != NULL ) + { + UTIL_MakeVectors ( UTIL_VecToAngles( m_hEnemy->Center() - pev->origin ) ); + } + else + { + // if there's no enemy, pretend there's a friendly in the way, so the grunt won't shoot. + return FALSE; + } + + //UTIL_MakeVectors ( pev->angles ); + + vecLeftSide = pev->origin - ( gpGlobals->v_right * ( pev->size.x * 1.5 ) ); + vecRightSide = pev->origin + ( gpGlobals->v_right * ( pev->size.x * 1.5 ) ); + v_left = gpGlobals->v_right * -1; + + leftPlane.InitializePlane ( gpGlobals->v_right, vecLeftSide ); + rightPlane.InitializePlane ( v_left, vecRightSide ); + backPlane.InitializePlane ( gpGlobals->v_forward, pev->origin ); + +/* + ALERT ( at_console, "LeftPlane: %f %f %f : %f\n", leftPlane.m_vecNormal.x, leftPlane.m_vecNormal.y, leftPlane.m_vecNormal.z, leftPlane.m_flDist ); + ALERT ( at_console, "RightPlane: %f %f %f : %f\n", rightPlane.m_vecNormal.x, rightPlane.m_vecNormal.y, rightPlane.m_vecNormal.z, rightPlane.m_flDist ); + ALERT ( at_console, "BackPlane: %f %f %f : %f\n", backPlane.m_vecNormal.x, backPlane.m_vecNormal.y, backPlane.m_vecNormal.z, backPlane.m_flDist ); +*/ + + CSquadMonster *pSquadLeader = MySquadLeader(); + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + CSquadMonster *pMember = pSquadLeader->MySquadMember(i); + if (pMember && pMember != this) + { + + if ( backPlane.PointInFront ( pMember->pev->origin ) && + leftPlane.PointInFront ( pMember->pev->origin ) && + rightPlane.PointInFront ( pMember->pev->origin) ) + { + // this guy is in the check volume! Don't shoot! + return FALSE; + } + } + } + + return TRUE; +} + +//========================================================= +// GetIdealState - surveys the Conditions information available +// and finds the best new state for a monster. +//========================================================= +MONSTERSTATE CSquadMonster :: GetIdealState ( void ) +{ + int iConditions; + + iConditions = IScheduleFlags(); + + // If no schedule conditions, the new ideal state is probably the reason we're in here. + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: + case MONSTERSTATE_ALERT: + if ( HasConditions ( bits_COND_NEW_ENEMY ) && InSquad() ) + { + SquadMakeEnemy ( m_hEnemy ); + } + break; + } + + return CBaseMonster :: GetIdealState(); +} + +//========================================================= +// FValidateCover - determines whether or not the chosen +// cover location is a good one to move to. (currently based +// on proximity to others in the squad) +//========================================================= +BOOL CSquadMonster :: FValidateCover ( const Vector &vecCoverLocation ) +{ + if ( !InSquad() ) + { + return TRUE; + } + + if (SquadMemberInRange( vecCoverLocation, 128 )) + { + // another squad member is too close to this piece of cover. + return FALSE; + } + + return TRUE; +} + +//========================================================= +// SquadEnemySplit- returns TRUE if not all squad members +// are fighting the same enemy. +//========================================================= +BOOL CSquadMonster :: SquadEnemySplit ( void ) +{ + if (!InSquad()) + return FALSE; + + CSquadMonster *pSquadLeader = MySquadLeader(); + CBaseEntity *pEnemy = pSquadLeader->m_hEnemy; + + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + CSquadMonster *pMember = pSquadLeader->MySquadMember(i); + if (pMember != NULL && pMember->m_hEnemy != NULL && pMember->m_hEnemy != pEnemy) + { + return TRUE; + } + } + return FALSE; +} + +//========================================================= +// FValidateCover - determines whether or not the chosen +// cover location is a good one to move to. (currently based +// on proximity to others in the squad) +//========================================================= +BOOL CSquadMonster :: SquadMemberInRange ( const Vector &vecLocation, float flDist ) +{ + if (!InSquad()) + return FALSE; + + CSquadMonster *pSquadLeader = MySquadLeader(); + + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + CSquadMonster *pSquadMember = pSquadLeader->MySquadMember(i); + if (pSquadMember && (vecLocation - pSquadMember->pev->origin ).Length2D() <= flDist) + return TRUE; + } + return FALSE; +} + + +extern Schedule_t slChaseEnemyFailed[]; + +Schedule_t *CSquadMonster::GetScheduleOfType( int iType ) +{ + switch ( iType ) + { + + case SCHED_CHASE_ENEMY_FAILED: + { + return &slChaseEnemyFailed[ 0 ]; + } + + default: + return CBaseMonster::GetScheduleOfType( iType ); + } +} + diff --git a/dlls/squadmonster.h b/dlls/squadmonster.h new file mode 100644 index 0000000..6ff1701 --- /dev/null +++ b/dlls/squadmonster.h @@ -0,0 +1,120 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// CSquadMonster - all the extra data for monsters that +// form squads. +//========================================================= + +#define SF_SQUADMONSTER_LEADER 32 + + +#define bits_NO_SLOT 0 + +// HUMAN GRUNT SLOTS +#define bits_SLOT_HGRUNT_ENGAGE1 ( 1 << 0 ) +#define bits_SLOT_HGRUNT_ENGAGE2 ( 1 << 1 ) +#define bits_SLOTS_HGRUNT_ENGAGE ( bits_SLOT_HGRUNT_ENGAGE1 | bits_SLOT_HGRUNT_ENGAGE2 ) + +#define bits_SLOT_HGRUNT_GRENADE1 ( 1 << 2 ) +#define bits_SLOT_HGRUNT_GRENADE2 ( 1 << 3 ) +#define bits_SLOTS_HGRUNT_GRENADE ( bits_SLOT_HGRUNT_GRENADE1 | bits_SLOT_HGRUNT_GRENADE2 ) + +// ALIEN GRUNT SLOTS +#define bits_SLOT_AGRUNT_HORNET1 ( 1 << 4 ) +#define bits_SLOT_AGRUNT_HORNET2 ( 1 << 5 ) +#define bits_SLOT_AGRUNT_CHASE ( 1 << 6 ) +#define bits_SLOTS_AGRUNT_HORNET ( bits_SLOT_AGRUNT_HORNET1 | bits_SLOT_AGRUNT_HORNET2 ) + +// HOUNDEYE SLOTS +#define bits_SLOT_HOUND_ATTACK1 ( 1 << 7 ) +#define bits_SLOT_HOUND_ATTACK2 ( 1 << 8 ) +#define bits_SLOT_HOUND_ATTACK3 ( 1 << 9 ) +#define bits_SLOTS_HOUND_ATTACK ( bits_SLOT_HOUND_ATTACK1 | bits_SLOT_HOUND_ATTACK2 | bits_SLOT_HOUND_ATTACK3 ) + +// global slots +#define bits_SLOT_SQUAD_SPLIT ( 1 << 10 )// squad members don't all have the same enemy + +#define NUM_SLOTS 11// update this every time you add/remove a slot. + +#define MAX_SQUAD_MEMBERS 5 + +//========================================================= +// CSquadMonster - for any monster that forms squads. +//========================================================= +class CSquadMonster : public CBaseMonster +{ +public: + // squad leader info + EHANDLE m_hSquadLeader; // who is my leader + EHANDLE m_hSquadMember[MAX_SQUAD_MEMBERS-1]; // valid only for leader + int m_afSquadSlots; + float m_flLastEnemySightTime; // last time anyone in the squad saw the enemy + BOOL m_fEnemyEluded; + + // squad member info + int m_iMySlot;// this is the behaviour slot that the monster currently holds in the squad. + + int CheckEnemy ( CBaseEntity *pEnemy ); + void StartMonster ( void ); + void VacateSlot( void ); + void ScheduleChange( void ); + void Killed( entvars_t *pevAttacker, int iGib ); + BOOL OccupySlot( int iDesiredSlot ); + BOOL NoFriendlyFire( void ); + + // squad functions still left in base class + CSquadMonster *MySquadLeader( ) + { + CSquadMonster *pSquadLeader = (CSquadMonster *)((CBaseEntity *)m_hSquadLeader); + if (pSquadLeader != NULL) + return pSquadLeader; + return this; + } + CSquadMonster *MySquadMember( int i ) + { + if (i >= MAX_SQUAD_MEMBERS-1) + return this; + else + return (CSquadMonster *)((CBaseEntity *)m_hSquadMember[i]); + } + int InSquad ( void ) { return m_hSquadLeader != NULL; } + int IsLeader ( void ) { return m_hSquadLeader == this; } + int SquadJoin ( int searchRadius ); + int SquadRecruit ( int searchRadius, int maxMembers ); + int SquadCount( void ); + void SquadRemove( CSquadMonster *pRemove ); + void SquadUnlink( void ); + BOOL SquadAdd( CSquadMonster *pAdd ); + void SquadDisband( void ); + void SquadAddConditions ( int iConditions ); + void SquadMakeEnemy ( CBaseEntity *pEnemy ); + void SquadPasteEnemyInfo ( void ); + void SquadCopyEnemyInfo ( void ); + BOOL SquadEnemySplit ( void ); + BOOL SquadMemberInRange( const Vector &vecLocation, float flDist ); + + virtual CSquadMonster *MySquadMonsterPointer( void ) { return this; } + + static TYPEDESCRIPTION m_SaveData[]; + + int Save( CSave &save ); + int Restore( CRestore &restore ); + + BOOL FValidateCover ( const Vector &vecCoverLocation ); + + MONSTERSTATE GetIdealState ( void ); + Schedule_t *GetScheduleOfType ( int iType ); +}; + diff --git a/dlls/squeakgrenade.cpp b/dlls/squeakgrenade.cpp new file mode 100644 index 0000000..2084d79 --- /dev/null +++ b/dlls/squeakgrenade.cpp @@ -0,0 +1,601 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "soundent.h" +#include "gamerules.h" + +enum w_squeak_e { + WSQUEAK_IDLE1 = 0, + WSQUEAK_FIDGET, + WSQUEAK_JUMP, + WSQUEAK_RUN, +}; + +enum squeak_e { + SQUEAK_IDLE1 = 0, + SQUEAK_FIDGETFIT, + SQUEAK_FIDGETNIP, + SQUEAK_DOWN, + SQUEAK_UP, + SQUEAK_THROW +}; + +#ifndef CLIENT_DLL + +class CSqueakGrenade : public CGrenade +{ + void Spawn( void ); + void Precache( void ); + int Classify( void ); + void EXPORT SuperBounceTouch( CBaseEntity *pOther ); + void EXPORT HuntThink( void ); + int BloodColor( void ) { return BLOOD_COLOR_YELLOW; } + void Killed( entvars_t *pevAttacker, int iGib ); + void GibMonster( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + static float m_flNextBounceSoundTime; + + // CBaseEntity *m_pTarget; + float m_flDie; + Vector m_vecTarget; + float m_flNextHunt; + float m_flNextHit; + Vector m_posPrev; + EHANDLE m_hOwner; + int m_iMyClass; +}; + +float CSqueakGrenade::m_flNextBounceSoundTime = 0; + +LINK_ENTITY_TO_CLASS( monster_snark, CSqueakGrenade ); +TYPEDESCRIPTION CSqueakGrenade::m_SaveData[] = +{ + DEFINE_FIELD( CSqueakGrenade, m_flDie, FIELD_TIME ), + DEFINE_FIELD( CSqueakGrenade, m_vecTarget, FIELD_VECTOR ), + DEFINE_FIELD( CSqueakGrenade, m_flNextHunt, FIELD_TIME ), + DEFINE_FIELD( CSqueakGrenade, m_flNextHit, FIELD_TIME ), + DEFINE_FIELD( CSqueakGrenade, m_posPrev, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CSqueakGrenade, m_hOwner, FIELD_EHANDLE ), +}; + +IMPLEMENT_SAVERESTORE( CSqueakGrenade, CGrenade ); + +#define SQUEEK_DETONATE_DELAY 15.0 + +int CSqueakGrenade :: Classify ( void ) +{ + if (m_iMyClass != 0) + return m_iMyClass; // protect against recursion + + if (m_hEnemy != NULL) + { + m_iMyClass = CLASS_INSECT; // no one cares about it + switch( m_hEnemy->Classify( ) ) + { + case CLASS_PLAYER: + case CLASS_HUMAN_PASSIVE: + case CLASS_HUMAN_MILITARY: + m_iMyClass = 0; + return CLASS_ALIEN_MILITARY; // barney's get mad, grunts get mad at it + } + m_iMyClass = 0; + } + + return CLASS_ALIEN_BIOWEAPON; +} + +void CSqueakGrenade :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_BOUNCE; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/w_squeak.mdl"); + UTIL_SetSize(pev, Vector( -4, -4, 0), Vector(4, 4, 8)); + UTIL_SetOrigin( pev, pev->origin ); + + SetTouch( &CSqueakGrenade::SuperBounceTouch ); + SetThink( &CSqueakGrenade::HuntThink ); + pev->nextthink = gpGlobals->time + 0.1; + m_flNextHunt = gpGlobals->time + 1E6; + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_AIM; + pev->health = gSkillData.snarkHealth; + pev->gravity = 0.5; + pev->friction = 0.5; + + pev->dmg = gSkillData.snarkDmgPop; + + m_flDie = gpGlobals->time + SQUEEK_DETONATE_DELAY; + + m_flFieldOfView = 0; // 180 degrees + + if ( pev->owner ) + m_hOwner = Instance( pev->owner ); + + m_flNextBounceSoundTime = gpGlobals->time;// reset each time a snark is spawned. + + pev->sequence = WSQUEAK_RUN; + ResetSequenceInfo( ); +} + +void CSqueakGrenade::Precache( void ) +{ + PRECACHE_MODEL("models/w_squeak.mdl"); + PRECACHE_SOUND("squeek/sqk_blast1.wav"); + PRECACHE_SOUND("common/bodysplat.wav"); + PRECACHE_SOUND("squeek/sqk_die1.wav"); + PRECACHE_SOUND("squeek/sqk_hunt1.wav"); + PRECACHE_SOUND("squeek/sqk_hunt2.wav"); + PRECACHE_SOUND("squeek/sqk_hunt3.wav"); + PRECACHE_SOUND("squeek/sqk_deploy1.wav"); +} + + +void CSqueakGrenade :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->model = iStringNull;// make invisible + SetThink( &CSqueakGrenade::SUB_Remove ); + SetTouch( NULL ); + pev->nextthink = gpGlobals->time + 0.1; + + // since squeak grenades never leave a body behind, clear out their takedamage now. + // Squeaks do a bit of radius damage when they pop, and that radius damage will + // continue to call this function unless we acknowledge the Squeak's death now. (sjb) + pev->takedamage = DAMAGE_NO; + + // play squeek blast + EMIT_SOUND_DYN(ENT(pev), CHAN_ITEM, "squeek/sqk_blast1.wav", 1, 0.5, 0, PITCH_NORM); + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, SMALL_EXPLOSION_VOLUME, 3.0 ); + + UTIL_BloodDrips( pev->origin, g_vecZero, BloodColor(), 80 ); + + if (m_hOwner != NULL) + RadiusDamage ( pev, m_hOwner->pev, pev->dmg, CLASS_NONE, DMG_BLAST ); + else + RadiusDamage ( pev, pev, pev->dmg, CLASS_NONE, DMG_BLAST ); + + // reset owner so death message happens + if (m_hOwner != NULL) + pev->owner = m_hOwner->edict(); + + CBaseMonster :: Killed( pevAttacker, GIB_ALWAYS ); +} + +void CSqueakGrenade :: GibMonster( void ) +{ + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "common/bodysplat.wav", 0.75, ATTN_NORM, 0, 200); +} + + + +void CSqueakGrenade::HuntThink( void ) +{ + // ALERT( at_console, "think\n" ); + + if (!IsInWorld()) + { + SetTouch( NULL ); + UTIL_Remove( this ); + return; + } + + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + // explode when ready + if (gpGlobals->time >= m_flDie) + { + g_vecAttackDir = pev->velocity.Normalize( ); + pev->health = -1; + Killed( pev, 0 ); + return; + } + + // float + if (pev->waterlevel != 0) + { + if (pev->movetype == MOVETYPE_BOUNCE) + { + pev->movetype = MOVETYPE_FLY; + } + pev->velocity = pev->velocity * 0.9; + pev->velocity.z += 8.0; + } + else if (pev->movetype = MOVETYPE_FLY) + { + pev->movetype = MOVETYPE_BOUNCE; + } + + // return if not time to hunt + if (m_flNextHunt > gpGlobals->time) + return; + + m_flNextHunt = gpGlobals->time + 2.0; + + CBaseEntity *pOther = NULL; + Vector vecDir; + TraceResult tr; + + Vector vecFlat = pev->velocity; + vecFlat.z = 0; + vecFlat = vecFlat.Normalize( ); + + UTIL_MakeVectors( pev->angles ); + + if (m_hEnemy == NULL || !m_hEnemy->IsAlive()) + { + // find target, bounce a bit towards it. + Look( 512 ); + m_hEnemy = BestVisibleEnemy( ); + } + + // squeek if it's about time blow up + if ((m_flDie - gpGlobals->time <= 0.5) && (m_flDie - gpGlobals->time >= 0.3)) + { + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_die1.wav", 1, ATTN_NORM, 0, 100 + RANDOM_LONG(0,0x3F)); + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 256, 0.25 ); + } + + // higher pitch as squeeker gets closer to detonation time + float flpitch = 155.0 - 60.0 * ((m_flDie - gpGlobals->time) / SQUEEK_DETONATE_DELAY); + if (flpitch < 80) + flpitch = 80; + + if (m_hEnemy != NULL) + { + if (FVisible( m_hEnemy )) + { + vecDir = m_hEnemy->EyePosition() - pev->origin; + m_vecTarget = vecDir.Normalize( ); + } + + float flVel = pev->velocity.Length(); + float flAdj = 50.0 / (flVel + 10.0); + + if (flAdj > 1.2) + flAdj = 1.2; + + // ALERT( at_console, "think : enemy\n"); + + // ALERT( at_console, "%.0f %.2f %.2f %.2f\n", flVel, m_vecTarget.x, m_vecTarget.y, m_vecTarget.z ); + + pev->velocity = pev->velocity * flAdj + m_vecTarget * 300; + } + + if (pev->flags & FL_ONGROUND) + { + pev->avelocity = Vector( 0, 0, 0 ); + } + else + { + if (pev->avelocity == Vector( 0, 0, 0)) + { + pev->avelocity.x = RANDOM_FLOAT( -100, 100 ); + pev->avelocity.z = RANDOM_FLOAT( -100, 100 ); + } + } + + if ((pev->origin - m_posPrev).Length() < 1.0) + { + pev->velocity.x = RANDOM_FLOAT( -100, 100 ); + pev->velocity.y = RANDOM_FLOAT( -100, 100 ); + } + m_posPrev = pev->origin; + + pev->angles = UTIL_VecToAngles( pev->velocity ); + pev->angles.z = 0; + pev->angles.x = 0; +} + + +void CSqueakGrenade::SuperBounceTouch( CBaseEntity *pOther ) +{ + float flpitch; + + TraceResult tr = UTIL_GetGlobalTrace( ); + + // don't hit the guy that launched this grenade + if ( pev->owner && pOther->edict() == pev->owner ) + return; + + // at least until we've bounced once + pev->owner = NULL; + + pev->angles.x = 0; + pev->angles.z = 0; + + // avoid bouncing too much + if (m_flNextHit > gpGlobals->time) + return; + + // higher pitch as squeeker gets closer to detonation time + flpitch = 155.0 - 60.0 * ((m_flDie - gpGlobals->time) / SQUEEK_DETONATE_DELAY); + + if ( pOther->pev->takedamage && m_flNextAttack < gpGlobals->time ) + { + // attack! + + // make sure it's me who has touched them + if (tr.pHit == pOther->edict()) + { + // and it's not another squeakgrenade + if (tr.pHit->v.modelindex != pev->modelindex) + { + // ALERT( at_console, "hit enemy\n"); + ClearMultiDamage( ); + pOther->TraceAttack(pev, gSkillData.snarkDmgBite, gpGlobals->v_forward, &tr, DMG_SLASH ); + if (m_hOwner != NULL) + ApplyMultiDamage( pev, m_hOwner->pev ); + else + ApplyMultiDamage( pev, pev ); + + pev->dmg += gSkillData.snarkDmgPop; // add more explosion damage + // m_flDie += 2.0; // add more life + + // make bite sound + EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, "squeek/sqk_deploy1.wav", 1.0, ATTN_NORM, 0, (int)flpitch); + m_flNextAttack = gpGlobals->time + 0.5; + } + } + else + { + // ALERT( at_console, "been hit\n"); + } + } + + m_flNextHit = gpGlobals->time + 0.1; + m_flNextHunt = gpGlobals->time; + + if ( g_pGameRules->IsMultiplayer() ) + { + // in multiplayer, we limit how often snarks can make their bounce sounds to prevent overflows. + if ( gpGlobals->time < m_flNextBounceSoundTime ) + { + // too soon! + return; + } + } + + if (!(pev->flags & FL_ONGROUND)) + { + // play bounce sound + float flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.33 ) + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_hunt1.wav", 1, ATTN_NORM, 0, (int)flpitch); + else if (flRndSound <= 0.66) + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_hunt2.wav", 1, ATTN_NORM, 0, (int)flpitch); + else + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_hunt3.wav", 1, ATTN_NORM, 0, (int)flpitch); + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 256, 0.25 ); + } + else + { + // skittering sound + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 100, 0.1 ); + } + + m_flNextBounceSoundTime = gpGlobals->time + 0.5;// half second. +} + +#endif + +LINK_ENTITY_TO_CLASS( weapon_snark, CSqueak ); + + +void CSqueak::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_SNARK; + SET_MODEL(ENT(pev), "models/w_sqknest.mdl"); + + FallInit();//get ready to fall down. + + m_iDefaultAmmo = SNARK_DEFAULT_GIVE; + + pev->sequence = 1; + pev->animtime = gpGlobals->time; + pev->framerate = 1.0; +} + + +void CSqueak::Precache( void ) +{ + PRECACHE_MODEL("models/w_sqknest.mdl"); + PRECACHE_MODEL("models/v_squeak.mdl"); + PRECACHE_MODEL("models/p_squeak.mdl"); + PRECACHE_SOUND("squeek/sqk_hunt2.wav"); + PRECACHE_SOUND("squeek/sqk_hunt3.wav"); + UTIL_PrecacheOther("monster_snark"); + + m_usSnarkFire = PRECACHE_EVENT ( 1, "events/snarkfire.sc" ); +} + + +int CSqueak::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "Snarks"; + p->iMaxAmmo1 = SNARK_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 4; + p->iPosition = 3; + p->iId = m_iId = WEAPON_SNARK; + p->iWeight = SNARK_WEIGHT; + p->iFlags = ITEM_FLAG_LIMITINWORLD | ITEM_FLAG_EXHAUSTIBLE; + + return 1; +} + + + +BOOL CSqueak::Deploy( ) +{ + // play hunt sound + float flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.5 ) + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_hunt2.wav", 1, ATTN_NORM, 0, 100); + else + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_hunt3.wav", 1, ATTN_NORM, 0, 100); + + m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME; + + return DefaultDeploy( "models/v_squeak.mdl", "models/p_squeak.mdl", SQUEAK_UP, "squeak" ); +} + + +void CSqueak::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + + if ( !m_pPlayer->m_rgAmmo[ m_iPrimaryAmmoType ] ) + { + m_pPlayer->pev->weapons &= ~(1<nextthink = gpGlobals->time + 0.1; + return; + } + + SendWeaponAnim( SQUEAK_DOWN ); + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM); +} + + +void CSqueak::PrimaryAttack() +{ + if ( m_pPlayer->m_rgAmmo[ m_iPrimaryAmmoType ] ) + { + UTIL_MakeVectors( m_pPlayer->pev->v_angle ); + TraceResult tr; + Vector trace_origin; + + // HACK HACK: Ugly hacks to handle change in origin based on new physics code for players + // Move origin up if crouched and start trace a bit outside of body ( 20 units instead of 16 ) + trace_origin = m_pPlayer->pev->origin; + if ( m_pPlayer->pev->flags & FL_DUCKING ) + { + trace_origin = trace_origin - ( VEC_HULL_MIN - VEC_DUCK_HULL_MIN ); + } + + // find place to toss monster + UTIL_TraceLine( trace_origin + gpGlobals->v_forward * 20, trace_origin + gpGlobals->v_forward * 64, dont_ignore_monsters, NULL, &tr ); + + int flags; +#ifdef CLIENT_WEAPONS + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usSnarkFire, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); + + if ( tr.fAllSolid == 0 && tr.fStartSolid == 0 && tr.flFraction > 0.25 ) + { + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + +#ifndef CLIENT_DLL + CBaseEntity *pSqueak = CBaseEntity::Create( "monster_snark", tr.vecEndPos, m_pPlayer->pev->v_angle, m_pPlayer->edict() ); + pSqueak->pev->velocity = gpGlobals->v_forward * 200 + m_pPlayer->pev->velocity; +#endif + + // play hunt sound + float flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.5 ) + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_hunt2.wav", 1, ATTN_NORM, 0, 105); + else + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_hunt3.wav", 1, ATTN_NORM, 0, 105); + + m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME; + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + m_fJustThrown = 1; + + m_flNextPrimaryAttack = GetNextAttackDelay(0.3); + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.0; + } + } +} + + +void CSqueak::SecondaryAttack( void ) +{ + +} + + +void CSqueak::WeaponIdle( void ) +{ + if ( m_flTimeWeaponIdle > UTIL_WeaponTimeBase() ) + return; + + if (m_fJustThrown) + { + m_fJustThrown = 0; + + if ( !m_pPlayer->m_rgAmmo[PrimaryAmmoIndex()] ) + { + RetireWeapon(); + return; + } + + SendWeaponAnim( SQUEAK_UP ); + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + return; + } + + int iAnim; + float flRand = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0, 1 ); + if (flRand <= 0.75) + { + iAnim = SQUEAK_IDLE1; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 30.0 / 16 * (2); + } + else if (flRand <= 0.875) + { + iAnim = SQUEAK_FIDGETFIT; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 70.0 / 16.0; + } + else + { + iAnim = SQUEAK_FIDGETNIP; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 80.0 / 16.0; + } + SendWeaponAnim( iAnim ); +} + +#endif diff --git a/dlls/stats.cpp b/dlls/stats.cpp new file mode 100644 index 0000000..0a7ea91 --- /dev/null +++ b/dlls/stats.cpp @@ -0,0 +1,156 @@ +//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: New version of the slider bar +// +// $NoKeywords: $ +//============================================================================= + +#include "extdll.h" +#include "util.h" + +#include "cbase.h" +#include "player.h" +#include "trains.h" +#include "nodes.h" +#include "weapons.h" +#include "soundent.h" +#include "monsters.h" +#include "..\engine\shake.h" +#include "decals.h" +#include "gamerules.h" + + +float AmmoDamage( const char *pName ) +{ + if ( !pName ) + return 0; + + if ( !strcmp( pName, "9mm" ) ) + return gSkillData.plrDmg9MM; + if ( !strcmp( pName, "357" ) ) + return gSkillData.plrDmg357; + if ( !strcmp( pName, "ARgrenades" ) ) + return gSkillData.plrDmgM203Grenade; + if ( !strcmp( pName, "buckshot" ) ) + return gSkillData.plrDmgBuckshot; + if ( !strcmp( pName, "bolts") ) + return gSkillData.plrDmgCrossbowMonster; + if ( !strcmp( pName, "rockets") ) + return gSkillData.plrDmgRPG; + if ( !strcmp( pName, "uranium") ) + return gSkillData.plrDmgGauss; + if ( !strcmp( pName, "Hand Grenade") ) + return gSkillData.plrDmgHandGrenade; + if ( !strcmp( pName, "Satchel Charge") ) + return gSkillData.plrDmgSatchel; + if ( !strcmp( pName, "Trip Mine") ) + return gSkillData.plrDmgTripmine; + + return 0; +} + + +void UpdateStatsFile( float dataTime, char *pMapname, float health, float ammo, int skillLevel ) +{ + FILE *fp; + + fp = fopen( "stats.txt", "a" ); + if ( !fp ) + return; + fprintf( fp, "%6.2f, %6.2f, %6.2f, %s, %2d\n", dataTime, health, ammo, pMapname, skillLevel ); + fclose( fp ); +} + + +#define AMMO_THRESHOLD 10 // This much ammo goes by before it is "interesting" +#define HEALTH_THRESHOLD 10 // Same for health +#define OUTPUT_LATENCY 3 // This many seconds for ammo/health to settle + +typedef struct +{ + int lastAmmo; + float lastHealth; + float lastOutputTime; // NOTE: These times are in "game" time -- a running total of elapsed time since the game started + float nextOutputTime; + float dataTime; + float gameTime; + float lastGameTime; +} TESTSTATS; + +TESTSTATS gStats = {0,0,0,0,0,0,0}; + +void UpdateStats( CBasePlayer *pPlayer ) +{ + int i; + + int ammoCount[ MAX_AMMO_SLOTS ]; + memcpy( ammoCount, pPlayer->m_rgAmmo, MAX_AMMO_SLOTS * sizeof(int) ); + + // Keep a running time, so the graph doesn't overlap + + if ( gpGlobals->time < gStats.lastGameTime ) // Changed level or died, don't b0rk + { + gStats.lastGameTime = gpGlobals->time; + gStats.dataTime = gStats.gameTime; + } + + gStats.gameTime += gpGlobals->time - gStats.lastGameTime; + gStats.lastGameTime = gpGlobals->time; + + for (i = 0; i < MAX_ITEM_TYPES; i++) + { + CBasePlayerItem *p = pPlayer->m_rgpPlayerItems[i]; + while (p) + { + ItemInfo II; + + memset(&II, 0, sizeof(II)); + p->GetItemInfo(&II); + + int index = pPlayer->GetAmmoIndex(II.pszAmmo1); + if ( index >= 0 ) + ammoCount[ index ] += ((CBasePlayerWeapon *)p)->m_iClip; + + p = p->m_pNext; + } + } + + float ammo = 0; + for (i = 1; i < MAX_AMMO_SLOTS; i++) + { + ammo += ammoCount[i] * AmmoDamage( CBasePlayerItem::AmmoInfoArray[i].pszName ); + } + + float health = pPlayer->pev->health + pPlayer->pev->armorvalue * 2; // Armor is 2X health + float ammoDelta = fabs( ammo - gStats.lastAmmo ); + float healthDelta = fabs( health - gStats.lastHealth ); + int forceWrite = 0; + if ( health <= 0 && gStats.lastHealth > 0 ) + forceWrite = 1; + + if ( (ammoDelta > AMMO_THRESHOLD || healthDelta > HEALTH_THRESHOLD) && !forceWrite ) + { + if ( gStats.nextOutputTime == 0 ) + gStats.dataTime = gStats.gameTime; + + gStats.lastAmmo = ammo; + gStats.lastHealth = health; + + gStats.nextOutputTime = gStats.gameTime + OUTPUT_LATENCY; + } + else if ( (gStats.nextOutputTime != 0 && gStats.nextOutputTime < gStats.gameTime) || forceWrite ) + { + UpdateStatsFile( gStats.dataTime, (char *)STRING(gpGlobals->mapname), health, ammo, (int)CVAR_GET_FLOAT("skill") ); + + gStats.lastAmmo = ammo; + gStats.lastHealth = health; + gStats.lastOutputTime = gStats.gameTime; + gStats.nextOutputTime = 0; + } +} + +void InitStats( CBasePlayer *pPlayer ) +{ + gStats.lastGameTime = gpGlobals->time; // Fixup stats time +} + diff --git a/dlls/subs.cpp b/dlls/subs.cpp new file mode 100644 index 0000000..0fa7c44 --- /dev/null +++ b/dlls/subs.cpp @@ -0,0 +1,567 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== subs.cpp ======================================================== + + frequently used global functions + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "nodes.h" +#include "doors.h" + +extern CGraph WorldGraph; + +extern BOOL FEntIsVisible(entvars_t* pev, entvars_t* pevTarget); + +extern DLL_GLOBAL int g_iSkillLevel; + + +// Landmark class +void CPointEntity :: Spawn( void ) +{ + pev->solid = SOLID_NOT; +// UTIL_SetSize(pev, g_vecZero, g_vecZero); +} + + +class CNullEntity : public CBaseEntity +{ +public: + void Spawn( void ); +}; + + +// Null Entity, remove on startup +void CNullEntity :: Spawn( void ) +{ + REMOVE_ENTITY(ENT(pev)); +} +LINK_ENTITY_TO_CLASS(info_null,CNullEntity); + +class CBaseDMStart : public CPointEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + BOOL IsTriggered( CBaseEntity *pEntity ); + +private: +}; + +// These are the new entry points to entities. +LINK_ENTITY_TO_CLASS(info_player_deathmatch,CBaseDMStart); +LINK_ENTITY_TO_CLASS(info_player_start,CPointEntity); +LINK_ENTITY_TO_CLASS(info_landmark,CPointEntity); + +void CBaseDMStart::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "master")) + { + pev->netname = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +BOOL CBaseDMStart::IsTriggered( CBaseEntity *pEntity ) +{ + BOOL master = UTIL_IsMasterTriggered( pev->netname, pEntity ); + + return master; +} + +// This updates global tables that need to know about entities being removed +void CBaseEntity::UpdateOnRemove( void ) +{ + int i; + + if ( FBitSet( pev->flags, FL_GRAPHED ) ) + { + // this entity was a LinkEnt in the world node graph, so we must remove it from + // the graph since we are removing it from the world. + for ( i = 0 ; i < WorldGraph.m_cLinks ; i++ ) + { + if ( WorldGraph.m_pLinkPool [ i ].m_pLinkEnt == pev ) + { + // if this link has a link ent which is the same ent that is removing itself, remove it! + WorldGraph.m_pLinkPool [ i ].m_pLinkEnt = NULL; + } + } + } + if ( pev->globalname ) + gGlobalState.EntitySetState( pev->globalname, GLOBAL_DEAD ); +} + +// Convenient way to delay removing oneself +void CBaseEntity :: SUB_Remove( void ) +{ + UpdateOnRemove(); + if (pev->health > 0) + { + // this situation can screw up monsters who can't tell their entity pointers are invalid. + pev->health = 0; + ALERT( at_aiconsole, "SUB_Remove called on entity with health > 0\n"); + } + + REMOVE_ENTITY(ENT(pev)); +} + + +// Convenient way to explicitly do nothing (passed to functions that require a method) +void CBaseEntity :: SUB_DoNothing( void ) +{ +} + + +// Global Savedata for Delay +TYPEDESCRIPTION CBaseDelay::m_SaveData[] = +{ + DEFINE_FIELD( CBaseDelay, m_flDelay, FIELD_FLOAT ), + DEFINE_FIELD( CBaseDelay, m_iszKillTarget, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CBaseDelay, CBaseEntity ); + +void CBaseDelay :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "delay")) + { + m_flDelay = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "killtarget")) + { + m_iszKillTarget = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + { + CBaseEntity::KeyValue( pkvd ); + } +} + + +/* +============================== +SUB_UseTargets + +If self.delay is set, a DelayedUse entity will be created that will actually +do the SUB_UseTargets after that many seconds have passed. + +Removes all entities with a targetname that match self.killtarget, +and removes them, so some events can remove other triggers. + +Search for (string)targetname in all entities that +match (string)self.target and call their .use function (if they have one) + +============================== +*/ +void CBaseEntity :: SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ) +{ + // + // fire targets + // + if (!FStringNull(pev->target)) + { + FireTargets( STRING(pev->target), pActivator, this, useType, value ); + } +} + + +void FireTargets( const char *targetName, CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + edict_t *pentTarget = NULL; + if ( !targetName ) + return; + + ALERT( at_aiconsole, "Firing: (%s)\n", targetName ); + + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, targetName); + if (FNullEnt(pentTarget)) + break; + + CBaseEntity *pTarget = CBaseEntity::Instance( pentTarget ); + if ( pTarget && !(pTarget->pev->flags & FL_KILLME) ) // Don't use dying ents + { + ALERT( at_aiconsole, "Found: %s, firing (%s)\n", STRING(pTarget->pev->classname), targetName ); + pTarget->Use( pActivator, pCaller, useType, value ); + } + } +} + +LINK_ENTITY_TO_CLASS( DelayedUse, CBaseDelay ); + + +void CBaseDelay :: SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ) +{ + // + // exit immediatly if we don't have a target or kill target + // + if (FStringNull(pev->target) && !m_iszKillTarget) + return; + + // + // check for a delay + // + if (m_flDelay != 0) + { + // create a temp object to fire at a later time + CBaseDelay *pTemp = GetClassPtr( (CBaseDelay *)NULL); + pTemp->pev->classname = MAKE_STRING("DelayedUse"); + + pTemp->pev->nextthink = gpGlobals->time + m_flDelay; + + pTemp->SetThink( &CBaseDelay::DelayThink ); + + // Save the useType + pTemp->pev->button = (int)useType; + pTemp->m_iszKillTarget = m_iszKillTarget; + pTemp->m_flDelay = 0; // prevent "recursion" + pTemp->pev->target = pev->target; + + // HACKHACK + // This wasn't in the release build of Half-Life. We should have moved m_hActivator into this class + // but changing member variable hierarchy would break save/restore without some ugly code. + // This code is not as ugly as that code + if ( pActivator && pActivator->IsPlayer() ) // If a player activates, then save it + { + pTemp->pev->owner = pActivator->edict(); + } + else + { + pTemp->pev->owner = NULL; + } + + return; + } + + // + // kill the killtargets + // + + if ( m_iszKillTarget ) + { + edict_t *pentKillTarget = NULL; + + ALERT( at_aiconsole, "KillTarget: %s\n", STRING(m_iszKillTarget) ); + pentKillTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_iszKillTarget) ); + while ( !FNullEnt(pentKillTarget) ) + { + UTIL_Remove( CBaseEntity::Instance(pentKillTarget) ); + + ALERT( at_aiconsole, "killing %s\n", STRING( pentKillTarget->v.classname ) ); + pentKillTarget = FIND_ENTITY_BY_TARGETNAME( pentKillTarget, STRING(m_iszKillTarget) ); + } + } + + // + // fire targets + // + if (!FStringNull(pev->target)) + { + FireTargets( STRING(pev->target), pActivator, this, useType, value ); + } +} + + +/* +void CBaseDelay :: SUB_UseTargetsEntMethod( void ) +{ + SUB_UseTargets(pev); +} +*/ + +/* +QuakeEd only writes a single float for angles (bad idea), so up and down are +just constant angles. +*/ +void SetMovedir( entvars_t *pev ) +{ + if (pev->angles == Vector(0, -1, 0)) + { + pev->movedir = Vector(0, 0, 1); + } + else if (pev->angles == Vector(0, -2, 0)) + { + pev->movedir = Vector(0, 0, -1); + } + else + { + UTIL_MakeVectors(pev->angles); + pev->movedir = gpGlobals->v_forward; + } + + pev->angles = g_vecZero; +} + + + + +void CBaseDelay::DelayThink( void ) +{ + CBaseEntity *pActivator = NULL; + + if ( pev->owner != NULL ) // A player activated this on delay + { + pActivator = CBaseEntity::Instance( pev->owner ); + } + // The use type is cached (and stashed) in pev->button + SUB_UseTargets( pActivator, (USE_TYPE)pev->button, 0 ); + REMOVE_ENTITY(ENT(pev)); +} + + +// Global Savedata for Toggle +TYPEDESCRIPTION CBaseToggle::m_SaveData[] = +{ + DEFINE_FIELD( CBaseToggle, m_toggle_state, FIELD_INTEGER ), + DEFINE_FIELD( CBaseToggle, m_flActivateFinished, FIELD_TIME ), + DEFINE_FIELD( CBaseToggle, m_flMoveDistance, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flWait, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flLip, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flTWidth, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flTLength, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_vecPosition1, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_vecPosition2, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_vecAngle1, FIELD_VECTOR ), // UNDONE: Position could go through transition, but also angle? + DEFINE_FIELD( CBaseToggle, m_vecAngle2, FIELD_VECTOR ), // UNDONE: Position could go through transition, but also angle? + DEFINE_FIELD( CBaseToggle, m_cTriggersLeft, FIELD_INTEGER ), + DEFINE_FIELD( CBaseToggle, m_flHeight, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_hActivator, FIELD_EHANDLE ), + DEFINE_FIELD( CBaseToggle, m_pfnCallWhenMoveDone, FIELD_FUNCTION ), + DEFINE_FIELD( CBaseToggle, m_vecFinalDest, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_vecFinalAngle, FIELD_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_sMaster, FIELD_STRING), + DEFINE_FIELD( CBaseToggle, m_bitsDamageInflict, FIELD_INTEGER ), // damage type inflicted +}; +IMPLEMENT_SAVERESTORE( CBaseToggle, CBaseAnimating ); + + +void CBaseToggle::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "lip")) + { + m_flLip = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "master")) + { + m_sMaster = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "distance")) + { + m_flMoveDistance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + +/* +============= +LinearMove + +calculate pev->velocity and pev->nextthink to reach vecDest from +pev->origin traveling at flSpeed +=============== +*/ +void CBaseToggle :: LinearMove( Vector vecDest, float flSpeed ) +{ + ASSERTSZ(flSpeed != 0, "LinearMove: no speed is defined!"); +// ASSERTSZ(m_pfnCallWhenMoveDone != NULL, "LinearMove: no post-move function defined"); + + m_vecFinalDest = vecDest; + + // Already there? + if (vecDest == pev->origin) + { + LinearMoveDone(); + return; + } + + // set destdelta to the vector needed to move + Vector vecDestDelta = vecDest - pev->origin; + + // divide vector length by speed to get time to reach dest + float flTravelTime = vecDestDelta.Length() / flSpeed; + + // set nextthink to trigger a call to LinearMoveDone when dest is reached + pev->nextthink = pev->ltime + flTravelTime; + SetThink( &CBaseToggle::LinearMoveDone ); + + // scale the destdelta vector by the time spent traveling to get velocity + pev->velocity = vecDestDelta / flTravelTime; +} + + +/* +============ +After moving, set origin to exact final destination, call "move done" function +============ +*/ +void CBaseToggle :: LinearMoveDone( void ) +{ + Vector delta = m_vecFinalDest - pev->origin; + float error = delta.Length(); + if ( error > 0.03125 ) + { + LinearMove( m_vecFinalDest, 100 ); + return; + } + + UTIL_SetOrigin(pev, m_vecFinalDest); + pev->velocity = g_vecZero; + pev->nextthink = -1; + if ( m_pfnCallWhenMoveDone ) + (this->*m_pfnCallWhenMoveDone)(); +} + +BOOL CBaseToggle :: IsLockedByMaster( void ) +{ + if (m_sMaster && !UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + return TRUE; + else + return FALSE; +} + +/* +============= +AngularMove + +calculate pev->velocity and pev->nextthink to reach vecDest from +pev->origin traveling at flSpeed +Just like LinearMove, but rotational. +=============== +*/ +void CBaseToggle :: AngularMove( Vector vecDestAngle, float flSpeed ) +{ + ASSERTSZ(flSpeed != 0, "AngularMove: no speed is defined!"); +// ASSERTSZ(m_pfnCallWhenMoveDone != NULL, "AngularMove: no post-move function defined"); + + m_vecFinalAngle = vecDestAngle; + + // Already there? + if (vecDestAngle == pev->angles) + { + AngularMoveDone(); + return; + } + + // set destdelta to the vector needed to move + Vector vecDestDelta = vecDestAngle - pev->angles; + + // divide by speed to get time to reach dest + float flTravelTime = vecDestDelta.Length() / flSpeed; + + // set nextthink to trigger a call to AngularMoveDone when dest is reached + pev->nextthink = pev->ltime + flTravelTime; + SetThink( &CBaseToggle::AngularMoveDone ); + + // scale the destdelta vector by the time spent traveling to get velocity + pev->avelocity = vecDestDelta / flTravelTime; +} + + +/* +============ +After rotating, set angle to exact final angle, call "move done" function +============ +*/ +void CBaseToggle :: AngularMoveDone( void ) +{ + pev->angles = m_vecFinalAngle; + pev->avelocity = g_vecZero; + pev->nextthink = -1; + if ( m_pfnCallWhenMoveDone ) + (this->*m_pfnCallWhenMoveDone)(); +} + + +float CBaseToggle :: AxisValue( int flags, const Vector &angles ) +{ + if ( FBitSet(flags, SF_DOOR_ROTATE_Z) ) + return angles.z; + if ( FBitSet(flags, SF_DOOR_ROTATE_X) ) + return angles.x; + + return angles.y; +} + + +void CBaseToggle :: AxisDir( entvars_t *pev ) +{ + if ( FBitSet(pev->spawnflags, SF_DOOR_ROTATE_Z) ) + pev->movedir = Vector ( 0, 0, 1 ); // around z-axis + else if ( FBitSet(pev->spawnflags, SF_DOOR_ROTATE_X) ) + pev->movedir = Vector ( 1, 0, 0 ); // around x-axis + else + pev->movedir = Vector ( 0, 1, 0 ); // around y-axis +} + + +float CBaseToggle :: AxisDelta( int flags, const Vector &angle1, const Vector &angle2 ) +{ + if ( FBitSet (flags, SF_DOOR_ROTATE_Z) ) + return angle1.z - angle2.z; + + if ( FBitSet (flags, SF_DOOR_ROTATE_X) ) + return angle1.x - angle2.x; + + return angle1.y - angle2.y; +} + + +/* +============= +FEntIsVisible + +returns TRUE if the passed entity is visible to caller, even if not infront () +============= +*/ + BOOL +FEntIsVisible( + entvars_t* pev, + entvars_t* pevTarget) + { + Vector vecSpot1 = pev->origin + pev->view_ofs; + Vector vecSpot2 = pevTarget->origin + pevTarget->view_ofs; + TraceResult tr; + + UTIL_TraceLine(vecSpot1, vecSpot2, ignore_monsters, ENT(pev), &tr); + + if (tr.fInOpen && tr.fInWater) + return FALSE; // sight line crossed contents + + if (tr.flFraction == 1) + return TRUE; + + return FALSE; + } + + diff --git a/dlls/talkmonster.cpp b/dlls/talkmonster.cpp new file mode 100644 index 0000000..36dbf69 --- /dev/null +++ b/dlls/talkmonster.cpp @@ -0,0 +1,1472 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "talkmonster.h" +#include "defaultai.h" +#include "scripted.h" +#include "soundent.h" +#include "animation.h" + +//========================================================= +// Talking monster base class +// Used for scientists and barneys +//========================================================= +float CTalkMonster::g_talkWaitTime = 0; // time delay until it's ok to speak: used so that two NPCs don't talk at once + +// NOTE: m_voicePitch & m_szGrp should be fixed up by precache each save/restore + +TYPEDESCRIPTION CTalkMonster::m_SaveData[] = +{ + DEFINE_FIELD( CTalkMonster, m_bitsSaid, FIELD_INTEGER ), + DEFINE_FIELD( CTalkMonster, m_nSpeak, FIELD_INTEGER ), + + // Recalc'ed in Precache() + // DEFINE_FIELD( CTalkMonster, m_voicePitch, FIELD_INTEGER ), + // DEFINE_FIELD( CTalkMonster, m_szGrp, FIELD_??? ), + DEFINE_FIELD( CTalkMonster, m_useTime, FIELD_TIME ), + DEFINE_FIELD( CTalkMonster, m_iszUse, FIELD_STRING ), + DEFINE_FIELD( CTalkMonster, m_iszUnUse, FIELD_STRING ), + DEFINE_FIELD( CTalkMonster, m_flLastSaidSmelled, FIELD_TIME ), + DEFINE_FIELD( CTalkMonster, m_flStopTalkTime, FIELD_TIME ), + DEFINE_FIELD( CTalkMonster, m_hTalkTarget, FIELD_EHANDLE ), +}; + +IMPLEMENT_SAVERESTORE( CTalkMonster, CBaseMonster ); + +// array of friend names +char *CTalkMonster::m_szFriends[TLK_CFRIENDS] = +{ + "monster_barney", + "monster_scientist", + "monster_sitting_scientist", +}; + + +//========================================================= +// AI Schedules Specific to talking monsters +//========================================================= + +Task_t tlIdleResponse[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_IDLE },// Stop and listen + { TASK_WAIT, (float)0.5 },// Wait until sure it's me they are talking to + { TASK_TLK_EYECONTACT, (float)0 },// Wait until speaker is done + { TASK_TLK_RESPOND, (float)0 },// Wait and then say my response + { TASK_TLK_IDEALYAW, (float)0 },// look at who I'm talking to + { TASK_FACE_IDEAL, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 }, + { TASK_TLK_EYECONTACT, (float)0 },// Wait until speaker is done +}; + +Schedule_t slIdleResponse[] = +{ + { + tlIdleResponse, + ARRAYSIZE ( tlIdleResponse ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "Idle Response" + + }, +}; + +Task_t tlIdleSpeak[] = +{ + { TASK_TLK_SPEAK, (float)0 },// question or remark + { TASK_TLK_IDEALYAW, (float)0 },// look at who I'm talking to + { TASK_FACE_IDEAL, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 }, + { TASK_TLK_EYECONTACT, (float)0 }, + { TASK_WAIT_RANDOM, (float)0.5 }, +}; + +Schedule_t slIdleSpeak[] = +{ + { + tlIdleSpeak, + ARRAYSIZE ( tlIdleSpeak ), + bits_COND_NEW_ENEMY | + bits_COND_CLIENT_PUSH | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "Idle Speak" + }, +}; + +Task_t tlIdleSpeakWait[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 },// Stop and talk + { TASK_TLK_SPEAK, (float)0 },// question or remark + { TASK_TLK_EYECONTACT, (float)0 },// + { TASK_WAIT, (float)2 },// wait - used when sci is in 'use' mode to keep head turned +}; + +Schedule_t slIdleSpeakWait[] = +{ + { + tlIdleSpeakWait, + ARRAYSIZE ( tlIdleSpeakWait ), + bits_COND_NEW_ENEMY | + bits_COND_CLIENT_PUSH | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "Idle Speak Wait" + }, +}; + +Task_t tlIdleHello[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 },// Stop and talk + { TASK_TLK_HELLO, (float)0 },// Try to say hello to player + { TASK_TLK_EYECONTACT, (float)0 }, + { TASK_WAIT, (float)0.5 },// wait a bit + { TASK_TLK_HELLO, (float)0 },// Try to say hello to player + { TASK_TLK_EYECONTACT, (float)0 }, + { TASK_WAIT, (float)0.5 },// wait a bit + { TASK_TLK_HELLO, (float)0 },// Try to say hello to player + { TASK_TLK_EYECONTACT, (float)0 }, + { TASK_WAIT, (float)0.5 },// wait a bit + { TASK_TLK_HELLO, (float)0 },// Try to say hello to player + { TASK_TLK_EYECONTACT, (float)0 }, + { TASK_WAIT, (float)0.5 },// wait a bit + +}; + +Schedule_t slIdleHello[] = +{ + { + tlIdleHello, + ARRAYSIZE ( tlIdleHello ), + bits_COND_NEW_ENEMY | + bits_COND_CLIENT_PUSH | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT, + "Idle Hello" + }, +}; + +Task_t tlIdleStopShooting[] = +{ + { TASK_TLK_STOPSHOOTING, (float)0 },// tell player to stop shooting friend + // { TASK_TLK_EYECONTACT, (float)0 },// look at the player +}; + +Schedule_t slIdleStopShooting[] = +{ + { + tlIdleStopShooting, + ARRAYSIZE ( tlIdleStopShooting ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + 0, + "Idle Stop Shooting" + }, +}; + +Task_t tlMoveAway[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_MOVE_AWAY_FAIL }, + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_MOVE_AWAY_PATH, (float)100 }, + { TASK_WALK_PATH_FOR_UNITS, (float)100 }, + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_PLAYER, (float)0.5 }, +}; + +Schedule_t slMoveAway[] = +{ + { + tlMoveAway, + ARRAYSIZE ( tlMoveAway ), + 0, + 0, + "MoveAway" + }, +}; + + +Task_t tlMoveAwayFail[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_PLAYER, (float)0.5 }, +}; + +Schedule_t slMoveAwayFail[] = +{ + { + tlMoveAwayFail, + ARRAYSIZE ( tlMoveAwayFail ), + 0, + 0, + "MoveAwayFail" + }, +}; + + + +Task_t tlMoveAwayFollow[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_TARGET_FACE }, + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_MOVE_AWAY_PATH, (float)100 }, + { TASK_WALK_PATH_FOR_UNITS, (float)100 }, + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE }, +}; + +Schedule_t slMoveAwayFollow[] = +{ + { + tlMoveAwayFollow, + ARRAYSIZE ( tlMoveAwayFollow ), + 0, + 0, + "MoveAwayFollow" + }, +}; + +Task_t tlTlkIdleWatchClient[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_TLK_LOOK_AT_CLIENT, (float)6 }, +}; + +Task_t tlTlkIdleWatchClientStare[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_TLK_CLIENT_STARE, (float)6 }, + { TASK_TLK_STARE, (float)0 }, + { TASK_TLK_IDEALYAW, (float)0 },// look at who I'm talking to + { TASK_FACE_IDEAL, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 }, + { TASK_TLK_EYECONTACT, (float)0 }, +}; + +Schedule_t slTlkIdleWatchClient[] = +{ + { + tlTlkIdleWatchClient, + ARRAYSIZE ( tlTlkIdleWatchClient ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL | + bits_COND_CLIENT_PUSH | + bits_COND_CLIENT_UNSEEN | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags - change these, and you'll break the talking code. + //bits_SOUND_PLAYER | + //bits_SOUND_WORLD | + + bits_SOUND_DANGER | + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "TlkIdleWatchClient" + }, + + { + tlTlkIdleWatchClientStare, + ARRAYSIZE ( tlTlkIdleWatchClientStare ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL | + bits_COND_CLIENT_PUSH | + bits_COND_CLIENT_UNSEEN | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags - change these, and you'll break the talking code. + //bits_SOUND_PLAYER | + //bits_SOUND_WORLD | + + bits_SOUND_DANGER | + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "TlkIdleWatchClientStare" + }, +}; + + +Task_t tlTlkIdleEyecontact[] = +{ + { TASK_TLK_IDEALYAW, (float)0 },// look at who I'm talking to + { TASK_FACE_IDEAL, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 }, + { TASK_TLK_EYECONTACT, (float)0 },// Wait until speaker is done +}; + +Schedule_t slTlkIdleEyecontact[] = +{ + { + tlTlkIdleEyecontact, + ARRAYSIZE ( tlTlkIdleEyecontact ), + bits_COND_NEW_ENEMY | + bits_COND_CLIENT_PUSH | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "TlkIdleEyecontact" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CTalkMonster ) +{ + slIdleResponse, + slIdleSpeak, + slIdleHello, + slIdleSpeakWait, + slIdleStopShooting, + slMoveAway, + slMoveAwayFollow, + slMoveAwayFail, + slTlkIdleWatchClient, + &slTlkIdleWatchClient[ 1 ], + slTlkIdleEyecontact, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CTalkMonster, CBaseMonster ); + + +void CTalkMonster :: SetActivity ( Activity newActivity ) +{ + if (newActivity == ACT_IDLE && IsTalking() ) + newActivity = ACT_SIGNAL3; + + if ( newActivity == ACT_SIGNAL3 && (LookupActivity ( ACT_SIGNAL3 ) == ACTIVITY_NOT_AVAILABLE)) + newActivity = ACT_IDLE; + + CBaseMonster::SetActivity( newActivity ); +} + + +void CTalkMonster :: StartTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_TLK_SPEAK: + // ask question or make statement + FIdleSpeak(); + TaskComplete(); + break; + + case TASK_TLK_RESPOND: + // respond to question + IdleRespond(); + TaskComplete(); + break; + + case TASK_TLK_HELLO: + // greet player + FIdleHello(); + TaskComplete(); + break; + + + case TASK_TLK_STARE: + // let the player know I know he's staring at me. + FIdleStare(); + TaskComplete(); + break; + + case TASK_FACE_PLAYER: + case TASK_TLK_LOOK_AT_CLIENT: + case TASK_TLK_CLIENT_STARE: + // track head to the client for a while. + m_flWaitFinished = gpGlobals->time + pTask->flData; + break; + + case TASK_TLK_EYECONTACT: + break; + + case TASK_TLK_IDEALYAW: + if (m_hTalkTarget != NULL) + { + pev->yaw_speed = 60; + float yaw = VecToYaw(m_hTalkTarget->pev->origin - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + if (yaw < 0) + { + pev->ideal_yaw = min( yaw + 45, 0 ) + pev->angles.y; + } + else + { + pev->ideal_yaw = max( yaw - 45, 0 ) + pev->angles.y; + } + } + TaskComplete(); + break; + + case TASK_TLK_HEADRESET: + // reset head position after looking at something + m_hTalkTarget = NULL; + TaskComplete(); + break; + + case TASK_TLK_STOPSHOOTING: + // tell player to stop shooting + PlaySentence( m_szGrp[TLK_NOSHOOT], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_NORM ); + TaskComplete(); + break; + + case TASK_CANT_FOLLOW: + StopFollowing( FALSE ); + PlaySentence( m_szGrp[TLK_STOP], RANDOM_FLOAT(2, 2.5), VOL_NORM, ATTN_NORM ); + TaskComplete(); + break; + + case TASK_WALK_PATH_FOR_UNITS: + m_movementActivity = ACT_WALK; + break; + + case TASK_MOVE_AWAY_PATH: + { + Vector dir = pev->angles; + dir.y = pev->ideal_yaw + 180; + Vector move; + + UTIL_MakeVectorsPrivate( dir, move, NULL, NULL ); + dir = pev->origin + move * pTask->flData; + if ( MoveToLocation( ACT_WALK, 2, dir ) ) + { + TaskComplete(); + } + else if ( FindCover( pev->origin, pev->view_ofs, 0, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + 2; + TaskComplete(); + } + else + { + // nowhere to go? + TaskFail(); + } + } + break; + + case TASK_PLAY_SCRIPT: + m_hTalkTarget = NULL; + CBaseMonster::StartTask( pTask ); + break; + + default: + CBaseMonster::StartTask( pTask ); + } +} + + +void CTalkMonster :: RunTask( Task_t *pTask ) +{ + switch( pTask->iTask ) + { + case TASK_TLK_CLIENT_STARE: + case TASK_TLK_LOOK_AT_CLIENT: + + edict_t *pPlayer; + + // track head to the client for a while. + if ( m_MonsterState == MONSTERSTATE_IDLE && + !IsMoving() && + !IsTalking() ) + { + // Get edict for one player + pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + if ( pPlayer ) + { + IdleHeadTurn( pPlayer->v.origin ); + } + } + else + { + // started moving or talking + TaskFail(); + return; + } + + if ( pTask->iTask == TASK_TLK_CLIENT_STARE ) + { + // fail out if the player looks away or moves away. + if ( ( pPlayer->v.origin - pev->origin ).Length2D() > TLK_STARE_DIST ) + { + // player moved away. + TaskFail(); + } + + UTIL_MakeVectors( pPlayer->v.angles ); + if ( UTIL_DotPoints( pPlayer->v.origin, pev->origin, gpGlobals->v_forward ) < m_flFieldOfView ) + { + // player looked away + TaskFail(); + } + } + + if ( gpGlobals->time > m_flWaitFinished ) + { + TaskComplete(); + } + break; + + case TASK_FACE_PLAYER: + { + // Get edict for one player + edict_t *pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + if ( pPlayer ) + { + MakeIdealYaw ( pPlayer->v.origin ); + ChangeYaw ( pev->yaw_speed ); + IdleHeadTurn( pPlayer->v.origin ); + if ( gpGlobals->time > m_flWaitFinished && FlYawDiff() < 10 ) + { + TaskComplete(); + } + } + else + { + TaskFail(); + } + } + break; + + case TASK_TLK_EYECONTACT: + if (!IsMoving() && IsTalking() && m_hTalkTarget != NULL) + { + // ALERT( at_console, "waiting %f\n", m_flStopTalkTime - gpGlobals->time ); + IdleHeadTurn( m_hTalkTarget->pev->origin ); + } + else + { + TaskComplete(); + } + break; + + case TASK_WALK_PATH_FOR_UNITS: + { + float distance; + + distance = (m_vecLastPosition - pev->origin).Length2D(); + + // Walk path until far enough away + if ( distance > pTask->flData || MovementIsComplete() ) + { + TaskComplete(); + RouteClear(); // Stop moving + } + } + break; + case TASK_WAIT_FOR_MOVEMENT: + if (IsTalking() && m_hTalkTarget != NULL) + { + // ALERT(at_console, "walking, talking\n"); + IdleHeadTurn( m_hTalkTarget->pev->origin ); + } + else + { + IdleHeadTurn( pev->origin ); + // override so that during walk, a scientist may talk and greet player + FIdleHello(); + if (RANDOM_LONG(0,m_nSpeak * 20) == 0) + { + FIdleSpeak(); + } + } + + CBaseMonster::RunTask( pTask ); + if (TaskIsComplete()) + IdleHeadTurn( pev->origin ); + break; + + default: + if (IsTalking() && m_hTalkTarget != NULL) + { + IdleHeadTurn( m_hTalkTarget->pev->origin ); + } + else + { + SetBoneController( 0, 0 ); + } + CBaseMonster::RunTask( pTask ); + } +} + + +void CTalkMonster :: Killed( entvars_t *pevAttacker, int iGib ) +{ + // If a client killed me (unless I was already Barnacle'd), make everyone else mad/afraid of him + if ( (pevAttacker->flags & FL_CLIENT) && m_MonsterState != MONSTERSTATE_PRONE ) + { + AlertFriends(); + LimitFollowers( CBaseEntity::Instance(pevAttacker), 0 ); + } + + m_hTargetEnt = NULL; + // Don't finish that sentence + StopTalking(); + SetUse( NULL ); + CBaseMonster::Killed( pevAttacker, iGib ); +} + + + +CBaseEntity *CTalkMonster::EnumFriends( CBaseEntity *pPrevious, int listNumber, BOOL bTrace ) +{ + CBaseEntity *pFriend = pPrevious; + char *pszFriend; + TraceResult tr; + Vector vecCheck; + + pszFriend = m_szFriends[ FriendNumber(listNumber) ]; + while (pFriend = UTIL_FindEntityByClassname( pFriend, pszFriend )) + { + if (pFriend == this || !pFriend->IsAlive()) + // don't talk to self or dead people + continue; + if ( bTrace ) + { + vecCheck = pFriend->pev->origin; + vecCheck.z = pFriend->pev->absmax.z; + + UTIL_TraceLine( pev->origin, vecCheck, ignore_monsters, ENT(pev), &tr); + } + else + tr.flFraction = 1.0; + + if (tr.flFraction == 1.0) + { + return pFriend; + } + } + + return NULL; +} + + +void CTalkMonster::AlertFriends( void ) +{ + CBaseEntity *pFriend = NULL; + int i; + + // for each friend in this bsp... + for ( i = 0; i < TLK_CFRIENDS; i++ ) + { + while (pFriend = EnumFriends( pFriend, i, TRUE )) + { + CBaseMonster *pMonster = pFriend->MyMonsterPointer(); + if ( pMonster->IsAlive() ) + { + // don't provoke a friend that's playing a death animation. They're a goner + pMonster->m_afMemory |= bits_MEMORY_PROVOKED; + } + } + } +} + + + +void CTalkMonster::ShutUpFriends( void ) +{ + CBaseEntity *pFriend = NULL; + int i; + + // for each friend in this bsp... + for ( i = 0; i < TLK_CFRIENDS; i++ ) + { + while (pFriend = EnumFriends( pFriend, i, TRUE )) + { + CBaseMonster *pMonster = pFriend->MyMonsterPointer(); + if ( pMonster ) + { + pMonster->SentenceStop(); + } + } + } +} + + +// UNDONE: Keep a follow time in each follower, make a list of followers in this function and do LRU +// UNDONE: Check this in Restore to keep restored monsters from joining a full list of followers +void CTalkMonster::LimitFollowers( CBaseEntity *pPlayer, int maxFollowers ) +{ + CBaseEntity *pFriend = NULL; + int i, count; + + count = 0; + // for each friend in this bsp... + for ( i = 0; i < TLK_CFRIENDS; i++ ) + { + while (pFriend = EnumFriends( pFriend, i, FALSE )) + { + CBaseMonster *pMonster = pFriend->MyMonsterPointer(); + if ( pMonster ) + { + if ( pMonster->m_hTargetEnt == pPlayer ) + { + count++; + if ( count > maxFollowers ) + pMonster->StopFollowing( TRUE ); + } + } + } + } +} + + +float CTalkMonster::TargetDistance( void ) +{ + // If we lose the player, or he dies, return a really large distance + if ( m_hTargetEnt == NULL || !m_hTargetEnt->IsAlive() ) + return 1e6; + + return (m_hTargetEnt->pev->origin - pev->origin).Length(); +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CTalkMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case SCRIPT_EVENT_SENTENCE_RND1: // Play a named sentence group 25% of the time + if (RANDOM_LONG(0,99) < 75) + break; + // fall through... + case SCRIPT_EVENT_SENTENCE: // Play a named sentence group + ShutUpFriends(); + PlaySentence( pEvent->options, RANDOM_FLOAT(2.8, 3.4), VOL_NORM, ATTN_IDLE ); + //ALERT(at_console, "script event speak\n"); + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +// monsters derived from ctalkmonster should call this in precache() + +void CTalkMonster :: TalkInit( void ) +{ + // every new talking monster must reset this global, otherwise + // when a level is loaded, nobody will talk (time is reset to 0) + + CTalkMonster::g_talkWaitTime = 0; + + m_voicePitch = 100; +} +//========================================================= +// FindNearestFriend +// Scan for nearest, visible friend. If fPlayer is true, look for +// nearest player +//========================================================= +CBaseEntity *CTalkMonster :: FindNearestFriend(BOOL fPlayer) +{ + CBaseEntity *pFriend = NULL; + CBaseEntity *pNearest = NULL; + float range = 10000000.0; + TraceResult tr; + Vector vecStart = pev->origin; + Vector vecCheck; + int i; + char *pszFriend; + int cfriends; + + vecStart.z = pev->absmax.z; + + if (fPlayer) + cfriends = 1; + else + cfriends = TLK_CFRIENDS; + + // for each type of friend... + + for (i = cfriends-1; i > -1; i--) + { + if (fPlayer) + pszFriend = "player"; + else + pszFriend = m_szFriends[FriendNumber(i)]; + + if (!pszFriend) + continue; + + // for each friend in this bsp... + while (pFriend = UTIL_FindEntityByClassname( pFriend, pszFriend )) + { + if (pFriend == this || !pFriend->IsAlive()) + // don't talk to self or dead people + continue; + + CBaseMonster *pMonster = pFriend->MyMonsterPointer(); + + // If not a monster for some reason, or in a script, or prone + if ( !pMonster || pMonster->m_MonsterState == MONSTERSTATE_SCRIPT || pMonster->m_MonsterState == MONSTERSTATE_PRONE ) + continue; + + vecCheck = pFriend->pev->origin; + vecCheck.z = pFriend->pev->absmax.z; + + // if closer than previous friend, and in range, see if he's visible + + if (range > (vecStart - vecCheck).Length()) + { + UTIL_TraceLine(vecStart, vecCheck, ignore_monsters, ENT(pev), &tr); + + if (tr.flFraction == 1.0) + { + // visible and in range, this is the new nearest scientist + if ((vecStart - vecCheck).Length() < TALKRANGE_MIN) + { + pNearest = pFriend; + range = (vecStart - vecCheck).Length(); + } + } + } + } + } + return pNearest; +} + +int CTalkMonster :: GetVoicePitch( void ) +{ + return m_voicePitch + RANDOM_LONG(0,3); +} + + +void CTalkMonster :: Touch( CBaseEntity *pOther ) +{ + // Did the player touch me? + if ( pOther->IsPlayer() ) + { + // Ignore if pissed at player + if ( m_afMemory & bits_MEMORY_PROVOKED ) + return; + + // Stay put during speech + if ( IsTalking() ) + return; + + // Heuristic for determining if the player is pushing me away + float speed = fabs(pOther->pev->velocity.x) + fabs(pOther->pev->velocity.y); + if ( speed > 50 ) + { + SetConditions( bits_COND_CLIENT_PUSH ); + MakeIdealYaw( pOther->pev->origin ); + } + } +} + + + +//========================================================= +// IdleRespond +// Respond to a previous question +//========================================================= +void CTalkMonster :: IdleRespond( void ) +{ + int pitch = GetVoicePitch(); + + // play response + PlaySentence( m_szGrp[TLK_ANSWER], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); +} + +int CTalkMonster :: FOkToSpeak( void ) +{ + // if in the grip of a barnacle, don't speak + if ( m_MonsterState == MONSTERSTATE_PRONE || m_IdealMonsterState == MONSTERSTATE_PRONE ) + { + return FALSE; + } + + // if not alive, certainly don't speak + if ( pev->deadflag != DEAD_NO ) + { + return FALSE; + } + + // if someone else is talking, don't speak + if (gpGlobals->time <= CTalkMonster::g_talkWaitTime) + return FALSE; + + if ( pev->spawnflags & SF_MONSTER_GAG ) + return FALSE; + + if ( m_MonsterState == MONSTERSTATE_PRONE ) + return FALSE; + + // if player is not in pvs, don't speak + if (!IsAlive() || FNullEnt(FIND_CLIENT_IN_PVS(edict()))) + return FALSE; + + // don't talk if you're in combat + if (m_hEnemy != NULL && FVisible( m_hEnemy )) + return FALSE; + + return TRUE; +} + + +int CTalkMonster::CanPlaySentence( BOOL fDisregardState ) +{ + if ( fDisregardState ) + return CBaseMonster::CanPlaySentence( fDisregardState ); + return FOkToSpeak(); +} + +//========================================================= +// FIdleStare +//========================================================= +int CTalkMonster :: FIdleStare( void ) +{ + if (!FOkToSpeak()) + return FALSE; + + PlaySentence( m_szGrp[TLK_STARE], RANDOM_FLOAT(5, 7.5), VOL_NORM, ATTN_IDLE ); + + m_hTalkTarget = FindNearestFriend( TRUE ); + return TRUE; +} + +//========================================================= +// IdleHello +// Try to greet player first time he's seen +//========================================================= +int CTalkMonster :: FIdleHello( void ) +{ + if (!FOkToSpeak()) + return FALSE; + + // if this is first time scientist has seen player, greet him + if (!FBitSet(m_bitsSaid, bit_saidHelloPlayer)) + { + // get a player + CBaseEntity *pPlayer = FindNearestFriend(TRUE); + + if (pPlayer) + { + if (FInViewCone(pPlayer) && FVisible(pPlayer)) + { + m_hTalkTarget = pPlayer; + + if (FBitSet(pev->spawnflags, SF_MONSTER_PREDISASTER)) + PlaySentence( m_szGrp[TLK_PHELLO], RANDOM_FLOAT(3, 3.5), VOL_NORM, ATTN_IDLE ); + else + PlaySentence( m_szGrp[TLK_HELLO], RANDOM_FLOAT(3, 3.5), VOL_NORM, ATTN_IDLE ); + + SetBits(m_bitsSaid, bit_saidHelloPlayer); + + return TRUE; + } + } + } + return FALSE; +} + + +// turn head towards supplied origin +void CTalkMonster :: IdleHeadTurn( Vector &vecFriend ) +{ + // turn head in desired direction only if ent has a turnable head + if (m_afCapability & bits_CAP_TURN_HEAD) + { + float yaw = VecToYaw(vecFriend - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + // turn towards vector + SetBoneController( 0, yaw ); + } +} + +//========================================================= +// FIdleSpeak +// ask question of nearby friend, or make statement +//========================================================= +int CTalkMonster :: FIdleSpeak ( void ) +{ + // try to start a conversation, or make statement + int pitch; + const char *szIdleGroup; + const char *szQuestionGroup; + float duration; + + if (!FOkToSpeak()) + return FALSE; + + // set idle groups based on pre/post disaster + if (FBitSet(pev->spawnflags, SF_MONSTER_PREDISASTER)) + { + szIdleGroup = m_szGrp[TLK_PIDLE]; + szQuestionGroup = m_szGrp[TLK_PQUESTION]; + // set global min delay for next conversation + duration = RANDOM_FLOAT(4.8, 5.2); + } + else + { + szIdleGroup = m_szGrp[TLK_IDLE]; + szQuestionGroup = m_szGrp[TLK_QUESTION]; + // set global min delay for next conversation + duration = RANDOM_FLOAT(2.8, 3.2); + + } + + pitch = GetVoicePitch(); + + // player using this entity is alive and wounded? + CBaseEntity *pTarget = m_hTargetEnt; + + if ( pTarget != NULL ) + { + if ( pTarget->IsPlayer() ) + { + if ( pTarget->IsAlive() ) + { + m_hTalkTarget = m_hTargetEnt; + if (!FBitSet(m_bitsSaid, bit_saidDamageHeavy) && + (m_hTargetEnt->pev->health <= m_hTargetEnt->pev->max_health / 8)) + { + //EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, m_szGrp[TLK_PLHURT3], 1.0, ATTN_IDLE, 0, pitch); + PlaySentence( m_szGrp[TLK_PLHURT3], duration, VOL_NORM, ATTN_IDLE ); + SetBits(m_bitsSaid, bit_saidDamageHeavy); + return TRUE; + } + else if (!FBitSet(m_bitsSaid, bit_saidDamageMedium) && + (m_hTargetEnt->pev->health <= m_hTargetEnt->pev->max_health / 4)) + { + //EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, m_szGrp[TLK_PLHURT2], 1.0, ATTN_IDLE, 0, pitch); + PlaySentence( m_szGrp[TLK_PLHURT2], duration, VOL_NORM, ATTN_IDLE ); + SetBits(m_bitsSaid, bit_saidDamageMedium); + return TRUE; + } + else if (!FBitSet(m_bitsSaid, bit_saidDamageLight) && + (m_hTargetEnt->pev->health <= m_hTargetEnt->pev->max_health / 2)) + { + //EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, m_szGrp[TLK_PLHURT1], 1.0, ATTN_IDLE, 0, pitch); + PlaySentence( m_szGrp[TLK_PLHURT1], duration, VOL_NORM, ATTN_IDLE ); + SetBits(m_bitsSaid, bit_saidDamageLight); + return TRUE; + } + } + else + { + //!!!KELLY - here's a cool spot to have the talkmonster talk about the dead player if we want. + // "Oh dear, Gordon Freeman is dead!" -Scientist + // "Damn, I can't do this without you." -Barney + } + } + } + + // if there is a friend nearby to speak to, play sentence, set friend's response time, return + CBaseEntity *pFriend = FindNearestFriend(FALSE); + + if (pFriend && !(pFriend->IsMoving()) && (RANDOM_LONG(0,99) < 75)) + { + PlaySentence( szQuestionGroup, duration, VOL_NORM, ATTN_IDLE ); + //SENTENCEG_PlayRndSz( ENT(pev), szQuestionGroup, 1.0, ATTN_IDLE, 0, pitch ); + + // force friend to answer + CTalkMonster *pTalkMonster = (CTalkMonster *)pFriend; + m_hTalkTarget = pFriend; + pTalkMonster->SetAnswerQuestion( this ); // UNDONE: This is EVIL!!! + pTalkMonster->m_flStopTalkTime = m_flStopTalkTime; + + m_nSpeak++; + return TRUE; + } + + // otherwise, play an idle statement, try to face client when making a statement. + if ( RANDOM_LONG(0,1) ) + { + //SENTENCEG_PlayRndSz( ENT(pev), szIdleGroup, 1.0, ATTN_IDLE, 0, pitch ); + CBaseEntity *pFriend = FindNearestFriend(TRUE); + + if ( pFriend ) + { + m_hTalkTarget = pFriend; + PlaySentence( szIdleGroup, duration, VOL_NORM, ATTN_IDLE ); + m_nSpeak++; + return TRUE; + } + } + + // didn't speak + Talk( 0 ); + CTalkMonster::g_talkWaitTime = 0; + return FALSE; +} + +void CTalkMonster::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ) +{ + if ( !bConcurrent ) + ShutUpFriends(); + + ClearConditions( bits_COND_CLIENT_PUSH ); // Forget about moving! I've got something to say! + m_useTime = gpGlobals->time + duration; + PlaySentence( pszSentence, duration, volume, attenuation ); + + m_hTalkTarget = pListener; +} + +void CTalkMonster::PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ) +{ + if ( !pszSentence ) + return; + + Talk ( duration ); + + CTalkMonster::g_talkWaitTime = gpGlobals->time + duration + 2.0; + if ( pszSentence[0] == '!' ) + EMIT_SOUND_DYN( edict(), CHAN_VOICE, pszSentence, volume, attenuation, 0, GetVoicePitch()); + else + SENTENCEG_PlayRndSz( edict(), pszSentence, volume, attenuation, 0, GetVoicePitch() ); + + // If you say anything, don't greet the player - you may have already spoken to them + SetBits(m_bitsSaid, bit_saidHelloPlayer); +} + +//========================================================= +// Talk - set a timer that tells us when the monster is done +// talking. +//========================================================= +void CTalkMonster :: Talk( float flDuration ) +{ + if ( flDuration <= 0 ) + { + // no duration :( + m_flStopTalkTime = gpGlobals->time + 3; + } + else + { + m_flStopTalkTime = gpGlobals->time + flDuration; + } +} + +// Prepare this talking monster to answer question +void CTalkMonster :: SetAnswerQuestion( CTalkMonster *pSpeaker ) +{ + if ( !m_pCine ) + ChangeSchedule( slIdleResponse ); + m_hTalkTarget = (CBaseMonster *)pSpeaker; +} + +int CTalkMonster :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType) +{ + if ( IsAlive() ) + { + // if player damaged this entity, have other friends talk about it + if (pevAttacker && m_MonsterState != MONSTERSTATE_PRONE && FBitSet(pevAttacker->flags, FL_CLIENT)) + { + CBaseEntity *pFriend = FindNearestFriend(FALSE); + + if (pFriend && pFriend->IsAlive()) + { + // only if not dead or dying! + CTalkMonster *pTalkMonster = (CTalkMonster *)pFriend; + pTalkMonster->ChangeSchedule( slIdleStopShooting ); + } + } + } + return CBaseMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); +} + + +Schedule_t* CTalkMonster :: GetScheduleOfType ( int Type ) +{ + switch( Type ) + { + case SCHED_MOVE_AWAY: + return slMoveAway; + + case SCHED_MOVE_AWAY_FOLLOW: + return slMoveAwayFollow; + + case SCHED_MOVE_AWAY_FAIL: + return slMoveAwayFail; + + case SCHED_TARGET_FACE: + // speak during 'use' + if (RANDOM_LONG(0,99) < 2) + //ALERT ( at_console, "target chase speak\n" ); + return slIdleSpeakWait; + else + return slIdleStand; + + case SCHED_IDLE_STAND: + { + // if never seen player, try to greet him + if (!FBitSet(m_bitsSaid, bit_saidHelloPlayer)) + { + return slIdleHello; + } + + // sustained light wounds? + if (!FBitSet(m_bitsSaid, bit_saidWoundLight) && (pev->health <= (pev->max_health * 0.75))) + { + //SENTENCEG_PlayRndSz( ENT(pev), m_szGrp[TLK_WOUND], 1.0, ATTN_IDLE, 0, GetVoicePitch() ); + //CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(2.8, 3.2); + PlaySentence( m_szGrp[TLK_WOUND], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + SetBits(m_bitsSaid, bit_saidWoundLight); + return slIdleStand; + } + // sustained heavy wounds? + else if (!FBitSet(m_bitsSaid, bit_saidWoundHeavy) && (pev->health <= (pev->max_health * 0.5))) + { + //SENTENCEG_PlayRndSz( ENT(pev), m_szGrp[TLK_MORTAL], 1.0, ATTN_IDLE, 0, GetVoicePitch() ); + //CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(2.8, 3.2); + PlaySentence( m_szGrp[TLK_MORTAL], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + SetBits(m_bitsSaid, bit_saidWoundHeavy); + return slIdleStand; + } + + // talk about world + if (FOkToSpeak() && RANDOM_LONG(0,m_nSpeak * 2) == 0) + { + //ALERT ( at_console, "standing idle speak\n" ); + return slIdleSpeak; + } + + if ( !IsTalking() && HasConditions ( bits_COND_SEE_CLIENT ) && RANDOM_LONG( 0, 6 ) == 0 ) + { + edict_t *pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + if ( pPlayer ) + { + // watch the client. + UTIL_MakeVectors ( pPlayer->v.angles ); + if ( ( pPlayer->v.origin - pev->origin ).Length2D() < TLK_STARE_DIST && + UTIL_DotPoints( pPlayer->v.origin, pev->origin, gpGlobals->v_forward ) >= m_flFieldOfView ) + { + // go into the special STARE schedule if the player is close, and looking at me too. + return &slTlkIdleWatchClient[ 1 ]; + } + + return slTlkIdleWatchClient; + } + } + else + { + if (IsTalking()) + // look at who we're talking to + return slTlkIdleEyecontact; + else + // regular standing idle + return slIdleStand; + } + + + // NOTE - caller must first CTalkMonster::GetScheduleOfType, + // then check result and decide what to return ie: if sci gets back + // slIdleStand, return slIdleSciStand + } + break; + } + + return CBaseMonster::GetScheduleOfType( Type ); +} + +//========================================================= +// IsTalking - am I saying a sentence right now? +//========================================================= +BOOL CTalkMonster :: IsTalking( void ) +{ + if ( m_flStopTalkTime > gpGlobals->time ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// If there's a player around, watch him. +//========================================================= +void CTalkMonster :: PrescheduleThink ( void ) +{ + if ( !HasConditions ( bits_COND_SEE_CLIENT ) ) + { + SetConditions ( bits_COND_CLIENT_UNSEEN ); + } +} + +// try to smell something +void CTalkMonster :: TrySmellTalk( void ) +{ + if ( !FOkToSpeak() ) + return; + + // clear smell bits periodically + if ( gpGlobals->time > m_flLastSaidSmelled ) + { +// ALERT ( at_aiconsole, "Clear smell bits\n" ); + ClearBits(m_bitsSaid, bit_saidSmelled); + } + // smelled something? + if (!FBitSet(m_bitsSaid, bit_saidSmelled) && HasConditions ( bits_COND_SMELL )) + { + PlaySentence( m_szGrp[TLK_SMELL], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + m_flLastSaidSmelled = gpGlobals->time + 60;// don't talk about the stinky for a while. + SetBits(m_bitsSaid, bit_saidSmelled); + } +} + + + +int CTalkMonster::IRelationship( CBaseEntity *pTarget ) +{ + if ( pTarget->IsPlayer() ) + if ( m_afMemory & bits_MEMORY_PROVOKED ) + return R_HT; + return CBaseMonster::IRelationship( pTarget ); +} + + +void CTalkMonster::StopFollowing( BOOL clearSchedule ) +{ + if ( IsFollowing() ) + { + if ( !(m_afMemory & bits_MEMORY_PROVOKED) ) + { + PlaySentence( m_szGrp[TLK_UNUSE], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + m_hTalkTarget = m_hTargetEnt; + } + + if ( m_movementGoal == MOVEGOAL_TARGETENT ) + RouteClear(); // Stop him from walking toward the player + m_hTargetEnt = NULL; + if ( clearSchedule ) + ClearSchedule(); + if ( m_hEnemy != NULL ) + m_IdealMonsterState = MONSTERSTATE_COMBAT; + } +} + + +void CTalkMonster::StartFollowing( CBaseEntity *pLeader ) +{ + if ( m_pCine ) + m_pCine->CancelScript(); + + if ( m_hEnemy != NULL ) + m_IdealMonsterState = MONSTERSTATE_ALERT; + + m_hTargetEnt = pLeader; + PlaySentence( m_szGrp[TLK_USE], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + m_hTalkTarget = m_hTargetEnt; + ClearConditions( bits_COND_CLIENT_PUSH ); + ClearSchedule(); +} + + +BOOL CTalkMonster::CanFollow( void ) +{ + if ( m_MonsterState == MONSTERSTATE_SCRIPT ) + { + if ( !m_pCine->CanInterrupt() ) + return FALSE; + } + + if ( !IsAlive() ) + return FALSE; + + return !IsFollowing(); +} + + +void CTalkMonster :: FollowerUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Don't allow use during a scripted_sentence + if ( m_useTime > gpGlobals->time ) + return; + + if ( pCaller != NULL && pCaller->IsPlayer() ) + { + // Pre-disaster followers can't be used + if ( pev->spawnflags & SF_MONSTER_PREDISASTER ) + { + DeclineFollowing(); + } + else if ( CanFollow() ) + { + LimitFollowers( pCaller , 1 ); + + if ( m_afMemory & bits_MEMORY_PROVOKED ) + ALERT( at_console, "I'm not following you, you evil person!\n" ); + else + { + StartFollowing( pCaller ); + SetBits(m_bitsSaid, bit_saidHelloPlayer); // Don't say hi after you've started following + } + } + else + { + StopFollowing( TRUE ); + } + } +} + +void CTalkMonster::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "UseSentence")) + { + m_iszUse = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "UnUseSentence")) + { + m_iszUnUse = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + + +void CTalkMonster::Precache( void ) +{ + if ( m_iszUse ) + m_szGrp[TLK_USE] = STRING( m_iszUse ); + if ( m_iszUnUse ) + m_szGrp[TLK_UNUSE] = STRING( m_iszUnUse ); +} + diff --git a/dlls/talkmonster.h b/dlls/talkmonster.h new file mode 100644 index 0000000..52e5d6a --- /dev/null +++ b/dlls/talkmonster.h @@ -0,0 +1,183 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef TALKMONSTER_H +#define TALKMONSTER_H + +#ifndef MONSTERS_H +#include "monsters.h" +#endif + +//========================================================= +// Talking monster base class +// Used for scientists and barneys +//========================================================= + +#define TALKRANGE_MIN 500.0 // don't talk to anyone farther away than this + +#define TLK_STARE_DIST 128 // anyone closer than this and looking at me is probably staring at me. + +#define bit_saidDamageLight (1<<0) // bits so we don't repeat key sentences +#define bit_saidDamageMedium (1<<1) +#define bit_saidDamageHeavy (1<<2) +#define bit_saidHelloPlayer (1<<3) +#define bit_saidWoundLight (1<<4) +#define bit_saidWoundHeavy (1<<5) +#define bit_saidHeard (1<<6) +#define bit_saidSmelled (1<<7) + +#define TLK_CFRIENDS 3 + +typedef enum +{ + TLK_ANSWER = 0, + TLK_QUESTION, + TLK_IDLE, + TLK_STARE, + TLK_USE, + TLK_UNUSE, + TLK_STOP, + TLK_NOSHOOT, + TLK_HELLO, + TLK_PHELLO, + TLK_PIDLE, + TLK_PQUESTION, + TLK_PLHURT1, + TLK_PLHURT2, + TLK_PLHURT3, + TLK_SMELL, + TLK_WOUND, + TLK_MORTAL, + + TLK_CGROUPS, // MUST be last entry +} TALKGROUPNAMES; + + +enum +{ + SCHED_CANT_FOLLOW = LAST_COMMON_SCHEDULE + 1, + SCHED_MOVE_AWAY, // Try to get out of the player's way + SCHED_MOVE_AWAY_FOLLOW, // same, but follow afterward + SCHED_MOVE_AWAY_FAIL, // Turn back toward player + + LAST_TALKMONSTER_SCHEDULE, // MUST be last +}; + +enum +{ + TASK_CANT_FOLLOW = LAST_COMMON_TASK + 1, + TASK_MOVE_AWAY_PATH, + TASK_WALK_PATH_FOR_UNITS, + + TASK_TLK_RESPOND, // say my response + TASK_TLK_SPEAK, // question or remark + TASK_TLK_HELLO, // Try to say hello to player + TASK_TLK_HEADRESET, // reset head position + TASK_TLK_STOPSHOOTING, // tell player to stop shooting friend + TASK_TLK_STARE, // let the player know I know he's staring at me. + TASK_TLK_LOOK_AT_CLIENT,// faces player if not moving and not talking and in idle. + TASK_TLK_CLIENT_STARE, // same as look at client, but says something if the player stares. + TASK_TLK_EYECONTACT, // maintain eyecontact with person who I'm talking to + TASK_TLK_IDEALYAW, // set ideal yaw to face who I'm talking to + TASK_FACE_PLAYER, // Face the player + + LAST_TALKMONSTER_TASK, // MUST be last +}; + +class CTalkMonster : public CBaseMonster +{ +public: + void TalkInit( void ); + CBaseEntity *FindNearestFriend(BOOL fPlayer); + float TargetDistance( void ); + void StopTalking( void ) { SentenceStop(); } + + // Base Monster functions + void Precache( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType); + void Touch( CBaseEntity *pOther ); + void Killed( entvars_t *pevAttacker, int iGib ); + int IRelationship ( CBaseEntity *pTarget ); + virtual int CanPlaySentence( BOOL fDisregardState ); + virtual void PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ); + void PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ); + void KeyValue( KeyValueData *pkvd ); + + // AI functions + void SetActivity ( Activity newActivity ); + Schedule_t *GetScheduleOfType ( int Type ); + void StartTask( Task_t *pTask ); + void RunTask( Task_t *pTask ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void PrescheduleThink( void ); + + + // Conversations / communication + int GetVoicePitch( void ); + void IdleRespond( void ); + int FIdleSpeak( void ); + int FIdleStare( void ); + int FIdleHello( void ); + void IdleHeadTurn( Vector &vecFriend ); + int FOkToSpeak( void ); + void TrySmellTalk( void ); + CBaseEntity *EnumFriends( CBaseEntity *pentPrevious, int listNumber, BOOL bTrace ); + void AlertFriends( void ); + void ShutUpFriends( void ); + BOOL IsTalking( void ); + void Talk( float flDuration ); + // For following + BOOL CanFollow( void ); + BOOL IsFollowing( void ) { return m_hTargetEnt != NULL && m_hTargetEnt->IsPlayer(); } + void StopFollowing( BOOL clearSchedule ); + void StartFollowing( CBaseEntity *pLeader ); + virtual void DeclineFollowing( void ) {} + void LimitFollowers( CBaseEntity *pPlayer, int maxFollowers ); + + void EXPORT FollowerUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual void SetAnswerQuestion( CTalkMonster *pSpeaker ); + virtual int FriendNumber( int arrayNumber ) { return arrayNumber; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + + static char *m_szFriends[TLK_CFRIENDS]; // array of friend names + static float g_talkWaitTime; + + int m_bitsSaid; // set bits for sentences we don't want repeated + int m_nSpeak; // number of times initiated talking + int m_voicePitch; // pitch of voice for this head + const char *m_szGrp[TLK_CGROUPS]; // sentence group names + float m_useTime; // Don't allow +USE until this time + int m_iszUse; // Custom +USE sentence group (follow) + int m_iszUnUse; // Custom +USE sentence group (stop following) + + float m_flLastSaidSmelled;// last time we talked about something that stinks + float m_flStopTalkTime;// when in the future that I'll be done saying this sentence. + + EHANDLE m_hTalkTarget; // who to look at while talking + CUSTOM_SCHEDULES; +}; + + +// Clients can push talkmonsters out of their way +#define bits_COND_CLIENT_PUSH ( bits_COND_SPECIAL1 ) +// Don't see a client right now. +#define bits_COND_CLIENT_UNSEEN ( bits_COND_SPECIAL2 ) + + +#endif //TALKMONSTER_H diff --git a/dlls/teamplay_gamerules.cpp b/dlls/teamplay_gamerules.cpp new file mode 100644 index 0000000..1554abf --- /dev/null +++ b/dlls/teamplay_gamerules.cpp @@ -0,0 +1,630 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.cpp +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "teamplay_gamerules.h" +#include "game.h" + +static char team_names[MAX_TEAMS][MAX_TEAMNAME_LENGTH]; +static int team_scores[MAX_TEAMS]; +static int num_teams = 0; + +extern DLL_GLOBAL BOOL g_fGameOver; + +CHalfLifeTeamplay :: CHalfLifeTeamplay() +{ + m_DisableDeathMessages = FALSE; + m_DisableDeathPenalty = FALSE; + + memset( team_names, 0, sizeof(team_names) ); + memset( team_scores, 0, sizeof(team_scores) ); + num_teams = 0; + + // Copy over the team from the server config + m_szTeamList[0] = 0; + + // Cache this because the team code doesn't want to deal with changing this in the middle of a game + strncpy( m_szTeamList, teamlist.string, TEAMPLAY_TEAMLISTLENGTH ); + + edict_t *pWorld = INDEXENT(0); + if ( pWorld && pWorld->v.team ) + { + if ( teamoverride.value ) + { + const char *pTeamList = STRING(pWorld->v.team); + if ( pTeamList && strlen(pTeamList) ) + { + strncpy( m_szTeamList, pTeamList, TEAMPLAY_TEAMLISTLENGTH ); + } + } + } + // Has the server set teams + if ( strlen( m_szTeamList ) ) + m_teamLimit = TRUE; + else + m_teamLimit = FALSE; + + RecountTeams(); +} + +extern cvar_t timeleft, fragsleft; + +#include "voice_gamemgr.h" +extern CVoiceGameMgr g_VoiceGameMgr; + +void CHalfLifeTeamplay :: Think ( void ) +{ + ///// Check game rules ///// + static int last_frags; + static int last_time; + + int frags_remaining = 0; + int time_remaining = 0; + + g_VoiceGameMgr.Update(gpGlobals->frametime); + + if ( g_fGameOver ) // someone else quit the game already + { + CHalfLifeMultiplay::Think(); + return; + } + + float flTimeLimit = CVAR_GET_FLOAT("mp_timelimit") * 60; + + time_remaining = (int)(flTimeLimit ? ( flTimeLimit - gpGlobals->time ) : 0); + + if ( flTimeLimit != 0 && gpGlobals->time >= flTimeLimit ) + { + GoToIntermission(); + return; + } + + float flFragLimit = fraglimit.value; + if ( flFragLimit ) + { + int bestfrags = 9999; + int remain; + + // check if any team is over the frag limit + for ( int i = 0; i < num_teams; i++ ) + { + if ( team_scores[i] >= flFragLimit ) + { + GoToIntermission(); + return; + } + + remain = flFragLimit - team_scores[i]; + if ( remain < bestfrags ) + { + bestfrags = remain; + } + } + frags_remaining = bestfrags; + } + + // Updates when frags change + if ( frags_remaining != last_frags ) + { + g_engfuncs.pfnCvar_DirectSet( &fragsleft, UTIL_VarArgs( "%i", frags_remaining ) ); + } + + // Updates once per second + if ( timeleft.value != last_time ) + { + g_engfuncs.pfnCvar_DirectSet( &timeleft, UTIL_VarArgs( "%i", time_remaining ) ); + } + + last_frags = frags_remaining; + last_time = time_remaining; +} + +//========================================================= +// ClientCommand +// the user has typed a command which is unrecognized by everything else; +// this check to see if the gamerules knows anything about the command +//========================================================= +BOOL CHalfLifeTeamplay :: ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) +{ + if(g_VoiceGameMgr.ClientCommand(pPlayer, pcmd)) + return TRUE; + + if ( FStrEq( pcmd, "menuselect" ) ) + { + if ( CMD_ARGC() < 2 ) + return TRUE; + + int slot = atoi( CMD_ARGV(1) ); + + // select the item from the current menu + + return TRUE; + } + + return FALSE; +} + +extern int gmsgGameMode; +extern int gmsgSayText; +extern int gmsgTeamInfo; +extern int gmsgTeamNames; +extern int gmsgScoreInfo; + +void CHalfLifeTeamplay :: UpdateGameMode( CBasePlayer *pPlayer ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgGameMode, NULL, pPlayer->edict() ); + WRITE_BYTE( 1 ); // game mode teamplay + MESSAGE_END(); +} + + +const char *CHalfLifeTeamplay::SetDefaultPlayerTeam( CBasePlayer *pPlayer ) +{ + // copy out the team name from the model + char *mdls = g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model" ); + strncpy( pPlayer->m_szTeamName, mdls, TEAM_NAME_LENGTH ); + + RecountTeams(); + + // update the current player of the team he is joining + if ( pPlayer->m_szTeamName[0] == '\0' || !IsValidTeam( pPlayer->m_szTeamName ) || defaultteam.value ) + { + const char *pTeamName = NULL; + + if ( defaultteam.value ) + { + pTeamName = team_names[0]; + } + else + { + pTeamName = TeamWithFewestPlayers(); + } + strncpy( pPlayer->m_szTeamName, pTeamName, TEAM_NAME_LENGTH ); + } + + return pPlayer->m_szTeamName; +} + + +//========================================================= +// InitHUD +//========================================================= +void CHalfLifeTeamplay::InitHUD( CBasePlayer *pPlayer ) +{ + int i; + + SetDefaultPlayerTeam( pPlayer ); + CHalfLifeMultiplay::InitHUD( pPlayer ); + + // Send down the team names + MESSAGE_BEGIN( MSG_ONE, gmsgTeamNames, NULL, pPlayer->edict() ); + WRITE_BYTE( num_teams ); + for ( i = 0; i < num_teams; i++ ) + { + WRITE_STRING( team_names[ i ] ); + } + MESSAGE_END(); + + RecountTeams(); + + char *mdls = g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model" ); + // update the current player of the team he is joining + char text[1024]; + if ( !strcmp( mdls, pPlayer->m_szTeamName ) ) + { + sprintf( text, "* you are on team \'%s\'\n", pPlayer->m_szTeamName ); + } + else + { + sprintf( text, "* assigned to team %s\n", pPlayer->m_szTeamName ); + } + + ChangePlayerTeam( pPlayer, pPlayer->m_szTeamName, FALSE, FALSE ); + UTIL_SayText( text, pPlayer ); + int clientIndex = pPlayer->entindex(); + RecountTeams(); + // update this player with all the other players team info + // loop through all active players and send their team info to the new client + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *plr = UTIL_PlayerByIndex( i ); + if ( plr && IsValidTeam( plr->TeamID() ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgTeamInfo, NULL, pPlayer->edict() ); + WRITE_BYTE( plr->entindex() ); + WRITE_STRING( plr->TeamID() ); + MESSAGE_END(); + } + } +} + + +void CHalfLifeTeamplay::ChangePlayerTeam( CBasePlayer *pPlayer, const char *pTeamName, BOOL bKill, BOOL bGib ) +{ + int damageFlags = DMG_GENERIC; + int clientIndex = pPlayer->entindex(); + + if ( !bGib ) + { + damageFlags |= DMG_NEVERGIB; + } + else + { + damageFlags |= DMG_ALWAYSGIB; + } + + if ( bKill ) + { + // kill the player, remove a death, and let them start on the new team + m_DisableDeathMessages = TRUE; + m_DisableDeathPenalty = TRUE; + + entvars_t *pevWorld = VARS( INDEXENT(0) ); + pPlayer->TakeDamage( pevWorld, pevWorld, 900, damageFlags ); + + m_DisableDeathMessages = FALSE; + m_DisableDeathPenalty = FALSE; + } + + // copy out the team name from the model + strncpy( pPlayer->m_szTeamName, pTeamName, TEAM_NAME_LENGTH ); + + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model", pPlayer->m_szTeamName ); + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "team", pPlayer->m_szTeamName ); + + // notify everyone's HUD of the team change + MESSAGE_BEGIN( MSG_ALL, gmsgTeamInfo ); + WRITE_BYTE( clientIndex ); + WRITE_STRING( pPlayer->m_szTeamName ); + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo ); + WRITE_BYTE( clientIndex ); + WRITE_SHORT( pPlayer->pev->frags ); + WRITE_SHORT( pPlayer->m_iDeaths ); + WRITE_SHORT( 0 ); + WRITE_SHORT( g_pGameRules->GetTeamIndex( pPlayer->m_szTeamName ) + 1 ); + MESSAGE_END(); +} + + +//========================================================= +// ClientUserInfoChanged +//========================================================= +void CHalfLifeTeamplay::ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ) +{ + char text[1024]; + + // prevent skin/color/model changes + char *mdls = g_engfuncs.pfnInfoKeyValue( infobuffer, "model" ); + + if ( !stricmp( mdls, pPlayer->m_szTeamName ) ) + return; + + if ( defaultteam.value ) + { + int clientIndex = pPlayer->entindex(); + + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model", pPlayer->m_szTeamName ); + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "team", pPlayer->m_szTeamName ); + sprintf( text, "* Not allowed to change teams in this game!\n" ); + UTIL_SayText( text, pPlayer ); + return; + } + + if ( defaultteam.value || !IsValidTeam( mdls ) ) + { + int clientIndex = pPlayer->entindex(); + + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model", pPlayer->m_szTeamName ); + sprintf( text, "* Can't change team to \'%s\'\n", mdls ); + UTIL_SayText( text, pPlayer ); + sprintf( text, "* Server limits teams to \'%s\'\n", m_szTeamList ); + UTIL_SayText( text, pPlayer ); + return; + } + // notify everyone of the team change + sprintf( text, "* %s has changed to team \'%s\'\n", STRING(pPlayer->pev->netname), mdls ); + UTIL_SayTextAll( text, pPlayer ); + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" joined team \"%s\"\n", + STRING(pPlayer->pev->netname), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + pPlayer->m_szTeamName, + mdls ); + + ChangePlayerTeam( pPlayer, mdls, TRUE, TRUE ); + // recound stuff + RecountTeams( TRUE ); +} + +extern int gmsgDeathMsg; + +//========================================================= +// Deathnotice. +//========================================================= +void CHalfLifeTeamplay::DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pevInflictor ) +{ + if ( m_DisableDeathMessages ) + return; + + if ( pVictim && pKiller && pKiller->flags & FL_CLIENT ) + { + CBasePlayer *pk = (CBasePlayer*) CBaseEntity::Instance( pKiller ); + + if ( pk ) + { + if ( (pk != pVictim) && (PlayerRelationship( pVictim, pk ) == GR_TEAMMATE) ) + { + MESSAGE_BEGIN( MSG_ALL, gmsgDeathMsg ); + WRITE_BYTE( ENTINDEX(ENT(pKiller)) ); // the killer + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); // the victim + WRITE_STRING( "teammate" ); // flag this as a teammate kill + MESSAGE_END(); + return; + } + } + } + + CHalfLifeMultiplay::DeathNotice( pVictim, pKiller, pevInflictor ); +} + +//========================================================= +//========================================================= +void CHalfLifeTeamplay :: PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) +{ + if ( !m_DisableDeathPenalty ) + { + CHalfLifeMultiplay::PlayerKilled( pVictim, pKiller, pInflictor ); + RecountTeams(); + } +} + + +//========================================================= +// IsTeamplay +//========================================================= +BOOL CHalfLifeTeamplay::IsTeamplay( void ) +{ + return TRUE; +} + +BOOL CHalfLifeTeamplay::FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ) +{ + if ( pAttacker && PlayerRelationship( pPlayer, pAttacker ) == GR_TEAMMATE ) + { + // my teammate hit me. + if ( (friendlyfire.value == 0) && (pAttacker != pPlayer) ) + { + // friendly fire is off, and this hit came from someone other than myself, then don't get hurt + return FALSE; + } + } + + return CHalfLifeMultiplay::FPlayerCanTakeDamage( pPlayer, pAttacker ); +} + +//========================================================= +//========================================================= +int CHalfLifeTeamplay::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) +{ + // half life multiplay has a simple concept of Player Relationships. + // you are either on another player's team, or you are not. + if ( !pPlayer || !pTarget || !pTarget->IsPlayer() ) + return GR_NOTTEAMMATE; + + if ( (*GetTeamID(pPlayer) != '\0') && (*GetTeamID(pTarget) != '\0') && !stricmp( GetTeamID(pPlayer), GetTeamID(pTarget) ) ) + { + return GR_TEAMMATE; + } + + return GR_NOTTEAMMATE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeTeamplay::ShouldAutoAim( CBasePlayer *pPlayer, edict_t *target ) +{ + // always autoaim, unless target is a teammate + CBaseEntity *pTgt = CBaseEntity::Instance( target ); + if ( pTgt && pTgt->IsPlayer() ) + { + if ( PlayerRelationship( pPlayer, pTgt ) == GR_TEAMMATE ) + return FALSE; // don't autoaim at teammates + } + + return CHalfLifeMultiplay::ShouldAutoAim( pPlayer, target ); +} + +//========================================================= +//========================================================= +int CHalfLifeTeamplay::IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) +{ + if ( !pKilled ) + return 0; + + if ( !pAttacker ) + return 1; + + if ( pAttacker != pKilled && PlayerRelationship( pAttacker, pKilled ) == GR_TEAMMATE ) + return -1; + + return 1; +} + +//========================================================= +//========================================================= +const char *CHalfLifeTeamplay::GetTeamID( CBaseEntity *pEntity ) +{ + if ( pEntity == NULL || pEntity->pev == NULL ) + return ""; + + // return their team name + return pEntity->TeamID(); +} + + +int CHalfLifeTeamplay::GetTeamIndex( const char *pTeamName ) +{ + if ( pTeamName && *pTeamName != 0 ) + { + // try to find existing team + for ( int tm = 0; tm < num_teams; tm++ ) + { + if ( !stricmp( team_names[tm], pTeamName ) ) + return tm; + } + } + + return -1; // No match +} + + +const char *CHalfLifeTeamplay::GetIndexedTeamName( int teamIndex ) +{ + if ( teamIndex < 0 || teamIndex >= num_teams ) + return ""; + + return team_names[ teamIndex ]; +} + + +BOOL CHalfLifeTeamplay::IsValidTeam( const char *pTeamName ) +{ + if ( !m_teamLimit ) // Any team is valid if the teamlist isn't set + return TRUE; + + return ( GetTeamIndex( pTeamName ) != -1 ) ? TRUE : FALSE; +} + +const char *CHalfLifeTeamplay::TeamWithFewestPlayers( void ) +{ + int i; + int minPlayers = MAX_TEAMS; + int teamCount[ MAX_TEAMS ]; + char *pTeamName = NULL; + + memset( teamCount, 0, MAX_TEAMS * sizeof(int) ); + + // loop through all clients, count number of players on each team + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *plr = UTIL_PlayerByIndex( i ); + + if ( plr ) + { + int team = GetTeamIndex( plr->TeamID() ); + if ( team >= 0 ) + teamCount[team] ++; + } + } + + // Find team with least players + for ( i = 0; i < num_teams; i++ ) + { + if ( teamCount[i] < minPlayers ) + { + minPlayers = teamCount[i]; + pTeamName = team_names[i]; + } + } + + return pTeamName; +} + + +//========================================================= +//========================================================= +void CHalfLifeTeamplay::RecountTeams( bool bResendInfo ) +{ + char *pName; + char teamlist[TEAMPLAY_TEAMLISTLENGTH]; + + // loop through all teams, recounting everything + num_teams = 0; + + // Copy all of the teams from the teamlist + // make a copy because strtok is destructive + strcpy( teamlist, m_szTeamList ); + pName = teamlist; + pName = strtok( pName, ";" ); + while ( pName != NULL && *pName ) + { + if ( GetTeamIndex( pName ) < 0 ) + { + strcpy( team_names[num_teams], pName ); + num_teams++; + } + pName = strtok( NULL, ";" ); + } + + if ( num_teams < 2 ) + { + num_teams = 0; + m_teamLimit = FALSE; + } + + // Sanity check + memset( team_scores, 0, sizeof(team_scores) ); + + // loop through all clients + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *plr = UTIL_PlayerByIndex( i ); + + if ( plr ) + { + const char *pTeamName = plr->TeamID(); + // try add to existing team + int tm = GetTeamIndex( pTeamName ); + + if ( tm < 0 ) // no team match found + { + if ( !m_teamLimit ) + { + // add to new team + tm = num_teams; + num_teams++; + team_scores[tm] = 0; + strncpy( team_names[tm], pTeamName, MAX_TEAMNAME_LENGTH ); + } + } + + if ( tm >= 0 ) + { + team_scores[tm] += plr->pev->frags; + } + + if ( bResendInfo ) //Someone's info changed, let's send the team info again. + { + if ( plr && IsValidTeam( plr->TeamID() ) ) + { + MESSAGE_BEGIN( MSG_ALL, gmsgTeamInfo, NULL ); + WRITE_BYTE( plr->entindex() ); + WRITE_STRING( plr->TeamID() ); + MESSAGE_END(); + } + } + } + } +} diff --git a/dlls/teamplay_gamerules.h b/dlls/teamplay_gamerules.h new file mode 100644 index 0000000..4ec6385 --- /dev/null +++ b/dlls/teamplay_gamerules.h @@ -0,0 +1,57 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.h +// + +#define MAX_TEAMNAME_LENGTH 16 +#define MAX_TEAMS 32 + +#define TEAMPLAY_TEAMLISTLENGTH MAX_TEAMS*MAX_TEAMNAME_LENGTH + +class CHalfLifeTeamplay : public CHalfLifeMultiplay +{ +public: + CHalfLifeTeamplay(); + + virtual BOOL ClientCommand( CBasePlayer *pPlayer, const char *pcmd ); + virtual void ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ); + virtual BOOL IsTeamplay( void ); + virtual BOOL FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ); + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); + virtual const char *GetTeamID( CBaseEntity *pEntity ); + virtual BOOL ShouldAutoAim( CBasePlayer *pPlayer, edict_t *target ); + virtual int IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ); + virtual void InitHUD( CBasePlayer *pl ); + virtual void DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pevInflictor ); + virtual const char *GetGameDescription( void ) { return "HL Teamplay"; } // this is the game name that gets seen in the server browser + virtual void UpdateGameMode( CBasePlayer *pPlayer ); // the client needs to be informed of the current game mode + virtual void PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); + virtual void Think ( void ); + virtual int GetTeamIndex( const char *pTeamName ); + virtual const char *GetIndexedTeamName( int teamIndex ); + virtual BOOL IsValidTeam( const char *pTeamName ); + const char *SetDefaultPlayerTeam( CBasePlayer *pPlayer ); + virtual void ChangePlayerTeam( CBasePlayer *pPlayer, const char *pTeamName, BOOL bKill, BOOL bGib ); + +private: + void RecountTeams( bool bResendInfo = FALSE ); + const char *TeamWithFewestPlayers( void ); + + BOOL m_DisableDeathMessages; + BOOL m_DisableDeathPenalty; + BOOL m_teamLimit; // This means the server set only some teams as valid + char m_szTeamList[TEAMPLAY_TEAMLISTLENGTH]; +}; diff --git a/dlls/tempmonster.cpp b/dlls/tempmonster.cpp new file mode 100644 index 0000000..052ce6f --- /dev/null +++ b/dlls/tempmonster.cpp @@ -0,0 +1,117 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// monster template +//========================================================= +#if 0 + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +class CMyMonster : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); +}; +LINK_ENTITY_TO_CLASS( my_monster, CMyMonster ); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CMyMonster :: Classify ( void ) +{ + return CLASS_MY_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CMyMonster :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + default: + ys = 90; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CMyMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case 0: + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CMyMonster :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/mymodel.mdl"); + UTIL_SetSize( pev, Vector( -12, -12, 0 ), Vector( 12, 12, 24 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->health = 8; + pev->view_ofs = Vector ( 0, 0, 0 );// position of the eyes relative to monster's origin. + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CMyMonster :: Precache() +{ + PRECACHE_SOUND("mysound.wav"); + + PRECACHE_MODEL("models/mymodel.mdl"); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +#endif 0 diff --git a/dlls/tentacle.cpp b/dlls/tentacle.cpp new file mode 100644 index 0000000..abb0e43 --- /dev/null +++ b/dlls/tentacle.cpp @@ -0,0 +1,1044 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +/* + + h_tentacle.cpp - silo of death tentacle monster (half life) + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "soundent.h" + + +#define ACT_T_IDLE 1010 +#define ACT_T_TAP 1020 +#define ACT_T_STRIKE 1030 +#define ACT_T_REARIDLE 1040 + +class CTentacle : public CBaseMonster +{ +public: + CTentacle( void ); + + void Spawn( ); + void Precache( ); + void KeyValue( KeyValueData *pkvd ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + // Don't allow the tentacle to go across transitions!!! + virtual int ObjectCaps( void ) { return CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector(-400, -400, 0); + pev->absmax = pev->origin + Vector(400, 400, 850); + } + + void EXPORT Cycle( void ); + void EXPORT CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Start( void ); + void EXPORT DieThink( void ); + + void EXPORT Test( void ); + + void EXPORT HitTouch( CBaseEntity *pOther ); + + float HearingSensitivity( void ) { return 2.0; }; + + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void Killed( entvars_t *pevAttacker, int iGib ); + + MONSTERSTATE GetIdealState ( void ) { return MONSTERSTATE_IDLE; }; + int CanPlaySequence( BOOL fDisregardState ) { return TRUE; }; + + int Classify( void ); + + int Level( float dz ); + int MyLevel( void ); + float MyHeight( void ); + + float m_flInitialYaw; + int m_iGoalAnim; + int m_iLevel; + int m_iDir; + float m_flFramerateAdj; + float m_flSoundYaw; + int m_iSoundLevel; + float m_flSoundTime; + float m_flSoundRadius; + int m_iHitDmg; + float m_flHitTime; + + float m_flTapRadius; + + float m_flNextSong; + static int g_fFlySound; + static int g_fSquirmSound; + + float m_flMaxYaw; + int m_iTapSound; + + Vector m_vecPrevSound; + float m_flPrevSoundTime; + + static const char *pHitSilo[]; + static const char *pHitDirt[]; + static const char *pHitWater[]; +}; + + + +int CTentacle :: g_fFlySound; +int CTentacle :: g_fSquirmSound; + +LINK_ENTITY_TO_CLASS( monster_tentacle, CTentacle ); + +// stike sounds +#define TE_NONE -1 +#define TE_SILO 0 +#define TE_DIRT 1 +#define TE_WATER 2 + +const char *CTentacle::pHitSilo[] = +{ + "tentacle/te_strike1.wav", + "tentacle/te_strike2.wav", +}; + +const char *CTentacle::pHitDirt[] = +{ + "player/pl_dirt1.wav", + "player/pl_dirt2.wav", + "player/pl_dirt3.wav", + "player/pl_dirt4.wav", +}; + +const char *CTentacle::pHitWater[] = +{ + "player/pl_slosh1.wav", + "player/pl_slosh2.wav", + "player/pl_slosh3.wav", + "player/pl_slosh4.wav", +}; + + +TYPEDESCRIPTION CTentacle::m_SaveData[] = +{ + DEFINE_FIELD( CTentacle, m_flInitialYaw, FIELD_FLOAT ), + DEFINE_FIELD( CTentacle, m_iGoalAnim, FIELD_INTEGER ), + DEFINE_FIELD( CTentacle, m_iLevel, FIELD_INTEGER ), + DEFINE_FIELD( CTentacle, m_iDir, FIELD_INTEGER ), + DEFINE_FIELD( CTentacle, m_flFramerateAdj, FIELD_FLOAT ), + DEFINE_FIELD( CTentacle, m_flSoundYaw, FIELD_FLOAT ), + DEFINE_FIELD( CTentacle, m_iSoundLevel, FIELD_INTEGER ), + DEFINE_FIELD( CTentacle, m_flSoundTime, FIELD_TIME ), + DEFINE_FIELD( CTentacle, m_flSoundRadius, FIELD_FLOAT ), + DEFINE_FIELD( CTentacle, m_iHitDmg, FIELD_INTEGER ), + DEFINE_FIELD( CTentacle, m_flHitTime, FIELD_TIME ), + DEFINE_FIELD( CTentacle, m_flTapRadius, FIELD_FLOAT ), + DEFINE_FIELD( CTentacle, m_flNextSong, FIELD_TIME ), + DEFINE_FIELD( CTentacle, m_iTapSound, FIELD_INTEGER ), + DEFINE_FIELD( CTentacle, m_flMaxYaw, FIELD_FLOAT ), + DEFINE_FIELD( CTentacle, m_vecPrevSound, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CTentacle, m_flPrevSoundTime, FIELD_TIME ), +}; +IMPLEMENT_SAVERESTORE( CTentacle, CBaseMonster ); + + +// animation sequence aliases +typedef enum +{ + TENTACLE_ANIM_Pit_Idle, + + TENTACLE_ANIM_rise_to_Temp1, + TENTACLE_ANIM_Temp1_to_Floor, + TENTACLE_ANIM_Floor_Idle, + TENTACLE_ANIM_Floor_Fidget_Pissed, + TENTACLE_ANIM_Floor_Fidget_SmallRise, + TENTACLE_ANIM_Floor_Fidget_Wave, + TENTACLE_ANIM_Floor_Strike, + TENTACLE_ANIM_Floor_Tap, + TENTACLE_ANIM_Floor_Rotate, + TENTACLE_ANIM_Floor_Rear, + TENTACLE_ANIM_Floor_Rear_Idle, + TENTACLE_ANIM_Floor_to_Lev1, + + TENTACLE_ANIM_Lev1_Idle, + TENTACLE_ANIM_Lev1_Fidget_Claw, + TENTACLE_ANIM_Lev1_Fidget_Shake, + TENTACLE_ANIM_Lev1_Fidget_Snap, + TENTACLE_ANIM_Lev1_Strike, + TENTACLE_ANIM_Lev1_Tap, + TENTACLE_ANIM_Lev1_Rotate, + TENTACLE_ANIM_Lev1_Rear, + TENTACLE_ANIM_Lev1_Rear_Idle, + TENTACLE_ANIM_Lev1_to_Lev2, + + TENTACLE_ANIM_Lev2_Idle, + TENTACLE_ANIM_Lev2_Fidget_Shake, + TENTACLE_ANIM_Lev2_Fidget_Swing, + TENTACLE_ANIM_Lev2_Fidget_Tut, + TENTACLE_ANIM_Lev2_Strike, + TENTACLE_ANIM_Lev2_Tap, + TENTACLE_ANIM_Lev2_Rotate, + TENTACLE_ANIM_Lev2_Rear, + TENTACLE_ANIM_Lev2_Rear_Idle, + TENTACLE_ANIM_Lev2_to_Lev3, + + TENTACLE_ANIM_Lev3_Idle, + TENTACLE_ANIM_Lev3_Fidget_Shake, + TENTACLE_ANIM_Lev3_Fidget_Side, + TENTACLE_ANIM_Lev3_Fidget_Swipe, + TENTACLE_ANIM_Lev3_Strike, + TENTACLE_ANIM_Lev3_Tap, + TENTACLE_ANIM_Lev3_Rotate, + TENTACLE_ANIM_Lev3_Rear, + TENTACLE_ANIM_Lev3_Rear_Idle, + + TENTACLE_ANIM_Lev1_Door_reach, + + TENTACLE_ANIM_Lev3_to_Engine, + TENTACLE_ANIM_Engine_Idle, + TENTACLE_ANIM_Engine_Sway, + TENTACLE_ANIM_Engine_Swat, + TENTACLE_ANIM_Engine_Bob, + TENTACLE_ANIM_Engine_Death1, + TENTACLE_ANIM_Engine_Death2, + TENTACLE_ANIM_Engine_Death3, + + TENTACLE_ANIM_none +} TENTACLE_ANIM; + + + + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CTentacle :: Classify ( void ) +{ + return CLASS_ALIEN_MONSTER; +} + +// +// Tentacle Spawn +// +void CTentacle :: Spawn( ) +{ + Precache( ); + + pev->solid = SOLID_BBOX; + pev->movetype = MOVETYPE_FLY; + pev->effects = 0; + pev->health = 75; + pev->sequence = 0; + + SET_MODEL(ENT(pev), "models/tentacle2.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ) ); + + pev->takedamage = DAMAGE_AIM; + pev->flags |= FL_MONSTER; + + m_bloodColor = BLOOD_COLOR_GREEN; + + SetThink( &CTentacle::Start ); + SetTouch( &CTentacle::HitTouch ); + SetUse( &CTentacle::CommandUse ); + + pev->nextthink = gpGlobals->time + 0.2; + + ResetSequenceInfo( ); + m_iDir = 1; + + pev->yaw_speed = 18; + m_flInitialYaw = pev->angles.y; + pev->ideal_yaw = m_flInitialYaw; + + g_fFlySound = FALSE; + g_fSquirmSound = FALSE; + + m_iHitDmg = 20; + + if (m_flMaxYaw <= 0) + m_flMaxYaw = 65; + + m_MonsterState = MONSTERSTATE_IDLE; + + // SetThink( Test ); + UTIL_SetOrigin( pev, pev->origin ); +} + +void CTentacle :: Precache( ) +{ + PRECACHE_MODEL("models/tentacle2.mdl"); + + PRECACHE_SOUND("ambience/flies.wav"); + PRECACHE_SOUND("ambience/squirm2.wav"); + + PRECACHE_SOUND("tentacle/te_alert1.wav"); + PRECACHE_SOUND("tentacle/te_alert2.wav"); + PRECACHE_SOUND("tentacle/te_flies1.wav"); + PRECACHE_SOUND("tentacle/te_move1.wav"); + PRECACHE_SOUND("tentacle/te_move2.wav"); + PRECACHE_SOUND("tentacle/te_roar1.wav"); + PRECACHE_SOUND("tentacle/te_roar2.wav"); + PRECACHE_SOUND("tentacle/te_search1.wav"); + PRECACHE_SOUND("tentacle/te_search2.wav"); + PRECACHE_SOUND("tentacle/te_sing1.wav"); + PRECACHE_SOUND("tentacle/te_sing2.wav"); + PRECACHE_SOUND("tentacle/te_squirm2.wav"); + PRECACHE_SOUND("tentacle/te_strike1.wav"); + PRECACHE_SOUND("tentacle/te_strike2.wav"); + PRECACHE_SOUND("tentacle/te_swing1.wav"); + PRECACHE_SOUND("tentacle/te_swing2.wav"); + + PRECACHE_SOUND_ARRAY( pHitSilo ); + PRECACHE_SOUND_ARRAY( pHitDirt ); + PRECACHE_SOUND_ARRAY( pHitWater ); +} + + +CTentacle::CTentacle( ) +{ + m_flMaxYaw = 65; + m_iTapSound = 0; +} + +void CTentacle::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "sweeparc")) + { + m_flMaxYaw = atof(pkvd->szValue) / 2.0; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sound")) + { + m_iTapSound = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + } + else + CBaseMonster::KeyValue( pkvd ); +} + + + +int CTentacle :: Level( float dz ) +{ + if (dz < 216) + return 0; + if (dz < 408) + return 1; + if (dz < 600) + return 2; + return 3; +} + + +float CTentacle :: MyHeight( ) +{ + switch ( MyLevel( ) ) + { + case 1: + return 256; + case 2: + return 448; + case 3: + return 640; + } + return 0; +} + + +int CTentacle :: MyLevel( ) +{ + switch( pev->sequence ) + { + case TENTACLE_ANIM_Pit_Idle: + return -1; + + case TENTACLE_ANIM_rise_to_Temp1: + case TENTACLE_ANIM_Temp1_to_Floor: + case TENTACLE_ANIM_Floor_to_Lev1: + return 0; + + case TENTACLE_ANIM_Floor_Idle: + case TENTACLE_ANIM_Floor_Fidget_Pissed: + case TENTACLE_ANIM_Floor_Fidget_SmallRise: + case TENTACLE_ANIM_Floor_Fidget_Wave: + case TENTACLE_ANIM_Floor_Strike: + case TENTACLE_ANIM_Floor_Tap: + case TENTACLE_ANIM_Floor_Rotate: + case TENTACLE_ANIM_Floor_Rear: + case TENTACLE_ANIM_Floor_Rear_Idle: + return 0; + + case TENTACLE_ANIM_Lev1_Idle: + case TENTACLE_ANIM_Lev1_Fidget_Claw: + case TENTACLE_ANIM_Lev1_Fidget_Shake: + case TENTACLE_ANIM_Lev1_Fidget_Snap: + case TENTACLE_ANIM_Lev1_Strike: + case TENTACLE_ANIM_Lev1_Tap: + case TENTACLE_ANIM_Lev1_Rotate: + case TENTACLE_ANIM_Lev1_Rear: + case TENTACLE_ANIM_Lev1_Rear_Idle: + return 1; + + case TENTACLE_ANIM_Lev1_to_Lev2: + return 1; + + case TENTACLE_ANIM_Lev2_Idle: + case TENTACLE_ANIM_Lev2_Fidget_Shake: + case TENTACLE_ANIM_Lev2_Fidget_Swing: + case TENTACLE_ANIM_Lev2_Fidget_Tut: + case TENTACLE_ANIM_Lev2_Strike: + case TENTACLE_ANIM_Lev2_Tap: + case TENTACLE_ANIM_Lev2_Rotate: + case TENTACLE_ANIM_Lev2_Rear: + case TENTACLE_ANIM_Lev2_Rear_Idle: + return 2; + + case TENTACLE_ANIM_Lev2_to_Lev3: + return 2; + + case TENTACLE_ANIM_Lev3_Idle: + case TENTACLE_ANIM_Lev3_Fidget_Shake: + case TENTACLE_ANIM_Lev3_Fidget_Side: + case TENTACLE_ANIM_Lev3_Fidget_Swipe: + case TENTACLE_ANIM_Lev3_Strike: + case TENTACLE_ANIM_Lev3_Tap: + case TENTACLE_ANIM_Lev3_Rotate: + case TENTACLE_ANIM_Lev3_Rear: + case TENTACLE_ANIM_Lev3_Rear_Idle: + return 3; + + case TENTACLE_ANIM_Lev1_Door_reach: + return -1; + } + return -1; +} + + +void CTentacle :: Test( void ) +{ + pev->sequence = TENTACLE_ANIM_Floor_Strike; + pev->framerate = 0; + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; +} + + + +// +// TentacleThink +// +void CTentacle :: Cycle( void ) +{ + // ALERT( at_console, "%s %.2f %d %d\n", STRING( pev->targetname ), pev->origin.z, m_MonsterState, m_IdealMonsterState ); + pev->nextthink = gpGlobals-> time + 0.1; + + // ALERT( at_console, "%s %d %d %d %f %f\n", STRING( pev->targetname ), pev->sequence, m_iGoalAnim, m_iDir, pev->framerate, pev->health ); + + if (m_MonsterState == MONSTERSTATE_SCRIPT || m_IdealMonsterState == MONSTERSTATE_SCRIPT) + { + pev->angles.y = m_flInitialYaw; + pev->ideal_yaw = m_flInitialYaw; + ClearConditions( IgnoreConditions() ); + MonsterThink( ); + m_iGoalAnim = TENTACLE_ANIM_Pit_Idle; + return; + } + + DispatchAnimEvents( ); + StudioFrameAdvance( ); + + ChangeYaw( pev->yaw_speed ); + + CSound *pSound; + + Listen( ); + + // Listen will set this if there's something in my sound list + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + pSound = PBestSound(); + else + pSound = NULL; + + if ( pSound ) + { + Vector vecDir; + if (gpGlobals->time - m_flPrevSoundTime < 0.5) + { + float dt = gpGlobals->time - m_flPrevSoundTime; + vecDir = pSound->m_vecOrigin + (pSound->m_vecOrigin - m_vecPrevSound) / dt - pev->origin; + } + else + { + vecDir = pSound->m_vecOrigin - pev->origin; + } + m_flPrevSoundTime = gpGlobals->time; + m_vecPrevSound = pSound->m_vecOrigin; + + m_flSoundYaw = UTIL_VecToYaw ( vecDir ) - m_flInitialYaw; + m_iSoundLevel = Level( vecDir.z ); + + if (m_flSoundYaw < -180) + m_flSoundYaw += 360; + if (m_flSoundYaw > 180) + m_flSoundYaw -= 360; + + // ALERT( at_console, "sound %d %.0f\n", m_iSoundLevel, m_flSoundYaw ); + if (m_flSoundTime < gpGlobals->time) + { + // play "I hear new something" sound + char *sound; + + switch( RANDOM_LONG(0,1) ) + { + case 0: sound = "tentacle/te_alert1.wav"; break; + case 1: sound = "tentacle/te_alert2.wav"; break; + } + + // UTIL_EmitAmbientSound(ENT(pev), pev->origin + Vector( 0, 0, MyHeight()), sound, 1.0, ATTN_NORM, 0, 100); + } + m_flSoundTime = gpGlobals->time + RANDOM_FLOAT( 5.0, 10.0 ); + } + + // clip ideal_yaw + float dy = m_flSoundYaw; + switch( pev->sequence ) + { + case TENTACLE_ANIM_Floor_Rear: + case TENTACLE_ANIM_Floor_Rear_Idle: + case TENTACLE_ANIM_Lev1_Rear: + case TENTACLE_ANIM_Lev1_Rear_Idle: + case TENTACLE_ANIM_Lev2_Rear: + case TENTACLE_ANIM_Lev2_Rear_Idle: + case TENTACLE_ANIM_Lev3_Rear: + case TENTACLE_ANIM_Lev3_Rear_Idle: + if (dy < 0 && dy > -m_flMaxYaw) + dy = -m_flMaxYaw; + if (dy > 0 && dy < m_flMaxYaw) + dy = m_flMaxYaw; + break; + default: + if (dy < -m_flMaxYaw) + dy = -m_flMaxYaw; + if (dy > m_flMaxYaw) + dy = m_flMaxYaw; + } + pev->ideal_yaw = m_flInitialYaw + dy; + + if (m_fSequenceFinished) + { + // ALERT( at_console, "%s done %d %d\n", STRING( pev->targetname ), pev->sequence, m_iGoalAnim ); + if (pev->health <= 1) + { + m_iGoalAnim = TENTACLE_ANIM_Pit_Idle; + if (pev->sequence == TENTACLE_ANIM_Pit_Idle) + { + pev->health = 75; + } + } + else if ( m_flSoundTime > gpGlobals->time ) + { + if (m_flSoundYaw >= -(m_flMaxYaw + 30) && m_flSoundYaw <= (m_flMaxYaw + 30)) + { + // strike + m_iGoalAnim = LookupActivity( ACT_T_STRIKE + m_iSoundLevel ); + } + else if (m_flSoundYaw >= -m_flMaxYaw * 2 && m_flSoundYaw <= m_flMaxYaw * 2) + { + // tap + m_iGoalAnim = LookupActivity( ACT_T_TAP + m_iSoundLevel ); + } + else + { + // go into rear idle + m_iGoalAnim = LookupActivity( ACT_T_REARIDLE + m_iSoundLevel ); + } + } + else if (pev->sequence == TENTACLE_ANIM_Pit_Idle) + { + // stay in pit until hear noise + m_iGoalAnim = TENTACLE_ANIM_Pit_Idle; + } + else if (pev->sequence == m_iGoalAnim) + { + if (MyLevel() >= 0 && gpGlobals->time < m_flSoundTime) + { + if (RANDOM_LONG(0,9) < m_flSoundTime - gpGlobals->time) + { + // continue stike + m_iGoalAnim = LookupActivity( ACT_T_STRIKE + m_iSoundLevel ); + } + else + { + // tap + m_iGoalAnim = LookupActivity( ACT_T_TAP + m_iSoundLevel ); + } + } + else if (MyLevel( ) < 0) + { + m_iGoalAnim = LookupActivity( ACT_T_IDLE + 0 ); + } + else + { + if (m_flNextSong < gpGlobals->time) + { + // play "I hear new something" sound + char *sound; + + switch( RANDOM_LONG(0,1) ) + { + case 0: sound = "tentacle/te_sing1.wav"; break; + case 1: sound = "tentacle/te_sing2.wav"; break; + } + + EMIT_SOUND(ENT(pev), CHAN_VOICE, sound, 1.0, ATTN_NORM); + + m_flNextSong = gpGlobals->time + RANDOM_FLOAT( 10, 20 ); + } + + if (RANDOM_LONG(0,15) == 0) + { + // idle on new level + m_iGoalAnim = LookupActivity( ACT_T_IDLE + RANDOM_LONG(0,3) ); + } + else if (RANDOM_LONG(0,3) == 0) + { + // tap + m_iGoalAnim = LookupActivity( ACT_T_TAP + MyLevel( ) ); + } + else + { + // idle + m_iGoalAnim = LookupActivity( ACT_T_IDLE + MyLevel( ) ); + } + } + if (m_flSoundYaw < 0) + m_flSoundYaw += RANDOM_FLOAT( 2, 8 ); + else + m_flSoundYaw -= RANDOM_FLOAT( 2, 8 ); + } + + pev->sequence = FindTransition( pev->sequence, m_iGoalAnim, &m_iDir ); + + if (m_iDir > 0) + { + pev->frame = 0; + } + else + { + m_iDir = -1; // just to safe + pev->frame = 255; + } + ResetSequenceInfo( ); + + m_flFramerateAdj = RANDOM_FLOAT( -0.2, 0.2 ); + pev->framerate = m_iDir * 1.0 + m_flFramerateAdj; + + switch( pev->sequence) + { + case TENTACLE_ANIM_Floor_Tap: + case TENTACLE_ANIM_Lev1_Tap: + case TENTACLE_ANIM_Lev2_Tap: + case TENTACLE_ANIM_Lev3_Tap: + { + Vector vecSrc; + UTIL_MakeVectors( pev->angles ); + + TraceResult tr1, tr2; + + vecSrc = pev->origin + Vector( 0, 0, MyHeight() - 4); + UTIL_TraceLine( vecSrc, vecSrc + gpGlobals->v_forward * 512, ignore_monsters, ENT( pev ), &tr1 ); + + vecSrc = pev->origin + Vector( 0, 0, MyHeight() + 8); + UTIL_TraceLine( vecSrc, vecSrc + gpGlobals->v_forward * 512, ignore_monsters, ENT( pev ), &tr2 ); + + // ALERT( at_console, "%f %f\n", tr1.flFraction * 512, tr2.flFraction * 512 ); + + m_flTapRadius = SetBlending( 0, RANDOM_FLOAT( tr1.flFraction * 512, tr2.flFraction * 512 ) ); + } + break; + default: + m_flTapRadius = 336; // 400 - 64 + break; + } + pev->view_ofs.z = MyHeight( ); + // ALERT( at_console, "seq %d\n", pev->sequence ); + } + + if (m_flPrevSoundTime + 2.0 > gpGlobals->time) + { + // 1.5 normal speed if hears sounds + pev->framerate = m_iDir * 1.5 + m_flFramerateAdj; + } + else if (m_flPrevSoundTime + 5.0 > gpGlobals->time) + { + // slowdown to normal + pev->framerate = m_iDir + m_iDir * (5 - (gpGlobals->time - m_flPrevSoundTime)) / 2 + m_flFramerateAdj; + } +} + + + +void CTentacle::CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // ALERT( at_console, "%s triggered %d\n", STRING( pev->targetname ), useType ); + switch( useType ) + { + case USE_OFF: + pev->takedamage = DAMAGE_NO; + SetThink( &CTentacle::DieThink ); + m_iGoalAnim = TENTACLE_ANIM_Engine_Death1; + break; + case USE_ON: + if (pActivator) + { + // ALERT( at_console, "insert sound\n"); + CSoundEnt::InsertSound ( bits_SOUND_WORLD, pActivator->pev->origin, 1024, 1.0 ); + } + break; + case USE_SET: + break; + case USE_TOGGLE: + pev->takedamage = DAMAGE_NO; + SetThink( &CTentacle::DieThink ); + m_iGoalAnim = TENTACLE_ANIM_Engine_Idle; + break; + } + +} + + + +void CTentacle :: DieThink( void ) +{ + pev->nextthink = gpGlobals-> time + 0.1; + + DispatchAnimEvents( ); + StudioFrameAdvance( ); + + ChangeYaw( 24 ); + + if (m_fSequenceFinished) + { + if (pev->sequence == m_iGoalAnim) + { + switch( m_iGoalAnim ) + { + case TENTACLE_ANIM_Engine_Idle: + case TENTACLE_ANIM_Engine_Sway: + case TENTACLE_ANIM_Engine_Swat: + case TENTACLE_ANIM_Engine_Bob: + m_iGoalAnim = TENTACLE_ANIM_Engine_Sway + RANDOM_LONG( 0, 2 ); + break; + case TENTACLE_ANIM_Engine_Death1: + case TENTACLE_ANIM_Engine_Death2: + case TENTACLE_ANIM_Engine_Death3: + UTIL_Remove( this ); + return; + } + } + + // ALERT( at_console, "%d : %d => ", pev->sequence, m_iGoalAnim ); + pev->sequence = FindTransition( pev->sequence, m_iGoalAnim, &m_iDir ); + // ALERT( at_console, "%d\n", pev->sequence ); + + if (m_iDir > 0) + { + pev->frame = 0; + } + else + { + pev->frame = 255; + } + ResetSequenceInfo( ); + + float dy; + switch( pev->sequence ) + { + case TENTACLE_ANIM_Floor_Rear: + case TENTACLE_ANIM_Floor_Rear_Idle: + case TENTACLE_ANIM_Lev1_Rear: + case TENTACLE_ANIM_Lev1_Rear_Idle: + case TENTACLE_ANIM_Lev2_Rear: + case TENTACLE_ANIM_Lev2_Rear_Idle: + case TENTACLE_ANIM_Lev3_Rear: + case TENTACLE_ANIM_Lev3_Rear_Idle: + case TENTACLE_ANIM_Engine_Idle: + case TENTACLE_ANIM_Engine_Sway: + case TENTACLE_ANIM_Engine_Swat: + case TENTACLE_ANIM_Engine_Bob: + case TENTACLE_ANIM_Engine_Death1: + case TENTACLE_ANIM_Engine_Death2: + case TENTACLE_ANIM_Engine_Death3: + pev->framerate = RANDOM_FLOAT( m_iDir - 0.2, m_iDir + 0.2 ); + dy = 180; + break; + default: + pev->framerate = 1.5; + dy = 0; + break; + } + pev->ideal_yaw = m_flInitialYaw + dy; + } +} + + +void CTentacle :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + char *sound; + + switch( pEvent->event ) + { + case 1: // bang + { + Vector vecSrc, vecAngles; + GetAttachment( 0, vecSrc, vecAngles ); + + // Vector vecSrc = pev->origin + m_flTapRadius * Vector( cos( pev->angles.y * (3.14192653 / 180.0) ), sin( pev->angles.y * (M_PI / 180.0) ), 0.0 ); + + // vecSrc.z += MyHeight( ); + + switch( m_iTapSound ) + { + case TE_SILO: + UTIL_EmitAmbientSound(ENT(pev), vecSrc, RANDOM_SOUND_ARRAY( pHitSilo ), 1.0, ATTN_NORM, 0, 100); + break; + case TE_NONE: + break; + case TE_DIRT: + UTIL_EmitAmbientSound(ENT(pev), vecSrc, RANDOM_SOUND_ARRAY( pHitDirt ), 1.0, ATTN_NORM, 0, 100); + break; + case TE_WATER: + UTIL_EmitAmbientSound(ENT(pev), vecSrc, RANDOM_SOUND_ARRAY( pHitWater ), 1.0, ATTN_NORM, 0, 100); + break; + } + gpGlobals->force_retouch++; + } + break; + + case 3: // start killing swing + m_iHitDmg = 200; + // UTIL_EmitAmbientSound(ENT(pev), pev->origin + Vector( 0, 0, MyHeight()), "tentacle/te_swing1.wav", 1.0, ATTN_NORM, 0, 100); + break; + + case 4: // end killing swing + m_iHitDmg = 25; + break; + + case 5: // just "whoosh" sound + // UTIL_EmitAmbientSound(ENT(pev), pev->origin + Vector( 0, 0, MyHeight()), "tentacle/te_swing2.wav", 1.0, ATTN_NORM, 0, 100); + break; + + case 2: // tap scrape + case 6: // light tap + { + Vector vecSrc = pev->origin + m_flTapRadius * Vector( cos( pev->angles.y * (M_PI / 180.0) ), sin( pev->angles.y * (M_PI / 180.0) ), 0.0 ); + + vecSrc.z += MyHeight( ); + + float flVol = RANDOM_FLOAT( 0.3, 0.5 ); + + switch( m_iTapSound ) + { + case TE_SILO: + UTIL_EmitAmbientSound(ENT(pev), vecSrc, RANDOM_SOUND_ARRAY( pHitSilo ), flVol, ATTN_NORM, 0, 100); + break; + case TE_NONE: + break; + case TE_DIRT: + UTIL_EmitAmbientSound(ENT(pev), vecSrc, RANDOM_SOUND_ARRAY( pHitDirt ), flVol, ATTN_NORM, 0, 100); + break; + case TE_WATER: + UTIL_EmitAmbientSound(ENT(pev), vecSrc, RANDOM_SOUND_ARRAY( pHitWater ), flVol, ATTN_NORM, 0, 100); + break; + } + } + break; + + + case 7: // roar + switch( RANDOM_LONG(0,1) ) + { + case 0: sound = "tentacle/te_roar1.wav"; break; + case 1: sound = "tentacle/te_roar2.wav"; break; + } + + UTIL_EmitAmbientSound(ENT(pev), pev->origin + Vector( 0, 0, MyHeight()), sound, 1.0, ATTN_NORM, 0, 100); + break; + + case 8: // search + switch( RANDOM_LONG(0,1) ) + { + case 0: sound = "tentacle/te_search1.wav"; break; + case 1: sound = "tentacle/te_search2.wav"; break; + } + + UTIL_EmitAmbientSound(ENT(pev), pev->origin + Vector( 0, 0, MyHeight()), sound, 1.0, ATTN_NORM, 0, 100); + break; + + case 9: // swing + switch( RANDOM_LONG(0,1) ) + { + case 0: sound = "tentacle/te_move1.wav"; break; + case 1: sound = "tentacle/te_move2.wav"; break; + } + + UTIL_EmitAmbientSound(ENT(pev), pev->origin + Vector( 0, 0, MyHeight()), sound, 1.0, ATTN_NORM, 0, 100); + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + } +} + + +// +// TentacleStart +// +// void CTentacle :: Start( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +void CTentacle :: Start( void ) +{ + SetThink( &CTentacle::Cycle ); + + if ( !g_fFlySound ) + { + EMIT_SOUND (ENT(pev), CHAN_BODY, "ambience/flies.wav", 1, ATTN_NORM ); + g_fFlySound = TRUE; +// pev->nextthink = gpGlobals-> time + 0.1; + } + else if ( !g_fSquirmSound ) + { + EMIT_SOUND (ENT(pev), CHAN_BODY, "ambience/squirm2.wav", 1, ATTN_NORM ); + g_fSquirmSound = TRUE; + } + + pev->nextthink = gpGlobals->time + 0.1; +} + + + + +void CTentacle :: HitTouch( CBaseEntity *pOther ) +{ + TraceResult tr = UTIL_GetGlobalTrace( ); + + if (pOther->pev->modelindex == pev->modelindex) + return; + + if (m_flHitTime > gpGlobals->time) + return; + + // only look at the ones where the player hit me + if (tr.pHit == NULL || tr.pHit->v.modelindex != pev->modelindex) + return; + + if (tr.iHitgroup >= 3) + { + pOther->TakeDamage( pev, pev, m_iHitDmg, DMG_CRUSH ); + // ALERT( at_console, "wack %3d : ", m_iHitDmg ); + } + else if (tr.iHitgroup != 0) + { + pOther->TakeDamage( pev, pev, 20, DMG_CRUSH ); + // ALERT( at_console, "tap %3d : ", 20 ); + } + else + { + return; // Huh? + } + + m_flHitTime = gpGlobals->time + 0.5; + + // ALERT( at_console, "%s : ", STRING( tr.pHit->v.classname ) ); + + // ALERT( at_console, "%.0f : %s : %d\n", pev->angles.y, STRING( pOther->pev->classname ), tr.iHitgroup ); +} + + +int CTentacle::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if (flDamage > pev->health) + { + pev->health = 1; + } + else + { + pev->health -= flDamage; + } + return 1; +} + + + + +void CTentacle :: Killed( entvars_t *pevAttacker, int iGib ) +{ + m_iGoalAnim = TENTACLE_ANIM_Pit_Idle; + return; +} + + + +class CTentacleMaw : public CBaseMonster +{ +public: + void Spawn( ); + void Precache( ); +}; + +LINK_ENTITY_TO_CLASS( monster_tentaclemaw, CTentacleMaw ); + +// +// Tentacle Spawn +// +void CTentacleMaw :: Spawn( ) +{ + Precache( ); + SET_MODEL(ENT(pev), "models/maw.mdl"); + UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 64)); + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_STEP; + pev->effects = 0; + pev->health = 75; + pev->yaw_speed = 8; + pev->sequence = 0; + + pev->angles.x = 90; + // ResetSequenceInfo( ); +} + +void CTentacleMaw :: Precache( ) +{ + PRECACHE_MODEL("models/maw.mdl"); +} + +#endif diff --git a/dlls/trains.h b/dlls/trains.h new file mode 100644 index 0000000..4ca1cf7 --- /dev/null +++ b/dlls/trains.h @@ -0,0 +1,127 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef TRAINS_H +#define TRAINS_H + +// Tracktrain spawn flags +#define SF_TRACKTRAIN_NOPITCH 0x0001 +#define SF_TRACKTRAIN_NOCONTROL 0x0002 +#define SF_TRACKTRAIN_FORWARDONLY 0x0004 +#define SF_TRACKTRAIN_PASSABLE 0x0008 + +// Spawnflag for CPathTrack +#define SF_PATH_DISABLED 0x00000001 +#define SF_PATH_FIREONCE 0x00000002 +#define SF_PATH_ALTREVERSE 0x00000004 +#define SF_PATH_DISABLE_TRAIN 0x00000008 +#define SF_PATH_ALTERNATE 0x00008000 + +// Spawnflags of CPathCorner +#define SF_CORNER_WAITFORTRIG 0x001 +#define SF_CORNER_TELEPORT 0x002 +#define SF_CORNER_FIREONCE 0x004 + +//#define PATH_SPARKLE_DEBUG 1 // This makes a particle effect around path_track entities for debugging +class CPathTrack : public CPointEntity +{ +public: + void Spawn( void ); + void Activate( void ); + void KeyValue( KeyValueData* pkvd); + + void SetPrevious( CPathTrack *pprevious ); + void Link( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + CPathTrack *ValidPath( CPathTrack *ppath, int testFlag ); // Returns ppath if enabled, NULL otherwise + void Project( CPathTrack *pstart, CPathTrack *pend, Vector *origin, float dist ); + + static CPathTrack *Instance( edict_t *pent ); + + CPathTrack *LookAhead( Vector *origin, float dist, int move ); + CPathTrack *Nearest( Vector origin ); + + CPathTrack *GetNext( void ); + CPathTrack *GetPrevious( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; +#if PATH_SPARKLE_DEBUG + void EXPORT Sparkle(void); +#endif + + float m_length; + string_t m_altName; + CPathTrack *m_pnext; + CPathTrack *m_pprevious; + CPathTrack *m_paltpath; +}; + + +class CFuncTrackTrain : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + + void Blocked( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData* pkvd ); + + void EXPORT Next( void ); + void EXPORT Find( void ); + void EXPORT NearestPath( void ); + void EXPORT DeadEnd( void ); + + void NextThink( float thinkTime, BOOL alwaysThink ); + + void SetTrack( CPathTrack *track ) { m_ppath = track->Nearest(pev->origin); } + void SetControls( entvars_t *pevControls ); + BOOL OnControls( entvars_t *pev ); + + void StopSound ( void ); + void UpdateSound ( void ); + + static CFuncTrackTrain *Instance( edict_t *pent ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DIRECTIONAL_USE; } + + virtual void OverrideReset( void ); + + CPathTrack *m_ppath; + float m_length; + float m_height; + float m_speed; + float m_dir; + float m_startSpeed; + Vector m_controlMins; + Vector m_controlMaxs; + int m_soundPlaying; + int m_sounds; + float m_flVolume; + float m_flBank; + float m_oldSpeed; + +private: + unsigned short m_usAdjustPitch; +}; + +#endif diff --git a/dlls/triggers.cpp b/dlls/triggers.cpp new file mode 100644 index 0000000..2f94673 --- /dev/null +++ b/dlls/triggers.cpp @@ -0,0 +1,2430 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== triggers.cpp ======================================================== + + spawn and use functions for editor-placed triggers + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "saverestore.h" +#include "trains.h" // trigger_camera has train functionality +#include "gamerules.h" + +#define SF_TRIGGER_PUSH_START_OFF 2//spawnflag that makes trigger_push spawn turned OFF +#define SF_TRIGGER_HURT_TARGETONCE 1// Only fire hurt target once +#define SF_TRIGGER_HURT_START_OFF 2//spawnflag that makes trigger_push spawn turned OFF +#define SF_TRIGGER_HURT_NO_CLIENTS 8//spawnflag that makes trigger_push spawn turned OFF +#define SF_TRIGGER_HURT_CLIENTONLYFIRE 16// trigger hurt will only fire its target if it is hurting a client +#define SF_TRIGGER_HURT_CLIENTONLYTOUCH 32// only clients may touch this trigger. + +extern DLL_GLOBAL BOOL g_fGameOver; + +extern void SetMovedir(entvars_t* pev); +extern Vector VecBModelOrigin( entvars_t* pevBModel ); + +class CFrictionModifier : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT ChangeFriction( CBaseEntity *pOther ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + static TYPEDESCRIPTION m_SaveData[]; + + float m_frictionFraction; // Sorry, couldn't resist this name :) +}; + +LINK_ENTITY_TO_CLASS( func_friction, CFrictionModifier ); + +// Global Savedata for changelevel friction modifier +TYPEDESCRIPTION CFrictionModifier::m_SaveData[] = +{ + DEFINE_FIELD( CFrictionModifier, m_frictionFraction, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE(CFrictionModifier,CBaseEntity); + + +// Modify an entity's friction +void CFrictionModifier :: Spawn( void ) +{ + pev->solid = SOLID_TRIGGER; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->movetype = MOVETYPE_NONE; + SetTouch( &CFrictionModifier::ChangeFriction ); +} + + +// Sets toucher's friction to m_frictionFraction (1.0 = normal friction) +void CFrictionModifier :: ChangeFriction( CBaseEntity *pOther ) +{ + if ( pOther->pev->movetype != MOVETYPE_BOUNCEMISSILE && pOther->pev->movetype != MOVETYPE_BOUNCE ) + pOther->pev->friction = m_frictionFraction; +} + + + +// Sets toucher's friction to m_frictionFraction (1.0 = normal friction) +void CFrictionModifier :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "modifier")) + { + m_frictionFraction = atof(pkvd->szValue) / 100.0; + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +// This trigger will fire when the level spawns (or respawns if not fire once) +// It will check a global state before firing. It supports delay and killtargets + +#define SF_AUTO_FIREONCE 0x0001 + +class CAutoTrigger : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Precache( void ); + void Think( void ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_globalstate; + USE_TYPE triggerType; +}; +LINK_ENTITY_TO_CLASS( trigger_auto, CAutoTrigger ); + +TYPEDESCRIPTION CAutoTrigger::m_SaveData[] = +{ + DEFINE_FIELD( CAutoTrigger, m_globalstate, FIELD_STRING ), + DEFINE_FIELD( CAutoTrigger, triggerType, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE(CAutoTrigger,CBaseDelay); + +void CAutoTrigger::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "globalstate")) + { + m_globalstate = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "triggerstate")) + { + int type = atoi( pkvd->szValue ); + switch( type ) + { + case 0: + triggerType = USE_OFF; + break; + case 2: + triggerType = USE_TOGGLE; + break; + default: + triggerType = USE_ON; + break; + } + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + + +void CAutoTrigger::Spawn( void ) +{ + Precache(); +} + + +void CAutoTrigger::Precache( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CAutoTrigger::Think( void ) +{ + if ( !m_globalstate || gGlobalState.EntityGetState( m_globalstate ) == GLOBAL_ON ) + { + SUB_UseTargets( this, triggerType, 0 ); + if ( pev->spawnflags & SF_AUTO_FIREONCE ) + UTIL_Remove( this ); + } +} + + + +#define SF_RELAY_FIREONCE 0x0001 + +class CTriggerRelay : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + USE_TYPE triggerType; +}; +LINK_ENTITY_TO_CLASS( trigger_relay, CTriggerRelay ); + +TYPEDESCRIPTION CTriggerRelay::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerRelay, triggerType, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerRelay,CBaseDelay); + +void CTriggerRelay::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "triggerstate")) + { + int type = atoi( pkvd->szValue ); + switch( type ) + { + case 0: + triggerType = USE_OFF; + break; + case 2: + triggerType = USE_TOGGLE; + break; + default: + triggerType = USE_ON; + break; + } + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + + +void CTriggerRelay::Spawn( void ) +{ +} + + + + +void CTriggerRelay::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SUB_UseTargets( this, triggerType, 0 ); + if ( pev->spawnflags & SF_RELAY_FIREONCE ) + UTIL_Remove( this ); +} + + +//********************************************************** +// The Multimanager Entity - when fired, will fire up to 16 targets +// at specified times. +// FLAG: THREAD (create clones when triggered) +// FLAG: CLONE (this is a clone for a threaded execution) + +#define SF_MULTIMAN_CLONE 0x80000000 +#define SF_MULTIMAN_THREAD 0x00000001 + +class CMultiManager : public CBaseToggle +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn ( void ); + void EXPORT ManagerThink ( void ); + void EXPORT ManagerUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +#if _DEBUG + void EXPORT ManagerReport( void ); +#endif + + BOOL HasTarget( string_t targetname ); + + int ObjectCaps( void ) { return CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_cTargets; // the total number of targets in this manager's fire list. + int m_index; // Current target + float m_startTime;// Time we started firing + int m_iTargetName [ MAX_MULTI_TARGETS ];// list if indexes into global string array + float m_flTargetDelay [ MAX_MULTI_TARGETS ];// delay (in seconds) from time of manager fire to target fire +private: + inline BOOL IsClone( void ) { return (pev->spawnflags & SF_MULTIMAN_CLONE) ? TRUE : FALSE; } + inline BOOL ShouldClone( void ) + { + if ( IsClone() ) + return FALSE; + + return (pev->spawnflags & SF_MULTIMAN_THREAD) ? TRUE : FALSE; + } + + CMultiManager *Clone( void ); +}; +LINK_ENTITY_TO_CLASS( multi_manager, CMultiManager ); + +// Global Savedata for multi_manager +TYPEDESCRIPTION CMultiManager::m_SaveData[] = +{ + DEFINE_FIELD( CMultiManager, m_cTargets, FIELD_INTEGER ), + DEFINE_FIELD( CMultiManager, m_index, FIELD_INTEGER ), + DEFINE_FIELD( CMultiManager, m_startTime, FIELD_TIME ), + DEFINE_ARRAY( CMultiManager, m_iTargetName, FIELD_STRING, MAX_MULTI_TARGETS ), + DEFINE_ARRAY( CMultiManager, m_flTargetDelay, FIELD_FLOAT, MAX_MULTI_TARGETS ), +}; + +IMPLEMENT_SAVERESTORE(CMultiManager,CBaseToggle); + +void CMultiManager :: KeyValue( KeyValueData *pkvd ) +{ + // UNDONE: Maybe this should do something like this: + //CBaseToggle::KeyValue( pkvd ); + // if ( !pkvd->fHandled ) + // ... etc. + + if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else // add this field to the target list + { + // this assumes that additional fields are targetnames and their values are delay values. + if ( m_cTargets < MAX_MULTI_TARGETS ) + { + char tmp[128]; + + UTIL_StripToken( pkvd->szKeyName, tmp ); + m_iTargetName [ m_cTargets ] = ALLOC_STRING( tmp ); + m_flTargetDelay [ m_cTargets ] = atof (pkvd->szValue); + m_cTargets++; + pkvd->fHandled = TRUE; + } + } +} + + +void CMultiManager :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + SetUse ( &CMultiManager::ManagerUse ); + SetThink ( &CMultiManager::ManagerThink); + + // Sort targets + // Quick and dirty bubble sort + int swapped = 1; + + while ( swapped ) + { + swapped = 0; + for ( int i = 1; i < m_cTargets; i++ ) + { + if ( m_flTargetDelay[i] < m_flTargetDelay[i-1] ) + { + // Swap out of order elements + int name = m_iTargetName[i]; + float delay = m_flTargetDelay[i]; + m_iTargetName[i] = m_iTargetName[i-1]; + m_flTargetDelay[i] = m_flTargetDelay[i-1]; + m_iTargetName[i-1] = name; + m_flTargetDelay[i-1] = delay; + swapped = 1; + } + } + } +} + + +BOOL CMultiManager::HasTarget( string_t targetname ) +{ + for ( int i = 0; i < m_cTargets; i++ ) + if ( FStrEq(STRING(targetname), STRING(m_iTargetName[i])) ) + return TRUE; + + return FALSE; +} + + +// Designers were using this to fire targets that may or may not exist -- +// so I changed it to use the standard target fire code, made it a little simpler. +void CMultiManager :: ManagerThink ( void ) +{ + float time; + + time = gpGlobals->time - m_startTime; + while ( m_index < m_cTargets && m_flTargetDelay[ m_index ] <= time ) + { + FireTargets( STRING( m_iTargetName[ m_index ] ), m_hActivator, this, USE_TOGGLE, 0 ); + m_index++; + } + + if ( m_index >= m_cTargets )// have we fired all targets? + { + SetThink( NULL ); + if ( IsClone() ) + { + UTIL_Remove( this ); + return; + } + SetUse ( &CMultiManager::ManagerUse );// allow manager re-use + } + else + pev->nextthink = m_startTime + m_flTargetDelay[ m_index ]; +} + +CMultiManager *CMultiManager::Clone( void ) +{ + CMultiManager *pMulti = GetClassPtr( (CMultiManager *)NULL ); + + edict_t *pEdict = pMulti->pev->pContainingEntity; + memcpy( pMulti->pev, pev, sizeof(*pev) ); + pMulti->pev->pContainingEntity = pEdict; + + pMulti->pev->spawnflags |= SF_MULTIMAN_CLONE; + pMulti->m_cTargets = m_cTargets; + memcpy( pMulti->m_iTargetName, m_iTargetName, sizeof( m_iTargetName ) ); + memcpy( pMulti->m_flTargetDelay, m_flTargetDelay, sizeof( m_flTargetDelay ) ); + + return pMulti; +} + + +// The USE function builds the time table and starts the entity thinking. +void CMultiManager :: ManagerUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // In multiplayer games, clone the MM and execute in the clone (like a thread) + // to allow multiple players to trigger the same multimanager + if ( ShouldClone() ) + { + CMultiManager *pClone = Clone(); + pClone->ManagerUse( pActivator, pCaller, useType, value ); + return; + } + + m_hActivator = pActivator; + m_index = 0; + m_startTime = gpGlobals->time; + + SetUse( NULL );// disable use until all targets have fired + + SetThink ( &CMultiManager::ManagerThink ); + pev->nextthink = gpGlobals->time; +} + +#if _DEBUG +void CMultiManager :: ManagerReport ( void ) +{ + int cIndex; + + for ( cIndex = 0 ; cIndex < m_cTargets ; cIndex++ ) + { + ALERT ( at_console, "%s %f\n", STRING(m_iTargetName[cIndex]), m_flTargetDelay[cIndex] ); + } +} +#endif + +//*********************************************************** + + +// +// Render parameters trigger +// +// This entity will copy its render parameters (renderfx, rendermode, rendercolor, renderamt) +// to its targets when triggered. +// + + +// Flags to indicate masking off various render parameters that are normally copied to the targets +#define SF_RENDER_MASKFX (1<<0) +#define SF_RENDER_MASKAMT (1<<1) +#define SF_RENDER_MASKMODE (1<<2) +#define SF_RENDER_MASKCOLOR (1<<3) + +class CRenderFxManager : public CBaseEntity +{ +public: + void Spawn( void ); + void Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; + +LINK_ENTITY_TO_CLASS( env_render, CRenderFxManager ); + + +void CRenderFxManager :: Spawn ( void ) +{ + pev->solid = SOLID_NOT; +} + +void CRenderFxManager :: Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (!FStringNull(pev->target)) + { + edict_t* pentTarget = NULL; + while ( 1 ) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->target)); + if (FNullEnt(pentTarget)) + break; + + entvars_t *pevTarget = VARS( pentTarget ); + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKFX ) ) + pevTarget->renderfx = pev->renderfx; + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKAMT ) ) + pevTarget->renderamt = pev->renderamt; + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKMODE ) ) + pevTarget->rendermode = pev->rendermode; + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKCOLOR ) ) + pevTarget->rendercolor = pev->rendercolor; + } + } +} + + + +class CBaseTrigger : public CBaseToggle +{ +public: + void EXPORT TeleportTouch ( CBaseEntity *pOther ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT MultiTouch( CBaseEntity *pOther ); + void EXPORT HurtTouch ( CBaseEntity *pOther ); + void EXPORT CDAudioTouch ( CBaseEntity *pOther ); + void ActivateMultiTrigger( CBaseEntity *pActivator ); + void EXPORT MultiWaitOver( void ); + void EXPORT CounterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void InitTrigger( void ); + + virtual int ObjectCaps( void ) { return CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +LINK_ENTITY_TO_CLASS( trigger, CBaseTrigger ); + +/* +================ +InitTrigger +================ +*/ +void CBaseTrigger::InitTrigger( ) +{ + // trigger angles are used for one-way touches. An angle of 0 is assumed + // to mean no restrictions, so use a yaw of 360 instead. + if (pev->angles != g_vecZero) + SetMovedir(pev); + pev->solid = SOLID_TRIGGER; + pev->movetype = MOVETYPE_NONE; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + if ( CVAR_GET_FLOAT("showtriggers") == 0 ) + SetBits( pev->effects, EF_NODRAW ); +} + + +// +// Cache user-entity-field values until spawn is called. +// + +void CBaseTrigger :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "damage")) + { + pev->dmg = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "count")) + { + m_cTriggersLeft = (int) atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damagetype")) + { + m_bitsDamageInflict = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +class CTriggerHurt : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT RadiationThink( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_hurt, CTriggerHurt ); + +// +// trigger_monsterjump +// +class CTriggerMonsterJump : public CBaseTrigger +{ +public: + void Spawn( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_monsterjump, CTriggerMonsterJump ); + + +void CTriggerMonsterJump :: Spawn ( void ) +{ + SetMovedir ( pev ); + + InitTrigger (); + + pev->nextthink = 0; + pev->speed = 200; + m_flHeight = 150; + + if ( !FStringNull ( pev->targetname ) ) + {// if targetted, spawn turned off + pev->solid = SOLID_NOT; + UTIL_SetOrigin( pev, pev->origin ); // Unlink from trigger list + SetUse( &CTriggerMonsterJump::ToggleUse ); + } +} + + +void CTriggerMonsterJump :: Think( void ) +{ + pev->solid = SOLID_NOT;// kill the trigger for now !!!UNDONE + UTIL_SetOrigin( pev, pev->origin ); // Unlink from trigger list + SetThink( NULL ); +} + +void CTriggerMonsterJump :: Touch( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + if ( !FBitSet ( pevOther->flags , FL_MONSTER ) ) + {// touched by a non-monster. + return; + } + + pevOther->origin.z += 1; + + if ( FBitSet ( pevOther->flags, FL_ONGROUND ) ) + {// clear the onground so physics don't bitch + pevOther->flags &= ~FL_ONGROUND; + } + + // toss the monster! + pevOther->velocity = pev->movedir * pev->speed; + pevOther->velocity.z += m_flHeight; + pev->nextthink = gpGlobals->time; +} + + +//===================================== +// +// trigger_cdaudio - starts/stops cd audio tracks +// +class CTriggerCDAudio : public CBaseTrigger +{ +public: + void Spawn( void ); + + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void PlayTrack( void ); + void Touch ( CBaseEntity *pOther ); +}; + +LINK_ENTITY_TO_CLASS( trigger_cdaudio, CTriggerCDAudio ); + +// +// Changes tracks or stops CD when player touches +// +// !!!HACK - overloaded HEALTH to avoid adding new field +void CTriggerCDAudio :: Touch ( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + {// only clients may trigger these events + return; + } + + PlayTrack(); +} + +void CTriggerCDAudio :: Spawn( void ) +{ + InitTrigger(); +} + +void CTriggerCDAudio::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + PlayTrack(); +} + +void PlayCDTrack( int iTrack ) +{ + edict_t *pClient; + + // manually find the single player. + pClient = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + // Can't play if the client is not connected! + if ( !pClient ) + return; + + if ( iTrack < -1 || iTrack > 30 ) + { + ALERT ( at_console, "TriggerCDAudio - Track %d out of range\n" ); + return; + } + + if ( iTrack == -1 ) + { + CLIENT_COMMAND ( pClient, "cd stop\n"); + } + else + { + char string [ 64 ]; + + sprintf( string, "cd play %3d\n", iTrack ); + CLIENT_COMMAND ( pClient, string); + } +} + + +// only plays for ONE client, so only use in single play! +void CTriggerCDAudio :: PlayTrack( void ) +{ + PlayCDTrack( (int)pev->health ); + + SetTouch( NULL ); + UTIL_Remove( this ); +} + + +// This plays a CD track when fired or when the player enters it's radius +class CTargetCDAudio : public CPointEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + void Play( void ); +}; + +LINK_ENTITY_TO_CLASS( target_cdaudio, CTargetCDAudio ); + +void CTargetCDAudio :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "radius")) + { + pev->scale = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CTargetCDAudio :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + if ( pev->scale > 0 ) + pev->nextthink = gpGlobals->time + 1.0; +} + +void CTargetCDAudio::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + Play(); +} + +// only plays for ONE client, so only use in single play! +void CTargetCDAudio::Think( void ) +{ + edict_t *pClient; + + // manually find the single player. + pClient = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + // Can't play if the client is not connected! + if ( !pClient ) + return; + + pev->nextthink = gpGlobals->time + 0.5; + + if ( (pClient->v.origin - pev->origin).Length() <= pev->scale ) + Play(); + +} + +void CTargetCDAudio::Play( void ) +{ + PlayCDTrack( (int)pev->health ); + UTIL_Remove(this); +} + +//===================================== + +// +// trigger_hurt - hurts anything that touches it. if the trigger has a targetname, firing it will toggle state +// +//int gfToggleState = 0; // used to determine when all radiation trigger hurts have called 'RadiationThink' + +void CTriggerHurt :: Spawn( void ) +{ + InitTrigger(); + SetTouch ( &CTriggerHurt::HurtTouch ); + + if ( !FStringNull ( pev->targetname ) ) + { + SetUse ( &CTriggerHurt::ToggleUse ); + } + else + { + SetUse ( NULL ); + } + + if (m_bitsDamageInflict & DMG_RADIATION) + { + SetThink ( &CTriggerHurt::RadiationThink ); + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(0.0, 0.5); + } + + if ( FBitSet (pev->spawnflags, SF_TRIGGER_HURT_START_OFF) )// if flagged to Start Turned Off, make trigger nonsolid. + pev->solid = SOLID_NOT; + + UTIL_SetOrigin( pev, pev->origin ); // Link into the list +} + +// trigger hurt that causes radiation will do a radius +// check and set the player's geiger counter level +// according to distance from center of trigger + +void CTriggerHurt :: RadiationThink( void ) +{ + + edict_t *pentPlayer; + CBasePlayer *pPlayer = NULL; + float flRange; + entvars_t *pevTarget; + Vector vecSpot1; + Vector vecSpot2; + Vector vecRange; + Vector origin; + Vector view_ofs; + + // check to see if a player is in pvs + // if not, continue + + // set origin to center of trigger so that this check works + origin = pev->origin; + view_ofs = pev->view_ofs; + + pev->origin = (pev->absmin + pev->absmax) * 0.5; + pev->view_ofs = pev->view_ofs * 0.0; + + pentPlayer = FIND_CLIENT_IN_PVS(edict()); + + pev->origin = origin; + pev->view_ofs = view_ofs; + + // reset origin + + if (!FNullEnt(pentPlayer)) + { + + pPlayer = GetClassPtr( (CBasePlayer *)VARS(pentPlayer)); + + pevTarget = VARS(pentPlayer); + + // get range to player; + + vecSpot1 = (pev->absmin + pev->absmax) * 0.5; + vecSpot2 = (pevTarget->absmin + pevTarget->absmax) * 0.5; + + vecRange = vecSpot1 - vecSpot2; + flRange = vecRange.Length(); + + // if player's current geiger counter range is larger + // than range to this trigger hurt, reset player's + // geiger counter range + + if (pPlayer->m_flgeigerRange >= flRange) + pPlayer->m_flgeigerRange = flRange; + } + + pev->nextthink = gpGlobals->time + 0.25; +} + +// +// ToggleUse - If this is the USE function for a trigger, its state will toggle every time it's fired +// +void CBaseTrigger :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (pev->solid == SOLID_NOT) + {// if the trigger is off, turn it on + pev->solid = SOLID_TRIGGER; + + // Force retouch + gpGlobals->force_retouch++; + } + else + {// turn the trigger off + pev->solid = SOLID_NOT; + } + UTIL_SetOrigin( pev, pev->origin ); +} + +// When touched, a hurt trigger does DMG points of damage each half-second +void CBaseTrigger :: HurtTouch ( CBaseEntity *pOther ) +{ + float fldmg; + + if ( !pOther->pev->takedamage ) + return; + + if ( (pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYTOUCH) && !pOther->IsPlayer() ) + { + // this trigger is only allowed to touch clients, and this ain't a client. + return; + } + + if ( (pev->spawnflags & SF_TRIGGER_HURT_NO_CLIENTS) && pOther->IsPlayer() ) + return; + + // HACKHACK -- In multiplayer, players touch this based on packet receipt. + // So the players who send packets later aren't always hurt. Keep track of + // how much time has passed and whether or not you've touched that player + if ( g_pGameRules->IsMultiplayer() ) + { + if ( pev->dmgtime > gpGlobals->time ) + { + if ( gpGlobals->time != pev->pain_finished ) + {// too early to hurt again, and not same frame with a different entity + if ( pOther->IsPlayer() ) + { + int playerMask = 1 << (pOther->entindex() - 1); + + // If I've already touched this player (this time), then bail out + if ( pev->impulse & playerMask ) + return; + + // Mark this player as touched + // BUGBUG - There can be only 32 players! + pev->impulse |= playerMask; + } + else + { + return; + } + } + } + else + { + // New clock, "un-touch" all players + pev->impulse = 0; + if ( pOther->IsPlayer() ) + { + int playerMask = 1 << (pOther->entindex() - 1); + + // Mark this player as touched + // BUGBUG - There can be only 32 players! + pev->impulse |= playerMask; + } + } + } + else // Original code -- single player + { + if ( pev->dmgtime > gpGlobals->time && gpGlobals->time != pev->pain_finished ) + {// too early to hurt again, and not same frame with a different entity + return; + } + } + + + + // If this is time_based damage (poison, radiation), override the pev->dmg with a + // default for the given damage type. Monsters only take time-based damage + // while touching the trigger. Player continues taking damage for a while after + // leaving the trigger + + fldmg = pev->dmg * 0.5; // 0.5 seconds worth of damage, pev->dmg is damage/second + + + // JAY: Cut this because it wasn't fully realized. Damage is simpler now. +#if 0 + switch (m_bitsDamageInflict) + { + default: break; + case DMG_POISON: fldmg = POISON_DAMAGE/4; break; + case DMG_NERVEGAS: fldmg = NERVEGAS_DAMAGE/4; break; + case DMG_RADIATION: fldmg = RADIATION_DAMAGE/4; break; + case DMG_PARALYZE: fldmg = PARALYZE_DAMAGE/4; break; // UNDONE: cut this? should slow movement to 50% + case DMG_ACID: fldmg = ACID_DAMAGE/4; break; + case DMG_SLOWBURN: fldmg = SLOWBURN_DAMAGE/4; break; + case DMG_SLOWFREEZE: fldmg = SLOWFREEZE_DAMAGE/4; break; + } +#endif + + if ( fldmg < 0 ) + pOther->TakeHealth( -fldmg, m_bitsDamageInflict ); + else + pOther->TakeDamage( pev, pev, fldmg, m_bitsDamageInflict ); + + // Store pain time so we can get all of the other entities on this frame + pev->pain_finished = gpGlobals->time; + + // Apply damage every half second + pev->dmgtime = gpGlobals->time + 0.5;// half second delay until this trigger can hurt toucher again + + + + if ( pev->target ) + { + // trigger has a target it wants to fire. + if ( pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYFIRE ) + { + // if the toucher isn't a client, don't fire the target! + if ( !pOther->IsPlayer() ) + { + return; + } + } + + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); + if ( pev->spawnflags & SF_TRIGGER_HURT_TARGETONCE ) + pev->target = 0; + } +} + + +/*QUAKED trigger_multiple (.5 .5 .5) ? notouch +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. +"wait" : Seconds between triggerings. (.2 default) +If notouch is set, the trigger is only fired by other entities, not by touching. +NOTOUCH has been obsoleted by trigger_relay! +sounds +1) secret +2) beep beep +3) large switch +4) +NEW +if a trigger has a NETNAME, that NETNAME will become the TARGET of the triggered object. +*/ +class CTriggerMultiple : public CBaseTrigger +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_multiple, CTriggerMultiple ); + + +void CTriggerMultiple :: Spawn( void ) +{ + if (m_flWait == 0) + m_flWait = 0.2; + + InitTrigger(); + + ASSERTSZ(pev->health == 0, "trigger_multiple with health"); +// UTIL_SetOrigin(pev, pev->origin); +// SET_MODEL( ENT(pev), STRING(pev->model) ); +// if (pev->health > 0) +// { +// if (FBitSet(pev->spawnflags, SPAWNFLAG_NOTOUCH)) +// ALERT(at_error, "trigger_multiple spawn: health and notouch don't make sense"); +// pev->max_health = pev->health; +//UNDONE: where to get pfnDie from? +// pev->pfnDie = multi_killed; +// pev->takedamage = DAMAGE_YES; +// pev->solid = SOLID_BBOX; +// UTIL_SetOrigin(pev, pev->origin); // make sure it links into the world +// } +// else + { + SetTouch( &CTriggerMultiple::MultiTouch ); + } + } + + +/*QUAKED trigger_once (.5 .5 .5) ? notouch +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 +"targetname". If "health" is set, the trigger must be killed to activate. +If notouch is set, the trigger is only fired by other entities, not by touching. +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. Use "360" for an angle of 0. +sounds +1) secret +2) beep beep +3) large switch +4) +*/ +class CTriggerOnce : public CTriggerMultiple +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_once, CTriggerOnce ); +void CTriggerOnce::Spawn( void ) +{ + m_flWait = -1; + + CTriggerMultiple :: Spawn(); +} + + + +void CBaseTrigger :: MultiTouch( CBaseEntity *pOther ) +{ + entvars_t *pevToucher; + + pevToucher = pOther->pev; + + // Only touch clients, monsters, or pushables (depending on flags) + if ( ((pevToucher->flags & FL_CLIENT) && !(pev->spawnflags & SF_TRIGGER_NOCLIENTS)) || + ((pevToucher->flags & FL_MONSTER) && (pev->spawnflags & SF_TRIGGER_ALLOWMONSTERS)) || + (pev->spawnflags & SF_TRIGGER_PUSHABLES) && FClassnameIs(pevToucher,"func_pushable") ) + { + +#if 0 + // if the trigger has an angles field, check player's facing direction + if (pev->movedir != g_vecZero) + { + UTIL_MakeVectors( pevToucher->angles ); + if ( DotProduct( gpGlobals->v_forward, pev->movedir ) < 0 ) + return; // not facing the right way + } +#endif + + ActivateMultiTrigger( pOther ); + } +} + + +// +// the trigger was just touched/killed/used +// self.enemy should be set to the activator so it can be held through a delay +// so wait for the delay time before firing +// +void CBaseTrigger :: ActivateMultiTrigger( CBaseEntity *pActivator ) +{ + if (pev->nextthink > gpGlobals->time) + return; // still waiting for reset time + + if (!UTIL_IsMasterTriggered(m_sMaster,pActivator)) + return; + + if (FClassnameIs(pev, "trigger_secret")) + { + if ( pev->enemy == NULL || !FClassnameIs(pev->enemy, "player")) + return; + gpGlobals->found_secrets++; + } + + if (!FStringNull(pev->noise)) + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + +// don't trigger again until reset +// pev->takedamage = DAMAGE_NO; + + m_hActivator = pActivator; + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + + if ( pev->message && pActivator->IsPlayer() ) + { + UTIL_ShowMessage( STRING(pev->message), pActivator ); +// CLIENT_PRINTF( ENT( pActivator->pev ), print_center, STRING(pev->message) ); + } + + if (m_flWait > 0) + { + SetThink( &CBaseTrigger::MultiWaitOver ); + pev->nextthink = gpGlobals->time + m_flWait; + } + else + { + // we can't just remove (self) here, because this is a touch function + // called while C code is looping through area links... + SetTouch( NULL ); + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CBaseTrigger::SUB_Remove ); + } +} + + +// the wait time has passed, so set back up for another activation +void CBaseTrigger :: MultiWaitOver( void ) +{ +// if (pev->max_health) +// { +// pev->health = pev->max_health; +// pev->takedamage = DAMAGE_YES; +// pev->solid = SOLID_BBOX; +// } + SetThink( NULL ); +} + + +// ========================= COUNTING TRIGGER ===================================== + +// +// GLOBALS ASSUMED SET: g_eoActivator +// +void CBaseTrigger::CounterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_cTriggersLeft--; + m_hActivator = pActivator; + + if (m_cTriggersLeft < 0) + return; + + BOOL fTellActivator = + (m_hActivator != 0) && + FClassnameIs(m_hActivator->pev, "player") && + !FBitSet(pev->spawnflags, SPAWNFLAG_NOMESSAGE); + if (m_cTriggersLeft != 0) + { + if (fTellActivator) + { + // UNDONE: I don't think we want these Quakesque messages + switch (m_cTriggersLeft) + { + case 1: ALERT(at_console, "Only 1 more to go..."); break; + case 2: ALERT(at_console, "Only 2 more to go..."); break; + case 3: ALERT(at_console, "Only 3 more to go..."); break; + default: ALERT(at_console, "There are more to go..."); break; + } + } + return; + } + + // !!!UNDONE: I don't think we want these Quakesque messages + if (fTellActivator) + ALERT(at_console, "Sequence completed!"); + + ActivateMultiTrigger( m_hActivator ); +} + + +/*QUAKED trigger_counter (.5 .5 .5) ? nomessage +Acts as an intermediary for an action that takes multiple inputs. +If nomessage is not set, it will print "1 more.. " etc when triggered and +"sequence complete" when finished. After the counter has been triggered "cTriggersLeft" +times (default 2), it will fire all of it's targets and remove itself. +*/ +class CTriggerCounter : public CBaseTrigger +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( trigger_counter, CTriggerCounter ); + +void CTriggerCounter :: Spawn( void ) +{ + // By making the flWait be -1, this counter-trigger will disappear after it's activated + // (but of course it needs cTriggersLeft "uses" before that happens). + m_flWait = -1; + + if (m_cTriggersLeft == 0) + m_cTriggersLeft = 2; + SetUse( &CTriggerCounter::CounterUse ); +} + +// ====================== TRIGGER_CHANGELEVEL ================================ + +class CTriggerVolume : public CPointEntity // Derive from point entity so this doesn't move across levels +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_transition, CTriggerVolume ); + +// Define space that travels across a level transition +void CTriggerVolume :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->model = NULL; + pev->modelindex = 0; +} + + +// Fires a target after level transition and then dies +class CFireAndDie : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void Think( void ); + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() | FCAP_FORCE_TRANSITION; } // Always go across transitions +}; +LINK_ENTITY_TO_CLASS( fireanddie, CFireAndDie ); + +void CFireAndDie::Spawn( void ) +{ + pev->classname = MAKE_STRING("fireanddie"); + // Don't call Precache() - it should be called on restore +} + + +void CFireAndDie::Precache( void ) +{ + // This gets called on restore + pev->nextthink = gpGlobals->time + m_flDelay; +} + + +void CFireAndDie::Think( void ) +{ + SUB_UseTargets( this, USE_TOGGLE, 0 ); + UTIL_Remove( this ); +} + + +#define SF_CHANGELEVEL_USEONLY 0x0002 +class CChangeLevel : public CBaseTrigger +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT UseChangeLevel ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT TriggerChangeLevel( void ); + void EXPORT ExecuteChangeLevel( void ); + void EXPORT TouchChangeLevel( CBaseEntity *pOther ); + void ChangeLevelNow( CBaseEntity *pActivator ); + + static edict_t *FindLandmark( const char *pLandmarkName ); + static int ChangeList( LEVELLIST *pLevelList, int maxList ); + static int AddTransitionToList( LEVELLIST *pLevelList, int listCount, const char *pMapName, const char *pLandmarkName, edict_t *pentLandmark ); + static int InTransitionVolume( CBaseEntity *pEntity, char *pVolumeName ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + char m_szMapName[cchMapNameMost]; // trigger_changelevel only: next map + char m_szLandmarkName[cchMapNameMost]; // trigger_changelevel only: landmark on next map + int m_changeTarget; + float m_changeTargetDelay; +}; +LINK_ENTITY_TO_CLASS( trigger_changelevel, CChangeLevel ); + +// Global Savedata for changelevel trigger +TYPEDESCRIPTION CChangeLevel::m_SaveData[] = +{ + DEFINE_ARRAY( CChangeLevel, m_szMapName, FIELD_CHARACTER, cchMapNameMost ), + DEFINE_ARRAY( CChangeLevel, m_szLandmarkName, FIELD_CHARACTER, cchMapNameMost ), + DEFINE_FIELD( CChangeLevel, m_changeTarget, FIELD_STRING ), + DEFINE_FIELD( CChangeLevel, m_changeTargetDelay, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE(CChangeLevel,CBaseTrigger); + +// +// Cache user-entity-field values until spawn is called. +// + +void CChangeLevel :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "map")) + { + if (strlen(pkvd->szValue) >= cchMapNameMost) + ALERT( at_error, "Map name '%s' too long (32 chars)\n", pkvd->szValue ); + strcpy(m_szMapName, pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "landmark")) + { + if (strlen(pkvd->szValue) >= cchMapNameMost) + ALERT( at_error, "Landmark name '%s' too long (32 chars)\n", pkvd->szValue ); + strcpy(m_szLandmarkName, pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "changetarget")) + { + m_changeTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "changedelay")) + { + m_changeTargetDelay = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseTrigger::KeyValue( pkvd ); +} + + +/*QUAKED trigger_changelevel (0.5 0.5 0.5) ? NO_INTERMISSION +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. +*/ + +void CChangeLevel :: Spawn( void ) +{ + if ( FStrEq( m_szMapName, "" ) ) + ALERT( at_console, "a trigger_changelevel doesn't have a map" ); + + if ( FStrEq( m_szLandmarkName, "" ) ) + ALERT( at_console, "trigger_changelevel to %s doesn't have a landmark", m_szMapName ); + + if (!FStringNull ( pev->targetname ) ) + { + SetUse ( &CChangeLevel::UseChangeLevel ); + } + InitTrigger(); + if ( !(pev->spawnflags & SF_CHANGELEVEL_USEONLY) ) + SetTouch( &CChangeLevel::TouchChangeLevel ); +// ALERT( at_console, "TRANSITION: %s (%s)\n", m_szMapName, m_szLandmarkName ); +} + + +void CChangeLevel :: ExecuteChangeLevel( void ) +{ + MESSAGE_BEGIN( MSG_ALL, SVC_CDTRACK ); + WRITE_BYTE( 3 ); + WRITE_BYTE( 3 ); + MESSAGE_END(); + + MESSAGE_BEGIN(MSG_ALL, SVC_INTERMISSION); + MESSAGE_END(); +} + + +FILE_GLOBAL char st_szNextMap[cchMapNameMost]; +FILE_GLOBAL char st_szNextSpot[cchMapNameMost]; + +edict_t *CChangeLevel :: FindLandmark( const char *pLandmarkName ) +{ + edict_t *pentLandmark; + + pentLandmark = FIND_ENTITY_BY_STRING( NULL, "targetname", pLandmarkName ); + while ( !FNullEnt( pentLandmark ) ) + { + // Found the landmark + if ( FClassnameIs( pentLandmark, "info_landmark" ) ) + return pentLandmark; + else + pentLandmark = FIND_ENTITY_BY_STRING( pentLandmark, "targetname", pLandmarkName ); + } + ALERT( at_error, "Can't find landmark %s\n", pLandmarkName ); + return NULL; +} + + +//========================================================= +// CChangeLevel :: Use - allows level transitions to be +// triggered by buttons, etc. +// +//========================================================= +void CChangeLevel :: UseChangeLevel ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + ChangeLevelNow( pActivator ); +} + +void CChangeLevel :: ChangeLevelNow( CBaseEntity *pActivator ) +{ + edict_t *pentLandmark; + LEVELLIST levels[16]; + + ASSERT(!FStrEq(m_szMapName, "")); + + // Don't work in deathmatch + if ( g_pGameRules->IsDeathmatch() ) + return; + + // Some people are firing these multiple times in a frame, disable + if ( gpGlobals->time == pev->dmgtime ) + return; + + pev->dmgtime = gpGlobals->time; + + + CBaseEntity *pPlayer = CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) ); + if ( !InTransitionVolume( pPlayer, m_szLandmarkName ) ) + { + ALERT( at_aiconsole, "Player isn't in the transition volume %s, aborting\n", m_szLandmarkName ); + return; + } + + // Create an entity to fire the changetarget + if ( m_changeTarget ) + { + CFireAndDie *pFireAndDie = GetClassPtr( (CFireAndDie *)NULL ); + if ( pFireAndDie ) + { + // Set target and delay + pFireAndDie->pev->target = m_changeTarget; + pFireAndDie->m_flDelay = m_changeTargetDelay; + pFireAndDie->pev->origin = pPlayer->pev->origin; + // Call spawn + DispatchSpawn( pFireAndDie->edict() ); + } + } + // This object will get removed in the call to CHANGE_LEVEL, copy the params into "safe" memory + strcpy(st_szNextMap, m_szMapName); + + m_hActivator = pActivator; + SUB_UseTargets( pActivator, USE_TOGGLE, 0 ); + st_szNextSpot[0] = 0; // Init landmark to NULL + + // look for a landmark entity + pentLandmark = FindLandmark( m_szLandmarkName ); + if ( !FNullEnt( pentLandmark ) ) + { + strcpy(st_szNextSpot, m_szLandmarkName); + gpGlobals->vecLandmarkOffset = VARS(pentLandmark)->origin; + } +// ALERT( at_console, "Level touches %d levels\n", ChangeList( levels, 16 ) ); + ALERT( at_console, "CHANGE LEVEL: %s %s\n", st_szNextMap, st_szNextSpot ); + CHANGE_LEVEL( st_szNextMap, st_szNextSpot ); +} + +// +// GLOBALS ASSUMED SET: st_szNextMap +// +void CChangeLevel :: TouchChangeLevel( CBaseEntity *pOther ) +{ + if (!FClassnameIs(pOther->pev, "player")) + return; + + ChangeLevelNow( pOther ); +} + + +// Add a transition to the list, but ignore duplicates +// (a designer may have placed multiple trigger_changelevels with the same landmark) +int CChangeLevel::AddTransitionToList( LEVELLIST *pLevelList, int listCount, const char *pMapName, const char *pLandmarkName, edict_t *pentLandmark ) +{ + int i; + + if ( !pLevelList || !pMapName || !pLandmarkName || !pentLandmark ) + return 0; + + for ( i = 0; i < listCount; i++ ) + { + if ( pLevelList[i].pentLandmark == pentLandmark && strcmp( pLevelList[i].mapName, pMapName ) == 0 ) + return 0; + } + strcpy( pLevelList[listCount].mapName, pMapName ); + strcpy( pLevelList[listCount].landmarkName, pLandmarkName ); + pLevelList[listCount].pentLandmark = pentLandmark; + pLevelList[listCount].vecLandmarkOrigin = VARS(pentLandmark)->origin; + + return 1; +} + +int BuildChangeList( LEVELLIST *pLevelList, int maxList ) +{ + return CChangeLevel::ChangeList( pLevelList, maxList ); +} + + +int CChangeLevel::InTransitionVolume( CBaseEntity *pEntity, char *pVolumeName ) +{ + edict_t *pentVolume; + + + if ( pEntity->ObjectCaps() & FCAP_FORCE_TRANSITION ) + return 1; + + // If you're following another entity, follow it through the transition (weapons follow the player) + if ( pEntity->pev->movetype == MOVETYPE_FOLLOW ) + { + if ( pEntity->pev->aiment != NULL ) + pEntity = CBaseEntity::Instance( pEntity->pev->aiment ); + } + + int inVolume = 1; // Unless we find a trigger_transition, everything is in the volume + + pentVolume = FIND_ENTITY_BY_TARGETNAME( NULL, pVolumeName ); + while ( !FNullEnt( pentVolume ) ) + { + CBaseEntity *pVolume = CBaseEntity::Instance( pentVolume ); + + if ( pVolume && FClassnameIs( pVolume->pev, "trigger_transition" ) ) + { + if ( pVolume->Intersects( pEntity ) ) // It touches one, it's in the volume + return 1; + else + inVolume = 0; // Found a trigger_transition, but I don't intersect it -- if I don't find another, don't go! + } + pentVolume = FIND_ENTITY_BY_TARGETNAME( pentVolume, pVolumeName ); + } + + return inVolume; +} + + +// We can only ever move 512 entities across a transition +#define MAX_ENTITY 512 + +// This has grown into a complicated beast +// Can we make this more elegant? +// This builds the list of all transitions on this level and which entities are in their PVS's and can / should +// be moved across. +int CChangeLevel::ChangeList( LEVELLIST *pLevelList, int maxList ) +{ + edict_t *pentChangelevel, *pentLandmark; + int i, count; + + count = 0; + + // Find all of the possible level changes on this BSP + pentChangelevel = FIND_ENTITY_BY_STRING( NULL, "classname", "trigger_changelevel" ); + if ( FNullEnt( pentChangelevel ) ) + return 0; + while ( !FNullEnt( pentChangelevel ) ) + { + CChangeLevel *pTrigger; + + pTrigger = GetClassPtr((CChangeLevel *)VARS(pentChangelevel)); + if ( pTrigger ) + { + // Find the corresponding landmark + pentLandmark = FindLandmark( pTrigger->m_szLandmarkName ); + if ( pentLandmark ) + { + // Build a list of unique transitions + if ( AddTransitionToList( pLevelList, count, pTrigger->m_szMapName, pTrigger->m_szLandmarkName, pentLandmark ) ) + { + count++; + if ( count >= maxList ) // FULL!! + break; + } + } + } + pentChangelevel = FIND_ENTITY_BY_STRING( pentChangelevel, "classname", "trigger_changelevel" ); + } + + if ( gpGlobals->pSaveData && ((SAVERESTOREDATA *)gpGlobals->pSaveData)->pTable ) + { + CSave saveHelper( (SAVERESTOREDATA *)gpGlobals->pSaveData ); + + for ( i = 0; i < count; i++ ) + { + int j, entityCount = 0; + CBaseEntity *pEntList[ MAX_ENTITY ]; + int entityFlags[ MAX_ENTITY ]; + + // Follow the linked list of entities in the PVS of the transition landmark + edict_t *pent = UTIL_EntitiesInPVS( pLevelList[i].pentLandmark ); + + // Build a list of valid entities in this linked list (we're going to use pent->v.chain again) + while ( !FNullEnt( pent ) ) + { + CBaseEntity *pEntity = CBaseEntity::Instance(pent); + if ( pEntity ) + { +// ALERT( at_console, "Trying %s\n", STRING(pEntity->pev->classname) ); + int caps = pEntity->ObjectCaps(); + if ( !(caps & FCAP_DONT_SAVE) ) + { + int flags = 0; + + // If this entity can be moved or is global, mark it + if ( caps & FCAP_ACROSS_TRANSITION ) + flags |= FENTTABLE_MOVEABLE; + if ( pEntity->pev->globalname && !pEntity->IsDormant() ) + flags |= FENTTABLE_GLOBAL; + if ( flags ) + { + pEntList[ entityCount ] = pEntity; + entityFlags[ entityCount ] = flags; + entityCount++; + if ( entityCount > MAX_ENTITY ) + ALERT( at_error, "Too many entities across a transition!" ); + } +// else +// ALERT( at_console, "Failed %s\n", STRING(pEntity->pev->classname) ); + } +// else +// ALERT( at_console, "DON'T SAVE %s\n", STRING(pEntity->pev->classname) ); + } + pent = pent->v.chain; + } + + for ( j = 0; j < entityCount; j++ ) + { + // Check to make sure the entity isn't screened out by a trigger_transition + if ( entityFlags[j] && InTransitionVolume( pEntList[j], pLevelList[i].landmarkName ) ) + { + // Mark entity table with 1<pev->classname) ); + + } + } + } + + return count; +} + +/* +go to the next level for deathmatch +only called if a time or frag limit has expired +*/ +void NextLevel( void ) +{ + edict_t* pent; + CChangeLevel *pChange; + + // find a trigger_changelevel + pent = FIND_ENTITY_BY_CLASSNAME(NULL, "trigger_changelevel"); + + // go back to start if no trigger_changelevel + if (FNullEnt(pent)) + { + gpGlobals->mapname = ALLOC_STRING("start"); + pChange = GetClassPtr( (CChangeLevel *)NULL ); + strcpy(pChange->m_szMapName, "start"); + } + else + pChange = GetClassPtr( (CChangeLevel *)VARS(pent)); + + strcpy(st_szNextMap, pChange->m_szMapName); + g_fGameOver = TRUE; + + if (pChange->pev->nextthink < gpGlobals->time) + { + pChange->SetThink( &CChangeLevel::ExecuteChangeLevel ); + pChange->pev->nextthink = gpGlobals->time + 0.1; + } +} + + +// ============================== LADDER ======================================= + +class CLadder : public CBaseTrigger +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS( func_ladder, CLadder ); + + +void CLadder :: KeyValue( KeyValueData *pkvd ) +{ + CBaseTrigger::KeyValue( pkvd ); +} + + +//========================================================= +// func_ladder - makes an area vertically negotiable +//========================================================= +void CLadder :: Precache( void ) +{ + // Do all of this in here because we need to 'convert' old saved games + pev->solid = SOLID_NOT; + pev->skin = CONTENTS_LADDER; + if ( CVAR_GET_FLOAT("showtriggers") == 0 ) + { + pev->rendermode = kRenderTransTexture; + pev->renderamt = 0; + } + pev->effects &= ~EF_NODRAW; +} + + +void CLadder :: Spawn( void ) +{ + Precache(); + + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->movetype = MOVETYPE_PUSH; +} + + +// ========================== A TRIGGER THAT PUSHES YOU =============================== + +class CTriggerPush : public CBaseTrigger +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Touch( CBaseEntity *pOther ); +}; +LINK_ENTITY_TO_CLASS( trigger_push, CTriggerPush ); + + +void CTriggerPush :: KeyValue( KeyValueData *pkvd ) +{ + CBaseTrigger::KeyValue( pkvd ); +} + + +/*QUAKED trigger_push (.5 .5 .5) ? TRIG_PUSH_ONCE +Pushes the player +*/ + +void CTriggerPush :: Spawn( ) +{ + if ( pev->angles == g_vecZero ) + pev->angles.y = 360; + InitTrigger(); + + if (pev->speed == 0) + pev->speed = 100; + + if ( FBitSet (pev->spawnflags, SF_TRIGGER_PUSH_START_OFF) )// if flagged to Start Turned Off, make trigger nonsolid. + pev->solid = SOLID_NOT; + + SetUse( &CTriggerPush::ToggleUse ); + + UTIL_SetOrigin( pev, pev->origin ); // Link into the list +} + + +void CTriggerPush :: Touch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + + // UNDONE: Is there a better way than health to detect things that have physics? (clients/monsters) + switch( pevToucher->movetype ) + { + case MOVETYPE_NONE: + case MOVETYPE_PUSH: + case MOVETYPE_NOCLIP: + case MOVETYPE_FOLLOW: + return; + } + + if ( pevToucher->solid != SOLID_NOT && pevToucher->solid != SOLID_BSP ) + { + // Instant trigger, just transfer velocity and remove + if (FBitSet(pev->spawnflags, SF_TRIG_PUSH_ONCE)) + { + pevToucher->velocity = pevToucher->velocity + (pev->speed * pev->movedir); + if ( pevToucher->velocity.z > 0 ) + pevToucher->flags &= ~FL_ONGROUND; + UTIL_Remove( this ); + } + else + { // Push field, transfer to base velocity + Vector vecPush = (pev->speed * pev->movedir); + if ( pevToucher->flags & FL_BASEVELOCITY ) + vecPush = vecPush + pevToucher->basevelocity; + + pevToucher->basevelocity = vecPush; + + pevToucher->flags |= FL_BASEVELOCITY; +// ALERT( at_console, "Vel %f, base %f\n", pevToucher->velocity.z, pevToucher->basevelocity.z ); + } + } +} + + +//====================================== +// teleport trigger +// +// + +void CBaseTrigger :: TeleportTouch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + edict_t *pentTarget = NULL; + + // Only teleport monsters or clients + if ( !FBitSet( pevToucher->flags, FL_CLIENT|FL_MONSTER ) ) + return; + + if (!UTIL_IsMasterTriggered(m_sMaster, pOther)) + return; + + if ( !( pev->spawnflags & SF_TRIGGER_ALLOWMONSTERS ) ) + {// no monsters allowed! + if ( FBitSet( pevToucher->flags, FL_MONSTER ) ) + { + return; + } + } + + if ( ( pev->spawnflags & SF_TRIGGER_NOCLIENTS ) ) + {// no clients allowed + if ( pOther->IsPlayer() ) + { + return; + } + } + + pentTarget = FIND_ENTITY_BY_TARGETNAME( pentTarget, STRING(pev->target) ); + if (FNullEnt(pentTarget)) + return; + + Vector tmp = VARS( pentTarget )->origin; + + if ( pOther->IsPlayer() ) + { + tmp.z -= pOther->pev->mins.z;// make origin adjustments in case the teleportee is a player. (origin in center, not at feet) + } + + tmp.z++; + + pevToucher->flags &= ~FL_ONGROUND; + + UTIL_SetOrigin( pevToucher, tmp ); + + pevToucher->angles = pentTarget->v.angles; + + if ( pOther->IsPlayer() ) + { + pevToucher->v_angle = pentTarget->v.angles; + } + + pevToucher->fixangle = TRUE; + pevToucher->velocity = pevToucher->basevelocity = g_vecZero; +} + + +class CTriggerTeleport : public CBaseTrigger +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( trigger_teleport, CTriggerTeleport ); + +void CTriggerTeleport :: Spawn( void ) +{ + InitTrigger(); + + SetTouch( &CTriggerTeleport::TeleportTouch ); +} + + +LINK_ENTITY_TO_CLASS( info_teleport_destination, CPointEntity ); + + + +class CTriggerSave : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT SaveTouch( CBaseEntity *pOther ); +}; +LINK_ENTITY_TO_CLASS( trigger_autosave, CTriggerSave ); + +void CTriggerSave::Spawn( void ) +{ + if ( g_pGameRules->IsDeathmatch() ) + { + REMOVE_ENTITY( ENT(pev) ); + return; + } + + InitTrigger(); + SetTouch( &CTriggerSave::SaveTouch ); +} + +void CTriggerSave::SaveTouch( CBaseEntity *pOther ) +{ + if ( !UTIL_IsMasterTriggered( m_sMaster, pOther ) ) + return; + + // Only save on clients + if ( !pOther->IsPlayer() ) + return; + + SetTouch( NULL ); + UTIL_Remove( this ); + SERVER_COMMAND( "autosave\n" ); +} + +#define SF_ENDSECTION_USEONLY 0x0001 + +class CTriggerEndSection : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT EndSectionTouch( CBaseEntity *pOther ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT EndSectionUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; +LINK_ENTITY_TO_CLASS( trigger_endsection, CTriggerEndSection ); + + +void CTriggerEndSection::EndSectionUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Only save on clients + if ( pActivator && !pActivator->IsNetClient() ) + return; + + SetUse( NULL ); + + if ( pev->message ) + { + g_engfuncs.pfnEndSection(STRING(pev->message)); + } + UTIL_Remove( this ); +} + +void CTriggerEndSection::Spawn( void ) +{ + if ( g_pGameRules->IsDeathmatch() ) + { + REMOVE_ENTITY( ENT(pev) ); + return; + } + + InitTrigger(); + + SetUse ( &CTriggerEndSection::EndSectionUse ); + // If it is a "use only" trigger, then don't set the touch function. + if ( ! (pev->spawnflags & SF_ENDSECTION_USEONLY) ) + SetTouch( &CTriggerEndSection::EndSectionTouch ); +} + +void CTriggerEndSection::EndSectionTouch( CBaseEntity *pOther ) +{ + // Only save on clients + if ( !pOther->IsNetClient() ) + return; + + SetTouch( NULL ); + + if (pev->message) + { + g_engfuncs.pfnEndSection(STRING(pev->message)); + } + UTIL_Remove( this ); +} + +void CTriggerEndSection :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "section")) + { +// m_iszSectionName = ALLOC_STRING( pkvd->szValue ); + // Store this in message so we don't have to write save/restore for this ent + pev->message = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseTrigger::KeyValue( pkvd ); +} + + +class CTriggerGravity : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT GravityTouch( CBaseEntity *pOther ); +}; +LINK_ENTITY_TO_CLASS( trigger_gravity, CTriggerGravity ); + +void CTriggerGravity::Spawn( void ) +{ + InitTrigger(); + SetTouch( &CTriggerGravity::GravityTouch ); +} + +void CTriggerGravity::GravityTouch( CBaseEntity *pOther ) +{ + // Only save on clients + if ( !pOther->IsPlayer() ) + return; + + pOther->pev->gravity = pev->gravity; +} + + + + + + + +// this is a really bad idea. +class CTriggerChangeTarget : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_iszNewTarget; +}; +LINK_ENTITY_TO_CLASS( trigger_changetarget, CTriggerChangeTarget ); + +TYPEDESCRIPTION CTriggerChangeTarget::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerChangeTarget, m_iszNewTarget, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerChangeTarget,CBaseDelay); + +void CTriggerChangeTarget::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszNewTarget")) + { + m_iszNewTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + +void CTriggerChangeTarget::Spawn( void ) +{ +} + + +void CTriggerChangeTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity *pTarget = UTIL_FindEntityByString( NULL, "targetname", STRING( pev->target ) ); + + if (pTarget) + { + pTarget->pev->target = m_iszNewTarget; + CBaseMonster *pMonster = pTarget->MyMonsterPointer( ); + if (pMonster) + { + pMonster->m_pGoalEnt = NULL; + } + } +} + + + + +#define SF_CAMERA_PLAYER_POSITION 1 +#define SF_CAMERA_PLAYER_TARGET 2 +#define SF_CAMERA_PLAYER_TAKECONTROL 4 + +class CTriggerCamera : public CBaseDelay +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT FollowTarget( void ); + void Move(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + static TYPEDESCRIPTION m_SaveData[]; + + EHANDLE m_hPlayer; + EHANDLE m_hTarget; + CBaseEntity *m_pentPath; + int m_sPath; + float m_flWait; + float m_flReturnTime; + float m_flStopTime; + float m_moveDistance; + float m_targetSpeed; + float m_initialSpeed; + float m_acceleration; + float m_deceleration; + int m_state; + +}; +LINK_ENTITY_TO_CLASS( trigger_camera, CTriggerCamera ); + +// Global Savedata for changelevel friction modifier +TYPEDESCRIPTION CTriggerCamera::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerCamera, m_hPlayer, FIELD_EHANDLE ), + DEFINE_FIELD( CTriggerCamera, m_hTarget, FIELD_EHANDLE ), + DEFINE_FIELD( CTriggerCamera, m_pentPath, FIELD_CLASSPTR ), + DEFINE_FIELD( CTriggerCamera, m_sPath, FIELD_STRING ), + DEFINE_FIELD( CTriggerCamera, m_flWait, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_flReturnTime, FIELD_TIME ), + DEFINE_FIELD( CTriggerCamera, m_flStopTime, FIELD_TIME ), + DEFINE_FIELD( CTriggerCamera, m_moveDistance, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_targetSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_initialSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_acceleration, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_deceleration, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_state, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerCamera,CBaseDelay); + +void CTriggerCamera::Spawn( void ) +{ + pev->movetype = MOVETYPE_NOCLIP; + pev->solid = SOLID_NOT; // Remove model & collisions + pev->renderamt = 0; // The engine won't draw this model if this is set to 0 and blending is on + pev->rendermode = kRenderTransTexture; + + m_initialSpeed = pev->speed; + if ( m_acceleration == 0 ) + m_acceleration = 500; + if ( m_deceleration == 0 ) + m_deceleration = 500; +} + + +void CTriggerCamera :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "moveto")) + { + m_sPath = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "acceleration")) + { + m_acceleration = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "deceleration")) + { + m_deceleration = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + + + +void CTriggerCamera::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_state ) ) + return; + + // Toggle state + m_state = !m_state; + if (m_state == 0) + { + m_flReturnTime = gpGlobals->time; + return; + } + if ( !pActivator || !pActivator->IsPlayer() ) + { + pActivator = CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex( 1 )); + } + + m_hPlayer = pActivator; + + m_flReturnTime = gpGlobals->time + m_flWait; + pev->speed = m_initialSpeed; + m_targetSpeed = m_initialSpeed; + + if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TARGET ) ) + { + m_hTarget = m_hPlayer; + } + else + { + m_hTarget = GetNextTarget(); + } + + // Nothing to look at! + if ( m_hTarget == NULL ) + { + return; + } + + + if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TAKECONTROL ) ) + { + ((CBasePlayer *)pActivator)->EnableControl(FALSE); + } + + if ( m_sPath ) + { + m_pentPath = Instance( FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(m_sPath)) ); + } + else + { + m_pentPath = NULL; + } + + m_flStopTime = gpGlobals->time; + if ( m_pentPath ) + { + if ( m_pentPath->pev->speed != 0 ) + m_targetSpeed = m_pentPath->pev->speed; + + m_flStopTime += m_pentPath->GetDelay(); + } + + // copy over player information + if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_POSITION ) ) + { + UTIL_SetOrigin( pev, pActivator->pev->origin + pActivator->pev->view_ofs ); + pev->angles.x = -pActivator->pev->angles.x; + pev->angles.y = pActivator->pev->angles.y; + pev->angles.z = 0; + pev->velocity = pActivator->pev->velocity; + } + else + { + pev->velocity = Vector( 0, 0, 0 ); + } + + SET_VIEW( pActivator->edict(), edict() ); + + SET_MODEL(ENT(pev), STRING(pActivator->pev->model) ); + + // follow the player down + SetThink( &CTriggerCamera::FollowTarget ); + pev->nextthink = gpGlobals->time; + + m_moveDistance = 0; + Move(); +} + + +void CTriggerCamera::FollowTarget( ) +{ + if (m_hPlayer == NULL) + return; + + if (m_hTarget == NULL || m_flReturnTime < gpGlobals->time) + { + if (m_hPlayer->IsAlive( )) + { + SET_VIEW( m_hPlayer->edict(), m_hPlayer->edict() ); + ((CBasePlayer *)((CBaseEntity *)m_hPlayer))->EnableControl(TRUE); + } + SUB_UseTargets( this, USE_TOGGLE, 0 ); + pev->avelocity = Vector( 0, 0, 0 ); + m_state = 0; + return; + } + + Vector vecGoal = UTIL_VecToAngles( m_hTarget->pev->origin - pev->origin ); + vecGoal.x = -vecGoal.x; + + if (pev->angles.y > 360) + pev->angles.y -= 360; + + if (pev->angles.y < 0) + pev->angles.y += 360; + + float dx = vecGoal.x - pev->angles.x; + float dy = vecGoal.y - pev->angles.y; + + if (dx < -180) + dx += 360; + if (dx > 180) + dx = dx - 360; + + if (dy < -180) + dy += 360; + if (dy > 180) + dy = dy - 360; + + pev->avelocity.x = dx * 40 * gpGlobals->frametime; + pev->avelocity.y = dy * 40 * gpGlobals->frametime; + + + if (!(FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TAKECONTROL))) + { + pev->velocity = pev->velocity * 0.8; + if (pev->velocity.Length( ) < 10.0) + pev->velocity = g_vecZero; + } + + pev->nextthink = gpGlobals->time; + + Move(); +} + +void CTriggerCamera::Move() +{ + // Not moving on a path, return + if (!m_pentPath) + return; + + // Subtract movement from the previous frame + m_moveDistance -= pev->speed * gpGlobals->frametime; + + // Have we moved enough to reach the target? + if ( m_moveDistance <= 0 ) + { + // Fire the passtarget if there is one + if ( m_pentPath->pev->message ) + { + FireTargets( STRING(m_pentPath->pev->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( m_pentPath->pev->spawnflags, SF_CORNER_FIREONCE ) ) + m_pentPath->pev->message = 0; + } + // Time to go to the next target + m_pentPath = m_pentPath->GetNextTarget(); + + // Set up next corner + if ( !m_pentPath ) + { + pev->velocity = g_vecZero; + } + else + { + if ( m_pentPath->pev->speed != 0 ) + m_targetSpeed = m_pentPath->pev->speed; + + Vector delta = m_pentPath->pev->origin - pev->origin; + m_moveDistance = delta.Length(); + pev->movedir = delta.Normalize(); + m_flStopTime = gpGlobals->time + m_pentPath->GetDelay(); + } + } + + if ( m_flStopTime > gpGlobals->time ) + pev->speed = UTIL_Approach( 0, pev->speed, m_deceleration * gpGlobals->frametime ); + else + pev->speed = UTIL_Approach( m_targetSpeed, pev->speed, m_acceleration * gpGlobals->frametime ); + + float fraction = 2 * gpGlobals->frametime; + pev->velocity = ((pev->movedir * pev->speed) * fraction) + (pev->velocity * (1-fraction)); +} diff --git a/dlls/tripmine.cpp b/dlls/tripmine.cpp new file mode 100644 index 0000000..4b558e0 --- /dev/null +++ b/dlls/tripmine.cpp @@ -0,0 +1,526 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "effects.h" +#include "gamerules.h" + +#define TRIPMINE_PRIMARY_VOLUME 450 + + + +enum tripmine_e { + TRIPMINE_IDLE1 = 0, + TRIPMINE_IDLE2, + TRIPMINE_ARM1, + TRIPMINE_ARM2, + TRIPMINE_FIDGET, + TRIPMINE_HOLSTER, + TRIPMINE_DRAW, + TRIPMINE_WORLD, + TRIPMINE_GROUND, +}; + + +#ifndef CLIENT_DLL + +class CTripmineGrenade : public CGrenade +{ + void Spawn( void ); + void Precache( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + void EXPORT WarningThink( void ); + void EXPORT PowerupThink( void ); + void EXPORT BeamBreakThink( void ); + void EXPORT DelayDeathThink( void ); + void Killed( entvars_t *pevAttacker, int iGib ); + + void MakeBeam( void ); + void KillBeam( void ); + + float m_flPowerUp; + Vector m_vecDir; + Vector m_vecEnd; + float m_flBeamLength; + + EHANDLE m_hOwner; + CBeam *m_pBeam; + Vector m_posOwner; + Vector m_angleOwner; + edict_t *m_pRealOwner;// tracelines don't hit PEV->OWNER, which means a player couldn't detonate his own trip mine, so we store the owner here. +}; + +LINK_ENTITY_TO_CLASS( monster_tripmine, CTripmineGrenade ); + +TYPEDESCRIPTION CTripmineGrenade::m_SaveData[] = +{ + DEFINE_FIELD( CTripmineGrenade, m_flPowerUp, FIELD_TIME ), + DEFINE_FIELD( CTripmineGrenade, m_vecDir, FIELD_VECTOR ), + DEFINE_FIELD( CTripmineGrenade, m_vecEnd, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CTripmineGrenade, m_flBeamLength, FIELD_FLOAT ), + DEFINE_FIELD( CTripmineGrenade, m_hOwner, FIELD_EHANDLE ), + DEFINE_FIELD( CTripmineGrenade, m_pBeam, FIELD_CLASSPTR ), + DEFINE_FIELD( CTripmineGrenade, m_posOwner, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CTripmineGrenade, m_angleOwner, FIELD_VECTOR ), + DEFINE_FIELD( CTripmineGrenade, m_pRealOwner, FIELD_EDICT ), +}; + +IMPLEMENT_SAVERESTORE(CTripmineGrenade,CGrenade); + + +void CTripmineGrenade :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_NOT; + + SET_MODEL(ENT(pev), "models/v_tripmine.mdl"); + pev->frame = 0; + pev->body = 3; + pev->sequence = TRIPMINE_WORLD; + ResetSequenceInfo( ); + pev->framerate = 0; + + UTIL_SetSize(pev, Vector( -8, -8, -8), Vector(8, 8, 8)); + UTIL_SetOrigin( pev, pev->origin ); + + if (pev->spawnflags & 1) + { + // power up quickly + m_flPowerUp = gpGlobals->time + 1.0; + } + else + { + // power up in 2.5 seconds + m_flPowerUp = gpGlobals->time + 2.5; + } + + SetThink( &CTripmineGrenade::PowerupThink ); + pev->nextthink = gpGlobals->time + 0.2; + + pev->takedamage = DAMAGE_YES; + pev->dmg = gSkillData.plrDmgTripmine; + pev->health = 1; // don't let die normally + + if (pev->owner != NULL) + { + // play deploy sound + EMIT_SOUND( ENT(pev), CHAN_VOICE, "weapons/mine_deploy.wav", 1.0, ATTN_NORM ); + EMIT_SOUND( ENT(pev), CHAN_BODY, "weapons/mine_charge.wav", 0.2, ATTN_NORM ); // chargeup + + m_pRealOwner = pev->owner;// see CTripmineGrenade for why. + } + + UTIL_MakeAimVectors( pev->angles ); + + m_vecDir = gpGlobals->v_forward; + m_vecEnd = pev->origin + m_vecDir * 2048; +} + + +void CTripmineGrenade :: Precache( void ) +{ + PRECACHE_MODEL("models/v_tripmine.mdl"); + PRECACHE_SOUND("weapons/mine_deploy.wav"); + PRECACHE_SOUND("weapons/mine_activate.wav"); + PRECACHE_SOUND("weapons/mine_charge.wav"); +} + + +void CTripmineGrenade :: WarningThink( void ) +{ + // play warning sound + // EMIT_SOUND( ENT(pev), CHAN_VOICE, "buttons/Blip2.wav", 1.0, ATTN_NORM ); + + // set to power up + SetThink( &CTripmineGrenade::PowerupThink ); + pev->nextthink = gpGlobals->time + 1.0; +} + + +void CTripmineGrenade :: PowerupThink( void ) +{ + TraceResult tr; + + if (m_hOwner == NULL) + { + // find an owner + edict_t *oldowner = pev->owner; + pev->owner = NULL; + UTIL_TraceLine( pev->origin + m_vecDir * 8, pev->origin - m_vecDir * 32, dont_ignore_monsters, ENT( pev ), &tr ); + if (tr.fStartSolid || (oldowner && tr.pHit == oldowner)) + { + pev->owner = oldowner; + m_flPowerUp += 0.1; + pev->nextthink = gpGlobals->time + 0.1; + return; + } + if (tr.flFraction < 1.0) + { + pev->owner = tr.pHit; + m_hOwner = CBaseEntity::Instance( pev->owner ); + m_posOwner = m_hOwner->pev->origin; + m_angleOwner = m_hOwner->pev->angles; + } + else + { + STOP_SOUND( ENT(pev), CHAN_VOICE, "weapons/mine_deploy.wav" ); + STOP_SOUND( ENT(pev), CHAN_BODY, "weapons/mine_charge.wav" ); + SetThink(&CTripmineGrenade::SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; + ALERT( at_console, "WARNING:Tripmine at %.0f, %.0f, %.0f removed\n", pev->origin.x, pev->origin.y, pev->origin.z ); + KillBeam(); + return; + } + } + else if (m_posOwner != m_hOwner->pev->origin || m_angleOwner != m_hOwner->pev->angles) + { + // disable + STOP_SOUND( ENT(pev), CHAN_VOICE, "weapons/mine_deploy.wav" ); + STOP_SOUND( ENT(pev), CHAN_BODY, "weapons/mine_charge.wav" ); + CBaseEntity *pMine = Create( "weapon_tripmine", pev->origin + m_vecDir * 24, pev->angles ); + pMine->pev->spawnflags |= SF_NORESPAWN; + + SetThink( &CTripmineGrenade::SUB_Remove ); + KillBeam(); + pev->nextthink = gpGlobals->time + 0.1; + return; + } + // ALERT( at_console, "%d %.0f %.0f %0.f\n", pev->owner, m_pOwner->pev->origin.x, m_pOwner->pev->origin.y, m_pOwner->pev->origin.z ); + + if (gpGlobals->time > m_flPowerUp) + { + // make solid + pev->solid = SOLID_BBOX; + UTIL_SetOrigin( pev, pev->origin ); + + MakeBeam( ); + + // play enabled sound + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "weapons/mine_activate.wav", 0.5, ATTN_NORM, 1.0, 75 ); + } + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CTripmineGrenade :: KillBeam( void ) +{ + if ( m_pBeam ) + { + UTIL_Remove( m_pBeam ); + m_pBeam = NULL; + } +} + + +void CTripmineGrenade :: MakeBeam( void ) +{ + TraceResult tr; + + // ALERT( at_console, "serverflags %f\n", gpGlobals->serverflags ); + + UTIL_TraceLine( pev->origin, m_vecEnd, dont_ignore_monsters, ENT( pev ), &tr ); + + m_flBeamLength = tr.flFraction; + + // set to follow laser spot + SetThink( &CTripmineGrenade::BeamBreakThink ); + pev->nextthink = gpGlobals->time + 0.1; + + Vector vecTmpEnd = pev->origin + m_vecDir * 2048 * m_flBeamLength; + + m_pBeam = CBeam::BeamCreate( g_pModelNameLaser, 10 ); + m_pBeam->PointEntInit( vecTmpEnd, entindex() ); + m_pBeam->SetColor( 0, 214, 198 ); + m_pBeam->SetScrollRate( 255 ); + m_pBeam->SetBrightness( 64 ); +} + + +void CTripmineGrenade :: BeamBreakThink( void ) +{ + BOOL bBlowup = 0; + + TraceResult tr; + + // HACKHACK Set simple box using this really nice global! + gpGlobals->trace_flags = FTRACE_SIMPLEBOX; + UTIL_TraceLine( pev->origin, m_vecEnd, dont_ignore_monsters, ENT( pev ), &tr ); + + // ALERT( at_console, "%f : %f\n", tr.flFraction, m_flBeamLength ); + + // respawn detect. + if ( !m_pBeam ) + { + MakeBeam( ); + if ( tr.pHit ) + m_hOwner = CBaseEntity::Instance( tr.pHit ); // reset owner too + } + + if (fabs( m_flBeamLength - tr.flFraction ) > 0.001) + { + bBlowup = 1; + } + else + { + if (m_hOwner == NULL) + bBlowup = 1; + else if (m_posOwner != m_hOwner->pev->origin) + bBlowup = 1; + else if (m_angleOwner != m_hOwner->pev->angles) + bBlowup = 1; + } + + if (bBlowup) + { + // a bit of a hack, but all CGrenade code passes pev->owner along to make sure the proper player gets credit for the kill + // so we have to restore pev->owner from pRealOwner, because an entity's tracelines don't strike it's pev->owner which meant + // that a player couldn't trigger his own tripmine. Now that the mine is exploding, it's safe the restore the owner so the + // CGrenade code knows who the explosive really belongs to. + pev->owner = m_pRealOwner; + pev->health = 0; + Killed( VARS( pev->owner ), GIB_NORMAL ); + return; + } + + pev->nextthink = gpGlobals->time + 0.1; +} + +int CTripmineGrenade :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if (gpGlobals->time < m_flPowerUp && flDamage < pev->health) + { + // disable + // Create( "weapon_tripmine", pev->origin + m_vecDir * 24, pev->angles ); + SetThink( &CTripmineGrenade::SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; + KillBeam(); + return FALSE; + } + return CGrenade::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CTripmineGrenade::Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->takedamage = DAMAGE_NO; + + if ( pevAttacker && ( pevAttacker->flags & FL_CLIENT ) ) + { + // some client has destroyed this mine, he'll get credit for any kills + pev->owner = ENT( pevAttacker ); + } + + SetThink( &CTripmineGrenade::DelayDeathThink ); + pev->nextthink = gpGlobals->time + RANDOM_FLOAT( 0.1, 0.3 ); + + EMIT_SOUND( ENT(pev), CHAN_BODY, "common/null.wav", 0.5, ATTN_NORM ); // shut off chargeup +} + + +void CTripmineGrenade::DelayDeathThink( void ) +{ + KillBeam(); + TraceResult tr; + UTIL_TraceLine ( pev->origin + m_vecDir * 8, pev->origin - m_vecDir * 64, dont_ignore_monsters, ENT(pev), & tr); + + Explode( &tr, DMG_BLAST ); +} +#endif + +LINK_ENTITY_TO_CLASS( weapon_tripmine, CTripmine ); + +void CTripmine::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_TRIPMINE; + SET_MODEL(ENT(pev), "models/v_tripmine.mdl"); + pev->frame = 0; + pev->body = 3; + pev->sequence = TRIPMINE_GROUND; + // ResetSequenceInfo( ); + pev->framerate = 0; + + FallInit();// get ready to fall down + + m_iDefaultAmmo = TRIPMINE_DEFAULT_GIVE; + +#ifdef CLIENT_DLL + if ( !bIsMultiplayer() ) +#else + if ( !g_pGameRules->IsDeathmatch() ) +#endif + { + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 28) ); + } +} + +void CTripmine::Precache( void ) +{ + PRECACHE_MODEL ("models/v_tripmine.mdl"); + PRECACHE_MODEL ("models/p_tripmine.mdl"); + UTIL_PrecacheOther( "monster_tripmine" ); + + m_usTripFire = PRECACHE_EVENT( 1, "events/tripfire.sc" ); +} + +int CTripmine::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "Trip Mine"; + p->iMaxAmmo1 = TRIPMINE_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 4; + p->iPosition = 2; + p->iId = m_iId = WEAPON_TRIPMINE; + p->iWeight = TRIPMINE_WEIGHT; + p->iFlags = ITEM_FLAG_LIMITINWORLD | ITEM_FLAG_EXHAUSTIBLE; + + return 1; +} + +BOOL CTripmine::Deploy( ) +{ + //pev->body = 0; + return DefaultDeploy( "models/v_tripmine.mdl", "models/p_tripmine.mdl", TRIPMINE_DRAW, "trip" ); +} + + +void CTripmine::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + + if (!m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + // out of mines + m_pPlayer->pev->weapons &= ~(1<nextthink = gpGlobals->time + 0.1; + } + + SendWeaponAnim( TRIPMINE_HOLSTER ); + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM); +} + +void CTripmine::PrimaryAttack( void ) +{ + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + return; + + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = gpGlobals->v_forward; + + TraceResult tr; + + UTIL_TraceLine( vecSrc, vecSrc + vecAiming * 128, dont_ignore_monsters, ENT( m_pPlayer->pev ), &tr ); + + int flags; +#ifdef CLIENT_WEAPONS + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usTripFire, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); + + if (tr.flFraction < 1.0) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + if ( pEntity && !(pEntity->pev->flags & FL_CONVEYOR) ) + { + Vector angles = UTIL_VecToAngles( tr.vecPlaneNormal ); + + CBaseEntity *pEnt = CBaseEntity::Create( "monster_tripmine", tr.vecEndPos + tr.vecPlaneNormal * 8, angles, m_pPlayer->edict() ); + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0 ) + { + // no more mines! + RetireWeapon(); + return; + } + } + else + { + // ALERT( at_console, "no deploy\n" ); + } + } + else + { + + } + + m_flNextPrimaryAttack = GetNextAttackDelay(0.3); + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); +} + +void CTripmine::WeaponIdle( void ) +{ + if ( m_flTimeWeaponIdle > UTIL_WeaponTimeBase() ) + return; + + if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] > 0 ) + { + SendWeaponAnim( TRIPMINE_DRAW ); + } + else + { + RetireWeapon(); + return; + } + + int iAnim; + float flRand = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0, 1 ); + if (flRand <= 0.25) + { + iAnim = TRIPMINE_IDLE1; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 90.0 / 30.0; + } + else if (flRand <= 0.75) + { + iAnim = TRIPMINE_IDLE2; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 60.0 / 30.0; + } + else + { + iAnim = TRIPMINE_FIDGET; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 100.0 / 30.0; + } + + SendWeaponAnim( iAnim ); +} + + + + diff --git a/dlls/turret.cpp b/dlls/turret.cpp new file mode 100644 index 0000000..45afa75 --- /dev/null +++ b/dlls/turret.cpp @@ -0,0 +1,1305 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +/* + +===== turret.cpp ======================================================== + +*/ + +// +// TODO: +// Take advantage of new monster fields like m_hEnemy and get rid of that OFFSET() stuff +// Revisit enemy validation stuff, maybe it's not necessary with the newest monster code +// + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "effects.h" + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); + +#define TURRET_SHOTS 2 +#define TURRET_RANGE (100 * 12) +#define TURRET_SPREAD Vector( 0, 0, 0 ) +#define TURRET_TURNRATE 30 //angles per 0.1 second +#define TURRET_MAXWAIT 15 // seconds turret will stay active w/o a target +#define TURRET_MAXSPIN 5 // seconds turret barrel will spin w/o a target +#define TURRET_MACHINE_VOLUME 0.5 + +typedef enum +{ + TURRET_ANIM_NONE = 0, + TURRET_ANIM_FIRE, + TURRET_ANIM_SPIN, + TURRET_ANIM_DEPLOY, + TURRET_ANIM_RETIRE, + TURRET_ANIM_DIE, +} TURRET_ANIM; + +class CBaseTurret : public CBaseMonster +{ +public: + void Spawn(void); + virtual void Precache(void); + void KeyValue( KeyValueData *pkvd ); + void EXPORT TurretUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + virtual int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + virtual int Classify(void); + + int BloodColor( void ) { return DONT_BLEED; } + void GibMonster( void ) {} // UNDONE: Throw turret gibs? + + // Think functions + + void EXPORT ActiveThink(void); + void EXPORT SearchThink(void); + void EXPORT AutoSearchThink(void); + void EXPORT TurretDeath(void); + + virtual void EXPORT SpinDownCall(void) { m_iSpin = 0; } + virtual void EXPORT SpinUpCall(void) { m_iSpin = 1; } + + // void SpinDown(void); + // float EXPORT SpinDownCall( void ) { return SpinDown(); } + + // virtual float SpinDown(void) { return 0;} + // virtual float Retire(void) { return 0;} + + void EXPORT Deploy(void); + void EXPORT Retire(void); + + void EXPORT Initialize(void); + + virtual void Ping(void); + virtual void EyeOn(void); + virtual void EyeOff(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + // other functions + void SetTurretAnim(TURRET_ANIM anim); + int MoveTurret(void); + virtual void Shoot(Vector &vecSrc, Vector &vecDirToEnemy) { }; + + float m_flMaxSpin; // Max time to spin the barrel w/o a target + int m_iSpin; + + CSprite *m_pEyeGlow; + int m_eyeBrightness; + + int m_iDeployHeight; + int m_iRetractHeight; + int m_iMinPitch; + + int m_iBaseTurnRate; // angles per second + float m_fTurnRate; // actual turn rate + int m_iOrientation; // 0 = floor, 1 = Ceiling + int m_iOn; + int m_fBeserk; // Sometimes this bitch will just freak out + int m_iAutoStart; // true if the turret auto deploys when a target + // enters its range + + Vector m_vecLastSight; + float m_flLastSight; // Last time we saw a target + float m_flMaxWait; // Max time to seach w/o a target + int m_iSearchSpeed; // Not Used! + + // movement + float m_flStartYaw; + Vector m_vecCurAngles; + Vector m_vecGoalAngles; + + + float m_flPingTime; // Time until the next ping, used when searching + float m_flSpinUpTime; // Amount of time until the barrel should spin down when searching +}; + + +TYPEDESCRIPTION CBaseTurret::m_SaveData[] = +{ + DEFINE_FIELD( CBaseTurret, m_flMaxSpin, FIELD_FLOAT ), + DEFINE_FIELD( CBaseTurret, m_iSpin, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseTurret, m_pEyeGlow, FIELD_CLASSPTR ), + DEFINE_FIELD( CBaseTurret, m_eyeBrightness, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iDeployHeight, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iRetractHeight, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iMinPitch, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseTurret, m_iBaseTurnRate, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_fTurnRate, FIELD_FLOAT ), + DEFINE_FIELD( CBaseTurret, m_iOrientation, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iOn, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_fBeserk, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iAutoStart, FIELD_INTEGER ), + + + DEFINE_FIELD( CBaseTurret, m_vecLastSight, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseTurret, m_flLastSight, FIELD_TIME ), + DEFINE_FIELD( CBaseTurret, m_flMaxWait, FIELD_FLOAT ), + DEFINE_FIELD( CBaseTurret, m_iSearchSpeed, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseTurret, m_flStartYaw, FIELD_FLOAT ), + DEFINE_FIELD( CBaseTurret, m_vecCurAngles, FIELD_VECTOR ), + DEFINE_FIELD( CBaseTurret, m_vecGoalAngles, FIELD_VECTOR ), + + DEFINE_FIELD( CBaseTurret, m_flPingTime, FIELD_TIME ), + DEFINE_FIELD( CBaseTurret, m_flSpinUpTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CBaseTurret, CBaseMonster ); + +class CTurret : public CBaseTurret +{ +public: + void Spawn(void); + void Precache(void); + // Think functions + void SpinUpCall(void); + void SpinDownCall(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + // other functions + void Shoot(Vector &vecSrc, Vector &vecDirToEnemy); + +private: + int m_iStartSpin; + +}; +TYPEDESCRIPTION CTurret::m_SaveData[] = +{ + DEFINE_FIELD( CTurret, m_iStartSpin, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CTurret, CBaseTurret ); + + +class CMiniTurret : public CBaseTurret +{ +public: + void Spawn( ); + void Precache(void); + // other functions + void Shoot(Vector &vecSrc, Vector &vecDirToEnemy); +}; + + +LINK_ENTITY_TO_CLASS( monster_turret, CTurret ); +LINK_ENTITY_TO_CLASS( monster_miniturret, CMiniTurret ); + +void CBaseTurret::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "maxsleep")) + { + m_flMaxWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "orientation")) + { + m_iOrientation = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + } + else if (FStrEq(pkvd->szKeyName, "searchspeed")) + { + m_iSearchSpeed = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + } + else if (FStrEq(pkvd->szKeyName, "turnrate")) + { + m_iBaseTurnRate = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + pkvd->fHandled = TRUE; + else + CBaseMonster::KeyValue( pkvd ); +} + + +void CBaseTurret::Spawn() +{ + Precache( ); + pev->nextthink = gpGlobals->time + 1; + pev->movetype = MOVETYPE_FLY; + pev->sequence = 0; + pev->frame = 0; + pev->solid = SOLID_SLIDEBOX; + pev->takedamage = DAMAGE_AIM; + + SetBits (pev->flags, FL_MONSTER); + SetUse( &CBaseTurret::TurretUse ); + + if (( pev->spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE ) + && !( pev->spawnflags & SF_MONSTER_TURRET_STARTINACTIVE )) + { + m_iAutoStart = TRUE; + } + + ResetSequenceInfo( ); + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + m_flFieldOfView = VIEW_FIELD_FULL; + // m_flSightRange = TURRET_RANGE; +} + + +void CBaseTurret::Precache( ) +{ + PRECACHE_SOUND ("turret/tu_fire1.wav"); + PRECACHE_SOUND ("turret/tu_ping.wav"); + PRECACHE_SOUND ("turret/tu_active2.wav"); + PRECACHE_SOUND ("turret/tu_die.wav"); + PRECACHE_SOUND ("turret/tu_die2.wav"); + PRECACHE_SOUND ("turret/tu_die3.wav"); + // PRECACHE_SOUND ("turret/tu_retract.wav"); // just use deploy sound to save memory + PRECACHE_SOUND ("turret/tu_deploy.wav"); + PRECACHE_SOUND ("turret/tu_spinup.wav"); + PRECACHE_SOUND ("turret/tu_spindown.wav"); + PRECACHE_SOUND ("turret/tu_search.wav"); + PRECACHE_SOUND ("turret/tu_alert.wav"); +} + +#define TURRET_GLOW_SPRITE "sprites/flare3.spr" + +void CTurret::Spawn() +{ + Precache( ); + SET_MODEL(ENT(pev), "models/turret.mdl"); + pev->health = gSkillData.turretHealth; + m_HackedGunPos = Vector( 0, 0, 12.75 ); + m_flMaxSpin = TURRET_MAXSPIN; + pev->view_ofs.z = 12.75; + + CBaseTurret::Spawn( ); + + m_iRetractHeight = 16; + m_iDeployHeight = 32; + m_iMinPitch = -15; + UTIL_SetSize(pev, Vector(-32, -32, -m_iRetractHeight), Vector(32, 32, m_iRetractHeight)); + + SetThink(&CTurret::Initialize); + + m_pEyeGlow = CSprite::SpriteCreate( TURRET_GLOW_SPRITE, pev->origin, FALSE ); + m_pEyeGlow->SetTransparency( kRenderGlow, 255, 0, 0, 0, kRenderFxNoDissipation ); + m_pEyeGlow->SetAttachment( edict(), 2 ); + m_eyeBrightness = 0; + + pev->nextthink = gpGlobals->time + 0.3; +} + +void CTurret::Precache() +{ + CBaseTurret::Precache( ); + PRECACHE_MODEL ("models/turret.mdl"); + PRECACHE_MODEL (TURRET_GLOW_SPRITE); +} + +void CMiniTurret::Spawn() +{ + Precache( ); + SET_MODEL(ENT(pev), "models/miniturret.mdl"); + pev->health = gSkillData.miniturretHealth; + m_HackedGunPos = Vector( 0, 0, 12.75 ); + m_flMaxSpin = 0; + pev->view_ofs.z = 12.75; + + CBaseTurret::Spawn( ); + m_iRetractHeight = 16; + m_iDeployHeight = 32; + m_iMinPitch = -15; + UTIL_SetSize(pev, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight)); + + SetThink(&CMiniTurret::Initialize); + pev->nextthink = gpGlobals->time + 0.3; +} + + +void CMiniTurret::Precache() +{ + CBaseTurret::Precache( ); + PRECACHE_MODEL ("models/miniturret.mdl"); + PRECACHE_SOUND("weapons/hks1.wav"); + PRECACHE_SOUND("weapons/hks2.wav"); + PRECACHE_SOUND("weapons/hks3.wav"); +} + +void CBaseTurret::Initialize(void) +{ + m_iOn = 0; + m_fBeserk = 0; + m_iSpin = 0; + + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + + if (m_iBaseTurnRate == 0) m_iBaseTurnRate = TURRET_TURNRATE; + if (m_flMaxWait == 0) m_flMaxWait = TURRET_MAXWAIT; + m_flStartYaw = pev->angles.y; + if (m_iOrientation == 1) + { + pev->idealpitch = 180; + pev->angles.x = 180; + pev->view_ofs.z = -pev->view_ofs.z; + pev->effects |= EF_INVLIGHT; + pev->angles.y = pev->angles.y + 180; + if (pev->angles.y > 360) + pev->angles.y = pev->angles.y - 360; + } + + m_vecGoalAngles.x = 0; + + if (m_iAutoStart) + { + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(&CBaseTurret::AutoSearchThink); + pev->nextthink = gpGlobals->time + .1; + } + else + SetThink(&CBaseTurret::SUB_DoNothing); +} + +void CBaseTurret::TurretUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_iOn ) ) + return; + + if (m_iOn) + { + m_hEnemy = NULL; + pev->nextthink = gpGlobals->time + 0.1; + m_iAutoStart = FALSE;// switching off a turret disables autostart + //!!!! this should spin down first!!BUGBUG + SetThink(&CBaseTurret::Retire); + } + else + { + pev->nextthink = gpGlobals->time + 0.1; // turn on delay + + // if the turret is flagged as an autoactivate turret, re-enable it's ability open self. + if ( pev->spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE ) + { + m_iAutoStart = TRUE; + } + + SetThink(&CBaseTurret::Deploy); + } +} + + +void CBaseTurret::Ping( void ) +{ + // make the pinging noise every second while searching + if (m_flPingTime == 0) + m_flPingTime = gpGlobals->time + 1; + else if (m_flPingTime <= gpGlobals->time) + { + m_flPingTime = gpGlobals->time + 1; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "turret/tu_ping.wav", 1, ATTN_NORM); + EyeOn( ); + } + else if (m_eyeBrightness > 0) + { + EyeOff( ); + } +} + + +void CBaseTurret::EyeOn( ) +{ + if (m_pEyeGlow) + { + if (m_eyeBrightness != 255) + { + m_eyeBrightness = 255; + } + m_pEyeGlow->SetBrightness( m_eyeBrightness ); + } +} + + +void CBaseTurret::EyeOff( ) +{ + if (m_pEyeGlow) + { + if (m_eyeBrightness > 0) + { + m_eyeBrightness = max( 0, m_eyeBrightness - 30 ); + m_pEyeGlow->SetBrightness( m_eyeBrightness ); + } + } +} + + +void CBaseTurret::ActiveThink(void) +{ + int fAttack = 0; + Vector vecDirToEnemy; + + pev->nextthink = gpGlobals->time + 0.1; + StudioFrameAdvance( ); + + if ((!m_iOn) || (m_hEnemy == NULL)) + { + m_hEnemy = NULL; + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(&CBaseTurret::SearchThink); + return; + } + + // if it's dead, look for something new + if ( !m_hEnemy->IsAlive() ) + { + if (!m_flLastSight) + { + m_flLastSight = gpGlobals->time + 0.5; // continue-shooting timeout + } + else + { + if (gpGlobals->time > m_flLastSight) + { + m_hEnemy = NULL; + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(&CBaseTurret::SearchThink); + return; + } + } + } + + Vector vecMid = pev->origin + pev->view_ofs; + Vector vecMidEnemy = m_hEnemy->BodyTarget( vecMid ); + + // Look for our current enemy + int fEnemyVisible = FBoxVisible(pev, m_hEnemy->pev, vecMidEnemy ); + + vecDirToEnemy = vecMidEnemy - vecMid; // calculate dir and dist to enemy + float flDistToEnemy = vecDirToEnemy.Length(); + + Vector vec = UTIL_VecToAngles(vecMidEnemy - vecMid); + + // Current enmey is not visible. + if (!fEnemyVisible || (flDistToEnemy > TURRET_RANGE)) + { + if (!m_flLastSight) + m_flLastSight = gpGlobals->time + 0.5; + else + { + // Should we look for a new target? + if (gpGlobals->time > m_flLastSight) + { + m_hEnemy = NULL; + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(&CBaseTurret::SearchThink); + return; + } + } + fEnemyVisible = 0; + } + else + { + m_vecLastSight = vecMidEnemy; + } + + UTIL_MakeAimVectors(m_vecCurAngles); + + /* + ALERT( at_console, "%.0f %.0f : %.2f %.2f %.2f\n", + m_vecCurAngles.x, m_vecCurAngles.y, + gpGlobals->v_forward.x, gpGlobals->v_forward.y, gpGlobals->v_forward.z ); + */ + + Vector vecLOS = vecDirToEnemy; //vecMid - m_vecLastSight; + vecLOS = vecLOS.Normalize(); + + // Is the Gun looking at the target + if (DotProduct(vecLOS, gpGlobals->v_forward) <= 0.866) // 30 degree slop + fAttack = FALSE; + else + fAttack = TRUE; + + // fire the gun + if (m_iSpin && ((fAttack) || (m_fBeserk))) + { + Vector vecSrc, vecAng; + GetAttachment( 0, vecSrc, vecAng ); + SetTurretAnim(TURRET_ANIM_FIRE); + Shoot(vecSrc, gpGlobals->v_forward ); + } + else + { + SetTurretAnim(TURRET_ANIM_SPIN); + } + + //move the gun + if (m_fBeserk) + { + if (RANDOM_LONG(0,9) == 0) + { + m_vecGoalAngles.y = RANDOM_FLOAT(0,360); + m_vecGoalAngles.x = RANDOM_FLOAT(0,90) - 90 * m_iOrientation; + TakeDamage(pev,pev,1, DMG_GENERIC); // don't beserk forever + return; + } + } + else if (fEnemyVisible) + { + if (vec.y > 360) + vec.y -= 360; + + if (vec.y < 0) + vec.y += 360; + + //ALERT(at_console, "[%.2f]", vec.x); + + if (vec.x < -180) + vec.x += 360; + + if (vec.x > 180) + vec.x -= 360; + + // now all numbers should be in [1...360] + // pin to turret limitations to [-90...15] + + if (m_iOrientation == 0) + { + if (vec.x > 90) + vec.x = 90; + else if (vec.x < m_iMinPitch) + vec.x = m_iMinPitch; + } + else + { + if (vec.x < -90) + vec.x = -90; + else if (vec.x > -m_iMinPitch) + vec.x = -m_iMinPitch; + } + + // ALERT(at_console, "->[%.2f]\n", vec.x); + + m_vecGoalAngles.y = vec.y; + m_vecGoalAngles.x = vec.x; + + } + + SpinUpCall(); + MoveTurret(); +} + + +void CTurret::Shoot(Vector &vecSrc, Vector &vecDirToEnemy) +{ + FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, BULLET_MONSTER_12MM, 1 ); + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "turret/tu_fire1.wav", 1, 0.6); + pev->effects = pev->effects | EF_MUZZLEFLASH; +} + + +void CMiniTurret::Shoot(Vector &vecSrc, Vector &vecDirToEnemy) +{ + FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, BULLET_MONSTER_9MM, 1 ); + + switch(RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks3.wav", 1, ATTN_NORM); break; + } + pev->effects = pev->effects | EF_MUZZLEFLASH; +} + + +void CBaseTurret::Deploy(void) +{ + pev->nextthink = gpGlobals->time + 0.1; + StudioFrameAdvance( ); + + if (pev->sequence != TURRET_ANIM_DEPLOY) + { + m_iOn = 1; + SetTurretAnim(TURRET_ANIM_DEPLOY); + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_deploy.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + SUB_UseTargets( this, USE_ON, 0 ); + } + + if (m_fSequenceFinished) + { + pev->maxs.z = m_iDeployHeight; + pev->mins.z = -m_iDeployHeight; + UTIL_SetSize(pev, pev->mins, pev->maxs); + + m_vecCurAngles.x = 0; + + if (m_iOrientation == 1) + { + m_vecCurAngles.y = UTIL_AngleMod( pev->angles.y + 180 ); + } + else + { + m_vecCurAngles.y = UTIL_AngleMod( pev->angles.y ); + } + + SetTurretAnim(TURRET_ANIM_SPIN); + pev->framerate = 0; + SetThink(&CBaseTurret::SearchThink); + } + + m_flLastSight = gpGlobals->time + m_flMaxWait; +} + +void CBaseTurret::Retire(void) +{ + // make the turret level + m_vecGoalAngles.x = 0; + m_vecGoalAngles.y = m_flStartYaw; + + pev->nextthink = gpGlobals->time + 0.1; + + StudioFrameAdvance( ); + + EyeOff( ); + + if (!MoveTurret()) + { + if (m_iSpin) + { + SpinDownCall(); + } + else if (pev->sequence != TURRET_ANIM_RETIRE) + { + SetTurretAnim(TURRET_ANIM_RETIRE); + EMIT_SOUND_DYN(ENT(pev), CHAN_BODY, "turret/tu_deploy.wav", TURRET_MACHINE_VOLUME, ATTN_NORM, 0, 120); + SUB_UseTargets( this, USE_OFF, 0 ); + } + else if (m_fSequenceFinished) + { + m_iOn = 0; + m_flLastSight = 0; + SetTurretAnim(TURRET_ANIM_NONE); + pev->maxs.z = m_iRetractHeight; + pev->mins.z = -m_iRetractHeight; + UTIL_SetSize(pev, pev->mins, pev->maxs); + if (m_iAutoStart) + { + SetThink(&CBaseTurret::AutoSearchThink); + pev->nextthink = gpGlobals->time + .1; + } + else + SetThink(&CBaseTurret::SUB_DoNothing); + } + } + else + { + SetTurretAnim(TURRET_ANIM_SPIN); + } +} + + +void CTurret::SpinUpCall(void) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + // Are we already spun up? If not start the two stage process. + if (!m_iSpin) + { + SetTurretAnim( TURRET_ANIM_SPIN ); + // for the first pass, spin up the the barrel + if (!m_iStartSpin) + { + pev->nextthink = gpGlobals->time + 1.0; // spinup delay + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_spinup.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + m_iStartSpin = 1; + pev->framerate = 0.1; + } + // after the barrel is spun up, turn on the hum + else if (pev->framerate >= 1.0) + { + pev->nextthink = gpGlobals->time + 0.1; // retarget delay + EMIT_SOUND(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + SetThink(&CTurret::ActiveThink); + m_iStartSpin = 0; + m_iSpin = 1; + } + else + { + pev->framerate += 0.075; + } + } + + if (m_iSpin) + { + SetThink(&CTurret::ActiveThink); + } +} + + +void CTurret::SpinDownCall(void) +{ + if (m_iSpin) + { + SetTurretAnim( TURRET_ANIM_SPIN ); + if (pev->framerate == 1.0) + { + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", 0, 0, SND_STOP, 100); + EMIT_SOUND(ENT(pev), CHAN_ITEM, "turret/tu_spindown.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + } + pev->framerate -= 0.02; + if (pev->framerate <= 0) + { + pev->framerate = 0; + m_iSpin = 0; + } + } +} + + +void CBaseTurret::SetTurretAnim(TURRET_ANIM anim) +{ + if (pev->sequence != anim) + { + switch(anim) + { + case TURRET_ANIM_FIRE: + case TURRET_ANIM_SPIN: + if (pev->sequence != TURRET_ANIM_FIRE && pev->sequence != TURRET_ANIM_SPIN) + { + pev->frame = 0; + } + break; + default: + pev->frame = 0; + break; + } + + pev->sequence = anim; + ResetSequenceInfo( ); + + switch(anim) + { + case TURRET_ANIM_RETIRE: + pev->frame = 255; + pev->framerate = -1.0; + break; + case TURRET_ANIM_DIE: + pev->framerate = 1.0; + break; + } + //ALERT(at_console, "Turret anim #%d\n", anim); + } +} + + +// +// This search function will sit with the turret deployed and look for a new target. +// After a set amount of time, the barrel will spin down. After m_flMaxWait, the turret will +// retact. +// +void CBaseTurret::SearchThink(void) +{ + // ensure rethink + SetTurretAnim(TURRET_ANIM_SPIN); + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + if (m_flSpinUpTime == 0 && m_flMaxSpin) + m_flSpinUpTime = gpGlobals->time + m_flMaxSpin; + + Ping( ); + + // If we have a target and we're still healthy + if (m_hEnemy != NULL) + { + if (!m_hEnemy->IsAlive() ) + m_hEnemy = NULL;// Dead enemy forces a search for new one + } + + + // Acquire Target + if (m_hEnemy == NULL) + { + Look(TURRET_RANGE); + m_hEnemy = BestVisibleEnemy(); + } + + // If we've found a target, spin up the barrel and start to attack + if (m_hEnemy != NULL) + { + m_flLastSight = 0; + m_flSpinUpTime = 0; + SetThink(&CBaseTurret::ActiveThink); + } + else + { + // Are we out of time, do we need to retract? + if (gpGlobals->time > m_flLastSight) + { + //Before we retrace, make sure that we are spun down. + m_flLastSight = 0; + m_flSpinUpTime = 0; + SetThink(&CBaseTurret::Retire); + } + // should we stop the spin? + else if ((m_flSpinUpTime) && (gpGlobals->time > m_flSpinUpTime)) + { + SpinDownCall(); + } + + // generic hunt for new victims + m_vecGoalAngles.y = (m_vecGoalAngles.y + 0.1 * m_fTurnRate); + if (m_vecGoalAngles.y >= 360) + m_vecGoalAngles.y -= 360; + MoveTurret(); + } +} + + +// +// This think function will deploy the turret when something comes into range. This is for +// automatically activated turrets. +// +void CBaseTurret::AutoSearchThink(void) +{ + // ensure rethink + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.3; + + // If we have a target and we're still healthy + + if (m_hEnemy != NULL) + { + if (!m_hEnemy->IsAlive() ) + m_hEnemy = NULL;// Dead enemy forces a search for new one + } + + // Acquire Target + + if (m_hEnemy == NULL) + { + Look( TURRET_RANGE ); + m_hEnemy = BestVisibleEnemy(); + } + + if (m_hEnemy != NULL) + { + SetThink(&CBaseTurret::Deploy); + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_alert.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + } +} + + +void CBaseTurret :: TurretDeath( void ) +{ + BOOL iActive = FALSE; + + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + if (pev->deadflag != DEAD_DEAD) + { + pev->deadflag = DEAD_DEAD; + + float flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.33 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die.wav", 1.0, ATTN_NORM); + else if ( flRndSound <= 0.66 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die2.wav", 1.0, ATTN_NORM); + else + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die3.wav", 1.0, ATTN_NORM); + + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", 0, 0, SND_STOP, 100); + + if (m_iOrientation == 0) + m_vecGoalAngles.x = -15; + else + m_vecGoalAngles.x = -90; + + SetTurretAnim(TURRET_ANIM_DIE); + + EyeOn( ); + } + + EyeOff( ); + + if (pev->dmgtime + RANDOM_FLOAT( 0, 2 ) > gpGlobals->time) + { + // lots of smoke + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( RANDOM_FLOAT( pev->absmin.x, pev->absmax.x ) ); + WRITE_COORD( RANDOM_FLOAT( pev->absmin.y, pev->absmax.y ) ); + WRITE_COORD( pev->origin.z - m_iOrientation * 64 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 25 ); // scale * 10 + WRITE_BYTE( 10 - m_iOrientation * 5); // framerate + MESSAGE_END(); + } + + if (pev->dmgtime + RANDOM_FLOAT( 0, 5 ) > gpGlobals->time) + { + Vector vecSrc = Vector( RANDOM_FLOAT( pev->absmin.x, pev->absmax.x ), RANDOM_FLOAT( pev->absmin.y, pev->absmax.y ), 0 ); + if (m_iOrientation == 0) + vecSrc = vecSrc + Vector( 0, 0, RANDOM_FLOAT( pev->origin.z, pev->absmax.z ) ); + else + vecSrc = vecSrc + Vector( 0, 0, RANDOM_FLOAT( pev->absmin.z, pev->origin.z ) ); + + UTIL_Sparks( vecSrc ); + } + + if (m_fSequenceFinished && !MoveTurret( ) && pev->dmgtime + 5 < gpGlobals->time) + { + pev->framerate = 0; + SetThink( NULL ); + } +} + + + +void CBaseTurret :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( ptr->iHitgroup == 10 ) + { + // hit armor + if ( pev->dmgtime != gpGlobals->time || (RANDOM_LONG(0,10) < 1) ) + { + UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT( 1, 2) ); + pev->dmgtime = gpGlobals->time; + } + + flDamage = 0.1;// don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated + } + + if ( !pev->takedamage ) + return; + + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); +} + +// take damage. bitsDamageType indicates type of damage sustained, ie: DMG_BULLET + +int CBaseTurret::TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) +{ + if ( !pev->takedamage ) + return 0; + + if (!m_iOn) + flDamage /= 10.0; + + pev->health -= flDamage; + if (pev->health <= 0) + { + pev->health = 0; + pev->takedamage = DAMAGE_NO; + pev->dmgtime = gpGlobals->time; + + ClearBits (pev->flags, FL_MONSTER); // why are they set in the first place??? + + SetUse(NULL); + SetThink(&CBaseTurret::TurretDeath); + SUB_UseTargets( this, USE_ON, 0 ); // wake up others + pev->nextthink = gpGlobals->time + 0.1; + + return 0; + } + + if (pev->health <= 10) + { + if (m_iOn && (1 || RANDOM_LONG(0, 0x7FFF) > 800)) + { + m_fBeserk = 1; + SetThink(&CBaseTurret::SearchThink); + } + } + + return 1; +} + +int CBaseTurret::MoveTurret(void) +{ + int state = 0; + // any x movement? + + if (m_vecCurAngles.x != m_vecGoalAngles.x) + { + float flDir = m_vecGoalAngles.x > m_vecCurAngles.x ? 1 : -1 ; + + m_vecCurAngles.x += 0.1 * m_fTurnRate * flDir; + + // if we started below the goal, and now we're past, peg to goal + if (flDir == 1) + { + if (m_vecCurAngles.x > m_vecGoalAngles.x) + m_vecCurAngles.x = m_vecGoalAngles.x; + } + else + { + if (m_vecCurAngles.x < m_vecGoalAngles.x) + m_vecCurAngles.x = m_vecGoalAngles.x; + } + + if (m_iOrientation == 0) + SetBoneController(1, -m_vecCurAngles.x); + else + SetBoneController(1, m_vecCurAngles.x); + state = 1; + } + + if (m_vecCurAngles.y != m_vecGoalAngles.y) + { + float flDir = m_vecGoalAngles.y > m_vecCurAngles.y ? 1 : -1 ; + float flDist = fabs(m_vecGoalAngles.y - m_vecCurAngles.y); + + if (flDist > 180) + { + flDist = 360 - flDist; + flDir = -flDir; + } + if (flDist > 30) + { + if (m_fTurnRate < m_iBaseTurnRate * 10) + { + m_fTurnRate += m_iBaseTurnRate; + } + } + else if (m_fTurnRate > 45) + { + m_fTurnRate -= m_iBaseTurnRate; + } + else + { + m_fTurnRate += m_iBaseTurnRate; + } + + m_vecCurAngles.y += 0.1 * m_fTurnRate * flDir; + + if (m_vecCurAngles.y < 0) + m_vecCurAngles.y += 360; + else if (m_vecCurAngles.y >= 360) + m_vecCurAngles.y -= 360; + + if (flDist < (0.05 * m_iBaseTurnRate)) + m_vecCurAngles.y = m_vecGoalAngles.y; + + //ALERT(at_console, "%.2f -> %.2f\n", m_vecCurAngles.y, y); + if (m_iOrientation == 0) + SetBoneController(0, m_vecCurAngles.y - pev->angles.y ); + else + SetBoneController(0, pev->angles.y - 180 - m_vecCurAngles.y ); + state = 1; + } + + if (!state) + m_fTurnRate = m_iBaseTurnRate; + + //ALERT(at_console, "(%.2f, %.2f)->(%.2f, %.2f)\n", m_vecCurAngles.x, + // m_vecCurAngles.y, m_vecGoalAngles.x, m_vecGoalAngles.y); + return state; +} + +// +// ID as a machine +// +int CBaseTurret::Classify ( void ) +{ + if (m_iOn || m_iAutoStart) + return CLASS_MACHINE; + return CLASS_NONE; +} + + + + +//========================================================= +// Sentry gun - smallest turret, placed near grunt entrenchments +//========================================================= +class CSentry : public CBaseTurret +{ +public: + void Spawn( ); + void Precache(void); + // other functions + void Shoot(Vector &vecSrc, Vector &vecDirToEnemy); + int TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType); + void EXPORT SentryTouch( CBaseEntity *pOther ); + void EXPORT SentryDeath( void ); + +}; + +LINK_ENTITY_TO_CLASS( monster_sentry, CSentry ); + +void CSentry::Precache() +{ + CBaseTurret::Precache( ); + PRECACHE_MODEL ("models/sentry.mdl"); +} + +void CSentry::Spawn() +{ + Precache( ); + SET_MODEL(ENT(pev), "models/sentry.mdl"); + pev->health = gSkillData.sentryHealth; + m_HackedGunPos = Vector( 0, 0, 48 ); + pev->view_ofs.z = 48; + m_flMaxWait = 1E6; + m_flMaxSpin = 1E6; + + CBaseTurret::Spawn(); + m_iRetractHeight = 64; + m_iDeployHeight = 64; + m_iMinPitch = -60; + UTIL_SetSize(pev, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight)); + + SetTouch(&CSentry::SentryTouch); + SetThink(&CSentry::Initialize); + pev->nextthink = gpGlobals->time + 0.3; +} + +void CSentry::Shoot(Vector &vecSrc, Vector &vecDirToEnemy) +{ + FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, BULLET_MONSTER_MP5, 1 ); + + switch(RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks3.wav", 1, ATTN_NORM); break; + } + pev->effects = pev->effects | EF_MUZZLEFLASH; +} + +int CSentry::TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) +{ + if ( !pev->takedamage ) + return 0; + + if (!m_iOn) + { + SetThink( &CSentry::Deploy ); + SetUse( NULL ); + pev->nextthink = gpGlobals->time + 0.1; + } + + pev->health -= flDamage; + if (pev->health <= 0) + { + pev->health = 0; + pev->takedamage = DAMAGE_NO; + pev->dmgtime = gpGlobals->time; + + ClearBits (pev->flags, FL_MONSTER); // why are they set in the first place??? + + SetUse(NULL); + SetThink( &CSentry::SentryDeath); + SUB_UseTargets( this, USE_ON, 0 ); // wake up others + pev->nextthink = gpGlobals->time + 0.1; + + return 0; + } + + return 1; +} + + +void CSentry::SentryTouch( CBaseEntity *pOther ) +{ + if ( pOther && (pOther->IsPlayer() || (pOther->pev->flags & FL_MONSTER)) ) + { + TakeDamage(pOther->pev, pOther->pev, 0, 0 ); + } +} + + +void CSentry :: SentryDeath( void ) +{ + BOOL iActive = FALSE; + + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + if (pev->deadflag != DEAD_DEAD) + { + pev->deadflag = DEAD_DEAD; + + float flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.33 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die.wav", 1.0, ATTN_NORM); + else if ( flRndSound <= 0.66 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die2.wav", 1.0, ATTN_NORM); + else + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die3.wav", 1.0, ATTN_NORM); + + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", 0, 0, SND_STOP, 100); + + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + + SetTurretAnim(TURRET_ANIM_DIE); + + pev->solid = SOLID_NOT; + pev->angles.y = UTIL_AngleMod( pev->angles.y + RANDOM_LONG( 0, 2 ) * 120 ); + + EyeOn( ); + } + + EyeOff( ); + + Vector vecSrc, vecAng; + GetAttachment( 1, vecSrc, vecAng ); + + if (pev->dmgtime + RANDOM_FLOAT( 0, 2 ) > gpGlobals->time) + { + // lots of smoke + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSrc.x + RANDOM_FLOAT( -16, 16 ) ); + WRITE_COORD( vecSrc.y + RANDOM_FLOAT( -16, 16 ) ); + WRITE_COORD( vecSrc.z - 32 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 15 ); // scale * 10 + WRITE_BYTE( 8 ); // framerate + MESSAGE_END(); + } + + if (pev->dmgtime + RANDOM_FLOAT( 0, 8 ) > gpGlobals->time) + { + UTIL_Sparks( vecSrc ); + } + + if (m_fSequenceFinished && pev->dmgtime + 5 < gpGlobals->time) + { + pev->framerate = 0; + SetThink( NULL ); + } +} + diff --git a/dlls/util.cpp b/dlls/util.cpp new file mode 100644 index 0000000..cee80d4 --- /dev/null +++ b/dlls/util.cpp @@ -0,0 +1,2549 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== util.cpp ======================================================== + + Utility code. Really not optional after all. + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include +#include "shake.h" +#include "decals.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" + +float UTIL_WeaponTimeBase( void ) +{ +#if defined( CLIENT_WEAPONS ) + return 0.0; +#else + return gpGlobals->time; +#endif +} + +static unsigned int glSeed = 0; + +unsigned int seed_table[ 256 ] = +{ + 28985, 27138, 26457, 9451, 17764, 10909, 28790, 8716, 6361, 4853, 17798, 21977, 19643, 20662, 10834, 20103, + 27067, 28634, 18623, 25849, 8576, 26234, 23887, 18228, 32587, 4836, 3306, 1811, 3035, 24559, 18399, 315, + 26766, 907, 24102, 12370, 9674, 2972, 10472, 16492, 22683, 11529, 27968, 30406, 13213, 2319, 23620, 16823, + 10013, 23772, 21567, 1251, 19579, 20313, 18241, 30130, 8402, 20807, 27354, 7169, 21211, 17293, 5410, 19223, + 10255, 22480, 27388, 9946, 15628, 24389, 17308, 2370, 9530, 31683, 25927, 23567, 11694, 26397, 32602, 15031, + 18255, 17582, 1422, 28835, 23607, 12597, 20602, 10138, 5212, 1252, 10074, 23166, 19823, 31667, 5902, 24630, + 18948, 14330, 14950, 8939, 23540, 21311, 22428, 22391, 3583, 29004, 30498, 18714, 4278, 2437, 22430, 3439, + 28313, 23161, 25396, 13471, 19324, 15287, 2563, 18901, 13103, 16867, 9714, 14322, 15197, 26889, 19372, 26241, + 31925, 14640, 11497, 8941, 10056, 6451, 28656, 10737, 13874, 17356, 8281, 25937, 1661, 4850, 7448, 12744, + 21826, 5477, 10167, 16705, 26897, 8839, 30947, 27978, 27283, 24685, 32298, 3525, 12398, 28726, 9475, 10208, + 617, 13467, 22287, 2376, 6097, 26312, 2974, 9114, 21787, 28010, 4725, 15387, 3274, 10762, 31695, 17320, + 18324, 12441, 16801, 27376, 22464, 7500, 5666, 18144, 15314, 31914, 31627, 6495, 5226, 31203, 2331, 4668, + 12650, 18275, 351, 7268, 31319, 30119, 7600, 2905, 13826, 11343, 13053, 15583, 30055, 31093, 5067, 761, + 9685, 11070, 21369, 27155, 3663, 26542, 20169, 12161, 15411, 30401, 7580, 31784, 8985, 29367, 20989, 14203, + 29694, 21167, 10337, 1706, 28578, 887, 3373, 19477, 14382, 675, 7033, 15111, 26138, 12252, 30996, 21409, + 25678, 18555, 13256, 23316, 22407, 16727, 991, 9236, 5373, 29402, 6117, 15241, 27715, 19291, 19888, 19847 +}; + +unsigned int U_Random( void ) +{ + glSeed *= 69069; + glSeed += seed_table[ glSeed & 0xff ]; + + return ( ++glSeed & 0x0fffffff ); +} + +void U_Srand( unsigned int seed ) +{ + glSeed = seed_table[ seed & 0xff ]; +} + +/* +===================== +UTIL_SharedRandomLong +===================== +*/ +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ) +{ + unsigned int range; + + U_Srand( (int)seed + low + high ); + + range = high - low + 1; + if ( !(range - 1) ) + { + return low; + } + else + { + int offset; + int rnum; + + rnum = U_Random(); + + offset = rnum % range; + + return (low + offset); + } +} + +/* +===================== +UTIL_SharedRandomFloat +===================== +*/ +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ) +{ + // + unsigned int range; + + U_Srand( (int)seed + *(int *)&low + *(int *)&high ); + + U_Random(); + U_Random(); + + range = high - low; + if ( !range ) + { + return low; + } + else + { + int tensixrand; + float offset; + + tensixrand = U_Random() & 65535; + + offset = (float)tensixrand / 65536.0; + + return (low + offset * range ); + } +} + +void UTIL_ParametricRocket( entvars_t *pev, Vector vecOrigin, Vector vecAngles, edict_t *owner ) +{ + pev->startpos = vecOrigin; + // Trace out line to end pos + TraceResult tr; + UTIL_MakeVectors( vecAngles ); + UTIL_TraceLine( pev->startpos, pev->startpos + gpGlobals->v_forward * 8192, ignore_monsters, owner, &tr); + pev->endpos = tr.vecEndPos; + + // Now compute how long it will take based on current velocity + Vector vecTravel = pev->endpos - pev->startpos; + float travelTime = 0.0; + if ( pev->velocity.Length() > 0 ) + { + travelTime = vecTravel.Length() / pev->velocity.Length(); + } + pev->starttime = gpGlobals->time; + pev->impacttime = gpGlobals->time + travelTime; +} + +int g_groupmask = 0; +int g_groupop = 0; + +// Normal overrides +void UTIL_SetGroupTrace( int groupmask, int op ) +{ + g_groupmask = groupmask; + g_groupop = op; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + +void UTIL_UnsetGroupTrace( void ) +{ + g_groupmask = 0; + g_groupop = 0; + + ENGINE_SETGROUPMASK( 0, 0 ); +} + +// Smart version, it'll clean itself up when it pops off stack +UTIL_GroupTrace::UTIL_GroupTrace( int groupmask, int op ) +{ + m_oldgroupmask = g_groupmask; + m_oldgroupop = g_groupop; + + g_groupmask = groupmask; + g_groupop = op; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + +UTIL_GroupTrace::~UTIL_GroupTrace( void ) +{ + g_groupmask = m_oldgroupmask; + g_groupop = m_oldgroupop; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + +TYPEDESCRIPTION gEntvarsDescription[] = +{ + DEFINE_ENTITY_FIELD( classname, FIELD_STRING ), + DEFINE_ENTITY_GLOBAL_FIELD( globalname, FIELD_STRING ), + + DEFINE_ENTITY_FIELD( origin, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_FIELD( oldorigin, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_FIELD( velocity, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( basevelocity, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( movedir, FIELD_VECTOR ), + + DEFINE_ENTITY_FIELD( angles, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( avelocity, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( punchangle, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( v_angle, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( fixangle, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( idealpitch, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( pitch_speed, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( ideal_yaw, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( yaw_speed, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( modelindex, FIELD_INTEGER ), + DEFINE_ENTITY_GLOBAL_FIELD( model, FIELD_MODELNAME ), + + DEFINE_ENTITY_FIELD( viewmodel, FIELD_MODELNAME ), + DEFINE_ENTITY_FIELD( weaponmodel, FIELD_MODELNAME ), + + DEFINE_ENTITY_FIELD( absmin, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_FIELD( absmax, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_GLOBAL_FIELD( mins, FIELD_VECTOR ), + DEFINE_ENTITY_GLOBAL_FIELD( maxs, FIELD_VECTOR ), + DEFINE_ENTITY_GLOBAL_FIELD( size, FIELD_VECTOR ), + + DEFINE_ENTITY_FIELD( ltime, FIELD_TIME ), + DEFINE_ENTITY_FIELD( nextthink, FIELD_TIME ), + + DEFINE_ENTITY_FIELD( solid, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( movetype, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( skin, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( body, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( effects, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( gravity, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( friction, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( light_level, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( frame, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( scale, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( sequence, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( animtime, FIELD_TIME ), + DEFINE_ENTITY_FIELD( framerate, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( controller, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( blending, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( rendermode, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( renderamt, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( rendercolor, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( renderfx, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( health, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( frags, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( weapons, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( takedamage, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( deadflag, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( view_ofs, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( button, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( impulse, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( chain, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( dmg_inflictor, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( enemy, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( aiment, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( owner, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( groundentity, FIELD_EDICT ), + + DEFINE_ENTITY_FIELD( spawnflags, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( flags, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( colormap, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( team, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( max_health, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( teleport_time, FIELD_TIME ), + DEFINE_ENTITY_FIELD( armortype, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( armorvalue, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( waterlevel, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( watertype, FIELD_INTEGER ), + + // Having these fields be local to the individual levels makes it easier to test those levels individually. + DEFINE_ENTITY_GLOBAL_FIELD( target, FIELD_STRING ), + DEFINE_ENTITY_GLOBAL_FIELD( targetname, FIELD_STRING ), + DEFINE_ENTITY_FIELD( netname, FIELD_STRING ), + DEFINE_ENTITY_FIELD( message, FIELD_STRING ), + + DEFINE_ENTITY_FIELD( dmg_take, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( dmg_save, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( dmg, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( dmgtime, FIELD_TIME ), + + DEFINE_ENTITY_FIELD( noise, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise1, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise2, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise3, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( speed, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( air_finished, FIELD_TIME ), + DEFINE_ENTITY_FIELD( pain_finished, FIELD_TIME ), + DEFINE_ENTITY_FIELD( radsuit_finished, FIELD_TIME ), +}; + +#define ENTVARS_COUNT (sizeof(gEntvarsDescription)/sizeof(gEntvarsDescription[0])) + + +#ifdef DEBUG +edict_t *DBG_EntOfVars( const entvars_t *pev ) +{ + if (pev->pContainingEntity != NULL) + return pev->pContainingEntity; + ALERT(at_console, "entvars_t pContainingEntity is NULL, calling into engine"); + edict_t* pent = (*g_engfuncs.pfnFindEntityByVars)((entvars_t*)pev); + if (pent == NULL) + ALERT(at_console, "DAMN! Even the engine couldn't FindEntityByVars!"); + ((entvars_t *)pev)->pContainingEntity = pent; + return pent; +} +#endif //DEBUG + + +#ifdef DEBUG + void +DBG_AssertFunction( + BOOL fExpr, + const char* szExpr, + const char* szFile, + int szLine, + const char* szMessage) + { + if (fExpr) + return; + char szOut[512]; + if (szMessage != NULL) + sprintf(szOut, "ASSERT FAILED:\n %s \n(%s@%d)\n%s", szExpr, szFile, szLine, szMessage); + else + sprintf(szOut, "ASSERT FAILED:\n %s \n(%s@%d)", szExpr, szFile, szLine); + ALERT(at_console, szOut); + } +#endif // DEBUG + +BOOL UTIL_GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) +{ + return g_pGameRules->GetNextBestWeapon( pPlayer, pCurrentWeapon ); +} + +// ripped this out of the engine +float UTIL_AngleMod(float a) +{ + if (a < 0) + { + a = a + 360 * ((int)(a / 360) + 1); + } + else if (a >= 360) + { + a = a - 360 * ((int)(a / 360)); + } + // a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + +float UTIL_AngleDiff( float destAngle, float srcAngle ) +{ + float delta; + + delta = destAngle - srcAngle; + if ( destAngle > srcAngle ) + { + if ( delta >= 180 ) + delta -= 360; + } + else + { + if ( delta <= -180 ) + delta += 360; + } + return delta; +} + +Vector UTIL_VecToAngles( const Vector &vec ) +{ + float rgflVecOut[3]; + VEC_TO_ANGLES(vec, rgflVecOut); + return Vector(rgflVecOut); +} + +// float UTIL_MoveToOrigin( edict_t *pent, const Vector vecGoal, float flDist, int iMoveType ) +void UTIL_MoveToOrigin( edict_t *pent, const Vector &vecGoal, float flDist, int iMoveType ) +{ + float rgfl[3]; + vecGoal.CopyToArray(rgfl); +// return MOVE_TO_ORIGIN ( pent, rgfl, flDist, iMoveType ); + MOVE_TO_ORIGIN ( pent, rgfl, flDist, iMoveType ); +} + + +int UTIL_EntitiesInBox( CBaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + int count; + + count = 0; + + if ( !pEdict ) + return count; + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + if ( pEdict->free ) // Not in use + continue; + + if ( flagMask && !(pEdict->v.flags & flagMask) ) // Does it meet the criteria? + continue; + + if ( mins.x > pEdict->v.absmax.x || + mins.y > pEdict->v.absmax.y || + mins.z > pEdict->v.absmax.z || + maxs.x < pEdict->v.absmin.x || + maxs.y < pEdict->v.absmin.y || + maxs.z < pEdict->v.absmin.z ) + continue; + + pEntity = CBaseEntity::Instance(pEdict); + if ( !pEntity ) + continue; + + pList[ count ] = pEntity; + count++; + + if ( count >= listMax ) + return count; + } + + return count; +} + + +int UTIL_MonstersInSphere( CBaseEntity **pList, int listMax, const Vector ¢er, float radius ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + int count; + float distance, delta; + + count = 0; + float radiusSquared = radius * radius; + + if ( !pEdict ) + return count; + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + if ( pEdict->free ) // Not in use + continue; + + if ( !(pEdict->v.flags & (FL_CLIENT|FL_MONSTER)) ) // Not a client/monster ? + continue; + + // Use origin for X & Y since they are centered for all monsters + // Now X + delta = center.x - pEdict->v.origin.x;//(pEdict->v.absmin.x + pEdict->v.absmax.x)*0.5; + delta *= delta; + + if ( delta > radiusSquared ) + continue; + distance = delta; + + // Now Y + delta = center.y - pEdict->v.origin.y;//(pEdict->v.absmin.y + pEdict->v.absmax.y)*0.5; + delta *= delta; + + distance += delta; + if ( distance > radiusSquared ) + continue; + + // Now Z + delta = center.z - (pEdict->v.absmin.z + pEdict->v.absmax.z)*0.5; + delta *= delta; + + distance += delta; + if ( distance > radiusSquared ) + continue; + + pEntity = CBaseEntity::Instance(pEdict); + if ( !pEntity ) + continue; + + pList[ count ] = pEntity; + count++; + + if ( count >= listMax ) + return count; + } + + + return count; +} + + +CBaseEntity *UTIL_FindEntityInSphere( CBaseEntity *pStartEntity, const Vector &vecCenter, float flRadius ) +{ + edict_t *pentEntity; + + if (pStartEntity) + pentEntity = pStartEntity->edict(); + else + pentEntity = NULL; + + pentEntity = FIND_ENTITY_IN_SPHERE( pentEntity, vecCenter, flRadius); + + if (!FNullEnt(pentEntity)) + return CBaseEntity::Instance(pentEntity); + return NULL; +} + + +CBaseEntity *UTIL_FindEntityByString( CBaseEntity *pStartEntity, const char *szKeyword, const char *szValue ) +{ + edict_t *pentEntity; + + if (pStartEntity) + pentEntity = pStartEntity->edict(); + else + pentEntity = NULL; + + pentEntity = FIND_ENTITY_BY_STRING( pentEntity, szKeyword, szValue ); + + if (!FNullEnt(pentEntity)) + return CBaseEntity::Instance(pentEntity); + return NULL; +} + +CBaseEntity *UTIL_FindEntityByClassname( CBaseEntity *pStartEntity, const char *szName ) +{ + return UTIL_FindEntityByString( pStartEntity, "classname", szName ); +} + +CBaseEntity *UTIL_FindEntityByTargetname( CBaseEntity *pStartEntity, const char *szName ) +{ + return UTIL_FindEntityByString( pStartEntity, "targetname", szName ); +} + + +CBaseEntity *UTIL_FindEntityGeneric( const char *szWhatever, Vector &vecSrc, float flRadius ) +{ + CBaseEntity *pEntity = NULL; + + pEntity = UTIL_FindEntityByTargetname( NULL, szWhatever ); + if (pEntity) + return pEntity; + + CBaseEntity *pSearch = NULL; + float flMaxDist2 = flRadius * flRadius; + while ((pSearch = UTIL_FindEntityByClassname( pSearch, szWhatever )) != NULL) + { + float flDist2 = (pSearch->pev->origin - vecSrc).Length(); + flDist2 = flDist2 * flDist2; + if (flMaxDist2 > flDist2) + { + pEntity = pSearch; + flMaxDist2 = flDist2; + } + } + return pEntity; +} + + +// returns a CBaseEntity pointer to a player by index. Only returns if the player is spawned and connected +// otherwise returns NULL +// Index is 1 based +CBaseEntity *UTIL_PlayerByIndex( int playerIndex ) +{ + CBaseEntity *pPlayer = NULL; + + if ( playerIndex > 0 && playerIndex <= gpGlobals->maxClients ) + { + edict_t *pPlayerEdict = INDEXENT( playerIndex ); + if ( pPlayerEdict && !pPlayerEdict->free ) + { + pPlayer = CBaseEntity::Instance( pPlayerEdict ); + } + } + + return pPlayer; +} + + +void UTIL_MakeVectors( const Vector &vecAngles ) +{ + MAKE_VECTORS( vecAngles ); +} + + +void UTIL_MakeAimVectors( const Vector &vecAngles ) +{ + float rgflVec[3]; + vecAngles.CopyToArray(rgflVec); + rgflVec[0] = -rgflVec[0]; + MAKE_VECTORS(rgflVec); +} + + +#define SWAP(a,b,temp) ((temp)=(a),(a)=(b),(b)=(temp)) + +void UTIL_MakeInvVectors( const Vector &vec, globalvars_t *pgv ) +{ + MAKE_VECTORS(vec); + + float tmp; + pgv->v_right = pgv->v_right * -1; + + SWAP(pgv->v_forward.y, pgv->v_right.x, tmp); + SWAP(pgv->v_forward.z, pgv->v_up.x, tmp); + SWAP(pgv->v_right.z, pgv->v_up.y, tmp); +} + + +void UTIL_EmitAmbientSound( edict_t *entity, const Vector &vecOrigin, const char *samp, float vol, float attenuation, int fFlags, int pitch ) +{ + float rgfl[3]; + vecOrigin.CopyToArray(rgfl); + + if (samp && *samp == '!') + { + char name[32]; + if (SENTENCEG_Lookup(samp, name) >= 0) + EMIT_AMBIENT_SOUND(entity, rgfl, name, vol, attenuation, fFlags, pitch); + } + else + EMIT_AMBIENT_SOUND(entity, rgfl, samp, vol, attenuation, fFlags, pitch); +} + +static unsigned short FixedUnsigned16( float value, float scale ) +{ + int output; + + output = value * scale; + if ( output < 0 ) + output = 0; + if ( output > 0xFFFF ) + output = 0xFFFF; + + return (unsigned short)output; +} + +static short FixedSigned16( float value, float scale ) +{ + int output; + + output = value * scale; + + if ( output > 32767 ) + output = 32767; + + if ( output < -32768 ) + output = -32768; + + return (short)output; +} + +// Shake the screen of all clients within radius +// radius == 0, shake all clients +// UNDONE: Allow caller to shake clients not ONGROUND? +// UNDONE: Fix falloff model (disabled)? +// UNDONE: Affect user controls? +void UTIL_ScreenShake( const Vector ¢er, float amplitude, float frequency, float duration, float radius ) +{ + int i; + float localAmplitude; + ScreenShake shake; + + shake.duration = FixedUnsigned16( duration, 1<<12 ); // 4.12 fixed + shake.frequency = FixedUnsigned16( frequency, 1<<8 ); // 8.8 fixed + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + if ( !pPlayer || !(pPlayer->pev->flags & FL_ONGROUND) ) // Don't shake if not onground + continue; + + localAmplitude = 0; + + if ( radius <= 0 ) + localAmplitude = amplitude; + else + { + Vector delta = center - pPlayer->pev->origin; + float distance = delta.Length(); + + // Had to get rid of this falloff - it didn't work well + if ( distance < radius ) + localAmplitude = amplitude;//radius - distance; + } + if ( localAmplitude ) + { + shake.amplitude = FixedUnsigned16( localAmplitude, 1<<12 ); // 4.12 fixed + + MESSAGE_BEGIN( MSG_ONE, gmsgShake, NULL, pPlayer->edict() ); // use the magic #1 for "one client" + + WRITE_SHORT( shake.amplitude ); // shake amount + WRITE_SHORT( shake.duration ); // shake lasts this long + WRITE_SHORT( shake.frequency ); // shake noise frequency + + MESSAGE_END(); + } + } +} + + + +void UTIL_ScreenShakeAll( const Vector ¢er, float amplitude, float frequency, float duration ) +{ + UTIL_ScreenShake( center, amplitude, frequency, duration, 0 ); +} + + +void UTIL_ScreenFadeBuild( ScreenFade &fade, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + fade.duration = FixedUnsigned16( fadeTime, 1<<12 ); // 4.12 fixed + fade.holdTime = FixedUnsigned16( fadeHold, 1<<12 ); // 4.12 fixed + fade.r = (int)color.x; + fade.g = (int)color.y; + fade.b = (int)color.z; + fade.a = alpha; + fade.fadeFlags = flags; +} + + +void UTIL_ScreenFadeWrite( const ScreenFade &fade, CBaseEntity *pEntity ) +{ + if ( !pEntity || !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsgFade, NULL, pEntity->edict() ); // use the magic #1 for "one client" + + WRITE_SHORT( fade.duration ); // fade lasts this long + WRITE_SHORT( fade.holdTime ); // fade lasts this long + WRITE_SHORT( fade.fadeFlags ); // fade type (in / out) + WRITE_BYTE( fade.r ); // fade red + WRITE_BYTE( fade.g ); // fade green + WRITE_BYTE( fade.b ); // fade blue + WRITE_BYTE( fade.a ); // fade blue + + MESSAGE_END(); +} + + +void UTIL_ScreenFadeAll( const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + int i; + ScreenFade fade; + + + UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, alpha, flags ); + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + UTIL_ScreenFadeWrite( fade, pPlayer ); + } +} + + +void UTIL_ScreenFade( CBaseEntity *pEntity, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + ScreenFade fade; + + UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, alpha, flags ); + UTIL_ScreenFadeWrite( fade, pEntity ); +} + + +void UTIL_HudMessage( CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage ) +{ + if ( !pEntity || !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, SVC_TEMPENTITY, NULL, pEntity->edict() ); + WRITE_BYTE( TE_TEXTMESSAGE ); + WRITE_BYTE( textparms.channel & 0xFF ); + + WRITE_SHORT( FixedSigned16( textparms.x, 1<<13 ) ); + WRITE_SHORT( FixedSigned16( textparms.y, 1<<13 ) ); + WRITE_BYTE( textparms.effect ); + + WRITE_BYTE( textparms.r1 ); + WRITE_BYTE( textparms.g1 ); + WRITE_BYTE( textparms.b1 ); + WRITE_BYTE( textparms.a1 ); + + WRITE_BYTE( textparms.r2 ); + WRITE_BYTE( textparms.g2 ); + WRITE_BYTE( textparms.b2 ); + WRITE_BYTE( textparms.a2 ); + + WRITE_SHORT( FixedUnsigned16( textparms.fadeinTime, 1<<8 ) ); + WRITE_SHORT( FixedUnsigned16( textparms.fadeoutTime, 1<<8 ) ); + WRITE_SHORT( FixedUnsigned16( textparms.holdTime, 1<<8 ) ); + + if ( textparms.effect == 2 ) + WRITE_SHORT( FixedUnsigned16( textparms.fxTime, 1<<8 ) ); + + if ( strlen( pMessage ) < 512 ) + { + WRITE_STRING( pMessage ); + } + else + { + char tmp[512]; + strncpy( tmp, pMessage, 511 ); + tmp[511] = 0; + WRITE_STRING( tmp ); + } + MESSAGE_END(); +} + +void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage ) +{ + int i; + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + UTIL_HudMessage( pPlayer, textparms, pMessage ); + } +} + + +extern int gmsgTextMsg, gmsgSayText; +void UTIL_ClientPrintAll( int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) +{ + MESSAGE_BEGIN( MSG_ALL, gmsgTextMsg ); + WRITE_BYTE( msg_dest ); + WRITE_STRING( msg_name ); + + if ( param1 ) + WRITE_STRING( param1 ); + if ( param2 ) + WRITE_STRING( param2 ); + if ( param3 ) + WRITE_STRING( param3 ); + if ( param4 ) + WRITE_STRING( param4 ); + + MESSAGE_END(); +} + +void ClientPrint( entvars_t *client, int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgTextMsg, NULL, client ); + WRITE_BYTE( msg_dest ); + WRITE_STRING( msg_name ); + + if ( param1 ) + WRITE_STRING( param1 ); + if ( param2 ) + WRITE_STRING( param2 ); + if ( param3 ) + WRITE_STRING( param3 ); + if ( param4 ) + WRITE_STRING( param4 ); + + MESSAGE_END(); +} + +void UTIL_SayText( const char *pText, CBaseEntity *pEntity ) +{ + if ( !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, pEntity->edict() ); + WRITE_BYTE( pEntity->entindex() ); + WRITE_STRING( pText ); + MESSAGE_END(); +} + +void UTIL_SayTextAll( const char *pText, CBaseEntity *pEntity ) +{ + MESSAGE_BEGIN( MSG_ALL, gmsgSayText, NULL ); + WRITE_BYTE( pEntity->entindex() ); + WRITE_STRING( pText ); + MESSAGE_END(); +} + + +char *UTIL_dtos1( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos2( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos3( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos4( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +void UTIL_ShowMessage( const char *pString, CBaseEntity *pEntity ) +{ + if ( !pEntity || !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsgHudText, NULL, pEntity->edict() ); + WRITE_STRING( pString ); + MESSAGE_END(); +} + + +void UTIL_ShowMessageAll( const char *pString ) +{ + int i; + + // loop through all players + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + UTIL_ShowMessage( pString, pPlayer ); + } +} + +// Overloaded to add IGNORE_GLASS +void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, IGNORE_GLASS ignoreGlass, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_LINE( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE) | (ignoreGlass?0x100:0), pentIgnore, ptr ); +} + + +void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_LINE( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE), pentIgnore, ptr ); +} + + +void UTIL_TraceHull( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, int hullNumber, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_HULL( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE), hullNumber, pentIgnore, ptr ); +} + +void UTIL_TraceModel( const Vector &vecStart, const Vector &vecEnd, int hullNumber, edict_t *pentModel, TraceResult *ptr ) +{ + g_engfuncs.pfnTraceModel( vecStart, vecEnd, hullNumber, pentModel, ptr ); +} + + +TraceResult UTIL_GetGlobalTrace( ) +{ + TraceResult tr; + + tr.fAllSolid = gpGlobals->trace_allsolid; + tr.fStartSolid = gpGlobals->trace_startsolid; + tr.fInOpen = gpGlobals->trace_inopen; + tr.fInWater = gpGlobals->trace_inwater; + tr.flFraction = gpGlobals->trace_fraction; + tr.flPlaneDist = gpGlobals->trace_plane_dist; + tr.pHit = gpGlobals->trace_ent; + tr.vecEndPos = gpGlobals->trace_endpos; + tr.vecPlaneNormal = gpGlobals->trace_plane_normal; + tr.iHitgroup = gpGlobals->trace_hitgroup; + return tr; +} + + +void UTIL_SetSize( entvars_t *pev, const Vector &vecMin, const Vector &vecMax ) +{ + SET_SIZE( ENT(pev), vecMin, vecMax ); +} + + +float UTIL_VecToYaw( const Vector &vec ) +{ + return VEC_TO_YAW(vec); +} + + +void UTIL_SetOrigin( entvars_t *pev, const Vector &vecOrigin ) +{ + edict_t *ent = ENT(pev); + if ( ent ) + SET_ORIGIN( ent, vecOrigin ); +} + +void UTIL_ParticleEffect( const Vector &vecOrigin, const Vector &vecDirection, ULONG ulColor, ULONG ulCount ) +{ + PARTICLE_EFFECT( vecOrigin, vecDirection, (float)ulColor, (float)ulCount ); +} + + +float UTIL_Approach( float target, float value, float speed ) +{ + float delta = target - value; + + if ( delta > speed ) + value += speed; + else if ( delta < -speed ) + value -= speed; + else + value = target; + + return value; +} + + +float UTIL_ApproachAngle( float target, float value, float speed ) +{ + target = UTIL_AngleMod( target ); + value = UTIL_AngleMod( target ); + + float delta = target - value; + + // Speed is assumed to be positive + if ( speed < 0 ) + speed = -speed; + + if ( delta < -180 ) + delta += 360; + else if ( delta > 180 ) + delta -= 360; + + if ( delta > speed ) + value += speed; + else if ( delta < -speed ) + value -= speed; + else + value = target; + + return value; +} + + +float UTIL_AngleDistance( float next, float cur ) +{ + float delta = next - cur; + + if ( delta < -180 ) + delta += 360; + else if ( delta > 180 ) + delta -= 360; + + return delta; +} + + +float UTIL_SplineFraction( float value, float scale ) +{ + value = scale * value; + float valueSquared = value * value; + + // Nice little ease-in, ease-out spline-like curve + return 3 * valueSquared - 2 * valueSquared * value; +} + + +char* UTIL_VarArgs( char *format, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start (argptr, format); + vsprintf (string, format,argptr); + va_end (argptr); + + return string; +} + +Vector UTIL_GetAimVector( edict_t *pent, float flSpeed ) +{ + Vector tmp; + GET_AIM_VECTOR(pent, flSpeed, tmp); + return tmp; +} + +int UTIL_IsMasterTriggered(string_t sMaster, CBaseEntity *pActivator) +{ + if (sMaster) + { + edict_t *pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(sMaster)); + + if ( !FNullEnt(pentTarget) ) + { + CBaseEntity *pMaster = CBaseEntity::Instance(pentTarget); + if ( pMaster && (pMaster->ObjectCaps() & FCAP_MASTER) ) + return pMaster->IsTriggered( pActivator ); + } + + ALERT(at_console, "Master was null or not a master!\n"); + } + + // if this isn't a master entity, just say yes. + return 1; +} + +BOOL UTIL_ShouldShowBlood( int color ) +{ + if ( color != DONT_BLEED ) + { + if ( color == BLOOD_COLOR_RED ) + { + if ( CVAR_GET_FLOAT("violence_hblood") != 0 ) + return TRUE; + } + else + { + if ( CVAR_GET_FLOAT("violence_ablood") != 0 ) + return TRUE; + } + } + return FALSE; +} + +int UTIL_PointContents( const Vector &vec ) +{ + return POINT_CONTENTS(vec); +} + +void UTIL_BloodStream( const Vector &origin, const Vector &direction, int color, int amount ) +{ + if ( !UTIL_ShouldShowBlood( color ) ) + return; + + if ( g_Language == LANGUAGE_GERMAN && color == BLOOD_COLOR_RED ) + color = 0; + + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, origin ); + WRITE_BYTE( TE_BLOODSTREAM ); + WRITE_COORD( origin.x ); + WRITE_COORD( origin.y ); + WRITE_COORD( origin.z ); + WRITE_COORD( direction.x ); + WRITE_COORD( direction.y ); + WRITE_COORD( direction.z ); + WRITE_BYTE( color ); + WRITE_BYTE( min( amount, 255 ) ); + MESSAGE_END(); +} + +void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, int amount ) +{ + if ( !UTIL_ShouldShowBlood( color ) ) + return; + + if ( color == DONT_BLEED || amount == 0 ) + return; + + if ( g_Language == LANGUAGE_GERMAN && color == BLOOD_COLOR_RED ) + color = 0; + + if ( g_pGameRules->IsMultiplayer() ) + { + // scale up blood effect in multiplayer for better visibility + amount *= 2; + } + + if ( amount > 255 ) + amount = 255; + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, origin ); + WRITE_BYTE( TE_BLOODSPRITE ); + WRITE_COORD( origin.x); // pos + WRITE_COORD( origin.y); + WRITE_COORD( origin.z); + WRITE_SHORT( g_sModelIndexBloodSpray ); // initial sprite model + WRITE_SHORT( g_sModelIndexBloodDrop ); // droplet sprite models + WRITE_BYTE( color ); // color index into host_basepal + WRITE_BYTE( min( max( 3, amount / 10 ), 16 ) ); // size + MESSAGE_END(); +} + +Vector UTIL_RandomBloodVector( void ) +{ + Vector direction; + + direction.x = RANDOM_FLOAT ( -1, 1 ); + direction.y = RANDOM_FLOAT ( -1, 1 ); + direction.z = RANDOM_FLOAT ( 0, 1 ); + + return direction; +} + + +void UTIL_BloodDecalTrace( TraceResult *pTrace, int bloodColor ) +{ + if ( UTIL_ShouldShowBlood( bloodColor ) ) + { + if ( bloodColor == BLOOD_COLOR_RED ) + UTIL_DecalTrace( pTrace, DECAL_BLOOD1 + RANDOM_LONG(0,5) ); + else + UTIL_DecalTrace( pTrace, DECAL_YBLOOD1 + RANDOM_LONG(0,5) ); + } +} + + +void UTIL_DecalTrace( TraceResult *pTrace, int decalNumber ) +{ + short entityIndex; + int index; + int message; + + if ( decalNumber < 0 ) + return; + + index = gDecals[ decalNumber ].index; + + if ( index < 0 ) + return; + + if (pTrace->flFraction == 1.0) + return; + + // Only decal BSP models + if ( pTrace->pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( pTrace->pHit ); + if ( pEntity && !pEntity->IsBSPModel() ) + return; + entityIndex = ENTINDEX( pTrace->pHit ); + } + else + entityIndex = 0; + + message = TE_DECAL; + if ( entityIndex != 0 ) + { + if ( index > 255 ) + { + message = TE_DECALHIGH; + index -= 256; + } + } + else + { + message = TE_WORLDDECAL; + if ( index > 255 ) + { + message = TE_WORLDDECALHIGH; + index -= 256; + } + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( message ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_BYTE( index ); + if ( entityIndex ) + WRITE_SHORT( entityIndex ); + MESSAGE_END(); +} + +/* +============== +UTIL_PlayerDecalTrace + +A player is trying to apply his custom decal for the spray can. +Tell connected clients to display it, or use the default spray can decal +if the custom can't be loaded. +============== +*/ +void UTIL_PlayerDecalTrace( TraceResult *pTrace, int playernum, int decalNumber, BOOL bIsCustom ) +{ + int index; + + if (!bIsCustom) + { + if ( decalNumber < 0 ) + return; + + index = gDecals[ decalNumber ].index; + if ( index < 0 ) + return; + } + else + index = decalNumber; + + if (pTrace->flFraction == 1.0) + return; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_PLAYERDECAL ); + WRITE_BYTE ( playernum ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_SHORT( (short)ENTINDEX(pTrace->pHit) ); + WRITE_BYTE( index ); + MESSAGE_END(); +} + +void UTIL_GunshotDecalTrace( TraceResult *pTrace, int decalNumber ) +{ + if ( decalNumber < 0 ) + return; + + int index = gDecals[ decalNumber ].index; + if ( index < 0 ) + return; + + if (pTrace->flFraction == 1.0) + return; + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pTrace->vecEndPos ); + WRITE_BYTE( TE_GUNSHOTDECAL ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_SHORT( (short)ENTINDEX(pTrace->pHit) ); + WRITE_BYTE( index ); + MESSAGE_END(); +} + + +void UTIL_Sparks( const Vector &position ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, position ); + WRITE_BYTE( TE_SPARKS ); + WRITE_COORD( position.x ); + WRITE_COORD( position.y ); + WRITE_COORD( position.z ); + MESSAGE_END(); +} + + +void UTIL_Ricochet( const Vector &position, float scale ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, position ); + WRITE_BYTE( TE_ARMOR_RICOCHET ); + WRITE_COORD( position.x ); + WRITE_COORD( position.y ); + WRITE_COORD( position.z ); + WRITE_BYTE( (int)(scale*10) ); + MESSAGE_END(); +} + + +BOOL UTIL_TeamsMatch( const char *pTeamName1, const char *pTeamName2 ) +{ + // Everyone matches unless it's teamplay + if ( !g_pGameRules->IsTeamplay() ) + return TRUE; + + // Both on a team? + if ( *pTeamName1 != 0 && *pTeamName2 != 0 ) + { + if ( !stricmp( pTeamName1, pTeamName2 ) ) // Same Team? + return TRUE; + } + + return FALSE; +} + + +void UTIL_StringToVector( float *pVector, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + + strcpy( tempString, pString ); + pstr = pfront = tempString; + + for ( j = 0; j < 3; j++ ) // lifted from pr_edict.c + { + pVector[j] = atof( pfront ); + + while ( *pstr && *pstr != ' ' ) + pstr++; + if (!*pstr) + break; + pstr++; + pfront = pstr; + } + if (j < 2) + { + /* + ALERT( at_error, "Bad field in entity!! %s:%s == \"%s\"\n", + pkvd->szClassName, pkvd->szKeyName, pkvd->szValue ); + */ + for (j = j+1;j < 3; j++) + pVector[j] = 0; + } +} + + +void UTIL_StringToIntArray( int *pVector, int count, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + + strcpy( tempString, pString ); + pstr = pfront = tempString; + + for ( j = 0; j < count; j++ ) // lifted from pr_edict.c + { + pVector[j] = atoi( pfront ); + + while ( *pstr && *pstr != ' ' ) + pstr++; + if (!*pstr) + break; + pstr++; + pfront = pstr; + } + + for ( j++; j < count; j++ ) + { + pVector[j] = 0; + } +} + +Vector UTIL_ClampVectorToBox( const Vector &input, const Vector &clampSize ) +{ + Vector sourceVector = input; + + if ( sourceVector.x > clampSize.x ) + sourceVector.x -= clampSize.x; + else if ( sourceVector.x < -clampSize.x ) + sourceVector.x += clampSize.x; + else + sourceVector.x = 0; + + if ( sourceVector.y > clampSize.y ) + sourceVector.y -= clampSize.y; + else if ( sourceVector.y < -clampSize.y ) + sourceVector.y += clampSize.y; + else + sourceVector.y = 0; + + if ( sourceVector.z > clampSize.z ) + sourceVector.z -= clampSize.z; + else if ( sourceVector.z < -clampSize.z ) + sourceVector.z += clampSize.z; + else + sourceVector.z = 0; + + return sourceVector.Normalize(); +} + + +float UTIL_WaterLevel( const Vector &position, float minz, float maxz ) +{ + Vector midUp = position; + midUp.z = minz; + + if (UTIL_PointContents(midUp) != CONTENTS_WATER) + return minz; + + midUp.z = maxz; + if (UTIL_PointContents(midUp) == CONTENTS_WATER) + return maxz; + + float diff = maxz - minz; + while (diff > 1.0) + { + midUp.z = minz + diff/2.0; + if (UTIL_PointContents(midUp) == CONTENTS_WATER) + { + minz = midUp.z; + } + else + { + maxz = midUp.z; + } + diff = maxz - minz; + } + + return midUp.z; +} + + +extern DLL_GLOBAL short g_sModelIndexBubbles;// holds the index for the bubbles model + +void UTIL_Bubbles( Vector mins, Vector maxs, int count ) +{ + Vector mid = (mins + maxs) * 0.5; + + float flHeight = UTIL_WaterLevel( mid, mid.z, mid.z + 1024 ); + flHeight = flHeight - mins.z; + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, mid ); + WRITE_BYTE( TE_BUBBLES ); + WRITE_COORD( mins.x ); // mins + WRITE_COORD( mins.y ); + WRITE_COORD( mins.z ); + WRITE_COORD( maxs.x ); // maxz + WRITE_COORD( maxs.y ); + WRITE_COORD( maxs.z ); + WRITE_COORD( flHeight ); // height + WRITE_SHORT( g_sModelIndexBubbles ); + WRITE_BYTE( count ); // count + WRITE_COORD( 8 ); // speed + MESSAGE_END(); +} + +void UTIL_BubbleTrail( Vector from, Vector to, int count ) +{ + float flHeight = UTIL_WaterLevel( from, from.z, from.z + 256 ); + flHeight = flHeight - from.z; + + if (flHeight < 8) + { + flHeight = UTIL_WaterLevel( to, to.z, to.z + 256 ); + flHeight = flHeight - to.z; + if (flHeight < 8) + return; + + // UNDONE: do a ploink sound + flHeight = flHeight + to.z - from.z; + } + + if (count > 255) + count = 255; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BUBBLETRAIL ); + WRITE_COORD( from.x ); // mins + WRITE_COORD( from.y ); + WRITE_COORD( from.z ); + WRITE_COORD( to.x ); // maxz + WRITE_COORD( to.y ); + WRITE_COORD( to.z ); + WRITE_COORD( flHeight ); // height + WRITE_SHORT( g_sModelIndexBubbles ); + WRITE_BYTE( count ); // count + WRITE_COORD( 8 ); // speed + MESSAGE_END(); +} + + +void UTIL_Remove( CBaseEntity *pEntity ) +{ + if ( !pEntity ) + return; + + pEntity->UpdateOnRemove(); + pEntity->pev->flags |= FL_KILLME; + pEntity->pev->targetname = 0; +} + + +BOOL UTIL_IsValidEntity( edict_t *pent ) +{ + if ( !pent || pent->free || (pent->v.flags & FL_KILLME) ) + return FALSE; + return TRUE; +} + + +void UTIL_PrecacheOther( const char *szClassname ) +{ + edict_t *pent; + + pent = CREATE_NAMED_ENTITY( MAKE_STRING( szClassname ) ); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in UTIL_PrecacheOther\n" ); + return; + } + + CBaseEntity *pEntity = CBaseEntity::Instance (VARS( pent )); + if (pEntity) + pEntity->Precache( ); + REMOVE_ENTITY(pent); +} + +//========================================================= +// UTIL_LogPrintf - Prints a logged message to console. +// Preceded by LOG: ( timestamp ) < message > +//========================================================= +void UTIL_LogPrintf( char *fmt, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start ( argptr, fmt ); + vsprintf ( string, fmt, argptr ); + va_end ( argptr ); + + // Print to server console + ALERT( at_logged, "%s", string ); +} + +//========================================================= +// UTIL_DotPoints - returns the dot product of a line from +// src to check and vecdir. +//========================================================= +float UTIL_DotPoints ( const Vector &vecSrc, const Vector &vecCheck, const Vector &vecDir ) +{ + Vector2D vec2LOS; + + vec2LOS = ( vecCheck - vecSrc ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + return DotProduct (vec2LOS , ( vecDir.Make2D() ) ); +} + + +//========================================================= +// UTIL_StripToken - for redundant keynames +//========================================================= +void UTIL_StripToken( const char *pKey, char *pDest ) +{ + int i = 0; + + while ( pKey[i] && pKey[i] != '#' ) + { + pDest[i] = pKey[i]; + i++; + } + pDest[i] = 0; +} + + +// -------------------------------------------------------------- +// +// CSave +// +// -------------------------------------------------------------- +static int gSizes[FIELD_TYPECOUNT] = +{ + sizeof(float), // FIELD_FLOAT + sizeof(int), // FIELD_STRING + sizeof(int), // FIELD_ENTITY + sizeof(int), // FIELD_CLASSPTR + sizeof(int), // FIELD_EHANDLE + sizeof(int), // FIELD_entvars_t + sizeof(int), // FIELD_EDICT + sizeof(float)*3, // FIELD_VECTOR + sizeof(float)*3, // FIELD_POSITION_VECTOR + sizeof(int *), // FIELD_POINTER + sizeof(int), // FIELD_INTEGER +#ifdef GNUC + sizeof(int *)*2, // FIELD_FUNCTION +#else + sizeof(int *), // FIELD_FUNCTION +#endif + sizeof(int), // FIELD_BOOLEAN + sizeof(short), // FIELD_SHORT + sizeof(char), // FIELD_CHARACTER + sizeof(float), // FIELD_TIME + sizeof(int), // FIELD_MODELNAME + sizeof(int), // FIELD_SOUNDNAME +}; + + +// Base class includes common SAVERESTOREDATA pointer, and manages the entity table +CSaveRestoreBuffer :: CSaveRestoreBuffer( void ) +{ + m_pdata = NULL; +} + + +CSaveRestoreBuffer :: CSaveRestoreBuffer( SAVERESTOREDATA *pdata ) +{ + m_pdata = pdata; +} + + +CSaveRestoreBuffer :: ~CSaveRestoreBuffer( void ) +{ +} + +int CSaveRestoreBuffer :: EntityIndex( CBaseEntity *pEntity ) +{ + if ( pEntity == NULL ) + return -1; + return EntityIndex( pEntity->pev ); +} + + +int CSaveRestoreBuffer :: EntityIndex( entvars_t *pevLookup ) +{ + if ( pevLookup == NULL ) + return -1; + return EntityIndex( ENT( pevLookup ) ); +} + +int CSaveRestoreBuffer :: EntityIndex( EOFFSET eoLookup ) +{ + return EntityIndex( ENT( eoLookup ) ); +} + + +int CSaveRestoreBuffer :: EntityIndex( edict_t *pentLookup ) +{ + if ( !m_pdata || pentLookup == NULL ) + return -1; + + int i; + ENTITYTABLE *pTable; + + for ( i = 0; i < m_pdata->tableCount; i++ ) + { + pTable = m_pdata->pTable + i; + if ( pTable->pent == pentLookup ) + return i; + } + return -1; +} + + +edict_t *CSaveRestoreBuffer :: EntityFromIndex( int entityIndex ) +{ + if ( !m_pdata || entityIndex < 0 ) + return NULL; + + int i; + ENTITYTABLE *pTable; + + for ( i = 0; i < m_pdata->tableCount; i++ ) + { + pTable = m_pdata->pTable + i; + if ( pTable->id == entityIndex ) + return pTable->pent; + } + return NULL; +} + + +int CSaveRestoreBuffer :: EntityFlagsSet( int entityIndex, int flags ) +{ + if ( !m_pdata || entityIndex < 0 ) + return 0; + if ( entityIndex > m_pdata->tableCount ) + return 0; + + m_pdata->pTable[ entityIndex ].flags |= flags; + + return m_pdata->pTable[ entityIndex ].flags; +} + + +void CSaveRestoreBuffer :: BufferRewind( int size ) +{ + if ( !m_pdata ) + return; + + if ( m_pdata->size < size ) + size = m_pdata->size; + + m_pdata->pCurrentData -= size; + m_pdata->size -= size; +} + +#ifndef _WIN32 +extern "C" { +unsigned _rotr ( unsigned val, int shift) +{ + register unsigned lobit; /* non-zero means lo bit set */ + register unsigned num = val; /* number to rotate */ + + shift &= 0x1f; /* modulo 32 -- this will also make + negative shifts work */ + + while (shift--) { + lobit = num & 1; /* get high bit */ + num >>= 1; /* shift right one bit */ + if (lobit) + num |= 0x80000000; /* set hi bit if lo bit was set */ + } + + return num; +} +} +#endif + +unsigned int CSaveRestoreBuffer :: HashString( const char *pszToken ) +{ + unsigned int hash = 0; + + while ( *pszToken ) + hash = _rotr( hash, 4 ) ^ *pszToken++; + + return hash; +} + +unsigned short CSaveRestoreBuffer :: TokenHash( const char *pszToken ) +{ + unsigned short hash = (unsigned short)(HashString( pszToken ) % (unsigned)m_pdata->tokenCount ); + +#if _DEBUG + static int tokensparsed = 0; + tokensparsed++; + if ( !m_pdata->tokenCount || !m_pdata->pTokens ) + ALERT( at_error, "No token table array in TokenHash()!" ); +#endif + + for ( int i=0; itokenCount; i++ ) + { +#if _DEBUG + static qboolean beentheredonethat = FALSE; + if ( i > 50 && !beentheredonethat ) + { + beentheredonethat = TRUE; + ALERT( at_error, "CSaveRestoreBuffer :: TokenHash() is getting too full!" ); + } +#endif + + int index = hash + i; + if ( index >= m_pdata->tokenCount ) + index -= m_pdata->tokenCount; + + if ( !m_pdata->pTokens[index] || strcmp( pszToken, m_pdata->pTokens[index] ) == 0 ) + { + m_pdata->pTokens[index] = (char *)pszToken; + return index; + } + } + + // Token hash table full!!! + // [Consider doing overflow table(s) after the main table & limiting linear hash table search] + ALERT( at_error, "CSaveRestoreBuffer :: TokenHash() is COMPLETELY FULL!" ); + return 0; +} + +void CSave :: WriteData( const char *pname, int size, const char *pdata ) +{ + BufferField( pname, size, pdata ); +} + + +void CSave :: WriteShort( const char *pname, const short *data, int count ) +{ + BufferField( pname, sizeof(short) * count, (const char *)data ); +} + + +void CSave :: WriteInt( const char *pname, const int *data, int count ) +{ + BufferField( pname, sizeof(int) * count, (const char *)data ); +} + + +void CSave :: WriteFloat( const char *pname, const float *data, int count ) +{ + BufferField( pname, sizeof(float) * count, (const char *)data ); +} + + +void CSave :: WriteTime( const char *pname, const float *data, int count ) +{ + int i; + Vector tmp, input; + + BufferHeader( pname, sizeof(float) * count ); + for ( i = 0; i < count; i++ ) + { + float tmp = data[0]; + + // Always encode time as a delta from the current time so it can be re-based if loaded in a new level + // Times of 0 are never written to the file, so they will be restored as 0, not a relative time + if ( m_pdata ) + tmp -= m_pdata->time; + + BufferData( (const char *)&tmp, sizeof(float) ); + data ++; + } +} + + +void CSave :: WriteString( const char *pname, const char *pdata ) +{ +#ifdef TOKENIZE + short token = (short)TokenHash( pdata ); + WriteShort( pname, &token, 1 ); +#else + BufferField( pname, strlen(pdata) + 1, pdata ); +#endif +} + + +void CSave :: WriteString( const char *pname, const int *stringId, int count ) +{ + int i, size; + +#ifdef TOKENIZE + short token = (short)TokenHash( STRING( *stringId ) ); + WriteShort( pname, &token, 1 ); +#else +#if 0 + if ( count != 1 ) + ALERT( at_error, "No string arrays!\n" ); + WriteString( pname, (char *)STRING(*stringId) ); +#endif + + size = 0; + for ( i = 0; i < count; i++ ) + size += strlen( STRING( stringId[i] ) ) + 1; + + BufferHeader( pname, size ); + for ( i = 0; i < count; i++ ) + { + const char *pString = STRING(stringId[i]); + BufferData( pString, strlen(pString)+1 ); + } +#endif +} + + +void CSave :: WriteVector( const char *pname, const Vector &value ) +{ + WriteVector( pname, &value.x, 1 ); +} + + +void CSave :: WriteVector( const char *pname, const float *value, int count ) +{ + BufferHeader( pname, sizeof(float) * 3 * count ); + BufferData( (const char *)value, sizeof(float) * 3 * count ); +} + + + +void CSave :: WritePositionVector( const char *pname, const Vector &value ) +{ + + if ( m_pdata && m_pdata->fUseLandmark ) + { + Vector tmp = value - m_pdata->vecLandmarkOffset; + WriteVector( pname, tmp ); + } + + WriteVector( pname, value ); +} + + +void CSave :: WritePositionVector( const char *pname, const float *value, int count ) +{ + int i; + Vector tmp, input; + + BufferHeader( pname, sizeof(float) * 3 * count ); + for ( i = 0; i < count; i++ ) + { + Vector tmp( value[0], value[1], value[2] ); + + if ( m_pdata && m_pdata->fUseLandmark ) + tmp = tmp - m_pdata->vecLandmarkOffset; + + BufferData( (const char *)&tmp.x, sizeof(float) * 3 ); + value += 3; + } +} + + +void CSave :: WriteFunction( const char *pname, void **data, int count ) +{ + const char *functionName; + + functionName = NAME_FOR_FUNCTION( (uint32)*data ); + if ( functionName ) + BufferField( pname, strlen(functionName) + 1, functionName ); + else + ALERT( at_error, "Invalid function pointer in entity!" ); +} + + +void EntvarsKeyvalue( entvars_t *pev, KeyValueData *pkvd ) +{ + int i; + TYPEDESCRIPTION *pField; + + for ( i = 0; i < ENTVARS_COUNT; i++ ) + { + pField = &gEntvarsDescription[i]; + + if ( !stricmp( pField->fieldName, pkvd->szKeyName ) ) + { + switch( pField->fieldType ) + { + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + case FIELD_STRING: + (*(int *)((char *)pev + pField->fieldOffset)) = ALLOC_STRING( pkvd->szValue ); + break; + + case FIELD_TIME: + case FIELD_FLOAT: + (*(float *)((char *)pev + pField->fieldOffset)) = atof( pkvd->szValue ); + break; + + case FIELD_INTEGER: + (*(int *)((char *)pev + pField->fieldOffset)) = atoi( pkvd->szValue ); + break; + + case FIELD_POSITION_VECTOR: + case FIELD_VECTOR: + UTIL_StringToVector( (float *)((char *)pev + pField->fieldOffset), pkvd->szValue ); + break; + + default: + case FIELD_EVARS: + case FIELD_CLASSPTR: + case FIELD_EDICT: + case FIELD_ENTITY: + case FIELD_POINTER: + ALERT( at_error, "Bad field in entity!!\n" ); + break; + } + pkvd->fHandled = TRUE; + return; + } + } +} + + + +int CSave :: WriteEntVars( const char *pname, entvars_t *pev ) +{ + return WriteFields( pname, pev, gEntvarsDescription, ENTVARS_COUNT ); +} + + + +int CSave :: WriteFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + int i, j, actualCount, emptyCount; + TYPEDESCRIPTION *pTest; + int entityArray[MAX_ENTITYARRAY]; + + // Precalculate the number of empty fields + emptyCount = 0; + for ( i = 0; i < fieldCount; i++ ) + { + void *pOutputData; + pOutputData = ((char *)pBaseData + pFields[i].fieldOffset ); + if ( DataEmpty( (const char *)pOutputData, pFields[i].fieldSize * gSizes[pFields[i].fieldType] ) ) + emptyCount++; + } + + // Empty fields will not be written, write out the actual number of fields to be written + actualCount = fieldCount - emptyCount; + WriteInt( pname, &actualCount, 1 ); + + for ( i = 0; i < fieldCount; i++ ) + { + void *pOutputData; + pTest = &pFields[ i ]; + pOutputData = ((char *)pBaseData + pTest->fieldOffset ); + + // UNDONE: Must we do this twice? + if ( DataEmpty( (const char *)pOutputData, pTest->fieldSize * gSizes[pTest->fieldType] ) ) + continue; + + switch( pTest->fieldType ) + { + case FIELD_FLOAT: + WriteFloat( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + case FIELD_TIME: + WriteTime( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + case FIELD_STRING: + WriteString( pTest->fieldName, (int *)pOutputData, pTest->fieldSize ); + break; + case FIELD_CLASSPTR: + case FIELD_EVARS: + case FIELD_EDICT: + case FIELD_ENTITY: + case FIELD_EHANDLE: + if ( pTest->fieldSize > MAX_ENTITYARRAY ) + ALERT( at_error, "Can't save more than %d entities in an array!!!\n", MAX_ENTITYARRAY ); + for ( j = 0; j < pTest->fieldSize; j++ ) + { + switch( pTest->fieldType ) + { + case FIELD_EVARS: + entityArray[j] = EntityIndex( ((entvars_t **)pOutputData)[j] ); + break; + case FIELD_CLASSPTR: + entityArray[j] = EntityIndex( ((CBaseEntity **)pOutputData)[j] ); + break; + case FIELD_EDICT: + entityArray[j] = EntityIndex( ((edict_t **)pOutputData)[j] ); + break; + case FIELD_ENTITY: + entityArray[j] = EntityIndex( ((EOFFSET *)pOutputData)[j] ); + break; + case FIELD_EHANDLE: + entityArray[j] = EntityIndex( (CBaseEntity *)(((EHANDLE *)pOutputData)[j]) ); + break; + } + } + WriteInt( pTest->fieldName, entityArray, pTest->fieldSize ); + break; + case FIELD_POSITION_VECTOR: + WritePositionVector( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + case FIELD_VECTOR: + WriteVector( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + + case FIELD_BOOLEAN: + case FIELD_INTEGER: + WriteInt( pTest->fieldName, (int *)pOutputData, pTest->fieldSize ); + break; + + case FIELD_SHORT: + WriteData( pTest->fieldName, 2 * pTest->fieldSize, ((char *)pOutputData) ); + break; + + case FIELD_CHARACTER: + WriteData( pTest->fieldName, pTest->fieldSize, ((char *)pOutputData) ); + break; + + // For now, just write the address out, we're not going to change memory while doing this yet! + case FIELD_POINTER: + WriteInt( pTest->fieldName, (int *)(char *)pOutputData, pTest->fieldSize ); + break; + + case FIELD_FUNCTION: + WriteFunction( pTest->fieldName, (void **)pOutputData, pTest->fieldSize ); + break; + default: + ALERT( at_error, "Bad field type\n" ); + } + } + + return 1; +} + + +void CSave :: BufferString( char *pdata, int len ) +{ + char c = 0; + + BufferData( pdata, len ); // Write the string + BufferData( &c, 1 ); // Write a null terminator +} + + +int CSave :: DataEmpty( const char *pdata, int size ) +{ + for ( int i = 0; i < size; i++ ) + { + if ( pdata[i] ) + return 0; + } + return 1; +} + + +void CSave :: BufferField( const char *pname, int size, const char *pdata ) +{ + BufferHeader( pname, size ); + BufferData( pdata, size ); +} + + +void CSave :: BufferHeader( const char *pname, int size ) +{ + short hashvalue = TokenHash( pname ); + if ( size > 1<<(sizeof(short)*8) ) + ALERT( at_error, "CSave :: BufferHeader() size parameter exceeds 'short'!" ); + BufferData( (const char *)&size, sizeof(short) ); + BufferData( (const char *)&hashvalue, sizeof(short) ); +} + + +void CSave :: BufferData( const char *pdata, int size ) +{ + if ( !m_pdata ) + return; + + if ( m_pdata->size + size > m_pdata->bufferSize ) + { + ALERT( at_error, "Save/Restore overflow!" ); + m_pdata->size = m_pdata->bufferSize; + return; + } + + memcpy( m_pdata->pCurrentData, pdata, size ); + m_pdata->pCurrentData += size; + m_pdata->size += size; +} + + + +// -------------------------------------------------------------- +// +// CRestore +// +// -------------------------------------------------------------- + +int CRestore::ReadField( void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount, int startField, int size, char *pName, void *pData ) +{ + int i, j, stringCount, fieldNumber, entityIndex; + TYPEDESCRIPTION *pTest; + float time, timeData; + Vector position; + edict_t *pent; + char *pString; + + time = 0; + position = Vector(0,0,0); + + if ( m_pdata ) + { + time = m_pdata->time; + if ( m_pdata->fUseLandmark ) + position = m_pdata->vecLandmarkOffset; + } + + for ( i = 0; i < fieldCount; i++ ) + { + fieldNumber = (i+startField)%fieldCount; + pTest = &pFields[ fieldNumber ]; + if ( !stricmp( pTest->fieldName, pName ) ) + { + if ( !m_global || !(pTest->flags & FTYPEDESC_GLOBAL) ) + { + for ( j = 0; j < pTest->fieldSize; j++ ) + { + void *pOutputData = ((char *)pBaseData + pTest->fieldOffset + (j*gSizes[pTest->fieldType]) ); + void *pInputData = (char *)pData + j * gSizes[pTest->fieldType]; + + switch( pTest->fieldType ) + { + case FIELD_TIME: + timeData = *(float *)pInputData; + // Re-base time variables + timeData += time; + *((float *)pOutputData) = timeData; + break; + case FIELD_FLOAT: + *((float *)pOutputData) = *(float *)pInputData; + break; + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + case FIELD_STRING: + // Skip over j strings + pString = (char *)pData; + for ( stringCount = 0; stringCount < j; stringCount++ ) + { + while (*pString) + pString++; + pString++; + } + pInputData = pString; + if ( strlen( (char *)pInputData ) == 0 ) + *((int *)pOutputData) = 0; + else + { + int string; + + string = ALLOC_STRING( (char *)pInputData ); + + *((int *)pOutputData) = string; + + if ( !FStringNull( string ) && m_precache ) + { + if ( pTest->fieldType == FIELD_MODELNAME ) + PRECACHE_MODEL( (char *)STRING( string ) ); + else if ( pTest->fieldType == FIELD_SOUNDNAME ) + PRECACHE_SOUND( (char *)STRING( string ) ); + } + } + break; + case FIELD_EVARS: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((entvars_t **)pOutputData) = VARS(pent); + else + *((entvars_t **)pOutputData) = NULL; + break; + case FIELD_CLASSPTR: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((CBaseEntity **)pOutputData) = CBaseEntity::Instance(pent); + else + *((CBaseEntity **)pOutputData) = NULL; + break; + case FIELD_EDICT: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + *((edict_t **)pOutputData) = pent; + break; + case FIELD_EHANDLE: + // Input and Output sizes are different! + pOutputData = (char *)pOutputData + j*(sizeof(EHANDLE) - gSizes[pTest->fieldType]); + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((EHANDLE *)pOutputData) = CBaseEntity::Instance(pent); + else + *((EHANDLE *)pOutputData) = NULL; + break; + case FIELD_ENTITY: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((EOFFSET *)pOutputData) = OFFSET(pent); + else + *((EOFFSET *)pOutputData) = 0; + break; + case FIELD_VECTOR: + ((float *)pOutputData)[0] = ((float *)pInputData)[0]; + ((float *)pOutputData)[1] = ((float *)pInputData)[1]; + ((float *)pOutputData)[2] = ((float *)pInputData)[2]; + break; + case FIELD_POSITION_VECTOR: + ((float *)pOutputData)[0] = ((float *)pInputData)[0] + position.x; + ((float *)pOutputData)[1] = ((float *)pInputData)[1] + position.y; + ((float *)pOutputData)[2] = ((float *)pInputData)[2] + position.z; + break; + + case FIELD_BOOLEAN: + case FIELD_INTEGER: + *((int *)pOutputData) = *( int *)pInputData; + break; + + case FIELD_SHORT: + *((short *)pOutputData) = *( short *)pInputData; + break; + + case FIELD_CHARACTER: + *((char *)pOutputData) = *( char *)pInputData; + break; + + case FIELD_POINTER: + *((int *)pOutputData) = *( int *)pInputData; + break; + case FIELD_FUNCTION: + if ( strlen( (char *)pInputData ) == 0 ) + *((int *)pOutputData) = 0; + else + *((int *)pOutputData) = FUNCTION_FROM_NAME( (char *)pInputData ); + break; + + default: + ALERT( at_error, "Bad field type\n" ); + } + } + } +#if 0 + else + { + ALERT( at_console, "Skipping global field %s\n", pName ); + } +#endif + return fieldNumber; + } + } + + return -1; +} + + +int CRestore::ReadEntVars( const char *pname, entvars_t *pev ) +{ + return ReadFields( pname, pev, gEntvarsDescription, ENTVARS_COUNT ); +} + + +int CRestore::ReadFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + unsigned short i, token; + int lastField, fileCount; + HEADER header; + + i = ReadShort(); + ASSERT( i == sizeof(int) ); // First entry should be an int + + token = ReadShort(); + + // Check the struct name + if ( token != TokenHash(pname) ) // Field Set marker + { +// ALERT( at_error, "Expected %s found %s!\n", pname, BufferPointer() ); + BufferRewind( 2*sizeof(short) ); + return 0; + } + + // Skip over the struct name + fileCount = ReadInt(); // Read field count + + lastField = 0; // Make searches faster, most data is read/written in the same order + + // Clear out base data + for ( i = 0; i < fieldCount; i++ ) + { + // Don't clear global fields + if ( !m_global || !(pFields[i].flags & FTYPEDESC_GLOBAL) ) + memset( ((char *)pBaseData + pFields[i].fieldOffset), 0, pFields[i].fieldSize * gSizes[pFields[i].fieldType] ); + } + + for ( i = 0; i < fileCount; i++ ) + { + BufferReadHeader( &header ); + lastField = ReadField( pBaseData, pFields, fieldCount, lastField, header.size, m_pdata->pTokens[header.token], header.pData ); + lastField++; + } + + return 1; +} + + +void CRestore::BufferReadHeader( HEADER *pheader ) +{ + ASSERT( pheader!=NULL ); + pheader->size = ReadShort(); // Read field size + pheader->token = ReadShort(); // Read field name token + pheader->pData = BufferPointer(); // Field Data is next + BufferSkipBytes( pheader->size ); // Advance to next field +} + + +short CRestore::ReadShort( void ) +{ + short tmp = 0; + + BufferReadBytes( (char *)&tmp, sizeof(short) ); + + return tmp; +} + +int CRestore::ReadInt( void ) +{ + int tmp = 0; + + BufferReadBytes( (char *)&tmp, sizeof(int) ); + + return tmp; +} + +int CRestore::ReadNamedInt( const char *pName ) +{ + HEADER header; + + BufferReadHeader( &header ); + return ((int *)header.pData)[0]; +} + +char *CRestore::ReadNamedString( const char *pName ) +{ + HEADER header; + + BufferReadHeader( &header ); +#ifdef TOKENIZE + return (char *)(m_pdata->pTokens[*(short *)header.pData]); +#else + return (char *)header.pData; +#endif +} + + +char *CRestore::BufferPointer( void ) +{ + if ( !m_pdata ) + return NULL; + + return m_pdata->pCurrentData; +} + +void CRestore::BufferReadBytes( char *pOutput, int size ) +{ + ASSERT( m_pdata !=NULL ); + + if ( !m_pdata || Empty() ) + return; + + if ( (m_pdata->size + size) > m_pdata->bufferSize ) + { + ALERT( at_error, "Restore overflow!" ); + m_pdata->size = m_pdata->bufferSize; + return; + } + + if ( pOutput ) + memcpy( pOutput, m_pdata->pCurrentData, size ); + m_pdata->pCurrentData += size; + m_pdata->size += size; +} + + +void CRestore::BufferSkipBytes( int bytes ) +{ + BufferReadBytes( NULL, bytes ); +} + +int CRestore::BufferSkipZString( void ) +{ + char *pszSearch; + int len; + + if ( !m_pdata ) + return 0; + + int maxLen = m_pdata->bufferSize - m_pdata->size; + + len = 0; + pszSearch = m_pdata->pCurrentData; + while ( *pszSearch++ && len < maxLen ) + len++; + + len++; + + BufferSkipBytes( len ); + + return len; +} + +int CRestore::BufferCheckZString( const char *string ) +{ + if ( !m_pdata ) + return 0; + + int maxLen = m_pdata->bufferSize - m_pdata->size; + int len = strlen( string ); + if ( len <= maxLen ) + { + if ( !strncmp( string, m_pdata->pCurrentData, len ) ) + return 1; + } + return 0; +} + diff --git a/dlls/util.h b/dlls/util.h new file mode 100644 index 0000000..bbc3c67 --- /dev/null +++ b/dlls/util.h @@ -0,0 +1,546 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "archtypes.h" // DAL + +// +// Misc utility code +// +#ifndef ACTIVITY_H +#include "activity.h" +#endif + +#ifndef ENGINECALLBACK_H +#include "enginecallback.h" +#endif +inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin, entvars_t *ent ); // implementation later in this file + +extern globalvars_t *gpGlobals; + +// Use this instead of ALLOC_STRING on constant strings +#define STRING(offset) ((const char *)(gpGlobals->pStringBase + (unsigned int)(offset))) +#define MAKE_STRING(str) ((uint64)(str) - (uint64)(STRING(0))) + +inline edict_t *FIND_ENTITY_BY_CLASSNAME(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "classname", pszName); +} + +inline edict_t *FIND_ENTITY_BY_TARGETNAME(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "targetname", pszName); +} + +// for doing a reverse lookup. Say you have a door, and want to find its button. +inline edict_t *FIND_ENTITY_BY_TARGET(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "target", pszName); +} + +// Keeps clutter down a bit, when writing key-value pairs +#define WRITEKEY_INT(pf, szKeyName, iKeyValue) ENGINE_FPRINTF(pf, "\"%s\" \"%d\"\n", szKeyName, iKeyValue) +#define WRITEKEY_FLOAT(pf, szKeyName, flKeyValue) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%f\"\n", szKeyName, flKeyValue) +#define WRITEKEY_STRING(pf, szKeyName, szKeyValue) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%s\"\n", szKeyName, szKeyValue) +#define WRITEKEY_VECTOR(pf, szKeyName, flX, flY, flZ) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%f %f %f\"\n", szKeyName, flX, flY, flZ) + +// Keeps clutter down a bit, when using a float as a bit-vector +#define SetBits(flBitVector, bits) ((flBitVector) = (int)(flBitVector) | (bits)) +#define ClearBits(flBitVector, bits) ((flBitVector) = (int)(flBitVector) & ~(bits)) +#define FBitSet(flBitVector, bit) ((int)(flBitVector) & (bit)) + +// Makes these more explicit, and easier to find +#define FILE_GLOBAL static +#define DLL_GLOBAL + +// Until we figure out why "const" gives the compiler problems, we'll just have to use +// this bogus "empty" define to mark things as constant. +#define CONSTANT + +// More explicit than "int" +typedef int EOFFSET; + +// In case it's not alread defined +typedef int BOOL; + +// In case this ever changes +#define M_PI 3.14159265358979323846 + +// Keeps clutter down a bit, when declaring external entity/global method prototypes +#define DECLARE_GLOBAL_METHOD(MethodName) extern void UTIL_DLLEXPORT MethodName( void ) +#define GLOBAL_METHOD(funcname) void UTIL_DLLEXPORT funcname(void) + +#ifndef UTIL_DLLEXPORT +#ifdef _WIN32 +#define UTIL_DLLEXPORT _declspec( dllexport ) +#else +#define UTIL_DLLEXPORT __attribute__ ((visibility("default"))) +#endif +#endif + +// This is the glue that hooks .MAP entity class names to our CPP classes +// The _declspec forces them to be exported by name so we can do a lookup with GetProcAddress() +// The function is used to intialize / allocate the object for the entity +#define LINK_ENTITY_TO_CLASS(mapClassName,DLLClassName) \ + extern "C" UTIL_DLLEXPORT void mapClassName( entvars_t *pev ); \ + void mapClassName( entvars_t *pev ) { GetClassPtr( (DLLClassName *)pev ); } + + +// +// Conversion among the three types of "entity", including identity-conversions. +// +#ifdef DEBUG + extern edict_t *DBG_EntOfVars(const entvars_t *pev); + inline edict_t *ENT(const entvars_t *pev) { return DBG_EntOfVars(pev); } +#else + inline edict_t *ENT(const entvars_t *pev) { return pev->pContainingEntity; } +#endif +inline edict_t *ENT(edict_t *pent) { return pent; } +inline edict_t *ENT(EOFFSET eoffset) { return (*g_engfuncs.pfnPEntityOfEntOffset)(eoffset); } +inline EOFFSET OFFSET(EOFFSET eoffset) { return eoffset; } +inline EOFFSET OFFSET(const edict_t *pent) +{ +#if _DEBUG + if ( !pent ) + ALERT( at_error, "Bad ent in OFFSET()\n" ); +#endif + return (*g_engfuncs.pfnEntOffsetOfPEntity)(pent); +} +inline EOFFSET OFFSET(entvars_t *pev) +{ +#if _DEBUG + if ( !pev ) + ALERT( at_error, "Bad pev in OFFSET()\n" ); +#endif + return OFFSET(ENT(pev)); +} +inline entvars_t *VARS(entvars_t *pev) { return pev; } + +inline entvars_t *VARS(edict_t *pent) +{ + if ( !pent ) + return NULL; + + return &pent->v; +} + +inline entvars_t* VARS(EOFFSET eoffset) { return VARS(ENT(eoffset)); } +inline int ENTINDEX(edict_t *pEdict) { return (*g_engfuncs.pfnIndexOfEdict)(pEdict); } +inline edict_t* INDEXENT( int iEdictNum ) { return (*g_engfuncs.pfnPEntityOfEntIndex)(iEdictNum); } +inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin, entvars_t *ent ) { + (*g_engfuncs.pfnMessageBegin)(msg_dest, msg_type, pOrigin, ENT(ent)); +} + +// Testing the three types of "entity" for nullity +#define eoNullEntity 0 +inline BOOL FNullEnt(EOFFSET eoffset) { return eoffset == 0; } +inline BOOL FNullEnt(const edict_t* pent) { return pent == NULL || FNullEnt(OFFSET(pent)); } +inline BOOL FNullEnt(entvars_t* pev) { return pev == NULL || FNullEnt(OFFSET(pev)); } + +// Testing strings for nullity +#define iStringNull 0 +inline BOOL FStringNull(int iString) { return iString == iStringNull; } + +#define cchMapNameMost 32 + +// Dot products for view cone checking +#define VIEW_FIELD_FULL (float)-1.0 // +-180 degrees +#define VIEW_FIELD_WIDE (float)-0.7 // +-135 degrees 0.1 // +-85 degrees, used for full FOV checks +#define VIEW_FIELD_NARROW (float)0.7 // +-45 degrees, more narrow check used to set up ranged attacks +#define VIEW_FIELD_ULTRA_NARROW (float)0.9 // +-25 degrees, more narrow check used to set up ranged attacks + +// All monsters need this data +#define DONT_BLEED -1 +#define BLOOD_COLOR_RED (BYTE)247 +#define BLOOD_COLOR_YELLOW (BYTE)195 +#define BLOOD_COLOR_GREEN BLOOD_COLOR_YELLOW + +typedef enum +{ + + MONSTERSTATE_NONE = 0, + MONSTERSTATE_IDLE, + MONSTERSTATE_COMBAT, + MONSTERSTATE_ALERT, + MONSTERSTATE_HUNT, + MONSTERSTATE_PRONE, + MONSTERSTATE_SCRIPT, + MONSTERSTATE_PLAYDEAD, + MONSTERSTATE_DEAD + +} MONSTERSTATE; + + + +// Things that toggle (buttons/triggers/doors) need this +typedef enum + { + TS_AT_TOP, + TS_AT_BOTTOM, + TS_GOING_UP, + TS_GOING_DOWN + } TOGGLE_STATE; + +// Misc useful +inline BOOL FStrEq(const char*sz1, const char*sz2) + { return (strcmp(sz1, sz2) == 0); } +inline BOOL FClassnameIs(edict_t* pent, const char* szClassname) + { return FStrEq(STRING(VARS(pent)->classname), szClassname); } +inline BOOL FClassnameIs(entvars_t* pev, const char* szClassname) + { return FStrEq(STRING(pev->classname), szClassname); } + +class CBaseEntity; + +// Misc. Prototypes +extern void UTIL_SetSize (entvars_t* pev, const Vector &vecMin, const Vector &vecMax); +extern float UTIL_VecToYaw (const Vector &vec); +extern Vector UTIL_VecToAngles (const Vector &vec); +extern float UTIL_AngleMod (float a); +extern float UTIL_AngleDiff ( float destAngle, float srcAngle ); + +extern CBaseEntity *UTIL_FindEntityInSphere(CBaseEntity *pStartEntity, const Vector &vecCenter, float flRadius); +extern CBaseEntity *UTIL_FindEntityByString(CBaseEntity *pStartEntity, const char *szKeyword, const char *szValue ); +extern CBaseEntity *UTIL_FindEntityByClassname(CBaseEntity *pStartEntity, const char *szName ); +extern CBaseEntity *UTIL_FindEntityByTargetname(CBaseEntity *pStartEntity, const char *szName ); +extern CBaseEntity *UTIL_FindEntityGeneric(const char *szName, Vector &vecSrc, float flRadius ); + +// returns a CBaseEntity pointer to a player by index. Only returns if the player is spawned and connected +// otherwise returns NULL +// Index is 1 based +extern CBaseEntity *UTIL_PlayerByIndex( int playerIndex ); + +#define UTIL_EntitiesInPVS(pent) (*g_engfuncs.pfnEntitiesInPVS)(pent) +extern void UTIL_MakeVectors (const Vector &vecAngles); + +// Pass in an array of pointers and an array size, it fills the array and returns the number inserted +extern int UTIL_MonstersInSphere( CBaseEntity **pList, int listMax, const Vector ¢er, float radius ); +extern int UTIL_EntitiesInBox( CBaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask ); + +inline void UTIL_MakeVectorsPrivate( const Vector &vecAngles, float *p_vForward, float *p_vRight, float *p_vUp ) +{ + g_engfuncs.pfnAngleVectors( vecAngles, p_vForward, p_vRight, p_vUp ); +} + +extern void UTIL_MakeAimVectors ( const Vector &vecAngles ); // like MakeVectors, but assumes pitch isn't inverted +extern void UTIL_MakeInvVectors ( const Vector &vec, globalvars_t *pgv ); + +extern void UTIL_SetOrigin ( entvars_t* pev, const Vector &vecOrigin ); +extern void UTIL_EmitAmbientSound ( edict_t *entity, const Vector &vecOrigin, const char *samp, float vol, float attenuation, int fFlags, int pitch ); +extern void UTIL_ParticleEffect ( const Vector &vecOrigin, const Vector &vecDirection, ULONG ulColor, ULONG ulCount ); +extern void UTIL_ScreenShake ( const Vector ¢er, float amplitude, float frequency, float duration, float radius ); +extern void UTIL_ScreenShakeAll ( const Vector ¢er, float amplitude, float frequency, float duration ); +extern void UTIL_ShowMessage ( const char *pString, CBaseEntity *pPlayer ); +extern void UTIL_ShowMessageAll ( const char *pString ); +extern void UTIL_ScreenFadeAll ( const Vector &color, float fadeTime, float holdTime, int alpha, int flags ); +extern void UTIL_ScreenFade ( CBaseEntity *pEntity, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ); + +typedef enum { ignore_monsters=1, dont_ignore_monsters=0, missile=2 } IGNORE_MONSTERS; +typedef enum { ignore_glass=1, dont_ignore_glass=0 } IGNORE_GLASS; +extern void UTIL_TraceLine (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, edict_t *pentIgnore, TraceResult *ptr); +extern void UTIL_TraceLine (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, IGNORE_GLASS ignoreGlass, edict_t *pentIgnore, TraceResult *ptr); +typedef enum { point_hull=0, human_hull=1, large_hull=2, head_hull=3 }; +extern void UTIL_TraceHull (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, int hullNumber, edict_t *pentIgnore, TraceResult *ptr); +extern TraceResult UTIL_GetGlobalTrace (void); +extern void UTIL_TraceModel (const Vector &vecStart, const Vector &vecEnd, int hullNumber, edict_t *pentModel, TraceResult *ptr); +extern Vector UTIL_GetAimVector (edict_t* pent, float flSpeed); +extern int UTIL_PointContents (const Vector &vec); + +extern int UTIL_IsMasterTriggered (string_t sMaster, CBaseEntity *pActivator); +extern void UTIL_BloodStream( const Vector &origin, const Vector &direction, int color, int amount ); +extern void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, int amount ); +extern Vector UTIL_RandomBloodVector( void ); +extern BOOL UTIL_ShouldShowBlood( int bloodColor ); +extern void UTIL_BloodDecalTrace( TraceResult *pTrace, int bloodColor ); +extern void UTIL_DecalTrace( TraceResult *pTrace, int decalNumber ); +extern void UTIL_PlayerDecalTrace( TraceResult *pTrace, int playernum, int decalNumber, BOOL bIsCustom ); +extern void UTIL_GunshotDecalTrace( TraceResult *pTrace, int decalNumber ); +extern void UTIL_Sparks( const Vector &position ); +extern void UTIL_Ricochet( const Vector &position, float scale ); +extern void UTIL_StringToVector( float *pVector, const char *pString ); +extern void UTIL_StringToIntArray( int *pVector, int count, const char *pString ); +extern Vector UTIL_ClampVectorToBox( const Vector &input, const Vector &clampSize ); +extern float UTIL_Approach( float target, float value, float speed ); +extern float UTIL_ApproachAngle( float target, float value, float speed ); +extern float UTIL_AngleDistance( float next, float cur ); + +extern char *UTIL_VarArgs( char *format, ... ); +extern void UTIL_Remove( CBaseEntity *pEntity ); +extern BOOL UTIL_IsValidEntity( edict_t *pent ); +extern BOOL UTIL_TeamsMatch( const char *pTeamName1, const char *pTeamName2 ); + +// Use for ease-in, ease-out style interpolation (accel/decel) +extern float UTIL_SplineFraction( float value, float scale ); + +// Search for water transition along a vertical line +extern float UTIL_WaterLevel( const Vector &position, float minz, float maxz ); +extern void UTIL_Bubbles( Vector mins, Vector maxs, int count ); +extern void UTIL_BubbleTrail( Vector from, Vector to, int count ); + +// allows precacheing of other entities +extern void UTIL_PrecacheOther( const char *szClassname ); + +// prints a message to each client +extern void UTIL_ClientPrintAll( int msg_dest, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ); +inline void UTIL_CenterPrintAll( const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ) +{ + UTIL_ClientPrintAll( HUD_PRINTCENTER, msg_name, param1, param2, param3, param4 ); +} + +class CBasePlayerItem; +class CBasePlayer; +extern BOOL UTIL_GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ); + +// prints messages through the HUD +extern void ClientPrint( entvars_t *client, int msg_dest, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ); + +// prints a message to the HUD say (chat) +extern void UTIL_SayText( const char *pText, CBaseEntity *pEntity ); +extern void UTIL_SayTextAll( const char *pText, CBaseEntity *pEntity ); + + +typedef struct hudtextparms_s +{ + float x; + float y; + int effect; + byte r1, g1, b1, a1; + byte r2, g2, b2, a2; + float fadeinTime; + float fadeoutTime; + float holdTime; + float fxTime; + int channel; +} hudtextparms_t; + +// prints as transparent 'title' to the HUD +extern void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage ); +extern void UTIL_HudMessage( CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage ); + +// for handy use with ClientPrint params +extern char *UTIL_dtos1( int d ); +extern char *UTIL_dtos2( int d ); +extern char *UTIL_dtos3( int d ); +extern char *UTIL_dtos4( int d ); + +// Writes message to console with timestamp and FragLog header. +extern void UTIL_LogPrintf( char *fmt, ... ); + +// Sorta like FInViewCone, but for nonmonsters. +extern float UTIL_DotPoints ( const Vector &vecSrc, const Vector &vecCheck, const Vector &vecDir ); + +extern void UTIL_StripToken( const char *pKey, char *pDest );// for redundant keynames + +// Misc functions +extern void SetMovedir(entvars_t* pev); +extern Vector VecBModelOrigin( entvars_t* pevBModel ); +extern int BuildChangeList( LEVELLIST *pLevelList, int maxList ); + +// +// How did I ever live without ASSERT? +// +#ifdef DEBUG +void DBG_AssertFunction(BOOL fExpr, const char* szExpr, const char* szFile, int szLine, const char* szMessage); +#define ASSERT(f) DBG_AssertFunction(f, #f, __FILE__, __LINE__, NULL) +#define ASSERTSZ(f, sz) DBG_AssertFunction(f, #f, __FILE__, __LINE__, sz) +#else // !DEBUG +#define ASSERT(f) +#define ASSERTSZ(f, sz) +#endif // !DEBUG + + +extern DLL_GLOBAL const Vector g_vecZero; + +// +// Constants that were used only by QC (maybe not used at all now) +// +// Un-comment only as needed +// +#define LANGUAGE_ENGLISH 0 +#define LANGUAGE_GERMAN 1 +#define LANGUAGE_FRENCH 2 +#define LANGUAGE_BRITISH 3 + +extern DLL_GLOBAL int g_Language; + +#define AMBIENT_SOUND_STATIC 0 // medium radius attenuation +#define AMBIENT_SOUND_EVERYWHERE 1 +#define AMBIENT_SOUND_SMALLRADIUS 2 +#define AMBIENT_SOUND_MEDIUMRADIUS 4 +#define AMBIENT_SOUND_LARGERADIUS 8 +#define AMBIENT_SOUND_START_SILENT 16 +#define AMBIENT_SOUND_NOT_LOOPING 32 + +#define SPEAKER_START_SILENT 1 // wait for trigger 'on' to start announcements + +#define SND_SPAWNING (1<<8) // duplicated in protocol.h we're spawing, used in some cases for ambients +#define SND_STOP (1<<5) // duplicated in protocol.h stop sound +#define SND_CHANGE_VOL (1<<6) // duplicated in protocol.h change sound vol +#define SND_CHANGE_PITCH (1<<7) // duplicated in protocol.h change sound pitch + +#define LFO_SQUARE 1 +#define LFO_TRIANGLE 2 +#define LFO_RANDOM 3 + +// func_rotating +#define SF_BRUSH_ROTATE_Y_AXIS 0 +#define SF_BRUSH_ROTATE_INSTANT 1 +#define SF_BRUSH_ROTATE_BACKWARDS 2 +#define SF_BRUSH_ROTATE_Z_AXIS 4 +#define SF_BRUSH_ROTATE_X_AXIS 8 +#define SF_PENDULUM_AUTO_RETURN 16 +#define SF_PENDULUM_PASSABLE 32 + + +#define SF_BRUSH_ROTATE_SMALLRADIUS 128 +#define SF_BRUSH_ROTATE_MEDIUMRADIUS 256 +#define SF_BRUSH_ROTATE_LARGERADIUS 512 + +#define PUSH_BLOCK_ONLY_X 1 +#define PUSH_BLOCK_ONLY_Y 2 + +#define VEC_HULL_MIN Vector(-16, -16, -36) +#define VEC_HULL_MAX Vector( 16, 16, 36) +#define VEC_HUMAN_HULL_MIN Vector( -16, -16, 0 ) +#define VEC_HUMAN_HULL_MAX Vector( 16, 16, 72 ) +#define VEC_HUMAN_HULL_DUCK Vector( 16, 16, 36 ) + +#define VEC_VIEW Vector( 0, 0, 28 ) + +#define VEC_DUCK_HULL_MIN Vector(-16, -16, -18 ) +#define VEC_DUCK_HULL_MAX Vector( 16, 16, 18) +#define VEC_DUCK_VIEW Vector( 0, 0, 12 ) + +#define SVC_TEMPENTITY 23 +#define SVC_INTERMISSION 30 +#define SVC_CDTRACK 32 +#define SVC_WEAPONANIM 35 +#define SVC_ROOMTYPE 37 +#define SVC_DIRECTOR 51 + + + +// triggers +#define SF_TRIGGER_ALLOWMONSTERS 1// monsters allowed to fire this trigger +#define SF_TRIGGER_NOCLIENTS 2// players not allowed to fire this trigger +#define SF_TRIGGER_PUSHABLES 4// only pushables can fire this trigger + +// func breakable +#define SF_BREAK_TRIGGER_ONLY 1// may only be broken by trigger +#define SF_BREAK_TOUCH 2// can be 'crashed through' by running player (plate glass) +#define SF_BREAK_PRESSURE 4// can be broken by a player standing on it +#define SF_BREAK_CROWBAR 256// instant break if hit with crowbar + +// func_pushable (it's also func_breakable, so don't collide with those flags) +#define SF_PUSH_BREAKABLE 128 + +#define SF_LIGHT_START_OFF 1 + +#define SPAWNFLAG_NOMESSAGE 1 +#define SPAWNFLAG_NOTOUCH 1 +#define SPAWNFLAG_DROIDONLY 4 + +#define SPAWNFLAG_USEONLY 1 // can't be touched, must be used (buttons) + +#define TELE_PLAYER_ONLY 1 +#define TELE_SILENT 2 + +#define SF_TRIG_PUSH_ONCE 1 + + +// Sound Utilities + +// sentence groups +#define CBSENTENCENAME_MAX 16 +#define CVOXFILESENTENCEMAX 1536 // max number of sentences in game. NOTE: this must match + // CVOXFILESENTENCEMAX in engine\sound.h!!! + +extern char gszallsentencenames[CVOXFILESENTENCEMAX][CBSENTENCENAME_MAX]; +extern int gcallsentences; + +int USENTENCEG_Pick(int isentenceg, char *szfound); +int USENTENCEG_PickSequential(int isentenceg, char *szfound, int ipick, int freset); +void USENTENCEG_InitLRU(unsigned char *plru, int count); + +void SENTENCEG_Init(); +void SENTENCEG_Stop(edict_t *entity, int isentenceg, int ipick); +int SENTENCEG_PlayRndI(edict_t *entity, int isentenceg, float volume, float attenuation, int flags, int pitch); +int SENTENCEG_PlayRndSz(edict_t *entity, const char *szrootname, float volume, float attenuation, int flags, int pitch); +int SENTENCEG_PlaySequentialSz(edict_t *entity, const char *szrootname, float volume, float attenuation, int flags, int pitch, int ipick, int freset); +int SENTENCEG_GetIndex(const char *szrootname); +int SENTENCEG_Lookup(const char *sample, char *sentencenum); + +void TEXTURETYPE_Init(); +char TEXTURETYPE_Find(char *name); +float TEXTURETYPE_PlaySound(TraceResult *ptr, Vector vecSrc, Vector vecEnd, int iBulletType); + +// NOTE: use EMIT_SOUND_DYN to set the pitch of a sound. Pitch of 100 +// is no pitch shift. Pitch > 100 up to 255 is a higher pitch, pitch < 100 +// down to 1 is a lower pitch. 150 to 70 is the realistic range. +// EMIT_SOUND_DYN with pitch != 100 should be used sparingly, as it's not quite as +// fast as EMIT_SOUND (the pitchshift mixer is not native coded). + +void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volume, float attenuation, + int flags, int pitch); + + +inline void EMIT_SOUND(edict_t *entity, int channel, const char *sample, float volume, float attenuation) +{ + EMIT_SOUND_DYN(entity, channel, sample, volume, attenuation, 0, PITCH_NORM); +} + +inline void STOP_SOUND(edict_t *entity, int channel, const char *sample) +{ + EMIT_SOUND_DYN(entity, channel, sample, 0, 0, SND_STOP, PITCH_NORM); +} + +void EMIT_SOUND_SUIT(edict_t *entity, const char *sample); +void EMIT_GROUPID_SUIT(edict_t *entity, int isentenceg); +void EMIT_GROUPNAME_SUIT(edict_t *entity, const char *groupname); + +#define PRECACHE_SOUND_ARRAY( a ) \ + { for (int i = 0; i < ARRAYSIZE( a ); i++ ) PRECACHE_SOUND((char *) a [i]); } + +#define EMIT_SOUND_ARRAY_DYN( chan, array ) \ + EMIT_SOUND_DYN ( ENT(pev), chan , array [ RANDOM_LONG(0,ARRAYSIZE( array )-1) ], 1.0, ATTN_NORM, 0, RANDOM_LONG(95,105) ); + +#define RANDOM_SOUND_ARRAY( array ) (array) [ RANDOM_LONG(0,ARRAYSIZE( (array) )-1) ] + +#define PLAYBACK_EVENT( flags, who, index ) PLAYBACK_EVENT_FULL( flags, who, index, 0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); +#define PLAYBACK_EVENT_DELAY( flags, who, index, delay ) PLAYBACK_EVENT_FULL( flags, who, index, delay, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); + +#define GROUP_OP_AND 0 +#define GROUP_OP_NAND 1 + +extern int g_groupmask; +extern int g_groupop; + +class UTIL_GroupTrace +{ +public: + UTIL_GroupTrace( int groupmask, int op ); + ~UTIL_GroupTrace( void ); + +private: + int m_oldgroupmask, m_oldgroupop; +}; + +void UTIL_SetGroupTrace( int groupmask, int op ); +void UTIL_UnsetGroupTrace( void ); + +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ); +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ); + +float UTIL_WeaponTimeBase( void ); diff --git a/dlls/vector.h b/dlls/vector.h new file mode 100644 index 0000000..9f475b0 --- /dev/null +++ b/dlls/vector.h @@ -0,0 +1,112 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef VECTOR_H +#define VECTOR_H + +//========================================================= +// 2DVector - used for many pathfinding and many other +// operations that are treated as planar rather than 3d. +//========================================================= +class Vector2D +{ +public: + inline Vector2D(void) { } + inline Vector2D(float X, float Y) { x = X; y = Y; } + inline Vector2D operator+(const Vector2D& v) const { return Vector2D(x+v.x, y+v.y); } + inline Vector2D operator-(const Vector2D& v) const { return Vector2D(x-v.x, y-v.y); } + inline Vector2D operator*(float fl) const { return Vector2D(x*fl, y*fl); } + inline Vector2D operator/(float fl) const { return Vector2D(x/fl, y/fl); } + + inline float Length(void) const { return sqrt(x*x + y*y ); } + + inline Vector2D Normalize ( void ) const + { + Vector2D vec2; + + float flLen = Length(); + if ( flLen == 0 ) + { + return Vector2D( 0, 0 ); + } + else + { + flLen = 1 / flLen; + return Vector2D( x * flLen, y * flLen ); + } + } + + vec_t x, y; +}; + +inline float DotProduct(const Vector2D& a, const Vector2D& b) { return( a.x*b.x + a.y*b.y ); } +inline Vector2D operator*(float fl, const Vector2D& v) { return v * fl; } + +//========================================================= +// 3D Vector +//========================================================= +class Vector // same data-layout as engine's vec3_t, +{ // which is a vec_t[3] +public: + // Construction/destruction + inline Vector(void) { } + inline Vector(float X, float Y, float Z) { x = X; y = Y; z = Z; } + //inline Vector(double X, double Y, double Z) { x = (float)X; y = (float)Y; z = (float)Z; } + //inline Vector(int X, int Y, int Z) { x = (float)X; y = (float)Y; z = (float)Z; } + inline Vector(const Vector& v) { x = v.x; y = v.y; z = v.z; } + inline Vector(float rgfl[3]) { x = rgfl[0]; y = rgfl[1]; z = rgfl[2]; } + + // Operators + inline Vector operator-(void) const { return Vector(-x,-y,-z); } + inline int operator==(const Vector& v) const { return x==v.x && y==v.y && z==v.z; } + inline int operator!=(const Vector& v) const { return !(*this==v); } + inline Vector operator+(const Vector& v) const { return Vector(x+v.x, y+v.y, z+v.z); } + inline Vector operator-(const Vector& v) const { return Vector(x-v.x, y-v.y, z-v.z); } + inline Vector operator*(float fl) const { return Vector(x*fl, y*fl, z*fl); } + inline Vector operator/(float fl) const { return Vector(x/fl, y/fl, z/fl); } + + // Methods + inline void CopyToArray(float* rgfl) const { rgfl[0] = x, rgfl[1] = y, rgfl[2] = z; } + inline float Length(void) const { return sqrt(x*x + y*y + z*z); } + operator float *() { return &x; } // Vectors will now automatically convert to float * when needed + operator const float *() const { return &x; } // Vectors will now automatically convert to float * when needed + inline Vector Normalize(void) const + { + float flLen = Length(); + if (flLen == 0) return Vector(0,0,1); // ???? + flLen = 1 / flLen; + return Vector(x * flLen, y * flLen, z * flLen); + } + + inline Vector2D Make2D ( void ) const + { + Vector2D Vec2; + + Vec2.x = x; + Vec2.y = y; + + return Vec2; + } + inline float Length2D(void) const { return sqrt(x*x + y*y); } + + // Members + vec_t x, y, z; +}; +inline Vector operator*(float fl, const Vector& v) { return v * fl; } +inline float DotProduct(const Vector& a, const Vector& b) { return(a.x*b.x+a.y*b.y+a.z*b.z); } +inline Vector CrossProduct(const Vector& a, const Vector& b) { return Vector( a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x ); } + + + +#endif diff --git a/dlls/weapons.cpp b/dlls/weapons.cpp new file mode 100644 index 0000000..cdee5d7 --- /dev/null +++ b/dlls/weapons.cpp @@ -0,0 +1,1617 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== weapons.cpp ======================================================== + + functions governing the selection/use of weapons for players + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "soundent.h" +#include "decals.h" +#include "gamerules.h" + +extern CGraph WorldGraph; +extern int gEvilImpulse101; + + +#define NOT_USED 255 + +DLL_GLOBAL short g_sModelIndexLaser;// holds the index for the laser beam +DLL_GLOBAL const char *g_pModelNameLaser = "sprites/laserbeam.spr"; +DLL_GLOBAL short g_sModelIndexLaserDot;// holds the index for the laser beam dot +DLL_GLOBAL short g_sModelIndexFireball;// holds the index for the fireball +DLL_GLOBAL short g_sModelIndexSmoke;// holds the index for the smoke cloud +DLL_GLOBAL short g_sModelIndexWExplosion;// holds the index for the underwater explosion +DLL_GLOBAL short g_sModelIndexBubbles;// holds the index for the bubbles model +DLL_GLOBAL short g_sModelIndexBloodDrop;// holds the sprite index for the initial blood +DLL_GLOBAL short g_sModelIndexBloodSpray;// holds the sprite index for splattered blood + +ItemInfo CBasePlayerItem::ItemInfoArray[MAX_WEAPONS]; +AmmoInfo CBasePlayerItem::AmmoInfoArray[MAX_AMMO_SLOTS]; + +extern int gmsgCurWeapon; + +MULTIDAMAGE gMultiDamage; + +#define TRACER_FREQ 4 // Tracers fire every fourth bullet + + +//========================================================= +// MaxAmmoCarry - pass in a name and this function will tell +// you the maximum amount of that type of ammunition that a +// player can carry. +//========================================================= +int MaxAmmoCarry( int iszName ) +{ + for ( int i = 0; i < MAX_WEAPONS; i++ ) + { + if ( CBasePlayerItem::ItemInfoArray[i].pszAmmo1 && !strcmp( STRING(iszName), CBasePlayerItem::ItemInfoArray[i].pszAmmo1 ) ) + return CBasePlayerItem::ItemInfoArray[i].iMaxAmmo1; + if ( CBasePlayerItem::ItemInfoArray[i].pszAmmo2 && !strcmp( STRING(iszName), CBasePlayerItem::ItemInfoArray[i].pszAmmo2 ) ) + return CBasePlayerItem::ItemInfoArray[i].iMaxAmmo2; + } + + ALERT( at_console, "MaxAmmoCarry() doesn't recognize '%s'!\n", STRING( iszName ) ); + return -1; +} + + +/* +============================================================================== + +MULTI-DAMAGE + +Collects multiple small damages into a single damage + +============================================================================== +*/ + +// +// ClearMultiDamage - resets the global multi damage accumulator +// +void ClearMultiDamage(void) +{ + gMultiDamage.pEntity = NULL; + gMultiDamage.amount = 0; + gMultiDamage.type = 0; +} + + +// +// ApplyMultiDamage - inflicts contents of global multi damage register on gMultiDamage.pEntity +// +// GLOBALS USED: +// gMultiDamage + +void ApplyMultiDamage(entvars_t *pevInflictor, entvars_t *pevAttacker ) +{ + Vector vecSpot1;//where blood comes from + Vector vecDir;//direction blood should go + TraceResult tr; + + if ( !gMultiDamage.pEntity ) + return; + + gMultiDamage.pEntity->TakeDamage(pevInflictor, pevAttacker, gMultiDamage.amount, gMultiDamage.type ); +} + + +// GLOBALS USED: +// gMultiDamage + +void AddMultiDamage( entvars_t *pevInflictor, CBaseEntity *pEntity, float flDamage, int bitsDamageType) +{ + if ( !pEntity ) + return; + + gMultiDamage.type |= bitsDamageType; + + if ( pEntity != gMultiDamage.pEntity ) + { + ApplyMultiDamage(pevInflictor,pevInflictor); // UNDONE: wrong attacker! + gMultiDamage.pEntity = pEntity; + gMultiDamage.amount = 0; + } + + gMultiDamage.amount += flDamage; +} + +/* +================ +SpawnBlood +================ +*/ +void SpawnBlood(Vector vecSpot, int bloodColor, float flDamage) +{ + UTIL_BloodDrips( vecSpot, g_vecAttackDir, bloodColor, (int)flDamage ); +} + + +int DamageDecal( CBaseEntity *pEntity, int bitsDamageType ) +{ + if ( !pEntity ) + return (DECAL_GUNSHOT1 + RANDOM_LONG(0,4)); + + return pEntity->DamageDecal( bitsDamageType ); +} + +void DecalGunshot( TraceResult *pTrace, int iBulletType ) +{ + // Is the entity valid + if ( !UTIL_IsValidEntity( pTrace->pHit ) ) + return; + + if ( VARS(pTrace->pHit)->solid == SOLID_BSP || VARS(pTrace->pHit)->movetype == MOVETYPE_PUSHSTEP ) + { + CBaseEntity *pEntity = NULL; + // Decal the wall with a gunshot + if ( !FNullEnt(pTrace->pHit) ) + pEntity = CBaseEntity::Instance(pTrace->pHit); + + switch( iBulletType ) + { + case BULLET_PLAYER_9MM: + case BULLET_MONSTER_9MM: + case BULLET_PLAYER_MP5: + case BULLET_MONSTER_MP5: + case BULLET_PLAYER_BUCKSHOT: + case BULLET_PLAYER_357: + default: + // smoke and decal + UTIL_GunshotDecalTrace( pTrace, DamageDecal( pEntity, DMG_BULLET ) ); + break; + case BULLET_MONSTER_12MM: + // smoke and decal + UTIL_GunshotDecalTrace( pTrace, DamageDecal( pEntity, DMG_BULLET ) ); + break; + case BULLET_PLAYER_CROWBAR: + // wall decal + UTIL_DecalTrace( pTrace, DamageDecal( pEntity, DMG_CLUB ) ); + break; + } + } +} + + + +// +// EjectBrass - tosses a brass shell from passed origin at passed velocity +// +void EjectBrass ( const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int model, int soundtype ) +{ + // FIX: when the player shoots, their gun isn't in the same position as it is on the model other players see. + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecOrigin ); + WRITE_BYTE( TE_MODEL); + WRITE_COORD( vecOrigin.x); + WRITE_COORD( vecOrigin.y); + WRITE_COORD( vecOrigin.z); + WRITE_COORD( vecVelocity.x); + WRITE_COORD( vecVelocity.y); + WRITE_COORD( vecVelocity.z); + WRITE_ANGLE( rotation ); + WRITE_SHORT( model ); + WRITE_BYTE ( soundtype); + WRITE_BYTE ( 25 );// 2.5 seconds + MESSAGE_END(); +} + + +#if 0 +// UNDONE: This is no longer used? +void ExplodeModel( const Vector &vecOrigin, float speed, int model, int count ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecOrigin ); + WRITE_BYTE ( TE_EXPLODEMODEL ); + WRITE_COORD( vecOrigin.x ); + WRITE_COORD( vecOrigin.y ); + WRITE_COORD( vecOrigin.z ); + WRITE_COORD( speed ); + WRITE_SHORT( model ); + WRITE_SHORT( count ); + WRITE_BYTE ( 15 );// 1.5 seconds + MESSAGE_END(); +} +#endif + + +int giAmmoIndex = 0; + +// Precaches the ammo and queues the ammo info for sending to clients +void AddAmmoNameToAmmoRegistry( const char *szAmmoname ) +{ + // make sure it's not already in the registry + for ( int i = 0; i < MAX_AMMO_SLOTS; i++ ) + { + if ( !CBasePlayerItem::AmmoInfoArray[i].pszName) + continue; + + if ( stricmp( CBasePlayerItem::AmmoInfoArray[i].pszName, szAmmoname ) == 0 ) + return; // ammo already in registry, just quite + } + + + giAmmoIndex++; + ASSERT( giAmmoIndex < MAX_AMMO_SLOTS ); + if ( giAmmoIndex >= MAX_AMMO_SLOTS ) + giAmmoIndex = 0; + + CBasePlayerItem::AmmoInfoArray[giAmmoIndex].pszName = szAmmoname; + CBasePlayerItem::AmmoInfoArray[giAmmoIndex].iId = giAmmoIndex; // yes, this info is redundant +} + + +// Precaches the weapon and queues the weapon info for sending to clients +void UTIL_PrecacheOtherWeapon( const char *szClassname ) +{ + edict_t *pent; + + pent = CREATE_NAMED_ENTITY( MAKE_STRING( szClassname ) ); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in UTIL_PrecacheOtherWeapon\n" ); + return; + } + + CBaseEntity *pEntity = CBaseEntity::Instance (VARS( pent )); + + if (pEntity) + { + ItemInfo II; + pEntity->Precache( ); + memset( &II, 0, sizeof II ); + if ( ((CBasePlayerItem*)pEntity)->GetItemInfo( &II ) ) + { + CBasePlayerItem::ItemInfoArray[II.iId] = II; + + if ( II.pszAmmo1 && *II.pszAmmo1 ) + { + AddAmmoNameToAmmoRegistry( II.pszAmmo1 ); + } + + if ( II.pszAmmo2 && *II.pszAmmo2 ) + { + AddAmmoNameToAmmoRegistry( II.pszAmmo2 ); + } + + memset( &II, 0, sizeof II ); + } + } + + REMOVE_ENTITY(pent); +} + +// called by worldspawn +void W_Precache(void) +{ + memset( CBasePlayerItem::ItemInfoArray, 0, sizeof(CBasePlayerItem::ItemInfoArray) ); + memset( CBasePlayerItem::AmmoInfoArray, 0, sizeof(CBasePlayerItem::AmmoInfoArray) ); + giAmmoIndex = 0; + + // custom items... + + // common world objects + UTIL_PrecacheOther( "item_suit" ); + UTIL_PrecacheOther( "item_battery" ); + UTIL_PrecacheOther( "item_antidote" ); + UTIL_PrecacheOther( "item_security" ); + UTIL_PrecacheOther( "item_longjump" ); + + // shotgun + UTIL_PrecacheOtherWeapon( "weapon_shotgun" ); + UTIL_PrecacheOther( "ammo_buckshot" ); + + // crowbar + UTIL_PrecacheOtherWeapon( "weapon_crowbar" ); + + // glock + UTIL_PrecacheOtherWeapon( "weapon_9mmhandgun" ); + UTIL_PrecacheOther( "ammo_9mmclip" ); + + // mp5 + UTIL_PrecacheOtherWeapon( "weapon_9mmAR" ); + UTIL_PrecacheOther( "ammo_9mmAR" ); + UTIL_PrecacheOther( "ammo_ARgrenades" ); + +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + // python + UTIL_PrecacheOtherWeapon( "weapon_357" ); + UTIL_PrecacheOther( "ammo_357" ); +#endif + +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + // gauss + UTIL_PrecacheOtherWeapon( "weapon_gauss" ); + UTIL_PrecacheOther( "ammo_gaussclip" ); +#endif + +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + // rpg + UTIL_PrecacheOtherWeapon( "weapon_rpg" ); + UTIL_PrecacheOther( "ammo_rpgclip" ); +#endif + +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + // crossbow + UTIL_PrecacheOtherWeapon( "weapon_crossbow" ); + UTIL_PrecacheOther( "ammo_crossbow" ); +#endif + +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + // egon + UTIL_PrecacheOtherWeapon( "weapon_egon" ); +#endif + + // tripmine + UTIL_PrecacheOtherWeapon( "weapon_tripmine" ); + +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + // satchel charge + UTIL_PrecacheOtherWeapon( "weapon_satchel" ); +#endif + + // hand grenade + UTIL_PrecacheOtherWeapon("weapon_handgrenade"); + +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + // squeak grenade + UTIL_PrecacheOtherWeapon( "weapon_snark" ); +#endif + +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + // hornetgun + UTIL_PrecacheOtherWeapon( "weapon_hornetgun" ); +#endif + + +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + if ( g_pGameRules->IsDeathmatch() ) + { + UTIL_PrecacheOther( "weaponbox" );// container for dropped deathmatch weapons + } +#endif + + g_sModelIndexFireball = PRECACHE_MODEL ("sprites/zerogxplode.spr");// fireball + g_sModelIndexWExplosion = PRECACHE_MODEL ("sprites/WXplo1.spr");// underwater fireball + g_sModelIndexSmoke = PRECACHE_MODEL ("sprites/steam1.spr");// smoke + g_sModelIndexBubbles = PRECACHE_MODEL ("sprites/bubble.spr");//bubbles + g_sModelIndexBloodSpray = PRECACHE_MODEL ("sprites/bloodspray.spr"); // initial blood + g_sModelIndexBloodDrop = PRECACHE_MODEL ("sprites/blood.spr"); // splattered blood + + g_sModelIndexLaser = PRECACHE_MODEL( (char *)g_pModelNameLaser ); + g_sModelIndexLaserDot = PRECACHE_MODEL("sprites/laserdot.spr"); + + + // used by explosions + PRECACHE_MODEL ("models/grenade.mdl"); + PRECACHE_MODEL ("sprites/explode1.spr"); + + PRECACHE_SOUND ("weapons/debris1.wav");// explosion aftermaths + PRECACHE_SOUND ("weapons/debris2.wav");// explosion aftermaths + PRECACHE_SOUND ("weapons/debris3.wav");// explosion aftermaths + + PRECACHE_SOUND ("weapons/grenade_hit1.wav");//grenade + PRECACHE_SOUND ("weapons/grenade_hit2.wav");//grenade + PRECACHE_SOUND ("weapons/grenade_hit3.wav");//grenade + + PRECACHE_SOUND ("weapons/bullet_hit1.wav"); // hit by bullet + PRECACHE_SOUND ("weapons/bullet_hit2.wav"); // hit by bullet + + PRECACHE_SOUND ("items/weapondrop1.wav");// weapon falls to the ground + +} + + + + +TYPEDESCRIPTION CBasePlayerItem::m_SaveData[] = +{ + DEFINE_FIELD( CBasePlayerItem, m_pPlayer, FIELD_CLASSPTR ), + DEFINE_FIELD( CBasePlayerItem, m_pNext, FIELD_CLASSPTR ), + //DEFINE_FIELD( CBasePlayerItem, m_fKnown, FIELD_INTEGER ),Reset to zero on load + DEFINE_FIELD( CBasePlayerItem, m_iId, FIELD_INTEGER ), + // DEFINE_FIELD( CBasePlayerItem, m_iIdPrimary, FIELD_INTEGER ), + // DEFINE_FIELD( CBasePlayerItem, m_iIdSecondary, FIELD_INTEGER ), +}; +IMPLEMENT_SAVERESTORE( CBasePlayerItem, CBaseAnimating ); + + +TYPEDESCRIPTION CBasePlayerWeapon::m_SaveData[] = +{ +#if defined( CLIENT_WEAPONS ) + DEFINE_FIELD( CBasePlayerWeapon, m_flNextPrimaryAttack, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayerWeapon, m_flNextSecondaryAttack, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayerWeapon, m_flTimeWeaponIdle, FIELD_FLOAT ), +#else // CLIENT_WEAPONS + DEFINE_FIELD( CBasePlayerWeapon, m_flNextPrimaryAttack, FIELD_TIME ), + DEFINE_FIELD( CBasePlayerWeapon, m_flNextSecondaryAttack, FIELD_TIME ), + DEFINE_FIELD( CBasePlayerWeapon, m_flTimeWeaponIdle, FIELD_TIME ), +#endif // CLIENT_WEAPONS + DEFINE_FIELD( CBasePlayerWeapon, m_iPrimaryAmmoType, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerWeapon, m_iSecondaryAmmoType, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerWeapon, m_iClip, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerWeapon, m_iDefaultAmmo, FIELD_INTEGER ), +// DEFINE_FIELD( CBasePlayerWeapon, m_iClientClip, FIELD_INTEGER ) , reset to zero on load so hud gets updated correctly +// DEFINE_FIELD( CBasePlayerWeapon, m_iClientWeaponState, FIELD_INTEGER ), reset to zero on load so hud gets updated correctly +}; + +IMPLEMENT_SAVERESTORE( CBasePlayerWeapon, CBasePlayerItem ); + + +void CBasePlayerItem :: SetObjectCollisionBox( void ) +{ + pev->absmin = pev->origin + Vector(-24, -24, 0); + pev->absmax = pev->origin + Vector(24, 24, 16); +} + + +//========================================================= +// Sets up movetype, size, solidtype for a new weapon. +//========================================================= +void CBasePlayerItem :: FallInit( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_BBOX; + + UTIL_SetOrigin( pev, pev->origin ); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0) );//pointsize until it lands on the ground. + + SetTouch( &CBasePlayerItem::DefaultTouch ); + SetThink( &CBasePlayerItem::FallThink ); + + pev->nextthink = gpGlobals->time + 0.1; +} + +//========================================================= +// FallThink - Items that have just spawned run this think +// to catch them when they hit the ground. Once we're sure +// that the object is grounded, we change its solid type +// to trigger and set it in a large box that helps the +// player get it. +//========================================================= +void CBasePlayerItem::FallThink ( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if ( pev->flags & FL_ONGROUND ) + { + // clatter if we have an owner (i.e., dropped by someone) + // don't clatter if the gun is waiting to respawn (if it's waiting, it is invisible!) + if ( !FNullEnt( pev->owner ) ) + { + int pitch = 95 + RANDOM_LONG(0,29); + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "items/weapondrop1.wav", 1, ATTN_NORM, 0, pitch); + } + + // lie flat + pev->angles.x = 0; + pev->angles.z = 0; + + Materialize(); + } +} + +//========================================================= +// Materialize - make a CBasePlayerItem visible and tangible +//========================================================= +void CBasePlayerItem::Materialize( void ) +{ + if ( pev->effects & EF_NODRAW ) + { + // changing from invisible state to visible. + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "items/suitchargeok1.wav", 1, ATTN_NORM, 0, 150 ); + pev->effects &= ~EF_NODRAW; + pev->effects |= EF_MUZZLEFLASH; + } + + pev->solid = SOLID_TRIGGER; + + UTIL_SetOrigin( pev, pev->origin );// link into world. + SetTouch (&CBasePlayerItem::DefaultTouch); + SetThink (NULL); + +} + +//========================================================= +// AttemptToMaterialize - the item is trying to rematerialize, +// should it do so now or wait longer? +//========================================================= +void CBasePlayerItem::AttemptToMaterialize( void ) +{ + float time = g_pGameRules->FlWeaponTryRespawn( this ); + + if ( time == 0 ) + { + Materialize(); + return; + } + + pev->nextthink = gpGlobals->time + time; +} + +//========================================================= +// CheckRespawn - a player is taking this weapon, should +// it respawn? +//========================================================= +void CBasePlayerItem :: CheckRespawn ( void ) +{ + switch ( g_pGameRules->WeaponShouldRespawn( this ) ) + { + case GR_WEAPON_RESPAWN_YES: + Respawn(); + break; + case GR_WEAPON_RESPAWN_NO: + return; + break; + } +} + +//========================================================= +// Respawn- this item is already in the world, but it is +// invisible and intangible. Make it visible and tangible. +//========================================================= +CBaseEntity* CBasePlayerItem::Respawn( void ) +{ + // make a copy of this weapon that is invisible and inaccessible to players (no touch function). The weapon spawn/respawn code + // will decide when to make the weapon visible and touchable. + CBaseEntity *pNewWeapon = CBaseEntity::Create( (char *)STRING( pev->classname ), g_pGameRules->VecWeaponRespawnSpot( this ), pev->angles, pev->owner ); + + if ( pNewWeapon ) + { + pNewWeapon->pev->effects |= EF_NODRAW;// invisible for now + pNewWeapon->SetTouch( NULL );// no touch + pNewWeapon->SetThink( &CBasePlayerItem::AttemptToMaterialize ); + + DROP_TO_FLOOR ( ENT(pev) ); + + // not a typo! We want to know when the weapon the player just picked up should respawn! This new entity we created is the replacement, + // but when it should respawn is based on conditions belonging to the weapon that was taken. + pNewWeapon->pev->nextthink = g_pGameRules->FlWeaponRespawnTime( this ); + } + else + { + ALERT ( at_console, "Respawn failed to create %s!\n", STRING( pev->classname ) ); + } + + return pNewWeapon; +} + +void CBasePlayerItem::DefaultTouch( CBaseEntity *pOther ) +{ + // if it's not a player, ignore + if ( !pOther->IsPlayer() ) + return; + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + + // can I have this? + if ( !g_pGameRules->CanHavePlayerItem( pPlayer, this ) ) + { + if ( gEvilImpulse101 ) + { + UTIL_Remove( this ); + } + return; + } + + if (pOther->AddPlayerItem( this )) + { + AttachToPlayer( pPlayer ); + EMIT_SOUND(ENT(pPlayer->pev), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM); + } + + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); // UNDONE: when should this happen? +} + +BOOL CanAttack( float attack_time, float curtime, BOOL isPredicted ) +{ +#if defined( CLIENT_WEAPONS ) + if ( !isPredicted ) +#else + if ( 1 ) +#endif + { + return ( attack_time <= curtime ) ? TRUE : FALSE; + } + else + { + return ( attack_time <= 0.0 ) ? TRUE : FALSE; + } +} + +void CBasePlayerWeapon::ItemPostFrame( void ) +{ + if ((m_fInReload) && ( m_pPlayer->m_flNextAttack <= UTIL_WeaponTimeBase() ) ) + { + // complete the reload. + int j = min( iMaxClip() - m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); + + // Add them to the clip + m_iClip += j; + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= j; + + m_pPlayer->TabulateAmmo(); + + m_fInReload = FALSE; + } + + if ( !(m_pPlayer->pev->button & IN_ATTACK ) ) + { + m_flLastFireTime = 0.0f; + } + + if ((m_pPlayer->pev->button & IN_ATTACK2) && CanAttack( m_flNextSecondaryAttack, gpGlobals->time, UseDecrement() ) ) + { + if ( pszAmmo2() && !m_pPlayer->m_rgAmmo[SecondaryAmmoIndex()] ) + { + m_fFireOnEmpty = TRUE; + } + + m_pPlayer->TabulateAmmo(); + SecondaryAttack(); + m_pPlayer->pev->button &= ~IN_ATTACK2; + } + else if ((m_pPlayer->pev->button & IN_ATTACK) && CanAttack( m_flNextPrimaryAttack, gpGlobals->time, UseDecrement() ) ) + { + if ( (m_iClip == 0 && pszAmmo1()) || (iMaxClip() == -1 && !m_pPlayer->m_rgAmmo[PrimaryAmmoIndex()] ) ) + { + m_fFireOnEmpty = TRUE; + } + + m_pPlayer->TabulateAmmo(); + PrimaryAttack(); + } + else if ( m_pPlayer->pev->button & IN_RELOAD && iMaxClip() != WEAPON_NOCLIP && !m_fInReload ) + { + // reload when reload is pressed, or if no buttons are down and weapon is empty. + Reload(); + } + else if ( !(m_pPlayer->pev->button & (IN_ATTACK|IN_ATTACK2) ) ) + { + // no fire buttons down + + m_fFireOnEmpty = FALSE; + + if ( !IsUseable() && m_flNextPrimaryAttack < ( UseDecrement() ? 0.0 : gpGlobals->time ) ) + { + // weapon isn't useable, switch. + if ( !(iFlags() & ITEM_FLAG_NOAUTOSWITCHEMPTY) && g_pGameRules->GetNextBestWeapon( m_pPlayer, this ) ) + { + m_flNextPrimaryAttack = ( UseDecrement() ? 0.0 : gpGlobals->time ) + 0.3; + return; + } + } + else + { + // weapon is useable. Reload if empty and weapon has waited as long as it has to after firing + if ( m_iClip == 0 && !(iFlags() & ITEM_FLAG_NOAUTORELOAD) && m_flNextPrimaryAttack < ( UseDecrement() ? 0.0 : gpGlobals->time ) ) + { + Reload(); + return; + } + } + + WeaponIdle( ); + return; + } + + // catch all + if ( ShouldWeaponIdle() ) + { + WeaponIdle(); + } +} + +void CBasePlayerItem::DestroyItem( void ) +{ + if ( m_pPlayer ) + { + // if attached to a player, remove. + m_pPlayer->RemovePlayerItem( this ); + } + + Kill( ); +} + +int CBasePlayerItem::AddToPlayer( CBasePlayer *pPlayer ) +{ + m_pPlayer = pPlayer; + + return TRUE; +} + +void CBasePlayerItem::Drop( void ) +{ + SetTouch( NULL ); + SetThink(&CBasePlayerItem::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; +} + +void CBasePlayerItem::Kill( void ) +{ + SetTouch( NULL ); + SetThink(&CBasePlayerItem::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; +} + +void CBasePlayerItem::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->pev->viewmodel = 0; + m_pPlayer->pev->weaponmodel = 0; +} + +void CBasePlayerItem::AttachToPlayer ( CBasePlayer *pPlayer ) +{ + pev->movetype = MOVETYPE_FOLLOW; + pev->solid = SOLID_NOT; + pev->aiment = pPlayer->edict(); + pev->effects = EF_NODRAW; // ?? + pev->modelindex = 0;// server won't send down to clients if modelindex == 0 + pev->model = iStringNull; + pev->owner = pPlayer->edict(); + pev->nextthink = gpGlobals->time + .1; + SetTouch( NULL ); +} + +// CALLED THROUGH the newly-touched weapon's instance. The existing player weapon is pOriginal +int CBasePlayerWeapon::AddDuplicate( CBasePlayerItem *pOriginal ) +{ + if ( m_iDefaultAmmo ) + { + return ExtractAmmo( (CBasePlayerWeapon *)pOriginal ); + } + else + { + // a dead player dropped this. + return ExtractClipAmmo( (CBasePlayerWeapon *)pOriginal ); + } +} + + +int CBasePlayerWeapon::AddToPlayer( CBasePlayer *pPlayer ) +{ + int bResult = CBasePlayerItem::AddToPlayer( pPlayer ); + + pPlayer->pev->weapons |= (1<GetAmmoIndex( pszAmmo1() ); + m_iSecondaryAmmoType = pPlayer->GetAmmoIndex( pszAmmo2() ); + } + + + if (bResult) + return AddWeapon( ); + return FALSE; +} + +int CBasePlayerWeapon::UpdateClientData( CBasePlayer *pPlayer ) +{ + BOOL bSend = FALSE; + int state = 0; + if ( pPlayer->m_pActiveItem == this ) + { + if ( pPlayer->m_fOnTarget ) + state = WEAPON_IS_ONTARGET; + else + state = 1; + } + + // Forcing send of all data! + if ( !pPlayer->m_fWeapon ) + { + bSend = TRUE; + } + + // This is the current or last weapon, so the state will need to be updated + if ( this == pPlayer->m_pActiveItem || + this == pPlayer->m_pClientActiveItem ) + { + if ( pPlayer->m_pActiveItem != pPlayer->m_pClientActiveItem ) + { + bSend = TRUE; + } + } + + // If the ammo, state, or fov has changed, update the weapon + if ( m_iClip != m_iClientClip || + state != m_iClientWeaponState || + pPlayer->m_iFOV != pPlayer->m_iClientFOV ) + { + bSend = TRUE; + } + + if ( bSend ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pPlayer->pev ); + WRITE_BYTE( state ); + WRITE_BYTE( m_iId ); + WRITE_BYTE( m_iClip ); + MESSAGE_END(); + + m_iClientClip = m_iClip; + m_iClientWeaponState = state; + pPlayer->m_fWeapon = TRUE; + } + + if ( m_pNext ) + m_pNext->UpdateClientData( pPlayer ); + + return 1; +} + + +void CBasePlayerWeapon::SendWeaponAnim( int iAnim, int skiplocal, int body ) +{ + if ( UseDecrement() ) + skiplocal = 1; + else + skiplocal = 0; + + m_pPlayer->pev->weaponanim = iAnim; + +#if defined( CLIENT_WEAPONS ) + if ( skiplocal && ENGINE_CANSKIP( m_pPlayer->edict() ) ) + return; +#endif + + MESSAGE_BEGIN( MSG_ONE, SVC_WEAPONANIM, NULL, m_pPlayer->pev ); + WRITE_BYTE( iAnim ); // sequence number + WRITE_BYTE( pev->body ); // weaponmodel bodygroup. + MESSAGE_END(); +} + +BOOL CBasePlayerWeapon :: AddPrimaryAmmo( int iCount, char *szName, int iMaxClip, int iMaxCarry ) +{ + int iIdAmmo; + + if (iMaxClip < 1) + { + m_iClip = -1; + iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName, iMaxCarry ); + } + else if (m_iClip == 0) + { + int i; + i = min( m_iClip + iCount, iMaxClip ) - m_iClip; + m_iClip += i; + iIdAmmo = m_pPlayer->GiveAmmo( iCount - i, szName, iMaxCarry ); + } + else + { + iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName, iMaxCarry ); + } + + // m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] = iMaxCarry; // hack for testing + + if (iIdAmmo > 0) + { + m_iPrimaryAmmoType = iIdAmmo; + if (m_pPlayer->HasPlayerItem( this ) ) + { + // play the "got ammo" sound only if we gave some ammo to a player that already had this gun. + // if the player is just getting this gun for the first time, DefaultTouch will play the "picked up gun" sound for us. + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + } + } + + return iIdAmmo > 0 ? TRUE : FALSE; +} + + +BOOL CBasePlayerWeapon :: AddSecondaryAmmo( int iCount, char *szName, int iMax ) +{ + int iIdAmmo; + + iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName, iMax ); + + //m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] = iMax; // hack for testing + + if (iIdAmmo > 0) + { + m_iSecondaryAmmoType = iIdAmmo; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + } + return iIdAmmo > 0 ? TRUE : FALSE; +} + +//========================================================= +// IsUseable - this function determines whether or not a +// weapon is useable by the player in its current state. +// (does it have ammo loaded? do I have any ammo for the +// weapon?, etc) +//========================================================= +BOOL CBasePlayerWeapon :: IsUseable( void ) +{ + if ( m_iClip <= 0 ) + { + if ( m_pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] <= 0 && iMaxAmmo1() != -1 ) + { + // clip is empty (or nonexistant) and the player has no more ammo of this type. + return FALSE; + } + } + + return TRUE; +} + +BOOL CBasePlayerWeapon :: CanDeploy( void ) +{ + BOOL bHasAmmo = 0; + + if ( !pszAmmo1() ) + { + // this weapon doesn't use ammo, can always deploy. + return TRUE; + } + + if ( pszAmmo1() ) + { + bHasAmmo |= (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] != 0); + } + if ( pszAmmo2() ) + { + bHasAmmo |= (m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] != 0); + } + if (m_iClip > 0) + { + bHasAmmo |= 1; + } + if (!bHasAmmo) + { + return FALSE; + } + + return TRUE; +} + +BOOL CBasePlayerWeapon :: DefaultDeploy( char *szViewModel, char *szWeaponModel, int iAnim, char *szAnimExt, int skiplocal /* = 0 */, int body ) +{ + if (!CanDeploy( )) + return FALSE; + + m_pPlayer->TabulateAmmo(); + m_pPlayer->pev->viewmodel = MAKE_STRING(szViewModel); + m_pPlayer->pev->weaponmodel = MAKE_STRING(szWeaponModel); + strcpy( m_pPlayer->m_szAnimExtention, szAnimExt ); + SendWeaponAnim( iAnim, skiplocal, body ); + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.0; + m_flLastFireTime = 0.0; + + return TRUE; +} + + +BOOL CBasePlayerWeapon :: DefaultReload( int iClipSize, int iAnim, float fDelay, int body ) +{ + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + return FALSE; + + int j = min(iClipSize - m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); + + if (j == 0) + return FALSE; + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + fDelay; + + //!!UNDONE -- reload sound goes here !!! + SendWeaponAnim( iAnim, UseDecrement() ? 1 : 0 ); + + m_fInReload = TRUE; + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 3; + return TRUE; +} + +BOOL CBasePlayerWeapon :: PlayEmptySound( void ) +{ + if (m_iPlayEmptySound) + { + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/357_cock1.wav", 0.8, ATTN_NORM); + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +void CBasePlayerWeapon :: ResetEmptySound( void ) +{ + m_iPlayEmptySound = 1; +} + +//========================================================= +//========================================================= +int CBasePlayerWeapon::PrimaryAmmoIndex( void ) +{ + return m_iPrimaryAmmoType; +} + +//========================================================= +//========================================================= +int CBasePlayerWeapon::SecondaryAmmoIndex( void ) +{ + return -1; +} + +void CBasePlayerWeapon::Holster( int skiplocal /* = 0 */ ) +{ + m_fInReload = FALSE; // cancel any reload in progress. + m_pPlayer->pev->viewmodel = 0; + m_pPlayer->pev->weaponmodel = 0; +} + +void CBasePlayerAmmo::Spawn( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 16)); + UTIL_SetOrigin( pev, pev->origin ); + + SetTouch( &CBasePlayerAmmo::DefaultTouch ); +} + +CBaseEntity* CBasePlayerAmmo::Respawn( void ) +{ + pev->effects |= EF_NODRAW; + SetTouch( NULL ); + + UTIL_SetOrigin( pev, g_pGameRules->VecAmmoRespawnSpot( this ) );// move to wherever I'm supposed to repawn. + + SetThink( &CBasePlayerAmmo::Materialize ); + pev->nextthink = g_pGameRules->FlAmmoRespawnTime( this ); + + return this; +} + +void CBasePlayerAmmo::Materialize( void ) +{ + if ( pev->effects & EF_NODRAW ) + { + // changing from invisible state to visible. + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "items/suitchargeok1.wav", 1, ATTN_NORM, 0, 150 ); + pev->effects &= ~EF_NODRAW; + pev->effects |= EF_MUZZLEFLASH; + } + + SetTouch( &CBasePlayerAmmo::DefaultTouch ); +} + +void CBasePlayerAmmo :: DefaultTouch( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + { + return; + } + + if (AddAmmo( pOther )) + { + if ( g_pGameRules->AmmoShouldRespawn( this ) == GR_AMMO_RESPAWN_YES ) + { + Respawn(); + } + else + { + SetTouch( NULL ); + SetThink(&CBasePlayerAmmo::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; + } + } + else if (gEvilImpulse101) + { + // evil impulse 101 hack, kill always + SetTouch( NULL ); + SetThink(&CBasePlayerAmmo::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; + } +} + +//========================================================= +// called by the new item with the existing item as parameter +// +// if we call ExtractAmmo(), it's because the player is picking up this type of weapon for +// the first time. If it is spawned by the world, m_iDefaultAmmo will have a default ammo amount in it. +// if this is a weapon dropped by a dying player, has 0 m_iDefaultAmmo, which means only the ammo in +// the weapon clip comes along. +//========================================================= +int CBasePlayerWeapon::ExtractAmmo( CBasePlayerWeapon *pWeapon ) +{ + int iReturn; + + if ( pszAmmo1() != NULL ) + { + // blindly call with m_iDefaultAmmo. It's either going to be a value or zero. If it is zero, + // we only get the ammo in the weapon's clip, which is what we want. + iReturn = pWeapon->AddPrimaryAmmo( m_iDefaultAmmo, (char *)pszAmmo1(), iMaxClip(), iMaxAmmo1() ); + m_iDefaultAmmo = 0; + } + + if ( pszAmmo2() != NULL ) + { + iReturn = pWeapon->AddSecondaryAmmo( 0, (char *)pszAmmo2(), iMaxAmmo2() ); + } + + return iReturn; +} + +//========================================================= +// called by the new item's class with the existing item as parameter +//========================================================= +int CBasePlayerWeapon::ExtractClipAmmo( CBasePlayerWeapon *pWeapon ) +{ + int iAmmo; + + if ( m_iClip == WEAPON_NOCLIP ) + { + iAmmo = 0;// guns with no clips always come empty if they are second-hand + } + else + { + iAmmo = m_iClip; + } + + return pWeapon->m_pPlayer->GiveAmmo( iAmmo, (char *)pszAmmo1(), iMaxAmmo1() ); // , &m_iPrimaryAmmoType +} + +//========================================================= +// RetireWeapon - no more ammo for this gun, put it away. +//========================================================= +void CBasePlayerWeapon::RetireWeapon( void ) +{ + // first, no viewmodel at all. + m_pPlayer->pev->viewmodel = iStringNull; + m_pPlayer->pev->weaponmodel = iStringNull; + //m_pPlayer->pev->viewmodelindex = NULL; + + g_pGameRules->GetNextBestWeapon( m_pPlayer, this ); +} + +//========================================================================= +// GetNextAttackDelay - An accurate way of calcualting the next attack time. +//========================================================================= +float CBasePlayerWeapon::GetNextAttackDelay( float delay ) +{ + if(m_flLastFireTime == 0 || m_flNextPrimaryAttack == -1) + { + // At this point, we are assuming that the client has stopped firing + // and we are going to reset our book keeping variables. + m_flLastFireTime = gpGlobals->time; + m_flPrevPrimaryAttack = delay; + } + // calculate the time between this shot and the previous + float flTimeBetweenFires = gpGlobals->time - m_flLastFireTime; + float flCreep = 0.0f; + if(flTimeBetweenFires > 0) + flCreep = flTimeBetweenFires - m_flPrevPrimaryAttack; // postive or negative + + // save the last fire time + m_flLastFireTime = gpGlobals->time; + + float flNextAttack = UTIL_WeaponTimeBase() + delay - flCreep; + // we need to remember what the m_flNextPrimaryAttack time is set to for each shot, + // store it as m_flPrevPrimaryAttack. + m_flPrevPrimaryAttack = flNextAttack - UTIL_WeaponTimeBase(); +// char szMsg[256]; +// _snprintf( szMsg, sizeof(szMsg), "next attack time: %0.4f\n", gpGlobals->time + flNextAttack ); +// OutputDebugString( szMsg ); + return flNextAttack; +} + + +//********************************************************* +// weaponbox code: +//********************************************************* + +LINK_ENTITY_TO_CLASS( weaponbox, CWeaponBox ); + +TYPEDESCRIPTION CWeaponBox::m_SaveData[] = +{ + DEFINE_ARRAY( CWeaponBox, m_rgAmmo, FIELD_INTEGER, MAX_AMMO_SLOTS ), + DEFINE_ARRAY( CWeaponBox, m_rgiszAmmo, FIELD_STRING, MAX_AMMO_SLOTS ), + DEFINE_ARRAY( CWeaponBox, m_rgpPlayerItems, FIELD_CLASSPTR, MAX_ITEM_TYPES ), + DEFINE_FIELD( CWeaponBox, m_cAmmoTypes, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CWeaponBox, CBaseEntity ); + +//========================================================= +// +//========================================================= +void CWeaponBox::Precache( void ) +{ + PRECACHE_MODEL("models/w_weaponbox.mdl"); +} + +//========================================================= +//========================================================= +void CWeaponBox :: KeyValue( KeyValueData *pkvd ) +{ + if ( m_cAmmoTypes < MAX_AMMO_SLOTS ) + { + PackAmmo( ALLOC_STRING(pkvd->szKeyName), atoi(pkvd->szValue) ); + m_cAmmoTypes++;// count this new ammo type. + + pkvd->fHandled = TRUE; + } + else + { + ALERT ( at_console, "WeaponBox too full! only %d ammotypes allowed\n", MAX_AMMO_SLOTS ); + } +} + +//========================================================= +// CWeaponBox - Spawn +//========================================================= +void CWeaponBox::Spawn( void ) +{ + Precache( ); + + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + + UTIL_SetSize( pev, g_vecZero, g_vecZero ); + + SET_MODEL( ENT(pev), "models/w_weaponbox.mdl"); +} + +//========================================================= +// CWeaponBox - Kill - the think function that removes the +// box from the world. +//========================================================= +void CWeaponBox::Kill( void ) +{ + CBasePlayerItem *pWeapon; + int i; + + // destroy the weapons + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pWeapon = m_rgpPlayerItems[ i ]; + + while ( pWeapon ) + { + pWeapon->SetThink(&CBasePlayerItem::SUB_Remove); + pWeapon->pev->nextthink = gpGlobals->time + 0.1; + pWeapon = pWeapon->m_pNext; + } + } + + // remove the box + UTIL_Remove( this ); +} + +//========================================================= +// CWeaponBox - Touch: try to add my contents to the toucher +// if the toucher is a player. +//========================================================= +void CWeaponBox::Touch( CBaseEntity *pOther ) +{ + if ( !(pev->flags & FL_ONGROUND ) ) + { + return; + } + + if ( !pOther->IsPlayer() ) + { + // only players may touch a weaponbox. + return; + } + + if ( !pOther->IsAlive() ) + { + // no dead guys. + return; + } + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + int i; + +// dole out ammo + for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ ) + { + if ( !FStringNull( m_rgiszAmmo[ i ] ) ) + { + // there's some ammo of this type. + pPlayer->GiveAmmo( m_rgAmmo[ i ], (char *)STRING( m_rgiszAmmo[ i ] ), MaxAmmoCarry( m_rgiszAmmo[ i ] ) ); + + //ALERT ( at_console, "Gave %d rounds of %s\n", m_rgAmmo[i], STRING(m_rgiszAmmo[i]) ); + + // now empty the ammo from the weaponbox since we just gave it to the player + m_rgiszAmmo[ i ] = iStringNull; + m_rgAmmo[ i ] = 0; + } + } + +// go through my weapons and try to give the usable ones to the player. +// it's important the the player be given ammo first, so the weapons code doesn't refuse +// to deploy a better weapon that the player may pick up because he has no ammo for it. + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + CBasePlayerItem *pItem; + + // have at least one weapon in this slot + while ( m_rgpPlayerItems[ i ] ) + { + //ALERT ( at_console, "trying to give %s\n", STRING( m_rgpPlayerItems[ i ]->pev->classname ) ); + + pItem = m_rgpPlayerItems[ i ]; + m_rgpPlayerItems[ i ] = m_rgpPlayerItems[ i ]->m_pNext;// unlink this weapon from the box + + if ( pPlayer->AddPlayerItem( pItem ) ) + { + pItem->AttachToPlayer( pPlayer ); + } + } + } + } + + EMIT_SOUND( pOther->edict(), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM ); + SetTouch(NULL); + UTIL_Remove(this); +} + +//========================================================= +// CWeaponBox - PackWeapon: Add this weapon to the box +//========================================================= +BOOL CWeaponBox::PackWeapon( CBasePlayerItem *pWeapon ) +{ + // is one of these weapons already packed in this box? + if ( HasWeapon( pWeapon ) ) + { + return FALSE;// box can only hold one of each weapon type + } + + if ( pWeapon->m_pPlayer ) + { + if ( !pWeapon->m_pPlayer->RemovePlayerItem( pWeapon ) ) + { + // failed to unhook the weapon from the player! + return FALSE; + } + } + + int iWeaponSlot = pWeapon->iItemSlot(); + + if ( m_rgpPlayerItems[ iWeaponSlot ] ) + { + // there's already one weapon in this slot, so link this into the slot's column + pWeapon->m_pNext = m_rgpPlayerItems[ iWeaponSlot ]; + m_rgpPlayerItems[ iWeaponSlot ] = pWeapon; + } + else + { + // first weapon we have for this slot + m_rgpPlayerItems[ iWeaponSlot ] = pWeapon; + pWeapon->m_pNext = NULL; + } + + pWeapon->pev->spawnflags |= SF_NORESPAWN;// never respawn + pWeapon->pev->movetype = MOVETYPE_NONE; + pWeapon->pev->solid = SOLID_NOT; + pWeapon->pev->effects = EF_NODRAW; + pWeapon->pev->modelindex = 0; + pWeapon->pev->model = iStringNull; + pWeapon->pev->owner = edict(); + pWeapon->SetThink( NULL );// crowbar may be trying to swing again, etc. + pWeapon->SetTouch( NULL ); + pWeapon->m_pPlayer = NULL; + + //ALERT ( at_console, "packed %s\n", STRING(pWeapon->pev->classname) ); + + return TRUE; +} + +//========================================================= +// CWeaponBox - PackAmmo +//========================================================= +BOOL CWeaponBox::PackAmmo( int iszName, int iCount ) +{ + int iMaxCarry; + + if ( FStringNull( iszName ) ) + { + // error here + ALERT ( at_console, "NULL String in PackAmmo!\n" ); + return FALSE; + } + + iMaxCarry = MaxAmmoCarry( iszName ); + + if ( iMaxCarry != -1 && iCount > 0 ) + { + //ALERT ( at_console, "Packed %d rounds of %s\n", iCount, STRING(iszName) ); + GiveAmmo( iCount, (char *)STRING( iszName ), iMaxCarry ); + return TRUE; + } + + return FALSE; +} + +//========================================================= +// CWeaponBox - GiveAmmo +//========================================================= +int CWeaponBox::GiveAmmo( int iCount, char *szName, int iMax, int *pIndex/* = NULL*/ ) +{ + int i; + + for (i = 1; i < MAX_AMMO_SLOTS && !FStringNull( m_rgiszAmmo[i] ); i++) + { + if (stricmp( szName, STRING( m_rgiszAmmo[i])) == 0) + { + if (pIndex) + *pIndex = i; + + int iAdd = min( iCount, iMax - m_rgAmmo[i]); + if (iCount == 0 || iAdd > 0) + { + m_rgAmmo[i] += iAdd; + + return i; + } + return -1; + } + } + if (i < MAX_AMMO_SLOTS) + { + if (pIndex) + *pIndex = i; + + m_rgiszAmmo[i] = MAKE_STRING( szName ); + m_rgAmmo[i] = iCount; + + return i; + } + ALERT( at_console, "out of named ammo slots\n"); + return i; +} + +//========================================================= +// CWeaponBox::HasWeapon - is a weapon of this type already +// packed in this box? +//========================================================= +BOOL CWeaponBox::HasWeapon( CBasePlayerItem *pCheckItem ) +{ + CBasePlayerItem *pItem = m_rgpPlayerItems[pCheckItem->iItemSlot()]; + + while (pItem) + { + if (FClassnameIs( pItem->pev, STRING( pCheckItem->pev->classname) )) + { + return TRUE; + } + pItem = pItem->m_pNext; + } + + return FALSE; +} + +//========================================================= +// CWeaponBox::IsEmpty - is there anything in this box? +//========================================================= +BOOL CWeaponBox::IsEmpty( void ) +{ + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + return FALSE; + } + } + + for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ ) + { + if ( !FStringNull( m_rgiszAmmo[ i ] ) ) + { + // still have a bit of this type of ammo + return FALSE; + } + } + + return TRUE; +} + +//========================================================= +//========================================================= +void CWeaponBox::SetObjectCollisionBox( void ) +{ + pev->absmin = pev->origin + Vector(-16, -16, 0); + pev->absmax = pev->origin + Vector(16, 16, 16); +} + + +void CBasePlayerWeapon::PrintState( void ) +{ + ALERT( at_console, "primary: %f\n", m_flNextPrimaryAttack ); + ALERT( at_console, "idle : %f\n", m_flTimeWeaponIdle ); + +// ALERT( at_console, "nextrl : %f\n", m_flNextReload ); +// ALERT( at_console, "nextpum: %f\n", m_flPumpTime ); + +// ALERT( at_console, "m_frt : %f\n", m_fReloadTime ); + ALERT( at_console, "m_finre: %i\n", m_fInReload ); +// ALERT( at_console, "m_finsr: %i\n", m_fInSpecialReload ); + + ALERT( at_console, "m_iclip: %i\n", m_iClip ); +} + + +TYPEDESCRIPTION CRpg::m_SaveData[] = +{ + DEFINE_FIELD( CRpg, m_fSpotActive, FIELD_INTEGER ), + DEFINE_FIELD( CRpg, m_cActiveRockets, FIELD_INTEGER ), +}; +IMPLEMENT_SAVERESTORE( CRpg, CBasePlayerWeapon ); + +TYPEDESCRIPTION CRpgRocket::m_SaveData[] = +{ + DEFINE_FIELD( CRpgRocket, m_flIgniteTime, FIELD_TIME ), + DEFINE_FIELD( CRpgRocket, m_pLauncher, FIELD_CLASSPTR ), +}; +IMPLEMENT_SAVERESTORE( CRpgRocket, CGrenade ); + +TYPEDESCRIPTION CShotgun::m_SaveData[] = +{ + DEFINE_FIELD( CShotgun, m_flNextReload, FIELD_TIME ), + DEFINE_FIELD( CShotgun, m_fInSpecialReload, FIELD_INTEGER ), + DEFINE_FIELD( CShotgun, m_flNextReload, FIELD_TIME ), + // DEFINE_FIELD( CShotgun, m_iShell, FIELD_INTEGER ), + DEFINE_FIELD( CShotgun, m_flPumpTime, FIELD_TIME ), +}; +IMPLEMENT_SAVERESTORE( CShotgun, CBasePlayerWeapon ); + +TYPEDESCRIPTION CGauss::m_SaveData[] = +{ + DEFINE_FIELD( CGauss, m_fInAttack, FIELD_INTEGER ), +// DEFINE_FIELD( CGauss, m_flStartCharge, FIELD_TIME ), +// DEFINE_FIELD( CGauss, m_flPlayAftershock, FIELD_TIME ), +// DEFINE_FIELD( CGauss, m_flNextAmmoBurn, FIELD_TIME ), + DEFINE_FIELD( CGauss, m_fPrimaryFire, FIELD_BOOLEAN ), +}; +IMPLEMENT_SAVERESTORE( CGauss, CBasePlayerWeapon ); + +TYPEDESCRIPTION CEgon::m_SaveData[] = +{ +// DEFINE_FIELD( CEgon, m_pBeam, FIELD_CLASSPTR ), +// DEFINE_FIELD( CEgon, m_pNoise, FIELD_CLASSPTR ), +// DEFINE_FIELD( CEgon, m_pSprite, FIELD_CLASSPTR ), + DEFINE_FIELD( CEgon, m_shootTime, FIELD_TIME ), + DEFINE_FIELD( CEgon, m_fireState, FIELD_INTEGER ), + DEFINE_FIELD( CEgon, m_fireMode, FIELD_INTEGER ), + DEFINE_FIELD( CEgon, m_shakeTime, FIELD_TIME ), + DEFINE_FIELD( CEgon, m_flAmmoUseTime, FIELD_TIME ), +}; +IMPLEMENT_SAVERESTORE( CEgon, CBasePlayerWeapon ); + +TYPEDESCRIPTION CSatchel::m_SaveData[] = +{ + DEFINE_FIELD( CSatchel, m_chargeReady, FIELD_INTEGER ), +}; +IMPLEMENT_SAVERESTORE( CSatchel, CBasePlayerWeapon ); + diff --git a/dlls/weapons.h b/dlls/weapons.h new file mode 100644 index 0000000..4a34326 --- /dev/null +++ b/dlls/weapons.h @@ -0,0 +1,1019 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef WEAPONS_H +#define WEAPONS_H + +#include "effects.h" + +class CBasePlayer; +extern int gmsgWeapPickup; + +void DeactivateSatchels( CBasePlayer *pOwner ); + +// Contact Grenade / Timed grenade / Satchel Charge +class CGrenade : public CBaseMonster +{ +public: + void Spawn( void ); + + typedef enum { SATCHEL_DETONATE = 0, SATCHEL_RELEASE } SATCHELCODE; + + static CGrenade *ShootTimed( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time ); + static CGrenade *ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + static CGrenade *ShootSatchelCharge( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + static void UseSatchelCharges( entvars_t *pevOwner, SATCHELCODE code ); + + void Explode( Vector vecSrc, Vector vecAim ); + void Explode( TraceResult *pTrace, int bitsDamageType ); + void EXPORT Smoke( void ); + + void EXPORT BounceTouch( CBaseEntity *pOther ); + void EXPORT SlideTouch( CBaseEntity *pOther ); + void EXPORT ExplodeTouch( CBaseEntity *pOther ); + void EXPORT DangerSoundThink( void ); + void EXPORT PreDetonate( void ); + void EXPORT Detonate( void ); + void EXPORT DetonateUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT TumbleThink( void ); + + virtual void BounceSound( void ); + virtual int BloodColor( void ) { return DONT_BLEED; } + virtual void Killed( entvars_t *pevAttacker, int iGib ); + + BOOL m_fRegisteredSound;// whether or not this grenade has issued its DANGER sound to the world sound list yet. +}; + + +// constant items +#define ITEM_HEALTHKIT 1 +#define ITEM_ANTIDOTE 2 +#define ITEM_SECURITY 3 +#define ITEM_BATTERY 4 + +#define WEAPON_NONE 0 +#define WEAPON_CROWBAR 1 +#define WEAPON_GLOCK 2 +#define WEAPON_PYTHON 3 +#define WEAPON_MP5 4 +#define WEAPON_CHAINGUN 5 +#define WEAPON_CROSSBOW 6 +#define WEAPON_SHOTGUN 7 +#define WEAPON_RPG 8 +#define WEAPON_GAUSS 9 +#define WEAPON_EGON 10 +#define WEAPON_HORNETGUN 11 +#define WEAPON_HANDGRENADE 12 +#define WEAPON_TRIPMINE 13 +#define WEAPON_SATCHEL 14 +#define WEAPON_SNARK 15 + +#define WEAPON_ALLWEAPONS (~(1<absmin = pev->origin + Vector(-16, -16, -5); + pev->absmax = pev->origin + Vector(16, 16, 28); + } + + void PrimaryAttack( void ); + BOOL Deploy( void ); + void Holster( int skiplocal = 0 ); + void WeaponIdle( void ); + + virtual BOOL UseDecrement( void ) + { +#if defined( CLIENT_WEAPONS ) + return TRUE; +#else + return FALSE; +#endif + } + +private: + unsigned short m_usTripFire; + +}; + +class CSqueak : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 5; } + int GetItemInfo(ItemInfo *p); + + void PrimaryAttack( void ); + void SecondaryAttack( void ); + BOOL Deploy( void ); + void Holster( int skiplocal = 0 ); + void WeaponIdle( void ); + int m_fJustThrown; + + virtual BOOL UseDecrement( void ) + { +#if defined( CLIENT_WEAPONS ) + return TRUE; +#else + return FALSE; +#endif + } + +private: + unsigned short m_usSnarkFire; +}; + + +#endif // WEAPONS_H diff --git a/dlls/world.cpp b/dlls/world.cpp new file mode 100644 index 0000000..945be75 --- /dev/null +++ b/dlls/world.cpp @@ -0,0 +1,742 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== world.cpp ======================================================== + + precaches and defs for entities and other data that must always be available. + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "nodes.h" +#include "soundent.h" +#include "client.h" +#include "decals.h" +#include "skill.h" +#include "effects.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "teamplay_gamerules.h" + +extern CGraph WorldGraph; +extern CSoundEnt *pSoundEnt; + +extern CBaseEntity *g_pLastSpawn; +DLL_GLOBAL edict_t *g_pBodyQueueHead; +CGlobalState gGlobalState; +extern DLL_GLOBAL int gDisplayTitle; + +extern void W_Precache(void); + +// +// This must match the list in util.h +// +DLL_DECALLIST gDecals[] = { + { "{shot1", 0 }, // DECAL_GUNSHOT1 + { "{shot2", 0 }, // DECAL_GUNSHOT2 + { "{shot3",0 }, // DECAL_GUNSHOT3 + { "{shot4", 0 }, // DECAL_GUNSHOT4 + { "{shot5", 0 }, // DECAL_GUNSHOT5 + { "{lambda01", 0 }, // DECAL_LAMBDA1 + { "{lambda02", 0 }, // DECAL_LAMBDA2 + { "{lambda03", 0 }, // DECAL_LAMBDA3 + { "{lambda04", 0 }, // DECAL_LAMBDA4 + { "{lambda05", 0 }, // DECAL_LAMBDA5 + { "{lambda06", 0 }, // DECAL_LAMBDA6 + { "{scorch1", 0 }, // DECAL_SCORCH1 + { "{scorch2", 0 }, // DECAL_SCORCH2 + { "{blood1", 0 }, // DECAL_BLOOD1 + { "{blood2", 0 }, // DECAL_BLOOD2 + { "{blood3", 0 }, // DECAL_BLOOD3 + { "{blood4", 0 }, // DECAL_BLOOD4 + { "{blood5", 0 }, // DECAL_BLOOD5 + { "{blood6", 0 }, // DECAL_BLOOD6 + { "{yblood1", 0 }, // DECAL_YBLOOD1 + { "{yblood2", 0 }, // DECAL_YBLOOD2 + { "{yblood3", 0 }, // DECAL_YBLOOD3 + { "{yblood4", 0 }, // DECAL_YBLOOD4 + { "{yblood5", 0 }, // DECAL_YBLOOD5 + { "{yblood6", 0 }, // DECAL_YBLOOD6 + { "{break1", 0 }, // DECAL_GLASSBREAK1 + { "{break2", 0 }, // DECAL_GLASSBREAK2 + { "{break3", 0 }, // DECAL_GLASSBREAK3 + { "{bigshot1", 0 }, // DECAL_BIGSHOT1 + { "{bigshot2", 0 }, // DECAL_BIGSHOT2 + { "{bigshot3", 0 }, // DECAL_BIGSHOT3 + { "{bigshot4", 0 }, // DECAL_BIGSHOT4 + { "{bigshot5", 0 }, // DECAL_BIGSHOT5 + { "{spit1", 0 }, // DECAL_SPIT1 + { "{spit2", 0 }, // DECAL_SPIT2 + { "{bproof1", 0 }, // DECAL_BPROOF1 + { "{gargstomp", 0 }, // DECAL_GARGSTOMP1, // Gargantua stomp crack + { "{smscorch1", 0 }, // DECAL_SMALLSCORCH1, // Small scorch mark + { "{smscorch2", 0 }, // DECAL_SMALLSCORCH2, // Small scorch mark + { "{smscorch3", 0 }, // DECAL_SMALLSCORCH3, // Small scorch mark + { "{mommablob", 0 }, // DECAL_MOMMABIRTH // BM Birth spray + { "{mommablob", 0 }, // DECAL_MOMMASPLAT // BM Mortar spray?? need decal +}; + +/* +============================================================================== + +BODY QUE + +============================================================================== +*/ + +#define SF_DECAL_NOTINDEATHMATCH 2048 + +class CDecal : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT StaticDecal( void ); + void EXPORT TriggerDecal( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; + +LINK_ENTITY_TO_CLASS( infodecal, CDecal ); + +// UNDONE: These won't get sent to joining players in multi-player +void CDecal :: Spawn( void ) +{ + if ( pev->skin < 0 || (gpGlobals->deathmatch && FBitSet( pev->spawnflags, SF_DECAL_NOTINDEATHMATCH )) ) + { + REMOVE_ENTITY(ENT(pev)); + return; + } + + if ( FStringNull ( pev->targetname ) ) + { + SetThink( &CDecal::StaticDecal ); + // if there's no targetname, the decal will spray itself on as soon as the world is done spawning. + pev->nextthink = gpGlobals->time; + } + else + { + // if there IS a targetname, the decal sprays itself on when it is triggered. + SetThink ( &CDecal::SUB_DoNothing ); + SetUse( &CDecal::TriggerDecal); + } +} + +void CDecal :: TriggerDecal ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // this is set up as a USE function for infodecals that have targetnames, so that the + // decal doesn't get applied until it is fired. (usually by a scripted sequence) + TraceResult trace; + int entityIndex; + + UTIL_TraceLine( pev->origin - Vector(5,5,5), pev->origin + Vector(5,5,5), ignore_monsters, ENT(pev), &trace ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY); + WRITE_BYTE( TE_BSPDECAL ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( (int)pev->skin ); + entityIndex = (short)ENTINDEX(trace.pHit); + WRITE_SHORT( entityIndex ); + if ( entityIndex ) + WRITE_SHORT( (int)VARS(trace.pHit)->modelindex ); + MESSAGE_END(); + + SetThink( &CDecal::SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CDecal :: StaticDecal( void ) +{ + TraceResult trace; + int entityIndex, modelIndex; + + UTIL_TraceLine( pev->origin - Vector(5,5,5), pev->origin + Vector(5,5,5), ignore_monsters, ENT(pev), &trace ); + + entityIndex = (short)ENTINDEX(trace.pHit); + if ( entityIndex ) + modelIndex = (int)VARS(trace.pHit)->modelindex; + else + modelIndex = 0; + + g_engfuncs.pfnStaticDecal( pev->origin, (int)pev->skin, entityIndex, modelIndex ); + + SUB_Remove(); +} + + +void CDecal :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "texture")) + { + pev->skin = DECAL_INDEX( pkvd->szValue ); + + // Found + if ( pev->skin >= 0 ) + return; + ALERT( at_console, "Can't find decal %s\n", pkvd->szValue ); + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +// Body queue class here.... It's really just CBaseEntity +class CCorpse : public CBaseEntity +{ + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +LINK_ENTITY_TO_CLASS( bodyque, CCorpse ); + +static void InitBodyQue(void) +{ + string_t istrClassname = MAKE_STRING("bodyque"); + + g_pBodyQueueHead = CREATE_NAMED_ENTITY( istrClassname ); + entvars_t *pev = VARS(g_pBodyQueueHead); + + // Reserve 3 more slots for dead bodies + for ( int i = 0; i < 3; i++ ) + { + pev->owner = CREATE_NAMED_ENTITY( istrClassname ); + pev = VARS(pev->owner); + } + + pev->owner = g_pBodyQueueHead; +} + + +// +// make a body que entry for the given ent so the ent can be respawned elsewhere +// +// GLOBALS ASSUMED SET: g_eoBodyQueueHead +// +void CopyToBodyQue(entvars_t *pev) +{ + if (pev->effects & EF_NODRAW) + return; + + entvars_t *pevHead = VARS(g_pBodyQueueHead); + + pevHead->angles = pev->angles; + pevHead->model = pev->model; + pevHead->modelindex = pev->modelindex; + pevHead->frame = pev->frame; + pevHead->colormap = pev->colormap; + pevHead->movetype = MOVETYPE_TOSS; + pevHead->velocity = pev->velocity; + pevHead->flags = 0; + pevHead->deadflag = pev->deadflag; + pevHead->renderfx = kRenderFxDeadPlayer; + pevHead->renderamt = ENTINDEX( ENT( pev ) ); + + pevHead->effects = pev->effects | EF_NOINTERP; + //pevHead->goalstarttime = pev->goalstarttime; + //pevHead->goalframe = pev->goalframe; + //pevHead->goalendtime = pev->goalendtime ; + + pevHead->sequence = pev->sequence; + pevHead->animtime = pev->animtime; + + UTIL_SetOrigin(pevHead, pev->origin); + UTIL_SetSize(pevHead, pev->mins, pev->maxs); + g_pBodyQueueHead = pevHead->owner; +} + + +CGlobalState::CGlobalState( void ) +{ + Reset(); +} + +void CGlobalState::Reset( void ) +{ + m_pList = NULL; + m_listCount = 0; +} + +globalentity_t *CGlobalState :: Find( string_t globalname ) +{ + if ( !globalname ) + return NULL; + + globalentity_t *pTest; + const char *pEntityName = STRING(globalname); + + + pTest = m_pList; + while ( pTest ) + { + if ( FStrEq( pEntityName, pTest->name ) ) + break; + + pTest = pTest->pNext; + } + + return pTest; +} + + +// This is available all the time now on impulse 104, remove later +//#ifdef _DEBUG +void CGlobalState :: DumpGlobals( void ) +{ + static char *estates[] = { "Off", "On", "Dead" }; + globalentity_t *pTest; + + ALERT( at_console, "-- Globals --\n" ); + pTest = m_pList; + while ( pTest ) + { + ALERT( at_console, "%s: %s (%s)\n", pTest->name, pTest->levelName, estates[pTest->state] ); + pTest = pTest->pNext; + } +} +//#endif + + +void CGlobalState :: EntityAdd( string_t globalname, string_t mapName, GLOBALESTATE state ) +{ + ASSERT( !Find(globalname) ); + + globalentity_t *pNewEntity = (globalentity_t *)calloc( sizeof( globalentity_t ), 1 ); + ASSERT( pNewEntity != NULL ); + pNewEntity->pNext = m_pList; + m_pList = pNewEntity; + strcpy( pNewEntity->name, STRING( globalname ) ); + strcpy( pNewEntity->levelName, STRING(mapName) ); + pNewEntity->state = state; + m_listCount++; +} + + +void CGlobalState :: EntitySetState( string_t globalname, GLOBALESTATE state ) +{ + globalentity_t *pEnt = Find( globalname ); + + if ( pEnt ) + pEnt->state = state; +} + + +const globalentity_t *CGlobalState :: EntityFromTable( string_t globalname ) +{ + globalentity_t *pEnt = Find( globalname ); + + return pEnt; +} + + +GLOBALESTATE CGlobalState :: EntityGetState( string_t globalname ) +{ + globalentity_t *pEnt = Find( globalname ); + if ( pEnt ) + return pEnt->state; + + return GLOBAL_OFF; +} + + +// Global Savedata for Delay +TYPEDESCRIPTION CGlobalState::m_SaveData[] = +{ + DEFINE_FIELD( CGlobalState, m_listCount, FIELD_INTEGER ), +}; + +// Global Savedata for Delay +TYPEDESCRIPTION gGlobalEntitySaveData[] = +{ + DEFINE_ARRAY( globalentity_t, name, FIELD_CHARACTER, 64 ), + DEFINE_ARRAY( globalentity_t, levelName, FIELD_CHARACTER, 32 ), + DEFINE_FIELD( globalentity_t, state, FIELD_INTEGER ), +}; + + +int CGlobalState::Save( CSave &save ) +{ + int i; + globalentity_t *pEntity; + + if ( !save.WriteFields( "GLOBAL", this, m_SaveData, ARRAYSIZE(m_SaveData) ) ) + return 0; + + pEntity = m_pList; + for ( i = 0; i < m_listCount && pEntity; i++ ) + { + if ( !save.WriteFields( "GENT", pEntity, gGlobalEntitySaveData, ARRAYSIZE(gGlobalEntitySaveData) ) ) + return 0; + + pEntity = pEntity->pNext; + } + + return 1; +} + +int CGlobalState::Restore( CRestore &restore ) +{ + int i, listCount; + globalentity_t tmpEntity; + + + ClearStates(); + if ( !restore.ReadFields( "GLOBAL", this, m_SaveData, ARRAYSIZE(m_SaveData) ) ) + return 0; + + listCount = m_listCount; // Get new list count + m_listCount = 0; // Clear loaded data + + for ( i = 0; i < listCount; i++ ) + { + if ( !restore.ReadFields( "GENT", &tmpEntity, gGlobalEntitySaveData, ARRAYSIZE(gGlobalEntitySaveData) ) ) + return 0; + EntityAdd( MAKE_STRING(tmpEntity.name), MAKE_STRING(tmpEntity.levelName), tmpEntity.state ); + } + return 1; +} + +void CGlobalState::EntityUpdate( string_t globalname, string_t mapname ) +{ + globalentity_t *pEnt = Find( globalname ); + + if ( pEnt ) + strcpy( pEnt->levelName, STRING(mapname) ); +} + + +void CGlobalState::ClearStates( void ) +{ + globalentity_t *pFree = m_pList; + while ( pFree ) + { + globalentity_t *pNext = pFree->pNext; + free( pFree ); + pFree = pNext; + } + Reset(); +} + + +void SaveGlobalState( SAVERESTOREDATA *pSaveData ) +{ + CSave saveHelper( pSaveData ); + gGlobalState.Save( saveHelper ); +} + + +void RestoreGlobalState( SAVERESTOREDATA *pSaveData ) +{ + CRestore restoreHelper( pSaveData ); + gGlobalState.Restore( restoreHelper ); +} + + +void ResetGlobalState( void ) +{ + gGlobalState.ClearStates(); + gInitHUD = TRUE; // Init the HUD on a new game / load game +} + +// moved CWorld class definition to cbase.h +//======================= +// CWorld +// +// This spawns first when each level begins. +//======================= + +LINK_ENTITY_TO_CLASS( worldspawn, CWorld ); + +#define SF_WORLD_DARK 0x0001 // Fade from black at startup +#define SF_WORLD_TITLE 0x0002 // Display game title at startup +#define SF_WORLD_FORCETEAM 0x0004 // Force teams + +extern DLL_GLOBAL BOOL g_fGameOver; +float g_flWeaponCheat; + +void CWorld :: Spawn( void ) +{ + g_fGameOver = FALSE; + Precache( ); + g_flWeaponCheat = CVAR_GET_FLOAT( "sv_cheats" ); // Is the impulse 101 command allowed? +} + +void CWorld :: Precache( void ) +{ + g_pLastSpawn = NULL; + +#if 1 + CVAR_SET_STRING("sv_gravity", "800"); // 67ft/sec + CVAR_SET_STRING("sv_stepsize", "18"); +#else + CVAR_SET_STRING("sv_gravity", "384"); // 32ft/sec + CVAR_SET_STRING("sv_stepsize", "24"); +#endif + + CVAR_SET_STRING("room_type", "0");// clear DSP + + // Set up game rules + if (g_pGameRules) + { + delete g_pGameRules; + } + + g_pGameRules = InstallGameRules( ); + + //!!!UNDONE why is there so much Spawn code in the Precache function? I'll just keep it here + + ///!!!LATER - do we want a sound ent in deathmatch? (sjb) + //pSoundEnt = CBaseEntity::Create( "soundent", g_vecZero, g_vecZero, edict() ); + pSoundEnt = GetClassPtr( ( CSoundEnt *)NULL ); + pSoundEnt->Spawn(); + + if ( !pSoundEnt ) + { + ALERT ( at_console, "**COULD NOT CREATE SOUNDENT**\n" ); + } + + InitBodyQue(); + +// init sentence group playback stuff from sentences.txt. +// ok to call this multiple times, calls after first are ignored. + + SENTENCEG_Init(); + +// init texture type array from materials.txt + + TEXTURETYPE_Init(); + + +// the area based ambient sounds MUST be the first precache_sounds + +// player precaches + W_Precache (); // get weapon precaches + + ClientPrecache(); + +// sounds used from C physics code + PRECACHE_SOUND("common/null.wav"); // clears sound channels + + PRECACHE_SOUND( "items/suitchargeok1.wav" );//!!! temporary sound for respawning weapons. + PRECACHE_SOUND( "items/gunpickup2.wav" );// player picks up a gun. + + PRECACHE_SOUND( "common/bodydrop3.wav" );// dead bodies hitting the ground (animation events) + PRECACHE_SOUND( "common/bodydrop4.wav" ); + + g_Language = (int)CVAR_GET_FLOAT( "sv_language" ); + if ( g_Language == LANGUAGE_GERMAN ) + { + PRECACHE_MODEL( "models/germangibs.mdl" ); + } + else + { + PRECACHE_MODEL( "models/hgibs.mdl" ); + PRECACHE_MODEL( "models/agibs.mdl" ); + } + + PRECACHE_SOUND ("weapons/ric1.wav"); + PRECACHE_SOUND ("weapons/ric2.wav"); + PRECACHE_SOUND ("weapons/ric3.wav"); + PRECACHE_SOUND ("weapons/ric4.wav"); + PRECACHE_SOUND ("weapons/ric5.wav"); +// +// Setup light animation tables. 'a' is total darkness, 'z' is maxbright. +// + + // 0 normal + LIGHT_STYLE(0, "m"); + + // 1 FLICKER (first variety) + LIGHT_STYLE(1, "mmnmmommommnonmmonqnmmo"); + + // 2 SLOW STRONG PULSE + LIGHT_STYLE(2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); + + // 3 CANDLE (first variety) + LIGHT_STYLE(3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"); + + // 4 FAST STROBE + LIGHT_STYLE(4, "mamamamamama"); + + // 5 GENTLE PULSE 1 + LIGHT_STYLE(5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj"); + + // 6 FLICKER (second variety) + LIGHT_STYLE(6, "nmonqnmomnmomomno"); + + // 7 CANDLE (second variety) + LIGHT_STYLE(7, "mmmaaaabcdefgmmmmaaaammmaamm"); + + // 8 CANDLE (third variety) + LIGHT_STYLE(8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"); + + // 9 SLOW STROBE (fourth variety) + LIGHT_STYLE(9, "aaaaaaaazzzzzzzz"); + + // 10 FLUORESCENT FLICKER + LIGHT_STYLE(10, "mmamammmmammamamaaamammma"); + + // 11 SLOW PULSE NOT FADE TO BLACK + LIGHT_STYLE(11, "abcdefghijklmnopqrrqponmlkjihgfedcba"); + + // 12 UNDERWATER LIGHT MUTATION + // this light only distorts the lightmap - no contribution + // is made to the brightness of affected surfaces + LIGHT_STYLE(12, "mmnnmmnnnmmnn"); + + // styles 32-62 are assigned by the light program for switchable lights + + // 63 testing + LIGHT_STYLE(63, "a"); + + for ( int i = 0; i < ARRAYSIZE(gDecals); i++ ) + gDecals[i].index = DECAL_INDEX( gDecals[i].name ); + +// init the WorldGraph. + WorldGraph.InitGraph(); + +// make sure the .NOD file is newer than the .BSP file. + if ( !WorldGraph.CheckNODFile ( ( char * )STRING( gpGlobals->mapname ) ) ) + {// NOD file is not present, or is older than the BSP file. + WorldGraph.AllocNodes (); + } + else + {// Load the node graph for this level + if ( !WorldGraph.FLoadGraph ( (char *)STRING( gpGlobals->mapname ) ) ) + {// couldn't load, so alloc and prepare to build a graph. + ALERT ( at_console, "*Error opening .NOD file\n" ); + WorldGraph.AllocNodes (); + } + else + { + ALERT ( at_console, "\n*Graph Loaded!\n" ); + } + } + + if ( pev->speed > 0 ) + CVAR_SET_FLOAT( "sv_zmax", pev->speed ); + else + CVAR_SET_FLOAT( "sv_zmax", 4096 ); + + if ( pev->netname ) + { + ALERT( at_aiconsole, "Chapter title: %s\n", STRING(pev->netname) ); + CBaseEntity *pEntity = CBaseEntity::Create( "env_message", g_vecZero, g_vecZero, NULL ); + if ( pEntity ) + { + pEntity->SetThink( &CBaseEntity::SUB_CallUseToggle ); + pEntity->pev->message = pev->netname; + pev->netname = 0; + pEntity->pev->nextthink = gpGlobals->time + 0.3; + pEntity->pev->spawnflags = SF_MESSAGE_ONCE; + } + } + + if ( pev->spawnflags & SF_WORLD_DARK ) + CVAR_SET_FLOAT( "v_dark", 1.0 ); + else + CVAR_SET_FLOAT( "v_dark", 0.0 ); + + if ( pev->spawnflags & SF_WORLD_TITLE ) + gDisplayTitle = TRUE; // display the game title if this key is set + else + gDisplayTitle = FALSE; + + if ( pev->spawnflags & SF_WORLD_FORCETEAM ) + { + CVAR_SET_FLOAT( "mp_defaultteam", 1 ); + } + else + { + CVAR_SET_FLOAT( "mp_defaultteam", 0 ); + } +} + + +// +// Just to ignore the "wad" field. +// +void CWorld :: KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "skyname") ) + { + // Sent over net now. + CVAR_SET_STRING( "sv_skyname", pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "sounds") ) + { + gpGlobals->cdAudioTrack = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "WaveHeight") ) + { + // Sent over net now. + pev->scale = atof(pkvd->szValue) * (1.0/8.0); + pkvd->fHandled = TRUE; + CVAR_SET_FLOAT( "sv_wateramp", pev->scale ); + } + else if ( FStrEq(pkvd->szKeyName, "MaxRange") ) + { + pev->speed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "chaptertitle") ) + { + pev->netname = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "startdark") ) + { + // UNDONE: This is a gross hack!!! The CVAR is NOT sent over the client/sever link + // but it will work for single player + int flag = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + if ( flag ) + pev->spawnflags |= SF_WORLD_DARK; + } + else if ( FStrEq(pkvd->szKeyName, "newunit") ) + { + // Single player only. Clear save directory if set + if ( atoi(pkvd->szValue) ) + CVAR_SET_FLOAT( "sv_newunit", 1 ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "gametitle") ) + { + if ( atoi(pkvd->szValue) ) + pev->spawnflags |= SF_WORLD_TITLE; + + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "mapteams") ) + { + pev->team = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "defaultteam") ) + { + if ( atoi(pkvd->szValue) ) + { + pev->spawnflags |= SF_WORLD_FORCETEAM; + } + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} diff --git a/dlls/wpn_shared/hl_wpn_glock.cpp b/dlls/wpn_shared/hl_wpn_glock.cpp new file mode 100644 index 0000000..a72d52c --- /dev/null +++ b/dlls/wpn_shared/hl_wpn_glock.cpp @@ -0,0 +1,274 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" + +enum glock_e { + GLOCK_IDLE1 = 0, + GLOCK_IDLE2, + GLOCK_IDLE3, + GLOCK_SHOOT, + GLOCK_SHOOT_EMPTY, + GLOCK_RELOAD, + GLOCK_RELOAD_NOT_EMPTY, + GLOCK_DRAW, + GLOCK_HOLSTER, + GLOCK_ADD_SILENCER +}; + +LINK_ENTITY_TO_CLASS( weapon_glock, CGlock ); +LINK_ENTITY_TO_CLASS( weapon_9mmhandgun, CGlock ); + + +void CGlock::Spawn( ) +{ + pev->classname = MAKE_STRING("weapon_9mmhandgun"); // hack to allow for old names + Precache( ); + m_iId = WEAPON_GLOCK; + SET_MODEL(ENT(pev), "models/w_9mmhandgun.mdl"); + + m_iDefaultAmmo = GLOCK_DEFAULT_GIVE; + + FallInit();// get ready to fall down. +} + + +void CGlock::Precache( void ) +{ + PRECACHE_MODEL("models/v_9mmhandgun.mdl"); + PRECACHE_MODEL("models/w_9mmhandgun.mdl"); + PRECACHE_MODEL("models/p_9mmhandgun.mdl"); + + m_iShell = PRECACHE_MODEL ("models/shell.mdl");// brass shell + + PRECACHE_SOUND("items/9mmclip1.wav"); + PRECACHE_SOUND("items/9mmclip2.wav"); + + PRECACHE_SOUND ("weapons/pl_gun1.wav");//silenced handgun + PRECACHE_SOUND ("weapons/pl_gun2.wav");//silenced handgun + PRECACHE_SOUND ("weapons/pl_gun3.wav");//handgun + + m_usFireGlock1 = PRECACHE_EVENT( 1, "events/glock1.sc" ); + m_usFireGlock2 = PRECACHE_EVENT( 1, "events/glock2.sc" ); +} + +int CGlock::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "9mm"; + p->iMaxAmmo1 = _9MM_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = GLOCK_MAX_CLIP; + p->iSlot = 1; + p->iPosition = 0; + p->iFlags = 0; + p->iId = m_iId = WEAPON_GLOCK; + p->iWeight = GLOCK_WEIGHT; + + return 1; +} + +BOOL CGlock::Deploy( ) +{ + // pev->body = 1; + return DefaultDeploy( "models/v_9mmhandgun.mdl", "models/p_9mmhandgun.mdl", GLOCK_DRAW, "onehanded", /*UseDecrement() ? 1 : 0*/ 0 ); +} + +void CGlock::SecondaryAttack( void ) +{ + GlockFire( 0.1, 0.2, FALSE ); +} + +void CGlock::PrimaryAttack( void ) +{ + GlockFire( 0.01, 0.3, TRUE ); +} + +void CGlock::GlockFire( float flSpread , float flCycleTime, BOOL fUseAutoAim ) +{ + if (m_iClip <= 0) + { + if (m_fFireOnEmpty) + { + PlayEmptySound(); + m_flNextPrimaryAttack = GetNextAttackDelay(0.2); + } + + return; + } + + m_iClip--; + + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + int flags; + +#if defined( CLIENT_WEAPONS ) + flags = FEV_NOTHOST; +#else + flags = 0; +#endif + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + // silenced + if (pev->body == 1) + { + m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = DIM_GUN_FLASH; + } + else + { + // non-silenced + m_pPlayer->m_iWeaponVolume = NORMAL_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + } + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming; + + if ( fUseAutoAim ) + { + vecAiming = m_pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES ); + } + else + { + vecAiming = gpGlobals->v_forward; + } + + Vector vecDir; + vecDir = m_pPlayer->FireBulletsPlayer( 1, vecSrc, vecAiming, Vector( flSpread, flSpread, flSpread ), 8192, BULLET_PLAYER_9MM, 0, 0, m_pPlayer->pev, m_pPlayer->random_seed ); + + PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), fUseAutoAim ? m_usFireGlock1 : m_usFireGlock2, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, vecDir.x, vecDir.y, 0, 0, ( m_iClip == 0 ) ? 1 : 0, 0 ); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = GetNextAttackDelay(flCycleTime); + + if (!m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + // HEV suit - indicate out of ammo condition + m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); +} + + +void CGlock::Reload( void ) +{ + if ( m_pPlayer->ammo_9mm <= 0 ) + return; + + int iResult; + + if (m_iClip == 0) + iResult = DefaultReload( 17, GLOCK_RELOAD, 1.5 ); + else + iResult = DefaultReload( 17, GLOCK_RELOAD_NOT_EMPTY, 1.5 ); + + if (iResult) + { + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + } +} + + + +void CGlock::WeaponIdle( void ) +{ + ResetEmptySound( ); + + m_pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES ); + + if ( m_flTimeWeaponIdle > UTIL_WeaponTimeBase() ) + return; + + // only idle if the slid isn't back + if (m_iClip != 0) + { + int iAnim; + float flRand = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0.0, 1.0 ); + + if (flRand <= 0.3 + 0 * 0.75) + { + iAnim = GLOCK_IDLE3; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 49.0 / 16; + } + else if (flRand <= 0.6 + 0 * 0.875) + { + iAnim = GLOCK_IDLE1; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 60.0 / 16.0; + } + else + { + iAnim = GLOCK_IDLE2; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 40.0 / 16.0; + } + SendWeaponAnim( iAnim, 1 ); + } +} + + + + + + + + +class CGlockAmmo : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_9mmclip.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_9mmclip.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + if (pOther->GiveAmmo( AMMO_GLOCKCLIP_GIVE, "9mm", _9MM_MAX_CARRY ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + return TRUE; + } + return FALSE; + } +}; +LINK_ENTITY_TO_CLASS( ammo_glockclip, CGlockAmmo ); +LINK_ENTITY_TO_CLASS( ammo_9mmclip, CGlockAmmo ); + + + + + + + + + + + + + + + diff --git a/dlls/wxdebug.h b/dlls/wxdebug.h new file mode 100644 index 0000000..026039a --- /dev/null +++ b/dlls/wxdebug.h @@ -0,0 +1,137 @@ +#ifndef __WXDEBUG__ +#define __WXDEBUG__ + +// This library provides fairly straight forward debugging functionality, this +// is split into two main sections. The first is assertion handling, there are +// three types of assertions provided here. The most commonly used one is the +// ASSERT(condition) macro which will pop up a message box including the file +// and line number if the condition evaluates to FALSE. Then there is the +// EXECUTE_ASSERT macro which is the same as ASSERT except the condition will +// still be executed in NON debug builds. The final type of assertion is the +// KASSERT macro which is more suitable for pure (perhaps kernel) filters as +// the condition is printed onto the debugger rather than in a message box. +// +// The other part of the debug module facilties is general purpose logging. +// This is accessed by calling DbgLog(). The function takes a type and level +// field which define the type of informational string you are presenting and +// it's relative importance. The type field can be a combination (one or more) +// of LOG_TIMING, LOG_TRACE, LOG_MEMORY, LOG_LOCKING and LOG_ERROR. The level +// is a DWORD value where zero defines highest important. Use of zero as the +// debug logging level is to be encouraged ONLY for major errors or events as +// they will ALWAYS be displayed on the debugger. Other debug output has it's +// level matched against the current debug output level stored in the registry +// for this module and if less than the current setting it will be displayed. +// +// Each module or executable has it's own debug output level for each of the +// five types. These are read in when the DbgInitialise function is called +// for DLLs linking to STRMBASE.LIB this is done automatically when the DLL +// is loaded, executables must call it explicitely with the module instance +// handle given to them through the WINMAIN entry point. An executable must +// also call DbgTerminate when they have finished to clean up the resources +// the debug library uses, once again this is done automatically for DLLs + +// These are the five different categories of logging information + +#ifdef _DEBUG + + +enum +{ + LOG_TRACE = 0x00000001, // General tracing + LOG_ENTRY = 0x00000002, // Function entry logging + LOG_EXIT = 0x00000004, // Function exit logging + LOG_MEMORY = 0x00000008, // Memory alloc/free debugging + LOG_ERROR = 0x00000010, // Error notification + LOG_UNUSED0 = 0x00000020, // reserved + LOG_UNUSED1 = 0x00000040, // reserved + LOG_UNUSED2 = 0x00000080, // reserved + LOG_CHUM = 0x00000100, // Chumtoad debugging + LOG_LEECH = 0x00000200, // Leech debugging + LOG_ICHTHYOSAUR = 0x00000400, // Ichthyosaur debugging +}; + + +// These are public but should be called only by the DLLMain function +void WINAPI DbgInitialise(HINSTANCE hInst); +void WINAPI DbgTerminate(); +// These are public but should be called by macro only +void WINAPI DbgKernelAssert(const TCHAR *pCondition,const TCHAR *pFileName,INT iLine); +void WINAPI DbgLogInfo(DWORD Type,DWORD Level,const TCHAR *pFormat,...); +void WINAPI DbgOutString(LPCTSTR psz); + + +// These are the macros that should be used in code. + +#define DBGASSERT(_x_) \ + if (!(_x_)) \ + DbgKernelAssert(TEXT(#_x_),TEXT(__FILE__),__LINE__) + +#define DBGBREAK(_x_) \ + DbgKernelAssert(TEXT(#_x_),TEXT(__FILE__),__LINE__) + +#define DBGASSERTEXECUTE(_x_) DBGASSERT(_x_) + +#define DBGLOG(_x_) DbgLogInfo _x_ + +#define DBGOUT(_x_) DbgOutString(_x_) + +#define ValidateReadPtr(p,cb) \ + {if(IsBadReadPtr((PVOID)p,cb) == TRUE) \ + DBGBREAK("Invalid read pointer");} + +#define ValidateWritePtr(p,cb) \ + {if(IsBadWritePtr((PVOID)p,cb) == TRUE) \ + DBGBREAK("Invalid write pointer");} + +#define ValidateReadWritePtr(p,cb) \ + {ValidateReadPtr(p,cb) ValidateWritePtr(p,cb)} + +#define ValidateStringPtr(p) \ + {if(IsBadStringPtr((LPCTSTR)p,INFINITE) == TRUE) \ + DBGBREAK("Invalid string pointer");} + +#define ValidateStringPtrA(p) \ + {if(IsBadStringPtrA((LPCSTR)p,INFINITE) == TRUE) \ + DBGBREAK("Invalid ANSII string pointer");} + +#define ValidateStringPtrW(p) \ + {if(IsBadStringPtrW((LPCWSTR)p,INFINITE) == TRUE) \ + DBGBREAK("Invalid UNICODE string pointer");} + +#else // !_DEBUG + +// Retail builds make public debug functions inert - WARNING the source +// files do not define or build any of the entry points in debug builds +// (public entry points compile to nothing) so if you go trying to call +// any of the private entry points in your source they won't compile + +#define DBGASSERT(_x_) +#define DBGBREAK(_x_) +#define DBGASSERTEXECUTE(_x_) _x_ +#define DBGLOG(_x_) +#define DBGOUT(_x_) +#define ValidateReadPtr(p,cb) +#define ValidateWritePtr(p,cb) +#define ValidateReadWritePtr(p,cb) +#define ValidateStringPtr(p) +#define ValidateStringPtrA(p) +#define ValidateStringPtrW(p) + +#endif // !_DEBUG + + +#ifndef REMIND + // REMIND macro - generates warning as reminder to complete coding + // (eg) usage: + // + // #pragma message (REMIND("Add automation support")) + + + #define REMINDQUOTE(x) #x + #define REMINDQQUOTE(y) REMINDQUOTE(y) + #define REMIND(str) __FILE__ "(" REMINDQQUOTE(__LINE__) ") : " str +#endif + +#endif // __WXDEBUG__ + + diff --git a/dlls/xen.cpp b/dlls/xen.cpp new file mode 100644 index 0000000..f3b614d --- /dev/null +++ b/dlls/xen.cpp @@ -0,0 +1,584 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "animation.h" +#include "effects.h" + + +#define XEN_PLANT_GLOW_SPRITE "sprites/flare3.spr" +#define XEN_PLANT_HIDE_TIME 5 + + +class CActAnimating : public CBaseAnimating +{ +public: + void SetActivity( Activity act ); + inline Activity GetActivity( void ) { return m_Activity; } + + virtual int ObjectCaps( void ) { return CBaseAnimating :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + Activity m_Activity; +}; + +TYPEDESCRIPTION CActAnimating::m_SaveData[] = +{ + DEFINE_FIELD( CActAnimating, m_Activity, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CActAnimating, CBaseAnimating ); + +void CActAnimating :: SetActivity( Activity act ) +{ + int sequence = LookupActivity( act ); + if ( sequence != ACTIVITY_NOT_AVAILABLE ) + { + pev->sequence = sequence; + m_Activity = act; + pev->frame = 0; + ResetSequenceInfo( ); + } +} + + + + +class CXenPLight : public CActAnimating +{ +public: + void Spawn( void ); + void Precache( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); + + void LightOn( void ); + void LightOff( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + CSprite *m_pGlow; +}; + +LINK_ENTITY_TO_CLASS( xen_plantlight, CXenPLight ); + +TYPEDESCRIPTION CXenPLight::m_SaveData[] = +{ + DEFINE_FIELD( CXenPLight, m_pGlow, FIELD_CLASSPTR ), +}; + +IMPLEMENT_SAVERESTORE( CXenPLight, CActAnimating ); + +void CXenPLight :: Spawn( void ) +{ + Precache(); + + SET_MODEL( ENT(pev), "models/light.mdl" ); + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_TRIGGER; + + UTIL_SetSize( pev, Vector(-80,-80,0), Vector(80,80,32)); + SetActivity( ACT_IDLE ); + pev->nextthink = gpGlobals->time + 0.1; + pev->frame = RANDOM_FLOAT(0,255); + + m_pGlow = CSprite::SpriteCreate( XEN_PLANT_GLOW_SPRITE, pev->origin + Vector(0,0,(pev->mins.z+pev->maxs.z)*0.5), FALSE ); + m_pGlow->SetTransparency( kRenderGlow, pev->rendercolor.x, pev->rendercolor.y, pev->rendercolor.z, pev->renderamt, pev->renderfx ); + m_pGlow->SetAttachment( edict(), 1 ); +} + + +void CXenPLight :: Precache( void ) +{ + PRECACHE_MODEL( "models/light.mdl" ); + PRECACHE_MODEL( XEN_PLANT_GLOW_SPRITE ); +} + + +void CXenPLight :: Think( void ) +{ + StudioFrameAdvance(); + pev->nextthink = gpGlobals->time + 0.1; + + switch( GetActivity() ) + { + case ACT_CROUCH: + if ( m_fSequenceFinished ) + { + SetActivity( ACT_CROUCHIDLE ); + LightOff(); + } + break; + + case ACT_CROUCHIDLE: + if ( gpGlobals->time > pev->dmgtime ) + { + SetActivity( ACT_STAND ); + LightOn(); + } + break; + + case ACT_STAND: + if ( m_fSequenceFinished ) + SetActivity( ACT_IDLE ); + break; + + case ACT_IDLE: + default: + break; + } +} + + +void CXenPLight :: Touch( CBaseEntity *pOther ) +{ + if ( pOther->IsPlayer() ) + { + pev->dmgtime = gpGlobals->time + XEN_PLANT_HIDE_TIME; + if ( GetActivity() == ACT_IDLE || GetActivity() == ACT_STAND ) + { + SetActivity( ACT_CROUCH ); + } + } +} + + +void CXenPLight :: LightOn( void ) +{ + SUB_UseTargets( this, USE_ON, 0 ); + if ( m_pGlow ) + m_pGlow->pev->effects &= ~EF_NODRAW; +} + + +void CXenPLight :: LightOff( void ) +{ + SUB_UseTargets( this, USE_OFF, 0 ); + if ( m_pGlow ) + m_pGlow->pev->effects |= EF_NODRAW; +} + + + +class CXenHair : public CActAnimating +{ +public: + void Spawn( void ); + void Precache( void ); + void Think( void ); +}; + +LINK_ENTITY_TO_CLASS( xen_hair, CXenHair ); + +#define SF_HAIR_SYNC 0x0001 + +void CXenHair::Spawn( void ) +{ + Precache(); + SET_MODEL( edict(), "models/hair.mdl" ); + UTIL_SetSize( pev, Vector(-4,-4,0), Vector(4,4,32)); + pev->sequence = 0; + + if ( !(pev->spawnflags & SF_HAIR_SYNC) ) + { + pev->frame = RANDOM_FLOAT(0,255); + pev->framerate = RANDOM_FLOAT( 0.7, 1.4 ); + } + ResetSequenceInfo( ); + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->nextthink = gpGlobals->time + RANDOM_FLOAT( 0.1, 0.4 ); // Load balance these a bit +} + + +void CXenHair::Think( void ) +{ + StudioFrameAdvance(); + pev->nextthink = gpGlobals->time + 0.5; +} + + +void CXenHair::Precache( void ) +{ + PRECACHE_MODEL( "models/hair.mdl" ); +} + + +class CXenTreeTrigger : public CBaseEntity +{ +public: + void Touch( CBaseEntity *pOther ); + static CXenTreeTrigger *TriggerCreate( edict_t *pOwner, const Vector &position ); +}; +LINK_ENTITY_TO_CLASS( xen_ttrigger, CXenTreeTrigger ); + +CXenTreeTrigger *CXenTreeTrigger :: TriggerCreate( edict_t *pOwner, const Vector &position ) +{ + CXenTreeTrigger *pTrigger = GetClassPtr( (CXenTreeTrigger *)NULL ); + pTrigger->pev->origin = position; + pTrigger->pev->classname = MAKE_STRING("xen_ttrigger"); + pTrigger->pev->solid = SOLID_TRIGGER; + pTrigger->pev->movetype = MOVETYPE_NONE; + pTrigger->pev->owner = pOwner; + + return pTrigger; +} + + +void CXenTreeTrigger::Touch( CBaseEntity *pOther ) +{ + if ( pev->owner ) + { + CBaseEntity *pEntity = CBaseEntity::Instance(pev->owner); + pEntity->Touch( pOther ); + } +} + + +#define TREE_AE_ATTACK 1 + +class CXenTree : public CActAnimating +{ +public: + void Spawn( void ); + void Precache( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { Attack(); return 0; } + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void Attack( void ); + int Classify( void ) { return CLASS_BARNACLE; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + static const char *pAttackHitSounds[]; + static const char *pAttackMissSounds[]; + +private: + CXenTreeTrigger *m_pTrigger; +}; + +LINK_ENTITY_TO_CLASS( xen_tree, CXenTree ); + +TYPEDESCRIPTION CXenTree::m_SaveData[] = +{ + DEFINE_FIELD( CXenTree, m_pTrigger, FIELD_CLASSPTR ), +}; + +IMPLEMENT_SAVERESTORE( CXenTree, CActAnimating ); + +void CXenTree :: Spawn( void ) +{ + Precache(); + + SET_MODEL( ENT(pev), "models/tree.mdl" ); + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_BBOX; + + pev->takedamage = DAMAGE_YES; + + UTIL_SetSize( pev, Vector(-30,-30,0), Vector(30,30,188)); + SetActivity( ACT_IDLE ); + pev->nextthink = gpGlobals->time + 0.1; + pev->frame = RANDOM_FLOAT(0,255); + pev->framerate = RANDOM_FLOAT( 0.7, 1.4 ); + + Vector triggerPosition; + UTIL_MakeVectorsPrivate( pev->angles, triggerPosition, NULL, NULL ); + triggerPosition = pev->origin + (triggerPosition * 64); + // Create the trigger + m_pTrigger = CXenTreeTrigger::TriggerCreate( edict(), triggerPosition ); + UTIL_SetSize( m_pTrigger->pev, Vector( -24, -24, 0 ), Vector( 24, 24, 128 ) ); +} + +const char *CXenTree::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CXenTree::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +void CXenTree :: Precache( void ) +{ + PRECACHE_MODEL( "models/tree.mdl" ); + PRECACHE_MODEL( XEN_PLANT_GLOW_SPRITE ); + PRECACHE_SOUND_ARRAY( pAttackHitSounds ); + PRECACHE_SOUND_ARRAY( pAttackMissSounds ); +} + + +void CXenTree :: Touch( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() && FClassnameIs( pOther->pev, "monster_bigmomma" ) ) + return; + + Attack(); +} + + +void CXenTree :: Attack( void ) +{ + if ( GetActivity() == ACT_IDLE ) + { + SetActivity( ACT_MELEE_ATTACK1 ); + pev->framerate = RANDOM_FLOAT( 1.0, 1.4 ); + EMIT_SOUND_ARRAY_DYN( CHAN_WEAPON, pAttackMissSounds ); + } +} + + +void CXenTree :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case TREE_AE_ATTACK: + { + CBaseEntity *pList[8]; + BOOL sound = FALSE; + int count = UTIL_EntitiesInBox( pList, 8, m_pTrigger->pev->absmin, m_pTrigger->pev->absmax, FL_MONSTER|FL_CLIENT ); + Vector forward; + + UTIL_MakeVectorsPrivate( pev->angles, forward, NULL, NULL ); + + for ( int i = 0; i < count; i++ ) + { + if ( pList[i] != this ) + { + if ( pList[i]->pev->owner != edict() ) + { + sound = TRUE; + pList[i]->TakeDamage( pev, pev, 25, DMG_CRUSH | DMG_SLASH ); + pList[i]->pev->punchangle.x = 15; + pList[i]->pev->velocity = pList[i]->pev->velocity + forward * 100; + } + } + } + + if ( sound ) + { + EMIT_SOUND_ARRAY_DYN( CHAN_WEAPON, pAttackHitSounds ); + } + } + return; + } + + CActAnimating::HandleAnimEvent( pEvent ); +} + +void CXenTree :: Think( void ) +{ + float flInterval = StudioFrameAdvance(); + pev->nextthink = gpGlobals->time + 0.1; + DispatchAnimEvents( flInterval ); + + switch( GetActivity() ) + { + case ACT_MELEE_ATTACK1: + if ( m_fSequenceFinished ) + { + SetActivity( ACT_IDLE ); + pev->framerate = RANDOM_FLOAT( 0.6, 1.4 ); + } + break; + + default: + case ACT_IDLE: + break; + + } +} + + +// UNDONE: These need to smoke somehow when they take damage +// Touch behavior? +// Cause damage in smoke area + +// +// Spores +// +class CXenSpore : public CActAnimating +{ +public: + void Spawn( void ); + void Precache( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { Attack(); return 0; } +// void HandleAnimEvent( MonsterEvent_t *pEvent ); + void Attack( void ) {} + + static const char *pModelNames[]; +}; + +class CXenSporeSmall : public CXenSpore +{ + void Spawn( void ); +}; + +class CXenSporeMed : public CXenSpore +{ + void Spawn( void ); +}; + +class CXenSporeLarge : public CXenSpore +{ + void Spawn( void ); + + static const Vector m_hullSizes[]; +}; + +// Fake collision box for big spores +class CXenHull : public CPointEntity +{ +public: + static CXenHull *CreateHull( CBaseEntity *source, const Vector &mins, const Vector &maxs, const Vector &offset ); + int Classify( void ) { return CLASS_BARNACLE; } +}; + +CXenHull *CXenHull :: CreateHull( CBaseEntity *source, const Vector &mins, const Vector &maxs, const Vector &offset ) +{ + CXenHull *pHull = GetClassPtr( (CXenHull *)NULL ); + + UTIL_SetOrigin( pHull->pev, source->pev->origin + offset ); + SET_MODEL( pHull->edict(), STRING(source->pev->model) ); + pHull->pev->solid = SOLID_BBOX; + pHull->pev->classname = MAKE_STRING("xen_hull"); + pHull->pev->movetype = MOVETYPE_NONE; + pHull->pev->owner = source->edict(); + UTIL_SetSize( pHull->pev, mins, maxs ); + pHull->pev->renderamt = 0; + pHull->pev->rendermode = kRenderTransTexture; + // pHull->pev->effects = EF_NODRAW; + + return pHull; +} + + +LINK_ENTITY_TO_CLASS( xen_spore_small, CXenSporeSmall ); +LINK_ENTITY_TO_CLASS( xen_spore_medium, CXenSporeMed ); +LINK_ENTITY_TO_CLASS( xen_spore_large, CXenSporeLarge ); +LINK_ENTITY_TO_CLASS( xen_hull, CXenHull ); + +void CXenSporeSmall::Spawn( void ) +{ + pev->skin = 0; + CXenSpore::Spawn(); + UTIL_SetSize( pev, Vector(-16,-16,0), Vector(16,16,64)); +} +void CXenSporeMed::Spawn( void ) +{ + pev->skin = 1; + CXenSpore::Spawn(); + UTIL_SetSize( pev, Vector(-40,-40,0), Vector(40,40,120)); +} + + +// I just eyeballed these -- fill in hulls for the legs +const Vector CXenSporeLarge::m_hullSizes[] = +{ + Vector( 90, -25, 0 ), + Vector( 25, 75, 0 ), + Vector( -15, -100, 0 ), + Vector( -90, -35, 0 ), + Vector( -90, 60, 0 ), +}; + +void CXenSporeLarge::Spawn( void ) +{ + pev->skin = 2; + CXenSpore::Spawn(); + UTIL_SetSize( pev, Vector(-48,-48,110), Vector(48,48,240)); + + Vector forward, right; + + UTIL_MakeVectorsPrivate( pev->angles, forward, right, NULL ); + + // Rotate the leg hulls into position + for ( int i = 0; i < ARRAYSIZE(m_hullSizes); i++ ) + CXenHull :: CreateHull( this, Vector(-12, -12, 0 ), Vector( 12, 12, 120 ), (m_hullSizes[i].x * forward) + (m_hullSizes[i].y * right) ); +} + +void CXenSpore :: Spawn( void ) +{ + Precache(); + + SET_MODEL( ENT(pev), pModelNames[pev->skin] ); + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_BBOX; + pev->takedamage = DAMAGE_YES; + +// SetActivity( ACT_IDLE ); + pev->sequence = 0; + pev->frame = RANDOM_FLOAT(0,255); + pev->framerate = RANDOM_FLOAT( 0.7, 1.4 ); + ResetSequenceInfo( ); + pev->nextthink = gpGlobals->time + RANDOM_FLOAT( 0.1, 0.4 ); // Load balance these a bit +} + +const char *CXenSpore::pModelNames[] = +{ + "models/fungus(small).mdl", + "models/fungus.mdl", + "models/fungus(large).mdl", +}; + + +void CXenSpore :: Precache( void ) +{ + PRECACHE_MODEL( (char *)pModelNames[pev->skin] ); +} + + +void CXenSpore :: Touch( CBaseEntity *pOther ) +{ +} + + +void CXenSpore :: Think( void ) +{ + float flInterval = StudioFrameAdvance(); + pev->nextthink = gpGlobals->time + 0.1; + +#if 0 + DispatchAnimEvents( flInterval ); + + switch( GetActivity() ) + { + default: + case ACT_IDLE: + break; + + } +#endif +} + + diff --git a/dlls/zombie.cpp b/dlls/zombie.cpp new file mode 100644 index 0000000..1967da7 --- /dev/null +++ b/dlls/zombie.cpp @@ -0,0 +1,346 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Zombie +//========================================================= + +// UNDONE: Don't flinch every time you get hit + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define ZOMBIE_AE_ATTACK_RIGHT 0x01 +#define ZOMBIE_AE_ATTACK_LEFT 0x02 +#define ZOMBIE_AE_ATTACK_BOTH 0x03 + +#define ZOMBIE_FLINCH_DELAY 2 // at most one flinch every n secs + +class CZombie : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int IgnoreConditions ( void ); + + float m_flNextFlinch; + + void PainSound( void ); + void AlertSound( void ); + void IdleSound( void ); + void AttackSound( void ); + + static const char *pAttackSounds[]; + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + static const char *pPainSounds[]; + static const char *pAttackHitSounds[]; + static const char *pAttackMissSounds[]; + + // No range attacks + BOOL CheckRangeAttack1 ( float flDot, float flDist ) { return FALSE; } + BOOL CheckRangeAttack2 ( float flDot, float flDist ) { return FALSE; } + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); +}; + +LINK_ENTITY_TO_CLASS( monster_zombie, CZombie ); + +const char *CZombie::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CZombie::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CZombie::pAttackSounds[] = +{ + "zombie/zo_attack1.wav", + "zombie/zo_attack2.wav", +}; + +const char *CZombie::pIdleSounds[] = +{ + "zombie/zo_idle1.wav", + "zombie/zo_idle2.wav", + "zombie/zo_idle3.wav", + "zombie/zo_idle4.wav", +}; + +const char *CZombie::pAlertSounds[] = +{ + "zombie/zo_alert10.wav", + "zombie/zo_alert20.wav", + "zombie/zo_alert30.wav", +}; + +const char *CZombie::pPainSounds[] = +{ + "zombie/zo_pain1.wav", + "zombie/zo_pain2.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CZombie :: Classify ( void ) +{ + return CLASS_ALIEN_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CZombie :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + +#if 0 + switch ( m_Activity ) + { + } +#endif + + pev->yaw_speed = ys; +} + +int CZombie :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // Take 30% damage from bullets + if ( bitsDamageType == DMG_BULLET ) + { + Vector vecDir = pev->origin - (pevInflictor->absmin + pevInflictor->absmax) * 0.5; + vecDir = vecDir.Normalize(); + float flForce = DamageForce( flDamage ); + pev->velocity = pev->velocity + vecDir * flForce; + flDamage *= 0.3; + } + + // HACK HACK -- until we fix this. + if ( IsAlive() ) + PainSound(); + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CZombie :: PainSound( void ) +{ + int pitch = 95 + RANDOM_LONG(0,9); + + if (RANDOM_LONG(0,5) < 2) + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1) ], 1.0, ATTN_NORM, 0, pitch ); +} + +void CZombie :: AlertSound( void ) +{ + int pitch = 95 + RANDOM_LONG(0,9); + + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAlertSounds[ RANDOM_LONG(0,ARRAYSIZE(pAlertSounds)-1) ], 1.0, ATTN_NORM, 0, pitch ); +} + +void CZombie :: IdleSound( void ) +{ + int pitch = 95 + RANDOM_LONG(0,9); + + // Play a random idle sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pIdleSounds[ RANDOM_LONG(0,ARRAYSIZE(pIdleSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); +} + +void CZombie :: AttackSound( void ) +{ + // Play a random attack sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAttackSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CZombie :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case ZOMBIE_AE_ATTACK_RIGHT: + { + // do stuff for this event. + // ALERT( at_console, "Slash right!\n" ); + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.zombieDmgOneSlash, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.z = -18; + pHurt->pev->punchangle.x = 5; + pHurt->pev->velocity = pHurt->pev->velocity - gpGlobals->v_right * 100; + } + // Play a random attack hit sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + } + else // Play a random attack miss sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + + if (RANDOM_LONG(0,1)) + AttackSound(); + } + break; + + case ZOMBIE_AE_ATTACK_LEFT: + { + // do stuff for this event. + // ALERT( at_console, "Slash left!\n" ); + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.zombieDmgOneSlash, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.z = 18; + pHurt->pev->punchangle.x = 5; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_right * 100; + } + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + } + else + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + + if (RANDOM_LONG(0,1)) + AttackSound(); + } + break; + + case ZOMBIE_AE_ATTACK_BOTH: + { + // do stuff for this event. + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.zombieDmgBothSlash, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.x = 5; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * -100; + } + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + } + else + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + + if (RANDOM_LONG(0,1)) + AttackSound(); + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CZombie :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/zombie.mdl"); + UTIL_SetSize( pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->health = gSkillData.zombieHealth; + pev->view_ofs = VEC_VIEW;// position of the eyes relative to monster's origin. + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_afCapability = bits_CAP_DOORS_GROUP; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CZombie :: Precache() +{ + int i; + + PRECACHE_MODEL("models/zombie.mdl"); + + for ( i = 0; i < ARRAYSIZE( pAttackHitSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackHitSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackMissSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackMissSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pIdleSounds ); i++ ) + PRECACHE_SOUND((char *)pIdleSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAlertSounds ); i++ ) + PRECACHE_SOUND((char *)pAlertSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pPainSounds ); i++ ) + PRECACHE_SOUND((char *)pPainSounds[i]); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + + +int CZombie::IgnoreConditions ( void ) +{ + int iIgnore = CBaseMonster::IgnoreConditions(); + + if ((m_Activity == ACT_MELEE_ATTACK1) || (m_Activity == ACT_MELEE_ATTACK1)) + { +#if 0 + if (pev->health < 20) + iIgnore |= (bits_COND_LIGHT_DAMAGE|bits_COND_HEAVY_DAMAGE); + else +#endif + if (m_flNextFlinch >= gpGlobals->time) + iIgnore |= (bits_COND_LIGHT_DAMAGE|bits_COND_HEAVY_DAMAGE); + } + + if ((m_Activity == ACT_SMALL_FLINCH) || (m_Activity == ACT_BIG_FLINCH)) + { + if (m_flNextFlinch < gpGlobals->time) + m_flNextFlinch = gpGlobals->time + ZOMBIE_FLINCH_DELAY; + } + + return iIgnore; + +} \ No newline at end of file diff --git a/dmc/cl_dll/CTF_FlagStatus.cpp b/dmc/cl_dll/CTF_FlagStatus.cpp new file mode 100644 index 0000000..b11adcf --- /dev/null +++ b/dmc/cl_dll/CTF_FlagStatus.cpp @@ -0,0 +1,246 @@ +#ifdef THREEWAVE + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "entity_types.h" +#include "usercmd.h" +#include "pm_defs.h" +#include "pm_materials.h" +#include "ref_params.h" +#include +#include "vgui_viewport.h" +#include "vgui_ScorePanel.h" + +#define RED_FLAG_STOLE 1 +#define BLUE_FLAG_STOLE 2 +#define RED_FLAG_LOST 3 +#define BLUE_FLAG_LOST 4 +#define RED_FLAG_ATBASE 5 +#define BLUE_FLAG_ATBASE 6 + +#define ITEM_RUNE1_FLAG 1 +#define ITEM_RUNE2_FLAG 2 +#define ITEM_RUNE3_FLAG 3 +#define ITEM_RUNE4_FLAG 4 + +DECLARE_MESSAGE(m_FlagStat, FlagStat) +DECLARE_MESSAGE(m_FlagStat, RuneStat) +DECLARE_MESSAGE(m_FlagStat, FlagCarrier) + +int CHudFlagStatus::Init(void) +{ + HOOK_MESSAGE( FlagStat ); + HOOK_MESSAGE( RuneStat ); + HOOK_MESSAGE( FlagCarrier ); + + m_iFlags |= HUD_ACTIVE; + + gHUD.AddHudElem(this); + + Reset(); + + return 1; +}; + +int CHudFlagStatus::VidInit(void) +{ + m_iBlueAtBaseIndex = gHUD.GetSpriteIndex( "blue_atbase" ); + m_iBlueLostIndex = gHUD.GetSpriteIndex( "blue_lost" ); + m_iBlueStolenIndex = gHUD.GetSpriteIndex( "blue_stolen" ); + + m_iRedAtBaseIndex = gHUD.GetSpriteIndex( "red_atbase" ); + m_iRedLostIndex = gHUD.GetSpriteIndex( "red_lost" ); + m_iRedStolenIndex = gHUD.GetSpriteIndex( "red_stolen" ); + + m_iRune1Index = gHUD.GetSpriteIndex( "rune1" ); + m_iRune2Index = gHUD.GetSpriteIndex( "rune2" ); + m_iRune3Index = gHUD.GetSpriteIndex( "rune3" ); + m_iRune4Index = gHUD.GetSpriteIndex( "rune4" ); + + m_hBlueAtBase = gHUD.GetSprite( m_iBlueAtBaseIndex ); + m_hBlueLost = gHUD.GetSprite( m_iBlueLostIndex ); + m_hBlueStolen = gHUD.GetSprite( m_iBlueStolenIndex ); + + m_hRedAtBase = gHUD.GetSprite( m_iRedAtBaseIndex ); + m_hRedLost = gHUD.GetSprite( m_iRedLostIndex ); + m_hRedStolen = gHUD.GetSprite( m_iRedStolenIndex ); + + m_hRune1 = gHUD.GetSprite( m_iRune1Index ); + m_hRune2 = gHUD.GetSprite( m_iRune2Index ); + m_hRune3 = gHUD.GetSprite( m_iRune3Index ); + m_hRune4 = gHUD.GetSprite( m_iRune4Index ); + + // Load sprites here + m_iBlueFlagIndex = gHUD.GetSpriteIndex( "b_flag_c" ); + m_iRedFlagIndex = gHUD.GetSpriteIndex( "r_flag_c" ); + + m_hBlueFlag = gHUD.GetSprite( m_iBlueFlagIndex ); + m_hRedFlag = gHUD.GetSprite( m_iRedFlagIndex ); + + return 1; +} + +void CHudFlagStatus :: Reset( void ) +{ + return; +} + + +int CHudFlagStatus ::Draw(float flTime ) +{ + + if ( !iDrawStatus ) + return 1; + + int x, y; + int r,g,b; + + r = g = b = 255; + + x = 20; + y = ( ScreenHeight - gHUD.m_iFontHeight ) - ( gHUD.m_iFontHeight / 2 ) - 40; + + + switch ( iBlueFlagStatus ) + { + case BLUE_FLAG_STOLE: + SPR_Set( m_hBlueStolen, r, g, b ); + SPR_DrawHoles( 1, x, y, NULL ); + break; + case BLUE_FLAG_LOST: + SPR_Set( m_hBlueLost, r, g, b ); + SPR_DrawHoles( 1, x, y, NULL ); + break; + case BLUE_FLAG_ATBASE: + SPR_Set( m_hBlueAtBase, r, g, b ); + SPR_DrawHoles( 1, x, y, NULL ); + break; + } + + x = 50; + + if ( iBlueTeamScore < 10) + { + x += 3; + gHUD.DrawHudNumber( x, y + 4, DHN_DRAWZERO, iBlueTeamScore, 255, 255, 255 ); + } + else if ( iBlueTeamScore >= 10 && iBlueTeamScore < 100 ) + gHUD.DrawHudNumber( x, y + 4, DHN_2DIGITS | DHN_DRAWZERO, iBlueTeamScore, 255, 255, 255 ); + + x = 20; + y = ( ScreenHeight - gHUD.m_iFontHeight ) - ( gHUD.m_iFontHeight / 2 ) - 75; + + switch ( iRedFlagStatus ) + { + case RED_FLAG_STOLE: + SPR_Set( m_hRedStolen, r, g, b ); + SPR_DrawHoles( 1, x, y, NULL ); + break; + case RED_FLAG_LOST: + SPR_Set( m_hRedLost, r, g, b ); + SPR_DrawHoles( 1, x, y, NULL ); + break; + case RED_FLAG_ATBASE: + SPR_Set( m_hRedAtBase, r, g, b ); + SPR_DrawHoles( 1, x, y, NULL ); + break; + } + + x = 50; + if ( iRedTeamScore < 10) + { + x += 3; + gHUD.DrawHudNumber( x, y + 4, DHN_DRAWZERO, iRedTeamScore, 255, 255, 255 ); + } + else if ( iBlueTeamScore >= 10 && iBlueTeamScore < 100 ) + gHUD.DrawHudNumber( x, y + 4, DHN_2DIGITS | DHN_DRAWZERO, iRedTeamScore, 255, 255, 255 ); + + x = 20; + y = ( ScreenHeight - gHUD.m_iFontHeight ) - ( gHUD.m_iFontHeight / 2 ) - 110; + + switch ( m_iRuneStat ) + { + case ITEM_RUNE1_FLAG: + SPR_Set( m_hRune1, r, g, b ); + SPR_Draw( 1, x, y, NULL ); + break; + + case ITEM_RUNE2_FLAG: + SPR_Set( m_hRune2, r, g, b ); + SPR_Draw( 1, x, y, NULL ); + break; + + case ITEM_RUNE3_FLAG: + SPR_Set( m_hRune3, r, g, b ); + SPR_Draw( 1, x, y, NULL ); + break; + + case ITEM_RUNE4_FLAG: + SPR_Set( m_hRune4, r, g, b ); + SPR_Draw( 1, x, y, NULL ); + break; + } + + return 1; +} + +int CHudFlagStatus::MsgFunc_FlagStat(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + iDrawStatus = READ_BYTE(); + iRedFlagStatus = READ_BYTE(); + iBlueFlagStatus = READ_BYTE(); + + iRedTeamScore = READ_BYTE(); + iBlueTeamScore = READ_BYTE(); + + return 1; +} + +int CHudFlagStatus::MsgFunc_RuneStat(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + m_iRuneStat = READ_BYTE(); + + return 1; +} + +int CHudFlagStatus::MsgFunc_FlagCarrier(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + int index = READ_BYTE(); + + bool bRedFlag = false; + bool bBlueFlag = false; + + g_PlayerExtraInfo[ index ].iHasFlag = READ_BYTE(); + + for ( int i = 1; i < MAX_PLAYERS + 1; i++ ) + { + if ( g_PlayerExtraInfo[ i ].iHasFlag ) + { + if ( g_PlayerExtraInfo[ i ].teamnumber == 1 ) + bRedFlag = true; + else if ( g_PlayerExtraInfo[ i ].teamnumber == 2 ) + bBlueFlag = true; + } + } + + if ( !bRedFlag ) + gViewPort->m_pScoreBoard->m_pImages[ 5 ]->setVisible( false ); + if ( !bBlueFlag ) + gViewPort->m_pScoreBoard->m_pImages[ 4 ]->setVisible( false ); + + return 1; +} + + +#endif \ No newline at end of file diff --git a/dmc/cl_dll/CTF_HudMessage.cpp b/dmc/cl_dll/CTF_HudMessage.cpp new file mode 100644 index 0000000..c7d640f --- /dev/null +++ b/dmc/cl_dll/CTF_HudMessage.cpp @@ -0,0 +1,184 @@ +#ifdef THREEWAVE + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "entity_types.h" +#include "usercmd.h" +#include "pm_defs.h" +#include "pm_materials.h" +#include "ref_params.h" +#include + + +#define MAX_BONUS 10 + +#define RED_FLAG_STOLEN 1 +#define BLUE_FLAG_STOLEN 2 +#define RED_FLAG_CAPTURED 3 +#define BLUE_FLAG_CAPTURED 4 +#define RED_FLAG_RETURNED_PLAYER 5 +#define BLUE_FLAG_RETURNED_PLAYER 6 +#define RED_FLAG_RETURNED 7 +#define BLUE_FLAG_RETURNED 8 +#define RED_FLAG_LOST_HUD 9 +#define BLUE_FLAG_LOST_HUD 10 + + + +char *sBonusStrings[] = +{ + "", + "\\w stole the \\rRED\\w Flag!", + "\\w stole the \\bBLUE\\w Flag!", + "\\w captured the \\rRED\\w Flag", + "\\w captured the \\bBLUE\\w Flag", + "\\w returned the \\rRED\\w Flag", + "\\w returned the \\bBLUE\\w Flag", + "\\wThe \\rRED\\w Flag has Returned", + "\\wThe \\bBLUE\\w Flag has Returned", + "\\w lost the \\rRED\\w Flag!", + "\\w lost the \\bBLUE\\w Flag!", +}; + +DECLARE_MESSAGE(m_Bonus, Bonus) + +struct bonus_info_t +{ + int iSlot; + int iType; + bool bActive; + float flBonusTime; + char sPlayerName[64]; +}; + +bonus_info_t g_PlayerBonus[MAX_BONUS+1]; + +int CHudBonus::Init(void) +{ + HOOK_MESSAGE( Bonus ); + + m_iFlags |= HUD_ACTIVE; + + gHUD.AddHudElem(this); + + Reset(); + + return 1; +}; + +int CHudBonus::VidInit(void) +{ + return 1; +} + +void CHudBonus :: Reset( void ) +{ + m_iFlags |= HUD_ACTIVE; + + for ( int reset = 0; reset < MAX_BONUS; reset++) + { + g_PlayerBonus[ reset ].flBonusTime = 0.0; + g_PlayerBonus[ reset ].iSlot = 0; + g_PlayerBonus[ reset ].iType = 0; + g_PlayerBonus[ reset ].bActive = false; + m_bUsedSlot[ reset ] = false; + strcpy ( g_PlayerBonus[ reset ].sPlayerName, "" ); + } +} + +int CHudBonus ::Draw(float flTime ) +{ + for (int index = 1; index < MAX_BONUS + 1; index++) + { + //Just activated + if ( g_PlayerBonus[ index ].bActive && !g_PlayerBonus[ index ].flBonusTime ) + { + g_PlayerBonus[ index ].flBonusTime = flTime + 5.0; + + for ( int i = 1; i < MAX_BONUS + 1; i++ ) + { + if ( m_bUsedSlot[ i ] == false ) //found one thats not used + { + m_bUsedSlot[ i ] = true; //use it! + g_PlayerBonus[ index ].iSlot = i; + break; + } + } + } + + if ( g_PlayerBonus[ index ].flBonusTime > flTime ) + { + int YPos; + int iMod = gHUD.ReturnStringPixelLength( "\\w\\r\\w" ); + + YPos = ( ( ScreenHeight - gHUD.m_iFontHeight ) - ( gHUD.m_iFontHeight / 2 ) + 3 ) - ( 30 * g_PlayerBonus[ index ].iSlot ); + + int XPos = 75; + + char szText[256]; + + strcpy ( szText, g_PlayerBonus[ index ].sPlayerName ); + strcat ( szText, sBonusStrings[ g_PlayerBonus[ index ].iType ] ); + + if ( gHUD.m_FlagStat.iBlueTeamScore >= 10 ) + gHUD.DrawHudStringCTF( XPos + 20, YPos, 640, szText, 255, 255, 255 ); + else + gHUD.DrawHudStringCTF( XPos , YPos, 320, szText, 255, 255, 255 ); + + } + + if ( g_PlayerBonus[ index ].flBonusTime < flTime ) + { + g_PlayerBonus[ index ].bActive = false; + m_bUsedSlot[ g_PlayerBonus[ index ].iSlot ] = false; + g_PlayerBonus[ index ].iSlot = 0; + strcpy ( g_PlayerBonus[ index ].sPlayerName, "" ); + } + } + + return 1; +} + +int CHudBonus::MsgFunc_Bonus(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + for ( int index = 1; index < MAX_BONUS + 1; index++) + { + //Find wich one is not used + if ( g_PlayerBonus[ index ].bActive == false ) + break; //Not using this one?, then we shall use this. + } + + g_PlayerBonus[ index ].bActive = true; + g_PlayerBonus[ index ].flBonusTime = 0.0; + g_PlayerBonus[ index ].iType = READ_BYTE(); + strcpy ( g_PlayerBonus[ index ].sPlayerName, READ_STRING() ); + + switch ( g_PlayerBonus[ index ].iType ) + { + case RED_FLAG_STOLEN: + case BLUE_FLAG_STOLEN: + PlaySound( "ctf/flagtk.wav", 1 ); + break; + case RED_FLAG_CAPTURED: + case BLUE_FLAG_CAPTURED: + PlaySound( "ctf/flagcap.wav", 1 ); + break; + case RED_FLAG_RETURNED_PLAYER: + case BLUE_FLAG_RETURNED_PLAYER: + case RED_FLAG_RETURNED: + case BLUE_FLAG_RETURNED: + PlaySound( "ctf/flagret.wav", 1 ); + break; + } + + return 1; +} + +#endif \ No newline at end of file diff --git a/dmc/cl_dll/DMC_BSPFile.h b/dmc/cl_dll/DMC_BSPFile.h new file mode 100644 index 0000000..a6bf4a6 --- /dev/null +++ b/dmc/cl_dll/DMC_BSPFile.h @@ -0,0 +1,48 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( DMC_BSPFILE_H ) +#define DMC_BSPFILE_H +#ifdef _WIN32 +#pragma once +#endif + +// MINI-version of BSPFILE.H to support DeathMatch Classic's entity lump extraction stuff. + +#define BSPVERSION 30 + +typedef struct +{ + int fileofs, filelen; +} lump_t; + +#define LUMP_ENTITIES 0 +#define LUMP_PLANES 1 +#define LUMP_TEXTURES 2 +#define LUMP_VERTEXES 3 +#define LUMP_VISIBILITY 4 +#define LUMP_NODES 5 +#define LUMP_TEXINFO 6 +#define LUMP_FACES 7 +#define LUMP_LIGHTING 8 +#define LUMP_CLIPNODES 9 +#define LUMP_LEAFS 10 +#define LUMP_MARKSURFACES 11 +#define LUMP_EDGES 12 +#define LUMP_SURFEDGES 13 +#define LUMP_MODELS 14 + +#define HEADER_LUMPS 15 + +typedef struct +{ + int version; + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + + +#endif // DMC_BSPFILE_H \ No newline at end of file diff --git a/dmc/cl_dll/DMC_Teleporters.cpp b/dmc/cl_dll/DMC_Teleporters.cpp new file mode 100644 index 0000000..17107d6 --- /dev/null +++ b/dmc/cl_dll/DMC_Teleporters.cpp @@ -0,0 +1,520 @@ +//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "extdll.h" +#include "entity_state.h" +#include "pm_defs.h" +#include "pm_shared.h" +#include "pm_movevars.h" +#include "hud_iface.h" +#include "com_model.h" +#include "event_api.h" +#include "com_weapons.h" +#include "event_flags.h" +#include "DMC_BSPFile.h" +#include "cl_util.h" + +#include "FileSystem.h" + +extern IFileSystem *g_pFileSystem; + +extern "C" playermove_t *pmove; +extern int g_runfuncs; + +// Don't support more than MAX_TELE teleporters ( map still can load tho ) +#define MAX_TELES 256 + +extern Vector g_vecTeleMins[ MAX_TELES ]; +extern Vector g_vecTeleMaxs[ MAX_TELES ]; +extern int g_iTeleNum; +extern int g_iUser1; +extern bool g_bLoadedTeles; +vec3_t vecTempAngles; +bool bChangeAngles; + + +// We only care about two kinds of entities for now: Teleporters and their targets +// FIXME: After loading, store a pointer from teleporter to target instead of looking up all the time. +typedef enum +{ + // Entity is a teleporter + DMC_TELE = 0, + // Entity is a target + DMC_TARGET +} dmc_teletype_t; + +typedef struct +{ + // Type of entity + dmc_teletype_t type; + + // Classname + char classname[ 32 ]; + + // What this entity targets + char target[ 32 ]; + + // If entity is a target, the name tag it uses + char targetname[ 32 ]; + + // Orientation of the teleporter + float angles[3]; + + // Target origin + float origin[3]; + + // Bounding box of the teleporter + float absmin[3]; + float absmax[3]; + +} dmc_tele_t; + +// Teleporter/Target entity database +static dmc_tele_t s_teles[ MAX_TELES ]; +static int s_num_teles = 0; + +// We'll use this for playing the teleporting sounds locally. +static unsigned short s_usTeleport; + +/* +============================== +Dmc_SetKeyValue + +Fill in key/values fro the teleporter +============================== +*/ +void Dmc_SetKeyValue( dmc_tele_t *pTele, const char *key, const char *value ) +{ + float x, y, z; + + if ( !stricmp( key, "classname" ) ) + { + strcpy( pTele->classname, value ); + } + else if ( !stricmp( key, "target" ) ) + { + strcpy( pTele->target, value ); + } + else if ( !stricmp( key, "targetname" ) ) + { + strcpy( pTele->targetname, value ); + } + else if ( !stricmp( key, "angles" ) ) + { + if ( sscanf( value, "%f %f %f", &x, &y, &z ) == 3 ) + { + pTele->angles[ 0 ] = x ; + pTele->angles[ 1 ] = y; + pTele->angles[ 2 ] = z; + } + } + else if ( !stricmp( key, "origin" ) ) + { + if ( sscanf( value, "%f %f %f", &x, &y, &z ) == 3 ) + { + pTele->origin[ 0 ] = x; + pTele->origin[ 1 ] = y; + pTele->origin[ 2 ] = z; + } + } +} + +/* +============================== +Dmc_ParseTeleporter + +Evaluate Key/Value pairs for the entity +============================== +*/ +char *Dmc_ParseTeleporter( char *buffer, dmc_tele_t *pTele, int *error ) +{ + char key[256]; + char token[ 1024 ]; + int n; + + memset( pTele, 0, sizeof( *pTele ) ); + + while (1) + { + // Parse key + buffer = gEngfuncs.COM_ParseFile ( buffer, token ); + if ( token[0] == '}' ) + break; + + // Ran out of input buffer? + if ( !buffer ) + { + *error = 1; + break; + } + + // Store off the key + strcpy ( key, token ); + + // Fix heynames with trailing spaces + n = strlen( key ); + while (n && key[n-1] == ' ') + { + key[n-1] = 0; + n--; + } + + // Parse value + buffer = gEngfuncs.COM_ParseFile ( buffer, token ); + + // Ran out of buffer? + if (!buffer) + { + *error = 1; + break; + } + + // Hit the end instead of a value? + if ( token[0] == '}' ) + { + *error = 1; + break; + } + + if ( token[0] == '}' && token[1] == '(' ) + int k = 0; + + // Assign k/v pair + Dmc_SetKeyValue( pTele, key, token ); + } + + // Return what's left in the stream + return buffer; +} + +/* +============================== +Dmc_ProcessEnts + +Parse through entity lump looking for teleporters or targets +============================== +*/ +void Dmc_ProcessEnts( char *buffer ) +{ + char token[ 1024 ]; + dmc_tele_t *pTele = NULL; + int error = 0; + + // parse entities from entity lump of .bsp file + while ( 1 ) + { + // parse the opening brace + buffer = gEngfuncs.COM_ParseFile ( buffer, token ); + if (!buffer) + break; + + // Didn't find opening brace? + if ( token[0] != '{' ) + { + gEngfuncs.Con_Printf ("Dmc_ProcessEnts: found %s when expecting {\n", token ); + return; + } + + // Assume we're filling in this tele + pTele = &s_teles[ s_num_teles ]; + + // Fill in data + buffer = Dmc_ParseTeleporter( buffer, pTele, &error ); + + // Check for errors and abort if any + if ( error ) + { + gEngfuncs.Con_Printf ("Dmc_ProcessEnts: error parsing entities\n" ); + return; + } + + // Check classname + if ( stricmp( pTele->classname, "trigger_teleport" ) && stricmp( pTele->classname, "info_teleport_destination" ) ) + continue; + + // Set type based on classname + if ( !stricmp( pTele->classname, "trigger_teleport" ) ) + { + pTele->type = DMC_TELE; + } + else + { + pTele->type = DMC_TARGET; + } + + // If we got to here, we're using the entity + s_num_teles++; + + // No more room... + if ( s_num_teles >= MAX_TELES ) + break; + } +} + +/* +============================== +Dmc_LoadEntityLump + +Open the .bsp and read in the entity lump +============================== +*/ +char *Dmc_LoadEntityLump( const char *filename ) +{ + FileHandle_t fp; + int i; + dheader_t header; + int size; + lump_t *curLump; + char *buffer = NULL; + + fp = g_pFileSystem->Open( filename, "rb" ); + if ( !fp ) + return NULL; + + // Read in the .bsp header + if ( g_pFileSystem->Read(&header, sizeof(dheader_t), fp) != sizeof(dheader_t) ) + { + gEngfuncs.Con_Printf("Dmc_LoadEntityLump: Could not read BSP header for map [%s].\n", filename); + g_pFileSystem->Close(fp); + return NULL; + } + + // Check the version + i = header.version; + if ( i != 29 && i != 30) + { + g_pFileSystem->Close(fp); + gEngfuncs.Con_Printf("Dmc_LoadEntityLump: Map [%s] has incorrect BSP version (%i should be %i).\n", filename, i, BSPVERSION); + return NULL; + } + + // Get entity lump + curLump = &header.lumps[ LUMP_ENTITIES ]; + // and entity lump size + size = curLump->filelen; + + // Jump to it + g_pFileSystem->Seek( fp, curLump->fileofs, FILESYSTEM_SEEK_HEAD ); + + // Allocate sufficient memmory + buffer = (char *)malloc( size + 1 ); + if ( !buffer ) + { + g_pFileSystem->Close(fp); + gEngfuncs.Con_Printf("Dmc_LoadEntityLump: Couldn't allocate %i bytes\n", size + 1 ); + return NULL; + } + + // Read in the entity lump + g_pFileSystem->Read( buffer, size, fp ); + + // Terminate the string + buffer[ size ] = '\0'; + + if ( fp ) + { + g_pFileSystem->Close(fp); + } + + return buffer; +} + +/* +============================== +Dmc_LoadTeleporters + +Load in the .bsp file and process the entities +============================== +*/ +void Dmc_LoadTeleporters( const char *map ) +{ + char *buffer = NULL; + char filename[ 256 ]; + + sprintf( filename, "%s", map ); + + // TODO: Fix Slashes? + + // Reset count + s_num_teles = 0; + + // Load entity lump + buffer = Dmc_LoadEntityLump( filename ); + if ( !buffer ) + return; + + // Process buffer and extract teleporters/targets + Dmc_ProcessEnts( buffer ); + + // Discard buffer + free( buffer ); +} + +/* +============================== +Dmc_FindTarget + +Search entity list for target matching "name" +============================== +*/ +dmc_tele_t *Dmc_FindTarget( const char *name, int numtele, dmc_tele_t *pTeles ) +{ + int i; + dmc_tele_t *target; + + // Find the target + for ( i = 0; i < numtele; i++ ) + { + target = &pTeles[ i ]; + + if ( !target ) + continue; + + if ( stricmp( target->targetname, name ) ) + continue; + + return target; + } + + return NULL; +} + +/* +============================== +Dmc_TeleporterTouched + +Imparts the desired velocity to the player +after touching a teleporter. +============================== +*/ +void Dmc_TeleporterTouched( int numtele, dmc_tele_t *pTeles, dmc_tele_t *pTele, struct local_state_s *player ) +{ + int i; + dmc_tele_t *target; + pmtrace_t tr; + float flGravity = pmove->movevars->gravity; + + vec3_t forward, up, right; + + float zero[ 3 ] = { 0.0, 0.0, 0.0 }; + + // Find the target + target = Dmc_FindTarget( pTele->target, numtele, pTeles ); + + for ( i = 0; i < 3; i++ ) + player->playerstate.origin[ i ] = target->origin[ i ]; + + player->playerstate.origin[ 2 ] += 27; + + AngleVectors( target->angles, forward, right, up ); + player->client.velocity = forward * 300; + + // Play sound if appropriate + if ( s_usTeleport && g_runfuncs ) + { + //Adrian - This is a little hack to make the player face + //the destination angles as soon as we step out. + //Check view.cpp for the rest. + for ( i = 0; i < 3; i++ ) + vecTempAngles[ i ] = target->angles[ i ]; + + bChangeAngles = true; + + gEngfuncs.pfnPlaybackEvent( FEV_NOTHOST, NULL, s_usTeleport, 0.0, target->origin, zero, 0.0, 0.0, 0, 0, 0, 0 ); + } +} + +/* +============================== +Dmc_TouchTeleporters + +See if player is touching a teleporter ( not that kind of touching! ). +============================== +*/ +void Dmc_TouchTeleporters ( struct local_state_s *player, dmc_tele_t *pTeles, int numtele ) +{ + int i, j; + dmc_tele_t *pTele; + float absmin[3], absmax[3]; + float pmins[ 3 ] = { 13, 13, 24 }; + float pmaxs[ 3 ] = { 13, 13, 32 }; + vec3_t LengthVector; + int iTeleNum = 0; + + + // Determine player's bbox + for ( j = 0; j < 3; j++ ) + { + absmin[ j ] = player->playerstate.origin[ j ] - pmins[ j ]; + absmax[ j ] = player->playerstate.origin[ j ] + pmaxs[ j ]; + } + + for ( i = 0; i < numtele; i++ ) + { + pTele = &pTeles[ i ]; + if ( !pTele ) + continue; + + if ( pTele->type != DMC_TELE ) + continue; + + //Adrian - Load all the teleporter Mins and Max size. + //This comes via an event when the player connects. + if ( !g_bLoadedTeles ) + { + for ( int j = 0; j < 3; j++ ) + { + pTele->absmin[ j ] = g_vecTeleMins[ iTeleNum ][ j ] - 1.0; + pTele->absmax[ j ] = g_vecTeleMaxs[ iTeleNum ][ j ] + 1.0; + } + iTeleNum++; + + //Done going thru all the teleporters + if ( iTeleNum == g_iTeleNum ) + g_bLoadedTeles = true; + } + + if ( absmin[0] > pTele->absmax[0] + || absmin[1] > pTele->absmax[1] + || absmin[2] > pTele->absmax[2] + || absmax[0] < pTele->absmin[0] + || absmax[1] < pTele->absmin[1] + || absmax[2] < pTele->absmin[2] ) + continue; + + Dmc_TeleporterTouched( numtele, pTeles, pTele, player ); + + break; + } +} + +/* +============================== +Dmc_CheckTeleporters + +Load data if needed, otherwise just run checks on player's final position to see if teleporter needs +to impart velocity on the player. +============================== +*/ +void Dmc_CheckTeleporters( struct local_state_s *from, struct local_state_s *to ) +{ + static char current_level[ 128 ]; + + // See if we've changed to a new map + if ( stricmp( current_level, gEngfuncs.pfnGetLevelName() ) ) + { + strcpy( current_level, gEngfuncs.pfnGetLevelName() ); + Dmc_LoadTeleporters( current_level ); + + // Grab sound event + s_usTeleport = gEngfuncs.pfnPrecacheEvent( 1, "events/teleport.sc" ); + } + + // Run test, only if we're not a spectator + if ( g_iUser1 == OBS_NONE ) + Dmc_TouchTeleporters( to, s_teles, s_num_teles ); +} diff --git a/dmc/cl_dll/DMC_Teleporters.h b/dmc/cl_dll/DMC_Teleporters.h new file mode 100644 index 0000000..e14a3c7 --- /dev/null +++ b/dmc/cl_dll/DMC_Teleporters.h @@ -0,0 +1,16 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( DMC_TELEPORTERS_H ) +#define DMC_TELEPORTERS_H +#ifdef _WIN32 +#pragma once +#endif + +void Dmc_CheckTeleporters( struct local_state_s *from, struct local_state_s *to ); + +#endif // DMC_TELEPORTERS_H \ No newline at end of file diff --git a/dmc/cl_dll/Exports.h b/dmc/cl_dll/Exports.h new file mode 100644 index 0000000..cc66e15 --- /dev/null +++ b/dmc/cl_dll/Exports.h @@ -0,0 +1,117 @@ +// CL_DLLEXPORT is the client version of dllexport. It's turned off for secure clients. +#ifdef _WIN32 +#define CL_DLLEXPORT __declspec(dllexport) +#else +#define CL_DLLEXPORT __attribute__ ((visibility("default"))) +#endif + +extern "C" +{ + // From hl_weapons + void CL_DLLEXPORT HUD_PostRunCmd( struct local_state_s *from, struct local_state_s *to, struct usercmd_s *cmd, int runfuncs, double time, unsigned int random_seed ); + + // From cdll_int + int CL_DLLEXPORT Initialize( cl_enginefunc_t *pEnginefuncs, int iVersion ); + int CL_DLLEXPORT HUD_VidInit( void ); + void CL_DLLEXPORT HUD_Init( void ); + int CL_DLLEXPORT HUD_Redraw( float flTime, int intermission ); + int CL_DLLEXPORT HUD_UpdateClientData( client_data_t *cdata, float flTime ); + void CL_DLLEXPORT HUD_Reset ( void ); + void CL_DLLEXPORT HUD_PlayerMove( struct playermove_s *ppmove, int server ); + void CL_DLLEXPORT HUD_PlayerMoveInit( struct playermove_s *ppmove ); + char CL_DLLEXPORT HUD_PlayerMoveTexture( char *name ); + int CL_DLLEXPORT HUD_ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); + int CL_DLLEXPORT HUD_GetHullBounds( int hullnumber, float *mins, float *maxs ); + void CL_DLLEXPORT HUD_Frame( double time ); + void CL_DLLEXPORT HUD_VoiceStatus(int entindex, qboolean bTalking); + void CL_DLLEXPORT HUD_DirectorMessage( int iSize, void *pbuf ); + void CL_DLLEXPORT HUD_ChatInputPosition( int *x, int *y ); + + // From demo + void CL_DLLEXPORT Demo_ReadBuffer( int size, unsigned char *buffer ); + + // From entity + int CL_DLLEXPORT HUD_AddEntity( int type, struct cl_entity_s *ent, const char *modelname ); + void CL_DLLEXPORT HUD_CreateEntities( void ); + void CL_DLLEXPORT HUD_StudioEvent( const struct mstudioevent_s *event, const struct cl_entity_s *entity ); + void CL_DLLEXPORT HUD_TxferLocalOverrides( struct entity_state_s *state, const struct clientdata_s *client ); + void CL_DLLEXPORT HUD_ProcessPlayerState( struct entity_state_s *dst, const struct entity_state_s *src ); + void CL_DLLEXPORT HUD_TxferPredictionData ( struct entity_state_s *ps, const struct entity_state_s *pps, struct clientdata_s *pcd, const struct clientdata_s *ppcd, struct weapon_data_s *wd, const struct weapon_data_s *pwd ); + void CL_DLLEXPORT HUD_TempEntUpdate( double frametime, double client_time, double cl_gravity, struct tempent_s **ppTempEntFree, struct tempent_s **ppTempEntActive, int ( *Callback_AddVisibleEntity )( struct cl_entity_s *pEntity ), void ( *Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp ) ); + struct cl_entity_s CL_DLLEXPORT *HUD_GetUserEntity( int index ); + + // From in_camera + void CL_DLLEXPORT CAM_Think( void ); + int CL_DLLEXPORT CL_IsThirdPerson( void ); + void CL_DLLEXPORT CL_CameraOffset( float *ofs ); + + // From input + struct kbutton_s CL_DLLEXPORT *KB_Find( const char *name ); + void CL_DLLEXPORT CL_CreateMove ( float frametime, struct usercmd_s *cmd, int active ); + void CL_DLLEXPORT HUD_Shutdown( void ); + int CL_DLLEXPORT HUD_Key_Event( int eventcode, int keynum, const char *pszCurrentBinding ); + + // From inputw32 + void CL_DLLEXPORT IN_ActivateMouse( void ); + void CL_DLLEXPORT IN_DeactivateMouse( void ); + void CL_DLLEXPORT IN_MouseEvent (int mstate); + void CL_DLLEXPORT IN_Accumulate (void); + void CL_DLLEXPORT IN_ClearStates (void); + + // From tri + void CL_DLLEXPORT HUD_DrawNormalTriangles( void ); + void CL_DLLEXPORT HUD_DrawTransparentTriangles( void ); + + // From view + void CL_DLLEXPORT V_CalcRefdef( struct ref_params_s *pparams ); + + // From GameStudioModelRenderer + int CL_DLLEXPORT HUD_GetStudioModelInterface( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio ); +} + +/* +extern cldll_func_dst_t *g_pcldstAddrs; + +// Macros for the client receiving calls from the engine +#define RecClInitialize(a, b) (g_pcldstAddrs->pInitFunc(&a, &b)) +#define RecClHudInit() (g_pcldstAddrs->pHudInitFunc()) +#define RecClHudVidInit() (g_pcldstAddrs->pHudVidInitFunc()) +#define RecClHudRedraw(a, b) (g_pcldstAddrs->pHudRedrawFunc(&a, &b)) +#define RecClHudUpdateClientData(a, b) (g_pcldstAddrs->pHudUpdateClientDataFunc(&a, &b)) +#define RecClHudReset() (g_pcldstAddrs->pHudResetFunc()) +#define RecClClientMove(a, b) (g_pcldstAddrs->pClientMove(&a, &b)) +#define RecClClientMoveInit(a) (g_pcldstAddrs->pClientMoveInit(&a)) +#define RecClClientTextureType(a) (g_pcldstAddrs->pClientTextureType(&a)) +#define RecClIN_ActivateMouse() (g_pcldstAddrs->pIN_ActivateMouse()) +#define RecClIN_DeactivateMouse() (g_pcldstAddrs->pIN_DeactivateMouse()) +#define RecClIN_MouseEvent(a) (g_pcldstAddrs->pIN_MouseEvent(&a)) +#define RecClIN_ClearStates() (g_pcldstAddrs->pIN_ClearStates()) +#define RecClIN_Accumulate() (g_pcldstAddrs->pIN_Accumulate()) +#define RecClCL_CreateMove(a, b, c) (g_pcldstAddrs->pCL_CreateMove(&a, &b, &c)) +#define RecClCL_IsThirdPerson() (g_pcldstAddrs->pCL_IsThirdPerson()) +#define RecClCL_GetCameraOffsets(a) (g_pcldstAddrs->pCL_GetCameraOffsets(&a)) +#define RecClFindKey(a) (g_pcldstAddrs->pFindKey(&a)) +#define RecClCamThink() (g_pcldstAddrs->pCamThink()) +#define RecClCalcRefdef(a) (g_pcldstAddrs->pCalcRefdef(&a)) +#define RecClAddEntity(a, b, c) (g_pcldstAddrs->pAddEntity(&a, &b, &c)) +#define RecClCreateEntities() (g_pcldstAddrs->pCreateEntities()) +#define RecClDrawNormalTriangles() (g_pcldstAddrs->pDrawNormalTriangles()) +#define RecClDrawTransparentTriangles() (g_pcldstAddrs->pDrawTransparentTriangles()) +#define RecClStudioEvent(a, b) (g_pcldstAddrs->pStudioEvent(&a, &b)) +#define RecClPostRunCmd(a, b, c, d, e, f) (g_pcldstAddrs->pPostRunCmd(&a, &b, &c, &d, &e, &f)) +#define RecClShutdown() (g_pcldstAddrs->pShutdown()) +#define RecClTxferLocalOverrides(a, b) (g_pcldstAddrs->pTxferLocalOverrides(&a, &b)) +#define RecClProcessPlayerState(a, b) (g_pcldstAddrs->pProcessPlayerState(&a, &b)) +#define RecClTxferPredictionData(a, b, c, d, e, f) (g_pcldstAddrs->pTxferPredictionData(&a, &b, &c, &d, &e, &f)) +#define RecClReadDemoBuffer(a, b) (g_pcldstAddrs->pReadDemoBuffer(&a, &b)) +#define RecClConnectionlessPacket(a, b, c, d) (g_pcldstAddrs->pConnectionlessPacket(&a, &b, &c, &d)) +#define RecClGetHullBounds(a, b, c) (g_pcldstAddrs->pGetHullBounds(&a, &b, &c)) +#define RecClHudFrame(a) (g_pcldstAddrs->pHudFrame(&a)) +#define RecClKeyEvent(a, b, c) (g_pcldstAddrs->pKeyEvent(&a, &b, &c)) +#define RecClTempEntUpdate(a, b, c, d, e, f, g) (g_pcldstAddrs->pTempEntUpdate(&a, &b, &c, &d, &e, &f, &g)) +#define RecClGetUserEntity(a) (g_pcldstAddrs->pGetUserEntity(&a)) +#define RecClVoiceStatus(a, b) (g_pcldstAddrs->pVoiceStatus(&a, &b)) +#define RecClDirectorMessage(a, b) (g_pcldstAddrs->pDirectorMessage(&a, &b)) +#define RecClStudioInterface(a, b, c) (g_pcldstAddrs->pStudioInterface(&a, &b, &c)) +#define RecClChatInputPosition(a, b) (g_pcldstAddrs->pChatInputPosition(&a, &b)) +*/ \ No newline at end of file diff --git a/dmc/cl_dll/GameStudioModelRenderer.cpp b/dmc/cl_dll/GameStudioModelRenderer.cpp new file mode 100644 index 0000000..57cc8ce --- /dev/null +++ b/dmc/cl_dll/GameStudioModelRenderer.cpp @@ -0,0 +1,119 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include "studio.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "dlight.h" +#include "triangleapi.h" + +#include +#include +#include +#include + +#include "studio_util.h" +#include "r_studioint.h" + +#include "StudioModelRenderer.h" +#include "GameStudioModelRenderer.h" + +// +// Override the StudioModelRender virtual member functions here to implement custom bone +// setup, blending, etc. +// + +// Global engine <-> studio model rendering code interface +extern engine_studio_api_t IEngineStudio; + +// The renderer object, created on the stack. +CGameStudioModelRenderer g_StudioRenderer; + +/* +==================== +CGameStudioModelRenderer + +==================== +*/ +CGameStudioModelRenderer::CGameStudioModelRenderer( void ) +{ +} + +//////////////////////////////////// +// Hooks to class implementation +//////////////////////////////////// + +/* +==================== +R_StudioDrawPlayer + +==================== +*/ +int R_StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + return g_StudioRenderer.StudioDrawPlayer( flags, pplayer ); +} + +/* +==================== +R_StudioDrawModel + +==================== +*/ +int R_StudioDrawModel( int flags ) +{ + return g_StudioRenderer.StudioDrawModel( flags ); +} + +/* +==================== +R_StudioInit + +==================== +*/ +void R_StudioInit( void ) +{ + g_StudioRenderer.Init(); +} + +// The simple drawing interface we'll pass back to the engine +r_studio_interface_t studio = +{ + STUDIO_INTERFACE_VERSION, + R_StudioDrawModel, + R_StudioDrawPlayer, +}; + +/* +==================== +HUD_GetStudioModelInterface + +Export this function for the engine to use the studio renderer class to render objects. +==================== +*/ +extern "C" int EXPORT HUD_GetStudioModelInterface( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio ) +{ + if ( version != STUDIO_INTERFACE_VERSION ) + return 0; + + // Point the engine to our callbacks + *ppinterface = &studio; + + // Copy in engine helper functions + memcpy( &IEngineStudio, pstudio, sizeof( IEngineStudio ) ); + + // Initialize local variables, etc. + R_StudioInit(); + + // Success + return 1; +} diff --git a/dmc/cl_dll/GameStudioModelRenderer.h b/dmc/cl_dll/GameStudioModelRenderer.h new file mode 100644 index 0000000..7d06f70 --- /dev/null +++ b/dmc/cl_dll/GameStudioModelRenderer.h @@ -0,0 +1,26 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( GAMESTUDIOMODELRENDERER_H ) +#define GAMESTUDIOMODELRENDERER_H +#if defined( _WIN32 ) +#pragma once +#endif + +/* +==================== +CGameStudioModelRenderer + +==================== +*/ +class CGameStudioModelRenderer : public CStudioModelRenderer +{ +public: + CGameStudioModelRenderer( void ); +}; + +#endif // GAMESTUDIOMODELRENDERER_H \ No newline at end of file diff --git a/dmc/cl_dll/MOTD.cpp b/dmc/cl_dll/MOTD.cpp new file mode 100644 index 0000000..a06b231 --- /dev/null +++ b/dmc/cl_dll/MOTD.cpp @@ -0,0 +1,155 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// MOTD.cpp +// +// for displaying a server-sent message of the day +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +DECLARE_MESSAGE( m_MOTD, MOTD ); + +int CHudMOTD::MOTD_DISPLAY_TIME; + +int CHudMOTD :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( MOTD ); + + CVAR_CREATE( "motd_display_time", "15", 0 ); + + m_iFlags &= ~HUD_ACTIVE; // start out inactive + m_szMOTD[0] = 0; + + return 1; +} + +int CHudMOTD :: VidInit( void ) +{ + // Load sprites here + + return 1; +} + +void CHudMOTD :: Reset( void ) +{ + m_iFlags &= ~HUD_ACTIVE; // start out inactive + m_szMOTD[0] = 0; + m_iLines = 0; + m_flActiveRemaining = 0; +} + +#define LINE_HEIGHT 13 + +int CHudMOTD :: Draw( float fTime ) +{ + static float sfLastTime; + float fElapsed; + + // Draw MOTD line-by-line + if ( m_flActiveRemaining <= 0.0 ) + { + // finished with MOTD, disable it + m_szMOTD[0] = 0; + m_iLines = 0; + m_iFlags &= ~HUD_ACTIVE; + m_flActiveRemaining = 0.0; + return 1; + } + + fElapsed = gHUD.m_flTime - sfLastTime; + + // Don't let time go negative ( level transition? ) + fElapsed = max( 0.0, fElapsed ); + // Don't let time go hugely positive ( first connection to active server ? ) + fElapsed = min( 1.0, fElapsed ); + + // Remember last timestamp + sfLastTime = gHUD.m_flTime; + + // Remove a bit of time + m_flActiveRemaining -= fElapsed; + + // find the top of where the MOTD should be drawn, so the whole thing is centered in the screen + int ypos = max(((ScreenHeight - (m_iLines * LINE_HEIGHT)) / 2) - 40, 30 ); // shift it up slightly + char *ch = m_szMOTD; + while ( *ch ) + { + int line_length = 0; // count the length of the current line + for ( char *next_line = ch; *next_line != '\n' && *next_line != 0; next_line++ ) + line_length += gHUD.m_scrinfo.charWidths[ *next_line ]; + char *top = next_line; + if ( *top == '\n' ) + *top = 0; + else + top = NULL; + + // find where to start drawing the line + int xpos = (ScreenWidth - line_length) / 2; + + gHUD.DrawHudString( xpos, ypos, ScreenWidth, ch, 255, 180, 0 ); + + ypos += LINE_HEIGHT; + + if ( top ) // restore + *top = '\n'; + ch = next_line; + if ( *ch == '\n' ) + ch++; + + if ( ypos > (ScreenHeight - 20) ) + break; // don't let it draw too low + } + + return 1; +} + +int CHudMOTD :: MsgFunc_MOTD( const char *pszName, int iSize, void *pbuf ) +{ + if ( m_iFlags & HUD_ACTIVE ) + { + Reset(); // clear the current MOTD in prep for this one + } + + BEGIN_READ( pbuf, iSize ); + + int is_finished = READ_BYTE(); + strcat( m_szMOTD, READ_STRING() ); + + if ( is_finished ) + { + m_iFlags |= HUD_ACTIVE; + + MOTD_DISPLAY_TIME = max( 10, CVAR_GET_FLOAT( "motd_display_time" ) ); + + m_flActiveRemaining = MOTD_DISPLAY_TIME; + + for ( char *sz = m_szMOTD; *sz != 0; sz++ ) // count the number of lines in the MOTD + { + if ( *sz == '\n' ) + m_iLines++; + } + } + + return 1; +} + diff --git a/dmc/cl_dll/StudioModelRenderer.cpp b/dmc/cl_dll/StudioModelRenderer.cpp new file mode 100644 index 0000000..9f8b02b --- /dev/null +++ b/dmc/cl_dll/StudioModelRenderer.cpp @@ -0,0 +1,1723 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// studio_model.cpp +// routines for setting up to draw 3DStudio models + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include "studio.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "dlight.h" +#include "triangleapi.h" + +#include +#include +#include +#include + +#include "studio_util.h" +#include "r_studioint.h" + +#include "StudioModelRenderer.h" +#include "GameStudioModelRenderer.h" + +// Global engine <-> studio model rendering code interface +engine_studio_api_t IEngineStudio; + +///////////////////// +// Implementation of CStudioModelRenderer.h + +/* +==================== +Init + +==================== +*/ +void CStudioModelRenderer::Init( void ) +{ + // Set up some variables shared with engine + m_pCvarHiModels = IEngineStudio.GetCvar( "cl_himodels" ); + m_pCvarDeveloper = IEngineStudio.GetCvar( "developer" ); + m_pCvarDrawEntities = IEngineStudio.GetCvar( "r_drawentities" ); + + m_pChromeSprite = IEngineStudio.GetChromeSprite(); + + IEngineStudio.GetModelCounters( &m_pStudioModelCount, &m_pModelsDrawn ); + + // Get pointers to engine data structures + m_pbonetransform = (float (*)[MAXSTUDIOBONES][3][4])IEngineStudio.StudioGetBoneTransform(); + m_plighttransform = (float (*)[MAXSTUDIOBONES][3][4])IEngineStudio.StudioGetLightTransform(); + m_paliastransform = (float (*)[3][4])IEngineStudio.StudioGetAliasTransform(); + m_protationmatrix = (float (*)[3][4])IEngineStudio.StudioGetRotationMatrix(); +} + +/* +==================== +CStudioModelRenderer + +==================== +*/ +CStudioModelRenderer::CStudioModelRenderer( void ) +{ + m_fDoInterp = 1; + m_fGaitEstimation = 1; + m_pCurrentEntity = NULL; + m_pCvarHiModels = NULL; + m_pCvarDeveloper = NULL; + m_pCvarDrawEntities = NULL; + m_pChromeSprite = NULL; + m_pStudioModelCount = NULL; + m_pModelsDrawn = NULL; + m_protationmatrix = NULL; + m_paliastransform = NULL; + m_pbonetransform = NULL; + m_plighttransform = NULL; + m_pStudioHeader = NULL; + m_pBodyPart = NULL; + m_pSubModel = NULL; + m_pPlayerInfo = NULL; + m_pRenderModel = NULL; +} + +/* +==================== +~CStudioModelRenderer + +==================== +*/ +CStudioModelRenderer::~CStudioModelRenderer( void ) +{ +} + +/* +==================== +StudioCalcBoneAdj + +==================== +*/ +void CStudioModelRenderer::StudioCalcBoneAdj( float dadt, float *adj, const byte *pcontroller1, const byte *pcontroller2, byte mouthopen ) +{ + int i, j; + float value; + mstudiobonecontroller_t *pbonecontroller; + + pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex); + + for (j = 0; j < m_pStudioHeader->numbonecontrollers; j++) + { + i = pbonecontroller[j].index; + if (i <= 3) + { + // check for 360% wrapping + if (pbonecontroller[j].type & STUDIO_RLOOP) + { + if (abs(pcontroller1[i] - pcontroller2[i]) > 128) + { + int a, b; + a = (pcontroller1[j] + 128) % 256; + b = (pcontroller2[j] + 128) % 256; + value = ((a * dadt) + (b * (1 - dadt)) - 128) * (360.0/256.0) + pbonecontroller[j].start; + } + else + { + value = ((pcontroller1[i] * dadt + (pcontroller2[i]) * (1.0 - dadt))) * (360.0/256.0) + pbonecontroller[j].start; + } + } + else + { + value = (pcontroller1[i] * dadt + pcontroller2[i] * (1.0 - dadt)) / 255.0; + if (value < 0) value = 0; + if (value > 1.0) value = 1.0; + value = (1.0 - value) * pbonecontroller[j].start + value * pbonecontroller[j].end; + } + // Con_DPrintf( "%d %d %f : %f\n", m_pCurrentEntity->curstate.controller[j], m_pCurrentEntity->latched.prevcontroller[j], value, dadt ); + } + else + { + value = mouthopen / 64.0; + if (value > 1.0) value = 1.0; + value = (1.0 - value) * pbonecontroller[j].start + value * pbonecontroller[j].end; + // Con_DPrintf("%d %f\n", mouthopen, value ); + } + switch(pbonecontroller[j].type & STUDIO_TYPES) + { + case STUDIO_XR: + case STUDIO_YR: + case STUDIO_ZR: + adj[j] = value * (M_PI / 180.0); + break; + case STUDIO_X: + case STUDIO_Y: + case STUDIO_Z: + adj[j] = value; + break; + } + } +} + + +/* +==================== +StudioCalcBoneQuaterion + +==================== +*/ +void CStudioModelRenderer::StudioCalcBoneQuaterion( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *q ) +{ + int j, k; + vec4_t q1, q2; + vec3_t angle1, angle2; + mstudioanimvalue_t *panimvalue; + + for (j = 0; j < 3; j++) + { + if (panim->offset[j+3] == 0) + { + angle2[j] = angle1[j] = pbone->value[j+3]; // default; + } + else + { + panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[j+3]); + k = frame; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + while (panimvalue->num.total <= k) + { + k -= panimvalue->num.total; + panimvalue += panimvalue->num.valid + 1; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + } + // Bah, missing blend! + if (panimvalue->num.valid > k) + { + angle1[j] = panimvalue[k+1].value; + + if (panimvalue->num.valid > k + 1) + { + angle2[j] = panimvalue[k+2].value; + } + else + { + if (panimvalue->num.total > k + 1) + angle2[j] = angle1[j]; + else + angle2[j] = panimvalue[panimvalue->num.valid+2].value; + } + } + else + { + angle1[j] = panimvalue[panimvalue->num.valid].value; + if (panimvalue->num.total > k + 1) + { + angle2[j] = angle1[j]; + } + else + { + angle2[j] = panimvalue[panimvalue->num.valid + 2].value; + } + } + angle1[j] = pbone->value[j+3] + angle1[j] * pbone->scale[j+3]; + angle2[j] = pbone->value[j+3] + angle2[j] * pbone->scale[j+3]; + } + + if (pbone->bonecontroller[j+3] != -1) + { + angle1[j] += adj[pbone->bonecontroller[j+3]]; + angle2[j] += adj[pbone->bonecontroller[j+3]]; + } + } + + if (!VectorCompare( angle1, angle2 )) + { + AngleQuaternion( angle1, q1 ); + AngleQuaternion( angle2, q2 ); + QuaternionSlerp( q1, q2, s, q ); + } + else + { + AngleQuaternion( angle1, q ); + } +} + +/* +==================== +StudioCalcBonePosition + +==================== +*/ +void CStudioModelRenderer::StudioCalcBonePosition( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *pos ) +{ + int j, k; + mstudioanimvalue_t *panimvalue; + + for (j = 0; j < 3; j++) + { + pos[j] = pbone->value[j]; // default; + if (panim->offset[j] != 0) + { + panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[j]); + /* + if (i == 0 && j == 0) + Con_DPrintf("%d %d:%d %f\n", frame, panimvalue->num.valid, panimvalue->num.total, s ); + */ + + k = frame; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + // find span of values that includes the frame we want + while (panimvalue->num.total <= k) + { + k -= panimvalue->num.total; + panimvalue += panimvalue->num.valid + 1; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + } + // if we're inside the span + if (panimvalue->num.valid > k) + { + // and there's more data in the span + if (panimvalue->num.valid > k + 1) + { + pos[j] += (panimvalue[k+1].value * (1.0 - s) + s * panimvalue[k+2].value) * pbone->scale[j]; + } + else + { + pos[j] += panimvalue[k+1].value * pbone->scale[j]; + } + } + else + { + // are we at the end of the repeating values section and there's another section with data? + if (panimvalue->num.total <= k + 1) + { + pos[j] += (panimvalue[panimvalue->num.valid].value * (1.0 - s) + s * panimvalue[panimvalue->num.valid + 2].value) * pbone->scale[j]; + } + else + { + pos[j] += panimvalue[panimvalue->num.valid].value * pbone->scale[j]; + } + } + } + if ( pbone->bonecontroller[j] != -1 && adj ) + { + pos[j] += adj[pbone->bonecontroller[j]]; + } + } +} + +/* +==================== +StudioSlerpBones + +==================== +*/ +void CStudioModelRenderer::StudioSlerpBones( vec4_t q1[], float pos1[][3], vec4_t q2[], float pos2[][3], float s ) +{ + int i; + vec4_t q3; + float s1; + + if (s < 0) s = 0; + else if (s > 1.0) s = 1.0; + + s1 = 1.0 - s; + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + QuaternionSlerp( q1[i], q2[i], s, q3 ); + q1[i][0] = q3[0]; + q1[i][1] = q3[1]; + q1[i][2] = q3[2]; + q1[i][3] = q3[3]; + pos1[i][0] = pos1[i][0] * s1 + pos2[i][0] * s; + pos1[i][1] = pos1[i][1] * s1 + pos2[i][1] * s; + pos1[i][2] = pos1[i][2] * s1 + pos2[i][2] * s; + } +} + +/* +==================== +StudioGetAnim + +==================== +*/ +mstudioanim_t *CStudioModelRenderer::StudioGetAnim( model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc ) +{ + mstudioseqgroup_t *pseqgroup; + cache_user_t *paSequences; + + pseqgroup = (mstudioseqgroup_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqgroupindex) + pseqdesc->seqgroup; + + if (pseqdesc->seqgroup == 0) + { + return (mstudioanim_t *)((byte *)m_pStudioHeader + pseqdesc->animindex); + } + + paSequences = (cache_user_t *)m_pSubModel->submodels; + + if (paSequences == NULL) + { + paSequences = (cache_user_t *)IEngineStudio.Mem_Calloc( 16, sizeof( cache_user_t ) ); // UNDONE: leak! + m_pSubModel->submodels = (dmodel_t *)paSequences; + } + + if (!IEngineStudio.Cache_Check( (struct cache_user_s *)&(paSequences[pseqdesc->seqgroup]))) + { + gEngfuncs.Con_DPrintf("loading %s\n", pseqgroup->name ); + IEngineStudio.LoadCacheFile( pseqgroup->name, (struct cache_user_s *)&paSequences[pseqdesc->seqgroup] ); + } + return (mstudioanim_t *)((byte *)paSequences[pseqdesc->seqgroup].data + pseqdesc->animindex); +} + +/* +==================== +StudioPlayerBlend + +==================== +*/ +void CStudioModelRenderer::StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ) +{ + // calc up/down pointing + *pBlend = (*pPitch * 3); + if (*pBlend < pseqdesc->blendstart[0]) + { + *pPitch -= pseqdesc->blendstart[0] / 3.0; + *pBlend = 0; + } + else if (*pBlend > pseqdesc->blendend[0]) + { + *pPitch -= pseqdesc->blendend[0] / 3.0; + *pBlend = 255; + } + else + { + if (pseqdesc->blendend[0] - pseqdesc->blendstart[0] < 0.1) // catch qc error + *pBlend = 127; + else + *pBlend = 255 * (*pBlend - pseqdesc->blendstart[0]) / (pseqdesc->blendend[0] - pseqdesc->blendstart[0]); + *pPitch = 0; + } +} + +/* +==================== +StudioSetUpTransform + +==================== +*/ +void CStudioModelRenderer::StudioSetUpTransform (int trivial_accept) +{ + int i; + vec3_t angles; + vec3_t modelpos; + + + VectorCopy( m_pCurrentEntity->origin, modelpos ); + +// TODO: should really be stored with the entity instead of being reconstructed +// TODO: should use a look-up table +// TODO: could cache lazily, stored in the entity + angles[ROLL] = m_pCurrentEntity->curstate.angles[ROLL]; + angles[PITCH] = m_pCurrentEntity->curstate.angles[PITCH]; + angles[YAW] = m_pCurrentEntity->curstate.angles[YAW]; + + //Adrian - Have the model rotate ( weapon world models, powerups and armor. ) + //Yeah, we're too lazy to animate them!. + if ( strstr( m_pCurrentEntity->model->name, "g_" ) || strstr( m_pCurrentEntity->model->name, "pow_" ) || strstr( m_pCurrentEntity->model->name, "armour" ) ) + { + float timemod; + + timemod = fmod( gEngfuncs.GetClientTime(), 2.0 ); + + m_pCurrentEntity->angles[0] = 0; + m_pCurrentEntity->angles[YAW] = timemod * 180.0 - 90.0; + m_pCurrentEntity->angles[2] = 0; + + VectorCopy( m_pCurrentEntity->angles, m_pCurrentEntity->curstate.angles ); + } + + //Con_DPrintf("Angles %4.2f prev %4.2f for %i\n", angles[PITCH], m_pCurrentEntity->index); + //Con_DPrintf("movetype %d %d\n", m_pCurrentEntity->movetype, m_pCurrentEntity->aiment ); + if (m_pCurrentEntity->curstate.movetype == MOVETYPE_STEP) + { + float f = 0; + float d; + + // don't do it if the goalstarttime hasn't updated in a while. + + // NOTE: Because we need to interpolate multiplayer characters, the interpolation time limit + // was increased to 1.0 s., which is 2x the max lag we are accounting for. + + if ( ( m_clTime < m_pCurrentEntity->curstate.animtime + 1.0f ) && + ( m_pCurrentEntity->curstate.animtime != m_pCurrentEntity->latched.prevanimtime ) ) + { + f = (m_clTime - m_pCurrentEntity->curstate.animtime) / (m_pCurrentEntity->curstate.animtime - m_pCurrentEntity->latched.prevanimtime); + //Con_DPrintf("%4.2f %.2f %.2f\n", f, m_pCurrentEntity->curstate.animtime, m_clTime); + } + + if (m_fDoInterp) + { + // ugly hack to interpolate angle, position. current is reached 0.1 seconds after being set + f = f - 1.0; + } + else + { + f = 0; + } + + for (i = 0; i < 3; i++) + { + modelpos[i] += (m_pCurrentEntity->origin[i] - m_pCurrentEntity->latched.prevorigin[i]) * f; + } + + // NOTE: Because multiplayer lag can be relatively large, we don't want to cap + // f at 1.5 anymore. + //if (f > -1.0 && f < 1.5) {} + +// gEngfuncs.Con_DPrintf("%.0f %.0f\n",m_pCurrentEntity->angles[0][YAW], m_pCurrentEntity->angles[1][YAW] ); + for (i = 0; i < 3; i++) + { + float ang1, ang2; + + ang1 = m_pCurrentEntity->angles[i]; + ang2 = m_pCurrentEntity->latched.prevangles[i]; + + d = ang1 - ang2; + if (d > 180) + { + d -= 360; + } + else if (d < -180) + { + d += 360; + } + + angles[i] += d * f; + } + //Con_DPrintf("%.3f \n", f ); + } + else if ( m_pCurrentEntity->curstate.movetype != MOVETYPE_NONE ) + { + VectorCopy( m_pCurrentEntity->angles, angles ); + } + + //Con_DPrintf("%.0f %0.f %0.f\n", modelpos[0], modelpos[1], modelpos[2] ); +// gEngfuncs.Con_DPrintf("%.0f %0.f %0.f\n", angles[0], angles[1], angles[2] ); + + + angles[PITCH] = -angles[PITCH]; + AngleMatrix (angles, (*m_protationmatrix)); + + if ( !IEngineStudio.IsHardware() ) + { + static float viewmatrix[3][4]; + + VectorCopy (m_vRight, viewmatrix[0]); + VectorCopy (m_vUp, viewmatrix[1]); + VectorInverse (viewmatrix[1]); + VectorCopy (m_vNormal, viewmatrix[2]); + + (*m_protationmatrix)[0][3] = modelpos[0] - m_vRenderOrigin[0]; + (*m_protationmatrix)[1][3] = modelpos[1] - m_vRenderOrigin[1]; + (*m_protationmatrix)[2][3] = modelpos[2] - m_vRenderOrigin[2]; + + ConcatTransforms (viewmatrix, (*m_protationmatrix), (*m_paliastransform)); + + // do the scaling up of x and y to screen coordinates as part of the transform + // for the unclipped case (it would mess up clipping in the clipped case). + // Also scale down z, so 1/z is scaled 31 bits for free, and scale down x and y + // correspondingly so the projected x and y come out right + // FIXME: make this work for clipped case too? + if (trivial_accept) + { + for (i=0 ; i<4 ; i++) + { + (*m_paliastransform)[0][i] *= m_fSoftwareXScale * + (1.0 / (ZISCALE * 0x10000)); + (*m_paliastransform)[1][i] *= m_fSoftwareYScale * + (1.0 / (ZISCALE * 0x10000)); + (*m_paliastransform)[2][i] *= 1.0 / (ZISCALE * 0x10000); + + } + } + } + + (*m_protationmatrix)[0][3] = modelpos[0]; + (*m_protationmatrix)[1][3] = modelpos[1]; + (*m_protationmatrix)[2][3] = modelpos[2]; +} + + +/* +==================== +StudioEstimateInterpolant + +==================== +*/ +float CStudioModelRenderer::StudioEstimateInterpolant( void ) +{ + float dadt = 1.0; + + if ( m_fDoInterp && ( m_pCurrentEntity->curstate.animtime >= m_pCurrentEntity->latched.prevanimtime + 0.01 ) ) + { + dadt = (m_clTime - m_pCurrentEntity->curstate.animtime) / 0.1; + if (dadt > 2.0) + { + dadt = 2.0; + } + } + return dadt; +} + +/* +==================== +StudioCalcRotations + +==================== +*/ +void CStudioModelRenderer::StudioCalcRotations ( float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f ) +{ + int i; + int frame; + mstudiobone_t *pbone; + + float s; + float adj[MAXSTUDIOCONTROLLERS]; + float dadt; + + if (f > pseqdesc->numframes - 1) + { + f = 0; // bah, fix this bug with changing sequences too fast + } + // BUG ( somewhere else ) but this code should validate this data. + // This could cause a crash if the frame # is negative, so we'll go ahead + // and clamp it here + else if ( f < -0.01 ) + { + f = -0.01; + } + + frame = (int)f; + + // Con_DPrintf("%d %.4f %.4f %.4f %.4f %d\n", m_pCurrentEntity->curstate.sequence, m_clTime, m_pCurrentEntity->animtime, m_pCurrentEntity->frame, f, frame ); + + // Con_DPrintf( "%f %f %f\n", m_pCurrentEntity->angles[ROLL], m_pCurrentEntity->angles[PITCH], m_pCurrentEntity->angles[YAW] ); + + // Con_DPrintf("frame %d %d\n", frame1, frame2 ); + + + dadt = StudioEstimateInterpolant( ); + s = (f - frame); + + // add in programtic controllers + pbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + StudioCalcBoneAdj( dadt, adj, m_pCurrentEntity->curstate.controller, m_pCurrentEntity->latched.prevcontroller, m_pCurrentEntity->mouth.mouthopen ); + + for (i = 0; i < m_pStudioHeader->numbones; i++, pbone++, panim++) + { + StudioCalcBoneQuaterion( frame, s, pbone, panim, adj, q[i] ); + + StudioCalcBonePosition( frame, s, pbone, panim, adj, pos[i] ); + // if (0 && i == 0) + // Con_DPrintf("%d %d %d %d\n", m_pCurrentEntity->curstate.sequence, frame, j, k ); + } + + if (pseqdesc->motiontype & STUDIO_X) + { + pos[pseqdesc->motionbone][0] = 0.0; + } + if (pseqdesc->motiontype & STUDIO_Y) + { + pos[pseqdesc->motionbone][1] = 0.0; + } + if (pseqdesc->motiontype & STUDIO_Z) + { + pos[pseqdesc->motionbone][2] = 0.0; + } + + s = 0 * ((1.0 - (f - (int)(f))) / (pseqdesc->numframes)) * m_pCurrentEntity->curstate.framerate; + + if (pseqdesc->motiontype & STUDIO_LX) + { + pos[pseqdesc->motionbone][0] += s * pseqdesc->linearmovement[0]; + } + if (pseqdesc->motiontype & STUDIO_LY) + { + pos[pseqdesc->motionbone][1] += s * pseqdesc->linearmovement[1]; + } + if (pseqdesc->motiontype & STUDIO_LZ) + { + pos[pseqdesc->motionbone][2] += s * pseqdesc->linearmovement[2]; + } +} + +/* +==================== +Studio_FxTransform + +==================== +*/ +void CStudioModelRenderer::StudioFxTransform( cl_entity_t *ent, float transform[3][4] ) +{ + + switch( ent->curstate.renderfx ) + { + case kRenderFxDistort: + case kRenderFxHologram: + if ( gEngfuncs.pfnRandomLong(0,49) == 0 ) + { + int axis = gEngfuncs.pfnRandomLong(0,1); + if ( axis == 1 ) // Choose between x & z + axis = 2; + VectorScale( transform[axis], gEngfuncs.pfnRandomFloat(1,1.484), transform[axis] ); + } + else if ( gEngfuncs.pfnRandomLong(0,49) == 0 ) + { + float offset; + int axis = gEngfuncs.pfnRandomLong(0,1); + if ( axis == 1 ) // Choose between x & z + axis = 2; + offset = gEngfuncs.pfnRandomFloat(-10,10); + transform[gEngfuncs.pfnRandomLong(0,2)][3] += offset; + } + break; + case kRenderFxExplode: + { + float scale; + + scale = 1.0 + ( m_clTime - ent->curstate.animtime) * 10.0; + if ( scale > 2 ) // Don't blow up more than 200% + scale = 2; + transform[0][1] *= scale; + transform[1][1] *= scale; + transform[2][1] *= scale; + } + break; + + } +} + +/* +==================== +StudioEstimateFrame + +==================== +*/ +float CStudioModelRenderer::StudioEstimateFrame( mstudioseqdesc_t *pseqdesc ) +{ + double dfdt, f; + + if ( m_fDoInterp ) + { + if ( m_clTime < m_pCurrentEntity->curstate.animtime ) + { + dfdt = 0; + } + else + { + dfdt = (m_clTime - m_pCurrentEntity->curstate.animtime) * m_pCurrentEntity->curstate.framerate * pseqdesc->fps; + + } + } + else + { + dfdt = 0; + } + + if (pseqdesc->numframes <= 1) + { + f = 0; + } + else + { + f = (m_pCurrentEntity->curstate.frame * (pseqdesc->numframes - 1)) / 256.0; + } + + f += dfdt; + + if (pseqdesc->flags & STUDIO_LOOPING) + { + if (pseqdesc->numframes > 1) + { + f -= (int)(f / (pseqdesc->numframes - 1)) * (pseqdesc->numframes - 1); + } + if (f < 0) + { + f += (pseqdesc->numframes - 1); + } + } + else + { + if (f >= pseqdesc->numframes - 1.001) + { + f = pseqdesc->numframes - 1.001; + } + if (f < 0.0) + { + f = 0.0; + } + } + return f; +} + +/* +==================== +StudioSetupBones + +==================== +*/ +void CStudioModelRenderer::StudioSetupBones ( void ) +{ + int i; + double f; + + mstudiobone_t *pbones; + mstudioseqdesc_t *pseqdesc; + mstudioanim_t *panim; + + static float pos[MAXSTUDIOBONES][3]; + static vec4_t q[MAXSTUDIOBONES]; + float bonematrix[3][4]; + + static float pos2[MAXSTUDIOBONES][3]; + static vec4_t q2[MAXSTUDIOBONES]; + static float pos3[MAXSTUDIOBONES][3]; + static vec4_t q3[MAXSTUDIOBONES]; + static float pos4[MAXSTUDIOBONES][3]; + static vec4_t q4[MAXSTUDIOBONES]; + + if (m_pCurrentEntity->curstate.sequence >= m_pStudioHeader->numseq) + { + m_pCurrentEntity->curstate.sequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + f = StudioEstimateFrame( pseqdesc ); + + if (m_pCurrentEntity->latched.prevframe > f) + { + //Con_DPrintf("%f %f\n", m_pCurrentEntity->prevframe, f ); + } + + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + + if (pseqdesc->numblends > 1) + { + float s; + float dadt; + + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos2, q2, pseqdesc, panim, f ); + + dadt = StudioEstimateInterpolant(); + s = (m_pCurrentEntity->curstate.blending[0] * dadt + m_pCurrentEntity->latched.prevblending[0] * (1.0 - dadt)) / 255.0; + + StudioSlerpBones( q, pos, q2, pos2, s ); + + if (pseqdesc->numblends == 4) + { + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos3, q3, pseqdesc, panim, f ); + + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos4, q4, pseqdesc, panim, f ); + + s = (m_pCurrentEntity->curstate.blending[0] * dadt + m_pCurrentEntity->latched.prevblending[0] * (1.0 - dadt)) / 255.0; + StudioSlerpBones( q3, pos3, q4, pos4, s ); + + s = (m_pCurrentEntity->curstate.blending[1] * dadt + m_pCurrentEntity->latched.prevblending[1] * (1.0 - dadt)) / 255.0; + StudioSlerpBones( q, pos, q3, pos3, s ); + } + } + + if (m_fDoInterp && + m_pCurrentEntity->latched.sequencetime && + ( m_pCurrentEntity->latched.sequencetime + 0.2 > m_clTime ) && + ( m_pCurrentEntity->latched.prevsequence < m_pStudioHeader->numseq )) + { + // blend from last sequence + static float pos1b[MAXSTUDIOBONES][3]; + static vec4_t q1b[MAXSTUDIOBONES]; + float s; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->latched.prevsequence; + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + // clip prevframe + StudioCalcRotations( pos1b, q1b, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + if (pseqdesc->numblends > 1) + { + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos2, q2, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + s = (m_pCurrentEntity->latched.prevseqblending[0]) / 255.0; + StudioSlerpBones( q1b, pos1b, q2, pos2, s ); + + if (pseqdesc->numblends == 4) + { + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos3, q3, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos4, q4, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + s = (m_pCurrentEntity->latched.prevseqblending[0]) / 255.0; + StudioSlerpBones( q3, pos3, q4, pos4, s ); + + s = (m_pCurrentEntity->latched.prevseqblending[1]) / 255.0; + StudioSlerpBones( q1b, pos1b, q3, pos3, s ); + } + } + + s = 1.0 - (m_clTime - m_pCurrentEntity->latched.sequencetime) / 0.2; + StudioSlerpBones( q, pos, q1b, pos1b, s ); + } + else + { + //Con_DPrintf("prevframe = %4.2f\n", f); + m_pCurrentEntity->latched.prevframe = f; + } + + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + // calc gait animation + if (m_pPlayerInfo && m_pPlayerInfo->gaitsequence != 0) + { + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pPlayerInfo->gaitsequence; + + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + StudioCalcRotations( pos2, q2, pseqdesc, panim, m_pPlayerInfo->gaitframe ); + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + if (strcmp( pbones[i].name, "Bip01 Spine") == 0) + break; + memcpy( pos[i], pos2[i], sizeof( pos[i] )); + memcpy( q[i], q2[i], sizeof( q[i] )); + } + } + + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + QuaternionMatrix( q[i], bonematrix ); + + bonematrix[0][3] = pos[i][0]; + bonematrix[1][3] = pos[i][1]; + bonematrix[2][3] = pos[i][2]; + + if (pbones[i].parent == -1) + { + + //Adrian - Scale the player's model height down + //NOTE: This is only relative for a few models, + //some other models might need a different number. + if ( m_pCurrentEntity->player ) + bonematrix[2][2] *= 0.89; // (2,2) is Z component + + if ( IEngineStudio.IsHardware() ) + { + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + } + else + { + ConcatTransforms ((*m_paliastransform), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + } + + // Apply client-side effects to the transformation matrix + StudioFxTransform( m_pCurrentEntity, (*m_pbonetransform)[i] ); + } + else + { + ConcatTransforms ((*m_pbonetransform)[pbones[i].parent], bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_plighttransform)[pbones[i].parent], bonematrix, (*m_plighttransform)[i]); + } + } + +} + + +/* +==================== +StudioSaveBones + +==================== +*/ +void CStudioModelRenderer::StudioSaveBones( void ) +{ + int i; + + mstudiobone_t *pbones; + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + m_nCachedBones = m_pStudioHeader->numbones; + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + strcpy( m_nCachedBoneNames[i], pbones[i].name ); + MatrixCopy( (*m_pbonetransform)[i], m_rgCachedBoneTransform[i] ); + MatrixCopy( (*m_plighttransform)[i], m_rgCachedLightTransform[i] ); + } +} + + +/* +==================== +StudioMergeBones + +==================== +*/ +void CStudioModelRenderer::StudioMergeBones ( model_t *m_pSubModel ) +{ + int i, j; + double f; + int do_hunt = true; + + mstudiobone_t *pbones; + mstudioseqdesc_t *pseqdesc; + mstudioanim_t *panim; + + static float pos[MAXSTUDIOBONES][3]; + float bonematrix[3][4]; + static vec4_t q[MAXSTUDIOBONES]; + + if (m_pCurrentEntity->curstate.sequence >= m_pStudioHeader->numseq) + { + m_pCurrentEntity->curstate.sequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + f = StudioEstimateFrame( pseqdesc ); + + if (m_pCurrentEntity->latched.prevframe > f) + { + //Con_DPrintf("%f %f\n", m_pCurrentEntity->prevframe, f ); + } + + panim = StudioGetAnim( m_pSubModel, pseqdesc ); + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + for (j = 0; j < m_nCachedBones; j++) + { + if (stricmp(pbones[i].name, m_nCachedBoneNames[j]) == 0) + { + MatrixCopy( m_rgCachedBoneTransform[j], (*m_pbonetransform)[i] ); + MatrixCopy( m_rgCachedLightTransform[j], (*m_plighttransform)[i] ); + break; + } + } + if (j >= m_nCachedBones) + { + QuaternionMatrix( q[i], bonematrix ); + + bonematrix[0][3] = pos[i][0]; + bonematrix[1][3] = pos[i][1]; + bonematrix[2][3] = pos[i][2]; + + if (pbones[i].parent == -1) + { + if ( IEngineStudio.IsHardware() ) + { + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + //MatrixCopy( (*m_pbonetransform)[i], (*m_plighttransform)[i] ); + } + else + { + ConcatTransforms ((*m_paliastransform), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + } + + // Apply client-side effects to the transformation matrix + StudioFxTransform( m_pCurrentEntity, (*m_pbonetransform)[i] ); + } + else + { + ConcatTransforms ((*m_pbonetransform)[pbones[i].parent], bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_plighttransform)[pbones[i].parent], bonematrix, (*m_plighttransform)[i]); + } + } + } +} + +/* +==================== +StudioDrawModel + +==================== +*/ +int CStudioModelRenderer::StudioDrawModel( int flags ) +{ + alight_t lighting; + vec3_t dir; + + m_pCurrentEntity = IEngineStudio.GetCurrentEntity(); + IEngineStudio.GetTimes( &m_nFrameCount, &m_clTime, &m_clOldTime ); + IEngineStudio.GetViewInfo( m_vRenderOrigin, m_vUp, m_vRight, m_vNormal ); + IEngineStudio.GetAliasScale( &m_fSoftwareXScale, &m_fSoftwareYScale ); + + if (m_pCurrentEntity->curstate.renderfx == kRenderFxDeadPlayer) + { + entity_state_t deadplayer; + + int result; + int save_interp; + + if (m_pCurrentEntity->curstate.renderamt <= 0 || m_pCurrentEntity->curstate.renderamt > gEngfuncs.GetMaxClients() ) + return 0; + + // get copy of player + deadplayer = *(IEngineStudio.GetPlayerState( m_pCurrentEntity->curstate.renderamt - 1 )); //cl.frames[cl.parsecount & CL_UPDATE_MASK].playerstate[m_pCurrentEntity->curstate.renderamt-1]; + + // clear weapon, movement state + deadplayer.number = m_pCurrentEntity->curstate.renderamt; + deadplayer.weaponmodel = 0; + deadplayer.gaitsequence = 0; + + deadplayer.movetype = MOVETYPE_NONE; + VectorCopy( m_pCurrentEntity->curstate.angles, deadplayer.angles ); + VectorCopy( m_pCurrentEntity->curstate.origin, deadplayer.origin ); + + save_interp = m_fDoInterp; + m_fDoInterp = 0; + + // draw as though it were a player + result = StudioDrawPlayer( flags, &deadplayer ); + + m_fDoInterp = save_interp; + return result; + } + + m_pRenderModel = m_pCurrentEntity->model; + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (m_pRenderModel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + IEngineStudio.SetRenderModel( m_pRenderModel ); + + StudioSetUpTransform( 0 ); + + if (flags & STUDIO_RENDER) + { + // see if the bounding box lets us trivially reject, also sets + if (!IEngineStudio.StudioCheckBBox ()) + return 0; + + (*m_pModelsDrawn)++; + (*m_pStudioModelCount)++; // render data cache cookie + + if (m_pStudioHeader->numbodyparts == 0) + return 1; + } + + if (m_pCurrentEntity->curstate.movetype == MOVETYPE_FOLLOW) + { + StudioMergeBones( m_pRenderModel ); + } + else + { + StudioSetupBones( ); + } + + StudioSaveBones( ); + + if (flags & STUDIO_EVENTS) + { + StudioCalcAttachments( ); + IEngineStudio.StudioClientEvents( ); + // copy attachments into global entity array + if ( m_pCurrentEntity->index > 0 ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( m_pCurrentEntity->index ); + + memcpy( ent->attachment, m_pCurrentEntity->attachment, sizeof( vec3_t ) * 4 ); + } + } + + if (flags & STUDIO_RENDER) + { + lighting.plightvec = dir; + IEngineStudio.StudioDynamicLight(m_pCurrentEntity, &lighting ); + + IEngineStudio.StudioEntityLight( &lighting ); + + //Scale the spike model lighting by a factor of 30 ( H4X!! ) + if ( strstr ( m_pCurrentEntity->model->name, "spike.mdl" ) ) + lighting.ambientlight *= 30; + + // model and frame independant + IEngineStudio.StudioSetupLighting (&lighting); + + // get remap colors + m_nTopColor = m_pCurrentEntity->curstate.colormap & 0xFF; + m_nBottomColor = (m_pCurrentEntity->curstate.colormap & 0xFF00) >> 8; + + IEngineStudio.StudioSetRemapColors( m_nTopColor, m_nBottomColor ); + + StudioRenderModel( ); + } + + return 1; +} + +/* +==================== +StudioEstimateGait + +==================== +*/ +void CStudioModelRenderer::StudioEstimateGait( entity_state_t *pplayer ) +{ + float dt; + vec3_t est_velocity; + + dt = (m_clTime - m_clOldTime); + if (dt < 0) + dt = 0; + else if (dt > 1.0) + dt = 1; + + if (dt == 0 || m_pPlayerInfo->renderframe == m_nFrameCount) + { + m_flGaitMovement = 0; + return; + } + + // VectorAdd( pplayer->velocity, pplayer->prediction_error, est_velocity ); + if ( m_fGaitEstimation ) + { + VectorSubtract( m_pCurrentEntity->origin, m_pPlayerInfo->prevgaitorigin, est_velocity ); + VectorCopy( m_pCurrentEntity->origin, m_pPlayerInfo->prevgaitorigin ); + m_flGaitMovement = Length( est_velocity ); + if (dt <= 0 || m_flGaitMovement / dt < 5) + { + m_flGaitMovement = 0; + est_velocity[0] = 0; + est_velocity[1] = 0; + } + } + else + { + VectorCopy( pplayer->velocity, est_velocity ); + m_flGaitMovement = Length( est_velocity ) * dt; + } + + if (est_velocity[1] == 0 && est_velocity[0] == 0) + { + float flYawDiff = m_pCurrentEntity->angles[YAW] - m_pPlayerInfo->gaityaw; + flYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360; + if (flYawDiff > 180) + flYawDiff -= 360; + if (flYawDiff < -180) + flYawDiff += 360; + + if (dt < 0.25) + flYawDiff *= dt * 4; + else + flYawDiff *= dt; + + m_pPlayerInfo->gaityaw += flYawDiff; + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - (int)(m_pPlayerInfo->gaityaw / 360) * 360; + + m_flGaitMovement = 0; + } + else + { + m_pPlayerInfo->gaityaw = (atan2(est_velocity[1], est_velocity[0]) * 180 / M_PI); + if (m_pPlayerInfo->gaityaw > 180) + m_pPlayerInfo->gaityaw = 180; + if (m_pPlayerInfo->gaityaw < -180) + m_pPlayerInfo->gaityaw = -180; + } + +} + +/* +==================== +StudioProcessGait + +==================== +*/ +void CStudioModelRenderer::StudioProcessGait( entity_state_t *pplayer ) +{ + mstudioseqdesc_t *pseqdesc; + float dt; + int iBlend; + float flYaw; // view direction relative to movement + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + StudioPlayerBlend( pseqdesc, &iBlend, &m_pCurrentEntity->angles[PITCH] ); + + m_pCurrentEntity->latched.prevangles[PITCH] = m_pCurrentEntity->angles[PITCH]; + m_pCurrentEntity->curstate.blending[0] = iBlend; + m_pCurrentEntity->latched.prevblending[0] = m_pCurrentEntity->curstate.blending[0]; + m_pCurrentEntity->latched.prevseqblending[0] = m_pCurrentEntity->curstate.blending[0]; + + // Con_DPrintf("%f %d\n", m_pCurrentEntity->angles[PITCH], m_pCurrentEntity->blending[0] ); + + dt = (m_clTime - m_clOldTime); + if (dt < 0) + dt = 0; + else if (dt > 1.0) + dt = 1; + + StudioEstimateGait( pplayer ); + + // Con_DPrintf("%f %f\n", m_pCurrentEntity->angles[YAW], m_pPlayerInfo->gaityaw ); + + // calc side to side turning + flYaw = m_pCurrentEntity->angles[YAW] - m_pPlayerInfo->gaityaw; + flYaw = flYaw - (int)(flYaw / 360) * 360; + if (flYaw < -180) + flYaw = flYaw + 360; + if (flYaw > 180) + flYaw = flYaw - 360; + + if (flYaw > 120) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - 180; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw - 180; + } + else if (flYaw < -120) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw + 180; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw + 180; + } + + // adjust torso + m_pCurrentEntity->curstate.controller[0] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->curstate.controller[1] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->curstate.controller[2] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->curstate.controller[3] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->latched.prevcontroller[0] = m_pCurrentEntity->curstate.controller[0]; + m_pCurrentEntity->latched.prevcontroller[1] = m_pCurrentEntity->curstate.controller[1]; + m_pCurrentEntity->latched.prevcontroller[2] = m_pCurrentEntity->curstate.controller[2]; + m_pCurrentEntity->latched.prevcontroller[3] = m_pCurrentEntity->curstate.controller[3]; + + m_pCurrentEntity->angles[YAW] = m_pPlayerInfo->gaityaw; + if (m_pCurrentEntity->angles[YAW] < -0) + m_pCurrentEntity->angles[YAW] += 360; + m_pCurrentEntity->latched.prevangles[YAW] = m_pCurrentEntity->angles[YAW]; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + pplayer->gaitsequence; + + // calc gait frame + if (pseqdesc->linearmovement[0] > 0) + { + m_pPlayerInfo->gaitframe += (m_flGaitMovement / pseqdesc->linearmovement[0]) * pseqdesc->numframes; + } + else + { + m_pPlayerInfo->gaitframe += pseqdesc->fps * dt; + } + + // do modulo + m_pPlayerInfo->gaitframe = m_pPlayerInfo->gaitframe - (int)(m_pPlayerInfo->gaitframe / pseqdesc->numframes) * pseqdesc->numframes; + if (m_pPlayerInfo->gaitframe < 0) + m_pPlayerInfo->gaitframe += pseqdesc->numframes; +} + +/* +==================== +StudioDrawPlayer + +==================== +*/ +int CStudioModelRenderer::StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + alight_t lighting; + vec3_t dir; + + m_pCurrentEntity = IEngineStudio.GetCurrentEntity(); + IEngineStudio.GetTimes( &m_nFrameCount, &m_clTime, &m_clOldTime ); + IEngineStudio.GetViewInfo( m_vRenderOrigin, m_vUp, m_vRight, m_vNormal ); + IEngineStudio.GetAliasScale( &m_fSoftwareXScale, &m_fSoftwareYScale ); + + // Con_DPrintf("DrawPlayer %d\n", m_pCurrentEntity->blending[0] ); + + // Con_DPrintf("DrawPlayer %d %d (%d)\n", m_nFrameCount, pplayer->player_index, m_pCurrentEntity->curstate.sequence ); + + // Con_DPrintf("Player %.2f %.2f %.2f\n", pplayer->velocity[0], pplayer->velocity[1], pplayer->velocity[2] ); + + m_nPlayerIndex = pplayer->number - 1; + + if (m_nPlayerIndex < 0 || m_nPlayerIndex >= gEngfuncs.GetMaxClients()) + return 0; + + m_pRenderModel = IEngineStudio.SetupPlayerModel( m_nPlayerIndex ); + if (m_pRenderModel == NULL) + return 0; + + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (m_pRenderModel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + IEngineStudio.SetRenderModel( m_pRenderModel ); + + if (pplayer->gaitsequence) + { + vec3_t orig_angles; + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + + VectorCopy( m_pCurrentEntity->angles, orig_angles ); + + StudioProcessGait( pplayer ); + + m_pPlayerInfo->gaitsequence = pplayer->gaitsequence; + m_pPlayerInfo = NULL; + + StudioSetUpTransform( 0 ); + VectorCopy( orig_angles, m_pCurrentEntity->angles ); + } + else + { + m_pCurrentEntity->curstate.controller[0] = 127; + m_pCurrentEntity->curstate.controller[1] = 127; + m_pCurrentEntity->curstate.controller[2] = 127; + m_pCurrentEntity->curstate.controller[3] = 127; + m_pCurrentEntity->latched.prevcontroller[0] = m_pCurrentEntity->curstate.controller[0]; + m_pCurrentEntity->latched.prevcontroller[1] = m_pCurrentEntity->curstate.controller[1]; + m_pCurrentEntity->latched.prevcontroller[2] = m_pCurrentEntity->curstate.controller[2]; + m_pCurrentEntity->latched.prevcontroller[3] = m_pCurrentEntity->curstate.controller[3]; + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + m_pPlayerInfo->gaitsequence = 0; + + StudioSetUpTransform( 0 ); + } + + if (flags & STUDIO_RENDER) + { + // see if the bounding box lets us trivially reject, also sets + if (!IEngineStudio.StudioCheckBBox ()) + return 0; + + (*m_pModelsDrawn)++; + (*m_pStudioModelCount)++; // render data cache cookie + + if (m_pStudioHeader->numbodyparts == 0) + return 1; + } + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + StudioSetupBones( ); + + StudioSaveBones( ); + m_pPlayerInfo->renderframe = m_nFrameCount; + + m_pPlayerInfo = NULL; + + if (flags & STUDIO_EVENTS) + { + StudioCalcAttachments( ); + IEngineStudio.StudioClientEvents( ); + // copy attachments into global entity array + if ( m_pCurrentEntity->index > 0 ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( m_pCurrentEntity->index ); + + memcpy( ent->attachment, m_pCurrentEntity->attachment, sizeof( vec3_t ) * 4 ); + } + } + + if (flags & STUDIO_RENDER) + { + if (m_pCvarHiModels->value && m_pRenderModel != m_pCurrentEntity->model ) + { + // show highest resolution multiplayer model + m_pCurrentEntity->curstate.body = 255; + } + + if (!(m_pCvarDeveloper->value == 0 && gEngfuncs.GetMaxClients() == 1 ) && ( m_pRenderModel == m_pCurrentEntity->model ) ) + { + m_pCurrentEntity->curstate.body = 1; // force helmet + } + + lighting.plightvec = dir; + IEngineStudio.StudioDynamicLight(m_pCurrentEntity, &lighting ); + + IEngineStudio.StudioEntityLight( &lighting ); + + //Scale player model lighting by a factor of 100 ( H4X!! ) + lighting.ambientlight *= 100; + + // model and frame independant + IEngineStudio.StudioSetupLighting (&lighting); + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + + // get remap colors + m_nTopColor = m_pPlayerInfo->topcolor; + if (m_nTopColor < 0) + m_nTopColor = 0; + if (m_nTopColor > 360) + m_nTopColor = 360; + m_nBottomColor = m_pPlayerInfo->bottomcolor; + if (m_nBottomColor < 0) + m_nBottomColor = 0; + if (m_nBottomColor > 360) + m_nBottomColor = 360; + + IEngineStudio.StudioSetRemapColors( m_nTopColor, m_nBottomColor ); + + StudioRenderModel( ); + m_pPlayerInfo = NULL; + + if ( pplayer->weaponmodel ) + { + cl_entity_t saveent = *m_pCurrentEntity; + + model_t *pweaponmodel = IEngineStudio.GetModelByIndex( pplayer->weaponmodel ); + + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (pweaponmodel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + + //Animate p_ model. + if ( strstr ( pweaponmodel->name, "p_nail2.mdl" ) ) + { + if ( m_pCurrentEntity->curstate.sequence == 50 ) + m_pCurrentEntity->curstate.sequence = 1; + else + m_pCurrentEntity->curstate.sequence = 0; + + StudioSetupBones( ); + } + else if ( strstr ( pweaponmodel->name, "p_rock.mdl" ) ) + { + if ( m_pCurrentEntity->curstate.sequence == 46 ) + m_pCurrentEntity->curstate.sequence = 1; + else + m_pCurrentEntity->curstate.sequence = 0; + + StudioSetupBones( ); + } + + StudioMergeBones( pweaponmodel); + + IEngineStudio.StudioSetupLighting (&lighting); + + StudioRenderModel( ); + + StudioCalcAttachments( ); + + *m_pCurrentEntity = saveent; + } + } + + return 1; +} + +/* +==================== +StudioCalcAttachments + +==================== +*/ +void CStudioModelRenderer::StudioCalcAttachments( void ) +{ + int i; + mstudioattachment_t *pattachment; + + if ( m_pStudioHeader->numattachments > 4 ) + { + gEngfuncs.Con_DPrintf( "Too many attachments on %s\n", m_pCurrentEntity->model->name ); + exit( -1 ); + } + + // calculate attachment points + pattachment = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); + for (i = 0; i < m_pStudioHeader->numattachments; i++) + { + VectorTransform( pattachment[i].org, (*m_plighttransform)[pattachment[i].bone], m_pCurrentEntity->attachment[i] ); + } +} + +/* +==================== +StudioRenderModel + +==================== +*/ +void CStudioModelRenderer::StudioRenderModel( void ) +{ + IEngineStudio.SetChromeOrigin(); + IEngineStudio.SetForceFaceFlags( 0 ); + + if ( m_pCurrentEntity->curstate.renderfx == kRenderFxGlowShell ) + { + if ( strstr ( m_pCurrentEntity->model->name, "v_" ) ) + { + if ( m_pCurrentEntity->curstate.renderamt != 5 ) + { + m_pCurrentEntity->curstate.renderfx = kRenderFxNone; + StudioRenderFinal( ); + } + } + else + { + if ( m_pCurrentEntity->curstate.renderamt != 5 && m_pCurrentEntity->curstate.rendermode != kRenderTransColor ) + { + m_pCurrentEntity->curstate.renderfx = kRenderFxNone; + StudioRenderFinal( ); + } + } + + if ( !IEngineStudio.IsHardware() ) + { + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + } + + IEngineStudio.SetForceFaceFlags( STUDIO_NF_CHROME ); + + gEngfuncs.pTriAPI->SpriteTexture( m_pChromeSprite, 0 ); + m_pCurrentEntity->curstate.renderfx = kRenderFxGlowShell; + + StudioRenderFinal( ); + if ( !IEngineStudio.IsHardware() ) + { + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + } + + } + else + { + StudioRenderFinal( ); + } +} + +/* +==================== +StudioRenderFinal_Software + +==================== +*/ +void CStudioModelRenderer::StudioRenderFinal_Software( void ) +{ + int i; + + // Note, rendermode set here has effect in SW + IEngineStudio.SetupRenderer( 0 ); + + if (m_pCvarDrawEntities->value == 2) + { + IEngineStudio.StudioDrawBones( ); + } + else if (m_pCvarDrawEntities->value == 3) + { + IEngineStudio.StudioDrawHulls( ); + } + else + { + for (i=0 ; i < m_pStudioHeader->numbodyparts ; i++) + { + IEngineStudio.StudioSetupModel( i, (void **)&m_pBodyPart, (void **)&m_pSubModel ); + IEngineStudio.StudioDrawPoints( ); + } + } + + if (m_pCvarDrawEntities->value == 4) + { + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + IEngineStudio.StudioDrawHulls( ); + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + } + + if (m_pCvarDrawEntities->value == 5) + { + IEngineStudio.StudioDrawAbsBBox( ); + } + + IEngineStudio.RestoreRenderer(); +} + +/* +==================== +StudioRenderFinal_Hardware + +==================== +*/ +void CStudioModelRenderer::StudioRenderFinal_Hardware( void ) +{ + int i; + int rendermode; + + rendermode = IEngineStudio.GetForceFaceFlags() ? kRenderTransAdd : m_pCurrentEntity->curstate.rendermode; + IEngineStudio.SetupRenderer( rendermode ); + + if (m_pCvarDrawEntities->value == 2) + { + IEngineStudio.StudioDrawBones(); + } + else if (m_pCvarDrawEntities->value == 3) + { + IEngineStudio.StudioDrawHulls(); + } + else + { + for (i=0 ; i < m_pStudioHeader->numbodyparts ; i++) + { + IEngineStudio.StudioSetupModel( i, (void **)&m_pBodyPart, (void **)&m_pSubModel ); + + if (m_fDoInterp) + { + // interpolation messes up bounding boxes. + m_pCurrentEntity->trivial_accept = 0; + } + + IEngineStudio.GL_SetRenderMode( rendermode ); + IEngineStudio.StudioDrawPoints(); + IEngineStudio.GL_StudioDrawShadow(); + } + } + + if ( m_pCvarDrawEntities->value == 4 ) + { + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + IEngineStudio.StudioDrawHulls( ); + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + } + + IEngineStudio.RestoreRenderer(); +} + +/* +==================== +StudioRenderFinal + +==================== +*/ +void CStudioModelRenderer::StudioRenderFinal(void) +{ + if ( IEngineStudio.IsHardware() ) + { + StudioRenderFinal_Hardware(); + } + else + { + StudioRenderFinal_Software(); + } +} + diff --git a/dmc/cl_dll/StudioModelRenderer.h b/dmc/cl_dll/StudioModelRenderer.h new file mode 100644 index 0000000..8f1427f --- /dev/null +++ b/dmc/cl_dll/StudioModelRenderer.h @@ -0,0 +1,189 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined ( STUDIOMODELRENDERER_H ) +#define STUDIOMODELRENDERER_H +#if defined( _WIN32 ) +#pragma once +#endif + +/* +==================== +CStudioModelRenderer + +==================== +*/ +class CStudioModelRenderer +{ +public: + // Construction/Destruction + CStudioModelRenderer( void ); + virtual ~CStudioModelRenderer( void ); + + // Initialization + virtual void Init( void ); + +public: + // Public Interfaces + virtual int StudioDrawModel ( int flags ); + virtual int StudioDrawPlayer ( int flags, struct entity_state_s *pplayer ); + +public: + // Local interfaces + // + + // Look up animation data for sequence + virtual mstudioanim_t *StudioGetAnim ( model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc ); + + // Interpolate model position and angles and set up matrices + virtual void StudioSetUpTransform (int trivial_accept); + + // Set up model bone positions + virtual void StudioSetupBones ( void ); + + // Find final attachment points + virtual void StudioCalcAttachments ( void ); + + // Save bone matrices and names + virtual void StudioSaveBones( void ); + + // Merge cached bones with current bones for model + virtual void StudioMergeBones ( model_t *m_pSubModel ); + + // Determine interpolation fraction + virtual float StudioEstimateInterpolant( void ); + + // Determine current frame for rendering + virtual float StudioEstimateFrame ( mstudioseqdesc_t *pseqdesc ); + + // Apply special effects to transform matrix + virtual void StudioFxTransform( cl_entity_t *ent, float transform[3][4] ); + + // Spherical interpolation of bones + virtual void StudioSlerpBones ( vec4_t q1[], float pos1[][3], vec4_t q2[], float pos2[][3], float s ); + + // Compute bone adjustments ( bone controllers ) + virtual void StudioCalcBoneAdj ( float dadt, float *adj, const byte *pcontroller1, const byte *pcontroller2, byte mouthopen ); + + // Get bone quaternions + virtual void StudioCalcBoneQuaterion ( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *q ); + + // Get bone positions + virtual void StudioCalcBonePosition ( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *pos ); + + // Compute rotations + virtual void StudioCalcRotations ( float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f ); + + // Send bones and verts to renderer + virtual void StudioRenderModel ( void ); + + // Finalize rendering + virtual void StudioRenderFinal (void); + + // GL&D3D vs. Software renderer finishing functions + virtual void StudioRenderFinal_Software ( void ); + virtual void StudioRenderFinal_Hardware ( void ); + + // Player specific data + // Determine pitch and blending amounts for players + virtual void StudioPlayerBlend ( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ); + + // Estimate gait frame for player + virtual void StudioEstimateGait ( entity_state_t *pplayer ); + + // Process movement of player + virtual void StudioProcessGait ( entity_state_t *pplayer ); + +public: + + // Client clock + double m_clTime; + // Old Client clock + double m_clOldTime; + + // Do interpolation? + int m_fDoInterp; + // Do gait estimation? + int m_fGaitEstimation; + + // Current render frame # + int m_nFrameCount; + + // Cvars that studio model code needs to reference + // + // Use high quality models? + cvar_t *m_pCvarHiModels; + // Developer debug output desired? + cvar_t *m_pCvarDeveloper; + // Draw entities bone hit boxes, etc? + cvar_t *m_pCvarDrawEntities; + + // The entity which we are currently rendering. + cl_entity_t *m_pCurrentEntity; + + // The model for the entity being rendered + model_t *m_pRenderModel; + + // Player info for current player, if drawing a player + player_info_t *m_pPlayerInfo; + + // The index of the player being drawn + int m_nPlayerIndex; + + // The player's gait movement + float m_flGaitMovement; + + // Pointer to header block for studio model data + studiohdr_t *m_pStudioHeader; + + // Pointers to current body part and submodel + mstudiobodyparts_t *m_pBodyPart; + mstudiomodel_t *m_pSubModel; + + // Palette substition for top and bottom of model + int m_nTopColor; + int m_nBottomColor; + + // + // Sprite model used for drawing studio model chrome + model_t *m_pChromeSprite; + + // Caching + // Number of bones in bone cache + int m_nCachedBones; + // Names of cached bones + char m_nCachedBoneNames[ MAXSTUDIOBONES ][ 32 ]; + // Cached bone & light transformation matrices + float m_rgCachedBoneTransform [ MAXSTUDIOBONES ][ 3 ][ 4 ]; + float m_rgCachedLightTransform[ MAXSTUDIOBONES ][ 3 ][ 4 ]; + + // Software renderer scale factors + float m_fSoftwareXScale, m_fSoftwareYScale; + + // Current view vectors and render origin + float m_vUp[ 3 ]; + float m_vRight[ 3 ]; + float m_vNormal[ 3 ]; + + float m_vRenderOrigin[ 3 ]; + + // Model render counters ( from engine ) + int *m_pStudioModelCount; + int *m_pModelsDrawn; + + // Matrices + // Model to world transformation + float (*m_protationmatrix)[ 3 ][ 4 ]; + // Model to view transformation + float (*m_paliastransform)[ 3 ][ 4 ]; + + // Concatenated bone and light transforms + float (*m_pbonetransform) [ MAXSTUDIOBONES ][ 3 ][ 4 ]; + float (*m_plighttransform)[ MAXSTUDIOBONES ][ 3 ][ 4 ]; +}; + +#endif // STUDIOMODELRENDERER_H \ No newline at end of file diff --git a/dmc/cl_dll/ammo.cpp b/dmc/cl_dll/ammo.cpp new file mode 100644 index 0000000..a607739 --- /dev/null +++ b/dmc/cl_dll/ammo.cpp @@ -0,0 +1,1131 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Ammo.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "ammohistory.h" + +WEAPON *gpActiveSel; // NULL means off, 1 means just the menu bar, otherwise + // this points to the active weapon menu item +WEAPON *gpLastSel; // Last weapon menu selection + +client_sprite_t *GetSpriteList(client_sprite_t *pList, const char *psz, int iRes, int iCount); + +WeaponsResource gWR; + +int g_weaponselect = 0; + +void WeaponsResource :: LoadAllWeaponSprites( void ) +{ + for (int i = 0; i < MAX_WEAPONS; i++) + { + if ( rgWeapons[i].iId ) + LoadWeaponSprites( &rgWeapons[i] ); + } +} + +int WeaponsResource :: CountAmmo( int iId ) +{ + if ( iId < 0 ) + return 0; + + //Fixes some crashes. + if ( iId > MAX_AMMO_TYPES ) + return 0; + + return riAmmo[iId]; +} + +int WeaponsResource :: HasAmmo( WEAPON *p ) +{ + if ( !p ) + return FALSE; + + // weapons with no max ammo can always be selected + if ( p->iMax1 == -1 ) + return TRUE; + + return (p->iAmmoType == -1) || p->iClip > 0 || CountAmmo(p->iAmmoType) + || CountAmmo(p->iAmmo2Type) || ( p->iFlags & WEAPON_FLAGS_SELECTONEMPTY ); +} + + +void WeaponsResource :: LoadWeaponSprites( WEAPON *pWeapon ) +{ + int i, iRes; + + if (ScreenWidth < 640) + iRes = 320; + else + iRes = 640; + + char sz[128]; + + if ( !pWeapon ) + return; + + memset( &pWeapon->rcActive, 0, sizeof(wrect_t) ); + memset( &pWeapon->rcInactive, 0, sizeof(wrect_t) ); + memset( &pWeapon->rcAmmo, 0, sizeof(wrect_t) ); + memset( &pWeapon->rcAmmo2, 0, sizeof(wrect_t) ); + pWeapon->hInactive = 0; + pWeapon->hActive = 0; + pWeapon->hAmmo = 0; + pWeapon->hAmmo2 = 0; + + sprintf(sz, "sprites/%s.txt", pWeapon->szName); + client_sprite_t *pList = SPR_GetList(sz, &i); + + if (!pList) + return; + + client_sprite_t *p; + + p = GetSpriteList( pList, "crosshair", iRes, i ); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hCrosshair = SPR_Load(sz); + pWeapon->rcCrosshair = p->rc; + } + else + pWeapon->hCrosshair = NULL; + + p = GetSpriteList(pList, "autoaim", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hAutoaim = SPR_Load(sz); + pWeapon->rcAutoaim = p->rc; + } + else + pWeapon->hAutoaim = 0; + + p = GetSpriteList( pList, "zoom", iRes, i ); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hZoomedCrosshair = SPR_Load(sz); + pWeapon->rcZoomedCrosshair = p->rc; + } + else + { + pWeapon->hZoomedCrosshair = pWeapon->hCrosshair; //default to non-zoomed crosshair + pWeapon->rcZoomedCrosshair = pWeapon->rcCrosshair; + } + + p = GetSpriteList(pList, "zoom_autoaim", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hZoomedAutoaim = SPR_Load(sz); + pWeapon->rcZoomedAutoaim = p->rc; + } + else + { + pWeapon->hZoomedAutoaim = pWeapon->hZoomedCrosshair; //default to zoomed crosshair + pWeapon->rcZoomedAutoaim = pWeapon->rcZoomedCrosshair; + } + + p = GetSpriteList(pList, "weapon", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hInactive = SPR_Load(sz); + pWeapon->rcInactive = p->rc; + + gHR.iHistoryGap = max( gHR.iHistoryGap, pWeapon->rcActive.bottom - pWeapon->rcActive.top ); + } + else + pWeapon->hInactive = 0; + + p = GetSpriteList(pList, "weapon_s", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hActive = SPR_Load(sz); + pWeapon->rcActive = p->rc; + } + else + pWeapon->hActive = 0; + + p = GetSpriteList(pList, "ammo", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hAmmo = SPR_Load(sz); + pWeapon->rcAmmo = p->rc; + + gHR.iHistoryGap = max( gHR.iHistoryGap, pWeapon->rcActive.bottom - pWeapon->rcActive.top ); + } + else + pWeapon->hAmmo = 0; + + p = GetSpriteList(pList, "ammo2", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hAmmo2 = SPR_Load(sz); + pWeapon->rcAmmo2 = p->rc; + + gHR.iHistoryGap = max( gHR.iHistoryGap, pWeapon->rcActive.bottom - pWeapon->rcActive.top ); + } + else + pWeapon->hAmmo2 = 0; + +} + +// Returns the first weapon for a given slot. +WEAPON *WeaponsResource :: GetFirstPos( int iSlot ) +{ + WEAPON *pret = NULL; + + for (int i = 0; i < MAX_WEAPON_POSITIONS; i++) + { + if ( rgSlots[iSlot][i] && HasAmmo( rgSlots[iSlot][i] ) ) + { + pret = rgSlots[iSlot][i]; + break; + } + } + + return pret; +} + + +WEAPON* WeaponsResource :: GetNextActivePos( int iSlot, int iSlotPos ) +{ + if ( iSlotPos >= MAX_WEAPON_POSITIONS || iSlot >= MAX_WEAPON_SLOTS ) + return NULL; + + WEAPON *p = gWR.rgSlots[ iSlot ][ iSlotPos+1 ]; + + if ( !p || !gWR.HasAmmo(p) ) + return GetNextActivePos( iSlot, iSlotPos + 1 ); + + return p; +} + + +int giBucketHeight, giBucketWidth, giABHeight, giABWidth; // Ammo Bar width and height + +HSPRITE ghsprBuckets; // Sprite for top row of weapons menu + +DECLARE_MESSAGE(m_Ammo, CurWeapon ); // Current weapon and clip +DECLARE_MESSAGE(m_Ammo, WeaponList); // new weapon type +DECLARE_MESSAGE(m_Ammo, AmmoX); // update known ammo type's count +DECLARE_MESSAGE(m_Ammo, AmmoPickup); // flashes an ammo pickup record +DECLARE_MESSAGE(m_Ammo, WeapPickup); // flashes a weapon pickup record +DECLARE_MESSAGE(m_Ammo, HideWeapon); // hides the weapon, ammo, and crosshair displays temporarily +DECLARE_MESSAGE(m_Ammo, ItemPickup); + +DECLARE_COMMAND(m_Ammo, Slot1); +DECLARE_COMMAND(m_Ammo, Slot2); +DECLARE_COMMAND(m_Ammo, Slot3); +DECLARE_COMMAND(m_Ammo, Slot4); +DECLARE_COMMAND(m_Ammo, Slot5); +DECLARE_COMMAND(m_Ammo, Slot6); +DECLARE_COMMAND(m_Ammo, Slot7); +DECLARE_COMMAND(m_Ammo, Slot8); +DECLARE_COMMAND(m_Ammo, Slot9); +DECLARE_COMMAND(m_Ammo, Slot10); +DECLARE_COMMAND(m_Ammo, Close); +DECLARE_COMMAND(m_Ammo, NextWeapon); +DECLARE_COMMAND(m_Ammo, PrevWeapon); + +// width of ammo fonts +#define AMMO_SMALL_WIDTH 10 +#define AMMO_LARGE_WIDTH 20 + +#define HISTORY_DRAW_TIME "5" + +int CHudAmmo::Init(void) +{ + gHUD.AddHudElem(this); + + HOOK_MESSAGE(CurWeapon); + HOOK_MESSAGE(WeaponList); + HOOK_MESSAGE(AmmoPickup); + HOOK_MESSAGE(WeapPickup); + HOOK_MESSAGE(ItemPickup); + HOOK_MESSAGE(HideWeapon); + HOOK_MESSAGE(AmmoX); + + HOOK_COMMAND("slot1", Slot1); + HOOK_COMMAND("slot2", Slot2); + HOOK_COMMAND("slot3", Slot3); + HOOK_COMMAND("slot4", Slot4); + HOOK_COMMAND("slot5", Slot5); + HOOK_COMMAND("slot6", Slot6); + HOOK_COMMAND("slot7", Slot7); + HOOK_COMMAND("slot8", Slot8); + HOOK_COMMAND("slot9", Slot9); + HOOK_COMMAND("slot10", Slot10); + HOOK_COMMAND("cancelselect", Close); + HOOK_COMMAND("invnext", NextWeapon); + HOOK_COMMAND("invprev", PrevWeapon); + + Reset(); + + CVAR_CREATE( "hud_drawhistory_time", HISTORY_DRAW_TIME, 0 ); + CVAR_CREATE( "hud_fastswitch", "0", FCVAR_ARCHIVE ); // controls whether or not weapons can be selected in one keypress + + m_iFlags |= HUD_ACTIVE; //!!! + + gWR.Init(); + gHR.Init(); + + return 1; +}; + +void CHudAmmo::Reset(void) +{ + m_fFade = 0; + m_iFlags |= HUD_ACTIVE; //!!! + + gpActiveSel = NULL; + gHUD.m_iHideHUDDisplay = 0; + + gWR.Reset(); + gHR.Reset(); + + // VidInit(); + +} + +int CHudAmmo::VidInit(void) +{ + // Load sprites for buckets (top row of weapon menu) + m_HUD_bucket0 = gHUD.GetSpriteIndex( "bucket1" ); + m_HUD_selection = gHUD.GetSpriteIndex( "selection" ); + + ghsprBuckets = gHUD.GetSprite(m_HUD_bucket0); + giBucketWidth = gHUD.GetSpriteRect(m_HUD_bucket0).right - gHUD.GetSpriteRect(m_HUD_bucket0).left; + giBucketHeight = gHUD.GetSpriteRect(m_HUD_bucket0).bottom - gHUD.GetSpriteRect(m_HUD_bucket0).top; + + gHR.iHistoryGap = max( gHR.iHistoryGap, gHUD.GetSpriteRect(m_HUD_bucket0).bottom - gHUD.GetSpriteRect(m_HUD_bucket0).top); + + // If we've already loaded weapons, let's get new sprites + gWR.LoadAllWeaponSprites(); + + if (ScreenWidth >= 640) + { + giABWidth = 20; + giABHeight = 4; + } + else + { + giABWidth = 10; + giABHeight = 2; + } + + return 1; +} + +// +// Think: +// Used for selection of weapon menu item. +// +void CHudAmmo::Think(void) +{ + if ( gHUD.m_fPlayerDead ) + return; + + if ( gHUD.m_iWeaponBits != gWR.iOldWeaponBits ) + { + gWR.iOldWeaponBits = gHUD.m_iWeaponBits; + + for (int i = MAX_WEAPONS-1; i > 0; i-- ) + { + WEAPON *p = gWR.GetWeapon(i); + + if ( p ) + { + if ( gHUD.m_iWeaponBits & p->iId ) + gWR.PickupWeapon( p ); + else + gWR.DropWeapon( p ); + } + } + } + + if (!gpActiveSel) + return; + + // has the player selected one? + if (gHUD.m_iKeyBits & IN_ATTACK) + { + if (gpActiveSel != (WEAPON *)1) + { + //ServerCmd(gpActiveSel->szName); + g_weaponselect = gpActiveSel->iSlot; + } + + gpLastSel = gpActiveSel; + gpActiveSel = NULL; + gHUD.m_iKeyBits &= ~IN_ATTACK; + + PlaySound("common/wpn_select.wav", 1); + } + +} + +// +// Helper function to return a Ammo pointer from id +// + +HSPRITE* WeaponsResource :: GetAmmoPicFromWeapon( int iAmmoId, wrect_t& rect ) +{ + for ( int i = 0; i < MAX_WEAPONS; i++ ) + { + if ( rgWeapons[i].iAmmoType == iAmmoId ) + { + rect = rgWeapons[i].rcAmmo; + return &rgWeapons[i].hAmmo; + } + else if ( rgWeapons[i].iAmmo2Type == iAmmoId ) + { + rect = rgWeapons[i].rcAmmo2; + return &rgWeapons[i].hAmmo2; + } + } + + return NULL; +} + +void WeaponsResource :: SelectSlot( int iSlot, int fAdvance, int iDirection ) +{ + if ( gHUD.m_Menu.m_fMenuDisplayed && (fAdvance == FALSE) && (iDirection == 1) ) + { // menu is overriding slot use commands + gHUD.m_Menu.SelectMenuItem( iSlot ); + return; + } + + if ( iSlot > MAX_WEAPON_SLOTS ) + return; + + if ( gHUD.m_fPlayerDead || gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL ) ) + return; + + WEAPON *p = NULL; + bool fastSwitch = CVAR_GET_FLOAT( "hud_fastswitch" ) != 0; + + if ( (gpActiveSel == NULL) || (gpActiveSel == (WEAPON *)1) || (iSlot != gpActiveSel->iSlot) ) + { + PlaySound( "common/wpn_hudon.wav", 1 ); + p = GetFirstPos( iSlot ); + + if ( p && fastSwitch ) // check for fast weapon switch mode + { + // if fast weapon switch is on, then weapons can be selected in a single keypress + // but only if there is only one item in the bucket + WEAPON *p2 = GetNextActivePos( p->iSlot, p->iSlotPos ); + if ( !p2 ) + { // only one active item in bucket, so change directly to weapon + //ServerCmd( p->szName ); + g_weaponselect = iSlot; + return; + } + } + } + else + { + PlaySound("common/wpn_moveselect.wav", 1); + if ( gpActiveSel ) + p = GetNextActivePos( gpActiveSel->iSlot, gpActiveSel->iSlotPos ); + if ( !p ) + p = GetFirstPos( iSlot ); + } + + + if ( !p ) // no selection found + { + // just display the weapon list, unless fastswitch is on just ignore it + if ( !fastSwitch ) + gpActiveSel = (WEAPON *)1; + else + gpActiveSel = NULL; + } + else + gpActiveSel = p; +} + +//------------------------------------------------------------------------ +// Message Handlers +//------------------------------------------------------------------------ + +// +// AmmoX -- Update the count of a known type of ammo +// +int CHudAmmo::MsgFunc_AmmoX(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + int iIndex = READ_BYTE(); + int iCount = READ_BYTE(); + + gWR.SetAmmo( iIndex, abs(iCount) ); + + return 1; +} + +int CHudAmmo::MsgFunc_AmmoPickup( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + int iIndex = READ_BYTE(); + int iCount = READ_BYTE(); + + // Add ammo to the history + gHR.AddToHistory( HISTSLOT_AMMO, iIndex, abs(iCount) ); + + return 1; +} + +int CHudAmmo::MsgFunc_WeapPickup( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + int iIndex = READ_BYTE(); + + // Add the weapon to the history + gHR.AddToHistory( HISTSLOT_WEAP, iIndex ); + + return 1; +} + +int CHudAmmo::MsgFunc_ItemPickup( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + const char *szName = READ_STRING(); + + // Add the weapon to the history + gHR.AddToHistory( HISTSLOT_ITEM, szName ); + + return 1; +} + + +int CHudAmmo::MsgFunc_HideWeapon( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + gHUD.m_iHideHUDDisplay = READ_BYTE(); + + if ( gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL ) ) + { + static wrect_t nullrc; + gpActiveSel = NULL; + SetCrosshair( 0, nullrc, 0, 0, 0 ); + } + + return 1; +} + +// +// CurWeapon: Update hud state with the current weapon and clip count. Ammo +// counts are updated with AmmoX. Server assures that the Weapon ammo type +// numbers match a real ammo type. +// +int CHudAmmo::MsgFunc_CurWeapon(const char *pszName, int iSize, void *pbuf ) +{ + static wrect_t nullrc; + int fOnTarget = FALSE; + + + BEGIN_READ( pbuf, iSize ); + + int iState = READ_BYTE(); + int iId = READ_BYTE(); + int iClip = READ_CHAR(); + + // detect if we're also on target + if ( iState > 1 ) + { + fOnTarget = TRUE; + } + + if ( iId < 1 ) + { + SetCrosshair(0, nullrc, 0, 0, 0); + return 0; + } + + // Is player dead??? + if ((iId == -1) && (iClip == -1)) + { + gHUD.m_fPlayerDead = TRUE; + gpActiveSel = NULL; + return 1; + } + gHUD.m_fPlayerDead = FALSE; + + WEAPON *pWeapon = gWR.GetWeapon( iId ); + + if ( !pWeapon ) + return 0; + + if ( iClip < -1 ) + pWeapon->iClip = abs(iClip); + else + pWeapon->iClip = iClip; + + + if ( iState == 0 ) // we're not the current weapon, so update no more + return 1; + + m_pWeapon = pWeapon; + + m_fFade = 200.0f; //!!! + m_iFlags |= HUD_ACTIVE; + + return 1; +} + +// +// WeaponList -- Tells the hud about a new weapon type. +// +int CHudAmmo::MsgFunc_WeaponList(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + WEAPON Weapon; + + strcpy( Weapon.szName, READ_STRING() ); + Weapon.iAmmoType = (int)READ_CHAR(); + + Weapon.iMax1 = READ_BYTE(); + if (Weapon.iMax1 == 255) + Weapon.iMax1 = -1; + + Weapon.iAmmo2Type = READ_BYTE(); + Weapon.iMax2 = READ_BYTE(); + if (Weapon.iMax2 == 255) + Weapon.iMax2 = -1; + + Weapon.iSlot = READ_BYTE(); + Weapon.iSlotPos = READ_BYTE(); + Weapon.iId = READ_LONG(); + + Weapon.iFlags = READ_BYTE(); + + Weapon.iClip = 0; + + gWR.AddWeapon( &Weapon ); + + return 1; + +} + +//------------------------------------------------------------------------ +// Command Handlers +//------------------------------------------------------------------------ + +void CHudAmmo::UserCmd_Slot1(void) +{ + gWR.SelectSlot(1, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot2(void) +{ + gWR.SelectSlot(2, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot3(void) +{ + gWR.SelectSlot(3, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot4(void) +{ + gWR.SelectSlot(4, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot5(void) +{ + gWR.SelectSlot(5, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot6(void) +{ + gWR.SelectSlot(6, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot7(void) +{ + gWR.SelectSlot(7, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot8(void) +{ + gWR.SelectSlot(8, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot9(void) +{ + gWR.SelectSlot(9, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot10(void) +{ + gWR.SelectSlot(10, FALSE, 1); +} + +void CHudAmmo::UserCmd_Close(void) +{ + if (gpActiveSel) + { + gpLastSel = gpActiveSel; + gpActiveSel = NULL; + PlaySound("common/wpn_hudoff.wav", 1); + } + else + ClientCmd("escape"); +} + + +// Selects the next item in the weapon menu +void CHudAmmo::UserCmd_NextWeapon(void) +{ + if ( gHUD.m_fPlayerDead || (gHUD.m_iHideHUDDisplay & (HIDEHUD_WEAPONS | HIDEHUD_ALL)) ) + return; + + if ( !gpActiveSel || gpActiveSel == (WEAPON*)1 ) + gpActiveSel = m_pWeapon; + + int pos = 0; + int slot = 0; + if ( gpActiveSel ) + { + pos = gpActiveSel->iSlotPos + 1; + slot = gpActiveSel->iSlot; + } + + for ( int loop = 0; loop <= 1; loop++ ) + { + for ( ; slot < MAX_WEAPON_SLOTS + 1; slot++ ) + { + for ( ; pos < MAX_WEAPON_POSITIONS; pos++ ) + { + WEAPON *wsp = gWR.GetWeaponSlot( slot, pos ); + + if ( wsp && gWR.HasAmmo(wsp) ) + { + gpActiveSel = wsp; + return; + } + } + + pos = 0; + } + + slot = 0; // start looking from the first slot again + } + + gpActiveSel = NULL; +} + +// Selects the previous item in the menu +void CHudAmmo::UserCmd_PrevWeapon(void) +{ + if ( gHUD.m_fPlayerDead || (gHUD.m_iHideHUDDisplay & (HIDEHUD_WEAPONS | HIDEHUD_ALL)) ) + return; + + if ( !gpActiveSel || gpActiveSel == (WEAPON*)1 ) + gpActiveSel = m_pWeapon; + + int pos = MAX_WEAPON_POSITIONS-1; + int slot = MAX_WEAPON_SLOTS; + if ( gpActiveSel ) + { + pos = gpActiveSel->iSlotPos - 1; + slot = gpActiveSel->iSlot; + } + + for ( int loop = 0; loop <= 1; loop++ ) + { + for ( ; slot >= 0; slot-- ) + { + for ( ; pos >= 0; pos-- ) + { + WEAPON *wsp = gWR.GetWeaponSlot( slot, pos ); + + if ( wsp && gWR.HasAmmo(wsp) ) + { + gpActiveSel = wsp; + return; + } + } + + pos = MAX_WEAPON_POSITIONS-1; + } + + slot = MAX_WEAPON_SLOTS; + } + + gpActiveSel = NULL; +} + + + +//------------------------------------------------------------------------- +// Drawing code +//------------------------------------------------------------------------- + +int CHudAmmo::Draw(float flTime) +{ + int a, x, y, r, g, b; + int AmmoWidth; + + int iCrossX; + int iCrossY; + int iCrossLength; + char *chCrossHair = "+"; // Heh + + /*if (!(gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT)) )) + return 1;*/ + + if ( (gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL )) ) + return 1; + + // Draw Weapon Menu + DrawWList(flTime); + + // Draw ammo pickup history + gHR.DrawAmmoHistory( flTime ); + + if (!(m_iFlags & HUD_ACTIVE)) + return 0; + + if (!m_pWeapon) + return 0; + + WEAPON *pw = m_pWeapon; // shorthand + + // SPR_Draw Ammo + if ((pw->iAmmoType < 0) && (pw->iAmmo2Type < 0)) + return 0; + + + int iFlags = DHN_DRAWZERO; // draw 0 values + + AmmoWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; + + a = (int) max( MIN_ALPHA, m_fFade ); + + if (m_fFade > 0) + m_fFade -= (gHUD.m_flTimeDelta * 20); + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + ScaleColors(r, g, b, a ); + + // Does this weapon have a clip? + y = ScreenHeight - gHUD.m_iFontHeight - gHUD.m_iFontHeight/2; + + /******************* DRAW CROSSHAIR *********************/ + iCrossLength = gHUD.m_scrinfo.charWidths[ *chCrossHair ]; + iCrossY = ScreenHeight / 2 - gHUD.m_scrinfo.iCharHeight / 2; + iCrossX = ScreenWidth / 2 - iCrossLength / 2; + + gHUD.DrawHudString( iCrossX, iCrossY, iCrossX + 50, chCrossHair, 170, 170, 170 ); + /******************* DRAW CROSSHAIR *********************/ + + // Does weapon have any ammo at all? + if (m_pWeapon->iAmmoType > 0) + { + int iIconWidth = m_pWeapon->rcAmmo.right - m_pWeapon->rcAmmo.left; + + if (pw->iClip >= 0) + { + x = ScreenWidth - (8 * AmmoWidth) - iIconWidth; + + UnpackRGB(r,g,b, RGB_YELLOWISH); + // GL Seems to need this + ScaleColors( r, g, b, a ); + m_iNumberXPosition = x = gHUD.DrawHudNumber(x, y, iFlags | DHN_3DIGITS, gWR.CountAmmo(pw->iAmmoType), r, g, b); + } + + // Draw the ammo Icon + int iOffset = (m_pWeapon->rcAmmo.bottom - m_pWeapon->rcAmmo.top)/8; + SPR_Set(m_pWeapon->hAmmo, r, g, b); + SPR_DrawAdditive(0, x, y - iOffset, &m_pWeapon->rcAmmo); + + m_iXPosition = x; + } + + return 1; +} + + +// +// Draws the ammo bar on the hud +// +int DrawBar(int x, int y, int width, int height, float f) +{ + int r, g, b; + + if (f < 0) + f = 0; + if (f > 1) + f = 1; + + if (f) + { + int w = f * width; + + // Always show at least one pixel if we have ammo. + if (w <= 0) + w = 1; + UnpackRGB(r, g, b, RGB_GREENISH); + FillRGBA(x, y, w, height, r, g, b, 255); + x += w; + width -= w; + } + + UnpackRGB(r, g, b, RGB_YELLOWISH); + + FillRGBA(x, y, width, height, r, g, b, 128); + + return (x + width); +} + + + +void DrawAmmoBar(WEAPON *p, int x, int y, int width, int height) +{ + if ( !p ) + return; + + if (p->iAmmoType != -1) + { + if (!gWR.CountAmmo(p->iAmmoType)) + return; + + float f = (float)gWR.CountAmmo(p->iAmmoType)/(float)p->iMax1; + + x = DrawBar(x, y, width, height, f); + } +} + + + + +// +// Draw Weapon Menu +// +int CHudAmmo::DrawWList(float flTime) +{ + int r,g,b,x,y,a,i; + + if ( !gpActiveSel ) + return 0; + + int iActiveSlot; + + if ( gpActiveSel == (WEAPON *)1 ) + iActiveSlot = -1; // current slot has no weapons + else + iActiveSlot = gpActiveSel->iSlot; + + x = 10; //!!! + y = 10; //!!! + + + // Ensure that there are available choices in the active slot + if ( iActiveSlot > 0 ) + { + if ( !gWR.GetFirstPos( iActiveSlot ) ) + { + gpActiveSel = (WEAPON *)1; + iActiveSlot = -1; + } + } + + // Draw top line + for ( i = 0; i < MAX_WEAPON_SLOTS; i++ ) + { + int iWidth; + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + if ( iActiveSlot == i ) + a = 255; + else + a = 192; + + ScaleColors(r, g, b, 255); + SPR_Set(gHUD.GetSprite(m_HUD_bucket0 + i), r, g, b ); + + // make active slot wide enough to accomodate gun pictures + if ( i == iActiveSlot ) + { + WEAPON *p = gWR.GetFirstPos(iActiveSlot); + + if ( p ) + iWidth = p->rcActive.right - p->rcActive.left; + } + else + iWidth = giBucketWidth; + + if ( i == iActiveSlot ) + SPR_DrawAdditive(0, x + 104, y, &gHUD.GetSpriteRect(m_HUD_bucket0 + i)); + else + SPR_DrawAdditive(0, x, y, &gHUD.GetSpriteRect(m_HUD_bucket0 + i)); + + x += iWidth + 5; + } + + + a = 128; //!!! + x = 10; + + // Draw all of the buckets + for (i = 1; i < MAX_WEAPON_SLOTS + 1; i++) + { + y = giBucketHeight + 10; + + // If this is the active slot, draw the bigger pictures, + // otherwise just draw boxes + if ( i == iActiveSlot ) + { + WEAPON *p = gWR.GetFirstPos( i ); + int iWidth = giBucketWidth; + if ( p ) + iWidth = p->rcActive.right - p->rcActive.left; + + for ( int iPos = 0; iPos < MAX_WEAPON_POSITIONS; iPos++ ) + { + p = gWR.GetWeaponSlot( i, iPos ); + + if ( !p || !p->iId ) + continue; + + UnpackRGB( r,g,b, RGB_YELLOWISH ); + + // if active, then we must have ammo. + + if ( gpActiveSel == p ) + { + if ( gWR.HasAmmo(p) ) + ScaleColors(r, g, b, 192); + else + { + UnpackRGB(r,g,b, RGB_REDISH); + ScaleColors(r, g, b, 128); + } + + SPR_Set(p->hActive, r, g, b ); + SPR_DrawAdditive(0, x, y, &p->rcActive); + } + else + { + // Draw Weapon if Red if no ammo + + if ( gWR.HasAmmo(p) ) + ScaleColors(r, g, b, 192); + else + { + UnpackRGB(r,g,b, RGB_REDISH); + ScaleColors(r, g, b, 128); + } + + SPR_Set( p->hInactive, r, g, b ); + SPR_DrawAdditive( 0, x, y, &p->rcInactive ); + } + + // Draw Ammo Bar + + DrawAmmoBar(p, x + giABWidth/2, y, giABWidth, giABHeight); + + y += p->rcActive.bottom - p->rcActive.top + 5; + } + + x += iWidth + 5; + + } + else + { + // Draw Row of weapons. + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + for ( int iPos = 0; iPos < MAX_WEAPON_POSITIONS; iPos++ ) + { + WEAPON *p = gWR.GetWeaponSlot( i, iPos ); + + if ( !p || !p->iId ) + continue; + + if ( gWR.HasAmmo(p) ) + { + UnpackRGB(r,g,b, RGB_YELLOWISH); + a = 128; + } + else + { + UnpackRGB(r,g,b, RGB_REDISH); + a = 96; + } + + FillRGBA( x, y, giBucketWidth, giBucketHeight, r, g, b, a ); + + y += giBucketHeight + 5; + } + + x += giBucketWidth + 5; + } + } + + return 1; + +} + + +/* ================================= + GetSpriteList + +Finds and returns the matching +sprite name 'psz' and resolution 'iRes' +in the given sprite list 'pList' +iCount is the number of items in the pList +================================= */ +client_sprite_t *GetSpriteList(client_sprite_t *pList, const char *psz, int iRes, int iCount) +{ + if (!pList) + return NULL; + + int i = iCount; + client_sprite_t *p = pList; + + while(i--) + { + if ((!strcmp(psz, p->szName)) && (p->iRes == iRes)) + return p; + p++; + } + + return NULL; +} + + diff --git a/dmc/cl_dll/ammo.h b/dmc/cl_dll/ammo.h new file mode 100644 index 0000000..5e44065 --- /dev/null +++ b/dmc/cl_dll/ammo.h @@ -0,0 +1,62 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef __AMMO_H__ +#define __AMMO_H__ + +#define MAX_WEAPON_NAME 128 + + +#define WEAPON_FLAGS_SELECTONEMPTY 1 + +#define WEAPON_IS_ONTARGET 0x40 + +struct WEAPON +{ + char szName[MAX_WEAPON_NAME]; + int iAmmoType; + int iAmmo2Type; + int iMax1; + int iMax2; + int iSlot; + int iSlotPos; + int iFlags; + int iId; + int iClip; + + int iCount; // # of itesm in plist + + HSPRITE hActive; + wrect_t rcActive; + HSPRITE hInactive; + wrect_t rcInactive; + HSPRITE hAmmo; + wrect_t rcAmmo; + HSPRITE hAmmo2; + wrect_t rcAmmo2; + HSPRITE hCrosshair; + wrect_t rcCrosshair; + HSPRITE hAutoaim; + wrect_t rcAutoaim; + HSPRITE hZoomedCrosshair; + wrect_t rcZoomedCrosshair; + HSPRITE hZoomedAutoaim; + wrect_t rcZoomedAutoaim; +}; + +typedef int AMMO; + + +#endif \ No newline at end of file diff --git a/dmc/cl_dll/ammo_secondary.cpp b/dmc/cl_dll/ammo_secondary.cpp new file mode 100644 index 0000000..e2a2f59 --- /dev/null +++ b/dmc/cl_dll/ammo_secondary.cpp @@ -0,0 +1,159 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ammo_secondary.cpp +// +// implementation of CHudAmmoSecondary class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE( m_AmmoSecondary, SecAmmoVal ); +DECLARE_MESSAGE( m_AmmoSecondary, SecAmmoIcon ); + +int CHudAmmoSecondary :: Init( void ) +{ + HOOK_MESSAGE( SecAmmoVal ); + HOOK_MESSAGE( SecAmmoIcon ); + + gHUD.AddHudElem(this); + m_HUD_ammoicon = 0; + + for ( int i = 0; i < MAX_SEC_AMMO_VALUES; i++ ) + m_iAmmoAmounts[i] = -1; // -1 means don't draw this value + + Reset(); + + return 1; +} + +void CHudAmmoSecondary :: Reset( void ) +{ + m_fFade = 0; +} + +int CHudAmmoSecondary :: VidInit( void ) +{ + return 1; +} + +int CHudAmmoSecondary :: Draw(float flTime) +{ + if ( (gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL )) ) + return 1; + + // draw secondary ammo icons above normal ammo readout + int a, x, y, r, g, b, AmmoWidth; + UnpackRGB( r, g, b, RGB_YELLOWISH ); + a = (int) max( MIN_ALPHA, m_fFade ); + if (m_fFade > 0) + m_fFade -= (gHUD.m_flTimeDelta * 20); // slowly lower alpha to fade out icons + ScaleColors( r, g, b, a ); + + AmmoWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; + + y = ScreenHeight - (gHUD.m_iFontHeight*4); // this is one font height higher than the weapon ammo values + x = ScreenWidth - AmmoWidth; + + if ( m_HUD_ammoicon ) + { + // Draw the ammo icon + x -= (gHUD.GetSpriteRect(m_HUD_ammoicon).right - gHUD.GetSpriteRect(m_HUD_ammoicon).left); + y -= (gHUD.GetSpriteRect(m_HUD_ammoicon).top - gHUD.GetSpriteRect(m_HUD_ammoicon).bottom); + + SPR_Set( gHUD.GetSprite(m_HUD_ammoicon), r, g, b ); + SPR_DrawAdditive( 0, x, y, &gHUD.GetSpriteRect(m_HUD_ammoicon) ); + } + else + { // move the cursor by the '0' char instead, since we don't have an icon to work with + x -= AmmoWidth; + y -= (gHUD.GetSpriteRect(gHUD.m_HUD_number_0).top - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).bottom); + } + + // draw the ammo counts, in reverse order, from right to left + for ( int i = MAX_SEC_AMMO_VALUES-1; i >= 0; i-- ) + { + if ( m_iAmmoAmounts[i] < 0 ) + continue; // negative ammo amounts imply that they shouldn't be drawn + + // half a char gap between the ammo number and the previous pic + x -= (AmmoWidth / 2); + + // draw the number, right-aligned + x -= (gHUD.GetNumWidth( m_iAmmoAmounts[i], DHN_DRAWZERO ) * AmmoWidth); + gHUD.DrawHudNumber( x, y, DHN_DRAWZERO, m_iAmmoAmounts[i], r, g, b ); + + if ( i != 0 ) + { + // draw the divider bar + x -= (AmmoWidth / 2); + FillRGBA(x, y, (AmmoWidth/10), gHUD.m_iFontHeight, r, g, b, a); + } + } + + return 1; +} + +// Message handler for Secondary Ammo Value +// accepts one value: +// string: sprite name +int CHudAmmoSecondary :: MsgFunc_SecAmmoIcon( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + m_HUD_ammoicon = gHUD.GetSpriteIndex( READ_STRING() ); + + return 1; +} + +// Message handler for Secondary Ammo Icon +// Sets an ammo value +// takes two values: +// byte: ammo index +// byte: ammo value +int CHudAmmoSecondary :: MsgFunc_SecAmmoVal( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int index = READ_BYTE(); + if ( index < 0 || index >= MAX_SEC_AMMO_VALUES ) + return 1; + + m_iAmmoAmounts[index] = READ_BYTE(); + m_iFlags |= HUD_ACTIVE; + + // check to see if there is anything left to draw + int count = 0; + for ( int i = 0; i < MAX_SEC_AMMO_VALUES; i++ ) + { + count += max( 0, m_iAmmoAmounts[i] ); + } + + if ( count == 0 ) + { // the ammo fields are all empty, so turn off this hud area + m_iFlags &= ~HUD_ACTIVE; + return 1; + } + + // make the icons light up + m_fFade = 200.0f; + + return 1; +} + + diff --git a/dmc/cl_dll/ammohistory.cpp b/dmc/cl_dll/ammohistory.cpp new file mode 100644 index 0000000..bc78dad --- /dev/null +++ b/dmc/cl_dll/ammohistory.cpp @@ -0,0 +1,190 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ammohistory.cpp +// + + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "ammohistory.h" + +HistoryResource gHR; + +#define AMMO_PICKUP_GAP (gHR.iHistoryGap+5) +#define AMMO_PICKUP_PICK_HEIGHT (32 + (gHR.iHistoryGap * 2)) +#define AMMO_PICKUP_HEIGHT_MAX (ScreenHeight - 100) + +#define MAX_ITEM_NAME 32 +int HISTORY_DRAW_TIME = 5; + +// keep a list of items +struct ITEM_INFO +{ + char szName[MAX_ITEM_NAME]; + HSPRITE spr; + wrect_t rect; +}; + +void HistoryResource :: AddToHistory( int iType, int iId, int iCount ) +{ + if ( iType == HISTSLOT_AMMO && !iCount ) + return; // no amount, so don't add + + if ( (((AMMO_PICKUP_GAP * iCurrentHistorySlot) + AMMO_PICKUP_PICK_HEIGHT) > AMMO_PICKUP_HEIGHT_MAX) || (iCurrentHistorySlot >= MAX_HISTORY) ) + { // the pic would have to be drawn too high + // so start from the bottom + iCurrentHistorySlot = 0; + } + + HIST_ITEM *freeslot = &rgAmmoHistory[iCurrentHistorySlot++]; // default to just writing to the first slot + HISTORY_DRAW_TIME = CVAR_GET_FLOAT( "hud_drawhistory_time" ); + + freeslot->type = iType; + freeslot->iId = iId; + freeslot->iCount = iCount; + freeslot->DisplayTime = gHUD.m_flTime + HISTORY_DRAW_TIME; +} + +void HistoryResource :: AddToHistory( int iType, const char *szName, int iCount ) +{ + if ( iType != HISTSLOT_ITEM ) + return; + + if ( (((AMMO_PICKUP_GAP * iCurrentHistorySlot) + AMMO_PICKUP_PICK_HEIGHT) > AMMO_PICKUP_HEIGHT_MAX) || (iCurrentHistorySlot >= MAX_HISTORY) ) + { // the pic would have to be drawn too high + // so start from the bottom + iCurrentHistorySlot = 0; + } + + HIST_ITEM *freeslot = &rgAmmoHistory[iCurrentHistorySlot++]; // default to just writing to the first slot + + // I am really unhappy with all the code in this file + + int i = gHUD.GetSpriteIndex( szName ); + if ( i == -1 ) + return; // unknown sprite name, don't add it to history + + freeslot->iId = i; + freeslot->type = iType; + freeslot->iCount = iCount; + + HISTORY_DRAW_TIME = CVAR_GET_FLOAT( "hud_drawhistory_time" ); + freeslot->DisplayTime = gHUD.m_flTime + HISTORY_DRAW_TIME; +} + + +void HistoryResource :: CheckClearHistory( void ) +{ + for ( int i = 0; i < MAX_HISTORY; i++ ) + { + if ( rgAmmoHistory[i].type ) + return; + } + + iCurrentHistorySlot = 0; +} + +// +// Draw Ammo pickup history +// +int HistoryResource :: DrawAmmoHistory( float flTime ) +{ + for ( int i = 0; i < MAX_HISTORY; i++ ) + { + if ( rgAmmoHistory[i].type ) + { + rgAmmoHistory[i].DisplayTime = min( rgAmmoHistory[i].DisplayTime, gHUD.m_flTime + HISTORY_DRAW_TIME ); + + if ( rgAmmoHistory[i].DisplayTime <= flTime ) + { // pic drawing time has expired + memset( &rgAmmoHistory[i], 0, sizeof(HIST_ITEM) ); + CheckClearHistory(); + } + else if ( rgAmmoHistory[i].type == HISTSLOT_AMMO ) + { + wrect_t rcPic; + HSPRITE *spr = gWR.GetAmmoPicFromWeapon( rgAmmoHistory[i].iId, rcPic ); + + int r, g, b; + UnpackRGB(r,g,b, RGB_YELLOWISH); + float scale = (rgAmmoHistory[i].DisplayTime - flTime) * 80; + ScaleColors(r, g, b, min(scale, 255) ); + + // Draw the pic + int ypos = ScreenHeight - (AMMO_PICKUP_PICK_HEIGHT + (AMMO_PICKUP_GAP * i)); + int xpos = ScreenWidth - 24; + if ( spr && *spr ) // weapon isn't loaded yet so just don't draw the pic + { // the dll has to make sure it has sent info the weapons you need + SPR_Set( *spr, r, g, b ); + SPR_DrawAdditive( 0, xpos, ypos, &rcPic ); + } + + // Draw the number + gHUD.DrawHudNumberString( xpos - 10, ypos, xpos - 100, rgAmmoHistory[i].iCount, r, g, b ); + } + else if ( rgAmmoHistory[i].type == HISTSLOT_WEAP ) + { + WEAPON *weap = gWR.GetWeapon( rgAmmoHistory[i].iId ); + + if ( !weap ) + return 1; // we don't know about the weapon yet, so don't draw anything + + int r, g, b; + UnpackRGB(r,g,b, RGB_YELLOWISH); + + if ( !gWR.HasAmmo( weap ) ) + UnpackRGB(r,g,b, RGB_REDISH); // if the weapon doesn't have ammo, display it as red + + float scale = (rgAmmoHistory[i].DisplayTime - flTime) * 80; + ScaleColors(r, g, b, min(scale, 255) ); + + int ypos = ScreenHeight - (AMMO_PICKUP_PICK_HEIGHT + (AMMO_PICKUP_GAP * i)); + int xpos = ScreenWidth - (weap->rcInactive.right - weap->rcInactive.left); + SPR_Set( weap->hInactive, r, g, b ); + SPR_DrawAdditive( 0, xpos, ypos, &weap->rcInactive ); + } + else if ( rgAmmoHistory[i].type == HISTSLOT_ITEM ) + { + int r, g, b; + + if ( !rgAmmoHistory[i].iId ) + continue; // sprite not loaded + + wrect_t rect = gHUD.GetSpriteRect( rgAmmoHistory[i].iId ); + + UnpackRGB(r,g,b, RGB_YELLOWISH); + float scale = (rgAmmoHistory[i].DisplayTime - flTime) * 80; + ScaleColors(r, g, b, min(scale, 255) ); + + int ypos = ScreenHeight - (AMMO_PICKUP_PICK_HEIGHT + (AMMO_PICKUP_GAP * i)); + int xpos = ScreenWidth - (rect.right - rect.left) - 10; + + SPR_Set( gHUD.GetSprite( rgAmmoHistory[i].iId ), r, g, b ); + SPR_DrawAdditive( 0, xpos, ypos, &rect ); + } + } + } + + + return 1; +} + + diff --git a/dmc/cl_dll/ammohistory.h b/dmc/cl_dll/ammohistory.h new file mode 100644 index 0000000..5da8921 --- /dev/null +++ b/dmc/cl_dll/ammohistory.h @@ -0,0 +1,144 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ammohistory.h +// + +// this is the max number of items in each bucket +#define MAX_WEAPON_POSITIONS 1 + +class WeaponsResource +{ +private: + // Information about weapons & ammo + WEAPON rgWeapons[MAX_WEAPONS]; // Weapons Array + + // counts of weapons * ammo + WEAPON* rgSlots[MAX_WEAPON_SLOTS+1][MAX_WEAPON_POSITIONS+1]; // The slots currently in use by weapons. The value is a pointer to the weapon; if it's NULL, no weapon is there + int riAmmo[MAX_AMMO_TYPES]; // count of each ammo type + +public: + void Init( void ) + { + memset( rgWeapons, 0, sizeof rgWeapons ); + Reset(); + } + + void Reset( void ) + { + iOldWeaponBits = 0; + memset( rgSlots, 0, sizeof rgSlots ); + memset( riAmmo, 0, sizeof riAmmo ); + } + +///// WEAPON ///// + int iOldWeaponBits; + + WEAPON *GetWeapon( int iId ) { return &rgWeapons[iId]; } + void AddWeapon( WEAPON *wp ) + { + rgWeapons[ wp->iId ] = *wp; + LoadWeaponSprites( &rgWeapons[ wp->iId ] ); + } + + void PickupWeapon( WEAPON *wp ) + { + rgSlots[ wp->iSlot ][ 0 ] = wp; + } + + void DropWeapon( WEAPON *wp ) + { + rgSlots[ wp->iSlot ][ 0 ] = NULL; + } + + void DropAllWeapons( void ) + { + for ( int i = 0; i < MAX_WEAPONS; i++ ) + { + if ( rgWeapons[i].iId ) + DropWeapon( &rgWeapons[i] ); + } + } + + WEAPON* GetWeaponSlot( int slot, int pos ) { return rgSlots[slot][pos]; } + + void LoadWeaponSprites( WEAPON* wp ); + void LoadAllWeaponSprites( void ); + WEAPON* GetFirstPos( int iSlot ); + void SelectSlot( int iSlot, int fAdvance, int iDirection ); + WEAPON* GetNextActivePos( int iSlot, int iSlotPos ); + + int HasAmmo( WEAPON *p ); + +///// AMMO ///// + AMMO GetAmmo( int iId ) { return iId; } + + void SetAmmo( int iId, int iCount ) { riAmmo[ iId ] = iCount; } + + int CountAmmo( int iId ); + + HSPRITE* GetAmmoPicFromWeapon( int iAmmoId, wrect_t& rect ); + +}; + +extern WeaponsResource gWR; + + +#define MAX_HISTORY 12 +enum { + HISTSLOT_EMPTY, + HISTSLOT_AMMO, + HISTSLOT_WEAP, + HISTSLOT_ITEM, +}; + +class HistoryResource +{ +private: + struct HIST_ITEM { + int type; + float DisplayTime; // the time at which this item should be removed from the history + int iCount; + int iId; + }; + + HIST_ITEM rgAmmoHistory[MAX_HISTORY]; + +public: + + void Init( void ) + { + Reset(); + } + + void Reset( void ) + { + memset( rgAmmoHistory, 0, sizeof rgAmmoHistory ); + } + + int iHistoryGap; + int iCurrentHistorySlot; + + void AddToHistory( int iType, int iId, int iCount = 0 ); + void AddToHistory( int iType, const char *szName, int iCount = 0 ); + + void CheckClearHistory( void ); + int DrawAmmoHistory( float flTime ); +}; + +extern HistoryResource gHR; + + + diff --git a/dmc/cl_dll/battery.cpp b/dmc/cl_dll/battery.cpp new file mode 100644 index 0000000..1c328ef --- /dev/null +++ b/dmc/cl_dll/battery.cpp @@ -0,0 +1,135 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// battery.cpp +// +// implementation of CHudBattery class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +DECLARE_MESSAGE(m_Battery, Battery) + +int CHudBattery::Init(void) +{ + m_iBat = 0; + m_fFade = 0; + m_iFlags = 0; + + HOOK_MESSAGE(Battery); + + gHUD.AddHudElem(this); + + return 1; +}; + + +int CHudBattery::VidInit(void) +{ + int HUD_suit_empty = gHUD.GetSpriteIndex( "suit_empty" ); + int HUD_suit_full = gHUD.GetSpriteIndex( "suit_full" ); + + m_hSprite1 = m_hSprite2 = 0; // delaying get sprite handles until we know the sprites are loaded + m_prc1 = &gHUD.GetSpriteRect( HUD_suit_empty ); + m_prc2 = &gHUD.GetSpriteRect( HUD_suit_full ); + m_iHeight = m_prc2->bottom - m_prc1->top; + m_fFade = 0; + return 1; +}; + +int CHudBattery:: MsgFunc_Battery(const char *pszName, int iSize, void *pbuf ) +{ + m_iFlags |= HUD_ACTIVE; + + + BEGIN_READ( pbuf, iSize ); + int x = READ_SHORT(); + + if (x != m_iBat) + { + m_fFade = FADE_TIME; + m_iBat = x; + } + + return 1; +} + + +int CHudBattery::Draw(float flTime) +{ + if ( gHUD.m_iHideHUDDisplay & HIDEHUD_HEALTH ) + return 1; + + int r, g, b, x, y, a; + wrect_t rc; + + rc = *m_prc2; + rc.top += m_iHeight * ((float)(100-(min(100,m_iBat))) * 0.01); // battery can go from 0 to 100 so * 0.01 goes from 0 to 1 + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + // Has health changed? Flash the health # + if (m_fFade) + { + if (m_fFade > FADE_TIME) + m_fFade = FADE_TIME; + + m_fFade -= (gHUD.m_flTimeDelta * 20); + if (m_fFade <= 0) + { + a = 128; + m_fFade = 0; + } + + // Fade the health number back to dim + + a = MIN_ALPHA + (m_fFade/FADE_TIME) * 128; + + } + else + a = MIN_ALPHA; + + ScaleColors(r, g, b, a ); + + int iOffset = (m_prc1->bottom - m_prc1->top)/6; + + y = ScreenHeight - gHUD.m_iFontHeight - gHUD.m_iFontHeight / 2; + x = ScreenWidth/5; + + // make sure we have the right sprite handles + if ( !m_hSprite1 ) + m_hSprite1 = gHUD.GetSprite( gHUD.GetSpriteIndex( "suit_empty" ) ); + if ( !m_hSprite2 ) + m_hSprite2 = gHUD.GetSprite( gHUD.GetSpriteIndex( "suit_full" ) ); + + SPR_Set(m_hSprite1, r, g, b ); + SPR_DrawAdditive( 0, x, y - iOffset, m_prc1); + + if (rc.bottom > rc.top) + { + SPR_Set(m_hSprite2, r, g, b ); + SPR_DrawAdditive( 0, x, y - iOffset + (rc.top - m_prc2->top), &rc); + } + + x += (m_prc1->right - m_prc1->left); + x = gHUD.DrawHudNumber(x, y, DHN_3DIGITS | DHN_DRAWZERO, m_iBat, r, g, b); + + return 1; +} \ No newline at end of file diff --git a/dmc/cl_dll/camera.h b/dmc/cl_dll/camera.h new file mode 100644 index 0000000..08c8792 --- /dev/null +++ b/dmc/cl_dll/camera.h @@ -0,0 +1,24 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// Camera.h -- defines and such for a 3rd person camera +// NOTE: must include quakedef.h first + +#ifndef _CAMERA_H_ +#define _CAMERA_H_ + +// pitch, yaw, dist +extern vec3_t cam_ofs; +// Using third person camera +extern int cam_thirdperson; + +void CAM_Init( void ); +void CAM_ClearStates( void ); +void CAM_StartMouseMove(void); +void CAM_EndMouseMove(void); + +#endif // _CAMERA_H_ diff --git a/dmc/cl_dll/cdll_int.cpp b/dmc/cl_dll/cdll_int.cpp new file mode 100644 index 0000000..fe35223 --- /dev/null +++ b/dmc/cl_dll/cdll_int.cpp @@ -0,0 +1,342 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cdll_int.c +// +// this implementation handles the linking of the engine to the DLL +// + +#include "hud.h" +#include "cl_util.h" +#include +#include "netadr.h" +#include "vgui_int.h" +#include "voice_status.h" + +#include "FileSystem.h" + +#include "interface.h" +#include "vgui_SchemeManager.h" + +#ifdef _WIN32 +#include +#endif + +CSysModule *g_pFileSystemModule = NULL; +IFileSystem *g_pFileSystem = NULL; + +CSysModule *g_hTrackerModule = NULL; + +cl_enginefunc_t gEngfuncs; +CHud gHUD; +TeamFortressViewport *gViewPort = NULL; + +extern "C" +{ +#include "pm_shared.h" +} + +#include "hud_servers.h" + +void InitInput (void); +void EV_HookEvents( void ); +void IN_Commands( void ); + +/* +========================== + Initialize + +Called when the DLL is first loaded. +========================== +*/ +extern "C" +{ +int EXPORT Initialize( cl_enginefunc_t *pEnginefuncs, int iVersion ); +int EXPORT HUD_VidInit( void ); +int EXPORT HUD_Init( void ); +int EXPORT HUD_Redraw( float flTime, int intermission ); +int EXPORT HUD_UpdateClientData( client_data_t *cdata, float flTime ); +int EXPORT HUD_Reset ( void ); +void EXPORT HUD_PlayerMove( struct playermove_s *ppmove, int server ); +void EXPORT HUD_PlayerMoveInit( struct playermove_s *ppmove ); +char EXPORT HUD_PlayerMoveTexture( char *name ); +int EXPORT HUD_ConnectionlessPacket( struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); +int EXPORT HUD_GetHullBounds( int hullnumber, float *mins, float *maxs ); +void EXPORT HUD_Frame( double time ); +void EXPORT HUD_PostRunCmd( struct local_state_s *from, struct local_state_s *to, struct usercmd_s *cmd, int runfuncs, double time, unsigned int random_seed ); +void EXPORT HUD_VoiceStatus(int entindex, qboolean bTalking); +void EXPORT HUD_DirectorMessage( int iSize, void *pbuf ); +} + +/* +================================ +HUD_GetHullBounds + + Engine calls this to enumerate player collision hulls, for prediction. Return 0 if the hullnumber doesn't exist. +================================ +*/ +int EXPORT HUD_GetHullBounds( int hullnumber, float *mins, float *maxs ) +{ + int iret = 0; + + switch ( hullnumber ) + { + case 0: // Normal player + mins = Vector(-16, -16, -32); + maxs = Vector(16, 16, 32); + iret = 1; + break; + case 1: // Crouched player + mins = Vector(-16, -16, -32); + maxs = Vector(16, 16, 32); + iret = 1; + break; + case 2: // Point based hull + mins = Vector( 0, 0, 0 ); + maxs = Vector( 0, 0, 0 ); + iret = 1; + break; + } + + return iret; +} + +/* +================================ +HUD_ConnectionlessPacket + + Return 1 if the packet is valid. Set response_buffer_size if you want to send a response packet. Incoming, it holds the max + size of the response_buffer, so you must zero it out if you choose not to respond. +================================ +*/ +int EXPORT HUD_ConnectionlessPacket( struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ) +{ + // Parse stuff from args + int max_buffer_size = *response_buffer_size; + + // Zero it out since we aren't going to respond. + // If we wanted to response, we'd write data into response_buffer + *response_buffer_size = 0; + + // Since we don't listen for anything here, just respond that it's a bogus message + // If we didn't reject the message, we'd return 1 for success instead. + return 0; +} + +void EXPORT HUD_PlayerMoveInit( struct playermove_s *ppmove ) +{ + PM_Init( ppmove ); +} + +char EXPORT HUD_PlayerMoveTexture( char *name ) +{ + return PM_FindTextureType( name ); +} + +void EXPORT HUD_PlayerMove( struct playermove_s *ppmove, int server ) +{ + PM_Move( ppmove, server ); +} + +int EXPORT Initialize( cl_enginefunc_t *pEnginefuncs, int iVersion ) +{ + gEngfuncs = *pEnginefuncs; + + //!!! mwh UNDONE We need to think about our versioning strategy. Do we want to try to be compatible + // with previous versions, especially when we're only 'bonus' functionality? Should it be the engine + // that decides if the DLL is compliant? + + if (iVersion != CLDLL_INTERFACE_VERSION) + return 0; + + memcpy(&gEngfuncs, pEnginefuncs, sizeof(cl_enginefunc_t)); + + EV_HookEvents(); + + // Determine which filesystem to use. +#if defined ( _WIN32 ) + char *szFsModule = "filesystem_stdio.dll"; +#elif defined(OSX) + char *szFsModule = "filesystem_stdio.dylib"; +#elif defined(LINUX) + char *szFsModule = "filesystem_stdio.so"; +#else +#error +#endif + + + char szFSDir[MAX_PATH]; + szFSDir[0] = 0; + if ( gEngfuncs.COM_ExpandFilename( szFsModule, szFSDir, sizeof( szFSDir ) ) == FALSE ) + { + return false; + } + + // Get filesystem interface. + g_pFileSystemModule = Sys_LoadModule( szFSDir ); + assert( g_pFileSystemModule ); + if( !g_pFileSystemModule ) + { + return false; + } + + CreateInterfaceFn fileSystemFactory = Sys_GetFactory( g_pFileSystemModule ); + if( !fileSystemFactory ) + { + return false; + } + + g_pFileSystem = ( IFileSystem * )fileSystemFactory( FILESYSTEM_INTERFACE_VERSION, NULL ); + assert( g_pFileSystem ); + if( !g_pFileSystem ) + { + return false; + } + + return 1; +} + + +/* +========================== + HUD_VidInit + +Called when the game initializes +and whenever the vid_mode is changed +so the HUD can reinitialize itself. +========================== +*/ + +int EXPORT HUD_VidInit( void ) +{ + gHUD.VidInit(); + + VGui_Startup(); + + return 1; +} + +/* +========================== + HUD_Init + +Called whenever the client connects +to a server. Reinitializes all +the hud variables. +========================== +*/ + +int EXPORT HUD_Init( void ) +{ + InitInput(); + gHUD.Init(); + Scheme_Init(); + + return 1; +} + + +/* +========================== + HUD_Redraw + +called every screen frame to +redraw the HUD. +=========================== +*/ + +int EXPORT HUD_Redraw( float time, int intermission ) +{ + gHUD.Redraw( time, intermission ); + + return 1; +} + + +/* +========================== + HUD_UpdateClientData + +called every time shared client +dll/engine data gets changed, +and gives the cdll a chance +to modify the data. + +returns 1 if anything has been changed, 0 otherwise. +========================== +*/ + +int EXPORT HUD_UpdateClientData(client_data_t *pcldata, float flTime ) +{ + return gHUD.UpdateClientData(pcldata, flTime ); +} + +/* +========================== + HUD_Reset + +Called at start and end of demos to restore to "non"HUD state. +========================== +*/ + +int EXPORT HUD_Reset( void ) +{ + gHUD.VidInit(); + return 1; +} + +/* +========================== +HUD_Frame + +Called by engine every frame that client .dll is loaded +========================== +*/ + +void EXPORT HUD_Frame( double time ) +{ + IN_Commands(); + + ServersThink( time ); + + GetClientVoiceMgr()->Frame(time); +} + + +/* +========================== +HUD_VoiceStatus + +Called when a player starts or stops talking. +========================== +*/ + +void EXPORT HUD_VoiceStatus(int entindex, qboolean bTalking) +{ + GetClientVoiceMgr()->UpdateSpeakerStatus(entindex, bTalking); +} + +/* +========================== +HUD_DirectorMessage + +Called when a director event message was received +========================== +*/ + +void EXPORT HUD_DirectorMessage( int iSize, void *pbuf ) +{ + gHUD.m_Spectator.DirectorMessage( iSize, pbuf ); +} diff --git a/dmc/cl_dll/cl_dll.dsp b/dmc/cl_dll/cl_dll.dsp new file mode 100644 index 0000000..d64a514 --- /dev/null +++ b/dmc/cl_dll/cl_dll.dsp @@ -0,0 +1,598 @@ +# Microsoft Developer Studio Project File - Name="cl_dll" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=cl_dll - 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 "cl_dll.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 "cl_dll.mak" CFG="cl_dll - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "cl_dll - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "cl_dll - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/GoldSrc/dmc/cl_dlls", STQCAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "cl_dll - 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" /YX /c +# ADD CPP /nologo /MT /W3 /GX /Zi /O2 /I "..\utils\common" /I "..\..\public" /I "..\engine" /I "..\common" /I "..\utils\vgui\include" /I "..\dlls" /I "..\..\engine" /I "..\..\common" /I "..\pm_shared" /I "..\..\utils\vgui\include" /I "." /I "..\..\game_shared" /I "..\..\external" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "CLIENT_DLL" /D "DMC_BUILD" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /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 /subsystem:windows /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib winmm.lib ..\..\utils\vgui\lib\win32_vc6\vgui.lib wsock32.lib ..\..\lib\public\sdl2.lib /nologo /subsystem:windows /dll /map /debug /machine:I386 /out:".\Release\client.dll" +# Begin Custom Build - Copying to cl_dlls +InputDir=.\Release +ProjDir=. +InputPath=.\Release\client.dll +SOURCE="$(InputPath)" + +BuildCmds= \ + call ..\..\filecopy.bat $(InputPath) $(ProjDir)\..\..\..\game\mod\cl_dlls\client.dll \ + call ..\..\filecopy.bat $(InputDir)\client.pdb $(ProjDir)\..\..\..\game\mod\cl_dlls\client.pdb \ + + +"..\..\..\game\mod\cl_dlls\client.dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\game\mod\cl_dlls\client.pdb" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) +# End Custom Build + +!ELSEIF "$(CFG)" == "cl_dll - 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" /YX /c +# ADD CPP /nologo /MTd /W3 /GX /ZI /Od /I "..\..\public" /I "..\engine" /I "..\common" /I "..\utils\vgui\include" /I "..\dlls" /I "..\..\engine" /I "..\..\common" /I "..\pm_shared" /I "..\..\utils\vgui\include" /I "." /I "..\..\game_shared" /I "..\..\external" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "CLIENT_DLL" /D "DMC_BUILD" /FR /Fp".\Release/cl_dll.pch" /YX /Fo".\Release/" /Fd".\Release/" /FD /c +# ADD BASE MTL /nologo /D "_DEBUG" /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 gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 +# ADD LINK32 oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib winmm.lib ..\..\utils\vgui\lib\win32_vc6\vgui.lib wsock32.lib ..\..\lib\public\sdl2.lib /nologo /subsystem:windows /dll /profile /map /debug /machine:I386 /out:".\Debug\client.dll" +# Begin Custom Build - Copying to cl_dlls +InputDir=.\Debug +ProjDir=. +InputPath=.\Debug\client.dll +SOURCE="$(InputPath)" + +BuildCmds= \ + call ..\..\filecopy.bat $(InputPath) $(ProjDir)\..\..\..\game\mod\cl_dlls\client.dll \ + call ..\..\filecopy.bat $(InputDir)\client.pdb $(ProjDir)\..\..\..\game\mod\cl_dlls\client.pdb \ + + +"..\..\..\game\mod\cl_dlls\client.dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"..\..\..\game\mod\cl_dlls\client.pdb" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "cl_dll - Win32 Release" +# Name "cl_dll - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Group "quake" + +# PROP Default_Filter "*.cpp" +# Begin Source File + +SOURCE=.\CTF_FlagStatus.cpp +# End Source File +# Begin Source File + +SOURCE=.\CTF_HudMessage.cpp +# End Source File +# Begin Source File + +SOURCE=.\DMC_Teleporters.cpp +# End Source File +# Begin Source File + +SOURCE=.\ev_hldm.cpp +# End Source File +# Begin Source File + +SOURCE=.\quake\quake_baseentity.cpp +# End Source File +# Begin Source File + +SOURCE=.\quake\quake_events.cpp +# End Source File +# Begin Source File + +SOURCE=..\dlls\quake_gun.cpp +# End Source File +# Begin Source File + +SOURCE=.\quake\quake_objects.cpp +# End Source File +# Begin Source File + +SOURCE=.\quake\quake_weapons.cpp +# End Source File +# Begin Source File + +SOURCE=..\dlls\quake_weapons_all.cpp +# End Source File +# Begin Source File + +SOURCE=.\studio_util.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_SpectatorPanel.cpp +# End Source File +# End Group +# Begin Source File + +SOURCE=.\ammo.cpp +# End Source File +# Begin Source File + +SOURCE=.\ammo_secondary.cpp +# End Source File +# Begin Source File + +SOURCE=.\ammohistory.cpp +# End Source File +# Begin Source File + +SOURCE=.\battery.cpp +# End Source File +# Begin Source File + +SOURCE=.\cdll_int.cpp +# End Source File +# Begin Source File + +SOURCE=.\com_weapons.cpp +# End Source File +# Begin Source File + +SOURCE=.\death.cpp +# End Source File +# Begin Source File + +SOURCE=.\demo.cpp +# End Source File +# Begin Source File + +SOURCE=.\entity.cpp +# End Source File +# Begin Source File + +SOURCE=.\ev_common.cpp +# End Source File +# Begin Source File + +SOURCE=.\events.cpp +# End Source File +# Begin Source File + +SOURCE=.\GameStudioModelRenderer.cpp +# End Source File +# Begin Source File + +SOURCE=.\geiger.cpp +# End Source File +# Begin Source File + +SOURCE=.\health.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud_msg.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud_redraw.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud_servers.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud_spectator.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud_update.cpp +# End Source File +# Begin Source File + +SOURCE=.\in_camera.cpp +# End Source File +# Begin Source File + +SOURCE=.\input.cpp +# End Source File +# Begin Source File + +SOURCE=.\inputw32.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\public\interface.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu.cpp +# End Source File +# Begin Source File + +SOURCE=.\message.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\common\parsemsg.cpp +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_debug.c +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_math.c +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_shared.c +# End Source File +# Begin Source File + +SOURCE=.\saytext.cpp +# End Source File +# Begin Source File + +SOURCE=.\scoreboard.cpp +# PROP Exclude_From_Build 1 +# End Source File +# Begin Source File + +SOURCE=.\status_icons.cpp +# End Source File +# Begin Source File + +SOURCE=.\statusbar.cpp +# End Source File +# Begin Source File + +SOURCE=.\StudioModelRenderer.cpp +# End Source File +# Begin Source File + +SOURCE=.\text_message.cpp +# End Source File +# Begin Source File + +SOURCE=.\train.cpp +# End Source File +# Begin Source File + +SOURCE=.\tri.cpp +# End Source File +# Begin Source File + +SOURCE=.\util.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_checkbutton2.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_grid.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_helpers.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_int.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_listbox.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_loadtga.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_ScorePanel.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_scrollbar2.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_ServerBrowser.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_slider2.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_viewport.cpp +# End Source File +# Begin Source File + +SOURCE=.\view.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\voice_banmgr.cpp +# End Source File +# Begin Source File + +SOURCE=.\voice_status.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=.\ammo.h +# End Source File +# Begin Source File + +SOURCE=.\ammohistory.h +# End Source File +# Begin Source File + +SOURCE=.\camera.h +# End Source File +# Begin Source File + +SOURCE=.\cl_dll.h +# End Source File +# Begin Source File + +SOURCE=.\cl_util.h +# End Source File +# Begin Source File + +SOURCE=.\com_weapons.h +# End Source File +# Begin Source File + +SOURCE=.\demo.h +# End Source File +# Begin Source File + +SOURCE=.\DMC_BSPFile.h +# End Source File +# Begin Source File + +SOURCE=.\DMC_Teleporters.h +# End Source File +# Begin Source File + +SOURCE=.\ev_hldm.h +# End Source File +# Begin Source File + +SOURCE=.\eventscripts.h +# End Source File +# Begin Source File + +SOURCE=.\GameStudioModelRenderer.h +# End Source File +# Begin Source File + +SOURCE=.\health.h +# End Source File +# Begin Source File + +SOURCE=.\hud.h +# End Source File +# Begin Source File + +SOURCE=.\hud_iface.h +# End Source File +# Begin Source File + +SOURCE=.\hud_servers.h +# End Source File +# Begin Source File + +SOURCE=.\hud_servers_priv.h +# End Source File +# Begin Source File + +SOURCE=.\hud_spectator.h +# End Source File +# Begin Source File + +SOURCE=.\in_defs.h +# End Source File +# Begin Source File + +SOURCE=.\kbutton.h +# End Source File +# Begin Source File + +SOURCE=..\..\common\parsemsg.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_debug.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_defs.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_info.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_materials.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_movevars.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_shared.h +# End Source File +# Begin Source File + +SOURCE=..\dlls\quake_gun.h +# End Source File +# Begin Source File + +SOURCE=.\StudioModelRenderer.h +# End Source File +# Begin Source File + +SOURCE=.\util.h +# End Source File +# Begin Source File + +SOURCE=.\util_vector.h +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_checkbutton2.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_ControlConfigPanel.h +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_grid.h +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_helpers.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_int.h +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_listbox.h +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_loadtga.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_SchemeManager.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_ScorePanel.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_ServerBrowser.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_SpectatorPanel.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_viewport.h +# End Source File +# Begin Source File + +SOURCE=.\view.h +# End Source File +# Begin Source File + +SOURCE=.\voice_status.h +# End Source File +# Begin Source File + +SOURCE=.\wrect.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# Begin Source File + +SOURCE=.\vgui_CustomObjects.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_MOTDWindow.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_SchemeManager.cpp +# End Source File +# End Target +# End Project diff --git a/dmc/cl_dll/cl_dll.h b/dmc/cl_dll/cl_dll.h new file mode 100644 index 0000000..1422cfa --- /dev/null +++ b/dmc/cl_dll/cl_dll.h @@ -0,0 +1,43 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cl_dll.h +// + +// 4-23-98 JOHN + +// +// This DLL is linked by the client when they first initialize. +// This DLL is responsible for the following tasks: +// - Loading the HUD graphics upon initialization +// - Drawing the HUD graphics every frame +// - Handling the custum HUD-update packets +// +typedef unsigned char byte; +typedef unsigned short word; +typedef float vec_t; +typedef int (*pfnUserMsgHook)(const char *pszName, int iSize, void *pbuf); + +#include "util_vector.h" +#ifdef _WIN32 +#define EXPORT _declspec( dllexport ) +#else +#define EXPORT __attribute__ ((visibility("default"))) +#endif + +#include "../engine/cdll_int.h" +#include "../dlls/cdll_dll.h" + +extern cl_enginefunc_t gEngfuncs; diff --git a/dmc/cl_dll/cl_util.h b/dmc/cl_dll/cl_util.h new file mode 100644 index 0000000..33162ec --- /dev/null +++ b/dmc/cl_dll/cl_util.h @@ -0,0 +1,199 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cl_util.h +// + +#include "cvardef.h" + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +#include // for safe_sprintf() +#include // " +#include // for safe_strcpy() + + +// Macros to hook function calls into the HUD object +#define HOOK_MESSAGE(x) gEngfuncs.pfnHookUserMsg(#x, __MsgFunc_##x ); + +#define DECLARE_MESSAGE(y, x) int __MsgFunc_##x(const char *pszName, int iSize, void *pbuf) \ + { \ + return gHUD.y.MsgFunc_##x(pszName, iSize, pbuf ); \ + } + + +#define HOOK_COMMAND(x, y) gEngfuncs.pfnAddCommand( x, __CmdFunc_##y ); +#define DECLARE_COMMAND(y, x) void __CmdFunc_##x( void ) \ + { \ + gHUD.y.UserCmd_##x( ); \ + } + +inline float CVAR_GET_FLOAT( const char *x ) { return gEngfuncs.pfnGetCvarFloat( (char*)x ); } +inline char* CVAR_GET_STRING( const char *x ) { return gEngfuncs.pfnGetCvarString( (char*)x ); } +inline struct cvar_s *CVAR_CREATE( const char *cv, const char *val, const int flags ) { return gEngfuncs.pfnRegisterVariable( (char*)cv, (char*)val, flags ); } + +#define SPR_Load (*gEngfuncs.pfnSPR_Load) +#define SPR_Set (*gEngfuncs.pfnSPR_Set) +#define SPR_Frames (*gEngfuncs.pfnSPR_Frames) +#define SPR_GetList (*gEngfuncs.pfnSPR_GetList) + +// SPR_Draw draws a the current sprite as solid +#define SPR_Draw (*gEngfuncs.pfnSPR_Draw) +// SPR_DrawHoles draws the current sprites, with color index255 not drawn (transparent) +#define SPR_DrawHoles (*gEngfuncs.pfnSPR_DrawHoles) +// SPR_DrawAdditive adds the sprites RGB values to the background (additive transulency) +#define SPR_DrawAdditive (*gEngfuncs.pfnSPR_DrawAdditive) + +// SPR_EnableScissor sets a clipping rect for HUD sprites. (0,0) is the top-left hand corner of the screen. +#define SPR_EnableScissor (*gEngfuncs.pfnSPR_EnableScissor) +// SPR_DisableScissor disables the clipping rect +#define SPR_DisableScissor (*gEngfuncs.pfnSPR_DisableScissor) +// +#define FillRGBA (*gEngfuncs.pfnFillRGBA) + + +// ScreenHeight returns the height of the screen, in pixels +#define ScreenHeight (gHUD.m_scrinfo.iHeight) +// ScreenWidth returns the width of the screen, in pixels +#define ScreenWidth (gHUD.m_scrinfo.iWidth) + +// Use this to set any co-ords in 640x480 space +#define XRES(x) ((int)(float(x) * ((float)ScreenWidth / 640.0f) + 0.5f)) +#define YRES(y) ((int)(float(y) * ((float)ScreenHeight / 480.0f) + 0.5f)) + +// use this to project world coordinates to screen coordinates +#define XPROJECT(x) ( (1.0f+(x))*ScreenWidth*0.5f ) +#define YPROJECT(y) ( (1.0f-(y))*ScreenHeight*0.5f ) + +#define GetScreenInfo (*gEngfuncs.pfnGetScreenInfo) +#define ServerCmd (*gEngfuncs.pfnServerCmd) +#define ClientCmd (*gEngfuncs.pfnClientCmd) +#define SetCrosshair (*gEngfuncs.pfnSetCrosshair) +#define AngleVectors (*gEngfuncs.pfnAngleVectors) + + +// Gets the height & width of a sprite, at the specified frame +inline int SPR_Height( HSPRITE x, int f ) { return gEngfuncs.pfnSPR_Height(x, f); } +inline int SPR_Width( HSPRITE x, int f ) { return gEngfuncs.pfnSPR_Width(x, f); } + +inline client_textmessage_t *TextMessageGet( const char *pName ) { return gEngfuncs.pfnTextMessageGet( pName ); } +inline int TextMessageDrawChar( int x, int y, int number, int r, int g, int b ) +{ + return gEngfuncs.pfnDrawCharacter( x, y, number, r, g, b ); +} + +inline int DrawConsoleString( int x, int y, const char *string ) +{ + return gEngfuncs.pfnDrawConsoleString( x, y, (char*) string ); +} + +inline void GetConsoleStringSize( const char *string, int *width, int *height ) +{ + gEngfuncs.pfnDrawConsoleStringLen( string, width, height ); +} + +inline int ConsoleStringLen( const char *string ) +{ + int _width, _height; + GetConsoleStringSize( string, &_width, &_height ); + return _width; +} + +inline void ConsolePrint( const char *string ) +{ + gEngfuncs.pfnConsolePrint( string ); +} + +inline void CenterPrint( const char *string ) +{ + gEngfuncs.pfnCenterPrint( string ); +} + + +inline char *safe_strcpy( char *dst, const char *src, int len_dst) +{ + if( len_dst <= 0 ) + { + return NULL; // this is bad + } + + strncpy(dst,src,len_dst); + dst[ len_dst - 1 ] = '\0'; + + return dst; +} + +inline int safe_sprintf( char *dst, int len_dst, const char *format, ...) +{ + if( len_dst <= 0 ) + { + return -1; // this is bad + } + + va_list v; + + va_start(v, format); + + _vsnprintf(dst,len_dst,format,v); + + va_end(v); + + dst[ len_dst - 1 ] = '\0'; + + return 0; +} + +// returns the players name of entity no. +#define GetPlayerInfo (*gEngfuncs.pfnGetPlayerInfo) + +// sound functions +inline void PlaySound( char *szSound, float vol ) { gEngfuncs.pfnPlaySoundByName( szSound, vol ); } +inline void PlaySound( int iSound, float vol ) { gEngfuncs.pfnPlaySoundByIndex( iSound, vol ); } + +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#define fabs(x) ((x) > 0 ? (x) : 0 - (x)) + +void ScaleColors( int &r, int &g, int &b, int a ); + +#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 VectorClear(a) { a[0]=0.0;a[1]=0.0;a[2]=0.0;} +float Length(const float *v); +void VectorMA (const float *veca, float scale, const float *vecb, float *vecc); +void VectorScale (const float *in, float scale, float *out); +float VectorNormalize (float *v); +void VectorInverse ( float *v ); + +extern vec3_t vec3_origin; + +// disable 'possible loss of data converting float to int' warning message +#pragma warning( disable: 4244 ) +// disable 'truncation from 'const double' to 'float' warning message +#pragma warning( disable: 4305 ) + +inline void UnpackRGB(int &r, int &g, int &b, unsigned long ulRGB)\ +{\ + r = (ulRGB & 0xFF0000) >>16;\ + g = (ulRGB & 0xFF00) >> 8;\ + b = ulRGB & 0xFF;\ +} + +HSPRITE LoadSprite(const char *pszName); diff --git a/dmc/cl_dll/com_model.h b/dmc/cl_dll/com_model.h new file mode 100644 index 0000000..2711599 --- /dev/null +++ b/dmc/cl_dll/com_model.h @@ -0,0 +1,359 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +// com_model.h +#if !defined( COM_MODEL_H ) +#define COM_MODEL_H +#if defined( _WIN32 ) +#pragma once +#endif + +#define STUDIO_RENDER 1 +#define STUDIO_EVENTS 2 + +#define MAX_CLIENTS 32 +#define MAX_EDICTS 900 + +#define MAX_MODEL_NAME 64 +#define MAX_MAP_HULLS 4 +#define MIPLEVELS 4 +#define NUM_AMBIENTS 4 // automatic ambient sounds +#define MAXLIGHTMAPS 4 +#define PLANE_ANYZ 5 + +#define ALIAS_Z_CLIP_PLANE 5 + +// flags in finalvert_t.flags +#define ALIAS_LEFT_CLIP 0x0001 +#define ALIAS_TOP_CLIP 0x0002 +#define ALIAS_RIGHT_CLIP 0x0004 +#define ALIAS_BOTTOM_CLIP 0x0008 +#define ALIAS_Z_CLIP 0x0010 +#define ALIAS_ONSEAM 0x0020 +#define ALIAS_XY_CLIP_MASK 0x000F + +#define ZISCALE ((float)0x8000) + +#define CACHE_SIZE 32 // used to align key data structures + +typedef enum +{ + mod_brush, + mod_sprite, + mod_alias, + mod_studio +} modtype_t; + +// must match definition in modelgen.h +#ifndef SYNCTYPE_T +#define SYNCTYPE_T + +typedef enum +{ + ST_SYNC=0, + ST_RAND +} synctype_t; + +#endif + +typedef struct +{ + float mins[3], maxs[3]; + float origin[3]; + int headnode[MAX_MAP_HULLS]; + int visleafs; // not including the solid leaf 0 + int firstface, numfaces; +} dmodel_t; + +// plane_t structure +typedef struct mplane_s +{ + vec3_t normal; // surface normal + float dist; // closest appoach to origin + byte type; // for texture axis selection and fast side tests + byte signbits; // signx + signy<<1 + signz<<1 + byte pad[2]; +} mplane_t; + +typedef struct +{ + vec3_t position; +} mvertex_t; + +typedef struct +{ + unsigned short v[2]; + unsigned int cachededgeoffset; +} medge_t; + +typedef struct texture_s +{ + char name[16]; + unsigned width, height; + int anim_total; // total tenths in sequence ( 0 = no) + int anim_min, anim_max; // time for this frame min <=time< max + struct texture_s *anim_next; // in the animation sequence + struct texture_s *alternate_anims; // bmodels in frame 1 use these + unsigned offsets[MIPLEVELS]; // four mip maps stored + unsigned paloffset; +} texture_t; + +typedef struct +{ + float vecs[2][4]; // [s/t] unit vectors in world space. + // [i][3] is the s/t offset relative to the origin. + // s or t = dot(3Dpoint,vecs[i])+vecs[i][3] + float mipadjust; // ?? mipmap limits for very small surfaces + texture_t *texture; + int flags; // sky or slime, no lightmap or 256 subdivision +} mtexinfo_t; + +typedef struct mnode_s +{ +// common with leaf + int contents; // 0, to differentiate from leafs + int visframe; // node needs to be traversed if current + + short minmaxs[6]; // for bounding box culling + + struct mnode_s *parent; + +// node specific + mplane_t *plane; + struct mnode_s *children[2]; + + unsigned short firstsurface; + unsigned short numsurfaces; +} mnode_t; + +typedef struct msurface_s msurface_t; +typedef struct decal_s decal_t; + +// JAY: Compress this as much as possible +struct decal_s +{ + decal_t *pnext; // linked list for each surface + msurface_t *psurface; // Surface id for persistence / unlinking + short dx; // Offsets into surface texture (in texture coordinates, so we don't need floats) + short dy; + short texture; // Decal texture + byte scale; // Pixel scale + byte flags; // Decal flags + + short entityIndex; // Entity this is attached to +}; + +typedef struct mleaf_s +{ +// common with node + int contents; // wil be a negative contents number + int visframe; // node needs to be traversed if current + + short minmaxs[6]; // for bounding box culling + + struct mnode_s *parent; + +// leaf specific + byte *compressed_vis; + struct efrag_s *efrags; + + msurface_t **firstmarksurface; + int nummarksurfaces; + int key; // BSP sequence number for leaf's contents + byte ambient_sound_level[NUM_AMBIENTS]; +} mleaf_t; + +struct msurface_s +{ + int visframe; // should be drawn when node is crossed + + int dlightframe; // last frame the surface was checked by an animated light + int dlightbits; // dynamically generated. Indicates if the surface illumination + // is modified by an animated light. + + mplane_t *plane; // pointer to shared plane + int flags; // see SURF_ #defines + + int firstedge; // look up in model->surfedges[], negative numbers + int numedges; // are backwards edges + +// surface generation data + struct surfcache_s *cachespots[MIPLEVELS]; + + short texturemins[2]; // smallest s/t position on the surface. + short extents[2]; // ?? s/t texture size, 1..256 for all non-sky surfaces + + mtexinfo_t *texinfo; + +// lighting info + byte styles[MAXLIGHTMAPS]; // index into d_lightstylevalue[] for animated lights + // no one surface can be effected by more than 4 + // animated lights. + color24 *samples; + + decal_t *pdecals; +}; + +typedef struct +{ + int planenum; + short children[2]; // negative numbers are contents +} dclipnode_t; + +typedef struct hull_s +{ + dclipnode_t *clipnodes; + mplane_t *planes; + int firstclipnode; + int lastclipnode; + vec3_t clip_mins; + vec3_t clip_maxs; +} hull_t; + +#if !defined( CACHE_USER ) && !defined( QUAKEDEF_H ) +#define CACHE_USER +typedef struct cache_user_s +{ + void *data; +} cache_user_t; +#endif + +typedef struct model_s +{ + char name[ MAX_MODEL_NAME ]; + qboolean needload; // bmodels and sprites don't cache normally + + modtype_t type; + int numframes; + synctype_t synctype; + + int flags; + +// +// volume occupied by the model +// + vec3_t mins, maxs; + float radius; + +// +// brush model +// + int firstmodelsurface, nummodelsurfaces; + + int numsubmodels; + dmodel_t *submodels; + + int numplanes; + mplane_t *planes; + + int numleafs; // number of visible leafs, not counting 0 + struct mleaf_s *leafs; + + int numvertexes; + mvertex_t *vertexes; + + int numedges; + medge_t *edges; + + int numnodes; + mnode_t *nodes; + + int numtexinfo; + mtexinfo_t *texinfo; + + int numsurfaces; + msurface_t *surfaces; + + int numsurfedges; + int *surfedges; + + int numclipnodes; + dclipnode_t *clipnodes; + + int nummarksurfaces; + msurface_t **marksurfaces; + + hull_t hulls[MAX_MAP_HULLS]; + + int numtextures; + texture_t **textures; + + byte *visdata; + + color24 *lightdata; + + char *entities; + +// +// additional model data +// + cache_user_t cache; // only access through Mod_Extradata + +} model_t; + +typedef vec_t vec4_t[4]; + +typedef struct alight_s +{ + int ambientlight; // clip at 128 + int shadelight; // clip at 192 - ambientlight + vec3_t color; + float *plightvec; +} alight_t; + +typedef struct auxvert_s +{ + float fv[3]; // viewspace x, y +} auxvert_t; + +#include "custom.h" + +#define MAX_INFO_STRING 256 +#define MAX_SCOREBOARDNAME 32 +typedef struct player_info_s +{ + // User id on server + int userid; + + // User info string + char userinfo[ MAX_INFO_STRING ]; + + // Name + char name[ MAX_SCOREBOARDNAME ]; + + // Spectator or not, unused + int spectator; + + int ping; + int packet_loss; + + // skin information + char model[MAX_QPATH]; + int topcolor; + int bottomcolor; + + // last frame rendered + int renderframe; + + // Gait frame estimation + int gaitsequence; + float gaitframe; + float gaityaw; + vec3_t prevgaitorigin; + + customization_t customdata; +} player_info_t; + +#endif // #define COM_MODEL_H diff --git a/dmc/cl_dll/com_weapons.cpp b/dmc/cl_dll/com_weapons.cpp new file mode 100644 index 0000000..ee51d5b --- /dev/null +++ b/dmc/cl_dll/com_weapons.cpp @@ -0,0 +1,278 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +// Com_Weapons.cpp +// Shared weapons common/shared functions +#include +#include "hud.h" +#include "cl_util.h" +#include "com_weapons.h" + +#include "const.h" +#include "entity_state.h" +#include "r_efx.h" + +// g_runfuncs is true if this is the first time we've "predicated" a particular movement/firing +// command. If it is 1, then we should play events/sounds etc., otherwise, we just will be +// updating state info, but not firing events +int g_runfuncs = 0; + +// During our weapon prediction processing, we'll need to reference some data that is part of +// the final state passed into the postthink functionality. We'll set this pointer and then +// reset it to NULL as appropriate +struct local_state_s *g_finalstate = NULL; + +/* +==================== +COM_Log + +Log debug messages to file ( appends ) +==================== +*/ +void COM_Log( char *pszFile, char *fmt, ...) +{ + va_list argptr; + char string[1024]; + FILE *fp; + char *pfilename; + + if ( !pszFile ) + { + pfilename = "c:\\hllog.txt"; + } + else + { + pfilename = pszFile; + } + + va_start (argptr,fmt); + vsprintf (string, fmt,argptr); + va_end (argptr); + + fp = fopen( pfilename, "a+t"); + if (fp) + { + fprintf(fp, "%s", string); + fclose(fp); + } +} + +// remember the current animation for the view model, in case we get out of sync with +// server. +static int g_currentanim; + +/* +===================== +HUD_SendWeaponAnim + +Change weapon model animation +===================== +*/ +void HUD_SendWeaponAnim( int iAnim, int body, int force ) +{ + // Don't actually change it. + if ( !g_runfuncs && !force ) + return; + + g_currentanim = iAnim; + + // Tell animation system new info + gEngfuncs.pfnWeaponAnim( iAnim, body ); +} + +/* +===================== +HUD_GetWeaponAnim + +Retrieve current predicted weapon animation +===================== +*/ +int HUD_GetWeaponAnim( void ) +{ + return g_currentanim; +} + +/* +===================== +HUD_PlaySound + +Play a sound, if we are seeing this command for the first time +===================== +*/ +void HUD_PlaySound( char *sound, float volume ) +{ + if ( !g_runfuncs || !g_finalstate ) + return; + + gEngfuncs.pfnPlaySoundByNameAtLocation( sound, volume, (float *)&g_finalstate->playerstate.origin ); +} + +/* +===================== +HUD_PlaybackEvent + +Directly queue up an event on the client +===================== +*/ +void HUD_PlaybackEvent( int flags, const edict_t *pInvoker, unsigned short eventindex, float delay, + float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ) +{ + vec3_t org; + vec3_t ang; + + if ( !g_runfuncs || !g_finalstate ) + return; + + // Weapon prediction events are assumed to occur at the player's origin + org = g_finalstate->playerstate.origin; + ang = v_angles; + gEngfuncs.pfnPlaybackEvent( flags, pInvoker, eventindex, delay, (float *)&org, (float *)&ang, fparam1, fparam2, iparam1, iparam2, bparam1, bparam2 ); +} + +/* +===================== +HUD_SetMaxSpeed + +===================== +*/ +void HUD_SetMaxSpeed( const edict_t *ed, float speed ) +{ +} + + +/* +===================== +UTIL_WeaponTimeBase + +Always 0.0 on client, even if not predicting weapons ( won't get called + in that case ) +===================== +*/ +float UTIL_WeaponTimeBase( void ) +{ + return 0.0; +} + +static unsigned int glSeed = 0; + +unsigned int seed_table[ 256 ] = +{ + 28985, 27138, 26457, 9451, 17764, 10909, 28790, 8716, 6361, 4853, 17798, 21977, 19643, 20662, 10834, 20103, + 27067, 28634, 18623, 25849, 8576, 26234, 23887, 18228, 32587, 4836, 3306, 1811, 3035, 24559, 18399, 315, + 26766, 907, 24102, 12370, 9674, 2972, 10472, 16492, 22683, 11529, 27968, 30406, 13213, 2319, 23620, 16823, + 10013, 23772, 21567, 1251, 19579, 20313, 18241, 30130, 8402, 20807, 27354, 7169, 21211, 17293, 5410, 19223, + 10255, 22480, 27388, 9946, 15628, 24389, 17308, 2370, 9530, 31683, 25927, 23567, 11694, 26397, 32602, 15031, + 18255, 17582, 1422, 28835, 23607, 12597, 20602, 10138, 5212, 1252, 10074, 23166, 19823, 31667, 5902, 24630, + 18948, 14330, 14950, 8939, 23540, 21311, 22428, 22391, 3583, 29004, 30498, 18714, 4278, 2437, 22430, 3439, + 28313, 23161, 25396, 13471, 19324, 15287, 2563, 18901, 13103, 16867, 9714, 14322, 15197, 26889, 19372, 26241, + 31925, 14640, 11497, 8941, 10056, 6451, 28656, 10737, 13874, 17356, 8281, 25937, 1661, 4850, 7448, 12744, + 21826, 5477, 10167, 16705, 26897, 8839, 30947, 27978, 27283, 24685, 32298, 3525, 12398, 28726, 9475, 10208, + 617, 13467, 22287, 2376, 6097, 26312, 2974, 9114, 21787, 28010, 4725, 15387, 3274, 10762, 31695, 17320, + 18324, 12441, 16801, 27376, 22464, 7500, 5666, 18144, 15314, 31914, 31627, 6495, 5226, 31203, 2331, 4668, + 12650, 18275, 351, 7268, 31319, 30119, 7600, 2905, 13826, 11343, 13053, 15583, 30055, 31093, 5067, 761, + 9685, 11070, 21369, 27155, 3663, 26542, 20169, 12161, 15411, 30401, 7580, 31784, 8985, 29367, 20989, 14203, + 29694, 21167, 10337, 1706, 28578, 887, 3373, 19477, 14382, 675, 7033, 15111, 26138, 12252, 30996, 21409, + 25678, 18555, 13256, 23316, 22407, 16727, 991, 9236, 5373, 29402, 6117, 15241, 27715, 19291, 19888, 19847 +}; + +unsigned int U_Random( void ) +{ + glSeed *= 69069; + glSeed += seed_table[ glSeed & 0xff ]; + + return ( ++glSeed & 0x0fffffff ); +} + +void U_Srand( unsigned int seed ) +{ + glSeed = seed_table[ seed & 0xff ]; +} + +/* +===================== +UTIL_SharedRandomLong +===================== +*/ +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ) +{ + + unsigned int range; + + U_Srand( (int)seed + low + high ); + + range = high - low + 1; + if ( !(range - 1) ) + { + return low; + } + else + { + int offset; + int rnum; + + rnum = U_Random(); + + offset = rnum % range; + + return (low + offset); + } +} + +/* +===================== +UTIL_SharedRandomFloat +===================== +*/ +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ) +{ + // + unsigned int range; + + U_Srand( (int)seed + *(int *)&low + *(int *)&high ); + + U_Random(); + U_Random(); + + range = high - low; + if ( !range ) + { + return low; + } + else + { + int tensixrand; + float offset; + + tensixrand = U_Random() & 65535; + + offset = (float)tensixrand / 65536.0; + + return (low + offset * range ); + } +} + +/* +====================== +stub_* + +stub functions for such things as precaching. So we don't have to modify weapons code that + is compiled into both game and client .dlls. +====================== +*/ +int stub_PrecacheModel ( char* s ) { return 0; } +int stub_PrecacheSound ( char* s ) { return 0; } +unsigned short stub_PrecacheEvent ( int type, const char *s ) { return 0; } +const char *stub_NameForFunction ( uint32 function ) { return "func"; } +void stub_SetModel ( edict_t *e, const char *m ) {} diff --git a/dmc/cl_dll/com_weapons.h b/dmc/cl_dll/com_weapons.h new file mode 100644 index 0000000..2edd675 --- /dev/null +++ b/dmc/cl_dll/com_weapons.h @@ -0,0 +1,48 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// com_weapons.h +// Shared weapons common function prototypes +#if !defined( COM_WEAPONSH ) +#define COM_WEAPONSH +#ifdef _WIN32 +#pragma once +#endif + +#include "hud_iface.h" + +extern "C" +{ + void _DLLEXPORT HUD_PostRunCmd( struct local_state_s *from, struct local_state_s *to, struct usercmd_s *cmd, int runfuncs, double time, unsigned int random_seed ); +} + +void COM_Log( char *pszFile, char *fmt, ...); +int CL_IsDead( void ); + +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ); +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ); + +int HUD_GetWeaponAnim( void ); +void HUD_SendWeaponAnim( int iAnim, int body, int force ); +void HUD_PlaySound( char *sound, float volume ); +void HUD_PlaybackEvent( int flags, const struct edict_s *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); +void HUD_SetMaxSpeed( const struct edict_s *ed, float speed ); +int stub_PrecacheModel( char* s ); +int stub_PrecacheSound( char* s ); +unsigned short stub_PrecacheEvent( int type, const char *s ); +const char *stub_NameForFunction ( uint32 function ); +void stub_SetModel ( struct edict_s *e, const char *m ); + + +extern cvar_t *cl_lw; + +extern int g_runfuncs; +extern vec3_t v_angles; +extern float g_lastFOV; +extern struct local_state_s *g_finalstate; + +#endif \ No newline at end of file diff --git a/dmc/cl_dll/death.cpp b/dmc/cl_dll/death.cpp new file mode 100644 index 0000000..086a539 --- /dev/null +++ b/dmc/cl_dll/death.cpp @@ -0,0 +1,293 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// death notice +// +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "vgui_viewport.h" + +DECLARE_MESSAGE( m_DeathNotice, DeathMsg ); + +struct DeathNoticeItem { + char szKiller[MAX_PLAYER_NAME_LENGTH*2]; + char szVictim[MAX_PLAYER_NAME_LENGTH*2]; + int iId; // the index number of the associated sprite + int iSuicide; + int iTeamKill; + float flDisplayTime; + float *KillerColor; + float *VictimColor; +}; + +#define MAX_DEATHNOTICES 4 +static int DEATHNOTICE_DISPLAY_TIME = 6; + +#define DEATHNOTICE_TOP 32 + +DeathNoticeItem rgDeathNoticeList[ MAX_DEATHNOTICES + 1 ]; + +extern extra_player_info_t g_PlayerExtraInfo[MAX_PLAYERS+1]; +extern hud_player_info_t g_PlayerInfoList[MAX_PLAYERS+1]; + +float g_ColorBlue[3] = { 0.6, 0.8, 1.0 }; +float g_ColorRed[3] = { 1.0, 0.25, 0.25 }; +float g_ColorGreen[3] = { 0.6, 1.0, 0.6 }; +float g_ColorYellow[3] = { 1.0, 0.7, 0.0 }; +float g_ColorYellowish[3] = { 1.0, 0.625, 0.0 }; + +float *GetClientColor( int clientIndex ) +{ + const char *teamName = g_PlayerExtraInfo[ clientIndex].teamname; + + if ( !teamName || *teamName == 0 ) + return g_ColorYellowish; + + if ( !stricmp( "blue", teamName ) ) + return g_ColorBlue; + else if ( !stricmp( "red", teamName ) ) + return g_ColorRed; + else if ( !stricmp( "green", teamName ) ) + return g_ColorGreen; + else if ( !stricmp( "yellow", teamName ) ) + return g_ColorYellow; + + return g_ColorYellowish; +} + +int GetTeamIndex( int clientIndex ) +{ + const char *teamName = g_PlayerExtraInfo[ clientIndex].teamname; + + if ( !teamName || *teamName == 0 ) + return NULL; + + if ( !stricmp( "red", teamName ) ) + return 1; + else if ( !stricmp( "blue", teamName ) ) + return 2; + + return 0; +} + +int CHudDeathNotice :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( DeathMsg ); + + CVAR_CREATE( "hud_deathnotice_time", "6", 0 ); + + return 1; +} + + +void CHudDeathNotice :: InitHUDData( void ) +{ + memset( rgDeathNoticeList, 0, sizeof(rgDeathNoticeList) ); +} + + +int CHudDeathNotice :: VidInit( void ) +{ + m_HUD_d_skull = gHUD.GetSpriteIndex( "d_skull" ); + + return 1; +} + +int CHudDeathNotice :: Draw( float flTime ) +{ + int x, y, r, g, b; + + for ( int i = 0; i < MAX_DEATHNOTICES; i++ ) + { + if ( rgDeathNoticeList[i].iId == 0 ) + break; // we've gone through them all + + if ( rgDeathNoticeList[i].flDisplayTime < flTime ) + { // display time has expired + // remove the current item from the list + memmove( &rgDeathNoticeList[i], &rgDeathNoticeList[i+1], sizeof(DeathNoticeItem) * (MAX_DEATHNOTICES - i) ); + i--; // continue on the next item; stop the counter getting incremented + continue; + } + + rgDeathNoticeList[i].flDisplayTime = min( rgDeathNoticeList[i].flDisplayTime, gHUD.m_flTime + DEATHNOTICE_DISPLAY_TIME ); + + // Draw the death notice + y = YRES(DEATHNOTICE_TOP) + 2 + (20 * i); //!!! + + int id = (rgDeathNoticeList[i].iId == -1) ? m_HUD_d_skull : rgDeathNoticeList[i].iId; + x = ScreenWidth - ConsoleStringLen(rgDeathNoticeList[i].szVictim) - (gHUD.GetSpriteRect(id).right - gHUD.GetSpriteRect(id).left); + + if ( !rgDeathNoticeList[i].iSuicide ) + { + x -= (5 + ConsoleStringLen( rgDeathNoticeList[i].szKiller ) ); + + // Draw killers name + if ( rgDeathNoticeList[i].KillerColor ) + gEngfuncs.pfnDrawSetTextColor( rgDeathNoticeList[i].KillerColor[0], rgDeathNoticeList[i].KillerColor[1], rgDeathNoticeList[i].KillerColor[2] ); + x = 5 + DrawConsoleString( x, y, rgDeathNoticeList[i].szKiller ); + } + + r = 255; g = 80; b = 0; + if ( rgDeathNoticeList[i].iTeamKill ) + { + r = 10; g = 240; b = 10; // display it in sickly green + } + + // Draw death weapon + SPR_Set( gHUD.GetSprite(id), r, g, b ); + SPR_DrawAdditive( 0, x, y, &gHUD.GetSpriteRect(id) ); + + x += (gHUD.GetSpriteRect(id).right - gHUD.GetSpriteRect(id).left); + + // Draw victims name + if ( rgDeathNoticeList[i].VictimColor ) + gEngfuncs.pfnDrawSetTextColor( rgDeathNoticeList[i].VictimColor[0], rgDeathNoticeList[i].VictimColor[1], rgDeathNoticeList[i].VictimColor[2] ); + x = DrawConsoleString( x, y, rgDeathNoticeList[i].szVictim ); + } + + return 1; +} + + +// This message handler may be better off elsewhere +int CHudDeathNotice :: MsgFunc_DeathMsg( const char *pszName, int iSize, void *pbuf ) +{ + m_iFlags |= HUD_ACTIVE; + + BEGIN_READ( pbuf, iSize ); + + int killer = READ_BYTE(); + int victim = READ_BYTE(); + + char killedwith[32]; + strcpy( killedwith, "d_" ); + strncat( killedwith, READ_STRING(), 32 ); + + if (gViewPort) + gViewPort->DeathMsg( killer, victim ); + + gHUD.m_Spectator.DeathMessage(victim); + int i; + for ( i = 0; i < MAX_DEATHNOTICES; i++ ) + { + if ( rgDeathNoticeList[i].iId == 0 ) + break; + } + if ( i == MAX_DEATHNOTICES ) + { // move the rest of the list forward to make room for this item + memmove( rgDeathNoticeList, rgDeathNoticeList+1, sizeof(DeathNoticeItem) * MAX_DEATHNOTICES ); + i = MAX_DEATHNOTICES - 1; + } + + //gHUD.m_Scoreboard.GetAllPlayersInfo(); + + if (gViewPort) + gViewPort->GetAllPlayersInfo(); + + char *killer_name = g_PlayerInfoList[ killer ].name; + char *victim_name = g_PlayerInfoList[ victim ].name; + if ( !killer_name ) + { + killer_name = ""; + rgDeathNoticeList[i].szKiller[0] = 0; + } + else + { + rgDeathNoticeList[i].KillerColor = GetClientColor( killer ); + strncpy( rgDeathNoticeList[i].szKiller, killer_name, MAX_PLAYER_NAME_LENGTH ); + rgDeathNoticeList[i].szKiller[MAX_PLAYER_NAME_LENGTH-1] = 0; + } + + if ( !victim_name ) + { + victim_name = ""; + rgDeathNoticeList[i].szVictim[0] = 0; + } + else + { + rgDeathNoticeList[i].VictimColor = GetClientColor( victim ); + strncpy( rgDeathNoticeList[i].szVictim, victim_name, MAX_PLAYER_NAME_LENGTH ); + rgDeathNoticeList[i].szVictim[MAX_PLAYER_NAME_LENGTH-1] = 0; + } + + if ( killer == victim || killer == 0 ) + rgDeathNoticeList[i].iSuicide = TRUE; + + if ( !strcmp( killedwith, "d_teammate" ) ) + rgDeathNoticeList[i].iTeamKill = TRUE; + + // Find the sprite in the list + int spr = gHUD.GetSpriteIndex( killedwith ); + + rgDeathNoticeList[i].iId = spr; + + DEATHNOTICE_DISPLAY_TIME = CVAR_GET_FLOAT( "hud_deathnotice_time" ); + rgDeathNoticeList[i].flDisplayTime = gHUD.m_flTime + DEATHNOTICE_DISPLAY_TIME; + + // record the death notice in the console + if ( rgDeathNoticeList[i].iSuicide ) + { + ConsolePrint( rgDeathNoticeList[i].szVictim ); + + if ( !strcmp( killedwith, "d_world" ) ) + { + ConsolePrint( " died" ); + } + else + { + ConsolePrint( " killed self" ); + } + } + else if ( rgDeathNoticeList[i].iTeamKill ) + { + ConsolePrint( rgDeathNoticeList[i].szKiller ); + ConsolePrint( " killed his teammate " ); + ConsolePrint( rgDeathNoticeList[i].szVictim ); + } + else + { + ConsolePrint( rgDeathNoticeList[i].szKiller ); + ConsolePrint( " killed " ); + ConsolePrint( rgDeathNoticeList[i].szVictim ); + } + + if ( killedwith && *killedwith && (*killedwith > 13 ) && strcmp( killedwith, "d_world" ) && !rgDeathNoticeList[i].iTeamKill ) + { + ConsolePrint( " with " ); + + // replace the code names with the 'real' names + if ( !strcmp( killedwith+2, "egon" ) ) + strcpy( killedwith, "d_gluon gun" ); + if ( !strcmp( killedwith+2, "gauss" ) ) + strcpy( killedwith, "d_tau cannon" ); + + ConsolePrint( killedwith+2 ); // skip over the "d_" part + } + + ConsolePrint( "\n" ); + + return 1; +} + + + diff --git a/dmc/cl_dll/demo.cpp b/dmc/cl_dll/demo.cpp new file mode 100644 index 0000000..a7cb395 --- /dev/null +++ b/dmc/cl_dll/demo.cpp @@ -0,0 +1,61 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "hud.h" +#include "cl_util.h" +#include "demo.h" +#include "demo_api.h" +#include + +extern "C" +{ + void EXPORT Demo_ReadBuffer( int size, unsigned char *buffer ); +} + +/* +===================== +Demo_WriteBuffer + +Write some data to the demo stream +===================== +*/ +void Demo_WriteBuffer( int type, int size, unsigned char *buffer ) +{ + int pos = 0; + unsigned char buf[ 32 * 1024 ]; + *( int * )&buf[pos] = type; + pos+=sizeof( int ); + + memcpy( &buf[pos], buffer, size ); + + // Write full buffer out + gEngfuncs.pDemoAPI->WriteBuffer( size + sizeof( int ), buf ); +} + +/* +===================== +Demo_ReadBuffer + +Engine wants us to parse some data from the demo stream +===================== +*/ +void EXPORT Demo_ReadBuffer( int size, unsigned char *buffer ) +{ + int type; + int i = 0; + + type = *( int * )buffer; + i += sizeof( int ); + switch ( type ) + { + case TYPE_USER: + break; + default: + gEngfuncs.Con_DPrintf( "Unknown demo buffer type, skipping.\n" ); + break; + } +} \ No newline at end of file diff --git a/dmc/cl_dll/demo.h b/dmc/cl_dll/demo.h new file mode 100644 index 0000000..a220327 --- /dev/null +++ b/dmc/cl_dll/demo.h @@ -0,0 +1,20 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( DEMOH ) +#define DEMOH +#pragma once + +// Types of demo messages we can write/parse +enum +{ + TYPE_USER = 0, +}; + +void Demo_WriteBuffer( int type, int size, unsigned char *buffer ); + +#endif \ No newline at end of file diff --git a/dmc/cl_dll/entity.cpp b/dmc/cl_dll/entity.cpp new file mode 100644 index 0000000..9c1a17a --- /dev/null +++ b/dmc/cl_dll/entity.cpp @@ -0,0 +1,860 @@ +/**** +* +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Client side entity management functions +#include + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_types.h" +#include "studio_event.h" // def. of mstudioevent_t +#include "r_efx.h" +#include "usercmd.h" +#include "event_api.h" +#include "pm_defs.h" +#include "pm_shared.h" +#include "pmtrace.h" +#include "voice_status.h" + +void Game_AddObjects( void ); + + +extern vec3_t v_origin; + +extern "C" +{ + int EXPORT HUD_AddEntity( int type, struct cl_entity_s *ent, const char *modelname ); + void EXPORT HUD_CreateEntities( void ); + void EXPORT HUD_StudioEvent( const struct mstudioevent_s *event, const struct cl_entity_s *entity ); + void EXPORT HUD_TxferLocalOverrides( struct entity_state_s *state, const struct clientdata_s *client ); + void EXPORT HUD_ProcessPlayerState( struct entity_state_s *dst, const struct entity_state_s *src ); + void EXPORT HUD_TxferPredictionData ( struct entity_state_s *ps, const struct entity_state_s *pps, struct clientdata_s *pcd, const struct clientdata_s *ppcd, struct weapon_data_s *wd, const struct weapon_data_s *pwd ); + void EXPORT HUD_TempEntUpdate( double frametime, double client_time, double cl_gravity, struct tempent_s **ppTempEntFree, struct tempent_s **ppTempEntActive, int ( *Callback_AddVisibleEntity )( struct cl_entity_s *pEntity ), void ( *Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp ) ); + struct cl_entity_s EXPORT *HUD_GetUserEntity( int index ); +} + +/* +======================== +HUD_AddEntity + Return 0 to filter entity from visible list for rendering +======================== +*/ +int EXPORT HUD_AddEntity( int type, struct cl_entity_s *ent, const char *modelname ) +{ + switch ( type ) + { + case ET_NORMAL: + case ET_PLAYER: + case ET_BEAM: + case ET_TEMPENTITY: + case ET_FRAGMENTED: + default: + break; + } + + // each frame every entity passes this function, so the overview hooks it to filter the overview entities + // in spectator mode: + // each frame every entity passes this function, so the overview hooks + // it to filter the overview entities + + if ( g_iUser1 ) + { + gHUD.m_Spectator.AddOverviewEntity( type, ent, modelname ); + + if ( ( g_iUser1 == OBS_IN_EYE || gHUD.m_Spectator.m_pip->value == INSET_IN_EYE ) && + ent->index == g_iUser2 ) + return 0; // don't draw the player we are following in eye + + } + return 1; +} + +/* +========================= +HUD_TxferLocalOverrides + +The server sends us our origin with extra precision as part of the clientdata structure, not during the normal +playerstate update in entity_state_t. In order for these overrides to eventually get to the appropriate playerstate +structure, we need to copy them into the state structure at this point. +========================= +*/ +void EXPORT HUD_TxferLocalOverrides( struct entity_state_s *state, const struct clientdata_s *client ) +{ + VectorCopy( client->origin, state->origin ); + + // Observer + state->iuser1 = client->iuser1; + state->iuser2 = client->iuser2; +} + +/* +========================= +HUD_ProcessPlayerState + +We have received entity_state_t for this player over the network. We need to copy appropriate fields to the +playerstate structure +========================= +*/ +void EXPORT HUD_ProcessPlayerState( struct entity_state_s *dst, const struct entity_state_s *src ) +{ + // Copy in network data + VectorCopy( src->origin, dst->origin ); + VectorCopy( src->angles, dst->angles ); + + VectorCopy( src->velocity, dst->velocity ); + + dst->frame = src->frame; + dst->modelindex = src->modelindex; + dst->skin = src->skin; + dst->effects = src->effects; + dst->weaponmodel = src->weaponmodel; + dst->movetype = src->movetype; + dst->sequence = src->sequence; + dst->animtime = src->animtime; + + dst->solid = src->solid; + + dst->rendermode = src->rendermode; + dst->renderamt = src->renderamt; + dst->rendercolor.r = src->rendercolor.r; + dst->rendercolor.g = src->rendercolor.g; + dst->rendercolor.b = src->rendercolor.b; + dst->renderfx = src->renderfx; + + dst->framerate = src->framerate; + dst->body = src->body; + + memcpy( &dst->controller[0], &src->controller[0], 4 * sizeof( byte ) ); + memcpy( &dst->blending[0], &src->blending[0], 2 * sizeof( byte ) ); + + VectorCopy( src->basevelocity, dst->basevelocity ); + + dst->friction = src->friction; + dst->gravity = src->gravity; + dst->gaitsequence = src->gaitsequence; + dst->spectator = src->spectator; + dst->usehull = src->usehull; + dst->playerclass = src->playerclass; + dst->team = src->team; + dst->colormap = src->colormap; + // Save off some data so other areas of the Client DLL can get to it + cl_entity_t *player = gEngfuncs.GetLocalPlayer(); // Get the local player's index + if ( dst->number == player->index ) + { + g_iUser1 = src->iuser1; + g_iUser2 = src->iuser2; + g_iUser3 = src->iuser3; + } +} + +/* +========================= +HUD_TxferPredictionData + +Because we can predict an arbitrary number of frames before the server responds with an update, we need to be able to copy client side prediction data in + from the state that the server ack'd receiving, which can be anywhere along the predicted frame path ( i.e., we could predict 20 frames into the future and the server ack's + up through 10 of those frames, so we need to copy persistent client-side only state from the 10th predicted frame to the slot the server + update is occupying. +========================= +*/ +void EXPORT HUD_TxferPredictionData ( struct entity_state_s *ps, const struct entity_state_s *pps, struct clientdata_s *pcd, const struct clientdata_s *ppcd, struct weapon_data_s *wd, const struct weapon_data_s *pwd ) +{ + ps->oldbuttons = pps->oldbuttons; + ps->flFallVelocity = pps->flFallVelocity; + ps->iStepLeft = pps->iStepLeft; + ps->playerclass = pps->playerclass; + + pcd->viewmodel = ppcd->viewmodel; + pcd->m_iId = ppcd->m_iId; + pcd->ammo_shells = ppcd->ammo_shells; + pcd->ammo_nails = ppcd->ammo_nails; + pcd->ammo_cells = ppcd->ammo_cells; + pcd->ammo_rockets = ppcd->ammo_rockets; + pcd->m_flNextAttack = ppcd->m_flNextAttack; + pcd->fov = ppcd->fov; + pcd->weaponanim = ppcd->weaponanim; + pcd->tfstate = ppcd->tfstate; + pcd->maxspeed = ppcd->maxspeed; + + pcd->deadflag = ppcd->deadflag; + + // Observer + pcd->iuser1 = ppcd->iuser1; + pcd->iuser2 = ppcd->iuser2; + + if ( gEngfuncs.IsSpectateOnly() ) + { + // in specator mode we tell the engine who we want to spectate and how + // iuser3 is not used for duck prevention (since the spectator can't duck at all) + pcd->iuser1 = g_iUser1; // observer mode + pcd->iuser2 = g_iUser2; // first target + pcd->iuser3 = g_iUser3; // second target + } + // m_iQuakeItems + pcd->iuser3 = ppcd->iuser3; + // m_iQuakeWeapon # + pcd->fuser1 = ppcd->fuser1; + // m_iNailIndex + pcd->fuser2 = ppcd->fuser2; + // m_iRuneStatus + pcd->fuser3 = ppcd->fuser3; + + memcpy( wd, pwd, 32 * sizeof( weapon_data_t ) ); +} + +/* +//#define TEST_IT +#if defined( TEST_IT ) + +cl_entity_t mymodel[9]; + +void MoveModel( void ) +{ + cl_entity_t *player; + int i, j; + int modelindex; + struct model_s *mod; + + // Load it up with some bogus data + player = gEngfuncs.GetLocalPlayer(); + if ( !player ) + return; + + mod = gEngfuncs.CL_LoadModel( "models/sentry3.mdl", &modelindex ); + for ( i = 0; i < 3; i++ ) + { + for ( j = 0; j < 3; j++ ) + { + // Don't draw over ourself... + if ( ( i == 1 ) && ( j == 1 ) ) + continue; + + mymodel[ i * 3 + j ] = *player; + + mymodel[ i * 3 + j ].player = 0; + + mymodel[ i * 3 + j ].model = mod; + mymodel[ i * 3 + j ].curstate.modelindex = modelindex; + + // Move it out a bit + mymodel[ i * 3 + j ].origin[0] = player->origin[0] + 50 * ( 1 - i ); + mymodel[ i * 3 + j ].origin[1] = player->origin[1] + 50 * ( 1 - j ); + + gEngfuncs.CL_CreateVisibleEntity( ET_NORMAL, &mymodel[i*3+j] ); + } + } + +} + +#endif + +//#define TRACE_TEST +#if defined( TRACE_TEST ) + +extern int hitent; + +cl_entity_t hit; + +void TraceModel( void ) +{ + cl_entity_t *ent; + + if ( hitent <= 0 ) + return; + + // Load it up with some bogus data + ent = gEngfuncs.GetEntityByIndex( hitent ); + if ( !ent ) + return; + + hit = *ent; + //hit.curstate.rendermode = kRenderTransTexture; + //hit.curstate.renderfx = kRenderFxGlowShell; + //hit.curstate.renderamt = 100; + + hit.origin[2] += 40; + + gEngfuncs.CL_CreateVisibleEntity( ET_NORMAL, &hit ); +} + +#endif +*/ + +/* +void ParticleCallback( struct particle_s *particle, float frametime ) +{ + int i; + + for ( i = 0; i < 3; i++ ) + { + particle->org[ i ] += particle->vel[ i ] * frametime; + } +} + +void Particles( void ) +{ + static float lasttime; + float curtime; + + curtime = gEngfuncs.GetClientTime(); + + if ( ( curtime - lasttime ) < 10.0 ) + return; + + lasttime = curtime; + + // Create a few particles + particle_t *p; + int i, j; + + for ( i = 0; i < 100; i++ ) + { + p = gEngfuncs.pEfxAPI->R_AllocParticle( ParticleCallback ); + if ( !p ) + break; + + for ( j = 0; j < 3; j++ ) + { + p->org[ j ] = v_origin[ j ]; + p->vel[ j ] = gEngfuncs.pfnRandomFloat( -100.0, 100.0 ); + } + + p->color = gEngfuncs.pfnRandomLong( 0, 255 ); + gEngfuncs.pEfxAPI->R_GetPackedColor( &p->packedColor, p->color ); + + // p->die is set to current time so all you have to do is add an additional time to it + p->die += 5.0; + } +} +*/ + +/* +void TempEntCallback ( struct tempent_s *ent, float frametime, float currenttime ) +{ + int i; + + for ( i = 0; i < 3; i++ ) + { + ent->entity.curstate.origin[ i ] += ent->entity.baseline.origin[ i ] * frametime; + } +} + +void TempEnts( void ) +{ + static float lasttime; + float curtime; + + curtime = gEngfuncs.GetClientTime(); + + if ( ( curtime - lasttime ) < 10.0 ) + return; + + lasttime = curtime; + + TEMPENTITY *p; + int i, j; + struct model_s *mod; + vec3_t origin; + int index; + + mod = gEngfuncs.CL_LoadModel( "sprites/laserdot.spr", &index ); + + for ( i = 0; i < 100; i++ ) + { + for ( j = 0; j < 3; j++ ) + { + origin[ j ] = v_origin[ j ]; + if ( j != 2 ) + { + origin[ j ] += 75; + } + } + + p = gEngfuncs.pEfxAPI->CL_TentEntAllocCustom( (float *)&origin, mod, 0, TempEntCallback ); + if ( !p ) + break; + + for ( j = 0; j < 3; j++ ) + { + p->entity.curstate.origin[ j ] = origin[ j ]; + + // Store velocity in baseline origin + p->entity.baseline.origin[ j ] = gEngfuncs.pfnRandomFloat( -100, 100 ); + } + + // p->die is set to current time so all you have to do is add an additional time to it + p->die += 10.0; + } +} +*/ + +/* +========================= +HUD_CreateEntities + +Gives us a chance to add additional entities to the render this frame +========================= +*/ +void EXPORT HUD_CreateEntities( void ) +{ + // e.g., create a persistent cl_entity_t somewhere. + // Load an appropriate model into it ( gEngfuncs.CL_LoadModel ) + // Call gEngfuncs.CL_CreateVisibleEntity to add it to the visedicts list +/* +#if defined( TEST_IT ) + MoveModel(); +#endif + +#if defined( TRACE_TEST ) + TraceModel(); +#endif +*/ +/* + Particles(); +*/ +/* + TempEnts(); +*/ + // Add in any game specific objects + Game_AddObjects(); + + GetClientVoiceMgr()->CreateEntities(); +} + +/* +========================= +HUD_StudioEvent + +The entity's studio model description indicated an event was +fired during this frame, handle the event by it's tag ( e.g., muzzleflash, sound ) +========================= +*/ +void EXPORT HUD_StudioEvent( const struct mstudioevent_s *event, const struct cl_entity_s *entity ) +{ + switch( event->event ) + { + case 5001: + gEngfuncs.pEfxAPI->R_MuzzleFlash( (float *)&entity->attachment[0], atoi( event->options) ); + break; + case 5011: + gEngfuncs.pEfxAPI->R_MuzzleFlash( (float *)&entity->attachment[1], atoi( event->options) ); + break; + case 5021: + gEngfuncs.pEfxAPI->R_MuzzleFlash( (float *)&entity->attachment[2], atoi( event->options) ); + break; + case 5031: + gEngfuncs.pEfxAPI->R_MuzzleFlash( (float *)&entity->attachment[3], atoi( event->options) ); + break; + case 5002: + gEngfuncs.pEfxAPI->R_SparkEffect( (float *)&entity->attachment[0], atoi( event->options), -100, 100 ); + break; + // Client side sound + case 5004: + gEngfuncs.pfnPlaySoundByNameAtLocation( (char *)event->options, 1.0, (float *)&entity->attachment[0] ); + break; + default: + break; + } +} + +/* +================= +CL_UpdateTEnts + +Simulation and cleanup of temporary entities +================= +*/ +void EXPORT HUD_TempEntUpdate ( + double frametime, // Simulation time + double client_time, // Absolute time on client + double cl_gravity, // True gravity on client + TEMPENTITY **ppTempEntFree, // List of freed temporary ents + TEMPENTITY **ppTempEntActive, // List + int ( *Callback_AddVisibleEntity )( cl_entity_t *pEntity ), + void ( *Callback_TempEntPlaySound )( TEMPENTITY *pTemp, float damp ) ) +{ + static int gTempEntFrame = 0; + int i; + TEMPENTITY *pTemp, *pnext, *pprev; + float freq, gravity, gravitySlow, life, fastFreq; + + // Nothing to simulate + if ( !*ppTempEntActive ) + return; + + // in order to have tents collide with players, we have to run the player prediction code so + // that the client has the player list. We run this code once when we detect any COLLIDEALL + // tent, then set this BOOL to true so the code doesn't get run again if there's more than + // one COLLIDEALL ent for this update. (often are). + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( -1 ); + + // !!!BUGBUG -- This needs to be time based + gTempEntFrame = (gTempEntFrame+1) & 31; + + pTemp = *ppTempEntActive; + + // !!! Don't simulate while paused.... This is sort of a hack, revisit. + if ( frametime <= 0 ) + { + while ( pTemp ) + { + if ( !(pTemp->flags & FTENT_NOMODEL ) ) + { + Callback_AddVisibleEntity( &pTemp->entity ); + } + pTemp = pTemp->next; + } + goto finish; + } + + pprev = NULL; + freq = client_time * 0.01; + fastFreq = client_time * 5.5; + gravity = -frametime * cl_gravity; + gravitySlow = gravity * 0.5; + + while ( pTemp ) + { + int active; + + active = 1; + + life = pTemp->die - client_time; + pnext = pTemp->next; + if ( life < 0 ) + { + if ( pTemp->flags & FTENT_FADEOUT ) + { + if (pTemp->entity.curstate.rendermode == kRenderNormal) + pTemp->entity.curstate.rendermode = kRenderTransTexture; + pTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt * ( 1 + life * pTemp->fadeSpeed ); + if ( pTemp->entity.curstate.renderamt <= 0 ) + active = 0; + + } + else + active = 0; + } + if ( !active ) // Kill it + { + pTemp->next = *ppTempEntFree; + *ppTempEntFree = pTemp; + if ( !pprev ) // Deleting at head of list + *ppTempEntActive = pnext; + else + pprev->next = pnext; + } + else + { + pprev = pTemp; + + VectorCopy( pTemp->entity.origin, pTemp->entity.prevstate.origin ); + + if ( pTemp->flags & FTENT_SPARKSHOWER ) + { + // Adjust speed if it's time + // Scale is next think time + if ( client_time > pTemp->entity.baseline.scale ) + { + // Show Sparks + gEngfuncs.pEfxAPI->R_SparkEffect( pTemp->entity.origin, 8, -200, 200 ); + + // Reduce life + pTemp->entity.baseline.framerate -= 0.1; + + if ( pTemp->entity.baseline.framerate <= 0.0 ) + { + pTemp->die = client_time; + } + else + { + // So it will die no matter what + pTemp->die = client_time + 0.5; + + // Next think + pTemp->entity.baseline.scale = client_time + 0.1; + } + } + } + else if ( pTemp->flags & FTENT_PLYRATTACHMENT ) + { + cl_entity_t *pClient; + + pClient = gEngfuncs.GetEntityByIndex( pTemp->clientIndex ); + + VectorAdd( pClient->origin, pTemp->tentOffset, pTemp->entity.origin ); + } + else if ( pTemp->flags & FTENT_SINEWAVE ) + { + pTemp->x += pTemp->entity.baseline.origin[0] * frametime; + pTemp->y += pTemp->entity.baseline.origin[1] * frametime; + + pTemp->entity.origin[0] = pTemp->x + sin( pTemp->entity.baseline.origin[2] + client_time * pTemp->entity.prevstate.frame ) * (10*pTemp->entity.curstate.framerate); + pTemp->entity.origin[1] = pTemp->y + sin( pTemp->entity.baseline.origin[2] + fastFreq + 0.7 ) * (8*pTemp->entity.curstate.framerate); + pTemp->entity.origin[2] += pTemp->entity.baseline.origin[2] * frametime; + } + else if ( pTemp->flags & FTENT_SPIRAL ) + { + float s, c; + s = sin( pTemp->entity.baseline.origin[2] + fastFreq ); + c = cos( pTemp->entity.baseline.origin[2] + fastFreq ); + + pTemp->entity.origin[0] += pTemp->entity.baseline.origin[0] * frametime + 8 * sin( client_time * 20 + (int)pTemp ); + pTemp->entity.origin[1] += pTemp->entity.baseline.origin[1] * frametime + 4 * sin( client_time * 30 + (int)pTemp ); + pTemp->entity.origin[2] += pTemp->entity.baseline.origin[2] * frametime; + } + else + { + for ( i = 0; i < 3; i++ ) + pTemp->entity.origin[i] += pTemp->entity.baseline.origin[i] * frametime; + } + + if ( pTemp->flags & FTENT_SPRANIMATE ) + { + pTemp->entity.curstate.frame += frametime * pTemp->entity.curstate.framerate; + if ( pTemp->entity.curstate.frame >= pTemp->frameMax ) + { + pTemp->entity.curstate.frame = pTemp->entity.curstate.frame - (int)(pTemp->entity.curstate.frame); + + if ( !(pTemp->flags & FTENT_SPRANIMATELOOP) ) + { + // this animating sprite isn't set to loop, so destroy it. + pTemp->die = client_time; + pTemp = pnext; + continue; + } + } + } + else if ( pTemp->flags & FTENT_SPRCYCLE ) + { + pTemp->entity.curstate.frame += frametime * 10; + if ( pTemp->entity.curstate.frame >= pTemp->frameMax ) + { + pTemp->entity.curstate.frame = pTemp->entity.curstate.frame - (int)(pTemp->entity.curstate.frame); + } + } +// Experiment +#if 0 + if ( pTemp->flags & FTENT_SCALE ) + pTemp->entity.curstate.framerate += 20.0 * (frametime / pTemp->entity.curstate.framerate); +#endif + + if ( pTemp->flags & FTENT_ROTATE ) + { + pTemp->entity.angles[0] += pTemp->entity.baseline.angles[0] * frametime; + pTemp->entity.angles[1] += pTemp->entity.baseline.angles[1] * frametime; + pTemp->entity.angles[2] += pTemp->entity.baseline.angles[2] * frametime; + + VectorCopy( pTemp->entity.angles, pTemp->entity.latched.prevangles ); + } + + if ( pTemp->flags & (FTENT_COLLIDEALL | FTENT_COLLIDEWORLD) ) + { + vec3_t traceNormal; + float traceFraction = 1; + + if ( pTemp->flags & FTENT_COLLIDEALL ) + { + pmtrace_t pmtrace; + physent_t *pe; + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + + gEngfuncs.pEventAPI->EV_PlayerTrace( pTemp->entity.prevstate.origin, pTemp->entity.origin, PM_STUDIO_BOX, -1, &pmtrace ); + + + if ( pmtrace.fraction != 1 ) + { + pe = gEngfuncs.pEventAPI->EV_GetPhysent( pmtrace.ent ); + + if ( !pmtrace.ent || ( pe->info != pTemp->clientIndex ) ) + { + traceFraction = pmtrace.fraction; + VectorCopy( pmtrace.plane.normal, traceNormal ); + + if ( pTemp->hitcallback ) + { + (*pTemp->hitcallback)( pTemp, &pmtrace ); + } + } + } + } + else if ( pTemp->flags & FTENT_COLLIDEWORLD ) + { + pmtrace_t pmtrace; + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + + gEngfuncs.pEventAPI->EV_PlayerTrace( pTemp->entity.prevstate.origin, pTemp->entity.origin, PM_STUDIO_BOX | PM_WORLD_ONLY, -1, &pmtrace ); + + if ( pmtrace.fraction != 1 ) + { + traceFraction = pmtrace.fraction; + VectorCopy( pmtrace.plane.normal, traceNormal ); + + if ( pTemp->flags & FTENT_SPARKSHOWER ) + { + // Chop spark speeds a bit more + // + VectorScale( pTemp->entity.baseline.origin, 0.6, pTemp->entity.baseline.origin ); + + if ( Length( pTemp->entity.baseline.origin ) < 10 ) + { + pTemp->entity.baseline.framerate = 0.0; + } + } + + if ( pTemp->hitcallback ) + { + (*pTemp->hitcallback)( pTemp, &pmtrace ); + } + } + } + + if ( traceFraction != 1 ) // Decent collision now, and damping works + { + float proj, damp; + + // Place at contact point + VectorMA( pTemp->entity.prevstate.origin, traceFraction*frametime, pTemp->entity.baseline.origin, pTemp->entity.origin ); + // Damp velocity + damp = pTemp->bounceFactor; + if ( pTemp->flags & (FTENT_GRAVITY|FTENT_SLOWGRAVITY) ) + { + damp *= 0.5; + if ( traceNormal[2] > 0.9 ) // Hit floor? + { + if ( pTemp->entity.baseline.origin[2] <= 0 && pTemp->entity.baseline.origin[2] >= gravity*3 ) + { + damp = 0; // Stop + pTemp->flags &= ~(FTENT_ROTATE|FTENT_GRAVITY|FTENT_SLOWGRAVITY|FTENT_COLLIDEWORLD|FTENT_SMOKETRAIL); + pTemp->entity.angles[0] = 0; + pTemp->entity.angles[2] = 0; + } + } + } + + if (pTemp->hitSound) + { + Callback_TempEntPlaySound(pTemp, damp); + } + + if (pTemp->flags & FTENT_COLLIDEKILL) + { + // die on impact + pTemp->flags &= ~FTENT_FADEOUT; + pTemp->die = client_time; + } + else + { + // Reflect velocity + if ( damp != 0 ) + { + proj = DotProduct( pTemp->entity.baseline.origin, traceNormal ); + VectorMA( pTemp->entity.baseline.origin, -proj*2, traceNormal, pTemp->entity.baseline.origin ); + // Reflect rotation (fake) + + pTemp->entity.angles[1] = -pTemp->entity.angles[1]; + } + + if ( damp != 1 ) + { + + VectorScale( pTemp->entity.baseline.origin, damp, pTemp->entity.baseline.origin ); + VectorScale( pTemp->entity.angles, 0.9, pTemp->entity.angles ); + } + } + } + } + + + if ( (pTemp->flags & FTENT_FLICKER) && gTempEntFrame == pTemp->entity.curstate.effects ) + { + dlight_t *dl = gEngfuncs.pEfxAPI->CL_AllocDlight (0); + VectorCopy (pTemp->entity.origin, dl->origin); + dl->radius = 60; + dl->color.r = 255; + dl->color.g = 120; + dl->color.b = 0; + dl->die = client_time + 0.01; + } + + if ( pTemp->flags & FTENT_SMOKETRAIL ) + { + if ( pTemp->entity.baseline.sequence == 69 ) // Little smoke + gEngfuncs.pEfxAPI->R_RocketTrail ( pTemp->entity.prevstate.origin, pTemp->entity.origin, 1 ); + else if ( pTemp->entity.baseline.sequence == 70 ) // Rocket Powered smoke ( heh? ) + gEngfuncs.pEfxAPI->R_RocketTrail ( pTemp->entity.prevstate.origin, pTemp->entity.origin, 0 ); + else + gEngfuncs.pEfxAPI->R_RocketTrail ( pTemp->entity.prevstate.origin, pTemp->entity.origin, 2 ); + } + + + if ( pTemp->flags & FTENT_GRAVITY ) + pTemp->entity.baseline.origin[2] += gravity; + else if ( pTemp->flags & FTENT_SLOWGRAVITY ) + pTemp->entity.baseline.origin[2] += gravitySlow; + + if ( pTemp->flags & FTENT_CLIENTCUSTOM ) + { + if ( pTemp->callback ) + { + ( *pTemp->callback )( pTemp, frametime, client_time ); + } + } + + // Cull to PVS (not frustum cull, just PVS) + if ( !(pTemp->flags & FTENT_NOMODEL ) ) + { + if ( !Callback_AddVisibleEntity( &pTemp->entity ) ) + { + if ( !(pTemp->flags & FTENT_PERSIST) ) + { + pTemp->die = client_time; // If we can't draw it this frame, just dump it. + pTemp->flags &= ~FTENT_FADEOUT; // Don't fade out, just die + } + } + } + } + pTemp = pnext; + } + +finish: + // Restore state info + gEngfuncs.pEventAPI->EV_PopPMStates(); +} + +/* +================= +HUD_GetUserEntity + +If you specify negative numbers for beam start and end point entities, then + the engine will call back into this function requesting a pointer to a cl_entity_t + object that describes the entity to attach the beam onto. + +Indices must start at 1, not zero. +================= +*/ +cl_entity_t EXPORT *HUD_GetUserEntity( int index ) +{ +return NULL; +} diff --git a/dmc/cl_dll/ev_common.cpp b/dmc/cl_dll/ev_common.cpp new file mode 100644 index 0000000..9138409 --- /dev/null +++ b/dmc/cl_dll/ev_common.cpp @@ -0,0 +1,198 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// shared event functions +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" + +#include "r_efx.h" + +#include "eventscripts.h" +#include "event_api.h" + +/* +================= +GetEntity + +Return's the requested cl_entity_t +================= +*/ +struct cl_entity_s *GetEntity( int idx ) +{ + return gEngfuncs.GetEntityByIndex( idx ); +} + +/* +================= +GetViewEntity + +Return's the current weapon/view model +================= +*/ +struct cl_entity_s *GetViewEntity( void ) +{ + return gEngfuncs.GetViewModel(); +} + +/* +================= +EV_CreateTracer + +Creates a tracer effect +================= +*/ +void EV_CreateTracer( float *start, float *end ) +{ + gEngfuncs.pEfxAPI->R_TracerEffect( start, end ); +} + +/* +================= +EV_IsPlayer + +Is the entity's index in the player range? +================= +*/ +qboolean EV_IsPlayer( int idx ) +{ + if ( idx >= 1 && idx <= gEngfuncs.GetMaxClients() ) + return true; + + return false; +} + +/* +================= +EV_IsLocal + +Is the entity == the local player +================= +*/ +qboolean EV_IsLocal( int idx ) +{ + return gEngfuncs.pEventAPI->EV_IsLocal( idx - 1 ) ? true : false; +} + +/* +================= +EV_GetGunPosition + +Figure out the height of the gun +================= +*/ +void EV_GetGunPosition( event_args_t *args, float *pos, float *origin ) +{ + int idx; + vec3_t view_ofs; + + idx = args->entindex; + + VectorClear( view_ofs ); + view_ofs[2] = DEFAULT_VIEWHEIGHT; + + if ( EV_IsPlayer( idx ) ) + { + if ( EV_IsLocal( idx ) ) + { + // Grab predicted result for local player + gEngfuncs.pEventAPI->EV_LocalPlayerViewheight( view_ofs ); + } + else if ( args->ducking == 1 ) + { + view_ofs[2] = VEC_DUCK_VIEW; + } + } + + VectorAdd( origin, view_ofs, pos ); +} + +/* +================= +EV_EjectBrass + +Bullet shell casings +================= +*/ +void EV_EjectBrass( float *origin, float *velocity, float rotation, int model, int soundtype ) +{ + vec3_t endpos; + VectorClear( endpos ); + endpos[1] = rotation; + gEngfuncs.pEfxAPI->R_TempModel( origin, velocity, endpos, 2.5, model, soundtype ); +} + +/* +================= +EV_GetDefaultShellInfo + +Determine where to eject shells from +================= +*/ +void EV_GetDefaultShellInfo( event_args_t *args, float *origin, float *velocity, float *ShellVelocity, float *ShellOrigin, float *forward, float *right, float *up, float forwardScale, float upScale, float rightScale ) +{ + int i; + vec3_t view_ofs; + float fR, fU; + + int idx; + + idx = args->entindex; + + VectorClear( view_ofs ); + view_ofs[2] = DEFAULT_VIEWHEIGHT; + + if ( EV_IsPlayer( idx ) ) + { + if ( EV_IsLocal( idx ) ) + { + gEngfuncs.pEventAPI->EV_LocalPlayerViewheight( view_ofs ); + } + else if ( args->ducking == 1 ) + { + view_ofs[2] = VEC_DUCK_VIEW; + } + } + + fR = gEngfuncs.pfnRandomFloat( 50, 70 ); + fU = gEngfuncs.pfnRandomFloat( 100, 150 ); + + for ( i = 0; i < 3; i++ ) + { + ShellVelocity[i] = velocity[i] + right[i] * fR + up[i] * fU + forward[i] * 25; + ShellOrigin[i] = origin[i] + view_ofs[i] + up[i] * upScale + forward[i] * forwardScale + right[i] * rightScale; + } +} + +/* +================= +EV_MuzzleFlash + +Flag weapon/view model for muzzle flash +================= +*/ +void EV_MuzzleFlash( void ) +{ + // Add muzzle flash to current weapon model + cl_entity_t *ent = GetViewEntity(); + if ( !ent ) + { + return; + } + + // Or in the muzzle flash + ent->curstate.effects |= EF_MUZZLEFLASH; +} \ No newline at end of file diff --git a/dmc/cl_dll/ev_hldm.cpp b/dmc/cl_dll/ev_hldm.cpp new file mode 100644 index 0000000..2a9b2e4 --- /dev/null +++ b/dmc/cl_dll/ev_hldm.cpp @@ -0,0 +1,1544 @@ +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "entity_types.h" +#include "usercmd.h" +#include "pm_defs.h" +#include "pm_materials.h" + +#include "eventscripts.h" +#include "ev_hldm.h" + +#include "r_efx.h" +#include "event_api.h" +#include "event_args.h" +#include "in_defs.h" + +#include + +// QUAKECLASSIC +#define Q_SMALL_PUNCHANGLE_KICK -2 +#define Q_BIG_PUNCHANGLE_KICK -4 + +extern "C" char PM_FindTextureType( char *name ); + +void V_PunchAxis( int axis, float punch ); +extern vec3_t v_origin; + +extern "C" +{ + +// HLDM +void EV_FireShotGunSingle( struct event_args_s *args ); +void EV_FireShotGunDouble( struct event_args_s *args ); +void EV_FireAxe( struct event_args_s *args ); +void EV_FireAxeSwing( struct event_args_s *args ); +void EV_FireRocket( struct event_args_s *args ); +void EV_FireLightning( struct event_args_s *args ); +void EV_FireSpike( struct event_args_s *args ); +void EV_FireSuperSpike( struct event_args_s *args ); +void EV_FireGrenade( struct event_args_s *args ); +void EV_Gibbed( event_args_t *args ); +void EV_Teleport( event_args_t *args ); +void EV_Trail( event_args_t *args ); +void EV_Explosion( event_args_t *args ); + +void EV_PlayerPowerup( struct event_args_s *args ); + +void EV_DMC_DoorGoUp( struct event_args_s *args ); +void EV_DMC_DoorGoDown( struct event_args_s *args ); +void EV_DMC_DoorHitTop( struct event_args_s *args ); +void EV_DMC_DoorHitBottom( struct event_args_s *args ); + +#ifdef THREEWAVE + void EV_Hook( event_args_t *args ); + void EV_Cable( struct event_args_s *args ); + void EV_FollowCarrier( struct event_args_s *args ); + void EV_FlagSpawn( struct event_args_s *args ); +#endif + + +void EV_TrainPitchAdjust( struct event_args_s *args ); +} + +#define VECTOR_CONE_1DEGREES Vector( 0.00873, 0.00873, 0.00873 ) +#define VECTOR_CONE_2DEGREES Vector( 0.01745, 0.01745, 0.01745 ) +#define VECTOR_CONE_3DEGREES Vector( 0.02618, 0.02618, 0.02618 ) +#define VECTOR_CONE_4DEGREES Vector( 0.03490, 0.03490, 0.03490 ) +#define VECTOR_CONE_5DEGREES Vector( 0.04362, 0.04362, 0.04362 ) +#define VECTOR_CONE_6DEGREES Vector( 0.05234, 0.05234, 0.05234 ) +#define VECTOR_CONE_7DEGREES Vector( 0.06105, 0.06105, 0.06105 ) +#define VECTOR_CONE_8DEGREES Vector( 0.06976, 0.06976, 0.06976 ) +#define VECTOR_CONE_9DEGREES Vector( 0.07846, 0.07846, 0.07846 ) +#define VECTOR_CONE_10DEGREES Vector( 0.08716, 0.08716, 0.08716 ) +#define VECTOR_CONE_15DEGREES Vector( 0.13053, 0.13053, 0.13053 ) +#define VECTOR_CONE_20DEGREES Vector( 0.17365, 0.17365, 0.17365 ) + +// play a strike sound based on the texture that was hit by the attack traceline. VecSrc/VecEnd are the +// original traceline endpoints used by the attacker, iBulletType is the type of bullet that hit the texture. +// returns volume of strike instrument (crowbar) to play +float EV_HLDM_PlayTextureSound( int idx, pmtrace_t *ptr, float *vecSrc, float *vecEnd, int iBulletType ) +{ + // hit the world, try to play sound based on texture material type + char chTextureType = CHAR_TEX_CONCRETE; + float fvol; + float fvolbar; + char *rgsz[4]; + int cnt; + float fattn = ATTN_NORM; + int entity; + char *pTextureName; + char texname[ 64 ]; + char szbuffer[ 64 ]; + + entity = gEngfuncs.pEventAPI->EV_IndexFromTrace( ptr ); + + // FIXME check if playtexture sounds movevar is set + // + + chTextureType = 0; + + // Player + if ( entity >= 1 && entity <= gEngfuncs.GetMaxClients() ) + { + // hit body + chTextureType = CHAR_TEX_FLESH; + } + else if ( entity == 0 ) + { + // get texture from entity or world (world is ent(0)) + pTextureName = (char *)gEngfuncs.pEventAPI->EV_TraceTexture( ptr->ent, vecSrc, vecEnd ); + + if ( pTextureName ) + { + strcpy( texname, pTextureName ); + pTextureName = texname; + + // strip leading '-0' or '+0~' or '{' or '!' + if (*pTextureName == '-' || *pTextureName == '+') + { + pTextureName += 2; + } + + if (*pTextureName == '{' || *pTextureName == '!' || *pTextureName == '~' || *pTextureName == ' ') + { + pTextureName++; + } + + // '}}' + strcpy( szbuffer, pTextureName ); + szbuffer[ CBTEXTURENAMEMAX - 1 ] = 0; + + // get texture type + chTextureType = PM_FindTextureType( szbuffer ); + } + } + + switch (chTextureType) + { + default: + case CHAR_TEX_CONCRETE: fvol = 0.9; fvolbar = 0.6; + rgsz[0] = "player/pl_step1.wav"; + rgsz[1] = "player/pl_step2.wav"; + cnt = 2; + break; + case CHAR_TEX_METAL: fvol = 0.9; fvolbar = 0.3; + rgsz[0] = "player/pl_metal1.wav"; + rgsz[1] = "player/pl_metal2.wav"; + cnt = 2; + break; + case CHAR_TEX_DIRT: fvol = 0.9; fvolbar = 0.1; + rgsz[0] = "player/pl_dirt1.wav"; + rgsz[1] = "player/pl_dirt2.wav"; + rgsz[2] = "player/pl_dirt3.wav"; + cnt = 3; + break; + case CHAR_TEX_VENT: fvol = 0.5; fvolbar = 0.3; + rgsz[0] = "player/pl_duct1.wav"; + rgsz[1] = "player/pl_duct1.wav"; + cnt = 2; + break; + case CHAR_TEX_GRATE: fvol = 0.9; fvolbar = 0.5; + rgsz[0] = "player/pl_grate1.wav"; + rgsz[1] = "player/pl_grate4.wav"; + cnt = 2; + break; + case CHAR_TEX_TILE: fvol = 0.8; fvolbar = 0.2; + rgsz[0] = "player/pl_tile1.wav"; + rgsz[1] = "player/pl_tile3.wav"; + rgsz[2] = "player/pl_tile2.wav"; + rgsz[3] = "player/pl_tile4.wav"; + cnt = 4; + break; + case CHAR_TEX_SLOSH: fvol = 0.9; fvolbar = 0.0; + rgsz[0] = "player/pl_slosh1.wav"; + rgsz[1] = "player/pl_slosh3.wav"; + rgsz[2] = "player/pl_slosh2.wav"; + rgsz[3] = "player/pl_slosh4.wav"; + cnt = 4; + break; + case CHAR_TEX_WOOD: fvol = 0.9; fvolbar = 0.2; + rgsz[0] = "debris/wood1.wav"; + rgsz[1] = "debris/wood2.wav"; + rgsz[2] = "debris/wood3.wav"; + cnt = 3; + break; + case CHAR_TEX_GLASS: + case CHAR_TEX_COMPUTER: + fvol = 0.8; fvolbar = 0.2; + rgsz[0] = "debris/glass1.wav"; + rgsz[1] = "debris/glass2.wav"; + rgsz[2] = "debris/glass3.wav"; + cnt = 3; + break; + case CHAR_TEX_FLESH: + if (iBulletType == BULLET_PLAYER_CROWBAR) + return 0.0; // crowbar already makes this sound + fvol = 1.0; fvolbar = 0.2; + rgsz[0] = "weapons/bullet_hit1.wav"; + rgsz[1] = "weapons/bullet_hit2.wav"; + fattn = 1.0; + cnt = 2; + break; + } + + // play material hit sound + gEngfuncs.pEventAPI->EV_PlaySound( 0, ptr->endpos, CHAN_STATIC, rgsz[gEngfuncs.pfnRandomLong(0,cnt-1)], fvol, fattn, 0, 96 + gEngfuncs.pfnRandomLong(0,0xf) ); + return fvolbar; +} + +//CheckPVS see if playerIndex is in same PVS as localplayer +bool CheckPVS( int playerIndex ) +{ + //returns true if the player is in the same PVS + cl_entity_t *localPlayer = gEngfuncs.GetLocalPlayer(); + cl_entity_t *player; + + player = gEngfuncs.GetEntityByIndex( playerIndex ); + + if( !player || !localPlayer ) + return false; + + if ( player == localPlayer ) + return true; + + if( player->curstate.messagenum < localPlayer->curstate.messagenum ) + return false; + + return true; +} + + +char *EV_HLDM_RocketDamageDecal( void ) +{ + static char decalname[ 32 ]; + int idx; + + idx = gEngfuncs.pfnRandomLong( 1, 3 ); + sprintf( decalname, "{scorch%i", idx ); + return decalname; +} + +char *EV_HLDM_DamageDecal( void ) +{ + static char decalname[ 32 ]; + int idx; + + idx = gEngfuncs.pfnRandomLong( 0, 4 ); + sprintf( decalname, "{shot%i", idx + 1 ); + return decalname; +} + + +char *EV_Lightning_DamageDecal( void ) +{ + int idx; + static char decalname[ 32 ]; + //sprintf( decalname, "{smscorch1"); + + idx = gEngfuncs.pfnRandomLong( 0, 2 ); + sprintf( decalname, "{smscorch%i", idx + 1 ); + + return decalname; +} + +char *EV_Quake_DamageDecalClub( void ) +{ + static char decalname[ 32 ]; + int idx; + + idx = gEngfuncs.pfnRandomLong( 0, 4 ); + sprintf( decalname, "{shot%i", idx + 1 ); + return decalname; +} + +void EV_Quake_GunshotDecalTrace( pmtrace_t *pTrace, char *decalName ) +{ + int iRand; + physent_t *pe; + + gEngfuncs.pEfxAPI->R_BulletImpactParticles( pTrace->endpos ); + + iRand = gEngfuncs.pfnRandomLong(0,0x7FFF); + if ( iRand < (0x7fff/2) )// not every bullet makes a sound. + { + switch( iRand % 5) + { + case 0: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric1.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric2.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 2: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric3.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric4.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 4: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric5.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + } + } + + pe = gEngfuncs.pEventAPI->EV_GetPhysent( pTrace->ent ); + + // Only decal brush models such as the world etc. + if ( decalName && decalName[0] && pe && ( pe->solid == SOLID_BSP || pe->movetype == MOVETYPE_PUSHSTEP ) ) + { + if ( CVAR_GET_FLOAT( "r_decals" ) ) + { + gEngfuncs.pEfxAPI->R_DecalShoot( + gEngfuncs.pEfxAPI->Draw_DecalIndex( gEngfuncs.pEfxAPI->Draw_DecalIndexFromName( decalName ) ), + gEngfuncs.pEventAPI->EV_IndexFromTrace( pTrace ), 0, pTrace->endpos, 0 ); + } + } +} + + +void EV_Quake_DecalTrace( pmtrace_t *pTrace, char *decalName ) +{ + physent_t *pe; + + pe = gEngfuncs.pEventAPI->EV_GetPhysent( pTrace->ent ); + + // Only decal brush models such as the world etc. + if ( decalName && decalName[0] && pe && ( pe->solid == SOLID_BSP || pe->movetype == MOVETYPE_PUSHSTEP ) ) + { + if ( CVAR_GET_FLOAT( "r_decals" ) ) + { + gEngfuncs.pEfxAPI->R_DecalShoot( + gEngfuncs.pEfxAPI->Draw_DecalIndex( gEngfuncs.pEfxAPI->Draw_DecalIndexFromName( decalName ) ), + gEngfuncs.pEventAPI->EV_IndexFromTrace( pTrace ), 0, pTrace->endpos, 0 ); + } + } +} + +void EV_HLDM_DecalGunshot( pmtrace_t *pTrace, int iBulletType ) +{ + physent_t *pe; + + pe = gEngfuncs.pEventAPI->EV_GetPhysent( pTrace->ent ); + + if ( pe && pe->solid == SOLID_BSP ) + { + switch( iBulletType ) + { + case BULLET_PLAYER_CROWBAR: + EV_Quake_DecalTrace( pTrace, EV_Quake_DamageDecalClub() ); + break; + + case BULLET_PLAYER_9MM: + case BULLET_MONSTER_9MM: + case BULLET_PLAYER_MP5: + case BULLET_MONSTER_MP5: + case BULLET_PLAYER_BUCKSHOT: + case BULLET_PLAYER_357: + default: + // smoke and decal + EV_Quake_GunshotDecalTrace( pTrace, EV_HLDM_DamageDecal() ); + break; + } + } +} + +void EV_Quake_PlayQuadSound ( int idx, float *origin, int iFlag ) +{ + if ( iFlag == 3 ) + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_ITEM, "rune/rune22.wav", 1, ATTN_NORM, 0, PITCH_NORM); + else if ( iFlag == 2 ) + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_ITEM, "rune/rune2.wav", 1, ATTN_NORM, 0, PITCH_NORM); + else if ( iFlag == 4 ) + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_ITEM, "rune/rune3.wav", 1, ATTN_NORM, 0, PITCH_NORM); + else if ( iFlag == 1 ) + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_ITEM, "items/damage3.wav", 1, ATTN_NORM, 0, PITCH_NORM); +} + +/* +================ +FireBullets + +Go to the trouble of combining multiple pellets into a single damage call. +================ +*/ +void EV_Quake_FireBullets( int idx, float *forward, float *right, float *up, int cShots, float *vecSrc, float *vecDirShooting, float *vecSpread ) +{ + int i; + pmtrace_t tr; + int iShot; + vec3_t vecRight, vecUp; + + VectorCopy( right, vecRight ); + VectorCopy( up, vecUp ); + + for ( iShot = 1; iShot <= cShots; iShot++ ) + { + vec3_t vecDir, vecEnd; + + // get circular gaussian spread + vec3_t spread; + do { + spread[0] = gEngfuncs.pfnRandomFloat(-1.0,1.0) + gEngfuncs.pfnRandomFloat(-1.0,1.0); + spread[1] = gEngfuncs.pfnRandomFloat(-1.0,1.0) + gEngfuncs.pfnRandomFloat(-1.0,1.0); + spread[2] = spread[0] * spread[0] + spread[1] *spread[1]; + } while (spread[2] > 1); + + for ( i = 0 ; i < 3; i++ ) + { + vecDir[i] = vecDirShooting[i] + spread[ 0 ] * vecSpread[ 0 ] * vecRight[ i ] + spread[ 1 ] * vecSpread[ 1 ] * up [ i ]; + vecEnd[i] = vecSrc[ i ] + 2048.0 * vecDir[ i ]; + } + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( idx - 1 ); + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( vecSrc, vecEnd, PM_STUDIO_BOX, -1, &tr ); + + int iBulletType = BULLET_PLAYER_BUCKSHOT; + + // do damage, paint decals + if ( tr.fraction != 1.0 ) + { + switch(iBulletType) + { + default: + case BULLET_PLAYER_9MM: + EV_HLDM_PlayTextureSound( idx, &tr, vecSrc, vecEnd, iBulletType ); + EV_HLDM_DecalGunshot( &tr, iBulletType ); + break; + case BULLET_PLAYER_MP5: + EV_HLDM_PlayTextureSound( idx, &tr, vecSrc, vecEnd, iBulletType ); + EV_HLDM_DecalGunshot( &tr, iBulletType ); + break; + case BULLET_PLAYER_BUCKSHOT: + EV_HLDM_DecalGunshot( &tr, iBulletType ); + break; + case BULLET_PLAYER_357: + EV_HLDM_PlayTextureSound( idx, &tr, vecSrc, vecEnd, iBulletType ); + EV_HLDM_DecalGunshot( &tr, iBulletType ); + break; + } + } + + gEngfuncs.pEventAPI->EV_PopPMStates(); + } +} + +void EV_FireShotGunDouble( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + + int i; + vec3_t vecSrc, vecAiming; + vec3_t vecSpread; + vec3_t up, right, forward; + float flSpread = 0.01; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + + AngleVectors( angles, forward, right, up ); + + EV_Quake_PlayQuadSound( idx, origin, args->iparam1 ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/shotgn2.wav", 1.0, ATTN_NORM, 0, 100 ); + + EV_GetGunPosition( args, vecSrc, origin ); + VectorCopy( forward, vecAiming ); + + for ( i = 0; i < 3; i++ ) + { + vecSpread[0] = 0.04; + vecSpread[1] = 0.04; + vecSpread[2] = 0.00; + } + + EV_Quake_FireBullets( idx, forward, right, up, 14, vecSrc, vecAiming, vecSpread ); + + if ( EV_IsLocal( idx ) ) + { + V_PunchAxis( 0, Q_BIG_PUNCHANGLE_KICK ); + } +} + +void EV_FireShotGunSingle( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + + int i; + vec3_t vecSrc, vecAiming; + vec3_t vecSpread; + vec3_t up, right, forward; + float flSpread = 0.01; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + + AngleVectors( angles, forward, right, up ); + + EV_Quake_PlayQuadSound( idx, origin, args->iparam1 ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/guncock.wav", 1.0, ATTN_NORM, 0, 100 ); + + EV_GetGunPosition( args, vecSrc, origin ); + VectorCopy( forward, vecAiming ); + + for ( i = 0; i < 3; i++ ) + { + vecSpread[0] = 0.04; + vecSpread[1] = 0.04; + vecSpread[2] = 0.00; + } + + EV_Quake_FireBullets( idx, forward, right, up, 6, vecSrc, vecAiming, vecSpread ); + + if ( EV_IsLocal( idx ) ) + { + V_PunchAxis( 0, Q_SMALL_PUNCHANGLE_KICK ); + } +} + +enum soundtypes_e +{ + SOUND_MISS, + SOUND_HIT_BODY, + SOUND_HIT_WALL, +}; + +void EV_Quake_PlayAxeSound( int idx, float *origin, int iSoundType ) +{ + switch ( iSoundType ) + { + case SOUND_HIT_BODY: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "player/axhitbod.wav", 1, ATTN_NORM, 0, PITCH_NORM); + break; + + case SOUND_HIT_WALL: + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM, 0, PITCH_NORM); + break; + + default: + break; + } +} + + +void EV_FireAxe( event_args_t *args ) +{ + int ent; + + int fDidHit = 0; + int m_bHullHit = 1; + + vec3_t vecSrc, vecEnd; + vec3_t up, right, forward; + pmtrace_t tr; + + int idx; + vec3_t origin; + vec3_t angles; + vec3_t velocity; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + + AngleVectors( angles, forward, right, up ); + + EV_GetGunPosition( args, vecSrc, origin ); + + VectorMA( vecSrc, 64, forward, vecEnd ); + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( idx - 1 ); + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( vecSrc, vecEnd, PM_NORMAL, -1, &tr ); + + if ( tr.fraction < 1.0 ) + { + ent = gEngfuncs.pEventAPI->EV_IndexFromTrace( &tr ); + + if ( !EV_IsPlayer( ent ) ) + { + EV_Quake_PlayAxeSound( idx, origin, SOUND_HIT_WALL ); + EV_HLDM_DecalGunshot( &tr, BULLET_PLAYER_CROWBAR ); + } + + EV_Quake_PlayQuadSound( idx, origin, args->iparam1 ); + } + + gEngfuncs.pEventAPI->EV_PopPMStates(); + +} + +void EV_FireAxeSwing( event_args_t *args ) +{ + int idx; + vec3_t origin; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + + EV_Quake_PlayQuadSound( idx, origin, args->iparam1 ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/ax1.wav", 1.0, ATTN_NORM, 0, 100 ); +} + + +#ifdef THREEWAVE + +void EV_Hook( event_args_t *args ) +{ + return; +} + +void EV_Cable( event_args_t *args ) +{ + int idx, attached, team, modelIndex; + + float r, g, b; + + idx = args->entindex; + attached = args->iparam1; + team = args->iparam2; + + modelIndex = gEngfuncs.pEventAPI->EV_FindModelIndex( "sprites/smoke.spr" ); + + if ( !modelIndex ) + return; + + if ( team == 1 ) + { + r = 500; + g = 0; + b = 0; + } + else if ( team == 2 ) + { + r = 0; + g = 0; + b = 500; + } + + if ( args->bparam1 == 1) + gEngfuncs.pEfxAPI->R_BeamKill ( attached ); + else + gEngfuncs.pEfxAPI->R_BeamEnts ( idx, attached, modelIndex, 9999, 1, 0.001, 0.8, 0.0, 0.0, 0.0, r, g, b ); +} + +void EV_GenericParticleCallback( struct particle_s *particle, float frametime ) +{ + int i; + + for ( i = 0; i < 3; i++ ) + { + particle->org[ i ] += particle->vel[ i ] * frametime; + } +} + +void EV_TrailCallback ( struct tempent_s *ent, float frametime, float currenttime ) +{ + + //If the Player is not on our PVS, then go back + if ( !CheckPVS( ent->clientIndex ) ) + return; + + dlight_t *dl = gEngfuncs.pEfxAPI->CL_AllocDlight ( 0 ); + + cl_entity_t *player = gEngfuncs.GetEntityByIndex( ent->clientIndex ); + + if ( !player ) + return; + + VectorCopy ( player->origin, dl->origin ); + + dl->radius = 240; + dl->die = gEngfuncs.GetClientTime() + 0.001; //Kill it right away + + if ( ent->entity.baseline.movetype == 2 ) + { + dl->color.r = 240; + dl->color.g = 25; + dl->color.b = 25; + } + else + { + dl->color.r = 25; + dl->color.g = 25; + dl->color.b = 240; + } + + //I know what you are thinking and yes, this was the only place I could find on where to put the timer + //Hacky; Yes, Works; Yes!. + if ( ent->entity.baseline.animtime > gEngfuncs.GetClientTime() ) + return; + + for ( int i = 0; i < 4; i++ ) + { + particle_t *bPart = gEngfuncs.pEfxAPI->R_AllocParticle (EV_GenericParticleCallback); + + if ( bPart ) + { + VectorCopy ( ent->entity.origin, bPart->org); + bPart->org[0] += gEngfuncs.pfnRandomFloat (-2, 2); + bPart->org[1] += gEngfuncs.pfnRandomFloat (-2, 2); + bPart->org[2] += gEngfuncs.pfnRandomFloat (-2, 2); + bPart->vel[0] = gEngfuncs.pfnRandomFloat (-50, 50); + bPart->vel[1] = gEngfuncs.pfnRandomFloat (-50, 50); + bPart->vel[2] = gEngfuncs.pfnRandomFloat (75, 80); + + //Check team and color the particle correctly + if ( ent->entity.baseline.movetype == 2 ) + bPart->color = 70; + else + bPart->color = 43; + + bPart->type = pt_slowgrav; + bPart->die = gEngfuncs.GetClientTime() + 0.5; + } + } + + ent->entity.baseline.animtime = gEngfuncs.GetClientTime() + 0.3; +} + + +void EV_FollowCarrier (event_args_t *args) +{ + int iEntIndex = args->iparam1; + int iTeam = args->iparam2; + float r,g,b; + + int modelIndex; + char *model = "sprites/smoke.spr"; + + modelIndex = gEngfuncs.pEventAPI->EV_FindModelIndex ( model ); + + if ( iTeam == 2 ) + { + r = 500; + g = 0; + b = 0; + } + else if ( iTeam == 1 ) + { + r = 0; + g = 0; + b = 500; + } + + if ( args->bparam1 == 1) + gEngfuncs.pEfxAPI->R_KillAttachedTents ( iEntIndex ); + else + { + TEMPENTITY *pTrailSpawner = NULL; + pTrailSpawner = gEngfuncs.pEfxAPI->R_TempModel ( args->origin, args->velocity, args->angles, 9999, modelIndex, TE_BOUNCE_NULL ); + + if ( pTrailSpawner != NULL) + { + pTrailSpawner->flags |= ( FTENT_PLYRATTACHMENT | FTENT_PERSIST | FTENT_NOMODEL | FTENT_CLIENTCUSTOM ); + pTrailSpawner->clientIndex = iEntIndex; + + pTrailSpawner->entity.baseline.movetype = iTeam; // Hack to store the team number on this temp ent. + pTrailSpawner->entity.baseline.animtime = gEngfuncs.GetClientTime() + 0.3; + pTrailSpawner->callback = EV_TrailCallback; + } + } +} + +void EV_FlagSpawn (event_args_t *args) +{ + vec3_t origin; + VectorCopy( args->origin, origin ); + + gEngfuncs.pEfxAPI->R_Implosion ( origin, 50, 20, 0.5 ); + + for ( int i = 0; i < 20; i++ ) + { + particle_t *bPart = gEngfuncs.pEfxAPI->R_AllocParticle ( EV_GenericParticleCallback ); + + if ( bPart ) + { + VectorCopy ( args->origin, bPart->org); + bPart->org[0] += gEngfuncs.pfnRandomFloat (-4, 4); + bPart->org[1] += gEngfuncs.pfnRandomFloat (-4, 4); + bPart->org[2] += gEngfuncs.pfnRandomFloat (-4, 4); + bPart->vel[0] = gEngfuncs.pfnRandomFloat (-50, 50); + bPart->vel[1] = gEngfuncs.pfnRandomFloat (-50, 50); + bPart->vel[2] = gEngfuncs.pfnRandomFloat (250, 350); + + //Check team and color the particle correctly + if ( args->iparam1 == 1 ) + bPart->color = 70; + else + bPart->color = 43; + + bPart->type = pt_grav; + bPart->die = gEngfuncs.GetClientTime() + 3; + } + } +} + +#endif + + +void EV_PowerupCallback ( struct tempent_s *ent, float frametime, float currenttime ) +{ + //If the Player is not on our PVS, then go back + if ( !CheckPVS( ent->clientIndex ) ) + return; + + dlight_t *dl = gEngfuncs.pEfxAPI->CL_AllocDlight ( 0 ); + + cl_entity_t *player = gEngfuncs.GetEntityByIndex( ent->clientIndex ); + + if ( !player ) + return; + + VectorCopy ( player->origin, dl->origin ); + + dl->radius = 270; + dl->dark = true; + dl->die = gEngfuncs.GetClientTime() + 0.001; //Kill it right away + + if ( ent->entity.baseline.iuser2 == 1 ) + { + if ( ent->entity.baseline.iuser1 == 1 ) + { + dl->color.r = 255; + dl->color.g = 128; + dl->color.b = 128; + } + else + { + dl->color.r = 0; + dl->color.g = 75; + dl->color.b = 255; + } + } + else if ( ent->entity.baseline.iuser2 == 2 ) + { + if ( ent->entity.baseline.iuser1 == 1 ) + { + dl->color.r = 255; + dl->color.g = 128; + dl->color.b = 0; + } + else if ( ent->entity.baseline.iuser1 == 2 ) + { + dl->color.r = 0; + dl->color.g = 128; + dl->color.b = 250; + } + else + { + dl->color.r = 255; + dl->color.g = 75; + dl->color.b = 0; + } + } + else if ( ent->entity.baseline.iuser2 == 3 ) + { + dl->color.r = 255; + dl->color.g = 125; + dl->color.b = 255; + } +} + + +void EV_PlayerPowerup (event_args_t *args) +{ + int iEntIndex = args->iparam1; + int iTeam = args->iparam2; + int iPowerUp = (int)args->fparam1; + + int modelIndex; + char *model = "sprites/smoke.spr"; + + modelIndex = gEngfuncs.pEventAPI->EV_FindModelIndex ( model ); + + if ( args->bparam1 == 1) + gEngfuncs.pEfxAPI->R_KillAttachedTents ( iEntIndex ); + + if ( iPowerUp ) + { + TEMPENTITY *pTrailSpawner = NULL; + pTrailSpawner = gEngfuncs.pEfxAPI->R_TempModel ( args->origin, args->velocity, args->angles, 9999, modelIndex, TE_BOUNCE_NULL ); + + if ( pTrailSpawner != NULL) + { + pTrailSpawner->flags |= ( FTENT_PLYRATTACHMENT | FTENT_PERSIST | FTENT_NOMODEL | FTENT_CLIENTCUSTOM ); + pTrailSpawner->clientIndex = iEntIndex; + + pTrailSpawner->entity.baseline.iuser1 = iTeam; + pTrailSpawner->entity.baseline.iuser2 = iPowerUp; + + pTrailSpawner->callback = EV_PowerupCallback; + } + } +} + +float g_flLightTime; +BEAM *pBeam; + +void EV_FireLightning( event_args_t *args ) +{ + int idx; + vec3_t origin, endorigin; + vec3_t angles; + vec3_t vecEnd; + vec3_t up, right, forward; + int iShutDown; + + cl_entity_t *player; + + pmtrace_t tr; + int modelIndex; + + bool bSound = false; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + bSound = args->bparam1 ? true : false; + iShutDown = args->iparam2; + + modelIndex = gEngfuncs.pEventAPI->EV_FindModelIndex( "sprites/laserbeam.spr" ); + + // Load it up with some bogus data + player = gEngfuncs.GetLocalPlayer(); + + AngleVectors( angles, forward, right, up ); + + if ( EV_IsLocal( idx ) ) + { + if ( g_flLightTime <= gEngfuncs.GetClientTime() ) + { + gEngfuncs.pfnWeaponAnim( 1, 0 ); + g_flLightTime = gEngfuncs.GetClientTime() + 0.5; + } + + V_PunchAxis( 0, Q_SMALL_PUNCHANGLE_KICK ); + + cl_entity_t *player = gEngfuncs.GetViewModel(); + origin = player->attachment[0]; + } + else + { + origin = origin + Vector( 0, 0, 16 ); + } + + + if ( bSound ) + { + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/lhit.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); + EV_Quake_PlayQuadSound( idx, origin, args->iparam1 ); + } + + if ( iShutDown == 0 && EV_IsLocal( idx ) && pBeam == NULL ) + { + vec3_t vecSrc, vecEnd, origin, angles, forward, right, up; + pmtrace_t tr; + + cl_entity_t *pl = gEngfuncs.GetEntityByIndex( idx ); + + if ( pl ) + { + VectorCopy( gHUD.m_vecAngles, angles ); + + AngleVectors( angles, forward, right, up ); + + EV_GetGunPosition( args, vecSrc, pl->origin ); + + VectorMA( vecSrc, 2048, forward, vecEnd ); + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( idx - 1 ); + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( vecSrc, vecEnd, PM_STUDIO_BOX, -1, &tr ); + + gEngfuncs.pEventAPI->EV_PopPMStates(); + + pBeam = gEngfuncs.pEfxAPI->R_BeamEntPoint ( idx | 0x1000, tr.endpos, modelIndex, 99999, 5.0, 0.15, 2.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0 ); + } + } + else if ( iShutDown == 1 ) + { + if ( EV_IsLocal( idx ) ) + { + if ( pBeam ) + { + pBeam->die = 0.0; + pBeam = NULL; + } + } + } +} + + +void EV_FireRocket( event_args_t *args ) +{ + int idx; + vec3_t origin; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/sgun1.wav", 1.0, ATTN_NORM, 0, 100 ); + + EV_Quake_PlayQuadSound( idx, origin, args->iparam1 ); + + if ( EV_IsLocal( idx ) ) + { + V_PunchAxis( 0, Q_SMALL_PUNCHANGLE_KICK ); + } +} + +void EV_FireGrenade( event_args_t *args ) +{ + int idx; + vec3_t origin; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + + EV_Quake_PlayQuadSound( idx, origin, args->iparam1 ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/grenade.wav", 1.0, ATTN_NORM, 0, 100 ); + + if ( EV_IsLocal( idx ) ) + { + V_PunchAxis( 0, Q_SMALL_PUNCHANGLE_KICK ); + } +} + +void EV_Quake_NailTouch( struct tempent_s *ent, pmtrace_t *ptr ) +{ + char decalname[ 32 ]; + int idx; + physent_t *pe; + + pe = gEngfuncs.pEventAPI->EV_GetPhysent( ptr->ent ); + if ( pe && ( pe->solid == SOLID_BSP || pe->movetype == MOVETYPE_PUSHSTEP ) ) + { + decalname[ 0 ] = '\0'; + idx = gEngfuncs.pfnRandomLong( 0, 4 ); + sprintf( decalname, "{shot%i", idx + 1 ); + EV_Quake_GunshotDecalTrace( ptr, decalname ); + } +} + +void EV_Quake_ClientProjectile( float *vecOrigin, float *vecVelocity, short sModelIndex, int iOwnerIndex, int iLife, void (*hitcallback)( struct tempent_s *ent, pmtrace_t *ptr ) ) +{ + gEngfuncs.pEfxAPI->R_Projectile( vecOrigin, vecVelocity, sModelIndex, iLife, iOwnerIndex, hitcallback ); +} + +void EV_FireSpike( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t up, right, forward; + vec3_t vecVelocity; + float offset = args->bparam1 ? 2.0 : -2.0; + + int shell; + + // gEngfuncs.Con_NPrintf( 22, "offset %f", offset ); + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + + shell = gEngfuncs.pEventAPI->EV_FindModelIndex( "models/spike.mdl" ); + AngleVectors( angles, forward, right, up ); + + EV_Quake_PlayQuadSound( idx, origin, args->iparam1 ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/rocket1i.wav", 1.0, ATTN_NORM, 0, 100 ); + + // make nails + VectorScale( forward, 1000, vecVelocity ); + EV_Quake_ClientProjectile( origin + Vector(0,0,10) + (right * offset), vecVelocity, (short)shell, idx, 6, EV_Quake_NailTouch ); + + if ( EV_IsLocal( idx ) ) + { + V_PunchAxis( 0, Q_SMALL_PUNCHANGLE_KICK ); + } +} + +void EV_FireSuperSpike( event_args_t *args ) +{ + int idx; + vec3_t origin; + vec3_t angles; + vec3_t up, right, forward; + vec3_t vecVelocity; + + int shell; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + + shell = gEngfuncs.pEventAPI->EV_FindModelIndex( "models/spike.mdl" ); + AngleVectors( angles, forward, right, up ); + + EV_Quake_PlayQuadSound( idx, origin, args->iparam1 ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/spike2.wav", 1.0, ATTN_NORM, 0, 100 ); + + // make nails + VectorScale( forward, 1000, vecVelocity ); + EV_Quake_ClientProjectile( origin + Vector(0,0,16), vecVelocity, (short)shell, idx, 6, EV_Quake_NailTouch ); + + if ( EV_IsLocal( idx ) ) + { + V_PunchAxis( 0, Q_SMALL_PUNCHANGLE_KICK ); + } +} + +#define SND_CHANGE_PITCH (1<<7) + +void EV_TrainPitchAdjust( event_args_t *args ) +{ + int idx; + vec3_t origin; + + unsigned short us_params; + int noise; + float m_flVolume; + int pitch; + int stop; + + char sz[ 256 ]; + + idx = args->entindex; + + VectorCopy( args->origin, origin ); + + us_params = (unsigned short)args->iparam1; + stop = args->bparam1; + + m_flVolume = (float)(us_params & 0x003f)/40.0; + noise = (int)(((us_params) >> 12 ) & 0x0007); + pitch = (int)( 10.0 * (float)( ( us_params >> 6 ) & 0x003f ) ); + + switch ( noise ) + { + case 1: strcpy( sz, "plats/ttrain1.wav"); break; + case 2: strcpy( sz, "plats/ttrain2.wav"); break; + case 3: strcpy( sz, "plats/ttrain3.wav"); break; + case 4: strcpy( sz, "plats/ttrain4.wav"); break; + case 5: strcpy( sz, "plats/ttrain6.wav"); break; + case 6: strcpy( sz, "plats/ttrain7.wav"); break; + default: + // no sound + strcpy( sz, "" ); + return; + } + + if ( stop ) + { + gEngfuncs.pEventAPI->EV_StopSound( idx, CHAN_STATIC, sz ); + } + else + { + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_STATIC, sz, m_flVolume, ATTN_NORM, SND_CHANGE_PITCH, pitch ); + } +} + +char *DMC_BloodDecal (void) +{ + static char blooddecal[ 32 ]; + int idx; + + idx = gEngfuncs.pfnRandomLong( 0, 5 ); + + sprintf( blooddecal, "{blood%i", idx + 1 ); + + return blooddecal; +} + +void DMC_DecalTrace( pmtrace_t *pTrace, char *decalName ) +{ + physent_t *pe; + + pe = gEngfuncs.pEventAPI->EV_GetPhysent( pTrace->ent ); + + // Only decal brush models such as the world etc. + if ( decalName && decalName[0] && pe && ( pe->solid == SOLID_BSP || pe->movetype == MOVETYPE_PUSHSTEP ) ) + { + if ( CVAR_GET_FLOAT( "r_decals" ) ) + { + gEngfuncs.pEfxAPI->R_DecalShoot( + gEngfuncs.pEfxAPI->Draw_DecalIndex( gEngfuncs.pEfxAPI->Draw_DecalIndexFromName( decalName ) ), + gEngfuncs.pEventAPI->EV_IndexFromTrace( pTrace ), 0, pTrace->endpos, 0 ); + } + } +} + +void EV_GibTouch ( struct tempent_s *ent, struct pmtrace_s *ptr ) +{ + DMC_DecalTrace (ptr, DMC_BloodDecal()); + + // 1 in 5 chance of squishy sound + if (gEngfuncs.pfnRandomLong(0, 4) > 0) + return; + + switch (gEngfuncs.pfnRandomLong(0, 5)) + { + case 0 : gEngfuncs.pEventAPI->EV_PlaySound( 0, ptr->endpos, CHAN_STATIC, "debris/flesh1.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 1 : gEngfuncs.pEventAPI->EV_PlaySound( 0, ptr->endpos, CHAN_STATIC, "debris/flesh2.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 2 : gEngfuncs.pEventAPI->EV_PlaySound( 0, ptr->endpos, CHAN_STATIC, "debris/flesh3.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 3 : gEngfuncs.pEventAPI->EV_PlaySound( 0, ptr->endpos, CHAN_STATIC, "debris/flesh5.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 4 : gEngfuncs.pEventAPI->EV_PlaySound( 0, ptr->endpos, CHAN_STATIC, "debris/flesh6.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 5 : gEngfuncs.pEventAPI->EV_PlaySound( 0, ptr->endpos, CHAN_STATIC, "debris/flesh7.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + } +} + +void EV_GibParticleCallback( struct particle_s *particle, float frametime ) +{ + int i; + + for ( i = 0; i < 3; i++ ) + { + particle->org[ i ] += particle->vel[ i ] * frametime; + } +} + +void EV_Gibbed (event_args_t *args) +{ + + vec3_t origin, velocity, angles, rotate; + int modelindex, i; + TEMPENTITY *pGib = NULL; + int gibs = 5; + char *model1 = "models/gib_1.mdl"; + char *model2 = "models/gib_2.mdl"; + char *model3 = "models/gib_3.mdl"; + + VectorCopy( args->origin, origin ); + + gEngfuncs.pEventAPI->EV_PlaySound( 0, origin, CHAN_STATIC, "player/gib.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); + + rotate[0] = gEngfuncs.pfnRandomLong (-100, 100); + rotate[1] = gEngfuncs.pfnRandomLong (-100, 100); + rotate[2] = gEngfuncs.pfnRandomLong (-100, 100); + + for (i = 0; i < gibs; i++) + { + switch ( gEngfuncs.pfnRandomLong( 1, 3 ) ) + { + case 1: modelindex = gEngfuncs.pEventAPI->EV_FindModelIndex ( model1 ); break; + case 2: modelindex = gEngfuncs.pEventAPI->EV_FindModelIndex ( model2 ); break; + case 3: modelindex = gEngfuncs.pEventAPI->EV_FindModelIndex ( model3 ); break; + //Just in case + default: modelindex = gEngfuncs.pEventAPI->EV_FindModelIndex ( model1 ); break; + } + + if (!modelindex) + return; + + VectorCopy( args->angles, angles ); + + VectorScale( angles, -1.0, angles ); + + angles[0] += gEngfuncs.pfnRandomFloat ( -0.30, 0.30 ); + angles[1] += gEngfuncs.pfnRandomFloat ( -0.30, 0.30 ); + angles[2] += gEngfuncs.pfnRandomFloat ( -0.30, 0.30 ); + + VectorScale ( angles, gEngfuncs.pfnRandomFloat( 500, 1200 ), velocity ); + velocity[2] += 600; + + pGib = gEngfuncs.pEfxAPI->R_TempModel (origin, velocity, rotate, 15, modelindex, TE_BOUNCE_NULL); + + if (pGib != NULL) + { + pGib->flags |= (FTENT_COLLIDEWORLD | FTENT_ROTATE | FTENT_FADEOUT | FTENT_CLIENTCUSTOM | FTENT_SMOKETRAIL); + pGib->hitcallback = EV_GibTouch; + + } + } +} + +//Spawns the teleport effect. +void EV_Teleport ( event_args_t *args ) +{ + vec3_t vecOrg; + + VectorCopy( args->origin, vecOrg ); + + switch (gEngfuncs.pfnRandomLong(0, 4)) + { + case 0 : gEngfuncs.pEventAPI->EV_PlaySound( 0, vecOrg, CHAN_STATIC, "misc/r_tele1.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 1 : gEngfuncs.pEventAPI->EV_PlaySound( 0, vecOrg, CHAN_STATIC, "misc/r_tele2.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 2 : gEngfuncs.pEventAPI->EV_PlaySound( 0, vecOrg, CHAN_STATIC, "misc/r_tele3.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 3 : gEngfuncs.pEventAPI->EV_PlaySound( 0, vecOrg, CHAN_STATIC, "misc/r_tele4.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 4 : gEngfuncs.pEventAPI->EV_PlaySound( 0, vecOrg, CHAN_STATIC, "misc/r_tele5.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + } + + gEngfuncs.pEfxAPI->R_TeleportSplash( vecOrg ); +} + +int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3}; + +void EV_RocketTrailCallback ( struct tempent_s *ent, float frametime, float currenttime ) +{ + if ( currenttime < ent->entity.baseline.fuser1 ) + return; + + if ( ent->entity.origin == ent->entity.attachment[0] ) + ent->die = gEngfuncs.GetClientTime(); + else + VectorCopy ( ent->entity.origin, ent->entity.attachment[0] ); + + //Make the Rocket light up. ( And only rockets, no Grenades ). + if ( ent->entity.baseline.sequence == 70 ) + { + dlight_t *dl = gEngfuncs.pEfxAPI->CL_AllocDlight ( 0 ); + VectorCopy ( ent->entity.origin, dl->origin ); + + dl->radius = 160; + dl->dark = true; + dl->die = gEngfuncs.GetClientTime() + 0.001; //Kill it right away + + dl->color.r = 255; + dl->color.g = 255; + dl->color.b = 255; + } +} + +#define GRENADE_TRAIL 1 +#define ROCKET_TRAIL 2 + +void EV_Trail (event_args_t *args) +{ + int iEntIndex = args->iparam1; + TEMPENTITY *pTrailSpawner = NULL; + + pTrailSpawner = gEngfuncs.pEfxAPI->CL_TempEntAllocNoModel ( args->origin ); + + if ( pTrailSpawner != NULL) + { + pTrailSpawner->flags |= ( FTENT_PLYRATTACHMENT | FTENT_COLLIDEKILL | FTENT_CLIENTCUSTOM | FTENT_SMOKETRAIL | FTENT_COLLIDEWORLD ); + pTrailSpawner->callback = EV_RocketTrailCallback; + pTrailSpawner->clientIndex = iEntIndex; + + if ( args->iparam2 == GRENADE_TRAIL ) + pTrailSpawner->entity.baseline.sequence = 69; + else if ( args->iparam2 == ROCKET_TRAIL ) + pTrailSpawner->entity.baseline.sequence = 70; + + pTrailSpawner->die = gEngfuncs.GetClientTime() + 10; // Just in case + pTrailSpawner->entity.baseline.fuser1 = gEngfuncs.GetClientTime() + 0.5; // Don't try to die till 500ms ahead + } +} + +void EV_Explosion (event_args_t *args) +{ + vec3_t origin, scorch_origin, velocity, forward, right, up; + int modelIndex; + char *model = "sprites/zerogxplode.spr"; + modelIndex = gEngfuncs.pEventAPI->EV_FindModelIndex (model); + pmtrace_t tr; + + //Make decals and Explosions + //Might not work for grenades. + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, velocity ); //Velocity + + scorch_origin = origin - velocity.Normalize() * 32; + + gEngfuncs.pEfxAPI->R_Explosion( origin, modelIndex, 2.5, 15, TE_EXPLFLAG_NONE ); + gEngfuncs.pEfxAPI->R_ParticleExplosion2( origin , 111, 8 ); + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); + + gEngfuncs.pEventAPI->EV_PushPMStates(); + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( -1 ); + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( scorch_origin, scorch_origin + velocity.Normalize() * 64, PM_STUDIO_BOX | PM_WORLD_ONLY , -1, &tr ); + + gEngfuncs.pEventAPI->EV_PopPMStates(); + + + DMC_DecalTrace( &tr, EV_HLDM_RocketDamageDecal() ); + +} + +#define EV_DMC_MOVE_SOUND 0 +#define EV_DMC_STOP_SOUND 1 +char *EV_DMC_LookupDoorSound( int type, int index ) +{ + static char sound[ 128 ]; + int idx; + + // Assume the worst + strcpy( sound, "common/null.wav"); + + if ( type == EV_DMC_MOVE_SOUND ) + { + idx = ( index >> 8 ) & 0xff; + + switch (idx) + { + case 0: + strcpy( sound, "common/null.wav"); + break; + case 1: + strcpy( sound, "doors/doormove1.wav"); + break; + case 2: + strcpy( sound, "doors/doormove2.wav"); + break; + case 3: + strcpy( sound, "doors/doormove3.wav"); + break; + case 4: + strcpy( sound, "doors/doormove4.wav"); + break; + case 5: + strcpy( sound, "doors/doormove5.wav"); + break; + case 6: + strcpy( sound, "doors/doormove6.wav"); + break; + case 7: + strcpy( sound, "doors/doormove7.wav"); + break; + case 8: + strcpy( sound, "doors/doormove8.wav"); + break; + case 9: + strcpy( sound, "doors/doormove9.wav"); + break; + case 10: + strcpy( sound, "doors/doormove10.wav"); + break; + default: + strcpy( sound, "common/null.wav"); + break; + } + } + else if ( type == EV_DMC_STOP_SOUND ) + { + idx = ( index & 0xff ); + + // set the door's 'reached destination' stop sound + switch ( idx ) + { + case 0: + strcpy( sound, "common/null.wav"); + break; + case 1: + strcpy( sound, "doors/doorstop1.wav"); + break; + case 2: + strcpy( sound, "doors/doorstop2.wav"); + break; + case 3: + strcpy( sound, "doors/doorstop3.wav"); + break; + case 4: + strcpy( sound, "doors/doorstop4.wav"); + break; + case 5: + strcpy( sound, "doors/doorstop5.wav"); + break; + case 6: + strcpy( sound, "doors/doorstop6.wav"); + break; + case 7: + strcpy( sound, "doors/doorstop7.wav"); + break; + case 8: + strcpy( sound, "doors/doorstop8.wav"); + break; + default: + strcpy( sound, "common/null.wav"); + break; + } + } + return sound; +} + +void EV_DMC_DoorGoUp( event_args_t *args ) +{ + int idx = -1; + int soundindex = args->iparam1; + + gEngfuncs.pEventAPI->EV_StopSound( idx, CHAN_STATIC, EV_DMC_LookupDoorSound( EV_DMC_MOVE_SOUND, soundindex )); + gEngfuncs.pEventAPI->EV_PlaySound( idx, args->origin, CHAN_STATIC, EV_DMC_LookupDoorSound( EV_DMC_MOVE_SOUND, soundindex ), 1.0, ATTN_NORM, 0, PITCH_NORM ); +} + +void EV_DMC_DoorGoDown( event_args_t *args ) +{ + int idx = -1; + int soundindex = args->iparam1; + + gEngfuncs.pEventAPI->EV_StopSound( idx, CHAN_STATIC, EV_DMC_LookupDoorSound( EV_DMC_MOVE_SOUND, soundindex )); + gEngfuncs.pEventAPI->EV_PlaySound( idx, args->origin, CHAN_STATIC, EV_DMC_LookupDoorSound( EV_DMC_MOVE_SOUND, soundindex ), 1.0, ATTN_NORM, 0, PITCH_NORM ); +} + +void EV_DMC_DoorHitTop( event_args_t *args ) +{ + int idx = -1; + int soundindex = args->iparam1; + + gEngfuncs.pEventAPI->EV_StopSound( idx, CHAN_STATIC, EV_DMC_LookupDoorSound( EV_DMC_MOVE_SOUND, soundindex )); + gEngfuncs.pEventAPI->EV_PlaySound( idx, args->origin, CHAN_STATIC, EV_DMC_LookupDoorSound( EV_DMC_STOP_SOUND, soundindex ), 1.0, ATTN_NORM, 0, PITCH_NORM ); +} + +void EV_DMC_DoorHitBottom( event_args_t *args ) +{ + int idx = -1; + int soundindex = args->iparam1; + + gEngfuncs.pEventAPI->EV_StopSound( idx, CHAN_STATIC, EV_DMC_LookupDoorSound( EV_DMC_MOVE_SOUND, soundindex )); + gEngfuncs.pEventAPI->EV_PlaySound( idx, args->origin, CHAN_STATIC, EV_DMC_LookupDoorSound( EV_DMC_STOP_SOUND, soundindex ), 1.0, ATTN_NORM, 0, PITCH_NORM ); +} + + diff --git a/dmc/cl_dll/ev_hldm.h b/dmc/cl_dll/ev_hldm.h new file mode 100644 index 0000000..2c5e4ce --- /dev/null +++ b/dmc/cl_dll/ev_hldm.h @@ -0,0 +1,96 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined ( EV_HLDMH ) +#define EV_HLDMH + +// bullet types +typedef enum +{ + BULLET_NONE = 0, + BULLET_PLAYER_9MM, // glock + BULLET_PLAYER_MP5, // mp5 + BULLET_PLAYER_357, // python + BULLET_PLAYER_BUCKSHOT, // shotgun + BULLET_PLAYER_CROWBAR, // crowbar swipe + + BULLET_MONSTER_9MM, + BULLET_MONSTER_MP5, + BULLET_MONSTER_12MM, +} Bullet; + +enum glock_e { + GLOCK_IDLE1 = 0, + GLOCK_IDLE2, + GLOCK_IDLE3, + GLOCK_SHOOT, + GLOCK_SHOOT_EMPTY, + GLOCK_RELOAD, + GLOCK_RELOAD_NOT_EMPTY, + GLOCK_DRAW, + GLOCK_HOLSTER, + GLOCK_ADD_SILENCER +}; + +enum shotgun_e { + SHOTGUN_IDLE = 0, + SHOTGUN_FIRE, + SHOTGUN_FIRE2, + SHOTGUN_RELOAD, + SHOTGUN_PUMP, + SHOTGUN_START_RELOAD, + SHOTGUN_DRAW, + SHOTGUN_HOLSTER, + SHOTGUN_IDLE4, + SHOTGUN_IDLE_DEEP +}; + +enum mp5_e +{ + MP5_LONGIDLE = 0, + MP5_IDLE1, + MP5_LAUNCH, + MP5_RELOAD, + MP5_DEPLOY, + MP5_FIRE1, + MP5_FIRE2, + MP5_FIRE3, +}; + +enum python_e { + PYTHON_IDLE1 = 0, + PYTHON_FIDGET, + PYTHON_FIRE1, + PYTHON_RELOAD, + PYTHON_HOLSTER, + PYTHON_DRAW, + PYTHON_IDLE2, + PYTHON_IDLE3 +}; + +#define GAUSS_PRIMARY_CHARGE_VOLUME 256// how loud gauss is while charging +#define GAUSS_PRIMARY_FIRE_VOLUME 450// how loud gauss is when discharged + +enum gauss_e { + GAUSS_IDLE = 0, + GAUSS_IDLE2, + GAUSS_FIDGET, + GAUSS_SPINUP, + GAUSS_SPIN, + GAUSS_FIRE, + GAUSS_FIRE2, + GAUSS_HOLSTER, + GAUSS_DRAW +}; + +int EV_HLDM_DamageDecal( int entity, int bitsDamageType ); +void EV_HLDM_GunshotDecalTrace( pmtrace_t *pTrace, char *decalName ); +void EV_HLDM_DecalGunshot( pmtrace_t *pTrace, int iBulletType ); +int EV_HLDM_CheckTracer( int idx, float *vecSrc, float *end, float *forward, float *right, int iBulletType, int iTracerFreq, int *tracerCount ); +void EV_HLDM_FireBullets( int idx, float *forward, float *right, float *up, int cShots, float *vecSrc, float *vecDirShooting, float *vecSpread, float flDistance, int iBulletType, int iTracerFreq, int *tracerCount ); + +#endif // EV_HLDMH \ No newline at end of file diff --git a/dmc/cl_dll/events.cpp b/dmc/cl_dll/events.cpp new file mode 100644 index 0000000..a170888 --- /dev/null +++ b/dmc/cl_dll/events.cpp @@ -0,0 +1,30 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "hud.h" +#include "cl_util.h" + +void Game_HookEvents( void ); + +/* +=================== +EV_HookEvents + +See if game specific code wants to hook any events. +=================== +*/ +void EV_HookEvents( void ) +{ + Game_HookEvents(); +} diff --git a/dmc/cl_dll/eventscripts.h b/dmc/cl_dll/eventscripts.h new file mode 100644 index 0000000..5611729 --- /dev/null +++ b/dmc/cl_dll/eventscripts.h @@ -0,0 +1,73 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// eventscripts.h +#if !defined ( EVENTSCRIPTSH ) +#define EVENTSCRIPTSH + +// defaults for clientinfo messages +#define DEFAULT_VIEWHEIGHT 22 +#define VEC_DUCK_VIEW 12 + +#define FTENT_FADEOUT 0x00000080 + +#define DMG_GENERIC 0 // generic damage was done +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FREEZE (1 << 4) // frozen +#define DMG_FALL (1 << 5) // fell too far +#define DMG_BLAST (1 << 6) // explosive blast damage +#define DMG_CLUB (1 << 7) // crowbar, punch, headbutt +#define DMG_SHOCK (1 << 8) // electric shock +#define DMG_SONIC (1 << 9) // sound pulse shockwave +#define DMG_ENERGYBEAM (1 << 10) // laser or other high energy beam +#define DMG_NEVERGIB (1 << 12) // with this bit OR'd in, no damage type will be able to gib victims upon death +#define DMG_ALWAYSGIB (1 << 13) // with this bit OR'd in, any damage type can be made to gib victims upon death. + +// time-based damage +//mask off TF-specific stuff too +#define DMG_TIMEBASED (~(0xff003fff)) // mask for time-based damage + +#define DMG_DROWN (1 << 14) // Drowning +#define DMG_FIRSTTIMEBASED DMG_DROWN + +#define DMG_PARALYZE (1 << 15) // slows affected creature down +#define DMG_NERVEGAS (1 << 16) // nerve toxins, very bad +#define DMG_POISON (1 << 17) // blood poisioning +#define DMG_RADIATION (1 << 18) // radiation exposure +#define DMG_DROWNRECOVER (1 << 19) // drowning recovery +#define DMG_ACID (1 << 20) // toxic chemicals or acid burns +#define DMG_SLOWBURN (1 << 21) // in an oven +#define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer +#define DMG_MORTAR (1 << 23) // Hit by air raid (done to distinguish grenade from mortar) + +//TF ADDITIONS +#define DMG_IGNITE (1 << 24) // Players hit by this begin to burn +#define DMG_RADIUS_MAX (1 << 25) // Radius damage with this flag doesn't decrease over distance +#define DMG_RADIUS_QUAKE (1 << 26) // Radius damage is done like Quake. 1/2 damage at 1/2 radius. +#define DMG_IGNOREARMOR (1 << 27) // Damage ignores target's armor +#define DMG_AIMED (1 << 28) // Does Hit location damage +#define DMG_WALLPIERCING (1 << 29) // Blast Damages ents through walls + +#define DMG_CALTROP (1<<30) +#define DMG_HALLUC (1<<31) + +// Some of these are HL/TFC specific? +void EV_EjectBrass( float *origin, float *velocity, float rotation, int model, int soundtype ); +void EV_GetGunPosition( struct event_args_s *args, float *pos, float *origin ); +void EV_GetDefaultShellInfo( struct event_args_s *args, float *origin, float *velocity, float *ShellVelocity, float *ShellOrigin, float *forward, float *right, float *up, float forwardScale, float upScale, float rightScale ); +qboolean EV_IsLocal( int idx ); +qboolean EV_IsPlayer( int idx ); +void EV_CreateTracer( float *start, float *end ); + +struct cl_entity_s *GetEntity( int idx ); +struct cl_entity_s *GetViewEntity( void ); +void EV_MuzzleFlash( void ); + +#endif // EVENTSCRIPTSH diff --git a/dmc/cl_dll/flashlight.cpp b/dmc/cl_dll/flashlight.cpp new file mode 100644 index 0000000..8578334 --- /dev/null +++ b/dmc/cl_dll/flashlight.cpp @@ -0,0 +1,146 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// flashlight.cpp +// +// implementation of CHudFlashlight class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + + + +DECLARE_MESSAGE(m_Flash, FlashBat) +DECLARE_MESSAGE(m_Flash, Flashlight) + +#define BAT_NAME "sprites/%d_Flashlight.spr" + +int CHudFlashlight::Init(void) +{ + m_fFade = 0; + m_fOn = 0; + + HOOK_MESSAGE(Flashlight); + HOOK_MESSAGE(FlashBat); + + m_iFlags |= HUD_ACTIVE; + + gHUD.AddHudElem(this); + + return 1; +}; + +void CHudFlashlight::Reset(void) +{ + m_fFade = 0; + m_fOn = 0; +} + +int CHudFlashlight::VidInit(void) +{ + int HUD_flash_empty = gHUD.GetSpriteIndex( "flash_empty" ); + int HUD_flash_full = gHUD.GetSpriteIndex( "flash_full" ); + int HUD_flash_beam = gHUD.GetSpriteIndex( "flash_beam" ); + + m_hSprite1 = gHUD.GetSprite(HUD_flash_empty); + m_hSprite2 = gHUD.GetSprite(HUD_flash_full); + m_hBeam = gHUD.GetSprite(HUD_flash_beam); + m_prc1 = &gHUD.GetSpriteRect(HUD_flash_empty); + m_prc2 = &gHUD.GetSpriteRect(HUD_flash_full); + m_prcBeam = &gHUD.GetSpriteRect(HUD_flash_beam); + m_iWidth = m_prc2->right - m_prc2->left; + + return 1; +}; + +int CHudFlashlight:: MsgFunc_FlashBat(const char *pszName, int iSize, void *pbuf ) +{ + + + BEGIN_READ( pbuf, iSize ); + int x = READ_BYTE(); + m_iBat = x; + m_flBat = ((float)x)/100.0; + + return 1; +} + +int CHudFlashlight:: MsgFunc_Flashlight(const char *pszName, int iSize, void *pbuf ) +{ + + BEGIN_READ( pbuf, iSize ); + m_fOn = READ_BYTE(); + int x = READ_BYTE(); + m_iBat = x; + m_flBat = ((float)x)/100.0; + + return 1; +} + +int CHudFlashlight::Draw(float flTime) +{ + if ( gHUD.m_iHideHUDDisplay & ( HIDEHUD_FLASHLIGHT | HIDEHUD_ALL ) ) + return 1; + + int r, g, b, x, y, a; + wrect_t rc; + + if (m_fOn) + a = 225; + else + a = MIN_ALPHA; + + if (m_flBat < 0.20) + UnpackRGB(r,g,b, RGB_REDISH); + else + UnpackRGB(r,g,b, RGB_YELLOWISH); + + ScaleColors(r, g, b, a); + + y = (m_prc1->bottom - m_prc2->top)/2; + x = ScreenWidth - m_iWidth - m_iWidth/2 ; + + // Draw the flashlight casing + SPR_Set(m_hSprite1, r, g, b ); + SPR_DrawAdditive( 0, x, y, m_prc1); + + if ( m_fOn ) + { // draw the flashlight beam + x = ScreenWidth - m_iWidth/2; + + SPR_Set( m_hBeam, r, g, b ); + SPR_DrawAdditive( 0, x, y, m_prcBeam ); + } + + // draw the flashlight energy level + x = ScreenWidth - m_iWidth - m_iWidth/2 ; + int iOffset = m_iWidth * (1.0 - m_flBat); + if (iOffset < m_iWidth) + { + rc = *m_prc2; + rc.left += iOffset; + + SPR_Set(m_hSprite2, r, g, b ); + SPR_DrawAdditive( 0, x + iOffset, y, &rc); + } + + + return 1; +} \ No newline at end of file diff --git a/dmc/cl_dll/geiger.cpp b/dmc/cl_dll/geiger.cpp new file mode 100644 index 0000000..8fa1407 --- /dev/null +++ b/dmc/cl_dll/geiger.cpp @@ -0,0 +1,184 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Geiger.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include + +#include "parsemsg.h" + +DECLARE_MESSAGE(m_Geiger, Geiger ) + +int CHudGeiger::Init(void) +{ + HOOK_MESSAGE( Geiger ); + + m_iGeigerRange = 0; + m_iFlags = 0; + + gHUD.AddHudElem(this); + + srand( (unsigned)time( NULL ) ); + + return 1; +}; + +int CHudGeiger::VidInit(void) +{ + return 1; +}; + +int CHudGeiger::MsgFunc_Geiger(const char *pszName, int iSize, void *pbuf) +{ + + BEGIN_READ( pbuf, iSize ); + + // update geiger data + m_iGeigerRange = READ_BYTE(); + m_iGeigerRange = m_iGeigerRange << 2; + + m_iFlags |= HUD_ACTIVE; + + return 1; +} + +int CHudGeiger::Draw (float flTime) +{ + int pct; + float flvol; + int rg[3]; + int i; + + if (m_iGeigerRange < 1000 && m_iGeigerRange > 0) + { + // peicewise linear is better than continuous formula for this + if (m_iGeigerRange > 800) + { + pct = 0; //Con_Printf ( "range > 800\n"); + } + else if (m_iGeigerRange > 600) + { + pct = 2; + flvol = 0.4; //Con_Printf ( "range > 600\n"); + rg[0] = 1; + rg[1] = 1; + i = 2; + } + else if (m_iGeigerRange > 500) + { + pct = 4; + flvol = 0.5; //Con_Printf ( "range > 500\n"); + rg[0] = 1; + rg[1] = 2; + i = 2; + } + else if (m_iGeigerRange > 400) + { + pct = 8; + flvol = 0.6; //Con_Printf ( "range > 400\n"); + rg[0] = 1; + rg[1] = 2; + rg[2] = 3; + i = 3; + } + else if (m_iGeigerRange > 300) + { + pct = 8; + flvol = 0.7; //Con_Printf ( "range > 300\n"); + rg[0] = 2; + rg[1] = 3; + rg[2] = 4; + i = 3; + } + else if (m_iGeigerRange > 200) + { + pct = 28; + flvol = 0.78; //Con_Printf ( "range > 200\n"); + rg[0] = 2; + rg[1] = 3; + rg[2] = 4; + i = 3; + } + else if (m_iGeigerRange > 150) + { + pct = 40; + flvol = 0.80; //Con_Printf ( "range > 150\n"); + rg[0] = 3; + rg[1] = 4; + rg[2] = 5; + i = 3; + } + else if (m_iGeigerRange > 100) + { + pct = 60; + flvol = 0.85; //Con_Printf ( "range > 100\n"); + rg[0] = 3; + rg[1] = 4; + rg[2] = 5; + i = 3; + } + else if (m_iGeigerRange > 75) + { + pct = 80; + flvol = 0.9; //Con_Printf ( "range > 75\n"); + //gflGeigerDelay = cl.time + GEIGERDELAY * 0.75; + rg[0] = 4; + rg[1] = 5; + rg[2] = 6; + i = 3; + } + else if (m_iGeigerRange > 50) + { + pct = 90; + flvol = 0.95; //Con_Printf ( "range > 50\n"); + rg[0] = 5; + rg[1] = 6; + i = 2; + } + else + { + pct = 95; + flvol = 1.0; //Con_Printf ( "range < 50\n"); + rg[0] = 5; + rg[1] = 6; + i = 2; + } + + flvol = (flvol * ((rand() & 127)) / 255) + 0.25; // UTIL_RandomFloat(0.25, 0.5); + + if ((rand() & 127) < pct || (rand() & 127) < pct) + { + //S_StartDynamicSound (-1, 0, rgsfx[rand() % i], r_origin, flvol, 1.0, 0, 100); + char sz[256]; + + int j = rand() & 1; + if (i > 2) + j += rand() & 1; + + sprintf(sz, "player/geiger%d.wav", j + 1); + PlaySound(sz, flvol); + + } + } + + return 1; +} diff --git a/dmc/cl_dll/health.cpp b/dmc/cl_dll/health.cpp new file mode 100644 index 0000000..8d7f6df --- /dev/null +++ b/dmc/cl_dll/health.cpp @@ -0,0 +1,476 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Health.cpp +// +// implementation of CHudHealth class +// + +#include "stdio.h" +#include "stdlib.h" +#include "math.h" + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" +#include + + +DECLARE_MESSAGE(m_Health, Health ) +DECLARE_MESSAGE(m_Health, Damage ) + +#define PAIN_NAME "sprites/%d_pain.spr" +#define DAMAGE_NAME "sprites/%d_dmg.spr" + +int giDmgHeight, giDmgWidth; + +int giDmgFlags[NUM_DMG_TYPES] = +{ + DMG_POISON, + DMG_ACID, + DMG_FREEZE|DMG_SLOWFREEZE, + DMG_DROWN, + DMG_BURN|DMG_SLOWBURN, + DMG_NERVEGAS, + DMG_RADIATION, + DMG_SHOCK, + DMG_CALTROP, + DMG_TRANQ, + DMG_CONCUSS, + DMG_HALLUC +}; + +int CHudHealth::Init(void) +{ + HOOK_MESSAGE(Health); + HOOK_MESSAGE(Damage); + m_iHealth = 100; + m_fFade = 0; + m_iFlags = 0; + m_bitsDamage = 0; + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 0; + giDmgHeight = 0; + giDmgWidth = 0; + + memset(m_dmg, 0, sizeof(DAMAGE_IMAGE) * NUM_DMG_TYPES); + + + gHUD.AddHudElem(this); + return 1; +} + +void CHudHealth::Reset( void ) +{ + // make sure the pain compass is cleared when the player respawns + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 0; + + + // force all the flashing damage icons to expire + m_bitsDamage = 0; + for ( int i = 0; i < NUM_DMG_TYPES; i++ ) + { + m_dmg[i].fExpire = 0; + } +} + +int CHudHealth::VidInit(void) +{ + m_hSprite = 0; + + m_HUD_dmg_bio = gHUD.GetSpriteIndex( "dmg_bio" ) + 1; + m_HUD_cross = gHUD.GetSpriteIndex( "cross" ); + + giDmgHeight = gHUD.GetSpriteRect(m_HUD_dmg_bio).right - gHUD.GetSpriteRect(m_HUD_dmg_bio).left; + giDmgWidth = gHUD.GetSpriteRect(m_HUD_dmg_bio).bottom - gHUD.GetSpriteRect(m_HUD_dmg_bio).top; + return 1; +} + +int CHudHealth:: MsgFunc_Health(const char *pszName, int iSize, void *pbuf ) +{ + // TODO: update local health data + BEGIN_READ( pbuf, iSize ); + int x = READ_BYTE(); + + m_iFlags |= HUD_ACTIVE; + + // Only update the fade if we've changed health + if (x != m_iHealth) + { + m_fFade = FADE_TIME; + m_iHealth = x; + } + + return 1; +} + + +int CHudHealth:: MsgFunc_Damage(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int armor = READ_BYTE(); // armor + int damageTaken = READ_BYTE(); // health + long bitsDamage = READ_LONG(); // damage bits + + vec3_t vecFrom; + + for ( int i = 0 ; i < 3 ; i++) + vecFrom[i] = READ_COORD(); + + UpdateTiles(gHUD.m_flTime, bitsDamage); + + // Actually took damage? + if ( damageTaken > 0 || armor > 0 ) + CalcDamageDirection(vecFrom); + + return 1; +} + + +// Returns back a color from the +// Green <-> Yellow <-> Red ramp +void CHudHealth::GetPainColor( int &r, int &g, int &b ) +{ + int iHealth = m_iHealth; + + if (iHealth > 25) + iHealth -= 25; + else if ( iHealth < 0 ) + iHealth = 0; +#if 0 + g = iHealth * 255 / 100; + r = 255 - g; + b = 0; +#else + if (m_iHealth > 25) + { + UnpackRGB(r,g,b, RGB_YELLOWISH); + } + else + { + r = 250; + g = 0; + b = 0; + } +#endif +} + +int CHudHealth::Draw(float flTime) +{ + int r, g, b; + int a = 0, x, y; + int HealthWidth; + +// if (m_iHealth <= 0) +// return 1; + + if ( gHUD.m_iHideHUDDisplay & HIDEHUD_HEALTH ) + return 1; + + if ( !m_hSprite ) + m_hSprite = LoadSprite(PAIN_NAME); + + // Has health changed? Flash the health # + if (m_fFade) + { + m_fFade -= (gHUD.m_flTimeDelta * 20); + if (m_fFade <= 0) + { + a = MIN_ALPHA; + m_fFade = 0; + } + + // Fade the health number back to dim + + a = MIN_ALPHA + (m_fFade/FADE_TIME) * 128; + + } + else + a = MIN_ALPHA; + + // If health is getting low, make it bright red + if (m_iHealth <= 15) + a = 255; + + GetPainColor( r, g, b ); + ScaleColors(r, g, b, a ); + + // Only draw health if we have the suit. + { + HealthWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; + int CrossWidth = gHUD.GetSpriteRect(m_HUD_cross).right - gHUD.GetSpriteRect(m_HUD_cross).left; + + y = ScreenHeight - gHUD.m_iFontHeight - gHUD.m_iFontHeight / 2; + x = CrossWidth /2; + + SPR_Set(gHUD.GetSprite(m_HUD_cross), r, g, b); + SPR_DrawAdditive(0, x, y, &gHUD.GetSpriteRect(m_HUD_cross)); + + x = CrossWidth + HealthWidth / 2; + + x = gHUD.DrawHudNumber(x, y, DHN_3DIGITS | DHN_DRAWZERO, m_iHealth, r, g, b); + + x += HealthWidth/2; + + int iHeight = gHUD.m_iFontHeight; + int iWidth = HealthWidth/10; + FillRGBA(x, y, iWidth, iHeight, 255, 160, 0, a); + } + + DrawDamage(flTime); + return DrawPain(flTime); +} + +void CHudHealth::CalcDamageDirection(vec3_t vecFrom) +{ + vec3_t forward, right, up; + float side, front; + vec3_t vecOrigin, vecAngles; + + if (!vecFrom[0] && !vecFrom[1] && !vecFrom[2]) + { + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 0; + return; + } + + + memcpy(vecOrigin, gHUD.m_vecOrigin, sizeof(vec3_t)); + memcpy(vecAngles, gHUD.m_vecAngles, sizeof(vec3_t)); + + + VectorSubtract (vecFrom, vecOrigin, vecFrom); + + float flDistToTarget = vecFrom.Length(); + + vecFrom = vecFrom.Normalize(); + AngleVectors (vecAngles, forward, right, up); + + front = DotProduct (vecFrom, right); + side = DotProduct (vecFrom, forward); + + if (flDistToTarget <= 50) + { + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 1; + } + else + { + if (side > 0) + { + if (side > 0.3) + m_fAttackFront = max(m_fAttackFront, side); + } + else + { + float f = fabs(side); + if (f > 0.3) + m_fAttackRear = max(m_fAttackRear, f); + } + + if (front > 0) + { + if (front > 0.3) + m_fAttackRight = max(m_fAttackRight, front); + } + else + { + float f = fabs(front); + if (f > 0.3) + m_fAttackLeft = max(m_fAttackLeft, f); + } + } +} + +int CHudHealth::DrawPain(float flTime) +{ + if (!(m_fAttackFront || m_fAttackRear || m_fAttackLeft || m_fAttackRight)) + return 1; + + int r, g, b; + int x, y, a, shade; + + // TODO: get the shift value of the health + a = 255; // max brightness until then + + float fFade = gHUD.m_flTimeDelta * 2; + + // SPR_Draw top + if (m_fAttackFront > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackFront, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 - SPR_Width(m_hSprite, 0)/2; + y = ScreenHeight/2 - SPR_Height(m_hSprite,0) * 3; + SPR_DrawAdditive(0, x, y, NULL); + m_fAttackFront = max( 0, m_fAttackFront - fFade ); + } else + m_fAttackFront = 0; + + if (m_fAttackRight > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackRight, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 + SPR_Width(m_hSprite, 1) * 2; + y = ScreenHeight/2 - SPR_Height(m_hSprite,1)/2; + SPR_DrawAdditive(1, x, y, NULL); + m_fAttackRight = max( 0, m_fAttackRight - fFade ); + } else + m_fAttackRight = 0; + + if (m_fAttackRear > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackRear, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 - SPR_Width(m_hSprite, 2)/2; + y = ScreenHeight/2 + SPR_Height(m_hSprite,2) * 2; + SPR_DrawAdditive(2, x, y, NULL); + m_fAttackRear = max( 0, m_fAttackRear - fFade ); + } else + m_fAttackRear = 0; + + if (m_fAttackLeft > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackLeft, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 - SPR_Width(m_hSprite, 3) * 3; + y = ScreenHeight/2 - SPR_Height(m_hSprite,3)/2; + SPR_DrawAdditive(3, x, y, NULL); + + m_fAttackLeft = max( 0, m_fAttackLeft - fFade ); + } else + m_fAttackLeft = 0; + + return 1; +} + +int CHudHealth::DrawDamage(float flTime) +{ + int r, g, b, a; + DAMAGE_IMAGE *pdmg; + + if (!m_bitsDamage) + return 1; + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + a = (int)( fabs(sin(flTime*2)) * 256.0); + + ScaleColors(r, g, b, a); + + // Draw all the items + int i; + for (i = 0; i < NUM_DMG_TYPES; i++) + { + if (m_bitsDamage & giDmgFlags[i]) + { + pdmg = &m_dmg[i]; + SPR_Set(gHUD.GetSprite(m_HUD_dmg_bio + i), r, g, b ); + SPR_DrawAdditive(0, pdmg->x, pdmg->y, &gHUD.GetSpriteRect(m_HUD_dmg_bio + i)); + } + } + + + // check for bits that should be expired + for ( i = 0; i < NUM_DMG_TYPES; i++ ) + { + DAMAGE_IMAGE *pdmg = &m_dmg[i]; + + if ( m_bitsDamage & giDmgFlags[i] ) + { + pdmg->fExpire = min( flTime + DMG_IMAGE_LIFE, pdmg->fExpire ); + + if ( pdmg->fExpire <= flTime // when the time has expired + && a < 40 ) // and the flash is at the low point of the cycle + { + pdmg->fExpire = 0; + + int y = pdmg->y; + pdmg->x = pdmg->y = 0; + + // move everyone above down + for (int j = 0; j < NUM_DMG_TYPES; j++) + { + pdmg = &m_dmg[j]; + if ((pdmg->y) && (pdmg->y < y)) + pdmg->y += giDmgHeight; + + } + + m_bitsDamage &= ~giDmgFlags[i]; // clear the bits + } + } + } + + return 1; +} + + +void CHudHealth::UpdateTiles(float flTime, long bitsDamage) +{ + DAMAGE_IMAGE *pdmg; + + // Which types are new? + long bitsOn = ~m_bitsDamage & bitsDamage; + + for (int i = 0; i < NUM_DMG_TYPES; i++) + { + pdmg = &m_dmg[i]; + + // Is this one already on? + if (m_bitsDamage & giDmgFlags[i]) + { + pdmg->fExpire = flTime + DMG_IMAGE_LIFE; // extend the duration + if (!pdmg->fBaseline) + pdmg->fBaseline = flTime; + } + + // Are we just turning it on? + if (bitsOn & giDmgFlags[i]) + { + // put this one at the bottom + pdmg->x = giDmgWidth/8; + pdmg->y = ScreenHeight - giDmgHeight * 2; + pdmg->fExpire=flTime + DMG_IMAGE_LIFE; + + // move everyone else up + for (int j = 0; j < NUM_DMG_TYPES; j++) + { + if (j == i) + continue; + + pdmg = &m_dmg[j]; + if (pdmg->y) + pdmg->y -= giDmgHeight; + + } + pdmg = &m_dmg[i]; + } + } + + // damage bits are only turned on here; they are turned off when the draw time has expired (in DrawDamage()) + m_bitsDamage |= bitsDamage; +} + diff --git a/dmc/cl_dll/health.h b/dmc/cl_dll/health.h new file mode 100644 index 0000000..5120103 --- /dev/null +++ b/dmc/cl_dll/health.h @@ -0,0 +1,130 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#define DMG_IMAGE_LIFE 2 // seconds that image is up + +#define DMG_IMAGE_POISON 0 +#define DMG_IMAGE_ACID 1 +#define DMG_IMAGE_COLD 2 +#define DMG_IMAGE_DROWN 3 +#define DMG_IMAGE_BURN 4 +#define DMG_IMAGE_NERVE 5 +#define DMG_IMAGE_RAD 6 +#define DMG_IMAGE_SHOCK 7 +//tf defines +#define DMG_IMAGE_CALTROP 8 +#define DMG_IMAGE_TRANQ 9 +#define DMG_IMAGE_CONCUSS 10 +#define DMG_IMAGE_HALLUC 11 +#define NUM_DMG_TYPES 12 +// instant damage + +#define DMG_GENERIC 0 // generic damage was done +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FREEZE (1 << 4) // frozen +#define DMG_FALL (1 << 5) // fell too far +#define DMG_BLAST (1 << 6) // explosive blast damage +#define DMG_CLUB (1 << 7) // crowbar, punch, headbutt +#define DMG_SHOCK (1 << 8) // electric shock +#define DMG_SONIC (1 << 9) // sound pulse shockwave +#define DMG_ENERGYBEAM (1 << 10) // laser or other high energy beam +#define DMG_NEVERGIB (1 << 12) // with this bit OR'd in, no damage type will be able to gib victims upon death +#define DMG_ALWAYSGIB (1 << 13) // with this bit OR'd in, any damage type can be made to gib victims upon death. + + +// time-based damage +//mask off TF-specific stuff too +#define DMG_TIMEBASED (~(0xff003fff)) // mask for time-based damage + + +#define DMG_DROWN (1 << 14) // Drowning +#define DMG_FIRSTTIMEBASED DMG_DROWN + +#define DMG_PARALYZE (1 << 15) // slows affected creature down +#define DMG_NERVEGAS (1 << 16) // nerve toxins, very bad +#define DMG_POISON (1 << 17) // blood poisioning +#define DMG_RADIATION (1 << 18) // radiation exposure +#define DMG_DROWNRECOVER (1 << 19) // drowning recovery +#define DMG_ACID (1 << 20) // toxic chemicals or acid burns +#define DMG_SLOWBURN (1 << 21) // in an oven +#define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer +#define DMG_MORTAR (1 << 23) // Hit by air raid (done to distinguish grenade from mortar) + +//TF ADDITIONS +#define DMG_IGNITE (1 << 24) // Players hit by this begin to burn +#define DMG_RADIUS_MAX (1 << 25) // Radius damage with this flag doesn't decrease over distance +#define DMG_RADIUS_QUAKE (1 << 26) // Radius damage is done like Quake. 1/2 damage at 1/2 radius. +#define DMG_IGNOREARMOR (1 << 27) // Damage ignores target's armor +#define DMG_AIMED (1 << 28) // Does Hit location damage +#define DMG_WALLPIERCING (1 << 29) // Blast Damages ents through walls + +#define DMG_CALTROP (1<<30) +#define DMG_HALLUC (1<<31) + +// TF Healing Additions for TakeHealth +#define DMG_IGNORE_MAXHEALTH DMG_IGNITE +// TF Redefines since we never use the originals +#define DMG_NAIL DMG_SLASH +#define DMG_NOT_SELF DMG_FREEZE + + +#define DMG_TRANQ DMG_MORTAR +#define DMG_CONCUSS DMG_SONIC + + + +typedef struct +{ + float fExpire; + float fBaseline; + int x, y; +} DAMAGE_IMAGE; + +// +//----------------------------------------------------- +// +class CHudHealth: public CHudBase +{ +public: + virtual int Init( void ); + virtual int VidInit( void ); + virtual int Draw(float fTime); + virtual void Reset( void ); + int MsgFunc_Health(const char *pszName, int iSize, void *pbuf); + int MsgFunc_Damage(const char *pszName, int iSize, void *pbuf); + int m_iHealth; + int m_HUD_dmg_bio; + int m_HUD_cross; + + void GetPainColor( int &r, int &g, int &b ); + float m_fFade; + +private: + HSPRITE m_hSprite; + HSPRITE m_hDamage; + + DAMAGE_IMAGE m_dmg[NUM_DMG_TYPES]; + int m_bitsDamage; + + + int DrawPain(float fTime); + int DrawDamage(float fTime); + float m_fAttackFront, m_fAttackRear, m_fAttackLeft, m_fAttackRight; + void CalcDamageDirection(vec3_t vecFrom); + void UpdateTiles(float fTime, long bits); +}; diff --git a/dmc/cl_dll/hud.cpp b/dmc/cl_dll/hud.cpp new file mode 100644 index 0000000..65fa852 --- /dev/null +++ b/dmc/cl_dll/hud.cpp @@ -0,0 +1,594 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud.cpp +// +// implementation of CHud class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" +#include "hud_servers.h" +#include "vgui_viewport.h" +#include "demo.h" +#include "demo_api.h" +#include "voice_status.h" +#include "vgui_ScorePanel.h" + + +class CDMCVoiceStatusHelper : public IVoiceStatusHelper +{ +public: + virtual void GetPlayerTextColor(int entindex, int color[3]) + { + color[0] = color[1] = color[2] = 255; + + if( entindex >= 0 && entindex < sizeof(g_PlayerExtraInfo)/sizeof(g_PlayerExtraInfo[0]) ) + { + int iTeam = g_PlayerExtraInfo[entindex].teamnumber; + + if( iTeam >= 0 && iTeam < sizeof(iTeamColors)/sizeof(iTeamColors[0]) ) + { + color[0] = iTeamColors[iTeam][0]; + color[1] = iTeamColors[iTeam][1]; + color[2] = iTeamColors[iTeam][2]; + } + } + } + + virtual void UpdateCursorState() + { + gViewPort->UpdateCursorState(); + } + + virtual int GetAckIconHeight() + { + return ScreenHeight - gHUD.m_iFontHeight*2 - 6; + } + + virtual bool CanShowSpeakerLabels() + { + if( gViewPort && gViewPort->m_pScoreBoard ) + return !gViewPort->m_pScoreBoard->isVisible(); + else + return false; + } +}; +static CDMCVoiceStatusHelper g_VoiceStatusHelper; + + + +extern client_sprite_t *GetSpriteList(client_sprite_t *pList, const char *psz, int iRes, int iCount); + +extern cvar_t *sensitivity; +cvar_t *cl_lw = NULL; +cvar_t *cl_autowepswitch; +cvar_t *cl_rollspeed; +cvar_t *cl_rollangle; +cvar_t *cl_fov; + +void ShutdownInput (void); + +void __CmdFunc_ToggleServerBrowser( void ) +{ + if ( gViewPort ) + { + gViewPort->ToggleServerBrowser(); + } +} + +//DECLARE_MESSAGE(m_Logo, Logo) +int __MsgFunc_Logo(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_Logo(pszName, iSize, pbuf ); +} + +//DECLARE_MESSAGE(m_Logo, Logo) +int __MsgFunc_ResetHUD(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_ResetHUD(pszName, iSize, pbuf ); +} + +int __MsgFunc_InitHUD(const char *pszName, int iSize, void *pbuf) +{ + gHUD.MsgFunc_InitHUD( pszName, iSize, pbuf ); + return 1; +} + +int __MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_SetFOV( pszName, iSize, pbuf ); +} + +int __MsgFunc_Concuss(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_Concuss( pszName, iSize, pbuf ); +} + +int __MsgFunc_GameMode(const char *pszName, int iSize, void *pbuf ) +{ + return gHUD.MsgFunc_GameMode( pszName, iSize, pbuf ); +} + +int __MsgFunc_MOTD(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_MOTD( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_ServerName(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_ServerName( pszName, iSize, pbuf ); + return 0; +} + +// QUAKECLASSIC +int __MsgFunc_QItems(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_QItems( pszName, iSize, pbuf ); +} + +int __MsgFunc_ScoreInfo(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_ScoreInfo( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_TeamInfo(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_TeamInfo( pszName, iSize, pbuf ); + return 0; +} + +void __CmdFunc_OpenCommandMenu(void) +{ + if ( gViewPort ) + { + gViewPort->ShowCommandMenu( gViewPort->m_StandardMenu ); + } +} + +void __CmdFunc_CloseCommandMenu(void) +{ + if ( gViewPort ) + { + gViewPort->InputSignalHideCommandMenu(); + } +} + +void __CmdFunc_ForceCloseCommandMenu( void ) +{ + if ( gViewPort ) + { + gViewPort->HideCommandMenu(); + } +} + +// This is called every time the DLL is loaded +void CHud :: Init( void ) +{ + HOOK_MESSAGE( Logo ); + HOOK_MESSAGE( ResetHUD ); + HOOK_MESSAGE( GameMode ); + HOOK_MESSAGE( InitHUD ); + HOOK_MESSAGE( SetFOV ); + HOOK_MESSAGE( Concuss ); + + HOOK_MESSAGE( MOTD ); + HOOK_MESSAGE( ServerName ); + + HOOK_COMMAND( "togglebrowser", ToggleServerBrowser ); + + HOOK_COMMAND( "+commandmenu", OpenCommandMenu ); + HOOK_COMMAND( "-commandmenu", CloseCommandMenu ); + HOOK_COMMAND( "ForceCloseCommandMenu", ForceCloseCommandMenu ); + + // QUAKECLASSIC + HOOK_MESSAGE( QItems ); + HOOK_MESSAGE( ScoreInfo ); + //HOOK_MESSAGE( TeamScore ); + HOOK_MESSAGE( TeamInfo ); + + m_iLogo = 0; + m_iFOV = 0; + + CVAR_CREATE( "zoom_sensitivity_ratio", "1.2", 0 ); + default_fov = CVAR_CREATE( "default_fov", "90", 0 ); + cl_lw = gEngfuncs.pfnGetCvarPointer( "cl_lw" ); + m_pCvarStealMouse = CVAR_CREATE( "hud_capturemouse", "1", FCVAR_ARCHIVE ); + m_pCvarDraw = CVAR_CREATE( "hud_draw", "1", FCVAR_ARCHIVE ); + /************************ CLIENT CVAR DEFINITIONS ************************/ + cl_autowepswitch = gEngfuncs.pfnRegisterVariable ( "cl_autowepswitch", "2", FCVAR_USERINFO|FCVAR_ARCHIVE ); + cl_rollangle = gEngfuncs.pfnRegisterVariable ( "cl_rollangle", "0.65", FCVAR_CLIENTDLL|FCVAR_ARCHIVE ); + cl_rollspeed = gEngfuncs.pfnRegisterVariable ( "cl_rollspeed", "300", FCVAR_CLIENTDLL|FCVAR_ARCHIVE ); + cl_fov = gEngfuncs.pfnRegisterVariable ( "cl_fov", "90", FCVAR_USERINFO|FCVAR_ARCHIVE ); + /************************ CLIENT CVAR DEFINITIONS ************************/ + + m_pSpriteList = NULL; + + // Clear any old HUD list + if ( m_pHudList ) + { + HUDLIST *pList; + while ( m_pHudList ) + { + pList = m_pHudList; + m_pHudList = m_pHudList->pNext; + free( pList ); + } + m_pHudList = NULL; + } + + // In case we get messages before the first update -- time will be valid + m_flTime = 1.0; + + m_Ammo.Init(); + m_Health.Init(); + m_SayText.Init(); + m_Spectator.Init(); + m_Geiger.Init(); + m_Train.Init(); + m_Battery.Init(); + m_Message.Init(); +// m_Scoreboard.Init(); +// m_MOTD.Init(); + m_StatusBar.Init(); + m_DeathNotice.Init(); + m_AmmoSecondary.Init(); + m_TextMessage.Init(); + m_StatusIcons.Init(); + + GetClientVoiceMgr()->Init(&g_VoiceStatusHelper, (vgui::Panel**)&gViewPort); + + + m_Menu.Init(); + + ServersInit(); + + MsgFunc_ResetHUD(0, 0, NULL ); +} + +CHud::CHud() : m_iSpriteCount(0), m_pHudList(NULL) +{ +} + +// CHud destructor +// cleans up memory allocated for m_rg* arrays +CHud :: ~CHud() +{ + delete [] m_rghSprites; + delete [] m_rgrcRects; + delete [] m_rgszSpriteNames; + + if ( m_pHudList ) + { + HUDLIST *pList; + while ( m_pHudList ) + { + pList = m_pHudList; + m_pHudList = m_pHudList->pNext; + free( pList ); + } + m_pHudList = NULL; + } + + ServersShutdown(); +} + +// GetSpriteIndex() +// searches through the sprite list loaded from hud.txt for a name matching SpriteName +// returns an index into the gHUD.m_rghSprites[] array +// returns 0 if sprite not found +int CHud :: GetSpriteIndex( const char *SpriteName ) +{ + // look through the loaded sprite name list for SpriteName + for ( int i = 0; i < m_iSpriteCount; i++ ) + { + if ( strncmp( SpriteName, m_rgszSpriteNames + (i * MAX_SPRITE_NAME_LENGTH), MAX_SPRITE_NAME_LENGTH ) == 0 ) + return i; + } + + return -1; // invalid sprite +} + +void CHud :: VidInit( void ) +{ + m_scrinfo.iSize = sizeof(m_scrinfo); + GetScreenInfo(&m_scrinfo); + + // ---------- + // Load Sprites + // --------- +// m_hsprFont = LoadSprite("sprites/%d_font.spr"); + + m_hsprLogo = 0; + m_hsprCursor = 0; + + if (ScreenWidth < 640) + m_iRes = 320; + else + m_iRes = 640; + + // Only load this once + if ( !m_pSpriteList ) + { + // we need to load the hud.txt, and all sprites within + m_pSpriteList = SPR_GetList("sprites/hud.txt", &m_iSpriteCountAllRes); + + if (m_pSpriteList) + { + // count the number of sprites of the appropriate res + m_iSpriteCount = 0; + client_sprite_t *p = m_pSpriteList; + int j; + for ( j = 0; j < m_iSpriteCountAllRes; j++ ) + { + if ( p->iRes == m_iRes ) + m_iSpriteCount++; + p++; + } + + // allocated memory for sprite handle arrays + m_rghSprites = new HSPRITE[m_iSpriteCount]; + m_rgrcRects = new wrect_t[m_iSpriteCount]; + m_rgszSpriteNames = new char[m_iSpriteCount * MAX_SPRITE_NAME_LENGTH]; + + p = m_pSpriteList; + int index = 0; + for ( j = 0; j < m_iSpriteCountAllRes; j++ ) + { + if ( p->iRes == m_iRes ) + { + char sz[256]; + sprintf(sz, "sprites/%s.spr", p->szSprite); + m_rghSprites[index] = SPR_Load(sz); + m_rgrcRects[index] = p->rc; + strncpy( &m_rgszSpriteNames[index * MAX_SPRITE_NAME_LENGTH], p->szName, MAX_SPRITE_NAME_LENGTH ); + + index++; + } + + p++; + } + } + } + else + { + // we have already have loaded the sprite reference from hud.txt, but + // we need to make sure all the sprites have been loaded (we've gone through a transition, or loaded a save game) + client_sprite_t *p = m_pSpriteList; + int index = 0; + for ( int j = 0; j < m_iSpriteCountAllRes; j++ ) + { + if ( p->iRes == m_iRes ) + { + char sz[256]; + sprintf( sz, "sprites/%s.spr", p->szSprite ); + m_rghSprites[index] = SPR_Load(sz); + index++; + } + + p++; + } + } + + // assumption: number_1, number_2, etc, are all listed and loaded sequentially + m_HUD_number_0 = GetSpriteIndex( "number_0" ); + + m_iFontHeight = m_rgrcRects[m_HUD_number_0].bottom - m_rgrcRects[m_HUD_number_0].top; + + m_Ammo.VidInit(); + m_Health.VidInit(); + m_Spectator.VidInit(); + m_Geiger.VidInit(); + m_Train.VidInit(); + m_Battery.VidInit(); + m_Message.VidInit(); +// m_Scoreboard.VidInit(); +// m_MOTD.VidInit(); + m_StatusBar.VidInit(); + m_DeathNotice.VidInit(); + m_SayText.VidInit(); + m_Menu.VidInit(); + m_AmmoSecondary.VidInit(); + m_TextMessage.VidInit(); + m_StatusIcons.VidInit(); + + GetClientVoiceMgr()->VidInit(); +} + +int CHud::MsgFunc_Logo(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + // update Train data + m_iLogo = READ_BYTE(); + + return 1; +} + +float g_lastFOV = 0.0; + +/* +============ +COM_FileBase +============ +*/ +// Extracts the base name of a file (no path, no extension, assumes '/' as path separator) +void COM_FileBase ( const char *in, char *out) +{ + int len, start, end; + + len = strlen( in ); + + // scan backward for '.' + end = len - 1; + while ( end && in[end] != '.' && in[end] != '/' && in[end] != '\\' ) + end--; + + if ( in[end] != '.' ) // no '.', copy to end + end = len-1; + else + end--; // Found ',', copy to left of '.' + + + // Scan backward for '/' + start = len-1; + while ( start >= 0 && in[start] != '/' && in[start] != '\\' ) + start--; + + if ( in[start] != '/' && in[start] != '\\' ) + start = 0; + else + start++; + + // Length of new sting + len = end - start + 1; + + // Copy partial string + strncpy( out, &in[start], len ); + // Terminate it + out[len] = 0; +} + +/* +================= +HUD_IsGame + +================= +*/ +int HUD_IsGame( const char *game ) +{ + const char *gamedir; + char gd[ 1024 ]; + + gamedir = gEngfuncs.pfnGetGameDirectory(); + if ( gamedir && gamedir[0] ) + { + COM_FileBase( gamedir, gd ); + if ( !stricmp( gd, game ) ) + return 1; + } + return 0; +} + +/* +===================== +HUD_GetFOV + +Returns last FOV +===================== +*/ +float HUD_GetFOV( void ) +{ + /* + if ( gEngfuncs.pDemoAPI->IsRecording() ) + { + // Write it + int i = 0; + unsigned char buf[ 100 ]; + + // Active + *( float * )&buf[ i ] = g_lastFOV; + i += sizeof( float ); + + Demo_WriteBuffer( TYPE_ZOOM, i, buf ); + } + + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + { + g_lastFOV = g_demozoom; + } + */ + return g_lastFOV; +} + +int CHud::MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + int newfov = READ_BYTE(); + int def_fov = CVAR_GET_FLOAT( "default_fov" ); + + if ( newfov == 0 ) + { + m_iFOV = def_fov; + } + else + { + m_iFOV = newfov; + } + + // the clients fov is actually set in the client data update section of the hud + + // Set a new sensitivity + if ( m_iFOV == def_fov ) + { + // reset to saved sensitivity + m_flMouseSensitivity = 0; + } + else + { + // set a new sensitivity that is proportional to the change from the FOV default + m_flMouseSensitivity = sensitivity->value * ((float)newfov / (float)def_fov) * CVAR_GET_FLOAT("zoom_sensitivity_ratio"); + } + + return 1; +} + + +void CHud::AddHudElem(CHudBase *phudelem) +{ + HUDLIST *pdl, *ptemp; + +//phudelem->Think(); + + if (!phudelem) + return; + + pdl = (HUDLIST *)malloc(sizeof(HUDLIST)); + if (!pdl) + return; + + memset(pdl, 0, sizeof(HUDLIST)); + pdl->p = phudelem; + + if (!m_pHudList) + { + m_pHudList = pdl; + return; + } + + ptemp = m_pHudList; + + while (ptemp->pNext) + ptemp = ptemp->pNext; + + ptemp->pNext = pdl; +} + +float CHud::GetSensitivity( void ) +{ + return m_flMouseSensitivity; +} + + diff --git a/dmc/cl_dll/hud.h b/dmc/cl_dll/hud.h new file mode 100644 index 0000000..dd74d36 --- /dev/null +++ b/dmc/cl_dll/hud.h @@ -0,0 +1,659 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud.h +// +// class CHud declaration +// +// CHud handles the message, calculation, and drawing the HUD +// + + +#define RGB_YELLOWISH 0x00FFA000 //255,160,0 +#define RGB_REDISH 0x00FF1010 //255,160,0 +#define RGB_GREENISH 0x0000A000 //0,160,0 +#define RGB_NORMAL 0x00FFFFFF // 255,255,255 + +#include "wrect.h" + +#include "cl_dll.h" +#include "ammo.h" + +#define DHN_DRAWZERO 1 +#define DHN_2DIGITS 2 +#define DHN_3DIGITS 4 +#define MIN_ALPHA 100 + +#define HUDELEM_ACTIVE 1 + +typedef struct { + int x, y; +} POSITION; + +typedef struct { + unsigned char r,g,b,a; +} RGBA; +typedef struct cvar_s cvar_t; + + +#define HUD_ACTIVE 1 +#define HUD_INTERMISSION 2 + +#define MAX_PLAYER_NAME_LENGTH 32 + +#define MAX_MOTD_LENGTH 1024 + +#ifndef _WIN32 +#define _cdecl +#endif + +enum +{ + MAX_PLAYERS = 64, + MAX_TEAMS = 64, + MAX_TEAM_NAME = 16, +}; + +struct extra_player_info_t +{ + short frags; + short deaths; + short teamnumber; + char teamname[MAX_TEAM_NAME]; +}; + +struct team_info_t +{ + char name[MAX_TEAM_NAME]; + short frags; + short deaths; + short ping; + short packetloss; + short ownteam; + short players; + int already_drawn; + int scores_overriden; + int teamnumber; +}; + +extern hud_player_info_t g_PlayerInfoList[MAX_PLAYERS+1]; // player info from the engine +extern extra_player_info_t g_PlayerExtraInfo[MAX_PLAYERS+1]; // additional player info sent directly to the client dll +extern team_info_t g_TeamInfo[MAX_TEAMS+1]; + +// +//----------------------------------------------------- +// +class CHudBase +{ +public: + POSITION m_pos; + int m_type; + int m_iFlags; // active, moving, + virtual int Init( void ) {return 0;} + virtual int VidInit( void ) {return 0;} + virtual int Draw(float flTime) {return 0;} + virtual void Think(void) {return;} + virtual void Reset(void) {return;} + virtual void InitHUDData( void ) {} // called every time a server is connected to + +}; + +struct HUDLIST { + CHudBase *p; + HUDLIST *pNext; +}; + + +#include "hud_spectator.h" +// +//----------------------------------------------------- +// +class CHudAmmo: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + void Think(void); + void Reset(void); + int DrawWList(float flTime); + int DrawFastList(float flTime); + float m_flFastListTime; + int MsgFunc_CurWeapon(const char *pszName, int iSize, void *pbuf); + int MsgFunc_WeaponList(const char *pszName, int iSize, void *pbuf); + int MsgFunc_AmmoX(const char *pszName, int iSize, void *pbuf); + int MsgFunc_AmmoPickup( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_WeapPickup( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_ItemPickup( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_HideWeapon( const char *pszName, int iSize, void *pbuf ); + + void _cdecl UserCmd_Slot1( void ); + void _cdecl UserCmd_Slot2( void ); + void _cdecl UserCmd_Slot3( void ); + void _cdecl UserCmd_Slot4( void ); + void _cdecl UserCmd_Slot5( void ); + void _cdecl UserCmd_Slot6( void ); + void _cdecl UserCmd_Slot7( void ); + void _cdecl UserCmd_Slot8( void ); + void _cdecl UserCmd_Slot9( void ); + void _cdecl UserCmd_Slot10( void ); + void _cdecl UserCmd_Close( void ); + void _cdecl UserCmd_NextWeapon( void ); + void _cdecl UserCmd_PrevWeapon( void ); + + int m_iXPosition; + int m_iNumberXPosition; + HSPRITE m_sprAmmoSprite; + int m_HUD_Ammo; + wrect_t *m_prc1; + +private: + float m_fFade; + RGBA m_rgba; + WEAPON *m_pWeapon; + int m_HUD_bucket0; + int m_HUD_selection; + + +}; + +// +//----------------------------------------------------- +// + +class CHudAmmoSecondary: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + void Reset( void ); + int Draw(float flTime); + + int MsgFunc_SecAmmoVal( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_SecAmmoIcon( const char *pszName, int iSize, void *pbuf ); + +private: + enum { + MAX_SEC_AMMO_VALUES = 4 + }; + + int m_HUD_ammoicon; // sprite indices + int m_iAmmoAmounts[MAX_SEC_AMMO_VALUES]; + float m_fFade; +}; + + +#include "health.h" + + +#define FADE_TIME 100 + +// +//----------------------------------------------------- +// +class CHudGeiger: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_Geiger(const char *pszName, int iSize, void *pbuf); + +private: + int m_iGeigerRange; + +}; + +// +//----------------------------------------------------- +// +class CHudTrain: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_Train(const char *pszName, int iSize, void *pbuf); + +private: + HSPRITE m_hSprite; + int m_iPos; + +}; +// +//----------------------------------------------------- +// +// REMOVED: Vgui has replaced this. +// +// +//----------------------------------------------------- +/* +class CHudMOTD : public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw( float flTime ); + void Reset( void ); + + int MsgFunc_MOTD( const char *pszName, int iSize, void *pbuf ); + +protected: + static int MOTD_DISPLAY_TIME; + char m_szMOTD[ MAX_MOTD_LENGTH ]; + float m_flActiveRemaining; + int m_iLines; +}; +*/ +// +//----------------------------------------------------- +// +class CHudStatusBar : public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw( float flTime ); + void Reset( void ); + void ParseStatusString( int line_num ); + + int MsgFunc_StatusText( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_StatusValue( const char *pszName, int iSize, void *pbuf ); + +protected: + enum { + MAX_STATUSTEXT_LENGTH = 128, + MAX_STATUSBAR_VALUES = 8, + MAX_STATUSBAR_LINES = 2, + }; + + HSPRITE m_hArmor; + HSPRITE m_hHealth; + int m_iArmorSpriteIndex; + int m_iHealthSpriteIndex; + + + char m_szStatusText[MAX_STATUSBAR_LINES][MAX_STATUSTEXT_LENGTH]; // a text string describing how the status bar is to be drawn + char m_szStatusBar[MAX_STATUSBAR_LINES][MAX_STATUSTEXT_LENGTH]; // the constructed bar that is drawn + int m_iStatusValues[MAX_STATUSBAR_VALUES]; // an array of values for use in the status bar + + char m_szName[MAX_STATUSBAR_LINES][MAX_STATUSTEXT_LENGTH]; + char m_szHealth[MAX_STATUSBAR_LINES][MAX_STATUSTEXT_LENGTH]; + char m_szArmor[MAX_STATUSBAR_LINES][MAX_STATUSTEXT_LENGTH]; + + int m_iTeamMate[MAX_STATUSBAR_LINES]; + + int m_bReparseString; // set to TRUE whenever the m_szStatusBar needs to be recalculated +}; + +// +//----------------------------------------------------- +// + +/*class CHudScoreboard: public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + int Draw( float flTime ); + int DrawPlayers( int xoffset, float listslot, int nameoffset = 0, char *team = NULL ); // returns the ypos where it finishes drawing + void UserCmd_ShowScores( void ); + void UserCmd_HideScores( void ); + int MsgFunc_ScoreInfo( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_TeamInfo( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_TeamScore( const char *pszName, int iSize, void *pbuf ); + void DeathMsg( int killer, int victim ); + + + + int m_iNumTeams; + + int m_iLastKilledBy; + int m_fLastKillTime; + int m_iPlayerNum; + int m_iShowscoresHeld; + + void GetAllPlayersInfo( void ); + + + +}; + +*/ + +// +//----------------------------------------------------- +// +class CHudDeathNotice : public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + int Draw( float flTime ); + int MsgFunc_DeathMsg( const char *pszName, int iSize, void *pbuf ); + +private: + int m_HUD_d_skull; // sprite index of skull icon + + char szText[256][4]; +}; + +// +//----------------------------------------------------- +// +class CHudMenu : public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + void Reset( void ); + int Draw( float flTime ); + int MsgFunc_ShowMenu( const char *pszName, int iSize, void *pbuf ); + + void SelectMenuItem( int menu_item ); + + int m_fMenuDisplayed; + int m_bitsValidSlots; + float m_flShutoffTime; + int m_fWaitingForMore; +}; + +// +//----------------------------------------------------- +// +class CHudSayText : public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + int Draw( float flTime ); + int MsgFunc_SayText( const char *pszName, int iSize, void *pbuf ); + void SayTextPrint( const char *pszBuf, int iBufSize, int clientIndex = -1 ); + void EnsureTextFitsInOneLineAndWrapIfHaveTo( int line ); + + struct cvar_s * m_HUD_saytext; + struct cvar_s * m_HUD_saytext_time; +}; + +// +//----------------------------------------------------- +// +class CHudBattery: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_Battery(const char *pszName, int iSize, void *pbuf ); + +private: + HSPRITE m_hSprite1; + HSPRITE m_hSprite2; + wrect_t *m_prc1; + wrect_t *m_prc2; + int m_iBat; + float m_fFade; + int m_iHeight; // width of the battery innards +}; + + +// +//----------------------------------------------------- +// +const int maxHUDMessages = 16; +struct message_parms_t +{ + client_textmessage_t *pMessage; + float time; + int x, y; + int totalWidth, totalHeight; + int width; + int lines; + int lineLength; + int length; + int r, g, b; + int text; + int fadeBlend; + float charTime; + float fadeTime; +}; + +// +//----------------------------------------------------- +// + +class CHudTextMessage: public CHudBase +{ +public: + int Init( void ); + static char *LocaliseTextString( const char *msg, char *dst_buffer, int buffer_size ); + static char *BufferedLocaliseTextString( const char *msg ); + char *LookupString( const char *msg_name, int *msg_dest = NULL ); + int MsgFunc_TextMsg(const char *pszName, int iSize, void *pbuf); +}; + +// +//----------------------------------------------------- +// + +class CHudMessage: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_HudText(const char *pszName, int iSize, void *pbuf); + int MsgFunc_GameTitle(const char *pszName, int iSize, void *pbuf); + + float FadeBlend( float fadein, float fadeout, float hold, float localTime ); + int XPosition( float x, int width, int lineWidth ); + int YPosition( float y, int height ); + + void MessageAdd( const char *pName, float time ); + void MessageAdd(client_textmessage_t * newMessage ); + void MessageDrawScan( client_textmessage_t *pMessage, float time ); + void MessageScanStart( void ); + void MessageScanNextChar( void ); + void Reset( void ); + +private: + client_textmessage_t *m_pMessages[maxHUDMessages]; + float m_startTime[maxHUDMessages]; + message_parms_t m_parms; + float m_gameTitleTime; + client_textmessage_t *m_pGameTitle; + + int m_HUD_title_life; + int m_HUD_title_half; +}; + +// +//----------------------------------------------------- +// +#define MAX_SPRITE_NAME_LENGTH 24 + +class CHudStatusIcons: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + void Reset( void ); + int Draw(float flTime); + int MsgFunc_StatusIcon(const char *pszName, int iSize, void *pbuf); + + enum { + MAX_ICONSPRITENAME_LENGTH = MAX_SPRITE_NAME_LENGTH, + MAX_ICONSPRITES = 4, + }; + + + //had to make these public so CHud could access them (to enable concussion icon) + //could use a friend declaration instead... + void EnableIcon( char *pszIconName, unsigned char red, unsigned char green, unsigned char blue ); + void DisableIcon( char *pszIconName ); + +private: + + typedef struct + { + char szSpriteName[MAX_ICONSPRITENAME_LENGTH]; + HSPRITE spr; + wrect_t rc; + unsigned char r, g, b; + } icon_sprite_t; + + icon_sprite_t m_IconList[MAX_ICONSPRITES]; + +}; + + +// +//----------------------------------------------------- +// +class CVoiceStatus; +typedef struct cvar_s cvar_t; + +class CHud +{ +private: + HUDLIST *m_pHudList; + HSPRITE m_hsprLogo; + int m_iLogo; + client_sprite_t *m_pSpriteList; + int m_iSpriteCount; + int m_iSpriteCountAllRes; + float m_flMouseSensitivity; + int m_iConcussionEffect; + +public: + + HSPRITE m_hsprCursor; + float m_flTime; // the current client time + float m_fOldTime; // the time at which the HUD was last redrawn + double m_flTimeDelta; // the difference between flTime and fOldTime + Vector m_vecOrigin; + Vector m_vecAngles; + int m_iKeyBits; + int m_iHideHUDDisplay; + int m_iFOV; + int m_Teamplay; + int m_iRes; + cvar_t *m_pCvarStealMouse; + cvar_t *m_pCvarDraw; + + // QUAKECLASSIC + int m_iQuakeItems; + + int m_iFontHeight; + int DrawHudNumber(int x, int y, int iFlags, int iNumber, int r, int g, int b ); + int DrawHudString(int x, int y, int iMaxX, char *szString, int r, int g, int b ); + + int DrawHudStringCTF(int x, int y, int iMaxX, char *szString, int r, int g, int b ); + + int ReturnStringPixelLength ( char *Hihi ); + + int DrawHudStringReverse( int xpos, int ypos, int iMinX, char *szString, int r, int g, int b ); + int DrawHudNumberString( int xpos, int ypos, int iMinX, int iNumber, int r, int g, int b ); + int GetNumWidth(int iNumber, int iFlags); + +private: + // the memory for these arrays are allocated in the first call to CHud::VidInit(), when the hud.txt and associated sprites are loaded. + // freed in ~CHud() + HSPRITE *m_rghSprites; /*[HUD_SPRITE_COUNT]*/ // the sprites loaded from hud.txt + wrect_t *m_rgrcRects; /*[HUD_SPRITE_COUNT]*/ + char *m_rgszSpriteNames; /*[HUD_SPRITE_COUNT][MAX_SPRITE_NAME_LENGTH]*/ + + struct cvar_s *default_fov; +public: + HSPRITE GetSprite( int index ) + { + return (index < 0) ? 0 : m_rghSprites[index]; + } + + wrect_t& GetSpriteRect( int index ) + { + return m_rgrcRects[index]; + } + + + int GetSpriteIndex( const char *SpriteName ); // gets a sprite index, for use in the m_rghSprites[] array + + CHudAmmo m_Ammo; + CHudHealth m_Health; + CHudSpectator m_Spectator; + CHudGeiger m_Geiger; + CHudBattery m_Battery; + CHudTrain m_Train; + CHudMessage m_Message; +// CHudScoreboard m_Scoreboard; +// CHudMOTD m_MOTD; + CHudStatusBar m_StatusBar; + CHudDeathNotice m_DeathNotice; + CHudSayText m_SayText; + CHudMenu m_Menu; + CHudAmmoSecondary m_AmmoSecondary; + CHudTextMessage m_TextMessage; + CHudStatusIcons m_StatusIcons; + + void Init( void ); + void VidInit( void ); + void Think(void); + int Redraw( float flTime, int intermission ); + int UpdateClientData( client_data_t *cdata, float time ); + + CHud(); + ~CHud(); // destructor, frees allocated memory + + // user messages + int _cdecl MsgFunc_Damage(const char *pszName, int iSize, void *pbuf ); + int _cdecl MsgFunc_GameMode(const char *pszName, int iSize, void *pbuf ); + int _cdecl MsgFunc_Logo(const char *pszName, int iSize, void *pbuf); + int _cdecl MsgFunc_ResetHUD(const char *pszName, int iSize, void *pbuf); + void _cdecl MsgFunc_InitHUD( const char *pszName, int iSize, void *pbuf ); + int _cdecl MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf); + int _cdecl MsgFunc_Concuss( const char *pszName, int iSize, void *pbuf ); + + // QUAKECLASSIC + int _cdecl MsgFunc_QItems( const char *pszName, int iSize, void *pbuf ); + + // Screen information + SCREENINFO m_scrinfo; + + int m_iWeaponBits; + int m_fPlayerDead; + int m_iIntermission; + + // sprite indexes + int m_HUD_number_0; + + + void AddHudElem(CHudBase *p); + + float GetSensitivity(); +}; + +class TeamFortressViewport; + +extern CHud gHUD; +extern TeamFortressViewport *gViewPort; + +extern int g_iUser1; +extern int g_iUser2; +extern int g_iUser3; + diff --git a/dmc/cl_dll/hud_iface.h b/dmc/cl_dll/hud_iface.h new file mode 100644 index 0000000..f3d9356 --- /dev/null +++ b/dmc/cl_dll/hud_iface.h @@ -0,0 +1,24 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( HUD_IFACEH ) +#define HUD_IFACEH +#pragma once + +#ifdef _WIN32 +#define EXPORT _declspec( dllexport ) +#else +#define EXPORT __attribute__ ((visibility("default"))) +#endif +#define _DLLEXPORT EXPORT + +typedef int (*pfnUserMsgHook)(const char *pszName, int iSize, void *pbuf); +#include "wrect.h" +#include "../engine/cdll_int.h" +extern cl_enginefunc_t gEngfuncs; + +#endif \ No newline at end of file diff --git a/dmc/cl_dll/hud_msg.cpp b/dmc/cl_dll/hud_msg.cpp new file mode 100644 index 0000000..1be43ea --- /dev/null +++ b/dmc/cl_dll/hud_msg.cpp @@ -0,0 +1,171 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud_msg.cpp +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +extern float g_flLightTime; + +#define MAX_TELES 256 +Vector g_vecTeleMins[ MAX_TELES ]; +Vector g_vecTeleMaxs[ MAX_TELES ]; +int g_iTeleNum; +bool g_bLoadedTeles; + +float g_iFogColor[3]; +float g_iStartDist; +float g_iEndDist; + +/// USER-DEFINED SERVER MESSAGE HANDLERS + +int CHud :: MsgFunc_ResetHUD(const char *pszName, int iSize, void *pbuf ) +{ + ASSERT( iSize == 0 ); + + // clear all hud data + HUDLIST *pList = m_pHudList; + + while ( pList ) + { + if ( pList->p ) + pList->p->Reset(); + pList = pList->pNext; + } + + // reset sensitivity + m_flMouseSensitivity = 0; + + // reset concussion effect + m_iConcussionEffect = 0; + + g_flLightTime = 0.0; + + return 1; +} + +void CHud :: MsgFunc_InitHUD( const char *pszName, int iSize, void *pbuf ) +{ + g_iTeleNum = 0; + g_bLoadedTeles = false; + int i; + + //Clear all the teleporters + for ( i = 0; i < MAX_TELES; i++ ) + { + g_vecTeleMins[ i ].x = 0.0; + g_vecTeleMins[ i ].y = 0.0; + g_vecTeleMins[ i ].z = 0.0; + + g_vecTeleMaxs[ i ].x = 0.0; + g_vecTeleMaxs[ i ].y = 0.0; + g_vecTeleMaxs[ i ].z = 0.0; + } + + /***** FOG CLEARING JIBBA JABBA *****/ + for ( i = 0; i < 3; i++ ) + g_iFogColor[ i ] = 0.0; + + g_iStartDist = 0.0; + g_iEndDist = 0.0; + /***** FOG CLEARING JIBBA JABBA *****/ + + // prepare all hud data + HUDLIST *pList = m_pHudList; + + while (pList) + { + if ( pList->p ) + pList->p->InitHUDData(); + pList = pList->pNext; + } + + BEGIN_READ( pbuf, iSize ); + g_iTeleNum = READ_BYTE(); + + for ( i = 0; i < g_iTeleNum; i++ ) + { + g_vecTeleMins[ i ].x = READ_COORD(); + g_vecTeleMins[ i ].y = READ_COORD(); + g_vecTeleMins[ i ].z = READ_COORD(); + g_vecTeleMaxs[ i ].x = READ_COORD(); + g_vecTeleMaxs[ i ].y = READ_COORD(); + g_vecTeleMaxs[ i ].z = READ_COORD(); + } + + for ( i = 0; i < 3; i++ ) + g_iFogColor[ i ] = READ_SHORT(); // Should just get a byte. + + //If they both are 0, it means no fog for this level. + g_iStartDist = READ_SHORT(); + g_iEndDist = READ_SHORT(); +} + + +int CHud :: MsgFunc_GameMode(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + m_Teamplay = READ_BYTE(); + + return 1; +} + + +int CHud :: MsgFunc_Damage(const char *pszName, int iSize, void *pbuf ) +{ + int armor, blood; + Vector from; + int i; + float count; + + BEGIN_READ( pbuf, iSize ); + armor = READ_BYTE(); + blood = READ_BYTE(); + + for (i=0 ; i<3 ; i++) + from[i] = READ_COORD(); + + count = (blood * 0.5) + (armor * 0.5); + + if (count < 10) + count = 10; + + // TODO: kick viewangles, show damage visually + + return 1; +} + +int CHud :: MsgFunc_Concuss( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + m_iConcussionEffect = READ_BYTE(); + if (m_iConcussionEffect) + this->m_StatusIcons.EnableIcon("dmg_concuss",255,160,0); + else + this->m_StatusIcons.DisableIcon("dmg_concuss"); + return 1; +} + +// QUAKECLASSIC +int CHud :: MsgFunc_QItems(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + m_iQuakeItems = READ_LONG(); + + return 1; +} \ No newline at end of file diff --git a/dmc/cl_dll/hud_redraw.cpp b/dmc/cl_dll/hud_redraw.cpp new file mode 100644 index 0000000..444343a --- /dev/null +++ b/dmc/cl_dll/hud_redraw.cpp @@ -0,0 +1,447 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud_redraw.cpp +// +#include +#include "hud.h" +#include "cl_util.h" +#include +#include "vgui_viewport.h" + +extern int g_iVisibleMouse; + +#define MAX_LOGO_FRAMES 56 + +int grgLogoFrame[MAX_LOGO_FRAMES] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 13, 13, 13, 13, 13, 12, 11, 10, 9, 8, 14, 15, + 16, 17, 18, 19, 20, 20, 20, 20, 20, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 29, 29, 29, 29, 29, 28, 27, 26, 25, 24, 30, 31 +}; + + +// Think +void CHud::Think(void) +{ + m_scrinfo.iSize = sizeof(m_scrinfo); + GetScreenInfo(&m_scrinfo); + + HUDLIST *pList = m_pHudList; + while (pList) + { + if (pList->p->m_iFlags & HUD_ACTIVE) + pList->p->Think(); + pList = pList->pNext; + } + + // think about default fov + if ( m_iFOV == 0 ) + { // only let players adjust up in fov, and only if they are not overriden by something else + m_iFOV = max( default_fov->value, 90 ); + } + + +} + +// Redraw +// step through the local data, placing the appropriate graphics & text as appropriate +// returns 1 if they've changed, 0 otherwise +int CHud :: Redraw( float flTime, int intermission ) +{ + m_fOldTime = m_flTime; // save time of previous redraw + m_flTime = flTime; + m_flTimeDelta = (double)m_flTime - m_fOldTime; + static float m_flShotTime = 0; + + // Clock was reset, reset delta + if ( m_flTimeDelta < 0 ) + m_flTimeDelta = 0; + + // Bring up the scoreboard during intermission + if (gViewPort) + { + if ( m_iIntermission && !intermission ) + { + // Have to do this here so the scoreboard goes away + m_iIntermission = intermission; + gViewPort->HideCommandMenu(); + gViewPort->HideScoreBoard(); + gViewPort->UpdateSpectatorPanel(); + } + else if ( !m_iIntermission && intermission ) + { + m_iIntermission = intermission; + gViewPort->HideCommandMenu(); + gViewPort->HideVGUIMenu(); + gViewPort->ShowScoreBoard(); + gViewPort->UpdateSpectatorPanel(); + + // Take a screenshot if the client's got the cvar set + if ( CVAR_GET_FLOAT( "hud_takesshots" ) != 0 ) + m_flShotTime = flTime + 1.0; // Take a screenshot in a second + } + } + + if (m_flShotTime && m_flShotTime < flTime) + { + gEngfuncs.pfnClientCmd("snapshot\n"); + m_flShotTime = 0; + } + // if no redrawing is necessary + // return 0; + + // draw all registered HUD elements + if ( m_pCvarDraw->value ) + { + HUDLIST *pList = m_pHudList; + + while (pList) + { + if ( !intermission ) + { + if ((pList->p->m_iFlags & HUD_ACTIVE) && !(m_iHideHUDDisplay & HIDEHUD_ALL)) + pList->p->Draw(flTime); + } + else + { // it's an intermission, so only draw hud elements that are set to draw during intermissions + if ( pList->p->m_iFlags & HUD_INTERMISSION ) + pList->p->Draw( flTime ); + } + + pList = pList->pNext; + } + } + + // are we in demo mode? do we need to draw the logo in the top corner? + if (m_iLogo) + { + int x, y, i; + + if (m_hsprLogo == 0) + m_hsprLogo = LoadSprite("sprites/%d_logo.spr"); + + SPR_Set(m_hsprLogo, 250, 250, 250 ); + + x = SPR_Width(m_hsprLogo, 0); + x = ScreenWidth - x; + y = SPR_Height(m_hsprLogo, 0)/2; + + // Draw the logo at 20 fps + int iFrame = (int)(flTime * 20) % MAX_LOGO_FRAMES; + i = grgLogoFrame[iFrame] - 1; + + SPR_DrawAdditive(i, x, y, NULL); + } + + + return 1; +} + +void ScaleColors( int &r, int &g, int &b, int a ) +{ + float x = (float)a / 255; + r = (int)(r * x); + g = (int)(g * x); + b = (int)(b * x); +} + + + +/* +=========================== +int ReturnStringPixelLength ( char *Hihi ) + +Returns a integer representing the length of the string passed +=========================== +*/ +int CHud :: ReturnStringPixelLength ( char *Hihi ) +{ + int iNameLength = 0; + + int strleng = ( strlen( Hihi ) ); + + for ( int har = 0; har < strleng; har++) + iNameLength += gHUD.m_scrinfo.charWidths[ Hihi[har] ]; + + return iNameLength; +} + +int LastColor; + +int CHud :: DrawHudStringCTF(int xpos, int ypos, int iMaxX, char *szIt, int r, int g, int b ) +{ + int WantColor = 0; + int Color = 0; + + // draw the string until we hit the null character or a newline character + for ( ; *szIt != 0 && *szIt != '\n'; szIt++ ) + { + int next;// = xpos + gHUD.m_scrinfo.charWidths[ *szIt ]; // variable-width fonts look cool + + /* if ( next > iMaxX ) + return xpos;*/ + + + if (*szIt == '\\') + { + if (Color > 0) + Color = 0; + + WantColor = 1; + + } + + if (WantColor == 1 && *szIt == 'w') + { + Color = 1; + LastColor = Color; + } + + + if (WantColor == 1 && *szIt == 'g') + { + Color = 2; + LastColor = Color; + } + + + + if (WantColor == 1 && *szIt == 'b') + { + Color = 3; + LastColor = Color; + } + + + if (WantColor == 1 && *szIt == 'r') + { + Color = 4; + LastColor = Color; + } + + + + if (WantColor == 1 && *szIt == 'y') + { + Color = 5; + LastColor = Color; + } + + + + if (WantColor == 1 && *szIt == 'q') + { + Color = 6; + LastColor = Color; + } + + + + if (Color == 0 && WantColor == 0) + { + if (LastColor == 1) + TextMessageDrawChar( xpos, ypos, *szIt, 255, 255, 255 ); + if (LastColor == 2) + TextMessageDrawChar( xpos, ypos, *szIt, 0, 79, 0); + if (LastColor == 3) + TextMessageDrawChar( xpos, ypos, *szIt, 0, 0, 200); + if (LastColor == 4) + TextMessageDrawChar( xpos, ypos, *szIt, 200, 0, 0 ); + if (LastColor == 5) + TextMessageDrawChar( xpos, ypos, *szIt, 198, 221, 66 ); + if (LastColor == 6) + TextMessageDrawChar( xpos, ypos, *szIt, 136, 136, 136 ); + + else if (LastColor == 0) + TextMessageDrawChar( xpos, ypos, *szIt, r, g, b ); + + next = xpos + gHUD.m_scrinfo.charWidths[ *szIt ]; + } + + else if (Color > 0 && WantColor == 0 ) + { + if (Color == 1) + TextMessageDrawChar( xpos, ypos, *szIt, 255, 255, 255 ); + if (Color == 2) + TextMessageDrawChar( xpos, ypos, *szIt, 0, 79, 0); + if (Color == 3) + TextMessageDrawChar( xpos, ypos, *szIt, 0, 0, 200); + if (Color == 4) + TextMessageDrawChar( xpos, ypos, *szIt, 200, 0, 0 ); + if (Color == 5) + TextMessageDrawChar( xpos, ypos, *szIt, 198, 221, 66 ); + if (Color == 6) + TextMessageDrawChar( xpos, ypos, *szIt, 136, 136, 136 ); + + next = xpos + gHUD.m_scrinfo.charWidths[ *szIt ]; + } + + else if (Color > 0 && WantColor == 1) + { + //next = xpos + (gHUD.m_scrinfo.charWidths[ *szIt ] * 2); // variable-width fonts look cool + WantColor = 0; + } + + + xpos = next; + } + + return xpos; +} + + +int CHud :: DrawHudString(int xpos, int ypos, int iMaxX, char *szIt, int r, int g, int b ) +{ + // draw the string until we hit the null character or a newline character + for ( ; *szIt != 0 && *szIt != '\n'; szIt++ ) + { + int next = xpos + gHUD.m_scrinfo.charWidths[ *szIt ]; // variable-width fonts look cool + if ( next > iMaxX ) + return xpos; + + TextMessageDrawChar( xpos, ypos, *szIt, r, g, b ); + xpos = next; + } + + return xpos; +} + +int CHud :: DrawHudNumberString( int xpos, int ypos, int iMinX, int iNumber, int r, int g, int b ) +{ + char szString[32]; + sprintf( szString, "%d", iNumber ); + return DrawHudStringReverse( xpos, ypos, iMinX, szString, r, g, b ); + +} + +// draws a string from right to left (right-aligned) +int CHud :: DrawHudStringReverse( int xpos, int ypos, int iMinX, char *szString, int r, int g, int b ) +{ + // find the end of the string + char *szIt; + for ( szIt = szString; *szIt != 0; szIt++ ) + { // we should count the length? + } + + // iterate throug the string in reverse + for ( szIt--; szIt != (szString-1); szIt-- ) + { + int next = xpos - gHUD.m_scrinfo.charWidths[ *szIt ]; // variable-width fonts look cool + if ( next < iMinX ) + return xpos; + xpos = next; + + TextMessageDrawChar( xpos, ypos, *szIt, r, g, b ); + } + + return xpos; +} + +int CHud :: DrawHudNumber( int x, int y, int iFlags, int iNumber, int r, int g, int b) +{ + int iWidth = GetSpriteRect(m_HUD_number_0).right - GetSpriteRect(m_HUD_number_0).left; + int k; + + if (iNumber > 0) + { + // SPR_Draw 100's + if (iNumber >= 100) + { + k = iNumber/100; + SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); + SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + x += iWidth; + } + else if (iFlags & (DHN_3DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + // SPR_Draw 10's + if (iNumber >= 10) + { + k = (iNumber % 100)/10; + SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); + SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + x += iWidth; + } + else if (iFlags & (DHN_3DIGITS | DHN_2DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + // SPR_Draw ones + k = iNumber % 10; + SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); + SPR_DrawAdditive(0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + x += iWidth; + } + else if (iFlags & DHN_DRAWZERO) + { + SPR_Set(GetSprite(m_HUD_number_0), r, g, b ); + + // SPR_Draw 100's + if (iFlags & (DHN_3DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + if (iFlags & (DHN_3DIGITS | DHN_2DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + // SPR_Draw ones + + SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0)); + x += iWidth; + } + + return x; +} + + +int CHud::GetNumWidth( int iNumber, int iFlags ) +{ + if (iFlags & (DHN_3DIGITS)) + return 3; + + if (iFlags & (DHN_2DIGITS)) + return 2; + + if (iNumber <= 0) + { + if (iFlags & (DHN_DRAWZERO)) + return 1; + else + return 0; + } + + if (iNumber < 10) + return 1; + + if (iNumber < 100) + return 2; + + return 3; + +} + + diff --git a/dmc/cl_dll/hud_servers.cpp b/dmc/cl_dll/hud_servers.cpp new file mode 100644 index 0000000..19849e7 --- /dev/null +++ b/dmc/cl_dll/hud_servers.cpp @@ -0,0 +1,1254 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// hud_servers.cpp +#include "hud.h" +#include "cl_util.h" +#include "hud_servers_priv.h" +#include "hud_servers.h" +#include "net_api.h" +#include +#ifdef _WIN32 +#include +#else +#define __cdecl +#include +#endif + +static int context_id; + +// Default master server address in case we can't read any from valvecomm.lst file +#define VALVE_MASTER_ADDRESS "half-life.east.won.net" +#define PORT_MASTER 27010 +#define PORT_SERVER 27015 + +// File where we really should look for master servers +#define MASTER_PARSE_FILE "valvecomm.lst" + +#define MAX_QUERIES 20 + +#define NET_API gEngfuncs.pNetAPI + +static CHudServers *g_pServers = NULL; + +/* +=================== +ListResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK ListResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->ListResponse( response ); + } +} + +/* +=================== +ServerResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK ServerResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->ServerResponse( response ); + } +} + +/* +=================== +PingResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK PingResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->PingResponse( response ); + } +} + +/* +=================== +RulesResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK RulesResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->RulesResponse( response ); + } +} +/* +=================== +PlayersResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK PlayersResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->PlayersResponse( response ); + } +} +/* +=================== +ListResponse + +=================== +*/ +void CHudServers::ListResponse( struct net_response_s *response ) +{ + request_t *list; + request_t *p; + int c = 0; + + if ( !( response->error == NET_SUCCESS ) ) + return; + + if ( response->type != NETAPI_REQUEST_SERVERLIST ) + return; + + if ( response->response ) + { + list = ( request_t * ) response->response; + while ( list ) + { + c++; + + //if ( c < 40 ) + { + // Copy from parsed stuff + p = new request_t; + p->context = -1; + p->remote_address = list->remote_address; + p->next = m_pServerList; + m_pServerList = p; + } + + // Move on + list = list->next; + } + } + + gEngfuncs.Con_Printf( "got list\n" ); + + m_nQuerying = 1; + m_nActiveQueries = 0; +} + +/* +=================== +ServerResponse + +=================== +*/ +void CHudServers::ServerResponse( struct net_response_s *response ) +{ + char *szresponse; + request_t *p; + server_t *browser; + int len; + char sz[ 32 ]; + + // Remove from active list + p = FindRequest( response->context, m_pActiveList ); + if ( p ) + { + static int first = 0; + + RemoveServerFromList( &m_pActiveList, p ); + m_nActiveQueries--; + + if ( !first ) + { + gEngfuncs.Con_Printf( "recv first %f\n", gEngfuncs.GetClientTime() ); + first = 1; + } + } + + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_DETAILS: + if ( response->response ) + { + szresponse = (char *)response->response; + len = strlen( szresponse ) + 100 + 1; + sprintf( sz, "%i", (int)( 1000.0 * response->ping ) ); + + browser = new server_t; + browser->remote_address = response->remote_address; + browser->info = new char[ len ]; + browser->ping = (int)( 1000.0 * response->ping ); + strcpy( browser->info, szresponse ); + + NET_API->SetValueForKey( browser->info, "address", gEngfuncs.pNetAPI->AdrToString( &response->remote_address ), len ); + NET_API->SetValueForKey( browser->info, "ping", sz, len ); + + AddServer( &m_pServers, browser ); + } + break; + default: + break; + } +} + +/* +=================== +PingResponse + +=================== +*/ +void CHudServers::PingResponse( struct net_response_s *response ) +{ + char sz[ 32 ]; + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_PING: + sprintf( sz, "%.2f", 1000.0 * response->ping ); + + gEngfuncs.Con_Printf( "ping == %s\n", sz ); + break; + default: + break; + } +} + +/* +=================== +RulesResponse + +=================== +*/ +void CHudServers::RulesResponse( struct net_response_s *response ) +{ + char *szresponse; + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_RULES: + if ( response->response ) + { + szresponse = (char *)response->response; + + gEngfuncs.Con_Printf( "rules %s\n", szresponse ); + } + break; + default: + break; + } +} + +/* +=================== +PlayersResponse + +=================== +*/ +void CHudServers::PlayersResponse( struct net_response_s *response ) +{ + char *szresponse; + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_PLAYERS: + if ( response->response ) + { + szresponse = (char *)response->response; + + gEngfuncs.Con_Printf( "players %s\n", szresponse ); + } + break; + default: + break; + } +} + +/* +=================== +CompareServers + +Return 1 if p1 is "less than" p2, 0 otherwise +=================== +*/ +int CHudServers::CompareServers( server_t *p1, server_t *p2 ) +{ + const char *n1, *n2; + + if ( p1->ping < p2->ping ) + return 1; + + if ( p1->ping == p2->ping ) + { + // Pings equal, sort by second key: hostname + if ( p1->info && p2->info ) + { + n1 = NET_API->ValueForKey( p1->info, "hostname" ); + n2 = NET_API->ValueForKey( p2->info, "hostname" ); + + if ( n1 && n2 ) + { + if ( stricmp( n1, n2 ) < 0 ) + return 1; + } + } + } + + return 0; +} + +/* +=================== +AddServer + +=================== +*/ +void CHudServers::AddServer( server_t **ppList, server_t *p ) +{ +server_t *list; + + if ( !ppList || ! p ) + return; + + m_nServerCount++; + + // What sort key? Ping? + list = *ppList; + + // Head of list? + if ( !list ) + { + p->next = NULL; + *ppList = p; + return; + } + + // Put on head of list + if ( CompareServers( p, list ) ) + { + p->next = *ppList; + *ppList = p; + } + else + { + while ( list->next ) + { + // Insert before list next + if ( CompareServers( p, list->next ) ) + { + p->next = list->next->next; + list->next = p; + return; + } + + list = list->next; + } + + // Just add at end + p->next = NULL; + list->next = p; + } +} + +/* +=================== +Think + +=================== +*/ +void CHudServers::Think( double time ) +{ + m_fElapsed += time; + + if ( !m_nRequesting ) + return; + + if ( !m_nQuerying ) + return; + + QueryThink(); + + if ( ServerListSize() > 0 ) + return; + + m_dStarted = 0.0; + m_nRequesting = 0; + m_nDone = 0; + m_nQuerying = 0; + m_nActiveQueries = 0; +} + +/* +=================== +QueryThink + +=================== +*/ +void CHudServers::QueryThink( void ) +{ + request_t *p; + + if ( !m_nRequesting || m_nDone ) + return; + + if ( !m_nQuerying ) + return; + + if ( m_nActiveQueries > MAX_QUERIES ) + return; + + // Nothing left + if ( !m_pServerList ) + return; + + while ( 1 ) + { + static int first = 0; + p = m_pServerList; + + // No more in list? + if ( !p ) + break; + + // Move to next + m_pServerList = m_pServerList->next; + + // Setup context_id + p->context = context_id; + + // Make sure networking system has started. + // NET_API->InitNetworking(); + + if ( !first ) + { + gEngfuncs.Con_Printf( "send first %f\n", gEngfuncs.GetClientTime() ); + first = 1; + } + + // Start up query on this one + NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, 0, 2.0, &p->remote_address, ::ServerResponse ); + + // Increment active list + m_nActiveQueries++; + + // Add to active list + p->next = m_pActiveList; + m_pActiveList = p; + + // Too many active? + if ( m_nActiveQueries > MAX_QUERIES ) + break; + } +} + +/* +================== +ServerListSize + +# of servers in active query and in pending to be queried lists +================== +*/ +int CHudServers::ServerListSize( void ) +{ + int c = 0; + request_t *p; + + p = m_pServerList; + while ( p ) + { + c++; + p = p->next; + } + + p = m_pActiveList; + while ( p ) + { + c++; + p = p->next; + } + + return c; +} + +/* +=================== +FindRequest + +Look up a request by context id +=================== +*/ +CHudServers::request_t *CHudServers::FindRequest( int context, request_t *pList ) +{ + request_t *p; + p = pList; + while ( p ) + { + if ( context == p->context ) + return p; + + p = p->next; + } + return NULL; +} + +/* +=================== +RemoveServerFromList + +Remote, but don't delete, item from *ppList +=================== +*/ +void CHudServers::RemoveServerFromList( request_t **ppList, request_t *item ) +{ + request_t *p, *n; + request_t *newlist = NULL; + + if ( !ppList ) + return; + + p = *ppList; + while ( p ) + { + n = p->next; + if ( p != item ) + { + p->next = newlist; + newlist = p; + } + p = n; + } + *ppList = newlist; +} + +/* +=================== +ClearRequestList + +=================== +*/ +void CHudServers::ClearRequestList( request_t **ppList ) +{ + request_t *p, *n; + + if ( !ppList ) + return; + + p = *ppList; + while ( p ) + { + n = p->next; + delete p; + p = n; + } + *ppList = NULL; +} + +/* +=================== +ClearServerList + +=================== +*/ +void CHudServers::ClearServerList( server_t **ppList ) +{ + server_t *p, *n; + + if ( !ppList ) + return; + + p = *ppList; + while ( p ) + { + n = p->next; + delete[] p->info; + delete p; + p = n; + } + *ppList = NULL; +} + +int CompareField( CHudServers::server_t *p1, CHudServers::server_t *p2, const char *fieldname, int iSortOrder ) +{ + const char *sz1, *sz2; + float fv1, fv2; + + sz1 = NET_API->ValueForKey( p1->info, fieldname ); + sz2 = NET_API->ValueForKey( p2->info, fieldname ); + + fv1 = atof( sz1 ); + fv2 = atof( sz2 ); + + if ( fv1 && fv2 ) + { + if ( fv1 > fv2 ) + return iSortOrder; + else if ( fv1 < fv2 ) + return -iSortOrder; + else + return 0; + } + + // String compare + return stricmp( sz1, sz2 ); +} + +int ServerListCompareFunc( CHudServers::server_t *p1, CHudServers::server_t *p2, const char *fieldname ) +{ + if (!p1 || !p2) // No meaningful comparison + return 0; + + int iSortOrder = 1; + + int retval = 0; + + retval = CompareField( p1, p2, fieldname, iSortOrder ); + + return retval; +} + +static char g_fieldname[ 256 ]; +int __cdecl FnServerCompare(const void *elem1, const void *elem2 ) +{ + CHudServers::server_t *list1, *list2; + + list1 = *(CHudServers::server_t **)elem1; + list2 = *(CHudServers::server_t **)elem2; + + return ServerListCompareFunc( list1, list2, g_fieldname ); +} + +void CHudServers::SortServers( const char *fieldname ) +{ + server_t *p; + // Create a list + if ( !m_pServers ) + return; + + strcpy( g_fieldname, fieldname ); + + int i; + int c = 0; + + p = m_pServers; + while ( p ) + { + c++; + p = p->next; + } + + server_t **pSortArray; + + pSortArray = new server_t *[ c ]; + memset( pSortArray, 0, c * sizeof( server_t * ) ); + + // Now copy the list into the pSortArray: + p = m_pServers; + i = 0; + while ( p ) + { + pSortArray[ i++ ] = p; + p = p->next; + } + + // Now do that actual sorting. + size_t nCount = c; + size_t nSize = sizeof( server_t * ); + + qsort( + pSortArray, + (size_t)nCount, + (size_t)nSize, + FnServerCompare + ); + + // Now rebuild the list. + m_pServers = pSortArray[0]; + for ( i = 0; i < c - 1; i++ ) + { + pSortArray[ i ]->next = pSortArray[ i + 1 ]; + } + pSortArray[ c - 1 ]->next = NULL; + + // Clean Up. + delete[] pSortArray; +} + +/* +=================== +GetServer + +Return particular server +=================== +*/ +CHudServers::server_t *CHudServers::GetServer( int server ) +{ + int c = 0; + server_t *p; + + p = m_pServers; + while ( p ) + { + if ( c == server ) + return p; + + c++; + p = p->next; + } + return NULL; +} + +/* +=================== +GetServerInfo + +Return info ( key/value ) string for particular server +=================== +*/ +char *CHudServers::GetServerInfo( int server ) +{ + server_t *p = GetServer( server ); + if ( p ) + { + return p->info; + } + return NULL; +} + +/* +=================== +CancelRequest + +Kill all pending requests in engine +=================== +*/ +void CHudServers::CancelRequest( void ) +{ + m_nRequesting = 0; + m_nQuerying = 0; + m_nDone = 1; + + NET_API->CancelAllRequests(); +} + +/* +================== +LoadMasterAddresses + +Loads the master server addresses from file and into the passed in array +================== +*/ +int CHudServers::LoadMasterAddresses( int maxservers, int *count, netadr_t *padr ) +{ + int i; + char szMaster[ 256 ]; + char szMasterFile[256]; + char *pbuffer = NULL; + char *pstart = NULL ; + netadr_t adr; + char szAdr[64]; + int nPort; + int nCount = 0; + bool bIgnore; + int nDefaultPort; + + // Assume default master and master file + strcpy( szMaster, VALVE_MASTER_ADDRESS ); // IP:PORT string + strcpy( szMasterFile, MASTER_PARSE_FILE ); + + // See if there is a command line override + i = gEngfuncs.CheckParm( "-comm", &pstart ); + if ( i && pstart ) + { + strcpy (szMasterFile, pstart ); + } + + // Read them in from proper file + pbuffer = (char *)gEngfuncs.COM_LoadFile( szMasterFile, 5, NULL ); // Use malloc + if ( !pbuffer ) + { + goto finish_master; + } + + pstart = pbuffer; + + while ( nCount < maxservers ) + { + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if ( strlen(m_szToken) <= 0) + break; + + bIgnore = true; + + if ( !stricmp( m_szToken, "Master" ) ) + { + nDefaultPort = PORT_MASTER; + bIgnore = FALSE; + } + + // Now parse all addresses between { } + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + if ( strlen(m_szToken) <= 0 ) + break; + + if ( stricmp ( m_szToken, "{" ) ) + break; + + // Parse addresses until we get to "}" + while ( nCount < maxservers ) + { + char base[256]; + + // Now parse all addresses between { } + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if (strlen(m_szToken) <= 0) + break; + + if ( !stricmp ( m_szToken, "}" ) ) + break; + + sprintf( base, "%s", m_szToken ); + + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if (strlen(m_szToken) <= 0) + break; + + if ( stricmp( m_szToken, ":" ) ) + break; + + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if (strlen(m_szToken) <= 0) + break; + + nPort = atoi ( m_szToken ); + if ( !nPort ) + nPort = nDefaultPort; + + sprintf( szAdr, "%s:%i", base, nPort ); + + // Can we resolve it any better + if ( !NET_API->StringToAdr( szAdr, &adr ) ) + bIgnore = true; + + if ( !bIgnore ) + { + padr[ nCount++ ] = adr; + } + } + } + +finish_master: + if ( !nCount ) + { + sprintf( szMaster, VALVE_MASTER_ADDRESS ); // IP:PORT string + + // Convert to netadr_t + if ( NET_API->StringToAdr ( szMaster, &adr ) ) + { + + padr[ nCount++ ] = adr; + } + } + + *count = nCount; + + if ( pbuffer ) + { + gEngfuncs.COM_FreeFile( pbuffer ); + } + + return ( nCount > 0 ) ? 1 : 0; +} + +/* +=================== +RequestList + +Request list of game servers from master +=================== +*/ +void CHudServers::RequestList( void ) +{ + m_nRequesting = 1; + m_nDone = 0; + m_dStarted = m_fElapsed; + + int count = 0; + netadr_t adr; + + if ( !LoadMasterAddresses( 1, &count, &adr ) ) + { + gEngfuncs.Con_DPrintf( "SendRequest: Unable to read master server addresses\n" ); + return; + } + + ClearRequestList( &m_pActiveList ); + ClearRequestList( &m_pServerList ); + ClearServerList( &m_pServers ); + + m_nServerCount = 0; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Kill off left overs if any + NET_API->CancelAllRequests(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_SERVERLIST, 0, 5.0, &adr, ::ListResponse ); +} + +void CHudServers::RequestBroadcastList( int clearpending ) +{ + m_nRequesting = 1; + m_nDone = 0; + m_dStarted = m_fElapsed; + + netadr_t adr; + memset( &adr, 0, sizeof( adr ) ); + + if ( clearpending ) + { + ClearRequestList( &m_pActiveList ); + ClearRequestList( &m_pServerList ); + ClearServerList( &m_pServers ); + + m_nServerCount = 0; + } + + // Make sure to byte swap server if necessary ( using "host" to "net" conversion + adr.port = htons( PORT_SERVER ); + + // Make sure networking system has started. + NET_API->InitNetworking(); + + if ( clearpending ) + { + // Kill off left overs if any + NET_API->CancelAllRequests(); + } + + adr.type = NA_BROADCAST; + + // Request Servers from LAN via IP + NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, FNETAPI_MULTIPLE_RESPONSE, 5.0, &adr, ::ServerResponse ); + + adr.type = NA_BROADCAST_IPX; + + // Request Servers from LAN via IPX ( if supported ) + NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, FNETAPI_MULTIPLE_RESPONSE, 5.0, &adr, ::ServerResponse ); +} + +void CHudServers::ServerPing( int server ) +{ + server_t *p; + + p = GetServer( server ); + if ( !p ) + return; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_PING, 0, 5.0, &p->remote_address, ::PingResponse ); +} + +void CHudServers::ServerRules( int server ) +{ + server_t *p; + + p = GetServer( server ); + if ( !p ) + return; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_RULES, 0, 5.0, &p->remote_address, ::RulesResponse ); +} + +void CHudServers::ServerPlayers( int server ) +{ + server_t *p; + + p = GetServer( server ); + if ( !p ) + return; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_PLAYERS, 0, 5.0, &p->remote_address, ::PlayersResponse ); +} + +int CHudServers::isQuerying() +{ + return m_nRequesting ? 1 : 0; +} + + +/* +=================== +GetServerCount + +Return number of servers in browser list +=================== +*/ +int CHudServers::GetServerCount( void ) +{ + return m_nServerCount; +} + +/* +=================== +CHudServers + +=================== +*/ +CHudServers::CHudServers( void ) +{ + m_nRequesting = 0; + m_dStarted = 0.0; + m_nDone = 0; + m_pServerList = NULL; + m_pServers = NULL; + m_pActiveList = NULL; + m_nQuerying = 0; + m_nActiveQueries = 0; + + m_fElapsed = 0.0; + + + m_pPingRequest = NULL; + m_pRulesRequest = NULL; + m_pPlayersRequest = NULL; +} + +/* +=================== +~CHudServers + +=================== +*/ +CHudServers::~CHudServers( void ) +{ + ClearRequestList( &m_pActiveList ); + ClearRequestList( &m_pServerList ); + ClearServerList( &m_pServers ); + + m_nServerCount = 0; + + if ( m_pPingRequest ) + { + delete m_pPingRequest; + m_pPingRequest = NULL; + + } + + if ( m_pRulesRequest ) + { + delete m_pRulesRequest; + m_pRulesRequest = NULL; + } + + if ( m_pPlayersRequest ) + { + delete m_pPlayersRequest; + m_pPlayersRequest = NULL; + } +} + +/////////////////////////////// +// +// PUBLIC APIs +// +/////////////////////////////// + +/* +=================== +ServersGetCount + +=================== +*/ +int ServersGetCount( void ) +{ + if ( g_pServers ) + { + return g_pServers->GetServerCount(); + } + return 0; +} + +int ServersIsQuerying( void ) +{ + if ( g_pServers ) + { + return g_pServers->isQuerying(); + } + return 0; +} + +/* +=================== +ServersGetInfo + +=================== +*/ +const char *ServersGetInfo( int server ) +{ + if ( g_pServers ) + { + return g_pServers->GetServerInfo( server ); + } + + return NULL; +} + +void SortServers( const char *fieldname ) +{ + if ( g_pServers ) + { + g_pServers->SortServers( fieldname ); + } +} + +/* +=================== +ServersShutdown + +=================== +*/ +void ServersShutdown( void ) +{ + if ( g_pServers ) + { + delete g_pServers; + g_pServers = NULL; + } +} + +/* +=================== +ServersInit + +=================== +*/ +void ServersInit( void ) +{ + // Kill any previous instance + ServersShutdown(); + + g_pServers = new CHudServers(); +} + +/* +=================== +ServersThink + +=================== +*/ +void ServersThink( double time ) +{ + if ( g_pServers ) + { + g_pServers->Think( time ); + } +} + +/* +=================== +ServersCancel + +=================== +*/ +void ServersCancel( void ) +{ + if ( g_pServers ) + { + g_pServers->CancelRequest(); + } +} + +// Requests +/* +=================== +ServersList + +=================== +*/ +void ServersList( void ) +{ + if ( g_pServers ) + { + g_pServers->RequestList(); + } +} + +void BroadcastServersList( int clearpending ) +{ + if ( g_pServers ) + { + g_pServers->RequestBroadcastList( clearpending ); + } +} + +void ServerPing( int server ) +{ + if ( g_pServers ) + { + g_pServers->ServerPing( server ); + } +} + +void ServerRules( int server ) +{ + if ( g_pServers ) + { + g_pServers->ServerRules( server ); + } +} + +void ServerPlayers( int server ) +{ + if ( g_pServers ) + { + g_pServers->ServerPlayers( server ); + } +} diff --git a/dmc/cl_dll/hud_servers.h b/dmc/cl_dll/hud_servers.h new file mode 100644 index 0000000..01e9442 --- /dev/null +++ b/dmc/cl_dll/hud_servers.h @@ -0,0 +1,41 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( HUD_SERVERSH ) +#define HUD_SERVERSH +#pragma once + +#define NET_CALLBACK /* */ + +// Dispatchers +void NET_CALLBACK ListResponse( struct net_response_s *response ); +void NET_CALLBACK ServerResponse( struct net_response_s *response ); +void NET_CALLBACK PingResponse( struct net_response_s *response ); +void NET_CALLBACK RulesResponse( struct net_response_s *response ); +void NET_CALLBACK PlayersResponse( struct net_response_s *response ); + +void ServersInit( void ); +void ServersShutdown( void ); +void ServersThink( double time ); +void ServersCancel( void ); + +// Get list and get server info from each +void ServersList( void ); + +// Query for IP / IPX LAN servers +void BroadcastServersList( int clearpending ); + +void ServerPing( int server ); +void ServerRules( int server ); +void ServerPlayers( int server ); + +int ServersGetCount( void ); +const char *ServersGetInfo( int server ); +int ServersIsQuerying( void ); +void SortServers( const char *fieldname ); + +#endif // HUD_SERVERSH \ No newline at end of file diff --git a/dmc/cl_dll/hud_servers_priv.h b/dmc/cl_dll/hud_servers_priv.h new file mode 100644 index 0000000..73692f4 --- /dev/null +++ b/dmc/cl_dll/hud_servers_priv.h @@ -0,0 +1,98 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( HUD_SERVERS_PRIVH ) +#define HUD_SERVERS_PRIVH +#pragma once + +#include "netadr.h" + +class CHudServers +{ +public: + typedef struct request_s + { + struct request_s *next; + netadr_t remote_address; + int context; + } request_t; + + typedef struct server_s + { + struct server_s *next; + netadr_t remote_address; + char *info; + int ping; + } server_t; + + CHudServers(); + ~CHudServers(); + + void Think( double time ); + void QueryThink( void ); + int isQuerying( void ); + + int LoadMasterAddresses( int maxservers, int *count, netadr_t *padr ); + + void RequestList( void ); + void RequestBroadcastList( int clearpending ); + + void ServerPing( int server ); + void ServerRules( int server ); + void ServerPlayers( int server ); + + void CancelRequest( void ); + + int CompareServers( server_t *p1, server_t *p2 ); + + void ClearServerList( server_t **ppList ); + void ClearRequestList( request_t **ppList ); + + void AddServer( server_t **ppList, server_t *p ); + + void RemoveServerFromList( request_t **ppList, request_t *item ); + + request_t *FindRequest( int context, request_t *pList ); + + int ServerListSize( void ); + char *GetServerInfo( int server ); + int GetServerCount( void ); + void SortServers( const char *fieldname ); + + void ListResponse( struct net_response_s *response ); + void ServerResponse( struct net_response_s *response ); + void PingResponse( struct net_response_s *response ); + void RulesResponse( struct net_response_s *response ); + void PlayersResponse( struct net_response_s *response ); +private: + + server_t *GetServer( int server ); + + // + char m_szToken[ 1024 ]; + int m_nRequesting; + int m_nDone; + + double m_dStarted; + + request_t *m_pServerList; + request_t *m_pActiveList; + + server_t *m_pServers; + + int m_nServerCount; + + int m_nActiveQueries; + int m_nQuerying; + double m_fElapsed; + + request_t *m_pPingRequest; + request_t *m_pRulesRequest; + request_t *m_pPlayersRequest; +}; + +#endif // HUD_SERVERS_PRIVH \ No newline at end of file diff --git a/dmc/cl_dll/hud_spectator.cpp b/dmc/cl_dll/hud_spectator.cpp new file mode 100644 index 0000000..ac4df41 --- /dev/null +++ b/dmc/cl_dll/hud_spectator.cpp @@ -0,0 +1,1576 @@ +//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "hud.h" +#include "cl_util.h" +#include "cl_entity.h" +#include "triangleapi.h" +#include "vgui_viewport.h" +#include "vgui_SpectatorPanel.h" +#include "hltv.h" + +#include "pm_shared.h" +#include "pm_defs.h" +#include "pmtrace.h" +#include "parsemsg.h" +#include "entity_types.h" + +// these are included for the math functions +#include "com_model.h" +#include "demo_api.h" +#include "studio_util.h" + +#pragma warning(disable: 4244) + +extern "C" int iJumpSpectator; +extern "C" float vJumpOrigin[3]; +extern "C" float vJumpAngles[3]; + + +extern void V_GetInEyePos(int entity, float * origin, float * angles ); +extern void V_ResetChaseCam(); +extern void V_GetChasePos(int target, float * cl_angles, float * origin, float * angles); +extern void VectorAngles( const float *forward, float *angles ); +extern "C" void NormalizeAngles( float *angles ); +extern float * GetClientColor( int clientIndex ); + +extern vec3_t v_origin; // last view origin +extern vec3_t v_angles; // last view angle +extern vec3_t v_cl_angles; // last client/mouse angle +extern vec3_t v_sim_org; // last sim origin + +void SpectatorMode(void) +{ + + + if ( gEngfuncs.Cmd_Argc() <= 1 ) + { + gEngfuncs.Con_Printf( "usage: spec_mode
[]\n" ); + return; + } + + // SetModes() will decide if command is executed on server or local + if ( gEngfuncs.Cmd_Argc() == 2 ) + gHUD.m_Spectator.SetModes( atoi( gEngfuncs.Cmd_Argv(1) ), -1 ); + else if ( gEngfuncs.Cmd_Argc() == 3 ) + gHUD.m_Spectator.SetModes( atoi( gEngfuncs.Cmd_Argv(1) ), atoi( gEngfuncs.Cmd_Argv(2) ) ); +} + +void SpectatorSpray(void) +{ + vec3_t forward; + char string[128]; + + if ( !gEngfuncs.IsSpectateOnly() ) + return; + + AngleVectors(v_angles,forward,NULL,NULL); + VectorScale(forward, 128, forward); + VectorAdd(forward, v_origin, forward); + pmtrace_t * trace = gEngfuncs.PM_TraceLine( v_origin, forward, PM_TRACELINE_PHYSENTSONLY, 2, -1 ); + if ( trace->fraction != 1.0 ) + { + sprintf(string, "drc_spray %.2f %.2f %.2f %i", + trace->endpos[0], trace->endpos[1], trace->endpos[2], trace->ent ); + gEngfuncs.pfnServerCmd(string); + } + +} +void SpectatorHelp(void) +{ + if ( gViewPort ) + { + gViewPort->ShowVGUIMenu( MENU_SPECHELP ); + } + else + { + char *text = CHudTextMessage::BufferedLocaliseTextString( "#Spec_Help_Text" ); + + if ( text ) + { + while ( *text ) + { + if ( *text != 13 ) + gEngfuncs.Con_Printf( "%c", *text ); + text++; + } + } + } +} + +void SpectatorMenu( void ) +{ + if ( gEngfuncs.Cmd_Argc() <= 1 ) + { + gEngfuncs.Con_Printf( "usage: spec_menu <0|1>\n" ); + return; + } + + gViewPort->m_pSpectatorPanel->ShowMenu( atoi( gEngfuncs.Cmd_Argv(1))!=0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CHudSpectator::Init() +{ + gHUD.AddHudElem(this); + + m_iFlags |= HUD_ACTIVE; + m_flNextObserverInput = 0.0f; + m_zoomDelta = 0.0f; + m_moveDelta = 0.0f; + m_chatEnabled = (gHUD.m_SayText.m_HUD_saytext->value!=0); + iJumpSpectator = 0; + + memset( &m_OverviewData, 0, sizeof(m_OverviewData)); + memset( &m_OverviewEntities, 0, sizeof(m_OverviewEntities)); + m_lastPrimaryObject = m_lastSecondaryObject = 0; + + gEngfuncs.pfnAddCommand ("spec_mode", SpectatorMode ); + gEngfuncs.pfnAddCommand ("spec_decal", SpectatorSpray ); + gEngfuncs.pfnAddCommand ("spec_help", SpectatorHelp ); + gEngfuncs.pfnAddCommand ("spec_menu", SpectatorMenu ); + + m_drawnames = gEngfuncs.pfnRegisterVariable("spec_drawnames","1",0); + m_drawcone = gEngfuncs.pfnRegisterVariable("spec_drawcone","1",0); + m_drawstatus = gEngfuncs.pfnRegisterVariable("spec_drawstatus","1",0); + m_autoDirector = gEngfuncs.pfnRegisterVariable("spec_autodirector","1",0); + m_pip = gEngfuncs.pfnRegisterVariable("spec_pip","1",0); + + + if ( !m_drawnames || !m_drawcone || !m_drawstatus || !m_autoDirector || !m_pip) + { + gEngfuncs.Con_Printf("ERROR! Couldn't register all spectator variables.\n"); + return 0; + } + + return 1; +} + + +//----------------------------------------------------------------------------- +// UTIL_StringToVector originally from ..\dlls\util.cpp, slightly changed +//----------------------------------------------------------------------------- + +void UTIL_StringToVector( float * pVector, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + + strcpy( tempString, pString ); + pstr = pfront = tempString; + + for ( j = 0; j < 3; j++ ) + { + pVector[j] = atof( pfront ); + + while ( *pstr && *pstr != ' ' ) + pstr++; + if (!*pstr) + break; + pstr++; + pfront = pstr; + } + + if (j < 2) + { + for (j = j+1;j < 3; j++) + pVector[j] = 0; + } +} + +int UTIL_FindEntityInMap(char * name, float * origin, float * angle) +{ + int n,found = 0; + char keyname[256]; + char token[1024]; + + cl_entity_t * pEnt = gEngfuncs.GetEntityByIndex( 0 ); // get world model + + if ( !pEnt ) return 0; + + if ( !pEnt->model ) return 0; + + char * data = pEnt->model->entities; + + while (data) + { + data = gEngfuncs.COM_ParseFile(data, token); + + if ( (token[0] == '}') || (token[0]==0) ) + break; + + if (!data) + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: EOF without closing brace\n"); + return 0; + } + + if (token[0] != '{') + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: expected {\n"); + return 0; + } + + // we parse the first { now parse entities properties + + while ( 1 ) + { + // parse key + data = gEngfuncs.COM_ParseFile(data, token); + if (token[0] == '}') + break; // finish parsing this entity + + if (!data) + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: EOF without closing brace\n"); + return 0; + }; + + strcpy (keyname, token); + + // another hack to fix keynames with trailing spaces + n = strlen(keyname); + while (n && keyname[n-1] == ' ') + { + keyname[n-1] = 0; + n--; + } + + // parse value + data = gEngfuncs.COM_ParseFile(data, token); + if (!data) + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: EOF without closing brace\n"); + return 0; + }; + + if (token[0] == '}') + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: closing brace without data"); + return 0; + } + + if (!strcmp(keyname,"classname")) + { + if (!strcmp(token, name )) + { + found = 1; // thats our entity + } + }; + + if( !strcmp( keyname, "angle" ) ) + { + float y = atof( token ); + + if (y >= 0) + { + angle[0] = 0.0f; + angle[1] = y; + } + else if ((int)y == -1) + { + angle[0] = -90.0f; + angle[1] = 0.0f;; + } + else + { + angle[0] = 90.0f; + angle[1] = 0.0f; + } + + angle[2] = 0.0f; + } + + if( !strcmp( keyname, "angles" ) ) + { + UTIL_StringToVector(angle, token); + } + + if (!strcmp(keyname,"origin")) + { + UTIL_StringToVector(origin, token); + + }; + + } // while (1) + + if (found) + return 1; + + } + + return 0; // we search all entities, but didn't found the correct + +} + +//----------------------------------------------------------------------------- +// SetSpectatorStartPosition(): +// Get valid map position and 'beam' spectator to this position +//----------------------------------------------------------------------------- + +void CHudSpectator::SetSpectatorStartPosition() +{ + VectorCopy(vec3_origin, m_cameraOrigin); + VectorCopy(vec3_origin, m_cameraAngles); + + + // search for info_player start + if (!UTIL_FindEntityInMap( "trigger_camera", m_cameraOrigin, m_cameraAngles ) ) + { + if (!UTIL_FindEntityInMap( "info_player_start", m_cameraOrigin, m_cameraAngles ) ) + gEngfuncs.Con_Printf("Couldn't find spectator spawn point.\n"); + // uh, we didn't find anything + } + + VectorCopy(m_cameraOrigin, vJumpOrigin); + VectorCopy(m_cameraAngles, vJumpAngles); + + iJumpSpectator = 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Loads new icons +//----------------------------------------------------------------------------- +int CHudSpectator::VidInit() +{ + m_hsprPlayer = SPR_Load("sprites/iplayer.spr"); + m_hsprPlayerBlue = SPR_Load("sprites/iplayerblue.spr"); + m_hsprPlayerRed = SPR_Load("sprites/iplayerred.spr"); + m_hsprPlayerDead = SPR_Load("sprites/iplayerdead.spr"); + m_hsprUnkownMap = SPR_Load("sprites/tile.spr"); + m_hsprBeam = SPR_Load("sprites/laserbeam.spr"); + m_hsprCamera = SPR_Load("sprites/camera.spr"); + m_hCrosshair = SPR_Load("sprites/crosshairs.spr"); + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flTime - +// intermission - +//----------------------------------------------------------------------------- +int CHudSpectator::Draw(float flTime) +{ + int lx; + + char string[256]; + float * color; + + // draw only in spectator mode + if ( !g_iUser1 ) + return 1; + + // if user pressed zoom, aplly changes + if ( (m_zoomDelta != 0.0f) && (g_iUser1 != OBS_ROAMING) ) + { + m_mapZoom += m_zoomDelta; + + if ( m_mapZoom > 3.0f ) + m_mapZoom = 3.0f; + + if ( m_mapZoom < 0.5f ) + m_mapZoom = 0.5f; + } + + // if user moves in map mode, change map origin + if ( (m_moveDelta != 0.0f) && (g_iUser1 != OBS_ROAMING) ) + { + vec3_t right; + AngleVectors(v_angles, NULL, right, NULL); + VectorNormalize(right); + VectorScale(right, m_moveDelta, right ); + + VectorAdd( m_mapOrigin, right, m_mapOrigin ) + + } + + // Only draw the icon names only if map mode is in Main Mode + if ( g_iUser1 < OBS_MAP_FREE ) + return 1; + + if ( !m_drawnames->value ) + return 1; + + // make sure we have player info + gViewPort->GetAllPlayersInfo(); + + + // loop through all the players and draw additional infos to their sprites on the map + for (int i = 0; i < MAX_PLAYERS; i++) + { + + if ( m_vPlayerPos[i][2]<0 ) // marked as invisible ? + continue; + + // check if name would be in inset window + if ( m_pip->value != INSET_OFF ) + { + if ( m_vPlayerPos[i][0] > XRES( m_OverviewData.insetWindowX ) && + m_vPlayerPos[i][1] > YRES( m_OverviewData.insetWindowY ) && + m_vPlayerPos[i][0] < XRES( m_OverviewData.insetWindowX + m_OverviewData.insetWindowWidth ) && + m_vPlayerPos[i][1] < YRES( m_OverviewData.insetWindowY + m_OverviewData.insetWindowHeight) + ) continue; + } + + color = GetClientColor( i+1 ); + + // draw the players name and health underneath + sprintf(string, "%s", g_PlayerInfoList[i+1].name ); + + lx = strlen(string)*3; // 3 is avg. character length :) + + gEngfuncs.pfnDrawSetTextColor( color[0], color[1], color[2] ); + DrawConsoleString( m_vPlayerPos[i][0]-lx,m_vPlayerPos[i][1], string); + + } + + return 1; +} + + +void CHudSpectator::DirectorMessage( int iSize, void *pbuf ) +{ + float value; + char * string; + + BEGIN_READ( pbuf, iSize ); + + int cmd = READ_BYTE(); + + switch ( cmd ) // director command byte + { + case DRC_CMD_START : + // now we have to do some things clientside, since the proxy doesn't know our mod + + // fake a InitHUD message + gHUD.MsgFunc_InitHUD(NULL,0, NULL); + + break; + + case DRC_CMD_EVENT : + m_lastPrimaryObject = READ_WORD(); + m_lastSecondaryObject = READ_WORD(); + m_iObserverFlags = READ_LONG(); + + if ( m_autoDirector->value ) + { + if ( (g_iUser2 != m_lastPrimaryObject) || (g_iUser3 != m_lastSecondaryObject) ) + V_ResetChaseCam(); + + g_iUser2 = m_lastPrimaryObject; + g_iUser3 = m_lastSecondaryObject; + } + + // gEngfuncs.Con_Printf("Director Camera: %i %i\n", firstObject, secondObject); + break; + + case DRC_CMD_MODE : + if ( m_autoDirector->value ) + { + SetModes( READ_BYTE(), -1 ); + } + break; + + case DRC_CMD_CAMERA : + if ( m_autoDirector->value ) + { + vJumpOrigin[0] = READ_COORD(); // position + vJumpOrigin[1] = READ_COORD(); + vJumpOrigin[2] = READ_COORD(); + + vJumpAngles[0] = READ_COORD(); // view angle + vJumpAngles[1] = READ_COORD(); + vJumpAngles[0] = READ_COORD(); + + iJumpSpectator = 1; + } + break; + + case DRC_CMD_MESSAGE: + { + client_textmessage_t * msg = &m_HUDMessages[m_lastHudMessage]; + + msg->effect = READ_BYTE(); // effect + + UnpackRGB( (int&)msg->r1, (int&)msg->g1, (int&)msg->b2, READ_LONG() ); // color + msg->r2 = msg->r1; + msg->g2 = msg->g1; + msg->b2 = msg->b1; + msg->a2 = msg->a1 = 0xFF; // not transparent + + msg->x = READ_FLOAT(); // x pos + msg->y = READ_FLOAT(); // y pos + + msg->fadein = READ_FLOAT(); // fadein + msg->fadeout = READ_FLOAT(); // fadeout + msg->holdtime = READ_FLOAT(); // holdtime + msg->fxtime = READ_FLOAT(); // fxtime; + + strncpy( m_HUDMessageText[m_lastHudMessage], READ_STRING(), 128 ); + m_HUDMessageText[m_lastHudMessage][127]=0; // text + + msg->pMessage = m_HUDMessageText[m_lastHudMessage]; + msg->pName = "HUD_MESSAGE"; + + gHUD.m_Message.MessageAdd( msg ); + + m_lastHudMessage++; + m_lastHudMessage %= MAX_SPEC_HUD_MESSAGES; + + } + + break; + + case DRC_CMD_SOUND : + string = READ_STRING(); + value = READ_FLOAT(); + + // gEngfuncs.Con_Printf("DRC_CMD_FX_SOUND: %s %.2f\n", string, value ); + PlaySound( string, value ); + + break; + case DRC_CMD_TIMESCALE : + value = READ_FLOAT(); + break; + + + +/* case DRC_CMD_STATUS: + READ_LONG(); // total number of spectator slots + m_iSpectatorNumber = READ_LONG(); // total number of spectator + READ_WORD(); // total number of relay proxies + + gViewPort->UpdateSpectatorPanel(); + break; + + case DRC_CMD_BANNER: + // gEngfuncs.Con_DPrintf("GUI: Banner %s\n",READ_STRING() ); // name of banner tga eg gfx/temp/7454562234563475.tga + gViewPort->m_pSpectatorPanel->m_TopBanner->LoadImage( READ_STRING() ); + gViewPort->UpdateSpectatorPanel(); + break; + case DRC_CMD_FADE: + { + screenfade_t sf; + + sf.fader = 255; + sf.fadeg = 0; + sf.fadeb = 0; + sf.fadealpha = 128; + sf.fadeFlags = FFADE_STAYOUT | FFADE_OUT; + + // gHUD.m_flTime = cl.time + + stream->ReadFloat(); // duration + sf.stream->ReadFloat(); // holdTime + sf.fadeFlags = READ_SHORT(); // flags + stream->ReadLong(); // color RGB + + CallEnghudSetScreenFade( &sf ); + } + break; +*/ + + case DRC_CMD_STUFFTEXT: + ClientCmd( READ_STRING() ); + break; + + default : gEngfuncs.Con_DPrintf("CHudSpectator::DirectorMessage: unknown command %i.\n", cmd ); + } +} + +void CHudSpectator::FindNextPlayer(bool bReverse) +{ + // MOD AUTHORS: Modify the logic of this function if you want to restrict the observer to watching + // only a subset of the players. e.g. Make it check the target's team. + + int iStart; + cl_entity_t * pEnt = NULL; + + // if we are NOT in HLTV mode, spectator targets are set on server + if ( !gEngfuncs.IsSpectateOnly() ) + { + char cmdstring[32]; + // forward command to server + sprintf(cmdstring,"follownext %i",bReverse?1:0); + gEngfuncs.pfnServerCmd(cmdstring); + return; + } + + if ( g_iUser2 ) + iStart = g_iUser2; + else + iStart = 1; + + g_iUser2 = 0; + + int iCurrent = iStart; + + int iDir = bReverse ? -1 : 1; + + // make sure we have player info + gViewPort->GetAllPlayersInfo(); + + + do + { + iCurrent += iDir; + + // Loop through the clients + if (iCurrent > MAX_PLAYERS) + iCurrent = 1; + if (iCurrent < 1) + iCurrent = MAX_PLAYERS; + + pEnt = gEngfuncs.GetEntityByIndex( iCurrent ); + + if ( !IsActivePlayer( pEnt ) ) + continue; + + // MOD AUTHORS: Add checks on target here. + + g_iUser2 = iCurrent; + break; + + } while ( iCurrent != iStart ); + + // Did we find a target? + if ( !g_iUser2 ) + { + gEngfuncs.Con_DPrintf( "No observer targets.\n" ); + // take save camera position + VectorCopy(m_cameraOrigin, vJumpOrigin); + VectorCopy(m_cameraAngles, vJumpAngles); + } + else + { + // use new entity position for roaming + VectorCopy ( pEnt->origin, vJumpOrigin ); + VectorCopy ( pEnt->angles, vJumpAngles ); + } + iJumpSpectator = 1; +} + +void CHudSpectator::HandleButtonsDown( int ButtonPressed ) +{ + double time = gEngfuncs.GetClientTime(); + + int newMainMode = -1; + int newInsetMode = m_pip->value; + + // gEngfuncs.Con_Printf(" HandleButtons:%i\n", ButtonPressed ); + if ( !gViewPort ) + return; + + if ( !g_iUser1 ) + return; // dont do anything if not in spectator mode + + // don't handle buttons during normal demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() && !gEngfuncs.IsSpectateOnly() ) + return; + // Slow down mouse clicks. + if ( m_flNextObserverInput > time ) + return; + + // enable spectator screen + if ( ButtonPressed & IN_DUCK ) + gViewPort->m_pSpectatorPanel->ShowMenu(!gViewPort->m_pSpectatorPanel->m_menuVisible); + + // 'Use' changes inset window mode + if ( ButtonPressed & IN_USE ) + { + newInsetMode = ToggleInset(true); + } + + // if not in HLTV mode, buttons are handled server side + if ( gEngfuncs.IsSpectateOnly() ) + { + // changing target or chase mode not in overviewmode without inset window + + // Jump changes main window modes + if ( ButtonPressed & IN_JUMP ) + { + if ( g_iUser1 == OBS_CHASE_LOCKED ) + newMainMode = OBS_CHASE_FREE; + + else if ( g_iUser1 == OBS_CHASE_FREE ) + newMainMode = OBS_IN_EYE; + + else if ( g_iUser1 == OBS_IN_EYE ) + newMainMode = OBS_ROAMING; + + else if ( g_iUser1 == OBS_ROAMING ) + newMainMode = OBS_MAP_FREE; + + else if ( g_iUser1 == OBS_MAP_FREE ) + newMainMode = OBS_MAP_CHASE; + + else + newMainMode = OBS_CHASE_FREE; // don't use OBS_CHASE_LOCKED anymore + } + + // Attack moves to the next player + if ( ButtonPressed & (IN_ATTACK | IN_ATTACK2) ) + { + FindNextPlayer( (ButtonPressed & IN_ATTACK2) ? true:false ); + + if ( g_iUser1 == OBS_ROAMING ) + { + gEngfuncs.SetViewAngles( vJumpAngles ); + iJumpSpectator = 1; + + } + // lease directed mode if player want to see another player + m_autoDirector->value = 0.0f; + } + } + + SetModes(newMainMode, newInsetMode); + + if ( ButtonPressed & IN_FORWARD ) + m_zoomDelta = 0.01f; + + if ( ButtonPressed & IN_BACK ) + m_zoomDelta = -0.01f; + + if ( ButtonPressed & IN_MOVELEFT ) + m_moveDelta = -12.0f; + + if ( ButtonPressed & IN_MOVERIGHT ) + m_moveDelta = 12.0f; + + m_flNextObserverInput = time + 0.2; +} + +void CHudSpectator::HandleButtonsUp( int ButtonPressed ) +{ + if ( !gViewPort ) + return; + + if ( !gViewPort->m_pSpectatorPanel->isVisible() ) + return; // dont do anything if not in spectator mode + + if ( ButtonPressed & (IN_FORWARD | IN_BACK) ) + m_zoomDelta = 0.0f; + + if ( ButtonPressed & (IN_MOVELEFT | IN_MOVERIGHT) ) + m_moveDelta = 0.0f; +} +void CHudSpectator::SetModes(int iNewMainMode, int iNewInsetMode) +{ + static wrect_t crosshairRect; + + // if value == -1 keep old value + if ( iNewMainMode == -1 ) + iNewMainMode = g_iUser1; + + if ( iNewInsetMode == -1 ) + iNewInsetMode = m_pip->value; + + // inset mode is handled only clients side + m_pip->value = iNewInsetMode; + + if ( iNewMainMode < OBS_CHASE_LOCKED || iNewMainMode > OBS_MAP_CHASE ) + { + gEngfuncs.Con_Printf("Invalid spectator mode.\n"); + return; + } + + // main modes ettings will override inset window settings + if ( iNewMainMode != g_iUser1 ) + { + // if we are NOT in HLTV mode, main spectator mode is set on server + if ( !gEngfuncs.IsSpectateOnly() ) + { + char cmdstring[32]; + // forward command to server + sprintf(cmdstring,"specmode %i",iNewMainMode ); + gEngfuncs.pfnServerCmd(cmdstring); + return; + } + + if ( !g_iUser2 ) // make sure we have a target + { + // choose last Director object if still available + if ( IsActivePlayer( gEngfuncs.GetEntityByIndex( m_lastPrimaryObject ) ) ) + { + g_iUser2 = m_lastPrimaryObject; + g_iUser3 = m_lastSecondaryObject; + } + else + FindNextPlayer(false); // find any target + } + + switch ( iNewMainMode ) + { + case OBS_CHASE_LOCKED: g_iUser1 = OBS_CHASE_LOCKED; + break; + + case OBS_CHASE_FREE : g_iUser1 = OBS_CHASE_FREE; + break; + + case OBS_ROAMING : // jump to current vJumpOrigin/angle + g_iUser1 = OBS_ROAMING; + V_GetChasePos( g_iUser2, v_cl_angles, vJumpOrigin, vJumpAngles ); + gEngfuncs.SetViewAngles( vJumpAngles ); + iJumpSpectator = 1; + break; + + case OBS_IN_EYE : g_iUser1 = OBS_IN_EYE; + break; + + case OBS_MAP_FREE : g_iUser1 = OBS_MAP_FREE; + // reset user values + m_mapZoom = m_OverviewData.zoom; + m_mapOrigin = m_OverviewData.origin; + break; + + case OBS_MAP_CHASE : g_iUser1 = OBS_MAP_CHASE; + // reset user values + m_mapZoom = m_OverviewData.zoom; + m_mapOrigin = m_OverviewData.origin; + break; + } + + // enable or disable crosshair + if ( (g_iUser1 == OBS_IN_EYE) || (g_iUser1 == OBS_ROAMING) ) + { + crosshairRect.left = 24; + crosshairRect.top = 0; + crosshairRect.right = 48; + crosshairRect.bottom = 24; + + SetCrosshair( m_hCrosshair, crosshairRect, 255, 255, 255 ); + } + else + { + memset( &crosshairRect,0,sizeof(crosshairRect) ); + SetCrosshair( 0, crosshairRect, 0, 0, 0 ); + } + + char string[128]; + sprintf(string, "#Spec_Mode%d", g_iUser1 ); + sprintf(string, "%c%s", HUD_PRINTCENTER, CHudTextMessage::BufferedLocaliseTextString( string )); + gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); + } + + gViewPort->UpdateSpectatorPanel(); + +} + +bool CHudSpectator::IsActivePlayer(cl_entity_t * ent) +{ + return ( ent && + ent->player && + ent->curstate.solid != SOLID_NOT && + ent != gEngfuncs.GetLocalPlayer() && + g_PlayerInfoList[ent->index].name != NULL + ); +} + + +bool CHudSpectator::ParseOverviewFile( ) +{ + char filename[255]; + char levelname[255]; + char token[1024]; + float height; + + char *pfile = NULL; + + memset( &m_OverviewData, 0, sizeof(m_OverviewData)); + + // fill in standrd values + m_OverviewData.insetWindowX = 4; // upper left corner + m_OverviewData.insetWindowY = 4; + m_OverviewData.insetWindowHeight = 180; + m_OverviewData.insetWindowWidth = 240; + m_OverviewData.origin[0] = 0.0f; + m_OverviewData.origin[1] = 0.0f; + m_OverviewData.origin[2] = 0.0f; + m_OverviewData.zoom = 1.0f; + m_OverviewData.layers = 0; + m_OverviewData.layersHeights[0] = 0.0f; + strcpy( m_OverviewData.map, gEngfuncs.pfnGetLevelName() ); + + if ( strlen( m_OverviewData.map ) == 0 ) + return false; // not active yet + + strcpy(levelname, m_OverviewData.map + 5); + levelname[strlen(levelname)-4] = 0; + + sprintf(filename, "overviews/%s.txt", levelname ); + + pfile = (char *)gEngfuncs.COM_LoadFile( filename, 5, NULL); + + if (!pfile) + { + gEngfuncs.Con_Printf("Couldn't open file %s. Using default values for overiew mode.\n", filename ); + return false; + } + + + while (true) + { + pfile = gEngfuncs.COM_ParseFile(pfile, token); + + if (!pfile) + break; + + if ( !stricmp( token, "global" ) ) + { + // parse the global data + pfile = gEngfuncs.COM_ParseFile(pfile, token); + if ( stricmp( token, "{" ) ) + { + gEngfuncs.Con_Printf("Error parsing overview file %s. (expected { )\n", filename ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); + + while (stricmp( token, "}") ) + { + if ( !stricmp( token, "zoom" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.zoom = atof( token ); + } + else if ( !stricmp( token, "origin" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile, token); + m_OverviewData.origin[0] = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.origin[1] = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile, token); + m_OverviewData.origin[2] = atof( token ); + } + else if ( !stricmp( token, "rotated" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.rotated = atoi( token ); + } + else if ( !stricmp( token, "inset" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowX = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowY = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowWidth = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowHeight = atof( token ); + + } + else + { + gEngfuncs.Con_Printf("Error parsing overview file %s. (%s unkown)\n", filename, token ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); // parse next token + + } + } + else if ( !stricmp( token, "layer" ) ) + { + // parse a layer data + + if ( m_OverviewData.layers == OVERVIEW_MAX_LAYERS ) + { + gEngfuncs.Con_Printf("Error parsing overview file %s. ( too many layers )\n", filename ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); + + + if ( stricmp( token, "{" ) ) + { + gEngfuncs.Con_Printf("Error parsing overview file %s. (expected { )\n", filename ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); + + while (stricmp( token, "}") ) + { + if ( !stricmp( token, "image" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + strcpy(m_OverviewData.layersImages[ m_OverviewData.layers ], token); + + + } + else if ( !stricmp( token, "height" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + height = atof(token); + m_OverviewData.layersHeights[ m_OverviewData.layers ] = height; + } + else + { + gEngfuncs.Con_Printf("Error parsing overview file %s. (%s unkown)\n", filename, token ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); // parse next token + } + + m_OverviewData.layers++; + + } + } + + gEngfuncs.COM_FreeFile( pfile ); + + m_mapZoom = m_OverviewData.zoom; + m_mapOrigin = m_OverviewData.origin; + + return true; + +} + +void CHudSpectator::LoadMapSprites() +{ + // right now only support for one map layer + if (m_OverviewData.layers > 0 ) + { + m_MapSprite = gEngfuncs.LoadMapSprite( m_OverviewData.layersImages[0] ); + } + else + m_MapSprite = NULL; // the standard "unkown map" sprite will be used instead +} + +void CHudSpectator::DrawOverviewLayer() +{ + float screenaspect, xs, ys, xStep, yStep, x,y,z; + int ix,iy,i,xTiles,yTiles,frame; + + qboolean hasMapImage = m_MapSprite?TRUE:FALSE; + model_t * dummySprite = (struct model_s *)gEngfuncs.GetSpritePointer( m_hsprUnkownMap); + + if ( hasMapImage) + { + i = m_MapSprite->numframes / (4*3); + i = sqrt(i); + xTiles = i*4; + yTiles = i*3; + } + else + { + xTiles = 8; + yTiles = 6; + } + + + screenaspect = 4.0f/3.0f; + + + xs = m_OverviewData.origin[0]; + ys = m_OverviewData.origin[1]; + z = ( 90.0f - v_angles[0] ) / 90.0f; + z *= m_OverviewData.layersHeights[0]; // gOverviewData.z_min - 32; + + // i = r_overviewTexture + ( layer*OVERVIEW_X_TILES*OVERVIEW_Y_TILES ); + + gEngfuncs.pTriAPI->RenderMode( kRenderTransTexture ); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); + gEngfuncs.pTriAPI->Color4f( 1.0, 1.0, 1.0, 1.0 ); + + frame = 0; + + + // rotated view ? + if ( m_OverviewData.rotated ) + { + xStep = (2*4096.0f / m_OverviewData.zoom ) / xTiles; + yStep = -(2*4096.0f / (m_OverviewData.zoom* screenaspect) ) / yTiles; + + y = ys + (4096.0f / (m_OverviewData.zoom * screenaspect)); + + for (iy = 0; iy < yTiles; iy++) + { + x = xs - (4096.0f / (m_OverviewData.zoom)); + + for (ix = 0; ix < xTiles; ix++) + { + if (hasMapImage) + gEngfuncs.pTriAPI->SpriteTexture( m_MapSprite, frame ); + else + gEngfuncs.pTriAPI->SpriteTexture( dummySprite, 0 ); + + gEngfuncs.pTriAPI->Begin( TRI_QUADS ); + gEngfuncs.pTriAPI->TexCoord2f( 0, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x, y, z); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x+xStep ,y, z); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x+xStep, y+yStep, z); + + gEngfuncs.pTriAPI->TexCoord2f( 0, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x, y+yStep, z); + gEngfuncs.pTriAPI->End(); + + frame++; + x+= xStep; + } + + y+=yStep; + } + } + else + { + xStep = -(2*4096.0f / m_OverviewData.zoom ) / xTiles; + yStep = -(2*4096.0f / (m_OverviewData.zoom* screenaspect) ) / yTiles; + + + x = xs + (4096.0f / (m_OverviewData.zoom * screenaspect )); + + + + for (ix = 0; ix < yTiles; ix++) + { + + y = ys + (4096.0f / (m_OverviewData.zoom)); + + for (iy = 0; iy < xTiles; iy++) + { + if (hasMapImage) + gEngfuncs.pTriAPI->SpriteTexture( m_MapSprite, frame ); + else + gEngfuncs.pTriAPI->SpriteTexture( dummySprite, 0 ); + + gEngfuncs.pTriAPI->Begin( TRI_QUADS ); + gEngfuncs.pTriAPI->TexCoord2f( 0, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x, y, z); + + gEngfuncs.pTriAPI->TexCoord2f( 0, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x+xStep ,y, z); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x+xStep, y+yStep, z); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x, y+yStep, z); + gEngfuncs.pTriAPI->End(); + + frame++; + + y+=yStep; + } + + x+= xStep; + + } + } +} + +void CHudSpectator::DrawOverviewEntities() +{ + int i,ir,ig,ib; + struct model_s *hSpriteModel; + vec3_t origin, angles, point, forward, right, left, up, world, screen, offset; + float x,y,z, r,g,b, sizeScale = 4.0f; + cl_entity_t * ent; + float rmatrix[3][4]; // transformation matrix + + float zScale = (90.0f - v_angles[0] ) / 90.0f; + + + z = m_OverviewData.layersHeights[0] * zScale; + // get yellow/brown HUD color + UnpackRGB(ir,ig,ib, RGB_YELLOWISH); + r = (float)ir/255.0f; + g = (float)ig/255.0f; + b = (float)ib/255.0f; + + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); + + for (i=0; i < MAX_PLAYERS; i++ ) + m_vPlayerPos[i][2] = -1; // mark as invisible + + // draw all players + for (i=0 ; i < MAX_OVERVIEW_ENTITIES ; i++) + { + if ( !m_OverviewEntities[i].hSprite ) + continue; + + hSpriteModel = (struct model_s *)gEngfuncs.GetSpritePointer( m_OverviewEntities[i].hSprite ); + ent = m_OverviewEntities[i].entity; + + gEngfuncs.pTriAPI->SpriteTexture( hSpriteModel, 0 ); + gEngfuncs.pTriAPI->RenderMode( kRenderTransTexture ); + + // see R_DrawSpriteModel + // draws players sprite + + AngleVectors(ent->angles, right, up, NULL ); + + VectorCopy(ent->origin,origin); + + gEngfuncs.pTriAPI->Begin( TRI_QUADS ); + + gEngfuncs.pTriAPI->Color4f( 1.0, 1.0, 1.0, 1.0 ); + + gEngfuncs.pTriAPI->TexCoord2f (1, 0); + VectorMA (origin, 16.0f * sizeScale, up, point); + VectorMA (point, 16.0f * sizeScale, right, point); + point[2] *= zScale; + gEngfuncs.pTriAPI->Vertex3fv (point); + + gEngfuncs.pTriAPI->TexCoord2f (0, 0); + + VectorMA (origin, 16.0f * sizeScale, up, point); + VectorMA (point, -16.0f * sizeScale, right, point); + point[2] *= zScale; + gEngfuncs.pTriAPI->Vertex3fv (point); + + gEngfuncs.pTriAPI->TexCoord2f (0,1); + VectorMA (origin, -16.0f * sizeScale, up, point); + VectorMA (point, -16.0f * sizeScale, right, point); + point[2] *= zScale; + gEngfuncs.pTriAPI->Vertex3fv (point); + + gEngfuncs.pTriAPI->TexCoord2f (1,1); + VectorMA (origin, -16.0f * sizeScale, up, point); + VectorMA (point, 16.0f * sizeScale, right, point); + point[2] *= zScale; + gEngfuncs.pTriAPI->Vertex3fv (point); + + gEngfuncs.pTriAPI->End (); + + + if ( !ent->player) + continue; + // draw line under player icons + origin[2] *= zScale; + + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + + hSpriteModel = (struct model_s *)gEngfuncs.GetSpritePointer( m_hsprBeam ); + gEngfuncs.pTriAPI->SpriteTexture( hSpriteModel, 0 ); + + gEngfuncs.pTriAPI->Color4f(r, g, b, 0.3); + + gEngfuncs.pTriAPI->Begin ( TRI_QUADS ); + gEngfuncs.pTriAPI->TexCoord2f (1, 0); + gEngfuncs.pTriAPI->Vertex3f (origin[0]+4, origin[1]+4, origin[2]-zScale); + gEngfuncs.pTriAPI->TexCoord2f (0, 0); + gEngfuncs.pTriAPI->Vertex3f (origin[0]-4, origin[1]-4, origin[2]-zScale); + gEngfuncs.pTriAPI->TexCoord2f (0, 1); + gEngfuncs.pTriAPI->Vertex3f (origin[0]-4, origin[1]-4,z); + gEngfuncs.pTriAPI->TexCoord2f (1, 1); + gEngfuncs.pTriAPI->Vertex3f (origin[0]+4, origin[1]+4,z); + gEngfuncs.pTriAPI->End (); + + gEngfuncs.pTriAPI->Begin ( TRI_QUADS ); + gEngfuncs.pTriAPI->TexCoord2f (1, 0); + gEngfuncs.pTriAPI->Vertex3f (origin[0]-4, origin[1]+4, origin[2]-zScale); + gEngfuncs.pTriAPI->TexCoord2f (0, 0); + gEngfuncs.pTriAPI->Vertex3f (origin[0]+4, origin[1]-4, origin[2]-zScale); + gEngfuncs.pTriAPI->TexCoord2f (0, 1); + gEngfuncs.pTriAPI->Vertex3f (origin[0]+4, origin[1]-4,z); + gEngfuncs.pTriAPI->TexCoord2f (1, 1); + gEngfuncs.pTriAPI->Vertex3f (origin[0]-4, origin[1]+4,z); + gEngfuncs.pTriAPI->End (); + + // calculate screen position for name and infromation in hud::draw() + if ( gEngfuncs.pTriAPI->WorldToScreen(origin,screen) ) + continue; // object is behind viewer + + screen[0] = XPROJECT(screen[0]); + screen[1] = YPROJECT(screen[1]); + screen[2] = 0.0f; + + // calculate some offset under the icon + origin[0]+=32.0f; + origin[1]+=32.0f; + + gEngfuncs.pTriAPI->WorldToScreen(origin,offset); + + offset[0] = XPROJECT(offset[0]); + offset[1] = YPROJECT(offset[1]); + offset[2] = 0.0f; + + VectorSubtract(offset, screen, offset ); + + int playerNum = ent->index - 1; + + m_vPlayerPos[playerNum][0] = screen[0]; + m_vPlayerPos[playerNum][1] = screen[1] + Length(offset); + m_vPlayerPos[playerNum][2] = 1; // mark player as visible + } + + if ( !m_pip->value || !m_drawcone->value ) + return; + + // get current camera position and angle + + if ( m_pip->value == INSET_IN_EYE || g_iUser1 == OBS_IN_EYE ) + { + V_GetInEyePos( g_iUser2, origin, angles ); + } + else if ( m_pip->value == INSET_CHASE_FREE || g_iUser1 == OBS_CHASE_FREE ) + { + V_GetChasePos( g_iUser2, v_cl_angles, origin, angles ); + } + else if ( g_iUser1 == OBS_ROAMING ) + { + VectorCopy( v_sim_org, origin ); + VectorCopy( v_cl_angles, angles ); + } + else + V_GetChasePos( g_iUser2, NULL, origin, angles ); + + // draw camera sprite + + x = origin[0]; + y = origin[1]; + z = origin[2]; + + angles[0] = 0; // always show horizontal camera sprite + + hSpriteModel = (struct model_s *)gEngfuncs.GetSpritePointer( m_hsprCamera ); + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + gEngfuncs.pTriAPI->SpriteTexture( hSpriteModel, 0 ); + + gEngfuncs.pTriAPI->Color4f( r, g, b, 1.0 ); + + AngleVectors(angles, forward, NULL, NULL ); + VectorScale (forward, 512.0f, forward); + + offset[0] = 0.0f; + offset[1] = 45.0f; + offset[2] = 0.0f; + + AngleMatrix(offset, rmatrix ); + VectorTransform(forward, rmatrix , right ); + + offset[1]= -45.0f; + AngleMatrix(offset, rmatrix ); + VectorTransform(forward, rmatrix , left ); + + gEngfuncs.pTriAPI->Begin (TRI_TRIANGLES); + gEngfuncs.pTriAPI->TexCoord2f( 0, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x+right[0], y+right[1], (z+right[2]) * zScale); + + gEngfuncs.pTriAPI->TexCoord2f( 0, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x, y, z * zScale); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x+left[0], y+left[1], (z+left[2]) * zScale); + gEngfuncs.pTriAPI->End (); + +} + + +void CHudSpectator::DrawOverview() +{ + // draw only in sepctator mode + if ( !g_iUser1 ) + return; + + // Only draw the overview if Map Mode is selected for this view + if ( m_iDrawCycle == 0 && ( (g_iUser1 != OBS_MAP_FREE) && (g_iUser1 != OBS_MAP_CHASE) ) ) + return; + + if ( m_iDrawCycle == 1 && m_pip->value < INSET_MAP_FREE ) + return; + + DrawOverviewLayer(); + DrawOverviewEntities(); + CheckOverviewEntities(); +} +void CHudSpectator::CheckOverviewEntities() +{ + double time = gEngfuncs.GetClientTime(); + + // removes old entities from list + for ( int i = 0; i< MAX_OVERVIEW_ENTITIES; i++ ) + { + // remove entity from list if it is too old + if ( m_OverviewEntities[i].killTime < time ) + { + memset( &m_OverviewEntities[i], 0, sizeof (overviewEntity_t) ); + } + } +} + +bool CHudSpectator::AddOverviewEntity( int type, struct cl_entity_s *ent, const char *modelname) +{ + HSPRITE hSprite = 0; + double duration = -1.0f; // duration -1 means show it only this frame; + + if ( !ent ) + return false; + + if ( type == ET_PLAYER ) + { + if ( ent->curstate.solid != SOLID_NOT) + { + switch ( g_PlayerExtraInfo[ent->index].teamnumber ) + { + // blue and red teams are swapped in CS and TFC + case 1 : hSprite = m_hsprPlayerBlue; break; + case 2 : hSprite = m_hsprPlayerRed; break; + default : hSprite = m_hsprPlayer; break; + } + } + else + return false; // it's an spectator + } + else if (type == ET_NORMAL) + { + return false; + } + else + return false; + + return AddOverviewEntityToList(hSprite, ent, gEngfuncs.GetClientTime() + duration ); +} + +void CHudSpectator::DeathMessage(int victim) +{ + // find out where the victim is + cl_entity_t *pl = gEngfuncs.GetEntityByIndex(victim); + + if (pl && pl->player) + AddOverviewEntityToList(m_hsprPlayerDead, pl, gEngfuncs.GetClientTime() + 2.0f ); +} + +bool CHudSpectator::AddOverviewEntityToList(HSPRITE sprite, cl_entity_t *ent, double killTime) +{ + for ( int i = 0; i< MAX_OVERVIEW_ENTITIES; i++ ) + { + // find empty entity slot + if ( m_OverviewEntities[i].entity == NULL) + { + m_OverviewEntities[i].entity = ent; + m_OverviewEntities[i].hSprite = sprite; + m_OverviewEntities[i].killTime = killTime; + return true; + } + } + + return false; // maximum overview entities reached +} +void CHudSpectator::CheckSettings() +{ + // disallow same inset mode as main mode: + + if ( ( g_iUser1 < OBS_MAP_FREE ) && ( m_pip->value == INSET_CHASE_FREE || m_pip->value == INSET_IN_EYE ) ) + { + // otherwise both would show in World picures + m_pip->value = INSET_MAP_FREE; + } + + if ( ( g_iUser1 >= OBS_MAP_FREE ) && ( m_pip->value >= INSET_MAP_FREE ) ) + { + // both would show map views + m_pip->value = INSET_CHASE_FREE; + } + + // disble in intermission screen + if ( gHUD.m_iIntermission ) + m_pip->value = INSET_OFF; + + // check chat mode + if ( m_chatEnabled != (gHUD.m_SayText.m_HUD_saytext->value!=0) ) + { + // hud_saytext changed + m_chatEnabled = (gHUD.m_SayText.m_HUD_saytext->value!=0); + + if ( gEngfuncs.IsSpectateOnly() ) + { + // tell proxy our new chat mode + char chatcmd[32]; + sprintf(chatcmd, "ignoremsg %i", m_chatEnabled?0:1 ); + gEngfuncs.pfnServerCmd(chatcmd); + } + } + + + // draw small border around inset view, adjust upper black bar + gViewPort->m_pSpectatorPanel->EnableInsetView( m_pip->value != INSET_OFF ); +} + +int CHudSpectator::ToggleInset(bool allowOff) +{ + int newInsetMode = m_pip->value + 1; + + if ( g_iUser1 < OBS_MAP_FREE ) + { + if ( newInsetMode > INSET_MAP_CHASE ) + { + if (allowOff) + newInsetMode = INSET_OFF; + else + newInsetMode = INSET_MAP_FREE; + } + + if ( newInsetMode == INSET_CHASE_FREE ) + newInsetMode = INSET_MAP_FREE; + } + else + { + if ( newInsetMode > INSET_IN_EYE ) + { + if (allowOff) + newInsetMode = INSET_OFF; + else + newInsetMode = INSET_CHASE_FREE; + } + } + + return newInsetMode; +} +void CHudSpectator::Reset() +{ + // Reset HUD + if ( strcmp( m_OverviewData.map, gEngfuncs.pfnGetLevelName() ) ) + { + // update level overview if level changed + ParseOverviewFile(); + LoadMapSprites(); + SetSpectatorStartPosition(); + } + + memset( &m_OverviewEntities, 0, sizeof(m_OverviewEntities)); +} + +void CHudSpectator::InitHUDData() +{ + m_lastPrimaryObject = m_lastSecondaryObject = 0; + m_flNextObserverInput = 0.0f; + m_lastHudMessage = 0; + m_iSpectatorNumber = 0; + iJumpSpectator = 0; + g_iUser1 = g_iUser2 = 0; + + memset( &m_OverviewData, 0, sizeof(m_OverviewData)); + memset( &m_OverviewEntities, 0, sizeof(m_OverviewEntities)); + + if ( gEngfuncs.IsSpectateOnly() || gEngfuncs.pDemoAPI->IsPlayingback() ) + m_autoDirector->value = 1.0f; + else + m_autoDirector->value = 0.0f; + + SetModes( OBS_CHASE_FREE, INSET_OFF ); + + g_iUser2 = 0; // fake not target until first camera command + + // reset HUD FOV + gHUD.m_iFOV = CVAR_GET_FLOAT("default_fov"); + Reset(); + SetSpectatorStartPosition(); +} + diff --git a/dmc/cl_dll/hud_spectator.h b/dmc/cl_dll/hud_spectator.h new file mode 100644 index 0000000..ca57ea8 --- /dev/null +++ b/dmc/cl_dll/hud_spectator.h @@ -0,0 +1,129 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef SPECTATOR_H +#define SPECTATOR_H +#pragma once + +#include "cl_entity.h" + + + +#define INSET_OFF 0 +#define INSET_CHASE_FREE 1 +#define INSET_IN_EYE 2 +#define INSET_MAP_FREE 3 +#define INSET_MAP_CHASE 4 + +#define MAX_SPEC_HUD_MESSAGES 8 + + + +#define OVERVIEW_TILE_SIZE 128 // don't change this +#define OVERVIEW_MAX_LAYERS 1 + +//----------------------------------------------------------------------------- +// Purpose: Handles the drawing of the spectator stuff (camera & top-down map and all the things on it ) +//----------------------------------------------------------------------------- + +typedef struct overviewInfo_s { + char map[64]; // cl.levelname or empty + vec3_t origin; // center of map + float zoom; // zoom of map images + int layers; // how may layers do we have + float layersHeights[OVERVIEW_MAX_LAYERS]; + char layersImages[OVERVIEW_MAX_LAYERS][255]; + qboolean rotated; // are map images rotated (90 degrees) ? + + int insetWindowX; + int insetWindowY; + int insetWindowHeight; + int insetWindowWidth; +} overviewInfo_t; + +typedef struct overviewEntity_s { + + HSPRITE hSprite; + struct cl_entity_s * entity; + double killTime; +} overviewEntity_t; + +#define MAX_OVERVIEW_ENTITIES 128 + +class CHudSpectator : public CHudBase +{ +public: + void Reset(); + int ToggleInset(bool allowOff); + void CheckSettings(); + void InitHUDData( void ); + bool AddOverviewEntityToList( HSPRITE sprite, cl_entity_t * ent, double killTime); + void DeathMessage(int victim); + bool AddOverviewEntity( int type, struct cl_entity_s *ent, const char *modelname ); + void CheckOverviewEntities(); + void DrawOverview(); + void DrawOverviewEntities(); + void GetMapPosition( float * returnvec ); + void DrawOverviewLayer(); + void LoadMapSprites(); + bool ParseOverviewFile(); + bool IsActivePlayer(cl_entity_t * ent); + void SetModes(int iMainMode, int iInsetMode); + void HandleButtonsDown(int ButtonPressed); + void HandleButtonsUp(int ButtonPressed); + void FindNextPlayer( bool bReverse ); + void DirectorMessage( int iSize, void *pbuf ); + void SetSpectatorStartPosition(); + int Init(); + int VidInit(); + + int Draw(float flTime); + + int m_iDrawCycle; + client_textmessage_t m_HUDMessages[MAX_SPEC_HUD_MESSAGES]; + char m_HUDMessageText[MAX_SPEC_HUD_MESSAGES][128]; + int m_lastHudMessage; + overviewInfo_t m_OverviewData; + overviewEntity_t m_OverviewEntities[MAX_OVERVIEW_ENTITIES]; + int m_iObserverFlags; + int m_iSpectatorNumber; + + float m_mapZoom; // zoom the user currently uses + vec3_t m_mapOrigin; // origin where user rotates around + cvar_t * m_drawnames; + cvar_t * m_drawcone; + cvar_t * m_drawstatus; + cvar_t * m_autoDirector; + cvar_t * m_pip; + + + qboolean m_chatEnabled; + + vec3_t m_cameraOrigin; // a help camera + vec3_t m_cameraAngles; // and it's angles + + +private: + vec3_t m_vPlayerPos[MAX_PLAYERS]; + HSPRITE m_hsprPlayerBlue; + HSPRITE m_hsprPlayerRed; + HSPRITE m_hsprPlayer; + HSPRITE m_hsprCamera; + HSPRITE m_hsprPlayerDead; + HSPRITE m_hsprViewcone; + HSPRITE m_hsprUnkownMap; + HSPRITE m_hsprBeam; + HSPRITE m_hCrosshair; + struct model_s * m_MapSprite; // each layer image is saved in one sprite, where each tile is a sprite frame + float m_flNextObserverInput; + float m_zoomDelta; + float m_moveDelta; + int m_lastPrimaryObject; + int m_lastSecondaryObject; +}; + +#endif // SPECTATOR_H diff --git a/dmc/cl_dll/hud_update.cpp b/dmc/cl_dll/hud_update.cpp new file mode 100644 index 0000000..7f2b2b8 --- /dev/null +++ b/dmc/cl_dll/hud_update.cpp @@ -0,0 +1,56 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud_update.cpp +// + +#include +#include "hud.h" +#include "cl_util.h" +#include +#include + +int CL_ButtonBits( int ); +void CL_ResetButtonBits( int bits ); + +extern float v_idlescale; +float in_fov; +extern void HUD_SetCmdBits( int bits ); +int iCarriedWeapons; + +int CHud::UpdateClientData(client_data_t *cdata, float time) +{ + memcpy(m_vecOrigin, cdata->origin, sizeof(vec3_t)); + memcpy(m_vecAngles, cdata->viewangles, sizeof(vec3_t)); + + m_iKeyBits = CL_ButtonBits( 0 ); + m_iWeaponBits = cdata->iWeaponBits; + + in_fov = cdata->fov; + + Think(); + + cdata->fov = m_iFOV; + + v_idlescale = m_iConcussionEffect; + + CL_ResetButtonBits( m_iKeyBits ); + + // return 1 if in anything in the client_data struct has been changed, 0 otherwise + return 1; +} + + + diff --git a/dmc/cl_dll/in_camera.cpp b/dmc/cl_dll/in_camera.cpp new file mode 100644 index 0000000..319141c --- /dev/null +++ b/dmc/cl_dll/in_camera.cpp @@ -0,0 +1,628 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" + +#include "SDL2/SDL_mouse.h" +#include "port.h" + +float CL_KeyState (kbutton_t *key); + +extern "C" +{ + void EXPORT CAM_Think( void ); + int EXPORT CL_IsThirdPerson( void ); + void EXPORT CL_CameraOffset( float *ofs ); +} + +extern cl_enginefunc_t gEngfuncs; + +//-------------------------------------------------- Constants + +#define CAM_DIST_DELTA 1.0 +#define CAM_ANGLE_DELTA 2.5 +#define CAM_ANGLE_SPEED 2.5 +#define CAM_MIN_DIST 30.0 +#define CAM_ANGLE_MOVE .5 +#define MAX_ANGLE_DIFF 10.0 +#define PITCH_MAX 90.0 +#define PITCH_MIN 0 +#define YAW_MAX 135.0 +#define YAW_MIN -135.0 + +enum ECAM_Command +{ + CAM_COMMAND_NONE = 0, + CAM_COMMAND_TOTHIRDPERSON = 1, + CAM_COMMAND_TOFIRSTPERSON = 2 +}; + +//-------------------------------------------------- Global Variables + +cvar_t *cam_command; +cvar_t *cam_snapto; +cvar_t *cam_idealyaw; +cvar_t *cam_idealpitch; +cvar_t *cam_idealdist; +cvar_t *cam_contain; + +cvar_t *c_maxpitch; +cvar_t *c_minpitch; +cvar_t *c_maxyaw; +cvar_t *c_minyaw; +cvar_t *c_maxdistance; +cvar_t *c_mindistance; + +// pitch, yaw, dist +vec3_t cam_ofs; + + +// In third person +int cam_thirdperson; +int cam_mousemove; //true if we are moving the cam with the mouse, False if not +int iMouseInUse=0; +int cam_distancemove; +extern int mouse_x, mouse_y; //used to determine what the current x and y values are +int cam_old_mouse_x, cam_old_mouse_y; //holds the last ticks mouse movement +POINT cam_mouse; +//-------------------------------------------------- Local Variables + +static kbutton_t cam_pitchup, cam_pitchdown, cam_yawleft, cam_yawright; +static kbutton_t cam_in, cam_out, cam_move; + +//-------------------------------------------------- Prototypes + +void CAM_ToThirdPerson(void); +void CAM_ToFirstPerson(void); +void CAM_StartDistance(void); +void CAM_EndDistance(void); + +void SDL_GetCursorPos( POINT *p ) +{ + SDL_GetMouseState( (int *)&p->x, (int *)&p->y ); +} + +void SDL_SetCursorPos( const int x, const int y ) +{ +} + + +//-------------------------------------------------- Local Functions + +float MoveToward( float cur, float goal, float maxspeed ) +{ + if( cur != goal ) + { + if( abs( cur - goal ) > 180.0 ) + { + if( cur < goal ) + cur += 360.0; + else + cur -= 360.0; + } + + if( cur < goal ) + { + if( cur < goal - 1.0 ) + cur += ( goal - cur ) / 4.0; + else + cur = goal; + } + else + { + if( cur > goal + 1.0 ) + cur -= ( cur - goal ) / 4.0; + else + cur = goal; + } + } + + + // bring cur back into range + if( cur < 0 ) + cur += 360.0; + else if( cur >= 360 ) + cur -= 360; + + return cur; +} + + +//-------------------------------------------------- Gobal Functions + +typedef struct +{ + vec3_t boxmins, boxmaxs;// enclose the test object along entire move + float *mins, *maxs; // size of the moving object + vec3_t mins2, maxs2; // size when clipping against mosnters + float *start, *end; + trace_t trace; + int type; + edict_t *passedict; + qboolean monsterclip; +} moveclip_t; + +extern trace_t SV_ClipMoveToEntity (edict_t *ent, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end); + +void EXPORT CAM_Think( void ) +{ + vec3_t origin; + vec3_t ext, pnt, camForward, camRight, camUp; + moveclip_t clip; + float dist; + vec3_t camAngles; + float flSensitivity; +#ifdef LATER + int i; +#endif + vec3_t viewangles; + + switch( (int) cam_command->value ) + { + case CAM_COMMAND_TOTHIRDPERSON: + CAM_ToThirdPerson(); + break; + + case CAM_COMMAND_TOFIRSTPERSON: + CAM_ToFirstPerson(); + break; + + case CAM_COMMAND_NONE: + default: + break; + } + + if( !cam_thirdperson ) + return; + +#ifdef LATER + if ( cam_contain->value ) + { + gEngfuncs.GetClientOrigin( origin ); + ext[0] = ext[1] = ext[2] = 0.0; + } +#endif + + camAngles[ PITCH ] = cam_idealpitch->value; + camAngles[ YAW ] = cam_idealyaw->value; + dist = cam_idealdist->value; + // + //movement of the camera with the mouse + // + if (cam_mousemove) + { + //get windows cursor position + SDL_GetCursorPos (&cam_mouse); + //check for X delta values and adjust accordingly + //eventually adjust YAW based on amount of movement + //don't do any movement of the cam using YAW/PITCH if we are zooming in/out the camera + if (!cam_distancemove) + { + + //keep the camera within certain limits around the player (ie avoid certain bad viewing angles) + if (cam_mouse.x>gEngfuncs.GetWindowCenterX()) + { + //if ((camAngles[YAW]>=225.0)||(camAngles[YAW]<135.0)) + if (camAngles[YAW]value) + { + camAngles[ YAW ] += (CAM_ANGLE_MOVE)*((cam_mouse.x-gEngfuncs.GetWindowCenterX())/2); + } + if (camAngles[YAW]>c_maxyaw->value) + { + + camAngles[YAW]=c_maxyaw->value; + } + } + else if (cam_mouse.x225.0)) + if (camAngles[YAW]>c_minyaw->value) + { + camAngles[ YAW ] -= (CAM_ANGLE_MOVE)* ((gEngfuncs.GetWindowCenterX()-cam_mouse.x)/2); + + } + if (camAngles[YAW]value) + { + camAngles[YAW]=c_minyaw->value; + + } + } + + //check for y delta values and adjust accordingly + //eventually adjust PITCH based on amount of movement + //also make sure camera is within bounds + if (cam_mouse.y>gEngfuncs.GetWindowCenterY()) + { + if(camAngles[PITCH]value) + { + camAngles[PITCH] +=(CAM_ANGLE_MOVE)* ((cam_mouse.y-gEngfuncs.GetWindowCenterY())/2); + } + if (camAngles[PITCH]>c_maxpitch->value) + { + camAngles[PITCH]=c_maxpitch->value; + } + } + else if (cam_mouse.yc_minpitch->value) + { + camAngles[PITCH] -= (CAM_ANGLE_MOVE)*((gEngfuncs.GetWindowCenterY()-cam_mouse.y)/2); + } + if (camAngles[PITCH]value) + { + camAngles[PITCH]=c_minpitch->value; + } + } + + //set old mouse coordinates to current mouse coordinates + //since we are done with the mouse + + if ( ( flSensitivity = gHUD.GetSensitivity() ) != 0 ) + { + cam_old_mouse_x=cam_mouse.x*flSensitivity; + cam_old_mouse_y=cam_mouse.y*flSensitivity; + } + else + { + cam_old_mouse_x=cam_mouse.x; + cam_old_mouse_y=cam_mouse.y; + } + SDL_SetCursorPos (gEngfuncs.GetWindowCenterX(), gEngfuncs.GetWindowCenterY()); + } + } + + //Nathan code here + if( CL_KeyState( &cam_pitchup ) ) + camAngles[ PITCH ] += CAM_ANGLE_DELTA; + else if( CL_KeyState( &cam_pitchdown ) ) + camAngles[ PITCH ] -= CAM_ANGLE_DELTA; + + if( CL_KeyState( &cam_yawleft ) ) + camAngles[ YAW ] -= CAM_ANGLE_DELTA; + else if( CL_KeyState( &cam_yawright ) ) + camAngles[ YAW ] += CAM_ANGLE_DELTA; + + if( CL_KeyState( &cam_in ) ) + { + dist -= CAM_DIST_DELTA; + if( dist < CAM_MIN_DIST ) + { + // If we go back into first person, reset the angle + camAngles[ PITCH ] = 0; + camAngles[ YAW ] = 0; + dist = CAM_MIN_DIST; + } + + } + else if( CL_KeyState( &cam_out ) ) + dist += CAM_DIST_DELTA; + + if (cam_distancemove) + { + if (cam_mouse.y>gEngfuncs.GetWindowCenterY()) + { + if(distvalue) + { + dist +=CAM_DIST_DELTA * ((cam_mouse.y-gEngfuncs.GetWindowCenterY())/2); + } + if (dist>c_maxdistance->value) + { + dist=c_maxdistance->value; + } + } + else if (cam_mouse.yc_mindistance->value) + { + dist -= (CAM_DIST_DELTA)*((gEngfuncs.GetWindowCenterY()-cam_mouse.y)/2); + } + if (distvalue) + { + dist=c_mindistance->value; + } + } + //set old mouse coordinates to current mouse coordinates + //since we are done with the mouse + cam_old_mouse_x=cam_mouse.x*gHUD.GetSensitivity(); + cam_old_mouse_y=cam_mouse.y*gHUD.GetSensitivity(); + SDL_SetCursorPos (gEngfuncs.GetWindowCenterX(), gEngfuncs.GetWindowCenterY()); + } +#ifdef LATER + if( cam_contain->value ) + { + // check new ideal + VectorCopy( origin, pnt ); + AngleVectors( camAngles, camForward, camRight, camUp ); + for (i=0 ; i<3 ; i++) + pnt[i] += -dist*camForward[i]; + + // check line from r_refdef.vieworg to pnt + memset ( &clip, 0, sizeof ( moveclip_t ) ); + clip.trace = SV_ClipMoveToEntity( sv.edicts, r_refdef.vieworg, ext, ext, pnt ); + if( clip.trace.fraction == 1.0 ) + { + // update ideal + cam_idealpitch->value = camAngles[ PITCH ]; + cam_idealyaw->value = camAngles[ YAW ]; + cam_idealdist->value = dist; + } + } + else +#endif + { + // update ideal + cam_idealpitch->value = camAngles[ PITCH ]; + cam_idealyaw->value = camAngles[ YAW ]; + cam_idealdist->value = dist; + } + + // Move towards ideal + VectorCopy( cam_ofs, camAngles ); + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + if( cam_snapto->value ) + { + camAngles[ YAW ] = cam_idealyaw->value + viewangles[ YAW ]; + camAngles[ PITCH ] = cam_idealpitch->value + viewangles[ PITCH ]; + camAngles[ 2 ] = cam_idealdist->value; + } + else + { + if( camAngles[ YAW ] - viewangles[ YAW ] != cam_idealyaw->value ) + camAngles[ YAW ] = MoveToward( camAngles[ YAW ], cam_idealyaw->value + viewangles[ YAW ], CAM_ANGLE_SPEED ); + + if( camAngles[ PITCH ] - viewangles[ PITCH ] != cam_idealpitch->value ) + camAngles[ PITCH ] = MoveToward( camAngles[ PITCH ], cam_idealpitch->value + viewangles[ PITCH ], CAM_ANGLE_SPEED ); + + if( abs( camAngles[ 2 ] - cam_idealdist->value ) < 2.0 ) + camAngles[ 2 ] = cam_idealdist->value; + else + camAngles[ 2 ] += ( cam_idealdist->value - camAngles[ 2 ] ) / 4.0; + } +#ifdef LATER + if( cam_contain->value ) + { + // Test new position + dist = camAngles[ ROLL ]; + camAngles[ ROLL ] = 0; + + VectorCopy( origin, pnt ); + AngleVectors( camAngles, camForward, camRight, camUp ); + for (i=0 ; i<3 ; i++) + pnt[i] += -dist*camForward[i]; + + // check line from r_refdef.vieworg to pnt + memset ( &clip, 0, sizeof ( moveclip_t ) ); + ext[0] = ext[1] = ext[2] = 0.0; + clip.trace = SV_ClipMoveToEntity( sv.edicts, r_refdef.vieworg, ext, ext, pnt ); + if( clip.trace.fraction != 1.0 ) + return; + } +#endif + cam_ofs[ 0 ] = camAngles[ 0 ]; + cam_ofs[ 1 ] = camAngles[ 1 ]; + cam_ofs[ 2 ] = dist; +} + +extern void KeyDown (kbutton_t *b); // HACK +extern void KeyUp (kbutton_t *b); // HACK + +void CAM_PitchUpDown(void) { KeyDown( &cam_pitchup ); } +void CAM_PitchUpUp(void) { KeyUp( &cam_pitchup ); } +void CAM_PitchDownDown(void) { KeyDown( &cam_pitchdown ); } +void CAM_PitchDownUp(void) { KeyUp( &cam_pitchdown ); } +void CAM_YawLeftDown(void) { KeyDown( &cam_yawleft ); } +void CAM_YawLeftUp(void) { KeyUp( &cam_yawleft ); } +void CAM_YawRightDown(void) { KeyDown( &cam_yawright ); } +void CAM_YawRightUp(void) { KeyUp( &cam_yawright ); } +void CAM_InDown(void) { KeyDown( &cam_in ); } +void CAM_InUp(void) { KeyUp( &cam_in ); } +void CAM_OutDown(void) { KeyDown( &cam_out ); } +void CAM_OutUp(void) { KeyUp( &cam_out ); } + +void CAM_ToThirdPerson(void) +{ + //Only allow cam_command if we are running a debug version of the client.dll +#ifndef _DEBUG + return; +#endif + + vec3_t viewangles; + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + if( !cam_thirdperson ) + { + cam_thirdperson = 1; + + cam_ofs[ YAW ] = viewangles[ YAW ]; + cam_ofs[ PITCH ] = viewangles[ PITCH ]; + cam_ofs[ 2 ] = CAM_MIN_DIST; + } + + gEngfuncs.Cvar_SetValue( "cam_command", 0 ); +} + +void CAM_ToFirstPerson(void) +{ + cam_thirdperson = 0; + + gEngfuncs.Cvar_SetValue( "cam_command", 0 ); +} + +void CAM_ToggleSnapto( void ) +{ + cam_snapto->value = !cam_snapto->value; +} + +void CAM_Init( void ) +{ + gEngfuncs.pfnAddCommand( "+campitchup", CAM_PitchUpDown ); + gEngfuncs.pfnAddCommand( "-campitchup", CAM_PitchUpUp ); + gEngfuncs.pfnAddCommand( "+campitchdown", CAM_PitchDownDown ); + gEngfuncs.pfnAddCommand( "-campitchdown", CAM_PitchDownUp ); + gEngfuncs.pfnAddCommand( "+camyawleft", CAM_YawLeftDown ); + gEngfuncs.pfnAddCommand( "-camyawleft", CAM_YawLeftUp ); + gEngfuncs.pfnAddCommand( "+camyawright", CAM_YawRightDown ); + gEngfuncs.pfnAddCommand( "-camyawright", CAM_YawRightUp ); + gEngfuncs.pfnAddCommand( "+camin", CAM_InDown ); + gEngfuncs.pfnAddCommand( "-camin", CAM_InUp ); + gEngfuncs.pfnAddCommand( "+camout", CAM_OutDown ); + gEngfuncs.pfnAddCommand( "-camout", CAM_OutUp ); + gEngfuncs.pfnAddCommand( "thirdperson", CAM_ToThirdPerson ); + gEngfuncs.pfnAddCommand( "firstperson", CAM_ToFirstPerson ); + gEngfuncs.pfnAddCommand( "+cammousemove",CAM_StartMouseMove); + gEngfuncs.pfnAddCommand( "-cammousemove",CAM_EndMouseMove); + gEngfuncs.pfnAddCommand( "+camdistance", CAM_StartDistance ); + gEngfuncs.pfnAddCommand( "-camdistance", CAM_EndDistance ); + gEngfuncs.pfnAddCommand( "snapto", CAM_ToggleSnapto ); + + cam_command = gEngfuncs.pfnRegisterVariable ( "cam_command", "0", 0 ); // tells camera to go to thirdperson + cam_snapto = gEngfuncs.pfnRegisterVariable ( "cam_snapto", "0", 0 ); // snap to thirdperson view + cam_idealyaw = gEngfuncs.pfnRegisterVariable ( "cam_idealyaw", "90", 0 ); // thirdperson yaw + cam_idealpitch = gEngfuncs.pfnRegisterVariable ( "cam_idealpitch", "0", 0 ); // thirperson pitch + cam_idealdist = gEngfuncs.pfnRegisterVariable ( "cam_idealdist", "64", 0 ); // thirdperson distance + cam_contain = gEngfuncs.pfnRegisterVariable ( "cam_contain", "0", 0 ); // contain camera to world + + c_maxpitch = gEngfuncs.pfnRegisterVariable ( "c_maxpitch", "90.0", 0 ); + c_minpitch = gEngfuncs.pfnRegisterVariable ( "c_minpitch", "0.0", 0 ); + c_maxyaw = gEngfuncs.pfnRegisterVariable ( "c_maxyaw", "135.0", 0 ); + c_minyaw = gEngfuncs.pfnRegisterVariable ( "c_minyaw", "-135.0", 0 ); + c_maxdistance = gEngfuncs.pfnRegisterVariable ( "c_maxdistance", "200.0", 0 ); + c_mindistance = gEngfuncs.pfnRegisterVariable ( "c_mindistance", "30.0", 0 ); +} + +void CAM_ClearStates( void ) +{ + vec3_t viewangles; + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + cam_pitchup.state = 0; + cam_pitchdown.state = 0; + cam_yawleft.state = 0; + cam_yawright.state = 0; + cam_in.state = 0; + cam_out.state = 0; + + cam_thirdperson = 0; + cam_command->value = 0; + cam_mousemove=0; + + cam_snapto->value = 0; + cam_distancemove = 0; + + cam_ofs[ 0 ] = 0.0; + cam_ofs[ 1 ] = 0.0; + cam_ofs[ 2 ] = CAM_MIN_DIST; + + cam_idealpitch->value = viewangles[ PITCH ]; + cam_idealyaw->value = viewangles[ YAW ]; + cam_idealdist->value = CAM_MIN_DIST; +} + +void CAM_StartMouseMove(void) +{ + float flSensitivity; + + //only move the cam with mouse if we are in third person. + if (cam_thirdperson) + { + //set appropriate flags and initialize the old mouse position + //variables for mouse camera movement + if (!cam_mousemove) + { + cam_mousemove=1; + iMouseInUse=1; + SDL_GetCursorPos (&cam_mouse); + + if ( ( flSensitivity = gHUD.GetSensitivity() ) != 0 ) + { + cam_old_mouse_x=cam_mouse.x*flSensitivity; + cam_old_mouse_y=cam_mouse.y*flSensitivity; + } + else + { + cam_old_mouse_x=cam_mouse.x; + cam_old_mouse_y=cam_mouse.y; + } + } + } + //we are not in 3rd person view..therefore do not allow camera movement + else + { + cam_mousemove=0; + iMouseInUse=0; + } +} + +//the key has been released for camera movement +//tell the engine that mouse camera movement is off +void CAM_EndMouseMove(void) +{ + cam_mousemove=0; + iMouseInUse=0; +} + + +//---------------------------------------------------------- +//routines to start the process of moving the cam in or out +//using the mouse +//---------------------------------------------------------- +void CAM_StartDistance(void) +{ + //only move the cam with mouse if we are in third person. + if (cam_thirdperson) + { + //set appropriate flags and initialize the old mouse position + //variables for mouse camera movement + if (!cam_distancemove) + { + cam_distancemove=1; + cam_mousemove=1; + iMouseInUse=1; + SDL_GetCursorPos (&cam_mouse); + cam_old_mouse_x=cam_mouse.x*gHUD.GetSensitivity(); + cam_old_mouse_y=cam_mouse.y*gHUD.GetSensitivity(); + } + } + //we are not in 3rd person view..therefore do not allow camera movement + else + { + cam_distancemove=0; + cam_mousemove=0; + iMouseInUse=0; + } +} + +//the key has been released for camera movement +//tell the engine that mouse camera movement is off +void CAM_EndDistance(void) +{ + cam_distancemove=0; + cam_mousemove=0; + iMouseInUse=0; +} + +int EXPORT CL_IsThirdPerson( void ) +{ + return cam_thirdperson ? 1 : 0; +} + +void EXPORT CL_CameraOffset( float *ofs ) +{ + VectorCopy( cam_ofs, ofs ); +} diff --git a/dmc/cl_dll/in_defs.h b/dmc/cl_dll/in_defs.h new file mode 100644 index 0000000..19ae004 --- /dev/null +++ b/dmc/cl_dll/in_defs.h @@ -0,0 +1,21 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( IN_DEFSH ) +#define IN_DEFSH +#pragma once + +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 + +#define DLLEXPORT __declspec( dllexport ) + +#endif \ No newline at end of file diff --git a/dmc/cl_dll/input.cpp b/dmc/cl_dll/input.cpp new file mode 100644 index 0000000..b463100 --- /dev/null +++ b/dmc/cl_dll/input.cpp @@ -0,0 +1,1039 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// cl.input.c -- builds an intended movement command to send to the server + +//xxxxxx Move bob and pitch drifting code here and other stuff from view if needed + +// Quake is a trademark of Id Software, Inc., (c) 1996 Id Software, Inc. All +// rights reserved. +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +extern "C" +{ +#include "kbutton.h" +} +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "view.h" +#include +#include +#include "vgui_viewport.h" +#include "voice_status.h" + +extern "C" +{ + struct kbutton_s EXPORT *KB_Find( const char *name ); + void EXPORT CL_CreateMove ( float frametime, struct usercmd_s *cmd, int active ); + void EXPORT HUD_Shutdown( void ); + int EXPORT HUD_Key_Event( int eventcode, int keynum, const char *pszCurrentBinding ); +} + +extern int g_weaponselect; +extern cl_enginefunc_t gEngfuncs; + +// Defined in pm_math.c +extern "C" float anglemod( float a ); + +void IN_Init (void); +void IN_Move ( float frametime, usercmd_t *cmd); +void IN_Shutdown( void ); +void V_Init( void ); +int CL_ButtonBits( int ); + +// xxx need client dll function to get and clear impuse +extern cvar_t *in_joystick; + +int in_impulse = 0; +int in_cancel = 0; + +cvar_t *m_pitch; +cvar_t *m_yaw; +cvar_t *m_forward; +cvar_t *m_side; + +cvar_t *lookstrafe; +cvar_t *lookspring; +cvar_t *cl_pitchup; +cvar_t *cl_pitchdown; +cvar_t *cl_upspeed; +cvar_t *cl_forwardspeed; +cvar_t *cl_backspeed; +cvar_t *cl_sidespeed; +cvar_t *cl_movespeedkey; +cvar_t *cl_yawspeed; +cvar_t *cl_pitchspeed; +cvar_t *cl_anglespeedkey; +cvar_t *cl_vsmoothing; +/* +=============================================================================== + +KEY BUTTONS + +Continuous button event tracking is complicated by the fact that two different +input sources (say, mouse button 1 and the control key) can both press the +same button, but the button should only be released when both of the +pressing key have been released. + +When a key event issues a button command (+forward, +attack, etc), it appends +its key number as a parameter to the command so it can be matched up with +the release. + +state bit 0 is the current state of the key +state bit 1 is edge triggered on the up to down transition +state bit 2 is edge triggered on the down to up transition + +=============================================================================== +*/ + + +kbutton_t in_mlook; +kbutton_t in_klook; +kbutton_t in_jlook; +kbutton_t in_left; +kbutton_t in_right; +kbutton_t in_forward; +kbutton_t in_back; +kbutton_t in_lookup; +kbutton_t in_lookdown; +kbutton_t in_moveleft; +kbutton_t in_moveright; +kbutton_t in_strafe; +kbutton_t in_speed; +kbutton_t in_use; +kbutton_t in_jump; +kbutton_t in_attack; +kbutton_t in_attack2; +kbutton_t in_up; +kbutton_t in_down; +kbutton_t in_duck; +kbutton_t in_reload; +kbutton_t in_alt1; +kbutton_t in_score; +kbutton_t in_break; +kbutton_t in_graph; // Display the netgraph + +typedef struct kblist_s +{ + struct kblist_s *next; + kbutton_t *pkey; + char name[32]; +} kblist_t; + +kblist_t *g_kbkeys = NULL; + +/* +============ +KB_ConvertString + +Removes references to +use and replaces them with the keyname in the output string. If + a binding is unfound, then the original text is retained. +NOTE: Only works for text with +word in it. +============ +*/ +int KB_ConvertString( char *in, char **ppout ) +{ + char sz[ 4096 ]; + char binding[ 64 ]; + char *p; + char *pOut; + char *pEnd; + const char *pBinding; + + if ( !ppout ) + return 0; + + *ppout = NULL; + p = in; + pOut = sz; + while ( *p ) + { + if ( *p == '+' ) + { + pEnd = binding; + while ( *p && ( isalnum( *p ) || ( pEnd == binding ) ) && ( ( pEnd - binding ) < 63 ) ) + { + *pEnd++ = *p++; + } + + *pEnd = '\0'; + + pBinding = NULL; + if ( strlen( binding + 1 ) > 0 ) + { + // See if there is a binding for binding? + pBinding = gEngfuncs.Key_LookupBinding( binding + 1 ); + } + + if ( pBinding ) + { + *pOut++ = '['; + pEnd = (char *)pBinding; + } + else + { + pEnd = binding; + } + + while ( *pEnd ) + { + *pOut++ = *pEnd++; + } + + if ( pBinding ) + { + *pOut++ = ']'; + } + } + else + { + *pOut++ = *p++; + } + } + + *pOut = '\0'; + + pOut = ( char * )malloc( strlen( sz ) + 1 ); + strcpy( pOut, sz ); + *ppout = pOut; + + return 1; +} + +/* +============ +KB_Find + +Allows the engine to get a kbutton_t directly ( so it can check +mlook state, etc ) for saving out to .cfg files +============ +*/ +struct kbutton_s EXPORT *KB_Find( const char *name ) +{ + kblist_t *p; + p = g_kbkeys; + while ( p ) + { + if ( !stricmp( name, p->name ) ) + return p->pkey; + + p = p->next; + } + return NULL; +} + +/* +============ +KB_Add + +Add a kbutton_t * to the list of pointers the engine can retrieve via KB_Find +============ +*/ +void KB_Add( const char *name, kbutton_t *pkb ) +{ + kblist_t *p; + kbutton_t *kb; + + kb = KB_Find( name ); + + if ( kb ) + return; + + p = ( kblist_t * )malloc( sizeof( kblist_t ) ); + memset( p, 0, sizeof( *p ) ); + + strcpy( p->name, name ); + p->pkey = pkb; + + p->next = g_kbkeys; + g_kbkeys = p; +} + +/* +============ +KB_Init + +Add kbutton_t definitions that the engine can query if needed +============ +*/ +void KB_Init( void ) +{ + g_kbkeys = NULL; + + KB_Add( "in_graph", &in_graph ); + KB_Add( "in_mlook", &in_mlook ); + KB_Add( "in_jlook", &in_jlook ); +} + +/* +============ +KB_Shutdown + +Clear kblist +============ +*/ +void KB_Shutdown( void ) +{ + kblist_t *p, *n; + p = g_kbkeys; + while ( p ) + { + n = p->next; + free( p ); + p = n; + } + g_kbkeys = NULL; +} + +/* +============ +KeyDown +============ +*/ +void KeyDown (kbutton_t *b) +{ + int k; + char *c; + + c = gEngfuncs.Cmd_Argv(1); + if (c[0]) + k = atoi(c); + else + k = -1; // typed manually at the console for continuous down + + if (k == b->down[0] || k == b->down[1]) + return; // repeating key + + if (!b->down[0]) + b->down[0] = k; + else if (!b->down[1]) + b->down[1] = k; + else + { + gEngfuncs.Con_DPrintf ("Three keys down for a button '%c' '%c' '%c'!\n", b->down[0], b->down[1], c); + return; + } + + if (b->state & 1) + return; // still down + b->state |= 1 + 2; // down + impulse down +} + +/* +============ +KeyUp +============ +*/ +void KeyUp (kbutton_t *b) +{ + int k; + char *c; + + c = gEngfuncs.Cmd_Argv(1); + if (c[0]) + k = atoi(c); + else + { // typed manually at the console, assume for unsticking, so clear all + b->down[0] = b->down[1] = 0; + b->state = 4; // impulse up + return; + } + + if (b->down[0] == k) + b->down[0] = 0; + else if (b->down[1] == k) + b->down[1] = 0; + else + return; // key up without coresponding down (menu pass through) + if (b->down[0] || b->down[1]) + { + //Con_Printf ("Keys down for button: '%c' '%c' '%c' (%d,%d,%d)!\n", b->down[0], b->down[1], c, b->down[0], b->down[1], c); + return; // some other key is still holding it down + } + + if (!(b->state & 1)) + return; // still up (this should not happen) + + b->state &= ~1; // now up + b->state |= 4; // impulse up +} + +/* +============ +HUD_Key_Event + +Return 1 to allow engine to process the key, otherwise, act on it as needed +============ +*/ +int EXPORT HUD_Key_Event( int down, int keynum, const char *pszCurrentBinding ) +{ + if (gViewPort) + { + return gViewPort->KeyInput(down, keynum, pszCurrentBinding); + } + + return 1; +} + +void IN_BreakDown( void ) { KeyDown( &in_break );}; +void IN_BreakUp( void ) { KeyUp( &in_break ); }; +void IN_KLookDown (void) {KeyDown(&in_klook);} +void IN_KLookUp (void) {KeyUp(&in_klook);} +void IN_JLookDown (void) {KeyDown(&in_jlook);} +void IN_JLookUp (void) {KeyUp(&in_jlook);} +void IN_MLookDown (void) {KeyDown(&in_mlook);} +void IN_UpDown(void) {KeyDown(&in_up);} +void IN_UpUp(void) {KeyUp(&in_up);} +void IN_DownDown(void) {KeyDown(&in_down);} +void IN_DownUp(void) {KeyUp(&in_down);} +void IN_LeftDown(void) {KeyDown(&in_left);} +void IN_LeftUp(void) {KeyUp(&in_left);} +void IN_RightDown(void) {KeyDown(&in_right);} +void IN_RightUp(void) {KeyUp(&in_right);} + +void IN_ForwardDown(void) +{ + KeyDown(&in_forward); + gHUD.m_Spectator.HandleButtonsDown( IN_FORWARD ); +} + +void IN_ForwardUp(void) +{ + KeyUp(&in_forward); + gHUD.m_Spectator.HandleButtonsUp( IN_FORWARD ); +} + +void IN_BackDown(void) +{ + KeyDown(&in_back); + gHUD.m_Spectator.HandleButtonsDown( IN_BACK ); +} + +void IN_BackUp(void) +{ + KeyUp(&in_back); + gHUD.m_Spectator.HandleButtonsUp( IN_BACK ); +} +void IN_LookupDown(void) {KeyDown(&in_lookup);} +void IN_LookupUp(void) {KeyUp(&in_lookup);} +void IN_LookdownDown(void) {KeyDown(&in_lookdown);} +void IN_LookdownUp(void) {KeyUp(&in_lookdown);} +void IN_MoveleftDown(void) +{ + KeyDown(&in_moveleft); + gHUD.m_Spectator.HandleButtonsDown( IN_MOVELEFT ); +} + +void IN_MoveleftUp(void) +{ + KeyUp(&in_moveleft); + gHUD.m_Spectator.HandleButtonsUp( IN_MOVELEFT ); +} + +void IN_MoverightDown(void) +{ + KeyDown(&in_moveright); + gHUD.m_Spectator.HandleButtonsDown( IN_MOVERIGHT ); +} + +void IN_MoverightUp(void) +{ + KeyUp(&in_moveright); + gHUD.m_Spectator.HandleButtonsUp( IN_MOVERIGHT ); +} +void IN_SpeedDown(void) {KeyDown(&in_speed);} +void IN_SpeedUp(void) {KeyUp(&in_speed);} +void IN_StrafeDown(void) {KeyDown(&in_strafe);} +void IN_StrafeUp(void) {KeyUp(&in_strafe);} + +void IN_Attack2Down(void) +{ + KeyDown(&in_attack2); + gHUD.m_Spectator.HandleButtonsDown( IN_ATTACK2 ); +} + +void IN_Attack2Up(void) {KeyUp(&in_attack2);} +void IN_UseDown (void) +{ + KeyDown(&in_use); + gHUD.m_Spectator.HandleButtonsDown( IN_USE ); +} +void IN_UseUp (void) {KeyUp(&in_use);} + +void IN_JumpDown (void) +{ + KeyDown(&in_jump); + gHUD.m_Spectator.HandleButtonsDown( IN_JUMP ); +} +void IN_JumpUp (void) {KeyUp(&in_jump);} + +void IN_DuckDown(void) +{ + KeyDown(&in_duck); + gHUD.m_Spectator.HandleButtonsDown( IN_DUCK ); +} +void IN_DuckUp(void) {KeyUp(&in_duck);} +void IN_ReloadDown(void) {KeyDown(&in_reload);} +void IN_ReloadUp(void) {KeyUp(&in_reload);} +void IN_Alt1Down(void) {KeyDown(&in_alt1);} +void IN_Alt1Up(void) {KeyUp(&in_alt1);} +void IN_GraphDown(void) {KeyDown(&in_graph);} +void IN_GraphUp(void) {KeyUp(&in_graph);} + +void IN_AttackDown(void) +{ + KeyDown( &in_attack ); + gHUD.m_Spectator.HandleButtonsDown( IN_ATTACK ); +} + +void IN_AttackUp(void) +{ + KeyUp( &in_attack ); + in_cancel = 0; +} + +// Special handling +void IN_Cancel(void) +{ + in_cancel = 1; +} + +void IN_Impulse (void) +{ + in_impulse = atoi( gEngfuncs.Cmd_Argv(1) ); +} + +void IN_ScoreDown(void) +{ + KeyDown(&in_score); + + if ( gViewPort ) + { + gViewPort->ShowScoreBoard(); + } +} + +void IN_ScoreUp(void) +{ + KeyUp(&in_score); + + if ( gViewPort ) + { + gViewPort->HideScoreBoard(); + } +} + +void IN_MLookUp (void) +{ + KeyUp( &in_mlook ); + if ( !( in_mlook.state & 1 ) && lookspring->value ) + { + V_StartPitchDrift(); + } +} + +/* +=============== +CL_KeyState + +Returns 0.25 if a key was pressed and released during the frame, +0.5 if it was pressed and held +0 if held then released, and +1.0 if held for the entire time +=============== +*/ +float CL_KeyState (kbutton_t *key) +{ + float val = 0.0; + int impulsedown, impulseup, down; + + impulsedown = key->state & 2; + impulseup = key->state & 4; + down = key->state & 1; + + if ( impulsedown && !impulseup ) + { + // pressed and held this frame? + val = down ? 0.5 : 0.0; + } + + if ( impulseup && !impulsedown ) + { + // released this frame? + val = down ? 0.0 : 0.0; + } + + if ( !impulsedown && !impulseup ) + { + // held the entire frame? + val = down ? 1.0 : 0.0; + } + + if ( impulsedown && impulseup ) + { + if ( down ) + { + // released and re-pressed this frame + val = 0.75; + } + else + { + // pressed and released this frame + val = 0.25; + } + } + + // clear impulses + key->state &= 1; + return val; +} + +/* +================ +CL_AdjustAngles + +Moves the local angle positions +================ +*/ +void CL_AdjustAngles ( float frametime, float *viewangles ) +{ + float speed; + float up, down; + + if (in_speed.state & 1) + { + speed = frametime * cl_anglespeedkey->value; + } + else + { + speed = frametime; + } + + if (!(in_strafe.state & 1)) + { + viewangles[YAW] -= speed*cl_yawspeed->value*CL_KeyState (&in_right); + viewangles[YAW] += speed*cl_yawspeed->value*CL_KeyState (&in_left); + viewangles[YAW] = anglemod(viewangles[YAW]); + } + if (in_klook.state & 1) + { + V_StopPitchDrift (); + viewangles[PITCH] -= speed*cl_pitchspeed->value * CL_KeyState (&in_forward); + viewangles[PITCH] += speed*cl_pitchspeed->value * CL_KeyState (&in_back); + } + + up = CL_KeyState (&in_lookup); + down = CL_KeyState(&in_lookdown); + + viewangles[PITCH] -= speed*cl_pitchspeed->value * up; + viewangles[PITCH] += speed*cl_pitchspeed->value * down; + + if (up || down) + V_StopPitchDrift (); + + if (viewangles[PITCH] > cl_pitchdown->value) + viewangles[PITCH] = cl_pitchdown->value; + if (viewangles[PITCH] < -cl_pitchup->value) + viewangles[PITCH] = -cl_pitchup->value; + + if (viewangles[ROLL] > 50) + viewangles[ROLL] = 50; + if (viewangles[ROLL] < -50) + viewangles[ROLL] = -50; +} + +/* +================ +CL_CreateMove + +Send the intended movement message to the server +if active == 1 then we are 1) not playing back demos ( where our commands are ignored ) and +2 ) we have finished signing on to server +================ +*/ +void EXPORT CL_CreateMove ( float frametime, struct usercmd_s *cmd, int active ) +{ + float spd; + vec3_t viewangles; + static vec3_t oldangles; + + if ( active ) + { + //memset( viewangles, 0, sizeof( vec3_t ) ); + //viewangles[ 0 ] = viewangles[ 1 ] = viewangles[ 2 ] = 0.0; + gEngfuncs.GetViewAngles( (float *)viewangles ); + + CL_AdjustAngles ( frametime, viewangles ); + + memset (cmd, 0, sizeof(*cmd)); + + gEngfuncs.SetViewAngles( (float *)viewangles ); + + if ( in_strafe.state & 1 ) + { + cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_right); + cmd->sidemove -= cl_sidespeed->value * CL_KeyState (&in_left); + } + + cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_moveright); + cmd->sidemove -= cl_sidespeed->value * CL_KeyState (&in_moveleft); + + cmd->upmove += cl_upspeed->value * CL_KeyState (&in_up); + cmd->upmove -= cl_upspeed->value * CL_KeyState (&in_down); + + if ( !(in_klook.state & 1 ) ) + { + cmd->forwardmove += cl_forwardspeed->value * CL_KeyState (&in_forward); + cmd->forwardmove -= cl_backspeed->value * CL_KeyState (&in_back); + } + + // adjust for speed key + if ( in_speed.state & 1 ) + { + cmd->forwardmove *= cl_movespeedkey->value; + cmd->sidemove *= cl_movespeedkey->value; + cmd->upmove *= cl_movespeedkey->value; + } + + // clip to maxspeed + spd = gEngfuncs.GetClientMaxspeed(); + if ( spd != 0.0 ) + { + // scale the 3 speeds so that the total velocity is not > cl.maxspeed + float fmov = sqrt( (cmd->forwardmove*cmd->forwardmove) + (cmd->sidemove*cmd->sidemove) + (cmd->upmove*cmd->upmove) ); + + if ( fmov > spd ) + { + float fratio = spd / fmov; + cmd->forwardmove *= fratio; + cmd->sidemove *= fratio; + cmd->upmove *= fratio; + } + } + + // Allow mice and other controllers to add their inputs + IN_Move ( frametime, cmd ); + } + + cmd->impulse = in_impulse; + in_impulse = 0; + + cmd->weaponselect = g_weaponselect; + g_weaponselect = 0; + + // + // set button and flag bits + // + cmd->buttons = CL_ButtonBits( 1 ); + + // If they're in a modal dialog, ignore the attack button. + if(GetClientVoiceMgr()->IsInSquelchMode()) + cmd->buttons &= ~IN_ATTACK; + + // Using joystick? + if ( in_joystick->value ) + { + if ( cmd->forwardmove > 0 ) + { + cmd->buttons |= IN_FORWARD; + } + else if ( cmd->forwardmove < 0 ) + { + cmd->buttons |= IN_BACK; + } + } + + gEngfuncs.GetViewAngles( (float *)viewangles ); + // Set current view angles. + + if ( gHUD.m_Health.m_iHealth > 0 ) + { + VectorCopy( viewangles, cmd->viewangles ); + VectorCopy( viewangles, oldangles ); + } + else + { + VectorCopy( oldangles, cmd->viewangles ); + } +} + +/* +============ +CL_IsDead + +Returns 1 if health is <= 0 +============ +*/ +int CL_IsDead( void ) +{ + return ( gHUD.m_Health.m_iHealth <= 0 ) ? 1 : 0; +} + +/* +============ +CL_ButtonBits + +Returns appropriate button info for keyboard and mouse state +Set bResetState to 1 to clear old state info +============ +*/ +int CL_ButtonBits( int bResetState ) +{ + int bits = 0; + + if ( in_attack.state & 3 ) + { + bits |= IN_ATTACK; + } + + if (in_duck.state & 3) + { + bits |= IN_DUCK; + } + + if (in_jump.state & 3) + { + bits |= IN_JUMP; + } + + if ( in_forward.state & 3 ) + { + bits |= IN_FORWARD; + } + + if (in_back.state & 3) + { + bits |= IN_BACK; + } + + if (in_use.state & 3) + { + bits |= IN_USE; + } + + if (in_cancel) + { + bits |= IN_CANCEL; + } + + if ( in_left.state & 3 ) + { + bits |= IN_LEFT; + } + + if (in_right.state & 3) + { + bits |= IN_RIGHT; + } + + if ( in_moveleft.state & 3 ) + { + bits |= IN_MOVELEFT; + } + + if (in_moveright.state & 3) + { + bits |= IN_MOVERIGHT; + } + + if (in_attack2.state & 3) + { + bits |= IN_ATTACK2; + } + + if (in_reload.state & 3) + { + bits |= IN_RELOAD; + } + + if (in_alt1.state & 3) + { + bits |= IN_ALT1; + } + + if ( in_score.state & 3 ) + { + bits |= IN_SCORE; + } + + // Dead or in intermission? Shore scoreboard, too + if ( CL_IsDead() || gHUD.m_iIntermission ) + { + bits |= IN_SCORE; + } + + if ( bResetState ) + { + in_attack.state &= ~2; + in_duck.state &= ~2; + in_jump.state &= ~2; + in_forward.state &= ~2; + in_back.state &= ~2; + in_use.state &= ~2; + in_left.state &= ~2; + in_right.state &= ~2; + in_moveleft.state &= ~2; + in_moveright.state &= ~2; + in_attack2.state &= ~2; + in_reload.state &= ~2; + in_alt1.state &= ~2; + in_score.state &= ~2; + } + + return bits; +} + +/* +============ +CL_ResetButtonBits + +============ +*/ +void CL_ResetButtonBits( int bits ) +{ + int bitsNew = CL_ButtonBits( 0 ) ^ bits; + + // Has the attack button been changed + if ( bitsNew & IN_ATTACK ) + { + // Was it pressed? or let go? + if ( bits & IN_ATTACK ) + { + KeyDown( &in_attack ); + } + else + { + // totally clear state + in_attack.state &= ~7; + } + } +} + +/* +============ +InitInput +============ +*/ +void InitInput (void) +{ + gEngfuncs.pfnAddCommand ("+moveup",IN_UpDown); + gEngfuncs.pfnAddCommand ("-moveup",IN_UpUp); + gEngfuncs.pfnAddCommand ("+movedown",IN_DownDown); + gEngfuncs.pfnAddCommand ("-movedown",IN_DownUp); + gEngfuncs.pfnAddCommand ("+left",IN_LeftDown); + gEngfuncs.pfnAddCommand ("-left",IN_LeftUp); + gEngfuncs.pfnAddCommand ("+right",IN_RightDown); + gEngfuncs.pfnAddCommand ("-right",IN_RightUp); + gEngfuncs.pfnAddCommand ("+forward",IN_ForwardDown); + gEngfuncs.pfnAddCommand ("-forward",IN_ForwardUp); + gEngfuncs.pfnAddCommand ("+back",IN_BackDown); + gEngfuncs.pfnAddCommand ("-back",IN_BackUp); + gEngfuncs.pfnAddCommand ("+lookup", IN_LookupDown); + gEngfuncs.pfnAddCommand ("-lookup", IN_LookupUp); + gEngfuncs.pfnAddCommand ("+lookdown", IN_LookdownDown); + gEngfuncs.pfnAddCommand ("-lookdown", IN_LookdownUp); + gEngfuncs.pfnAddCommand ("+strafe", IN_StrafeDown); + gEngfuncs.pfnAddCommand ("-strafe", IN_StrafeUp); + gEngfuncs.pfnAddCommand ("+moveleft", IN_MoveleftDown); + gEngfuncs.pfnAddCommand ("-moveleft", IN_MoveleftUp); + gEngfuncs.pfnAddCommand ("+moveright", IN_MoverightDown); + gEngfuncs.pfnAddCommand ("-moveright", IN_MoverightUp); + gEngfuncs.pfnAddCommand ("+speed", IN_SpeedDown); + gEngfuncs.pfnAddCommand ("-speed", IN_SpeedUp); + gEngfuncs.pfnAddCommand ("+attack", IN_AttackDown); + gEngfuncs.pfnAddCommand ("-attack", IN_AttackUp); + gEngfuncs.pfnAddCommand ("+attack2", IN_Attack2Down); + gEngfuncs.pfnAddCommand ("-attack2", IN_Attack2Up); + gEngfuncs.pfnAddCommand ("+use", IN_UseDown); + gEngfuncs.pfnAddCommand ("-use", IN_UseUp); + gEngfuncs.pfnAddCommand ("+jump", IN_JumpDown); + gEngfuncs.pfnAddCommand ("-jump", IN_JumpUp); + gEngfuncs.pfnAddCommand ("impulse", IN_Impulse); + gEngfuncs.pfnAddCommand ("+klook", IN_KLookDown); + gEngfuncs.pfnAddCommand ("-klook", IN_KLookUp); + gEngfuncs.pfnAddCommand ("+mlook", IN_MLookDown); + gEngfuncs.pfnAddCommand ("-mlook", IN_MLookUp); + gEngfuncs.pfnAddCommand ("+jlook", IN_JLookDown); + gEngfuncs.pfnAddCommand ("-jlook", IN_JLookUp); + gEngfuncs.pfnAddCommand ("+duck", IN_DuckDown); + gEngfuncs.pfnAddCommand ("-duck", IN_DuckUp); + gEngfuncs.pfnAddCommand ("+reload", IN_ReloadDown); + gEngfuncs.pfnAddCommand ("-reload", IN_ReloadUp); + gEngfuncs.pfnAddCommand ("+alt1", IN_Alt1Down); + gEngfuncs.pfnAddCommand ("-alt1", IN_Alt1Up); + gEngfuncs.pfnAddCommand ("+score", IN_ScoreDown); + gEngfuncs.pfnAddCommand ("-score", IN_ScoreUp); + gEngfuncs.pfnAddCommand ("+showscores", IN_ScoreDown); + gEngfuncs.pfnAddCommand ("-showscores", IN_ScoreUp); + gEngfuncs.pfnAddCommand ("+graph", IN_GraphDown); + gEngfuncs.pfnAddCommand ("-graph", IN_GraphUp); + gEngfuncs.pfnAddCommand ("+break",IN_BreakDown); + gEngfuncs.pfnAddCommand ("-break",IN_BreakUp); + + lookstrafe = gEngfuncs.pfnRegisterVariable ( "lookstrafe", "0", FCVAR_ARCHIVE ); + lookspring = gEngfuncs.pfnRegisterVariable ( "lookspring", "0", FCVAR_ARCHIVE ); + cl_anglespeedkey = gEngfuncs.pfnRegisterVariable ( "cl_anglespeedkey", "0.67", 0 ); + cl_yawspeed = gEngfuncs.pfnRegisterVariable ( "cl_yawspeed", "210", 0 ); + cl_pitchspeed = gEngfuncs.pfnRegisterVariable ( "cl_pitchspeed", "225", 0 ); + cl_upspeed = gEngfuncs.pfnRegisterVariable ( "cl_upspeed", "320", 0 ); + cl_forwardspeed = gEngfuncs.pfnRegisterVariable ( "cl_forwardspeed", "400", FCVAR_ARCHIVE ); + cl_backspeed = gEngfuncs.pfnRegisterVariable ( "cl_backspeed", "400", FCVAR_ARCHIVE ); + cl_sidespeed = gEngfuncs.pfnRegisterVariable ( "cl_sidespeed", "400", 0 ); + cl_movespeedkey = gEngfuncs.pfnRegisterVariable ( "cl_movespeedkey", "0.3", 0 ); + cl_pitchup = gEngfuncs.pfnRegisterVariable ( "cl_pitchup", "89", 0 ); + cl_pitchdown = gEngfuncs.pfnRegisterVariable ( "cl_pitchdown", "89", 0 ); + + cl_vsmoothing = gEngfuncs.pfnRegisterVariable ( "cl_vsmoothing", "0.05", FCVAR_ARCHIVE ); + + m_pitch = gEngfuncs.pfnRegisterVariable ( "m_pitch","0.022", FCVAR_ARCHIVE ); + m_yaw = gEngfuncs.pfnRegisterVariable ( "m_yaw","0.022", FCVAR_ARCHIVE ); + m_forward = gEngfuncs.pfnRegisterVariable ( "m_forward","1", FCVAR_ARCHIVE ); + m_side = gEngfuncs.pfnRegisterVariable ( "m_side","0.8", FCVAR_ARCHIVE ); + + // Initialize third person camera controls. + CAM_Init(); + // Initialize inputs + IN_Init(); + // Initialize keyboard + KB_Init(); + // Initialize view system + V_Init(); +} + +/* +============ +ShutdownInput +============ +*/ +void ShutdownInput (void) +{ + IN_Shutdown(); + KB_Shutdown(); +} + +#include "interface.h" + +void EXPORT HUD_Shutdown( void ) +{ + ShutdownInput(); + + extern CSysModule *g_hTrackerModule; + if (g_hTrackerModule) + { + Sys_UnloadModule(g_hTrackerModule); + } + + extern CSysModule *g_pFileSystemModule; + if (g_pFileSystemModule) + { + Sys_UnloadModule(g_pFileSystemModule); + } + +} diff --git a/dmc/cl_dll/inputw32.cpp b/dmc/cl_dll/inputw32.cpp new file mode 100644 index 0000000..c8a0f7c --- /dev/null +++ b/dmc/cl_dll/inputw32.cpp @@ -0,0 +1,942 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// in_win.c -- windows 95 mouse and joystick code +// 02/21/97 JCB Added extended DirectInput code to support external controllers. + +#include "port.h" +#include +#include + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "../public/keydefs.h" +#include "view.h" +#include "Exports.h" +/*#ifdef _WIN32 +#define CL_DLLEXPORT EXPORT +#else +#define CL_DLLEXPORT __attribute__ ((visibility("default"))) +#endif*/ + +#define MOUSE_BUTTON_COUNT 5 + +// Set this to 1 to show mouse cursor. Experimental +int g_iVisibleMouse = 0; + +extern cl_enginefunc_t gEngfuncs; + +extern int iMouseInUse; + +extern kbutton_t in_strafe; +extern kbutton_t in_mlook; +extern kbutton_t in_speed; +extern kbutton_t in_jlook; + +extern cvar_t *m_pitch; +extern cvar_t *m_yaw; +extern cvar_t *m_forward; +extern cvar_t *m_side; + +extern cvar_t *lookstrafe; +extern cvar_t *lookspring; +extern cvar_t *cl_pitchdown; +extern cvar_t *cl_pitchup; +extern cvar_t *cl_yawspeed; +extern cvar_t *cl_sidespeed; +extern cvar_t *cl_forwardspeed; +extern cvar_t *cl_pitchspeed; +extern cvar_t *cl_movespeedkey; + +// mouse variables +cvar_t *m_filter; +cvar_t *sensitivity; + +// Custom mouse acceleration (0 disable, 1 to enable, 2 enable with separate yaw/pitch rescale) +static cvar_t *m_customaccel; +//Formula: mousesensitivity = ( rawmousedelta^m_customaccel_exponent ) * m_customaccel_scale + sensitivity +// If mode is 2, then x and y sensitivity are scaled by m_pitch and m_yaw respectively. +// Custom mouse acceleration value. +static cvar_t *m_customaccel_scale; +//Max mouse move scale factor, 0 for no limit +static cvar_t *m_customaccel_max; +//Mouse move is raised to this power before being scaled by scale factor +static cvar_t *m_customaccel_exponent; + +int mouse_buttons; +int mouse_oldbuttonstate; +POINT current_pos; +int old_mouse_x, old_mouse_y, mx_accum, my_accum; +float mouse_x, mouse_y; + +static int restore_spi; +static int originalmouseparms[3], newmouseparms[3] = {0, 0, 1}; +static int mouseactive; +int mouseinitialized; +static int mouseparmsvalid; +static int mouseshowtoggle = 1; + +// joystick defines and variables +// where should defines be moved? +#define JOY_ABSOLUTE_AXIS 0x00000000 // control like a joystick +#define JOY_RELATIVE_AXIS 0x00000010 // control like a mouse, spinner, trackball +#define JOY_MAX_AXES 6 // X, Y, Z, R, U, V +#define JOY_AXIS_X 0 +#define JOY_AXIS_Y 1 +#define JOY_AXIS_Z 2 +#define JOY_AXIS_R 3 +#define JOY_AXIS_U 4 +#define JOY_AXIS_V 5 + +enum _ControlList +{ + AxisNada = 0, + AxisForward, + AxisLook, + AxisSide, + AxisTurn +}; + + +DWORD dwAxisMap[ JOY_MAX_AXES ]; +DWORD dwControlMap[ JOY_MAX_AXES ]; +DWORD pdwRawValue[ JOY_MAX_AXES ]; +DWORD joy_oldbuttonstate, joy_oldpovstate; + +int joy_id; +DWORD joy_flags; +DWORD joy_numbuttons; + +SDL_GameController *s_pJoystick = NULL; + +// none of these cvars are saved over a session +// this means that advanced controller configuration needs to be executed +// each time. this avoids any problems with getting back to a default usage +// or when changing from one controller to another. this way at least something +// works. +cvar_t *in_joystick; +cvar_t *joy_name; +cvar_t *joy_advanced; +cvar_t *joy_advaxisx; +cvar_t *joy_advaxisy; +cvar_t *joy_advaxisz; +cvar_t *joy_advaxisr; +cvar_t *joy_advaxisu; +cvar_t *joy_advaxisv; +cvar_t *joy_forwardthreshold; +cvar_t *joy_sidethreshold; +cvar_t *joy_pitchthreshold; +cvar_t *joy_yawthreshold; +cvar_t *joy_forwardsensitivity; +cvar_t *joy_sidesensitivity; +cvar_t *joy_pitchsensitivity; +cvar_t *joy_yawsensitivity; +cvar_t *joy_wwhack1; +cvar_t *joy_wwhack2; + +int joy_avail, joy_advancedinit, joy_haspov; + +/* +=========== +Force_CenterView_f +=========== +*/ +void Force_CenterView_f (void) +{ + vec3_t viewangles; + + if (!iMouseInUse) + { + gEngfuncs.GetViewAngles( (float *)viewangles ); + viewangles[PITCH] = 0; + gEngfuncs.SetViewAngles( (float *)viewangles ); + } +} + +/* +=========== +IN_ActivateMouse +=========== +*/ +void CL_DLLEXPORT IN_ActivateMouse (void) +{ + if (mouseinitialized) + { +#ifdef _WIN32 + if (mouseparmsvalid) + restore_spi = SystemParametersInfo (SPI_SETMOUSE, 0, newmouseparms, 0); +#endif + mouseactive = 1; + } +} + +/* +=========== +IN_DeactivateMouse +=========== +*/ +void CL_DLLEXPORT IN_DeactivateMouse (void) +{ + if (mouseinitialized) + { +#ifdef _WIN32 + if (restore_spi) + SystemParametersInfo (SPI_SETMOUSE, 0, originalmouseparms, 0); +#endif + + mouseactive = 0; + } +} + +/* +=========== +IN_StartupMouse +=========== +*/ +void IN_StartupMouse (void) +{ + if ( gEngfuncs.CheckParm ("-nomouse", NULL ) ) + return; + + mouseinitialized = 1; +#ifdef _WIN32 + mouseparmsvalid = SystemParametersInfo (SPI_GETMOUSE, 0, originalmouseparms, 0); + + if (mouseparmsvalid) + { + if ( gEngfuncs.CheckParm ("-noforcemspd", NULL ) ) + newmouseparms[2] = originalmouseparms[2]; + + if ( gEngfuncs.CheckParm ("-noforcemaccel", NULL ) ) + { + newmouseparms[0] = originalmouseparms[0]; + newmouseparms[1] = originalmouseparms[1]; + } + + if ( gEngfuncs.CheckParm ("-noforcemparms", NULL ) ) + { + newmouseparms[0] = originalmouseparms[0]; + newmouseparms[1] = originalmouseparms[1]; + newmouseparms[2] = originalmouseparms[2]; + } + } +#endif + + mouse_buttons = MOUSE_BUTTON_COUNT; +} + +/* +=========== +IN_Shutdown +=========== +*/ +void IN_Shutdown (void) +{ + IN_DeactivateMouse (); +} + +/* +=========== +IN_GetMousePos + +Ask for mouse position from engine +=========== +*/ +void IN_GetMousePos( int *mx, int *my ) +{ + gEngfuncs.GetMousePosition( mx, my ); +} + +/* +=========== +IN_ResetMouse + +FIXME: Call through to engine? +=========== +*/ +void IN_ResetMouse( void ) +{ +} + +/* +=========== +IN_MouseEvent +=========== +*/ +void CL_DLLEXPORT IN_MouseEvent (int mstate) +{ + int i; + + if ( iMouseInUse || g_iVisibleMouse ) + return; + + // perform button actions + for (i=0 ; ivalue; + + // Using special accleration values + if ( m_customaccel->value != 0 ) + { + float raw_mouse_movement_distance = sqrt( mx * mx + my * my ); + float acceleration_scale = m_customaccel_scale->value; + float accelerated_sensitivity_max = m_customaccel_max->value; + float accelerated_sensitivity_exponent = m_customaccel_exponent->value; + float accelerated_sensitivity = ( (float)pow( raw_mouse_movement_distance, accelerated_sensitivity_exponent ) * acceleration_scale + mouse_senstivity ); + + if ( accelerated_sensitivity_max > 0.0001f && + accelerated_sensitivity > accelerated_sensitivity_max ) + { + accelerated_sensitivity = accelerated_sensitivity_max; + } + + *x *= accelerated_sensitivity; + *y *= accelerated_sensitivity; + + // Further re-scale by yaw and pitch magnitude if user requests alternate mode 2 + // This means that they will need to up their value for m_customaccel_scale greatly (>40x) since m_pitch/yaw default + // to 0.022 + if ( m_customaccel->value == 2 ) + { + *x *= m_yaw->value; + *y *= m_pitch->value; + } + } + else + { + // Just apply the default + *x *= mouse_senstivity; + *y *= mouse_senstivity; + } +} + +/* +=========== +IN_MouseMove +=========== +*/ +void IN_MouseMove ( float frametime, usercmd_t *cmd) +{ + int mx, my; + vec3_t viewangles; + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + if ( in_mlook.state & 1) + { + V_StopPitchDrift (); + } + + //jjb - this disbles normal mouse control if the user is trying to + // move the camera, or if the mouse cursor is visible or if we're in intermission + if ( !iMouseInUse && !gHUD.m_iIntermission && !g_iVisibleMouse ) + { + int deltaX, deltaY; + SDL_GetRelativeMouseState( &deltaX, &deltaY ); + current_pos.x = deltaX; + current_pos.y = deltaY; + mx = deltaX + mx_accum; + my = deltaY + my_accum; + + mx_accum = 0; + my_accum = 0; + + if (m_filter->value) + { + mouse_x = (mx + old_mouse_x) * 0.5; + mouse_y = (my + old_mouse_y) * 0.5; + } + else + { + mouse_x = mx; + mouse_y = my; + } + + old_mouse_x = mx; + old_mouse_y = my; + + // Apply custom mouse scaling/acceleration + IN_ScaleMouse( &mouse_x, &mouse_y ); + + // add mouse X/Y movement to cmd + if ( (in_strafe.state & 1) || (lookstrafe->value && (in_mlook.state & 1) )) + cmd->sidemove += m_side->value * mouse_x; + else + viewangles[YAW] -= m_yaw->value * mouse_x; + + if ( (in_mlook.state & 1) && !(in_strafe.state & 1)) + { + viewangles[PITCH] += m_pitch->value * mouse_y; + if (viewangles[PITCH] > cl_pitchdown->value) + viewangles[PITCH] = cl_pitchdown->value; + if (viewangles[PITCH] < -cl_pitchup->value) + viewangles[PITCH] = -cl_pitchup->value; + } + else + { + if ((in_strafe.state & 1) && gEngfuncs.IsNoClipping() ) + { + cmd->upmove -= m_forward->value * mouse_y; + } + else + { + cmd->forwardmove -= m_forward->value * mouse_y; + } + } + + // if the mouse has moved, force it to the center, so there's room to move + if ( mx || my ) + { + IN_ResetMouse(); + } + } + + gEngfuncs.SetViewAngles( (float *)viewangles ); + +/* +//#define TRACE_TEST +#if defined( TRACE_TEST ) + { + int mx, my; + void V_Move( int mx, int my ); + IN_GetMousePos( &mx, &my ); + V_Move( mx, my ); + } +#endif +*/ +} + +/* +=========== +IN_Accumulate +=========== +*/ +void CL_DLLEXPORT IN_Accumulate (void) +{ + //only accumulate mouse if we are not moving the camera with the mouse + if ( !iMouseInUse && !g_iVisibleMouse) + { + if (mouseactive) + { + int deltaX, deltaY; + SDL_GetRelativeMouseState( &deltaX, &deltaY ); + mx_accum += deltaX; + my_accum += deltaY; + // force the mouse to the center, so there's room to move + IN_ResetMouse(); + + } + } + +} + +/* +=================== +IN_ClearStates +=================== +*/ +void CL_DLLEXPORT IN_ClearStates (void) +{ + if ( !mouseactive ) + return; + + mx_accum = 0; + my_accum = 0; + mouse_oldbuttonstate = 0; +} + +/* +=============== +IN_StartupJoystick +=============== +*/ +void IN_StartupJoystick (void) +{ + // abort startup if user requests no joystick + if ( gEngfuncs.CheckParm ("-nojoy", NULL ) ) + return; + + // assume no joystick + joy_avail = 0; + + int nJoysticks = SDL_NumJoysticks(); + if ( nJoysticks > 0 ) + { + for ( int i = 0; i < nJoysticks; i++ ) + { + if ( SDL_IsGameController( i ) ) + { + s_pJoystick = SDL_GameControllerOpen( i ); + if ( s_pJoystick ) + { + //save the joystick's number of buttons and POV status + joy_numbuttons = SDL_CONTROLLER_BUTTON_MAX; + joy_haspov = 0; + + // old button and POV states default to no buttons pressed + joy_oldbuttonstate = joy_oldpovstate = 0; + + // mark the joystick as available and advanced initialization not completed + // this is needed as cvars are not available during initialization + gEngfuncs.Con_Printf ("joystick found\n\n", SDL_GameControllerName(s_pJoystick)); + joy_avail = 1; + joy_advancedinit = 0; + break; + } + + } + } + } + else + { + gEngfuncs.Con_DPrintf ("joystick not found -- driver not present\n\n"); + } +} + +int RawValuePointer (int axis) +{ + switch (axis) + { + default: + case JOY_AXIS_X: + return SDL_GameControllerGetAxis( s_pJoystick, SDL_CONTROLLER_AXIS_LEFTX ); + case JOY_AXIS_Y: + return SDL_GameControllerGetAxis( s_pJoystick, SDL_CONTROLLER_AXIS_LEFTY ); + case JOY_AXIS_Z: + return SDL_GameControllerGetAxis( s_pJoystick, SDL_CONTROLLER_AXIS_RIGHTX ); + case JOY_AXIS_R: + return SDL_GameControllerGetAxis( s_pJoystick, SDL_CONTROLLER_AXIS_RIGHTY ); + + } +} + +/* +=========== +Joy_AdvancedUpdate_f +=========== +*/ +void Joy_AdvancedUpdate_f (void) +{ + + // called once by IN_ReadJoystick and by user whenever an update is needed + // cvars are now available + int i; + DWORD dwTemp; + + // initialize all the maps + for (i = 0; i < JOY_MAX_AXES; i++) + { + dwAxisMap[i] = AxisNada; + dwControlMap[i] = JOY_ABSOLUTE_AXIS; + pdwRawValue[i] = RawValuePointer(i); + } + + if( joy_advanced->value == 0.0) + { + // default joystick initialization + // 2 axes only with joystick control + dwAxisMap[JOY_AXIS_X] = AxisTurn; + // dwControlMap[JOY_AXIS_X] = JOY_ABSOLUTE_AXIS; + dwAxisMap[JOY_AXIS_Y] = AxisForward; + // dwControlMap[JOY_AXIS_Y] = JOY_ABSOLUTE_AXIS; + } + else + { + if ( strcmp ( joy_name->string, "joystick") != 0 ) + { + // notify user of advanced controller + gEngfuncs.Con_Printf ("\n%s configured\n\n", joy_name->string); + } + + // advanced initialization here + // data supplied by user via joy_axisn cvars + dwTemp = (DWORD) joy_advaxisx->value; + dwAxisMap[JOY_AXIS_X] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_X] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisy->value; + dwAxisMap[JOY_AXIS_Y] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_Y] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisz->value; + dwAxisMap[JOY_AXIS_Z] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_Z] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisr->value; + dwAxisMap[JOY_AXIS_R] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_R] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisu->value; + dwAxisMap[JOY_AXIS_U] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_U] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisv->value; + dwAxisMap[JOY_AXIS_V] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_V] = dwTemp & JOY_RELATIVE_AXIS; + } +} + + +/* +=========== +IN_Commands +=========== +*/ +void IN_Commands (void) +{ + int i, key_index; + + if (!joy_avail) + { + return; + } + + DWORD buttonstate, povstate; + + // loop through the joystick buttons + // key a joystick event or auxillary event for higher number buttons for each state change + buttonstate = 0; + for ( i = 0; i < SDL_CONTROLLER_BUTTON_MAX; i++ ) + { + if ( SDL_GameControllerGetButton( s_pJoystick, (SDL_GameControllerButton)i ) ) + { + buttonstate |= 1<value) + { + return; + } + + // collect the joystick data, if possible + if (IN_ReadJoystick () != 1) + { + return; + } + + if (in_speed.state & 1) + speed = cl_movespeedkey->value; + else + speed = 1; + + aspeed = speed * frametime; + + // loop through the axes + for (i = 0; i < JOY_MAX_AXES; i++) + { + // get the floating point zero-centered, potentially-inverted data for the current axis + fAxisValue = (float) pdwRawValue[i]; + // move centerpoint to zero + fAxisValue -= 32768.0; + + if (joy_wwhack2->value != 0.0) + { + if (dwAxisMap[i] == AxisTurn) + { + // this is a special formula for the Logitech WingMan Warrior + // y=ax^b; where a = 300 and b = 1.3 + // also x values are in increments of 800 (so this is factored out) + // then bounds check result to level out excessively high spin rates + fTemp = 300.0 * pow(abs(fAxisValue) / 800.0, 1.3); + if (fTemp > 14000.0) + fTemp = 14000.0; + // restore direction information + fAxisValue = (fAxisValue > 0.0) ? fTemp : -fTemp; + } + } + + // convert range from -32768..32767 to -1..1 + fAxisValue /= 32768.0; + + switch (dwAxisMap[i]) + { + case AxisForward: + if ((joy_advanced->value == 0.0) && (in_jlook.state & 1)) + { + // user wants forward control to become look control + if (fabs(fAxisValue) > joy_pitchthreshold->value) + { + // if mouse invert is on, invert the joystick pitch value + // only absolute control support here (joy_advanced is 0) + if (m_pitch->value < 0.0) + { + viewangles[PITCH] -= (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; + } + else + { + viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; + } + V_StopPitchDrift(); + } + else + { + // no pitch movement + // disable pitch return-to-center unless requested by user + // *** this code can be removed when the lookspring bug is fixed + // *** the bug always has the lookspring feature on + if(lookspring->value == 0.0) + { + V_StopPitchDrift(); + } + } + } + else + { + // user wants forward control to be forward control + if (fabs(fAxisValue) > joy_forwardthreshold->value) + { + cmd->forwardmove += (fAxisValue * joy_forwardsensitivity->value) * speed * cl_forwardspeed->value; + } + } + break; + + case AxisSide: + if (fabs(fAxisValue) > joy_sidethreshold->value) + { + cmd->sidemove += (fAxisValue * joy_sidesensitivity->value) * speed * cl_sidespeed->value; + } + break; + + case AxisTurn: + if ((in_strafe.state & 1) || (lookstrafe->value && (in_jlook.state & 1))) + { + // user wants turn control to become side control + if (fabs(fAxisValue) > joy_sidethreshold->value) + { + cmd->sidemove -= (fAxisValue * joy_sidesensitivity->value) * speed * cl_sidespeed->value; + } + } + else + { + // user wants turn control to be turn control + if (fabs(fAxisValue) > joy_yawthreshold->value) + { + if(dwControlMap[i] == JOY_ABSOLUTE_AXIS) + { + viewangles[YAW] += (fAxisValue * joy_yawsensitivity->value) * aspeed * cl_yawspeed->value; + } + else + { + viewangles[YAW] += (fAxisValue * joy_yawsensitivity->value) * speed * 180.0; + } + + } + } + break; + + case AxisLook: + if (in_jlook.state & 1) + { + if (fabs(fAxisValue) > joy_pitchthreshold->value) + { + // pitch movement detected and pitch movement desired by user + if(dwControlMap[i] == JOY_ABSOLUTE_AXIS) + { + viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; + } + else + { + viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * speed * 180.0; + } + V_StopPitchDrift(); + } + else + { + // no pitch movement + // disable pitch return-to-center unless requested by user + // *** this code can be removed when the lookspring bug is fixed + // *** the bug always has the lookspring feature on + if( lookspring->value == 0.0 ) + { + V_StopPitchDrift(); + } + } + } + break; + + default: + break; + } + } + + // bounds check pitch + if (viewangles[PITCH] > cl_pitchdown->value) + viewangles[PITCH] = cl_pitchdown->value; + if (viewangles[PITCH] < -cl_pitchup->value) + viewangles[PITCH] = -cl_pitchup->value; + + gEngfuncs.SetViewAngles( (float *)viewangles ); +} + +/* +=========== +IN_Move +=========== +*/ +void IN_Move ( float frametime, usercmd_t *cmd) +{ + if ( !iMouseInUse && mouseactive ) + { + IN_MouseMove ( frametime, cmd); + } + + IN_JoyMove ( frametime, cmd); +} + +/* +=========== +IN_Init +=========== +*/ +void IN_Init (void) +{ + m_filter = gEngfuncs.pfnRegisterVariable ( "m_filter","0", FCVAR_ARCHIVE ); + sensitivity = gEngfuncs.pfnRegisterVariable ( "sensitivity","3", FCVAR_ARCHIVE ); // user mouse sensitivity setting. + + in_joystick = gEngfuncs.pfnRegisterVariable ( "joystick","0", FCVAR_ARCHIVE ); + joy_name = gEngfuncs.pfnRegisterVariable ( "joyname", "joystick", 0 ); + joy_advanced = gEngfuncs.pfnRegisterVariable ( "joyadvanced", "0", 0 ); + joy_advaxisx = gEngfuncs.pfnRegisterVariable ( "joyadvaxisx", "0", 0 ); + joy_advaxisy = gEngfuncs.pfnRegisterVariable ( "joyadvaxisy", "0", 0 ); + joy_advaxisz = gEngfuncs.pfnRegisterVariable ( "joyadvaxisz", "0", 0 ); + joy_advaxisr = gEngfuncs.pfnRegisterVariable ( "joyadvaxisr", "0", 0 ); + joy_advaxisu = gEngfuncs.pfnRegisterVariable ( "joyadvaxisu", "0", 0 ); + joy_advaxisv = gEngfuncs.pfnRegisterVariable ( "joyadvaxisv", "0", 0 ); + joy_forwardthreshold = gEngfuncs.pfnRegisterVariable ( "joyforwardthreshold", "0.15", 0 ); + joy_sidethreshold = gEngfuncs.pfnRegisterVariable ( "joysidethreshold", "0.15", 0 ); + joy_pitchthreshold = gEngfuncs.pfnRegisterVariable ( "joypitchthreshold", "0.15", 0 ); + joy_yawthreshold = gEngfuncs.pfnRegisterVariable ( "joyyawthreshold", "0.15", 0 ); + joy_forwardsensitivity = gEngfuncs.pfnRegisterVariable ( "joyforwardsensitivity", "-1.0", 0 ); + joy_sidesensitivity = gEngfuncs.pfnRegisterVariable ( "joysidesensitivity", "-1.0", 0 ); + joy_pitchsensitivity = gEngfuncs.pfnRegisterVariable ( "joypitchsensitivity", "1.0", 0 ); + joy_yawsensitivity = gEngfuncs.pfnRegisterVariable ( "joyyawsensitivity", "-1.0", 0 ); + joy_wwhack1 = gEngfuncs.pfnRegisterVariable ( "joywwhack1", "0.0", 0 ); + joy_wwhack2 = gEngfuncs.pfnRegisterVariable ( "joywwhack2", "0.0", 0 ); + + m_customaccel = gEngfuncs.pfnRegisterVariable ( "m_customaccel", "0", FCVAR_ARCHIVE ); + m_customaccel_scale = gEngfuncs.pfnRegisterVariable ( "m_customaccel_scale", "0.04", FCVAR_ARCHIVE ); + m_customaccel_max = gEngfuncs.pfnRegisterVariable ( "m_customaccel_max", "0", FCVAR_ARCHIVE ); + m_customaccel_exponent = gEngfuncs.pfnRegisterVariable ( "m_customaccel_exponent", "1", FCVAR_ARCHIVE ); + + gEngfuncs.pfnAddCommand ("force_centerview", Force_CenterView_f); + gEngfuncs.pfnAddCommand ("joyadvancedupdate", Joy_AdvancedUpdate_f); + + IN_StartupMouse (); + IN_StartupJoystick (); +} diff --git a/dmc/cl_dll/kbutton.h b/dmc/cl_dll/kbutton.h new file mode 100644 index 0000000..23cd8ac --- /dev/null +++ b/dmc/cl_dll/kbutton.h @@ -0,0 +1,18 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( KBUTTONH ) +#define KBUTTONH +#pragma once + +typedef struct kbutton_s +{ + int down[2]; // key nums holding it down + int state; // low bit is down state +} kbutton_t; + +#endif // !KBUTTONH \ No newline at end of file diff --git a/dmc/cl_dll/menu.cpp b/dmc/cl_dll/menu.cpp new file mode 100644 index 0000000..47ed242 --- /dev/null +++ b/dmc/cl_dll/menu.cpp @@ -0,0 +1,282 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// menu.cpp +// +// generic menu handler +// +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include +#include "vgui_viewport.h" + +#define MAX_MENU_STRING 512 +char g_szMenuString[MAX_MENU_STRING]; +char g_szPrelocalisedMenuString[MAX_MENU_STRING]; + +int KB_ConvertString( char *in, char **ppout ); + +DECLARE_MESSAGE( m_Menu, ShowMenu ); + +int CHudMenu :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( ShowMenu ); + + InitHUDData(); + + return 1; +} + +void CHudMenu :: InitHUDData( void ) +{ + m_fMenuDisplayed = 0; + m_bitsValidSlots = 0; + Reset(); +} + +void CHudMenu :: Reset( void ) +{ + g_szPrelocalisedMenuString[0] = 0; + m_fWaitingForMore = FALSE; +} + +int CHudMenu :: VidInit( void ) +{ + return 1; +} + + +/*================================= + ParseEscapeToken + + Interprets the given escape token (backslash followed by a letter). The + first character of the token must be a backslash. The second character + specifies the operation to perform: + + \w : White text (this is the default) + \d : Dim (gray) text + \y : Yellow text + \r : Red text + \R : Right-align (just for the remainder of the current line) +=================================*/ + +static int menu_r, menu_g, menu_b, menu_x, menu_ralign; + +static inline const char* ParseEscapeToken( const char* token ) +{ + if ( *token != '\\' ) + return token; + + token++; + + switch ( *token ) + { + case '\0': + return token; + + case 'w': + menu_r = 255; + menu_g = 255; + menu_b = 255; + break; + + case 'd': + menu_r = 100; + menu_g = 100; + menu_b = 100; + break; + + case 'y': + menu_r = 255; + menu_g = 210; + menu_b = 64; + break; + + case 'r': + menu_r = 210; + menu_g = 24; + menu_b = 0; + break; + + case 'R': + menu_x = ScreenWidth/2; + menu_ralign = TRUE; + break; + } + + return ++token; +} + + +int CHudMenu :: Draw( float flTime ) +{ + // check for if menu is set to disappear + if ( m_flShutoffTime > 0 ) + { + if ( m_flShutoffTime <= gHUD.m_flTime ) + { // times up, shutoff + m_fMenuDisplayed = 0; + m_iFlags &= ~HUD_ACTIVE; + return 1; + } + } + + // don't draw the menu if the scoreboard is being shown + if ( gViewPort && gViewPort->IsScoreBoardVisible() ) + return 1; + + // draw the menu, along the left-hand side of the screen + + // count the number of newlines + int nlc = 0; + int i; + for ( i = 0; i < MAX_MENU_STRING && g_szMenuString[i] != '\0'; i++ ) + { + if ( g_szMenuString[i] == '\n' ) + nlc++; + } + + // center it + int y = (ScreenHeight/2) - ((nlc/2)*12) - 40; // make sure it is above the say text + + menu_r = 255; + menu_g = 255; + menu_b = 255; + menu_x = 20; + menu_ralign = FALSE; + + const char* sptr = g_szMenuString; + + while ( *sptr != '\0' ) + { + if ( *sptr == '\\' ) + { + sptr = ParseEscapeToken( sptr ); + } + else if ( *sptr == '\n' ) + { + menu_ralign = FALSE; + menu_x = 20; + y += (12); + + sptr++; + } + else + { + char menubuf[ 80 ]; + const char *ptr = sptr; + while ( *sptr != '\0' && *sptr != '\n' && *sptr != '\\') + { + sptr++; + } + strncpy( menubuf, ptr, min( ( sptr - ptr), (int)sizeof( menubuf ) )); + menubuf[ min( ( sptr - ptr), (int)(sizeof( menubuf )-1) ) ] = '\0'; + + if ( menu_ralign ) + { + // IMPORTANT: Right-to-left rendered text does not parse escape tokens! + menu_x = gHUD.DrawHudStringReverse( menu_x, y, 0, menubuf, menu_r, menu_g, menu_b ); + } + else + { + menu_x = gHUD.DrawHudString( menu_x, y, 320, menubuf, menu_r, menu_g, menu_b ); + } + } + } + + return 1; +} + +// selects an item from the menu +void CHudMenu :: SelectMenuItem( int menu_item ) +{ + // if menu_item is in a valid slot, send a menuselect command to the server + if ( (menu_item > 0) && (m_bitsValidSlots & (1 << (menu_item-1))) ) + { + char szbuf[32]; + sprintf( szbuf, "menuselect %d\n", menu_item ); + ClientCmd( szbuf ); + + // remove the menu + m_fMenuDisplayed = 0; + m_iFlags &= ~HUD_ACTIVE; + } +} + + +// Message handler for ShowMenu message +// takes four values: +// short: a bitfield of keys that are valid input +// char : the duration, in seconds, the menu should stay up. -1 means is stays until something is chosen. +// byte : a boolean, TRUE if there is more string yet to be received before displaying the menu, FALSE if it's the last string +// string: menu string to display +// if this message is never received, then scores will simply be the combined totals of the players. +int CHudMenu :: MsgFunc_ShowMenu( const char *pszName, int iSize, void *pbuf ) +{ + char *temp = NULL; + + BEGIN_READ( pbuf, iSize ); + + m_bitsValidSlots = READ_SHORT(); + int DisplayTime = READ_CHAR(); + int NeedMore = READ_BYTE(); + + if ( DisplayTime > 0 ) + m_flShutoffTime = DisplayTime + gHUD.m_flTime; + else + m_flShutoffTime = -1; + + if ( m_bitsValidSlots ) + { + if ( !m_fWaitingForMore ) // this is the start of a new menu + { + strncpy( g_szPrelocalisedMenuString, READ_STRING(), MAX_MENU_STRING ); + } + else + { // append to the current menu string + strncat( g_szPrelocalisedMenuString, READ_STRING(), MAX_MENU_STRING - strlen(g_szPrelocalisedMenuString) ); + } + g_szPrelocalisedMenuString[MAX_MENU_STRING-1] = 0; // ensure null termination (strncat/strncpy does not) + + if ( !NeedMore ) + { // we have the whole string, so we can localise it now + strcpy( g_szMenuString, gHUD.m_TextMessage.BufferedLocaliseTextString( g_szPrelocalisedMenuString ) ); + + // Swap in characters + if ( KB_ConvertString( g_szMenuString, &temp ) ) + { + strcpy( g_szMenuString, temp ); + free( temp ); + } + } + + m_fMenuDisplayed = 1; + m_iFlags |= HUD_ACTIVE; + } + else + { + m_fMenuDisplayed = 0; // no valid slots means that the menu should be turned off + m_iFlags &= ~HUD_ACTIVE; + } + + m_fWaitingForMore = NeedMore; + + return 1; +} diff --git a/dmc/cl_dll/message.cpp b/dmc/cl_dll/message.cpp new file mode 100644 index 0000000..e86396f --- /dev/null +++ b/dmc/cl_dll/message.cpp @@ -0,0 +1,482 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Message.cpp +// +// implementation of CHudMessage class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE( m_Message, HudText ) +DECLARE_MESSAGE( m_Message, GameTitle ) + + +int CHudMessage::Init(void) +{ + HOOK_MESSAGE( HudText ); + HOOK_MESSAGE( GameTitle ); + + gHUD.AddHudElem(this); + + Reset(); + + return 1; +}; + +int CHudMessage::VidInit( void ) +{ + m_HUD_title_half = gHUD.GetSpriteIndex( "title_half" ); + m_HUD_title_life = gHUD.GetSpriteIndex( "title_life" ); + + return 1; +}; + + +void CHudMessage::Reset( void ) +{ + memset( m_pMessages, 0, sizeof( m_pMessages[0] ) * maxHUDMessages ); + memset( m_startTime, 0, sizeof( m_startTime[0] ) * maxHUDMessages ); + + m_gameTitleTime = 0; + m_pGameTitle = NULL; +} + + +float CHudMessage::FadeBlend( float fadein, float fadeout, float hold, float localTime ) +{ + float fadeTime = fadein + hold; + float fadeBlend; + + if ( localTime < 0 ) + return 0; + + if ( localTime < fadein ) + { + fadeBlend = 1 - ((fadein - localTime) / fadein); + } + else if ( localTime > fadeTime ) + { + if ( fadeout > 0 ) + fadeBlend = 1 - ((localTime - fadeTime) / fadeout); + else + fadeBlend = 0; + } + else + fadeBlend = 1; + + return fadeBlend; +} + + +int CHudMessage::XPosition( float x, int width, int totalWidth ) +{ + int xPos; + + if ( x == -1 ) + { + xPos = (ScreenWidth - width) / 2; + } + else + { + if ( x < 0 ) + xPos = (1.0 + x) * ScreenWidth - totalWidth; // Alight right + else + xPos = x * ScreenWidth; + } + + if ( xPos + width > ScreenWidth ) + xPos = ScreenWidth - width; + else if ( xPos < 0 ) + xPos = 0; + + return xPos; +} + + +int CHudMessage::YPosition( float y, int height ) +{ + int yPos; + + if ( y == -1 ) // Centered? + yPos = (ScreenHeight - height) * 0.5; + else + { + // Alight bottom? + if ( y < 0 ) + yPos = (1.0 + y) * ScreenHeight - height; // Alight bottom + else // align top + yPos = y * ScreenHeight; + } + + if ( yPos + height > ScreenHeight ) + yPos = ScreenHeight - height; + else if ( yPos < 0 ) + yPos = 0; + + return yPos; +} + + +void CHudMessage::MessageScanNextChar( void ) +{ + int srcRed, srcGreen, srcBlue, destRed, destGreen, destBlue; + int blend; + + srcRed = m_parms.pMessage->r1; + srcGreen = m_parms.pMessage->g1; + srcBlue = m_parms.pMessage->b1; + blend = 0; // Pure source + + switch( m_parms.pMessage->effect ) + { + // Fade-in / Fade-out + case 0: + case 1: + destRed = destGreen = destBlue = 0; + blend = m_parms.fadeBlend; + break; + + case 2: + m_parms.charTime += m_parms.pMessage->fadein; + if ( m_parms.charTime > m_parms.time ) + { + srcRed = srcGreen = srcBlue = 0; + blend = 0; // pure source + } + else + { + float deltaTime = m_parms.time - m_parms.charTime; + + destRed = destGreen = destBlue = 0; + if ( m_parms.time > m_parms.fadeTime ) + { + blend = m_parms.fadeBlend; + } + else if ( deltaTime > m_parms.pMessage->fxtime ) + blend = 0; // pure dest + else + { + destRed = m_parms.pMessage->r2; + destGreen = m_parms.pMessage->g2; + destBlue = m_parms.pMessage->b2; + blend = 255 - (deltaTime * (1.0/m_parms.pMessage->fxtime) * 255.0 + 0.5); + } + } + break; + } + if ( blend > 255 ) + blend = 255; + else if ( blend < 0 ) + blend = 0; + + m_parms.r = ((srcRed * (255-blend)) + (destRed * blend)) >> 8; + m_parms.g = ((srcGreen * (255-blend)) + (destGreen * blend)) >> 8; + m_parms.b = ((srcBlue * (255-blend)) + (destBlue * blend)) >> 8; + + if ( m_parms.pMessage->effect == 1 && m_parms.charTime != 0 ) + { + if ( m_parms.x >= 0 && m_parms.y >= 0 && (m_parms.x + gHUD.m_scrinfo.charWidths[ m_parms.text ]) <= ScreenWidth ) + TextMessageDrawChar( m_parms.x, m_parms.y, m_parms.text, m_parms.pMessage->r2, m_parms.pMessage->g2, m_parms.pMessage->b2 ); + } +} + + +void CHudMessage::MessageScanStart( void ) +{ + switch( m_parms.pMessage->effect ) + { + // Fade-in / out with flicker + case 1: + case 0: + m_parms.fadeTime = m_parms.pMessage->fadein + m_parms.pMessage->holdtime; + + + if ( m_parms.time < m_parms.pMessage->fadein ) + { + m_parms.fadeBlend = ((m_parms.pMessage->fadein - m_parms.time) * (1.0/m_parms.pMessage->fadein) * 255); + } + else if ( m_parms.time > m_parms.fadeTime ) + { + if ( m_parms.pMessage->fadeout > 0 ) + m_parms.fadeBlend = (((m_parms.time - m_parms.fadeTime) / m_parms.pMessage->fadeout) * 255); + else + m_parms.fadeBlend = 255; // Pure dest (off) + } + else + m_parms.fadeBlend = 0; // Pure source (on) + m_parms.charTime = 0; + + if ( m_parms.pMessage->effect == 1 && (rand()%100) < 10 ) + m_parms.charTime = 1; + break; + + case 2: + m_parms.fadeTime = (m_parms.pMessage->fadein * m_parms.length) + m_parms.pMessage->holdtime; + + if ( m_parms.time > m_parms.fadeTime && m_parms.pMessage->fadeout > 0 ) + m_parms.fadeBlend = (((m_parms.time - m_parms.fadeTime) / m_parms.pMessage->fadeout) * 255); + else + m_parms.fadeBlend = 0; + break; + } +} + + +void CHudMessage::MessageDrawScan( client_textmessage_t *pMessage, float time ) +{ + int i, j, length, width; + const char *pText; + unsigned char line[80]; + + pText = pMessage->pMessage; + // Count lines + m_parms.lines = 1; + m_parms.time = time; + m_parms.pMessage = pMessage; + length = 0; + width = 0; + m_parms.totalWidth = 0; + while ( *pText ) + { + if ( *pText == '\n' ) + { + m_parms.lines++; + if ( width > m_parms.totalWidth ) + m_parms.totalWidth = width; + width = 0; + } + else + width += gHUD.m_scrinfo.charWidths[*pText]; + pText++; + length++; + } + m_parms.length = length; + m_parms.totalHeight = (m_parms.lines * gHUD.m_scrinfo.iCharHeight); + + + m_parms.y = YPosition( pMessage->y, m_parms.totalHeight ); + pText = pMessage->pMessage; + + m_parms.charTime = 0; + + MessageScanStart(); + + for ( i = 0; i < m_parms.lines; i++ ) + { + m_parms.lineLength = 0; + m_parms.width = 0; + while ( *pText && *pText != '\n' ) + { + unsigned char c = *pText; + line[m_parms.lineLength] = c; + m_parms.width += gHUD.m_scrinfo.charWidths[c]; + m_parms.lineLength++; + pText++; + } + pText++; // Skip LF + line[m_parms.lineLength] = 0; + + m_parms.x = XPosition( pMessage->x, m_parms.width, m_parms.totalWidth ); + + for ( j = 0; j < m_parms.lineLength; j++ ) + { + m_parms.text = line[j]; + int next = m_parms.x + gHUD.m_scrinfo.charWidths[ m_parms.text ]; + MessageScanNextChar(); + + if ( m_parms.x >= 0 && m_parms.y >= 0 && next <= ScreenWidth ) + TextMessageDrawChar( m_parms.x, m_parms.y, m_parms.text, m_parms.r, m_parms.g, m_parms.b ); + m_parms.x = next; + } + + m_parms.y += gHUD.m_scrinfo.iCharHeight; + } +} + + +int CHudMessage::Draw( float fTime ) +{ + int i, drawn; + client_textmessage_t *pMessage; + float endTime; + + drawn = 0; + + if ( m_gameTitleTime > 0 ) + { + float localTime = gHUD.m_flTime - m_gameTitleTime; + float brightness; + + // Maybe timer isn't set yet + if ( m_gameTitleTime > gHUD.m_flTime ) + m_gameTitleTime = gHUD.m_flTime; + + if ( localTime > (m_pGameTitle->fadein + m_pGameTitle->holdtime + m_pGameTitle->fadeout) ) + m_gameTitleTime = 0; + else + { + brightness = FadeBlend( m_pGameTitle->fadein, m_pGameTitle->fadeout, m_pGameTitle->holdtime, localTime ); + + int halfWidth = gHUD.GetSpriteRect(m_HUD_title_half).right - gHUD.GetSpriteRect(m_HUD_title_half).left; + int fullWidth = halfWidth + gHUD.GetSpriteRect(m_HUD_title_life).right - gHUD.GetSpriteRect(m_HUD_title_life).left; + int fullHeight = gHUD.GetSpriteRect(m_HUD_title_half).bottom - gHUD.GetSpriteRect(m_HUD_title_half).top; + + int x = XPosition( m_pGameTitle->x, fullWidth, fullWidth ); + int y = YPosition( m_pGameTitle->y, fullHeight ); + + + SPR_Set( gHUD.GetSprite(m_HUD_title_half), brightness * m_pGameTitle->r1, brightness * m_pGameTitle->g1, brightness * m_pGameTitle->b1 ); + SPR_DrawAdditive( 0, x, y, &gHUD.GetSpriteRect(m_HUD_title_half) ); + + SPR_Set( gHUD.GetSprite(m_HUD_title_life), brightness * m_pGameTitle->r1, brightness * m_pGameTitle->g1, brightness * m_pGameTitle->b1 ); + SPR_DrawAdditive( 0, x + halfWidth, y, &gHUD.GetSpriteRect(m_HUD_title_life) ); + + drawn = 1; + } + } + // Fixup level transitions + for ( i = 0; i < maxHUDMessages; i++ ) + { + // Assume m_parms.time contains last time + if ( m_pMessages[i] ) + { + pMessage = m_pMessages[i]; + if ( m_startTime[i] > gHUD.m_flTime ) + m_startTime[i] = gHUD.m_flTime + m_parms.time - m_startTime[i] + 0.2; // Server takes 0.2 seconds to spawn, adjust for this + } + } + + for ( i = 0; i < maxHUDMessages; i++ ) + { + if ( m_pMessages[i] ) + { + pMessage = m_pMessages[i]; + + // This is when the message is over + switch( pMessage->effect ) + { + case 0: + case 1: + endTime = m_startTime[i] + pMessage->fadein + pMessage->fadeout + pMessage->holdtime; + break; + + // Fade in is per character in scanning messages + case 2: + endTime = m_startTime[i] + (pMessage->fadein * strlen( pMessage->pMessage )) + pMessage->fadeout + pMessage->holdtime; + break; + } + + if ( fTime <= endTime ) + { + float messageTime = fTime - m_startTime[i]; + + // Draw the message + // effect 0 is fade in/fade out + // effect 1 is flickery credits + // effect 2 is write out (training room) + MessageDrawScan( pMessage, messageTime ); + + drawn++; + } + else + { + // The message is over + m_pMessages[i] = NULL; + } + } + } + + // Remember the time -- to fix up level transitions + m_parms.time = gHUD.m_flTime; + // Don't call until we get another message + if ( !drawn ) + m_iFlags &= ~HUD_ACTIVE; + + return 1; +} + + +void CHudMessage::MessageAdd( const char *pName, float time ) +{ + int i; + + for ( i = 0; i < maxHUDMessages; i++ ) + { + if ( !m_pMessages[i] ) + { + m_pMessages[i] = TextMessageGet( pName ); + m_startTime[i] = time; + return; + } + } +} + + +int CHudMessage::MsgFunc_HudText( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + char *pString = READ_STRING(); + + MessageAdd( pString, gHUD.m_flTime ); + // Remember the time -- to fix up level transitions + m_parms.time = gHUD.m_flTime; + + // Turn on drawing + if ( !(m_iFlags & HUD_ACTIVE) ) + m_iFlags |= HUD_ACTIVE; + + return 1; +} + + +int CHudMessage::MsgFunc_GameTitle( const char *pszName, int iSize, void *pbuf ) +{ + m_pGameTitle = TextMessageGet( "GAMETITLE" ); + if ( m_pGameTitle != NULL ) + { + m_gameTitleTime = gHUD.m_flTime; + + // Turn on drawing + if ( !(m_iFlags & HUD_ACTIVE) ) + m_iFlags |= HUD_ACTIVE; + } + + return 1; +} +void CHudMessage::MessageAdd(client_textmessage_t * newMessage ) +{ + m_parms.time = gHUD.m_flTime; + + // Turn on drawing + if ( !(m_iFlags & HUD_ACTIVE) ) + m_iFlags |= HUD_ACTIVE; + + for ( int i = 0; i < maxHUDMessages; i++ ) + { + if ( !m_pMessages[i] ) + { + m_pMessages[i] = newMessage; + m_startTime[i] = gHUD.m_flTime; + return; + } + } + +} diff --git a/dmc/cl_dll/quake/quake_baseentity.cpp b/dmc/cl_dll/quake/quake_baseentity.cpp new file mode 100644 index 0000000..fb0d394 --- /dev/null +++ b/dmc/cl_dll/quake/quake_baseentity.cpp @@ -0,0 +1,330 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +/* +========================== +This file contains "stubs" of class member implementations so that we can predict certain + weapons client side. From time to time you might find that you need to implement part of the + these functions. If so, cut it from here, paste it in hl_weapons.cpp or somewhere else and + add in the functionality you need. +========================== +*/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "nodes.h" + +// Globals used by game logic +const Vector g_vecZero = Vector( 0, 0, 0 ); +int gmsgWeapPickup = 0; +enginefuncs_t g_engfuncs; +globalvars_t *gpGlobals; + +short g_sModelIndexLaser; + +ItemInfo CBasePlayerItem::ItemInfoArray[MAX_WEAPONS]; + +void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volume, float attenuation, int flags, int pitch) { } + +// CBaseEntity Stubs +int CBaseEntity :: TakeHealth( float flHealth, int bitsDamageType ) { return 1; } +int CBaseEntity :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) { return 1; } +CBaseEntity *CBaseEntity::GetNextTarget( void ) { return NULL; } +int CBaseEntity::Save( CSave &save ) { return 1; } +int CBaseEntity::Restore( CRestore &restore ) { return 1; } +void CBaseEntity::SetObjectCollisionBox( void ) { } +int CBaseEntity :: Intersects( CBaseEntity *pOther ) { return 0; } +void CBaseEntity :: MakeDormant( void ) { } +int CBaseEntity :: IsDormant( void ) { return 0; } +BOOL CBaseEntity :: IsInWorld( void ) { return TRUE; } +int CBaseEntity::ShouldToggle( USE_TYPE useType, BOOL currentState ) { return 0; } +int CBaseEntity :: DamageDecal( int bitsDamageType ) { return -1; } +CBaseEntity * CBaseEntity::Create( char *szName, const Vector &vecOrigin, const Vector &vecAngles, edict_t *pentOwner ) { return NULL; } +void CBaseEntity::SUB_Remove( void ) { } + +// CBaseDelay Stubs +void CBaseDelay :: KeyValue( struct KeyValueData_s * ) { } +int CBaseDelay::Restore( class CRestore & ) { return 1; } +int CBaseDelay::Save( class CSave & ) { return 1; } + +// CBaseAnimating Stubs +int CBaseAnimating::Restore( class CRestore & ) { return 1; } +int CBaseAnimating::Save( class CSave & ) { return 1; } + +// DEBUG Stubs +edict_t *DBG_EntOfVars( const entvars_t *pev ) { return NULL; } +void DBG_AssertFunction(BOOL fExpr, const char* szExpr, const char* szFile, int szLine, const char* szMessage) { } + +// UTIL_* Stubs +void UTIL_PrecacheOther( const char *szClassname ) { } +void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, int amount ) { } +void UTIL_DecalTrace( TraceResult *pTrace, int decalNumber ) { } +void UTIL_GunshotDecalTrace( TraceResult *pTrace, int decalNumber ) { } +void UTIL_MakeVectors( const Vector &vecAngles ) { } +BOOL UTIL_IsValidEntity( edict_t *pent ) { return TRUE; } +void UTIL_SetOrigin( entvars_t *, const Vector &org ) { } +BOOL UTIL_GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) { return TRUE; } +void UTIL_LogPrintf(char *,...) { } +void UTIL_ClientPrintAll( int,char const *,char const *,char const *,char const *,char const *) { } +void ClientPrint( entvars_t *client, int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) { } + +// CBaseToggle Stubs +int CBaseToggle::Restore( class CRestore & ) { return 1; } +int CBaseToggle::Save( class CSave & ) { return 1; } +void CBaseToggle :: KeyValue( struct KeyValueData_s * ) { } + +// CGrenade Stubs +void CGrenade::BounceSound( void ) { } +void CGrenade::Explode( Vector, Vector ) { } +void CGrenade::Explode( TraceResult *, int ) { } +void CGrenade::Killed( entvars_t *, int ) { } +void CGrenade::Spawn( void ) { } + +CBaseEntity* CBaseMonster :: CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ) { return NULL; } +void CBaseMonster :: Eat ( float flFullDuration ) { } +BOOL CBaseMonster :: FShouldEat ( void ) { return TRUE; } +void CBaseMonster :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) { } +void CBaseMonster :: BarnacleVictimReleased ( void ) { } +void CBaseMonster :: Listen ( void ) { } +float CBaseMonster :: FLSoundVolume ( CSound *pSound ) { return 0.0; } +BOOL CBaseMonster :: FValidateHintType ( short sHint ) { return FALSE; } +void CBaseMonster :: Look ( int iDistance ) { } +int CBaseMonster :: ISoundMask ( void ) { return 0; } +CSound* CBaseMonster :: PBestSound ( void ) { return NULL; } +CSound* CBaseMonster :: PBestScent ( void ) { return NULL; } +float CBaseAnimating :: StudioFrameAdvance ( float flInterval ) { return 0.0; } +void CBaseMonster :: MonsterThink ( void ) { } +void CBaseMonster :: MonsterUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { } +int CBaseMonster :: IgnoreConditions ( void ) { return 0; } +void CBaseMonster :: RouteClear ( void ) { } +void CBaseMonster :: RouteNew ( void ) { } +BOOL CBaseMonster :: FRouteClear ( void ) { return FALSE; } +BOOL CBaseMonster :: FRefreshRoute ( void ) { return 0; } +BOOL CBaseMonster::MoveToEnemy( Activity movementAct, float waitTime ) { return FALSE; } +BOOL CBaseMonster::MoveToLocation( Activity movementAct, float waitTime, const Vector &goal ) { return FALSE; } +BOOL CBaseMonster::MoveToTarget( Activity movementAct, float waitTime ) { return FALSE; } +BOOL CBaseMonster::MoveToNode( Activity movementAct, float waitTime, const Vector &goal ) { return FALSE; } +int ShouldSimplify( int routeType ) { return TRUE; } +void CBaseMonster :: RouteSimplify( CBaseEntity *pTargetEnt ) { } +BOOL CBaseMonster :: FBecomeProne ( void ) { return TRUE; } +BOOL CBaseMonster :: CheckRangeAttack1 ( float flDot, float flDist ) { return FALSE; } +BOOL CBaseMonster :: CheckRangeAttack2 ( float flDot, float flDist ) { return FALSE; } +BOOL CBaseMonster :: CheckMeleeAttack1 ( float flDot, float flDist ) { return FALSE; } +BOOL CBaseMonster :: CheckMeleeAttack2 ( float flDot, float flDist ) { return FALSE; } +void CBaseMonster :: CheckAttacks ( CBaseEntity *pTarget, float flDist ) { } +BOOL CBaseMonster :: FCanCheckAttacks ( void ) { return FALSE; } +int CBaseMonster :: CheckEnemy ( CBaseEntity *pEnemy ) { return 0; } +void CBaseMonster :: PushEnemy( CBaseEntity *pEnemy, Vector &vecLastKnownPos ) { } +BOOL CBaseMonster :: PopEnemy( ) { return FALSE; } +void CBaseMonster :: SetActivity ( Activity NewActivity ) { } +void CBaseMonster :: SetSequenceByName ( char *szSequence ) { } +int CBaseMonster :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ) { return 0; } +float CBaseMonster :: OpenDoorAndWait( entvars_t *pevDoor ) { return 0.0; } +void CBaseMonster :: AdvanceRoute ( float distance ) { } +int CBaseMonster :: RouteClassify( int iMoveFlag ) { return 0; } +BOOL CBaseMonster :: BuildRoute ( const Vector &vecGoal, int iMoveFlag, CBaseEntity *pTarget ) { return FALSE; } +void CBaseMonster :: InsertWaypoint ( Vector vecLocation, int afMoveFlags ) { } +BOOL CBaseMonster :: FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ) { return FALSE; } +void CBaseMonster :: Move ( float flInterval ) { } +BOOL CBaseMonster:: ShouldAdvanceRoute( float flWaypointDist ) { return FALSE; } +void CBaseMonster::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) { } +void CBaseMonster :: MonsterInit ( void ) { } +void CBaseMonster :: MonsterInitThink ( void ) { } +void CBaseMonster :: StartMonster ( void ) { } +void CBaseMonster :: MovementComplete( void ) { } +int CBaseMonster::TaskIsRunning( void ) { return 0; } +int CBaseMonster::IRelationship ( CBaseEntity *pTarget ) { return 0; } +BOOL CBaseMonster :: FindCover ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ) { return FALSE; } +BOOL CBaseMonster :: BuildNearestRoute ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ) { return FALSE; } +CBaseEntity *CBaseMonster :: BestVisibleEnemy ( void ) { return NULL; } +BOOL CBaseMonster :: FInViewCone ( CBaseEntity *pEntity ) { return FALSE; } +BOOL CBaseMonster :: FInViewCone ( Vector *pOrigin ) { return FALSE; } +BOOL CBaseEntity :: FVisible ( CBaseEntity *pEntity ) { return FALSE; } +BOOL CBaseEntity :: FVisible ( const Vector &vecOrigin ) { return FALSE; } +void CBaseMonster :: MakeIdealYaw( Vector vecTarget ) { } +float CBaseMonster::FlYawDiff ( void ) { return 0.0; } +float CBaseMonster::ChangeYaw ( int yawSpeed ) { return 0; } +float CBaseMonster::VecToYaw ( Vector vecDir ) { return 0.0; } +int CBaseAnimating :: LookupActivity ( int activity ) { return 0; } +int CBaseAnimating :: LookupActivityHeaviest ( int activity ) { return 0; } +void CBaseMonster :: SetEyePosition ( void ) { } +int CBaseAnimating :: LookupSequence ( const char *label ) { return 0; } +void CBaseAnimating :: ResetSequenceInfo ( ) { } +BOOL CBaseAnimating :: GetSequenceFlags( ) { return FALSE; } +void CBaseAnimating :: DispatchAnimEvents ( float flInterval ) { } +void CBaseMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) { } +float CBaseAnimating :: SetBoneController ( int iController, float flValue ) { return 0.0; } +void CBaseAnimating :: InitBoneControllers ( void ) { } +float CBaseAnimating :: SetBlending ( int iBlender, float flValue ) { return 0; } +void CBaseAnimating :: GetBonePosition ( int iBone, Vector &origin, Vector &angles ) { } +void CBaseAnimating :: GetAttachment ( int iAttachment, Vector &origin, Vector &angles ) { } +int CBaseAnimating :: FindTransition( int iEndingSequence, int iGoalSequence, int *piDir ) { return -1; } +void CBaseAnimating :: GetAutomovement( Vector &origin, Vector &angles, float flInterval ) { } +void CBaseAnimating :: SetBodygroup( int iGroup, int iValue ) { } +int CBaseAnimating :: GetBodygroup( int iGroup ) { return 0; } +Vector CBaseMonster :: GetGunPosition( void ) { return g_vecZero; } +void CBaseEntity::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) { } +void CBaseEntity::FireBullets(ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq, int iDamage, entvars_t *pevAttacker ) { } +void CBaseEntity :: TraceBleed( float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) { } +void CBaseMonster :: MakeDamageBloodDecal ( int cCount, float flNoise, TraceResult *ptr, const Vector &vecDir ) { } +BOOL CBaseMonster :: FGetNodeRoute ( Vector vecDest ) { return TRUE; } +int CBaseMonster :: FindHintNode ( void ) { return NO_NODE; } +void CBaseMonster::ReportAIState( void ) { } +void CBaseMonster :: KeyValue( KeyValueData *pkvd ) { } +BOOL CBaseMonster :: FCheckAITrigger ( void ) { return FALSE; } +int CBaseMonster :: CanPlaySequence( BOOL fDisregardMonsterState, int interruptLevel ) { return FALSE; } +BOOL CBaseMonster :: FindLateralCover ( const Vector &vecThreat, const Vector &vecViewOffset ) { return FALSE; } +Vector CBaseMonster :: ShootAtEnemy( const Vector &shootOrigin ) { return g_vecZero; } +BOOL CBaseMonster :: FacingIdeal( void ) { return FALSE; } +BOOL CBaseMonster :: FCanActiveIdle ( void ) { return FALSE; } +void CBaseMonster::PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ) { } +void CBaseMonster::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ) { } +void CBaseMonster::SentenceStop( void ) { } +void CBaseMonster::CorpseFallThink( void ) { } +void CBaseMonster :: MonsterInitDead( void ) { } +BOOL CBaseMonster :: BBoxFlat ( void ) { return TRUE; } +BOOL CBaseMonster :: GetEnemy ( void ) { return FALSE; } +void CBaseMonster :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) { } +CBaseEntity* CBaseMonster :: DropItem ( char *pszItemName, const Vector &vecPos, const Vector &vecAng ) { return NULL; } +BOOL CBaseMonster :: ShouldFadeOnDeath( void ) { return FALSE; } +void CBaseMonster :: RadiusDamage(entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) { } +void CBaseMonster :: RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) { } +void CBaseMonster::FadeMonster( void ) { } +void CBaseMonster :: GibMonster( void ) { } +BOOL CBaseMonster :: HasHumanGibs( void ) { return FALSE; } +BOOL CBaseMonster :: HasAlienGibs( void ) { return FALSE; } +Activity CBaseMonster :: GetDeathActivity ( void ) { return ACT_DIE_HEADSHOT; } +MONSTERSTATE CBaseMonster :: GetIdealState ( void ) { return MONSTERSTATE_ALERT; } +Schedule_t* CBaseMonster :: GetScheduleOfType ( int Type ) { return NULL; } +Schedule_t *CBaseMonster :: GetSchedule ( void ) { return NULL; } +void CBaseMonster :: RunTask ( Task_t *pTask ) { } +void CBaseMonster :: StartTask ( Task_t *pTask ) { } +Schedule_t *CBaseMonster::ScheduleFromName( const char *pName ) { return NULL;} +void CBaseMonster::BecomeDead( void ) {} +void CBaseMonster :: RunAI ( void ) {} +void CBaseMonster :: Killed( entvars_t *pevAttacker, int iGib ) {} +int CBaseMonster :: TakeHealth (float flHealth, int bitsDamageType) { return 0; } +int CBaseMonster :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { return 0; } +int CBaseMonster::Restore( class CRestore & ) { return 1; } +int CBaseMonster::Save( class CSave & ) { return 1; } + +int TrainSpeed(int iSpeed, int iMax) { return 0; } +void CBasePlayer::CheckAmmo(void) { } +void CBasePlayer :: DeathSound( void ) { } +int CBasePlayer :: TakeHealth( float flHealth, int bitsDamageType ) { return 0; } +void CBasePlayer :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) { } +int CBasePlayer :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { return 0; } +void CBasePlayer::RemoveAllItems( BOOL removeSuit ) { } +void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) { } +void CBasePlayer::WaterMove() { } +BOOL CBasePlayer::IsOnLadder( void ) { return FALSE; } +void CBasePlayer::PlayerDeathThink(void) { } +void CBasePlayer::StartDeathCam( void ) { } +void CBasePlayer::StartObserver( Vector vecPosition, Vector vecViewAngle ) { } +void CBasePlayer::PlayerUse ( void ) { } +void CBasePlayer::Jump() { } +void CBasePlayer::Duck( ) { } +int CBasePlayer::Classify ( void ) { return 0; } +void CBasePlayer :: PlayStepSound(int step, float fvol) { } +void CBasePlayer :: UpdateStepSound( void ) { } +void CBasePlayer::PreThink(void) { } +void CBasePlayer::CheckTimeBasedDamage() { } +void CBasePlayer :: UpdateGeigerCounter( void ) { } +void CBasePlayer::CheckSuitUpdate() { } +void CBasePlayer::SetSuitUpdate(char *name, int fgroup, int iNoRepeatTime) { } +void CBasePlayer::PostThink() { } +int CBasePlayer::Save( CSave &save ) { return 0; } +void CBasePlayer::RenewItems(void) { } +int CBasePlayer::Restore( CRestore &restore ) { return 0; } +void CBasePlayer::SelectNextItem( int iItem ) { } +BOOL CBasePlayer::HasWeapons( void ) { return FALSE; } +void CBasePlayer::SelectPrevItem( int iItem ) { } +CBaseEntity *FindEntityForward( CBaseEntity *pMe ) { return NULL; } +BOOL CBasePlayer :: FlashlightIsOn( void ) { return FALSE; } +void CBasePlayer :: FlashlightTurnOn( void ) { } +void CBasePlayer :: FlashlightTurnOff( void ) { } +void CBasePlayer :: ForceClientDllUpdate( void ) { } +void CBasePlayer::ImpulseCommands( ) { } +void CBasePlayer::CheatImpulseCommands( int iImpulse ) { } +int CBasePlayer::AddPlayerItem( CBasePlayerItem *pItem ) { return FALSE; } +int CBasePlayer::RemovePlayerItem( CBasePlayerItem *pItem ) { return FALSE; } +void CBasePlayer::ItemPreFrame() { } +void CBasePlayer::ItemPostFrame() { } +int CBasePlayer::AmmoInventory( int iAmmoIndex ) { return -1; } +int CBasePlayer::GetAmmoIndex(const char *psz) { return -1; } +void CBasePlayer::SendAmmoUpdate(void) { } +void CBasePlayer :: UpdateClientData( void ) { } +BOOL CBasePlayer :: FBecomeProne ( void ) { return TRUE; } +void CBasePlayer :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) { } +void CBasePlayer :: BarnacleVictimReleased ( void ) { } +int CBasePlayer :: Illumination( void ) { return 0; } +void CBasePlayer :: EnableControl(BOOL fControl) { } +Vector CBasePlayer :: GetAutoaimVector( float flDelta ) { return g_vecZero; } +Vector CBasePlayer :: AutoaimDeflection( Vector &vecSrc, float flDist, float flDelta ) { return g_vecZero; } +void CBasePlayer :: ResetAutoaim( ) { } +void CBasePlayer :: SetCustomDecalFrames( int nFrames ) { } +int CBasePlayer :: GetCustomDecalFrames( void ) { return -1; } +void CBasePlayer::DropPlayerItem ( char *pszItemName ) { } +BOOL CBasePlayer::HasPlayerItem( CBasePlayerItem *pCheckItem ) { return FALSE; } +BOOL CBasePlayer :: SwitchWeapon( CBasePlayerItem *pWeapon ) { return FALSE; } +Vector CBasePlayer :: GetGunPosition( void ) { return g_vecZero; } +const char *CBasePlayer::TeamID( void ) { return ""; } +int CBasePlayer :: GiveAmmo( int iCount, char *szName, int iMax ) { return 0; } +void CBasePlayer::AddPoints( int score, BOOL bAllowNegativeScore ) { } +void CBasePlayer::AddPointsToTeam( int score, BOOL bAllowNegativeScore ) { } + +void ClearMultiDamage(void) { } +void ApplyMultiDamage(entvars_t *pevInflictor, entvars_t *pevAttacker ) { } +void AddMultiDamage( entvars_t *pevInflictor, CBaseEntity *pEntity, float flDamage, int bitsDamageType) { } +void SpawnBlood(Vector vecSpot, int bloodColor, float flDamage) { } +int DamageDecal( CBaseEntity *pEntity, int bitsDamageType ) { return 0; } +void DecalGunshot( TraceResult *pTrace, int iBulletType ) { } +void EjectBrass ( const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int model, int soundtype ) { } +void AddAmmoNameToAmmoRegistry( const char *szAmmoname ) { } +int CBasePlayerItem::Restore( class CRestore & ) { return 1; } +int CBasePlayerItem::Save( class CSave & ) { return 1; } +int CBasePlayerWeapon::Restore( class CRestore & ) { return 1; } +int CBasePlayerWeapon::Save( class CSave & ) { return 1; } +void CBasePlayerItem :: SetObjectCollisionBox( void ) { } +void CBasePlayerItem :: FallInit( void ) { } +void CBasePlayerItem::FallThink ( void ) { } +void CBasePlayerItem::Materialize( void ) { } +void CBasePlayerItem::AttemptToMaterialize( void ) { } +void CBasePlayerItem :: CheckRespawn ( void ) { } +CBaseEntity* CBasePlayerItem::Respawn( void ) { return NULL; } +void CBasePlayerItem::DefaultTouch( CBaseEntity *pOther ) { } +void CBasePlayerItem::DestroyItem( void ) { } +int CBasePlayerItem::AddToPlayer( CBasePlayer *pPlayer ) { return TRUE; } +void CBasePlayerItem::Drop( void ) { } +void CBasePlayerItem::Kill( void ) { } +void CBasePlayerItem::Holster( int skiplocal ) { } +void CBasePlayerItem::AttachToPlayer ( CBasePlayer *pPlayer ) { } +int CBasePlayerWeapon::AddDuplicate( CBasePlayerItem *pOriginal ) { return 0; } +int CBasePlayerWeapon::AddToPlayer( CBasePlayer *pPlayer ) { return FALSE; } +int CBasePlayerWeapon::UpdateClientData( CBasePlayer *pPlayer ) { return 0; } +BOOL CBasePlayerWeapon :: AddPrimaryAmmo( int iCount, char *szName, int iMaxClip, int iMaxCarry ) { return TRUE; } +BOOL CBasePlayerWeapon :: AddSecondaryAmmo( int iCount, char *szName, int iMax ) { return TRUE; } +BOOL CBasePlayerWeapon :: IsUseable( void ) { return TRUE; } +int CBasePlayerWeapon::PrimaryAmmoIndex( void ) { return -1; } +int CBasePlayerWeapon::SecondaryAmmoIndex( void ) { return -1; } +void CBasePlayerAmmo::Spawn( void ) { } +CBaseEntity* CBasePlayerAmmo::Respawn( void ) { return this; } +void CBasePlayerAmmo::Materialize( void ) { } +void CBasePlayerAmmo :: DefaultTouch( CBaseEntity *pOther ) { } +int CBasePlayerWeapon::ExtractAmmo( CBasePlayerWeapon *pWeapon ) { return 0; } +int CBasePlayerWeapon::ExtractClipAmmo( CBasePlayerWeapon *pWeapon ) { return 0; } +void CBasePlayerWeapon::RetireWeapon( void ) { } \ No newline at end of file diff --git a/dmc/cl_dll/quake/quake_events.cpp b/dmc/cl_dll/quake/quake_events.cpp new file mode 100644 index 0000000..6925bbf --- /dev/null +++ b/dmc/cl_dll/quake/quake_events.cpp @@ -0,0 +1,83 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "../hud.h" +#include "../cl_util.h" +#include "event_api.h" + +extern "C" +{ +// Deathmatch Classic +void EV_FireShotGunSingle( struct event_args_s *args ); +void EV_FireShotGunDouble( struct event_args_s *args ); +void EV_FireAxe( struct event_args_s *args ); +void EV_FireAxeSwing( struct event_args_s *args ); +void EV_FireRocket( struct event_args_s *args ); +void EV_FireLightning( struct event_args_s *args ); +void EV_FireSpike( struct event_args_s *args ); +void EV_FireSuperSpike( struct event_args_s *args ); +void EV_FireGrenade( struct event_args_s *args ); +void EV_Gibbed (struct event_args_s *args ); +void EV_Teleport (struct event_args_s *args ); +void EV_Trail (struct event_args_s *args ); +void EV_Explosion (struct event_args_s *args ); +void EV_PlayerPowerup ( struct event_args_s *args ); + +void EV_DMC_DoorGoUp( struct event_args_s *args ); +void EV_DMC_DoorGoDown( struct event_args_s *args ); +void EV_DMC_DoorHitTop( struct event_args_s *args ); +void EV_DMC_DoorHitBottom( struct event_args_s *args ); + +// HLDM +void EV_TrainPitchAdjust( struct event_args_s *args ); +} + +/* +====================== +Game_HookEvents + +Associate script file name with callback functions. Callback's must be extern "C" so + the engine doesn't get confused about name mangling stuff. Note that the format is + always the same. Of course, a clever mod team could actually embed parameters, behavior + into the actual .sc files and create a .sc file parser and hook their functionality through + that.. i.e., a scripting system. + +That was what we were going to do, but we ran out of time...oh well. +====================== +*/ +void Game_HookEvents( void ) +{ + gEngfuncs.pfnHookEvent( "events/shotgun1.sc", EV_FireShotGunSingle ); + gEngfuncs.pfnHookEvent( "events/shotgun2.sc", EV_FireShotGunDouble ); + gEngfuncs.pfnHookEvent( "events/axe.sc", EV_FireAxe ); + gEngfuncs.pfnHookEvent( "events/axeswing.sc", EV_FireAxeSwing ); + gEngfuncs.pfnHookEvent( "events/rocket.sc", EV_FireRocket ); + gEngfuncs.pfnHookEvent( "events/lightning.sc", EV_FireLightning ); + gEngfuncs.pfnHookEvent( "events/grenade.sc", EV_FireGrenade ); + gEngfuncs.pfnHookEvent( "events/spike.sc", EV_FireSpike ); + gEngfuncs.pfnHookEvent( "events/superspike.sc", EV_FireSuperSpike ); + gEngfuncs.pfnHookEvent( "events/gibs.sc", EV_Gibbed ); + gEngfuncs.pfnHookEvent( "events/teleport.sc", EV_Teleport ); + gEngfuncs.pfnHookEvent( "events/trail.sc", EV_Trail ); + gEngfuncs.pfnHookEvent( "events/explosion.sc", EV_Explosion ); + + gEngfuncs.pfnHookEvent( "events/powerup.sc", EV_PlayerPowerup ); + + gEngfuncs.pfnHookEvent( "events/door/doorgoup.sc", EV_DMC_DoorGoUp ); + gEngfuncs.pfnHookEvent( "events/door/doorgodown.sc", EV_DMC_DoorGoDown ); + gEngfuncs.pfnHookEvent( "events/door/doorhittop.sc", EV_DMC_DoorHitTop ); + gEngfuncs.pfnHookEvent( "events/door/doorhitbottom.sc", EV_DMC_DoorHitBottom ); + + gEngfuncs.pfnHookEvent( "events/train.sc", EV_TrainPitchAdjust ); +} diff --git a/dmc/cl_dll/quake/quake_objects.cpp b/dmc/cl_dll/quake/quake_objects.cpp new file mode 100644 index 0000000..68c0a82 --- /dev/null +++ b/dmc/cl_dll/quake/quake_objects.cpp @@ -0,0 +1,81 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "../hud.h" +#include "../cl_util.h" +#include "../demo.h" + +#include "demo_api.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" + +#include "pm_defs.h" +#include "event_api.h" +#include "entity_types.h" +#include "r_efx.h" + + +extern BEAM *pBeam; +void HUD_GetLastOrg( float *org ); + +void UpdateBeams ( void ) +{ + vec3_t forward, vecSrc, vecEnd, origin, angles, right, up; + vec3_t view_ofs; + pmtrace_t tr; + cl_entity_t *pthisplayer = gEngfuncs.GetLocalPlayer(); + int idx = pthisplayer->index; + + // Get our exact viewangles from engine + gEngfuncs.GetViewAngles( (float *)angles ); + + // Determine our last predicted origin + HUD_GetLastOrg( (float *)&origin ); + + AngleVectors( angles, forward, right, up ); + + VectorCopy( origin, vecSrc ); + + VectorMA( vecSrc, 2048, forward, vecEnd ); + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( idx - 1 ); + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( vecSrc, vecEnd, PM_STUDIO_BOX, -1, &tr ); + + gEngfuncs.pEventAPI->EV_PopPMStates(); + + pBeam->target = tr.endpos; + pBeam->die = gEngfuncs.GetClientTime() + 0.1; // We keep it alive just a little bit forward in the future, just in case. +} + +/* +===================== +Game_AddObjects + +Add game specific, client-side objects here +===================== +*/ +void Game_AddObjects( void ) +{ + if ( pBeam ) + UpdateBeams(); +} \ No newline at end of file diff --git a/dmc/cl_dll/quake/quake_weapons.cpp b/dmc/cl_dll/quake/quake_weapons.cpp new file mode 100644 index 0000000..a42fdfc --- /dev/null +++ b/dmc/cl_dll/quake/quake_weapons.cpp @@ -0,0 +1,988 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" + +#include "usercmd.h" +#include "entity_state.h" +#include "demo_api.h" +#include "pm_defs.h" +#include "event_api.h" +#include "r_efx.h" + +#include "../hud_iface.h" +#include "../com_weapons.h" +#include "../demo.h" + +#include "quake_gun.h" +#include "../DMC_Teleporters.h" + +extern globalvars_t *gpGlobals; + +// Pool of client side entities/entvars_t +static entvars_t ev[ 32 ]; +static int num_ents = 0; + +// The entity we'll use to represent the local client +static CBasePlayer player; + +// Local version of game .dll global variables ( time, etc. ) +static globalvars_t Globals; + +static CBasePlayerWeapon *g_pWpns[ 32 ]; +extern int iCarriedWeapons; +int g_iWaterLevel; +// HLDM Weapon placeholder entities. +CQuakeGun g_QuakeGun; + +extern BEAM *pBeam; +/* +====================== +AlertMessage + +Print debug messages to console +====================== +*/ +void AlertMessage( ALERT_TYPE atype, char *szFmt, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start (argptr, szFmt); + vsprintf (string, szFmt,argptr); + va_end (argptr); + + gEngfuncs.Con_Printf( "cl: " ); + gEngfuncs.Con_Printf( string ); +} + +/* +===================== +HUD_PrepEntity + +Links the raw entity to an entvars_s holder. If a player is passed in as the owner, then +we set up the m_pPlayer field. +===================== +*/ +void HUD_PrepEntity( CBaseEntity *pEntity, CBasePlayer *pWeaponOwner ) +{ + memset( &ev[ num_ents ], 0, sizeof( entvars_t ) ); + pEntity->pev = &ev[ num_ents++ ]; + + pEntity->Precache(); + pEntity->Spawn(); + + if ( pWeaponOwner ) + { + ItemInfo info; + + ((CBasePlayerWeapon *)pEntity)->m_pPlayer = pWeaponOwner; + + ((CBasePlayerWeapon *)pEntity)->GetItemInfo( &info ); + + g_pWpns[ info.iId ] = (CBasePlayerWeapon *)pEntity; + } +} + +CQuakeRocket *CQuakeRocket::CreateRocket( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner ) +{ + return NULL; +} + +CQuakeRocket *CQuakeRocket::CreateGrenade( Vector vecOrigin, Vector vecVelocity, CBaseEntity *pOwner ) +{ + return NULL; +} + +CQuakeNail *CQuakeNail::CreateSuperNail( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner ) +{ + return NULL; +} + +CQuakeNail *CQuakeNail::CreateNail( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner ) +{ + return NULL; +} + +void CBasePlayer :: Precache( void ) +{ + m_usShotgunSingle = PRECACHE_EVENT( 1, "events/shotgun1.sc" ); + m_usShotgunDouble = PRECACHE_EVENT( 1, "events/shotgun2.sc" ); + m_usAxe = PRECACHE_EVENT( 1, "events/axe.sc" ); + m_usAxeSwing = PRECACHE_EVENT( 1, "events/axeswing.sc" ); + m_usRocket = PRECACHE_EVENT( 1, "events/rocket.sc" ); + m_usGrenade = PRECACHE_EVENT( 1, "events/grenade.sc" ); + m_usLightning = PRECACHE_EVENT( 1, "events/lightning.sc" ); + m_usSpike = PRECACHE_EVENT( 1, "events/spike.sc" ); + m_usSuperSpike = PRECACHE_EVENT( 1, "events/superspike.sc" ); +} + +/* +===================== +CBaseEntity :: Killed + +If weapons code "kills" an entity, just set its effects to EF_NODRAW +===================== +*/ +void CBaseEntity :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->effects |= EF_NODRAW; +} + +/* +===================== +CBasePlayerWeapon :: DefaultReload +===================== +*/ +BOOL CBasePlayerWeapon :: DefaultReload( int iClipSize, int iAnim, float fDelay ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + fDelay; + + //!!UNDONE -- reload sound goes here !!! + SendWeaponAnim( iAnim ); + + m_fInReload = TRUE; + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 3; + return TRUE; +} + +/* +===================== +CBasePlayerWeapon :: CanDeploy +===================== +*/ +BOOL CBasePlayerWeapon :: CanDeploy( void ) +{ + BOOL bHasAmmo = 0; + + if ( !pszAmmo1() ) + { + // this weapon doesn't use ammo, can always deploy. + return TRUE; + } + + if ( pszAmmo1() ) + { + bHasAmmo |= (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] != 0); + } + if ( pszAmmo2() ) + { + bHasAmmo |= (m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] != 0); + } + if (m_iClip > 0) + { + bHasAmmo |= 1; + } + if (!bHasAmmo) + { + return FALSE; + } + + return TRUE; +} + +/* +===================== +CBasePlayerWeapon :: DefaultDeploy + +===================== +*/ +BOOL CBasePlayerWeapon :: DefaultDeploy( char *szViewModel, char *szWeaponModel, int iAnim, char *szAnimExt, int skiplocal ) +{ + if ( !CanDeploy() ) + return FALSE; + + gEngfuncs.CL_LoadModel( szViewModel, &m_pPlayer->pev->viewmodel ); + + SendWeaponAnim( iAnim ); + + m_bPlayedIdleAnim = FALSE; + + m_pPlayer->m_flNextAttack = 0.5; + m_flTimeWeaponIdle = 1.0; + return TRUE; +} + +/* +===================== +CBasePlayerWeapon :: PlayEmptySound + +===================== +*/ +BOOL CBasePlayerWeapon :: PlayEmptySound( void ) +{ + if (m_iPlayEmptySound) + { + HUD_PlaySound( "weapons/357_cock1.wav", 0.8 ); + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +/* +===================== +CBasePlayerWeapon :: ResetEmptySound + +===================== +*/ +void CBasePlayerWeapon :: ResetEmptySound( void ) +{ + m_iPlayEmptySound = 1; +} + +/* +===================== +CBasePlayerWeapon::Holster + +Put away weapon +===================== +*/ +void CBasePlayerWeapon::Holster( int skiplocal /* = 0 */ ) +{ + m_fInReload = FALSE; // cancel any reload in progress. + m_pPlayer->pev->viewmodel = 0; +} + +/* +===================== +CBasePlayerWeapon::SendWeaponAnim + +Animate weapon model +===================== +*/ +void CBasePlayerWeapon::SendWeaponAnim( int iAnim, int skiplocal ) +{ + m_pPlayer->pev->weaponanim = iAnim; + + int body = 0; + + HUD_SendWeaponAnim( iAnim, body, 0 ); +} + +/* +===================== +CBasePlayerWeapon::ItemPostFrame + +Handles weapon firing, reloading, etc. +===================== +*/ +void CBasePlayerWeapon::ItemPostFrame( void ) +{ + if ((m_fInReload) && (m_pPlayer->m_flNextAttack <= 0.0)) + { +#if 0 // FIXME, need ammo on client to make this work right + // complete the reload. + int j = min( iMaxClip() - m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); + + // Add them to the clip + m_iClip += j; + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= j; +#else + m_iClip += 10; +#endif + m_fInReload = FALSE; + } + + if ((m_pPlayer->pev->button & IN_ATTACK2) && (m_flNextSecondaryAttack <= 0.0)) + { + if ( pszAmmo2() && !m_pPlayer->m_rgAmmo[SecondaryAmmoIndex()] ) + { + m_fFireOnEmpty = TRUE; + } + + SecondaryAttack(); + m_pPlayer->pev->button &= ~IN_ATTACK2; + } + else if ((m_pPlayer->pev->button & IN_ATTACK) && (m_flNextPrimaryAttack <= 0.0)) + { + if ( (m_iClip == 0 && pszAmmo1()) || (iMaxClip() == -1 && !m_pPlayer->m_rgAmmo[PrimaryAmmoIndex()] ) ) + { + m_fFireOnEmpty = TRUE; + } + + PrimaryAttack(); + } + else if ( m_pPlayer->pev->button & IN_RELOAD && iMaxClip() != WEAPON_NOCLIP && !m_fInReload ) + { + // reload when reload is pressed, or if no buttons are down and weapon is empty. + Reload(); + } + else if ( !(m_pPlayer->pev->button & (IN_ATTACK|IN_ATTACK2) ) ) + { + // no fire buttons down + + m_fFireOnEmpty = FALSE; + + if ( !m_bPlayedIdleAnim ) + { + m_bPlayedIdleAnim = TRUE; + + if ( m_pPlayer->m_iQuakeWeapon == IT_LIGHTNING ) + PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_pPlayer->m_usLightning, 0, (float *)&m_pPlayer->pev->origin, (float *)&m_pPlayer->pev->angles, 0.0, 0.0, 0, 1, 0, 0 ); + } + + WeaponIdle( ); + return; + } + + // catch all + if ( ShouldWeaponIdle() ) + { + WeaponIdle(); + } +} + +/* +===================== +CBasePlayer::SelectItem + + Switch weapons +===================== +*/ +void CBasePlayer::SelectItem(const char *pstr) +{ + if (!pstr) + return; + + CBasePlayerItem *pItem = NULL; + + if (!pItem) + return; + + + if (pItem == m_pActiveItem) + return; + + if (m_pActiveItem) + m_pActiveItem->Holster( ); + + m_pLastItem = m_pActiveItem; + m_pActiveItem = pItem; + + if (m_pActiveItem) + { + m_pActiveItem->Deploy( ); + } +} + +/* +===================== +CBasePlayer::SelectLastItem + +===================== +*/ +void CBasePlayer::SelectLastItem(void) +{ + if (!m_pLastItem) + { + return; + } + + if ( m_pActiveItem && !m_pActiveItem->CanHolster() ) + { + return; + } + + if (m_pActiveItem) + m_pActiveItem->Holster( ); + + CBasePlayerItem *pTemp = m_pActiveItem; + m_pActiveItem = m_pLastItem; + m_pLastItem = pTemp; + m_pActiveItem->Deploy( ); +} + +/* +===================== +CBasePlayer::Killed + +===================== +*/ +void CBasePlayer::Killed( entvars_t *pevAttacker, int iGib ) +{ + if ( m_iQuakeWeapon == IT_LIGHTNING ) + { + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usLightning, 0, (float *)&pev->origin, (float *)&pev->angles, 0.0, 0.0, 0, 1, 0, 0 ); + } + + // Holster weapon immediately, to allow it to cleanup + if (m_pActiveItem) + m_pActiveItem->Holster( ); +} + +/* +===================== +CBasePlayer::Spawn + +===================== +*/ +void CBasePlayer::Spawn( void ) +{ + if (m_pActiveItem) + m_pActiveItem->Deploy( ); +} + +BOOL CQuakeGun::Deploy( ) +{ + gEngfuncs.CL_LoadModel( "models/v_axe.mdl", &m_pPlayer->pev->viewmodel ); + strcpy( m_pPlayer->m_szAnimExtention, "onehanded" ); + return TRUE; +} + +int HUD_GetModelIndex( char *modelname ) +{ + int retval = 0; + gEngfuncs.CL_LoadModel( modelname, &retval ); + return retval; +} + +/* +===================== +UTIL_TraceLine + +Don't actually trace, but act like the trace didn't hit anything. +===================== +*/ +void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, edict_t *pentIgnore, TraceResult *ptr ) +{ + memset( ptr, 0, sizeof( *ptr ) ); + ptr->flFraction = 1.0; +} + +/* +===================== +UTIL_ParticleBox + +For debugging, draw a box around a player made out of particles +===================== +*/ +void UTIL_ParticleBox( CBasePlayer *player, float *mins, float *maxs, float life, unsigned char r, unsigned char g, unsigned char b ) +{ + int i; + vec3_t mmin, mmax; + + for ( i = 0; i < 3; i++ ) + { + mmin[ i ] = player->pev->origin[ i ] + mins[ i ]; + mmax[ i ] = player->pev->origin[ i ] + maxs[ i ]; + } + + gEngfuncs.pEfxAPI->R_ParticleBox( (float *)&mmin, (float *)&mmax, 5.0, 0, 255, 0 ); +} + +/* +===================== +UTIL_ParticleBoxes + +For debugging, draw boxes for other collidable players +===================== +*/ +void UTIL_ParticleBoxes( void ) +{ + int idx; + physent_t *pe; + cl_entity_t *player; + vec3_t mins, maxs; + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + player = gEngfuncs.GetLocalPlayer(); + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( player->index - 1 ); + + for ( idx = 1; idx < 100; idx++ ) + { + pe = gEngfuncs.pEventAPI->EV_GetPhysent( idx ); + if ( !pe ) + break; + + if ( pe->info >= 1 && pe->info <= gEngfuncs.GetMaxClients() ) + { + mins = pe->origin + pe->mins; + maxs = pe->origin + pe->maxs; + + gEngfuncs.pEfxAPI->R_ParticleBox( (float *)&mins, (float *)&maxs, 0, 0, 255, 2.0 ); + } + } + + gEngfuncs.pEventAPI->EV_PopPMStates(); +} + +/* +===================== +UTIL_ParticleLine + +For debugging, draw a line made out of particles +===================== +*/ +void UTIL_ParticleLine( CBasePlayer *player, float *start, float *end, float life, unsigned char r, unsigned char g, unsigned char b ) +{ + gEngfuncs.pEfxAPI->R_ParticleLine( start, end, r, g, b, life ); +} + +/* +===================== +CBasePlayerWeapon::PrintState + +For debugging, print out state variables to log file +===================== +*/ +void CBasePlayerWeapon::PrintState( void ) +{ + COM_Log( "c:\\hl.log", "%.4f ", gpGlobals->time ); + COM_Log( "c:\\hl.log", "%.4f ", m_pPlayer->m_flNextAttack ); + COM_Log( "c:\\hl.log", "%.4f ", m_flNextPrimaryAttack ); + COM_Log( "c:\\hl.log", "%.4f ", m_flTimeWeaponIdle - gpGlobals->time); + COM_Log( "c:\\hl.log", "%i ", m_iClip ); +} + +vec3_t previousorigin; + +/* +===================== +HUD_GetLastOrg + +Retruns the last position that we stored for egon beam endpoint. +===================== +*/ +void HUD_GetLastOrg( float *org ) +{ + int i; + + // Return last origin + for ( i = 0; i < 3; i++ ) + { + org[i] = previousorigin[i]; + } +} + +/* +===================== +HUD_SetLastOrg + +Remember our exact predicted origin so we can draw the egon to the right position. +===================== +*/ +void HUD_SetLastOrg( void ) +{ + int i; + + // Offset final origin by view_offset + for ( i = 0; i < 3; i++ ) + { + previousorigin[i] = g_finalstate->playerstate.origin[i] + g_finalstate->client.view_ofs[ i ]; + } +} + +/* +===================== +HUD_InitClientWeapons + +Set up weapons, player and functions needed to run weapons code client-side. +===================== +*/ +void HUD_InitClientWeapons( void ) +{ + static int initialized = 0; + if ( initialized ) + return; + + initialized = 1; + + // Set up pointer ( dummy object ) + gpGlobals = &Globals; + + // Fill in current time ( probably not needed ) + gpGlobals->time = gEngfuncs.GetClientTime(); + + // Fake functions + g_engfuncs.pfnPrecacheModel = stub_PrecacheModel; + g_engfuncs.pfnPrecacheSound = stub_PrecacheSound; + g_engfuncs.pfnPrecacheEvent = stub_PrecacheEvent; + g_engfuncs.pfnNameForFunction = stub_NameForFunction; + g_engfuncs.pfnSetModel = stub_SetModel; + g_engfuncs.pfnSetClientMaxspeed = HUD_SetMaxSpeed; + + // Handled locally + g_engfuncs.pfnPlaybackEvent = HUD_PlaybackEvent; + g_engfuncs.pfnAlertMessage = AlertMessage; + + // Pass through to engine + g_engfuncs.pfnPrecacheEvent = gEngfuncs.pfnPrecacheEvent; + g_engfuncs.pfnRandomFloat = gEngfuncs.pfnRandomFloat; + g_engfuncs.pfnRandomLong = gEngfuncs.pfnRandomLong; + + // Allocate a slot for the local player + HUD_PrepEntity( &player , NULL ); + + // Allocate slot(s) for each weapon that we are going to be predicting + HUD_PrepEntity( &g_QuakeGun , &player ); +} + +int Quake_NumForWeaponItem( int quakeitem ) +{ + int retval = 1; + switch ( quakeitem ) + { + default: + case IT_AXE: + retval = 1; + break; + case IT_SHOTGUN: + retval = 2; + break; + case IT_SUPER_SHOTGUN: + retval = 3; + break; + case IT_NAILGUN: + retval = 4; + break; + case IT_SUPER_NAILGUN: + retval = 5; + break; + case IT_GRENADE_LAUNCHER: + retval = 6; + break; + case IT_ROCKET_LAUNCHER: + retval = 7; + break; + + case IT_LIGHTNING: + retval = 8; + break; + } + + return retval; +} + +/* +===================== +HUD_WeaponsPostThink + +Run Weapon firing code on client +===================== +*/ +void HUD_WeaponsPostThink( local_state_s *from, local_state_s *to, usercmd_t *cmd, double time, unsigned int random_seed ) +{ + int i; + int buttonsChanged; + CBasePlayerWeapon *pWeapon = NULL; + CBasePlayerWeapon *pCurrent; + weapon_data_t nulldata, *pfrom, *pto; + static int lasthealth; + + memset( &nulldata, 0, sizeof( nulldata ) ); + + HUD_InitClientWeapons(); + + // Get current clock + gpGlobals->time = time; + + // Fill in data based on selected weapon + // FIXME, make this a method in each weapon? where you pass in an entity_state_t *? + switch ( from->client.m_iId ) + { + case WEAPON_GLOCK: + pWeapon = &g_QuakeGun; + break; + } + + // Store pointer to our destination entity_state_t so we can get our origin, etc. from it + // for setting up events on the client + g_finalstate = to; + + // If we are running events/etc. go ahead and see if we + // managed to die between last frame and this one + // If so, run the appropriate player killed or spawn function + if ( g_runfuncs ) + { + if ( to->client.health <= 0 && lasthealth > 0 ) + { + player.Killed( NULL, 0 ); + } + else if ( to->client.health > 0 && lasthealth <= 0 ) + { + player.Spawn(); + } + + lasthealth = to->client.health; + } + + // We are not predicting the current weapon, just bow out here. + if ( !pWeapon ) + return; + + gpGlobals->deathmatch = from->client.iuser4; + + for ( i = 0; i < 32; i++ ) + { + pCurrent = g_pWpns[ i ]; + if ( !pCurrent ) + { + continue; + } + + pfrom = &from->weapondata[ i ]; + + pCurrent->m_fInReload = pfrom->m_fInReload; + pCurrent->m_iClip = pfrom->m_iClip; + pCurrent->m_flNextPrimaryAttack = pfrom->m_flNextPrimaryAttack; + pCurrent->m_flNextSecondaryAttack = pfrom->m_flNextSecondaryAttack; + pCurrent->m_flTimeWeaponIdle = pfrom->m_flTimeWeaponIdle; + } + + // For random weapon events, use this seed to seed random # generator + player.random_seed = random_seed; + + // Get old buttons from previous state. + player.m_afButtonLast = from->playerstate.oldbuttons; + + // Which buttsons chave changed + buttonsChanged = (player.m_afButtonLast ^ cmd->buttons); // These buttons have changed this frame + + // Debounced button codes for pressed/released + // The changed ones still down are "pressed" + player.m_afButtonPressed = buttonsChanged & cmd->buttons; + // The ones not down are "released" + player.m_afButtonReleased = buttonsChanged & (~cmd->buttons); + + // Set player variables that weapons code might check/alter + player.pev->button = cmd->buttons; + + player.pev->velocity = from->client.velocity; + player.pev->flags = from->client.flags; + + player.pev->deadflag = from->client.deadflag; + g_iWaterLevel = player.pev->waterlevel = from->client.waterlevel; + player.pev->maxspeed = from->client.maxspeed; + player.pev->fov = from->client.fov; + player.pev->weaponanim = from->client.weaponanim; + player.pev->viewmodel = from->client.viewmodel; + player.m_flNextAttack = from->client.m_flNextAttack; + player.m_iQuakeWeapon = (int)from->client.fuser1; + iCarriedWeapons = player.m_iQuakeItems = from->client.iuser3; + + player.m_iAmmoShells = from->client.ammo_shells; + player.m_iAmmoCells = from->client.ammo_cells; + player.m_iAmmoRockets = from->client.ammo_rockets; + player.m_iAmmoNails = from->client.ammo_nails; + + player.m_iNailOffset = (int)from->client.fuser2 != 0.0 ? 4.0 : -4.0; + + + + // REally useful for debugging prediction +/* if ( player.m_iQuakeWeapon > 0 ) + { + gEngfuncs.Con_NPrintf( 9, "got qw %i", player.m_iQuakeWeapon ); + char items[33]; + for ( int i = 0; i < 32; i++ ) + { + if ( player.m_iQuakeItems & (1<viewmodel ); + gEngfuncs.Con_NPrintf( 16, "dm == %i", gpGlobals->deathmatch ); + }*/ + + + // Point to current weapon object + if ( from->client.m_iId ) + { + player.m_pActiveItem = g_pWpns[ from->client.m_iId ]; + } + + // Set ammo, but don't change anim + player.W_SetCurrentAmmo( 0 ); + + + + // Don't go firing anything if we have died. + // Or if we don't have a weapon model deployed + if ( ( player.pev->deadflag != ( DEAD_DISCARDBODY + 1 ) ) && !CL_IsDead() ) // && player.pev->viewmodel ) + { + if ( player.m_flNextAttack <= 0 ) + { + pWeapon->ItemPostFrame(); + } + } + + // Assume that we are not going to switch weapons + to->client.m_iId = from->client.m_iId; + + // Now see if we issued a changeweapon command ( and we're not dead ) + if ( cmd->weaponselect && ( player.pev->deadflag != ( DEAD_DISCARDBODY + 1 ) ) ) + { + // Switched to a different weapon? + if ( Quake_NumForWeaponItem( player.m_iQuakeWeapon ) != cmd->weaponselect ) + { + player.W_ChangeWeapon( cmd->weaponselect ); + } + } + + if ( player.m_iQuakeWeapon != IT_LIGHTNING && pBeam != NULL ) + { + pBeam->die = 0.0; + pBeam = NULL; + } + // Copy in results of predcition code + + to->client.viewmodel = player.pev->viewmodel; + to->client.fov = player.pev->fov; + to->client.weaponanim = player.pev->weaponanim; + to->client.m_flNextAttack = player.m_flNextAttack; + to->client.maxspeed = player.pev->maxspeed; + to->client.iuser3 = player.m_iQuakeItems; + to->client.fuser1 = (float)player.m_iQuakeWeapon; + to->client.fuser2 = (float)player.m_iNailOffset > 0.0 ? 1.0 : 0.0; + + to->client.ammo_shells = player.m_iAmmoShells; + to->client.ammo_cells = player.m_iAmmoCells; + to->client.ammo_rockets = player.m_iAmmoRockets; + to->client.ammo_nails = player.m_iAmmoNails; + + // Make sure that weapon animation matches what the game .dll is telling us + // over the wire ( fixes some animation glitches ) + if ( g_runfuncs && ( HUD_GetWeaponAnim() != to->client.weaponanim ) ) + { + int body = 2; + // Force a fixed anim down to viewmodel + HUD_SendWeaponAnim( to->client.weaponanim, body, 1 ); + } + + for ( i = 0; i < 32; i++ ) + { + pCurrent = g_pWpns[ i ]; + + pto = &to->weapondata[ i ]; + + if ( !pCurrent ) + { + memset( pto, 0, sizeof( weapon_data_t ) ); + continue; + } + + pto->m_fInReload = pCurrent->m_fInReload; + pto->m_iClip = pCurrent->m_iClip; + pto->m_flNextPrimaryAttack = pCurrent->m_flNextPrimaryAttack; + pto->m_flNextSecondaryAttack = pCurrent->m_flNextSecondaryAttack; + pto->m_flTimeWeaponIdle = pCurrent->m_flTimeWeaponIdle; + + // Decrement weapon counters, server does this at same time ( during post think, after doing everything else ) + pto->m_flNextReload -= cmd->msec / 1000.0; + pto->m_fNextAimBonus -= cmd->msec / 1000.0; + pto->m_flNextPrimaryAttack -= cmd->msec / 1000.0; + pto->m_flNextSecondaryAttack -= cmd->msec / 1000.0; + pto->m_flTimeWeaponIdle -= cmd->msec / 1000.0; + + if ( pto->m_flPumpTime != -9999 ) + { + pto->m_flPumpTime -= cmd->msec / 1000.0; + if ( pto->m_flPumpTime < -0.001 ) + pto->m_flPumpTime = -0.001; + } + + if ( pto->m_fNextAimBonus < -1.0 ) + { + pto->m_fNextAimBonus = -1.0; + } + + if ( pto->m_flNextPrimaryAttack < -1.0 ) + { + pto->m_flNextPrimaryAttack = -1.0; + } + + if ( pto->m_flNextSecondaryAttack < -0.001 ) + { + pto->m_flNextSecondaryAttack = -0.001; + } + + if ( pto->m_flTimeWeaponIdle < -0.001 ) + { + pto->m_flTimeWeaponIdle = -0.001; + } + + if ( pto->m_flNextReload < -0.001 ) + { + pto->m_flNextReload = -0.001; + } + } + + // m_flNextAttack is now part of the weapons, but is part of the player instead + to->client.m_flNextAttack -= cmd->msec / 1000.0; + if ( to->client.m_flNextAttack < -0.001 ) + { + to->client.m_flNextAttack = -0.001; + } + + // Store off the last position from the predicted state. + HUD_SetLastOrg(); + // Wipe it so we can't use it after this frame + g_finalstate = NULL; +} + +/* +===================== +HUD_PostRunCmd + +Client calls this during prediction, after it has moved the player and updated any info changed into to-> +time is the current client clock based on prediction +cmd is the command that caused the movement, etc +runfuncs is 1 if this is the first time we've predicted this command. If so, sounds and effects should play, otherwise, they should +be ignored +===================== +*/ +void _DLLEXPORT HUD_PostRunCmd( struct local_state_s *from, struct local_state_s *to, struct usercmd_s *cmd, int runfuncs, double time, unsigned int random_seed ) +{ + g_runfuncs = runfuncs; + + // Only run post think stuff for glock for the sample + // implementation + if ( cl_lw && cl_lw->value ) + { + HUD_WeaponsPostThink( from, to, cmd, time, random_seed ); + } + else + { + to->client.fov = g_lastFOV; + } + + Dmc_CheckTeleporters( from, to ); // See if we stepped on a jump pad + + // All games can use FOV state + g_lastFOV = to->client.fov; +} diff --git a/dmc/cl_dll/readme.txt b/dmc/cl_dll/readme.txt new file mode 100644 index 0000000..249205c --- /dev/null +++ b/dmc/cl_dll/readme.txt @@ -0,0 +1,107 @@ + client dll readme.txt +------------------------- + +This file details the structure of the half-life client dll, and +how it communicates with the half-life game engine. + + +Engine callback functions: + +Drawing functions: + HSPRITE SPR_Load( char *picname ); + Loads a sprite into memory, and returns a handle to it. + + int SPR_Frames( HSPRITE sprite ); + Returns the number of frames stored in the specified sprite. + + int SPR_Height( HSPRITE x, int frame ) + Returns the height, in pixels, of a sprite at the specified frame. + Returns 0 is the frame number or the sprite handle is invalid. + + int SPR_Width( HSPRITE x, int f ) + Returns the width, in pixels, of a sprite at the specified frame. + Returns 0 is the frame number or the sprite handle is invalid. + + int SPR_Set( HSPRITE sprite, int r, int g, int b ); + Prepares a sprite about to be drawn. RBG color values are applied to the sprite at this time. + + + void SPR_Draw( int frame, int x, int y ); + Precondition: SPR_Set has already been called for a sprite. + Draws the currently active sprite to the screen, at position (x,y), where (0,0) is + the top left-hand corner of the screen. + + + void SPR_DrawHoles( int frame, int x, int y ); + Precondition: SPR_Set has already been called for a sprite. + Draws the currently active sprite to the screen. Color index #255 is treated as transparent. + + void SPR_DrawAdditive( int frame, int x, int y ); + Precondition: SPR_Set has already been called for a sprite. + Draws the currently active sprite to the screen, adding it's color values to the background. + + void SPR_EnableScissor( int x, int y, int width, int height ); + Creates a clipping rectangle. No pixels will be drawn outside the specified area. Will + stay in effect until either the next frame, or SPR_DisableScissor is called. + + void SPR_DisableScissor( void ); + Disables the effect of an SPR_EnableScissor call. + + int IsHighRes( void ); + returns 1 if the res mode is 640x480 or higher; 0 otherwise. + + int ScreenWidth( void ); + returns the screen width, in pixels. + + int ScreenHeight( void ); + returns the screen height, in pixels. + +// Sound functions + void PlaySound( char *szSound, int volume ) + plays the sound 'szSound' at the specified volume. Loads the sound if it hasn't been cached. + If it can't find the sound, it displays an error message and plays no sound. + + void PlaySound( int iSound, int volume ) + Precondition: iSound has been precached. + Plays the sound, from the precache list. + + +// Communication functions + void SendClientCmd( char *szCmdString ); + sends a command to the server, just as if the client had typed the szCmdString at the console. + + char *GetPlayerName( int entity_number ); + returns a pointer to a string, that contains the name of the specified client. + Returns NULL if the entity_number is not a client. + + + DECLARE_MESSAGE(), HOOK_MESSAGE() + These two macros bind the message sending between the entity DLL and the client DLL to + the CHud object. + + HOOK_MESSAGE( message_name ) + This is used inside CHud::Init(). It calls into the engine to hook that message + from the incoming message stream. + Precondition: There must be a function of name UserMsg_message_name declared + for CHud. Eg, CHud::UserMsg_Health() must be declared if you want to + use HOOK_MESSAGE( Health ); + + DECLARE_MESSAGE( message_name ) + For each HOOK_MESSAGE you must have an equivalent DECLARE_MESSAGE. This creates + a function which passes the hooked messages into the CHud object. + + + HOOK_COMMAND(), DECLARE_COMMAND() + These two functions declare and hook console commands into the client dll. + + HOOK_COMMAND( char *command, command_name ) + Whenever the user types the 'command' at the console, the function 'command_name' + will be called. + Precondition: There must be a function of the name UserCmd_command_name declared + for CHud. Eg, CHud::UserMsg_ShowScores() must be declared if you want to + use HOOK_COMMAND( "+showscores", ShowScores ); + + DECLARE_COMMAND( command_name ) + For each HOOK_COMMAND you must have an equivelant DECLARE_COMMAND. This creates + a function which passes the hooked commands into the CHud object. + diff --git a/dmc/cl_dll/saytext.cpp b/dmc/cl_dll/saytext.cpp new file mode 100644 index 0000000..20d30ff --- /dev/null +++ b/dmc/cl_dll/saytext.cpp @@ -0,0 +1,312 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// saytext.cpp +// +// implementation of CHudSayText class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +extern float *GetClientColor( int clientIndex ); +extern hud_player_info_t g_PlayerInfoList[MAX_PLAYERS+1]; + +#define MAX_LINES 5 +#define MAX_CHARS_PER_LINE 256 /* it can be less than this, depending on char size */ + +// allow 20 pixels on either side of the text +#define MAX_LINE_WIDTH ( ScreenWidth - 40 ) +#define LINE_START 10 +static float SCROLL_SPEED = 5; + +static char g_szLineBuffer[ MAX_LINES + 1 ][ MAX_CHARS_PER_LINE ]; +static float *g_pflNameColors[ MAX_LINES + 1 ]; +static int g_iNameLengths[ MAX_LINES + 1 ]; +static float flScrollTime = 0; // the time at which the lines next scroll up + +static int Y_START = 0; +static int line_height = 0; + +DECLARE_MESSAGE( m_SayText, SayText ); + +int CHudSayText :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( SayText ); + + InitHUDData(); + + m_HUD_saytext = gEngfuncs.pfnRegisterVariable( "hud_saytext", "1", 0 ); + m_HUD_saytext_time = gEngfuncs.pfnRegisterVariable( "hud_saytext_time", "5", 0 ); + + return 1; +} + + +void CHudSayText :: InitHUDData( void ) +{ + memset( g_szLineBuffer, 0, sizeof g_szLineBuffer ); + memset( g_pflNameColors, 0, sizeof g_pflNameColors ); + memset( g_iNameLengths, 0, sizeof g_iNameLengths ); + + m_iFlags |= HUD_INTERMISSION; // is always drawn during an intermission +} + +int CHudSayText :: VidInit( void ) +{ + return 1; +} + + +int ScrollTextUp( void ) +{ + ConsolePrint( g_szLineBuffer[0] ); // move the first line into the console buffer + g_szLineBuffer[MAX_LINES][0] = 0; + memmove( g_szLineBuffer[0], g_szLineBuffer[1], sizeof(g_szLineBuffer) - sizeof(g_szLineBuffer[0]) ); // overwrite the first line + memmove( &g_pflNameColors[0], &g_pflNameColors[1], sizeof(g_pflNameColors) - sizeof(g_pflNameColors[0]) ); + memmove( &g_iNameLengths[0], &g_iNameLengths[1], sizeof(g_iNameLengths) - sizeof(g_iNameLengths[0]) ); + g_szLineBuffer[MAX_LINES-1][0] = 0; + + if ( g_szLineBuffer[0][0] == ' ' ) // also scroll up following lines + { + g_szLineBuffer[0][0] = 2; + return 1 + ScrollTextUp(); + } + + return 1; +} + +int CHudSayText :: Draw( float flTime ) +{ + int y = Y_START; + + // make sure the scrolltime is within reasonable bounds, to guard against the clock being reset + flScrollTime = min( flScrollTime, flTime + m_HUD_saytext_time->value ); + + // make sure the scrolltime is within reasonable bounds, to guard against the clock being reset + flScrollTime = min( flScrollTime, flTime + m_HUD_saytext_time->value ); + + if ( flScrollTime <= flTime ) + { + if ( *g_szLineBuffer[0] ) + { + flScrollTime = flTime + m_HUD_saytext_time->value; + // push the console up + ScrollTextUp(); + } + else + { // buffer is empty, just disable drawing of this section + m_iFlags &= ~HUD_ACTIVE; + } + } + + for ( int i = 0; i < MAX_LINES; i++ ) + { + if ( *g_szLineBuffer[i] ) + { + if ( *g_szLineBuffer[i] == 2 && g_pflNameColors[i] ) + { + // it's a saytext string + static char buf[MAX_PLAYER_NAME_LENGTH+32]; + + // draw the first x characters in the player color + strncpy( buf, g_szLineBuffer[i], min(g_iNameLengths[i], MAX_PLAYER_NAME_LENGTH+32) ); + buf[ min(g_iNameLengths[i], MAX_PLAYER_NAME_LENGTH+31) ] = 0; + gEngfuncs.pfnDrawSetTextColor( g_pflNameColors[i][0], g_pflNameColors[i][1], g_pflNameColors[i][2] ); + + int x = DrawConsoleString( LINE_START, y, buf ); + + // color is reset after each string draw + DrawConsoleString( x, y, g_szLineBuffer[i] + g_iNameLengths[i] ); + } + else + { + // normal draw + DrawConsoleString( LINE_START, y, g_szLineBuffer[i] ); + } + } + + y += line_height; + } + + + return 1; +} + +int CHudSayText :: MsgFunc_SayText( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int client_index = READ_BYTE(); // the client who spoke the message + SayTextPrint( READ_STRING(), iSize - 1, client_index ); + + return 1; +} + +void CHudSayText :: SayTextPrint( const char *pszBuf, int iBufSize, int clientIndex ) +{ + // find an empty string slot + int i; + for ( i = 0; i < MAX_LINES; i++ ) + { + if ( ! *g_szLineBuffer[i] ) + break; + } + if ( i == MAX_LINES ) + { + // force scroll buffer up + ScrollTextUp(); + i = MAX_LINES - 1; + } + + g_iNameLengths[i] = 0; + g_pflNameColors[i] = NULL; + + // if it's a say message, search for the players name in the string + if ( *pszBuf == 2 && clientIndex > 0 ) + { + GetPlayerInfo( clientIndex, &g_PlayerInfoList[clientIndex] ); + const char *pName = g_PlayerInfoList[clientIndex].name; + + if ( pName ) + { + const char *nameInString = strstr( pszBuf, pName ); + + if ( nameInString ) + { + g_iNameLengths[i] = strlen( pName ) + (nameInString - pszBuf); + g_pflNameColors[i] = GetClientColor( clientIndex ); + } + } + } + + strncpy( g_szLineBuffer[i], pszBuf, max(iBufSize -1, MAX_CHARS_PER_LINE-1) ); + + // make sure the text fits in one line + EnsureTextFitsInOneLineAndWrapIfHaveTo( i ); + + // Set scroll time + if ( i == 0 ) + { + flScrollTime = gHUD.m_flTime + m_HUD_saytext_time->value; + } + + m_iFlags |= HUD_ACTIVE; + PlaySound( "misc/talk.wav", 1 ); + + if ( ScreenHeight >= 480 ) + Y_START = ScreenHeight - 138; + else + Y_START = ScreenHeight - 70; + Y_START -= (line_height * (MAX_LINES+1)); + +} + +void CHudSayText :: EnsureTextFitsInOneLineAndWrapIfHaveTo( int line ) +{ + int line_width = 0; + GetConsoleStringSize( g_szLineBuffer[line], &line_width, &line_height ); + + if ( (line_width + LINE_START) > MAX_LINE_WIDTH ) + { // string is too long to fit on line + // scan the string until we find what word is too long, and wrap the end of the sentence after the word + int length = LINE_START; + int tmp_len = 0; + char *last_break = NULL; + for ( char *x = g_szLineBuffer[line]; *x != 0; x++ ) + { + // check for a color change, if so skip past it + if ( x[0] == '/' && x[1] == '(' ) + { + x += 2; + // skip forward until past mode specifier + while ( *x != 0 && *x != ')' ) + x++; + + if ( *x != 0 ) + x++; + + if ( *x == 0 ) + break; + } + + char buf[2]; + buf[1] = 0; + + if ( *x == ' ' && x != g_szLineBuffer[line] ) // store each line break, except for the very first character + last_break = x; + + buf[0] = *x; // get the length of the current character + GetConsoleStringSize( buf, &tmp_len, &line_height ); + length += tmp_len; + + if ( length > MAX_LINE_WIDTH ) + { // needs to be broken up + if ( !last_break ) + last_break = x-1; + + x = last_break; + + // find an empty string slot + int j; + do + { + for ( j = 0; j < MAX_LINES; j++ ) + { + if ( ! *g_szLineBuffer[j] ) + break; + } + if ( j == MAX_LINES ) + { + // need to make more room to display text, scroll stuff up then fix the pointers + int linesmoved = ScrollTextUp(); + line -= linesmoved; + last_break = last_break - (sizeof(g_szLineBuffer[0]) * linesmoved); + } + } + while ( j == MAX_LINES ); + + // copy remaining string into next buffer, making sure it starts with a space character + if ( (char)*last_break == (char)' ' ) + { + int linelen = strlen(g_szLineBuffer[j]); + int remaininglen = strlen(last_break); + + if ( (linelen - remaininglen) <= MAX_CHARS_PER_LINE ) + strcat( g_szLineBuffer[j], last_break ); + } + else + { + if ( (strlen(g_szLineBuffer[j]) - strlen(last_break) - 2) < MAX_CHARS_PER_LINE ) + { + strcat( g_szLineBuffer[j], " " ); + strcat( g_szLineBuffer[j], last_break ); + } + } + + *last_break = 0; // cut off the last string + + EnsureTextFitsInOneLineAndWrapIfHaveTo( j ); + break; + } + } + } +} diff --git a/dmc/cl_dll/scoreboard.cpp b/dmc/cl_dll/scoreboard.cpp new file mode 100644 index 0000000..adfe64d --- /dev/null +++ b/dmc/cl_dll/scoreboard.cpp @@ -0,0 +1,527 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Scoreboard.cpp +// +// implementation of CHudScoreboard class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +DECLARE_COMMAND( m_Scoreboard, ShowScores ); +DECLARE_COMMAND( m_Scoreboard, HideScores ); + +DECLARE_MESSAGE( m_Scoreboard, ScoreInfo ); +DECLARE_MESSAGE( m_Scoreboard, TeamInfo ); +DECLARE_MESSAGE( m_Scoreboard, TeamScore ); + + +int CHudScoreboard :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( ScoreInfo ); + HOOK_MESSAGE( TeamScore ); + HOOK_MESSAGE( TeamInfo ); + + InitHUDData(); + + return 1; +} + + +int CHudScoreboard :: VidInit( void ) +{ + + return 1; +} + +void CHudScoreboard :: InitHUDData( void ) +{ + memset( g_PlayerExtraInfo, 0, sizeof g_PlayerExtraInfo ); + m_iLastKilledBy = 0; + m_fLastKillTime = 0; + m_iPlayerNum = 0; + m_iNumTeams = 0; + + memset( m_TeamInfo, 0, sizeof m_TeamInfo ); + + m_iFlags &= ~HUD_ACTIVE; // starts out inactive + + m_iFlags |= HUD_INTERMISSION; // is always drawn during an intermission +} + +/* The scoreboard +We have a minimum width of 1-320 - we could have the field widths scale with it? +*/ + +// X positions +// relative to the side of the scoreboard +#define NAME_RANGE_MIN 20 +#define NAME_RANGE_MAX 145 +#define KILLS_RANGE_MIN 130 +#define KILLS_RANGE_MAX 170 +#define DIVIDER_POS 180 +#define DEATHS_RANGE_MIN 185 +#define DEATHS_RANGE_MAX 210 +#define PING_RANGE_MIN 245 +#define PING_RANGE_MAX 295 + +#define SCOREBOARD_WIDTH 320 + + +// Y positions +#define ROW_GAP 13 +#define ROW_RANGE_MIN 15 +#define ROW_RANGE_MAX ( ScreenHeight - 50 ) + +int CHudScoreboard :: Draw( float fTime ) +{ + if ( !m_iShowscoresHeld && gHUD.m_Health.m_iHealth > 0 && !gHUD.m_iIntermission ) + return 1; + + + GetAllPlayersInfo(); + + // just sort the list on the fly + // list is sorted first by frags, then by deaths + float list_slot = 0; + int xpos_rel = (ScreenWidth - SCOREBOARD_WIDTH) / 2; + + // print the heading line + int ypos = ROW_RANGE_MIN + (list_slot * ROW_GAP); + int xpos = NAME_RANGE_MIN + xpos_rel; + + if ( !gHUD.m_Teamplay ) + gHUD.DrawHudString( xpos, ypos, NAME_RANGE_MAX + xpos_rel, "Player", 255, 140, 0 ); + else + gHUD.DrawHudString( xpos, ypos, NAME_RANGE_MAX + xpos_rel, "Teams", 255, 140, 0 ); + + gHUD.DrawHudStringReverse( KILLS_RANGE_MAX + xpos_rel, ypos, 0, "kills", 255, 140, 0 ); + gHUD.DrawHudString( DIVIDER_POS + xpos_rel, ypos, ScreenWidth, "/", 255, 140, 0 ); + gHUD.DrawHudString( DEATHS_RANGE_MIN + xpos_rel + 5, ypos, ScreenWidth, "deaths", 255, 140, 0 ); + gHUD.DrawHudString( PING_RANGE_MAX + xpos_rel - 35, ypos, ScreenWidth, "latency", 255, 140, 0 ); + + list_slot += 1.2; + ypos = ROW_RANGE_MIN + (list_slot * ROW_GAP); + xpos = NAME_RANGE_MIN + xpos_rel; + FillRGBA( xpos - 5, ypos, PING_RANGE_MAX - 5, 1, 255, 140, 0, 255); // draw the seperator line + + list_slot += 0.8; + + if ( !gHUD.m_Teamplay ) + { + // it's not teamplay, so just draw a simple player list + DrawPlayers( xpos_rel, list_slot ); + return 1; + } + + // clear out team scores + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + if ( !m_TeamInfo[i].scores_overriden ) + m_TeamInfo[i].frags = m_TeamInfo[i].deaths = 0; + m_TeamInfo[i].ping = m_TeamInfo[i].packetloss = 0; + } + + // recalc the team scores, then draw them + for ( i = 1; i < MAX_PLAYERS; i++ ) + { + if ( m_PlayerInfoList[i].name == NULL ) + continue; // empty player slot, skip + + if ( m_PlayerExtraInfo[i].teamname[0] == 0 ) + continue; // skip over players who are not in a team + + // find what team this player is in + for ( int j = 1; j <= m_iNumTeams; j++ ) + { + if ( !stricmp( m_PlayerExtraInfo[i].teamname, m_TeamInfo[j].name ) ) + break; + } + if ( j > m_iNumTeams ) // player is not in a team, skip to the next guy + continue; + + if ( !m_TeamInfo[j].scores_overriden ) + { + m_TeamInfo[j].frags += m_PlayerExtraInfo[i].frags; + m_TeamInfo[j].deaths += m_PlayerExtraInfo[i].deaths; + } + + m_TeamInfo[j].ping += m_PlayerInfoList[i].ping; + m_TeamInfo[j].packetloss += m_PlayerInfoList[i].packetloss; + + if ( m_PlayerInfoList[i].thisplayer ) + m_TeamInfo[j].ownteam = TRUE; + else + m_TeamInfo[j].ownteam = FALSE; + } + + // find team ping/packetloss averages + for ( i = 1; i <= m_iNumTeams; i++ ) + { + m_TeamInfo[i].already_drawn = FALSE; + + if ( m_TeamInfo[i].players > 0 ) + { + m_TeamInfo[i].ping /= m_TeamInfo[i].players; // use the average ping of all the players in the team as the teams ping + m_TeamInfo[i].packetloss /= m_TeamInfo[i].players; // use the average ping of all the players in the team as the teams ping + } + } + + // Draw the teams + while ( 1 ) + { + int highest_frags = -99999; int lowest_deaths = 99999; + int best_team = 0; + + for ( i = 1; i <= m_iNumTeams; i++ ) + { + if ( m_TeamInfo[i].players < 0 ) + continue; + + if ( !m_TeamInfo[i].already_drawn && m_TeamInfo[i].frags >= highest_frags ) + { + if ( m_TeamInfo[i].frags > highest_frags || m_TeamInfo[i].deaths < lowest_deaths ) + { + best_team = i; + lowest_deaths = m_TeamInfo[i].deaths; + highest_frags = m_TeamInfo[i].frags; + } + } + } + + // draw the best team on the scoreboard + if ( !best_team ) + break; + + // draw out the best team + team_info_t *team_info = &m_TeamInfo[best_team]; + + ypos = ROW_RANGE_MIN + (list_slot * ROW_GAP); + + // check we haven't drawn too far down + if ( ypos > ROW_RANGE_MAX ) // don't draw to close to the lower border + break; + + xpos = NAME_RANGE_MIN + xpos_rel; + int r = 255, g = 225, b = 55; // draw the stuff kinda yellowish + + if ( team_info->ownteam ) // if it is their team, draw the background different color + { + // overlay the background in blue, then draw the score text over it + FillRGBA( NAME_RANGE_MIN + xpos_rel - 5, ypos, PING_RANGE_MAX - 5, ROW_GAP, 0, 0, 255, 70 ); + } + + // draw their name (left to right) + gHUD.DrawHudString( xpos, ypos, NAME_RANGE_MAX + xpos_rel, team_info->name, r, g, b ); + + // draw kills (right to left) + xpos = KILLS_RANGE_MAX + xpos_rel; + gHUD.DrawHudNumberString( xpos, ypos, KILLS_RANGE_MIN + xpos_rel, team_info->frags, r, g, b ); + + // draw divider + xpos = DIVIDER_POS + xpos_rel; + gHUD.DrawHudString( xpos, ypos, xpos + 20, "/", r, g, b ); + + // draw deaths + xpos = DEATHS_RANGE_MAX + xpos_rel; + gHUD.DrawHudNumberString( xpos, ypos, DEATHS_RANGE_MIN + xpos_rel, team_info->deaths, r, g, b ); + + // draw ping + // draw ping & packetloss + static char buf[64]; + sprintf( buf, "%d", team_info->ping ); + xpos = ((PING_RANGE_MAX - PING_RANGE_MIN) / 2) + PING_RANGE_MIN + xpos_rel + 25; + UnpackRGB( r, g, b, RGB_YELLOWISH ); + gHUD.DrawHudStringReverse( xpos, ypos, xpos - 50, buf, r, g, b ); + + /* Packetloss removed on Kelly 'shipping nazi' Bailey's orders + sprintf( buf, " %d", team_info->packetloss ); + gHUD.DrawHudString( xpos, ypos, xpos+50, buf, r, g, b ); + */ + + team_info->already_drawn = TRUE; // set the already_drawn to be TRUE, so this team won't get drawn again + list_slot++; + + // draw all the players that belong to this team, indented slightly + list_slot = DrawPlayers( xpos_rel, list_slot, 10, team_info->name ); + } + + // draw all the players who are not in a team + list_slot += 0.5; + DrawPlayers( xpos_rel, list_slot, 0, "" ); + + return 1; +} + +// returns the ypos where it finishes drawing +int CHudScoreboard :: DrawPlayers( int xpos_rel, float list_slot, int nameoffset, char *team ) +{ + // draw the players, in order, and restricted to team if set + while ( 1 ) + { + // Find the top ranking player + int highest_frags = -99999; int lowest_deaths = 99999; + int best_player = 0; + + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + if ( m_PlayerInfoList[i].name && m_PlayerExtraInfo[i].frags >= highest_frags ) + { + if ( !(team && stricmp(m_PlayerExtraInfo[i].teamname, team)) ) // make sure it is the specified team + { + extra_player_info_t *pl_info = &m_PlayerExtraInfo[i]; + if ( pl_info->frags > highest_frags || pl_info->deaths < lowest_deaths ) + { + best_player = i; + lowest_deaths = pl_info->deaths; + highest_frags = pl_info->frags; + + } + } + } + } + + if ( !best_player ) + break; + + // draw out the best player + hud_player_info_t *pl_info = &m_PlayerInfoList[best_player]; + + int ypos = ROW_RANGE_MIN + (list_slot * ROW_GAP); + + // check we haven't drawn too far down + if ( ypos > ROW_RANGE_MAX ) // don't draw to close to the lower border + break; + + int xpos = NAME_RANGE_MIN + xpos_rel; + int r = 255, g = 255, b = 255; + if ( best_player == m_iLastKilledBy && m_fLastKillTime && m_fLastKillTime > gHUD.m_flTime ) + { + if ( pl_info->thisplayer ) + { // green is the suicide color? i wish this could do grey... + FillRGBA( NAME_RANGE_MIN + xpos_rel - 5, ypos, PING_RANGE_MAX - 5, ROW_GAP, 80, 155, 0, 70 ); + } + else + { // Highlight the killers name - overlay the background in red, then draw the score text over it + FillRGBA( NAME_RANGE_MIN + xpos_rel - 5, ypos, PING_RANGE_MAX - 5, ROW_GAP, 255, 0, 0, ((float)15 * (float)(m_fLastKillTime - gHUD.m_flTime)) ); + } + } + else if ( pl_info->thisplayer ) // if it is their name, draw it a different color + { + // overlay the background in blue, then draw the score text over it + FillRGBA( NAME_RANGE_MIN + xpos_rel - 5, ypos, PING_RANGE_MAX - 5, ROW_GAP, 0, 0, 255, 70 ); + } + + // draw their name (left to right) + gHUD.DrawHudString( xpos + nameoffset, ypos, NAME_RANGE_MAX + xpos_rel, pl_info->name, r, g, b ); + + // draw kills (right to left) + xpos = KILLS_RANGE_MAX + xpos_rel; + gHUD.DrawHudNumberString( xpos, ypos, KILLS_RANGE_MIN + xpos_rel, m_PlayerExtraInfo[best_player].frags, r, g, b ); + + // draw divider + xpos = DIVIDER_POS + xpos_rel; + gHUD.DrawHudString( xpos, ypos, xpos + 20, "/", r, g, b ); + + // draw deaths + xpos = DEATHS_RANGE_MAX + xpos_rel; + gHUD.DrawHudNumberString( xpos, ypos, DEATHS_RANGE_MIN + xpos_rel, m_PlayerExtraInfo[best_player].deaths, r, g, b ); + + // draw ping & packetloss + static char buf[64]; + sprintf( buf, "%d", m_PlayerInfoList[best_player].ping ); + xpos = ((PING_RANGE_MAX - PING_RANGE_MIN) / 2) + PING_RANGE_MIN + xpos_rel + 25; + gHUD.DrawHudStringReverse( xpos, ypos, xpos - 50, buf, r, g, b ); + + /* Packetloss removed on Kelly 'shipping nazi' Bailey's orders + if ( m_PlayerInfoList[best_player].packetloss >= 63 ) + { + UnpackRGB( r, g, b, RGB_REDISH ); + sprintf( buf, " !!!!" ); + } + else + { + sprintf( buf, " %d", m_PlayerInfoList[best_player].packetloss ); + } + + gHUD.DrawHudString( xpos, ypos, xpos+50, buf, r, g, b ); + */ + + pl_info->name = NULL; // set the name to be NULL, so this client won't get drawn again + list_slot++; + } + + return list_slot; +} + + +void CHudScoreboard :: GetAllPlayersInfo( void ) +{ + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + GetPlayerInfo( i, &m_PlayerInfoList[i] ); + + if ( m_PlayerInfoList[i].thisplayer ) + m_iPlayerNum = i; // !!!HACK: this should be initialized elsewhere... maybe gotten from the engine + } +} + +int CHudScoreboard :: MsgFunc_ScoreInfo( const char *pszName, int iSize, void *pbuf ) +{ + m_iFlags |= HUD_ACTIVE; + + BEGIN_READ( pbuf, iSize ); + short cl = READ_BYTE(); + short frags = READ_SHORT(); + short deaths = READ_SHORT(); + + if ( cl > 0 && cl <= MAX_PLAYERS ) + { + m_PlayerExtraInfo[cl].frags = frags; + m_PlayerExtraInfo[cl].deaths = deaths; + } + + return 1; +} + +// Message handler for TeamInfo message +// accepts two values: +// byte: client number +// string: client team name +int CHudScoreboard :: MsgFunc_TeamInfo( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + short cl = READ_BYTE(); + + if ( cl > 0 && cl <= MAX_PLAYERS ) + { // set the players team + strncpy( m_PlayerExtraInfo[cl].teamname, READ_STRING(), MAX_TEAM_NAME ); + } + + // rebuild the list of teams + + // clear out player counts from teams + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + m_TeamInfo[i].players = 0; + } + + // rebuild the team list + GetAllPlayersInfo(); + m_iNumTeams = 0; + for ( i = 1; i < MAX_PLAYERS; i++ ) + { + if ( m_PlayerInfoList[i].name == NULL ) + continue; + + if ( m_PlayerExtraInfo[i].teamname[0] == 0 ) + continue; // skip over players who are not in a team + + // is this player in an existing team? + for ( int j = 1; j <= m_iNumTeams; j++ ) + { + if ( m_TeamInfo[j].name[0] == '\0' ) + break; + + if ( !stricmp( m_PlayerExtraInfo[i].teamname, m_TeamInfo[j].name ) ) + break; + } + + if ( j > m_iNumTeams ) + { // they aren't in a listed team, so make a new one + // search through for an empty team slot + for ( int j = 1; j <= m_iNumTeams; j++ ) + { + if ( m_TeamInfo[j].name[0] == '\0' ) + break; + } + m_iNumTeams = max( j, m_iNumTeams ); + + strncpy( m_TeamInfo[j].name, m_PlayerExtraInfo[i].teamname, MAX_TEAM_NAME ); + m_TeamInfo[j].players = 0; + } + + m_TeamInfo[j].players++; + } + + // clear out any empty teams + for ( i = 1; i <= m_iNumTeams; i++ ) + { + if ( m_TeamInfo[i].players < 1 ) + memset( &m_TeamInfo[i], 0, sizeof(team_info_t) ); + } + + return 1; +} + +// Message handler for TeamScore message +// accepts three values: +// string: team name +// short: teams kills +// short: teams deaths +// if this message is never received, then scores will simply be the combined totals of the players. +int CHudScoreboard :: MsgFunc_TeamScore( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + char *TeamName = READ_STRING(); + + // find the team matching the name + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + if ( !stricmp( TeamName, m_TeamInfo[i].name ) ) + break; + } + if ( i > m_iNumTeams ) + return 1; + + // use this new score data instead of combined player scores + m_TeamInfo[i].scores_overriden = TRUE; + m_TeamInfo[i].frags = READ_SHORT(); + m_TeamInfo[i].deaths = READ_SHORT(); + + return 1; +} + +void CHudScoreboard :: DeathMsg( int killer, int victim ) +{ + // if we were the one killed, or the world killed us, set the scoreboard to indicate suicide + if ( victim == m_iPlayerNum || killer == 0 ) + { + m_iLastKilledBy = killer ? killer : m_iPlayerNum; + m_fLastKillTime = gHUD.m_flTime + 10; // display who we were killed by for 10 seconds + + if ( killer == m_iPlayerNum ) + m_iLastKilledBy = m_iPlayerNum; + } +} + + + +void CHudScoreboard :: UserCmd_ShowScores( void ) +{ + m_iShowscoresHeld = TRUE; +} + +void CHudScoreboard :: UserCmd_HideScores( void ) +{ + m_iShowscoresHeld = FALSE; +} diff --git a/dmc/cl_dll/status_icons.cpp b/dmc/cl_dll/status_icons.cpp new file mode 100644 index 0000000..8733dcc --- /dev/null +++ b/dmc/cl_dll/status_icons.cpp @@ -0,0 +1,150 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// status_icons.cpp +// +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE( m_StatusIcons, StatusIcon ); + +int CHudStatusIcons::Init( void ) +{ + HOOK_MESSAGE( StatusIcon ); + + gHUD.AddHudElem( this ); + + Reset(); + + return 1; +} + +int CHudStatusIcons::VidInit( void ) +{ + + return 1; +} + +void CHudStatusIcons::Reset( void ) +{ + memset( m_IconList, 0, sizeof m_IconList ); + m_iFlags &= ~HUD_ACTIVE; +} + +// Draw status icons along the left-hand side of the screen +int CHudStatusIcons::Draw( float flTime ) +{ + // find starting position to draw from, along right-hand side of screen + int x = 5; + int y = ScreenHeight / 2; + + // loop through icon list, and draw any valid icons drawing up from the middle of screen + for ( int i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( m_IconList[i].spr ) + { + y -= ( m_IconList[i].rc.bottom - m_IconList[i].rc.top ) + 5; + + SPR_Set( m_IconList[i].spr, m_IconList[i].r, m_IconList[i].g, m_IconList[i].b ); + SPR_DrawAdditive( 0, x, y, &m_IconList[i].rc ); + } + } + + return 1; +} + +// Message handler for StatusIcon message +// accepts five values: +// byte : TRUE = ENABLE icon, FALSE = DISABLE icon +// string : the sprite name to display +// byte : red +// byte : green +// byte : blue +int CHudStatusIcons::MsgFunc_StatusIcon( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int ShouldEnable = READ_BYTE(); + char *pszIconName = READ_STRING(); + if ( ShouldEnable ) + { + int r = READ_BYTE(); + int g = READ_BYTE(); + int b = READ_BYTE(); + EnableIcon( pszIconName, r, g, b ); + m_iFlags |= HUD_ACTIVE; + } + else + { + DisableIcon( pszIconName ); + } + + return 1; +} + +// add the icon to the icon list, and set it's drawing color +void CHudStatusIcons::EnableIcon( char *pszIconName, unsigned char red, unsigned char green, unsigned char blue ) +{ + // check to see if the sprite is in the current list + int i; + for ( i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( !stricmp( m_IconList[i].szSpriteName, pszIconName ) ) + break; + } + + if ( i == MAX_ICONSPRITES ) + { + // icon not in list, so find an empty slot to add to + for ( i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( !m_IconList[i].spr ) + break; + } + } + + // if we've run out of space in the list, overwrite the first icon + if ( i == MAX_ICONSPRITES ) + { + i = 0; + } + + // Load the sprite and add it to the list + // the sprite must be listed in hud.txt + int spr_index = gHUD.GetSpriteIndex( pszIconName ); + m_IconList[i].spr = gHUD.GetSprite( spr_index ); + m_IconList[i].rc = gHUD.GetSpriteRect( spr_index ); + m_IconList[i].r = red; + m_IconList[i].g = green; + m_IconList[i].b = blue; + strcpy( m_IconList[i].szSpriteName, pszIconName ); +} + +void CHudStatusIcons::DisableIcon( char *pszIconName ) +{ + // find the sprite is in the current list + for ( int i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( !stricmp( m_IconList[i].szSpriteName, pszIconName ) ) + { + // clear the item from the list + memset( &m_IconList[i], 0, sizeof(icon_sprite_t) ); + return; + } + } +} diff --git a/dmc/cl_dll/statusbar.cpp b/dmc/cl_dll/statusbar.cpp new file mode 100644 index 0000000..43ef061 --- /dev/null +++ b/dmc/cl_dll/statusbar.cpp @@ -0,0 +1,262 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// statusbar.cpp +// +// generic text status bar, set by game dll +// runs across bottom of screen +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +DECLARE_MESSAGE( m_StatusBar, StatusText ); +DECLARE_MESSAGE( m_StatusBar, StatusValue ); + +#define STATUSBAR_ID_LINE 1 + +extern int GetTeamIndex( int clientIndex ); +int g_iNameColors; + +int CHudStatusBar :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( StatusText ); + HOOK_MESSAGE( StatusValue ); + + Reset(); + + return 1; +} + +int CHudStatusBar :: VidInit( void ) +{ + // Load sprites here + m_iArmorSpriteIndex = gHUD.GetSpriteIndex( "armor_bar" ); + m_hArmor = gHUD.GetSprite( m_iArmorSpriteIndex ); + + m_iHealthSpriteIndex = gHUD.GetSpriteIndex( "health_bar" ); + m_hHealth = gHUD.GetSprite( m_iHealthSpriteIndex ); + + return 1; +} + +void CHudStatusBar :: Reset( void ) +{ + m_iFlags &= ~HUD_ACTIVE; // start out inactive + for ( int i = 0; i < MAX_STATUSBAR_LINES; i++ ) + m_szStatusText[i][0] = 0; + memset( m_iStatusValues, 0, sizeof m_iStatusValues ); + + m_iStatusValues[0] = 1; // 0 is the special index, which always returns true +} + +void CHudStatusBar :: ParseStatusString( int line_num ) +{ + int indexval; + + indexval = m_iStatusValues[ 1 ]; + + GetPlayerInfo( indexval, &g_PlayerInfoList[indexval] ); + + if ( g_PlayerInfoList[indexval].name != NULL ) + { + strncpy( m_szName[line_num], g_PlayerInfoList[indexval].name, MAX_PLAYER_NAME_LENGTH ); + } + else + { + strncpy( m_szName[line_num], "******", MAX_PLAYER_NAME_LENGTH ); + } + + g_iNameColors = GetTeamIndex( indexval ); + + indexval = m_iStatusValues[ 2 ]; + sprintf( m_szHealth[ line_num ], ":%d", indexval ); + + indexval = m_iStatusValues[ 3 ]; + sprintf( m_szArmor[ line_num ], ":%d", indexval ); + + m_iTeamMate[ line_num ] = m_iStatusValues[ 5 ]; +} + +int CHudStatusBar :: Draw( float fTime ) +{ + int r , g, b, a, name_r, name_g, name_b; + + if ( m_bReparseString ) + { + for ( int i = 0; i < MAX_STATUSBAR_LINES; i++ ) + ParseStatusString( i ); + m_bReparseString = FALSE; + } + + //Not Watching anyone + if ( m_iStatusValues[ 1 ] == 0 ) + { + m_iFlags &= ~HUD_ACTIVE; + return 1; + } + + // Draw the status bar lines + for ( int i = 0; i < MAX_STATUSBAR_LINES; i++ ) + { + int TextHeight = 0; + int TotalTextWidth = 0; + + //Ugly way to get + if ( m_iTeamMate[i] ) + { + TotalTextWidth += gHUD.ReturnStringPixelLength ( m_szName[i] ); + TotalTextWidth += gHUD.ReturnStringPixelLength ( m_szHealth[i] ); + TotalTextWidth += gHUD.ReturnStringPixelLength ( m_szArmor[i] ); + TotalTextWidth += 48; + TextHeight = gHUD.m_scrinfo.iCharHeight; + } + else + TotalTextWidth += gHUD.ReturnStringPixelLength ( m_szName[i] ); + + TextHeight = gHUD.m_scrinfo.iCharHeight; + + if ( g_iNameColors == 1 ) + { + name_r = 255; + name_g = 50; + name_b = 50; + } + else if ( g_iNameColors == 2 ) + { + name_r = 50; + name_g = 50; + name_b = 255; + } + else + name_r = name_g = name_b = 255; + + int Y_START; + if ( ScreenHeight >= 480 ) + Y_START = ScreenHeight - 55; + else + Y_START = ScreenHeight - 45; + + int x = gHUD.m_Ammo.m_iNumberXPosition; + int y = Y_START; // = ( ScreenHeight / 2 ) + ( TextHeight * 3 ); + + int x_offset; + a = 200; + + UnpackRGB( r, g, b, RGB_NORMAL); + ScaleColors( r, g, b, a ); + ScaleColors( name_r, name_g, name_b, 125 ); + + //Draw the name First + gHUD.DrawHudString( x, y, 1024, m_szName[i], name_r, name_g, name_b ); + + if ( !m_iTeamMate[i] ) + continue; + + //Get the length in pixels for the name + x_offset = gHUD.ReturnStringPixelLength ( m_szName[i] ); + + //Add the offset + x += ( x_offset + 8 ); + + //Now draw the Sprite for the health + SPR_Set( m_hHealth, r, g, b ); + SPR_DrawHoles( 0, x , y, &gHUD.GetSpriteRect( m_iHealthSpriteIndex ) ); + + //Add the sprite width size + x += 16; + + //Draw the health value ( x + offset for the name lenght + width of the sprite ) + gHUD.DrawHudString( x, y, 1024, m_szHealth[i], name_r, name_g, name_b ); + + //Get the length in pixels for the health + x_offset = gHUD.ReturnStringPixelLength ( m_szHealth[i] ); + + //Add the offset + x += ( x_offset + 8 ); + + //Now draw the Sprite for the Armor + SPR_Set( m_hArmor, r, g, b ); + SPR_DrawHoles( 0, x, y, &gHUD.GetSpriteRect( m_iArmorSpriteIndex ) ); + + x += 16; + + //Draw the armor value ( x + offset for the name lenght + width of the sprite ) + gHUD.DrawHudString( x, y, 1024, m_szArmor[i], name_r, name_g, name_b ); + } + + return 1; +} + +// Message handler for StatusText message +// accepts two values: +// byte: line number of status bar text +// string: status bar text +// this string describes how the status bar should be drawn +// a semi-regular expression: +// ( slotnum ([a..z] [%pX] [%iX])*)* +// where slotnum is an index into the Value table (see below) +// if slotnum is 0, the string is always drawn +// if StatusValue[slotnum] != 0, the following string is drawn, upto the next newline - otherwise the text is skipped upto next newline +// %pX, where X is an integer, will substitute a player name here, getting the player index from StatusValue[X] +// %iX, where X is an integer, will substitute a number here, getting the number from StatusValue[X] +int CHudStatusBar :: MsgFunc_StatusText( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int line = READ_BYTE(); + + if ( line < 0 || line >= MAX_STATUSBAR_LINES ) + return 1; + + strncpy( m_szStatusText[line], READ_STRING(), MAX_STATUSTEXT_LENGTH ); + m_szStatusText[line][MAX_STATUSTEXT_LENGTH-1] = 0; // ensure it's null terminated ( strncpy() won't null terminate if read string too long) + + if ( m_szStatusText[0] == 0 ) + m_iFlags &= ~HUD_ACTIVE; + else + m_iFlags |= HUD_ACTIVE; // we have status text, so turn on the status bar + + m_bReparseString = TRUE; + + return 1; +} + +// Message handler for StatusText message +// accepts two values: +// byte: index into the status value array +// short: value to store +int CHudStatusBar :: MsgFunc_StatusValue( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int index = READ_BYTE(); + if ( index < 1 || index >= MAX_STATUSBAR_VALUES ) + return 1; // index out of range + + m_iStatusValues[index] = READ_SHORT(); + + m_iFlags |= HUD_ACTIVE; + + m_bReparseString = TRUE; + + return 1; +} \ No newline at end of file diff --git a/dmc/cl_dll/studio_util.cpp b/dmc/cl_dll/studio_util.cpp new file mode 100644 index 0000000..df5fc4b --- /dev/null +++ b/dmc/cl_dll/studio_util.cpp @@ -0,0 +1,251 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include "studio_util.h" + +/* +==================== +AngleMatrix + +==================== +*/ +void AngleMatrix (const float *angles, float (*matrix)[4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + // matrix = (YAW * PITCH) * ROLL + matrix[0][0] = cp*cy; + matrix[1][0] = cp*sy; + matrix[2][0] = -sp; + matrix[0][1] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[2][1] = sr*cp; + matrix[0][2] = (cr*sp*cy+-sr*-sy); + matrix[1][2] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + +/* +==================== +VectorCompare + +==================== +*/ +int VectorCompare (const float *v1, const float *v2) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (v1[i] != v2[i]) + return 0; + + return 1; +} + +/* +==================== +CrossProduct + +==================== +*/ +void CrossProduct (const float *v1, const float *v2, float *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]; +} + +/* +==================== +VectorTransform + +==================== +*/ +void VectorTransform (const float *in1, float in2[3][4], float *out) +{ + out[0] = DotProduct(in1, in2[0]) + in2[0][3]; + out[1] = DotProduct(in1, in2[1]) + in2[1][3]; + out[2] = DotProduct(in1, in2[2]) + in2[2][3]; +} + +/* +================ +ConcatTransforms + +================ +*/ +void ConcatTransforms (float in1[3][4], 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]; +} + +// angles index are not the same as ROLL, PITCH, YAW + +/* +==================== +AngleQuaternion + +==================== +*/ +void AngleQuaternion( float *angles, vec4_t quaternion ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + // FIXME: rescale the inputs to 1/2 angle + angle = angles[2] * 0.5; + sy = sin(angle); + cy = cos(angle); + angle = angles[1] * 0.5; + sp = sin(angle); + cp = cos(angle); + angle = angles[0] * 0.5; + sr = sin(angle); + cr = cos(angle); + + quaternion[0] = sr*cp*cy-cr*sp*sy; // X + quaternion[1] = cr*sp*cy+sr*cp*sy; // Y + quaternion[2] = cr*cp*sy-sr*sp*cy; // Z + quaternion[3] = cr*cp*cy+sr*sp*sy; // W +} + +/* +==================== +QuaternionSlerp + +==================== +*/ +void QuaternionSlerp( vec4_t p, vec4_t q, float t, vec4_t qt ) +{ + int i; + float omega, cosom, sinom, sclp, sclq; + + // decide if one of the quaternions is backwards + float a = 0; + float b = 0; + + for (i = 0; i < 4; i++) + { + a += (p[i]-q[i])*(p[i]-q[i]); + b += (p[i]+q[i])*(p[i]+q[i]); + } + if (a > b) + { + for (i = 0; i < 4; i++) + { + q[i] = -q[i]; + } + } + + cosom = p[0]*q[0] + p[1]*q[1] + p[2]*q[2] + p[3]*q[3]; + + if ((1.0 + cosom) > 0.000001) + { + if ((1.0 - cosom) > 0.000001) + { + omega = acos( cosom ); + sinom = sin( omega ); + sclp = sin( (1.0 - t)*omega) / sinom; + sclq = sin( t*omega ) / sinom; + } + else + { + sclp = 1.0 - t; + sclq = t; + } + for (i = 0; i < 4; i++) { + qt[i] = sclp * p[i] + sclq * q[i]; + } + } + else + { + qt[0] = -q[1]; + qt[1] = q[0]; + qt[2] = -q[3]; + qt[3] = q[2]; + sclp = sin( (1.0 - t) * (0.5 * M_PI)); + sclq = sin( t * (0.5 * M_PI)); + for (i = 0; i < 3; i++) + { + qt[i] = sclp * p[i] + sclq * qt[i]; + } + } +} + +/* +==================== +QuaternionMatrix + +==================== +*/ +void QuaternionMatrix( vec4_t quaternion, float (*matrix)[4] ) +{ + matrix[0][0] = 1.0 - 2.0 * quaternion[1] * quaternion[1] - 2.0 * quaternion[2] * quaternion[2]; + matrix[1][0] = 2.0 * quaternion[0] * quaternion[1] + 2.0 * quaternion[3] * quaternion[2]; + matrix[2][0] = 2.0 * quaternion[0] * quaternion[2] - 2.0 * quaternion[3] * quaternion[1]; + + matrix[0][1] = 2.0 * quaternion[0] * quaternion[1] - 2.0 * quaternion[3] * quaternion[2]; + matrix[1][1] = 1.0 - 2.0 * quaternion[0] * quaternion[0] - 2.0 * quaternion[2] * quaternion[2]; + matrix[2][1] = 2.0 * quaternion[1] * quaternion[2] + 2.0 * quaternion[3] * quaternion[0]; + + matrix[0][2] = 2.0 * quaternion[0] * quaternion[2] + 2.0 * quaternion[3] * quaternion[1]; + matrix[1][2] = 2.0 * quaternion[1] * quaternion[2] - 2.0 * quaternion[3] * quaternion[0]; + matrix[2][2] = 1.0 - 2.0 * quaternion[0] * quaternion[0] - 2.0 * quaternion[1] * quaternion[1]; +} + +/* +==================== +MatrixCopy + +==================== +*/ +void MatrixCopy( float in[3][4], float out[3][4] ) +{ + memcpy( out, in, sizeof( float ) * 3 * 4 ); +} \ No newline at end of file diff --git a/dmc/cl_dll/studio_util.h b/dmc/cl_dll/studio_util.h new file mode 100644 index 0000000..aa8dcf6 --- /dev/null +++ b/dmc/cl_dll/studio_util.h @@ -0,0 +1,40 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( STUDIO_UTIL_H ) +#define STUDIO_UTIL_H +#if defined( WIN32 ) +#pragma once +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +#ifndef PITCH +// MOVEMENT INFO +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 +#endif + +#define FDotProduct( a, b ) (fabs((a[0])*(b[0])) + fabs((a[1])*(b[1])) + fabs((a[2])*(b[2]))) + +void AngleMatrix (const float *angles, float (*matrix)[4] ); +int VectorCompare (const float *v1, const float *v2); +void CrossProduct (const float *v1, const float *v2, float *cross); +void VectorTransform (const float *in1, float in2[3][4], float *out); +void ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]); +void MatrixCopy( float in[3][4], float out[3][4] ); +void QuaternionMatrix( vec4_t quaternion, float (*matrix)[4] ); +void QuaternionSlerp( vec4_t p, vec4_t q, float t, vec4_t qt ); +void AngleQuaternion( float *angles, vec4_t quaternion ); + +#endif // STUDIO_UTIL_H \ No newline at end of file diff --git a/dmc/cl_dll/text_message.cpp b/dmc/cl_dll/text_message.cpp new file mode 100644 index 0000000..721c245 --- /dev/null +++ b/dmc/cl_dll/text_message.cpp @@ -0,0 +1,208 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// text_message.cpp +// +// implementation of CHudTextMessage class +// +// this class routes messages through titles.txt for localisation +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE( m_TextMessage, TextMsg ); + +int CHudTextMessage::Init(void) +{ + HOOK_MESSAGE( TextMsg ); + + gHUD.AddHudElem( this ); + + Reset(); + + return 1; +}; + +// Searches through the string for any msg names (indicated by a '#') +// any found are looked up in titles.txt and the new message substituted +// the new value is pushed into dst_buffer +char *CHudTextMessage::LocaliseTextString( const char *msg, char *dst_buffer, int buffer_size ) +{ + char *dst = dst_buffer; + for ( char *src = (char*)msg; *src != 0 && buffer_size > 0; buffer_size-- ) + { + if ( *src == '#' ) + { + // cut msg name out of string + static char word_buf[255]; + char *wdst = word_buf, *word_start = src; + for ( ++src ; (*src >= 'A' && *src <= 'z') || (*src >= '0' && *src <= '9'); wdst++, src++ ) + { + *wdst = *src; + } + *wdst = 0; + + // lookup msg name in titles.txt + client_textmessage_t *clmsg = TextMessageGet( word_buf ); + if ( !clmsg || !(clmsg->pMessage) ) + { + src = word_start; + *dst = *src; + dst++, src++; + continue; + } + + // copy string into message over the msg name + for ( char *wsrc = (char*)clmsg->pMessage; *wsrc != 0; wsrc++, dst++ ) + { + *dst = *wsrc; + } + *dst = 0; + } + else + { + *dst = *src; + dst++, src++; + *dst = 0; + } + } + + dst_buffer[buffer_size-1] = 0; // ensure null termination + return dst_buffer; +} + +// As above, but with a local static buffer +char *CHudTextMessage::BufferedLocaliseTextString( const char *msg ) +{ + static char dst_buffer[1024]; + return LocaliseTextString( msg, dst_buffer, 1024 ); +} + +// Simplified version of LocaliseTextString; assumes string is only one word +char *CHudTextMessage::LookupString( const char *msg, int *msg_dest ) +{ + if ( !msg ) + return ""; + + // '#' character indicates this is a reference to a string in titles.txt, and not the string itself + if ( msg[0] == '#' ) + { + // this is a message name, so look up the real message + client_textmessage_t *clmsg = TextMessageGet( msg+1 ); + + if ( !clmsg || !(clmsg->pMessage) ) + return (char*)msg; // lookup failed, so return the original string + + if ( msg_dest ) + { + // check to see if titles.txt info overrides msg destination + // if clmsg->effect is less than 0, then clmsg->effect holds -1 * message_destination + if ( clmsg->effect < 0 ) // + *msg_dest = -clmsg->effect; + } + + return (char*)clmsg->pMessage; + } + else + { // nothing special about this message, so just return the same string + return (char*)msg; + } +} + +void StripEndNewlineFromString( char *str ) +{ + int s = strlen( str ) - 1; + if ( str[s] == '\n' || str[s] == '\r' ) + str[s] = 0; +} + +// converts all '\r' characters to '\n', so that the engine can deal with the properly +// returns a pointer to str +char* ConvertCRtoNL( char *str ) +{ + for ( char *ch = str; *ch != 0; ch++ ) + if ( *ch == '\r' ) + *ch = '\n'; + return str; +} + +// Message handler for text messages +// displays a string, looking them up from the titles.txt file, which can be localised +// parameters: +// byte: message direction ( HUD_PRINTCONSOLE, HUD_PRINTNOTIFY, HUD_PRINTCENTER, HUD_PRINTTALK ) +// string: message +// optional parameters: +// string: message parameter 1 +// string: message parameter 2 +// string: message parameter 3 +// string: message parameter 4 +// any string that starts with the character '#' is a message name, and is used to look up the real message in titles.txt +// the next (optional) one to four strings are parameters for that string (which can also be message names if they begin with '#') +int CHudTextMessage::MsgFunc_TextMsg( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int msg_dest = READ_BYTE(); + +#define MSG_BUF_SIZE 128 + static char szBuf[6][MSG_BUF_SIZE]; + char *msg_text = LookupString( READ_STRING(), &msg_dest ); + msg_text = safe_strcpy( szBuf[0], msg_text, MSG_BUF_SIZE ); + + // keep reading strings and using C format strings for subsituting the strings into the localised text string + char *sstr1 = LookupString( READ_STRING() ); + sstr1 = safe_strcpy( szBuf[1], sstr1 , MSG_BUF_SIZE ); + StripEndNewlineFromString( sstr1 ); // these strings are meant for subsitution into the main strings, so cull the automatic end newlines + char *sstr2 = LookupString( READ_STRING() ); + sstr2 = safe_strcpy( szBuf[2], sstr2 , MSG_BUF_SIZE ); + StripEndNewlineFromString( sstr2 ); + char *sstr3 = LookupString( READ_STRING() ); + sstr3 = safe_strcpy( szBuf[3], sstr3 , MSG_BUF_SIZE ); + StripEndNewlineFromString( sstr3 ); + char *sstr4 = LookupString( READ_STRING() ); + sstr4 = safe_strcpy( szBuf[4], sstr4 , MSG_BUF_SIZE ); + StripEndNewlineFromString( sstr4 ); + char *psz = szBuf[5]; + + switch ( msg_dest ) + { + case HUD_PRINTCENTER: + safe_sprintf( psz, MSG_BUF_SIZE, msg_text, sstr1, sstr2, sstr3, sstr4 ); + CenterPrint( ConvertCRtoNL( psz ) ); + break; + + case HUD_PRINTNOTIFY: + psz[0] = 1; // mark this message to go into the notify buffer + safe_sprintf( psz+1, MSG_BUF_SIZE, msg_text, sstr1, sstr2, sstr3, sstr4 ); + ConsolePrint( ConvertCRtoNL( psz ) ); + break; + + case HUD_PRINTTALK: + safe_sprintf( psz, MSG_BUF_SIZE, msg_text, sstr1, sstr2, sstr3, sstr4 ); + gHUD.m_SayText.SayTextPrint( ConvertCRtoNL( psz ), 128 ); + break; + + case HUD_PRINTCONSOLE: + safe_sprintf( psz, MSG_BUF_SIZE, msg_text, sstr1, sstr2, sstr3, sstr4 ); + ConsolePrint( ConvertCRtoNL( psz ) ); + break; + } + + return 1; +} diff --git a/dmc/cl_dll/train.cpp b/dmc/cl_dll/train.cpp new file mode 100644 index 0000000..0286c68 --- /dev/null +++ b/dmc/cl_dll/train.cpp @@ -0,0 +1,85 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Train.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE(m_Train, Train ) + + +int CHudTrain::Init(void) +{ + HOOK_MESSAGE( Train ); + + m_iPos = 0; + m_iFlags = 0; + gHUD.AddHudElem(this); + + return 1; +}; + +int CHudTrain::VidInit(void) +{ + m_hSprite = 0; + + return 1; +}; + +int CHudTrain::Draw(float fTime) +{ + if ( !m_hSprite ) + m_hSprite = LoadSprite("sprites/%d_train.spr"); + + if (m_iPos) + { + int r, g, b, x, y; + + UnpackRGB(r,g,b, RGB_YELLOWISH); + SPR_Set(m_hSprite, r, g, b ); + + // This should show up to the right and part way up the armor number + y = ScreenHeight - SPR_Height(m_hSprite,0) - gHUD.m_iFontHeight; + x = ScreenWidth/3 + SPR_Width(m_hSprite,0)/4; + + SPR_DrawAdditive( m_iPos - 1, x, y, NULL); + + } + + return 1; +} + + +int CHudTrain::MsgFunc_Train(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + // update Train data + m_iPos = READ_BYTE(); + + if (m_iPos) + m_iFlags |= HUD_ACTIVE; + else + m_iFlags &= ~HUD_ACTIVE; + + return 1; +} diff --git a/dmc/cl_dll/tri.cpp b/dmc/cl_dll/tri.cpp new file mode 100644 index 0000000..beff737 --- /dev/null +++ b/dmc/cl_dll/tri.cpp @@ -0,0 +1,129 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// Triangle rendering, if any + +#include "hud.h" +#include "cl_util.h" + +// Triangle rendering apis are in gEngfuncs.pTriAPI + +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "triangleapi.h" + +extern "C" +{ + void EXPORT HUD_DrawNormalTriangles( void ); + void EXPORT HUD_DrawTransparentTriangles( void ); +}; + +extern float g_iFogColor[3]; +extern float g_iStartDist; +extern float g_iEndDist; +extern int g_iWaterLevel; + +//#define TEST_IT +#if defined( TEST_IT ) + +/* +================= +Draw_Triangles + +Example routine. Draws a sprite offset from the player origin. +================= +*/ +void Draw_Triangles( void ) +{ + cl_entity_t *player; + vec3_t org; + + // Load it up with some bogus data + player = gEngfuncs.GetLocalPlayer(); + if ( !player ) + return; + + org = player->origin; + + org.x += 50; + org.y += 50; + + if (gHUD.m_hsprCursor == 0) + { + char sz[256]; + sprintf( sz, "sprites/cursor.spr" ); + gHUD.m_hsprCursor = SPR_Load( sz ); + } + + if ( !gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *)gEngfuncs.GetSpritePointer( gHUD.m_hsprCursor ), 0 )) + { + return; + } + + // Create a triangle, sigh + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); + gEngfuncs.pTriAPI->Begin( TRI_QUADS ); + // Overload p->color with index into tracer palette, p->packedColor with brightness + gEngfuncs.pTriAPI->Color4f( 1.0, 1.0, 1.0, 1.0 ); + // UNDONE: This gouraud shading causes tracers to disappear on some cards (permedia2) + gEngfuncs.pTriAPI->Brightness( 1 ); + gEngfuncs.pTriAPI->TexCoord2f( 0, 0 ); + gEngfuncs.pTriAPI->Vertex3f( org.x, org.y, org.z ); + + gEngfuncs.pTriAPI->Brightness( 1 ); + gEngfuncs.pTriAPI->TexCoord2f( 0, 1 ); + gEngfuncs.pTriAPI->Vertex3f( org.x, org.y + 50, org.z ); + + gEngfuncs.pTriAPI->Brightness( 1 ); + gEngfuncs.pTriAPI->TexCoord2f( 1, 1 ); + gEngfuncs.pTriAPI->Vertex3f( org.x + 50, org.y + 50, org.z ); + + gEngfuncs.pTriAPI->Brightness( 1 ); + gEngfuncs.pTriAPI->TexCoord2f( 1, 0 ); + gEngfuncs.pTriAPI->Vertex3f( org.x + 50, org.y, org.z ); + + gEngfuncs.pTriAPI->End(); + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); +} + +#endif + +//Render fog ( duh ). +void RenderFog ( void ) +{ + //Not in water and we want fog. + bool bFog = g_iWaterLevel < 2 && g_iStartDist >= 0 && g_iEndDist >= 0; + gEngfuncs.pTriAPI->Fog ( g_iFogColor, g_iStartDist, g_iEndDist, bFog ); +} + + +/* +================= +HUD_DrawNormalTriangles + +Non-transparent triangles-- add them here +================= +*/ +void EXPORT HUD_DrawNormalTriangles( void ) +{ + gHUD.m_Spectator.DrawOverview(); +} + +/* +================= +HUD_DrawTransparentTriangles + +Render any triangles with transparent rendermode needs here +================= +*/ +void EXPORT HUD_DrawTransparentTriangles( void ) +{ + + RenderFog(); +} diff --git a/dmc/cl_dll/util.cpp b/dmc/cl_dll/util.cpp new file mode 100644 index 0000000..cf88cd0 --- /dev/null +++ b/dmc/cl_dll/util.cpp @@ -0,0 +1,134 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// util.cpp +// +// implementation of class-less helper functions +// + +#include "stdio.h" +#include "stdlib.h" +#include "math.h" + +#include "hud.h" +#include "cl_util.h" +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +vec3_t vec3_origin( 0, 0, 0 ); + +double sqrt(double x); + +float Length(const float *v) +{ + int i; + float length; + + length = 0; + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); // FIXME + + return length; +} + +void VectorAngles( const float *forward, float *angles ) +{ + float tmp, yaw, pitch; + + if (forward[1] == 0 && forward[0] == 0) + { + yaw = 0; + if (forward[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + yaw = (atan2(forward[1], forward[0]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + + tmp = sqrt (forward[0]*forward[0] + forward[1]*forward[1]); + pitch = (atan2(forward[2], tmp) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + angles[0] = pitch; + angles[1] = yaw; + angles[2] = 0; +} + +float VectorNormalize (float *v) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; + +} + +void VectorInverse ( float *v ) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void VectorScale (const float *in, float scale, float *out) +{ + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; +} + +void VectorMA (const float *veca, float scale, const float *vecb, float *vecc) +{ + vecc[0] = veca[0] + scale*vecb[0]; + vecc[1] = veca[1] + scale*vecb[1]; + vecc[2] = veca[2] + scale*vecb[2]; +} + + +HSPRITE LoadSprite(const char *pszName) +{ + int i; + char sz[256]; + + if (ScreenWidth < 640) + i = 320; + else + i = 640; + + sprintf(sz, pszName, i); + + return SPR_Load(sz); +} + diff --git a/dmc/cl_dll/util.h b/dmc/cl_dll/util.h new file mode 100644 index 0000000..2a7ff20 --- /dev/null +++ b/dmc/cl_dll/util.h @@ -0,0 +1,152 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// util.h +// + +#include "cvardef.h" + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +// Macros to hook function calls into the HUD object +#define HOOK_MESSAGE(x) gEngfuncs.pfnHookUserMsg(#x, __MsgFunc_##x ); + +#define DECLARE_MESSAGE(y, x) int __MsgFunc_##x(const char *pszName, int iSize, void *pbuf) \ + { \ + return gHUD.##y.MsgFunc_##x(pszName, iSize, pbuf ); \ + } + + +#define HOOK_COMMAND(x, y) gEngfuncs.pfnAddCommand( x, __CmdFunc_##y ); +#define DECLARE_COMMAND(y, x) void __CmdFunc_##x( void ) \ + { \ + gHUD.##y.UserCmd_##x( ); \ + } + +inline float CVAR_GET_FLOAT( const char *x ) { return gEngfuncs.pfnGetCvarFloat( (char*)x ); } +inline char* CVAR_GET_STRING( const char *x ) { return gEngfuncs.pfnGetCvarString( (char*)x ); } +inline struct cvar_s *CVAR_CREATE( const char *cv, const char *val, const int flags ) { return gEngfuncs.pfnRegisterVariable( (char*)cv, (char*)val, flags ); } + +#define SPR_Load (*gEngfuncs.pfnSPR_Load) +#define SPR_Set (*gEngfuncs.pfnSPR_Set) +#define SPR_Frames (*gEngfuncs.pfnSPR_Frames) +#define SPR_GetList (*gEngfuncs.pfnSPR_GetList) + +// SPR_Draw draws a the current sprite as solid +#define SPR_Draw (*gEngfuncs.pfnSPR_Draw) +// SPR_DrawHoles draws the current sprites, with color index255 not drawn (transparent) +#define SPR_DrawHoles (*gEngfuncs.pfnSPR_DrawHoles) +// SPR_DrawAdditive adds the sprites RGB values to the background (additive transulency) +#define SPR_DrawAdditive (*gEngfuncs.pfnSPR_DrawAdditive) + +// SPR_EnableScissor sets a clipping rect for HUD sprites. (0,0) is the top-left hand corner of the screen. +#define SPR_EnableScissor (*gEngfuncs.pfnSPR_EnableScissor) +// SPR_DisableScissor disables the clipping rect +#define SPR_DisableScissor (*gEngfuncs.pfnSPR_DisableScissor) +// +#define FillRGBA (*gEngfuncs.pfnFillRGBA) + + +// ScreenHeight returns the height of the screen, in pixels +#define ScreenHeight (gHUD.m_scrinfo.iHeight) +// ScreenWidth returns the width of the screen, in pixels +#define ScreenWidth (gHUD.m_scrinfo.iWidth) + +#define GetScreenInfo (*gEngfuncs.pfnGetScreenInfo) +#define ServerCmd (*gEngfuncs.pfnServerCmd) +#define ClientCmd (*gEngfuncs.pfnClientCmd) +#define SetCrosshair (*gEngfuncs.pfnSetCrosshair) +#define AngleVectors (*gEngfuncs.pfnAngleVectors) + + +// Gets the height & width of a sprite, at the specified frame +inline int SPR_Height( HSPRITE x, int f ) { return gEngfuncs.pfnSPR_Height(x, f); } +inline int SPR_Width( HSPRITE x, int f ) { return gEngfuncs.pfnSPR_Width(x, f); } + +inline client_textmessage_t *TextMessageGet( const char *pName ) { return gEngfuncs.pfnTextMessageGet( pName ); } +inline int TextMessageDrawChar( int x, int y, int number, int r, int g, int b ) +{ + return gEngfuncs.pfnDrawCharacter( x, y, number, r, g, b ); +} + +inline int DrawConsoleString( int x, int y, const char *string ) +{ + return gEngfuncs.pfnDrawConsoleString( x, y, (char*) string ); +} + +inline void GetConsoleStringSize( const char *string, int *width, int *height ) +{ + gEngfuncs.pfnDrawConsoleStringLen( string, width, height ); +} + +inline int ConsoleStringLen( const char *string ) +{ + int _width, _height; + GetConsoleStringSize( string, &_width, &_height ); + return _width; +} + +inline void ConsolePrint( const char *string ) +{ + gEngfuncs.pfnConsolePrint( string ); +} + +inline void CenterPrint( const char *string ) +{ + gEngfuncs.pfnCenterPrint( string ); +} + +// returns the players name of entity no. +#define GetPlayerInfo (*gEngfuncs.pfnGetPlayerInfo) + +// sound functions +inline void PlaySound( char *szSound, float vol ) { gEngfuncs.pfnPlaySoundByName( szSound, vol ); } +inline void PlaySound( int iSound, float vol ) { gEngfuncs.pfnPlaySoundByIndex( iSound, vol ); } + +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#define fabs(x) ((x) > 0 ? (x) : 0 - (x)) + +void ScaleColors( int &r, int &g, int &b, int a ); + +#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 VectorClear(a) { a[0]=0.0;a[1]=0.0;a[2]=0.0;} +float Length(const float *v); +void VectorMA (const float *veca, float scale, const float *vecb, float *vecc); +void VectorScale (const float *in, float scale, float *out); +float VectorNormalize (float *v); +void VectorInverse ( float *v ); + +extern vec3_t vec3_origin; + +// disable 'possible loss of data converting float to int' warning message +#pragma warning( disable: 4244 ) +// disable 'truncation from 'const double' to 'float' warning message +#pragma warning( disable: 4305 ) + +inline void UnpackRGB(int &r, int &g, int &b, unsigned long ulRGB)\ +{\ + r = (ulRGB & 0xFF0000) >>16;\ + g = (ulRGB & 0xFF00) >> 8;\ + b = ulRGB & 0xFF;\ +} + +HSPRITE LoadSprite(const char *pszName); diff --git a/dmc/cl_dll/util_vector.h b/dmc/cl_dll/util_vector.h new file mode 100644 index 0000000..fdac17c --- /dev/null +++ b/dmc/cl_dll/util_vector.h @@ -0,0 +1,121 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Vector.h +// A subset of the extdll.h in the project HL Entity DLL +// + +// Misc C-runtime library headers +#include "stdio.h" +#include "stdlib.h" +#include "math.h" + +// Header file containing definition of globalvars_t and entvars_t +typedef unsigned int func_t; // +typedef unsigned int string_t; // from engine's pr_comp.h; +typedef float vec_t; // needed before including progdefs.h + +//========================================================= +// 2DVector - used for many pathfinding and many other +// operations that are treated as planar rather than 3d. +//========================================================= +class Vector2D +{ +public: + inline Vector2D(void) { } + inline Vector2D(float X, float Y) { x = X; y = Y; } + inline Vector2D operator+(const Vector2D& v) const { return Vector2D(x+v.x, y+v.y); } + inline Vector2D operator-(const Vector2D& v) const { return Vector2D(x-v.x, y-v.y); } + inline Vector2D operator*(float fl) const { return Vector2D(x*fl, y*fl); } + inline Vector2D operator/(float fl) const { return Vector2D(x/fl, y/fl); } + + inline float Length(void) const { return (float)sqrt(x*x + y*y ); } + + inline Vector2D Normalize ( void ) const + { + Vector2D vec2; + + float flLen = Length(); + if ( flLen == 0 ) + { + return Vector2D( (float)0, (float)0 ); + } + else + { + flLen = 1 / flLen; + return Vector2D( x * flLen, y * flLen ); + } + } + + vec_t x, y; +}; + +inline float DotProduct(const Vector2D& a, const Vector2D& b) { return( a.x*b.x + a.y*b.y ); } +inline Vector2D operator*(float fl, const Vector2D& v) { return v * fl; } + +//========================================================= +// 3D Vector +//========================================================= +class Vector // same data-layout as engine's vec3_t, +{ // which is a vec_t[3] +public: + // Construction/destruction + inline Vector(void) { } + inline Vector(float X, float Y, float Z) { x = X; y = Y; z = Z; } + inline Vector(double X, double Y, double Z) { x = (float)X; y = (float)Y; z = (float)Z; } + inline Vector(int X, int Y, int Z) { x = (float)X; y = (float)Y; z = (float)Z; } + inline Vector(const Vector& v) { x = v.x; y = v.y; z = v.z; } + inline Vector(float rgfl[3]) { x = rgfl[0]; y = rgfl[1]; z = rgfl[2]; } + + // Operators + inline Vector operator-(void) const { return Vector(-x,-y,-z); } + inline int operator==(const Vector& v) const { return x==v.x && y==v.y && z==v.z; } + inline int operator!=(const Vector& v) const { return !(*this==v); } + inline Vector operator+(const Vector& v) const { return Vector(x+v.x, y+v.y, z+v.z); } + inline Vector operator-(const Vector& v) const { return Vector(x-v.x, y-v.y, z-v.z); } + inline Vector operator*(float fl) const { return Vector(x*fl, y*fl, z*fl); } + inline Vector operator/(float fl) const { return Vector(x/fl, y/fl, z/fl); } + + // Methods + inline void CopyToArray(float* rgfl) const { rgfl[0] = x, rgfl[1] = y, rgfl[2] = z; } + inline float Length(void) const { return (float)sqrt(x*x + y*y + z*z); } + operator float *() { return &x; } // Vectors will now automatically convert to float * when needed + operator const float *() const { return &x; } // Vectors will now automatically convert to float * when needed + inline Vector Normalize(void) const + { + float flLen = Length(); + if (flLen == 0) return Vector(0,0,1); // ???? + flLen = 1 / flLen; + return Vector(x * flLen, y * flLen, z * flLen); + } + + inline Vector2D Make2D ( void ) const + { + Vector2D Vec2; + + Vec2.x = x; + Vec2.y = y; + + return Vec2; + } + inline float Length2D(void) const { return (float)sqrt(x*x + y*y); } + + // Members + vec_t x, y, z; +}; +inline Vector operator*(float fl, const Vector& v) { return v * fl; } +inline float DotProduct(const Vector& a, const Vector& b) { return(a.x*b.x+a.y*b.y+a.z*b.z); } +inline Vector CrossProduct(const Vector& a, const Vector& b) { return Vector( a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x ); } + +#define vec3_t Vector diff --git a/dmc/cl_dll/vgui_ControlConfigPanel.h b/dmc/cl_dll/vgui_ControlConfigPanel.h new file mode 100644 index 0000000..4c413a0 --- /dev/null +++ b/dmc/cl_dll/vgui_ControlConfigPanel.h @@ -0,0 +1,47 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef CONTROLCONFIGPANEL_H +#define CONTROLCONFIGPANEL_H + +#include +#include + +namespace vgui +{ +class HeaderPanel; +class TablePanel; +class ScrollPanel; +class InputStream; +class Label; +} + +class ControlConfigPanel : public vgui::Panel +{ +private: + vgui::HeaderPanel* _headerPanel; + vgui::TablePanel* _tablePanel; + vgui::ScrollPanel* _scrollPanel; + vgui::Dar _cvarDar; + vgui::Dar _descDar; + vgui::Label* _actionLabel; + vgui::Label* _keyButtonLabel; + vgui::Label* _alternateLabel; +public: + ControlConfigPanel(int x,int y,int wide,int tall); +public: + void AddCVar(const char* cvar,const char* desc); + void AddCVarFromInputStream(vgui::InputStream* is); + int GetCVarCount(); + void GetCVar(int index,char* cvar,int cvarLen,char* desc,int descLen); + void GetCVarBind(const char* cvar,char* bind,int bindLen,char* bindAlt,int bindAltLen); + void SetCVarBind(const char* cvar,const char* bind,const char* bindAlt); +}; + + + +#endif \ No newline at end of file diff --git a/dmc/cl_dll/vgui_CustomObjects.cpp b/dmc/cl_dll/vgui_CustomObjects.cpp new file mode 100644 index 0000000..9a6beb1 --- /dev/null +++ b/dmc/cl_dll/vgui_CustomObjects.cpp @@ -0,0 +1,428 @@ +//=========== (C) Copyright 1996-2002 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: Contains implementation of various VGUI-derived objects +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "VGUI_Font.h" + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "parsemsg.h" + +#include "vgui_int.h" +#include "vgui_viewport.h" +#include "vgui_ServerBrowser.h" +#include "VGUI_BitmapTGA.h" + + +// Arrow filenames +char *sArrowFilenames[] = +{ + "arrowup", + "arrowdn", + "arrowlt", + "arrowrt", +}; + + +//----------------------------------------------------------------------------- +// Purpose: Loads a .tga file and returns a pointer to the VGUI tga object +//----------------------------------------------------------------------------- +BitmapTGA *LoadTGA( const char* pImageName ) +{ + BitmapTGA *pTGA; + + char sz[256]; + sprintf(sz, "%%d_%s", pImageName); + + // Load the Image + FileInputStream* fis = new FileInputStream( GetVGUITGAName(sz), false ); + pTGA = new BitmapTGA(fis,true); + fis->close(); + + return pTGA; +} + +//=========================================================== +// All TFC Hud buttons are derived from this one. +CommandButton::CommandButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight) : Button("",x,y,wide,tall) +{ + m_iPlayerClass = 0; + m_bNoHighlight = bNoHighlight; + Init(); + setText( text ); +} + +CommandButton::CommandButton( int iPlayerClass, const char* text,int x,int y,int wide,int tall) : Button("",x,y,wide,tall) +{ + m_iPlayerClass = iPlayerClass; + m_bNoHighlight = false; + Init(); + setText( text ); +} + +void CommandButton::Init( void ) +{ + m_pSubMenu = NULL; + m_pSubLabel = NULL; + m_pParentMenu = NULL; + + // Set text color to orange + setFgColor(Scheme::sc_primary1); + + // left align + setContentAlignment( vgui::Label::a_west ); + + // Add the Highlight signal + if (!m_bNoHighlight) + addInputSignal( new CHandler_CommandButtonHighlight(this) ); + + // not bound to any button yet + m_cBoundKey = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Prepends the button text with the current bound key +// if no bound key, then a clear space ' ' instead +//----------------------------------------------------------------------------- +void CommandButton::RecalculateText( void ) +{ + char szBuf[128]; + + if ( m_cBoundKey != 0 ) + { + if ( m_cBoundKey == (char)255 ) + { + strcpy( szBuf, m_sMainText ); + } + else + { + sprintf( szBuf, " %c %s", m_cBoundKey, m_sMainText ); + } + szBuf[MAX_BUTTON_SIZE-1] = 0; + } + else + { + // just draw a space if no key bound + sprintf( szBuf, " %s", m_sMainText ); + szBuf[MAX_BUTTON_SIZE-1] = 0; + } + + Button::setText( szBuf ); +} + +void CommandButton::setText( const char *text ) +{ + strncpy( m_sMainText, text, MAX_BUTTON_SIZE ); + m_sMainText[MAX_BUTTON_SIZE-1] = 0; + + RecalculateText(); +} + +void CommandButton::setBoundKey( char boundKey ) +{ + m_cBoundKey = boundKey; + RecalculateText(); +} + +char CommandButton::getBoundKey( void ) +{ + return m_cBoundKey; +} + +void CommandButton::AddSubMenu( CCommandMenu *pNewMenu ) +{ + m_pSubMenu = pNewMenu; + + // Prevent this button from being pushed + setMouseClickEnabled( MOUSE_LEFT, false ); +} + +void CommandButton::UpdateSubMenus( int iAdjustment ) +{ + if ( m_pSubMenu ) + m_pSubMenu->RecalculatePositions( iAdjustment ); +} + +void CommandButton::paint() +{ + // Make the sub label paint the same as the button + if ( m_pSubLabel ) + { + if ( isSelected() ) + m_pSubLabel->PushDown(); + else + m_pSubLabel->PushUp(); + } + + // draw armed button text in white + if ( isArmed() ) + { + setFgColor( Scheme::sc_secondary2 ); + } + else + { + setFgColor( Scheme::sc_primary1 ); + } + + Button::paint(); +} + +void CommandButton::paintBackground() +{ + if ( isArmed() ) + { + // Orange highlight background + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect(0,0,_size[0],_size[1]); + } + + // Orange Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect(0,0,_size[0],_size[1]); +} + +//----------------------------------------------------------------------------- +// Purpose: Highlights the current button, and all it's parent menus +//----------------------------------------------------------------------------- +void CommandButton::cursorEntered( void ) +{ + // unarm all the other buttons in this menu + CCommandMenu *containingMenu = getParentMenu(); + if ( containingMenu ) + { + containingMenu->ClearButtonsOfArmedState(); + + // make all our higher buttons armed + CCommandMenu *pCParent = containingMenu->GetParentMenu(); + if ( pCParent ) + { + CommandButton *pParentButton = pCParent->FindButtonWithSubmenu( containingMenu ); + + pParentButton->cursorEntered(); + } + } + + // arm ourselves + setArmed( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CommandButton::cursorExited( void ) +{ + // only clear ourselves if we have do not have a containing menu + // only stay armed if we have a sub menu + // the buttons only unarm themselves when another button is armed instead + if ( !getParentMenu() || !GetSubMenu() ) + { + setArmed( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the command menu that the button is part of, if any +// Output : CCommandMenu * +//----------------------------------------------------------------------------- +CCommandMenu *CommandButton::getParentMenu( void ) +{ + return m_pParentMenu; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the menu that contains this button +// Input : *pParentMenu - +//----------------------------------------------------------------------------- +void CommandButton::setParentMenu( CCommandMenu *pParentMenu ) +{ + m_pParentMenu = pParentMenu; +} + + +//=========================================================== +int ClassButton::IsNotValid() +{ + return false; +} + +//=========================================================== +// Button with Class image beneath it +CImageLabel::CImageLabel( const char* pImageName,int x,int y ) : Label( "", x,y ) +{ + setContentFitted(true); + m_pTGA = LoadTGA(pImageName); + setImage( m_pTGA ); +} + +CImageLabel::CImageLabel( const char* pImageName,int x,int y,int wide,int tall ) : Label( "", x,y,wide,tall ) +{ + setContentFitted(true); + m_pTGA = LoadTGA(pImageName); + setImage( m_pTGA ); +} + +//=========================================================== +// Image size +int CImageLabel::getImageWide( void ) +{ + int iXSize, iYSize; + m_pTGA->getSize( iXSize, iYSize ); + return iXSize; +} + +int CImageLabel::getImageTall( void ) +{ + int iXSize, iYSize; + m_pTGA->getSize( iXSize, iYSize ); + return iYSize; +} + +//=========================================================== +// Various overloaded paint functions for Custom VGUI objects +void CCommandMenu::paintBackground() +{ + // Transparent black background + drawSetColor(Scheme::sc_primary3); + drawFilledRect(0,0,_size[0],_size[1]); +} + +//================================================================================= +// CUSTOM SCROLLPANEL +//================================================================================= +CTFScrollButton::CTFScrollButton(int iArrow, const char* text,int x,int y,int wide,int tall) : CommandButton(text,x,y,wide,tall) +{ + // Set text color to orange + setFgColor(Scheme::sc_primary1); + + // Load in the arrow + m_pTGA = LoadTGA( sArrowFilenames[iArrow] ); + setImage( m_pTGA ); + + // Highlight signal + InputSignal *pISignal = new CHandler_CommandButtonHighlight(this); + addInputSignal(pISignal); +} + +void CTFScrollButton::paint( void ) +{ + // draw armed button text in white + if ( isArmed() ) + { + m_pTGA->setColor( Color(255,255,255, 0) ); + } + else + { + m_pTGA->setColor( Color(255,255,255, 128) ); + } + + m_pTGA->doPaint(this); +} + +void CTFScrollButton::paintBackground( void ) +{ +/* + if ( isArmed() ) + { + // Orange highlight background + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect(0,0,_size[0],_size[1]); + } + + // Orange Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect(0,0,_size[0]-1,_size[1]); +*/ +} + +void CTFSlider::paintBackground( void ) +{ + int wide,tall,nobx,noby; + getPaintSize(wide,tall); + getNobPos(nobx,noby); + + // Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect( 0,0,wide,tall ); + + if( isVertical() ) + { + // Nob Fill + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect( 0,nobx,wide,noby ); + + // Nob Outline + drawSetColor( Scheme::sc_primary1 ); + drawOutlinedRect( 0,nobx,wide,noby ); + } + else + { + // Nob Fill + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect( nobx,0,noby,tall ); + + // Nob Outline + drawSetColor( Scheme::sc_primary1 ); + drawOutlinedRect( nobx,0,noby,tall ); + } +} + +CTFScrollPanel::CTFScrollPanel(int x,int y,int wide,int tall) : ScrollPanel(x,y,wide,tall) +{ + ScrollBar *pScrollBar = getVerticalScrollBar(); + pScrollBar->setButton( new CTFScrollButton( ARROW_UP, "", 0,0,16,16 ), 0 ); + pScrollBar->setButton( new CTFScrollButton( ARROW_DOWN, "", 0,0,16,16 ), 1 ); + pScrollBar->setSlider( new CTFSlider(0,wide-1,wide,(tall-(wide*2))+2,true) ); + pScrollBar->setPaintBorderEnabled(false); + pScrollBar->setPaintBackgroundEnabled(false); + pScrollBar->setPaintEnabled(false); + + pScrollBar = getHorizontalScrollBar(); + pScrollBar->setButton( new CTFScrollButton( ARROW_LEFT, "", 0,0,16,16 ), 0 ); + pScrollBar->setButton( new CTFScrollButton( ARROW_RIGHT, "", 0,0,16,16 ), 1 ); + pScrollBar->setSlider( new CTFSlider(tall,0,wide-(tall*2),tall,false) ); + pScrollBar->setPaintBorderEnabled(false); + pScrollBar->setPaintBackgroundEnabled(false); + pScrollBar->setPaintEnabled(false); +} + + +//================================================================================= +// CUSTOM HANDLERS +//================================================================================= +void CHandler_MenuButtonOver::cursorEntered(Panel *panel) +{ + if ( gViewPort && m_pMenuPanel ) + { + m_pMenuPanel->SetActiveInfo( m_iButton ); + } +} + +void CMenuHandler_StringCommandClassSelect::actionPerformed(Panel* panel) +{ + CMenuHandler_StringCommand::actionPerformed( panel ); + + bool bAutoKill = CVAR_GET_FLOAT( "hud_classautokill" ) != 0; + +} + diff --git a/dmc/cl_dll/vgui_MOTDWindow.cpp b/dmc/cl_dll/vgui_MOTDWindow.cpp new file mode 100644 index 0000000..66d09b4 --- /dev/null +++ b/dmc/cl_dll/vgui_MOTDWindow.cpp @@ -0,0 +1,153 @@ +//=========== (C) Copyright 1996-2002 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "VGUI_Font.h" +#include "VGUI_ScrollPanel.h" +#include "VGUI_TextImage.h" + +#include + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "const.h" + +#include "vgui_int.h" +#include "vgui_viewport.h" +#include "vgui_ServerBrowser.h" + +#define MOTD_TITLE_X XRES(16) +#define MOTD_TITLE_Y YRES(16) + +#define MOTD_WINDOW_X XRES(112) +#define MOTD_WINDOW_Y YRES(80) +#define MOTD_WINDOW_SIZE_X XRES(424) +#define MOTD_WINDOW_SIZE_Y YRES(312) + +//----------------------------------------------------------------------------- +// Purpose: Displays the MOTD and basic server information +//----------------------------------------------------------------------------- +class CMessageWindowPanel : public CMenuPanel +{ +public: + CMessageWindowPanel( const char *szMOTD, const char *szTitle, int iShadeFullScreen, int iRemoveMe, int x, int y, int wide, int tall ); + +private: + CTransparentPanel *m_pBackgroundPanel; + +}; + +//----------------------------------------------------------------------------- +// Purpose: Creates a new CMessageWindowPanel +// Output : CMenuPanel - interface to the panel +//----------------------------------------------------------------------------- +CMenuPanel *CMessageWindowPanel_Create( const char *szMOTD, const char *szTitle, int iShadeFullscreen, int iRemoveMe, int x, int y, int wide, int tall ) +{ + return new CMessageWindowPanel( szMOTD, szTitle, iShadeFullscreen, iRemoveMe, x, y, wide, tall ); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructs a message panel +//----------------------------------------------------------------------------- +CMessageWindowPanel::CMessageWindowPanel( const char *szMOTD, const char *szTitle, int iShadeFullscreen, int iRemoveMe, int x, int y, int wide, int tall ) : CMenuPanel( iShadeFullscreen ? 100 : 255, iRemoveMe, x, y, wide, tall ) +{ + // Get the scheme used for the Titles + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + + // schemes + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle( "Title Font" ); + SchemeHandle_t hMOTDText = pSchemes->getSchemeHandle( "Briefing Text" ); + + // color schemes + int r, g, b, a; + + // Create the window + m_pBackgroundPanel = new CTransparentPanel( iShadeFullscreen ? 255 : 100, MOTD_WINDOW_X, MOTD_WINDOW_Y, MOTD_WINDOW_SIZE_X, MOTD_WINDOW_SIZE_Y ); + m_pBackgroundPanel->setParent( this ); + m_pBackgroundPanel->setBorder( new LineBorder( Color(255 * 0.7,170 * 0.7,0,0)) ); + m_pBackgroundPanel->setVisible( true ); + + int iXSize,iYSize,iXPos,iYPos; + m_pBackgroundPanel->getPos( iXPos,iYPos ); + m_pBackgroundPanel->getSize( iXSize,iYSize ); + + // Create the title + Label *pLabel = new Label( "", iXPos + MOTD_TITLE_X, iYPos + MOTD_TITLE_Y ); + pLabel->setParent( this ); + pLabel->setFont( pSchemes->getFont(hTitleScheme) ); + pLabel->setFont( Scheme::sf_primary1 ); + + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + pLabel->setFgColor( r, g, b, a ); + pLabel->setFgColor( Scheme::sc_primary1 ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + pLabel->setBgColor( r, g, b, a ); + pLabel->setContentAlignment( vgui::Label::a_west ); + pLabel->setText(szTitle); + + // Create the Scroll panel + ScrollPanel *pScrollPanel = new CTFScrollPanel( iXPos + XRES(16), iYPos + MOTD_TITLE_Y*2 + YRES(16), iXSize - XRES(32), iYSize - (YRES(48) + BUTTON_SIZE_Y*2) ); + pScrollPanel->setParent(this); + //force the scrollbars on so clientClip will take them in account after the validate + pScrollPanel->setScrollBarAutoVisible(false, false); + pScrollPanel->setScrollBarVisible(true, true); + pScrollPanel->validate(); + + // Create the text panel + TextPanel *pText = new TextPanel( "", 0,0, 64,64); + pText->setParent( pScrollPanel->getClient() ); + + // get the font and colors from the scheme + pText->setFont( pSchemes->getFont(hMOTDText) ); + pSchemes->getFgColor( hMOTDText, r, g, b, a ); + pText->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hMOTDText, r, g, b, a ); + pText->setBgColor( r, g, b, a ); + pText->setText(szMOTD); + + // Get the total size of the MOTD text and resize the text panel + int iScrollSizeX, iScrollSizeY; + + // First, set the size so that the client's wdith is correct at least because the + // width is critical for getting the "wrapped" size right. + // You'll see a horizontal scroll bar if there is a single word that won't wrap in the + // specified width. + pText->getTextImage()->setSize(pScrollPanel->getClientClip()->getWide(), pScrollPanel->getClientClip()->getTall()); + pText->getTextImage()->getTextSizeWrapped( iScrollSizeX, iScrollSizeY ); + + // Now resize the textpanel to fit the scrolled size + pText->setSize( iScrollSizeX , iScrollSizeY ); + + //turn the scrollbars back into automode + pScrollPanel->setScrollBarAutoVisible(true, true); + pScrollPanel->setScrollBarVisible(false, false); + + pScrollPanel->validate(); + + CommandButton *pButton = new CommandButton( CHudTextMessage::BufferedLocaliseTextString( "#Menu_OK" ), iXPos + XRES(16), iYPos + iYSize - YRES(16) - BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_TextWindow(HIDE_TEXTWINDOW)); + pButton->setParent(this); + +} + + + + + + diff --git a/dmc/cl_dll/vgui_SchemeManager.cpp b/dmc/cl_dll/vgui_SchemeManager.cpp new file mode 100644 index 0000000..dddd268 --- /dev/null +++ b/dmc/cl_dll/vgui_SchemeManager.cpp @@ -0,0 +1,556 @@ +//=========== (C) Copyright 1996-2002 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "hud.h" +#include "vgui_SchemeManager.h" +#include "cvardef.h" + +#include + + +cvar_t *g_CV_BitmapFonts; + + +void Scheme_Init() +{ + g_CV_BitmapFonts = gEngfuncs.pfnRegisterVariable("bitmapfonts", "1", 0); +} + + + +//----------------------------------------------------------------------------- +// Purpose: Scheme managers data container +//----------------------------------------------------------------------------- +class CSchemeManager::CScheme +{ +public: + enum { + SCHEME_NAME_LENGTH = 32, + FONT_NAME_LENGTH = 48, + FONT_FILENAME_LENGTH = 64, + }; + + // name + char schemeName[SCHEME_NAME_LENGTH]; + + // font + char fontName[FONT_NAME_LENGTH]; + + int fontSize; + int fontWeight; + + vgui::Font *font; + int ownFontPointer; // true if the font is ours to delete + + // scheme + byte fgColor[4]; + byte bgColor[4]; + byte armedFgColor[4]; + byte armedBgColor[4]; + byte mousedownFgColor[4]; + byte mousedownBgColor[4]; + byte borderColor[4]; + + // construction/destruction + CScheme(); + ~CScheme(); +}; + +CSchemeManager::CScheme::CScheme() +{ + schemeName[0] = 0; + fontName[0] = 0; + fontSize = 0; + fontWeight = 0; + font = NULL; + ownFontPointer = false; +} + +CSchemeManager::CScheme::~CScheme() +{ + // only delete our font pointer if we own it + if ( ownFontPointer ) + { + delete font; + } +} + +//----------------------------------------------------------------------------- +// Purpose: resolution information +// !! needs to be shared out +//----------------------------------------------------------------------------- +static int g_ResArray[] = +{ + 320, + 400, + 512, + 640, + 800, + 1024, + 1152, + 1280, + 1600 +}; +static int g_NumReses = sizeof(g_ResArray) / sizeof(int); + +static byte *LoadFileByResolution( const char *filePrefix, int xRes, const char *filePostfix ) +{ + // find our resolution in the res array + int resNum = g_NumReses - 1; + while ( g_ResArray[resNum] > xRes ) + { + resNum--; + + if ( resNum < 0 ) + return NULL; + } + + // try open the file + byte *pFile = NULL; + while ( 1 ) + { + + // try load + char fname[256]; + sprintf( fname, "%s%d%s", filePrefix, g_ResArray[resNum], filePostfix ); + pFile = gEngfuncs.COM_LoadFile( fname, 5, NULL ); + + if ( pFile ) + break; + + if ( resNum == 0 ) + return NULL; + + resNum--; + }; + + return pFile; +} + +static void ParseRGBAFromString( byte colorArray[4], const char *colorVector ) +{ + int r, g, b, a; + sscanf( colorVector, "%d %d %d %d", &r, &g, &b, &a ); + colorArray[0] = r; + colorArray[1] = g; + colorArray[2] = b; + colorArray[3] = a; +} + +//----------------------------------------------------------------------------- +// Purpose: initializes the scheme manager +// loading the scheme files for the current resolution +// Input : xRes - +// yRes - dimensions of output window +//----------------------------------------------------------------------------- +CSchemeManager::CSchemeManager( int xRes, int yRes ) +{ + // basic setup + m_pSchemeList = NULL; + m_iNumSchemes = 0; + + // find the closest matching scheme file to our resolution + char token[1024]; + char *pFile = (char*)LoadFileByResolution( "", xRes, "_textscheme.txt" ); + m_xRes = xRes; + + char *pFileStart = pFile; + + byte *pFontData; + int fontFileLength; + char fontFilename[512]; + + // + // Read the scheme descriptions from the text file, into a temporary array + // format is simply: + // = + // + // a of "SchemeName" signals a new scheme is being described + // + + const static int numTmpSchemes = 64; + static CScheme tmpSchemes[numTmpSchemes]; + memset( tmpSchemes, 0, sizeof(tmpSchemes) ); + int currentScheme = -1; + CScheme *pScheme = NULL; + + if ( !pFile ) + { + gEngfuncs.Con_DPrintf( "Unable to find *_textscheme.txt\n"); + goto buildDefaultFont; + } + + // record what has been entered so we can create defaults from the different values + bool hasFgColor, hasBgColor, hasArmedFgColor, hasArmedBgColor, hasMouseDownFgColor, hasMouseDownBgColor; + + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + while ( strlen(token) > 0 && (currentScheme < numTmpSchemes) ) + { + // get the paramName name + static const int tokenSize = 64; + char paramName[tokenSize], paramValue[tokenSize]; + + strncpy( paramName, token, tokenSize ); + paramName[tokenSize-1] = 0; // ensure null termination + + // get the '=' character + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + if ( stricmp( token, "=" ) ) + { + if ( currentScheme < 0 ) + { + gEngfuncs.Con_Printf( "error parsing font scheme text file at file start - expected '=', found '%s''\n", token ); + } + else + { + gEngfuncs.Con_Printf( "error parsing font scheme text file at scheme '%s' - expected '=', found '%s''\n", tmpSchemes[currentScheme].schemeName, token ); + } + break; + } + + // get paramValue + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + strncpy( paramValue, token, tokenSize ); + paramValue[tokenSize-1] = 0; // ensure null termination + + // is this a new scheme? + if ( !stricmp(paramName, "SchemeName") ) + { + // setup the defaults for the current scheme + if ( pScheme ) + { + // foreground color defaults (normal -> armed -> mouse down) + if ( !hasFgColor ) + { + pScheme->fgColor[0] = pScheme->fgColor[1] = pScheme->fgColor[2] = pScheme->fgColor[3] = 255; + } + if ( !hasArmedFgColor ) + { + memcpy( pScheme->armedFgColor, pScheme->fgColor, sizeof(pScheme->armedFgColor) ); + } + if ( !hasMouseDownFgColor ) + { + memcpy( pScheme->mousedownFgColor, pScheme->armedFgColor, sizeof(pScheme->mousedownFgColor) ); + } + + // background color (normal -> armed -> mouse down) + if ( !hasBgColor ) + { + pScheme->bgColor[0] = pScheme->bgColor[1] = pScheme->bgColor[2] = pScheme->bgColor[3] = 0; + } + if ( !hasArmedBgColor ) + { + memcpy( pScheme->armedBgColor, pScheme->bgColor, sizeof(pScheme->armedBgColor) ); + } + if ( !hasMouseDownBgColor ) + { + memcpy( pScheme->mousedownBgColor, pScheme->armedBgColor, sizeof(pScheme->mousedownBgColor) ); + } + + // font size + if ( !pScheme->fontSize ) + { + pScheme->fontSize = 17; + } + if ( !pScheme->fontName[0] ) + { + strcpy( pScheme->fontName, "Arial" ); + } + } + + // create the new scheme + currentScheme++; + pScheme = &tmpSchemes[currentScheme]; + hasFgColor = hasBgColor = hasArmedFgColor = hasArmedBgColor = hasMouseDownFgColor = hasMouseDownBgColor = false; + + strncpy( pScheme->schemeName, paramValue, CScheme::SCHEME_NAME_LENGTH ); + pScheme->schemeName[CScheme::SCHEME_NAME_LENGTH-1] = '\0'; // ensure null termination of string + } + + if ( !pScheme ) + { + gEngfuncs.Con_Printf( "font scheme text file MUST start with a 'SchemeName'\n"); + break; + } + + // pull the data out into the scheme + if ( !stricmp(paramName, "FontName") ) + { + strncpy( pScheme->fontName, paramValue, CScheme::FONT_NAME_LENGTH ); + pScheme->fontName[CScheme::FONT_NAME_LENGTH-1] = 0; + } + else if ( !stricmp(paramName, "FontSize") ) + { + pScheme->fontSize = atoi( paramValue ); + } + else if ( !stricmp(paramName, "FontWeight") ) + { + pScheme->fontWeight = atoi( paramValue ); + } + else if ( !stricmp(paramName, "FgColor") ) + { + ParseRGBAFromString( pScheme->fgColor, paramValue ); + hasFgColor = true; + } + else if ( !stricmp(paramName, "BgColor") ) + { + ParseRGBAFromString( pScheme->bgColor, paramValue ); + hasBgColor = true; + } + else if ( !stricmp(paramName, "FgColorArmed") ) + { + ParseRGBAFromString( pScheme->armedFgColor, paramValue ); + hasArmedFgColor = true; + } + else if ( !stricmp(paramName, "BgColorArmed") ) + { + ParseRGBAFromString( pScheme->armedBgColor, paramValue ); + hasArmedBgColor = true; + } + else if ( !stricmp(paramName, "FgColorMousedown") ) + { + ParseRGBAFromString( pScheme->mousedownFgColor, paramValue ); + hasMouseDownFgColor = true; + } + else if ( !stricmp(paramName, "BgColorMousedown") ) + { + ParseRGBAFromString( pScheme->mousedownBgColor, paramValue ); + hasMouseDownBgColor = true; + } + else if ( !stricmp(paramName, "BorderColor") ) + { + ParseRGBAFromString( pScheme->borderColor, paramValue ); + hasMouseDownBgColor = true; + } + + // get the new token last, so we now if the loop needs to be continued or not + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + } + + // free the file + gEngfuncs.COM_FreeFile( pFileStart ); + + +buildDefaultFont: + + // make sure we have at least 1 valid font + if ( currentScheme < 0 ) + { + currentScheme = 0; + strcpy( tmpSchemes[0].schemeName, "Default Scheme" ); + strcpy( tmpSchemes[0].fontName, "Arial" ); + tmpSchemes[0].fontSize = 0; + tmpSchemes[0].fgColor[0] = tmpSchemes[0].fgColor[1] = tmpSchemes[0].fgColor[2] = tmpSchemes[0].fgColor[3] = 255; + tmpSchemes[0].armedFgColor[0] = tmpSchemes[0].armedFgColor[1] = tmpSchemes[0].armedFgColor[2] = tmpSchemes[0].armedFgColor[3] = 255; + tmpSchemes[0].mousedownFgColor[0] = tmpSchemes[0].mousedownFgColor[1] = tmpSchemes[0].mousedownFgColor[2] = tmpSchemes[0].mousedownFgColor[3] = 255; + } + + // we have the full list of schemes in the tmpSchemes array + // now allocate the correct sized list + m_iNumSchemes = currentScheme + 1; // 0-based index + m_pSchemeList = new CScheme[ m_iNumSchemes ]; + + // copy in the data + memcpy( m_pSchemeList, tmpSchemes, sizeof(CScheme) * m_iNumSchemes ); + + // create the fonts + for ( int i = 0; i < m_iNumSchemes; i++ ) + { + m_pSchemeList[i].font = NULL; + + // see if the current font values exist in a previously loaded font + for ( int j = 0; j < i; j++ ) + { + // check if the font name, size, and weight are the same + if ( !stricmp(m_pSchemeList[i].fontName, m_pSchemeList[j].fontName) + && m_pSchemeList[i].fontSize == m_pSchemeList[j].fontSize + && m_pSchemeList[i].fontWeight == m_pSchemeList[j].fontWeight ) + { + // copy the pointer, but mark i as not owning it + m_pSchemeList[i].font = m_pSchemeList[j].font; + m_pSchemeList[i].ownFontPointer = false; + } + } + + // if we haven't found the font already, load it ourselves + if ( !m_pSchemeList[i].font ) + { + fontFileLength = -1; + pFontData = NULL; + + if(g_CV_BitmapFonts && g_CV_BitmapFonts->value) + { + int fontRes = 640; + if ( m_xRes >= 1600 ) + fontRes = 1600; + else if ( m_xRes >= 1280 ) + fontRes = 1280; + else if ( m_xRes >= 1152 ) + fontRes = 1152; + else if ( m_xRes >= 1024 ) + fontRes = 1024; + else if ( m_xRes >= 800 ) + fontRes = 800; + + sprintf(fontFilename, "gfx\\vgui\\fonts\\%d_%s.tga", fontRes, m_pSchemeList[i].schemeName); + pFontData = gEngfuncs.COM_LoadFile( fontFilename, 5, &fontFileLength ); + if(!pFontData) + gEngfuncs.Con_Printf("Missing bitmap font: %s\n", fontFilename); + } + + m_pSchemeList[i].font = new vgui::Font( + m_pSchemeList[i].fontName, + pFontData, + fontFileLength, + m_pSchemeList[i].fontSize, + 0, + 0, + m_pSchemeList[i].fontWeight, + false, + false, + false, + false); + + m_pSchemeList[i].ownFontPointer = true; + } + + // fix up alpha values; VGUI uses 1-A (A=0 being solid, A=255 transparent) + m_pSchemeList[i].fgColor[3] = 255 - m_pSchemeList[i].fgColor[3]; + m_pSchemeList[i].bgColor[3] = 255 - m_pSchemeList[i].bgColor[3]; + m_pSchemeList[i].armedFgColor[3] = 255 - m_pSchemeList[i].armedFgColor[3]; + m_pSchemeList[i].armedBgColor[3] = 255 - m_pSchemeList[i].armedBgColor[3]; + m_pSchemeList[i].mousedownFgColor[3] = 255 - m_pSchemeList[i].mousedownFgColor[3]; + m_pSchemeList[i].mousedownBgColor[3] = 255 - m_pSchemeList[i].mousedownBgColor[3]; + } +} + +//----------------------------------------------------------------------------- +// Purpose: frees all the memory used by the scheme manager +//----------------------------------------------------------------------------- +CSchemeManager::~CSchemeManager() +{ + delete [] m_pSchemeList; + m_iNumSchemes = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Finds a scheme in the list, by name +// Input : char *schemeName - string name of the scheme +// Output : SchemeHandle_t handle to the scheme +//----------------------------------------------------------------------------- +SchemeHandle_t CSchemeManager::getSchemeHandle( const char *schemeName ) +{ + // iterate through the list + for ( int i = 0; i < m_iNumSchemes; i++ ) + { + if ( !stricmp(schemeName, m_pSchemeList[i].schemeName) ) + return i; + } + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: always returns a valid scheme handle +// Input : schemeHandle - +// Output : CScheme +//----------------------------------------------------------------------------- +CSchemeManager::CScheme *CSchemeManager::getSafeScheme( SchemeHandle_t schemeHandle ) +{ + if ( schemeHandle < m_iNumSchemes ) + return m_pSchemeList + schemeHandle; + + return m_pSchemeList; +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns the schemes pointer to a font +// Input : schemeHandle - +// Output : vgui::Font +//----------------------------------------------------------------------------- +vgui::Font *CSchemeManager::getFont( SchemeHandle_t schemeHandle ) +{ + return getSafeScheme( schemeHandle )->font; +} + +void CSchemeManager::getFgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->fgColor[0]; + g = pScheme->fgColor[1]; + b = pScheme->fgColor[2]; + a = pScheme->fgColor[3]; +} + +void CSchemeManager::getBgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->bgColor[0]; + g = pScheme->bgColor[1]; + b = pScheme->bgColor[2]; + a = pScheme->bgColor[3]; +} + +void CSchemeManager::getFgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->armedFgColor[0]; + g = pScheme->armedFgColor[1]; + b = pScheme->armedFgColor[2]; + a = pScheme->armedFgColor[3]; +} + +void CSchemeManager::getBgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->armedBgColor[0]; + g = pScheme->armedBgColor[1]; + b = pScheme->armedBgColor[2]; + a = pScheme->armedBgColor[3]; +} + +void CSchemeManager::getFgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->mousedownFgColor[0]; + g = pScheme->mousedownFgColor[1]; + b = pScheme->mousedownFgColor[2]; + a = pScheme->mousedownFgColor[3]; +} + +void CSchemeManager::getBgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->mousedownBgColor[0]; + g = pScheme->mousedownBgColor[1]; + b = pScheme->mousedownBgColor[2]; + a = pScheme->mousedownBgColor[3]; +} + +void CSchemeManager::getBorderColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->borderColor[0]; + g = pScheme->borderColor[1]; + b = pScheme->borderColor[2]; + a = pScheme->borderColor[3]; +} + + + diff --git a/dmc/cl_dll/vgui_SchemeManager.h b/dmc/cl_dll/vgui_SchemeManager.h new file mode 100644 index 0000000..0e7d99a --- /dev/null +++ b/dmc/cl_dll/vgui_SchemeManager.h @@ -0,0 +1,52 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include + +// handle to an individual scheme +typedef int SchemeHandle_t; + +// Register console variables, etc.. +void Scheme_Init(); + + +//----------------------------------------------------------------------------- +// Purpose: Handles the loading of text scheme description from disk +// supports different font/color/size schemes at different resolutions +//----------------------------------------------------------------------------- +class CSchemeManager +{ +public: + // initialization + CSchemeManager( int xRes, int yRes ); + virtual ~CSchemeManager(); + + // scheme handling + SchemeHandle_t getSchemeHandle( const char *schemeName ); + + // getting info from schemes + vgui::Font *getFont( SchemeHandle_t schemeHandle ); + void getFgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getFgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getFgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBorderColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + +private: + class CScheme; + CScheme *m_pSchemeList; + int m_iNumSchemes; + + // Resolution we were initted at. + int m_xRes; + + CScheme *getSafeScheme( SchemeHandle_t schemeHandle ); +}; + + diff --git a/dmc/cl_dll/vgui_ScorePanel.cpp b/dmc/cl_dll/vgui_ScorePanel.cpp new file mode 100644 index 0000000..d7fd71e --- /dev/null +++ b/dmc/cl_dll/vgui_ScorePanel.cpp @@ -0,0 +1,1031 @@ +//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: VGUI scoreboard +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + + +#include + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "vgui_viewport.h" +#include "vgui_ScorePanel.h" +#include "voice_status.h" +#include "vgui_helpers.h" +#include "vgui_loadtga.h" + +extern int g_iTeamNumber; + +hud_player_info_t g_PlayerInfoList[MAX_PLAYERS+1]; // player info from the engine +extra_player_info_t g_PlayerExtraInfo[MAX_PLAYERS+1]; // additional player info sent directly to the client dll +team_info_t g_TeamInfo[MAX_TEAMS+1]; +int g_IsSpectator[MAX_PLAYERS+1]; + +// Scoreboard dimensions +#define SBOARD_TITLE_SIZE_Y YRES(22) + +#define X_BORDER XRES(4) + +// Column sizes +class SBColumnInfo +{ +public: + char *m_pTitle; // If null, ignore, if starts with #, it's localized, otherwise use the string directly. + int m_Width; // Based on 640 width. Scaled to fit other resolutions. + Label::Alignment m_Alignment; +}; + +// grid size is marked out for 640x480 screen + +SBColumnInfo g_ColumnInfo[NUM_COLUMNS] = +{ + {NULL, 24, Label::a_east}, // tracker column + {NULL, 140, Label::a_west}, // name + {"#SCORE", 80, Label::a_east}, + {"#DEATHS", 46, Label::a_east}, + {"#LATENCY", 46, Label::a_east}, + {"#VOICE", 40, Label::a_east}, + {NULL, 2, Label::a_east}, // blank column to take up the slack +}; + + +#define TEAM_NO 0 +#define TEAM_YES 1 +#define TEAM_SPECTATORS 2 +#define TEAM_BLANK 3 + + +//----------------------------------------------------------------------------- +// ScorePanel::HitTestPanel. +//----------------------------------------------------------------------------- + +void ScorePanel::HitTestPanel::internalMousePressed(MouseCode code) +{ + for(int i=0;i<_inputSignalDar.getCount();i++) + { + _inputSignalDar[i]->mousePressed(code,this); + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: Create the ScoreBoard panel +//----------------------------------------------------------------------------- +ScorePanel::ScorePanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) +{ + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle("Scoreboard Title Text"); + SchemeHandle_t hSmallScheme = pSchemes->getSchemeHandle("Scoreboard Small Text"); + Font *tfont = pSchemes->getFont(hTitleScheme); + Font *smallfont = pSchemes->getFont(hSmallScheme); + + setBgColor(0, 0, 0, 96); + m_pCurrentHighlightLabel = NULL; + m_iHighlightRow = -1; + + // Initialize the top title. + m_TitleLabel.setFont(tfont); + m_TitleLabel.setText(""); + m_TitleLabel.setBgColor( 0, 0, 0, 255 ); + m_TitleLabel.setFgColor( Scheme::sc_primary1 ); + m_TitleLabel.setContentAlignment( vgui::Label::a_west ); + + LineBorder *border = new LineBorder(Color(60, 60, 60, 128)); + setBorder(border); + setPaintBorderEnabled(true); + + int xpos = g_ColumnInfo[0].m_Width + 3; + if (ScreenWidth >= 640) + { + // only expand column size for res greater than 640 + xpos = XRES(xpos); + } + m_TitleLabel.setBounds(xpos, 4, wide, SBOARD_TITLE_SIZE_Y); + m_TitleLabel.setContentFitted(false); + m_TitleLabel.setParent(this); + + // Setup the header (labels like "name", "class", etc..). + m_HeaderGrid.SetDimensions(NUM_COLUMNS, 1); + m_HeaderGrid.SetSpacing(0, 0); + + for(int i=0; i < NUM_COLUMNS; i++) + { + if (g_ColumnInfo[i].m_pTitle && g_ColumnInfo[i].m_pTitle[0] == '#') + m_HeaderLabels[i].setText(CHudTextMessage::BufferedLocaliseTextString(g_ColumnInfo[i].m_pTitle)); + else if(g_ColumnInfo[i].m_pTitle) + m_HeaderLabels[i].setText(g_ColumnInfo[i].m_pTitle); + + int xwide = g_ColumnInfo[i].m_Width; + if (ScreenWidth >= 640) + { + xwide = XRES(xwide); + } + else if (ScreenWidth == 400) + { + // hack to make 400x300 resolution scoreboard fit + if (i == 1) + { + // reduces size of player name cell + xwide -= 28; + } + else if (i == 0) + { + // tracker icon cell + xwide -= 8; + } + } + + m_HeaderGrid.SetColumnWidth(i, xwide); + m_HeaderGrid.SetEntry(i, 0, &m_HeaderLabels[i]); + + m_HeaderLabels[i].setBgColor(0,0,0,255); + m_HeaderLabels[i].setFgColor(Scheme::sc_primary1); + m_HeaderLabels[i].setFont(smallfont); + m_HeaderLabels[i].setContentAlignment(g_ColumnInfo[i].m_Alignment); + + int yres = 12; + if (ScreenHeight >= 480) + { + yres = YRES(yres); + } + m_HeaderLabels[i].setSize(50, yres); + } + + // Set the width of the last column to be the remaining space. + int ex, ey, ew, eh; + m_HeaderGrid.GetEntryBox(NUM_COLUMNS - 2, 0, ex, ey, ew, eh); + m_HeaderGrid.SetColumnWidth(NUM_COLUMNS - 1, (wide - X_BORDER) - (ex + ew)); + + m_HeaderGrid.AutoSetRowHeights(); + m_HeaderGrid.setBounds(X_BORDER, SBOARD_TITLE_SIZE_Y, wide - X_BORDER*2, m_HeaderGrid.GetRowHeight(0)); + m_HeaderGrid.setParent(this); + m_HeaderGrid.setBgColor(0,0,0,255); + + + // Now setup the listbox with the actual player data in it. + int headerX, headerY, headerWidth, headerHeight; + m_HeaderGrid.getBounds(headerX, headerY, headerWidth, headerHeight); + m_PlayerList.setBounds(headerX, headerY+headerHeight, headerWidth, tall - headerY - headerHeight - 6); + m_PlayerList.setBgColor(0,0,0,255); + m_PlayerList.setParent(this); + + for(int row=0; row < NUM_ROWS; row++) + { + CGrid *pGridRow = &m_PlayerGrids[row]; + + pGridRow->SetDimensions(NUM_COLUMNS, 1); + + for(int col=0; col < NUM_COLUMNS; col++) + { + m_PlayerEntries[col][row].setContentFitted(false); + m_PlayerEntries[col][row].setRow(row); + m_PlayerEntries[col][row].addInputSignal(this); + pGridRow->SetEntry(col, 0, &m_PlayerEntries[col][row]); + } + + pGridRow->setBgColor(0,0,0,255); + pGridRow->SetSpacing(0, 0); + pGridRow->CopyColumnWidths(&m_HeaderGrid); + pGridRow->AutoSetRowHeights(); + pGridRow->setSize(PanelWidth(pGridRow), pGridRow->CalcDrawHeight()); + pGridRow->RepositionContents(); + + m_PlayerList.AddItem(pGridRow); + } + + + // Add the hit test panel. It is invisible and traps mouse clicks so we can go into squelch mode. + m_HitTestPanel.setBgColor(0,0,0,255); + m_HitTestPanel.setParent(this); + m_HitTestPanel.setBounds(0, 0, wide, tall); + m_HitTestPanel.addInputSignal(this); + + Initialize(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Called each time a new level is started. +//----------------------------------------------------------------------------- +void ScorePanel::Initialize( void ) +{ + // Clear out scoreboard data + m_iLastKilledBy = 0; + m_fLastKillTime = 0; + m_iPlayerNum = 0; + m_iNumTeams = 0; + memset( g_PlayerExtraInfo, 0, sizeof g_PlayerExtraInfo ); + memset( g_TeamInfo, 0, sizeof g_TeamInfo ); +} + + +bool HACK_GetPlayerUniqueID( int iPlayer, char playerID[16] ) +{ + return !!gEngfuncs.GetPlayerUniqueID( iPlayer, playerID ); +} + +//----------------------------------------------------------------------------- +// Purpose: Recalculate the internal scoreboard data +//----------------------------------------------------------------------------- +void ScorePanel::Update() +{ + int i; + + // Set the title + if (gViewPort->m_szServerName) + { + char sz[MAX_SERVERNAME_LENGTH + 16]; + sprintf(sz, "%s", gViewPort->m_szServerName ); + m_TitleLabel.setText(sz); + } + + m_iRows = 0; + gViewPort->GetAllPlayersInfo(); + + // Clear out sorts + for (i = 0; i < NUM_ROWS; i++) + { + m_iSortedRows[i] = 0; + m_iIsATeam[i] = TEAM_NO; + } + for (i = 0; i < MAX_PLAYERS; i++) + { + m_bHasBeenSorted[i] = false; + } + + // If it's not teamplay, sort all the players. Otherwise, sort the teams. + if ( !gHUD.m_Teamplay ) + SortPlayers( 0, NULL ); + else + SortTeams(); + + // set scrollbar range + m_PlayerList.SetScrollRange(m_iRows); + + FillGrid(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sort all the teams +//----------------------------------------------------------------------------- +void ScorePanel::SortTeams() +{ + // clear out team scores + int i; + for ( i = 1; i <= m_iNumTeams; i++ ) + { + if ( !g_TeamInfo[i].scores_overriden ) + g_TeamInfo[i].frags = g_TeamInfo[i].deaths = 0; + g_TeamInfo[i].ping = g_TeamInfo[i].packetloss = 0; + } + + // recalc the team scores, then draw them + for ( i = 1; i < MAX_PLAYERS; i++ ) + { + if ( g_PlayerInfoList[i].name == NULL ) + continue; // empty player slot, skip + + if ( g_PlayerExtraInfo[i].teamname[0] == 0 ) + continue; // skip over players who are not in a team + + // find what team this player is in + int j; + for ( j = 1; j <= m_iNumTeams; j++ ) + { + if ( !stricmp( g_PlayerExtraInfo[i].teamname, g_TeamInfo[j].name ) ) + break; + } + if ( j > m_iNumTeams ) // player is not in a team, skip to the next guy + continue; + + if ( !g_TeamInfo[j].scores_overriden ) + { + g_TeamInfo[j].frags += g_PlayerExtraInfo[i].frags; + g_TeamInfo[j].deaths += g_PlayerExtraInfo[i].deaths; + } + + g_TeamInfo[j].ping += g_PlayerInfoList[i].ping; + g_TeamInfo[j].packetloss += g_PlayerInfoList[i].packetloss; + + if ( g_PlayerInfoList[i].thisplayer ) + g_TeamInfo[j].ownteam = TRUE; + else + g_TeamInfo[j].ownteam = FALSE; + + // Set the team's number (used for team colors) + g_TeamInfo[j].teamnumber = g_PlayerExtraInfo[i].teamnumber; + } + + // find team ping/packetloss averages + for ( i = 1; i <= m_iNumTeams; i++ ) + { + g_TeamInfo[i].already_drawn = FALSE; + + if ( g_TeamInfo[i].players > 0 ) + { + g_TeamInfo[i].ping /= g_TeamInfo[i].players; // use the average ping of all the players in the team as the teams ping + g_TeamInfo[i].packetloss /= g_TeamInfo[i].players; // use the average ping of all the players in the team as the teams ping + } + } + + // Draw the teams + while ( 1 ) + { + int highest_frags = -99999; int lowest_deaths = 99999; + int best_team = 0; + + for ( i = 1; i <= m_iNumTeams; i++ ) + { + if ( g_TeamInfo[i].players < 1 ) + continue; + + if ( !g_TeamInfo[i].already_drawn && g_TeamInfo[i].frags >= highest_frags ) + { + if ( g_TeamInfo[i].frags > highest_frags || g_TeamInfo[i].deaths < lowest_deaths ) + { + best_team = i; + lowest_deaths = g_TeamInfo[i].deaths; + highest_frags = g_TeamInfo[i].frags; + } + } + } + + // draw the best team on the scoreboard + if ( !best_team ) + break; + + // Put this team in the sorted list + m_iSortedRows[ m_iRows ] = best_team; + m_iIsATeam[ m_iRows ] = TEAM_YES; + g_TeamInfo[best_team].already_drawn = TRUE; // set the already_drawn to be TRUE, so this team won't get sorted again + m_iRows++; + + // Now sort all the players on this team + SortPlayers( 0, g_TeamInfo[best_team].name ); + } + + // Add all the players who aren't in a team yet into spectators + SortPlayers( TEAM_SPECTATORS, NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sort a list of players +//----------------------------------------------------------------------------- +void ScorePanel::SortPlayers( int iTeam, char *team ) +{ + bool bCreatedTeam = false; + + // draw the players, in order, and restricted to team if set + while ( 1 ) + { + // Find the top ranking player + int highest_frags = -99999; int lowest_deaths = 99999; + int best_player; + best_player = 0; + + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + if ( m_bHasBeenSorted[i] == false && g_PlayerInfoList[i].name && g_PlayerExtraInfo[i].frags >= highest_frags ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( i ); + + if ( ent && !(team && stricmp(g_PlayerExtraInfo[i].teamname, team)) ) + { + extra_player_info_t *pl_info = &g_PlayerExtraInfo[i]; + if ( pl_info->frags > highest_frags || pl_info->deaths < lowest_deaths ) + { + best_player = i; + lowest_deaths = pl_info->deaths; + highest_frags = pl_info->frags; + } + } + } + } + + if ( !best_player ) + break; + + // If we haven't created the Team yet, do it first + if (!bCreatedTeam && iTeam) + { + m_iIsATeam[ m_iRows ] = iTeam; + m_iRows++; + + bCreatedTeam = true; + } + + // Put this player in the sorted list + m_iSortedRows[ m_iRows ] = best_player; + m_bHasBeenSorted[ best_player ] = true; + m_iRows++; + } + + if (team) + { + m_iIsATeam[m_iRows++] = TEAM_BLANK; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Recalculate the existing teams in the match +//----------------------------------------------------------------------------- +void ScorePanel::RebuildTeams() +{ + // clear out player counts from teams + int i; + for ( i = 1; i <= m_iNumTeams; i++ ) + { + g_TeamInfo[i].players = 0; + } + + // rebuild the team list + gViewPort->GetAllPlayersInfo(); + m_iNumTeams = 0; + for ( i = 1; i < MAX_PLAYERS; i++ ) + { + if ( g_PlayerInfoList[i].name == NULL ) + continue; + + if ( g_PlayerExtraInfo[i].teamname[0] == 0 ) + continue; // skip over players who are not in a team + + // is this player in an existing team? + int j; + for ( j = 1; j <= m_iNumTeams; j++ ) + { + if ( g_TeamInfo[j].name[0] == '\0' ) + break; + + if ( !stricmp( g_PlayerExtraInfo[i].teamname, g_TeamInfo[j].name ) ) + break; + } + + if ( j > m_iNumTeams ) + { // they aren't in a listed team, so make a new one + // search through for an empty team slot + for ( int j = 1; j <= m_iNumTeams; j++ ) + { + if ( g_TeamInfo[j].name[0] == '\0' ) + break; + } + m_iNumTeams = max( j, m_iNumTeams ); + + strncpy( g_TeamInfo[j].name, g_PlayerExtraInfo[i].teamname, MAX_TEAM_NAME ); + g_TeamInfo[j].players = 0; + } + + g_TeamInfo[j].players++; + } + + // clear out any empty teams + for ( i = 1; i <= m_iNumTeams; i++ ) + { + if ( g_TeamInfo[i].players < 1 ) + memset( &g_TeamInfo[i], 0, sizeof(team_info_t) ); + } + + // Update the scoreboard + Update(); +} + + +void ScorePanel::FillGrid() +{ + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + SchemeHandle_t hScheme = pSchemes->getSchemeHandle("Scoreboard Text"); + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle("Scoreboard Title Text"); + SchemeHandle_t hSmallScheme = pSchemes->getSchemeHandle("Scoreboard Small Text"); + + Font *sfont = pSchemes->getFont(hScheme); + Font *tfont = pSchemes->getFont(hTitleScheme); + Font *smallfont = pSchemes->getFont(hSmallScheme); + + // update highlight position + int x, y; + getApp()->getCursorPos( x, y ); + screenToLocal( x, y ); + cursorMoved( x, y, this ); + + // remove highlight row if we're not in squelch mode + if (!GetClientVoiceMgr()->IsInSquelchMode()) + { + m_iHighlightRow = -1; + } + + bool bNextRowIsGap = false; + + int row; + for(row=0; row < NUM_ROWS; row++) + { + CGrid *pGridRow = &m_PlayerGrids[row]; + pGridRow->SetRowUnderline(0, false, 0, 0, 0, 0, 0); + + if(row >= m_iRows) + { + for(int col=0; col < NUM_COLUMNS; col++) + m_PlayerEntries[col][row].setVisible(false); + + continue; + } + + bool bRowIsGap = false; + if (bNextRowIsGap) + { + bNextRowIsGap = false; + bRowIsGap = true; + } + + for(int col=0; col < NUM_COLUMNS; col++) + { + CLabelHeader *pLabel = &m_PlayerEntries[col][row]; + + pLabel->setVisible(true); + pLabel->setText2(""); + pLabel->setImage(NULL); + pLabel->setFont(sfont); + pLabel->setTextOffset(0, 0); + + int rowheight = 13; + if (ScreenHeight > 480) + { + rowheight = YRES(rowheight); + } + else + { + // more tweaking, make sure icons fit at low res + rowheight = 15; + } + pLabel->setSize(pLabel->getWide(), rowheight); + pLabel->setBgColor(0, 0, 0, 255); + + char sz[128]; + hud_player_info_t *pl_info = NULL; + team_info_t *team_info = NULL; + + if (m_iIsATeam[row] == TEAM_BLANK) + { + pLabel->setText(" "); + continue; + } + else if ( m_iIsATeam[row] == TEAM_YES ) + { + // Get the team's data + team_info = &g_TeamInfo[ m_iSortedRows[row] ]; + + // team color text for team names + pLabel->setFgColor( iTeamColors[team_info->teamnumber][0], iTeamColors[team_info->teamnumber][1], iTeamColors[team_info->teamnumber][2], 0 ); + + // different height for team header rows + rowheight = 20; + if (ScreenHeight >= 480) + { + rowheight = YRES(rowheight); + } + pLabel->setSize(pLabel->getWide(), rowheight); + pLabel->setFont(tfont); + + pGridRow->SetRowUnderline(0, true, YRES(3), iTeamColors[team_info->teamnumber][0], iTeamColors[team_info->teamnumber][1], iTeamColors[team_info->teamnumber][2], 0); + } + else if ( m_iIsATeam[row] == TEAM_SPECTATORS ) + { + // grey text for spectators + pLabel->setFgColor(100, 100, 100, 0); + + // different height for team header rows + rowheight = 20; + if (ScreenHeight >= 480) + { + rowheight = YRES(rowheight); + } + pLabel->setSize(pLabel->getWide(), rowheight); + pLabel->setFont(tfont); + + pGridRow->SetRowUnderline(0, true, YRES(3), 100, 100, 100, 0); + } + else + { + // team color text for player names + pLabel->setFgColor( iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ][0], iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ][1], iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ][2], 0 ); + + // Get the player's data + pl_info = &g_PlayerInfoList[ m_iSortedRows[row] ]; + + // Set background color + if ( pl_info->thisplayer ) // if it is their name, draw it a different color + { + // Highlight this player + pLabel->setFgColor(Scheme::sc_white); + pLabel->setBgColor( iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ][0], iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ][1], iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ][2], 196 ); + } + else if ( m_iSortedRows[row] == m_iLastKilledBy && m_fLastKillTime && m_fLastKillTime > gHUD.m_flTime ) + { + // Killer's name + pLabel->setBgColor( 255,0,0, 255 - ((float)15 * (float)(m_fLastKillTime - gHUD.m_flTime)) ); + } + } + + // Align + if (col == COLUMN_NAME) + { + pLabel->setContentAlignment( vgui::Label::a_west ); + } + else if (col == COLUMN_TRACKER) + { + pLabel->setContentAlignment( vgui::Label::a_center ); + } + else + { + pLabel->setContentAlignment( vgui::Label::a_east ); + } + + // Fill out with the correct data + strcpy(sz, ""); + if ( m_iIsATeam[row] ) + { + char sz2[128]; + + switch (col) + { + case COLUMN_NAME: + if ( m_iIsATeam[row] == TEAM_SPECTATORS ) + { + sprintf( sz2, CHudTextMessage::BufferedLocaliseTextString( "#Spectators" ) ); + } + else + { + sprintf( sz2, CHudTextMessage::BufferedLocaliseTextString( team_info->name ) ); + } + + strcpy(sz, sz2); + + // Append the number of players + if ( m_iIsATeam[row] == TEAM_YES ) + { + if (team_info->players == 1) + { + sprintf(sz2, "(%d %s)", team_info->players, CHudTextMessage::BufferedLocaliseTextString( "#Player" ) ); + } + else + { + sprintf(sz2, "(%d %s)", team_info->players, CHudTextMessage::BufferedLocaliseTextString( "#Player_plural" ) ); + } + + pLabel->setText2(sz2); + pLabel->setFont2(smallfont); + } + break; + case COLUMN_VOICE: + break; + case COLUMN_KILLS: + if ( m_iIsATeam[row] == TEAM_YES ) + sprintf(sz, "%d", team_info->frags ); + break; + case COLUMN_DEATHS: + if ( m_iIsATeam[row] == TEAM_YES ) + sprintf(sz, "%d", team_info->deaths ); + break; + case COLUMN_LATENCY: + if ( m_iIsATeam[row] == TEAM_YES ) + sprintf(sz, "%d", team_info->ping ); + break; + default: + break; + } + } + else + { + bool bShowClass = false; + + switch (col) + { + case COLUMN_NAME: + sprintf(sz, "%s ", pl_info->name); + break; + case COLUMN_VOICE: + sz[0] = 0; + GetClientVoiceMgr()->UpdateSpeakerImage(pLabel, m_iSortedRows[row]); + break; + case COLUMN_KILLS: + sprintf(sz, "%d", g_PlayerExtraInfo[ m_iSortedRows[row] ].frags ); + break; + case COLUMN_DEATHS: + sprintf(sz, "%d", g_PlayerExtraInfo[ m_iSortedRows[row] ].deaths ); + break; + case COLUMN_LATENCY: + sprintf(sz, "%d", g_PlayerInfoList[ m_iSortedRows[row] ].ping ); + break; + default: + break; + } + } + + pLabel->setText(sz); + } + } + + for(row=0; row < NUM_ROWS; row++) + { + CGrid *pGridRow = &m_PlayerGrids[row]; + + pGridRow->AutoSetRowHeights(); + pGridRow->setSize(PanelWidth(pGridRow), pGridRow->CalcDrawHeight()); + pGridRow->RepositionContents(); + } + + // hack, for the thing to resize + m_PlayerList.getSize(x, y); + m_PlayerList.setSize(x, y); +} + + +//----------------------------------------------------------------------------- +// Purpose: Setup highlights for player names in scoreboard +//----------------------------------------------------------------------------- +void ScorePanel::DeathMsg( int killer, int victim ) +{ + // if we were the one killed, or the world killed us, set the scoreboard to indicate suicide + if ( victim == m_iPlayerNum || killer == 0 ) + { + m_iLastKilledBy = killer ? killer : m_iPlayerNum; + m_fLastKillTime = gHUD.m_flTime + 10; // display who we were killed by for 10 seconds + + if ( killer == m_iPlayerNum ) + m_iLastKilledBy = m_iPlayerNum; + } +} + + +void ScorePanel::Open( void ) +{ + RebuildTeams(); + setVisible(true); + m_HitTestPanel.setVisible(true); +} + + +void ScorePanel::mousePressed(MouseCode code, Panel* panel) +{ + if(gHUD.m_iIntermission) + return; + + if (!GetClientVoiceMgr()->IsInSquelchMode()) + { + GetClientVoiceMgr()->StartSquelchMode(); + m_HitTestPanel.setVisible(false); + } + else if (m_iHighlightRow >= 0) + { + // mouse has been pressed, toggle mute state + int iPlayer = m_iSortedRows[m_iHighlightRow]; + if (iPlayer > 0) + { + // print text message + hud_player_info_t *pl_info = &g_PlayerInfoList[iPlayer]; + + if (pl_info && pl_info->name && pl_info->name[0]) + { + char string[256]; + if (GetClientVoiceMgr()->IsPlayerBlocked(iPlayer)) + { + char string1[1024]; + + // remove mute + GetClientVoiceMgr()->SetPlayerBlockedState(iPlayer, false); + + sprintf( string1, CHudTextMessage::BufferedLocaliseTextString( "#Unmuted" ), pl_info->name ); + sprintf( string, "%c** %s\n", HUD_PRINTTALK, string1 ); + + gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); + } + else + { + char string1[1024]; + char string2[1024]; + + // mute the player + GetClientVoiceMgr()->SetPlayerBlockedState(iPlayer, true); + + sprintf( string1, CHudTextMessage::BufferedLocaliseTextString( "#Muted" ), pl_info->name ); + sprintf( string2, CHudTextMessage::BufferedLocaliseTextString( "#No_longer_hear_that_player" ) ); + sprintf( string, "%c** %s %s\n", HUD_PRINTTALK, string1, string2 ); + + gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); + } + } + } + } +} + + +void ScorePanel::cursorMoved(int x, int y, Panel *panel) +{ + // Translate from local coordinates to screen coordinates. + panel->localToScreen( x, y ); + + if (GetClientVoiceMgr()->IsInSquelchMode()) + { + // look for which cell the mouse is currently over + for (int i = 0; i < NUM_ROWS; i++) + { + int row, col; + if (m_PlayerGrids[i].getCellAtPoint(x, y, row, col)) + { + MouseOverCell(i, col); + return; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handles mouse movement over a cell +// Input : row - +// col - +//----------------------------------------------------------------------------- +void ScorePanel::MouseOverCell(int row, int col) +{ + CLabelHeader *label = &m_PlayerEntries[col][row]; + + // clear the previously highlighted label + if (m_pCurrentHighlightLabel != label) + { + m_pCurrentHighlightLabel = NULL; + m_iHighlightRow = -1; + } + if (!label) + return; + + // don't act on teams + if (m_iIsATeam[row] != TEAM_NO) + return; + + // don't act on disconnected players or ourselves + hud_player_info_t *pl_info = &g_PlayerInfoList[ m_iSortedRows[row] ]; + if (!pl_info->name || !pl_info->name[0]) + return; + + if (pl_info->thisplayer && !gEngfuncs.IsSpectateOnly() ) + return; + + // setup the new highlight + m_pCurrentHighlightLabel = label; + m_iHighlightRow = row; +} + +//----------------------------------------------------------------------------- +// Purpose: Label paint functions - take into account current highligh status +//----------------------------------------------------------------------------- +void CLabelHeader::paintBackground() +{ + Color oldBg; + getBgColor(oldBg); + + if (gViewPort->m_pScoreBoard->m_iHighlightRow == _row) + { + setBgColor(134, 91, 19, 0); + } + + Panel::paintBackground(); + + setBgColor(oldBg); +} + + +//----------------------------------------------------------------------------- +// Purpose: Label paint functions - take into account current highligh status +//----------------------------------------------------------------------------- +void CLabelHeader::paint() +{ + Color oldFg; + getFgColor(oldFg); + + if (gViewPort->m_pScoreBoard->m_iHighlightRow == _row) + { + setFgColor(255, 255, 255, 0); + } + + // draw text + int x, y, iwide, itall; + getTextSize(iwide, itall); + calcAlignment(iwide, itall, x, y); + _dualImage->setPos(x, y); + + int x1, y1; + _dualImage->GetImage(1)->getPos(x1, y1); + _dualImage->GetImage(1)->setPos(_gap, y1); + + _dualImage->doPaint(this); + + // get size of the panel and the image + if (_image) + { + _image->getSize(iwide, itall); + calcAlignment(iwide, itall, x, y); + _image->setPos(x, y); + _image->doPaint(this); + } + + setFgColor(oldFg[0], oldFg[1], oldFg[2], oldFg[3]); +} + + +void CLabelHeader::calcAlignment(int iwide, int itall, int &x, int &y) +{ + // calculate alignment ourselves, since vgui is so broken + int wide, tall; + getSize(wide, tall); + + x = 0, y = 0; + + // align left/right + switch (_contentAlignment) + { + // left + case Label::a_northwest: + case Label::a_west: + case Label::a_southwest: + { + x = 0; + break; + } + + // center + case Label::a_north: + case Label::a_center: + case Label::a_south: + { + x = (wide - iwide) / 2; + break; + } + + // right + case Label::a_northeast: + case Label::a_east: + case Label::a_southeast: + { + x = wide - iwide; + break; + } + } + + // top/down + switch (_contentAlignment) + { + // top + case Label::a_northwest: + case Label::a_north: + case Label::a_northeast: + { + y = 0; + break; + } + + // center + case Label::a_west: + case Label::a_center: + case Label::a_east: + { + y = (tall - itall) / 2; + break; + } + + // south + case Label::a_southwest: + case Label::a_south: + case Label::a_southeast: + { + y = tall - itall; + break; + } + } + +// don't clip to Y +// if (y < 0) +// { +// y = 0; +// } + if (x < 0) + { + x = 0; + } + + x += _offset[0]; + y += _offset[1]; +} diff --git a/dmc/cl_dll/vgui_ScorePanel.h b/dmc/cl_dll/vgui_ScorePanel.h new file mode 100644 index 0000000..2df04b9 --- /dev/null +++ b/dmc/cl_dll/vgui_ScorePanel.h @@ -0,0 +1,314 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef SCOREPANEL_H +#define SCOREPANEL_H + +#include +#include +#include +#include +#include +#include +#include "vgui_listbox.h" + +#include + +#define MAX_SCORES 10 +#define MAX_SCOREBOARD_TEAMS 5 + +// Scoreboard cells +#define COLUMN_TRACKER 0 +#define COLUMN_NAME 1 +#define COLUMN_KILLS 2 +#define COLUMN_DEATHS 3 +#define COLUMN_LATENCY 4 +#define COLUMN_VOICE 5 +#define COLUMN_BLANK 6 +#define NUM_COLUMNS 7 + +#define NUM_ROWS (MAX_PLAYERS + (MAX_SCOREBOARD_TEAMS * 2)) + +using namespace vgui; + +class CTextImage2 : public Image +{ +public: + CTextImage2() + { + _image[0] = new TextImage(""); + _image[1] = new TextImage(""); + } + + ~CTextImage2() + { + delete _image[0]; + delete _image[1]; + } + + TextImage *GetImage(int image) + { + return _image[image]; + } + + void getSize(int &wide, int &tall) + { + int w1, w2, t1, t2; + _image[0]->getTextSize(w1, t1); + _image[1]->getTextSize(w2, t2); + + wide = w1 + w2; + tall = max(t1, t2); + setSize(wide, tall); + } + + void doPaint(Panel *panel) + { + _image[0]->doPaint(panel); + _image[1]->doPaint(panel); + } + + void setPos(int x, int y) + { + _image[0]->setPos(x, y); + + int swide, stall; + _image[0]->getSize(swide, stall); + + int wide, tall; + _image[1]->getSize(wide, tall); + _image[1]->setPos(x + wide, y + (stall * 0.9) - tall); + } + + void setColor(Color color) + { + _image[0]->setColor(color); + } + + void setColor2(Color color) + { + _image[1]->setColor(color); + } + +private: + TextImage *_image[2]; + +}; + +//----------------------------------------------------------------------------- +// Purpose: Custom label for cells in the Scoreboard's Table Header +//----------------------------------------------------------------------------- +class CLabelHeader : public Label +{ +public: + CLabelHeader() : Label("") + { + _dualImage = new CTextImage2(); + _dualImage->setColor2(Color(255, 170, 0, 0)); + _row = -2; + _useFgColorAsImageColor = true; + _offset[0] = 0; + _offset[1] = 0; + } + + ~CLabelHeader() + { + delete _dualImage; + } + + void setRow(int row) + { + _row = row; + } + + void setFgColorAsImageColor(bool state) + { + _useFgColorAsImageColor = state; + } + + virtual void setText(int textBufferLen, const char* text) + { + _dualImage->GetImage(0)->setText(text); + + // calculate the text size + Font *font = _dualImage->GetImage(0)->getFont(); + _gap = 0; + for (const char *ch = text; *ch != 0; ch++) + { + int a, b, c; + font->getCharABCwide(*ch, a, b, c); + _gap += (a + b + c); + } + + _gap += XRES(5); + } + + virtual void setText(const char* text) + { + // strip any non-alnum characters from the end + char buf[512]; + strcpy(buf, text); + + int len = strlen(buf); + while (len && isspace(buf[--len])) + { + buf[len] = 0; + } + + CLabelHeader::setText(0, buf); + } + + void setText2(const char *text) + { + _dualImage->GetImage(1)->setText(text); + } + + void getTextSize(int &wide, int &tall) + { + _dualImage->getSize(wide, tall); + } + + void setFgColor(int r,int g,int b,int a) + { + Label::setFgColor(r,g,b,a); + Color color(r,g,b,a); + _dualImage->setColor(color); + _dualImage->setColor2(color); + if (_image && _useFgColorAsImageColor) + { + _image->setColor(color); + } + repaint(); + } + + void setFgColor(Scheme::SchemeColor sc) + { + Label::setFgColor(sc); + _dualImage->setColor(sc); + } + + void setFont(Font *font) + { + _dualImage->GetImage(0)->setFont(font); + } + + void setFont2(Font *font) + { + _dualImage->GetImage(1)->setFont(font); + } + + // this adjust the absolute position of the text after alignment is calculated + void setTextOffset(int x, int y) + { + _offset[0] = x; + _offset[1] = y; + } + + void paint(); + void paintBackground(); + void calcAlignment(int iwide, int itall, int &x, int &y); + +private: + CTextImage2 *_dualImage; + int _row; + int _gap; + int _offset[2]; + bool _useFgColorAsImageColor; +}; + +class ScoreTablePanel; + +#include "vgui_grid.h" +#include "vgui_defaultinputsignal.h" + +//----------------------------------------------------------------------------- +// Purpose: Scoreboard back panel +//----------------------------------------------------------------------------- +class ScorePanel : public Panel, public vgui::CDefaultInputSignal +{ +private: + // Default panel implementation doesn't forward mouse messages when there is no cursor and we need them. + class HitTestPanel : public Panel + { + public: + virtual void internalMousePressed(MouseCode code); + }; + + +private: + + Label m_TitleLabel; + + // Here is how these controls are arranged hierarchically. + // m_HeaderGrid + // m_HeaderLabels + + // m_PlayerGridScroll + // m_PlayerGrid + // m_PlayerEntries + + CGrid m_HeaderGrid; + CLabelHeader m_HeaderLabels[NUM_COLUMNS]; // Labels above the + CLabelHeader *m_pCurrentHighlightLabel; + int m_iHighlightRow; + + vgui::CListBox m_PlayerList; + CGrid m_PlayerGrids[NUM_ROWS]; // The grid with player and team info. + CLabelHeader m_PlayerEntries[NUM_COLUMNS][NUM_ROWS]; // Labels for the grid entries. + + ScorePanel::HitTestPanel m_HitTestPanel; + + CLabelHeader* GetPlayerEntry(int x, int y) {return &m_PlayerEntries[x][y];} + +public: + + int m_iNumTeams; + int m_iPlayerNum; + int m_iShowscoresHeld; + + int m_iRows; + int m_iSortedRows[NUM_ROWS]; + int m_iIsATeam[NUM_ROWS]; + bool m_bHasBeenSorted[MAX_PLAYERS]; + int m_iLastKilledBy; + int m_fLastKillTime; + + CImageLabel *m_pImages[ 7 ]; + + +public: + + ScorePanel(int x,int y,int wide,int tall); + + void Update( void ); + + void SortTeams( void ); + void SortPlayers( int iTeam, char *team ); + void RebuildTeams( void ); + + void FillGrid(); + + void DeathMsg( int killer, int victim ); + + void Initialize( void ); + + void Open( void ); + + void MouseOverCell(int row, int col); + + +// InputSignal overrides. +public: + + virtual void mousePressed(MouseCode code, Panel* panel); + virtual void cursorMoved(int x, int y, Panel *panel); + + friend class CLabelHeader; +}; + +#endif + diff --git a/dmc/cl_dll/vgui_ServerBrowser.cpp b/dmc/cl_dll/vgui_ServerBrowser.cpp new file mode 100644 index 0000000..55b89cd --- /dev/null +++ b/dmc/cl_dll/vgui_ServerBrowser.cpp @@ -0,0 +1,623 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include +#include +#include +#include +#include + +#include "hud.h" +#include "cl_util.h" +#include "hud_servers.h" +#include "net_api.h" + +#include "vgui_viewport.h" +#include "vgui_ServerBrowser.h" + +using namespace vgui; + +namespace +{ + +#define MAX_SB_ROWS 24 + +#define NUM_COLUMNS 5 + +#define HEADER_SIZE_Y YRES(18) + +// Column sizes +#define CSIZE_ADDRESS XRES(200) +#define CSIZE_SERVER XRES(400) +#define CSIZE_MAP XRES(500) +#define CSIZE_CURRENT XRES(570) +#define CSIZE_PING XRES(640) + +#define CELL_HEIGHT YRES(15) + +class ServerBrowserTablePanel; + +class CBrowser_InputSignal : public InputSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; +public: + CBrowser_InputSignal( ServerBrowserTablePanel *pBrowser ) + { + m_pBrowser = pBrowser; + } + + virtual void cursorMoved(int x,int y,Panel* panel) {}; + virtual void cursorEntered(Panel* panel){}; + virtual void cursorExited(Panel* Panel) {}; + + virtual void mousePressed(MouseCode code,Panel* panel); + + virtual void mouseDoublePressed(MouseCode code,Panel* panel); + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; +}; + +class ServerBrowserTablePanel : public TablePanel +{ +private: + Label *m_pLabel; + int m_nMouseOverRow; + +public: + + ServerBrowserTablePanel( int x,int y,int wide,int tall,int columnCount) : TablePanel( x,y,wide,tall,columnCount) + { + m_pLabel = new Label( "", 0, 0 /*,wide, tall*/ ); + + m_nMouseOverRow = 0; + } + +public: + void setMouseOverRow( int row ) + { + m_nMouseOverRow = row; + } + + void DoSort( char *sortkey ) + { + // Request server list and refresh servers... + SortServers( sortkey ); + } + + void DoRefresh( void ) + { + // Request server list and refresh servers... + ServersList(); + BroadcastServersList( 0 ); + } + + void DoBroadcastRefresh( void ) + { + // Request server list and refresh servers... + BroadcastServersList( 1 ); + } + + void DoStop( void ) + { + // Stop requesting + ServersCancel(); + } + + void DoCancel( void ) + { + ClientCmd( "togglebrowser\n" ); + } + + void DoConnect( void ) + { + const char *info; + const char *address; + char sz[ 256 ]; + + info = ServersGetInfo( m_nMouseOverRow ); + if ( !info ) + return; + + address = gEngfuncs.pNetAPI->ValueForKey( info, "address" ); + //gEngfuncs.Con_Printf( "Connecting to %s\n", address ); + + sprintf( sz, "connect %s\n", address ); + + ClientCmd( sz ); + + DoCancel(); + } + + void DoPing( void ) + { + ServerPing( 0 ); + ServerRules( 0 ); + ServerPlayers( 0 ); + } + + virtual int getRowCount() + { + int rowcount; + int height, width; + + getSize( width, height ); + + // Space for buttons + height -= YRES(20); + height = max( 0, height ); + + rowcount = height / CELL_HEIGHT; + + return rowcount; + } + + virtual int getCellTall(int row) + { + return CELL_HEIGHT - 2; + } + + virtual Panel* getCellRenderer(int column,int row,bool columnSelected,bool rowSelected,bool cellSelected) + { + const char *info; + const char *val, *val2; + char sz[ 32 ]; + + info = ServersGetInfo( row ); + + if ( row == m_nMouseOverRow ) + { + m_pLabel->setFgColor( 200, 240, 63, 100 ); + } + else + { + m_pLabel->setFgColor( 255, 255, 255, 0 ); + } + m_pLabel->setBgColor( 0, 0, 0, 200 ); + m_pLabel->setContentAlignment( vgui::Label::a_west ); + m_pLabel->setFont( Scheme::sf_primary2 ); + + if ( info ) + { + // Fill out with the correct data + switch ( column ) + { + case 0: + val = gEngfuncs.pNetAPI->ValueForKey( info, "address" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Name; + m_pLabel->setText( sz ); + } + break; + case 1: + val = gEngfuncs.pNetAPI->ValueForKey( info, "hostname" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Map; + m_pLabel->setText( sz ); + } + break; + case 2: + val = gEngfuncs.pNetAPI->ValueForKey( info, "map" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Name; + m_pLabel->setText( sz ); + } + break; + case 3: + val = gEngfuncs.pNetAPI->ValueForKey( info, "current" ); + val2 = gEngfuncs.pNetAPI->ValueForKey( info, "max" ); + if ( val && val2 ) + { + sprintf( sz, "%s/%s", val, val2 ); + sz[ 31 ] = '\0'; + // Server Map; + m_pLabel->setText( sz ); + } + break; + case 4: + val = gEngfuncs.pNetAPI->ValueForKey( info, "ping" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Name; + m_pLabel->setText( sz ); + } + break; + default: + break; + } + } + else + { + if ( !row && !column ) + { + if ( ServersIsQuerying() ) + { + m_pLabel->setText( "Waiting for servers to respond..." ); + } + else + { + m_pLabel->setText( "Press 'Refresh' to search for servers..." ); + } + } + else + { + m_pLabel->setText( "" ); + } + } + + return m_pLabel; + } + + virtual Panel* startCellEditing(int column,int row) + { + return null; + } + +}; + +class ConnectHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + ConnectHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoConnect(); + } +}; + +class RefreshHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + RefreshHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoRefresh(); + } +}; + +class BroadcastRefreshHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + BroadcastRefreshHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoBroadcastRefresh(); + } +}; + +class StopHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + StopHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoStop(); + } +}; + +class CancelHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + CancelHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoCancel(); + } +}; + +class PingHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + PingHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoPing(); + } +}; + +class SortHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + SortHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoSort( "map" ); + } +}; + +} + +class LabelSortInputHandler : public InputSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + char m_szSortKey[ 64 ]; + +public: + LabelSortInputHandler( ServerBrowserTablePanel *pBrowser, char *name ) + { + m_pBrowser = pBrowser; + strcpy( m_szSortKey, name ); + } + + virtual void cursorMoved(int x,int y,Panel* panel) {}; + virtual void cursorEntered(Panel* panel){}; + virtual void cursorExited(Panel* Panel) {}; + + virtual void mousePressed(MouseCode code,Panel* panel) + { + m_pBrowser->DoSort( m_szSortKey ); + } + + virtual void mouseDoublePressed(MouseCode code,Panel* panel) + { + m_pBrowser->DoSort( m_szSortKey ); + } + + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; +}; + +class CSBLabel : public Label +{ + +private: + char m_szSortKey[ 64 ]; + ServerBrowserTablePanel *m_pBrowser; + +public: + CSBLabel( char *name, char *sortkey ) : Label( name ) + { + m_pBrowser = NULL; + + strcpy( m_szSortKey, sortkey ); + + int label_bg_r = 120, + label_bg_g = 75, + label_bg_b = 32, + label_bg_a = 200; + + int label_fg_r = 255, + label_fg_g = 0, + label_fg_b = 0, + label_fg_a = 0; + + setContentAlignment( vgui::Label::a_west ); + setFgColor( label_fg_r, label_fg_g, label_fg_b, label_fg_a ); + setBgColor( label_bg_r, label_bg_g, label_bg_b, label_bg_a ); + setFont( Scheme::sf_primary2 ); + + } + + void setTable( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + + addInputSignal( new LabelSortInputHandler( (ServerBrowserTablePanel * )m_pBrowser, m_szSortKey ) ); + } +}; + +ServerBrowser::ServerBrowser(int x,int y,int wide,int tall) : CTransparentPanel( 100, x,y,wide,tall ) +{ + int i; + + _headerPanel = new HeaderPanel(0,0,wide,HEADER_SIZE_Y); + _headerPanel->setParent(this); + _headerPanel->setFgColor( 100,100,100, 100 ); + _headerPanel->setBgColor( 0, 0, 0, 100 ); + + CSBLabel *pLabel[5]; + + pLabel[0] = new CSBLabel( "Address", "address" ); + pLabel[1] = new CSBLabel( "Server", "hostname" ); + pLabel[2] = new CSBLabel( "Map", "map" ); + pLabel[3] = new CSBLabel( "Current", "current" ); + pLabel[4] = new CSBLabel( "Latency", "ping" ); + + for ( i = 0; i < 5; i++ ) + { + _headerPanel->addSectionPanel( pLabel[i] ); + } + + // _headerPanel->setFont( Scheme::sf_primary1 ); + + _headerPanel->setSliderPos( 0, CSIZE_ADDRESS ); + _headerPanel->setSliderPos( 1, CSIZE_SERVER ); + _headerPanel->setSliderPos( 2, CSIZE_MAP ); + _headerPanel->setSliderPos( 3, CSIZE_CURRENT ); + _headerPanel->setSliderPos( 4, CSIZE_PING ); + + _tablePanel = new ServerBrowserTablePanel( 0, HEADER_SIZE_Y, wide, tall - HEADER_SIZE_Y, NUM_COLUMNS ); + _tablePanel->setParent(this); + _tablePanel->setHeaderPanel(_headerPanel); + _tablePanel->setFgColor( 100,100,100, 100 ); + _tablePanel->setBgColor( 0, 0, 0, 100 ); + + _tablePanel->addInputSignal( new CBrowser_InputSignal( (ServerBrowserTablePanel *)_tablePanel ) ); + + for ( i = 0; i < 5; i++ ) + { + pLabel[i]->setTable( (ServerBrowserTablePanel * )_tablePanel ); + } + + int bw = 80, bh = 15; + int by = tall - HEADER_SIZE_Y; + + int btnx = 10; + + _connectButton = new CommandButton( "Connect", btnx, by, bw, bh ); + _connectButton->setParent( this ); + _connectButton->addActionSignal( new ConnectHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + btnx += bw; + + _refreshButton = new CommandButton( "Refresh", btnx, by, bw, bh ); + _refreshButton->setParent( this ); + _refreshButton->addActionSignal( new RefreshHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + /* + btnx += bw; + + _broadcastRefreshButton = new CommandButton( "LAN", btnx, by, bw, bh ); + _broadcastRefreshButton->setParent( this ); + _broadcastRefreshButton->addActionSignal( new BroadcastRefreshHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + */ + + btnx += bw; + + _stopButton = new CommandButton( "Stop", btnx, by, bw, bh ); + _stopButton->setParent( this ); + _stopButton->addActionSignal( new StopHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + /* + btnx += bw; + + _pingButton = new CommandButton( "Test", btnx, by, bw, bh ); + _pingButton->setParent( this ); + _pingButton->addActionSignal( new PingHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + btnx += bw; + + _sortButton = new CommandButton( "Sort", btnx, by, bw, bh ); + _sortButton->setParent( this ); + _sortButton->addActionSignal( new SortHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + */ + + btnx += bw; + + _cancelButton = new CommandButton( "Close", btnx, by, bw, bh ); + _cancelButton->setParent( this ); + _cancelButton->addActionSignal( new CancelHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + setPaintBorderEnabled(false); + setPaintBackgroundEnabled(false); + setPaintEnabled(false); + +} + +void ServerBrowser::setSize(int wide,int tall) +{ + Panel::setSize(wide,tall); + + _headerPanel->setBounds(0,0,wide,HEADER_SIZE_Y); + _tablePanel->setBounds(0,HEADER_SIZE_Y,wide,tall - HEADER_SIZE_Y); + + _connectButton->setBounds( 5, tall - HEADER_SIZE_Y, 75, 15 ); + _refreshButton->setBounds( 85, tall - HEADER_SIZE_Y, 75, 15 ); + /* + _broadcastRefreshButton->setBounds( 165, tall - HEADER_SIZE_Y, 75, 15 ); + */ + _stopButton->setBounds( 165, tall - HEADER_SIZE_Y, 75, 15 ); + /* + _pingButton->setBounds( 325, tall - HEADER_SIZE_Y, 75, 15 ); + */ + _cancelButton->setBounds( 245, tall - HEADER_SIZE_Y, 75, 15 ); +} + +void CBrowser_InputSignal::mousePressed(MouseCode code,Panel* panel) +{ + int x, y; + int therow = 2; + + if ( code != MOUSE_LEFT ) + return; + + panel->getApp()->getCursorPos(x,y); + panel->screenToLocal( x, y ); + + therow = y / CELL_HEIGHT; + + // Figure out which row it's on + m_pBrowser->setMouseOverRow( therow ); +} + +void CBrowser_InputSignal::mouseDoublePressed(MouseCode code,Panel* panel) +{ + int x, y; + int therow = 2; + + if ( code != MOUSE_LEFT ) + return; + + panel->getApp()->getCursorPos(x,y); + panel->screenToLocal( x, y ); + + therow = y / CELL_HEIGHT; + + // Figure out which row it's on + m_pBrowser->setMouseOverRow( therow ); + m_pBrowser->DoConnect(); +} diff --git a/dmc/cl_dll/vgui_ServerBrowser.h b/dmc/cl_dll/vgui_ServerBrowser.h new file mode 100644 index 0000000..01f5693 --- /dev/null +++ b/dmc/cl_dll/vgui_ServerBrowser.h @@ -0,0 +1,50 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef ServerBrowser_H +#define ServerBrowser_H + +#include + +namespace vgui +{ +class Button; +class TablePanel; +class HeaderPanel; +} + +class CTransparentPanel; +class CommandButton; + +// Scoreboard positions +#define SB_X_INDENT (20 * ((float)ScreenHeight / 640)) +#define SB_Y_INDENT (20 * ((float)ScreenHeight / 480)) + +class ServerBrowser : public CTransparentPanel +{ +private: + HeaderPanel * _headerPanel; + TablePanel* _tablePanel; + + CommandButton* _connectButton; + CommandButton* _refreshButton; + CommandButton* _broadcastRefreshButton; + CommandButton* _stopButton; + CommandButton* _sortButton; + CommandButton* _cancelButton; + + CommandButton* _pingButton; + +public: + ServerBrowser(int x,int y,int wide,int tall); +public: + virtual void setSize(int wide,int tall); +}; + + + +#endif diff --git a/dmc/cl_dll/vgui_SpectatorPanel.cpp b/dmc/cl_dll/vgui_SpectatorPanel.cpp new file mode 100644 index 0000000..e3a85d6 --- /dev/null +++ b/dmc/cl_dll/vgui_SpectatorPanel.cpp @@ -0,0 +1,204 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// vgui_SpectatorPanel.cpp: implementation of the SpectatorPanel class. +// +////////////////////////////////////////////////////////////////////// + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "pm_shared.h" +#include "vgui_viewport.h" +#include "vgui_SpectatorPanel.h" + + + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +SpectatorPanel::SpectatorPanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) +{ +} + +SpectatorPanel::~SpectatorPanel() +{ + +} + +void SpectatorPanel::ActionSignal(int cmd) +{ + switch (cmd) + { + case SPECTATOR_PANEL_CMD_NONE : break; + + case SPECTATOR_PANEL_CMD_OPTIONS : gViewPort->ShowCommandMenu( gViewPort->m_SpectatorMenu ); + break; + + case SPECTATOR_PANEL_CMD_NEXTPLAYER : gHUD.m_Spectator.FindNextPlayer(true); + break; + + case SPECTATOR_PANEL_CMD_PREVPLAYER : gHUD.m_Spectator.FindNextPlayer(false); + break; + + case SPECTATOR_PANEL_CMD_HIDEMENU : ShowMenu(false); + break; + + + + case SPECTATOR_PANEL_CMD_TOGGLE_INSET : gHUD.m_Spectator.SetModes( -1, + gHUD.m_Spectator.ToggleInset(false) ); + break; + + + default : gEngfuncs.Con_DPrintf("Unknown SpectatorPanel ActionSingal %i.\n",cmd); break; + } + +} + + +void SpectatorPanel::Initialize() +{ + int x,y,wide,tall; + + getBounds(x,y,wide,tall); + + CSchemeManager * pSchemes = gViewPort->GetSchemeManager(); + + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle( "Title Font" ); + + m_TopBorder = new CTransparentPanel(64, 0, 0, ScreenWidth, YRES(32)); + m_TopBorder->setParent(this); + + m_BottomBorder = new CTransparentPanel(64, 0, ScreenHeight - YRES(32), ScreenWidth, YRES(32)); + m_BottomBorder->setParent(this); + + setPaintBackgroundEnabled(false); + + // Initialize the bottom title. + m_BottomMainLabel = new Label( "Spectator Bottom", XRES(6+64+6+24+6), YRES(4), XRES(428), YRES(24) ); + m_BottomMainLabel->setParent(m_BottomBorder); + m_BottomMainLabel->setFont( pSchemes->getFont( hTitleScheme ) ); + m_BottomMainLabel->setPaintBackgroundEnabled(false); + m_BottomMainLabel->setFgColor( Scheme::sc_primary1 ); + m_BottomMainLabel->setContentAlignment( vgui::Label::a_center ); + + LineBorder * border = new LineBorder(1, Scheme::sc_secondary1); + m_BottomMainLabel->setBorder(border); + m_BottomMainLabel->setPaintBorderEnabled(true); + + // Initialize the top title. + m_TopMainLabel = new Label( "Spectator Top", 0, 0, wide, YRES(32) ); + m_TopMainLabel->setParent(m_TopBorder); + m_TopMainLabel->setFont( pSchemes->getFont( hTitleScheme ) ); + m_TopMainLabel->setPaintBackgroundEnabled(false); + m_TopMainLabel->setFgColor( Scheme::sc_primary1 ); + m_TopMainLabel->setContentAlignment( vgui::Label::a_center ); + + // Initialize command buttons. + m_OptionButton = new CommandButton("Options", XRES(6), YRES(6), XRES(64), YRES(20) ); + m_OptionButton->setParent( m_BottomBorder ); + m_OptionButton->setContentAlignment( vgui::Label::a_center ); + m_OptionButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_OptionButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_OPTIONS) ); + + m_PrevPlayerButton= new CommandButton("<<", XRES(6+64+6), YRES(6), XRES(24), YRES(20) ); + m_PrevPlayerButton->setParent( m_BottomBorder ); + m_PrevPlayerButton->setContentAlignment( vgui::Label::a_center ); + m_PrevPlayerButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_PrevPlayerButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_PREVPLAYER) ); + + m_NextPlayerButton= new CommandButton(">>", XRES(640-6-64-6-24), YRES(6), XRES(24), YRES(20) ); + m_NextPlayerButton->setParent( m_BottomBorder ); + m_NextPlayerButton->setContentAlignment( vgui::Label::a_center ); + m_NextPlayerButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_NextPlayerButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_NEXTPLAYER) ); + + m_HideButton = new CommandButton("Hide", XRES(640-6-64), YRES(6), XRES(64), YRES(20) ); + m_HideButton->setParent( m_BottomBorder ); + m_HideButton->setContentAlignment( vgui::Label::a_center ); + m_HideButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_HideButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_HIDEMENU) ); + + + m_InsetViewButton = new CommandButton("", XRES(2), YRES(2), XRES(240), YRES(180) ); + m_InsetViewButton->setParent( this ); + m_InsetViewButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_TOGGLE_INSET) ); + + + m_menuVisible = false; + m_HideButton->setVisible(false); + m_OptionButton->setVisible(false); + m_NextPlayerButton->setVisible(false); + m_PrevPlayerButton->setVisible(false); + m_BottomMainLabel->setPaintBorderEnabled(false); + m_TopMainLabel->setVisible(false); + +} + +void SpectatorPanel::ShowMenu(bool isVisible) +{ + m_HideButton->setVisible(isVisible); + m_OptionButton->setVisible(isVisible); + m_NextPlayerButton->setVisible(isVisible); + m_PrevPlayerButton->setVisible(isVisible); + m_BottomMainLabel->setPaintBorderEnabled(isVisible); + m_TopMainLabel->setVisible(isVisible); + + if ( !isVisible ) + { + gViewPort->HideCommandMenu(); + + // if switching from visible menu to invisible menu, show help text + if ( m_menuVisible && this->isVisible() ) + { + char string[ 64 ]; + + _snprintf( string, sizeof( string ) - 1, "%c%s", HUD_PRINTCENTER, CHudTextMessage::BufferedLocaliseTextString( "#Spec_Duck" ) ); + string[ sizeof( string ) - 1 ] = '\0'; + + gHUD.m_TextMessage.MsgFunc_TextMsg( NULL, strlen( string ) + 1, string ); + } + } + + m_menuVisible = isVisible; + + gViewPort->UpdateCursorState(); +} + +void SpectatorPanel::EnableInsetView(bool isEnabled) +{ + int x = gHUD.m_Spectator.m_OverviewData.insetWindowX; + int y = gHUD.m_Spectator.m_OverviewData.insetWindowY; + int wide = gHUD.m_Spectator.m_OverviewData.insetWindowWidth; + int tall = gHUD.m_Spectator.m_OverviewData.insetWindowHeight; + + if ( isEnabled ) + { + // short black bar to see full inset + m_TopBorder->setBounds( XRES(x+wide+2), 0, XRES(640 - (x+wide+2) ), YRES(32) ); + + m_TopMainLabel->setBounds( 0, 0, XRES(640 - (x+wide+2)), YRES(32) ); + + m_InsetViewButton->setBounds( XRES( x ), YRES( y ), + XRES( wide ), YRES( tall ) ); + m_InsetViewButton->setVisible(true); + } + else + { + // full black bar, no inset border + m_TopBorder->setBounds( 0, 0, ScreenWidth, YRES(32) ); + m_TopMainLabel->setBounds( 0, 0, ScreenWidth, YRES(32) ); + m_InsetViewButton->setVisible(false); + } +} + + + diff --git a/dmc/cl_dll/vgui_SpectatorPanel.h b/dmc/cl_dll/vgui_SpectatorPanel.h new file mode 100644 index 0000000..ee1f712 --- /dev/null +++ b/dmc/cl_dll/vgui_SpectatorPanel.h @@ -0,0 +1,93 @@ + +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// vgui_SpectatorPanel.h: interface for the SpectatorPanel class. +// +////////////////////////////////////////////////////////////////////// + +#ifndef SPECTATORPANEL_H +#define SPECTATORPANEL_H + +#include +#include +#include + +using namespace vgui; + +#define SPECTATOR_PANEL_CMD_NONE 0 + +#define SPECTATOR_PANEL_CMD_OPTIONS 1 +#define SPECTATOR_PANEL_CMD_PREVPLAYER 2 +#define SPECTATOR_PANEL_CMD_NEXTPLAYER 3 +#define SPECTATOR_PANEL_CMD_HIDEMENU 4 +#define SPECTATOR_PANEL_CMD_TOGGLE_INSET 5 + + +class SpectatorPanel : public Panel //, public vgui::CDefaultInputSignal +{ + +public: + SpectatorPanel(int x,int y,int wide,int tall); + virtual ~SpectatorPanel(); + + void ActionSignal(int cmd); + + // InputSignal overrides. +public: + void Initialize(); + + + + +public: + + void EnableInsetView(bool isEnabled); + void ShowMenu(bool isVisible); + + + CommandButton * m_OptionButton; + CommandButton * m_HideButton; + CommandButton * m_PrevPlayerButton; + CommandButton * m_NextPlayerButton; + + CTransparentPanel * m_TopBorder; + CTransparentPanel * m_BottomBorder; + + CommandButton * m_InsetViewButton; + + Label * m_TopMainLabel; + Label * m_BottomMainLabel; + + + bool m_menuVisible; +}; + + + +class CSpectatorHandler_Command : public ActionSignal +{ + +private: + SpectatorPanel * m_pFather; + int m_cmd; + +public: + CSpectatorHandler_Command( SpectatorPanel * panel, int cmd ) + { + m_pFather = panel; + m_cmd = cmd; + } + + virtual void actionPerformed( Panel * panel ) + { + m_pFather->ActionSignal(m_cmd); + } +}; + + +#endif // !defined SPECTATORPANEL_H diff --git a/dmc/cl_dll/vgui_int.cpp b/dmc/cl_dll/vgui_int.cpp new file mode 100644 index 0000000..0f4b9e5 --- /dev/null +++ b/dmc/cl_dll/vgui_int.cpp @@ -0,0 +1,128 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include"vgui_int.h" +#include +#include +#include +#include +#include +#include +#include +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "vgui_viewport.h" +#include "vgui_ControlConfigPanel.h" + +namespace +{ + +class TexturePanel : public Panel , public ActionSignal +{ +private: + int _bindIndex; + TextEntry* _textEntry; +public: + TexturePanel() : Panel(0,0,256,276) + { + _bindIndex=2700; + _textEntry=new TextEntry("2700",0,0,128,20); + _textEntry->setParent(this); + _textEntry->addActionSignal(this); + } +public: + virtual bool isWithin(int x,int y) + { + return _textEntry->isWithin(x,y); + } +public: + virtual void actionPerformed(Panel* panel) + { + char buf[256]; + _textEntry->getText(0,buf,256); + sscanf(buf,"%d",&_bindIndex); + } +protected: + virtual void paintBackground() + { + Panel::paintBackground(); + + int wide,tall; + getPaintSize(wide,tall); + + drawSetColor(0,0,255,0); + drawSetTexture(_bindIndex); + drawTexturedRect(0,19,257,257); + } + +}; + +} + +using namespace vgui; + +void VGui_ViewportPaintBackground(int extents[4]) +{ + gEngfuncs.VGui_ViewportPaintBackground(extents); +} + +void* VGui_GetPanel() +{ + return (Panel*)gEngfuncs.VGui_GetPanel(); +} + +void VGui_Startup() +{ + Panel* root=(Panel*)VGui_GetPanel(); + root->setBgColor(128,128,0,0); + //root->setNonPainted(false); + //root->setBorder(new LineBorder()); + root->setLayout(new BorderLayout(0)); + + + //root->getSurfaceBase()->setEmulatedCursorVisible(true); + + if (gViewPort != NULL) + { +// root->removeChild(gViewPort); + + // free the memory +// delete gViewPort; +// gViewPort = NULL; + + gViewPort->Initialize(); + } + else + { + gViewPort = new TeamFortressViewport(0,0,root->getWide(),root->getTall()); + gViewPort->setParent(root); + } + + /* + TexturePanel* texturePanel=new TexturePanel(); + texturePanel->setParent(gViewPort); + */ + +} + +void VGui_Shutdown() +{ + delete gViewPort; + gViewPort = NULL; +} + + + + + diff --git a/dmc/cl_dll/vgui_int.h b/dmc/cl_dll/vgui_int.h new file mode 100644 index 0000000..6510853 --- /dev/null +++ b/dmc/cl_dll/vgui_int.h @@ -0,0 +1,21 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_INT_H +#define VGUI_INT_H + +extern "C" +{ +void VGui_Startup(); +void VGui_Shutdown(); + +//Only safe to call from inside subclass of Panel::paintBackground +void VGui_ViewportPaintBackground(int extents[4]); +} + + +#endif \ No newline at end of file diff --git a/dmc/cl_dll/vgui_viewport.cpp b/dmc/cl_dll/vgui_viewport.cpp new file mode 100644 index 0000000..a7c8746 --- /dev/null +++ b/dmc/cl_dll/vgui_viewport.cpp @@ -0,0 +1,1983 @@ +//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: Client DLL VGUI Viewport +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "pm_shared.h" +#include "parsemsg.h" +#include "keydefs.h" +#include "demo.h" +#include "demo_api.h" + +#include "vgui_int.h" +#include "vgui_viewport.h" +#include "vgui_ServerBrowser.h" +#include "vgui_ScorePanel.h" +#include "vgui_SpectatorPanel.h" +#include "voice_status.h" + +extern int g_iVisibleMouse; +class CCommandMenu; +int g_iPlayerClass; +int g_iTeamNumber; +int g_iUser1 = 0; +int g_iUser2 = 0; +int g_iUser3 = 0; + +// Scoreboard positions +#define SBOARD_INDENT_X XRES(104) +#define SBOARD_INDENT_Y YRES(40) + +// low-res scoreboard indents +#define SBOARD_INDENT_X_512 30 +#define SBOARD_INDENT_Y_512 30 + +#define SBOARD_INDENT_X_400 0 +#define SBOARD_INDENT_Y_400 20 + + + +void IN_ResetMouse( void ); +extern CMenuPanel* CMessageWindowPanel_Create( const char *szMOTD, const char *szTitle, int iShadeFullscreen, int iRemoveMe, int x, int y, int wide, int tall ); +extern float * GetClientColor( int clientIndex ); + +using namespace vgui; + +// Team Colors +int iTeamColors[5][3] = +{ + { 255, 170, 0 }, // HL Orange + { 66, 115, 247 }, + { 220, 51, 38 }, + { 240, 135, 0 }, + { 115, 240, 115 }, +}; + +// Used for Class specific buttons +char *sTFClasses[] = +{ + "", + "SCOUT", + "SNIPER", + "SOLDIER", + "DEMOMAN", + "MEDIC", + "HWGUY", + "PYRO", + "SPY", + "ENGINEER", + "CIVILIAN", +}; + +char *sLocalisedClasses[] = +{ + "#Civilian", + "#Scout", + "#Sniper", + "#Soldier", + "#Demoman", + "#Medic", + "#HWGuy", + "#Pyro", + "#Spy", + "#Engineer", + "#Random", + "#Civilian", +}; + +char *sTFClassSelection[] = +{ + "civilian", + "scout", + "sniper", + "soldier", + "demoman", + "medic", + "hwguy", + "pyro", + "spy", + "engineer", + "randompc", + "civilian", +}; + + +// Get the name of TGA file, based on GameDir +char* GetVGUITGAName(const char *pszName) +{ + int i; + char sz[256]; + static char gd[256]; + const char *gamedir; + + if (ScreenWidth < 640) + i = 320; + else + i = 640; + sprintf(sz, pszName, i); + + gamedir = gEngfuncs.pfnGetGameDirectory(); + sprintf(gd, "%s/gfx/vgui/%s.tga",gamedir,sz); + + return gd; +} + +//================================================================ +// COMMAND MENU +//================================================================ +void CCommandMenu::AddButton( CommandButton *pButton ) +{ + if (m_iButtons >= MAX_BUTTONS) + return; + + m_aButtons[m_iButtons] = pButton; + m_iButtons++; + pButton->setParent( this ); + pButton->setFont( Scheme::sf_primary3 ); + + // give the button a default key binding + if ( m_iButtons < 10 ) + { + pButton->setBoundKey( m_iButtons + '0' ); + } + else if ( m_iButtons == 10 ) + { + pButton->setBoundKey( '0' ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Tries to find a button that has a key bound to the input, and +// presses the button if found +// Input : keyNum - the character number of the input key +// Output : Returns true if the command menu should close, false otherwise +//----------------------------------------------------------------------------- +bool CCommandMenu::KeyInput( int keyNum ) +{ + // loop through all our buttons looking for one bound to keyNum + for ( int i = 0; i < m_iButtons; i++ ) + { + if ( !m_aButtons[i]->IsNotValid() ) + { + if ( m_aButtons[i]->getBoundKey() == keyNum ) + { + // hit the button + if ( m_aButtons[i]->GetSubMenu() ) + { + // open the sub menu + gViewPort->SetCurrentCommandMenu( m_aButtons[i]->GetSubMenu() ); + return false; + } + else + { + // run the bound command + m_aButtons[i]->fireActionSignal(); + return true; + } + } + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: clears the current menus buttons of any armed (highlighted) +// state, and all their sub buttons +//----------------------------------------------------------------------------- +void CCommandMenu::ClearButtonsOfArmedState( void ) +{ + for ( int i = 0; i < GetNumButtons(); i++ ) + { + m_aButtons[i]->setArmed( false ); + + if ( m_aButtons[i]->GetSubMenu() ) + { + m_aButtons[i]->GetSubMenu()->ClearButtonsOfArmedState(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pSubMenu - +// Output : CommandButton +//----------------------------------------------------------------------------- +CommandButton *CCommandMenu::FindButtonWithSubmenu( CCommandMenu *pSubMenu ) +{ + for ( int i = 0; i < GetNumButtons(); i++ ) + { + if ( m_aButtons[i]->GetSubMenu() == pSubMenu ) + return m_aButtons[i]; + } + + return NULL; +} + +// Recalculate the visible buttons +bool CCommandMenu::RecalculateVisibles( int iYOffset, bool bHideAll ) +{ + int i, iCurrentY = 0; + int iVisibleButtons = 0; + + // Cycle through all the buttons in this menu, and see which will be visible + for (i = 0; i < m_iButtons; i++) + { + int iClass = m_aButtons[i]->GetPlayerClass(); + + if ( (iClass && iClass != g_iPlayerClass ) || ( m_aButtons[i]->IsNotValid() ) || bHideAll ) + { + m_aButtons[i]->setVisible( false ); + if ( m_aButtons[i]->GetSubMenu() != NULL ) + { + (m_aButtons[i]->GetSubMenu())->RecalculateVisibles( 0, true ); + } + } + else + { + // If it's got a submenu, force it to check visibilities + if ( m_aButtons[i]->GetSubMenu() != NULL ) + { + if ( !(m_aButtons[i]->GetSubMenu())->RecalculateVisibles( 0 , false ) ) + { + // The submenu had no visible buttons, so don't display this button + m_aButtons[i]->setVisible( false ); + continue; + } + } + + m_aButtons[i]->setVisible( true ); + iVisibleButtons++; + } + } + + // Set Size + setSize( _size[0], (iVisibleButtons * (BUTTON_SIZE_Y-1)) + 1 ); + + if ( iYOffset ) + { + m_iYOffset = iYOffset; + } + + for (i = 0; i < m_iButtons; i++) + { + if ( m_aButtons[i]->isVisible() ) + { + if ( m_aButtons[i]->GetSubMenu() != NULL ) + (m_aButtons[i]->GetSubMenu())->RecalculateVisibles( iCurrentY + m_iYOffset, false ); + + + // Make sure it's at the right Y position + // m_aButtons[i]->getPos( iXPos, iYPos ); + + if ( m_iDirection ) + { + m_aButtons[i]->setPos( 0, (iVisibleButtons-1) * (BUTTON_SIZE_Y-1) - iCurrentY ); + } + else + { + m_aButtons[i]->setPos( 0, iCurrentY ); + } + + iCurrentY += (BUTTON_SIZE_Y-1); + } + } + + return iVisibleButtons?true:false; +} + +// Make sure all submenus can fit on the screen +void CCommandMenu::RecalculatePositions( int iYOffset ) +{ + int iTop; + int iAdjust = 0; + + m_iYOffset+= iYOffset; + + if ( m_iDirection ) + iTop = ScreenHeight - (m_iYOffset + _size[1] ); + else + iTop = m_iYOffset; + + if ( iTop < 0 ) + iTop = 0; + + // Calculate if this is going to fit onscreen, and shuffle it up if it won't + int iBottom = iTop + _size[1]; + + if ( iBottom > ScreenHeight ) + { + // Move in increments of button sizes + while (iAdjust < (iBottom - ScreenHeight)) + { + iAdjust += BUTTON_SIZE_Y - 1; + } + + iTop -= iAdjust; + + // Make sure it doesn't move off the top of the screen (the menu's too big to fit it all) + if ( iTop < 0 ) + { + iAdjust -= (0 - iTop); + iTop = 0; + } + } + + setPos( _pos[0], iTop ); + + // We need to force all menus below this one to update their positions now, because they + // might have submenus riding off buttons in this menu that have just shifted. + for (int i = 0; i < m_iButtons; i++) + m_aButtons[i]->UpdateSubMenus( iAdjust ); +} + + +// Make this menu and all menus above it in the chain visible +void CCommandMenu::MakeVisible( CCommandMenu *pChildMenu ) +{ +/* + // Push down the button leading to the child menu + for (int i = 0; i < m_iButtons; i++) + { + if ( (pChildMenu != NULL) && (m_aButtons[i]->GetSubMenu() == pChildMenu) ) + { + m_aButtons[i]->setArmed( true ); + } + else + { + m_aButtons[i]->setArmed( false ); + } + } +*/ + + setVisible(true); + if (m_pParentMenu) + m_pParentMenu->MakeVisible( this ); +} + +//================================================================ +// CreateSubMenu +CCommandMenu *TeamFortressViewport::CreateSubMenu( CommandButton *pButton, CCommandMenu *pParentMenu, int iOffsetY ) +{ + int iXPos = 0; + int iYPos = 0; + int iWide = CMENU_SIZE_X; + int iTall = 0; + int iDirection = 0; + + if (pParentMenu) + { + iXPos = m_pCurrentCommandMenu->GetXOffset() + (CMENU_SIZE_X - 1); + iYPos = m_pCurrentCommandMenu->GetYOffset() + iOffsetY; + iDirection = pParentMenu->GetDirection(); + } + + CCommandMenu *pMenu = new CCommandMenu(pParentMenu, iDirection, iXPos, iYPos, iWide, iTall ); + pMenu->setParent(this); + pButton->AddSubMenu( pMenu ); + pButton->setFont( Scheme::sf_primary3 ); + + // Create the Submenu-open signal + InputSignal *pISignal = new CMenuHandler_PopupSubMenuInput(pButton, pMenu); + pButton->addInputSignal(pISignal); + + // Put a > to show it's a submenu + CImageLabel *pLabel = new CImageLabel( "arrow", CMENU_SIZE_X - SUBMENU_SIZE_X, SUBMENU_SIZE_Y ); + pLabel->setParent(pButton); + pLabel->addInputSignal(pISignal); + + // Reposition + pLabel->getPos( iXPos, iYPos ); + pLabel->setPos( CMENU_SIZE_X - pLabel->getImageWide(), (BUTTON_SIZE_Y - pLabel->getImageTall()) / 2 ); + + // Create the mouse off signal for the Label too + if (!pButton->m_bNoHighlight) + pLabel->addInputSignal( new CHandler_CommandButtonHighlight(pButton) ); + + return pMenu; +} + +//----------------------------------------------------------------------------- +// Purpose: Makes sure the memory allocated for TeamFortressViewport is nulled out +// Input : stAllocateBlock - +// Output : void * +//----------------------------------------------------------------------------- +void *TeamFortressViewport::operator new( size_t stAllocateBlock ) +{ +// void *mem = Panel::operator new( stAllocateBlock ); + void *mem = ::operator new( stAllocateBlock ); + memset( mem, 0, stAllocateBlock ); + return mem; +} + +//----------------------------------------------------------------------------- +// Purpose: InputSignal handler for the main viewport +//----------------------------------------------------------------------------- +class CViewPortInputHandler : public InputSignal +{ +public: + bool bPressed; + + CViewPortInputHandler() + { + } + + virtual void cursorMoved(int x,int y,Panel* panel) {} + virtual void cursorEntered(Panel* panel) {} + virtual void cursorExited(Panel* panel) {} + virtual void mousePressed(MouseCode code,Panel* panel) + { + if ( code != MOUSE_LEFT ) + { + // send a message to close the command menu + // this needs to be a message, since a direct call screws the timing + gEngfuncs.pfnClientCmd( "ForceCloseCommandMenu\n" ); + } + } + virtual void mouseReleased(MouseCode code,Panel* panel) + { + } + + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {} + virtual void mouseWheeled(int delta,Panel* panel) {} + virtual void keyPressed(KeyCode code,Panel* panel) {} + virtual void keyTyped(KeyCode code,Panel* panel) {} + virtual void keyReleased(KeyCode code,Panel* panel) {} + virtual void keyFocusTicked(Panel* panel) {} +}; + + +//================================================================ +TeamFortressViewport::TeamFortressViewport(int x,int y,int wide,int tall) : Panel(x,y,wide,tall), m_SchemeManager(wide,tall) +{ + gViewPort = this; + m_iInitialized = false; +// m_pTeamMenu = NULL; +// m_pClassMenu = NULL; + m_pScoreBoard = NULL; + m_pSpectatorPanel = NULL; + m_pCurrentMenu = NULL; + m_pCurrentCommandMenu = NULL; + + CVAR_CREATE( "hud_classautokill", "1", FCVAR_ARCHIVE ); // controls whether or not to suicide immediately on TF class switch + CVAR_CREATE( "hud_takesshots", "0", FCVAR_ARCHIVE ); // controls whether or not to automatically take screenshots at the end of a round + + Initialize(); + addInputSignal( new CViewPortInputHandler ); + + int r, g, b, a; + + Scheme* pScheme = App::getInstance()->getScheme(); + + // primary text color + // Get the colors + //!! two different types of scheme here, need to integrate + SchemeHandle_t hPrimaryScheme = m_SchemeManager.getSchemeHandle( "Primary Button Text" ); + { + // font + pScheme->setFont( Scheme::sf_primary1, m_SchemeManager.getFont(hPrimaryScheme) ); + + // text color + m_SchemeManager.getFgColor( hPrimaryScheme, r, g, b, a ); + pScheme->setColor(Scheme::sc_primary1, r, g, b, a ); // sc_primary1 is non-transparent orange + + // background color (transparent black) + m_SchemeManager.getBgColor( hPrimaryScheme, r, g, b, a ); + pScheme->setColor(Scheme::sc_primary3, r, g, b, a ); + + // armed foreground color + m_SchemeManager.getFgArmedColor( hPrimaryScheme, r, g, b, a ); + pScheme->setColor(Scheme::sc_secondary2, r, g, b, a ); + + // armed background color + m_SchemeManager.getBgArmedColor( hPrimaryScheme, r, g, b, a ); + pScheme->setColor(Scheme::sc_primary2, r, g, b, a ); + + //!! need to get this color from scheme file + // used for orange borders around buttons + m_SchemeManager.getBorderColor( hPrimaryScheme, r, g, b, a ); + // pScheme->setColor(Scheme::sc_secondary1, r, g, b, a ); + pScheme->setColor(Scheme::sc_secondary1, 255*0.7, 170*0.7, 0, 0); + } + + // Change the second primary font (used in the scoreboard) + SchemeHandle_t hScoreboardScheme = m_SchemeManager.getSchemeHandle( "Scoreboard Text" ); + { + pScheme->setFont(Scheme::sf_primary2, m_SchemeManager.getFont(hScoreboardScheme) ); + } + + // Change the third primary font (used in command menu) + SchemeHandle_t hCommandMenuScheme = m_SchemeManager.getSchemeHandle( "CommandMenu Text" ); + { + pScheme->setFont(Scheme::sf_primary3, m_SchemeManager.getFont(hCommandMenuScheme) ); + } + + App::getInstance()->setScheme(pScheme); + + // VGUI MENUS + CreateTeamMenu(); + CreateClassMenu(); + CreateSpectatorMenu(); + CreateScoreBoard(); + // Init command menus + m_iNumMenus = 0; + m_iCurrentTeamNumber = m_iUser1 = m_iUser2 = 0; + m_StandardMenu = CreateCommandMenu("commandmenu.txt", 0, CMENU_TOP); + m_SpectatorMenu = CreateCommandMenu("spectatormenu.txt", 1, YRES(32) ); // above bottom bar + + CreateServerBrowser(); + +} + +//----------------------------------------------------------------------------- +// Purpose: Called everytime a new level is started. Viewport clears out it's data. +//----------------------------------------------------------------------------- +void TeamFortressViewport::Initialize( void ) +{ + // Force each menu to Initialize +/* if (m_pTeamMenu) + { + m_pTeamMenu->Initialize(); + } + if (m_pClassMenu) + { + m_pClassMenu->Initialize(); + }*/ + if (m_pScoreBoard) + { + m_pScoreBoard->Initialize(); + HideScoreBoard(); + } + if (m_pSpectatorPanel) + { + // Spectator menu doesn't need initializing + m_pSpectatorPanel->setVisible( false ); + } + + // Make sure all menus are hidden + HideVGUIMenu(); + HideCommandMenu(); + + // Clear out some data + m_iGotAllMOTD = true; + m_iRandomPC = false; + m_flScoreBoardLastUpdated = 0; + + // reset player info + g_iPlayerClass = 0; + g_iTeamNumber = 0; + + strcpy(m_sMapName, ""); + strcpy(m_szServerName, ""); + for (int i = 0; i < 5; i++) + { + m_iValidClasses[i] = 0; + strcpy(m_sTeamNames[i], ""); + } + + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::scu_none) ); +} + +class CException; +//----------------------------------------------------------------------------- +// Purpose: Read the Command Menu structure from the txt file and create the menu. +// Returns Index of menu in m_pCommandMenus +//----------------------------------------------------------------------------- +int TeamFortressViewport::CreateCommandMenu( char * menuFile, int direction, int yOffset ) +{ + // COMMAND MENU + // Create the root of this new Command Menu + + int newIndex = m_iNumMenus; + + m_pCommandMenus[newIndex] = new CCommandMenu(NULL, direction, 0, yOffset, CMENU_SIZE_X, 300); // This will be resized once we know how many items are in it + m_pCommandMenus[newIndex]->setParent(this); + m_pCommandMenus[newIndex]->setVisible(false); + + m_iNumMenus++; + + // Read Command Menu from the txt file + char token[1024]; + char *pfile = (char*)gEngfuncs.COM_LoadFile( menuFile, 5, NULL); + if (!pfile) + { + gEngfuncs.Con_DPrintf( "Unable to open %s\n", menuFile); + SetCurrentCommandMenu( NULL ); + return newIndex; + } + +#ifdef _WIN32 +try +{ +#endif + // First, read in the localisation strings + + // Detpack strings + gHUD.m_TextMessage.LocaliseTextString( "#DetpackSet_For5Seconds", m_sDetpackStrings[0], MAX_BUTTON_SIZE ); + gHUD.m_TextMessage.LocaliseTextString( "#DetpackSet_For20Seconds", m_sDetpackStrings[1], MAX_BUTTON_SIZE ); + gHUD.m_TextMessage.LocaliseTextString( "#DetpackSet_For50Seconds", m_sDetpackStrings[2], MAX_BUTTON_SIZE ); + + // Now start parsing the menu structure + m_pCurrentCommandMenu = m_pCommandMenus[newIndex]; + char szLastButtonText[32] = "file start"; + pfile = gEngfuncs.COM_ParseFile(pfile, token); + while ( ( strlen ( token ) > 0 ) && ( m_iNumMenus < MAX_MENUS ) ) + { + // Keep looping until we hit the end of this menu + while ( token[0] != '}' && ( strlen( token ) > 0 ) ) + { + char cText[32] = ""; + char cBoundKey[32] = ""; + char cCustom[32] = ""; + static const int cCommandLength = 128; + char cCommand[cCommandLength] = ""; + char szMap[MAX_MAPNAME] = ""; + int iPlayerClass = 0; + int iCustom = false; + int iTeamOnly = -1; + int iToggle = 0; + int iButtonY; + bool bGetExtraToken = true; + CommandButton *pButton = NULL; + + // We should never be here without a Command Menu + if (!m_pCurrentCommandMenu) + { + gEngfuncs.Con_Printf("Error in %s file after '%s'.\n",menuFile, szLastButtonText ); + m_iInitialized = false; + return newIndex; + } + + // token should already be the bound key, or the custom name + strncpy( cCustom, token, 32 ); + cCustom[31] = '\0'; + + // See if it's a custom button + if (!strcmp(cCustom, "CUSTOM") ) + { + iCustom = true; + + // Get the next token + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + // See if it's a map + else if (!strcmp(cCustom, "MAP") ) + { + // Get the mapname + pfile = gEngfuncs.COM_ParseFile(pfile, token); + strncpy( szMap, token, MAX_MAPNAME ); + szMap[MAX_MAPNAME-1] = '\0'; + + // Get the next token + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + else if ( !strncmp(cCustom, "TEAM", 4) ) // TEAM1, TEAM2, TEAM3, TEAM4 + { + // make it a team only button + iTeamOnly = atoi( cCustom + 4 ); + + // Get the next token + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + else if ( !strncmp(cCustom, "TOGGLE", 6) ) + { + iToggle = true; + // Get the next token + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + + // Get the button bound key + strncpy( cBoundKey, token, 32 ); + cText[31] = '\0'; + + // Get the button text + pfile = gEngfuncs.COM_ParseFile(pfile, token); + strncpy( cText, token, 32 ); + cText[31] = '\0'; + + // save off the last button text we've come across (for error reporting) + strcpy( szLastButtonText, cText ); + + // Get the button command + pfile = gEngfuncs.COM_ParseFile(pfile, token); + strncpy( cCommand, token, cCommandLength ); + cCommand[cCommandLength - 1] = '\0'; + + iButtonY = (BUTTON_SIZE_Y-1) * m_pCurrentCommandMenu->GetNumButtons(); + + // Custom button handling + if ( iCustom ) + { + pButton = CreateCustomButton( cText, cCommand, iButtonY ); + + // Get the next token to see if we're a menu + pfile = gEngfuncs.COM_ParseFile(pfile, token); + + if ( token[0] == '{' ) + { + strcpy( cCommand, token ); + } + else + { + bGetExtraToken = false; + } + } + else if ( szMap[0] != '\0' ) + { + // create a map button + pButton = new MapButton(szMap, cText, 0, iButtonY, CMENU_SIZE_X, BUTTON_SIZE_Y); + } + else if ( iTeamOnly != -1) + { + // button that only shows up if the player is on team iTeamOnly + pButton = new TeamOnlyCommandButton( iTeamOnly, cText, 0, iButtonY, CMENU_SIZE_X, BUTTON_SIZE_Y ); + } + else if ( iToggle ) + { + pButton = new ToggleCommandButton( cCommand, cText,0, iButtonY, CMENU_SIZE_X, BUTTON_SIZE_Y ); + } + else + { + // normal button + pButton = new CommandButton( iPlayerClass, cText, 0, iButtonY, CMENU_SIZE_X, BUTTON_SIZE_Y ); + } + + // add the button into the command menu + if ( pButton ) + { + m_pCurrentCommandMenu->AddButton( pButton ); + pButton->setBoundKey( cBoundKey[0] ); + pButton->setParentMenu( m_pCurrentCommandMenu ); + + // Override font in CommandMenu + pButton->setFont( Scheme::sf_primary3 ); + } + + // Find out if it's a submenu or a button we're dealing with + if ( cCommand[0] == '{' ) + { + if ( m_iNumMenus >= MAX_MENUS ) + { + gEngfuncs.Con_Printf( "Too many menus in %s past '%s'\n",menuFile, szLastButtonText ); + } + else + { + // Create the menu + m_pCommandMenus[m_iNumMenus] = CreateSubMenu(pButton, m_pCurrentCommandMenu, iButtonY ); + m_pCurrentCommandMenu = m_pCommandMenus[m_iNumMenus]; + m_iNumMenus++; + } + } + else if ( !iCustom ) + { + // Create the button and attach it to the current menu + if ( iToggle ) + pButton->addActionSignal(new CMenuHandler_ToggleCvar(cCommand)); + else + pButton->addActionSignal(new CMenuHandler_StringCommand(cCommand)); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + + // Get the next token + if ( bGetExtraToken ) + { + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + } + + // Move back up a menu + m_pCurrentCommandMenu = m_pCurrentCommandMenu->GetParentMenu(); + + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } +#ifdef _WIN32 +} +catch( CException *e ) +{ + e; + //e->Delete(); + e = NULL; + m_iInitialized = false; + return newIndex; +} +#endif + SetCurrentMenu( NULL ); + SetCurrentCommandMenu( NULL ); + gEngfuncs.COM_FreeFile( pfile ); + + m_iInitialized = true; + return newIndex; +} + +//----------------------------------------------------------------------------- +// Purpose: Creates all the class choices under a spy's disguise menus, and +// maps a command to them +// Output : CCommandMenu +//----------------------------------------------------------------------------- + +CCommandMenu *TeamFortressViewport::CreateDisguiseSubmenu( CommandButton *pButton, CCommandMenu *pParentMenu, const char *commandText, int iYOffset ) +{ + // create the submenu, under which the class choices will be listed + CCommandMenu *pMenu = CreateSubMenu( pButton, pParentMenu, iYOffset ); + m_pCommandMenus[m_iNumMenus] = pMenu; + m_iNumMenus++; + + return pMenu; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pButtonText - +// *pButtonName - +// Output : CommandButton +//----------------------------------------------------------------------------- +CommandButton *TeamFortressViewport::CreateCustomButton( char *pButtonText, char *pButtonName, int iYOffset ) +{ + CommandButton *pButton = NULL; + CCommandMenu *pMenu = NULL; + + // ChangeTeam + if ( !strcmp( pButtonName, "!CHANGETEAM" ) ) + { + // ChangeTeam Submenu + pButton = new CommandButton(pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + + // Create the submenu + pMenu = CreateSubMenu(pButton, m_pCurrentCommandMenu, iYOffset ); + m_pCommandMenus[m_iNumMenus] = pMenu; + m_iNumMenus++; + + // ChangeTeam buttons + for (int i = 0; i < 4; i++) + { + char sz[256]; + sprintf(sz, "jointeam %d", i+1); + m_pTeamButtons[i] = new TeamButton(i+1, "teamname", 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + m_pTeamButtons[i]->addActionSignal(new CMenuHandler_StringCommandWatch( sz )); + pMenu->AddButton( m_pTeamButtons[i] ); + } + + // Auto Assign button + m_pTeamButtons[4] = new TeamButton(5, gHUD.m_TextMessage.BufferedLocaliseTextString( "#Team_AutoAssign" ), 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + m_pTeamButtons[4]->addActionSignal(new CMenuHandler_StringCommand( "jointeam 5" )); + pMenu->AddButton( m_pTeamButtons[4] ); + + // Spectate button + m_pTeamButtons[5] = new SpectateButton( CHudTextMessage::BufferedLocaliseTextString( "#Menu_Spectate" ), 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y, false); + m_pTeamButtons[5]->addActionSignal(new CMenuHandler_StringCommand( "spectate" )); + pMenu->AddButton( m_pTeamButtons[5] ); + } + + else if ( !strcmp( pButtonName, "!MAPBRIEFING" ) ) + { + pButton = new CommandButton(pButtonText, 0, BUTTON_SIZE_Y * m_pCurrentCommandMenu->GetNumButtons(), CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_TextWindow(MENU_MAPBRIEFING)); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + + else if ( !strcmp( pButtonName, "!SERVERINFO" ) ) + { + pButton = new ClassButton(0, pButtonText, 0, BUTTON_SIZE_Y * m_pCurrentCommandMenu->GetNumButtons(), CMENU_SIZE_X, BUTTON_SIZE_Y, false); + pButton->addActionSignal(new CMenuHandler_TextWindow(MENU_INTRO)); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + + return pButton; +} + +void TeamFortressViewport::ToggleServerBrowser() +{ + if (!m_iInitialized) + return; + + if ( !m_pServerBrowser ) + return; + + if ( m_pServerBrowser->isVisible() ) + { + m_pServerBrowser->setVisible( false ); + } + else + { + m_pServerBrowser->setVisible( true ); + } + + UpdateCursorState(); +} + +//======================================================================= +//======================================================================= +void TeamFortressViewport::ShowCommandMenu(int menuIndex) +{ + if (!m_iInitialized) + return; + + //Already have a menu open. + if ( m_pCurrentMenu ) + return; + + // is the command menu open? + if ( m_pCurrentCommandMenu == m_pCommandMenus[menuIndex] ) + { + HideCommandMenu(); + return; + } + + // Not visible while in intermission + if ( gHUD.m_iIntermission ) + return; + + // Recalculate visible menus + UpdateCommandMenu( menuIndex ); + HideVGUIMenu(); + + SetCurrentCommandMenu( m_pCommandMenus[menuIndex] ); + m_flMenuOpenTime = gHUD.m_flTime; + UpdateCursorState(); + + // get command menu parameters + for ( int i = 2; i < gEngfuncs.Cmd_Argc(); i++ ) + { + const char *param = gEngfuncs.Cmd_Argv( i - 1 ); + if ( param ) + { + if ( m_pCurrentCommandMenu->KeyInput(param[0]) ) + { + // kill the menu open time, since the key input is final + HideCommandMenu(); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handles the key input of "-commandmenu" +// Input : +//----------------------------------------------------------------------------- +void TeamFortressViewport::InputSignalHideCommandMenu() +{ + if (!m_iInitialized) + return; + + // if they've just tapped the command menu key, leave it open + if ( (m_flMenuOpenTime + 0.3) > gHUD.m_flTime ) + return; + + HideCommandMenu(); +} + +//----------------------------------------------------------------------------- +// Purpose: Hides the command menu +//----------------------------------------------------------------------------- +void TeamFortressViewport::HideCommandMenu() +{ + if (!m_iInitialized) + return; + + if ( m_pCommandMenus[m_StandardMenu] ) + { + m_pCommandMenus[m_StandardMenu]->ClearButtonsOfArmedState(); + } + + if ( m_pCommandMenus[m_SpectatorMenu] ) + { + m_pCommandMenus[m_SpectatorMenu]->ClearButtonsOfArmedState(); + } + + m_flMenuOpenTime = 0.0f; + SetCurrentCommandMenu( NULL ); + UpdateCursorState(); +} + +//----------------------------------------------------------------------------- +// Purpose: Bring up the scoreboard +//----------------------------------------------------------------------------- +void TeamFortressViewport::ShowScoreBoard( void ) +{ + if (m_pScoreBoard) + { + // No Scoreboard in single-player + if ( gEngfuncs.GetMaxClients() > 1 ) + { + m_pScoreBoard->Open(); + UpdateCursorState(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if the scoreboard is up +//----------------------------------------------------------------------------- +bool TeamFortressViewport::IsScoreBoardVisible( void ) +{ + if (m_pScoreBoard) + return m_pScoreBoard->isVisible(); + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Hide the scoreboard +//----------------------------------------------------------------------------- +void TeamFortressViewport::HideScoreBoard( void ) +{ + // Prevent removal of scoreboard during intermission + if ( gHUD.m_iIntermission ) + return; + + if (m_pScoreBoard) + { + m_pScoreBoard->setVisible(false); + + GetClientVoiceMgr()->StopSquelchMode(); + + UpdateCursorState(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Activate's the player special ability +// called when the player hits their "special" key +//----------------------------------------------------------------------------- +void TeamFortressViewport::InputPlayerSpecial( void ) +{ + if (!m_iInitialized) + return; +} + +// Set the submenu of the Command Menu +void TeamFortressViewport::SetCurrentCommandMenu( CCommandMenu *pNewMenu ) +{ + for (int i = 0; i < m_iNumMenus; i++) + m_pCommandMenus[i]->setVisible(false); + + m_pCurrentCommandMenu = pNewMenu; + + if (m_pCurrentCommandMenu) + m_pCurrentCommandMenu->MakeVisible( NULL ); +} + +void TeamFortressViewport::UpdateCommandMenu(int menuIndex) +{ + m_pCommandMenus[menuIndex]->RecalculateVisibles( 0, false ); + m_pCommandMenus[menuIndex]->RecalculatePositions( 0 ); +} + +void TeamFortressViewport::UpdateSpectatorPanel() +{ + m_iUser1 = g_iUser1; + m_iUser2 = g_iUser2; + + if (!m_pSpectatorPanel) + return; + + // check if spectator combinations are still valid + gHUD.m_Spectator.CheckSettings(); + + if ( g_iUser1 && gHUD.m_pCvarDraw->value && !gHUD.m_iIntermission) // don't draw in dev_overview mode + { + char bottomText[128]; + char helpString2[128]; + char tempString[128]; + char * name; + int player = 0; + + if ( !m_pSpectatorPanel->isVisible() ) + { + m_pSpectatorPanel->setVisible( true ); // show spectator panel, but + m_pSpectatorPanel->ShowMenu( false ); // dsiable all menus/buttons + + _snprintf( tempString, sizeof( tempString ) - 1, "%c%s", HUD_PRINTCENTER, CHudTextMessage::BufferedLocaliseTextString( "#Spec_Duck" ) ); + tempString[ sizeof( tempString ) - 1 ] = '\0'; + + gHUD.m_TextMessage.MsgFunc_TextMsg( NULL, strlen( tempString ) + 1, tempString ); + } + + sprintf(bottomText,"#Spec_Mode%d", g_iUser1 ); + sprintf(helpString2,"#Spec_Mode%d", g_iUser1 ); + + if ( gEngfuncs.IsSpectateOnly() ) + strcat(helpString2, " - HLTV"); + + // check if we're locked onto a target, show the player's name + if ( (g_iUser2 > 0) && (g_iUser2 <= gEngfuncs.GetMaxClients()) && (g_iUser1 != OBS_ROAMING) ) + { + player = g_iUser2; + } + + // special case in free map and inset off, don't show names + if ( (g_iUser1 == OBS_MAP_FREE) && !gHUD.m_Spectator.m_pip->value ) + name = NULL; + else + name = g_PlayerInfoList[player].name; + + // create player & health string + if ( player && name ) + { + strcpy( bottomText, name ); + } + + // in first person mode colorize player names + if ( (g_iUser1 == OBS_IN_EYE) && player ) + { + float * color = GetClientColor( player ); + int r = color[0]*255; + int g = color[1]*255; + int b = color[2]*255; + + // set team color, a bit transparent + m_pSpectatorPanel->m_BottomMainLabel->setFgColor(r,g,b,0); + } + else + { // restore GUI color + m_pSpectatorPanel->m_BottomMainLabel->setFgColor( Scheme::sc_primary1 ); + } + + // add sting auto if we are in auto directed mode + if ( gHUD.m_Spectator.m_autoDirector->value ) + { + char tempString[128]; + sprintf(tempString, "#Spec_Auto %s", helpString2); + strcpy( helpString2, tempString ); + } + + m_pSpectatorPanel->m_BottomMainLabel->setText( CHudTextMessage::BufferedLocaliseTextString( bottomText ) ); + m_pSpectatorPanel->m_TopMainLabel->setText( CHudTextMessage::BufferedLocaliseTextString( helpString2 ) ); + + } + else + { + m_pSpectatorPanel->setVisible( false ); + m_pSpectatorPanel->ShowMenu( false ); // dsiable all menus/buttons + } +} + +//====================================================================== +void TeamFortressViewport::CreateScoreBoard( void ) +{ + int xdent = SBOARD_INDENT_X, ydent = SBOARD_INDENT_Y; + if (ScreenWidth == 512) + { + xdent = SBOARD_INDENT_X_512; + ydent = SBOARD_INDENT_Y_512; + } + else if (ScreenWidth == 400) + { + xdent = SBOARD_INDENT_X_400; + ydent = SBOARD_INDENT_Y_400; + } + m_pScoreBoard = new ScorePanel(xdent, ydent, ScreenWidth - (xdent * 2), ScreenHeight - (ydent * 2)); + m_pScoreBoard->setParent(this); + m_pScoreBoard->setVisible(false); +} + +void TeamFortressViewport::CreateServerBrowser( void ) +{ + m_pServerBrowser = new ServerBrowser( 0, 0, ScreenWidth, ScreenHeight ); + m_pServerBrowser->setParent(this); + m_pServerBrowser->setVisible(false); +} + + +//====================================================================== +// Set the VGUI Menu +void TeamFortressViewport::SetCurrentMenu( CMenuPanel *pMenu ) +{ + m_pCurrentMenu = pMenu; + if ( m_pCurrentMenu ) + { + // Don't open menus in demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + return; + + m_pCurrentMenu->Open(); + } +} + +//================================================================ +// Text Window +CMenuPanel* TeamFortressViewport::CreateTextWindow( int iTextToShow ) +{ + char sz[256]; + char *cText; + char *pfile = NULL; + static const int MAX_TITLE_LENGTH = 32; + char cTitle[MAX_TITLE_LENGTH]; + + if ( iTextToShow == SHOW_MOTD ) + { + if (!m_szServerName || !m_szServerName[0]) + strcpy( cTitle, "Half-Life" ); + else + strncpy( cTitle, m_szServerName, MAX_TITLE_LENGTH ); + cTitle[MAX_TITLE_LENGTH-1] = 0; + cText = m_szMOTD; + } + else if ( iTextToShow == SHOW_MAPBRIEFING ) + { + // Get the current mapname, and open it's map briefing text + if (m_sMapName && m_sMapName[0]) + { + strcpy( sz, "maps/"); + strcat( sz, m_sMapName ); + strcat( sz, ".txt" ); + } + else + { + const char *level = gEngfuncs.pfnGetLevelName(); + if (!level) + return NULL; + + strcpy( sz, level ); + char *ch = strchr( sz, '.' ); + *ch = '\0'; + strcat( sz, ".txt" ); + + // pull out the map name + strcpy( m_sMapName, level ); + ch = strchr( m_sMapName, '.' ); + if ( ch ) + { + *ch = 0; + } + + ch = strchr( m_sMapName, '/' ); + if ( ch ) + { + // move the string back over the '/' + memmove( m_sMapName, ch+1, strlen(ch)+1 ); + } + } + + pfile = (char*)gEngfuncs.COM_LoadFile( sz, 5, NULL ); + + if (!pfile) + return NULL; + + cText = pfile; + + strncpy( cTitle, m_sMapName, MAX_TITLE_LENGTH ); + cTitle[MAX_TITLE_LENGTH-1] = 0; + } + else if ( iTextToShow == SHOW_SPECHELP ) + { + CHudTextMessage::LocaliseTextString( "#Spec_Help_Title", cTitle, MAX_TITLE_LENGTH ); + cTitle[MAX_TITLE_LENGTH-1] = 0; + + char *pfile = CHudTextMessage::BufferedLocaliseTextString( "#Spec_Help_Text" ); + if ( pfile ) + { + cText = pfile; + } + } + else + return NULL; + + // if we're in the game (ie. have selected a class), flag the menu to be only grayed in the dialog box, instead of full screen + CMenuPanel *pMOTDPanel = CMessageWindowPanel_Create( cText, cTitle, g_iPlayerClass == 11, false, 0, 0, ScreenWidth, ScreenHeight ); + pMOTDPanel->setParent( this ); + + if ( pfile ) + gEngfuncs.COM_FreeFile( pfile ); + + return pMOTDPanel; +} + +//================================================================ +// VGUI Menus +void TeamFortressViewport::ShowVGUIMenu( int iMenu ) +{ + CMenuPanel *pNewMenu = NULL; + + // Don't open menus in demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + return; + + // Don't create one if it's already in the list + if (m_pCurrentMenu) + { + CMenuPanel *pMenu = m_pCurrentMenu; + while (pMenu != NULL) + { + if (pMenu->GetMenuID() == iMenu) + return; + pMenu = pMenu->GetNextMenu(); + } + } + + switch ( iMenu ) + { + case MENU_TEAM: + pNewMenu = ShowTeamMenu(); + break; + + // Map Briefing removed now that it appears in the team menu + case MENU_MAPBRIEFING: + pNewMenu = CreateTextWindow( SHOW_MAPBRIEFING ); + break; + + case MENU_INTRO: + pNewMenu = CreateTextWindow( SHOW_MOTD ); + break; + + case MENU_CLASSHELP: + pNewMenu = CreateTextWindow( SHOW_CLASSDESC ); + break; + + case MENU_SPECHELP: + pNewMenu = CreateTextWindow( SHOW_SPECHELP ); + break; + case MENU_CLASS: + pNewMenu = ShowClassMenu(); + break; + + default: + break; + } + + if (!pNewMenu) + return; + + // Close the Command Menu if it's open + HideCommandMenu(); + + pNewMenu->SetMenuID( iMenu ); + pNewMenu->SetActive( true ); + pNewMenu->setParent(this); + + // See if another menu is visible, and if so, cache this one for display once the other one's finished + if (m_pCurrentMenu) + { + m_pCurrentMenu->SetNextMenu( pNewMenu ); + } + else + { + m_pCurrentMenu = pNewMenu; + m_pCurrentMenu->Open(); + UpdateCursorState(); + } +} + +// Removes all VGUI Menu's onscreen +void TeamFortressViewport::HideVGUIMenu() +{ + while (m_pCurrentMenu) + { + HideTopMenu(); + } +} + +// Remove the top VGUI menu, and bring up the next one +void TeamFortressViewport::HideTopMenu() +{ + if (m_pCurrentMenu) + { + // Close the top one + m_pCurrentMenu->Close(); + + // Bring up the next one + gViewPort->SetCurrentMenu( m_pCurrentMenu->GetNextMenu() ); + } + + UpdateCursorState(); +} + +// Return TRUE if the HUD's allowed to print text messages +bool TeamFortressViewport::AllowedToPrintText( void ) +{ + // Prevent text messages when fullscreen menus are up + if ( m_pCurrentMenu && g_iPlayerClass == 0 ) + { + int iId = m_pCurrentMenu->GetMenuID(); + if ( iId == MENU_TEAM || iId == MENU_CLASS || iId == MENU_INTRO || iId == MENU_CLASSHELP ) + return FALSE; + } + + return TRUE; +} + +//====================================================================================== +// TEAM MENU +//====================================================================================== +// Bring up the Team selection Menu +CMenuPanel* TeamFortressViewport::ShowTeamMenu() +{ + // Don't open menus in demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + return NULL; + +// m_pTeamMenu->Reset(); +// return m_pTeamMenu; + + return NULL; +} + +void TeamFortressViewport::CreateTeamMenu() +{ + // Create the panel +/* m_pTeamMenu = new CTeamMenuPanel(100, false, 0, 0, ScreenWidth, ScreenHeight); + m_pTeamMenu->setParent( this ); + m_pTeamMenu->setVisible( false );*/ +} + +//====================================================================================== +// CLASS MENU +//====================================================================================== +// Bring up the Class selection Menu +CMenuPanel* TeamFortressViewport::ShowClassMenu() +{ + // Don't open menus in demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + return NULL; + +// m_pClassMenu->Reset(); +// return m_pClassMenu; + + return NULL; +} + +void TeamFortressViewport::CreateClassMenu() +{ + // Create the panel +/* m_pClassMenu = new CClassMenuPanel(100, false, 0, 0, ScreenWidth, ScreenHeight); + m_pClassMenu->setParent(this); + m_pClassMenu->setVisible( false );*/ +} + +//====================================================================================== +// SPECTATOR MENU +//====================================================================================== +// Spectator "Menu" explaining the Spectator buttons +void TeamFortressViewport::CreateSpectatorMenu() +{ + // Create the Panel + m_pSpectatorPanel = new SpectatorPanel(0, 0, ScreenWidth, ScreenHeight); + m_pSpectatorPanel->setParent(this); + m_pSpectatorPanel->setVisible(false); + m_pSpectatorPanel->Initialize(); +} + +//====================================================================================== +// UPDATE HUD SECTIONS +//====================================================================================== +// We've got an update on player info +// Recalculate any menus that use it. +void TeamFortressViewport::UpdateOnPlayerInfo() +{ +/* if (m_pTeamMenu) + m_pTeamMenu->Update(); + if (m_pClassMenu) + m_pClassMenu->Update();*/ + if (m_pScoreBoard) + m_pScoreBoard->Update(); +} + +void TeamFortressViewport::UpdateCursorState() +{ + // Need cursor if any VGUI window is up + if ( m_pSpectatorPanel->m_menuVisible || m_pCurrentMenu || m_pServerBrowser->isVisible() || GetClientVoiceMgr()->IsInSquelchMode() ) + { + g_iVisibleMouse = true; + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::scu_arrow) ); + return; + } + else if ( m_pCurrentCommandMenu ) + { + // commandmenu doesn't have cursor if hud_capturemouse is turned off + if ( gHUD.m_pCvarStealMouse->value != 0.0f ) + { + g_iVisibleMouse = true; + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::scu_arrow) ); + return; + } + } + + IN_ResetMouse(); + g_iVisibleMouse = false; + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::scu_none) ); +} + +void TeamFortressViewport::UpdateHighlights() +{ + if (m_pCurrentCommandMenu) + m_pCurrentCommandMenu->MakeVisible( NULL ); +} + +void TeamFortressViewport::GetAllPlayersInfo( void ) +{ + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + GetPlayerInfo( i, &g_PlayerInfoList[i] ); + + if ( g_PlayerInfoList[i].thisplayer ) + m_pScoreBoard->m_iPlayerNum = i; // !!!HACK: this should be initialized elsewhere... maybe gotten from the engine + } +} + +void TeamFortressViewport::paintBackground() +{ + int wide, tall; + getParent()->getSize( wide, tall ); + setSize( wide, tall ); + + // See if the command menu is visible and needs recalculating due to some external change +/* if ( g_iTeamNumber != m_iCurrentTeamNumber ) + { + UpdateCommandMenu(); + + if ( m_pClassMenu ) + { + m_pClassMenu->Update(); + } + + m_iCurrentTeamNumber = g_iTeamNumber; + } + + if ( g_iPlayerClass != m_iCurrentPlayerClass ) + { + UpdateCommandMenu(); + + m_iCurrentPlayerClass = g_iPlayerClass; + } */ + + // See if the Spectator Menu needs to be update + if ( g_iUser1 != m_iUser1 || g_iUser2 != m_iUser2 ) + { + UpdateSpectatorPanel(); + } + + // Update the Scoreboard, if it's visible + if ( m_pScoreBoard->isVisible() && (m_flScoreBoardLastUpdated < gHUD.m_flTime) ) + { + m_pScoreBoard->Update(); + m_flScoreBoardLastUpdated = gHUD.m_flTime + 0.5; + } + + int extents[4]; + getAbsExtents(extents[0],extents[1],extents[2],extents[3]); + VGui_ViewportPaintBackground(extents); +} + +//================================================================ +// Input Handler for Drag N Drop panels +void CDragNDropHandler::cursorMoved(int x,int y,Panel* panel) +{ + if(m_bDragging) + { + App::getInstance()->getCursorPos(x,y); + m_pPanel->setPos(m_iaDragOrgPos[0]+(x-m_iaDragStart[0]),m_iaDragOrgPos[1]+(y-m_iaDragStart[1])); + + if(m_pPanel->getParent()!=null) + { + m_pPanel->getParent()->repaint(); + } + } +} + +void CDragNDropHandler::mousePressed(MouseCode code,Panel* panel) +{ + int x,y; + App::getInstance()->getCursorPos(x,y); + m_bDragging=true; + m_iaDragStart[0]=x; + m_iaDragStart[1]=y; + m_pPanel->getPos(m_iaDragOrgPos[0],m_iaDragOrgPos[1]); + App::getInstance()->setMouseCapture(panel); + + m_pPanel->setDragged(m_bDragging); + m_pPanel->requestFocus(); +} + +void CDragNDropHandler::mouseReleased(MouseCode code,Panel* panel) +{ + m_bDragging=false; + m_pPanel->setDragged(m_bDragging); + App::getInstance()->setMouseCapture(null); +} + +//================================================================ +// Number Key Input +bool TeamFortressViewport::SlotInput( int iSlot ) +{ + // If there's a menu up, give it the input + if ( m_pCurrentMenu ) + return m_pCurrentMenu->SlotInput( iSlot ); + + return FALSE; +} + +// Direct Key Input +int TeamFortressViewport::KeyInput( int down, int keynum, const char *pszCurrentBinding ) +{ + // Enter gets out of Spectator Mode by bringing up the Team Menu + if (m_iUser1 && gEngfuncs.Con_IsVisible() == false ) + { + if ( down && (keynum == K_ENTER || keynum == K_KP_ENTER) ) + ShowVGUIMenu( MENU_TEAM ); + } + + // Open Text Window? + if (m_pCurrentMenu && gEngfuncs.Con_IsVisible() == false) + { + int iMenuID = m_pCurrentMenu->GetMenuID(); + + // Get number keys as Input for Team/Class menus + if (iMenuID == MENU_TEAM || iMenuID == MENU_CLASS) + { + // Escape gets you out of Team/Class menus if the Cancel button is visible + if ( keynum == K_ESCAPE ) + { + if ( (iMenuID == MENU_TEAM && g_iTeamNumber) || (iMenuID == MENU_CLASS && g_iPlayerClass) ) + { + HideTopMenu(); + return 0; + } + } + + for (int i = '0'; i <= '9'; i++) + { + if ( down && (keynum == i) ) + { + SlotInput( i - '0' ); + return 0; + } + } + } + + // Grab enter keys to close TextWindows + if ( down && (keynum == K_ENTER || keynum == K_KP_ENTER || keynum == K_SPACE || keynum == K_ESCAPE) ) + { + if ( iMenuID == MENU_MAPBRIEFING || iMenuID == MENU_INTRO || iMenuID == MENU_CLASSHELP ) + { + HideTopMenu(); + return 0; + } + } + + // Grab jump key on Team Menu as autoassign + if ( pszCurrentBinding && down && !strcmp(pszCurrentBinding, "+jump") ) + { + if (iMenuID == MENU_TEAM) + { +// m_pTeamMenu->SlotInput(5); + return 0; + } + } + + } + + // if we're in a command menu, try hit one of it's buttons + if ( down && m_pCurrentCommandMenu ) + { + // Escape hides the command menu + if ( keynum == K_ESCAPE ) + { + HideCommandMenu(); + return 0; + } + + // only trap the number keys + if ( keynum >= '0' && keynum <= '9' ) + { + if ( m_pCurrentCommandMenu->KeyInput(keynum) ) + { + // a final command has been issued, so close the command menu + HideCommandMenu(); + } + + return 0; + } + } + + return 1; +} + +//================================================================ +// Message Handlers +int TeamFortressViewport::MsgFunc_ValClass(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + for (int i = 0; i < 5; i++) + m_iValidClasses[i] = READ_SHORT(); + + // Force the menu to update + UpdateCommandMenu( m_StandardMenu ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_TeamNames(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iNumberOfTeams = READ_BYTE(); + + for (int i = 0; i < m_iNumberOfTeams; i++) + { + int teamNum = i + 1; + + gHUD.m_TextMessage.LocaliseTextString( READ_STRING(), m_sTeamNames[teamNum], MAX_TEAMNAME_SIZE ); + + // Set the team name buttons + if (m_pTeamButtons[i]) + m_pTeamButtons[i]->setText( m_sTeamNames[teamNum] ); + + // Set the disguise buttons + if (m_pDisguiseButtons[i]) + m_pDisguiseButtons[i]->setText( m_sTeamNames[teamNum] ); + } + + // Update the Team Menu +// if (m_pTeamMenu) +// m_pTeamMenu->Update(); + + return 1; +} + +int TeamFortressViewport::MsgFunc_Feign(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iIsFeigning = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu( m_StandardMenu ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_Detpack(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iIsSettingDetpack = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu( m_StandardMenu ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_VGUIMenu(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int iMenu = READ_BYTE(); + + // Map briefing includes the name of the map (because it's sent down before the client knows what map it is) + if (iMenu == MENU_MAPBRIEFING) + { + strncpy( m_sMapName, READ_STRING(), sizeof(m_sMapName) ); + m_sMapName[ sizeof(m_sMapName) - 1 ] = '\0'; + } + + // Bring up the menu6 + ShowVGUIMenu( iMenu ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_MOTD( const char *pszName, int iSize, void *pbuf ) +{ + if (m_iGotAllMOTD) + m_szMOTD[0] = 0; + + BEGIN_READ( pbuf, iSize ); + + m_iGotAllMOTD = READ_BYTE(); + int roomInArray = sizeof(m_szMOTD) - strlen(m_szMOTD) - 1; + + strncat( m_szMOTD, READ_STRING(), roomInArray >= 0 ? roomInArray : 0 ); + m_szMOTD[ sizeof(m_szMOTD)-1 ] = '\0'; + + if ( m_iGotAllMOTD ) + { + ShowVGUIMenu( MENU_INTRO ); + } + + return 1; +} + +int TeamFortressViewport::MsgFunc_BuildSt( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iBuildState = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu( m_StandardMenu ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_RandomPC( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iRandomPC = READ_BYTE(); + + return 1; +} + +int TeamFortressViewport::MsgFunc_ServerName( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + strncpy( m_szServerName, READ_STRING(), MAX_SERVERNAME_LENGTH ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_ScoreInfo( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + short cl = READ_BYTE(); + short frags = READ_SHORT(); + short deaths = READ_SHORT(); + short teamnumber = READ_SHORT(); + + if ( cl > 0 && cl <= MAX_PLAYERS ) + { + g_PlayerExtraInfo[cl].frags = frags; + g_PlayerExtraInfo[cl].deaths = deaths; + g_PlayerExtraInfo[cl].teamnumber = teamnumber; + + UpdateOnPlayerInfo(); + } + + return 1; +} + +// Message handler for TeamScore message +// accepts three values: +// string: team name +// short: teams kills +// short: teams deaths +// if this message is never received, then scores will simply be the combined totals of the players. +int TeamFortressViewport::MsgFunc_TeamScore( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + char *TeamName = READ_STRING(); + + // find the team matching the name + int i; + for ( i = 1; i <= m_pScoreBoard->m_iNumTeams; i++ ) + { + if ( !stricmp( TeamName, g_TeamInfo[i].name ) ) + break; + } + + if ( i > m_pScoreBoard->m_iNumTeams ) + return 1; + + // use this new score data instead of combined player scoresw + g_TeamInfo[i].scores_overriden = TRUE; + g_TeamInfo[i].frags = READ_SHORT(); + g_TeamInfo[i].deaths = READ_SHORT(); + + return 1; +} + +// Message handler for TeamInfo message +// accepts two values: +// byte: client number +// string: client team name +int TeamFortressViewport::MsgFunc_TeamInfo( const char *pszName, int iSize, void *pbuf ) +{ + if (!m_pScoreBoard) + return 1; + + BEGIN_READ( pbuf, iSize ); + short cl = READ_BYTE(); + + if ( cl > 0 && cl <= MAX_PLAYERS ) + { + // set the players team + strncpy( g_PlayerExtraInfo[cl].teamname, READ_STRING(), MAX_TEAM_NAME ); + } + + // rebuild the list of teams + m_pScoreBoard->RebuildTeams(); + + return 1; +} + +void TeamFortressViewport::DeathMsg( int killer, int victim ) +{ + m_pScoreBoard->DeathMsg(killer,victim); +} + +int TeamFortressViewport::MsgFunc_Spectator( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + +/* short cl = READ_BYTE(); + if ( cl > 0 && cl <= MAX_PLAYERS ) + { + g_IsSpectator[cl] = READ_BYTE(); + }*/ + + return 1; +} + +int TeamFortressViewport::MsgFunc_AllowSpec( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iAllowSpectators = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu( m_StandardMenu ); + + // If the team menu is up, update it too +// if (m_pTeamMenu) +// m_pTeamMenu->Update(); + + return 1; +} + diff --git a/dmc/cl_dll/vgui_viewport.h b/dmc/cl_dll/vgui_viewport.h new file mode 100644 index 0000000..1fcd156 --- /dev/null +++ b/dmc/cl_dll/vgui_viewport.h @@ -0,0 +1,1331 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef TEAMFORTRESSVIEWPORT_H +#define TEAMFORTRESSVIEWPORT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// custom scheme handling +#include "vgui_SchemeManager.h" + +#define PC_LASTCLASS 12 + +#define MENU_DEFAULT 1 +#define MENU_TEAM 2 +#define MENU_CLASS 3 +#define MENU_MAPBRIEFING 4 +#define MENU_INTRO 5 +#define MENU_CLASSHELP 6 +#define MENU_CLASSHELP2 7 +#define MENU_REPEATHELP 8 +#define MENU_SPECHELP 9 + + +using namespace vgui; + +class Cursor; +class ScorePanel; +class SpectatorPanel; +class CCommandMenu; +class CommandLabel; +class CommandButton; +class BuildButton; +class ClassButton; +class CMenuPanel; +class ServerBrowser; +class DragNDropPanel; +class CTransparentPanel; +//class CClassMenuPanel; +//class CTeamMenuPanel; + +char* GetVGUITGAName(const char *pszName); +BitmapTGA *LoadTGA( const char* pImageName ); +void ScaleColors( int &r, int &g, int &b, int a ); +extern char *sTFClassSelection[]; +extern int sTFValidClassInts[]; +extern char *sLocalisedClasses[]; +extern int iTeamColors[5][3]; + +#define MAX_SERVERNAME_LENGTH 32 + + +// Command Menu positions +#define MAX_MENUS 40 +#define MAX_BUTTONS 100 + +#define BUTTON_SIZE_Y YRES(30) +#define CMENU_SIZE_X XRES(160) + +#define SUBMENU_SIZE_X (CMENU_SIZE_X / 8) +#define SUBMENU_SIZE_Y (BUTTON_SIZE_Y / 6) + +#define CMENU_TOP (BUTTON_SIZE_Y * 4) + +#define MAX_TEAMNAME_SIZE 64 +#define MAX_BUTTON_SIZE 32 + +// Map Briefing Window +#define MAPBRIEF_INDENT 30 + +// Team Menu +#define TMENU_INDENT_X (30 * ((float)ScreenHeight / 640)) +#define TMENU_HEADER 100 +#define TMENU_SIZE_X (ScreenWidth - (TMENU_INDENT_X * 2)) +#define TMENU_SIZE_Y (TMENU_HEADER + BUTTON_SIZE_Y * 7) +#define TMENU_PLAYER_INDENT (((float)TMENU_SIZE_X / 3) * 2) +#define TMENU_INDENT_Y (((float)ScreenHeight - TMENU_SIZE_Y) / 2) + +// Class Menu +#define CLMENU_INDENT_X (30 * ((float)ScreenHeight / 640)) +#define CLMENU_HEADER 100 +#define CLMENU_SIZE_X (ScreenWidth - (CLMENU_INDENT_X * 2)) +#define CLMENU_SIZE_Y (CLMENU_HEADER + BUTTON_SIZE_Y * 11) +#define CLMENU_PLAYER_INDENT (((float)CLMENU_SIZE_X / 3) * 2) +#define CLMENU_INDENT_Y (((float)ScreenHeight - CLMENU_SIZE_Y) / 2) + +// Arrows +enum +{ + ARROW_UP, + ARROW_DOWN, + ARROW_LEFT, + ARROW_RIGHT, +}; + +//============================================================================== +// VIEWPORT PIECES +//============================================================ +// Wrapper for an Image Label without a background +class CImageLabel : public Label +{ +public: + BitmapTGA *m_pTGA; + +public: + CImageLabel( const char* pImageName,int x,int y ); + CImageLabel( const char* pImageName,int x,int y,int wide,int tall ); + + virtual int getImageTall(); + virtual int getImageWide(); + + virtual void paintBackground() + { + // Do nothing, so the background's left transparent. + } +}; + +// Command Label +// Overridden label so we can darken it when submenus open +class CommandLabel : public Label +{ +private: + int m_iState; + +public: + CommandLabel(const char* text,int x,int y,int wide,int tall) : Label(text,x,y,wide,tall) + { + m_iState = false; + } + + void PushUp() + { + m_iState = false; + repaint(); + } + + void PushDown() + { + m_iState = true; + repaint(); + } +}; + +//============================================================ +// Command Buttons +class CommandButton : public Button +{ +private: + int m_iPlayerClass; + + // Submenus under this button + CCommandMenu *m_pSubMenu; + CCommandMenu *m_pParentMenu; + CommandLabel *m_pSubLabel; + + char m_sMainText[MAX_BUTTON_SIZE]; + char m_cBoundKey; + + SchemeHandle_t m_hTextScheme; + + void RecalculateText( void ); + +public: + bool m_bNoHighlight; + +public: + // Constructors + CommandButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight = false); + CommandButton( int iPlayerClass, const char* text,int x,int y,int wide,int tall); + + void Init( void ); + + // Menu Handling + void AddSubMenu( CCommandMenu *pNewMenu ); + void AddSubLabel( CommandLabel *pSubLabel ) + { + m_pSubLabel = pSubLabel; + } + + virtual int IsNotValid( void ) + { + return false; + } + + void UpdateSubMenus( int iAdjustment ); + int GetPlayerClass() { return m_iPlayerClass; }; + CCommandMenu *GetSubMenu() { return m_pSubMenu; }; + + CCommandMenu *getParentMenu( void ); + void setParentMenu( CCommandMenu *pParentMenu ); + + // Overloaded vgui functions + virtual void paint(); + virtual void setText( const char *text ); + virtual void paintBackground(); + + void cursorEntered( void ); + void cursorExited( void ); + + void setBoundKey( char boundKey ); + char getBoundKey( void ); +}; + +//============================================================ +// Command Menus +class CCommandMenu : public Panel +{ +private: + CCommandMenu *m_pParentMenu; + int m_iXOffset; + int m_iYOffset; + + // Buttons in this menu + CommandButton *m_aButtons[ MAX_BUTTONS ]; + int m_iButtons; + + // opens menu from top to bottom (0 = default), or from bottom to top (1)? + int m_iDirection; +public: + CCommandMenu( CCommandMenu *pParentMenu, int x,int y,int wide,int tall ) : Panel(x,y,wide,tall) + { + m_pParentMenu = pParentMenu; + m_iXOffset = x; + m_iYOffset = y; + m_iButtons = 0; + m_iDirection = 0; + } + + CCommandMenu( CCommandMenu *pParentMenu, int direction, int x,int y,int wide,int tall ) : Panel(x,y,wide,tall) + { + m_pParentMenu = pParentMenu; + m_iXOffset = x; + m_iYOffset = y; + m_iButtons = 0; + m_iDirection = direction; + } + + void AddButton( CommandButton *pButton ); + bool RecalculateVisibles( int iNewYPos, bool bHideAll ); + void RecalculatePositions( int iYOffset ); + void MakeVisible( CCommandMenu *pChildMenu ); + + CCommandMenu *GetParentMenu() { return m_pParentMenu; }; + int GetXOffset() { return m_iXOffset; }; + int GetYOffset() { return m_iYOffset; }; + int GetDirection() { return m_iDirection; }; + int GetNumButtons() { return m_iButtons; }; + CommandButton *FindButtonWithSubmenu( CCommandMenu *pSubMenu ); + + void ClearButtonsOfArmedState( void ); + + + bool KeyInput( int keyNum ); + + virtual void paintBackground(); +}; + +//============================================================================== +class TeamFortressViewport : public Panel +{ +private: + vgui::Cursor* _cursorNone; + vgui::Cursor* _cursorArrow; + + int m_iInitialized; + + CCommandMenu *m_pCommandMenus[ MAX_MENUS ]; + CCommandMenu *m_pCurrentCommandMenu; + float m_flMenuOpenTime; + float m_flScoreBoardLastUpdated; + int m_iNumMenus; + int m_iCurrentTeamNumber; + int m_iCurrentPlayerClass; + int m_iUser1; + int m_iUser2; + + // VGUI Menus + void CreateTeamMenu( void ); + CMenuPanel* ShowTeamMenu( void ); + void CreateClassMenu( void ); + CMenuPanel* ShowClassMenu( void ); + void CreateSpectatorMenu( void ); + + // Scheme handler + CSchemeManager m_SchemeManager; + + // MOTD + int m_iGotAllMOTD; + char m_szMOTD[ MAX_MOTD_LENGTH ]; + + // Command Menu Team buttons + CommandButton *m_pTeamButtons[6]; + CommandButton *m_pDisguiseButtons[5]; + BuildButton *m_pBuildButtons[3]; + BuildButton *m_pBuildActiveButtons[3]; + + // Server Browser + ServerBrowser *m_pServerBrowser; + + int m_iAllowSpectators; + + // Data for specific sections of the Command Menu + int m_iValidClasses[5]; + int m_iIsFeigning; + int m_iIsSettingDetpack; + int m_iNumberOfTeams; + int m_iBuildState; + int m_iRandomPC; + char m_sTeamNames[5][MAX_TEAMNAME_SIZE]; + + // Localisation strings + char m_sDetpackStrings[3][MAX_BUTTON_SIZE]; + + char m_sMapName[64]; +public: + TeamFortressViewport(int x,int y,int wide,int tall); + void Initialize( void ); + + int CreateCommandMenu( char * menuFile, int direction, int yOffset ); + void CreateScoreBoard( void ); + void CreateServerBrowser( void ); + CommandButton * CreateCustomButton( char *pButtonText, char * pButtonName, int iYOffset ); + CCommandMenu * CreateDisguiseSubmenu( CommandButton *pButton, CCommandMenu *pParentMenu, const char *commandText, int iYOffset ); + + void UpdateCursorState( void ); + void UpdateCommandMenu(int menuIndex); + void UpdateOnPlayerInfo( void ); + void UpdateHighlights( void ); + void UpdateSpectatorPanel( void ); + + int KeyInput( int down, int keynum, const char *pszCurrentBinding ); + void InputPlayerSpecial( void ); + void GetAllPlayersInfo( void ); + void DeathMsg( int killer, int victim ); + + void ShowCommandMenu(int menuIndex); + void InputSignalHideCommandMenu( void ); + void HideCommandMenu( void ); + void SetCurrentCommandMenu( CCommandMenu *pNewMenu ); + void SetCurrentMenu( CMenuPanel *pMenu ); + + void ShowScoreBoard( void ); + void HideScoreBoard( void ); + bool IsScoreBoardVisible( void ); + + bool AllowedToPrintText( void ); + + void ShowVGUIMenu( int iMenu ); + void HideVGUIMenu( void ); + void HideTopMenu( void ); + + void ToggleServerBrowser( void ); + + CMenuPanel* CreateTextWindow( int iTextToShow ); + + CCommandMenu *CreateSubMenu( CommandButton *pButton, CCommandMenu *pParentMenu, int iYOffset ); + + // Data Handlers + int GetValidClasses(int iTeam) { return m_iValidClasses[iTeam]; }; + int GetNumberOfTeams() { return m_iNumberOfTeams; }; + int GetIsFeigning() { return m_iIsFeigning; }; + int GetIsSettingDetpack() { return m_iIsSettingDetpack; }; + int GetBuildState() { return m_iBuildState; }; + int IsRandomPC() { return m_iRandomPC; }; + char *GetTeamName( int iTeam ) { return m_sTeamNames[iTeam]; }; + int GetAllowSpectators() { return m_iAllowSpectators; }; + + // Message Handlers + int MsgFunc_ValClass(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_TeamNames(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Feign(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Detpack(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_VGUIMenu(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_MOTD( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_BuildSt( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_RandomPC( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_ServerName( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_ScoreInfo( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_TeamScore( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_TeamInfo( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Spectator( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_AllowSpec( const char *pszName, int iSize, void *pbuf ); + + // Input + bool SlotInput( int iSlot ); + + virtual void paintBackground(); + + CSchemeManager *GetSchemeManager( void ) { return &m_SchemeManager; } + + void *operator new( size_t stAllocateBlock ); + +public: + // VGUI Menus + CMenuPanel *m_pCurrentMenu; + int m_SpectatorMenu; // indexs in m_pCommandMenus + int m_StandardMenu; +// CTeamMenuPanel *m_pTeamMenu; +// CClassMenuPanel *m_pClassMenu; + ScorePanel *m_pScoreBoard; + SpectatorPanel *m_pSpectatorPanel; + char m_szServerName[ MAX_SERVERNAME_LENGTH ]; +}; + +//============================================================ +// Command Menu Button Handlers +#define MAX_COMMAND_SIZE 256 + +class CMenuHandler_StringCommand : public ActionSignal +{ +protected: + char m_pszCommand[MAX_COMMAND_SIZE]; + int m_iCloseVGUIMenu; +public: + CMenuHandler_StringCommand( char *pszCommand ) + { + strncpy( m_pszCommand, pszCommand, MAX_COMMAND_SIZE); + m_pszCommand[MAX_COMMAND_SIZE-1] = '\0'; + m_iCloseVGUIMenu = false; + } + + CMenuHandler_StringCommand( char *pszCommand, int iClose ) + { + strncpy( m_pszCommand, pszCommand, MAX_COMMAND_SIZE); + m_pszCommand[MAX_COMMAND_SIZE-1] = '\0'; + m_iCloseVGUIMenu = true; + } + + virtual void actionPerformed(Panel* panel) + { + gEngfuncs.pfnClientCmd(m_pszCommand); + + if (m_iCloseVGUIMenu) + gViewPort->HideTopMenu(); + else + gViewPort->HideCommandMenu(); + } +}; + +// This works the same as CMenuHandler_StringCommand, except it watches the string command +// for specific commands, and modifies client vars based upon them. +class CMenuHandler_StringCommandWatch : public CMenuHandler_StringCommand +{ +private: +public: + CMenuHandler_StringCommandWatch( char *pszCommand ) : CMenuHandler_StringCommand( pszCommand ) + { + } + + CMenuHandler_StringCommandWatch( char *pszCommand, int iClose ) : CMenuHandler_StringCommand( pszCommand, iClose ) + { + } + + virtual void actionPerformed(Panel* panel) + { + CMenuHandler_StringCommand::actionPerformed( panel ); + + // Try to guess the player's new team (it'll be corrected if it's wrong) + /* if ( !strcmp( m_pszCommand, "jointeam 1" ) ) + g_iTeamNumber = 1; + else if ( !strcmp( m_pszCommand, "jointeam 2" ) ) + g_iTeamNumber = 2; + else if ( !strcmp( m_pszCommand, "jointeam 3" ) ) + g_iTeamNumber = 3; + else if ( !strcmp( m_pszCommand, "jointeam 4" ) ) + g_iTeamNumber = 4;*/ + } +}; + +// Used instead of CMenuHandler_StringCommand for Class Selection buttons. +// Checks the state of hud_classautokill and kills the player if set +class CMenuHandler_StringCommandClassSelect : public CMenuHandler_StringCommand +{ +private: +public: + CMenuHandler_StringCommandClassSelect( char *pszCommand ) : CMenuHandler_StringCommand( pszCommand ) + { + } + + CMenuHandler_StringCommandClassSelect( char *pszCommand, int iClose ) : CMenuHandler_StringCommand( pszCommand, iClose ) + { + } + + virtual void actionPerformed(Panel* panel); +}; + +class CMenuHandler_PopupSubMenuInput : public InputSignal +{ +private: + CCommandMenu *m_pSubMenu; + Button *m_pButton; +public: + CMenuHandler_PopupSubMenuInput( Button *pButton, CCommandMenu *pSubMenu ) + { + m_pSubMenu = pSubMenu; + m_pButton = pButton; + } + + virtual void cursorMoved(int x,int y,Panel* panel) + { + //gViewPort->SetCurrentCommandMenu( m_pSubMenu ); + } + + virtual void cursorEntered(Panel* panel) + { + gViewPort->SetCurrentCommandMenu( m_pSubMenu ); + + if (m_pButton) + m_pButton->setArmed(true); + }; + virtual void cursorExited(Panel* Panel) {}; + virtual void mousePressed(MouseCode code,Panel* panel) {}; + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {}; + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; +}; + +class CMenuHandler_LabelInput : public InputSignal +{ +private: + ActionSignal *m_pActionSignal; +public: + CMenuHandler_LabelInput( ActionSignal *pSignal ) + { + m_pActionSignal = pSignal; + } + + virtual void mousePressed(MouseCode code,Panel* panel) + { + m_pActionSignal->actionPerformed( panel ); + } + + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void cursorEntered(Panel* panel) {}; + virtual void cursorExited(Panel* Panel) {}; + virtual void cursorMoved(int x,int y,Panel* panel) {}; + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; +}; + +#define HIDE_TEXTWINDOW 0 +#define SHOW_MAPBRIEFING 1 +#define SHOW_CLASSDESC 2 +#define SHOW_MOTD 3 +#define SHOW_SPECHELP 4 + +class CMenuHandler_TextWindow : public ActionSignal +{ +private: + int m_iState; +public: + CMenuHandler_TextWindow( int iState ) + { + m_iState = iState; + } + + virtual void actionPerformed(Panel* panel) + { + if (m_iState == HIDE_TEXTWINDOW) + { + gViewPort->HideTopMenu(); + } + else + { + gViewPort->HideCommandMenu(); + gViewPort->ShowVGUIMenu( m_iState ); + } + } +}; + +class CMenuHandler_ToggleCvar : public ActionSignal +{ +private: + struct cvar_s * m_cvar; + +public: + CMenuHandler_ToggleCvar( char * cvarname ) + { + m_cvar = gEngfuncs.pfnGetCvarPointer( cvarname ); + } + + virtual void actionPerformed(Panel* panel) + { + if ( m_cvar->value ) + m_cvar->value = 0.0f; + else + m_cvar->value = 1.0f; + + gViewPort->UpdateSpectatorPanel(); + } + + +}; +class CDragNDropHandler : public InputSignal +{ +private: + DragNDropPanel* m_pPanel; + bool m_bDragging; + int m_iaDragOrgPos[2]; + int m_iaDragStart[2]; + +public: + CDragNDropHandler(DragNDropPanel* pPanel) + { + m_pPanel = pPanel; + m_bDragging = false; + } + + void cursorMoved(int x,int y,Panel* panel); + void mousePressed(MouseCode code,Panel* panel); + void mouseReleased(MouseCode code,Panel* panel); + + void mouseDoublePressed(MouseCode code,Panel* panel) {}; + void cursorEntered(Panel* panel) {}; + void cursorExited(Panel* panel) {}; + void mouseWheeled(int delta,Panel* panel) {}; + void keyPressed(KeyCode code,Panel* panel) {}; + void keyTyped(KeyCode code,Panel* panel) {}; + void keyReleased(KeyCode code,Panel* panel) {}; + void keyFocusTicked(Panel* panel) {}; +}; + +class CHandler_MenuButtonOver : public InputSignal +{ +private: + int m_iButton; + CMenuPanel *m_pMenuPanel; +public: + CHandler_MenuButtonOver( CMenuPanel *pPanel, int iButton ) + { + m_iButton = iButton; + m_pMenuPanel = pPanel; + } + + void cursorEntered(Panel *panel); + + void cursorMoved(int x,int y,Panel* panel) {}; + void mousePressed(MouseCode code,Panel* panel) {}; + void mouseReleased(MouseCode code,Panel* panel) {}; + void mouseDoublePressed(MouseCode code,Panel* panel) {}; + void cursorExited(Panel* panel) {}; + void mouseWheeled(int delta,Panel* panel) {}; + void keyPressed(KeyCode code,Panel* panel) {}; + void keyTyped(KeyCode code,Panel* panel) {}; + void keyReleased(KeyCode code,Panel* panel) {}; + void keyFocusTicked(Panel* panel) {}; +}; + +class CHandler_ButtonHighlight : public InputSignal +{ +private: + Button *m_pButton; +public: + CHandler_ButtonHighlight( Button *pButton ) + { + m_pButton = pButton; + } + + virtual void cursorEntered(Panel* panel) + { + m_pButton->setArmed(true); + }; + virtual void cursorExited(Panel* Panel) + { + m_pButton->setArmed(false); + }; + virtual void mousePressed(MouseCode code,Panel* panel) {}; + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void cursorMoved(int x,int y,Panel* panel) {}; + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; +}; + +//----------------------------------------------------------------------------- +// Purpose: Special handler for highlighting of command menu buttons +//----------------------------------------------------------------------------- +class CHandler_CommandButtonHighlight : public CHandler_ButtonHighlight +{ +private: + CommandButton *m_pCommandButton; +public: + CHandler_CommandButtonHighlight( CommandButton *pButton ) : CHandler_ButtonHighlight( pButton ) + { + m_pCommandButton = pButton; + } + + virtual void cursorEntered( Panel *panel ) + { + m_pCommandButton->cursorEntered(); + } + + virtual void cursorExited( Panel *panel ) + { + m_pCommandButton->cursorExited(); + } +}; + + +//================================================================ +// Overidden Command Buttons for special visibilities +class ClassButton : public CommandButton +{ +protected: + int m_iPlayerClass; + +public: + ClassButton( int iClass, const char* text,int x,int y,int wide,int tall, bool bNoHighlight ) : CommandButton( text,x,y,wide,tall, bNoHighlight) + { + m_iPlayerClass = iClass; + } + + virtual int IsNotValid(); +}; + +class TeamButton : public CommandButton +{ +private: + int m_iTeamNumber; +public: + TeamButton( int iTeam, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iTeamNumber = iTeam; + } + + virtual int IsNotValid() + { + int iTeams = gViewPort->GetNumberOfTeams(); + // Never valid if there's only 1 team + if (iTeams == 1) + return true; + + // Auto Team's always visible + if (m_iTeamNumber == 5) + return false; + +// if (iTeams >= m_iTeamNumber && m_iTeamNumber != g_iTeamNumber) +// return false; + + return true; + } +}; + +class FeignButton : public CommandButton +{ +private: + int m_iFeignState; +public: + FeignButton( int iState, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iFeignState = iState; + } + + virtual int IsNotValid() + { + return true; + } +}; + +class SpectateButton : public CommandButton +{ +public: + SpectateButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight ) : CommandButton( text,x,y,wide,tall, bNoHighlight) + { + } + + virtual int IsNotValid() + { + // Only visible if the server allows it + if ( gViewPort->GetAllowSpectators() != 0 ) + return false; + + return true; + } +}; + +#define DISGUISE_TEAM1 (1<<0) +#define DISGUISE_TEAM2 (1<<1) +#define DISGUISE_TEAM3 (1<<2) +#define DISGUISE_TEAM4 (1<<3) + +class DisguiseButton : public CommandButton +{ +private: + int m_iValidTeamsBits; + int m_iThisTeam; +public: + DisguiseButton( int iValidTeamNumsBits, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall,false ) + { + m_iValidTeamsBits = iValidTeamNumsBits; + } + + virtual int IsNotValid() + { + // if it's not tied to a specific team, then always show (for spies) + if ( !m_iValidTeamsBits ) + return false; + + // if we're tied to a team make sure we can change to that team + int iTmp = 1 << (gViewPort->GetNumberOfTeams() - 1); + if ( m_iValidTeamsBits & iTmp ) + return false; + + return true; + } +}; + +class DetpackButton : public CommandButton +{ +private: + int m_iDetpackState; +public: + DetpackButton( int iState, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iDetpackState = iState; + } + + virtual int IsNotValid() + { + return true; + } +}; + +extern int iBuildingCosts[]; +#define BUILDSTATE_HASBUILDING (1<<0) // Data is building ID (1 = Dispenser, 2 = Sentry) +#define BUILDSTATE_BUILDING (1<<1) +#define BUILDSTATE_BASE (1<<2) +#define BUILDSTATE_CANBUILD (1<<3) // Data is building ID (0 = Dispenser, 1 = Sentry) + +class BuildButton : public CommandButton +{ +private: + int m_iBuildState; + int m_iBuildData; + +public: + enum Buildings + { + DISPENSER = 0, + SENTRYGUN = 1, + }; + + BuildButton( int iState, int iData, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iBuildState = iState; + m_iBuildData = iData; + } + + virtual int IsNotValid() + { + return true; + } +}; + +#define MAX_MAPNAME 256 + +class MapButton : public CommandButton +{ +private: + char m_szMapName[ MAX_MAPNAME ]; + +public: + MapButton( const char *pMapName, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + sprintf( m_szMapName, "maps/%s.bsp", pMapName ); + } + + virtual int IsNotValid() + { + const char *level = gEngfuncs.pfnGetLevelName(); + if (!level) + return true; + + // Does it match the current map name? + if ( strcmp(m_szMapName, level) ) + return true; + + return false; + } +}; + +//----------------------------------------------------------------------------- +// Purpose: CommandButton which is only displayed if the player is on team X +//----------------------------------------------------------------------------- +class TeamOnlyCommandButton : public CommandButton +{ +private: + int m_iTeamNum; + +public: + TeamOnlyCommandButton( int iTeamNum, const char* text,int x,int y,int wide,int tall ) : + CommandButton( text, x, y, wide, tall ), m_iTeamNum(iTeamNum) {} + + virtual int IsNotValid() + { +/* if ( g_iTeamNumber != m_iTeamNum ) + return true;*/ + + return CommandButton::IsNotValid(); + } +}; + +//----------------------------------------------------------------------------- +// Purpose: CommandButton which is only displayed if the player is on team X +//----------------------------------------------------------------------------- +class ToggleCommandButton : public CommandButton, public InputSignal +{ +private: + struct cvar_s * m_cvar; + CImageLabel * pLabelOn; + CImageLabel * pLabelOff; + + +public: + ToggleCommandButton( const char* cvarname, const char* text,int x,int y,int wide,int tall ) : + CommandButton( text, x, y, wide, tall ) + { + m_cvar = gEngfuncs.pfnGetCvarPointer( cvarname ); + + // Put a > to show it's a submenu + pLabelOn = new CImageLabel( "checked", 0, 0 ); + pLabelOn->setParent(this); + pLabelOn->addInputSignal(this); + + pLabelOff = new CImageLabel( "unchecked", 0, 0 ); + pLabelOff->setParent(this); + pLabelOff->setEnabled(true); + pLabelOff->addInputSignal(this); + + int textwide, texttall; + getTextSize( textwide, texttall); + + // Reposition + pLabelOn->setPos( textwide, (tall - pLabelOn->getTall()) / 2 ); + + pLabelOff->setPos( textwide, (tall - pLabelOff->getTall()) / 2 ); + + // Set text color to orange + setFgColor(Scheme::sc_primary1); + } + + virtual void cursorEntered(Panel* panel) + { + CommandButton::cursorEntered(); + } + + virtual void cursorExited(Panel* panel) + { + CommandButton::cursorExited(); + } + + virtual void mousePressed(MouseCode code,Panel* panel) + { + doClick(); + }; + + virtual void cursorMoved(int x,int y,Panel* panel) {}; + + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {}; + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; + + virtual void paint( void ) + { + if ( !m_cvar ) + { + pLabelOff->setVisible(false); + pLabelOn->setVisible(false); + } + else if ( m_cvar->value ) + { + pLabelOff->setVisible(false); + pLabelOn->setVisible(true); + } + else + { + pLabelOff->setVisible(true); + pLabelOn->setVisible(false); + } + + CommandButton::paint(); + + } +}; +//============================================================ +// Panel that can be dragged around +class DragNDropPanel : public Panel +{ +private: + bool m_bBeingDragged; + LineBorder *m_pBorder; +public: + DragNDropPanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) + { + m_bBeingDragged = false; + + // Create the Drag Handler + addInputSignal( new CDragNDropHandler(this) ); + + // Create the border (for dragging) + m_pBorder = new LineBorder(); + } + + virtual void setDragged( bool bState ) + { + m_bBeingDragged = bState; + + if (m_bBeingDragged) + setBorder(m_pBorder); + else + setBorder(NULL); + } +}; + +//================================================================ +// Panel that draws itself with a transparent black background +class CTransparentPanel : public Panel +{ +private: + int m_iTransparency; +public: + CTransparentPanel(int iTrans, int x,int y,int wide,int tall) : Panel(x,y,wide,tall) + { + m_iTransparency = iTrans; + } + + virtual void paintBackground() + { + if (m_iTransparency) + { + // Transparent black background + drawSetColor( 0,0,0, m_iTransparency ); + drawFilledRect(0,0,_size[0],_size[1]); + } + } +}; + +//================================================================ +// Menu Panel that supports buffering of menus +class CMenuPanel : public CTransparentPanel +{ +private: + CMenuPanel *m_pNextMenu; + int m_iMenuID; + int m_iRemoveMe; + int m_iIsActive; + float m_flOpenTime; +public: + CMenuPanel(int iRemoveMe, int x,int y,int wide,int tall) : CTransparentPanel(100, x,y,wide,tall) + { + Reset(); + m_iRemoveMe = iRemoveMe; + } + + CMenuPanel(int iTrans, int iRemoveMe, int x,int y,int wide,int tall) : CTransparentPanel(iTrans, x,y,wide,tall) + { + Reset(); + m_iRemoveMe = iRemoveMe; + } + + virtual void Reset( void ) + { + m_pNextMenu = NULL; + m_iIsActive = false; + m_flOpenTime = 0; + } + + void SetNextMenu( CMenuPanel *pNextPanel ) + { + if (m_pNextMenu) + m_pNextMenu->SetNextMenu( pNextPanel ); + else + m_pNextMenu = pNextPanel; + } + + void SetMenuID( int iID ) + { + m_iMenuID = iID; + } + + void SetActive( int iState ) + { + m_iIsActive = iState; + } + + virtual void Open( void ) + { + setVisible( true ); + + // Note the open time, so we can delay input for a bit + m_flOpenTime = gHUD.m_flTime; + } + + virtual void Close( void ) + { + setVisible( false ); + m_iIsActive = false; + + if ( m_iMenuID == MENU_INTRO ) + { + gEngfuncs.pfnClientCmd( "_firstspawn\n" ); + } + + if ( m_iRemoveMe ) + gViewPort->removeChild( this ); + + // This MenuPanel has now been deleted. Don't append code here. + } + + int ShouldBeRemoved() { return m_iRemoveMe; }; + CMenuPanel* GetNextMenu() { return m_pNextMenu; }; + int GetMenuID() { return m_iMenuID; }; + int IsActive() { return m_iIsActive; }; + float GetOpenTime() { return m_flOpenTime; }; + + // Numeric input + virtual bool SlotInput( int iSlot ) { return false; }; + virtual void SetActiveInfo( int iInput ) {}; +}; + +//================================================================ +// Custom drawn scroll bars +class CTFScrollButton : public CommandButton +{ +private: + BitmapTGA *m_pTGA; + +public: + CTFScrollButton(int iArrow, const char* text,int x,int y,int wide,int tall); + + virtual void paint( void ); + virtual void paintBackground( void ); +}; + +// Custom drawn slider bar +class CTFSlider : public Slider +{ +public: + CTFSlider(int x,int y,int wide,int tall,bool vertical) : Slider(x,y,wide,tall,vertical) + { + }; + + virtual void paintBackground( void ); +}; + +// Custom drawn scrollpanel +class CTFScrollPanel : public ScrollPanel +{ +public: + CTFScrollPanel(int x,int y,int wide,int tall); +}; + +//================================================================ +// Menu Panels that take key input +//============================================================ +/*class CClassMenuPanel : public CMenuPanel +{ +private: + CTransparentPanel *m_pClassInfoPanel[PC_LASTCLASS]; + Label *m_pPlayers[PC_LASTCLASS]; + ClassButton *m_pButtons[PC_LASTCLASS]; + CommandButton *m_pCancelButton; + ScrollPanel *m_pScrollPanel; + + CImageLabel *m_pClassImages[MAX_TEAMS][PC_LASTCLASS]; + + int m_iCurrentInfo; + + enum { STRLENMAX_PLAYERSONTEAM = 128 }; + char m_sPlayersOnTeamString[STRLENMAX_PLAYERSONTEAM]; + +public: + CClassMenuPanel(int iTrans, int iRemoveMe, int x,int y,int wide,int tall); + + virtual bool SlotInput( int iSlot ); + virtual void Open( void ); + virtual void Update( void ); + virtual void SetActiveInfo( int iInput ); + virtual void Initialize( void ); + + virtual void Reset( void ) + { + CMenuPanel::Reset(); + m_iCurrentInfo = 0; + } +};*/ +/* +class CTeamMenuPanel : public CMenuPanel +{ +public: + ScrollPanel *m_pScrollPanel; + CTransparentPanel *m_pTeamWindow; + Label *m_pMapTitle; + TextPanel *m_pBriefing; + TextPanel *m_pTeamInfoPanel[6]; + CommandButton *m_pButtons[6]; + bool m_bUpdatedMapName; + CommandButton *m_pCancelButton; + CommandButton *m_pSpectateButton; + + int m_iCurrentInfo; + +public: + CTeamMenuPanel(int iTrans, int iRemoveMe, int x,int y,int wide,int tall); + + virtual bool SlotInput( int iSlot ); + virtual void Open( void ); + virtual void Update( void ); + virtual void SetActiveInfo( int iInput ); + virtual void paintBackground( void ); + + virtual void Initialize( void ); + + virtual void Reset( void ) + { + CMenuPanel::Reset(); + m_iCurrentInfo = 0; + } +}; +*/ +//========================================================= +// Specific Menus to handle old HUD sections +class CHealthPanel : public DragNDropPanel +{ +private: + BitmapTGA *m_pHealthTGA; + Label *m_pHealthLabel; +public: + CHealthPanel(int x,int y,int wide,int tall) : DragNDropPanel(x,y,wide,tall) + { + // Load the Health icon + FileInputStream* fis = new FileInputStream( GetVGUITGAName("%d_hud_health"), false); + m_pHealthTGA = new BitmapTGA(fis,true); + fis->close(); + + // Create the Health Label + int iXSize,iYSize; + m_pHealthTGA->getSize(iXSize,iYSize); + m_pHealthLabel = new Label("",0,0,iXSize,iYSize); + m_pHealthLabel->setImage(m_pHealthTGA); + m_pHealthLabel->setParent(this); + + // Set panel dimension + // Shouldn't be needed once Billy's fized setImage not recalculating the size + //setSize( iXSize + 100, gHUD.m_iFontHeight + 10 ); + //m_pHealthLabel->setPos( 10, (getTall() - iYSize) / 2 ); + } + + virtual void paintBackground() + { + } + + void paint() + { + // Get the paint color + int r,g,b,a; + // Has health changed? Flash the health # + if (gHUD.m_Health.m_fFade) + { + gHUD.m_Health.m_fFade -= (gHUD.m_flTimeDelta * 20); + if (gHUD.m_Health.m_fFade <= 0) + { + a = MIN_ALPHA; + gHUD.m_Health.m_fFade = 0; + } + + // Fade the health number back to dim + a = MIN_ALPHA + (gHUD.m_Health.m_fFade/FADE_TIME) * 128; + } + else + a = MIN_ALPHA; + + gHUD.m_Health.GetPainColor( r, g, b ); + ScaleColors(r, g, b, a ); + + // If health is getting low, make it bright red + if (gHUD.m_Health.m_iHealth <= 15) + a = 255; + + int iXSize,iYSize, iXPos, iYPos; + m_pHealthTGA->getSize(iXSize,iYSize); + m_pHealthTGA->getPos(iXPos, iYPos); + + // Paint the player's health + int x = gHUD.DrawHudNumber( iXPos + iXSize + 5, iYPos + 5, DHN_3DIGITS | DHN_DRAWZERO, gHUD.m_Health.m_iHealth, r, g, b); + + // Draw the vertical line + int HealthWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; + x += HealthWidth / 2; + FillRGBA(x, iYPos + 5, HealthWidth / 10, gHUD.m_iFontHeight, 255, 160, 0, a); + } +}; + +#endif diff --git a/dmc/cl_dll/view.cpp b/dmc/cl_dll/view.cpp new file mode 100644 index 0000000..13dfc86 --- /dev/null +++ b/dmc/cl_dll/view.cpp @@ -0,0 +1,1677 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// view/refresh setup functions + +#include +#include "hud.h" +#include "cl_util.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" + +#include "entity_state.h" +#include "cl_entity.h" +#include "ref_params.h" +#include "in_defs.h" // PITCH YAW ROLL +#include "pm_movevars.h" +#include "pm_shared.h" +#include "pm_defs.h" +#include "event_api.h" +#include "pmtrace.h" +#include "hltv.h" + + +// QUAKECLASSIC +extern int iMouseInUse; +extern vec3_t vecTempAngles; +extern bool bChangeAngles; + + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +extern "C" +{ + int CL_IsThirdPerson( void ); + void CL_CameraOffset( float *ofs ); + + void EXPORT V_CalcRefdef( struct ref_params_s *pparams ); + + void PM_ParticleLine( float *start, float *end, int pcolor, float life, float vert); + int PM_GetInfo( int ent ); + void InterpolateAngles( float * start, float * end, float * output, float frac ); + void NormalizeAngles( float * angles ); + float Distance(const float * v1, const float * v2); + float AngleBetweenVectors( const float * v1, const float * v2 ); + + float vJumpOrigin[3]; + float vJumpAngles[3]; +} + +#include "r_studioint.h" +#include "com_model.h" + +extern engine_studio_api_t IEngineStudio; + +void V_DropPunchAngle ( float frametime, float *ev_punchangle ); +void VectorAngles( const float *forward, float *angles ); + +/* +The view is allowed to move slightly from it's true position for bobbing, +but if it exceeds 8 pixels linear distance (spherical, not box), the list of +entities sent from the server may not include everything in the pvs, especially +when crossing a water boudnary. +*/ + +extern cvar_t *cl_forwardspeed; +extern cvar_t *chase_active; +extern cvar_t *cl_vsmoothing; + +vec3_t v_origin, v_angles, v_cl_angles, v_sim_org, v_lastAngles; +float v_frametime, v_lastDistance; + +vec3_t ev_punchangle; + +cvar_t *scr_ofsx; +cvar_t *scr_ofsy; +cvar_t *scr_ofsz; + +cvar_t *v_centermove; +cvar_t *v_centerspeed; + +cvar_t *cl_bobcycle; +cvar_t *cl_bob; +cvar_t *cl_bobup; +cvar_t *cl_waterdist; +cvar_t *cl_chasedist; + +// These cvars are not registered (so users can't cheat), so set the ->value field directly +// Register these cvars in V_Init() if needed for easy tweaking +cvar_t v_iyaw_cycle = {"v_iyaw_cycle", "2", 0, 2}; +cvar_t v_iroll_cycle = {"v_iroll_cycle", "0.5", 0, 0.5}; +cvar_t v_ipitch_cycle = {"v_ipitch_cycle", "1", 0, 1}; +cvar_t v_iyaw_level = {"v_iyaw_level", "0.3", 0, 0.3}; +cvar_t v_iroll_level = {"v_iroll_level", "0.1", 0, 0.1}; +cvar_t v_ipitch_level = {"v_ipitch_level", "0.3", 0, 0.3}; + +float v_idlescale; // used by TFC for concussion grenade effect + +/* +//============================================================================= +void V_NormalizeAngles( float *angles ) +{ + int i; + // Normalize angles + for ( i = 0; i < 3; i++ ) + { + if ( angles[i] > 180.0 ) + { + angles[i] -= 360.0; + } + else if ( angles[i] < -180.0 ) + { + angles[i] += 360.0; + } + } +} + +/* +=================== +V_InterpolateAngles + +Interpolate Euler angles. +FIXME: Use Quaternions to avoid discontinuities +Frac is 0.0 to 1.0 ( i.e., should probably be clamped, but doesn't have to be ) +=================== + +void V_InterpolateAngles( float *start, float *end, float *output, float frac ) +{ + int i; + float ang1, ang2; + float d; + + V_NormalizeAngles( start ); + V_NormalizeAngles( end ); + + for ( i = 0 ; i < 3 ; i++ ) + { + ang1 = start[i]; + ang2 = end[i]; + + d = ang2 - ang1; + if ( d > 180 ) + { + d -= 360; + } + else if ( d < -180 ) + { + d += 360; + } + + output[i] = ang1 + d * frac; + } + + V_NormalizeAngles( output ); +} */ + + +// Quakeworld bob code, this fixes jitters in the mutliplayer since the clock (pparams->time) isn't quite linear +float V_CalcBob ( struct ref_params_s *pparams ) +{ + static double bobtime; + static float bob; + float cycle; + static float lasttime; + vec3_t vel; + + + if ( pparams->onground == -1 || + pparams->time == lasttime ) + { + // just use old value + return bob; + } + + lasttime = pparams->time; + + bobtime += pparams->frametime; + cycle = bobtime - (int)( bobtime / cl_bobcycle->value ) * cl_bobcycle->value; + cycle /= cl_bobcycle->value; + + if ( cycle < cl_bobup->value ) + { + cycle = M_PI * cycle / cl_bobup->value; + } + else + { + cycle = M_PI + M_PI * ( cycle - cl_bobup->value )/( 1.0 - cl_bobup->value ); + } + + // bob is proportional to simulated velocity in the xy plane + // (don't count Z, or jumping messes it up) + VectorCopy( pparams->simvel, vel ); + vel[2] = 0; + + bob = sqrt( vel[0] * vel[0] + vel[1] * vel[1] ) * cl_bob->value; + bob = bob * 0.3 + bob * 0.7 * sin(cycle); + bob = min( bob, 4 ); + bob = max( bob, -7 ); + return bob; + +} + +/* +=============== +V_CalcRoll +Used by view and sv_user +=============== +*/ +float V_CalcRoll (vec3_t angles, vec3_t velocity, float rollangle, float rollspeed ) +{ + float sign; + float side; + float value; + vec3_t forward, right, up; + + AngleVectors ( angles, forward, right, up ); + + side = DotProduct (velocity, right); + sign = side < 0 ? -1 : 1; + side = fabs( side ); + + value = rollangle; + if (side < rollspeed) + { + side = side * value / rollspeed; + } + else + { + side = value; + } + return side * sign; +} + + +typedef struct pitchdrift_s +{ + float pitchvel; + int nodrift; + float driftmove; + double laststop; +} pitchdrift_t; + +static pitchdrift_t pd; + +void V_StartPitchDrift( void ) +{ + if ( pd.laststop == gEngfuncs.GetClientTime() ) + { + return; // something else is keeping it from drifting + } + + if ( pd.nodrift || !pd.pitchvel ) + { + pd.pitchvel = v_centerspeed->value; + pd.nodrift = 0; + pd.driftmove = 0; + } +} + +void V_StopPitchDrift ( void ) +{ + pd.laststop = gEngfuncs.GetClientTime(); + pd.nodrift = 1; + pd.pitchvel = 0; +} + +/* +=============== +V_DriftPitch + +Moves the client pitch angle towards idealpitch sent by the server. + +If the user is adjusting pitch manually, either with lookup/lookdown, +mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped. +=============== +*/ +#include "kbutton.h" +extern kbutton_t in_mlook; + +void V_DriftPitch ( struct ref_params_s *pparams ) +{ + float delta, move; + + if ( ( in_mlook.state & 1) || gEngfuncs.IsNoClipping() || !pparams->onground || pparams->demoplayback || pparams->spectator ) + { + pd.driftmove = 0; + pd.pitchvel = 0; + return; + } + + // don't count small mouse motion + if (pd.nodrift) + { + if ( fabs( pparams->cmd->forwardmove ) < cl_forwardspeed->value ) + pd.driftmove = 0; + else + pd.driftmove += pparams->frametime; + + if ( pd.driftmove > v_centermove->value) + { + V_StartPitchDrift (); + } + return; + } + + delta = pparams->idealpitch - pparams->cl_viewangles[PITCH]; + + if (!delta) + { + pd.pitchvel = 0; + return; + } + + move = pparams->frametime * pd.pitchvel; + pd.pitchvel += pparams->frametime * v_centerspeed->value; + +//Con_Printf ("move: %f (%f)\n", move, pparams->frametime); + + if (delta > 0) + { + if (move > delta) + { + pd.pitchvel = 0; + move = delta; + } + pparams->cl_viewangles[PITCH] += move; + } + else if (delta < 0) + { + if (move > -delta) + { + pd.pitchvel = 0; + move = -delta; + } + pparams->cl_viewangles[PITCH] -= move; + } +} + +/* +============================================================================== + VIEW RENDERING +============================================================================== +*/ + +/* +================== +V_CalcGunAngle +================== +*/ +void V_CalcGunAngle ( struct ref_params_s *pparams ) +{ + cl_entity_t *viewent; + + viewent = gEngfuncs.GetViewModel(); + if ( !viewent ) + return; + + viewent->angles[YAW] = pparams->viewangles[YAW] + pparams->crosshairangle[YAW]; + viewent->angles[PITCH] = -pparams->viewangles[PITCH] + pparams->crosshairangle[PITCH] * 0.25; + viewent->angles[ROLL] -= v_idlescale * sin(pparams->time*v_iroll_cycle.value) * v_iroll_level.value; + + // don't apply all of the v_ipitch to prevent normally unseen parts of viewmodel from coming into view. + viewent->angles[PITCH] -= v_idlescale * sin(pparams->time*v_ipitch_cycle.value) * (v_ipitch_level.value * 0.5); + viewent->angles[YAW] -= v_idlescale * sin(pparams->time*v_iyaw_cycle.value) * v_iyaw_level.value; + + VectorCopy( viewent->angles, viewent->curstate.angles ); + VectorCopy( viewent->angles, viewent->latched.prevangles ); +} + +/* +============== +V_AddIdle + +Idle swaying +============== +*/ +void V_AddIdle ( struct ref_params_s *pparams ) +{ + pparams->viewangles[ROLL] += v_idlescale * sin(pparams->time*v_iroll_cycle.value) * v_iroll_level.value; + pparams->viewangles[PITCH] += v_idlescale * sin(pparams->time*v_ipitch_cycle.value) * v_ipitch_level.value; + pparams->viewangles[YAW] += v_idlescale * sin(pparams->time*v_iyaw_cycle.value) * v_iyaw_level.value; +} + + +extern cvar_t *cl_rollspeed; +extern cvar_t *cl_rollangle; +/* +============== +V_CalcViewRoll + +Roll is induced by movement and damage +============== +*/ +void V_CalcViewRoll ( struct ref_params_s *pparams ) +{ + cl_entity_t *viewentity; + + viewentity = gEngfuncs.GetEntityByIndex( pparams->viewentity ); + if ( !viewentity ) + return; + + //Roll the angles when strafing Quake style! + pparams->viewangles[ROLL] = V_CalcRoll (pparams->viewangles, pparams->simvel, cl_rollangle->value, cl_rollspeed->value ) * 4; + + if ( pparams->health <= 0 && ( pparams->viewheight[2] != 0 ) ) + { + // only roll the view if the player is dead and the viewheight[2] is nonzero + // this is so deadcam in multiplayer will work. + pparams->viewangles[ROLL] = 80; // dead view angle + return; + } +} + + +/* +================== +V_CalcIntermissionRefdef + +================== +*/ +void V_CalcIntermissionRefdef ( struct ref_params_s *pparams ) +{ + cl_entity_t *ent, *view; + float old; + +// don't allow cheats in multiplayer + if ( pparams->maxclients > 1 ) + { + scr_ofsx->value = 0.0; + scr_ofsy->value = 0.0; + scr_ofsz->value = 0.0; + } + + // ent is the player model ( visible when out of body ) + ent = gEngfuncs.GetLocalPlayer(); + + // view is the weapon model (only visible from inside body ) + view = gEngfuncs.GetViewModel(); + + VectorCopy ( pparams->simorg, pparams->vieworg ); + VectorCopy ( pparams->cl_viewangles, pparams->viewangles ); + + view->model = NULL; + + // allways idle in intermission + old = v_idlescale; + v_idlescale = 1; + + V_AddIdle ( pparams ); + + if ( gEngfuncs.IsSpectateOnly() ) + { + // in HLTV we must go to 'intermission' position by ourself + VectorCopy( gHUD.m_Spectator.m_cameraOrigin, pparams->vieworg ); + VectorCopy( gHUD.m_Spectator.m_cameraAngles, pparams->viewangles ); + } + v_idlescale = old; + + v_cl_angles = pparams->cl_viewangles; + v_origin = pparams->vieworg; + v_angles = pparams->viewangles; +} + +#define ORIGIN_BACKUP 64 +#define ORIGIN_MASK ( ORIGIN_BACKUP - 1 ) + +typedef struct +{ + float Origins[ ORIGIN_BACKUP ][3]; + float OriginTime[ ORIGIN_BACKUP ]; + + float Angles[ ORIGIN_BACKUP ][3]; + float AngleTime[ ORIGIN_BACKUP ]; + + int CurrentOrigin; + int CurrentAngle; +} viewinterp_t; + +/* +================== +V_CalcRefdef + +================== +*/ +void V_CalcNormalRefdef ( struct ref_params_s *pparams ) +{ + cl_entity_t *ent, *view; + int i; + vec3_t angles; + float bob, waterOffset; + static viewinterp_t ViewInterp; + + static float oldz = 0; + static float lasttime; + + static float lastang[3]; + vec3_t angdelta; + + vec3_t camAngles, camForward, camRight, camUp; + cl_entity_t *pwater; + + //Force angle change + if ( bChangeAngles == true ) + { + pparams->cl_viewangles[PITCH] = vecTempAngles[PITCH]; + pparams->cl_viewangles[YAW] = vecTempAngles[YAW]; + pparams->cl_viewangles[ROLL] = vecTempAngles[ROLL]; + + vecTempAngles = Vector ( 0, 0, 0 ); + bChangeAngles = false; + } + + // don't allow cheats in multiplayer + if ( pparams->maxclients > 1 ) + { + gEngfuncs.Cvar_SetValue ("scr_ofsx", 0); + gEngfuncs.Cvar_SetValue ("scr_ofsy", 0); + gEngfuncs.Cvar_SetValue ("scr_ofsz", 0); + } + + V_DriftPitch ( pparams ); + + // ent is the player model ( visible when out of body ) + ent = gEngfuncs.GetLocalPlayer(); + + // view is the weapon model (only visible from inside body ) + view = gEngfuncs.GetViewModel(); + + // transform the view offset by the model's matrix to get the offset from + // model origin for the view + bob = V_CalcBob ( pparams ); + + // refresh position + VectorCopy ( pparams->simorg, pparams->vieworg ); + pparams->vieworg[2] += ( bob ); + VectorAdd( pparams->vieworg, pparams->viewheight, pparams->vieworg ); + + VectorCopy ( pparams->cl_viewangles, pparams->viewangles ); + + gEngfuncs.V_CalcShake(); + gEngfuncs.V_ApplyShake( pparams->vieworg, pparams->viewangles, 1.0 ); + + // never let view origin sit exactly on a node line, because a water plane can + // dissapear when viewed with the eye exactly on it. + // FIXME, we send origin at 1/128 now, change this? + // the server protocol only specifies to 1/16 pixel, so add 1/32 in each axis + + pparams->vieworg[0] += 1.0/32; + pparams->vieworg[1] += 1.0/32; + pparams->vieworg[2] += 1.0/32; + + // Check for problems around water, move the viewer artificially if necessary + // -- this prevents drawing errors in GL due to waves + + waterOffset = 0; + if ( pparams->waterlevel >= 2 ) + { + int i, contents, waterDist, waterEntity; + vec3_t point; + waterDist = cl_waterdist->value; + + if ( pparams->hardware ) + { + waterEntity = gEngfuncs.PM_WaterEntity( pparams->simorg ); + if ( waterEntity >= 0 && waterEntity < pparams->max_entities ) + { + pwater = gEngfuncs.GetEntityByIndex( waterEntity ); + if ( pwater && ( pwater->model != NULL ) ) + { + waterDist += ( pwater->curstate.scale * 16 ); // Add in wave height + } + } + } + else + { + waterEntity = 0; // Don't need this in software + } + + VectorCopy( pparams->vieworg, point ); + + // Eyes are above water, make sure we're above the waves + if ( pparams->waterlevel == 2 ) + { + point[2] -= waterDist; + for ( i = 0; i < waterDist; i++ ) + { + contents = gEngfuncs.PM_PointContents( point, NULL ); + if ( contents > CONTENTS_WATER ) + break; + point[2] += 1; + } + waterOffset = (point[2] + waterDist) - pparams->vieworg[2]; + } + else + { + // eyes are under water. Make sure we're far enough under + point[2] += waterDist; + + for ( i = 0; i < waterDist; i++ ) + { + contents = gEngfuncs.PM_PointContents( point, NULL ); + if ( contents <= CONTENTS_WATER ) + break; + point[2] -= 1; + } + waterOffset = (point[2] - waterDist) - pparams->vieworg[2]; + } + } + + pparams->vieworg[2] += waterOffset; + + V_CalcViewRoll ( pparams ); + + V_AddIdle ( pparams ); + + // offsets + VectorCopy( pparams->cl_viewangles, angles ); + + AngleVectors ( angles, pparams->forward, pparams->right, pparams->up ); + + for ( i=0 ; i<3 ; i++ ) + { + pparams->vieworg[i] += scr_ofsx->value*pparams->forward[i] + scr_ofsy->value*pparams->right[i] + scr_ofsz->value*pparams->up[i]; + } + + // Treating cam_ofs[2] as the distance + if( CL_IsThirdPerson() ) + { + vec3_t ofs; + + ofs[0] = ofs[1] = ofs[2] = 0.0; + + CL_CameraOffset( (float *)&ofs ); + + VectorCopy( ofs, camAngles ); + camAngles[ ROLL ] = 0; + + AngleVectors( camAngles, camForward, camRight, camUp ); + + for ( i = 0; i < 3; i++ ) + { + pparams->vieworg[ i ] += -ofs[2] * camForward[ i ]; + } + } + + // Give gun our viewangles + VectorCopy ( pparams->cl_viewangles, view->angles ); + + // set up gun position + V_CalcGunAngle ( pparams ); + + // Use predicted origin as view origin. + VectorCopy ( pparams->simorg, view->origin ); + view->origin[2] += ( waterOffset ); + VectorAdd( view->origin, pparams->viewheight, view->origin ); + + // Let the viewmodel shake at about 10% of the amplitude + gEngfuncs.V_ApplyShake( view->origin, view->angles, 0.9 ); + + for ( i = 0; i < 3; i++ ) + { + view->origin[ i ] += bob * 0.4 * pparams->forward[ i ]; + } + view->origin[2] += bob; + + // throw in a little tilt. + view->angles[YAW] -= bob * 0.5; + view->angles[ROLL] -= bob * 1; + view->angles[PITCH] -= bob * 0.3; + + // pushing the view origin down off of the same X/Z plane as the ent's origin will give the + // gun a very nice 'shifting' effect when the player looks up/down. If there is a problem + // with view model distortion, this may be a cause. (SJB). + view->origin[2] -= 1; + + // fudge position around to keep amount of weapon visible + // roughly equal with different FOV + if (pparams->viewsize == 110) + { + view->origin[2] += 1; + } + else if (pparams->viewsize == 100) + { + view->origin[2] += 2; + } + else if (pparams->viewsize == 90) + { + view->origin[2] += 1; + } + else if (pparams->viewsize == 80) + { + view->origin[2] += 0.5; + } + + // Add in the punchangle, if any + VectorAdd ( pparams->viewangles, pparams->punchangle, pparams->viewangles ); + + // Include client side punch, too + VectorAdd ( pparams->viewangles, (float *)&ev_punchangle, pparams->viewangles); + + V_DropPunchAngle ( pparams->frametime, (float *)&ev_punchangle ); + + // smooth out stair step ups +#if 1 + if ( !pparams->smoothing && pparams->onground && pparams->simorg[2] - oldz > 0) + { + float steptime; + + steptime = pparams->time - lasttime; + if (steptime < 0) + //FIXME I_Error ("steptime < 0"); + steptime = 0; + + oldz += steptime * 150; + if (oldz > pparams->simorg[2]) + oldz = pparams->simorg[2]; + if (pparams->simorg[2] - oldz > 18) + oldz = pparams->simorg[2]- 18; + pparams->vieworg[2] += oldz - pparams->simorg[2]; + view->origin[2] += oldz - pparams->simorg[2]; + } + else + { + oldz = pparams->simorg[2]; + } +#endif + + { + static float lastorg[3]; + vec3_t delta; + + VectorSubtract( pparams->simorg, lastorg, delta ); + + if ( Length( delta ) != 0.0 ) + { + VectorCopy( pparams->simorg, ViewInterp.Origins[ ViewInterp.CurrentOrigin & ORIGIN_MASK ] ); + ViewInterp.OriginTime[ ViewInterp.CurrentOrigin & ORIGIN_MASK ] = pparams->time; + ViewInterp.CurrentOrigin++; + + VectorCopy( pparams->simorg, lastorg ); + } + } + + // Smooth out whole view in multiplayer when on trains, lifts + if ( cl_vsmoothing && cl_vsmoothing->value && + ( pparams->smoothing && ( pparams->maxclients > 1 ) ) ) + { + int foundidx; + int i; + float t; + + if ( cl_vsmoothing->value < 0.0 ) + { + gEngfuncs.Cvar_SetValue( "cl_vsmoothing", 0.0 ); + } + + t = pparams->time - cl_vsmoothing->value; + + for ( i = 1; i < ORIGIN_MASK; i++ ) + { + foundidx = ViewInterp.CurrentOrigin - 1 - i; + if ( ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ] <= t ) + break; + } + + if ( i < ORIGIN_MASK && ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ] != 0.0 ) + { + // Interpolate + vec3_t delta; + double frac; + double dt; + vec3_t neworg; + + dt = ViewInterp.OriginTime[ (foundidx + 1) & ORIGIN_MASK ] - ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ]; + if ( dt > 0.0 ) + { + frac = ( t - ViewInterp.OriginTime[ foundidx & ORIGIN_MASK] ) / dt; + frac = min( 1.0, frac ); + VectorSubtract( ViewInterp.Origins[ ( foundidx + 1 ) & ORIGIN_MASK ], ViewInterp.Origins[ foundidx & ORIGIN_MASK ], delta ); + VectorMA( ViewInterp.Origins[ foundidx & ORIGIN_MASK ], frac, delta, neworg ); + + VectorSubtract( neworg, pparams->simorg, delta ); + + VectorAdd( pparams->simorg, delta, pparams->simorg ); + VectorAdd( pparams->vieworg, delta, pparams->vieworg ); + VectorAdd( view->origin, delta, view->origin ); + } + } + } + + // Store off v_angles before munging for third person + v_angles = pparams->viewangles; + v_cl_angles = pparams->cl_viewangles; + + if ( CL_IsThirdPerson() ) + { + VectorCopy( camAngles, pparams->viewangles); + } + + // override all previous settings if the viewent isn't the client + if ( pparams->viewentity > pparams->maxclients ) + { + cl_entity_t *viewentity; + viewentity = gEngfuncs.GetEntityByIndex( pparams->viewentity ); + if ( viewentity ) + { + VectorCopy( viewentity->origin, pparams->vieworg ); + VectorCopy( viewentity->angles, pparams->viewangles ); + + // Store off overridden viewangles + v_angles = pparams->viewangles; + } + } + + lasttime = pparams->time; + + v_origin = pparams->vieworg; +} +void V_SmoothInterpolateAngles( float * startAngle, float * endAngle, float * finalAngle, float degreesPerSec ) +{ + float frac,d; + + NormalizeAngles( startAngle ); + NormalizeAngles( endAngle ); + + for ( int i = 0 ; i < 3 ; i++ ) + { + d = endAngle[i] - startAngle[i]; + + if ( d > 180.0f ) + { + d -= 360.0f; + } + else if ( d < -180.0f ) + { + d += 360.0f; + } + + float absd = fabs(d); + + if ( absd > 0.1f ) + { + frac = (degreesPerSec * v_frametime ) / absd; + + if ( absd < 45.0f ) + frac*= absd / 45.0f; // slow down last 45 degrees + + if ( frac > 1.0f ) + { + finalAngle[i] = endAngle[i]; + } + else + { + finalAngle[i] = startAngle[i] + d * frac; + } + } + else + { + finalAngle[i] = endAngle[i]; + } + + } + + NormalizeAngles( finalAngle ); +} + +// Get the origin of the Observer based around the target's position and angles +void V_GetChaseOrigin( float * angles, float * origin, float distance, qboolean worldOnly, float * returnvec ) +{ + int tracefinished = false; + vec3_t vecEnd; + vec3_t forward; + vec3_t vecStart; + pmtrace_t * trace; + + // Trace back from the target using the player's view angles + AngleVectors(angles, forward, NULL, NULL); + + VectorScale(forward,-1,forward); + + VectorCopy( origin, vecStart ); + + VectorMA(vecStart, distance , forward, vecEnd); + + while (!tracefinished) + { + + trace = gEngfuncs.PM_TraceLine( vecStart, vecEnd, PM_TRACELINE_PHYSENTSONLY, 2, -1 ); + + if ( trace->ent <= 0 || !worldOnly) + { + tracefinished = true; + } + else + { + if( Distance(trace->endpos, vecEnd ) > 1.0f ) + { + VectorAdd( trace->endpos, forward, vecStart); + } + else + { + tracefinished = true; + } + } + } + + // gEngfuncs.Con_Printf("Trace loop %i\n", trace->ent ); + + VectorMA( trace->endpos, 4, trace->plane.normal, returnvec ); + + v_lastDistance = Distance(trace->endpos, origin); // real distance without offset +} + +void V_GetDirectedChasePosition(cl_entity_t * ent1, cl_entity_t * ent2,float * angle, float * origin) +{ + float newAngle[3]; float newOrigin[3]; float tempVec[3]; + + int flags = gHUD.m_Spectator.m_iObserverFlags; + + qboolean deadPlayer = ent1->player && (ent1->curstate.solid == SOLID_NOT); + + float dfactor = ( flags & DRC_FLAG_DRAMATIC )? -1.0f : 1.0f; + + if ( ent1->player && (ent1->curstate.solid == SOLID_NOT) ) + dfactor = 1.5f; // zoom away if player dies + + float distance = 112.0f + ( 16.0f * dfactor ); // get close if dramatic; + + // go away in final scenes + if (flags & DRC_FLAG_FINAL ) + distance*=2.0f; + + // let v_lastDistance float smoothly away + v_lastDistance+= v_frametime * 24.0f; // move unit per seconds back + + if ( distance > v_lastDistance ) + distance = v_lastDistance; + + + VectorCopy(ent1->origin, newOrigin); + + if ( ent1->player ) + { + if ( deadPlayer ) + newOrigin[2]+= 2; //laying on ground + else + newOrigin[2]+= 28; // head level of living player + + } + else + newOrigin[2]+= 8; // object, tricky, must be above bomb in CS + + if ( ( ent2 == (cl_entity_t*)0xFFFFFFFF ) || deadPlayer ) // we have no second target or player just died + { + // we have no second target, choose view direction based on + // show front of primary target + VectorCopy(ent1->angles, newAngle); + newAngle[1]+= 180.0f; + + newAngle[0]+= 12.5f * dfactor; // lower angle if dramatic + + // if final scene (bomb), show from real high pos + if ( flags & DRC_FLAG_FINAL ) + newAngle[0] = 22.5f; + + // choose side of object/player + if ( flags & DRC_FLAG_SIDE ) + newAngle[1]+=22.5f; + else + newAngle[1]-=22.5f; + + // if ( AngleBetweenVectors( tempVec, newAngle ) > 1.0f ) + V_SmoothInterpolateAngles( v_lastAngles, newAngle, angle, 120.0f ); + + // HACK, if player is dead don't clip against his dead body, can't check this + V_GetChaseOrigin( angle, newOrigin, distance, deadPlayer, origin ); + + } + else if ( ent2 ) + { + // get new angle towards second target + VectorSubtract( ent2->origin, ent1->origin, newAngle ); + + VectorAngles( newAngle, newAngle ); + newAngle[0] = -newAngle[0]; + + // set angle diffrent in Dramtaic scenes + newAngle[0]+= 12.5f * dfactor; // lower angle if dramatic + + if ( flags & DRC_FLAG_SIDE ) + newAngle[1]+=22.5f; + else + newAngle[1]-=22.5f; + + V_GetChaseOrigin( newAngle, newOrigin, distance, false, origin ); + + origin[2]+= 16.0f*( 1.0f - (v_lastDistance / distance) ); + + // calculate angle to second target + VectorSubtract( ent2->origin, origin, tempVec ); + VectorAngles( tempVec, tempVec ); + tempVec[0] = -tempVec[0]; + + // take middle between two viewangles + InterpolateAngles( newAngle, tempVec, angle, 0.5f); + + } + else + { + // second target disappeard somehow (dead) + // keep last good viewangle + V_GetChaseOrigin( angle, newOrigin, distance, false, origin ); + } + + VectorCopy(angle, v_lastAngles); +} + +void V_GetChasePos(int target, float * cl_angles, float * origin, float * angles) +{ + if ( !target) + { + // just copy a save in-map position + VectorCopy ( vJumpAngles, angles ); + VectorCopy ( vJumpOrigin, origin ); + return; + }; + + cl_entity_t * ent = gEngfuncs.GetEntityByIndex( target ); + + if (!ent) return; + + + if ( gHUD.m_Spectator.m_autoDirector->value ) + { + if ( g_iUser3 ) + V_GetDirectedChasePosition( ent, gEngfuncs.GetEntityByIndex( g_iUser3 ), + angles, origin ); + else + V_GetDirectedChasePosition( ent, ( cl_entity_t*)0xFFFFFFFF, + angles, origin ); + } + else + { + if ( cl_angles == NULL ) // no mouse angles given, use entity angles ( locked mode ) + { + VectorCopy ( ent->angles, angles); + angles[0]*=-1; + } + else + VectorCopy ( cl_angles, angles); + + + VectorCopy ( ent->origin, origin); + + origin[2]+= 28; // DEFAULT_VIEWHEIGHT - some offset + + V_GetChaseOrigin( angles, origin, cl_chasedist->value, false, origin ); + } +} + +void V_ResetChaseCam() +{ + v_lastDistance = 4096.0f; +} + + +void V_GetInEyePos(int target, float * origin, float * angles ) +{ + if ( !target) + { + // just copy a save in-map position + VectorCopy ( vJumpAngles, angles ); + VectorCopy ( vJumpOrigin, origin ); + return; + }; + + + cl_entity_t * ent = gEngfuncs.GetEntityByIndex( target ); + + if ( !ent ) + return; + + VectorCopy ( ent->origin, origin ); + VectorCopy ( ent->angles, angles ); + + angles[0]*=-M_PI; + + if ( ent->curstate.solid == SOLID_NOT ) + { + angles[ROLL] = 80; // dead view angle + origin[2]+= -8 ; // PM_DEAD_VIEWHEIGHT + } + else if (ent->curstate.usehull == 1 ) + origin[2]+= 12; // VEC_DUCK_VIEW; + else + // exacty eye position can't be caluculated since it depends on + // client values like cl_bobcycle, this offset matches the default values + origin[2]+= 28; // DEFAULT_VIEWHEIGHT +} + +void V_GetMapFreePosition( float * cl_angles, float * origin, float * angles ) +{ + vec3_t forward; + vec3_t zScaledTarget; + + VectorCopy(cl_angles, angles); + + // modify angles since we don't wanna see map's bottom + angles[0] = 51.25f + 38.75f*(angles[0]/90.0f); + + zScaledTarget[0] = gHUD.m_Spectator.m_mapOrigin[0]; + zScaledTarget[1] = gHUD.m_Spectator.m_mapOrigin[1]; + zScaledTarget[2] = gHUD.m_Spectator.m_mapOrigin[2] * (( 90.0f - angles[0] ) / 90.0f ); + + + AngleVectors(angles, forward, NULL, NULL); + + VectorNormalize(forward); + + VectorMA(zScaledTarget, -( 4096.0f / gHUD.m_Spectator.m_mapZoom ), forward , origin); +} + +void V_GetMapChasePosition(int target, float * cl_angles, float * origin, float * angles) +{ + vec3_t forward; + + if ( target ) + { + cl_entity_t * ent = gEngfuncs.GetEntityByIndex( target ); + + if ( gHUD.m_Spectator.m_autoDirector->value ) + { + // this is done to get the angles made by director mode + V_GetChasePos(target, cl_angles, origin, angles); + VectorCopy(ent->origin, origin); + + // keep fix chase angle horizontal + angles[0] = 45.0f; + } + else + { + VectorCopy(cl_angles, angles); + VectorCopy(ent->origin, origin); + + // modify angles since we don't wanna see map's bottom + angles[0] = 51.25f + 38.75f*(angles[0]/90.0f); + } + } + else + { + // keep out roaming position, but modify angles + VectorCopy(cl_angles, angles); + angles[0] = 51.25f + 38.75f*(angles[0]/90.0f); + } + + origin[2] *= (( 90.0f - angles[0] ) / 90.0f ); + angles[2] = 0.0f; // don't roll angle (if chased player is dead) + + AngleVectors(angles, forward, NULL, NULL); + + VectorNormalize(forward); + + VectorMA(origin, -1536, forward, origin); +} + +int V_FindViewModelByWeaponModel(int weaponindex) +{ + + static char * modelmap[][2] = { + +#ifdef THREEWAVE + // { "models/p_grapple.mdl", "models/v_grapple.mdl" }, +#endif + + { "models/p_crowbar.mdl", "models/v_crowbar.mdl" }, + { "models/p_shot.mdl", "models/v_shot.mdl" }, + { "models/p_shot2.mdl", "models/v_shot2.mdl" }, + { "models/p_nail.mdl", "models/v_nail.mdl" }, + { "models/p_nail2.mdl", "models/v_nail2.mdl" }, + { "models/p_rock.mdl", "models/v_rock.mdl" }, + { "models/p_rock2.mdl", "models/v_rock2.mdl" }, + { "models/p_light.mdl", "models/v_light.mdl" }, + { NULL, NULL } }; + + struct model_s * weaponModel = IEngineStudio.GetModelByIndex( weaponindex ); + + if ( weaponModel ) + { + int len = strlen( weaponModel->name ); + int i = 0; + + while ( *modelmap[i] != NULL ) + { + if ( !strnicmp( weaponModel->name, modelmap[i][0], len ) ) + { + return gEngfuncs.pEventAPI->EV_FindModelIndex( modelmap[i][1] ); + } + i++; + } + + return 0; + } + else + return 0; + +} + + +/* +================== +V_CalcSpectatorRefdef + +================== +*/ +void V_CalcSpectatorRefdef ( struct ref_params_s * pparams ) +{ + + vec3_t angles; + static viewinterp_t ViewInterp; + static float bob = 0.0f; + static vec3_t velocity ( 0.0f, 0.0f, 0.0f); + + static int lastWeaponModelIndex = 0; + static int lastViewModelIndex = 0; + + cl_entity_t * ent = gEngfuncs.GetEntityByIndex( g_iUser2 ); + cl_entity_t * gunModel = gEngfuncs.GetViewModel(); + static float lasttime; + + static float lastang[3]; + static float lastorg[3]; + + vec3_t delta; + pparams->onlyClientDraw = false; + + // refresh position + VectorCopy ( pparams->simorg, v_sim_org ); + + // get old values + VectorCopy ( pparams->cl_viewangles, v_cl_angles ); + VectorCopy ( pparams->viewangles, v_angles ); + VectorCopy ( pparams->vieworg, v_origin ); + v_frametime = pparams->frametime; + + if ( pparams->nextView == 0 ) + { + // first renderer cycle, full screen + + switch ( g_iUser1 ) + { + case OBS_CHASE_LOCKED: V_GetChasePos( g_iUser2, NULL, v_origin, v_angles ); + break; + + case OBS_CHASE_FREE: V_GetChasePos( g_iUser2, v_cl_angles, v_origin, v_angles ); + break; + + case OBS_ROAMING : VectorCopy (v_cl_angles, v_angles); + VectorCopy (v_sim_org, v_origin); + break; + + case OBS_IN_EYE : V_GetInEyePos( g_iUser2, v_origin, v_angles ); + break; + + case OBS_MAP_FREE : pparams->onlyClientDraw = true; + V_GetMapFreePosition( v_cl_angles, v_origin, v_angles ); + break; + + case OBS_MAP_CHASE : pparams->onlyClientDraw = true; + V_GetMapChasePosition( g_iUser2, v_cl_angles, v_origin, v_angles ); + break; + } + + if ( gHUD.m_Spectator.m_pip->value ) + pparams->nextView = 1; // force a second renderer view + + gHUD.m_Spectator.m_iDrawCycle = 0; + + } + else + { + // second renderer cycle, inset window + + // set inset parameters + pparams->viewport[0] = XRES(gHUD.m_Spectator.m_OverviewData.insetWindowX); // change viewport to inset window + pparams->viewport[1] = YRES(gHUD.m_Spectator.m_OverviewData.insetWindowY); + pparams->viewport[2] = XRES(gHUD.m_Spectator.m_OverviewData.insetWindowWidth); + pparams->viewport[3] = YRES(gHUD.m_Spectator.m_OverviewData.insetWindowHeight); + pparams->nextView = 0; // on further view + pparams->onlyClientDraw = false; + + // override some settings in certain modes + switch ( (int)gHUD.m_Spectator.m_pip->value ) + { + case INSET_CHASE_FREE : V_GetChasePos( g_iUser2, v_cl_angles, v_origin, v_angles ); + break; + + case INSET_IN_EYE : V_GetInEyePos( g_iUser2, v_origin, v_angles ); + break; + + case INSET_MAP_FREE : pparams->onlyClientDraw = true; + V_GetMapFreePosition( v_cl_angles, v_origin, v_angles ); + break; + + case INSET_MAP_CHASE : pparams->onlyClientDraw = true; + + if ( g_iUser1 == OBS_ROAMING ) + V_GetMapChasePosition( 0, v_cl_angles, v_origin, v_angles ); + else + V_GetMapChasePosition( g_iUser2, v_cl_angles, v_origin, v_angles ); + + break; + } + + gHUD.m_Spectator.m_iDrawCycle = 1; + } + + + // do the smoothing only once per frame, not in roaming or map mode + if ( (gHUD.m_Spectator.m_iDrawCycle == 0) && (g_iUser1 == OBS_IN_EYE) ) + { + // smooth angles + + VectorSubtract( v_angles, lastang, delta ); + if ( Length( delta ) != 0.0f ) + { + VectorCopy( v_angles, ViewInterp.Angles[ ViewInterp.CurrentAngle & ORIGIN_MASK ] ); + ViewInterp.AngleTime[ ViewInterp.CurrentAngle & ORIGIN_MASK ] = pparams->time; + ViewInterp.CurrentAngle++; + VectorCopy( v_angles, lastang ); + } + + if ( cl_vsmoothing && cl_vsmoothing->value ) + { + int foundidx; + int i; + float t; + + t = pparams->time - cl_vsmoothing->value; + + for ( i = 1; i < ORIGIN_MASK; i++ ) + { + foundidx = ViewInterp.CurrentAngle - 1 - i; + if ( ViewInterp.AngleTime[ foundidx & ORIGIN_MASK ] <= t ) + break; + } + + if ( i < ORIGIN_MASK && ViewInterp.AngleTime[ foundidx & ORIGIN_MASK ] != 0.0 ) + { + // Interpolate + double dt; + float da; + vec3_t v1,v2; + + AngleVectors( ViewInterp.Angles[ foundidx & ORIGIN_MASK ], v1, NULL, NULL ); + AngleVectors( ViewInterp.Angles[ (foundidx + 1) & ORIGIN_MASK ], v2, NULL, NULL ); + da = AngleBetweenVectors( v1, v2 ); + + dt = ViewInterp.AngleTime[ (foundidx + 1) & ORIGIN_MASK ] - ViewInterp.AngleTime[ foundidx & ORIGIN_MASK ]; + + if ( dt > 0.0 && ( da < 22.5f) ) + { + double frac; + + frac = ( t - ViewInterp.AngleTime[ foundidx & ORIGIN_MASK] ) / dt; + frac = min( 1.0, frac ); + + // interpolate angles + InterpolateAngles( ViewInterp.Angles[ foundidx & ORIGIN_MASK ], ViewInterp.Angles[ (foundidx + 1) & ORIGIN_MASK ], v_angles, frac ); + } + } + } + + // smooth origin + + VectorSubtract( v_origin, lastorg, delta ); + + if ( Length( delta ) != 0.0 ) + { + VectorCopy( v_origin, ViewInterp.Origins[ ViewInterp.CurrentOrigin & ORIGIN_MASK ] ); + ViewInterp.OriginTime[ ViewInterp.CurrentOrigin & ORIGIN_MASK ] = pparams->time; + ViewInterp.CurrentOrigin++; + + VectorCopy( v_origin, lastorg ); + } + + // don't smooth in roaming (already smoothd), + if ( cl_vsmoothing && cl_vsmoothing->value ) + { + int foundidx; + int i; + float t; + + t = pparams->time - cl_vsmoothing->value; + + for ( i = 1; i < ORIGIN_MASK; i++ ) + { + foundidx = ViewInterp.CurrentOrigin - 1 - i; + if ( ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ] <= t ) + break; + } + + if ( i < ORIGIN_MASK && ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ] != 0.0 ) + { + // Interpolate + vec3_t delta; + double frac; + double dt; + vec3_t neworg; + + dt = ViewInterp.OriginTime[ (foundidx + 1) & ORIGIN_MASK ] - ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ]; + if ( dt > 0.0 ) + { + frac = ( t - ViewInterp.OriginTime[ foundidx & ORIGIN_MASK] ) / dt; + frac = min( 1.0, frac ); + VectorSubtract( ViewInterp.Origins[ ( foundidx + 1 ) & ORIGIN_MASK ], ViewInterp.Origins[ foundidx & ORIGIN_MASK ], delta ); + VectorMA( ViewInterp.Origins[ foundidx & ORIGIN_MASK ], frac, delta, neworg ); + + // Dont interpolate large changes + if ( Length( delta ) < 64 ) + { + VectorCopy( neworg, v_origin ); + } + } + } + } + } + + // Hack in weapon model: + + + if ( (g_iUser1 == OBS_IN_EYE || gHUD.m_Spectator.m_pip->value == INSET_IN_EYE) + && ent && g_iUser2 ) + { + // get position for weapon model + VectorCopy( v_origin, gunModel->origin); + VectorCopy( v_angles, gunModel->angles); + + // add idle tremble + gunModel->angles[PITCH]*=-1; + + // calculate player velocity + float timeDiff = ent->curstate.msg_time - ent->prevstate.msg_time; + + if ( timeDiff > 0 ) + { + vec3_t distance; + VectorSubtract(ent->prevstate.origin, ent->curstate.origin, distance); + VectorScale(distance, 1/timeDiff, distance ); + + velocity[0] = velocity[0]*0.66f + distance[0]*0.33f; + velocity[1] = velocity[1]*0.66f + distance[1]*0.33f; + velocity[2] = velocity[2]*0.66f + distance[2]*0.33f; + + VectorCopy(velocity, pparams->simvel); + pparams->onground = 1; + + bob = V_CalcBob( pparams ); + } + + vec3_t forward; + AngleVectors(v_angles, forward, NULL, NULL ); + + for ( int i = 0; i < 3; i++ ) + { + gunModel->origin[ i ] += bob * 0.4 * forward[ i ]; + } + + // throw in a little tilt. + gunModel->angles[YAW] -= bob * 0.5; + gunModel->angles[ROLL] -= bob * 1; + gunModel->angles[PITCH] -= bob * 0.3; + + VectorCopy( gunModel->angles, gunModel->curstate.angles ); + VectorCopy( gunModel->angles, gunModel->latched.prevangles ); + + if ( lastWeaponModelIndex != ent->curstate.weaponmodel ) + { + // weapon model changed + + lastWeaponModelIndex = ent->curstate.weaponmodel; + lastViewModelIndex = V_FindViewModelByWeaponModel( lastWeaponModelIndex ); + if ( lastViewModelIndex ) + { + gEngfuncs.pfnWeaponAnim(0,0); // reset weapon animation + } + else + { + // model not found + gunModel->model = NULL; // disable weaopn model + lastWeaponModelIndex = lastViewModelIndex = 0; + } + } + + if ( lastViewModelIndex ) + { + gunModel->model = IEngineStudio.GetModelByIndex( lastViewModelIndex ); + gunModel->curstate.modelindex = lastViewModelIndex; + gunModel->curstate.frame = 0; + gunModel->curstate.colormap = 0; + gunModel->index = g_iUser2; + } + else + { + gunModel->model = NULL; // disable weaopn model + } + } + else + { + gunModel->model = NULL; // disable weaopn model + lastWeaponModelIndex = lastViewModelIndex = 0; + } + + lasttime = pparams->time; + + // write back new values into pparams + + VectorCopy ( v_angles, pparams->viewangles ) + VectorCopy ( v_origin, pparams->vieworg ); + +} + +void EXPORT V_CalcRefdef( struct ref_params_s *pparams ) +{ + // intermission / finale rendering + if ( pparams->intermission ) + { + V_CalcIntermissionRefdef ( pparams ); + } + else if ( pparams->spectator || g_iUser1 ) // g_iUser true if in spectator mode + { + V_CalcSpectatorRefdef ( pparams ); + } + else if ( !pparams->paused ) + { + V_CalcNormalRefdef ( pparams ); + } +} + +/* +============= +V_DropPunchAngle + +============= +*/ +void V_DropPunchAngle ( float frametime, float *ev_punchangle ) +{ + float len; + + len = VectorNormalize ( ev_punchangle ); + len -= (10.0 + len * 0.5) * frametime; + len = max( len, 0.0 ); + VectorScale ( ev_punchangle, len, ev_punchangle ); +} + +/* +============= +V_PunchAxis + +Client side punch effect +============= +*/ +void V_PunchAxis( int axis, float punch ) +{ + ev_punchangle[ axis ] = punch; +} + +/* +============= +V_Init +============= +*/ +void V_Init (void) +{ + gEngfuncs.pfnAddCommand ("centerview", V_StartPitchDrift ); + + scr_ofsx = gEngfuncs.pfnRegisterVariable( "scr_ofsx","0", 0 ); + scr_ofsy = gEngfuncs.pfnRegisterVariable( "scr_ofsy","0", 0 ); + scr_ofsz = gEngfuncs.pfnRegisterVariable( "scr_ofsz","0", 0 ); + + v_centermove = gEngfuncs.pfnRegisterVariable( "v_centermove", "0.15", 0 ); + v_centerspeed = gEngfuncs.pfnRegisterVariable( "v_centerspeed","500", 0 ); + + cl_bobcycle = gEngfuncs.pfnRegisterVariable( "cl_bobcycle","0.8", 0 );// best default for my experimental gun wag (sjb) + cl_bob = gEngfuncs.pfnRegisterVariable( "cl_bob","0.01", 0 );// best default for my experimental gun wag (sjb) + cl_bobup = gEngfuncs.pfnRegisterVariable( "cl_bobup","0.5", 0 ); + cl_waterdist = gEngfuncs.pfnRegisterVariable( "cl_waterdist","4", 0 ); + cl_chasedist = gEngfuncs.pfnRegisterVariable( "cl_chasedist","112", 0 ); +} + + +//#define TRACE_TEST +#if defined( TRACE_TEST ) + +extern float in_fov; +/* +==================== +CalcFov +==================== +*/ +float CalcFov (float fov_x, float width, float height) +{ + float a; + float x; + + if (fov_x < 1 || fov_x > 179) + fov_x = 90; // error, set to 90 + + x = width/tan(fov_x/360*M_PI); + + a = atan (height/x); + + a = a*360/M_PI; + + return a; +} + +int hitent = -1; + +void V_Move( int mx, int my ) +{ + float fov; + float fx, fy; + float dx, dy; + float c_x, c_y; + float dX, dY; + vec3_t forward, up, right; + vec3_t newangles; + + vec3_t farpoint; + pmtrace_t tr; + + fov = CalcFov( in_fov, (float)ScreenWidth, (float)ScreenHeight ); + + c_x = (float)ScreenWidth / 2.0; + c_y = (float)ScreenHeight / 2.0; + + dx = (float)mx - c_x; + dy = (float)my - c_y; + + // Proportion we moved in each direction + fx = dx / c_x; + fy = dy / c_y; + + dX = fx * in_fov / 2.0 ; + dY = fy * fov / 2.0; + + newangles = v_angles; + + newangles[ YAW ] -= dX; + newangles[ PITCH ] += dY; + + // Now rotate v_forward around that point + AngleVectors ( newangles, forward, right, up ); + + farpoint = v_origin + 8192 * forward; + + // Trace + tr = *(gEngfuncs.PM_TraceLine( (float *)&v_origin, (float *)&farpoint, PM_TRACELINE_PHYSENTSONLY, 2 /*point sized hull*/, -1 )); + + if ( tr.fraction != 1.0 && tr.ent != 0 ) + { + hitent = PM_GetInfo( tr.ent ); + PM_ParticleLine( (float *)&v_origin, (float *)&tr.endpos, 5, 1.0, 0.0 ); + } + else + { + hitent = -1; + } +} + +#endif diff --git a/dmc/cl_dll/view.h b/dmc/cl_dll/view.h new file mode 100644 index 0000000..1afbe38 --- /dev/null +++ b/dmc/cl_dll/view.h @@ -0,0 +1,15 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined ( VIEWH ) +#define VIEWH +#pragma once + +void V_StartPitchDrift( void ); +void V_StopPitchDrift( void ); + +#endif // !VIEWH \ No newline at end of file diff --git a/dmc/cl_dll/voice_status.cpp b/dmc/cl_dll/voice_status.cpp new file mode 100644 index 0000000..f6cda4d --- /dev/null +++ b/dmc/cl_dll/voice_status.cpp @@ -0,0 +1,885 @@ +//========= Copyright � 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// There are hud.h's coming out of the woodwork so this ensures that we get the right one. +#if defined(THREEWAVE) || defined(DMC_BUILD) + #include "../dmc/cl_dll/hud.h" +#elif defined(CSTRIKE) + #include "../cstrike/cl_dll/hud.h" +#elif defined(DOD) + #include "../dod/cl_dll/hud.h" +#else + #include "hud.h" +#endif + +#include "cl_util.h" +#include +#include +#include +#include "parsemsg.h" +#include "hud_servers.h" +#include "demo.h" +#include "demo_api.h" +#include "voice_status.h" +#include "r_efx.h" +#include "entity_types.h" +#include "VGUI_ActionSignal.h" +#include "VGUI_Scheme.h" +#include "VGUI_TextImage.h" +#include "vgui_loadtga.h" +#include "vgui_helpers.h" +#include "VGUI_MouseCode.h" + + + +using namespace vgui; + + +extern int cam_thirdperson; + + +#define VOICE_MODEL_INTERVAL 0.3 +#define SCOREBOARD_BLINK_FREQUENCY 0.3 // How often to blink the scoreboard icons. +#define SQUELCHOSCILLATE_PER_SECOND 2.0f + + +extern BitmapTGA *LoadTGA( const char* pImageName ); + + + +// ---------------------------------------------------------------------- // +// The voice manager for the client. +// ---------------------------------------------------------------------- // +CVoiceStatus g_VoiceStatus; + +CVoiceStatus* GetClientVoiceMgr() +{ + return &g_VoiceStatus; +} + + + +// ---------------------------------------------------------------------- // +// CVoiceStatus. +// ---------------------------------------------------------------------- // + +static CVoiceStatus *g_pInternalVoiceStatus = NULL; + +int __MsgFunc_VoiceMask(const char *pszName, int iSize, void *pbuf) +{ + if(g_pInternalVoiceStatus) + g_pInternalVoiceStatus->HandleVoiceMaskMsg(iSize, pbuf); + + return 1; +} + +int __MsgFunc_ReqState(const char *pszName, int iSize, void *pbuf) +{ + if(g_pInternalVoiceStatus) + g_pInternalVoiceStatus->HandleReqStateMsg(iSize, pbuf); + + return 1; +} + + +int g_BannedPlayerPrintCount; +void ForEachBannedPlayer(char id[16]) +{ + char str[256]; + sprintf(str, "Ban %d: %2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x\n", + g_BannedPlayerPrintCount++, + id[0], id[1], id[2], id[3], + id[4], id[5], id[6], id[7], + id[8], id[9], id[10], id[11], + id[12], id[13], id[14], id[15] + ); +#ifdef _WIN32 + strupr(str); +#endif + gEngfuncs.pfnConsolePrint(str); +} + + +void ShowBannedCallback() +{ + if(g_pInternalVoiceStatus) + { + g_BannedPlayerPrintCount = 0; + gEngfuncs.pfnConsolePrint("------- BANNED PLAYERS -------\n"); + g_pInternalVoiceStatus->m_BanMgr.ForEachBannedPlayer(ForEachBannedPlayer); + gEngfuncs.pfnConsolePrint("------------------------------\n"); + } +} + + +// ---------------------------------------------------------------------- // +// CVoiceStatus. +// ---------------------------------------------------------------------- // + +CVoiceStatus::CVoiceStatus() +{ + m_bBanMgrInitialized = false; + m_LastUpdateServerState = 0; + + m_pSpeakerLabelIcon = NULL; + m_pScoreboardNeverSpoken = NULL; + m_pScoreboardNotSpeaking = NULL; + m_pScoreboardSpeaking = NULL; + m_pScoreboardSpeaking2 = NULL; + m_pScoreboardSquelch = NULL; + m_pScoreboardBanned = NULL; + + m_pLocalBitmap = NULL; + m_pAckBitmap = NULL; + + m_bTalking = m_bServerAcked = false; + + memset(m_pBanButtons, 0, sizeof(m_pBanButtons)); + + m_pParentPanel = NULL; + + m_bServerModEnable = -1; + + m_pchGameDir = NULL; +} + + +CVoiceStatus::~CVoiceStatus() +{ + g_pInternalVoiceStatus = NULL; + + for(int i=0; i < MAX_VOICE_SPEAKERS; i++) + { + delete m_Labels[i].m_pLabel; + m_Labels[i].m_pLabel = NULL; + + delete m_Labels[i].m_pIcon; + m_Labels[i].m_pIcon = NULL; + + delete m_Labels[i].m_pBackground; + m_Labels[i].m_pBackground = NULL; + } + + delete m_pLocalLabel; + m_pLocalLabel = NULL; + + FreeBitmaps(); + + if(m_pchGameDir) + { + if(m_bBanMgrInitialized) + { + m_BanMgr.SaveState(m_pchGameDir); + } + + free(m_pchGameDir); + } +} + + +int CVoiceStatus::Init( + IVoiceStatusHelper *pHelper, + Panel **pParentPanel) +{ + // Setup the voice_modenable cvar. + gEngfuncs.pfnRegisterVariable("voice_modenable", "1", FCVAR_ARCHIVE); + + gEngfuncs.pfnRegisterVariable("voice_clientdebug", "0", 0); + + gEngfuncs.pfnAddCommand("voice_showbanned", ShowBannedCallback); + + if(gEngfuncs.pfnGetGameDirectory()) + { + m_BanMgr.Init(gEngfuncs.pfnGetGameDirectory()); + m_bBanMgrInitialized = true; + } + + assert(!g_pInternalVoiceStatus); + g_pInternalVoiceStatus = this; + + m_BlinkTimer = 0; + m_VoiceHeadModel = NULL; + memset(m_Labels, 0, sizeof(m_Labels)); + + for(int i=0; i < MAX_VOICE_SPEAKERS; i++) + { + CVoiceLabel *pLabel = &m_Labels[i]; + + pLabel->m_pBackground = new Label(""); + + if(pLabel->m_pLabel = new Label("")) + { + pLabel->m_pLabel->setVisible( true ); + pLabel->m_pLabel->setFont( Scheme::sf_primary2 ); + pLabel->m_pLabel->setTextAlignment( Label::a_east ); + pLabel->m_pLabel->setContentAlignment( Label::a_east ); + pLabel->m_pLabel->setParent( pLabel->m_pBackground ); + } + + if( pLabel->m_pIcon = new ImagePanel( NULL ) ) + { + pLabel->m_pIcon->setVisible( true ); + pLabel->m_pIcon->setParent( pLabel->m_pBackground ); + } + + pLabel->m_clientindex = -1; + } + + m_pLocalLabel = new ImagePanel(NULL); + + m_bInSquelchMode = false; + + m_pHelper = pHelper; + m_pParentPanel = pParentPanel; + gHUD.AddHudElem(this); + m_iFlags = HUD_ACTIVE; + HOOK_MESSAGE(VoiceMask); + HOOK_MESSAGE(ReqState); + + // Cache the game directory for use when we shut down + const char *pchGameDirT = gEngfuncs.pfnGetGameDirectory(); + m_pchGameDir = (char *)malloc(strlen(pchGameDirT) + 1); + strcpy(m_pchGameDir, pchGameDirT); + + return 1; +} + + +int CVoiceStatus::VidInit() +{ + FreeBitmaps(); + + + if( m_pLocalBitmap = vgui_LoadTGA("gfx/vgui/icntlk_pl.tga") ) + { + m_pLocalBitmap->setColor(Color(255,255,255,135)); + } + + if( m_pAckBitmap = vgui_LoadTGA("gfx/vgui/icntlk_sv.tga") ) + { + m_pAckBitmap->setColor(Color(255,255,255,135)); // Give just a tiny bit of translucency so software draws correctly. + } + + m_pLocalLabel->setImage( m_pLocalBitmap ); + m_pLocalLabel->setVisible( false ); + + + if( m_pSpeakerLabelIcon = vgui_LoadTGANoInvertAlpha("gfx/vgui/speaker4.tga" ) ) + m_pSpeakerLabelIcon->setColor( Color(255,255,255,1) ); // Give just a tiny bit of translucency so software draws correctly. + + if (m_pScoreboardNeverSpoken = vgui_LoadTGANoInvertAlpha("gfx/vgui/640_speaker1.tga")) + m_pScoreboardNeverSpoken->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardNotSpeaking = vgui_LoadTGANoInvertAlpha("gfx/vgui/640_speaker2.tga")) + m_pScoreboardNotSpeaking->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardSpeaking = vgui_LoadTGANoInvertAlpha("gfx/vgui/640_speaker3.tga")) + m_pScoreboardSpeaking->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardSpeaking2 = vgui_LoadTGANoInvertAlpha("gfx/vgui/640_speaker4.tga")) + m_pScoreboardSpeaking2->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardSquelch = vgui_LoadTGA("gfx/vgui/icntlk_squelch.tga")) + m_pScoreboardSquelch->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardBanned = vgui_LoadTGA("gfx/vgui/640_voiceblocked.tga")) + m_pScoreboardBanned->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + // Figure out the voice head model height. + m_VoiceHeadModelHeight = 45; + char *pFile = (char *)gEngfuncs.COM_LoadFile("scripts/voicemodel.txt", 5, NULL); + if(pFile) + { + char token[4096]; + gEngfuncs.COM_ParseFile(pFile, token); + if(token[0] >= '0' && token[0] <= '9') + { + m_VoiceHeadModelHeight = (float)atof(token); + } + + gEngfuncs.COM_FreeFile(pFile); + } + + m_VoiceHeadModel = gEngfuncs.pfnSPR_Load("sprites/voiceicon.spr"); + return TRUE; +} + + +void CVoiceStatus::Frame(double frametime) +{ + // check server banned players once per second + if(gEngfuncs.GetClientTime() - m_LastUpdateServerState > 1) + { + UpdateServerState(false); + } + + m_BlinkTimer += frametime; + + // Update speaker labels. + if( m_pHelper->CanShowSpeakerLabels() ) + { + for( int i=0; i < MAX_VOICE_SPEAKERS; i++ ) + m_Labels[i].m_pBackground->setVisible( m_Labels[i].m_clientindex != -1 ); + } + else + { + for( int i=0; i < MAX_VOICE_SPEAKERS; i++ ) + m_Labels[i].m_pBackground->setVisible( false ); + } + + for(int i=0; i < VOICE_MAX_PLAYERS; i++) + UpdateBanButton(i); +} + + +void CVoiceStatus::CreateEntities() +{ + if(!m_VoiceHeadModel) + return; + + cl_entity_t *localPlayer = gEngfuncs.GetLocalPlayer(); + + int iOutModel = 0; + for(int i=0; i < VOICE_MAX_PLAYERS; i++) + { + if(!m_VoicePlayers[i]) + continue; + + cl_entity_s *pClient = gEngfuncs.GetEntityByIndex(i+1); + + // Don't show an icon if the player is not in our PVS. + if(!pClient || pClient->curstate.messagenum < localPlayer->curstate.messagenum) + continue; + + // Don't show an icon for dead or spectating players (ie: invisible entities). + if(pClient->curstate.effects & EF_NODRAW) + continue; + + // Don't show an icon for the local player unless we're in thirdperson mode. + if(pClient == localPlayer && !cam_thirdperson) + continue; + + cl_entity_s *pEnt = &m_VoiceHeadModels[iOutModel]; + ++iOutModel; + + memset(pEnt, 0, sizeof(*pEnt)); + + pEnt->curstate.rendermode = kRenderTransAdd; + pEnt->curstate.renderamt = 255; + pEnt->baseline.renderamt = 255; + pEnt->curstate.renderfx = kRenderFxNoDissipation; + pEnt->curstate.framerate = 1; + pEnt->curstate.frame = 0; + pEnt->model = (struct model_s*)gEngfuncs.GetSpritePointer(m_VoiceHeadModel); + pEnt->angles[0] = pEnt->angles[1] = pEnt->angles[2] = 0; + pEnt->curstate.scale = 0.5f; + + pEnt->origin[0] = pEnt->origin[1] = 0; + pEnt->origin[2] = 45; + + VectorAdd(pEnt->origin, pClient->origin, pEnt->origin); + + // Tell the engine. + gEngfuncs.CL_CreateVisibleEntity(ET_NORMAL, pEnt); + } +} + + +void CVoiceStatus::UpdateSpeakerStatus( int entindex, qboolean bTalking ) +{ + cvar_t *pVoiceLoopback = NULL; + + if ( !m_pParentPanel || !*m_pParentPanel ) + { + return; + } + + if ( gEngfuncs.pfnGetCvarFloat( "voice_clientdebug" ) ) + { + char msg[256]; + _snprintf( msg, sizeof( msg ), "CVoiceStatus::UpdateSpeakerStatus: ent %d talking = %d\n", entindex, bTalking ); + gEngfuncs.pfnConsolePrint( msg ); + } + + int iLocalPlayerIndex = gEngfuncs.GetLocalPlayer()->index; + + // Is it the local player talking? + if ( entindex == -1 ) + { + m_bTalking = !!bTalking; + if( bTalking ) + { + // Enable voice for them automatically if they try to talk. + gEngfuncs.pfnClientCmd( "voice_modenable 1" ); + } + + // now set the player index to the correct index for the local player + // this will allow us to have the local player's icon flash in the scoreboard + entindex = iLocalPlayerIndex; + + pVoiceLoopback = gEngfuncs.pfnGetCvarPointer( "voice_loopback" ); + } + else if ( entindex == -2 ) + { + m_bServerAcked = !!bTalking; + } + + if ( entindex >= 0 && entindex <= VOICE_MAX_PLAYERS ) + { + int iClient = entindex - 1; + if ( iClient < 0 ) + { + return; + } + + CVoiceLabel *pLabel = FindVoiceLabel( iClient ); + if ( bTalking ) + { + m_VoicePlayers[iClient] = true; + m_VoiceEnabledPlayers[iClient] = true; + + // If we don't have a label for this guy yet, then create one. + if ( !pLabel ) + { + // if this isn't the local player (unless they have voice_loopback on) + if ( ( entindex != iLocalPlayerIndex ) || ( pVoiceLoopback && pVoiceLoopback->value ) ) + { + if ( pLabel = GetFreeVoiceLabel() ) + { + // Get the name from the engine. + hud_player_info_t info; + memset( &info, 0, sizeof( info ) ); + GetPlayerInfo( entindex, &info ); + + char paddedName[512]; + _snprintf( paddedName, sizeof( paddedName ), "%s ", info.name ); + + int color[3]; + m_pHelper->GetPlayerTextColor( entindex, color ); + + if ( pLabel->m_pBackground ) + { + pLabel->m_pBackground->setBgColor( color[0], color[1], color[2], 135 ); + pLabel->m_pBackground->setParent( *m_pParentPanel ); + pLabel->m_pBackground->setVisible( m_pHelper->CanShowSpeakerLabels() ); + } + + if ( pLabel->m_pLabel ) + { + pLabel->m_pLabel->setFgColor( 255, 255, 255, 0 ); + pLabel->m_pLabel->setBgColor( 0, 0, 0, 255 ); + pLabel->m_pLabel->setText( paddedName ); + } + + pLabel->m_clientindex = iClient; + } + } + } + } + else + { + m_VoicePlayers[iClient] = false; + + // If we have a label for this guy, kill it. + if ( pLabel ) + { + pLabel->m_pBackground->setVisible( false ); + pLabel->m_clientindex = -1; + } + } + } + + RepositionLabels(); +} + + +void CVoiceStatus::UpdateServerState(bool bForce) +{ + // Can't do anything when we're not in a level. + char const *pLevelName = gEngfuncs.pfnGetLevelName(); + if( pLevelName[0] == 0 ) + { + if( gEngfuncs.pfnGetCvarFloat("voice_clientdebug") ) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::UpdateServerState: pLevelName[0]==0\n" ); + } + + return; + } + + int bCVarModEnable = !!gEngfuncs.pfnGetCvarFloat("voice_modenable"); + if(bForce || m_bServerModEnable != bCVarModEnable) + { + m_bServerModEnable = bCVarModEnable; + + char str[256]; + _snprintf(str, sizeof(str), "VModEnable %d", m_bServerModEnable); + ServerCmd(str); + + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char msg[256]; + sprintf(msg, "CVoiceStatus::UpdateServerState: Sending '%s'\n", str); + gEngfuncs.pfnConsolePrint(msg); + } + } + + char str[2048]; + sprintf(str, "vban"); + bool bChange = false; + + for(unsigned long dw=0; dw < VOICE_MAX_PLAYERS_DW; dw++) + { + unsigned long serverBanMask = 0; + unsigned long banMask = 0; + for(unsigned long i=0; i < 32; i++) + { + char playerID[16]; + if(!gEngfuncs.GetPlayerUniqueID(i+1, playerID)) + continue; + + if(m_BanMgr.GetPlayerBan(playerID)) + banMask |= 1 << i; + + if(m_ServerBannedPlayers[dw*32 + i]) + serverBanMask |= 1 << i; + } + + if(serverBanMask != banMask) + bChange = true; + + // Ok, the server needs to be updated. + char numStr[512]; + sprintf(numStr, " %x", banMask); + strcat(str, numStr); + } + + if(bChange || bForce) + { + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char msg[256]; + sprintf(msg, "CVoiceStatus::UpdateServerState: Sending '%s'\n", str); + gEngfuncs.pfnConsolePrint(msg); + } + + gEngfuncs.pfnServerCmdUnreliable(str); // Tell the server.. + } + else + { + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::UpdateServerState: no change\n" ); + } + } + + m_LastUpdateServerState = gEngfuncs.GetClientTime(); +} + +void CVoiceStatus::UpdateSpeakerImage(Label *pLabel, int iPlayer) +{ + m_pBanButtons[iPlayer-1] = pLabel; + UpdateBanButton(iPlayer-1); +} + +void CVoiceStatus::UpdateBanButton(int iClient) +{ + Label *pPanel = m_pBanButtons[iClient]; + + if (!pPanel) + return; + + char playerID[16]; + extern bool HACK_GetPlayerUniqueID( int iPlayer, char playerID[16] ); + if(!HACK_GetPlayerUniqueID(iClient+1, playerID)) + return; + + // Figure out if it's blinking or not. + bool bBlink = fmod(m_BlinkTimer, SCOREBOARD_BLINK_FREQUENCY*2) < SCOREBOARD_BLINK_FREQUENCY; + bool bTalking = !!m_VoicePlayers[iClient]; + bool bBanned = m_BanMgr.GetPlayerBan(playerID); + bool bNeverSpoken = !m_VoiceEnabledPlayers[iClient]; + + // Get the appropriate image to display on the panel. + if (bBanned) + { + pPanel->setImage(m_pScoreboardBanned); + } + else if (bTalking) + { + if (bBlink) + { + pPanel->setImage(m_pScoreboardSpeaking2); + } + else + { + pPanel->setImage(m_pScoreboardSpeaking); + } + pPanel->setFgColor(255, 170, 0, 1); + } + else if (bNeverSpoken) + { + pPanel->setImage(m_pScoreboardNeverSpoken); + pPanel->setFgColor(100, 100, 100, 1); + } + else + { + pPanel->setImage(m_pScoreboardNotSpeaking); + } +} + + +void CVoiceStatus::HandleVoiceMaskMsg(int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + unsigned long dw; + for(dw=0; dw < VOICE_MAX_PLAYERS_DW; dw++) + { + m_AudiblePlayers.SetDWord(dw, (unsigned long)READ_LONG()); + m_ServerBannedPlayers.SetDWord(dw, (unsigned long)READ_LONG()); + + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char str[256]; + gEngfuncs.pfnConsolePrint("CVoiceStatus::HandleVoiceMaskMsg\n"); + + sprintf(str, " - m_AudiblePlayers[%d] = %lu\n", dw, m_AudiblePlayers.GetDWord(dw)); + gEngfuncs.pfnConsolePrint(str); + + sprintf(str, " - m_ServerBannedPlayers[%d] = %lu\n", dw, m_ServerBannedPlayers.GetDWord(dw)); + gEngfuncs.pfnConsolePrint(str); + } + } + + m_bServerModEnable = READ_BYTE(); +} + +void CVoiceStatus::HandleReqStateMsg(int iSize, void *pbuf) +{ + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint("CVoiceStatus::HandleReqStateMsg\n"); + } + + UpdateServerState(true); +} + +void CVoiceStatus::StartSquelchMode() +{ + if(m_bInSquelchMode) + return; + + m_bInSquelchMode = true; + m_pHelper->UpdateCursorState(); +} + +void CVoiceStatus::StopSquelchMode() +{ + m_bInSquelchMode = false; + m_pHelper->UpdateCursorState(); +} + +bool CVoiceStatus::IsInSquelchMode() +{ + return m_bInSquelchMode; +} + +CVoiceLabel* CVoiceStatus::FindVoiceLabel(int clientindex) +{ + for(int i=0; i < MAX_VOICE_SPEAKERS; i++) + { + if(m_Labels[i].m_clientindex == clientindex) + return &m_Labels[i]; + } + + return NULL; +} + + +CVoiceLabel* CVoiceStatus::GetFreeVoiceLabel() +{ + return FindVoiceLabel(-1); +} + + +void CVoiceStatus::RepositionLabels() +{ + // find starting position to draw from, along right-hand side of screen + int y = ScreenHeight / 2; + + int iconWide = 8, iconTall = 8; + if( m_pSpeakerLabelIcon ) + { + m_pSpeakerLabelIcon->getSize( iconWide, iconTall ); + } + + // Reposition active labels. + for(int i = 0; i < MAX_VOICE_SPEAKERS; i++) + { + CVoiceLabel *pLabel = &m_Labels[i]; + + if( pLabel->m_clientindex == -1 || !pLabel->m_pLabel ) + { + if( pLabel->m_pBackground ) + pLabel->m_pBackground->setVisible( false ); + + continue; + } + + int textWide, textTall; + pLabel->m_pLabel->getContentSize( textWide, textTall ); + + // Don't let it stretch too far across their screen. + if( textWide > (ScreenWidth*2)/3 ) + textWide = (ScreenWidth*2)/3; + + // Setup the background label to fit everything in. + int border = 2; + int bgWide = textWide + iconWide + border*3; + int bgTall = max( textTall, iconTall ) + border*2; + pLabel->m_pBackground->setBounds( ScreenWidth - bgWide - 8, y, bgWide, bgTall ); + + // Put the text at the left. + pLabel->m_pLabel->setBounds( border, (bgTall - textTall) / 2, textWide, textTall ); + + // Put the icon at the right. + int iconLeft = border + textWide + border; + int iconTop = (bgTall - iconTall) / 2; + if( pLabel->m_pIcon ) + { + pLabel->m_pIcon->setImage( m_pSpeakerLabelIcon ); + pLabel->m_pIcon->setBounds( iconLeft, iconTop, iconWide, iconTall ); + } + + y += bgTall + 2; + } + + if( m_pLocalBitmap && m_pAckBitmap && m_pLocalLabel && (m_bTalking || m_bServerAcked) ) + { + m_pLocalLabel->setParent(*m_pParentPanel); + m_pLocalLabel->setVisible( true ); + + if( m_bServerAcked && !!gEngfuncs.pfnGetCvarFloat("voice_clientdebug") ) + m_pLocalLabel->setImage( m_pAckBitmap ); + else + m_pLocalLabel->setImage( m_pLocalBitmap ); + + int sizeX, sizeY; + m_pLocalBitmap->getSize(sizeX, sizeY); + + int local_xPos = ScreenWidth - sizeX - 10; + int local_yPos = m_pHelper->GetAckIconHeight() - sizeY; + + m_pLocalLabel->setPos( local_xPos, local_yPos ); + } + else + { + m_pLocalLabel->setVisible( false ); + } +} + + +void CVoiceStatus::FreeBitmaps() +{ + // Delete all the images we have loaded. + delete m_pLocalBitmap; + m_pLocalBitmap = NULL; + + delete m_pAckBitmap; + m_pAckBitmap = NULL; + + delete m_pSpeakerLabelIcon; + m_pSpeakerLabelIcon = NULL; + + delete m_pScoreboardNeverSpoken; + m_pScoreboardNeverSpoken = NULL; + + delete m_pScoreboardNotSpeaking; + m_pScoreboardNotSpeaking = NULL; + + delete m_pScoreboardSpeaking; + m_pScoreboardSpeaking = NULL; + + delete m_pScoreboardSpeaking2; + m_pScoreboardSpeaking2 = NULL; + + delete m_pScoreboardSquelch; + m_pScoreboardSquelch = NULL; + + delete m_pScoreboardBanned; + m_pScoreboardBanned = NULL; + + // Clear references to the images in panels. + for(int i=0; i < VOICE_MAX_PLAYERS; i++) + { + if (m_pBanButtons[i]) + { + m_pBanButtons[i]->setImage(NULL); + } + } + + if(m_pLocalLabel) + m_pLocalLabel->setImage(NULL); +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the target client has been banned +// Input : playerID - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CVoiceStatus::IsPlayerBlocked(int iPlayer) +{ + char playerID[16]; + if (!gEngfuncs.GetPlayerUniqueID(iPlayer, playerID)) + return false; + + return m_BanMgr.GetPlayerBan(playerID); +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the player can't hear the other client due to game rules (eg. the other team) +// Input : playerID - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CVoiceStatus::IsPlayerAudible(int iPlayer) +{ + return !!m_AudiblePlayers[iPlayer-1]; +} + +//----------------------------------------------------------------------------- +// Purpose: blocks/unblocks the target client from being heard +// Input : playerID - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +void CVoiceStatus::SetPlayerBlockedState(int iPlayer, bool blocked) +{ + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::SetPlayerBlockedState part 1\n" ); + } + + char playerID[16]; + if (!gEngfuncs.GetPlayerUniqueID(iPlayer, playerID)) + return; + + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::SetPlayerBlockedState part 2\n" ); + } + + // Squelch or (try to) unsquelch this player. + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char str[256]; + sprintf(str, "CVoiceStatus::SetPlayerBlockedState: setting player %d ban to %d\n", iPlayer, !m_BanMgr.GetPlayerBan(playerID)); + gEngfuncs.pfnConsolePrint(str); + } + + m_BanMgr.SetPlayerBan( playerID, blocked ); + UpdateServerState(false); +} diff --git a/dmc/cl_dll/voice_status.h b/dmc/cl_dll/voice_status.h new file mode 100644 index 0000000..8edf58c --- /dev/null +++ b/dmc/cl_dll/voice_status.h @@ -0,0 +1,228 @@ +//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VOICE_STATUS_H +#define VOICE_STATUS_H +#pragma once + + +#include "VGUI_Label.h" +#include "VGUI_LineBorder.h" +#include "VGUI_ImagePanel.h" +#include "VGUI_BitmapTGA.h" +#include "VGUI_InputSignal.h" +#include "VGUI_Button.h" +#include "voice_common.h" +#include "cl_entity.h" +#include "voice_banmgr.h" +#include "vgui_checkbutton2.h" +#include "vgui_defaultinputsignal.h" + + +class CVoiceStatus; + + +class CVoiceLabel +{ +public: + vgui::Label *m_pLabel; + vgui::Label *m_pBackground; + vgui::ImagePanel *m_pIcon; // Voice icon next to player name. + int m_clientindex; // Client index of the speaker. -1 if this label isn't being used. +}; + + +// This is provided by each mod to access data that may not be the same across mods. +class IVoiceStatusHelper +{ +public: + virtual ~IVoiceStatusHelper() {} + + // Get RGB color for voice status text about this player. + virtual void GetPlayerTextColor(int entindex, int color[3]) = 0; + + // Force it to update the cursor state. + virtual void UpdateCursorState() = 0; + + // Return the height above the bottom that the voice ack icons should be drawn at. + virtual int GetAckIconHeight() = 0; + + // Return true if the voice manager is allowed to show speaker labels + // (mods usually return false when the scoreboard is up). + virtual bool CanShowSpeakerLabels() = 0; +}; + +//----------------------------------------------------------------------------- +// Purpose: Holds a color for the shared image +//----------------------------------------------------------------------------- +class VoiceImagePanel : public vgui::ImagePanel +{ + virtual void paintBackground() + { + if (_image!=null) + { + vgui::Color col; + getFgColor(col); + _image->setColor(col); + _image->doPaint(this); + } + } +}; + + +class CVoiceStatus : public CHudBase, public vgui::CDefaultInputSignal +{ +public: + CVoiceStatus(); + virtual ~CVoiceStatus(); + +// CHudBase overrides. +public: + + // Initialize the cl_dll's voice manager. + virtual int Init( + IVoiceStatusHelper *m_pHelper, + vgui::Panel **pParentPanel); + + // ackPosition is the bottom position of where CVoiceStatus will draw the voice acknowledgement labels. + virtual int VidInit(); + + +public: + + // Call from HUD_Frame each frame. + void Frame(double frametime); + + // Called when a player starts or stops talking. + // entindex is -1 to represent the local client talking (before the data comes back from the server). + // When the server acknowledges that the local client is talking, then entindex will be gEngfuncs.GetLocalPlayer(). + // entindex is -2 to represent the local client's voice being acked by the server. + void UpdateSpeakerStatus(int entindex, qboolean bTalking); + + // sets the correct image in the label for the player + void UpdateSpeakerImage(vgui::Label *pLabel, int iPlayer); + + // Call from the HUD_CreateEntities function so it can add sprites above player heads. + void CreateEntities(); + + // Called when the server registers a change to who this client can hear. + void HandleVoiceMaskMsg(int iSize, void *pbuf); + + // The server sends this message initially to tell the client to send their state. + void HandleReqStateMsg(int iSize, void *pbuf); + + +// Squelch mode functions. +public: + + // When you enter squelch mode, pass in + void StartSquelchMode(); + void StopSquelchMode(); + bool IsInSquelchMode(); + + // returns true if the target client has been banned + // playerIndex is of range 1..maxplayers + bool IsPlayerBlocked(int iPlayerIndex); + + // returns false if the player can't hear the other client due to game rules (eg. the other team) + bool IsPlayerAudible(int iPlayerIndex); + + // blocks the target client from being heard + void SetPlayerBlockedState(int iPlayerIndex, bool blocked); + +public: + + CVoiceLabel* FindVoiceLabel(int clientindex); // Find a CVoiceLabel representing the specified speaker. + // Returns NULL if none. + // entindex can be -1 if you want a currently-unused voice label. + CVoiceLabel* GetFreeVoiceLabel(); // Get an unused voice label. Returns NULL if none. + + void RepositionLabels(); + + void FreeBitmaps(); + + void UpdateServerState(bool bForce); + + // Update the button artwork to reflect the client's current state. + void UpdateBanButton(int iClient); + + +public: + + enum {MAX_VOICE_SPEAKERS=7}; + + float m_LastUpdateServerState; // Last time we called this function. + int m_bServerModEnable; // What we've sent to the server about our "voice_modenable" cvar. + + vgui::Panel **m_pParentPanel; + CPlayerBitVec m_VoicePlayers; // Who is currently talking. Indexed by client index. + + // This is the gamerules-defined list of players that you can hear. It is based on what teams people are on + // and is totally separate from the ban list. Indexed by client index. + CPlayerBitVec m_AudiblePlayers; + + // Players who have spoken at least once in the game so far + CPlayerBitVec m_VoiceEnabledPlayers; + + // This is who the server THINKS we have banned (it can become incorrect when a new player arrives on the server). + // It is checked periodically, and the server is told to squelch or unsquelch the appropriate players. + CPlayerBitVec m_ServerBannedPlayers; + + cl_entity_s m_VoiceHeadModels[VOICE_MAX_PLAYERS]; // These aren't necessarily in the order of players. They are just + // a place for it to put data in during CreateEntities. + + IVoiceStatusHelper *m_pHelper; // Each mod provides an implementation of this. + + + // Scoreboard icons. + double m_BlinkTimer; // Blink scoreboard icons.. + vgui::BitmapTGA *m_pScoreboardNeverSpoken; + vgui::BitmapTGA *m_pScoreboardNotSpeaking; + vgui::BitmapTGA *m_pScoreboardSpeaking; + vgui::BitmapTGA *m_pScoreboardSpeaking2; + vgui::BitmapTGA *m_pScoreboardSquelch; + vgui::BitmapTGA *m_pScoreboardBanned; + + vgui::Label *m_pBanButtons[VOICE_MAX_PLAYERS]; // scoreboard buttons. + + // Squelch mode stuff. + bool m_bInSquelchMode; + + HSPRITE m_VoiceHeadModel; // Voice head model (goes above players who are speaking). + float m_VoiceHeadModelHeight; // Height above their head to place the model. + + vgui::Image *m_pSpeakerLabelIcon; // Icon next to speaker labels. + + // Lower-right icons telling when the local player is talking.. + vgui::BitmapTGA *m_pLocalBitmap; // Represents the local client talking. + vgui::BitmapTGA *m_pAckBitmap; // Represents the server ack'ing the client talking. + vgui::ImagePanel *m_pLocalLabel; // Represents the local client talking. + + bool m_bTalking; // Set to true when the client thinks it's talking. + bool m_bServerAcked; // Set to true when the server knows the client is talking. + +public: + + CVoiceBanMgr m_BanMgr; // Tracks which users we have squelched and don't want to hear. + +public: + + bool m_bBanMgrInitialized; + + // Labels telling who is speaking. + CVoiceLabel m_Labels[MAX_VOICE_SPEAKERS]; + + // Cache the game directory for use when we shut down + char * m_pchGameDir; +}; + + +// Get the (global) voice manager. +CVoiceStatus* GetClientVoiceMgr(); + + +#endif // VOICE_STATUS_H diff --git a/dmc/cl_dll/wrect.h b/dmc/cl_dll/wrect.h new file mode 100644 index 0000000..a3494ae --- /dev/null +++ b/dmc/cl_dll/wrect.h @@ -0,0 +1,16 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( WRECTH ) +#define WRECTH + +typedef struct rect_s +{ + int left, right, top, bottom; +} wrect_t; + +#endif \ No newline at end of file diff --git a/dmc/dlls/Makefile b/dmc/dlls/Makefile new file mode 100644 index 0000000..e383cda --- /dev/null +++ b/dmc/dlls/Makefile @@ -0,0 +1,126 @@ +# +# Half-Life DMC SDK 2.3 dmc_i386.so Makefile for x86 Linux +# +# October 2002 by Leon Hartwig (hartwig@valvesoftware.com) +# + +DLLNAME=dmc + +ARCH=i386 + +#make sure this is the correct compiler for your system +CC=gcc + +DLL_SRCDIR=. +ENGINE_SRCDIR=../../engine +COMMON_SRCDIR=../../common +PM_SHARED_SRCDIR=../pm_shared +GAME_SHARED_SRCDIR=../../game_shared + +DLL_OBJDIR=$(DLL_SRCDIR)/obj +PM_SHARED_OBJDIR=$(DLL_OBJDIR)/pm_shared +GAME_SHARED_OBJDIR=$(DLL_OBJDIR)/game_shared + +BASE_CFLAGS= -Dstricmp=strcasecmp -D_strnicmp=strncasecmp -Dstrnicmp=strncasecmp -D_vsnprintf=vsnprintf\ + -DCLIENT_WEAPONS + +#safe optimization +CFLAGS=$(BASE_CFLAGS) -w -m486 -O1 + +#full optimization +#CFLAGS=$(BASE_CFLAGS) -w -O1 -m486 -ffast-math -funroll-loops \ + -fomit-frame-pointer -fexpensive-optimizations \ + -malign-loops=2 -malign-jumps=2 -malign-functions=2 + +#use these when debugging +#CFLAGS=$(BASE_CFLAGS) -g + +INCLUDEDIRS=-I. -I$(ENGINE_SRCDIR) -I$(COMMON_SRCDIR) -I$(PM_SHARED_SRCDIR) -I$(GAME_SHARED_SRCDIR) + +LDFLAGS= + +SHLIBEXT=so +SHLIBCFLAGS=-fPIC +SHLIBLDFLAGS=-shared + +DO_CC=$(CC) $(CFLAGS) $(SHLIBCFLAGS) $(INCLUDEDIRS) -o $@ -c $< + +############################################################################# +# SETUP AND BUILD +# GAME +############################################################################# + +$(DLL_OBJDIR)/%.o: $(DLL_SRCDIR)/%.cpp + $(DO_CC) + +$(GAME_SHARED_OBJDIR)/%.o: $(GAME_SHARED_SRCDIR)/%.cpp + $(DO_CC) + +$(PM_SHARED_OBJDIR)/%.o: $(PM_SHARED_SRCDIR)/%.c + $(DO_CC) + +OBJ = \ + $(DLL_OBJDIR)/animating.o \ + $(DLL_OBJDIR)/animation.o \ + $(DLL_OBJDIR)/bmodels.o \ + $(DLL_OBJDIR)/buttons.o \ + $(DLL_OBJDIR)/cbase.o \ + $(DLL_OBJDIR)/client.o \ + $(DLL_OBJDIR)/combat.o \ + $(DLL_OBJDIR)/doors.o \ + $(DLL_OBJDIR)/effects.o \ + $(DLL_OBJDIR)/explode.o \ + $(DLL_OBJDIR)/func_break.o \ + $(DLL_OBJDIR)/game.o \ + $(DLL_OBJDIR)/gamerules.o \ + $(DLL_OBJDIR)/globals.o \ + $(DLL_OBJDIR)/h_ai.o \ + $(DLL_OBJDIR)/h_export.o \ + $(DLL_OBJDIR)/lights.o \ + $(DLL_OBJDIR)/maprules.o \ + $(DLL_OBJDIR)/monsters.o \ + $(DLL_OBJDIR)/monsterstate.o \ + $(DLL_OBJDIR)/multiplay_gamerules.o \ + $(DLL_OBJDIR)/nodes.o \ + $(DLL_OBJDIR)/observer.o \ + $(DLL_OBJDIR)/pathcorner.o \ + $(DLL_OBJDIR)/plane.o \ + $(DLL_OBJDIR)/plats.o \ + $(DLL_OBJDIR)/player.o \ + $(DLL_OBJDIR)/quake_gun.o \ + $(DLL_OBJDIR)/quake_items.o \ + $(DLL_OBJDIR)/quake_nail.o \ + $(DLL_OBJDIR)/quake_player.o \ + $(DLL_OBJDIR)/quake_rocket.o \ + $(DLL_OBJDIR)/quake_weapons_all.o \ + $(DLL_OBJDIR)/schedule.o \ + $(DLL_OBJDIR)/singleplay_gamerules.o \ + $(DLL_OBJDIR)/skill.o \ + $(DLL_OBJDIR)/sound.o \ + $(DLL_OBJDIR)/spectator.o \ + $(DLL_OBJDIR)/subs.o \ + $(DLL_OBJDIR)/teamplay_gamerules.o \ + $(DLL_OBJDIR)/triggers.o \ + $(DLL_OBJDIR)/util.o \ + $(DLL_OBJDIR)/weapons.o \ + $(DLL_OBJDIR)/world.o \ + $(PM_SHARED_OBJDIR)/pm_shared.o \ + $(PM_SHARED_OBJDIR)/pm_math.o \ + $(PM_SHARED_OBJDIR)/pm_debug.o \ + $(GAME_SHARED_OBJDIR)/voice_gamemgr.o + +$(DLLNAME)_$(ARCH).$(SHLIBEXT) : neat $(OBJ) + $(CC) $(CFLAGS) $(SHLIBLDFLAGS) $(LDFLAGS) -o $@ $(OBJ) + +neat: + -mkdir $(DLL_OBJDIR) + -mkdir $(GAME_SHARED_OBJDIR) + -mkdir $(PM_SHARED_OBJDIR) +clean: + -rm -f $(OBJ) + -rm -f $(DLLNAME)_$(ARCH).$(SHLIBEXT) +spotless: clean + -rm -r $(GAME_SHARED_OBJDIR) + -rm -r $(PM_SHARED_OBJDIR) + -rm -r $(DLL_OBJDIR) + diff --git a/dmc/dlls/activity.h b/dmc/dlls/activity.h new file mode 100644 index 0000000..6fd3a18 --- /dev/null +++ b/dmc/dlls/activity.h @@ -0,0 +1,109 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef ACTIVITY_H +#define ACTIVITY_H + + +typedef enum { + ACT_RESET = 0, // Set m_Activity to this invalid value to force a reset to m_IdealActivity + ACT_IDLE = 1, + ACT_GUARD, + ACT_WALK, + ACT_RUN, + ACT_FLY, // Fly (and flap if appropriate) + ACT_SWIM, + ACT_HOP, // vertical jump + ACT_LEAP, // long forward jump + ACT_FALL, + ACT_LAND, + ACT_STRAFE_LEFT, + ACT_STRAFE_RIGHT, + ACT_ROLL_LEFT, // tuck and roll, left + ACT_ROLL_RIGHT, // tuck and roll, right + ACT_TURN_LEFT, // turn quickly left (stationary) + ACT_TURN_RIGHT, // turn quickly right (stationary) + ACT_CROUCH, // the act of crouching down from a standing position + ACT_CROUCHIDLE, // holding body in crouched position (loops) + ACT_STAND, // the act of standing from a crouched position + ACT_USE, + ACT_SIGNAL1, + ACT_SIGNAL2, + ACT_SIGNAL3, + ACT_TWITCH, + ACT_COWER, + ACT_SMALL_FLINCH, + ACT_BIG_FLINCH, + ACT_RANGE_ATTACK1, + ACT_RANGE_ATTACK2, + ACT_MELEE_ATTACK1, + ACT_MELEE_ATTACK2, + ACT_RELOAD, + ACT_ARM, // pull out gun, for instance + ACT_DISARM, // reholster gun + ACT_EAT, // monster chowing on a large food item (loop) + ACT_DIESIMPLE, + ACT_DIEBACKWARD, + ACT_DIEFORWARD, + ACT_DIEVIOLENT, + ACT_BARNACLE_HIT, // barnacle tongue hits a monster + ACT_BARNACLE_PULL, // barnacle is lifting the monster ( loop ) + ACT_BARNACLE_CHOMP, // barnacle latches on to the monster + ACT_BARNACLE_CHEW, // barnacle is holding the monster in its mouth ( loop ) + ACT_SLEEP, + ACT_INSPECT_FLOOR, // for active idles, look at something on or near the floor + ACT_INSPECT_WALL, // for active idles, look at something directly ahead of you ( doesn't HAVE to be a wall or on a wall ) + ACT_IDLE_ANGRY, // alternate idle animation in which the monster is clearly agitated. (loop) + ACT_WALK_HURT, // limp (loop) + ACT_RUN_HURT, // limp (loop) + ACT_HOVER, // Idle while in flight + ACT_GLIDE, // Fly (don't flap) + ACT_FLY_LEFT, // Turn left in flight + ACT_FLY_RIGHT, // Turn right in flight + ACT_DETECT_SCENT, // this means the monster smells a scent carried by the air + ACT_SNIFF, // this is the act of actually sniffing an item in front of the monster + ACT_BITE, // some large monsters can eat small things in one bite. This plays one time, EAT loops. + ACT_THREAT_DISPLAY, // without attacking, monster demonstrates that it is angry. (Yell, stick out chest, etc ) + ACT_FEAR_DISPLAY, // monster just saw something that it is afraid of + ACT_EXCITED, // for some reason, monster is excited. Sees something he really likes to eat, or whatever. + ACT_SPECIAL_ATTACK1, // very monster specific special attacks. + ACT_SPECIAL_ATTACK2, + ACT_COMBAT_IDLE, // agitated idle. + ACT_WALK_SCARED, + ACT_RUN_SCARED, + ACT_VICTORY_DANCE, // killed a player, do a victory dance. + ACT_DIE_HEADSHOT, // die, hit in head. + ACT_DIE_CHESTSHOT, // die, hit in chest + ACT_DIE_GUTSHOT, // die, hit in gut + ACT_DIE_BACKSHOT, // die, hit in back + ACT_FLINCH_HEAD, + ACT_FLINCH_CHEST, + ACT_FLINCH_STOMACH, + ACT_FLINCH_LEFTARM, + ACT_FLINCH_RIGHTARM, + ACT_FLINCH_LEFTLEG, + ACT_FLINCH_RIGHTLEG, +} Activity; + + +typedef struct { + int type; + char *name; +} activity_map_t; + +extern activity_map_t activity_map[]; + + +#endif //ACTIVITY_H diff --git a/dmc/dlls/activitymap.h b/dmc/dlls/activitymap.h new file mode 100644 index 0000000..92cadae --- /dev/null +++ b/dmc/dlls/activitymap.h @@ -0,0 +1,97 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#define _A( a ) { a, #a } + +activity_map_t activity_map[] = +{ +_A( ACT_IDLE ), +_A( ACT_GUARD ), +_A( ACT_WALK ), +_A( ACT_RUN ), +_A( ACT_FLY ), +_A( ACT_SWIM ), +_A( ACT_HOP ), +_A( ACT_LEAP ), +_A( ACT_FALL ), +_A( ACT_LAND ), +_A( ACT_STRAFE_LEFT ), +_A( ACT_STRAFE_RIGHT ), +_A( ACT_ROLL_LEFT ), +_A( ACT_ROLL_RIGHT ), +_A( ACT_TURN_LEFT ), +_A( ACT_TURN_RIGHT ), +_A( ACT_CROUCH ), +_A( ACT_CROUCHIDLE ), +_A( ACT_STAND ), +_A( ACT_USE ), +_A( ACT_SIGNAL1 ), +_A( ACT_SIGNAL2 ), +_A( ACT_SIGNAL3 ), +_A( ACT_TWITCH ), +_A( ACT_COWER ), +_A( ACT_SMALL_FLINCH ), +_A( ACT_BIG_FLINCH ), +_A( ACT_RANGE_ATTACK1 ), +_A( ACT_RANGE_ATTACK2 ), +_A( ACT_MELEE_ATTACK1 ), +_A( ACT_MELEE_ATTACK2 ), +_A( ACT_RELOAD ), +_A( ACT_ARM ), +_A( ACT_DISARM ), +_A( ACT_EAT ), +_A( ACT_DIESIMPLE ), +_A( ACT_DIEBACKWARD ), +_A( ACT_DIEFORWARD ), +_A( ACT_DIEVIOLENT ), +_A( ACT_BARNACLE_HIT ), +_A( ACT_BARNACLE_PULL ), +_A( ACT_BARNACLE_CHOMP ), +_A( ACT_BARNACLE_CHEW ), +_A( ACT_SLEEP ), +_A( ACT_INSPECT_FLOOR ), +_A( ACT_INSPECT_WALL ), +_A( ACT_IDLE_ANGRY ), +_A( ACT_WALK_HURT ), +_A( ACT_RUN_HURT ), +_A( ACT_HOVER ), +_A( ACT_GLIDE ), +_A( ACT_FLY_LEFT ), +_A( ACT_FLY_RIGHT ), +_A( ACT_DETECT_SCENT ), +_A( ACT_SNIFF ), +_A( ACT_BITE ), +_A( ACT_THREAT_DISPLAY ), +_A( ACT_FEAR_DISPLAY ), +_A( ACT_EXCITED ), +_A( ACT_SPECIAL_ATTACK1 ), +_A( ACT_SPECIAL_ATTACK2 ), +_A( ACT_COMBAT_IDLE ), +_A( ACT_WALK_SCARED ), +_A( ACT_RUN_SCARED ), +_A( ACT_VICTORY_DANCE ), +_A( ACT_DIE_HEADSHOT ), +_A( ACT_DIE_CHESTSHOT ), +_A( ACT_DIE_GUTSHOT ), +_A( ACT_DIE_BACKSHOT ), +_A( ACT_FLINCH_HEAD ), +_A( ACT_FLINCH_CHEST ), +_A( ACT_FLINCH_STOMACH ), +_A( ACT_FLINCH_LEFTARM ), +_A( ACT_FLINCH_RIGHTARM ), +_A( ACT_FLINCH_LEFTLEG ), +_A( ACT_FLINCH_RIGHTLEG ), +0, NULL +}; diff --git a/dmc/dlls/animating.cpp b/dmc/dlls/animating.cpp new file mode 100644 index 0000000..2da7536 --- /dev/null +++ b/dmc/dlls/animating.cpp @@ -0,0 +1,313 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== monsters.cpp ======================================================== + + Monster-related utility code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "animation.h" +#include "saverestore.h" + +TYPEDESCRIPTION CBaseAnimating::m_SaveData[] = +{ + DEFINE_FIELD( CBaseMonster, m_flFrameRate, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flGroundSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flLastEventCheck, FIELD_TIME ), + DEFINE_FIELD( CBaseMonster, m_fSequenceFinished, FIELD_BOOLEAN ), + DEFINE_FIELD( CBaseMonster, m_fSequenceLoops, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CBaseAnimating, CBaseDelay ); + + +//========================================================= +// StudioFrameAdvance - advance the animation frame up to the current time +// if an flInterval is passed in, only advance animation that number of seconds +//========================================================= +float CBaseAnimating :: StudioFrameAdvance ( float flInterval ) +{ + if (flInterval == 0.0) + { + flInterval = (gpGlobals->time - pev->animtime); + if (flInterval <= 0.001) + { + pev->animtime = gpGlobals->time; + return 0.0; + } + } + if (! pev->animtime) + flInterval = 0.0; + + pev->frame += flInterval * m_flFrameRate * pev->framerate; + pev->animtime = gpGlobals->time; + + if (pev->frame < 0.0 || pev->frame >= 256.0) + { + if (m_fSequenceLoops) + pev->frame -= (int)(pev->frame / 256.0) * 256.0; + else + pev->frame = (pev->frame < 0.0) ? 0 : 255; + m_fSequenceFinished = TRUE; // just in case it wasn't caught in GetEvents + } + + return flInterval; +} + +//========================================================= +// LookupActivity +//========================================================= +int CBaseAnimating :: LookupActivity ( int activity ) +{ + ASSERT( activity != 0 ); + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupActivity( pmodel, pev, activity ); +} + +//========================================================= +// LookupActivityHeaviest +// +// Get activity with highest 'weight' +// +//========================================================= +int CBaseAnimating :: LookupActivityHeaviest ( int activity ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupActivityHeaviest( pmodel, pev, activity ); +} + +//========================================================= +//========================================================= +int CBaseAnimating :: LookupSequence ( const char *label ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupSequence( pmodel, label ); +} + + +//========================================================= +//========================================================= +void CBaseAnimating :: ResetSequenceInfo ( ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + GetSequenceInfo( pmodel, pev, &m_flFrameRate, &m_flGroundSpeed ); + m_fSequenceLoops = ((GetSequenceFlags() & STUDIO_LOOPING) != 0); + pev->animtime = gpGlobals->time; + pev->framerate = 1.0; + m_fSequenceFinished = FALSE; + m_flLastEventCheck = gpGlobals->time; +} + + + +//========================================================= +//========================================================= +BOOL CBaseAnimating :: GetSequenceFlags( ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::GetSequenceFlags( pmodel, pev ); +} + +//========================================================= +// DispatchAnimEvents +//========================================================= +void CBaseAnimating :: DispatchAnimEvents ( float flInterval ) +{ + MonsterEvent_t event; + + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + if ( !pmodel ) + { + ALERT( at_aiconsole, "Gibbed monster is thinking!\n" ); + return; + } + + // FIXME: I have to do this or some events get missed, and this is probably causing the problem below + flInterval = 0.1; + + // FIX: this still sometimes hits events twice + float flStart = pev->frame + (m_flLastEventCheck - pev->animtime) * m_flFrameRate * pev->framerate; + float flEnd = pev->frame + flInterval * m_flFrameRate * pev->framerate; + m_flLastEventCheck = pev->animtime + flInterval; + + m_fSequenceFinished = FALSE; + if (flEnd >= 256 || flEnd <= 0.0) + m_fSequenceFinished = TRUE; + + int index = 0; + + while ( (index = GetAnimationEvent( pmodel, pev, &event, flStart, flEnd, index ) ) != 0 ) + { + HandleAnimEvent( &event ); + } +} + + +//========================================================= +//========================================================= +float CBaseAnimating :: SetBoneController ( int iController, float flValue ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return SetController( pmodel, pev, iController, flValue ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: InitBoneControllers ( void ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + SetController( pmodel, pev, 0, 0.0 ); + SetController( pmodel, pev, 1, 0.0 ); + SetController( pmodel, pev, 2, 0.0 ); + SetController( pmodel, pev, 3, 0.0 ); +} + +//========================================================= +//========================================================= +float CBaseAnimating :: SetBlending ( int iBlender, float flValue ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::SetBlending( pmodel, pev, iBlender, flValue ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: GetBonePosition ( int iBone, Vector &origin, Vector &angles ) +{ + GET_BONE_POSITION( ENT(pev), iBone, origin, angles ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: GetAttachment ( int iAttachment, Vector &origin, Vector &angles ) +{ + GET_ATTACHMENT( ENT(pev), iAttachment, origin, angles ); +} + +//========================================================= +//========================================================= +int CBaseAnimating :: FindTransition( int iEndingSequence, int iGoalSequence, int *piDir ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + if (piDir == NULL) + { + int iDir; + int sequence = ::FindTransition( pmodel, iEndingSequence, iGoalSequence, &iDir ); + if (iDir != 1) + return -1; + else + return sequence; + } + + return ::FindTransition( pmodel, iEndingSequence, iGoalSequence, piDir ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: GetAutomovement( Vector &origin, Vector &angles, float flInterval ) +{ + +} + +void CBaseAnimating :: SetBodygroup( int iGroup, int iValue ) +{ + ::SetBodygroup( GET_MODEL_PTR( ENT(pev) ), pev, iGroup, iValue ); +} + +int CBaseAnimating :: GetBodygroup( int iGroup ) +{ + return ::GetBodygroup( GET_MODEL_PTR( ENT(pev) ), pev, iGroup ); +} + + +int CBaseAnimating :: ExtractBbox( int sequence, float *mins, float *maxs ) +{ + return ::ExtractBbox( GET_MODEL_PTR( ENT(pev) ), sequence, mins, maxs ); +} + +//========================================================= +//========================================================= + +void CBaseAnimating :: SetSequenceBox( void ) +{ + Vector mins, maxs; + + // Get sequence bbox + if ( ExtractBbox( pev->sequence, mins, maxs ) ) + { + // expand box for rotation + // find min / max for rotations + float yaw = pev->angles.y * (M_PI / 180.0); + + Vector xvector, yvector; + xvector.x = cos(yaw); + xvector.y = sin(yaw); + yvector.x = -sin(yaw); + yvector.y = cos(yaw); + Vector bounds[2]; + + bounds[0] = mins; + bounds[1] = maxs; + + Vector rmin( 9999, 9999, 9999 ); + Vector rmax( -9999, -9999, -9999 ); + Vector base, transformed; + + for (int i = 0; i <= 1; i++ ) + { + base.x = bounds[i].x; + for ( int j = 0; j <= 1; j++ ) + { + base.y = bounds[j].y; + for ( int k = 0; k <= 1; k++ ) + { + base.z = bounds[k].z; + + // transform the point + transformed.x = xvector.x*base.x + yvector.x*base.y; + transformed.y = xvector.y*base.x + yvector.y*base.y; + transformed.z = base.z; + + for ( int l = 0; l < 3; l++ ) + { + if (transformed[l] < rmin[l]) + rmin[l] = transformed[l]; + if (transformed[l] > rmax[l]) + rmax[l] = transformed[l]; + } + } + } + } + rmin.z = 0; + rmax.z = rmin.z + 1; + UTIL_SetSize( pev, rmin, rmax ); + } +} + diff --git a/dmc/dlls/animation.cpp b/dmc/dlls/animation.cpp new file mode 100644 index 0000000..42707f4 --- /dev/null +++ b/dmc/dlls/animation.cpp @@ -0,0 +1,527 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include +#include +#include + +typedef bool BOOL; + +// hack into header files that we can ship +typedef int qboolean; +typedef unsigned char byte; +#include "../utils/common/mathlib.h" +#include "const.h" + +#include "progdefs.h" +#include "edict.h" +#include "eiface.h" + +#include "studio.h" + +#include "../engine/studio.h" + +#ifndef ACTIVITY_H +#include "activity.h" +#endif + +#include "activitymap.h" + +#ifndef ANIMATION_H +#include "animation.h" +#endif + +#ifndef SCRIPTEVENT_H +#include "scriptevent.h" +#endif + +#ifndef ENGINECALLBACK_H +#include "enginecallback.h" +#endif + +extern globalvars_t *gpGlobals; + +#pragma warning( disable : 4244 ) + + + +int ExtractBbox( void *pmodel, int sequence, float *mins, float *maxs ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + mins[0] = pseqdesc[ sequence ].bbmin[0]; + mins[1] = pseqdesc[ sequence ].bbmin[1]; + mins[2] = pseqdesc[ sequence ].bbmin[2]; + + maxs[0] = pseqdesc[ sequence ].bbmax[0]; + maxs[1] = pseqdesc[ sequence ].bbmax[1]; + maxs[2] = pseqdesc[ sequence ].bbmax[2]; + + return 1; +} + + +int LookupActivity( void *pmodel, entvars_t *pev, int activity ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + int weighttotal = 0; + int seq = ACTIVITY_NOT_AVAILABLE; + for (int i = 0; i < pstudiohdr->numseq; i++) + { + if (pseqdesc[i].activity == activity) + { + weighttotal += pseqdesc[i].actweight; + if (!weighttotal || RANDOM_LONG(0,weighttotal-1) < pseqdesc[i].actweight) + seq = i; + } + } + + return seq; +} + + +int LookupActivityHeaviest( void *pmodel, entvars_t *pev, int activity ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr ) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + int weight = 0; + int seq = ACTIVITY_NOT_AVAILABLE; + for (int i = 0; i < pstudiohdr->numseq; i++) + { + if (pseqdesc[i].activity == activity) + { + if ( pseqdesc[i].actweight > weight ) + { + weight = pseqdesc[i].actweight; + seq = i; + } + } + } + + return seq; +} + +void GetEyePosition ( void *pmodel, float *vecEyePosition ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + + if ( !pstudiohdr ) + { + ALERT ( at_console, "GetEyePosition() Can't get pstudiohdr ptr!\n" ); + return; + } + + VectorCopy ( pstudiohdr->eyeposition, vecEyePosition ); +} + +int LookupSequence( void *pmodel, const char *label ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + for (int i = 0; i < pstudiohdr->numseq; i++) + { + if (stricmp( pseqdesc[i].label, label ) == 0) + return i; + } + + return -1; +} + + +int IsSoundEvent( int eventNumber ) +{ + if ( eventNumber == SCRIPT_EVENT_SOUND || eventNumber == SCRIPT_EVENT_SOUND_VOICE ) + return 1; + return 0; +} + + +void SequencePrecache( void *pmodel, const char *pSequenceName ) +{ + int index = LookupSequence( pmodel, pSequenceName ); + if ( index >= 0 ) + { + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || index >= pstudiohdr->numseq ) + return; + + mstudioseqdesc_t *pseqdesc; + mstudioevent_t *pevent; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + index; + pevent = (mstudioevent_t *)((byte *)pstudiohdr + pseqdesc->eventindex); + + for (int i = 0; i < pseqdesc->numevents; i++) + { + // Don't send client-side events to the server AI + if ( pevent[i].event >= EVENT_CLIENT ) + continue; + + // UNDONE: Add a callback to check to see if a sound is precached yet and don't allocate a copy + // of it's name if it is. + if ( IsSoundEvent( pevent[i].event ) ) + { + if ( !strlen(pevent[i].options) ) + { + ALERT( at_error, "Bad sound event %d in sequence %s :: %s (sound is \"%s\")\n", pevent[i].event, pstudiohdr->name, pSequenceName, pevent[i].options ); + } + + PRECACHE_SOUND( (char *)(gpGlobals->pStringBase + ALLOC_STRING(pevent[i].options) ) ); + } + } + } +} + + + +void GetSequenceInfo( void *pmodel, entvars_t *pev, float *pflFrameRate, float *pflGroundSpeed ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return; + + mstudioseqdesc_t *pseqdesc; + + if (pev->sequence >= pstudiohdr->numseq) + { + *pflFrameRate = 0.0; + *pflGroundSpeed = 0.0; + return; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + if (pseqdesc->numframes > 1) + { + *pflFrameRate = 256 * pseqdesc->fps / (pseqdesc->numframes - 1); + *pflGroundSpeed = sqrt( pseqdesc->linearmovement[0]*pseqdesc->linearmovement[0]+ pseqdesc->linearmovement[1]*pseqdesc->linearmovement[1]+ pseqdesc->linearmovement[2]*pseqdesc->linearmovement[2] ); + *pflGroundSpeed = *pflGroundSpeed * pseqdesc->fps / (pseqdesc->numframes - 1); + } + else + { + *pflFrameRate = 256.0; + *pflGroundSpeed = 0.0; + } +} + + +int GetSequenceFlags( void *pmodel, entvars_t *pev ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq ) + return 0; + + mstudioseqdesc_t *pseqdesc; + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + return pseqdesc->flags; +} + + +int GetAnimationEvent( void *pmodel, entvars_t *pev, MonsterEvent_t *pMonsterEvent, float flStart, float flEnd, int index ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq || !pMonsterEvent ) + return 0; + + int events = 0; + + mstudioseqdesc_t *pseqdesc; + mstudioevent_t *pevent; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + pevent = (mstudioevent_t *)((byte *)pstudiohdr + pseqdesc->eventindex); + + if (pseqdesc->numevents == 0 || index > pseqdesc->numevents ) + return 0; + + if (pseqdesc->numframes > 1) + { + flStart *= (pseqdesc->numframes - 1) / 256.0; + flEnd *= (pseqdesc->numframes - 1) / 256.0; + } + else + { + flStart = 0; + flEnd = 1.0; + } + + for (; index < pseqdesc->numevents; index++) + { + // Don't send client-side events to the server AI + if ( pevent[index].event >= EVENT_CLIENT ) + continue; + + if ( (pevent[index].frame >= flStart && pevent[index].frame < flEnd) || + ((pseqdesc->flags & STUDIO_LOOPING) && flEnd >= pseqdesc->numframes - 1 && pevent[index].frame < flEnd - pseqdesc->numframes + 1) ) + { + pMonsterEvent->event = pevent[index].event; + pMonsterEvent->options = pevent[index].options; + return index + 1; + } + } + return 0; +} + +float SetController( void *pmodel, entvars_t *pev, int iController, float flValue ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return flValue; + + mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)pstudiohdr + pstudiohdr->bonecontrollerindex); + + // find first controller that matches the index + int i; + for ( i = 0; i < pstudiohdr->numbonecontrollers; i++, pbonecontroller++) + { + if (pbonecontroller->index == iController) + break; + } + if (i >= pstudiohdr->numbonecontrollers) + return flValue; + + // wrap 0..360 if it's a rotational controller + + if (pbonecontroller->type & (STUDIO_XR | STUDIO_YR | STUDIO_ZR)) + { + // ugly hack, invert value if end < start + if (pbonecontroller->end < pbonecontroller->start) + flValue = -flValue; + + // does the controller not wrap? + if (pbonecontroller->start + 359.0 >= pbonecontroller->end) + { + if (flValue > ((pbonecontroller->start + pbonecontroller->end) / 2.0) + 180) + flValue = flValue - 360; + if (flValue < ((pbonecontroller->start + pbonecontroller->end) / 2.0) - 180) + flValue = flValue + 360; + } + else + { + if (flValue > 360) + flValue = flValue - (int)(flValue / 360.0) * 360.0; + else if (flValue < 0) + flValue = flValue + (int)((flValue / -360.0) + 1) * 360.0; + } + } + + int setting = 255 * (flValue - pbonecontroller->start) / (pbonecontroller->end - pbonecontroller->start); + + if (setting < 0) setting = 0; + if (setting > 255) setting = 255; + pev->controller[iController] = setting; + + return setting * (1.0 / 255.0) * (pbonecontroller->end - pbonecontroller->start) + pbonecontroller->start; +} + + +float SetBlending( void *pmodel, entvars_t *pev, int iBlender, float flValue ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return flValue; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + if (pseqdesc->blendtype[iBlender] == 0) + return flValue; + + if (pseqdesc->blendtype[iBlender] & (STUDIO_XR | STUDIO_YR | STUDIO_ZR)) + { + // ugly hack, invert value if end < start + if (pseqdesc->blendend[iBlender] < pseqdesc->blendstart[iBlender]) + flValue = -flValue; + + // does the controller not wrap? + if (pseqdesc->blendstart[iBlender] + 359.0 >= pseqdesc->blendend[iBlender]) + { + if (flValue > ((pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender]) / 2.0) + 180) + flValue = flValue - 360; + if (flValue < ((pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender]) / 2.0) - 180) + flValue = flValue + 360; + } + } + + int setting = 255 * (flValue - pseqdesc->blendstart[iBlender]) / (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender]); + + if (setting < 0) setting = 0; + if (setting > 255) setting = 255; + + pev->blending[iBlender] = setting; + + return setting * (1.0 / 255.0) * (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender]) + pseqdesc->blendstart[iBlender]; +} + + + + +int FindTransition( void *pmodel, int iEndingAnim, int iGoalAnim, int *piDir ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return iGoalAnim; + + mstudioseqdesc_t *pseqdesc; + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + // bail if we're going to or from a node 0 + if (pseqdesc[iEndingAnim].entrynode == 0 || pseqdesc[iGoalAnim].entrynode == 0) + { + return iGoalAnim; + } + + int iEndNode; + + // ALERT( at_console, "from %d to %d: ", pEndNode->iEndNode, pGoalNode->iStartNode ); + + if (*piDir > 0) + { + iEndNode = pseqdesc[iEndingAnim].exitnode; + } + else + { + iEndNode = pseqdesc[iEndingAnim].entrynode; + } + + if (iEndNode == pseqdesc[iGoalAnim].entrynode) + { + *piDir = 1; + return iGoalAnim; + } + + byte *pTransition = ((byte *)pstudiohdr + pstudiohdr->transitionindex); + + int iInternNode = pTransition[(iEndNode-1)*pstudiohdr->numtransitions + (pseqdesc[iGoalAnim].entrynode-1)]; + + if (iInternNode == 0) + return iGoalAnim; + + int i; + + // look for someone going + for (i = 0; i < pstudiohdr->numseq; i++) + { + if (pseqdesc[i].entrynode == iEndNode && pseqdesc[i].exitnode == iInternNode) + { + *piDir = 1; + return i; + } + if (pseqdesc[i].nodeflags) + { + if (pseqdesc[i].exitnode == iEndNode && pseqdesc[i].entrynode == iInternNode) + { + *piDir = -1; + return i; + } + } + } + + ALERT( at_console, "error in transition graph" ); + return iGoalAnim; +} + +void SetBodygroup( void *pmodel, entvars_t *pev, int iGroup, int iValue ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return; + + if (iGroup > pstudiohdr->numbodyparts) + return; + + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex) + iGroup; + + if (iValue >= pbodypart->nummodels) + return; + + int iCurrent = (pev->body / pbodypart->base) % pbodypart->nummodels; + + pev->body = (pev->body - (iCurrent * pbodypart->base) + (iValue * pbodypart->base)); +} + + +int GetBodygroup( void *pmodel, entvars_t *pev, int iGroup ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + if (iGroup > pstudiohdr->numbodyparts) + return 0; + + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex) + iGroup; + + if (pbodypart->nummodels <= 1) + return 0; + + int iCurrent = (pev->body / pbodypart->base) % pbodypart->nummodels; + + return iCurrent; +} diff --git a/dmc/dlls/animation.h b/dmc/dlls/animation.h new file mode 100644 index 0000000..174bd71 --- /dev/null +++ b/dmc/dlls/animation.h @@ -0,0 +1,47 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef ANIMATION_H +#define ANIMATION_H + +#define ACTIVITY_NOT_AVAILABLE -1 + +#ifndef MONSTEREVENT_H +#include "monsterevent.h" +#endif + +extern int IsSoundEvent( int eventNumber ); + +int LookupActivity( void *pmodel, entvars_t *pev, int activity ); +int LookupActivityHeaviest( void *pmodel, entvars_t *pev, int activity ); +int LookupSequence( void *pmodel, const char *label ); +void GetSequenceInfo( void *pmodel, entvars_t *pev, float *pflFrameRate, float *pflGroundSpeed ); +int GetSequenceFlags( void *pmodel, entvars_t *pev ); +int LookupAnimationEvents( void *pmodel, entvars_t *pev, float flStart, float flEnd ); +float SetController( void *pmodel, entvars_t *pev, int iController, float flValue ); +float SetBlending( void *pmodel, entvars_t *pev, int iBlender, float flValue ); +void GetEyePosition( void *pmodel, float *vecEyePosition ); +void SequencePrecache( void *pmodel, const char *pSequenceName ); +int FindTransition( void *pmodel, int iEndingAnim, int iGoalAnim, int *piDir ); +void SetBodygroup( void *pmodel, entvars_t *pev, int iGroup, int iValue ); +int GetBodygroup( void *pmodel, entvars_t *pev, int iGroup ); + +int GetAnimationEvent( void *pmodel, entvars_t *pev, MonsterEvent_t *pMonsterEvent, float flStart, float flEnd, int index ); +int ExtractBbox( void *pmodel, int sequence, float *mins, float *maxs ); + +// From /engine/studio.h +#define STUDIO_LOOPING 0x0001 + + +#endif //ANIMATION_H diff --git a/dmc/dlls/basemonster.h b/dmc/dlls/basemonster.h new file mode 100644 index 0000000..190965d --- /dev/null +++ b/dmc/dlls/basemonster.h @@ -0,0 +1,339 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ + +#ifndef BASEMONSTER_H +#define BASEMONSTER_H + +// +// generic Monster +// +class CBaseMonster : public CBaseToggle +{ +private: + int m_afConditions; + +public: + typedef enum + { + SCRIPT_PLAYING = 0, // Playing the sequence + SCRIPT_WAIT, // Waiting on everyone in the script to be ready + SCRIPT_CLEANUP, // Cancelling the script / cleaning up + SCRIPT_WALK_TO_MARK, + SCRIPT_RUN_TO_MARK, + } SCRIPTSTATE; + + + + // these fields have been added in the process of reworking the state machine. (sjb) + EHANDLE m_hEnemy; // the entity that the monster is fighting. + EHANDLE m_hTargetEnt; // the entity that the monster is trying to reach + EHANDLE m_hOldEnemy[ MAX_OLD_ENEMIES ]; + Vector m_vecOldEnemy[ MAX_OLD_ENEMIES ]; + + float m_flFieldOfView;// width of monster's field of view ( dot product ) + float m_flWaitFinished;// if we're told to wait, this is the time that the wait will be over. + float m_flMoveWaitFinished; + + Activity m_Activity;// what the monster is doing (animation) + Activity m_IdealActivity;// monster should switch to this activity + + int m_LastHitGroup; // the last body region that took damage + + MONSTERSTATE m_MonsterState;// monster's current state + MONSTERSTATE m_IdealMonsterState;// monster should change to this state + + int m_iTaskStatus; + Schedule_t *m_pSchedule; + int m_iScheduleIndex; + + WayPoint_t m_Route[ ROUTE_SIZE ]; // Positions of movement + int m_movementGoal; // Goal that defines route + int m_iRouteIndex; // index into m_Route[] + float m_moveWaitTime; // How long I should wait for something to move + + Vector m_vecMoveGoal; // kept around for node graph moves, so we know our ultimate goal + Activity m_movementActivity; // When moving, set this activity + + int m_iAudibleList; // first index of a linked list of sounds that the monster can hear. + int m_afSoundTypes; + + Vector m_vecLastPosition;// monster sometimes wants to return to where it started after an operation. + + int m_iHintNode; // this is the hint node that the monster is moving towards or performing active idle on. + + int m_afMemory; + + int m_iMaxHealth;// keeps track of monster's maximum health value (for re-healing, etc) + + Vector m_vecEnemyLKP;// last known position of enemy. (enemy's origin) + + int m_cAmmoLoaded; // how much ammo is in the weapon (used to trigger reload anim sequences) + + int m_afCapability;// tells us what a monster can/can't do. + + float m_flNextAttack; // cannot attack again until this time + + int m_bitsDamageType; // what types of damage has monster (player) taken + BYTE m_rgbTimeBasedDamage[CDMG_TIMEBASED]; + + int m_lastDamageAmount;// how much damage did monster (player) last take + // time based damage counters, decr. 1 per 2 seconds + int m_bloodColor; // color of blood particless + + int m_failSchedule; // Schedule type to choose if current schedule fails + + float m_flHungryTime;// set this is a future time to stop the monster from eating for a while. + + float m_flDistTooFar; // if enemy farther away than this, bits_COND_ENEMY_TOOFAR set in CheckEnemy + float m_flDistLook; // distance monster sees (Default 2048) + + int m_iTriggerCondition;// for scripted AI, this is the condition that will cause the activation of the monster's TriggerTarget + string_t m_iszTriggerTarget;// name of target that should be fired. + + Vector m_HackedGunPos; // HACK until we can query end of gun + +// Scripted sequence Info + SCRIPTSTATE m_scriptState; // internal cinematic state + CCineMonster *m_pCine; + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + void KeyValue( KeyValueData *pkvd ); + +// monster use function + void EXPORT MonsterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT CorpseUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +// overrideable Monster member functions + + virtual int BloodColor( void ) { return m_bloodColor; } + + virtual CBaseMonster *MyMonsterPointer( void ) { return this; } + virtual void Look ( int iDistance );// basic sight function for monsters + virtual void RunAI ( void );// core ai function! + void Listen ( void ); + + virtual BOOL IsAlive( void ) { return (pev->deadflag != DEAD_DEAD); } + virtual BOOL ShouldFadeOnDeath( void ); + +// Basic Monster AI functions + virtual float ChangeYaw ( int speed ); + float VecToYaw( Vector vecDir ); + float FlYawDiff ( void ); + + float DamageForce( float damage ); + +// stuff written for new state machine + virtual void MonsterThink( void ); + void EXPORT CallMonsterThink( void ) { this->MonsterThink(); } + virtual int IRelationship ( CBaseEntity *pTarget ); + virtual void MonsterInit ( void ); + virtual void MonsterInitDead( void ); // Call after animation/pose is set up + virtual void BecomeDead( void ); + void EXPORT CorpseFallThink( void ); + + void EXPORT MonsterInitThink ( void ); + virtual void StartMonster ( void ); + virtual CBaseEntity* BestVisibleEnemy ( void );// finds best visible enemy for attack + virtual BOOL FInViewCone ( CBaseEntity *pEntity );// see if pEntity is in monster's view cone + virtual BOOL FInViewCone ( Vector *pOrigin );// see if given location is in monster's view cone + virtual void HandleAnimEvent( MonsterEvent_t *pEvent ); + + virtual int CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist );// check validity of a straight move through space + virtual void Move( float flInterval = 0.1 ); + virtual void MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ); + virtual BOOL ShouldAdvanceRoute( float flWaypointDist ); + + virtual Activity GetStoppedActivity( void ) { return ACT_IDLE; } + virtual void Stop( void ) { m_IdealActivity = GetStoppedActivity(); } + + // This will stop animation until you call ResetSequenceInfo() at some point in the future + inline void StopAnimation( void ) { pev->framerate = 0; } + + // these functions will survey conditions and set appropriate conditions bits for attack types. + virtual BOOL CheckRangeAttack1( float flDot, float flDist ); + virtual BOOL CheckRangeAttack2( float flDot, float flDist ); + virtual BOOL CheckMeleeAttack1( float flDot, float flDist ); + virtual BOOL CheckMeleeAttack2( float flDot, float flDist ); + + BOOL FHaveSchedule( void ); + BOOL FScheduleValid ( void ); + void ClearSchedule( void ); + BOOL FScheduleDone ( void ); + void ChangeSchedule ( Schedule_t *pNewSchedule ); + void NextScheduledTask ( void ); + Schedule_t *ScheduleInList( const char *pName, Schedule_t **pList, int listCount ); + + virtual Schedule_t *ScheduleFromName( const char *pName ); + static Schedule_t *m_scheduleList[]; + + void MaintainSchedule ( void ); + virtual void StartTask ( Task_t *pTask ); + virtual void RunTask ( Task_t *pTask ); + virtual Schedule_t *GetScheduleOfType( int Type ); + virtual Schedule_t *GetSchedule( void ); + virtual void ScheduleChange( void ) {} + // virtual int CanPlaySequence( void ) { return ((m_pCine == NULL) && (m_MonsterState == MONSTERSTATE_NONE || m_MonsterState == MONSTERSTATE_IDLE || m_IdealMonsterState == MONSTERSTATE_IDLE)); } + virtual int CanPlaySequence( BOOL fDisregardState, int interruptLevel ); + virtual int CanPlaySentence( BOOL fDisregardState ) { return IsAlive(); } + virtual void PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ); + virtual void PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ); + + virtual void SentenceStop( void ); + + Task_t *GetTask ( void ); + virtual MONSTERSTATE GetIdealState ( void ); + virtual void SetActivity ( Activity NewActivity ); + void SetSequenceByName ( char *szSequence ); + void SetState ( MONSTERSTATE State ); + virtual void ReportAIState( void ); + + void CheckAttacks ( CBaseEntity *pTarget, float flDist ); + virtual int CheckEnemy ( CBaseEntity *pEnemy ); + void PushEnemy( CBaseEntity *pEnemy, Vector &vecLastKnownPos ); + BOOL PopEnemy( void ); + + BOOL FGetNodeRoute ( Vector vecDest ); + + inline void TaskComplete( void ) { if ( !HasConditions(bits_COND_TASK_FAILED) ) m_iTaskStatus = TASKSTATUS_COMPLETE; } + void MovementComplete( void ); + inline void TaskFail( void ) { SetConditions(bits_COND_TASK_FAILED); } + inline void TaskBegin( void ) { m_iTaskStatus = TASKSTATUS_RUNNING; } + int TaskIsRunning( void ); + inline int TaskIsComplete( void ) { return (m_iTaskStatus == TASKSTATUS_COMPLETE); } + inline int MovementIsComplete( void ) { return (m_movementGoal == MOVEGOAL_NONE); } + + int IScheduleFlags ( void ); + BOOL FRefreshRoute( void ); + BOOL FRouteClear ( void ); + void RouteSimplify( CBaseEntity *pTargetEnt ); + void AdvanceRoute ( float distance ); + virtual BOOL FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ); + void MakeIdealYaw( Vector vecTarget ); + virtual void SetYawSpeed ( void ) { return; };// allows different yaw_speeds for each activity + BOOL BuildRoute ( const Vector &vecGoal, int iMoveFlag, CBaseEntity *pTarget ); + virtual BOOL BuildNearestRoute ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ); + int RouteClassify( int iMoveFlag ); + void InsertWaypoint ( Vector vecLocation, int afMoveFlags ); + + BOOL FindLateralCover ( const Vector &vecThreat, const Vector &vecViewOffset ); + virtual BOOL FindCover ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ); + virtual BOOL FValidateCover ( const Vector &vecCoverLocation ) { return TRUE; }; + virtual float CoverRadius( void ) { return 784; } // Default cover radius + + virtual BOOL FCanCheckAttacks ( void ); + virtual void CheckAmmo( void ) { return; }; + virtual int IgnoreConditions ( void ); + + inline void SetConditions( int iConditions ) { m_afConditions |= iConditions; } + inline void ClearConditions( int iConditions ) { m_afConditions &= ~iConditions; } + inline BOOL HasConditions( int iConditions ) { if ( m_afConditions & iConditions ) return TRUE; return FALSE; } + inline BOOL HasAllConditions( int iConditions ) { if ( (m_afConditions & iConditions) == iConditions ) return TRUE; return FALSE; } + + virtual BOOL FValidateHintType( short sHint ); + int FindHintNode ( void ); + virtual BOOL FCanActiveIdle ( void ); + void SetTurnActivity ( void ); + float FLSoundVolume ( CSound *pSound ); + + BOOL MoveToNode( Activity movementAct, float waitTime, const Vector &goal ); + BOOL MoveToTarget( Activity movementAct, float waitTime ); + BOOL MoveToLocation( Activity movementAct, float waitTime, const Vector &goal ); + BOOL MoveToEnemy( Activity movementAct, float waitTime ); + + // Returns the time when the door will be open + float OpenDoorAndWait( entvars_t *pevDoor ); + + virtual int ISoundMask( void ); + virtual CSound* PBestSound ( void ); + virtual CSound* PBestScent ( void ); + virtual float HearingSensitivity( void ) { return 1.0; }; + + BOOL FBecomeProne ( void ); + virtual void BarnacleVictimBitten( entvars_t *pevBarnacle ); + virtual void BarnacleVictimReleased( void ); + + void SetEyePosition ( void ); + + BOOL FShouldEat( void );// see if a monster is 'hungry' + void Eat ( float flFullDuration );// make the monster 'full' for a while. + + CBaseEntity *CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ); + BOOL FacingIdeal( void ); + + BOOL FCheckAITrigger( void );// checks and, if necessary, fires the monster's trigger target. + BOOL NoFriendlyFire( void ); + + BOOL BBoxFlat( void ); + + // PrescheduleThink + virtual void PrescheduleThink( void ) { return; }; + + BOOL GetEnemy ( void ); + void MakeDamageBloodDecal ( int cCount, float flNoise, TraceResult *ptr, const Vector &vecDir ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + + // combat functions + float UpdateTarget ( entvars_t *pevTarget ); + virtual Activity GetDeathActivity ( void ); + Activity GetSmallFlinchActivity( void ); + virtual void Killed( entvars_t *pevAttacker, int iGib ); + virtual void GibMonster( void ); + BOOL ShouldGibMonster( int iGib ); + void CallGibMonster( void ); + virtual BOOL HasHumanGibs( void ); + virtual BOOL HasAlienGibs( void ); + virtual void FadeMonster( void ); // Called instead of GibMonster() when gibs are disabled + + Vector ShootAtEnemy( const Vector &shootOrigin ); + virtual Vector BodyTarget( const Vector &posSrc ) { return Center( ) * 0.75 + EyePosition() * 0.25; }; // position to shoot at + + virtual Vector GetGunPosition( void ); + + virtual int TakeHealth( float flHealth, int bitsDamageType ); + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + int DeadTakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + void RadiusDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ); + void RadiusDamage(Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ); + virtual int IsMoving( void ) { return m_movementGoal != MOVEGOAL_NONE; } + + void RouteClear( void ); + void RouteNew( void ); + + virtual void DeathSound ( void ) { return; }; + virtual void AlertSound ( void ) { return; }; + virtual void IdleSound ( void ) { return; }; + virtual void PainSound ( void ) { return; }; + + virtual void StopFollowing( BOOL clearSchedule ) {} + + inline void Remember( int iMemory ) { m_afMemory |= iMemory; } + inline void Forget( int iMemory ) { m_afMemory &= ~iMemory; } + inline BOOL HasMemory( int iMemory ) { if ( m_afMemory & iMemory ) return TRUE; return FALSE; } + inline BOOL HasAllMemories( int iMemory ) { if ( (m_afMemory & iMemory) == iMemory ) return TRUE; return FALSE; } + + BOOL ExitScriptedSequence( ); + BOOL CineCleanup( ); + + CBaseEntity* DropItem ( char *pszItemName, const Vector &vecPos, const Vector &vecAng );// drop an item. +}; + + + +#endif // BASEMONSTER_H diff --git a/dmc/dlls/bmodels.cpp b/dmc/dlls/bmodels.cpp new file mode 100644 index 0000000..e08ffa4 --- /dev/null +++ b/dmc/dlls/bmodels.cpp @@ -0,0 +1,958 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== bmodels.cpp ======================================================== + + spawn, think, and use functions for entities that use brush models + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "doors.h" + +extern DLL_GLOBAL Vector g_vecAttackDir; + +#define SF_BRUSH_ACCDCC 16// brush should accelerate and decelerate when toggled +#define SF_BRUSH_HURT 32// rotating brush that inflicts pain based on rotation speed +#define SF_ROTATING_NOT_SOLID 64 // some special rotating objects are not solid. + +// covering cheesy noise1, noise2, & noise3 fields so they make more sense (for rotating fans) +#define noiseStart noise1 +#define noiseStop noise2 +#define noiseRunning noise3 + +#define SF_PENDULUM_SWING 2 // spawnflag that makes a pendulum a rope swing. +// +// BModelOrigin - calculates origin of a bmodel from absmin/size because all bmodel origins are 0 0 0 +// +Vector VecBModelOrigin( entvars_t* pevBModel ) +{ + return pevBModel->absmin + ( pevBModel->size * 0.5 ); +} + +// =================== FUNC_WALL ============================================== + +/*QUAKED func_wall (0 .5 .8) ? +This is just a solid wall if not inhibited +*/ +class CFuncWall : public CBaseEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + // Bmodels don't go across transitions + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +LINK_ENTITY_TO_CLASS( func_wall, CFuncWall ); + +void CFuncWall :: Spawn( void ) +{ + pev->angles = g_vecZero; + pev->movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + pev->solid = SOLID_BSP; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + // If it can't move/go away, it's really part of the world + pev->flags |= FL_WORLDBRUSH; +} + + +void CFuncWall :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( ShouldToggle( useType, (int)(pev->frame)) ) + pev->frame = 1 - pev->frame; +} + + +#define SF_WALL_START_OFF 0x0001 + +class CFuncWallToggle : public CFuncWall +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void TurnOff( void ); + void TurnOn( void ); + BOOL IsOn( void ); +}; + +LINK_ENTITY_TO_CLASS( func_wall_toggle, CFuncWallToggle ); + +void CFuncWallToggle :: Spawn( void ) +{ + CFuncWall::Spawn(); + if ( pev->spawnflags & SF_WALL_START_OFF ) + TurnOff(); +} + + +void CFuncWallToggle :: TurnOff( void ) +{ + pev->solid = SOLID_NOT; + pev->effects |= EF_NODRAW; + UTIL_SetOrigin( pev, pev->origin ); +} + + +void CFuncWallToggle :: TurnOn( void ) +{ + pev->solid = SOLID_BSP; + pev->effects &= ~EF_NODRAW; + UTIL_SetOrigin( pev, pev->origin ); +} + + +BOOL CFuncWallToggle :: IsOn( void ) +{ + if ( pev->solid == SOLID_NOT ) + return FALSE; + return TRUE; +} + + +void CFuncWallToggle :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int status = IsOn(); + + if ( ShouldToggle( useType, status ) ) + { + if ( status ) + TurnOff(); + else + TurnOn(); + } +} + + +#define SF_CONVEYOR_VISUAL 0x0001 +#define SF_CONVEYOR_NOTSOLID 0x0002 + +class CFuncConveyor : public CFuncWall +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void UpdateSpeed( float speed ); +}; + +LINK_ENTITY_TO_CLASS( func_conveyor, CFuncConveyor ); +void CFuncConveyor :: Spawn( void ) +{ + SetMovedir( pev ); + CFuncWall::Spawn(); + + if ( !(pev->spawnflags & SF_CONVEYOR_VISUAL) ) + SetBits( pev->flags, FL_CONVEYOR ); + + // HACKHACK - This is to allow for some special effects + if ( pev->spawnflags & SF_CONVEYOR_NOTSOLID ) + { + pev->solid = SOLID_NOT; + pev->skin = 0; // Don't want the engine thinking we've got special contents on this brush + } + + if ( pev->speed == 0 ) + pev->speed = 100; + + UpdateSpeed( pev->speed ); +} + + +// HACKHACK -- This is ugly, but encode the speed in the rendercolor to avoid adding more data to the network stream +void CFuncConveyor :: UpdateSpeed( float speed ) +{ + // Encode it as an integer with 4 fractional bits + int speedCode = (int)(fabs(speed) * 16.0); + + if ( speed < 0 ) + pev->rendercolor.x = 1; + else + pev->rendercolor.x = 0; + + pev->rendercolor.y = (speedCode >> 8); + pev->rendercolor.z = (speedCode & 0xFF); +} + + +void CFuncConveyor :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + pev->speed = -pev->speed; + UpdateSpeed( pev->speed ); +} + + + +// =================== FUNC_ILLUSIONARY ============================================== + + +/*QUAKED func_illusionary (0 .5 .8) ? +A simple entity that looks solid but lets you walk through it. +*/ +class CFuncIllusionary : public CBaseToggle +{ +public: + void Spawn( void ); + void EXPORT SloshTouch( CBaseEntity *pOther ); + void KeyValue( KeyValueData *pkvd ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +LINK_ENTITY_TO_CLASS( func_illusionary, CFuncIllusionary ); + +void CFuncIllusionary :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "skin"))//skin is used for content type + { + pev->skin = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CFuncIllusionary :: Spawn( void ) +{ + pev->angles = g_vecZero; + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT;// always solid_not + SET_MODEL( ENT(pev), STRING(pev->model) ); + + // I'd rather eat the network bandwidth of this than figure out how to save/restore + // these entities after they have been moved to the client, or respawn them ala Quake + // Perhaps we can do this in deathmatch only. + // MAKE_STATIC(ENT(pev)); +} + + +// ------------------------------------------------------------------------------- +// +// Monster only clip brush +// +// This brush will be solid for any entity who has the FL_MONSTERCLIP flag set +// in pev->flags +// +// otherwise it will be invisible and not solid. This can be used to keep +// specific monsters out of certain areas +// +// ------------------------------------------------------------------------------- +class CFuncMonsterClip : public CFuncWall +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) {} // Clear out func_wall's use function +}; + +LINK_ENTITY_TO_CLASS( func_monsterclip, CFuncMonsterClip ); + +void CFuncMonsterClip::Spawn( void ) +{ + CFuncWall::Spawn(); + if ( CVAR_GET_FLOAT("showtriggers") == 0 ) + pev->effects = EF_NODRAW; + pev->flags |= FL_MONSTERCLIP; +} + + +// =================== FUNC_ROTATING ============================================== +class CFuncRotating : public CBaseEntity +{ +public: + // basic functions + void Spawn( void ); + void Precache( void ); + void EXPORT SpinUp ( void ); + void EXPORT SpinDown ( void ); + void KeyValue( KeyValueData* pkvd); + void EXPORT HurtTouch ( CBaseEntity *pOther ); + void EXPORT RotatingUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Rotate( void ); + void RampPitchVol (int fUp ); + void Blocked( CBaseEntity *pOther ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flFanFriction; + float m_flAttenuation; + float m_flVolume; + float m_pitch; + int m_sounds; +}; + +TYPEDESCRIPTION CFuncRotating::m_SaveData[] = +{ + DEFINE_FIELD( CFuncRotating, m_flFanFriction, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_flAttenuation, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_flVolume, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_pitch, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_sounds, FIELD_INTEGER ) +}; + +IMPLEMENT_SAVERESTORE( CFuncRotating, CBaseEntity ); + + +LINK_ENTITY_TO_CLASS( func_rotating, CFuncRotating ); + +void CFuncRotating :: KeyValue( KeyValueData* pkvd) +{ + if (FStrEq(pkvd->szKeyName, "fanfriction")) + { + m_flFanFriction = atof(pkvd->szValue)/100; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "Volume")) + { + m_flVolume = atof(pkvd->szValue)/10.0; + + if (m_flVolume > 1.0) + m_flVolume = 1.0; + if (m_flVolume < 0.0) + m_flVolume = 0.0; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spawnorigin")) + { + Vector tmp; + UTIL_StringToVector( (float *)tmp, pkvd->szValue ); + if ( tmp != g_vecZero ) + pev->origin = tmp; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +/*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS +You need to have an origin brush as part of this entity. The +center of that brush will be +the point around which it is rotated. It will rotate around the Z +axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"speed" determines how fast it moves; default value is 100. +"dmg" damage to inflict when blocked (2 default) + +REVERSE will cause the it to rotate in the opposite direction. +*/ + + +void CFuncRotating :: Spawn( ) +{ + // set final pitch. Must not be PITCH_NORM, since we + // plan on pitch shifting later. + + m_pitch = PITCH_NORM - 1; + + // maintain compatibility with previous maps + if (m_flVolume == 0.0) + m_flVolume = 1.0; + + // if the designer didn't set a sound attenuation, default to one. + m_flAttenuation = ATTN_NORM; + + if ( FBitSet ( pev->spawnflags, SF_BRUSH_ROTATE_SMALLRADIUS) ) + { + m_flAttenuation = ATTN_IDLE; + } + else if ( FBitSet ( pev->spawnflags, SF_BRUSH_ROTATE_MEDIUMRADIUS) ) + { + m_flAttenuation = ATTN_STATIC; + } + else if ( FBitSet ( pev->spawnflags, SF_BRUSH_ROTATE_LARGERADIUS) ) + { + m_flAttenuation = ATTN_NORM; + } + + // prevent divide by zero if level designer forgets friction! + if ( m_flFanFriction == 0 ) + { + m_flFanFriction = 1; + } + + if ( FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_Z_AXIS) ) + pev->movedir = Vector(0,0,1); + else if ( FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_X_AXIS) ) + pev->movedir = Vector(1,0,0); + else + pev->movedir = Vector(0,1,0); // y-axis + + // check for reverse rotation + if ( FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_BACKWARDS) ) + pev->movedir = pev->movedir * -1; + + // some rotating objects like fake volumetric lights will not be solid. + if ( FBitSet(pev->spawnflags, SF_ROTATING_NOT_SOLID) ) + { + pev->solid = SOLID_NOT; + pev->skin = CONTENTS_EMPTY; + pev->movetype = MOVETYPE_PUSH; + } + else + { + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + } + + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + SetUse( &CFuncRotating::RotatingUse ); + // did level designer forget to assign speed? + if (pev->speed <= 0) + pev->speed = 0; + + // Removed this per level designers request. -- JAY + // if (pev->dmg == 0) + // pev->dmg = 2; + + // instant-use brush? + if ( FBitSet( pev->spawnflags, SF_BRUSH_ROTATE_INSTANT) ) + { + SetThink( &CFuncRotating::SUB_CallUseToggle ); + pev->nextthink = pev->ltime + 1.5; // leave a magic delay for client to start up + } + // can this brush inflict pain? + if ( FBitSet (pev->spawnflags, SF_BRUSH_HURT) ) + { + SetTouch( &CFuncRotating::HurtTouch ); + } + + Precache( ); +} + + +void CFuncRotating :: Precache( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + + // set up fan sounds + + if (!FStringNull( pev->message ) && strlen( szSoundFile ) > 0) + { + // if a path is set for a wave, use it + + PRECACHE_SOUND(szSoundFile); + + pev->noiseRunning = ALLOC_STRING(szSoundFile); + } else + { + // otherwise use preset sound + switch (m_sounds) + { + case 1: + PRECACHE_SOUND ("fans/fan1.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan1.wav"); + break; + case 2: + PRECACHE_SOUND ("fans/fan2.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan2.wav"); + break; + case 3: + PRECACHE_SOUND ("fans/fan3.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan3.wav"); + break; + case 4: + PRECACHE_SOUND ("fans/fan4.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan4.wav"); + break; + case 5: + PRECACHE_SOUND ("fans/fan5.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan5.wav"); + break; + + case 0: + default: + if (!FStringNull( pev->message ) && strlen( szSoundFile ) > 0) + { + PRECACHE_SOUND(szSoundFile); + + pev->noiseRunning = ALLOC_STRING(szSoundFile); + break; + } else + { + pev->noiseRunning = ALLOC_STRING("common/null.wav"); + break; + } + } + } + + if (pev->avelocity != g_vecZero ) + { + // if fan was spinning, and we went through transition or save/restore, + // make sure we restart the sound. 1.5 sec delay is magic number. KDB + + SetThink ( &CFuncRotating::SpinUp ); + pev->nextthink = pev->ltime + 1.5; + } +} + + + +// +// Touch - will hurt others based on how fast the brush is spinning +// +void CFuncRotating :: HurtTouch ( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + // we can't hurt this thing, so we're not concerned with it + if ( !pevOther->takedamage ) + return; + + // calculate damage based on rotation speed + pev->dmg = pev->avelocity.Length() / 10; + + pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH); + + pevOther->velocity = (pevOther->origin - VecBModelOrigin(pev) ).Normalize() * pev->dmg; +} + +// +// RampPitchVol - ramp pitch and volume up to final values, based on difference +// between how fast we're going vs how fast we plan to go +// +#define FANPITCHMIN 30 +#define FANPITCHMAX 100 + +void CFuncRotating :: RampPitchVol (int fUp) +{ + + Vector vecAVel = pev->avelocity; + vec_t vecCur; + vec_t vecFinal; + float fpct; + float fvol; + float fpitch; + int pitch; + + // get current angular velocity + + vecCur = abs(vecAVel.x != 0 ? vecAVel.x : (vecAVel.y != 0 ? vecAVel.y : vecAVel.z)); + + // get target angular velocity + + vecFinal = (pev->movedir.x != 0 ? pev->movedir.x : (pev->movedir.y != 0 ? pev->movedir.y : pev->movedir.z)); + vecFinal *= pev->speed; + vecFinal = abs(vecFinal); + + // calc volume and pitch as % of final vol and pitch + + fpct = vecCur / vecFinal; +// if (fUp) +// fvol = m_flVolume * (0.5 + fpct/2.0); // spinup volume ramps up from 50% max vol +// else + fvol = m_flVolume * fpct; // slowdown volume ramps down to 0 + + fpitch = FANPITCHMIN + (FANPITCHMAX - FANPITCHMIN) * fpct; + + pitch = (int) fpitch; + if (pitch == PITCH_NORM) + pitch = PITCH_NORM-1; + + // change the fan's vol and pitch + + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + fvol, m_flAttenuation, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch); + +} + +// +// SpinUp - accelerates a non-moving func_rotating up to it's speed +// +void CFuncRotating :: SpinUp( void ) +{ + Vector vecAVel;//rotational velocity + + pev->nextthink = pev->ltime + 0.1; + pev->avelocity = pev->avelocity + ( pev->movedir * ( pev->speed * m_flFanFriction ) ); + + vecAVel = pev->avelocity;// cache entity's rotational velocity + + // if we've met or exceeded target speed, set target speed and stop thinking + if ( abs(vecAVel.x) >= abs(pev->movedir.x * pev->speed) && + abs(vecAVel.y) >= abs(pev->movedir.y * pev->speed) && + abs(vecAVel.z) >= abs(pev->movedir.z * pev->speed) ) + { + pev->avelocity = pev->movedir * pev->speed;// set speed in case we overshot + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + m_flVolume, m_flAttenuation, SND_CHANGE_PITCH | SND_CHANGE_VOL, FANPITCHMAX); + + SetThink( &CFuncRotating::Rotate ); + Rotate(); + } + else + { + RampPitchVol(TRUE); + } +} + +// +// SpinDown - decelerates a moving func_rotating to a standstill. +// +void CFuncRotating :: SpinDown( void ) +{ + Vector vecAVel;//rotational velocity + vec_t vecdir; + + pev->nextthink = pev->ltime + 0.1; + + pev->avelocity = pev->avelocity - ( pev->movedir * ( pev->speed * m_flFanFriction ) );//spin down slower than spinup + + vecAVel = pev->avelocity;// cache entity's rotational velocity + + if (pev->movedir.x != 0) + vecdir = pev->movedir.x; + else if (pev->movedir.y != 0) + vecdir = pev->movedir.y; + else + vecdir = pev->movedir.z; + + // if we've met or exceeded target speed, set target speed and stop thinking + // (note: must check for movedir > 0 or < 0) + if (((vecdir > 0) && (vecAVel.x <= 0 && vecAVel.y <= 0 && vecAVel.z <= 0)) || + ((vecdir < 0) && (vecAVel.x >= 0 && vecAVel.y >= 0 && vecAVel.z >= 0))) + { + pev->avelocity = g_vecZero;// set speed in case we overshot + + // stop sound, we're done + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning /* Stop */), + 0, 0, SND_STOP, m_pitch); + + SetThink( &CFuncRotating::Rotate ); + Rotate(); + } + else + { + RampPitchVol(FALSE); + } +} + +void CFuncRotating :: Rotate( void ) +{ + pev->nextthink = pev->ltime + 10; +} + +//========================================================= +// Rotating Use - when a rotating brush is triggered +//========================================================= +void CFuncRotating :: RotatingUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // is this a brush that should accelerate and decelerate when turned on/off (fan)? + if ( FBitSet ( pev->spawnflags, SF_BRUSH_ACCDCC ) ) + { + // fan is spinning, so stop it. + if ( pev->avelocity != g_vecZero ) + { + SetThink ( &CFuncRotating::SpinDown ); + //EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, (char *)STRING(pev->noiseStop), + // m_flVolume, m_flAttenuation, 0, m_pitch); + + pev->nextthink = pev->ltime + 0.1; + } + else// fan is not moving, so start it + { + SetThink ( &CFuncRotating::SpinUp ); + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + 0.01, m_flAttenuation, 0, FANPITCHMIN); + + pev->nextthink = pev->ltime + 0.1; + } + } + else if ( !FBitSet ( pev->spawnflags, SF_BRUSH_ACCDCC ) )//this is a normal start/stop brush. + { + if ( pev->avelocity != g_vecZero ) + { + // play stopping sound here + SetThink ( &CFuncRotating::SpinDown ); + + // EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, (char *)STRING(pev->noiseStop), + // m_flVolume, m_flAttenuation, 0, m_pitch); + + pev->nextthink = pev->ltime + 0.1; + // pev->avelocity = g_vecZero; + } + else + { + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + m_flVolume, m_flAttenuation, 0, FANPITCHMAX); + pev->avelocity = pev->movedir * pev->speed; + + SetThink( &CFuncRotating::Rotate ); + Rotate(); + } + } +} + + +// +// RotatingBlocked - An entity has blocked the brush +// +void CFuncRotating :: Blocked( CBaseEntity *pOther ) + +{ + pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH); +} + + + + + + +//#endif + + +class CPendulum : public CBaseEntity +{ +public: + void Spawn ( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT Swing( void ); + void EXPORT PendulumUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Stop( void ); + void Touch( CBaseEntity *pOther ); + void EXPORT RopeTouch ( CBaseEntity *pOther );// this touch func makes the pendulum a rope + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + void Blocked( CBaseEntity *pOther ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_accel; // Acceleration + float m_distance; // + float m_time; + float m_damp; + float m_maxSpeed; + float m_dampSpeed; + vec3_t m_center; + vec3_t m_start; +}; + +LINK_ENTITY_TO_CLASS( func_pendulum, CPendulum ); + +TYPEDESCRIPTION CPendulum::m_SaveData[] = +{ + DEFINE_FIELD( CPendulum, m_accel, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_distance, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_time, FIELD_TIME ), + DEFINE_FIELD( CPendulum, m_damp, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_maxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_dampSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_center, FIELD_VECTOR ), + DEFINE_FIELD( CPendulum, m_start, FIELD_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CPendulum, CBaseEntity ); + + + +void CPendulum :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "distance")) + { + m_distance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damp")) + { + m_damp = atof(pkvd->szValue) * 0.001; + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +void CPendulum :: Spawn( void ) +{ + // set the axis of rotation + CBaseToggle :: AxisDir( pev ); + + if ( FBitSet (pev->spawnflags, SF_DOOR_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + if ( m_distance == 0 ) + return; + + if (pev->speed == 0) + pev->speed = 100; + + m_accel = (pev->speed * pev->speed) / (2 * fabs(m_distance)); // Calculate constant acceleration from speed and distance + m_maxSpeed = pev->speed; + m_start = pev->angles; + m_center = pev->angles + (m_distance * 0.5) * pev->movedir; + + if ( FBitSet( pev->spawnflags, SF_BRUSH_ROTATE_INSTANT) ) + { + SetThink( &CPendulum::SUB_CallUseToggle ); + pev->nextthink = gpGlobals->time + 0.1; + } + pev->speed = 0; + SetUse( &CPendulum::PendulumUse ); + + if ( FBitSet( pev->spawnflags, SF_PENDULUM_SWING ) ) + { + SetTouch ( &CPendulum::RopeTouch ); + } +} + + +void CPendulum :: PendulumUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->speed ) // Pendulum is moving, stop it and auto-return if necessary + { + if ( FBitSet( pev->spawnflags, SF_PENDULUM_AUTO_RETURN ) ) + { + float delta; + + delta = CBaseToggle :: AxisDelta( pev->spawnflags, pev->angles, m_start ); + + pev->avelocity = m_maxSpeed * pev->movedir; + pev->nextthink = pev->ltime + (delta / m_maxSpeed); + SetThink( &CPendulum::Stop ); + } + else + { + pev->speed = 0; // Dead stop + SetThink( NULL ); + pev->avelocity = g_vecZero; + } + } + else + { + pev->nextthink = pev->ltime + 0.1; // Start the pendulum moving + m_time = gpGlobals->time; // Save time to calculate dt + SetThink( &CPendulum::Swing ); + m_dampSpeed = m_maxSpeed; + } +} + + +void CPendulum :: Stop( void ) +{ + pev->angles = m_start; + pev->speed = 0; + SetThink( NULL ); + pev->avelocity = g_vecZero; +} + + +void CPendulum::Blocked( CBaseEntity *pOther ) +{ + m_time = gpGlobals->time; +} + + +void CPendulum :: Swing( void ) +{ + float delta, dt; + + delta = CBaseToggle :: AxisDelta( pev->spawnflags, pev->angles, m_center ); + dt = gpGlobals->time - m_time; // How much time has passed? + m_time = gpGlobals->time; // Remember the last time called + + if ( delta > 0 && m_accel > 0 ) + pev->speed -= m_accel * dt; // Integrate velocity + else + pev->speed += m_accel * dt; + + if ( pev->speed > m_maxSpeed ) + pev->speed = m_maxSpeed; + else if ( pev->speed < -m_maxSpeed ) + pev->speed = -m_maxSpeed; + // scale the destdelta vector by the time spent traveling to get velocity + pev->avelocity = pev->speed * pev->movedir; + + // Call this again + pev->nextthink = pev->ltime + 0.1; + + if ( m_damp ) + { + m_dampSpeed -= m_damp * m_dampSpeed * dt; + if ( m_dampSpeed < 30.0 ) + { + pev->angles = m_center; + pev->speed = 0; + SetThink( NULL ); + pev->avelocity = g_vecZero; + } + else if ( pev->speed > m_dampSpeed ) + pev->speed = m_dampSpeed; + else if ( pev->speed < -m_dampSpeed ) + pev->speed = -m_dampSpeed; + + } +} + + +void CPendulum :: Touch ( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + if ( pev->dmg <= 0 ) + return; + + // we can't hurt this thing, so we're not concerned with it + if ( !pevOther->takedamage ) + return; + + // calculate damage based on rotation speed + float damage = pev->dmg * pev->speed * 0.01; + + if ( damage < 0 ) + damage = -damage; + + pOther->TakeDamage( pev, pev, damage, DMG_CRUSH ); + + pevOther->velocity = (pevOther->origin - VecBModelOrigin(pev) ).Normalize() * damage; +} + +void CPendulum :: RopeTouch ( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + if ( !pOther->IsPlayer() ) + {// not a player! + ALERT ( at_console, "Not a client\n" ); + return; + } + + if ( ENT(pevOther) == pev->enemy ) + {// this player already on the rope. + return; + } + + pev->enemy = pOther->edict(); + pevOther->velocity = g_vecZero; + pevOther->movetype = MOVETYPE_NONE; +} + + diff --git a/dmc/dlls/buttons.cpp b/dmc/dlls/buttons.cpp new file mode 100644 index 0000000..5ea927d --- /dev/null +++ b/dmc/dlls/buttons.cpp @@ -0,0 +1,1279 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== buttons.cpp ======================================================== + + button-related code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "doors.h" + +#if !defined ( _WIN32 ) +#include // memset()))) +#endif + +#define SF_BUTTON_DONTMOVE 1 +#define SF_ROTBUTTON_NOTSOLID 1 +#define SF_BUTTON_TOGGLE 32 // button stays pushed until reactivated +#define SF_BUTTON_SPARK_IF_OFF 64 // button sparks in OFF state +#define SF_BUTTON_TOUCH_ONLY 256 // button only fires as a result of USE key. + +#define SF_GLOBAL_SET 1 // Set global state to initial state on spawn + +class CEnvGlobal : public CPointEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + string_t m_globalstate; + int m_triggermode; + int m_initialstate; +}; + +TYPEDESCRIPTION CEnvGlobal::m_SaveData[] = +{ + DEFINE_FIELD( CEnvGlobal, m_globalstate, FIELD_STRING ), + DEFINE_FIELD( CEnvGlobal, m_triggermode, FIELD_INTEGER ), + DEFINE_FIELD( CEnvGlobal, m_initialstate, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CEnvGlobal, CBaseEntity ); + +LINK_ENTITY_TO_CLASS( env_global, CEnvGlobal ); + +void CEnvGlobal::KeyValue( KeyValueData *pkvd ) +{ + pkvd->fHandled = TRUE; + + if ( FStrEq(pkvd->szKeyName, "globalstate") ) // State name + m_globalstate = ALLOC_STRING( pkvd->szValue ); + else if ( FStrEq(pkvd->szKeyName, "triggermode") ) + m_triggermode = atoi( pkvd->szValue ); + else if ( FStrEq(pkvd->szKeyName, "initialstate") ) + m_initialstate = atoi( pkvd->szValue ); + else + CPointEntity::KeyValue( pkvd ); +} + +void CEnvGlobal::Spawn( void ) +{ + if ( !m_globalstate ) + { + REMOVE_ENTITY( ENT(pev) ); + return; + } + if ( FBitSet( pev->spawnflags, SF_GLOBAL_SET ) ) + { + if ( !gGlobalState.EntityInTable( m_globalstate ) ) + gGlobalState.EntityAdd( m_globalstate, gpGlobals->mapname, (GLOBALESTATE)m_initialstate ); + } +} + + +void CEnvGlobal::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + GLOBALESTATE oldState = gGlobalState.EntityGetState( m_globalstate ); + GLOBALESTATE newState; + + switch( m_triggermode ) + { + case 0: + newState = GLOBAL_OFF; + break; + + case 1: + newState = GLOBAL_ON; + break; + + case 2: + newState = GLOBAL_DEAD; + break; + + default: + case 3: + if ( oldState == GLOBAL_ON ) + newState = GLOBAL_OFF; + else if ( oldState == GLOBAL_OFF ) + newState = GLOBAL_ON; + else + newState = oldState; + } + + if ( gGlobalState.EntityInTable( m_globalstate ) ) + gGlobalState.EntitySetState( m_globalstate, newState ); + else + gGlobalState.EntityAdd( m_globalstate, gpGlobals->mapname, newState ); +} + + + +TYPEDESCRIPTION CMultiSource::m_SaveData[] = +{ + //!!!BUGBUG FIX + DEFINE_ARRAY( CMultiSource, m_rgEntities, FIELD_EHANDLE, MS_MAX_TARGETS ), + DEFINE_ARRAY( CMultiSource, m_rgTriggered, FIELD_INTEGER, MS_MAX_TARGETS ), + DEFINE_FIELD( CMultiSource, m_iTotal, FIELD_INTEGER ), + DEFINE_FIELD( CMultiSource, m_globalstate, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CMultiSource, CBaseEntity ); + +LINK_ENTITY_TO_CLASS( multisource, CMultiSource ); +// +// Cache user-entity-field values until spawn is called. +// + +void CMultiSource::KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "killtarget") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + pkvd->fHandled = TRUE; + else if ( FStrEq(pkvd->szKeyName, "globalstate") ) + { + m_globalstate = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +#define SF_MULTI_INIT 1 + +void CMultiSource::Spawn() +{ + // set up think for later registration + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->nextthink = gpGlobals->time + 0.1; + pev->spawnflags |= SF_MULTI_INIT; // Until it's initialized + SetThink(&CMultiSource::Register); +} + +void CMultiSource::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int i = 0; + + // Find the entity in our list + while (i < m_iTotal) + if ( m_rgEntities[i++] == pCaller ) + break; + + // if we didn't find it, report error and leave + if (i > m_iTotal) + { + ALERT(at_console, "MultiSrc:Used by non member %s.\n", STRING(pCaller->pev->classname)); + return; + } + + // CONSIDER: a Use input to the multisource always toggles. Could check useType for ON/OFF/TOGGLE + + m_rgTriggered[i-1] ^= 1; + + // + if ( IsTriggered( pActivator ) ) + { + ALERT( at_aiconsole, "Multisource %s enabled (%d inputs)\n", STRING(pev->targetname), m_iTotal ); + USE_TYPE useType = USE_TOGGLE; + if ( m_globalstate ) + useType = USE_ON; + SUB_UseTargets( NULL, useType, 0 ); + } +} + + +BOOL CMultiSource::IsTriggered( CBaseEntity * ) +{ + // Is everything triggered? + int i = 0; + + // Still initializing? + if ( pev->spawnflags & SF_MULTI_INIT ) + return 0; + + while (i < m_iTotal) + { + if (m_rgTriggered[i] == 0) + break; + i++; + } + + if (i == m_iTotal) + { + if ( !m_globalstate || gGlobalState.EntityGetState( m_globalstate ) == GLOBAL_ON ) + return 1; + } + + return 0; +} + +void CMultiSource::Register(void) +{ + edict_t *pentTarget = NULL; + + m_iTotal = 0; + memset( m_rgEntities, 0, MS_MAX_TARGETS * sizeof(EHANDLE) ); + + SetThink(&CMultiSource::SUB_DoNothing); + + // search for all entities which target this multisource (pev->targetname) + + pentTarget = FIND_ENTITY_BY_STRING(NULL, "target", STRING(pev->targetname)); + + while (!FNullEnt(pentTarget) && (m_iTotal < MS_MAX_TARGETS)) + { + CBaseEntity *pTarget = CBaseEntity::Instance(pentTarget); + if ( pTarget ) + m_rgEntities[m_iTotal++] = pTarget; + + pentTarget = FIND_ENTITY_BY_STRING( pentTarget, "target", STRING(pev->targetname)); + } + + pentTarget = FIND_ENTITY_BY_STRING(NULL, "classname", "multi_manager"); + while (!FNullEnt(pentTarget) && (m_iTotal < MS_MAX_TARGETS)) + { + CBaseEntity *pTarget = CBaseEntity::Instance(pentTarget); + if ( pTarget && pTarget->HasTarget(pev->targetname) ) + m_rgEntities[m_iTotal++] = pTarget; + + pentTarget = FIND_ENTITY_BY_STRING( pentTarget, "classname", "multi_manager" ); + } + + pev->spawnflags &= ~SF_MULTI_INIT; +} + +// CBaseButton +TYPEDESCRIPTION CBaseButton::m_SaveData[] = +{ + DEFINE_FIELD( CBaseButton, m_fStayPushed, FIELD_BOOLEAN ), + DEFINE_FIELD( CBaseButton, m_fRotating, FIELD_BOOLEAN ), + + DEFINE_FIELD( CBaseButton, m_sounds, FIELD_INTEGER ), + DEFINE_FIELD( CBaseButton, m_bLockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_bLockedSentence, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_bUnlockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_bUnlockedSentence, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_strChangeTarget, FIELD_STRING ), +// DEFINE_FIELD( CBaseButton, m_ls, FIELD_??? ), // This is restored in Precache() +}; + + +IMPLEMENT_SAVERESTORE( CBaseButton, CBaseToggle ); + +void CBaseButton::Precache( void ) +{ + char *pszSound; + + if ( FBitSet ( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) )// this button should spark in OFF state + { + PRECACHE_SOUND ("buttons/spark1.wav"); + PRECACHE_SOUND ("buttons/spark2.wav"); + PRECACHE_SOUND ("buttons/spark3.wav"); + PRECACHE_SOUND ("buttons/spark4.wav"); + PRECACHE_SOUND ("buttons/spark5.wav"); + PRECACHE_SOUND ("buttons/spark6.wav"); + } + + // get door button sounds, for doors which require buttons to open + + if (m_bLockedSound) + { + pszSound = ButtonSound( (int)m_bLockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sLockedSound = ALLOC_STRING(pszSound); + } + + if (m_bUnlockedSound) + { + pszSound = ButtonSound( (int)m_bUnlockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sUnlockedSound = ALLOC_STRING(pszSound); + } + + // get sentence group names, for doors which are directly 'touched' to open + + switch (m_bLockedSentence) + { + case 1: m_ls.sLockedSentence = MAKE_STRING("NA"); break; // access denied + case 2: m_ls.sLockedSentence = MAKE_STRING("ND"); break; // security lockout + case 3: m_ls.sLockedSentence = MAKE_STRING("NF"); break; // blast door + case 4: m_ls.sLockedSentence = MAKE_STRING("NFIRE"); break; // fire door + case 5: m_ls.sLockedSentence = MAKE_STRING("NCHEM"); break; // chemical door + case 6: m_ls.sLockedSentence = MAKE_STRING("NRAD"); break; // radiation door + case 7: m_ls.sLockedSentence = MAKE_STRING("NCON"); break; // gen containment + case 8: m_ls.sLockedSentence = MAKE_STRING("NH"); break; // maintenance door + case 9: m_ls.sLockedSentence = MAKE_STRING("NG"); break; // broken door + + default: m_ls.sLockedSentence = 0; break; + } + + switch (m_bUnlockedSentence) + { + case 1: m_ls.sUnlockedSentence = MAKE_STRING("EA"); break; // access granted + case 2: m_ls.sUnlockedSentence = MAKE_STRING("ED"); break; // security door + case 3: m_ls.sUnlockedSentence = MAKE_STRING("EF"); break; // blast door + case 4: m_ls.sUnlockedSentence = MAKE_STRING("EFIRE"); break; // fire door + case 5: m_ls.sUnlockedSentence = MAKE_STRING("ECHEM"); break; // chemical door + case 6: m_ls.sUnlockedSentence = MAKE_STRING("ERAD"); break; // radiation door + case 7: m_ls.sUnlockedSentence = MAKE_STRING("ECON"); break; // gen containment + case 8: m_ls.sUnlockedSentence = MAKE_STRING("EH"); break; // maintenance door + + default: m_ls.sUnlockedSentence = 0; break; + } +} + +// +// Cache user-entity-field values until spawn is called. +// + +void CBaseButton::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "changetarget")) + { + m_strChangeTarget = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sound")) + { + m_bLockedSound = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sentence")) + { + m_bLockedSentence = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sound")) + { + m_bUnlockedSound = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sentence")) + { + m_bUnlockedSentence = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +// +// ButtonShot +// +int CBaseButton::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + BUTTON_CODE code = ButtonResponseToTouch(); + + if ( code == BUTTON_NOTHING ) + return 0; + // Temporarily disable the touch function, until movement is finished. + SetTouch( NULL ); + + m_hActivator = CBaseEntity::Instance( pevAttacker ); + if ( m_hActivator == NULL ) + return 0; + + if ( code == BUTTON_RETURN ) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + // Toggle buttons fire when they get back to their "home" position + if ( !(pev->spawnflags & SF_BUTTON_TOGGLE) ) + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + ButtonReturn(); + } + else // code == BUTTON_ACTIVATE + ButtonActivate( ); + + return 0; +} + +/*QUAKED func_button (0 .5 .8) ? +When a button is touched, it moves some distance in the direction of it's angle, +triggers all of it's targets, waits some time, then returns to it's original position +where it can be triggered again. + +"angle" determines the opening direction +"target" all entities with a matching targetname will be used +"speed" override the default 40 speed +"wait" override the default 1 second wait (-1 = never return) +"lip" override the default 4 pixel lip remaining at end of move +"health" if set, the button must be killed instead of touched +"sounds" +0) steam metal +1) wooden clunk +2) metallic click +3) in-out +*/ +LINK_ENTITY_TO_CLASS( func_button, CBaseButton ); + + +void CBaseButton::Spawn( ) +{ + char *pszSound; + + //---------------------------------------------------- + //determine sounds for buttons + //a sound of 0 should not make a sound + //---------------------------------------------------- + pszSound = ButtonSound( m_sounds ); + PRECACHE_SOUND(pszSound); + pev->noise = ALLOC_STRING(pszSound); + + Precache(); + + if ( FBitSet ( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) )// this button should spark in OFF state + { + SetThink ( &CBaseButton::ButtonSpark ); + pev->nextthink = gpGlobals->time + 0.5;// no hurry, make sure everything else spawns + } + + SetMovedir(pev); + + pev->movetype = MOVETYPE_PUSH; + pev->solid = SOLID_BSP; + SET_MODEL(ENT(pev), STRING(pev->model)); + + if (pev->speed == 0) + pev->speed = 40; + + if (pev->health > 0) + { + pev->takedamage = DAMAGE_YES; + } + + if (m_flWait == 0) + m_flWait = 1; + if (m_flLip == 0) + m_flLip = 4; + + m_toggle_state = TS_AT_BOTTOM; + m_vecPosition1 = pev->origin; + // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big + m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); + + + // Is this a non-moving button? + if ( ((m_vecPosition2 - m_vecPosition1).Length() < 1) || (pev->spawnflags & SF_BUTTON_DONTMOVE) ) + m_vecPosition2 = m_vecPosition1; + + m_fStayPushed = (m_flWait == -1 ? TRUE : FALSE); + m_fRotating = FALSE; + + // if the button is flagged for USE button activation only, take away it's touch function and add a use function + + if ( FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // touchable button + { + SetTouch( &CBaseButton::ButtonTouch ); + } + else + { + SetTouch ( NULL ); + SetUse ( &CBaseButton::ButtonUse ); + } +} + + +// Button sound table. +// Also used by CBaseDoor to get 'touched' door lock/unlock sounds + +char *ButtonSound( int sound ) +{ + char *pszSound; + + switch ( sound ) + { + case 0: pszSound = "common/null.wav"; break; + case 1: pszSound = "buttons/button1.wav"; break; + case 2: pszSound = "buttons/button2.wav"; break; + case 3: pszSound = "buttons/button3.wav"; break; + case 4: pszSound = "buttons/button4.wav"; break; + case 5: pszSound = "buttons/button5.wav"; break; + case 6: pszSound = "buttons/button6.wav"; break; + case 7: pszSound = "buttons/button7.wav"; break; + case 8: pszSound = "buttons/button8.wav"; break; + case 9: pszSound = "buttons/button9.wav"; break; + case 10: pszSound = "buttons/button10.wav"; break; + case 11: pszSound = "buttons/button11.wav"; break; + case 12: pszSound = "buttons/latchlocked1.wav"; break; + case 13: pszSound = "buttons/latchunlocked1.wav"; break; + case 14: pszSound = "buttons/lightswitch2.wav";break; + +// next 6 slots reserved for any additional sliding button sounds we may add + + case 21: pszSound = "buttons/lever1.wav"; break; + case 22: pszSound = "buttons/lever2.wav"; break; + case 23: pszSound = "buttons/lever3.wav"; break; + case 24: pszSound = "buttons/lever4.wav"; break; + case 25: pszSound = "buttons/lever5.wav"; break; + + default:pszSound = "buttons/button9.wav"; break; + } + + return pszSound; +} + +// +// Makes flagged buttons spark when turned off +// + +void DoSpark(entvars_t *pev, const Vector &location ) +{ + Vector tmp = location + pev->size * 0.5; + UTIL_Sparks( tmp ); + + float flVolume = RANDOM_FLOAT ( 0.25 , 0.75 ) * 0.4;//random volume range + switch ( (int)(RANDOM_FLOAT(0,1) * 6) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark1.wav", flVolume, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark2.wav", flVolume, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark3.wav", flVolume, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark4.wav", flVolume, ATTN_NORM); break; + case 4: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM); break; + case 5: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM); break; + } +} + +void CBaseButton::ButtonSpark ( void ) +{ + SetThink ( &CBaseButton::ButtonSpark ); + pev->nextthink = gpGlobals->time + ( 0.1 + RANDOM_FLOAT ( 0, 1.5 ) );// spark again at random interval + + DoSpark( pev, pev->mins ); +} + + +// +// Button's Use function +// +void CBaseButton::ButtonUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Ignore touches if button is moving, or pushed-in and waiting to auto-come-out. + // UNDONE: Should this use ButtonResponseToTouch() too? + if (m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN ) + return; + + m_hActivator = pActivator; + if ( m_toggle_state == TS_AT_TOP) + { + if (!m_fStayPushed && FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE)) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + //SUB_UseTargets( m_eoActivator ); + ButtonReturn(); + } + } + else + ButtonActivate( ); +} + + +CBaseButton::BUTTON_CODE CBaseButton::ButtonResponseToTouch( void ) +{ + // Ignore touches if button is moving, or pushed-in and waiting to auto-come-out. + if (m_toggle_state == TS_GOING_UP || + m_toggle_state == TS_GOING_DOWN || + (m_toggle_state == TS_AT_TOP && !m_fStayPushed && !FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE) ) ) + return BUTTON_NOTHING; + + if (m_toggle_state == TS_AT_TOP) + { + if((FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE) ) && !m_fStayPushed) + { + return BUTTON_RETURN; + } + } + else + return BUTTON_ACTIVATE; + + return BUTTON_NOTHING; +} + + +// +// Touching a button simply "activates" it. +// +void CBaseButton:: ButtonTouch( CBaseEntity *pOther ) +{ + // Ignore touches by anything but players + if (!FClassnameIs(pOther->pev, "player")) + return; + + m_hActivator = pOther; + + BUTTON_CODE code = ButtonResponseToTouch(); + + if ( code == BUTTON_NOTHING ) + return; + + if (!UTIL_IsMasterTriggered(m_sMaster, pOther)) + { + // play button locked sound + PlayLockSounds(pev, &m_ls, TRUE, TRUE); + return; + } + + // Temporarily disable the touch function, until movement is finished. + SetTouch( NULL ); + + if ( code == BUTTON_RETURN ) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + ButtonReturn(); + } + else // code == BUTTON_ACTIVATE + ButtonActivate( ); +} + +// +// Starts the button moving "in/up". +// +void CBaseButton::ButtonActivate( ) +{ + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + { + // button is locked, play locked sound + PlayLockSounds(pev, &m_ls, TRUE, TRUE); + return; + } + else + { + // button is unlocked, play unlocked sound + PlayLockSounds(pev, &m_ls, FALSE, TRUE); + } + + ASSERT(m_toggle_state == TS_AT_BOTTOM); + m_toggle_state = TS_GOING_UP; + + SetMoveDone( &CBaseButton::TriggerAndWait ); + if (!m_fRotating) + LinearMove( m_vecPosition2, pev->speed); + else + AngularMove( m_vecAngle2, pev->speed); +} + +// +// Button has reached the "in/up" position. Activate its "targets", and pause before "popping out". +// +void CBaseButton::TriggerAndWait( void ) +{ + ASSERT(m_toggle_state == TS_GOING_UP); + + if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + return; + + m_toggle_state = TS_AT_TOP; + + // If button automatically comes back out, start it moving out. + // Else re-instate touch method + if (m_fStayPushed || FBitSet ( pev->spawnflags, SF_BUTTON_TOGGLE ) ) + { + if ( !FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // this button only works if USED, not touched! + { + // ALL buttons are now use only + SetTouch ( NULL ); + } + else + SetTouch( &CBaseButton::ButtonTouch ); + } + else + { + pev->nextthink = pev->ltime + m_flWait; + SetThink( &CBaseButton::ButtonReturn ); + } + + pev->frame = 1; // use alternate textures + + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); +} + + +// +// Starts the button moving "out/down". +// +void CBaseButton::ButtonReturn( void ) +{ + ASSERT(m_toggle_state == TS_AT_TOP); + m_toggle_state = TS_GOING_DOWN; + + SetMoveDone( &CBaseButton::ButtonBackHome ); + if (!m_fRotating) + LinearMove( m_vecPosition1, pev->speed); + else + AngularMove( m_vecAngle1, pev->speed); + + pev->frame = 0; // use normal textures +} + + +// +// Button has returned to start state. Quiesce it. +// +void CBaseButton::ButtonBackHome( void ) +{ + ASSERT(m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_AT_BOTTOM; + + if ( FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE) ) + { + //EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + } + + + if (!FStringNull(pev->target)) + { + edict_t* pentTarget = NULL; + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->target)); + + if (FNullEnt(pentTarget)) + break; + + if (!FClassnameIs(pentTarget, "multisource")) + continue; + CBaseEntity *pTarget = CBaseEntity::Instance( pentTarget ); + + if ( pTarget ) + pTarget->Use( m_hActivator, this, USE_TOGGLE, 0 ); + } + } + +// Re-instate touch method, movement cycle is complete. + if ( !FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // this button only works if USED, not touched! + { + // All buttons are now use only + SetTouch ( NULL ); + } + else + SetTouch( &CBaseButton::ButtonTouch ); + +// reset think for a sparking button + if ( FBitSet ( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) ) + { + SetThink ( &CBaseButton::ButtonSpark ); + pev->nextthink = gpGlobals->time + 0.5;// no hurry. + } +} + + + +// +// Rotating button (aka "lever") +// +class CRotButton : public CBaseButton +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( func_rot_button, CRotButton ); + +void CRotButton::Spawn( void ) +{ + char *pszSound; + //---------------------------------------------------- + //determine sounds for buttons + //a sound of 0 should not make a sound + //---------------------------------------------------- + pszSound = ButtonSound( m_sounds ); + PRECACHE_SOUND(pszSound); + pev->noise = ALLOC_STRING(pszSound); + + // set the axis of rotation + CBaseToggle::AxisDir( pev ); + + // check for clockwise rotation + if ( FBitSet (pev->spawnflags, SF_DOOR_ROTATE_BACKWARDS) ) + pev->movedir = pev->movedir * -1; + + pev->movetype = MOVETYPE_PUSH; + + if ( pev->spawnflags & SF_ROTBUTTON_NOTSOLID ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + + SET_MODEL(ENT(pev), STRING(pev->model)); + + if (pev->speed == 0) + pev->speed = 40; + + if (m_flWait == 0) + m_flWait = 1; + + if (pev->health > 0) + { + pev->takedamage = DAMAGE_YES; + } + + m_toggle_state = TS_AT_BOTTOM; + m_vecAngle1 = pev->angles; + m_vecAngle2 = pev->angles + pev->movedir * m_flMoveDistance; + ASSERTSZ(m_vecAngle1 != m_vecAngle2, "rotating button start/end positions are equal"); + + m_fStayPushed = (m_flWait == -1 ? TRUE : FALSE); + m_fRotating = TRUE; + + // if the button is flagged for USE button activation only, take away it's touch function and add a use function + if ( !FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) + { + SetTouch ( NULL ); + SetUse ( &CRotButton::ButtonUse ); + } + else // touchable button + SetTouch( &CRotButton::ButtonTouch ); + + //SetTouch( ButtonTouch ); +} + + +// Make this button behave like a door (HACKHACK) +// This will disable use and make the button solid +// rotating buttons were made SOLID_NOT by default since their were some +// collision problems with them... +#define SF_MOMENTARY_DOOR 0x0001 + +class CMomentaryRotButton : public CBaseToggle +{ +public: + void Spawn ( void ); + void KeyValue( KeyValueData *pkvd ); + virtual int ObjectCaps( void ) + { + int flags = CBaseToggle :: ObjectCaps() & (~FCAP_ACROSS_TRANSITION); + if ( pev->spawnflags & SF_MOMENTARY_DOOR ) + return flags; + return flags | FCAP_CONTINUOUS_USE; + } + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Off( void ); + void EXPORT Return( void ); + void UpdateSelf( float value ); + void UpdateSelfReturn( float value ); + void UpdateAllButtons( float value, int start ); + + void PlaySound( void ); + void UpdateTarget( float value ); + + static CMomentaryRotButton *Instance( edict_t *pent ) { return (CMomentaryRotButton *)GET_PRIVATE(pent);}; + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_lastUsed; + int m_direction; + float m_returnSpeed; + vec3_t m_start; + vec3_t m_end; + int m_sounds; +}; +TYPEDESCRIPTION CMomentaryRotButton::m_SaveData[] = +{ + DEFINE_FIELD( CMomentaryRotButton, m_lastUsed, FIELD_INTEGER ), + DEFINE_FIELD( CMomentaryRotButton, m_direction, FIELD_INTEGER ), + DEFINE_FIELD( CMomentaryRotButton, m_returnSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CMomentaryRotButton, m_start, FIELD_VECTOR ), + DEFINE_FIELD( CMomentaryRotButton, m_end, FIELD_VECTOR ), + DEFINE_FIELD( CMomentaryRotButton, m_sounds, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CMomentaryRotButton, CBaseToggle ); + +LINK_ENTITY_TO_CLASS( momentary_rot_button, CMomentaryRotButton ); + +void CMomentaryRotButton::Spawn( void ) +{ + CBaseToggle::AxisDir( pev ); + + if ( pev->speed == 0 ) + pev->speed = 100; + + if ( m_flMoveDistance < 0 ) + { + m_start = pev->angles + pev->movedir * m_flMoveDistance; + m_end = pev->angles; + m_direction = 1; // This will toggle to -1 on the first use() + m_flMoveDistance = -m_flMoveDistance; + } + else + { + m_start = pev->angles; + m_end = pev->angles + pev->movedir * m_flMoveDistance; + m_direction = -1; // This will toggle to +1 on the first use() + } + + if ( pev->spawnflags & SF_MOMENTARY_DOOR ) + pev->solid = SOLID_BSP; + else + pev->solid = SOLID_NOT; + + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + char *pszSound = ButtonSound( m_sounds ); + PRECACHE_SOUND(pszSound); + pev->noise = ALLOC_STRING(pszSound); + m_lastUsed = 0; +} + +void CMomentaryRotButton::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "returnspeed")) + { + m_returnSpeed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CMomentaryRotButton::PlaySound( void ) +{ + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); +} + +// BUGBUG: This design causes a latentcy. When the button is retriggered, the first impulse +// will send the target in the wrong direction because the parameter is calculated based on the +// current, not future position. +void CMomentaryRotButton::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + pev->ideal_yaw = CBaseToggle::AxisDelta( pev->spawnflags, pev->angles, m_start ) / m_flMoveDistance; + + UpdateAllButtons( pev->ideal_yaw, 1 ); + UpdateTarget( pev->ideal_yaw ); +} + +void CMomentaryRotButton::UpdateAllButtons( float value, int start ) +{ + // Update all rot buttons attached to the same target + edict_t *pentTarget = NULL; + for (;;) + { + + pentTarget = FIND_ENTITY_BY_STRING(pentTarget, "target", STRING(pev->target)); + if (FNullEnt(pentTarget)) + break; + + if ( FClassnameIs( VARS(pentTarget), "momentary_rot_button" ) ) + { + CMomentaryRotButton *pEntity = CMomentaryRotButton::Instance(pentTarget); + if ( pEntity ) + { + if ( start ) + pEntity->UpdateSelf( value ); + else + pEntity->UpdateSelfReturn( value ); + } + } + } +} + +void CMomentaryRotButton::UpdateSelf( float value ) +{ + BOOL fplaysound = FALSE; + + if ( !m_lastUsed ) + { + fplaysound = TRUE; + m_direction = -m_direction; + } + m_lastUsed = 1; + + pev->nextthink = pev->ltime + 0.1; + if ( m_direction > 0 && value >= 1.0 ) + { + pev->avelocity = g_vecZero; + pev->angles = m_end; + return; + } + else if ( m_direction < 0 && value <= 0 ) + { + pev->avelocity = g_vecZero; + pev->angles = m_start; + return; + } + + if (fplaysound) + PlaySound(); + + // HACKHACK -- If we're going slow, we'll get multiple player packets per frame, bump nexthink on each one to avoid stalling + if ( pev->nextthink < pev->ltime ) + pev->nextthink = pev->ltime + 0.1; + else + pev->nextthink += 0.1; + + pev->avelocity = (m_direction * pev->speed) * pev->movedir; + SetThink( &CMomentaryRotButton::Off ); +} + +void CMomentaryRotButton::UpdateTarget( float value ) +{ + if (!FStringNull(pev->target)) + { + edict_t* pentTarget = NULL; + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->target)); + if (FNullEnt(pentTarget)) + break; + CBaseEntity *pEntity = CBaseEntity::Instance(pentTarget); + if ( pEntity ) + { + pEntity->Use( this, this, USE_SET, value ); + } + } + } +} + +void CMomentaryRotButton::Off( void ) +{ + pev->avelocity = g_vecZero; + m_lastUsed = 0; + if ( FBitSet( pev->spawnflags, SF_PENDULUM_AUTO_RETURN ) && m_returnSpeed > 0 ) + { + SetThink( &CMomentaryRotButton::Return ); + pev->nextthink = pev->ltime + 0.1; + m_direction = -1; + } + else + SetThink( NULL ); +} + +void CMomentaryRotButton::Return( void ) +{ + float value = CBaseToggle::AxisDelta( pev->spawnflags, pev->angles, m_start ) / m_flMoveDistance; + + UpdateAllButtons( value, 0 ); // This will end up calling UpdateSelfReturn() n times, but it still works right + if ( value > 0 ) + UpdateTarget( value ); +} + + +void CMomentaryRotButton::UpdateSelfReturn( float value ) +{ + if ( value <= 0 ) + { + pev->avelocity = g_vecZero; + pev->angles = m_start; + pev->nextthink = -1; + SetThink( NULL ); + } + else + { + pev->avelocity = -m_returnSpeed * pev->movedir; + pev->nextthink = pev->ltime + 0.1; + } +} + + +//---------------------------------------------------------------- +// Spark +//---------------------------------------------------------------- + +class CEnvSpark : public CBaseEntity +{ +public: + void Spawn(void); + void Precache(void); + void EXPORT SparkThink(void); + void EXPORT SparkStart(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT SparkStop(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue(KeyValueData *pkvd); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flDelay; +}; + + +TYPEDESCRIPTION CEnvSpark::m_SaveData[] = +{ + DEFINE_FIELD( CEnvSpark, m_flDelay, FIELD_FLOAT), +}; + +IMPLEMENT_SAVERESTORE( CEnvSpark, CBaseEntity ); + +LINK_ENTITY_TO_CLASS(env_spark, CEnvSpark); +LINK_ENTITY_TO_CLASS(env_debris, CEnvSpark); + +void CEnvSpark::Spawn(void) +{ + SetThink( NULL ); + SetUse( NULL ); + + if (FBitSet(pev->spawnflags, 32)) // Use for on/off + { + if (FBitSet(pev->spawnflags, 64)) // Start on + { + SetThink(&CEnvSpark::SparkThink); // start sparking + SetUse(&CEnvSpark::SparkStop); // set up +USE to stop sparking + } + else + SetUse(&CEnvSpark::SparkStart); + } + else + SetThink(&CEnvSpark::SparkThink); + + pev->nextthink = gpGlobals->time + ( 0.1 + RANDOM_FLOAT ( 0, 1.5 ) ); + + if (m_flDelay <= 0) + m_flDelay = 1.5; + + Precache( ); +} + + +void CEnvSpark::Precache(void) +{ + PRECACHE_SOUND( "buttons/spark1.wav" ); + PRECACHE_SOUND( "buttons/spark2.wav" ); + PRECACHE_SOUND( "buttons/spark3.wav" ); + PRECACHE_SOUND( "buttons/spark4.wav" ); + PRECACHE_SOUND( "buttons/spark5.wav" ); + PRECACHE_SOUND( "buttons/spark6.wav" ); +} + +void CEnvSpark::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "MaxDelay")) + { + m_flDelay = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "killtarget") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + pkvd->fHandled = TRUE; + else + CBaseEntity::KeyValue( pkvd ); +} + +void EXPORT CEnvSpark::SparkThink(void) +{ + pev->nextthink = gpGlobals->time + 0.1 + RANDOM_FLOAT (0, m_flDelay); + DoSpark( pev, pev->origin ); +} + +void EXPORT CEnvSpark::SparkStart(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetUse(&CEnvSpark::SparkStop); + SetThink(&CEnvSpark::SparkThink); + pev->nextthink = gpGlobals->time + (0.1 + RANDOM_FLOAT ( 0, m_flDelay)); +} + +void EXPORT CEnvSpark::SparkStop(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetUse(&CEnvSpark::SparkStart); + SetThink(NULL); +} + +#define SF_BTARGET_USE 0x0001 +#define SF_BTARGET_ON 0x0002 + +class CButtonTarget : public CBaseEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + int ObjectCaps( void ); + +}; + +LINK_ENTITY_TO_CLASS( button_target, CButtonTarget ); + +void CButtonTarget::Spawn( void ) +{ + pev->movetype = MOVETYPE_PUSH; + pev->solid = SOLID_BSP; + SET_MODEL(ENT(pev), STRING(pev->model)); + pev->takedamage = DAMAGE_YES; + + if ( FBitSet( pev->spawnflags, SF_BTARGET_ON ) ) + pev->frame = 1; +} + +void CButtonTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, (int)pev->frame ) ) + return; + pev->frame = 1-pev->frame; + if ( pev->frame ) + SUB_UseTargets( pActivator, USE_ON, 0 ); + else + SUB_UseTargets( pActivator, USE_OFF, 0 ); +} + + +int CButtonTarget :: ObjectCaps( void ) +{ + int caps = CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; + + if ( FBitSet(pev->spawnflags, SF_BTARGET_USE) ) + return caps | FCAP_IMPULSE_USE; + else + return caps; +} + + +int CButtonTarget::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + Use( Instance(pevAttacker), this, USE_TOGGLE, 0 ); + + return 1; +} diff --git a/dmc/dlls/cbase.cpp b/dmc/dlls/cbase.cpp new file mode 100644 index 0000000..0d5c083 --- /dev/null +++ b/dmc/dlls/cbase.cpp @@ -0,0 +1,767 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "client.h" +#include "decals.h" +#include "gamerules.h" +#include "game.h" + +extern "C" void PM_Move ( struct playermove_s *ppmove, int server ); +extern "C" void PM_Init ( struct playermove_s *ppmove ); +extern "C" char PM_FindTextureType( char *name ); + +void EntvarsKeyvalue( entvars_t *pev, KeyValueData *pkvd ); + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); +extern DLL_GLOBAL Vector g_vecAttackDir; +extern DLL_GLOBAL int g_iSkillLevel; + +static DLL_FUNCTIONS gFunctionTable = +{ + GameDLLInit, //pfnGameInit + DispatchSpawn, //pfnSpawn + DispatchThink, //pfnThink + DispatchUse, //pfnUse + DispatchTouch, //pfnTouch + DispatchBlocked, //pfnBlocked + DispatchKeyValue, //pfnKeyValue + DispatchSave, //pfnSave + DispatchRestore, //pfnRestore + DispatchObjectCollsionBox, //pfnAbsBox + + SaveWriteFields, //pfnSaveWriteFields + SaveReadFields, //pfnSaveReadFields + + SaveGlobalState, //pfnSaveGlobalState + RestoreGlobalState, //pfnRestoreGlobalState + ResetGlobalState, //pfnResetGlobalState + + ClientConnect, //pfnClientConnect + ClientDisconnect, //pfnClientDisconnect + ClientKill, //pfnClientKill + ClientPutInServer, //pfnClientPutInServer + ClientCommand, //pfnClientCommand + ClientUserInfoChanged, //pfnClientUserInfoChanged + ServerActivate, //pfnServerActivate + ServerDeactivate, //pfnServerDeactivate + + PlayerPreThink, //pfnPlayerPreThink + PlayerPostThink, //pfnPlayerPostThink + + StartFrame, //pfnStartFrame + ParmsNewLevel, //pfnParmsNewLevel + ParmsChangeLevel, //pfnParmsChangeLevel + + GetGameDescription, //pfnGetGameDescription Returns string describing current .dll game. + PlayerCustomization, //pfnPlayerCustomization Notifies .dll of new customization for player. + + SpectatorConnect, //pfnSpectatorConnect Called when spectator joins server + SpectatorDisconnect, //pfnSpectatorDisconnect Called when spectator leaves the server + SpectatorThink, //pfnSpectatorThink Called when spectator sends a command packet (usercmd_t) + + Sys_Error, //pfnSys_Error Called when engine has encountered an error + + PM_Move, //pfnPM_Move + PM_Init, //pfnPM_Init Server version of player movement initialization + PM_FindTextureType, //pfnPM_FindTextureType + + SetupVisibility, //pfnSetupVisibility Set up PVS and PAS for networking for this client + UpdateClientData, //pfnUpdateClientData Set up data sent only to specific client + AddToFullPack, //pfnAddToFullPack + CreateBaseline, //pfnCreateBaseline Tweak entity baseline for network encoding, allows setup of player baselines, too. + RegisterEncoders, //pfnRegisterEncoders Callbacks for network encoding + GetWeaponData, //pfnGetWeaponData + CmdStart, //pfnCmdStart + CmdEnd, //pfnCmdEnd + ConnectionlessPacket, //pfnConnectionlessPacket + GetHullBounds, //pfnGetHullBounds + CreateInstancedBaselines, //pfnCreateInstancedBaselines + InconsistentFile, //pfnInconsistentFile + AllowLagCompensation, //pfnAllowLagCompensation +}; + +static void SetObjectCollisionBox( entvars_t *pev ); + +int GetEntityAPI( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion ) +{ + if ( !pFunctionTable || interfaceVersion != INTERFACE_VERSION ) + { + return FALSE; + } + + memcpy( pFunctionTable, &gFunctionTable, sizeof( DLL_FUNCTIONS ) ); + return TRUE; +} + +int GetEntityAPI2( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ) +{ + if ( !pFunctionTable || *interfaceVersion != INTERFACE_VERSION ) + { + // Tell engine what version we had, so it can figure out who is out of date. + *interfaceVersion = INTERFACE_VERSION; + return FALSE; + } + + memcpy( pFunctionTable, &gFunctionTable, sizeof( DLL_FUNCTIONS ) ); + return TRUE; +} + + +int DispatchSpawn( edict_t *pent ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if (pEntity) + { + // Initialize these or entities who don't link to the world won't have anything in here + pEntity->pev->absmin = pEntity->pev->origin - Vector(1,1,1); + pEntity->pev->absmax = pEntity->pev->origin + Vector(1,1,1); + + pEntity->Spawn(); + + // Try to get the pointer again, in case the spawn function deleted the entity. + // UNDONE: Spawn() should really return a code to ask that the entity be deleted, but + // that would touch too much code for me to do that right now. + pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if ( pEntity ) + { + if ( g_pGameRules && !g_pGameRules->IsAllowedToSpawn( pEntity ) ) + return -1; // return that this entity should be deleted + if ( pEntity->pev->flags & FL_KILLME ) + return -1; + } + + + // Handle global stuff here + if ( pEntity && pEntity->pev->globalname ) + { + const globalentity_t *pGlobal = gGlobalState.EntityFromTable( pEntity->pev->globalname ); + if ( pGlobal ) + { + // Already dead? delete + if ( pGlobal->state == GLOBAL_DEAD ) + return -1; + else if ( !FStrEq( STRING(gpGlobals->mapname), pGlobal->levelName ) ) + pEntity->MakeDormant(); // Hasn't been moved to this level yet, wait but stay alive + // In this level & not dead, continue on as normal + } + else + { + // Spawned entities default to 'On' + gGlobalState.EntityAdd( pEntity->pev->globalname, gpGlobals->mapname, GLOBAL_ON ); +// ALERT( at_console, "Added global entity %s (%s)\n", STRING(pEntity->pev->classname), STRING(pEntity->pev->globalname) ); + } + } + + } + + return 0; +} + +void DispatchKeyValue( edict_t *pentKeyvalue, KeyValueData *pkvd ) +{ + if ( !pkvd || !pentKeyvalue ) + return; + + EntvarsKeyvalue( VARS(pentKeyvalue), pkvd ); + + // If the key was an entity variable, or there's no class set yet, don't look for the object, it may + // not exist yet. + if ( pkvd->fHandled || pkvd->szClassName == NULL ) + return; + + // Get the actualy entity object + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pentKeyvalue); + + if ( !pEntity ) + return; + + pEntity->KeyValue( pkvd ); +} + + +// HACKHACK -- this is a hack to keep the node graph entity from "touching" things (like triggers) +// while it builds the graph +BOOL gTouchDisabled = FALSE; +void DispatchTouch( edict_t *pentTouched, edict_t *pentOther ) +{ + if ( gTouchDisabled ) + return; + + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pentTouched); + CBaseEntity *pOther = (CBaseEntity *)GET_PRIVATE( pentOther ); + + if ( pEntity && pOther && ! ((pEntity->pev->flags | pOther->pev->flags) & FL_KILLME) ) + pEntity->Touch( pOther ); +} + + +void DispatchUse( edict_t *pentUsed, edict_t *pentOther ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pentUsed); + CBaseEntity *pOther = (CBaseEntity *)GET_PRIVATE(pentOther); + + if (pEntity && !(pEntity->pev->flags & FL_KILLME) ) + pEntity->Use( pOther, pOther, USE_TOGGLE, 0 ); +} + +void DispatchThink( edict_t *pent ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + if (pEntity) + { + if ( FBitSet( pEntity->pev->flags, FL_DORMANT ) ) + ALERT( at_error, "Dormant entity %s is thinking!!\n", STRING(pEntity->pev->classname) ); + + pEntity->Think(); + } +} + +void DispatchBlocked( edict_t *pentBlocked, edict_t *pentOther ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE( pentBlocked ); + CBaseEntity *pOther = (CBaseEntity *)GET_PRIVATE( pentOther ); + + if (pEntity) + pEntity->Blocked( pOther ); +} + +void DispatchSave( edict_t *pent, SAVERESTOREDATA *pSaveData ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if ( pEntity && pSaveData ) + { + ENTITYTABLE *pTable = &pSaveData->pTable[ pSaveData->currentIndex ]; + + if ( pTable->pent != pent ) + ALERT( at_error, "ENTITY TABLE OR INDEX IS WRONG!!!!\n" ); + + if ( pEntity->ObjectCaps() & FCAP_DONT_SAVE ) + return; + + // These don't use ltime & nextthink as times really, but we'll fudge around it. + if ( pEntity->pev->movetype == MOVETYPE_PUSH ) + { + float delta = pEntity->pev->nextthink - pEntity->pev->ltime; + pEntity->pev->ltime = gpGlobals->time; + pEntity->pev->nextthink = pEntity->pev->ltime + delta; + } + + pTable->location = pSaveData->size; // Remember entity position for file I/O + pTable->classname = pEntity->pev->classname; // Remember entity class for respawn + + CSave saveHelper( pSaveData ); + pEntity->Save( saveHelper ); + + pTable->size = pSaveData->size - pTable->location; // Size of entity block is data size written to block + } +} + + +// Find the matching global entity. Spit out an error if the designer made entities of +// different classes with the same global name +CBaseEntity *FindGlobalEntity( string_t classname, string_t globalname ) +{ + edict_t *pent = FIND_ENTITY_BY_STRING( NULL, "globalname", STRING(globalname) ); + CBaseEntity *pReturn = CBaseEntity::Instance( pent ); + if ( pReturn ) + { + if ( !FClassnameIs( pReturn->pev, STRING(classname) ) ) + { + ALERT( at_console, "Global entity found %s, wrong class %s\n", STRING(globalname), STRING(pReturn->pev->classname) ); + pReturn = NULL; + } + } + + return pReturn; +} + + +int DispatchRestore( edict_t *pent, SAVERESTOREDATA *pSaveData, int globalEntity ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if ( pEntity && pSaveData ) + { + entvars_t tmpVars; + Vector oldOffset; + + CRestore restoreHelper( pSaveData ); + if ( globalEntity ) + { + CRestore tmpRestore( pSaveData ); + tmpRestore.PrecacheMode( 0 ); + tmpRestore.ReadEntVars( "ENTVARS", &tmpVars ); + + // HACKHACK - reset the save pointers, we're going to restore for real this time + pSaveData->size = pSaveData->pTable[pSaveData->currentIndex].location; + pSaveData->pCurrentData = pSaveData->pBaseData + pSaveData->size; + // ------------------- + + + const globalentity_t *pGlobal = gGlobalState.EntityFromTable( tmpVars.globalname ); + + // Don't overlay any instance of the global that isn't the latest + // pSaveData->szCurrentMapName is the level this entity is coming from + // pGlobla->levelName is the last level the global entity was active in. + // If they aren't the same, then this global update is out of date. + if ( !FStrEq( pSaveData->szCurrentMapName, pGlobal->levelName ) ) + return 0; + + // Compute the new global offset + oldOffset = pSaveData->vecLandmarkOffset; + CBaseEntity *pNewEntity = FindGlobalEntity( tmpVars.classname, tmpVars.globalname ); + if ( pNewEntity ) + { +// ALERT( at_console, "Overlay %s with %s\n", STRING(pNewEntity->pev->classname), STRING(tmpVars.classname) ); + // Tell the restore code we're overlaying a global entity from another level + restoreHelper.SetGlobalMode( 1 ); // Don't overwrite global fields + pSaveData->vecLandmarkOffset = (pSaveData->vecLandmarkOffset - pNewEntity->pev->mins) + tmpVars.mins; + pEntity = pNewEntity;// we're going to restore this data OVER the old entity + pent = ENT( pEntity->pev ); + // Update the global table to say that the global definition of this entity should come from this level + gGlobalState.EntityUpdate( pEntity->pev->globalname, gpGlobals->mapname ); + } + else + { + // This entity will be freed automatically by the engine. If we don't do a restore on a matching entity (below) + // or call EntityUpdate() to move it to this level, we haven't changed global state at all. + return 0; + } + + } + + if ( pEntity->ObjectCaps() & FCAP_MUST_SPAWN ) + { + pEntity->Restore( restoreHelper ); + pEntity->Spawn(); + } + else + { + pEntity->Restore( restoreHelper ); + pEntity->Precache( ); + } + + // Again, could be deleted, get the pointer again. + pEntity = (CBaseEntity *)GET_PRIVATE(pent); + +#if 0 + if ( pEntity && pEntity->pev->globalname && globalEntity ) + { + ALERT( at_console, "Global %s is %s\n", STRING(pEntity->pev->globalname), STRING(pEntity->pev->model) ); + } +#endif + + // Is this an overriding global entity (coming over the transition), or one restoring in a level + if ( globalEntity ) + { +// ALERT( at_console, "After: %f %f %f %s\n", pEntity->pev->origin.x, pEntity->pev->origin.y, pEntity->pev->origin.z, STRING(pEntity->pev->model) ); + pSaveData->vecLandmarkOffset = oldOffset; + if ( pEntity ) + { + UTIL_SetOrigin( pEntity->pev, pEntity->pev->origin ); + pEntity->OverrideReset(); + } + } + else if ( pEntity && pEntity->pev->globalname ) + { + const globalentity_t *pGlobal = gGlobalState.EntityFromTable( pEntity->pev->globalname ); + if ( pGlobal ) + { + // Already dead? delete + if ( pGlobal->state == GLOBAL_DEAD ) + return -1; + else if ( !FStrEq( STRING(gpGlobals->mapname), pGlobal->levelName ) ) + { + pEntity->MakeDormant(); // Hasn't been moved to this level yet, wait but stay alive + } + // In this level & not dead, continue on as normal + } + else + { + ALERT( at_error, "Global Entity %s (%s) not in table!!!\n", STRING(pEntity->pev->globalname), STRING(pEntity->pev->classname) ); + // Spawned entities default to 'On' + gGlobalState.EntityAdd( pEntity->pev->globalname, gpGlobals->mapname, GLOBAL_ON ); + } + } + } + return 0; +} + + +void DispatchObjectCollsionBox( edict_t *pent ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + if (pEntity) + { + pEntity->SetObjectCollisionBox(); + } + else + SetObjectCollisionBox( &pent->v ); +} + + +void SaveWriteFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + CSave saveHelper( pSaveData ); + saveHelper.WriteFields( pname, pBaseData, pFields, fieldCount ); +} + + +void SaveReadFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + CRestore restoreHelper( pSaveData ); + restoreHelper.ReadFields( pname, pBaseData, pFields, fieldCount ); +} + + +edict_t * EHANDLE::Get( void ) +{ + if (m_pent) + { + if (m_pent->serialnumber == m_serialnumber) + return m_pent; + else + return NULL; + } + return NULL; +}; + +edict_t * EHANDLE::Set( edict_t *pent ) +{ + m_pent = pent; + if (pent) + m_serialnumber = m_pent->serialnumber; + return pent; +}; + + +EHANDLE :: operator CBaseEntity *() +{ + return (CBaseEntity *)GET_PRIVATE( Get( ) ); +}; + + +CBaseEntity * EHANDLE :: operator = (CBaseEntity *pEntity) +{ + if (pEntity) + { + m_pent = ENT( pEntity->pev ); + if (m_pent) + m_serialnumber = m_pent->serialnumber; + } + else + { + m_pent = NULL; + m_serialnumber = 0; + } + return pEntity; +} + +EHANDLE :: operator int () +{ + return Get() != NULL; +} + +CBaseEntity * EHANDLE :: operator -> () +{ + return (CBaseEntity *)GET_PRIVATE( Get( ) ); +} + + +// give health +int CBaseEntity :: TakeHealth( float flHealth, int bitsDamageType ) +{ + if (!pev->takedamage) + return 0; + +// heal + if ( pev->health >= pev->max_health ) + return 0; + + pev->health += flHealth; + + if (pev->health > pev->max_health) + pev->health = pev->max_health; + + return 1; +} + +// inflict damage on this entity. bitsDamageType indicates type of damage inflicted, ie: DMG_CRUSH + +int CBaseEntity :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecTemp; + + if (!pev->takedamage) + return 0; + + // UNDONE: some entity types may be immune or resistant to some bitsDamageType + + // if Attacker == Inflictor, the attack was a melee or other instant-hit attack. + // (that is, no actual entity projectile was involved in the attack so use the shooter's origin). + if ( pevAttacker == pevInflictor ) + { + vecTemp = pevAttacker->origin - ( VecBModelOrigin(pev) ); + } + else + // an actual missile was involved. + { + vecTemp = pevInflictor->origin - ( VecBModelOrigin(pev) ); + } + +// this global is still used for glass and other non-monster killables, along with decals. + g_vecAttackDir = vecTemp.Normalize(); + +// save damage based on the target's armor level + +// figure momentum add (don't let hurt brushes or other triggers move player) + if ((!FNullEnt(pevInflictor)) && (pev->movetype == MOVETYPE_WALK || pev->movetype == MOVETYPE_STEP) && (pevAttacker->solid != SOLID_TRIGGER) ) + { + Vector vecDir = pev->origin - (pevInflictor->absmin + pevInflictor->absmax) * 0.5; + vecDir = vecDir.Normalize(); + + float flForce = flDamage * ((32 * 32 * 72.0) / (pev->size.x * pev->size.y * pev->size.z)) * 5; + + if (flForce > 1000.0) + flForce = 1000.0; + pev->velocity = pev->velocity + vecDir * flForce; + } + +// do the damage + pev->health -= flDamage; + if (pev->health <= 0) + { + Killed( pevAttacker, GIB_NORMAL ); + return 0; + } + + return 1; +} + + +void CBaseEntity :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->takedamage = DAMAGE_NO; + pev->deadflag = DEAD_DEAD; + UTIL_Remove( this ); +} + + +CBaseEntity *CBaseEntity::GetNextTarget( void ) +{ + if ( FStringNull( pev->target ) ) + return NULL; + edict_t *pTarget = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(pev->target) ); + if ( FNullEnt(pTarget) ) + return NULL; + + return Instance( pTarget ); +} + +// Global Savedata for Delay +TYPEDESCRIPTION CBaseEntity::m_SaveData[] = +{ + DEFINE_FIELD( CBaseEntity, m_pGoalEnt, FIELD_CLASSPTR ), + + DEFINE_FIELD( CBaseEntity, m_pfnThink, FIELD_FUNCTION ), // UNDONE: Build table of these!!! + DEFINE_FIELD( CBaseEntity, m_pfnTouch, FIELD_FUNCTION ), + DEFINE_FIELD( CBaseEntity, m_pfnUse, FIELD_FUNCTION ), + DEFINE_FIELD( CBaseEntity, m_pfnBlocked, FIELD_FUNCTION ), +}; + + +int CBaseEntity::Save( CSave &save ) +{ + if ( save.WriteEntVars( "ENTVARS", pev ) ) + return save.WriteFields( "BASE", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + + return 0; +} + +int CBaseEntity::Restore( CRestore &restore ) +{ + int status; + + status = restore.ReadEntVars( "ENTVARS", pev ); + if ( status ) + status = restore.ReadFields( "BASE", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + + if ( pev->modelindex != 0 && !FStringNull(pev->model) ) + { + Vector mins, maxs; + mins = pev->mins; // Set model is about to destroy these + maxs = pev->maxs; + + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL(ENT(pev), STRING(pev->model)); + UTIL_SetSize(pev, mins, maxs); // Reset them + } + + return status; +} + + +// Initialize absmin & absmax to the appropriate box +void SetObjectCollisionBox( entvars_t *pev ) +{ + if ( (pev->solid == SOLID_BSP) && + (pev->angles.x || pev->angles.y|| pev->angles.z) ) + { // expand for rotation + float max, v; + int i; + + max = 0; + for (i=0 ; i<3 ; i++) + { + v = fabs( pev->mins[i]); + if (v > max) + max = v; + v = fabs( pev->maxs[i]); + if (v > max) + max = v; + } + for (i=0 ; i<3 ; i++) + { + pev->absmin[i] = pev->origin[i] - max; + pev->absmax[i] = pev->origin[i] + max; + } + } + else + { + pev->absmin = pev->origin + pev->mins; + pev->absmax = pev->origin + pev->maxs; + } + + pev->absmin.x -= 1; + pev->absmin.y -= 1; + pev->absmin.z -= 1; + pev->absmax.x += 1; + pev->absmax.y += 1; + pev->absmax.z += 1; +} + + +void CBaseEntity::SetObjectCollisionBox( void ) +{ + ::SetObjectCollisionBox( pev ); +} + + +int CBaseEntity :: Intersects( CBaseEntity *pOther ) +{ + if ( pOther->pev->absmin.x > pev->absmax.x || + pOther->pev->absmin.y > pev->absmax.y || + pOther->pev->absmin.z > pev->absmax.z || + pOther->pev->absmax.x < pev->absmin.x || + pOther->pev->absmax.y < pev->absmin.y || + pOther->pev->absmax.z < pev->absmin.z ) + return 0; + return 1; +} + +void CBaseEntity :: MakeDormant( void ) +{ + SetBits( pev->flags, FL_DORMANT ); + + // Don't touch + pev->solid = SOLID_NOT; + // Don't move + pev->movetype = MOVETYPE_NONE; + // Don't draw + SetBits( pev->effects, EF_NODRAW ); + // Don't think + pev->nextthink = 0; + // Relink + UTIL_SetOrigin( pev, pev->origin ); +} + +int CBaseEntity :: IsDormant( void ) +{ + return FBitSet( pev->flags, FL_DORMANT ); +} + +BOOL CBaseEntity :: IsInWorld( void ) +{ + // position + if (pev->origin.x >= 4096) return FALSE; + if (pev->origin.y >= 4096) return FALSE; + if (pev->origin.z >= 4096) return FALSE; + if (pev->origin.x <= -4096) return FALSE; + if (pev->origin.y <= -4096) return FALSE; + if (pev->origin.z <= -4096) return FALSE; + // speed + if (pev->velocity.x >= 2000) return FALSE; + if (pev->velocity.y >= 2000) return FALSE; + if (pev->velocity.z >= 2000) return FALSE; + if (pev->velocity.x <= -2000) return FALSE; + if (pev->velocity.y <= -2000) return FALSE; + if (pev->velocity.z <= -2000) return FALSE; + + return TRUE; +} + +int CBaseEntity::ShouldToggle( USE_TYPE useType, BOOL currentState ) +{ + if ( useType != USE_TOGGLE && useType != USE_SET ) + { + if ( (currentState && useType == USE_ON) || (!currentState && useType == USE_OFF) ) + return 0; + } + return 1; +} + + +int CBaseEntity :: DamageDecal( int bitsDamageType ) +{ + if ( pev->rendermode == kRenderTransAlpha ) + return -1; + + if ( pev->rendermode != kRenderNormal ) + return DECAL_BPROOF1; + + return DECAL_GUNSHOT1 + RANDOM_LONG(0,4); +} + + + +// NOTE: szName must be a pointer to constant memory, e.g. "monster_class" because the entity +// will keep a pointer to it after this call. +CBaseEntity * CBaseEntity::Create( char *szName, const Vector &vecOrigin, const Vector &vecAngles, edict_t *pentOwner ) +{ + edict_t *pent; + CBaseEntity *pEntity; + + pent = CREATE_NAMED_ENTITY( MAKE_STRING( szName )); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in Create!\n" ); + return NULL; + } + pEntity = Instance( pent ); + pEntity->pev->owner = pentOwner; + pEntity->pev->origin = vecOrigin; + pEntity->pev->angles = vecAngles; + DispatchSpawn( pEntity->edict() ); + return pEntity; +} + + diff --git a/dmc/dlls/cbase.h b/dmc/dlls/cbase.h new file mode 100644 index 0000000..a26595b --- /dev/null +++ b/dmc/dlls/cbase.h @@ -0,0 +1,805 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +Class Hierachy + +CBaseEntity + CBaseDelay + CBaseToggle + CBaseItem + CBaseMonster + CBaseCycler + CBasePlayer + CBaseGroup +*/ + +#define MAX_PATH_SIZE 10 // max number of nodes available for a path. + +// These are caps bits to indicate what an object's capabilities (currently used for save/restore and level transitions) +#define FCAP_CUSTOMSAVE 0x00000001 +#define FCAP_ACROSS_TRANSITION 0x00000002 // should transfer between transitions +#define FCAP_MUST_SPAWN 0x00000004 // Spawn after restore +#define FCAP_DONT_SAVE 0x80000000 // Don't save this +#define FCAP_IMPULSE_USE 0x00000008 // can be used by the player +#define FCAP_CONTINUOUS_USE 0x00000010 // can be used by the player +#define FCAP_ONOFF_USE 0x00000020 // can be used by the player +#define FCAP_DIRECTIONAL_USE 0x00000040 // Player sends +/- 1 when using (currently only tracktrains) +#define FCAP_MASTER 0x00000080 // Can be used to "master" other entities (like multisource) + +// UNDONE: This will ignore transition volumes (trigger_transition), but not the PVS!!! +#define FCAP_FORCE_TRANSITION 0x00000080 // ALWAYS goes across transitions + +#include "archtypes.h" // DAL +#include "saverestore.h" +#include "schedule.h" + +#ifndef MONSTEREVENT_H +#include "monsterevent.h" +#endif + +// C functions for external declarations that call the appropriate C++ methods +#ifndef CBASE_DLLEXPORT +#ifdef _WIN32 +#define CBASE_DLLEXPORT _declspec( dllexport ) +#else +#define CBASE_DLLEXPORT __attribute__ ((visibility("default"))) +#endif +#endif + +#define EXPORT CBASE_DLLEXPORT + +extern "C" CBASE_DLLEXPORT int GetEntityAPI( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion ); +extern "C" CBASE_DLLEXPORT int GetEntityAPI2( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ); + +extern int DispatchSpawn( edict_t *pent ); +extern void DispatchKeyValue( edict_t *pentKeyvalue, KeyValueData *pkvd ); +extern void DispatchTouch( edict_t *pentTouched, edict_t *pentOther ); +extern void DispatchUse( edict_t *pentUsed, edict_t *pentOther ); +extern void DispatchThink( edict_t *pent ); +extern void DispatchBlocked( edict_t *pentBlocked, edict_t *pentOther ); +extern void DispatchSave( edict_t *pent, SAVERESTOREDATA *pSaveData ); +extern int DispatchRestore( edict_t *pent, SAVERESTOREDATA *pSaveData, int globalEntity ); +extern void DispatchObjectCollsionBox( edict_t *pent ); +extern void SaveWriteFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); +extern void SaveReadFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); +extern void SaveGlobalState( SAVERESTOREDATA *pSaveData ); +extern void RestoreGlobalState( SAVERESTOREDATA *pSaveData ); +extern void ResetGlobalState( void ); + +typedef enum { USE_OFF = 0, USE_ON = 1, USE_SET = 2, USE_TOGGLE = 3 } USE_TYPE; + +extern void FireTargets( const char *targetName, CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +typedef void (CBaseEntity::*BASEPTR)(void); +typedef void (CBaseEntity::*ENTITYFUNCPTR)(CBaseEntity *pOther ); +typedef void (CBaseEntity::*USEPTR)( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +// For CLASSIFY +#define CLASS_NONE 0 +#define CLASS_MACHINE 1 +#define CLASS_PLAYER 2 +#define CLASS_HUMAN_PASSIVE 3 +#define CLASS_HUMAN_MILITARY 4 +#define CLASS_ALIEN_MILITARY 5 +#define CLASS_ALIEN_PASSIVE 6 +#define CLASS_ALIEN_MONSTER 7 +#define CLASS_ALIEN_PREY 8 +#define CLASS_ALIEN_PREDATOR 9 +#define CLASS_INSECT 10 +#define CLASS_PLAYER_ALLY 11 +#define CLASS_PLAYER_BIOWEAPON 12 // hornets and snarks.launched by players +#define CLASS_ALIEN_BIOWEAPON 13 // hornets and snarks.launched by the alien menace +#define CLASS_BARNACLE 99 // special because no one pays attention to it, and it eats a wide cross-section of creatures. + +class CBaseEntity; +class CBaseMonster; +class CBasePlayerItem; +class CSquadMonster; + + +#define SF_NORESPAWN ( 1 << 30 )// !!!set this bit on guns and stuff that should never respawn. + +// +// EHANDLE. Safe way to point to CBaseEntities who may die between frames +// +class EHANDLE +{ +private: + edict_t *m_pent; + int m_serialnumber; +public: + edict_t *Get( void ); + edict_t *Set( edict_t *pent ); + + operator int (); + + operator CBaseEntity *(); + + CBaseEntity * operator = (CBaseEntity *pEntity); + CBaseEntity * operator ->(); +}; + + +// +// Base Entity. All entity types derive from this +// +class CBaseEntity +{ +public: + // Constructor. Set engine to use C/C++ callback functions + // pointers to engine data + entvars_t *pev; // Don't need to save/restore this pointer, the engine resets it + + // path corners + CBaseEntity *m_pGoalEnt;// path corner we are heading towards + CBaseEntity *m_pLink;// used for temporary link-list operations. + + // initialization functions + virtual void Spawn( void ) { return; } + virtual void Precache( void ) { return; } + virtual void KeyValue( KeyValueData* pkvd) { pkvd->fHandled = FALSE; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return FCAP_ACROSS_TRANSITION; } + virtual void Activate( void ) {} + + // Setup the object->object collision box (pev->mins / pev->maxs is the object->world collision box) + virtual void SetObjectCollisionBox( void ); + +// Classify - returns the type of group (i.e, "houndeye", or "human military" so that monsters with different classnames +// still realize that they are teammates. (overridden for monsters that form groups) + virtual int Classify ( void ) { return CLASS_NONE; }; + virtual void DeathNotice ( entvars_t *pevChild ) {}// monster maker children use this to tell the monster maker that they have died. + + + static TYPEDESCRIPTION m_SaveData[]; + + virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + virtual int TakeHealth( float flHealth, int bitsDamageType ); + virtual void Killed( entvars_t *pevAttacker, int iGib ); + virtual int BloodColor( void ) { return DONT_BLEED; } + virtual void TraceBleed( float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); + virtual BOOL IsTriggered( CBaseEntity *pActivator ) {return TRUE;} + virtual CBaseMonster *MyMonsterPointer( void ) { return NULL;} + virtual CSquadMonster *MySquadMonsterPointer( void ) { return NULL;} + virtual int GetToggleState( void ) { return TS_AT_TOP; } + virtual void AddPoints( int score, BOOL bAllowNegativeScore ) {} + virtual void AddPointsToTeam( int score, BOOL bAllowNegativeScore ) {} + virtual BOOL AddPlayerItem( CBasePlayerItem *pItem ) { return 0; } + virtual BOOL RemovePlayerItem( CBasePlayerItem *pItem ) { return 0; } + virtual int GiveAmmo( int iAmount, char *szName, int iMax ) { return -1; }; + virtual float GetDelay( void ) { return 0; } + virtual int IsMoving( void ) { return pev->velocity != g_vecZero; } + virtual void OverrideReset( void ) {} + virtual int DamageDecal( int bitsDamageType ); + // This is ONLY used by the node graph to test movement through a door + virtual void SetToggleState( int state ) {} + virtual void StartSneaking( void ) {} + virtual void StopSneaking( void ) {} + virtual BOOL OnControls( entvars_t *pev ) { return FALSE; } + virtual BOOL IsSneaking( void ) { return FALSE; } + virtual BOOL IsAlive( void ) { return (pev->deadflag == DEAD_NO) && pev->health > 0; } + virtual BOOL IsBSPModel( void ) { return pev->solid == SOLID_BSP || pev->movetype == MOVETYPE_PUSHSTEP; } + virtual BOOL ReflectGauss( void ) { return ( IsBSPModel() && !pev->takedamage ); } + virtual BOOL HasTarget( string_t targetname ) { return FStrEq(STRING(targetname), STRING(pev->targetname) ); } + virtual BOOL IsInWorld( void ); + virtual BOOL IsPlayer( void ) { return FALSE; } + virtual BOOL IsNetClient( void ) { return FALSE; } + virtual const char *TeamID( void ) { return ""; } + + +// virtual void SetActivator( CBaseEntity *pActivator ) {} + virtual CBaseEntity *GetNextTarget( void ); + + // fundamental callbacks + void (CBaseEntity ::*m_pfnThink)(void); + void (CBaseEntity ::*m_pfnTouch)( CBaseEntity *pOther ); + void (CBaseEntity ::*m_pfnUse)( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void (CBaseEntity ::*m_pfnBlocked)( CBaseEntity *pOther ); + + virtual void Think( void ) { if (m_pfnThink) (this->*m_pfnThink)(); }; + virtual void Touch( CBaseEntity *pOther ) { if (m_pfnTouch) (this->*m_pfnTouch)( pOther ); }; + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) + { + if (m_pfnUse) + (this->*m_pfnUse)( pActivator, pCaller, useType, value ); + } + virtual void Blocked( CBaseEntity *pOther ) { if (m_pfnBlocked) (this->*m_pfnBlocked)( pOther ); }; + + // allow engine to allocate instance data + void *operator new( size_t stAllocateBlock, entvars_t *pev ) + { + return (void *)ALLOC_PRIVATE(ENT(pev), stAllocateBlock); + }; + + // don't use this. +#if _MSC_VER >= 1200 // only build this code if MSVC++ 6.0 or higher + void operator delete(void *pMem, entvars_t *pev) + { + pev->flags |= FL_KILLME; + }; +#endif + + void UpdateOnRemove( void ); + + // common member functions + void EXPORT SUB_Remove( void ); + void EXPORT SUB_DoNothing( void ); + void EXPORT SUB_StartFadeOut ( void ); + void EXPORT SUB_FadeOut ( void ); + void EXPORT SUB_CallUseToggle( void ) { this->Use( this, this, USE_TOGGLE, 0 ); } + int ShouldToggle( USE_TYPE useType, BOOL currentState ); + void FireBullets( ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq = 4, int iDamage = 0, entvars_t *pevAttacker = NULL ); + + virtual CBaseEntity *Respawn( void ) { return NULL; } + + void SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ); + // Do the bounding boxes of these two intersect? + int Intersects( CBaseEntity *pOther ); + void MakeDormant( void ); + int IsDormant( void ); + BOOL IsLockedByMaster( void ) { return FALSE; } + + static CBaseEntity *Instance( edict_t *pent ) + { + if ( !pent ) + pent = ENT(0); + CBaseEntity *pEnt = (CBaseEntity *)GET_PRIVATE(pent); + return pEnt; + } + + static CBaseEntity *Instance( entvars_t *pev ) { return Instance( ENT( pev ) ); } + static CBaseEntity *Instance( int eoffset) { return Instance( ENT( eoffset) ); } + + CBaseMonster *GetMonsterPointer( entvars_t *pevMonster ) + { + CBaseEntity *pEntity = Instance( pevMonster ); + if ( pEntity ) + return pEntity->MyMonsterPointer(); + return NULL; + } + CBaseMonster *GetMonsterPointer( edict_t *pentMonster ) + { + CBaseEntity *pEntity = Instance( pentMonster ); + if ( pEntity ) + return pEntity->MyMonsterPointer(); + return NULL; + } + + + // Ugly code to lookup all functions to make sure they are exported when set. +#ifdef _DEBUG + void FunctionCheck( void *pFunction, char *name ) + { +#ifdef _WIN32 + if (pFunction && !NAME_FOR_FUNCTION((uint32)pFunction) ) + ALERT( at_error, "No EXPORT: %s:%s (%08lx)\n", STRING(pev->classname), name, pFunction ); +#endif // _WIN32 + } + + BASEPTR ThinkSet( BASEPTR func, char *name ) + { + m_pfnThink = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnThink)))), name ); + return func; + } + ENTITYFUNCPTR TouchSet( ENTITYFUNCPTR func, char *name ) + { + m_pfnTouch = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnTouch)))), name ); + return func; + } + USEPTR UseSet( USEPTR func, char *name ) + { + m_pfnUse = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnUse)))), name ); + return func; + } + ENTITYFUNCPTR BlockedSet( ENTITYFUNCPTR func, char *name ) + { + m_pfnBlocked = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnBlocked)))), name ); + return func; + } + +#endif + + + // virtual functions used by a few classes + + // used by monsters that are created by the MonsterMaker + virtual void UpdateOwner( void ) { return; }; + + + // + static CBaseEntity *Create( char *szName, const Vector &vecOrigin, const Vector &vecAngles, edict_t *pentOwner = NULL ); + + virtual BOOL FBecomeProne( void ) {return FALSE;}; + edict_t *edict() { return ENT( pev ); }; + EOFFSET eoffset( ) { return OFFSET( pev ); }; + int entindex( ) { return ENTINDEX( edict() ); }; + + virtual Vector Center( ) { return (pev->absmax + pev->absmin) * 0.5; }; // center point of entity + virtual Vector EyePosition( ) { return pev->origin + pev->view_ofs; }; // position of eyes + virtual Vector EarPosition( ) { return pev->origin + pev->view_ofs; }; // position of ears + virtual Vector BodyTarget( const Vector &posSrc ) { return Center( ); }; // position to shoot at + + virtual int Illumination( ) { return GETENTITYILLUM( ENT( pev ) ); }; + + virtual BOOL FVisible ( CBaseEntity *pEntity ); + virtual BOOL FVisible ( const Vector &vecOrigin ); + + + // QUAKECLASSIC + BOOL m_bAxHitMe; + void Spawn_Telefog( Vector vecOrg, CBaseEntity *pOther ); + Vector m_vecTeleAngles; +}; + + + +// Ugly technique to override base member functions +// Normally it's illegal to cast a pointer to a member function of a derived class to a pointer to a +// member function of a base class. static_cast is a sleezy way around that problem. + +#ifdef _DEBUG + +#define SetThink( a ) ThinkSet( static_cast (a), #a ) +#define SetTouch( a ) TouchSet( static_cast (a), #a ) +#define SetUse( a ) UseSet( static_cast (a), #a ) +#define SetBlocked( a ) BlockedSet( static_cast (a), #a ) + +#else + +#define SetThink( a ) m_pfnThink = static_cast (a) +#define SetTouch( a ) m_pfnTouch = static_cast (a) +#define SetUse( a ) m_pfnUse = static_cast (a) +#define SetBlocked( a ) m_pfnBlocked = static_cast (a) + +#endif + + +class CPointEntity : public CBaseEntity +{ +public: + void Spawn( void ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +private: +}; + + +typedef struct locksounds // sounds that doors and buttons make when locked/unlocked +{ + string_t sLockedSound; // sound a door makes when it's locked + string_t sLockedSentence; // sentence group played when door is locked + string_t sUnlockedSound; // sound a door makes when it's unlocked + string_t sUnlockedSentence; // sentence group played when door is unlocked + + int iLockedSentence; // which sentence in sentence group to play next + int iUnlockedSentence; // which sentence in sentence group to play next + + float flwaitSound; // time delay between playing consecutive 'locked/unlocked' sounds + float flwaitSentence; // time delay between playing consecutive sentences + BYTE bEOFLocked; // true if hit end of list of locked sentences + BYTE bEOFUnlocked; // true if hit end of list of unlocked sentences +} locksound_t; + +void PlayLockSounds(entvars_t *pev, locksound_t *pls, int flocked, int fbutton); + +// +// MultiSouce +// + +#define MAX_MULTI_TARGETS 16 // maximum number of targets a single multi_manager entity may be assigned. +#define MS_MAX_TARGETS 32 + +class CMultiSource : public CPointEntity +{ +public: + void Spawn( ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int ObjectCaps( void ) { return (CPointEntity::ObjectCaps() | FCAP_MASTER); } + BOOL IsTriggered( CBaseEntity *pActivator ); + void EXPORT Register( void ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + EHANDLE m_rgEntities[MS_MAX_TARGETS]; + int m_rgTriggered[MS_MAX_TARGETS]; + + int m_iTotal; + string_t m_globalstate; +}; + + +// +// generic Delay entity. +// +class CBaseDelay : public CBaseEntity +{ +public: + float m_flDelay; + int m_iszKillTarget; + + virtual void KeyValue( KeyValueData* pkvd); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + // common member functions + void SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ); + void EXPORT DelayThink( void ); +}; + + +class CBaseAnimating : public CBaseDelay +{ +public: + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + // Basic Monster Animation functions + float StudioFrameAdvance( float flInterval = 0.0 ); // accumulate animation frame time from last time called until now + int GetSequenceFlags( void ); + int LookupActivity ( int activity ); + int LookupActivityHeaviest ( int activity ); + int LookupSequence ( const char *label ); + void ResetSequenceInfo ( ); + void DispatchAnimEvents ( float flFutureInterval = 0.1 ); // Handle events that have happend since last time called up until X seconds into the future + virtual void HandleAnimEvent( MonsterEvent_t *pEvent ) { return; }; + float SetBoneController ( int iController, float flValue ); + void InitBoneControllers ( void ); + float SetBlending ( int iBlender, float flValue ); + void GetBonePosition ( int iBone, Vector &origin, Vector &angles ); + void GetAutomovement( Vector &origin, Vector &angles, float flInterval = 0.1 ); + int FindTransition( int iEndingSequence, int iGoalSequence, int *piDir ); + void GetAttachment ( int iAttachment, Vector &origin, Vector &angles ); + void SetBodygroup( int iGroup, int iValue ); + int GetBodygroup( int iGroup ); + int ExtractBbox( int sequence, float *mins, float *maxs ); + void SetSequenceBox( void ); + + // animation needs + float m_flFrameRate; // computed FPS for current sequence + float m_flGroundSpeed; // computed linear movement rate for current sequence + float m_flLastEventCheck; // last time the event list was checked + BOOL m_fSequenceFinished;// flag set when StudioAdvanceFrame moves across a frame boundry + BOOL m_fSequenceLoops; // true if the sequence loops +}; + + +// +// generic Toggle entity. +// +#define SF_ITEM_USE_ONLY 256 // ITEM_USE_ONLY = BUTTON_USE_ONLY = DOOR_USE_ONLY!!! + +class CBaseToggle : public CBaseAnimating +{ +public: + void KeyValue( KeyValueData *pkvd ); + + TOGGLE_STATE m_toggle_state; + float m_flActivateFinished;//like attack_finished, but for doors + float m_flMoveDistance;// how far a door should slide or rotate + float m_flWait; + float m_flLip; + float m_flTWidth;// for plats + float m_flTLength;// for plats + + Vector m_vecPosition1; + Vector m_vecPosition2; + Vector m_vecAngle1; + Vector m_vecAngle2; + + int m_cTriggersLeft; // trigger_counter only, # of activations remaining + float m_flHeight; + EHANDLE m_hActivator; + void (CBaseToggle::*m_pfnCallWhenMoveDone)(void); + Vector m_vecFinalDest; + Vector m_vecFinalAngle; + + int m_bitsDamageInflict; // DMG_ damage type that the door or tigger does + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + virtual int GetToggleState( void ) { return m_toggle_state; } + virtual float GetDelay( void ) { return m_flWait; } + + // common member functions + void LinearMove( Vector vecDest, float flSpeed ); + void EXPORT LinearMoveDone( void ); + void AngularMove( Vector vecDestAngle, float flSpeed ); + void EXPORT AngularMoveDone( void ); + BOOL IsLockedByMaster( void ); + + static float AxisValue( int flags, const Vector &angles ); + static void AxisDir( entvars_t *pev ); + static float AxisDelta( int flags, const Vector &angle1, const Vector &angle2 ); + + string_t m_sMaster; // If this button has a master switch, this is the targetname. + // A master switch must be of the multisource type. If all + // of the switches in the multisource have been triggered, then + // the button will be allowed to operate. Otherwise, it will be + // deactivated. +}; +#define SetMoveDone( a ) m_pfnCallWhenMoveDone = static_cast (a) + + +// people gib if their health is <= this at the time of death +#define GIB_HEALTH_VALUE -30 + +#define ROUTE_SIZE 8 // how many waypoints a monster can store at one time +#define MAX_OLD_ENEMIES 4 // how many old enemies to remember + +#define bits_CAP_DUCK ( 1 << 0 )// crouch +#define bits_CAP_JUMP ( 1 << 1 )// jump/leap +#define bits_CAP_STRAFE ( 1 << 2 )// strafe ( walk/run sideways) +#define bits_CAP_SQUAD ( 1 << 3 )// can form squads +#define bits_CAP_SWIM ( 1 << 4 )// proficiently navigate in water +#define bits_CAP_CLIMB ( 1 << 5 )// climb ladders/ropes +#define bits_CAP_USE ( 1 << 6 )// open doors/push buttons/pull levers +#define bits_CAP_HEAR ( 1 << 7 )// can hear forced sounds +#define bits_CAP_AUTO_DOORS ( 1 << 8 )// can trigger auto doors +#define bits_CAP_OPEN_DOORS ( 1 << 9 )// can open manual doors +#define bits_CAP_TURN_HEAD ( 1 << 10)// can turn head, always bone controller 0 + +#define bits_CAP_RANGE_ATTACK1 ( 1 << 11)// can do a range attack 1 +#define bits_CAP_RANGE_ATTACK2 ( 1 << 12)// can do a range attack 2 +#define bits_CAP_MELEE_ATTACK1 ( 1 << 13)// can do a melee attack 1 +#define bits_CAP_MELEE_ATTACK2 ( 1 << 14)// can do a melee attack 2 + +#define bits_CAP_FLY ( 1 << 15)// can fly, move all around + +#define bits_CAP_DOORS_GROUP (bits_CAP_USE | bits_CAP_AUTO_DOORS | bits_CAP_OPEN_DOORS) + +// used by suit voice to indicate damage sustained and repaired type to player + +// instant damage + +#define DMG_GENERIC 0 // generic damage was done +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FREEZE (1 << 4) // frozen +#define DMG_FALL (1 << 5) // fell too far +#define DMG_BLAST (1 << 6) // explosive blast damage +#define DMG_CLUB (1 << 7) // crowbar, punch, headbutt +#define DMG_SHOCK (1 << 8) // electric shock +#define DMG_SONIC (1 << 9) // sound pulse shockwave +#define DMG_ENERGYBEAM (1 << 10) // laser or other high energy beam +#define DMG_NEVERGIB (1 << 12) // with this bit OR'd in, no damage type will be able to gib victims upon death +#define DMG_ALWAYSGIB (1 << 13) // with this bit OR'd in, any damage type can be made to gib victims upon death. +#define DMG_DROWN (1 << 14) // Drowning +// time-based damage +#define DMG_TIMEBASED (~(0x3fff)) // mask for time-based damage + +#define DMG_PARALYZE (1 << 15) // slows affected creature down +#define DMG_NERVEGAS (1 << 16) // nerve toxins, very bad +#define DMG_POISON (1 << 17) // blood poisioning +#define DMG_RADIATION (1 << 18) // radiation exposure +#define DMG_DROWNRECOVER (1 << 19) // drowning recovery +#define DMG_ACID (1 << 20) // toxic chemicals or acid burns +#define DMG_SLOWBURN (1 << 21) // in an oven +#define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer +#define DMG_MORTAR (1 << 23) // Hit by air raid (done to distinguish grenade from mortar) + +#define DMG_IGNORE_MAXHEALTH (1<< 24) // Used by TakeHealth only. Ignores the player's max health when healing. + +// these are the damage types that are allowed to gib corpses +#define DMG_GIB_CORPSE ( DMG_CRUSH | DMG_FALL | DMG_BLAST | DMG_SONIC | DMG_CLUB ) + +// these are the damage types that have client hud art +#define DMG_SHOWNHUD (DMG_POISON | DMG_ACID | DMG_FREEZE | DMG_SLOWFREEZE | DMG_DROWN | DMG_BURN | DMG_SLOWBURN | DMG_NERVEGAS | DMG_RADIATION | DMG_SHOCK) + +// NOTE: tweak these values based on gameplay feedback: + +#define PARALYZE_DURATION 2 // number of 2 second intervals to take damage +#define PARALYZE_DAMAGE 1.0 // damage to take each 2 second interval + +#define NERVEGAS_DURATION 2 +#define NERVEGAS_DAMAGE 5.0 + +#define POISON_DURATION 5 +#define POISON_DAMAGE 2.0 + +#define RADIATION_DURATION 2 +#define RADIATION_DAMAGE 1.0 + +#define ACID_DURATION 2 +#define ACID_DAMAGE 5.0 + +#define SLOWBURN_DURATION 2 +#define SLOWBURN_DAMAGE 1.0 + +#define SLOWFREEZE_DURATION 2 +#define SLOWFREEZE_DAMAGE 1.0 + + +#define itbd_Paralyze 0 +#define itbd_NerveGas 1 +#define itbd_Poison 2 +#define itbd_Radiation 3 +#define itbd_DrownRecover 4 +#define itbd_Acid 5 +#define itbd_SlowBurn 6 +#define itbd_SlowFreeze 7 +#define CDMG_TIMEBASED 8 + +// when calling KILLED(), a value that governs gib behavior is expected to be +// one of these three values +#define GIB_NORMAL 0// gib if entity was overkilled +#define GIB_NEVER 1// never gib, no matter how much death damage is done ( freezing, etc ) +#define GIB_ALWAYS 2// always gib ( Houndeye Shock, Barnacle Bite ) + +class CBaseMonster; +class CCineMonster; +class CSound; + +#include "basemonster.h" + + +char *ButtonSound( int sound ); // get string of button sound number + + +// +// Generic Button +// +class CBaseButton : public CBaseToggle +{ +public: + void Spawn( void ); + virtual void Precache( void ); + void RotSpawn( void ); + virtual void KeyValue( KeyValueData* pkvd); + + void ButtonActivate( ); + void SparkSoundCache( void ); + + void EXPORT ButtonShot( void ); + void EXPORT ButtonTouch( CBaseEntity *pOther ); + void EXPORT ButtonSpark ( void ); + void EXPORT TriggerAndWait( void ); + void EXPORT ButtonReturn( void ); + void EXPORT ButtonBackHome( void ); + void EXPORT ButtonUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + enum BUTTON_CODE { BUTTON_NOTHING, BUTTON_ACTIVATE, BUTTON_RETURN }; + BUTTON_CODE ButtonResponseToTouch( void ); + + static TYPEDESCRIPTION m_SaveData[]; + // Buttons that don't take damage can be IMPULSE used + virtual int ObjectCaps( void ) { return (CBaseToggle:: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | (pev->takedamage?0:FCAP_IMPULSE_USE); } + + BOOL m_fStayPushed; // button stays pushed in until touched again? + BOOL m_fRotating; // a rotating button? default is a sliding button. + + string_t m_strChangeTarget; // if this field is not null, this is an index into the engine string array. + // when this button is touched, it's target entity's TARGET field will be set + // to the button's ChangeTarget. This allows you to make a func_train switch paths, etc. + + locksound_t m_ls; // door lock sounds + + BYTE m_bLockedSound; // ordinals from entity selection + BYTE m_bLockedSentence; + BYTE m_bUnlockedSound; + BYTE m_bUnlockedSentence; + int m_sounds; +}; + +// +// Weapons +// + +#define BAD_WEAPON 0x00007FFF + +// +// Converts a entvars_t * to a class pointer +// It will allocate the class and entity if necessary +// +template T * GetClassPtr( T *a ) +{ + entvars_t *pev = (entvars_t *)a; + + // allocate entity if necessary + if (pev == NULL) + pev = VARS(CREATE_ENTITY()); + + // get the private data + a = (T *)GET_PRIVATE(ENT(pev)); + + if (a == NULL) + { + // allocate private data + a = new(pev) T; + a->pev = pev; + } + return a; +} + + +/* +bit_PUSHBRUSH_DATA | bit_TOGGLE_DATA +bit_MONSTER_DATA +bit_DELAY_DATA +bit_TOGGLE_DATA | bit_DELAY_DATA | bit_MONSTER_DATA +bit_PLAYER_DATA | bit_MONSTER_DATA +bit_MONSTER_DATA | CYCLER_DATA +bit_LIGHT_DATA +path_corner_data +bit_MONSTER_DATA | wildcard_data +bit_MONSTER_DATA | bit_GROUP_DATA +boid_flock_data +boid_data +CYCLER_DATA +bit_ITEM_DATA +bit_ITEM_DATA | func_hud_data +bit_TOGGLE_DATA | bit_ITEM_DATA +EOFFSET +env_sound_data +env_sound_data +push_trigger_data +*/ + +#define TRACER_FREQ 4 // Tracers fire every 4 bullets + +typedef struct _SelAmmo +{ + BYTE Ammo1Type; + BYTE Ammo1; + BYTE Ammo2Type; + BYTE Ammo2; +} SelAmmo; + + +// this moved here from world.cpp, to allow classes to be derived from it +//======================= +// CWorld +// +// This spawns first when each level begins. +//======================= +class CWorld : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); +}; + +class CClientFog : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + + float m_iStartDist; + float m_iEndDist; +}; + +// QUAKECLASSIC +extern char *g_szDeathType; diff --git a/dmc/dlls/cdll_dll.h b/dmc/dlls/cdll_dll.h new file mode 100644 index 0000000..65279fc --- /dev/null +++ b/dmc/dlls/cdll_dll.h @@ -0,0 +1,46 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cdll_dll.h + +// this file is included by both the game-dll and the client-dll, + +#ifndef CDLL_DLL_H +#define CDLL_DLL_H + +#define MAX_WEAPONS 257 // ??? + +#define MAX_WEAPON_SLOTS 9 // hud item selection slots +#define MAX_ITEM_TYPES 6 // hud item selection slots + +#define MAX_ITEMS 5 // hard coded item types + +#define HIDEHUD_WEAPONS ( 1<<0 ) +#define HIDEHUD_FLASHLIGHT ( 1<<1 ) +#define HIDEHUD_ALL ( 1<<2 ) +#define HIDEHUD_HEALTH ( 1<<3 ) + +#define MAX_AMMO_TYPES 32 // ??? +#define MAX_AMMO_SLOTS 32 // not really slots + +#define HUD_PRINTNOTIFY 1 +#define HUD_PRINTCONSOLE 2 +#define HUD_PRINTTALK 3 +#define HUD_PRINTCENTER 4 + + +#define WEAPON_SUIT 31 + +#endif diff --git a/dmc/dlls/client.cpp b/dmc/dlls/client.cpp new file mode 100644 index 0000000..a248f37 --- /dev/null +++ b/dmc/dlls/client.cpp @@ -0,0 +1,1817 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Robin, 4-22-98: Moved set_suicide_frame() here from player.cpp to allow us to +// have one without a hardcoded player.mdl in tf_client.cpp + +/* + +===== client.cpp ======================================================== + + client/server game specific stuff + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "player.h" +#include "spectator.h" +#include "client.h" +#include "gamerules.h" +#include "customentity.h" +#include "weapons.h" +#include "weaponinfo.h" +#include "usercmd.h" +#include "netadr.h" + +#if !defined ( _WIN32 ) +#include // isspace,isprint +#endif + +extern DLL_GLOBAL ULONG g_ulModelIndexPlayer; +extern DLL_GLOBAL BOOL g_fGameOver; +extern DLL_GLOBAL int g_iSkillLevel; +extern DLL_GLOBAL ULONG g_ulFrameCount; + +#if defined( THREEWAVE ) +char* GetTeamName( int team ); +#endif + +extern bool g_bHaveMOTD; + +extern void CopyToBodyQue(entvars_t* pev); +extern int giPrecacheGrunt; +extern int gmsgSayText; + +extern unsigned short g_sGibbed; +extern unsigned short g_sTeleport; +extern unsigned short g_sTrail; +extern unsigned short g_sExplosion; +extern unsigned short g_usPowerUp; + +#ifdef THREEWAVE +extern unsigned short g_usHook; +extern unsigned short g_usCable; +extern unsigned short g_usCarried; +extern unsigned short g_usFlagSpawn; +#endif + +void LinkUserMessages( void ); + +/* + * used by kill command and disconnect command + * ROBIN: Moved here from player.cpp, to allow multiple player models + */ +void set_suicide_frame(entvars_t* pev) +{ + if (!FStrEq(STRING(pev->model), "models/player.mdl")) + return; // allready gibbed + +// pev->frame = $deatha11; + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_TOSS; + pev->deadflag = DEAD_DEAD; + pev->nextthink = -1; +} + + +/* +=========== +ClientConnect + +called when a player connects to a server +============ +*/ +BOOL ClientConnect( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) +{ + return g_pGameRules->ClientConnected( pEntity, pszName, pszAddress, szRejectReason ); + +// a client connecting during an intermission can cause problems +// if (intermission_running) +// ExitIntermission (); + +} + + +/* +=========== +ClientDisconnect + +called when a player disconnects from a server + +GLOBALS ASSUMED SET: g_fGameOver +============ +*/ +void ClientDisconnect( edict_t *pEntity ) +{ + if (g_fGameOver) + return; + + char text[256]; + sprintf( text, "- %s has left the game\n", STRING(pEntity->v.netname) ); + MESSAGE_BEGIN( MSG_ALL, gmsgSayText, NULL ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + + // since the edict doesn't get deleted, fix it so it doesn't interfere. + pEntity->v.takedamage = DAMAGE_NO;// don't attract autoaim + pEntity->v.solid = SOLID_NOT;// nonsolid + UTIL_SetOrigin ( &pEntity->v, pEntity->v.origin ); + + g_pGameRules->ClientDisconnected( pEntity ); +} + + +// called by ClientKill and DeadThink +void respawn(entvars_t* pev, BOOL fCopyCorpse) +{ + if (gpGlobals->coop || gpGlobals->deathmatch) + { + if ( fCopyCorpse ) + { + // make a copy of the dead body for appearances sake + CopyToBodyQue(pev); + } + + // respawn player + GetClassPtr( (CBasePlayer *)pev)->Spawn( ); + } + else + { // restart the entire server + SERVER_COMMAND("reload\n"); + } +} + +/* +============ +ClientKill + +Player entered the suicide command + +GLOBALS ASSUMED SET: g_ulModelIndexPlayer +============ +*/ +void ClientKill( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + + CBasePlayer *pl = (CBasePlayer*) CBasePlayer::Instance( pev ); + + if ( pl->m_fNextSuicideTime > gpGlobals->time ) + return; // prevent suiciding too ofter + + pl->m_fNextSuicideTime = gpGlobals->time + 1; // don't let them suicide for 5 seconds after suiciding + + // have the player kill themself + pev->health = 0; + pl->Killed( pev, GIB_ALWAYS ); + +// pev->modelindex = g_ulModelIndexPlayer; +// pev->frags -= 2; // extra penalty +// respawn( pev ); +} + +/* +=========== +ClientPutInServer + +called each time a player is spawned +============ +*/ +void ClientPutInServer( edict_t *pEntity ) +{ + CBasePlayer *pPlayer; + + entvars_t *pev = &pEntity->v; + + pPlayer = GetClassPtr((CBasePlayer *)pev); + pPlayer->SetCustomDecalFrames(-1); // Assume none; + + pPlayer->m_bHadFirstSpawn = false; + + // Allocate a CBasePlayer for pev, and call spawn + pPlayer->Spawn(); + + // Reset interpolation during first frame + pPlayer->pev->effects |= EF_NOINTERP; +} + + +#include "threewave_gamerules.h" + +//// HOST_SAY +// String comes in as +// say blah blah blah +// or as +// blah blah blah +// +void Host_Say( edict_t *pEntity, int teamonly ) +{ + CBasePlayer *client; + int j; + char *p; + char text[128]; + char szTemp[256]; + const char *cpSay = "say"; + const char *cpSayTeam = "say_team"; + const char *pcmd = CMD_ARGV(0); + + // We can get a raw string now, without the "say " prepended + if ( CMD_ARGC() == 0 ) + return; + + if ( !stricmp( pcmd, cpSay) || !stricmp( pcmd, cpSayTeam ) ) + { + if ( CMD_ARGC() >= 2 ) + { + p = (char *)CMD_ARGS(); + } + else + { + // say with a blank message, nothing to do + return; + } + } + else // Raw text, need to prepend argv[0] + { + if ( CMD_ARGC() >= 2 ) + { + sprintf( szTemp, "%s %s", ( char * )pcmd, (char *)CMD_ARGS() ); + } + else + { + // Just a one word command, use the first word...sigh + sprintf( szTemp, "%s", ( char * )pcmd ); + } + p = szTemp; + } + +// remove quotes if present + if (*p == '"') + { + p++; + p[strlen(p)-1] = 0; + } + +// make sure the text has content + char *pc; + for ( pc = p; pc != NULL && *pc != 0; pc++ ) + { + if ( isprint( *pc ) && !isspace( *pc ) ) + { + pc = NULL; // we've found an alphanumeric character, so text is valid + break; + } + } + if ( pc != NULL ) + return; // no character found, so say nothing + +// turn on color set 2 (color on, no sound) + if ( teamonly ) + sprintf( text, "%c(TEAM) %s: ", 2, STRING( pEntity->v.netname ) ); + else + sprintf( text, "%c%s: ", 2, STRING( pEntity->v.netname ) ); + + j = sizeof(text) - 2 - strlen(text); // -2 for /n and null terminator + if ( (int)strlen(p) > j ) + p[j] = 0; + + strcat( text, p ); + strcat( text, "\n" ); + + entvars_t *pev = &pEntity->v; + CBasePlayer* player = GetClassPtr((CBasePlayer *)pev); + + // loop through all players + // Start with the first player. + // This may return the world in single player if the client types something between levels or during spawn + // so check it, or it will infinite loop + + client = NULL; + while ( ((client = (CBasePlayer*)UTIL_FindEntityByClassname( client, "player" )) != NULL) && (!FNullEnt(client->edict())) ) + { + if ( !client->pev ) + continue; + + if ( client->edict() == pEntity ) + continue; + + if ( !(client->IsNetClient()) ) // Not a client ? (should never be true) + continue; + + // can the receiver hear the sender? or has he muted him? +#ifdef THREEWAVE + if ( ((CThreeWave *)g_pGameRules)->m_VoiceGameMgr.PlayerHasBlockedPlayer( client, player ) ) +#else + if ( ((CHalfLifeMultiplay *)g_pGameRules)->m_VoiceGameMgr.PlayerHasBlockedPlayer( client, player ) ) +#endif + continue; + + if ( teamonly && g_pGameRules->PlayerRelationship(client, CBaseEntity::Instance(pEntity)) != GR_TEAMMATE ) + continue; + + MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, client->pev ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + + } + + // print to the sending client + MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, &pEntity->v ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + + // echo to server console + g_engfuncs.pfnServerPrint( text ); + + char * temp; + if ( teamonly ) + temp = "say_team"; + else + temp = "say"; + +#if !defined( THREEWAVE ) + + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" %s \"%s\"\n", + STRING( pEntity->v.netname ), + GETPLAYERUSERID( pEntity ), + GETPLAYERAUTHID( pEntity ), + GETPLAYERUSERID( pEntity ), + temp, + p ); +#else + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" %s \"%s\"\n", + STRING( pEntity->v.netname ), + GETPLAYERUSERID( pEntity ), + GETPLAYERAUTHID( pEntity ), + GetTeamName( pEntity->v.team ), + temp, + p ); +#endif +} + + +/* +=========== +ClientCommand +called each time a player uses a "cmd" command +============ +*/ +extern float g_flWeaponCheat; + +// Use CMD_ARGV, CMD_ARGV, and CMD_ARGC to get pointers the character string command. +void ClientCommand( edict_t *pEntity ) +{ + const char *pcmd = CMD_ARGV(0); + + // Is the client spawned yet? + if ( !pEntity->pvPrivateData ) + return; + + entvars_t *pev = &pEntity->v; + + if ( FStrEq(pcmd, "say" ) ) + { + Host_Say( pEntity, 0 ); + } + else if ( FStrEq(pcmd, "say_team" ) ) + { + Host_Say( pEntity, 1 ); + } + else if ( FStrEq(pcmd, "fullupdate" ) ) + { + GetClassPtr((CBasePlayer *)pev)->ForceClientDllUpdate(); + } + else if ( FStrEq(pcmd, "give" ) ) + { + if ( g_flWeaponCheat != 0.0) + { + int iszItem = ALLOC_STRING( CMD_ARGV(1) ); // Make a copy of the classname + GetClassPtr((CBasePlayer *)pev)->GiveNamedItem( STRING(iszItem) ); + } + } + else if ( FStrEq(pcmd, "_firstspawn" ) ) + { + CBasePlayer * pPlayer = GetClassPtr((CBasePlayer *)pev); + + if ( pPlayer->m_bHadFirstSpawn == false && g_bHaveMOTD ) + { + pPlayer->m_bHadFirstSpawn = true; +#ifndef THREEWAVE + pPlayer->Spawn(); +#endif + + } + + + + } + + // QUAKECLASSIC + // Some commands removed: Drop Use Weapon + // Some added: qweapon +/* else if ( FStrEq(pcmd, "qweapon" ) ) + { + if ( CMD_ARGC() > 1 ) + { + GetClassPtr((CBasePlayer *)pev)->W_ChangeWeapon( atoi( CMD_ARGV(1) ) ); + } + }*/ + else if ( FStrEq(pcmd, "spectate" ) ) + { + CBasePlayer * pPlayer = GetClassPtr((CBasePlayer *)pev); + + if ( pPlayer->pev->flags & FL_PROXY ) + { + edict_t *pentSpawnSpot = g_pGameRules->GetPlayerSpawnSpot( pPlayer ); + pPlayer->StartObserver( pev->origin, VARS(pentSpawnSpot)->angles); + } + } + else if (FStrEq(pcmd, "lastinv" )) + { + GetClassPtr((CBasePlayer *)pev)->SelectLastItem(); + } + else if ( g_pGameRules->ClientCommand( GetClassPtr((CBasePlayer *)pev), pcmd ) ) + { + // MenuSelect returns true only if the command is properly handled, so don't print a warning + } + else if ( FStrEq( pcmd, "specmode" ) ) // new spectator mode + { + CBasePlayer * pPlayer = GetClassPtr((CBasePlayer *)pev); + if ( pPlayer->IsObserver() ) + pPlayer->Observer_SetMode( atoi( CMD_ARGV(1) ) ); + } + else if ( FStrEq( pcmd, "follownext" ) ) // follow next player + { + CBasePlayer * pPlayer = GetClassPtr((CBasePlayer *)pev); + if ( pPlayer->IsObserver() ) + pPlayer->Observer_FindNextPlayer(); + } + else + { + // tell the user they entered an unknown command + ClientPrint( &pEntity->v, HUD_PRINTCONSOLE, UTIL_VarArgs( "Unknown command: %s\n", pcmd ) ); + } +} + + +/* +======================== +ClientUserInfoChanged + +called after the player changes +userinfo - gives dll a chance to modify it before +it gets sent into the rest of the engine. +======================== +*/ +void ClientUserInfoChanged( edict_t *pEntity, char *infobuffer ) +{ + // Is the client spawned yet? + if ( !pEntity->pvPrivateData ) + return; + + // msg everyone if someone changes their name, and it isn't the first time (changing no name to current name) + if ( pEntity->v.netname && STRING(pEntity->v.netname)[0] != 0 && !FStrEq( STRING(pEntity->v.netname), g_engfuncs.pfnInfoKeyValue( infobuffer, "name" )) ) + { + char text[256]; + sprintf( text, "* %s changed name to %s\n", STRING(pEntity->v.netname), g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ) ); + MESSAGE_BEGIN( MSG_ALL, gmsgSayText, NULL ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + +#if !defined( THREEWAVE ) + + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" changed name to \"%s\"\n", + STRING( pEntity->v.netname ), + GETPLAYERUSERID( pEntity ), + GETPLAYERAUTHID( pEntity ), + GETPLAYERUSERID( pEntity ), + g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ) ); +#else + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" changed name to \"%s\"\n", + STRING( pEntity->v.netname ), + GETPLAYERUSERID( pEntity ), + GETPLAYERAUTHID( pEntity ), + GetTeamName( pEntity->v.team ), + g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ) ); +#endif + } + + // QUAKECLASSIC + // Weapon switching behaviour + ((CBasePlayer*)pEntity)->m_iWeaponSwitch = 8; + char *st = g_engfuncs.pfnInfoKeyValue( infobuffer, "w_switch" ); + if (st && st[0]) + { + ((CBasePlayer*)pEntity)->m_iWeaponSwitch = atoi(st); + } + ((CBasePlayer*)pEntity)->m_iBackpackSwitch = 8; + st = g_engfuncs.pfnInfoKeyValue( infobuffer, "b_switch" ); + if (st && st[0]) + { + ((CBasePlayer*)pEntity)->m_iBackpackSwitch = atoi(st); + } + + g_pGameRules->ClientUserInfoChanged( GetClassPtr((CBasePlayer *)&pEntity->v), infobuffer ); + +} + +static int g_serveractive = 0; + +void ServerDeactivate( void ) +{ + // It's possible that the engine will call this function more times than is necessary + // Therefore, only run it one time for each call to ServerActivate + if ( g_serveractive != 1 ) + { + return; + } + + g_serveractive = 0; + + // Peform any shutdown operations here... + // +} + +void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) +{ + int i; + CBaseEntity *pClass; + + // Every call to ServerActivate should be matched by a call to ServerDeactivate + g_serveractive = 1; + + // Clients have not been initialized yet + for ( i = 0; i < edictCount; i++ ) + { + if ( pEdictList[i].free ) + continue; + + // Clients aren't necessarily initialized until ClientPutInServer() + if ( i < clientMax || !pEdictList[i].pvPrivateData ) + continue; + + pClass = CBaseEntity::Instance( &pEdictList[i] ); + // Activate this entity if it's got a class & isn't dormant + if ( pClass && !(pClass->pev->flags & FL_DORMANT) ) + { + pClass->Activate(); + } + else + { + ALERT( at_console, "Can't instance %s\n", STRING(pEdictList[i].v.classname) ); + } + } + + // Link user messages here to make sure first client can get them... + LinkUserMessages(); +} + + +/* +================ +PlayerPreThink + +Called every frame before physics are run +================ +*/ +void PlayerPreThink( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + CBasePlayer *pPlayer = (CBasePlayer *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->PreThink( ); +} + +/* +================ +PlayerPostThink + +Called every frame after physics are run +================ +*/ +void PlayerPostThink( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + CBasePlayer *pPlayer = (CBasePlayer *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->PostThink( ); +} + + + +void ParmsNewLevel( void ) +{ +} + + +void ParmsChangeLevel( void ) +{ + // retrieve the pointer to the save data + SAVERESTOREDATA *pSaveData = (SAVERESTOREDATA *)gpGlobals->pSaveData; + + if ( pSaveData ) + pSaveData->connectionCount = BuildChangeList( pSaveData->levelList, MAX_LEVEL_CONNECTIONS ); +} + + +// +// GLOBALS ASSUMED SET: g_ulFrameCount +// +void StartFrame( void ) +{ + if ( g_pGameRules ) + g_pGameRules->Think(); + + if ( g_fGameOver ) + return; + + gpGlobals->teamplay = CVAR_GET_FLOAT("teamplay"); + g_iSkillLevel = CVAR_GET_FLOAT("skill"); + g_ulFrameCount++; +} + + +void ClientPrecache( void ) +{ + // setup precaches always needed + PRECACHE_SOUND("player/sprayer.wav"); // spray paint sound for PreAlpha + + // PRECACHE_SOUND("player/pl_jumpland2.wav"); // UNDONE: play 2x step sound + + PRECACHE_SOUND("player/pl_fallpain2.wav"); + PRECACHE_SOUND("player/pl_fallpain3.wav"); + + PRECACHE_SOUND("player/pl_step1.wav"); // walk on concrete + PRECACHE_SOUND("player/pl_step2.wav"); + PRECACHE_SOUND("player/pl_step3.wav"); + PRECACHE_SOUND("player/pl_step4.wav"); + + PRECACHE_SOUND("common/npc_step1.wav"); // NPC walk on concrete + PRECACHE_SOUND("common/npc_step2.wav"); + PRECACHE_SOUND("common/npc_step3.wav"); + PRECACHE_SOUND("common/npc_step4.wav"); + + PRECACHE_SOUND("player/pl_metal1.wav"); // walk on metal + PRECACHE_SOUND("player/pl_metal2.wav"); + PRECACHE_SOUND("player/pl_metal3.wav"); + PRECACHE_SOUND("player/pl_metal4.wav"); + + PRECACHE_SOUND("player/pl_dirt1.wav"); // walk on dirt + PRECACHE_SOUND("player/pl_dirt2.wav"); + PRECACHE_SOUND("player/pl_dirt3.wav"); + PRECACHE_SOUND("player/pl_dirt4.wav"); + + PRECACHE_SOUND("player/pl_duct1.wav"); // walk in duct + PRECACHE_SOUND("player/pl_duct2.wav"); + PRECACHE_SOUND("player/pl_duct3.wav"); + PRECACHE_SOUND("player/pl_duct4.wav"); + + PRECACHE_SOUND("player/pl_grate1.wav"); // walk on grate + PRECACHE_SOUND("player/pl_grate2.wav"); + PRECACHE_SOUND("player/pl_grate3.wav"); + PRECACHE_SOUND("player/pl_grate4.wav"); + + PRECACHE_SOUND("player/pl_slosh1.wav"); // walk in shallow water + PRECACHE_SOUND("player/pl_slosh2.wav"); + PRECACHE_SOUND("player/pl_slosh3.wav"); + PRECACHE_SOUND("player/pl_slosh4.wav"); + + PRECACHE_SOUND("player/pl_tile1.wav"); // walk on tile + PRECACHE_SOUND("player/pl_tile2.wav"); + PRECACHE_SOUND("player/pl_tile3.wav"); + PRECACHE_SOUND("player/pl_tile4.wav"); + PRECACHE_SOUND("player/pl_tile5.wav"); + + PRECACHE_SOUND("player/pl_swim1.wav"); // breathe bubbles + PRECACHE_SOUND("player/pl_swim2.wav"); + PRECACHE_SOUND("player/pl_swim3.wav"); + PRECACHE_SOUND("player/pl_swim4.wav"); + + PRECACHE_SOUND("player/pl_ladder1.wav"); // climb ladder rung + PRECACHE_SOUND("player/pl_ladder2.wav"); + PRECACHE_SOUND("player/pl_ladder3.wav"); + PRECACHE_SOUND("player/pl_ladder4.wav"); + + PRECACHE_SOUND("player/pl_wade1.wav"); // wade in water + PRECACHE_SOUND("player/pl_wade2.wav"); + PRECACHE_SOUND("player/pl_wade3.wav"); + PRECACHE_SOUND("player/pl_wade4.wav"); + + PRECACHE_SOUND("debris/wood1.wav"); // hit wood texture + PRECACHE_SOUND("debris/wood2.wav"); + PRECACHE_SOUND("debris/wood3.wav"); + + PRECACHE_SOUND("plats/train_use1.wav"); // use a train + + PRECACHE_SOUND("buttons/spark5.wav"); // hit computer texture + PRECACHE_SOUND("buttons/spark6.wav"); + PRECACHE_SOUND("debris/glass1.wav"); + PRECACHE_SOUND("debris/glass2.wav"); + PRECACHE_SOUND("debris/glass3.wav"); + + PRECACHE_SOUND( SOUND_FLASHLIGHT_ON ); + PRECACHE_SOUND( SOUND_FLASHLIGHT_OFF ); + +// player gib sounds + PRECACHE_SOUND("common/bodysplat.wav"); + +// player pain sounds + PRECACHE_SOUND("player/pain1.wav"); + PRECACHE_SOUND("player/pain2.wav"); + PRECACHE_SOUND("player/pain3.wav"); + PRECACHE_SOUND("player/pain4.wav"); + PRECACHE_SOUND("player/pain5.wav"); + PRECACHE_SOUND("player/pain6.wav"); + PRECACHE_SOUND("player/drown1.wav"); + PRECACHE_SOUND("player/drown2.wav"); + PRECACHE_SOUND("player/lburn1.wav"); + PRECACHE_SOUND("player/lburn2.wav"); + PRECACHE_SOUND("player/death1.wav"); + PRECACHE_SOUND("player/death2.wav"); + PRECACHE_SOUND("player/death3.wav"); + PRECACHE_SOUND("player/death4.wav"); + PRECACHE_SOUND("player/death5.wav"); + + PRECACHE_SOUND("player/h2odeath.wav"); + + PRECACHE_MODEL("models/player.mdl"); + + // hud sounds + + PRECACHE_SOUND("common/wpn_hudoff.wav"); + PRECACHE_SOUND("common/wpn_hudon.wav"); + PRECACHE_SOUND("common/wpn_moveselect.wav"); + PRECACHE_SOUND("common/wpn_select.wav"); + PRECACHE_SOUND("common/wpn_denyselect.wav"); + + PRECACHE_SOUND("player/gib.wav"); + + PRECACHE_MODEL("models/gib_1.mdl"); + PRECACHE_MODEL("models/gib_2.mdl"); + PRECACHE_MODEL("models/gib_3.mdl"); + + PRECACHE_SOUND("player/plyrjmp8.wav"); + +#ifdef THREEWAVE + + PRECACHE_MODEL("models/rune_resist.mdl"); + PRECACHE_MODEL("models/rune_haste.mdl"); + PRECACHE_MODEL("models/rune_regen.mdl"); + PRECACHE_MODEL("models/rune_strength.mdl"); + + PRECACHE_SOUND("rune/rune1.wav"); + PRECACHE_SOUND("rune/rune2.wav"); + PRECACHE_SOUND("rune/rune22.wav"); // Quad + Strength Rune. + PRECACHE_SOUND("rune/rune3.wav"); + PRECACHE_SOUND("rune/rune4.wav"); + + PRECACHE_MODEL("models/hook.mdl"); + + PRECACHE_MODEL("sprites/rope.spr"); + + PRECACHE_SOUND("weapons/grfire.wav"); + PRECACHE_SOUND("weapons/grhang.wav"); + PRECACHE_SOUND("weapons/grhit.wav"); + PRECACHE_SOUND("weapons/grpull.wav"); + PRECACHE_SOUND("weapons/grreset.wav"); + + g_usHook = PRECACHE_EVENT( 1, "events/hook.sc" ); + g_usCable = PRECACHE_EVENT( 1, "events/cable.sc" ); + g_usCarried = PRECACHE_EVENT( 1, "events/follow.sc" ); + g_usFlagSpawn = PRECACHE_EVENT( 1, "events/flagspawn.sc" ); + +#endif + + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/p_crowbar.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/p_light.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/p_nail.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/p_nail2.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/p_rock.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/p_rock2.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/p_shot.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/p_shot2.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/spike.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/rocket.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/grenade.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/backpack.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/backpack.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/armor_g.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/armor_r.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/armor_y.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/armor_y.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/b_nail0.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/b_nail1.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/g_light.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/g_nail.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/g_nail2.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/g_rock.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/g_rock2.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/g_shot2.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/pow_invis.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/pow_quad.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/pow_invuln.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/suit.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/w_battery.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/w_batteryl.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/w_medkit.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/w_medkitl.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/w_medkits.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/w_rpgammo.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/w_rpgammo_big.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/w_shotbox.mdl"); + ENGINE_FORCE_UNMODIFIED(force_exactfile, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ),"models/w_shotbox_big.mdl"); + + g_sGibbed = PRECACHE_EVENT( 1, "events/gibs.sc" ); + g_sTeleport = PRECACHE_EVENT( 1, "events/teleport.sc" ); + g_sTrail = PRECACHE_EVENT( 1, "events/trail.sc" ); + g_sExplosion = PRECACHE_EVENT( 1, "events/explosion.sc" ); + g_usPowerUp = PRECACHE_EVENT( 1, "events/powerup.sc" ); + + if (giPrecacheGrunt) + UTIL_PrecacheOther("monster_human_grunt"); +} + +/* +================ +Sys_Error + +Engine is going to shut down, allows setting a breakpoint in game .dll to catch that occasion +================ +*/ +void Sys_Error( const char *error_string ) +{ + // Default case, do nothing. MOD AUTHORS: Add code ( e.g., _asm { int 3 }; here to cause a breakpoint for debugging your game .dlls +} + +/* +=============== +const char *GetGameDescription() + +Returns the descriptive name of this .dll. E.g., Half-Life, or Team Fortress 2 +=============== +*/ +const char *GetGameDescription() +{ + if ( g_pGameRules ) // this function may be called before the world has spawned, and the game rules initialized + return g_pGameRules->GetGameDescription(); + else + return "DMC"; +} + +/* +================ +PlayerCustomization + +A new player customization has been registered on the server +UNDONE: This only sets the # of frames of the spray can logo +animation right now. +================ +*/ +void PlayerCustomization( edict_t *pEntity, customization_t *pCust ) +{ + entvars_t *pev = &pEntity->v; + CBasePlayer *pPlayer = (CBasePlayer *)GET_PRIVATE(pEntity); + + if (!pPlayer) + { + ALERT(at_console, "PlayerCustomization: Couldn't get player!\n"); + return; + } + + if (!pCust) + { + ALERT(at_console, "PlayerCustomization: NULL customization!\n"); + return; + } + + switch (pCust->resource.type) + { + case t_decal: + pPlayer->SetCustomDecalFrames(pCust->nUserData2); // Second int is max # of frames. + break; + case t_sound: + case t_skin: + case t_model: + // Ignore for now. + break; + default: + ALERT(at_console, "PlayerCustomization: Unknown customization type!\n"); + break; + } +} + +/* +================ +SpectatorConnect + +A spectator has joined the game +================ +*/ +void SpectatorConnect( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + CBaseSpectator *pPlayer = (CBaseSpectator *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->SpectatorConnect( ); +} + +/* +================ +SpectatorConnect + +A spectator has left the game +================ +*/ +void SpectatorDisconnect( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + CBaseSpectator *pPlayer = (CBaseSpectator *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->SpectatorDisconnect( ); +} + +/* +================ +SpectatorConnect + +A spectator has sent a usercmd +================ +*/ +void SpectatorThink( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + CBaseSpectator *pPlayer = (CBaseSpectator *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->SpectatorThink( ); +} + +//////////////////////////////////////////////////////// +// PAS and PVS routines for client messaging +// + +/* +================ +SetupVisibility + +A client can have a separate "view entity" indicating that his/her view should depend on the origin of that +view entity. If that's the case, then pViewEntity will be non-NULL and will be used. Otherwise, the current +entity's origin is used. Either is offset by the view_ofs to get the eye position. + +From the eye position, we set up the PAS and PVS to use for filtering network messages to the client. At this point, we could + override the actual PAS or PVS values, or use a different origin. + +NOTE: Do not cache the values of pas and pvs, as they depend on reusable memory in the engine, they are only good for this one frame +================ +*/ +void SetupVisibility( edict_t *pViewEntity, edict_t *pClient, unsigned char **pvs, unsigned char **pas ) +{ + Vector org; + edict_t *pView = pClient; + + // Find the client's PVS + if ( pViewEntity ) + { + pView = pViewEntity; + } + + if ( pClient->v.flags & FL_PROXY ) + { + *pvs = NULL; // the spectator proxy sees + *pas = NULL; // and hears everything + return; + } + org = pView->v.origin + pView->v.view_ofs; + if ( pView->v.flags & FL_DUCKING ) + { + org = org + ( VEC_HULL_MIN - VEC_DUCK_HULL_MIN ); + } + + *pvs = ENGINE_SET_PVS ( (float *)&org ); + *pas = ENGINE_SET_PAS ( (float *)&org ); +} + +#include "entity_state.h" + +/* +AddToFullPack + +Return 1 if the entity state has been filled in for the ent and the entity will be propagated to the client, 0 otherwise + +state is the server maintained copy of the state info that is transmitted to the client +a MOD could alter values copied into state to send the "host" a different look for a particular entity update, etc. +e and ent are the entity that is being added to the update, if 1 is returned +host is the player's edict of the player whom we are sending the update to +player is 1 if the ent/e is a player and 0 otherwise +pSet is either the PAS or PVS that we previous set up. We can use it to ask the engine to filter the entity against the PAS or PVS. +we could also use the pas/ pvs that we set in SetupVisibility, if we wanted to. Caching the value is valid in that case, but still only for the current frame +*/ +int AddToFullPack( struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet ) +{ + int i; + + // don't send if flagged for NODRAW and it's not the host getting the message + if ( ( ent->v.effects & EF_NODRAW ) && + ( ent != host ) ) + return 0; + + // Ignore ents without valid / visible models + if ( !ent->v.modelindex || !STRING( ent->v.model ) ) + return 0; + + // Don't send spectators to other players + if ( ( ent->v.flags & FL_SPECTATOR ) && ( ent != host ) ) + { + return 0; + } + + // Ignore if not the host and not touching a PVS/PAS leaf + // If pSet is NULL, then the test will always succeed and the entity will be added to the update + if ( ent != host ) + { + //We still want to show all ents for a quick period ( max 700ms of lag ) for when we predict teleporters + //if we don't do this, the entities on the other side wont show until the real origin comes thru and reaches + //the new PVS/PAS. + if ( !ENGINE_CHECK_VISIBILITY( (const struct edict_s *)ent, pSet ) && ent->v.fuser4 < gpGlobals->time ) + { + return 0; + } + } + + + // Don't send entity to local client if the client says it's predicting the entity itself. + if ( ent->v.flags & FL_SKIPLOCALHOST ) + { + if ( ( hostflags & 1 ) && ( ent->v.owner == host ) ) + return 0; + } + + if ( host->v.groupinfo ) + { + UTIL_SetGroupTrace( host->v.groupinfo, GROUP_OP_AND ); + + // Should always be set, of course + if ( ent->v.groupinfo ) + { + if ( g_groupop == GROUP_OP_AND ) + { + if ( !(ent->v.groupinfo & host->v.groupinfo ) ) + return 0; + } + else if ( g_groupop == GROUP_OP_NAND ) + { + if ( ent->v.groupinfo & host->v.groupinfo ) + return 0; + } + } + + UTIL_UnsetGroupTrace(); + } + + memset( state, 0, sizeof( *state ) ); + + // Assign index so we can track this entity from frame to frame and + // delta from it. + state->number = e; + state->entityType = ENTITY_NORMAL; + + // Flag custom entities. + if ( ent->v.flags & FL_CUSTOMENTITY ) + { + state->entityType = ENTITY_BEAM; + } + + // + // Copy state data + // + + // Round animtime to nearest millisecond + state->animtime = (int)(1000.0 * ent->v.animtime ) / 1000.0; + + memcpy( state->origin, ent->v.origin, 3 * sizeof( float ) ); + memcpy( state->angles, ent->v.angles, 3 * sizeof( float ) ); + memcpy( state->mins, ent->v.mins, 3 * sizeof( float ) ); + memcpy( state->maxs, ent->v.maxs, 3 * sizeof( float ) ); + + memcpy( state->startpos, ent->v.startpos, 3 * sizeof( float ) ); + memcpy( state->endpos, ent->v.endpos, 3 * sizeof( float ) ); + + state->impacttime = ent->v.impacttime; + state->starttime = ent->v.starttime; + + state->modelindex = ent->v.modelindex; + + state->frame = ent->v.frame; + state->skin = ent->v.skin; + state->effects = ent->v.effects; + + // This non-player entity is being moved by the game .dll and not the physics simulation system + // make sure that we interpolate it's position on the client if it moves + if ( !player && + ent->v.animtime && + ent->v.velocity[ 0 ] == 0 && + ent->v.velocity[ 1 ] == 0 && + ent->v.velocity[ 2 ] == 0 ) + { + state->eflags |= EFLAG_SLERP; + } + + state->scale = ent->v.scale; + state->solid = ent->v.solid; + state->colormap = ent->v.colormap; + state->movetype = ent->v.movetype; + state->sequence = ent->v.sequence; + state->framerate = ent->v.framerate; + state->body = ent->v.body; + + for (i = 0; i < 4; i++) + { + state->controller[i] = ent->v.controller[i]; + } + + for (i = 0; i < 2; i++) + { + state->blending[i] = ent->v.blending[i]; + } + + state->rendermode = ent->v.rendermode; + state->renderamt = ent->v.renderamt; + state->renderfx = ent->v.renderfx; + state->rendercolor.r = ent->v.rendercolor[0]; + state->rendercolor.g = ent->v.rendercolor[1]; + state->rendercolor.b = ent->v.rendercolor[2]; + + state->aiment = 0; + if ( ent->v.aiment ) + { + state->aiment = ENTINDEX( ent->v.aiment ); + } + + state->owner = 0; + if ( ent->v.owner ) + { + int owner = ENTINDEX( ent->v.owner ); + + // Only care if owned by a player + if ( owner >= 1 && owner <= gpGlobals->maxClients ) + { + state->owner = owner; + } + } + // Special stuff for players only + if ( player ) + { + memcpy( state->basevelocity, ent->v.basevelocity, 3 * sizeof( float ) ); + + state->weaponmodel = MODEL_INDEX( STRING( ent->v.weaponmodel ) ); + state->gaitsequence = ent->v.gaitsequence; + state->spectator = ent->v.flags & FL_SPECTATOR; + state->friction = ent->v.friction; + + state->gravity = ent->v.gravity; +// state->team = ent->v.team; +// state->playerclass = ent->v.playerclass; + state->usehull = ( ent->v.flags & FL_DUCKING ) ? 1 : 0; + state->health = ent->v.health; + } + + return 1; +} + +// defaults for clientinfo messages +#define DEFAULT_VIEWHEIGHT 28 + +/* +=================== +CreateBaseline + +Creates baselines used for network encoding, especially for player data since players are not spawned until connect time. +=================== +*/ +void CreateBaseline( int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, vec3_t player_mins, vec3_t player_maxs ) +{ + baseline->origin = entity->v.origin; + baseline->angles = entity->v.angles; + baseline->frame = entity->v.frame; + baseline->skin = (short)entity->v.skin; + + // render information + baseline->rendermode = (byte)entity->v.rendermode; + baseline->renderamt = (byte)entity->v.renderamt; + baseline->rendercolor.r = (byte)entity->v.rendercolor[0]; + baseline->rendercolor.g = (byte)entity->v.rendercolor[1]; + baseline->rendercolor.b = (byte)entity->v.rendercolor[2]; + baseline->renderfx = (byte)entity->v.renderfx; + + if ( player ) + { + baseline->mins = player_mins; + baseline->maxs = player_maxs; + + baseline->colormap = eindex; + baseline->modelindex = playermodelindex; + baseline->friction = 1.0; + baseline->movetype = MOVETYPE_WALK; + + baseline->scale = entity->v.scale; + baseline->solid = SOLID_SLIDEBOX; + baseline->framerate = 1.0; + baseline->gravity = 1.0; + + } + else + { + baseline->mins = entity->v.mins; + baseline->maxs = entity->v.maxs; + + baseline->colormap = 0; + baseline->modelindex = entity->v.modelindex;//SV_ModelIndex(pr_strings + entity->v.model); + baseline->movetype = entity->v.movetype; + + baseline->scale = entity->v.scale; + baseline->solid = entity->v.solid; + baseline->framerate = entity->v.framerate; + baseline->gravity = entity->v.gravity; + } +} + +typedef struct +{ + char name[32]; + int field; +} entity_field_alias_t; + +#define FIELD_ORIGIN0 0 +#define FIELD_ORIGIN1 1 +#define FIELD_ORIGIN2 2 +#define FIELD_ANGLES0 3 +#define FIELD_ANGLES1 4 +#define FIELD_ANGLES2 5 + +static entity_field_alias_t entity_field_alias[]= +{ + { "origin[0]", 0 }, + { "origin[1]", 0 }, + { "origin[2]", 0 }, + { "angles[0]", 0 }, + { "angles[1]", 0 }, + { "angles[2]", 0 }, +}; + +void Entity_FieldInit( struct delta_s *pFields ) +{ + entity_field_alias[ FIELD_ORIGIN0 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ORIGIN0 ].name ); + entity_field_alias[ FIELD_ORIGIN1 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ORIGIN1 ].name ); + entity_field_alias[ FIELD_ORIGIN2 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ORIGIN2 ].name ); + entity_field_alias[ FIELD_ANGLES0 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ANGLES0 ].name ); + entity_field_alias[ FIELD_ANGLES1 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ANGLES1 ].name ); + entity_field_alias[ FIELD_ANGLES2 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ANGLES2 ].name ); +} + +/* +================== +Entity_Encode + +Callback for sending entity_state_t info over network. +FIXME: Move to script +================== +*/ +void Entity_Encode( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) +{ + entity_state_t *f, *t; + + int localplayer = 0; + static int initialized = 0; + + if ( !initialized ) + { + Entity_FieldInit( pFields ); + initialized = 1; + } + + f = (entity_state_t *)from; + t = (entity_state_t *)to; + + // Never send origin to local player, it's sent with more resolution in clientdata_t structure + localplayer = ( t->number - 1 ) == ENGINE_CURRENT_PLAYER(); + if ( localplayer ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + + if ( ( t->impacttime != 0 ) && ( t->starttime != 0 ) ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ANGLES0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ANGLES1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ANGLES2 ].field ); + } + + if ( ( t->movetype == MOVETYPE_FOLLOW ) && + ( t->aiment != 0 ) ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + else if ( t->aiment != f->aiment ) + { + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } +} + +static entity_field_alias_t player_field_alias[]= +{ + { "origin[0]", 0 }, + { "origin[1]", 0 }, + { "origin[2]", 0 }, +}; + +void Player_FieldInit( struct delta_s *pFields ) +{ + player_field_alias[ FIELD_ORIGIN0 ].field = DELTA_FINDFIELD( pFields, player_field_alias[ FIELD_ORIGIN0 ].name ); + player_field_alias[ FIELD_ORIGIN1 ].field = DELTA_FINDFIELD( pFields, player_field_alias[ FIELD_ORIGIN1 ].name ); + player_field_alias[ FIELD_ORIGIN2 ].field = DELTA_FINDFIELD( pFields, player_field_alias[ FIELD_ORIGIN2 ].name ); +} + +/* +================== +Player_Encode + +Callback for sending entity_state_t for players info over network. +================== +*/ +void Player_Encode( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) +{ + entity_state_t *f, *t; + int localplayer = 0; + static int initialized = 0; + + if ( !initialized ) + { + Player_FieldInit( pFields ); + initialized = 1; + } + + f = (entity_state_t *)from; + t = (entity_state_t *)to; + + // Never send origin to local player, it's sent with more resolution in clientdata_t structure + localplayer = ( t->number - 1 ) == ENGINE_CURRENT_PLAYER(); + if ( localplayer ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + + if ( ( t->movetype == MOVETYPE_FOLLOW ) && + ( t->aiment != 0 ) ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + else if ( t->aiment != f->aiment ) + { + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } +} + +#define CUSTOMFIELD_ORIGIN0 0 +#define CUSTOMFIELD_ORIGIN1 1 +#define CUSTOMFIELD_ORIGIN2 2 +#define CUSTOMFIELD_ANGLES0 3 +#define CUSTOMFIELD_ANGLES1 4 +#define CUSTOMFIELD_ANGLES2 5 +#define CUSTOMFIELD_SKIN 6 +#define CUSTOMFIELD_SEQUENCE 7 +#define CUSTOMFIELD_ANIMTIME 8 + +entity_field_alias_t custom_entity_field_alias[]= +{ + { "origin[0]", 0 }, + { "origin[1]", 0 }, + { "origin[2]", 0 }, + { "angles[0]", 0 }, + { "angles[1]", 0 }, + { "angles[2]", 0 }, + { "skin", 0 }, + { "sequence", 0 }, + { "animtime", 0 }, +}; + +void Custom_Entity_FieldInit( struct delta_s *pFields ) +{ + custom_entity_field_alias[ CUSTOMFIELD_ORIGIN0 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN0 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ORIGIN1 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN1 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ORIGIN2 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN2 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANGLES0 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES0 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANGLES1 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES1 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANGLES2 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES2 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_SKIN ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_SKIN ].name ); + custom_entity_field_alias[ CUSTOMFIELD_SEQUENCE ].field= DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_SEQUENCE ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANIMTIME ].field= DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANIMTIME ].name ); +} + +/* +================== +Custom_Encode + +Callback for sending entity_state_t info ( for custom entities ) over network. +FIXME: Move to script +================== +*/ +void Custom_Encode( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) +{ + entity_state_t *f, *t; + int beamType; + static int initialized = 0; + + if ( !initialized ) + { + Custom_Entity_FieldInit( pFields ); + initialized = 1; + } + + f = (entity_state_t *)from; + t = (entity_state_t *)to; + + beamType = t->rendermode & 0x0f; + + if ( beamType != BEAM_POINTS && beamType != BEAM_ENTPOINT ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN2 ].field ); + } + + if ( beamType != BEAM_POINTS ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES0 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES1 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES2 ].field ); + } + + if ( beamType != BEAM_ENTS && beamType != BEAM_ENTPOINT ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_SKIN ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_SEQUENCE ].field ); + } + + // animtime is compared by rounding first + // see if we really shouldn't actually send it + if ( (int)f->animtime == (int)t->animtime ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANIMTIME ].field ); + } +} + +/* +================= +RegisterEncoders + +Allows game .dll to override network encoding of certain types of entities and tweak values, etc. +================= +*/ +void RegisterEncoders( void ) +{ + DELTA_ADDENCODER( "Entity_Encode", Entity_Encode ); + DELTA_ADDENCODER( "Custom_Encode", Custom_Encode ); + DELTA_ADDENCODER( "Player_Encode", Player_Encode ); +} + +int GetWeaponData( struct edict_s *player, struct weapon_data_s *info ) +{ + int i; + weapon_data_t *item; + entvars_t *pev = &player->v; + CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); + CBasePlayerWeapon *gun; + + ItemInfo II; + + memset( info, 0, 32 * sizeof( weapon_data_t ) ); + + if ( !pl ) + return 1; + + // go through all of the weapons and make a list of the ones to pack + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( pl->m_rgpPlayerItems[ i ] ) + { + // there's a weapon here. Should I pack it? + CBasePlayerItem *pPlayerItem = pl->m_rgpPlayerItems[ i ]; + + while ( pPlayerItem ) + { + gun = (CBasePlayerWeapon *)pPlayerItem->GetWeaponPtr(); + if ( gun && gun->UseDecrement() ) + { + // Get The ID. + memset( &II, 0, sizeof( II ) ); + gun->GetItemInfo( &II ); + + if ( II.iId >= 0 && II.iId < 32 ) + { + item = &info[ II.iId ]; + + item->m_iId = II.iId; + item->m_iClip = gun->m_iClip; + + item->m_flTimeWeaponIdle = max( gun->m_flTimeWeaponIdle, -0.001 ); + item->m_flNextPrimaryAttack = max( gun->m_flNextPrimaryAttack, -0.001 ); + item->m_flNextSecondaryAttack = max( gun->m_flNextSecondaryAttack, -0.001 ); + item->m_fInReload = gun->m_fInReload; + } + } + pPlayerItem = pPlayerItem->m_pNext; + } + } + } + return 1; +} + +/* +================= +UpdateClientData + +Data sent to current client only +engine sets cd to 0 before calling. +================= +*/ +void UpdateClientData ( const struct edict_s *ent, int sendweapons, struct clientdata_s *cd ) +{ + cd->flags = ent->v.flags; + cd->health = ent->v.health; + + cd->viewmodel = MODEL_INDEX( STRING( ent->v.viewmodel ) ); + cd->waterlevel = ent->v.waterlevel; + cd->watertype = ent->v.watertype; + //cd->weapons = ent->v.weapons; + + // Vectors + cd->origin = ent->v.origin; + cd->velocity = ent->v.velocity; + cd->view_ofs = ent->v.view_ofs; + cd->punchangle = ent->v.punchangle; + + cd->bInDuck = ent->v.bInDuck; + cd->flTimeStepSound = ent->v.flTimeStepSound; + cd->flDuckTime = ent->v.flDuckTime; + cd->flSwimTime = ent->v.flSwimTime; + cd->waterjumptime = ent->v.teleport_time; + + strcpy( cd->physinfo, ENGINE_GETPHYSINFO( ent ) ); + + cd->maxspeed = ent->v.maxspeed; + cd->fov = ent->v.fov; + cd->weaponanim = ent->v.weaponanim; + + cd->pushmsec = ent->v.pushmsec; + + // Observer + cd->iuser1 = ent->v.iuser1; + cd->iuser2 = ent->v.iuser2; + cd->iuser3 = ent->v.iuser3; + + if ( sendweapons ) + { + entvars_t *pev = (entvars_t *)&ent->v; + CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); + + if ( pl ) + { + cd->m_flNextAttack = pl->m_flNextAttack; + cd->weapons = pl->m_iQuakeItems; + + if ( pl->m_pActiveItem ) + { + CBasePlayerWeapon *gun; + gun = (CBasePlayerWeapon *)pl->m_pActiveItem->GetWeaponPtr(); + if ( gun && gun->UseDecrement() ) + { + ItemInfo II; + memset( &II, 0, sizeof( II ) ); + gun->GetItemInfo( &II ); + + cd->m_iId = II.iId; + } + } + + cd->fuser1 = (float)pl->m_iQuakeWeapon; + cd->iuser4 = gpGlobals->deathmatch; + cd->fuser2 = pl->m_iNailOffset > 0 ? 1.0 : 0.0; +#ifdef THREEWAVE + cd->fuser3 = (float)pl->m_iRuneStatus; +#endif + + cd->iuser3 = pl->m_iQuakeItems; + + cd->ammo_shells = pl->m_iAmmoShells; + cd->ammo_nails = pl->m_iAmmoNails; + cd->ammo_cells = pl->m_iAmmoCells; + cd->ammo_rockets = pl->m_iAmmoRockets; + } + } +} + +/* +================= +CmdStart + +We're about to run this usercmd for the specified player. We can set up groupinfo and masking here, etc. +This is the time to examine the usercmd for anything extra. This call happens even if think does not. +================= +*/ +void CmdStart( const edict_t *player, const struct usercmd_s *cmd, unsigned int random_seed ) +{ + entvars_t *pev = (entvars_t *)&player->v; + CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); + + if( !pl ) + return; + + if ( cmd->weaponselect != 0 ) + { + usercmd_t *c = (usercmd_t *)cmd; + pl->W_ChangeWeapon( c->weaponselect ); + c->weaponselect = 0; + } + + if ( pl->pev->groupinfo != 0 ) + { + UTIL_SetGroupTrace( pl->pev->groupinfo, GROUP_OP_AND ); + } + + pl->random_seed = random_seed; +} + +/* +================= +CmdEnd + +Each cmdstart is exactly matched with a cmd end, clean up any group trace flags, etc. here +================= +*/ +void CmdEnd ( const edict_t *player ) +{ + entvars_t *pev = (entvars_t *)&player->v; + CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); + + if( !pl ) + return; + if ( pl->pev->groupinfo != 0 ) + { + UTIL_UnsetGroupTrace(); + } +} + +/* +================================ +ConnectionlessPacket + + Return 1 if the packet is valid. Set response_buffer_size if you want to send a response packet. Incoming, it holds the max + size of the response_buffer, so you must zero it out if you choose not to respond. +================================ +*/ +int ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ) +{ + // Parse stuff from args + int max_buffer_size = *response_buffer_size; + + // Zero it out since we aren't going to respond. + // If we wanted to response, we'd write data into response_buffer + *response_buffer_size = 0; + + // Since we don't listen for anything here, just respond that it's a bogus message + // If we didn't reject the message, we'd return 1 for success instead. + return 0; +} + +/* +================================ +GetHullBounds + + Engine calls this to enumerate player collision hulls, for prediction. Return 0 if the hullnumber doesn't exist. +================================ +*/ +int GetHullBounds( int hullnumber, float *mins, float *maxs ) +{ + int iret = 0; + + switch ( hullnumber ) + { + case 0: // Normal player + mins = Vector(-16, -16, -32); + maxs = Vector(16, 16, 32); + iret = 1; + break; + case 1: // Crouched player + mins = Vector(-16, -16, -32); + maxs = Vector(16, 16, 32); + iret = 1; + break; + case 2: // Point based hull + mins = Vector( 0, 0, 0 ); + maxs = Vector( 0, 0, 0 ); + iret = 1; + break; + } + + return iret; +} + +/* +================================ +CreateInstancedBaselines + +Create pseudo-baselines for items that aren't placed in the map at spawn time, but which are likely +to be created during play ( e.g., grenades, ammo packs, projectiles, corpses, etc. ) +================================ +*/ +void CreateInstancedBaselines ( void ) +{ + int iret = 0; + entity_state_t state; + + memset( &state, 0, sizeof( state ) ); + + // Create any additional baselines here for things like grendates, etc. + // iret = ENGINE_INSTANCE_BASELINE( pc->pev->classname, &state ); + + // Destroy objects. + //UTIL_Remove( pc ); +} + +/* +================================ +InconsistentFile + +One of the ENGINE_FORCE_UNMODIFIED files failed the consistency check for the specified player + Return 0 to allow the client to continue, 1 to force immediate disconnection ( with an optional disconnect message of up to 256 characters ) +================================ +*/ +int InconsistentFile( const edict_t *player, const char *filename, char *disconnect_message ) +{ + // Server doesn't care? + if ( CVAR_GET_FLOAT( "mp_consistency" ) != 1 ) + return 0; + + // Default behavior is to kick the player + sprintf( disconnect_message, "Server is enforcing file consistency for %s\n", filename ); + + // Kick now with specified disconnect message. + return 1; +} + +/* +================================ +AllowLagCompensation + + The game .dll should return 1 if lag compensation should be allowed ( could also just set + the sv_unlag cvar. + Most games right now should return 0, until client-side weapon prediction code is written + and tested for them ( note you can predict weapons, but not do lag compensation, too, + if you want. +================================ +*/ +int AllowLagCompensation( void ) +{ + return 0; +} + diff --git a/dmc/dlls/client.h b/dmc/dlls/client.h new file mode 100644 index 0000000..1e66cc8 --- /dev/null +++ b/dmc/dlls/client.h @@ -0,0 +1,65 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef CLIENT_H +#define CLIENT_H + +extern void respawn( entvars_t* pev, BOOL fCopyCorpse ); +extern BOOL ClientConnect( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); +extern void ClientDisconnect( edict_t *pEntity ); +extern void ClientKill( edict_t *pEntity ); +extern void ClientPutInServer( edict_t *pEntity ); +extern void ClientCommand( edict_t *pEntity ); +extern void ClientUserInfoChanged( edict_t *pEntity, char *infobuffer ); +extern void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ); +extern void ServerDeactivate( void ); +extern void StartFrame( void ); +extern void PlayerPostThink( edict_t *pEntity ); +extern void PlayerPreThink( edict_t *pEntity ); +extern void ParmsNewLevel( void ); +extern void ParmsChangeLevel( void ); + +extern void ClientPrecache( void ); + +extern const char *GetGameDescription( void ); +extern void PlayerCustomization( edict_t *pEntity, customization_t *pCust ); + +extern void SpectatorConnect ( edict_t *pEntity ); +extern void SpectatorDisconnect ( edict_t *pEntity ); +extern void SpectatorThink ( edict_t *pEntity ); + +extern void Sys_Error( const char *error_string ); + +extern void SetupVisibility( edict_t *pViewEntity, edict_t *pClient, unsigned char **pvs, unsigned char **pas ); +extern void UpdateClientData ( const struct edict_s *ent, int sendweapons, struct clientdata_s *cd ); +extern int AddToFullPack( struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet ); +extern void CreateBaseline( int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, vec3_t player_mins, vec3_t player_maxs ); +extern void RegisterEncoders( void ); + +extern int GetWeaponData( struct edict_s *player, struct weapon_data_s *info ); + +extern void CmdStart( const edict_t *player, const struct usercmd_s *cmd, unsigned int random_seed ); +extern void CmdEnd ( const edict_t *player ); + +extern int ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); + +extern int GetHullBounds( int hullnumber, float *mins, float *maxs ); + +extern void CreateInstancedBaselines ( void ); + +extern int InconsistentFile( const edict_t *player, const char *filename, char *disconnect_message ); + +extern int AllowLagCompensation( void ); + +#endif // CLIENT_H diff --git a/dmc/dlls/combat.cpp b/dmc/dlls/combat.cpp new file mode 100644 index 0000000..53e3dab --- /dev/null +++ b/dmc/dlls/combat.cpp @@ -0,0 +1,1099 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== combat.cpp ======================================================== + + functions dealing with damage infliction & death + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "soundent.h" +#include "decals.h" +#include "animation.h" +#include "weapons.h" +#include "func_break.h" +#include "player.h" + +extern DLL_GLOBAL Vector g_vecAttackDir; +extern DLL_GLOBAL int g_iSkillLevel; + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); +extern entvars_t *g_pevLastInflictor; + +unsigned short g_sGibbed; +unsigned short g_sTeleport; +unsigned short g_sTrail; +unsigned short g_sExplosion; +unsigned short g_usPowerUp; + +#define GERMAN_GIB_COUNT 4 +#define HUMAN_GIB_COUNT 6 +#define ALIEN_GIB_COUNT 4 + + +// HACKHACK -- The gib velocity equations don't work +void CGib :: LimitVelocity( void ) +{ + float length = pev->velocity.Length(); + + // ceiling at 1500. The gib velocity equation is not bounded properly. Rather than tune it + // in 3 separate places again, I'll just limit it here. + if ( length > 1500.0 ) + pev->velocity = pev->velocity.Normalize() * 1500; // This should really be sv_maxvelocity * 0.75 or something +} + + +void CGib :: SpawnStickyGibs( entvars_t *pevVictim, Vector vecOrigin, int cGibs ) +{ + int i; + + if ( g_Language == LANGUAGE_GERMAN ) + { + // no sticky gibs in germany right now! + return; + } + + for ( i = 0 ; i < cGibs ; i++ ) + { + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + pGib->Spawn( "models/stickygib.mdl" ); + pGib->pev->body = RANDOM_LONG(0,2); + + if ( pevVictim ) + { + pGib->pev->origin.x = vecOrigin.x + RANDOM_FLOAT( -3, 3 ); + pGib->pev->origin.y = vecOrigin.y + RANDOM_FLOAT( -3, 3 ); + pGib->pev->origin.z = vecOrigin.z + RANDOM_FLOAT( -3, 3 ); + + /* + pGib->pev->origin.x = pevVictim->absmin.x + pevVictim->size.x * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.y = pevVictim->absmin.y + pevVictim->size.y * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.z = pevVictim->absmin.z + pevVictim->size.z * (RANDOM_FLOAT ( 0 , 1 ) ); + */ + + // make the gib fly away from the attack vector + pGib->pev->velocity = g_vecAttackDir * -1; + + // mix in some noise + pGib->pev->velocity.x += RANDOM_FLOAT ( -0.15, 0.15 ); + pGib->pev->velocity.y += RANDOM_FLOAT ( -0.15, 0.15 ); + pGib->pev->velocity.z += RANDOM_FLOAT ( -0.15, 0.15 ); + + pGib->pev->velocity = pGib->pev->velocity * 900; + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 250, 400 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 250, 400 ); + + // copy owner's blood color + pGib->m_bloodColor = (CBaseEntity::Instance(pevVictim))->BloodColor(); + + if ( pevVictim->health > -50) + { + pGib->pev->velocity = pGib->pev->velocity * 0.7; + } + else if ( pevVictim->health > -200) + { + pGib->pev->velocity = pGib->pev->velocity * 2; + } + else + { + pGib->pev->velocity = pGib->pev->velocity * 4; + } + + + pGib->pev->movetype = MOVETYPE_TOSS; + pGib->pev->solid = SOLID_BBOX; + UTIL_SetSize ( pGib->pev, Vector ( 0, 0 ,0 ), Vector ( 0, 0, 0 ) ); + pGib->SetTouch ( &CGib::StickyGibTouch ); + pGib->SetThink (NULL); + } + pGib->LimitVelocity(); + } +} + +void CGib :: SpawnHeadGib( entvars_t *pevVictim ) +{ + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + if ( g_Language == LANGUAGE_GERMAN ) + { + pGib->Spawn( "models/germangibs.mdl" );// throw one head + pGib->pev->body = 0; + } + else + { + pGib->Spawn( "models/hgibs.mdl" );// throw one head + pGib->pev->body = 0; + } + + if ( pevVictim ) + { + pGib->pev->origin = pevVictim->origin + pevVictim->view_ofs; + + edict_t *pentPlayer = FIND_CLIENT_IN_PVS( pGib->edict() ); + + if ( RANDOM_LONG ( 0, 100 ) <= 5 && pentPlayer ) + { + // 5% chance head will be thrown at player's face. + entvars_t *pevPlayer; + + pevPlayer = VARS( pentPlayer ); + pGib->pev->velocity = ( ( pevPlayer->origin + pevPlayer->view_ofs ) - pGib->pev->origin ).Normalize() * 300; + pGib->pev->velocity.z += 100; + } + else + { + pGib->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + } + + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + // copy owner's blood color + pGib->m_bloodColor = (CBaseEntity::Instance(pevVictim))->BloodColor(); + + if ( pevVictim->health > -50) + { + pGib->pev->velocity = pGib->pev->velocity * 0.7; + } + else if ( pevVictim->health > -200) + { + pGib->pev->velocity = pGib->pev->velocity * 2; + } + else + { + pGib->pev->velocity = pGib->pev->velocity * 4; + } + } + pGib->LimitVelocity(); +} + +void CGib :: SpawnRandomGibs( entvars_t *pevVictim, int cGibs, int human ) +{ + int cSplat; + + for ( cSplat = 0 ; cSplat < cGibs ; cSplat++ ) + { + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + if ( g_Language == LANGUAGE_GERMAN ) + { + pGib->Spawn( "models/germangibs.mdl" ); + pGib->pev->body = RANDOM_LONG(0,GERMAN_GIB_COUNT-1); + } + else + { + if ( human ) + { + // human pieces + pGib->Spawn( "models/hgibs.mdl" ); + pGib->pev->body = RANDOM_LONG(1,HUMAN_GIB_COUNT-1);// start at one to avoid throwing random amounts of skulls (0th gib) + } + else + { + // aliens + pGib->Spawn( "models/agibs.mdl" ); + pGib->pev->body = RANDOM_LONG(0,ALIEN_GIB_COUNT-1); + } + } + + if ( pevVictim ) + { + // spawn the gib somewhere in the monster's bounding volume + pGib->pev->origin.x = pevVictim->absmin.x + pevVictim->size.x * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.y = pevVictim->absmin.y + pevVictim->size.y * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.z = pevVictim->absmin.z + pevVictim->size.z * (RANDOM_FLOAT ( 0 , 1 ) ) + 1; // absmin.z is in the floor because the engine subtracts 1 to enlarge the box + + // make the gib fly away from the attack vector + pGib->pev->velocity = g_vecAttackDir * -1; + + // mix in some noise + pGib->pev->velocity.x += RANDOM_FLOAT ( -0.25, 0.25 ); + pGib->pev->velocity.y += RANDOM_FLOAT ( -0.25, 0.25 ); + pGib->pev->velocity.z += RANDOM_FLOAT ( -0.25, 0.25 ); + + pGib->pev->velocity = pGib->pev->velocity * RANDOM_FLOAT ( 300, 400 ); + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + // copy owner's blood color + pGib->m_bloodColor = (CBaseEntity::Instance(pevVictim))->BloodColor(); + + if ( pevVictim->health > -50) + { + pGib->pev->velocity = pGib->pev->velocity * 0.7; + } + else if ( pevVictim->health > -200) + { + pGib->pev->velocity = pGib->pev->velocity * 2; + } + else + { + pGib->pev->velocity = pGib->pev->velocity * 4; + } + + pGib->pev->solid = SOLID_BBOX; + UTIL_SetSize ( pGib->pev, Vector( 0 , 0 , 0 ), Vector ( 0, 0, 0 ) ); + } + pGib->LimitVelocity(); + } +} + +//========================================================= +// WaitTillLand - in order to emit their meaty scent from +// the proper location, gibs should wait until they stop +// bouncing to emit their scent. That's what this function +// does. +//========================================================= +void CGib :: WaitTillLand ( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + if ( pev->velocity == g_vecZero ) + { + SetThink (&CGib::SUB_StartFadeOut); + pev->nextthink = gpGlobals->time + m_lifeTime; + } + else + { + // wait and check again in another half second. + pev->nextthink = gpGlobals->time + 0.5; + } +} + +// +// Gib bounces on the ground or wall, sponges some blood down, too! +// +void CGib :: BounceGibTouch ( CBaseEntity *pOther ) +{ + Vector vecSpot; + TraceResult tr; + + //if ( RANDOM_LONG(0,1) ) + // return;// don't bleed everytime + + if (pev->flags & FL_ONGROUND) + { + pev->velocity = pev->velocity * 0.9; + pev->angles.x = 0; + pev->angles.z = 0; + pev->avelocity.x = 0; + pev->avelocity.z = 0; + } + else + { + if ( g_Language != LANGUAGE_GERMAN && m_cBloodDecals > 0 && m_bloodColor != DONT_BLEED ) + { + vecSpot = pev->origin + Vector ( 0 , 0 , 8 );//move up a bit, and trace down. + UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -24 ), ignore_monsters, ENT(pev), & tr); + + UTIL_BloodDecalTrace( &tr, m_bloodColor ); + + m_cBloodDecals--; + } + + if ( m_material != matNone && RANDOM_LONG(0,2) == 0 ) + { + float volume; + float zvel = fabs(pev->velocity.z); + + volume = 0.8 * min(1.0, ((float)zvel) / 450.0); + + CBreakable::MaterialSoundRandom( edict(), (Materials)m_material, volume ); + } + } +} + +// +// Sticky gib puts blood on the wall and stays put. +// +void CGib :: StickyGibTouch ( CBaseEntity *pOther ) +{ + Vector vecSpot; + TraceResult tr; + + SetThink ( &CGib::SUB_Remove ); + pev->nextthink = gpGlobals->time + 10; + + if ( !FClassnameIs( pOther->pev, "worldspawn" ) ) + { + pev->nextthink = gpGlobals->time; + return; + } + + UTIL_TraceLine ( pev->origin, pev->origin + pev->velocity * 32, ignore_monsters, ENT(pev), & tr); + + UTIL_BloodDecalTrace( &tr, m_bloodColor ); + + pev->velocity = tr.vecPlaneNormal * -1; + pev->angles = UTIL_VecToAngles ( pev->velocity ); + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + pev->movetype = MOVETYPE_NONE; +} + +//========================================================= +// GibMonster - create some gore and get rid of a monster's +// model. +//========================================================= +void CBaseMonster :: GibMonster( void ) +{ + TraceResult tr; + BOOL gibbed = FALSE; + + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "common/bodysplat.wav", 1, ATTN_NORM); + + if ( CVAR_GET_FLOAT("violence_hgibs") != 0 ) // Only the player will ever get here + { + if ( IsPlayer() ) + { + PLAYBACK_EVENT_FULL ( FEV_GLOBAL, edict(), + g_sGibbed, 0.0, (float *)&pev->origin, (float *)&g_vecAttackDir, 0.0, 0.0, 0, 0, 0, 0); + } + + /*CGib::SpawnHeadGib( pev ); + CGib::SpawnRandomGibs( pev, 4, 1 ); // throw some human gibs.*/ + } + gibbed = TRUE; +} + +//========================================================= +// GetDeathActivity - determines the best type of death +// anim to play. +//========================================================= +Activity CBaseMonster :: GetDeathActivity ( void ) +{ + Activity deathActivity; + BOOL fTriedDirection; + float flDot; + TraceResult tr; + Vector vecSrc; + + if ( pev->deadflag != DEAD_NO ) + { + // don't run this while dying. + return m_IdealActivity; + } + + vecSrc = Center(); + + fTriedDirection = FALSE; + deathActivity = ACT_DIESIMPLE;// in case we can't find any special deaths to do. + + UTIL_MakeVectors ( pev->angles ); + flDot = DotProduct ( gpGlobals->v_forward, g_vecAttackDir * -1 ); + + switch ( m_LastHitGroup ) + { + // try to pick a region-specific death. + case HITGROUP_HEAD: + deathActivity = ACT_DIE_HEADSHOT; + break; + + case HITGROUP_STOMACH: + deathActivity = ACT_DIE_GUTSHOT; + break; + + case HITGROUP_GENERIC: + // try to pick a death based on attack direction + fTriedDirection = TRUE; + + if ( flDot > 0.3 ) + { + deathActivity = ACT_DIEFORWARD; + } + else if ( flDot <= -0.3 ) + { + deathActivity = ACT_DIEBACKWARD; + } + break; + + default: + // try to pick a death based on attack direction + fTriedDirection = TRUE; + + if ( flDot > 0.3 ) + { + deathActivity = ACT_DIEFORWARD; + } + else if ( flDot <= -0.3 ) + { + deathActivity = ACT_DIEBACKWARD; + } + break; + } + + + // can we perform the prescribed death? + if ( LookupActivity ( deathActivity ) == ACTIVITY_NOT_AVAILABLE ) + { + // no! did we fail to perform a directional death? + if ( fTriedDirection ) + { + // if yes, we're out of options. Go simple. + deathActivity = ACT_DIESIMPLE; + } + else + { + // cannot perform the ideal region-specific death, so try a direction. + if ( flDot > 0.3 ) + { + deathActivity = ACT_DIEFORWARD; + } + else if ( flDot <= -0.3 ) + { + deathActivity = ACT_DIEBACKWARD; + } + } + } + + if ( LookupActivity ( deathActivity ) == ACTIVITY_NOT_AVAILABLE ) + { + // if we're still invalid, simple is our only option. + deathActivity = ACT_DIESIMPLE; + } + + if ( deathActivity == ACT_DIEFORWARD ) + { + // make sure there's room to fall forward + UTIL_TraceHull ( vecSrc, vecSrc + gpGlobals->v_forward * 64, dont_ignore_monsters, head_hull, edict(), &tr ); + + if ( tr.flFraction != 1.0 ) + { + deathActivity = ACT_DIESIMPLE; + } + } + + if ( deathActivity == ACT_DIEBACKWARD ) + { + // make sure there's room to fall backward + UTIL_TraceHull ( vecSrc, vecSrc - gpGlobals->v_forward * 64, dont_ignore_monsters, head_hull, edict(), &tr ); + + if ( tr.flFraction != 1.0 ) + { + deathActivity = ACT_DIESIMPLE; + } + } + + return deathActivity; +} + +//========================================================= +// GetSmallFlinchActivity - determines the best type of flinch +// anim to play. +//========================================================= +Activity CBaseMonster :: GetSmallFlinchActivity ( void ) +{ + Activity flinchActivity; + BOOL fTriedDirection; + float flDot; + + fTriedDirection = FALSE; + UTIL_MakeVectors ( pev->angles ); + flDot = DotProduct ( gpGlobals->v_forward, g_vecAttackDir * -1 ); + + switch ( m_LastHitGroup ) + { + // pick a region-specific flinch + case HITGROUP_HEAD: + flinchActivity = ACT_FLINCH_HEAD; + break; + case HITGROUP_STOMACH: + flinchActivity = ACT_FLINCH_STOMACH; + break; + case HITGROUP_LEFTARM: + flinchActivity = ACT_FLINCH_LEFTARM; + break; + case HITGROUP_RIGHTARM: + flinchActivity = ACT_FLINCH_RIGHTARM; + break; + case HITGROUP_LEFTLEG: + flinchActivity = ACT_FLINCH_LEFTLEG; + break; + case HITGROUP_RIGHTLEG: + flinchActivity = ACT_FLINCH_RIGHTLEG; + break; + case HITGROUP_GENERIC: + default: + // just get a generic flinch. + flinchActivity = ACT_SMALL_FLINCH; + break; + } + + + // do we have a sequence for the ideal activity? + if ( LookupActivity ( flinchActivity ) == ACTIVITY_NOT_AVAILABLE ) + { + flinchActivity = ACT_SMALL_FLINCH; + } + + return flinchActivity; +} + + +BOOL CBaseMonster::ShouldGibMonster( int iGib ) +{ + if ( ( iGib == GIB_NORMAL && pev->health < GIB_HEALTH_VALUE ) || ( iGib == GIB_ALWAYS ) ) + return TRUE; + + return FALSE; +} + + +void CBaseMonster::CallGibMonster( void ) +{ + BOOL fade = FALSE; + + if ( HasHumanGibs() ) + { + if ( CVAR_GET_FLOAT("violence_hgibs") == 0 ) + fade = TRUE; + } + else if ( HasAlienGibs() ) + { + if ( CVAR_GET_FLOAT("violence_agibs") == 0 ) + fade = TRUE; + } + + pev->takedamage = DAMAGE_NO; + pev->solid = SOLID_NOT;// do something with the body. while monster blows up + + if ( fade ) + { + FadeMonster(); + } + else + { + pev->effects = EF_NODRAW; // make the model invisible. + GibMonster(); + } + + pev->deadflag = DEAD_DEAD; + FCheckAITrigger(); + + // don't let the status bar glitch for players.with <0 health. + if (pev->health < -99) + { + pev->health = 0; + } + + if ( ShouldFadeOnDeath() && !fade ) + UTIL_Remove(this); +} + +// +// fade out - slowly fades a entity out, then removes it. +// +// DON'T USE ME FOR GIBS AND STUFF IN MULTIPLAYER! +// SET A FUTURE THINK AND A RENDERMODE!! +void CBaseEntity :: SUB_StartFadeOut ( void ) +{ + if (pev->rendermode == kRenderNormal) + { + pev->renderamt = 255; + pev->rendermode = kRenderTransTexture; + } + + pev->solid = SOLID_NOT; + pev->avelocity = g_vecZero; + + pev->nextthink = gpGlobals->time + 0.1; + SetThink ( &CBaseEntity::SUB_FadeOut ); +} + +void CBaseEntity :: SUB_FadeOut ( void ) +{ + if ( pev->renderamt > 7 ) + { + pev->renderamt -= 7; + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + pev->renderamt = 0; + pev->nextthink = gpGlobals->time + 0.2; + SetThink ( &CBaseEntity::SUB_Remove ); + } +} + +// +// Throw a chunk +// +void CGib :: Spawn( const char *szGibModel ) +{ + pev->movetype = MOVETYPE_BOUNCE; + pev->friction = 0.55; // deading the bounce a bit + + // sometimes an entity inherits the edict from a former piece of glass, + // and will spawn using the same render FX or rendermode! bad! + pev->renderamt = 255; + pev->rendermode = kRenderNormal; + pev->renderfx = kRenderFxNone; + pev->solid = SOLID_SLIDEBOX;/// hopefully this will fix the VELOCITY TOO LOW crap + pev->classname = MAKE_STRING("gib"); + + SET_MODEL(ENT(pev), szGibModel); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + + pev->nextthink = gpGlobals->time + 4; + m_lifeTime = 25; + SetThink ( &CGib::WaitTillLand ); + SetTouch ( &CGib::BounceGibTouch ); + + m_material = matNone; + m_cBloodDecals = 5;// how many blood decals this gib can place (1 per bounce until none remain). +} + +/* +============ +TakeDamage + +The damage is coming from inflictor, but get mad at attacker +This should be the only function that ever reduces health. +bitsDamageType indicates the type of damage sustained, ie: DMG_SHOCK + +Time-based damage: only occurs while the monster is within the trigger_hurt. +When a monster is poisoned via an arrow etc it takes all the poison damage at once. + + + +GLOBALS ASSUMED SET: g_iSkillLevel +============ +*/ +int CBaseMonster :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + float flTake; + Vector vecDir; + + if (!pev->takedamage) + return 0; + + if ( !IsAlive() ) + { + return DeadTakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); + } + + if ( pev->deadflag == DEAD_NO ) + { + // no pain sound during death animation. + PainSound();// "Ouch!" + } + + //!!!LATER - make armor consideration here! + flTake = flDamage; + + // set damage type sustained + m_bitsDamageType |= bitsDamageType; + + // grab the vector of the incoming attack. ( pretend that the inflictor is a little lower than it really is, so the body will tend to fly upward a bit). + vecDir = Vector( 0, 0, 0 ); + if (!FNullEnt( pevInflictor )) + { + CBaseEntity *pInflictor = CBaseEntity :: Instance( pevInflictor ); + if (pInflictor) + { + vecDir = ( pInflictor->Center() - Vector ( 0, 0, 10 ) - Center() ).Normalize(); + vecDir = g_vecAttackDir = vecDir.Normalize(); + } + } + + // add to the damage total for clients, which will be sent as a single + // message at the end of the frame + // todo: remove after combining shotgun blasts? + if ( IsPlayer() ) + { + if ( pevInflictor ) + pev->dmg_inflictor = ENT(pevInflictor); + + pev->dmg_take += flTake; + + // check for godmode or invincibility + if ( pev->flags & FL_GODMODE ) + { + return 0; + } + } + + // if this is a player, move him around! + if ( ( !FNullEnt( pevInflictor ) ) && (pev->movetype == MOVETYPE_WALK) && (!pevAttacker || pevAttacker->solid != SOLID_TRIGGER) ) + { + pev->velocity = pev->velocity + vecDir * -DamageForce( flDamage ); + } + + // do the damage + pev->health -= flTake; + + // HACKHACK Don't kill monsters in a script. Let them break their scripts first + if ( m_MonsterState == MONSTERSTATE_SCRIPT ) + { + SetConditions( bits_COND_LIGHT_DAMAGE ); + return 0; + } + + if ( pev->health <= 0 ) + { + g_pevLastInflictor = pevInflictor; + + if ( bitsDamageType & DMG_ALWAYSGIB ) + { + Killed( pevAttacker, GIB_ALWAYS ); + } + else if ( bitsDamageType & DMG_NEVERGIB ) + { + Killed( pevAttacker, GIB_NEVER ); + } + else + { + Killed( pevAttacker, GIB_NORMAL ); + } + + g_pevLastInflictor = NULL; + + return 0; + } + + // react to the damage (get mad) + if ( (pev->flags & FL_MONSTER) && !FNullEnt(pevAttacker) ) + { + if ( pevAttacker->flags & (FL_MONSTER | FL_CLIENT) ) + {// only if the attack was a monster or client! + + // enemy's last known position is somewhere down the vector that the attack came from. + if (pevInflictor) + { + if (m_hEnemy == NULL || pevInflictor == m_hEnemy->pev || !HasConditions(bits_COND_SEE_ENEMY)) + { + m_vecEnemyLKP = pevInflictor->origin; + } + } + else + { + m_vecEnemyLKP = pev->origin + ( g_vecAttackDir * 64 ); + } + + MakeIdealYaw( m_vecEnemyLKP ); + + // add pain to the conditions + // !!!HACKHACK - fudged for now. Do we want to have a virtual function to determine what is light and + // heavy damage per monster class? + if ( flDamage > 0 ) + { + SetConditions(bits_COND_LIGHT_DAMAGE); + } + + if ( flDamage >= 20 ) + { + SetConditions(bits_COND_HEAVY_DAMAGE); + } + } + } + + return 1; +} + +//========================================================= +// DeadTakeDamage - takedamage function called when a monster's +// corpse is damaged. +//========================================================= +int CBaseMonster :: DeadTakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecDir; + + // grab the vector of the incoming attack. ( pretend that the inflictor is a little lower than it really is, so the body will tend to fly upward a bit). + vecDir = Vector( 0, 0, 0 ); + if (!FNullEnt( pevInflictor )) + { + CBaseEntity *pInflictor = CBaseEntity :: Instance( pevInflictor ); + if (pInflictor) + { + vecDir = ( pInflictor->Center() - Vector ( 0, 0, 10 ) - Center() ).Normalize(); + vecDir = g_vecAttackDir = vecDir.Normalize(); + } + } + +#if 0// turn this back on when the bounding box issues are resolved. + + pev->flags &= ~FL_ONGROUND; + pev->origin.z += 1; + + // let the damage scoot the corpse around a bit. + if ( !FNullEnt(pevInflictor) && (pevAttacker->solid != SOLID_TRIGGER) ) + { + pev->velocity = pev->velocity + vecDir * -DamageForce( flDamage ); + } + +#endif + + // kill the corpse if enough damage was done to destroy the corpse and the damage is of a type that is allowed to destroy the corpse. + if ( bitsDamageType & DMG_GIB_CORPSE ) + { + if ( pev->health <= flDamage ) + { + pev->health = -50; + Killed( pevAttacker, GIB_ALWAYS ); + return 0; + } + // Accumulate corpse gibbing damage, so you can gib with multiple hits + pev->health -= flDamage * 0.1; + } + + return 1; +} + + +float CBaseMonster :: DamageForce( float damage ) +{ + float force = damage * ((32 * 32 * 72.0) / (pev->size.x * pev->size.y * pev->size.z)) * 5; + + if ( force > 1000.0) + { + force = 1000.0; + } + + return force; +} + +// +// RadiusDamage - this entity is exploding, or otherwise needs to inflict damage upon entities within a certain range. +// +// only damage ents that can clearly be seen by the explosion! + + +void RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, float flRadius, int iClassIgnore, int bitsDamageType ) +{ + CBaseEntity *pEntity = NULL; + TraceResult tr; + float flAdjustedDamage, falloff; + Vector vecSpot; + + if ( flRadius ) + falloff = flDamage / flRadius; + else + falloff = 1.0; + + int bInWater = (UTIL_PointContents ( vecSrc ) == CONTENTS_WATER); + + vecSrc.z += 1;// in case grenade is lying on the ground + + if ( !pevAttacker ) + pevAttacker = pevInflictor; + + // iterate on all entities in the vicinity. + while ((pEntity = UTIL_FindEntityInSphere( pEntity, vecSrc, flRadius )) != NULL) + { + if ( pEntity->pev->takedamage != DAMAGE_NO ) + { + // UNDONE: this should check a damage mask, not an ignore + if ( iClassIgnore != CLASS_NONE && pEntity->Classify() == iClassIgnore ) + {// houndeyes don't hurt other houndeyes with their attack + continue; + } + + // blast's don't tavel into or out of water + if (bInWater && pEntity->pev->waterlevel == 0) + continue; + if (!bInWater && pEntity->pev->waterlevel == 3) + continue; + + vecSpot = pEntity->BodyTarget( vecSrc ); + + UTIL_TraceLine ( vecSrc, vecSpot, dont_ignore_monsters, ENT(pevInflictor), &tr ); + + if ( tr.flFraction == 1.0 || tr.pHit == pEntity->edict() ) + {// the explosion can 'see' this entity, so hurt them! + if (tr.fStartSolid) + { + // if we're stuck inside them, fixup the position and distance + tr.vecEndPos = vecSrc; + tr.flFraction = 0.0; + } + + // decrease damage for an ent that's farther from the bomb. + flAdjustedDamage = ( vecSrc - tr.vecEndPos ).Length() * falloff; + flAdjustedDamage = flDamage - flAdjustedDamage; + + if ( flAdjustedDamage < 0 ) + { + flAdjustedDamage = 0; + } + + // ALERT( at_console, "hit %s\n", STRING( pEntity->pev->classname ) ); + if (tr.flFraction != 1.0) + { + ClearMultiDamage( ); + pEntity->TraceAttack( pevInflictor, flAdjustedDamage, (tr.vecEndPos - vecSrc).Normalize( ), &tr, bitsDamageType ); + ApplyMultiDamage( pevInflictor, pevAttacker ); + } + else + { + pEntity->TakeDamage ( pevInflictor, pevAttacker, flAdjustedDamage, bitsDamageType ); + } + } + } + } +} + + +void CBaseMonster :: RadiusDamage(entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) +{ + ::RadiusDamage( pev->origin, pevInflictor, pevAttacker, flDamage, flDamage * 2.5, iClassIgnore, bitsDamageType ); +} + + +void CBaseMonster :: RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) +{ + ::RadiusDamage( vecSrc, pevInflictor, pevAttacker, flDamage, flDamage * 2.5, iClassIgnore, bitsDamageType ); +} + +/* +//========================================================= +// TraceAttack +//========================================================= +void CBaseMonster::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + Vector vecOrigin = ptr->vecEndPos - vecDir * 4; + + ALERT ( at_console, "%d\n", ptr->iHitgroup ); + + + if ( pev->takedamage ) + { + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + + int blood = BloodColor(); + + if ( blood != DONT_BLEED ) + { + SpawnBlood(vecOrigin, blood, flDamage);// a little surface blood. + } + } +} +*/ + +void CBaseEntity :: TraceBleed( float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + if (BloodColor() == DONT_BLEED) + return; + + if (flDamage == 0) + return; + + if (! (bitsDamageType & (DMG_CRUSH | DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB | DMG_MORTAR))) + return; + + // make blood decal on the wall! + TraceResult Bloodtr; + Vector vecTraceDir; + float flNoise; + int cCount; + int i; + + if (flDamage < 10) + { + flNoise = 0.1; + cCount = 1; + } + else if (flDamage < 25) + { + flNoise = 0.2; + cCount = 2; + } + else + { + flNoise = 0.3; + cCount = 4; + } + + for ( i = 0 ; i < cCount ; i++ ) + { + vecTraceDir = vecDir * -1;// trace in the opposite direction the shot came from (the direction the shot is going) + + vecTraceDir.x += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.y += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.z += RANDOM_FLOAT( -flNoise, flNoise ); + + UTIL_TraceLine( ptr->vecEndPos, ptr->vecEndPos + vecTraceDir * -172, ignore_monsters, ENT(pev), &Bloodtr); + + if ( Bloodtr.flFraction != 1.0 ) + { + UTIL_BloodDecalTrace( &Bloodtr, BloodColor() ); + } + } +} + +//========================================================= +//========================================================= +void CBaseMonster :: MakeDamageBloodDecal ( int cCount, float flNoise, TraceResult *ptr, const Vector &vecDir ) +{ + // make blood decal on the wall! + TraceResult Bloodtr; + Vector vecTraceDir; + int i; + + if ( !IsAlive() ) + { + // dealing with a dead monster. + if ( pev->max_health <= 0 ) + { + // no blood decal for a monster that has already decalled its limit. + return; + } + else + { + pev->max_health--; + } + } + + for ( i = 0 ; i < cCount ; i++ ) + { + vecTraceDir = vecDir; + + vecTraceDir.x += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.y += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.z += RANDOM_FLOAT( -flNoise, flNoise ); + + UTIL_TraceLine( ptr->vecEndPos, ptr->vecEndPos + vecTraceDir * 172, ignore_monsters, ENT(pev), &Bloodtr); + +/* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + WRITE_COORD( ptr->vecEndPos.x ); + WRITE_COORD( ptr->vecEndPos.y ); + WRITE_COORD( ptr->vecEndPos.z ); + + WRITE_COORD( Bloodtr.vecEndPos.x ); + WRITE_COORD( Bloodtr.vecEndPos.y ); + WRITE_COORD( Bloodtr.vecEndPos.z ); + MESSAGE_END(); +*/ + + if ( Bloodtr.flFraction != 1.0 ) + { + UTIL_BloodDecalTrace( &Bloodtr, BloodColor() ); + } + } +} diff --git a/dmc/dlls/decals.h b/dmc/dlls/decals.h new file mode 100644 index 0000000..95fa44f --- /dev/null +++ b/dmc/dlls/decals.h @@ -0,0 +1,75 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef DECALS_H +#define DECALS_H + +// +// Dynamic Decals +// +enum decal_e +{ + DECAL_GUNSHOT1 = 0, + DECAL_GUNSHOT2, + DECAL_GUNSHOT3, + DECAL_GUNSHOT4, + DECAL_GUNSHOT5, + DECAL_LAMBDA1, + DECAL_LAMBDA2, + DECAL_LAMBDA3, + DECAL_LAMBDA4, + DECAL_LAMBDA5, + DECAL_LAMBDA6, + DECAL_SCORCH1, + DECAL_SCORCH2, + DECAL_BLOOD1, + DECAL_BLOOD2, + DECAL_BLOOD3, + DECAL_BLOOD4, + DECAL_BLOOD5, + DECAL_BLOOD6, + DECAL_YBLOOD1, + DECAL_YBLOOD2, + DECAL_YBLOOD3, + DECAL_YBLOOD4, + DECAL_YBLOOD5, + DECAL_YBLOOD6, + DECAL_GLASSBREAK1, + DECAL_GLASSBREAK2, + DECAL_GLASSBREAK3, + DECAL_BIGSHOT1, + DECAL_BIGSHOT2, + DECAL_BIGSHOT3, + DECAL_BIGSHOT4, + DECAL_BIGSHOT5, + DECAL_SPIT1, + DECAL_SPIT2, + DECAL_BPROOF1, // Bulletproof glass decal + DECAL_GARGSTOMP1, // Gargantua stomp crack + DECAL_SMALLSCORCH1, // Small scorch mark + DECAL_SMALLSCORCH2, // Small scorch mark + DECAL_SMALLSCORCH3, // Small scorch mark + DECAL_MOMMABIRTH, // Big momma birth splatter + DECAL_MOMMASPLAT, +}; + +typedef struct +{ + char *name; + int index; +} DLL_DECALLIST; + +extern DLL_DECALLIST gDecals[]; + +#endif // DECALS_H diff --git a/dmc/dlls/defaultai.cpp b/dmc/dlls/defaultai.cpp new file mode 100644 index 0000000..2b23124 --- /dev/null +++ b/dmc/dlls/defaultai.cpp @@ -0,0 +1,240 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Default behaviors. +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "defaultai.h" +#include "nodes.h" +#include "scripted.h" + +Schedule_t *CBaseMonster::m_scheduleList[] = +{ +}; + +Schedule_t *CBaseMonster::ScheduleFromName( const char *pName ) +{ + return ScheduleInList( pName, m_scheduleList, ARRAYSIZE(m_scheduleList) ); +} + + +Schedule_t *CBaseMonster :: ScheduleInList( const char *pName, Schedule_t **pList, int listCount ) +{ + int i; + + if ( !pName ) + { + ALERT( at_console, "%s set to unnamed schedule!\n", STRING(pev->classname) ); + return NULL; + } + + + for ( i = 0; i < listCount; i++ ) + { + if ( !pList[i]->pName ) + { + ALERT( at_console, "Unnamed schedule!\n" ); + continue; + } + if ( stricmp( pName, pList[i]->pName ) == 0 ) + return pList[i]; + } + return NULL; +} + +//========================================================= +// GetScheduleOfType - returns a pointer to one of the +// monster's available schedules of the indicated type. +//========================================================= +Schedule_t* CBaseMonster :: GetScheduleOfType ( int Type ) +{ +// ALERT ( at_console, "Sched Type:%d\n", Type ); + switch ( Type ) + { + // This is the schedule for scripted sequences AND scripted AI + case SCHED_AISCRIPT: + { + ASSERT( m_pCine != NULL ); + if ( !m_pCine ) + { + ALERT( at_aiconsole, "Script failed for %s\n", STRING(pev->classname) ); + CineCleanup(); + return GetScheduleOfType( SCHED_IDLE_STAND ); + } +// else +// ALERT( at_aiconsole, "Starting script %s for %s\n", STRING( m_pCine->m_iszPlay ), STRING(pev->classname) ); + + switch ( m_pCine->m_fMoveTo ) + { + case 0: + case 4: + return slWaitScript; + case 1: + return slWalkToScript; + case 2: + return slRunToScript; + case 5: + return slFaceScript; + } + break; + } + case SCHED_IDLE_STAND: + { + if ( RANDOM_LONG(0,14) == 0 && FCanActiveIdle() ) + { + return &slActiveIdle[ 0 ]; + } + + return &slIdleStand[ 0 ]; + } + case SCHED_IDLE_WALK: + { + return &slIdleWalk[ 0 ]; + } + case SCHED_WAIT_TRIGGER: + { + return &slIdleTrigger[ 0 ]; + } + case SCHED_WAKE_ANGRY: + { + return &slWakeAngry[ 0 ]; + } + case SCHED_ALERT_FACE: + { + return &slAlertFace[ 0 ]; + } + case SCHED_ALERT_STAND: + { + return &slAlertStand[ 0 ]; + } + case SCHED_COMBAT_STAND: + { + return &slCombatStand[ 0 ]; + } + case SCHED_COMBAT_FACE: + { + return &slCombatFace[ 0 ]; + } + case SCHED_CHASE_ENEMY: + { + return &slChaseEnemy[ 0 ]; + } + case SCHED_CHASE_ENEMY_FAILED: + { + return &slFail[ 0 ]; + } + case SCHED_SMALL_FLINCH: + { + return &slSmallFlinch[ 0 ]; + } + case SCHED_ALERT_SMALL_FLINCH: + { + return &slAlertSmallFlinch[ 0 ]; + } + case SCHED_RELOAD: + { + return &slReload[ 0 ]; + } + case SCHED_ARM_WEAPON: + { + return &slArmWeapon[ 0 ]; + } + case SCHED_STANDOFF: + { + return &slStandoff[ 0 ]; + } + case SCHED_RANGE_ATTACK1: + { + return &slRangeAttack1[ 0 ]; + } + case SCHED_RANGE_ATTACK2: + { + return &slRangeAttack2[ 0 ]; + } + case SCHED_MELEE_ATTACK1: + { + return &slPrimaryMeleeAttack[ 0 ]; + } + case SCHED_MELEE_ATTACK2: + { + return &slSecondaryMeleeAttack[ 0 ]; + } + case SCHED_SPECIAL_ATTACK1: + { + return &slSpecialAttack1[ 0 ]; + } + case SCHED_SPECIAL_ATTACK2: + { + return &slSpecialAttack2[ 0 ]; + } + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + { + return &slTakeCoverFromBestSound[ 0 ]; + } + case SCHED_TAKE_COVER_FROM_ENEMY: + { + return &slTakeCoverFromEnemy[ 0 ]; + } + case SCHED_COWER: + { + return &slCower[ 0 ]; + } + case SCHED_AMBUSH: + { + return &slAmbush[ 0 ]; + } + case SCHED_BARNACLE_VICTIM_GRAB: + { + return &slBarnacleVictimGrab[ 0 ]; + } + case SCHED_BARNACLE_VICTIM_CHOMP: + { + return &slBarnacleVictimChomp[ 0 ]; + } + case SCHED_INVESTIGATE_SOUND: + { + return &slInvestigateSound[ 0 ]; + } + case SCHED_DIE: + { + return &slDie[ 0 ]; + } + case SCHED_TAKE_COVER_FROM_ORIGIN: + { + return &slTakeCoverFromOrigin[ 0 ]; + } + case SCHED_VICTORY_DANCE: + { + return &slVictoryDance[ 0 ]; + } + case SCHED_FAIL: + { + return slFail; + } + default: + { + ALERT ( at_console, "GetScheduleOfType()\nNo CASE for Schedule Type %d!\n", Type ); + + return &slIdleStand[ 0 ]; + break; + } + } + + return NULL; +} diff --git a/dmc/dlls/defaultai.h b/dmc/dlls/defaultai.h new file mode 100644 index 0000000..652d108 --- /dev/null +++ b/dmc/dlls/defaultai.h @@ -0,0 +1,98 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef DEFAULTAI_H +#define DEFAULTAI_H + +//========================================================= +// Failed +//========================================================= +extern Schedule_t slFail[]; + +//========================================================= +// Idle Schedules +//========================================================= +extern Schedule_t slIdleStand[]; +extern Schedule_t slIdleTrigger[]; +extern Schedule_t slIdleWalk[]; + +//========================================================= +// Wake Schedules +//========================================================= +extern Schedule_t slWakeAngry[]; + +//========================================================= +// AlertTurn Schedules +//========================================================= +extern Schedule_t slAlertFace[]; + +//========================================================= +// AlertIdle Schedules +//========================================================= +extern Schedule_t slAlertStand[]; + +//========================================================= +// CombatIdle Schedule +//========================================================= +extern Schedule_t slCombatStand[]; + +//========================================================= +// CombatFace Schedule +//========================================================= +extern Schedule_t slCombatFace[]; + +//========================================================= +// reload schedule +//========================================================= +extern Schedule_t slReload[]; + +//========================================================= +// Attack Schedules +//========================================================= + +extern Schedule_t slRangeAttack1[]; +extern Schedule_t slRangeAttack2[]; + +extern Schedule_t slTakeCoverFromBestSound[]; + +// primary melee attack +extern Schedule_t slMeleeAttack[]; + +// Chase enemy schedule +extern Schedule_t slChaseEnemy[]; + +//========================================================= +// small flinch, used when a relatively minor bit of damage +// is inflicted. +//========================================================= +extern Schedule_t slSmallFlinch[]; + +//========================================================= +// Die! +//========================================================= +extern Schedule_t slDie[]; + +//========================================================= +// Universal Error Schedule +//========================================================= +extern Schedule_t slError[]; + +//========================================================= +// Scripted sequences +//========================================================= +extern Schedule_t slWalkToScript[]; +extern Schedule_t slRunToScript[]; +extern Schedule_t slWaitScript[]; + +#endif // DEFAULTAI_H diff --git a/dmc/dlls/dmc.def b/dmc/dlls/dmc.def new file mode 100644 index 0000000..b455ca1 --- /dev/null +++ b/dmc/dlls/dmc.def @@ -0,0 +1,5 @@ +LIBRARY dmc +EXPORTS + GiveFnptrsToDll @1 +SECTIONS + .data READ WRITE diff --git a/dmc/dlls/dmcgl.def b/dmc/dlls/dmcgl.def new file mode 100644 index 0000000..a02b87c --- /dev/null +++ b/dmc/dlls/dmcgl.def @@ -0,0 +1,15 @@ +LIBRARY dmcgl +EXPORTS + GiveFnptrsToDll @1 + GetEntityInterfaces @2 + SetChangeParms @3 + SetNewParms @4 + ClientKill @5 + PutClientInServer @6 + PlayerPreThink @7 + PlayerPostThink @8 + ClientConnect @9 + ClientDisconnect @10 + StartFrame @11 +SECTIONS + .data READ WRITE diff --git a/dmc/dlls/doors.cpp b/dmc/dlls/doors.cpp new file mode 100644 index 0000000..9de01f2 --- /dev/null +++ b/dmc/dlls/doors.cpp @@ -0,0 +1,1099 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== doors.cpp ======================================================== + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "doors.h" + + +extern void SetMovedir(entvars_t* ev); + +#define noiseMoving noise1 +#define noiseArrived noise2 + +class CBaseDoor : public CBaseToggle +{ +public: + void Spawn( void ); + void Precache( void ); + virtual void KeyValue( KeyValueData *pkvd ); + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void Blocked( CBaseEntity *pOther ); + + + virtual int ObjectCaps( void ) + { + if (pev->spawnflags & SF_ITEM_USE_ONLY) + return (CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_IMPULSE_USE; + else + return (CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION); + }; + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + virtual void SetToggleState( int state ); + + // used to selectivly override defaults + void EXPORT DoorTouch( CBaseEntity *pOther ); + + // local functions + int DoorActivate( ); + void EXPORT DoorGoUp( void ); + void EXPORT DoorGoDown( void ); + void EXPORT DoorHitTop( void ); + void EXPORT DoorHitBottom( void ); + + BYTE m_bHealthValue;// some doors are medi-kit doors, they give players health + + BYTE m_bMoveSnd; // sound a door makes while moving + BYTE m_bStopSnd; // sound a door makes when it stops + + locksound_t m_ls; // door lock sounds + + BYTE m_bLockedSound; // ordinals from entity selection + BYTE m_bLockedSentence; + BYTE m_bUnlockedSound; + BYTE m_bUnlockedSentence; + float m_fNextSoundPlay; + bool m_bIsReopening; // If the bIsReopening flag is set, the door's not fully shut, but it still wants to reopen + // because a player's standing in it's field. + bool m_bStoppedOpenSound; // TRUE once the original opening sound has been stopped + +private: + unsigned short m_usDoorGoUp; + unsigned short m_usDoorGoDown; + unsigned short m_usDoorHitTop; + unsigned short m_usDoorHitBottom; +}; + + +TYPEDESCRIPTION CBaseDoor::m_SaveData[] = +{ + DEFINE_FIELD( CBaseDoor, m_bHealthValue, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bMoveSnd, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bStopSnd, FIELD_CHARACTER ), + + DEFINE_FIELD( CBaseDoor, m_bLockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bLockedSentence, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bUnlockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bUnlockedSentence, FIELD_CHARACTER ), + +}; + +IMPLEMENT_SAVERESTORE( CBaseDoor, CBaseToggle ); + + +#define DOOR_SENTENCEWAIT 6 +#define DOOR_SOUNDWAIT 3 +#define BUTTON_SOUNDWAIT 0.5 + +// play door or button locked or unlocked sounds. +// pass in pointer to valid locksound struct. +// if flocked is true, play 'door is locked' sound, +// otherwise play 'door is unlocked' sound +// NOTE: this routine is shared by doors and buttons + +void PlayLockSounds(entvars_t *pev, locksound_t *pls, int flocked, int fbutton) +{ + // LOCKED SOUND + + // CONSIDER: consolidate the locksound_t struct (all entries are duplicates for lock/unlock) + // CONSIDER: and condense this code. + float flsoundwait; + + if (fbutton) + flsoundwait = BUTTON_SOUNDWAIT; + else + flsoundwait = DOOR_SOUNDWAIT; + + if (flocked) + { + int fplaysound = (pls->sLockedSound && gpGlobals->time > pls->flwaitSound); + int fplaysentence = (pls->sLockedSentence && !pls->bEOFLocked && gpGlobals->time > pls->flwaitSentence); + float fvol; + + if (fplaysound && fplaysentence) + fvol = 0.25; + else + fvol = 1.0; + + // if there is a locked sound, and we've debounced, play sound + if (fplaysound) + { + // play 'door locked' sound + EMIT_SOUND(ENT(pev), CHAN_ITEM, (char*)STRING(pls->sLockedSound), fvol, ATTN_NORM); + pls->flwaitSound = gpGlobals->time + flsoundwait; + } + + // if there is a sentence, we've not played all in list, and we've debounced, play sound + if (fplaysentence) + { + // play next 'door locked' sentence in group + int iprev = pls->iLockedSentence; + + pls->iLockedSentence = SENTENCEG_PlaySequentialSz(ENT(pev), STRING(pls->sLockedSentence), + 0.85, ATTN_NORM, 0, 100, pls->iLockedSentence, FALSE); + pls->iUnlockedSentence = 0; + + // make sure we don't keep calling last sentence in list + pls->bEOFLocked = (iprev == pls->iLockedSentence); + + pls->flwaitSentence = gpGlobals->time + DOOR_SENTENCEWAIT; + } + } + else + { + // UNLOCKED SOUND + + int fplaysound = (pls->sUnlockedSound && gpGlobals->time > pls->flwaitSound); + int fplaysentence = (pls->sUnlockedSentence && !pls->bEOFUnlocked && gpGlobals->time > pls->flwaitSentence); + float fvol; + + // if playing both sentence and sound, lower sound volume so we hear sentence + if (fplaysound && fplaysentence) + fvol = 0.25; + else + fvol = 1.0; + + // play 'door unlocked' sound if set + if (fplaysound) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, (char*)STRING(pls->sUnlockedSound), fvol, ATTN_NORM); + pls->flwaitSound = gpGlobals->time + flsoundwait; + } + + // play next 'door unlocked' sentence in group + if (fplaysentence) + { + int iprev = pls->iUnlockedSentence; + + pls->iUnlockedSentence = SENTENCEG_PlaySequentialSz(ENT(pev), STRING(pls->sUnlockedSentence), + 0.85, ATTN_NORM, 0, 100, pls->iUnlockedSentence, FALSE); + pls->iLockedSentence = 0; + + // make sure we don't keep calling last sentence in list + pls->bEOFUnlocked = (iprev == pls->iUnlockedSentence); + pls->flwaitSentence = gpGlobals->time + DOOR_SENTENCEWAIT; + } + } +} + +// +// Cache user-entity-field values until spawn is called. +// + +void CBaseDoor::KeyValue( KeyValueData *pkvd ) +{ + + if (FStrEq(pkvd->szKeyName, "skin"))//skin is used for content type + { + pev->skin = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "movesnd")) + { + m_bMoveSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "stopsnd")) + { + m_bStopSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "healthvalue")) + { + m_bHealthValue = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sound")) + { + m_bLockedSound = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sentence")) + { + m_bLockedSentence = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sound")) + { + m_bUnlockedSound = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sentence")) + { + m_bUnlockedSentence = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "WaveHeight")) + { + pev->scale = atof(pkvd->szValue) * (1.0/8.0); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +/*QUAKED func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK TOGGLE +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. + +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). + +"angle" determines the opening direction +"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" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (2 default) +"sounds" +0) no sound +1) stone +2) base +3) stone chain +4) screechy metal +*/ + +LINK_ENTITY_TO_CLASS( func_door, CBaseDoor ); +// +// func_water - same as a door. +// +LINK_ENTITY_TO_CLASS( func_water, CBaseDoor ); + + +void CBaseDoor::Spawn( ) +{ + Precache(); + SetMovedir (pev); + + if ( pev->skin == 0 ) + {//normal door + if ( FBitSet (pev->spawnflags, SF_DOOR_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + } + else + {// special contents + pev->solid = SOLID_NOT; + SetBits( pev->spawnflags, SF_DOOR_SILENT ); // water is silent for now + } + + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + if (pev->speed == 0) + pev->speed = 100; + + m_vecPosition1 = pev->origin; + // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big + m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); + ASSERTSZ(m_vecPosition1 != m_vecPosition2, "door start/end positions are equal"); + if ( FBitSet (pev->spawnflags, SF_DOOR_START_OPEN) ) + { // swap pos1 and pos2, put door at pos2 + UTIL_SetOrigin(pev, m_vecPosition2); + m_vecPosition2 = m_vecPosition1; + m_vecPosition1 = pev->origin; + } + + m_toggle_state = TS_AT_BOTTOM; + + m_fNextSoundPlay = 0; + m_bIsReopening = false; + m_bStoppedOpenSound = false; + // if the door is flagged for USE button activation only, use NULL touch function + if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + { + SetTouch ( NULL ); + } + else // touchable button + SetTouch( &CBaseDoor::DoorTouch ); +} + + +void CBaseDoor :: SetToggleState( int state ) +{ + if ( state == TS_AT_TOP ) + UTIL_SetOrigin( pev, m_vecPosition2 ); + else + UTIL_SetOrigin( pev, m_vecPosition1 ); +} + + +void CBaseDoor::Precache( void ) +{ + char *pszSound; + +// set the door's "in-motion" sound + switch (m_bMoveSnd) + { + case 0: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("doors/doormove1.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove1.wav"); + break; + case 2: + PRECACHE_SOUND ("doors/doormove2.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove2.wav"); + break; + case 3: + PRECACHE_SOUND ("doors/doormove3.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove3.wav"); + break; + case 4: + PRECACHE_SOUND ("doors/doormove4.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove4.wav"); + break; + case 5: + PRECACHE_SOUND ("doors/doormove5.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove5.wav"); + break; + case 6: + PRECACHE_SOUND ("doors/doormove6.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove6.wav"); + break; + case 7: + PRECACHE_SOUND ("doors/doormove7.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove7.wav"); + break; + case 8: + PRECACHE_SOUND ("doors/doormove8.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove8.wav"); + break; + case 9: + PRECACHE_SOUND ("doors/doormove9.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove9.wav"); + break; + case 10: + PRECACHE_SOUND ("doors/doormove10.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove10.wav"); + break; + default: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + } + +// set the door's 'reached destination' stop sound + switch (m_bStopSnd) + { + case 0: + pev->noiseArrived = ALLOC_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("doors/doorstop1.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop1.wav"); + break; + case 2: + PRECACHE_SOUND ("doors/doorstop2.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop2.wav"); + break; + case 3: + PRECACHE_SOUND ("doors/doorstop3.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop3.wav"); + break; + case 4: + PRECACHE_SOUND ("doors/doorstop4.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop4.wav"); + break; + case 5: + PRECACHE_SOUND ("doors/doorstop5.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop5.wav"); + break; + case 6: + PRECACHE_SOUND ("doors/doorstop6.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop6.wav"); + break; + case 7: + PRECACHE_SOUND ("doors/doorstop7.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop7.wav"); + break; + case 8: + PRECACHE_SOUND ("doors/doorstop8.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop8.wav"); + break; + default: + pev->noiseArrived = ALLOC_STRING("common/null.wav"); + break; + } + + // get door button sounds, for doors which are directly 'touched' to open + + if (m_bLockedSound) + { + pszSound = ButtonSound( (int)m_bLockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sLockedSound = ALLOC_STRING(pszSound); + } + + if (m_bUnlockedSound) + { + pszSound = ButtonSound( (int)m_bUnlockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sUnlockedSound = ALLOC_STRING(pszSound); + } + + // get sentence group names, for doors which are directly 'touched' to open + + switch (m_bLockedSentence) + { + case 1: m_ls.sLockedSentence = ALLOC_STRING("NA"); break; // access denied + case 2: m_ls.sLockedSentence = ALLOC_STRING("ND"); break; // security lockout + case 3: m_ls.sLockedSentence = ALLOC_STRING("NF"); break; // blast door + case 4: m_ls.sLockedSentence = ALLOC_STRING("NFIRE"); break; // fire door + case 5: m_ls.sLockedSentence = ALLOC_STRING("NCHEM"); break; // chemical door + case 6: m_ls.sLockedSentence = ALLOC_STRING("NRAD"); break; // radiation door + case 7: m_ls.sLockedSentence = ALLOC_STRING("NCON"); break; // gen containment + case 8: m_ls.sLockedSentence = ALLOC_STRING("NH"); break; // maintenance door + case 9: m_ls.sLockedSentence = ALLOC_STRING("NG"); break; // broken door + + default: m_ls.sLockedSentence = 0; break; + } + + switch (m_bUnlockedSentence) + { + case 1: m_ls.sUnlockedSentence = ALLOC_STRING("EA"); break; // access granted + case 2: m_ls.sUnlockedSentence = ALLOC_STRING("ED"); break; // security door + case 3: m_ls.sUnlockedSentence = ALLOC_STRING("EF"); break; // blast door + case 4: m_ls.sUnlockedSentence = ALLOC_STRING("EFIRE"); break; // fire door + case 5: m_ls.sUnlockedSentence = ALLOC_STRING("ECHEM"); break; // chemical door + case 6: m_ls.sUnlockedSentence = ALLOC_STRING("ERAD"); break; // radiation door + case 7: m_ls.sUnlockedSentence = ALLOC_STRING("ECON"); break; // gen containment + case 8: m_ls.sUnlockedSentence = ALLOC_STRING("EH"); break; // maintenance door + + default: m_ls.sUnlockedSentence = 0; break; + } + m_usDoorGoUp = PRECACHE_EVENT( 1, "events/door/doorgoup.sc" ); + m_usDoorGoDown = PRECACHE_EVENT( 1, "events/door/doorgodown.sc" ); + m_usDoorHitTop = PRECACHE_EVENT( 1, "events/door/doorhittop.sc" ); + m_usDoorHitBottom = PRECACHE_EVENT( 1, "events/door/doorhitbottom.sc" ); +} + +// +// Doors not tied to anything (e.g. button, another door) can be touched, to make them activate. +// +void CBaseDoor::DoorTouch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + + // Ignore touches by anything but players + if (!FClassnameIs(pevToucher, "player")) + return; + + // If door has master, and it's not ready to trigger, + // play 'locked' sound + + if (m_sMaster && !UTIL_IsMasterTriggered(m_sMaster, pOther)) + PlayLockSounds(pev, &m_ls, TRUE, FALSE); + + // If door is somebody's target, then touching does nothing. + // You have to activate the owner (e.g. button). + + if (!FStringNull(pev->targetname)) + { + // play locked sound + PlayLockSounds(pev, &m_ls, TRUE, FALSE); + return; + } + + m_hActivator = pOther;// remember who activated the door + + if (DoorActivate( )) + SetTouch( NULL ); // Temporarily disable the touch function, until movement is finished. +} + + +// +// Used by SUB_UseTargets, when a door is the target of a button. +// +void CBaseDoor::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_hActivator = pActivator; + // if not ready to be used, ignore "use" command. + if (m_toggle_state == TS_AT_BOTTOM || FBitSet(pev->spawnflags, SF_DOOR_NO_AUTO_RETURN) && m_toggle_state == TS_AT_TOP) + DoorActivate(); +} + +// +// Causes the door to "do its thing", i.e. start moving, and cascade activation. +// +int CBaseDoor::DoorActivate( ) +{ + if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + return 0; + + if (FBitSet(pev->spawnflags, SF_DOOR_NO_AUTO_RETURN) && m_toggle_state == TS_AT_TOP) + {// door should close + DoorGoDown(); + } + else + {// door should open + + if ( m_hActivator != NULL && m_hActivator->IsPlayer() ) + {// give health if player opened the door (medikit) + // VARS( m_eoActivator )->health += m_bHealthValue; + + m_hActivator->TakeHealth( m_bHealthValue, DMG_GENERIC ); + + } + + // play door unlock sounds + if (!m_bIsReopening) + { + PlayLockSounds(pev, &m_ls, FALSE, FALSE); + } + DoorGoUp(); + } + + return 1; +} + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); + +// +// Starts the door going to its "up" position (simply ToggleData->vecPosition2). +// +void CBaseDoor::DoorGoUp( void ) +{ + entvars_t *pevActivator; + + // It could be going-down, if blocked. + ASSERT( m_bIsReopening == true || (m_toggle_state == TS_AT_BOTTOM || m_toggle_state == TS_GOING_DOWN) ); + + // emit door moving and stop sounds on CHAN_STATIC so that the multicast doesn't + // filter them out and leave a client stuck with looping door sounds! + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) && m_bIsReopening == false ) + { + // don't play sounds too often + if ( m_fNextSoundPlay < gpGlobals->time ) + { + Vector vecCenter( Center() ); + float *pCenter = (float *)&vecCenter; + PLAYBACK_EVENT_FULL( FEV_RELIABLE, NULL, m_usDoorGoUp, 0.0, pCenter, (float *)&g_vecZero, 0.0, 0.0, ( m_bMoveSnd << 8 ) | ( m_bStopSnd & 0xff ), 0, 0, 0 ); +#if defined ( OLD_SOUNDS ) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving) ); + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving), 1, ATTN_NORM); +#endif + } + } + + m_toggle_state = TS_GOING_UP; + + SetMoveDone( &CBaseDoor::DoorHitTop ); + if ( FClassnameIs(pev, "func_door_rotating")) // !!! BUGBUG Triggered doors don't work with this yet + { + float sign = 1.0; + + if ( m_hActivator != NULL ) + { + pevActivator = m_hActivator->pev; + + if ( !FBitSet( pev->spawnflags, SF_DOOR_ONEWAY ) && pev->movedir.y ) // Y axis rotation, move away from the player + { + Vector vec = pevActivator->origin - pev->origin; + Vector angles = pevActivator->angles; + angles.x = 0; + angles.z = 0; + UTIL_MakeVectors (angles); + // Vector vnext = (pevToucher->origin + (pevToucher->velocity * 10)) - pev->origin; + UTIL_MakeVectors ( pevActivator->angles ); + Vector vnext = (pevActivator->origin + (gpGlobals->v_forward * 10)) - pev->origin; + if ( (vec.x*vnext.y - vec.y*vnext.x) < 0 ) + sign = -1.0; + } + } + AngularMove(m_vecAngle2*sign, pev->speed); + } + else + LinearMove(m_vecPosition2, pev->speed); +} + + +// +// The door has reached the "up" position. Either go back down, or wait for another activation. +// +void CBaseDoor::DoorHitTop( void ) +{ + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + { + // don't play sounds too often + if ( (( m_fNextSoundPlay < gpGlobals->time ) && !m_bIsReopening) || (!m_bStoppedOpenSound) ) + { + m_bStoppedOpenSound = true; + + Vector vecCenter( Center() ); + float *pCenter = (float *)&vecCenter; + + PLAYBACK_EVENT_FULL( FEV_RELIABLE, NULL, m_usDoorHitTop, 0.0, pCenter, (float *)&g_vecZero, 0.0, 0.0, ( m_bMoveSnd << 8 ) | ( m_bStopSnd & 0xff ), 0, 0, 0 ); +#if defined ( OLD_SOUNDS ) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving) ); + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseArrived), 1, ATTN_NORM); +#endif + } + } + + ASSERT(m_toggle_state == TS_GOING_UP); + m_toggle_state = TS_AT_TOP; + m_bIsReopening = false; + + // toggle-doors don't come down automatically, they wait for refire. + if (FBitSet(pev->spawnflags, SF_DOOR_NO_AUTO_RETURN)) + { + // Re-instate touch method, movement is complete + if ( !FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + SetTouch( &CBaseDoor::DoorTouch ); + } + else + { + // In flWait seconds, DoorGoDown will fire, unless wait is -1, then door stays open + pev->nextthink = pev->ltime + m_flWait; + SetThink( &CBaseDoor::DoorGoDown ); + + if ( m_flWait == -1 ) + { + pev->nextthink = -1; + } + } + + // Fire the close target (if startopen is set, then "top" is closed) - netname is the close target + if ( pev->netname && (pev->spawnflags & SF_DOOR_START_OPEN) ) + FireTargets( STRING(pev->netname), m_hActivator, this, USE_TOGGLE, 0 ); + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); // this isn't finished +} + + +// +// Starts the door going to its "down" position (simply ToggleData->vecPosition1). +// +void CBaseDoor::DoorGoDown( void ) +{ + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + { + // don't play sounds too often + if ( m_fNextSoundPlay < gpGlobals->time ) + { + Vector vecCenter( Center() ); + float *pCenter = (float *)&vecCenter; + + PLAYBACK_EVENT_FULL( FEV_RELIABLE, NULL, m_usDoorGoDown, 0.0, pCenter, (float *)&g_vecZero, 0.0, 0.0, ( m_bMoveSnd << 8 ) | ( m_bStopSnd & 0xff ), 0, 0, 0 ); +#if defined ( OLD_SOUNDS ) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving) ); + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving), 1, ATTN_NORM); +#endif + } + } + +#ifdef DOOR_ASSERT + ASSERT(m_toggle_state == TS_AT_TOP); +#endif // DOOR_ASSERT + m_toggle_state = TS_GOING_DOWN; + + SetMoveDone( &CBaseDoor::DoorHitBottom ); + if ( FClassnameIs(pev, "func_door_rotating"))//rotating door + AngularMove( m_vecAngle1, pev->speed); + else + LinearMove( m_vecPosition1, pev->speed); +} + +// +// The door has reached the "down" position. Back to quiescence. +// +void CBaseDoor::DoorHitBottom( void ) +{ + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + { + // don't play sounds too often + if ( m_fNextSoundPlay < gpGlobals->time ) + { + Vector vecCenter( Center() ); + float *pCenter = (float *)&vecCenter; + PLAYBACK_EVENT_FULL( FEV_RELIABLE, NULL, m_usDoorHitBottom, 0.0, pCenter, (float *)&g_vecZero, 0.0, 0.0, ( m_bMoveSnd << 8 ) | ( m_bStopSnd & 0xff ), 0, 0, 0 ); +#if defined ( OLD_SOUNDS ) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving) ); + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseArrived), 1, ATTN_NORM); +#endif + } + } + + ASSERT(m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_AT_BOTTOM; + + // Re-instate touch method, cycle is complete + if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + {// use only door + SetTouch ( NULL ); + } + else // touchable door + SetTouch( &CBaseDoor::DoorTouch ); + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); // this isn't finished + + // Fire the close target (if startopen is set, then "top" is closed) - netname is the close target + if ( pev->netname && !(pev->spawnflags & SF_DOOR_START_OPEN) ) + FireTargets( STRING(pev->netname), m_hActivator, this, USE_TOGGLE, 0 ); +} + +void CBaseDoor::Blocked( CBaseEntity *pOther ) +{ + edict_t *pentTarget = NULL; + CBaseDoor *pDoor = NULL; + + + // Hurt the blocker a little. + if ( pev->dmg ) + pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH ); + + // if a door has a negative wait, it would never come back if blocked, + // so let it just squash the object to death real fast + + if (m_flWait >= 0) + { + if (m_toggle_state == TS_GOING_DOWN) + { + DoorGoUp(); + } + else + { + DoorGoDown(); + } + } + + // Don't play blocked sounds too often + if ( m_fNextSoundPlay <= gpGlobals->time ) + { + m_fNextSoundPlay = gpGlobals->time + 0.3; + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving) ); + } + + // Block all door pieces with the same targetname here. + if ( !FStringNull ( pev->targetname ) ) + { + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->targetname)); + + if ( VARS( pentTarget ) != pev ) + { + if (FNullEnt(pentTarget)) + break; + + if ( FClassnameIs ( pentTarget, "func_door" ) || FClassnameIs ( pentTarget, "func_door_rotating" ) ) + { + + pDoor = GetClassPtr( (CBaseDoor *) VARS(pentTarget) ); + + if ( pDoor->m_flWait >= 0) + { + if (pDoor->pev->velocity == pev->velocity && pDoor->pev->avelocity == pev->velocity) + { + // this is the most hacked, evil, bastardized thing I've ever seen. kjb + if ( FClassnameIs ( pentTarget, "func_door" ) ) + {// set origin to realign normal doors + pDoor->pev->origin = pev->origin; + pDoor->pev->velocity = g_vecZero;// stop! + } + else + {// set angles to realign rotating doors + pDoor->pev->angles = pev->angles; + pDoor->pev->avelocity = g_vecZero; + } + } + + if ( pDoor->m_toggle_state == TS_GOING_DOWN) + pDoor->DoorGoUp(); + else + pDoor->DoorGoDown(); + } + } + } + } + } +} + + +/*QUAKED FuncRotDoorSpawn (0 .5 .8) ? START_OPEN REVERSE +DOOR_DONT_LINK TOGGLE X_AXIS Y_AXIS +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. + +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). + +You need to have an origin brush as part of this entity. The +center of that brush will be +the point around which it is rotated. It will rotate around the Z +axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"distance" is how many degrees the door will be rotated. +"speed" determines how fast the door moves; default value is 100. + +REVERSE will cause the door to rotate in the opposite direction. + +"angle" determines the opening direction +"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" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"dmg" damage to inflict when blocked (2 default) +"sounds" +0) no sound +1) stone +2) base +3) stone chain +4) screechy metal +*/ +class CRotDoor : public CBaseDoor +{ +public: + void Spawn( void ); + virtual void SetToggleState( int state ); +}; + +LINK_ENTITY_TO_CLASS( func_door_rotating, CRotDoor ); + + +void CRotDoor::Spawn( void ) +{ + Precache(); + // set the axis of rotation + CBaseToggle::AxisDir( pev ); + + // check for clockwise rotation + if ( FBitSet (pev->spawnflags, SF_DOOR_ROTATE_BACKWARDS) ) + pev->movedir = pev->movedir * -1; + + //m_flWait = 2; who the hell did this? (sjb) + m_vecAngle1 = pev->angles; + m_vecAngle2 = pev->angles + pev->movedir * m_flMoveDistance; + + ASSERTSZ(m_vecAngle1 != m_vecAngle2, "rotating door start/end positions are equal"); + + if ( FBitSet (pev->spawnflags, SF_DOOR_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + if (pev->speed == 0) + pev->speed = 100; + +// DOOR_START_OPEN is to allow an entity to be lighted in the closed position +// but spawn in the open position + if ( FBitSet (pev->spawnflags, SF_DOOR_START_OPEN) ) + { // swap pos1 and pos2, put door at pos2, invert movement direction + pev->angles = m_vecAngle2; + Vector vecSav = m_vecAngle1; + m_vecAngle2 = m_vecAngle1; + m_vecAngle1 = vecSav; + pev->movedir = pev->movedir * -1; + } + + m_toggle_state = TS_AT_BOTTOM; + + if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + { + SetTouch ( NULL ); + } + else // touchable button + SetTouch( &CRotDoor::DoorTouch ); +} + + +void CRotDoor :: SetToggleState( int state ) +{ + if ( state == TS_AT_TOP ) + pev->angles = m_vecAngle2; + else + pev->angles = m_vecAngle1; + + UTIL_SetOrigin( pev, pev->origin ); +} + + +class CMomentaryDoor : public CBaseToggle +{ +public: + void Spawn( void ); + void Precache( void ); + + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + BYTE m_bMoveSnd; // sound a door makes while moving +}; + +LINK_ENTITY_TO_CLASS( momentary_door, CMomentaryDoor ); + +TYPEDESCRIPTION CMomentaryDoor::m_SaveData[] = +{ + DEFINE_FIELD( CMomentaryDoor, m_bMoveSnd, FIELD_CHARACTER ), +}; + +IMPLEMENT_SAVERESTORE( CMomentaryDoor, CBaseToggle ); + +void CMomentaryDoor::Spawn( void ) +{ + SetMovedir (pev); + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + if (pev->speed == 0) + pev->speed = 100; + if (pev->dmg == 0) + pev->dmg = 2; + + m_vecPosition1 = pev->origin; + // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big + m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); + ASSERTSZ(m_vecPosition1 != m_vecPosition2, "door start/end positions are equal"); + + if ( FBitSet (pev->spawnflags, SF_DOOR_START_OPEN) ) + { // swap pos1 and pos2, put door at pos2 + UTIL_SetOrigin(pev, m_vecPosition2); + m_vecPosition2 = m_vecPosition1; + m_vecPosition1 = pev->origin; + } + SetTouch( NULL ); + + Precache(); +} + +void CMomentaryDoor::Precache( void ) +{ + +// set the door's "in-motion" sound + switch (m_bMoveSnd) + { + case 0: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("doors/doormove1.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove1.wav"); + break; + case 2: + PRECACHE_SOUND ("doors/doormove2.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove2.wav"); + break; + case 3: + PRECACHE_SOUND ("doors/doormove3.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove3.wav"); + break; + case 4: + PRECACHE_SOUND ("doors/doormove4.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove4.wav"); + break; + case 5: + PRECACHE_SOUND ("doors/doormove5.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove5.wav"); + break; + case 6: + PRECACHE_SOUND ("doors/doormove6.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove6.wav"); + break; + case 7: + PRECACHE_SOUND ("doors/doormove7.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove7.wav"); + break; + case 8: + PRECACHE_SOUND ("doors/doormove8.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove8.wav"); + break; + default: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + } +} + +void CMomentaryDoor::KeyValue( KeyValueData *pkvd ) +{ + + if (FStrEq(pkvd->szKeyName, "movesnd")) + { + m_bMoveSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "stopsnd")) + { +// m_bStopSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "healthvalue")) + { +// m_bHealthValue = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CMomentaryDoor::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( useType != USE_SET ) // Momentary buttons will pass down a float in here + return; + + if ( value > 1.0 ) + value = 1.0; + Vector move = m_vecPosition1 + (value * (m_vecPosition2 - m_vecPosition1)); + + Vector delta = move - pev->origin; + float speed = delta.Length() * 10; + + if ( speed != 0 ) + { + // This entity only thinks when it moves, so if it's thinking, it's in the process of moving + // play the sound when it starts moving + if ( pev->nextthink < pev->ltime || pev->nextthink == 0 ) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving), 1, ATTN_NORM); + + LinearMove( move, speed ); + } + +} diff --git a/dmc/dlls/doors.h b/dmc/dlls/doors.h new file mode 100644 index 0000000..8008861 --- /dev/null +++ b/dmc/dlls/doors.h @@ -0,0 +1,33 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef DOORS_H +#define DOORS_H + +// doors +#define SF_DOOR_ROTATE_Y 0 +#define SF_DOOR_START_OPEN 1 +#define SF_DOOR_ROTATE_BACKWARDS 2 +#define SF_DOOR_PASSABLE 8 +#define SF_DOOR_ONEWAY 16 +#define SF_DOOR_NO_AUTO_RETURN 32 +#define SF_DOOR_ROTATE_Z 64 +#define SF_DOOR_ROTATE_X 128 +#define SF_DOOR_USE_ONLY 256 // door must be opened by player's use button. +#define SF_DOOR_NOMONSTERS 512 // Monster can't open +#define SF_DOOR_SILENT 0x80000000 + + + +#endif //DOORS_H diff --git a/dmc/dlls/effects.cpp b/dmc/dlls/effects.cpp new file mode 100644 index 0000000..76fbc65 --- /dev/null +++ b/dmc/dlls/effects.cpp @@ -0,0 +1,2268 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "customentity.h" +#include "effects.h" +#include "weapons.h" +#include "decals.h" +#include "func_break.h" +#include "shake.h" + +#define SF_GIBSHOOTER_REPEATABLE 1 // allows a gibshooter to be refired + +#define SF_FUNNEL_REVERSE 1 // funnel effect repels particles instead of attracting them. + + +// Lightning target, just alias landmark +LINK_ENTITY_TO_CLASS( info_target, CPointEntity ); + + +class CBubbling : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + void EXPORT FizzThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + static TYPEDESCRIPTION m_SaveData[]; + + int m_density; + int m_frequency; + int m_bubbleModel; + int m_state; +}; + +LINK_ENTITY_TO_CLASS( env_bubbles, CBubbling ); + +TYPEDESCRIPTION CBubbling::m_SaveData[] = +{ + DEFINE_FIELD( CBubbling, m_density, FIELD_INTEGER ), + DEFINE_FIELD( CBubbling, m_frequency, FIELD_INTEGER ), + DEFINE_FIELD( CBubbling, m_state, FIELD_INTEGER ), + // Let spawn restore this! + // DEFINE_FIELD( CBubbling, m_bubbleModel, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CBubbling, CBaseEntity ); + + +#define SF_BUBBLES_STARTOFF 0x0001 + +void CBubbling::Spawn( void ) +{ + Precache( ); + SET_MODEL( ENT(pev), STRING(pev->model) ); // Set size + + pev->solid = SOLID_NOT; // Remove model & collisions + pev->renderamt = 0; // The engine won't draw this model if this is set to 0 and blending is on + pev->rendermode = kRenderTransTexture; + int speed = pev->speed > 0 ? pev->speed : -pev->speed; + + // HACKHACK!!! - Speed in rendercolor + pev->rendercolor.x = speed >> 8; + pev->rendercolor.y = speed & 255; + pev->rendercolor.z = (pev->speed < 0) ? 1 : 0; + + + if ( !(pev->spawnflags & SF_BUBBLES_STARTOFF) ) + { + SetThink( &CBubbling::FizzThink ); + pev->nextthink = gpGlobals->time + 2.0; + m_state = 1; + } + else + m_state = 0; +} + +void CBubbling::Precache( void ) +{ + m_bubbleModel = PRECACHE_MODEL("sprites/bubble.spr"); // Precache bubble sprite +} + + +void CBubbling::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( ShouldToggle( useType, m_state ) ) + m_state = !m_state; + + if ( m_state ) + { + SetThink( & CBubbling::FizzThink ); + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + SetThink( NULL ); + pev->nextthink = 0; + } +} + + +void CBubbling::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "density")) + { + m_density = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "frequency")) + { + m_frequency = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "current")) + { + pev->speed = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +void CBubbling::FizzThink( void ) +{ + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, VecBModelOrigin(pev) ); + WRITE_BYTE( TE_FIZZ ); + WRITE_SHORT( (short)ENTINDEX( edict() ) ); + WRITE_SHORT( (short)m_bubbleModel ); + WRITE_BYTE( m_density ); + MESSAGE_END(); + + if ( m_frequency > 19 ) + pev->nextthink = gpGlobals->time + 0.5; + else + pev->nextthink = gpGlobals->time + 2.5 - (0.1 * m_frequency); +} + +// -------------------------------------------------- +// +// Beams +// +// -------------------------------------------------- + +LINK_ENTITY_TO_CLASS( beam, CBeam ); + +void CBeam::Spawn( void ) +{ + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); +} + +void CBeam::Precache( void ) +{ + if ( pev->owner ) + SetStartEntity( ENTINDEX( pev->owner ) ); + if ( pev->aiment ) + SetEndEntity( ENTINDEX( pev->aiment ) ); +} + +void CBeam::SetStartEntity( int entityIndex ) +{ + pev->sequence = (entityIndex & 0x0FFF) | ((pev->sequence&0xF000)<<12); + pev->owner = g_engfuncs.pfnPEntityOfEntIndex( entityIndex ); +} + +void CBeam::SetEndEntity( int entityIndex ) +{ + pev->skin = (entityIndex & 0x0FFF) | ((pev->skin&0xF000)<<12); + pev->aiment = g_engfuncs.pfnPEntityOfEntIndex( entityIndex ); +} + + +// These don't take attachments into account +const Vector &CBeam::GetStartPos( void ) +{ + if ( GetType() == BEAM_ENTS ) + { + edict_t *pent = g_engfuncs.pfnPEntityOfEntIndex( GetStartEntity() ); + return pent->v.origin; + } + return pev->origin; +} + + +const Vector &CBeam::GetEndPos( void ) +{ + int type = GetType(); + if ( type == BEAM_POINTS || type == BEAM_HOSE ) + { + return pev->angles; + } + + edict_t *pent = g_engfuncs.pfnPEntityOfEntIndex( GetEndEntity() ); + if ( pent ) + return pent->v.origin; + return pev->angles; +} + + +CBeam *CBeam::BeamCreate( const char *pSpriteName, int width ) +{ + // Create a new entity with CBeam private data + CBeam *pBeam = GetClassPtr( (CBeam *)NULL ); + pBeam->pev->classname = MAKE_STRING("beam"); + + pBeam->BeamInit( pSpriteName, width ); + + return pBeam; +} + + +void CBeam::BeamInit( const char *pSpriteName, int width ) +{ + pev->flags |= FL_CUSTOMENTITY; + SetColor( 255, 255, 255 ); + SetBrightness( 255 ); + SetNoise( 0 ); + SetFrame( 0 ); + SetScrollRate( 0 ); + pev->model = MAKE_STRING( pSpriteName ); + SetTexture( PRECACHE_MODEL( (char *)pSpriteName ) ); + SetWidth( width ); + pev->skin = 0; + pev->sequence = 0; + pev->rendermode = 0; +} + + +void CBeam::PointsInit( const Vector &start, const Vector &end ) +{ + SetType( BEAM_POINTS ); + SetStartPos( start ); + SetEndPos( end ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CBeam::HoseInit( const Vector &start, const Vector &direction ) +{ + SetType( BEAM_HOSE ); + SetStartPos( start ); + SetEndPos( direction ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CBeam::PointEntInit( const Vector &start, int endIndex ) +{ + SetType( BEAM_ENTPOINT ); + SetStartPos( start ); + SetEndEntity( endIndex ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + +void CBeam::EntsInit( int startIndex, int endIndex ) +{ + SetType( BEAM_ENTS ); + SetStartEntity( startIndex ); + SetEndEntity( endIndex ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CBeam::RelinkBeam( void ) +{ + const Vector &startPos = GetStartPos(), &endPos = GetEndPos(); + + pev->mins.x = min( startPos.x, endPos.x ); + pev->mins.y = min( startPos.y, endPos.y ); + pev->mins.z = min( startPos.z, endPos.z ); + pev->maxs.x = max( startPos.x, endPos.x ); + pev->maxs.y = max( startPos.y, endPos.y ); + pev->maxs.z = max( startPos.z, endPos.z ); + pev->mins = pev->mins - pev->origin; + pev->maxs = pev->maxs - pev->origin; + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); +} + +#if 0 +void CBeam::SetObjectCollisionBox( void ) +{ + const Vector &startPos = GetStartPos(), &endPos = GetEndPos(); + + pev->absmin.x = min( startPos.x, endPos.x ); + pev->absmin.y = min( startPos.y, endPos.y ); + pev->absmin.z = min( startPos.z, endPos.z ); + pev->absmax.x = max( startPos.x, endPos.x ); + pev->absmax.y = max( startPos.y, endPos.y ); + pev->absmax.z = max( startPos.z, endPos.z ); +} +#endif + + +void CBeam::TriggerTouch( CBaseEntity *pOther ) +{ + if ( pOther->pev->flags & (FL_CLIENT | FL_MONSTER) ) + { + if ( pev->owner ) + { + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + pOwner->Use( pOther, this, USE_TOGGLE, 0 ); + } + ALERT( at_console, "Firing targets!!!\n" ); + } +} + + +CBaseEntity *CBeam::RandomTargetname( const char *szName ) +{ + int total = 0; + + CBaseEntity *pEntity = NULL; + CBaseEntity *pNewEntity = NULL; + while ((pNewEntity = UTIL_FindEntityByTargetname( pNewEntity, szName )) != NULL) + { + total++; + if (RANDOM_LONG(0,total-1) < 1) + pEntity = pNewEntity; + } + return pEntity; +} + + +void CBeam::DoSparks( const Vector &start, const Vector &end ) +{ + if ( pev->spawnflags & (SF_BEAM_SPARKSTART|SF_BEAM_SPARKEND) ) + { + if ( pev->spawnflags & SF_BEAM_SPARKSTART ) + { + UTIL_Sparks( start ); + } + if ( pev->spawnflags & SF_BEAM_SPARKEND ) + { + UTIL_Sparks( end ); + } + } +} + + +class CLightning : public CBeam +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void Activate( void ); + + void EXPORT StrikeThink( void ); + void EXPORT DamageThink( void ); + void RandomArea( void ); + void RandomPoint( Vector &vecSrc ); + void Zap( const Vector &vecSrc, const Vector &vecDest ); + void EXPORT StrikeUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + inline BOOL ServerSide( void ) + { + if ( m_life == 0 && !(pev->spawnflags & SF_BEAM_RING) ) + return TRUE; + return FALSE; + } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void BeamUpdateVars( void ); + + int m_active; + int m_iszStartEntity; + int m_iszEndEntity; + float m_life; + int m_boltWidth; + int m_noiseAmplitude; + int m_brightness; + int m_speed; + float m_restrike; + int m_spriteTexture; + int m_iszSpriteName; + int m_frameStart; + + float m_radius; +}; + +LINK_ENTITY_TO_CLASS( env_lightning, CLightning ); +LINK_ENTITY_TO_CLASS( env_beam, CLightning ); + +// UNDONE: Jay -- This is only a test +#if _DEBUG +class CTripBeam : public CLightning +{ + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( trip_beam, CTripBeam ); + +void CTripBeam::Spawn( void ) +{ + CLightning::Spawn(); + SetTouch( TriggerTouch ); + pev->solid = SOLID_TRIGGER; + RelinkBeam(); +} +#endif + + + +TYPEDESCRIPTION CLightning::m_SaveData[] = +{ + DEFINE_FIELD( CLightning, m_active, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_iszStartEntity, FIELD_STRING ), + DEFINE_FIELD( CLightning, m_iszEndEntity, FIELD_STRING ), + DEFINE_FIELD( CLightning, m_life, FIELD_FLOAT ), + DEFINE_FIELD( CLightning, m_boltWidth, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_noiseAmplitude, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_brightness, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_speed, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_restrike, FIELD_FLOAT ), + DEFINE_FIELD( CLightning, m_spriteTexture, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_iszSpriteName, FIELD_STRING ), + DEFINE_FIELD( CLightning, m_frameStart, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_radius, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CLightning, CBeam ); + + +void CLightning::Spawn( void ) +{ + if ( FStringNull( m_iszSpriteName ) ) + { + SetThink( &CLightning::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); + + pev->dmgtime = gpGlobals->time; + + if ( ServerSide() ) + { + SetThink( NULL ); + if ( pev->dmg > 0 ) + { + SetThink( &CLightning::DamageThink ); + pev->nextthink = gpGlobals->time + 0.1; + } + if ( pev->targetname ) + { + if ( !(pev->spawnflags & SF_BEAM_STARTON) ) + { + pev->effects = EF_NODRAW; + m_active = 0; + pev->nextthink = 0; + } + else + m_active = 1; + + SetUse( &CLightning::ToggleUse ); + } + } + else + { + m_active = 0; + if ( !FStringNull(pev->targetname) ) + { + SetUse( &CLightning::StrikeUse ); + } + if ( FStringNull(pev->targetname) || FBitSet(pev->spawnflags, SF_BEAM_STARTON) ) + { + SetThink( &CLightning::StrikeThink ); + pev->nextthink = gpGlobals->time + 1.0; + } + } +} + +void CLightning::Precache( void ) +{ + m_spriteTexture = PRECACHE_MODEL( (char *)STRING(m_iszSpriteName) ); + CBeam::Precache(); +} + + +void CLightning::Activate( void ) +{ + if ( ServerSide() ) + BeamUpdateVars(); +} + + +void CLightning::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "LightningStart")) + { + m_iszStartEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "LightningEnd")) + { + m_iszEndEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "life")) + { + m_life = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "BoltWidth")) + { + m_boltWidth = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "NoiseAmplitude")) + { + m_noiseAmplitude = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "TextureScroll")) + { + m_speed = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "StrikeTime")) + { + m_restrike = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "texture")) + { + m_iszSpriteName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "framestart")) + { + m_frameStart = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "Radius")) + { + m_radius = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damage")) + { + pev->dmg = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBeam::KeyValue( pkvd ); +} + + +void CLightning::ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_active ) ) + return; + if ( m_active ) + { + m_active = 0; + pev->effects |= EF_NODRAW; + pev->nextthink = 0; + } + else + { + m_active = 1; + pev->effects &= ~EF_NODRAW; + DoSparks( GetStartPos(), GetEndPos() ); + if ( pev->dmg > 0 ) + { + pev->nextthink = gpGlobals->time; + pev->dmgtime = gpGlobals->time; + } + } +} + + +void CLightning::StrikeUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_active ) ) + return; + + if ( m_active ) + { + m_active = 0; + SetThink( NULL ); + } + else + { + SetThink( &CLightning::StrikeThink ); + pev->nextthink = gpGlobals->time + 0.1; + } + + if ( !FBitSet( pev->spawnflags, SF_BEAM_TOGGLE ) ) + SetUse( NULL ); +} + + +int IsPointEntity( CBaseEntity *pEnt ) +{ + if ( !pEnt->pev->modelindex ) + return 1; + if ( FClassnameIs( pEnt->pev, "info_target" ) || FClassnameIs( pEnt->pev, "info_landmark" ) || FClassnameIs( pEnt->pev, "path_corner" ) ) + return 1; + + return 0; +} + + +void CLightning::StrikeThink( void ) +{ + if ( m_life != 0 ) + { + if ( pev->spawnflags & SF_BEAM_RANDOM ) + pev->nextthink = gpGlobals->time + m_life + RANDOM_FLOAT( 0, m_restrike ); + else + pev->nextthink = gpGlobals->time + m_life + m_restrike; + } + m_active = 1; + + if (FStringNull(m_iszEndEntity)) + { + if (FStringNull(m_iszStartEntity)) + { + RandomArea( ); + } + else + { + CBaseEntity *pStart = RandomTargetname( STRING(m_iszStartEntity) ); + if (pStart != NULL) + RandomPoint( pStart->pev->origin ); + else + ALERT( at_console, "env_beam: unknown entity \"%s\"\n", STRING(m_iszStartEntity) ); + } + return; + } + + CBaseEntity *pStart = RandomTargetname( STRING(m_iszStartEntity) ); + CBaseEntity *pEnd = RandomTargetname( STRING(m_iszEndEntity) ); + + if ( pStart != NULL && pEnd != NULL ) + { + if ( IsPointEntity( pStart ) || IsPointEntity( pEnd ) ) + { + if ( pev->spawnflags & SF_BEAM_RING) + { + // don't work + return; + } + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + if ( IsPointEntity( pStart ) || IsPointEntity( pEnd ) ) + { + if ( !IsPointEntity( pEnd ) ) // One point entity must be in pEnd + { + CBaseEntity *pTemp; + pTemp = pStart; + pStart = pEnd; + pEnd = pTemp; + } + if ( !IsPointEntity( pStart ) ) // One sided + { + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( pStart->entindex() ); + WRITE_COORD( pEnd->pev->origin.x); + WRITE_COORD( pEnd->pev->origin.y); + WRITE_COORD( pEnd->pev->origin.z); + } + else + { + WRITE_BYTE( TE_BEAMPOINTS); + WRITE_COORD( pStart->pev->origin.x); + WRITE_COORD( pStart->pev->origin.y); + WRITE_COORD( pStart->pev->origin.z); + WRITE_COORD( pEnd->pev->origin.x); + WRITE_COORD( pEnd->pev->origin.y); + WRITE_COORD( pEnd->pev->origin.z); + } + + + } + else + { + if ( pev->spawnflags & SF_BEAM_RING) + WRITE_BYTE( TE_BEAMRING ); + else + WRITE_BYTE( TE_BEAMENTS ); + WRITE_SHORT( pStart->entindex() ); + WRITE_SHORT( pEnd->entindex() ); + } + + WRITE_SHORT( m_spriteTexture ); + WRITE_BYTE( m_frameStart ); // framestart + WRITE_BYTE( (int)pev->framerate); // framerate + WRITE_BYTE( (int)(m_life*10.0) ); // life + WRITE_BYTE( m_boltWidth ); // width + WRITE_BYTE( m_noiseAmplitude ); // noise + WRITE_BYTE( (int)pev->rendercolor.x ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.y ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.z ); // r, g, b + WRITE_BYTE( pev->renderamt ); // brightness + WRITE_BYTE( m_speed ); // speed + MESSAGE_END(); + DoSparks( pStart->pev->origin, pEnd->pev->origin ); + if ( pev->dmg > 0 ) + { + TraceResult tr; + UTIL_TraceLine( pStart->pev->origin, pEnd->pev->origin, dont_ignore_monsters, NULL, &tr ); + BeamDamageInstant( &tr, pev->dmg ); + } + } +} + + +void CBeam::BeamDamage( TraceResult *ptr ) +{ + RelinkBeam(); + if ( ptr->flFraction != 1.0 && ptr->pHit != NULL ) + { + CBaseEntity *pHit = CBaseEntity::Instance(ptr->pHit); + if ( pHit ) + { + ClearMultiDamage(); + pHit->TraceAttack( pev, pev->dmg * (gpGlobals->time - pev->dmgtime), (ptr->vecEndPos - pev->origin).Normalize(), ptr, DMG_ENERGYBEAM ); + ApplyMultiDamage( pev, pev ); + if ( pev->spawnflags & SF_BEAM_DECALS ) + { + if ( pHit->IsBSPModel() ) + UTIL_DecalTrace( ptr, DECAL_BIGSHOT1 + RANDOM_LONG(0,4) ); + } + } + } + pev->dmgtime = gpGlobals->time; +} + + +void CLightning::DamageThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + TraceResult tr; + UTIL_TraceLine( GetStartPos(), GetEndPos(), dont_ignore_monsters, NULL, &tr ); + BeamDamage( &tr ); +} + + + +void CLightning::Zap( const Vector &vecSrc, const Vector &vecDest ) +{ +#if 1 + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS); + WRITE_COORD(vecSrc.x); + WRITE_COORD(vecSrc.y); + WRITE_COORD(vecSrc.z); + WRITE_COORD(vecDest.x); + WRITE_COORD(vecDest.y); + WRITE_COORD(vecDest.z); + WRITE_SHORT( m_spriteTexture ); + WRITE_BYTE( m_frameStart ); // framestart + WRITE_BYTE( (int)pev->framerate); // framerate + WRITE_BYTE( (int)(m_life*10.0) ); // life + WRITE_BYTE( m_boltWidth ); // width + WRITE_BYTE( m_noiseAmplitude ); // noise + WRITE_BYTE( (int)pev->rendercolor.x ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.y ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.z ); // r, g, b + WRITE_BYTE( pev->renderamt ); // brightness + WRITE_BYTE( m_speed ); // speed + MESSAGE_END(); +#else + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE(TE_LIGHTNING); + WRITE_COORD(vecSrc.x); + WRITE_COORD(vecSrc.y); + WRITE_COORD(vecSrc.z); + WRITE_COORD(vecDest.x); + WRITE_COORD(vecDest.y); + WRITE_COORD(vecDest.z); + WRITE_BYTE(10); + WRITE_BYTE(50); + WRITE_BYTE(40); + WRITE_SHORT(m_spriteTexture); + MESSAGE_END(); +#endif + DoSparks( vecSrc, vecDest ); +} + +void CLightning::RandomArea( void ) +{ + int iLoops = 0; + + for (iLoops = 0; iLoops < 10; iLoops++) + { + Vector vecSrc = pev->origin; + + Vector vecDir1 = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + vecDir1 = vecDir1.Normalize(); + TraceResult tr1; + UTIL_TraceLine( vecSrc, vecSrc + vecDir1 * m_radius, ignore_monsters, ENT(pev), &tr1 ); + + if (tr1.flFraction == 1.0) + continue; + + Vector vecDir2; + do { + vecDir2 = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + } while (DotProduct(vecDir1, vecDir2 ) > 0); + vecDir2 = vecDir2.Normalize(); + TraceResult tr2; + UTIL_TraceLine( vecSrc, vecSrc + vecDir2 * m_radius, ignore_monsters, ENT(pev), &tr2 ); + + if (tr2.flFraction == 1.0) + continue; + + if ((tr1.vecEndPos - tr2.vecEndPos).Length() < m_radius * 0.1) + continue; + + UTIL_TraceLine( tr1.vecEndPos, tr2.vecEndPos, ignore_monsters, ENT(pev), &tr2 ); + + if (tr2.flFraction != 1.0) + continue; + + Zap( tr1.vecEndPos, tr2.vecEndPos ); + + break; + } +} + + +void CLightning::RandomPoint( Vector &vecSrc ) +{ + int iLoops = 0; + + for (iLoops = 0; iLoops < 10; iLoops++) + { + Vector vecDir1 = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + vecDir1 = vecDir1.Normalize(); + TraceResult tr1; + UTIL_TraceLine( vecSrc, vecSrc + vecDir1 * m_radius, ignore_monsters, ENT(pev), &tr1 ); + + if ((tr1.vecEndPos - vecSrc).Length() < m_radius * 0.1) + continue; + + if (tr1.flFraction == 1.0) + continue; + + Zap( vecSrc, tr1.vecEndPos ); + break; + } +} + + + +void CLightning::BeamUpdateVars( void ) +{ + int beamType; + int pointStart, pointEnd; + + edict_t *pStart = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(m_iszStartEntity) ); + edict_t *pEnd = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(m_iszEndEntity) ); + pointStart = IsPointEntity( CBaseEntity::Instance(pStart) ); + pointEnd = IsPointEntity( CBaseEntity::Instance(pEnd) ); + + pev->skin = 0; + pev->sequence = 0; + pev->rendermode = 0; + pev->flags |= FL_CUSTOMENTITY; + pev->model = m_iszSpriteName; + SetTexture( m_spriteTexture ); + + beamType = BEAM_ENTS; + if ( pointStart || pointEnd ) + { + if ( !pointStart ) // One point entity must be in pStart + { + edict_t *pTemp; + // Swap start & end + pTemp = pStart; + pStart = pEnd; + pEnd = pTemp; + int swap = pointStart; + pointStart = pointEnd; + pointEnd = swap; + } + if ( !pointEnd ) + beamType = BEAM_ENTPOINT; + else + beamType = BEAM_POINTS; + } + + SetType( beamType ); + if ( beamType == BEAM_POINTS || beamType == BEAM_ENTPOINT || beamType == BEAM_HOSE ) + { + SetStartPos( pStart->v.origin ); + if ( beamType == BEAM_POINTS || beamType == BEAM_HOSE ) + SetEndPos( pEnd->v.origin ); + else + SetEndEntity( ENTINDEX(pEnd) ); + } + else + { + SetStartEntity( ENTINDEX(pStart) ); + SetEndEntity( ENTINDEX(pEnd) ); + } + + RelinkBeam(); + + SetWidth( m_boltWidth ); + SetNoise( m_noiseAmplitude ); + SetFrame( m_frameStart ); + SetScrollRate( m_speed ); + if ( pev->spawnflags & SF_BEAM_SHADEIN ) + SetFlags( BEAM_FSHADEIN ); + else if ( pev->spawnflags & SF_BEAM_SHADEOUT ) + SetFlags( BEAM_FSHADEOUT ); +} + + +LINK_ENTITY_TO_CLASS( env_laser, CLaser ); + +TYPEDESCRIPTION CLaser::m_SaveData[] = +{ + DEFINE_FIELD( CLaser, m_pSprite, FIELD_CLASSPTR ), + DEFINE_FIELD( CLaser, m_iszSpriteName, FIELD_STRING ), + DEFINE_FIELD( CLaser, m_firePosition, FIELD_POSITION_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CLaser, CBeam ); + +void CLaser::Spawn( void ) +{ + if ( FStringNull( pev->model ) ) + { + SetThink( &CLaser::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); + + SetThink( &CLaser::StrikeThink ); + pev->flags |= FL_CUSTOMENTITY; + + PointsInit( pev->origin, pev->origin ); + + if ( !m_pSprite && m_iszSpriteName ) + m_pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteName), pev->origin, TRUE ); + else + m_pSprite = NULL; + + if ( m_pSprite ) + m_pSprite->SetTransparency( kRenderGlow, pev->rendercolor.x, pev->rendercolor.y, pev->rendercolor.z, pev->renderamt, pev->renderfx ); + + if ( pev->targetname && !(pev->spawnflags & SF_BEAM_STARTON) ) + TurnOff(); + else + TurnOn(); +} + +void CLaser::Precache( void ) +{ + pev->modelindex = PRECACHE_MODEL( (char *)STRING(pev->model) ); + if ( m_iszSpriteName ) + PRECACHE_MODEL( (char *)STRING(m_iszSpriteName) ); +} + + +void CLaser::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "LaserTarget")) + { + pev->message = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "width")) + { + SetWidth( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "NoiseAmplitude")) + { + SetNoise( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "TextureScroll")) + { + SetScrollRate( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "texture")) + { + pev->model = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "EndSprite")) + { + m_iszSpriteName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "framestart")) + { + pev->frame = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damage")) + { + pev->dmg = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBeam::KeyValue( pkvd ); +} + + +int CLaser::IsOn( void ) +{ + if (pev->effects & EF_NODRAW) + return 0; + return 1; +} + + +void CLaser::TurnOff( void ) +{ + pev->effects |= EF_NODRAW; + pev->nextthink = 0; + if ( m_pSprite ) + m_pSprite->TurnOff(); +} + + +void CLaser::TurnOn( void ) +{ + pev->effects &= ~EF_NODRAW; + if ( m_pSprite ) + m_pSprite->TurnOn(); + pev->dmgtime = gpGlobals->time; + pev->nextthink = gpGlobals->time; +} + + +void CLaser::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int active = IsOn(); + + if ( !ShouldToggle( useType, active ) ) + return; + if ( active ) + { + TurnOff(); + } + else + { + TurnOn(); + } +} + + +void CLaser::FireAtPoint( TraceResult &tr ) +{ + SetEndPos( tr.vecEndPos ); + if ( m_pSprite ) + UTIL_SetOrigin( m_pSprite->pev, tr.vecEndPos ); + + BeamDamage( &tr ); + DoSparks( GetStartPos(), tr.vecEndPos ); +} + +void CLaser::StrikeThink( void ) +{ + CBaseEntity *pEnd = RandomTargetname( STRING(pev->message) ); + + if ( pEnd ) + m_firePosition = pEnd->pev->origin; + + TraceResult tr; + + UTIL_TraceLine( pev->origin, m_firePosition, dont_ignore_monsters, NULL, &tr ); + FireAtPoint( tr ); + pev->nextthink = gpGlobals->time + 0.1; +} + + + +class CGlow : public CPointEntity +{ +public: + void Spawn( void ); + void Think( void ); + void Animate( float frames ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + float m_lastTime; + float m_maxFrame; +}; + +LINK_ENTITY_TO_CLASS( env_glow, CGlow ); + +TYPEDESCRIPTION CGlow::m_SaveData[] = +{ + DEFINE_FIELD( CGlow, m_lastTime, FIELD_TIME ), + DEFINE_FIELD( CGlow, m_maxFrame, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CGlow, CPointEntity ); + +void CGlow::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; + if ( m_maxFrame > 1.0 && pev->framerate != 0 ) + pev->nextthink = gpGlobals->time + 0.1; + + m_lastTime = gpGlobals->time; +} + + +void CGlow::Think( void ) +{ + Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); + + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; +} + + +void CGlow::Animate( float frames ) +{ + if ( m_maxFrame > 0 ) + pev->frame = fmod( pev->frame + frames, m_maxFrame ); +} + + +LINK_ENTITY_TO_CLASS( env_sprite, CSprite ); + +TYPEDESCRIPTION CSprite::m_SaveData[] = +{ + DEFINE_FIELD( CSprite, m_lastTime, FIELD_TIME ), + DEFINE_FIELD( CSprite, m_maxFrame, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CSprite, CPointEntity ); + +void CSprite::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + + Precache(); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; + if ( pev->targetname && !(pev->spawnflags & SF_SPRITE_STARTON) ) + TurnOff(); + else + TurnOn(); + + // Worldcraft only sets y rotation, copy to Z + if ( pev->angles.y != 0 && pev->angles.z == 0 ) + { + pev->angles.z = pev->angles.y; + pev->angles.y = 0; + } +} + + +void CSprite::Precache( void ) +{ + PRECACHE_MODEL( (char *)STRING(pev->model) ); + + // Reset attachment after save/restore + if ( pev->aiment ) + SetAttachment( pev->aiment, pev->body ); + else + { + // Clear attachment + pev->skin = 0; + pev->body = 0; + } +} + + +void CSprite::SpriteInit( const char *pSpriteName, const Vector &origin ) +{ + pev->model = MAKE_STRING(pSpriteName); + pev->origin = origin; + Spawn(); +} + +CSprite *CSprite::SpriteCreate( const char *pSpriteName, const Vector &origin, BOOL animate ) +{ + CSprite *pSprite = GetClassPtr( (CSprite *)NULL ); + pSprite->SpriteInit( pSpriteName, origin ); + pSprite->pev->classname = MAKE_STRING("env_sprite"); + pSprite->pev->solid = SOLID_NOT; + pSprite->pev->movetype = MOVETYPE_NOCLIP; + if ( animate ) + pSprite->TurnOn(); + + return pSprite; +} + + +void CSprite::AnimateThink( void ) +{ + Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); + + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; +} + +void CSprite::AnimateUntilDead( void ) +{ + if ( gpGlobals->time > pev->dmgtime ) + UTIL_Remove(this); + else + { + AnimateThink(); + pev->nextthink = gpGlobals->time; + } +} + +void CSprite::Expand( float scaleSpeed, float fadeSpeed ) +{ + pev->speed = scaleSpeed; + pev->health = fadeSpeed; + SetThink( &CSprite::ExpandThink ); + + pev->nextthink = gpGlobals->time; + m_lastTime = gpGlobals->time; +} + + +void CSprite::ExpandThink( void ) +{ + float frametime = gpGlobals->time - m_lastTime; + pev->scale += pev->speed * frametime; + pev->renderamt -= pev->health * frametime; + if ( pev->renderamt <= 0 ) + { + pev->renderamt = 0; + UTIL_Remove( this ); + } + else + { + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; + } +} + + +void CSprite::Animate( float frames ) +{ + pev->frame += frames; + if ( pev->frame > m_maxFrame ) + { + if ( pev->spawnflags & SF_SPRITE_ONCE ) + { + TurnOff(); + } + else + { + if ( m_maxFrame > 0 ) + pev->frame = fmod( pev->frame, m_maxFrame ); + } + } +} + + +void CSprite::TurnOff( void ) +{ + pev->effects = EF_NODRAW; + pev->nextthink = 0; +} + + +void CSprite::TurnOn( void ) +{ + pev->effects = 0; + if ( (pev->framerate && m_maxFrame > 1.0) || (pev->spawnflags & SF_SPRITE_ONCE) ) + { + SetThink( &CSprite::AnimateThink ); + pev->nextthink = gpGlobals->time; + m_lastTime = gpGlobals->time; + } + pev->frame = 0; +} + + +void CSprite::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int on = pev->effects != EF_NODRAW; + if ( ShouldToggle( useType, on ) ) + { + if ( on ) + { + TurnOff(); + } + else + { + TurnOn(); + } + } +} + + +class CGibShooter : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT ShootThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual CGib *CreateGib( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iGibs; + int m_iGibCapacity; + int m_iGibMaterial; + int m_iGibModelIndex; + float m_flGibVelocity; + float m_flVariance; + float m_flGibLife; +}; + +TYPEDESCRIPTION CGibShooter::m_SaveData[] = +{ + DEFINE_FIELD( CGibShooter, m_iGibs, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_iGibCapacity, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_iGibMaterial, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_iGibModelIndex, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_flGibVelocity, FIELD_FLOAT ), + DEFINE_FIELD( CGibShooter, m_flVariance, FIELD_FLOAT ), + DEFINE_FIELD( CGibShooter, m_flGibLife, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CGibShooter, CBaseDelay ); +LINK_ENTITY_TO_CLASS( gibshooter, CGibShooter ); + + +void CGibShooter :: Precache ( void ) +{ + if ( g_Language == LANGUAGE_GERMAN ) + { + m_iGibModelIndex = PRECACHE_MODEL ("models/germanygibs.mdl"); + } + else + { + m_iGibModelIndex = PRECACHE_MODEL ("models/hgibs.mdl"); + } +} + + +void CGibShooter::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iGibs")) + { + m_iGibs = m_iGibCapacity = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flVelocity")) + { + m_flGibVelocity = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flVariance")) + { + m_flVariance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flGibLife")) + { + m_flGibLife = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + { + CBaseDelay::KeyValue( pkvd ); + } +} + +void CGibShooter::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( &CGibShooter::ShootThink ); + pev->nextthink = gpGlobals->time; +} + +void CGibShooter::Spawn( void ) +{ + Precache(); + + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + + if ( m_flDelay == 0 ) + { + m_flDelay = 0.1; + } + + if ( m_flGibLife == 0 ) + { + m_flGibLife = 25; + } + + SetMovedir ( pev ); + pev->body = MODEL_FRAMES( m_iGibModelIndex ); +} + + +CGib *CGibShooter :: CreateGib ( void ) +{ + if ( CVAR_GET_FLOAT("violence_hgibs") == 0 ) + return NULL; + + CGib *pGib = GetClassPtr( (CGib *)NULL ); + pGib->Spawn( "models/hgibs.mdl" ); + pGib->m_bloodColor = BLOOD_COLOR_RED; + + if ( pev->body <= 1 ) + { + ALERT ( at_aiconsole, "GibShooter Body is <= 1!\n" ); + } + + pGib->pev->body = RANDOM_LONG ( 1, pev->body - 1 );// avoid throwing random amounts of the 0th gib. (skull). + + return pGib; +} + + +void CGibShooter :: ShootThink ( void ) +{ + pev->nextthink = gpGlobals->time + m_flDelay; + + Vector vecShootDir; + + vecShootDir = pev->movedir; + + vecShootDir = vecShootDir + gpGlobals->v_right * RANDOM_FLOAT( -1, 1) * m_flVariance;; + vecShootDir = vecShootDir + gpGlobals->v_forward * RANDOM_FLOAT( -1, 1) * m_flVariance;; + vecShootDir = vecShootDir + gpGlobals->v_up * RANDOM_FLOAT( -1, 1) * m_flVariance;; + + vecShootDir = vecShootDir.Normalize(); + CGib *pGib = CreateGib(); + + if ( pGib ) + { + pGib->pev->origin = pev->origin; + pGib->pev->velocity = vecShootDir * m_flGibVelocity; + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + float thinkTime = pGib->pev->nextthink - gpGlobals->time; + + pGib->m_lifeTime = (m_flGibLife * RANDOM_FLOAT( 0.95, 1.05 )); // +/- 5% + if ( pGib->m_lifeTime < thinkTime ) + { + pGib->pev->nextthink = gpGlobals->time + pGib->m_lifeTime; + pGib->m_lifeTime = 0; + } + + } + + if ( --m_iGibs <= 0 ) + { + if ( pev->spawnflags & SF_GIBSHOOTER_REPEATABLE ) + { + m_iGibs = m_iGibCapacity; + SetThink ( NULL ); + pev->nextthink = gpGlobals->time; + } + else + { + SetThink ( &CGibShooter::SUB_Remove ); + pev->nextthink = gpGlobals->time; + } + } +} + + +class CEnvShooter : public CGibShooter +{ + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + CGib *CreateGib( void ); +}; + +LINK_ENTITY_TO_CLASS( env_shooter, CEnvShooter ); + +void CEnvShooter :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "shootmodel")) + { + pev->model = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "shootsounds")) + { + int iNoise = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + switch( iNoise ) + { + case 0: + m_iGibMaterial = matGlass; + break; + case 1: + m_iGibMaterial = matWood; + break; + case 2: + m_iGibMaterial = matMetal; + break; + case 3: + m_iGibMaterial = matFlesh; + break; + case 4: + m_iGibMaterial = matRocks; + break; + + default: + case -1: + m_iGibMaterial = matNone; + break; + } + } + else + { + CGibShooter::KeyValue( pkvd ); + } +} + + +void CEnvShooter :: Precache ( void ) +{ + m_iGibModelIndex = PRECACHE_MODEL( (char *)STRING(pev->model) ); + CBreakable::MaterialSoundPrecache( (Materials)m_iGibMaterial ); +} + + +CGib *CEnvShooter :: CreateGib ( void ) +{ + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + pGib->Spawn( STRING(pev->model) ); + + int bodyPart = 0; + + if ( pev->body > 1 ) + bodyPart = RANDOM_LONG( 0, pev->body-1 ); + + pGib->pev->body = bodyPart; + pGib->m_bloodColor = DONT_BLEED; + pGib->m_material = m_iGibMaterial; + + pGib->pev->rendermode = pev->rendermode; + pGib->pev->renderamt = pev->renderamt; + pGib->pev->rendercolor = pev->rendercolor; + pGib->pev->renderfx = pev->renderfx; + pGib->pev->scale = pev->scale; + pGib->pev->skin = pev->skin; + + return pGib; +} + + + + +class CTestEffect : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + // void KeyValue( KeyValueData *pkvd ); + void EXPORT TestThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int m_iLoop; + int m_iBeam; + CBeam *m_pBeam[24]; + float m_flBeamTime[24]; + float m_flStartTime; +}; + + +LINK_ENTITY_TO_CLASS( test_effect, CTestEffect ); + +void CTestEffect::Spawn( void ) +{ + Precache( ); +} + +void CTestEffect::Precache( void ) +{ + PRECACHE_MODEL( "sprites/lgtning.spr" ); +} + +void CTestEffect::TestThink( void ) +{ + int i; + float t = (gpGlobals->time - m_flStartTime); + + if (m_iBeam < 24) + { + CBeam *pbeam = CBeam::BeamCreate( "sprites/lgtning.spr", 100 ); + + TraceResult tr; + + Vector vecSrc = pev->origin; + Vector vecDir = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + vecDir = vecDir.Normalize(); + UTIL_TraceLine( vecSrc, vecSrc + vecDir * 128, ignore_monsters, ENT(pev), &tr); + + pbeam->PointsInit( vecSrc, tr.vecEndPos ); + // pbeam->SetColor( 80, 100, 255 ); + pbeam->SetColor( 255, 180, 100 ); + pbeam->SetWidth( 100 ); + pbeam->SetScrollRate( 12 ); + + m_flBeamTime[m_iBeam] = gpGlobals->time; + m_pBeam[m_iBeam] = pbeam; + m_iBeam++; + +#if 0 + Vector vecMid = (vecSrc + tr.vecEndPos) * 0.5; + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE(TE_DLIGHT); + WRITE_COORD(vecMid.x); // X + WRITE_COORD(vecMid.y); // Y + WRITE_COORD(vecMid.z); // Z + WRITE_BYTE( 20 ); // radius * 0.1 + WRITE_BYTE( 255 ); // r + WRITE_BYTE( 180 ); // g + WRITE_BYTE( 100 ); // b + WRITE_BYTE( 20 ); // time * 10 + WRITE_BYTE( 0 ); // decay * 0.1 + MESSAGE_END( ); +#endif + } + + if (t < 3.0) + { + for (i = 0; i < m_iBeam; i++) + { + t = (gpGlobals->time - m_flBeamTime[i]) / ( 3 + m_flStartTime - m_flBeamTime[i]); + m_pBeam[i]->SetBrightness( 255 * t ); + // m_pBeam[i]->SetScrollRate( 20 * t ); + } + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + for (i = 0; i < m_iBeam; i++) + { + UTIL_Remove( m_pBeam[i] ); + } + m_flStartTime = gpGlobals->time; + m_iBeam = 0; + // pev->nextthink = gpGlobals->time; + SetThink( NULL ); + } +} + + +void CTestEffect::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( &CTestEffect::TestThink ); + pev->nextthink = gpGlobals->time + 0.1; + m_flStartTime = gpGlobals->time; +} + + + +// Blood effects +class CBlood : public CPointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline int Color( void ) { return pev->impulse; } + inline float BloodAmount( void ) { return pev->dmg; } + + inline void SetColor( int color ) { pev->impulse = color; } + inline void SetBloodAmount( float amount ) { pev->dmg = amount; } + + Vector Direction( void ); + Vector BloodPosition( CBaseEntity *pActivator ); + +private: +}; + +LINK_ENTITY_TO_CLASS( env_blood, CBlood ); + + + +#define SF_BLOOD_RANDOM 0x0001 +#define SF_BLOOD_STREAM 0x0002 +#define SF_BLOOD_PLAYER 0x0004 +#define SF_BLOOD_DECAL 0x0008 + +void CBlood::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + SetMovedir( pev ); +} + + +void CBlood::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "color")) + { + int color = atoi(pkvd->szValue); + switch( color ) + { + case 1: + SetColor( BLOOD_COLOR_YELLOW ); + break; + default: + SetColor( BLOOD_COLOR_RED ); + break; + } + + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "amount")) + { + SetBloodAmount( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +Vector CBlood::Direction( void ) +{ + if ( pev->spawnflags & SF_BLOOD_RANDOM ) + return UTIL_RandomBloodVector(); + + return pev->movedir; +} + + +Vector CBlood::BloodPosition( CBaseEntity *pActivator ) +{ + if ( pev->spawnflags & SF_BLOOD_PLAYER ) + { + edict_t *pPlayer; + + if ( pActivator && pActivator->IsPlayer() ) + { + pPlayer = pActivator->edict(); + } + else + pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + if ( pPlayer ) + return (pPlayer->v.origin + pPlayer->v.view_ofs) + Vector( RANDOM_FLOAT(-10,10), RANDOM_FLOAT(-10,10), RANDOM_FLOAT(-10,10) ); + } + + return pev->origin; +} + + +void CBlood::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->spawnflags & SF_BLOOD_STREAM ) + UTIL_BloodStream( BloodPosition(pActivator), Direction(), Color(), BloodAmount() ); + else + UTIL_BloodDrips( BloodPosition(pActivator), Direction(), Color(), BloodAmount() ); + + if ( pev->spawnflags & SF_BLOOD_DECAL ) + { + Vector forward = Direction(); + Vector start = BloodPosition( pActivator ); + TraceResult tr; + + UTIL_TraceLine( start, start + forward * BloodAmount() * 2, ignore_monsters, NULL, &tr ); + if ( tr.flFraction != 1.0 ) + UTIL_BloodDecalTrace( &tr, Color() ); + } +} + + + +// Screen shake +class CShake : public CPointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline float Amplitude( void ) { return pev->scale; } + inline float Frequency( void ) { return pev->dmg_save; } + inline float Duration( void ) { return pev->dmg_take; } + inline float Radius( void ) { return pev->dmg; } + + inline void SetAmplitude( float amplitude ) { pev->scale = amplitude; } + inline void SetFrequency( float frequency ) { pev->dmg_save = frequency; } + inline void SetDuration( float duration ) { pev->dmg_take = duration; } + inline void SetRadius( float radius ) { pev->dmg = radius; } +private: +}; + +LINK_ENTITY_TO_CLASS( env_shake, CShake ); + +// pev->scale is amplitude +// pev->dmg_save is frequency +// pev->dmg_take is duration +// pev->dmg is radius +// radius of 0 means all players +// NOTE: UTIL_ScreenShake() will only shake players who are on the ground + +#define SF_SHAKE_EVERYONE 0x0001 // Don't check radius +// UNDONE: These don't work yet +#define SF_SHAKE_DISRUPT 0x0002 // Disrupt controls +#define SF_SHAKE_INAIR 0x0004 // Shake players in air + +void CShake::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + + if ( pev->spawnflags & SF_SHAKE_EVERYONE ) + pev->dmg = 0; +} + + +void CShake::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "amplitude")) + { + SetAmplitude( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "frequency")) + { + SetFrequency( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "duration")) + { + SetDuration( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "radius")) + { + SetRadius( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CShake::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + UTIL_ScreenShake( pev->origin, Amplitude(), Frequency(), Duration(), Radius() ); +} + + +class CFade : public CPointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline float Duration( void ) { return pev->dmg_take; } + inline float HoldTime( void ) { return pev->dmg_save; } + + inline void SetDuration( float duration ) { pev->dmg_take = duration; } + inline void SetHoldTime( float hold ) { pev->dmg_save = hold; } +private: +}; + +LINK_ENTITY_TO_CLASS( env_fade, CFade ); + +// pev->dmg_take is duration +// pev->dmg_save is hold duration +#define SF_FADE_IN 0x0001 // Fade in, not out +#define SF_FADE_MODULATE 0x0002 // Modulate, don't blend +#define SF_FADE_ONLYONE 0x0004 + +void CFade::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; +} + + +void CFade::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "duration")) + { + SetDuration( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "holdtime")) + { + SetHoldTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CFade::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int fadeFlags = 0; + + if ( !(pev->spawnflags & SF_FADE_IN) ) + fadeFlags |= FFADE_OUT; + + if ( pev->spawnflags & SF_FADE_MODULATE ) + fadeFlags |= FFADE_MODULATE; + + if ( pev->spawnflags & SF_FADE_ONLYONE ) + { + if ( pActivator->IsNetClient() ) + { + UTIL_ScreenFade( pActivator, pev->rendercolor, Duration(), HoldTime(), pev->renderamt, fadeFlags ); + } + } + else + { + UTIL_ScreenFadeAll( pev->rendercolor, Duration(), HoldTime(), pev->renderamt, fadeFlags ); + } + SUB_UseTargets( this, USE_TOGGLE, 0 ); +} + + +class CMessage : public CPointEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); +private: +}; + +LINK_ENTITY_TO_CLASS( env_message, CMessage ); + + +void CMessage::Spawn( void ) +{ + Precache(); + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + switch( pev->impulse ) + { + case 1: // Medium radius + pev->speed = ATTN_STATIC; + break; + + case 2: // Large radius + pev->speed = ATTN_NORM; + break; + + case 3: //EVERYWHERE + pev->speed = ATTN_NONE; + break; + + default: + case 0: // Small radius + pev->speed = ATTN_IDLE; + break; + } + pev->impulse = 0; + + // No volume, use normal + if ( pev->scale <= 0 ) + pev->scale = 1.0; +} + + +void CMessage::Precache( void ) +{ + if ( pev->noise ) + PRECACHE_SOUND( (char *)STRING(pev->noise) ); +} + +void CMessage::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "messagesound")) + { + pev->noise = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "messagevolume")) + { + pev->scale = atof(pkvd->szValue) * 0.1; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "messageattenuation")) + { + pev->impulse = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CMessage::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity *pPlayer = NULL; + + if ( pev->spawnflags & SF_MESSAGE_ALL ) + UTIL_ShowMessageAll( STRING(pev->message) ); + else + { + if ( pActivator && pActivator->IsPlayer() ) + pPlayer = pActivator; + else + { + pPlayer = CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) ); + } + if ( pPlayer ) + UTIL_ShowMessage( STRING(pev->message), pPlayer ); + } + if ( pev->noise ) + { + EMIT_SOUND( edict(), CHAN_BODY, STRING(pev->noise), pev->scale, pev->speed ); + } + if ( pev->spawnflags & SF_MESSAGE_ONCE ) + UTIL_Remove( this ); + + SUB_UseTargets( this, USE_TOGGLE, 0 ); +} + + + +//========================================================= +// FunnelEffect +//========================================================= +class CEnvFunnel : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int m_iSprite; // Don't save, precache +}; + +void CEnvFunnel :: Precache ( void ) +{ + m_iSprite = PRECACHE_MODEL ( "sprites/flare6.spr" ); +} + +LINK_ENTITY_TO_CLASS( env_funnel, CEnvFunnel ); + +void CEnvFunnel::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_LARGEFUNNEL ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( m_iSprite ); + + if ( pev->spawnflags & SF_FUNNEL_REVERSE )// funnel flows in reverse? + { + WRITE_SHORT( 1 ); + } + else + { + WRITE_SHORT( 0 ); + } + + + MESSAGE_END(); + + SetThink( &CEnvFunnel::SUB_Remove ); + pev->nextthink = gpGlobals->time; +} + +void CEnvFunnel::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; +} + +//========================================================= +// Beverage Dispenser +// overloaded pev->frags, is now a flag for whether or not a can is stuck in the dispenser. +// overloaded pev->health, is now how many cans remain in the machine. +//========================================================= +class CEnvBeverage : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; + +void CEnvBeverage :: Precache ( void ) +{ + PRECACHE_MODEL( "models/can.mdl" ); + PRECACHE_SOUND( "weapons/g_bounce3.wav" ); +} + +LINK_ENTITY_TO_CLASS( env_beverage, CEnvBeverage ); + +void CEnvBeverage::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->frags != 0 || pev->health <= 0 ) + { + // no more cans while one is waiting in the dispenser, or if I'm out of cans. + return; + } + + CBaseEntity *pCan = CBaseEntity::Create( "item_sodacan", pev->origin, pev->angles, edict() ); + + if ( pev->skin == 6 ) + { + // random + pCan->pev->skin = RANDOM_LONG( 0, 5 ); + } + else + { + pCan->pev->skin = pev->skin; + } + + pev->frags = 1; + pev->health--; + + //SetThink (SUB_Remove); + //pev->nextthink = gpGlobals->time; +} + +void CEnvBeverage::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + pev->frags = 0; + + if ( pev->health == 0 ) + { + pev->health = 10; + } +} + +//========================================================= +// Soda can +//========================================================= +class CItemSoda : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT CanThink ( void ); + void EXPORT CanTouch ( CBaseEntity *pOther ); +}; + +void CItemSoda :: Precache ( void ) +{ +} + +LINK_ENTITY_TO_CLASS( item_sodacan, CItemSoda ); + +void CItemSoda::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_TOSS; + + SET_MODEL ( ENT(pev), "models/can.mdl" ); + UTIL_SetSize ( pev, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ) ); + + SetThink (&CItemSoda::CanThink); + pev->nextthink = gpGlobals->time + 0.5; +} + +void CItemSoda::CanThink ( void ) +{ + EMIT_SOUND (ENT(pev), CHAN_WEAPON, "weapons/g_bounce3.wav", 1, ATTN_NORM ); + + pev->solid = SOLID_TRIGGER; + UTIL_SetSize ( pev, Vector ( -8, -8, 0 ), Vector ( 8, 8, 8 ) ); + SetThink ( NULL ); + SetTouch ( &CItemSoda::CanTouch ); +} + +void CItemSoda::CanTouch ( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + { + return; + } + + // spoit sound here + + pOther->TakeHealth( 1, DMG_GENERIC );// a bit of health. + + if ( !FNullEnt( pev->owner ) ) + { + // tell the machine the can was taken + pev->owner->v.frags = 0; + } + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = EF_NODRAW; + SetTouch ( NULL ); + SetThink ( &CItemSoda::SUB_Remove ); + pev->nextthink = gpGlobals->time; +} diff --git a/dmc/dlls/effects.h b/dmc/dlls/effects.h new file mode 100644 index 0000000..f93a9d8 --- /dev/null +++ b/dmc/dlls/effects.h @@ -0,0 +1,209 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef EFFECTS_H +#define EFFECTS_H + +#define SF_BEAM_STARTON 0x0001 +#define SF_BEAM_TOGGLE 0x0002 +#define SF_BEAM_RANDOM 0x0004 +#define SF_BEAM_RING 0x0008 +#define SF_BEAM_SPARKSTART 0x0010 +#define SF_BEAM_SPARKEND 0x0020 +#define SF_BEAM_DECALS 0x0040 +#define SF_BEAM_SHADEIN 0x0080 +#define SF_BEAM_SHADEOUT 0x0100 +#define SF_BEAM_TEMPORARY 0x8000 + +#define SF_SPRITE_STARTON 0x0001 +#define SF_SPRITE_ONCE 0x0002 +#define SF_SPRITE_TEMPORARY 0x8000 + +class CSprite : public CPointEntity +{ +public: + void Spawn( void ); + void Precache( void ); + + int ObjectCaps( void ) + { + int flags = 0; + if ( pev->spawnflags & SF_SPRITE_TEMPORARY ) + flags = FCAP_DONT_SAVE; + return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | flags; + } + void EXPORT AnimateThink( void ); + void EXPORT ExpandThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Animate( float frames ); + void Expand( float scaleSpeed, float fadeSpeed ); + void SpriteInit( const char *pSpriteName, const Vector &origin ); + + inline void SetAttachment( edict_t *pEntity, int attachment ) + { + if ( pEntity ) + { + pev->skin = ENTINDEX(pEntity); + pev->body = attachment; + pev->aiment = pEntity; + pev->movetype = MOVETYPE_FOLLOW; + } + } + void TurnOff( void ); + void TurnOn( void ); + inline float Frames( void ) { return m_maxFrame; } + inline void SetTransparency( int rendermode, int r, int g, int b, int a, int fx ) + { + pev->rendermode = rendermode; + pev->rendercolor.x = r; + pev->rendercolor.y = g; + pev->rendercolor.z = b; + pev->renderamt = a; + pev->renderfx = fx; + } + inline void SetTexture( int spriteIndex ) { pev->modelindex = spriteIndex; } + inline void SetScale( float scale ) { pev->scale = scale; } + inline void SetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline void SetBrightness( int brightness ) { pev->renderamt = brightness; } + + inline void AnimateAndDie( float framerate ) + { + SetThink(&CSprite::AnimateUntilDead); + pev->framerate = framerate; + pev->dmgtime = gpGlobals->time + (m_maxFrame / framerate); + pev->nextthink = gpGlobals->time; + } + + void EXPORT AnimateUntilDead( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + static CSprite *SpriteCreate( const char *pSpriteName, const Vector &origin, BOOL animate ); + +private: + + float m_lastTime; + float m_maxFrame; +}; + + +class CBeam : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + int ObjectCaps( void ) + { + int flags = 0; + if ( pev->spawnflags & SF_BEAM_TEMPORARY ) + flags = FCAP_DONT_SAVE; + return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | flags; + } + + void EXPORT TriggerTouch( CBaseEntity *pOther ); + + // These functions are here to show the way beams are encoded as entities. + // Encoding beams as entities simplifies their management in the client/server architecture + inline void SetType( int type ) { pev->rendermode = (pev->rendermode & 0xF0) | (type&0x0F); } + inline void SetFlags( int flags ) { pev->rendermode = (pev->rendermode & 0x0F) | (flags&0xF0); } + inline void SetStartPos( const Vector& pos ) { pev->origin = pos; } + inline void SetEndPos( const Vector& pos ) { pev->angles = pos; } + void SetStartEntity( int entityIndex ); + void SetEndEntity( int entityIndex ); + + inline void SetStartAttachment( int attachment ) { pev->sequence = (pev->sequence & 0x0FFF) | ((attachment&0xF)<<12); } + inline void SetEndAttachment( int attachment ) { pev->skin = (pev->skin & 0x0FFF) | ((attachment&0xF)<<12); } + + inline void SetTexture( int spriteIndex ) { pev->modelindex = spriteIndex; } + inline void SetWidth( int width ) { pev->scale = width; } + inline void SetNoise( int amplitude ) { pev->body = amplitude; } + inline void SetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline void SetBrightness( int brightness ) { pev->renderamt = brightness; } + inline void SetFrame( float frame ) { pev->frame = frame; } + inline void SetScrollRate( int speed ) { pev->animtime = speed; } + + inline int GetType( void ) { return pev->rendermode & 0x0F; } + inline int GetFlags( void ) { return pev->rendermode & 0xF0; } + inline int GetStartEntity( void ) { return pev->sequence & 0xFFF; } + inline int GetEndEntity( void ) { return pev->skin & 0xFFF; } + + const Vector &GetStartPos( void ); + const Vector &GetEndPos( void ); + + Vector Center( void ) { return (GetStartPos() + GetEndPos()) * 0.5; }; // center point of beam + + inline int GetTexture( void ) { return pev->modelindex; } + inline int GetWidth( void ) { return pev->scale; } + inline int GetNoise( void ) { return pev->body; } + // inline void GetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline int GetBrightness( void ) { return pev->renderamt; } + inline int GetFrame( void ) { return pev->frame; } + inline int GetScrollRate( void ) { return pev->animtime; } + + // Call after you change start/end positions + void RelinkBeam( void ); +// void SetObjectCollisionBox( void ); + + void DoSparks( const Vector &start, const Vector &end ); + CBaseEntity *RandomTargetname( const char *szName ); + void BeamDamage( TraceResult *ptr ); + // Init after BeamCreate() + void BeamInit( const char *pSpriteName, int width ); + void PointsInit( const Vector &start, const Vector &end ); + void PointEntInit( const Vector &start, int endIndex ); + void EntsInit( int startIndex, int endIndex ); + void HoseInit( const Vector &start, const Vector &direction ); + + static CBeam *BeamCreate( const char *pSpriteName, int width ); + + inline void LiveForTime( float time ) { SetThink(&CBeam::SUB_Remove); pev->nextthink = gpGlobals->time + time; } + inline void BeamDamageInstant( TraceResult *ptr, float damage ) + { + pev->dmg = damage; + pev->dmgtime = gpGlobals->time - 1; + BeamDamage(ptr); + } +}; + + +#define SF_MESSAGE_ONCE 0x0001 // Fade in, not out +#define SF_MESSAGE_ALL 0x0002 // Send to all clients + + +class CLaser : public CBeam +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + void TurnOn( void ); + void TurnOff( void ); + int IsOn( void ); + + void FireAtPoint( TraceResult &point ); + + void EXPORT StrikeThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + CSprite *m_pSprite; + int m_iszSpriteName; + Vector m_firePosition; +}; + +#endif //EFFECTS_H diff --git a/dmc/dlls/enginecallback.h b/dmc/dlls/enginecallback.h new file mode 100644 index 0000000..28f3141 --- /dev/null +++ b/dmc/dlls/enginecallback.h @@ -0,0 +1,159 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef ENGINECALLBACK_H +#define ENGINECALLBACK_H +#pragma once + +#include "event_flags.h" + +// Must be provided by user of this code +extern enginefuncs_t g_engfuncs; + +// The actual engine callbacks +#define GETPLAYERUSERID (*g_engfuncs.pfnGetPlayerUserId) +#define GETPLAYERAUTHID (*g_engfuncs.pfnGetPlayerAuthId) +#define PRECACHE_MODEL (*g_engfuncs.pfnPrecacheModel) +#define PRECACHE_SOUND (*g_engfuncs.pfnPrecacheSound) +#define PRECACHE_GENERIC (*g_engfuncs.pfnPrecacheGeneric) +#define SET_MODEL (*g_engfuncs.pfnSetModel) +#define MODEL_INDEX (*g_engfuncs.pfnModelIndex) +#define MODEL_FRAMES (*g_engfuncs.pfnModelFrames) +#define SET_SIZE (*g_engfuncs.pfnSetSize) +#define CHANGE_LEVEL (*g_engfuncs.pfnChangeLevel) +#define GET_SPAWN_PARMS (*g_engfuncs.pfnGetSpawnParms) +#define SAVE_SPAWN_PARMS (*g_engfuncs.pfnSaveSpawnParms) +#define VEC_TO_YAW (*g_engfuncs.pfnVecToYaw) +#define VEC_TO_ANGLES (*g_engfuncs.pfnVecToAngles) +#define MOVE_TO_ORIGIN (*g_engfuncs.pfnMoveToOrigin) +#define oldCHANGE_YAW (*g_engfuncs.pfnChangeYaw) +#define CHANGE_PITCH (*g_engfuncs.pfnChangePitch) +#define MAKE_VECTORS (*g_engfuncs.pfnMakeVectors) +#define CREATE_ENTITY (*g_engfuncs.pfnCreateEntity) +#define REMOVE_ENTITY (*g_engfuncs.pfnRemoveEntity) +#define CREATE_NAMED_ENTITY (*g_engfuncs.pfnCreateNamedEntity) +#define MAKE_STATIC (*g_engfuncs.pfnMakeStatic) +#define ENT_IS_ON_FLOOR (*g_engfuncs.pfnEntIsOnFloor) +#define DROP_TO_FLOOR (*g_engfuncs.pfnDropToFloor) +#define WALK_MOVE (*g_engfuncs.pfnWalkMove) +#define SET_ORIGIN (*g_engfuncs.pfnSetOrigin) +#define EMIT_SOUND_DYN2 (*g_engfuncs.pfnEmitSound) +#define BUILD_SOUND_MSG (*g_engfuncs.pfnBuildSoundMsg) +#define TRACE_LINE (*g_engfuncs.pfnTraceLine) +#define TRACE_TOSS (*g_engfuncs.pfnTraceToss) +#define TRACE_MONSTER_HULL (*g_engfuncs.pfnTraceMonsterHull) +#define TRACE_HULL (*g_engfuncs.pfnTraceHull) +#define GET_AIM_VECTOR (*g_engfuncs.pfnGetAimVector) +#define SERVER_COMMAND (*g_engfuncs.pfnServerCommand) +#define SERVER_EXECUTE (*g_engfuncs.pfnServerExecute) +#define CLIENT_COMMAND (*g_engfuncs.pfnClientCommand) +#define PARTICLE_EFFECT (*g_engfuncs.pfnParticleEffect) +#define LIGHT_STYLE (*g_engfuncs.pfnLightStyle) +#define DECAL_INDEX (*g_engfuncs.pfnDecalIndex) +#define POINT_CONTENTS (*g_engfuncs.pfnPointContents) +#define CRC32_INIT (*g_engfuncs.pfnCRC32_Init) +#define CRC32_PROCESS_BUFFER (*g_engfuncs.pfnCRC32_ProcessBuffer) +#define CRC32_PROCESS_BYTE (*g_engfuncs.pfnCRC32_ProcessByte) +#define CRC32_FINAL (*g_engfuncs.pfnCRC32_Final) +#define RANDOM_LONG (*g_engfuncs.pfnRandomLong) +#define RANDOM_FLOAT (*g_engfuncs.pfnRandomFloat) + +inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin = NULL, edict_t *ed = NULL ) { + (*g_engfuncs.pfnMessageBegin)(msg_dest, msg_type, pOrigin, ed); +} +#define MESSAGE_END (*g_engfuncs.pfnMessageEnd) +#define WRITE_BYTE (*g_engfuncs.pfnWriteByte) +#define WRITE_CHAR (*g_engfuncs.pfnWriteChar) +#define WRITE_SHORT (*g_engfuncs.pfnWriteShort) +#define WRITE_LONG (*g_engfuncs.pfnWriteLong) +#define WRITE_ANGLE (*g_engfuncs.pfnWriteAngle) +#define WRITE_COORD (*g_engfuncs.pfnWriteCoord) +#define WRITE_STRING (*g_engfuncs.pfnWriteString) +#define WRITE_ENTITY (*g_engfuncs.pfnWriteEntity) +#define CVAR_REGISTER (*g_engfuncs.pfnCVarRegister) +#define CVAR_GET_FLOAT (*g_engfuncs.pfnCVarGetFloat) +#define CVAR_GET_STRING (*g_engfuncs.pfnCVarGetString) +#define CVAR_SET_FLOAT (*g_engfuncs.pfnCVarSetFloat) +#define CVAR_SET_STRING (*g_engfuncs.pfnCVarSetString) +#define ALERT (*g_engfuncs.pfnAlertMessage) +#define ENGINE_FPRINTF (*g_engfuncs.pfnEngineFprintf) +#define ALLOC_PRIVATE (*g_engfuncs.pfnPvAllocEntPrivateData) +inline void *GET_PRIVATE( edict_t *pent ) +{ + if ( pent ) + return pent->pvPrivateData; + return NULL; +} + +#define FREE_PRIVATE (*g_engfuncs.pfnFreeEntPrivateData) +//#define STRING (*g_engfuncs.pfnSzFromIndex) +#define ALLOC_STRING (*g_engfuncs.pfnAllocString) +#define FIND_ENTITY_BY_STRING (*g_engfuncs.pfnFindEntityByString) +#define GETENTITYILLUM (*g_engfuncs.pfnGetEntityIllum) +#define FIND_ENTITY_IN_SPHERE (*g_engfuncs.pfnFindEntityInSphere) +#define FIND_CLIENT_IN_PVS (*g_engfuncs.pfnFindClientInPVS) +#define EMIT_AMBIENT_SOUND (*g_engfuncs.pfnEmitAmbientSound) +#define GET_MODEL_PTR (*g_engfuncs.pfnGetModelPtr) +#define REG_USER_MSG (*g_engfuncs.pfnRegUserMsg) +#define GET_BONE_POSITION (*g_engfuncs.pfnGetBonePosition) +#define FUNCTION_FROM_NAME (*g_engfuncs.pfnFunctionFromName) +#define NAME_FOR_FUNCTION (*g_engfuncs.pfnNameForFunction) +#define TRACE_TEXTURE (*g_engfuncs.pfnTraceTexture) +#define CLIENT_PRINTF (*g_engfuncs.pfnClientPrintf) +#define CMD_ARGS (*g_engfuncs.pfnCmd_Args) +#define CMD_ARGC (*g_engfuncs.pfnCmd_Argc) +#define CMD_ARGV (*g_engfuncs.pfnCmd_Argv) +#define GET_ATTACHMENT (*g_engfuncs.pfnGetAttachment) +#define SET_VIEW (*g_engfuncs.pfnSetView) +#define SET_CROSSHAIRANGLE (*g_engfuncs.pfnCrosshairAngle) +#define LOAD_FILE_FOR_ME (*g_engfuncs.pfnLoadFileForMe) +#define FREE_FILE (*g_engfuncs.pfnFreeFile) +#define COMPARE_FILE_TIME (*g_engfuncs.pfnCompareFileTime) +#define GET_GAME_DIR (*g_engfuncs.pfnGetGameDir) +#define IS_MAP_VALID (*g_engfuncs.pfnIsMapValid) +#define NUMBER_OF_ENTITIES (*g_engfuncs.pfnNumberOfEntities) +#define IS_DEDICATED_SERVER (*g_engfuncs.pfnIsDedicatedServer) + +#define CVAR_GET_POINTER (*g_engfuncs.pfnCVarGetPointer) + +#define PRECACHE_EVENT (*g_engfuncs.pfnPrecacheEvent) +#define PLAYBACK_EVENT_FULL (*g_engfuncs.pfnPlaybackEvent) + +#define ENGINE_SET_PVS (*g_engfuncs.pfnSetFatPVS) +#define ENGINE_SET_PAS (*g_engfuncs.pfnSetFatPAS) + +#define ENGINE_CHECK_VISIBILITY (*g_engfuncs.pfnCheckVisibility) + +#define DELTA_SET ( *g_engfuncs.pfnDeltaSetField ) +#define DELTA_UNSET ( *g_engfuncs.pfnDeltaUnsetField ) +#define DELTA_ADDENCODER ( *g_engfuncs.pfnDeltaAddEncoder ) +#define ENGINE_CURRENT_PLAYER ( *g_engfuncs.pfnGetCurrentPlayer ) + +#define ENGINE_CANSKIP ( *g_engfuncs.pfnCanSkipPlayer ) + +#define DELTA_FINDFIELD ( *g_engfuncs.pfnDeltaFindField ) +#define DELTA_SETBYINDEX ( *g_engfuncs.pfnDeltaSetFieldByIndex ) +#define DELTA_UNSETBYINDEX ( *g_engfuncs.pfnDeltaUnsetFieldByIndex ) + +#define ENGINE_GETPHYSINFO ( *g_engfuncs.pfnGetPhysicsInfoString ) + +#define ENGINE_SETGROUPMASK ( *g_engfuncs.pfnSetGroupMask ) + +#define ENGINE_INSTANCE_BASELINE ( *g_engfuncs.pfnCreateInstancedBaseline ) + +#define ENGINE_FORCE_UNMODIFIED ( *g_engfuncs.pfnForceUnmodified ) + +#define PLAYER_CNX_STATS ( *g_engfuncs.pfnGetPlayerStats ) + +#endif //ENGINECALLBACK_H diff --git a/dmc/dlls/explode.cpp b/dmc/dlls/explode.cpp new file mode 100644 index 0000000..4e5f1c1 --- /dev/null +++ b/dmc/dlls/explode.cpp @@ -0,0 +1,273 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== explode.cpp ======================================================== + + Explosion-related code + +*/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "decals.h" +#include "explode.h" + +// Spark Shower +class CShower : public CBaseEntity +{ + void Spawn( void ); + void Think( void ); + void Touch( CBaseEntity *pOther ); + int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +LINK_ENTITY_TO_CLASS( spark_shower, CShower ); + +void CShower::Spawn( void ) +{ + pev->velocity = RANDOM_FLOAT( 200, 300 ) * pev->angles; + pev->velocity.x += RANDOM_FLOAT(-100.f,100.f); + pev->velocity.y += RANDOM_FLOAT(-100.f,100.f); + if ( pev->velocity.z >= 0 ) + pev->velocity.z += 200; + else + pev->velocity.z -= 200; + pev->movetype = MOVETYPE_BOUNCE; + pev->gravity = 0.5; + pev->nextthink = gpGlobals->time + 0.1; + pev->solid = SOLID_NOT; + SET_MODEL( edict(), "models/grenade.mdl"); // Need a model, just use the grenade, we don't draw it anyway + UTIL_SetSize(pev, g_vecZero, g_vecZero ); + pev->effects |= EF_NODRAW; + pev->speed = RANDOM_FLOAT( 0.5, 1.5 ); + + pev->angles = g_vecZero; +} + + +void CShower::Think( void ) +{ + UTIL_Sparks( pev->origin ); + + pev->speed -= 0.1; + if ( pev->speed > 0 ) + pev->nextthink = gpGlobals->time + 0.1; + else + UTIL_Remove( this ); + pev->flags &= ~FL_ONGROUND; +} + +void CShower::Touch( CBaseEntity *pOther ) +{ + if ( pev->flags & FL_ONGROUND ) + pev->velocity = pev->velocity * 0.1; + else + pev->velocity = pev->velocity * 0.6; + + if ( (pev->velocity.x*pev->velocity.x+pev->velocity.y*pev->velocity.y) < 10.0 ) + pev->speed = 0; +} + +class CEnvExplosion : public CBaseMonster +{ +public: + void Spawn( ); + void EXPORT Smoke ( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iMagnitude;// how large is the fireball? how much damage? + int m_spriteScale; // what's the exact fireball sprite scale? +}; + +TYPEDESCRIPTION CEnvExplosion::m_SaveData[] = +{ + DEFINE_FIELD( CEnvExplosion, m_iMagnitude, FIELD_INTEGER ), + DEFINE_FIELD( CEnvExplosion, m_spriteScale, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CEnvExplosion, CBaseMonster ); +LINK_ENTITY_TO_CLASS( env_explosion, CEnvExplosion ); + +void CEnvExplosion::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "iMagnitude")) + { + m_iMagnitude = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CEnvExplosion::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + + pev->movetype = MOVETYPE_NONE; + /* + if ( m_iMagnitude > 250 ) + { + m_iMagnitude = 250; + } + */ + + float flSpriteScale; + flSpriteScale = ( m_iMagnitude - 50) * 0.6; + + /* + if ( flSpriteScale > 50 ) + { + flSpriteScale = 50; + } + */ + if ( flSpriteScale < 10 ) + { + flSpriteScale = 10; + } + + m_spriteScale = (int)flSpriteScale; +} + +void CEnvExplosion::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + TraceResult tr; + + pev->model = iStringNull;//invisible + pev->solid = SOLID_NOT;// intangible + + Vector vecSpot;// trace starts here! + + vecSpot = pev->origin + Vector ( 0 , 0 , 8 ); + + UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -40 ), ignore_monsters, ENT(pev), & tr); + + // Pull out of the wall a bit + if ( tr.flFraction != 1.0 ) + { + pev->origin = tr.vecEndPos + (tr.vecPlaneNormal * (m_iMagnitude - 24) * 0.6); + } + else + { + pev->origin = pev->origin; + } + + // draw decal + if (! ( pev->spawnflags & SF_ENVEXPLOSION_NODECAL)) + { + if ( RANDOM_FLOAT( 0 , 1 ) < 0.5 ) + { + UTIL_DecalTrace( &tr, DECAL_SCORCH1 ); + } + else + { + UTIL_DecalTrace( &tr, DECAL_SCORCH2 ); + } + } + + // draw fireball + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NOFIREBALL ) ) + { + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( (BYTE)m_spriteScale ); // scale * 10 + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + } + else + { + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( 0 ); // no sprite + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + } + + // do damage + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NODAMAGE ) ) + { + RadiusDamage ( pev, pev, m_iMagnitude, CLASS_NONE, DMG_BLAST ); + } + + SetThink( &CEnvExplosion::Smoke ); + pev->nextthink = gpGlobals->time + 0.3; + + // draw sparks + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NOSPARKS ) ) + { + int sparkCount = RANDOM_LONG(0,3); + + for ( int i = 0; i < sparkCount; i++ ) + { + Create( "spark_shower", pev->origin, tr.vecPlaneNormal, NULL ); + } + } +} + +void CEnvExplosion::Smoke( void ) +{ + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NOSMOKE ) ) + { + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( (BYTE)m_spriteScale ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + } + + if ( !(pev->spawnflags & SF_ENVEXPLOSION_REPEATABLE) ) + { + UTIL_Remove( this ); + } +} + + +// HACKHACK -- create one of these and fake a keyvalue to get the right explosion setup +void ExplosionCreate( const Vector ¢er, const Vector &angles, edict_t *pOwner, int magnitude, BOOL doDamage ) +{ + KeyValueData kvd; + char buf[128]; + + CBaseEntity *pExplosion = CBaseEntity::Create( "env_explosion", center, angles, pOwner ); + sprintf( buf, "%3d", magnitude ); + kvd.szKeyName = "iMagnitude"; + kvd.szValue = buf; + pExplosion->KeyValue( &kvd ); + if ( !doDamage ) + pExplosion->pev->spawnflags |= SF_ENVEXPLOSION_NODAMAGE; + + pExplosion->Spawn(); + pExplosion->Use( NULL, NULL, USE_TOGGLE, 0 ); +} diff --git a/dmc/dlls/explode.h b/dmc/dlls/explode.h new file mode 100644 index 0000000..3feb011 --- /dev/null +++ b/dmc/dlls/explode.h @@ -0,0 +1,32 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef EXPLODE_H +#define EXPLODE_H + + +#define SF_ENVEXPLOSION_NODAMAGE ( 1 << 0 ) // when set, ENV_EXPLOSION will not actually inflict damage +#define SF_ENVEXPLOSION_REPEATABLE ( 1 << 1 ) // can this entity be refired? +#define SF_ENVEXPLOSION_NOFIREBALL ( 1 << 2 ) // don't draw the fireball +#define SF_ENVEXPLOSION_NOSMOKE ( 1 << 3 ) // don't draw the smoke +#define SF_ENVEXPLOSION_NODECAL ( 1 << 4 ) // don't make a scorch mark +#define SF_ENVEXPLOSION_NOSPARKS ( 1 << 5 ) // don't make a scorch mark + +extern DLL_GLOBAL short g_sModelIndexFireball; +extern DLL_GLOBAL short g_sModelIndexSmoke; + + +extern void ExplosionCreate( const Vector ¢er, const Vector &angles, edict_t *pOwner, int magnitude, BOOL doDamage ); + +#endif //EXPLODE_H diff --git a/dmc/dlls/extdll.h b/dmc/dlls/extdll.h new file mode 100644 index 0000000..5b8b8e5 --- /dev/null +++ b/dmc/dlls/extdll.h @@ -0,0 +1,99 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef EXTDLL_H +#define EXTDLL_H + +#include "archtypes.h" // DAL + +// +// Global header file for extension DLLs +// + +// Allow "DEBUG" in addition to default "_DEBUG" +#ifdef _DEBUG +#define DEBUG 1 +#endif + +// Silence certain warnings +#pragma warning(disable : 4244) // int or float down-conversion +#pragma warning(disable : 4305) // int or float data truncation +#pragma warning(disable : 4201) // nameless struct/union +#pragma warning(disable : 4514) // unreferenced inline function removed +#pragma warning(disable : 4100) // unreferenced formal parameter + +#ifdef _WIN32 +// Prevent tons of unused windows definitions +#define WIN32_LEAN_AND_MEAN +#define NOWINRES +#define NOSERVICE +#define NOMCX +#define NOIME +#include "windows.h" + +#else // _WIN32 +#define FALSE 0 +#define TRUE (!FALSE) + +typedef uint32 ULONG; +typedef unsigned char BYTE; +typedef int BOOL; + +#define MAX_PATH PATH_MAX + +#include +#include +#include + +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +#define itoa(a,b,c) sprintf(b, "%d", a) +#define _snprintf snprintf +#define _vsnprintf vsnprintf +#endif //_WIN32 + +// Misc C-runtime library headers +#include "stdio.h" +#include "stdlib.h" +#include "math.h" + +// Header file containing definition of globalvars_t and entvars_t +typedef unsigned int func_t; // +typedef unsigned int string_t; // from engine's pr_comp.h; +typedef float vec_t; // needed before including progdefs.h + +// Vector class +#include "vector.h" + +// Defining it as a (bogus) struct helps enforce type-checking +#define vec3_t Vector + +// Shared engine/DLL constants +#include "const.h" +#include "progdefs.h" +#include "edict.h" + +// Shared header describing protocol between engine and DLLs +#include "eiface.h" + +// Shared header between the client DLL and the game DLLs +#include "cdll_dll.h" + +#endif //EXTDLL_H diff --git a/dmc/dlls/func_break.cpp b/dmc/dlls/func_break.cpp new file mode 100644 index 0000000..393c86c --- /dev/null +++ b/dmc/dlls/func_break.cpp @@ -0,0 +1,998 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== bmodels.cpp ======================================================== + + spawn, think, and use functions for entities that use brush models + +*/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "func_break.h" +#include "decals.h" +#include "explode.h" + +extern DLL_GLOBAL Vector g_vecAttackDir; + +// =================== FUNC_Breakable ============================================== + +// Just add more items to the bottom of this array and they will automagically be supported +// This is done instead of just a classname in the FGD so we can control which entities can +// be spawned, and still remain fairly flexible +const char *CBreakable::pSpawnObjects[] = +{ + NULL, // 0 + "item_battery", // 1 + "item_healthkit", // 2 + "weapon_9mmhandgun",// 3 + "ammo_9mmclip", // 4 + "weapon_9mmAR", // 5 + "ammo_9mmAR", // 6 + "ammo_ARgrenades", // 7 + "weapon_shotgun", // 8 + "ammo_buckshot", // 9 + "weapon_crossbow", // 10 + "ammo_crossbow", // 11 + "weapon_357", // 12 + "ammo_357", // 13 + "weapon_rpg", // 14 + "ammo_rpgclip", // 15 + "ammo_gaussclip", // 16 + "weapon_handgrenade",// 17 + "weapon_tripmine", // 18 + "weapon_satchel", // 19 + "weapon_snark", // 20 + "weapon_hornetgun", // 21 +}; + +void CBreakable::KeyValue( KeyValueData* pkvd ) +{ + // UNDONE_WC: explicitly ignoring these fields, but they shouldn't be in the map file! + if (FStrEq(pkvd->szKeyName, "explosion")) + { + if (!stricmp(pkvd->szValue, "directed")) + m_Explosion = expDirected; + else if (!stricmp(pkvd->szValue, "random")) + m_Explosion = expRandom; + else + m_Explosion = expRandom; + + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "material")) + { + int i = atoi( pkvd->szValue); + + // 0:glass, 1:metal, 2:flesh, 3:wood + + if ((i < 0) || (i >= matLastMaterial)) + m_Material = matWood; + else + m_Material = (Materials)i; + + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "deadmodel")) + { + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "shards")) + { +// m_iShards = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "gibmodel") ) + { + m_iszGibModel = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spawnobject") ) + { + int object = atoi( pkvd->szValue ); + if ( object > 0 && object < ARRAYSIZE(pSpawnObjects) ) + m_iszSpawnObject = MAKE_STRING( pSpawnObjects[object] ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "explodemagnitude") ) + { + ExplosionSetMagnitude( atoi( pkvd->szValue ) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "lip") ) + pkvd->fHandled = TRUE; + else + CBaseDelay::KeyValue( pkvd ); +} + + +// +// func_breakable - bmodel that breaks into pieces after taking damage +// +LINK_ENTITY_TO_CLASS( func_breakable, CBreakable ); +TYPEDESCRIPTION CBreakable::m_SaveData[] = +{ + DEFINE_FIELD( CBreakable, m_Material, FIELD_INTEGER ), + DEFINE_FIELD( CBreakable, m_Explosion, FIELD_INTEGER ), + +// Don't need to save/restore these because we precache after restore +// DEFINE_FIELD( CBreakable, m_idShard, FIELD_INTEGER ), + + DEFINE_FIELD( CBreakable, m_angle, FIELD_FLOAT ), + DEFINE_FIELD( CBreakable, m_iszGibModel, FIELD_STRING ), + DEFINE_FIELD( CBreakable, m_iszSpawnObject, FIELD_STRING ), + + // Explosion magnitude is stored in pev->impulse +}; + +IMPLEMENT_SAVERESTORE( CBreakable, CBaseEntity ); + +void CBreakable::Spawn( void ) +{ + Precache( ); + + if ( FBitSet( pev->spawnflags, SF_BREAK_TRIGGER_ONLY ) ) + pev->takedamage = DAMAGE_NO; + else + pev->takedamage = DAMAGE_YES; + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + m_angle = pev->angles.y; + pev->angles.y = 0; + + + SET_MODEL(ENT(pev), STRING(pev->model) );//set size and link into world. + + SetTouch( &CBreakable::BreakTouch ); + if ( FBitSet( pev->spawnflags, SF_BREAK_TRIGGER_ONLY ) ) // Only break on trigger + SetTouch( NULL ); + + // Flag unbreakable glass as "worldbrush" so it will block ALL tracelines + if ( !IsBreakable() && pev->rendermode != kRenderNormal ) + pev->flags |= FL_WORLDBRUSH; +} + + +const char *CBreakable::pSoundsWood[] = +{ + "debris/wood1.wav", + "debris/wood2.wav", + "debris/wood3.wav", +}; + +const char *CBreakable::pSoundsFlesh[] = +{ + "debris/flesh1.wav", + "debris/flesh2.wav", + "debris/flesh3.wav", + "debris/flesh5.wav", + "debris/flesh6.wav", + "debris/flesh7.wav", +}; + +const char *CBreakable::pSoundsMetal[] = +{ + "debris/metal1.wav", + "debris/metal2.wav", + "debris/metal3.wav", +}; + +const char *CBreakable::pSoundsConcrete[] = +{ + "debris/concrete1.wav", + "debris/concrete2.wav", + "debris/concrete3.wav", +}; + + +const char *CBreakable::pSoundsGlass[] = +{ + "debris/glass1.wav", + "debris/glass2.wav", + "debris/glass3.wav", +}; + +const char **CBreakable::MaterialSoundList( Materials precacheMaterial, int &soundCount ) +{ + const char **pSoundList = NULL; + + switch ( precacheMaterial ) + { + case matWood: + pSoundList = pSoundsWood; + soundCount = ARRAYSIZE(pSoundsWood); + break; + case matFlesh: + pSoundList = pSoundsFlesh; + soundCount = ARRAYSIZE(pSoundsFlesh); + break; + case matComputer: + case matUnbreakableGlass: + case matGlass: + pSoundList = pSoundsGlass; + soundCount = ARRAYSIZE(pSoundsGlass); + break; + + case matMetal: + pSoundList = pSoundsMetal; + soundCount = ARRAYSIZE(pSoundsMetal); + break; + + case matCinderBlock: + case matRocks: + pSoundList = pSoundsConcrete; + soundCount = ARRAYSIZE(pSoundsConcrete); + break; + + + case matCeilingTile: + case matNone: + default: + soundCount = 0; + break; + } + + return pSoundList; +} + +void CBreakable::MaterialSoundPrecache( Materials precacheMaterial ) +{ + const char **pSoundList; + int i, soundCount = 0; + + pSoundList = MaterialSoundList( precacheMaterial, soundCount ); + + for ( i = 0; i < soundCount; i++ ) + { + PRECACHE_SOUND( (char *)pSoundList[i] ); + } +} + +void CBreakable::MaterialSoundRandom( edict_t *pEdict, Materials soundMaterial, float volume ) +{ + const char **pSoundList; + int soundCount = 0; + + pSoundList = MaterialSoundList( soundMaterial, soundCount ); + + if ( soundCount ) + EMIT_SOUND( pEdict, CHAN_BODY, pSoundList[ RANDOM_LONG(0,soundCount-1) ], volume, 1.0 ); +} + + +void CBreakable::Precache( void ) +{ + const char *pGibName; + + switch (m_Material) + { + case matWood: + pGibName = "models/woodgibs.mdl"; + + PRECACHE_SOUND("debris/bustcrate1.wav"); + PRECACHE_SOUND("debris/bustcrate2.wav"); + break; + case matFlesh: + pGibName = "models/fleshgibs.mdl"; + + PRECACHE_SOUND("debris/bustflesh1.wav"); + PRECACHE_SOUND("debris/bustflesh2.wav"); + break; + case matComputer: + PRECACHE_SOUND("buttons/spark5.wav"); + PRECACHE_SOUND("buttons/spark6.wav"); + pGibName = "models/computergibs.mdl"; + + PRECACHE_SOUND("debris/bustmetal1.wav"); + PRECACHE_SOUND("debris/bustmetal2.wav"); + break; + + case matUnbreakableGlass: + case matGlass: + pGibName = "models/glassgibs.mdl"; + + PRECACHE_SOUND("debris/bustglass1.wav"); + PRECACHE_SOUND("debris/bustglass2.wav"); + break; + case matMetal: + pGibName = "models/metalplategibs.mdl"; + + PRECACHE_SOUND("debris/bustmetal1.wav"); + PRECACHE_SOUND("debris/bustmetal2.wav"); + break; + case matCinderBlock: + pGibName = "models/cindergibs.mdl"; + + PRECACHE_SOUND("debris/bustconcrete1.wav"); + PRECACHE_SOUND("debris/bustconcrete2.wav"); + break; + case matRocks: + pGibName = "models/rockgibs.mdl"; + + PRECACHE_SOUND("debris/bustconcrete1.wav"); + PRECACHE_SOUND("debris/bustconcrete2.wav"); + break; + case matCeilingTile: + pGibName = "models/ceilinggibs.mdl"; + + PRECACHE_SOUND ("debris/bustceiling.wav"); + break; + } + MaterialSoundPrecache( m_Material ); + if ( m_iszGibModel ) + pGibName = STRING(m_iszGibModel); + + m_idShard = PRECACHE_MODEL( (char *)pGibName ); + + // Precache the spawn item's data + if ( m_iszSpawnObject ) + UTIL_PrecacheOther( (char *)STRING( m_iszSpawnObject ) ); +} + +// play shard sound when func_breakable takes damage. +// the more damage, the louder the shard sound. + + +void CBreakable::DamageSound( void ) +{ + int pitch; + float fvol; + char *rgpsz[6]; + int i; + int material = m_Material; + +// if (RANDOM_LONG(0,1)) +// return; + + if (RANDOM_LONG(0,2)) + pitch = PITCH_NORM; + else + pitch = 95 + RANDOM_LONG(0,34); + + fvol = RANDOM_FLOAT(0.75, 1.0); + + if (material == matComputer && RANDOM_LONG(0,1)) + material = matMetal; + + switch (material) + { + case matComputer: + case matGlass: + case matUnbreakableGlass: + rgpsz[0] = "debris/glass1.wav"; + rgpsz[1] = "debris/glass2.wav"; + rgpsz[2] = "debris/glass3.wav"; + i = 3; + break; + + case matWood: + rgpsz[0] = "debris/wood1.wav"; + rgpsz[1] = "debris/wood2.wav"; + rgpsz[2] = "debris/wood3.wav"; + i = 3; + break; + + case matMetal: + rgpsz[0] = "debris/metal1.wav"; + rgpsz[1] = "debris/metal3.wav"; + rgpsz[2] = "debris/metal2.wav"; + i = 2; + break; + + case matFlesh: + rgpsz[0] = "debris/flesh1.wav"; + rgpsz[1] = "debris/flesh2.wav"; + rgpsz[2] = "debris/flesh3.wav"; + rgpsz[3] = "debris/flesh5.wav"; + rgpsz[4] = "debris/flesh6.wav"; + rgpsz[5] = "debris/flesh7.wav"; + i = 6; + break; + + case matRocks: + case matCinderBlock: + rgpsz[0] = "debris/concrete1.wav"; + rgpsz[1] = "debris/concrete2.wav"; + rgpsz[2] = "debris/concrete3.wav"; + i = 3; + break; + + case matCeilingTile: + // UNDONE: no ceiling tile shard sound yet + i = 0; + break; + } + + if (i) + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, rgpsz[RANDOM_LONG(0,i-1)], fvol, ATTN_NORM, 0, pitch); +} + +void CBreakable::BreakTouch( CBaseEntity *pOther ) +{ + float flDamage; + entvars_t* pevToucher = pOther->pev; + + // only players can break these right now + if ( !pOther->IsPlayer() || !IsBreakable() ) + { + return; + } + + if ( FBitSet ( pev->spawnflags, SF_BREAK_TOUCH ) ) + {// can be broken when run into + flDamage = pevToucher->velocity.Length() * 0.01; + + if (flDamage >= pev->health) + { + SetTouch( NULL ); + TakeDamage(pevToucher, pevToucher, flDamage, DMG_CRUSH); + + // do a little damage to player if we broke glass or computer + pOther->TakeDamage( pev, pev, flDamage/4, DMG_SLASH ); + } + } + + if ( FBitSet ( pev->spawnflags, SF_BREAK_PRESSURE ) && pevToucher->absmin.z >= pev->maxs.z - 2 ) + {// can be broken when stood upon + + // play creaking sound here. + DamageSound(); + + SetThink ( &CBreakable::Die ); + SetTouch( NULL ); + + if ( m_flDelay == 0 ) + {// !!!BUGBUG - why doesn't zero delay work? + m_flDelay = 0.1; + } + + pev->nextthink = pev->ltime + m_flDelay; + + } + +} + + +// +// Smash the our breakable object +// + +// Break when triggered +void CBreakable::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( IsBreakable() ) + { + pev->angles.y = m_angle; + UTIL_MakeVectors(pev->angles); + g_vecAttackDir = gpGlobals->v_forward; + + Die(); + } +} + + +void CBreakable::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + // random spark if this is a 'computer' object + if (RANDOM_LONG(0,1) ) + { + switch( m_Material ) + { + case matComputer: + { + UTIL_Sparks( ptr->vecEndPos ); + + float flVolume = RANDOM_FLOAT ( 0.7 , 1.0 );//random volume range + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM); break; + } + } + break; + + case matUnbreakableGlass: + UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT(0.5,1.5) ); + break; + } + } + + CBaseDelay::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + +//========================================================= +// Special takedamage for func_breakable. Allows us to make +// exceptions that are breakable-specific +// bitsDamageType indicates the type of damage sustained ie: DMG_CRUSH +//========================================================= +int CBreakable :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecTemp; + + // if Attacker == Inflictor, the attack was a melee or other instant-hit attack. + // (that is, no actual entity projectile was involved in the attack so use the shooter's origin). + if ( pevAttacker == pevInflictor ) + { + vecTemp = pevInflictor->origin - ( pev->absmin + ( pev->size * 0.5 ) ); + + // if a client hit the breakable with a crowbar, and breakable is crowbar-sensitive, break it now. + if ( FBitSet ( pevAttacker->flags, FL_CLIENT ) && + FBitSet ( pev->spawnflags, SF_BREAK_CROWBAR ) && (bitsDamageType & DMG_CLUB)) + flDamage = pev->health; + } + else + // an actual missile was involved. + { + vecTemp = pevInflictor->origin - ( pev->absmin + ( pev->size * 0.5 ) ); + } + + if (!IsBreakable()) + return 0; + + // Breakables take double damage from the crowbar + if ( bitsDamageType & DMG_CLUB ) + flDamage *= 2; + + // Boxes / glass / etc. don't take much poison damage, just the impact of the dart - consider that 10% + if ( bitsDamageType & DMG_POISON ) + flDamage *= 0.1; + +// this global is still used for glass and other non-monster killables, along with decals. + g_vecAttackDir = vecTemp.Normalize(); + +// do the damage + pev->health -= flDamage; + if (pev->health <= 0) + { + Killed( pevAttacker, GIB_NORMAL ); + Die(); + return 0; + } + + // Make a shard noise each time func breakable is hit. + // Don't play shard noise if cbreakable actually died. + + DamageSound(); + + return 1; +} + + +void CBreakable::Die( void ) +{ + Vector vecSpot;// shard origin + Vector vecVelocity;// shard velocity + CBaseEntity *pEntity = NULL; + char cFlag = 0; + int pitch; + float fvol; + + pitch = 95 + RANDOM_LONG(0,29); + + if (pitch > 97 && pitch < 103) + pitch = 100; + + // The more negative pev->health, the louder + // the sound should be. + + fvol = RANDOM_FLOAT(0.85, 1.0) + (abs(pev->health) / 100.0); + + if (fvol > 1.0) + fvol = 1.0; + + + switch (m_Material) + { + case matGlass: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustglass1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustglass2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_GLASS; + break; + + case matWood: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustcrate1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustcrate2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_WOOD; + break; + + case matComputer: + case matMetal: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustmetal1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustmetal2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_METAL; + break; + + case matFlesh: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustflesh1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustflesh2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_FLESH; + break; + + case matRocks: + case matCinderBlock: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustconcrete1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustconcrete2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_CONCRETE; + break; + + case matCeilingTile: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustceiling.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + + + if (m_Explosion == expDirected) + vecVelocity = g_vecAttackDir * 200; + else + { + vecVelocity.x = 0; + vecVelocity.y = 0; + vecVelocity.z = 0; + } + + vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z ); + + // size + WRITE_COORD( pev->size.x); + WRITE_COORD( pev->size.y); + WRITE_COORD( pev->size.z); + + // velocity + WRITE_COORD( vecVelocity.x ); + WRITE_COORD( vecVelocity.y ); + WRITE_COORD( vecVelocity.z ); + + // randomization + WRITE_BYTE( 10 ); + + // Model + WRITE_SHORT( m_idShard ); //model id# + + // # of shards + WRITE_BYTE( 0 ); // let client decide + + // duration + WRITE_BYTE( 25 );// 2.5 seconds + + // flags + WRITE_BYTE( cFlag ); + MESSAGE_END(); + + float size = pev->size.x; + if ( size < pev->size.y ) + size = pev->size.y; + if ( size < pev->size.z ) + size = pev->size.z; + + // !!! HACK This should work! + // Build a box above the entity that looks like an 8 pixel high sheet + Vector mins = pev->absmin; + Vector maxs = pev->absmax; + mins.z = pev->absmax.z; + maxs.z += 8; + + // BUGBUG -- can only find 256 entities on a breakable -- should be enough + CBaseEntity *pList[256]; + int count = UTIL_EntitiesInBox( pList, 256, mins, maxs, FL_ONGROUND ); + if ( count ) + { + for ( int i = 0; i < count; i++ ) + { + ClearBits( pList[i]->pev->flags, FL_ONGROUND ); + pList[i]->pev->groundentity = NULL; + } + } + + // Don't fire something that could fire myself + pev->targetname = 0; + + pev->solid = SOLID_NOT; + // Fire targets on break + SUB_UseTargets( NULL, USE_TOGGLE, 0 ); + + SetThink( &CBreakable::SUB_Remove ); + pev->nextthink = pev->ltime + 0.1; + if ( m_iszSpawnObject ) + CBaseEntity::Create( (char *)STRING(m_iszSpawnObject), VecBModelOrigin(pev), pev->angles, edict() ); + + + if ( Explodable() ) + { + ExplosionCreate( Center(), pev->angles, edict(), ExplosionMagnitude(), TRUE ); + } +} + + + +BOOL CBreakable :: IsBreakable( void ) +{ + return m_Material != matUnbreakableGlass; +} + + +int CBreakable :: DamageDecal( int bitsDamageType ) +{ + if ( m_Material == matGlass ) + return DECAL_GLASSBREAK1 + RANDOM_LONG(0,2); + + if ( m_Material == matUnbreakableGlass ) + return DECAL_BPROOF1; + + return CBaseEntity::DamageDecal( bitsDamageType ); +} + + +class CPushable : public CBreakable +{ +public: + void Spawn ( void ); + void Precache( void ); + void Touch ( CBaseEntity *pOther ); + void Move( CBaseEntity *pMover, int push ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT StopSound( void ); +// virtual void SetActivator( CBaseEntity *pActivator ) { m_pPusher = pActivator; } + + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_CONTINUOUS_USE; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + inline float MaxSpeed( void ) { return m_maxSpeed; } + + // breakables use an overridden takedamage + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + + static TYPEDESCRIPTION m_SaveData[]; + + static char *m_soundNames[3]; + int m_lastSound; // no need to save/restore, just keeps the same sound from playing twice in a row + float m_maxSpeed; + float m_soundTime; +}; + +TYPEDESCRIPTION CPushable::m_SaveData[] = +{ + DEFINE_FIELD( CPushable, m_maxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CPushable, m_soundTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CPushable, CBreakable ); + +LINK_ENTITY_TO_CLASS( func_pushable, CPushable ); + +char *CPushable :: m_soundNames[3] = { "debris/pushbox1.wav", "debris/pushbox2.wav", "debris/pushbox3.wav" }; + + +void CPushable :: Spawn( void ) +{ + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + CBreakable::Spawn(); + else + Precache( ); + + pev->movetype = MOVETYPE_PUSHSTEP; + pev->solid = SOLID_BBOX; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + if ( pev->friction > 399 ) + pev->friction = 399; + + m_maxSpeed = 400 - pev->friction; + SetBits( pev->flags, FL_FLOAT ); + pev->friction = 0; + + pev->origin.z += 1; // Pick up off of the floor + UTIL_SetOrigin( pev, pev->origin ); + + // Multiply by area of the box's cross-section (assume 1000 units^3 standard volume) + pev->skin = ( pev->skin * (pev->maxs.x - pev->mins.x) * (pev->maxs.y - pev->mins.y) ) * 0.0005; + m_soundTime = 0; +} + + +void CPushable :: Precache( void ) +{ + for ( int i = 0; i < 3; i++ ) + PRECACHE_SOUND( m_soundNames[i] ); + + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + CBreakable::Precache( ); +} + + +void CPushable :: KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "size") ) + { + int bbox = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + switch( bbox ) + { + case 0: // Point + UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8)); + break; + + case 2: // Big Hull!?!? !!!BUGBUG Figure out what this hull really is + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN*2, VEC_DUCK_HULL_MAX*2); + break; + + case 3: // Player duck + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + break; + + default: + case 1: // Player + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + break; + } + + } + else if ( FStrEq(pkvd->szKeyName, "buoyancy") ) + { + pev->skin = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBreakable::KeyValue( pkvd ); +} + + +// Pull the func_pushable +void CPushable :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !pActivator || !pActivator->IsPlayer() ) + { + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + this->CBreakable::Use( pActivator, pCaller, useType, value ); + return; + } + + if ( pActivator->pev->velocity != g_vecZero ) + Move( pActivator, 0 ); +} + + +void CPushable :: Touch( CBaseEntity *pOther ) +{ + if ( FClassnameIs( pOther->pev, "worldspawn" ) ) + return; + + Move( pOther, 1 ); +} + + +void CPushable :: Move( CBaseEntity *pOther, int push ) +{ + entvars_t* pevToucher = pOther->pev; + int playerTouch = 0; + + // Is entity standing on this pushable ? + if ( FBitSet(pevToucher->flags,FL_ONGROUND) && pevToucher->groundentity && VARS(pevToucher->groundentity) == pev ) + { + // Only push if floating + if ( pev->waterlevel > 0 ) + pev->velocity.z += pevToucher->velocity.z * 0.1; + + return; + } + + + if ( pOther->IsPlayer() ) + { + if ( push && !(pevToucher->button & (IN_FORWARD|IN_USE)) ) // Don't push unless the player is pushing forward and NOT use (pull) + return; + playerTouch = 1; + } + + float factor; + + if ( playerTouch ) + { + if ( !(pevToucher->flags & FL_ONGROUND) ) // Don't push away from jumping/falling players unless in water + { + if ( pev->waterlevel < 1 ) + return; + else + factor = 0.1; + } + else + factor = 1; + } + else + factor = 0.25; + + pev->velocity.x += pevToucher->velocity.x * factor; + pev->velocity.y += pevToucher->velocity.y * factor; + + float length = sqrt( pev->velocity.x * pev->velocity.x + pev->velocity.y * pev->velocity.y ); + if ( push && (length > MaxSpeed()) ) + { + pev->velocity.x = (pev->velocity.x * MaxSpeed() / length ); + pev->velocity.y = (pev->velocity.y * MaxSpeed() / length ); + } + if ( playerTouch ) + { + pevToucher->velocity.x = pev->velocity.x; + pevToucher->velocity.y = pev->velocity.y; + if ( (gpGlobals->time - m_soundTime) > 0.7 ) + { + m_soundTime = gpGlobals->time; + if ( length > 0 && FBitSet(pev->flags,FL_ONGROUND) ) + { + m_lastSound = RANDOM_LONG(0,2); + EMIT_SOUND(ENT(pev), CHAN_WEAPON, m_soundNames[m_lastSound], 0.5, ATTN_NORM); + // SetThink( StopSound ); + // pev->nextthink = pev->ltime + 0.1; + } + else + STOP_SOUND( ENT(pev), CHAN_WEAPON, m_soundNames[m_lastSound] ); + } + } +} + +#if 0 +void CPushable::StopSound( void ) +{ + Vector dist = pev->oldorigin - pev->origin; + if ( dist.Length() <= 0 ) + STOP_SOUND( ENT(pev), CHAN_WEAPON, m_soundNames[m_lastSound] ); +} +#endif + +int CPushable::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + return CBreakable::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); + + return 1; +} + diff --git a/dmc/dlls/func_break.h b/dmc/dlls/func_break.h new file mode 100644 index 0000000..9bb281d --- /dev/null +++ b/dmc/dlls/func_break.h @@ -0,0 +1,74 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef FUNC_BREAK_H +#define FUNC_BREAK_H + +typedef enum { expRandom, expDirected} Explosions; +typedef enum { matGlass = 0, matWood, matMetal, matFlesh, matCinderBlock, matCeilingTile, matComputer, matUnbreakableGlass, matRocks, matNone, matLastMaterial } Materials; + +#define NUM_SHARDS 6 // this many shards spawned when breakable objects break; + +class CBreakable : public CBaseDelay +{ +public: + // basic functions + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData* pkvd); + void EXPORT BreakTouch( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void DamageSound( void ); + + // breakables use an overridden takedamage + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + // To spark when hit + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); + + BOOL IsBreakable( void ); + BOOL SparkWhenHit( void ); + + int DamageDecal( int bitsDamageType ); + + void EXPORT Die( void ); + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + inline BOOL Explodable( void ) { return ExplosionMagnitude() > 0; } + inline int ExplosionMagnitude( void ) { return pev->impulse; } + inline void ExplosionSetMagnitude( int magnitude ) { pev->impulse = magnitude; } + + static void MaterialSoundPrecache( Materials precacheMaterial ); + static void MaterialSoundRandom( edict_t *pEdict, Materials soundMaterial, float volume ); + static const char **MaterialSoundList( Materials precacheMaterial, int &soundCount ); + + static const char *pSoundsWood[]; + static const char *pSoundsFlesh[]; + static const char *pSoundsGlass[]; + static const char *pSoundsMetal[]; + static const char *pSoundsConcrete[]; + static const char *pSpawnObjects[]; + + static TYPEDESCRIPTION m_SaveData[]; + + Materials m_Material; + Explosions m_Explosion; + int m_idShard; + float m_angle; + int m_iszGibModel; + int m_iszSpawnObject; +}; + +#endif // FUNC_BREAK_H diff --git a/dmc/dlls/func_tank.cpp b/dmc/dlls/func_tank.cpp new file mode 100644 index 0000000..2076285 --- /dev/null +++ b/dmc/dlls/func_tank.cpp @@ -0,0 +1,1039 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "effects.h" +#include "weapons.h" +#include "explode.h" + +#include "player.h" + + +#define SF_TANK_ACTIVE 0x0001 +#define SF_TANK_PLAYER 0x0002 +#define SF_TANK_HUMANS 0x0004 +#define SF_TANK_ALIENS 0x0008 +#define SF_TANK_LINEOFSIGHT 0x0010 +#define SF_TANK_CANCONTROL 0x0020 +#define SF_TANK_SOUNDON 0x8000 + +enum TANKBULLET +{ + TANK_BULLET_NONE = 0, + TANK_BULLET_9MM = 1, + TANK_BULLET_MP5 = 2, + TANK_BULLET_12MM = 3, +}; + +// Custom damage +// env_laser (duration is 0.5 rate of fire) +// rockets +// explosion? + +class CFuncTank : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + void TrackTarget( void ); + + virtual void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); + virtual Vector UpdateTargetPosition( CBaseEntity *pTarget ) + { + return pTarget->BodyTarget( pev->origin ); + } + + void StartRotSound( void ); + void StopRotSound( void ); + + // Bmodels don't go across transitions + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + inline BOOL IsActive( void ) { return (pev->spawnflags & SF_TANK_ACTIVE)?TRUE:FALSE; } + inline void TankActivate( void ) { pev->spawnflags |= SF_TANK_ACTIVE; pev->nextthink = pev->ltime + 0.1; m_fireLast = 0; } + inline void TankDeactivate( void ) { pev->spawnflags &= ~SF_TANK_ACTIVE; m_fireLast = 0; StopRotSound(); } + inline BOOL CanFire( void ) { return (gpGlobals->time - m_lastSightTime) < m_persist; } + BOOL InRange( float range ); + + // Acquire a target. pPlayer is a player in the PVS + edict_t *FindTarget( edict_t *pPlayer ); + + void TankTrace( const Vector &vecStart, const Vector &vecForward, const Vector &vecSpread, TraceResult &tr ); + + Vector BarrelPosition( void ) + { + Vector forward, right, up; + UTIL_MakeVectorsPrivate( pev->angles, forward, right, up ); + return pev->origin + (forward * m_barrelPos.x) + (right * m_barrelPos.y) + (up * m_barrelPos.z); + } + + void AdjustAnglesForBarrel( Vector &angles, float distance ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + BOOL OnControls( entvars_t *pevTest ); + BOOL StartControl( CBasePlayer* pController ); + void StopControl( void ); + void ControllerPostFrame( void ); + + +protected: + CBasePlayer* m_pController; + float m_flNextAttack; + Vector m_vecControllerUsePos; + + float m_yawCenter; // "Center" yaw + float m_yawRate; // Max turn rate to track targets + float m_yawRange; // Range of turning motion (one-sided: 30 is +/- 30 degress from center) + // Zero is full rotation + float m_yawTolerance; // Tolerance angle + + float m_pitchCenter; // "Center" pitch + float m_pitchRate; // Max turn rate on pitch + float m_pitchRange; // Range of pitch motion as above + float m_pitchTolerance; // Tolerance angle + + float m_fireLast; // Last time I fired + float m_fireRate; // How many rounds/second + float m_lastSightTime;// Last time I saw target + float m_persist; // Persistence of firing (how long do I shoot when I can't see) + float m_minRange; // Minimum range to aim/track + float m_maxRange; // Max range to aim/track + + Vector m_barrelPos; // Length of the freakin barrel + float m_spriteScale; // Scale of any sprites we shoot + int m_iszSpriteSmoke; + int m_iszSpriteFlash; + TANKBULLET m_bulletType; // Bullet type + int m_iBulletDamage; // 0 means use Bullet type's default damage + + Vector m_sightOrigin; // Last sight of target + int m_spread; // firing spread + int m_iszMaster; // Master entity (game_team_master or multisource) +}; + + +TYPEDESCRIPTION CFuncTank::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTank, m_yawCenter, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_yawRate, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_yawRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_yawTolerance, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchCenter, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchRate, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchTolerance, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_fireLast, FIELD_TIME ), + DEFINE_FIELD( CFuncTank, m_fireRate, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_lastSightTime, FIELD_TIME ), + DEFINE_FIELD( CFuncTank, m_persist, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_minRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_maxRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_barrelPos, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTank, m_spriteScale, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_iszSpriteSmoke, FIELD_STRING ), + DEFINE_FIELD( CFuncTank, m_iszSpriteFlash, FIELD_STRING ), + DEFINE_FIELD( CFuncTank, m_bulletType, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTank, m_sightOrigin, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTank, m_spread, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTank, m_pController, FIELD_CLASSPTR ), + DEFINE_FIELD( CFuncTank, m_vecControllerUsePos, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTank, m_flNextAttack, FIELD_TIME ), + DEFINE_FIELD( CFuncTank, m_iBulletDamage, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTank, m_iszMaster, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTank, CBaseEntity ); + +static Vector gTankSpread[] = +{ + Vector( 0, 0, 0 ), // perfect + Vector( 0.025, 0.025, 0.025 ), // small cone + Vector( 0.05, 0.05, 0.05 ), // medium cone + Vector( 0.1, 0.1, 0.1 ), // large cone + Vector( 0.25, 0.25, 0.25 ), // extra-large cone +}; +#define MAX_FIRING_SPREADS ARRAYSIZE(gTankSpread) + + +void CFuncTank :: Spawn( void ) +{ + Precache(); + + pev->movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + pev->solid = SOLID_BSP; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_yawCenter = pev->angles.y; + m_pitchCenter = pev->angles.x; + + if ( IsActive() ) + pev->nextthink = pev->ltime + 1.0; + + m_sightOrigin = BarrelPosition(); // Point at the end of the barrel + + if ( m_fireRate <= 0 ) + m_fireRate = 1; + if ( m_spread > MAX_FIRING_SPREADS ) + m_spread = 0; + + pev->oldorigin = pev->origin; +} + + +void CFuncTank :: Precache( void ) +{ + if ( m_iszSpriteSmoke ) + PRECACHE_MODEL( (char *)STRING(m_iszSpriteSmoke) ); + if ( m_iszSpriteFlash ) + PRECACHE_MODEL( (char *)STRING(m_iszSpriteFlash) ); + + if ( pev->noise ) + PRECACHE_SOUND( (char *)STRING(pev->noise) ); +} + + +void CFuncTank :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "yawrate")) + { + m_yawRate = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "yawrange")) + { + m_yawRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "yawtolerance")) + { + m_yawTolerance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitchrange")) + { + m_pitchRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitchrate")) + { + m_pitchRate = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitchtolerance")) + { + m_pitchTolerance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "firerate")) + { + m_fireRate = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "barrel")) + { + m_barrelPos.x = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "barrely")) + { + m_barrelPos.y = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "barrelz")) + { + m_barrelPos.z = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spritescale")) + { + m_spriteScale = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spritesmoke")) + { + m_iszSpriteSmoke = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spriteflash")) + { + m_iszSpriteFlash = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "rotatesound")) + { + pev->noise = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "persistence")) + { + m_persist = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "bullet")) + { + m_bulletType = (TANKBULLET)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "bullet_damage" )) + { + m_iBulletDamage = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "firespread")) + { + m_spread = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "minRange")) + { + m_minRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "maxRange")) + { + m_maxRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "master")) + { + m_iszMaster = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +////////////// START NEW STUFF ////////////// + +//================================================================================== +// TANK CONTROLLING +BOOL CFuncTank :: OnControls( entvars_t *pevTest ) +{ + if ( !(pev->spawnflags & SF_TANK_CANCONTROL) ) + return FALSE; + + Vector offset = pevTest->origin - pev->origin; + + if ( (m_vecControllerUsePos - pevTest->origin).Length() < 30 ) + return TRUE; + + return FALSE; +} + +BOOL CFuncTank :: StartControl( CBasePlayer *pController ) +{ + if ( m_pController != NULL ) + return FALSE; + + // Team only or disabled? + if ( m_iszMaster ) + { + if ( !UTIL_IsMasterTriggered( m_iszMaster, pController ) ) + return FALSE; + } + + ALERT( at_console, "using TANK!\n"); + + m_pController = pController; + if ( m_pController->m_pActiveItem ) + { + m_pController->m_pActiveItem->Holster(); + m_pController->pev->weaponmodel = 0; + m_pController->pev->viewmodel = 0; + + } + + m_pController->m_iHideHUD |= HIDEHUD_WEAPONS; + m_vecControllerUsePos = m_pController->pev->origin; + + pev->nextthink = pev->ltime + 0.1; + + return TRUE; +} + +void CFuncTank :: StopControl() +{ + // TODO: bring back the controllers current weapon + if ( !m_pController ) + return; + + if ( m_pController->m_pActiveItem ) + m_pController->m_pActiveItem->Deploy(); + + ALERT( at_console, "stopped using TANK\n"); + + m_pController->m_iHideHUD &= ~HIDEHUD_WEAPONS; + + pev->nextthink = 0; + m_pController = NULL; + + if ( IsActive() ) + pev->nextthink = pev->ltime + 1.0; +} + +// Called each frame by the player's ItemPostFrame +void CFuncTank :: ControllerPostFrame( void ) +{ + ASSERT(m_pController != NULL); + + if ( gpGlobals->time < m_flNextAttack ) + return; + + if ( m_pController->pev->button & IN_ATTACK ) + { + Vector vecForward; + UTIL_MakeVectorsPrivate( pev->angles, vecForward, NULL, NULL ); + + m_fireLast = gpGlobals->time - (1/m_fireRate) - 0.01; // to make sure the gun doesn't fire too many bullets + + Fire( BarrelPosition(), vecForward, m_pController->pev ); + + // HACKHACK -- make some noise (that the AI can hear) + if ( m_pController && m_pController->IsPlayer() ) + ((CBasePlayer *)m_pController)->m_iWeaponVolume = LOUD_GUN_VOLUME; + + m_flNextAttack = gpGlobals->time + (1/m_fireRate); + } +} +////////////// END NEW STUFF ////////////// + + +void CFuncTank :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->spawnflags & SF_TANK_CANCONTROL ) + { // player controlled turret + + if ( pActivator->Classify() != CLASS_PLAYER ) + return; + + if ( value == 2 && useType == USE_SET ) + { + ControllerPostFrame(); + } + else if ( !m_pController && useType != USE_OFF ) + { + ((CBasePlayer*)pActivator)->m_pTank = this; + StartControl( (CBasePlayer*)pActivator ); + } + else + { + StopControl(); + } + } + else + { + if ( !ShouldToggle( useType, IsActive() ) ) + return; + + if ( IsActive() ) + TankDeactivate(); + else + TankActivate(); + } +} + + +edict_t *CFuncTank :: FindTarget( edict_t *pPlayer ) +{ + return pPlayer; +} + + + +BOOL CFuncTank :: InRange( float range ) +{ + if ( range < m_minRange ) + return FALSE; + if ( m_maxRange > 0 && range > m_maxRange ) + return FALSE; + + return TRUE; +} + + +void CFuncTank :: Think( void ) +{ + pev->avelocity = g_vecZero; + TrackTarget(); + + if ( fabs(pev->avelocity.x) > 1 || fabs(pev->avelocity.y) > 1 ) + StartRotSound(); + else + StopRotSound(); +} + +void CFuncTank::TrackTarget( void ) +{ + TraceResult tr; + edict_t *pPlayer = FIND_CLIENT_IN_PVS( edict() ); + BOOL updateTime = FALSE, lineOfSight; + Vector angles, direction, targetPosition, barrelEnd; + edict_t *pTarget; + + // Get a position to aim for + if (m_pController) + { + // Tanks attempt to mirror the player's angles + angles = m_pController->pev->v_angle; + angles[0] = 0 - angles[0]; + pev->nextthink = pev->ltime + 0.05; + } + else + { + if ( IsActive() ) + pev->nextthink = pev->ltime + 0.1; + else + return; + + if ( FNullEnt( pPlayer ) ) + { + if ( IsActive() ) + pev->nextthink = pev->ltime + 2; // Wait 2 secs + return; + } + pTarget = FindTarget( pPlayer ); + if ( !pTarget ) + return; + + // Calculate angle needed to aim at target + barrelEnd = BarrelPosition(); + targetPosition = pTarget->v.origin + pTarget->v.view_ofs; + float range = (targetPosition - barrelEnd).Length(); + + if ( !InRange( range ) ) + return; + + UTIL_TraceLine( barrelEnd, targetPosition, dont_ignore_monsters, edict(), &tr ); + + lineOfSight = FALSE; + // No line of sight, don't track + if ( tr.flFraction == 1.0 || tr.pHit == pTarget ) + { + lineOfSight = TRUE; + + CBaseEntity *pInstance = CBaseEntity::Instance(pTarget); + if ( InRange( range ) && pInstance && pInstance->IsAlive() ) + { + updateTime = TRUE; + m_sightOrigin = UpdateTargetPosition( pInstance ); + } + } + + // Track sight origin + +// !!! I'm not sure what i changed + direction = m_sightOrigin - pev->origin; +// direction = m_sightOrigin - barrelEnd; + angles = UTIL_VecToAngles( direction ); + + // Calculate the additional rotation to point the end of the barrel at the target (not the gun's center) + AdjustAnglesForBarrel( angles, direction.Length() ); + } + + angles.x = -angles.x; + + // Force the angles to be relative to the center position + angles.y = m_yawCenter + UTIL_AngleDistance( angles.y, m_yawCenter ); + angles.x = m_pitchCenter + UTIL_AngleDistance( angles.x, m_pitchCenter ); + + // Limit against range in y + if ( angles.y > m_yawCenter + m_yawRange ) + { + angles.y = m_yawCenter + m_yawRange; + updateTime = FALSE; // Don't update if you saw the player, but out of range + } + else if ( angles.y < (m_yawCenter - m_yawRange) ) + { + angles.y = (m_yawCenter - m_yawRange); + updateTime = FALSE; // Don't update if you saw the player, but out of range + } + + if ( updateTime ) + m_lastSightTime = gpGlobals->time; + + // Move toward target at rate or less + float distY = UTIL_AngleDistance( angles.y, pev->angles.y ); + pev->avelocity.y = distY * 10; + if ( pev->avelocity.y > m_yawRate ) + pev->avelocity.y = m_yawRate; + else if ( pev->avelocity.y < -m_yawRate ) + pev->avelocity.y = -m_yawRate; + + // Limit against range in x + if ( angles.x > m_pitchCenter + m_pitchRange ) + angles.x = m_pitchCenter + m_pitchRange; + else if ( angles.x < m_pitchCenter - m_pitchRange ) + angles.x = m_pitchCenter - m_pitchRange; + + // Move toward target at rate or less + float distX = UTIL_AngleDistance( angles.x, pev->angles.x ); + pev->avelocity.x = distX * 10; + + if ( pev->avelocity.x > m_pitchRate ) + pev->avelocity.x = m_pitchRate; + else if ( pev->avelocity.x < -m_pitchRate ) + pev->avelocity.x = -m_pitchRate; + + if ( m_pController ) + return; + + if ( CanFire() && ( (fabs(distX) < m_pitchTolerance && fabs(distY) < m_yawTolerance) || (pev->spawnflags & SF_TANK_LINEOFSIGHT) ) ) + { + BOOL fire = FALSE; + Vector forward; + UTIL_MakeVectorsPrivate( pev->angles, forward, NULL, NULL ); + + if ( pev->spawnflags & SF_TANK_LINEOFSIGHT ) + { + float length = direction.Length(); + UTIL_TraceLine( barrelEnd, barrelEnd + forward * length, dont_ignore_monsters, edict(), &tr ); + if ( tr.pHit == pTarget ) + fire = TRUE; + } + else + fire = TRUE; + + if ( fire ) + { + Fire( BarrelPosition(), forward, pev ); + } + else + m_fireLast = 0; + } + else + m_fireLast = 0; +} + + +// If barrel is offset, add in additional rotation +void CFuncTank::AdjustAnglesForBarrel( Vector &angles, float distance ) +{ + float r2, d2; + + + if ( m_barrelPos.y != 0 || m_barrelPos.z != 0 ) + { + distance -= m_barrelPos.z; + d2 = distance * distance; + if ( m_barrelPos.y ) + { + r2 = m_barrelPos.y * m_barrelPos.y; + angles.y += (180.0 / M_PI) * atan2( m_barrelPos.y, sqrt( d2 - r2 ) ); + } + if ( m_barrelPos.z ) + { + r2 = m_barrelPos.z * m_barrelPos.z; + angles.x += (180.0 / M_PI) * atan2( -m_barrelPos.z, sqrt( d2 - r2 ) ); + } + } +} + + +// Fire targets and spawn sprites +void CFuncTank::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + if ( m_fireLast != 0 ) + { + if ( m_iszSpriteSmoke ) + { + CSprite *pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteSmoke), barrelEnd, TRUE ); + pSprite->AnimateAndDie( RANDOM_FLOAT( 15.0, 20.0 ) ); + pSprite->SetTransparency( kRenderTransAlpha, pev->rendercolor.x, pev->rendercolor.y, pev->rendercolor.z, 255, kRenderFxNone ); + pSprite->pev->velocity.z = RANDOM_FLOAT(40, 80); + pSprite->SetScale( m_spriteScale ); + } + if ( m_iszSpriteFlash ) + { + CSprite *pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteFlash), barrelEnd, TRUE ); + pSprite->AnimateAndDie( 60 ); + pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); + pSprite->SetScale( m_spriteScale ); + + // Hack Hack, make it stick around for at least 100 ms. + pSprite->pev->nextthink += 0.1; + } + SUB_UseTargets( this, USE_TOGGLE, 0 ); + } + m_fireLast = gpGlobals->time; +} + + +void CFuncTank::TankTrace( const Vector &vecStart, const Vector &vecForward, const Vector &vecSpread, TraceResult &tr ) +{ + // get circular gaussian spread + float x, y, z; + do { + x = RANDOM_FLOAT(-0.5,0.5) + RANDOM_FLOAT(-0.5,0.5); + y = RANDOM_FLOAT(-0.5,0.5) + RANDOM_FLOAT(-0.5,0.5); + z = x*x+y*y; + } while (z > 1); + Vector vecDir = vecForward + + x * vecSpread.x * gpGlobals->v_right + + y * vecSpread.y * gpGlobals->v_up; + Vector vecEnd; + + vecEnd = vecStart + vecDir * 4096; + UTIL_TraceLine( vecStart, vecEnd, dont_ignore_monsters, edict(), &tr ); +} + + +void CFuncTank::StartRotSound( void ) +{ + if ( !pev->noise || (pev->spawnflags & SF_TANK_SOUNDON) ) + return; + pev->spawnflags |= SF_TANK_SOUNDON; + EMIT_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noise), 0.85, ATTN_NORM); +} + + +void CFuncTank::StopRotSound( void ) +{ + if ( pev->spawnflags & SF_TANK_SOUNDON ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noise) ); + pev->spawnflags &= ~SF_TANK_SOUNDON; +} + +class CFuncTankGun : public CFuncTank +{ +public: + void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); +}; +LINK_ENTITY_TO_CLASS( func_tank, CFuncTankGun ); + +void CFuncTankGun::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + int i; + + if ( m_fireLast != 0 ) + { + // FireBullets needs gpGlobals->v_up, etc. + UTIL_MakeAimVectors(pev->angles); + + int bulletCount = (gpGlobals->time - m_fireLast) * m_fireRate; + if ( bulletCount > 0 ) + { + for ( i = 0; i < bulletCount; i++ ) + { + switch( m_bulletType ) + { + case TANK_BULLET_9MM: + FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096, BULLET_MONSTER_9MM, 1, m_iBulletDamage, pevAttacker ); + break; + + case TANK_BULLET_MP5: + FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096, BULLET_MONSTER_MP5, 1, m_iBulletDamage, pevAttacker ); + break; + + case TANK_BULLET_12MM: + FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096, BULLET_MONSTER_12MM, 1, m_iBulletDamage, pevAttacker ); + break; + + default: + case TANK_BULLET_NONE: + break; + } + } + CFuncTank::Fire( barrelEnd, forward, pevAttacker ); + } + } + else + CFuncTank::Fire( barrelEnd, forward, pevAttacker ); +} + + + +class CFuncTankLaser : public CFuncTank +{ +public: + void Activate( void ); + void KeyValue( KeyValueData *pkvd ); + void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); + void Think( void ); + CLaser *GetLaser( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + CLaser *m_pLaser; + float m_laserTime; +}; +LINK_ENTITY_TO_CLASS( func_tanklaser, CFuncTankLaser ); + +TYPEDESCRIPTION CFuncTankLaser::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTankLaser, m_pLaser, FIELD_CLASSPTR ), + DEFINE_FIELD( CFuncTankLaser, m_laserTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTankLaser, CFuncTank ); + +void CFuncTankLaser::Activate( void ) +{ + if ( !GetLaser() ) + { + UTIL_Remove(this); + ALERT( at_error, "Laser tank with no env_laser!\n" ); + } + else + { + m_pLaser->TurnOff(); + } +} + + +void CFuncTankLaser::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "laserentity")) + { + pev->message = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CFuncTank::KeyValue( pkvd ); +} + + +CLaser *CFuncTankLaser::GetLaser( void ) +{ + if ( m_pLaser ) + return m_pLaser; + + edict_t *pentLaser; + + pentLaser = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->message) ); + while ( !FNullEnt( pentLaser ) ) + { + // Found the landmark + if ( FClassnameIs( pentLaser, "env_laser" ) ) + { + m_pLaser = (CLaser *)CBaseEntity::Instance(pentLaser); + break; + } + else + pentLaser = FIND_ENTITY_BY_TARGETNAME( pentLaser, STRING(pev->message) ); + } + + return m_pLaser; +} + + +void CFuncTankLaser::Think( void ) +{ + if ( m_pLaser && (gpGlobals->time > m_laserTime) ) + m_pLaser->TurnOff(); + + CFuncTank::Think(); +} + + +void CFuncTankLaser::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + int i; + TraceResult tr; + + if ( m_fireLast != 0 && GetLaser() ) + { + // TankTrace needs gpGlobals->v_up, etc. + UTIL_MakeAimVectors(pev->angles); + + int bulletCount = (gpGlobals->time - m_fireLast) * m_fireRate; + if ( bulletCount ) + { + for ( i = 0; i < bulletCount; i++ ) + { + m_pLaser->pev->origin = barrelEnd; + TankTrace( barrelEnd, forward, gTankSpread[m_spread], tr ); + + m_laserTime = gpGlobals->time; + m_pLaser->TurnOn(); + m_pLaser->pev->dmgtime = gpGlobals->time - 1.0; + m_pLaser->FireAtPoint( tr ); + m_pLaser->pev->nextthink = 0; + } + CFuncTank::Fire( barrelEnd, forward, pev ); + } + } + else + { + CFuncTank::Fire( barrelEnd, forward, pev ); + } +} + +class CFuncTankRocket : public CFuncTank +{ +public: + void Precache( void ); + void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); +}; +LINK_ENTITY_TO_CLASS( func_tankrocket, CFuncTankRocket ); + +void CFuncTankRocket::Precache( void ) +{ + UTIL_PrecacheOther( "rpg_rocket" ); + CFuncTank::Precache(); +} + + + +void CFuncTankRocket::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + int i; + + if ( m_fireLast != 0 ) + { + int bulletCount = (gpGlobals->time - m_fireLast) * m_fireRate; + if ( bulletCount > 0 ) + { + for ( i = 0; i < bulletCount; i++ ) + { + CBaseEntity *pRocket = CBaseEntity::Create( "rpg_rocket", barrelEnd, pev->angles, edict() ); + } + CFuncTank::Fire( barrelEnd, forward, pev ); + } + } + else + CFuncTank::Fire( barrelEnd, forward, pev ); +} + + +class CFuncTankMortar : public CFuncTank +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); +}; +LINK_ENTITY_TO_CLASS( func_tankmortar, CFuncTankMortar ); + + +void CFuncTankMortar::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "iMagnitude")) + { + pev->impulse = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CFuncTank::KeyValue( pkvd ); +} + + +void CFuncTankMortar::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + if ( m_fireLast != 0 ) + { + int bulletCount = (gpGlobals->time - m_fireLast) * m_fireRate; + // Only create 1 explosion + if ( bulletCount > 0 ) + { + TraceResult tr; + + // TankTrace needs gpGlobals->v_up, etc. + UTIL_MakeAimVectors(pev->angles); + + TankTrace( barrelEnd, forward, gTankSpread[m_spread], tr ); + + ExplosionCreate( tr.vecEndPos, pev->angles, edict(), pev->impulse, TRUE ); + + CFuncTank::Fire( barrelEnd, forward, pev ); + } + } + else + CFuncTank::Fire( barrelEnd, forward, pev ); +} + + + +//============================================================================ +// FUNC TANK CONTROLS +//============================================================================ +class CFuncTankControls : public CBaseEntity +{ +public: + virtual int ObjectCaps( void ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + CFuncTank *m_pTank; +}; +LINK_ENTITY_TO_CLASS( func_tankcontrols, CFuncTankControls ); + +TYPEDESCRIPTION CFuncTankControls::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTankControls, m_pTank, FIELD_CLASSPTR ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTankControls, CBaseEntity ); + +int CFuncTankControls :: ObjectCaps( void ) +{ + return (CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_IMPULSE_USE; +} + + +void CFuncTankControls :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ // pass the Use command onto the controls + if ( m_pTank ) + m_pTank->Use( pActivator, pCaller, useType, value ); + + ASSERT( m_pTank != NULL ); // if this fails, most likely means save/restore hasn't worked properly +} + + +void CFuncTankControls :: Think( void ) +{ + edict_t *pTarget = NULL; + + do + { + pTarget = FIND_ENTITY_BY_TARGETNAME( pTarget, STRING(pev->target) ); + } while ( !FNullEnt(pTarget) && strncmp( STRING(pTarget->v.classname), "func_tank", 9 ) ); + + if ( FNullEnt( pTarget ) ) + { + ALERT( at_console, "No tank %s\n", STRING(pev->target) ); + return; + } + + m_pTank = (CFuncTank*)Instance(pTarget); +} + +void CFuncTankControls::Spawn( void ) +{ + pev->solid = SOLID_TRIGGER; + pev->movetype = MOVETYPE_NONE; + pev->effects |= EF_NODRAW; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); + + pev->nextthink = gpGlobals->time + 0.3; // After all the func_tank's have spawned + + CBaseEntity::Spawn(); +} diff --git a/dmc/dlls/game.cpp b/dmc/dlls/game.cpp new file mode 100644 index 0000000..e514815 --- /dev/null +++ b/dmc/dlls/game.cpp @@ -0,0 +1,892 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "eiface.h" +#include "util.h" +#include "game.h" + +// QUAKECLASSIC +cvar_t rj = {"rj", "0"}; + +cvar_t displaysoundlist = {"displaysoundlist","0"}; + +// multiplayer server rules +cvar_t fragsleft = {"mp_fragsleft","0", FCVAR_SERVER | FCVAR_UNLOGGED }; // Don't spam console/log files/users with this changing +cvar_t timeleft = {"mp_timeleft","0" , FCVAR_SERVER | FCVAR_UNLOGGED }; // " " + +// multiplayer server rules +cvar_t teamplay = {"mp_teamplay","0", FCVAR_SERVER }; + +cvar_t fraglimit = {"mp_fraglimit","0", FCVAR_SERVER }; +cvar_t timelimit = {"mp_timelimit","0", FCVAR_SERVER }; +cvar_t friendlyfire= {"mp_friendlyfire","0", FCVAR_SERVER }; +cvar_t falldamage = {"mp_falldamage","0", FCVAR_SERVER }; +cvar_t weaponstay = {"mp_weaponstay","1", FCVAR_SERVER }; +cvar_t forcerespawn= {"mp_forcerespawn","1", FCVAR_SERVER }; +cvar_t flashlight = {"mp_flashlight","0", FCVAR_SERVER }; +cvar_t aimcrosshair= {"mp_autocrosshair","1", FCVAR_SERVER }; +cvar_t decalfrequency = {"decalfrequency","30", FCVAR_SERVER }; +cvar_t teamlist = {"mp_teamlist","hgrunt;scientist", FCVAR_SERVER }; +cvar_t teamoverride = {"mp_teamoverride","1" }; +cvar_t defaultteam = {"mp_defaultteam","0" }; +cvar_t allowmonsters={"mp_allowmonsters","0", FCVAR_SERVER }; + +cvar_t mp_chattime = {"mp_chattime","10", FCVAR_SERVER }; + +cvar_t *g_psv_gravity = NULL; +cvar_t *g_psv_aim = NULL; +cvar_t *g_footsteps = NULL; + +//CVARS FOR SKILL LEVEL SETTINGS +// Agrunt +cvar_t sk_agrunt_health1 = {"sk_agrunt_health1","0"}; +cvar_t sk_agrunt_health2 = {"sk_agrunt_health2","0"}; +cvar_t sk_agrunt_health3 = {"sk_agrunt_health3","0"}; + +cvar_t sk_agrunt_dmg_punch1 = {"sk_agrunt_dmg_punch1","0"}; +cvar_t sk_agrunt_dmg_punch2 = {"sk_agrunt_dmg_punch2","0"}; +cvar_t sk_agrunt_dmg_punch3 = {"sk_agrunt_dmg_punch3","0"}; + +// Apache +cvar_t sk_apache_health1 = {"sk_apache_health1","0"}; +cvar_t sk_apache_health2 = {"sk_apache_health2","0"}; +cvar_t sk_apache_health3 = {"sk_apache_health3","0"}; + +// Barney +cvar_t sk_barney_health1 = {"sk_barney_health1","0"}; +cvar_t sk_barney_health2 = {"sk_barney_health2","0"}; +cvar_t sk_barney_health3 = {"sk_barney_health3","0"}; + +// Bullsquid +cvar_t sk_bullsquid_health1 = {"sk_bullsquid_health1","0"}; +cvar_t sk_bullsquid_health2 = {"sk_bullsquid_health2","0"}; +cvar_t sk_bullsquid_health3 = {"sk_bullsquid_health3","0"}; + +cvar_t sk_bullsquid_dmg_bite1 = {"sk_bullsquid_dmg_bite1","0"}; +cvar_t sk_bullsquid_dmg_bite2 = {"sk_bullsquid_dmg_bite2","0"}; +cvar_t sk_bullsquid_dmg_bite3 = {"sk_bullsquid_dmg_bite3","0"}; + +cvar_t sk_bullsquid_dmg_whip1 = {"sk_bullsquid_dmg_whip1","0"}; +cvar_t sk_bullsquid_dmg_whip2 = {"sk_bullsquid_dmg_whip2","0"}; +cvar_t sk_bullsquid_dmg_whip3 = {"sk_bullsquid_dmg_whip3","0"}; + +cvar_t sk_bullsquid_dmg_spit1 = {"sk_bullsquid_dmg_spit1","0"}; +cvar_t sk_bullsquid_dmg_spit2 = {"sk_bullsquid_dmg_spit2","0"}; +cvar_t sk_bullsquid_dmg_spit3 = {"sk_bullsquid_dmg_spit3","0"}; + + +// Big Momma +cvar_t sk_bigmomma_health_factor1 = {"sk_bigmomma_health_factor1","1.0"}; +cvar_t sk_bigmomma_health_factor2 = {"sk_bigmomma_health_factor2","1.0"}; +cvar_t sk_bigmomma_health_factor3 = {"sk_bigmomma_health_factor3","1.0"}; + +cvar_t sk_bigmomma_dmg_slash1 = {"sk_bigmomma_dmg_slash1","50"}; +cvar_t sk_bigmomma_dmg_slash2 = {"sk_bigmomma_dmg_slash2","50"}; +cvar_t sk_bigmomma_dmg_slash3 = {"sk_bigmomma_dmg_slash3","50"}; + +cvar_t sk_bigmomma_dmg_blast1 = {"sk_bigmomma_dmg_blast1","100"}; +cvar_t sk_bigmomma_dmg_blast2 = {"sk_bigmomma_dmg_blast2","100"}; +cvar_t sk_bigmomma_dmg_blast3 = {"sk_bigmomma_dmg_blast3","100"}; + +cvar_t sk_bigmomma_radius_blast1 = {"sk_bigmomma_radius_blast1","250"}; +cvar_t sk_bigmomma_radius_blast2 = {"sk_bigmomma_radius_blast2","250"}; +cvar_t sk_bigmomma_radius_blast3 = {"sk_bigmomma_radius_blast3","250"}; + +// Gargantua +cvar_t sk_gargantua_health1 = {"sk_gargantua_health1","0"}; +cvar_t sk_gargantua_health2 = {"sk_gargantua_health2","0"}; +cvar_t sk_gargantua_health3 = {"sk_gargantua_health3","0"}; + +cvar_t sk_gargantua_dmg_slash1 = {"sk_gargantua_dmg_slash1","0"}; +cvar_t sk_gargantua_dmg_slash2 = {"sk_gargantua_dmg_slash2","0"}; +cvar_t sk_gargantua_dmg_slash3 = {"sk_gargantua_dmg_slash3","0"}; + +cvar_t sk_gargantua_dmg_fire1 = {"sk_gargantua_dmg_fire1","0"}; +cvar_t sk_gargantua_dmg_fire2 = {"sk_gargantua_dmg_fire2","0"}; +cvar_t sk_gargantua_dmg_fire3 = {"sk_gargantua_dmg_fire3","0"}; + +cvar_t sk_gargantua_dmg_stomp1 = {"sk_gargantua_dmg_stomp1","0"}; +cvar_t sk_gargantua_dmg_stomp2 = {"sk_gargantua_dmg_stomp2","0"}; +cvar_t sk_gargantua_dmg_stomp3 = {"sk_gargantua_dmg_stomp3","0"}; + + +// Hassassin +cvar_t sk_hassassin_health1 = {"sk_hassassin_health1","0"}; +cvar_t sk_hassassin_health2 = {"sk_hassassin_health2","0"}; +cvar_t sk_hassassin_health3 = {"sk_hassassin_health3","0"}; + + +// Headcrab +cvar_t sk_headcrab_health1 = {"sk_headcrab_health1","0"}; +cvar_t sk_headcrab_health2 = {"sk_headcrab_health2","0"}; +cvar_t sk_headcrab_health3 = {"sk_headcrab_health3","0"}; + +cvar_t sk_headcrab_dmg_bite1 = {"sk_headcrab_dmg_bite1","0"}; +cvar_t sk_headcrab_dmg_bite2 = {"sk_headcrab_dmg_bite2","0"}; +cvar_t sk_headcrab_dmg_bite3 = {"sk_headcrab_dmg_bite3","0"}; + + +// Hgrunt +cvar_t sk_hgrunt_health1 = {"sk_hgrunt_health1","0"}; +cvar_t sk_hgrunt_health2 = {"sk_hgrunt_health2","0"}; +cvar_t sk_hgrunt_health3 = {"sk_hgrunt_health3","0"}; + +cvar_t sk_hgrunt_kick1 = {"sk_hgrunt_kick1","0"}; +cvar_t sk_hgrunt_kick2 = {"sk_hgrunt_kick2","0"}; +cvar_t sk_hgrunt_kick3 = {"sk_hgrunt_kick3","0"}; + +cvar_t sk_hgrunt_pellets1 = {"sk_hgrunt_pellets1","0"}; +cvar_t sk_hgrunt_pellets2 = {"sk_hgrunt_pellets2","0"}; +cvar_t sk_hgrunt_pellets3 = {"sk_hgrunt_pellets3","0"}; + +cvar_t sk_hgrunt_gspeed1 = {"sk_hgrunt_gspeed1","0"}; +cvar_t sk_hgrunt_gspeed2 = {"sk_hgrunt_gspeed2","0"}; +cvar_t sk_hgrunt_gspeed3 = {"sk_hgrunt_gspeed3","0"}; + +// Houndeye +cvar_t sk_houndeye_health1 = {"sk_houndeye_health1","0"}; +cvar_t sk_houndeye_health2 = {"sk_houndeye_health2","0"}; +cvar_t sk_houndeye_health3 = {"sk_houndeye_health3","0"}; + +cvar_t sk_houndeye_dmg_blast1 = {"sk_houndeye_dmg_blast1","0"}; +cvar_t sk_houndeye_dmg_blast2 = {"sk_houndeye_dmg_blast2","0"}; +cvar_t sk_houndeye_dmg_blast3 = {"sk_houndeye_dmg_blast3","0"}; + + +// ISlave +cvar_t sk_islave_health1 = {"sk_islave_health1","0"}; +cvar_t sk_islave_health2 = {"sk_islave_health2","0"}; +cvar_t sk_islave_health3 = {"sk_islave_health3","0"}; + +cvar_t sk_islave_dmg_claw1 = {"sk_islave_dmg_claw1","0"}; +cvar_t sk_islave_dmg_claw2 = {"sk_islave_dmg_claw2","0"}; +cvar_t sk_islave_dmg_claw3 = {"sk_islave_dmg_claw3","0"}; + +cvar_t sk_islave_dmg_clawrake1 = {"sk_islave_dmg_clawrake1","0"}; +cvar_t sk_islave_dmg_clawrake2 = {"sk_islave_dmg_clawrake2","0"}; +cvar_t sk_islave_dmg_clawrake3 = {"sk_islave_dmg_clawrake3","0"}; + +cvar_t sk_islave_dmg_zap1 = {"sk_islave_dmg_zap1","0"}; +cvar_t sk_islave_dmg_zap2 = {"sk_islave_dmg_zap2","0"}; +cvar_t sk_islave_dmg_zap3 = {"sk_islave_dmg_zap3","0"}; + + +// Icthyosaur +cvar_t sk_ichthyosaur_health1 = {"sk_ichthyosaur_health1","0"}; +cvar_t sk_ichthyosaur_health2 = {"sk_ichthyosaur_health2","0"}; +cvar_t sk_ichthyosaur_health3 = {"sk_ichthyosaur_health3","0"}; + +cvar_t sk_ichthyosaur_shake1 = {"sk_ichthyosaur_shake1","0"}; +cvar_t sk_ichthyosaur_shake2 = {"sk_ichthyosaur_shake2","0"}; +cvar_t sk_ichthyosaur_shake3 = {"sk_ichthyosaur_shake3","0"}; + + +// Leech +cvar_t sk_leech_health1 = {"sk_leech_health1","0"}; +cvar_t sk_leech_health2 = {"sk_leech_health2","0"}; +cvar_t sk_leech_health3 = {"sk_leech_health3","0"}; + +cvar_t sk_leech_dmg_bite1 = {"sk_leech_dmg_bite1","0"}; +cvar_t sk_leech_dmg_bite2 = {"sk_leech_dmg_bite2","0"}; +cvar_t sk_leech_dmg_bite3 = {"sk_leech_dmg_bite3","0"}; + +// Controller +cvar_t sk_controller_health1 = {"sk_controller_health1","0"}; +cvar_t sk_controller_health2 = {"sk_controller_health2","0"}; +cvar_t sk_controller_health3 = {"sk_controller_health3","0"}; + +cvar_t sk_controller_dmgzap1 = {"sk_controller_dmgzap1","0"}; +cvar_t sk_controller_dmgzap2 = {"sk_controller_dmgzap2","0"}; +cvar_t sk_controller_dmgzap3 = {"sk_controller_dmgzap3","0"}; + +cvar_t sk_controller_speedball1 = {"sk_controller_speedball1","0"}; +cvar_t sk_controller_speedball2 = {"sk_controller_speedball2","0"}; +cvar_t sk_controller_speedball3 = {"sk_controller_speedball3","0"}; + +cvar_t sk_controller_dmgball1 = {"sk_controller_dmgball1","0"}; +cvar_t sk_controller_dmgball2 = {"sk_controller_dmgball2","0"}; +cvar_t sk_controller_dmgball3 = {"sk_controller_dmgball3","0"}; + +// Nihilanth +cvar_t sk_nihilanth_health1 = {"sk_nihilanth_health1","0"}; +cvar_t sk_nihilanth_health2 = {"sk_nihilanth_health2","0"}; +cvar_t sk_nihilanth_health3 = {"sk_nihilanth_health3","0"}; + +cvar_t sk_nihilanth_zap1 = {"sk_nihilanth_zap1","0"}; +cvar_t sk_nihilanth_zap2 = {"sk_nihilanth_zap2","0"}; +cvar_t sk_nihilanth_zap3 = {"sk_nihilanth_zap3","0"}; + +// Scientist +cvar_t sk_scientist_health1 = {"sk_scientist_health1","0"}; +cvar_t sk_scientist_health2 = {"sk_scientist_health2","0"}; +cvar_t sk_scientist_health3 = {"sk_scientist_health3","0"}; + + +// Snark +cvar_t sk_snark_health1 = {"sk_snark_health1","0"}; +cvar_t sk_snark_health2 = {"sk_snark_health2","0"}; +cvar_t sk_snark_health3 = {"sk_snark_health3","0"}; + +cvar_t sk_snark_dmg_bite1 = {"sk_snark_dmg_bite1","0"}; +cvar_t sk_snark_dmg_bite2 = {"sk_snark_dmg_bite2","0"}; +cvar_t sk_snark_dmg_bite3 = {"sk_snark_dmg_bite3","0"}; + +cvar_t sk_snark_dmg_pop1 = {"sk_snark_dmg_pop1","0"}; +cvar_t sk_snark_dmg_pop2 = {"sk_snark_dmg_pop2","0"}; +cvar_t sk_snark_dmg_pop3 = {"sk_snark_dmg_pop3","0"}; + + + +// Zombie +cvar_t sk_zombie_health1 = {"sk_zombie_health1","0"}; +cvar_t sk_zombie_health2 = {"sk_zombie_health2","0"}; +cvar_t sk_zombie_health3 = {"sk_zombie_health3","0"}; + +cvar_t sk_zombie_dmg_one_slash1 = {"sk_zombie_dmg_one_slash1","0"}; +cvar_t sk_zombie_dmg_one_slash2 = {"sk_zombie_dmg_one_slash2","0"}; +cvar_t sk_zombie_dmg_one_slash3 = {"sk_zombie_dmg_one_slash3","0"}; + +cvar_t sk_zombie_dmg_both_slash1 = {"sk_zombie_dmg_both_slash1","0"}; +cvar_t sk_zombie_dmg_both_slash2 = {"sk_zombie_dmg_both_slash2","0"}; +cvar_t sk_zombie_dmg_both_slash3 = {"sk_zombie_dmg_both_slash3","0"}; + + +//Turret +cvar_t sk_turret_health1 = {"sk_turret_health1","0"}; +cvar_t sk_turret_health2 = {"sk_turret_health2","0"}; +cvar_t sk_turret_health3 = {"sk_turret_health3","0"}; + + +// MiniTurret +cvar_t sk_miniturret_health1 = {"sk_miniturret_health1","0"}; +cvar_t sk_miniturret_health2 = {"sk_miniturret_health2","0"}; +cvar_t sk_miniturret_health3 = {"sk_miniturret_health3","0"}; + + +// Sentry Turret +cvar_t sk_sentry_health1 = {"sk_sentry_health1","0"}; +cvar_t sk_sentry_health2 = {"sk_sentry_health2","0"}; +cvar_t sk_sentry_health3 = {"sk_sentry_health3","0"}; + + +// PLAYER WEAPONS + +// Crowbar whack +cvar_t sk_plr_crowbar1 = {"sk_plr_crowbar1","0"}; +cvar_t sk_plr_crowbar2 = {"sk_plr_crowbar2","0"}; +cvar_t sk_plr_crowbar3 = {"sk_plr_crowbar3","0"}; + +// Glock Round +cvar_t sk_plr_9mm_bullet1 = {"sk_plr_9mm_bullet1","0"}; +cvar_t sk_plr_9mm_bullet2 = {"sk_plr_9mm_bullet2","0"}; +cvar_t sk_plr_9mm_bullet3 = {"sk_plr_9mm_bullet3","0"}; + +// 357 Round +cvar_t sk_plr_357_bullet1 = {"sk_plr_357_bullet1","0"}; +cvar_t sk_plr_357_bullet2 = {"sk_plr_357_bullet2","0"}; +cvar_t sk_plr_357_bullet3 = {"sk_plr_357_bullet3","0"}; + +// MP5 Round +cvar_t sk_plr_9mmAR_bullet1 = {"sk_plr_9mmAR_bullet1","0"}; +cvar_t sk_plr_9mmAR_bullet2 = {"sk_plr_9mmAR_bullet2","0"}; +cvar_t sk_plr_9mmAR_bullet3 = {"sk_plr_9mmAR_bullet3","0"}; + + +// M203 grenade +cvar_t sk_plr_9mmAR_grenade1 = {"sk_plr_9mmAR_grenade1","0"}; +cvar_t sk_plr_9mmAR_grenade2 = {"sk_plr_9mmAR_grenade2","0"}; +cvar_t sk_plr_9mmAR_grenade3 = {"sk_plr_9mmAR_grenade3","0"}; + + +// Shotgun buckshot +cvar_t sk_plr_buckshot1 = {"sk_plr_buckshot1","0"}; +cvar_t sk_plr_buckshot2 = {"sk_plr_buckshot2","0"}; +cvar_t sk_plr_buckshot3 = {"sk_plr_buckshot3","0"}; + + +// Crossbow +cvar_t sk_plr_xbow_bolt_client1 = {"sk_plr_xbow_bolt_client1","0"}; +cvar_t sk_plr_xbow_bolt_client2 = {"sk_plr_xbow_bolt_client2","0"}; +cvar_t sk_plr_xbow_bolt_client3 = {"sk_plr_xbow_bolt_client3","0"}; + +cvar_t sk_plr_xbow_bolt_monster1 = {"sk_plr_xbow_bolt_monster1","0"}; +cvar_t sk_plr_xbow_bolt_monster2 = {"sk_plr_xbow_bolt_monster2","0"}; +cvar_t sk_plr_xbow_bolt_monster3 = {"sk_plr_xbow_bolt_monster3","0"}; + + +// RPG +cvar_t sk_plr_rpg1 = {"sk_plr_rpg1","0"}; +cvar_t sk_plr_rpg2 = {"sk_plr_rpg2","0"}; +cvar_t sk_plr_rpg3 = {"sk_plr_rpg3","0"}; + + +// Zero Point Generator +cvar_t sk_plr_gauss1 = {"sk_plr_gauss1","0"}; +cvar_t sk_plr_gauss2 = {"sk_plr_gauss2","0"}; +cvar_t sk_plr_gauss3 = {"sk_plr_gauss3","0"}; + + +// Tau Cannon +cvar_t sk_plr_egon_narrow1 = {"sk_plr_egon_narrow1","0"}; +cvar_t sk_plr_egon_narrow2 = {"sk_plr_egon_narrow2","0"}; +cvar_t sk_plr_egon_narrow3 = {"sk_plr_egon_narrow3","0"}; + +cvar_t sk_plr_egon_wide1 = {"sk_plr_egon_wide1","0"}; +cvar_t sk_plr_egon_wide2 = {"sk_plr_egon_wide2","0"}; +cvar_t sk_plr_egon_wide3 = {"sk_plr_egon_wide3","0"}; + + +// Hand Grendade +cvar_t sk_plr_hand_grenade1 = {"sk_plr_hand_grenade1","0"}; +cvar_t sk_plr_hand_grenade2 = {"sk_plr_hand_grenade2","0"}; +cvar_t sk_plr_hand_grenade3 = {"sk_plr_hand_grenade3","0"}; + + +// Satchel Charge +cvar_t sk_plr_satchel1 = {"sk_plr_satchel1","0"}; +cvar_t sk_plr_satchel2 = {"sk_plr_satchel2","0"}; +cvar_t sk_plr_satchel3 = {"sk_plr_satchel3","0"}; + + +// Tripmine +cvar_t sk_plr_tripmine1 = {"sk_plr_tripmine1","0"}; +cvar_t sk_plr_tripmine2 = {"sk_plr_tripmine2","0"}; +cvar_t sk_plr_tripmine3 = {"sk_plr_tripmine3","0"}; + + +// WORLD WEAPONS +cvar_t sk_12mm_bullet1 = {"sk_12mm_bullet1","0"}; +cvar_t sk_12mm_bullet2 = {"sk_12mm_bullet2","0"}; +cvar_t sk_12mm_bullet3 = {"sk_12mm_bullet3","0"}; + +cvar_t sk_9mmAR_bullet1 = {"sk_9mmAR_bullet1","0"}; +cvar_t sk_9mmAR_bullet2 = {"sk_9mmAR_bullet2","0"}; +cvar_t sk_9mmAR_bullet3 = {"sk_9mmAR_bullet3","0"}; + +cvar_t sk_9mm_bullet1 = {"sk_9mm_bullet1","0"}; +cvar_t sk_9mm_bullet2 = {"sk_9mm_bullet2","0"}; +cvar_t sk_9mm_bullet3 = {"sk_9mm_bullet3","0"}; + + +// HORNET +cvar_t sk_hornet_dmg1 = {"sk_hornet_dmg1","0"}; +cvar_t sk_hornet_dmg2 = {"sk_hornet_dmg2","0"}; +cvar_t sk_hornet_dmg3 = {"sk_hornet_dmg3","0"}; + +// HEALTH/CHARGE +cvar_t sk_suitcharger1 = { "sk_suitcharger1","0" }; +cvar_t sk_suitcharger2 = { "sk_suitcharger2","0" }; +cvar_t sk_suitcharger3 = { "sk_suitcharger3","0" }; + +cvar_t sk_battery1 = { "sk_battery1","0" }; +cvar_t sk_battery2 = { "sk_battery2","0" }; +cvar_t sk_battery3 = { "sk_battery3","0" }; + +cvar_t sk_healthcharger1 = { "sk_healthcharger1","0" }; +cvar_t sk_healthcharger2 = { "sk_healthcharger2","0" }; +cvar_t sk_healthcharger3 = { "sk_healthcharger3","0" }; + +cvar_t sk_healthkit1 = { "sk_healthkit1","0" }; +cvar_t sk_healthkit2 = { "sk_healthkit2","0" }; +cvar_t sk_healthkit3 = { "sk_healthkit3","0" }; + +cvar_t sk_scientist_heal1 = { "sk_scientist_heal1","0" }; +cvar_t sk_scientist_heal2 = { "sk_scientist_heal2","0" }; +cvar_t sk_scientist_heal3 = { "sk_scientist_heal3","0" }; + + +// monster damage adjusters +cvar_t sk_monster_head1 = { "sk_monster_head1","2" }; +cvar_t sk_monster_head2 = { "sk_monster_head2","2" }; +cvar_t sk_monster_head3 = { "sk_monster_head3","2" }; + +cvar_t sk_monster_chest1 = { "sk_monster_chest1","1" }; +cvar_t sk_monster_chest2 = { "sk_monster_chest2","1" }; +cvar_t sk_monster_chest3 = { "sk_monster_chest3","1" }; + +cvar_t sk_monster_stomach1 = { "sk_monster_stomach1","1" }; +cvar_t sk_monster_stomach2 = { "sk_monster_stomach2","1" }; +cvar_t sk_monster_stomach3 = { "sk_monster_stomach3","1" }; + +cvar_t sk_monster_arm1 = { "sk_monster_arm1","1" }; +cvar_t sk_monster_arm2 = { "sk_monster_arm2","1" }; +cvar_t sk_monster_arm3 = { "sk_monster_arm3","1" }; + +cvar_t sk_monster_leg1 = { "sk_monster_leg1","1" }; +cvar_t sk_monster_leg2 = { "sk_monster_leg2","1" }; +cvar_t sk_monster_leg3 = { "sk_monster_leg3","1" }; + +// player damage adjusters +cvar_t sk_player_head1 = { "sk_player_head1","2" }; +cvar_t sk_player_head2 = { "sk_player_head2","2" }; +cvar_t sk_player_head3 = { "sk_player_head3","2" }; + +cvar_t sk_player_chest1 = { "sk_player_chest1","1" }; +cvar_t sk_player_chest2 = { "sk_player_chest2","1" }; +cvar_t sk_player_chest3 = { "sk_player_chest3","1" }; + +cvar_t sk_player_stomach1 = { "sk_player_stomach1","1" }; +cvar_t sk_player_stomach2 = { "sk_player_stomach2","1" }; +cvar_t sk_player_stomach3 = { "sk_player_stomach3","1" }; + +cvar_t sk_player_arm1 = { "sk_player_arm1","1" }; +cvar_t sk_player_arm2 = { "sk_player_arm2","1" }; +cvar_t sk_player_arm3 = { "sk_player_arm3","1" }; + +cvar_t sk_player_leg1 = { "sk_player_leg1","1" }; +cvar_t sk_player_leg2 = { "sk_player_leg2","1" }; +cvar_t sk_player_leg3 = { "sk_player_leg3","1" }; + +// END Cvars for Skill Level settings + +// Register your console variables here +// This gets called one time when the game is initialied +void GameDLLInit( void ) +{ + // Register cvars here: + g_psv_gravity = CVAR_GET_POINTER( "sv_gravity" ); + g_psv_aim = CVAR_GET_POINTER( "sv_aim" ); + g_footsteps = CVAR_GET_POINTER( "mp_footsteps" ); + + // QUAKECLASSIC + CVAR_REGISTER (&rj); + + CVAR_REGISTER (&displaysoundlist); + + CVAR_REGISTER (&teamplay); + CVAR_REGISTER (&fraglimit); + CVAR_REGISTER (&timelimit); + + CVAR_REGISTER (&fragsleft); + CVAR_REGISTER (&timeleft); + + CVAR_REGISTER (&friendlyfire); + CVAR_REGISTER (&falldamage); + CVAR_REGISTER (&weaponstay); + CVAR_REGISTER (&forcerespawn); + CVAR_REGISTER (&flashlight); + CVAR_REGISTER (&aimcrosshair); + CVAR_REGISTER (&decalfrequency); + CVAR_REGISTER (&teamlist); + CVAR_REGISTER (&teamoverride); + CVAR_REGISTER (&defaultteam); + CVAR_REGISTER (&allowmonsters); + + CVAR_REGISTER (&mp_chattime); + +// REGISTER CVARS FOR SKILL LEVEL STUFF + // Agrunt + CVAR_REGISTER ( &sk_agrunt_health1 );// {"sk_agrunt_health1","0"}; + CVAR_REGISTER ( &sk_agrunt_health2 );// {"sk_agrunt_health2","0"}; + CVAR_REGISTER ( &sk_agrunt_health3 );// {"sk_agrunt_health3","0"}; + + CVAR_REGISTER ( &sk_agrunt_dmg_punch1 );// {"sk_agrunt_dmg_punch1","0"}; + CVAR_REGISTER ( &sk_agrunt_dmg_punch2 );// {"sk_agrunt_dmg_punch2","0"}; + CVAR_REGISTER ( &sk_agrunt_dmg_punch3 );// {"sk_agrunt_dmg_punch3","0"}; + + // Apache + CVAR_REGISTER ( &sk_apache_health1 );// {"sk_apache_health1","0"}; + CVAR_REGISTER ( &sk_apache_health2 );// {"sk_apache_health2","0"}; + CVAR_REGISTER ( &sk_apache_health3 );// {"sk_apache_health3","0"}; + + // Barney + CVAR_REGISTER ( &sk_barney_health1 );// {"sk_barney_health1","0"}; + CVAR_REGISTER ( &sk_barney_health2 );// {"sk_barney_health2","0"}; + CVAR_REGISTER ( &sk_barney_health3 );// {"sk_barney_health3","0"}; + + // Bullsquid + CVAR_REGISTER ( &sk_bullsquid_health1 );// {"sk_bullsquid_health1","0"}; + CVAR_REGISTER ( &sk_bullsquid_health2 );// {"sk_bullsquid_health2","0"}; + CVAR_REGISTER ( &sk_bullsquid_health3 );// {"sk_bullsquid_health3","0"}; + + CVAR_REGISTER ( &sk_bullsquid_dmg_bite1 );// {"sk_bullsquid_dmg_bite1","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_bite2 );// {"sk_bullsquid_dmg_bite2","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_bite3 );// {"sk_bullsquid_dmg_bite3","0"}; + + CVAR_REGISTER ( &sk_bullsquid_dmg_whip1 );// {"sk_bullsquid_dmg_whip1","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_whip2 );// {"sk_bullsquid_dmg_whip2","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_whip3 );// {"sk_bullsquid_dmg_whip3","0"}; + + CVAR_REGISTER ( &sk_bullsquid_dmg_spit1 );// {"sk_bullsquid_dmg_spit1","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_spit2 );// {"sk_bullsquid_dmg_spit2","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_spit3 );// {"sk_bullsquid_dmg_spit3","0"}; + + + CVAR_REGISTER ( &sk_bigmomma_health_factor1 );// {"sk_bigmomma_health_factor1","1.0"}; + CVAR_REGISTER ( &sk_bigmomma_health_factor2 );// {"sk_bigmomma_health_factor2","1.0"}; + CVAR_REGISTER ( &sk_bigmomma_health_factor3 );// {"sk_bigmomma_health_factor3","1.0"}; + + CVAR_REGISTER ( &sk_bigmomma_dmg_slash1 );// {"sk_bigmomma_dmg_slash1","50"}; + CVAR_REGISTER ( &sk_bigmomma_dmg_slash2 );// {"sk_bigmomma_dmg_slash2","50"}; + CVAR_REGISTER ( &sk_bigmomma_dmg_slash3 );// {"sk_bigmomma_dmg_slash3","50"}; + + CVAR_REGISTER ( &sk_bigmomma_dmg_blast1 );// {"sk_bigmomma_dmg_blast1","100"}; + CVAR_REGISTER ( &sk_bigmomma_dmg_blast2 );// {"sk_bigmomma_dmg_blast2","100"}; + CVAR_REGISTER ( &sk_bigmomma_dmg_blast3 );// {"sk_bigmomma_dmg_blast3","100"}; + + CVAR_REGISTER ( &sk_bigmomma_radius_blast1 );// {"sk_bigmomma_radius_blast1","250"}; + CVAR_REGISTER ( &sk_bigmomma_radius_blast2 );// {"sk_bigmomma_radius_blast2","250"}; + CVAR_REGISTER ( &sk_bigmomma_radius_blast3 );// {"sk_bigmomma_radius_blast3","250"}; + + // Gargantua + CVAR_REGISTER ( &sk_gargantua_health1 );// {"sk_gargantua_health1","0"}; + CVAR_REGISTER ( &sk_gargantua_health2 );// {"sk_gargantua_health2","0"}; + CVAR_REGISTER ( &sk_gargantua_health3 );// {"sk_gargantua_health3","0"}; + + CVAR_REGISTER ( &sk_gargantua_dmg_slash1 );// {"sk_gargantua_dmg_slash1","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_slash2 );// {"sk_gargantua_dmg_slash2","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_slash3 );// {"sk_gargantua_dmg_slash3","0"}; + + CVAR_REGISTER ( &sk_gargantua_dmg_fire1 );// {"sk_gargantua_dmg_fire1","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_fire2 );// {"sk_gargantua_dmg_fire2","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_fire3 );// {"sk_gargantua_dmg_fire3","0"}; + + CVAR_REGISTER ( &sk_gargantua_dmg_stomp1 );// {"sk_gargantua_dmg_stomp1","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_stomp2 );// {"sk_gargantua_dmg_stomp2","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_stomp3 );// {"sk_gargantua_dmg_stomp3","0"}; + + + // Hassassin + CVAR_REGISTER ( &sk_hassassin_health1 );// {"sk_hassassin_health1","0"}; + CVAR_REGISTER ( &sk_hassassin_health2 );// {"sk_hassassin_health2","0"}; + CVAR_REGISTER ( &sk_hassassin_health3 );// {"sk_hassassin_health3","0"}; + + + // Headcrab + CVAR_REGISTER ( &sk_headcrab_health1 );// {"sk_headcrab_health1","0"}; + CVAR_REGISTER ( &sk_headcrab_health2 );// {"sk_headcrab_health2","0"}; + CVAR_REGISTER ( &sk_headcrab_health3 );// {"sk_headcrab_health3","0"}; + + CVAR_REGISTER ( &sk_headcrab_dmg_bite1 );// {"sk_headcrab_dmg_bite1","0"}; + CVAR_REGISTER ( &sk_headcrab_dmg_bite2 );// {"sk_headcrab_dmg_bite2","0"}; + CVAR_REGISTER ( &sk_headcrab_dmg_bite3 );// {"sk_headcrab_dmg_bite3","0"}; + + + // Hgrunt + CVAR_REGISTER ( &sk_hgrunt_health1 );// {"sk_hgrunt_health1","0"}; + CVAR_REGISTER ( &sk_hgrunt_health2 );// {"sk_hgrunt_health2","0"}; + CVAR_REGISTER ( &sk_hgrunt_health3 );// {"sk_hgrunt_health3","0"}; + + CVAR_REGISTER ( &sk_hgrunt_kick1 );// {"sk_hgrunt_kick1","0"}; + CVAR_REGISTER ( &sk_hgrunt_kick2 );// {"sk_hgrunt_kick2","0"}; + CVAR_REGISTER ( &sk_hgrunt_kick3 );// {"sk_hgrunt_kick3","0"}; + + CVAR_REGISTER ( &sk_hgrunt_pellets1 ); + CVAR_REGISTER ( &sk_hgrunt_pellets2 ); + CVAR_REGISTER ( &sk_hgrunt_pellets3 ); + + CVAR_REGISTER ( &sk_hgrunt_gspeed1 ); + CVAR_REGISTER ( &sk_hgrunt_gspeed2 ); + CVAR_REGISTER ( &sk_hgrunt_gspeed3 ); + + // Houndeye + CVAR_REGISTER ( &sk_houndeye_health1 );// {"sk_houndeye_health1","0"}; + CVAR_REGISTER ( &sk_houndeye_health2 );// {"sk_houndeye_health2","0"}; + CVAR_REGISTER ( &sk_houndeye_health3 );// {"sk_houndeye_health3","0"}; + + CVAR_REGISTER ( &sk_houndeye_dmg_blast1 );// {"sk_houndeye_dmg_blast1","0"}; + CVAR_REGISTER ( &sk_houndeye_dmg_blast2 );// {"sk_houndeye_dmg_blast2","0"}; + CVAR_REGISTER ( &sk_houndeye_dmg_blast3 );// {"sk_houndeye_dmg_blast3","0"}; + + + // ISlave + CVAR_REGISTER ( &sk_islave_health1 );// {"sk_islave_health1","0"}; + CVAR_REGISTER ( &sk_islave_health2 );// {"sk_islave_health2","0"}; + CVAR_REGISTER ( &sk_islave_health3 );// {"sk_islave_health3","0"}; + + CVAR_REGISTER ( &sk_islave_dmg_claw1 );// {"sk_islave_dmg_claw1","0"}; + CVAR_REGISTER ( &sk_islave_dmg_claw2 );// {"sk_islave_dmg_claw2","0"}; + CVAR_REGISTER ( &sk_islave_dmg_claw3 );// {"sk_islave_dmg_claw3","0"}; + + CVAR_REGISTER ( &sk_islave_dmg_clawrake1 );// {"sk_islave_dmg_clawrake1","0"}; + CVAR_REGISTER ( &sk_islave_dmg_clawrake2 );// {"sk_islave_dmg_clawrake2","0"}; + CVAR_REGISTER ( &sk_islave_dmg_clawrake3 );// {"sk_islave_dmg_clawrake3","0"}; + + CVAR_REGISTER ( &sk_islave_dmg_zap1 );// {"sk_islave_dmg_zap1","0"}; + CVAR_REGISTER ( &sk_islave_dmg_zap2 );// {"sk_islave_dmg_zap2","0"}; + CVAR_REGISTER ( &sk_islave_dmg_zap3 );// {"sk_islave_dmg_zap3","0"}; + + + // Icthyosaur + CVAR_REGISTER ( &sk_ichthyosaur_health1 );// {"sk_ichthyosaur_health1","0"}; + CVAR_REGISTER ( &sk_ichthyosaur_health2 );// {"sk_ichthyosaur_health2","0"}; + CVAR_REGISTER ( &sk_ichthyosaur_health3 );// {"sk_ichthyosaur_health3","0"}; + + CVAR_REGISTER ( &sk_ichthyosaur_shake1 );// {"sk_ichthyosaur_health3","0"}; + CVAR_REGISTER ( &sk_ichthyosaur_shake2 );// {"sk_ichthyosaur_health3","0"}; + CVAR_REGISTER ( &sk_ichthyosaur_shake3 );// {"sk_ichthyosaur_health3","0"}; + + + + // Leech + CVAR_REGISTER ( &sk_leech_health1 );// {"sk_leech_health1","0"}; + CVAR_REGISTER ( &sk_leech_health2 );// {"sk_leech_health2","0"}; + CVAR_REGISTER ( &sk_leech_health3 );// {"sk_leech_health3","0"}; + + CVAR_REGISTER ( &sk_leech_dmg_bite1 );// {"sk_leech_dmg_bite1","0"}; + CVAR_REGISTER ( &sk_leech_dmg_bite2 );// {"sk_leech_dmg_bite2","0"}; + CVAR_REGISTER ( &sk_leech_dmg_bite3 );// {"sk_leech_dmg_bite3","0"}; + + + // Controller + CVAR_REGISTER ( &sk_controller_health1 ); + CVAR_REGISTER ( &sk_controller_health2 ); + CVAR_REGISTER ( &sk_controller_health3 ); + + CVAR_REGISTER ( &sk_controller_dmgzap1 ); + CVAR_REGISTER ( &sk_controller_dmgzap2 ); + CVAR_REGISTER ( &sk_controller_dmgzap3 ); + + CVAR_REGISTER ( &sk_controller_speedball1 ); + CVAR_REGISTER ( &sk_controller_speedball2 ); + CVAR_REGISTER ( &sk_controller_speedball3 ); + + CVAR_REGISTER ( &sk_controller_dmgball1 ); + CVAR_REGISTER ( &sk_controller_dmgball2 ); + CVAR_REGISTER ( &sk_controller_dmgball3 ); + + // Nihilanth + CVAR_REGISTER ( &sk_nihilanth_health1 );// {"sk_nihilanth_health1","0"}; + CVAR_REGISTER ( &sk_nihilanth_health2 );// {"sk_nihilanth_health2","0"}; + CVAR_REGISTER ( &sk_nihilanth_health3 );// {"sk_nihilanth_health3","0"}; + + CVAR_REGISTER ( &sk_nihilanth_zap1 ); + CVAR_REGISTER ( &sk_nihilanth_zap2 ); + CVAR_REGISTER ( &sk_nihilanth_zap3 ); + + // Scientist + CVAR_REGISTER ( &sk_scientist_health1 );// {"sk_scientist_health1","0"}; + CVAR_REGISTER ( &sk_scientist_health2 );// {"sk_scientist_health2","0"}; + CVAR_REGISTER ( &sk_scientist_health3 );// {"sk_scientist_health3","0"}; + + + // Snark + CVAR_REGISTER ( &sk_snark_health1 );// {"sk_snark_health1","0"}; + CVAR_REGISTER ( &sk_snark_health2 );// {"sk_snark_health2","0"}; + CVAR_REGISTER ( &sk_snark_health3 );// {"sk_snark_health3","0"}; + + CVAR_REGISTER ( &sk_snark_dmg_bite1 );// {"sk_snark_dmg_bite1","0"}; + CVAR_REGISTER ( &sk_snark_dmg_bite2 );// {"sk_snark_dmg_bite2","0"}; + CVAR_REGISTER ( &sk_snark_dmg_bite3 );// {"sk_snark_dmg_bite3","0"}; + + CVAR_REGISTER ( &sk_snark_dmg_pop1 );// {"sk_snark_dmg_pop1","0"}; + CVAR_REGISTER ( &sk_snark_dmg_pop2 );// {"sk_snark_dmg_pop2","0"}; + CVAR_REGISTER ( &sk_snark_dmg_pop3 );// {"sk_snark_dmg_pop3","0"}; + + + + // Zombie + CVAR_REGISTER ( &sk_zombie_health1 );// {"sk_zombie_health1","0"}; + CVAR_REGISTER ( &sk_zombie_health2 );// {"sk_zombie_health3","0"}; + CVAR_REGISTER ( &sk_zombie_health3 );// {"sk_zombie_health3","0"}; + + CVAR_REGISTER ( &sk_zombie_dmg_one_slash1 );// {"sk_zombie_dmg_one_slash1","0"}; + CVAR_REGISTER ( &sk_zombie_dmg_one_slash2 );// {"sk_zombie_dmg_one_slash2","0"}; + CVAR_REGISTER ( &sk_zombie_dmg_one_slash3 );// {"sk_zombie_dmg_one_slash3","0"}; + + CVAR_REGISTER ( &sk_zombie_dmg_both_slash1 );// {"sk_zombie_dmg_both_slash1","0"}; + CVAR_REGISTER ( &sk_zombie_dmg_both_slash2 );// {"sk_zombie_dmg_both_slash2","0"}; + CVAR_REGISTER ( &sk_zombie_dmg_both_slash3 );// {"sk_zombie_dmg_both_slash3","0"}; + + + //Turret + CVAR_REGISTER ( &sk_turret_health1 );// {"sk_turret_health1","0"}; + CVAR_REGISTER ( &sk_turret_health2 );// {"sk_turret_health2","0"}; + CVAR_REGISTER ( &sk_turret_health3 );// {"sk_turret_health3","0"}; + + + // MiniTurret + CVAR_REGISTER ( &sk_miniturret_health1 );// {"sk_miniturret_health1","0"}; + CVAR_REGISTER ( &sk_miniturret_health2 );// {"sk_miniturret_health2","0"}; + CVAR_REGISTER ( &sk_miniturret_health3 );// {"sk_miniturret_health3","0"}; + + + // Sentry Turret + CVAR_REGISTER ( &sk_sentry_health1 );// {"sk_sentry_health1","0"}; + CVAR_REGISTER ( &sk_sentry_health2 );// {"sk_sentry_health2","0"}; + CVAR_REGISTER ( &sk_sentry_health3 );// {"sk_sentry_health3","0"}; + + + // PLAYER WEAPONS + + // Crowbar whack + CVAR_REGISTER ( &sk_plr_crowbar1 );// {"sk_plr_crowbar1","0"}; + CVAR_REGISTER ( &sk_plr_crowbar2 );// {"sk_plr_crowbar2","0"}; + CVAR_REGISTER ( &sk_plr_crowbar3 );// {"sk_plr_crowbar3","0"}; + + // Glock Round + CVAR_REGISTER ( &sk_plr_9mm_bullet1 );// {"sk_plr_9mm_bullet1","0"}; + CVAR_REGISTER ( &sk_plr_9mm_bullet2 );// {"sk_plr_9mm_bullet2","0"}; + CVAR_REGISTER ( &sk_plr_9mm_bullet3 );// {"sk_plr_9mm_bullet3","0"}; + + // 357 Round + CVAR_REGISTER ( &sk_plr_357_bullet1 );// {"sk_plr_357_bullet1","0"}; + CVAR_REGISTER ( &sk_plr_357_bullet2 );// {"sk_plr_357_bullet2","0"}; + CVAR_REGISTER ( &sk_plr_357_bullet3 );// {"sk_plr_357_bullet3","0"}; + + // MP5 Round + CVAR_REGISTER ( &sk_plr_9mmAR_bullet1 );// {"sk_plr_9mmAR_bullet1","0"}; + CVAR_REGISTER ( &sk_plr_9mmAR_bullet2 );// {"sk_plr_9mmAR_bullet2","0"}; + CVAR_REGISTER ( &sk_plr_9mmAR_bullet3 );// {"sk_plr_9mmAR_bullet3","0"}; + + + // M203 grenade + CVAR_REGISTER ( &sk_plr_9mmAR_grenade1 );// {"sk_plr_9mmAR_grenade1","0"}; + CVAR_REGISTER ( &sk_plr_9mmAR_grenade2 );// {"sk_plr_9mmAR_grenade2","0"}; + CVAR_REGISTER ( &sk_plr_9mmAR_grenade3 );// {"sk_plr_9mmAR_grenade3","0"}; + + + // Shotgun buckshot + CVAR_REGISTER ( &sk_plr_buckshot1 );// {"sk_plr_buckshot1","0"}; + CVAR_REGISTER ( &sk_plr_buckshot2 );// {"sk_plr_buckshot2","0"}; + CVAR_REGISTER ( &sk_plr_buckshot3 );// {"sk_plr_buckshot3","0"}; + + + // Crossbow + CVAR_REGISTER ( &sk_plr_xbow_bolt_monster1 );// {"sk_plr_xbow_bolt1","0"}; + CVAR_REGISTER ( &sk_plr_xbow_bolt_monster2 );// {"sk_plr_xbow_bolt2","0"}; + CVAR_REGISTER ( &sk_plr_xbow_bolt_monster3 );// {"sk_plr_xbow_bolt3","0"}; + + CVAR_REGISTER ( &sk_plr_xbow_bolt_client1 );// {"sk_plr_xbow_bolt1","0"}; + CVAR_REGISTER ( &sk_plr_xbow_bolt_client2 );// {"sk_plr_xbow_bolt2","0"}; + CVAR_REGISTER ( &sk_plr_xbow_bolt_client3 );// {"sk_plr_xbow_bolt3","0"}; + + + // RPG + CVAR_REGISTER ( &sk_plr_rpg1 );// {"sk_plr_rpg1","0"}; + CVAR_REGISTER ( &sk_plr_rpg2 );// {"sk_plr_rpg2","0"}; + CVAR_REGISTER ( &sk_plr_rpg3 );// {"sk_plr_rpg3","0"}; + + + // Gauss Gun + CVAR_REGISTER ( &sk_plr_gauss1 );// {"sk_plr_gauss1","0"}; + CVAR_REGISTER ( &sk_plr_gauss2 );// {"sk_plr_gauss2","0"}; + CVAR_REGISTER ( &sk_plr_gauss3 );// {"sk_plr_gauss3","0"}; + + + // Egon Gun + CVAR_REGISTER ( &sk_plr_egon_narrow1 );// {"sk_plr_egon_narrow1","0"}; + CVAR_REGISTER ( &sk_plr_egon_narrow2 );// {"sk_plr_egon_narrow2","0"}; + CVAR_REGISTER ( &sk_plr_egon_narrow3 );// {"sk_plr_egon_narrow3","0"}; + + CVAR_REGISTER ( &sk_plr_egon_wide1 );// {"sk_plr_egon_wide1","0"}; + CVAR_REGISTER ( &sk_plr_egon_wide2 );// {"sk_plr_egon_wide2","0"}; + CVAR_REGISTER ( &sk_plr_egon_wide3 );// {"sk_plr_egon_wide3","0"}; + + + // Hand Grendade + CVAR_REGISTER ( &sk_plr_hand_grenade1 );// {"sk_plr_hand_grenade1","0"}; + CVAR_REGISTER ( &sk_plr_hand_grenade2 );// {"sk_plr_hand_grenade2","0"}; + CVAR_REGISTER ( &sk_plr_hand_grenade3 );// {"sk_plr_hand_grenade3","0"}; + + + // Satchel Charge + CVAR_REGISTER ( &sk_plr_satchel1 );// {"sk_plr_satchel1","0"}; + CVAR_REGISTER ( &sk_plr_satchel2 );// {"sk_plr_satchel2","0"}; + CVAR_REGISTER ( &sk_plr_satchel3 );// {"sk_plr_satchel3","0"}; + + + // Tripmine + CVAR_REGISTER ( &sk_plr_tripmine1 );// {"sk_plr_tripmine1","0"}; + CVAR_REGISTER ( &sk_plr_tripmine2 );// {"sk_plr_tripmine2","0"}; + CVAR_REGISTER ( &sk_plr_tripmine3 );// {"sk_plr_tripmine3","0"}; + + + // WORLD WEAPONS + CVAR_REGISTER ( &sk_12mm_bullet1 );// {"sk_12mm_bullet1","0"}; + CVAR_REGISTER ( &sk_12mm_bullet2 );// {"sk_12mm_bullet2","0"}; + CVAR_REGISTER ( &sk_12mm_bullet3 );// {"sk_12mm_bullet3","0"}; + + CVAR_REGISTER ( &sk_9mmAR_bullet1 );// {"sk_9mm_bullet1","0"}; + CVAR_REGISTER ( &sk_9mmAR_bullet2 );// {"sk_9mm_bullet1","0"}; + CVAR_REGISTER ( &sk_9mmAR_bullet3 );// {"sk_9mm_bullet1","0"}; + + CVAR_REGISTER ( &sk_9mm_bullet1 );// {"sk_9mm_bullet1","0"}; + CVAR_REGISTER ( &sk_9mm_bullet2 );// {"sk_9mm_bullet2","0"}; + CVAR_REGISTER ( &sk_9mm_bullet3 );// {"sk_9mm_bullet3","0"}; + + + // HORNET + CVAR_REGISTER ( &sk_hornet_dmg1 );// {"sk_hornet_dmg1","0"}; + CVAR_REGISTER ( &sk_hornet_dmg2 );// {"sk_hornet_dmg2","0"}; + CVAR_REGISTER ( &sk_hornet_dmg3 );// {"sk_hornet_dmg3","0"}; + + // HEALTH/SUIT CHARGE DISTRIBUTION + CVAR_REGISTER ( &sk_suitcharger1 ); + CVAR_REGISTER ( &sk_suitcharger2 ); + CVAR_REGISTER ( &sk_suitcharger3 ); + + CVAR_REGISTER ( &sk_battery1 ); + CVAR_REGISTER ( &sk_battery2 ); + CVAR_REGISTER ( &sk_battery3 ); + + CVAR_REGISTER ( &sk_healthcharger1 ); + CVAR_REGISTER ( &sk_healthcharger2 ); + CVAR_REGISTER ( &sk_healthcharger3 ); + + CVAR_REGISTER ( &sk_healthkit1 ); + CVAR_REGISTER ( &sk_healthkit2 ); + CVAR_REGISTER ( &sk_healthkit3 ); + + CVAR_REGISTER ( &sk_scientist_heal1 ); + CVAR_REGISTER ( &sk_scientist_heal2 ); + CVAR_REGISTER ( &sk_scientist_heal3 ); + +// monster damage adjusters + CVAR_REGISTER ( &sk_monster_head1 ); + CVAR_REGISTER ( &sk_monster_head2 ); + CVAR_REGISTER ( &sk_monster_head3 ); + + CVAR_REGISTER ( &sk_monster_chest1 ); + CVAR_REGISTER ( &sk_monster_chest2 ); + CVAR_REGISTER ( &sk_monster_chest3 ); + + CVAR_REGISTER ( &sk_monster_stomach1 ); + CVAR_REGISTER ( &sk_monster_stomach2 ); + CVAR_REGISTER ( &sk_monster_stomach3 ); + + CVAR_REGISTER ( &sk_monster_arm1 ); + CVAR_REGISTER ( &sk_monster_arm2 ); + CVAR_REGISTER ( &sk_monster_arm3 ); + + CVAR_REGISTER ( &sk_monster_leg1 ); + CVAR_REGISTER ( &sk_monster_leg2 ); + CVAR_REGISTER ( &sk_monster_leg3 ); + +// player damage adjusters + CVAR_REGISTER ( &sk_player_head1 ); + CVAR_REGISTER ( &sk_player_head2 ); + CVAR_REGISTER ( &sk_player_head3 ); + + CVAR_REGISTER ( &sk_player_chest1 ); + CVAR_REGISTER ( &sk_player_chest2 ); + CVAR_REGISTER ( &sk_player_chest3 ); + + CVAR_REGISTER ( &sk_player_stomach1 ); + CVAR_REGISTER ( &sk_player_stomach2 ); + CVAR_REGISTER ( &sk_player_stomach3 ); + + CVAR_REGISTER ( &sk_player_arm1 ); + CVAR_REGISTER ( &sk_player_arm2 ); + CVAR_REGISTER ( &sk_player_arm3 ); + + CVAR_REGISTER ( &sk_player_leg1 ); + CVAR_REGISTER ( &sk_player_leg2 ); + CVAR_REGISTER ( &sk_player_leg3 ); +// END REGISTER CVARS FOR SKILL LEVEL STUFF + + SERVER_COMMAND( "exec skill.cfg\n" ); +} + diff --git a/dmc/dlls/game.h b/dmc/dlls/game.h new file mode 100644 index 0000000..e61c3f3 --- /dev/null +++ b/dmc/dlls/game.h @@ -0,0 +1,46 @@ +/*** +* +* Copyright (c) 1996-2002 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef GAME_H +#define GAME_H + +extern void GameDLLInit( void ); + + +extern cvar_t displaysoundlist; +extern cvar_t mapcyclefile; +extern cvar_t servercfgfile; +extern cvar_t lservercfgfile; + +// multiplayer server rules +extern cvar_t fraglimit; +extern cvar_t timelimit; +extern cvar_t friendlyfir; +extern cvar_t falldamage; +extern cvar_t weaponstay; +extern cvar_t forcerespaw; +extern cvar_t flashlight; +extern cvar_t aimcrosshair; +extern cvar_t decalfrequency; +extern cvar_t teamlist; +extern cvar_t teamoverride; +extern cvar_t defaultteam; + +// Engine Cvars +extern cvar_t *g_psv_gravity; +extern cvar_t *g_psv_aim; +extern cvar_t *g_footsteps; + +#endif // GAME_H diff --git a/dmc/dlls/gamerules.cpp b/dmc/dlls/gamerules.cpp new file mode 100644 index 0000000..c5da3fc --- /dev/null +++ b/dmc/dlls/gamerules.cpp @@ -0,0 +1,179 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// GameRules.cpp +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "teamplay_gamerules.h" + +extern Vector g_vecTeleMins[ MAX_TELES ]; +extern Vector g_vecTeleMaxs[ MAX_TELES ]; +extern int g_iTeleNum; + +#include "skill.h" + +extern edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ); + +DLL_GLOBAL CGameRules* g_pGameRules = NULL; +extern DLL_GLOBAL BOOL g_fGameOver; +extern int gmsgDeathMsg; // client dll messages +extern int gmsgScoreInfo; +extern int gmsgMOTD; + +//========================================================= +//========================================================= +BOOL CGameRules::CanHaveAmmo( CBasePlayer *pPlayer, const char *pszAmmoName, int iMaxCarry ) +{ + int iAmmoIndex; + + if ( pszAmmoName ) + { + iAmmoIndex = pPlayer->GetAmmoIndex( pszAmmoName ); + + if ( iAmmoIndex > -1 ) + { + if ( pPlayer->AmmoInventory( iAmmoIndex ) < iMaxCarry ) + { + // player has room for more of this type of ammo + return TRUE; + } + } + } + + return FALSE; +} + +//========================================================= +//========================================================= +edict_t *CGameRules :: GetPlayerSpawnSpot( CBasePlayer *pPlayer ) +{ + edict_t *pentSpawnSpot = EntSelectSpawnPoint( pPlayer ); + + pPlayer->pev->origin = VARS(pentSpawnSpot)->origin + Vector(0,0,1); + pPlayer->pev->v_angle = g_vecZero; + pPlayer->pev->velocity = g_vecZero; + pPlayer->pev->angles = VARS(pentSpawnSpot)->angles; + pPlayer->pev->punchangle = g_vecZero; + pPlayer->pev->fixangle = TRUE; + + return pentSpawnSpot; +} + +//========================================================= +//========================================================= +BOOL CGameRules::CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ + if ( pWeapon->pszAmmo1() ) + { + if ( !CanHaveAmmo( pPlayer, pWeapon->pszAmmo1(), pWeapon->iMaxAmmo1() ) ) + { + // we can't carry anymore ammo for this gun. We can only + // have the gun if we aren't already carrying one of this type + if ( pPlayer->HasPlayerItem( pWeapon ) ) + { + return FALSE; + } + } + } + else + { + // weapon doesn't use ammo, don't take another if you already have it. + if ( pPlayer->HasPlayerItem( pWeapon ) ) + { + return FALSE; + } + } + + // note: will fall through to here if GetItemInfo doesn't fill the struct! + return TRUE; +} + +//========================================================= +// load the SkillData struct with the proper values based on the skill level. +//========================================================= +void CGameRules::RefreshSkillData ( void ) +{ + int iSkill; + + iSkill = (int)CVAR_GET_FLOAT("skill"); + + if ( iSkill < 1 ) + { + iSkill = 1; + } + else if ( iSkill > 3 ) + { + iSkill = 3; + } + + gSkillData.iSkillLevel = iSkill; +} + +//========================================================= +// instantiate the proper game rules object +//========================================================= + +CGameRules *InstallGameRules( void ) +{ + SERVER_COMMAND( "exec game.cfg\n" ); + SERVER_EXECUTE( ); + + //Clear all the teleporters + for ( int i = 0; i < MAX_TELES; i++ ) + { + g_vecTeleMins[ i ].x = 0.0; + g_vecTeleMins[ i ].y = 0.0; + g_vecTeleMins[ i ].z = 0.0; + + g_vecTeleMaxs[ i ].x = 0.0; + g_vecTeleMaxs[ i ].y = 0.0; + g_vecTeleMaxs[ i ].z = 0.0; + } + + g_iTeleNum = 0; + + if ( !gpGlobals->deathmatch ) + { + // generic half-life + return new CHalfLifeRules; + } + else + { + if ( CVAR_GET_FLOAT( "mp_teamplay" ) > 0 ) + { + // teamplay + return new CHalfLifeTeamplay; + } + if ((int)gpGlobals->deathmatch == 1) + { + // vanilla deathmatch + return new CHalfLifeMultiplay; + } + else + { + // vanilla deathmatch?? + return new CHalfLifeMultiplay; + } + } +} + + + diff --git a/dmc/dlls/gamerules.h b/dmc/dlls/gamerules.h new file mode 100644 index 0000000..7886df4 --- /dev/null +++ b/dmc/dlls/gamerules.h @@ -0,0 +1,370 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// GameRules +//========================================================= + +//#include "weapons.h" +//#include "items.h" +class CBasePlayerItem; +class CBasePlayer; +class CItem; +class CBasePlayerAmmo; + +// weapon respawning return codes +enum +{ + GR_NONE = 0, + + GR_WEAPON_RESPAWN_YES, + GR_WEAPON_RESPAWN_NO, + + GR_AMMO_RESPAWN_YES, + GR_AMMO_RESPAWN_NO, + + GR_ITEM_RESPAWN_YES, + GR_ITEM_RESPAWN_NO, + + GR_PLR_DROP_GUN_ALL, + GR_PLR_DROP_GUN_ACTIVE, + GR_PLR_DROP_GUN_NO, + + GR_PLR_DROP_AMMO_ALL, + GR_PLR_DROP_AMMO_ACTIVE, + GR_PLR_DROP_AMMO_NO, +}; + +// Player relationship return codes +enum +{ + GR_NOTTEAMMATE = 0, + GR_TEAMMATE, + GR_ENEMY, + GR_ALLY, + GR_NEUTRAL, +}; + +class CGameRules +{ +public: + virtual void RefreshSkillData( void );// fill skill data struct with proper values + virtual void Think( void ) = 0;// GR_Think - runs every server frame, should handle any timer tasks, periodic events, etc. + virtual BOOL IsAllowedToSpawn( CBaseEntity *pEntity ) = 0; // Can this item spawn (eg monsters don't spawn in deathmatch). + + virtual BOOL FAllowFlashlight( void ) = 0;// Are players allowed to switch on their flashlight? + virtual BOOL FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) = 0;// should the player switch to this weapon? + virtual BOOL GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) = 0;// I can't use this weapon anymore, get me the next best one. + +// Functions to verify the single/multiplayer status of a game + virtual BOOL IsMultiplayer( void ) = 0;// is this a multiplayer game? (either coop or deathmatch) + virtual BOOL IsDeathmatch( void ) = 0;//is this a deathmatch game? + virtual BOOL IsTeamplay( void ) { return FALSE; };// is this deathmatch game being played with team rules? + virtual BOOL IsCoOp( void ) = 0;// is this a coop game? + virtual const char *GetGameDescription( void ) { return "DMC"; } // this is the game name that gets seen in the server browser + +// Client connection/disconnection + virtual BOOL ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) = 0;// a client just connected to the server (player hasn't spawned yet) + virtual void InitHUD( CBasePlayer *pl ) = 0; // the client dll is ready for updating + virtual void ClientDisconnected( edict_t *pClient ) = 0;// a client just disconnected from the server + virtual void UpdateGameMode( CBasePlayer *pPlayer ) {} // the client needs to be informed of the current game mode + +// Client damage rules + virtual float FlPlayerFallDamage( CBasePlayer *pPlayer ) = 0;// this client just hit the ground after a fall. How much damage? + virtual BOOL FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ) {return TRUE;};// can this player take damage from this attacker? + virtual BOOL ShouldAutoAim( CBasePlayer *pPlayer, edict_t *target ) { return TRUE; } + +// Client spawn/respawn control + virtual void PlayerSpawn( CBasePlayer *pPlayer ) = 0;// called by CBasePlayer::Spawn just before releasing player into the game + virtual void PlayerThink( CBasePlayer *pPlayer ) = 0; // called by CBasePlayer::PreThink every frame, before physics are run and after keys are accepted + virtual BOOL FPlayerCanRespawn( CBasePlayer *pPlayer ) = 0;// is this player allowed to respawn now? + virtual float FlPlayerSpawnTime( CBasePlayer *pPlayer ) = 0;// When in the future will this player be able to spawn? + virtual edict_t *GetPlayerSpawnSpot( CBasePlayer *pPlayer );// Place this player on their spawnspot and face them the proper direction. + + virtual BOOL AllowAutoTargetCrosshair( void ) { return TRUE; }; + virtual BOOL ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) { return FALSE; }; // handles the user commands; returns TRUE if command handled properly + virtual void ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ) {} // the player has changed userinfo; can change it now + +// Client kills/scoring + virtual int IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) = 0;// how many points do I award whoever kills this player? + virtual void PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) = 0;// Called each time a player dies + virtual void DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor )= 0;// Call this from within a GameRules class to report an obituary. +// Weapon retrieval + virtual BOOL CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon );// The player is touching an CBasePlayerItem, do I give it to him? + virtual void PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) = 0;// Called each time a player picks up a weapon from the ground + +// Weapon spawn/respawn control + virtual int WeaponShouldRespawn( CBasePlayerItem *pWeapon ) = 0;// should this weapon respawn? + virtual float FlWeaponRespawnTime( CBasePlayerItem *pWeapon ) = 0;// when may this weapon respawn? + virtual float FlWeaponTryRespawn( CBasePlayerItem *pWeapon ) = 0; // can i respawn now, and if not, when should i try again? + virtual Vector VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ) = 0;// where in the world should this weapon respawn? + +// Item retrieval + virtual BOOL CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ) = 0;// is this player allowed to take this item? + virtual void PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ) = 0;// call each time a player picks up an item (battery, healthkit, longjump) + +// Item spawn/respawn control + virtual int ItemShouldRespawn( CItem *pItem ) = 0;// Should this item respawn? + virtual float FlItemRespawnTime( CItem *pItem ) = 0;// when may this item respawn? + virtual Vector VecItemRespawnSpot( CItem *pItem ) = 0;// where in the world should this item respawn? + +// Ammo retrieval + virtual BOOL CanHaveAmmo( CBasePlayer *pPlayer, const char *pszAmmoName, int iMaxCarry );// can this player take more of this ammo? + virtual void PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ) = 0;// called each time a player picks up some ammo in the world + +// Ammo spawn/respawn control + virtual int AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ) = 0;// should this ammo item respawn? + virtual float FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ) = 0;// when should this ammo item respawn? + virtual Vector VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ) = 0;// where in the world should this ammo item respawn? + // by default, everything spawns + +// Healthcharger respawn control + virtual float FlHealthChargerRechargeTime( void ) = 0;// how long until a depleted HealthCharger recharges itself? + virtual float FlHEVChargerRechargeTime( void ) { return 0; }// how long until a depleted HealthCharger recharges itself? + +// What happens to a dead player's weapons + virtual int DeadPlayerWeapons( CBasePlayer *pPlayer ) = 0;// what do I do with a player's weapons when he's killed? + +// What happens to a dead player's ammo + virtual int DeadPlayerAmmo( CBasePlayer *pPlayer ) = 0;// Do I drop ammo when the player dies? How much? + +// Teamplay stuff + virtual const char *GetTeamID( CBaseEntity *pEntity ) = 0;// what team is this entity on? + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) = 0;// What is the player's relationship with this entity? + virtual int GetTeamIndex( const char *pTeamName ) { return -1; } + virtual const char *GetIndexedTeamName( int teamIndex ) { return ""; } + virtual BOOL IsValidTeam( const char *pTeamName ) { return TRUE; } + virtual void ChangePlayerTeam( CBasePlayer *pPlayer, const char *pTeamName, BOOL bKill, BOOL bGib ) {} + virtual const char *SetDefaultPlayerTeam( CBasePlayer *pPlayer ) { return ""; } + +// Sounds + virtual BOOL PlayTextureSounds( void ) { return TRUE; } + virtual BOOL PlayFootstepSounds( CBasePlayer *pl, float fvol ) { return TRUE; } + +// Monsters + virtual BOOL FAllowMonsters( void ) = 0;//are monsters allowed + + // Immediately end a multiplayer game + virtual void EndMultiplayerGame( void ) {} +}; + +extern CGameRules *InstallGameRules( void ); + + +//========================================================= +// CHalfLifeRules - rules for the single player Half-Life +// game. +//========================================================= +class CHalfLifeRules : public CGameRules +{ +public: + CHalfLifeRules ( void ); + +// GR_Think + virtual void Think( void ); + virtual BOOL IsAllowedToSpawn( CBaseEntity *pEntity ); + virtual BOOL FAllowFlashlight( void ) { return TRUE; }; + + virtual BOOL FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + virtual BOOL GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ); + +// Functions to verify the single/multiplayer status of a game + virtual BOOL IsMultiplayer( void ); + virtual BOOL IsDeathmatch( void ); + virtual BOOL IsCoOp( void ); + +// Client connection/disconnection + virtual BOOL ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); + virtual void InitHUD( CBasePlayer *pl ); // the client dll is ready for updating + virtual void ClientDisconnected( edict_t *pClient ); + +// Client damage rules + virtual float FlPlayerFallDamage( CBasePlayer *pPlayer ); + +// Client spawn/respawn control + virtual void PlayerSpawn( CBasePlayer *pPlayer ); + virtual void PlayerThink( CBasePlayer *pPlayer ); + virtual BOOL FPlayerCanRespawn( CBasePlayer *pPlayer ); + virtual float FlPlayerSpawnTime( CBasePlayer *pPlayer ); + + virtual BOOL AllowAutoTargetCrosshair( void ); + +// Client kills/scoring + virtual int IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ); + virtual void PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); + virtual void DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); + +// Weapon retrieval + virtual void PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + +// Weapon spawn/respawn control + virtual int WeaponShouldRespawn( CBasePlayerItem *pWeapon ); + virtual float FlWeaponRespawnTime( CBasePlayerItem *pWeapon ); + virtual float FlWeaponTryRespawn( CBasePlayerItem *pWeapon ); + virtual Vector VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ); + +// Item retrieval + virtual BOOL CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ); + virtual void PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ); + +// Item spawn/respawn control + virtual int ItemShouldRespawn( CItem *pItem ); + virtual float FlItemRespawnTime( CItem *pItem ); + virtual Vector VecItemRespawnSpot( CItem *pItem ); + +// Ammo retrieval + virtual void PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ); + +// Ammo spawn/respawn control + virtual int AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ); + virtual float FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ); + virtual Vector VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ); + +// Healthcharger respawn control + virtual float FlHealthChargerRechargeTime( void ); + +// What happens to a dead player's weapons + virtual int DeadPlayerWeapons( CBasePlayer *pPlayer ); + +// What happens to a dead player's ammo + virtual int DeadPlayerAmmo( CBasePlayer *pPlayer ); + +// Monsters + virtual BOOL FAllowMonsters( void ); + +// Teamplay stuff + virtual const char *GetTeamID( CBaseEntity *pEntity ) {return "";}; + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); +}; + + +#include "voice_gamemgr.h" + +//========================================================= +// CHalfLifeMultiplay - rules for the basic half life multiplayer +// competition +//========================================================= +class CHalfLifeMultiplay : public CGameRules +{ +public: + CHalfLifeMultiplay(); + + virtual BOOL ClientCommand( CBasePlayer *pPlayer, const char *pcmd ); + +// GR_Think + virtual void Think( void ); + virtual void RefreshSkillData( void ); + virtual BOOL IsAllowedToSpawn( CBaseEntity *pEntity ); + virtual BOOL FAllowFlashlight( void ); + + virtual BOOL FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + virtual BOOL GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ); + +// Functions to verify the single/multiplayer status of a game + virtual BOOL IsMultiplayer( void ); + virtual BOOL IsDeathmatch( void ); + virtual BOOL IsCoOp( void ); + +// Client connection/disconnection + // If ClientConnected returns FALSE, the connection is rejected and the user is provided the reason specified in + // svRejectReason + // Only the client's name and remote address are provided to the dll for verification. + virtual BOOL ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); + virtual void InitHUD( CBasePlayer *pl ); // the client dll is ready for updating + virtual void ClientDisconnected( edict_t *pClient ); + virtual void UpdateGameMode( CBasePlayer *pPlayer ); // the client needs to be informed of the current game mode + +// Client damage rules + virtual float FlPlayerFallDamage( CBasePlayer *pPlayer ); + virtual BOOL FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ); + +// Client spawn/respawn control + virtual void PlayerSpawn( CBasePlayer *pPlayer ); + virtual void PlayerThink( CBasePlayer *pPlayer ); + virtual BOOL FPlayerCanRespawn( CBasePlayer *pPlayer ); + virtual float FlPlayerSpawnTime( CBasePlayer *pPlayer ); + virtual edict_t *GetPlayerSpawnSpot( CBasePlayer *pPlayer ); + + virtual BOOL AllowAutoTargetCrosshair( void ); + +// Client kills/scoring + virtual int IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ); + virtual void PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); + virtual void DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); + +// Weapon retrieval + virtual void PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + virtual BOOL CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon );// The player is touching an CBasePlayerItem, do I give it to him? + +// Weapon spawn/respawn control + virtual int WeaponShouldRespawn( CBasePlayerItem *pWeapon ); + virtual float FlWeaponRespawnTime( CBasePlayerItem *pWeapon ); + virtual float FlWeaponTryRespawn( CBasePlayerItem *pWeapon ); + virtual Vector VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ); + +// Item retrieval + virtual BOOL CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ); + virtual void PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ); + +// Item spawn/respawn control + virtual int ItemShouldRespawn( CItem *pItem ); + virtual float FlItemRespawnTime( CItem *pItem ); + virtual Vector VecItemRespawnSpot( CItem *pItem ); + +// Ammo retrieval + virtual void PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ); + +// Ammo spawn/respawn control + virtual int AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ); + virtual float FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ); + virtual Vector VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ); + +// Healthcharger respawn control + virtual float FlHealthChargerRechargeTime( void ); + virtual float FlHEVChargerRechargeTime( void ); + +// What happens to a dead player's weapons + virtual int DeadPlayerWeapons( CBasePlayer *pPlayer ); + +// What happens to a dead player's ammo + virtual int DeadPlayerAmmo( CBasePlayer *pPlayer ); + +// Teamplay stuff + virtual const char *GetTeamID( CBaseEntity *pEntity ) {return "";} + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); + + virtual BOOL PlayTextureSounds( void ) { return FALSE; } + virtual BOOL PlayFootstepSounds( CBasePlayer *pl, float fvol ); + +// Monsters + virtual BOOL FAllowMonsters( void ); + + // Immediately end a multiplayer game + virtual void EndMultiplayerGame( void ) { GoToIntermission(); } + + CVoiceGameMgr m_VoiceGameMgr; + +protected: + virtual void ChangeLevel( void ); + virtual void GoToIntermission( void ); + float m_flIntermissionEndTime; + float m_flIntermissionStartTime; + BOOL m_iEndIntermissionButtonHit; + void SendMOTDToClient( edict_t *client ); + + float m_flGameEndTime; + virtual void ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ); +}; + +extern DLL_GLOBAL CGameRules* g_pGameRules; diff --git a/dmc/dlls/globals.cpp b/dmc/dlls/globals.cpp new file mode 100644 index 0000000..ef657c2 --- /dev/null +++ b/dmc/dlls/globals.cpp @@ -0,0 +1,39 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== globals.cpp ======================================================== + + DLL-wide global variable definitions. + They're all defined here, for convenient centralization. + Source files that need them should "extern ..." declare each + variable, to better document what globals they care about. + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "soundent.h" + +DLL_GLOBAL ULONG g_ulFrameCount; +DLL_GLOBAL ULONG g_ulModelIndexEyes; +DLL_GLOBAL ULONG g_ulModelIndexPlayer; +DLL_GLOBAL Vector g_vecAttackDir; +DLL_GLOBAL int g_iSkillLevel; +DLL_GLOBAL int gDisplayTitle; +DLL_GLOBAL BOOL g_fGameOver; +DLL_GLOBAL const Vector g_vecZero = Vector(0,0,0); +DLL_GLOBAL int g_Language; diff --git a/dmc/dlls/h_ai.cpp b/dmc/dlls/h_ai.cpp new file mode 100644 index 0000000..7eabdf8 --- /dev/null +++ b/dmc/dlls/h_ai.cpp @@ -0,0 +1,198 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + + h_ai.cpp - halflife specific ai code + +*/ + + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" + +#define NUM_LATERAL_CHECKS 13 // how many checks are made on each side of a monster looking for lateral cover +#define NUM_LATERAL_LOS_CHECKS 6 // how many checks are made on each side of a monster looking for lateral cover + +//float flRandom = RANDOM_FLOAT(0,1); + +DLL_GLOBAL BOOL g_fDrawLines = FALSE; + +//========================================================= +// +// AI UTILITY FUNCTIONS +// +// !!!UNDONE - move CBaseMonster functions to monsters.cpp +//========================================================= + +//========================================================= +// FBoxVisible - a more accurate ( and slower ) version +// of FVisible. +// +// !!!UNDONE - make this CBaseMonster? +//========================================================= +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget, Vector &vecTargetOrigin, float flSize ) +{ + // don't look through water + if ((pevLooker->waterlevel != 3 && pevTarget->waterlevel == 3) + || (pevLooker->waterlevel == 3 && pevTarget->waterlevel == 0)) + return FALSE; + + TraceResult tr; + Vector vecLookerOrigin = pevLooker->origin + pevLooker->view_ofs;//look through the monster's 'eyes' + for (int i = 0; i < 5; i++) + { + Vector vecTarget = pevTarget->origin; + vecTarget.x += RANDOM_FLOAT( pevTarget->mins.x + flSize, pevTarget->maxs.x - flSize); + vecTarget.y += RANDOM_FLOAT( pevTarget->mins.y + flSize, pevTarget->maxs.y - flSize); + vecTarget.z += RANDOM_FLOAT( pevTarget->mins.z + flSize, pevTarget->maxs.z - flSize); + + UTIL_TraceLine(vecLookerOrigin, vecTarget, ignore_monsters, ignore_glass, ENT(pevLooker)/*pentIgnore*/, &tr); + + if (tr.flFraction == 1.0) + { + vecTargetOrigin = vecTarget; + return TRUE;// line of sight is valid. + } + } + return FALSE;// Line of sight is not established +} + +// +// VecCheckToss - returns the velocity at which an object should be lobbed from vecspot1 to land near vecspot2. +// returns g_vecZero if toss is not feasible. +// +Vector VecCheckToss ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flGravityAdj ) +{ + TraceResult tr; + Vector vecMidPoint;// halfway point between Spot1 and Spot2 + Vector vecApex;// highest point + Vector vecScale; + Vector vecGrenadeVel; + Vector vecTemp; + float flGravity = CVAR_GET_FLOAT( "sv_gravity" ) * flGravityAdj; + + if (vecSpot2.z - vecSpot1.z > 500) + { + // to high, fail + return g_vecZero; + } + + UTIL_MakeVectors (pev->angles); + + // toss a little bit to the left or right, not right down on the enemy's bean (head). + vecSpot2 = vecSpot2 + gpGlobals->v_right * ( RANDOM_FLOAT(-8,8) + RANDOM_FLOAT(-16,16) ); + vecSpot2 = vecSpot2 + gpGlobals->v_forward * ( RANDOM_FLOAT(-8,8) + RANDOM_FLOAT(-16,16) ); + + // calculate the midpoint and apex of the 'triangle' + // UNDONE: normalize any Z position differences between spot1 and spot2 so that triangle is always RIGHT + + // How much time does it take to get there? + + // get a rough idea of how high it can be thrown + vecMidPoint = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5; + UTIL_TraceLine(vecMidPoint, vecMidPoint + Vector(0,0,500), ignore_monsters, ENT(pev), &tr); + vecMidPoint = tr.vecEndPos; + // (subtract 15 so the grenade doesn't hit the ceiling) + vecMidPoint.z -= 15; + + if (vecMidPoint.z < vecSpot1.z || vecMidPoint.z < vecSpot2.z) + { + // to not enough space, fail + return g_vecZero; + } + + // How high should the grenade travel to reach the apex + float distance1 = (vecMidPoint.z - vecSpot1.z); + float distance2 = (vecMidPoint.z - vecSpot2.z); + + // How long will it take for the grenade to travel this distance + float time1 = sqrt( distance1 / (0.5 * flGravity) ); + float time2 = sqrt( distance2 / (0.5 * flGravity) ); + + if (time1 < 0.1) + { + // too close + return g_vecZero; + } + + // how hard to throw sideways to get there in time. + vecGrenadeVel = (vecSpot2 - vecSpot1) / (time1 + time2); + // how hard upwards to reach the apex at the right time. + vecGrenadeVel.z = flGravity * time1; + + // find the apex + vecApex = vecSpot1 + vecGrenadeVel * time1; + vecApex.z = vecMidPoint.z; + + UTIL_TraceLine(vecSpot1, vecApex, dont_ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + // UNDONE: either ignore monsters or change it to not care if we hit our enemy + UTIL_TraceLine(vecSpot2, vecApex, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + return vecGrenadeVel; +} + + +// +// VecCheckThrow - returns the velocity vector at which an object should be thrown from vecspot1 to hit vecspot2. +// returns g_vecZero if throw is not feasible. +// +Vector VecCheckThrow ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flSpeed, float flGravityAdj ) +{ + float flGravity = CVAR_GET_FLOAT( "sv_gravity" ) * flGravityAdj; + + Vector vecGrenadeVel = (vecSpot2 - vecSpot1); + + // throw at a constant time + float time = vecGrenadeVel.Length( ) / flSpeed; + vecGrenadeVel = vecGrenadeVel * (1.0 / time); + + // adjust upward toss to compensate for gravity loss + vecGrenadeVel.z += flGravity * time * 0.5; + + Vector vecApex = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5; + vecApex.z += 0.5 * flGravity * (time * 0.5) * (time * 0.5); + + TraceResult tr; + UTIL_TraceLine(vecSpot1, vecApex, dont_ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + UTIL_TraceLine(vecSpot2, vecApex, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + return vecGrenadeVel; +} + + diff --git a/dmc/dlls/h_export.cpp b/dmc/dlls/h_export.cpp new file mode 100644 index 0000000..85a54da --- /dev/null +++ b/dmc/dlls/h_export.cpp @@ -0,0 +1,63 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== h_export.cpp ======================================================== + + Entity classes exported by Halflife. + +*/ + +#include "extdll.h" +#include "util.h" + +#include "cbase.h" +#undef DLLEXPORT + +#ifdef _WIN32 +#define DLLEXPORT __stdcall +#else +#define DLLEXPORT __attribute__ ((visibility("default"))) +#endif + +// Holds engine functionality callbacks +enginefuncs_t g_engfuncs; +globalvars_t *gpGlobals; + + +#ifdef _WIN32 + +// Required DLL entry point +BOOL WINAPI DllMain( + HINSTANCE hinstDLL, + DWORD fdwReason, + LPVOID lpvReserved) +{ + if (fdwReason == DLL_PROCESS_ATTACH) + { + } + else if (fdwReason == DLL_PROCESS_DETACH) + { + } + return TRUE; +} +#endif + +extern "C" void DLLEXPORT GiveFnptrsToDll( enginefuncs_t* pengfuncsFromEngine, globalvars_t *pGlobals ) +{ + memcpy(&g_engfuncs, pengfuncsFromEngine, sizeof(enginefuncs_t)); + gpGlobals = pGlobals; +} + diff --git a/dmc/dlls/hl.def b/dmc/dlls/hl.def new file mode 100644 index 0000000..b4211ab --- /dev/null +++ b/dmc/dlls/hl.def @@ -0,0 +1,5 @@ +LIBRARY hl +EXPORTS + GiveFnptrsToDll @1 +SECTIONS + .data READ WRITE diff --git a/dmc/dlls/hl.dsp b/dmc/dlls/hl.dsp new file mode 100644 index 0000000..e261503 --- /dev/null +++ b/dmc/dlls/hl.dsp @@ -0,0 +1,509 @@ +# Microsoft Developer Studio Project File - Name="hl" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=hl - 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 "hl.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 "hl.mak" CFG="hl - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "hl - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "hl - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/GoldSrc/dmc/dlls", TTQCAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "hl - 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 ".\Releasehl" +# PROP Intermediate_Dir ".\Releasehl" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /G5 /MT /W3 /Zi /O2 /I "..\engine" /I "..\common" /I "..\..\public" /I "." /I "..\..\game_shared" /I "..\dlls" /I "..\..\engine" /I "..\..\common" /I "..\pm_shared" /I "..\\" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "QUIVER" /D "VOXEL" /D "QUAKE2" /D "VALVE_DLL" /Fr /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /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 /subsystem:windows /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /map /debug /machine:I386 /def:".\hl.def" /out:".\Releasehl/dmc.dll" +# SUBTRACT LINK32 /profile +# Begin Custom Build - Copying... +InputDir=.\Releasehl +ProjDir=. +InputPath=.\Releasehl\dmc.dll +InputName=dmc +SOURCE="$(InputPath)" + +BuildCmds= \ + call ..\..\filecopy.bat $(InputPath) $(ProjDir)\..\..\..\game\mod\dlls\$(InputName).dll \ + call ..\..\filecopy.bat $(InputDir)\$(InputName).pdb $(ProjDir)\..\..\..\game\mod\dlls\$(InputName).pdb \ + + +"$(ProjDir)\..\..\..\game\mod\dlls\$(InputName).dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"$(ProjDir)\..\..\..\game\mod\dlls\$(InputName).pdb" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) +# End Custom Build + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\hl___Win" +# PROP BASE Intermediate_Dir ".\hl___Win" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir ".\debughl" +# PROP Intermediate_Dir ".\debughl" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /G5 /MTd /W3 /Gm /ZI /Od /I "..\public" /I "." /I "..\..\game_shared" /I "..\dlls" /I "..\..\engine" /I "..\..\common" /I "..\pm_shared" /I "..\\" /I "..\..\public" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "QUIVER" /D "VOXEL" /D "QUAKE2" /D "VALVE_DLL" /FR /YX /FD /c +# ADD BASE MTL /nologo /D "_DEBUG" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /i "..\engine" /d "_DEBUG" +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 /subsystem:windows /dll /debug /machine:I386 +# ADD LINK32 user32.lib advapi32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /def:".\hl.def" /implib:".\Debug\hl.lib" +# SUBTRACT LINK32 /profile +# Begin Custom Build - Copying... +ProjDir=. +InputPath=.\debughl\hl.dll +InputName=hl +SOURCE="$(InputPath)" + +"$(ProjDir)\..\..\..\game\mod\dlls\$(InputName).dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + call ..\..\filecopy.bat $(InputPath) $(ProjDir)\..\..\..\game\mod\dlls\$(InputName).dll + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "hl - Win32 Release" +# Name "hl - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Group "Shared Weapons" + +# PROP Default_Filter "*.cpp" +# Begin Source File + +SOURCE=.\quake_gun.cpp +# End Source File +# Begin Source File + +SOURCE=.\quake_weapons_all.cpp +# End Source File +# End Group +# Begin Source File + +SOURCE=.\animating.cpp +# End Source File +# Begin Source File + +SOURCE=.\animation.cpp +# End Source File +# Begin Source File + +SOURCE=.\bmodels.cpp +# End Source File +# Begin Source File + +SOURCE=.\buttons.cpp +# End Source File +# Begin Source File + +SOURCE=.\cbase.cpp +# End Source File +# Begin Source File + +SOURCE=.\client.cpp +# End Source File +# Begin Source File + +SOURCE=.\combat.cpp +# End Source File +# Begin Source File + +SOURCE=.\doors.cpp +# End Source File +# Begin Source File + +SOURCE=.\effects.cpp +# End Source File +# Begin Source File + +SOURCE=.\explode.cpp +# End Source File +# Begin Source File + +SOURCE=.\func_break.cpp +# End Source File +# Begin Source File + +SOURCE=.\func_tank.cpp +# End Source File +# Begin Source File + +SOURCE=.\game.cpp +# End Source File +# Begin Source File + +SOURCE=.\gamerules.cpp +# End Source File +# Begin Source File + +SOURCE=.\globals.cpp +# End Source File +# Begin Source File + +SOURCE=.\h_ai.cpp +# End Source File +# Begin Source File + +SOURCE=.\h_export.cpp +# End Source File +# Begin Source File + +SOURCE=.\lights.cpp +# End Source File +# Begin Source File + +SOURCE=.\maprules.cpp +# End Source File +# Begin Source File + +SOURCE=.\monsters.cpp +# End Source File +# Begin Source File + +SOURCE=.\monsterstate.cpp +# End Source File +# Begin Source File + +SOURCE=.\multiplay_gamerules.cpp +# End Source File +# Begin Source File + +SOURCE=.\nodes.cpp +# End Source File +# Begin Source File + +SOURCE=.\observer.cpp +# End Source File +# Begin Source File + +SOURCE=.\pathcorner.cpp +# End Source File +# Begin Source File + +SOURCE=.\plane.cpp +# End Source File +# Begin Source File + +SOURCE=.\plats.cpp +# End Source File +# Begin Source File + +SOURCE=.\player.cpp +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_debug.c +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_math.c +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_shared.c +# End Source File +# Begin Source File + +SOURCE=.\quake_items.cpp +# End Source File +# Begin Source File + +SOURCE=.\quake_nail.cpp +# End Source File +# Begin Source File + +SOURCE=.\quake_player.cpp +# End Source File +# Begin Source File + +SOURCE=.\quake_rocket.cpp +# End Source File +# Begin Source File + +SOURCE=.\schedule.cpp +# End Source File +# Begin Source File + +SOURCE=.\singleplay_gamerules.cpp +# End Source File +# Begin Source File + +SOURCE=.\skill.cpp +# End Source File +# Begin Source File + +SOURCE=.\sound.cpp +# End Source File +# Begin Source File + +SOURCE=.\spectator.cpp +# End Source File +# Begin Source File + +SOURCE=.\subs.cpp +# End Source File +# Begin Source File + +SOURCE=.\teamplay_gamerules.cpp +# End Source File +# Begin Source File + +SOURCE=.\threewave_gamerules.cpp +# End Source File +# Begin Source File + +SOURCE=.\triggers.cpp +# End Source File +# Begin Source File + +SOURCE=.\util.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\voice_gamemgr.cpp +# End Source File +# Begin Source File + +SOURCE=.\weapons.cpp +# End Source File +# Begin Source File + +SOURCE=.\world.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=.\activity.h +# End Source File +# Begin Source File + +SOURCE=.\activitymap.h +# End Source File +# Begin Source File + +SOURCE=.\animation.h +# End Source File +# Begin Source File + +SOURCE=.\basemonster.h +# End Source File +# Begin Source File + +SOURCE=.\cbase.h +# End Source File +# Begin Source File + +SOURCE=.\cdll_dll.h +# End Source File +# Begin Source File + +SOURCE=.\client.h +# End Source File +# Begin Source File + +SOURCE=.\decals.h +# End Source File +# Begin Source File + +SOURCE=.\defaultai.h +# End Source File +# Begin Source File + +SOURCE=.\doors.h +# End Source File +# Begin Source File + +SOURCE=.\effects.h +# End Source File +# Begin Source File + +SOURCE=.\enginecallback.h +# End Source File +# Begin Source File + +SOURCE=.\explode.h +# End Source File +# Begin Source File + +SOURCE=.\extdll.h +# End Source File +# Begin Source File + +SOURCE=.\func_break.h +# End Source File +# Begin Source File + +SOURCE=.\gamerules.h +# End Source File +# Begin Source File + +SOURCE=.\items.h +# End Source File +# Begin Source File + +SOURCE=.\monsterevent.h +# End Source File +# Begin Source File + +SOURCE=.\monsters.h +# End Source File +# Begin Source File + +SOURCE=.\nodes.h +# End Source File +# Begin Source File + +SOURCE=.\plane.h +# End Source File +# Begin Source File + +SOURCE=.\player.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_debug.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_defs.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_info.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_materials.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_movevars.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_shared.h +# End Source File +# Begin Source File + +SOURCE=.\quake_gun.h +# End Source File +# Begin Source File + +SOURCE=.\saverestore.h +# End Source File +# Begin Source File + +SOURCE=.\schedule.h +# End Source File +# Begin Source File + +SOURCE=.\scripted.h +# End Source File +# Begin Source File + +SOURCE=.\scriptevent.h +# End Source File +# Begin Source File + +SOURCE=.\skill.h +# End Source File +# Begin Source File + +SOURCE=.\soundent.h +# End Source File +# Begin Source File + +SOURCE=.\spectator.h +# End Source File +# Begin Source File + +SOURCE=.\teamplay_gamerules.h +# End Source File +# Begin Source File + +SOURCE=.\threewave_gamerules.h +# End Source File +# Begin Source File + +SOURCE=.\trains.h +# End Source File +# Begin Source File + +SOURCE=.\util.h +# End Source File +# Begin Source File + +SOURCE=.\vector.h +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\voice_gamemgr.h +# End Source File +# Begin Source File + +SOURCE=.\weapons.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/dmc/dlls/hlgl.def b/dmc/dlls/hlgl.def new file mode 100644 index 0000000..b94aa63 --- /dev/null +++ b/dmc/dlls/hlgl.def @@ -0,0 +1,15 @@ +LIBRARY hlgl +EXPORTS + GiveFnptrsToDll @1 + GetEntityInterfaces @2 + SetChangeParms @3 + SetNewParms @4 + ClientKill @5 + PutClientInServer @6 + PlayerPreThink @7 + PlayerPostThink @8 + ClientConnect @9 + ClientDisconnect @10 + StartFrame @11 +SECTIONS + .data READ WRITE diff --git a/dmc/dlls/items.cpp b/dmc/dlls/items.cpp new file mode 100644 index 0000000..6458eeb --- /dev/null +++ b/dmc/dlls/items.cpp @@ -0,0 +1,337 @@ +/*** +* +* Copyright (c) 1996-2002 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== items.cpp ======================================================== + + functions governing the selection/use of weapons for players + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "player.h" +#include "skill.h" +#include "items.h" +#include "gamerules.h" + +extern int gmsgItemPickup; + +class CWorldItem : public CBaseEntity +{ +public: + void KeyValue(KeyValueData *pkvd ); + void Spawn( void ); + int m_iType; +}; + +LINK_ENTITY_TO_CLASS(world_items, CWorldItem); + +void CWorldItem::KeyValue(KeyValueData *pkvd) +{ + if (FStrEq(pkvd->szKeyName, "type")) + { + m_iType = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CWorldItem::Spawn( void ) +{ + CBaseEntity *pEntity = NULL; + + switch (m_iType) + { + case 44: // ITEM_BATTERY: + pEntity = CBaseEntity::Create( "item_battery", pev->origin, pev->angles ); + break; + case 42: // ITEM_ANTIDOTE: + pEntity = CBaseEntity::Create( "item_antidote", pev->origin, pev->angles ); + break; + case 43: // ITEM_SECURITY: + pEntity = CBaseEntity::Create( "item_security", pev->origin, pev->angles ); + break; + case 45: // ITEM_SUIT: + pEntity = CBaseEntity::Create( "item_suit", pev->origin, pev->angles ); + break; + } + + if (!pEntity) + { + ALERT( at_console, "unable to create world_item %d\n", m_iType ); + } + else + { + pEntity->pev->target = pev->target; + pEntity->pev->targetname = pev->targetname; + pEntity->pev->spawnflags = pev->spawnflags; + } + + REMOVE_ENTITY(edict()); +} + + +void CItem::Spawn( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + UTIL_SetOrigin( pev, pev->origin ); + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 16)); + SetTouch(ItemTouch); + + if (DROP_TO_FLOOR(ENT(pev)) == 0) + { + ALERT(at_error, "Item %s fell out of level at %f,%f,%f", STRING( pev->classname ), pev->origin.x, pev->origin.y, pev->origin.z); + UTIL_Remove( this ); + return; + } +} + +extern int gEvilImpulse101; + +void CItem::ItemTouch( CBaseEntity *pOther ) +{ + // if it's not a player, ignore + if ( !pOther->IsPlayer() ) + { + return; + } + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + + // ok, a player is touching this item, but can he have it? + if ( !g_pGameRules->CanHaveItem( pPlayer, this ) ) + { + // no? Ignore the touch. + return; + } + + if (MyTouch( pPlayer )) + { + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); + SetTouch( NULL ); + + // player grabbed the item. + g_pGameRules->PlayerGotItem( pPlayer, this ); + if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_YES ) + { + Respawn(); + } + else + { + UTIL_Remove( this ); + } + } + else if (gEvilImpulse101) + { + UTIL_Remove( this ); + } +} + +CBaseEntity* CItem::Respawn( void ) +{ + SetTouch( NULL ); + pev->effects |= EF_NODRAW; + + UTIL_SetOrigin( pev, g_pGameRules->VecItemRespawnSpot( this ) );// blip to whereever you should respawn. + + SetThink ( Materialize ); + pev->nextthink = g_pGameRules->FlItemRespawnTime( this ); + return this; +} + +void CItem::Materialize( void ) +{ + if ( pev->effects & EF_NODRAW ) + { + // changing from invisible state to visible. + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "items/suitchargeok1.wav", 1, ATTN_NORM, 0, 150 ); + pev->effects &= ~EF_NODRAW; + pev->effects |= EF_MUZZLEFLASH; + } + + SetTouch( ItemTouch ); +} + +#define SF_SUIT_SHORTLOGON 0x0001 + +class CItemSuit : public CItem +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_suit.mdl"); + CItem::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_suit.mdl"); + } + BOOL MyTouch( CBasePlayer *pPlayer ) + { + if ( pPlayer->pev->weapons & (1<spawnflags & SF_SUIT_SHORTLOGON ) + EMIT_SOUND_SUIT(pPlayer->edict(), "!HEV_A0"); // short version of suit logon, + else + EMIT_SOUND_SUIT(pPlayer->edict(), "!HEV_AAx"); // long version of suit logon + + pPlayer->pev->weapons |= (1<pev->armorvalue < MAX_NORMAL_BATTERY) && + (pPlayer->pev->weapons & (1<pev->armorvalue += gSkillData.batteryCapacity; + pPlayer->pev->armorvalue = min(pPlayer->pev->armorvalue, MAX_NORMAL_BATTERY); + + EMIT_SOUND( pPlayer->edict(), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM ); + + MESSAGE_BEGIN( MSG_ONE, gmsgItemPickup, NULL, pPlayer->pev ); + WRITE_STRING( STRING(pev->classname) ); + MESSAGE_END(); + + + // Suit reports new power level + // For some reason this wasn't working in release build -- round it. + pct = (int)( (float)(pPlayer->pev->armorvalue * 100.0) * (1.0/MAX_NORMAL_BATTERY) + 0.5); + pct = (pct / 5); + if (pct > 0) + pct--; + + sprintf( szcharge,"!HEV_%1dP", pct ); + + //EMIT_SOUND_SUIT(ENT(pev), szcharge); + pPlayer->SetSuitUpdate(szcharge, FALSE, SUIT_NEXT_IN_30SEC); + return TRUE; + } + return FALSE; + } +}; + +LINK_ENTITY_TO_CLASS(item_battery, CItemBattery); + + +class CItemAntidote : public CItem +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_antidote.mdl"); + CItem::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_antidote.mdl"); + } + BOOL MyTouch( CBasePlayer *pPlayer ) + { + pPlayer->SetSuitUpdate("!HEV_DET4", FALSE, SUIT_NEXT_IN_1MIN); + + pPlayer->m_rgItems[ITEM_ANTIDOTE] += 1; + return TRUE; + } +}; + +LINK_ENTITY_TO_CLASS(item_antidote, CItemAntidote); + + +class CItemSecurity : public CItem +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_security.mdl"); + CItem::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_security.mdl"); + } + BOOL MyTouch( CBasePlayer *pPlayer ) + { + pPlayer->m_rgItems[ITEM_SECURITY] += 1; + return TRUE; + } +}; + +LINK_ENTITY_TO_CLASS(item_security, CItemSecurity); + +class CItemLongJump : public CItem +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_longjump.mdl"); + CItem::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_longjump.mdl"); + } + BOOL MyTouch( CBasePlayer *pPlayer ) + { + if ( pPlayer->m_fLongJump ) + { + return FALSE; + } + + if ( ( pPlayer->pev->weapons & (1<m_fLongJump = TRUE;// player now has longjump module + + g_engfuncs.pfnSetPhysicsKeyValue( pPlayer->edict(), "slj", "1" ); + + MESSAGE_BEGIN( MSG_ONE, gmsgItemPickup, NULL, pPlayer->pev ); + WRITE_STRING( STRING(pev->classname) ); + MESSAGE_END(); + + EMIT_SOUND_SUIT( pPlayer->edict(), "!HEV_A1" ); // Play the longjump sound UNDONE: Kelly? correct sound? + return TRUE; + } + return FALSE; + } +}; + +LINK_ENTITY_TO_CLASS( item_longjump, CItemLongJump ); diff --git a/dmc/dlls/items.h b/dmc/dlls/items.h new file mode 100644 index 0000000..e985296 --- /dev/null +++ b/dmc/dlls/items.h @@ -0,0 +1,29 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef ITEMS_H +#define ITEMS_H + + +class CItem : public CBaseEntity +{ +public: + void Spawn( void ); + CBaseEntity* Respawn( void ); + void EXPORT ItemTouch( CBaseEntity *pOther ); + void EXPORT Materialize( void ); + virtual BOOL MyTouch( CBasePlayer *pPlayer ) { return FALSE; }; +}; + +#endif // ITEMS_H diff --git a/dmc/dlls/lights.cpp b/dmc/dlls/lights.cpp new file mode 100644 index 0000000..312aa30 --- /dev/null +++ b/dmc/dlls/lights.cpp @@ -0,0 +1,199 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== lights.cpp ======================================================== + + spawn and think functions for editor-placed lights + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" + + + +class CLight : public CPointEntity +{ +public: + virtual void KeyValue( KeyValueData* pkvd ); + virtual void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_iStyle; + int m_iszPattern; +}; +LINK_ENTITY_TO_CLASS( light, CLight ); + +TYPEDESCRIPTION CLight::m_SaveData[] = +{ + DEFINE_FIELD( CLight, m_iStyle, FIELD_INTEGER ), + DEFINE_FIELD( CLight, m_iszPattern, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CLight, CPointEntity ); + + +// +// Cache user-entity-field values until spawn is called. +// +void CLight :: KeyValue( KeyValueData* pkvd) +{ + if (FStrEq(pkvd->szKeyName, "style")) + { + m_iStyle = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitch")) + { + pev->angles.x = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pattern")) + { + m_iszPattern = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CPointEntity::KeyValue( pkvd ); + } +} + +/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) LIGHT_START_OFF +Non-displayed light. +Default light value is 300 +Default style is 0 +If targeted, it will toggle between on or off. +*/ + +void CLight :: Spawn( void ) +{ + if (FStringNull(pev->targetname)) + { // inert light + REMOVE_ENTITY(ENT(pev)); + return; + } + + if (m_iStyle >= 32) + { +// CHANGE_METHOD(ENT(pev), em_use, light_use); + if (FBitSet(pev->spawnflags, SF_LIGHT_START_OFF)) + LIGHT_STYLE(m_iStyle, "a"); + else if (m_iszPattern) + LIGHT_STYLE(m_iStyle, (char *)STRING( m_iszPattern )); + else + LIGHT_STYLE(m_iStyle, "m"); + } +} + + +void CLight :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (m_iStyle >= 32) + { + if ( !ShouldToggle( useType, !FBitSet(pev->spawnflags, SF_LIGHT_START_OFF) ) ) + return; + + if (FBitSet(pev->spawnflags, SF_LIGHT_START_OFF)) + { + if (m_iszPattern) + LIGHT_STYLE(m_iStyle, (char *)STRING( m_iszPattern )); + else + LIGHT_STYLE(m_iStyle, "m"); + ClearBits(pev->spawnflags, SF_LIGHT_START_OFF); + } + else + { + LIGHT_STYLE(m_iStyle, "a"); + SetBits(pev->spawnflags, SF_LIGHT_START_OFF); + } + } +} + +// +// shut up spawn functions for new spotlights +// +LINK_ENTITY_TO_CLASS( light_spot, CLight ); + + +class CEnvLight : public CLight +{ +public: + void KeyValue( KeyValueData* pkvd ); + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( light_environment, CEnvLight ); + +void CEnvLight::KeyValue( KeyValueData* pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "_light")) + { + int r, g, b, v, j; + char szColor[64]; + j = sscanf( pkvd->szValue, "%d %d %d %d\n", &r, &g, &b, &v ); + if (j == 1) + { + g = b = r; + } + else if (j == 4) + { + r = r * (v / 255.0); + g = g * (v / 255.0); + b = b * (v / 255.0); + } + + // simulate qrad direct, ambient,and gamma adjustments, as well as engine scaling + r = pow( r / 114.0, 0.6 ) * 264; + g = pow( g / 114.0, 0.6 ) * 264; + b = pow( b / 114.0, 0.6 ) * 264; + + pkvd->fHandled = TRUE; + sprintf( szColor, "%d", r ); + CVAR_SET_STRING( "sv_skycolor_r", szColor ); + sprintf( szColor, "%d", g ); + CVAR_SET_STRING( "sv_skycolor_g", szColor ); + sprintf( szColor, "%d", b ); + CVAR_SET_STRING( "sv_skycolor_b", szColor ); + } + else + { + CLight::KeyValue( pkvd ); + } +} + + +void CEnvLight :: Spawn( void ) +{ + char szVector[64]; + UTIL_MakeAimVectors( pev->angles ); + + sprintf( szVector, "%f", gpGlobals->v_forward.x ); + CVAR_SET_STRING( "sv_skyvec_x", szVector ); + sprintf( szVector, "%f", gpGlobals->v_forward.y ); + CVAR_SET_STRING( "sv_skyvec_y", szVector ); + sprintf( szVector, "%f", gpGlobals->v_forward.z ); + CVAR_SET_STRING( "sv_skyvec_z", szVector ); + + CLight::Spawn( ); +} diff --git a/dmc/dlls/maprules.cpp b/dmc/dlls/maprules.cpp new file mode 100644 index 0000000..f6d1334 --- /dev/null +++ b/dmc/dlls/maprules.cpp @@ -0,0 +1,918 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +// ------------------------------------------- +// +// maprules.cpp +// +// This module contains entities for implementing/changing game +// rules dynamically within each map (.BSP) +// +// ------------------------------------------- + +#include "extdll.h" +#include "eiface.h" +#include "util.h" +#include "gamerules.h" +#include "maprules.h" +#include "cbase.h" +#include "player.h" + +class CRuleEntity : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void SetMaster( int iszMaster ) { m_iszMaster = iszMaster; } + +protected: + BOOL CanFireForActivator( CBaseEntity *pActivator ); + +private: + string_t m_iszMaster; +}; + +TYPEDESCRIPTION CRuleEntity::m_SaveData[] = +{ + DEFINE_FIELD( CRuleEntity, m_iszMaster, FIELD_STRING), +}; + +IMPLEMENT_SAVERESTORE( CRuleEntity, CBaseEntity ); + + +void CRuleEntity::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = EF_NODRAW; +} + + +void CRuleEntity::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "master")) + { + SetMaster( ALLOC_STRING(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +BOOL CRuleEntity::CanFireForActivator( CBaseEntity *pActivator ) +{ + if ( m_iszMaster ) + { + if ( UTIL_IsMasterTriggered( m_iszMaster, pActivator ) ) + return TRUE; + else + return FALSE; + } + + return TRUE; +} + +// +// CRulePointEntity -- base class for all rule "point" entities (not brushes) +// +class CRulePointEntity : public CRuleEntity +{ +public: + void Spawn( void ); +}; + +void CRulePointEntity::Spawn( void ) +{ + CRuleEntity::Spawn(); + pev->frame = 0; + pev->model = 0; +} + +// +// CRuleBrushEntity -- base class for all rule "brush" entities (not brushes) +// Default behavior is to set up like a trigger, invisible, but keep the model for volume testing +// +class CRuleBrushEntity : public CRuleEntity +{ +public: + void Spawn( void ); + +private: +}; + +void CRuleBrushEntity::Spawn( void ) +{ + SET_MODEL( edict(), STRING(pev->model) ); + CRuleEntity::Spawn(); +} + + +// CGameScore / game_score -- award points to player / team +// Points +/- total +// Flag: Allow negative scores SF_SCORE_NEGATIVE +// Flag: Award points to team in teamplay SF_SCORE_TEAM + +#define SF_SCORE_NEGATIVE 0x0001 +#define SF_SCORE_TEAM 0x0002 + +class CGameScore : public CRulePointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline int Points( void ) { return pev->frags; } + inline BOOL AllowNegativeScore( void ) { return pev->spawnflags & SF_SCORE_NEGATIVE; } + inline BOOL AwardToTeam( void ) { return pev->spawnflags & SF_SCORE_TEAM; } + + inline void SetPoints( int points ) { pev->frags = points; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_score, CGameScore ); + + +void CGameScore::Spawn( void ) +{ + CRulePointEntity::Spawn(); +} + + +void CGameScore::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "points")) + { + SetPoints( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CRulePointEntity::KeyValue( pkvd ); +} + + + +void CGameScore::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + // Only players can use this + if ( pActivator->IsPlayer() ) + { + if ( AwardToTeam() ) + { + pActivator->AddPointsToTeam( Points(), AllowNegativeScore() ); + } + else + { + pActivator->AddPoints( Points(), AllowNegativeScore() ); + } + } +} + + +// CGameEnd / game_end -- Ends the game in MP + +class CGameEnd : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +private: +}; + +LINK_ENTITY_TO_CLASS( game_end, CGameEnd ); + + +void CGameEnd::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + g_pGameRules->EndMultiplayerGame(); +} + + +// +// CGameText / game_text -- NON-Localized HUD Message (use env_message to display a titles.txt message) +// Flag: All players SF_ENVTEXT_ALLPLAYERS +// + + +#define SF_ENVTEXT_ALLPLAYERS 0x0001 + + +class CGameText : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + inline BOOL MessageToAll( void ) { return (pev->spawnflags & SF_ENVTEXT_ALLPLAYERS); } + inline void MessageSet( const char *pMessage ) { pev->message = ALLOC_STRING(pMessage); } + inline const char *MessageGet( void ) { return STRING(pev->message); } + +private: + + hudtextparms_t m_textParms; +}; + +LINK_ENTITY_TO_CLASS( game_text, CGameText ); + +// Save parms as a block. Will break save/restore if the structure changes, but this entity didn't ship with Half-Life, so +// it can't impact saved Half-Life games. +TYPEDESCRIPTION CGameText::m_SaveData[] = +{ + DEFINE_ARRAY( CGameText, m_textParms, FIELD_CHARACTER, sizeof(hudtextparms_t) ), +}; + +IMPLEMENT_SAVERESTORE( CGameText, CRulePointEntity ); + + +void CGameText::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "channel")) + { + m_textParms.channel = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "x")) + { + m_textParms.x = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "y")) + { + m_textParms.y = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "effect")) + { + m_textParms.effect = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "color")) + { + int color[4]; + UTIL_StringToIntArray( color, 4, pkvd->szValue ); + m_textParms.r1 = color[0]; + m_textParms.g1 = color[1]; + m_textParms.b1 = color[2]; + m_textParms.a1 = color[3]; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "color2")) + { + int color[4]; + UTIL_StringToIntArray( color, 4, pkvd->szValue ); + m_textParms.r2 = color[0]; + m_textParms.g2 = color[1]; + m_textParms.b2 = color[2]; + m_textParms.a2 = color[3]; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "fadein")) + { + m_textParms.fadeinTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "fadeout")) + { + m_textParms.fadeoutTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "holdtime")) + { + m_textParms.holdTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "fxtime")) + { + m_textParms.fxTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CRulePointEntity::KeyValue( pkvd ); +} + + +void CGameText::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( MessageToAll() ) + { + UTIL_HudMessageAll( m_textParms, MessageGet() ); + } + else + { + if ( pActivator->IsNetClient() ) + { + UTIL_HudMessage( pActivator, m_textParms, MessageGet() ); + } + } +} + + +// +// CGameTeamMaster / game_team_master -- "Masters" like multisource, but based on the team of the activator +// Only allows mastered entity to fire if the team matches my team +// +// team index (pulled from server team list "mp_teamlist" +// Flag: Remove on Fire +// Flag: Any team until set? -- Any team can use this until the team is set (otherwise no teams can use it) +// + +#define SF_TEAMMASTER_FIREONCE 0x0001 +#define SF_TEAMMASTER_ANYTEAM 0x0002 + +class CGameTeamMaster : public CRulePointEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int ObjectCaps( void ) { return CRulePointEntity:: ObjectCaps() | FCAP_MASTER; } + + BOOL IsTriggered( CBaseEntity *pActivator ); + const char *TeamID( void ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_TEAMMASTER_FIREONCE) ? TRUE : FALSE; } + inline BOOL AnyTeam( void ) { return (pev->spawnflags & SF_TEAMMASTER_ANYTEAM) ? TRUE : FALSE; } + +private: + BOOL TeamMatch( CBaseEntity *pActivator ); + + int m_teamIndex; + USE_TYPE triggerType; +}; + +LINK_ENTITY_TO_CLASS( game_team_master, CGameTeamMaster ); + +void CGameTeamMaster::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "teamindex")) + { + m_teamIndex = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "triggerstate")) + { + int type = atoi( pkvd->szValue ); + switch( type ) + { + case 0: + triggerType = USE_OFF; + break; + case 2: + triggerType = USE_TOGGLE; + break; + default: + triggerType = USE_ON; + break; + } + pkvd->fHandled = TRUE; + } + else + CRulePointEntity::KeyValue( pkvd ); +} + + +void CGameTeamMaster::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( useType == USE_SET ) + { + if ( value < 0 ) + { + m_teamIndex = -1; + } + else + { + m_teamIndex = g_pGameRules->GetTeamIndex( pActivator->TeamID() ); + } + return; + } + + if ( TeamMatch( pActivator ) ) + { + SUB_UseTargets( pActivator, triggerType, value ); + if ( RemoveOnFire() ) + UTIL_Remove( this ); + } +} + + +BOOL CGameTeamMaster::IsTriggered( CBaseEntity *pActivator ) +{ + return TeamMatch( pActivator ); +} + + +const char *CGameTeamMaster::TeamID( void ) +{ + if ( m_teamIndex < 0 ) // Currently set to "no team" + return ""; + + return g_pGameRules->GetIndexedTeamName( m_teamIndex ); // UNDONE: Fill this in with the team from the "teamlist" +} + + +BOOL CGameTeamMaster::TeamMatch( CBaseEntity *pActivator ) +{ + if ( m_teamIndex < 0 && AnyTeam() ) + return TRUE; + + if ( !pActivator ) + return FALSE; + + return UTIL_TeamsMatch( pActivator->TeamID(), TeamID() ); +} + + +// +// CGameTeamSet / game_team_set -- Changes the team of the entity it targets to the activator's team +// Flag: Fire once +// Flag: Clear team -- Sets the team to "NONE" instead of activator + +#define SF_TEAMSET_FIREONCE 0x0001 +#define SF_TEAMSET_CLEARTEAM 0x0002 + +class CGameTeamSet : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_TEAMSET_FIREONCE) ? TRUE : FALSE; } + inline BOOL ShouldClearTeam( void ) { return (pev->spawnflags & SF_TEAMSET_CLEARTEAM) ? TRUE : FALSE; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_team_set, CGameTeamSet ); + + +void CGameTeamSet::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( ShouldClearTeam() ) + { + SUB_UseTargets( pActivator, USE_SET, -1 ); + } + else + { + SUB_UseTargets( pActivator, USE_SET, 0 ); + } + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + +// +// CGamePlayerZone / game_player_zone -- players in the zone fire my target when I'm fired +// +// Needs master? +class CGamePlayerZone : public CRuleBrushEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + string_t m_iszInTarget; + string_t m_iszOutTarget; + string_t m_iszInCount; + string_t m_iszOutCount; +}; + +LINK_ENTITY_TO_CLASS( game_zone_player, CGamePlayerZone ); +TYPEDESCRIPTION CGamePlayerZone::m_SaveData[] = +{ + DEFINE_FIELD( CGamePlayerZone, m_iszInTarget, FIELD_STRING ), + DEFINE_FIELD( CGamePlayerZone, m_iszOutTarget, FIELD_STRING ), + DEFINE_FIELD( CGamePlayerZone, m_iszInCount, FIELD_STRING ), + DEFINE_FIELD( CGamePlayerZone, m_iszOutCount, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CGamePlayerZone, CRuleBrushEntity ); + +void CGamePlayerZone::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "intarget")) + { + m_iszInTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "outtarget")) + { + m_iszOutTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "incount")) + { + m_iszInCount = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "outcount")) + { + m_iszOutCount = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CRuleBrushEntity::KeyValue( pkvd ); +} + +void CGamePlayerZone::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int playersInCount = 0; + int playersOutCount = 0; + + if ( !CanFireForActivator( pActivator ) ) + return; + + CBaseEntity *pPlayer = NULL; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + { + TraceResult trace; + int hullNumber; + + hullNumber = human_hull; + if ( pPlayer->pev->flags & FL_DUCKING ) + { + hullNumber = head_hull; + } + + UTIL_TraceModel( pPlayer->pev->origin, pPlayer->pev->origin, hullNumber, edict(), &trace ); + + if ( trace.fStartSolid ) + { + playersInCount++; + if ( m_iszInTarget ) + { + FireTargets( STRING(m_iszInTarget), pPlayer, pActivator, useType, value ); + } + } + else + { + playersOutCount++; + if ( m_iszOutTarget ) + { + FireTargets( STRING(m_iszOutTarget), pPlayer, pActivator, useType, value ); + } + } + } + } + + if ( m_iszInCount ) + { + FireTargets( STRING(m_iszInCount), pActivator, this, USE_SET, playersInCount ); + } + + if ( m_iszOutCount ) + { + FireTargets( STRING(m_iszOutCount), pActivator, this, USE_SET, playersOutCount ); + } +} + + + +// +// CGamePlayerHurt / game_player_hurt -- Damages the player who fires it +// Flag: Fire once + +#define SF_PKILL_FIREONCE 0x0001 +class CGamePlayerHurt : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_PKILL_FIREONCE) ? TRUE : FALSE; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_player_hurt, CGamePlayerHurt ); + + +void CGamePlayerHurt::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( pActivator->IsPlayer() ) + { + if ( pev->dmg < 0 ) + pActivator->TakeHealth( -pev->dmg, DMG_GENERIC ); + else + pActivator->TakeDamage( pev, pev, pev->dmg, DMG_GENERIC ); + } + + SUB_UseTargets( pActivator, useType, value ); + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + + +// +// CGameCounter / game_counter -- Counts events and fires target +// Flag: Fire once +// Flag: Reset on Fire + +#define SF_GAMECOUNT_FIREONCE 0x0001 +#define SF_GAMECOUNT_RESET 0x0002 + +class CGameCounter : public CRulePointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_GAMECOUNT_FIREONCE) ? TRUE : FALSE; } + inline BOOL ResetOnFire( void ) { return (pev->spawnflags & SF_GAMECOUNT_RESET) ? TRUE : FALSE; } + + inline void CountUp( void ) { pev->frags++; } + inline void CountDown( void ) { pev->frags--; } + inline void ResetCount( void ) { pev->frags = pev->dmg; } + inline int CountValue( void ) { return pev->frags; } + inline int LimitValue( void ) { return pev->health; } + + inline BOOL HitLimit( void ) { return CountValue() == LimitValue(); } + +private: + + inline void SetCountValue( int value ) { pev->frags = value; } + inline void SetInitialValue( int value ) { pev->dmg = value; } +}; + +LINK_ENTITY_TO_CLASS( game_counter, CGameCounter ); + +void CGameCounter::Spawn( void ) +{ + // Save off the initial count + SetInitialValue( CountValue() ); + CRulePointEntity::Spawn(); +} + + +void CGameCounter::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + switch( useType ) + { + case USE_ON: + case USE_TOGGLE: + CountUp(); + break; + + case USE_OFF: + CountDown(); + break; + + case USE_SET: + SetCountValue( (int)value ); + break; + } + + if ( HitLimit() ) + { + SUB_UseTargets( pActivator, USE_TOGGLE, 0 ); + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } + + if ( ResetOnFire() ) + { + ResetCount(); + } + } +} + + + +// +// CGameCounterSet / game_counter_set -- Sets the counter's value +// Flag: Fire once + +#define SF_GAMECOUNTSET_FIREONCE 0x0001 + +class CGameCounterSet : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_GAMECOUNTSET_FIREONCE) ? TRUE : FALSE; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_counter_set, CGameCounterSet ); + + +void CGameCounterSet::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + SUB_UseTargets( pActivator, USE_SET, pev->frags ); + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + +// +// CGamePlayerEquip / game_playerequip -- Sets the default player equipment +// Flag: USE Only + +#define SF_PLAYEREQUIP_USEONLY 0x0001 +#define MAX_EQUIP 32 + +class CGamePlayerEquip : public CRulePointEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Touch( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + inline BOOL UseOnly( void ) { return (pev->spawnflags & SF_PLAYEREQUIP_USEONLY) ? TRUE : FALSE; } + +private: + + void EquipPlayer( CBaseEntity *pPlayer ); + + string_t m_weaponNames[MAX_EQUIP]; + int m_weaponCount[MAX_EQUIP]; +}; + +LINK_ENTITY_TO_CLASS( game_player_equip, CGamePlayerEquip ); + + +void CGamePlayerEquip::KeyValue( KeyValueData *pkvd ) +{ + CRulePointEntity::KeyValue( pkvd ); + + if ( !pkvd->fHandled ) + { + for ( int i = 0; i < MAX_EQUIP; i++ ) + { + if ( !m_weaponNames[i] ) + { + char tmp[128]; + + UTIL_StripToken( pkvd->szKeyName, tmp ); + + m_weaponNames[i] = ALLOC_STRING(tmp); + m_weaponCount[i] = atoi(pkvd->szValue); + m_weaponCount[i] = max(1,m_weaponCount[i]); + pkvd->fHandled = TRUE; + break; + } + } + } +} + + +void CGamePlayerEquip::Touch( CBaseEntity *pOther ) +{ + if ( !CanFireForActivator( pOther ) ) + return; + + if ( UseOnly() ) + return; + + EquipPlayer( pOther ); +} + +void CGamePlayerEquip::EquipPlayer( CBaseEntity *pEntity ) +{ + CBasePlayer *pPlayer = NULL; + + if ( pEntity->IsPlayer() ) + { + pPlayer = (CBasePlayer *)pEntity; + } + + if ( !pPlayer ) + return; + + for ( int i = 0; i < MAX_EQUIP; i++ ) + { + if ( !m_weaponNames[i] ) + break; + for ( int j = 0; j < m_weaponCount[i]; j++ ) + { + pPlayer->GiveNamedItem( STRING(m_weaponNames[i]) ); + } + } +} + + +void CGamePlayerEquip::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + EquipPlayer( pActivator ); +} + + +// +// CGamePlayerTeam / game_player_team -- Changes the team of the player who fired it +// Flag: Fire once +// Flag: Kill Player +// Flag: Gib Player + +#define SF_PTEAM_FIREONCE 0x0001 +#define SF_PTEAM_KILL 0x0002 +#define SF_PTEAM_GIB 0x0004 + +class CGamePlayerTeam : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +private: + + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_PTEAM_FIREONCE) ? TRUE : FALSE; } + inline BOOL ShouldKillPlayer( void ) { return (pev->spawnflags & SF_PTEAM_KILL) ? TRUE : FALSE; } + inline BOOL ShouldGibPlayer( void ) { return (pev->spawnflags & SF_PTEAM_GIB) ? TRUE : FALSE; } + + const char *TargetTeamName( const char *pszTargetName ); +}; + +LINK_ENTITY_TO_CLASS( game_player_team, CGamePlayerTeam ); + + +const char *CGamePlayerTeam::TargetTeamName( const char *pszTargetName ) +{ + CBaseEntity *pTeamEntity = NULL; + + while ((pTeamEntity = UTIL_FindEntityByTargetname( pTeamEntity, pszTargetName )) != NULL) + { + if ( FClassnameIs( pTeamEntity->pev, "game_team_master" ) ) + return pTeamEntity->TeamID(); + } + + return NULL; +} + + +void CGamePlayerTeam::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( pActivator->IsPlayer() ) + { + const char *pszTargetTeam = TargetTeamName( STRING(pev->target) ); + if ( pszTargetTeam ) + { + CBasePlayer *pPlayer = (CBasePlayer *)pActivator; + g_pGameRules->ChangePlayerTeam( pPlayer, pszTargetTeam, ShouldKillPlayer(), ShouldGibPlayer() ); + } + } + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + diff --git a/dmc/dlls/maprules.h b/dmc/dlls/maprules.h new file mode 100644 index 0000000..3772094 --- /dev/null +++ b/dmc/dlls/maprules.h @@ -0,0 +1,22 @@ +/*** +* +* Copyright (c) 1996-2002 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef MAPRULES_H +#define MAPRULES_H + + + +#endif // MAPRULES_H + diff --git a/dmc/dlls/monsterevent.h b/dmc/dlls/monsterevent.h new file mode 100644 index 0000000..46c5624 --- /dev/null +++ b/dmc/dlls/monsterevent.h @@ -0,0 +1,34 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef MONSTEREVENT_H +#define MONSTEREVENT_H + +typedef struct +{ + int event; + char *options; +} MonsterEvent_t; + +#define EVENT_SPECIFIC 0 +#define EVENT_SCRIPTED 1000 +#define EVENT_SHARED 2000 +#define EVENT_CLIENT 5000 + +#define MONSTER_EVENT_BODYDROP_LIGHT 2001 +#define MONSTER_EVENT_BODYDROP_HEAVY 2002 + +#define MONSTER_EVENT_SWISHSOUND 2010 + +#endif // MONSTEREVENT_H diff --git a/dmc/dlls/monsters.cpp b/dmc/dlls/monsters.cpp new file mode 100644 index 0000000..f2ff0b4 --- /dev/null +++ b/dmc/dlls/monsters.cpp @@ -0,0 +1,134 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +/* + +===== monsters.cpp ======================================================== + + Monster-related utility code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "nodes.h" +#include "monsters.h" + +CBaseEntity* CBaseMonster :: CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ) { return NULL; } +void CBaseMonster :: Eat ( float flFullDuration ) { } +BOOL CBaseMonster :: FShouldEat ( void ) { return TRUE; } +void CBaseMonster :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) { } +void CBaseMonster :: BarnacleVictimReleased ( void ) { } +void CBaseMonster :: Listen ( void ) { } +float CBaseMonster :: FLSoundVolume ( CSound *pSound ) { return 0.0; } +BOOL CBaseMonster :: FValidateHintType ( short sHint ) { return FALSE; } +void CBaseMonster :: Look ( int iDistance ) { } +int CBaseMonster :: ISoundMask ( void ) { return 0; } +CSound* CBaseMonster :: PBestSound ( void ) { return NULL; } +CSound* CBaseMonster :: PBestScent ( void ) { return NULL; } +void CBaseMonster :: MonsterThink ( void ) { } +void CBaseMonster :: MonsterUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { } +int CBaseMonster :: IgnoreConditions ( void ) { return 0; } +void CBaseMonster :: RouteClear ( void ) { } +void CBaseMonster :: RouteNew ( void ) { } +BOOL CBaseMonster :: FRouteClear ( void ) { return FALSE; } +BOOL CBaseMonster :: FRefreshRoute ( void ) { return 0; } +BOOL CBaseMonster::MoveToEnemy( Activity movementAct, float waitTime ) { return FALSE; } +BOOL CBaseMonster::MoveToLocation( Activity movementAct, float waitTime, const Vector &goal ) { return FALSE; } +BOOL CBaseMonster::MoveToTarget( Activity movementAct, float waitTime ) { return FALSE; } +BOOL CBaseMonster::MoveToNode( Activity movementAct, float waitTime, const Vector &goal ) { return FALSE; } +int ShouldSimplify( int routeType ) { return TRUE; } +void CBaseMonster :: RouteSimplify( CBaseEntity *pTargetEnt ) { } +BOOL CBaseMonster :: FBecomeProne ( void ) { return TRUE; } +BOOL CBaseMonster :: CheckRangeAttack1 ( float flDot, float flDist ) { return FALSE; } +BOOL CBaseMonster :: CheckRangeAttack2 ( float flDot, float flDist ) { return FALSE; } +BOOL CBaseMonster :: CheckMeleeAttack1 ( float flDot, float flDist ) { return FALSE; } +BOOL CBaseMonster :: CheckMeleeAttack2 ( float flDot, float flDist ) { return FALSE; } +void CBaseMonster :: CheckAttacks ( CBaseEntity *pTarget, float flDist ) { } +BOOL CBaseMonster :: FCanCheckAttacks ( void ) { return FALSE; } +int CBaseMonster :: CheckEnemy ( CBaseEntity *pEnemy ) { return 0; } +void CBaseMonster :: PushEnemy( CBaseEntity *pEnemy, Vector &vecLastKnownPos ) { } +BOOL CBaseMonster :: PopEnemy( ) { return FALSE; } +void CBaseMonster :: SetActivity ( Activity NewActivity ) { } +void CBaseMonster :: SetSequenceByName ( char *szSequence ) { } +int CBaseMonster :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ) { return 0; } +float CBaseMonster :: OpenDoorAndWait( entvars_t *pevDoor ) { return 0.0; } +void CBaseMonster :: AdvanceRoute ( float distance ) { } +int CBaseMonster :: RouteClassify( int iMoveFlag ) { return 0; } +BOOL CBaseMonster :: BuildRoute ( const Vector &vecGoal, int iMoveFlag, CBaseEntity *pTarget ) { return FALSE; } +void CBaseMonster :: InsertWaypoint ( Vector vecLocation, int afMoveFlags ) { } +BOOL CBaseMonster :: FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ) { return FALSE; } +void CBaseMonster :: Move ( float flInterval ) { } +BOOL CBaseMonster:: ShouldAdvanceRoute( float flWaypointDist ) { return FALSE; } +void CBaseMonster::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) { } +void CBaseMonster :: MonsterInit ( void ) { } +void CBaseMonster :: MonsterInitThink ( void ) { } +void CBaseMonster :: StartMonster ( void ) { } +void CBaseMonster :: MovementComplete( void ) { } +int CBaseMonster::TaskIsRunning( void ) { return 0; } +int CBaseMonster::IRelationship ( CBaseEntity *pTarget ) { return 0; } +BOOL CBaseMonster :: FindCover ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ) { return FALSE; } +BOOL CBaseMonster :: BuildNearestRoute ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ) { return FALSE; } +CBaseEntity *CBaseMonster :: BestVisibleEnemy ( void ) { return NULL; } +BOOL CBaseMonster :: FInViewCone ( CBaseEntity *pEntity ) { return FALSE; } +BOOL CBaseMonster :: FInViewCone ( Vector *pOrigin ) { return FALSE; } +BOOL CBaseEntity :: FVisible ( CBaseEntity *pEntity ) { return FALSE; } +BOOL CBaseEntity :: FVisible ( const Vector &vecOrigin ) { return FALSE; } +void CBaseMonster :: MakeIdealYaw( Vector vecTarget ) { } +float CBaseMonster::FlYawDiff ( void ) { return 0.0; } +float CBaseMonster::ChangeYaw ( int yawSpeed ) { return 0; } +float CBaseMonster::VecToYaw ( Vector vecDir ) { return 0.0; } +void CBaseMonster :: SetEyePosition ( void ) { } +void CBaseMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) { } +Vector CBaseMonster :: GetGunPosition( void ) { return g_vecZero; } +void CBaseEntity::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) { } +void CBaseEntity::FireBullets(ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq, int iDamage, entvars_t *pevAttacker ) { } +BOOL CBaseMonster :: FGetNodeRoute ( Vector vecDest ) { return TRUE; } +int CBaseMonster :: FindHintNode ( void ) { return NO_NODE; } +void CBaseMonster::ReportAIState( void ) { } +void CBaseMonster :: KeyValue( KeyValueData *pkvd ) { } +BOOL CBaseMonster :: FCheckAITrigger ( void ) { return FALSE; } +int CBaseMonster :: CanPlaySequence( BOOL fDisregardMonsterState, int interruptLevel ) { return FALSE; } +BOOL CBaseMonster :: FindLateralCover ( const Vector &vecThreat, const Vector &vecViewOffset ) { return FALSE; } +Vector CBaseMonster :: ShootAtEnemy( const Vector &shootOrigin ) { return g_vecZero; } +BOOL CBaseMonster :: FacingIdeal( void ) { return FALSE; } +BOOL CBaseMonster :: FCanActiveIdle ( void ) { return FALSE; } +void CBaseMonster::PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ) { } +void CBaseMonster::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ) { } +void CBaseMonster::SentenceStop( void ) { } +void CBaseMonster::CorpseFallThink( void ) { } +void CBaseMonster :: MonsterInitDead( void ) { } +BOOL CBaseMonster :: BBoxFlat ( void ) { return TRUE; } +BOOL CBaseMonster :: GetEnemy ( void ) { return FALSE; } +void CBaseMonster :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) { } +CBaseEntity* CBaseMonster :: DropItem ( char *pszItemName, const Vector &vecPos, const Vector &vecAng ) { return NULL; } +BOOL CBaseMonster :: ShouldFadeOnDeath( void ) { return FALSE; } +void CBaseMonster::FadeMonster( void ) { } +BOOL CBaseMonster :: HasHumanGibs( void ) { return TRUE; } +BOOL CBaseMonster :: HasAlienGibs( void ) { return FALSE; } +MONSTERSTATE CBaseMonster :: GetIdealState ( void ) { return MONSTERSTATE_ALERT; } +Schedule_t* CBaseMonster :: GetScheduleOfType ( int Type ) { return NULL; } +Schedule_t *CBaseMonster :: GetSchedule ( void ) { return NULL; } +void CBaseMonster :: RunTask ( Task_t *pTask ) { } +void CBaseMonster :: StartTask ( Task_t *pTask ) { } +Schedule_t *CBaseMonster::ScheduleFromName( const char *pName ) { return NULL;} +void CBaseMonster::BecomeDead( void ) {} +void CBaseMonster :: RunAI ( void ) {} +void CBaseMonster :: Killed( entvars_t *pevAttacker, int iGib ) {} +int CBaseMonster :: TakeHealth (float flHealth, int bitsDamageType) { return 0; } +int CBaseMonster::Restore( class CRestore & ) { return 1; } +int CBaseMonster::Save( class CSave & ) { return 1; } + + diff --git a/dmc/dlls/monsters.h b/dmc/dlls/monsters.h new file mode 100644 index 0000000..f7f1a03 --- /dev/null +++ b/dmc/dlls/monsters.h @@ -0,0 +1,183 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef MONSTERS_H +#include "skill.h" +#define MONSTERS_H + +/* + +===== monsters.h ======================================================== + + Header file for monster-related utility code + +*/ + +// CHECKLOCALMOVE result types +#define LOCALMOVE_INVALID 0 // move is not possible +#define LOCALMOVE_INVALID_DONT_TRIANGULATE 1 // move is not possible, don't try to triangulate +#define LOCALMOVE_VALID 2 // move is possible + +// Hit Group standards +#define HITGROUP_GENERIC 0 +#define HITGROUP_HEAD 1 +#define HITGROUP_CHEST 2 +#define HITGROUP_STOMACH 3 +#define HITGROUP_LEFTARM 4 +#define HITGROUP_RIGHTARM 5 +#define HITGROUP_LEFTLEG 6 +#define HITGROUP_RIGHTLEG 7 + + +// Monster Spawnflags +#define SF_MONSTER_WAIT_TILL_SEEN 1// spawnflag that makes monsters wait until player can see them before attacking. +#define SF_MONSTER_GAG 2 // no idle noises from this monster +#define SF_MONSTER_HITMONSTERCLIP 4 +// 8 +#define SF_MONSTER_PRISONER 16 // monster won't attack anyone, no one will attacke him. +// 32 +// 64 +#define SF_MONSTER_WAIT_FOR_SCRIPT 128 //spawnflag that makes monsters wait to check for attacking until the script is done or they've been attacked +#define SF_MONSTER_PREDISASTER 256 //this is a predisaster scientist or barney. Influences how they speak. +#define SF_MONSTER_FADECORPSE 512 // Fade out corpse after death +#define SF_MONSTER_FALL_TO_GROUND 0x80000000 + +// specialty spawnflags +#define SF_MONSTER_TURRET_AUTOACTIVATE 32 +#define SF_MONSTER_TURRET_STARTINACTIVE 64 +#define SF_MONSTER_WAIT_UNTIL_PROVOKED 64 // don't attack the player unless provoked + + + +// MoveToOrigin stuff +#define MOVE_START_TURN_DIST 64 // when this far away from moveGoal, start turning to face next goal +#define MOVE_STUCK_DIST 32 // if a monster can't step this far, it is stuck. + + +// MoveToOrigin stuff +#define MOVE_NORMAL 0// normal move in the direction monster is facing +#define MOVE_STRAFE 1// moves in direction specified, no matter which way monster is facing + +// spawn flags 256 and above are already taken by the engine +extern void UTIL_MoveToOrigin( edict_t* pent, const Vector &vecGoal, float flDist, int iMoveType ); + +Vector VecCheckToss ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flGravityAdj = 1.0 ); +Vector VecCheckThrow ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flSpeed, float flGravityAdj = 1.0 ); +extern DLL_GLOBAL Vector g_vecAttackDir; +extern DLL_GLOBAL CONSTANT float g_flMeleeRange; +extern DLL_GLOBAL CONSTANT float g_flMediumRange; +extern DLL_GLOBAL CONSTANT float g_flLongRange; +extern void EjectBrass (const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int model, int soundtype ); +extern void ExplodeModel( const Vector &vecOrigin, float speed, int model, int count ); + +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget ); +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget, Vector &vecTargetOrigin, float flSize = 0.0 ); + +// monster to monster relationship types +#define R_AL -2 // (ALLY) pals. Good alternative to R_NO when applicable. +#define R_FR -1// (FEAR)will run +#define R_NO 0// (NO RELATIONSHIP) disregard +#define R_DL 1// (DISLIKE) will attack +#define R_HT 2// (HATE)will attack this character instead of any visible DISLIKEd characters +#define R_NM 3// (NEMESIS) A monster Will ALWAYS attack its nemsis, no matter what + + +// these bits represent the monster's memory +#define MEMORY_CLEAR 0 +#define bits_MEMORY_PROVOKED ( 1 << 0 )// right now only used for houndeyes. +#define bits_MEMORY_INCOVER ( 1 << 1 )// monster knows it is in a covered position. +#define bits_MEMORY_SUSPICIOUS ( 1 << 2 )// Ally is suspicious of the player, and will move to provoked more easily +#define bits_MEMORY_PATH_FINISHED ( 1 << 3 )// Finished monster path (just used by big momma for now) +#define bits_MEMORY_ON_PATH ( 1 << 4 )// Moving on a path +#define bits_MEMORY_MOVE_FAILED ( 1 << 5 )// Movement has already failed +#define bits_MEMORY_FLINCHED ( 1 << 6 )// Has already flinched +#define bits_MEMORY_KILLED ( 1 << 7 )// HACKHACK -- remember that I've already called my Killed() +#define bits_MEMORY_CUSTOM4 ( 1 << 28 ) // Monster-specific memory +#define bits_MEMORY_CUSTOM3 ( 1 << 29 ) // Monster-specific memory +#define bits_MEMORY_CUSTOM2 ( 1 << 30 ) // Monster-specific memory +#define bits_MEMORY_CUSTOM1 ( 1 << 31 ) // Monster-specific memory + +// trigger conditions for scripted AI +// these MUST match the CHOICES interface in halflife.fgd for the base monster +enum +{ + AITRIGGER_NONE = 0, + AITRIGGER_SEEPLAYER_ANGRY_AT_PLAYER, + AITRIGGER_TAKEDAMAGE, + AITRIGGER_HALFHEALTH, + AITRIGGER_DEATH, + AITRIGGER_SQUADMEMBERDIE, + AITRIGGER_SQUADLEADERDIE, + AITRIGGER_HEARWORLD, + AITRIGGER_HEARPLAYER, + AITRIGGER_HEARCOMBAT, + AITRIGGER_SEEPLAYER_UNCONDITIONAL, + AITRIGGER_SEEPLAYER_NOT_IN_COMBAT, +}; +/* + 0 : "No Trigger" + 1 : "See Player" + 2 : "Take Damage" + 3 : "50% Health Remaining" + 4 : "Death" + 5 : "Squad Member Dead" + 6 : "Squad Leader Dead" + 7 : "Hear World" + 8 : "Hear Player" + 9 : "Hear Combat" +*/ + +// +// A gib is a chunk of a body, or a piece of wood/metal/rocks/etc. +// +class CGib : public CBaseEntity +{ +public: + void Spawn( const char *szGibModel ); + void EXPORT BounceGibTouch ( CBaseEntity *pOther ); + void EXPORT StickyGibTouch ( CBaseEntity *pOther ); + void EXPORT WaitTillLand( void ); + void LimitVelocity( void ); + + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DONT_SAVE; } + static void SpawnHeadGib( entvars_t *pevVictim ); + static void SpawnRandomGibs( entvars_t *pevVictim, int cGibs, int human ); + static void SpawnStickyGibs( entvars_t *pevVictim, Vector vecOrigin, int cGibs ); + + int m_bloodColor; + int m_cBloodDecals; + int m_material; + float m_lifeTime; +}; + + +#define CUSTOM_SCHEDULES\ + virtual Schedule_t *ScheduleFromName( const char *pName );\ + static Schedule_t *m_scheduleList[]; + +#define DEFINE_CUSTOM_SCHEDULES(derivedClass)\ + Schedule_t *derivedClass::m_scheduleList[] = + +#define IMPLEMENT_CUSTOM_SCHEDULES(derivedClass, baseClass)\ + Schedule_t *derivedClass::ScheduleFromName( const char *pName )\ + {\ + Schedule_t *pSchedule = ScheduleInList( pName, m_scheduleList, ARRAYSIZE(m_scheduleList) );\ + if ( !pSchedule )\ + return baseClass::ScheduleFromName(pName);\ + return pSchedule;\ + } + + + +#endif //MONSTERS_H diff --git a/dmc/dlls/monsterstate.cpp b/dmc/dlls/monsterstate.cpp new file mode 100644 index 0000000..ddc0e70 --- /dev/null +++ b/dmc/dlls/monsterstate.cpp @@ -0,0 +1,28 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// monsterstate.cpp - base class monster functions for +// controlling core AI. +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "nodes.h" +#include "monsters.h" +#include "animation.h" +#include "saverestore.h" + +void CBaseMonster :: SetState ( MONSTERSTATE State ) { }; diff --git a/dmc/dlls/multiplay_gamerules.cpp b/dmc/dlls/multiplay_gamerules.cpp new file mode 100644 index 0000000..cae23d1 --- /dev/null +++ b/dmc/dlls/multiplay_gamerules.cpp @@ -0,0 +1,1672 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.cpp +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "skill.h" +#include "game.h" +#include "items.h" +#include "hltv.h" + +#if !defined ( _WIN32 ) +#include +#endif + +#define INTERMISSION_TIME 60 + +#if defined( THREEWAVE ) +char* GetTeamName( int team ); +#endif + +class CDMCGameMgrHelper : public IVoiceGameMgrHelper +{ +public: + virtual bool CanPlayerHearPlayer(CBasePlayer *pPlayer1, CBasePlayer *pPlayer2) + { + return true; + } +}; +static CDMCGameMgrHelper g_GameMgrHelper; + +bool g_bHaveMOTD; + +extern DLL_GLOBAL CGameRules *g_pGameRules; +extern DLL_GLOBAL BOOL g_fGameOver; +extern int gmsgDeathMsg; // client dll messages +extern int gmsgScoreInfo; +extern int gmsgMOTD; +extern int gmsgServerName; + +#define ITEM_RESPAWN_TIME 30 +#define WEAPON_RESPAWN_TIME 20 +#define AMMO_RESPAWN_TIME 20 + +char *g_szDeathType; + +//********************************************************* +// Rules for the half-life multiplayer game. +//********************************************************* + +CHalfLifeMultiplay :: CHalfLifeMultiplay() +{ + m_VoiceGameMgr.Init(&g_GameMgrHelper, gpGlobals->maxClients); + int length; + + char * pFileList = (char*)LOAD_FILE_FOR_ME( "motd.txt", &length ); + + if ( pFileList ) + g_bHaveMOTD = true; + else + g_bHaveMOTD = false; + + RefreshSkillData(); + m_flIntermissionEndTime = 0; + m_flGameEndTime = 0.0; + + // 11/8/98 + // Modified by YWB: Server .cfg file is now a cvar, so that + // server ops can run multiple game servers, with different server .cfg files, + // from a single installed directory. + // Mapcyclefile is already a cvar. + + // 3/31/99 + // Added lservercfg file cvar, since listen and dedicated servers should not + // share a single config file. (sjb) + if ( IS_DEDICATED_SERVER() ) + { + // dedicated server + char *servercfgfile = (char *)CVAR_GET_STRING( "servercfgfile" ); + + if ( servercfgfile && servercfgfile[0] ) + { + char szCommand[256]; + + ALERT( at_console, "Executing dedicated server config file\n" ); + sprintf( szCommand, "exec %s\n", servercfgfile ); + SERVER_COMMAND( szCommand ); + } + } + else + { + // listen server + char *lservercfgfile = (char *)CVAR_GET_STRING( "lservercfgfile" ); + + if ( lservercfgfile && lservercfgfile[0] ) + { + char szCommand[256]; + + ALERT( at_console, "Executing listen server config file\n" ); + sprintf( szCommand, "exec %s\n", lservercfgfile ); + SERVER_COMMAND( szCommand ); + } + } +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay::RefreshSkillData( void ) +{ +// load all default values + CGameRules::RefreshSkillData(); + +// override some values for multiplay. + + // suitcharger + gSkillData.suitchargerCapacity = 30; + + // Crowbar whack + gSkillData.plrDmgCrowbar = 25; + + // Glock Round + gSkillData.plrDmg9MM = 12; + + // 357 Round + gSkillData.plrDmg357 = 40; + + // MP5 Round + gSkillData.plrDmgMP5 = 12; + + // M203 grenade + gSkillData.plrDmgM203Grenade = 100; + + // Shotgun buckshot + gSkillData.plrDmgBuckshot = 20;// fewer pellets in deathmatch + + // Crossbow + gSkillData.plrDmgCrossbowClient = 20; + + // RPG + gSkillData.plrDmgRPG = 120; + + // Egon + gSkillData.plrDmgEgonWide = 20; + gSkillData.plrDmgEgonNarrow = 10; + + // Hand Grendade + gSkillData.plrDmgHandGrenade = 100; + + // Satchel Charge + gSkillData.plrDmgSatchel = 120; + + // Tripmine + gSkillData.plrDmgTripmine = 150; + + // hornet + gSkillData.plrDmgHornet = 10; +} + +// longest the intermission can last, in seconds +#define MAX_INTERMISSION_TIME 120 + +BOOL CHalfLifeMultiplay::ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) +{ + if(m_VoiceGameMgr.ClientCommand(pPlayer, pcmd)) + return TRUE; + + return CGameRules::ClientCommand(pPlayer, pcmd); +} + +extern cvar_t mp_chattime; + +extern cvar_t timeleft, fragsleft; +//========================================================= +//========================================================= +void CHalfLifeMultiplay :: Think ( void ) +{ + m_VoiceGameMgr.Update(gpGlobals->frametime); + + ///// Check game rules ///// + static int last_frags; + static int last_time; + + int frags_remaining = 0; + int time_remaining = 0; + + if ( g_fGameOver ) // someone else quit the game already + { + // bounds check + int time = (int)CVAR_GET_FLOAT( "mp_chattime" ); + if ( time < 1 ) + CVAR_SET_STRING( "mp_chattime", "1" ); + else if ( time > INTERMISSION_TIME ) + CVAR_SET_STRING( "mp_chattime", UTIL_dtos1( INTERMISSION_TIME ) ); + + m_flIntermissionEndTime = m_flIntermissionStartTime + mp_chattime.value; + + // check to see if we should change levels now + if ( m_flIntermissionEndTime < gpGlobals->time ) + { + if ( m_iEndIntermissionButtonHit // check that someone has pressed a key, or the max intermission time is over + || ( ( m_flIntermissionStartTime + INTERMISSION_TIME ) < gpGlobals->time) ) + ChangeLevel(); // intermission is over + } + + return; + } + + if ( m_flGameEndTime != 0.0 && m_flGameEndTime <= gpGlobals->time ) + { + GoToIntermission(); + m_flGameEndTime = 0.0; + return; + } + + float flTimeLimit = timelimit.value * 60; + float flFragLimit = fraglimit.value; + + time_remaining = (int)(flTimeLimit ? ( flTimeLimit - gpGlobals->time ) : 0); + + if ( flTimeLimit != 0 && gpGlobals->time >= flTimeLimit && m_flGameEndTime == 0.0 ) + { + GoToIntermission(); + return; + } + + if ( flFragLimit && m_flGameEndTime == 0.0 ) + { + int bestfrags = 9999; + int remain; + + // check if any player is over the frag limit + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + if ( pPlayer && pPlayer->pev->frags >= flFragLimit ) + { + m_flGameEndTime = gpGlobals->time + 1.5; + return; + } + + + if ( pPlayer ) + { + remain = flFragLimit - pPlayer->pev->frags; + if ( remain < bestfrags ) + { + bestfrags = remain; + } + } + + } + frags_remaining = bestfrags; + } + + // Updates when frags change + if ( frags_remaining != last_frags ) + { + g_engfuncs.pfnCvar_DirectSet( &fragsleft, UTIL_VarArgs( "%i", frags_remaining ) ); + } + + // Updates once per second + if ( timeleft.value != last_time ) + { + g_engfuncs.pfnCvar_DirectSet( &timeleft, UTIL_VarArgs( "%i", time_remaining ) ); + } + + last_frags = frags_remaining; + last_time = time_remaining; +} + + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsMultiplayer( void ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsDeathmatch( void ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsCoOp( void ) +{ + return gpGlobals->coop; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ + if ( !pWeapon->CanDeploy() ) + { + // that weapon can't deploy anyway. + return FALSE; + } + + if ( !pPlayer->m_pActiveItem ) + { + // player doesn't have an active item! + return TRUE; + } + + if ( !pPlayer->m_pActiveItem->CanHolster() ) + { + // can't put away the active item. + return FALSE; + } + + if ( pWeapon->iWeight() > pPlayer->m_pActiveItem->iWeight() ) + { + return TRUE; + } + + return FALSE; +} + +BOOL CHalfLifeMultiplay :: GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) +{ + + CBasePlayerItem *pCheck; + CBasePlayerItem *pBest;// this will be used in the event that we don't find a weapon in the same category. + int iBestWeight; + int i; + + iBestWeight = -1;// no weapon lower than -1 can be autoswitched to + pBest = NULL; + + if ( !pCurrentWeapon->CanHolster() ) + { + // can't put this gun away right now, so can't switch. + return FALSE; + } + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pCheck = pPlayer->m_rgpPlayerItems[ i ]; + + while ( pCheck ) + { + if ( pCheck->iWeight() > -1 && pCheck->iWeight() == pCurrentWeapon->iWeight() && pCheck != pCurrentWeapon ) + { + // this weapon is from the same category. + if ( pCheck->CanDeploy() ) + { + if ( pPlayer->SwitchWeapon( pCheck ) ) + { + return TRUE; + } + } + } + else if ( pCheck->iWeight() > iBestWeight && pCheck != pCurrentWeapon )// don't reselect the weapon we're trying to get rid of + { + //ALERT ( at_console, "Considering %s\n", STRING( pCheck->pev->classname ) ); + // we keep updating the 'best' weapon just in case we can't find a weapon of the same weight + // that the player was using. This will end up leaving the player with his heaviest-weighted + // weapon. + if ( pCheck->CanDeploy() ) + { + // if this weapon is useable, flag it as the best + iBestWeight = pCheck->iWeight(); + pBest = pCheck; + } + } + + pCheck = pCheck->m_pNext; + } + } + + // if we make it here, we've checked all the weapons and found no useable + // weapon in the same catagory as the current weapon. + + // if pBest is null, we didn't find ANYTHING. Shouldn't be possible- should always + // at least get the crowbar, but ya never know. + if ( !pBest ) + { + return FALSE; + } + + pPlayer->SwitchWeapon( pBest ); + + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay :: ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) +{ + m_VoiceGameMgr.ClientConnected(pEntity); + return TRUE; +} + +extern int gmsgSayText; +extern int gmsgGameMode; + +void CHalfLifeMultiplay :: UpdateGameMode( CBasePlayer *pPlayer ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgGameMode, NULL, pPlayer->edict() ); + WRITE_BYTE( 0 ); // game mode none + MESSAGE_END(); +} + +void CHalfLifeMultiplay :: InitHUD( CBasePlayer *pl ) +{ + // notify other clients of player joining the game + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, UTIL_VarArgs( "%s has joined the game\n", + ( pl->pev->netname && STRING(pl->pev->netname)[0] != 0 ) ? STRING(pl->pev->netname) : "unconnected" ) ); + +#if !defined( THREEWAVE ) + + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" entered the game\n", + STRING( pl->pev->netname ), + GETPLAYERUSERID( pl->edict() ), + GETPLAYERAUTHID( pl->edict() ), + GETPLAYERUSERID( pl->edict() ) ); +#else + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" entered the game\n", + STRING( pl->pev->netname ), + GETPLAYERUSERID( pl->edict() ), + GETPLAYERAUTHID( pl->edict() ), + GetTeamName( pl->pev->team ) ); +#endif + + UpdateGameMode( pl ); + + // sending just one score makes the hud scoreboard active; otherwise + // it is just disabled for single play + MESSAGE_BEGIN( MSG_ONE, gmsgScoreInfo, NULL, pl->edict() ); + WRITE_BYTE( ENTINDEX(pl->edict()) ); + WRITE_SHORT( 0 ); + WRITE_SHORT( 0 ); + WRITE_SHORT( 0 ); + MESSAGE_END(); + + SendMOTDToClient( pl->edict() ); + + // loop through all active players and send their score info to the new client + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + // FIXME: Probably don't need to cast this just to read m_iDeaths + CBasePlayer *plr = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if ( plr ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgScoreInfo, NULL, pl->edict() ); + WRITE_BYTE( i ); // client number + WRITE_SHORT( plr->pev->frags ); + WRITE_SHORT( plr->m_iDeaths ); + WRITE_SHORT( plr->pev->team ); + MESSAGE_END(); + } + } + + if ( g_fGameOver ) + { + MESSAGE_BEGIN( MSG_ONE, SVC_INTERMISSION, NULL, pl->edict() ); + MESSAGE_END(); + } +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay :: ClientDisconnected( edict_t *pClient ) +{ + if ( pClient ) + { + CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pClient ); + + if ( pPlayer ) + { + FireTargets( "game_playerleave", pPlayer, pPlayer, USE_TOGGLE, 0 ); + +#if !defined( THREEWAVE ) + + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" disconnected\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + GETPLAYERUSERID( pPlayer->edict() ) ); +#else + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" disconnected\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + GetTeamName( pPlayer->pev->team ) ); +#endif + + pPlayer->RemoveAllItems( TRUE );// destroy all of the players weapons and items + } + } +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay :: FlPlayerFallDamage( CBasePlayer *pPlayer ) +{ + int iFallDamage = (int)CVAR_GET_FLOAT("mp_falldamage"); + + switch ( iFallDamage ) + { + case 1://progressive + pPlayer->m_flFallVelocity -= PLAYER_MAX_SAFE_FALL_SPEED; + return pPlayer->m_flFallVelocity * DAMAGE_FOR_FALL_SPEED; + break; + default: + case 0:// fixed + return 5; + break; + } +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay :: PlayerThink( CBasePlayer *pPlayer ) +{ + if ( g_fGameOver ) + { + // check for button presses + if ( pPlayer->m_afButtonPressed & ( IN_DUCK | IN_ATTACK | IN_ATTACK2 | IN_USE | IN_JUMP ) ) + m_iEndIntermissionButtonHit = TRUE; + + // clear attack/use commands from player + pPlayer->m_afButtonPressed = 0; + pPlayer->pev->button = 0; + pPlayer->m_afButtonReleased = 0; + } +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay :: PlayerSpawn( CBasePlayer *pPlayer ) +{ + BOOL addDefault; + CBaseEntity *pWeaponEntity = NULL; + + pPlayer->pev->weapons |= (1<Touch( pPlayer ); + addDefault = FALSE; + } + + if ( addDefault ) + { + // Start with init ammoload + pPlayer->m_iAmmoShells = 25; + + // Start with shotgun and axe + pPlayer->GiveNamedItem( "weapon_quakegun" ); + pPlayer->m_iQuakeItems |= (IT_SHOTGUN | IT_AXE); + pPlayer->m_iQuakeWeapon = pPlayer->W_BestWeapon(); + pPlayer->W_SetCurrentAmmo(); + } +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay :: FPlayerCanRespawn( CBasePlayer *pPlayer ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay :: FlPlayerSpawnTime( CBasePlayer *pPlayer ) +{ + return gpGlobals->time;//now! +} + +BOOL CHalfLifeMultiplay :: AllowAutoTargetCrosshair( void ) +{ + return ( CVAR_GET_FLOAT( "mp_autocrosshair" ) != 0 ); +} + +//========================================================= +// IPointsForKill - how many points awarded to anyone +// that kills this player? +//========================================================= +int CHalfLifeMultiplay :: IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) +{ + return 1; +} + +void CHalfLifeMultiplay::ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ) +{ + pPlayer->m_iFOV = atoi( g_engfuncs.pfnInfoKeyValue( infobuffer, "cl_fov" ) ); + pPlayer->m_iAutoWepSwitch = atoi( g_engfuncs.pfnInfoKeyValue( infobuffer, "cl_autowepswitch" ) ); +} + +//========================================================= +// PlayerKilled - someone/something killed this player +//========================================================= +void CHalfLifeMultiplay :: PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) +{ + DeathNotice( pVictim, pKiller, pInflictor ); + + pVictim->m_iDeaths += 1; + + + FireTargets( "game_playerdie", pVictim, pVictim, USE_TOGGLE, 0 ); + CBasePlayer *peKiller = NULL; + CBaseEntity *ktmp = CBaseEntity::Instance( pKiller ); + if ( ktmp && (ktmp->Classify() == CLASS_PLAYER) ) + peKiller = (CBasePlayer*)ktmp; + + if ( pVictim->pev == pKiller ) + { // killed self + pKiller->frags -= 1; + } + else if ( ktmp && ktmp->IsPlayer() ) + { + // if a player dies in a deathmatch game and the killer is a client, award the killer some points + pKiller->frags += IPointsForKill( peKiller, pVictim ); + + FireTargets( "game_playerkill", ktmp, ktmp, USE_TOGGLE, 0 ); + } + else + { // killed by the world + pVictim->pev->frags -= 1; + } + + // update the scores + // killed scores + MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo ); + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); + WRITE_SHORT( pVictim->pev->frags ); + WRITE_SHORT( pVictim->m_iDeaths ); + WRITE_SHORT( pVictim->pev->team ); + MESSAGE_END(); + + // killers score, if it's a player + CBaseEntity *ep = CBaseEntity::Instance( pKiller ); + if ( ep && ep->Classify() == CLASS_PLAYER ) + { + CBasePlayer *PK = (CBasePlayer*)ep; + + MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo ); + WRITE_BYTE( ENTINDEX(PK->edict()) ); + WRITE_SHORT( PK->pev->frags ); + WRITE_SHORT( PK->m_iDeaths ); + WRITE_SHORT( PK->pev->team ); + MESSAGE_END(); + + // let the killer paint another decal as soon as he'd like. + PK->m_flNextDecalTime = gpGlobals->time; + } +} + +//========================================================= +// Deathnotice. +//========================================================= +void CHalfLifeMultiplay::DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pevInflictor ) +{ + // Work out what killed the player, and send a message to all clients about it + CBaseEntity *Killer = CBaseEntity::Instance( pKiller ); + + const char *killer_weapon_name = "world"; // by default, the player is killed by the world + int killer_index = 0; + + // Hack to fix name change + char *tau = "tau_cannon"; + char *gluon = "gluon gun"; + + // QUAKECLASSIC + // Might have overridden + if (g_szDeathType) + { + killer_weapon_name = g_szDeathType; + } + else + { + if ( pKiller->flags & FL_CLIENT ) + { + killer_index = ENTINDEX(ENT(pKiller)); + + if ( pevInflictor ) + { + if ( pevInflictor == pKiller ) + { + // If the inflictor is the killer, then it must be their current weapon doing the damage + CBasePlayer *pPlayer = (CBasePlayer*)CBaseEntity::Instance( pKiller ); + + switch( pPlayer->m_iQuakeWeapon ) + { + case IT_AXE: killer_weapon_name = "weapon_axe"; break; + case IT_SHOTGUN: killer_weapon_name = "weapon_shotgun"; break; + case IT_SUPER_SHOTGUN: killer_weapon_name = "weapon_doubleshotgun"; break; + case IT_NAILGUN: killer_weapon_name = "weapon_nailgun"; break; + case IT_SUPER_NAILGUN: killer_weapon_name = "weapon_supernail"; break; + case IT_GRENADE_LAUNCHER: killer_weapon_name = "weapon_grenadel"; break; + case IT_ROCKET_LAUNCHER: killer_weapon_name = "weapon_rocketl"; break; + case IT_LIGHTNING: killer_weapon_name = "weapon_lightning"; break; + + default: + killer_weapon_name = "Empty"; + } + + } + else + { + killer_weapon_name = STRING( pevInflictor->classname ); // it's just that easy + } + } + } + else + { + killer_weapon_name = STRING( pevInflictor->classname ); + } + + // strip the monster_* or weapon_* from the inflictor's classname + if ( strncmp( killer_weapon_name, "weapon_", 7 ) == 0 ) + killer_weapon_name += 7; + else if ( strncmp( killer_weapon_name, "monster_", 8 ) == 0 ) + killer_weapon_name += 8; + else if ( strncmp( killer_weapon_name, "func_", 5 ) == 0 ) + killer_weapon_name += 5; + } + + MESSAGE_BEGIN( MSG_ALL, gmsgDeathMsg ); + WRITE_BYTE( killer_index ); // the killer + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); // the victim + WRITE_STRING( killer_weapon_name ); // what they were killed by (should this be a string?) + MESSAGE_END(); + + // replace the code names with the 'real' names + if ( !strcmp( killer_weapon_name, "egon" ) ) + killer_weapon_name = gluon; + else if ( !strcmp( killer_weapon_name, "gauss" ) ) + killer_weapon_name = tau; + + if ( pVictim->pev == pKiller ) + { + // killed self +#if !defined( THREEWAVE ) + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" committed suicide with \"%s\"\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); +#else + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" committed suicide with \"%s\"\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GetTeamName( pVictim->pev->team ), + killer_weapon_name ); +#endif + } + else if ( pKiller->flags & FL_CLIENT ) + { +#if !defined( THREEWAVE ) + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" killed \"%s<%i><%s><%i>\" with \"%s\"\n", + STRING( pKiller->netname ), + GETPLAYERUSERID( ENT(pKiller) ), + GETPLAYERAUTHID( ENT(pKiller) ), + GETPLAYERUSERID( ENT(pKiller) ), + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); +#else + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" killed \"%s<%i><%s><%s>\" with \"%s\"\n", + STRING( pKiller->netname ), + GETPLAYERUSERID( ENT(pKiller) ), + GETPLAYERAUTHID( ENT(pKiller) ), + GetTeamName( pKiller->team ), + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GetTeamName( pVictim->pev->team ), + killer_weapon_name ); +#endif + } + else + { + // killed by the world +#if !defined( THREEWAVE ) + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" committed suicide with \"%s\" (world)\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); +#else + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" committed suicide with \"%s\" (world)\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GetTeamName( pVictim->pev->team ), + killer_weapon_name ); +#endif + } + + g_szDeathType = NULL; + + // HLTV event msg + MESSAGE_BEGIN( MSG_SPEC, SVC_DIRECTOR ); + WRITE_BYTE ( 9 ); // command length in bytes + WRITE_BYTE ( DRC_CMD_EVENT ); // player killed + WRITE_SHORT( ENTINDEX(pVictim->edict()) ); // index number of primary entity + if (pevInflictor) + WRITE_SHORT( ENTINDEX(ENT(pevInflictor)) ); // index number of secondary entity + else + WRITE_SHORT( ENTINDEX(ENT(pKiller)) ); // index number of secondary entity + WRITE_LONG( 7 | DRC_FLAG_DRAMATIC); // eventflags (priority and flags) + MESSAGE_END(); + +// Print a standard message + // TODO: make this go direct to console + return; // just remove for now + +} + +//========================================================= +// PlayerGotWeapon - player has grabbed a weapon that was +// sitting in the world +//========================================================= +void CHalfLifeMultiplay :: PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ +} + +//========================================================= +// FlWeaponRespawnTime - what is the time in the future +// at which this weapon may spawn? +//========================================================= +float CHalfLifeMultiplay :: FlWeaponRespawnTime( CBasePlayerItem *pWeapon ) +{ + if ( CVAR_GET_FLOAT("mp_weaponstay") > 0 ) + { + // make sure it's only certain weapons + if ( !(pWeapon->iFlags() & ITEM_FLAG_LIMITINWORLD) ) + { + return gpGlobals->time + 0; // weapon respawns almost instantly + } + } + + return gpGlobals->time + WEAPON_RESPAWN_TIME; +} + +// when we are within this close to running out of entities, items +// marked with the ITEM_FLAG_LIMITINWORLD will delay their respawn +#define ENTITY_INTOLERANCE 100 + +//========================================================= +// FlWeaponRespawnTime - Returns 0 if the weapon can respawn +// now, otherwise it returns the time at which it can try +// to spawn again. +//========================================================= +float CHalfLifeMultiplay :: FlWeaponTryRespawn( CBasePlayerItem *pWeapon ) +{ + if ( pWeapon && pWeapon->m_iId && (pWeapon->iFlags() & ITEM_FLAG_LIMITINWORLD) ) + { + if ( NUMBER_OF_ENTITIES() < (gpGlobals->maxEntities - ENTITY_INTOLERANCE) ) + return 0; + + // we're past the entity tolerance level, so delay the respawn + return FlWeaponRespawnTime( pWeapon ); + } + + return 0; +} + +//========================================================= +// VecWeaponRespawnSpot - where should this weapon spawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHalfLifeMultiplay :: VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ) +{ + return pWeapon->pev->origin; +} + +//========================================================= +// WeaponShouldRespawn - any conditions inhibiting the +// respawning of this weapon? +//========================================================= +int CHalfLifeMultiplay :: WeaponShouldRespawn( CBasePlayerItem *pWeapon ) +{ + if ( pWeapon->pev->spawnflags & SF_NORESPAWN ) + { + return GR_WEAPON_RESPAWN_NO; + } + + return GR_WEAPON_RESPAWN_YES; +} + +//========================================================= +// CanHaveWeapon - returns FALSE if the player is not allowed +// to pick up this weapon +//========================================================= +BOOL CHalfLifeMultiplay::CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pItem ) +{ + if ( CVAR_GET_FLOAT("mp_weaponstay") > 0 ) + { + if ( pItem->iFlags() & ITEM_FLAG_LIMITINWORLD ) + return CGameRules::CanHavePlayerItem( pPlayer, pItem ); + + // check if the player already has this weapon + for ( int i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + CBasePlayerItem *it = pPlayer->m_rgpPlayerItems[i]; + + while ( it != NULL ) + { + if ( it->m_iId == pItem->m_iId ) + { + return FALSE; + } + + it = it->m_pNext; + } + } + } + + return CGameRules::CanHavePlayerItem( pPlayer, pItem ); +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay::PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ) +{ +} + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::ItemShouldRespawn( CItem *pItem ) +{ + if ( pItem->pev->spawnflags & SF_NORESPAWN ) + { + return GR_ITEM_RESPAWN_NO; + } + + return GR_ITEM_RESPAWN_YES; +} + + +//========================================================= +// At what time in the future may this Item respawn? +//========================================================= +float CHalfLifeMultiplay::FlItemRespawnTime( CItem *pItem ) +{ + return gpGlobals->time + ITEM_RESPAWN_TIME; +} + +//========================================================= +// Where should this item respawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHalfLifeMultiplay::VecItemRespawnSpot( CItem *pItem ) +{ + return pItem->pev->origin; +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay::PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ) +{ +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsAllowedToSpawn( CBaseEntity *pEntity ) +{ +// if ( pEntity->pev->flags & FL_MONSTER ) +// return FALSE; + + return TRUE; +} + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ) +{ + if ( pAmmo->pev->spawnflags & SF_NORESPAWN ) + { + return GR_AMMO_RESPAWN_NO; + } + + return GR_AMMO_RESPAWN_YES; +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay::FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ) +{ + return gpGlobals->time + AMMO_RESPAWN_TIME; +} + +//========================================================= +//========================================================= +Vector CHalfLifeMultiplay::VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ) +{ + return pAmmo->pev->origin; +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay::FlHealthChargerRechargeTime( void ) +{ + return 60; +} + + +float CHalfLifeMultiplay::FlHEVChargerRechargeTime( void ) +{ + return 30; +} + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::DeadPlayerWeapons( CBasePlayer *pPlayer ) +{ + return GR_PLR_DROP_GUN_ACTIVE; +} + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::DeadPlayerAmmo( CBasePlayer *pPlayer ) +{ + return GR_PLR_DROP_AMMO_ACTIVE; +} + +edict_t *CHalfLifeMultiplay::GetPlayerSpawnSpot( CBasePlayer *pPlayer ) +{ + edict_t *pentSpawnSpot = CGameRules::GetPlayerSpawnSpot( pPlayer ); + if ( IsMultiplayer() && pentSpawnSpot->v.target ) + { + FireTargets( STRING(pentSpawnSpot->v.target), pPlayer, pPlayer, USE_TOGGLE, 0 ); + } + + return pentSpawnSpot; +} + + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) +{ + // half life deathmatch has only enemies + return GR_NOTTEAMMATE; +} + +BOOL CHalfLifeMultiplay :: PlayFootstepSounds( CBasePlayer *pl, float fvol ) +{ + return FALSE; + + if ( g_footsteps && g_footsteps->value == 0 ) + return FALSE; + + if ( pl->IsOnLadder() || pl->pev->velocity.Length2D() > 220 ) + return TRUE; // only make step sounds in multiplayer if the player is moving fast enough + + return FALSE; +} + +BOOL CHalfLifeMultiplay :: FAllowFlashlight( void ) +{ + return CVAR_GET_FLOAT( "mp_flashlight" ) != 0; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay :: FAllowMonsters( void ) +{ + return ( CVAR_GET_FLOAT( "mp_allowmonsters" ) != 0 ); +} + +//========================================================= +//======== CHalfLifeMultiplay private functions =========== + +void CHalfLifeMultiplay :: GoToIntermission( void ) +{ + if ( g_fGameOver ) + return; // intermission has already been triggered, so ignore. + + MESSAGE_BEGIN(MSG_ALL, SVC_INTERMISSION); + MESSAGE_END(); + + // bounds check + int time = (int)CVAR_GET_FLOAT( "mp_chattime" ); + if ( time < 1 ) + CVAR_SET_STRING( "mp_chattime", "1" ); + else if ( time > INTERMISSION_TIME ) + CVAR_SET_STRING( "mp_chattime", UTIL_dtos1( INTERMISSION_TIME ) ); + + m_flIntermissionEndTime = gpGlobals->time + ( (int)mp_chattime.value ); + m_flIntermissionStartTime = gpGlobals->time; + + g_fGameOver = TRUE; + m_iEndIntermissionButtonHit = FALSE; +} + +#define MAX_RULE_BUFFER 1024 + +typedef struct mapcycle_item_s +{ + struct mapcycle_item_s *next; + + char mapname[ 32 ]; + int minplayers, maxplayers; + char rulebuffer[ MAX_RULE_BUFFER ]; +} mapcycle_item_t; + +typedef struct mapcycle_s +{ + struct mapcycle_item_s *items; + struct mapcycle_item_s *next_item; +} mapcycle_t; + +/* +============== +DestroyMapCycle + +Clean up memory used by mapcycle when switching it +============== +*/ +void DestroyMapCycle( mapcycle_t *cycle ) +{ + mapcycle_item_t *p, *n, *start; + p = cycle->items; + if ( p ) + { + start = p; + p = p->next; + while ( p != start ) + { + n = p->next; + delete p; + p = n; + } + + delete cycle->items; + } + cycle->items = NULL; + cycle->next_item = NULL; +} + +static char com_token[ 1500 ]; + +/* +============== +COM_Parse + +Parse a token out of a string +============== +*/ +char *COM_Parse (char *data) +{ + int c; + int len; + + len = 0; + com_token[0] = 0; + + if (!data) + return NULL; + +// skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + return NULL; // end of file; + data++; + } + +// skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + + +// handle quoted strings specially + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if (c=='\"' || !c) + { + com_token[len] = 0; + return data; + } + com_token[len] = c; + len++; + } + } + +// parse single characters + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c == ',' ) + { + com_token[len] = c; + len++; + com_token[len] = 0; + return data+1; + } + +// parse a regular word + do + { + com_token[len] = c; + data++; + len++; + c = *data; + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c == ',' ) + break; + } while (c>32); + + com_token[len] = 0; + return data; +} + +/* +============== +COM_TokenWaiting + +Returns 1 if additional data is waiting to be processed on this line +============== +*/ +int COM_TokenWaiting( char *buffer ) +{ + char *p; + + p = buffer; + while ( *p && *p!='\n') + { + if ( !isspace( *p ) || isalnum( *p ) ) + return 1; + + p++; + } + + return 0; +} + +/* +============== +ReloadMapCycleFile + +Parses mapcycle.txt file into mapcycle_t structure +============== +*/ +int ReloadMapCycleFile( char *filename, mapcycle_t *cycle ) +{ + char szBuffer[ MAX_RULE_BUFFER ]; + char szMap[ 32 ]; + int length; + char *pFileList; + char *aFileList = pFileList = (char*)LOAD_FILE_FOR_ME( filename, &length ); + int hasbuffer; + mapcycle_item_s *item, *newlist = NULL, *next; + + if ( pFileList && length ) + { + // the first map name in the file becomes the default + while ( 1 ) + { + hasbuffer = 0; + memset( szBuffer, 0, MAX_RULE_BUFFER ); + + + pFileList = COM_Parse( pFileList ); + if ( strlen( com_token ) <= 0 ) + + + break; + + strcpy( szMap, com_token ); + + // Any more tokens on this line? + if ( COM_TokenWaiting( pFileList ) ) + { + pFileList = COM_Parse( pFileList ); + if ( strlen( com_token ) > 0 ) + { + hasbuffer = 1; + strcpy( szBuffer, com_token ); + } + } + + // Check map + if ( IS_MAP_VALID( szMap ) ) + { + // Create entry + char *s; + + item = new mapcycle_item_s; + + strcpy( item->mapname, szMap ); + + item->minplayers = 0; + item->maxplayers = 0; + + memset( item->rulebuffer, 0, MAX_RULE_BUFFER ); + + if ( hasbuffer ) + { + s = g_engfuncs.pfnInfoKeyValue( szBuffer, "minplayers" ); + if ( s && s[0] ) + { + item->minplayers = atoi( s ); + item->minplayers = max( item->minplayers, 0 ); + item->minplayers = min( item->minplayers, gpGlobals->maxClients ); + } + s = g_engfuncs.pfnInfoKeyValue( szBuffer, "maxplayers" ); + if ( s && s[0] ) + { + item->maxplayers = atoi( s ); + item->maxplayers = max( item->maxplayers, 0 ); + item->maxplayers = min( item->maxplayers, gpGlobals->maxClients ); + } + + // Remove keys + // + g_engfuncs.pfnInfo_RemoveKey( szBuffer, "minplayers" ); + g_engfuncs.pfnInfo_RemoveKey( szBuffer, "maxplayers" ); + + strcpy( item->rulebuffer, szBuffer ); + } + + item->next = cycle->items; + cycle->items = item; + } + else + { + ALERT( at_console, "Skipping %s from mapcycle, not a valid map\n", szMap ); + } + + + } + + FREE_FILE( aFileList ); + } + + // Fixup circular list pointer + item = cycle->items; + + // Reverse it to get original order + while ( item ) + { + next = item->next; + item->next = newlist; + newlist = item; + item = next; + } + cycle->items = newlist; + item = cycle->items; + + // Didn't parse anything + if ( !item ) + { + return 0; + } + + while ( item->next ) + { + item = item->next; + } + item->next = cycle->items; + + cycle->next_item = item->next; + + return 1; +} + +/* +============== +CountPlayers + +Determine the current # of active players on the server for map cycling logic +============== +*/ +int CountPlayers( void ) +{ + int num = 0; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pEnt = UTIL_PlayerByIndex( i ); + + if ( pEnt ) + { + num = num + 1; + } + } + + return num; +} + +/* +============== +ExtractCommandString + +Parse commands/key value pairs to issue right after map xxx command is issued on server + level transition +============== +*/ +void ExtractCommandString( char *s, char *szCommand ) +{ + // Now make rules happen + char pkey[512]; + char value[512]; // use two buffers so compares + // work without stomping on each other + char *o; + + if ( *s == '\\' ) + s++; + + while (1) + { + o = pkey; + while ( *s != '\\' ) + { + if ( !*s ) + return; + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + + while (*s != '\\' && *s) + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + + strcat( szCommand, pkey ); + if ( strlen( value ) > 0 ) + { + strcat( szCommand, " " ); + strcat( szCommand, value ); + } + strcat( szCommand, "\n" ); + + if (!*s) + return; + s++; + } +} + +/* +============== +ChangeLevel + +Server is changing to a new level, check mapcycle.txt for map name and setup info +============== +*/ +void CHalfLifeMultiplay :: ChangeLevel( void ) +{ + static char szPreviousMapCycleFile[ 256 ]; + static mapcycle_t mapcycle; + + char szNextMap[32]; + char szFirstMapInList[32]; + char szCommands[ 1500 ]; + char szRules[ 1500 ]; + int minplayers = 0, maxplayers = 0; + strcpy( szFirstMapInList, "hldm1" ); // the absolute default level is hldm1 + + int curplayers; + BOOL do_cycle = TRUE; + + // find the map to change to + char *mapcfile = (char*)CVAR_GET_STRING( "mapcyclefile" ); + ASSERT( mapcfile != NULL ); + + szCommands[ 0 ] = '\0'; + szRules[ 0 ] = '\0'; + + curplayers = CountPlayers(); + + // Has the map cycle filename changed? + if ( stricmp( mapcfile, szPreviousMapCycleFile ) ) + { + strcpy( szPreviousMapCycleFile, mapcfile ); + + DestroyMapCycle( &mapcycle ); + + if ( !ReloadMapCycleFile( mapcfile, &mapcycle ) || ( !mapcycle.items ) ) + { + ALERT( at_console, "Unable to load map cycle file %s\n", mapcfile ); + do_cycle = FALSE; + } + } + + if ( do_cycle && mapcycle.items ) + { + BOOL keeplooking = FALSE; + BOOL found = FALSE; + mapcycle_item_s *item; + + // Assume current map + strcpy( szNextMap, STRING(gpGlobals->mapname) ); + strcpy( szFirstMapInList, STRING(gpGlobals->mapname) ); + + // Traverse list + for ( item = mapcycle.next_item; item->next != mapcycle.next_item; item = item->next ) + { + keeplooking = FALSE; + + ASSERT( item != NULL ); + + if ( item->minplayers != 0 ) + { + if ( curplayers >= item->minplayers ) + { + found = TRUE; + minplayers = item->minplayers; + } + else + { + keeplooking = TRUE; + } + } + + if ( item->maxplayers != 0 ) + { + if ( curplayers <= item->maxplayers ) + { + found = TRUE; + maxplayers = item->maxplayers; + } + else + { + keeplooking = TRUE; + } + } + + if ( keeplooking ) + continue; + + found = TRUE; + break; + } + + if ( !found ) + { + item = mapcycle.next_item; + } + + // Increment next item pointer + mapcycle.next_item = item->next; + + // Perform logic on current item + strcpy( szNextMap, item->mapname ); + + ExtractCommandString( item->rulebuffer, szCommands ); + strcpy( szRules, item->rulebuffer ); + } + + if ( !IS_MAP_VALID(szNextMap) ) + { + strcpy( szNextMap, szFirstMapInList ); + } + + g_fGameOver = TRUE; + + ALERT( at_console, "CHANGE LEVEL: %s\n", szNextMap ); + if ( minplayers || maxplayers ) + { + ALERT( at_console, "PLAYER COUNT: min %i max %i current %i\n", minplayers, maxplayers, curplayers ); + } + if ( strlen( szRules ) > 0 ) + { + ALERT( at_console, "RULES: %s\n", szRules ); + } + + CHANGE_LEVEL( szNextMap, NULL ); + if ( strlen( szCommands ) > 0 ) + { + SERVER_COMMAND( szCommands ); + } +} + +#define MAX_MOTD_CHUNK 60 +#define MAX_MOTD_LENGTH (MAX_MOTD_CHUNK * 4) + +void CHalfLifeMultiplay :: SendMOTDToClient( edict_t *client ) +{ + // read from the MOTD.txt file + int length, char_count = 0; + char *pFileList; + char *aFileList = pFileList = (char*)LOAD_FILE_FOR_ME( "motd.txt", &length ); + + // send the server name + MESSAGE_BEGIN( MSG_ONE, gmsgServerName, NULL, client ); + WRITE_STRING( CVAR_GET_STRING("hostname") ); + MESSAGE_END(); + + // Send the message of the day + // read it chunk-by-chunk, and send it in parts + + while ( pFileList && *pFileList && char_count < MAX_MOTD_LENGTH ) + { + char chunk[MAX_MOTD_CHUNK+1]; + + if ( strlen( pFileList ) < MAX_MOTD_CHUNK ) + { + strcpy( chunk, pFileList ); + } + else + { + strncpy( chunk, pFileList, MAX_MOTD_CHUNK ); + chunk[MAX_MOTD_CHUNK] = 0; // strncpy doesn't always append the null terminator + } + + char_count += strlen( chunk ); + if ( char_count < MAX_MOTD_LENGTH ) + pFileList = aFileList + char_count; + else + *pFileList = 0; + + MESSAGE_BEGIN( MSG_ONE, gmsgMOTD, NULL, client ); + WRITE_BYTE( *pFileList ? FALSE : TRUE ); // FALSE means there is still more message to come + WRITE_STRING( chunk ); + MESSAGE_END(); + } + + FREE_FILE( aFileList ); +} + + diff --git a/dmc/dlls/nodes.cpp b/dmc/dlls/nodes.cpp new file mode 100644 index 0000000..b2bd199 --- /dev/null +++ b/dmc/dlls/nodes.cpp @@ -0,0 +1,3654 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// nodes.cpp - AI node tree stuff. +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "nodes.h" +#include "animation.h" +#include "doors.h" + +#ifndef _WIN32 +#include +#include +#include +#define CreateDirectory(p, n) mkdir(p, 0777) +#endif // _WIN32 + +#define HULL_STEP_SIZE 16// how far the test hull moves on each step +#define NODE_HEIGHT 8 // how high to lift nodes off the ground after we drop them all (make stair/ramp mapping easier) + +// to help eliminate node clutter by level designers, this is used to cap how many other nodes +// any given node is allowed to 'see' in the first stage of graph creation "LinkVisibleNodes()". +#define MAX_NODE_INITIAL_LINKS 128 +#define MAX_NODES 1024 + +extern DLL_GLOBAL edict_t *g_pBodyQueueHead; + +Vector VecBModelOrigin( entvars_t* pevBModel ); + +CGraph WorldGraph; + +LINK_ENTITY_TO_CLASS( info_node, CNodeEnt ); +LINK_ENTITY_TO_CLASS( info_node_air, CNodeEnt ); + +//========================================================= +// CGraph - InitGraph - prepares the graph for use. Frees any +// memory currently in use by the world graph, NULLs +// all pointers, and zeros the node count. +//========================================================= +void CGraph :: InitGraph( void) +{ + + // Make the graph unavailable + // + m_fGraphPresent = FALSE; + m_fGraphPointersSet = FALSE; + m_fRoutingComplete = FALSE; + + // Free the link pool + // + if ( m_pLinkPool ) + { + free ( m_pLinkPool ); + m_pLinkPool = NULL; + } + + // Free the node info + // + if ( m_pNodes ) + { + free ( m_pNodes ); + m_pNodes = NULL; + } + + if ( m_di ) + { + free ( m_di ); + m_di = NULL; + } + + // Free the routing info. + // + if ( m_pRouteInfo ) + { + free ( m_pRouteInfo ); + m_pRouteInfo = NULL; + } + + if (m_pHashLinks) + { + free(m_pHashLinks); + m_pHashLinks = NULL; + } + + // Zero node and link counts + // + m_cNodes = 0; + m_cLinks = 0; + m_nRouteInfo = 0; + + m_iLastActiveIdleSearch = 0; + m_iLastCoverSearch = 0; +} + +//========================================================= +// CGraph - AllocNodes - temporary function that mallocs a +// reasonable number of nodes so we can build the path which +// will be saved to disk. +//========================================================= +int CGraph :: AllocNodes ( void ) +{ +// malloc all of the nodes + WorldGraph.m_pNodes = (CNode *)calloc ( sizeof ( CNode ), MAX_NODES ); + +// could not malloc space for all the nodes! + if ( !WorldGraph.m_pNodes ) + { + ALERT ( at_aiconsole, "**ERROR**\nCouldn't malloc %d nodes!\n", WorldGraph.m_cNodes ); + return FALSE; + } + + return TRUE; +} + +//========================================================= +// CGraph - LinkEntForLink - sometimes the ent that blocks +// a path is a usable door, in which case the monster just +// needs to face the door and fire it. In other cases, the +// monster needs to operate a button or lever to get the +// door to open. This function will return a pointer to the +// button if the monster needs to hit a button to open the +// door, or returns a pointer to the door if the monster +// need only use the door. +// +// pNode is the node the monster will be standing on when it +// will need to stop and trigger the ent. +//========================================================= +entvars_t* CGraph :: LinkEntForLink ( CLink *pLink, CNode *pNode ) +{ + edict_t *pentSearch; + edict_t *pentTrigger; + entvars_t *pevTrigger; + entvars_t *pevLinkEnt; + TraceResult tr; + + pevLinkEnt = pLink->m_pLinkEnt; + if ( !pevLinkEnt ) + return NULL; + + pentSearch = NULL;// start search at the top of the ent list. + + if ( FClassnameIs ( pevLinkEnt, "func_door" ) || FClassnameIs ( pevLinkEnt, "func_door_rotating" ) ) + { + + ///!!!UNDONE - check for TOGGLE or STAY open doors here. If a door is in the way, and is + // TOGGLE or STAY OPEN, even monsters that can't open doors can go that way. + + if ( ( pevLinkEnt->spawnflags & SF_DOOR_USE_ONLY ) ) + {// door is use only, so the door is all the monster has to worry about + return pevLinkEnt; + } + + while ( 1 ) + { + pentTrigger = FIND_ENTITY_BY_TARGET ( pentSearch, STRING( pevLinkEnt->targetname ) );// find the button or trigger + + if ( FNullEnt( pentTrigger ) ) + {// no trigger found + + // right now this is a problem among auto-open doors, or any door that opens through the use + // of a trigger brush. Trigger brushes have no models, and don't show up in searches. Just allow + // monsters to open these sorts of doors for now. + return pevLinkEnt; + } + + pentSearch = pentTrigger; + pevTrigger = VARS( pentTrigger ); + + if ( FClassnameIs(pevTrigger, "func_button") || FClassnameIs(pevTrigger, "func_rot_button" ) ) + {// only buttons are handled right now. + + // trace from the node to the trigger, make sure it's one we can see from the node. + // !!!HACKHACK Use bodyqueue here cause there are no ents we really wish to ignore! + UTIL_TraceLine ( pNode->m_vecOrigin, VecBModelOrigin( pevTrigger ), ignore_monsters, g_pBodyQueueHead, &tr ); + + + if ( VARS(tr.pHit) == pevTrigger ) + {// good to go! + return VARS( tr.pHit ); + } + } + } + } + else + { + ALERT ( at_aiconsole, "Unsupported PathEnt:\n'%s'\n", STRING ( pevLinkEnt->classname ) ); + return NULL; + } +} + +//========================================================= +// CGraph - HandleLinkEnt - a brush ent is between two +// nodes that would otherwise be able to see each other. +// Given the monster's capability, determine whether +// or not the monster can go this way. +//========================================================= +int CGraph :: HandleLinkEnt ( int iNode, entvars_t *pevLinkEnt, int afCapMask, NODEQUERY queryType ) +{ + edict_t *pentWorld; + CBaseEntity *pDoor; + TraceResult tr; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return FALSE; + } + + if ( FNullEnt ( pevLinkEnt ) ) + { + ALERT ( at_aiconsole, "dead path ent!\n" ); + return TRUE; + } + pentWorld = NULL; + +// func_door + if ( FClassnameIs( pevLinkEnt, "func_door" ) || FClassnameIs( pevLinkEnt, "func_door_rotating" ) ) + {// ent is a door. + + pDoor = ( CBaseEntity::Instance( pevLinkEnt ) ); + + if ( ( pevLinkEnt->spawnflags & SF_DOOR_USE_ONLY ) ) + {// door is use only. + + if ( ( afCapMask & bits_CAP_OPEN_DOORS ) ) + {// let monster right through if he can open doors + return TRUE; + } + else + { + // monster should try for it if the door is open and looks as if it will stay that way + if ( pDoor->GetToggleState()== TS_AT_TOP && ( pevLinkEnt->spawnflags & SF_DOOR_NO_AUTO_RETURN ) ) + { + return TRUE; + } + + return FALSE; + } + } + else + {// door must be opened with a button or trigger field. + + // monster should try for it if the door is open and looks as if it will stay that way + if ( pDoor->GetToggleState() == TS_AT_TOP && ( pevLinkEnt->spawnflags & SF_DOOR_NO_AUTO_RETURN ) ) + { + return TRUE; + } + if ( ( afCapMask & bits_CAP_OPEN_DOORS ) ) + { + if ( !( pevLinkEnt->spawnflags & SF_DOOR_NOMONSTERS ) || queryType == NODEGRAPH_STATIC ) + return TRUE; + } + + return FALSE; + } + } +// func_breakable + else if ( FClassnameIs( pevLinkEnt, "func_breakable" ) && queryType == NODEGRAPH_STATIC ) + { + return TRUE; + } + else + { + ALERT ( at_aiconsole, "Unhandled Ent in Path %s\n", STRING( pevLinkEnt->classname ) ); + return FALSE; + } + + return FALSE; +} + +#if 0 +//========================================================= +// FindNearestLink - finds the connection (line) nearest +// the given point. Returns FALSE if fails, or TRUE if it +// has stuffed the index into the nearest link pool connection +// into the passed int pointer, and a BOOL telling whether or +// not the point is along the line into the passed BOOL pointer. +//========================================================= +int CGraph :: FindNearestLink ( const Vector &vecTestPoint, int *piNearestLink, BOOL *pfAlongLine ) +{ + int i, j;// loops + + int iNearestLink;// index into the link pool, this is the nearest node at any time. + float flMinDist;// the distance of of the nearest case so far + float flDistToLine;// the distance of the current test case + + BOOL fCurrentAlongLine; + BOOL fSuccess; + + //float flConstant;// line constant + Vector vecSpot1, vecSpot2; + Vector2D vec2Spot1, vec2Spot2, vec2TestPoint; + Vector2D vec2Normal;// line normal + Vector2D vec2Line; + + TraceResult tr; + + iNearestLink = -1;// prepare for failure + fSuccess = FALSE; + + flMinDist = 9999;// anything will be closer than this + +// go through all of the nodes, and each node's connections + int cSkip = 0;// how many links proper pairing allowed us to skip + int cChecked = 0;// how many links were checked + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + vecSpot1 = m_pNodes[ i ].m_vecOrigin; + + if ( m_pNodes[ i ].m_cNumLinks <= 0 ) + {// this shouldn't happen! + ALERT ( at_aiconsole, "**Node %d has no links\n", i ); + continue; + } + + for ( j = 0 ; j < m_pNodes[ i ].m_cNumLinks ; j++ ) + { + /* + !!!This optimization only works when the node graph consists of properly linked pairs. + if ( INodeLink ( i, j ) <= i ) + { + // since we're going through the nodes in order, don't check + // any connections whose second node is lower in the list + // than the node we're currently working with. This eliminates + // redundant checks. + cSkip++; + continue; + } + */ + + vecSpot2 = PNodeLink ( i, j )->m_vecOrigin; + + // these values need a little attention now and then, or sometimes ramps cause trouble. + if ( fabs ( vecSpot1.z - vecTestPoint.z ) > 48 && fabs ( vecSpot2.z - vecTestPoint.z ) > 48 ) + { + // if both endpoints of the line are 32 units or more above or below the monster, + // the monster won't be able to get to them, so we do a bit of trivial rejection here. + // this may change if monsters are allowed to jump down. + // + // !!!LATER: some kind of clever X/Y hashing should be used here, too + continue; + } + +// now we have two endpoints for a line segment that we've not already checked. +// since all lines that make it this far are within -/+ 32 units of the test point's +// Z Plane, we can get away with doing the point->line check in 2d. + + cChecked++; + + vec2Spot1 = vecSpot1.Make2D(); + vec2Spot2 = vecSpot2.Make2D(); + vec2TestPoint = vecTestPoint.Make2D(); + + // get the line normal. + vec2Line = ( vec2Spot1 - vec2Spot2 ).Normalize(); + vec2Normal.x = -vec2Line.y; + vec2Normal.y = vec2Line.x; + + if ( DotProduct ( vec2Line, ( vec2TestPoint - vec2Spot1 ) ) > 0 ) + {// point outside of line + flDistToLine = ( vec2TestPoint - vec2Spot1 ).Length(); + fCurrentAlongLine = FALSE; + } + else if ( DotProduct ( vec2Line, ( vec2TestPoint - vec2Spot2 ) ) < 0 ) + {// point outside of line + flDistToLine = ( vec2TestPoint - vec2Spot2 ).Length(); + fCurrentAlongLine = FALSE; + } + else + {// point inside line + flDistToLine = fabs( DotProduct ( vec2TestPoint - vec2Spot2, vec2Normal ) ); + fCurrentAlongLine = TRUE; + } + + if ( flDistToLine < flMinDist ) + {// just found a line nearer than any other so far + + UTIL_TraceLine ( vecTestPoint, SourceNode( i, j ).m_vecOrigin, ignore_monsters, g_pBodyQueueHead, &tr ); + + if ( tr.flFraction != 1.0 ) + {// crap. can't see the first node of this link, try to see the other + + UTIL_TraceLine ( vecTestPoint, DestNode( i, j ).m_vecOrigin, ignore_monsters, g_pBodyQueueHead, &tr ); + if ( tr.flFraction != 1.0 ) + {// can't use this link, cause can't see either node! + continue; + } + + } + + fSuccess = TRUE;// we know there will be something to return. + flMinDist = flDistToLine; + iNearestLink = m_pNodes [ i ].m_iFirstLink + j; + *piNearestLink = m_pNodes[ i ].m_iFirstLink + j; + *pfAlongLine = fCurrentAlongLine; + } + } + } + +/* + if ( fSuccess ) + { + WRITE_BYTE(MSG_BROADCAST, SVC_TEMPENTITY); + WRITE_BYTE(MSG_BROADCAST, TE_SHOWLINE); + + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iSrcNode ].m_vecOrigin.x ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iSrcNode ].m_vecOrigin.y ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iSrcNode ].m_vecOrigin.z + NODE_HEIGHT); + + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iDestNode ].m_vecOrigin.x ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iDestNode ].m_vecOrigin.y ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iDestNode ].m_vecOrigin.z + NODE_HEIGHT); + } +*/ + + ALERT ( at_aiconsole, "%d Checked\n", cChecked ); + return fSuccess; +} + +#endif + +int CGraph::HullIndex( const CBaseEntity *pEntity ) +{ + if ( pEntity->pev->movetype == MOVETYPE_FLY) + return NODE_FLY_HULL; + + if ( pEntity->pev->mins == Vector( -12, -12, 0 ) ) + return NODE_SMALL_HULL; + else if ( pEntity->pev->mins == VEC_HUMAN_HULL_MIN ) + return NODE_HUMAN_HULL; + else if ( pEntity->pev->mins == Vector ( -32, -32, 0 ) ) + return NODE_LARGE_HULL; + +// ALERT ( at_aiconsole, "Unknown Hull Mins!\n" ); + return NODE_HUMAN_HULL; +} + + +int CGraph::NodeType( const CBaseEntity *pEntity ) +{ + if ( pEntity->pev->movetype == MOVETYPE_FLY) + { + if (pEntity->pev->waterlevel != 0) + { + return bits_NODE_WATER; + } + else + { + return bits_NODE_AIR; + } + } + return bits_NODE_LAND; +} + + +// Sum up graph weights on the path from iStart to iDest to determine path length +float CGraph::PathLength( int iStart, int iDest, int iHull, int afCapMask ) +{ + float distance = 0; + int iNext; + + int iMaxLoop = m_cNodes; + + int iCurrentNode = iStart; + int iCap = CapIndex( afCapMask ); + + while (iCurrentNode != iDest) + { + if (iMaxLoop-- <= 0) + { + ALERT( at_console, "Route Failure\n" ); + return 0; + } + + iNext = NextNodeInRoute( iCurrentNode, iDest, iHull, iCap ); + if (iCurrentNode == iNext) + { + //ALERT(at_aiconsole, "SVD: Can't get there from here..\n"); + return 0; + } + + int iLink; + HashSearch(iCurrentNode, iNext, iLink); + if (iLink < 0) + { + ALERT(at_console, "HashLinks is broken from %d to %d.\n", iCurrentNode, iDest); + return 0; + } + CLink &link = Link(iLink); + distance += link.m_flWeight; + + iCurrentNode = iNext; + } + + return distance; +} + + +// Parse the routing table at iCurrentNode for the next node on the shortest path to iDest +int CGraph::NextNodeInRoute( int iCurrentNode, int iDest, int iHull, int iCap ) +{ + int iNext = iCurrentNode; + int nCount = iDest+1; + char *pRoute = m_pRouteInfo + m_pNodes[ iCurrentNode ].m_pNextBestNode[iHull][iCap]; + + // Until we decode the next best node + // + while (nCount > 0) + { + char ch = *pRoute++; + //ALERT(at_aiconsole, "C(%d)", ch); + if (ch < 0) + { + // Sequence phrase + // + ch = -ch; + if (nCount <= ch) + { + iNext = iDest; + nCount = 0; + //ALERT(at_aiconsole, "SEQ: iNext/iDest=%d\n", iNext); + } + else + { + //ALERT(at_aiconsole, "SEQ: nCount + ch (%d + %d)\n", nCount, ch); + nCount = nCount - ch; + } + } + else + { + //ALERT(at_aiconsole, "C(%d)", *pRoute); + + // Repeat phrase + // + if (nCount <= ch+1) + { + iNext = iCurrentNode + *pRoute; + if (iNext >= m_cNodes) iNext -= m_cNodes; + else if (iNext < 0) iNext += m_cNodes; + nCount = 0; + //ALERT(at_aiconsole, "REP: iNext=%d\n", iNext); + } + else + { + //ALERT(at_aiconsole, "REP: nCount - ch+1 (%d - %d+1)\n", nCount, ch); + nCount = nCount - ch - 1; + } + pRoute++; + } + } + + return iNext; +} + + +//========================================================= +// CGraph - FindShortestPath +// +// accepts a capability mask (afCapMask), and will only +// find a path usable by a monster with those capabilities +// returns the number of nodes copied into supplied array +//========================================================= +int CGraph :: FindShortestPath ( int *piPath, int iStart, int iDest, int iHull, int afCapMask) +{ + int iVisitNode; + int iCurrentNode; + int iNumPathNodes; + int iHullMask; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available or built + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return FALSE; + } + + if ( iStart < 0 || iStart > m_cNodes ) + {// The start node is bad? + ALERT ( at_aiconsole, "Can't build a path, iStart is %d!\n", iStart ); + return FALSE; + } + + if (iStart == iDest) + { + piPath[0] = iStart; + piPath[1] = iDest; + return 2; + } + + // Is routing information present. + // + if (m_fRoutingComplete) + { + int iCap = CapIndex( afCapMask ); + + iNumPathNodes = 0; + piPath[iNumPathNodes++] = iStart; + iCurrentNode = iStart; + int iNext; + + //ALERT(at_aiconsole, "GOAL: %d to %d\n", iStart, iDest); + + // Until we arrive at the destination + // + while (iCurrentNode != iDest) + { + iNext = NextNodeInRoute( iCurrentNode, iDest, iHull, iCap ); + if (iCurrentNode == iNext) + { + //ALERT(at_aiconsole, "SVD: Can't get there from here..\n"); + return 0; + break; + } + if (iNumPathNodes >= MAX_PATH_SIZE) + { + //ALERT(at_aiconsole, "SVD: Don't return the entire path.\n"); + break; + } + piPath[iNumPathNodes++] = iNext; + iCurrentNode = iNext; + } + //ALERT( at_aiconsole, "SVD: Path with %d nodes.\n", iNumPathNodes); + } + else + { + CQueuePriority queue; + + switch( iHull ) + { + case NODE_SMALL_HULL: + iHullMask = bits_LINK_SMALL_HULL; + break; + case NODE_HUMAN_HULL: + iHullMask = bits_LINK_HUMAN_HULL; + break; + case NODE_LARGE_HULL: + iHullMask = bits_LINK_LARGE_HULL; + break; + case NODE_FLY_HULL: + iHullMask = bits_LINK_FLY_HULL; + break; + } + + // Mark all the nodes as unvisited. + // + int i; + for ( i = 0; i < m_cNodes; i++) + { + m_pNodes[ i ].m_flClosestSoFar = -1.0; + } + + m_pNodes[ iStart ].m_flClosestSoFar = 0.0; + m_pNodes[ iStart ].m_iPreviousNode = iStart;// tag this as the origin node + queue.Insert( iStart, 0.0 );// insert start node + + while ( !queue.Empty() ) + { + // now pull a node out of the queue + float flCurrentDistance; + iCurrentNode = queue.Remove(flCurrentDistance); + + // For straight-line weights, the following Shortcut works. For arbitrary weights, + // it doesn't. + // + if (iCurrentNode == iDest) break; + + CNode *pCurrentNode = &m_pNodes[ iCurrentNode ]; + + for ( i = 0 ; i < pCurrentNode->m_cNumLinks ; i++ ) + {// run through all of this node's neighbors + + iVisitNode = INodeLink ( iCurrentNode, i ); + if ( ( m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_afLinkInfo & iHullMask ) != iHullMask ) + {// monster is too large to walk this connection + //ALERT ( at_aiconsole, "fat ass %d/%d\n",m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_afLinkInfo, iMonsterHull ); + continue; + } + // check the connection from the current node to the node we're about to mark visited and push into the queue + if ( m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_pLinkEnt != NULL ) + {// there's a brush ent in the way! Don't mark this node or put it into the queue unless the monster can negotiate it + + if ( !HandleLinkEnt ( iCurrentNode, m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_pLinkEnt, afCapMask, NODEGRAPH_STATIC ) ) + {// monster should not try to go this way. + continue; + } + } + float flOurDistance = flCurrentDistance + m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i].m_flWeight; + if ( m_pNodes[ iVisitNode ].m_flClosestSoFar < -0.5 + || flOurDistance < m_pNodes[ iVisitNode ].m_flClosestSoFar - 0.001 ) + { + m_pNodes[iVisitNode].m_flClosestSoFar = flOurDistance; + m_pNodes[iVisitNode].m_iPreviousNode = iCurrentNode; + + queue.Insert ( iVisitNode, flOurDistance ); + } + } + } + if ( m_pNodes[iDest].m_flClosestSoFar < -0.5 ) + {// Destination is unreachable, no path found. + return 0; + } + + // the queue is not empty + + // now we must walk backwards through the m_iPreviousNode field, and count how many connections there are in the path + iCurrentNode = iDest; + iNumPathNodes = 1;// count the dest + + while ( iCurrentNode != iStart ) + { + iNumPathNodes++; + iCurrentNode = m_pNodes[ iCurrentNode ].m_iPreviousNode; + } + + iCurrentNode = iDest; + for ( i = iNumPathNodes - 1 ; i >= 0 ; i-- ) + { + piPath[ i ] = iCurrentNode; + iCurrentNode = m_pNodes [ iCurrentNode ].m_iPreviousNode; + } + } + +#if 0 + + if (m_fRoutingComplete) + { + // This will draw the entire path that was generated for the monster. + + for ( int i = 0 ; i < iNumPathNodes - 1 ; i++ ) + { + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( m_pNodes[ piPath[ i ] ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ piPath[ i ] ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ piPath[ i ] ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( m_pNodes[ piPath[ i + 1 ] ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ piPath[ i + 1 ] ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ piPath[ i + 1 ] ].m_vecOrigin.z + NODE_HEIGHT ); + MESSAGE_END(); + } + } + +#endif +#if 0 // MAZE map + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( m_pNodes[ 4 ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ 4 ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ 4 ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( m_pNodes[ 9 ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ 9 ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ 9 ].m_vecOrigin.z + NODE_HEIGHT ); + MESSAGE_END(); +#endif + + return iNumPathNodes; +} + +inline ULONG Hash(void *p, int len) +{ + CRC32_t ulCrc; + CRC32_INIT(&ulCrc); + CRC32_PROCESS_BUFFER(&ulCrc, p, len); + return CRC32_FINAL(ulCrc); +} + +void inline CalcBounds(int &Lower, int &Upper, int Goal, int Best) +{ + int Temp = 2*Goal - Best; + if (Best > Goal) + { + Lower = max(0, Temp); + Upper = Best; + } + else + { + Upper = min(255, Temp); + Lower = Best; + } +} + +// Convert from [-8192,8192] to [0, 255] +// +inline int CALC_RANGE(int x, int lower, int upper) +{ + return NUM_RANGES*(x-lower)/((upper-lower+1)); +} + + +void inline UpdateRange(int &minValue, int &maxValue, int Goal, int Best) +{ + int Lower, Upper; + CalcBounds(Lower, Upper, Goal, Best); + if (Upper < maxValue) maxValue = Upper; + if (minValue < Lower) minValue = Lower; +} + +void CGraph :: CheckNode(Vector vecOrigin, int iNode) +{ + // Have we already seen this point before?. + // + if (m_di[iNode].m_CheckedEvent == m_CheckedCounter) return; + m_di[iNode].m_CheckedEvent = m_CheckedCounter; + + float flDist = ( vecOrigin - m_pNodes[ iNode ].m_vecOriginPeek ).Length(); + + if ( flDist < m_flShortest ) + { + TraceResult tr; + + // make sure that vecOrigin can trace to this node! + UTIL_TraceLine ( vecOrigin, m_pNodes[ iNode ].m_vecOriginPeek, ignore_monsters, 0, &tr ); + + if ( tr.flFraction == 1.0 ) + { + m_iNearest = iNode; + m_flShortest = flDist; + + UpdateRange(m_minX, m_maxX, CALC_RANGE(vecOrigin.x, m_RegionMin[0], m_RegionMax[0]), m_pNodes[iNode].m_Region[0]); + UpdateRange(m_minY, m_maxY, CALC_RANGE(vecOrigin.y, m_RegionMin[1], m_RegionMax[1]), m_pNodes[iNode].m_Region[1]); + UpdateRange(m_minZ, m_maxZ, CALC_RANGE(vecOrigin.z, m_RegionMin[2], m_RegionMax[2]), m_pNodes[iNode].m_Region[2]); + + // From maxCircle, calculate maximum bounds box. All points must be + // simultaneously inside all bounds of the box. + // + m_minBoxX = CALC_RANGE(vecOrigin.x - flDist, m_RegionMin[0], m_RegionMax[0]); + m_maxBoxX = CALC_RANGE(vecOrigin.x + flDist, m_RegionMin[0], m_RegionMax[0]); + m_minBoxY = CALC_RANGE(vecOrigin.y - flDist, m_RegionMin[1], m_RegionMax[1]); + m_maxBoxY = CALC_RANGE(vecOrigin.y + flDist, m_RegionMin[1], m_RegionMax[1]); + m_minBoxZ = CALC_RANGE(vecOrigin.z - flDist, m_RegionMin[2], m_RegionMax[2]); + m_maxBoxZ = CALC_RANGE(vecOrigin.z + flDist, m_RegionMin[2], m_RegionMax[2]); + } + } +} + +//========================================================= +// CGraph - FindNearestNode - returns the index of the node nearest +// the given vector -1 is failure (couldn't find a valid +// near node ) +//========================================================= +int CGraph :: FindNearestNode ( const Vector &vecOrigin, CBaseEntity *pEntity ) +{ + return FindNearestNode( vecOrigin, NodeType( pEntity ) ); +} + +int CGraph :: FindNearestNode ( const Vector &vecOrigin, int afNodeTypes ) +{ + int i; + TraceResult tr; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return -1; + } + + // Check with the cache + // + ULONG iHash = (CACHE_SIZE-1) & Hash((void *)(const float *)vecOrigin, sizeof(vecOrigin)); + if (m_Cache[iHash].v == vecOrigin) + { + //ALERT(at_aiconsole, "Cache Hit.\n"); + return m_Cache[iHash].n; + } + else + { + //ALERT(at_aiconsole, "Cache Miss.\n"); + } + + // Mark all points as unchecked. + // + m_CheckedCounter++; + if (m_CheckedCounter == 0) + { + for (int i = 0; i < m_cNodes; i++) + { + m_di[i].m_CheckedEvent = 0; + } + m_CheckedCounter++; + } + + m_iNearest = -1; + m_flShortest = 999999.0; // just a big number. + + // If we can find a visible point, then let CalcBounds set the limits, but if + // we have no visible point at all to start with, then don't restrict the limits. + // +#if 1 + m_minX = 0; m_maxX = 255; + m_minY = 0; m_maxY = 255; + m_minZ = 0; m_maxZ = 255; + m_minBoxX = 0; m_maxBoxX = 255; + m_minBoxY = 0; m_maxBoxY = 255; + m_minBoxZ = 0; m_maxBoxZ = 255; +#else + m_minBoxX = CALC_RANGE(vecOrigin.x - flDist, m_RegionMin[0], m_RegionMax[0]); + m_maxBoxX = CALC_RANGE(vecOrigin.x + flDist, m_RegionMin[0], m_RegionMax[0]); + m_minBoxY = CALC_RANGE(vecOrigin.y - flDist, m_RegionMin[1], m_RegionMax[1]); + m_maxBoxY = CALC_RANGE(vecOrigin.y + flDist, m_RegionMin[1], m_RegionMax[1]); + m_minBoxZ = CALC_RANGE(vecOrigin.z - flDist, m_RegionMin[2], m_RegionMax[2]); + m_maxBoxZ = CALC_RANGE(vecOrigin.z + flDist, m_RegionMin[2], m_RegionMax[2]) + CalcBounds(m_minX, m_maxX, CALC_RANGE(vecOrigin.x, m_RegionMin[0], m_RegionMax[0]), m_pNodes[m_iNearest].m_Region[0]); + CalcBounds(m_minY, m_maxY, CALC_RANGE(vecOrigin.y, m_RegionMin[1], m_RegionMax[1]), m_pNodes[m_iNearest].m_Region[1]); + CalcBounds(m_minZ, m_maxZ, CALC_RANGE(vecOrigin.z, m_RegionMin[2], m_RegionMax[2]), m_pNodes[m_iNearest].m_Region[2]); +#endif + + int halfX = (m_minX+m_maxX)/2; + int halfY = (m_minY+m_maxY)/2; + int halfZ = (m_minZ+m_maxZ)/2; + + int j; + + for (i = halfX; i >= m_minX; i--) + { + for (j = m_RangeStart[0][i]; j <= m_RangeEnd[0][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[0]].m_afNodeInfo & afNodeTypes)) continue; + + int rgY = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[1]; + if (rgY > m_maxBoxY) break; + if (rgY < m_minBoxY) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[2]; + if (rgZ < m_minBoxZ) continue; + if (rgZ > m_maxBoxZ) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[0]); + } + } + + for (i = max(m_minY,halfY+1); i <= m_maxY; i++) + { + for (j = m_RangeStart[1][i]; j <= m_RangeEnd[1][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[1]].m_afNodeInfo & afNodeTypes)) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[2]; + if (rgZ > m_maxBoxZ) break; + if (rgZ < m_minBoxZ) continue; + int rgX = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[0]; + if (rgX < m_minBoxX) continue; + if (rgX > m_maxBoxX) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[1]); + } + } + + for (i = min(m_maxZ,halfZ); i >= m_minZ; i--) + { + for (j = m_RangeStart[2][i]; j <= m_RangeEnd[2][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[2]].m_afNodeInfo & afNodeTypes)) continue; + + int rgX = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[0]; + if (rgX > m_maxBoxX) break; + if (rgX < m_minBoxX) continue; + int rgY = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[1]; + if (rgY < m_minBoxY) continue; + if (rgY > m_maxBoxY) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[2]); + } + } + + for (i = max(m_minX,halfX+1); i <= m_maxX; i++) + { + for (j = m_RangeStart[0][i]; j <= m_RangeEnd[0][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[0]].m_afNodeInfo & afNodeTypes)) continue; + + int rgY = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[1]; + if (rgY > m_maxBoxY) break; + if (rgY < m_minBoxY) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[2]; + if (rgZ < m_minBoxZ) continue; + if (rgZ > m_maxBoxZ) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[0]); + } + } + + for (i = min(m_maxY,halfY); i >= m_minY; i--) + { + for (j = m_RangeStart[1][i]; j <= m_RangeEnd[1][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[1]].m_afNodeInfo & afNodeTypes)) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[2]; + if (rgZ > m_maxBoxZ) break; + if (rgZ < m_minBoxZ) continue; + int rgX = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[0]; + if (rgX < m_minBoxX) continue; + if (rgX > m_maxBoxX) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[1]); + } + } + + for (i = max(m_minZ,halfZ+1); i <= m_maxZ; i++) + { + for (j = m_RangeStart[2][i]; j <= m_RangeEnd[2][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[2]].m_afNodeInfo & afNodeTypes)) continue; + + int rgX = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[0]; + if (rgX > m_maxBoxX) break; + if (rgX < m_minBoxX) continue; + int rgY = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[1]; + if (rgY < m_minBoxY) continue; + if (rgY > m_maxBoxY) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[2]); + } + } + +#if 0 + // Verify our answers. + // + int iNearestCheck = -1; + m_flShortest = 8192;// find nodes within this radius + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + float flDist = ( vecOrigin - m_pNodes[ i ].m_vecOriginPeek ).Length(); + + if ( flDist < m_flShortest ) + { + // make sure that vecOrigin can trace to this node! + UTIL_TraceLine ( vecOrigin, m_pNodes[ i ].m_vecOriginPeek, ignore_monsters, 0, &tr ); + + if ( tr.flFraction == 1.0 ) + { + iNearestCheck = i; + m_flShortest = flDist; + } + } + } + + if (iNearestCheck != m_iNearest) + { + ALERT( at_aiconsole, "NOT closest %d(%f,%f,%f) %d(%f,%f,%f).\n", + iNearestCheck, + m_pNodes[iNearestCheck].m_vecOriginPeek.x, + m_pNodes[iNearestCheck].m_vecOriginPeek.y, + m_pNodes[iNearestCheck].m_vecOriginPeek.z, + m_iNearest, + (m_iNearest == -1?0.0:m_pNodes[m_iNearest].m_vecOriginPeek.x), + (m_iNearest == -1?0.0:m_pNodes[m_iNearest].m_vecOriginPeek.y), + (m_iNearest == -1?0.0:m_pNodes[m_iNearest].m_vecOriginPeek.z)); + } + if (m_iNearest == -1) + { + ALERT(at_aiconsole, "All that work for nothing.\n"); + } +#endif + m_Cache[iHash].v = vecOrigin; + m_Cache[iHash].n = m_iNearest; + return m_iNearest; +} + +//========================================================= +// CGraph - ShowNodeConnections - draws a line from the given node +// to all connected nodes +//========================================================= +void CGraph :: ShowNodeConnections ( int iNode ) +{ + Vector vecSpot; + CNode *pNode; + CNode *pLinkNode; + int i; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available or built + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return; + } + + if ( iNode < 0 ) + { + ALERT( at_aiconsole, "Can't show connections for node %d\n", iNode ); + return; + } + + pNode = &m_pNodes[ iNode ]; + + UTIL_ParticleEffect( pNode->m_vecOrigin, g_vecZero, 255, 20 );// show node position + + if ( pNode->m_cNumLinks <= 0 ) + {// no connections! + ALERT ( at_aiconsole, "**No Connections!\n" ); + } + + for ( i = 0 ; i < pNode->m_cNumLinks ; i++ ) + { + + pLinkNode = &Node( NodeLink( iNode, i).m_iDestNode ); + vecSpot = pLinkNode->m_vecOrigin; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( m_pNodes[ iNode ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ iNode ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ iNode ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + NODE_HEIGHT ); + MESSAGE_END(); + + } +} + +//========================================================= +// CGraph - LinkVisibleNodes - the first, most basic +// function of node graph creation, this connects every +// node to every other node that it can see. Expects a +// pointer to an empty connection pool and a file pointer +// to write progress to. Returns the total number of initial +// links. +// +// If there's a problem with this process, the index +// of the offending node will be written to piBadNode +//========================================================= +int CGraph :: LinkVisibleNodes ( CLink *pLinkPool, FILE *file, int *piBadNode ) +{ + int i,j,z; + edict_t *pTraceEnt; + int cTotalLinks, cLinksThisNode, cMaxInitialLinks; + TraceResult tr; + + // !!!BUGBUG - this function returns 0 if there is a problem in the middle of connecting the graph + // it also returns 0 if none of the nodes in a level can see each other. piBadNode is ALWAYS read + // by BuildNodeGraph() if this function returns a 0, so make sure that it doesn't get some random + // number back. + *piBadNode = 0; + + + if ( m_cNodes <= 0 ) + { + ALERT ( at_aiconsole, "No Nodes!\n" ); + return FALSE; + } + + // if the file pointer is bad, don't blow up, just don't write the + // file. + if ( !file ) + { + ALERT ( at_aiconsole, "**LinkVisibleNodes:\ncan't write to file." ); + } + else + { + fprintf ( file, "----------------------------------------------------------------------------\n" ); + fprintf ( file, "LinkVisibleNodes - Initial Connections\n" ); + fprintf ( file, "----------------------------------------------------------------------------\n" ); + } + + cTotalLinks = 0;// start with no connections + + // to keep track of the maximum number of initial links any node had so far. + // this lets us keep an eye on MAX_NODE_INITIAL_LINKS to ensure that we are + // being generous enough. + cMaxInitialLinks = 0; + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + cLinksThisNode = 0;// reset this count for each node. + + if ( file ) + { + fprintf ( file, "Node #%4d:\n\n", i ); + } + + for ( z = 0 ; z < MAX_NODE_INITIAL_LINKS ; z++ ) + {// clear out the important fields in the link pool for this node + pLinkPool [ cTotalLinks + z ].m_iSrcNode = i;// so each link knows which node it originates from + pLinkPool [ cTotalLinks + z ].m_iDestNode = 0; + pLinkPool [ cTotalLinks + z ].m_pLinkEnt = NULL; + } + + m_pNodes [ i ].m_iFirstLink = cTotalLinks; + + // now build a list of every other node that this node can see + for ( j = 0 ; j < m_cNodes ; j++ ) + { + if ( j == i ) + {// don't connect to self! + continue; + } + +#if 0 + + if ( (m_pNodes[ i ].m_afNodeInfo & bits_NODE_WATER) != (m_pNodes[ j ].m_afNodeInfo & bits_NODE_WATER) ) + { + // don't connect water nodes to air nodes or land nodes. It just wouldn't be prudent at this juncture. + continue; + } +#else + if ( (m_pNodes[ i ].m_afNodeInfo & bits_NODE_GROUP_REALM) != (m_pNodes[ j ].m_afNodeInfo & bits_NODE_GROUP_REALM) ) + { + // don't connect air nodes to water nodes to land nodes. It just wouldn't be prudent at this juncture. + continue; + } +#endif + + tr.pHit = NULL;// clear every time so we don't get stuck with last trace's hit ent + pTraceEnt = 0; + + UTIL_TraceLine ( m_pNodes[ i ].m_vecOrigin, + m_pNodes[ j ].m_vecOrigin, + ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &tr ); + + + if ( tr.fStartSolid ) + continue; + + if ( tr.flFraction != 1.0 ) + {// trace hit a brush ent, trace backwards to make sure that this ent is the only thing in the way. + + pTraceEnt = tr.pHit;// store the ent that the trace hit, for comparison + + UTIL_TraceLine ( m_pNodes[ j ].m_vecOrigin, + m_pNodes[ i ].m_vecOrigin, + ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &tr ); + + +// there is a solid_bsp ent in the way of these two nodes, so we must record several things about in order to keep +// track of it in the pathfinding code, as well as through save and restore of the node graph. ANY data that is manipulated +// as part of the process of adding a LINKENT to a connection here must also be done in CGraph::SetGraphPointers, where reloaded +// graphs are prepared for use. + if ( tr.pHit == pTraceEnt && !FClassnameIs( tr.pHit, "worldspawn" ) ) + { + // get a pointer + pLinkPool [ cTotalLinks ].m_pLinkEnt = VARS( tr.pHit ); + + // record the modelname, so that we can save/load node trees + memcpy( pLinkPool [ cTotalLinks ].m_szLinkEntModelname, STRING( VARS(tr.pHit)->model ), 4 ); + + // set the flag for this ent that indicates that it is attached to the world graph + // if this ent is removed from the world, it must also be removed from the connections + // that it formerly blocked. + if ( !FBitSet( VARS( tr.pHit )->flags, FL_GRAPHED ) ) + { + VARS( tr.pHit )->flags += FL_GRAPHED; + } + } + else + {// even if the ent wasn't there, these nodes couldn't be connected. Skip. + continue; + } + } + + if ( file ) + { + fprintf ( file, "%4d", j ); + + if ( !FNullEnt( pLinkPool[ cTotalLinks ].m_pLinkEnt ) ) + {// record info about the ent in the way, if any. + fprintf ( file, " Entity on connection: %s, name: %s Model: %s", STRING( VARS( pTraceEnt )->classname ), STRING ( VARS( pTraceEnt )->targetname ), STRING ( VARS(tr.pHit)->model ) ); + } + + fprintf ( file, "\n", j ); + } + + pLinkPool [ cTotalLinks ].m_iDestNode = j; + cLinksThisNode++; + cTotalLinks++; + + // If we hit this, either a level designer is placing too many nodes in the same area, or + // we need to allow for a larger initial link pool. + if ( cLinksThisNode == MAX_NODE_INITIAL_LINKS ) + { + ALERT ( at_aiconsole, "**LinkVisibleNodes:\nNode %d has NodeLinks > MAX_NODE_INITIAL_LINKS", i ); + fprintf ( file, "** NODE %d HAS NodeLinks > MAX_NODE_INITIAL_LINKS **\n", i ); + *piBadNode = i; + return FALSE; + } + else if ( cTotalLinks > MAX_NODE_INITIAL_LINKS * m_cNodes ) + {// this is paranoia + ALERT ( at_aiconsole, "**LinkVisibleNodes:\nTotalLinks > MAX_NODE_INITIAL_LINKS * NUMNODES" ); + *piBadNode = i; + return FALSE; + } + + if ( cLinksThisNode == 0 ) + { + fprintf ( file, "**NO INITIAL LINKS**\n" ); + } + + // record the connection info in the link pool + WorldGraph.m_pNodes [ i ].m_cNumLinks = cLinksThisNode; + + // keep track of the most initial links ANY node had, so we can figure out + // if we have a large enough default link pool + if ( cLinksThisNode > cMaxInitialLinks ) + { + cMaxInitialLinks = cLinksThisNode; + } + } + + + if ( file ) + { + fprintf ( file, "----------------------------------------------------------------------------\n" ); + } + } + + fprintf ( file, "\n%4d Total Initial Connections - %4d Maximum connections for a single node.\n", cTotalLinks, cMaxInitialLinks ); + fprintf ( file, "----------------------------------------------------------------------------\n\n\n" ); + + return cTotalLinks; +} + +//========================================================= +// CGraph - RejectInlineLinks - expects a pointer to a link +// pool, and a pointer to and already-open file ( if you +// want status reports written to disk ). RETURNS the number +// of connections that were rejected +//========================================================= +int CGraph :: RejectInlineLinks ( CLink *pLinkPool, FILE *file ) +{ + int i,j,k; + + int cRejectedLinks; + + BOOL fRestartLoop;// have to restart the J loop if we eliminate a link. + + CNode *pSrcNode; + CNode *pCheckNode;// the node we are testing for (one of pSrcNode's connections) + CNode *pTestNode;// the node we are checking against ( also one of pSrcNode's connections) + + float flDistToTestNode, flDistToCheckNode; + + Vector2D vec2DirToTestNode, vec2DirToCheckNode; + + if ( file ) + { + fprintf ( file, "----------------------------------------------------------------------------\n" ); + fprintf ( file, "InLine Rejection:\n" ); + fprintf ( file, "----------------------------------------------------------------------------\n" ); + } + + cRejectedLinks = 0; + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + pSrcNode = &m_pNodes[ i ]; + + if ( file ) + { + fprintf ( file, "Node %3d:\n", i ); + } + + for ( j = 0 ; j < pSrcNode->m_cNumLinks ; j++ ) + { + pCheckNode = &m_pNodes[ pLinkPool[ pSrcNode->m_iFirstLink + j ].m_iDestNode ]; + + vec2DirToCheckNode = ( pCheckNode->m_vecOrigin - pSrcNode->m_vecOrigin ).Make2D(); + flDistToCheckNode = vec2DirToCheckNode.Length(); + vec2DirToCheckNode = vec2DirToCheckNode.Normalize(); + + pLinkPool[ pSrcNode->m_iFirstLink + j ].m_flWeight = flDistToCheckNode; + + fRestartLoop = FALSE; + for ( k = 0 ; k < pSrcNode->m_cNumLinks && !fRestartLoop ; k++ ) + { + if ( k == j ) + {// don't check against same node + continue; + } + + pTestNode = &m_pNodes [ pLinkPool[ pSrcNode->m_iFirstLink + k ].m_iDestNode ]; + + vec2DirToTestNode = ( pTestNode->m_vecOrigin - pSrcNode->m_vecOrigin ).Make2D(); + + flDistToTestNode = vec2DirToTestNode.Length(); + vec2DirToTestNode = vec2DirToTestNode.Normalize(); + + if ( DotProduct ( vec2DirToCheckNode, vec2DirToTestNode ) >= 0.998 ) + { + // there's a chance that TestNode intersects the line to CheckNode. If so, we should disconnect the link to CheckNode. + if ( flDistToTestNode < flDistToCheckNode ) + { + if ( file ) + { + fprintf ( file, "REJECTED NODE %3d through Node %3d, Dot = %8f\n", pLinkPool[ pSrcNode->m_iFirstLink + j ].m_iDestNode, pLinkPool[ pSrcNode->m_iFirstLink + k ].m_iDestNode, DotProduct ( vec2DirToCheckNode, vec2DirToTestNode ) ); + } + + pLinkPool[ pSrcNode->m_iFirstLink + j ] = pLinkPool[ pSrcNode->m_iFirstLink + ( pSrcNode->m_cNumLinks - 1 ) ]; + pSrcNode->m_cNumLinks--; + j--; + + cRejectedLinks++;// keeping track of how many links are cut, so that we can return that value. + + fRestartLoop = TRUE; + } + } + } + } + + if ( file ) + { + fprintf ( file, "----------------------------------------------------------------------------\n\n" ); + } + } + + return cRejectedLinks; +} + +//========================================================= +// TestHull is a modelless clip hull that verifies reachable +// nodes by walking from every node to each of it's connections +//========================================================= +class CTestHull : public CBaseMonster +{ + +public: + void Spawn( entvars_t *pevMasterNode ); + virtual int ObjectCaps( void ) { return CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + void EXPORT CallBuildNodeGraph ( void ); + void BuildNodeGraph ( void ); + void EXPORT ShowBadNode ( void ); + void EXPORT DropDelay ( void ); + void EXPORT PathFind ( void ); + + Vector vecBadNodeOrigin; +}; + +LINK_ENTITY_TO_CLASS( testhull, CTestHull ); + +//========================================================= +// CTestHull::Spawn +//========================================================= +void CTestHull :: Spawn( entvars_t *pevMasterNode ) +{ + SET_MODEL(ENT(pev), "models/player.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + pev->effects = 0; + pev->health = 50; + pev->yaw_speed = 8; + + if ( WorldGraph.m_fGraphPresent ) + {// graph loaded from disk, so we don't need the test hull + SetThink ( &CTestHull::SUB_Remove ); + pev->nextthink = gpGlobals->time; + } + else + { + SetThink ( &CTestHull::DropDelay ); + pev->nextthink = gpGlobals->time + 1; + } + + // Make this invisible + // UNDONE: Shouldn't we just use EF_NODRAW? This doesn't need to go to the client. + pev->rendermode = kRenderTransTexture; + pev->renderamt = 0; +} + +//========================================================= +// TestHull::DropDelay - spawns TestHull on top of +// the 0th node and drops it to the ground. +//========================================================= +void CTestHull::DropDelay ( void ) +{ +// UTIL_CenterPrintAll( "Node Graph out of Date. Rebuilding..." ); + + UTIL_SetOrigin ( VARS(pev), WorldGraph.m_pNodes[ 0 ].m_vecOrigin ); + + SetThink ( &CTestHull::CallBuildNodeGraph ); + + pev->nextthink = gpGlobals->time + 1; +} + +//========================================================= +// nodes start out as ents in the world. As they are spawned, +// the node info is recorded then the ents are discarded. +//========================================================= +void CNodeEnt :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "hinttype")) + { + m_sHintType = (short)atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + + if (FStrEq(pkvd->szKeyName, "activity")) + { + m_sHintActivity = (short)atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +//========================================================= +//========================================================= +void CNodeEnt :: Spawn( void ) +{ + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT;// always solid_not + + if ( WorldGraph.m_fGraphPresent ) + {// graph loaded from disk, so discard all these node ents as soon as they spawn + REMOVE_ENTITY( edict() ); + return; + } + + if ( WorldGraph.m_cNodes == 0 ) + {// this is the first node to spawn, spawn the test hull entity that builds and walks the node tree + CTestHull *pHull = GetClassPtr((CTestHull *)NULL); + pHull->Spawn( pev ); + } + + if ( WorldGraph.m_cNodes >= MAX_NODES ) + { + ALERT ( at_aiconsole, "cNodes > MAX_NODES\n" ); + return; + } + + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_vecOriginPeek = + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_vecOrigin = pev->origin; + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_flHintYaw = pev->angles.y; + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_sHintType = m_sHintType; + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_sHintActivity = m_sHintActivity; + + if (FClassnameIs( pev, "info_node_air" )) + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_afNodeInfo = bits_NODE_AIR; + else + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_afNodeInfo = 0; + + WorldGraph.m_cNodes++; + + REMOVE_ENTITY( edict() ); +} + +//========================================================= +// CTestHull - ShowBadNode - makes a bad node fizzle. When +// there's a problem with node graph generation, the test +// hull will be placed up the bad node's location and will generate +// particles +//========================================================= +void CTestHull :: ShowBadNode( void ) +{ + pev->movetype = MOVETYPE_FLY; + pev->angles.y = pev->angles.y + 4; + + UTIL_MakeVectors ( pev->angles ); + + UTIL_ParticleEffect ( pev->origin, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin + gpGlobals->v_forward * 64, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin - gpGlobals->v_forward * 64, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin + gpGlobals->v_right * 64, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin - gpGlobals->v_right * 64, g_vecZero, 255, 25 ); + + pev->nextthink = gpGlobals->time + 0.1; +} + +extern BOOL gTouchDisabled; +void CTestHull::CallBuildNodeGraph( void ) +{ + // TOUCH HACK -- Don't allow this entity to call anyone's "touch" function + gTouchDisabled = TRUE; + BuildNodeGraph(); + gTouchDisabled = FALSE; + // Undo TOUCH HACK +} + +//========================================================= +// BuildNodeGraph - think function called by the empty walk +// hull that is spawned by the first node to spawn. This +// function links all nodes that can see each other, then +// eliminates all inline links, then uses a monster-sized +// hull that walks between each node and each of its links +// to ensure that a monster can actually fit through the space +//========================================================= +void CTestHull :: BuildNodeGraph( void ) +{ + TraceResult tr; + FILE *file; + + char szNrpFilename [MAX_PATH];// text node report filename + + CLink *pTempPool; // temporary link pool + + CNode *pSrcNode;// node we're currently working with + CNode *pDestNode;// the other node in comparison operations + + BOOL fSkipRemainingHulls;//if smallest hull can't fit, don't check any others + BOOL fPairsValid;// are all links in the graph evenly paired? + + int i, j, hull; + + int iBadNode;// this is the node that caused graph generation to fail + + int cMaxInitialLinks = 0; + int cMaxValidLinks = 0; + + int iPoolIndex = 0; + int cPoolLinks;// number of links in the pool. + + Vector vecDirToCheckNode; + Vector vecDirToTestNode; + Vector vecStepCheckDir; + Vector vecTraceSpot; + Vector vecSpot; + + Vector2D vec2DirToCheckNode; + Vector2D vec2DirToTestNode; + Vector2D vec2StepCheckDir; + Vector2D vec2TraceSpot; + Vector2D vec2Spot; + + float flYaw;// use this stuff to walk the hull between nodes + float flDist; + int step; + + SetThink ( &CTestHull::SUB_Remove );// no matter what happens, the hull gets rid of itself. + pev->nextthink = gpGlobals->time; + +// malloc a swollen temporary connection pool that we trim down after we know exactly how many connections there are. + pTempPool = (CLink *)calloc ( sizeof ( CLink ) , ( WorldGraph.m_cNodes * MAX_NODE_INITIAL_LINKS ) ); + if ( !pTempPool ) + { + ALERT ( at_aiconsole, "**Could not malloc TempPool!\n" ); + return; + } + + + // make sure directories have been made + GET_GAME_DIR( szNrpFilename ); + strcat( szNrpFilename, "/maps" ); + CreateDirectory( szNrpFilename, NULL ); + strcat( szNrpFilename, "/graphs" ); + CreateDirectory( szNrpFilename, NULL ); + + strcat( szNrpFilename, "/" ); + strcat( szNrpFilename, STRING( gpGlobals->mapname ) ); + strcat( szNrpFilename, ".nrp" ); + + file = fopen ( szNrpFilename, "w+" ); + + if ( !file ) + {// file error + ALERT ( at_aiconsole, "Couldn't create %s!\n", szNrpFilename ); + + if ( pTempPool ) + { + free ( pTempPool ); + } + + return; + } + + fprintf( file, "Node Graph Report for map: %s.bsp\n", STRING(gpGlobals->mapname) ); + fprintf ( file, "%d Total Nodes\n\n", WorldGraph.m_cNodes ); + + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + {// print all node numbers and their locations to the file. + WorldGraph.m_pNodes[ i ].m_cNumLinks = 0; + WorldGraph.m_pNodes[ i ].m_iFirstLink = 0; + memset(WorldGraph.m_pNodes[ i ].m_pNextBestNode, 0, sizeof(WorldGraph.m_pNodes[ i ].m_pNextBestNode)); + + fprintf ( file, "Node# %4d\n", i ); + fprintf ( file, "Location %4d,%4d,%4d\n",(int)WorldGraph.m_pNodes[ i ].m_vecOrigin.x, (int)WorldGraph.m_pNodes[ i ].m_vecOrigin.y, (int)WorldGraph.m_pNodes[ i ].m_vecOrigin.z ); + fprintf ( file, "HintType: %4d\n", WorldGraph.m_pNodes[ i ].m_sHintType ); + fprintf ( file, "HintActivity: %4d\n", WorldGraph.m_pNodes[ i ].m_sHintActivity ); + fprintf ( file, "HintYaw: %4f\n", WorldGraph.m_pNodes[ i ].m_flHintYaw ); + fprintf ( file, "-------------------------------------------------------------------------------\n" ); + } + fprintf ( file, "\n\n" ); + + + // Automatically recognize WATER nodes and drop the LAND nodes to the floor. + // + for ( i = 0; i < WorldGraph.m_cNodes; i++) + { + if (WorldGraph.m_pNodes[ i ].m_afNodeInfo & bits_NODE_AIR) + { + // do nothing + } + else if (UTIL_PointContents(WorldGraph.m_pNodes[ i ].m_vecOrigin) == CONTENTS_WATER) + { + WorldGraph.m_pNodes[ i ].m_afNodeInfo |= bits_NODE_WATER; + } + else + { + WorldGraph.m_pNodes[ i ].m_afNodeInfo |= bits_NODE_LAND; + + // trace to the ground, then pop up 8 units and place node there to make it + // easier for them to connect (think stairs, chairs, and bumps in the floor). + // After the routing is done, push them back down. + // + TraceResult tr; + + UTIL_TraceLine ( WorldGraph.m_pNodes[i].m_vecOrigin, + WorldGraph.m_pNodes[i].m_vecOrigin - Vector ( 0, 0, 384 ), + ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &tr ); + + // This trace is ONLY used if we hit an entity flagged with FL_WORLDBRUSH + TraceResult trEnt; + UTIL_TraceLine ( WorldGraph.m_pNodes[i].m_vecOrigin, + WorldGraph.m_pNodes[i].m_vecOrigin - Vector ( 0, 0, 384 ), + dont_ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &trEnt ); + + + // Did we hit something closer than the floor? + if ( trEnt.flFraction < tr.flFraction ) + { + // If it was a world brush entity, copy the node location + if ( trEnt.pHit && (trEnt.pHit->v.flags & FL_WORLDBRUSH) ) + tr.vecEndPos = trEnt.vecEndPos; + } + + WorldGraph.m_pNodes[i].m_vecOriginPeek.z = + WorldGraph.m_pNodes[i].m_vecOrigin.z = tr.vecEndPos.z + NODE_HEIGHT; + } + } + + cPoolLinks = WorldGraph.LinkVisibleNodes( pTempPool, file, &iBadNode ); + + if ( !cPoolLinks ) + { + ALERT ( at_aiconsole, "**ConnectVisibleNodes FAILED!\n" ); + + SetThink ( &CTestHull::ShowBadNode );// send the hull off to show the offending node. + //pev->solid = SOLID_NOT; + pev->origin = WorldGraph.m_pNodes[ iBadNode ].m_vecOrigin; + + if ( pTempPool ) + { + free ( pTempPool ); + } + + if ( file ) + {// close the file + fclose ( file ); + } + + return; + } + +// send the walkhull to all of this node's connections now. We'll do this here since +// so much of it relies on being able to control the test hull. + fprintf ( file, "----------------------------------------------------------------------------\n" ); + fprintf ( file, "Walk Rejection:\n"); + + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + pSrcNode = &WorldGraph.m_pNodes[ i ]; + + fprintf ( file, "-------------------------------------------------------------------------------\n"); + fprintf ( file, "Node %4d:\n\n", i ); + + for ( j = 0 ; j < pSrcNode->m_cNumLinks ; j++ ) + { + // assume that all hulls can walk this link, then eliminate the ones that can't. + pTempPool [ pSrcNode->m_iFirstLink + j ].m_afLinkInfo = bits_LINK_SMALL_HULL | bits_LINK_HUMAN_HULL | bits_LINK_LARGE_HULL | bits_LINK_FLY_HULL; + + + // do a check for each hull size. + + // if we can't fit a tiny hull through a connection, no other hulls with fit either, so we + // should just fall out of the loop. Do so by setting the SkipRemainingHulls flag. + fSkipRemainingHulls = FALSE; + for ( hull = 0 ; hull < MAX_NODE_HULLS; hull++ ) + { + if (fSkipRemainingHulls && (hull == NODE_HUMAN_HULL || hull == NODE_LARGE_HULL)) // skip the remaining walk hulls + continue; + + switch ( hull ) + { + case NODE_SMALL_HULL: + UTIL_SetSize(pev, Vector(-12, -12, 0), Vector(12, 12, 24)); + break; + case NODE_HUMAN_HULL: + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX ); + break; + case NODE_LARGE_HULL: + UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 64)); + break; + case NODE_FLY_HULL: + UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 64)); + // UTIL_SetSize(pev, Vector(0, 0, 0), Vector(0, 0, 0)); + break; + } + + UTIL_SetOrigin ( pev, pSrcNode->m_vecOrigin );// place the hull on the node + + if ( !FBitSet ( pev->flags, FL_ONGROUND ) ) + { + ALERT ( at_aiconsole, "OFFGROUND!\n" ); + } + + // now build a yaw that points to the dest node, and get the distance. + if ( j < 0 ) + { + ALERT ( at_aiconsole, "**** j = %d ****\n", j ); + if ( pTempPool ) + { + free ( pTempPool ); + } + + if ( file ) + {// close the file + fclose ( file ); + } + return; + } + + pDestNode = &WorldGraph.m_pNodes [ pTempPool[ pSrcNode->m_iFirstLink + j ].m_iDestNode ]; + + vecSpot = pDestNode->m_vecOrigin; + //vecSpot.z = pev->origin.z; + + if (hull < NODE_FLY_HULL) + { + int SaveFlags = pev->flags; + int MoveMode = WALKMOVE_WORLDONLY; + if (pSrcNode->m_afNodeInfo & bits_NODE_WATER) + { + pev->flags |= FL_SWIM; + MoveMode = WALKMOVE_NORMAL; + } + + flYaw = UTIL_VecToYaw ( pDestNode->m_vecOrigin - pev->origin ); + + flDist = ( vecSpot - pev->origin ).Length2D(); + + int fWalkFailed = FALSE; + + // in this loop we take tiny steps from the current node to the nodes that it links to, one at a time. + // pev->angles.y = flYaw; + for ( step = 0 ; step < flDist && !fWalkFailed ; step += HULL_STEP_SIZE ) + { + float stepSize = HULL_STEP_SIZE; + + if ( (step + stepSize) >= (flDist-1) ) + stepSize = (flDist - step) - 1; + + if ( !WALK_MOVE( ENT(pev), flYaw, stepSize, MoveMode ) ) + {// can't take the next step + + fWalkFailed = TRUE; + break; + } + } + + if (!fWalkFailed && (pev->origin - vecSpot).Length() > 64) + { + // ALERT( at_console, "bogus walk\n"); + // we thought we + fWalkFailed = TRUE; + } + + if (fWalkFailed) + { + + //pTempPool[ pSrcNode->m_iFirstLink + j ] = pTempPool [ pSrcNode->m_iFirstLink + ( pSrcNode->m_cNumLinks - 1 ) ]; + + // now me must eliminate the hull that couldn't walk this connection + switch ( hull ) + { + case NODE_SMALL_HULL: // if this hull can't fit, nothing can, so drop the connection + fprintf ( file, "NODE_SMALL_HULL step %f\n", step ); + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~(bits_LINK_SMALL_HULL | bits_LINK_HUMAN_HULL | bits_LINK_LARGE_HULL); + fSkipRemainingHulls = TRUE;// don't bother checking larger hulls + break; + case NODE_HUMAN_HULL: + fprintf ( file, "NODE_HUMAN_HULL step %f\n", step ); + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~(bits_LINK_HUMAN_HULL | bits_LINK_LARGE_HULL); + fSkipRemainingHulls = TRUE;// don't bother checking larger hulls + break; + case NODE_LARGE_HULL: + fprintf ( file, "NODE_LARGE_HULL step %f\n", step ); + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~bits_LINK_LARGE_HULL; + break; + } + } + pev->flags = SaveFlags; + } + else + { + TraceResult tr; + + UTIL_TraceHull( pSrcNode->m_vecOrigin + Vector( 0, 0, 32 ), pDestNode->m_vecOriginPeek + Vector( 0, 0, 32 ), ignore_monsters, large_hull, ENT( pev ), &tr ); + if (tr.fStartSolid || tr.flFraction < 1.0) + { + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~bits_LINK_FLY_HULL; + } + } + } + + if (pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo == 0) + { + fprintf ( file, "Rejected Node %3d - Unreachable by ", pTempPool [ pSrcNode->m_iFirstLink + j ].m_iDestNode ); + pTempPool[ pSrcNode->m_iFirstLink + j ] = pTempPool [ pSrcNode->m_iFirstLink + ( pSrcNode->m_cNumLinks - 1 ) ]; + fprintf ( file, "Any Hull\n" ); + + pSrcNode->m_cNumLinks--; + cPoolLinks--;// we just removed a link, so decrement the total number of links in the pool. + j--; + } + + } + } + fprintf ( file, "-------------------------------------------------------------------------------\n\n\n"); + + cPoolLinks -= WorldGraph.RejectInlineLinks ( pTempPool, file ); + +// now malloc a pool just large enough to hold the links that are actually used + WorldGraph.m_pLinkPool = (CLink *) calloc ( sizeof ( CLink ), cPoolLinks ); + + if ( !WorldGraph.m_pLinkPool ) + {// couldn't make the link pool! + ALERT ( at_aiconsole, "Couldn't malloc LinkPool!\n" ); + if ( pTempPool ) + { + free ( pTempPool ); + } + if ( file ) + {// close the file + fclose ( file ); + } + + return; + } + WorldGraph.m_cLinks = cPoolLinks; + +//copy only the used portions of the TempPool into the graph's link pool + int iFinalPoolIndex = 0; + int iOldFirstLink; + + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + iOldFirstLink = WorldGraph.m_pNodes[ i ].m_iFirstLink;// store this, because we have to re-assign it before entering the copy loop + + WorldGraph.m_pNodes[ i ].m_iFirstLink = iFinalPoolIndex; + + for ( j = 0 ; j < WorldGraph.m_pNodes[ i ].m_cNumLinks ; j++ ) + { + WorldGraph.m_pLinkPool[ iFinalPoolIndex++ ] = pTempPool[ iOldFirstLink + j ]; + } + } + + + // Node sorting numbers linked nodes close to each other + // + WorldGraph.SortNodes(); + + // This is used for HashSearch + // + WorldGraph.BuildLinkLookups(); + + fPairsValid = TRUE; // assume that the connection pairs are all valid to start + + fprintf ( file, "\n\n-------------------------------------------------------------------------------\n"); + fprintf ( file, "Link Pairings:\n"); + +// link integrity check. The idea here is that if Node A links to Node B, node B should +// link to node A. If not, we have a situation that prevents us from using a basic +// optimization in the FindNearestLink function. + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + for ( j = 0 ; j < WorldGraph.m_pNodes[ i ].m_cNumLinks ; j++ ) + { + int iLink; + WorldGraph.HashSearch(WorldGraph.INodeLink(i,j), i, iLink); + if (iLink < 0) + { + fPairsValid = FALSE;// unmatched link pair. + fprintf ( file, "WARNING: Node %3d does not connect back to Node %3d\n", WorldGraph.INodeLink(i, j), i); + } + } + } + + // !!!LATER - if all connections are properly paired, when can enable an optimization in the pathfinding code + // (in the find nearest line function) + if ( fPairsValid ) + { + fprintf ( file, "\nAll Connections are Paired!\n"); + } + + fprintf ( file, "-------------------------------------------------------------------------------\n"); + fprintf ( file, "\n\n-------------------------------------------------------------------------------\n"); + fprintf ( file, "Total Number of Connections in Pool: %d\n", cPoolLinks ); + fprintf ( file, "-------------------------------------------------------------------------------\n"); + fprintf ( file, "Connection Pool: %d bytes\n", sizeof ( CLink ) * cPoolLinks ); + fprintf ( file, "-------------------------------------------------------------------------------\n"); + + + ALERT ( at_aiconsole, "%d Nodes, %d Connections\n", WorldGraph.m_cNodes, cPoolLinks ); + + // This is used for FindNearestNode + // + WorldGraph.BuildRegionTables(); + + + // Push all of the LAND nodes down to the ground now. Leave the water and air nodes alone. + // + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + if ((WorldGraph.m_pNodes[ i ].m_afNodeInfo & bits_NODE_LAND)) + { + WorldGraph.m_pNodes[ i ].m_vecOrigin.z -= NODE_HEIGHT; + } + } + + + if ( pTempPool ) + {// free the temp pool + free ( pTempPool ); + } + + if ( file ) + { + fclose ( file ); + } + + // We now have some graphing capabilities. + // + WorldGraph.m_fGraphPresent = TRUE;//graph is in memory. + WorldGraph.m_fGraphPointersSet = TRUE;// since the graph was generated, the pointers are ready + WorldGraph.m_fRoutingComplete = FALSE; // Optimal routes aren't computed, yet. + + // Compute and compress the routing information. + // + WorldGraph.ComputeStaticRoutingTables(); + +// save the node graph for this level + WorldGraph.FSaveGraph( (char *)STRING( gpGlobals->mapname ) ); + ALERT( at_console, "Done.\n"); +} + + +//========================================================= +// returns a hardcoded path. +//========================================================= +void CTestHull :: PathFind ( void ) +{ + int iPath[ 50 ]; + int iPathSize; + int i; + CNode *pNode, *pNextNode; + + if ( !WorldGraph.m_fGraphPresent || !WorldGraph.m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return; + } + + iPathSize = WorldGraph.FindShortestPath ( iPath, 0, 19, 0, 0 ); // UNDONE use hull constant + + if ( !iPathSize ) + { + ALERT ( at_aiconsole, "No Path!\n" ); + return; + } + + ALERT ( at_aiconsole, "%d\n", iPathSize ); + + pNode = &WorldGraph.m_pNodes[ iPath [ 0 ] ]; + + for ( i = 0 ; i < iPathSize - 1 ; i++ ) + { + + pNextNode = &WorldGraph.m_pNodes[ iPath [ i + 1 ] ]; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( pNode->m_vecOrigin.x ); + WRITE_COORD( pNode->m_vecOrigin.y ); + WRITE_COORD( pNode->m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( pNextNode->m_vecOrigin.x); + WRITE_COORD( pNextNode->m_vecOrigin.y); + WRITE_COORD( pNextNode->m_vecOrigin.z + NODE_HEIGHT); + MESSAGE_END(); + + pNode = pNextNode; + } + +} + + +//========================================================= +// CStack Constructor +//========================================================= +CStack :: CStack( void ) +{ + m_level = 0; +} + +//========================================================= +// pushes a value onto the stack +//========================================================= +void CStack :: Push( int value ) +{ + if ( m_level >= MAX_STACK_NODES ) + { + printf("Error!\n"); + return; + } + m_stack[m_level] = value; + m_level++; +} + +//========================================================= +// pops a value off of the stack +//========================================================= +int CStack :: Pop( void ) +{ + if ( m_level <= 0 ) + return -1; + + m_level--; + return m_stack[ m_level ]; +} + +//========================================================= +// returns the value on the top of the stack +//========================================================= +int CStack :: Top ( void ) +{ + return m_stack[ m_level - 1 ]; +} + +//========================================================= +// copies every element on the stack into an array LIFO +//========================================================= +void CStack :: CopyToArray ( int *piArray ) +{ + int i; + + for ( i = 0 ; i < m_level ; i++ ) + { + piArray[ i ] = m_stack[ i ]; + } +} + +//========================================================= +// CQueue constructor +//========================================================= +CQueue :: CQueue( void ) +{ + m_cSize = 0; + m_head = 0; + m_tail = -1; +} + +//========================================================= +// inserts a value into the queue +//========================================================= +void CQueue :: Insert ( int iValue, float fPriority ) +{ + + if ( Full() ) + { + printf ( "Queue is full!\n" ); + return; + } + + m_tail++; + + if ( m_tail == MAX_STACK_NODES ) + {//wrap around + m_tail = 0; + } + + m_queue[ m_tail ].Id = iValue; + m_queue[ m_tail ].Priority = fPriority; + m_cSize++; +} + +//========================================================= +// removes a value from the queue (FIFO) +//========================================================= +int CQueue :: Remove ( float &fPriority ) +{ + if ( m_head == MAX_STACK_NODES ) + {// wrap + m_head = 0; + } + + m_cSize--; + fPriority = m_queue[ m_head ].Priority; + return m_queue[ m_head++ ].Id; +} + +//========================================================= +// CQueue constructor +//========================================================= +CQueuePriority :: CQueuePriority( void ) +{ + m_cSize = 0; +} + +//========================================================= +// inserts a value into the priority queue +//========================================================= +void CQueuePriority :: Insert( int iValue, float fPriority ) +{ + + if ( Full() ) + { + printf ( "Queue is full!\n" ); + return; + } + + m_heap[ m_cSize ].Priority = fPriority; + m_heap[ m_cSize ].Id = iValue; + m_cSize++; + Heap_SiftUp(); +} + +//========================================================= +// removes the smallest item from the priority queue +// +//========================================================= +int CQueuePriority :: Remove( float &fPriority ) +{ + int iReturn = m_heap[ 0 ].Id; + fPriority = m_heap[ 0 ].Priority; + + m_cSize--; + + m_heap[ 0 ] = m_heap[ m_cSize ]; + + Heap_SiftDown(0); + return iReturn; +} + +#define HEAP_LEFT_CHILD(x) (2*(x)+1) +#define HEAP_RIGHT_CHILD(x) (2*(x)+2) +#define HEAP_PARENT(x) (((x)-1)/2) + +void CQueuePriority::Heap_SiftDown(int iSubRoot) +{ + int parent = iSubRoot; + int child = HEAP_LEFT_CHILD(parent); + + struct tag_HEAP_NODE Ref = m_heap[ parent ]; + + while (child < m_cSize) + { + int rightchild = HEAP_RIGHT_CHILD(parent); + if (rightchild < m_cSize) + { + if ( m_heap[ rightchild ].Priority < m_heap[ child ].Priority ) + { + child = rightchild; + } + } + if ( Ref.Priority <= m_heap[ child ].Priority ) + break; + + m_heap[ parent ] = m_heap[ child ]; + parent = child; + child = HEAP_LEFT_CHILD(parent); + } + m_heap[ parent ] = Ref; +} + +void CQueuePriority::Heap_SiftUp(void) +{ + int child = m_cSize-1; + while (child) + { + int parent = HEAP_PARENT(child); + if ( m_heap[ parent ].Priority <= m_heap[ child ].Priority ) + break; + + struct tag_HEAP_NODE Tmp; + Tmp = m_heap[ child ]; + m_heap[ child ] = m_heap[ parent ]; + m_heap[ parent ] = Tmp; + + child = parent; + } +} + +//========================================================= +// CGraph - FLoadGraph - attempts to load a node graph from disk. +// if the current level is maps/snar.bsp, maps/graphs/snar.nod +// will be loaded. If file cannot be loaded, the node tree +// will be created and saved to disk. +//========================================================= +int CGraph :: FLoadGraph ( char *szMapName ) +{ + char szFilename[MAX_PATH]; + int iVersion; + int length; + byte *aMemFile; + byte *pMemFile; + + // make sure the directories have been made + char szDirName[MAX_PATH]; + GET_GAME_DIR( szDirName ); + strcat( szDirName, "/maps" ); + CreateDirectory( szDirName, NULL ); + strcat( szDirName, "/graphs" ); + CreateDirectory( szDirName, NULL ); + + strcpy ( szFilename, "maps/graphs/" ); + strcat ( szFilename, szMapName ); + strcat( szFilename, ".nod" ); + + pMemFile = aMemFile = LOAD_FILE_FOR_ME(szFilename, &length); + + if ( !aMemFile ) + { + return FALSE; + } + else + { + // Read the graph version number + // + length -= sizeof(int); + if (length < 0) goto ShortFile; + memcpy(&iVersion, pMemFile, sizeof(int)); + pMemFile += sizeof(int); + + if ( iVersion != GRAPH_VERSION ) + { + // This file was written by a different build of the dll! + // + ALERT ( at_aiconsole, "**ERROR** Graph version is %d, expected %d\n",iVersion, GRAPH_VERSION ); + goto ShortFile; + } + + // Read the graph class + // + length -= sizeof(CGraph); + if (length < 0) goto ShortFile; + memcpy(this, pMemFile, sizeof(CGraph)); + pMemFile += sizeof(CGraph); + + // Set the pointers to zero, just in case we run out of memory. + // + m_pNodes = NULL; + m_pLinkPool = NULL; + m_di = NULL; + m_pRouteInfo = NULL; + m_pHashLinks = NULL; + + + // Malloc for the nodes + // + m_pNodes = ( CNode * )calloc ( sizeof ( CNode ), m_cNodes ); + + if ( !m_pNodes ) + { + ALERT ( at_aiconsole, "**ERROR**\nCouldn't malloc %d nodes!\n", m_cNodes ); + goto NoMemory; + } + + // Read in all the nodes + // + length -= sizeof(CNode) * m_cNodes; + if (length < 0) goto ShortFile; + memcpy(m_pNodes, pMemFile, sizeof(CNode)*m_cNodes); + pMemFile += sizeof(CNode) * m_cNodes; + + + // Malloc for the link pool + // + m_pLinkPool = ( CLink * )calloc ( sizeof ( CLink ), m_cLinks ); + + if ( !m_pLinkPool ) + { + ALERT ( at_aiconsole, "**ERROR**\nCouldn't malloc %d link!\n", m_cLinks ); + goto NoMemory; + } + + // Read in all the links + // + length -= sizeof(CLink)*m_cLinks; + if (length < 0) goto ShortFile; + memcpy(m_pLinkPool, pMemFile, sizeof(CLink)*m_cLinks); + pMemFile += sizeof(CLink)*m_cLinks; + + // Malloc for the sorting info. + // + m_di = (DIST_INFO *)calloc( sizeof(DIST_INFO), m_cNodes ); + if ( !m_di ) + { + ALERT ( at_aiconsole, "***ERROR**\nCouldn't malloc %d entries sorting nodes!\n", m_cNodes ); + goto NoMemory; + } + + // Read it in. + // + length -= sizeof(DIST_INFO)*m_cNodes; + if (length < 0) goto ShortFile; + memcpy(m_di, pMemFile, sizeof(DIST_INFO)*m_cNodes); + pMemFile += sizeof(DIST_INFO)*m_cNodes; + + // Malloc for the routing info. + // + m_fRoutingComplete = FALSE; + m_pRouteInfo = (char *)calloc( sizeof(char), m_nRouteInfo ); + if ( !m_pRouteInfo ) + { + ALERT ( at_aiconsole, "***ERROR**\nCounldn't malloc %d route bytes!\n", m_nRouteInfo ); + goto NoMemory; + } + m_CheckedCounter = 0; + for (int i = 0; i < m_cNodes; i++) + { + m_di[i].m_CheckedEvent = 0; + } + + // Read in the route information. + // + length -= sizeof(char)*m_nRouteInfo; + if (length < 0) goto ShortFile; + memcpy(m_pRouteInfo, pMemFile, sizeof(char)*m_nRouteInfo); + pMemFile += sizeof(char)*m_nRouteInfo; + m_fRoutingComplete = TRUE;; + + // malloc for the hash links + // + m_pHashLinks = (short *)calloc(sizeof(short), m_nHashLinks); + if (!m_pHashLinks) + { + ALERT ( at_aiconsole, "***ERROR**\nCounldn't malloc %d hash link bytes!\n", m_nHashLinks ); + goto NoMemory; + } + + // Read in the hash link information + // + length -= sizeof(short)*m_nHashLinks; + if (length < 0) goto ShortFile; + memcpy(m_pHashLinks, pMemFile, sizeof(short)*m_nHashLinks); + pMemFile += sizeof(short)*m_nHashLinks; + + // Set the graph present flag, clear the pointers set flag + // + m_fGraphPresent = TRUE; + m_fGraphPointersSet = FALSE; + + FREE_FILE(aMemFile); + + if (length != 0) + { + ALERT ( at_aiconsole, "***WARNING***:Node graph was longer than expected by %d bytes.!\n", length); + } + + return TRUE; + } + +ShortFile: +NoMemory: + FREE_FILE(aMemFile); + return FALSE; +} + +//========================================================= +// CGraph - FSaveGraph - It's not rocket science. +// this WILL overwrite existing files. +//========================================================= +int CGraph :: FSaveGraph ( char *szMapName ) +{ + + int iVersion = GRAPH_VERSION; + char szFilename[MAX_PATH]; + FILE *file; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available or built + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return FALSE; + } + + // make sure directories have been made + GET_GAME_DIR( szFilename ); + strcat( szFilename, "/maps" ); + CreateDirectory( szFilename, NULL ); + strcat( szFilename, "/graphs" ); + CreateDirectory( szFilename, NULL ); + + strcat( szFilename, "/" ); + strcat( szFilename, szMapName ); + strcat( szFilename, ".nod" ); + + file = fopen ( szFilename, "wb" ); + + ALERT ( at_aiconsole, "Created: %s\n", szFilename ); + + if ( !file ) + {// couldn't create + ALERT ( at_aiconsole, "Couldn't Create: %s\n", szFilename ); + return FALSE; + } + else + { + // write the version + fwrite ( &iVersion, sizeof ( int ), 1, file ); + + // write the CGraph class + fwrite ( this, sizeof ( CGraph ), 1, file ); + + // write the nodes + fwrite ( m_pNodes, sizeof ( CNode ), m_cNodes, file ); + + // write the links + fwrite ( m_pLinkPool, sizeof ( CLink ), m_cLinks, file ); + + fwrite ( m_di, sizeof(DIST_INFO), m_cNodes, file ); + + // Write the route info. + // + if ( m_pRouteInfo && m_nRouteInfo ) + { + fwrite ( m_pRouteInfo, sizeof( char ), m_nRouteInfo, file ); + } + + if (m_pHashLinks && m_nHashLinks) + { + fwrite(m_pHashLinks, sizeof(short), m_nHashLinks, file); + } + fclose ( file ); + return TRUE; + } +} + +//========================================================= +// CGraph - FSetGraphPointers - Takes the modelnames of +// all of the brush ents that block connections in the node +// graph and resolves them into pointers to those entities. +// this is done after loading the graph from disk, whereupon +// the pointers are not valid. +//========================================================= +int CGraph :: FSetGraphPointers ( void ) +{ + int i; + edict_t *pentLinkEnt; + + for ( i = 0 ; i < m_cLinks ; i++ ) + {// go through all of the links + + if ( m_pLinkPool[ i ].m_pLinkEnt != NULL ) + { + char name[5]; + // when graphs are saved, any valid pointers are will be non-zero, signifying that we should + // reset those pointers upon reloading. Any pointers that were NULL when the graph was saved + // will be NULL when reloaded, and will ignored by this function. + + // m_szLinkEntModelname is not necessarily NULL terminated (so we can store it in a more alignment-friendly 4 bytes) + memcpy( name, m_pLinkPool[ i ].m_szLinkEntModelname, 4 ); + name[4] = 0; + pentLinkEnt = FIND_ENTITY_BY_STRING( NULL, "model", name ); + + if ( FNullEnt ( pentLinkEnt ) ) + { + // the ent isn't around anymore? Either there is a major problem, or it was removed from the world + // ( like a func_breakable that's been destroyed or something ). Make sure that LinkEnt is null. + ALERT ( at_aiconsole, "**Could not find model %s\n", name ); + m_pLinkPool[ i ].m_pLinkEnt = NULL; + } + else + { + m_pLinkPool[ i ].m_pLinkEnt = VARS( pentLinkEnt ); + + if ( !FBitSet( m_pLinkPool[ i ].m_pLinkEnt->flags, FL_GRAPHED ) ) + { + m_pLinkPool[ i ].m_pLinkEnt->flags += FL_GRAPHED; + } + } + } + } + + // the pointers are now set. + m_fGraphPointersSet = TRUE; + return TRUE; +} + +//========================================================= +// CGraph - CheckNODFile - this function checks the date of +// the BSP file that was just loaded and the date of the a +// ssociated .NOD file. If the NOD file is not present, or +// is older than the BSP file, we rebuild it. +// +// returns FALSE if the .NOD file doesn't qualify and needs +// to be rebuilt. +// +// !!!BUGBUG - the file times we get back are 20 hours ahead! +// since this happens consistantly, we can still correctly +// determine which of the 2 files is newer. This needs fixed, +// though. ( I now suspect that we are getting GMT back from +// these functions and must compensate for local time ) (sjb) +//========================================================= +int CGraph :: CheckNODFile ( char *szMapName ) +{ + int retValue; + + char szBspFilename[MAX_PATH]; + char szGraphFilename[MAX_PATH]; + + + strcpy ( szBspFilename, "maps/" ); + strcat ( szBspFilename, szMapName ); + strcat ( szBspFilename, ".bsp" ); + + strcpy ( szGraphFilename, "maps/graphs/" ); + strcat ( szGraphFilename, szMapName ); + strcat ( szGraphFilename, ".nod" ); + + retValue = TRUE; + + int iCompare; + if (COMPARE_FILE_TIME(szBspFilename, szGraphFilename, &iCompare)) + { + if ( iCompare > 0 ) + {// BSP file is newer. + ALERT ( at_aiconsole, ".NOD File will be updated\n\n" ); + retValue = FALSE; + } + } + else + { + retValue = FALSE; + } + + return retValue; +} + +#define ENTRY_STATE_EMPTY -1 + +struct tagNodePair +{ + short iSrc; + short iDest; +}; + +void CGraph::HashInsert(int iSrcNode, int iDestNode, int iKey) +{ + struct tagNodePair np; + + np.iSrc = iSrcNode; + np.iDest = iDestNode; + CRC32_t dwHash; + CRC32_INIT(&dwHash); + CRC32_PROCESS_BUFFER(&dwHash, &np, sizeof(np)); + dwHash = CRC32_FINAL(dwHash); + + int di = m_HashPrimes[dwHash&15]; + int i = (dwHash >> 4) % m_nHashLinks; + while (m_pHashLinks[i] != ENTRY_STATE_EMPTY) + { + i += di; + if (i >= m_nHashLinks) i -= m_nHashLinks; + } + m_pHashLinks[i] = iKey; +} + +void CGraph::HashSearch(int iSrcNode, int iDestNode, int &iKey) +{ + struct tagNodePair np; + + np.iSrc = iSrcNode; + np.iDest = iDestNode; + CRC32_t dwHash; + CRC32_INIT(&dwHash); + CRC32_PROCESS_BUFFER(&dwHash, &np, sizeof(np)); + dwHash = CRC32_FINAL(dwHash); + + int di = m_HashPrimes[dwHash&15]; + int i = (dwHash >> 4) % m_nHashLinks; + while (m_pHashLinks[i] != ENTRY_STATE_EMPTY) + { + CLink &link = Link(m_pHashLinks[i]); + if (iSrcNode == link.m_iSrcNode && iDestNode == link.m_iDestNode) + { + break; + } + else + { + i += di; + if (i >= m_nHashLinks) i -= m_nHashLinks; + } + } + iKey = m_pHashLinks[i]; +} + +#define NUMBER_OF_PRIMES 177 + +int Primes[NUMBER_OF_PRIMES] = +{ 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, +71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, +157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, +241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, +347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, +439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, +547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, +643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, +751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, +859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, +977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 0 }; + +void CGraph::HashChoosePrimes(int TableSize) +{ + int LargestPrime = TableSize/2; + if (LargestPrime > Primes[NUMBER_OF_PRIMES-2]) + { + LargestPrime = Primes[NUMBER_OF_PRIMES-2]; + } + int Spacing = LargestPrime/16; + + // Pick a set primes that are evenly spaced from (0 to LargestPrime) + // We divide this interval into 16 equal sized zones. We want to find + // one prime number that best represents that zone. + // + int iPrime,iZone; + for (iZone = 1, iPrime = 0; iPrime < 16; iZone += Spacing) + { + // Search for a prime number that is less than the target zone + // number given by iZone. + // + int Lower = Primes[0]; + for (int jPrime = 0; Primes[jPrime] != 0; jPrime++) + { + if (jPrime != 0 && TableSize % Primes[jPrime] == 0) continue; + int Upper = Primes[jPrime]; + if (Lower <= iZone && iZone <= Upper) + { + // Choose the closest lower prime number. + // + if (iZone - Lower <= Upper - iZone) + { + m_HashPrimes[iPrime++] = Lower; + } + else + { + m_HashPrimes[iPrime++] = Upper; + } + break; + } + Lower = Upper; + } + } + + // Alternate negative and positive numbers + // + for (iPrime = 0; iPrime < 16; iPrime += 2) + { + m_HashPrimes[iPrime] = TableSize-m_HashPrimes[iPrime]; + } + + // Shuffle the set of primes to reduce correlation with bits in + // hash key. + // + for (iPrime = 0; iPrime < 16-1; iPrime++) + { + int Pick = RANDOM_LONG(0, 15-iPrime); + int Temp = m_HashPrimes[Pick]; + m_HashPrimes[Pick] = m_HashPrimes[15-iPrime]; + m_HashPrimes[15-iPrime] = Temp; + } +} + +// Renumber nodes so that nodes that link together are together. +// +#define UNNUMBERED_NODE -1 +void CGraph::SortNodes(void) +{ + // We are using m_iPreviousNode to be the new node number. + // After assigning new node numbers to everything, we move + // things and patchup the links. + // + int iNodeCnt = 0; + int i; + m_pNodes[0].m_iPreviousNode = iNodeCnt++; + + for (i = 1; i < m_cNodes; i++) + { + m_pNodes[i].m_iPreviousNode = UNNUMBERED_NODE; + } + + for (i = 0; i < m_cNodes; i++) + { + // Run through all of this node's neighbors + // + for (int j = 0 ; j < m_pNodes[i].m_cNumLinks; j++ ) + { + int iDestNode = INodeLink(i, j); + if (m_pNodes[iDestNode].m_iPreviousNode == UNNUMBERED_NODE) + { + m_pNodes[iDestNode].m_iPreviousNode = iNodeCnt++; + } + } + } + + // Assign remaining node numbers to unlinked nodes. + // + for (i = 0; i < m_cNodes; i++) + { + if (m_pNodes[i].m_iPreviousNode == UNNUMBERED_NODE) + { + m_pNodes[i].m_iPreviousNode = iNodeCnt++; + } + } + + // Alter links to reflect new node numbers. + // + for (i = 0; i < m_cLinks; i++) + { + m_pLinkPool[i].m_iSrcNode = m_pNodes[m_pLinkPool[i].m_iSrcNode].m_iPreviousNode; + m_pLinkPool[i].m_iDestNode = m_pNodes[m_pLinkPool[i].m_iDestNode].m_iPreviousNode; + } + + // Rearrange nodes to reflect new node numbering. + // + for (i = 0; i < m_cNodes; i++) + { + while (m_pNodes[i].m_iPreviousNode != i) + { + // Move current node off to where it should be, and bring + // that other node back into the current slot. + // + int iDestNode = m_pNodes[i].m_iPreviousNode; + CNode TempNode = m_pNodes[iDestNode]; + m_pNodes[iDestNode] = m_pNodes[i]; + m_pNodes[i] = TempNode; + } + } +} + +void CGraph::BuildLinkLookups(void) +{ + m_nHashLinks = 3*m_cLinks/2 + 3; + + HashChoosePrimes(m_nHashLinks); + m_pHashLinks = (short *)calloc(sizeof(short), m_nHashLinks); + if (!m_pHashLinks) + { + ALERT(at_aiconsole, "Couldn't allocated Link Lookup Table.\n"); + return; + } + int i; + for (i = 0; i < m_nHashLinks; i++) + { + m_pHashLinks[i] = ENTRY_STATE_EMPTY; + } + + for (i = 0; i < m_cLinks; i++) + { + CLink &link = Link(i); + HashInsert(link.m_iSrcNode, link.m_iDestNode, i); + } +#if 0 + for (i = 0; i < m_cLinks; i++) + { + CLink &link = Link(i); + int iKey; + HashSearch(link.m_iSrcNode, link.m_iDestNode, iKey); + if (iKey != i) + { + ALERT(at_aiconsole, "HashLinks don't match (%d versus %d)\n", i, iKey); + } + } +#endif +} + +void CGraph::BuildRegionTables(void) +{ + if (m_di) free(m_di); + + // Go ahead and setup for range searching the nodes for FindNearestNodes + // + m_di = (DIST_INFO *)calloc(sizeof(DIST_INFO), m_cNodes); + if (!m_di) + { + ALERT(at_aiconsole, "Couldn't allocated node ordering array.\n"); + return; + } + + // Calculate regions for all the nodes. + // + // + int i; + for (i = 0; i < 3; i++) + { + m_RegionMin[i] = 999999999.0; // just a big number out there; + m_RegionMax[i] = -999999999.0; // just a big number out there; + } + for (i = 0; i < m_cNodes; i++) + { + if (m_pNodes[i].m_vecOrigin.x < m_RegionMin[0]) + m_RegionMin[0] = m_pNodes[i].m_vecOrigin.x; + if (m_pNodes[i].m_vecOrigin.y < m_RegionMin[1]) + m_RegionMin[1] = m_pNodes[i].m_vecOrigin.y; + if (m_pNodes[i].m_vecOrigin.z < m_RegionMin[2]) + m_RegionMin[2] = m_pNodes[i].m_vecOrigin.z; + + if (m_pNodes[i].m_vecOrigin.x > m_RegionMax[0]) + m_RegionMax[0] = m_pNodes[i].m_vecOrigin.x; + if (m_pNodes[i].m_vecOrigin.y > m_RegionMax[1]) + m_RegionMax[1] = m_pNodes[i].m_vecOrigin.y; + if (m_pNodes[i].m_vecOrigin.z > m_RegionMax[2]) + m_RegionMax[2] = m_pNodes[i].m_vecOrigin.z; + } + for (i = 0; i < m_cNodes; i++) + { + m_pNodes[i].m_Region[0] = CALC_RANGE(m_pNodes[i].m_vecOrigin.x, m_RegionMin[0], m_RegionMax[0]); + m_pNodes[i].m_Region[1] = CALC_RANGE(m_pNodes[i].m_vecOrigin.y, m_RegionMin[1], m_RegionMax[1]); + m_pNodes[i].m_Region[2] = CALC_RANGE(m_pNodes[i].m_vecOrigin.z, m_RegionMin[2], m_RegionMax[2]); + } + + for (i = 0; i < 3; i++) + { + int j; + for (j = 0; j < NUM_RANGES; j++) + { + m_RangeStart[i][j] = 255; + m_RangeEnd[i][j] = 0; + } + for (j = 0; j < m_cNodes; j++) + { + m_di[j].m_SortedBy[i] = j; + } + + for (j = 0; j < m_cNodes - 1; j++) + { + int jNode = m_di[j].m_SortedBy[i]; + int jCodeX = m_pNodes[jNode].m_Region[0]; + int jCodeY = m_pNodes[jNode].m_Region[1]; + int jCodeZ = m_pNodes[jNode].m_Region[2]; + int jCode; + switch (i) + { + case 0: + jCode = (jCodeX << 16) + (jCodeY << 8) + jCodeZ; + break; + case 1: + jCode = (jCodeY << 16) + (jCodeZ << 8) + jCodeX; + break; + case 2: + jCode = (jCodeZ << 16) + (jCodeX << 8) + jCodeY; + break; + } + + for (int k = j+1; k < m_cNodes; k++) + { + int kNode = m_di[k].m_SortedBy[i]; + int kCodeX = m_pNodes[kNode].m_Region[0]; + int kCodeY = m_pNodes[kNode].m_Region[1]; + int kCodeZ = m_pNodes[kNode].m_Region[2]; + int kCode; + switch (i) + { + case 0: + kCode = (kCodeX << 16) + (kCodeY << 8) + kCodeZ; + break; + case 1: + kCode = (kCodeY << 16) + (kCodeZ << 8) + kCodeX; + break; + case 2: + kCode = (kCodeZ << 16) + (kCodeX << 8) + kCodeY; + break; + } + + if (kCode < jCode) + { + // Swap j and k entries. + // + int Tmp = m_di[j].m_SortedBy[i]; + m_di[j].m_SortedBy[i] = m_di[k].m_SortedBy[i]; + m_di[k].m_SortedBy[i] = Tmp; + } + } + } + } + + // Generate lookup tables. + // + for (i = 0; i < m_cNodes; i++) + { + int CodeX = m_pNodes[m_di[i].m_SortedBy[0]].m_Region[0]; + int CodeY = m_pNodes[m_di[i].m_SortedBy[1]].m_Region[1]; + int CodeZ = m_pNodes[m_di[i].m_SortedBy[2]].m_Region[2]; + + if (i < m_RangeStart[0][CodeX]) + { + m_RangeStart[0][CodeX] = i; + } + if (i < m_RangeStart[1][CodeY]) + { + m_RangeStart[1][CodeY] = i; + } + if (i < m_RangeStart[2][CodeZ]) + { + m_RangeStart[2][CodeZ] = i; + } + if (m_RangeEnd[0][CodeX] < i) + { + m_RangeEnd[0][CodeX] = i; + } + if (m_RangeEnd[1][CodeY] < i) + { + m_RangeEnd[1][CodeY] = i; + } + if (m_RangeEnd[2][CodeZ] < i) + { + m_RangeEnd[2][CodeZ] = i; + } + } + + // Initialize the cache. + // + memset(m_Cache, 0, sizeof(m_Cache)); +} + +void CGraph :: ComputeStaticRoutingTables( void ) +{ + int nRoutes = m_cNodes*m_cNodes; +#define FROM_TO(x,y) ((x)*m_cNodes+(y)) + short *Routes = new short[nRoutes]; + + int *pMyPath = new int[m_cNodes]; + unsigned short *BestNextNodes = new unsigned short[m_cNodes]; + char *pRoute = new char[m_cNodes*2]; + + + if (Routes && pMyPath && BestNextNodes && pRoute) + { + int nTotalCompressedSize = 0; + for (int iHull = 0; iHull < MAX_NODE_HULLS; iHull++) + { + for (int iCap = 0; iCap < 2; iCap++) + { + int iCapMask; + switch (iCap) + { + case 0: + iCapMask = 0; + break; + + case 1: + iCapMask = bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE; + break; + } + + + // Initialize Routing table to uncalculated. + // + int iFrom; + for (iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = 0; iTo < m_cNodes; iTo++) + { + Routes[FROM_TO(iFrom, iTo)] = -1; + } + } + + for (iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = m_cNodes-1; iTo >= 0; iTo--) + { + if (Routes[FROM_TO(iFrom, iTo)] != -1) continue; + + int cPathSize = FindShortestPath(pMyPath, iFrom, iTo, iHull, iCapMask); + + // Use the computed path to update the routing table. + // + if (cPathSize > 1) + { + for (int iNode = 0; iNode < cPathSize-1; iNode++) + { + int iStart = pMyPath[iNode]; + int iNext = pMyPath[iNode+1]; + for (int iNode1 = iNode+1; iNode1 < cPathSize; iNode1++) + { + int iEnd = pMyPath[iNode1]; + Routes[FROM_TO(iStart, iEnd)] = iNext; + } + } +#if 0 + // Well, at first glance, this should work, but actually it's safer + // to be told explictly that you can take a series of node in a + // particular direction. Some links don't appear to have links in + // the opposite direction. + // + for (iNode = cPathSize-1; iNode >= 1; iNode--) + { + int iStart = pMyPath[iNode]; + int iNext = pMyPath[iNode-1]; + for (int iNode1 = iNode-1; iNode1 >= 0; iNode1--) + { + int iEnd = pMyPath[iNode1]; + Routes[FROM_TO(iStart, iEnd)] = iNext; + } + } +#endif + } + else + { + Routes[FROM_TO(iFrom, iTo)] = iFrom; + Routes[FROM_TO(iTo, iFrom)] = iTo; + } + } + } + + for (iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = 0; iTo < m_cNodes; iTo++) + { + BestNextNodes[iTo] = Routes[FROM_TO(iFrom, iTo)]; + } + + // Compress this node's routing table. + // + int iLastNode = 9999999; // just really big. + int cSequence = 0; + int cRepeats = 0; + int CompressedSize = 0; + char *p = pRoute; + for (int i = 0; i < m_cNodes; i++) + { + BOOL CanRepeat = ((BestNextNodes[i] == iLastNode) && cRepeats < 127); + BOOL CanSequence = (BestNextNodes[i] == i && cSequence < 128); + + if (cRepeats) + { + if (CanRepeat) + { + cRepeats++; + } + else + { + // Emit the repeat phrase. + // + CompressedSize += 2; // (count-1, iLastNode-i) + *p++ = cRepeats - 1; + int a = iLastNode - iFrom; + int b = iLastNode - iFrom + m_cNodes; + int c = iLastNode - iFrom - m_cNodes; + if (-128 <= a && a <= 127) + { + *p++ = a; + } + else if (-128 <= b && b <= 127) + { + *p++ = b; + } + else if (-128 <= c && c <= 127) + { + *p++ = c; + } + else + { + ALERT( at_aiconsole, "Nodes need sorting (%d,%d)!\n", iLastNode, iFrom); + } + cRepeats = 0; + + if (CanSequence) + { + // Start a sequence. + // + cSequence++; + } + else + { + // Start another repeat. + // + cRepeats++; + } + } + } + else if (cSequence) + { + if (CanSequence) + { + cSequence++; + } + else + { + // It may be advantageous to combine + // a single-entry sequence phrase with the + // next repeat phrase. + // + if (cSequence == 1 && CanRepeat) + { + // Combine with repeat phrase. + // + cRepeats = 2; + cSequence = 0; + } + else + { + // Emit the sequence phrase. + // + CompressedSize += 1; // (-count) + *p++ = -cSequence; + cSequence = 0; + + // Start a repeat sequence. + // + cRepeats++; + } + } + } + else + { + if (CanSequence) + { + // Start a sequence phrase. + // + cSequence++; + } + else + { + // Start a repeat sequence. + // + cRepeats++; + } + } + iLastNode = BestNextNodes[i]; + } + if (cRepeats) + { + // Emit the repeat phrase. + // + CompressedSize += 2; + *p++ = cRepeats - 1; +#if 0 + iLastNode = iFrom + *pRoute; + if (iLastNode >= m_cNodes) iLastNode -= m_cNodes; + else if (iLastNode < 0) iLastNode += m_cNodes; +#endif + int a = iLastNode - iFrom; + int b = iLastNode - iFrom + m_cNodes; + int c = iLastNode - iFrom - m_cNodes; + if (-128 <= a && a <= 127) + { + *p++ = a; + } + else if (-128 <= b && b <= 127) + { + *p++ = b; + } + else if (-128 <= c && c <= 127) + { + *p++ = c; + } + else + { + ALERT( at_aiconsole, "Nodes need sorting (%d,%d)!\n", iLastNode, iFrom); + } + } + if (cSequence) + { + // Emit the Sequence phrase. + // + CompressedSize += 1; + *p++ = -cSequence; + } + + // Go find a place to store this thing and point to it. + // + int nRoute = p - pRoute; + if (m_pRouteInfo) + { + int i; + for (i = 0; i < m_nRouteInfo - nRoute; i++) + { + if (memcmp(m_pRouteInfo + i, pRoute, nRoute) == 0) + { + break; + } + } + if (i < m_nRouteInfo - nRoute) + { + m_pNodes[ iFrom ].m_pNextBestNode[iHull][iCap] = i; + } + else + { + char *Tmp = (char *)calloc(sizeof(char), (m_nRouteInfo + nRoute)); + memcpy(Tmp, m_pRouteInfo, m_nRouteInfo); + free(m_pRouteInfo); + m_pRouteInfo = Tmp; + memcpy(m_pRouteInfo + m_nRouteInfo, pRoute, nRoute); + m_pNodes[ iFrom ].m_pNextBestNode[iHull][iCap] = m_nRouteInfo; + m_nRouteInfo += nRoute; + nTotalCompressedSize += CompressedSize; + } + } + else + { + m_nRouteInfo = nRoute; + m_pRouteInfo = (char *)calloc(sizeof(char), nRoute); + memcpy(m_pRouteInfo, pRoute, nRoute); + m_pNodes[ iFrom ].m_pNextBestNode[iHull][iCap] = 0; + nTotalCompressedSize += CompressedSize; + } + } + } + } + ALERT( at_aiconsole, "Size of Routes = %d\n", nTotalCompressedSize); + } + if (Routes) delete Routes; + if (BestNextNodes) delete BestNextNodes; + if (pRoute) delete pRoute; + if (pMyPath) delete pMyPath; + Routes = 0; + BestNextNodes = 0; + pRoute = 0; + pMyPath = 0; + +#if 0 + TestRoutingTables(); +#endif + m_fRoutingComplete = TRUE; +} + +// Test those routing tables. Doesn't really work, yet. +// +void CGraph :: TestRoutingTables( void ) +{ + int *pMyPath = new int[m_cNodes]; + int *pMyPath2 = new int[m_cNodes]; + if (pMyPath && pMyPath2) + { + for (int iHull = 0; iHull < MAX_NODE_HULLS; iHull++) + { + for (int iCap = 0; iCap < 2; iCap++) + { + int iCapMask; + switch (iCap) + { + case 0: + iCapMask = 0; + break; + + case 1: + iCapMask = bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE; + break; + } + + for (int iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = 0; iTo < m_cNodes; iTo++) + { + m_fRoutingComplete = FALSE; + int cPathSize1 = FindShortestPath(pMyPath, iFrom, iTo, iHull, iCapMask); + m_fRoutingComplete = TRUE; + int cPathSize2 = FindShortestPath(pMyPath2, iFrom, iTo, iHull, iCapMask); + + // Unless we can look at the entire path, we can verify that it's correct. + // + if (cPathSize2 == MAX_PATH_SIZE) continue; + + // Compare distances. + // +#if 1 + float flDistance1 = 0.0; + int i; + for (i = 0; i < cPathSize1-1; i++) + { + // Find the link from pMyPath[i] to pMyPath[i+1] + // + if (pMyPath[i] == pMyPath[i+1]) continue; + int iVisitNode; + BOOL bFound = FALSE; + for (int iLink = 0; iLink < m_pNodes[pMyPath[i]].m_cNumLinks; iLink++) + { + iVisitNode = INodeLink ( pMyPath[i], iLink ); + if (iVisitNode == pMyPath[i+1]) + { + flDistance1 += m_pLinkPool[ m_pNodes[ pMyPath[i] ].m_iFirstLink + iLink].m_flWeight; + bFound = TRUE; + break; + } + } + if (!bFound) + { + ALERT(at_aiconsole, "No link.\n"); + } + } + + float flDistance2 = 0.0; + for (i = 0; i < cPathSize2-1; i++) + { + // Find the link from pMyPath2[i] to pMyPath2[i+1] + // + if (pMyPath2[i] == pMyPath2[i+1]) continue; + int iVisitNode; + BOOL bFound = FALSE; + for (int iLink = 0; iLink < m_pNodes[pMyPath2[i]].m_cNumLinks; iLink++) + { + iVisitNode = INodeLink ( pMyPath2[i], iLink ); + if (iVisitNode == pMyPath2[i+1]) + { + flDistance2 += m_pLinkPool[ m_pNodes[ pMyPath2[i] ].m_iFirstLink + iLink].m_flWeight; + bFound = TRUE; + break; + } + } + if (!bFound) + { + ALERT(at_aiconsole, "No link.\n"); + } + } + if (fabs(flDistance1 - flDistance2) > 0.10) + { +#else + if (cPathSize1 != cPathSize2 || memcmp(pMyPath, pMyPath2, sizeof(int)*cPathSize1) != 0) + { +#endif + ALERT(at_aiconsole, "Routing is inconsistent!!!\n"); + ALERT(at_aiconsole, "(%d to %d |%d/%d)1:", iFrom, iTo, iHull, iCap); + for (int i = 0; i < cPathSize1; i++) + { + ALERT(at_aiconsole, "%d ", pMyPath[i]); + } + ALERT(at_aiconsole, "\n(%d to %d |%d/%d)2:", iFrom, iTo, iHull, iCap); + for (i = 0; i < cPathSize2; i++) + { + ALERT(at_aiconsole, "%d ", pMyPath2[i]); + } + ALERT(at_aiconsole, "\n"); + m_fRoutingComplete = FALSE; + cPathSize1 = FindShortestPath(pMyPath, iFrom, iTo, iHull, iCapMask); + m_fRoutingComplete = TRUE; + cPathSize2 = FindShortestPath(pMyPath2, iFrom, iTo, iHull, iCapMask); + goto EnoughSaid; + } + } + } + } + } + } + +EnoughSaid: + + if (pMyPath) delete pMyPath; + if (pMyPath2) delete pMyPath2; + pMyPath = 0; + pMyPath2 = 0; +} + + + + + + + + + +//========================================================= +// CNodeViewer - Draws a graph of the shorted path from all nodes +// to current location (typically the player). It then draws +// as many connects as it can per frame, trying not to overflow the buffer +//========================================================= +class CNodeViewer : public CBaseEntity +{ +public: + void Spawn( void ); + + int m_iBaseNode; + int m_iDraw; + int m_nVisited; + int m_aFrom[128]; + int m_aTo[128]; + int m_iHull; + int m_afNodeType; + Vector m_vecColor; + + void FindNodeConnections( int iNode ); + void AddNode( int iFrom, int iTo ); + void EXPORT DrawThink( void ); + +}; +LINK_ENTITY_TO_CLASS( node_viewer, CNodeViewer ); +LINK_ENTITY_TO_CLASS( node_viewer_human, CNodeViewer ); +LINK_ENTITY_TO_CLASS( node_viewer_fly, CNodeViewer ); +LINK_ENTITY_TO_CLASS( node_viewer_large, CNodeViewer ); + +void CNodeViewer::Spawn( ) +{ + if ( !WorldGraph.m_fGraphPresent || !WorldGraph.m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available or built + ALERT ( at_console, "Graph not ready!\n" ); + UTIL_Remove( this ); + return; + } + + + if (FClassnameIs( pev, "node_viewer_fly")) + { + m_iHull = NODE_FLY_HULL; + m_afNodeType = bits_NODE_AIR; + m_vecColor = Vector( 160, 100, 255 ); + } + else if (FClassnameIs( pev, "node_viewer_large")) + { + m_iHull = NODE_LARGE_HULL; + m_afNodeType = bits_NODE_LAND | bits_NODE_WATER; + m_vecColor = Vector( 100, 255, 160 ); + } + else + { + m_iHull = NODE_HUMAN_HULL; + m_afNodeType = bits_NODE_LAND | bits_NODE_WATER; + m_vecColor = Vector( 255, 160, 100 ); + } + + + m_iBaseNode = WorldGraph.FindNearestNode ( pev->origin, m_afNodeType ); + + if ( m_iBaseNode < 0 ) + { + ALERT( at_console, "No nearby node\n" ); + return; + } + + m_nVisited = 0; + + ALERT( at_aiconsole, "basenode %d\n", m_iBaseNode ); + + if (WorldGraph.m_cNodes < 128) + { + for (int i = 0; i < WorldGraph.m_cNodes; i++) + { + AddNode( i, WorldGraph.NextNodeInRoute( i, m_iBaseNode, m_iHull, 0 )); + } + } + else + { + // do a depth traversal + FindNodeConnections( m_iBaseNode ); + + int start = 0; + int end; + do { + end = m_nVisited; + // ALERT( at_console, "%d :", m_nVisited ); + for (end = m_nVisited; start < end; start++) + { + FindNodeConnections( m_aFrom[start] ); + FindNodeConnections( m_aTo[start] ); + } + } while (end != m_nVisited); + } + + ALERT( at_aiconsole, "%d nodes\n", m_nVisited ); + + m_iDraw = 0; + SetThink( &CNodeViewer::DrawThink ); + pev->nextthink = gpGlobals->time; +} + + +void CNodeViewer :: FindNodeConnections ( int iNode ) +{ + AddNode( iNode, WorldGraph.NextNodeInRoute( iNode, m_iBaseNode, m_iHull, 0 )); + for ( int i = 0 ; i < WorldGraph.m_pNodes[ iNode ].m_cNumLinks ; i++ ) + { + CLink *pToLink = &WorldGraph.NodeLink( iNode, i); + AddNode( pToLink->m_iDestNode, WorldGraph.NextNodeInRoute( pToLink->m_iDestNode, m_iBaseNode, m_iHull, 0 )); + } +} + +void CNodeViewer::AddNode( int iFrom, int iTo ) +{ + if (m_nVisited >= 128) + { + return; + } + else + { + if (iFrom == iTo) + return; + + for (int i = 0; i < m_nVisited; i++) + { + if (m_aFrom[i] == iFrom && m_aTo[i] == iTo) + return; + if (m_aFrom[i] == iTo && m_aTo[i] == iFrom) + return; + } + m_aFrom[m_nVisited] = iFrom; + m_aTo[m_nVisited] = iTo; + m_nVisited++; + } +} + + +void CNodeViewer :: DrawThink( void ) +{ + pev->nextthink = gpGlobals->time; + + for (int i = 0; i < 10; i++) + { + if (m_iDraw == m_nVisited) + { + UTIL_Remove( this ); + return; + } + + extern short g_sModelIndexLaser; + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aFrom[m_iDraw] ].m_vecOrigin.x ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aFrom[m_iDraw] ].m_vecOrigin.y ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aFrom[m_iDraw] ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( WorldGraph.m_pNodes[ m_aTo[m_iDraw] ].m_vecOrigin.x ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aTo[m_iDraw] ].m_vecOrigin.y ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aTo[m_iDraw] ].m_vecOrigin.z + NODE_HEIGHT ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 250 ); // life + WRITE_BYTE( 40 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( m_vecColor.x ); // r, g, b + WRITE_BYTE( m_vecColor.y ); // r, g, b + WRITE_BYTE( m_vecColor.z ); // r, g, b + WRITE_BYTE( 128 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + m_iDraw++; + } +} + + diff --git a/dmc/dlls/nodes.h b/dmc/dlls/nodes.h new file mode 100644 index 0000000..269fe42 --- /dev/null +++ b/dmc/dlls/nodes.h @@ -0,0 +1,374 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// nodes.h +//========================================================= + +//========================================================= +// DEFINE +//========================================================= +#define MAX_STACK_NODES 100 +#define NO_NODE -1 +#define MAX_NODE_HULLS 4 + +#define bits_NODE_LAND ( 1 << 0 ) // Land node, so nudge if necessary. +#define bits_NODE_AIR ( 1 << 1 ) // Air node, don't nudge. +#define bits_NODE_WATER ( 1 << 2 ) // Water node, don't nudge. +#define bits_NODE_GROUP_REALM (bits_NODE_LAND | bits_NODE_AIR | bits_NODE_WATER) + +//========================================================= +// Instance of a node. +//========================================================= +class CNode +{ +public: + Vector m_vecOrigin;// location of this node in space + Vector m_vecOriginPeek; // location of this node (LAND nodes are NODE_HEIGHT higher). + BYTE m_Region[3]; // Which of 256 regions do each of the coordinate belong? + int m_afNodeInfo;// bits that tell us more about this location + + int m_cNumLinks; // how many links this node has + int m_iFirstLink;// index of this node's first link in the link pool. + + // Where to start looking in the compressed routing table (offset into m_pRouteInfo). + // (4 hull sizes -- smallest to largest + fly/swim), and secondly, door capability. + // + int m_pNextBestNode[MAX_NODE_HULLS][2]; + + // Used in finding the shortest path. m_fClosestSoFar is -1 if not visited. + // Then it is the distance to the source. If another path uses this node + // and has a closer distance, then m_iPreviousNode is also updated. + // + float m_flClosestSoFar; // Used in finding the shortest path. + int m_iPreviousNode; + + short m_sHintType;// there is something interesting in the world at this node's position + short m_sHintActivity;// there is something interesting in the world at this node's position + float m_flHintYaw;// monster on this node should face this yaw to face the hint. +}; + +//========================================================= +// CLink - A link between 2 nodes +//========================================================= +#define bits_LINK_SMALL_HULL ( 1 << 0 )// headcrab box can fit through this connection +#define bits_LINK_HUMAN_HULL ( 1 << 1 )// player box can fit through this connection +#define bits_LINK_LARGE_HULL ( 1 << 2 )// big box can fit through this connection +#define bits_LINK_FLY_HULL ( 1 << 3 )// a flying big box can fit through this connection +#define bits_LINK_DISABLED ( 1 << 4 )// link is not valid when the set + +#define NODE_SMALL_HULL 0 +#define NODE_HUMAN_HULL 1 +#define NODE_LARGE_HULL 2 +#define NODE_FLY_HULL 3 + +class CLink +{ +public: + int m_iSrcNode;// the node that 'owns' this link ( keeps us from having to make reverse lookups ) + int m_iDestNode;// the node on the other end of the link. + + entvars_t *m_pLinkEnt;// the entity that blocks this connection (doors, etc) + + // m_szLinkEntModelname is not necessarily NULL terminated (so we can store it in a more alignment-friendly 4 bytes) + char m_szLinkEntModelname[ 4 ];// the unique name of the brush model that blocks the connection (this is kept for save/restore) + + int m_afLinkInfo;// information about this link + float m_flWeight;// length of the link line segment +}; + + +typedef struct +{ + int m_SortedBy[3]; + int m_CheckedEvent; +} DIST_INFO; + +typedef struct +{ + Vector v; + short n; // Nearest node or -1 if no node found. +} CACHE_ENTRY; + +//========================================================= +// CGraph +//========================================================= +#define GRAPH_VERSION (int)16// !!!increment this whever graph/node/link classes change, to obsolesce older disk files. +class CGraph +{ +public: + +// the graph has two flags, and should not be accessed unless both flags are TRUE! + BOOL m_fGraphPresent;// is the graph in memory? + BOOL m_fGraphPointersSet;// are the entity pointers for the graph all set? + BOOL m_fRoutingComplete; // are the optimal routes computed, yet? + + CNode *m_pNodes;// pointer to the memory block that contains all node info + CLink *m_pLinkPool;// big list of all node connections + char *m_pRouteInfo; // compressed routing information the nodes use. + + int m_cNodes;// total number of nodes + int m_cLinks;// total number of links + int m_nRouteInfo; // size of m_pRouteInfo in bytes. + + // Tables for making nearest node lookup faster. SortedBy provided nodes in a + // order of a particular coordinate. Instead of doing a binary search, RangeStart + // and RangeEnd let you get to the part of SortedBy that you are interested in. + // + // Once you have a point of interest, the only way you'll find a closer point is + // if at least one of the coordinates is closer than the ones you have now. So we + // search each range. After the search is exhausted, we know we have the closest + // node. + // +#define CACHE_SIZE 128 +#define NUM_RANGES 256 + DIST_INFO *m_di; // This is m_cNodes long, but the entries don't correspond to CNode entries. + int m_RangeStart[3][NUM_RANGES]; + int m_RangeEnd[3][NUM_RANGES]; + float m_flShortest; + int m_iNearest; + int m_minX, m_minY, m_minZ, m_maxX, m_maxY, m_maxZ; + int m_minBoxX, m_minBoxY, m_minBoxZ, m_maxBoxX, m_maxBoxY, m_maxBoxZ; + int m_CheckedCounter; + float m_RegionMin[3], m_RegionMax[3]; // The range of nodes. + CACHE_ENTRY m_Cache[CACHE_SIZE]; + + + int m_HashPrimes[16]; + short *m_pHashLinks; + int m_nHashLinks; + + + // kinda sleazy. In order to allow variety in active idles for monster groups in a room with more than one node, + // we keep track of the last node we searched from and store it here. Subsequent searches by other monsters will pick + // up where the last search stopped. + int m_iLastActiveIdleSearch; + + // another such system used to track the search for cover nodes, helps greatly with two monsters trying to get to the same node. + int m_iLastCoverSearch; + + // functions to create the graph + int LinkVisibleNodes ( CLink *pLinkPool, FILE *file, int *piBadNode ); + int RejectInlineLinks ( CLink *pLinkPool, FILE *file ); + int FindShortestPath ( int *piPath, int iStart, int iDest, int iHull, int afCapMask); + int FindNearestNode ( const Vector &vecOrigin, CBaseEntity *pEntity ); + int FindNearestNode ( const Vector &vecOrigin, int afNodeTypes ); + //int FindNearestLink ( const Vector &vecTestPoint, int *piNearestLink, BOOL *pfAlongLine ); + float PathLength( int iStart, int iDest, int iHull, int afCapMask ); + int NextNodeInRoute( int iCurrentNode, int iDest, int iHull, int iCap ); + + enum NODEQUERY { NODEGRAPH_DYNAMIC, NODEGRAPH_STATIC }; + // A static query means we're asking about the possiblity of handling this entity at ANY time + // A dynamic query means we're asking about it RIGHT NOW. So we should query the current state + int HandleLinkEnt ( int iNode, entvars_t *pevLinkEnt, int afCapMask, NODEQUERY queryType ); + entvars_t* LinkEntForLink ( CLink *pLink, CNode *pNode ); + void ShowNodeConnections ( int iNode ); + void InitGraph( void ); + int AllocNodes ( void ); + + int CheckNODFile(char *szMapName); + int FLoadGraph(char *szMapName); + int FSaveGraph(char *szMapName); + int FSetGraphPointers(void); + void CheckNode(Vector vecOrigin, int iNode); + + void BuildRegionTables(void); + void ComputeStaticRoutingTables(void); + void TestRoutingTables(void); + + void HashInsert(int iSrcNode, int iDestNode, int iKey); + void HashSearch(int iSrcNode, int iDestNode, int &iKey); + void HashChoosePrimes(int TableSize); + void BuildLinkLookups(void); + + void SortNodes(void); + + int HullIndex( const CBaseEntity *pEntity ); // what hull the monster uses + int NodeType( const CBaseEntity *pEntity ); // what node type the monster uses + inline int CapIndex( int afCapMask ) + { + if (afCapMask & (bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE)) + return 1; + return 0; + } + + + inline CNode &Node( int i ) + { +#ifdef _DEBUG + if ( !m_pNodes || i < 0 || i > m_cNodes ) + ALERT( at_error, "Bad Node!\n" ); +#endif + return m_pNodes[i]; + } + + inline CLink &Link( int i ) + { +#ifdef _DEBUG + if ( !m_pLinkPool || i < 0 || i > m_cLinks ) + ALERT( at_error, "Bad link!\n" ); +#endif + return m_pLinkPool[i]; + } + + inline CLink &NodeLink( int iNode, int iLink ) + { + return Link( Node( iNode ).m_iFirstLink + iLink ); + } + + inline CLink &NodeLink( const CNode &node, int iLink ) + { + return Link( node.m_iFirstLink + iLink ); + } + + inline int INodeLink ( int iNode, int iLink ) + { + return NodeLink( iNode, iLink ).m_iDestNode; + } + +#if 0 + inline CNode &SourceNode( int iNode, int iLink ) + { + return Node( NodeLink( iNode, iLink ).m_iSrcNode ); + } + + inline CNode &DestNode( int iNode, int iLink ) + { + return Node( NodeLink( iNode, iLink ).m_iDestNode ); + } + + inline CNode *PNodeLink ( int iNode, int iLink ) + { + return &DestNode( iNode, iLink ); + } +#endif +}; + +//========================================================= +// Nodes start out as ents in the level. The node graph +// is built, then these ents are discarded. +//========================================================= +class CNodeEnt : public CBaseEntity +{ + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + short m_sHintType; + short m_sHintActivity; +}; + + +//========================================================= +// CStack - last in, first out. +//========================================================= +class CStack +{ +public: + CStack( void ); + void Push( int value ); + int Pop( void ); + int Top( void ); + int Empty( void ) { return m_level==0; } + int Size( void ) { return m_level; } + void CopyToArray ( int *piArray ); + +private: + int m_stack[ MAX_STACK_NODES ]; + int m_level; +}; + + +//========================================================= +// CQueue - first in, first out. +//========================================================= +class CQueue +{ +public: + + CQueue( void );// constructor + inline int Full ( void ) { return ( m_cSize == MAX_STACK_NODES ); } + inline int Empty ( void ) { return ( m_cSize == 0 ); } + //inline int Tail ( void ) { return ( m_queue[ m_tail ] ); } + inline int Size ( void ) { return ( m_cSize ); } + void Insert( int, float ); + int Remove( float & ); + +private: + int m_cSize; + struct tag_QUEUE_NODE + { + int Id; + float Priority; + } m_queue[ MAX_STACK_NODES ]; + int m_head; + int m_tail; +}; + +//========================================================= +// CQueuePriority - Priority queue (smallest item out first). +// +//========================================================= +class CQueuePriority +{ +public: + + CQueuePriority( void );// constructor + inline int Full ( void ) { return ( m_cSize == MAX_STACK_NODES ); } + inline int Empty ( void ) { return ( m_cSize == 0 ); } + //inline int Tail ( float & ) { return ( m_queue[ m_tail ].Id ); } + inline int Size ( void ) { return ( m_cSize ); } + void Insert( int, float ); + int Remove( float &); + +private: + int m_cSize; + struct tag_HEAP_NODE + { + int Id; + float Priority; + } m_heap[ MAX_STACK_NODES ]; + void Heap_SiftDown(int); + void Heap_SiftUp(void); + +}; + +//========================================================= +// hints - these MUST coincide with the HINTS listed under +// info_node in the FGD file! +//========================================================= +enum +{ + HINT_NONE = 0, + HINT_WORLD_DOOR, + HINT_WORLD_WINDOW, + HINT_WORLD_BUTTON, + HINT_WORLD_MACHINERY, + HINT_WORLD_LEDGE, + HINT_WORLD_LIGHT_SOURCE, + HINT_WORLD_HEAT_SOURCE, + HINT_WORLD_BLINKING_LIGHT, + HINT_WORLD_BRIGHT_COLORS, + HINT_WORLD_HUMAN_BLOOD, + HINT_WORLD_ALIEN_BLOOD, + + HINT_TACTICAL_EXIT = 100, + HINT_TACTICAL_VANTAGE, + HINT_TACTICAL_AMBUSH, + + HINT_STUKA_PERCH = 300, + HINT_STUKA_LANDING, +}; + +extern CGraph WorldGraph; diff --git a/dmc/dlls/observer.cpp b/dmc/dlls/observer.cpp new file mode 100644 index 0000000..2fe0ee2 --- /dev/null +++ b/dmc/dlls/observer.cpp @@ -0,0 +1,142 @@ +//=========== (C) Copyright 1996-2002, Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: Functionality for the observer chase camera +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "pm_shared.h" + +// Find the next client in the game for this player to spectate +void CBasePlayer::Observer_FindNextPlayer() +{ + // MOD AUTHORS: Modify the logic of this function if you want to restrict the observer to watching + // only a subset of the players. e.g. Make it check the target's team. + + CBaseEntity *client = m_hObserverTarget; + while ( (client = (CBaseEntity*)UTIL_FindEntityByClassname( client, "player" )) != m_hObserverTarget ) + { + if ( !client ) + continue; + if ( !client->pev ) + continue; + if ( client == this ) + continue; + + // Add checks on target here. + + m_hObserverTarget = client; + break; + } + + // Did we find a target? + if ( m_hObserverTarget ) + { + // Store the target in pev so the physics DLL can get to it + if (pev->iuser1 != OBS_ROAMING) + pev->iuser2 = ENTINDEX( m_hObserverTarget->edict() ); + // Move to the target + UTIL_SetOrigin( pev, m_hObserverTarget->pev->origin ); + + ALERT( at_console, "Now Tracking %s\n", STRING( m_hObserverTarget->pev->classname ) ); + } + else + { + ALERT( at_console, "No observer targets.\n" ); + } +} + +// Handle buttons in observer mode +void CBasePlayer::Observer_HandleButtons() +{ + // Slow down mouse clicks + if ( m_flNextObserverInput > gpGlobals->time ) + return; + + // Jump changes from modes: Chase to Roaming + if ( m_afButtonPressed & IN_JUMP ) + { + if ( pev->iuser1 == OBS_CHASE_LOCKED ) + Observer_SetMode( OBS_CHASE_FREE ); + + else if ( pev->iuser1 == OBS_CHASE_FREE ) + Observer_SetMode( OBS_ROAMING ); + + else if ( pev->iuser1 == OBS_ROAMING ) + Observer_SetMode( OBS_IN_EYE ); + + else if ( pev->iuser1 == OBS_IN_EYE ) + Observer_SetMode( OBS_MAP_FREE ); + + else if ( pev->iuser1 == OBS_MAP_FREE ) + Observer_SetMode( OBS_MAP_CHASE ); + + else + Observer_SetMode( OBS_CHASE_FREE ); // don't use OBS_CHASE_LOCKED anymore + + m_flNextObserverInput = gpGlobals->time + 0.2; + } + + // Attack moves to the next player + if ( m_afButtonPressed & IN_ATTACK ) + { + Observer_FindNextPlayer(); + + m_flNextObserverInput = gpGlobals->time + 0.2; + } + +} + +// Attempt to change the observer mode +void CBasePlayer::Observer_SetMode( int iMode ) +{ + // Just abort if we're changing to the mode we're already in + if ( iMode == pev->iuser1 ) + return; + + // is valid mode ? + if ( iMode < OBS_CHASE_LOCKED || iMode > OBS_MAP_CHASE ) + iMode = OBS_IN_EYE; // now it is + + // if we are not roaming, we need a valid target to track + if ( (iMode != OBS_ROAMING) && (m_hObserverTarget == NULL) ) + { + Observer_FindNextPlayer(); + + // if we didn't find a valid target switch to roaming + if (m_hObserverTarget == NULL) + { + ClientPrint( pev, HUD_PRINTCENTER, "#Spec_NoTarget" ); + iMode = OBS_ROAMING; + } + } + + // set spectator mode + pev->iuser1 = iMode; + + // set target if not roaming + if (iMode == OBS_ROAMING) + pev->iuser2 = 0; + else + pev->iuser2 = ENTINDEX( m_hObserverTarget->edict() ); + + // print spepctaor mode on client screen + + char modemsg[16]; + sprintf(modemsg,"#Spec_Mode%i", iMode); + ClientPrint( pev, HUD_PRINTCENTER, modemsg ); +} diff --git a/dmc/dlls/pathcorner.cpp b/dmc/dlls/pathcorner.cpp new file mode 100644 index 0000000..b2c1e3f --- /dev/null +++ b/dmc/dlls/pathcorner.cpp @@ -0,0 +1,428 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ========================== PATH_CORNER =========================== +// + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "trains.h" +#include "saverestore.h" + +class CPathCorner : public CPointEntity +{ +public: + void Spawn( ); + void KeyValue( KeyValueData* pkvd ); + float GetDelay( void ) { return m_flWait; } +// void Touch( CBaseEntity *pOther ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + float m_flWait; +}; + +LINK_ENTITY_TO_CLASS( path_corner, CPathCorner ); + +// Global Savedata for Delay +TYPEDESCRIPTION CPathCorner::m_SaveData[] = +{ + DEFINE_FIELD( CPathCorner, m_flWait, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CPathCorner, CPointEntity ); + +// +// Cache user-entity-field values until spawn is called. +// +void CPathCorner :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CPathCorner :: Spawn( ) +{ + ASSERTSZ(!FStringNull(pev->targetname), "path_corner without a targetname"); +} + +#if 0 +void CPathCorner :: Touch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + + if ( FBitSet ( pevToucher->flags, FL_MONSTER ) ) + {// monsters don't navigate path corners based on touch anymore + return; + } + + // If OTHER isn't explicitly looking for this path_corner, bail out + if ( pOther->m_pGoalEnt != this ) + { + return; + } + + // If OTHER has an enemy, this touch is incidental, ignore + if ( !FNullEnt(pevToucher->enemy) ) + { + return; // fighting, not following a path + } + + // UNDONE: support non-zero flWait + /* + if (m_flWait != 0) + ALERT(at_warning, "Non-zero path-cornder waits NYI"); + */ + + // Find the next "stop" on the path, make it the goal of the "toucher". + if (FStringNull(pev->target)) + { + ALERT(at_warning, "PathCornerTouch: no next stop specified"); + } + + pOther->m_pGoalEnt = CBaseEntity::Instance( FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(pev->target) ) ); + + // If "next spot" was not found (does not exist - level design error) + if ( !pOther->m_pGoalEnt ) + { + ALERT(at_console, "PathCornerTouch--%s couldn't find next stop in path: %s", STRING(pev->classname), STRING(pev->target)); + return; + } + + // Turn towards the next stop in the path. + pevToucher->ideal_yaw = UTIL_VecToYaw ( pOther->m_pGoalEnt->pev->origin - pevToucher->origin ); +} +#endif + + + +TYPEDESCRIPTION CPathTrack::m_SaveData[] = +{ + DEFINE_FIELD( CPathTrack, m_length, FIELD_FLOAT ), + DEFINE_FIELD( CPathTrack, m_pnext, FIELD_CLASSPTR ), + DEFINE_FIELD( CPathTrack, m_paltpath, FIELD_CLASSPTR ), + DEFINE_FIELD( CPathTrack, m_pprevious, FIELD_CLASSPTR ), + DEFINE_FIELD( CPathTrack, m_altName, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CPathTrack, CBaseEntity ); +LINK_ENTITY_TO_CLASS( path_track, CPathTrack ); + +// +// Cache user-entity-field values until spawn is called. +// +void CPathTrack :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "altpath")) + { + m_altName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CPathTrack :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int on; + + // Use toggles between two paths + if ( m_paltpath ) + { + on = !FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ); + if ( ShouldToggle( useType, on ) ) + { + if ( on ) + SetBits( pev->spawnflags, SF_PATH_ALTERNATE ); + else + ClearBits( pev->spawnflags, SF_PATH_ALTERNATE ); + } + } + else // Use toggles between enabled/disabled + { + on = !FBitSet( pev->spawnflags, SF_PATH_DISABLED ); + + if ( ShouldToggle( useType, on ) ) + { + if ( on ) + SetBits( pev->spawnflags, SF_PATH_DISABLED ); + else + ClearBits( pev->spawnflags, SF_PATH_DISABLED ); + } + } +} + + +void CPathTrack :: Link( void ) +{ + edict_t *pentTarget; + + if ( !FStringNull(pev->target) ) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) ); + if ( !FNullEnt(pentTarget) ) + { + m_pnext = CPathTrack::Instance( pentTarget ); + + if ( m_pnext ) // If no next pointer, this is the end of a path + { + m_pnext->SetPrevious( this ); + } + } + else + ALERT( at_console, "Dead end link %s\n", STRING(pev->target) ); + } + + // Find "alternate" path + if ( m_altName ) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_altName) ); + if ( !FNullEnt(pentTarget) ) + { + m_paltpath = CPathTrack::Instance( pentTarget ); + + if ( m_paltpath ) // If no next pointer, this is the end of a path + { + m_paltpath->SetPrevious( this ); + } + } + } +} + + +void CPathTrack :: Spawn( void ) +{ + pev->solid = SOLID_TRIGGER; + UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8)); + + m_pnext = NULL; + m_pprevious = NULL; +// DEBUGGING CODE +#if PATH_SPARKLE_DEBUG + SetThink( Sparkle ); + pev->nextthink = gpGlobals->time + 0.5; +#endif +} + + +void CPathTrack::Activate( void ) +{ + if ( !FStringNull( pev->targetname ) ) // Link to next, and back-link + Link(); +} + +CPathTrack *CPathTrack :: ValidPath( CPathTrack *ppath, int testFlag ) +{ + if ( !ppath ) + return NULL; + + if ( testFlag && FBitSet( ppath->pev->spawnflags, SF_PATH_DISABLED ) ) + return NULL; + + return ppath; +} + + +void CPathTrack :: Project( CPathTrack *pstart, CPathTrack *pend, Vector *origin, float dist ) +{ + if ( pstart && pend ) + { + Vector dir = (pend->pev->origin - pstart->pev->origin); + dir = dir.Normalize(); + *origin = pend->pev->origin + dir * dist; + } +} + +CPathTrack *CPathTrack::GetNext( void ) +{ + if ( m_paltpath && FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ) && !FBitSet( pev->spawnflags, SF_PATH_ALTREVERSE ) ) + return m_paltpath; + + return m_pnext; +} + + + +CPathTrack *CPathTrack::GetPrevious( void ) +{ + if ( m_paltpath && FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ) && FBitSet( pev->spawnflags, SF_PATH_ALTREVERSE ) ) + return m_paltpath; + + return m_pprevious; +} + + + +void CPathTrack::SetPrevious( CPathTrack *pprev ) +{ + // Only set previous if this isn't my alternate path + if ( pprev && !FStrEq( STRING(pprev->pev->targetname), STRING(m_altName) ) ) + m_pprevious = pprev; +} + + +// Assumes this is ALWAYS enabled +CPathTrack *CPathTrack :: LookAhead( Vector *origin, float dist, int move ) +{ + CPathTrack *pcurrent; + float originalDist = dist; + + pcurrent = this; + Vector currentPos = *origin; + + if ( dist < 0 ) // Travelling backwards through path + { + dist = -dist; + while ( dist > 0 ) + { + Vector dir = pcurrent->pev->origin - currentPos; + float length = dir.Length(); + if ( !length ) + { + if ( !ValidPath(pcurrent->GetPrevious(), move) ) // If there is no previous node, or it's disabled, return now. + { + if ( !move ) + Project( pcurrent->GetNext(), pcurrent, origin, dist ); + return NULL; + } + pcurrent = pcurrent->GetPrevious(); + } + else if ( length > dist ) // enough left in this path to move + { + *origin = currentPos + (dir * (dist / length)); + return pcurrent; + } + else + { + dist -= length; + currentPos = pcurrent->pev->origin; + *origin = currentPos; + if ( !ValidPath(pcurrent->GetPrevious(), move) ) // If there is no previous node, or it's disabled, return now. + return NULL; + + pcurrent = pcurrent->GetPrevious(); + } + } + *origin = currentPos; + return pcurrent; + } + else + { + while ( dist > 0 ) + { + if ( !ValidPath(pcurrent->GetNext(), move) ) // If there is no next node, or it's disabled, return now. + { + if ( !move ) + Project( pcurrent->GetPrevious(), pcurrent, origin, dist ); + return NULL; + } + Vector dir = pcurrent->GetNext()->pev->origin - currentPos; + float length = dir.Length(); + if ( !length && !ValidPath( pcurrent->GetNext()->GetNext(), move ) ) + { + if ( dist == originalDist ) // HACK -- up against a dead end + return NULL; + return pcurrent; + } + if ( length > dist ) // enough left in this path to move + { + *origin = currentPos + (dir * (dist / length)); + return pcurrent; + } + else + { + dist -= length; + currentPos = pcurrent->GetNext()->pev->origin; + pcurrent = pcurrent->GetNext(); + *origin = currentPos; + } + } + *origin = currentPos; + } + + return pcurrent; +} + + +// Assumes this is ALWAYS enabled +CPathTrack *CPathTrack :: Nearest( Vector origin ) +{ + int deadCount; + float minDist, dist; + Vector delta; + CPathTrack *ppath, *pnearest; + + + delta = origin - pev->origin; + delta.z = 0; + minDist = delta.Length(); + pnearest = this; + ppath = GetNext(); + + // Hey, I could use the old 2 racing pointers solution to this, but I'm lazy :) + deadCount = 0; + while ( ppath && ppath != this ) + { + deadCount++; + if ( deadCount > 9999 ) + { + ALERT( at_error, "Bad sequence of path_tracks from %s", STRING(pev->targetname) ); + return NULL; + } + delta = origin - ppath->pev->origin; + delta.z = 0; + dist = delta.Length(); + if ( dist < minDist ) + { + minDist = dist; + pnearest = ppath; + } + ppath = ppath->GetNext(); + } + return pnearest; +} + + +CPathTrack *CPathTrack::Instance( edict_t *pent ) +{ + if ( FClassnameIs( pent, "path_track" ) ) + return (CPathTrack *)GET_PRIVATE(pent); + return NULL; +} + + + // DEBUGGING CODE +#if PATH_SPARKLE_DEBUG +void CPathTrack :: Sparkle( void ) +{ + + pev->nextthink = gpGlobals->time + 0.2; + if ( FBitSet( pev->spawnflags, SF_PATH_DISABLED ) ) + UTIL_ParticleEffect(pev->origin, Vector(0,0,100), 210, 10); + else + UTIL_ParticleEffect(pev->origin, Vector(0,0,100), 84, 10); +} +#endif + diff --git a/dmc/dlls/plane.cpp b/dmc/dlls/plane.cpp new file mode 100644 index 0000000..39a91c3 --- /dev/null +++ b/dmc/dlls/plane.cpp @@ -0,0 +1,60 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "plane.h" + +//========================================================= +// Plane +//========================================================= +CPlane :: CPlane ( void ) +{ + m_fInitialized = FALSE; +} + +//========================================================= +// InitializePlane - Takes a normal for the plane and a +// point on the plane and +//========================================================= +void CPlane :: InitializePlane ( const Vector &vecNormal, const Vector &vecPoint ) +{ + m_vecNormal = vecNormal; + m_flDist = DotProduct ( m_vecNormal, vecPoint ); + m_fInitialized = TRUE; +} + + +//========================================================= +// PointInFront - determines whether the given vector is +// in front of the plane. +//========================================================= +BOOL CPlane :: PointInFront ( const Vector &vecPoint ) +{ + float flFace; + + if ( !m_fInitialized ) + { + return FALSE; + } + + flFace = DotProduct ( m_vecNormal, vecPoint ) - m_flDist; + + if ( flFace >= 0 ) + { + return TRUE; + } + + return FALSE; +} + diff --git a/dmc/dlls/plane.h b/dmc/dlls/plane.h new file mode 100644 index 0000000..af70f1c --- /dev/null +++ b/dmc/dlls/plane.h @@ -0,0 +1,43 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PLANE_H +#define PLANE_H + +//========================================================= +// Plane +//========================================================= +class CPlane +{ +public: + CPlane ( void ); + + //========================================================= + // InitializePlane - Takes a normal for the plane and a + // point on the plane and + //========================================================= + void InitializePlane ( const Vector &vecNormal, const Vector &vecPoint ); + + //========================================================= + // PointInFront - determines whether the given vector is + // in front of the plane. + //========================================================= + BOOL PointInFront ( const Vector &vecPoint ); + + Vector m_vecNormal; + float m_flDist; + BOOL m_fInitialized; +}; + +#endif // PLANE_H diff --git a/dmc/dlls/plats.cpp b/dmc/dlls/plats.cpp new file mode 100644 index 0000000..73872ed --- /dev/null +++ b/dmc/dlls/plats.cpp @@ -0,0 +1,2285 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== plats.cpp ======================================================== + + spawn, think, and touch functions for trains, etc + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "trains.h" +#include "saverestore.h" + +static void PlatSpawnInsideTrigger(entvars_t* pevPlatform); + +#define SF_PLAT_TOGGLE 0x0001 + +class CBasePlatTrain : public CBaseToggle +{ +public: + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + void KeyValue( KeyValueData* pkvd); + void Precache( void ); + + // This is done to fix spawn flag collisions between this class and a derived class + virtual BOOL IsTogglePlat( void ) { return (pev->spawnflags & SF_PLAT_TOGGLE) ? TRUE : FALSE; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + BYTE m_bMoveSnd; // sound a plat makes while moving + BYTE m_bStopSnd; // sound a plat makes when it stops + float m_volume; // Sound volume +}; + +TYPEDESCRIPTION CBasePlatTrain::m_SaveData[] = +{ + DEFINE_FIELD( CBasePlatTrain, m_bMoveSnd, FIELD_CHARACTER ), + DEFINE_FIELD( CBasePlatTrain, m_bStopSnd, FIELD_CHARACTER ), + DEFINE_FIELD( CBasePlatTrain, m_volume, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CBasePlatTrain, CBaseToggle ); + +void CBasePlatTrain :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "lip")) + { + m_flLip = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "height")) + { + m_flHeight = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "rotation")) + { + m_vecFinalAngle.x = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "movesnd")) + { + m_bMoveSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "stopsnd")) + { + m_bStopSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "volume")) + { + m_volume = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +#define noiseMoving noise +#define noiseArrived noise1 + +void CBasePlatTrain::Precache( void ) +{ +// set the plat's "in-motion" sound + switch (m_bMoveSnd) + { + case 0: + pev->noiseMoving = MAKE_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("plats/bigmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/bigmove1.wav"); + break; + case 2: + PRECACHE_SOUND ("plats/bigmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/bigmove2.wav"); + break; + case 3: + PRECACHE_SOUND ("plats/elevmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/elevmove1.wav"); + break; + case 4: + PRECACHE_SOUND ("plats/elevmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/elevmove2.wav"); + break; + case 5: + PRECACHE_SOUND ("plats/elevmove3.wav"); + pev->noiseMoving = MAKE_STRING("plats/elevmove3.wav"); + break; + case 6: + PRECACHE_SOUND ("plats/freightmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/freightmove1.wav"); + break; + case 7: + PRECACHE_SOUND ("plats/freightmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/freightmove2.wav"); + break; + case 8: + PRECACHE_SOUND ("plats/heavymove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/heavymove1.wav"); + break; + case 9: + PRECACHE_SOUND ("plats/rackmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/rackmove1.wav"); + break; + case 10: + PRECACHE_SOUND ("plats/railmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/railmove1.wav"); + break; + case 11: + PRECACHE_SOUND ("plats/squeekmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/squeekmove1.wav"); + break; + case 12: + PRECACHE_SOUND ("plats/talkmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/talkmove1.wav"); + break; + case 13: + PRECACHE_SOUND ("plats/talkmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/talkmove2.wav"); + break; + default: + pev->noiseMoving = MAKE_STRING("common/null.wav"); + break; + } + +// set the plat's 'reached destination' stop sound + switch (m_bStopSnd) + { + case 0: + pev->noiseArrived = MAKE_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("plats/bigstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/bigstop1.wav"); + break; + case 2: + PRECACHE_SOUND ("plats/bigstop2.wav"); + pev->noiseArrived = MAKE_STRING("plats/bigstop2.wav"); + break; + case 3: + PRECACHE_SOUND ("plats/freightstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/freightstop1.wav"); + break; + case 4: + PRECACHE_SOUND ("plats/heavystop2.wav"); + pev->noiseArrived = MAKE_STRING("plats/heavystop2.wav"); + break; + case 5: + PRECACHE_SOUND ("plats/rackstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/rackstop1.wav"); + break; + case 6: + PRECACHE_SOUND ("plats/railstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/railstop1.wav"); + break; + case 7: + PRECACHE_SOUND ("plats/squeekstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/squeekstop1.wav"); + break; + case 8: + PRECACHE_SOUND ("plats/talkstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/talkstop1.wav"); + break; + + default: + pev->noiseArrived = MAKE_STRING("common/null.wav"); + break; + } +} + +// +//====================== PLAT code ==================================================== +// + + +#define noiseMovement noise +#define noiseStopMoving noise1 + +class CFuncPlat : public CBasePlatTrain +{ +public: + void Spawn( void ); + void Precache( void ); + void Setup( void ); + + virtual void Blocked( CBaseEntity *pOther ); + + + void EXPORT PlatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + void EXPORT CallGoDown( void ) { GoDown(); } + void EXPORT CallHitTop( void ) { HitTop(); } + void EXPORT CallHitBottom( void ) { HitBottom(); } + + virtual void GoUp( void ); + virtual void GoDown( void ); + virtual void HitTop( void ); + virtual void HitBottom( void ); +}; +LINK_ENTITY_TO_CLASS( func_plat, CFuncPlat ); + + +// UNDONE: Need to save this!!! It needs class & linkage +class CPlatTrigger : public CBaseEntity +{ +public: + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DONT_SAVE; } + void SpawnInsideTrigger( CFuncPlat *pPlatform ); + void Touch( CBaseEntity *pOther ); + CFuncPlat *m_pPlatform; +}; + + + +/*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER +speed default 150 + +Plats are always drawn in the extended position, so they will light correctly. + +If the plat is the target of another trigger or button, it will start out disabled in +the extended position until it is trigger, when it will lower and become a normal plat. + +If the "height" key is set, that will determine the amount the plat moves, instead of +being implicitly determined by the model's height. + +Set "sounds" to one of the following: +1) base fast +2) chain slow +*/ + +void CFuncPlat :: Setup( void ) +{ + //pev->noiseMovement = MAKE_STRING("plats/platmove1.wav"); + //pev->noiseStopMoving = MAKE_STRING("plats/platstop1.wav"); + + if (m_flTLength == 0) + m_flTLength = 80; + if (m_flTWidth == 0) + m_flTWidth = 10; + + pev->angles = g_vecZero; + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); // set size and link into world + UTIL_SetSize(pev, pev->mins, pev->maxs); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + // vecPosition1 is the top position, vecPosition2 is the bottom + m_vecPosition1 = pev->origin; + m_vecPosition2 = pev->origin; + if (m_flHeight != 0) + m_vecPosition2.z = pev->origin.z - m_flHeight; + else + m_vecPosition2.z = pev->origin.z - pev->size.z + 8; + if (pev->speed == 0) + pev->speed = 150; + + if ( m_volume == 0 ) + m_volume = 0.85; +} + + +void CFuncPlat :: Precache( ) +{ + CBasePlatTrain::Precache(); + //PRECACHE_SOUND("plats/platmove1.wav"); + //PRECACHE_SOUND("plats/platstop1.wav"); + if ( !IsTogglePlat() ) + PlatSpawnInsideTrigger( pev ); // the "start moving" trigger +} + + +void CFuncPlat :: Spawn( ) +{ + Setup(); + + Precache(); + + // If this platform is the target of some button, it starts at the TOP position, + // and is brought down by that button. Otherwise, it starts at BOTTOM. + if ( !FStringNull(pev->targetname) ) + { + UTIL_SetOrigin (pev, m_vecPosition1); + m_toggle_state = TS_AT_TOP; + SetUse( &CFuncPlat::PlatUse ); + } + else + { + UTIL_SetOrigin (pev, m_vecPosition2); + m_toggle_state = TS_AT_BOTTOM; + } +} + + + +static void PlatSpawnInsideTrigger(entvars_t* pevPlatform) +{ + GetClassPtr( (CPlatTrigger *)NULL)->SpawnInsideTrigger( GetClassPtr( (CFuncPlat *)pevPlatform ) ); +} + + +// +// Create a trigger entity for a platform. +// +void CPlatTrigger :: SpawnInsideTrigger( CFuncPlat *pPlatform ) +{ + m_pPlatform = pPlatform; + // Create trigger entity, "point" it at the owning platform, give it a touch method + pev->solid = SOLID_TRIGGER; + pev->movetype = MOVETYPE_NONE; + pev->origin = pPlatform->pev->origin; + + // Establish the trigger field's size + Vector vecTMin = m_pPlatform->pev->mins + Vector ( 25 , 25 , 0 ); + Vector vecTMax = m_pPlatform->pev->maxs + Vector ( 25 , 25 , 8 ); + vecTMin.z = vecTMax.z - ( m_pPlatform->m_vecPosition1.z - m_pPlatform->m_vecPosition2.z + 8 ); + if (m_pPlatform->pev->size.x <= 50) + { + vecTMin.x = (m_pPlatform->pev->mins.x + m_pPlatform->pev->maxs.x) / 2; + vecTMax.x = vecTMin.x + 1; + } + if (m_pPlatform->pev->size.y <= 50) + { + vecTMin.y = (m_pPlatform->pev->mins.y + m_pPlatform->pev->maxs.y) / 2; + vecTMax.y = vecTMin.y + 1; + } + UTIL_SetSize ( pev, vecTMin, vecTMax ); +} + + +// +// When the platform's trigger field is touched, the platform ??? +// +void CPlatTrigger :: Touch( CBaseEntity *pOther ) +{ + // Ignore touches by non-players + entvars_t* pevToucher = pOther->pev; + if ( !FClassnameIs (pevToucher, "player") ) + return; + + // Ignore touches by corpses + if (!pOther->IsAlive()) + return; + + // Make linked platform go up/down. + if (m_pPlatform->m_toggle_state == TS_AT_BOTTOM) + m_pPlatform->GoUp(); + else if (m_pPlatform->m_toggle_state == TS_AT_TOP) + m_pPlatform->pev->nextthink = m_pPlatform->pev->ltime + 1;// delay going down +} + + +// +// Used by SUB_UseTargets, when a platform is the target of a button. +// Start bringing platform down. +// +void CFuncPlat :: PlatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( IsTogglePlat() ) + { + // Top is off, bottom is on + BOOL on = (m_toggle_state == TS_AT_BOTTOM) ? TRUE : FALSE; + + if ( !ShouldToggle( useType, on ) ) + return; + + if (m_toggle_state == TS_AT_TOP) + GoDown(); + else if ( m_toggle_state == TS_AT_BOTTOM ) + GoUp(); + } + else + { + SetUse( NULL ); + + if (m_toggle_state == TS_AT_TOP) + GoDown(); + } +} + + +// +// Platform is at top, now starts moving down. +// +void CFuncPlat :: GoDown( void ) +{ + if(pev->noiseMovement) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_AT_TOP || m_toggle_state == TS_GOING_UP); + m_toggle_state = TS_GOING_DOWN; + SetMoveDone(&CFuncPlat::CallHitBottom); + LinearMove(m_vecPosition2, pev->speed); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncPlat :: HitBottom( void ) +{ + if(pev->noiseMovement) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement)); + + if (pev->noiseStopMoving) + EMIT_SOUND(ENT(pev), CHAN_WEAPON, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_AT_BOTTOM; +} + + +// +// Platform is at bottom, now starts moving up +// +void CFuncPlat :: GoUp( void ) +{ + if (pev->noiseMovement) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_AT_BOTTOM || m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_GOING_UP; + SetMoveDone(&CFuncPlat::CallHitTop); + LinearMove(m_vecPosition1, pev->speed); +} + + +// +// Platform has hit top. Pauses, then starts back down again. +// +void CFuncPlat :: HitTop( void ) +{ + if(pev->noiseMovement) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement)); + + if (pev->noiseStopMoving) + EMIT_SOUND(ENT(pev), CHAN_WEAPON, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_GOING_UP); + m_toggle_state = TS_AT_TOP; + + if ( !IsTogglePlat() ) + { + // After a delay, the platform will automatically start going down again. + SetThink( &CFuncPlat::CallGoDown ); + pev->nextthink = pev->ltime + 3; + } +} + + +void CFuncPlat :: Blocked( CBaseEntity *pOther ) +{ + ALERT( at_aiconsole, "%s Blocked by %s\n", STRING(pev->classname), STRING(pOther->pev->classname) ); + // Hurt the blocker a little + pOther->TakeDamage(pev, pev, 1, DMG_CRUSH); + + if(pev->noiseMovement) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement)); + + // Send the platform back where it came from + ASSERT(m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN); + if (m_toggle_state == TS_GOING_UP) + GoDown(); + else if (m_toggle_state == TS_GOING_DOWN) + GoUp (); +} + + +class CFuncPlatRot : public CFuncPlat +{ +public: + void Spawn( void ); + void SetupRotation( void ); + + virtual void GoUp( void ); + virtual void GoDown( void ); + virtual void HitTop( void ); + virtual void HitBottom( void ); + + void RotMove( Vector &destAngle, float time ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + Vector m_end, m_start; +}; +LINK_ENTITY_TO_CLASS( func_platrot, CFuncPlatRot ); +TYPEDESCRIPTION CFuncPlatRot::m_SaveData[] = +{ + DEFINE_FIELD( CFuncPlatRot, m_end, FIELD_VECTOR ), + DEFINE_FIELD( CFuncPlatRot, m_start, FIELD_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CFuncPlatRot, CFuncPlat ); + + +void CFuncPlatRot :: SetupRotation( void ) +{ + if ( m_vecFinalAngle.x != 0 ) // This plat rotates too! + { + CBaseToggle :: AxisDir( pev ); + m_start = pev->angles; + m_end = pev->angles + pev->movedir * m_vecFinalAngle.x; + } + else + { + m_start = g_vecZero; + m_end = g_vecZero; + } + if ( !FStringNull(pev->targetname) ) // Start at top + { + pev->angles = m_end; + } +} + + +void CFuncPlatRot :: Spawn( void ) +{ + CFuncPlat :: Spawn(); + SetupRotation(); +} + +void CFuncPlatRot :: GoDown( void ) +{ + CFuncPlat :: GoDown(); + RotMove( m_start, pev->nextthink - pev->ltime ); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncPlatRot :: HitBottom( void ) +{ + CFuncPlat :: HitBottom(); + pev->avelocity = g_vecZero; + pev->angles = m_start; +} + + +// +// Platform is at bottom, now starts moving up +// +void CFuncPlatRot :: GoUp( void ) +{ + CFuncPlat :: GoUp(); + RotMove( m_end, pev->nextthink - pev->ltime ); +} + + +// +// Platform has hit top. Pauses, then starts back down again. +// +void CFuncPlatRot :: HitTop( void ) +{ + CFuncPlat :: HitTop(); + pev->avelocity = g_vecZero; + pev->angles = m_end; +} + + +void CFuncPlatRot :: RotMove( Vector &destAngle, float time ) +{ + // set destdelta to the vector needed to move + Vector vecDestDelta = destAngle - pev->angles; + + // Travel time is so short, we're practically there already; so make it so. + if ( time >= 0.1) + pev->avelocity = vecDestDelta / time; + else + { + pev->avelocity = vecDestDelta; + pev->nextthink = pev->ltime + 1; + } +} + + +// +//====================== TRAIN code ================================================== +// + +class CFuncTrain : public CBasePlatTrain +{ +public: + void Spawn( void ); + void Precache( void ); + void Activate( void ); + void OverrideReset( void ); + + void Blocked( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + + void EXPORT Wait( void ); + void EXPORT Next( void ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + entvars_t *m_pevCurrentTarget; + int m_sounds; + BOOL m_activated; +}; + +LINK_ENTITY_TO_CLASS( func_train, CFuncTrain ); +TYPEDESCRIPTION CFuncTrain::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTrain, m_sounds, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrain, m_pevCurrentTarget, FIELD_EVARS ), + DEFINE_FIELD( CFuncTrain, m_activated, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTrain, CBasePlatTrain ); + + +void CFuncTrain :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBasePlatTrain::KeyValue( pkvd ); +} + + +void CFuncTrain :: Blocked( CBaseEntity *pOther ) + +{ + if ( gpGlobals->time < m_flActivateFinished) + return; + + m_flActivateFinished = gpGlobals->time + 0.5; + + pOther->TakeDamage(pev, pev, pev->dmg, DMG_CRUSH); +} + + +void CFuncTrain :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->spawnflags & SF_TRAIN_WAIT_RETRIGGER ) + { + // Move toward my target + pev->spawnflags &= ~SF_TRAIN_WAIT_RETRIGGER; + Next(); + } + else + { + pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER; + // Pop back to last target if it's available + if ( pev->enemy ) + pev->target = pev->enemy->v.targetname; + pev->nextthink = 0; + pev->velocity = g_vecZero; + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + } +} + + +void CFuncTrain :: Wait( void ) +{ + // Fire the pass target if there is one + if ( m_pevCurrentTarget->message ) + { + FireTargets( STRING(m_pevCurrentTarget->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( m_pevCurrentTarget->spawnflags, SF_CORNER_FIREONCE ) ) + m_pevCurrentTarget->message = 0; + } + + // need pointer to LAST target. + if ( FBitSet (m_pevCurrentTarget->spawnflags , SF_TRAIN_WAIT_RETRIGGER ) || ( pev->spawnflags & SF_TRAIN_WAIT_RETRIGGER ) ) + { + pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER; + // clear the sound channel. + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + pev->nextthink = 0; + return; + } + + // ALERT ( at_console, "%f\n", m_flWait ); + + if (m_flWait != 0) + {// -1 wait will wait forever! + pev->nextthink = pev->ltime + m_flWait; + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + SetThink( &CFuncTrain::Next ); + } + else + { + Next();// do it RIGHT now! + } +} + + +// +// Train next - path corner needs to change to next target +// +void CFuncTrain :: Next( void ) +{ + CBaseEntity *pTarg; + + + // now find our next target + pTarg = GetNextTarget(); + + if ( !pTarg ) + { + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + // Play stop sound + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + return; + } + + // Save last target in case we need to find it again + pev->message = pev->target; + + pev->target = pTarg->pev->target; + m_flWait = pTarg->GetDelay(); + + if ( m_pevCurrentTarget && m_pevCurrentTarget->speed != 0 ) + {// don't copy speed from target if it is 0 (uninitialized) + pev->speed = m_pevCurrentTarget->speed; + ALERT( at_aiconsole, "Train %s speed to %4.2f\n", STRING(pev->targetname), pev->speed ); + } + m_pevCurrentTarget = pTarg->pev;// keep track of this since path corners change our target for us. + + pev->enemy = pTarg->edict();//hack + + if(FBitSet(m_pevCurrentTarget->spawnflags, SF_CORNER_TELEPORT)) + { + // Path corner has indicated a teleport to the next corner. + SetBits(pev->effects, EF_NOINTERP); + UTIL_SetOrigin(pev, pTarg->pev->origin - (pev->mins + pev->maxs)* 0.5); + Wait(); // Get on with doing the next path corner. + } + else + { + // Normal linear move. + + // CHANGED this from CHAN_VOICE to CHAN_STATIC around OEM beta time because trains should + // use CHAN_STATIC for their movement sounds to prevent sound field problems. + // this is not a hack or temporary fix, this is how things should be. (sjb). + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + if ( pev->noiseMovement ) + EMIT_SOUND (ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement), m_volume, ATTN_NORM); + ClearBits(pev->effects, EF_NOINTERP); + SetMoveDone( &CFuncTrain::Wait ); + LinearMove (pTarg->pev->origin - (pev->mins + pev->maxs)* 0.5, pev->speed); + } +} + + +void CFuncTrain :: Activate( void ) +{ + // Not yet active, so teleport to first target + if ( !m_activated ) + { + m_activated = TRUE; + entvars_t *pevTarg = VARS( FIND_ENTITY_BY_TARGETNAME (NULL, STRING(pev->target) ) ); + + pev->target = pevTarg->target; + m_pevCurrentTarget = pevTarg;// keep track of this since path corners change our target for us. + + UTIL_SetOrigin (pev, pevTarg->origin - (pev->mins + pev->maxs) * 0.5 ); + + if ( FStringNull(pev->targetname) ) + { // not triggered, so start immediately + pev->nextthink = pev->ltime + 0.1; + SetThink( &CFuncTrain::Next ); + } + else + pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER; + } +} + +/*QUAKED func_train (0 .5 .8) ? +Trains are moving platforms that players can ride. +The targets origin specifies the min point of the train at each corner. +The train spawns at the first target it is pointing at. +If the train is the target of a button or trigger, it will not begin moving until activated. +speed default 100 +dmg default 2 +sounds +1) ratchet metal +*/ + +void CFuncTrain :: Spawn( void ) +{ + Precache(); + if (pev->speed == 0) + pev->speed = 100; + + if ( FStringNull(pev->target) ) + ALERT(at_console, "FuncTrain with no target"); + + if (pev->dmg == 0) + pev->dmg = 2; + + pev->movetype = MOVETYPE_PUSH; + + if ( FBitSet (pev->spawnflags, SF_TRACKTRAIN_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + + SET_MODEL( ENT(pev), STRING(pev->model) ); + UTIL_SetSize (pev, pev->mins, pev->maxs); + UTIL_SetOrigin(pev, pev->origin); + + m_activated = FALSE; + + if ( m_volume == 0 ) + m_volume = 0.85; +} + + +void CFuncTrain::Precache( void ) +{ + CBasePlatTrain::Precache(); + +#if 0 // obsolete + // otherwise use preset sound + switch (m_sounds) + { + case 0: + pev->noise = 0; + pev->noise1 = 0; + break; + + case 1: + PRECACHE_SOUND ("plats/train2.wav"); + PRECACHE_SOUND ("plats/train1.wav"); + pev->noise = MAKE_STRING("plats/train2.wav"); + pev->noise1 = MAKE_STRING("plats/train1.wav"); + break; + + case 2: + PRECACHE_SOUND ("plats/platmove1.wav"); + PRECACHE_SOUND ("plats/platstop1.wav"); + pev->noise = MAKE_STRING("plats/platstop1.wav"); + pev->noise1 = MAKE_STRING("plats/platmove1.wav"); + break; + } +#endif +} + + +void CFuncTrain::OverrideReset( void ) +{ + CBaseEntity *pTarg; + + // Are we moving? + if ( pev->velocity != g_vecZero && pev->nextthink != 0 ) + { + pev->target = pev->message; + // now find our next target + pTarg = GetNextTarget(); + if ( !pTarg ) + { + pev->nextthink = 0; + pev->velocity = g_vecZero; + } + else // Keep moving for 0.1 secs, then find path_corner again and restart + { + SetThink( &CFuncTrain::Next ); + pev->nextthink = pev->ltime + 0.1; + } + } +} + + + + +// --------------------------------------------------------------------- +// +// Track Train +// +// --------------------------------------------------------------------- + +TYPEDESCRIPTION CFuncTrackTrain::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTrackTrain, m_ppath, FIELD_CLASSPTR ), + DEFINE_FIELD( CFuncTrackTrain, m_length, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_height, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_speed, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_dir, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_startSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_controlMins, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTrackTrain, m_controlMaxs, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTrackTrain, m_sounds, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrackTrain, m_flVolume, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_flBank, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_oldSpeed, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTrackTrain, CBaseEntity ); +LINK_ENTITY_TO_CLASS( func_tracktrain, CFuncTrackTrain ); + +void CFuncTrackTrain :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "wheels")) + { + m_length = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "height")) + { + m_height = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "startspeed")) + { + m_startSpeed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "volume")) + { + m_flVolume = (float) (atoi(pkvd->szValue)); + m_flVolume *= 0.1; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "bank")) + { + m_flBank = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +void CFuncTrackTrain :: NextThink( float thinkTime, BOOL alwaysThink ) +{ + if ( alwaysThink ) + pev->flags |= FL_ALWAYSTHINK; + else + pev->flags &= ~FL_ALWAYSTHINK; + + pev->nextthink = thinkTime; +} + + +void CFuncTrackTrain :: Blocked( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + // Blocker is on-ground on the train + if ( FBitSet( pevOther->flags, FL_ONGROUND ) && VARS(pevOther->groundentity) == pev ) + { + float deltaSpeed = fabs(pev->speed); + if ( deltaSpeed > 50 ) + deltaSpeed = 50; + if ( !pevOther->velocity.z ) + pevOther->velocity.z += deltaSpeed; + return; + } + else + pevOther->velocity = (pevOther->origin - pev->origin ).Normalize() * pev->dmg; + + ALERT( at_aiconsole, "TRAIN(%s): Blocked by %s (dmg:%.2f)\n", STRING(pev->targetname), STRING(pOther->pev->classname), pev->dmg ); + if ( pev->dmg <= 0 ) + return; + // we can't hurt this thing, so we're not concerned with it + pOther->TakeDamage(pev, pev, pev->dmg, DMG_CRUSH); +} + + +void CFuncTrackTrain :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( useType != USE_SET ) + { + if ( !ShouldToggle( useType, (pev->speed != 0) ) ) + return; + + if ( pev->speed == 0 ) + { + pev->speed = m_speed * m_dir; + + Next(); + } + else + { + pev->speed = 0; + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + StopSound(); + SetThink( NULL ); + } + } + else + { + float delta = value; + + delta = ((int)(pev->speed * 4) / (int)m_speed)*0.25 + 0.25 * delta; + if ( delta > 1 ) + delta = 1; + else if ( delta < -1 ) + delta = -1; + if ( pev->spawnflags & SF_TRACKTRAIN_FORWARDONLY ) + { + if ( delta < 0 ) + delta = 0; + } + pev->speed = m_speed * delta; + Next(); + ALERT( at_aiconsole, "TRAIN(%s), speed to %.2f\n", STRING(pev->targetname), pev->speed ); + } +} + + +static float Fix( float angle ) +{ + while ( angle < 0 ) + angle += 360; + while ( angle > 360 ) + angle -= 360; + + return angle; +} + + +static void FixupAngles( Vector &v ) +{ + v.x = Fix( v.x ); + v.y = Fix( v.y ); + v.z = Fix( v.z ); +} + +#define TRAIN_STARTPITCH 60 +#define TRAIN_MAXPITCH 200 +#define TRAIN_MAXSPEED 1000 // approx max speed for sound pitch calculation + +void CFuncTrackTrain :: StopSound( void ) +{ + // if sound playing, stop it + if (m_soundPlaying && pev->noise) + { + unsigned short us_encode; + unsigned short us_sound = ( ( unsigned short )( m_sounds ) & 0x0007 ) << 12; + + us_encode = us_sound; + + PLAYBACK_EVENT_FULL( FEV_RELIABLE | FEV_UPDATE, edict(), m_usAdjustPitch, 0.0, + (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, us_encode, 0, 1, 0 ); + + /* + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noise)); + */ + EMIT_SOUND_DYN(ENT(pev), CHAN_ITEM, "plats/ttrain_brake1.wav", m_flVolume, ATTN_NORM, 0, 100); + } + + m_soundPlaying = 0; +} + +// update pitch based on speed, start sound if not playing +// NOTE: when train goes through transition, m_soundPlaying should go to 0, +// which will cause the looped sound to restart. + +void CFuncTrackTrain :: UpdateSound( void ) +{ + float flpitch; + + if (!pev->noise) + return; + + flpitch = TRAIN_STARTPITCH + (abs(pev->speed) * (TRAIN_MAXPITCH - TRAIN_STARTPITCH) / TRAIN_MAXSPEED); + + if (!m_soundPlaying) + { + // play startup sound for train + EMIT_SOUND_DYN(ENT(pev), CHAN_ITEM, "plats/ttrain_start1.wav", m_flVolume, ATTN_NORM, 0, 100); + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noise), m_flVolume, ATTN_NORM, 0, (int) flpitch); + m_soundPlaying = 1; + } + else + { +/* + // update pitch + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noise), m_flVolume, ATTN_NORM, SND_CHANGE_PITCH, (int) flpitch); +*/ + // volume 0.0 - 1.0 - 6 bits + // m_sounds 3 bits + // flpitch = 6 bits + // 15 bits total + + unsigned short us_encode; + unsigned short us_sound = ( ( unsigned short )( m_sounds ) & 0x0007 ) << 12; + unsigned short us_pitch = ( ( unsigned short )( flpitch / 10.0 ) & 0x003f ) << 6; + unsigned short us_volume = ( ( unsigned short )( m_flVolume * 40.0 ) & 0x003f ); + + us_encode = us_sound | us_pitch | us_volume; + + PLAYBACK_EVENT_FULL( FEV_RELIABLE | FEV_UPDATE, edict(), m_usAdjustPitch, 0.0, + (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, us_encode, 0, 0, 0 ); + } +} + + +void CFuncTrackTrain :: Next( void ) +{ + float time = 0.5; + + if ( !pev->speed ) + { + ALERT( at_aiconsole, "TRAIN(%s): Speed is 0\n", STRING(pev->targetname) ); + StopSound(); + return; + } + +// if ( !m_ppath ) +// m_ppath = CPathTrack::Instance(FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) )); + if ( !m_ppath ) + { + ALERT( at_aiconsole, "TRAIN(%s): Lost path\n", STRING(pev->targetname) ); + StopSound(); + return; + } + + UpdateSound(); + + Vector nextPos = pev->origin; + + nextPos.z -= m_height; + CPathTrack *pnext = m_ppath->LookAhead( &nextPos, pev->speed * 0.1, 1 ); + nextPos.z += m_height; + + pev->velocity = (nextPos - pev->origin) * 10; + Vector nextFront = pev->origin; + + nextFront.z -= m_height; + if ( m_length > 0 ) + m_ppath->LookAhead( &nextFront, m_length, 0 ); + else + m_ppath->LookAhead( &nextFront, 100, 0 ); + nextFront.z += m_height; + + Vector delta = nextFront - pev->origin; + Vector angles = UTIL_VecToAngles( delta ); + // The train actually points west + angles.y += 180; + + // !!! All of this crap has to be done to make the angles not wrap around, revisit this. + FixupAngles( angles ); + FixupAngles( pev->angles ); + + if ( !pnext || (delta.x == 0 && delta.y == 0) ) + angles = pev->angles; + + float vy, vx; + if ( !(pev->spawnflags & SF_TRACKTRAIN_NOPITCH) ) + vx = UTIL_AngleDistance( angles.x, pev->angles.x ); + else + vx = 0; + vy = UTIL_AngleDistance( angles.y, pev->angles.y ); + + pev->avelocity.y = vy * 10; + pev->avelocity.x = vx * 10; + + if ( m_flBank != 0 ) + { + if ( pev->avelocity.y < -5 ) + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( -m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); + else if ( pev->avelocity.y > 5 ) + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); + else + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( 0, pev->angles.z, m_flBank*4 ), pev->angles.z) * 4; + } + + if ( pnext ) + { + if ( pnext != m_ppath ) + { + CPathTrack *pFire; + if ( pev->speed >= 0 ) + pFire = pnext; + else + pFire = m_ppath; + + m_ppath = pnext; + // Fire the pass target if there is one + if ( pFire->pev->message ) + { + FireTargets( STRING(pFire->pev->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( pFire->pev->spawnflags, SF_PATH_FIREONCE ) ) + pFire->pev->message = 0; + } + + if ( pFire->pev->spawnflags & SF_PATH_DISABLE_TRAIN ) + pev->spawnflags |= SF_TRACKTRAIN_NOCONTROL; + + // Don't override speed if under user control + if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL ) + { + if ( pFire->pev->speed != 0 ) + {// don't copy speed from target if it is 0 (uninitialized) + pev->speed = pFire->pev->speed; + ALERT( at_aiconsole, "TrackTrain %s speed to %4.2f\n", STRING(pev->targetname), pev->speed ); + } + } + + } + SetThink( &CFuncTrackTrain::Next ); + NextThink( pev->ltime + time, TRUE ); + } + else // end of path, stop + { + StopSound(); + pev->velocity = (nextPos - pev->origin); + pev->avelocity = g_vecZero; + float distance = pev->velocity.Length(); + m_oldSpeed = pev->speed; + + + pev->speed = 0; + + // Move to the dead end + + // Are we there yet? + if ( distance > 0 ) + { + // no, how long to get there? + time = distance / m_oldSpeed; + pev->velocity = pev->velocity * (m_oldSpeed / distance); + SetThink( &CFuncTrackTrain::DeadEnd ); + NextThink( pev->ltime + time, FALSE ); + } + else + { + DeadEnd(); + } + } +} + + +void CFuncTrackTrain::DeadEnd( void ) +{ + // Fire the dead-end target if there is one + CPathTrack *pTrack, *pNext; + + pTrack = m_ppath; + + ALERT( at_aiconsole, "TRAIN(%s): Dead end ", STRING(pev->targetname) ); + // Find the dead end path node + // HACKHACK -- This is bugly, but the train can actually stop moving at a different node depending on it's speed + // so we have to traverse the list to it's end. + if ( pTrack ) + { + if ( m_oldSpeed < 0 ) + { + do + { + pNext = pTrack->ValidPath( pTrack->GetPrevious(), TRUE ); + if ( pNext ) + pTrack = pNext; + } while ( pNext ); + } + else + { + do + { + pNext = pTrack->ValidPath( pTrack->GetNext(), TRUE ); + if ( pNext ) + pTrack = pNext; + } while ( pNext ); + } + } + + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + if ( pTrack ) + { + ALERT( at_aiconsole, "at %s\n", STRING(pTrack->pev->targetname) ); + if ( pTrack->pev->netname ) + FireTargets( STRING(pTrack->pev->netname), this, this, USE_TOGGLE, 0 ); + } + else + ALERT( at_aiconsole, "\n" ); +} + + +void CFuncTrackTrain :: SetControls( entvars_t *pevControls ) +{ + Vector offset = pevControls->origin - pev->oldorigin; + + m_controlMins = pevControls->mins + offset; + m_controlMaxs = pevControls->maxs + offset; +} + + +BOOL CFuncTrackTrain :: OnControls( entvars_t *pevTest ) +{ + Vector offset = pevTest->origin - pev->origin; + + if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL ) + return FALSE; + + // Transform offset into local coordinates + UTIL_MakeVectors( pev->angles ); + Vector local; + local.x = DotProduct( offset, gpGlobals->v_forward ); + local.y = -DotProduct( offset, gpGlobals->v_right ); + local.z = DotProduct( offset, gpGlobals->v_up ); + + if ( local.x >= m_controlMins.x && local.y >= m_controlMins.y && local.z >= m_controlMins.z && + local.x <= m_controlMaxs.x && local.y <= m_controlMaxs.y && local.z <= m_controlMaxs.z ) + return TRUE; + + return FALSE; +} + + +void CFuncTrackTrain :: Find( void ) +{ + m_ppath = CPathTrack::Instance(FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) )); + if ( !m_ppath ) + return; + + entvars_t *pevTarget = m_ppath->pev; + if ( !FClassnameIs( pevTarget, "path_track" ) ) + { + ALERT( at_error, "func_track_train must be on a path of path_track\n" ); + m_ppath = NULL; + return; + } + + Vector nextPos = pevTarget->origin; + nextPos.z += m_height; + + Vector look = nextPos; + look.z -= m_height; + m_ppath->LookAhead( &look, m_length, 0 ); + look.z += m_height; + + pev->angles = UTIL_VecToAngles( look - nextPos ); + // The train actually points west + pev->angles.y += 180; + + if ( pev->spawnflags & SF_TRACKTRAIN_NOPITCH ) + pev->angles.x = 0; + UTIL_SetOrigin( pev, nextPos ); + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::Next ); + pev->speed = m_startSpeed; + + UpdateSound(); +} + + +void CFuncTrackTrain :: NearestPath( void ) +{ + CBaseEntity *pTrack = NULL; + CBaseEntity *pNearest = NULL; + float dist, closest; + + closest = 1024; + + while ((pTrack = UTIL_FindEntityInSphere( pTrack, pev->origin, 1024 )) != NULL) + { + // filter out non-tracks + if ( !(pTrack->pev->flags & (FL_CLIENT|FL_MONSTER)) && FClassnameIs( pTrack->pev, "path_track" ) ) + { + dist = (pev->origin - pTrack->pev->origin).Length(); + if ( dist < closest ) + { + closest = dist; + pNearest = pTrack; + } + } + } + + if ( !pNearest ) + { + ALERT( at_console, "Can't find a nearby track !!!\n" ); + SetThink(NULL); + return; + } + + ALERT( at_aiconsole, "TRAIN: %s, Nearest track is %s\n", STRING(pev->targetname), STRING(pNearest->pev->targetname) ); + // If I'm closer to the next path_track on this path, then it's my real path + pTrack = ((CPathTrack *)pNearest)->GetNext(); + if ( pTrack ) + { + if ( (pev->origin - pTrack->pev->origin).Length() < (pev->origin - pNearest->pev->origin).Length() ) + pNearest = pTrack; + } + + m_ppath = (CPathTrack *)pNearest; + + if ( pev->speed != 0 ) + { + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::Next ); + } +} + + +void CFuncTrackTrain::OverrideReset( void ) +{ + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::NearestPath ); +} + + +CFuncTrackTrain *CFuncTrackTrain::Instance( edict_t *pent ) +{ + if ( FClassnameIs( pent, "func_tracktrain" ) ) + return (CFuncTrackTrain *)GET_PRIVATE(pent); + return NULL; +} + +/*QUAKED func_train (0 .5 .8) ? +Trains are moving platforms that players can ride. +The targets origin specifies the min point of the train at each corner. +The train spawns at the first target it is pointing at. +If the train is the target of a button or trigger, it will not begin moving until activated. +speed default 100 +dmg default 2 +sounds +1) ratchet metal +*/ + +void CFuncTrackTrain :: Spawn( void ) +{ + if ( pev->speed == 0 ) + m_speed = 100; + else + m_speed = pev->speed; + + pev->speed = 0; + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + pev->impulse = m_speed; + + m_dir = 1; + + if ( FStringNull(pev->target) ) + ALERT( at_console, "FuncTrain with no target" ); + + if ( pev->spawnflags & SF_TRACKTRAIN_PASSABLE ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + SET_MODEL( ENT(pev), STRING(pev->model) ); + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); + + // Cache off placed origin for train controls + pev->oldorigin = pev->origin; + + m_controlMins = pev->mins; + m_controlMaxs = pev->maxs; + m_controlMaxs.z += 72; +// start trains on the next frame, to make sure their targets have had +// a chance to spawn/activate + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::Find ); + Precache(); +} + +void CFuncTrackTrain :: Precache( void ) +{ + if (m_flVolume == 0.0) + m_flVolume = 1.0; + + switch (m_sounds) + { + default: + // no sound + pev->noise = 0; + break; + case 1: PRECACHE_SOUND("plats/ttrain1.wav"); pev->noise = MAKE_STRING("plats/ttrain1.wav");break; + case 2: PRECACHE_SOUND("plats/ttrain2.wav"); pev->noise = MAKE_STRING("plats/ttrain2.wav");break; + case 3: PRECACHE_SOUND("plats/ttrain3.wav"); pev->noise = MAKE_STRING("plats/ttrain3.wav");break; + case 4: PRECACHE_SOUND("plats/ttrain4.wav"); pev->noise = MAKE_STRING("plats/ttrain4.wav");break; + case 5: PRECACHE_SOUND("plats/ttrain6.wav"); pev->noise = MAKE_STRING("plats/ttrain6.wav");break; + case 6: PRECACHE_SOUND("plats/ttrain7.wav"); pev->noise = MAKE_STRING("plats/ttrain7.wav");break; + } + + PRECACHE_SOUND("plats/ttrain_brake1.wav"); + PRECACHE_SOUND("plats/ttrain_start1.wav"); + + m_usAdjustPitch = PRECACHE_EVENT( 1, "events/train.sc" ); +} + +// This class defines the volume of space that the player must stand in to control the train +class CFuncTrainControls : public CBaseEntity +{ +public: + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + void Spawn( void ); + void EXPORT Find( void ); +}; +LINK_ENTITY_TO_CLASS( func_traincontrols, CFuncTrainControls ); + + +void CFuncTrainControls :: Find( void ) +{ + edict_t *pTarget = NULL; + + do + { + pTarget = FIND_ENTITY_BY_TARGETNAME( pTarget, STRING(pev->target) ); + } while ( !FNullEnt(pTarget) && !FClassnameIs(pTarget, "func_tracktrain") ); + + if ( FNullEnt( pTarget ) ) + { + ALERT( at_console, "No train %s\n", STRING(pev->target) ); + return; + } + + CFuncTrackTrain *ptrain = CFuncTrackTrain::Instance(pTarget); + ptrain->SetControls( pev ); + UTIL_Remove( this ); +} + + +void CFuncTrainControls :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); + + SetThink(&CFuncTrainControls::Find ); + pev->nextthink = gpGlobals->time; +} + + + +// ---------------------------------------------------------------------------- +// +// Track changer / Train elevator +// +// ---------------------------------------------------------------------------- + +#define SF_TRACK_ACTIVATETRAIN 0x00000001 +#define SF_TRACK_RELINK 0x00000002 +#define SF_TRACK_ROTMOVE 0x00000004 +#define SF_TRACK_STARTBOTTOM 0x00000008 +#define SF_TRACK_DONT_MOVE 0x00000010 + +// +// This entity is a rotating/moving platform that will carry a train to a new track. +// It must be larger in X-Y planar area than the train, since it must contain the +// train within these dimensions in order to operate when the train is near it. +// + +typedef enum { TRAIN_SAFE, TRAIN_BLOCKING, TRAIN_FOLLOWING } TRAIN_CODE; + +class CFuncTrackChange : public CFuncPlatRot +{ +public: + void Spawn( void ); + void Precache( void ); + +// virtual void Blocked( void ); + virtual void EXPORT GoUp( void ); + virtual void EXPORT GoDown( void ); + + void KeyValue( KeyValueData* pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Find( void ); + TRAIN_CODE EvaluateTrain( CPathTrack *pcurrent ); + void UpdateTrain( Vector &dest ); + virtual void HitBottom( void ); + virtual void HitTop( void ); + void Touch( CBaseEntity *pOther ); + virtual void UpdateAutoTargets( int toggleState ); + virtual BOOL IsTogglePlat( void ) { return TRUE; } + + void DisableUse( void ) { m_use = 0; } + void EnableUse( void ) { m_use = 1; } + int UseEnabled( void ) { return m_use; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual void OverrideReset( void ); + + + CPathTrack *m_trackTop; + CPathTrack *m_trackBottom; + + CFuncTrackTrain *m_train; + + int m_trackTopName; + int m_trackBottomName; + int m_trainName; + TRAIN_CODE m_code; + int m_targetState; + int m_use; +}; +LINK_ENTITY_TO_CLASS( func_trackchange, CFuncTrackChange ); + +TYPEDESCRIPTION CFuncTrackChange::m_SaveData[] = +{ + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackTop, FIELD_CLASSPTR ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackBottom, FIELD_CLASSPTR ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_train, FIELD_CLASSPTR ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackTopName, FIELD_STRING ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackBottomName, FIELD_STRING ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trainName, FIELD_STRING ), + DEFINE_FIELD( CFuncTrackChange, m_code, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrackChange, m_targetState, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrackChange, m_use, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTrackChange, CFuncPlatRot ); + +void CFuncTrackChange :: Spawn( void ) +{ + Setup(); + if ( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) ) + m_vecPosition2.z = pev->origin.z; + + SetupRotation(); + + if ( FBitSet( pev->spawnflags, SF_TRACK_STARTBOTTOM ) ) + { + UTIL_SetOrigin (pev, m_vecPosition2); + m_toggle_state = TS_AT_BOTTOM; + pev->angles = m_start; + m_targetState = TS_AT_TOP; + } + else + { + UTIL_SetOrigin (pev, m_vecPosition1); + m_toggle_state = TS_AT_TOP; + pev->angles = m_end; + m_targetState = TS_AT_BOTTOM; + } + + EnableUse(); + pev->nextthink = pev->ltime + 2.0; + SetThink( &CFuncTrackChange::Find ); + Precache(); +} + +void CFuncTrackChange :: Precache( void ) +{ + // Can't trigger sound + PRECACHE_SOUND( "buttons/button11.wav" ); + + CFuncPlatRot::Precache(); +} + + +// UNDONE: Filter touches before re-evaluating the train. +void CFuncTrackChange :: Touch( CBaseEntity *pOther ) +{ +#if 0 + TRAIN_CODE code; + entvars_t *pevToucher = pOther->pev; +#endif +} + + + +void CFuncTrackChange :: KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "train") ) + { + m_trainName = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "toptrack") ) + { + m_trackTopName = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "bottomtrack") ) + { + m_trackBottomName = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CFuncPlatRot::KeyValue( pkvd ); // Pass up to base class + } +} + + +void CFuncTrackChange::OverrideReset( void ) +{ + pev->nextthink = pev->ltime + 1.0; + SetThink( &CFuncTrackChange::Find ); +} + +void CFuncTrackChange :: Find( void ) +{ + // Find track entities + edict_t *target; + + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trackTopName) ); + if ( !FNullEnt(target) ) + { + m_trackTop = CPathTrack::Instance( target ); + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trackBottomName) ); + if ( !FNullEnt(target) ) + { + m_trackBottom = CPathTrack::Instance( target ); + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trainName) ); + if ( !FNullEnt(target) ) + { + m_train = CFuncTrackTrain::Instance( FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trainName) ) ); + if ( !m_train ) + { + ALERT( at_error, "Can't find train for track change! %s\n", STRING(m_trainName) ); + return; + } + Vector center = (pev->absmin + pev->absmax) * 0.5; + m_trackBottom = m_trackBottom->Nearest( center ); + m_trackTop = m_trackTop->Nearest( center ); + UpdateAutoTargets( m_toggle_state ); + SetThink( NULL ); + return; + } + else + { + ALERT( at_error, "Can't find train for track change! %s\n", STRING(m_trainName) ); + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trainName) ); + } + } + else + ALERT( at_error, "Can't find bottom track for track change! %s\n", STRING(m_trackBottomName) ); + } + else + ALERT( at_error, "Can't find top track for track change! %s\n", STRING(m_trackTopName) ); +} + + + +TRAIN_CODE CFuncTrackChange :: EvaluateTrain( CPathTrack *pcurrent ) +{ + // Go ahead and work, we don't have anything to switch, so just be an elevator + if ( !pcurrent || !m_train ) + return TRAIN_SAFE; + + if ( m_train->m_ppath == pcurrent || (pcurrent->m_pprevious && m_train->m_ppath == pcurrent->m_pprevious) || + (pcurrent->m_pnext && m_train->m_ppath == pcurrent->m_pnext) ) + { + if ( m_train->pev->speed != 0 ) + return TRAIN_BLOCKING; + + Vector dist = pev->origin - m_train->pev->origin; + float length = dist.Length2D(); + if ( length < m_train->m_length ) // Empirically determined close distance + return TRAIN_FOLLOWING; + else if ( length > (150 + m_train->m_length) ) + return TRAIN_SAFE; + + return TRAIN_BLOCKING; + } + + return TRAIN_SAFE; +} + + +void CFuncTrackChange :: UpdateTrain( Vector &dest ) +{ + float time = (pev->nextthink - pev->ltime); + + m_train->pev->velocity = pev->velocity; + m_train->pev->avelocity = pev->avelocity; + m_train->NextThink( m_train->pev->ltime + time, FALSE ); + + // Attempt at getting the train to rotate properly around the origin of the trackchange + if ( time <= 0 ) + return; + + Vector offset = m_train->pev->origin - pev->origin; + Vector delta = dest - pev->angles; + // Transform offset into local coordinates + UTIL_MakeInvVectors( delta, gpGlobals ); + Vector local; + local.x = DotProduct( offset, gpGlobals->v_forward ); + local.y = DotProduct( offset, gpGlobals->v_right ); + local.z = DotProduct( offset, gpGlobals->v_up ); + + local = local - offset; + m_train->pev->velocity = pev->velocity + (local * (1.0/time)); +} + +void CFuncTrackChange :: GoDown( void ) +{ + if ( m_code == TRAIN_BLOCKING ) + return; + + // HitBottom may get called during CFuncPlat::GoDown(), so set up for that + // before you call GoDown() + + UpdateAutoTargets( TS_GOING_DOWN ); + // If ROTMOVE, move & rotate + if ( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) ) + { + SetMoveDone( &CFuncTrackChange::CallHitBottom ); + m_toggle_state = TS_GOING_DOWN; + AngularMove( m_start, pev->speed ); + } + else + { + CFuncPlat :: GoDown(); + SetMoveDone( &CFuncTrackChange::CallHitBottom ); + RotMove( m_start, pev->nextthink - pev->ltime ); + } + // Otherwise, rotate first, move second + + // If the train is moving with the platform, update it + if ( m_code == TRAIN_FOLLOWING ) + { + UpdateTrain( m_start ); + m_train->m_ppath = NULL; + } +} + + +// +// Platform is at bottom, now starts moving up +// +void CFuncTrackChange :: GoUp( void ) +{ + if ( m_code == TRAIN_BLOCKING ) + return; + + // HitTop may get called during CFuncPlat::GoUp(), so set up for that + // before you call GoUp(); + + UpdateAutoTargets( TS_GOING_UP ); + if ( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) ) + { + m_toggle_state = TS_GOING_UP; + SetMoveDone( &CFuncTrackChange::CallHitTop ); + AngularMove( m_end, pev->speed ); + } + else + { + // If ROTMOVE, move & rotate + CFuncPlat :: GoUp(); + SetMoveDone( &CFuncTrackChange::CallHitTop ); + RotMove( m_end, pev->nextthink - pev->ltime ); + } + + // Otherwise, move first, rotate second + + // If the train is moving with the platform, update it + if ( m_code == TRAIN_FOLLOWING ) + { + UpdateTrain( m_end ); + m_train->m_ppath = NULL; + } +} + + +// Normal track change +void CFuncTrackChange :: UpdateAutoTargets( int toggleState ) +{ + if ( !m_trackTop || !m_trackBottom ) + return; + + if ( toggleState == TS_AT_TOP ) + ClearBits( m_trackTop->pev->spawnflags, SF_PATH_DISABLED ); + else + SetBits( m_trackTop->pev->spawnflags, SF_PATH_DISABLED ); + + if ( toggleState == TS_AT_BOTTOM ) + ClearBits( m_trackBottom->pev->spawnflags, SF_PATH_DISABLED ); + else + SetBits( m_trackBottom->pev->spawnflags, SF_PATH_DISABLED ); +} + + +void CFuncTrackChange :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( m_toggle_state != TS_AT_TOP && m_toggle_state != TS_AT_BOTTOM ) + return; + + // If train is in "safe" area, but not on the elevator, play alarm sound + if ( m_toggle_state == TS_AT_TOP ) + m_code = EvaluateTrain( m_trackTop ); + else if ( m_toggle_state == TS_AT_BOTTOM ) + m_code = EvaluateTrain( m_trackBottom ); + else + m_code = TRAIN_BLOCKING; + if ( m_code == TRAIN_BLOCKING ) + { + // Play alarm and return + EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/button11.wav", 1, ATTN_NORM); + return; + } + + // Otherwise, it's safe to move + // If at top, go down + // at bottom, go up + + DisableUse(); + if (m_toggle_state == TS_AT_TOP) + GoDown(); + else + GoUp(); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncTrackChange :: HitBottom( void ) +{ + CFuncPlatRot :: HitBottom(); + if ( m_code == TRAIN_FOLLOWING ) + { +// UpdateTrain(); + m_train->SetTrack( m_trackBottom ); + } + SetThink( NULL ); + pev->nextthink = -1; + + UpdateAutoTargets( m_toggle_state ); + + EnableUse(); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncTrackChange :: HitTop( void ) +{ + CFuncPlatRot :: HitTop(); + if ( m_code == TRAIN_FOLLOWING ) + { +// UpdateTrain(); + m_train->SetTrack( m_trackTop ); + } + + // Don't let the plat go back down + SetThink( NULL ); + pev->nextthink = -1; + UpdateAutoTargets( m_toggle_state ); + EnableUse(); +} + + + +class CFuncTrackAuto : public CFuncTrackChange +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void UpdateAutoTargets( int toggleState ); +}; + +LINK_ENTITY_TO_CLASS( func_trackautochange, CFuncTrackAuto ); + +// Auto track change +void CFuncTrackAuto :: UpdateAutoTargets( int toggleState ) +{ + CPathTrack *pTarget, *pNextTarget; + + if ( !m_trackTop || !m_trackBottom ) + return; + + if ( m_targetState == TS_AT_TOP ) + { + pTarget = m_trackTop->GetNext(); + pNextTarget = m_trackBottom->GetNext(); + } + else + { + pTarget = m_trackBottom->GetNext(); + pNextTarget = m_trackTop->GetNext(); + } + if ( pTarget ) + { + ClearBits( pTarget->pev->spawnflags, SF_PATH_DISABLED ); + if ( m_code == TRAIN_FOLLOWING && m_train && m_train->pev->speed == 0 ) + m_train->Use( this, this, USE_ON, 0 ); + } + + if ( pNextTarget ) + SetBits( pNextTarget->pev->spawnflags, SF_PATH_DISABLED ); + +} + + +void CFuncTrackAuto :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CPathTrack *pTarget; + + if ( !UseEnabled() ) + return; + + if ( m_toggle_state == TS_AT_TOP ) + pTarget = m_trackTop; + else if ( m_toggle_state == TS_AT_BOTTOM ) + pTarget = m_trackBottom; + else + pTarget = NULL; + + if ( FClassnameIs( pActivator->pev, "func_tracktrain" ) ) + { + m_code = EvaluateTrain( pTarget ); + // Safe to fire? + if ( m_code == TRAIN_FOLLOWING && m_toggle_state != m_targetState ) + { + DisableUse(); + if (m_toggle_state == TS_AT_TOP) + GoDown(); + else + GoUp(); + } + } + else + { + if ( pTarget ) + pTarget = pTarget->GetNext(); + if ( pTarget && m_train->m_ppath != pTarget && ShouldToggle( useType, m_targetState ) ) + { + if ( m_targetState == TS_AT_TOP ) + m_targetState = TS_AT_BOTTOM; + else + m_targetState = TS_AT_TOP; + } + + UpdateAutoTargets( m_targetState ); + } +} + + +// ---------------------------------------------------------- +// +// +// pev->speed is the travel speed +// pev->health is current health +// pev->max_health is the amount to reset to each time it starts + +#define FGUNTARGET_START_ON 0x0001 + +class CGunTarget : public CBaseMonster +{ +public: + void Spawn( void ); + void Activate( void ); + void EXPORT Next( void ); + void EXPORT Start( void ); + void EXPORT Wait( void ); + void Stop( void ); + + int BloodColor( void ) { return DONT_BLEED; } + int Classify( void ) { return CLASS_MACHINE; } + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + Vector BodyTarget( const Vector &posSrc ) { return pev->origin; } + + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + BOOL m_on; +}; + + +LINK_ENTITY_TO_CLASS( func_guntarget, CGunTarget ); + +TYPEDESCRIPTION CGunTarget::m_SaveData[] = +{ + DEFINE_FIELD( CGunTarget, m_on, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CGunTarget, CBaseMonster ); + + +void CGunTarget::Spawn( void ) +{ + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + if ( pev->speed == 0 ) + pev->speed = 100; + + // Don't take damage until "on" + pev->takedamage = DAMAGE_NO; + pev->flags |= FL_MONSTER; + + m_on = FALSE; + pev->max_health = pev->health; + + if ( pev->spawnflags & FGUNTARGET_START_ON ) + { + SetThink( &CGunTarget::Start ); + pev->nextthink = pev->ltime + 0.3; + } +} + + +void CGunTarget::Activate( void ) +{ + CBaseEntity *pTarg; + + // now find our next target + pTarg = GetNextTarget(); + if ( pTarg ) + { + m_hTargetEnt = pTarg; + UTIL_SetOrigin( pev, pTarg->pev->origin - (pev->mins + pev->maxs) * 0.5 ); + } +} + + +void CGunTarget::Start( void ) +{ + Use( this, this, USE_ON, 0 ); +} + + +void CGunTarget::Next( void ) +{ + SetThink( NULL ); + + m_hTargetEnt = GetNextTarget(); + CBaseEntity *pTarget = m_hTargetEnt; + + if ( !pTarget ) + { + Stop(); + return; + } + SetMoveDone( &CGunTarget::Wait ); + LinearMove( pTarget->pev->origin - (pev->mins + pev->maxs) * 0.5, pev->speed ); +} + + +void CGunTarget::Wait( void ) +{ + CBaseEntity *pTarget = m_hTargetEnt; + + if ( !pTarget ) + { + Stop(); + return; + } + + // Fire the pass target if there is one + if ( pTarget->pev->message ) + { + FireTargets( STRING(pTarget->pev->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( pTarget->pev->spawnflags, SF_CORNER_FIREONCE ) ) + pTarget->pev->message = 0; + } + + m_flWait = pTarget->GetDelay(); + + pev->target = pTarget->pev->target; + SetThink( &CGunTarget::Next ); + if (m_flWait != 0) + {// -1 wait will wait forever! + pev->nextthink = pev->ltime + m_flWait; + } + else + { + Next();// do it RIGHT now! + } +} + + +void CGunTarget::Stop( void ) +{ + pev->velocity = g_vecZero; + pev->nextthink = 0; + pev->takedamage = DAMAGE_NO; +} + + +int CGunTarget::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( pev->health > 0 ) + { + pev->health -= flDamage; + if ( pev->health <= 0 ) + { + pev->health = 0; + Stop(); + if ( pev->message ) + FireTargets( STRING(pev->message), this, this, USE_TOGGLE, 0 ); + } + } + return 0; +} + + +void CGunTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_on ) ) + return; + + if ( m_on ) + { + Stop(); + } + else + { + pev->takedamage = DAMAGE_AIM; + m_hTargetEnt = GetNextTarget(); + if ( m_hTargetEnt == NULL ) + return; + pev->health = pev->max_health; + Next(); + } +} + + + diff --git a/dmc/dlls/player.cpp b/dmc/dlls/player.cpp new file mode 100644 index 0000000..58b0d80 --- /dev/null +++ b/dmc/dlls/player.cpp @@ -0,0 +1,4887 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== player.cpp ======================================================== + + functions dealing with the player + +*/ + +#include "extdll.h" +#include "util.h" + +#include "cbase.h" +#include "player.h" +#include "trains.h" +#include "nodes.h" +#include "weapons.h" +#include "monsters.h" +#include "../engine/shake.h" +#include "decals.h" +#include "gamerules.h" +#include "quake_gun.h" + +// #define DUCKFIX + +extern bool g_bHaveMOTD; +#include "pm_shared.h" +#include "hltv.h" + +extern DLL_GLOBAL ULONG g_ulModelIndexPlayer; +extern DLL_GLOBAL BOOL g_fGameOver; +extern DLL_GLOBAL BOOL g_fDrawLines; +extern unsigned short g_sTeleport; +extern unsigned short g_usPowerUp; + +Vector g_vecTeleMins[ MAX_TELES ]; +Vector g_vecTeleMaxs[ MAX_TELES ]; +int g_iTeleNum; + +int gEvilImpulse101; +extern DLL_GLOBAL int g_iSkillLevel, gDisplayTitle; +BOOL gInitHUD = TRUE; + +extern void CopyToBodyQue(entvars_t* pev); +extern void respawn(entvars_t *pev, BOOL fCopyCorpse); +extern Vector VecBModelOrigin(entvars_t *pevBModel ); + +extern edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ); + +int MapTextureTypeStepType(char chTextureType); + +void AddAmmoNameToAmmoRegistry( const char *szAmmoname ); + +// the world node graph +extern CGraph WorldGraph; + +#define PLAYER_WALLJUMP_SPEED 300 // how fast we can spring off walls +#define PLAYER_LONGJUMP_SPEED 350 // how fast we longjump + +#define TRAIN_ACTIVE 0x80 +#define TRAIN_NEW 0xc0 +#define TRAIN_OFF 0x00 +#define TRAIN_NEUTRAL 0x01 +#define TRAIN_SLOW 0x02 +#define TRAIN_MEDIUM 0x03 +#define TRAIN_FAST 0x04 +#define TRAIN_BACK 0x05 + +#define FLASH_DRAIN_TIME 1.2 //100 units/3 minutes +#define FLASH_CHARGE_TIME 0.2 // 100 units/20 seconds (seconds per unit) + + +//#define PLAYER_MAX_SAFE_FALL_DIST 20// falling any farther than this many feet will inflict damage +//#define PLAYER_FATAL_FALL_DIST 60// 100% damage inflicted if player falls this many feet +//#define DAMAGE_PER_UNIT_FALLEN (float)( 100 ) / ( ( PLAYER_FATAL_FALL_DIST - PLAYER_MAX_SAFE_FALL_DIST ) * 12 ) +//#define MAX_SAFE_FALL_UNITS ( PLAYER_MAX_SAFE_FALL_DIST * 12 ) + +// Global Savedata for player +TYPEDESCRIPTION CBasePlayer::m_playerSaveData[] = +{ + DEFINE_FIELD( CBasePlayer, m_flFlashLightTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_iFlashBattery, FIELD_INTEGER ), + + DEFINE_FIELD( CBasePlayer, m_afButtonLast, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_afButtonPressed, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_afButtonReleased, FIELD_INTEGER ), + + DEFINE_ARRAY( CBasePlayer, m_rgItems, FIELD_INTEGER, MAX_ITEMS ), + DEFINE_FIELD( CBasePlayer, m_afPhysicsFlags, FIELD_INTEGER ), + + DEFINE_FIELD( CBasePlayer, m_flTimeStepSound, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flTimeWeaponIdle, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flSwimTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flDuckTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flWallJumpTime, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_flSuitUpdate, FIELD_TIME ), + DEFINE_ARRAY( CBasePlayer, m_rgSuitPlayList, FIELD_INTEGER, CSUITPLAYLIST ), + DEFINE_FIELD( CBasePlayer, m_iSuitPlayNext, FIELD_INTEGER ), + DEFINE_ARRAY( CBasePlayer, m_rgiSuitNoRepeat, FIELD_INTEGER, CSUITNOREPEAT ), + DEFINE_ARRAY( CBasePlayer, m_rgflSuitNoRepeatTime, FIELD_TIME, CSUITNOREPEAT ), + DEFINE_FIELD( CBasePlayer, m_lastDamageAmount, FIELD_INTEGER ), + + DEFINE_ARRAY( CBasePlayer, m_rgpPlayerItems, FIELD_CLASSPTR, MAX_ITEM_TYPES ), + DEFINE_FIELD( CBasePlayer, m_pActiveItem, FIELD_CLASSPTR ), + DEFINE_FIELD( CBasePlayer, m_pLastItem, FIELD_CLASSPTR ), + + DEFINE_ARRAY( CBasePlayer, m_rgAmmo, FIELD_INTEGER, MAX_AMMO_SLOTS ), + DEFINE_FIELD( CBasePlayer, m_idrowndmg, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_idrownrestored, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_tSneaking, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_iTrain, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_bitsHUDDamage, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_flFallVelocity, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayer, m_iTargetVolume, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iWeaponVolume, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iExtraSoundTypes, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iWeaponFlash, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_fLongJump, FIELD_BOOLEAN ), + DEFINE_FIELD( CBasePlayer, m_fInitHUD, FIELD_BOOLEAN ), + DEFINE_FIELD( CBasePlayer, m_tbdPrev, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_pTank, FIELD_EHANDLE ), + DEFINE_FIELD( CBasePlayer, m_iHideHUD, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iFOV, FIELD_INTEGER ), + + //DEFINE_FIELD( CBasePlayer, m_fDeadTime, FIELD_FLOAT ), // only used in multiplayer games + //DEFINE_FIELD( CBasePlayer, m_fGameHUDInitialized, FIELD_INTEGER ), // only used in multiplayer games + //DEFINE_FIELD( CBasePlayer, m_flStopExtraSoundTime, FIELD_TIME ), + //DEFINE_FIELD( CBasePlayer, m_fKnownItem, FIELD_INTEGER ), // reset to zero on load + //DEFINE_FIELD( CBasePlayer, m_iPlayerSound, FIELD_INTEGER ), // Don't restore, set in Precache() + //DEFINE_FIELD( CBasePlayer, m_pentSndLast, FIELD_EDICT ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_flSndRoomtype, FIELD_FLOAT ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_flSndRange, FIELD_FLOAT ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_fNewAmmo, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_flgeigerRange, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_flgeigerDelay, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_igeigerRangePrev, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_iStepLeft, FIELD_INTEGER ), // Don't need to restore + //DEFINE_ARRAY( CBasePlayer, m_szTextureName, FIELD_CHARACTER, CBTEXTURENAMEMAX ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_chTextureType, FIELD_CHARACTER ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_fNoPlayerSound, FIELD_BOOLEAN ), // Don't need to restore, debug + //DEFINE_FIELD( CBasePlayer, m_iUpdateTime, FIELD_INTEGER ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_iClientHealth, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_iClientBattery, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_iClientHideHUD, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_fWeapon, FIELD_BOOLEAN ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_nCustomSprayFrames, FIELD_INTEGER ), // Don't restore, depends on server message after spawning and only matters in multiplayer + //DEFINE_FIELD( CBasePlayer, m_vecAutoAim, FIELD_VECTOR ), // Don't save/restore - this is recomputed + //DEFINE_ARRAY( CBasePlayer, m_rgAmmoLast, FIELD_INTEGER, MAX_AMMO_SLOTS ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_fOnTarget, FIELD_BOOLEAN ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_nCustomSprayFrames, FIELD_INTEGER ), // Don't need to restore + +}; + + +int giPrecacheGrunt = 0; +int gmsgShake = 0; +int gmsgFade = 0; +int gmsgSelAmmo = 0; +int gmsgFlashlight = 0; +int gmsgFlashBattery = 0; +int gmsgResetHUD = 0; +int gmsgInitHUD = 0; +int gmsgShowGameTitle = 0; +int gmsgCurWeapon = 0; +int gmsgHealth = 0; +int gmsgDamage = 0; +int gmsgBattery = 0; +int gmsgTrain = 0; +int gmsgLogo = 0; +int gmsgWeaponList = 0; +int gmsgAmmoX = 0; +int gmsgHudText = 0; +int gmsgDeathMsg = 0; +int gmsgScoreInfo = 0; +int gmsgTeamInfo = 0; +int gmsgTeamScore = 0; +int gmsgGameMode = 0; +int gmsgMOTD = 0; +int gmsgServerName = 0; +int gmsgAmmoPickup = 0; +int gmsgWeapPickup = 0; +int gmsgItemPickup = 0; +int gmsgHideWeapon = 0; +int gmsgSetCurWeap = 0; +int gmsgSayText = 0; +int gmsgTextMsg = 0; +int gmsgSetFOV = 0; +int gmsgShowMenu = 0; +int gmsgGeigerRange = 0; + +// QUAKECLASSIC +int gmsgQItems = 0; +int gmsgStatusText = 0; +int gmsgStatusValue = 0; + +void LinkUserMessages( void ) +{ + // Already taken care of? + if ( gmsgSelAmmo ) + { + return; + } + + gmsgSelAmmo = REG_USER_MSG("SelAmmo", sizeof(SelAmmo)); + gmsgCurWeapon = REG_USER_MSG("CurWeapon", 3); + gmsgGeigerRange = REG_USER_MSG("Geiger", 1); + gmsgFlashlight = REG_USER_MSG("Flashlight", 2); + gmsgFlashBattery = REG_USER_MSG("FlashBat", 1); + gmsgHealth = REG_USER_MSG( "Health", 1 ); + gmsgDamage = REG_USER_MSG( "Damage", 12 ); + gmsgBattery = REG_USER_MSG( "Battery", 2); + gmsgTrain = REG_USER_MSG( "Train", 1); + gmsgHudText = REG_USER_MSG( "HudText", -1 ); + gmsgSayText = REG_USER_MSG( "SayText", -1 ); + gmsgTextMsg = REG_USER_MSG( "TextMsg", -1 ); + gmsgWeaponList = REG_USER_MSG("WeaponList", -1); + gmsgResetHUD = REG_USER_MSG("ResetHUD", 1); // called every respawn + gmsgInitHUD = REG_USER_MSG("InitHUD", -1 ); // called every time a new player joins the server + gmsgShowGameTitle = REG_USER_MSG("GameTitle", 1); + gmsgDeathMsg = REG_USER_MSG( "DeathMsg", -1 ); + gmsgScoreInfo = REG_USER_MSG( "ScoreInfo", 7 ); + gmsgTeamInfo = REG_USER_MSG( "TeamInfo", -1 ); // sets the name of a player's team + gmsgTeamScore = REG_USER_MSG( "TeamScore", -1 ); // sets the score of a team on the scoreboard + gmsgGameMode = REG_USER_MSG( "GameMode", 1 ); + gmsgMOTD = REG_USER_MSG( "MOTD", -1 ); + gmsgServerName = REG_USER_MSG( "ServerName", -1 ); + gmsgAmmoPickup = REG_USER_MSG( "AmmoPickup", 2 ); + gmsgWeapPickup = REG_USER_MSG( "WeapPickup", 1 ); + gmsgItemPickup = REG_USER_MSG( "ItemPickup", -1 ); + gmsgHideWeapon = REG_USER_MSG( "HideWeapon", 1 ); + gmsgSetFOV = REG_USER_MSG( "SetFOV", 1 ); + gmsgShowMenu = REG_USER_MSG( "ShowMenu", -1 ); + gmsgShake = REG_USER_MSG("ScreenShake", sizeof(ScreenShake)); + gmsgFade = REG_USER_MSG("ScreenFade", sizeof(ScreenFade)); + gmsgAmmoX = REG_USER_MSG("AmmoX", 2); + + gmsgQItems = REG_USER_MSG( "QItems", 4 ); + + gmsgStatusText = REG_USER_MSG("StatusText", -1); + gmsgStatusValue = REG_USER_MSG("StatusValue", 3); +} + +LINK_ENTITY_TO_CLASS( player, CBasePlayer ); + + +// QUAKECLASSIC: Play pain sounds +void CBasePlayer :: Pain( CBaseEntity *pAttacker ) +{ + if (pev->health < 0) + return; + + if ( FClassnameIs( pAttacker->pev, "teledeath" ) ) + { + EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/teledth1.wav", 1, ATTN_NORM ); + return; + } + + // water pain sounds + if (pev->watertype == CONTENT_WATER && pev->waterlevel == 3) + { + UTIL_Bubbles( pev->mins, pev->maxs, 3 ); + if ( RANDOM_FLOAT(0,1) > 0.5 ) + EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/drown1.wav", 1, ATTN_NORM ); + else + EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/drown2.wav", 1, ATTN_NORM ); + return; + } + + // slime pain sounds + if (pev->watertype == CONTENT_SLIME) + { + if ( RANDOM_FLOAT(0,1) > 0.5 ) + EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/lburn1.wav", 1, ATTN_NORM ); + else + EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/lburn2.wav", 1, ATTN_NORM ); + return; + } + + // lava pain sounds + if (pev->watertype == CONTENT_LAVA) + { + if ( RANDOM_FLOAT(0,1) > 0.5 ) + EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/lburn1.wav", 1, ATTN_NORM ); + else + EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/lburn2.wav", 1, ATTN_NORM ); + return; + } + + // don't make multiple pain sounds right after each other + if (m_flPainSoundFinished > gpGlobals->time) + { + m_bAxHitMe = 0; + return; + } + m_flPainSoundFinished = gpGlobals->time + 0.5; + + // ax pain sound + if (m_bAxHitMe == 1) + { + m_bAxHitMe = 0; + EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/axhitbod.wav", 1, ATTN_NORM ); + return; + } + + // play random sound + switch ( (int)(RANDOM_FLOAT(0,1) * 6) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/pain1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/pain2.wav", 1, ATTN_NORM ); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/pain3.wav", 1, ATTN_NORM ); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/pain4.wav", 1, ATTN_NORM ); break; + case 4: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/pain5.wav", 1, ATTN_NORM ); break; + case 5: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/pain6.wav", 1, ATTN_NORM ); break; + } +} + +// QUAKECLASSIC: Play a random death sound +void CBasePlayer :: DeathSound( void ) +{ + // water death sounds + if (pev->waterlevel == 3) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/h2odeath.wav", 1, ATTN_NONE); + return; + } + + // play random sound + switch ( (int)(RANDOM_FLOAT(0,1) * 5) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/death1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/death2.wav", 1, ATTN_NORM ); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/death3.wav", 1, ATTN_NORM ); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/death4.wav", 1, ATTN_NORM ); break; + case 4: EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/death5.wav", 1, ATTN_NORM ); break; + } +} + +/* + * + */ +Vector VecVelocityForDamage(float flDamage) +{ + Vector vec(RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + + if (flDamage > -50) + vec = vec * 0.7; + else if (flDamage > -200) + vec = vec * 2; + else + vec = vec * 10; + + return vec; +} + +#if 0 /* +static void ThrowGib(entvars_t *pev, char *szGibModel, float flDamage) +{ + edict_t *pentNew = CREATE_ENTITY(); + entvars_t *pevNew = VARS(pentNew); + + pevNew->origin = pev->origin; + SET_MODEL(ENT(pevNew), szGibModel); + UTIL_SetSize(pevNew, g_vecZero, g_vecZero); + + pevNew->velocity = VecVelocityForDamage(flDamage); + pevNew->movetype = MOVETYPE_BOUNCE; + pevNew->solid = SOLID_NOT; + pevNew->avelocity.x = RANDOM_FLOAT(0,600); + pevNew->avelocity.y = RANDOM_FLOAT(0,600); + pevNew->avelocity.z = RANDOM_FLOAT(0,600); + CHANGE_METHOD(ENT(pevNew), em_think, SUB_Remove); + pevNew->ltime = gpGlobals->time; + pevNew->nextthink = gpGlobals->time + RANDOM_FLOAT(10,20); + pevNew->frame = 0; + pevNew->flags = 0; +} + + +static void ThrowHead(entvars_t *pev, char *szGibModel, floatflDamage) +{ + SET_MODEL(ENT(pev), szGibModel); + pev->frame = 0; + pev->nextthink = -1; + pev->movetype = MOVETYPE_BOUNCE; + pev->takedamage = DAMAGE_NO; + pev->solid = SOLID_NOT; + pev->view_ofs = Vector(0,0,8); + UTIL_SetSize(pev, Vector(-16,-16,0), Vector(16,16,56)); + pev->velocity = VecVelocityForDamage(flDamage); + pev->avelocity = RANDOM_FLOAT(-1,1) * Vector(0,600,0); + pev->origin.z -= 24; + ClearBits(pev->flags, FL_ONGROUND); +} + + +*/ +#endif + +int TrainSpeed(int iSpeed, int iMax) +{ + float fSpeed, fMax; + int iRet = 0; + + fMax = (float)iMax; + fSpeed = iSpeed; + + fSpeed = fSpeed/fMax; + + if (iSpeed < 0) + iRet = TRAIN_BACK; + else if (iSpeed == 0) + iRet = TRAIN_NEUTRAL; + else if (fSpeed < 0.33) + iRet = TRAIN_SLOW; + else if (fSpeed < 0.66) + iRet = TRAIN_MEDIUM; + else + iRet = TRAIN_FAST; + + return iRet; +} + + +// override takehealth +// bitsDamageType indicates type of damage healed. + +int CBasePlayer :: TakeHealth( float flHealth, int bitsDamageType ) +{ + // QUAKECLASSIC + if (pev->health <= 0) + return 0; + if ( !(bitsDamageType & DMG_IGNORE_MAXHEALTH) && (pev->health >= pev->max_health) ) + return 0; + int iHealAmount = ceil(flHealth); + + pev->health += iHealAmount; + if ( !(bitsDamageType & DMG_IGNORE_MAXHEALTH) && (pev->health >= pev->max_health) ) + pev->health = pev->max_health; + + if (pev->health > 250) + pev->health = 250; + return 1; +} + +Vector CBasePlayer :: GetGunPosition( ) +{ +// UTIL_MakeVectors(pev->v_angle); +// m_HackedGunPos = pev->view_ofs; + Vector origin; + + origin = pev->origin + pev->view_ofs; + return origin; +} + +//========================================================= +// TraceAttack +//========================================================= +void CBasePlayer :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( pev->takedamage ) + { + SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage);// a little surface blood. + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + } +} + +void CBasePlayer::RemoveAllItems( BOOL removeSuit ) +{ + if (m_pActiveItem) + { + ResetAutoaim( ); + m_pActiveItem->Holster( ); + m_pActiveItem = NULL; + } + + m_pLastItem = NULL; + + int i; + CBasePlayerItem *pPendingItem; + for (i = 0; i < MAX_ITEM_TYPES; i++) + { + m_pActiveItem = m_rgpPlayerItems[i]; + while (m_pActiveItem) + { + pPendingItem = m_pActiveItem->m_pNext; + m_pActiveItem->Drop( ); + m_pActiveItem = pPendingItem; + } + m_rgpPlayerItems[i] = NULL; + } + m_pActiveItem = NULL; + + pev->viewmodel = 0; + pev->weaponmodel = 0; + + if ( removeSuit ) + pev->weapons = 0; + else + pev->weapons &= ~WEAPON_ALLWEAPONS; + + for ( i = 0; i < MAX_AMMO_SLOTS;i++) + m_rgAmmo[i] = 0; + + UpdateClientData(); + // send Selected Weapon Message to our client + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(0); + WRITE_BYTE(0); + MESSAGE_END(); +} + +/* + * GLOBALS ASSUMED SET: g_ulModelIndexPlayer + * + * ENTITY_METHOD(PlayerDie) + */ +entvars_t *g_pevLastInflictor; // Set in combat.cpp. Used to pass the damage inflictor for death messages. + // Better solution: Add as parameter to all Killed() functions. + +void CBasePlayer::Killed( entvars_t *pevAttacker, int iGib ) +{ + g_pGameRules->PlayerKilled( this, pevAttacker, g_pevLastInflictor ); + + if ( m_pTank != NULL ) + { + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + } + + + //Remove all powerups and powerup timers + m_flInvincibleFinished = m_flRadsuitFinished = m_flInvisibleFinished = m_flSuperDamageFinished = 0.0; + PowerUpThink(); + + SetAnimation( PLAYER_DIE ); + + m_iRespawnFrames = 0; + + pev->modelindex = g_ulModelIndexPlayer; // don't use eyes + + pev->deadflag = DEAD_DYING; + pev->movetype = MOVETYPE_TOSS; + ClearBits(pev->flags, FL_ONGROUND); + + if ( m_iQuakeWeapon == IT_LIGHTNING ) + { + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usLightning, 0, (float *)&pev->origin, (float *)&pev->angles, 0.0, 0.0, 0, 1, 0, 0 ); + + if ( m_pActiveItem ) + ((CQuakeGun*)m_pActiveItem)->DestroyEffect(); + } + + if (pev->velocity.z < 10) + pev->velocity.z += RANDOM_FLOAT(0,300); + + // clear out the suit message cache so we don't keep chattering + SetSuitUpdate(NULL, FALSE, 0); + + // send "health" update message to zero + m_iClientHealth = 0; + MESSAGE_BEGIN( MSG_ONE, gmsgHealth, NULL, pev ); + WRITE_BYTE( m_iClientHealth ); + MESSAGE_END(); + + // Tell Ammo Hud that the player is dead + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(0XFF); + WRITE_BYTE(0xFF); + MESSAGE_END(); + + + // UNDONE: Put this in, but add FFADE_PERMANENT and make fade time 8.8 instead of 4.12 + // UTIL_ScreenFade( edict(), Vector(128,0,0), 6, 15, 255, FFADE_OUT | FFADE_MODULATE ); + + //Quake Player always gibs + if ( pev->health < -40 ) + { + pev->solid = SOLID_NOT; + + GibMonster(); // This clears pev->model + pev->effects |= EF_NODRAW; + return; + } + + DeathSound(); + + pev->angles.x = 0; + pev->angles.z = 0; + + SetThink(&CBasePlayer::PlayerDeathThink); + pev->nextthink = gpGlobals->time + 0.1; +} + + +// Set the activity based on an event or current state +void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) +{ + int animDesired; + float speed; + char szAnim[64]; + + speed = pev->velocity.Length2D(); + + if (pev->flags & FL_FROZEN) + { + speed = 0; + playerAnim = PLAYER_IDLE; + } + + switch (playerAnim) + { + case PLAYER_JUMP: + m_IdealActivity = ACT_HOP; + break; + + case PLAYER_SUPERJUMP: + m_IdealActivity = ACT_LEAP; + break; + + case PLAYER_DIE: + m_IdealActivity = ACT_DIESIMPLE; + m_IdealActivity = GetDeathActivity( ); + break; + + case PLAYER_ATTACK1: + switch( m_Activity ) + { + case ACT_HOVER: + case ACT_SWIM: + case ACT_HOP: + case ACT_LEAP: + case ACT_DIESIMPLE: + m_IdealActivity = m_Activity; + break; + default: + m_IdealActivity = ACT_RANGE_ATTACK1; + break; + } + break; + case PLAYER_IDLE: + case PLAYER_WALK: + if ( !FBitSet( pev->flags, FL_ONGROUND ) && (m_Activity == ACT_HOP || m_Activity == ACT_LEAP) ) // Still jumping + { + m_IdealActivity = m_Activity; + } + else if ( pev->waterlevel > 1 ) + { + if ( speed == 0 ) + m_IdealActivity = ACT_HOVER; + else + m_IdealActivity = ACT_SWIM; + } + else + { + m_IdealActivity = ACT_WALK; + } + break; + } + + switch (m_IdealActivity) + { + case ACT_HOVER: + case ACT_LEAP: + case ACT_SWIM: + case ACT_HOP: + case ACT_DIESIMPLE: + default: + if ( m_Activity == m_IdealActivity) + return; + m_Activity = m_IdealActivity; + + animDesired = LookupActivity( m_Activity ); + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + pev->gaitsequence = 0; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + + case ACT_RANGE_ATTACK1: + if ( FBitSet( pev->flags, FL_DUCKING ) ) // crouching + strcpy( szAnim, "crouch_shoot_" ); + else + strcpy( szAnim, "ref_shoot_" ); + strcat( szAnim, m_szAnimExtention ); + animDesired = LookupSequence( szAnim ); + if (animDesired == -1) + animDesired = 0; + + if ( pev->sequence != animDesired || !m_fSequenceLoops ) + { + pev->frame = 0; + } + + if (!m_fSequenceLoops) + { + pev->effects |= EF_NOINTERP; + } + + m_Activity = m_IdealActivity; + + pev->sequence = animDesired; + ResetSequenceInfo( ); + break; + + case ACT_WALK: + if (m_Activity != ACT_RANGE_ATTACK1 || m_fSequenceFinished) + { + if ( FBitSet( pev->flags, FL_DUCKING ) ) // crouching + strcpy( szAnim, "crouch_aim_" ); + else + strcpy( szAnim, "ref_aim_" ); + strcat( szAnim, m_szAnimExtention ); + animDesired = LookupSequence( szAnim ); + if (animDesired == -1) + animDesired = 0; + m_Activity = ACT_WALK; + } + else + { + animDesired = pev->sequence; + } + } + + if ( FBitSet( pev->flags, FL_DUCKING ) ) + { + if ( speed == 0) + { + pev->gaitsequence = LookupActivity( ACT_CROUCHIDLE ); + // pev->gaitsequence = LookupActivity( ACT_CROUCH ); + } + else + { + pev->gaitsequence = LookupActivity( ACT_CROUCH ); + } + } + else if ( speed > 220 ) + { + pev->gaitsequence = LookupActivity( ACT_RUN ); + } + else if (speed > 0) + { + pev->gaitsequence = LookupActivity( ACT_WALK ); + } + else + { + // pev->gaitsequence = LookupActivity( ACT_WALK ); + pev->gaitsequence = LookupSequence( "deep_idle" ); + } + + + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + //ALERT( at_console, "Set animation to %d\n", animDesired ); + // Reset to first frame of desired animation + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); +} + + +/* +=========== +WaterMove +============ +*/ +#define AIRTIME 12 // lung full of air lasts this many seconds + +void CBasePlayer::WaterMove() +{ + int air; + + if (pev->movetype == MOVETYPE_NOCLIP) + return; + + if (pev->health < 0) + return; + + // waterlevel 0 - not in water + // waterlevel 1 - feet in water + // waterlevel 2 - waist in water + // waterlevel 3 - head in water + + if (pev->waterlevel != 3) + { + // not underwater + + // play 'up for air' sound + if (pev->air_finished < gpGlobals->time) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_wade1.wav", 1, ATTN_NORM); + else if (pev->air_finished < gpGlobals->time + 9) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_wade2.wav", 1, ATTN_NORM); + + pev->air_finished = gpGlobals->time + AIRTIME; + pev->dmg = 2; + + // if we took drowning damage, give it back slowly + if (m_idrowndmg > m_idrownrestored) + { + // set drowning damage bit. hack - dmg_drownrecover actually + // makes the time based damage code 'give back' health over time. + // make sure counter is cleared so we start count correctly. + + // NOTE: this actually causes the count to continue restarting + // until all drowning damage is healed. + + m_bitsDamageType &= ~DMG_DROWN; + + /* m_bitsDamageType |= DMG_DROWNRECOVER; + + m_rgbTimeBasedDamage[itbd_DrownRecover] = 0;*/ + } + + } + else + { // fully under water + // stop restoring damage while underwater + m_bitsDamageType &= ~DMG_DROWNRECOVER; + m_rgbTimeBasedDamage[itbd_DrownRecover] = 0; + + if (pev->air_finished < gpGlobals->time) // drown! + { + if (pev->pain_finished < gpGlobals->time) + { + // take drowning damage + pev->dmg += 1; + if (pev->dmg > 5) + pev->dmg = 5; + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), pev->dmg, DMG_DROWN); + pev->pain_finished = gpGlobals->time + 1; + + // track drowning damage, give it back when + // player finally takes a breath + + m_idrowndmg += pev->dmg; + } + } + else + { + m_bitsDamageType &= ~DMG_DROWN; + } + } + + if (!pev->waterlevel) + { + if (FBitSet(pev->flags, FL_INWATER)) + { +#if 0 + // play leave water sound + switch (RANDOM_LONG(0,3)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM); break; + } +#endif + + ClearBits(pev->flags, FL_INWATER); + } + return; + } + + // make bubbles + + air = (int)(pev->air_finished - gpGlobals->time); + if (!RANDOM_LONG(0,0x1f) && RANDOM_LONG(0,AIRTIME-1) >= air) + { + switch (RANDOM_LONG(0,3)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim1.wav", 0.8, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim2.wav", 0.8, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim3.wav", 0.8, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim4.wav", 0.8, ATTN_NORM); break; + } + } + + if (pev->watertype == CONTENT_LAVA) // do damage + { + if (pev->dmgtime < gpGlobals->time) + { + if ( m_iQuakeItems & IT_SUIT ) + pev->dmgtime = gpGlobals->time + 1.0; + else + pev->dmgtime = gpGlobals->time + 0.2; + + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), 10 * pev->waterlevel, DMG_BURN ); + } + } + else if (pev->watertype == CONTENT_SLIME) // do damage + { + if (pev->dmgtime < gpGlobals->time) + { + pev->dmgtime = gpGlobals->time + 1.0; + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), 4 * pev->waterlevel, DMG_ACID ); + } + } + + if (!FBitSet(pev->flags, FL_INWATER)) + { +#if 0 + // player enter water sound + if (pev->watertype == CONTENT_WATER) + { + switch (RANDOM_LONG(0,3)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM); break; + } + } +#endif + + SetBits(pev->flags, FL_INWATER); + pev->dmgtime = 0; + } + + if (!FBitSet(pev->flags, FL_WATERJUMP)) + pev->velocity = pev->velocity - 0.8 * pev->waterlevel * gpGlobals->frametime * pev->velocity; +} + + +// TRUE if the player is attached to a ladder +BOOL CBasePlayer::IsOnLadder( void ) +{ + return (pev->movetype == MOVETYPE_FLY); +} + +void CBasePlayer::PlayerDeathThink(void) +{ + float flForward; + + if (FBitSet(pev->flags, FL_ONGROUND)) + { + flForward = pev->velocity.Length() - 20; + if (flForward <= 0) + pev->velocity = g_vecZero; + else + pev->velocity = flForward * pev->velocity.Normalize(); + } + + if ( m_iQuakeWeapon ) + { + DropBackpack(); + } + + if (pev->modelindex && (!m_fSequenceFinished) && (pev->deadflag == DEAD_DYING)) + { + StudioFrameAdvance( ); + + m_iRespawnFrames++; // Note, these aren't necessarily real "frames", so behavior is dependent on # of client movement commands + if ( m_iRespawnFrames < 120 ) // Animations should be no longer than this + return; + } + + if (m_fSequenceFinished == 0) + { + StudioFrameAdvance(); + return; + } + + if (pev->deadflag == DEAD_DYING) + pev->deadflag = DEAD_DEAD; + + StopAnimation(); + + + pev->effects |= EF_NOINTERP; + pev->framerate = 0.0; + + BOOL fAnyButtonDown = (pev->button & ~IN_SCORE ); + + // wait for all buttons released + if (pev->deadflag == DEAD_DEAD) + { + if (fAnyButtonDown) + return; + + if ( g_pGameRules->FPlayerCanRespawn( this ) ) + { + m_fDeadTime = gpGlobals->time; + pev->deadflag = DEAD_RESPAWNABLE; + } + + return; + } + +// if the player has been dead for one second longer than allowed by forcerespawn, +// forcerespawn isn't on. Send the player off to an intermission camera until they +// choose to respawn. +/* if ( g_pGameRules->IsMultiplayer() && ( gpGlobals->time > (m_fDeadTime + 6) ) && !(m_afPhysicsFlags & PFLAG_OBSERVER) ) + { + // go to dead camera. + StartDeathCam(); + }*/ + +// wait for any button down, or mp_forcerespawn is set and the respawn time is up + if (!fAnyButtonDown + && !( g_pGameRules->IsMultiplayer() && CVAR_GET_FLOAT("mp_forcerespawn") > 0 && (gpGlobals->time > (m_fDeadTime + 5))) ) + return; + + pev->button = 0; + m_iRespawnFrames = 0; + + + + //ALERT(at_console, "Respawn\n"); + + respawn(pev, !(m_afPhysicsFlags & PFLAG_OBSERVER) );// don't copy a corpse if we're in deathcam. + pev->nextthink = -1; +} + +//========================================================= +// StartDeathCam - find an intermission spot and send the +// player off into observer mode +//========================================================= +void CBasePlayer::StartDeathCam( void ) +{ + edict_t *pSpot, *pNewSpot; + int iRand; + + if ( pev->view_ofs == g_vecZero ) + { + // don't accept subsequent attempts to StartDeathCam() + return; + } + + pSpot = FIND_ENTITY_BY_CLASSNAME( NULL, "info_intermission"); + + if ( !FNullEnt( pSpot ) ) + { + // at least one intermission spot in the world. + iRand = RANDOM_LONG( 0, 3 ); + + while ( iRand > 0 ) + { + pNewSpot = FIND_ENTITY_BY_CLASSNAME( pSpot, "info_intermission"); + + if ( pNewSpot ) + { + pSpot = pNewSpot; + } + + iRand--; + } + + CopyToBodyQue( pev ); + StartObserver( pSpot->v.origin, pSpot->v.v_angle ); + } + else + { + // no intermission spot. Push them up in the air, looking down at their corpse + TraceResult tr; + CopyToBodyQue( pev ); + UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, 128 ), ignore_monsters, edict(), &tr ); + StartObserver( tr.vecEndPos, UTIL_VecToAngles( tr.vecEndPos - pev->origin ) ); + return; + } +} + +void CBasePlayer::StartObserver( Vector vecPosition, Vector vecViewAngle ) +{ + m_iHideHUD |= (HIDEHUD_HEALTH | HIDEHUD_WEAPONS); + m_afPhysicsFlags |= PFLAG_OBSERVER; + + pev->effects = EF_NODRAW; + pev->view_ofs = g_vecZero; + pev->angles = pev->v_angle = vecViewAngle; + pev->fixangle = TRUE; + pev->solid = SOLID_NOT; + pev->takedamage = DAMAGE_NO; + pev->movetype = MOVETYPE_NONE; + UTIL_SetOrigin( pev, vecPosition ); + + ClearBits( m_afPhysicsFlags, PFLAG_DUCKING ); + ClearBits( pev->flags, FL_DUCKING ); + // pev->flags = FL_CLIENT | FL_SPECTATOR; // Should we set Spectator flag? Or is it reserver for people connecting with spectator 1? + pev->deadflag = DEAD_RESPAWNABLE; + + // Tell the physics code that this player's now in observer mode + Observer_SetMode(OBS_CHASE_LOCKED); + m_flNextObserverInput = 0; +} + +// +// PlayerUse - handles USE keypress +// +#define PLAYER_SEARCH_RADIUS (float)64 + +void CBasePlayer::PlayerUse ( void ) +{ + // Was use pressed or released? + if ( ! ((pev->button | m_afButtonPressed | m_afButtonReleased) & IN_USE) ) + return; + + // Hit Use on a train? + if ( m_afButtonPressed & IN_USE ) + { + if ( m_pTank != NULL ) + { + // Stop controlling the tank + // TODO: Send HUD Update + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + return; + } + else + { + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + { + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + else + { // Start controlling the train! + CBaseEntity *pTrain = CBaseEntity::Instance( pev->groundentity ); + + if ( pTrain && !(pev->button & IN_JUMP) && FBitSet(pev->flags, FL_ONGROUND) && (pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) && pTrain->OnControls(pev) ) + { + m_afPhysicsFlags |= PFLAG_ONTRAIN; + m_iTrain = TrainSpeed(pTrain->pev->speed, pTrain->pev->impulse); + m_iTrain |= TRAIN_NEW; + EMIT_SOUND( ENT(pev), CHAN_ITEM, "plats/train_use1.wav", 0.8, ATTN_NORM); + return; + } + } + } + } + + CBaseEntity *pObject = NULL; + CBaseEntity *pClosest = NULL; + Vector vecLOS; + float flMaxDot = VIEW_FIELD_NARROW; + float flDot; + + UTIL_MakeVectors ( pev->v_angle );// so we know which way we are facing + + while ((pObject = UTIL_FindEntityInSphere( pObject, pev->origin, PLAYER_SEARCH_RADIUS )) != NULL) + { + + if (pObject->ObjectCaps() & (FCAP_IMPULSE_USE | FCAP_CONTINUOUS_USE | FCAP_ONOFF_USE)) + { + // !!!PERFORMANCE- should this check be done on a per case basis AFTER we've determined that + // this object is actually usable? This dot is being done for every object within PLAYER_SEARCH_RADIUS + // when player hits the use key. How many objects can be in that area, anyway? (sjb) + vecLOS = (VecBModelOrigin( pObject->pev ) - (pev->origin + pev->view_ofs)); + + // This essentially moves the origin of the target to the corner nearest the player to test to see + // if it's "hull" is in the view cone + vecLOS = UTIL_ClampVectorToBox( vecLOS, pObject->pev->size * 0.5 ); + + flDot = DotProduct (vecLOS , gpGlobals->v_forward); + if (flDot > flMaxDot ) + {// only if the item is in front of the user + pClosest = pObject; + flMaxDot = flDot; +// ALERT( at_console, "%s : %f\n", STRING( pObject->pev->classname ), flDot ); + } +// ALERT( at_console, "%s : %f\n", STRING( pObject->pev->classname ), flDot ); + } + } + pObject = pClosest; + + // Found an object + if (pObject ) + { + //!!!UNDONE: traceline here to prevent USEing buttons through walls + int caps = pObject->ObjectCaps(); + + if ( m_afButtonPressed & IN_USE ) + EMIT_SOUND( ENT(pev), CHAN_ITEM, "common/wpn_select.wav", 0.4, ATTN_NORM); + + if ( ( (pev->button & IN_USE) && (caps & FCAP_CONTINUOUS_USE) ) || + ( (m_afButtonPressed & IN_USE) && (caps & (FCAP_IMPULSE_USE|FCAP_ONOFF_USE)) ) ) + { + if ( caps & FCAP_CONTINUOUS_USE ) + m_afPhysicsFlags |= PFLAG_USING; + + pObject->Use( this, this, USE_SET, 1 ); + } + // UNDONE: Send different USE codes for ON/OFF. Cache last ONOFF_USE object to send 'off' if you turn away + else if ( (m_afButtonReleased & IN_USE) && (pObject->ObjectCaps() & FCAP_ONOFF_USE) ) // BUGBUG This is an "off" use + { + pObject->Use( this, this, USE_SET, 0 ); + } + } + else + { + if ( m_afButtonPressed & IN_USE ) + EMIT_SOUND( ENT(pev), CHAN_ITEM, "common/wpn_denyselect.wav", 0.4, ATTN_NORM); + } +} + + + +void CBasePlayer::Jump() +{ + Vector vecWallCheckDir;// direction we're tracing a line to find a wall when walljumping + Vector vecAdjustedVelocity; + Vector vecSpot; + TraceResult tr; + + if (FBitSet(pev->flags, FL_WATERJUMP)) + return; + + if (pev->waterlevel >= 2) + { + return; + } + + // jump velocity is sqrt( height * gravity * 2) + + // If this isn't the first frame pressing the jump button, break out. + if ( !FBitSet( m_afButtonPressed, IN_JUMP ) ) + return; // don't pogo stick + + if ( !(pev->flags & FL_ONGROUND) || !pev->groundentity ) + { + return; + } + +// many features in this function use v_forward, so makevectors now. + UTIL_MakeVectors (pev->angles); + + SetAnimation( PLAYER_JUMP ); + + if ( FBitSet(pev->flags, FL_DUCKING ) || FBitSet(m_afPhysicsFlags, PFLAG_DUCKING) ) + { + if ( m_fLongJump && (pev->button & IN_DUCK) && gpGlobals->time - m_flDuckTime < 1 && pev->velocity.Length() > 50 ) + {// If jump pressed within a second of duck while moving, long jump! + SetAnimation( PLAYER_SUPERJUMP ); + } + } + + // If you're standing on a conveyor, add it's velocity to yours (for momentum) + entvars_t *pevGround = VARS(pev->groundentity); + if ( pevGround && (pevGround->flags & FL_CONVEYOR) ) + { + pev->velocity = pev->velocity + pev->basevelocity; + } +} + + + +// This is a glorious hack to find free space when you've crouched into some solid space +// Our crouching collisions do not work correctly for some reason and this is easier +// than fixing the problem :( +void FixPlayerCrouchStuck( edict_t *pPlayer ) +{ + TraceResult trace; + + // Move up as many as 18 pixels if the player is stuck. + for ( int i = 0; i < 18; i++ ) + { + UTIL_TraceHull( pPlayer->v.origin, pPlayer->v.origin, dont_ignore_monsters, head_hull, pPlayer, &trace ); + if ( trace.fStartSolid ) + pPlayer->v.origin.z ++; + else + break; + } +} + +void CBasePlayer::Duck( ) +{ + if (pev->button & IN_DUCK) + { + SetAnimation( PLAYER_WALK ); + } +} + +// +// ID's player as such. +// +int CBasePlayer::Classify ( void ) +{ + return CLASS_PLAYER; +} + + +void CBasePlayer::AddPoints( int score, BOOL bAllowNegativeScore ) +{ + // Positive score always adds + if ( score < 0 ) + { + if ( !bAllowNegativeScore ) + { + if ( pev->frags < 0 ) // Can't go more negative + return; + + if ( -score > pev->frags ) // Will this go negative? + { + score = -pev->frags; // Sum will be 0 + } + } + } + + pev->frags += score; + + MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo ); + WRITE_BYTE( ENTINDEX(edict()) ); + WRITE_SHORT( pev->frags ); + WRITE_SHORT( m_iDeaths ); + WRITE_SHORT( pev->team ); + MESSAGE_END(); +} + + +void CBasePlayer::AddPointsToTeam( int score, BOOL bAllowNegativeScore ) +{ + int index = entindex(); + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + if ( pPlayer && i != index ) + { + if ( g_pGameRules->PlayerRelationship( this, pPlayer ) == GR_TEAMMATE ) + { + pPlayer->AddPoints( score, bAllowNegativeScore ); + } + } + } +} + +#if 0 +void CBasePlayer::CheckWeapon(void) +{ + // play a weapon idle anim if it's time! + if ( gpGlobals->time > m_flTimeWeaponIdle ) + { + WeaponIdle ( ); + } +} +#endif + + +// play a footstep if it's time - this will eventually be frame-based. not time based. + +#define STEP_CONCRETE 0 // default step sound +#define STEP_METAL 1 // metal floor +#define STEP_DIRT 2 // dirt, sand, rock +#define STEP_VENT 3 // ventillation duct +#define STEP_GRATE 4 // metal grating +#define STEP_TILE 5 // floor tiles +#define STEP_SLOSH 6 // shallow liquid puddle +#define STEP_WADE 7 // wading in liquid +#define STEP_LADDER 8 // climbing ladder + +// Play correct step sound for material we're on or in + +void CBasePlayer :: PlayStepSound(int step, float fvol) +{ + return; + + static int iSkipStep = 0; + + if ( !g_pGameRules->PlayFootstepSounds( this, fvol ) ) + return; + + // irand - 0,1 for right foot, 2,3 for left foot + // used to alternate left and right foot + int irand = RANDOM_LONG(0,1) + (m_iStepLeft * 2); + + m_iStepLeft = !m_iStepLeft; + + switch (step) + { + default: + case STEP_CONCRETE: + switch (irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_METAL: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_DIRT: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_VENT: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_GRATE: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_TILE: + if (!RANDOM_LONG(0,4)) + irand = 4; + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile4.wav", fvol, ATTN_NORM); break; + case 4: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile5.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_SLOSH: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_WADE: + if ( iSkipStep == 0 ) + { + iSkipStep++; + break; + } + + if ( iSkipStep++ == 3 ) + { + iSkipStep = 0; + } + + switch (irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade2.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade3.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_LADDER: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder4.wav", fvol, ATTN_NORM); break; + } + break; + } +} + +// Simple mapping from texture type character to step type + +int MapTextureTypeStepType(char chTextureType) +{ +switch (chTextureType) + { + default: + case CHAR_TEX_CONCRETE: return STEP_CONCRETE; + case CHAR_TEX_METAL: return STEP_METAL; + case CHAR_TEX_DIRT: return STEP_DIRT; + case CHAR_TEX_VENT: return STEP_VENT; + case CHAR_TEX_GRATE: return STEP_GRATE; + case CHAR_TEX_TILE: return STEP_TILE; + case CHAR_TEX_SLOSH: return STEP_SLOSH; + } +} + +// Play left or right footstep based on material player is on or in + +void CBasePlayer :: UpdateStepSound( void ) +{ + int fWalking; + float fvol; + char szbuffer[64]; + const char *pTextureName; + Vector start, end; + float rgfl1[3]; + float rgfl2[3]; + Vector knee; + Vector feet; + Vector center; + float height; + float speed; + float velrun; + float velwalk; + float flduck; + int fLadder; + int step; + + if (gpGlobals->time <= m_flTimeStepSound) + return; + + if (pev->flags & FL_FROZEN) + return; + + speed = pev->velocity.Length(); + + // determine if we are on a ladder + fLadder = IsOnLadder(); + + // UNDONE: need defined numbers for run, walk, crouch, crouch run velocities!!!! + if (FBitSet(pev->flags, FL_DUCKING) || fLadder) + { + velwalk = 60; // These constants should be based on cl_movespeedkey * cl_forwardspeed somehow + velrun = 80; // UNDONE: Move walking to server + flduck = 0.1; + } + else + { + velwalk = 120; + velrun = 210; + flduck = 0.0; + } + + // ALERT (at_console, "vel: %f\n", vecVel.Length()); + + // if we're on a ladder or on the ground, and we're moving fast enough, + // play step sound. Also, if m_flTimeStepSound is zero, get the new + // sound right away - we just started moving in new level. + + if ((fLadder || FBitSet (pev->flags, FL_ONGROUND)) && pev->velocity != g_vecZero + && (speed >= velwalk || !m_flTimeStepSound)) + { + SetAnimation( PLAYER_WALK ); + + fWalking = speed < velrun; + + center = knee = feet = (pev->absmin + pev->absmax) * 0.5; + height = pev->absmax.z - pev->absmin.z; + + knee.z = pev->absmin.z + height * 0.2; + feet.z = pev->absmin.z; + + // find out what we're stepping in or on... + if (fLadder) + { + step = STEP_LADDER; + fvol = 0.35; + m_flTimeStepSound = gpGlobals->time + 0.35; + } + else if ( UTIL_PointContents ( knee ) == CONTENTS_WATER ) + { + step = STEP_WADE; + fvol = 0.65; + m_flTimeStepSound = gpGlobals->time + 0.6; + } + else if (UTIL_PointContents ( feet ) == CONTENTS_WATER ) + { + step = STEP_SLOSH; + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + } + else + { + // find texture under player, if different from current texture, + // get material type + + start = end = center; // center point of player BB + start.z = end.z = pev->absmin.z; // copy zmin + start.z += 4.0; // extend start up + end.z -= 24.0; // extend end down + + start.CopyToArray(rgfl1); + end.CopyToArray(rgfl2); + + pTextureName = TRACE_TEXTURE( ENT( pev->groundentity), rgfl1, rgfl2 ); + if ( pTextureName ) + { + // strip leading '-0' or '{' or '!' + if (*pTextureName == '-') + pTextureName += 2; + if (*pTextureName == '{' || *pTextureName == '!') + pTextureName++; + + if (_strnicmp(pTextureName, m_szTextureName, CBTEXTURENAMEMAX-1)) + { + // current texture is different from texture player is on... + // set current texture + strcpy(szbuffer, pTextureName); + szbuffer[CBTEXTURENAMEMAX - 1] = 0; + strcpy(m_szTextureName, szbuffer); + + // ALERT ( at_aiconsole, "texture: %s\n", m_szTextureName ); + + // get texture type + m_chTextureType = TEXTURETYPE_Find(m_szTextureName); + } + } + + step = MapTextureTypeStepType(m_chTextureType); + + switch (m_chTextureType) + { + default: + case CHAR_TEX_CONCRETE: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_METAL: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_DIRT: + fvol = fWalking ? 0.25 : 0.55; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_VENT: + fvol = fWalking ? 0.4 : 0.7; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_GRATE: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_TILE: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_SLOSH: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + } + } + + m_flTimeStepSound += flduck; // slower step time if ducking + + // play the sound + + // 35% volume if ducking + if ( pev->flags & FL_DUCKING ) + fvol *= 0.35; + } +} + +void CBasePlayer::PowerUpThink(void) +{ + int iPowerUp = 0; + bool bUpdate = FALSE; + + //Quad time ran out + if ( m_iQuakeItems & IT_QUAD && m_flSuperDamageFinished < gpGlobals->time ) + { + //Clear the glowing shell effect + pev->renderfx = kRenderFxNone; + //Reset quad timer + m_flSuperDamageFinished = 0.0; + m_bPlayedQuadSound = FALSE; + + //Remove the powerup + m_iQuakeItems &= ~IT_QUAD; + + //We have other powerups, choose the one with more time remaining + if ( m_iQuakeItems & IT_INVISIBILITY || m_iQuakeItems & IT_INVULNERABILITY ) + { + if ( m_iQuakeItems & IT_INVULNERABILITY ) + { + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 255, 128, 0 ); // RGB + pev->renderamt = 100; // Shell size + + iPowerUp = 2; + } + + if ( m_iQuakeItems & IT_INVISIBILITY ) + { + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 128, 128, 128 ); // RGB + pev->renderamt = 5; // Shell size + } + } + + bUpdate = TRUE; + } + + if ( m_iQuakeItems & IT_QUAD && m_flSuperDamageFinished <= gpGlobals->time + 3 && !m_bPlayedQuadSound ) + { + ClientPrint(pev, HUD_PRINTNOTIFY, "#Quad_Damage_Off" ); + EMIT_SOUND( ENT(pev), CHAN_ITEM, "items/damage2.wav", 1, ATTN_NORM ); + + m_bPlayedQuadSound = TRUE; + } + + //Env Suit time ran out + if ( m_iQuakeItems & IT_SUIT && m_flRadsuitFinished < gpGlobals->time ) + { + m_flRadsuitFinished = 0.0; + m_bPlayedEnvSound = FALSE; + + //Remove the powerup + m_iQuakeItems &= ~IT_SUIT; + + //We have other powerups, choose the one with more time remaining + if ( m_iQuakeItems & IT_QUAD || m_iQuakeItems & IT_INVULNERABILITY ) + { + + if ( m_iQuakeItems & IT_INVULNERABILITY && m_iQuakeItems & IT_QUAD ) + { + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 255, 125, 255 ); // RGB + pev->renderamt = 100; // Shell size + } + + else if ( m_flSuperDamageFinished > m_flInvincibleFinished ) + { + pev->renderfx = kRenderFxNone; + pev->rendermode = kRenderNormal; + pev->renderamt = 255; + + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 128, 128, 255 ); // RGB + pev->renderamt = 100; // Shell size + } + else + { + pev->renderfx = kRenderFxNone; + pev->rendermode = kRenderNormal; + pev->renderamt = 255; + + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 255, 128, 0 ); // RGB + pev->renderamt = 100; // Shell size + } + } + else //Clear the invisi screen effect + { + pev->renderfx = kRenderFxNone; + pev->rendermode = kRenderNormal; + pev->renderamt = 255; + } + } + + if ( m_iQuakeItems & IT_SUIT && m_flRadsuitFinished <= gpGlobals->time + 3 && !m_bPlayedEnvSound ) + { + ClientPrint(pev, HUD_PRINTNOTIFY, "#BioSuit_Off" ); + EMIT_SOUND( ENT(pev), CHAN_ITEM, "items/suit2.wav", 1, ATTN_NORM ); + + m_bPlayedEnvSound = TRUE; + + } + + //Invisibility time ran out + if ( m_iQuakeItems & IT_INVISIBILITY && m_flInvisibleFinished < gpGlobals->time ) + { + //Reset the invi timer + m_flInvisibleFinished = 0.0; + m_bPlayedProtectSound = FALSE; + + //Remove the powerup + m_iQuakeItems &= ~IT_INVISIBILITY; + + pev->renderfx = kRenderFxNone; + pev->rendermode = kRenderNormal; + pev->renderamt = 255; + + //We have other powerups, choose the one with more time remaining + if ( m_iQuakeItems & IT_QUAD || m_iQuakeItems & IT_INVULNERABILITY ) + { + if ( m_iQuakeItems & IT_QUAD && m_iQuakeItems & IT_INVULNERABILITY ) + { + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 255, 125, 255 ); // RGB + pev->renderamt = 100; // Shell size + + } + + else if ( m_flSuperDamageFinished > m_flInvincibleFinished ) + { + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 128, 128, 255 ); // RGB + pev->renderamt = 100; // Shell size + } + else + { + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 255, 128, 0 ); // RGB + pev->renderamt = 100; // Shell size + } + } + else //Clear the invisi screen effect + { + pev->renderfx = kRenderFxNone; + pev->rendermode = kRenderNormal; + pev->renderamt = 255; + } + + //Forces our view model to re-appear + W_SetCurrentAmmo(); + + } + + if ( m_iQuakeItems & IT_INVISIBILITY && m_flInvisibleFinished <= gpGlobals->time + 3 && !m_bPlayedInvSound ) + { + ClientPrint(pev, HUD_PRINTNOTIFY, "#Ring_Shadows_Off" ); + EMIT_SOUND( ENT(pev), CHAN_ITEM, "items/inv2.wav", 1, ATTN_NORM ); + + m_bPlayedInvSound = TRUE; + + } + + //666 time ran out + if ( m_iQuakeItems & IT_INVULNERABILITY && m_flInvincibleFinished < gpGlobals->time ) + { + //Clear the glowing shell effect + pev->renderfx = kRenderFxNone; + //Reset 666 timer + m_flInvincibleFinished = 0.0; + m_bPlayedProtectSound = FALSE; + + //Remove the powerup + m_iQuakeItems &= ~IT_INVULNERABILITY; + + //We have other powerups, choose the one with more time remaining + if ( m_iQuakeItems & IT_QUAD || m_iQuakeItems & IT_INVISIBILITY ) + { + if ( m_flSuperDamageFinished > m_flInvisibleFinished ) + { + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 128, 128, 255 ); // RGB + pev->renderamt = 100; // Shell size + + iPowerUp = 1; + } + else + { + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 128, 128, 128 ); // RGB + pev->renderamt = 5; // Shell size + } + } + + bUpdate = TRUE; + } + + if ( m_iQuakeItems & IT_INVULNERABILITY && m_flInvincibleFinished <= gpGlobals->time + 3 && !m_bPlayedProtectSound ) + { + ClientPrint(pev, HUD_PRINTNOTIFY, "#Protection_Off" ); + EMIT_SOUND( ENT(pev), CHAN_ITEM, "items/protect2.wav", 1, ATTN_NORM ); + + m_bPlayedProtectSound = TRUE; + } + + if ( bUpdate ) + { + W_SetCurrentAmmo(); + + PLAYBACK_EVENT_FULL( FEV_GLOBAL | FEV_RELIABLE, + edict(), g_usPowerUp, 0, (float *)&g_vecZero, (float *)&g_vecZero, + (float)iPowerUp, 0.0, entindex(), pev->team, 1, 0 ); + } + +} + + +#define CLIMB_SHAKE_FREQUENCY 22 // how many frames in between screen shakes when climbing +#define MAX_CLIMB_SPEED 200 // fastest vertical climbing speed possible +#define CLIMB_SPEED_DEC 15 // climbing deceleration rate +#define CLIMB_PUNCH_X -7 // how far to 'punch' client X axis when climbing +#define CLIMB_PUNCH_Z 7 // how far to 'punch' client Z axis when climbing + +void CBasePlayer::PreThink(void) +{ + int buttonsChanged = (m_afButtonLast ^ pev->button); // These buttons have changed this frame + + // Debounced button codes for pressed/released + // UNDONE: Do we need auto-repeat? + m_afButtonPressed = buttonsChanged & pev->button; // The changed ones still down are "pressed" + m_afButtonReleased = buttonsChanged & (~pev->button); // The ones not down are "released" + + g_pGameRules->PlayerThink( this ); + + if ( g_fGameOver ) + return; // intermission or finale + + UTIL_MakeVectors(pev->v_angle); // is this still used? + + ItemPreFrame( ); + WaterMove(); + + if ( g_pGameRules && g_pGameRules->FAllowFlashlight() ) + m_iHideHUD &= ~HIDEHUD_FLASHLIGHT; + else + m_iHideHUD |= HIDEHUD_FLASHLIGHT; + + + // JOHN: checks if new client data (for HUD and view control) needs to be sent to the client + UpdateClientData(); + + CheckTimeBasedDamage(); + + CheckSuitUpdate(); + + // Observer Button Handling + if ( pev->iuser1 != 0 ) + { + Observer_HandleButtons(); + return; + } + + //Run Powerup think (removes powerups over time, etc) + PowerUpThink(); + + // Dead think only if they're not an observer + if (pev->deadflag >= DEAD_DYING) + { + PlayerDeathThink(); + return; + } + + // So the correct flags get sent to client asap. + // + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + pev->flags |= FL_ONTRAIN; + else + pev->flags &= ~FL_ONTRAIN; + + // Train speed control + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + { + CBaseEntity *pTrain = CBaseEntity::Instance( pev->groundentity ); + float vel; + + if ( !pTrain ) + { + TraceResult trainTrace; + // Maybe this is on the other side of a level transition + UTIL_TraceLine( pev->origin, pev->origin + Vector(0,0,-38), ignore_monsters, ENT(pev), &trainTrace ); + + // HACKHACK - Just look for the func_tracktrain classname + if ( trainTrace.flFraction != 1.0 && trainTrace.pHit ) + pTrain = CBaseEntity::Instance( trainTrace.pHit ); + + + if ( !pTrain || !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) || !pTrain->OnControls(pev) ) + { + //ALERT( at_error, "In train mode with no train!\n" ); + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + } + else if ( !FBitSet( pev->flags, FL_ONGROUND ) || FBitSet( pTrain->pev->spawnflags, SF_TRACKTRAIN_NOCONTROL ) || (pev->button & (IN_MOVELEFT|IN_MOVERIGHT) ) ) + { + // Turn off the train if you jump, strafe, or the train controls go dead + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + + pev->velocity = g_vecZero; + vel = 0; + if ( m_afButtonPressed & IN_FORWARD ) + { + vel = 1; + pTrain->Use( this, this, USE_SET, (float)vel ); + } + else if ( m_afButtonPressed & IN_BACK ) + { + vel = -1; + pTrain->Use( this, this, USE_SET, (float)vel ); + } + + if (vel) + { + m_iTrain = TrainSpeed(pTrain->pev->speed, pTrain->pev->impulse); + m_iTrain |= TRAIN_ACTIVE|TRAIN_NEW; + } + + } else if (m_iTrain & TRAIN_ACTIVE) + m_iTrain = TRAIN_NEW; // turn off train + + if (pev->button & IN_JUMP) + { + // If on a ladder, jump off the ladder + // else Jump + Jump(); + } + + // QUAKECLASSIC + // Duck removed + + // play a footstep if it's time - this will eventually be frame-based. not time based. + UpdateStepSound(); + + if ( !FBitSet ( pev->flags, FL_ONGROUND ) ) + { + m_flFallVelocity = -pev->velocity.z; + } + + // StudioFrameAdvance( );//!!!HACKHACK!!! Can't be hit by traceline when not animating? + + // Clear out ladder pointer + m_hEnemy = NULL; + + if ( m_afPhysicsFlags & PFLAG_ONBARNACLE ) + { + pev->velocity = g_vecZero; + } +} +/* Time based Damage works as follows: + 1) There are several types of timebased damage: + + #define DMG_PARALYZE (1 << 14) // slows affected creature down + #define DMG_NERVEGAS (1 << 15) // nerve toxins, very bad + #define DMG_POISON (1 << 16) // blood poisioning + #define DMG_RADIATION (1 << 17) // radiation exposure + #define DMG_DROWNRECOVER (1 << 18) // drown recovery + #define DMG_ACID (1 << 19) // toxic chemicals or acid burns + #define DMG_SLOWBURN (1 << 20) // in an oven + #define DMG_SLOWFREEZE (1 << 21) // in a subzero freezer + + 2) A new hit inflicting tbd restarts the tbd counter - each monster has an 8bit counter, + per damage type. The counter is decremented every second, so the maximum time + an effect will last is 255/60 = 4.25 minutes. Of course, staying within the radius + of a damaging effect like fire, nervegas, radiation will continually reset the counter to max. + + 3) Every second that a tbd counter is running, the player takes damage. The damage + is determined by the type of tdb. + Paralyze - 1/2 movement rate, 30 second duration. + Nervegas - 5 points per second, 16 second duration = 80 points max dose. + Poison - 2 points per second, 25 second duration = 50 points max dose. + Radiation - 1 point per second, 50 second duration = 50 points max dose. + Drown - 5 points per second, 2 second duration. + Acid/Chemical - 5 points per second, 10 second duration = 50 points max. + Burn - 10 points per second, 2 second duration. + Freeze - 3 points per second, 10 second duration = 30 points max. + + 4) Certain actions or countermeasures counteract the damaging effects of tbds: + + Armor/Heater/Cooler - Chemical(acid),burn, freeze all do damage to armor power, then to body + - recharged by suit recharger + Air In Lungs - drowning damage is done to air in lungs first, then to body + - recharged by poking head out of water + - 10 seconds if swiming fast + Air In SCUBA - drowning damage is done to air in tanks first, then to body + - 2 minutes in tanks. Need new tank once empty. + Radiation Syringe - Each syringe full provides protection vs one radiation dosage + Antitoxin Syringe - Each syringe full provides protection vs one poisoning (nervegas or poison). + Health kit - Immediate stop to acid/chemical, fire or freeze damage. + Radiation Shower - Immediate stop to radiation damage, acid/chemical or fire damage. + + +*/ + +// If player is taking time based damage, continue doing damage to player - +// this simulates the effect of being poisoned, gassed, dosed with radiation etc - +// anything that continues to do damage even after the initial contact stops. +// Update all time based damage counters, and shut off any that are done. + +// The m_bitsDamageType bit MUST be set if any damage is to be taken. +// This routine will detect the initial on value of the m_bitsDamageType +// and init the appropriate counter. Only processes damage every second. + +//#define PARALYZE_DURATION 30 // number of 2 second intervals to take damage +//#define PARALYZE_DAMAGE 0.0 // damage to take each 2 second interval + +//#define NERVEGAS_DURATION 16 +//#define NERVEGAS_DAMAGE 5.0 + +//#define POISON_DURATION 25 +//#define POISON_DAMAGE 2.0 + +//#define RADIATION_DURATION 50 +//#define RADIATION_DAMAGE 1.0 + +//#define ACID_DURATION 10 +//#define ACID_DAMAGE 5.0 + +//#define SLOWBURN_DURATION 2 +//#define SLOWBURN_DAMAGE 1.0 + +//#define SLOWFREEZE_DURATION 1.0 +//#define SLOWFREEZE_DAMAGE 3.0 + +/* */ + + +void CBasePlayer::CheckTimeBasedDamage() +{ + int i; + BYTE bDuration = 0; + + static float gtbdPrev = 0.0; + + if (!(m_bitsDamageType & DMG_TIMEBASED)) + return; + + // only check for time based damage approx. every 2 seconds + if (abs(gpGlobals->time - m_tbdPrev) < 2.0) + return; + + m_tbdPrev = gpGlobals->time; + + for (i = 0; i < CDMG_TIMEBASED; i++) + { + // make sure bit is set for damage type + if (m_bitsDamageType & (DMG_PARALYZE << i)) + { + switch (i) + { + case itbd_Paralyze: + // UNDONE - flag movement as half-speed + bDuration = PARALYZE_DURATION; + break; + case itbd_NerveGas: +// TakeDamage(pev, pev, NERVEGAS_DAMAGE, DMG_GENERIC); + bDuration = NERVEGAS_DURATION; + break; + case itbd_Poison: + TakeDamage(pev, pev, POISON_DAMAGE, DMG_GENERIC); + bDuration = POISON_DURATION; + break; + case itbd_Radiation: +// TakeDamage(pev, pev, RADIATION_DAMAGE, DMG_GENERIC); + bDuration = RADIATION_DURATION; + break; + case itbd_DrownRecover: + // NOTE: this hack is actually used to RESTORE health + // after the player has been drowning and finally takes a breath + if (m_idrowndmg > m_idrownrestored) + { + int idif = min(m_idrowndmg - m_idrownrestored, 10); + + TakeHealth(idif, DMG_GENERIC); + m_idrownrestored += idif; + } + bDuration = 4; // get up to 5*10 = 50 points back + break; + case itbd_Acid: +// TakeDamage(pev, pev, ACID_DAMAGE, DMG_GENERIC); + bDuration = ACID_DURATION; + break; + case itbd_SlowBurn: +// TakeDamage(pev, pev, SLOWBURN_DAMAGE, DMG_GENERIC); + bDuration = SLOWBURN_DURATION; + break; + case itbd_SlowFreeze: +// TakeDamage(pev, pev, SLOWFREEZE_DAMAGE, DMG_GENERIC); + bDuration = SLOWFREEZE_DURATION; + break; + default: + bDuration = 0; + } + + if (m_rgbTimeBasedDamage[i]) + { + // use up an antitoxin on poison or nervegas after a few seconds of damage + if (((i == itbd_NerveGas) && (m_rgbTimeBasedDamage[i] < NERVEGAS_DURATION)) || + ((i == itbd_Poison) && (m_rgbTimeBasedDamage[i] < POISON_DURATION))) + { + if (m_rgItems[ITEM_ANTIDOTE]) + { + m_rgbTimeBasedDamage[i] = 0; + m_rgItems[ITEM_ANTIDOTE]--; + SetSuitUpdate("!HEV_HEAL4", FALSE, SUIT_REPEAT_OK); + } + } + + + // decrement damage duration, detect when done. + if (!m_rgbTimeBasedDamage[i] || --m_rgbTimeBasedDamage[i] == 0) + { + m_rgbTimeBasedDamage[i] = 0; + // if we're done, clear damage bits + m_bitsDamageType &= ~(DMG_PARALYZE << i); + } + } + else + // first time taking this damage type - init damage duration + m_rgbTimeBasedDamage[i] = bDuration; + } + } +} + +/* +THE POWER SUIT + +The Suit provides 3 main functions: Protection, Notification and Augmentation. +Some functions are automatic, some require power. +The player gets the suit shortly after getting off the train in C1A0 and it stays +with him for the entire game. + +Protection + + Heat/Cold + When the player enters a hot/cold area, the heating/cooling indicator on the suit + will come on and the battery will drain while the player stays in the area. + After the battery is dead, the player starts to take damage. + This feature is built into the suit and is automatically engaged. + Radiation Syringe + This will cause the player to be immune from the effects of radiation for N seconds. Single use item. + Anti-Toxin Syringe + This will cure the player from being poisoned. Single use item. + Health + Small (1st aid kits, food, etc.) + Large (boxes on walls) + Armor + The armor works using energy to create a protective field that deflects a + percentage of damage projectile and explosive attacks. After the armor has been deployed, + it will attempt to recharge itself to full capacity with the energy reserves from the battery. + It takes the armor N seconds to fully charge. + +Notification (via the HUD) + +x Health +x Ammo +x Automatic Health Care + Notifies the player when automatic healing has been engaged. +x Geiger counter + Classic Geiger counter sound and status bar at top of HUD + alerts player to dangerous levels of radiation. This is not visible when radiation levels are normal. +x Poison + Armor + Displays the current level of armor. + +Augmentation + + Reanimation (w/adrenaline) + Causes the player to come back to life after he has been dead for 3 seconds. + Will not work if player was gibbed. Single use. + Long Jump + Used by hitting the ??? key(s). Caused the player to further than normal. + SCUBA + Used automatically after picked up and after player enters the water. + Works for N seconds. Single use. + +Things powered by the battery + + Armor + Uses N watts for every M units of damage. + Heat/Cool + Uses N watts for every second in hot/cold area. + Long Jump + Uses N watts for every jump. + Alien Cloak + Uses N watts for each use. Each use lasts M seconds. + Alien Shield + Augments armor. Reduces Armor drain by one half + +*/ + +// if in range of radiation source, ping geiger counter + +#define GEIGERDELAY 0.25 + +void CBasePlayer :: UpdateGeigerCounter( void ) +{ + BYTE range; + + // delay per update ie: don't flood net with these msgs + if (gpGlobals->time < m_flgeigerDelay) + return; + + m_flgeigerDelay = gpGlobals->time + GEIGERDELAY; + + // send range to radition source to client + + range = (BYTE) (m_flgeigerRange / 4); + + if (range != m_igeigerRangePrev) + { + m_igeigerRangePrev = range; + + MESSAGE_BEGIN( MSG_ONE, gmsgGeigerRange, NULL, pev ); + WRITE_BYTE( range ); + MESSAGE_END(); + } + + // reset counter and semaphore + if (!RANDOM_LONG(0,3)) + m_flgeigerRange = 1000; + +} + +/* +================ +CheckSuitUpdate + +Play suit update if it's time +================ +*/ + +#define SUITUPDATETIME 3.5 +#define SUITFIRSTUPDATETIME 0.1 + +void CBasePlayer::CheckSuitUpdate() +{ + int i; + int isentence = 0; + int isearch = m_iSuitPlayNext; + + // Ignore suit updates if no suit + if ( !(pev->weapons & (1<IsMultiplayer() ) + { + // don't bother updating HEV voice in multiplayer. + return; + } + + if ( gpGlobals->time >= m_flSuitUpdate && m_flSuitUpdate > 0) + { + // play a sentence off of the end of the queue + for (i = 0; i < CSUITPLAYLIST; i++) + { + if (isentence = m_rgSuitPlayList[isearch]) + break; + + if (++isearch == CSUITPLAYLIST) + isearch = 0; + } + + if (isentence) + { + m_rgSuitPlayList[isearch] = 0; + if (isentence > 0) + { + // play sentence number + + char sentence[CBSENTENCENAME_MAX+1]; + strcpy(sentence, "!"); + strcat(sentence, gszallsentencenames[isentence]); + EMIT_SOUND_SUIT(ENT(pev), sentence); + } + else + { + // play sentence group + EMIT_GROUPID_SUIT(ENT(pev), -isentence); + } + m_flSuitUpdate = gpGlobals->time + SUITUPDATETIME; + } + else + // queue is empty, don't check + m_flSuitUpdate = 0; + } +} + +// add sentence to suit playlist queue. if fgroup is true, then +// name is a sentence group (HEV_AA), otherwise name is a specific +// sentence name ie: !HEV_AA0. If iNoRepeat is specified in +// seconds, then we won't repeat playback of this word or sentence +// for at least that number of seconds. + +void CBasePlayer::SetSuitUpdate(char *name, int fgroup, int iNoRepeatTime) +{ + int i; + int isentence; + int iempty = -1; + + + // Ignore suit updates if no suit + if ( !(pev->weapons & (1<IsMultiplayer() ) + { + // due to static channel design, etc. We don't play HEV sounds in multiplayer right now. + return; + } + + // if name == NULL, then clear out the queue + + if (!name) + { + for (i = 0; i < CSUITPLAYLIST; i++) + m_rgSuitPlayList[i] = 0; + return; + } + // get sentence or group number + if (!fgroup) + { + isentence = SENTENCEG_Lookup(name, NULL); + if (isentence < 0) + return; + } + else + // mark group number as negative + isentence = -SENTENCEG_GetIndex(name); + + // check norepeat list - this list lets us cancel + // the playback of words or sentences that have already + // been played within a certain time. + + for (i = 0; i < CSUITNOREPEAT; i++) + { + if (isentence == m_rgiSuitNoRepeat[i]) + { + // this sentence or group is already in + // the norepeat list + + if (m_rgflSuitNoRepeatTime[i] < gpGlobals->time) + { + // norepeat time has expired, clear it out + m_rgiSuitNoRepeat[i] = 0; + m_rgflSuitNoRepeatTime[i] = 0.0; + iempty = i; + break; + } + else + { + // don't play, still marked as norepeat + return; + } + } + // keep track of empty slot + if (!m_rgiSuitNoRepeat[i]) + iempty = i; + } + + // sentence is not in norepeat list, save if norepeat time was given + + if (iNoRepeatTime) + { + if (iempty < 0) + iempty = RANDOM_LONG(0, CSUITNOREPEAT-1); // pick random slot to take over + m_rgiSuitNoRepeat[iempty] = isentence; + m_rgflSuitNoRepeatTime[iempty] = iNoRepeatTime + gpGlobals->time; + } + + // find empty spot in queue, or overwrite last spot + + m_rgSuitPlayList[m_iSuitPlayNext++] = isentence; + if (m_iSuitPlayNext == CSUITPLAYLIST) + m_iSuitPlayNext = 0; + + if (m_flSuitUpdate <= gpGlobals->time) + { + if (m_flSuitUpdate == 0) + // play queue is empty, don't delay too long before playback + m_flSuitUpdate = gpGlobals->time + SUITFIRSTUPDATETIME; + else + m_flSuitUpdate = gpGlobals->time + SUITUPDATETIME; + } + +} + +/* +================ +CheckPowerups + +Check for turning off powerups + +GLOBALS ASSUMED SET: g_ulModelIndexPlayer +================ +*/ + static void +CheckPowerups(entvars_t *pev) +{ + if (pev->health <= 0) + return; + + pev->modelindex = g_ulModelIndexPlayer; // don't use eyes +} + +void CBasePlayer::PostThink() +{ + if ( g_fGameOver ) + goto pt_end; // intermission or finale + + if (!IsAlive()) + goto pt_end; + + // Handle Tank controlling + if ( m_pTank != NULL ) + { // if they've moved too far from the gun, or selected a weapon, unuse the gun + if ( m_pTank->OnControls( pev ) && !pev->weaponmodel ) + { + m_pTank->Use( this, this, USE_SET, 2 ); // try fire the gun + } + else + { // they've moved off the platform + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + } + } + +// do weapon stuff + ItemPostFrame( ); + +// check to see if player landed hard enough to make a sound +// falling farther than half of the maximum safe distance, but not as far a max safe distance will +// play a bootscrape sound, and no damage will be inflicted. Fallling a distance shorter than half +// of maximum safe distance will make no sound. Falling farther than max safe distance will play a +// fallpain sound, and damage will be inflicted based on how far the player fell + + if ( (FBitSet(pev->flags, FL_ONGROUND)) && (pev->health > 0) && m_flFallVelocity >= PLAYER_FALL_PUNCH_THRESHHOLD ) + { + float fvol = 0.5; + + // ALERT ( at_console, "%f\n", m_flFallVelocity ); + + if (pev->watertype == CONTENT_WATER) + { + // Did he hit the world or a non-moving entity? + // BUG - this happens all the time in water, especially when + // BUG - water has current force + // if ( !pev->groundentity || VARS(pev->groundentity)->velocity.z == 0 ) + // EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM); + } + else if ( m_flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED ) + {// after this point, we start doing damage + float flFallDamage = g_pGameRules->FlPlayerFallDamage( this ); + + if ( flFallDamage > pev->health ) + {//splat + // note: play on item channel because we play footstep landing on body channel + EMIT_SOUND(ENT(pev), CHAN_ITEM, "common/bodysplat.wav", 1, ATTN_NORM); + } + + if ( flFallDamage > 0 ) + { + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), flFallDamage, DMG_FALL ); + pev->punchangle.x = 0; + } + + fvol = 1.0; + } + else if ( m_flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED / 2 ) + { + // EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_jumpland2.wav", 1, ATTN_NORM); + fvol = 0.85; + } + else if ( m_flFallVelocity < PLAYER_MIN_BOUNCE_SPEED ) + { + fvol = 0; + } + + if ( fvol > 0.0 ) + { + // get current texture under player right away + m_flTimeStepSound = 0; + UpdateStepSound(); + } + + if ( IsAlive() ) + { + SetAnimation( PLAYER_WALK ); + } + } + + if (FBitSet(pev->flags, FL_ONGROUND)) + { + //if (m_flFallVelocity > 64 && !g_pGameRules->IsMultiplayer()) + // TODO Make noise + m_flFallVelocity = 0; + } + + // select the proper animation for the player character + if ( IsAlive() ) + { + if (!pev->velocity.x && !pev->velocity.y) + SetAnimation( PLAYER_IDLE ); + else if ((pev->velocity.x || pev->velocity.y) && (FBitSet(pev->flags, FL_ONGROUND))) + SetAnimation( PLAYER_WALK ); + else if (pev->waterlevel > 1) + SetAnimation( PLAYER_WALK ); + } + + StudioFrameAdvance( ); + CheckPowerups(pev); + +pt_end: + // Decay timers on weapons + // go through all of the weapons and make a list of the ones to pack + for ( int i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + CBasePlayerItem *pPlayerItem = m_rgpPlayerItems[ i ]; + + while ( pPlayerItem ) + { + CBasePlayerWeapon *gun; + + gun = (CBasePlayerWeapon *)pPlayerItem->GetWeaponPtr(); + + if ( gun && gun->UseDecrement() ) + { + gun->m_flNextPrimaryAttack = max( gun->m_flNextPrimaryAttack - gpGlobals->frametime, -1.0 ); + gun->m_flNextSecondaryAttack = max( gun->m_flNextSecondaryAttack - gpGlobals->frametime, -0.001 ); + + if ( gun->m_flTimeWeaponIdle != 1000 ) + { + gun->m_flTimeWeaponIdle = max( gun->m_flTimeWeaponIdle - gpGlobals->frametime, -0.001 ); + } + } + + pPlayerItem = pPlayerItem->m_pNext; + } + } + } + + m_flNextAttack -= gpGlobals->frametime; + if ( m_flNextAttack < -0.001 ) + m_flNextAttack = -0.001; + + // Track button info so we can detect 'pressed' and 'released' buttons next frame + m_afButtonLast = pev->button; +} + +BOOL IsSpawnPointValid( CBaseEntity *pPlayer, CBaseEntity *pSpot ) +{ + CBaseEntity *ent = NULL; + + if ( !pSpot->IsTriggered( pPlayer ) ) + { + return FALSE; + } + + while ( (ent = UTIL_FindEntityInSphere( ent, pSpot->pev->origin, 96 )) != NULL ) + { + // if ent is a client, don't spawn on 'em + if ( ent->pev->flags & FL_CLIENT ) + return FALSE; + } + + return TRUE; +} + +DLL_GLOBAL CBaseEntity *g_pLastSpawn; +inline int FNullEnt( CBaseEntity *ent ) { return (ent == NULL) || FNullEnt( ent->edict() ); } + +/* +============ +EntSelectSpawnPoint + +Returns the entity to spawn at + +USES AND SETS GLOBAL g_pLastSpawn +============ +*/ +edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ) +{ + CBaseEntity *pSpot; + edict_t *player; + + player = pPlayer->edict(); + +// choose a info_player_deathmatch point + if (g_pGameRules->IsCoOp()) + { + pSpot = UTIL_FindEntityByClassname( g_pLastSpawn, "info_player_coop"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + pSpot = UTIL_FindEntityByClassname( g_pLastSpawn, "info_player_start"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + else if ( g_pGameRules->IsDeathmatch() ) + { + pSpot = g_pLastSpawn; + // Randomize the start spot + for ( int i = RANDOM_LONG(1,5); i > 0; i-- ) + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + if ( FNullEnt( pSpot ) ) // skip over the null point + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + + CBaseEntity *pFirstSpot = pSpot; + + do + { + if ( pSpot ) + { + // check if pSpot is valid + if ( IsSpawnPointValid( pPlayer, pSpot ) ) + { + if ( pSpot->pev->origin == Vector( 0, 0, 0 ) ) + { + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + continue; + } + + // if so, go to pSpot + goto ReturnSpot; + } + } + // increment pSpot + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + } while ( pSpot != pFirstSpot ); // loop if we're not back to the start + + // we haven't found a place to spawn yet, so kill any guy at the first spawn point and spawn there + if ( !FNullEnt( pSpot ) ) + { + CBaseEntity *ent = NULL; + while ( (ent = UTIL_FindEntityInSphere( ent, pSpot->pev->origin, 128 )) != NULL ) + { + // if ent is a client, kill em (unless they are ourselves) + if ( ent->IsPlayer() && !(ent->edict() == player) ) + ent->TakeDamage( VARS(INDEXENT(0)), VARS(INDEXENT(0)), 300, DMG_GENERIC ); + } + goto ReturnSpot; + } + } + + // If startspot is set, (re)spawn there. + if ( FStringNull( gpGlobals->startspot ) || !strlen(STRING(gpGlobals->startspot))) + { + pSpot = UTIL_FindEntityByClassname(NULL, "info_player_start"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + else + { + pSpot = UTIL_FindEntityByTargetname( NULL, STRING(gpGlobals->startspot) ); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + +ReturnSpot: + if ( FNullEnt( pSpot ) ) + { + ALERT(at_error, "PutClientInServer: no info_player_start on level"); + return INDEXENT(0); + } + + g_pLastSpawn = pSpot; + return pSpot->edict(); +} + +void CBasePlayer::Spawn( void ) +{ + pev->classname = MAKE_STRING("player"); + pev->health = 100; + pev->armorvalue = 0; + pev->takedamage = DAMAGE_AIM; + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_WALK; + pev->max_health = pev->health; + pev->flags &= FL_PROXY; // keep proxy flag sey by engine + pev->flags |= FL_CLIENT; + pev->air_finished = gpGlobals->time + 12; + pev->dmg = 2; // initial water damage + pev->effects = 0; + pev->deadflag = DEAD_NO; + pev->dmg_take = 0; + pev->dmg_save = 0; + pev->friction = 1.0; + pev->gravity = 1.0; + m_bitsHUDDamage = -1; + m_bitsDamageType = 0; + m_afPhysicsFlags = 0; + m_fLongJump = FALSE;// no longjump module. + + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "slj", "0" ); + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "hl", "1" ); + + + m_flNextDecalTime = 0;// let this player decal as soon as he spawns. + + m_flgeigerDelay = gpGlobals->time + 2.0; // wait a few seconds until user-defined message registrations + // are recieved by all clients + + m_flTimeStepSound = 0; + m_iStepLeft = 0; + m_flFieldOfView = 0.5;// some monsters use this to determine whether or not the player is looking at them. + + m_bloodColor = BLOOD_COLOR_RED; + m_flNextAttack = UTIL_WeaponTimeBase(); + StartSneaking(); + + m_iFlashBattery = 99; + m_flFlashLightTime = 1; // force first message + +// dont let uninitialized value here hurt the player + m_flFallVelocity = 0; + + //g_pGameRules->SetDefaultPlayerTeam( this ); + g_pGameRules->GetPlayerSpawnSpot( this ); + + SET_MODEL(ENT(pev), "models/player.mdl"); + g_ulModelIndexPlayer = pev->modelindex; + pev->sequence = LookupActivity( ACT_IDLE ); + + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + + pev->view_ofs = VEC_VIEW; + Precache(); + m_HackedGunPos = Vector( 0, 32, 0 ); + + m_fNoPlayerSound = FALSE;// normal sound behavior. + + m_pLastItem = NULL; + m_fInitHUD = TRUE; + m_iClientHideHUD = -1; // force this to be recalculated + m_fWeapon = FALSE; + m_pClientActiveItem = NULL; + m_iClientBattery = -1; + + m_flLightningTime = 0.0; + + InitStatusBar(); + m_iQuakeItems = 0; + + // reset all ammo values to 0 + for ( int i = 0; i < MAX_AMMO_SLOTS; i++ ) + { + m_rgAmmo[i] = 0; + m_rgAmmoLast[i] = 0; // client ammo values also have to be reset (the death hud clear messages does on the client side) + } + + m_lastx = m_lasty = 0; + + if ( !m_bHadFirstSpawn && g_bHaveMOTD ) + { + pev->effects |= EF_NODRAW; + pev->solid = SOLID_NOT; + pev->takedamage = DAMAGE_NO; + pev->movetype = MOVETYPE_NONE; + + g_engfuncs.pfnSetClientMaxspeed( ENT( pev ), 1 ); + m_iHideHUD |= HIDEHUD_WEAPONS | HIDEHUD_FLASHLIGHT | HIDEHUD_HEALTH; + } + else + { + g_engfuncs.pfnSetClientMaxspeed( ENT( pev ), PLAYER_MAX_SPEED ); + m_iHideHUD &= ~( HIDEHUD_WEAPONS | HIDEHUD_HEALTH ); + + pev->effects &= ~EF_NODRAW; + + g_pGameRules->PlayerSpawn( this ); + + PLAYBACK_EVENT_FULL ( FEV_GLOBAL, edict(), g_sTeleport, 0.0, (float *)&pev->origin, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0); + + m_fKnownItem = false; + } + + +} + + +void CBasePlayer :: Precache( void ) +{ + // in the event that the player JUST spawned, and the level node graph + // was loaded, fix all of the node graph pointers before the game starts. + + // !!!BUGBUG - now that we have multiplayer, this needs to be moved! + if ( WorldGraph.m_fGraphPresent && !WorldGraph.m_fGraphPointersSet ) + { + if ( !WorldGraph.FSetGraphPointers() ) + { + ALERT ( at_console, "**Graph pointers were not set!\n"); + } + else + { + ALERT ( at_console, "**Graph Pointers Set!\n" ); + } + } + + // SOUNDS / MODELS ARE PRECACHED in ClientPrecache() (game specific) + // because they need to precache before any clients have connected + m_usShotgunSingle = PRECACHE_EVENT( 1, "events/shotgun1.sc" ); + m_usShotgunDouble = PRECACHE_EVENT( 1, "events/shotgun2.sc" ); + m_usAxe = PRECACHE_EVENT( 1, "events/axe.sc" ); + m_usAxeSwing = PRECACHE_EVENT( 1, "events/axeswing.sc" ); + m_usRocket = PRECACHE_EVENT( 1, "events/rocket.sc" ); + m_usGrenade = PRECACHE_EVENT( 1, "events/grenade.sc" ); + m_usLightning = PRECACHE_EVENT( 1, "events/lightning.sc" ); + m_usSpike = PRECACHE_EVENT( 1, "events/spike.sc" ); + m_usSuperSpike = PRECACHE_EVENT( 1, "events/superspike.sc" ); + + + // init geiger counter vars during spawn and each time + // we cross a level transition + + m_flgeigerRange = 1000; + m_igeigerRangePrev = 1000; + + m_bitsDamageType = 0; + m_bitsHUDDamage = -1; + + m_iClientBattery = -1; + + m_iTrain = TRAIN_NEW; + + // Make sure any necessary user messages have been registered + LinkUserMessages(); + + m_iUpdateTime = 5; // won't update for 1/2 a second + + if ( gInitHUD ) + m_fInitHUD = TRUE; +} + + +int CBasePlayer::Save( CSave &save ) +{ + if ( !CBaseMonster::Save(save) ) + return 0; + + return save.WriteFields( "PLAYER", this, m_playerSaveData, ARRAYSIZE(m_playerSaveData) ); +} + + +// +// Marks everything as new so the player will resend this to the hud. +// +void CBasePlayer::RenewItems(void) +{ + +} + + +int CBasePlayer::Restore( CRestore &restore ) +{ + if ( !CBaseMonster::Restore(restore) ) + return 0; + + int status = restore.ReadFields( "PLAYER", this, m_playerSaveData, ARRAYSIZE(m_playerSaveData) ); + + SAVERESTOREDATA *pSaveData = (SAVERESTOREDATA *)gpGlobals->pSaveData; + // landmark isn't present. + if ( !pSaveData->fUseLandmark ) + { + ALERT( at_console, "No Landmark:%s\n", pSaveData->szLandmarkName ); + + // default to normal spawn + edict_t* pentSpawnSpot = EntSelectSpawnPoint( this ); + pev->origin = VARS(pentSpawnSpot)->origin + Vector(0,0,1); + pev->angles = VARS(pentSpawnSpot)->angles; + } + pev->v_angle.z = 0; // Clear out roll + pev->angles = pev->v_angle; + + pev->fixangle = TRUE; // turn this way immediately + +// Copied from spawn() for now + m_bloodColor = BLOOD_COLOR_RED; + + g_ulModelIndexPlayer = pev->modelindex; + +/* if ( FBitSet(pev->flags, FL_DUCKING) ) + { + // Use the crouch HACK + // FixPlayerCrouchStuck( edict() ); + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + } + else + { + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + }*/ + + RenewItems(); + + return status; +} + + + +void CBasePlayer::SelectNextItem( int iItem ) +{ + CBasePlayerItem *pItem; + + pItem = m_rgpPlayerItems[ iItem ]; + + if (!pItem) + return; + + if (pItem == m_pActiveItem) + { + // select the next one in the chain + pItem = m_pActiveItem->m_pNext; + if (! pItem) + { + return; + } + + CBasePlayerItem *pLast; + pLast = pItem; + while (pLast->m_pNext) + pLast = pLast->m_pNext; + + // relink chain + pLast->m_pNext = m_pActiveItem; + m_pActiveItem->m_pNext = NULL; + m_rgpPlayerItems[ iItem ] = pItem; + } + + ResetAutoaim( ); + + // FIX, this needs to queue them up and delay + if (m_pActiveItem) + { + m_pActiveItem->Holster( ); + } + + m_pActiveItem = pItem; + + if (m_pActiveItem) + { + m_pActiveItem->Deploy( ); + m_pActiveItem->UpdateItemInfo( ); + } +} + +void CBasePlayer::SelectItem(const char *pstr) +{ + if (!pstr) + return; + + CBasePlayerItem *pItem = NULL; + + for (int i = 0; i < MAX_ITEM_TYPES; i++) + { + if (m_rgpPlayerItems[i]) + { + pItem = m_rgpPlayerItems[i]; + + while (pItem) + { + if (FClassnameIs(pItem->pev, pstr)) + break; + pItem = pItem->m_pNext; + } + } + + if (pItem) + break; + } + + if (!pItem) + return; + + + if (pItem == m_pActiveItem) + return; + + ResetAutoaim( ); + + // FIX, this needs to queue them up and delay + if (m_pActiveItem) + m_pActiveItem->Holster( ); + + m_pLastItem = m_pActiveItem; + m_pActiveItem = pItem; + + if (m_pActiveItem) + { + m_pActiveItem->Deploy( ); + m_pActiveItem->UpdateItemInfo( ); + } +} + + +void CBasePlayer::SelectLastItem(void) +{ + if (!m_pLastItem) + { + return; + } + + if ( m_pActiveItem && !m_pActiveItem->CanHolster() ) + { + return; + } + + ResetAutoaim( ); + + // FIX, this needs to queue them up and delay + if (m_pActiveItem) + m_pActiveItem->Holster( ); + + CBasePlayerItem *pTemp = m_pActiveItem; + m_pActiveItem = m_pLastItem; + m_pLastItem = pTemp; + m_pActiveItem->Deploy( ); + m_pActiveItem->UpdateItemInfo( ); +} + +//============================================== +// HasWeapons - do I have any weapons at all? +//============================================== +BOOL CBasePlayer::HasWeapons( void ) +{ + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + return TRUE; + } + } + + return FALSE; +} + +void CBasePlayer::SelectPrevItem( int iItem ) +{ +} + + +const char *CBasePlayer::TeamID( void ) +{ + if ( pev == NULL ) // Not fully connected yet + return ""; + + // return their team name + return m_szTeamName; +} + + +//============================================== +// !!!UNDONE:ultra temporary SprayCan entity to apply +// decal frame at a time. For PreAlpha CD +//============================================== +class CSprayCan : public CBaseEntity +{ +public: + void Spawn ( entvars_t *pevOwner ); + void Think( void ); + + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +void CSprayCan::Spawn ( entvars_t *pevOwner ) +{ + pev->origin = pevOwner->origin + Vector ( 0 , 0 , 32 ); + pev->angles = pevOwner->v_angle; + pev->owner = ENT(pevOwner); + pev->frame = 0; + + pev->nextthink = gpGlobals->time + 0.1; + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/sprayer.wav", 1, ATTN_NORM); +} + +void CSprayCan::Think( void ) +{ + TraceResult tr; + int playernum; + int nFrames; + CBasePlayer *pPlayer; + + pPlayer = (CBasePlayer *)GET_PRIVATE(pev->owner); + + if (pPlayer) + nFrames = pPlayer->GetCustomDecalFrames(); + else + nFrames = -1; + + playernum = ENTINDEX(pev->owner); + + // ALERT(at_console, "Spray by player %i, %i of %i\n", playernum, (int)(pev->frame + 1), nFrames); + + UTIL_MakeVectors(pev->angles); + UTIL_TraceLine ( pev->origin, pev->origin + gpGlobals->v_forward * 128, ignore_monsters, pev->owner, & tr); + + // No customization present. + if (nFrames == -1) + { + UTIL_DecalTrace( &tr, DECAL_LAMBDA6 ); + UTIL_Remove( this ); + } + else + { + UTIL_PlayerDecalTrace( &tr, playernum, pev->frame, TRUE ); + // Just painted last custom frame. + if ( pev->frame++ >= (nFrames - 1)) + UTIL_Remove( this ); + } + + pev->nextthink = gpGlobals->time + 0.1; +} + +class CBloodSplat : public CBaseEntity +{ +public: + void Spawn ( entvars_t *pevOwner ); + void Spray ( void ); +}; + +void CBloodSplat::Spawn ( entvars_t *pevOwner ) +{ + pev->origin = pevOwner->origin + Vector ( 0 , 0 , 32 ); + pev->angles = pevOwner->v_angle; + pev->owner = ENT(pevOwner); + + SetThink ( &CBloodSplat::Spray ); + pev->nextthink = gpGlobals->time + 0.1; +} + +void CBloodSplat::Spray ( void ) +{ + TraceResult tr; + + if ( g_Language != LANGUAGE_GERMAN ) + { + UTIL_MakeVectors(pev->angles); + UTIL_TraceLine ( pev->origin, pev->origin + gpGlobals->v_forward * 128, ignore_monsters, pev->owner, & tr); + + UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_RED ); + } + SetThink ( &CBloodSplat::SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; +} + +//============================================== + + + +void CBasePlayer::GiveNamedItem( const char *pszName ) +{ + edict_t *pent; + + int istr = MAKE_STRING(pszName); + + pent = CREATE_NAMED_ENTITY(istr); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in GiveNamedItem!\n" ); + return; + } + VARS( pent )->origin = pev->origin; + pent->v.spawnflags |= SF_NORESPAWN; + + DispatchSpawn( pent ); + DispatchTouch( pent, ENT( pev ) ); +} + + + +CBaseEntity *FindEntityForward( CBaseEntity *pMe ) +{ + TraceResult tr; + + UTIL_MakeVectors(pMe->pev->v_angle); + UTIL_TraceLine(pMe->pev->origin + pMe->pev->view_ofs,pMe->pev->origin + pMe->pev->view_ofs + gpGlobals->v_forward * 8192,dont_ignore_monsters, pMe->edict(), &tr ); + if ( tr.flFraction != 1.0 && !FNullEnt( tr.pHit) ) + { + CBaseEntity *pHit = CBaseEntity::Instance( tr.pHit ); + return pHit; + } + return NULL; +} + + +BOOL CBasePlayer :: FlashlightIsOn( void ) +{ + return FBitSet(pev->effects, EF_DIMLIGHT); +} + + +void CBasePlayer :: FlashlightTurnOn( void ) +{ + if ( !g_pGameRules->FAllowFlashlight() ) + { + return; + } + + if ( (pev->weapons & (1<effects, EF_DIMLIGHT); + MESSAGE_BEGIN( MSG_ONE, gmsgFlashlight, NULL, pev ); + WRITE_BYTE(1); + WRITE_BYTE(m_iFlashBattery); + MESSAGE_END(); + + m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->time; + + } +} + + +void CBasePlayer :: FlashlightTurnOff( void ) +{ + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, SOUND_FLASHLIGHT_OFF, 1.0, ATTN_NORM, 0, PITCH_NORM ); + ClearBits(pev->effects, EF_DIMLIGHT); + MESSAGE_BEGIN( MSG_ONE, gmsgFlashlight, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(m_iFlashBattery); + MESSAGE_END(); + + m_flFlashLightTime = FLASH_CHARGE_TIME + gpGlobals->time; + +} + +/* +=============== +ForceClientDllUpdate + +When recording a demo, we need to have the server tell us the entire client state +so that the client side .dll can behave correctly. +Reset stuff so that the state is transmitted. +=============== +*/ +void CBasePlayer :: ForceClientDllUpdate( void ) +{ + m_iClientHealth = -1; + m_iClientBattery = -1; + m_iClientQuakeItems = -1; + m_iClientQuakeWeapon = -1; + m_iTrain |= TRAIN_NEW; // Force new train message. + m_fWeapon = FALSE; // Force weapon send + m_fKnownItem = FALSE; // Force weaponinit messages. + m_fInitHUD = TRUE; // Force HUD gmsgResetHUD message + + // Now force all the necessary messages + // to be sent. + UpdateClientData(); +} + +/* +============ +ImpulseCommands +============ +*/ +extern float g_flWeaponCheat; + +void CBasePlayer::ImpulseCommands( ) +{ + TraceResult tr;// UNDONE: kill me! This is temporary for PreAlpha CDs + + // Handle use events + PlayerUse(); + + int iImpulse = (int)pev->impulse; + + // QUAKECLASSIC + // Handle weapon switches first +/* if (iImpulse >= 1 && iImpulse <= 8) + { + W_ChangeWeapon( iImpulse ); + } + else + {*/ + switch (iImpulse) + { + + // QUAKECLASSIC + case 10: + W_CycleWeaponCommand(); + break; + case 12: + W_CycleWeaponReverseCommand(); + break; + + case 99: + { + + int iOn; + + if (!gmsgLogo) + { + iOn = 1; + gmsgLogo = REG_USER_MSG("Logo", 1); + } + else + { + iOn = 0; + } + + ASSERT( gmsgLogo > 0 ); + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgLogo, NULL, pev ); + WRITE_BYTE(iOn); + MESSAGE_END(); + + if(!iOn) + gmsgLogo = 0; + break; + } + /* case 100: + // temporary flashlight for level designers + if ( FlashlightIsOn() ) + { + FlashlightTurnOff(); + } + else + { + FlashlightTurnOn(); + } + break;*/ + + case 201:// paint decal + + if ( gpGlobals->time < m_flNextDecalTime ) + { + // too early! + break; + } + + UTIL_MakeVectors(pev->v_angle); + UTIL_TraceLine ( pev->origin + pev->view_ofs, pev->origin + pev->view_ofs + gpGlobals->v_forward * 128, ignore_monsters, ENT(pev), & tr); + + if ( tr.flFraction != 1.0 ) + {// line hit something, so paint a decal + m_flNextDecalTime = gpGlobals->time + CVAR_GET_FLOAT("decalfrequency"); + CSprayCan *pCan = GetClassPtr((CSprayCan *)NULL); + pCan->Spawn( pev ); + } + + break; + + default: + // check all of the cheat impulse commands now + CheatImpulseCommands( iImpulse ); + break; + } + //} + + pev->impulse = 0; +} + +//========================================================= +//========================================================= +void CBasePlayer::CheatImpulseCommands( int iImpulse ) +{ +#if !defined( HLDEMO_BUILD ) + if ( g_flWeaponCheat == 0.0 ) + { + return; + } + + CBaseEntity *pEntity; + TraceResult tr; + + switch ( iImpulse ) + { + case 76: + { + if (!giPrecacheGrunt) + { + giPrecacheGrunt = 1; + ALERT(at_console, "You must now restart to use Grunt-o-matic.\n"); + } + else + { + UTIL_MakeVectors( Vector( 0, pev->v_angle.y, 0 ) ); + Create("monster_human_grunt", pev->origin + gpGlobals->v_forward * 128, pev->angles); + } + break; + } + + + case 101: + gEvilImpulse101 = TRUE; + m_iAmmoNails = 200; + m_iAmmoShells = 100; + m_iAmmoRockets = 100; + m_iAmmoCells = 100; + m_iQuakeItems |= IT_NAILGUN | IT_SUPER_NAILGUN | IT_SUPER_SHOTGUN | IT_ROCKET_LAUNCHER | IT_GRENADE_LAUNCHER | IT_LIGHTNING; + CheckAmmo(); + W_SetCurrentAmmo(); + gEvilImpulse101 = FALSE; + break; + + case 102: + // Gibbage!!! + CGib::SpawnRandomGibs( pev, 1, 1 ); + break; + + case 103: + // What the hell are you doing? + pEntity = FindEntityForward( this ); + if ( pEntity ) + { + CBaseMonster *pMonster = pEntity->MyMonsterPointer(); + if ( pMonster ) + pMonster->ReportAIState(); + } + break; + + case 104: + // Dump all of the global state varaibles (and global entity names) + gGlobalState.DumpGlobals(); + break; + + case 105:// player makes no sound for monsters to hear. + { + if ( m_fNoPlayerSound ) + { + ALERT ( at_console, "Player is audible\n" ); + m_fNoPlayerSound = FALSE; + } + else + { + ALERT ( at_console, "Player is silent\n" ); + m_fNoPlayerSound = TRUE; + } + break; + } + + case 106: + // Give me the classname and targetname of this entity. + pEntity = FindEntityForward( this ); + if ( pEntity ) + { + ALERT ( at_console, "Classname: %s", STRING( pEntity->pev->classname ) ); + + if ( !FStringNull ( pEntity->pev->targetname ) ) + { + ALERT ( at_console, " - Targetname: %s\n", STRING( pEntity->pev->targetname ) ); + } + else + { + ALERT ( at_console, " - TargetName: No Targetname\n" ); + } + + ALERT ( at_console, "Model: %s\n", STRING( pEntity->pev->model ) ); + if ( pEntity->pev->globalname ) + ALERT ( at_console, "Globalname: %s\n", STRING( pEntity->pev->globalname ) ); + } + break; + + case 107: + { + TraceResult tr; + + edict_t *pWorld = g_engfuncs.pfnPEntityOfEntIndex( 0 ); + + Vector start = pev->origin + pev->view_ofs; + Vector end = start + gpGlobals->v_forward * 1024; + UTIL_TraceLine( start, end, ignore_monsters, edict(), &tr ); + if ( tr.pHit ) + pWorld = tr.pHit; + const char *pTextureName = TRACE_TEXTURE( pWorld, start, end ); + if ( pTextureName ) + ALERT( at_console, "Texture: %s\n", pTextureName ); + } + break; + case 195:// show shortest paths for entire level to nearest node + { + Create("node_viewer_fly", pev->origin, pev->angles); + } + break; + case 196:// show shortest paths for entire level to nearest node + { + Create("node_viewer_large", pev->origin, pev->angles); + } + break; + case 197:// show shortest paths for entire level to nearest node + { + Create("node_viewer_human", pev->origin, pev->angles); + } + break; + case 199:// show nearest node and all connections + { + ALERT ( at_console, "%d\n", WorldGraph.FindNearestNode ( pev->origin, bits_NODE_GROUP_REALM ) ); + WorldGraph.ShowNodeConnections ( WorldGraph.FindNearestNode ( pev->origin, bits_NODE_GROUP_REALM ) ); + } + break; + case 202:// Random blood splatter + UTIL_MakeVectors(pev->v_angle); + UTIL_TraceLine ( pev->origin + pev->view_ofs, pev->origin + pev->view_ofs + gpGlobals->v_forward * 128, ignore_monsters, ENT(pev), & tr); + + if ( tr.flFraction != 1.0 ) + {// line hit something, so paint a decal + CBloodSplat *pBlood = GetClassPtr((CBloodSplat *)NULL); + pBlood->Spawn( pev ); + } + break; + case 203:// remove creature. + pEntity = FindEntityForward( this ); + if ( pEntity ) + { + if ( pEntity->pev->takedamage ) + pEntity->SetThink(&CBaseEntity::SUB_Remove); + } + break; + } +#endif // HLDEMO_BUILD +} + +// +// Add a weapon to the player (Item == Weapon == Selectable Object) +// +int CBasePlayer::AddPlayerItem( CBasePlayerItem *pItem ) +{ + CBasePlayerItem *pInsert; + + pInsert = m_rgpPlayerItems[pItem->iItemSlot()]; + + while (pInsert) + { + if (FClassnameIs( pInsert->pev, STRING( pItem->pev->classname) )) + { + if (pItem->AddDuplicate( pInsert )) + { + g_pGameRules->PlayerGotWeapon ( this, pItem ); + pItem->CheckRespawn(); + + // ugly hack to update clip w/o an update clip message + pInsert->UpdateItemInfo( ); + if (m_pActiveItem) + m_pActiveItem->UpdateItemInfo( ); + + pItem->Kill( ); + } + else if (gEvilImpulse101) + { + // FIXME: remove anyway for deathmatch testing + pItem->Kill( ); + } + return FALSE; + } + pInsert = pInsert->m_pNext; + } + + + if (pItem->AddToPlayer( this )) + { + g_pGameRules->PlayerGotWeapon ( this, pItem ); + pItem->CheckRespawn(); + + pItem->m_pNext = m_rgpPlayerItems[pItem->iItemSlot()]; + m_rgpPlayerItems[pItem->iItemSlot()] = pItem; + + // should we switch to this item? + if ( g_pGameRules->FShouldSwitchWeapon( this, pItem ) ) + { + SwitchWeapon( pItem ); + } + + return TRUE; + } + else if (gEvilImpulse101) + { + // FIXME: remove anyway for deathmatch testing + pItem->Kill( ); + } + return FALSE; +} + + + +int CBasePlayer::RemovePlayerItem( CBasePlayerItem *pItem ) +{ + if (m_pActiveItem == pItem) + { + ResetAutoaim( ); + pItem->Holster( ); + pItem->pev->nextthink = 0;// crowbar may be trying to swing again, etc. + pItem->SetThink( NULL ); + m_pActiveItem = NULL; + pev->viewmodel = 0; + pev->weaponmodel = 0; + } + else if ( m_pLastItem == pItem ) + m_pLastItem = NULL; + + CBasePlayerItem *pPrev = m_rgpPlayerItems[pItem->iItemSlot()]; + + if (pPrev == pItem) + { + m_rgpPlayerItems[pItem->iItemSlot()] = pItem->m_pNext; + return TRUE; + } + else + { + while (pPrev && pPrev->m_pNext != pItem) + { + pPrev = pPrev->m_pNext; + } + if (pPrev) + { + pPrev->m_pNext = pItem->m_pNext; + return TRUE; + } + } + return FALSE; +} + + +// +// Returns the unique ID for the ammo, or -1 if error +// +int CBasePlayer :: GiveAmmo( int iCount, char *szName, int iMax ) +{ + if ( !szName ) + { + // no ammo. + return -1; + } + + if ( !g_pGameRules->CanHaveAmmo( this, szName, iMax ) ) + { + // game rules say I can't have any more of this ammo type. + return -1; + } + + int i = 0; + + i = GetAmmoIndex( szName ); + + if ( i < 0 || i >= MAX_AMMO_SLOTS ) + return -1; + + int iAdd = min( iCount, iMax - m_rgAmmo[i] ); + if ( iAdd < 1 ) + return i; + + m_rgAmmo[ i ] += iAdd; + + + if ( gmsgAmmoPickup ) // make sure the ammo messages have been linked first + { + // Send the message that ammo has been picked up + MESSAGE_BEGIN( MSG_ONE, gmsgAmmoPickup, NULL, pev ); + WRITE_BYTE( GetAmmoIndex(szName) ); // ammo ID + WRITE_BYTE( iAdd ); // amount + MESSAGE_END(); + } + + return i; +} + + +/* +============ +ItemPreFrame + +Called every frame by the player PreThink +============ +*/ +void CBasePlayer::ItemPreFrame() +{ + if ( m_flNextAttack > 0 ) + { + return; + } + + if (!m_pActiveItem) + return; + + m_pActiveItem->ItemPreFrame( ); +} + + +/* +============ +ItemPostFrame + +Called every frame by the player PostThink +============ +*/ +void CBasePlayer::ItemPostFrame() +{ + static int fInSelect = FALSE; + + // check if the player is using a tank + if ( m_pTank != NULL ) + return; + + // HACKHACK: To make the axe fire 0.3 sec after fire is pressed + // See if the axe should fire now + if (m_flAxeFire && m_flAxeFire <= gpGlobals->time) + { + m_flAxeFire = 0; + W_FireAxe(); + } + + if ( m_flNextAttack > 0 ) + { + return; + } + + ImpulseCommands(); + + if (!m_pActiveItem) + return; + + m_pActiveItem->ItemPostFrame( ); +} + +int CBasePlayer::AmmoInventory( int iAmmoIndex ) +{ + if (iAmmoIndex == -1) + { + return -1; + } + + return m_rgAmmo[ iAmmoIndex ]; +} + +int CBasePlayer::GetAmmoIndex(const char *psz) +{ + int i; + + if (!psz) + return -1; + + for (i = 1; i < MAX_AMMO_SLOTS; i++) + { + if ( !CBasePlayerItem::AmmoInfoArray[i].pszName ) + continue; + + if (stricmp( psz, CBasePlayerItem::AmmoInfoArray[i].pszName ) == 0) + return i; + } + + return -1; +} + +// Called from UpdateClientData +// makes sure the client has all the necessary ammo info, if values have changed +void CBasePlayer::SendAmmoUpdate(void) +{ + for (int i=0; i < MAX_AMMO_SLOTS;i++) + { + if (m_rgAmmo[i] != m_rgAmmoLast[i]) + { + m_rgAmmoLast[i] = m_rgAmmo[i]; + + ASSERT( m_rgAmmo[i] >= 0 ); + ASSERT( m_rgAmmo[i] < 255 ); + + // send "Ammo" update message + MESSAGE_BEGIN( MSG_ONE, gmsgAmmoX, NULL, pev ); + WRITE_BYTE( i ); + WRITE_BYTE( max( min( m_rgAmmo[i], 254 ), 0 ) ); // clamp the value to one byte + MESSAGE_END(); + } + } +} + +/* +========================================================= + UpdateClientData + +resends any changed player HUD info to the client. +Called every frame by PlayerPreThink +Also called at start of demo recording and playback by +ForceClientDllUpdate to ensure the demo gets messages +reflecting all of the HUD state info. +========================================================= +*/ +void CBasePlayer :: UpdateClientData( void ) +{ + if (m_fInitHUD) + { + m_fInitHUD = FALSE; + gInitHUD = FALSE; + + MESSAGE_BEGIN( MSG_ONE, gmsgResetHUD, NULL, pev ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + + if ( !m_fGameHUDInitialized ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgInitHUD, NULL, pev ); + WRITE_BYTE ( g_iTeleNum ); + for ( int i = 0; i < g_iTeleNum; i++ ) + { + WRITE_COORD( g_vecTeleMins[ i ].x ); + WRITE_COORD( g_vecTeleMins[ i ].y ); + WRITE_COORD( g_vecTeleMins[ i ].z ); + + WRITE_COORD( g_vecTeleMaxs[ i ].x ); + WRITE_COORD( g_vecTeleMaxs[ i ].y ); + WRITE_COORD( g_vecTeleMaxs[ i ].z ); + } + + CBaseEntity *pEntity = NULL; + pEntity = UTIL_FindEntityByClassname( pEntity, "env_fog" ); + + if ( pEntity ) + { + ALERT( at_console, "Map has fog!\n" ); + CClientFog *pFog = (CClientFog *)pEntity; + + //Send as bytes?. + WRITE_SHORT ( pFog->pev->rendercolor.x ); + WRITE_SHORT ( pFog->pev->rendercolor.y ); + WRITE_SHORT ( pFog->pev->rendercolor.z ); + WRITE_SHORT ( pFog->m_iStartDist ); + WRITE_SHORT ( pFog->m_iEndDist ); + } + else + ALERT( at_console, "Map doesn't have any fog!\n" ); + + + MESSAGE_END(); + + g_pGameRules->InitHUD( this ); + m_fGameHUDInitialized = TRUE; + if ( g_pGameRules->IsMultiplayer() ) + { + FireTargets( "game_playerjoin", this, this, USE_TOGGLE, 0 ); + } + } + FireTargets( "game_playerspawn", this, this, USE_TOGGLE, 0 ); + } + + if ( m_iHideHUD != m_iClientHideHUD ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgHideWeapon, NULL, pev ); + WRITE_BYTE( m_iHideHUD ); + MESSAGE_END(); + + m_iClientHideHUD = m_iHideHUD; + } + + if ( m_iFOV != m_iClientFOV ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgSetFOV, NULL, pev ); + WRITE_BYTE( m_iFOV ); + MESSAGE_END(); + + // cache FOV change at end of function, so weapon updates can see that FOV has changed + } + + // HACKHACK -- send the message to display the game title + if (gDisplayTitle) + { + MESSAGE_BEGIN( MSG_ONE, gmsgShowGameTitle, NULL, pev ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + gDisplayTitle = 0; + } + + if (pev->health != m_iClientHealth) + { +#define clamp( val, min, max ) ( ((val) > (max)) ? (max) : ( ((val) < (min)) ? (min) : (val) ) ) + int iHealth = clamp( pev->health, 0, 255 ); // make sure that no negative health values are sent + if ( pev->health > 0.0f && pev->health <= 1.0f ) + iHealth = 1; + + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgHealth, NULL, pev ); + WRITE_BYTE( iHealth ); + MESSAGE_END(); + + m_iClientHealth = pev->health; + } + + // QUAKECLASSIC + // Send down the state of the QuakeItems + if ( m_iQuakeItems != m_iClientQuakeItems ) + { + // send "items" update message + MESSAGE_BEGIN( MSG_ONE, gmsgQItems, NULL, pev ); + WRITE_LONG( m_iQuakeItems ); + MESSAGE_END(); + + m_iClientQuakeItems = m_iQuakeItems; + } + + if (pev->armorvalue != m_iClientBattery) + { + m_iClientBattery = pev->armorvalue; + + ASSERT( gmsgBattery > 0 ); + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgBattery, NULL, pev ); + WRITE_SHORT( (int)pev->armorvalue); + MESSAGE_END(); + } + + if (pev->dmg_take || pev->dmg_save || m_bitsHUDDamage != m_bitsDamageType) + { + // Comes from inside me if not set + Vector damageOrigin = pev->origin; + // send "damage" message + // causes screen to flash, and pain compass to show direction of damage + edict_t *other = pev->dmg_inflictor; + if ( other && UTIL_IsValidEntity(other) ) + { + CBaseEntity *pEntity = CBaseEntity::Instance(other); + if ( pEntity ) + damageOrigin = pEntity->Center(); + } + + // only send down damage type that have hud art + int visibleDamageBits = m_bitsDamageType & DMG_SHOWNHUD; + + MESSAGE_BEGIN( MSG_ONE, gmsgDamage, NULL, pev ); + WRITE_BYTE( pev->dmg_save ); + WRITE_BYTE( pev->dmg_take ); + WRITE_LONG( visibleDamageBits ); + WRITE_COORD( damageOrigin.x ); + WRITE_COORD( damageOrigin.y ); + WRITE_COORD( damageOrigin.z ); + MESSAGE_END(); + + pev->dmg_take = 0; + pev->dmg_save = 0; + m_bitsHUDDamage = m_bitsDamageType; + + // Clear off non-time-based damage indicators + m_bitsDamageType &= DMG_TIMEBASED; + } + + // Update Status Bar + if ( m_flNextSBarUpdateTime < gpGlobals->time ) + { + UpdateStatusBar(); + m_flNextSBarUpdateTime = gpGlobals->time + 0.2; + } + + if (m_iTrain & TRAIN_NEW) + { + ASSERT( gmsgTrain > 0 ); + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgTrain, NULL, pev ); + WRITE_BYTE(m_iTrain & 0xF); + MESSAGE_END(); + + m_iTrain &= ~TRAIN_NEW; + } + + int iShellIndex = GetAmmoIndex("shells"); + int iNailIndex = GetAmmoIndex("nails"); + int iRocketIndex = GetAmmoIndex("rockets"); + int iCellIndex = GetAmmoIndex("cells"); + + // + // New Weapon? + // + if (!m_fKnownItem) + { + m_fKnownItem = TRUE; + + // WeaponInit Message + // byte = # of weapons + // + // for each weapon: + // byte name str length (not including null) + // bytes... name + // byte Ammo Type + // byte Ammo2 Type + // byte bucket + // byte bucket pos + // byte flags + // ???? Icons + + // Send ALL the weapon info now + // QUAKECLASSIC + // Tell the client about the Quake weapons + for (int i = 1; i < 10; i++) + { + const char *pszName; + int iAmmoIndex; + int iMaxAmmo = 0; + int iCurrentAmmo = 0; + int iBit = 0; + + switch( i ) + { + case 1: iBit = IT_AXE; break; + case 2: iBit = IT_SHOTGUN; break; + case 3: iBit = IT_SUPER_SHOTGUN; break; + case 4: iBit = IT_NAILGUN; break; + case 5: iBit = IT_SUPER_NAILGUN; break; + case 6: iBit = IT_GRENADE_LAUNCHER; break; + case 7: iBit = IT_ROCKET_LAUNCHER; break; + case 8: iBit = IT_LIGHTNING; break; + case 9: iBit = IT_EXTRA_WEAPON; break; + + default: + pszName = "Empty"; + } + + if ( ! ( m_iQuakeItems & iBit ) ) + continue; + + switch( i ) + { + case 1: pszName = "weapon_axe"; iAmmoIndex = -1; iBit |= IT_AXE; break; + case 2: pszName = "weapon_shotgun"; iAmmoIndex = iShellIndex; iBit |= IT_SHOTGUN; iCurrentAmmo = m_iAmmoShells ; iMaxAmmo = 100; break; + case 3: pszName = "weapon_doubleshotgun"; iAmmoIndex = iShellIndex; iBit |= IT_SUPER_SHOTGUN; iCurrentAmmo = m_iAmmoShells ; iMaxAmmo = 100; break; + case 4: pszName = "weapon_nailgun"; iAmmoIndex = iNailIndex; iBit |= IT_NAILGUN; iCurrentAmmo = m_iAmmoNails; iMaxAmmo = 200; break; + case 5: pszName = "weapon_supernail"; iAmmoIndex = iNailIndex; iBit |= IT_SUPER_NAILGUN; iCurrentAmmo = m_iAmmoNails; iMaxAmmo = 200; break; + case 6: pszName = "weapon_grenadel"; iAmmoIndex = iRocketIndex; iBit |= IT_GRENADE_LAUNCHER; iCurrentAmmo = m_iAmmoRockets; iMaxAmmo = 100; break; + case 7: pszName = "weapon_rocketl"; iAmmoIndex = iRocketIndex; iBit |= IT_ROCKET_LAUNCHER; iCurrentAmmo = m_iAmmoRockets; iMaxAmmo = 100; break; + case 8: pszName = "weapon_lightning"; iAmmoIndex = iCellIndex; iBit |= IT_LIGHTNING; iCurrentAmmo = m_iAmmoCells; iMaxAmmo = 100; break; + case 9: pszName = "weapon_grapple"; iAmmoIndex = -1; iBit |= IT_EXTRA_WEAPON; break; + + default: + pszName = "Empty"; + } + + MESSAGE_BEGIN( MSG_ONE, gmsgWeaponList, NULL, pev ); + WRITE_STRING(pszName); // string weapon name + WRITE_BYTE(iAmmoIndex); // byte Ammo Type + WRITE_BYTE( iMaxAmmo ); // byte Max Ammo 1 + WRITE_BYTE( -1 ); // byte Ammo2 Type + WRITE_BYTE( iCurrentAmmo ); // byte Max Ammo 2 + WRITE_BYTE( i ); // byte bucket + WRITE_BYTE( 0 ); // byte bucket pos + WRITE_LONG( iBit ); // byte id (bit index into pev->weapons) + + if ( i == 1 || i == 9 ) + WRITE_BYTE( 1 ); // We can select this one on empty + else + WRITE_BYTE( 0 ); // Can't select it when empty. + + MESSAGE_END(); + } + } + + + // QUAKECLASSIC + // HACKHACK: Make the HL ammo types equal the Quake ammo + m_rgAmmo[ iShellIndex ] = m_iAmmoShells; + m_rgAmmo[ iNailIndex ] = m_iAmmoNails; + m_rgAmmo[ iRocketIndex ] = m_iAmmoRockets; + m_rgAmmo[ iCellIndex ] = m_iAmmoCells; + SendAmmoUpdate(); + + // Update all the items + for ( int i = 0; i < MAX_ITEM_TYPES; i++ ) + { + if ( m_rgpPlayerItems[i] ) // each item updates it's successors + m_rgpPlayerItems[i]->UpdateClientData( this ); + } + + // Cache and client weapon change + m_pClientActiveItem = m_pActiveItem; + m_iClientFOV = m_iFOV; + + // QUAKECLASSIC + m_iClientQuakeWeapon = m_iQuakeWeapon; +} + + +//========================================================= +// FBecomeProne - Overridden for the player to set the proper +// physics flags when a barnacle grabs player. +//========================================================= +BOOL CBasePlayer :: FBecomeProne ( void ) +{ + m_afPhysicsFlags |= PFLAG_ONBARNACLE; + return TRUE; +} + +//========================================================= +// BarnacleVictimBitten - bad name for a function that is called +// by Barnacle victims when the barnacle pulls their head +// into its mouth. For the player, just die. +//========================================================= +void CBasePlayer :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) +{ + TakeDamage ( pevBarnacle, pevBarnacle, pev->health + pev->armorvalue, DMG_SLASH | DMG_ALWAYSGIB ); +} + +//========================================================= +// BarnacleVictimReleased - overridden for player who has +// physics flags concerns. +//========================================================= +void CBasePlayer :: BarnacleVictimReleased ( void ) +{ + m_afPhysicsFlags &= ~PFLAG_ONBARNACLE; +} + + +//========================================================= +// Illumination +// return player light level plus virtual muzzle flash +//========================================================= +int CBasePlayer :: Illumination( void ) +{ + int iIllum = CBaseEntity::Illumination( ); + + iIllum += m_iWeaponFlash; + if (iIllum > 255) + return 255; + return iIllum; +} + + +void CBasePlayer :: EnableControl(BOOL fControl) +{ + if (!fControl) + pev->flags |= FL_FROZEN; + else + pev->flags &= ~FL_FROZEN; + +} + + +#define DOT_1DEGREE 0.9998476951564 +#define DOT_2DEGREE 0.9993908270191 +#define DOT_3DEGREE 0.9986295347546 +#define DOT_4DEGREE 0.9975640502598 +#define DOT_5DEGREE 0.9961946980917 +#define DOT_6DEGREE 0.9945218953683 +#define DOT_7DEGREE 0.9925461516413 +#define DOT_8DEGREE 0.9902680687416 +#define DOT_9DEGREE 0.9876883405951 +#define DOT_10DEGREE 0.9848077530122 +#define DOT_15DEGREE 0.9659258262891 +#define DOT_20DEGREE 0.9396926207859 +#define DOT_25DEGREE 0.9063077870367 + +//========================================================= +// Autoaim +// set crosshair position to point to enemey +//========================================================= +Vector CBasePlayer :: GetAutoaimVector( float flDelta ) +{ + if (g_iSkillLevel == SKILL_HARD) + { + UTIL_MakeVectors( pev->v_angle + pev->punchangle ); + return gpGlobals->v_forward; + } + + Vector vecSrc = GetGunPosition( ); + float flDist = 8192; + + // always use non-sticky autoaim + // UNDONE: use sever variable to chose! + if (1 || g_iSkillLevel == SKILL_MEDIUM) + { + m_vecAutoAim = Vector( 0, 0, 0 ); + // flDelta *= 0.5; + } + + BOOL m_fOldTargeting = m_fOnTarget; + Vector angles = AutoaimDeflection(vecSrc, flDist, flDelta ); + + // update ontarget if changed + if ( !g_pGameRules->AllowAutoTargetCrosshair() ) + m_fOnTarget = 0; + else if (m_fOldTargeting != m_fOnTarget) + { + m_pActiveItem->UpdateItemInfo( ); + } + + if (angles.x > 180) + angles.x -= 360; + if (angles.x < -180) + angles.x += 360; + if (angles.y > 180) + angles.y -= 360; + if (angles.y < -180) + angles.y += 360; + + if (angles.x > 25) + angles.x = 25; + if (angles.x < -25) + angles.x = -25; + if (angles.y > 12) + angles.y = 12; + if (angles.y < -12) + angles.y = -12; + + + // always use non-sticky autoaim + // UNDONE: use sever variable to chose! + if (0 || g_iSkillLevel == SKILL_EASY) + { + m_vecAutoAim = m_vecAutoAim * 0.67 + angles * 0.33; + } + else + { + m_vecAutoAim = angles * 0.9; + } + + // m_vecAutoAim = m_vecAutoAim * 0.99; + + // Don't send across network if sv_aim is 0 + if ( CVAR_GET_FLOAT( "sv_aim" ) != 0 ) + { + if ( m_vecAutoAim.x != m_lastx || + m_vecAutoAim.y != m_lasty ) + { + SET_CROSSHAIRANGLE( edict(), -m_vecAutoAim.x, m_vecAutoAim.y ); + + m_lastx = m_vecAutoAim.x; + m_lasty = m_vecAutoAim.y; + } + } + + // ALERT( at_console, "%f %f\n", angles.x, angles.y ); + + UTIL_MakeVectors( pev->v_angle + pev->punchangle + m_vecAutoAim ); + return gpGlobals->v_forward; +} + + +Vector CBasePlayer :: AutoaimDeflection( Vector &vecSrc, float flDist, float flDelta ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + float bestdot; + Vector bestdir; + edict_t *bestent; + TraceResult tr; + + if ( CVAR_GET_FLOAT("sv_aim") == 0 ) + { + m_fOnTarget = FALSE; + return g_vecZero; + } + + UTIL_MakeVectors( pev->v_angle + pev->punchangle + m_vecAutoAim ); + + // try all possible entities + bestdir = gpGlobals->v_forward; + bestdot = flDelta; // +- 10 degrees + bestent = NULL; + + m_fOnTarget = FALSE; + + UTIL_TraceLine( vecSrc, vecSrc + bestdir * flDist, dont_ignore_monsters, edict(), &tr ); + + + if ( tr.pHit && tr.pHit->v.takedamage != DAMAGE_NO) + { + // don't look through water + if (!((pev->waterlevel != 3 && tr.pHit->v.waterlevel == 3) + || (pev->waterlevel == 3 && tr.pHit->v.waterlevel == 0))) + { + if (tr.pHit->v.takedamage == DAMAGE_AIM) + m_fOnTarget = TRUE; + + return m_vecAutoAim; + } + } + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + Vector center; + Vector dir; + float dot; + + if ( pEdict->free ) // Not in use + continue; + + if (pEdict->v.takedamage != DAMAGE_AIM) + continue; + if (pEdict == edict()) + continue; +// if (pev->team > 0 && pEdict->v.team == pev->team) +// continue; // don't aim at teammate + if ( !g_pGameRules->ShouldAutoAim( this, pEdict ) ) + continue; + + pEntity = Instance( pEdict ); + if (pEntity == NULL) + continue; + + if (!pEntity->IsAlive()) + continue; + + // don't look through water + if ((pev->waterlevel != 3 && pEntity->pev->waterlevel == 3) + || (pev->waterlevel == 3 && pEntity->pev->waterlevel == 0)) + continue; + + center = pEntity->BodyTarget( vecSrc ); + + dir = (center - vecSrc).Normalize( ); + + // make sure it's in front of the player + if (DotProduct (dir, gpGlobals->v_forward ) < 0) + continue; + + dot = fabs( DotProduct (dir, gpGlobals->v_right ) ) + + fabs( DotProduct (dir, gpGlobals->v_up ) ) * 0.5; + + // tweek for distance + dot *= 1.0 + 0.2 * ((center - vecSrc).Length() / flDist); + + if (dot > bestdot) + continue; // to far to turn + + UTIL_TraceLine( vecSrc, center, dont_ignore_monsters, edict(), &tr ); + if (tr.flFraction != 1.0 && tr.pHit != pEdict) + { + // ALERT( at_console, "hit %s, can't see %s\n", STRING( tr.pHit->v.classname ), STRING( pEdict->v.classname ) ); + continue; + } + + // don't shoot at friends + if (IRelationship( pEntity ) < 0) + { + if ( !pEntity->IsPlayer() && !g_pGameRules->IsDeathmatch()) + // ALERT( at_console, "friend\n"); + continue; + } + + // can shoot at this one + bestdot = dot; + bestent = pEdict; + bestdir = dir; + } + + if (bestent) + { + bestdir = UTIL_VecToAngles (bestdir); + bestdir.x = -bestdir.x; + bestdir = bestdir - pev->v_angle - pev->punchangle; + + if (bestent->v.takedamage == DAMAGE_AIM) + m_fOnTarget = TRUE; + + return bestdir; + } + + return Vector( 0, 0, 0 ); +} + + +void CBasePlayer :: ResetAutoaim( ) +{ + if (m_vecAutoAim.x != 0 || m_vecAutoAim.y != 0) + { + m_vecAutoAim = Vector( 0, 0, 0 ); + SET_CROSSHAIRANGLE( edict(), 0, 0 ); + } + m_fOnTarget = FALSE; +} + +/* +============= +SetCustomDecalFrames + + UNDONE: Determine real frame limit, 8 is a placeholder. + Note: -1 means no custom frames present. +============= +*/ +void CBasePlayer :: SetCustomDecalFrames( int nFrames ) +{ + if (nFrames > 0 && + nFrames < 8) + m_nCustomSprayFrames = nFrames; + else + m_nCustomSprayFrames = -1; +} + +/* +============= +GetCustomDecalFrames + + Returns the # of custom frames this player's custom clan logo contains. +============= +*/ +int CBasePlayer :: GetCustomDecalFrames( void ) +{ + return m_nCustomSprayFrames; +} + + +//========================================================= +// DropPlayerItem - drop the named item, or if no name, +// the active item. +//========================================================= +void CBasePlayer::DropPlayerItem ( char *pszItemName ) +{ + if ( !g_pGameRules->IsMultiplayer() || (CVAR_GET_FLOAT("mp_weaponstay") > 0) ) + { + // no dropping in single player. + return; + } + + if ( !strlen( pszItemName ) ) + { + // if this string has no length, the client didn't type a name! + // assume player wants to drop the active item. + // make the string null to make future operations in this function easier + pszItemName = NULL; + } + + CBasePlayerItem *pWeapon; + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pWeapon = m_rgpPlayerItems[ i ]; + + while ( pWeapon ) + { + if ( pszItemName ) + { + // try to match by name. + if ( !strcmp( pszItemName, STRING( pWeapon->pev->classname ) ) ) + { + // match! + break; + } + } + else + { + // trying to drop active item + if ( pWeapon == m_pActiveItem ) + { + // active item! + break; + } + } + + pWeapon = pWeapon->m_pNext; + } + + + // if we land here with a valid pWeapon pointer, that's because we found the + // item we want to drop and hit a BREAK; pWeapon is the item. + if ( pWeapon ) + { + g_pGameRules->GetNextBestWeapon( this, pWeapon ); + + UTIL_MakeVectors ( pev->angles ); + + pev->weapons &= ~(1<m_iId);// take item off hud + + CWeaponBox *pWeaponBox = (CWeaponBox *)CBaseEntity::Create( "weaponbox", pev->origin + gpGlobals->v_forward * 10, pev->angles, edict() ); + pWeaponBox->pev->angles.x = 0; + pWeaponBox->pev->angles.z = 0; + pWeaponBox->PackWeapon( pWeapon ); + pWeaponBox->pev->velocity = gpGlobals->v_forward * 300 + gpGlobals->v_forward * 100; + + // drop half of the ammo for this weapon. + int iAmmoIndex; + + iAmmoIndex = GetAmmoIndex ( pWeapon->pszAmmo1() ); // ??? + + if ( iAmmoIndex != -1 ) + { + // this weapon weapon uses ammo, so pack an appropriate amount. + if ( pWeapon->iFlags() & ITEM_FLAG_EXHAUSTIBLE ) + { + // pack up all the ammo, this weapon is its own ammo type + pWeaponBox->PackAmmo( MAKE_STRING(pWeapon->pszAmmo1()), m_rgAmmo[ iAmmoIndex ] ); + m_rgAmmo[ iAmmoIndex ] = 0; + + } + else + { + // pack half of the ammo + pWeaponBox->PackAmmo( MAKE_STRING(pWeapon->pszAmmo1()), m_rgAmmo[ iAmmoIndex ] / 2 ); + m_rgAmmo[ iAmmoIndex ] /= 2; + } + + } + + return;// we're done, so stop searching with the FOR loop. + } + } +} + +//========================================================= +// HasPlayerItem Does the player already have this item? +//========================================================= +BOOL CBasePlayer::HasPlayerItem( CBasePlayerItem *pCheckItem ) +{ + CBasePlayerItem *pItem = m_rgpPlayerItems[pCheckItem->iItemSlot()]; + + while (pItem) + { + if (FClassnameIs( pItem->pev, STRING( pCheckItem->pev->classname) )) + { + return TRUE; + } + pItem = pItem->m_pNext; + } + + return FALSE; +} + +//========================================================= +// HasNamedPlayerItem Does the player already have this item? +//========================================================= +BOOL CBasePlayer::HasNamedPlayerItem( const char *pszItemName ) +{ + CBasePlayerItem *pItem; + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pItem = m_rgpPlayerItems[ i ]; + + while (pItem) + { + if ( !strcmp( pszItemName, STRING( pItem->pev->classname ) ) ) + { + return TRUE; + } + pItem = pItem->m_pNext; + } + } + + return FALSE; +} + +//========================================================= +// +//========================================================= +BOOL CBasePlayer :: SwitchWeapon( CBasePlayerItem *pWeapon ) +{ + if ( !pWeapon->CanDeploy() ) + { + return FALSE; + } + + ResetAutoaim( ); + + if (m_pActiveItem) + { + m_pActiveItem->Holster( ); + } + + m_pActiveItem = pWeapon; + pWeapon->Deploy( ); + + return TRUE; +} + +//========================================================= +// Dead HEV suit prop +//========================================================= +class CDeadHEV : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_HUMAN_MILITARY; } + + void KeyValue( KeyValueData *pkvd ); + + int m_iPose;// which sequence to display -- temporary, don't need to save + static char *m_szPoses[4]; +}; + +char *CDeadHEV::m_szPoses[] = { "deadback", "deadsitting", "deadstomach", "deadtable" }; + +void CDeadHEV::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "pose")) + { + m_iPose = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +LINK_ENTITY_TO_CLASS( monster_hevsuit_dead, CDeadHEV ); + +//========================================================= +// ********** DeadHEV SPAWN ********** +//========================================================= +void CDeadHEV :: Spawn( void ) +{ + PRECACHE_MODEL("models/player.mdl"); + SET_MODEL(ENT(pev), "models/player.mdl"); + + pev->effects = 0; + pev->yaw_speed = 8; + pev->sequence = 0; + pev->body = 1; + m_bloodColor = BLOOD_COLOR_RED; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + + if (pev->sequence == -1) + { + ALERT ( at_console, "Dead hevsuit with bad pose\n" ); + pev->sequence = 0; + pev->effects = EF_BRIGHTFIELD; + } + + // Corpses have less health + pev->health = 8; + + MonsterInitDead(); +} + + +class CStripWeapons : public CPointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +private: +}; + +LINK_ENTITY_TO_CLASS( player_weaponstrip, CStripWeapons ); + +void CStripWeapons :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBasePlayer *pPlayer = NULL; + + if ( pActivator && pActivator->IsPlayer() ) + { + pPlayer = (CBasePlayer *)pActivator; + } + else if ( !g_pGameRules->IsDeathmatch() ) + { + pPlayer = (CBasePlayer *)CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) ); + } + + if ( pPlayer ) + pPlayer->RemoveAllItems( FALSE ); +} + + +class CRevertSaved : public CPointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT MessageThink( void ); + void EXPORT LoadThink( void ); + void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + inline float Duration( void ) { return pev->dmg_take; } + inline float HoldTime( void ) { return pev->dmg_save; } + inline float MessageTime( void ) { return m_messageTime; } + inline float LoadTime( void ) { return m_loadTime; } + + inline void SetDuration( float duration ) { pev->dmg_take = duration; } + inline void SetHoldTime( float hold ) { pev->dmg_save = hold; } + inline void SetMessageTime( float time ) { m_messageTime = time; } + inline void SetLoadTime( float time ) { m_loadTime = time; } + +private: + float m_messageTime; + float m_loadTime; +}; + +LINK_ENTITY_TO_CLASS( player_loadsaved, CRevertSaved ); + +TYPEDESCRIPTION CRevertSaved::m_SaveData[] = +{ + DEFINE_FIELD( CRevertSaved, m_messageTime, FIELD_FLOAT ), // These are not actual times, but durations, so save as floats + DEFINE_FIELD( CRevertSaved, m_loadTime, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CRevertSaved, CPointEntity ); + +void CRevertSaved :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "duration")) + { + SetDuration( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "holdtime")) + { + SetHoldTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "messagetime")) + { + SetMessageTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "loadtime")) + { + SetLoadTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CRevertSaved :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + UTIL_ScreenFadeAll( pev->rendercolor, Duration(), HoldTime(), pev->renderamt, FFADE_OUT ); + pev->nextthink = gpGlobals->time + MessageTime(); + SetThink( &CRevertSaved::MessageThink ); +} + + +void CRevertSaved :: MessageThink( void ) +{ + UTIL_ShowMessageAll( STRING(pev->message) ); + float nextThink = LoadTime() - MessageTime(); + if ( nextThink > 0 ) + { + pev->nextthink = gpGlobals->time + nextThink; + SetThink( &CRevertSaved::LoadThink ); + } + else + LoadThink(); +} + + +void CRevertSaved :: LoadThink( void ) +{ + if ( !gpGlobals->deathmatch ) + { + SERVER_COMMAND("reload\n"); + } +} + + +//========================================================= +// Multiplayer intermission spots. +//========================================================= +class CInfoIntermission:public CPointEntity +{ + void Spawn( void ); + void Think( void ); +}; + +void CInfoIntermission::Spawn( void ) +{ + UTIL_SetOrigin( pev, pev->origin ); + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + pev->v_angle = g_vecZero; + + pev->nextthink = gpGlobals->time + 2;// let targets spawn! + +} + +void CInfoIntermission::Think ( void ) +{ + edict_t *pTarget; + + // find my target + pTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) ); + + if ( !FNullEnt(pTarget) ) + { + pev->v_angle = UTIL_VecToAngles( (pTarget->v.origin - pev->origin).Normalize() ); + pev->v_angle.x = -pev->v_angle.x; + } +} + +LINK_ENTITY_TO_CLASS( info_intermission, CInfoIntermission ); + diff --git a/dmc/dlls/player.h b/dmc/dlls/player.h new file mode 100644 index 0000000..a9be420 --- /dev/null +++ b/dmc/dlls/player.h @@ -0,0 +1,497 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PLAYER_H +#define PLAYER_H + +#define PLAYER_FATAL_FALL_SPEED 1024// approx 60 feet +#define PLAYER_MAX_SAFE_FALL_SPEED 580// approx 20 feet +#define DAMAGE_FOR_FALL_SPEED (float) 100 / ( PLAYER_FATAL_FALL_SPEED - PLAYER_MAX_SAFE_FALL_SPEED )// damage per unit per second. +#define PLAYER_MIN_BOUNCE_SPEED 200 +#define PLAYER_FALL_PUNCH_THRESHHOLD (float)350 // won't punch player's screen/make scrape noise unless player falling at least this fast. + +// +// Player PHYSICS FLAGS bits +// +#define PFLAG_ONLADDER ( 1<<0 ) +#define PFLAG_ONSWING ( 1<<0 ) +#define PFLAG_ONTRAIN ( 1<<1 ) +#define PFLAG_ONBARNACLE ( 1<<2 ) +#define PFLAG_DUCKING ( 1<<3 ) // In the process of ducking, but totally squatted yet +#define PFLAG_USING ( 1<<4 ) // Using a continuous entity +#define PFLAG_OBSERVER ( 1<<5 ) // player is locked in stationary cam mode. Spectators can move, observers can't. + +// +// generic player +// +//----------------------------------------------------- +//This is Half-Life player entity +//----------------------------------------------------- +#define CSUITPLAYLIST 4 // max of 4 suit sentences queued up at any time + +#define SUIT_GROUP TRUE +#define SUIT_SENTENCE FALSE + +#define SUIT_REPEAT_OK 0 +#define SUIT_NEXT_IN_30SEC 30 +#define SUIT_NEXT_IN_1MIN 60 +#define SUIT_NEXT_IN_5MIN 300 +#define SUIT_NEXT_IN_10MIN 600 +#define SUIT_NEXT_IN_30MIN 1800 +#define SUIT_NEXT_IN_1HOUR 3600 + +#define CSUITNOREPEAT 32 + +#define SOUND_FLASHLIGHT_ON "items/flashlight1.wav" +#define SOUND_FLASHLIGHT_OFF "items/flashlight1.wav" + +#define TEAM_NAME_LENGTH 16 + +typedef enum +{ + PLAYER_IDLE, + PLAYER_WALK, + PLAYER_JUMP, + PLAYER_SUPERJUMP, + PLAYER_DIE, + PLAYER_ATTACK1, +} PLAYER_ANIM; + +#ifdef THREEWAVE +enum Player_Menu { + Team_Menu, + Team_Menu_IG, +}; +#endif + +#define MAX_ID_RANGE 2048 +#define SBAR_STRING_SIZE 128 +enum sbar_data +{ +SBAR_ID_TARGETNAME = 1, +SBAR_ID_TARGETHEALTH, +SBAR_ID_TARGETARMOR, +SBAR_ID_TARGETRUNE, +SBAR_ID_TARGETTEAM, +SBAR_END, +}; + +#define PLAYER_MAX_SPEED 300 + +class CBasePlayer : public CBaseMonster +{ +public: + int random_seed; // See that is shared between client & server for shared weapons code + + int m_iPlayerSound;// the index of the sound list slot reserved for this player + int m_iTargetVolume;// ideal sound volume. + int m_iWeaponVolume;// how loud the player's weapon is right now. + int m_iExtraSoundTypes;// additional classification for this weapon's sound + int m_iWeaponFlash;// brightness of the weapon flash + float m_flStopExtraSoundTime; + + float m_flFlashLightTime; // Time until next battery draw/Recharge + int m_iFlashBattery; // Flashlight Battery Draw + + int m_afButtonLast; + int m_afButtonPressed; + int m_afButtonReleased; + + edict_t *m_pentSndLast; // last sound entity to modify player room type + float m_flSndRoomtype; // last roomtype set by sound entity + float m_flSndRange; // dist from player to sound entity + + float m_flFallVelocity; + + int m_rgItems[MAX_ITEMS]; + int m_fKnownItem; // True when a new item needs to be added + int m_fNewAmmo; // True when a new item has been added + + unsigned int m_afPhysicsFlags; // physics flags - set when 'normal' physics should be revisited or overriden + float m_fNextSuicideTime; // the time after which the player can next use the suicide command + + +// these are time-sensitive things that we keep track of + float m_flTimeStepSound; // when the last stepping sound was made + float m_flTimeWeaponIdle; // when to play another weapon idle animation. + float m_flSwimTime; // how long player has been underwater + float m_flDuckTime; // how long we've been ducking + float m_flWallJumpTime; // how long until next walljump + + float m_flSuitUpdate; // when to play next suit update + int m_rgSuitPlayList[CSUITPLAYLIST];// next sentencenum to play for suit update + int m_iSuitPlayNext; // next sentence slot for queue storage; + int m_rgiSuitNoRepeat[CSUITNOREPEAT]; // suit sentence no repeat list + float m_rgflSuitNoRepeatTime[CSUITNOREPEAT]; // how long to wait before allowing repeat + int m_lastDamageAmount; // Last damage taken + float m_tbdPrev; // Time-based damage timer + + float m_flgeigerRange; // range to nearest radiation source + float m_flgeigerDelay; // delay per update of range msg to client + int m_igeigerRangePrev; + int m_iStepLeft; // alternate left/right foot stepping sound + char m_szTextureName[CBTEXTURENAMEMAX]; // current texture name we're standing on + char m_chTextureType; // current texture type + + int m_idrowndmg; // track drowning damage taken + int m_idrownrestored; // track drowning damage restored + + int m_bitsHUDDamage; // Damage bits for the current fame. These get sent to + // the hude via the DAMAGE message + BOOL m_fInitHUD; // True when deferred HUD restart msg needs to be sent + BOOL m_fGameHUDInitialized; + int m_iTrain; // Train control position + BOOL m_fWeapon; // Set this to FALSE to force a reset of the current weapon HUD info + + EHANDLE m_pTank; // the tank which the player is currently controlling, NULL if no tank + float m_fDeadTime; // the time at which the player died (used in PlayerDeathThink()) + + BOOL m_fNoPlayerSound; // a debugging feature. Player makes no sound if this is true. + BOOL m_fLongJump; // does this player have the longjump module? + + float m_tSneaking; + int m_iUpdateTime; // stores the number of frame ticks before sending HUD update messages + int m_iClientHealth; // the health currently known by the client. If this changes, send a new + int m_iClientBattery; // the Battery currently known by the client. If this changes, send a new + int m_iHideHUD; // the players hud weapon info is to be hidden + int m_iClientHideHUD; + int m_iFOV; // field of view + int m_iClientFOV; // client's known FOV + // usable player items + CBasePlayerItem *m_rgpPlayerItems[MAX_ITEM_TYPES]; + CBasePlayerItem *m_pActiveItem; + CBasePlayerItem *m_pClientActiveItem; // client version of the active item + CBasePlayerItem *m_pLastItem; + // shared ammo slots + int m_rgAmmo[MAX_AMMO_SLOTS]; + int m_rgAmmoLast[MAX_AMMO_SLOTS]; + + Vector m_vecAutoAim; + BOOL m_fOnTarget; + int m_iDeaths; + float m_iRespawnFrames; // used in PlayerDeathThink() to make sure players can always respawn + + int m_lastx, m_lasty; // These are the previous update's crosshair angles, DON"T SAVE/RESTORE + + int m_nCustomSprayFrames;// Custom clan logo frames for this player + float m_flNextDecalTime;// next time this player can spray a decal + + char m_szTeamName[TEAM_NAME_LENGTH]; + + virtual void Spawn( void ); + +// virtual void Think( void ); + virtual void Jump( void ); + virtual void Duck( void ); + virtual void PreThink( void ); + virtual void PostThink( void ); + virtual Vector GetGunPosition( void ); + virtual int TakeHealth( float flHealth, int bitsDamageType ); + virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + virtual void Killed( entvars_t *pevAttacker, int iGib ); + virtual Vector BodyTarget( const Vector &posSrc ) { return Center( ) + pev->view_ofs * RANDOM_FLOAT( 0.5, 1.1 ); }; // position to shoot at + virtual void StartSneaking( void ) { m_tSneaking = gpGlobals->time - 1; } + virtual void StopSneaking( void ) { m_tSneaking = gpGlobals->time + 30; } + virtual BOOL IsSneaking( void ) { return m_tSneaking <= gpGlobals->time; } + virtual BOOL IsAlive( void ) { return (pev->deadflag == DEAD_NO) && pev->health > 0; } + virtual BOOL ShouldFadeOnDeath( void ) { return FALSE; } + virtual BOOL IsPlayer( void ) { return TRUE; } // Spectators should return FALSE for this, they aren't "players" as far as game logic is concerned + + virtual BOOL IsNetClient( void ) { return TRUE; } // Bots should return FALSE for this, they can't receive NET messages + // Spectators should return TRUE for this + virtual const char *TeamID( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + void RenewItems(void); + void RemoveAllItems( BOOL removeSuit ); + BOOL SwitchWeapon( CBasePlayerItem *pWeapon ); + + // JOHN: sends custom messages if player HUD data has changed (eg health, ammo) + virtual void UpdateClientData( void ); + + static TYPEDESCRIPTION m_playerSaveData[]; + + // Player is moved across the transition by other means + virtual int ObjectCaps( void ) { return CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual void Precache( void ); + BOOL IsOnLadder( void ); + BOOL FlashlightIsOn( void ); + void FlashlightTurnOn( void ); + void FlashlightTurnOff( void ); + + void DeathSound ( void ); + + int Classify ( void ); + void SetAnimation( PLAYER_ANIM playerAnim ); + void SetWeaponAnimType( const char *szExtention ); + char m_szAnimExtention[32]; + + // custom player functions + virtual void ImpulseCommands( void ); + void CheatImpulseCommands( int iImpulse ); + + void StartDeathCam( void ); + void StartObserver( Vector vecPosition, Vector vecViewAngle ); + + void AddPoints( int score, BOOL bAllowNegativeScore ); + void AddPointsToTeam( int score, BOOL bAllowNegativeScore ); + BOOL AddPlayerItem( CBasePlayerItem *pItem ); + BOOL RemovePlayerItem( CBasePlayerItem *pItem ); + void DropPlayerItem ( char *pszItemName ); + BOOL HasPlayerItem( CBasePlayerItem *pCheckItem ); + BOOL HasNamedPlayerItem( const char *pszItemName ); + BOOL HasWeapons( void );// do I have ANY weapons? + void SelectPrevItem( int iItem ); + void SelectNextItem( int iItem ); + void SelectLastItem(void); + void SelectItem(const char *pstr); + void ItemPreFrame( void ); + void ItemPostFrame( void ); + void GiveNamedItem( const char *szName ); + void EnableControl(BOOL fControl); + + int GiveAmmo( int iAmount, char *szName, int iMax ); + void SendAmmoUpdate(void); + + void WaterMove( void ); + void EXPORT PlayerDeathThink( void ); + void PlayerUse( void ); + + void CheckSuitUpdate(); + void SetSuitUpdate(char *name, int fgroup, int iNoRepeat); + void UpdateGeigerCounter( void ); + void CheckTimeBasedDamage( void ); + void UpdateStepSound( void ); + void PlayStepSound(int step, float fvol); + + BOOL FBecomeProne ( void ); + void BarnacleVictimBitten ( entvars_t *pevBarnacle ); + void BarnacleVictimReleased ( void ); + static int GetAmmoIndex(const char *psz); + int AmmoInventory( int iAmmoIndex ); + int Illumination( void ); + + void ResetAutoaim( void ); + Vector GetAutoaimVector( float flDelta ); + Vector AutoaimDeflection( Vector &vecSrc, float flDist, float flDelta ); + + void ForceClientDllUpdate( void ); // Forces all client .dll specific data to be resent to client. + + void DeathMessage( entvars_t *pevKiller ); + + void SetCustomDecalFrames( int nFrames ); + int GetCustomDecalFrames( void ); + + // Observer camera + void Observer_FindNextPlayer(); + void Observer_HandleButtons(); + void Observer_SetMode( int iMode ); + EHANDLE m_hObserverTarget; + float m_flNextObserverInput; + int IsObserver() { return pev->iuser1; }; + + + // QUAKECLASSIC + // Player + void Pain( CBaseEntity *pAttacker ); + float m_flPainSoundFinished; + + BOOL m_bHadFirstSpawn; // used to handle the MOTD + + // Weapon selection + int W_BestWeapon( void ); + void W_SetCurrentAmmo( int sendanim = 1 ); + BOOL W_CheckNoAmmo( void ); + void W_ChangeWeapon( int iWeaponNumber ); + void W_CycleWeaponCommand( void ); + void W_CycleWeaponReverseCommand( void ); + + // Weapon functionality + void Q_FireBullets(int iShots, Vector vecDir, Vector vecSpread); + void LightningDamage( Vector p1, Vector p2, CBaseEntity *pAttacker, float flDamage,Vector vecDir); + + // Weapons + void W_Attack( int iQuadSound ); + void W_FireAxe( void ); + void W_FireShotgun( int QuadSound ); + void W_FireSuperShotgun( int QuadSound ); + void W_FireRocket( int QuadSound ); + void W_FireLightning( int QuadSound ); + void W_FireGrenade( int QuadSound ); + void W_FireSuperSpikes( int QuadSound ); + void W_FireSpikes( int QuadSound ); + + // Ammunition + void CheckAmmo( void ); + int *m_pCurrentAmmo; // Always points to one of the four ammo counts below + int m_iAmmoRockets; + int m_iAmmoCells; + int m_iAmmoShells; + int m_iAmmoNails; + + // Backpacks + void DropBackpack( void ); + + // Weapons + void Deathmatch_Weapon(int iOldWeapon, int iNewWeapon); + int m_iQuakeWeapon; + int m_iClientQuakeWeapon; // The last status of the m_iQuakeWeapon sent to the client. + int m_iQuakeItems; + int m_iClientQuakeItems; // The last status of the m_iQuakeItems sent to the client. + int m_iWeaponSwitch; + int m_iBackpackSwitch; + int m_iAutoWepSwitch; + + // Weapon Data + float m_flAxeFire; + float m_flLightningTime; + int m_iNailOffset; + float m_flNextQuadSound; + + // Powerups + float m_flSuperDamageFinished; + float m_flInvincibleFinished; + float m_flInvisibleFinished; + float m_flRadsuitFinished; + void PowerUpThink( void ); //Checks powerup timers and hadles their effects + char m_chOldModel[64]; //Save the player's model here + bool m_bPlayedQuadSound; + bool m_bPlayedEnvSound; + bool m_bPlayedInvSound; + bool m_bPlayedProtectSound; + + BOOL m_bLostInvincSound; + BOOL m_bLostInvisSound; + BOOL m_bLostSuperSound; + BOOL m_bLostRadSound; + float m_fInvincSound; + float m_fSuperSound; + + void InitStatusBar( void ); + void UpdateStatusBar( void ); + int m_izSBarState[ SBAR_END ]; + float m_flNextSBarUpdateTime; + float m_flStatusBarDisappearDelay; + char m_SbarString0[ SBAR_STRING_SIZE ]; + char m_SbarString1[ SBAR_STRING_SIZE ]; + + unsigned short m_usShotgunSingle; + unsigned short m_usShotgunDouble; + unsigned short m_usAxe; + unsigned short m_usAxeSwing; + unsigned short m_usRocket; + unsigned short m_usGrenade; + unsigned short m_usLightning; + unsigned short m_usSpike; + unsigned short m_usSuperSpike; + + +#ifdef THREEWAVE + int m_bHasFlag; + void ShowMenu ( int bitsValidSlots, int nDisplayTime, BOOL fNeedMore, char *pszText ); + int m_iMenu; + + float m_flNextTeamChange; + + CBasePlayer *pFlagCarrierKiller; + CBasePlayer *pFlagReturner; + CBasePlayer *pCarrierHurter; + + float m_flCarrierHurtTime; + float m_flCarrierPickupTime; + float m_flFlagCarrierKillTime; + float m_flFlagReturnTime; + float m_flFlagStatusTime; + + float m_flRegenTime; + + int m_iRuneStatus; + + void W_FireHook ( void ); + void Throw_Grapple ( void ); + + bool m_bHook_Out; + bool m_bOn_Hook; + CBaseEntity *m_ppHook; + + void Service_Grapple ( void ); + +#endif + + +//#ifdef THREEWAVE + +//#endif + +}; + +#define AUTOAIM_2DEGREES 0.0348994967025 +#define AUTOAIM_5DEGREES 0.08715574274766 +#define AUTOAIM_8DEGREES 0.1391731009601 +#define AUTOAIM_10DEGREES 0.1736481776669 + + + + + + +// QUAKECLASSIC +#define Q_SMALL_PUNCHANGLE_KICK -2 +#define Q_BIG_PUNCHANGLE_KICK -4 + +#define IT_AXE (1 << 0) +#define IT_SHOTGUN (1 << 1) +#define IT_SUPER_SHOTGUN (1 << 2) +#define IT_NAILGUN (1 << 3) +#define IT_SUPER_NAILGUN (1 << 4) +#define IT_GRENADE_LAUNCHER (1 << 5) +#define IT_ROCKET_LAUNCHER (1 << 6) +#define IT_LIGHTNING (1 << 7) +#define IT_EXTRA_WEAPON (1 << 8) + +#define IT_SHELLS (1 << 9) +#define IT_NAILS (1 << 10) +#define IT_ROCKETS (1 << 11) +#define IT_CELLS (1 << 12) + +#define IT_ARMOR1 (1 << 13) +#define IT_ARMOR2 (1 << 14) +#define IT_ARMOR3 (1 << 15) +#define IT_SUPERHEALTH (1 << 16) + +#define IT_KEY1 (1 << 17) +#define IT_KEY2 (1 << 18) + +#define IT_INVISIBILITY (1 << 19) +#define IT_INVULNERABILITY (1 << 20) +#define IT_SUIT (1 << 21) +#define IT_QUAD (1 << 22) + + +#define ITEM_RUNE1_FLAG 1 +#define ITEM_RUNE2_FLAG 2 +#define ITEM_RUNE3_FLAG 3 +#define ITEM_RUNE4_FLAG 4 + + + + +extern int gmsgHudText; +extern BOOL gInitHUD; + +#define MAX_TELES 256 + +#endif // PLAYER_H diff --git a/dmc/dlls/quake_gun.cpp b/dmc/dlls/quake_gun.cpp new file mode 100644 index 0000000..ff550ff --- /dev/null +++ b/dmc/dlls/quake_gun.cpp @@ -0,0 +1,174 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== quake_gun.cpp ======================================================== + + This is a half-life weapon that fires every one of the quake weapons. + It's automatically given to all players. + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "quake_gun.h" + +LINK_ENTITY_TO_CLASS( weapon_quakegun, CQuakeGun ); + + +//=========================================================== +void CQuakeGun::Spawn( ) +{ + Precache( ); + SET_MODEL(ENT(pev), "models/v_crowbar.mdl"); + m_iDefaultAmmo = GLOCK_DEFAULT_GIVE; + FallInit(); +} + +void CQuakeGun::Precache( void ) +{ + PRECACHE_MODEL("models/v_crowbar.mdl"); + PRECACHE_MODEL("models/p_9mmhandgun.mdl"); +} + +int CQuakeGun::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = NULL; + p->iMaxAmmo1 = -1; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = -1; + p->iSlot = 1; + p->iPosition = 1; + p->iFlags = 0; + p->iId = m_iId = WEAPON_GLOCK; + p->iWeight = GLOCK_WEIGHT; + + return 1; +} + +void CQuakeGun::DestroyEffect( void ) +{ + +#ifndef CLIENT_DLL + if ( m_pBeam ) + { + UTIL_Remove( m_pBeam ); + m_pBeam = NULL; + } +#endif + +} + +void CQuakeGun::CreateEffect( void ) +{ + +#ifndef CLIENT_DLL + DestroyEffect(); + + m_pBeam = CBeam::BeamCreate( "sprites/laserbeam.spr", 40 ); + m_pBeam->PointEntInit( pev->origin, m_pPlayer->entindex() ); + m_pBeam->SetBrightness( 100 ); + m_pBeam->SetEndAttachment( 1 ); + m_pBeam->pev->spawnflags |= SF_BEAM_TEMPORARY; // Flag these to be destroyed on save/restore or level transition + m_pBeam->pev->flags |= FL_SKIPLOCALHOST; + m_pBeam->pev->owner = m_pPlayer->edict(); + + m_pBeam->SetScrollRate( 110 ); + m_pBeam->SetNoise( 5 ); +#endif + +} + +void CQuakeGun::UpdateEffect( void ) +{ +#if !defined( CLIENT_DLL ) + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + Vector vecAiming = gpGlobals->v_forward; + Vector vecSrc = m_pPlayer->GetGunPosition( ); + + Vector vecDest = vecSrc + vecAiming * 2048; + edict_t *pentIgnore; + TraceResult tr; + + pentIgnore = m_pPlayer->edict(); + Vector tmpSrc = vecSrc + gpGlobals->v_up * -8 + gpGlobals->v_right * 3; + + // ALERT( at_console, "." ); + + UTIL_TraceLine( vecSrc, vecDest, dont_ignore_monsters, pentIgnore, &tr ); + + if (tr.fAllSolid) + return; + + if ( !m_pBeam ) + { + CreateEffect(); + } + + m_pBeam->SetStartPos( tr.vecEndPos ); +#endif + +} + +#if !defined( CLIENT_DLL ) +BOOL CQuakeGun::Deploy( ) +{ + m_pPlayer->pev->viewmodel = MAKE_STRING("models/v_crowbar.mdl"); + m_pPlayer->pev->weaponmodel = MAKE_STRING("models/p_9mmhandgun.mdl"); + strcpy( m_pPlayer->m_szAnimExtention, "onehanded" ); + +#ifdef CLIENT_DLL + g_flLightTime = 0.0; +#endif + + SendWeaponAnim( 0 ); + return TRUE; +} +#endif + +// Plays quad sound if needed +int CQuakeGun::SuperDamageSound() +{ + if ( m_pPlayer->m_iQuakeItems & IT_QUAD ) + { + if ( m_pPlayer->m_flNextQuadSound < gpGlobals->time) + { + m_pPlayer->m_flNextQuadSound = gpGlobals->time + 1; + return 1; + } + } + + return 0; +} + +// Firing the Quakegun forces the player to fire the appropriate weapon +void CQuakeGun::PrimaryAttack( void ) +{ + int iQuadSound = 0; + iQuadSound = SuperDamageSound(); + m_pPlayer->W_Attack( iQuadSound ); + +#if !defined( CLIENT_DLL ) + if ( m_pPlayer->m_iQuakeWeapon == IT_LIGHTNING && m_pPlayer->pev->deadflag == DEAD_NO ) + UpdateEffect(); +#endif + + m_bPlayedIdleAnim = FALSE; +} diff --git a/dmc/dlls/quake_gun.h b/dmc/dlls/quake_gun.h new file mode 100644 index 0000000..a0d16ec --- /dev/null +++ b/dmc/dlls/quake_gun.h @@ -0,0 +1,44 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== quake_gun.h ======================================================== + + This is a half-life weapon that fires every one of the quake weapons. + It's automatically given to all players. + +*/ +#include "effects.h" + +class CQuakeGun : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 1; } + int GetItemInfo(ItemInfo *p); + + int SuperDamageSound( void ); + + void PrimaryAttack( void ); + BOOL Deploy( void ); + + void UpdateEffect( void ); + + void CreateEffect ( void ); + void DestroyEffect ( void ); + + CBeam *m_pBeam; +}; diff --git a/dmc/dlls/quake_items.cpp b/dmc/dlls/quake_items.cpp new file mode 100644 index 0000000..0d83fe5 --- /dev/null +++ b/dmc/dlls/quake_items.cpp @@ -0,0 +1,1866 @@ +//=========== (C) Copyright 1996-2002, Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: Quake world items +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "shake.h" +#include "../engine/studio.h" +#include "weapons.h" +#include "quake_gun.h" +#include "hltv.h" + +extern unsigned short g_usPowerUp; + +class CQuakeItem : public CBaseEntity +{ +public: + void Spawn( void ); + + // Respawning + void EXPORT Materialize( void ); + void Respawn( float flTime ); + + virtual void SetObjectCollisionBox ( void ); + + // Touch + void EXPORT ItemTouch( CBaseEntity *pOther ); + virtual BOOL MyTouch( CBasePlayer *pOther ) { return FALSE; }; + + float m_flRespawnTime; +}; + +//----------------------------------------------------------------------------- +// Purpose: Spawn and drop to the floor +//----------------------------------------------------------------------------- + +void CQuakeItem :: SetObjectCollisionBox( void ) +{ + pev->absmin = pev->origin + Vector(-32, -32, 0); + pev->absmax = pev->origin + Vector(32, 32, 56); +} + +void CQuakeItem::Spawn() +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + + SetTouch(&CQuakeItem::ItemTouch); + + if (DROP_TO_FLOOR(ENT(pev)) == 0) + { + ALERT(at_error, "Item %s fell out of level at %f,%f,%f", STRING( pev->classname ), pev->origin.x, pev->origin.y, pev->origin.z); + UTIL_Remove( this ); + return; + } + + //UTIL_SetOrigin( pev, pev->origin + Vector(0,0,16) ); + + if (!m_flRespawnTime) + m_flRespawnTime = 20; +} + +//----------------------------------------------------------------------------- +// Purpose: Bring the item back +//----------------------------------------------------------------------------- +void CQuakeItem::Materialize() +{ + // Become visible and touchable + pev->effects &= ~EF_NODRAW; + SetTouch( &CQuakeItem::ItemTouch ); + + // Play respawn sound + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "items/itembk2.wav", 1, ATTN_NORM ); +} + +//----------------------------------------------------------------------------- +// Purpose: Setup the item's respawn in the time set +//----------------------------------------------------------------------------- +void CQuakeItem::Respawn( float flTime ) +{ + pev->effects |= EF_NODRAW; + SetTouch( NULL ); + + // Come back in time + SetThink ( &CQuakeItem::Materialize ); + pev->nextthink = gpGlobals->time + flTime; +} + + +//----------------------------------------------------------------------------- +// Purpose: Touch function that calls the virtual touch function +//----------------------------------------------------------------------------- +void CQuakeItem::ItemTouch( CBaseEntity *pOther ) +{ + // if it's not a player, ignore + if ( !pOther->IsPlayer() ) + return; + + //Dead? + if (pOther->pev->health <= 0) + return; + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + + // Call the virtual touch function + if ( MyTouch( pPlayer ) ) + { + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); + + // Respawn if it's not DM==2 + if (gpGlobals->deathmatch != 2) + { + Respawn( m_flRespawnTime ); + } + else + { + UTIL_Remove( this ); + } + } +} + +//====================================================================================== +// HEALTH ITEMS +//====================================================================================== +#define H_ROTTEN 1 +#define H_MEGA 2 + +class CItemHealth : public CQuakeItem +{ +public: + void Spawn( void ); + void Precache( void ); + BOOL MyTouch( CBasePlayer *pPlayer ); + void EXPORT MegahealthRot( void ); + + EHANDLE m_hRotTarget; + int m_iHealAmount; + int m_iHealType; +}; +LINK_ENTITY_TO_CLASS(item_health, CItemHealth); + +//-------------------------------------------- +// Spawn +void CItemHealth::Spawn( void ) +{ + Precache(); + + // Setup healing method + if (pev->spawnflags & H_ROTTEN) + { + SET_MODEL(ENT(pev), "models/w_medkits.mdl"); + pev->noise = MAKE_STRING( "items/r_item1.wav" ); + m_iHealAmount = 15; + m_iHealType = H_ROTTEN; + } + else if (pev->spawnflags & H_MEGA) + { + SET_MODEL(ENT(pev), "models/w_medkitl.mdl"); + pev->noise = MAKE_STRING( "items/r_item2.wav" ); + m_iHealAmount = 100; + m_iHealType = H_MEGA; + } + else + { + SET_MODEL(ENT(pev), "models/w_medkit.mdl"); + pev->noise = MAKE_STRING( "items/health1.wav" ); + m_iHealAmount = 25; + m_iHealType = H_ROTTEN; + } + + CQuakeItem::Spawn(); +} + +//-------------------------------------------- +// Precache +void CItemHealth::Precache() +{ + PRECACHE_MODEL("models/w_medkitl.mdl"); + PRECACHE_MODEL("models/w_medkits.mdl"); + PRECACHE_MODEL("models/w_medkit.mdl"); + PRECACHE_SOUND("items/r_item1.wav"); + PRECACHE_SOUND("items/r_item2.wav"); + PRECACHE_SOUND("items/health1.wav"); +} + +//-------------------------------------------- +// Health Touch +BOOL CItemHealth::MyTouch( CBasePlayer *pPlayer ) +{ + // Don't heal in DM==4 if they're invincible + if (gpGlobals->deathmatch == 4 && pPlayer->m_flInvincibleFinished > 0) + return FALSE; + + if (pPlayer->pev->health <= 0) + return FALSE; + + if (m_iHealType == H_MEGA) + { + if (pPlayer->pev->health >= 250) + return FALSE; + if ( !pPlayer->TakeHealth( m_iHealAmount, DMG_GENERIC | DMG_IGNORE_MAXHEALTH) ) + return FALSE; + } + else + { + // Heal the Player + if ( !pPlayer->TakeHealth( m_iHealAmount, DMG_GENERIC ) ) + return FALSE; + } + + ClientPrint( pPlayer->pev, HUD_PRINTNOTIFY, "#Get_Health", UTIL_dtos1(m_iHealAmount) ); + EMIT_SOUND( ENT(pev), CHAN_ITEM, STRING(pev->noise), 1, ATTN_NORM ); + + // Setup for respawn + if (m_iHealType == H_MEGA) + { + // Go invisible and fire targets + pev->effects |= EF_NODRAW; + SetTouch( NULL ); + SUB_UseTargets( pPlayer, USE_TOGGLE, 0 ); + + pPlayer->m_iQuakeItems |= IT_SUPERHEALTH; + if (gpGlobals->deathmatch != 4) + { + SetThink( &CItemHealth::MegahealthRot ); + pev->nextthink = gpGlobals->time + 5; + } + m_hRotTarget = pPlayer; + + // Return FALSE, because we want to handle our respawn ourselves + return FALSE; + } + + // Respawn as normal + return TRUE; +} + +//-------------------------------------------- +// Megahealth Rot function. Reduce player's health until it's below 100. Then respawn. +void CItemHealth::MegahealthRot( void ) +{ + if (m_hRotTarget) + { + CBasePlayer *pPlayer = ((CBasePlayer *)((CBaseEntity *)m_hRotTarget)); + + if (pPlayer->pev->health > pPlayer->pev->max_health ) + { + pPlayer->pev->health--; + pev->nextthink = gpGlobals->time + 1; + return; + } + + pPlayer->m_iQuakeItems &= ~IT_SUPERHEALTH; + } + + // Respawn if it's not DM==2 + if (gpGlobals->deathmatch != 2) + { + SetThink ( &CItemHealth::Materialize ); + pev->nextthink = gpGlobals->time + 20; + } + else + { + UTIL_Remove( this ); + } +} + +//====================================================================================== +// ARMOR ITEMS +//====================================================================================== +class CItemArmor : public CQuakeItem +{ +public: + BOOL MyTouch( CBasePlayer *pPlayer ); + + float m_flArmorValue; + float m_flArmorType; + int m_iArmorBit; +}; + +// Armor Touch +BOOL CItemArmor::MyTouch( CBasePlayer *pPlayer ) +{ + if (pPlayer->pev->health <= 0) + return FALSE; + + // Don't pickup in DM==4 if they're invincible + if (gpGlobals->deathmatch == 4 && pPlayer->m_flInvincibleFinished > 0) + return FALSE; + + // Don't pickup if this armor isn't as good as the stuff we've got + if ( (pPlayer->pev->armortype * pPlayer->pev->armorvalue) >= (m_flArmorType * m_flArmorValue) ) + return FALSE; + + pPlayer->pev->armortype = m_flArmorType; + pPlayer->pev->armorvalue = m_flArmorValue; + pPlayer->m_iQuakeItems &= ~(IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3); + pPlayer->m_iQuakeItems |= m_iArmorBit; + + EMIT_SOUND( ENT( pPlayer->pev ), CHAN_ITEM, "items/armor1.wav", 1, ATTN_NORM ); + + return TRUE; +} + +//=============== +// Green Armor +class CItemArmorGreen : public CItemArmor +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(item_armor1, CItemArmorGreen); + +// Spawn +void CItemArmorGreen::Spawn( void ) +{ + Precache(); + SET_MODEL(ENT(pev), "models/armour_g.mdl"); + CItemArmor::Spawn(); + + m_flArmorValue = 100; + m_flArmorType = 0.3; + m_iArmorBit = IT_ARMOR1; +} + +// Precache +void CItemArmorGreen::Precache( void ) +{ + PRECACHE_MODEL( "models/armour_g.mdl" ); + PRECACHE_SOUND( "items/armor1.wav" ); +} + +//=============== +// Yellow Armor +class CItemArmorYellow : public CItemArmor +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(item_armor2, CItemArmorYellow); + +// Spawn +void CItemArmorYellow::Spawn( void ) +{ + Precache(); + SET_MODEL(ENT(pev), "models/armour_y.mdl"); + CItemArmor::Spawn(); + + m_flArmorValue = 150; + m_flArmorType = 0.6; + m_iArmorBit = IT_ARMOR2; +} + +// Precache +void CItemArmorYellow::Precache( void ) +{ + PRECACHE_MODEL( "models/armour_y.mdl" ); + PRECACHE_SOUND( "items/armor1.wav" ); +} + +//=============== +// Red Armor +class CItemArmorRed : public CItemArmor +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(item_armor3, CItemArmorRed); +LINK_ENTITY_TO_CLASS(item_armorInv, CItemArmorRed); + +// Spawn +void CItemArmorRed::Spawn( void ) +{ + Precache(); + SET_MODEL(ENT(pev), "models/armour_r.mdl"); + CItemArmor::Spawn(); + + m_flArmorValue = 200; + m_flArmorType = 0.8; + m_iArmorBit = IT_ARMOR3; +} + +// Precache +void CItemArmorRed::Precache( void ) +{ + PRECACHE_MODEL( "models/armour_r.mdl" ); + PRECACHE_SOUND( "items/armor1.wav" ); +} + +//====================================================================================== +// WEAPON ITEMS +//====================================================================================== +void CBasePlayer::CheckAmmo() +{ + if (m_iAmmoShells > 100) + m_iAmmoShells = 100; + if (m_iAmmoNails > 200) + m_iAmmoNails = 200; + if (m_iAmmoRockets > 100) + m_iAmmoRockets = 100; + if (m_iAmmoCells > 100) + m_iAmmoCells = 100; +} + +int RankForWeapon(int iWeapon) +{ + switch (iWeapon) + { + case IT_LIGHTNING: + return 1; break; + case IT_ROCKET_LAUNCHER: + return 2; break; + case IT_SUPER_NAILGUN: + return 3; break; + case IT_GRENADE_LAUNCHER: + return 4; break; + case IT_SUPER_SHOTGUN: + return 5; break; + case IT_NAILGUN: + return 6; break; + + default: + break; + } + + return 7; +} + +int WeaponCode(int iWeapon) +{ + switch (iWeapon) + { + case IT_SUPER_SHOTGUN: + return 3; break; + case IT_NAILGUN: + return 4; break; + case IT_SUPER_NAILGUN: + return 5; break; + case IT_GRENADE_LAUNCHER: + return 6; break; + case IT_ROCKET_LAUNCHER: + return 7; break; + case IT_LIGHTNING: + return 8; break; + + default: + break; + } + + return 1; +} + +int GetWeaponValue ( int iWeapon ) +{ + int iWepValue; + + switch ( iWeapon ) + { + case IT_AXE: iWepValue = 1; break; + case IT_SHOTGUN: iWepValue = 2; break; + case IT_SUPER_SHOTGUN: iWepValue = 3; break; + case IT_NAILGUN: iWepValue = 4; break; + case IT_SUPER_NAILGUN: iWepValue = 5; break; + case IT_GRENADE_LAUNCHER: iWepValue = 6; break; + case IT_ROCKET_LAUNCHER: iWepValue = 7; break; + case IT_LIGHTNING: iWepValue = 8; break; + } + + return iWepValue; +} +// Change weapon only if the new one's better +void CBasePlayer::Deathmatch_Weapon(int iOldWeapon, int iNewWeapon) +{ + int iPickedWep = GetWeaponValue( iNewWeapon ); + int iOldWep = GetWeaponValue( m_iQuakeWeapon ); + + switch ( m_iAutoWepSwitch ) + { + case 0: return; break; + case 1: + W_ChangeWeapon( iPickedWep ); break; + case 2: + + if ( iPickedWep == 8 && !FBitSet(pev->flags , FL_INWATER) || iPickedWep > iOldWep ) + W_ChangeWeapon( iPickedWep ); + break; + } + + +} + +//----------------------------------------------- +// Base Quake Weapon object +class CItemWeapon : public CQuakeItem +{ +public: + BOOL MyTouch( CBasePlayer *pPlayer ); + + int m_iWeapon; +}; + +BOOL CItemWeapon::MyTouch( CBasePlayer *pPlayer ) +{ + BOOL bLeaveWeapon = FALSE; + + if (gpGlobals->deathmatch == 2 || gpGlobals->deathmatch == 3 || gpGlobals->deathmatch == 5 || CVAR_GET_FLOAT("mp_weaponstay") > 0 ) + bLeaveWeapon = TRUE; + + // Leave the weapon if the player's already got it + if ( bLeaveWeapon && (pPlayer->m_iQuakeItems & m_iWeapon) ) + return FALSE; + + if ( pPlayer->pev->health <= 0) + return FALSE; + + // Give the player some ammo + switch (m_iWeapon) + { + case IT_NAILGUN: + pPlayer->m_iAmmoNails += 30; + break; + case IT_SUPER_NAILGUN: + pPlayer->m_iAmmoNails += 30; + break; + case IT_SUPER_SHOTGUN: + pPlayer->m_iAmmoShells += 5; + break; + case IT_ROCKET_LAUNCHER: + pPlayer->m_iAmmoRockets += 5; + break; + case IT_GRENADE_LAUNCHER: + pPlayer->m_iAmmoRockets += 5; + break; + case IT_LIGHTNING: + pPlayer->m_iAmmoCells += 15; + break; + default: + break; + } + pPlayer->CheckAmmo(); + + EMIT_SOUND( ENT(pev), CHAN_ITEM, "weapons/pkup.wav", 1, ATTN_NORM ); + + // Change to new weapon? + int iOldItems = pPlayer->m_iQuakeWeapon; + pPlayer->m_iQuakeItems |= m_iWeapon; + + pPlayer->Deathmatch_Weapon(iOldItems, m_iWeapon); + + + // Update HUD + pPlayer->W_SetCurrentAmmo(); + pPlayer->m_iClientQuakeWeapon = -1; + pPlayer->m_fWeapon = FALSE; + pPlayer->m_fKnownItem = FALSE; + pPlayer->UpdateClientData(); + + if (bLeaveWeapon) + return FALSE; + + // Respawn + m_flRespawnTime = 30; + + return TRUE; +} + +//=============== +// Super Shotgun +class CItemWeaponSuperShotgun : public CItemWeapon +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(weapon_supershotgun, CItemWeaponSuperShotgun); + +// Spawn +void CItemWeaponSuperShotgun::Spawn( void ) +{ + if ( gpGlobals->deathmatch > 3) + { + UTIL_Remove(this); + return; + } + + Precache(); + SET_MODEL(ENT(pev), "models/g_shot2.mdl"); + CItemWeapon::Spawn(); + + m_iWeapon = IT_SUPER_SHOTGUN; + pev->netname = MAKE_STRING("Double-barrelled Shotgun"); +} + +// Precache +void CItemWeaponSuperShotgun::Precache( void ) +{ + PRECACHE_MODEL( "models/g_shot2.mdl" ); +} + +//=============== +// Nailgun +class CItemWeaponNailgun : public CItemWeapon +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(weapon_nailgun, CItemWeaponNailgun); + +// Spawn +void CItemWeaponNailgun::Spawn( void ) +{ + if ( gpGlobals->deathmatch > 3) + { + UTIL_Remove(this); + return; + } + + Precache(); + SET_MODEL(ENT(pev), "models/g_nail.mdl"); + CItemWeapon::Spawn(); + + m_iWeapon = IT_NAILGUN; + pev->netname = MAKE_STRING("Nailgun"); +} + +// Precache +void CItemWeaponNailgun::Precache( void ) +{ + PRECACHE_MODEL( "models/g_nail.mdl" ); +} + +//=============== +// Super Nailgun +class CItemWeaponSuperNailgun : public CItemWeapon +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(weapon_supernailgun, CItemWeaponSuperNailgun); + +// Spawn +void CItemWeaponSuperNailgun::Spawn( void ) +{ + if ( gpGlobals->deathmatch > 3) + { + UTIL_Remove(this); + return; + } + + Precache(); + SET_MODEL(ENT(pev), "models/g_nail2.mdl"); + CItemWeapon::Spawn(); + + m_iWeapon = IT_SUPER_NAILGUN; + pev->netname = MAKE_STRING("Super Nailgun"); +} + +// Precache +void CItemWeaponSuperNailgun::Precache( void ) +{ + PRECACHE_MODEL( "models/g_nail2.mdl" ); +} + +//=============== +// Grenade Launcher +class CItemWeaponGrenadeLauncher : public CItemWeapon +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(weapon_grenadelauncher, CItemWeaponGrenadeLauncher); + +// Spawn +void CItemWeaponGrenadeLauncher::Spawn( void ) +{ + if ( gpGlobals->deathmatch > 3) + { + UTIL_Remove(this); + return; + } + + Precache(); + SET_MODEL(ENT(pev), "models/g_rock.mdl"); + CItemWeapon::Spawn(); + + m_iWeapon = IT_GRENADE_LAUNCHER; + pev->netname = MAKE_STRING("Grenade Launcher"); +} + +// Precache +void CItemWeaponGrenadeLauncher::Precache( void ) +{ + PRECACHE_MODEL( "models/g_rock.mdl" ); +} + +//=============== +// Rocket Launcher +class CItemWeaponRocketLauncher : public CItemWeapon +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(weapon_rocketlauncher, CItemWeaponRocketLauncher); + +// Spawn +void CItemWeaponRocketLauncher::Spawn( void ) +{ + if ( gpGlobals->deathmatch > 3) + { + UTIL_Remove(this); + return; + } + + Precache(); + SET_MODEL(ENT(pev), "models/g_rock2.mdl"); + CItemWeapon::Spawn(); + + m_iWeapon = IT_ROCKET_LAUNCHER; + pev->netname = MAKE_STRING("Rocket Launcher"); +} + +// Precache +void CItemWeaponRocketLauncher::Precache( void ) +{ + PRECACHE_MODEL( "models/g_rock2.mdl" ); +} + +//=============== +// Lightning Gun +class CItemWeaponLightning : public CItemWeapon +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(weapon_lightning, CItemWeaponLightning); + +// Spawn +void CItemWeaponLightning::Spawn( void ) +{ + if ( gpGlobals->deathmatch > 3) + { + UTIL_Remove(this); + return; + } + + Precache(); + SET_MODEL(ENT(pev), "models/g_light.mdl"); + CItemWeapon::Spawn(); + + m_iWeapon = IT_LIGHTNING; + pev->netname = MAKE_STRING("Thunderbolt"); +} + +// Precache +void CItemWeaponLightning::Precache( void ) +{ + PRECACHE_MODEL( "models/g_light.mdl" ); +} + +//====================================================================================== +// AMMO ITEMS +//====================================================================================== +#define BIG_AMMOBOX 1 + +class CItemAmmo : public CQuakeItem +{ +public: + void Spawn( void ); + void Precache( void ); + BOOL MyTouch( CBasePlayer *pPlayer ); + + int m_isSmallBox; + int m_isLargeBox; + int ammo_shells; + int ammo_nails; + int ammo_rockets; + int ammo_cells; +}; + +// Spawn +void CItemAmmo::Spawn( void ) +{ + Precache(); + + // Set the box size + if (pev->spawnflags & BIG_AMMOBOX) + { + SET_MODEL( ENT(pev), STRING(m_isLargeBox) ); + ammo_shells *= 2; + ammo_nails *= 2; + ammo_rockets *= 2; + ammo_cells *= 2; + } + else + { + SET_MODEL( ENT(pev), STRING(m_isSmallBox) ); + } + + // Halve respawn times in DM==3 and DM==5 + if (gpGlobals->deathmatch == 3 || gpGlobals->deathmatch == 5) + m_flRespawnTime = 15; + else + m_flRespawnTime = 30; + + CQuakeItem::Spawn(); +} + +// Precache +void CItemAmmo::Precache( void ) +{ + if (pev->spawnflags & BIG_AMMOBOX) + PRECACHE_MODEL( (char*)STRING(m_isLargeBox) ); + else + PRECACHE_MODEL( (char*)STRING(m_isSmallBox) ); +} + +BOOL CItemAmmo::MyTouch( CBasePlayer *pPlayer ) +{ + if (pPlayer->pev->health <= 0) + return FALSE; + + // Find the player's best weapon + int iBestWeapon = pPlayer->W_BestWeapon(); + + // Return if the player can't carry + if (ammo_shells && pPlayer->m_iAmmoShells >= 100) + return FALSE; + if (ammo_nails && pPlayer->m_iAmmoNails >= 200) + return FALSE; + if (ammo_rockets && pPlayer->m_iAmmoRockets >= 100) + return FALSE; + if (ammo_cells && pPlayer->m_iAmmoCells >= 100) + return FALSE; + + pPlayer->m_iAmmoShells += ammo_shells; + pPlayer->m_iAmmoNails += ammo_nails; + pPlayer->m_iAmmoRockets += ammo_rockets; + pPlayer->m_iAmmoCells += ammo_cells; + pPlayer->CheckAmmo(); + + EMIT_SOUND( ENT(pev), CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM ); + + // Change to a better weapon if possible + if ( pPlayer->m_iQuakeWeapon == iBestWeapon ) + { + pPlayer->m_iQuakeWeapon = pPlayer->W_BestWeapon(); + } + + pPlayer->W_SetCurrentAmmo(); + return TRUE; +} + +//=============== +// Shells +class CItemAmmoShells : public CItemAmmo +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS(item_shells, CItemAmmoShells); + +// Spawn +void CItemAmmoShells::Spawn( void ) +{ + if ( gpGlobals->deathmatch == 4) + { + UTIL_Remove(this); + return; + } + + m_isSmallBox = MAKE_STRING("models/w_shotbox.mdl"); + m_isLargeBox = MAKE_STRING("models/w_shotbox_big.mdl"); + pev->netname = MAKE_STRING("shells"); + ammo_shells = 20; + + CItemAmmo::Spawn(); +} + +//=============== +// Spikes +class CItemAmmoSpikes : public CItemAmmo +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS(item_spikes, CItemAmmoSpikes); + +// Spawn +void CItemAmmoSpikes::Spawn( void ) +{ + if ( gpGlobals->deathmatch == 4) + { + UTIL_Remove(this); + return; + } + + m_isSmallBox = MAKE_STRING("models/b_nail0.mdl"); + m_isLargeBox = MAKE_STRING("models/b_nail1.mdl"); + pev->netname = MAKE_STRING("nails"); + ammo_nails = 25; + + CItemAmmo::Spawn(); +} + +//=============== +// Rockets +class CItemAmmoRockets : public CItemAmmo +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS(item_rockets, CItemAmmoRockets); + +// Spawn +void CItemAmmoRockets::Spawn( void ) +{ + if ( gpGlobals->deathmatch == 4) + { + UTIL_Remove(this); + return; + } + + m_isSmallBox = MAKE_STRING("models/w_rpgammo.mdl"); + m_isLargeBox = MAKE_STRING("models/w_rpgammo_big.mdl"); + pev->netname = MAKE_STRING("rockets"); + ammo_rockets = 5; + + CItemAmmo::Spawn(); +} + +//=============== +// Cells +class CItemAmmoCells : public CItemAmmo +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS(item_cells, CItemAmmoCells); + +// Spawn +void CItemAmmoCells::Spawn( void ) +{ + if ( gpGlobals->deathmatch == 4) + { + UTIL_Remove(this); + return; + } + + m_isSmallBox = MAKE_STRING("models/w_battery.mdl"); + m_isLargeBox = MAKE_STRING("models/w_battery.mdl"); + pev->netname = MAKE_STRING("cells"); + ammo_cells = 6; + + CItemAmmo::Spawn(); +} + +//=============== +// Weapon ammo +// Another method of placing ammo. Quake still uses it in some maps. +#define AW_SHOTGUN 1 +#define AW_ROCKET 2 +#define AW_SPIKES 4 +#define AW_BIG 8 + +class CItemAmmoWeapon : public CItemAmmo +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS(item_weapon, CItemAmmoWeapon); + +// Spawn +void CItemAmmoWeapon::Spawn( void ) +{ + if ( gpGlobals->deathmatch == 4) + { + UTIL_Remove(this); + return; + } + + // Shells + if (pev->spawnflags & AW_SHOTGUN) + { + m_isSmallBox = MAKE_STRING("models/w_shotbox.mdl"); + m_isLargeBox = MAKE_STRING("models/w_shotbox_big.mdl"); + pev->netname = MAKE_STRING("shells"); + ammo_shells = 20; + } + + // Nails + if (pev->spawnflags & AW_SPIKES) + { + m_isSmallBox = MAKE_STRING("models/b_nail0.mdl"); + m_isLargeBox = MAKE_STRING("models/b_nail1.mdl"); + pev->netname = MAKE_STRING("nails"); + ammo_nails = 25; + } + + // Rockets + if (pev->spawnflags & AW_ROCKET) + { + m_isSmallBox = MAKE_STRING("models/w_rpgammo.mdl"); + m_isLargeBox = MAKE_STRING("models/w_rpgammo_big.mdl"); + pev->netname = MAKE_STRING("rockets"); + ammo_rockets = 5; + } + + // Big? + if (pev->spawnflags & AW_BIG) + pev->spawnflags = BIG_AMMOBOX; + else + pev->spawnflags = 0; + + CItemAmmo::Spawn(); +} + +//=============================================================================== +// POWERUPS +//=============================================================================== +class CItemPowerup : public CQuakeItem +{ +public: + BOOL MyTouch( CBasePlayer *pPlayer ); + + int m_iPowerupBit; + float invincible_finished; + float radsuit_finished; + float invisible_finished; + float super_damage_finished; +}; + +// Powerup Touch +BOOL CItemPowerup::MyTouch( CBasePlayer *pPlayer ) +{ + if (pPlayer->pev->health <= 0) + return FALSE; + + EMIT_SOUND( ENT(pev), CHAN_ITEM, STRING(pev->noise), 1, ATTN_NORM ); + + pPlayer->m_iQuakeItems |= m_iPowerupBit; + + int iPowerUp = 0; + + // Invincibility + if (invincible_finished) + { + // Make them glow red + + if ( pPlayer->m_iQuakeItems & IT_QUAD ) + { + pPlayer->pev->renderfx = kRenderFxGlowShell; + pPlayer->pev->rendercolor = Vector( 255, 125, 255 ); // RGB + pPlayer->pev->renderamt = 100; // Shell size + + iPowerUp = 3; + } + else + { + pPlayer->pev->renderfx = kRenderFxGlowShell; + pPlayer->pev->rendercolor = Vector( 255, 128, 0 ); // RGB + pPlayer->pev->renderamt = 100; // Shell size + + iPowerUp = 2; + } + + if ( pPlayer->m_iQuakeItems & IT_INVISIBILITY ) + { + pPlayer->pev->rendermode = kRenderTransColor; + pPlayer->pev->renderamt = 1; + } + pPlayer->m_flInvincibleFinished = gpGlobals->time + invincible_finished; + + } + + // Quad Damage + if (super_damage_finished) + { + // Make them glow blue + + if ( pPlayer->m_iQuakeItems & IT_INVULNERABILITY ) + { + pPlayer->pev->renderfx = kRenderFxGlowShell; + pPlayer->pev->rendercolor = Vector( 255, 125, 255 ); // RGB + pPlayer->pev->renderamt = 100; // Shell size + + iPowerUp = 3; + } + else + { + pPlayer->pev->renderfx = kRenderFxGlowShell; + pPlayer->pev->rendercolor = Vector( 128, 128, 255 ); // RGB + pPlayer->pev->renderamt = 100; // Shell size + + iPowerUp = 1; + } + + if ( pPlayer->m_iQuakeItems & IT_INVISIBILITY ) + { + pPlayer->pev->rendermode = kRenderTransColor; + pPlayer->pev->renderamt = 1; + } + + + pPlayer->m_flSuperDamageFinished = gpGlobals->time + super_damage_finished; + + // Remove armor and cells if DM==4 + if (gpGlobals->deathmatch == 4) + { + pPlayer->pev->armortype = 0; + pPlayer->pev->armorvalue = 0; + pPlayer->m_iAmmoCells = 0; + } + } + + // Radiation suit + if (radsuit_finished) + pPlayer->m_flRadsuitFinished = gpGlobals->time + radsuit_finished; + + // Invisibility + if (invisible_finished) + { + pPlayer->m_flInvisibleFinished = gpGlobals->time + invisible_finished; + + pPlayer->pev->renderfx = kRenderFxGlowShell; + pPlayer->pev->rendercolor = Vector( 128, 128, 128 ); // RGB + pPlayer->pev->renderamt = 5; // Shell size + + } + + // tell director about it + MESSAGE_BEGIN( MSG_SPEC, SVC_DIRECTOR ); + WRITE_BYTE ( 9 ); // command length in bytes + WRITE_BYTE ( DRC_CMD_EVENT ); // powerup pickup + WRITE_SHORT( ENTINDEX(pPlayer->edict()) ); // player is primary target + WRITE_SHORT( ENTINDEX(this->edict()) ); // powerup as second target + WRITE_LONG( 9 ); // highst prio in game + MESSAGE_END(); + + pPlayer->W_SetCurrentAmmo(); + + PLAYBACK_EVENT_FULL( FEV_GLOBAL | FEV_RELIABLE, + pPlayer->edict(), g_usPowerUp, 0, (float *)&g_vecZero, (float *)&g_vecZero, + (float)iPowerUp, 0.0, pPlayer->entindex(), pPlayer->pev->team, 0, 0 ); + + return TRUE; +} + + +//=============== +// Pentagram +class CItemPowerupInvincible : public CItemPowerup +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(item_artifact_invulnerability, CItemPowerupInvincible); + +// Spawn +void CItemPowerupInvincible::Spawn( void ) +{ + Precache(); + CQuakeItem::Spawn(); + + m_flRespawnTime = 300; + invincible_finished = 30; + + SET_MODEL(ENT(pev), "models/pow_invuln.mdl"); + pev->netname = MAKE_STRING("Pentagram of Protection"); + pev->noise = MAKE_STRING("items/protect.wav"); + m_iPowerupBit = IT_INVULNERABILITY; + + // Make it glow red + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 255, 128, 0 ); // RGB + pev->renderamt = 100; // Shellsize +} + +// Precache +void CItemPowerupInvincible::Precache( void ) +{ + PRECACHE_MODEL("models/pow_invuln.mdl"); + PRECACHE_SOUND("items/protect.wav"); + PRECACHE_SOUND("items/protect2.wav"); + PRECACHE_SOUND("items/protect3.wav"); +} + +//=============== +// Radiation Suit +class CItemPowerupRadsuit : public CItemPowerup +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(item_artifact_envirosuit, CItemPowerupRadsuit); + +// Spawn +void CItemPowerupRadsuit::Spawn( void ) +{ + Precache(); + CQuakeItem::Spawn(); + + m_flRespawnTime = 60; + radsuit_finished = 30; + + SET_MODEL(ENT(pev), "models/suit.mdl"); + pev->netname = MAKE_STRING("Biosuit"); + pev->noise = MAKE_STRING("items/suit.wav"); + m_iPowerupBit = IT_SUIT; +} + +// Precache +void CItemPowerupRadsuit::Precache( void ) +{ + PRECACHE_MODEL("models/suit.mdl"); + PRECACHE_SOUND("items/suit.wav"); + PRECACHE_SOUND("items/suit2.wav"); +} + +//=============== +// Ring of Invisibility +class CItemPowerupInvisibility : public CItemPowerup +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(item_artifact_invisibility, CItemPowerupInvisibility); + +// Spawn +void CItemPowerupInvisibility::Spawn( void ) +{ + Precache(); + CQuakeItem::Spawn(); + + m_flRespawnTime = 300; + invisible_finished = 30; + + SET_MODEL(ENT(pev), "models/pow_invis.mdl"); + pev->netname = MAKE_STRING("Ring of Shadows"); + pev->noise = MAKE_STRING("items/inv1.wav"); + m_iPowerupBit = IT_INVISIBILITY; + + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 128, 128, 128 ); // RGB + pev->renderamt = 25; // Shell size + + pev->rendermode = kRenderTransColor; + pev->renderamt = 30; +} + +// Precache +void CItemPowerupInvisibility::Precache( void ) +{ + PRECACHE_MODEL("models/pow_invis.mdl"); + PRECACHE_SOUND("items/inv1.wav"); + PRECACHE_SOUND("items/inv2.wav"); + PRECACHE_SOUND("items/inv3.wav"); +} + +//=============== +// Quad Damage +class CItemPowerupQuad : public CItemPowerup +{ +public: + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS(item_artifact_super_damage, CItemPowerupQuad); + +// Spawn +void CItemPowerupQuad::Spawn( void ) +{ + Precache(); + CQuakeItem::Spawn(); + + m_flRespawnTime = 60; + super_damage_finished = 30; + + SET_MODEL(ENT(pev), "models/pow_quad.mdl"); + pev->netname = MAKE_STRING("Quad Damage"); + pev->noise = MAKE_STRING("items/damage.wav"); + m_iPowerupBit = IT_QUAD; + + // Make it glow blue + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor = Vector( 128, 128, 255 ); // RGB + pev->renderamt = 100; // Shell size +} + +// Precache +void CItemPowerupQuad::Precache( void ) +{ + PRECACHE_MODEL("models/pow_quad.mdl"); + PRECACHE_SOUND("items/damage.wav"); + PRECACHE_SOUND("items/damage2.wav"); + PRECACHE_SOUND("items/damage3.wav"); +} + +//=============================================================================== +// PLAYER BACKPACKS +//=============================================================================== +class CItemBackpack : public CQuakeItem +{ +public: + void Spawn( void ); +// void SetBox ( void ); + virtual void SetObjectCollisionBox ( void ); + + BOOL MyTouch( CBasePlayer *pPlayer ); + + int m_iItems; + int ammo_shells; + int ammo_nails; + int ammo_rockets; + int ammo_cells; +}; +LINK_ENTITY_TO_CLASS(item_backpack, CItemBackpack); + +void CItemBackpack :: SetObjectCollisionBox( void ) +{ + pev->absmin = pev->origin + Vector(-32, -32, 0); + pev->absmax = pev->origin + Vector(32, 32, 56); +} + +// Spawn +void CItemBackpack::Spawn() +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + UTIL_SetOrigin( pev, pev->origin ); + SET_MODEL(ENT(pev), "models/backpack.mdl"); + + SetTouch(&CItemBackpack::ItemTouch); +} + +// Drop a backpack containing this player's ammo/weapons +void CBasePlayer::DropBackpack() +{ + // Any ammo to drop? + if ( !(m_iAmmoShells + m_iAmmoNails + m_iAmmoRockets + m_iAmmoCells) ) + return; + + // Create the pack + CItemBackpack *pPack = (CItemBackpack *)CBaseEntity::Create( "item_backpack", pev->origin - Vector(0, 0, 24), g_vecZero, edict() ); + pPack->pev->velocity = Vector( RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), 300 ); + pPack->Spawn(); + + // Put the player's weapon in the pack + pPack->m_iItems = m_iQuakeWeapon; + switch (pPack->m_iItems) + { + case IT_AXE: + pPack->pev->netname = MAKE_STRING("Crowbar"); break; + case IT_SHOTGUN: + pPack->pev->netname = MAKE_STRING("Shotgun"); break; + case IT_SUPER_SHOTGUN: + pPack->pev->netname = MAKE_STRING("Double-barrelled Shotgun"); break; + case IT_NAILGUN: + pPack->pev->netname = MAKE_STRING("Nailgun"); break; + case IT_SUPER_NAILGUN: + pPack->pev->netname = MAKE_STRING("Super Nailgun"); break; + case IT_GRENADE_LAUNCHER: + pPack->pev->netname = MAKE_STRING("Grenade Launcher"); break; + case IT_ROCKET_LAUNCHER: + pPack->pev->netname = MAKE_STRING("Rocket Launcher"); break; + case IT_LIGHTNING: + pPack->pev->netname = MAKE_STRING("Thunderbolt"); break; + default: + pPack->pev->netname = MAKE_STRING("Invalid weapon."); break; + } + + // Put the ammo in + pPack->ammo_shells = m_iAmmoShells; + pPack->ammo_nails = m_iAmmoNails; + pPack->ammo_rockets = m_iAmmoRockets; + pPack->ammo_cells = m_iAmmoCells; + + //Remove them from the player + m_iAmmoShells = m_iAmmoNails = m_iAmmoRockets = m_iAmmoCells = 0; + + // Remove after 2 mins + pPack->pev->nextthink = gpGlobals->time + 120; + pPack->SetThink( &CItemBackpack::SUB_Remove ); + + // Remove all weapons + m_iQuakeItems = 0; + m_iQuakeWeapon = 0; +} + +// Pickup backpack +BOOL CItemBackpack::MyTouch( CBasePlayer *pPlayer ) +{ + if (pPlayer->pev->health <= 0) + return FALSE; + if (gpGlobals->deathmatch == 4 && pPlayer->m_flInvincibleFinished > 0) + return FALSE; + + + + if (gpGlobals->deathmatch == 4) + { + pPlayer->pev->health += 10; + ClientPrint( pPlayer->pev, HUD_PRINTNOTIFY, "#Additional_Health" ); + if ((pPlayer->pev->health > 250) && (pPlayer->pev->health < 300)) + EMIT_SOUND( ENT(pPlayer->pev), CHAN_ITEM, "items/protect3.wav", 1, ATTN_NORM ); + else + EMIT_SOUND( ENT(pPlayer->pev), CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM ); + + // Become invulnerable if the player's reached 300 health + if (pPlayer->pev->health > 299) + { + if (pPlayer->m_flInvincibleFinished == 0) + { + // Give player invincibility and quad + pPlayer->m_flInvincibleFinished = gpGlobals->time + 30; + pPlayer->m_flSuperDamageFinished = gpGlobals->time + 30; + pPlayer->m_iQuakeItems |= (IT_INVULNERABILITY | IT_QUAD); + pPlayer->m_iAmmoCells = 0; + + // Make player glow red + pPlayer->pev->renderfx = kRenderFxGlowShell; + pPlayer->pev->rendercolor = Vector( 255, 128, 0 ); // RGB + pPlayer->pev->renderamt = 100; // Shell size + + EMIT_SOUND( ENT(pPlayer->pev), CHAN_VOICE, "items/sight1.wav", 1, ATTN_NORM ); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "#Bonus_Power", STRING(pPlayer->pev->netname) ); + } + } + + UTIL_Remove( this ); + + // We've removed ourself, so don't let CQuakeItem handle respawn + return FALSE; + } + + BOOL bPrintComma = FALSE; + + // Get the weapon from the pack + if (m_iItems) + { + if ( !(pPlayer->m_iQuakeItems & m_iItems) ) + { + bPrintComma = TRUE; + + switch ( m_iItems ) + { + case IT_SUPER_SHOTGUN: + ClientPrint( pPlayer->pev, HUD_PRINTNOTIFY, "#You_Get_SS", UTIL_dtos1( ammo_shells ), UTIL_dtos2 ( ammo_nails ), UTIL_dtos3 ( ammo_rockets ), UTIL_dtos4 ( ammo_cells ) ); break; + case IT_NAILGUN: + ClientPrint( pPlayer->pev, HUD_PRINTNOTIFY, "#You_Get_NG", UTIL_dtos1( ammo_shells ), UTIL_dtos2 ( ammo_nails ), UTIL_dtos3 ( ammo_rockets ), UTIL_dtos4 ( ammo_cells ) ); break; + case IT_SUPER_NAILGUN: + ClientPrint( pPlayer->pev, HUD_PRINTNOTIFY, "#You_Get_SG", UTIL_dtos1( ammo_shells ), UTIL_dtos2 ( ammo_nails ), UTIL_dtos3 ( ammo_rockets ), UTIL_dtos4 ( ammo_cells ) ); break; + case IT_GRENADE_LAUNCHER: + ClientPrint( pPlayer->pev, HUD_PRINTNOTIFY, "#You_Get_GL", UTIL_dtos1( ammo_shells ), UTIL_dtos2 ( ammo_nails ), UTIL_dtos3 ( ammo_rockets ), UTIL_dtos4 ( ammo_cells ) ); break; + case IT_ROCKET_LAUNCHER: + ClientPrint( pPlayer->pev, HUD_PRINTNOTIFY, "#You_Get_RL", UTIL_dtos1( ammo_shells ), UTIL_dtos2 ( ammo_nails ), UTIL_dtos3 ( ammo_rockets ), UTIL_dtos4 ( ammo_cells ) ); break; + case IT_LIGHTNING: + ClientPrint( pPlayer->pev, HUD_PRINTNOTIFY, "#You_Get_LG", UTIL_dtos1( ammo_shells ), UTIL_dtos2 ( ammo_nails ), UTIL_dtos3 ( ammo_rockets ), UTIL_dtos4 ( ammo_cells ) ); break; + } + } + else + ClientPrint( pPlayer->pev, HUD_PRINTNOTIFY, "#You_Get_NoGun", UTIL_dtos1( ammo_shells ), UTIL_dtos2 ( ammo_nails ), UTIL_dtos3 ( ammo_rockets ), UTIL_dtos4 ( ammo_cells ) ); + } + + // Get ammo from pack + pPlayer->m_iAmmoShells += ammo_shells; + pPlayer->m_iAmmoNails += ammo_nails; + pPlayer->m_iAmmoRockets += ammo_rockets; + pPlayer->m_iAmmoCells += ammo_cells; + pPlayer->CheckAmmo(); + + int iNewWeapon = m_iItems; + if (!iNewWeapon) + iNewWeapon = pPlayer->m_iQuakeWeapon; + int iOldWeapon = pPlayer->m_iQuakeItems; + pPlayer->m_iQuakeItems |= m_iItems; + + // Give them at least 5 rockets in DM==3 and DM==5 + if ( (gpGlobals->deathmatch==3 || gpGlobals->deathmatch == 5) & ( (WeaponCode(iNewWeapon)==6) || (WeaponCode(iNewWeapon)==7) ) & (pPlayer->m_iAmmoRockets < 5) ) + pPlayer->m_iAmmoRockets = 5; + + EMIT_SOUND( ENT(pPlayer->pev), CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM ); + + // Switch to a better weapon + if ( WeaponCode(iNewWeapon) <= pPlayer->m_iBackpackSwitch ) + { + if (pPlayer->pev->flags & FL_INWATER) + { + if (iNewWeapon != IT_LIGHTNING) + { + pPlayer->Deathmatch_Weapon(iOldWeapon, iNewWeapon); + } + } + else + { + pPlayer->Deathmatch_Weapon(iOldWeapon, iNewWeapon); + } + } + pPlayer->W_SetCurrentAmmo(); + pPlayer->m_iClientQuakeWeapon = -1; + pPlayer->m_fWeapon = FALSE; + pPlayer->m_fKnownItem = FALSE; + pPlayer->UpdateClientData(); + + UTIL_Remove( this ); + + // We've removed ourself, so don't let CQuakeItem handle respawn + return FALSE; +} + +#if 0 + +/* +=============================================================================== +KEYS +=============================================================================== +*/ + +void() key_touch = +{ +local entity stemp; +local float best; + + if (other.classname != "player") + return; + if (other.health <= 0) + return; + if (other.items & self.items) + return; + + sprint (other, PRINT_LOW, "You got the "); + sprint (other, PRINT_LOW, self.netname); + sprint (other,PRINT_LOW, "\n"); + + sound (other, CHAN_ITEM, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + other.items = other.items | self.items; + + self.solid = SOLID_NOT; + self.model = string_null; + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + +void() key_setsounds = +{ + if (world.worldtype == 0) + { + precache_sound ("misc/medkey.wav"); + self.noise = "misc/medkey.wav"; + } + if (world.worldtype == 1) + { + precache_sound ("misc/runekey.wav"); + self.noise = "misc/runekey.wav"; + } + if (world.worldtype == 2) + { + precache_sound2 ("misc/basekey.wav"); + self.noise = "misc/basekey.wav"; + } +}; + +/*QUAKED item_key1 (0 .5 .8) (-16 -16 -24) (16 16 32) +SILVER key +In order for keys to work +you MUST set your maps +worldtype to one of the +following: +0: medieval +1: metal +2: base +*/ + +void() item_key1 = +{ + if (world.worldtype == 0) + { + precache_model ("progs/w_s_key.mdl"); + setmodel (self, "progs/w_s_key.mdl"); + self.netname = "silver key"; + } + else if (world.worldtype == 1) + { + precache_model ("progs/m_s_key.mdl"); + setmodel (self, "progs/m_s_key.mdl"); + self.netname = "silver runekey"; + } + else if (world.worldtype == 2) + { + precache_model2 ("progs/b_s_key.mdl"); + setmodel (self, "progs/b_s_key.mdl"); + self.netname = "silver keycard"; + } + key_setsounds(); + self.touch = key_touch; + self.items = IT_KEY1; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + +/*QUAKED item_key2 (0 .5 .8) (-16 -16 -24) (16 16 32) +GOLD key +In order for keys to work +you MUST set your maps +worldtype to one of the +following: +0: medieval +1: metal +2: base +*/ + +void() item_key2 = +{ + if (world.worldtype == 0) + { + precache_model ("progs/w_g_key.mdl"); + setmodel (self, "progs/w_g_key.mdl"); + self.netname = "gold key"; + } + if (world.worldtype == 1) + { + precache_model ("progs/m_g_key.mdl"); + setmodel (self, "progs/m_g_key.mdl"); + self.netname = "gold runekey"; + } + if (world.worldtype == 2) + { + precache_model2 ("progs/b_g_key.mdl"); + setmodel (self, "progs/b_g_key.mdl"); + self.netname = "gold keycard"; + } + key_setsounds(); + self.touch = key_touch; + self.items = IT_KEY2; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + + + +/* +=============================================================================== + +END OF LEVEL RUNES + +=============================================================================== +*/ + +void() sigil_touch = +{ +local entity stemp; +local float best; + + if (other.classname != "player") + return; + if (other.health <= 0) + return; + + centerprint (other, "You got the rune!"); + + sound (other, CHAN_ITEM, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + self.solid = SOLID_NOT; + self.model = string_null; + serverflags = serverflags | (self.spawnflags & 15); + self.classname = ""; // so rune doors won't find it + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + +/*QUAKED item_sigil (0 .5 .8) (-16 -16 -24) (16 16 32) E1 E2 E3 E4 +End of level sigil, pick up to end episode and return to jrstart. +*/ + +void() item_sigil = +{ + if (!self.spawnflags) + objerror ("no spawnflags"); + + precache_sound ("misc/runekey.wav"); + self.noise = "misc/runekey.wav"; + + if (self.spawnflags & 1) + { + precache_model ("progs/end1.mdl"); + setmodel (self, "progs/end1.mdl"); + } + if (self.spawnflags & 2) + { + precache_model2 ("progs/end2.mdl"); + setmodel (self, "progs/end2.mdl"); + } + if (self.spawnflags & 4) + { + precache_model2 ("progs/end3.mdl"); + setmodel (self, "progs/end3.mdl"); + } + if (self.spawnflags & 8) + { + precache_model2 ("progs/end4.mdl"); + setmodel (self, "progs/end4.mdl"); + } + + self.touch = sigil_touch; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + +void() q_touch = +{ +local entity stemp; +local float best; +local string s; + + if (other.classname != "player") + return; + if (other.health <= 0) + return; + + self.mdl = self.model; + + sound (other, CHAN_VOICE, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + self.solid = SOLID_NOT; + other.items = other.items | IT_QUAD; + self.model = string_null; + if (deathmatch == 4) + { + other.armortype = 0; + other.armorvalue = 0 * 0.01; + other.ammo_cells = 0; + } + +// do the apropriate action + other.super_time = 1; + other.super_damage_finished = self.cnt; + + s=ftos(rint(other.super_damage_finished - time)); + + bprint (PRINT_LOW, other.netname); + if (deathmatch == 4) + bprint (PRINT_LOW, " recovered an OctaPower with "); + else + bprint (PRINT_LOW, " recovered a Quad with "); + bprint (PRINT_LOW, s); + bprint (PRINT_LOW, " seconds remaining!\n"); + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + +void(float timeleft) DropQuad = +{ + local entity item; + + item = spawn(); + item.origin = self.origin - '0 0 24'; + + item.velocity_z = 300; + item.velocity_x = -100 + (random() * 200); + item.velocity_y = -100 + (random() * 200); + + item.flags = FL_ITEM; + item.solid = SOLID_TRIGGER; + item.movetype = MOVETYPE_TOSS; + item.noise = "items/damage.wav"; + setmodel (item, "progs/quaddama.mdl"); + setsize (item, '-16 -16 -24', '16 16 32'); + item.cnt = time + timeleft; + item.touch = q_touch; + item.nextthink = time + timeleft; // remove it with the time left on it + item.think = SUB_Remove; +}; + + +void() r_touch; + +void() r_touch = +{ +local entity stemp; +local float best; +local string s; + + if (other.classname != "player") + return; + if (other.health <= 0) + return; + + self.mdl = self.model; + + sound (other, CHAN_VOICE, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + self.solid = SOLID_NOT; + other.items = other.items | IT_INVISIBILITY; + self.model = string_null; + +// do the apropriate action + other.invisible_time = 1; + other.invisible_finished = self.cnt; + s=ftos(rint(other.invisible_finished - time)); + bprint (PRINT_LOW, other.netname); + bprint (PRINT_LOW, " recovered a Ring with "); + bprint (PRINT_LOW, s); + bprint (PRINT_LOW, " seconds remaining!\n"); + + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + +void(float timeleft) DropRing = +{ + local entity item; + + item = spawn(); + item.origin = self.origin - '0 0 24'; + + item.velocity_z = 300; + item.velocity_x = -100 + (random() * 200); + item.velocity_y = -100 + (random() * 200); + + item.flags = FL_ITEM; + item.solid = SOLID_TRIGGER; + item.movetype = MOVETYPE_TOSS; + item.noise = "items/inv1.wav"; + setmodel (item, "progs/invisibl.mdl"); + setsize (item, '-16 -16 -24', '16 16 32'); + item.cnt = time + timeleft; + item.touch = r_touch; + item.nextthink = time + timeleft; // remove after 30 seconds + item.think = SUB_Remove; +}; +#endif diff --git a/dmc/dlls/quake_nail.cpp b/dmc/dlls/quake_nail.cpp new file mode 100644 index 0000000..6ef51f4 --- /dev/null +++ b/dmc/dlls/quake_nail.cpp @@ -0,0 +1,122 @@ +//=========== (C) Copyright 1996-2002, Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: Quake nail entity +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "decals.h" +#include "gamerules.h" + +LINK_ENTITY_TO_CLASS( quake_nail, CQuakeNail ); + +//========================================================= +CQuakeNail *CQuakeNail::CreateNail( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner ) +{ + CQuakeNail *pNail = GetClassPtr( (CQuakeNail *)NULL ); + + UTIL_SetOrigin( pNail->pev, vecOrigin ); + + pNail->pev->velocity = vecAngles * 1000; + pNail->pev->angles = UTIL_VecToAngles(vecAngles); + pNail->pev->owner = pOwner->edict(); + pNail->Spawn(); + pNail->pev->classname = MAKE_STRING("spike"); + + // don't send to clients. + pNail->pev->effects |= EF_NODRAW; + + return pNail; +} + +CQuakeNail *CQuakeNail::CreateSuperNail( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner ) +{ + CQuakeNail *pNail = CreateNail( vecOrigin, vecAngles, pOwner ); + pNail->pev->classname = MAKE_STRING("superspike"); + + // Super nails simply do more damage + pNail->pev->dmg = 18; + return pNail; +} + +//========================================================= +void CQuakeNail::Spawn( void ) +{ + Precache(); + + // Setup + pev->movetype = MOVETYPE_FLYMISSILE; + pev->solid = SOLID_BBOX; + + // Safety removal + pev->nextthink = gpGlobals->time + 6; + SetThink( &CQuakeNail::SUB_Remove ); + + // Touch + SetTouch( &CQuakeNail::NailTouch ); + + // Model + SET_MODEL( ENT(pev), "models/spike.mdl" ); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( pev, pev->origin ); + + // Damage + pev->dmg = 9; +} + +//========================================================= +void CQuakeNail::NailTouch( CBaseEntity *pOther ) +{ + if (pOther->pev->solid == SOLID_TRIGGER) + return; + + // Remove if we've hit skybrush + if ( UTIL_PointContents(pev->origin) == CONTENT_SKY ) + { + UTIL_Remove( this ); + return; + } + + // Hit something that bleeds + if (pOther->pev->takedamage) + { + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + + if ( g_pGameRules->PlayerRelationship( pOther, pOwner ) != GR_TEAMMATE ) + SpawnBlood( pev->origin, pOther->BloodColor(), pev->dmg ); + + pOther->TakeDamage( pev, pOwner->pev, pev->dmg, DMG_GENERIC ); + } + else + { + if ( pOther->pev->solid == SOLID_BSP || pOther->pev->movetype == MOVETYPE_PUSHSTEP ) + { + TraceResult tr; + tr.vecEndPos = pev->origin; + tr.pHit = pOther->edict(); + + //Arent we doing this client side? + //UTIL_GunshotDecalTrace( &tr, DECAL_GUNSHOT1 + RANDOM_LONG( 0, 4 ) ); + } + } + + UTIL_Remove( this ); +} + + + diff --git a/dmc/dlls/quake_player.cpp b/dmc/dlls/quake_player.cpp new file mode 100644 index 0000000..1a4d6c6 --- /dev/null +++ b/dmc/dlls/quake_player.cpp @@ -0,0 +1,250 @@ +/*** +* +* Copyright (c) 1996-2002,, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== quake_player.cpp ======================================================== + + Quake Classic player functionality. + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "gamerules.h" +#include "hltv.h" + +extern entvars_t *g_pevLastInflictor; +extern int gmsgStatusText; +extern int gmsgStatusValue; +extern DLL_GLOBAL Vector g_vecAttackDir; + +/************************************* + STATUS BAR +/*************************************/ + +// Initialise the player's status bar +void CBasePlayer::InitStatusBar() +{ + m_flStatusBarDisappearDelay = 0; + m_SbarString1[0] = m_SbarString0[0] = 0; +} + +void CBasePlayer::UpdateStatusBar() +{ + int newSBarState[ SBAR_END ]; + memset( newSBarState, 0, sizeof(newSBarState) ); + + // Find an ID Target + TraceResult tr; + UTIL_MakeVectors( pev->v_angle + pev->punchangle ); + Vector vecSrc = EyePosition(); + Vector vecEnd = vecSrc + (gpGlobals->v_forward * MAX_ID_RANGE); + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, edict(), &tr); + + if (tr.flFraction != 1.0) + { + if ( !FNullEnt( tr.pHit ) ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + + if ( pEntity->Classify() == CLASS_PLAYER ) + { + newSBarState[ SBAR_ID_TARGETNAME ] = ENTINDEX( pEntity->edict() ); + newSBarState[ SBAR_ID_TARGETTEAM ] = FALSE; + + m_flStatusBarDisappearDelay = gpGlobals->time + 1.0; + } + } + else if ( m_flStatusBarDisappearDelay > gpGlobals->time ) + { + // hold the values for a short amount of time after viewing the object + newSBarState[ SBAR_ID_TARGETNAME ] = m_izSBarState[ SBAR_ID_TARGETNAME ]; + newSBarState[ SBAR_ID_TARGETHEALTH ] = m_izSBarState[ SBAR_ID_TARGETHEALTH ]; + newSBarState[ SBAR_ID_TARGETARMOR ] = m_izSBarState[ SBAR_ID_TARGETARMOR ]; + newSBarState[ SBAR_ID_TARGETTEAM ] = m_izSBarState[ SBAR_ID_TARGETTEAM ]; + } + } + + // Check values and send if they don't match + for (int i = 1; i < SBAR_END; i++) + { + if ( newSBarState[i] != m_izSBarState[i] ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgStatusValue, NULL, pev ); + WRITE_BYTE( i ); + WRITE_SHORT( newSBarState[i] ); + MESSAGE_END(); + + m_izSBarState[i] = newSBarState[i]; + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Player has taken some damage. This is now using the Quake functionality. +//----------------------------------------------------------------------------- +int CBasePlayer::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( (pev->takedamage == DAMAGE_NO) || (IsAlive() == FALSE) ) + return 0; + + + //We are wearing the suit and we want to be hurt by lava or slime + if ( m_iQuakeItems & IT_SUIT ) + { + if ( bitsDamageType & DMG_BURN || bitsDamageType & DMG_ACID ) + return 0; + } + + CBaseEntity *pAttacker = CBaseEntity::Instance(pevAttacker); + + // keep track of amount of damage last sustained + m_lastDamageAmount = flDamage; + + // check for quad damage powerup on the attacker + if (pAttacker->IsPlayer()) + { + if ( ((CBasePlayer*)pAttacker)->m_flSuperDamageFinished > gpGlobals->time ) + { + if (gpGlobals->deathmatch == 4) + flDamage *= 8; + else + flDamage *= 4; + } + } + + // team play damage avoidance + if ( g_pGameRules->PlayerRelationship( this, pAttacker ) == GR_TEAMMATE ) + { + // Teamplay 3 you can still hurt yourself + if ( CVAR_GET_FLOAT( "mp_teamplay" ) == 3 && pAttacker != this ) + return 0; + // Teamplay 1 can't hurt any teammates, including yourself + if ( CVAR_GET_FLOAT( "mp_teamplay" ) == 1 ) + return 0; + // Teamplay 2 you can still hurt teammates + } + + + // save damage based on the target's armor level + float flSave = ceil(pev->armortype * flDamage); + if (flSave >= pev->armorvalue) + { + flSave = pev->armorvalue; + pev->armortype = 0; // lost all armor + m_iQuakeItems &= ~(IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3); + } + pev->armorvalue -= flSave; + float flTake = ceil(flDamage - flSave); + + // add to the damage total for clients, which will be sent as a single message at the end of the frame + pev->dmg_take = pev->dmg_take + flTake; + pev->dmg_inflictor = ENT(pevInflictor); + + Vector vecTemp; + + if ( pevAttacker == pevInflictor ) + { + vecTemp = pevAttacker->origin - ( VecBModelOrigin(pev) ); + } + else + // an actual missile was involved. + { + vecTemp = pevInflictor->origin - ( VecBModelOrigin(pev) ); + } + + // this global is still used for glass and other non-monster killables, along with decals. + g_vecAttackDir = vecTemp.Normalize(); + + + // figure momentum add + if ( (pevInflictor) && (pev->movetype == MOVETYPE_WALK) && !( FBitSet (bitsDamageType, DMG_BURN) ) && !( FBitSet (bitsDamageType, DMG_ACID) ) ) + { + + + Vector vecPush = (pev->origin - (pevInflictor->absmin + pevInflictor->absmax) * 0.5).Normalize(); + // Set kickback for smaller weapons + // Read: only if it's not yourself doing the damage + if ( (flDamage < 60) && pAttacker->IsPlayer() && (pAttacker != this) ) + pev->velocity = pev->velocity + vecPush * flDamage * 11; + else + { + // Otherwise, these rules apply to rockets and grenades + // for blast velocity + if ( pAttacker == this ) + { + if ( m_iQuakeWeapon != IT_LIGHTNING ) + pev->velocity = pev->velocity + vecPush * flDamage * 8; + } + else + pev->velocity = pev->velocity + vecPush * flDamage * 8; + } + + // Rocket Jump modifiers + int iRocketJumpModifier = (int)CVAR_GET_FLOAT("rj"); + + if ( (iRocketJumpModifier > 1) && (pAttacker == this) && m_iQuakeWeapon == ( IT_ROCKET_LAUNCHER | IT_GRENADE_LAUNCHER ) ) + pev->velocity = pev->velocity + vecPush * flDamage * iRocketJumpModifier; + } + + // check for godmode or invincibility + if (pev->flags & FL_GODMODE) + return 0; + if (m_flInvincibleFinished > gpGlobals->time) + { + if (m_fInvincSound < gpGlobals->time) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/protect3.wav", 1, ATTN_NORM); + m_fInvincSound = gpGlobals->time + 2; + } + return 0; + } + + + // do the damage + pev->health -= (int)flTake; + + // tell director about it + MESSAGE_BEGIN( MSG_SPEC, SVC_DIRECTOR ); + WRITE_BYTE ( 9 ); // command length in bytes + WRITE_BYTE ( DRC_CMD_EVENT ); // take damage event + WRITE_SHORT( ENTINDEX(this->edict()) ); // index number of primary entity + WRITE_SHORT( ENTINDEX(ENT(pevInflictor)) ); // index number of secondary entity + WRITE_LONG( 5 ); // eventflags (priority and flags) + MESSAGE_END(); + + // react to the damage + m_bitsDamageType |= bitsDamageType; // Save this so we can report it to the client + m_bitsHUDDamage = -1; // make sure the damage bits get resent + + if ( pev->health <= 0 ) + { + g_pevLastInflictor = pevInflictor; + + Killed( pevAttacker, GIB_NORMAL ); + + g_pevLastInflictor = NULL; + return 0; + } + + // play pain sound + Pain( pAttacker ); + + return flTake; +} + diff --git a/dmc/dlls/quake_rocket.cpp b/dmc/dlls/quake_rocket.cpp new file mode 100644 index 0000000..3526981 --- /dev/null +++ b/dmc/dlls/quake_rocket.cpp @@ -0,0 +1,191 @@ +//=========== (C) Copyright 1996-2002, Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: Quake rocket entity +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" + +#define GRENADE_TRAIL 1 +#define ROCKET_TRAIL 2 + +extern unsigned short g_sTrail; +extern unsigned short g_sExplosion; + +LINK_ENTITY_TO_CLASS( quake_rocket, CQuakeRocket ); + +//========================================================= +CQuakeRocket *CQuakeRocket::CreateRocket( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner ) +{ + CQuakeRocket *pRocket = GetClassPtr( (CQuakeRocket *)NULL ); + + UTIL_SetOrigin( pRocket->pev, vecOrigin ); + SET_MODEL(ENT(pRocket->pev), "models/rocket.mdl"); + pRocket->Spawn(); + pRocket->pev->classname = MAKE_STRING("missile"); + pRocket->pev->owner = pOwner->edict(); + + // Setup + pRocket->pev->movetype = MOVETYPE_FLYMISSILE; + pRocket->pev->solid = SOLID_BBOX; + + // Velocity + pRocket->pev->velocity = vecAngles * 1000; + pRocket->pev->angles = UTIL_VecToAngles( vecAngles ); + + // Touch + pRocket->SetTouch( &CQuakeRocket::RocketTouch ); + + // Safety Remove + pRocket->pev->nextthink = gpGlobals->time + 5; + pRocket->SetThink( &CQuakeRocket::SUB_Remove ); + + // Effects +// pRocket->pev->effects |= EF_LIGHT; + + PLAYBACK_EVENT_FULL (FEV_GLOBAL, pRocket->edict(), g_sTrail, 0.0, + (float *)&pRocket->pev->origin, (float *)&pRocket->pev->angles, 0.7, 0.0, pRocket->entindex(), ROCKET_TRAIL, 0, 0); + + return pRocket; +} + +//========================================================= +CQuakeRocket *CQuakeRocket::CreateGrenade( Vector vecOrigin, Vector vecVelocity, CBaseEntity *pOwner ) +{ + CQuakeRocket *pRocket = GetClassPtr( (CQuakeRocket *)NULL ); + + UTIL_SetOrigin( pRocket->pev, vecOrigin ); + SET_MODEL(ENT(pRocket->pev), "models/grenade.mdl"); + pRocket->Spawn(); + pRocket->pev->classname = MAKE_STRING("grenade"); + pRocket->pev->owner = pOwner->edict(); + + // Setup + pRocket->pev->movetype = MOVETYPE_BOUNCE; + pRocket->pev->solid = SOLID_BBOX; + + pRocket->pev->avelocity = Vector(300,300,300); + + // Velocity + pRocket->pev->velocity = vecVelocity; + pRocket->pev->angles = UTIL_VecToAngles(vecVelocity); + pRocket->pev->friction = 0.5; + + // Touch + pRocket->SetTouch( &CQuakeRocket::GrenadeTouch ); + + // set newmis duration + if ( gpGlobals->deathmatch == 4 ) + { + pRocket->m_flAttackFinished = gpGlobals->time + 1.1; // What's this used for? + if (pOwner) + pOwner->TakeDamage( pOwner->pev, pOwner->pev, 10, DMG_GENERIC ); + } + + pRocket->pev->nextthink = gpGlobals->time + 2.5; + pRocket->SetThink( &CQuakeRocket::GrenadeExplode ); + + PLAYBACK_EVENT_FULL (FEV_GLOBAL, pRocket->edict(), g_sTrail, 0.0, + (float *)&g_vecZero, (float *)&g_vecZero, 0.7, 0.0, pRocket->entindex(), GRENADE_TRAIL, 0, 0); + + + return pRocket; +} + +//========================================================= +void CQuakeRocket::Spawn( void ) +{ + Precache(); + + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( pev, pev->origin ); +} + +//========================================================= +void CQuakeRocket::Precache( void ) +{ + m_iTrail = PRECACHE_MODEL("sprites/smoke.spr"); +} + +//========================================================= +void CQuakeRocket::RocketTouch ( CBaseEntity *pOther ) +{ + // Remove if we've hit skybrush + if ( UTIL_PointContents(pev->origin) == CONTENT_SKY ) + { + UTIL_Remove( this ); + return; + } + + // Do touch damage + float flDmg = RANDOM_FLOAT( 100, 120 ); + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + if (pOther->pev->health) + pOther->TakeDamage( pev, pOwner->pev, flDmg, DMG_BULLET ); + + // Don't do radius damage to the other, because all the damage was done in the impact + Q_RadiusDamage(this, pOwner, 120, pOther); + + // Finish and remove + Explode(); +} + +//========================================================= +void CQuakeRocket::GrenadeTouch( CBaseEntity *pOther ) +{ + if (pOther->pev->takedamage == DAMAGE_AIM) + { + GrenadeExplode(); + return; + } + + if (pev->flags & FL_ONGROUND) + { + // add a bit of static friction + pev->velocity = pev->velocity * 0.75; + + if (pev->velocity.Length() <= 20) + { + pev->avelocity = g_vecZero; + } + } + + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); + + if (pev->velocity == g_vecZero) + pev->avelocity = g_vecZero; +} + +//========================================================= +void CQuakeRocket::GrenadeExplode() +{ + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + Q_RadiusDamage(this, pOwner, 120, NULL); + + // Finish and remove + Explode(); +} + +//========================================================= +void CQuakeRocket::Explode() +{ + //We use the angles field to send the rocket velocity. + PLAYBACK_EVENT_FULL( FEV_GLOBAL, edict(), g_sExplosion, 0.0, (float *)&pev->origin, (float *)&pev->velocity, 0.0, 0.0, 0, 0, 0, 0 ); + + UTIL_Remove( this ); +} diff --git a/dmc/dlls/quake_weapons_all.cpp b/dmc/dlls/quake_weapons_all.cpp new file mode 100644 index 0000000..d639ecd --- /dev/null +++ b/dmc/dlls/quake_weapons_all.cpp @@ -0,0 +1,1087 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== quake_weapons.cpp ======================================================== + + Quake weaponry + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "quake_gun.h" + +char gszQ_DeathType[128]; +DLL_GLOBAL short g_sModelIndexNail; + +#ifdef THREEWAVE +extern unsigned short g_usHook; +extern unsigned short g_usCable; +extern unsigned short g_usCarried; +#endif + +#ifdef CLIENT_DLL +#include "cl_entity.h" +struct cl_entity_s *GetViewEntity( void ); +extern float g_flLightTime; +#endif + +// called by worldspawn +void QuakeClassicPrecache( void ) +{ + // Weapon sounds + PRECACHE_SOUND("weapons/ax1.wav"); + PRECACHE_SOUND("player/axhit2.wav"); + PRECACHE_SOUND("player/axhitbod.wav"); + PRECACHE_SOUND("weapons/r_exp3.wav"); // new rocket explosion + PRECACHE_SOUND("weapons/rocket1i.wav");// spike gun + PRECACHE_SOUND("weapons/sgun1.wav"); + PRECACHE_SOUND("weapons/lhit.wav"); + PRECACHE_SOUND("weapons/guncock.wav"); // player shotgun + PRECACHE_SOUND("weapons/ric1.wav"); // ricochet (used in c code) + PRECACHE_SOUND("weapons/ric2.wav"); // ricochet (used in c code) + PRECACHE_SOUND("weapons/ric3.wav"); // ricochet (used in c code) + PRECACHE_SOUND("weapons/spike2.wav"); // super spikes + PRECACHE_SOUND("weapons/tink1.wav"); // spikes tink (used in c code) + PRECACHE_SOUND("weapons/grenade.wav"); // grenade launcher + PRECACHE_SOUND("weapons/bounce.wav"); // grenade bounce + PRECACHE_SOUND("weapons/shotgn2.wav"); // super shotgun + PRECACHE_SOUND("weapons/lstart.wav"); // lightning start + + // Weapon models + PRECACHE_MODEL("models/v_crowbar.mdl"); + PRECACHE_MODEL("models/v_shot.mdl"); + PRECACHE_MODEL("models/v_shot2.mdl"); + PRECACHE_MODEL("models/v_nail.mdl"); + PRECACHE_MODEL("models/v_nail2.mdl"); + PRECACHE_MODEL("models/v_rock.mdl"); + PRECACHE_MODEL("models/v_rock2.mdl"); + PRECACHE_MODEL("models/v_light.mdl"); + + // Weapon player models + PRECACHE_MODEL("models/p_crowbar.mdl"); + PRECACHE_MODEL("models/p_rock2.mdl"); + PRECACHE_MODEL("models/p_rock.mdl"); + PRECACHE_MODEL("models/p_shot2.mdl"); + PRECACHE_MODEL("models/p_nail.mdl"); + PRECACHE_MODEL("models/p_nail2.mdl"); + PRECACHE_MODEL("models/p_light.mdl"); + PRECACHE_MODEL("models/p_shot.mdl"); + + + // Weapon effect models + PRECACHE_MODEL("models/rocket.mdl"); // rocket + PRECACHE_MODEL("models/grenade.mdl"); // grenade + g_sModelIndexNail = PRECACHE_MODEL("models/spike.mdl"); + g_sModelIndexLaser = PRECACHE_MODEL("sprites/laserbeam.spr"); + PRECACHE_MODEL("sprites/smoke.spr"); + + // Powerup models + + // Powerup sounds + PRECACHE_SOUND("items/damage3.wav"); + PRECACHE_SOUND("items/sight1.wav"); + + // Teleport sounds + PRECACHE_SOUND("misc/r_tele1.wav"); + PRECACHE_SOUND("misc/r_tele2.wav"); + PRECACHE_SOUND("misc/r_tele3.wav"); + PRECACHE_SOUND("misc/r_tele4.wav"); + PRECACHE_SOUND("misc/r_tele5.wav"); + + // Misc + PRECACHE_SOUND("weapons/lock4.wav"); + PRECACHE_SOUND("weapons/pkup.wav"); + PRECACHE_SOUND("items/itembk2.wav"); + PRECACHE_MODEL("models/backpack.mdl"); + +#ifdef THREEWAVE + PRECACHE_MODEL("models/v_grapple.mdl"); +#endif + +} + +//================================================================================================ +// WEAPON SELECTION +//================================================================================================ +// Return the ID of the best weapon being carried by the player +int CBasePlayer::W_BestWeapon() +{ + if (pev->waterlevel <= 1 && m_iAmmoCells >= 1 && (m_iQuakeItems & IT_LIGHTNING) ) + return IT_LIGHTNING; + else if(m_iAmmoNails >= 2 && (m_iQuakeItems & IT_SUPER_NAILGUN) ) + return IT_SUPER_NAILGUN; + else if(m_iAmmoShells >= 2 && (m_iQuakeItems & IT_SUPER_SHOTGUN) ) + return IT_SUPER_SHOTGUN; + else if(m_iAmmoNails >= 1 && (m_iQuakeItems & IT_NAILGUN) ) + return IT_NAILGUN; + else if(m_iAmmoShells >= 1 && (m_iQuakeItems & IT_SHOTGUN) ) + return IT_SHOTGUN; + + return IT_AXE; +} + +// Weapon setup after weapon switch +void CBasePlayer::W_SetCurrentAmmo( int sendanim /* = 1 */ ) +{ + m_iQuakeItems &= ~(IT_SHELLS | IT_NAILS | IT_ROCKETS | IT_CELLS); + int iszViewModel = 0; + char *viewmodel = ""; + int iszWeaponModel = 0; + char *szAnimExt; + + // Find out what weapon the player's using + if (m_iQuakeWeapon == IT_AXE) + { + m_pCurrentAmmo = NULL; + viewmodel = "models/v_crowbar.mdl"; + iszViewModel = MAKE_STRING(viewmodel); + szAnimExt = "crowbar"; + iszWeaponModel = MAKE_STRING("models/p_crowbar.mdl"); + } + else if (m_iQuakeWeapon == IT_SHOTGUN) + { + m_pCurrentAmmo = &m_iAmmoShells; + viewmodel = "models/v_shot.mdl"; + iszViewModel = MAKE_STRING(viewmodel); + iszWeaponModel = MAKE_STRING("models/p_shot.mdl"); + m_iQuakeItems |= IT_SHELLS; + szAnimExt = "shotgun"; + } + else if (m_iQuakeWeapon == IT_SUPER_SHOTGUN) + { + m_pCurrentAmmo = &m_iAmmoShells; + viewmodel = "models/v_shot2.mdl"; + iszViewModel = MAKE_STRING(viewmodel); + iszWeaponModel = MAKE_STRING("models/p_shot2.mdl"); + m_iQuakeItems |= IT_SHELLS; + szAnimExt = "shotgun"; + } + else if (m_iQuakeWeapon == IT_NAILGUN) + { + m_pCurrentAmmo = &m_iAmmoNails; + viewmodel = "models/v_nail.mdl"; + iszViewModel = MAKE_STRING(viewmodel); + iszWeaponModel = MAKE_STRING("models/p_nail.mdl"); + m_iQuakeItems |= IT_NAILS; + szAnimExt = "mp5"; + } + else if (m_iQuakeWeapon == IT_SUPER_NAILGUN) + { + m_pCurrentAmmo = &m_iAmmoNails; + viewmodel = "models/v_nail2.mdl"; + iszViewModel = MAKE_STRING(viewmodel); + iszWeaponModel = MAKE_STRING("models/p_nail2.mdl"); + m_iQuakeItems |= IT_NAILS; + szAnimExt = "mp5"; + } + else if (m_iQuakeWeapon == IT_GRENADE_LAUNCHER) + { + m_pCurrentAmmo = &m_iAmmoRockets; + viewmodel = "models/v_rock.mdl"; + iszViewModel = MAKE_STRING(viewmodel); + m_iQuakeItems |= IT_ROCKETS; + iszWeaponModel = MAKE_STRING("models/p_rock.mdl"); + szAnimExt = "gauss"; + } + else if (m_iQuakeWeapon == IT_ROCKET_LAUNCHER) + { + m_pCurrentAmmo = &m_iAmmoRockets; + viewmodel = "models/v_rock2.mdl"; + iszViewModel = MAKE_STRING(viewmodel); + m_iQuakeItems |= IT_ROCKETS; + iszWeaponModel = MAKE_STRING("models/p_rock2.mdl"); + szAnimExt = "gauss"; + } + else if (m_iQuakeWeapon == IT_LIGHTNING) + { + m_pCurrentAmmo = &m_iAmmoCells; + viewmodel = "models/v_light.mdl"; + iszViewModel = MAKE_STRING(viewmodel); + iszWeaponModel = MAKE_STRING("models/p_light.mdl"); + m_iQuakeItems |= IT_CELLS; + szAnimExt = "gauss"; + } +#ifdef THREEWAVE + else if (m_iQuakeWeapon == IT_EXTRA_WEAPON) + { + m_pCurrentAmmo = NULL; + viewmodel = "models/v_grapple.mdl"; + iszViewModel = MAKE_STRING(viewmodel); + szAnimExt = "crowbar"; + } +#endif + + else + { + m_pCurrentAmmo = NULL; + } + +#if !defined( CLIENT_DLL ) + + pev->viewmodel = iszViewModel; + + pev->weaponmodel = iszWeaponModel; + strcpy( m_szAnimExtention, szAnimExt ); + +#else + { + + int HUD_GetModelIndex( char *modelname ); + pev->viewmodel = HUD_GetModelIndex( viewmodel ); + + cl_entity_t *view; + view = GetViewEntity(); + + //Adrian - The actual "magic" is done in the + //Studio drawing code. + if ( m_iQuakeItems & IT_INVISIBILITY ) + { + if( view ) + { + view->curstate.renderfx = kRenderFxGlowShell; + view->curstate.renderamt = 5; + + view->curstate.rendercolor.r = 125; + view->curstate.rendercolor.g = 125; + view->curstate.rendercolor.b = 125; + } + } + else + { + if ( m_iQuakeItems & IT_INVULNERABILITY ) + { + if( view ) + { + view->curstate.renderfx = kRenderFxGlowShell; + view->curstate.renderamt = 15; + + view->curstate.rendercolor.r = 255; + view->curstate.rendercolor.g = 125; + view->curstate.rendercolor.b = 125; + } + } + else if ( m_iQuakeItems & IT_QUAD ) + { + if( view ) + { + view->curstate.renderfx = kRenderFxGlowShell; + view->curstate.renderamt = 15; + + view->curstate.rendercolor.r = 125; + view->curstate.rendercolor.g = 125; + view->curstate.rendercolor.b = 255; + } + } + else if ( m_iQuakeItems & ( IT_INVULNERABILITY | IT_QUAD ) ) + { + if( view ) + { + view->curstate.renderfx = kRenderFxGlowShell; + view->curstate.renderamt = 15; + + view->curstate.rendercolor.r = 255; + view->curstate.rendercolor.g = 125; + view->curstate.rendercolor.b = 255; + } + } + else + view->curstate.renderfx = kRenderFxNone; // Clear it. + } + } +#endif +} + +// Return TRUE if the weapon still has ammo +BOOL CBasePlayer::W_CheckNoAmmo() +{ + if ( m_pCurrentAmmo && *m_pCurrentAmmo > 0 ) + return TRUE; + + if ( m_iQuakeWeapon == IT_AXE ) + return TRUE; + +#ifdef THREEWAVE + if ( m_iQuakeWeapon == IT_EXTRA_WEAPON ) + return TRUE; +#endif + + if ( m_iQuakeWeapon == IT_LIGHTNING ) + { + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usLightning, 0, (float *)&pev->origin, (float *)&pev->angles, 0.0, 0.0, 0, 1, 0, 0 ); + + if ( m_pActiveItem ) + ((CQuakeGun*)m_pActiveItem)->DestroyEffect(); + } + + m_iQuakeWeapon = W_BestWeapon(); + W_SetCurrentAmmo(); + return FALSE; +} + +// Change to the specified weapon +void CBasePlayer::W_ChangeWeapon( int iWeaponNumber ) +{ + if ( m_iQuakeWeapon == IT_LIGHTNING ) + { + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usLightning, 0, (float *)&pev->origin, (float *)&pev->angles, 0.0, 0.0, 0, 1, 0, 0 ); + + if ( m_pActiveItem ) + ((CQuakeGun*)m_pActiveItem)->DestroyEffect(); + } + + int iWeapon = 0; + BOOL bHaveAmmo = TRUE; + + if (iWeaponNumber == 1) + { + iWeapon = IT_AXE; + } + else if (iWeaponNumber == 2) + { + iWeapon = IT_SHOTGUN; + if (m_iAmmoShells < 1) + bHaveAmmo = FALSE; + } + else if (iWeaponNumber == 3) + { + iWeapon = IT_SUPER_SHOTGUN; + if (m_iAmmoShells < 2) + bHaveAmmo = FALSE; + } + else if (iWeaponNumber == 4) + { + iWeapon = IT_NAILGUN; + if (m_iAmmoNails < 1) + bHaveAmmo = FALSE; + } + else if (iWeaponNumber == 5) + { + iWeapon = IT_SUPER_NAILGUN; + if (m_iAmmoNails < 2) + bHaveAmmo = FALSE; + } + else if (iWeaponNumber == 6) + { + iWeapon = IT_GRENADE_LAUNCHER; + if (m_iAmmoRockets < 1) + bHaveAmmo = FALSE; + } + else if (iWeaponNumber == 7) + { + iWeapon = IT_ROCKET_LAUNCHER; + if (m_iAmmoRockets < 1) + bHaveAmmo = FALSE; + } + else if (iWeaponNumber == 8) + { + iWeapon = IT_LIGHTNING; + + if (m_iAmmoCells < 1) + bHaveAmmo = FALSE; + } +#ifdef THREEWAVE + else if (iWeaponNumber == 9) + { + iWeapon = IT_EXTRA_WEAPON; + } +#endif + + // Have the weapon? + if ( !(m_iQuakeItems & iWeapon) ) + { + ClientPrint( pev, HUD_PRINTCONSOLE, "#No_Weapon" ); + return; + } + + // Have ammo for it? + if ( !bHaveAmmo ) + { + ClientPrint( pev, HUD_PRINTCONSOLE, "#No_Ammo" ); + return; + } + + // Set weapon, update ammo + m_iQuakeWeapon = iWeapon; + W_SetCurrentAmmo(); + +#ifdef CLIENT_DLL + g_flLightTime = 0.0; +#endif +} + +// Go to the next weapon with ammo +void CBasePlayer::W_CycleWeaponCommand( void ) +{ + while (1) + { + BOOL bHaveAmmo = TRUE; + + if (m_iQuakeWeapon == IT_LIGHTNING) + { + m_iQuakeWeapon = IT_EXTRA_WEAPON; + } + else if (m_iQuakeWeapon == IT_EXTRA_WEAPON) + { + m_iQuakeWeapon = IT_AXE; + } + else if (m_iQuakeWeapon == IT_AXE) + { + m_iQuakeWeapon = IT_SHOTGUN; + if (m_iAmmoShells < 1) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_SHOTGUN) + { + m_iQuakeWeapon = IT_SUPER_SHOTGUN; + if (m_iAmmoShells < 2) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_SUPER_SHOTGUN) + { + m_iQuakeWeapon = IT_NAILGUN; + if (m_iAmmoNails < 1) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_NAILGUN) + { + m_iQuakeWeapon = IT_SUPER_NAILGUN; + if (m_iAmmoNails < 2) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_SUPER_NAILGUN) + { + m_iQuakeWeapon = IT_GRENADE_LAUNCHER; + if (m_iAmmoRockets < 1) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_GRENADE_LAUNCHER) + { + m_iQuakeWeapon = IT_ROCKET_LAUNCHER; + if (m_iAmmoRockets < 1) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_ROCKET_LAUNCHER) + { + m_iQuakeWeapon = IT_LIGHTNING; + if (m_iAmmoCells < 1) + bHaveAmmo = FALSE; + } + + if ( (m_iQuakeItems & m_iQuakeWeapon) && bHaveAmmo ) + { + W_SetCurrentAmmo(); + return; + } + } + +} + +// Go to the prev weapon with ammo +void CBasePlayer::W_CycleWeaponReverseCommand() +{ + while (1) + { + BOOL bHaveAmmo = TRUE; + + if (m_iQuakeWeapon == IT_EXTRA_WEAPON) + { + m_iQuakeWeapon = IT_LIGHTNING; + } + else if (m_iQuakeWeapon == IT_LIGHTNING) + { + m_iQuakeWeapon = IT_ROCKET_LAUNCHER; + if (m_iAmmoRockets < 1) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_ROCKET_LAUNCHER) + { + m_iQuakeWeapon = IT_GRENADE_LAUNCHER; + if (m_iAmmoRockets < 1) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_GRENADE_LAUNCHER) + { + m_iQuakeWeapon = IT_SUPER_NAILGUN; + if (m_iAmmoNails < 2) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_SUPER_NAILGUN) + { + m_iQuakeWeapon = IT_NAILGUN; + if (m_iAmmoNails < 1) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_NAILGUN) + { + m_iQuakeWeapon = IT_SUPER_SHOTGUN; + if (m_iAmmoShells < 2) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_SUPER_SHOTGUN) + { + m_iQuakeWeapon = IT_SHOTGUN; + if (m_iAmmoShells < 1) + bHaveAmmo = FALSE; + } + else if (m_iQuakeWeapon == IT_SHOTGUN) + { + m_iQuakeWeapon = IT_AXE; + } + else if (m_iQuakeWeapon == IT_AXE) + { + m_iQuakeWeapon = IT_EXTRA_WEAPON; + } + else if (m_iQuakeWeapon == IT_EXTRA_WEAPON) + { + m_iQuakeWeapon = IT_LIGHTNING; + if (m_iAmmoCells < 1) + bHaveAmmo = FALSE; + } + + + if ( (m_iQuakeItems & m_iQuakeWeapon) && bHaveAmmo ) + { + W_SetCurrentAmmo(); + return; + } + } + +} + +//================================================================================================ +// WEAPON FUNCTIONS +//================================================================================================ +// Returns true if the inflictor can directly damage the target. Used for explosions and melee attacks. +float Q_CanDamage(CBaseEntity *pTarget, CBaseEntity *pInflictor) +{ + TraceResult trace; + + // bmodels need special checking because their origin is 0,0,0 + if (pTarget->pev->movetype == MOVETYPE_PUSH) + { + UTIL_TraceLine( pInflictor->pev->origin, 0.5 * (pTarget->pev->absmin + pTarget->pev->absmax), ignore_monsters, NULL, &trace ); + if (trace.flFraction == 1) + return TRUE; + CBaseEntity *pEntity = CBaseEntity::Instance(trace.pHit); + if (pEntity == pTarget) + return TRUE; + return FALSE; + } + + UTIL_TraceLine( pInflictor->pev->origin, pTarget->pev->origin, ignore_monsters, NULL, &trace ); + if (trace.flFraction == 1) + return TRUE; + UTIL_TraceLine( pInflictor->pev->origin, pTarget->pev->origin + Vector(15,15,0), ignore_monsters, NULL, &trace ); + if (trace.flFraction == 1) + return TRUE; + UTIL_TraceLine( pInflictor->pev->origin, pTarget->pev->origin + Vector(-15,-15,0), ignore_monsters, NULL, &trace ); + if (trace.flFraction == 1) + return TRUE; + UTIL_TraceLine( pInflictor->pev->origin, pTarget->pev->origin + Vector(-15,15,0), ignore_monsters, NULL, &trace ); + if (trace.flFraction == 1) + return TRUE; + UTIL_TraceLine( pInflictor->pev->origin, pTarget->pev->origin + Vector(15,-15,0), ignore_monsters, NULL, &trace ); + if (trace.flFraction == 1) + return TRUE; + + return FALSE; +} + +// Quake Bullet firing +void CBasePlayer::Q_FireBullets(int iShots, Vector vecDir, Vector vecSpread) +{ + TraceResult trace; + UTIL_MakeVectors(pev->v_angle); + + Vector vecSrc = pev->origin + (gpGlobals->v_forward * 10); + vecSrc.z = pev->absmin.z + (pev->size.z * 0.7); + ClearMultiDamage(); + + while ( iShots > 0 ) + { + Vector vecPath = vecDir + ( RANDOM_FLOAT( -1, 1 ) * vecSpread.x * gpGlobals->v_right ) + ( RANDOM_FLOAT( -1, 1 ) * vecSpread.y * gpGlobals->v_up ); + Vector vecEnd = vecSrc + ( vecPath * 2048 ); + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, ENT(pev), &trace ); + if (trace.flFraction != 1.0) + { + CBaseEntity *pEntity = CBaseEntity::Instance(trace.pHit); + if (pEntity && pEntity->pev->takedamage && pEntity->IsPlayer() ) + { + pEntity->TraceAttack(pev, 4, vecPath, &trace, DMG_BULLET); + //AddMultiDamage(pev, pEntity, 4, DMG_BULLET); + } + else if ( pEntity && pEntity->pev->takedamage ) + { + pEntity->TakeDamage( pev, pev, 4, DMG_BULLET ); + } + } + + iShots--; + } + + ApplyMultiDamage( pev, pev ); +} + +#if !defined( CLIENT_DLL ) +// Quake Radius damage +void Q_RadiusDamage( CBaseEntity *pInflictor, CBaseEntity *pAttacker, float flDamage, CBaseEntity *pIgnore ) +{ + CBaseEntity *pEnt = NULL; + + while ( (pEnt = UTIL_FindEntityInSphere( pEnt, pInflictor->pev->origin, flDamage+40 )) != NULL ) + { + if (pEnt != pIgnore) + { + if (pEnt->pev->takedamage) + { + Vector vecOrg = pEnt->pev->origin + ((pEnt->pev->mins + pEnt->pev->maxs) * 0.5); + float flPoints = 0.5 * (pInflictor->pev->origin - vecOrg).Length(); + if (flPoints < 0) + flPoints = 0; + flPoints = flDamage - flPoints; + + if (pEnt == pAttacker) + flPoints = flPoints * 0.5; + if (flPoints > 0) + { + if ( Q_CanDamage( pEnt, pInflictor ) ) + pEnt->TakeDamage( pInflictor->pev, pAttacker->pev, flPoints, DMG_GENERIC ); + } + } + } + } +} +#endif + +// Lightning hit a target +void LightningHit(CBaseEntity *pTarget, CBaseEntity *pAttacker, Vector vecHitPos, float flDamage, TraceResult *ptr, Vector vecDir ) +{ + +#ifndef CLIENT_DLL + SpawnBlood( vecHitPos, BLOOD_COLOR_RED, flDamage * 1.5 ); + + if ( g_pGameRules->PlayerRelationship( pTarget, pAttacker ) != GR_TEAMMATE ) + { + + pTarget->TakeDamage( pAttacker->pev, pAttacker->pev, flDamage, DMG_GENERIC ); + pTarget->TraceBleed( flDamage, vecDir, ptr, DMG_BULLET ); // have to use DMG_BULLET or it wont spawn. + + } +#endif +} + +// Lightning Damage +void CBasePlayer::LightningDamage( Vector p1, Vector p2, CBaseEntity *pAttacker, float flDamage, Vector vecDir) +{ +#if !defined( CLIENT_DLL ) + TraceResult trace; + Vector vecThru = (p2 - p1).Normalize(); + vecThru.x = 0 - vecThru.y; + vecThru.y = vecThru.x; + vecThru.z = 0; + vecThru = vecThru * 16; + + CBaseEntity *pEntity1 = NULL; + CBaseEntity *pEntity2 = NULL; + + // Hit first target? + UTIL_TraceLine( p1, p2, dont_ignore_monsters, ENT(pev), &trace ); + CBaseEntity *pEntity = CBaseEntity::Instance(trace.pHit); + if (pEntity && pEntity->pev->takedamage) + { + LightningHit(pEntity, pAttacker, trace.vecEndPos, flDamage, &trace, vecDir ); + } + pEntity1 = pEntity; + + // Hit second target? + UTIL_TraceLine( p1 + vecThru, p2 + vecThru, dont_ignore_monsters, ENT(pev), &trace ); + pEntity = CBaseEntity::Instance(trace.pHit); + if (pEntity && pEntity != pEntity1 && pEntity->pev->takedamage) + { + LightningHit(pEntity, pAttacker, trace.vecEndPos, flDamage, &trace, vecDir ); + } + pEntity2 = pEntity; + + // Hit third target? + UTIL_TraceLine( p1 - vecThru, p2 - vecThru, dont_ignore_monsters, ENT(pev), &trace ); + pEntity = CBaseEntity::Instance(trace.pHit); + if (pEntity && pEntity != pEntity1 && pEntity != pEntity2 && pEntity->pev->takedamage) + { + LightningHit(pEntity, pAttacker, trace.vecEndPos, flDamage, &trace, vecDir ); + } +#endif +} + +//================================================================================================ +// WEAPON FIRING +//================================================================================================ +// Axe +void CBasePlayer::W_FireAxe() +{ + TraceResult trace; + Vector vecSrc = pev->origin + Vector(0, 0, 16); + + // Swing forward 64 units + UTIL_MakeVectors(pev->v_angle); + UTIL_TraceLine( vecSrc, vecSrc + (gpGlobals->v_forward * 64), dont_ignore_monsters, ENT(pev), &trace ); + if (trace.flFraction == 1.0) + return; + + Vector vecOrg = trace.vecEndPos - gpGlobals->v_forward * 4; + + CBaseEntity *pEntity = CBaseEntity::Instance(trace.pHit); + if (pEntity && pEntity->pev->takedamage) + { + pEntity->m_bAxHitMe = TRUE; + int iDmg = 20; + if (gpGlobals->deathmatch > 3) + iDmg = 75; + + pEntity->TakeDamage( pev, pev, iDmg, DMG_GENERIC ); + +#ifndef CLIENT_DLL + if ( g_pGameRules->PlayerRelationship( this, pEntity ) != GR_TEAMMATE ) + SpawnBlood( vecOrg, BLOOD_COLOR_RED, iDmg * 4 ); // Make a lot of Blood! +#endif + } +} + +// Single barrel shotgun +void CBasePlayer::W_FireShotgun( int iQuadSound ) +{ + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usShotgunSingle, 0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, iQuadSound, 0, 0, 0 ); + + if (gpGlobals->deathmatch != 4 ) + *m_pCurrentAmmo -= 1; + + Vector vecDir = GetAutoaimVector( AUTOAIM_5DEGREES ); + Q_FireBullets(6, vecDir, Vector(0.04, 0.04, 0) ); +} + + +#ifdef THREEWAVE +void CBasePlayer::W_FireHook( void ) +{ + PLAYBACK_EVENT_FULL( FEV_NOTHOST | FEV_GLOBAL, edict(), g_usHook, 0, (float *)&pev->origin, (float *)&pev->angles, 0.0, 0.0, 0, 0, 0, 0 ); + + Throw_Grapple(); +} +#endif + +#ifdef THREEWAVE + +#ifdef CLIENT_DLL +unsigned short g_usCable; +unsigned short g_usHook; +unsigned short g_usCarried; + +void CBasePlayer::Throw_Grapple( void ) +{ +} +#endif +#endif + +// Double barrel shotgun +void CBasePlayer::W_FireSuperShotgun( int iQuadSound ) +{ + if (*m_pCurrentAmmo == 1) + { + W_FireShotgun( iQuadSound ); + return; + } + + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usShotgunDouble, 0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, iQuadSound, 0, 0, 0 ); + + if (gpGlobals->deathmatch != 4 ) + *m_pCurrentAmmo -= 2; + Vector vecDir = GetAutoaimVector( AUTOAIM_5DEGREES ); + Q_FireBullets(14, vecDir, Vector(0.14, 0.08, 0) ); +}; + +// Rocket launcher +void CBasePlayer::W_FireRocket( int iQuadSound ) +{ + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usRocket, 0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, iQuadSound, 0, 0, 0 ); + + if (gpGlobals->deathmatch != 4 ) + *m_pCurrentAmmo -= 1; + + // Create the rocket + UTIL_MakeVectors( pev->v_angle ); + Vector vecOrg = pev->origin + (gpGlobals->v_forward * 8) + Vector(0,0,16); + Vector vecDir = GetAutoaimVector( AUTOAIM_5DEGREES ); + CQuakeRocket *pRocket = CQuakeRocket::CreateRocket( vecOrg, vecDir, this ); +} + +// Grenade launcher +void CBasePlayer::W_FireGrenade( int iQuadSound ) +{ + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usGrenade, 0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, iQuadSound, 0, 0, 0 ); + + if (gpGlobals->deathmatch != 4 ) + *m_pCurrentAmmo -= 1; + + // Get initial velocity + UTIL_MakeVectors( pev->v_angle ); + Vector vecVelocity; + if ( pev->v_angle.x ) + { + vecVelocity = gpGlobals->v_forward * 600 + gpGlobals->v_up * 200 + RANDOM_FLOAT(-1,1) * gpGlobals->v_right * 10 + RANDOM_FLOAT(-1,1) * gpGlobals->v_up * 10; + } + else + { + vecVelocity = GetAutoaimVector( AUTOAIM_5DEGREES ); + vecVelocity = vecVelocity * 600; + vecVelocity.z = 200; + } + + // Create the grenade + CQuakeRocket *pRocket = CQuakeRocket::CreateGrenade( pev->origin, vecVelocity, this ); +} + +// Lightning Gun +void CBasePlayer::W_FireLightning( int iQuadSound ) +{ + if (*m_pCurrentAmmo < 1) + { + //This should already be IT_LIGHTNING but what the heck. + if ( m_iQuakeWeapon == IT_LIGHTNING ) + { + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usLightning, 0, (float *)&pev->origin, (float *)&pev->angles, 0.0, 0.0, 0, 1, 0, 0 ); + + if ( m_pActiveItem ) + ((CQuakeGun*)m_pActiveItem)->DestroyEffect(); + } + + m_iQuakeWeapon = W_BestWeapon (); + W_SetCurrentAmmo(); + return; + } + + bool playsound = false; + + // Make lightning sound every 0.6 seconds + if ( m_flLightningTime <= gpGlobals->time ) + { + playsound = true; + m_flLightningTime = gpGlobals->time + 0.6; + } + + // explode if under water + if (pev->waterlevel > 1) + { + if ( (gpGlobals->deathmatch > 3) && (RANDOM_FLOAT(0, 1) <= 0.5) ) + { + strcpy( gszQ_DeathType, "selfwater" ); + TakeDamage( pev, pev, 4000, DMG_GENERIC ); + } + else + { + float flCellsBurnt = *m_pCurrentAmmo; + *m_pCurrentAmmo = 0; + W_SetCurrentAmmo(); +#if !defined( CLIENT_DLL ) + Q_RadiusDamage( this, this, 35 * flCellsBurnt, NULL ); +#endif + return; + } + } + + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usLightning, 0, (float *)&pev->origin, (float *)&pev->angles, 0.0, 0.0, iQuadSound, 0, playsound, 0 ); + +#if !defined( CLIENT_DLL ) + + if (gpGlobals->deathmatch != 4 ) + *m_pCurrentAmmo -= 1; + + // Lightning bolt effect + TraceResult trace; + Vector vecOrg = pev->origin + Vector(0,0,16); + UTIL_MakeVectors( pev->v_angle ); + UTIL_TraceLine( vecOrg, vecOrg + (gpGlobals->v_forward * 600), ignore_monsters, ENT(pev), &trace ); + + Vector vecDir = gpGlobals->v_forward + ( 0.001 * gpGlobals->v_right ) + ( 0.001 * gpGlobals->v_up ); + // Do damage + LightningDamage(pev->origin, trace.vecEndPos + (gpGlobals->v_forward * 4), this, 30, vecDir ); + +#endif +} + +// Super Nailgun +void CBasePlayer::W_FireSuperSpikes( int iQuadSound ) +{ + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usSuperSpike, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, iQuadSound, 0, m_iNailOffset > 0.0 ? 1 : 0, 0 ); + + if (gpGlobals->deathmatch != 4 ) + *m_pCurrentAmmo -= 2; + m_flNextAttack = UTIL_WeaponTimeBase() + 0.1; + + // Fire the Nail + Vector vecDir = GetAutoaimVector( AUTOAIM_5DEGREES ); + CQuakeNail *pNail = CQuakeNail::CreateSuperNail( pev->origin + Vector(0,0,16), vecDir, this ); +} + +// Nailgun +void CBasePlayer::W_FireSpikes( int iQuadSound ) +{ + // If we're wielding the Super nailgun and we've got ammo for it, fire Super nails + if (*m_pCurrentAmmo >= 2 && m_iQuakeWeapon == IT_SUPER_NAILGUN) + { + W_FireSuperSpikes( iQuadSound ); + return; + } + + // Swap to next best weapon if this one just ran out + if (*m_pCurrentAmmo < 1) + { + m_iQuakeWeapon = W_BestWeapon (); + W_SetCurrentAmmo(); + return; + } + + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usSpike, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, iQuadSound, 0, m_iNailOffset > 0.0 ? 1 : 0, 0 ); + + // Fire left then right + if (m_iNailOffset == 2) + m_iNailOffset = -2; + else + m_iNailOffset = 2; + + if (gpGlobals->deathmatch != 4 ) + *m_pCurrentAmmo -= 1; + m_flNextAttack = UTIL_WeaponTimeBase() + 0.1; + + // Fire the nail + UTIL_MakeVectors( pev->v_angle ); + Vector vecDir = GetAutoaimVector( AUTOAIM_5DEGREES ); + CQuakeNail *pNail = CQuakeNail::CreateNail( pev->origin + Vector(0,0,10) + (gpGlobals->v_right * m_iNailOffset), vecDir, this ); +} + +//=============================================================================== +// PLAYER WEAPON USE +//=============================================================================== +void CBasePlayer::W_Attack( int iQuadSound ) +{ + // Out of ammo? + if ( !W_CheckNoAmmo() ) + return; + + if ( m_iQuakeWeapon != IT_LIGHTNING ) + ((CBasePlayerWeapon*)m_pActiveItem)->SendWeaponAnim( 1, 1 ); + + if (m_iQuakeWeapon == IT_AXE) + { +#ifdef THREEWAVE + if ( m_iRuneStatus == ITEM_RUNE3_FLAG ) + m_flNextAttack = UTIL_WeaponTimeBase() + 0.3; + else +#endif + m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usAxeSwing, 0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, iQuadSound, 0, 0, 0 ); + + // Delay attack for 0.3 + m_flAxeFire = gpGlobals->time + 0.3; + + PLAYBACK_EVENT_FULL( FEV_NOTHOST, edict(), m_usAxe, 0.3, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, iQuadSound, 0, 0, 0 ); + + } + else if (m_iQuakeWeapon == IT_SHOTGUN) + { +#ifdef THREEWAVE + if ( m_iRuneStatus == ITEM_RUNE3_FLAG ) + m_flNextAttack = UTIL_WeaponTimeBase() + 0.3; + else +#endif + m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + + W_FireShotgun( iQuadSound ); + } + else if (m_iQuakeWeapon == IT_SUPER_SHOTGUN) + { +#ifdef THREEWAVE + if ( m_iRuneStatus == ITEM_RUNE3_FLAG ) + m_flNextAttack = UTIL_WeaponTimeBase() + 0.4; + else +#endif + m_flNextAttack = UTIL_WeaponTimeBase() + 0.7; + + W_FireSuperShotgun( iQuadSound ); + } + else if (m_iQuakeWeapon == IT_NAILGUN) + { + m_flNextAttack = UTIL_WeaponTimeBase() + 0.1; + + W_FireSpikes( iQuadSound ); + } + else if (m_iQuakeWeapon == IT_SUPER_NAILGUN) + { + m_flNextAttack = UTIL_WeaponTimeBase() + 0.1; + W_FireSpikes( iQuadSound ); + } + else if (m_iQuakeWeapon == IT_GRENADE_LAUNCHER) + { +#ifdef THREEWAVE + if ( m_iRuneStatus == ITEM_RUNE3_FLAG ) + m_flNextAttack = UTIL_WeaponTimeBase() + 0.3; + else +#endif + m_flNextAttack = UTIL_WeaponTimeBase() + 0.6; + + W_FireGrenade( iQuadSound ); + } + else if (m_iQuakeWeapon == IT_ROCKET_LAUNCHER) + { +#ifdef THREEWAVE + if ( m_iRuneStatus == ITEM_RUNE3_FLAG ) + m_flNextAttack = UTIL_WeaponTimeBase() + 0.4; + else +#endif + m_flNextAttack = UTIL_WeaponTimeBase() + 0.8; + + W_FireRocket( iQuadSound ); + } + else if (m_iQuakeWeapon == IT_LIGHTNING) + { + m_flNextAttack = UTIL_WeaponTimeBase() + 0.1; + + // Play the lightning start sound if gun just started firing + if (m_afButtonPressed & IN_ATTACK) + EMIT_SOUND(ENT(pev), CHAN_AUTO, "weapons/lstart.wav", 1, ATTN_NORM); + + W_FireLightning( iQuadSound ); + } + +#ifdef THREEWAVE + else if ( m_iQuakeWeapon == IT_EXTRA_WEAPON ) + { + + if ( !m_bHook_Out ) + W_FireHook (); + + m_flNextAttack = UTIL_WeaponTimeBase() + 0.1; + } +#endif + + // Make player attack + if ( pev->health >= 0 ) + SetAnimation( PLAYER_ATTACK1 ); +} diff --git a/dmc/dlls/saverestore.h b/dmc/dlls/saverestore.h new file mode 100644 index 0000000..bcca972 --- /dev/null +++ b/dmc/dlls/saverestore.h @@ -0,0 +1,170 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Implementation in UTIL.CPP +#ifndef SAVERESTORE_H +#define SAVERESTORE_H + +class CBaseEntity; + +class CSaveRestoreBuffer +{ +public: + CSaveRestoreBuffer( void ); + CSaveRestoreBuffer( SAVERESTOREDATA *pdata ); + ~CSaveRestoreBuffer( void ); + + int EntityIndex( entvars_t *pevLookup ); + int EntityIndex( edict_t *pentLookup ); + int EntityIndex( EOFFSET eoLookup ); + int EntityIndex( CBaseEntity *pEntity ); + + int EntityFlags( int entityIndex, int flags ) { return EntityFlagsSet( entityIndex, 0 ); } + int EntityFlagsSet( int entityIndex, int flags ); + + edict_t *EntityFromIndex( int entityIndex ); + + unsigned short TokenHash( const char *pszToken ); + +protected: + SAVERESTOREDATA *m_pdata; + void BufferRewind( int size ); + unsigned int HashString( const char *pszToken ); +}; + + +class CSave : public CSaveRestoreBuffer +{ +public: + CSave( SAVERESTOREDATA *pdata ) : CSaveRestoreBuffer( pdata ) {}; + + void WriteShort( const char *pname, const short *value, int count ); + void WriteInt( const char *pname, const int *value, int count ); // Save an int + void WriteFloat( const char *pname, const float *value, int count ); // Save a float + void WriteTime( const char *pname, const float *value, int count ); // Save a float (timevalue) + void WriteData( const char *pname, int size, const char *pdata ); // Save a binary data block + void WriteString( const char *pname, const char *pstring ); // Save a null-terminated string + void WriteString( const char *pname, const int *stringId, int count ); // Save a null-terminated string (engine string) + void WriteVector( const char *pname, const Vector &value ); // Save a vector + void WriteVector( const char *pname, const float *value, int count ); // Save a vector + void WritePositionVector( const char *pname, const Vector &value ); // Offset for landmark if necessary + void WritePositionVector( const char *pname, const float *value, int count ); // array of pos vectors + void WriteFunction( const char *pname, void **value, int count ); // Save a function pointer + int WriteEntVars( const char *pname, entvars_t *pev ); // Save entvars_t (entvars_t) + int WriteFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); + +private: + int DataEmpty( const char *pdata, int size ); + void BufferField( const char *pname, int size, const char *pdata ); + void BufferString( char *pdata, int len ); + void BufferData( const char *pdata, int size ); + void BufferHeader( const char *pname, int size ); +}; + +typedef struct +{ + unsigned short size; + unsigned short token; + char *pData; +} HEADER; + +class CRestore : public CSaveRestoreBuffer +{ +public: + CRestore( SAVERESTOREDATA *pdata ) : CSaveRestoreBuffer( pdata ) { m_global = 0; m_precache = TRUE; } + + int ReadEntVars( const char *pname, entvars_t *pev ); // entvars_t + int ReadFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); + int ReadField( void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount, int startField, int size, char *pName, void *pData ); + int ReadInt( void ); + short ReadShort( void ); + int ReadNamedInt( const char *pName ); + char *ReadNamedString( const char *pName ); + int Empty( void ) { return (m_pdata == NULL) || ((m_pdata->pCurrentData-m_pdata->pBaseData)>=m_pdata->bufferSize); } + inline void SetGlobalMode( int global ) { m_global = global; } + void PrecacheMode( BOOL mode ) { m_precache = mode; } + +private: + char *BufferPointer( void ); + void BufferReadBytes( char *pOutput, int size ); + void BufferSkipBytes( int bytes ); + int BufferSkipZString( void ); + int BufferCheckZString( const char *string ); + + void BufferReadHeader( HEADER *pheader ); + + int m_global; // Restoring a global entity? + BOOL m_precache; +}; + +#define MAX_ENTITYARRAY 64 + +//#define ARRAYSIZE(p) (sizeof(p)/sizeof(p[0])) + +#define IMPLEMENT_SAVERESTORE(derivedClass,baseClass) \ + int derivedClass::Save( CSave &save )\ + {\ + if ( !baseClass::Save(save) )\ + return 0;\ + return save.WriteFields( #derivedClass, this, m_SaveData, ARRAYSIZE(m_SaveData) );\ + }\ + int derivedClass::Restore( CRestore &restore )\ + {\ + if ( !baseClass::Restore(restore) )\ + return 0;\ + return restore.ReadFields( #derivedClass, this, m_SaveData, ARRAYSIZE(m_SaveData) );\ + } + + +typedef enum { GLOBAL_OFF = 0, GLOBAL_ON = 1, GLOBAL_DEAD = 2 } GLOBALESTATE; + +typedef struct globalentity_s globalentity_t; + +struct globalentity_s +{ + char name[64]; + char levelName[32]; + GLOBALESTATE state; + globalentity_t *pNext; +}; + +class CGlobalState +{ +public: + CGlobalState(); + void Reset( void ); + void ClearStates( void ); + void EntityAdd( string_t globalname, string_t mapName, GLOBALESTATE state ); + void EntitySetState( string_t globalname, GLOBALESTATE state ); + void EntityUpdate( string_t globalname, string_t mapname ); + const globalentity_t *EntityFromTable( string_t globalname ); + GLOBALESTATE EntityGetState( string_t globalname ); + int EntityInTable( string_t globalname ) { return (Find( globalname ) != NULL) ? 1 : 0; } + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +//#ifdef _DEBUG + void DumpGlobals( void ); +//#endif + +private: + globalentity_t *Find( string_t globalname ); + globalentity_t *m_pList; + int m_listCount; +}; + +extern CGlobalState gGlobalState; + +#endif //SAVERESTORE_H diff --git a/dmc/dlls/schedule.cpp b/dmc/dlls/schedule.cpp new file mode 100644 index 0000000..9b15dcf --- /dev/null +++ b/dmc/dlls/schedule.cpp @@ -0,0 +1,39 @@ +/*** +* +* Copyright (c) 1996-2002,, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// schedule.cpp - functions and data pertaining to the +// monsters' AI scheduling system. +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "animation.h" +#include "scripted.h" +#include "nodes.h" +#include "defaultai.h" + +extern CGraph WorldGraph; + +BOOL CBaseMonster :: FHaveSchedule( void ) { return FALSE; }; +void CBaseMonster :: ClearSchedule( void ) { }; +BOOL CBaseMonster :: FScheduleDone ( void ) { return FALSE; }; +void CBaseMonster :: ChangeSchedule ( Schedule_t *pNewSchedule ) { }; +void CBaseMonster :: NextScheduledTask ( void ) { }; +int CBaseMonster :: IScheduleFlags ( void ) { return 0; }; +BOOL CBaseMonster :: FScheduleValid ( void ) { return FALSE; }; +void CBaseMonster :: MaintainSchedule ( void ) { }; +void CBaseMonster :: SetTurnActivity ( void ) { }; +Task_t *CBaseMonster :: GetTask ( void ) { return NULL; }; diff --git a/dmc/dlls/schedule.h b/dmc/dlls/schedule.h new file mode 100644 index 0000000..82cbdeb --- /dev/null +++ b/dmc/dlls/schedule.h @@ -0,0 +1,290 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Scheduling +//========================================================= + +#ifndef SCHEDULE_H +#define SCHEDULE_H + +#define TASKSTATUS_NEW 0 // Just started +#define TASKSTATUS_RUNNING 1 // Running task & movement +#define TASKSTATUS_RUNNING_MOVEMENT 2 // Just running movement +#define TASKSTATUS_RUNNING_TASK 3 // Just running task +#define TASKSTATUS_COMPLETE 4 // Completed, get next task + + +//========================================================= +// These are the schedule types +//========================================================= +typedef enum +{ + SCHED_NONE = 0, + SCHED_IDLE_STAND, + SCHED_IDLE_WALK, + SCHED_WAKE_ANGRY, + SCHED_WAKE_CALLED, + SCHED_ALERT_FACE, + SCHED_ALERT_SMALL_FLINCH, + SCHED_ALERT_BIG_FLINCH, + SCHED_ALERT_STAND, + SCHED_INVESTIGATE_SOUND, + SCHED_COMBAT_FACE, + SCHED_COMBAT_STAND, + SCHED_CHASE_ENEMY, + SCHED_CHASE_ENEMY_FAILED, + SCHED_VICTORY_DANCE, + SCHED_TARGET_FACE, + SCHED_TARGET_CHASE, + SCHED_SMALL_FLINCH, + SCHED_TAKE_COVER_FROM_ENEMY, + SCHED_TAKE_COVER_FROM_BEST_SOUND, + SCHED_TAKE_COVER_FROM_ORIGIN, + SCHED_COWER, // usually a last resort! + SCHED_MELEE_ATTACK1, + SCHED_MELEE_ATTACK2, + SCHED_RANGE_ATTACK1, + SCHED_RANGE_ATTACK2, + SCHED_SPECIAL_ATTACK1, + SCHED_SPECIAL_ATTACK2, + SCHED_STANDOFF, + SCHED_ARM_WEAPON, + SCHED_RELOAD, + SCHED_GUARD, + SCHED_AMBUSH, + SCHED_DIE, + SCHED_WAIT_TRIGGER, + SCHED_FOLLOW, + SCHED_SLEEP, + SCHED_WAKE, + SCHED_BARNACLE_VICTIM_GRAB, + SCHED_BARNACLE_VICTIM_CHOMP, + SCHED_AISCRIPT, + SCHED_FAIL, + + LAST_COMMON_SCHEDULE // Leave this at the bottom +} SCHEDULE_TYPE; + +//========================================================= +// These are the shared tasks +//========================================================= +typedef enum +{ + TASK_INVALID = 0, + TASK_WAIT, + TASK_WAIT_FACE_ENEMY, + TASK_WAIT_PVS, + TASK_SUGGEST_STATE, + TASK_WALK_TO_TARGET, + TASK_RUN_TO_TARGET, + TASK_MOVE_TO_TARGET_RANGE, + TASK_GET_PATH_TO_ENEMY, + TASK_GET_PATH_TO_ENEMY_LKP, + TASK_GET_PATH_TO_ENEMY_CORPSE, + TASK_GET_PATH_TO_LEADER, + TASK_GET_PATH_TO_SPOT, + TASK_GET_PATH_TO_TARGET, + TASK_GET_PATH_TO_HINTNODE, + TASK_GET_PATH_TO_LASTPOSITION, + TASK_GET_PATH_TO_BESTSOUND, + TASK_GET_PATH_TO_BESTSCENT, + TASK_RUN_PATH, + TASK_WALK_PATH, + TASK_STRAFE_PATH, + TASK_CLEAR_MOVE_WAIT, + TASK_STORE_LASTPOSITION, + TASK_CLEAR_LASTPOSITION, + TASK_PLAY_ACTIVE_IDLE, + TASK_FIND_HINTNODE, + TASK_CLEAR_HINTNODE, + TASK_SMALL_FLINCH, + TASK_FACE_IDEAL, + TASK_FACE_ROUTE, + TASK_FACE_ENEMY, + TASK_FACE_HINTNODE, + TASK_FACE_TARGET, + TASK_FACE_LASTPOSITION, + TASK_RANGE_ATTACK1, + TASK_RANGE_ATTACK2, + TASK_MELEE_ATTACK1, + TASK_MELEE_ATTACK2, + TASK_RELOAD, + TASK_RANGE_ATTACK1_NOTURN, + TASK_RANGE_ATTACK2_NOTURN, + TASK_MELEE_ATTACK1_NOTURN, + TASK_MELEE_ATTACK2_NOTURN, + TASK_RELOAD_NOTURN, + TASK_SPECIAL_ATTACK1, + TASK_SPECIAL_ATTACK2, + TASK_CROUCH, + TASK_STAND, + TASK_GUARD, + TASK_STEP_LEFT, + TASK_STEP_RIGHT, + TASK_STEP_FORWARD, + TASK_STEP_BACK, + TASK_DODGE_LEFT, + TASK_DODGE_RIGHT, + TASK_SOUND_ANGRY, + TASK_SOUND_DEATH, + TASK_SET_ACTIVITY, + TASK_SET_SCHEDULE, + TASK_SET_FAIL_SCHEDULE, + TASK_CLEAR_FAIL_SCHEDULE, + TASK_PLAY_SEQUENCE, + TASK_PLAY_SEQUENCE_FACE_ENEMY, + TASK_PLAY_SEQUENCE_FACE_TARGET, + TASK_SOUND_IDLE, + TASK_SOUND_WAKE, + TASK_SOUND_PAIN, + TASK_SOUND_DIE, + TASK_FIND_COVER_FROM_BEST_SOUND,// tries lateral cover first, then node cover + TASK_FIND_COVER_FROM_ENEMY,// tries lateral cover first, then node cover + TASK_FIND_LATERAL_COVER_FROM_ENEMY, + TASK_FIND_NODE_COVER_FROM_ENEMY, + TASK_FIND_NEAR_NODE_COVER_FROM_ENEMY,// data for this one is the MAXIMUM acceptable distance to the cover. + TASK_FIND_FAR_NODE_COVER_FROM_ENEMY,// data for this one is there MINIMUM aceptable distance to the cover. + TASK_FIND_COVER_FROM_ORIGIN, + TASK_EAT, + TASK_DIE, + TASK_WAIT_FOR_SCRIPT, + TASK_PLAY_SCRIPT, + TASK_ENABLE_SCRIPT, + TASK_PLANT_ON_SCRIPT, + TASK_FACE_SCRIPT, + TASK_WAIT_RANDOM, + TASK_WAIT_INDEFINITE, + TASK_STOP_MOVING, + TASK_TURN_LEFT, + TASK_TURN_RIGHT, + TASK_REMEMBER, + TASK_FORGET, + TASK_WAIT_FOR_MOVEMENT, // wait until MovementIsComplete() + LAST_COMMON_TASK, // LEAVE THIS AT THE BOTTOM!! (sjb) +} SHARED_TASKS; + + +// These go in the flData member of the TASK_WALK_TO_TARGET, TASK_RUN_TO_TARGET +enum +{ + TARGET_MOVE_NORMAL = 0, + TARGET_MOVE_SCRIPTED = 1, +}; + + +// A goal should be used for a task that requires several schedules to complete. +// The goal index should indicate which schedule (ordinally) the monster is running. +// That way, when tasks fail, the AI can make decisions based on the context of the +// current goal and sequence rather than just the current schedule. +enum +{ + GOAL_ATTACK_ENEMY, + GOAL_MOVE, + GOAL_TAKE_COVER, + GOAL_MOVE_TARGET, + GOAL_EAT, +}; + +// an array of tasks is a task list +// an array of schedules is a schedule list +struct Task_t +{ + + int iTask; + float flData; +}; + +struct Schedule_t +{ + + Task_t *pTasklist; + int cTasks; + int iInterruptMask;// a bit mask of conditions that can interrupt this schedule + + // a more specific mask that indicates which TYPES of sounds will interrupt the schedule in the + // event that the schedule is broken by COND_HEAR_SOUND + int iSoundMask; + const char *pName; +}; + +// an array of waypoints makes up the monster's route. +// !!!LATER- this declaration doesn't belong in this file. +struct WayPoint_t +{ + Vector vecLocation; + int iType; +}; + +// these MoveFlag values are assigned to a WayPoint's TYPE in order to demonstrate the +// type of movement the monster should use to get there. +#define bits_MF_TO_TARGETENT ( 1 << 0 ) // local move to targetent. +#define bits_MF_TO_ENEMY ( 1 << 1 ) // local move to enemy +#define bits_MF_TO_COVER ( 1 << 2 ) // local move to a hiding place +#define bits_MF_TO_DETOUR ( 1 << 3 ) // local move to detour point. +#define bits_MF_TO_PATHCORNER ( 1 << 4 ) // local move to a path corner +#define bits_MF_TO_NODE ( 1 << 5 ) // local move to a node +#define bits_MF_TO_LOCATION ( 1 << 6 ) // local move to an arbitrary point +#define bits_MF_IS_GOAL ( 1 << 7 ) // this waypoint is the goal of the whole move. +#define bits_MF_DONT_SIMPLIFY ( 1 << 8 ) // Don't let the route code simplify this waypoint + +// If you define any flags that aren't _TO_ flags, add them here so we can mask +// them off when doing compares. +#define bits_MF_NOT_TO_MASK (bits_MF_IS_GOAL | bits_MF_DONT_SIMPLIFY) + +#define MOVEGOAL_NONE (0) +#define MOVEGOAL_TARGETENT (bits_MF_TO_TARGETENT) +#define MOVEGOAL_ENEMY (bits_MF_TO_ENEMY) +#define MOVEGOAL_PATHCORNER (bits_MF_TO_PATHCORNER) +#define MOVEGOAL_LOCATION (bits_MF_TO_LOCATION) +#define MOVEGOAL_NODE (bits_MF_TO_NODE) + +// these bits represent conditions that may befall the monster, of which some are allowed +// to interrupt certain schedules. +#define bits_COND_NO_AMMO_LOADED ( 1 << 0 ) // weapon needs to be reloaded! +#define bits_COND_SEE_HATE ( 1 << 1 ) // see something that you hate +#define bits_COND_SEE_FEAR ( 1 << 2 ) // see something that you are afraid of +#define bits_COND_SEE_DISLIKE ( 1 << 3 ) // see something that you dislike +#define bits_COND_SEE_ENEMY ( 1 << 4 ) // target entity is in full view. +#define bits_COND_ENEMY_OCCLUDED ( 1 << 5 ) // target entity occluded by the world +#define bits_COND_SMELL_FOOD ( 1 << 6 ) +#define bits_COND_ENEMY_TOOFAR ( 1 << 7 ) +#define bits_COND_LIGHT_DAMAGE ( 1 << 8 ) // hurt a little +#define bits_COND_HEAVY_DAMAGE ( 1 << 9 ) // hurt a lot +#define bits_COND_CAN_RANGE_ATTACK1 ( 1 << 10) +#define bits_COND_CAN_MELEE_ATTACK1 ( 1 << 11) +#define bits_COND_CAN_RANGE_ATTACK2 ( 1 << 12) +#define bits_COND_CAN_MELEE_ATTACK2 ( 1 << 13) +// #define bits_COND_CAN_RANGE_ATTACK3 ( 1 << 14) +#define bits_COND_PROVOKED ( 1 << 15) +#define bits_COND_NEW_ENEMY ( 1 << 16) +#define bits_COND_HEAR_SOUND ( 1 << 17) // there is an interesting sound +#define bits_COND_SMELL ( 1 << 18) // there is an interesting scent +#define bits_COND_ENEMY_FACING_ME ( 1 << 19) // enemy is facing me +#define bits_COND_ENEMY_DEAD ( 1 << 20) // enemy was killed. If you get this in combat, try to find another enemy. If you get it in alert, victory dance. +#define bits_COND_SEE_CLIENT ( 1 << 21) // see a client +#define bits_COND_SEE_NEMESIS ( 1 << 22) // see my nemesis + +#define bits_COND_SPECIAL1 ( 1 << 28) // Defined by individual monster +#define bits_COND_SPECIAL2 ( 1 << 29) // Defined by individual monster + +#define bits_COND_TASK_FAILED ( 1 << 30) +#define bits_COND_SCHEDULE_DONE ( 1 << 31) + + +#define bits_COND_ALL_SPECIAL (bits_COND_SPECIAL1 | bits_COND_SPECIAL2) + +#define bits_COND_CAN_ATTACK (bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_MELEE_ATTACK1 | bits_COND_CAN_RANGE_ATTACK2 | bits_COND_CAN_MELEE_ATTACK2) + +#endif // SCHEDULE_H diff --git a/dmc/dlls/scripted.cpp b/dmc/dlls/scripted.cpp new file mode 100644 index 0000000..10659f5 --- /dev/null +++ b/dmc/dlls/scripted.cpp @@ -0,0 +1,1260 @@ +/*** +* +* Copyright (c) 1996-2002 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +/* + + +===== scripted.cpp ======================================================== + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" + +#ifndef ANIMATION_H +#include "animation.h" +#endif + +#ifndef SAVERESTORE_H +#include "saverestore.h" +#endif + +#include "schedule.h" +#include "scripted.h" +#include "defaultai.h" + + + +/* +classname "scripted_sequence" +targetname "me" - there can be more than one with the same name, and they act in concert +target "the_entity_I_want_to_start_playing" or "class entity_classname" will pick the closest inactive scientist +play "name_of_sequence" +idle "name of idle sequence to play before starting" +donetrigger "whatever" - can be any other triggerable entity such as another sequence, train, door, or a special case like "die" or "remove" +moveto - if set the monster first moves to this nodes position +range # - only search this far to find the target +spawnflags - (stop if blocked, stop if player seen) +*/ + + +// +// Cache user-entity-field values until spawn is called. +// + +void CCineMonster :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszIdle")) + { + m_iszIdle = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszPlay")) + { + m_iszPlay = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszEntity")) + { + m_iszEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fMoveTo")) + { + m_fMoveTo = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flRepeat")) + { + m_flRepeat = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flRadius")) + { + m_flRadius = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iFinishSchedule")) + { + m_iFinishSchedule = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CBaseMonster::KeyValue( pkvd ); + } +} + +TYPEDESCRIPTION CCineMonster::m_SaveData[] = +{ + DEFINE_FIELD( CCineMonster, m_iszIdle, FIELD_STRING ), + DEFINE_FIELD( CCineMonster, m_iszPlay, FIELD_STRING ), + DEFINE_FIELD( CCineMonster, m_iszEntity, FIELD_STRING ), + DEFINE_FIELD( CCineMonster, m_fMoveTo, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_flRepeat, FIELD_FLOAT ), + DEFINE_FIELD( CCineMonster, m_flRadius, FIELD_FLOAT ), + + DEFINE_FIELD( CCineMonster, m_iDelay, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_startTime, FIELD_TIME ), + + DEFINE_FIELD( CCineMonster, m_saved_movetype, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_saved_solid, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_saved_effects, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_iFinishSchedule, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_interruptable, FIELD_BOOLEAN ), +}; + + +IMPLEMENT_SAVERESTORE( CCineMonster, CBaseMonster ); + +LINK_ENTITY_TO_CLASS( scripted_sequence, CCineMonster ); +#define CLASSNAME "scripted_sequence" + +LINK_ENTITY_TO_CLASS( aiscripted_sequence, CCineAI ); + + +void CCineMonster :: Spawn( void ) +{ + // pev->solid = SOLID_TRIGGER; + // UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8)); + pev->solid = SOLID_NOT; + + + // REMOVE: The old side-effect +#if 0 + if ( m_iszIdle ) + m_fMoveTo = 4; +#endif + + // if no targetname, start now + if ( FStringNull(pev->targetname) || !FStringNull( m_iszIdle ) ) + { + SetThink( CineThink ); + pev->nextthink = gpGlobals->time + 1.0; + // Wait to be used? + if ( pev->targetname ) + m_startTime = gpGlobals->time + 1E6; + } + if ( pev->spawnflags & SF_SCRIPT_NOINTERRUPT ) + m_interruptable = FALSE; + else + m_interruptable = TRUE; +} + +//========================================================= +// FCanOverrideState - returns FALSE, scripted sequences +// cannot possess entities regardless of state. +//========================================================= +BOOL CCineMonster :: FCanOverrideState( void ) +{ + if ( pev->spawnflags & SF_SCRIPT_OVERRIDESTATE ) + return TRUE; + return FALSE; +} + +//========================================================= +// FCanOverrideState - returns true because scripted AI can +// possess entities regardless of their state. +//========================================================= +BOOL CCineAI :: FCanOverrideState( void ) +{ + return TRUE; +} + + +// +// CineStart +// +void CCineMonster :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // do I already know who I should use + CBaseEntity *pEntity = m_hTargetEnt; + CBaseMonster *pTarget = NULL; + + if ( pEntity ) + pTarget = pEntity->MyMonsterPointer(); + + if ( pTarget ) + { + // am I already playing the script? + if ( pTarget->m_scriptState == SCRIPT_PLAYING ) + return; + + m_startTime = gpGlobals->time + 0.05; + } + else + { + // if not, try finding them + SetThink( CineThink ); + pev->nextthink = gpGlobals->time; + } +} + + +// This doesn't really make sense since only MOVETYPE_PUSH get 'Blocked' events +void CCineMonster :: Blocked( CBaseEntity *pOther ) +{ + +} + +void CCineMonster :: Touch( CBaseEntity *pOther ) +{ +/* + ALERT( at_aiconsole, "Cine Touch\n" ); + if (m_pentTarget && OFFSET(pOther->pev) == OFFSET(m_pentTarget)) + { + CBaseMonster *pTarget = GetClassPtr((CBaseMonster *)VARS(m_pentTarget)); + pTarget->m_monsterState == MONSTERSTATE_SCRIPT; + } +*/ +} + + +/* + entvars_t *pevOther = VARS( gpGlobals->other ); + + if ( !FBitSet ( pevOther->flags , FL_MONSTER ) ) + {// touched by a non-monster. + return; + } + + pevOther->origin.z += 1; + + if ( FBitSet ( pevOther->flags, FL_ONGROUND ) ) + {// clear the onground so physics don't bitch + pevOther->flags -= FL_ONGROUND; + } + + // toss the monster! + pevOther->velocity = pev->movedir * pev->speed; + pevOther->velocity.z += m_flHeight; + + + pev->solid = SOLID_NOT;// kill the trigger for now !!!UNDONE +} +*/ + + +// +// ********** Cinematic DIE ********** +// +void CCineMonster :: Die( void ) +{ + SetThink( SUB_Remove ); +} + +// +// ********** Cinematic PAIN ********** +// +void CCineMonster :: Pain( void ) +{ + +} + +// +// ********** Cinematic Think ********** +// + +// find a viable entity +int CCineMonster :: FindEntity( void ) +{ + edict_t *pentTarget; + + pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(m_iszEntity)); + m_hTargetEnt = NULL; + CBaseMonster *pTarget = NULL; + + while (!FNullEnt(pentTarget)) + { + if ( FBitSet( VARS(pentTarget)->flags, FL_MONSTER )) + { + pTarget = GetMonsterPointer( pentTarget ); + if ( pTarget && pTarget->CanPlaySequence( FCanOverrideState(), SS_INTERRUPT_BY_NAME ) ) + { + m_hTargetEnt = pTarget; + return TRUE; + } + ALERT( at_console, "Found %s, but can't play!\n", STRING(m_iszEntity) ); + } + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(m_iszEntity)); + pTarget = NULL; + } + + if ( !pTarget ) + { + CBaseEntity *pEntity = NULL; + while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, m_flRadius )) != NULL) + { + if (FClassnameIs( pEntity->pev, STRING(m_iszEntity))) + { + if ( FBitSet( pEntity->pev->flags, FL_MONSTER )) + { + pTarget = pEntity->MyMonsterPointer( ); + if ( pTarget && pTarget->CanPlaySequence( FCanOverrideState(), SS_INTERRUPT_IDLE ) ) + { + m_hTargetEnt = pTarget; + return TRUE; + } + } + } + } + } + pTarget = NULL; + m_hTargetEnt = NULL; + return FALSE; +} + +// make the entity enter a scripted sequence +void CCineMonster :: PossessEntity( void ) +{ + CBaseEntity *pEntity = m_hTargetEnt; + CBaseMonster *pTarget = NULL; + if ( pEntity ) + pTarget = pEntity->MyMonsterPointer(); + + if ( pTarget ) + { + + // FindEntity() just checked this! +#if 0 + if ( !pTarget->CanPlaySequence( FCanOverrideState() ) ) + { + ALERT( at_aiconsole, "Can't possess entity %s\n", STRING(pTarget->pev->classname) ); + return; + } +#endif + + pTarget->m_pGoalEnt = this; + pTarget->m_pCine = this; + pTarget->m_hTargetEnt = this; + + m_saved_movetype = pTarget->pev->movetype; + m_saved_solid = pTarget->pev->solid; + m_saved_effects = pTarget->pev->effects; + pTarget->pev->effects |= pev->effects; + + switch (m_fMoveTo) + { + case 0: + pTarget->m_scriptState = SCRIPT_WAIT; + break; + + case 1: + pTarget->m_scriptState = SCRIPT_WALK_TO_MARK; + DelayStart( 1 ); + break; + + case 2: + pTarget->m_scriptState = SCRIPT_RUN_TO_MARK; + DelayStart( 1 ); + break; + + case 4: + UTIL_SetOrigin( pTarget->pev, pev->origin ); + pTarget->pev->ideal_yaw = pev->angles.y; + pTarget->pev->avelocity = Vector( 0, 0, 0 ); + pTarget->pev->velocity = Vector( 0, 0, 0 ); + pTarget->pev->effects |= EF_NOINTERP; + pTarget->pev->angles.y = pev->angles.y; + pTarget->m_scriptState = SCRIPT_WAIT; + m_startTime = gpGlobals->time + 1E6; + // UNDONE: Add a flag to do this so people can fixup physics after teleporting monsters + // pTarget->pev->flags &= ~FL_ONGROUND; + break; + } +// ALERT( at_aiconsole, "\"%s\" found and used (INT: %s)\n", STRING( pTarget->pev->targetname ), FBitSet(pev->spawnflags, SF_SCRIPT_NOINTERRUPT)?"No":"Yes" ); + + pTarget->m_IdealMonsterState = MONSTERSTATE_SCRIPT; + if (m_iszIdle) + { + StartSequence( pTarget, m_iszIdle, FALSE ); + if (FStrEq( STRING(m_iszIdle), STRING(m_iszPlay))) + { + pTarget->pev->framerate = 0; + } + } + } +} + +// make the entity carry out the scripted sequence instructions, but without +// destroying the monster's state. +void CCineAI :: PossessEntity( void ) +{ + Schedule_t *pNewSchedule; + + CBaseEntity *pEntity = m_hTargetEnt; + CBaseMonster *pTarget = NULL; + if ( pEntity ) + pTarget = pEntity->MyMonsterPointer(); + + if ( pTarget ) + { + if ( !pTarget->CanPlaySequence( FCanOverrideState(), SS_INTERRUPT_AI ) ) + { + ALERT( at_aiconsole, "(AI)Can't possess entity %s\n", STRING(pTarget->pev->classname) ); + return; + } + + pTarget->m_pGoalEnt = this; + pTarget->m_pCine = this; + pTarget->m_hTargetEnt = this; + + m_saved_movetype = pTarget->pev->movetype; + m_saved_solid = pTarget->pev->solid; + m_saved_effects = pTarget->pev->effects; + pTarget->pev->effects |= pev->effects; + + switch (m_fMoveTo) + { + case 0: + case 5: + pTarget->m_scriptState = SCRIPT_WAIT; + break; + + case 1: + pTarget->m_scriptState = SCRIPT_WALK_TO_MARK; + break; + + case 2: + pTarget->m_scriptState = SCRIPT_RUN_TO_MARK; + break; + + case 4: + // zap the monster instantly to the site of the script entity. + UTIL_SetOrigin( pTarget->pev, pev->origin ); + pTarget->pev->ideal_yaw = pev->angles.y; + pTarget->pev->avelocity = Vector( 0, 0, 0 ); + pTarget->pev->velocity = Vector( 0, 0, 0 ); + pTarget->pev->effects |= EF_NOINTERP; + pTarget->pev->angles.y = pev->angles.y; + pTarget->m_scriptState = SCRIPT_WAIT; + m_startTime = gpGlobals->time + 1E6; + // UNDONE: Add a flag to do this so people can fixup physics after teleporting monsters + pTarget->pev->flags &= ~FL_ONGROUND; + break; + default: + ALERT ( at_aiconsole, "aiscript: invalid Move To Position value!" ); + break; + } + + ALERT( at_aiconsole, "\"%s\" found and used\n", STRING( pTarget->pev->targetname ) ); + + pTarget->m_IdealMonsterState = MONSTERSTATE_SCRIPT; + +/* + if (m_iszIdle) + { + StartSequence( pTarget, m_iszIdle, FALSE ); + if (FStrEq( STRING(m_iszIdle), STRING(m_iszPlay))) + { + pTarget->pev->framerate = 0; + } + } +*/ + // Already in a scripted state? + if ( pTarget->m_MonsterState == MONSTERSTATE_SCRIPT ) + { + pNewSchedule = pTarget->GetScheduleOfType( SCHED_AISCRIPT ); + pTarget->ChangeSchedule( pNewSchedule ); + } + } +} + +void CCineMonster :: CineThink( void ) +{ + if (FindEntity()) + { + PossessEntity( ); + ALERT( at_aiconsole, "script \"%s\" using monster \"%s\"\n", STRING( pev->targetname ), STRING( m_iszEntity ) ); + } + else + { + CancelScript( ); + ALERT( at_aiconsole, "script \"%s\" can't find monster \"%s\"\n", STRING( pev->targetname ), STRING( m_iszEntity ) ); + pev->nextthink = gpGlobals->time + 1.0; + } +} + + +// lookup a sequence name and setup the target monster to play it +BOOL CCineMonster :: StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL completeOnEmpty ) +{ + if ( !iszSeq && completeOnEmpty ) + { + SequenceDone( pTarget ); + return FALSE; + } + + pTarget->pev->sequence = pTarget->LookupSequence( STRING( iszSeq ) ); + if (pTarget->pev->sequence == -1) + { + ALERT( at_error, "%s: unknown scripted sequence \"%s\"\n", STRING( pTarget->pev->targetname ), STRING( iszSeq) ); + pTarget->pev->sequence = 0; + // return FALSE; + } + +#if 0 + char *s; + if ( pev->spawnflags & SF_SCRIPT_NOINTERRUPT ) + s = "No"; + else + s = "Yes"; + + ALERT( at_console, "%s (%s): started \"%s\":INT:%s\n", STRING( pTarget->pev->targetname ), STRING( pTarget->pev->classname ), STRING( iszSeq), s ); +#endif + + pTarget->pev->frame = 0; + pTarget->ResetSequenceInfo( ); + return TRUE; +} + +// lookup a sequence name and setup the target monster to play it +// overridden for CCineAI because it's ok for them to not have an animation sequence +// for the monster to play. For a regular Scripted Sequence, that situation is an error. +BOOL CCineAI :: StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL completeOnEmpty ) +{ + if ( iszSeq == 0 && completeOnEmpty ) + { + // no sequence was provided. Just let the monster proceed, however, we still have to fire any Sequence target + // and remove any non-repeatable CineAI entities here ( because there is code elsewhere that handles those tasks, but + // not until the animation sequence is finished. We have to manually take care of these things where there is no sequence. + + SequenceDone ( pTarget ); + + return TRUE; + } + + pTarget->pev->sequence = pTarget->LookupSequence( STRING( iszSeq ) ); + + if (pTarget->pev->sequence == -1) + { + ALERT( at_error, "%s: unknown aiscripted sequence \"%s\"\n", STRING( pTarget->pev->targetname ), STRING( iszSeq) ); + pTarget->pev->sequence = 0; + // return FALSE; + } + + pTarget->pev->frame = 0; + pTarget->ResetSequenceInfo( ); + return TRUE; +} + +//========================================================= +// SequenceDone - called when a scripted sequence animation +// sequence is done playing ( or when an AI Scripted Sequence +// doesn't supply an animation sequence to play ). Expects +// the CBaseMonster pointer to the monster that the sequence +// possesses. +//========================================================= +void CCineMonster :: SequenceDone ( CBaseMonster *pMonster ) +{ + //ALERT( at_aiconsole, "Sequence %s finished\n", STRING( m_pCine->m_iszPlay ) ); + + if ( !( pev->spawnflags & SF_SCRIPT_REPEATABLE ) ) + { + SetThink( SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; + } + + // This is done so that another sequence can take over the monster when triggered by the first + + pMonster->CineCleanup(); + + FixScriptMonsterSchedule( pMonster ); + + // This may cause a sequence to attempt to grab this guy NOW, so we have to clear him out + // of the existing sequence + SUB_UseTargets( NULL, USE_TOGGLE, 0 ); +} + +//========================================================= +// When a monster finishes a scripted sequence, we have to +// fix up its state and schedule for it to return to a +// normal AI monster. +// +// Scripted sequences just dirty the Schedule and drop the +// monster in Idle State. +//========================================================= +void CCineMonster :: FixScriptMonsterSchedule( CBaseMonster *pMonster ) +{ + if ( pMonster->m_IdealMonsterState != MONSTERSTATE_DEAD ) + pMonster->m_IdealMonsterState = MONSTERSTATE_IDLE; + pMonster->ClearSchedule(); +} + +//========================================================= +// When a monster finishes a scripted sequence, we have to +// fix up its state and schedule for it to return to a +// normal AI monster. +// +// AI Scripted sequences will, depending on what the level +// designer selects: +// +// -Dirty the monster's schedule and drop out of the +// sequence in their current state. +// +// -Select a specific AMBUSH schedule, regardless of state. +//========================================================= +void CCineAI :: FixScriptMonsterSchedule( CBaseMonster *pMonster ) +{ + switch ( m_iFinishSchedule ) + { + case SCRIPT_FINISHSCHED_DEFAULT: + pMonster->ClearSchedule(); + break; + case SCRIPT_FINISHSCHED_AMBUSH: + pMonster->ChangeSchedule( pMonster->GetScheduleOfType( SCHED_AMBUSH ) ); + break; + default: + ALERT ( at_aiconsole, "FixScriptMonsterSchedule - no case!\n" ); + pMonster->ClearSchedule(); + break; + } +} + +BOOL CBaseMonster :: ExitScriptedSequence( ) +{ + if ( pev->deadflag == DEAD_DYING ) + { + // is this legal? + // BUGBUG -- This doesn't call Killed() + m_IdealMonsterState = MONSTERSTATE_DEAD; + return FALSE; + } + + if (m_pCine) + { + m_pCine->CancelScript( ); + } + + return TRUE; +} + + +void CCineMonster::AllowInterrupt( BOOL fAllow ) +{ + if ( pev->spawnflags & SF_SCRIPT_NOINTERRUPT ) + return; + m_interruptable = fAllow; +} + + +BOOL CCineMonster::CanInterrupt( void ) +{ + if ( !m_interruptable ) + return FALSE; + + CBaseEntity *pTarget = m_hTargetEnt; + + if ( pTarget != NULL && pTarget->pev->deadflag == DEAD_NO ) + return TRUE; + + return FALSE; +} + + +int CCineMonster::IgnoreConditions( void ) +{ + if ( CanInterrupt() ) + return 0; + return SCRIPT_BREAK_CONDITIONS; +} + + +void ScriptEntityCancel( edict_t *pentCine ) +{ + // make sure they are a scripted_sequence + if (FClassnameIs( pentCine, CLASSNAME )) + { + CCineMonster *pCineTarget = GetClassPtr((CCineMonster *)VARS(pentCine)); + // make sure they have a monster in mind for the script + CBaseEntity *pEntity = pCineTarget->m_hTargetEnt; + CBaseMonster *pTarget = NULL; + if ( pEntity ) + pTarget = pEntity->MyMonsterPointer(); + + if (pTarget) + { + // make sure their monster is actually playing a script + if ( pTarget->m_MonsterState == MONSTERSTATE_SCRIPT ) + { + // tell them do die + pTarget->m_scriptState = CCineMonster::SCRIPT_CLEANUP; + // do it now + pTarget->CineCleanup( ); + } + } + } +} + + +// find all the cinematic entities with my targetname and stop them from playing +void CCineMonster :: CancelScript( void ) +{ + ALERT( at_aiconsole, "Cancelling script: %s\n", STRING(m_iszPlay) ); + + if ( !pev->targetname ) + { + ScriptEntityCancel( edict() ); + return; + } + + edict_t *pentCineTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(pev->targetname)); + + while (!FNullEnt(pentCineTarget)) + { + ScriptEntityCancel( pentCineTarget ); + pentCineTarget = FIND_ENTITY_BY_TARGETNAME(pentCineTarget, STRING(pev->targetname)); + } +} + + +// find all the cinematic entities with my targetname and tell them to wait before starting +void CCineMonster :: DelayStart( int state ) +{ + edict_t *pentCine = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(pev->targetname)); + + while (!FNullEnt(pentCine)) + { + if (FClassnameIs( pentCine, "scripted_sequence" )) + { + CCineMonster *pTarget = GetClassPtr((CCineMonster *)VARS(pentCine)); + if (state) + { + pTarget->m_iDelay++; + } + else + { + pTarget->m_iDelay--; + if (pTarget->m_iDelay <= 0) + pTarget->m_startTime = gpGlobals->time + 0.05; + } + } + pentCine = FIND_ENTITY_BY_TARGETNAME(pentCine, STRING(pev->targetname)); + } +} + + + +// Find an entity that I'm interested in and precache the sounds he'll need in the sequence. +void CCineMonster :: Activate( void ) +{ + edict_t *pentTarget; + CBaseMonster *pTarget; + + // The entity name could be a target name or a classname + // Check the targetname + pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(m_iszEntity)); + pTarget = NULL; + + while (!pTarget && !FNullEnt(pentTarget)) + { + if ( FBitSet( VARS(pentTarget)->flags, FL_MONSTER )) + { + pTarget = GetMonsterPointer( pentTarget ); + } + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(m_iszEntity)); + } + + // If no entity with that targetname, check the classname + if ( !pTarget ) + { + pentTarget = FIND_ENTITY_BY_CLASSNAME(NULL, STRING(m_iszEntity)); + while (!pTarget && !FNullEnt(pentTarget)) + { + pTarget = GetMonsterPointer( pentTarget ); + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(m_iszEntity)); + } + } + // Found a compatible entity + if ( pTarget ) + { + void *pmodel; + pmodel = GET_MODEL_PTR( pTarget->edict() ); + if ( pmodel ) + { + // Look through the event list for stuff to precache + SequencePrecache( pmodel, STRING( m_iszIdle ) ); + SequencePrecache( pmodel, STRING( m_iszPlay ) ); + } + } +} + + +BOOL CBaseMonster :: CineCleanup( ) +{ + CCineMonster *pOldCine = m_pCine; + + // am I linked to a cinematic? + if (m_pCine) + { + // okay, reset me to what it thought I was before + m_pCine->m_hTargetEnt = NULL; + pev->movetype = m_pCine->m_saved_movetype; + pev->solid = m_pCine->m_saved_solid; + pev->effects = m_pCine->m_saved_effects; + } + else + { + // arg, punt + pev->movetype = MOVETYPE_STEP;// this is evil + pev->solid = SOLID_SLIDEBOX; + } + m_pCine = NULL; + m_hTargetEnt = NULL; + m_pGoalEnt = NULL; + if (pev->deadflag == DEAD_DYING) + { + // last frame of death animation? + pev->health = 0; + pev->framerate = 0.0; + pev->solid = SOLID_NOT; + SetState( MONSTERSTATE_DEAD ); + pev->deadflag = DEAD_DEAD; + UTIL_SetSize( pev, pev->mins, Vector(pev->maxs.x, pev->maxs.y, pev->mins.z + 2) ); + + if ( pOldCine && FBitSet( pOldCine->pev->spawnflags, SF_SCRIPT_LEAVECORPSE ) ) + { + SetUse( NULL ); // BUGBUG -- This doesn't call Killed() + SetThink( NULL ); // This will probably break some stuff + SetTouch( NULL ); + } + else + SUB_StartFadeOut(); // SetThink( SUB_DoNothing ); + // This turns off animation & physics in case their origin ends up stuck in the world or something + StopAnimation(); + pev->movetype = MOVETYPE_NONE; + pev->effects |= EF_NOINTERP; // Don't interpolate either, assume the corpse is positioned in its final resting place + return FALSE; + } + + // If we actually played a sequence + if ( pOldCine && pOldCine->m_iszPlay ) + { + if ( !(pOldCine->pev->spawnflags & SF_SCRIPT_NOSCRIPTMOVEMENT) ) + { + // reset position + Vector new_origin, new_angle; + GetBonePosition( 0, new_origin, new_angle ); + + // Figure out how far they have moved + // We can't really solve this problem because we can't query the movement of the origin relative + // to the sequence. We can get the root bone's position as we do here, but there are + // cases where the root bone is in a different relative position to the entity's origin + // before/after the sequence plays. So we are stuck doing this: + + // !!!HACKHACK: Float the origin up and drop to floor because some sequences have + // irregular motion that can't be properly accounted for. + + // UNDONE: THIS SHOULD ONLY HAPPEN IF WE ACTUALLY PLAYED THE SEQUENCE. + Vector oldOrigin = pev->origin; + + // UNDONE: ugly hack. Don't move monster if they don't "seem" to move + // this really needs to be done with the AX,AY,etc. flags, but that aren't consistantly + // being set, so animations that really do move won't be caught. + if ((oldOrigin - new_origin).Length2D() < 8.0) + new_origin = oldOrigin; + + pev->origin.x = new_origin.x; + pev->origin.y = new_origin.y; + pev->origin.z += 1; + + pev->flags |= FL_ONGROUND; + int drop = DROP_TO_FLOOR( ENT(pev) ); + + // Origin in solid? Set to org at the end of the sequence + if ( drop < 0 ) + pev->origin = oldOrigin; + else if ( drop == 0 ) // Hanging in air? + { + pev->origin.z = new_origin.z; + pev->flags &= ~FL_ONGROUND; + } + // else entity hit floor, leave there + + // pEntity->pev->origin.z = new_origin.z + 5.0; // damn, got to fix this + + UTIL_SetOrigin( pev, pev->origin ); + pev->effects |= EF_NOINTERP; + } + + // We should have some animation to put these guys in, but for now it's idle. + // Due to NOINTERP above, there won't be any blending between this anim & the sequence + m_Activity = ACT_RESET; + } + // set them back into a normal state + pev->enemy = NULL; + if ( pev->health > 0 ) + m_IdealMonsterState = MONSTERSTATE_IDLE; // m_previousState; + else + { + // Dropping out because he got killed + // Can't call killed() no attacker and weirdness (late gibbing) may result + m_IdealMonsterState = MONSTERSTATE_DEAD; + SetConditions( bits_COND_LIGHT_DAMAGE ); + pev->deadflag = DEAD_DYING; + FCheckAITrigger(); + pev->deadflag = DEAD_NO; + } + + + // SetAnimation( m_MonsterState ); + ClearBits(pev->spawnflags, SF_MONSTER_WAIT_FOR_SCRIPT ); + + return TRUE; +} + + + + +class CScriptedSentence : public CBaseToggle +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT FindThink( void ); + void EXPORT DelayThink( void ); + int ObjectCaps( void ) { return (CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + CBaseMonster *FindEntity( void ); + BOOL AcceptableSpeaker( CBaseMonster *pMonster ); + BOOL StartSentence( CBaseMonster *pTarget ); + + +private: + int m_iszSentence; // string index for idle animation + int m_iszEntity; // entity that is wanted for this sentence + float m_flRadius; // range to search + float m_flDuration; // How long the sentence lasts + float m_flRepeat; // repeat rate + float m_flAttenuation; + float m_flVolume; + BOOL m_active; + int m_iszListener; // name of entity to look at while talking +}; + +#define SF_SENTENCE_ONCE 0x0001 +#define SF_SENTENCE_FOLLOWERS 0x0002 // only say if following player +#define SF_SENTENCE_INTERRUPT 0x0004 // force talking except when dead +#define SF_SENTENCE_CONCURRENT 0x0008 // allow other people to keep talking + +TYPEDESCRIPTION CScriptedSentence::m_SaveData[] = +{ + DEFINE_FIELD( CScriptedSentence, m_iszSentence, FIELD_STRING ), + DEFINE_FIELD( CScriptedSentence, m_iszEntity, FIELD_STRING ), + DEFINE_FIELD( CScriptedSentence, m_flRadius, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flDuration, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flRepeat, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flAttenuation, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flVolume, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_active, FIELD_BOOLEAN ), + DEFINE_FIELD( CScriptedSentence, m_iszListener, FIELD_STRING ), +}; + + +IMPLEMENT_SAVERESTORE( CScriptedSentence, CBaseToggle ); + +LINK_ENTITY_TO_CLASS( scripted_sentence, CScriptedSentence ); + +void CScriptedSentence :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "sentence")) + { + m_iszSentence = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "entity")) + { + m_iszEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "duration")) + { + m_flDuration = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "radius")) + { + m_flRadius = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "refire")) + { + m_flRepeat = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if(FStrEq(pkvd->szKeyName, "attenuation")) + { + pev->impulse = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if(FStrEq(pkvd->szKeyName, "volume")) + { + m_flVolume = atof( pkvd->szValue ) * 0.1; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "listener")) + { + m_iszListener = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + + +void CScriptedSentence :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !m_active ) + return; +// ALERT( at_console, "Firing sentence: %s\n", STRING(m_iszSentence) ); + SetThink( FindThink ); + pev->nextthink = gpGlobals->time; +} + + +void CScriptedSentence :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + + m_active = TRUE; + // if no targetname, start now + if ( !pev->targetname ) + { + SetThink( FindThink ); + pev->nextthink = gpGlobals->time + 1.0; + } + + switch( pev->impulse ) + { + case 1: // Medium radius + m_flAttenuation = ATTN_STATIC; + break; + + case 2: // Large radius + m_flAttenuation = ATTN_NORM; + break; + + case 3: //EVERYWHERE + m_flAttenuation = ATTN_NONE; + break; + + default: + case 0: // Small radius + m_flAttenuation = ATTN_IDLE; + break; + } + pev->impulse = 0; + + // No volume, use normal + if ( m_flVolume <= 0 ) + m_flVolume = 1.0; +} + + +void CScriptedSentence :: FindThink( void ) +{ + CBaseMonster *pMonster = FindEntity(); + if ( pMonster ) + { + StartSentence( pMonster ); + if ( pev->spawnflags & SF_SENTENCE_ONCE ) + UTIL_Remove( this ); + SetThink( DelayThink ); + pev->nextthink = gpGlobals->time + m_flDuration + m_flRepeat; + m_active = FALSE; +// ALERT( at_console, "%s: found monster %s\n", STRING(m_iszSentence), STRING(m_iszEntity) ); + } + else + { +// ALERT( at_console, "%s: can't find monster %s\n", STRING(m_iszSentence), STRING(m_iszEntity) ); + pev->nextthink = gpGlobals->time + m_flRepeat + 0.5; + } +} + + +void CScriptedSentence :: DelayThink( void ) +{ + m_active = TRUE; + if ( !pev->targetname ) + pev->nextthink = gpGlobals->time + 0.1; + SetThink( FindThink ); +} + + +BOOL CScriptedSentence :: AcceptableSpeaker( CBaseMonster *pMonster ) +{ + if ( pMonster ) + { + if ( pev->spawnflags & SF_SENTENCE_FOLLOWERS ) + { + if ( pMonster->m_hTargetEnt == NULL || !FClassnameIs(pMonster->m_hTargetEnt->pev, "player") ) + return FALSE; + } + BOOL override; + if ( pev->spawnflags & SF_SENTENCE_INTERRUPT ) + override = TRUE; + else + override = FALSE; + if ( pMonster->CanPlaySentence( override ) ) + return TRUE; + } + return FALSE; +} + + +CBaseMonster *CScriptedSentence :: FindEntity( void ) +{ + edict_t *pentTarget; + CBaseMonster *pMonster; + + + pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(m_iszEntity)); + pMonster = NULL; + + while (!FNullEnt(pentTarget)) + { + pMonster = GetMonsterPointer( pentTarget ); + if ( pMonster != NULL ) + { + if ( AcceptableSpeaker( pMonster ) ) + return pMonster; +// ALERT( at_console, "%s (%s), not acceptable\n", STRING(pMonster->pev->classname), STRING(pMonster->pev->targetname) ); + } + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(m_iszEntity)); + } + + CBaseEntity *pEntity = NULL; + while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, m_flRadius )) != NULL) + { + if (FClassnameIs( pEntity->pev, STRING(m_iszEntity))) + { + if ( FBitSet( pEntity->pev->flags, FL_MONSTER )) + { + pMonster = pEntity->MyMonsterPointer( ); + if ( AcceptableSpeaker( pMonster ) ) + return pMonster; + } + } + } + + return NULL; +} + + +BOOL CScriptedSentence :: StartSentence( CBaseMonster *pTarget ) +{ + if ( !pTarget ) + { + ALERT( at_aiconsole, "Not Playing sentence %s\n", STRING(m_iszSentence) ); + return NULL; + } + + BOOL bConcurrent = FALSE; + if ( !(pev->spawnflags & SF_SENTENCE_CONCURRENT) ) + bConcurrent = TRUE; + + CBaseEntity *pListener = NULL; + if (!FStringNull(m_iszListener)) + { + float radius = m_flRadius; + + if ( FStrEq( STRING(m_iszListener ), "player" ) ) + radius = 4096; // Always find the player + + pListener = UTIL_FindEntityGeneric( STRING( m_iszListener ), pTarget->pev->origin, radius ); + } + + pTarget->PlayScriptedSentence( STRING(m_iszSentence), m_flDuration, m_flVolume, m_flAttenuation, bConcurrent, pListener ); + ALERT( at_aiconsole, "Playing sentence %s (%.1f)\n", STRING(m_iszSentence), m_flDuration ); + SUB_UseTargets( NULL, USE_TOGGLE, 0 ); + return TRUE; +} + + + + + +/* + +*/ + + +//========================================================= +// Furniture - this is the cool comment I cut-and-pasted +//========================================================= +class CFurniture : public CBaseMonster +{ +public: + void Spawn ( void ); + void Die( void ); + int Classify ( void ); + virtual int ObjectCaps( void ) { return (CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } +}; + + +LINK_ENTITY_TO_CLASS( monster_furniture, CFurniture ); + + +//========================================================= +// Furniture is killed +//========================================================= +void CFurniture :: Die ( void ) +{ + SetThink ( SUB_Remove ); + pev->nextthink = gpGlobals->time; +} + +//========================================================= +// This used to have something to do with bees flying, but +// now it only initializes moving furniture in scripted sequences +//========================================================= +void CFurniture :: Spawn( ) +{ + PRECACHE_MODEL((char *)STRING(pev->model)); + SET_MODEL(ENT(pev), STRING(pev->model)); + + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_BBOX; + pev->health = 80000; + pev->takedamage = DAMAGE_AIM; + pev->effects = 0; + pev->yaw_speed = 0; + pev->sequence = 0; + pev->frame = 0; + +// pev->nextthink += 1.0; +// SetThink (WalkMonsterDelay); + + ResetSequenceInfo( ); + pev->frame = 0; + MonsterInit(); +} + +//========================================================= +// ID's Furniture as neutral (noone will attack it) +//========================================================= +int CFurniture::Classify ( void ) +{ + return CLASS_NONE; +} + + diff --git a/dmc/dlls/scripted.h b/dmc/dlls/scripted.h new file mode 100644 index 0000000..dee12d7 --- /dev/null +++ b/dmc/dlls/scripted.h @@ -0,0 +1,107 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef SCRIPTED_H +#define SCRIPTED_H + +#ifndef SCRIPTEVENT_H +#include "scriptevent.h" +#endif + +#define SF_SCRIPT_WAITTILLSEEN 1 +#define SF_SCRIPT_EXITAGITATED 2 +#define SF_SCRIPT_REPEATABLE 4 +#define SF_SCRIPT_LEAVECORPSE 8 +//#define SF_SCRIPT_INTERPOLATE 16 // don't use, old bug +#define SF_SCRIPT_NOINTERRUPT 32 +#define SF_SCRIPT_OVERRIDESTATE 64 +#define SF_SCRIPT_NOSCRIPTMOVEMENT 128 + +#define SCRIPT_BREAK_CONDITIONS (bits_COND_LIGHT_DAMAGE|bits_COND_HEAVY_DAMAGE) + +enum SS_INTERRUPT +{ + SS_INTERRUPT_IDLE = 0, + SS_INTERRUPT_BY_NAME, + SS_INTERRUPT_AI, +}; + +// when a monster finishes an AI scripted sequence, we can choose +// a schedule to place them in. These defines are the aliases to +// resolve worldcraft input to real schedules (sjb) +#define SCRIPT_FINISHSCHED_DEFAULT 0 +#define SCRIPT_FINISHSCHED_AMBUSH 1 + +class CCineMonster : public CBaseMonster +{ +public: + void Spawn( void ); + virtual void KeyValue( KeyValueData *pkvd ); + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void Blocked( CBaseEntity *pOther ); + virtual void Touch( CBaseEntity *pOther ); + virtual int ObjectCaps( void ) { return (CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + virtual void Activate( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + // void EXPORT CineSpawnThink( void ); + void EXPORT CineThink( void ); + void Pain( void ); + void Die( void ); + void DelayStart( int state ); + BOOL FindEntity( void ); + virtual void PossessEntity( void ); + + void ReleaseEntity( CBaseMonster *pEntity ); + void CancelScript( void ); + virtual BOOL StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL completeOnEmpty ); + virtual BOOL FCanOverrideState ( void ); + void SequenceDone ( CBaseMonster *pMonster ); + virtual void FixScriptMonsterSchedule( CBaseMonster *pMonster ); + BOOL CanInterrupt( void ); + void AllowInterrupt( BOOL fAllow ); + int IgnoreConditions( void ); + + int m_iszIdle; // string index for idle animation + int m_iszPlay; // string index for scripted animation + int m_iszEntity; // entity that is wanted for this script + int m_fMoveTo; + int m_iFinishSchedule; + float m_flRadius; // range to search + float m_flRepeat; // repeat rate + + int m_iDelay; + float m_startTime; + + int m_saved_movetype; + int m_saved_solid; + int m_saved_effects; +// Vector m_vecOrigOrigin; + BOOL m_interruptable; +}; + +class CCineAI : public CCineMonster +{ + BOOL StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL completeOnEmpty ); + void PossessEntity( void ); + BOOL FCanOverrideState ( void ); + virtual void FixScriptMonsterSchedule( CBaseMonster *pMonster ); +}; + + +#endif //SCRIPTED_H diff --git a/dmc/dlls/scriptevent.h b/dmc/dlls/scriptevent.h new file mode 100644 index 0000000..42377cf --- /dev/null +++ b/dmc/dlls/scriptevent.h @@ -0,0 +1,29 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef SCRIPTEVENT_H +#define SCRIPTEVENT_H + +#define SCRIPT_EVENT_DEAD 1000 // character is now dead +#define SCRIPT_EVENT_NOINTERRUPT 1001 // does not allow interrupt +#define SCRIPT_EVENT_CANINTERRUPT 1002 // will allow interrupt +#define SCRIPT_EVENT_FIREEVENT 1003 // event now fires +#define SCRIPT_EVENT_SOUND 1004 // Play named wave file (on CHAN_BODY) +#define SCRIPT_EVENT_SENTENCE 1005 // Play named sentence +#define SCRIPT_EVENT_INAIR 1006 // Leave the character in air at the end of the sequence (don't find the floor) +#define SCRIPT_EVENT_ENDANIMATION 1007 // Set the animation by name after the sequence completes +#define SCRIPT_EVENT_SOUND_VOICE 1008 // Play named wave file (on CHAN_VOICE) +#define SCRIPT_EVENT_SENTENCE_RND1 1009 // Play sentence group 25% of the time +#define SCRIPT_EVENT_NOT_DEAD 1010 // Bring back to life (for life/death sequences) +#endif //SCRIPTEVENT_H diff --git a/dmc/dlls/singleplay_gamerules.cpp b/dmc/dlls/singleplay_gamerules.cpp new file mode 100644 index 0000000..947df3f --- /dev/null +++ b/dmc/dlls/singleplay_gamerules.cpp @@ -0,0 +1,336 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.cpp +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "skill.h" +#include "items.h" + +extern DLL_GLOBAL CGameRules *g_pGameRules; +extern DLL_GLOBAL BOOL g_fGameOver; +extern int gmsgDeathMsg; // client dll messages +extern int gmsgScoreInfo; +extern int gmsgMOTD; + +//========================================================= +//========================================================= +CHalfLifeRules::CHalfLifeRules( void ) +{ + RefreshSkillData(); +} + +//========================================================= +//========================================================= +void CHalfLifeRules::Think ( void ) +{ +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::IsMultiplayer( void ) +{ + return FALSE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::IsDeathmatch ( void ) +{ + return FALSE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::IsCoOp( void ) +{ + return FALSE; +} + + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ + if ( !pPlayer->m_pActiveItem ) + { + // player doesn't have an active item! + return TRUE; + } + + if ( !pPlayer->m_pActiveItem->CanHolster() ) + { + return FALSE; + } + + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) +{ + return FALSE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) +{ + return TRUE; +} + +void CHalfLifeRules :: InitHUD( CBasePlayer *pl ) +{ +} + +//========================================================= +//========================================================= +void CHalfLifeRules :: ClientDisconnected( edict_t *pClient ) +{ +} + +//========================================================= +//========================================================= +float CHalfLifeRules::FlPlayerFallDamage( CBasePlayer *pPlayer ) +{ + // subtract off the speed at which a player is allowed to fall without being hurt, + // so damage will be based on speed beyond that, not the entire fall + pPlayer->m_flFallVelocity -= PLAYER_MAX_SAFE_FALL_SPEED; + return pPlayer->m_flFallVelocity * DAMAGE_FOR_FALL_SPEED; +} + +//========================================================= +//========================================================= +void CHalfLifeRules :: PlayerSpawn( CBasePlayer *pPlayer ) +{ + // Start with init ammoload + pPlayer->m_iAmmoShells = 25; + + // Start with shotgun and axe + pPlayer->GiveNamedItem( "weapon_quakegun" ); + pPlayer->m_iQuakeItems |= (IT_SHOTGUN | IT_AXE); + pPlayer->m_iQuakeWeapon = pPlayer->W_BestWeapon(); + pPlayer->W_SetCurrentAmmo(); +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: AllowAutoTargetCrosshair( void ) +{ + return ( g_iSkillLevel == SKILL_EASY ); +} + +//========================================================= +//========================================================= +void CHalfLifeRules :: PlayerThink( CBasePlayer *pPlayer ) +{ +} + + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: FPlayerCanRespawn( CBasePlayer *pPlayer ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +float CHalfLifeRules :: FlPlayerSpawnTime( CBasePlayer *pPlayer ) +{ + return gpGlobals->time;//now! +} + +//========================================================= +// IPointsForKill - how many points awarded to anyone +// that kills this player? +//========================================================= +int CHalfLifeRules :: IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) +{ + return 1; +} + +//========================================================= +// PlayerKilled - someone/something killed this player +//========================================================= +void CHalfLifeRules :: PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) +{ +} + +//========================================================= +// Deathnotice +//========================================================= +void CHalfLifeRules::DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) +{ +} + +//========================================================= +// PlayerGotWeapon - player has grabbed a weapon that was +// sitting in the world +//========================================================= +void CHalfLifeRules :: PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ +} + +//========================================================= +// FlWeaponRespawnTime - what is the time in the future +// at which this weapon may spawn? +//========================================================= +float CHalfLifeRules :: FlWeaponRespawnTime( CBasePlayerItem *pWeapon ) +{ + return -1; +} + +//========================================================= +// FlWeaponRespawnTime - Returns 0 if the weapon can respawn +// now, otherwise it returns the time at which it can try +// to spawn again. +//========================================================= +float CHalfLifeRules :: FlWeaponTryRespawn( CBasePlayerItem *pWeapon ) +{ + return 0; +} + +//========================================================= +// VecWeaponRespawnSpot - where should this weapon spawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHalfLifeRules :: VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ) +{ + return pWeapon->pev->origin; +} + +//========================================================= +// WeaponShouldRespawn - any conditions inhibiting the +// respawning of this weapon? +//========================================================= +int CHalfLifeRules :: WeaponShouldRespawn( CBasePlayerItem *pWeapon ) +{ + return GR_WEAPON_RESPAWN_NO; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +void CHalfLifeRules::PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ) +{ +} + +//========================================================= +//========================================================= +int CHalfLifeRules::ItemShouldRespawn( CItem *pItem ) +{ + return GR_ITEM_RESPAWN_NO; +} + + +//========================================================= +// At what time in the future may this Item respawn? +//========================================================= +float CHalfLifeRules::FlItemRespawnTime( CItem *pItem ) +{ + return -1; +} + +//========================================================= +// Where should this item respawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHalfLifeRules::VecItemRespawnSpot( CItem *pItem ) +{ + return pItem->pev->origin; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::IsAllowedToSpawn( CBaseEntity *pEntity ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +void CHalfLifeRules::PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ) +{ +} + +//========================================================= +//========================================================= +int CHalfLifeRules::AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ) +{ + return GR_AMMO_RESPAWN_NO; +} + +//========================================================= +//========================================================= +float CHalfLifeRules::FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ) +{ + return -1; +} + +//========================================================= +//========================================================= +Vector CHalfLifeRules::VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ) +{ + return pAmmo->pev->origin; +} + +//========================================================= +//========================================================= +float CHalfLifeRules::FlHealthChargerRechargeTime( void ) +{ + return 0;// don't recharge +} + +//========================================================= +//========================================================= +int CHalfLifeRules::DeadPlayerWeapons( CBasePlayer *pPlayer ) +{ + return GR_PLR_DROP_GUN_NO; +} + +//========================================================= +//========================================================= +int CHalfLifeRules::DeadPlayerAmmo( CBasePlayer *pPlayer ) +{ + return GR_PLR_DROP_AMMO_NO; +} + +//========================================================= +//========================================================= +int CHalfLifeRules::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) +{ + // why would a single player in half life need this? + return GR_NOTTEAMMATE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: FAllowMonsters( void ) +{ + return TRUE; +} diff --git a/dmc/dlls/skill.cpp b/dmc/dlls/skill.cpp new file mode 100644 index 0000000..d82a607 --- /dev/null +++ b/dmc/dlls/skill.cpp @@ -0,0 +1,47 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// skill.cpp - code for skill level concerns +//========================================================= +#include "extdll.h" +#include "util.h" +#include "skill.h" + + +skilldata_t gSkillData; + + +//========================================================= +// take the name of a cvar, tack a digit for the skill level +// on, and return the value.of that Cvar +//========================================================= +float GetSkillCvar( char *pName ) +{ + int iCount; + float flValue; + char szBuffer[ 64 ]; + + iCount = sprintf( szBuffer, "%s%d",pName, gSkillData.iSkillLevel ); + + flValue = CVAR_GET_FLOAT ( szBuffer ); + + if ( flValue <= 0 ) + { + ALERT ( at_console, "\n\n** GetSkillCVar Got a zero for %s **\n\n", szBuffer ); + } + + return flValue; +} + diff --git a/dmc/dlls/skill.h b/dmc/dlls/skill.h new file mode 100644 index 0000000..5ec320c --- /dev/null +++ b/dmc/dlls/skill.h @@ -0,0 +1,147 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// skill.h - skill level concerns +//========================================================= + +struct skilldata_t +{ + + int iSkillLevel; // game skill level + +// Monster Health & Damage + float agruntHealth; + float agruntDmgPunch; + + float apacheHealth; + + float barneyHealth; + + float bigmommaHealthFactor; // Multiply each node's health by this + float bigmommaDmgSlash; // melee attack damage + float bigmommaDmgBlast; // mortar attack damage + float bigmommaRadiusBlast; // mortar attack radius + + float bullsquidHealth; + float bullsquidDmgBite; + float bullsquidDmgWhip; + float bullsquidDmgSpit; + + float gargantuaHealth; + float gargantuaDmgSlash; + float gargantuaDmgFire; + float gargantuaDmgStomp; + + float hassassinHealth; + + float headcrabHealth; + float headcrabDmgBite; + + float hgruntHealth; + float hgruntDmgKick; + float hgruntShotgunPellets; + float hgruntGrenadeSpeed; + + float houndeyeHealth; + float houndeyeDmgBlast; + + float slaveHealth; + float slaveDmgClaw; + float slaveDmgClawrake; + float slaveDmgZap; + + float ichthyosaurHealth; + float ichthyosaurDmgShake; + + float leechHealth; + float leechDmgBite; + + float controllerHealth; + float controllerDmgZap; + float controllerSpeedBall; + float controllerDmgBall; + + float nihilanthHealth; + float nihilanthZap; + + float scientistHealth; + + float snarkHealth; + float snarkDmgBite; + float snarkDmgPop; + + float zombieHealth; + float zombieDmgOneSlash; + float zombieDmgBothSlash; + + float turretHealth; + float miniturretHealth; + float sentryHealth; + + +// Player Weapons + float plrDmgCrowbar; + float plrDmg9MM; + float plrDmg357; + float plrDmgMP5; + float plrDmgM203Grenade; + float plrDmgBuckshot; + float plrDmgCrossbowClient; + float plrDmgCrossbowMonster; + float plrDmgRPG; + float plrDmgGauss; + float plrDmgEgonNarrow; + float plrDmgEgonWide; + float plrDmgHornet; + float plrDmgHandGrenade; + float plrDmgSatchel; + float plrDmgTripmine; + +// weapons shared by monsters + float monDmg9MM; + float monDmgMP5; + float monDmg12MM; + float monDmgHornet; + +// health/suit charge + float suitchargerCapacity; + float batteryCapacity; + float healthchargerCapacity; + float healthkitCapacity; + float scientistHeal; + +// monster damage adj + float monHead; + float monChest; + float monStomach; + float monLeg; + float monArm; + +// player damage adj + float plrHead; + float plrChest; + float plrStomach; + float plrLeg; + float plrArm; +}; + +extern DLL_GLOBAL skilldata_t gSkillData; +float GetSkillCvar( char *pName ); + +extern DLL_GLOBAL int g_iSkillLevel; + +#define SKILL_EASY 1 +#define SKILL_MEDIUM 2 +#define SKILL_HARD 3 diff --git a/dmc/dlls/sound.cpp b/dmc/dlls/sound.cpp new file mode 100644 index 0000000..5d2790e --- /dev/null +++ b/dmc/dlls/sound.cpp @@ -0,0 +1,1970 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// sound.cpp +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "player.h" +#include "gamerules.h" + +#if !defined ( _WIN32 ) +#include +#endif + +static char *memfgets( byte *pMemFile, int fileSize, int &filePos, char *pBuffer, int bufferSize ); + + +// ==================== GENERIC AMBIENT SOUND ====================================== + +// runtime pitch shift and volume fadein/out structure + +// NOTE: IF YOU CHANGE THIS STRUCT YOU MUST CHANGE THE SAVE/RESTORE VERSION NUMBER +// SEE BELOW (in the typedescription for the class) +typedef struct dynpitchvol +{ + // NOTE: do not change the order of these parameters + // NOTE: unless you also change order of rgdpvpreset array elements! + int preset; + + int pitchrun; // pitch shift % when sound is running 0 - 255 + int pitchstart; // pitch shift % when sound stops or starts 0 - 255 + int spinup; // spinup time 0 - 100 + int spindown; // spindown time 0 - 100 + + int volrun; // volume change % when sound is running 0 - 10 + int volstart; // volume change % when sound stops or starts 0 - 10 + int fadein; // volume fade in time 0 - 100 + int fadeout; // volume fade out time 0 - 100 + + // Low Frequency Oscillator + int lfotype; // 0) off 1) square 2) triangle 3) random + int lforate; // 0 - 1000, how fast lfo osciallates + + int lfomodpitch; // 0-100 mod of current pitch. 0 is off. + int lfomodvol; // 0-100 mod of current volume. 0 is off. + + int cspinup; // each trigger hit increments counter and spinup pitch + + + int cspincount; + + int pitch; + int spinupsav; + int spindownsav; + int pitchfrac; + + int vol; + int fadeinsav; + int fadeoutsav; + int volfrac; + + int lfofrac; + int lfomult; + + +} dynpitchvol_t; + +#define CDPVPRESETMAX 27 + +// presets for runtime pitch and vol modulation of ambient sounds + +dynpitchvol_t rgdpvpreset[CDPVPRESETMAX] = +{ +// pitch pstart spinup spindwn volrun volstrt fadein fadeout lfotype lforate modptch modvol cspnup +{1, 255, 75, 95, 95, 10, 1, 50, 95, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{2, 255, 85, 70, 88, 10, 1, 20, 88, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{3, 255, 100, 50, 75, 10, 1, 10, 75, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{4, 100, 100, 0, 0, 10, 1, 90, 90, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{5, 100, 100, 0, 0, 10, 1, 80, 80, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{6, 100, 100, 0, 0, 10, 1, 50, 70, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{7, 100, 100, 0, 0, 5, 1, 40, 50, 1, 50, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{8, 100, 100, 0, 0, 5, 1, 40, 50, 1, 150, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{9, 100, 100, 0, 0, 5, 1, 40, 50, 1, 750, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{10,128, 100, 50, 75, 10, 1, 30, 40, 2, 8, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{11,128, 100, 50, 75, 10, 1, 30, 40, 2, 25, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{12,128, 100, 50, 75, 10, 1, 30, 40, 2, 70, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{13,50, 50, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{14,70, 70, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{15,90, 90, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{16,120, 120, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{17,180, 180, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{18,255, 255, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{19,200, 75, 90, 90, 10, 1, 50, 90, 2, 100, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{20,255, 75, 97, 90, 10, 1, 50, 90, 1, 40, 50, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{21,100, 100, 0, 0, 10, 1, 30, 50, 3, 15, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{22,160, 160, 0, 0, 10, 1, 50, 50, 3, 500, 25, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{23,255, 75, 88, 0, 10, 1, 40, 0, 0, 0, 0, 0, 5, 0,0,0,0,0,0,0,0,0,0}, +{24,200, 20, 95, 70, 10, 1, 70, 70, 3, 20, 50, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{25,180, 100, 50, 60, 10, 1, 40, 60, 2, 90, 100, 100, 0, 0,0,0,0,0,0,0,0,0,0}, +{26,60, 60, 0, 0, 10, 1, 40, 70, 3, 80, 20, 50, 0, 0,0,0,0,0,0,0,0,0,0}, +{27,128, 90, 10, 10, 10, 1, 20, 40, 1, 5, 10, 20, 0, 0,0,0,0,0,0,0,0,0,0} +}; + +class CAmbientGeneric : public CBaseEntity +{ +public: + void KeyValue( KeyValueData* pkvd); + void Spawn( void ); + void Precache( void ); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT RampThink( void ); + void InitModulationParms(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + float m_flAttenuation; // attenuation value + dynpitchvol_t m_dpv; + + BOOL m_fActive; // only TRUE when the entity is playing a looping sound + BOOL m_fLooping; // TRUE when the sound played will loop +}; + +LINK_ENTITY_TO_CLASS( ambient_generic, CAmbientGeneric ); +TYPEDESCRIPTION CAmbientGeneric::m_SaveData[] = +{ + DEFINE_FIELD( CAmbientGeneric, m_flAttenuation, FIELD_FLOAT ), + DEFINE_FIELD( CAmbientGeneric, m_fActive, FIELD_BOOLEAN ), + DEFINE_FIELD( CAmbientGeneric, m_fLooping, FIELD_BOOLEAN ), + + // HACKHACK - This is not really in the spirit of the save/restore design, but save this + // out as a binary data block. If the dynpitchvol_t is changed, old saved games will NOT + // load these correctly, so bump the save/restore version if you change the size of the struct + // The right way to do this is to split the input parms (read in keyvalue) into members and re-init this + // struct in Precache(), but it's unlikely that the struct will change, so it's not worth the time right now. + DEFINE_ARRAY( CAmbientGeneric, m_dpv, FIELD_CHARACTER, sizeof(dynpitchvol_t) ), +}; + +IMPLEMENT_SAVERESTORE( CAmbientGeneric, CBaseEntity ); + +// +// ambient_generic - general-purpose user-defined static sound +// +void CAmbientGeneric :: Spawn( void ) +{ +/* + -1 : "Default" + 0 : "Everywhere" + 200 : "Small Radius" + 125 : "Medium Radius" + 80 : "Large Radius" +*/ + + if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_EVERYWHERE) ) + { + m_flAttenuation = ATTN_NONE; + } + else if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_SMALLRADIUS) ) + { + m_flAttenuation = ATTN_IDLE; + } + else if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_MEDIUMRADIUS) ) + { + m_flAttenuation = ATTN_STATIC; + } + else if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_LARGERADIUS) ) + { + m_flAttenuation = ATTN_NORM; + } + else + {// if the designer didn't set a sound attenuation, default to one. + m_flAttenuation = ATTN_STATIC; + } + + char* szSoundFile = (char*) STRING(pev->message); + + if ( FStringNull( pev->message ) || strlen( szSoundFile ) < 1 ) + { + ALERT( at_error, "EMPTY AMBIENT AT: %f, %f, %f\n", pev->origin.x, pev->origin.y, pev->origin.z ); + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CAmbientGeneric::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + // Set up think function for dynamic modification + // of ambient sound's pitch or volume. Don't + // start thinking yet. + + SetThink(&CAmbientGeneric::RampThink); + pev->nextthink = 0; + + // allow on/off switching via 'use' function. + + SetUse ( &CAmbientGeneric::ToggleUse ); + + m_fActive = FALSE; + + if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_NOT_LOOPING ) ) + m_fLooping = FALSE; + else + m_fLooping = TRUE; + Precache( ); +} + + +void CAmbientGeneric :: Precache( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + + if ( !FStringNull( pev->message ) && strlen( szSoundFile ) > 1 ) + { + if (*szSoundFile != '!') + PRECACHE_SOUND(szSoundFile); + } + // init all dynamic modulation parms + InitModulationParms(); + + if ( !FBitSet (pev->spawnflags, AMBIENT_SOUND_START_SILENT ) ) + { + // start the sound ASAP + if (m_fLooping) + m_fActive = TRUE; + } + if ( m_fActive ) + { + UTIL_EmitAmbientSound ( ENT(pev), pev->origin, szSoundFile, + (m_dpv.vol * 0.01), m_flAttenuation, SND_SPAWNING, m_dpv.pitch); + + pev->nextthink = gpGlobals->time + 0.1; + } +} + +// RampThink - Think at 5hz if we are dynamically modifying +// pitch or volume of the playing sound. This function will +// ramp pitch and/or volume up or down, modify pitch/volume +// with lfo if active. + +void CAmbientGeneric :: RampThink( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + int pitch = m_dpv.pitch; + int vol = m_dpv.vol; + int flags = 0; + int fChanged = 0; // FALSE if pitch and vol remain unchanged this round + int prev; + + if (!m_dpv.spinup && !m_dpv.spindown && !m_dpv.fadein && !m_dpv.fadeout && !m_dpv.lfotype) + return; // no ramps or lfo, stop thinking + + // ============== + // pitch envelope + // ============== + if (m_dpv.spinup || m_dpv.spindown) + { + prev = m_dpv.pitchfrac >> 8; + + if (m_dpv.spinup > 0) + m_dpv.pitchfrac += m_dpv.spinup; + else if (m_dpv.spindown > 0) + m_dpv.pitchfrac -= m_dpv.spindown; + + pitch = m_dpv.pitchfrac >> 8; + + if (pitch > m_dpv.pitchrun) + { + pitch = m_dpv.pitchrun; + m_dpv.spinup = 0; // done with ramp up + } + + if (pitch < m_dpv.pitchstart) + { + pitch = m_dpv.pitchstart; + m_dpv.spindown = 0; // done with ramp down + + // shut sound off + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + + // return without setting nextthink + return; + } + + if (pitch > 255) pitch = 255; + if (pitch < 1) pitch = 1; + + m_dpv.pitch = pitch; + + fChanged |= (prev != pitch); + flags |= SND_CHANGE_PITCH; + } + + // ================== + // amplitude envelope + // ================== + if (m_dpv.fadein || m_dpv.fadeout) + { + prev = m_dpv.volfrac >> 8; + + if (m_dpv.fadein > 0) + m_dpv.volfrac += m_dpv.fadein; + else if (m_dpv.fadeout > 0) + m_dpv.volfrac -= m_dpv.fadeout; + + vol = m_dpv.volfrac >> 8; + + if (vol > m_dpv.volrun) + { + vol = m_dpv.volrun; + m_dpv.fadein = 0; // done with ramp up + } + + if (vol < m_dpv.volstart) + { + vol = m_dpv.volstart; + m_dpv.fadeout = 0; // done with ramp down + + // shut sound off + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + + // return without setting nextthink + return; + } + + if (vol > 100) vol = 100; + if (vol < 1) vol = 1; + + m_dpv.vol = vol; + + fChanged |= (prev != vol); + flags |= SND_CHANGE_VOL; + } + + // =================== + // pitch/amplitude LFO + // =================== + if (m_dpv.lfotype) + { + int pos; + + if (m_dpv.lfofrac > 0x6fffffff) + m_dpv.lfofrac = 0; + + // update lfo, lfofrac/255 makes a triangle wave 0-255 + m_dpv.lfofrac += m_dpv.lforate; + pos = m_dpv.lfofrac >> 8; + + if (m_dpv.lfofrac < 0) + { + m_dpv.lfofrac = 0; + m_dpv.lforate = abs(m_dpv.lforate); + pos = 0; + } + else if (pos > 255) + { + pos = 255; + m_dpv.lfofrac = (255 << 8); + m_dpv.lforate = -abs(m_dpv.lforate); + } + + switch(m_dpv.lfotype) + { + case LFO_SQUARE: + if (pos < 128) + m_dpv.lfomult = 255; + else + m_dpv.lfomult = 0; + + break; + case LFO_RANDOM: + if (pos == 255) + m_dpv.lfomult = RANDOM_LONG(0, 255); + break; + case LFO_TRIANGLE: + default: + m_dpv.lfomult = pos; + break; + } + + if (m_dpv.lfomodpitch) + { + prev = pitch; + + // pitch 0-255 + pitch += ((m_dpv.lfomult - 128) * m_dpv.lfomodpitch) / 100; + + if (pitch > 255) pitch = 255; + if (pitch < 1) pitch = 1; + + + fChanged |= (prev != pitch); + flags |= SND_CHANGE_PITCH; + } + + if (m_dpv.lfomodvol) + { + // vol 0-100 + prev = vol; + + vol += ((m_dpv.lfomult - 128) * m_dpv.lfomodvol) / 100; + + if (vol > 100) vol = 100; + if (vol < 0) vol = 0; + + fChanged |= (prev != vol); + flags |= SND_CHANGE_VOL; + } + + } + + // Send update to playing sound only if we actually changed + // pitch or volume in this routine. + + if (flags && fChanged) + { + if (pitch == PITCH_NORM) + pitch = PITCH_NORM + 1; // don't send 'no pitch' ! + + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + (vol * 0.01), m_flAttenuation, flags, pitch); + } + + // update ramps at 5hz + pev->nextthink = gpGlobals->time + 0.2; + return; +} + +// Init all ramp params in preparation to +// play a new sound + +void CAmbientGeneric :: InitModulationParms(void) +{ + int pitchinc; + + m_dpv.volrun = pev->health * 10; // 0 - 100 + if (m_dpv.volrun > 100) m_dpv.volrun = 100; + if (m_dpv.volrun < 0) m_dpv.volrun = 0; + + // get presets + if (m_dpv.preset != 0 && m_dpv.preset <= CDPVPRESETMAX) + { + // load preset values + m_dpv = rgdpvpreset[m_dpv.preset - 1]; + + // fixup preset values, just like + // fixups in KeyValue routine. + if (m_dpv.spindown > 0) + m_dpv.spindown = (101 - m_dpv.spindown) * 64; + if (m_dpv.spinup > 0) + m_dpv.spinup = (101 - m_dpv.spinup) * 64; + + m_dpv.volstart *= 10; + m_dpv.volrun *= 10; + + if (m_dpv.fadein > 0) + m_dpv.fadein = (101 - m_dpv.fadein) * 64; + if (m_dpv.fadeout > 0) + m_dpv.fadeout = (101 - m_dpv.fadeout) * 64; + + m_dpv.lforate *= 256; + + m_dpv.fadeinsav = m_dpv.fadein; + m_dpv.fadeoutsav = m_dpv.fadeout; + m_dpv.spinupsav = m_dpv.spinup; + m_dpv.spindownsav = m_dpv.spindown; + } + + m_dpv.fadein = m_dpv.fadeinsav; + m_dpv.fadeout = 0; + + if (m_dpv.fadein) + m_dpv.vol = m_dpv.volstart; + else + m_dpv.vol = m_dpv.volrun; + + m_dpv.spinup = m_dpv.spinupsav; + m_dpv.spindown = 0; + + if (m_dpv.spinup) + m_dpv.pitch = m_dpv.pitchstart; + else + m_dpv.pitch = m_dpv.pitchrun; + + if (m_dpv.pitch == 0) + m_dpv.pitch = PITCH_NORM; + + m_dpv.pitchfrac = m_dpv.pitch << 8; + m_dpv.volfrac = m_dpv.vol << 8; + + m_dpv.lfofrac = 0; + m_dpv.lforate = abs(m_dpv.lforate); + + m_dpv.cspincount = 1; + + if (m_dpv.cspinup) + { + pitchinc = (255 - m_dpv.pitchstart) / m_dpv.cspinup; + + m_dpv.pitchrun = m_dpv.pitchstart + pitchinc; + if (m_dpv.pitchrun > 255) m_dpv.pitchrun = 255; + } + + if ((m_dpv.spinupsav || m_dpv.spindownsav || (m_dpv.lfotype && m_dpv.lfomodpitch)) + && (m_dpv.pitch == PITCH_NORM)) + m_dpv.pitch = PITCH_NORM + 1; // must never send 'no pitch' as first pitch + // if we intend to pitch shift later! +} + +// +// ToggleUse - turns an ambient sound on or off. If the +// ambient is a looping sound, mark sound as active (m_fActive) +// if it's playing, innactive if not. If the sound is not +// a looping sound, never mark it as active. +// +void CAmbientGeneric :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + char* szSoundFile = (char*) STRING(pev->message); + float fraction; + + if ( useType != USE_TOGGLE ) + { + if ( (m_fActive && useType == USE_ON) || (!m_fActive && useType == USE_OFF) ) + return; + } + // Directly change pitch if arg passed. Only works if sound is already playing. + + if (useType == USE_SET && m_fActive) // Momentary buttons will pass down a float in here + { + + fraction = value; + + if ( fraction > 1.0 ) + fraction = 1.0; + if (fraction < 0.0) + fraction = 0.01; + + m_dpv.pitch = fraction * 255; + + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_CHANGE_PITCH, m_dpv.pitch); + + return; + } + + // Toggle + + // m_fActive is TRUE only if a looping sound is playing. + + if ( m_fActive ) + {// turn sound off + + if (m_dpv.cspinup) + { + // Don't actually shut off. Each toggle causes + // incremental spinup to max pitch + + if (m_dpv.cspincount <= m_dpv.cspinup) + { + int pitchinc; + + // start a new spinup + m_dpv.cspincount++; + + pitchinc = (255 - m_dpv.pitchstart) / m_dpv.cspinup; + + m_dpv.spinup = m_dpv.spinupsav; + m_dpv.spindown = 0; + + m_dpv.pitchrun = m_dpv.pitchstart + pitchinc * m_dpv.cspincount; + if (m_dpv.pitchrun > 255) m_dpv.pitchrun = 255; + + pev->nextthink = gpGlobals->time + 0.1; + } + + } + else + { + m_fActive = FALSE; + + // HACKHACK - this makes the code in Precache() work properly after a save/restore + pev->spawnflags |= AMBIENT_SOUND_START_SILENT; + + if (m_dpv.spindownsav || m_dpv.fadeoutsav) + { + // spin it down (or fade it) before shutoff if spindown is set + m_dpv.spindown = m_dpv.spindownsav; + m_dpv.spinup = 0; + + m_dpv.fadeout = m_dpv.fadeoutsav; + m_dpv.fadein = 0; + pev->nextthink = gpGlobals->time + 0.1; + } + else + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + } + } + else + {// turn sound on + + // only toggle if this is a looping sound. If not looping, each + // trigger will cause the sound to play. If the sound is still + // playing from a previous trigger press, it will be shut off + // and then restarted. + + if (m_fLooping) + m_fActive = TRUE; + else + // shut sound off now - may be interrupting a long non-looping sound + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + + // init all ramp params for startup + + InitModulationParms(); + + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + (m_dpv.vol * 0.01), m_flAttenuation, 0, m_dpv.pitch); + + pev->nextthink = gpGlobals->time + 0.1; + + } +} +// KeyValue - load keyvalue pairs into member data of the +// ambient generic. NOTE: called BEFORE spawn! + +void CAmbientGeneric :: KeyValue( KeyValueData *pkvd ) +{ + // NOTE: changing any of the modifiers in this code + // NOTE: also requires changing InitModulationParms code. + + // preset + if (FStrEq(pkvd->szKeyName, "preset")) + { + m_dpv.preset = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + + // pitchrun + else if (FStrEq(pkvd->szKeyName, "pitch")) + { + m_dpv.pitchrun = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + if (m_dpv.pitchrun > 255) m_dpv.pitchrun = 255; + if (m_dpv.pitchrun < 0) m_dpv.pitchrun = 0; + } + + // pitchstart + else if (FStrEq(pkvd->szKeyName, "pitchstart")) + { + m_dpv.pitchstart = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + if (m_dpv.pitchstart > 255) m_dpv.pitchstart = 255; + if (m_dpv.pitchstart < 0) m_dpv.pitchstart = 0; + } + + // spinup + else if (FStrEq(pkvd->szKeyName, "spinup")) + { + m_dpv.spinup = atoi(pkvd->szValue); + + if (m_dpv.spinup > 100) m_dpv.spinup = 100; + if (m_dpv.spinup < 0) m_dpv.spinup = 0; + + if (m_dpv.spinup > 0) + m_dpv.spinup = (101 - m_dpv.spinup) * 64; + m_dpv.spinupsav = m_dpv.spinup; + pkvd->fHandled = TRUE; + } + + // spindown + else if (FStrEq(pkvd->szKeyName, "spindown")) + { + m_dpv.spindown = atoi(pkvd->szValue); + + if (m_dpv.spindown > 100) m_dpv.spindown = 100; + if (m_dpv.spindown < 0) m_dpv.spindown = 0; + + if (m_dpv.spindown > 0) + m_dpv.spindown = (101 - m_dpv.spindown) * 64; + m_dpv.spindownsav = m_dpv.spindown; + pkvd->fHandled = TRUE; + } + + // volstart + else if (FStrEq(pkvd->szKeyName, "volstart")) + { + m_dpv.volstart = atoi(pkvd->szValue); + + if (m_dpv.volstart > 10) m_dpv.volstart = 10; + if (m_dpv.volstart < 0) m_dpv.volstart = 0; + + m_dpv.volstart *= 10; // 0 - 100 + + pkvd->fHandled = TRUE; + } + + // fadein + else if (FStrEq(pkvd->szKeyName, "fadein")) + { + m_dpv.fadein = atoi(pkvd->szValue); + + if (m_dpv.fadein > 100) m_dpv.fadein = 100; + if (m_dpv.fadein < 0) m_dpv.fadein = 0; + + if (m_dpv.fadein > 0) + m_dpv.fadein = (101 - m_dpv.fadein) * 64; + m_dpv.fadeinsav = m_dpv.fadein; + pkvd->fHandled = TRUE; + } + + // fadeout + else if (FStrEq(pkvd->szKeyName, "fadeout")) + { + m_dpv.fadeout = atoi(pkvd->szValue); + + if (m_dpv.fadeout > 100) m_dpv.fadeout = 100; + if (m_dpv.fadeout < 0) m_dpv.fadeout = 0; + + if (m_dpv.fadeout > 0) + m_dpv.fadeout = (101 - m_dpv.fadeout) * 64; + m_dpv.fadeoutsav = m_dpv.fadeout; + pkvd->fHandled = TRUE; + } + + // lfotype + else if (FStrEq(pkvd->szKeyName, "lfotype")) + { + m_dpv.lfotype = atoi(pkvd->szValue); + if (m_dpv.lfotype > 4) m_dpv.lfotype = LFO_TRIANGLE; + pkvd->fHandled = TRUE; + } + + // lforate + else if (FStrEq(pkvd->szKeyName, "lforate")) + { + m_dpv.lforate = atoi(pkvd->szValue); + + if (m_dpv.lforate > 1000) m_dpv.lforate = 1000; + if (m_dpv.lforate < 0) m_dpv.lforate = 0; + + m_dpv.lforate *= 256; + + pkvd->fHandled = TRUE; + } + // lfomodpitch + else if (FStrEq(pkvd->szKeyName, "lfomodpitch")) + { + m_dpv.lfomodpitch = atoi(pkvd->szValue); + if (m_dpv.lfomodpitch > 100) m_dpv.lfomodpitch = 100; + if (m_dpv.lfomodpitch < 0) m_dpv.lfomodpitch = 0; + + + pkvd->fHandled = TRUE; + } + + // lfomodvol + else if (FStrEq(pkvd->szKeyName, "lfomodvol")) + { + m_dpv.lfomodvol = atoi(pkvd->szValue); + if (m_dpv.lfomodvol > 100) m_dpv.lfomodvol = 100; + if (m_dpv.lfomodvol < 0) m_dpv.lfomodvol = 0; + + pkvd->fHandled = TRUE; + } + + // cspinup + else if (FStrEq(pkvd->szKeyName, "cspinup")) + { + m_dpv.cspinup = atoi(pkvd->szValue); + if (m_dpv.cspinup > 100) m_dpv.cspinup = 100; + if (m_dpv.cspinup < 0) m_dpv.cspinup = 0; + + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +// =================== ROOM SOUND FX ========================================== + +class CEnvSound : public CPointEntity +{ +public: + void KeyValue( KeyValueData* pkvd); + void Spawn( void ); + + void Think( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + float m_flRadius; + float m_flRoomtype; +}; + +LINK_ENTITY_TO_CLASS( env_sound, CEnvSound ); +TYPEDESCRIPTION CEnvSound::m_SaveData[] = +{ + DEFINE_FIELD( CEnvSound, m_flRadius, FIELD_FLOAT ), + DEFINE_FIELD( CEnvSound, m_flRoomtype, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CEnvSound, CBaseEntity ); + + +void CEnvSound :: KeyValue( KeyValueData *pkvd ) +{ + + if (FStrEq(pkvd->szKeyName, "radius")) + { + m_flRadius = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + if (FStrEq(pkvd->szKeyName, "roomtype")) + { + m_flRoomtype = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } +} + +// returns TRUE if the given sound entity (pev) is in range +// and can see the given player entity (pevTarget) + +BOOL FEnvSoundInRange(entvars_t *pev, entvars_t *pevTarget, float *pflRange) +{ + CEnvSound *pSound = GetClassPtr( (CEnvSound *)pev ); + Vector vecSpot1 = pev->origin + pev->view_ofs; + Vector vecSpot2 = pevTarget->origin + pevTarget->view_ofs; + Vector vecRange; + float flRange; + TraceResult tr; + + UTIL_TraceLine(vecSpot1, vecSpot2, ignore_monsters, ENT(pev), &tr); + + // check if line of sight crosses water boundary, or is blocked + + if ((tr.fInOpen && tr.fInWater) || tr.flFraction != 1) + return FALSE; + + // calc range from sound entity to player + + vecRange = tr.vecEndPos - vecSpot1; + flRange = vecRange.Length(); + + if (pSound->m_flRadius < flRange) + return FALSE; + + if (pflRange) + *pflRange = flRange; + + return TRUE; +} + +// +// A client that is visible and in range of a sound entity will +// have its room_type set by that sound entity. If two or more +// sound entities are contending for a client, then the nearest +// sound entity to the client will set the client's room_type. +// A client's room_type will remain set to its prior value until +// a new in-range, visible sound entity resets a new room_type. +// + +// CONSIDER: if player in water state, autoset roomtype to 14,15 or 16. + +void CEnvSound :: Think( void ) +{ + // get pointer to client if visible; FIND_CLIENT_IN_PVS will + // cycle through visible clients on consecutive calls. + + edict_t *pentPlayer = FIND_CLIENT_IN_PVS(edict()); + CBasePlayer *pPlayer = NULL; + + if (FNullEnt(pentPlayer)) + goto env_sound_Think_slow; // no player in pvs of sound entity, slow it down + + pPlayer = GetClassPtr( (CBasePlayer *)VARS(pentPlayer)); + float flRange; + + // check to see if this is the sound entity that is + // currently affecting this player + + if(!FNullEnt(pPlayer->m_pentSndLast) && (pPlayer->m_pentSndLast == ENT(pev))) { + + // this is the entity currently affecting player, check + // for validity + + if (pPlayer->m_flSndRoomtype != 0 && pPlayer->m_flSndRange != 0) { + + // we're looking at a valid sound entity affecting + // player, make sure it's still valid, update range + + if (FEnvSoundInRange(pev, VARS(pentPlayer), &flRange)) { + pPlayer->m_flSndRange = flRange; + goto env_sound_Think_fast; + } else { + + // current sound entity affecting player is no longer valid, + // flag this state by clearing room_type and range. + // NOTE: we do not actually change the player's room_type + // NOTE: until we have a new valid room_type to change it to. + + pPlayer->m_flSndRange = 0; + pPlayer->m_flSndRoomtype = 0; + goto env_sound_Think_slow; + } + } else { + // entity is affecting player but is out of range, + // wait passively for another entity to usurp it... + goto env_sound_Think_slow; + } + } + + // if we got this far, we're looking at an entity that is contending + // for current player sound. the closest entity to player wins. + + if (FEnvSoundInRange(pev, VARS(pentPlayer), &flRange)) + { + if (flRange < pPlayer->m_flSndRange || pPlayer->m_flSndRange == 0) + { + // new entity is closer to player, so it wins. + pPlayer->m_pentSndLast = ENT(pev); + pPlayer->m_flSndRoomtype = m_flRoomtype; + pPlayer->m_flSndRange = flRange; + + // send room_type command to player's server. + // this should be a rare event - once per change of room_type + // only! + + //CLIENT_COMMAND(pentPlayer, "room_type %f", m_flRoomtype); + + MESSAGE_BEGIN( MSG_ONE, SVC_ROOMTYPE, NULL, pentPlayer ); // use the magic #1 for "one client" + WRITE_SHORT( (short)m_flRoomtype ); // sequence number + MESSAGE_END(); + + // crank up nextthink rate for new active sound entity + // by falling through to think_fast... + } + // player is not closer to the contending sound entity, + // just fall through to think_fast. this effectively + // cranks up the think_rate of entities near the player. + } + + // player is in pvs of sound entity, but either not visible or + // not in range. do nothing, fall through to think_fast... + +env_sound_Think_fast: + pev->nextthink = gpGlobals->time + 0.25; + return; + +env_sound_Think_slow: + pev->nextthink = gpGlobals->time + 0.75; + return; +} + +// +// env_sound - spawn a sound entity that will set player roomtype +// when player moves in range and sight. +// +// +void CEnvSound :: Spawn( ) +{ + // spread think times + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(0.0, 0.5); +} + +// ==================== SENTENCE GROUPS, UTILITY FUNCTIONS ====================================== + +#define CSENTENCE_LRU_MAX 32 // max number of elements per sentence group + +// group of related sentences + +typedef struct sentenceg +{ + char szgroupname[CBSENTENCENAME_MAX]; + int count; + unsigned char rgblru[CSENTENCE_LRU_MAX]; + +} SENTENCEG; + +#define CSENTENCEG_MAX 200 // max number of sentence groups +// globals + +SENTENCEG rgsentenceg[CSENTENCEG_MAX]; +int fSentencesInit = FALSE; + +char gszallsentencenames[CVOXFILESENTENCEMAX][CBSENTENCENAME_MAX]; +int gcallsentences = 0; + +// randomize list of sentence name indices + +void USENTENCEG_InitLRU(unsigned char *plru, int count) +{ + int i, j, k; + unsigned char temp; + + if (!fSentencesInit) + return; + + if (count > CSENTENCE_LRU_MAX) + count = CSENTENCE_LRU_MAX; + + for (i = 0; i < count; i++) + plru[i] = (unsigned char) i; + + // randomize array + for (i = 0; i < (count * 4); i++) + { + j = RANDOM_LONG(0,count-1); + k = RANDOM_LONG(0,count-1); + temp = plru[j]; + plru[j] = plru[k]; + plru[k] = temp; + } +} + +// ignore lru. pick next sentence from sentence group. Go in order until we hit the last sentence, +// then repeat list if freset is true. If freset is false, then repeat last sentence. +// ipick is passed in as the requested sentence ordinal. +// ipick 'next' is returned. +// return of -1 indicates an error. + +int USENTENCEG_PickSequential(int isentenceg, char *szfound, int ipick, int freset) +{ + char *szgroupname; + unsigned char count; + char sznum[8]; + + if (!fSentencesInit) + return -1; + + if (isentenceg < 0) + return -1; + + szgroupname = rgsentenceg[isentenceg].szgroupname; + count = rgsentenceg[isentenceg].count; + + if (count == 0) + return -1; + + if (ipick >= count) + ipick = count-1; + + strcpy(szfound, "!"); + strcat(szfound, szgroupname); + itoa(ipick, sznum, 10); + strcat(szfound, sznum); + + if (ipick >= count) + { + if (freset) + // reset at end of list + return 0; + else + return count; + } + + return ipick + 1; +} + + + +// pick a random sentence from rootname0 to rootnameX. +// picks from the rgsentenceg[isentenceg] least +// recently used, modifies lru array. returns the sentencename. +// note, lru must be seeded with 0-n randomized sentence numbers, with the +// rest of the lru filled with -1. The first integer in the lru is +// actually the size of the list. Returns ipick, the ordinal +// of the picked sentence within the group. + +int USENTENCEG_Pick(int isentenceg, char *szfound) +{ + char *szgroupname; + unsigned char *plru; + unsigned char i; + unsigned char count; + char sznum[8]; + unsigned char ipick; + int ffound = FALSE; + + if (!fSentencesInit) + return -1; + + if (isentenceg < 0) + return -1; + + szgroupname = rgsentenceg[isentenceg].szgroupname; + count = rgsentenceg[isentenceg].count; + plru = rgsentenceg[isentenceg].rgblru; + + while (!ffound) + { + for (i = 0; i < count; i++) + if (plru[i] != 0xFF) + { + ipick = plru[i]; + plru[i] = 0xFF; + ffound = TRUE; + break; + } + + if (!ffound) + USENTENCEG_InitLRU(plru, count); + else + { + strcpy(szfound, "!"); + strcat(szfound, szgroupname); + itoa(ipick, sznum, 10); + strcat(szfound, sznum); + return ipick; + } + } + return -1; +} + +// ===================== SENTENCE GROUPS, MAIN ROUTINES ======================== + +// Given sentence group rootname (name without number suffix), +// get sentence group index (isentenceg). Returns -1 if no such name. + +int SENTENCEG_GetIndex(const char *szgroupname) +{ + int i; + + if (!fSentencesInit || !szgroupname) + return -1; + + // search rgsentenceg for match on szgroupname + + i = 0; + while (rgsentenceg[i].count) + { + if (!strcmp(szgroupname, rgsentenceg[i].szgroupname)) + return i; + i++; + } + + return -1; +} + +// given sentence group index, play random sentence for given entity. +// returns ipick - which sentence was picked to +// play from the group. Ipick is only needed if you plan on stopping +// the sound before playback is done (see SENTENCEG_Stop). + +int SENTENCEG_PlayRndI(edict_t *entity, int isentenceg, + float volume, float attenuation, int flags, int pitch) +{ + char name[64]; + int ipick; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + ipick = USENTENCEG_Pick(isentenceg, name); + if (ipick > 0 && name) + EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch); + return ipick; +} + +// same as above, but takes sentence group name instead of index + +int SENTENCEG_PlayRndSz(edict_t *entity, const char *szgroupname, + float volume, float attenuation, int flags, int pitch) +{ + char name[64]; + int ipick; + int isentenceg; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + isentenceg = SENTENCEG_GetIndex(szgroupname); + if (isentenceg < 0) + { + ALERT( at_console, "No such sentence group %s\n", szgroupname ); + return -1; + } + + ipick = USENTENCEG_Pick(isentenceg, name); + if (ipick >= 0 && name[0]) + EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch); + + return ipick; +} + +// play sentences in sequential order from sentence group. Reset after last sentence. + +int SENTENCEG_PlaySequentialSz(edict_t *entity, const char *szgroupname, + float volume, float attenuation, int flags, int pitch, int ipick, int freset) +{ + char name[64]; + int ipicknext; + int isentenceg; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + isentenceg = SENTENCEG_GetIndex(szgroupname); + if (isentenceg < 0) + return -1; + + ipicknext = USENTENCEG_PickSequential(isentenceg, name, ipick, freset); + if (ipicknext >= 0 && name[0]) + EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch); + return ipicknext; +} + + +// for this entity, for the given sentence within the sentence group, stop +// the sentence. + +void SENTENCEG_Stop(edict_t *entity, int isentenceg, int ipick) +{ + char buffer[64]; + char sznum[8]; + + if (!fSentencesInit) + return; + + if (isentenceg < 0 || ipick < 0) + return; + + strcpy(buffer, "!"); + strcat(buffer, rgsentenceg[isentenceg].szgroupname); + itoa(ipick, sznum, 10); + strcat(buffer, sznum); + + STOP_SOUND(entity, CHAN_VOICE, buffer); +} + +// open sentences.txt, scan for groups, build rgsentenceg +// Should be called from world spawn, only works on the +// first call and is ignored subsequently. + +void SENTENCEG_Init() +{ + char buffer[512]; + char szgroup[64]; + int i, j; + int isentencegs; + + if (fSentencesInit) + return; + + memset(gszallsentencenames, 0, CVOXFILESENTENCEMAX * CBSENTENCENAME_MAX); + gcallsentences = 0; + + memset(rgsentenceg, 0, CSENTENCEG_MAX * sizeof(SENTENCEG)); + memset(buffer, 0, 512); + memset(szgroup, 0, 64); + isentencegs = -1; + + + int filePos = 0, fileSize; + byte *pMemFile = g_engfuncs.pfnLoadFileForMe( "sound/sentences.txt", &fileSize ); + if ( !pMemFile ) + return; + + // for each line in the file... + while ( memfgets(pMemFile, fileSize, filePos, buffer, 511) != NULL ) + { + // skip whitespace + i = 0; + while(buffer[i] && buffer[i] == ' ') + i++; + + if (!buffer[i]) + continue; + + if (buffer[i] == '/' || !isalpha(buffer[i])) + continue; + + // get sentence name + j = i; + while (buffer[j] && buffer[j] != ' ') + j++; + + if (!buffer[j]) + continue; + + if (gcallsentences > CVOXFILESENTENCEMAX) + { + ALERT (at_error, "Too many sentences in sentences.txt!\n"); + break; + } + + // null-terminate name and save in sentences array + buffer[j] = 0; + const char *pString = buffer + i; + + if ( strlen( pString ) >= CBSENTENCENAME_MAX ) + ALERT( at_warning, "Sentence %s longer than %d letters\n", pString, CBSENTENCENAME_MAX-1 ); + + strcpy( gszallsentencenames[gcallsentences++], pString ); + + j--; + if (j <= i) + continue; + if (!isdigit(buffer[j])) + continue; + + // cut out suffix numbers + while (j > i && isdigit(buffer[j])) + j--; + + if (j <= i) + continue; + + buffer[j+1] = 0; + + // if new name doesn't match previous group name, + // make a new group. + + if (strcmp(szgroup, &(buffer[i]))) + { + // name doesn't match with prev name, + // copy name into group, init count to 1 + isentencegs++; + if (isentencegs >= CSENTENCEG_MAX) + { + ALERT (at_error, "Too many sentence groups in sentences.txt!\n"); + break; + } + + strcpy(rgsentenceg[isentencegs].szgroupname, &(buffer[i])); + rgsentenceg[isentencegs].count = 1; + + strcpy(szgroup, &(buffer[i])); + + continue; + } + else + { + //name matches with previous, increment group count + if (isentencegs >= 0) + rgsentenceg[isentencegs].count++; + } + } + + g_engfuncs.pfnFreeFile( pMemFile ); + + fSentencesInit = TRUE; + + // init lru lists + + i = 0; + + while (rgsentenceg[i].count && i < CSENTENCEG_MAX) + { + USENTENCEG_InitLRU(&(rgsentenceg[i].rgblru[0]), rgsentenceg[i].count); + i++; + } + +} + +// convert sentence (sample) name to !sentencenum, return !sentencenum + +int SENTENCEG_Lookup(const char *sample, char *sentencenum) +{ + char sznum[8]; + int i; + + // this is a sentence name; lookup sentence number + // and give to engine as string. + for (i = 0; i < gcallsentences; i++) + if (!stricmp(gszallsentencenames[i], sample+1)) + { + if (sentencenum) + { + strcpy(sentencenum, "!"); + itoa(i, sznum, 10); + strcat(sentencenum, sznum); + } + return i; + } + // sentence name not found! + return -1; +} + +void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volume, float attenuation, + int flags, int pitch) +{ + if (sample && *sample == '!') + { + char name[32]; + if (SENTENCEG_Lookup(sample, name) >= 0) + EMIT_SOUND_DYN2(entity, channel, name, volume, attenuation, flags, pitch); + else + ALERT( at_aiconsole, "Unable to find %s in sentences.txt\n", sample ); + } + else + EMIT_SOUND_DYN2(entity, channel, sample, volume, attenuation, flags, pitch); +} + +// play a specific sentence over the HEV suit speaker - just pass player entity, and !sentencename + +void EMIT_SOUND_SUIT(edict_t *entity, const char *sample) +{ + float fvol; + int pitch = PITCH_NORM; + + fvol = CVAR_GET_FLOAT("suitvolume"); + if (RANDOM_LONG(0,1)) + pitch = RANDOM_LONG(0,6) + 98; + + if (fvol > 0.05) + EMIT_SOUND_DYN(entity, CHAN_STATIC, sample, fvol, ATTN_NORM, 0, pitch); +} + +// play a sentence, randomly selected from the passed in group id, over the HEV suit speaker + +void EMIT_GROUPID_SUIT(edict_t *entity, int isentenceg) +{ + float fvol; + int pitch = PITCH_NORM; + + fvol = CVAR_GET_FLOAT("suitvolume"); + if (RANDOM_LONG(0,1)) + pitch = RANDOM_LONG(0,6) + 98; + + if (fvol > 0.05) + SENTENCEG_PlayRndI(entity, isentenceg, fvol, ATTN_NORM, 0, pitch); +} + +// play a sentence, randomly selected from the passed in groupname + +void EMIT_GROUPNAME_SUIT(edict_t *entity, const char *groupname) +{ + float fvol; + int pitch = PITCH_NORM; + + fvol = CVAR_GET_FLOAT("suitvolume"); + if (RANDOM_LONG(0,1)) + pitch = RANDOM_LONG(0,6) + 98; + + if (fvol > 0.05) + SENTENCEG_PlayRndSz(entity, groupname, fvol, ATTN_NORM, 0, pitch); +} + +// ===================== MATERIAL TYPE DETECTION, MAIN ROUTINES ======================== +// +// Used to detect the texture the player is standing on, map the +// texture name to a material type. Play footstep sound based +// on material type. + +int fTextureTypeInit = FALSE; + +#define CTEXTURESMAX 512 // max number of textures loaded + +int gcTextures = 0; +char grgszTextureName[CTEXTURESMAX][CBTEXTURENAMEMAX]; // texture names +char grgchTextureType[CTEXTURESMAX]; // parallel array of texture types + +// open materials.txt, get size, alloc space, +// save in array. Only works first time called, +// ignored on subsequent calls. + +static char *memfgets( byte *pMemFile, int fileSize, int &filePos, char *pBuffer, int bufferSize ) +{ + // Bullet-proofing + if ( !pMemFile || !pBuffer ) + return NULL; + + if ( filePos >= fileSize ) + return NULL; + + int i = filePos; + int last = fileSize; + + // fgets always NULL terminates, so only read bufferSize-1 characters + if ( last - filePos > (bufferSize-1) ) + last = filePos + (bufferSize-1); + + int stop = 0; + + // Stop at the next newline (inclusive) or end of buffer + while ( i < last && !stop ) + { + if ( pMemFile[i] == '\n' ) + stop = 1; + i++; + } + + + // If we actually advanced the pointer, copy it over + if ( i != filePos ) + { + // We read in size bytes + int size = i - filePos; + // copy it out + memcpy( pBuffer, pMemFile + filePos, sizeof(byte)*size ); + + // If the buffer isn't full, terminate (this is always true) + if ( size < bufferSize ) + pBuffer[size] = 0; + + // Update file pointer + filePos = i; + return pBuffer; + } + + // No data read, bail + return NULL; +} + + +void TEXTURETYPE_Init() +{ + char buffer[512]; + int i, j; + byte *pMemFile; + int fileSize, filePos; + + if (fTextureTypeInit) + return; + + memset(&(grgszTextureName[0][0]), 0, CTEXTURESMAX * CBTEXTURENAMEMAX); + memset(grgchTextureType, 0, CTEXTURESMAX); + + gcTextures = 0; + memset(buffer, 0, 512); + + pMemFile = g_engfuncs.pfnLoadFileForMe( "sound/materials.txt", &fileSize ); + if ( !pMemFile ) + return; + + // for each line in the file... + while (memfgets(pMemFile, fileSize, filePos, buffer, 511) != NULL && (gcTextures < CTEXTURESMAX)) + { + // skip whitespace + i = 0; + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // skip comment lines + if (buffer[i] == '/' || !isalpha(buffer[i])) + continue; + + // get texture type + grgchTextureType[gcTextures] = toupper(buffer[i++]); + + // skip whitespace + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // get sentence name + j = i; + while (buffer[j] && !isspace(buffer[j])) + j++; + + if (!buffer[j]) + continue; + + // null-terminate name and save in sentences array + j = min (j, CBTEXTURENAMEMAX-1+i); + buffer[j] = 0; + strcpy(&(grgszTextureName[gcTextures++][0]), &(buffer[i])); + } + + g_engfuncs.pfnFreeFile( pMemFile ); + + fTextureTypeInit = TRUE; +} + +// given texture name, find texture type +// if not found, return type 'concrete' + +// NOTE: this routine should ONLY be called if the +// current texture under the player changes! + +char TEXTURETYPE_Find(char *name) +{ + // CONSIDER: pre-sort texture names and perform faster binary search here + + for (int i = 0; i < gcTextures; i++) + { + if (!_strnicmp(name, &(grgszTextureName[i][0]), CBTEXTURENAMEMAX-1)) + return (grgchTextureType[i]); + } + + return CHAR_TEX_CONCRETE; +} + +// play a strike sound based on the texture that was hit by the attack traceline. VecSrc/VecEnd are the +// original traceline endpoints used by the attacker, iBulletType is the type of bullet that hit the texture. +// returns volume of strike instrument (crowbar) to play + +float TEXTURETYPE_PlaySound(TraceResult *ptr, Vector vecSrc, Vector vecEnd, int iBulletType) +{ +// hit the world, try to play sound based on texture material type + + char chTextureType; + float fvol; + float fvolbar; + char szbuffer[64]; + const char *pTextureName; + float rgfl1[3]; + float rgfl2[3]; + char *rgsz[4]; + int cnt; + float fattn = ATTN_NORM; + + if ( !g_pGameRules->PlayTextureSounds() ) + return 0.0; + + CBaseEntity *pEntity = CBaseEntity::Instance(ptr->pHit); + + chTextureType = 0; + + if (pEntity && pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE) + // hit body + chTextureType = CHAR_TEX_FLESH; + else + { + // hit world + + // find texture under strike, get material type + + // copy trace vector into array for trace_texture + + vecSrc.CopyToArray(rgfl1); + vecEnd.CopyToArray(rgfl2); + + // get texture from entity or world (world is ent(0)) + if (pEntity) + pTextureName = TRACE_TEXTURE( ENT(pEntity->pev), rgfl1, rgfl2 ); + else + pTextureName = TRACE_TEXTURE( ENT(0), rgfl1, rgfl2 ); + + if ( pTextureName ) + { + // strip leading '-0' or '+0~' or '{' or '!' + if (*pTextureName == '-' || *pTextureName == '+') + pTextureName += 2; + + if (*pTextureName == '{' || *pTextureName == '!' || *pTextureName == '~' || *pTextureName == ' ') + pTextureName++; + // '}}' + strcpy(szbuffer, pTextureName); + szbuffer[CBTEXTURENAMEMAX - 1] = 0; + + // ALERT ( at_console, "texture hit: %s\n", szbuffer); + + // get texture type + chTextureType = TEXTURETYPE_Find(szbuffer); + } + } + + switch (chTextureType) + { + default: + case CHAR_TEX_CONCRETE: fvol = 0.9; fvolbar = 0.6; + rgsz[0] = "player/pl_step1.wav"; + rgsz[1] = "player/pl_step2.wav"; + cnt = 2; + break; + case CHAR_TEX_METAL: fvol = 0.9; fvolbar = 0.3; + rgsz[0] = "player/pl_metal1.wav"; + rgsz[1] = "player/pl_metal2.wav"; + cnt = 2; + break; + case CHAR_TEX_DIRT: fvol = 0.9; fvolbar = 0.1; + rgsz[0] = "player/pl_dirt1.wav"; + rgsz[1] = "player/pl_dirt2.wav"; + rgsz[2] = "player/pl_dirt3.wav"; + cnt = 3; + break; + case CHAR_TEX_VENT: fvol = 0.5; fvolbar = 0.3; + rgsz[0] = "player/pl_duct1.wav"; + rgsz[1] = "player/pl_duct1.wav"; + cnt = 2; + break; + case CHAR_TEX_GRATE: fvol = 0.9; fvolbar = 0.5; + rgsz[0] = "player/pl_grate1.wav"; + rgsz[1] = "player/pl_grate4.wav"; + cnt = 2; + break; + case CHAR_TEX_TILE: fvol = 0.8; fvolbar = 0.2; + rgsz[0] = "player/pl_tile1.wav"; + rgsz[1] = "player/pl_tile3.wav"; + rgsz[2] = "player/pl_tile2.wav"; + rgsz[3] = "player/pl_tile4.wav"; + cnt = 4; + break; + case CHAR_TEX_SLOSH: fvol = 0.9; fvolbar = 0.0; + rgsz[0] = "player/pl_slosh1.wav"; + rgsz[1] = "player/pl_slosh3.wav"; + rgsz[2] = "player/pl_slosh2.wav"; + rgsz[3] = "player/pl_slosh4.wav"; + cnt = 4; + break; + case CHAR_TEX_WOOD: fvol = 0.9; fvolbar = 0.2; + rgsz[0] = "debris/wood1.wav"; + rgsz[1] = "debris/wood2.wav"; + rgsz[2] = "debris/wood3.wav"; + cnt = 3; + break; + case CHAR_TEX_GLASS: + case CHAR_TEX_COMPUTER: + fvol = 0.8; fvolbar = 0.2; + rgsz[0] = "debris/glass1.wav"; + rgsz[1] = "debris/glass2.wav"; + rgsz[2] = "debris/glass3.wav"; + cnt = 3; + break; + case CHAR_TEX_FLESH: + if (iBulletType == BULLET_PLAYER_CROWBAR) + return 0.0; // crowbar already makes this sound + fvol = 1.0; fvolbar = 0.2; + rgsz[0] = "weapons/bullet_hit1.wav"; + rgsz[1] = "weapons/bullet_hit2.wav"; + fattn = 1.0; + cnt = 2; + break; + } + + // did we hit a breakable? + + if (pEntity && FClassnameIs(pEntity->pev, "func_breakable")) + { + // drop volumes, the object will already play a damaged sound + fvol /= 1.5; + fvolbar /= 2.0; + } + else if (chTextureType == CHAR_TEX_COMPUTER) + { + // play random spark if computer + + if ( ptr->flFraction != 1.0 && RANDOM_LONG(0,1)) + { + UTIL_Sparks( ptr->vecEndPos ); + + float flVolume = RANDOM_FLOAT ( 0.7 , 1.0 );//random volume range + switch ( RANDOM_LONG(0,1) ) + { + case 0: UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, "buttons/spark5.wav", flVolume, ATTN_NORM, 0, 100); break; + case 1: UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, "buttons/spark6.wav", flVolume, ATTN_NORM, 0, 100); break; + // case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM); break; + // case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM); break; + } + } + } + + // play material hit sound + UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, rgsz[RANDOM_LONG(0,cnt-1)], fvol, fattn, 0, 96 + RANDOM_LONG(0,0xf)); + //EMIT_SOUND_DYN( ENT(m_pPlayer->pev), CHAN_WEAPON, rgsz[RANDOM_LONG(0,cnt-1)], fvol, ATTN_NORM, 0, 96 + RANDOM_LONG(0,0xf)); + + return fvolbar; +} + +// =================================================================================== +// +// Speaker class. Used for announcements per level, for door lock/unlock spoken voice. +// + +class CSpeaker : public CBaseEntity +{ +public: + void KeyValue( KeyValueData* pkvd); + void Spawn( void ); + void Precache( void ); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT SpeakerThink( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + int m_preset; // preset number +}; + +LINK_ENTITY_TO_CLASS( speaker, CSpeaker ); +TYPEDESCRIPTION CSpeaker::m_SaveData[] = +{ + DEFINE_FIELD( CSpeaker, m_preset, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CSpeaker, CBaseEntity ); + +// +// ambient_generic - general-purpose user-defined static sound +// +void CSpeaker :: Spawn( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + + if ( !m_preset && (FStringNull( pev->message ) || strlen( szSoundFile ) < 1 )) + { + ALERT( at_error, "SPEAKER with no Level/Sentence! at: %f, %f, %f\n", pev->origin.x, pev->origin.y, pev->origin.z ); + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CSpeaker::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + + SetThink(&CSpeaker::SpeakerThink); + pev->nextthink = 0.0; + + // allow on/off switching via 'use' function. + + SetUse ( &CSpeaker::ToggleUse ); + + Precache( ); +} + +#define ANNOUNCE_MINUTES_MIN 0.25 +#define ANNOUNCE_MINUTES_MAX 2.25 + +void CSpeaker :: Precache( void ) +{ + if ( !FBitSet (pev->spawnflags, SPEAKER_START_SILENT ) ) + // set first announcement time for random n second + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(5.0, 15.0); +} +void CSpeaker :: SpeakerThink( void ) +{ + char* szSoundFile; + float flvolume = pev->health * 0.1; + float flattenuation = 0.3; + int flags = 0; + int pitch = 100; + + if (m_preset) + { + // go lookup preset text, assign szSoundFile + switch (m_preset) + { + case 1: szSoundFile = "C1A0_"; break; + case 2: szSoundFile = "C1A1_"; break; + case 3: szSoundFile = "C1A2_"; break; + case 4: szSoundFile = "C1A3_"; break; + case 5: szSoundFile = "C1A4_"; break; + case 6: szSoundFile = "C2A1_"; break; + case 7: szSoundFile = "C2A2_"; break; + case 8: szSoundFile = "C2A3_"; break; + case 9: szSoundFile = "C2A4_"; break; + case 10: szSoundFile = "C2A5_"; break; + case 11: szSoundFile = "C3A1_"; break; + case 12: szSoundFile = "C3A2_"; break; + } + } else + szSoundFile = (char*) STRING(pev->message); + + if (szSoundFile[0] == '!') + { + // play single sentence, one shot + UTIL_EmitAmbientSound ( ENT(pev), pev->origin, szSoundFile, + flvolume, flattenuation, flags, pitch); + + // shut off and reset + pev->nextthink = 0.0; + } + else + { + // make random announcement from sentence group + + if (SENTENCEG_PlayRndSz(ENT(pev), szSoundFile, flvolume, flattenuation, flags, pitch) < 0) + ALERT(at_console, "Level Design Error!\nSPEAKER has bad sentence group name: %s\n",szSoundFile); + + // set next announcement time for random 5 to 10 minute delay + pev->nextthink = gpGlobals->time + + RANDOM_FLOAT(ANNOUNCE_MINUTES_MIN * 60.0, ANNOUNCE_MINUTES_MAX * 60.0); + } + + return; +} + + +// +// ToggleUse - if an announcement is pending, cancel it. If no announcement is pending, start one. +// +void CSpeaker :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int fActive = (pev->nextthink > 0.0); + + // fActive is TRUE only if an announcement is pending + + if ( useType != USE_TOGGLE ) + { + // ignore if we're just turning something on that's already on, or + // turning something off that's already off. + if ( (fActive && useType == USE_ON) || (!fActive && useType == USE_OFF) ) + return; + } + + if ( useType == USE_ON ) + { + // turn on announcements + pev->nextthink = gpGlobals->time + 0.1; + return; + } + + if ( useType == USE_OFF ) + { + // turn off announcements + pev->nextthink = 0.0; + return; + + } + + // Toggle announcements + + + if ( fActive ) + { + // turn off announcements + pev->nextthink = 0.0; + } + else + { + // turn on announcements + pev->nextthink = gpGlobals->time + 0.1; + } +} + +// KeyValue - load keyvalue pairs into member data +// NOTE: called BEFORE spawn! + +void CSpeaker :: KeyValue( KeyValueData *pkvd ) +{ + + // preset + if (FStrEq(pkvd->szKeyName, "preset")) + { + m_preset = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} diff --git a/dmc/dlls/soundent.cpp b/dmc/dlls/soundent.cpp new file mode 100644 index 0000000..ffd5786 --- /dev/null +++ b/dmc/dlls/soundent.cpp @@ -0,0 +1,379 @@ +/*** +* +* Copyright (c) 1996-2002 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "soundent.h" + + +LINK_ENTITY_TO_CLASS( soundent, CSoundEnt ); + +CSoundEnt *pSoundEnt; + +//========================================================= +// CSound - Clear - zeros all fields for a sound +//========================================================= +void CSound :: Clear ( void ) +{ + m_vecOrigin = g_vecZero; + m_iType = 0; + m_iVolume = 0; + m_flExpireTime = 0; + m_iNext = SOUNDLIST_EMPTY; + m_iNextAudible = 0; +} + +//========================================================= +// Reset - clears the volume, origin, and type for a sound, +// but doesn't expire or unlink it. +//========================================================= +void CSound :: Reset ( void ) +{ + m_vecOrigin = g_vecZero; + m_iType = 0; + m_iVolume = 0; + m_iNext = SOUNDLIST_EMPTY; +} + +//========================================================= +// FIsSound - returns TRUE if the sound is an Audible sound +//========================================================= +BOOL CSound :: FIsSound ( void ) +{ + if ( m_iType & ( bits_SOUND_COMBAT | bits_SOUND_WORLD | bits_SOUND_PLAYER | bits_SOUND_DANGER ) ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// FIsScent - returns TRUE if the sound is actually a scent +//========================================================= +BOOL CSound :: FIsScent ( void ) +{ + if ( m_iType & ( bits_SOUND_CARCASS | bits_SOUND_MEAT | bits_SOUND_GARBAGE ) ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// Spawn +//========================================================= +void CSoundEnt :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + Initialize(); + + pev->nextthink = gpGlobals->time + 1; +} + +//========================================================= +// Think - at interval, the entire active sound list is checked +// for sounds that have ExpireTimes less than or equal +// to the current world time, and these sounds are deallocated. +//========================================================= +void CSoundEnt :: Think ( void ) +{ + int iSound; + int iPreviousSound; + + pev->nextthink = gpGlobals->time + 0.3;// how often to check the sound list. + + iPreviousSound = SOUNDLIST_EMPTY; + iSound = m_iActiveSound; + + while ( iSound != SOUNDLIST_EMPTY ) + { + if ( m_SoundPool[ iSound ].m_flExpireTime <= gpGlobals->time && m_SoundPool[ iSound ].m_flExpireTime != SOUND_NEVER_EXPIRE ) + { + int iNext = m_SoundPool[ iSound ].m_iNext; + + // move this sound back into the free list + FreeSound( iSound, iPreviousSound ); + + iSound = iNext; + } + else + { + iPreviousSound = iSound; + iSound = m_SoundPool[ iSound ].m_iNext; + } + } + + if ( m_fShowReport ) + { + ALERT ( at_aiconsole, "Soundlist: %d / %d (%d)\n", ISoundsInList( SOUNDLISTTYPE_ACTIVE ),ISoundsInList( SOUNDLISTTYPE_FREE ), ISoundsInList( SOUNDLISTTYPE_ACTIVE ) - m_cLastActiveSounds ); + m_cLastActiveSounds = ISoundsInList ( SOUNDLISTTYPE_ACTIVE ); + } + +} + +//========================================================= +// Precache - dummy function +//========================================================= +void CSoundEnt :: Precache ( void ) +{ +} + +//========================================================= +// FreeSound - clears the passed active sound and moves it +// to the top of the free list. TAKE CARE to only call this +// function for sounds in the Active list!! +//========================================================= +void CSoundEnt :: FreeSound ( int iSound, int iPrevious ) +{ + if ( !pSoundEnt ) + { + // no sound ent! + return; + } + + if ( iPrevious != SOUNDLIST_EMPTY ) + { + // iSound is not the head of the active list, so + // must fix the index for the Previous sound +// pSoundEnt->m_SoundPool[ iPrevious ].m_iNext = m_SoundPool[ iSound ].m_iNext; + pSoundEnt->m_SoundPool[ iPrevious ].m_iNext = pSoundEnt->m_SoundPool[ iSound ].m_iNext; + } + else + { + // the sound we're freeing IS the head of the active list. + pSoundEnt->m_iActiveSound = pSoundEnt->m_SoundPool [ iSound ].m_iNext; + } + + // make iSound the head of the Free list. + pSoundEnt->m_SoundPool[ iSound ].m_iNext = pSoundEnt->m_iFreeSound; + pSoundEnt->m_iFreeSound = iSound; +} + +//========================================================= +// IAllocSound - moves a sound from the Free list to the +// Active list returns the index of the alloc'd sound +//========================================================= +int CSoundEnt :: IAllocSound( void ) +{ + int iNewSound; + + if ( m_iFreeSound == SOUNDLIST_EMPTY ) + { + // no free sound! + ALERT ( at_console, "Free Sound List is full!\n" ); + return SOUNDLIST_EMPTY; + } + + // there is at least one sound available, so move it to the + // Active sound list, and return its SoundPool index. + + iNewSound = m_iFreeSound;// copy the index of the next free sound + + m_iFreeSound = m_SoundPool[ m_iFreeSound ].m_iNext;// move the index down into the free list. + + m_SoundPool[ iNewSound ].m_iNext = m_iActiveSound;// point the new sound at the top of the active list. + + m_iActiveSound = iNewSound;// now make the new sound the top of the active list. You're done. + + return iNewSound; +} + +//========================================================= +// InsertSound - Allocates a free sound and fills it with +// sound info. +//========================================================= +void CSoundEnt :: InsertSound ( int iType, const Vector &vecOrigin, int iVolume, float flDuration ) +{ + int iThisSound; + + if ( !pSoundEnt ) + { + // no sound ent! + return; + } + + iThisSound = pSoundEnt->IAllocSound(); + + if ( iThisSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_console, "Could not AllocSound() for InsertSound() (DLL)\n" ); + return; + } + + pSoundEnt->m_SoundPool[ iThisSound ].m_vecOrigin = vecOrigin; + pSoundEnt->m_SoundPool[ iThisSound ].m_iType = iType; + pSoundEnt->m_SoundPool[ iThisSound ].m_iVolume = iVolume; + pSoundEnt->m_SoundPool[ iThisSound ].m_flExpireTime = gpGlobals->time + flDuration; +} + +//========================================================= +// Initialize - clears all sounds and moves them into the +// free sound list. +//========================================================= +void CSoundEnt :: Initialize ( void ) +{ + int i; + int iSound; + + m_cLastActiveSounds; + m_iFreeSound = 0; + m_iActiveSound = SOUNDLIST_EMPTY; + + for ( i = 0 ; i < MAX_WORLD_SOUNDS ; i++ ) + {// clear all sounds, and link them into the free sound list. + m_SoundPool[ i ].Clear(); + m_SoundPool[ i ].m_iNext = i + 1; + } + + m_SoundPool[ i - 1 ].m_iNext = SOUNDLIST_EMPTY;// terminate the list here. + + + // now reserve enough sounds for each client + for ( i = 0 ; i < gpGlobals->maxClients ; i++ ) + { + iSound = pSoundEnt->IAllocSound(); + + if ( iSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_console, "Could not AllocSound() for Client Reserve! (DLL)\n" ); + return; + } + + pSoundEnt->m_SoundPool[ iSound ].m_flExpireTime = SOUND_NEVER_EXPIRE; + } + + if ( CVAR_GET_FLOAT("displaysoundlist") == 1 ) + { + m_fShowReport = TRUE; + } + else + { + m_fShowReport = FALSE; + } +} + +//========================================================= +// ISoundsInList - returns the number of sounds in the desired +// sound list. +//========================================================= +int CSoundEnt :: ISoundsInList ( int iListType ) +{ + int i; + int iThisSound; + + if ( iListType == SOUNDLISTTYPE_FREE ) + { + iThisSound = m_iFreeSound; + } + else if ( iListType == SOUNDLISTTYPE_ACTIVE ) + { + iThisSound = m_iActiveSound; + } + else + { + ALERT ( at_console, "Unknown Sound List Type!\n" ); + } + + if ( iThisSound == SOUNDLIST_EMPTY ) + { + return 0; + } + + i = 0; + + while ( iThisSound != SOUNDLIST_EMPTY ) + { + i++; + + iThisSound = m_SoundPool[ iThisSound ].m_iNext; + } + + return i; +} + +//========================================================= +// ActiveList - returns the head of the active sound list +//========================================================= +int CSoundEnt :: ActiveList ( void ) +{ + if ( !pSoundEnt ) + { + return SOUNDLIST_EMPTY; + } + + return pSoundEnt->m_iActiveSound; +} + +//========================================================= +// FreeList - returns the head of the free sound list +//========================================================= +int CSoundEnt :: FreeList ( void ) +{ + if ( !pSoundEnt ) + { + return SOUNDLIST_EMPTY; + } + + return pSoundEnt->m_iFreeSound; +} + +//========================================================= +// SoundPointerForIndex - returns a pointer to the instance +// of CSound at index's position in the sound pool. +//========================================================= +CSound* CSoundEnt :: SoundPointerForIndex( int iIndex ) +{ + if ( !pSoundEnt ) + { + return NULL; + } + + if ( iIndex > ( MAX_WORLD_SOUNDS - 1 ) ) + { + ALERT ( at_console, "SoundPointerForIndex() - Index too large!\n" ); + return NULL; + } + + if ( iIndex < 0 ) + { + ALERT ( at_console, "SoundPointerForIndex() - Index < 0!\n" ); + return NULL; + } + + return &pSoundEnt->m_SoundPool[ iIndex ]; +} + +//========================================================= +// Clients are numbered from 1 to MAXCLIENTS, but the client +// reserved sounds in the soundlist are from 0 to MAXCLIENTS - 1, +// so this function ensures that a client gets the proper index +// to his reserved sound in the soundlist. +//========================================================= +int CSoundEnt :: ClientSoundIndex ( edict_t *pClient ) +{ + int iReturn = ENTINDEX( pClient ) - 1; + +#ifdef _DEBUG + if ( iReturn < 0 || iReturn > gpGlobals->maxClients ) + { + ALERT ( at_console, "** ClientSoundIndex returning a bogus value! **\n" ); + } +#endif // _DEBUG + + return iReturn; +} \ No newline at end of file diff --git a/dmc/dlls/soundent.h b/dmc/dlls/soundent.h new file mode 100644 index 0000000..150daac --- /dev/null +++ b/dmc/dlls/soundent.h @@ -0,0 +1,95 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// Soundent.h - the entity that spawns when the world +// spawns, and handles the world's active and free sound +// lists. +//========================================================= + +#define MAX_WORLD_SOUNDS 64 // maximum number of sounds handled by the world at one time. + +#define bits_SOUND_NONE 0 +#define bits_SOUND_COMBAT ( 1 << 0 )// gunshots, explosions +#define bits_SOUND_WORLD ( 1 << 1 )// door opening/closing, glass breaking +#define bits_SOUND_PLAYER ( 1 << 2 )// all noises generated by player. walking, shooting, falling, splashing +#define bits_SOUND_CARCASS ( 1 << 3 )// dead body +#define bits_SOUND_MEAT ( 1 << 4 )// gib or pork chop +#define bits_SOUND_DANGER ( 1 << 5 )// pending danger. Grenade that is about to explode, explosive barrel that is damaged, falling crate +#define bits_SOUND_GARBAGE ( 1 << 6 )// trash cans, banana peels, old fast food bags. + +#define bits_ALL_SOUNDS 0xFFFFFFFF + +#define SOUNDLIST_EMPTY -1 + +#define SOUNDLISTTYPE_FREE 1// identifiers passed to functions that can operate on either list, to indicate which list to operate on. +#define SOUNDLISTTYPE_ACTIVE 2 + +#define SOUND_NEVER_EXPIRE -1 // with this set as a sound's ExpireTime, the sound will never expire. + +//========================================================= +// CSound - an instance of a sound in the world. +//========================================================= +class CSound +{ +public: + + void Clear ( void ); + void Reset ( void ); + + Vector m_vecOrigin; // sound's location in space + int m_iType; // what type of sound this is + int m_iVolume; // how loud the sound is + float m_flExpireTime; // when the sound should be purged from the list + int m_iNext; // index of next sound in this list ( Active or Free ) + int m_iNextAudible; // temporary link that monsters use to build a list of audible sounds + + BOOL FIsSound( void ); + BOOL FIsScent( void ); +}; + +//========================================================= +// CSoundEnt - a single instance of this entity spawns when +// the world spawns. The SoundEnt's job is to update the +// world's Free and Active sound lists. +//========================================================= +class CSoundEnt : public CBaseEntity +{ +public: + + void Precache ( void ); + void Spawn( void ); + void Think( void ); + void Initialize ( void ); + + static void InsertSound ( int iType, const Vector &vecOrigin, int iVolume, float flDuration ); + static void FreeSound ( int iSound, int iPrevious ); + static int ActiveList( void );// return the head of the active list + static int FreeList( void );// return the head of the free list + static CSound* SoundPointerForIndex( int iIndex );// return a pointer for this index in the sound list + static int ClientSoundIndex ( edict_t *pClient ); + + BOOL IsEmpty( void ) { return m_iActiveSound == SOUNDLIST_EMPTY; } + int ISoundsInList ( int iListType ); + int IAllocSound ( void ); + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } + + int m_iFreeSound; // index of the first sound in the free sound list + int m_iActiveSound; // indes of the first sound in the active sound list + int m_cLastActiveSounds; // keeps track of the number of active sounds at the last update. (for diagnostic work) + BOOL m_fShowReport; // if true, dump information about free/active sounds. + +private: + CSound m_SoundPool[ MAX_WORLD_SOUNDS ]; +}; diff --git a/dmc/dlls/spectator.cpp b/dmc/dlls/spectator.cpp new file mode 100644 index 0000000..5f91731 --- /dev/null +++ b/dmc/dlls/spectator.cpp @@ -0,0 +1,149 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// CBaseSpectator + +// YWB: UNDONE + +// Spectator functions +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "spectator.h" + +/* +=========== +SpectatorConnect + +called when a spectator connects to a server +============ +*/ +void CBaseSpectator::SpectatorConnect(void) +{ + pev->flags = FL_SPECTATOR; + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NOCLIP; + + m_pGoalEnt = NULL; +} + +/* +=========== +SpectatorDisconnect + +called when a spectator disconnects from a server +============ +*/ +void CBaseSpectator::SpectatorDisconnect(void) +{ +} + +/* +================ +SpectatorImpulseCommand + +Called by SpectatorThink if the spectator entered an impulse +================ +*/ +void CBaseSpectator::SpectatorImpulseCommand(void) +{ + static edict_t *pGoal = NULL; + edict_t *pPreviousGoal; + edict_t *pCurrentGoal; + BOOL bFound; + + switch (pev->impulse) + { + case 1: + // teleport the spectator to the next spawn point + // note that if the spectator is tracking, this doesn't do + // much + pPreviousGoal = pGoal; + pCurrentGoal = pGoal; + // Start at the current goal, skip the world, and stop if we looped + // back around + + bFound = FALSE; + while (1) + { + pCurrentGoal = FIND_ENTITY_BY_CLASSNAME(pCurrentGoal, "info_player_deathmatch"); + // Looped around, failure + if (pCurrentGoal == pPreviousGoal) + { + ALERT(at_console, "Could not find a spawn spot.\n"); + break; + } + // Found a non-world entity, set success, otherwise, look for the next one. + if (!FNullEnt(pCurrentGoal)) + { + bFound = TRUE; + break; + } + } + + if (!bFound) // Didn't find a good spot. + break; + + pGoal = pCurrentGoal; + UTIL_SetOrigin( pev, pGoal->v.origin ); + pev->angles = pGoal->v.angles; + pev->fixangle = FALSE; + break; + default: + ALERT(at_console, "Unknown spectator impulse\n"); + break; + } + + pev->impulse = 0; +} + +/* +================ +SpectatorThink + +Called every frame after physics are run +================ +*/ +void CBaseSpectator::SpectatorThink(void) +{ + if (!(pev->flags & FL_SPECTATOR)) + { + pev->flags = FL_SPECTATOR; + } + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NOCLIP; + + if (pev->impulse) + SpectatorImpulseCommand(); +} + +/* +=========== +Spawn + + Called when spectator is initialized: + UNDONE: Is this actually being called because spectators are not allocated in normal fashion? +============ +*/ +void CBaseSpectator::Spawn() +{ + pev->flags = FL_SPECTATOR; + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NOCLIP; + + m_pGoalEnt = NULL; +} diff --git a/dmc/dlls/spectator.h b/dmc/dlls/spectator.h new file mode 100644 index 0000000..2f755d6 --- /dev/null +++ b/dmc/dlls/spectator.h @@ -0,0 +1,27 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Spectator.h + +class CBaseSpectator : public CBaseEntity +{ +public: + void Spawn(); + void SpectatorConnect(void); + void SpectatorDisconnect(void); + void SpectatorThink(void); + +private: + void SpectatorImpulseCommand(void); +}; diff --git a/dmc/dlls/subs.cpp b/dmc/dlls/subs.cpp new file mode 100644 index 0000000..d3560bd --- /dev/null +++ b/dmc/dlls/subs.cpp @@ -0,0 +1,559 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== subs.cpp ======================================================== + + frequently used global functions + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "nodes.h" +#include "doors.h" + +extern CGraph WorldGraph; + +extern BOOL FEntIsVisible(entvars_t* pev, entvars_t* pevTarget); + +extern DLL_GLOBAL int g_iSkillLevel; + + +// Landmark class +void CPointEntity :: Spawn( void ) +{ + pev->solid = SOLID_NOT; +// UTIL_SetSize(pev, g_vecZero, g_vecZero); +} + + +class CNullEntity : public CBaseEntity +{ +public: + void Spawn( void ); +}; + + +// Null Entity, remove on startup +void CNullEntity :: Spawn( void ) +{ + REMOVE_ENTITY(ENT(pev)); +} +LINK_ENTITY_TO_CLASS(info_null,CNullEntity); + +class CBaseDMStart : public CPointEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + BOOL IsTriggered( CBaseEntity *pEntity ); + +private: +}; + +// These are the new entry points to entities. +LINK_ENTITY_TO_CLASS(info_player_deathmatch,CBaseDMStart); +LINK_ENTITY_TO_CLASS(info_player_start,CPointEntity); +LINK_ENTITY_TO_CLASS(info_landmark,CPointEntity); + +void CBaseDMStart::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "master")) + { + pev->netname = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +BOOL CBaseDMStart::IsTriggered( CBaseEntity *pEntity ) +{ + BOOL master = UTIL_IsMasterTriggered( pev->netname, pEntity ); + + return master; +} + +// This updates global tables that need to know about entities being removed +void CBaseEntity::UpdateOnRemove( void ) +{ + int i; + + if ( FBitSet( pev->flags, FL_GRAPHED ) ) + { + // this entity was a LinkEnt in the world node graph, so we must remove it from + // the graph since we are removing it from the world. + for ( i = 0 ; i < WorldGraph.m_cLinks ; i++ ) + { + if ( WorldGraph.m_pLinkPool [ i ].m_pLinkEnt == pev ) + { + // if this link has a link ent which is the same ent that is removing itself, remove it! + WorldGraph.m_pLinkPool [ i ].m_pLinkEnt = NULL; + } + } + } + if ( pev->globalname ) + gGlobalState.EntitySetState( pev->globalname, GLOBAL_DEAD ); +} + +// Convenient way to delay removing oneself +void CBaseEntity :: SUB_Remove( void ) +{ + UpdateOnRemove(); + if (pev->health > 0) + { + // this situation can screw up monsters who can't tell their entity pointers are invalid. + pev->health = 0; + ALERT( at_aiconsole, "SUB_Remove called on entity with health > 0\n"); + } + + REMOVE_ENTITY(ENT(pev)); +} + + +// Convenient way to explicitly do nothing (passed to functions that require a method) +void CBaseEntity :: SUB_DoNothing( void ) +{ +} + + +// Global Savedata for Delay +TYPEDESCRIPTION CBaseDelay::m_SaveData[] = +{ + DEFINE_FIELD( CBaseDelay, m_flDelay, FIELD_FLOAT ), + DEFINE_FIELD( CBaseDelay, m_iszKillTarget, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CBaseDelay, CBaseEntity ); + +void CBaseDelay :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "delay")) + { + m_flDelay = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "killtarget")) + { + m_iszKillTarget = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + { + CBaseEntity::KeyValue( pkvd ); + } +} + + +/* +============================== +SUB_UseTargets + +If self.delay is set, a DelayedUse entity will be created that will actually +do the SUB_UseTargets after that many seconds have passed. + +Removes all entities with a targetname that match self.killtarget, +and removes them, so some events can remove other triggers. + +Search for (string)targetname in all entities that +match (string)self.target and call their .use function (if they have one) + +============================== +*/ +void CBaseEntity :: SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ) +{ + // + // fire targets + // + if (!FStringNull(pev->target)) + { + FireTargets( STRING(pev->target), pActivator, this, useType, value ); + } +} + + +void FireTargets( const char *targetName, CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + edict_t *pentTarget = NULL; + if ( !targetName ) + return; + + ALERT( at_aiconsole, "Firing: (%s)\n", targetName ); + + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, targetName); + if (FNullEnt(pentTarget)) + break; + + CBaseEntity *pTarget = CBaseEntity::Instance( pentTarget ); + if ( pTarget && !(pTarget->pev->flags & FL_KILLME) ) // Don't use dying ents + { + ALERT( at_aiconsole, "Found: %s, firing (%s)\n", STRING(pTarget->pev->classname), targetName ); + pTarget->Use( pActivator, pCaller, useType, value ); + } + } +} + +LINK_ENTITY_TO_CLASS( DelayedUse, CBaseDelay ); + + +void CBaseDelay :: SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ) +{ + // + // exit immediatly if we don't have a target or kill target + // + if (FStringNull(pev->target) && !m_iszKillTarget) + return; + + // + // check for a delay + // + if (m_flDelay != 0) + { + // create a temp object to fire at a later time + CBaseDelay *pTemp = GetClassPtr( (CBaseDelay *)NULL); + pTemp->pev->classname = MAKE_STRING("DelayedUse"); + + pTemp->pev->nextthink = gpGlobals->time + m_flDelay; + + pTemp->SetThink( &CBaseDelay::DelayThink ); + + // Save the useType + pTemp->pev->button = (int)useType; + pTemp->m_iszKillTarget = m_iszKillTarget; + pTemp->m_flDelay = 0; // prevent "recursion" + pTemp->pev->target = pev->target; + + // HACKHACK + // This wasn't in the release build of Half-Life. We should have moved m_hActivator into this class + // but changing member variable hierarchy would break save/restore without some ugly code. + // This code is not as ugly as that code + if ( pActivator && pActivator->IsPlayer() ) // If a player activates, then save it + { + pTemp->pev->owner = pActivator->edict(); + } + else + { + pTemp->pev->owner = NULL; + } + + return; + } + + // + // kill the killtargets + // + + if ( m_iszKillTarget ) + { + edict_t *pentKillTarget = NULL; + + ALERT( at_aiconsole, "KillTarget: %s\n", STRING(m_iszKillTarget) ); + pentKillTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_iszKillTarget) ); + while ( !FNullEnt(pentKillTarget) ) + { + UTIL_Remove( CBaseEntity::Instance(pentKillTarget) ); + + ALERT( at_aiconsole, "killing %s\n", STRING( pentKillTarget->v.classname ) ); + pentKillTarget = FIND_ENTITY_BY_TARGETNAME( pentKillTarget, STRING(m_iszKillTarget) ); + } + } + + // + // fire targets + // + if (!FStringNull(pev->target)) + { + FireTargets( STRING(pev->target), pActivator, this, useType, value ); + } +} + + +/* +void CBaseDelay :: SUB_UseTargetsEntMethod( void ) +{ + SUB_UseTargets(pev); +} +*/ + +/* +QuakeEd only writes a single float for angles (bad idea), so up and down are +just constant angles. +*/ +void SetMovedir( entvars_t *pev ) +{ + if (pev->angles == Vector(0, -1, 0)) + { + pev->movedir = Vector(0, 0, 1); + } + else if (pev->angles == Vector(0, -2, 0)) + { + pev->movedir = Vector(0, 0, -1); + } + else + { + UTIL_MakeVectors(pev->angles); + pev->movedir = gpGlobals->v_forward; + } + + pev->angles = g_vecZero; +} + + + + +void CBaseDelay::DelayThink( void ) +{ + CBaseEntity *pActivator = NULL; + + if ( pev->owner != NULL ) // A player activated this on delay + { + pActivator = CBaseEntity::Instance( pev->owner ); + } + // The use type is cached (and stashed) in pev->button + SUB_UseTargets( pActivator, (USE_TYPE)pev->button, 0 ); + REMOVE_ENTITY(ENT(pev)); +} + + +// Global Savedata for Toggle +TYPEDESCRIPTION CBaseToggle::m_SaveData[] = +{ + DEFINE_FIELD( CBaseToggle, m_toggle_state, FIELD_INTEGER ), + DEFINE_FIELD( CBaseToggle, m_flActivateFinished, FIELD_TIME ), + DEFINE_FIELD( CBaseToggle, m_flMoveDistance, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flWait, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flLip, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flTWidth, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flTLength, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_vecPosition1, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_vecPosition2, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_vecAngle1, FIELD_VECTOR ), // UNDONE: Position could go through transition, but also angle? + DEFINE_FIELD( CBaseToggle, m_vecAngle2, FIELD_VECTOR ), // UNDONE: Position could go through transition, but also angle? + DEFINE_FIELD( CBaseToggle, m_cTriggersLeft, FIELD_INTEGER ), + DEFINE_FIELD( CBaseToggle, m_flHeight, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_hActivator, FIELD_EHANDLE ), + DEFINE_FIELD( CBaseToggle, m_pfnCallWhenMoveDone, FIELD_FUNCTION ), + DEFINE_FIELD( CBaseToggle, m_vecFinalDest, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_vecFinalAngle, FIELD_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_sMaster, FIELD_STRING), + DEFINE_FIELD( CBaseToggle, m_bitsDamageInflict, FIELD_INTEGER ), // damage type inflicted +}; +IMPLEMENT_SAVERESTORE( CBaseToggle, CBaseAnimating ); + + +void CBaseToggle::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "lip")) + { + m_flLip = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "master")) + { + m_sMaster = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "distance")) + { + m_flMoveDistance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + +/* +============= +LinearMove + +calculate pev->velocity and pev->nextthink to reach vecDest from +pev->origin traveling at flSpeed +=============== +*/ +void CBaseToggle :: LinearMove( Vector vecDest, float flSpeed ) +{ + ASSERTSZ(flSpeed != 0, "LinearMove: no speed is defined!"); +// ASSERTSZ(m_pfnCallWhenMoveDone != NULL, "LinearMove: no post-move function defined"); + + m_vecFinalDest = vecDest; + + // Already there? + if (vecDest == pev->origin) + { + LinearMoveDone(); + return; + } + + // set destdelta to the vector needed to move + Vector vecDestDelta = vecDest - pev->origin; + + // divide vector length by speed to get time to reach dest + float flTravelTime = vecDestDelta.Length() / flSpeed; + + // set nextthink to trigger a call to LinearMoveDone when dest is reached + pev->nextthink = pev->ltime + flTravelTime; + SetThink( &CBaseToggle::LinearMoveDone ); + + // scale the destdelta vector by the time spent traveling to get velocity + pev->velocity = vecDestDelta / flTravelTime; +} + + +/* +============ +After moving, set origin to exact final destination, call "move done" function +============ +*/ +void CBaseToggle :: LinearMoveDone( void ) +{ + UTIL_SetOrigin(pev, m_vecFinalDest); + pev->velocity = g_vecZero; + pev->nextthink = -1; + if ( m_pfnCallWhenMoveDone ) + (this->*m_pfnCallWhenMoveDone)(); +} + +BOOL CBaseToggle :: IsLockedByMaster( void ) +{ + if (m_sMaster && !UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + return TRUE; + else + return FALSE; +} + +/* +============= +AngularMove + +calculate pev->velocity and pev->nextthink to reach vecDest from +pev->origin traveling at flSpeed +Just like LinearMove, but rotational. +=============== +*/ +void CBaseToggle :: AngularMove( Vector vecDestAngle, float flSpeed ) +{ + ASSERTSZ(flSpeed != 0, "AngularMove: no speed is defined!"); +// ASSERTSZ(m_pfnCallWhenMoveDone != NULL, "AngularMove: no post-move function defined"); + + m_vecFinalAngle = vecDestAngle; + + // Already there? + if (vecDestAngle == pev->angles) + { + AngularMoveDone(); + return; + } + + // set destdelta to the vector needed to move + Vector vecDestDelta = vecDestAngle - pev->angles; + + // divide by speed to get time to reach dest + float flTravelTime = vecDestDelta.Length() / flSpeed; + + // set nextthink to trigger a call to AngularMoveDone when dest is reached + pev->nextthink = pev->ltime + flTravelTime; + SetThink( &CBaseToggle::AngularMoveDone ); + + // scale the destdelta vector by the time spent traveling to get velocity + pev->avelocity = vecDestDelta / flTravelTime; +} + + +/* +============ +After rotating, set angle to exact final angle, call "move done" function +============ +*/ +void CBaseToggle :: AngularMoveDone( void ) +{ + pev->angles = m_vecFinalAngle; + pev->avelocity = g_vecZero; + pev->nextthink = -1; + if ( m_pfnCallWhenMoveDone ) + (this->*m_pfnCallWhenMoveDone)(); +} + + +float CBaseToggle :: AxisValue( int flags, const Vector &angles ) +{ + if ( FBitSet(flags, SF_DOOR_ROTATE_Z) ) + return angles.z; + if ( FBitSet(flags, SF_DOOR_ROTATE_X) ) + return angles.x; + + return angles.y; +} + + +void CBaseToggle :: AxisDir( entvars_t *pev ) +{ + if ( FBitSet(pev->spawnflags, SF_DOOR_ROTATE_Z) ) + pev->movedir = Vector ( 0, 0, 1 ); // around z-axis + else if ( FBitSet(pev->spawnflags, SF_DOOR_ROTATE_X) ) + pev->movedir = Vector ( 1, 0, 0 ); // around x-axis + else + pev->movedir = Vector ( 0, 1, 0 ); // around y-axis +} + + +float CBaseToggle :: AxisDelta( int flags, const Vector &angle1, const Vector &angle2 ) +{ + if ( FBitSet (flags, SF_DOOR_ROTATE_Z) ) + return angle1.z - angle2.z; + + if ( FBitSet (flags, SF_DOOR_ROTATE_X) ) + return angle1.x - angle2.x; + + return angle1.y - angle2.y; +} + + +/* +============= +FEntIsVisible + +returns TRUE if the passed entity is visible to caller, even if not infront () +============= +*/ + BOOL +FEntIsVisible( + entvars_t* pev, + entvars_t* pevTarget) + { + Vector vecSpot1 = pev->origin + pev->view_ofs; + Vector vecSpot2 = pevTarget->origin + pevTarget->view_ofs; + TraceResult tr; + + UTIL_TraceLine(vecSpot1, vecSpot2, ignore_monsters, ENT(pev), &tr); + + if (tr.fInOpen && tr.fInWater) + return FALSE; // sight line crossed contents + + if (tr.flFraction == 1) + return TRUE; + + return FALSE; + } + + diff --git a/dmc/dlls/teamplay_gamerules.cpp b/dmc/dlls/teamplay_gamerules.cpp new file mode 100644 index 0000000..292d707 --- /dev/null +++ b/dmc/dlls/teamplay_gamerules.cpp @@ -0,0 +1,618 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.cpp +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "teamplay_gamerules.h" +#include "game.h" + + +/*class CDMCGameMgrHelper : public IVoiceGameMgrHelper +{ +public: + virtual bool ComparePlayerTeams(CBasePlayer *pPlayer1, CBasePlayer *pPlayer2) + { + return true; + } +}; +static CDMCGameMgrHelper g_GameMgrHelper;*/ + + +static char team_names[MAX_TEAMS][MAX_TEAMNAME_LENGTH]; +static int team_scores[MAX_TEAMS]; +static int num_teams = 0; + +extern DLL_GLOBAL BOOL g_fGameOver; + +CHalfLifeTeamplay :: CHalfLifeTeamplay() +{ +// m_VoiceGameMgr.Init(&g_GameMgrHelper, VGMode(HearPVS), gpGlobals->maxClients); + + m_DisableDeathMessages = FALSE; + m_DisableDeathPenalty = FALSE; + + memset( team_names, 0, sizeof(team_names) ); + memset( team_scores, 0, sizeof(team_scores) ); + num_teams = 0; + + // Copy over the team from the server config + m_szTeamList[0] = 0; + + // Cache this because the team code doesn't want to deal with changing this in the middle of a game + strncpy( m_szTeamList, teamlist.string, TEAMPLAY_TEAMLISTLENGTH ); + + edict_t *pWorld = INDEXENT(0); + if ( pWorld && pWorld->v.team ) + { + if ( teamoverride.value ) + { + const char *pTeamList = STRING(pWorld->v.team); + if ( pTeamList && strlen(pTeamList) ) + { + strncpy( m_szTeamList, pTeamList, TEAMPLAY_TEAMLISTLENGTH ); + } + } + } + // Has the server set teams + if ( strlen( m_szTeamList ) ) + m_teamLimit = TRUE; + else + m_teamLimit = FALSE; + + RecountTeams(); +} + +BOOL CHalfLifeTeamplay::ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) +{ +// m_VoiceGameMgr.ClientConnected(pEntity); + + return CHalfLifeMultiplay::ClientConnected(pEntity, pszName, pszAddress, szRejectReason); +} + + +extern cvar_t timeleft, fragsleft; + +void CHalfLifeTeamplay :: Think ( void ) +{ +// m_VoiceGameMgr.Update(gpGlobals->frametime); + + ///// Check game rules ///// + static int last_frags; + static int last_time; + + int frags_remaining = 0; + int time_remaining = 0; + + if ( g_fGameOver ) // someone else quit the game already + { + CHalfLifeMultiplay::Think(); + return; + } + + float flTimeLimit = CVAR_GET_FLOAT("mp_timelimit") * 60; + + time_remaining = (int)(flTimeLimit ? ( flTimeLimit - gpGlobals->time ) : 0); + + if ( flTimeLimit != 0 && gpGlobals->time >= flTimeLimit ) + { + GoToIntermission(); + return; + } + + float flFragLimit = fraglimit.value; + if ( flFragLimit ) + { + int bestfrags = 9999; + int remain; + + // check if any team is over the frag limit + for ( int i = 0; i < num_teams; i++ ) + { + if ( team_scores[i] >= flFragLimit ) + { + GoToIntermission(); + return; + } + + remain = flFragLimit - team_scores[i]; + if ( remain < bestfrags ) + { + bestfrags = remain; + } + } + frags_remaining = bestfrags; + } + + // Updates when frags change + if ( frags_remaining != last_frags ) + { + g_engfuncs.pfnCvar_DirectSet( &fragsleft, UTIL_VarArgs( "%i", frags_remaining ) ); + } + + // Updates once per second + if ( timeleft.value != last_time ) + { + g_engfuncs.pfnCvar_DirectSet( &timeleft, UTIL_VarArgs( "%i", time_remaining ) ); + } + + last_frags = frags_remaining; + last_time = time_remaining; +} + +//========================================================= +// ClientCommand +// the user has typed a command which is unrecognized by everything else; +// this check to see if the gamerules knows anything about the command +//========================================================= +BOOL CHalfLifeTeamplay :: ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) +{ +// if(m_VoiceGameMgr.ClientCommand(pPlayer, pcmd)) +// return TRUE; + + if ( FStrEq( pcmd, "menuselect" ) ) + { + if ( CMD_ARGC() < 2 ) + return TRUE; + + int slot = atoi( CMD_ARGV(1) ); + + // select the item from the current menu + + return TRUE; + } + + return FALSE; +} + +extern int gmsgGameMode; +extern int gmsgSayText; +extern int gmsgTeamInfo; + + +void CHalfLifeTeamplay :: UpdateGameMode( CBasePlayer *pPlayer ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgGameMode, NULL, pPlayer->edict() ); + WRITE_BYTE( 1 ); // game mode teamplay + MESSAGE_END(); +} + + +const char *CHalfLifeTeamplay::SetDefaultPlayerTeam( CBasePlayer *pPlayer ) +{ + // copy out the team name from the model + char *mdls = g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model" ); + strncpy( pPlayer->m_szTeamName, mdls, TEAM_NAME_LENGTH ); + + RecountTeams(); + + // update the current player of the team he is joining + if ( pPlayer->m_szTeamName[0] == '\0' || !IsValidTeam( pPlayer->m_szTeamName ) || defaultteam.value ) + { + const char *pTeamName = NULL; + + if ( defaultteam.value ) + { + pTeamName = team_names[0]; + } + else + { + pTeamName = TeamWithFewestPlayers(); + } + strncpy( pPlayer->m_szTeamName, pTeamName, TEAM_NAME_LENGTH ); + } + + return pPlayer->m_szTeamName; +} + + +//========================================================= +// InitHUD +//========================================================= +void CHalfLifeTeamplay::InitHUD( CBasePlayer *pPlayer ) +{ + SetDefaultPlayerTeam( pPlayer ); + CHalfLifeMultiplay::InitHUD( pPlayer ); + + RecountTeams(); + + char *mdls = g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model" ); + // update the current player of the team he is joining + char text[1024]; + if ( !strcmp( mdls, pPlayer->m_szTeamName ) ) + { + sprintf( text, "* you are on team \'%s\'\n", pPlayer->m_szTeamName ); + } + else + { + sprintf( text, "* assigned to team %s\n", pPlayer->m_szTeamName ); + } + + ChangePlayerTeam( pPlayer, pPlayer->m_szTeamName, FALSE, FALSE ); + UTIL_SayText( text, pPlayer ); + int clientIndex = pPlayer->entindex(); + RecountTeams(); + // update this player with all the other players team info + // loop through all active players and send their team info to the new client + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *plr = UTIL_PlayerByIndex( i ); + if ( plr && IsValidTeam( plr->TeamID() ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgTeamInfo, NULL, pPlayer->edict() ); + WRITE_BYTE( plr->entindex() ); + WRITE_STRING( plr->TeamID() ); + MESSAGE_END(); + } + } +} + + +void CHalfLifeTeamplay::ChangePlayerTeam( CBasePlayer *pPlayer, const char *pTeamName, BOOL bKill, BOOL bGib ) +{ + int damageFlags = DMG_GENERIC; + int clientIndex = pPlayer->entindex(); + + if ( !bGib ) + { + damageFlags |= DMG_NEVERGIB; + } + else + { + damageFlags |= DMG_ALWAYSGIB; + } + + if ( bKill ) + { + // kill the player, remove a death, and let them start on the new team + m_DisableDeathMessages = TRUE; + m_DisableDeathPenalty = TRUE; + + entvars_t *pevWorld = VARS( INDEXENT(0) ); + pPlayer->TakeDamage( pevWorld, pevWorld, 900, damageFlags ); + + m_DisableDeathMessages = FALSE; + m_DisableDeathPenalty = FALSE; + } + + // copy out the team name from the model + strncpy( pPlayer->m_szTeamName, pTeamName, TEAM_NAME_LENGTH ); + + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model", pPlayer->m_szTeamName ); + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "team", pPlayer->m_szTeamName ); + + // notify everyone's HUD of the team change + MESSAGE_BEGIN( MSG_ALL, gmsgTeamInfo ); + WRITE_BYTE( clientIndex ); + WRITE_STRING( pPlayer->m_szTeamName ); + MESSAGE_END(); +} + + +//========================================================= +// ClientUserInfoChanged +//========================================================= +void CHalfLifeTeamplay::ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ) +{ + char text[1024]; + + // prevent skin/color/model changes + char *mdls = g_engfuncs.pfnInfoKeyValue( infobuffer, "model" ); + + if ( !stricmp( mdls, pPlayer->m_szTeamName ) ) + return; + + if ( defaultteam.value ) + { + int clientIndex = pPlayer->entindex(); + + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model", pPlayer->m_szTeamName ); + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "team", pPlayer->m_szTeamName ); + sprintf( text, "* Not allowed to change teams in this game!\n" ); + UTIL_SayText( text, pPlayer ); + return; + } + + if ( defaultteam.value || !IsValidTeam( mdls ) ) + { + int clientIndex = pPlayer->entindex(); + + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model", pPlayer->m_szTeamName ); + sprintf( text, "* Can't change team to \'%s\'\n", mdls ); + UTIL_SayText( text, pPlayer ); + sprintf( text, "* Server limits teams to \'%s\'\n", m_szTeamList ); + UTIL_SayText( text, pPlayer ); + return; + } + // notify everyone of the team change + sprintf( text, "* %s has changed to team \'%s\'\n", STRING(pPlayer->pev->netname), mdls ); + UTIL_SayTextAll( text, pPlayer ); + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" joined team \"%s\"\n", + STRING(pPlayer->pev->netname), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + pPlayer->m_szTeamName, + mdls ); + + ChangePlayerTeam( pPlayer, mdls, TRUE, TRUE ); + // recound stuff + RecountTeams(); +} + +extern int gmsgDeathMsg; + +//========================================================= +// Deathnotice. +//========================================================= +void CHalfLifeTeamplay::DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pevInflictor ) +{ + if ( m_DisableDeathMessages ) + return; + + if ( pVictim && pKiller && pKiller->flags & FL_CLIENT ) + { + CBasePlayer *pk = (CBasePlayer*) CBaseEntity::Instance( pKiller ); + + if ( pk ) + { + if ( (pk != pVictim) && (PlayerRelationship( pVictim, pk ) == GR_TEAMMATE) ) + { + MESSAGE_BEGIN( MSG_ALL, gmsgDeathMsg ); + WRITE_BYTE( ENTINDEX(ENT(pKiller)) ); // the killer + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); // the victim + WRITE_STRING( "teammate" ); // flag this as a teammate kill + MESSAGE_END(); + return; + } + } + } + + CHalfLifeMultiplay::DeathNotice( pVictim, pKiller, pevInflictor ); +} + +//========================================================= +//========================================================= +void CHalfLifeTeamplay :: PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) +{ + if ( !m_DisableDeathPenalty ) + { + CHalfLifeMultiplay::PlayerKilled( pVictim, pKiller, pInflictor ); + RecountTeams(); + } +} + + +//========================================================= +// IsTeamplay +//========================================================= +BOOL CHalfLifeTeamplay::IsTeamplay( void ) +{ + return TRUE; +} + +BOOL CHalfLifeTeamplay::FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ) +{ + if ( pAttacker && PlayerRelationship( pPlayer, pAttacker ) == GR_TEAMMATE ) + { + // my teammate hit me. + if ( (CVAR_GET_FLOAT("mp_friendlyfire") == 0) && (pAttacker != pPlayer) ) + { + // friendly fire is off, and this hit came from someone other than myself, then don't get hurt + return FALSE; + } + } + + return CHalfLifeMultiplay::FPlayerCanTakeDamage( pPlayer, pAttacker ); +} + +//========================================================= +//========================================================= +int CHalfLifeTeamplay::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) +{ + // half life multiplay has a simple concept of Player Relationships. + // you are either on another player's team, or you are not. + if ( !pPlayer || !pTarget || !pTarget->IsPlayer() ) + return GR_NOTTEAMMATE; + + if ( (*GetTeamID(pPlayer) != '\0') && (*GetTeamID(pTarget) != '\0') && !stricmp( GetTeamID(pPlayer), GetTeamID(pTarget) ) ) + { + return GR_TEAMMATE; + } + + return GR_NOTTEAMMATE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeTeamplay::ShouldAutoAim( CBasePlayer *pPlayer, edict_t *target ) +{ + // always autoaim, unless target is a teammate + CBaseEntity *pTgt = CBaseEntity::Instance( target ); + if ( pTgt && pTgt->IsPlayer() ) + { + if ( PlayerRelationship( pPlayer, pTgt ) == GR_TEAMMATE ) + return FALSE; // don't autoaim at teammates + } + + return CHalfLifeMultiplay::ShouldAutoAim( pPlayer, target ); +} + +//========================================================= +//========================================================= +int CHalfLifeTeamplay::IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) +{ + if ( !pKilled ) + return 0; + + if ( !pAttacker ) + return 1; + + if ( pAttacker != pKilled && PlayerRelationship( pAttacker, pKilled ) == GR_TEAMMATE ) + return -1; + + return 1; +} + +//========================================================= +//========================================================= +const char *CHalfLifeTeamplay::GetTeamID( CBaseEntity *pEntity ) +{ + if ( pEntity == NULL || pEntity->pev == NULL ) + return ""; + + // return their team name + return pEntity->TeamID(); +} + + +int CHalfLifeTeamplay::GetTeamIndex( const char *pTeamName ) +{ + if ( pTeamName && *pTeamName != 0 ) + { + // try to find existing team + for ( int tm = 0; tm < num_teams; tm++ ) + { + if ( !stricmp( team_names[tm], pTeamName ) ) + return tm; + } + } + + return -1; // No match +} + + +const char *CHalfLifeTeamplay::GetIndexedTeamName( int teamIndex ) +{ + if ( teamIndex < 0 || teamIndex >= num_teams ) + return ""; + + return team_names[ teamIndex ]; +} + + +BOOL CHalfLifeTeamplay::IsValidTeam( const char *pTeamName ) +{ + if ( !m_teamLimit ) // Any team is valid if the teamlist isn't set + return TRUE; + + return ( GetTeamIndex( pTeamName ) != -1 ) ? TRUE : FALSE; +} + +const char *CHalfLifeTeamplay::TeamWithFewestPlayers( void ) +{ + int i; + int minPlayers = MAX_TEAMS; + int teamCount[ MAX_TEAMS ]; + char *pTeamName = NULL; + + memset( teamCount, 0, MAX_TEAMS * sizeof(int) ); + + // loop through all clients, count number of players on each team + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *plr = UTIL_PlayerByIndex( i ); + + if ( plr ) + { + int team = GetTeamIndex( plr->TeamID() ); + if ( team >= 0 ) + teamCount[team] ++; + } + } + + // Find team with least players + for ( i = 0; i < num_teams; i++ ) + { + if ( teamCount[i] < minPlayers ) + { + minPlayers = teamCount[i]; + pTeamName = team_names[i]; + } + } + + return pTeamName; +} + + +//========================================================= +//========================================================= +void CHalfLifeTeamplay::RecountTeams( void ) +{ + char *pName; + char teamlist[TEAMPLAY_TEAMLISTLENGTH]; + + // loop through all teams, recounting everything + num_teams = 0; + + // Copy all of the teams from the teamlist + // make a copy because strtok is destructive + strcpy( teamlist, m_szTeamList ); + pName = teamlist; + pName = strtok( pName, ";" ); + while ( pName != NULL && *pName ) + { + if ( GetTeamIndex( pName ) < 0 ) + { + strcpy( team_names[num_teams], pName ); + num_teams++; + } + pName = strtok( NULL, ";" ); + } + + if ( num_teams < 2 ) + { + num_teams = 0; + m_teamLimit = FALSE; + } + + // Sanity check + memset( team_scores, 0, sizeof(team_scores) ); + + // loop through all clients + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *plr = UTIL_PlayerByIndex( i ); + + if ( plr ) + { + const char *pTeamName = plr->TeamID(); + // try add to existing team + int tm = GetTeamIndex( pTeamName ); + + if ( tm < 0 ) // no team match found + { + if ( !m_teamLimit ) + { + // add to new team + tm = num_teams; + num_teams++; + team_scores[tm] = 0; + strncpy( team_names[tm], pTeamName, MAX_TEAMNAME_LENGTH ); + } + } + + if ( tm >= 0 ) + { + team_scores[tm] += plr->pev->frags; + } + } + } +} diff --git a/dmc/dlls/teamplay_gamerules.h b/dmc/dlls/teamplay_gamerules.h new file mode 100644 index 0000000..f9de3cb --- /dev/null +++ b/dmc/dlls/teamplay_gamerules.h @@ -0,0 +1,62 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.h +// + +#include "voice_gamemgr.h" + +#define MAX_TEAMNAME_LENGTH 16 +#define MAX_TEAMS 32 + +#define TEAMPLAY_TEAMLISTLENGTH MAX_TEAMS*MAX_TEAMNAME_LENGTH + +class CHalfLifeTeamplay : public CHalfLifeMultiplay +{ +public: + CHalfLifeTeamplay(); + + virtual BOOL ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); + virtual BOOL ClientCommand( CBasePlayer *pPlayer, const char *pcmd ); + virtual void ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ); + virtual BOOL IsTeamplay( void ); + virtual BOOL FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ); + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); + virtual const char *GetTeamID( CBaseEntity *pEntity ); + virtual BOOL ShouldAutoAim( CBasePlayer *pPlayer, edict_t *target ); + virtual int IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ); + virtual void InitHUD( CBasePlayer *pl ); + virtual void DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pevInflictor ); + virtual const char *GetGameDescription( void ) { return "HL Teamplay"; } // this is the game name that gets seen in the server browser + virtual void UpdateGameMode( CBasePlayer *pPlayer ); // the client needs to be informed of the current game mode + virtual void PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); + virtual void Think ( void ); + virtual int GetTeamIndex( const char *pTeamName ); + virtual const char *GetIndexedTeamName( int teamIndex ); + virtual BOOL IsValidTeam( const char *pTeamName ); + const char *SetDefaultPlayerTeam( CBasePlayer *pPlayer ); + virtual void ChangePlayerTeam( CBasePlayer *pPlayer, const char *pTeamName, BOOL bKill, BOOL bGib ); + + CVoiceGameMgr m_VoiceGameMgr; + +private: + void RecountTeams( void ); + const char *TeamWithFewestPlayers( void ); + + BOOL m_DisableDeathMessages; + BOOL m_DisableDeathPenalty; + BOOL m_teamLimit; // This means the server set only some teams as valid + char m_szTeamList[TEAMPLAY_TEAMLISTLENGTH]; +}; diff --git a/dmc/dlls/threewave_gamerules.cpp b/dmc/dlls/threewave_gamerules.cpp new file mode 100644 index 0000000..e235322 --- /dev/null +++ b/dmc/dlls/threewave_gamerules.cpp @@ -0,0 +1,3275 @@ +/*** +* +* Copyright (c) 2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== threewave_gamerules.cpp ======================================================== + + This contains all the gamerules for the ThreeWave CTF Gamemode. + It also contains the Flag entity information. + +*/ + +#ifdef THREEWAVE + +#define NUM_TEAMS 2 + +char *sTeamNames[] = +{ + "SPECTATOR", + "RED", + "BLUE", +}; + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "skill.h" +#include "game.h" +#include "items.h" +#include "threewave_gamerules.h" + +extern int gmsgCTFMsgs; +extern int gmsgShowMenu; +extern int gmsgFlagStatus; +extern int gmsgRuneStatus; +extern int gmsgFlagCarrier; +extern int gmsgScoreInfo; + +extern unsigned short g_usHook; +extern unsigned short g_usCable; +extern unsigned short g_usCarried; +extern unsigned short g_usFlagSpawn; + + +static char team_names[MAX_TEAMS][MAX_TEAMNAME_LENGTH]; +static int team_scores[MAX_TEAMS]; +static int num_teams = 0; + +bool g_bSpawnedRunes; +void SpawnRunes( void ); + +extern edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer, bool bCheckDM ); +extern edict_t *RuneSelectSpawnPoint( void ); + +// Standard Scoring +#define TEAM_CAPTURE_CAPTURE_BONUS 5 // what you get for capture +#define TEAM_CAPTURE_TEAM_BONUS 10 // what your team gets for capture +#define TEAM_CAPTURE_RECOVERY_BONUS 1 // what you get for recovery +#define TEAM_CAPTURE_FLAG_BONUS 0 // what you get for picking up enemy flag +#define TEAM_CAPTURE_FRAG_CARRIER_BONUS 2 // what you get for fragging a enemy flag carrier +#define TEAM_CAPTURE_FLAG_RETURN_TIME 40 // seconds until auto return + +// bonuses +#define TEAM_CAPTURE_CARRIER_DANGER_PROTECT_BONUS 2 // bonus for fraggin someone +// who has recently hurt your flag carrier +#define TEAM_CAPTURE_CARRIER_PROTECT_BONUS 1 // bonus for fraggin someone while +// either you or your target are near your flag carrier +#define TEAM_CAPTURE_FLAG_DEFENSE_BONUS 1 // bonus for fraggin someone while +// either you or your target are near your flag +#define TEAM_CAPTURE_RETURN_FLAG_ASSIST_BONUS 1 // awarded for returning a flag that causes a +// capture to happen almost immediately +#define TEAM_CAPTURE_FRAG_CARRIER_ASSIST_BONUS 2 // award for fragging a flag carrier if a +// capture happens almost immediately + +// Radius +#define TEAM_CAPTURE_TARGET_PROTECT_RADIUS 550 // the radius around an object being +// defended where a target will be worth extra frags +#define TEAM_CAPTURE_ATTACKER_PROTECT_RADIUS 550 // the radius around an object being +// defended where an attacker will get extra frags when making kills + +// timeouts +#define TEAM_CAPTURE_CARRIER_DANGER_PROTECT_TIMEOUT 4 +#define TEAM_CAPTURE_CARRIER_FLAG_SINCE_TIMEOUT 2 +#define TEAM_CAPTURE_FRAG_CARRIER_ASSIST_TIMEOUT 6 +#define TEAM_CAPTURE_RETURN_FLAG_ASSIST_TIMEOUT 4 + + + +class CThreeWaveGameMgrHelper : public IVoiceGameMgrHelper +{ +public: + virtual bool CanPlayerHearPlayer(CBasePlayer *pPlayer1, CBasePlayer *pPlayer2) + { + return stricmp(pPlayer1->TeamID(), pPlayer2->TeamID()) == 0; + } +}; +static CThreeWaveGameMgrHelper g_GameMgrHelper; + + + +extern DLL_GLOBAL BOOL g_fGameOver; + +char* GetTeamName( int team ) +{ + if ( team < 0 || team > NUM_TEAMS ) + team = 0; + + return sTeamNames[ team ]; +} + +CThreeWave :: CThreeWave() +{ + // CHalfLifeMultiplay already initialized it - just override its helper callback. + m_VoiceGameMgr.SetHelper(&g_GameMgrHelper); + + m_DisableDeathMessages = FALSE; + m_DisableDeathPenalty = FALSE; + + memset( team_names, 0, sizeof(team_names) ); + memset( team_scores, 0, sizeof(team_scores) ); + num_teams = 0; + + iBlueTeamScore = iRedTeamScore = 0; + g_bSpawnedRunes = FALSE; + + // Copy over the team from the server config + m_szTeamList[0] = 0; + + // Cache this because the team code doesn't want to deal with changing this in the middle of a game + strncpy( m_szTeamList, teamlist.string, TEAMPLAY_TEAMLISTLENGTH ); + + edict_t *pWorld = INDEXENT(0); + if ( pWorld && pWorld->v.team ) + { + if ( teamoverride.value ) + { + const char *pTeamList = STRING(pWorld->v.team); + if ( pTeamList && strlen(pTeamList) ) + { + strncpy( m_szTeamList, pTeamList, TEAMPLAY_TEAMLISTLENGTH ); + } + } + } + // Has the server set teams + if ( strlen( m_szTeamList ) ) + m_teamLimit = TRUE; + else + m_teamLimit = FALSE; + + RecountTeams(); +} + + +BOOL CThreeWave::ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) +{ + return CHalfLifeMultiplay::ClientConnected(pEntity, pszName, pszAddress, szRejectReason); +} + + +extern cvar_t timeleft, fragsleft; + +void CThreeWave :: Think ( void ) +{ + m_VoiceGameMgr.Update(gpGlobals->frametime); + + ///// Check game rules ///// + static int last_frags; + static int last_time; + + int frags_remaining = 0; + int time_remaining = 0; + + if ( g_fGameOver ) // someone else quit the game already + { + CHalfLifeMultiplay::Think(); + return; + } + + float flTimeLimit = CVAR_GET_FLOAT("mp_timelimit") * 60; + + time_remaining = (int)(flTimeLimit ? ( flTimeLimit - gpGlobals->time ) : 0); + + if ( flTimeLimit != 0 && gpGlobals->time >= flTimeLimit ) + { + GoToIntermission(); + return; + } + + float flFragLimit = fraglimit.value; + if ( flFragLimit ) + { + int bestfrags = 9999; + int remain; + + // check if any team is over the frag limit + for ( int i = 0; i < num_teams; i++ ) + { + if ( team_scores[i] >= flFragLimit ) + { + GoToIntermission(); + return; + } + + remain = flFragLimit - team_scores[i]; + if ( remain < bestfrags ) + { + bestfrags = remain; + } + } + frags_remaining = bestfrags; + } + + if ( !g_bSpawnedRunes ) + SpawnRunes(); + + if ( m_flFlagStatusTime && m_flFlagStatusTime <= gpGlobals->time ) + GetFlagStatus( NULL ); + + // Updates when frags change + if ( frags_remaining != last_frags ) + { + g_engfuncs.pfnCvar_DirectSet( &fragsleft, UTIL_VarArgs( "%i", frags_remaining ) ); + } + + // Updates once per second + if ( timeleft.value != last_time ) + { + g_engfuncs.pfnCvar_DirectSet( &timeleft, UTIL_VarArgs( "%i", time_remaining ) ); + } + + last_frags = frags_remaining; + last_time = time_remaining; +} + +void CThreeWave :: JoinTeam ( CBasePlayer *pPlayer, int iTeam ) +{ + if ( pPlayer->pev->team == iTeam ) + return; + + if ( pPlayer->m_flNextTeamChange > gpGlobals->time ) + return; + + pPlayer->m_flNextTeamChange = gpGlobals->time + 5; + + if ( pPlayer->pev->team == 0 ) + { + ChangePlayerTeam( pPlayer, iTeam ); + RecountTeams(); + + pPlayer->Spawn(); + } + else + { + ChangePlayerTeam( pPlayer, iTeam ); + RecountTeams(); + } +} + +int CThreeWave::TeamWithFewestPlayers( void ) +{ + + CBaseEntity *pPlayer = NULL; + CBasePlayer *player = NULL; + + int iNumRed, iNumBlue; + + int iTeam; + + // Initialize the player counts.. + iNumRed = iNumBlue = 0; + + pPlayer = UTIL_FindEntityByClassname ( pPlayer, "player" ); + + while ( (pPlayer != NULL) && (!FNullEnt(pPlayer->edict())) ) + { + if (pPlayer->pev->flags != FL_DORMANT) + { + player = GetClassPtr((CBasePlayer *)pPlayer->pev); + + + if ( player->pev->team == RED ) + iNumRed += 1; + + else if ( player->pev->team == BLUE ) + iNumBlue += 1; + + } + pPlayer = UTIL_FindEntityByClassname ( pPlayer, "player" ); + } + + if ( iNumRed == iNumBlue ) + { + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: iTeam = RED; break; + case 1: iTeam = BLUE; break; + } + } + else if ( iNumRed == 0 && iNumBlue == 0) + { + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: iTeam = RED; break; + case 1: iTeam = BLUE; break; + } + } + + else if ( iNumRed > iNumBlue ) + iTeam = BLUE; + + else if ( iNumRed < iNumBlue ) + iTeam = RED; + + return iTeam; + +} + +void DropRune ( CBasePlayer *pPlayer ); +//========================================================= +// ClientCommand +// the user has typed a command which is unrecognized by everything else; +// this check to see if the gamerules knows anything about the command +//========================================================= +BOOL CThreeWave :: ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) +{ + if( m_VoiceGameMgr.ClientCommand( pPlayer, pcmd ) ) + return TRUE; + + if ( FStrEq( pcmd, "menuselect" ) ) + { + if ( CMD_ARGC() < 2 ) + return TRUE; + + int slot = atoi( CMD_ARGV(1) ); + + // select the item from the current menu + switch( pPlayer->m_iMenu ) + { + case Team_Menu: + + switch ( slot ) + { + case 1: + JoinTeam( pPlayer, RED ); + break; + case 2: + JoinTeam( pPlayer, BLUE ); + break; + case 5: + JoinTeam( pPlayer, TeamWithFewestPlayers() ); + break; + } + + break; + + case Team_Menu_IG: + + switch ( slot ) + { + case 1: + JoinTeam( pPlayer, RED ); + break; + case 2: + JoinTeam( pPlayer, BLUE ); + break; + case 5: + JoinTeam( pPlayer, TeamWithFewestPlayers() ); + break; + default: + return TRUE; + } + + break; + } + + return TRUE; + } + else if ( FStrEq( pcmd, "droprune" ) ) + { + DropRune( pPlayer ); + + return TRUE; + } + else if ( FStrEq( pcmd, "changeteam" ) ) + { + if ( pPlayer->pev->team != 0 ) + { + pPlayer->ShowMenu( 1 + 2 + 16 + 512, -1, FALSE, "#Team_Menu_Join_IG" ); + pPlayer->m_iMenu = Team_Menu_IG; + } + + return TRUE; + } + + return FALSE; +} + +extern int gmsgGameMode; +extern int gmsgSayText; +extern int gmsgTeamInfo; + +void CThreeWave :: UpdateGameMode( CBasePlayer *pPlayer ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgGameMode, NULL, pPlayer->edict() ); + WRITE_BYTE( 1 ); // game mode teamplay + MESSAGE_END(); +} + +edict_t *CThreeWave::GetPlayerSpawnSpot( CBasePlayer *pPlayer ) +{ + edict_t *pentSpawnSpot; + + if ( FBitSet( pPlayer->m_afPhysicsFlags, PFLAG_OBSERVER ) || pPlayer->pev->team == 0 ) + pentSpawnSpot = EntSelectSpawnPoint( pPlayer, FALSE ); + else + { + if ( RANDOM_LONG ( 1, 7 ) < 3 ) + pentSpawnSpot= EntSelectSpawnPoint( pPlayer, TRUE ); + else + pentSpawnSpot= EntSelectSpawnPoint( pPlayer, FALSE ); + } + + if ( IsMultiplayer() && pentSpawnSpot->v.target ) + { + FireTargets( STRING(pentSpawnSpot->v.target), pPlayer, pPlayer, USE_TOGGLE, 0 ); + } + + pPlayer->pev->origin = VARS(pentSpawnSpot)->origin + Vector(0,0,1); + pPlayer->pev->v_angle = g_vecZero; + pPlayer->pev->velocity = g_vecZero; + pPlayer->pev->angles = VARS(pentSpawnSpot)->angles; + pPlayer->pev->punchangle = g_vecZero; + pPlayer->pev->fixangle = TRUE; + + return pentSpawnSpot; +} + +void CThreeWave :: PlayerTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ) +{ + if ( !pAttacker->IsPlayer() ) + return; + + if ( pPlayer->pev->team == pAttacker->pev->team ) + return; + + if ( pPlayer->m_bHasFlag ) + { + pPlayer->pCarrierHurter = (CBasePlayer *)pAttacker; + pPlayer->m_flCarrierHurtTime = gpGlobals->time + TEAM_CAPTURE_CARRIER_DANGER_PROTECT_TIMEOUT; + } + +} + +void CThreeWave :: PlayerSpawn( CBasePlayer *pPlayer ) +{ + BOOL addDefault; + CBaseEntity *pWeaponEntity = NULL; + + if ( pPlayer->pev->team == 0 ) + { + pPlayer->pev->takedamage = DAMAGE_NO; + pPlayer->pev->solid = SOLID_NOT; + pPlayer->pev->movetype = MOVETYPE_NOCLIP; + pPlayer->pev->effects |= EF_NODRAW; + pPlayer->pev->flags |= FL_NOTARGET; + pPlayer->m_afPhysicsFlags |= PFLAG_OBSERVER; + pPlayer->m_iHideHUD |= HIDEHUD_WEAPONS | HIDEHUD_FLASHLIGHT | HIDEHUD_HEALTH; + + pPlayer->m_flFlagStatusTime = gpGlobals->time + 0.1; + } + else + { + pPlayer->pev->weapons |= (1<Touch( pPlayer ); + addDefault = FALSE; + } + + if ( addDefault ) + { + pPlayer->m_bHasFlag = FALSE; + + pPlayer->m_iHideHUD &= ~HIDEHUD_WEAPONS; + pPlayer->m_iHideHUD &= ~HIDEHUD_FLASHLIGHT; + pPlayer->m_iHideHUD &= ~HIDEHUD_HEALTH; + pPlayer->m_afPhysicsFlags &= ~PFLAG_OBSERVER; + + // Start with init ammoload + pPlayer->m_iAmmoShells = 25; + + // Start with shotgun and axe + pPlayer->GiveNamedItem( "weapon_quakegun" ); + pPlayer->m_iQuakeItems |= ( IT_SHOTGUN | IT_AXE | IT_EXTRA_WEAPON ); + pPlayer->m_iQuakeWeapon = pPlayer->W_BestWeapon(); + pPlayer->W_SetCurrentAmmo(); + + pPlayer->m_flFlagStatusTime = gpGlobals->time + 0.1; + } + } + +/* MESSAGE_BEGIN( MSG_ONE, gmsgRuneStatus, NULL, pPlayer->pev); + WRITE_BYTE( pPlayer->m_iRuneStatus ); + MESSAGE_END();*/ +} + +void CBasePlayer::ShowMenu ( int bitsValidSlots, int nDisplayTime, BOOL fNeedMore, char *pszText ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgShowMenu, NULL, pev); + WRITE_SHORT( bitsValidSlots); + WRITE_CHAR( nDisplayTime ); + WRITE_BYTE( fNeedMore ); + WRITE_STRING (pszText); + MESSAGE_END(); +} + +//========================================================= +// InitHUD +//========================================================= +void CThreeWave::InitHUD( CBasePlayer *pPlayer ) +{ + CHalfLifeMultiplay::InitHUD( pPlayer ); + + int clientIndex = pPlayer->entindex(); + // update this player with all the other players team info + // loop through all active players and send their team info to the new client + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *plr = UTIL_PlayerByIndex( i ); + if ( plr ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgTeamInfo, NULL, pPlayer->edict() ); + WRITE_BYTE( plr->entindex() ); + WRITE_STRING( plr->TeamID() ); + MESSAGE_END(); + + if ( ((CBasePlayer *)plr)->m_bHasFlag ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgFlagCarrier, NULL, pPlayer->edict() ); + WRITE_BYTE( plr->entindex() ); + WRITE_BYTE( 1 ); + MESSAGE_END(); + } + } + } + + //Remove Rune icon if we have one. + MESSAGE_BEGIN( MSG_ONE, gmsgRuneStatus, NULL, pPlayer->pev); + WRITE_BYTE( 0 ); + MESSAGE_END(); + + if ( pPlayer->pev->team == 0) + { + pPlayer->ShowMenu( 1 + 2 + 16, -1, FALSE, "#Team_Menu_Join" ); + pPlayer->m_iMenu = Team_Menu; + } +} + + +void CThreeWave::ChangePlayerTeam( CBasePlayer *pPlayer, int iTeam ) +{ + int damageFlags = DMG_GENERIC; + int clientIndex = pPlayer->entindex(); + + if ( pPlayer->pev->team != 0 ) + { + damageFlags |= DMG_ALWAYSGIB; + + // kill the player, remove a death, and let them start on the new team + m_DisableDeathMessages = TRUE; + m_DisableDeathPenalty = TRUE; + + entvars_t *pevWorld = VARS( INDEXENT(0) ); + pPlayer->TakeDamage( pevWorld, pevWorld, 900, damageFlags ); + + m_DisableDeathMessages = FALSE; + m_DisableDeathPenalty = FALSE; + } + + int oldTeam = pPlayer->pev->team; + pPlayer->pev->team = iTeam; + + if ( pPlayer->pev->team == RED ) + { + strncpy( pPlayer->m_szTeamName, "RED", TEAM_NAME_LENGTH ); + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model", "red" ); + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "topcolor", UTIL_VarArgs( "%d", 255 ) ); + } + else if ( pPlayer->pev->team == BLUE ) + { + strncpy( pPlayer->m_szTeamName, "BLUE", TEAM_NAME_LENGTH ); + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model", "blue" ); + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "topcolor", UTIL_VarArgs( "%d", 153 ) ); + } + + // notify everyone's HUD of the team change + MESSAGE_BEGIN( MSG_ALL, gmsgTeamInfo ); + WRITE_BYTE( clientIndex ); + WRITE_STRING( pPlayer->m_szTeamName ); + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo ); + WRITE_BYTE( ENTINDEX(pPlayer->edict()) ); + WRITE_SHORT( pPlayer->pev->frags ); + WRITE_SHORT( pPlayer->m_iDeaths ); + WRITE_SHORT( pPlayer->pev->team ); + MESSAGE_END(); + + // log the change + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" joined team \"%s\"\n", + STRING(pPlayer->pev->netname), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + GetTeamName( oldTeam ), + pPlayer->m_szTeamName ); +} + + +//========================================================= +// ClientUserInfoChanged +//========================================================= +void CThreeWave::ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ) +{ + + int clientIndex = pPlayer->entindex(); + + if ( pPlayer->pev->team == RED ) + { + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "topcolor", UTIL_VarArgs( "%d", 255 ) ); + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model", "red" ); + } + else if ( pPlayer->pev->team == BLUE ) + { + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "topcolor", UTIL_VarArgs( "%d", 153 ) ); + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model", "blue" ); + } + +} + +extern int gmsgDeathMsg; + +//========================================================= +// Deathnotice. +//========================================================= +void CThreeWave::DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pevInflictor ) +{ + if ( m_DisableDeathMessages ) + return; + + if ( pVictim && pKiller && pKiller->flags & FL_CLIENT ) + { + CBasePlayer *pk = (CBasePlayer*) CBaseEntity::Instance( pKiller ); + + if ( pk ) + { + if ( (pk != pVictim) && (PlayerRelationship( pVictim, pk ) == GR_TEAMMATE) ) + { + MESSAGE_BEGIN( MSG_ALL, gmsgDeathMsg ); + WRITE_BYTE( ENTINDEX(ENT(pKiller)) ); // the killer + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); // the victim + WRITE_STRING( "teammate" ); // flag this as a teammate kill + MESSAGE_END(); + return; + } + } + } + + CHalfLifeMultiplay::DeathNotice( pVictim, pKiller, pevInflictor ); +} + +//========================================================= +//========================================================= +void CThreeWave :: ClientDisconnected( edict_t *pClient ) +{ + if ( pClient ) + { + CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pClient ); + + if ( pPlayer ) + { + //We have the flag, spawn it + if ( pPlayer->m_bHasFlag ) + { + CBaseEntity *pEnt; + + //We have the BLUE flag, Spawn it + if ( pPlayer->pev->team == RED ) + { + pEnt = CBaseEntity::Create( "item_flag_team2", pPlayer->pev->origin, pPlayer->pev->angles, pPlayer->edict() ); + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Dropped_Blue_Flag\"\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + GetTeamName( pPlayer->pev->team ) ); + } + //We have the RED flag, Spawn it + else if ( pPlayer->pev->team == BLUE ) + { + pEnt = CBaseEntity::Create( "item_flag_team1", pPlayer->pev->origin, pPlayer->pev->angles, pPlayer->edict() ); + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Dropped_Red_Flag\"\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + GetTeamName( pPlayer->pev->team ) ); + } + + pEnt->pev->velocity = pPlayer->pev->velocity * 1.2; + pEnt->pev->angles.x = 0; + + CItemFlag *pFlag = (CItemFlag *)pEnt; + pFlag->Dropped = TRUE; + pFlag->m_flDroppedTime = gpGlobals->time + TEAM_CAPTURE_FLAG_RETURN_TIME; + + PLAYBACK_EVENT_FULL( FEV_GLOBAL | FEV_RELIABLE, + pPlayer->edict(), g_usCarried, 0, (float *)&g_vecZero, (float *)&g_vecZero, + 0.0, 0.0, pPlayer->entindex(), pPlayer->pev->team, 1, 0 ); + + MESSAGE_BEGIN ( MSG_ALL, gmsgCTFMsgs, NULL ); + if ( pPlayer->pev->team == RED ) + WRITE_BYTE( BLUE_FLAG_LOST ); + else if ( pPlayer->pev->team == BLUE ) + WRITE_BYTE( RED_FLAG_LOST ); + + WRITE_STRING( STRING(pPlayer->pev->netname) ); + MESSAGE_END(); + + m_flFlagStatusTime = gpGlobals->time + 0.1; + + pPlayer->m_bHasFlag = FALSE; + } + + // drop any runes the player has + CBaseEntity *pRune; + char * runeName; + + switch ( pPlayer->m_iRuneStatus ) + { + case ITEM_RUNE1_FLAG: + + pRune = CBaseEntity::Create( "item_rune1", pPlayer->pev->origin, pPlayer->pev->angles, NULL ); + + pRune->pev->velocity = pPlayer->pev->velocity * 1.5; + pRune->pev->angles.x = 0; + ((CResistRune*)pRune)->dropped = true; + + runeName = "ResistRune"; + + break; + + case ITEM_RUNE2_FLAG: + + pRune = CBaseEntity::Create( "item_rune2", pPlayer->pev->origin, pPlayer->pev->angles, NULL ); + + pRune->pev->velocity = pPlayer->pev->velocity * 1.5; + pRune->pev->angles.x = 0; + ((CStrengthRune*)pRune)->dropped = true; + + runeName = "StrengthRune"; + + break; + + case ITEM_RUNE3_FLAG: + + pRune = CBaseEntity::Create( "item_rune3", pPlayer->pev->origin, pPlayer->pev->angles, NULL ); + + pRune->pev->velocity = pPlayer->pev->velocity * 1.5; + pRune->pev->angles.x = 0; + ((CHasteRune*)pRune)->dropped = true; + + runeName = "HasteRune"; + + break; + + case ITEM_RUNE4_FLAG: + + pRune = CBaseEntity::Create( "item_rune4", pPlayer->pev->origin, pPlayer->pev->angles, NULL ); + + pRune->pev->velocity = pPlayer->pev->velocity * 1.5; + pRune->pev->angles.x = 0; + ((CRegenRune*)pRune)->dropped = true; + + runeName = "RegenRune"; + + break; + + default: + + runeName = "Unknown"; + + break; + } + + if ( pPlayer->m_iRuneStatus ) + { + pPlayer->m_iRuneStatus = 0; + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Dropped_%s\"\n", + STRING(pPlayer->pev->netname), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + pPlayer->m_szTeamName, + runeName ); + } + + FireTargets( "game_playerleave", pPlayer, pPlayer, USE_TOGGLE, 0 ); + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" disconnected\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + GetTeamName( pPlayer->pev->team ) ); + + pPlayer->RemoveAllItems( TRUE );// destroy all of the players weapons and items + } + } +} + +void CThreeWave :: PlayerThink( CBasePlayer *pPlayer ) +{ + if ( g_fGameOver ) + { + // check for button presses + if ( pPlayer->m_afButtonPressed & ( IN_DUCK | IN_ATTACK | IN_ATTACK2 | IN_USE | IN_JUMP ) ) + m_iEndIntermissionButtonHit = TRUE; + + // clear attack/use commands from player + pPlayer->m_afButtonPressed = 0; + pPlayer->pev->button = 0; + pPlayer->m_afButtonReleased = 0; + } + + if ( pPlayer->pFlagCarrierKiller ) + { + if ( pPlayer->m_flFlagCarrierKillTime <= gpGlobals->time ) + pPlayer->pFlagCarrierKiller = NULL; + } + + if ( pPlayer->pFlagReturner ) + { + if ( pPlayer->m_flFlagReturnTime <= gpGlobals->time ) + pPlayer->pFlagReturner = NULL; + } + + if ( pPlayer->pCarrierHurter ) + { + if ( pPlayer->m_flCarrierHurtTime <= gpGlobals->time ) + pPlayer->pCarrierHurter = NULL; + } + + if ( pPlayer->m_iRuneStatus == ITEM_RUNE4_FLAG) + { + if ( pPlayer->m_flRegenTime <= gpGlobals->time) + { + + + if ( pPlayer->pev->health < 150 ) + { + pPlayer->pev->health += 5; + + if ( pPlayer->pev->health > 150) + pPlayer->pev->health = 150; + + pPlayer->m_flRegenTime = gpGlobals->time + 1; + + EMIT_SOUND(ENT(pPlayer->pev), CHAN_ITEM, "rune/rune4.wav", 1, ATTN_NORM); + } + if ( pPlayer->pev->armorvalue < 150 && pPlayer->pev->armorvalue ) + { + pPlayer->pev->armorvalue += 5; + + if ( pPlayer->pev->armorvalue > 150) + pPlayer->pev->armorvalue = 150; + + pPlayer->m_flRegenTime = gpGlobals->time + 1; + + EMIT_SOUND(ENT(pPlayer->pev), CHAN_ITEM, "rune/rune4.wav", 1, ATTN_NORM); + } + } + } + + if ( pPlayer->m_bOn_Hook ) + pPlayer->Service_Grapple(); + + if ( pPlayer->m_flFlagStatusTime && pPlayer->m_flFlagStatusTime <= gpGlobals->time ) + GetFlagStatus( pPlayer ); +} + +//========================================================= +//========================================================= +void CThreeWave :: PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) +{ + CBasePlayer *pk = NULL; + + if ( pKiller ) + { + CBaseEntity *pTemp = CBaseEntity::Instance( pKiller ); + + if ( pTemp->IsPlayer() ) + pk = (CBasePlayer*)pTemp; + } + + //Only award a bonus if the Flag carrier had the flag for more than 2 secs + //Prevents from people waiting for the flag carrier to grab the flag and then killing him + //Instead of actually defending the flag. + if ( pVictim->m_bHasFlag ) + { + if ( pk ) + { + if ( pVictim->pev->team != pk->pev->team ) + { + if ( pVictim->m_flCarrierPickupTime <= gpGlobals->time ) + pk->AddPoints( TEAM_CAPTURE_FRAG_CARRIER_BONUS, TRUE ); + + if ( pk->pev->team == RED ) + { + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, STRING( pk->pev->netname ) ); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, " fragged " ); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "BLUE" ); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "'s flag carrier!\n" ); + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Killed_Enemy_Flag_Carrier\"\n", + STRING( pk->pev->netname ), + GETPLAYERUSERID( pk->edict() ), + GETPLAYERAUTHID( pk->edict() ), + GetTeamName( pk->pev->team ) ); + + if ( iBlueFlagStatus == BLUE_FLAG_STOLEN ) + { + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pTeamMate = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if ( pTeamMate ) + { + if ( pTeamMate->m_bHasFlag ) + { + pTeamMate->pFlagCarrierKiller = pk; + pTeamMate->m_flFlagCarrierKillTime = gpGlobals->time + TEAM_CAPTURE_FRAG_CARRIER_ASSIST_TIMEOUT; + } + } + } + } + } + + if ( pk->pev->team == BLUE ) + { + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, STRING( pk->pev->netname ) ); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, " fragged " ); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "RED" ); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "'s flag carrier!\n" ); + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Killed_Enemy_Flag_Carrier\"\n", + STRING( pk->pev->netname ), + GETPLAYERUSERID( pk->edict() ), + GETPLAYERAUTHID( pk->edict() ), + GetTeamName( pk->pev->team ) ); + + if ( iRedFlagStatus == RED_FLAG_STOLEN ) + { + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pTeamMate = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if ( pTeamMate ) + { + if ( pTeamMate->m_bHasFlag ) + { + pTeamMate->pFlagCarrierKiller = pk; + pTeamMate->m_flFlagCarrierKillTime = gpGlobals->time + TEAM_CAPTURE_FRAG_CARRIER_ASSIST_TIMEOUT; + } + } + } + } + } + } + } + + + CBaseEntity *pEnt; + + //We have the BLUE flag, Spawn it + if ( pVictim->pev->team == RED ) + { + pEnt = CBaseEntity::Create( "item_flag_team2", pVictim->pev->origin, pVictim->pev->angles, pVictim->edict() ); + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Dropped_Blue_Flag\"\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GetTeamName( pVictim->pev->team ) ); + } + else if ( pVictim->pev->team == BLUE ) + { + pEnt = CBaseEntity::Create( "item_flag_team1", pVictim->pev->origin, pVictim->pev->angles, pVictim->edict() ); + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Dropped_Red_Flag\"\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GetTeamName( pVictim->pev->team ) ); + } + + pEnt->pev->velocity = pVictim->pev->velocity * 1.2; + pEnt->pev->angles.x = 0; + + CItemFlag *pFlag = (CItemFlag *)pEnt; + pFlag->Dropped = TRUE; + + PLAYBACK_EVENT_FULL( FEV_GLOBAL | FEV_RELIABLE, + pVictim->edict(), g_usCarried, 0, (float *)&g_vecZero, (float *)&g_vecZero, + 0.0, 0.0, pVictim->entindex(), pVictim->pev->team, 1, 0 ); + + pFlag->m_flDroppedTime = gpGlobals->time + TEAM_CAPTURE_FLAG_RETURN_TIME; + + MESSAGE_BEGIN ( MSG_ALL, gmsgCTFMsgs, NULL ); + if ( pVictim->pev->team == RED ) + WRITE_BYTE( BLUE_FLAG_LOST ); + else if ( pVictim->pev->team == BLUE ) + WRITE_BYTE( RED_FLAG_LOST ); + + WRITE_STRING( STRING(pVictim->pev->netname) ); + MESSAGE_END(); + + pVictim->m_bHasFlag = FALSE; + + m_flFlagStatusTime = gpGlobals->time + 0.1; + } + else + { + if ( pk ) + { + if ( pk->pev->team == RED ) + { + if ( iBlueFlagStatus == BLUE_FLAG_STOLEN ) + { + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pTeamMate = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if ( pTeamMate && pTeamMate != pk ) + { + if ( pTeamMate->pev->team == pk->pev->team ) + { + if ( pTeamMate->m_bHasFlag ) + { + if ( pTeamMate->pCarrierHurter ) + { + if ( pTeamMate->pCarrierHurter == pVictim ) + { + if ( pTeamMate->m_flCarrierHurtTime > gpGlobals->time ) + { + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, STRING( pk->pev->netname ) ); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, " defends "); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "RED" ); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "'s flag carrier against an agressive enemy\n"); + + pk->AddPoints( TEAM_CAPTURE_CARRIER_DANGER_PROTECT_BONUS, TRUE ); + } + } + } + } + } + } + } + } + } + + if ( pk->pev->team == BLUE ) + { + if ( iRedFlagStatus == RED_FLAG_STOLEN ) + { + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pTeamMate = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if ( pTeamMate && pTeamMate != pk ) + { + if ( pTeamMate->pev->team == pk->pev->team ) + { + if ( pTeamMate->m_bHasFlag ) + { + if ( pTeamMate->pCarrierHurter ) + { + if ( pTeamMate->pCarrierHurter == pVictim ) + { + if ( pTeamMate->m_flCarrierHurtTime > gpGlobals->time ) + { + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, STRING( pk->pev->netname ) ); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, " defends "); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "BLUE" ); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "'s flag carrier against an agressive enemy\n"); + + pk->AddPoints( TEAM_CAPTURE_CARRIER_DANGER_PROTECT_BONUS, TRUE ); + } + } + } + } + } + } + } + } + } + } + + } + + // Find if this guy is near our flag or our flag carrier + CBaseEntity *ent = NULL; + float Dist; + + if ( pk ) + { + if ( pk->pev->team == RED ) + { + while((ent = UTIL_FindEntityByClassname( ent, "item_flag_team1")) != NULL) + { + //Do not defend a invisible flag + if ( ent->pev->effects & EF_NODRAW ) + break; + + Dist = (pk->pev->origin - ent->pev->origin).Length(); + + if ( Dist <= TEAM_CAPTURE_TARGET_PROTECT_RADIUS ) + { + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, STRING ( pk->pev->netname )); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, " defends the "); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "RED"); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, " flag\n"); + + pk->AddPoints( TEAM_CAPTURE_FLAG_DEFENSE_BONUS, TRUE ); + break; + } + } + + if ( iBlueFlagStatus == BLUE_FLAG_STOLEN ) + { + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pTeamMate = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if ( pTeamMate && pTeamMate != pk ) + { + if ( pTeamMate->pev->team == pk->pev->team ) + { + if ( pTeamMate->m_bHasFlag ) + { + Dist = (pk->pev->origin - pTeamMate->pev->origin).Length(); + + if ( Dist <= TEAM_CAPTURE_TARGET_PROTECT_RADIUS ) + { + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, STRING ( pk->pev->netname )); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, " defends "); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "RED"); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "'s flag carrier\n"); + + pk->AddPoints( TEAM_CAPTURE_CARRIER_PROTECT_BONUS, TRUE ); + } + } + } + } + } + } + + } + else if ( pk->pev->team == BLUE ) + { + while((ent = UTIL_FindEntityByClassname( ent, "item_flag_team2")) != NULL) + { + //Do not defend a invisible flag + if ( ent->pev->effects & EF_NODRAW ) + break; + + Dist = (pk->pev->origin - ent->pev->origin).Length(); + + if ( Dist <= TEAM_CAPTURE_TARGET_PROTECT_RADIUS ) + { + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, STRING ( pk->pev->netname )); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, " defends the "); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "RED"); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, " flag\n"); + + pk->AddPoints( TEAM_CAPTURE_FLAG_DEFENSE_BONUS, TRUE ); + break; + } + } + + if ( iRedFlagStatus == RED_FLAG_STOLEN ) + { + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pTeamMate = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if ( pTeamMate && pTeamMate != pk ) + { + if ( pTeamMate->pev->team == pk->pev->team ) + { + if ( pTeamMate->m_bHasFlag ) + { + Dist = (pk->pev->origin - pTeamMate->pev->origin).Length(); + + if ( Dist <= TEAM_CAPTURE_TARGET_PROTECT_RADIUS ) + { + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, STRING ( pk->pev->netname )); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, " defends "); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "RED"); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "'s flag carrier\n"); + + pk->AddPoints( TEAM_CAPTURE_CARRIER_PROTECT_BONUS, TRUE ); + } + } + } + } + } + } + + } + } + + CBaseEntity *pRune; + char * runeName; + + switch ( pVictim->m_iRuneStatus ) + { + case ITEM_RUNE1_FLAG: + + pRune = CBaseEntity::Create( "item_rune1", pVictim->pev->origin, pVictim->pev->angles, NULL ); + + pRune->pev->velocity = pVictim->pev->velocity * 1.5; + pRune->pev->angles.x = 0; + ((CResistRune*)pRune)->dropped = true; + + runeName = "ResistRune"; + + break; + + case ITEM_RUNE2_FLAG: + + pRune = CBaseEntity::Create( "item_rune2", pVictim->pev->origin, pVictim->pev->angles, NULL ); + + pRune->pev->velocity = pVictim->pev->velocity * 1.5; + pRune->pev->angles.x = 0; + ((CStrengthRune*)pRune)->dropped = true; + + runeName = "StrengthRune"; + + break; + + case ITEM_RUNE3_FLAG: + + pRune = CBaseEntity::Create( "item_rune3", pVictim->pev->origin, pVictim->pev->angles, NULL ); + + pRune->pev->velocity = pVictim->pev->velocity * 1.5; + pRune->pev->angles.x = 0; + ((CHasteRune*)pRune)->dropped = true; + + runeName = "HasteRune"; + + break; + + case ITEM_RUNE4_FLAG: + + pRune = CBaseEntity::Create( "item_rune4", pVictim->pev->origin, pVictim->pev->angles, NULL ); + + pRune->pev->velocity = pVictim->pev->velocity * 1.5; + pRune->pev->angles.x = 0; + ((CRegenRune*)pRune)->dropped = true; + + runeName = "RegenRune"; + + break; + + default: + + runeName = "Unknown"; + + break; + } + + if ( pVictim->m_iRuneStatus ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Dropped_%s\"\n", + STRING(pVictim->pev->netname), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + pVictim->m_szTeamName, + runeName ); + } + + if ( pVictim->m_ppHook ) + (( CGrapple *)pVictim->m_ppHook)->Reset_Grapple(); + + pVictim->m_iRuneStatus = 0; + + MESSAGE_BEGIN( MSG_ONE, gmsgRuneStatus, NULL, pVictim->pev); + WRITE_BYTE( pVictim->m_iRuneStatus ); + MESSAGE_END(); + + if ( !m_DisableDeathPenalty ) + { + CHalfLifeMultiplay::PlayerKilled( pVictim, pKiller, pInflictor ); + RecountTeams(); + } +} + + +//========================================================= +// IsTeamplay +//========================================================= +BOOL CThreeWave::IsTeamplay( void ) +{ + return TRUE; +} + +BOOL CThreeWave::FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ) +{ + if ( pAttacker && PlayerRelationship( pPlayer, pAttacker ) == GR_TEAMMATE ) + { + // my teammate hit me. + if ( (CVAR_GET_FLOAT("mp_friendlyfire") == 0) && (pAttacker != pPlayer) ) + { + // friendly fire is off, and this hit came from someone other than myself, then don't get hurt + return FALSE; + } + } + + return CHalfLifeMultiplay::FPlayerCanTakeDamage( pPlayer, pAttacker ); +} + +//========================================================= +//========================================================= +int CThreeWave::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) +{ + // half life multiplay has a simple concept of Player Relationships. + // you are either on another player's team, or you are not. + if ( !pPlayer || !pTarget || !pTarget->IsPlayer() ) + return GR_NOTTEAMMATE; + + //As simple as this + if ( pPlayer->pev->team == pTarget->pev->team ) + { + return GR_TEAMMATE; + } + + return GR_NOTTEAMMATE; +} + +//========================================================= +//========================================================= +BOOL CThreeWave::ShouldAutoAim( CBasePlayer *pPlayer, edict_t *target ) +{ + // always autoaim, unless target is a teammate + CBaseEntity *pTgt = CBaseEntity::Instance( target ); + if ( pTgt && pTgt->IsPlayer() ) + { + if ( PlayerRelationship( pPlayer, pTgt ) == GR_TEAMMATE ) + return FALSE; // don't autoaim at teammates + } + + return CHalfLifeMultiplay::ShouldAutoAim( pPlayer, target ); +} + +//========================================================= +//========================================================= +int CThreeWave::IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) +{ + if ( !pKilled ) + return 0; + + if ( !pAttacker ) + return 1; + + if ( pAttacker != pKilled && PlayerRelationship( pAttacker, pKilled ) == GR_TEAMMATE ) + return -1; + + return 1; +} + +//========================================================= +//========================================================= +const char *CThreeWave::GetTeamID( CBaseEntity *pEntity ) +{ + if ( pEntity == NULL || pEntity->pev == NULL ) + return ""; + + // return their team name + return pEntity->TeamID(); +} + + +int CThreeWave::GetTeamIndex( const char *pTeamName ) +{ + if ( pTeamName && *pTeamName != 0 ) + { + // try to find existing team + for ( int tm = 0; tm < num_teams; tm++ ) + { + if ( !stricmp( team_names[tm], pTeamName ) ) + return tm; + } + } + + return -1; // No match +} + + +const char *CThreeWave::GetIndexedTeamName( int teamIndex ) +{ + if ( teamIndex < 0 || teamIndex >= num_teams ) + return ""; + + return team_names[ teamIndex ]; +} + + +BOOL CThreeWave::IsValidTeam( const char *pTeamName ) +{ + if ( !m_teamLimit ) // Any team is valid if the teamlist isn't set + return TRUE; + + return ( GetTeamIndex( pTeamName ) != -1 ) ? TRUE : FALSE; +} + + +void CThreeWave::GetFlagStatus( CBasePlayer *pPlayer ) +{ + + CBaseEntity *pFlag = NULL; + int iFoundCount = 0; + int iDropped = 0; + + while((pFlag = UTIL_FindEntityByClassname( pFlag, "carried_flag_team1")) != NULL) + { + if ( pFlag && !FBitSet( pFlag->pev->flags, FL_KILLME) ) + iFoundCount++; + } + + if ( iFoundCount >= 1 ) + iRedFlagStatus = RED_FLAG_STOLEN; + + if ( !iFoundCount ) + { + while((pFlag = UTIL_FindEntityByClassname( pFlag, "item_flag_team1")) != NULL) + { + if ( pFlag ) + { + if ( ((CItemFlag *)pFlag)->Dropped ) + iDropped++; + + iFoundCount++; + } + } + + if ( iFoundCount > 1 && iDropped == 1 ) + iRedFlagStatus = RED_FLAG_DROPPED; + else if ( iFoundCount >= 1 && iDropped == 0 ) + iRedFlagStatus = RED_FLAG_ATBASE; + } + + iDropped = iFoundCount = 0; + + while((pFlag = UTIL_FindEntityByClassname( pFlag, "carried_flag_team2")) != NULL) + { + if ( pFlag && !FBitSet( pFlag->pev->flags, FL_KILLME) ) + iFoundCount++; + } + + if ( iFoundCount >= 1 ) + iBlueFlagStatus = BLUE_FLAG_STOLEN; + + if ( !iFoundCount ) + { + + while((pFlag = UTIL_FindEntityByClassname( pFlag, "item_flag_team2")) != NULL) + { + if ( pFlag ) + { + if ( ((CItemFlag *)pFlag)->Dropped ) + iDropped++; + + iFoundCount++; + } + } + + if ( iFoundCount > 1 && iDropped == 1 ) + iBlueFlagStatus = BLUE_FLAG_DROPPED; + else if ( iFoundCount >= 1 && iDropped == 0 ) + iBlueFlagStatus = BLUE_FLAG_ATBASE; + } + + if ( pPlayer ) + { + if ( pPlayer->pev->team == 0 ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgFlagStatus, NULL, pPlayer->edict() ); + WRITE_BYTE( 0 ); + WRITE_BYTE( iRedFlagStatus ); + WRITE_BYTE( iBlueFlagStatus ); + WRITE_BYTE( iRedTeamScore ); + WRITE_BYTE( iBlueTeamScore ); + MESSAGE_END(); + } + else + { + MESSAGE_BEGIN( MSG_ONE, gmsgFlagStatus, NULL, pPlayer->edict() ); + WRITE_BYTE( 1 ); + WRITE_BYTE( iRedFlagStatus ); + WRITE_BYTE( iBlueFlagStatus ); + WRITE_BYTE( iRedTeamScore ); + WRITE_BYTE( iBlueTeamScore ); + MESSAGE_END(); + } + + pPlayer->m_flFlagStatusTime = 0.0; + } + else + { + MESSAGE_BEGIN( MSG_ALL, gmsgFlagStatus, NULL ); + WRITE_BYTE( 1 ); + WRITE_BYTE( iRedFlagStatus ); + WRITE_BYTE( iBlueFlagStatus ); + WRITE_BYTE( iRedTeamScore ); + WRITE_BYTE( iBlueTeamScore ); + MESSAGE_END(); + + m_flFlagStatusTime = 0.0; + } + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *plr = UTIL_PlayerByIndex( i ); + if ( plr ) + { + if ( ((CBasePlayer *)plr)->m_bHasFlag ) + { + MESSAGE_BEGIN( MSG_ALL, gmsgFlagCarrier, NULL ); + WRITE_BYTE( plr->entindex() ); + WRITE_BYTE( 1 ); + MESSAGE_END(); + } + else + { + MESSAGE_BEGIN( MSG_ALL, gmsgFlagCarrier, NULL ); + WRITE_BYTE( plr->entindex() ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + } + } + } +} + + +//========================================================= +//========================================================= +void CThreeWave::RecountTeams( void ) +{ + char *pName; + char teamlist[TEAMPLAY_TEAMLISTLENGTH]; + + // loop through all teams, recounting everything + num_teams = 0; + + // Copy all of the teams from the teamlist + // make a copy because strtok is destructive + strcpy( teamlist, m_szTeamList ); + pName = teamlist; + pName = strtok( pName, ";" ); + while ( pName != NULL && *pName ) + { + if ( GetTeamIndex( pName ) < 0 ) + { + strcpy( team_names[num_teams], pName ); + num_teams++; + } + pName = strtok( NULL, ";" ); + } + + if ( num_teams < 2 ) + { + num_teams = 0; + m_teamLimit = FALSE; + } + + // Sanity check + memset( team_scores, 0, sizeof(team_scores) ); + + // loop through all clients + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *plr = UTIL_PlayerByIndex( i ); + + if ( plr ) + { + const char *pTeamName = plr->TeamID(); + // try add to existing team + int tm = GetTeamIndex( pTeamName ); + + if ( tm < 0 ) // no team match found + { + if ( !m_teamLimit ) + { + // add to new team + tm = num_teams; + num_teams++; + team_scores[tm] = 0; + strncpy( team_names[tm], pTeamName, MAX_TEAMNAME_LENGTH ); + } + } + + if ( tm >= 0 ) + { + team_scores[tm] += plr->pev->frags; + } + } + } +} + +/***************************************************** +****************************************************** + THREEWAVE CTF FLAG CODE +****************************************************** +*****************************************************/ + +enum Flag_Anims +{ + ON_GROUND = 0, + NOT_CARRIED, + CARRIED, + WAVE_IDLE, + FLAG_POSITION +}; + + +void CItemFlag::Spawn ( void ) +{ + Precache( ); + SET_MODEL(ENT(pev), "models/flag.mdl"); + + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + UTIL_SetOrigin( pev, pev->origin ); + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 16)); + + SetThink( FlagThink ); + SetTouch( FlagTouch ); + + pev->nextthink = gpGlobals->time + 0.3; + + //Set the Skin based on the team. + pev->skin = pev->team; + + Dropped = FALSE; + m_flDroppedTime = 0.0; + + pev->sequence = NOT_CARRIED; + pev->framerate = 1.0; + + // if ( !DROP_TO_FLOOR(ENT(pev)) ) + // ResetFlag( pev->team ); +} + +void CItemFlag::FlagTouch ( CBaseEntity *pToucher ) +{ + if ( !pToucher ) + return; + + if ( !pToucher->IsPlayer() ) + return; + + if ( FBitSet( pev->effects, EF_NODRAW ) ) + return; + + if ( pToucher->pev->health <= 0 ) + return; + + if ( pToucher->pev->team == 0 ) + return; + + CBasePlayer *pPlayer = (CBasePlayer *)pToucher; + + //Same team as the flag + if ( pev->team == pToucher->pev->team ) + { + //Flag is dropped, let's return it + if ( Dropped ) + { + Dropped = FALSE; + + pPlayer->AddPoints( TEAM_CAPTURE_RECOVERY_BONUS, TRUE ); + + if ( pPlayer->pev->team == RED ) + { + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Returned_Red_Flag\"\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + GetTeamName( pPlayer->pev->team ) ); + + if ( ((CThreeWave *) g_pGameRules)->iBlueFlagStatus == BLUE_FLAG_STOLEN ) + { + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pTeamMate = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if ( pTeamMate ) + { + if ( pTeamMate->m_bHasFlag ) + { + pTeamMate->pFlagReturner = pPlayer; + pTeamMate->m_flFlagReturnTime = gpGlobals->time + TEAM_CAPTURE_RETURN_FLAG_ASSIST_TIMEOUT; + } + } + } + } + } + + if ( pPlayer->pev->team == BLUE ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Returned_Blue_Flag\"\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + GetTeamName( pPlayer->pev->team ) ); + + if ( ((CThreeWave *) g_pGameRules)->iRedFlagStatus == RED_FLAG_STOLEN ) + { + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pTeamMate = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if ( pTeamMate ) + { + if ( pTeamMate->m_bHasFlag ) + { + pTeamMate->pFlagReturner = pPlayer; + pTeamMate->m_flFlagReturnTime = gpGlobals->time + TEAM_CAPTURE_RETURN_FLAG_ASSIST_TIMEOUT; + } + } + } + } + } + + //Back at home! + ResetFlag( pev->team ); + + MESSAGE_BEGIN ( MSG_ALL, gmsgCTFMsgs, NULL ); + + if ( pev->team == RED ) + WRITE_BYTE( RED_FLAG_RETURNED_PLAYER ); + else if ( pev->team == BLUE ) + WRITE_BYTE( BLUE_FLAG_RETURNED_PLAYER ); + + WRITE_STRING( STRING(pToucher->pev->netname) ); + MESSAGE_END(); + + //Remove this one + UTIL_Remove( this ); + + return; + } + //Not Dropped, means it's the one in our base + else if ( !Dropped ) + { + //We have the enemy flag! + //Capture it! + if ( pPlayer->m_bHasFlag ) + { + if ( pev->team == RED ) + Capture( pPlayer, BLUE ); + else if ( pev->team == BLUE ) + Capture( pPlayer, RED ); + + PLAYBACK_EVENT_FULL( FEV_GLOBAL | FEV_RELIABLE, + pPlayer->edict(), g_usCarried, 0, (float *)&g_vecZero, (float *)&g_vecZero, + 0.0, 0.0, pPlayer->entindex(), pPlayer->pev->team, 1, 0 ); + + + return; + } + } + } + else + { + if ( Dropped ) + { + MESSAGE_BEGIN ( MSG_ALL, gmsgCTFMsgs, NULL ); + + if ( pev->team == RED ) + WRITE_BYTE( RED_FLAG_STOLEN ); + else if ( pev->team == BLUE ) + WRITE_BYTE( BLUE_FLAG_STOLEN ); + + WRITE_STRING( STRING(pToucher->pev->netname) ); + + MESSAGE_END(); + + pPlayer->m_bHasFlag = TRUE; + + CBaseEntity *pEnt = NULL; + + if ( pev->team == RED ) + { + pEnt = CBaseEntity::Create( "carried_flag_team1", pPlayer->pev->origin, pPlayer->pev->angles, pPlayer->edict() ); + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Picked_Up_Red_Flag\"\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + GetTeamName( pPlayer->pev->team ) ); + } + else if ( pev->team == BLUE ) + { + pEnt = CBaseEntity::Create( "carried_flag_team2", pPlayer->pev->origin, pPlayer->pev->angles, pPlayer->edict() ); + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Picked_Up_Blue_Flag\"\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + GetTeamName( pPlayer->pev->team ) ); + } + + CCarriedFlag *pCarriedFlag = (CCarriedFlag *)pEnt; + pCarriedFlag->Owner = pPlayer; + + PLAYBACK_EVENT_FULL( FEV_GLOBAL | FEV_RELIABLE, + pPlayer->edict(), g_usCarried, 0, (float *)&g_vecZero, (float *)&g_vecZero, + 0.0, 0.0, pPlayer->entindex(), pPlayer->pev->team, 0, 0 ); + + + + UTIL_Remove( this ); + } + else + { + pev->effects |= EF_NODRAW; + + MESSAGE_BEGIN ( MSG_ALL, gmsgCTFMsgs, NULL ); + + if ( pev->team == RED ) + WRITE_BYTE( RED_FLAG_STOLEN ); + else if ( pev->team == BLUE ) + WRITE_BYTE( BLUE_FLAG_STOLEN ); + + WRITE_STRING( STRING(pToucher->pev->netname) ); + + MESSAGE_END(); + + pPlayer->m_bHasFlag = TRUE; + pPlayer->m_flCarrierPickupTime = gpGlobals->time + TEAM_CAPTURE_CARRIER_FLAG_SINCE_TIMEOUT; + + CBaseEntity *pEnt = NULL; + + if ( pev->team == RED ) + { + pEnt = CBaseEntity::Create( "carried_flag_team1", pev->origin, pev->angles, pPlayer->edict() ); + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Stole_Red_Flag\"\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + GetTeamName( pPlayer->pev->team ) ); + } + else if ( pev->team == BLUE ) + { + pEnt = CBaseEntity::Create( "carried_flag_team2", pev->origin, pev->angles, pPlayer->edict() ); + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Stole_Blue_Flag\"\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + GetTeamName( pPlayer->pev->team ) ); + } + + CCarriedFlag *pCarriedFlag = (CCarriedFlag *)pEnt; + pCarriedFlag->Owner = pPlayer; + + PLAYBACK_EVENT_FULL( FEV_GLOBAL | FEV_RELIABLE, + pPlayer->edict(), g_usCarried, 0, (float *)&g_vecZero, (float *)&g_vecZero, + 0.0, 0.0, pPlayer->entindex(), pPlayer->pev->team, 0, 0 ); + } + + ((CThreeWave *) g_pGameRules)->m_flFlagStatusTime = gpGlobals->time + 0.1; + } +} + +void CItemFlag::Capture(CBasePlayer *pPlayer, int iTeam ) +{ + CBaseEntity *pFlag1 = NULL; + + MESSAGE_BEGIN ( MSG_ALL, gmsgCTFMsgs, NULL ); + + if ( iTeam == RED ) + WRITE_BYTE( RED_FLAG_CAPTURED ); + else if ( iTeam == BLUE ) + WRITE_BYTE( BLUE_FLAG_CAPTURED ); + + WRITE_STRING( STRING( pPlayer->pev->netname) ); + + MESSAGE_END(); + + if ( pPlayer->pFlagCarrierKiller ) + { + if ( pPlayer->m_flFlagCarrierKillTime > gpGlobals->time ) + { + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, STRING( pPlayer->pFlagCarrierKiller->pev->netname ) ); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, " gets an assist for fragging the flag carrier!\n"); + + pPlayer->pFlagCarrierKiller->AddPoints( TEAM_CAPTURE_FRAG_CARRIER_ASSIST_BONUS, TRUE ); + pPlayer->pFlagCarrierKiller = NULL; + pPlayer->m_flFlagCarrierKillTime = 0.0; + } + } + + if ( pPlayer->pFlagReturner ) + { + if ( pPlayer->m_flFlagReturnTime > gpGlobals->time ) + { + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, STRING( pPlayer->pFlagReturner->pev->netname ) ); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, " gets an assist for returning his flag!\n"); + + pPlayer->pFlagReturner->AddPoints( TEAM_CAPTURE_RETURN_FLAG_ASSIST_BONUS, TRUE ); + pPlayer->pFlagReturner = NULL; + pPlayer->m_flFlagReturnTime = 0.0; + } + } + + if ( iTeam != pPlayer->pev->team ) + { + if ( iTeam == RED ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Captured_Red_Flag\"\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + GetTeamName( pPlayer->pev->team ) ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Captured_Blue_Flag\"\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + GetTeamName( pPlayer->pev->team ) ); + } + } + + if ( iTeam == RED ) + { + ((CThreeWave *) g_pGameRules)->iBlueTeamScore++; + + while((pFlag1 = UTIL_FindEntityByClassname( pFlag1, "carried_flag_team1")) != NULL) + { + if ( pFlag1 ) + UTIL_Remove( pFlag1 ); + } + } + else if ( iTeam == BLUE ) + { + ((CThreeWave *) g_pGameRules)->iRedTeamScore++; + + while((pFlag1 = UTIL_FindEntityByClassname( pFlag1, "carried_flag_team2")) != NULL) + { + if ( pFlag1 ) + UTIL_Remove( pFlag1 ); + } + } + + pPlayer->m_bHasFlag = FALSE; + + pPlayer->AddPoints( TEAM_CAPTURE_CAPTURE_BONUS, TRUE ); + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pTeamMate = UTIL_PlayerByIndex( i ); + + if ( pTeamMate ) + { + if ( pTeamMate->pev->team == pPlayer->pev->team ) + pTeamMate->AddPoints( TEAM_CAPTURE_TEAM_BONUS, TRUE ); + } + } + + ResetFlag( iTeam ); +} + +void CItemFlag::Materialize( void ) +{ + if ( pev->effects & EF_NODRAW ) + { + pev->effects &= ~EF_NODRAW; + pev->effects |= EF_MUZZLEFLASH; + } + + PLAYBACK_EVENT_FULL( FEV_GLOBAL | FEV_RELIABLE, + edict(), g_usFlagSpawn, 0, (float *)&g_vecZero, (float *)&g_vecZero, + 0.0, 0.0, pev->team, 0, 0, 0 ); + + Dropped = FALSE; + + SetTouch( FlagTouch ); + SetThink( FlagThink ); +} + + +void CItemFlag::ResetFlag( int iTeam ) +{ + CBaseEntity *pFlag1 = NULL; + + if ( iTeam == BLUE ) + { + while((pFlag1 = UTIL_FindEntityByClassname( pFlag1, "item_flag_team2")) != NULL) + { + CItemFlag *pFlag2 = (CItemFlag *)pFlag1; + + if ( pFlag2->Dropped ) + continue; + + if ( pFlag2->pev->effects & EF_NODRAW) + pFlag2->Materialize(); + } + + } + else if ( iTeam == RED ) + { + while((pFlag1 = UTIL_FindEntityByClassname( pFlag1, "item_flag_team1")) != NULL) + { + CItemFlag *pFlag2 = (CItemFlag *)pFlag1; + + if ( pFlag2->Dropped ) + continue; + + if ( pFlag2->pev->effects & EF_NODRAW) + pFlag2->Materialize(); + } + } + + ((CThreeWave *) g_pGameRules)->m_flFlagStatusTime = gpGlobals->time + 0.1; + +} + +void CItemFlag::FlagThink( void ) +{ + if ( Dropped ) + { + if ( m_flDroppedTime <= gpGlobals->time ) + { + + ResetFlag( pev->team ); + + MESSAGE_BEGIN ( MSG_ALL, gmsgCTFMsgs, NULL ); + + if ( pev->team == RED ) + WRITE_BYTE( RED_FLAG_RETURNED ); + else if ( pev->team == BLUE ) + WRITE_BYTE( BLUE_FLAG_RETURNED ); + + WRITE_STRING( "" ); + MESSAGE_END(); + + UTIL_Remove( this ); + return; + } + } + + //Using 0.2 just in case we might lag the server. + pev->nextthink = gpGlobals->time + 0.2; +} + +void CItemFlag::Precache( void ) +{ + PRECACHE_MODEL ("models/flag.mdl"); + PRECACHE_SOUND ("ctf/flagcap.wav"); + PRECACHE_SOUND ("ctf/flagtk.wav"); + PRECACHE_SOUND ("ctf/flagret.wav"); +} + +class CItemFlagTeam1 : public CItemFlag +{ + void Spawn( void ) + { + pev->team = RED; + CItemFlag::Spawn( ); + } +}; + +class CItemFlagTeam2 : public CItemFlag +{ + void Spawn( void ) + { + pev->team = BLUE; + CItemFlag::Spawn( ); + } +}; + +LINK_ENTITY_TO_CLASS( item_flag_team1, CItemFlagTeam1 ); +LINK_ENTITY_TO_CLASS( item_flag_team2, CItemFlagTeam2 ); + + +void CCarriedFlag ::Spawn( ) +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/flag.mdl"); + UTIL_SetOrigin( pev, pev->origin ); + + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT; + + pev->effects |= EF_NODRAW; + + pev->sequence = WAVE_IDLE; + pev->framerate = 1.0; + + if ( pev->team == RED ) + pev->skin = 1; + else if ( pev->team == BLUE ) + pev->skin = 2; + + m_iOwnerOldVel = 0; + + SetThink( FlagThink ); + pev->nextthink = gpGlobals->time + 0.1; +} + +void CCarriedFlag::Precache( ) +{ + PRECACHE_MODEL ("models/flag.mdl"); +} + +void CCarriedFlag::FlagThink( ) +{ + //Make it visible + pev->effects &= ~EF_NODRAW; + + //And let if follow + pev->aiment = ENT(Owner->pev); + pev->movetype = MOVETYPE_FOLLOW; + + //Remove if owner is death + if (!Owner->IsAlive()) + UTIL_Remove( this ); + + //If owner lost flag, remove + if ( !Owner->m_bHasFlag ) + UTIL_Remove( this ); + else + { + //If owners speed is low, go in idle mode + if (Owner->pev->velocity.Length() <= 75 && pev->sequence != WAVE_IDLE) + { + pev->sequence = WAVE_IDLE; + } + //Else let the flag go wild + else if (Owner->pev->velocity.Length() >= 75 && pev->sequence != CARRIED) + { + pev->sequence = CARRIED; + } + pev->frame += pev->framerate; + if (pev->frame < 0.0 || pev->frame >= 256.0) + { + pev->frame -= (int)(pev->frame / 256.0) * 256.0; + } + pev->nextthink = gpGlobals->time + 0.1; + } +} + +class CCarriedFlagTeam1 : public CCarriedFlag +{ + void Spawn( void ) + { + pev->team = RED; + + CCarriedFlag::Spawn( ); + } +}; + +class CCarriedFlagTeam2 : public CCarriedFlag +{ + void Spawn( void ) + { + pev->team = BLUE; + + CCarriedFlag::Spawn( ); + } +}; + +LINK_ENTITY_TO_CLASS( carried_flag_team1, CCarriedFlagTeam1 ); +LINK_ENTITY_TO_CLASS( carried_flag_team2, CCarriedFlagTeam2 ); + + +/*************************************** +**************************************** + RUNES +**************************************** +***************************************/ + +/*---------------------------------------------------------------------- + The Rune Game modes + + Rune 1 - Earth Magic + resistance + Rune 2 - Black Magic + strength + Rune 3 - Hell Magic + haste + Rune 4 - Elder Magic + regeneration + + ----------------------------------------------------------------------*/ + +BOOL IsRuneSpawnPointValid( CBaseEntity *pSpot ) +{ + CBaseEntity *ent = NULL; + + while ( (ent = UTIL_FindEntityInSphere( ent, pSpot->pev->origin, 128 )) != NULL ) + { + //Try not to spawn it near other runes. + if ( !strcmp( STRING( ent->pev->classname ), "item_rune1") || + !strcmp( STRING( ent->pev->classname ), "item_rune2") || + !strcmp( STRING( ent->pev->classname ), "item_rune3") || + !strcmp( STRING( ent->pev->classname ), "item_rune4") ) + return FALSE; + } + + return TRUE; +} + +edict_t *RuneSelectSpawnPoint( void ) +{ + CBaseEntity *pSpot; + + pSpot = NULL; + + // Randomize the start spot + for ( int i = RANDOM_LONG(1,5); i > 0; i-- ) + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + if ( !pSpot ) // skip over the null point + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + + CBaseEntity *pFirstSpot = pSpot; + + do + { + if ( pSpot ) + { + if ( IsRuneSpawnPointValid( pSpot ) ) + { + if ( pSpot->pev->origin == Vector( 0, 0, 0 ) ) + { + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + continue; + } + // if so, go to pSpot + goto ReturnSpot; + } + + } + // increment pSpot + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + } while ( pSpot != pFirstSpot ); // loop if we're not back to the start + + // we haven't found a place to spawn yet, so kill any guy at the first spawn point and spawn there + if ( pSpot ) + goto ReturnSpot; + + // If startspot is set, (re)spawn there. + if ( FStringNull( gpGlobals->startspot ) || !strlen(STRING(gpGlobals->startspot))) + { + pSpot = UTIL_FindEntityByClassname(NULL, "info_player_start"); + if ( pSpot ) + goto ReturnSpot; + } + else + { + pSpot = UTIL_FindEntityByTargetname( NULL, STRING(gpGlobals->startspot) ); + if ( pSpot ) + goto ReturnSpot; + } + +ReturnSpot: + if ( !pSpot ) + { + ALERT(at_error, "PutClientInServer: no info_player_start on level"); + return INDEXENT(0); + } + return pSpot->edict(); +} + +void VectorScale (const float *in, float scale, float *out) +{ + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; +} + +void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result) +{ + result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1]; + result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1]; + result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + distance[2]; +} + +#define VectorSet(v, x, y, z) (v[0]=(x), v[1]=(y), v[2]=(z)) + +void DropRune ( CBasePlayer *pPlayer ) +{ + TraceResult tr; + + // do they even have a rune? + if ( pPlayer->m_iRuneStatus == 0 ) + return; + + // Make Sure there's enough room to drop the rune here + // This is so hacky ( the reason why we are doing this), and I hate it to death. + UTIL_MakeVectors ( pPlayer->pev->v_angle ); + Vector vecSrc = pPlayer->GetGunPosition( ); + Vector vecEnd = vecSrc + gpGlobals->v_forward * 32; + UTIL_TraceHull( vecSrc, vecEnd, dont_ignore_monsters, human_hull, ENT( pPlayer->pev ), &tr ); + + if (tr.flFraction != 1) + { + ClientPrint( pPlayer->pev, HUD_PRINTCENTER, "Not enough room to drop the rune here." ); + return; + } + + CBaseEntity *pRune = NULL; + char * runeName; + + if ( pPlayer->m_iRuneStatus == ITEM_RUNE1_FLAG ) + { + pRune = CBaseEntity::Create( "item_rune1", pPlayer->pev->origin, pPlayer->pev->angles, pPlayer->edict() ); + runeName = "ResistRune"; + + if ( pRune ) + ((CResistRune*)pRune)->dropped = true; + } + else if ( pPlayer->m_iRuneStatus == ITEM_RUNE2_FLAG ) + { + pRune = CBaseEntity::Create( "item_rune2", pPlayer->pev->origin, pPlayer->pev->angles, pPlayer->edict() ); + runeName = "StrengthRune"; + + if ( pRune ) + ((CStrengthRune*)pRune)->dropped = true; + } + else if ( pPlayer->m_iRuneStatus == ITEM_RUNE3_FLAG ) + { + pRune = CBaseEntity::Create( "item_rune3", pPlayer->pev->origin, pPlayer->pev->angles, pPlayer->edict() ); + runeName = "HasteRune"; + + if ( pRune ) + ((CHasteRune*)pRune)->dropped = true; + } + else if ( pPlayer->m_iRuneStatus == ITEM_RUNE4_FLAG ) + { + pRune = CBaseEntity::Create( "item_rune4", pPlayer->pev->origin, pPlayer->pev->angles, pPlayer->edict() ); + runeName = "RegenRune"; + + if ( pRune ) + ((CRegenRune*)pRune)->dropped = true; + } + else + { + runeName = "Unknown"; + } + + if ( pPlayer->m_iRuneStatus == ITEM_RUNE3_FLAG ) + g_engfuncs.pfnSetClientMaxspeed( ENT( pPlayer->pev ), PLAYER_MAX_SPEED ); //Reset Haste player speed to normal + + pPlayer->m_iRuneStatus = 0; + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Dropped_%s\"\n", + STRING(pPlayer->pev->netname), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + pPlayer->m_szTeamName, + runeName ); + + MESSAGE_BEGIN( MSG_ONE, gmsgRuneStatus, NULL, pPlayer->pev); + WRITE_BYTE( pPlayer->m_iRuneStatus ); + MESSAGE_END(); +} + + +void CResistRune::RuneTouch ( CBaseEntity *pOther ) +{ + //No toucher? + if ( !pOther ) + return; + + //Not a player? + if ( !pOther->IsPlayer() ) + return; + + //DEAD?! + if ( pOther->pev->health <= 0 ) + return; + + //Spectating? + if ( pOther->pev->movetype == MOVETYPE_NOCLIP ) + return; + + //Only one per customer + if ( ((CBasePlayer *)pOther)->m_iRuneStatus ) + { + ClientPrint( pOther->pev, HUD_PRINTCENTER, "You already have a rune!\n" ); + return; + } + + if ( !m_bTouchable ) + return; + + ((CBasePlayer *)pOther)->m_iRuneStatus = m_iRuneFlag; //Add me the rune flag + + ClientPrint( pOther->pev, HUD_PRINTCENTER, "You got the rune of Resistance!\n" ); + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Found_ResistRune\"\n", + STRING(pOther->pev->netname), + GETPLAYERUSERID( pOther->edict() ), + GETPLAYERAUTHID( pOther->edict() ), + ((CBasePlayer *)pOther)->m_szTeamName ); + + EMIT_SOUND( ENT(pev), CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM ); + + //Update my client side rune hud thingy. + MESSAGE_BEGIN( MSG_ONE, gmsgRuneStatus, NULL, pOther->pev); + WRITE_BYTE( ((CBasePlayer *)pOther)->m_iRuneStatus ); + MESSAGE_END(); + + //And Remove this entity + UTIL_Remove( this ); +} + + +void CResistRune::RuneRespawn ( void ) +{ + edict_t *pentSpawnSpot; + vec3_t vOrigin; + + pentSpawnSpot = RuneSelectSpawnPoint(); + vOrigin = VARS(pentSpawnSpot)->origin; + + UTIL_SetOrigin( pev, vOrigin ); + + if ( dropped ) + UTIL_LogPrintf( "\"<-1><><>\" triggered triggered \"Respawn_ResistRune\"\n" ); + + Spawn(); +} + +void CResistRune::MakeTouchable ( void ) +{ + m_bTouchable = TRUE; + pev->nextthink = gpGlobals->time + 120; // if no one touches it in two minutes, + // respawn it somewhere else, so inaccessible + // ones will come 'back' + SetThink ( RuneRespawn ); +} + +void CResistRune::Spawn ( void ) +{ + SET_MODEL( ENT(pev), "models/rune_resist.mdl"); + + m_bTouchable = FALSE; + + m_iRuneFlag = ITEM_RUNE1_FLAG; + + dropped = false; + + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + + vec3_t forward, right, up; + + UTIL_SetSize( pev, Vector(-15, -15, -15), Vector(15, 15, 15) ); + + pev->angles.z = pev->angles.x = 0; + pev->angles.y = RANDOM_LONG ( 0, 360 ); + + //If we got an owner, it means we are either dropping the flag or diying and letting it go. + if ( pev->owner ) + g_engfuncs.pfnAngleVectors ( pev->owner->v.angles, forward, right, up ); + else + g_engfuncs.pfnAngleVectors ( pev->angles, forward, right, up); + + UTIL_SetOrigin( pev, pev->origin ); + + pev->velocity = ( forward * 400 ) + ( up * 200 ); + + if ( pev->owner == NULL ) + { + pev->origin.z += 16; + pev->velocity.z = 300; + } + + pev->owner = NULL; + + SetTouch( RuneTouch ); + + pev->nextthink = gpGlobals->time + 1; + SetThink ( MakeTouchable ); +} + + +LINK_ENTITY_TO_CLASS( item_rune1, CResistRune ); + + +void CStrengthRune::MakeTouchable ( void ) +{ + m_bTouchable = TRUE; + pev->nextthink = gpGlobals->time + 120; // if no one touches it in two minutes, + // respawn it somewhere else, so inaccessible + // ones will come 'back' + SetThink ( RuneRespawn ); +} + +void CStrengthRune::RuneTouch ( CBaseEntity *pOther ) +{ + //No toucher? + if ( !pOther ) + return; + + //Not a player? + if ( !pOther->IsPlayer() ) + return; + + //DEAD?! + if ( pOther->pev->health <= 0 ) + return; + + //Spectating? + if ( pOther->pev->movetype == MOVETYPE_NOCLIP ) + return; + + //Only one per customer + if ( ((CBasePlayer *)pOther)->m_iRuneStatus ) + { + ClientPrint( pOther->pev, HUD_PRINTCENTER, "You already have a rune!\n" ); + return; + } + + if ( !m_bTouchable ) + return; + + ((CBasePlayer *)pOther)->m_iRuneStatus = m_iRuneFlag; //Add me the rune flag + + ClientPrint( pOther->pev, HUD_PRINTCENTER, "You got the rune of Strength!\n" ); + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Found_StrengthRune\"\n", + STRING(pOther->pev->netname), + GETPLAYERUSERID( pOther->edict() ), + GETPLAYERAUTHID( pOther->edict() ), + ((CBasePlayer *)pOther)->m_szTeamName ); + + EMIT_SOUND( ENT(pev), CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM ); + + //Update my client side rune hud thingy. + MESSAGE_BEGIN( MSG_ONE, gmsgRuneStatus, NULL, pOther->pev); + WRITE_BYTE( ((CBasePlayer *)pOther)->m_iRuneStatus ); + MESSAGE_END(); + + //And Remove this entity + UTIL_Remove( this ); +} + +void CStrengthRune::RuneRespawn ( void ) +{ + edict_t *pentSpawnSpot; + vec3_t vOrigin; + + pentSpawnSpot = RuneSelectSpawnPoint(); + vOrigin = VARS(pentSpawnSpot)->origin; + + UTIL_SetOrigin( pev, vOrigin ); + + if ( dropped ) + UTIL_LogPrintf( "\"<-1><><>\" triggered triggered \"Respawn_StrengthRune\"\n" ); + + Spawn(); +} + + +void CStrengthRune::Spawn ( void ) +{ + SET_MODEL( ENT(pev), "models/rune_strength.mdl"); + + m_bTouchable = FALSE; + + m_iRuneFlag = ITEM_RUNE2_FLAG; + + dropped = false; + + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + + vec3_t forward, right, up; + + UTIL_SetSize( pev, Vector(-15, -15, -15), Vector(15, 15, 15) ); + + pev->angles.z = pev->angles.x = 0; + pev->angles.y = RANDOM_LONG ( 0, 360 ); + + //If we got an owner, it means we are either dropping the flag or diying and letting it go. + if ( pev->owner ) + g_engfuncs.pfnAngleVectors ( pev->owner->v.angles, forward, right, up); + else + g_engfuncs.pfnAngleVectors ( pev->angles, forward, right, up); + + UTIL_SetOrigin( pev, pev->origin ); + + pev->velocity = ( forward * 400 ) + ( up * 200 ); + + if ( pev->owner == NULL ) + { + pev->origin.z += 16; + pev->velocity.z = 300; + } + + pev->owner = NULL; + + SetTouch( RuneTouch ); + + pev->nextthink = gpGlobals->time + 1; + SetThink ( MakeTouchable ); +} + + +LINK_ENTITY_TO_CLASS( item_rune2, CStrengthRune ); + +void CHasteRune::MakeTouchable ( void ) +{ + m_bTouchable = TRUE; + pev->nextthink = gpGlobals->time + 120; // if no one touches it in two minutes, + // respawn it somewhere else, so inaccessible + // ones will come 'back' + SetThink ( RuneRespawn ); +} + + +void CHasteRune::RuneTouch ( CBaseEntity *pOther ) +{ + //No toucher? + if ( !pOther ) + return; + + //Not a player? + if ( !pOther->IsPlayer() ) + return; + + //DEAD?! + if ( pOther->pev->health <= 0 ) + return; + + //Spectating? + if ( pOther->pev->movetype == MOVETYPE_NOCLIP ) + return; + + //Only one per customer + if ( ((CBasePlayer *)pOther)->m_iRuneStatus ) + { + ClientPrint( pOther->pev, HUD_PRINTCENTER, "You already have a rune!\n" ); + return; + } + + if ( !m_bTouchable ) + return; + + ((CBasePlayer *)pOther)->m_iRuneStatus = m_iRuneFlag; //Add me the rune flag + + ClientPrint( pOther->pev, HUD_PRINTCENTER, "You got the rune of Haste!\n" ); + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Found_HasteRune\"\n", + STRING(pOther->pev->netname), + GETPLAYERUSERID( pOther->edict() ), + GETPLAYERAUTHID( pOther->edict() ), + ((CBasePlayer *)pOther)->m_szTeamName ); + + g_engfuncs.pfnSetClientMaxspeed( ENT( pOther->pev ), ( PLAYER_MAX_SPEED * 1.25 ) ); //25% more speed + + EMIT_SOUND( ENT(pev), CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM ); + + //Update my client side rune hud thingy. + MESSAGE_BEGIN( MSG_ONE, gmsgRuneStatus, NULL, pOther->pev); + WRITE_BYTE( ((CBasePlayer *)pOther)->m_iRuneStatus ); + MESSAGE_END(); + + //And Remove this entity + UTIL_Remove( this ); +} + +void CHasteRune::RuneRespawn ( void ) +{ + edict_t *pentSpawnSpot; + vec3_t vOrigin; + + pentSpawnSpot = RuneSelectSpawnPoint(); + vOrigin = VARS(pentSpawnSpot)->origin; + + UTIL_SetOrigin( pev, vOrigin ); + + if ( dropped ) + UTIL_LogPrintf( "\"<-1><><>\" triggered triggered \"Respawn_HasteRune\"\n" ); + + Spawn(); +} + + +void CHasteRune::Spawn ( void ) +{ + SET_MODEL( ENT(pev), "models/rune_haste.mdl"); + + m_bTouchable = FALSE; + + m_iRuneFlag = ITEM_RUNE3_FLAG; + + dropped = false; + + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + + vec3_t forward, right, up; + + UTIL_SetSize( pev, Vector(-15, -15, -15), Vector(15, 15, 15) ); + + pev->angles.z = pev->angles.x = 0; + pev->angles.y = RANDOM_LONG ( 0, 360 ); + + //If we got an owner, it means we are either dropping the flag or diying and letting it go. + if ( pev->owner ) + g_engfuncs.pfnAngleVectors ( pev->owner->v.angles, forward, right, up); + else + g_engfuncs.pfnAngleVectors ( pev->angles, forward, right, up); + + UTIL_SetOrigin( pev, pev->origin ); + + pev->velocity = ( forward * 400 ) + ( up * 200 ); + + if ( pev->owner == NULL ) + { + pev->origin.z += 16; + pev->velocity.z = 300; + } + + pev->owner = NULL; + + SetTouch( RuneTouch ); + + pev->nextthink = gpGlobals->time + 1; // if no one touches it in two minutes, + // respawn it somewhere else, so inaccessible + // ones will come 'back' + SetThink ( MakeTouchable ); +} + + +LINK_ENTITY_TO_CLASS( item_rune3, CHasteRune ); + + +void CRegenRune::MakeTouchable ( void ) +{ + m_bTouchable = TRUE; + pev->nextthink = gpGlobals->time + 120; // if no one touches it in two minutes, + // respawn it somewhere else, so inaccessible + // ones will come 'back' + SetThink ( RuneRespawn ); +} + +void CRegenRune::RuneTouch ( CBaseEntity *pOther ) +{ + //No toucher? + if ( !pOther ) + return; + + //Not a player? + if ( !pOther->IsPlayer() ) + return; + + //DEAD?! + if ( pOther->pev->health <= 0 ) + return; + + //Spectating? + if ( pOther->pev->movetype == MOVETYPE_NOCLIP ) + return; + + //Only one per customer + if ( ((CBasePlayer *)pOther)->m_iRuneStatus ) + { + ClientPrint( pOther->pev, HUD_PRINTCENTER, "You already have a rune!\n" ); + return; + } + + if ( !m_bTouchable ) + return; + + ((CBasePlayer *)pOther)->m_iRuneStatus = m_iRuneFlag; //Add me the rune flag + + ClientPrint( pOther->pev, HUD_PRINTCENTER, "You got the rune of Regeneration!\n" ); + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Found_RegenRune\"\n", + STRING(pOther->pev->netname), + GETPLAYERUSERID( pOther->edict() ), + GETPLAYERAUTHID( pOther->edict() ), + ((CBasePlayer *)pOther)->m_szTeamName ); + + EMIT_SOUND( ENT(pev), CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM ); + + //Update my client side rune hud thingy. + MESSAGE_BEGIN( MSG_ONE, gmsgRuneStatus, NULL, pOther->pev); + WRITE_BYTE( ((CBasePlayer *)pOther)->m_iRuneStatus ); + MESSAGE_END(); + + //And Remove this entity + UTIL_Remove( this ); +} + +void CRegenRune::RuneRespawn ( void ) +{ + edict_t *pentSpawnSpot; + vec3_t vOrigin; + + pentSpawnSpot = RuneSelectSpawnPoint(); + vOrigin = VARS(pentSpawnSpot)->origin; + + UTIL_SetOrigin( pev, vOrigin ); + + if ( dropped ) + UTIL_LogPrintf( "\"<-1><><>\" triggered triggered \"Respawn_RegenRune\"\n" ); + + Spawn(); +} + + +void CRegenRune::Spawn ( void ) +{ + + SET_MODEL( ENT(pev), "models/rune_regen.mdl" ); + + m_bTouchable = FALSE; + + m_iRuneFlag = ITEM_RUNE4_FLAG; + + dropped = false; + + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + + vec3_t forward, right, up; + + UTIL_SetSize( pev, Vector(-15, -15, -15), Vector(15, 15, 15) ); + + pev->angles.z = pev->angles.x = 0; + pev->angles.y = RANDOM_LONG ( 0, 360 ); + + //If we got an owner, it means we are either dropping the flag or diying and letting it go. + if ( pev->owner ) + g_engfuncs.pfnAngleVectors ( pev->owner->v.angles, forward, right, up); + else + g_engfuncs.pfnAngleVectors ( pev->angles, forward, right, up); + + UTIL_SetOrigin( pev, pev->origin ); + + pev->velocity = ( forward * 400 ) + ( up * 200 ); + + if ( pev->owner == NULL ) + { + pev->origin.z += 16; + pev->velocity.z = 300; + } + + pev->owner = NULL; + + SetTouch( RuneTouch ); + + pev->nextthink = gpGlobals->time + 1; // if no one touches it in two minutes, + // respawn it somewhere else, so inaccessible + // ones will come 'back' + SetThink ( MakeTouchable ); +} + + +LINK_ENTITY_TO_CLASS( item_rune4, CRegenRune ); + +/* +================ +SpawnRunes +spawn all the runes +self is the entity that was created for us, we remove it +================ +*/ +void SpawnRunes( void ) +{ + if ( g_bSpawnedRunes ) + return; + + edict_t *pentSpawnSpot; + + pentSpawnSpot = RuneSelectSpawnPoint(); + CBaseEntity::Create( "item_rune1", VARS(pentSpawnSpot)->origin, VARS(pentSpawnSpot)->angles, NULL ); + + pentSpawnSpot = RuneSelectSpawnPoint(); + CBaseEntity::Create( "item_rune2", VARS(pentSpawnSpot)->origin, VARS(pentSpawnSpot)->angles, NULL ); + + pentSpawnSpot = RuneSelectSpawnPoint(); + CBaseEntity::Create( "item_rune3", VARS(pentSpawnSpot)->origin, VARS(pentSpawnSpot)->angles, NULL ); + + pentSpawnSpot = RuneSelectSpawnPoint(); + CBaseEntity::Create( "item_rune4", VARS(pentSpawnSpot)->origin, VARS(pentSpawnSpot)->angles, NULL ); + + g_bSpawnedRunes = TRUE; +} + + +/*********************************************** +************************************************ + GRAPPLE +************************************************ +***********************************************/ + +void CGrapple::Reset_Grapple ( void ) +{ + CBaseEntity *pOwner = CBaseEntity::Instance( pev->owner ); + + ((CBasePlayer *)pOwner)->m_bOn_Hook = FALSE; + ((CBasePlayer *)pOwner)->m_bHook_Out = FALSE; + + PLAYBACK_EVENT_FULL( FEV_GLOBAL | FEV_RELIABLE, + ((CBasePlayer *)pOwner)->edict(), g_usCable, 0, (float *)&g_vecZero, (float *)&g_vecZero, + 0.0, 0.0, entindex(), pev->team, 1, 0 ); + + STOP_SOUND( edict(), CHAN_WEAPON, "weapons/grhang.wav" ); + STOP_SOUND( ((CBasePlayer *)pOwner)->edict(), CHAN_WEAPON, "weapons/grfire.wav" ); + STOP_SOUND( ((CBasePlayer *)pOwner)->edict(), CHAN_WEAPON, "weapons/grpull.wav" ); + + ((CBasePlayer *)pOwner)->m_ppHook = NULL; + pev->enemy = NULL; + + UTIL_Remove ( this ); +} + +void CGrapple::GrappleTouch ( CBaseEntity *pOther ) +{ + CBaseEntity *pOwner = CBaseEntity::Instance( pev->owner ); + + if ( pOther == pOwner ) + return; + + + // DO NOT allow the grapple to hook to any projectiles, no matter WHAT! + // if you create new types of projectiles, make sure you use one of the + // classnames below or write code to exclude your new classname so + // grapples will not stick to them. + if ( FClassnameIs( pOther->pev, "grenade" )|| + FClassnameIs( pOther->pev, "spike" ) || + FClassnameIs( pOther->pev, "hook" ) ) + return; + + if ( FClassnameIs( pOther->pev, "player" ) ) + { + // glance off of teammates + if ( pOther->pev->team == pOwner->pev->team ) + return; + + // sound (self, CHAN_WEAPON, "player/axhit1.wav", 1, ATTN_NORM); + //TakeDamage( pOther->pev, pOwner->pev, 10, DMG_GENERIC ); + + // make hook invisible since we will be pulling directly + // towards the player the hook hit. Quakeworld makes it + // too quirky to try to match hook's velocity with that of + // the client that it hit. + // setmodel (self, ""); + + pev->velocity = Vector(0,0,0); + UTIL_SetOrigin( pev, pOther->pev->origin); + } + else if ( !FClassnameIs( pOther->pev, "player" ) ) + { + // sound (self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM); + + // One point of damage inflicted upon impact. Subsequent + // damage will only be done to PLAYERS... this way secret + // doors and triggers will only be damaged once. + if ( pOther->pev->takedamage ) + TakeDamage( pOther->pev, pOwner->pev, 1, DMG_GENERIC ); + + pev->velocity = Vector(0,0,0); + + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "weapons/grhit.wav", 1, ATTN_NORM); + + //No sparks underwater + if ( pev->waterlevel == 0 ) + UTIL_Sparks( pev->origin ); + } + + // conveniently clears the sound channel of the CHAIN1 sound, + // which is a looping sample and would continue to play. Tink1 is + // the least offensive choice, ass NULL.WAV loops and clogs the + // channel with silence + // sound (self.owner, CHAN_NO_PHS_ADD+CHAN_WEAPON, "weapons/tink1.wav", 1, ATTN_NORM); + + if ( !(pOwner->pev->button & IN_ATTACK) ) + { + if ( ((CBasePlayer*)pOwner)->m_bOn_Hook ) + { + Reset_Grapple(); + return; + } + } + + + if ( pOwner->pev->flags & FL_ONGROUND) + { + pOwner->pev->flags &= ~FL_ONGROUND; +// setorigin(self.owner,self.owner.origin + '0 0 1'); + } + + ((CBasePlayer*)pOwner)->m_bOn_Hook = TRUE; + + // sound (self.owner, CHAN_WEAPON, "weapons/chain2.wav", 1, ATTN_NORM); + + // CHAIN2 is a looping sample. Use LEFTY as a flag so that client.qc + // will know to only play the tink sound ONCE to clear the weapons + // sound channel. (Lefty is a leftover from AI.QC, so I reused it to + // avoid adding a field) + //self.owner.lefty = TRUE; + + STOP_SOUND( ((CBasePlayer *)pOwner)->edict(), CHAN_WEAPON, "weapons/grfire.wav" ); + + pev->enemy = pOther->edict();// remember this guy! + SetThink ( Grapple_Track ); + pev->nextthink = gpGlobals->time; + m_flNextIdleTime = gpGlobals->time + 0.1; + pev->solid = SOLID_NOT; + SetTouch ( NULL ); +}; + +bool CanSee ( CBaseEntity *pEnemy, CBaseEntity *pOwner ) +{ + TraceResult tr; + + UTIL_TraceLine ( pOwner->pev->origin, pEnemy->pev->origin, ignore_monsters, ENT( pOwner->pev ), &tr); + if ( tr.flFraction == 1 ) + return TRUE; + + UTIL_TraceLine ( pOwner->pev->origin, pEnemy->pev->origin + Vector( 15, 15, 0 ), ignore_monsters, ENT( pOwner->pev ), &tr); + if ( tr.flFraction == 1 ) + return TRUE; + + UTIL_TraceLine ( pOwner->pev->origin, pEnemy->pev->origin + Vector( -15, -15, 0 ), ignore_monsters, ENT( pOwner->pev ), &tr); + if ( tr.flFraction == 1 ) + return TRUE; + + UTIL_TraceLine ( pOwner->pev->origin, pEnemy->pev->origin + Vector( -15, 15, 0 ), ignore_monsters, ENT( pOwner->pev ), &tr); + if ( tr.flFraction == 1 ) + return TRUE; + + UTIL_TraceLine ( pOwner->pev->origin, pEnemy->pev->origin + Vector( 15, -15, 0 ), ignore_monsters, ENT( pOwner->pev ), &tr); + if ( tr.flFraction == 1 ) + return TRUE; + + return FALSE; +} + +void CGrapple::Grapple_Track ( void ) +{ + CBaseEntity *pOwner = CBaseEntity::Instance( pev->owner ); + CBaseEntity *pEnemy = CBaseEntity::Instance( pev->enemy ); + + // Release dead targets + if ( FClassnameIs( pEnemy->pev, "player" ) && pEnemy->pev->health <= 0) + Reset_Grapple(); + + + // drop the hook if owner is dead or has released the button + if ( !((CBasePlayer*)pOwner)->m_bOn_Hook|| ((CBasePlayer*)pOwner)->pev->health <= 0) + { + Reset_Grapple(); + return; + } + + if ( !(pOwner->pev->button & IN_ATTACK) ) + { + if ( ((CBasePlayer*)pOwner)->m_iQuakeWeapon == IT_EXTRA_WEAPON ) + { + Reset_Grapple(); + return; + } + } + + // bring the pAiN! + if ( FClassnameIs( pEnemy->pev, "player" ) ) + { + if ( !CanSee( pEnemy, pOwner ) ) + { + Reset_Grapple(); + return; + } + + + // move the hook along with the player. It's invisible, but + // we need this to make the sound come from the right spot + UTIL_SetOrigin( pev, pEnemy->pev->origin); + + //sound (self, CHAN_WEAPON, "blob/land1.wav", 1, ATTN_NORM); + + SpawnBlood( pEnemy->pev->origin, BLOOD_COLOR_RED, 1 ); + ((CBasePlayer *)pEnemy)->TakeDamage( pev, pOwner->pev, 1, DMG_GENERIC ); + } + + // If the hook is not attached to the player, constantly copy + // copy the target's velocity. Velocity copying DOES NOT work properly + // for a hooked client. + if ( !FClassnameIs( pEnemy->pev, "player" ) ) + pev->velocity = pEnemy->pev->velocity; + + pev->nextthink = gpGlobals->time + 0.1; +}; + +void CBasePlayer::Service_Grapple ( void ) +{ + Vector hook_dir; + CBaseEntity *pEnemy = CBaseEntity::Instance( pev->enemy ); + + // drop the hook if player lets go of button + if ( !(pev->button & IN_ATTACK) ) + { + if ( m_iQuakeWeapon == IT_EXTRA_WEAPON ) + { + ((CGrapple *)m_ppHook)->Reset_Grapple(); + return; + } + } + + if ( m_ppHook->pev->enemy != NULL ) + { + // If hooked to a player, track them directly! + if ( FClassnameIs( pEnemy->pev, "player" ) ) + { + pEnemy = CBaseEntity::Instance( pev->enemy ); + hook_dir = ( pEnemy->pev->origin - pev->origin ); + } + // else, track to hook + else if ( !FClassnameIs( pEnemy->pev, "player" ) ) + hook_dir = ( m_ppHook->pev->origin - pev->origin ); + + pev->velocity = ( (hook_dir).Normalize() * 750 ); + pev->speed = 750; + + if ( ((CGrapple *)m_ppHook)->m_flNextIdleTime <= gpGlobals->time && (hook_dir).Length() <= 50 ) + { + //No sparks underwater + if ( m_ppHook->pev->waterlevel == 0 ) + UTIL_Sparks( m_ppHook->pev->origin ); + + STOP_SOUND( edict(), CHAN_WEAPON, "weapons/grpull.wav" ); + EMIT_SOUND( ENT( m_ppHook->pev ), CHAN_WEAPON, "weapons/grhang.wav", 1, ATTN_NORM); + + ((CGrapple *)m_ppHook)->m_flNextIdleTime = gpGlobals->time + RANDOM_LONG( 1, 3 ); + + PLAYBACK_EVENT_FULL( FEV_GLOBAL | FEV_RELIABLE, + edict(), g_usCable, 0, (float *)&g_vecZero, (float *)&g_vecZero, + 0.0, 0.0, m_ppHook->entindex(), pev->team, 1, 0 ); + + } + else if ( ((CGrapple *)m_ppHook)->m_flNextIdleTime <= gpGlobals->time ) + { + //No sparks underwater + if ( m_ppHook->pev->waterlevel == 0 ) + UTIL_Sparks( m_ppHook->pev->origin ); + + STOP_SOUND( edict(), CHAN_WEAPON, "weapons/grfire.wav" ); + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "weapons/grpull.wav", 1, ATTN_NORM); + ((CGrapple *)m_ppHook)->m_flNextIdleTime = gpGlobals->time + RANDOM_LONG( 1, 3 ); + } + + } +}; + +void CGrapple::OnAirThink ( void ) +{ + TraceResult tr; + + CBaseEntity *pOwner = CBaseEntity::Instance( pev->owner ); + + if ( !(pOwner->pev->button & IN_ATTACK) ) + { + Reset_Grapple(); + return; + } + + UTIL_TraceLine ( pev->origin, pOwner->pev->origin, ignore_monsters, ENT(pev), &tr); + + if ( tr.flFraction < 1.0 ) + { + Reset_Grapple(); + return; + } + + pev->nextthink = gpGlobals->time + 0.5; +} + + + +void CGrapple::Spawn ( void ) +{ + pev->movetype = MOVETYPE_FLYMISSILE; + pev->solid = SOLID_BBOX; + + SET_MODEL ( ENT(pev),"models/hook.mdl"); + + SetTouch ( GrappleTouch ); + SetThink ( OnAirThink ); + + pev->nextthink = gpGlobals->time + 0.1; +} + +LINK_ENTITY_TO_CLASS( hook, CGrapple ); + +void CBasePlayer::Throw_Grapple ( void ) +{ + if ( m_bHook_Out ) + return; + + CBaseEntity *pHookCBEnt = NULL; + + pHookCBEnt = CBaseEntity::Create( "hook", pev->origin, pev->angles, NULL ); + + if ( pHookCBEnt ) + { + m_ppHook = pHookCBEnt; + + m_ppHook->pev->owner = edict(); + + UTIL_MakeVectors ( pev->v_angle); + + UTIL_SetOrigin ( m_ppHook->pev , pev->origin + gpGlobals->v_forward * 16 + Vector( 0, 0, 16 ) ); + UTIL_SetSize( m_ppHook->pev, Vector(0,0,0) , Vector(0,0,0) ); + + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "weapons/grfire.wav", 1, ATTN_NORM); + + //Make if fly forward + m_ppHook->pev->velocity = gpGlobals->v_forward * 1000; + //And make the hook face forward too! + m_ppHook->pev->angles = UTIL_VecToAngles ( gpGlobals->v_forward ); + m_ppHook->pev->fixangle = TRUE; + + PLAYBACK_EVENT_FULL( FEV_GLOBAL | FEV_RELIABLE, + edict(), g_usCable, 0, (float *)&g_vecZero, (float *)&g_vecZero, + 0.0, 0.0, m_ppHook->entindex(), pev->team, 0, 0 ); + + + m_bHook_Out = TRUE; + } +}; + + + +#endif diff --git a/dmc/dlls/threewave_gamerules.h b/dmc/dlls/threewave_gamerules.h new file mode 100644 index 0000000..9f7d148 --- /dev/null +++ b/dmc/dlls/threewave_gamerules.h @@ -0,0 +1,221 @@ +#ifdef THREEWAVE + +#define BLUE 2 +#define RED 1 + + +#include "voice_gamemgr.h" + + + +//========================================================= +// Flags +//========================================================= +class CItemFlag : public CBaseEntity +{ +public: + void Spawn( void ); + + BOOL Dropped; + float m_flDroppedTime; + + void EXPORT FlagThink( void ); + +private: + void Precache ( void ); + void Capture(CBasePlayer *pPlayer, int iTeam ); + void ResetFlag( int iTeam ); + void Materialize( void ); + void EXPORT FlagTouch( CBaseEntity *pOther ); + // BOOL MyTouch( CBasePlayer *pPlayer ); + +}; + +class CCarriedFlag : public CBaseEntity +{ +public: + void Spawn( void ); + + CBasePlayer *Owner; + + int m_iOwnerOldVel; + +private: + void Precache ( void ); + void EXPORT FlagThink( void ); +}; + + +class CResistRune : public CBaseEntity +{ +private: + + void EXPORT RuneRespawn ( void ); + +public: + + void EXPORT RuneTouch ( CBaseEntity *pOther ); + void Spawn( void ); + + void EXPORT MakeTouchable ( void ); + + int m_iRuneFlag; + bool m_bTouchable; + bool dropped; +}; + +class CStrengthRune : public CBaseEntity +{ + +private: + void EXPORT RuneRespawn ( void ); + +public: + + void EXPORT RuneTouch ( CBaseEntity *pOther ); + void Spawn( void ); + + void EXPORT MakeTouchable ( void ); + + int m_iRuneFlag; + bool m_bTouchable; + bool dropped; +}; + + +class CHasteRune : public CBaseEntity +{ + +private: + void EXPORT RuneRespawn ( void ); +public: + + void EXPORT RuneTouch ( CBaseEntity *pOther ); + + void EXPORT MakeTouchable ( void ); + void Spawn( void ); + + int m_iRuneFlag; + bool m_bTouchable; + bool dropped; +}; + + +class CRegenRune : public CBaseEntity +{ + +private: + void EXPORT RuneRespawn ( void ); + +public: + + void EXPORT RuneTouch ( CBaseEntity *pOther ); + void Spawn( void ); + + void EXPORT MakeTouchable ( void ); + + int m_iRuneFlag; + bool m_bTouchable; + bool dropped; +}; + +class CGrapple : public CBaseEntity +{ +public: + + //Yes, I have no imagination so I use standard touch, spawn and think function names. + //Sue me! =P. + void Spawn ( void ); + void EXPORT OnAirThink ( void ); + void EXPORT GrappleTouch ( CBaseEntity *pOther ); + void Reset_Grapple ( void ); + void EXPORT Grapple_Track ( void ); + + float m_flNextIdleTime; + +}; + + + +#define STEAL_SOUND 1 +#define CAPTURE_SOUND 2 +#define RETURN_SOUND 3 + +#define RED_FLAG_STOLEN 1 +#define BLUE_FLAG_STOLEN 2 +#define RED_FLAG_CAPTURED 3 +#define BLUE_FLAG_CAPTURED 4 +#define RED_FLAG_RETURNED_PLAYER 5 +#define BLUE_FLAG_RETURNED_PLAYER 6 +#define RED_FLAG_RETURNED 7 +#define BLUE_FLAG_RETURNED 8 +#define RED_FLAG_LOST 9 +#define BLUE_FLAG_LOST 10 + +#define RED_FLAG_STOLEN 1 +#define BLUE_FLAG_STOLEN 2 +#define RED_FLAG_DROPPED 3 +#define BLUE_FLAG_DROPPED 4 +#define RED_FLAG_ATBASE 5 +#define BLUE_FLAG_ATBASE 6 + + +#define MAX_TEAMNAME_LENGTH 16 +#define MAX_TEAMS 32 + +#define TEAMPLAY_TEAMLISTLENGTH MAX_TEAMS*MAX_TEAMNAME_LENGTH + +class CThreeWave : public CHalfLifeMultiplay +{ +public: + CThreeWave(); + + virtual BOOL ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); + virtual BOOL ClientCommand( CBasePlayer *pPlayer, const char *pcmd ); + virtual void ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ); + virtual BOOL IsTeamplay( void ); + virtual BOOL FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ); + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); + virtual const char *GetTeamID( CBaseEntity *pEntity ); + virtual BOOL ShouldAutoAim( CBasePlayer *pPlayer, edict_t *target ); + virtual int IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ); + virtual void InitHUD( CBasePlayer *pl ); + virtual void DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pevInflictor ); + virtual const char *GetGameDescription( void ) { return "3Wave CTF"; } // this is the game name that gets seen in the server browser + virtual void UpdateGameMode( CBasePlayer *pPlayer ); // the client needs to be informed of the current game mode + virtual void PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); + virtual void Think ( void ); + virtual int GetTeamIndex( const char *pTeamName ); + virtual const char *GetIndexedTeamName( int teamIndex ); + virtual BOOL IsValidTeam( const char *pTeamName ); + virtual void ChangePlayerTeam( CBasePlayer *pPlayer, int iTeam ); + virtual void PlayerSpawn( CBasePlayer *pPlayer ); + void JoinTeam ( CBasePlayer *pPlayer, int iTeam ); + int TeamWithFewestPlayers ( void ); + virtual void ClientDisconnected( edict_t *pClient ); + void GetFlagStatus( CBasePlayer *pPlayer ); + + virtual edict_t *GetPlayerSpawnSpot( CBasePlayer *pPlayer ); + + virtual void PlayerThink( CBasePlayer *pPlayer ); + + void PlayerTakeDamage( CBasePlayer *pPlayer , CBaseEntity *pAttacker ); + + int iBlueFlagStatus; + int iRedFlagStatus; + + int iBlueTeamScore; + int iRedTeamScore; + + float m_flFlagStatusTime; + +private: + void RecountTeams( void ); + + BOOL m_DisableDeathMessages; + BOOL m_DisableDeathPenalty; + BOOL m_teamLimit; // This means the server set only some teams as valid + char m_szTeamList[TEAMPLAY_TEAMLISTLENGTH]; +}; + +#endif diff --git a/dmc/dlls/trains.h b/dmc/dlls/trains.h new file mode 100644 index 0000000..87aec76 --- /dev/null +++ b/dmc/dlls/trains.h @@ -0,0 +1,127 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef TRAINS_H +#define TRAINS_H + +// Tracktrain spawn flags +#define SF_TRACKTRAIN_NOPITCH 0x0001 +#define SF_TRACKTRAIN_NOCONTROL 0x0002 +#define SF_TRACKTRAIN_FORWARDONLY 0x0004 +#define SF_TRACKTRAIN_PASSABLE 0x0008 + +// Spawnflag for CPathTrack +#define SF_PATH_DISABLED 0x00000001 +#define SF_PATH_FIREONCE 0x00000002 +#define SF_PATH_ALTREVERSE 0x00000004 +#define SF_PATH_DISABLE_TRAIN 0x00000008 +#define SF_PATH_ALTERNATE 0x00008000 + +// Spawnflags of CPathCorner +#define SF_CORNER_WAITFORTRIG 0x001 +#define SF_CORNER_TELEPORT 0x002 +#define SF_CORNER_FIREONCE 0x004 + +//#define PATH_SPARKLE_DEBUG 1 // This makes a particle effect around path_track entities for debugging +class CPathTrack : public CPointEntity +{ +public: + void Spawn( void ); + void Activate( void ); + void KeyValue( KeyValueData* pkvd); + + void SetPrevious( CPathTrack *pprevious ); + void Link( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + CPathTrack *ValidPath( CPathTrack *ppath, int testFlag ); // Returns ppath if enabled, NULL otherwise + void Project( CPathTrack *pstart, CPathTrack *pend, Vector *origin, float dist ); + + static CPathTrack *Instance( edict_t *pent ); + + CPathTrack *LookAhead( Vector *origin, float dist, int move ); + CPathTrack *Nearest( Vector origin ); + + CPathTrack *GetNext( void ); + CPathTrack *GetPrevious( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; +#if PATH_SPARKLE_DEBUG + void EXPORT Sparkle(void); +#endif + + float m_length; + string_t m_altName; + CPathTrack *m_pnext; + CPathTrack *m_pprevious; + CPathTrack *m_paltpath; +}; + + +class CFuncTrackTrain : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + + void Blocked( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData* pkvd ); + + void EXPORT Next( void ); + void EXPORT Find( void ); + void EXPORT NearestPath( void ); + void EXPORT DeadEnd( void ); + + void NextThink( float thinkTime, BOOL alwaysThink ); + + void SetTrack( CPathTrack *track ) { m_ppath = track->Nearest(pev->origin); } + void SetControls( entvars_t *pevControls ); + BOOL OnControls( entvars_t *pev ); + + void StopSound ( void ); + void UpdateSound ( void ); + + static CFuncTrackTrain *Instance( edict_t *pent ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DIRECTIONAL_USE; } + + virtual void OverrideReset( void ); + + CPathTrack *m_ppath; + float m_length; + float m_height; + float m_speed; + float m_dir; + float m_startSpeed; + Vector m_controlMins; + Vector m_controlMaxs; + int m_soundPlaying; + int m_sounds; + float m_flVolume; + float m_flBank; + float m_oldSpeed; + +private: + unsigned short m_usAdjustPitch; +}; + +#endif diff --git a/dmc/dlls/triggers.cpp b/dmc/dlls/triggers.cpp new file mode 100644 index 0000000..8fbf734 --- /dev/null +++ b/dmc/dlls/triggers.cpp @@ -0,0 +1,2716 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== triggers.cpp ======================================================== + + spawn and use functions for editor-placed triggers + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "saverestore.h" +#include "trains.h" // trigger_camera has train functionality +#include "gamerules.h" + +#define SF_TRIGGER_PUSH_START_OFF 2//spawnflag that makes trigger_push spawn turned OFF +#define SF_TRIGGER_HURT_TARGETONCE 1// Only fire hurt target once +#define SF_TRIGGER_HURT_START_OFF 2//spawnflag that makes trigger_push spawn turned OFF +#define SF_TRIGGER_HURT_NO_CLIENTS 8//spawnflag that makes trigger_push spawn turned OFF +#define SF_TRIGGER_HURT_CLIENTONLYFIRE 16// trigger hurt will only fire its target if it is hurting a client +#define SF_TRIGGER_HURT_CLIENTONLYTOUCH 32// only clients may touch this trigger. + +extern DLL_GLOBAL BOOL g_fGameOver; + +extern Vector g_vecTeleMins[ MAX_TELES ]; +extern Vector g_vecTeleMaxs[ MAX_TELES ]; +extern int g_iTeleNum; + +extern void SetMovedir(entvars_t* pev); +extern Vector VecBModelOrigin( entvars_t* pevBModel ); + +extern unsigned short g_sTeleport; + +class CFrictionModifier : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT ChangeFriction( CBaseEntity *pOther ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + static TYPEDESCRIPTION m_SaveData[]; + + float m_frictionFraction; // Sorry, couldn't resist this name :) +}; + +LINK_ENTITY_TO_CLASS( func_friction, CFrictionModifier ); + +// Global Savedata for changelevel friction modifier +TYPEDESCRIPTION CFrictionModifier::m_SaveData[] = +{ + DEFINE_FIELD( CFrictionModifier, m_frictionFraction, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE(CFrictionModifier,CBaseEntity); + + +// Modify an entity's friction +void CFrictionModifier :: Spawn( void ) +{ + pev->solid = SOLID_TRIGGER; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->movetype = MOVETYPE_NONE; + SetTouch( &CFrictionModifier::ChangeFriction ); +} + + +// Sets toucher's friction to m_frictionFraction (1.0 = normal friction) +void CFrictionModifier :: ChangeFriction( CBaseEntity *pOther ) +{ + if ( pOther->pev->movetype != MOVETYPE_BOUNCEMISSILE && pOther->pev->movetype != MOVETYPE_BOUNCE ) + pOther->pev->friction = m_frictionFraction; +} + + + +// Sets toucher's friction to m_frictionFraction (1.0 = normal friction) +void CFrictionModifier :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "modifier")) + { + m_frictionFraction = atof(pkvd->szValue) / 100.0; + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +// This trigger will fire when the level spawns (or respawns if not fire once) +// It will check a global state before firing. It supports delay and killtargets + +#define SF_AUTO_FIREONCE 0x0001 + +class CAutoTrigger : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Precache( void ); + void Think( void ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_globalstate; + USE_TYPE triggerType; +}; +LINK_ENTITY_TO_CLASS( trigger_auto, CAutoTrigger ); + +TYPEDESCRIPTION CAutoTrigger::m_SaveData[] = +{ + DEFINE_FIELD( CAutoTrigger, m_globalstate, FIELD_STRING ), + DEFINE_FIELD( CAutoTrigger, triggerType, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE(CAutoTrigger,CBaseDelay); + +void CAutoTrigger::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "globalstate")) + { + m_globalstate = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "triggerstate")) + { + int type = atoi( pkvd->szValue ); + switch( type ) + { + case 0: + triggerType = USE_OFF; + break; + case 2: + triggerType = USE_TOGGLE; + break; + default: + triggerType = USE_ON; + break; + } + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + + +void CAutoTrigger::Spawn( void ) +{ + Precache(); +} + + +void CAutoTrigger::Precache( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CAutoTrigger::Think( void ) +{ + if ( !m_globalstate || gGlobalState.EntityGetState( m_globalstate ) == GLOBAL_ON ) + { + SUB_UseTargets( this, triggerType, 0 ); + if ( pev->spawnflags & SF_AUTO_FIREONCE ) + UTIL_Remove( this ); + } +} + + + +#define SF_RELAY_FIREONCE 0x0001 + +class CTriggerRelay : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + USE_TYPE triggerType; +}; +LINK_ENTITY_TO_CLASS( trigger_relay, CTriggerRelay ); + +TYPEDESCRIPTION CTriggerRelay::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerRelay, triggerType, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerRelay,CBaseDelay); + +void CTriggerRelay::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "triggerstate")) + { + int type = atoi( pkvd->szValue ); + switch( type ) + { + case 0: + triggerType = USE_OFF; + break; + case 2: + triggerType = USE_TOGGLE; + break; + default: + triggerType = USE_ON; + break; + } + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + + +void CTriggerRelay::Spawn( void ) +{ +} + + + + +void CTriggerRelay::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SUB_UseTargets( this, triggerType, 0 ); + if ( pev->spawnflags & SF_RELAY_FIREONCE ) + UTIL_Remove( this ); +} + + +//********************************************************** +// The Multimanager Entity - when fired, will fire up to 16 targets +// at specified times. +// FLAG: THREAD (create clones when triggered) +// FLAG: CLONE (this is a clone for a threaded execution) + +#define SF_MULTIMAN_CLONE 0x80000000 +#define SF_MULTIMAN_THREAD 0x00000001 + +class CMultiManager : public CBaseToggle +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn ( void ); + void EXPORT ManagerThink ( void ); + void EXPORT ManagerUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +#if _DEBUG + void EXPORT ManagerReport( void ); +#endif + + BOOL HasTarget( string_t targetname ); + + int ObjectCaps( void ) { return CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_cTargets; // the total number of targets in this manager's fire list. + int m_index; // Current target + float m_startTime;// Time we started firing + int m_iTargetName [ MAX_MULTI_TARGETS ];// list if indexes into global string array + float m_flTargetDelay [ MAX_MULTI_TARGETS ];// delay (in seconds) from time of manager fire to target fire +private: + inline BOOL IsClone( void ) { return (pev->spawnflags & SF_MULTIMAN_CLONE) ? TRUE : FALSE; } + inline BOOL ShouldClone( void ) + { + if ( IsClone() ) + return FALSE; + + return (pev->spawnflags & SF_MULTIMAN_THREAD) ? TRUE : FALSE; + } + + CMultiManager *Clone( void ); +}; +LINK_ENTITY_TO_CLASS( multi_manager, CMultiManager ); + +// Global Savedata for multi_manager +TYPEDESCRIPTION CMultiManager::m_SaveData[] = +{ + DEFINE_FIELD( CMultiManager, m_cTargets, FIELD_INTEGER ), + DEFINE_FIELD( CMultiManager, m_index, FIELD_INTEGER ), + DEFINE_FIELD( CMultiManager, m_startTime, FIELD_TIME ), + DEFINE_ARRAY( CMultiManager, m_iTargetName, FIELD_STRING, MAX_MULTI_TARGETS ), + DEFINE_ARRAY( CMultiManager, m_flTargetDelay, FIELD_FLOAT, MAX_MULTI_TARGETS ), +}; + +IMPLEMENT_SAVERESTORE(CMultiManager,CBaseToggle); + +void CMultiManager :: KeyValue( KeyValueData *pkvd ) +{ + // UNDONE: Maybe this should do something like this: + //CBaseToggle::KeyValue( pkvd ); + // if ( !pkvd->fHandled ) + // ... etc. + + if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else // add this field to the target list + { + // this assumes that additional fields are targetnames and their values are delay values. + if ( m_cTargets < MAX_MULTI_TARGETS ) + { + char tmp[128]; + + UTIL_StripToken( pkvd->szKeyName, tmp ); + m_iTargetName [ m_cTargets ] = ALLOC_STRING( tmp ); + m_flTargetDelay [ m_cTargets ] = atof (pkvd->szValue); + m_cTargets++; + pkvd->fHandled = TRUE; + } + } +} + + +void CMultiManager :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + SetUse ( &CMultiManager::ManagerUse ); + SetThink ( &CMultiManager::ManagerThink); + + // Sort targets + // Quick and dirty bubble sort + int swapped = 1; + + while ( swapped ) + { + swapped = 0; + for ( int i = 1; i < m_cTargets; i++ ) + { + if ( m_flTargetDelay[i] < m_flTargetDelay[i-1] ) + { + // Swap out of order elements + int name = m_iTargetName[i]; + float delay = m_flTargetDelay[i]; + m_iTargetName[i] = m_iTargetName[i-1]; + m_flTargetDelay[i] = m_flTargetDelay[i-1]; + m_iTargetName[i-1] = name; + m_flTargetDelay[i-1] = delay; + swapped = 1; + } + } + } +} + + +BOOL CMultiManager::HasTarget( string_t targetname ) +{ + for ( int i = 0; i < m_cTargets; i++ ) + if ( FStrEq(STRING(targetname), STRING(m_iTargetName[i])) ) + return TRUE; + + return FALSE; +} + + +// Designers were using this to fire targets that may or may not exist -- +// so I changed it to use the standard target fire code, made it a little simpler. +void CMultiManager :: ManagerThink ( void ) +{ + float time; + + time = gpGlobals->time - m_startTime; + while ( m_index < m_cTargets && m_flTargetDelay[ m_index ] <= time ) + { + FireTargets( STRING( m_iTargetName[ m_index ] ), m_hActivator, this, USE_TOGGLE, 0 ); + m_index++; + } + + if ( m_index >= m_cTargets )// have we fired all targets? + { + SetThink( NULL ); + if ( IsClone() ) + { + UTIL_Remove( this ); + return; + } + SetUse ( &CMultiManager::ManagerUse );// allow manager re-use + } + else + pev->nextthink = m_startTime + m_flTargetDelay[ m_index ]; +} + +CMultiManager *CMultiManager::Clone( void ) +{ + CMultiManager *pMulti = GetClassPtr( (CMultiManager *)NULL ); + + edict_t *pEdict = pMulti->pev->pContainingEntity; + memcpy( pMulti->pev, pev, sizeof(*pev) ); + pMulti->pev->pContainingEntity = pEdict; + + pMulti->pev->spawnflags |= SF_MULTIMAN_CLONE; + pMulti->m_cTargets = m_cTargets; + memcpy( pMulti->m_iTargetName, m_iTargetName, sizeof( m_iTargetName ) ); + memcpy( pMulti->m_flTargetDelay, m_flTargetDelay, sizeof( m_flTargetDelay ) ); + + return pMulti; +} + + +// The USE function builds the time table and starts the entity thinking. +void CMultiManager :: ManagerUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // In multiplayer games, clone the MM and execute in the clone (like a thread) + // to allow multiple players to trigger the same multimanager + if ( ShouldClone() ) + { + CMultiManager *pClone = Clone(); + pClone->ManagerUse( pActivator, pCaller, useType, value ); + return; + } + + m_hActivator = pActivator; + m_index = 0; + m_startTime = gpGlobals->time; + + SetUse( NULL );// disable use until all targets have fired + + SetThink ( &CMultiManager::ManagerThink ); + pev->nextthink = gpGlobals->time; +} + +#if _DEBUG +void CMultiManager :: ManagerReport ( void ) +{ + int cIndex; + + for ( cIndex = 0 ; cIndex < m_cTargets ; cIndex++ ) + { + ALERT ( at_console, "%s %f\n", STRING(m_iTargetName[cIndex]), m_flTargetDelay[cIndex] ); + } +} +#endif + +//*********************************************************** + + +// +// Render parameters trigger +// +// This entity will copy its render parameters (renderfx, rendermode, rendercolor, renderamt) +// to its targets when triggered. +// + + +// Flags to indicate masking off various render parameters that are normally copied to the targets +#define SF_RENDER_MASKFX (1<<0) +#define SF_RENDER_MASKAMT (1<<1) +#define SF_RENDER_MASKMODE (1<<2) +#define SF_RENDER_MASKCOLOR (1<<3) + +class CRenderFxManager : public CBaseEntity +{ +public: + void Spawn( void ); + void Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; + +LINK_ENTITY_TO_CLASS( env_render, CRenderFxManager ); + + +void CRenderFxManager :: Spawn ( void ) +{ + pev->solid = SOLID_NOT; +} + +void CRenderFxManager :: Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (!FStringNull(pev->target)) + { + edict_t* pentTarget = NULL; + while ( 1 ) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->target)); + if (FNullEnt(pentTarget)) + break; + + entvars_t *pevTarget = VARS( pentTarget ); + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKFX ) ) + pevTarget->renderfx = pev->renderfx; + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKAMT ) ) + pevTarget->renderamt = pev->renderamt; + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKMODE ) ) + pevTarget->rendermode = pev->rendermode; + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKCOLOR ) ) + pevTarget->rendercolor = pev->rendercolor; + } + } +} + + + +class CBaseTrigger : public CBaseToggle +{ +public: + void EXPORT TeleportTouch ( CBaseEntity *pOther ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT MultiTouch( CBaseEntity *pOther ); + void EXPORT HurtTouch ( CBaseEntity *pOther ); + void EXPORT EnvTouch ( CBaseEntity *pOther ); + void EXPORT CDAudioTouch ( CBaseEntity *pOther ); + void ActivateMultiTrigger( CBaseEntity *pActivator ); + void EXPORT MultiWaitOver( void ); + void EXPORT CounterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void InitTrigger( void ); + + virtual int ObjectCaps( void ) { return CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +LINK_ENTITY_TO_CLASS( trigger, CBaseTrigger ); + +/* +================ +InitTrigger +================ +*/ +void CBaseTrigger::InitTrigger( ) +{ + // trigger angles are used for one-way touches. An angle of 0 is assumed + // to mean no restrictions, so use a yaw of 360 instead. + if (pev->angles != g_vecZero) + SetMovedir(pev); + pev->solid = SOLID_TRIGGER; + pev->movetype = MOVETYPE_NONE; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world +// if ( CVAR_GET_FLOAT("showtriggers") == 0 ) + SetBits( pev->effects, EF_NODRAW ); +} + + +// +// Cache user-entity-field values until spawn is called. +// + +void CBaseTrigger :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "damage")) + { + pev->dmg = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "count")) + { + m_cTriggersLeft = (int) atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damagetype")) + { + m_bitsDamageInflict = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +class CTriggerHurt : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT RadiationThink( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_hurt, CTriggerHurt ); + +class CTriggerEnvHurt : public CBaseTrigger +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_env_hurt, CTriggerEnvHurt ); + + +// +// trigger_monsterjump +// +class CTriggerMonsterJump : public CBaseTrigger +{ +public: + void Spawn( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_monsterjump, CTriggerMonsterJump ); + + +void CTriggerMonsterJump :: Spawn ( void ) +{ + SetMovedir ( pev ); + + InitTrigger (); + + pev->nextthink = 0; + pev->speed = 200; + m_flHeight = 150; + + if ( !FStringNull ( pev->targetname ) ) + {// if targetted, spawn turned off + pev->solid = SOLID_NOT; + UTIL_SetOrigin( pev, pev->origin ); // Unlink from trigger list + SetUse( &CTriggerMonsterJump::ToggleUse ); + } +} + + +void CTriggerMonsterJump :: Think( void ) +{ + pev->solid = SOLID_NOT;// kill the trigger for now !!!UNDONE + UTIL_SetOrigin( pev, pev->origin ); // Unlink from trigger list + SetThink( NULL ); +} + +void CTriggerMonsterJump :: Touch( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + if ( !FBitSet ( pevOther->flags , FL_MONSTER ) ) + {// touched by a non-monster. + return; + } + + pevOther->origin.z += 1; + + if ( FBitSet ( pevOther->flags, FL_ONGROUND ) ) + {// clear the onground so physics don't bitch + pevOther->flags &= ~FL_ONGROUND; + } + + // toss the monster! + pevOther->velocity = pev->movedir * pev->speed; + pevOther->velocity.z += m_flHeight; + pev->nextthink = gpGlobals->time; +} + + +//===================================== +// +// trigger_cdaudio - starts/stops cd audio tracks +// +class CTriggerCDAudio : public CBaseTrigger +{ +public: + void Spawn( void ); + + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void PlayTrack( void ); + void Touch ( CBaseEntity *pOther ); +}; + +LINK_ENTITY_TO_CLASS( trigger_cdaudio, CTriggerCDAudio ); + +// +// Changes tracks or stops CD when player touches +// +// !!!HACK - overloaded HEALTH to avoid adding new field +void CTriggerCDAudio :: Touch ( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + {// only clients may trigger these events + return; + } + + PlayTrack(); +} + +void CTriggerCDAudio :: Spawn( void ) +{ + InitTrigger(); +} + +void CTriggerCDAudio::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + PlayTrack(); +} + +void PlayCDTrack( int iTrack ) +{ + edict_t *pClient; + + // manually find the single player. + pClient = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + // Can't play if the client is not connected! + if ( !pClient ) + return; + + if ( iTrack < -1 || iTrack > 30 ) + { + ALERT ( at_console, "TriggerCDAudio - Track %d out of range\n" ); + return; + } + + if ( iTrack == -1 ) + { + CLIENT_COMMAND ( pClient, "cd stop\n"); + } + else + { + char string [ 64 ]; + + sprintf( string, "cd play %3d\n", iTrack ); + CLIENT_COMMAND ( pClient, string); + } +} + + +// only plays for ONE client, so only use in single play! +void CTriggerCDAudio :: PlayTrack( void ) +{ + //PlayCDTrack( (int)pev->health ); + + SetTouch( NULL ); + UTIL_Remove( this ); +} + + +// This plays a CD track when fired or when the player enters it's radius +class CTargetCDAudio : public CPointEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + void Play( void ); +}; + +LINK_ENTITY_TO_CLASS( target_cdaudio, CTargetCDAudio ); + +void CTargetCDAudio :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "radius")) + { + pev->scale = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CTargetCDAudio :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + if ( pev->scale > 0 ) + pev->nextthink = gpGlobals->time + 1.0; +} + +void CTargetCDAudio::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + Play(); +} + +// only plays for ONE client, so only use in single play! +void CTargetCDAudio::Think( void ) +{ + edict_t *pClient; + + // manually find the single player. + pClient = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + // Can't play if the client is not connected! + if ( !pClient ) + return; + + pev->nextthink = gpGlobals->time + 0.5; + + if ( (pClient->v.origin - pev->origin).Length() <= pev->scale ) + Play(); + +} + +void CTargetCDAudio::Play( void ) +{ + //PlayCDTrack( (int)pev->health ); + UTIL_Remove(this); +} + +//===================================== + +/* +========================== +CTriggerEnvHurt +Used to represent Slime or Lava +========================== +*/ + +void CTriggerEnvHurt :: Spawn( void ) +{ + InitTrigger(); + SetTouch ( &CBaseTrigger::EnvTouch ); + + if ( FBitSet (pev->spawnflags, SF_TRIGGER_HURT_START_OFF) )// if flagged to Start Turned Off, make trigger nonsolid. + pev->solid = SOLID_NOT; + + UTIL_SetOrigin( pev, pev->origin ); // Link into the list +} + +// When touched, a hurt trigger does DMG points of damage each half-second +void CBaseTrigger :: EnvTouch ( CBaseEntity *pOther ) +{ + float fldmg; + + if ( !pOther->pev->takedamage ) + return; + + if ( (pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYTOUCH) && !pOther->IsPlayer() ) + { + // this trigger is only allowed to touch clients, and this ain't a client. + return; + } + + if ( (pev->spawnflags & SF_TRIGGER_HURT_NO_CLIENTS) && pOther->IsPlayer() ) + return; + + // HACKHACK -- In multiplayer, players touch this based on packet receipt. + // So the players who send packets later aren't always hurt. Keep track of + // how much time has passed and whether or not you've touched that player + if ( g_pGameRules->IsMultiplayer() ) + { + if ( pev->dmgtime > gpGlobals->time ) + { + if ( gpGlobals->time != pev->pain_finished ) + {// too early to hurt again, and not same frame with a different entity + if ( pOther->IsPlayer() ) + { + int playerMask = 1 << (pOther->entindex() - 1); + + // If I've already touched this player (this time), then bail out + if ( pev->impulse & playerMask ) + return; + + // Mark this player as touched + // BUGBUG - There can be only 32 players! + pev->impulse |= playerMask; + } + else + { + return; + } + } + } + else + { + // New clock, "un-touch" all players + pev->impulse = 0; + if ( pOther->IsPlayer() ) + { + int playerMask = 1 << (pOther->entindex() - 1); + + // Mark this player as touched + // BUGBUG - There can be only 32 players! + pev->impulse |= playerMask; + } + } + } + else // Original code -- single player + { + if ( pev->dmgtime > gpGlobals->time && gpGlobals->time != pev->pain_finished ) + {// too early to hurt again, and not same frame with a different entity + return; + } + } + + + //We are in slime + if ( m_bitsDamageInflict & DMG_ACID ) + { + pev->dmg = 4; //Default damage of 4 + fldmg = (float)( pev->dmg * pOther->pev->waterlevel ); // pev->damage plus our current waterlevel + + pev->dmgtime = gpGlobals->time + 1.0; //Next damage in 1 second + } + //We are in lava + else if ( m_bitsDamageInflict & DMG_BURN ) + { + pev->dmg = 10; //Default damage of 10 + fldmg = (float)( pev->dmg * pOther->pev->waterlevel ); // pev->damage plus our current waterlevel + + if ( pOther->IsPlayer() ) + { + if ( ( (CBasePlayer *)pOther )->m_iQuakeItems & IT_SUIT) // Wearing the suit slows down the next damage time + pev->dmgtime = gpGlobals->time + 1.0; + else + pev->dmgtime = gpGlobals->time + 0.2; + } + else + pev->dmgtime = gpGlobals->time + 0.2; + } + + if ( fldmg < 0 ) + pOther->TakeHealth( -fldmg, m_bitsDamageInflict ); + else + pOther->TakeDamage( pev, pev, fldmg, m_bitsDamageInflict ); + + // Store pain time so we can get all of the other entities on this frame + pev->pain_finished = gpGlobals->time; + + + if ( pev->target ) + { + // trigger has a target it wants to fire. + if ( pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYFIRE ) + { + // if the toucher isn't a client, don't fire the target! + if ( !pOther->IsPlayer() ) + { + return; + } + } + + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); + if ( pev->spawnflags & SF_TRIGGER_HURT_TARGETONCE ) + pev->target = 0; + } +} + +// trigger_hurt - hurts anything that touches it. if the trigger has a targetname, firing it will toggle state +// +//int gfToggleState = 0; // used to determine when all radiation trigger hurts have called 'RadiationThink' +void CTriggerHurt :: Spawn( void ) +{ + InitTrigger(); + SetTouch ( &CTriggerHurt::HurtTouch ); + + if ( !FStringNull ( pev->targetname ) ) + { + SetUse ( &CTriggerHurt::ToggleUse ); + } + else + { + SetUse ( NULL ); + } + + if (m_bitsDamageInflict & DMG_RADIATION) + { + SetThink ( &CTriggerHurt::RadiationThink ); + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(0.0, 0.5); + } + + if ( FBitSet (pev->spawnflags, SF_TRIGGER_HURT_START_OFF) )// if flagged to Start Turned Off, make trigger nonsolid. + pev->solid = SOLID_NOT; + + UTIL_SetOrigin( pev, pev->origin ); // Link into the list +} + +// trigger hurt that causes radiation will do a radius +// check and set the player's geiger counter level +// according to distance from center of trigger + +void CTriggerHurt :: RadiationThink( void ) +{ + + edict_t *pentPlayer; + CBasePlayer *pPlayer = NULL; + float flRange; + entvars_t *pevTarget; + Vector vecSpot1; + Vector vecSpot2; + Vector vecRange; + Vector origin; + Vector view_ofs; + + // check to see if a player is in pvs + // if not, continue + + // set origin to center of trigger so that this check works + origin = pev->origin; + view_ofs = pev->view_ofs; + + pev->origin = (pev->absmin + pev->absmax) * 0.5; + pev->view_ofs = pev->view_ofs * 0.0; + + pentPlayer = FIND_CLIENT_IN_PVS(edict()); + + pev->origin = origin; + pev->view_ofs = view_ofs; + + // reset origin + + if (!FNullEnt(pentPlayer)) + { + + pPlayer = GetClassPtr( (CBasePlayer *)VARS(pentPlayer)); + + pevTarget = VARS(pentPlayer); + + // get range to player; + + vecSpot1 = (pev->absmin + pev->absmax) * 0.5; + vecSpot2 = (pevTarget->absmin + pevTarget->absmax) * 0.5; + + vecRange = vecSpot1 - vecSpot2; + flRange = vecRange.Length(); + + // if player's current geiger counter range is larger + // than range to this trigger hurt, reset player's + // geiger counter range + + if (pPlayer->m_flgeigerRange >= flRange) + pPlayer->m_flgeigerRange = flRange; + } + + pev->nextthink = gpGlobals->time + 0.25; +} + +// +// ToggleUse - If this is the USE function for a trigger, its state will toggle every time it's fired +// +void CBaseTrigger :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (pev->solid == SOLID_NOT) + {// if the trigger is off, turn it on + pev->solid = SOLID_TRIGGER; + + // Force retouch + gpGlobals->force_retouch++; + } + else + {// turn the trigger off + pev->solid = SOLID_NOT; + } + UTIL_SetOrigin( pev, pev->origin ); +} + +// When touched, a hurt trigger does DMG points of damage each half-second +void CBaseTrigger :: HurtTouch ( CBaseEntity *pOther ) +{ + float fldmg; + + if ( !pOther->pev->takedamage ) + return; + + if ( (pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYTOUCH) && !pOther->IsPlayer() ) + { + // this trigger is only allowed to touch clients, and this ain't a client. + return; + } + + if ( (pev->spawnflags & SF_TRIGGER_HURT_NO_CLIENTS) && pOther->IsPlayer() ) + return; + + // HACKHACK -- In multiplayer, players touch this based on packet receipt. + // So the players who send packets later aren't always hurt. Keep track of + // how much time has passed and whether or not you've touched that player + if ( g_pGameRules->IsMultiplayer() ) + { + if ( pev->dmgtime > gpGlobals->time ) + { + if ( gpGlobals->time != pev->pain_finished ) + {// too early to hurt again, and not same frame with a different entity + if ( pOther->IsPlayer() ) + { + int playerMask = 1 << (pOther->entindex() - 1); + + // If I've already touched this player (this time), then bail out + if ( pev->impulse & playerMask ) + return; + + // Mark this player as touched + // BUGBUG - There can be only 32 players! + pev->impulse |= playerMask; + } + else + { + return; + } + } + } + else + { + // New clock, "un-touch" all players + pev->impulse = 0; + if ( pOther->IsPlayer() ) + { + int playerMask = 1 << (pOther->entindex() - 1); + + // Mark this player as touched + // BUGBUG - There can be only 32 players! + pev->impulse |= playerMask; + } + } + } + else // Original code -- single player + { + if ( pev->dmgtime > gpGlobals->time && gpGlobals->time != pev->pain_finished ) + {// too early to hurt again, and not same frame with a different entity + return; + } + } + + + + // If this is time_based damage (poison, radiation), override the pev->dmg with a + // default for the given damage type. Monsters only take time-based damage + // while touching the trigger. Player continues taking damage for a while after + // leaving the trigger + + fldmg = pev->dmg * 0.5; // 0.5 seconds worth of damage, pev->dmg is damage/second + + + // JAY: Cut this because it wasn't fully realized. Damage is simpler now. +#if 0 + switch (m_bitsDamageInflict) + { + default: break; + case DMG_POISON: fldmg = POISON_DAMAGE/4; break; + case DMG_NERVEGAS: fldmg = NERVEGAS_DAMAGE/4; break; + case DMG_RADIATION: fldmg = RADIATION_DAMAGE/4; break; + case DMG_PARALYZE: fldmg = PARALYZE_DAMAGE/4; break; // UNDONE: cut this? should slow movement to 50% + case DMG_ACID: fldmg = ACID_DAMAGE/4; break; + case DMG_SLOWBURN: fldmg = SLOWBURN_DAMAGE/4; break; + case DMG_SLOWFREEZE: fldmg = SLOWFREEZE_DAMAGE/4; break; + } +#endif + + if ( fldmg < 0 ) + pOther->TakeHealth( -fldmg, m_bitsDamageInflict ); + else + pOther->TakeDamage( pev, pev, fldmg, m_bitsDamageInflict ); + + // Store pain time so we can get all of the other entities on this frame + pev->pain_finished = gpGlobals->time; + + // Apply damage every half second + pev->dmgtime = gpGlobals->time + 0.5;// half second delay until this trigger can hurt toucher again + + + + if ( pev->target ) + { + // trigger has a target it wants to fire. + if ( pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYFIRE ) + { + // if the toucher isn't a client, don't fire the target! + if ( !pOther->IsPlayer() ) + { + return; + } + } + + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); + if ( pev->spawnflags & SF_TRIGGER_HURT_TARGETONCE ) + pev->target = 0; + } +} + + +/*QUAKED trigger_multiple (.5 .5 .5) ? notouch +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. +"wait" : Seconds between triggerings. (.2 default) +If notouch is set, the trigger is only fired by other entities, not by touching. +NOTOUCH has been obsoleted by trigger_relay! +sounds +1) secret +2) beep beep +3) large switch +4) +NEW +if a trigger has a NETNAME, that NETNAME will become the TARGET of the triggered object. +*/ +class CTriggerMultiple : public CBaseTrigger +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_multiple, CTriggerMultiple ); + + +void CTriggerMultiple :: Spawn( void ) +{ + if (m_flWait == 0) + m_flWait = 0.2; + + InitTrigger(); + + ASSERTSZ(pev->health == 0, "trigger_multiple with health"); +// UTIL_SetOrigin(pev, pev->origin); +// SET_MODEL( ENT(pev), STRING(pev->model) ); +// if (pev->health > 0) +// { +// if (FBitSet(pev->spawnflags, SPAWNFLAG_NOTOUCH)) +// ALERT(at_error, "trigger_multiple spawn: health and notouch don't make sense"); +// pev->max_health = pev->health; +//UNDONE: where to get pfnDie from? +// pev->pfnDie = multi_killed; +// pev->takedamage = DAMAGE_YES; +// pev->solid = SOLID_BBOX; +// UTIL_SetOrigin(pev, pev->origin); // make sure it links into the world +// } +// else + { + SetTouch( &CTriggerMultiple::MultiTouch ); + } + } + + +/*QUAKED trigger_once (.5 .5 .5) ? notouch +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 +"targetname". If "health" is set, the trigger must be killed to activate. +If notouch is set, the trigger is only fired by other entities, not by touching. +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. Use "360" for an angle of 0. +sounds +1) secret +2) beep beep +3) large switch +4) +*/ +class CTriggerOnce : public CTriggerMultiple +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_once, CTriggerOnce ); +void CTriggerOnce::Spawn( void ) +{ + m_flWait = -1; + + CTriggerMultiple :: Spawn(); +} + + + +void CBaseTrigger :: MultiTouch( CBaseEntity *pOther ) +{ + entvars_t *pevToucher; + + pevToucher = pOther->pev; + + // Only touch clients, monsters, or pushables (depending on flags) + if ( ((pevToucher->flags & FL_CLIENT) && !(pev->spawnflags & SF_TRIGGER_NOCLIENTS)) || + ((pevToucher->flags & FL_MONSTER) && (pev->spawnflags & SF_TRIGGER_ALLOWMONSTERS)) || + (pev->spawnflags & SF_TRIGGER_PUSHABLES) && FClassnameIs(pevToucher,"func_pushable") ) + { + +#if 0 + // if the trigger has an angles field, check player's facing direction + if (pev->movedir != g_vecZero) + { + UTIL_MakeVectors( pevToucher->angles ); + if ( DotProduct( gpGlobals->v_forward, pev->movedir ) < 0 ) + return; // not facing the right way + } +#endif + + ActivateMultiTrigger( pOther ); + } +} + + +// +// the trigger was just touched/killed/used +// self.enemy should be set to the activator so it can be held through a delay +// so wait for the delay time before firing +// +void CBaseTrigger :: ActivateMultiTrigger( CBaseEntity *pActivator ) +{ + if (pev->nextthink > gpGlobals->time) + return; // still waiting for reset time + + if (!UTIL_IsMasterTriggered(m_sMaster,pActivator)) + return; + + if (FClassnameIs(pev, "trigger_secret")) + { + if ( pev->enemy == NULL || !FClassnameIs(pev->enemy, "player")) + return; + gpGlobals->found_secrets++; + } + + if (!FStringNull(pev->noise)) + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + +// don't trigger again until reset +// pev->takedamage = DAMAGE_NO; + + m_hActivator = pActivator; + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + + if ( pev->message && pActivator->IsPlayer() ) + { + UTIL_ShowMessage( STRING(pev->message), pActivator ); +// CLIENT_PRINTF( ENT( pActivator->pev ), print_center, STRING(pev->message) ); + } + + if (m_flWait > 0) + { + SetThink( &CBaseTrigger::MultiWaitOver ); + pev->nextthink = gpGlobals->time + m_flWait; + } + else + { + // we can't just remove (self) here, because this is a touch function + // called while C code is looping through area links... + SetTouch( NULL ); + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CBaseTrigger::SUB_Remove ); + } +} + + +// the wait time has passed, so set back up for another activation +void CBaseTrigger :: MultiWaitOver( void ) +{ +// if (pev->max_health) +// { +// pev->health = pev->max_health; +// pev->takedamage = DAMAGE_YES; +// pev->solid = SOLID_BBOX; +// } + SetThink( NULL ); +} + + +// ========================= COUNTING TRIGGER ===================================== + +// +// GLOBALS ASSUMED SET: g_eoActivator +// +void CBaseTrigger::CounterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_cTriggersLeft--; + m_hActivator = pActivator; + + if (m_cTriggersLeft < 0) + return; + + BOOL fTellActivator = + (m_hActivator != 0) && + FClassnameIs(m_hActivator->pev, "player") && + !FBitSet(pev->spawnflags, SPAWNFLAG_NOMESSAGE); + if (m_cTriggersLeft != 0) + { + if (fTellActivator) + { + // UNDONE: I don't think we want these Quakesque messages + switch (m_cTriggersLeft) + { + case 1: ALERT(at_console, "Only 1 more to go..."); break; + case 2: ALERT(at_console, "Only 2 more to go..."); break; + case 3: ALERT(at_console, "Only 3 more to go..."); break; + default: ALERT(at_console, "There are more to go..."); break; + } + } + return; + } + + // !!!UNDONE: I don't think we want these Quakesque messages + if (fTellActivator) + ALERT(at_console, "Sequence completed!"); + + ActivateMultiTrigger( m_hActivator ); +} + + +/*QUAKED trigger_counter (.5 .5 .5) ? nomessage +Acts as an intermediary for an action that takes multiple inputs. +If nomessage is not set, it will print "1 more.. " etc when triggered and +"sequence complete" when finished. After the counter has been triggered "cTriggersLeft" +times (default 2), it will fire all of it's targets and remove itself. +*/ +class CTriggerCounter : public CBaseTrigger +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( trigger_counter, CTriggerCounter ); + +void CTriggerCounter :: Spawn( void ) +{ + // By making the flWait be -1, this counter-trigger will disappear after it's activated + // (but of course it needs cTriggersLeft "uses" before that happens). + m_flWait = -1; + + if (m_cTriggersLeft == 0) + m_cTriggersLeft = 2; + SetUse( &CTriggerCounter::CounterUse ); +} + +// ====================== TRIGGER_CHANGELEVEL ================================ + +class CTriggerVolume : public CPointEntity // Derive from point entity so this doesn't move across levels +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_transition, CTriggerVolume ); + +// Define space that travels across a level transition +void CTriggerVolume :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->model = NULL; + pev->modelindex = 0; +} + + +// Fires a target after level transition and then dies +class CFireAndDie : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void Think( void ); + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() | FCAP_FORCE_TRANSITION; } // Always go across transitions +}; +LINK_ENTITY_TO_CLASS( fireanddie, CFireAndDie ); + +void CFireAndDie::Spawn( void ) +{ + pev->classname = MAKE_STRING("fireanddie"); + // Don't call Precache() - it should be called on restore +} + + +void CFireAndDie::Precache( void ) +{ + // This gets called on restore + pev->nextthink = gpGlobals->time + m_flDelay; +} + + +void CFireAndDie::Think( void ) +{ + SUB_UseTargets( this, USE_TOGGLE, 0 ); + UTIL_Remove( this ); +} + + +#define SF_CHANGELEVEL_USEONLY 0x0002 +class CChangeLevel : public CBaseTrigger +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT UseChangeLevel ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT TriggerChangeLevel( void ); + void EXPORT ExecuteChangeLevel( void ); + void EXPORT TouchChangeLevel( CBaseEntity *pOther ); + void ChangeLevelNow( CBaseEntity *pActivator ); + + static edict_t *FindLandmark( const char *pLandmarkName ); + static int ChangeList( LEVELLIST *pLevelList, int maxList ); + static int AddTransitionToList( LEVELLIST *pLevelList, int listCount, const char *pMapName, const char *pLandmarkName, edict_t *pentLandmark ); + static int InTransitionVolume( CBaseEntity *pEntity, char *pVolumeName ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + char m_szMapName[cchMapNameMost]; // trigger_changelevel only: next map + char m_szLandmarkName[cchMapNameMost]; // trigger_changelevel only: landmark on next map + int m_changeTarget; + float m_changeTargetDelay; +}; +LINK_ENTITY_TO_CLASS( trigger_changelevel, CChangeLevel ); + +// Global Savedata for changelevel trigger +TYPEDESCRIPTION CChangeLevel::m_SaveData[] = +{ + DEFINE_ARRAY( CChangeLevel, m_szMapName, FIELD_CHARACTER, cchMapNameMost ), + DEFINE_ARRAY( CChangeLevel, m_szLandmarkName, FIELD_CHARACTER, cchMapNameMost ), + DEFINE_FIELD( CChangeLevel, m_changeTarget, FIELD_STRING ), + DEFINE_FIELD( CChangeLevel, m_changeTargetDelay, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE(CChangeLevel,CBaseTrigger); + +// +// Cache user-entity-field values until spawn is called. +// + +void CChangeLevel :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "map")) + { + if (strlen(pkvd->szValue) >= cchMapNameMost) + ALERT( at_error, "Map name '%s' too long (32 chars)\n", pkvd->szValue ); + strcpy(m_szMapName, pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "landmark")) + { + if (strlen(pkvd->szValue) >= cchMapNameMost) + ALERT( at_error, "Landmark name '%s' too long (32 chars)\n", pkvd->szValue ); + strcpy(m_szLandmarkName, pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "changetarget")) + { + m_changeTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "changedelay")) + { + m_changeTargetDelay = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseTrigger::KeyValue( pkvd ); +} + + +/*QUAKED trigger_changelevel (0.5 0.5 0.5) ? NO_INTERMISSION +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. +*/ + +void CChangeLevel :: Spawn( void ) +{ + if ( FStrEq( m_szMapName, "" ) ) + ALERT( at_console, "a trigger_changelevel doesn't have a map" ); + + if ( FStrEq( m_szLandmarkName, "" ) ) + ALERT( at_console, "trigger_changelevel to %s doesn't have a landmark", m_szMapName ); + + if (!FStringNull ( pev->targetname ) ) + { + SetUse ( &CChangeLevel::UseChangeLevel ); + } + InitTrigger(); + if ( !(pev->spawnflags & SF_CHANGELEVEL_USEONLY) ) + SetTouch( &CChangeLevel::TouchChangeLevel ); +// ALERT( at_console, "TRANSITION: %s (%s)\n", m_szMapName, m_szLandmarkName ); +} + + +void CChangeLevel :: ExecuteChangeLevel( void ) +{ + MESSAGE_BEGIN( MSG_ALL, SVC_CDTRACK ); + WRITE_BYTE( 3 ); + WRITE_BYTE( 3 ); + MESSAGE_END(); + + MESSAGE_BEGIN(MSG_ALL, SVC_INTERMISSION); + MESSAGE_END(); +} + + +FILE_GLOBAL char st_szNextMap[cchMapNameMost]; +FILE_GLOBAL char st_szNextSpot[cchMapNameMost]; + +edict_t *CChangeLevel :: FindLandmark( const char *pLandmarkName ) +{ + edict_t *pentLandmark; + + pentLandmark = FIND_ENTITY_BY_STRING( NULL, "targetname", pLandmarkName ); + while ( !FNullEnt( pentLandmark ) ) + { + // Found the landmark + if ( FClassnameIs( pentLandmark, "info_landmark" ) ) + return pentLandmark; + else + pentLandmark = FIND_ENTITY_BY_STRING( pentLandmark, "targetname", pLandmarkName ); + } + ALERT( at_error, "Can't find landmark %s\n", pLandmarkName ); + return NULL; +} + + +//========================================================= +// CChangeLevel :: Use - allows level transitions to be +// triggered by buttons, etc. +// +//========================================================= +void CChangeLevel :: UseChangeLevel ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + ChangeLevelNow( pActivator ); +} + +void CChangeLevel :: ChangeLevelNow( CBaseEntity *pActivator ) +{ + edict_t *pentLandmark; + LEVELLIST levels[16]; + + ASSERT(!FStrEq(m_szMapName, "")); + + // Don't work in deathmatch + if ( g_pGameRules->IsDeathmatch() ) + return; + + // Some people are firing these multiple times in a frame, disable + if ( gpGlobals->time == pev->dmgtime ) + return; + + pev->dmgtime = gpGlobals->time; + + + CBaseEntity *pPlayer = CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) ); + if ( !InTransitionVolume( pPlayer, m_szLandmarkName ) ) + { + ALERT( at_aiconsole, "Player isn't in the transition volume %s, aborting\n", m_szLandmarkName ); + return; + } + + // Create an entity to fire the changetarget + if ( m_changeTarget ) + { + CFireAndDie *pFireAndDie = GetClassPtr( (CFireAndDie *)NULL ); + if ( pFireAndDie ) + { + // Set target and delay + pFireAndDie->pev->target = m_changeTarget; + pFireAndDie->m_flDelay = m_changeTargetDelay; + pFireAndDie->pev->origin = pPlayer->pev->origin; + // Call spawn + DispatchSpawn( pFireAndDie->edict() ); + } + } + // This object will get removed in the call to CHANGE_LEVEL, copy the params into "safe" memory + strcpy(st_szNextMap, m_szMapName); + + m_hActivator = pActivator; + SUB_UseTargets( pActivator, USE_TOGGLE, 0 ); + st_szNextSpot[0] = 0; // Init landmark to NULL + + // look for a landmark entity + pentLandmark = FindLandmark( m_szLandmarkName ); + if ( !FNullEnt( pentLandmark ) ) + { + strcpy(st_szNextSpot, m_szLandmarkName); + gpGlobals->vecLandmarkOffset = VARS(pentLandmark)->origin; + } +// ALERT( at_console, "Level touches %d levels\n", ChangeList( levels, 16 ) ); + ALERT( at_console, "CHANGE LEVEL: %s %s\n", st_szNextMap, st_szNextSpot ); + CHANGE_LEVEL( st_szNextMap, st_szNextSpot ); +} + +// +// GLOBALS ASSUMED SET: st_szNextMap +// +void CChangeLevel :: TouchChangeLevel( CBaseEntity *pOther ) +{ + if (!FClassnameIs(pOther->pev, "player")) + return; + + ChangeLevelNow( pOther ); +} + + +// Add a transition to the list, but ignore duplicates +// (a designer may have placed multiple trigger_changelevels with the same landmark) +int CChangeLevel::AddTransitionToList( LEVELLIST *pLevelList, int listCount, const char *pMapName, const char *pLandmarkName, edict_t *pentLandmark ) +{ + int i; + + if ( !pLevelList || !pMapName || !pLandmarkName || !pentLandmark ) + return 0; + + for ( i = 0; i < listCount; i++ ) + { + if ( pLevelList[i].pentLandmark == pentLandmark && strcmp( pLevelList[i].mapName, pMapName ) == 0 ) + return 0; + } + strcpy( pLevelList[listCount].mapName, pMapName ); + strcpy( pLevelList[listCount].landmarkName, pLandmarkName ); + pLevelList[listCount].pentLandmark = pentLandmark; + pLevelList[listCount].vecLandmarkOrigin = VARS(pentLandmark)->origin; + + return 1; +} + +int BuildChangeList( LEVELLIST *pLevelList, int maxList ) +{ + return CChangeLevel::ChangeList( pLevelList, maxList ); +} + + +int CChangeLevel::InTransitionVolume( CBaseEntity *pEntity, char *pVolumeName ) +{ + edict_t *pentVolume; + + + if ( pEntity->ObjectCaps() & FCAP_FORCE_TRANSITION ) + return 1; + + // If you're following another entity, follow it through the transition (weapons follow the player) + if ( pEntity->pev->movetype == MOVETYPE_FOLLOW ) + { + if ( pEntity->pev->aiment != NULL ) + pEntity = CBaseEntity::Instance( pEntity->pev->aiment ); + } + + int inVolume = 1; // Unless we find a trigger_transition, everything is in the volume + + pentVolume = FIND_ENTITY_BY_TARGETNAME( NULL, pVolumeName ); + while ( !FNullEnt( pentVolume ) ) + { + CBaseEntity *pVolume = CBaseEntity::Instance( pentVolume ); + + if ( pVolume && FClassnameIs( pVolume->pev, "trigger_transition" ) ) + { + if ( pVolume->Intersects( pEntity ) ) // It touches one, it's in the volume + return 1; + else + inVolume = 0; // Found a trigger_transition, but I don't intersect it -- if I don't find another, don't go! + } + pentVolume = FIND_ENTITY_BY_TARGETNAME( pentVolume, pVolumeName ); + } + + return inVolume; +} + + +// We can only ever move 512 entities across a transition +#define MAX_ENTITY 512 + +// This has grown into a complicated beast +// Can we make this more elegant? +// This builds the list of all transitions on this level and which entities are in their PVS's and can / should +// be moved across. +int CChangeLevel::ChangeList( LEVELLIST *pLevelList, int maxList ) +{ + edict_t *pentChangelevel, *pentLandmark; + int i, count; + + count = 0; + + // Find all of the possible level changes on this BSP + pentChangelevel = FIND_ENTITY_BY_STRING( NULL, "classname", "trigger_changelevel" ); + if ( FNullEnt( pentChangelevel ) ) + return 0; + while ( !FNullEnt( pentChangelevel ) ) + { + CChangeLevel *pTrigger; + + pTrigger = GetClassPtr((CChangeLevel *)VARS(pentChangelevel)); + if ( pTrigger ) + { + // Find the corresponding landmark + pentLandmark = FindLandmark( pTrigger->m_szLandmarkName ); + if ( pentLandmark ) + { + // Build a list of unique transitions + if ( AddTransitionToList( pLevelList, count, pTrigger->m_szMapName, pTrigger->m_szLandmarkName, pentLandmark ) ) + { + count++; + if ( count >= maxList ) // FULL!! + break; + } + } + } + pentChangelevel = FIND_ENTITY_BY_STRING( pentChangelevel, "classname", "trigger_changelevel" ); + } + + if ( gpGlobals->pSaveData && ((SAVERESTOREDATA *)gpGlobals->pSaveData)->pTable ) + { + CSave saveHelper( (SAVERESTOREDATA *)gpGlobals->pSaveData ); + + for ( i = 0; i < count; i++ ) + { + int j, entityCount = 0; + CBaseEntity *pEntList[ MAX_ENTITY ]; + int entityFlags[ MAX_ENTITY ]; + + // Follow the linked list of entities in the PVS of the transition landmark + edict_t *pent = UTIL_EntitiesInPVS( pLevelList[i].pentLandmark ); + + // Build a list of valid entities in this linked list (we're going to use pent->v.chain again) + while ( !FNullEnt( pent ) ) + { + CBaseEntity *pEntity = CBaseEntity::Instance(pent); + if ( pEntity ) + { +// ALERT( at_console, "Trying %s\n", STRING(pEntity->pev->classname) ); + int caps = pEntity->ObjectCaps(); + if ( !(caps & FCAP_DONT_SAVE) ) + { + int flags = 0; + + // If this entity can be moved or is global, mark it + if ( caps & FCAP_ACROSS_TRANSITION ) + flags |= FENTTABLE_MOVEABLE; + if ( pEntity->pev->globalname && !pEntity->IsDormant() ) + flags |= FENTTABLE_GLOBAL; + if ( flags ) + { + pEntList[ entityCount ] = pEntity; + entityFlags[ entityCount ] = flags; + entityCount++; + if ( entityCount > MAX_ENTITY ) + ALERT( at_error, "Too many entities across a transition!" ); + } +// else +// ALERT( at_console, "Failed %s\n", STRING(pEntity->pev->classname) ); + } +// else +// ALERT( at_console, "DON'T SAVE %s\n", STRING(pEntity->pev->classname) ); + } + pent = pent->v.chain; + } + + for ( j = 0; j < entityCount; j++ ) + { + // Check to make sure the entity isn't screened out by a trigger_transition + if ( entityFlags[j] && InTransitionVolume( pEntList[j], pLevelList[i].landmarkName ) ) + { + // Mark entity table with 1<pev->classname) ); + + } + } + } + + return count; +} + +/* +go to the next level for deathmatch +only called if a time or frag limit has expired +*/ +void NextLevel( void ) +{ + edict_t* pent; + CChangeLevel *pChange; + + // find a trigger_changelevel + pent = FIND_ENTITY_BY_CLASSNAME(NULL, "trigger_changelevel"); + + // go back to start if no trigger_changelevel + if (FNullEnt(pent)) + { + gpGlobals->mapname = ALLOC_STRING("start"); + pChange = GetClassPtr( (CChangeLevel *)NULL ); + strcpy(pChange->m_szMapName, "start"); + } + else + pChange = GetClassPtr( (CChangeLevel *)VARS(pent)); + + strcpy(st_szNextMap, pChange->m_szMapName); + g_fGameOver = TRUE; + + if (pChange->pev->nextthink < gpGlobals->time) + { + pChange->SetThink( &CChangeLevel::ExecuteChangeLevel ); + pChange->pev->nextthink = gpGlobals->time + 0.1; + } +} + + +// ============================== LADDER ======================================= + +class CLadder : public CBaseTrigger +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS( func_ladder, CLadder ); + + +void CLadder :: KeyValue( KeyValueData *pkvd ) +{ + CBaseTrigger::KeyValue( pkvd ); +} + + +//========================================================= +// func_ladder - makes an area vertically negotiable +//========================================================= +void CLadder :: Precache( void ) +{ + // Do all of this in here because we need to 'convert' old saved games + pev->solid = SOLID_NOT; + pev->skin = CONTENTS_LADDER; + if ( CVAR_GET_FLOAT("showtriggers") == 0 ) + { + pev->rendermode = kRenderTransTexture; + pev->renderamt = 0; + } + pev->effects &= ~EF_NODRAW; +} + + +void CLadder :: Spawn( void ) +{ + Precache(); + + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->movetype = MOVETYPE_PUSH; +} + + +// ========================== A TRIGGER THAT PUSHES YOU =============================== + +class CTriggerPush : public CBaseTrigger +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Touch( CBaseEntity *pOther ); +}; +LINK_ENTITY_TO_CLASS( trigger_push, CTriggerPush ); + + +void CTriggerPush :: KeyValue( KeyValueData *pkvd ) +{ + CBaseTrigger::KeyValue( pkvd ); +} + + +/*QUAKED trigger_push (.5 .5 .5) ? TRIG_PUSH_ONCE +Pushes the player +*/ + +void CTriggerPush :: Spawn( ) +{ + if ( pev->angles == g_vecZero ) + pev->angles.y = 360; + InitTrigger(); + + if (pev->speed == 0) + pev->speed = 100; + + if ( FBitSet (pev->spawnflags, SF_TRIGGER_PUSH_START_OFF) )// if flagged to Start Turned Off, make trigger nonsolid. + pev->solid = SOLID_NOT; + + SetUse( &CTriggerPush::ToggleUse ); + + UTIL_SetOrigin( pev, pev->origin ); // Link into the list +} + + +void CTriggerPush :: Touch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + + // UNDONE: Is there a better way than health to detect things that have physics? (clients/monsters) + switch( pevToucher->movetype ) + { + case MOVETYPE_NONE: + case MOVETYPE_PUSH: + case MOVETYPE_NOCLIP: + case MOVETYPE_FOLLOW: + return; + } + + //Only players + if ( !pOther->IsPlayer() ) + return; + + if ( pevToucher->solid != SOLID_NOT && pevToucher->solid != SOLID_BSP ) + { + // Instant trigger, just transfer velocity and remove + if (FBitSet(pev->spawnflags, SF_TRIG_PUSH_ONCE)) + { + pevToucher->velocity = pevToucher->velocity + (pev->speed * pev->movedir); + if ( pevToucher->velocity.z > 0 ) + pevToucher->flags &= ~FL_ONGROUND; + UTIL_Remove( this ); + } + else + { // Push field, transfer to base velocity + Vector vecPush = (pev->speed * pev->movedir); + if ( pevToucher->flags & FL_BASEVELOCITY ) + vecPush = vecPush + pevToucher->basevelocity; + + pevToucher->basevelocity = vecPush; + + pevToucher->flags |= FL_BASEVELOCITY; +// ALERT( at_console, "Vel %f, base %f\n", pevToucher->velocity.z, pevToucher->basevelocity.z ); + } + } +} + +//======================================================================================== +// TELEPORT TRIGGERS +//======================================================================================== + +//----------------------------------------------------------------------------- +// Purpose: Teleport Death entity. Kills anything it touches +//----------------------------------------------------------------------------- +class CTeleDeath : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT DeathTouch( CBaseEntity *pOther ); +}; +LINK_ENTITY_TO_CLASS( teledeath, CTeleDeath ); + +void CTeleDeath::Spawn( void ) +{ + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_TRIGGER; + pev->angles = g_vecZero; + + // Owner is the player who's spawned this + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + if (!pOwner) + return; + + UTIL_SetSize( pev, pOwner->pev->mins - Vector(1,1,1), pOwner->pev->maxs + Vector(1,1,1) ); + UTIL_SetOrigin( pev, pev->origin ); + + SetTouch( &CTeleDeath::DeathTouch ); + pev->nextthink = gpGlobals->time + 0.2; + SetThink( &CTeleDeath::SUB_Remove ); + + // Touch still players + gpGlobals->force_retouch = 2; +} + +void CTeleDeath::DeathTouch( CBaseEntity *pOther ) +{ + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + + if ( pOther == pOwner ) + return; + + // frag anyone who teleports in on top of an invincible player + if ( pOther->IsPlayer() ) + { + if ( ((CBasePlayer*)pOther)->m_flInvincibleFinished > gpGlobals->time ) + { + // Note: This did not work in Quake. Fixed in QUAKECLASSIC. + if ( pOwner->IsPlayer() ) + { + g_szDeathType = "teledeath2"; + pOwner->TakeDamage( pOwner->pev, pOwner->pev, 1000, DMG_GENERIC | DMG_ALWAYSGIB); + return; + } + } + } + + if ( pOther->pev->health ) + { + g_szDeathType = "teledeath"; + pOther->TakeDamage( pOther->pev, pOwner->pev, 1000, DMG_GENERIC | DMG_ALWAYSGIB); + } +} + +//====================================== +// teleport trigger +// +// QUAKECLASSIC: Different bitflags +#define TELE_PLAYER_ONLY 1 +#define TELE_SILENT 2 + +//----------------------------------------------------------------------------- +// Purpose: Spawn a teleport fog at the vector specified +//----------------------------------------------------------------------------- +void CBaseEntity::Spawn_Telefog( Vector vecOrg, CBaseEntity *pOther ) +{ + //Moved to the client + PLAYBACK_EVENT_FULL ( FEV_GLOBAL | FEV_NOTHOST, pOther->edict(), g_sTeleport, 0.0, (float *)&vecOrg, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0); +} + +//----------------------------------------------------------------------------- +// Purpose: Kill anything at the teleport destination +//----------------------------------------------------------------------------- +void CBaseTrigger :: TeleportTouch( CBaseEntity *pOther ) +{ + // no clients allowed? + if ( ( pev->spawnflags & TELE_PLAYER_ONLY ) ) + { + if ( pOther->IsPlayer() ) + return; + } + + // only teleport living creatures + if (pOther->pev->health <= 0 || pOther->pev->solid != SOLID_SLIDEBOX) + return; + + SUB_UseTargets( this, USE_TOGGLE, 0 ); + + // put a tfog where the player was + Spawn_Telefog(pOther->pev->origin, pOther ); + + edict_t *pentTarget = NULL; + pentTarget = FIND_ENTITY_BY_TARGETNAME( pentTarget, STRING(pev->target) ); + if (FNullEnt(pentTarget)) + return; + + // spawn a tfog flash in front of the destination + CBaseEntity *pTarget = CBaseEntity::Instance( pentTarget ); + UTIL_MakeVectors(pTarget->m_vecTeleAngles); + Vector vecNewOrg = pTarget->pev->origin + (gpGlobals->v_forward * 32); + pTarget->Spawn_Telefog( vecNewOrg, pOther ); + + // Spawn teledeath at destination + CTeleDeath *pDeath = GetClassPtr( (CTeleDeath *)NULL ); + pDeath->pev->owner = pOther->edict(); + pDeath->pev->origin = pTarget->pev->origin; + pDeath->Spawn(); + + // May be killed by teleporting onto an invulnerable player + if (!pOther->pev->health) + { + pOther->pev->origin = pTarget->pev->origin; + pOther->pev->velocity = (gpGlobals->v_forward * pOther->pev->velocity.x) + (gpGlobals->v_forward * pOther->pev->velocity.y); + return; + } + + // Move the player + UTIL_SetOrigin( pOther->pev, pTarget->pev->origin ); + pOther->pev->angles = pTarget->m_vecTeleAngles; + + if (pOther->IsPlayer()) + { + // pOther->pev->fixangle = 1; // turn this way immediately + + //Err, why is this here? + pOther->pev->fuser4 = gpGlobals->time + 0.7; + pOther->pev->velocity = gpGlobals->v_forward * 300; + } + pOther->pev->flags &= ~FL_ONGROUND; +} + + +class CTriggerTeleport : public CBaseTrigger +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( trigger_teleport, CTriggerTeleport ); + +void CTriggerTeleport :: Spawn( void ) +{ + InitTrigger(); + + SetTouch( &CTriggerTeleport::TeleportTouch ); + + g_vecTeleMins[ g_iTeleNum ] = pev->absmin; + g_vecTeleMaxs[ g_iTeleNum ] = pev->absmax; + + g_iTeleNum++; + + if (!(pev->spawnflags & TELE_SILENT)) + { + PRECACHE_SOUND("ambience/hum1.wav"); + UTIL_EmitAmbientSound(ENT(pev), (pev->mins + pev->maxs) * 0.5, "ambience/hum1.wav", 0.5, ATTN_STATIC, 0, 100); + } +} + + +class CTriggerTeleportDest : public CBaseTrigger +{ +public: + void Spawn( void ); +}; + +void CTriggerTeleportDest :: Spawn( void ) +{ + m_vecTeleAngles = pev->angles; + pev->angles = g_vecZero; + pev->origin.z += 27; + UTIL_SetOrigin( pev, pev->origin ); +} + +LINK_ENTITY_TO_CLASS( info_teleport_destination, CTriggerTeleportDest ); + +//========================================================================== +class CTriggerSave : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT SaveTouch( CBaseEntity *pOther ); +}; +LINK_ENTITY_TO_CLASS( trigger_autosave, CTriggerSave ); + +void CTriggerSave::Spawn( void ) +{ + if ( g_pGameRules->IsDeathmatch() ) + { + REMOVE_ENTITY( ENT(pev) ); + return; + } + + InitTrigger(); + SetTouch( &CTriggerSave::SaveTouch ); +} + +void CTriggerSave::SaveTouch( CBaseEntity *pOther ) +{ + if ( !UTIL_IsMasterTriggered( m_sMaster, pOther ) ) + return; + + // Only save on clients + if ( !pOther->IsPlayer() ) + return; + + SetTouch( NULL ); + UTIL_Remove( this ); + SERVER_COMMAND( "autosave\n" ); +} + +#define SF_ENDSECTION_USEONLY 0x0001 + +class CTriggerEndSection : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT EndSectionTouch( CBaseEntity *pOther ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT EndSectionUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; +LINK_ENTITY_TO_CLASS( trigger_endsection, CTriggerEndSection ); + + +void CTriggerEndSection::EndSectionUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Only save on clients + if ( !pActivator->IsNetClient() ) + return; + + SetUse( NULL ); + + if ( pev->message ) + { + g_engfuncs.pfnEndSection(STRING(pev->message)); + } + UTIL_Remove( this ); +} + +void CTriggerEndSection::Spawn( void ) +{ + if ( g_pGameRules->IsDeathmatch() ) + { + REMOVE_ENTITY( ENT(pev) ); + return; + } + + InitTrigger(); + + SetUse ( &CTriggerEndSection::EndSectionUse ); + // If it is a "use only" trigger, then don't set the touch function. + if ( ! (pev->spawnflags & SF_ENDSECTION_USEONLY) ) + SetTouch( &CTriggerEndSection::EndSectionTouch ); +} + +void CTriggerEndSection::EndSectionTouch( CBaseEntity *pOther ) +{ + // Only save on clients + if ( !pOther->IsNetClient() ) + return; + + SetTouch( NULL ); + + if (pev->message) + { + g_engfuncs.pfnEndSection(STRING(pev->message)); + } + UTIL_Remove( this ); +} + +void CTriggerEndSection :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "section")) + { +// m_iszSectionName = ALLOC_STRING( pkvd->szValue ); + // Store this in message so we don't have to write save/restore for this ent + pev->message = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseTrigger::KeyValue( pkvd ); +} + + +class CTriggerGravity : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT GravityTouch( CBaseEntity *pOther ); +}; +LINK_ENTITY_TO_CLASS( trigger_gravity, CTriggerGravity ); + +void CTriggerGravity::Spawn( void ) +{ + InitTrigger(); + SetTouch( &CTriggerGravity::GravityTouch ); +} + +void CTriggerGravity::GravityTouch( CBaseEntity *pOther ) +{ + // Only save on clients + if ( !pOther->IsPlayer() ) + return; + + pOther->pev->gravity = pev->gravity; +} + + + + + + + +// this is a really bad idea. +class CTriggerChangeTarget : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_iszNewTarget; +}; +LINK_ENTITY_TO_CLASS( trigger_changetarget, CTriggerChangeTarget ); + +TYPEDESCRIPTION CTriggerChangeTarget::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerChangeTarget, m_iszNewTarget, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerChangeTarget,CBaseDelay); + +void CTriggerChangeTarget::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszNewTarget")) + { + m_iszNewTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + +void CTriggerChangeTarget::Spawn( void ) +{ +} + + +void CTriggerChangeTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity *pTarget = UTIL_FindEntityByString( NULL, "targetname", STRING( pev->target ) ); + + if (pTarget) + { + pTarget->pev->target = m_iszNewTarget; + CBaseMonster *pMonster = pTarget->MyMonsterPointer( ); + if (pMonster) + { + pMonster->m_pGoalEnt = NULL; + } + } +} + + + + +#define SF_CAMERA_PLAYER_POSITION 1 +#define SF_CAMERA_PLAYER_TARGET 2 +#define SF_CAMERA_PLAYER_TAKECONTROL 4 + +class CTriggerCamera : public CBaseDelay +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT FollowTarget( void ); + void Move(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + static TYPEDESCRIPTION m_SaveData[]; + + EHANDLE m_hPlayer; + EHANDLE m_hTarget; + CBaseEntity *m_pentPath; + int m_sPath; + float m_flWait; + float m_flReturnTime; + float m_flStopTime; + float m_moveDistance; + float m_targetSpeed; + float m_initialSpeed; + float m_acceleration; + float m_deceleration; + int m_state; + +}; +LINK_ENTITY_TO_CLASS( trigger_camera, CTriggerCamera ); + +// Global Savedata for changelevel friction modifier +TYPEDESCRIPTION CTriggerCamera::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerCamera, m_hPlayer, FIELD_EHANDLE ), + DEFINE_FIELD( CTriggerCamera, m_hTarget, FIELD_EHANDLE ), + DEFINE_FIELD( CTriggerCamera, m_pentPath, FIELD_CLASSPTR ), + DEFINE_FIELD( CTriggerCamera, m_sPath, FIELD_STRING ), + DEFINE_FIELD( CTriggerCamera, m_flWait, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_flReturnTime, FIELD_TIME ), + DEFINE_FIELD( CTriggerCamera, m_flStopTime, FIELD_TIME ), + DEFINE_FIELD( CTriggerCamera, m_moveDistance, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_targetSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_initialSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_acceleration, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_deceleration, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_state, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerCamera,CBaseDelay); + +void CTriggerCamera::Spawn( void ) +{ + pev->movetype = MOVETYPE_NOCLIP; + pev->solid = SOLID_NOT; // Remove model & collisions + pev->renderamt = 0; // The engine won't draw this model if this is set to 0 and blending is on + pev->rendermode = kRenderTransTexture; + + m_initialSpeed = pev->speed; + if ( m_acceleration == 0 ) + m_acceleration = 500; + if ( m_deceleration == 0 ) + m_deceleration = 500; +} + + +void CTriggerCamera :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "moveto")) + { + m_sPath = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "acceleration")) + { + m_acceleration = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "deceleration")) + { + m_deceleration = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + + + +void CTriggerCamera::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_state ) ) + return; + + // Toggle state + m_state = !m_state; + if (m_state == 0) + { + m_flReturnTime = gpGlobals->time; + return; + } + if ( !pActivator || !pActivator->IsPlayer() ) + { + pActivator = CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex( 1 )); + } + + m_hPlayer = pActivator; + + m_flReturnTime = gpGlobals->time + m_flWait; + pev->speed = m_initialSpeed; + m_targetSpeed = m_initialSpeed; + + if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TARGET ) ) + { + m_hTarget = m_hPlayer; + } + else + { + m_hTarget = GetNextTarget(); + } + + // Nothing to look at! + if ( m_hTarget == NULL ) + { + return; + } + + + if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TAKECONTROL ) ) + { + ((CBasePlayer *)pActivator)->EnableControl(FALSE); + } + + if ( m_sPath ) + { + m_pentPath = Instance( FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(m_sPath)) ); + } + else + { + m_pentPath = NULL; + } + + m_flStopTime = gpGlobals->time; + if ( m_pentPath ) + { + if ( m_pentPath->pev->speed != 0 ) + m_targetSpeed = m_pentPath->pev->speed; + + m_flStopTime += m_pentPath->GetDelay(); + } + + // copy over player information + if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_POSITION ) ) + { + UTIL_SetOrigin( pev, pActivator->pev->origin + pActivator->pev->view_ofs ); + pev->angles.x = -pActivator->pev->angles.x; + pev->angles.y = pActivator->pev->angles.y; + pev->angles.z = 0; + pev->velocity = pActivator->pev->velocity; + } + else + { + pev->velocity = Vector( 0, 0, 0 ); + } + + SET_VIEW( pActivator->edict(), edict() ); + + SET_MODEL(ENT(pev), STRING(pActivator->pev->model) ); + + // follow the player down + SetThink( &CTriggerCamera::FollowTarget ); + pev->nextthink = gpGlobals->time; + + m_moveDistance = 0; + Move(); +} + + +void CTriggerCamera::FollowTarget( ) +{ + if (m_hPlayer == NULL) + return; + + if (m_hTarget == NULL || m_flReturnTime < gpGlobals->time) + { + if (m_hPlayer->IsAlive( )) + { + SET_VIEW( m_hPlayer->edict(), m_hPlayer->edict() ); + ((CBasePlayer *)((CBaseEntity *)m_hPlayer))->EnableControl(TRUE); + } + SUB_UseTargets( this, USE_TOGGLE, 0 ); + pev->avelocity = Vector( 0, 0, 0 ); + m_state = 0; + return; + } + + Vector vecGoal = UTIL_VecToAngles( m_hTarget->pev->origin - pev->origin ); + vecGoal.x = -vecGoal.x; + + if (pev->angles.y > 360) + pev->angles.y -= 360; + + if (pev->angles.y < 0) + pev->angles.y += 360; + + float dx = vecGoal.x - pev->angles.x; + float dy = vecGoal.y - pev->angles.y; + + if (dx < -180) + dx += 360; + if (dx > 180) + dx = dx - 360; + + if (dy < -180) + dy += 360; + if (dy > 180) + dy = dy - 360; + + pev->avelocity.x = dx * 40 * gpGlobals->frametime; + pev->avelocity.y = dy * 40 * gpGlobals->frametime; + + + if (!(FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TAKECONTROL))) + { + pev->velocity = pev->velocity * 0.8; + if (pev->velocity.Length( ) < 10.0) + pev->velocity = g_vecZero; + } + + pev->nextthink = gpGlobals->time; + + Move(); +} + +void CTriggerCamera::Move() +{ + // Not moving on a path, return + if (!m_pentPath) + return; + + // Subtract movement from the previous frame + m_moveDistance -= pev->speed * gpGlobals->frametime; + + // Have we moved enough to reach the target? + if ( m_moveDistance <= 0 ) + { + // Fire the passtarget if there is one + if ( m_pentPath->pev->message ) + { + FireTargets( STRING(m_pentPath->pev->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( m_pentPath->pev->spawnflags, SF_CORNER_FIREONCE ) ) + m_pentPath->pev->message = 0; + } + // Time to go to the next target + m_pentPath = m_pentPath->GetNextTarget(); + + // Set up next corner + if ( !m_pentPath ) + { + pev->velocity = g_vecZero; + } + else + { + if ( m_pentPath->pev->speed != 0 ) + m_targetSpeed = m_pentPath->pev->speed; + + Vector delta = m_pentPath->pev->origin - pev->origin; + m_moveDistance = delta.Length(); + pev->movedir = delta.Normalize(); + m_flStopTime = gpGlobals->time + m_pentPath->GetDelay(); + } + } + + if ( m_flStopTime > gpGlobals->time ) + pev->speed = UTIL_Approach( 0, pev->speed, m_deceleration * gpGlobals->frametime ); + else + pev->speed = UTIL_Approach( m_targetSpeed, pev->speed, m_acceleration * gpGlobals->frametime ); + + float fraction = 2 * gpGlobals->frametime; + pev->velocity = ((pev->movedir * pev->speed) * fraction) + (pev->velocity * (1-fraction)); +} + +void CClientFog :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "startdist")) + { + m_iStartDist = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "enddist")) + { + m_iEndDist = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CClientFog :: Spawn ( void ) +{ + pev->movetype = MOVETYPE_NOCLIP; + pev->solid = SOLID_NOT; // Remove model & collisions + pev->renderamt = 0; // The engine won't draw this model if this is set to 0 and blending is on + pev->rendermode = kRenderTransTexture; +} + +LINK_ENTITY_TO_CLASS( env_fog, CClientFog ); diff --git a/dmc/dlls/util.cpp b/dmc/dlls/util.cpp new file mode 100644 index 0000000..4a1d347 --- /dev/null +++ b/dmc/dlls/util.cpp @@ -0,0 +1,2713 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== util.cpp ======================================================== + + Utility code. Really not optional after all. + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include +#include "../engine/shake.h" +#include "decals.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" + +/* +===================== +UTIL_WeaponTimeBase + +Time basis for weapons ( zero based of predicting client weapons ) +===================== +*/ +float UTIL_WeaponTimeBase( void ) +{ + return 0.0; +} + +static unsigned int glSeed = 0; + +unsigned int seed_table[ 256 ] = +{ + 28985, 27138, 26457, 9451, 17764, 10909, 28790, 8716, 6361, 4853, 17798, 21977, 19643, 20662, 10834, 20103, + 27067, 28634, 18623, 25849, 8576, 26234, 23887, 18228, 32587, 4836, 3306, 1811, 3035, 24559, 18399, 315, + 26766, 907, 24102, 12370, 9674, 2972, 10472, 16492, 22683, 11529, 27968, 30406, 13213, 2319, 23620, 16823, + 10013, 23772, 21567, 1251, 19579, 20313, 18241, 30130, 8402, 20807, 27354, 7169, 21211, 17293, 5410, 19223, + 10255, 22480, 27388, 9946, 15628, 24389, 17308, 2370, 9530, 31683, 25927, 23567, 11694, 26397, 32602, 15031, + 18255, 17582, 1422, 28835, 23607, 12597, 20602, 10138, 5212, 1252, 10074, 23166, 19823, 31667, 5902, 24630, + 18948, 14330, 14950, 8939, 23540, 21311, 22428, 22391, 3583, 29004, 30498, 18714, 4278, 2437, 22430, 3439, + 28313, 23161, 25396, 13471, 19324, 15287, 2563, 18901, 13103, 16867, 9714, 14322, 15197, 26889, 19372, 26241, + 31925, 14640, 11497, 8941, 10056, 6451, 28656, 10737, 13874, 17356, 8281, 25937, 1661, 4850, 7448, 12744, + 21826, 5477, 10167, 16705, 26897, 8839, 30947, 27978, 27283, 24685, 32298, 3525, 12398, 28726, 9475, 10208, + 617, 13467, 22287, 2376, 6097, 26312, 2974, 9114, 21787, 28010, 4725, 15387, 3274, 10762, 31695, 17320, + 18324, 12441, 16801, 27376, 22464, 7500, 5666, 18144, 15314, 31914, 31627, 6495, 5226, 31203, 2331, 4668, + 12650, 18275, 351, 7268, 31319, 30119, 7600, 2905, 13826, 11343, 13053, 15583, 30055, 31093, 5067, 761, + 9685, 11070, 21369, 27155, 3663, 26542, 20169, 12161, 15411, 30401, 7580, 31784, 8985, 29367, 20989, 14203, + 29694, 21167, 10337, 1706, 28578, 887, 3373, 19477, 14382, 675, 7033, 15111, 26138, 12252, 30996, 21409, + 25678, 18555, 13256, 23316, 22407, 16727, 991, 9236, 5373, 29402, 6117, 15241, 27715, 19291, 19888, 19847 +}; + +unsigned int U_Random( void ) +{ + glSeed *= 69069; + glSeed += seed_table[ glSeed & 0xff ]; + + return ( ++glSeed & 0x0fffffff ); +} + +void U_Srand( unsigned int seed ) +{ + glSeed = seed_table[ seed & 0xff ]; +} + +/* +===================== +UTIL_SharedRandomLong +===================== +*/ +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ) +{ + + unsigned int range; + + U_Srand( (int)seed + low + high ); + + range = high - low + 1; + if ( !(range - 1) ) + { + return low; + } + else + { + int offset; + int rnum; + + rnum = U_Random(); + + offset = rnum % range; + + return (low + offset); + } +} + +/* +===================== +UTIL_SharedRandomFloat +===================== +*/ +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ) +{ + // + unsigned int range; + + U_Srand( (int)seed + *(int *)&low + *(int *)&high ); + + U_Random(); + U_Random(); + + range = high - low; + if ( !range ) + { + return low; + } + else + { + int tensixrand; + float offset; + + tensixrand = U_Random() & 65535; + + offset = (float)tensixrand / 65536.0; + + return (low + offset * range ); + } +} + +void UTIL_ParametricRocket( entvars_t *pev, Vector vecOrigin, Vector vecAngles, edict_t *owner ) +{ + pev->startpos = vecOrigin; + // Trace out line to end pos + TraceResult tr; + UTIL_MakeVectors( vecAngles ); + UTIL_TraceLine( pev->startpos, pev->startpos + gpGlobals->v_forward * 8192, ignore_monsters, owner, &tr); + pev->endpos = tr.vecEndPos; + + // Now compute how long it will take based on current velocity + Vector vecTravel = pev->endpos - pev->startpos; + float travelTime = 0.0; + if ( pev->velocity.Length() > 0 ) + { + travelTime = vecTravel.Length() / pev->velocity.Length(); + } + pev->starttime = gpGlobals->time; + pev->impacttime = gpGlobals->time + travelTime; +} + +void UTIL_ClientProjectile( const Vector &vecOrigin, const Vector &vecVelocity, short sModelIndex, int iOwnerIndex, int iLife ) +{ + // we'd like to just send this projectile to a person in the shooter's PAS. However + // the projectile won't be sent to a player outside of water if shot from inside water + // and vice-versa, so we do a trace here to figure out if the trace starts or stops in water. + // if it crosses contents, we'll just broadcast the projectile. Otherwise, just send to PVS + // of the trace's endpoint. + BOOL fBroadcast; + + fBroadcast = FALSE; // assume we're just gonna send this message to PVS. + + TraceResult tr; + Vector vecTraceDir; + + vecTraceDir = vecVelocity.Normalize(); + + UTIL_TraceLine( vecOrigin, vecOrigin + vecTraceDir * 4096, ignore_monsters, ENT(iOwnerIndex), &tr ); + + if ( UTIL_PointContents( vecOrigin ) != UTIL_PointContents( tr.vecEndPos ) ) + { + fBroadcast = TRUE; + } + + if ( fBroadcast ) + { + // The projectile is going to cross content types + // (which will block PVS/PAS). Send to every client + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + } + else + { + // just the PVS of where the projectile will hit. + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, tr.vecEndPos ); + } + WRITE_BYTE( TE_PROJECTILE ); + WRITE_COORD( vecOrigin.x ); + WRITE_COORD( vecOrigin.y ); + WRITE_COORD( vecOrigin.z ); + + WRITE_COORD( vecVelocity.x ); + WRITE_COORD( vecVelocity.y ); + WRITE_COORD( vecVelocity.z ); + + WRITE_SHORT( sModelIndex ); + WRITE_BYTE ( iLife ); + WRITE_BYTE ( (BYTE)iOwnerIndex ); + MESSAGE_END(); +} + +int g_groupmask = 0; +int g_groupop = 0; + +// Normal overrides +void UTIL_SetGroupTrace( int groupmask, int op ) +{ + g_groupmask = groupmask; + g_groupop = op; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + +void UTIL_UnsetGroupTrace( void ) +{ + g_groupmask = 0; + g_groupop = 0; + + ENGINE_SETGROUPMASK( 0, 0 ); +} + +// Smart version, it'll clean itself up when it pops off stack +UTIL_GroupTrace::UTIL_GroupTrace( int groupmask, int op ) +{ + m_oldgroupmask = g_groupmask; + m_oldgroupop = g_groupop; + + g_groupmask = groupmask; + g_groupop = op; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + +UTIL_GroupTrace::~UTIL_GroupTrace( void ) +{ + g_groupmask = m_oldgroupmask; + g_groupop = m_oldgroupop; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + +TYPEDESCRIPTION gEntvarsDescription[] = +{ + DEFINE_ENTITY_FIELD( classname, FIELD_STRING ), + DEFINE_ENTITY_GLOBAL_FIELD( globalname, FIELD_STRING ), + + DEFINE_ENTITY_FIELD( origin, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_FIELD( oldorigin, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_FIELD( velocity, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( basevelocity, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( movedir, FIELD_VECTOR ), + + DEFINE_ENTITY_FIELD( angles, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( avelocity, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( punchangle, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( v_angle, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( fixangle, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( idealpitch, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( pitch_speed, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( ideal_yaw, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( yaw_speed, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( modelindex, FIELD_INTEGER ), + DEFINE_ENTITY_GLOBAL_FIELD( model, FIELD_MODELNAME ), + + DEFINE_ENTITY_FIELD( viewmodel, FIELD_MODELNAME ), + DEFINE_ENTITY_FIELD( weaponmodel, FIELD_MODELNAME ), + + DEFINE_ENTITY_FIELD( absmin, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_FIELD( absmax, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_GLOBAL_FIELD( mins, FIELD_VECTOR ), + DEFINE_ENTITY_GLOBAL_FIELD( maxs, FIELD_VECTOR ), + DEFINE_ENTITY_GLOBAL_FIELD( size, FIELD_VECTOR ), + + DEFINE_ENTITY_FIELD( ltime, FIELD_TIME ), + DEFINE_ENTITY_FIELD( nextthink, FIELD_TIME ), + + DEFINE_ENTITY_FIELD( solid, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( movetype, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( skin, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( body, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( effects, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( gravity, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( friction, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( light_level, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( frame, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( scale, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( sequence, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( animtime, FIELD_TIME ), + DEFINE_ENTITY_FIELD( framerate, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( controller, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( blending, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( rendermode, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( renderamt, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( rendercolor, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( renderfx, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( health, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( frags, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( weapons, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( takedamage, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( deadflag, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( view_ofs, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( button, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( impulse, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( chain, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( dmg_inflictor, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( enemy, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( aiment, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( owner, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( groundentity, FIELD_EDICT ), + + DEFINE_ENTITY_FIELD( spawnflags, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( flags, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( colormap, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( team, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( max_health, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( teleport_time, FIELD_TIME ), + DEFINE_ENTITY_FIELD( armortype, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( armorvalue, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( waterlevel, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( watertype, FIELD_INTEGER ), + + // Having these fields be local to the individual levels makes it easier to test those levels individually. + DEFINE_ENTITY_GLOBAL_FIELD( target, FIELD_STRING ), + DEFINE_ENTITY_GLOBAL_FIELD( targetname, FIELD_STRING ), + DEFINE_ENTITY_FIELD( netname, FIELD_STRING ), + DEFINE_ENTITY_FIELD( message, FIELD_STRING ), + + DEFINE_ENTITY_FIELD( dmg_take, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( dmg_save, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( dmg, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( dmgtime, FIELD_TIME ), + + DEFINE_ENTITY_FIELD( noise, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise1, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise2, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise3, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( speed, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( air_finished, FIELD_TIME ), + DEFINE_ENTITY_FIELD( pain_finished, FIELD_TIME ), + DEFINE_ENTITY_FIELD( radsuit_finished, FIELD_TIME ), +}; + +#define ENTVARS_COUNT (sizeof(gEntvarsDescription)/sizeof(gEntvarsDescription[0])) + + +#ifdef DEBUG +edict_t *DBG_EntOfVars( const entvars_t *pev ) +{ + if (pev->pContainingEntity != NULL) + return pev->pContainingEntity; + ALERT(at_console, "entvars_t pContainingEntity is NULL, calling into engine"); + edict_t* pent = (*g_engfuncs.pfnFindEntityByVars)((entvars_t*)pev); + if (pent == NULL) + ALERT(at_console, "DAMN! Even the engine couldn't FindEntityByVars!"); + ((entvars_t *)pev)->pContainingEntity = pent; + return pent; +} +#endif //DEBUG + + +#ifdef DEBUG + void +DBG_AssertFunction( + BOOL fExpr, + const char* szExpr, + const char* szFile, + int szLine, + const char* szMessage) + { + if (fExpr) + return; + char szOut[512]; + if (szMessage != NULL) + sprintf(szOut, "ASSERT FAILED:\n %s \n(%s@%d)\n%s", szExpr, szFile, szLine, szMessage); + else + sprintf(szOut, "ASSERT FAILED:\n %s \n(%s@%d)", szExpr, szFile, szLine); + ALERT(at_console, szOut); + } +#endif // DEBUG + +BOOL UTIL_GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) +{ + return g_pGameRules->GetNextBestWeapon( pPlayer, pCurrentWeapon ); +} + +// ripped this out of the engine +float UTIL_AngleMod(float a) +{ + if (a < 0) + { + a = a + 360 * ((int)(a / 360) + 1); + } + else if (a >= 360) + { + a = a - 360 * ((int)(a / 360)); + } + // a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + +float UTIL_AngleDiff( float destAngle, float srcAngle ) +{ + float delta; + + delta = destAngle - srcAngle; + if ( destAngle > srcAngle ) + { + if ( delta >= 180 ) + delta -= 360; + } + else + { + if ( delta <= -180 ) + delta += 360; + } + return delta; +} + + +void EffectPrint( CBasePlayer *pPlayer, int color, int effect, int channel, char *text ) +{ + hudtextparms_t m_TesParms; + + memset(&m_TesParms, 0, sizeof(m_TesParms)); + + if (channel == WIN_MSG) + { + m_TesParms.x = -1; + m_TesParms.y = 0.4; + } + + if (channel == NOTIFY) + { + m_TesParms.x = -1; + m_TesParms.y = 0.9; + } + + else if (channel == INFO) + { + m_TesParms.x = 0; + m_TesParms.y = 0.85; + } + else if (channel == LEADER_HIT) + { + m_TesParms.x = -1; + m_TesParms.y = 1.0; + } + else if (channel == CRITICAL) + { + m_TesParms.x = -1; + m_TesParms.y = 0.7; + } + + else if (channel == MISC_SHIT) + { + m_TesParms.x = -1; + m_TesParms.y = 0.25; + } + else if (channel == CHASECAM) + { + m_TesParms.x = 0.0; + m_TesParms.y = 0.3; + } + else if (channel == CHASECAM_TARGET) + { + m_TesParms.x = 0.09; + m_TesParms.y = 0.3; + } + + m_TesParms.channel = channel; + + /* + effect = F_IN_OUT - FADE IN / FADE OUT + effect = CREDITS - CREDITS + effect = SCANOUT - SCAN OUT + */ + + m_TesParms.effect = effect; + + + if (color == BLUE) // Blue Color + { + m_TesParms.r1 = 0; + m_TesParms.g1 = 0; + m_TesParms.b1 = 255; + m_TesParms.a1 = 0; + } + else if (color == RED) // Red Color + { + m_TesParms.r1 = 255; + m_TesParms.g1 = 0; + m_TesParms.b1 = 0; + m_TesParms.a1 = 0; + } + else if (color == WHITE) //Whitey Color + { + m_TesParms.r1 = 255; + m_TesParms.g1 = 255; + m_TesParms.b1 = 200; + } + + //This is the second color in the effect, this is white for now... + m_TesParms.r2 = 255; + m_TesParms.g2 = 255; + m_TesParms.b2 = 255; + m_TesParms.a2 = 0; + + //Fade In Time + if (effect == SCANOUT) + m_TesParms.fadeinTime = 0.01; + else + m_TesParms.fadeinTime = 0.3; + + //Fade Out Time + + m_TesParms.fadeoutTime = 0.3; + + //Time the Effect is going to be up + if (effect == F_IN_OUT) + m_TesParms.holdTime = 1.5; + + else + m_TesParms.holdTime = 3.5; + + //Time the effect is aplied (?) + m_TesParms.fxTime = 0.25; + + if (pPlayer == NULL) + UTIL_HudMessageAll( m_TesParms, text ); + else + UTIL_HudMessage(pPlayer, m_TesParms, text ); + + +} + +Vector UTIL_VecToAngles( const Vector &vec ) +{ + float rgflVecOut[3]; + VEC_TO_ANGLES(vec, rgflVecOut); + return Vector(rgflVecOut); +} + +// float UTIL_MoveToOrigin( edict_t *pent, const Vector vecGoal, float flDist, int iMoveType ) +void UTIL_MoveToOrigin( edict_t *pent, const Vector &vecGoal, float flDist, int iMoveType ) +{ + float rgfl[3]; + vecGoal.CopyToArray(rgfl); +// return MOVE_TO_ORIGIN ( pent, rgfl, flDist, iMoveType ); + MOVE_TO_ORIGIN ( pent, rgfl, flDist, iMoveType ); +} + + +int UTIL_EntitiesInBox( CBaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + int count; + + count = 0; + + if ( !pEdict ) + return count; + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + if ( pEdict->free ) // Not in use + continue; + + if ( flagMask && !(pEdict->v.flags & flagMask) ) // Does it meet the criteria? + continue; + + if ( mins.x > pEdict->v.absmax.x || + mins.y > pEdict->v.absmax.y || + mins.z > pEdict->v.absmax.z || + maxs.x < pEdict->v.absmin.x || + maxs.y < pEdict->v.absmin.y || + maxs.z < pEdict->v.absmin.z ) + continue; + + pEntity = CBaseEntity::Instance(pEdict); + if ( !pEntity ) + continue; + + pList[ count ] = pEntity; + count++; + + if ( count >= listMax ) + return count; + } + + return count; +} + + +int UTIL_MonstersInSphere( CBaseEntity **pList, int listMax, const Vector ¢er, float radius ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + int count; + float distance, delta; + + count = 0; + float radiusSquared = radius * radius; + + if ( !pEdict ) + return count; + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + if ( pEdict->free ) // Not in use + continue; + + if ( !(pEdict->v.flags & (FL_CLIENT|FL_MONSTER)) ) // Not a client/monster ? + continue; + + // Use origin for X & Y since they are centered for all monsters + // Now X + delta = center.x - pEdict->v.origin.x;//(pEdict->v.absmin.x + pEdict->v.absmax.x)*0.5; + delta *= delta; + + if ( delta > radiusSquared ) + continue; + distance = delta; + + // Now Y + delta = center.y - pEdict->v.origin.y;//(pEdict->v.absmin.y + pEdict->v.absmax.y)*0.5; + delta *= delta; + + distance += delta; + if ( distance > radiusSquared ) + continue; + + // Now Z + delta = center.z - (pEdict->v.absmin.z + pEdict->v.absmax.z)*0.5; + delta *= delta; + + distance += delta; + if ( distance > radiusSquared ) + continue; + + pEntity = CBaseEntity::Instance(pEdict); + if ( !pEntity ) + continue; + + pList[ count ] = pEntity; + count++; + + if ( count >= listMax ) + return count; + } + + + return count; +} + + +CBaseEntity *UTIL_FindEntityInSphere( CBaseEntity *pStartEntity, const Vector &vecCenter, float flRadius ) +{ + edict_t *pentEntity; + + if (pStartEntity) + pentEntity = pStartEntity->edict(); + else + pentEntity = NULL; + + pentEntity = FIND_ENTITY_IN_SPHERE( pentEntity, vecCenter, flRadius); + + if (!FNullEnt(pentEntity)) + return CBaseEntity::Instance(pentEntity); + return NULL; +} + + +CBaseEntity *UTIL_FindEntityByString( CBaseEntity *pStartEntity, const char *szKeyword, const char *szValue ) +{ + edict_t *pentEntity; + + if (pStartEntity) + pentEntity = pStartEntity->edict(); + else + pentEntity = NULL; + + pentEntity = FIND_ENTITY_BY_STRING( pentEntity, szKeyword, szValue ); + + if (!FNullEnt(pentEntity)) + return CBaseEntity::Instance(pentEntity); + return NULL; +} + +CBaseEntity *UTIL_FindEntityByClassname( CBaseEntity *pStartEntity, const char *szName ) +{ + return UTIL_FindEntityByString( pStartEntity, "classname", szName ); +} + +CBaseEntity *UTIL_FindEntityByTargetname( CBaseEntity *pStartEntity, const char *szName ) +{ + return UTIL_FindEntityByString( pStartEntity, "targetname", szName ); +} + + +CBaseEntity *UTIL_FindEntityGeneric( const char *szWhatever, Vector &vecSrc, float flRadius ) +{ + CBaseEntity *pEntity = NULL; + + pEntity = UTIL_FindEntityByTargetname( NULL, szWhatever ); + if (pEntity) + return pEntity; + + CBaseEntity *pSearch = NULL; + float flMaxDist2 = flRadius * flRadius; + while ((pSearch = UTIL_FindEntityByClassname( pSearch, szWhatever )) != NULL) + { + float flDist2 = (pSearch->pev->origin - vecSrc).Length(); + flDist2 = flDist2 * flDist2; + if (flMaxDist2 > flDist2) + { + pEntity = pSearch; + flMaxDist2 = flDist2; + } + } + return pEntity; +} + + +// returns a CBaseEntity pointer to a player by index. Only returns if the player is spawned and connected +// otherwise returns NULL +// Index is 1 based +CBaseEntity *UTIL_PlayerByIndex( int playerIndex ) +{ + CBaseEntity *pPlayer = NULL; + + if ( playerIndex > 0 && playerIndex <= gpGlobals->maxClients ) + { + edict_t *pPlayerEdict = INDEXENT( playerIndex ); + if ( pPlayerEdict && !pPlayerEdict->free ) + { + pPlayer = CBaseEntity::Instance( pPlayerEdict ); + } + } + + return pPlayer; +} + + +void UTIL_MakeVectors( const Vector &vecAngles ) +{ + MAKE_VECTORS( vecAngles ); +} + + +void UTIL_MakeAimVectors( const Vector &vecAngles ) +{ + float rgflVec[3]; + vecAngles.CopyToArray(rgflVec); + rgflVec[0] = -rgflVec[0]; + MAKE_VECTORS(rgflVec); +} + + +#define SWAP(a,b,temp) ((temp)=(a),(a)=(b),(b)=(temp)) + +void UTIL_MakeInvVectors( const Vector &vec, globalvars_t *pgv ) +{ + MAKE_VECTORS(vec); + + float tmp; + pgv->v_right = pgv->v_right * -1; + + SWAP(pgv->v_forward.y, pgv->v_right.x, tmp); + SWAP(pgv->v_forward.z, pgv->v_up.x, tmp); + SWAP(pgv->v_right.z, pgv->v_up.y, tmp); +} + + +void UTIL_EmitAmbientSound( edict_t *entity, const Vector &vecOrigin, const char *samp, float vol, float attenuation, int fFlags, int pitch ) +{ + float rgfl[3]; + vecOrigin.CopyToArray(rgfl); + + if (samp && *samp == '!') + { + char name[32]; + if (SENTENCEG_Lookup(samp, name) >= 0) + EMIT_AMBIENT_SOUND(entity, rgfl, name, vol, attenuation, fFlags, pitch); + } + else + EMIT_AMBIENT_SOUND(entity, rgfl, samp, vol, attenuation, fFlags, pitch); +} + +static unsigned short FixedUnsigned16( float value, float scale ) +{ + int output; + + output = value * scale; + if ( output < 0 ) + output = 0; + if ( output > 0xFFFF ) + output = 0xFFFF; + + return (unsigned short)output; +} + +static short FixedSigned16( float value, float scale ) +{ + int output; + + output = value * scale; + + if ( output > 32767 ) + output = 32767; + + if ( output < -32768 ) + output = -32768; + + return (short)output; +} + +// Shake the screen of all clients within radius +// radius == 0, shake all clients +// UNDONE: Allow caller to shake clients not ONGROUND? +// UNDONE: Fix falloff model (disabled)? +// UNDONE: Affect user controls? +void UTIL_ScreenShake( const Vector ¢er, float amplitude, float frequency, float duration, float radius ) +{ + int i; + float localAmplitude; + ScreenShake shake; + + shake.duration = FixedUnsigned16( duration, 1<<12 ); // 4.12 fixed + shake.frequency = FixedUnsigned16( frequency, 1<<8 ); // 8.8 fixed + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + if ( !pPlayer || !(pPlayer->pev->flags & FL_ONGROUND) ) // Don't shake if not onground + continue; + + localAmplitude = 0; + + if ( radius <= 0 ) + localAmplitude = amplitude; + else + { + Vector delta = center - pPlayer->pev->origin; + float distance = delta.Length(); + + // Had to get rid of this falloff - it didn't work well + if ( distance < radius ) + localAmplitude = amplitude;//radius - distance; + } + if ( localAmplitude ) + { + shake.amplitude = FixedUnsigned16( localAmplitude, 1<<12 ); // 4.12 fixed + + MESSAGE_BEGIN( MSG_ONE, gmsgShake, NULL, pPlayer->edict() ); // use the magic #1 for "one client" + + WRITE_SHORT( shake.amplitude ); // shake amount + WRITE_SHORT( shake.duration ); // shake lasts this long + WRITE_SHORT( shake.frequency ); // shake noise frequency + + MESSAGE_END(); + } + } +} + + + +void UTIL_ScreenShakeAll( const Vector ¢er, float amplitude, float frequency, float duration ) +{ + UTIL_ScreenShake( center, amplitude, frequency, duration, 0 ); +} + + +void UTIL_ScreenFadeBuild( ScreenFade &fade, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + fade.duration = FixedUnsigned16( fadeTime, 1<<12 ); // 4.12 fixed + fade.holdTime = FixedUnsigned16( fadeHold, 1<<12 ); // 4.12 fixed + fade.r = (int)color.x; + fade.g = (int)color.y; + fade.b = (int)color.z; + fade.a = alpha; + fade.fadeFlags = flags; +} + + +void UTIL_ScreenFadeWrite( const ScreenFade &fade, CBaseEntity *pEntity ) +{ + if ( !pEntity || !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsgFade, NULL, pEntity->edict() ); // use the magic #1 for "one client" + + WRITE_SHORT( fade.duration ); // fade lasts this long + WRITE_SHORT( fade.holdTime ); // fade lasts this long + WRITE_SHORT( fade.fadeFlags ); // fade type (in / out) + WRITE_BYTE( fade.r ); // fade red + WRITE_BYTE( fade.g ); // fade green + WRITE_BYTE( fade.b ); // fade blue + WRITE_BYTE( fade.a ); // fade blue + + MESSAGE_END(); +} + + +void UTIL_ScreenFadeAll( const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + int i; + ScreenFade fade; + + + UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, alpha, flags ); + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + UTIL_ScreenFadeWrite( fade, pPlayer ); + } +} + + +void UTIL_ScreenFade( CBaseEntity *pEntity, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + ScreenFade fade; + + UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, alpha, flags ); + UTIL_ScreenFadeWrite( fade, pEntity ); +} + + +void UTIL_HudMessage( CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage ) +{ + if ( !pEntity || !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, SVC_TEMPENTITY, NULL, pEntity->edict() ); + WRITE_BYTE( TE_TEXTMESSAGE ); + WRITE_BYTE( textparms.channel & 0xFF ); + + WRITE_SHORT( FixedSigned16( textparms.x, 1<<13 ) ); + WRITE_SHORT( FixedSigned16( textparms.y, 1<<13 ) ); + WRITE_BYTE( textparms.effect ); + + WRITE_BYTE( textparms.r1 ); + WRITE_BYTE( textparms.g1 ); + WRITE_BYTE( textparms.b1 ); + WRITE_BYTE( textparms.a1 ); + + WRITE_BYTE( textparms.r2 ); + WRITE_BYTE( textparms.g2 ); + WRITE_BYTE( textparms.b2 ); + WRITE_BYTE( textparms.a2 ); + + WRITE_SHORT( FixedUnsigned16( textparms.fadeinTime, 1<<8 ) ); + WRITE_SHORT( FixedUnsigned16( textparms.fadeoutTime, 1<<8 ) ); + WRITE_SHORT( FixedUnsigned16( textparms.holdTime, 1<<8 ) ); + + if ( textparms.effect == 2 ) + WRITE_SHORT( FixedUnsigned16( textparms.fxTime, 1<<8 ) ); + + if ( strlen( pMessage ) < 512 ) + { + WRITE_STRING( pMessage ); + } + else + { + char tmp[512]; + strncpy( tmp, pMessage, 511 ); + tmp[511] = 0; + WRITE_STRING( tmp ); + } + MESSAGE_END(); +} + +void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage ) +{ + int i; + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + UTIL_HudMessage( pPlayer, textparms, pMessage ); + } +} + + +extern int gmsgTextMsg, gmsgSayText; +void UTIL_ClientPrintAll( int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) +{ + MESSAGE_BEGIN( MSG_ALL, gmsgTextMsg ); + WRITE_BYTE( msg_dest ); + WRITE_STRING( msg_name ); + + if ( param1 ) + WRITE_STRING( param1 ); + if ( param2 ) + WRITE_STRING( param2 ); + if ( param3 ) + WRITE_STRING( param3 ); + if ( param4 ) + WRITE_STRING( param4 ); + + MESSAGE_END(); +} + +void ClientPrint( entvars_t *client, int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgTextMsg, NULL, client ); + WRITE_BYTE( msg_dest ); + WRITE_STRING( msg_name ); + + if ( param1 ) + WRITE_STRING( param1 ); + if ( param2 ) + WRITE_STRING( param2 ); + if ( param3 ) + WRITE_STRING( param3 ); + if ( param4 ) + WRITE_STRING( param4 ); + + MESSAGE_END(); +} + +void UTIL_SayText( const char *pText, CBaseEntity *pEntity ) +{ + if ( !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, pEntity->edict() ); + WRITE_BYTE( pEntity->entindex() ); + WRITE_STRING( pText ); + MESSAGE_END(); +} + +void UTIL_SayTextAll( const char *pText, CBaseEntity *pEntity ) +{ + MESSAGE_BEGIN( MSG_ALL, gmsgSayText, NULL ); + WRITE_BYTE( pEntity->entindex() ); + WRITE_STRING( pText ); + MESSAGE_END(); +} + + +char *UTIL_dtos1( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos2( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos3( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos4( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +void UTIL_ShowMessage( const char *pString, CBaseEntity *pEntity ) +{ + if ( !pEntity || !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsgHudText, NULL, pEntity->edict() ); + WRITE_STRING( pString ); + MESSAGE_END(); +} + + +void UTIL_ShowMessageAll( const char *pString ) +{ + int i; + + // loop through all players + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + UTIL_ShowMessage( pString, pPlayer ); + } +} + +// Overloaded to add IGNORE_GLASS +void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, IGNORE_GLASS ignoreGlass, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_LINE( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE) | (ignoreGlass?0x100:0), pentIgnore, ptr ); +} + + +void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_LINE( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE), pentIgnore, ptr ); +} + + +void UTIL_TraceHull( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, int hullNumber, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_HULL( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE), hullNumber, pentIgnore, ptr ); +} + +void UTIL_TraceModel( const Vector &vecStart, const Vector &vecEnd, int hullNumber, edict_t *pentModel, TraceResult *ptr ) +{ + g_engfuncs.pfnTraceModel( vecStart, vecEnd, hullNumber, pentModel, ptr ); +} + + +TraceResult UTIL_GetGlobalTrace( ) +{ + TraceResult tr; + + tr.fAllSolid = gpGlobals->trace_allsolid; + tr.fStartSolid = gpGlobals->trace_startsolid; + tr.fInOpen = gpGlobals->trace_inopen; + tr.fInWater = gpGlobals->trace_inwater; + tr.flFraction = gpGlobals->trace_fraction; + tr.flPlaneDist = gpGlobals->trace_plane_dist; + tr.pHit = gpGlobals->trace_ent; + tr.vecEndPos = gpGlobals->trace_endpos; + tr.vecPlaneNormal = gpGlobals->trace_plane_normal; + tr.iHitgroup = gpGlobals->trace_hitgroup; + return tr; +} + + +void UTIL_SetSize( entvars_t *pev, const Vector &vecMin, const Vector &vecMax ) +{ + SET_SIZE( ENT(pev), vecMin, vecMax ); +} + + +float UTIL_VecToYaw( const Vector &vec ) +{ + return VEC_TO_YAW(vec); +} + + +void UTIL_SetOrigin( entvars_t *pev, const Vector &vecOrigin ) +{ + SET_ORIGIN(ENT(pev), vecOrigin ); +} + +void UTIL_ParticleEffect( const Vector &vecOrigin, const Vector &vecDirection, ULONG ulColor, ULONG ulCount ) +{ + PARTICLE_EFFECT( vecOrigin, vecDirection, (float)ulColor, (float)ulCount ); +} + + +float UTIL_Approach( float target, float value, float speed ) +{ + float delta = target - value; + + if ( delta > speed ) + value += speed; + else if ( delta < -speed ) + value -= speed; + else + value = target; + + return value; +} + + +float UTIL_ApproachAngle( float target, float value, float speed ) +{ + target = UTIL_AngleMod( target ); + value = UTIL_AngleMod( target ); + + float delta = target - value; + + // Speed is assumed to be positive + if ( speed < 0 ) + speed = -speed; + + if ( delta < -180 ) + delta += 360; + else if ( delta > 180 ) + delta -= 360; + + if ( delta > speed ) + value += speed; + else if ( delta < -speed ) + value -= speed; + else + value = target; + + return value; +} + + +float UTIL_AngleDistance( float next, float cur ) +{ + float delta = next - cur; + + if ( delta < -180 ) + delta += 360; + else if ( delta > 180 ) + delta -= 360; + + return delta; +} + + +float UTIL_SplineFraction( float value, float scale ) +{ + value = scale * value; + float valueSquared = value * value; + + // Nice little ease-in, ease-out spline-like curve + return 3 * valueSquared - 2 * valueSquared * value; +} + + +char* UTIL_VarArgs( char *format, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start (argptr, format); + vsprintf (string, format,argptr); + va_end (argptr); + + return string; +} + +Vector UTIL_GetAimVector( edict_t *pent, float flSpeed ) +{ + Vector tmp; + GET_AIM_VECTOR(pent, flSpeed, tmp); + return tmp; +} + +int UTIL_IsMasterTriggered(string_t sMaster, CBaseEntity *pActivator) +{ + if (sMaster) + { + edict_t *pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(sMaster)); + + if ( !FNullEnt(pentTarget) ) + { + CBaseEntity *pMaster = CBaseEntity::Instance(pentTarget); + if ( pMaster && (pMaster->ObjectCaps() & FCAP_MASTER) ) + return pMaster->IsTriggered( pActivator ); + } + + ALERT(at_console, "Master was null or not a master!\n"); + } + + // if this isn't a master entity, just say yes. + return 1; +} + +BOOL UTIL_ShouldShowBlood( int color ) +{ + if ( color != DONT_BLEED ) + { + if ( color == BLOOD_COLOR_RED ) + { + if ( CVAR_GET_FLOAT("violence_hblood") != 0 ) + return TRUE; + } + else + { + if ( CVAR_GET_FLOAT("violence_ablood") != 0 ) + return TRUE; + } + } + return FALSE; +} + +int UTIL_PointContents( const Vector &vec ) +{ + return POINT_CONTENTS(vec); +} + +void UTIL_BloodStream( const Vector &origin, const Vector &direction, int color, int amount ) +{ + if ( !UTIL_ShouldShowBlood( color ) ) + return; + + if ( g_Language == LANGUAGE_GERMAN && color == BLOOD_COLOR_RED ) + color = 0; + + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, origin ); + WRITE_BYTE( TE_BLOODSTREAM ); + WRITE_COORD( origin.x ); + WRITE_COORD( origin.y ); + WRITE_COORD( origin.z ); + WRITE_COORD( direction.x ); + WRITE_COORD( direction.y ); + WRITE_COORD( direction.z ); + WRITE_BYTE( color ); + WRITE_BYTE( min( amount, 255 ) ); + MESSAGE_END(); +} + +void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, int amount ) +{ + if ( !UTIL_ShouldShowBlood( color ) ) + return; + + if ( color == DONT_BLEED || amount == 0 ) + return; + + if ( g_Language == LANGUAGE_GERMAN && color == BLOOD_COLOR_RED ) + color = 0; + + if ( g_pGameRules->IsMultiplayer() ) + { + // scale up blood effect in multiplayer for better visibility + amount *= 2; + } + + if ( amount > 255 ) + amount = 255; + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, origin ); + WRITE_BYTE( TE_BLOODSPRITE ); + WRITE_COORD( origin.x); // pos + WRITE_COORD( origin.y); + WRITE_COORD( origin.z); + WRITE_SHORT( g_sModelIndexBloodSpray ); // initial sprite model + WRITE_SHORT( g_sModelIndexBloodDrop ); // droplet sprite models + WRITE_BYTE( color ); // color index into host_basepal + WRITE_BYTE( min( max( 3, amount / 10 ), 16 ) ); // size + MESSAGE_END(); +} + +Vector UTIL_RandomBloodVector( void ) +{ + Vector direction; + + direction.x = RANDOM_FLOAT ( -1, 1 ); + direction.y = RANDOM_FLOAT ( -1, 1 ); + direction.z = RANDOM_FLOAT ( 0, 1 ); + + return direction; +} + + +void UTIL_BloodDecalTrace( TraceResult *pTrace, int bloodColor ) +{ + if ( UTIL_ShouldShowBlood( bloodColor ) ) + { + if ( bloodColor == BLOOD_COLOR_RED ) + UTIL_DecalTrace( pTrace, DECAL_BLOOD1 + RANDOM_LONG(0,5) ); + else + UTIL_DecalTrace( pTrace, DECAL_YBLOOD1 + RANDOM_LONG(0,5) ); + } +} + + +void UTIL_DecalTrace( TraceResult *pTrace, int decalNumber ) +{ + short entityIndex; + int index; + int message; + + if ( decalNumber < 0 ) + return; + + index = gDecals[ decalNumber ].index; + + if ( index < 0 ) + return; + + if (pTrace->flFraction == 1.0) + return; + + // Only decal BSP models + if ( pTrace->pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( pTrace->pHit ); + if ( pEntity && !pEntity->IsBSPModel() ) + return; + entityIndex = ENTINDEX( pTrace->pHit ); + } + else + entityIndex = 0; + + message = TE_DECAL; + if ( entityIndex != 0 ) + { + if ( index > 255 ) + { + message = TE_DECALHIGH; + index -= 256; + } + } + else + { + message = TE_WORLDDECAL; + if ( index > 255 ) + { + message = TE_WORLDDECALHIGH; + index -= 256; + } + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( message ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_BYTE( index ); + if ( entityIndex ) + WRITE_SHORT( entityIndex ); + MESSAGE_END(); +} + +/* +============== +UTIL_PlayerDecalTrace + +A player is trying to apply his custom decal for the spray can. +Tell connected clients to display it, or use the default spray can decal +if the custom can't be loaded. +============== +*/ +void UTIL_PlayerDecalTrace( TraceResult *pTrace, int playernum, int decalNumber, BOOL bIsCustom ) +{ + int index; + + if (!bIsCustom) + { + if ( decalNumber < 0 ) + return; + + index = gDecals[ decalNumber ].index; + if ( index < 0 ) + return; + } + else + index = decalNumber; + + if (pTrace->flFraction == 1.0) + return; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_PLAYERDECAL ); + WRITE_BYTE ( playernum ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_SHORT( (short)ENTINDEX(pTrace->pHit) ); + WRITE_BYTE( index ); + MESSAGE_END(); +} + +void UTIL_GunshotDecalTrace( TraceResult *pTrace, int decalNumber ) +{ + if ( decalNumber < 0 ) + return; + + int index = gDecals[ decalNumber ].index; + if ( index < 0 ) + return; + + if (pTrace->flFraction == 1.0) + return; + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pTrace->vecEndPos ); + WRITE_BYTE( TE_GUNSHOTDECAL ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_SHORT( (short)ENTINDEX(pTrace->pHit) ); + WRITE_BYTE( index ); + MESSAGE_END(); +} + + +void UTIL_Sparks( const Vector &position ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, position ); + WRITE_BYTE( TE_SPARKS ); + WRITE_COORD( position.x ); + WRITE_COORD( position.y ); + WRITE_COORD( position.z ); + MESSAGE_END(); +} + + +void UTIL_Ricochet( const Vector &position, float scale ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, position ); + WRITE_BYTE( TE_ARMOR_RICOCHET ); + WRITE_COORD( position.x ); + WRITE_COORD( position.y ); + WRITE_COORD( position.z ); + WRITE_BYTE( (int)(scale*10) ); + MESSAGE_END(); +} + + +BOOL UTIL_TeamsMatch( const char *pTeamName1, const char *pTeamName2 ) +{ + // Everyone matches unless it's teamplay + if ( !g_pGameRules->IsTeamplay() ) + return TRUE; + + // Both on a team? + if ( *pTeamName1 != 0 && *pTeamName2 != 0 ) + { + if ( !stricmp( pTeamName1, pTeamName2 ) ) // Same Team? + return TRUE; + } + + return FALSE; +} + + +void UTIL_StringToVector( float *pVector, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + + strcpy( tempString, pString ); + pstr = pfront = tempString; + + for ( j = 0; j < 3; j++ ) // lifted from pr_edict.c + { + pVector[j] = atof( pfront ); + + while ( *pstr && *pstr != ' ' ) + pstr++; + if (!*pstr) + break; + pstr++; + pfront = pstr; + } + if (j < 2) + { + /* + ALERT( at_error, "Bad field in entity!! %s:%s == \"%s\"\n", + pkvd->szClassName, pkvd->szKeyName, pkvd->szValue ); + */ + for (j = j+1;j < 3; j++) + pVector[j] = 0; + } +} + + +void UTIL_StringToIntArray( int *pVector, int count, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + + strcpy( tempString, pString ); + pstr = pfront = tempString; + + for ( j = 0; j < count; j++ ) // lifted from pr_edict.c + { + pVector[j] = atoi( pfront ); + + while ( *pstr && *pstr != ' ' ) + pstr++; + if (!*pstr) + break; + pstr++; + pfront = pstr; + } + + for ( j++; j < count; j++ ) + { + pVector[j] = 0; + } +} + +Vector UTIL_ClampVectorToBox( const Vector &input, const Vector &clampSize ) +{ + Vector sourceVector = input; + + if ( sourceVector.x > clampSize.x ) + sourceVector.x -= clampSize.x; + else if ( sourceVector.x < -clampSize.x ) + sourceVector.x += clampSize.x; + else + sourceVector.x = 0; + + if ( sourceVector.y > clampSize.y ) + sourceVector.y -= clampSize.y; + else if ( sourceVector.y < -clampSize.y ) + sourceVector.y += clampSize.y; + else + sourceVector.y = 0; + + if ( sourceVector.z > clampSize.z ) + sourceVector.z -= clampSize.z; + else if ( sourceVector.z < -clampSize.z ) + sourceVector.z += clampSize.z; + else + sourceVector.z = 0; + + return sourceVector.Normalize(); +} + + +float UTIL_WaterLevel( const Vector &position, float minz, float maxz ) +{ + Vector midUp = position; + midUp.z = minz; + + if (UTIL_PointContents(midUp) != CONTENTS_WATER) + return minz; + + midUp.z = maxz; + if (UTIL_PointContents(midUp) == CONTENTS_WATER) + return maxz; + + float diff = maxz - minz; + while (diff > 1.0) + { + midUp.z = minz + diff/2.0; + if (UTIL_PointContents(midUp) == CONTENTS_WATER) + { + minz = midUp.z; + } + else + { + maxz = midUp.z; + } + diff = maxz - minz; + } + + return midUp.z; +} + + +extern DLL_GLOBAL short g_sModelIndexBubbles;// holds the index for the bubbles model + +void UTIL_Bubbles( Vector mins, Vector maxs, int count ) +{ + Vector mid = (mins + maxs) * 0.5; + + float flHeight = UTIL_WaterLevel( mid, mid.z, mid.z + 1024 ); + flHeight = flHeight - mins.z; + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, mid ); + WRITE_BYTE( TE_BUBBLES ); + WRITE_COORD( mins.x ); // mins + WRITE_COORD( mins.y ); + WRITE_COORD( mins.z ); + WRITE_COORD( maxs.x ); // maxz + WRITE_COORD( maxs.y ); + WRITE_COORD( maxs.z ); + WRITE_COORD( flHeight ); // height + WRITE_SHORT( g_sModelIndexBubbles ); + WRITE_BYTE( count ); // count + WRITE_COORD( 8 ); // speed + MESSAGE_END(); +} + +void UTIL_BubbleTrail( Vector from, Vector to, int count ) +{ + float flHeight = UTIL_WaterLevel( from, from.z, from.z + 256 ); + flHeight = flHeight - from.z; + + // Return if the start point isn't in water + if (flHeight < 8) + return; + + flHeight = UTIL_WaterLevel( to, to.z, to.z + 256 ); + flHeight = flHeight - to.z; + if (flHeight < 8) + return; + + // UNDONE: do a ploink sound + flHeight = flHeight + to.z - from.z; + + if (count > 255) + count = 255; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BUBBLETRAIL ); + WRITE_COORD( from.x ); // mins + WRITE_COORD( from.y ); + WRITE_COORD( from.z ); + WRITE_COORD( to.x ); // maxz + WRITE_COORD( to.y ); + WRITE_COORD( to.z ); + WRITE_COORD( flHeight ); // height + WRITE_SHORT( g_sModelIndexBubbles ); + WRITE_BYTE( count ); // count + WRITE_COORD( 8 ); // speed + MESSAGE_END(); +} + + +void UTIL_Remove( CBaseEntity *pEntity ) +{ + if ( !pEntity ) + return; + + pEntity->UpdateOnRemove(); + pEntity->pev->flags |= FL_KILLME; + pEntity->pev->targetname = 0; +} + + +BOOL UTIL_IsValidEntity( edict_t *pent ) +{ + if ( !pent || pent->free || (pent->v.flags & FL_KILLME) ) + return FALSE; + return TRUE; +} + + +void UTIL_PrecacheOther( const char *szClassname ) +{ + edict_t *pent; + + pent = CREATE_NAMED_ENTITY( MAKE_STRING( szClassname ) ); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in UTIL_PrecacheOther\n" ); + return; + } + + CBaseEntity *pEntity = CBaseEntity::Instance (VARS( pent )); + if (pEntity) + pEntity->Precache( ); + REMOVE_ENTITY(pent); +} + +//========================================================= +// UTIL_LogPrintf - Prints a logged message to console. +// Preceded by LOG: ( timestamp ) < message > +//========================================================= +void UTIL_LogPrintf( char *fmt, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start ( argptr, fmt ); + vsprintf ( string, fmt, argptr ); + va_end ( argptr ); + + // Print to server console + ALERT( at_logged, "%s", string ); +} + +//========================================================= +// UTIL_DotPoints - returns the dot product of a line from +// src to check and vecdir. +//========================================================= +float UTIL_DotPoints ( const Vector &vecSrc, const Vector &vecCheck, const Vector &vecDir ) +{ + Vector2D vec2LOS; + + vec2LOS = ( vecCheck - vecSrc ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + return DotProduct (vec2LOS , ( vecDir.Make2D() ) ); +} + + +//========================================================= +// UTIL_StripToken - for redundant keynames +//========================================================= +void UTIL_StripToken( const char *pKey, char *pDest ) +{ + int i = 0; + + while ( pKey[i] && pKey[i] != '#' ) + { + pDest[i] = pKey[i]; + i++; + } + pDest[i] = 0; +} + + +// -------------------------------------------------------------- +// +// CSave +// +// -------------------------------------------------------------- +static int gSizes[FIELD_TYPECOUNT] = +{ + sizeof(float), // FIELD_FLOAT + sizeof(int), // FIELD_STRING + sizeof(int), // FIELD_ENTITY + sizeof(int), // FIELD_CLASSPTR + sizeof(int), // FIELD_EHANDLE + sizeof(int), // FIELD_entvars_t + sizeof(int), // FIELD_EDICT + sizeof(float)*3, // FIELD_VECTOR + sizeof(float)*3, // FIELD_POSITION_VECTOR + sizeof(int *), // FIELD_POINTER + sizeof(int), // FIELD_INTEGER + sizeof(int *), // FIELD_FUNCTION + sizeof(int), // FIELD_BOOLEAN + sizeof(short), // FIELD_SHORT + sizeof(char), // FIELD_CHARACTER + sizeof(float), // FIELD_TIME + sizeof(int), // FIELD_MODELNAME + sizeof(int), // FIELD_SOUNDNAME +}; + + +// Base class includes common SAVERESTOREDATA pointer, and manages the entity table +CSaveRestoreBuffer :: CSaveRestoreBuffer( void ) +{ + m_pdata = NULL; +} + + +CSaveRestoreBuffer :: CSaveRestoreBuffer( SAVERESTOREDATA *pdata ) +{ + m_pdata = pdata; +} + + +CSaveRestoreBuffer :: ~CSaveRestoreBuffer( void ) +{ +} + +int CSaveRestoreBuffer :: EntityIndex( CBaseEntity *pEntity ) +{ + if ( pEntity == NULL ) + return -1; + return EntityIndex( pEntity->pev ); +} + + +int CSaveRestoreBuffer :: EntityIndex( entvars_t *pevLookup ) +{ + if ( pevLookup == NULL ) + return -1; + return EntityIndex( ENT( pevLookup ) ); +} + +int CSaveRestoreBuffer :: EntityIndex( EOFFSET eoLookup ) +{ + return EntityIndex( ENT( eoLookup ) ); +} + + +int CSaveRestoreBuffer :: EntityIndex( edict_t *pentLookup ) +{ + if ( !m_pdata || pentLookup == NULL ) + return -1; + + int i; + ENTITYTABLE *pTable; + + for ( i = 0; i < m_pdata->tableCount; i++ ) + { + pTable = m_pdata->pTable + i; + if ( pTable->pent == pentLookup ) + return i; + } + return -1; +} + + +edict_t *CSaveRestoreBuffer :: EntityFromIndex( int entityIndex ) +{ + if ( !m_pdata || entityIndex < 0 ) + return NULL; + + int i; + ENTITYTABLE *pTable; + + for ( i = 0; i < m_pdata->tableCount; i++ ) + { + pTable = m_pdata->pTable + i; + if ( pTable->id == entityIndex ) + return pTable->pent; + } + return NULL; +} + + +int CSaveRestoreBuffer :: EntityFlagsSet( int entityIndex, int flags ) +{ + if ( !m_pdata || entityIndex < 0 ) + return 0; + if ( entityIndex > m_pdata->tableCount ) + return 0; + + m_pdata->pTable[ entityIndex ].flags |= flags; + + return m_pdata->pTable[ entityIndex ].flags; +} + + +void CSaveRestoreBuffer :: BufferRewind( int size ) +{ + if ( !m_pdata ) + return; + + if ( m_pdata->size < size ) + size = m_pdata->size; + + m_pdata->pCurrentData -= size; + m_pdata->size -= size; +} + +#ifndef _WIN32 +extern "C" { +unsigned _rotr ( unsigned val, int shift) +{ + register unsigned lobit; /* non-zero means lo bit set */ + register unsigned num = val; /* number to rotate */ + + shift &= 0x1f; /* modulo 32 -- this will also make + negative shifts work */ + + while (shift--) { + lobit = num & 1; /* get high bit */ + num >>= 1; /* shift right one bit */ + if (lobit) + num |= 0x80000000; /* set hi bit if lo bit was set */ + } + + return num; +} +} +#endif + +unsigned int CSaveRestoreBuffer :: HashString( const char *pszToken ) +{ + unsigned int hash = 0; + + while ( *pszToken ) + hash = _rotr( hash, 4 ) ^ *pszToken++; + + return hash; +} + +unsigned short CSaveRestoreBuffer :: TokenHash( const char *pszToken ) +{ + unsigned short hash = (unsigned short)(HashString( pszToken ) % (unsigned)m_pdata->tokenCount ); + +#if _DEBUG + static int tokensparsed = 0; + tokensparsed++; + if ( !m_pdata->tokenCount || !m_pdata->pTokens ) + ALERT( at_error, "No token table array in TokenHash()!" ); +#endif + + for ( int i=0; itokenCount; i++ ) + { +#if _DEBUG + static qboolean beentheredonethat = FALSE; + if ( i > 50 && !beentheredonethat ) + { + beentheredonethat = TRUE; + ALERT( at_error, "CSaveRestoreBuffer :: TokenHash() is getting too full!" ); + } +#endif + + int index = hash + i; + if ( index >= m_pdata->tokenCount ) + index -= m_pdata->tokenCount; + + if ( !m_pdata->pTokens[index] || strcmp( pszToken, m_pdata->pTokens[index] ) == 0 ) + { + m_pdata->pTokens[index] = (char *)pszToken; + return index; + } + } + + // Token hash table full!!! + // [Consider doing overflow table(s) after the main table & limiting linear hash table search] + ALERT( at_error, "CSaveRestoreBuffer :: TokenHash() is COMPLETELY FULL!" ); + return 0; +} + +void CSave :: WriteData( const char *pname, int size, const char *pdata ) +{ + BufferField( pname, size, pdata ); +} + + +void CSave :: WriteShort( const char *pname, const short *data, int count ) +{ + BufferField( pname, sizeof(short) * count, (const char *)data ); +} + + +void CSave :: WriteInt( const char *pname, const int *data, int count ) +{ + BufferField( pname, sizeof(int) * count, (const char *)data ); +} + + +void CSave :: WriteFloat( const char *pname, const float *data, int count ) +{ + BufferField( pname, sizeof(float) * count, (const char *)data ); +} + + +void CSave :: WriteTime( const char *pname, const float *data, int count ) +{ + int i; + Vector tmp, input; + + BufferHeader( pname, sizeof(float) * count ); + for ( i = 0; i < count; i++ ) + { + float tmp = data[0]; + + // Always encode time as a delta from the current time so it can be re-based if loaded in a new level + // Times of 0 are never written to the file, so they will be restored as 0, not a relative time + if ( m_pdata ) + tmp -= m_pdata->time; + + BufferData( (const char *)&tmp, sizeof(float) ); + data ++; + } +} + + +void CSave :: WriteString( const char *pname, const char *pdata ) +{ +#ifdef TOKENIZE + short token = (short)TokenHash( pdata ); + WriteShort( pname, &token, 1 ); +#else + BufferField( pname, strlen(pdata) + 1, pdata ); +#endif +} + + +void CSave :: WriteString( const char *pname, const int *stringId, int count ) +{ + int i, size; + +#ifdef TOKENIZE + short token = (short)TokenHash( STRING( *stringId ) ); + WriteShort( pname, &token, 1 ); +#else +#if 0 + if ( count != 1 ) + ALERT( at_error, "No string arrays!\n" ); + WriteString( pname, (char *)STRING(*stringId) ); +#endif + + size = 0; + for ( i = 0; i < count; i++ ) + size += strlen( STRING( stringId[i] ) ) + 1; + + BufferHeader( pname, size ); + for ( i = 0; i < count; i++ ) + { + const char *pString = STRING(stringId[i]); + BufferData( pString, strlen(pString)+1 ); + } +#endif +} + + +void CSave :: WriteVector( const char *pname, const Vector &value ) +{ + WriteVector( pname, &value.x, 1 ); +} + + +void CSave :: WriteVector( const char *pname, const float *value, int count ) +{ + BufferHeader( pname, sizeof(float) * 3 * count ); + BufferData( (const char *)value, sizeof(float) * 3 * count ); +} + + + +void CSave :: WritePositionVector( const char *pname, const Vector &value ) +{ + + if ( m_pdata && m_pdata->fUseLandmark ) + { + Vector tmp = value - m_pdata->vecLandmarkOffset; + WriteVector( pname, tmp ); + } + + WriteVector( pname, value ); +} + + +void CSave :: WritePositionVector( const char *pname, const float *value, int count ) +{ + int i; + Vector tmp, input; + + BufferHeader( pname, sizeof(float) * 3 * count ); + for ( i = 0; i < count; i++ ) + { + Vector tmp( value[0], value[1], value[2] ); + + if ( m_pdata && m_pdata->fUseLandmark ) + tmp = tmp - m_pdata->vecLandmarkOffset; + + BufferData( (const char *)&tmp.x, sizeof(float) * 3 ); + value += 3; + } +} + + +void CSave :: WriteFunction( const char *pname, void **data, int count ) +{ + const char *functionName; + + functionName = NAME_FOR_FUNCTION( (uint32)*data ); + if ( functionName ) + BufferField( pname, strlen(functionName) + 1, functionName ); + else + ALERT( at_error, "Invalid function pointer in entity!" ); +} + + +void EntvarsKeyvalue( entvars_t *pev, KeyValueData *pkvd ) +{ + int i; + TYPEDESCRIPTION *pField; + + for ( i = 0; i < ENTVARS_COUNT; i++ ) + { + pField = &gEntvarsDescription[i]; + + if ( !stricmp( pField->fieldName, pkvd->szKeyName ) ) + { + switch( pField->fieldType ) + { + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + case FIELD_STRING: + (*(int *)((char *)pev + pField->fieldOffset)) = ALLOC_STRING( pkvd->szValue ); + break; + + case FIELD_TIME: + case FIELD_FLOAT: + (*(float *)((char *)pev + pField->fieldOffset)) = atof( pkvd->szValue ); + break; + + case FIELD_INTEGER: + (*(int *)((char *)pev + pField->fieldOffset)) = atoi( pkvd->szValue ); + break; + + case FIELD_POSITION_VECTOR: + case FIELD_VECTOR: + UTIL_StringToVector( (float *)((char *)pev + pField->fieldOffset), pkvd->szValue ); + break; + + default: + case FIELD_EVARS: + case FIELD_CLASSPTR: + case FIELD_EDICT: + case FIELD_ENTITY: + case FIELD_POINTER: + ALERT( at_error, "Bad field in entity!!\n" ); + break; + } + pkvd->fHandled = TRUE; + return; + } + } +} + + + +int CSave :: WriteEntVars( const char *pname, entvars_t *pev ) +{ + return WriteFields( pname, pev, gEntvarsDescription, ENTVARS_COUNT ); +} + + + +int CSave :: WriteFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + int i, j, actualCount, emptyCount; + TYPEDESCRIPTION *pTest; + int entityArray[MAX_ENTITYARRAY]; + + // Precalculate the number of empty fields + emptyCount = 0; + for ( i = 0; i < fieldCount; i++ ) + { + void *pOutputData; + pOutputData = ((char *)pBaseData + pFields[i].fieldOffset ); + if ( DataEmpty( (const char *)pOutputData, pFields[i].fieldSize * gSizes[pFields[i].fieldType] ) ) + emptyCount++; + } + + // Empty fields will not be written, write out the actual number of fields to be written + actualCount = fieldCount - emptyCount; + WriteInt( pname, &actualCount, 1 ); + + for ( i = 0; i < fieldCount; i++ ) + { + void *pOutputData; + pTest = &pFields[ i ]; + pOutputData = ((char *)pBaseData + pTest->fieldOffset ); + + // UNDONE: Must we do this twice? + if ( DataEmpty( (const char *)pOutputData, pTest->fieldSize * gSizes[pTest->fieldType] ) ) + continue; + + switch( pTest->fieldType ) + { + case FIELD_FLOAT: + WriteFloat( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + case FIELD_TIME: + WriteTime( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + case FIELD_STRING: + WriteString( pTest->fieldName, (int *)pOutputData, pTest->fieldSize ); + break; + case FIELD_CLASSPTR: + case FIELD_EVARS: + case FIELD_EDICT: + case FIELD_ENTITY: + case FIELD_EHANDLE: + if ( pTest->fieldSize > MAX_ENTITYARRAY ) + ALERT( at_error, "Can't save more than %d entities in an array!!!\n", MAX_ENTITYARRAY ); + for ( j = 0; j < pTest->fieldSize; j++ ) + { + switch( pTest->fieldType ) + { + case FIELD_EVARS: + entityArray[j] = EntityIndex( ((entvars_t **)pOutputData)[j] ); + break; + case FIELD_CLASSPTR: + entityArray[j] = EntityIndex( ((CBaseEntity **)pOutputData)[j] ); + break; + case FIELD_EDICT: + entityArray[j] = EntityIndex( ((edict_t **)pOutputData)[j] ); + break; + case FIELD_ENTITY: + entityArray[j] = EntityIndex( ((EOFFSET *)pOutputData)[j] ); + break; + case FIELD_EHANDLE: + entityArray[j] = EntityIndex( (CBaseEntity *)(((EHANDLE *)pOutputData)[j]) ); + break; + } + } + WriteInt( pTest->fieldName, entityArray, pTest->fieldSize ); + break; + case FIELD_POSITION_VECTOR: + WritePositionVector( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + case FIELD_VECTOR: + WriteVector( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + + case FIELD_BOOLEAN: + case FIELD_INTEGER: + WriteInt( pTest->fieldName, (int *)pOutputData, pTest->fieldSize ); + break; + + case FIELD_SHORT: + WriteData( pTest->fieldName, 2 * pTest->fieldSize, ((char *)pOutputData) ); + break; + + case FIELD_CHARACTER: + WriteData( pTest->fieldName, pTest->fieldSize, ((char *)pOutputData) ); + break; + + // For now, just write the address out, we're not going to change memory while doing this yet! + case FIELD_POINTER: + WriteInt( pTest->fieldName, (int *)(char *)pOutputData, pTest->fieldSize ); + break; + + case FIELD_FUNCTION: + WriteFunction( pTest->fieldName, (void **)pOutputData, pTest->fieldSize ); + break; + default: + ALERT( at_error, "Bad field type\n" ); + } + } + + return 1; +} + + +void CSave :: BufferString( char *pdata, int len ) +{ + char c = 0; + + BufferData( pdata, len ); // Write the string + BufferData( &c, 1 ); // Write a null terminator +} + + +int CSave :: DataEmpty( const char *pdata, int size ) +{ + for ( int i = 0; i < size; i++ ) + { + if ( pdata[i] ) + return 0; + } + return 1; +} + + +void CSave :: BufferField( const char *pname, int size, const char *pdata ) +{ + BufferHeader( pname, size ); + BufferData( pdata, size ); +} + + +void CSave :: BufferHeader( const char *pname, int size ) +{ + short hashvalue = TokenHash( pname ); + if ( size > 1<<(sizeof(short)*8) ) + ALERT( at_error, "CSave :: BufferHeader() size parameter exceeds 'short'!" ); + BufferData( (const char *)&size, sizeof(short) ); + BufferData( (const char *)&hashvalue, sizeof(short) ); +} + + +void CSave :: BufferData( const char *pdata, int size ) +{ + if ( !m_pdata ) + return; + + if ( m_pdata->size + size > m_pdata->bufferSize ) + { + ALERT( at_error, "Save/Restore overflow!" ); + m_pdata->size = m_pdata->bufferSize; + return; + } + + memcpy( m_pdata->pCurrentData, pdata, size ); + m_pdata->pCurrentData += size; + m_pdata->size += size; +} + + + +// -------------------------------------------------------------- +// +// CRestore +// +// -------------------------------------------------------------- + +int CRestore::ReadField( void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount, int startField, int size, char *pName, void *pData ) +{ + int i, j, stringCount, fieldNumber, entityIndex; + TYPEDESCRIPTION *pTest; + float time, timeData; + Vector position; + edict_t *pent; + char *pString; + + time = 0; + position = Vector(0,0,0); + + if ( m_pdata ) + { + time = m_pdata->time; + if ( m_pdata->fUseLandmark ) + position = m_pdata->vecLandmarkOffset; + } + + for ( i = 0; i < fieldCount; i++ ) + { + fieldNumber = (i+startField)%fieldCount; + pTest = &pFields[ fieldNumber ]; + if ( !stricmp( pTest->fieldName, pName ) ) + { + if ( !m_global || !(pTest->flags & FTYPEDESC_GLOBAL) ) + { + for ( j = 0; j < pTest->fieldSize; j++ ) + { + void *pOutputData = ((char *)pBaseData + pTest->fieldOffset + (j*gSizes[pTest->fieldType]) ); + void *pInputData = (char *)pData + j * gSizes[pTest->fieldType]; + + switch( pTest->fieldType ) + { + case FIELD_TIME: + timeData = *(float *)pInputData; + // Re-base time variables + timeData += time; + *((float *)pOutputData) = timeData; + break; + case FIELD_FLOAT: + *((float *)pOutputData) = *(float *)pInputData; + break; + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + case FIELD_STRING: + // Skip over j strings + pString = (char *)pData; + for ( stringCount = 0; stringCount < j; stringCount++ ) + { + while (*pString) + pString++; + pString++; + } + pInputData = pString; + if ( strlen( (char *)pInputData ) == 0 ) + *((int *)pOutputData) = 0; + else + { + int string; + + string = ALLOC_STRING( (char *)pInputData ); + + *((int *)pOutputData) = string; + + if ( !FStringNull( string ) && m_precache ) + { + if ( pTest->fieldType == FIELD_MODELNAME ) + PRECACHE_MODEL( (char *)STRING( string ) ); + else if ( pTest->fieldType == FIELD_SOUNDNAME ) + PRECACHE_SOUND( (char *)STRING( string ) ); + } + } + break; + case FIELD_EVARS: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((entvars_t **)pOutputData) = VARS(pent); + else + *((entvars_t **)pOutputData) = NULL; + break; + case FIELD_CLASSPTR: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((CBaseEntity **)pOutputData) = CBaseEntity::Instance(pent); + else + *((CBaseEntity **)pOutputData) = NULL; + break; + case FIELD_EDICT: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + *((edict_t **)pOutputData) = pent; + break; + case FIELD_EHANDLE: + // Input and Output sizes are different! + pOutputData = (char *)pOutputData + j*(sizeof(EHANDLE) - gSizes[pTest->fieldType]); + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((EHANDLE *)pOutputData) = CBaseEntity::Instance(pent); + else + *((EHANDLE *)pOutputData) = NULL; + break; + case FIELD_ENTITY: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((EOFFSET *)pOutputData) = OFFSET(pent); + else + *((EOFFSET *)pOutputData) = 0; + break; + case FIELD_VECTOR: + ((float *)pOutputData)[0] = ((float *)pInputData)[0]; + ((float *)pOutputData)[1] = ((float *)pInputData)[1]; + ((float *)pOutputData)[2] = ((float *)pInputData)[2]; + break; + case FIELD_POSITION_VECTOR: + ((float *)pOutputData)[0] = ((float *)pInputData)[0] + position.x; + ((float *)pOutputData)[1] = ((float *)pInputData)[1] + position.y; + ((float *)pOutputData)[2] = ((float *)pInputData)[2] + position.z; + break; + + case FIELD_BOOLEAN: + case FIELD_INTEGER: + *((int *)pOutputData) = *( int *)pInputData; + break; + + case FIELD_SHORT: + *((short *)pOutputData) = *( short *)pInputData; + break; + + case FIELD_CHARACTER: + *((char *)pOutputData) = *( char *)pInputData; + break; + + case FIELD_POINTER: + *((int *)pOutputData) = *( int *)pInputData; + break; + case FIELD_FUNCTION: + if ( strlen( (char *)pInputData ) == 0 ) + *((int *)pOutputData) = 0; + else + *((int *)pOutputData) = FUNCTION_FROM_NAME( (char *)pInputData ); + break; + + default: + ALERT( at_error, "Bad field type\n" ); + } + } + } +#if 0 + else + { + ALERT( at_console, "Skipping global field %s\n", pName ); + } +#endif + return fieldNumber; + } + } + + return -1; +} + + +int CRestore::ReadEntVars( const char *pname, entvars_t *pev ) +{ + return ReadFields( pname, pev, gEntvarsDescription, ENTVARS_COUNT ); +} + + +int CRestore::ReadFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + unsigned short i, token; + int lastField, fileCount; + HEADER header; + + i = ReadShort(); + ASSERT( i == sizeof(int) ); // First entry should be an int + + token = ReadShort(); + + // Check the struct name + if ( token != TokenHash(pname) ) // Field Set marker + { +// ALERT( at_error, "Expected %s found %s!\n", pname, BufferPointer() ); + BufferRewind( 2*sizeof(short) ); + return 0; + } + + // Skip over the struct name + fileCount = ReadInt(); // Read field count + + lastField = 0; // Make searches faster, most data is read/written in the same order + + // Clear out base data + for ( i = 0; i < fieldCount; i++ ) + { + // Don't clear global fields + if ( !m_global || !(pFields[i].flags & FTYPEDESC_GLOBAL) ) + memset( ((char *)pBaseData + pFields[i].fieldOffset), 0, pFields[i].fieldSize * gSizes[pFields[i].fieldType] ); + } + + for ( i = 0; i < fileCount; i++ ) + { + BufferReadHeader( &header ); + lastField = ReadField( pBaseData, pFields, fieldCount, lastField, header.size, m_pdata->pTokens[header.token], header.pData ); + lastField++; + } + + return 1; +} + + +void CRestore::BufferReadHeader( HEADER *pheader ) +{ + ASSERT( pheader!=NULL ); + pheader->size = ReadShort(); // Read field size + pheader->token = ReadShort(); // Read field name token + pheader->pData = BufferPointer(); // Field Data is next + BufferSkipBytes( pheader->size ); // Advance to next field +} + + +short CRestore::ReadShort( void ) +{ + short tmp = 0; + + BufferReadBytes( (char *)&tmp, sizeof(short) ); + + return tmp; +} + +int CRestore::ReadInt( void ) +{ + int tmp = 0; + + BufferReadBytes( (char *)&tmp, sizeof(int) ); + + return tmp; +} + +int CRestore::ReadNamedInt( const char *pName ) +{ + HEADER header; + + BufferReadHeader( &header ); + return ((int *)header.pData)[0]; +} + +char *CRestore::ReadNamedString( const char *pName ) +{ + HEADER header; + + BufferReadHeader( &header ); +#ifdef TOKENIZE + return (char *)(m_pdata->pTokens[*(short *)header.pData]); +#else + return (char *)header.pData; +#endif +} + + +char *CRestore::BufferPointer( void ) +{ + if ( !m_pdata ) + return NULL; + + return m_pdata->pCurrentData; +} + +void CRestore::BufferReadBytes( char *pOutput, int size ) +{ + ASSERT( m_pdata !=NULL ); + + if ( !m_pdata || Empty() ) + return; + + if ( (m_pdata->size + size) > m_pdata->bufferSize ) + { + ALERT( at_error, "Restore overflow!" ); + m_pdata->size = m_pdata->bufferSize; + return; + } + + if ( pOutput ) + memcpy( pOutput, m_pdata->pCurrentData, size ); + m_pdata->pCurrentData += size; + m_pdata->size += size; +} + + +void CRestore::BufferSkipBytes( int bytes ) +{ + BufferReadBytes( NULL, bytes ); +} + +int CRestore::BufferSkipZString( void ) +{ + char *pszSearch; + int len; + + if ( !m_pdata ) + return 0; + + int maxLen = m_pdata->bufferSize - m_pdata->size; + + len = 0; + pszSearch = m_pdata->pCurrentData; + while ( *pszSearch++ && len < maxLen ) + len++; + + len++; + + BufferSkipBytes( len ); + + return len; +} + +int CRestore::BufferCheckZString( const char *string ) +{ + if ( !m_pdata ) + return 0; + + int maxLen = m_pdata->bufferSize - m_pdata->size; + int len = strlen( string ); + if ( len <= maxLen ) + { + if ( !strncmp( string, m_pdata->pCurrentData, len ) ) + return 1; + } + return 0; +} diff --git a/dmc/dlls/util.h b/dmc/dlls/util.h new file mode 100644 index 0000000..b492386 --- /dev/null +++ b/dmc/dlls/util.h @@ -0,0 +1,601 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "archtypes.h" // DAL + +// +// Misc utility code +// +#ifndef ACTIVITY_H +#include "activity.h" +#endif + +#ifndef ENGINECALLBACK_H +#include "enginecallback.h" +#endif +inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin, entvars_t *ent ); // implementation later in this file + +extern globalvars_t *gpGlobals; + +// Use this instead of ALLOC_STRING on constant strings +#define STRING(offset) ((const char *)(gpGlobals->pStringBase + (unsigned int)(offset))) +#define MAKE_STRING(str) ((uint64)(str) - (uint64)STRING(0)) + + +inline edict_t *FIND_ENTITY_BY_CLASSNAME(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "classname", pszName); +} + +inline edict_t *FIND_ENTITY_BY_TARGETNAME(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "targetname", pszName); +} + +// for doing a reverse lookup. Say you have a door, and want to find its button. +inline edict_t *FIND_ENTITY_BY_TARGET(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "target", pszName); +} + +// Keeps clutter down a bit, when writing key-value pairs +#define WRITEKEY_INT(pf, szKeyName, iKeyValue) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%d\"\n", szKeyName, iKeyValue) +#define WRITEKEY_FLOAT(pf, szKeyName, flKeyValue) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%f\"\n", szKeyName, flKeyValue) +#define WRITEKEY_STRING(pf, szKeyName, szKeyValue) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%s\"\n", szKeyName, szKeyValue) +#define WRITEKEY_VECTOR(pf, szKeyName, flX, flY, flZ) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%f %f %f\"\n", szKeyName, flX, flY, flZ) + +// Keeps clutter down a bit, when using a float as a bit-vector +#define SetBits(flBitVector, bits) ((flBitVector) = (int)(flBitVector) | (bits)) +#define ClearBits(flBitVector, bits) ((flBitVector) = (int)(flBitVector) & ~(bits)) +#define FBitSet(flBitVector, bit) ((int)(flBitVector) & (bit)) + +// Makes these more explicit, and easier to find +#define FILE_GLOBAL static +#define DLL_GLOBAL + +// Until we figure out why "const" gives the compiler problems, we'll just have to use +// this bogus "empty" define to mark things as constant. +#define CONSTANT + +// More explicit than "int" +typedef int EOFFSET; + +// In case it's not alread defined +typedef int BOOL; + +// In case this ever changes +#define M_PI 3.14159265358979323846 + +#ifndef UTIL_DLLEXPORT +#ifdef _WIN32 +#define UTIL_DLLEXPORT _declspec( dllexport ) +#else +#define UTIL_DLLEXPORT __attribute__ ((visibility("default"))) +#endif +#endif + +// Keeps clutter down a bit, when declaring external entity/global method prototypes +#define DECLARE_GLOBAL_METHOD(MethodName) \ + extern void UTIL_DLLEXPORT MethodName( void ) +#define GLOBAL_METHOD(funcname) void UTIL_DLLEXPORT funcname(void) + +// This is the glue that hooks .MAP entity class names to our CPP classes +// The _declspec forces them to be exported by name so we can do a lookup with GetProcAddress() +// The function is used to intialize / allocate the object for the entity +#define LINK_ENTITY_TO_CLASS(mapClassName,DLLClassName) \ + extern "C" UTIL_DLLEXPORT void mapClassName( entvars_t *pev ); \ + void mapClassName( entvars_t *pev ) { GetClassPtr( (DLLClassName *)pev ); } + + +// +// Conversion among the three types of "entity", including identity-conversions. +// +#ifdef DEBUG + extern edict_t *DBG_EntOfVars(const entvars_t *pev); + inline edict_t *ENT(const entvars_t *pev) { return DBG_EntOfVars(pev); } +#else + inline edict_t *ENT(const entvars_t *pev) { return pev->pContainingEntity; } +#endif +inline edict_t *ENT(edict_t *pent) { return pent; } +inline edict_t *ENT(EOFFSET eoffset) { return (*g_engfuncs.pfnPEntityOfEntOffset)(eoffset); } +inline EOFFSET OFFSET(EOFFSET eoffset) { return eoffset; } +inline EOFFSET OFFSET(const edict_t *pent) +{ +#if _DEBUG + if ( !pent ) + ALERT( at_error, "Bad ent in OFFSET()\n" ); +#endif + return (*g_engfuncs.pfnEntOffsetOfPEntity)(pent); +} +inline EOFFSET OFFSET(entvars_t *pev) +{ +#if _DEBUG + if ( !pev ) + ALERT( at_error, "Bad pev in OFFSET()\n" ); +#endif + return OFFSET(ENT(pev)); +} +inline entvars_t *VARS(entvars_t *pev) { return pev; } + +inline entvars_t *VARS(edict_t *pent) +{ + if ( !pent ) + return NULL; + + return &pent->v; +} + +inline entvars_t* VARS(EOFFSET eoffset) { return VARS(ENT(eoffset)); } +inline int ENTINDEX(edict_t *pEdict) { return (*g_engfuncs.pfnIndexOfEdict)(pEdict); } +inline edict_t* INDEXENT( int iEdictNum ) { return (*g_engfuncs.pfnPEntityOfEntIndex)(iEdictNum); } +inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin, entvars_t *ent ) { + (*g_engfuncs.pfnMessageBegin)(msg_dest, msg_type, pOrigin, ENT(ent)); +} + +// Testing the three types of "entity" for nullity +#define eoNullEntity 0 +inline BOOL FNullEnt(EOFFSET eoffset) { return eoffset == 0; } +inline BOOL FNullEnt(const edict_t* pent) { return pent == NULL || FNullEnt(OFFSET(pent)); } +inline BOOL FNullEnt(entvars_t* pev) { return pev == NULL || FNullEnt(OFFSET(pev)); } + +// Testing strings for nullity +#define iStringNull 0 +inline BOOL FStringNull(int iString) { return iString == iStringNull; } + +#define cchMapNameMost 32 + +// Dot products for view cone checking +#define VIEW_FIELD_FULL (float)-1.0 // +-180 degrees +#define VIEW_FIELD_WIDE (float)-0.7 // +-135 degrees 0.1 // +-85 degrees, used for full FOV checks +#define VIEW_FIELD_NARROW (float)0.7 // +-45 degrees, more narrow check used to set up ranged attacks +#define VIEW_FIELD_ULTRA_NARROW (float)0.9 // +-25 degrees, more narrow check used to set up ranged attacks + +// All monsters need this data +#define DONT_BLEED -1 +#define BLOOD_COLOR_RED (BYTE)247 +#define BLOOD_COLOR_YELLOW (BYTE)195 +#define BLOOD_COLOR_GREEN BLOOD_COLOR_YELLOW + +typedef enum +{ + + MONSTERSTATE_NONE = 0, + MONSTERSTATE_IDLE, + MONSTERSTATE_COMBAT, + MONSTERSTATE_ALERT, + MONSTERSTATE_HUNT, + MONSTERSTATE_PRONE, + MONSTERSTATE_SCRIPT, + MONSTERSTATE_PLAYDEAD, + MONSTERSTATE_DEAD + +} MONSTERSTATE; + + + +// Things that toggle (buttons/triggers/doors) need this +typedef enum + { + TS_AT_TOP, + TS_AT_BOTTOM, + TS_GOING_UP, + TS_GOING_DOWN + } TOGGLE_STATE; + +// Misc useful +inline BOOL FStrEq(const char*sz1, const char*sz2) + { return (strcmp(sz1, sz2) == 0); } +inline BOOL FClassnameIs(edict_t* pent, const char* szClassname) + { return FStrEq(STRING(VARS(pent)->classname), szClassname); } +inline BOOL FClassnameIs(entvars_t* pev, const char* szClassname) + { return FStrEq(STRING(pev->classname), szClassname); } + +class CBaseEntity; + +// Misc. Prototypes +extern void UTIL_SetSize (entvars_t* pev, const Vector &vecMin, const Vector &vecMax); +extern float UTIL_VecToYaw (const Vector &vec); +extern Vector UTIL_VecToAngles (const Vector &vec); +extern float UTIL_AngleMod (float a); +extern float UTIL_AngleDiff ( float destAngle, float srcAngle ); + +extern CBaseEntity *UTIL_FindEntityInSphere(CBaseEntity *pStartEntity, const Vector &vecCenter, float flRadius); +extern CBaseEntity *UTIL_FindEntityByString(CBaseEntity *pStartEntity, const char *szKeyword, const char *szValue ); +extern CBaseEntity *UTIL_FindEntityByClassname(CBaseEntity *pStartEntity, const char *szName ); +extern CBaseEntity *UTIL_FindEntityByTargetname(CBaseEntity *pStartEntity, const char *szName ); +extern CBaseEntity *UTIL_FindEntityGeneric(const char *szName, Vector &vecSrc, float flRadius ); + +// returns a CBaseEntity pointer to a player by index. Only returns if the player is spawned and connected +// otherwise returns NULL +// Index is 1 based +extern CBaseEntity *UTIL_PlayerByIndex( int playerIndex ); + +#define UTIL_EntitiesInPVS(pent) (*g_engfuncs.pfnEntitiesInPVS)(pent) +extern void UTIL_MakeVectors (const Vector &vecAngles); + +// Pass in an array of pointers and an array size, it fills the array and returns the number inserted +extern int UTIL_MonstersInSphere( CBaseEntity **pList, int listMax, const Vector ¢er, float radius ); +extern int UTIL_EntitiesInBox( CBaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask ); + +inline void UTIL_MakeVectorsPrivate( const Vector &vecAngles, float *p_vForward, float *p_vRight, float *p_vUp ) +{ + g_engfuncs.pfnAngleVectors( vecAngles, p_vForward, p_vRight, p_vUp ); +} + +extern void UTIL_MakeAimVectors ( const Vector &vecAngles ); // like MakeVectors, but assumes pitch isn't inverted +extern void UTIL_MakeInvVectors ( const Vector &vec, globalvars_t *pgv ); + +extern void UTIL_SetOrigin ( entvars_t* pev, const Vector &vecOrigin ); +extern void UTIL_EmitAmbientSound ( edict_t *entity, const Vector &vecOrigin, const char *samp, float vol, float attenuation, int fFlags, int pitch ); +extern void UTIL_ParticleEffect ( const Vector &vecOrigin, const Vector &vecDirection, ULONG ulColor, ULONG ulCount ); +extern void UTIL_ScreenShake ( const Vector ¢er, float amplitude, float frequency, float duration, float radius ); +extern void UTIL_ScreenShakeAll ( const Vector ¢er, float amplitude, float frequency, float duration ); +extern void UTIL_ShowMessage ( const char *pString, CBaseEntity *pPlayer ); +extern void UTIL_ShowMessageAll ( const char *pString ); +extern void UTIL_ScreenFadeAll ( const Vector &color, float fadeTime, float holdTime, int alpha, int flags ); +extern void UTIL_ScreenFade ( CBaseEntity *pEntity, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ); + +typedef enum { ignore_monsters=1, dont_ignore_monsters=0, missile=2 } IGNORE_MONSTERS; +typedef enum { ignore_glass=1, dont_ignore_glass=0 } IGNORE_GLASS; +extern void UTIL_TraceLine (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, edict_t *pentIgnore, TraceResult *ptr); +extern void UTIL_TraceLine (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, IGNORE_GLASS ignoreGlass, edict_t *pentIgnore, TraceResult *ptr); +typedef enum { point_hull=0, human_hull=1, large_hull=2, head_hull=3 }; +extern void UTIL_TraceHull (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, int hullNumber, edict_t *pentIgnore, TraceResult *ptr); +extern TraceResult UTIL_GetGlobalTrace (void); +extern void UTIL_TraceModel (const Vector &vecStart, const Vector &vecEnd, int hullNumber, edict_t *pentModel, TraceResult *ptr); +extern Vector UTIL_GetAimVector (edict_t* pent, float flSpeed); +extern int UTIL_PointContents (const Vector &vec); + +extern int UTIL_IsMasterTriggered (string_t sMaster, CBaseEntity *pActivator); +extern void UTIL_BloodStream( const Vector &origin, const Vector &direction, int color, int amount ); +extern void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, int amount ); +extern Vector UTIL_RandomBloodVector( void ); +extern BOOL UTIL_ShouldShowBlood( int bloodColor ); +extern void UTIL_BloodDecalTrace( TraceResult *pTrace, int bloodColor ); +extern void UTIL_DecalTrace( TraceResult *pTrace, int decalNumber ); +extern void UTIL_PlayerDecalTrace( TraceResult *pTrace, int playernum, int decalNumber, BOOL bIsCustom ); +extern void UTIL_GunshotDecalTrace( TraceResult *pTrace, int decalNumber ); +extern void UTIL_Sparks( const Vector &position ); +extern void UTIL_Ricochet( const Vector &position, float scale ); +extern void UTIL_StringToVector( float *pVector, const char *pString ); +extern void UTIL_StringToIntArray( int *pVector, int count, const char *pString ); +extern Vector UTIL_ClampVectorToBox( const Vector &input, const Vector &clampSize ); +extern float UTIL_Approach( float target, float value, float speed ); +extern float UTIL_ApproachAngle( float target, float value, float speed ); +extern float UTIL_AngleDistance( float next, float cur ); + +extern char *UTIL_VarArgs( char *format, ... ); +extern void UTIL_Remove( CBaseEntity *pEntity ); +extern BOOL UTIL_IsValidEntity( edict_t *pent ); +extern BOOL UTIL_TeamsMatch( const char *pTeamName1, const char *pTeamName2 ); + +// create a TENT projectile on all clients +extern void UTIL_ClientProjectile( const Vector &vecOrigin, const Vector &vecVelocity, short sModelIndex, int iOwnerIndex, int iLife ); + +// Use for ease-in, ease-out style interpolation (accel/decel) +extern float UTIL_SplineFraction( float value, float scale ); + +// Search for water transition along a vertical line +extern float UTIL_WaterLevel( const Vector &position, float minz, float maxz ); +extern void UTIL_Bubbles( Vector mins, Vector maxs, int count ); +extern void UTIL_BubbleTrail( Vector from, Vector to, int count ); + +// allows precacheing of other entities +extern void UTIL_PrecacheOther( const char *szClassname ); + +// prints a message to each client +extern void UTIL_ClientPrintAll( int msg_dest, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ); +inline void UTIL_CenterPrintAll( const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ) +{ + UTIL_ClientPrintAll( HUD_PRINTCENTER, msg_name, param1, param2, param3, param4 ); +} + +class CBasePlayerItem; +class CBasePlayer; +extern BOOL UTIL_GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ); + +// prints messages through the HUD +extern void ClientPrint( entvars_t *client, int msg_dest, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ); + +// prints a message to the HUD say (chat) +extern void UTIL_SayText( const char *pText, CBaseEntity *pEntity ); +extern void UTIL_SayTextAll( const char *pText, CBaseEntity *pEntity ); + + +typedef struct hudtextparms_s +{ + float x; + float y; + int effect; + byte r1, g1, b1, a1; + byte r2, g2, b2, a2; + float fadeinTime; + float fadeoutTime; + float holdTime; + float fxTime; + int channel; +} hudtextparms_t; + +// prints as transparent 'title' to the HUD +extern void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage ); +extern void UTIL_HudMessage( CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage ); + +// for handy use with ClientPrint params +extern char *UTIL_dtos1( int d ); +extern char *UTIL_dtos2( int d ); +extern char *UTIL_dtos3( int d ); +extern char *UTIL_dtos4( int d ); + +// Writes message to console with timestamp and FragLog header. +extern void UTIL_LogPrintf( char *fmt, ... ); + +// Sorta like FInViewCone, but for nonmonsters. +extern float UTIL_DotPoints ( const Vector &vecSrc, const Vector &vecCheck, const Vector &vecDir ); + +extern void UTIL_StripToken( const char *pKey, char *pDest );// for redundant keynames + +// Misc functions +extern void SetMovedir(entvars_t* pev); +extern Vector VecBModelOrigin( entvars_t* pevBModel ); +extern int BuildChangeList( LEVELLIST *pLevelList, int maxList ); + +// +// How did I ever live without ASSERT? +// +#ifdef DEBUG +void DBG_AssertFunction(BOOL fExpr, const char* szExpr, const char* szFile, int szLine, const char* szMessage); +#define ASSERT(f) DBG_AssertFunction(f, #f, __FILE__, __LINE__, NULL) +#define ASSERTSZ(f, sz) DBG_AssertFunction(f, #f, __FILE__, __LINE__, sz) +#else // !DEBUG +#define ASSERT(f) +#define ASSERTSZ(f, sz) +#endif // !DEBUG + + +extern DLL_GLOBAL const Vector g_vecZero; + +// +// Constants that were used only by QC (maybe not used at all now) +// +// Un-comment only as needed +// +#define LANGUAGE_ENGLISH 0 +#define LANGUAGE_GERMAN 1 +#define LANGUAGE_FRENCH 2 +#define LANGUAGE_BRITISH 3 + +extern DLL_GLOBAL int g_Language; + +#define AMBIENT_SOUND_STATIC 0 // medium radius attenuation +#define AMBIENT_SOUND_EVERYWHERE 1 +#define AMBIENT_SOUND_SMALLRADIUS 2 +#define AMBIENT_SOUND_MEDIUMRADIUS 4 +#define AMBIENT_SOUND_LARGERADIUS 8 +#define AMBIENT_SOUND_START_SILENT 16 +#define AMBIENT_SOUND_NOT_LOOPING 32 + +#define SPEAKER_START_SILENT 1 // wait for trigger 'on' to start announcements + +#define SND_SPAWNING (1<<8) // duplicated in protocol.h we're spawing, used in some cases for ambients +#define SND_STOP (1<<5) // duplicated in protocol.h stop sound +#define SND_CHANGE_VOL (1<<6) // duplicated in protocol.h change sound vol +#define SND_CHANGE_PITCH (1<<7) // duplicated in protocol.h change sound pitch + +#define LFO_SQUARE 1 +#define LFO_TRIANGLE 2 +#define LFO_RANDOM 3 + +// func_rotating +#define SF_BRUSH_ROTATE_Y_AXIS 0 +#define SF_BRUSH_ROTATE_INSTANT 1 +#define SF_BRUSH_ROTATE_BACKWARDS 2 +#define SF_BRUSH_ROTATE_Z_AXIS 4 +#define SF_BRUSH_ROTATE_X_AXIS 8 +#define SF_PENDULUM_AUTO_RETURN 16 +#define SF_PENDULUM_PASSABLE 32 + + +#define SF_BRUSH_ROTATE_SMALLRADIUS 128 +#define SF_BRUSH_ROTATE_MEDIUMRADIUS 256 +#define SF_BRUSH_ROTATE_LARGERADIUS 512 + +#define PUSH_BLOCK_ONLY_X 1 +#define PUSH_BLOCK_ONLY_Y 2 + +// QUAKECLASSIC: Different player sizes +//#define VEC_HULL_MIN Vector(-16, -16, -36) +//#define VEC_HULL_MAX Vector( 16, 16, 36) +#define VEC_HULL_MIN Vector(-16, -16, -24) +#define VEC_HULL_MAX Vector(16, 16, 32) + +#define VEC_HUMAN_HULL_MIN Vector( -16, -16, 0 ) +#define VEC_HUMAN_HULL_MAX Vector( 16, 16, 72 ) +#define VEC_HUMAN_HULL_DUCK Vector( 16, 16, 36 ) + +#define VEC_VIEW Vector( 0, 0, 18 ) + +#define VEC_DUCK_HULL_MIN Vector(-16, -16, -18 ) +#define VEC_DUCK_HULL_MAX Vector( 16, 16, 18) +#define VEC_DUCK_VIEW Vector( 0, 0, 12 ) + +#define SVC_TEMPENTITY 23 +#define SVC_INTERMISSION 30 +#define SVC_CDTRACK 32 +#define SVC_WEAPONANIM 35 +#define SVC_ROOMTYPE 37 +#define SVC_ADDANGLE 38 // [vec3] add this angle to the view angle +#define SVC_NEWUSERMSG 39 +#define SVC_DIRECTOR 51 + + +// triggers +#define SF_TRIGGER_ALLOWMONSTERS 1// monsters allowed to fire this trigger +#define SF_TRIGGER_NOCLIENTS 2// players not allowed to fire this trigger +#define SF_TRIGGER_PUSHABLES 4// only pushables can fire this trigger + +// func breakable +#define SF_BREAK_TRIGGER_ONLY 1// may only be broken by trigger +#define SF_BREAK_TOUCH 2// can be 'crashed through' by running player (plate glass) +#define SF_BREAK_PRESSURE 4// can be broken by a player standing on it +#define SF_BREAK_CROWBAR 256// instant break if hit with crowbar + +// func_pushable (it's also func_breakable, so don't collide with those flags) +#define SF_PUSH_BREAKABLE 128 + +#define SF_LIGHT_START_OFF 1 + +#define SPAWNFLAG_NOMESSAGE 1 +#define SPAWNFLAG_NOTOUCH 1 +#define SPAWNFLAG_DROIDONLY 4 + +#define SPAWNFLAG_USEONLY 1 // can't be touched, must be used (buttons) + +#define TELE_PLAYER_ONLY 1 +#define TELE_SILENT 2 + +#define SF_TRIG_PUSH_ONCE 1 + + +// Sound Utilities + +// sentence groups +#define CBSENTENCENAME_MAX 16 +#define CVOXFILESENTENCEMAX 1536 // max number of sentences in game. NOTE: this must match + // CVOXFILESENTENCEMAX in engine\sound.h!!! + +extern char gszallsentencenames[CVOXFILESENTENCEMAX][CBSENTENCENAME_MAX]; +extern int gcallsentences; + +int USENTENCEG_Pick(int isentenceg, char *szfound); +int USENTENCEG_PickSequential(int isentenceg, char *szfound, int ipick, int freset); +void USENTENCEG_InitLRU(unsigned char *plru, int count); + +void SENTENCEG_Init(); +void SENTENCEG_Stop(edict_t *entity, int isentenceg, int ipick); +int SENTENCEG_PlayRndI(edict_t *entity, int isentenceg, float volume, float attenuation, int flags, int pitch); +int SENTENCEG_PlayRndSz(edict_t *entity, const char *szrootname, float volume, float attenuation, int flags, int pitch); +int SENTENCEG_PlaySequentialSz(edict_t *entity, const char *szrootname, float volume, float attenuation, int flags, int pitch, int ipick, int freset); +int SENTENCEG_GetIndex(const char *szrootname); +int SENTENCEG_Lookup(const char *sample, char *sentencenum); + +void TEXTURETYPE_Init(); +char TEXTURETYPE_Find(char *name); +float TEXTURETYPE_PlaySound(TraceResult *ptr, Vector vecSrc, Vector vecEnd, int iBulletType); + +#define CBTEXTURENAMEMAX 13 // only load first n chars of name + +#define CHAR_TEX_CONCRETE 'C' // texture types +#define CHAR_TEX_METAL 'M' +#define CHAR_TEX_DIRT 'D' +#define CHAR_TEX_VENT 'V' +#define CHAR_TEX_GRATE 'G' +#define CHAR_TEX_TILE 'T' +#define CHAR_TEX_SLOSH 'S' +#define CHAR_TEX_WOOD 'W' +#define CHAR_TEX_COMPUTER 'P' +#define CHAR_TEX_GLASS 'Y' +#define CHAR_TEX_FLESH 'F' + +// NOTE: use EMIT_SOUND_DYN to set the pitch of a sound. Pitch of 100 +// is no pitch shift. Pitch > 100 up to 255 is a higher pitch, pitch < 100 +// down to 1 is a lower pitch. 150 to 70 is the realistic range. +// EMIT_SOUND_DYN with pitch != 100 should be used sparingly, as it's not quite as +// fast as EMIT_SOUND (the pitchshift mixer is not native coded). + +void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volume, float attenuation, + int flags, int pitch); + + +inline void EMIT_SOUND(edict_t *entity, int channel, const char *sample, float volume, float attenuation) +{ + EMIT_SOUND_DYN(entity, channel, sample, volume, attenuation, 0, PITCH_NORM); +} + +inline void STOP_SOUND(edict_t *entity, int channel, const char *sample) +{ + EMIT_SOUND_DYN(entity, channel, sample, 0, 0, SND_STOP, PITCH_NORM); +} + +void EMIT_SOUND_SUIT(edict_t *entity, const char *sample); +void EMIT_GROUPID_SUIT(edict_t *entity, int isentenceg); +void EMIT_GROUPNAME_SUIT(edict_t *entity, const char *groupname); + +#define PRECACHE_SOUND_ARRAY( a ) \ + { for (int i = 0; i < ARRAYSIZE( a ); i++ ) PRECACHE_SOUND((char *) a [i]); } + +#define EMIT_SOUND_ARRAY_DYN( chan, array ) \ + EMIT_SOUND_DYN ( ENT(pev), chan , array [ RANDOM_LONG(0,ARRAYSIZE( array )-1) ], 1.0, ATTN_NORM, 0, RANDOM_LONG(95,105) ); + +#define RANDOM_SOUND_ARRAY( array ) (array) [ RANDOM_LONG(0,ARRAYSIZE( (array) )-1) ] + + +#define PLAYBACK_EVENT( flags, who, index ) PLAYBACK_EVENT_FULL( flags, who, index, 0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); +#define PLAYBACK_EVENT_DELAY( flags, who, index, delay ) PLAYBACK_EVENT_FULL( flags, who, index, delay, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); + +#define GROUP_OP_AND 0 +#define GROUP_OP_NAND 1 + +extern int g_groupmask; +extern int g_groupop; + +class UTIL_GroupTrace +{ +public: + UTIL_GroupTrace( int groupmask, int op ); + ~UTIL_GroupTrace( void ); + +private: + int m_oldgroupmask, m_oldgroupop; +}; + +void UTIL_SetGroupTrace( int groupmask, int op ); +void UTIL_UnsetGroupTrace( void ); + +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ); +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ); + +float UTIL_WeaponTimeBase( void ); + +typedef enum { + RED = 1, + BLUE, + GREEN, + YELLOW, + WHITE +} printcolor_t; + +typedef enum { + F_IN_OUT, + CREDITS, + SCANOUT +} printeffect_t; + +typedef enum { + + WIN_MSG, + CRITICAL, + INFO, + LEADER_HIT, + MISC_SHIT, + CHASECAM, + CHASECAM_TARGET, + NOTIFY, + +} effectchannel_t; + +extern void EffectPrint( CBasePlayer *pPlayer, int color, int effect, int channel, char *text ); diff --git a/dmc/dlls/vector.h b/dmc/dlls/vector.h new file mode 100644 index 0000000..9f475b0 --- /dev/null +++ b/dmc/dlls/vector.h @@ -0,0 +1,112 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef VECTOR_H +#define VECTOR_H + +//========================================================= +// 2DVector - used for many pathfinding and many other +// operations that are treated as planar rather than 3d. +//========================================================= +class Vector2D +{ +public: + inline Vector2D(void) { } + inline Vector2D(float X, float Y) { x = X; y = Y; } + inline Vector2D operator+(const Vector2D& v) const { return Vector2D(x+v.x, y+v.y); } + inline Vector2D operator-(const Vector2D& v) const { return Vector2D(x-v.x, y-v.y); } + inline Vector2D operator*(float fl) const { return Vector2D(x*fl, y*fl); } + inline Vector2D operator/(float fl) const { return Vector2D(x/fl, y/fl); } + + inline float Length(void) const { return sqrt(x*x + y*y ); } + + inline Vector2D Normalize ( void ) const + { + Vector2D vec2; + + float flLen = Length(); + if ( flLen == 0 ) + { + return Vector2D( 0, 0 ); + } + else + { + flLen = 1 / flLen; + return Vector2D( x * flLen, y * flLen ); + } + } + + vec_t x, y; +}; + +inline float DotProduct(const Vector2D& a, const Vector2D& b) { return( a.x*b.x + a.y*b.y ); } +inline Vector2D operator*(float fl, const Vector2D& v) { return v * fl; } + +//========================================================= +// 3D Vector +//========================================================= +class Vector // same data-layout as engine's vec3_t, +{ // which is a vec_t[3] +public: + // Construction/destruction + inline Vector(void) { } + inline Vector(float X, float Y, float Z) { x = X; y = Y; z = Z; } + //inline Vector(double X, double Y, double Z) { x = (float)X; y = (float)Y; z = (float)Z; } + //inline Vector(int X, int Y, int Z) { x = (float)X; y = (float)Y; z = (float)Z; } + inline Vector(const Vector& v) { x = v.x; y = v.y; z = v.z; } + inline Vector(float rgfl[3]) { x = rgfl[0]; y = rgfl[1]; z = rgfl[2]; } + + // Operators + inline Vector operator-(void) const { return Vector(-x,-y,-z); } + inline int operator==(const Vector& v) const { return x==v.x && y==v.y && z==v.z; } + inline int operator!=(const Vector& v) const { return !(*this==v); } + inline Vector operator+(const Vector& v) const { return Vector(x+v.x, y+v.y, z+v.z); } + inline Vector operator-(const Vector& v) const { return Vector(x-v.x, y-v.y, z-v.z); } + inline Vector operator*(float fl) const { return Vector(x*fl, y*fl, z*fl); } + inline Vector operator/(float fl) const { return Vector(x/fl, y/fl, z/fl); } + + // Methods + inline void CopyToArray(float* rgfl) const { rgfl[0] = x, rgfl[1] = y, rgfl[2] = z; } + inline float Length(void) const { return sqrt(x*x + y*y + z*z); } + operator float *() { return &x; } // Vectors will now automatically convert to float * when needed + operator const float *() const { return &x; } // Vectors will now automatically convert to float * when needed + inline Vector Normalize(void) const + { + float flLen = Length(); + if (flLen == 0) return Vector(0,0,1); // ???? + flLen = 1 / flLen; + return Vector(x * flLen, y * flLen, z * flLen); + } + + inline Vector2D Make2D ( void ) const + { + Vector2D Vec2; + + Vec2.x = x; + Vec2.y = y; + + return Vec2; + } + inline float Length2D(void) const { return sqrt(x*x + y*y); } + + // Members + vec_t x, y, z; +}; +inline Vector operator*(float fl, const Vector& v) { return v * fl; } +inline float DotProduct(const Vector& a, const Vector& b) { return(a.x*b.x+a.y*b.y+a.z*b.z); } +inline Vector CrossProduct(const Vector& a, const Vector& b) { return Vector( a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x ); } + + + +#endif diff --git a/dmc/dlls/weapons.cpp b/dmc/dlls/weapons.cpp new file mode 100644 index 0000000..413dfb8 --- /dev/null +++ b/dmc/dlls/weapons.cpp @@ -0,0 +1,1417 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== weapons.cpp ======================================================== + + functions governing the selection/use of weapons for players + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "decals.h" +#include "gamerules.h" +#include "quake_gun.h" + +extern CGraph WorldGraph; +extern int gEvilImpulse101; + + +#define NOT_USED 255 + +DLL_GLOBAL short g_sModelIndexLaser;// holds the index for the laser beam +DLL_GLOBAL const char *g_pModelNameLaser = "sprites/laserbeam.spr"; +DLL_GLOBAL short g_sModelIndexLaserDot;// holds the index for the laser beam dot +DLL_GLOBAL short g_sModelIndexFireball;// holds the index for the fireball +DLL_GLOBAL short g_sModelIndexSmoke;// holds the index for the smoke cloud +DLL_GLOBAL short g_sModelIndexWExplosion;// holds the index for the underwater explosion +DLL_GLOBAL short g_sModelIndexBubbles;// holds the index for the bubbles model +DLL_GLOBAL short g_sModelIndexBloodDrop;// holds the sprite index for the initial blood +DLL_GLOBAL short g_sModelIndexBloodSpray;// holds the sprite index for splattered blood + +ItemInfo CBasePlayerItem::ItemInfoArray[MAX_WEAPONS]; +AmmoInfo CBasePlayerItem::AmmoInfoArray[MAX_AMMO_SLOTS]; + +extern int gmsgCurWeapon; + +MULTIDAMAGE gMultiDamage; + +#define TRACER_FREQ 4 // Tracers fire every fourth bullet + + +//========================================================= +// MaxAmmoCarry - pass in a name and this function will tell +// you the maximum amount of that type of ammunition that a +// player can carry. +//========================================================= +int MaxAmmoCarry( int iszName ) +{ + for ( int i = 0; i < MAX_WEAPONS; i++ ) + { + if ( CBasePlayerItem::ItemInfoArray[i].pszAmmo1 && !strcmp( STRING(iszName), CBasePlayerItem::ItemInfoArray[i].pszAmmo1 ) ) + return CBasePlayerItem::ItemInfoArray[i].iMaxAmmo1; + if ( CBasePlayerItem::ItemInfoArray[i].pszAmmo2 && !strcmp( STRING(iszName), CBasePlayerItem::ItemInfoArray[i].pszAmmo2 ) ) + return CBasePlayerItem::ItemInfoArray[i].iMaxAmmo2; + } + + ALERT( at_console, "MaxAmmoCarry() doesn't recognize '%s'!\n", STRING( iszName ) ); + return -1; +} + + +/* +============================================================================== + +MULTI-DAMAGE + +Collects multiple small damages into a single damage + +============================================================================== +*/ + +// +// ClearMultiDamage - resets the global multi damage accumulator +// +void ClearMultiDamage(void) +{ + gMultiDamage.pEntity = NULL; + gMultiDamage.amount = 0; + gMultiDamage.type = 0; +} + + +// +// ApplyMultiDamage - inflicts contents of global multi damage register on gMultiDamage.pEntity +// +// GLOBALS USED: +// gMultiDamage + +void ApplyMultiDamage(entvars_t *pevInflictor, entvars_t *pevAttacker ) +{ + Vector vecSpot1;//where blood comes from + Vector vecDir;//direction blood should go + TraceResult tr; + + if ( !gMultiDamage.pEntity ) + return; + + gMultiDamage.pEntity->TakeDamage(pevInflictor, pevAttacker, gMultiDamage.amount, gMultiDamage.type ); +} + + +// GLOBALS USED: +// gMultiDamage + +void AddMultiDamage( entvars_t *pevInflictor, CBaseEntity *pEntity, float flDamage, int bitsDamageType) +{ + if ( !pEntity ) + return; + + gMultiDamage.type |= bitsDamageType; + + if ( pEntity != gMultiDamage.pEntity ) + { + ApplyMultiDamage(pevInflictor,pevInflictor); // UNDONE: wrong attacker! + gMultiDamage.pEntity = pEntity; + gMultiDamage.amount = 0; + } + + gMultiDamage.amount += flDamage; +} + +/* +================ +SpawnBlood +================ +*/ +void SpawnBlood(Vector vecSpot, int bloodColor, float flDamage) +{ + UTIL_BloodDrips( vecSpot, g_vecAttackDir, bloodColor, (int)flDamage ); +} + + +int DamageDecal( CBaseEntity *pEntity, int bitsDamageType ) +{ + if ( !pEntity ) + return (DECAL_GUNSHOT1 + RANDOM_LONG(0,4)); + + return pEntity->DamageDecal( bitsDamageType ); +} + +void DecalGunshot( TraceResult *pTrace, int iBulletType ) +{ + // Is the entity valid + if ( !UTIL_IsValidEntity( pTrace->pHit ) ) + return; + + if ( VARS(pTrace->pHit)->solid == SOLID_BSP || VARS(pTrace->pHit)->movetype == MOVETYPE_PUSHSTEP ) + { + CBaseEntity *pEntity = NULL; + // Decal the wall with a gunshot + if ( !FNullEnt(pTrace->pHit) ) + pEntity = CBaseEntity::Instance(pTrace->pHit); + + switch( iBulletType ) + { + case BULLET_PLAYER_9MM: + case BULLET_MONSTER_9MM: + case BULLET_PLAYER_MP5: + case BULLET_MONSTER_MP5: + case BULLET_PLAYER_BUCKSHOT: + case BULLET_PLAYER_357: + default: + // smoke and decal + UTIL_GunshotDecalTrace( pTrace, DamageDecal( pEntity, DMG_BULLET ) ); + break; + case BULLET_MONSTER_12MM: + // smoke and decal + UTIL_GunshotDecalTrace( pTrace, DamageDecal( pEntity, DMG_BULLET ) ); + break; + case BULLET_PLAYER_CROWBAR: + // wall decal + UTIL_DecalTrace( pTrace, DamageDecal( pEntity, DMG_CLUB ) ); + break; + } + } +} + + + +// +// EjectBrass - tosses a brass shell from passed origin at passed velocity +// +void EjectBrass ( const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int model, int soundtype ) +{ + // FIX: when the player shoots, their gun isn't in the same position as it is on the model other players see. + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecOrigin ); + WRITE_BYTE( TE_MODEL); + WRITE_COORD( vecOrigin.x); + WRITE_COORD( vecOrigin.y); + WRITE_COORD( vecOrigin.z); + WRITE_COORD( vecVelocity.x); + WRITE_COORD( vecVelocity.y); + WRITE_COORD( vecVelocity.z); + WRITE_ANGLE( rotation ); + WRITE_SHORT( model ); + WRITE_BYTE ( soundtype); + WRITE_BYTE ( 25 );// 2.5 seconds + MESSAGE_END(); +} + + +#if 0 +// UNDONE: This is no longer used? +void ExplodeModel( const Vector &vecOrigin, float speed, int model, int count ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecOrigin ); + WRITE_BYTE ( TE_EXPLODEMODEL ); + WRITE_COORD( vecOrigin.x ); + WRITE_COORD( vecOrigin.y ); + WRITE_COORD( vecOrigin.z ); + WRITE_COORD( speed ); + WRITE_SHORT( model ); + WRITE_SHORT( count ); + WRITE_BYTE ( 15 );// 1.5 seconds + MESSAGE_END(); +} +#endif + + +int giAmmoIndex = 0; + +// Precaches the ammo and queues the ammo info for sending to clients +void AddAmmoNameToAmmoRegistry( const char *szAmmoname ) +{ + // make sure it's not already in the registry + for ( int i = 0; i < MAX_AMMO_SLOTS; i++ ) + { + if ( !CBasePlayerItem::AmmoInfoArray[i].pszName) + continue; + + if ( stricmp( CBasePlayerItem::AmmoInfoArray[i].pszName, szAmmoname ) == 0 ) + return; // ammo already in registry, just quite + } + + + giAmmoIndex++; + ASSERT( giAmmoIndex < MAX_AMMO_SLOTS ); + if ( giAmmoIndex >= MAX_AMMO_SLOTS ) + giAmmoIndex = 0; + + CBasePlayerItem::AmmoInfoArray[giAmmoIndex].pszName = szAmmoname; + CBasePlayerItem::AmmoInfoArray[giAmmoIndex].iId = giAmmoIndex; // yes, this info is redundant +} + + +// Precaches the weapon and queues the weapon info for sending to clients +void UTIL_PrecacheOtherWeapon( const char *szClassname ) +{ + edict_t *pent; + + pent = CREATE_NAMED_ENTITY( MAKE_STRING( szClassname ) ); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in UTIL_PrecacheOtherWeapon\n" ); + return; + } + + CBaseEntity *pEntity = CBaseEntity::Instance (VARS( pent )); + + if (pEntity) + { + ItemInfo II; + pEntity->Precache( ); + memset( &II, 0, sizeof II ); + if ( ((CBasePlayerItem*)pEntity)->GetItemInfo( &II ) ) + { + CBasePlayerItem::ItemInfoArray[II.iId] = II; + + if ( II.pszAmmo1 && *II.pszAmmo1 ) + { + AddAmmoNameToAmmoRegistry( II.pszAmmo1 ); + } + + if ( II.pszAmmo2 && *II.pszAmmo2 ) + { + AddAmmoNameToAmmoRegistry( II.pszAmmo2 ); + } + + memset( &II, 0, sizeof II ); + } + } + + REMOVE_ENTITY(pent); +} + +// called by worldspawn +void W_Precache(void) +{ + memset( CBasePlayerItem::ItemInfoArray, 0, sizeof(CBasePlayerItem::ItemInfoArray) ); + memset( CBasePlayerItem::AmmoInfoArray, 0, sizeof(CBasePlayerItem::AmmoInfoArray) ); + giAmmoIndex = 0; + + // quake gun + UTIL_PrecacheOtherWeapon( "weapon_quakegun" ); + AddAmmoNameToAmmoRegistry( "shells" ); + AddAmmoNameToAmmoRegistry( "nails" ); + AddAmmoNameToAmmoRegistry( "rockets" ); + AddAmmoNameToAmmoRegistry( "cells" ); + + // global sprites + g_sModelIndexFireball = PRECACHE_MODEL ("sprites/zerogxplode.spr");// fireball + g_sModelIndexWExplosion = PRECACHE_MODEL ("sprites/WXplo1.spr");// underwater fireball + g_sModelIndexSmoke = PRECACHE_MODEL ("sprites/steam1.spr");// smoke + g_sModelIndexBubbles = PRECACHE_MODEL ("sprites/bubble.spr");//bubbles + g_sModelIndexBloodSpray = PRECACHE_MODEL ("sprites/bloodspray.spr"); // initial blood + g_sModelIndexBloodDrop = PRECACHE_MODEL ("sprites/blood.spr"); // splattered blood + + // used by explosions + PRECACHE_SOUND ("weapons/debris1.wav");// explosion aftermaths + PRECACHE_SOUND ("weapons/debris2.wav");// explosion aftermaths + PRECACHE_SOUND ("weapons/debris3.wav");// explosion aftermaths + + PRECACHE_SOUND ("weapons/grenade_hit1.wav");//grenade + PRECACHE_SOUND ("weapons/grenade_hit2.wav");//grenade + PRECACHE_SOUND ("weapons/grenade_hit3.wav");//grenade + + PRECACHE_SOUND ("weapons/bullet_hit1.wav"); // hit by bullet + PRECACHE_SOUND ("weapons/bullet_hit2.wav"); // hit by bullet + + PRECACHE_SOUND ("items/weapondrop1.wav");// weapon falls to the ground + + PRECACHE_EVENT( 1, "events/shotgun1.sc" ); + PRECACHE_EVENT( 1, "events/shotgun2.sc" ); + PRECACHE_EVENT( 1, "events/axe.sc" ); + PRECACHE_EVENT( 1, "events/axeswing.sc" ); + PRECACHE_EVENT( 1, "events/rocket.sc" ); + PRECACHE_EVENT( 1, "events/grenade.sc" ); + PRECACHE_EVENT( 1, "events/lightning.sc" ); + PRECACHE_EVENT( 1, "events/spike.sc" ); + PRECACHE_EVENT( 1, "events/superspike.sc" ); +} + + + + +TYPEDESCRIPTION CBasePlayerItem::m_SaveData[] = +{ + DEFINE_FIELD( CBasePlayerItem, m_pPlayer, FIELD_CLASSPTR ), + DEFINE_FIELD( CBasePlayerItem, m_pNext, FIELD_CLASSPTR ), + //DEFINE_FIELD( CBasePlayerItem, m_fKnown, FIELD_INTEGER ),Reset to zero on load + DEFINE_FIELD( CBasePlayerItem, m_iId, FIELD_INTEGER ), + // DEFINE_FIELD( CBasePlayerItem, m_iIdPrimary, FIELD_INTEGER ), + // DEFINE_FIELD( CBasePlayerItem, m_iIdSecondary, FIELD_INTEGER ), +}; +IMPLEMENT_SAVERESTORE( CBasePlayerItem, CBaseAnimating ); + + +TYPEDESCRIPTION CBasePlayerWeapon::m_SaveData[] = +{ + DEFINE_FIELD( CBasePlayerWeapon, m_flNextPrimaryAttack, FIELD_TIME ), + DEFINE_FIELD( CBasePlayerWeapon, m_flNextSecondaryAttack, FIELD_TIME ), + DEFINE_FIELD( CBasePlayerWeapon, m_flTimeWeaponIdle, FIELD_TIME ), + DEFINE_FIELD( CBasePlayerWeapon, m_iPrimaryAmmoType, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerWeapon, m_iSecondaryAmmoType, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerWeapon, m_iClip, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerWeapon, m_iDefaultAmmo, FIELD_INTEGER ), +// DEFINE_FIELD( CBasePlayerWeapon, m_iClientClip, FIELD_INTEGER ) , reset to zero on load so hud gets updated correctly +// DEFINE_FIELD( CBasePlayerWeapon, m_iClientWeaponState, FIELD_INTEGER ), reset to zero on load so hud gets updated correctly +}; + +IMPLEMENT_SAVERESTORE( CBasePlayerWeapon, CBasePlayerItem ); + + +void CBasePlayerItem :: SetObjectCollisionBox( void ) +{ + pev->absmin = pev->origin + Vector(-24, -24, 0); + pev->absmax = pev->origin + Vector(24, 24, 16); +} + + +//========================================================= +// Sets up movetype, size, solidtype for a new weapon. +//========================================================= +void CBasePlayerItem :: FallInit( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_BBOX; + + UTIL_SetOrigin( pev, pev->origin ); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0) );//pointsize until it lands on the ground. + + SetTouch( &CBasePlayerItem::DefaultTouch ); + SetThink( &CBasePlayerItem::FallThink ); + + pev->nextthink = gpGlobals->time + 0.1; +} + +//========================================================= +// FallThink - Items that have just spawned run this think +// to catch them when they hit the ground. Once we're sure +// that the object is grounded, we change its solid type +// to trigger and set it in a large box that helps the +// player get it. +//========================================================= +void CBasePlayerItem::FallThink ( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + + + if ( pev->flags & FL_ONGROUND ) + { + // clatter if we have an owner (i.e., dropped by someone) + // don't clatter if the gun is waiting to respawn (if it's waiting, it is invisible!) + if ( !FNullEnt( pev->owner ) ) + { + int pitch = 95 + RANDOM_LONG(0,29); + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "items/weapondrop1.wav", 1, ATTN_NORM, 0, pitch); + } + + // lie flat + pev->angles.x = 0; + pev->angles.z = 0; + + Materialize(); + } +} + +//========================================================= +// Materialize - make a CBasePlayerItem visible and tangible +//========================================================= +void CBasePlayerItem::Materialize( void ) +{ + if ( pev->effects & EF_NODRAW ) + { + // changing from invisible state to visible. + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "items/suitchargeok1.wav", 1, ATTN_NORM, 0, 150 ); + pev->effects &= ~EF_NODRAW; + pev->effects |= EF_MUZZLEFLASH; + } + + pev->solid = SOLID_TRIGGER; + + UTIL_SetOrigin( pev, pev->origin );// link into world. + SetTouch (&CBasePlayerItem::DefaultTouch); + SetThink (NULL); + +} + +//========================================================= +// AttemptToMaterialize - the item is trying to rematerialize, +// should it do so now or wait longer? +//========================================================= +void CBasePlayerItem::AttemptToMaterialize( void ) +{ + float time = g_pGameRules->FlWeaponTryRespawn( this ); + + if ( time == 0 ) + { + Materialize(); + return; + } + + pev->nextthink = gpGlobals->time + time; +} + +//========================================================= +// CheckRespawn - a player is taking this weapon, should +// it respawn? +//========================================================= +void CBasePlayerItem :: CheckRespawn ( void ) +{ + switch ( g_pGameRules->WeaponShouldRespawn( this ) ) + { + case GR_WEAPON_RESPAWN_YES: + Respawn(); + break; + case GR_WEAPON_RESPAWN_NO: + return; + break; + } +} + +//========================================================= +// Respawn- this item is already in the world, but it is +// invisible and intangible. Make it visible and tangible. +//========================================================= +CBaseEntity* CBasePlayerItem::Respawn( void ) +{ + // make a copy of this weapon that is invisible and inaccessible to players (no touch function). The weapon spawn/respawn code + // will decide when to make the weapon visible and touchable. + CBaseEntity *pNewWeapon = CBaseEntity::Create( (char *)STRING( pev->classname ), g_pGameRules->VecWeaponRespawnSpot( this ), pev->angles, pev->owner ); + + if ( pNewWeapon ) + { + pNewWeapon->pev->effects |= EF_NODRAW;// invisible for now + pNewWeapon->SetTouch( NULL );// no touch + pNewWeapon->SetThink( &CBasePlayerItem::AttemptToMaterialize ); + + DROP_TO_FLOOR ( ENT(pev) ); + + // not a typo! We want to know when the weapon the player just picked up should respawn! This new entity we created is the replacement, + // but when it should respawn is based on conditions belonging to the weapon that was taken. + pNewWeapon->pev->nextthink = g_pGameRules->FlWeaponRespawnTime( this ); + } + else + { + ALERT ( at_console, "Respawn failed to create %s!\n", STRING( pev->classname ) ); + } + + return pNewWeapon; +} + +void CBasePlayerItem::DefaultTouch( CBaseEntity *pOther ) +{ + // if it's not a player, ignore + if ( !pOther->IsPlayer() ) + return; + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + + // can I have this? + if ( !g_pGameRules->CanHavePlayerItem( pPlayer, this ) ) + { + + if ( gEvilImpulse101 || FClassnameIs( pev, "weapon_quakegun" ) ) + { + UTIL_Remove( this ); + } + return; + } + + if (pOther->AddPlayerItem( this )) + { + AttachToPlayer( pPlayer ); + EMIT_SOUND(ENT(pPlayer->pev), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM); + } + + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); // UNDONE: when should this happen? +} + +BOOL CanAttack( float attack_time, float curtime, BOOL isPredicted ) +{ + if ( !isPredicted ) + { + return ( attack_time <= curtime ) ? TRUE : FALSE; + } + else + { + return ( attack_time <= 0.0 ) ? TRUE : FALSE; + } +} + +void CBasePlayerWeapon::ItemPostFrame( void ) +{ + if ((m_fInReload) && ( m_pPlayer->m_flNextAttack <= UTIL_WeaponTimeBase() ) ) + { + // complete the reload. + int j = min( iMaxClip() - m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); + + // Add them to the clip + m_iClip += j; + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= j; + + m_fInReload = FALSE; + } + + if ((m_pPlayer->pev->button & IN_ATTACK2) && CanAttack( m_flNextSecondaryAttack, gpGlobals->time, UseDecrement() ) ) + { + if ( pszAmmo2() && !m_pPlayer->m_rgAmmo[SecondaryAmmoIndex()] ) + { + m_fFireOnEmpty = TRUE; + } + + SecondaryAttack(); + m_pPlayer->pev->button &= ~IN_ATTACK2; + } + else if ((m_pPlayer->pev->button & IN_ATTACK) && CanAttack( m_flNextPrimaryAttack, gpGlobals->time, UseDecrement() ) ) + { + if ( (m_iClip == 0 && pszAmmo1()) || (iMaxClip() == -1 && !m_pPlayer->m_rgAmmo[PrimaryAmmoIndex()] ) ) + { + m_fFireOnEmpty = TRUE; + } + + PrimaryAttack(); + } + else if ( m_pPlayer->pev->button & IN_RELOAD && iMaxClip() != WEAPON_NOCLIP && !m_fInReload ) + { + // reload when reload is pressed, or if no buttons are down and weapon is empty. + Reload(); + } + else if ( !(m_pPlayer->pev->button & (IN_ATTACK|IN_ATTACK2) ) ) + { + // no fire buttons down + if ( !m_bPlayedIdleAnim ) + { + m_bPlayedIdleAnim = TRUE; + SendWeaponAnim( 0, 1 ); + + if ( m_pPlayer->m_iQuakeWeapon == IT_LIGHTNING ) + { + PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_pPlayer->m_usLightning, 0, (float *)&m_pPlayer->pev->origin, (float *)&m_pPlayer->pev->angles, 0.0, 0.0, 0, 1, 0, 0 ); + + if ( m_pPlayer->m_pActiveItem ) + ((CQuakeGun*)m_pPlayer->m_pActiveItem)->DestroyEffect(); + } + } + + WeaponIdle( ); + return; + } + + // catch all + if ( ShouldWeaponIdle() ) + { + WeaponIdle(); + } +} + +void CBasePlayerItem::DestroyItem( void ) +{ + if ( m_pPlayer ) + { + // if attached to a player, remove. + m_pPlayer->RemovePlayerItem( this ); + } + + Kill( ); +} + +int CBasePlayerItem::AddToPlayer( CBasePlayer *pPlayer ) +{ + m_pPlayer = pPlayer; + + return TRUE; +} + +void CBasePlayerItem::Drop( void ) +{ + SetTouch( NULL ); + SetThink(&CBasePlayerItem::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; +} + +void CBasePlayerItem::Kill( void ) +{ + SetTouch( NULL ); + SetThink(&CBasePlayerItem::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; +} + +void CBasePlayerItem::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->pev->viewmodel = 0; + m_pPlayer->pev->weaponmodel = 0; +} + +void CBasePlayerItem::AttachToPlayer ( CBasePlayer *pPlayer ) +{ + pev->movetype = MOVETYPE_FOLLOW; + pev->solid = SOLID_NOT; + pev->aiment = pPlayer->edict(); + pev->effects = EF_NODRAW; // ?? + pev->modelindex = 0;// server won't send down to clients if modelindex == 0 + pev->model = iStringNull; + pev->owner = pPlayer->edict(); + pev->nextthink = gpGlobals->time + .1; + SetTouch( NULL ); +} + +// CALLED THROUGH the newly-touched weapon's instance. The existing player weapon is pOriginal +int CBasePlayerWeapon::AddDuplicate( CBasePlayerItem *pOriginal ) +{ + if ( m_iDefaultAmmo ) + { + return ExtractAmmo( (CBasePlayerWeapon *)pOriginal ); + } + else + { + // a dead player dropped this. + return ExtractClipAmmo( (CBasePlayerWeapon *)pOriginal ); + } +} + + +int CBasePlayerWeapon::AddToPlayer( CBasePlayer *pPlayer ) +{ + int bResult = CBasePlayerItem::AddToPlayer( pPlayer ); + + pPlayer->pev->weapons |= (1<GetAmmoIndex( pszAmmo1() ); + m_iSecondaryAmmoType = pPlayer->GetAmmoIndex( pszAmmo2() ); + } + + + if (bResult) + return AddWeapon( ); + return FALSE; +} + +int CBasePlayerWeapon::UpdateClientData( CBasePlayer *pPlayer ) +{ + BOOL bSend = FALSE; + int state = 0; + if ( pPlayer->m_pActiveItem == this ) + { + if ( pPlayer->m_fOnTarget ) + state = WEAPON_IS_ONTARGET; + else + state = 1; + } + + // Forcing send of all data! + if ( !pPlayer->m_fWeapon ) + bSend = TRUE; + + // See if the Quake Gun has changed "weapons" + if ( pPlayer->m_iQuakeWeapon != pPlayer->m_iClientQuakeWeapon ) + bSend = TRUE; + + // QUAKECLASSIC + m_iClip = 0; + + int iId; + + switch ( pPlayer->m_iQuakeWeapon ) + { + case IT_AXE: iId = IT_AXE; break; + case IT_SHOTGUN: iId = IT_SHOTGUN; break; + case IT_SUPER_SHOTGUN: iId = IT_SUPER_SHOTGUN; break; + case IT_NAILGUN: iId = IT_NAILGUN; break; + case IT_SUPER_NAILGUN: iId = IT_SUPER_NAILGUN; break; + case IT_GRENADE_LAUNCHER: iId = IT_GRENADE_LAUNCHER; break; + case IT_ROCKET_LAUNCHER: iId = IT_ROCKET_LAUNCHER; break; + case IT_LIGHTNING: iId = IT_LIGHTNING; break; + } + + if ( bSend ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pPlayer->pev ); + WRITE_BYTE( state ); + WRITE_BYTE( iId ); + WRITE_BYTE( m_iClip ); + MESSAGE_END(); + + m_iClientClip = m_iClip; + m_iClientWeaponState = state; + pPlayer->m_fWeapon = TRUE; + } + + if ( m_pNext ) + m_pNext->UpdateClientData( pPlayer ); + + return 1; +} + + +void CBasePlayerWeapon::SendWeaponAnim( int iAnim, int skiplocal ) +{ + m_pPlayer->pev->weaponanim = iAnim; + + if ( skiplocal && ENGINE_CANSKIP( m_pPlayer->edict() ) ) + return; + + MESSAGE_BEGIN( MSG_ONE, SVC_WEAPONANIM, NULL, m_pPlayer->pev ); + WRITE_BYTE( iAnim ); // sequence number + WRITE_BYTE( pev->body ); // weaponmodel bodygroup. + MESSAGE_END(); +} + +BOOL CBasePlayerWeapon :: AddPrimaryAmmo( int iCount, char *szName, int iMaxClip, int iMaxCarry ) +{ + int iIdAmmo; + + if (iMaxClip < 1) + { + m_iClip = -1; + iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName, iMaxCarry ); + } + else if (m_iClip == 0) + { + int i; + i = min( m_iClip + iCount, iMaxClip ) - m_iClip; + m_iClip += i; + iIdAmmo = m_pPlayer->GiveAmmo( iCount - i, szName, iMaxCarry ); + } + else + { + iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName, iMaxCarry ); + } + + // m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] = iMaxCarry; // hack for testing + + if (iIdAmmo > 0) + { + m_iPrimaryAmmoType = iIdAmmo; + if (m_pPlayer->HasPlayerItem( this ) ) + { + // play the "got ammo" sound only if we gave some ammo to a player that already had this gun. + // if the player is just getting this gun for the first time, DefaultTouch will play the "picked up gun" sound for us. + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + } + } + + return iIdAmmo > 0 ? TRUE : FALSE; +} + + +BOOL CBasePlayerWeapon :: AddSecondaryAmmo( int iCount, char *szName, int iMax ) +{ + int iIdAmmo; + + iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName, iMax ); + + //m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] = iMax; // hack for testing + + if (iIdAmmo > 0) + { + m_iSecondaryAmmoType = iIdAmmo; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + } + return iIdAmmo > 0 ? TRUE : FALSE; +} + +//========================================================= +// IsUseable - this function determines whether or not a +// weapon is useable by the player in its current state. +// (does it have ammo loaded? do I have any ammo for the +// weapon?, etc) +//========================================================= +BOOL CBasePlayerWeapon :: IsUseable( void ) +{ + if ( m_iClip <= 0 ) + { + if ( m_pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] <= 0 && iMaxAmmo1() != -1 ) + { + // clip is empty (or nonexistant) and the player has no more ammo of this type. + return FALSE; + } + } + + return TRUE; +} + +BOOL CBasePlayerWeapon :: CanDeploy( void ) +{ + BOOL bHasAmmo = 0; + + if ( !pszAmmo1() ) + { + // this weapon doesn't use ammo, can always deploy. + return TRUE; + } + + if ( pszAmmo1() ) + { + bHasAmmo |= (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] != 0); + } + if ( pszAmmo2() ) + { + bHasAmmo |= (m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] != 0); + } + if (m_iClip > 0) + { + bHasAmmo |= 1; + } + if (!bHasAmmo) + { + return FALSE; + } + + return TRUE; +} + +BOOL CBasePlayerWeapon :: DefaultDeploy( char *szViewModel, char *szWeaponModel, int iAnim, char *szAnimExt, int skiplocal /* = 0 */ ) +{ + if (!CanDeploy( )) + return FALSE; + + m_bPlayedIdleAnim = FALSE; + + m_pPlayer->pev->viewmodel = MAKE_STRING(szViewModel); + m_pPlayer->pev->weaponmodel = MAKE_STRING(szWeaponModel); + strcpy( m_pPlayer->m_szAnimExtention, szAnimExt ); + SendWeaponAnim( iAnim, skiplocal ); + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.0; + + return TRUE; +} + + +BOOL CBasePlayerWeapon :: DefaultReload( int iClipSize, int iAnim, float fDelay ) +{ + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + return FALSE; + + int j = min(iClipSize - m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); + + if (j == 0) + return FALSE; + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + fDelay; + + //!!UNDONE -- reload sound goes here !!! + SendWeaponAnim( iAnim ); + + m_fInReload = TRUE; + + m_flTimeWeaponIdle = gpGlobals->time + 3; + return TRUE; +} + +BOOL CBasePlayerWeapon :: PlayEmptySound( void ) +{ + if (m_iPlayEmptySound) + { + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/357_cock1.wav", 0.8, ATTN_NORM); + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +void CBasePlayerWeapon :: ResetEmptySound( void ) +{ + m_iPlayEmptySound = 1; +} + +//========================================================= +//========================================================= +int CBasePlayerWeapon::PrimaryAmmoIndex( void ) +{ + return m_iPrimaryAmmoType; +} + +//========================================================= +//========================================================= +int CBasePlayerWeapon::SecondaryAmmoIndex( void ) +{ + return -1; +} + +void CBasePlayerWeapon::Holster( int skiplocal /* = 0 */ ) +{ + m_fInReload = FALSE; // cancel any reload in progress. + m_pPlayer->pev->viewmodel = 0; + m_pPlayer->pev->weaponmodel = 0; +} + +void CBasePlayerAmmo::Spawn( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 16)); + UTIL_SetOrigin( pev, pev->origin ); + + SetTouch( &CBasePlayerAmmo::DefaultTouch ); +} + +CBaseEntity* CBasePlayerAmmo::Respawn( void ) +{ + pev->effects |= EF_NODRAW; + SetTouch( NULL ); + + UTIL_SetOrigin( pev, g_pGameRules->VecAmmoRespawnSpot( this ) );// move to wherever I'm supposed to repawn. + + SetThink( &CBasePlayerAmmo::Materialize ); + pev->nextthink = g_pGameRules->FlAmmoRespawnTime( this ); + + return this; +} + +void CBasePlayerAmmo::Materialize( void ) +{ + if ( pev->effects & EF_NODRAW ) + { + // changing from invisible state to visible. + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "items/suitchargeok1.wav", 1, ATTN_NORM, 0, 150 ); + pev->effects &= ~EF_NODRAW; + pev->effects |= EF_MUZZLEFLASH; + } + + SetTouch( &CBasePlayerAmmo::DefaultTouch ); +} + +void CBasePlayerAmmo :: DefaultTouch( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + { + return; + } + + if (AddAmmo( pOther )) + { + if ( g_pGameRules->AmmoShouldRespawn( this ) == GR_AMMO_RESPAWN_YES ) + { + Respawn(); + } + else + { + SetTouch( NULL ); + SetThink(&CBasePlayerAmmo::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; + } + } + else if (gEvilImpulse101) + { + // evil impulse 101 hack, kill always + SetTouch( NULL ); + SetThink(&CBasePlayerAmmo::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; + } +} + +//========================================================= +// called by the new item with the existing item as parameter +// +// if we call ExtractAmmo(), it's because the player is picking up this type of weapon for +// the first time. If it is spawned by the world, m_iDefaultAmmo will have a default ammo amount in it. +// if this is a weapon dropped by a dying player, has 0 m_iDefaultAmmo, which means only the ammo in +// the weapon clip comes along. +//========================================================= +int CBasePlayerWeapon::ExtractAmmo( CBasePlayerWeapon *pWeapon ) +{ + int iReturn; + + if ( pszAmmo1() != NULL ) + { + // blindly call with m_iDefaultAmmo. It's either going to be a value or zero. If it is zero, + // we only get the ammo in the weapon's clip, which is what we want. + iReturn = pWeapon->AddPrimaryAmmo( m_iDefaultAmmo, (char *)pszAmmo1(), iMaxClip(), iMaxAmmo1() ); + m_iDefaultAmmo = 0; + } + + if ( pszAmmo2() != NULL ) + { + iReturn = pWeapon->AddSecondaryAmmo( 0, (char *)pszAmmo2(), iMaxAmmo2() ); + } + + return iReturn; +} + +//========================================================= +// called by the new item's class with the existing item as parameter +//========================================================= +int CBasePlayerWeapon::ExtractClipAmmo( CBasePlayerWeapon *pWeapon ) +{ + int iAmmo; + + if ( m_iClip == WEAPON_NOCLIP ) + { + iAmmo = 0;// guns with no clips always come empty if they are second-hand + } + else + { + iAmmo = m_iClip; + } + + return pWeapon->m_pPlayer->GiveAmmo( iAmmo, (char *)pszAmmo1(), iMaxAmmo1() ); // , &m_iPrimaryAmmoType +} + +//========================================================= +// RetireWeapon - no more ammo for this gun, put it away. +//========================================================= +void CBasePlayerWeapon::RetireWeapon( void ) +{ + // first, no viewmodel at all. + m_pPlayer->pev->viewmodel = iStringNull; + m_pPlayer->pev->weaponmodel = iStringNull; + //m_pPlayer->pev->viewmodelindex = NULL; + + g_pGameRules->GetNextBestWeapon( m_pPlayer, this ); +} + +//********************************************************* +// weaponbox code: +//********************************************************* + +LINK_ENTITY_TO_CLASS( weaponbox, CWeaponBox ); + +TYPEDESCRIPTION CWeaponBox::m_SaveData[] = +{ + DEFINE_ARRAY( CWeaponBox, m_rgAmmo, FIELD_INTEGER, MAX_AMMO_SLOTS ), + DEFINE_ARRAY( CWeaponBox, m_rgiszAmmo, FIELD_STRING, MAX_AMMO_SLOTS ), + DEFINE_ARRAY( CWeaponBox, m_rgpPlayerItems, FIELD_CLASSPTR, MAX_ITEM_TYPES ), + DEFINE_FIELD( CWeaponBox, m_cAmmoTypes, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CWeaponBox, CBaseEntity ); + +//========================================================= +// +//========================================================= +void CWeaponBox::Precache( void ) +{ + PRECACHE_MODEL("models/w_weaponbox.mdl"); +} + +//========================================================= +//========================================================= +void CWeaponBox :: KeyValue( KeyValueData *pkvd ) +{ + if ( m_cAmmoTypes < MAX_AMMO_SLOTS ) + { + PackAmmo( ALLOC_STRING(pkvd->szKeyName), atoi(pkvd->szValue) ); + m_cAmmoTypes++;// count this new ammo type. + + pkvd->fHandled = TRUE; + } + else + { + ALERT ( at_console, "WeaponBox too full! only %d ammotypes allowed\n", MAX_AMMO_SLOTS ); + } +} + +//========================================================= +// CWeaponBox - Spawn +//========================================================= +void CWeaponBox::Spawn( void ) +{ + Precache( ); + + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + + UTIL_SetSize( pev, g_vecZero, g_vecZero ); + + SET_MODEL( ENT(pev), "models/w_weaponbox.mdl"); +} + +//========================================================= +// CWeaponBox - Kill - the think function that removes the +// box from the world. +//========================================================= +void CWeaponBox::Kill( void ) +{ + CBasePlayerItem *pWeapon; + int i; + + // destroy the weapons + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pWeapon = m_rgpPlayerItems[ i ]; + + while ( pWeapon ) + { + pWeapon->SetThink(&CBasePlayerItem::SUB_Remove); + pWeapon->pev->nextthink = gpGlobals->time + 0.1; + pWeapon = pWeapon->m_pNext; + } + } + + // remove the box + UTIL_Remove( this ); +} + +//========================================================= +// CWeaponBox - Touch: try to add my contents to the toucher +// if the toucher is a player. +//========================================================= +void CWeaponBox::Touch( CBaseEntity *pOther ) +{ + if ( !(pev->flags & FL_ONGROUND ) ) + { + return; + } + + if ( !pOther->IsPlayer() ) + { + // only players may touch a weaponbox. + return; + } + + if ( !pOther->IsAlive() ) + { + // no dead guys. + return; + } + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + int i; + +// dole out ammo + for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ ) + { + if ( !FStringNull( m_rgiszAmmo[ i ] ) ) + { + // there's some ammo of this type. + pPlayer->GiveAmmo( m_rgAmmo[ i ], (char *)STRING( m_rgiszAmmo[ i ] ), MaxAmmoCarry( m_rgiszAmmo[ i ] ) ); + + //ALERT ( at_console, "Gave %d rounds of %s\n", m_rgAmmo[i], STRING(m_rgiszAmmo[i]) ); + + // now empty the ammo from the weaponbox since we just gave it to the player + m_rgiszAmmo[ i ] = iStringNull; + m_rgAmmo[ i ] = 0; + } + } + +// go through my weapons and try to give the usable ones to the player. +// it's important the the player be given ammo first, so the weapons code doesn't refuse +// to deploy a better weapon that the player may pick up because he has no ammo for it. + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + CBasePlayerItem *pItem; + + // have at least one weapon in this slot + while ( m_rgpPlayerItems[ i ] ) + { + //ALERT ( at_console, "trying to give %s\n", STRING( m_rgpPlayerItems[ i ]->pev->classname ) ); + + pItem = m_rgpPlayerItems[ i ]; + m_rgpPlayerItems[ i ] = m_rgpPlayerItems[ i ]->m_pNext;// unlink this weapon from the box + + if ( pPlayer->AddPlayerItem( pItem ) ) + { + pItem->AttachToPlayer( pPlayer ); + } + } + } + } + + EMIT_SOUND( pOther->edict(), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM ); + SetTouch(NULL); + UTIL_Remove(this); +} + +//========================================================= +// CWeaponBox - PackWeapon: Add this weapon to the box +//========================================================= +BOOL CWeaponBox::PackWeapon( CBasePlayerItem *pWeapon ) +{ + // is one of these weapons already packed in this box? + if ( HasWeapon( pWeapon ) ) + { + return FALSE;// box can only hold one of each weapon type + } + + if ( pWeapon->m_pPlayer ) + { + if ( !pWeapon->m_pPlayer->RemovePlayerItem( pWeapon ) ) + { + // failed to unhook the weapon from the player! + return FALSE; + } + } + + int iWeaponSlot = pWeapon->iItemSlot(); + + if ( m_rgpPlayerItems[ iWeaponSlot ] ) + { + // there's already one weapon in this slot, so link this into the slot's column + pWeapon->m_pNext = m_rgpPlayerItems[ iWeaponSlot ]; + m_rgpPlayerItems[ iWeaponSlot ] = pWeapon; + } + else + { + // first weapon we have for this slot + m_rgpPlayerItems[ iWeaponSlot ] = pWeapon; + pWeapon->m_pNext = NULL; + } + + pWeapon->pev->spawnflags |= SF_NORESPAWN;// never respawn + pWeapon->pev->movetype = MOVETYPE_NONE; + pWeapon->pev->solid = SOLID_NOT; + pWeapon->pev->effects = EF_NODRAW; + pWeapon->pev->modelindex = 0; + pWeapon->pev->model = iStringNull; + pWeapon->pev->owner = edict(); + pWeapon->SetThink( NULL );// crowbar may be trying to swing again, etc. + pWeapon->SetTouch( NULL ); + pWeapon->m_pPlayer = NULL; + + //ALERT ( at_console, "packed %s\n", STRING(pWeapon->pev->classname) ); + + return TRUE; +} + +//========================================================= +// CWeaponBox - PackAmmo +//========================================================= +BOOL CWeaponBox::PackAmmo( int iszName, int iCount ) +{ + int iMaxCarry; + + if ( FStringNull( iszName ) ) + { + // error here + ALERT ( at_console, "NULL String in PackAmmo!\n" ); + return FALSE; + } + + iMaxCarry = MaxAmmoCarry( iszName ); + + if ( iMaxCarry != -1 && iCount > 0 ) + { + //ALERT ( at_console, "Packed %d rounds of %s\n", iCount, STRING(iszName) ); + GiveAmmo( iCount, (char *)STRING( iszName ), iMaxCarry ); + return TRUE; + } + + return FALSE; +} + +//========================================================= +// CWeaponBox - GiveAmmo +//========================================================= +int CWeaponBox::GiveAmmo( int iCount, char *szName, int iMax, int *pIndex/* = NULL*/ ) +{ + int i; + + for (i = 1; i < MAX_AMMO_SLOTS && !FStringNull( m_rgiszAmmo[i] ); i++) + { + if (stricmp( szName, STRING( m_rgiszAmmo[i])) == 0) + { + if (pIndex) + *pIndex = i; + + int iAdd = min( iCount, iMax - m_rgAmmo[i]); + if (iCount == 0 || iAdd > 0) + { + m_rgAmmo[i] += iAdd; + + return i; + } + return -1; + } + } + if (i < MAX_AMMO_SLOTS) + { + if (pIndex) + *pIndex = i; + + m_rgiszAmmo[i] = MAKE_STRING( szName ); + m_rgAmmo[i] = iCount; + + return i; + } + ALERT( at_console, "out of named ammo slots\n"); + return i; +} + +//========================================================= +// CWeaponBox::HasWeapon - is a weapon of this type already +// packed in this box? +//========================================================= +BOOL CWeaponBox::HasWeapon( CBasePlayerItem *pCheckItem ) +{ + CBasePlayerItem *pItem = m_rgpPlayerItems[pCheckItem->iItemSlot()]; + + while (pItem) + { + if (FClassnameIs( pItem->pev, STRING( pCheckItem->pev->classname) )) + { + return TRUE; + } + pItem = pItem->m_pNext; + } + + return FALSE; +} + +//========================================================= +// CWeaponBox::IsEmpty - is there anything in this box? +//========================================================= +BOOL CWeaponBox::IsEmpty( void ) +{ + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + return FALSE; + } + } + + for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ ) + { + if ( !FStringNull( m_rgiszAmmo[ i ] ) ) + { + // still have a bit of this type of ammo + return FALSE; + } + } + + return TRUE; +} + +//========================================================= +//========================================================= +void CWeaponBox::SetObjectCollisionBox( void ) +{ + pev->absmin = pev->origin + Vector(-16, -16, 0); + pev->absmax = pev->origin + Vector(16, 16, 16); +} + +void CBasePlayerWeapon::PrintState( void ) +{ +} + + diff --git a/dmc/dlls/weapons.h b/dmc/dlls/weapons.h new file mode 100644 index 0000000..a3d19d1 --- /dev/null +++ b/dmc/dlls/weapons.h @@ -0,0 +1,492 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef WEAPONS_H +#define WEAPONS_H + + +class CBasePlayer; +extern int gmsgWeapPickup; + +// Contact Grenade / Timed grenade / Satchel Charge +class CGrenade : public CBaseMonster +{ +public: + void Spawn( void ); + + typedef enum { SATCHEL_DETONATE = 0, SATCHEL_RELEASE } SATCHELCODE; + + static CGrenade *ShootTimed( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time ); + static CGrenade *ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + static CGrenade *ShootSatchelCharge( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + static void UseSatchelCharges( entvars_t *pevOwner, SATCHELCODE code ); + + void Explode( Vector vecSrc, Vector vecAim ); + void Explode( TraceResult *pTrace, int bitsDamageType ); + void EXPORT Smoke( void ); + + void EXPORT BounceTouch( CBaseEntity *pOther ); + void EXPORT SlideTouch( CBaseEntity *pOther ); + void EXPORT ExplodeTouch( CBaseEntity *pOther ); + void EXPORT DangerSoundThink( void ); + void EXPORT PreDetonate( void ); + void EXPORT Detonate( void ); + void EXPORT DetonateUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT TumbleThink( void ); + + virtual void BounceSound( void ); + virtual int BloodColor( void ) { return DONT_BLEED; } + virtual void Killed( entvars_t *pevAttacker, int iGib ); + + BOOL m_fRegisteredSound;// whether or not this grenade has issued its DANGER sound to the world sound list yet. +}; + + +// constant items +#define ITEM_HEALTHKIT 1 +#define ITEM_ANTIDOTE 2 +#define ITEM_SECURITY 3 +#define ITEM_BATTERY 4 + +#define WEAPON_NONE 0 +#define WEAPON_CROWBAR 1 +#define WEAPON_GLOCK 2 +#define WEAPON_PYTHON 3 +#define WEAPON_MP5 4 +#define WEAPON_CHAINGUN 5 +#define WEAPON_CROSSBOW 6 +#define WEAPON_SHOTGUN 7 +#define WEAPON_RPG 8 +#define WEAPON_GAUSS 9 +#define WEAPON_EGON 10 +#define WEAPON_HORNETGUN 11 +#define WEAPON_HANDGRENADE 12 +#define WEAPON_TRIPMINE 13 +#define WEAPON_SATCHEL 14 +#define WEAPON_SNARK 15 + +#define WEAPON_ALLWEAPONS (~(1<skin < 0 || (gpGlobals->deathmatch && FBitSet( pev->spawnflags, SF_DECAL_NOTINDEATHMATCH )) ) + { + REMOVE_ENTITY(ENT(pev)); + return; + } + + if ( FStringNull ( pev->targetname ) ) + { + SetThink( &CDecal::StaticDecal ); + // if there's no targetname, the decal will spray itself on as soon as the world is done spawning. + pev->nextthink = gpGlobals->time; + } + else + { + // if there IS a targetname, the decal sprays itself on when it is triggered. + SetThink ( &CDecal::SUB_DoNothing ); + SetUse(&CDecal::TriggerDecal); + } +} + +void CDecal :: TriggerDecal ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // this is set up as a USE function for infodecals that have targetnames, so that the + // decal doesn't get applied until it is fired. (usually by a scripted sequence) + TraceResult trace; + int entityIndex; + + UTIL_TraceLine( pev->origin - Vector(5,5,5), pev->origin + Vector(5,5,5), ignore_monsters, ENT(pev), &trace ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY); + WRITE_BYTE( TE_BSPDECAL ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( (int)pev->skin ); + entityIndex = (short)ENTINDEX(trace.pHit); + WRITE_SHORT( entityIndex ); + if ( entityIndex ) + WRITE_SHORT( (int)VARS(trace.pHit)->modelindex ); + MESSAGE_END(); + + SetThink( &CDecal::SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CDecal :: StaticDecal( void ) +{ + TraceResult trace; + int entityIndex, modelIndex; + + UTIL_TraceLine( pev->origin - Vector(5,5,5), pev->origin + Vector(5,5,5), ignore_monsters, ENT(pev), &trace ); + + entityIndex = (short)ENTINDEX(trace.pHit); + if ( entityIndex ) + modelIndex = (int)VARS(trace.pHit)->modelindex; + else + modelIndex = 0; + + g_engfuncs.pfnStaticDecal( pev->origin, (int)pev->skin, entityIndex, modelIndex ); + + SUB_Remove(); +} + + +void CDecal :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "texture")) + { + pev->skin = DECAL_INDEX( pkvd->szValue ); + + // Found + if ( pev->skin >= 0 ) + return; + ALERT( at_console, "Can't find decal %s\n", pkvd->szValue ); + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +// Body queue class here.... It's really just CBaseEntity +class CCorpse : public CBaseEntity +{ + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +LINK_ENTITY_TO_CLASS( bodyque, CCorpse ); + +static void InitBodyQue(void) +{ + string_t istrClassname = MAKE_STRING("bodyque"); + + g_pBodyQueueHead = CREATE_NAMED_ENTITY( istrClassname ); + entvars_t *pev = VARS(g_pBodyQueueHead); + + // Reserve 3 more slots for dead bodies + for ( int i = 0; i < 3; i++ ) + { + pev->owner = CREATE_NAMED_ENTITY( istrClassname ); + pev = VARS(pev->owner); + } + + pev->owner = g_pBodyQueueHead; +} + + +// +// make a body que entry for the given ent so the ent can be respawned elsewhere +// +// GLOBALS ASSUMED SET: g_eoBodyQueueHead +// +void CopyToBodyQue(entvars_t *pev) +{ + if (pev->effects & EF_NODRAW) + return; + + entvars_t *pevHead = VARS(g_pBodyQueueHead); + + pevHead->angles = pev->angles; + pevHead->model = pev->model; + pevHead->modelindex = pev->modelindex; + pevHead->frame = pev->frame; + pevHead->colormap = pev->colormap; + pevHead->movetype = MOVETYPE_TOSS; + pevHead->velocity = pev->velocity; + pevHead->flags = 0; + pevHead->deadflag = pev->deadflag; + pevHead->renderfx = kRenderFxDeadPlayer; + pevHead->renderamt = ENTINDEX( ENT( pev ) ); + + pevHead->effects = pev->effects | EF_NOINTERP; + //pevHead->goalstarttime = pev->goalstarttime; + //pevHead->goalframe = pev->goalframe; + //pevHead->goalendtime = pev->goalendtime ; + + pevHead->sequence = pev->sequence; + pevHead->animtime = pev->animtime; + + UTIL_SetOrigin(pevHead, pev->origin); + UTIL_SetSize(pevHead, pev->mins, pev->maxs); + g_pBodyQueueHead = pevHead->owner; +} + + +CGlobalState::CGlobalState( void ) +{ + Reset(); +} + +void CGlobalState::Reset( void ) +{ + m_pList = NULL; + m_listCount = 0; +} + +globalentity_t *CGlobalState :: Find( string_t globalname ) +{ + if ( !globalname ) + return NULL; + + globalentity_t *pTest; + const char *pEntityName = STRING(globalname); + + + pTest = m_pList; + while ( pTest ) + { + if ( FStrEq( pEntityName, pTest->name ) ) + break; + + pTest = pTest->pNext; + } + + return pTest; +} + + +// This is available all the time now on impulse 104, remove later +//#ifdef _DEBUG +void CGlobalState :: DumpGlobals( void ) +{ + static char *estates[] = { "Off", "On", "Dead" }; + globalentity_t *pTest; + + ALERT( at_console, "-- Globals --\n" ); + pTest = m_pList; + while ( pTest ) + { + ALERT( at_console, "%s: %s (%s)\n", pTest->name, pTest->levelName, estates[pTest->state] ); + pTest = pTest->pNext; + } +} +//#endif + + +void CGlobalState :: EntityAdd( string_t globalname, string_t mapName, GLOBALESTATE state ) +{ + ASSERT( !Find(globalname) ); + + globalentity_t *pNewEntity = (globalentity_t *)calloc( sizeof( globalentity_t ), 1 ); + ASSERT( pNewEntity != NULL ); + pNewEntity->pNext = m_pList; + m_pList = pNewEntity; + strcpy( pNewEntity->name, STRING( globalname ) ); + strcpy( pNewEntity->levelName, STRING(mapName) ); + pNewEntity->state = state; + m_listCount++; +} + + +void CGlobalState :: EntitySetState( string_t globalname, GLOBALESTATE state ) +{ + globalentity_t *pEnt = Find( globalname ); + + if ( pEnt ) + pEnt->state = state; +} + + +const globalentity_t *CGlobalState :: EntityFromTable( string_t globalname ) +{ + globalentity_t *pEnt = Find( globalname ); + + return pEnt; +} + + +GLOBALESTATE CGlobalState :: EntityGetState( string_t globalname ) +{ + globalentity_t *pEnt = Find( globalname ); + if ( pEnt ) + return pEnt->state; + + return GLOBAL_OFF; +} + + +// Global Savedata for Delay +TYPEDESCRIPTION CGlobalState::m_SaveData[] = +{ + DEFINE_FIELD( CGlobalState, m_listCount, FIELD_INTEGER ), +}; + +// Global Savedata for Delay +TYPEDESCRIPTION gGlobalEntitySaveData[] = +{ + DEFINE_ARRAY( globalentity_t, name, FIELD_CHARACTER, 64 ), + DEFINE_ARRAY( globalentity_t, levelName, FIELD_CHARACTER, 32 ), + DEFINE_FIELD( globalentity_t, state, FIELD_INTEGER ), +}; + + +int CGlobalState::Save( CSave &save ) +{ + int i; + globalentity_t *pEntity; + + if ( !save.WriteFields( "GLOBAL", this, m_SaveData, ARRAYSIZE(m_SaveData) ) ) + return 0; + + pEntity = m_pList; + for ( i = 0; i < m_listCount && pEntity; i++ ) + { + if ( !save.WriteFields( "GENT", pEntity, gGlobalEntitySaveData, ARRAYSIZE(gGlobalEntitySaveData) ) ) + return 0; + + pEntity = pEntity->pNext; + } + + return 1; +} + +int CGlobalState::Restore( CRestore &restore ) +{ + int i, listCount; + globalentity_t tmpEntity; + + + ClearStates(); + if ( !restore.ReadFields( "GLOBAL", this, m_SaveData, ARRAYSIZE(m_SaveData) ) ) + return 0; + + listCount = m_listCount; // Get new list count + m_listCount = 0; // Clear loaded data + + for ( i = 0; i < listCount; i++ ) + { + if ( !restore.ReadFields( "GENT", &tmpEntity, gGlobalEntitySaveData, ARRAYSIZE(gGlobalEntitySaveData) ) ) + return 0; + EntityAdd( MAKE_STRING(tmpEntity.name), MAKE_STRING(tmpEntity.levelName), tmpEntity.state ); + } + return 1; +} + +void CGlobalState::EntityUpdate( string_t globalname, string_t mapname ) +{ + globalentity_t *pEnt = Find( globalname ); + + if ( pEnt ) + strcpy( pEnt->levelName, STRING(mapname) ); +} + + +void CGlobalState::ClearStates( void ) +{ + globalentity_t *pFree = m_pList; + while ( pFree ) + { + globalentity_t *pNext = pFree->pNext; + free( pFree ); + pFree = pNext; + } + Reset(); +} + + +void SaveGlobalState( SAVERESTOREDATA *pSaveData ) +{ + CSave saveHelper( pSaveData ); + gGlobalState.Save( saveHelper ); +} + + +void RestoreGlobalState( SAVERESTOREDATA *pSaveData ) +{ + CRestore restoreHelper( pSaveData ); + gGlobalState.Restore( restoreHelper ); +} + + +void ResetGlobalState( void ) +{ + gGlobalState.ClearStates(); + gInitHUD = TRUE; // Init the HUD on a new game / load game +} + +// moved CWorld class definition to cbase.h +//======================= +// CWorld +// +// This spawns first when each level begins. +//======================= + +LINK_ENTITY_TO_CLASS( worldspawn, CWorld ); + +#define SF_WORLD_DARK 0x0001 // Fade from black at startup +#define SF_WORLD_TITLE 0x0002 // Display game title at startup +#define SF_WORLD_FORCETEAM 0x0004 // Force teams + +extern DLL_GLOBAL BOOL g_fGameOver; +float g_flWeaponCheat; + +void CWorld :: Spawn( void ) +{ + g_fGameOver = FALSE; + Precache( ); + g_flWeaponCheat = CVAR_GET_FLOAT( "sv_cheats" ); // Is the impulse 101 command allowed? +} + +void CWorld :: Precache( void ) +{ + g_pLastSpawn = NULL; + +#if 1 + CVAR_SET_STRING("sv_gravity", "800"); // 67ft/sec + CVAR_SET_STRING("sv_stepsize", "18"); +#else + CVAR_SET_STRING("sv_gravity", "384"); // 32ft/sec + CVAR_SET_STRING("sv_stepsize", "24"); +#endif + + CVAR_SET_STRING("room_type", "0");// clear DSP + + // QUAKECLASSIC + // Set various physics cvars to Quake's values + CVAR_SET_STRING("sv_friction", "4"); + CVAR_SET_STRING("sv_maxspeed", "400"); + CVAR_SET_STRING("sv_airaccelerate", "0.7"); + + // Set up game rules + if (g_pGameRules) + { + delete g_pGameRules; + } + + g_pGameRules = InstallGameRules( ); + + //!!!UNDONE why is there so much Spawn code in the Precache function? I'll just keep it here + InitBodyQue(); + +// init sentence group playback stuff from sentences.txt. +// ok to call this multiple times, calls after first are ignored. + + SENTENCEG_Init(); + +// init texture type array from materials.txt + + TEXTURETYPE_Init(); + + +// the area based ambient sounds MUST be the first precache_sounds + +// player precaches + W_Precache (); // get weapon precaches + + ClientPrecache(); + + // QUAKECLASSIC + QuakeClassicPrecache(); + +// sounds used from C physics code + PRECACHE_SOUND("common/null.wav"); // clears sound channels + + PRECACHE_SOUND( "items/suitchargeok1.wav" );//!!! temporary sound for respawning weapons. + PRECACHE_SOUND( "items/gunpickup2.wav" );// player picks up a gun. + + PRECACHE_SOUND( "common/bodydrop3.wav" );// dead bodies hitting the ground (animation events) + PRECACHE_SOUND( "common/bodydrop4.wav" ); + + g_Language = (int)CVAR_GET_FLOAT( "sv_language" ); + if ( g_Language == LANGUAGE_GERMAN ) + { + PRECACHE_MODEL( "models/germangibs.mdl" ); + } + else + { + PRECACHE_MODEL( "models/hgibs.mdl" ); + PRECACHE_MODEL( "models/agibs.mdl" ); + } + + PRECACHE_SOUND ("weapons/ric1.wav"); + PRECACHE_SOUND ("weapons/ric2.wav"); + PRECACHE_SOUND ("weapons/ric3.wav"); + PRECACHE_SOUND ("weapons/ric4.wav"); + PRECACHE_SOUND ("weapons/ric5.wav"); +// +// Setup light animation tables. 'a' is total darkness, 'z' is maxbright. +// + + // 0 normal + LIGHT_STYLE(0, "m"); + + // 1 FLICKER (first variety) + LIGHT_STYLE(1, "mmnmmommommnonmmonqnmmo"); + + // 2 SLOW STRONG PULSE + LIGHT_STYLE(2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); + + // 3 CANDLE (first variety) + LIGHT_STYLE(3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"); + + // 4 FAST STROBE + LIGHT_STYLE(4, "mamamamamama"); + + // 5 GENTLE PULSE 1 + LIGHT_STYLE(5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj"); + + // 6 FLICKER (second variety) + LIGHT_STYLE(6, "nmonqnmomnmomomno"); + + // 7 CANDLE (second variety) + LIGHT_STYLE(7, "mmmaaaabcdefgmmmmaaaammmaamm"); + + // 8 CANDLE (third variety) + LIGHT_STYLE(8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"); + + // 9 SLOW STROBE (fourth variety) + LIGHT_STYLE(9, "aaaaaaaazzzzzzzz"); + + // 10 FLUORESCENT FLICKER + LIGHT_STYLE(10, "mmamammmmammamamaaamammma"); + + // 11 SLOW PULSE NOT FADE TO BLACK + LIGHT_STYLE(11, "abcdefghijklmnopqrrqponmlkjihgfedcba"); + + // 12 UNDERWATER LIGHT MUTATION + // this light only distorts the lightmap - no contribution + // is made to the brightness of affected surfaces + LIGHT_STYLE(12, "mmnnmmnnnmmnn"); + + // styles 32-62 are assigned by the light program for switchable lights + + // 63 testing + LIGHT_STYLE(63, "a"); + + for ( int i = 0; i < ARRAYSIZE(gDecals); i++ ) + gDecals[i].index = DECAL_INDEX( gDecals[i].name ); + +// init the WorldGraph. + WorldGraph.InitGraph(); + +// make sure the .NOD file is newer than the .BSP file. + if ( !WorldGraph.CheckNODFile ( ( char * )STRING( gpGlobals->mapname ) ) ) + {// NOD file is not present, or is older than the BSP file. + WorldGraph.AllocNodes (); + } + else + {// Load the node graph for this level + if ( !WorldGraph.FLoadGraph ( (char *)STRING( gpGlobals->mapname ) ) ) + {// couldn't load, so alloc and prepare to build a graph. + ALERT ( at_console, "*Error opening .NOD file\n" ); + WorldGraph.AllocNodes (); + } + else + { + ALERT ( at_console, "\n*Graph Loaded!\n" ); + } + } + + if ( pev->speed > 0 ) + CVAR_SET_FLOAT( "sv_zmax", pev->speed ); + else + CVAR_SET_FLOAT( "sv_zmax", 4096 ); + + // QUAKECLASSIC: No Fades + /* + if ( pev->netname ) + { + ALERT( at_aiconsole, "Chapter title: %s\n", STRING(pev->netname) ); + CBaseEntity *pEntity = CBaseEntity::Create( "env_message", g_vecZero, g_vecZero, NULL ); + if ( pEntity ) + { + pEntity->SetThink( SUB_CallUseToggle ); + pEntity->pev->message = pev->netname; + pev->netname = 0; + pEntity->pev->nextthink = gpGlobals->time + 0.3; + pEntity->pev->spawnflags = SF_MESSAGE_ONCE; + } + } + */ + + // QUAKECLASSIC: No Darkness + /* + if ( pev->spawnflags & SF_WORLD_DARK ) + CVAR_SET_FLOAT( "v_dark", 1.0 ); + else + CVAR_SET_FLOAT( "v_dark", 0.0 ); + */ + + if ( pev->spawnflags & SF_WORLD_TITLE ) + gDisplayTitle = TRUE; // display the game title if this key is set + else + gDisplayTitle = FALSE; + + if ( pev->spawnflags & SF_WORLD_FORCETEAM ) + { + CVAR_SET_FLOAT( "mp_defaultteam", 1 ); + } + else + { + CVAR_SET_FLOAT( "mp_defaultteam", 0 ); + } +} + + +// +// Just to ignore the "wad" field. +// +void CWorld :: KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "skyname") ) + { + // Sent over net now. + CVAR_SET_STRING( "sv_skyname", pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "sounds") ) + { + gpGlobals->cdAudioTrack = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "WaveHeight") ) + { + // Sent over net now. + pev->scale = atof(pkvd->szValue) * (1.0/8.0); + pkvd->fHandled = TRUE; + CVAR_SET_FLOAT( "sv_wateramp", pev->scale ); + } + else if ( FStrEq(pkvd->szKeyName, "MaxRange") ) + { + pev->speed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "chaptertitle") ) + { + pev->netname = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "startdark") ) + { + // UNDONE: This is a gross hack!!! The CVAR is NOT sent over the client/sever link + // but it will work for single player + int flag = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + if ( flag ) + pev->spawnflags |= SF_WORLD_DARK; + } + else if ( FStrEq(pkvd->szKeyName, "newunit") ) + { + // Single player only. Clear save directory if set + if ( atoi(pkvd->szValue) ) + CVAR_SET_FLOAT( "sv_newunit", 1 ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "gametitle") ) + { + if ( atoi(pkvd->szValue) ) + pev->spawnflags |= SF_WORLD_TITLE; + + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "mapteams") ) + { + pev->team = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "defaultteam") ) + { + if ( atoi(pkvd->szValue) ) + { + pev->spawnflags |= SF_WORLD_FORCETEAM; + } + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} diff --git a/dmc/pm_shared/pm_debug.c b/dmc/pm_shared/pm_debug.c new file mode 100644 index 0000000..4d3a202 --- /dev/null +++ b/dmc/pm_shared/pm_debug.c @@ -0,0 +1,296 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "mathlib.h" +#include "const.h" +#include "usercmd.h" +#include "pm_defs.h" +#include "pm_shared.h" +#include "pm_movevars.h" +#include "pm_debug.h" + +#include + +#pragma warning(disable : 4244) +#pragma warning(disable : 4305) + +extern playermove_t *pmove; + +// Expand debugging BBOX particle hulls by this many units. +#define BOX_GAP 0.0f + +static int PM_boxpnt[6][4] = +{ + { 0, 4, 6, 2 }, // +X + { 0, 1, 5, 4 }, // +Y + { 0, 2, 3, 1 }, // +Z + { 7, 5, 1, 3 }, // -X + { 7, 3, 2, 6 }, // -Y + { 7, 6, 4, 5 }, // -Z +}; + +void PM_ShowClipBox( void ) +{ +#if defined( _DEBUG ) + if ( !pmove->runfuncs ) + return; + + // More debugging, draw the particle bbox for player and for the entity we are looking directly at. + // aslo prints entity info to the console overlay. + if ( !pmove->server ) + return; + + // Draw entity in center of view + // Also draws the normal to the clip plane that intersects our movement ray. Leaves a particle + // trail at the intersection point. + PM_ViewEntity(); + + // Show our BBOX in particles. + //PM_DrawBBox( pmove->player_mins[pmove->usehull], pmove->player_maxs[pmove->usehull], pmove->origin, 132, 0.1 ); +/* + { + int i; + for ( i = 0; i < pmove->numphysent; i++ ) + { + if ( pmove->physents[ i ].info >= 1 && pmove->physents[ i ].info <= 4 ) + { + PM_DrawBBox( pmove->player_mins[pmove->usehull], pmove->player_maxs[pmove->usehull], pmove->physents[i].origin, 132, 0.1 ); + } + } + } +*/ +#endif +} + +/* +=============== +PM_ParticleLine(vec3_t start, vec3_t end, int color, float life) + +================ +*/ +void PM_ParticleLine(vec3_t start, vec3_t end, int pcolor, float life, float vert) +{ + float linestep = 2.0f; + float curdist; + float len; + vec3_t curpos; + vec3_t diff; + int i; + // Determine distance; + + VectorSubtract(end, start, diff); + + len = VectorNormalize(diff); + + curdist = 0; + while (curdist <= len) + { + for (i = 0; i < 3; i++) + curpos[i] = start[i] + curdist * diff[i]; + + pmove->PM_Particle( curpos, pcolor, life, 0, vert); + curdist += linestep; + } + +} + +/* +================ +PM_DrawRectangle(vec3_t tl, vec3_t br) + +================ +*/ +void PM_DrawRectangle(vec3_t tl, vec3_t bl, vec3_t tr, vec3_t br, int pcolor, float life) +{ + PM_ParticleLine(tl, bl, pcolor, life, 0); + PM_ParticleLine(bl, br, pcolor, life, 0); + PM_ParticleLine(br, tr, pcolor, life, 0); + PM_ParticleLine(tr, tl, pcolor, life, 0); +} + +/* +================ +PM_DrawPhysEntBBox(int num) + +================ +*/ +void PM_DrawPhysEntBBox(int num, int pcolor, float life) +{ + physent_t *pe; + vec3_t org; + int j; + vec3_t tmp; + vec3_t p[8]; + float gap = BOX_GAP; + vec3_t modelmins, modelmaxs; + + if (num >= pmove->numphysent || + num <= 0) + return; + + pe = &pmove->physents[num]; + + if (pe->model) + { + VectorCopy(pe->origin, org); + + pmove->PM_GetModelBounds( pe->model, modelmins, modelmaxs ); + for (j = 0; j < 8; j++) + { + tmp[0] = (j & 1) ? modelmins[0] - gap : modelmaxs[0] + gap; + tmp[1] = (j & 2) ? modelmins[1] - gap : modelmaxs[1] + gap; + tmp[2] = (j & 4) ? modelmins[2] - gap : modelmaxs[2] + gap; + + VectorCopy(tmp, p[j]); + } + + // If the bbox should be rotated, do that + if (pe->angles[0] || pe->angles[1] || pe->angles[2]) + { + vec3_t forward, right, up; + + AngleVectorsTranspose(pe->angles, forward, right, up); + for (j = 0; j < 8; j++) + { + VectorCopy(p[j], tmp); + p[j][0] = DotProduct ( tmp, forward ); + p[j][1] = DotProduct ( tmp, right ); + p[j][2] = DotProduct ( tmp, up ); + } + } + + // Offset by entity origin, if any. + for (j = 0; j < 8; j++) + VectorAdd(p[j], org, p[j]); + + for (j = 0; j < 6; j++) + { + PM_DrawRectangle( + p[PM_boxpnt[j][1]], + p[PM_boxpnt[j][0]], + p[PM_boxpnt[j][2]], + p[PM_boxpnt[j][3]], + pcolor, life); + } + } + else + { + for (j = 0; j < 8; j++) + { + tmp[0] = (j & 1) ? pe->mins[0] : pe->maxs[0]; + tmp[1] = (j & 2) ? pe->mins[1] : pe->maxs[1]; + tmp[2] = (j & 4) ? pe->mins[2] : pe->maxs[2]; + + VectorAdd(tmp, pe->origin, tmp); + VectorCopy(tmp, p[j]); + } + + for (j = 0; j < 6; j++) + { + PM_DrawRectangle( + p[PM_boxpnt[j][1]], + p[PM_boxpnt[j][0]], + p[PM_boxpnt[j][2]], + p[PM_boxpnt[j][3]], + pcolor, life); + } + + } +} + +/* +================ +PM_DrawBBox(vec3_t mins, vec3_t maxs, vec3_t origin, int pcolor, float life) + +================ +*/ +void PM_DrawBBox(vec3_t mins, vec3_t maxs, vec3_t origin, int pcolor, float life) +{ + int j; + + vec3_t tmp; + vec3_t p[8]; + float gap = BOX_GAP; + + for (j = 0; j < 8; j++) + { + tmp[0] = (j & 1) ? mins[0] - gap : maxs[0] + gap; + tmp[1] = (j & 2) ? mins[1] - gap : maxs[1] + gap ; + tmp[2] = (j & 4) ? mins[2] - gap : maxs[2] + gap ; + + VectorAdd(tmp, origin, tmp); + VectorCopy(tmp, p[j]); + } + + for (j = 0; j < 6; j++) + { + PM_DrawRectangle( + p[PM_boxpnt[j][1]], + p[PM_boxpnt[j][0]], + p[PM_boxpnt[j][2]], + p[PM_boxpnt[j][3]], + pcolor, life); + } +} + + +#ifndef DEDICATED + +/* +================ +PM_ViewEntity + +Shows a particle trail from player to entity in crosshair. +Shows particles at that entities bbox + +Tries to shoot a ray out by about 128 units. +================ +*/ +void PM_ViewEntity( void ) +{ + vec3_t forward, right, up; + float raydist = 256.0f; + vec3_t origin; + vec3_t end; + int i; + pmtrace_t trace; + int pcolor = 77; + float fup; + +#if 0 + if ( !pm_showclip.value ) + return; +#endif + + AngleVectors (pmove->angles, forward, right, up); // Determine movement angles + + VectorCopy( pmove->origin, origin); + + fup = 0.5*( pmove->player_mins[pmove->usehull][2] + pmove->player_maxs[pmove->usehull][2] ); + fup += pmove->view_ofs[2]; + fup -= 4; + + for (i = 0; i < 3; i++) + { + end[i] = origin[i] + raydist * forward[i]; + } + + trace = pmove->PM_PlayerTrace( origin, end, PM_STUDIO_BOX, -1 ); + + if (trace.ent > 0) // Not the world + { + pcolor = 111; + } + + // Draw the hull or bbox. + if (trace.ent > 0) + { + PM_DrawPhysEntBBox(trace.ent, pcolor, 0.3f); + } +} + +#endif \ No newline at end of file diff --git a/dmc/pm_shared/pm_debug.h b/dmc/pm_shared/pm_debug.h new file mode 100644 index 0000000..552253f --- /dev/null +++ b/dmc/pm_shared/pm_debug.h @@ -0,0 +1,17 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef PM_DEBUG_H +#define PM_DEBUG_H +#pragma once + +void PM_ViewEntity( void ); +void PM_DrawBBox(vec3_t mins, vec3_t maxs, vec3_t origin, int pcolor, float life); +void PM_ParticleLine(vec3_t start, vec3_t end, int pcolor, float life, float vert); +void PM_ShowClipBox( void ); + +#endif // PMOVEDBG_H \ No newline at end of file diff --git a/dmc/pm_shared/pm_defs.h b/dmc/pm_shared/pm_defs.h new file mode 100644 index 0000000..a8e4045 --- /dev/null +++ b/dmc/pm_shared/pm_defs.h @@ -0,0 +1,213 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// pm_defs.h +#if !defined( PM_DEFSH ) +#define PM_DEFSH +#pragma once + +#define MAX_PHYSENTS 600 // Must have room for all entities in the world. +#define MAX_MOVEENTS 64 +#define MAX_CLIP_PLANES 5 + +#define PM_NORMAL 0x00000000 +#define PM_STUDIO_IGNORE 0x00000001 // Skip studio models +#define PM_STUDIO_BOX 0x00000002 // Use boxes for non-complex studio models (even in traceline) +#define PM_GLASS_IGNORE 0x00000004 // Ignore entities with non-normal rendermode +#define PM_WORLD_ONLY 0x00000008 // Only trace against the world + +// Values for flags parameter of PM_TraceLine +#define PM_TRACELINE_ANYVISIBLE 0 +#define PM_TRACELINE_PHYSENTSONLY 1 + +#include "archtypes.h" // DAL +#include "pm_info.h" + +// PM_PlayerTrace results. +#include "pmtrace.h" + +#if !defined ( USERCMD_H ) +#include "usercmd.h" +#endif + + +// physent_t +typedef struct physent_s +{ + char name[32]; // Name of model, or "player" or "world". + int player; + vec3_t origin; // Model's origin in world coordinates. + struct model_s *model; // only for bsp models + struct model_s *studiomodel; // SOLID_BBOX, but studio clip intersections. + vec3_t mins, maxs; // only for non-bsp models + int info; // For client or server to use to identify (index into edicts or cl_entities) + vec3_t angles; // rotated entities need this info for hull testing to work. + + int solid; // Triggers and func_door type WATER brushes are SOLID_NOT + int skin; // BSP Contents for such things like fun_door water brushes. + int rendermode; // So we can ignore glass + + // Complex collision detection. + float frame; + int sequence; + byte controller[4]; + byte blending[2]; + + int movetype; + int takedamage; + int blooddecal; + int team; + int classnumber; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; +} physent_t; + +typedef struct playermove_s playermove_t; + +struct playermove_s +{ + int player_index; // So we don't try to run the PM_CheckStuck nudging too quickly. + qboolean server; // For debugging, are we running physics code on server side? + + qboolean multiplayer; // 1 == multiplayer server + float time; // realtime on host, for reckoning duck timing + float frametime; // Duration of this frame + + vec3_t forward, right, up; // Vectors for angles + // player state + vec3_t origin; // Movement origin. + vec3_t angles; // Movement view angles. + vec3_t oldangles; // Angles before movement view angles were looked at. + vec3_t velocity; // Current movement direction. + vec3_t movedir; // For waterjumping, a forced forward velocity so we can fly over lip of ledge. + vec3_t basevelocity; // Velocity of the conveyor we are standing, e.g. + + // For ducking/dead + vec3_t view_ofs; // Our eye position. + float flDuckTime; // Time we started duck + qboolean bInDuck; // In process of ducking or ducked already? + + // For walking/falling + int flTimeStepSound; // Next time we can play a step sound + int iStepLeft; + + float flFallVelocity; + vec3_t punchangle; + + float flSwimTime; + + float flNextPrimaryAttack; + + int effects; // MUZZLE FLASH, e.g. + + int flags; // FL_ONGROUND, FL_DUCKING, etc. + int usehull; // 0 = regular player hull, 1 = ducked player hull, 2 = point hull + float gravity; // Our current gravity and friction. + float friction; + int oldbuttons; // Buttons last usercmd + float waterjumptime; // Amount of time left in jumping out of water cycle. + qboolean dead; // Are we a dead player? + int deadflag; + int spectator; // Should we use spectator physics model? + int movetype; // Our movement type, NOCLIP, WALK, FLY + + int onground; + int waterlevel; + int watertype; + int oldwaterlevel; + + char sztexturename[256]; + char chtexturetype; + + float maxspeed; + float clientmaxspeed; // Player specific maxspeed + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; + // world state + // Number of entities to clip against. + int numphysent; + physent_t physents[MAX_PHYSENTS]; + // Number of momvement entities (ladders) + int nummoveent; + // just a list of ladders + physent_t moveents[MAX_MOVEENTS]; + + // All things being rendered, for tracing against things you don't actually collide with + int numvisent; + physent_t visents[ MAX_PHYSENTS ]; + + // input to run through physics. + usercmd_t cmd; + + // Trace results for objects we collided with. + int numtouch; + pmtrace_t touchindex[MAX_PHYSENTS]; + + char physinfo[ MAX_PHYSINFO_STRING ]; // Physics info string + + struct movevars_s *movevars; + vec3_t player_mins[4]; + vec3_t player_maxs[4]; + + // Common functions + const char *(*PM_Info_ValueForKey) ( const char *s, const char *key ); + void (*PM_Particle)( vec3_t origin, int color, float life, int zpos, int zvel); + int (*PM_TestPlayerPosition) (vec3_t pos, pmtrace_t *ptrace ); + void (*Con_NPrintf)( int idx, char *fmt, ... ); + void (*Con_DPrintf)( char *fmt, ... ); + void (*Con_Printf)( char *fmt, ... ); + double (*Sys_FloatTime)( void ); + void (*PM_StuckTouch)( int hitent, pmtrace_t *ptraceresult ); + int (*PM_PointContents) (vec3_t p, int *truecontents /*filled in if this is non-null*/ ); + int (*PM_TruePointContents) (vec3_t p); + int (*PM_HullPointContents) ( struct hull_s *hull, int num, vec3_t p); + pmtrace_t (*PM_PlayerTrace) (vec3_t start, vec3_t end, int traceFlags, int ignore_pe ); + struct pmtrace_s *(*PM_TraceLine)( float *start, float *end, int flags, int usehulll, int ignore_pe ); + int32 (*RandomLong)( int32 lLow, int32 lHigh ); + float (*RandomFloat)( float flLow, float flHigh ); + int (*PM_GetModelType)( struct model_s *mod ); + void (*PM_GetModelBounds)( struct model_s *mod, vec3_t mins, vec3_t maxs ); + void *(*PM_HullForBsp)( physent_t *pe, vec3_t offset ); + float (*PM_TraceModel)( physent_t *pEnt, vec3_t start, vec3_t end, trace_t *trace ); + int (*COM_FileSize)(char *filename); + byte *(*COM_LoadFile) (char *path, int usehunk, int *pLength); + void (*COM_FreeFile) ( void *buffer ); + char *(*memfgets)( byte *pMemFile, int fileSize, int *pFilePos, char *pBuffer, int bufferSize ); + + // Functions + // Run functions for this frame? + qboolean runfuncs; + void (*PM_PlaySound) ( int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch ); + const char *(*PM_TraceTexture) ( int ground, vec3_t vstart, vec3_t vend ); + void (*PM_PlaybackEventFull) ( int flags, int clientindex, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); +}; + +#endif diff --git a/dmc/pm_shared/pm_info.h b/dmc/pm_shared/pm_info.h new file mode 100644 index 0000000..e9cc6d3 --- /dev/null +++ b/dmc/pm_shared/pm_info.h @@ -0,0 +1,15 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// Physics info string definition +#if !defined( PM_INFOH ) +#define PM_INFOH +#pragma once + +#define MAX_PHYSINFO_STRING 256 + +#endif // PM_INFOH \ No newline at end of file diff --git a/dmc/pm_shared/pm_materials.h b/dmc/pm_shared/pm_materials.h new file mode 100644 index 0000000..ad9aaac --- /dev/null +++ b/dmc/pm_shared/pm_materials.h @@ -0,0 +1,26 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( PM_MATERIALSH ) +#define PM_MATERIALSH +#pragma once + +#define CBTEXTURENAMEMAX 13 // only load first n chars of name + +#define CHAR_TEX_CONCRETE 'C' // texture types +#define CHAR_TEX_METAL 'M' +#define CHAR_TEX_DIRT 'D' +#define CHAR_TEX_VENT 'V' +#define CHAR_TEX_GRATE 'G' +#define CHAR_TEX_TILE 'T' +#define CHAR_TEX_SLOSH 'S' +#define CHAR_TEX_WOOD 'W' +#define CHAR_TEX_COMPUTER 'P' +#define CHAR_TEX_GLASS 'Y' +#define CHAR_TEX_FLESH 'F' + +#endif // !PM_MATERIALSH \ No newline at end of file diff --git a/dmc/pm_shared/pm_math.c b/dmc/pm_shared/pm_math.c new file mode 100644 index 0000000..09814f5 --- /dev/null +++ b/dmc/pm_shared/pm_math.c @@ -0,0 +1,416 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// mathlib.c -- math primitives + +#include "mathlib.h" +#include "const.h" +#include + +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 + +#pragma warning(disable : 4244) + +#ifndef DISABLE_VEC_ORIGIN +vec3_t vec3_origin = {0,0,0}; +#endif +int nanmask = 255<<23; + +float anglemod(float a) +{ + a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + +void AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + if (forward) + { + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + } + if (right) + { + right[0] = (-1*sr*sp*cy+-1*cr*-sy); + right[1] = (-1*sr*sp*sy+-1*cr*cy); + right[2] = -1*sr*cp; + } + if (up) + { + up[0] = (cr*sp*cy+-sr*-sy); + up[1] = (cr*sp*sy+-sr*cy); + up[2] = cr*cp; + } +} + +void AngleVectorsTranspose (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + if (forward) + { + forward[0] = cp*cy; + forward[1] = (sr*sp*cy+cr*-sy); + forward[2] = (cr*sp*cy+-sr*-sy); + } + if (right) + { + right[0] = cp*sy; + right[1] = (sr*sp*sy+cr*cy); + right[2] = (cr*sp*sy+-sr*cy); + } + if (up) + { + up[0] = -sp; + up[1] = sr*cp; + up[2] = cr*cp; + } +} + + +void AngleMatrix (const vec3_t angles, float (*matrix)[4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + // matrix = (YAW * PITCH) * ROLL + matrix[0][0] = cp*cy; + matrix[1][0] = cp*sy; + matrix[2][0] = -sp; + matrix[0][1] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[2][1] = sr*cp; + matrix[0][2] = (cr*sp*cy+-sr*-sy); + matrix[1][2] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + +void AngleIMatrix (const vec3_t angles, float matrix[3][4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + // matrix = (YAW * PITCH) * ROLL + matrix[0][0] = cp*cy; + matrix[0][1] = cp*sy; + matrix[0][2] = -sp; + matrix[1][0] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[1][2] = sr*cp; + matrix[2][0] = (cr*sp*cy+-sr*-sy); + matrix[2][1] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + +void NormalizeAngles( float *angles ) +{ + int i; + // Normalize angles + for ( i = 0; i < 3; i++ ) + { + if ( angles[i] > 180.0 ) + { + angles[i] -= 360.0; + } + else if ( angles[i] < -180.0 ) + { + angles[i] += 360.0; + } + } +} + +/* +=================== +InterpolateAngles + +Interpolate Euler angles. +FIXME: Use Quaternions to avoid discontinuities +Frac is 0.0 to 1.0 ( i.e., should probably be clamped, but doesn't have to be ) +=================== +*/ +void InterpolateAngles( float *start, float *end, float *output, float frac ) +{ + int i; + float ang1, ang2; + float d; + + NormalizeAngles( start ); + NormalizeAngles( end ); + + for ( i = 0 ; i < 3 ; i++ ) + { + ang1 = start[i]; + ang2 = end[i]; + + d = ang2 - ang1; + if ( d > 180 ) + { + d -= 360; + } + else if ( d < -180 ) + { + d += 360; + } + + output[i] = ang1 + d * frac; + } + + NormalizeAngles( output ); +} + + +/* +=================== +AngleBetweenVectors + +=================== +*/ +float AngleBetweenVectors( const vec3_t v1, const vec3_t v2 ) +{ + float angle; + float l1 = Length( v1 ); + float l2 = Length( v2 ); + + if ( !l1 || !l2 ) + return 0.0f; + + angle = acos( DotProduct( v1, v2 ) ) / (l1*l2); + angle = ( angle * 180.0f ) / M_PI; + + return angle; +} + +void VectorTransform (const vec3_t in1, float in2[3][4], vec3_t out) +{ + out[0] = DotProduct(in1, in2[0]) + in2[0][3]; + out[1] = DotProduct(in1, in2[1]) + in2[1][3]; + out[2] = DotProduct(in1, in2[2]) + in2[2][3]; +} + + +int VectorCompare (const vec3_t v1, const vec3_t v2) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (v1[i] != v2[i]) + return 0; + + return 1; +} + +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 (vec3_t v1, vec3_t v2) +{ + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]-vecb[0]; + out[1] = veca[1]-vecb[1]; + out[2] = veca[2]-vecb[2]; +} + +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]+vecb[0]; + out[1] = veca[1]+vecb[1]; + out[2] = veca[2]+vecb[2]; +} + +void _VectorCopy (vec3_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +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]; +} + +double sqrt(double x); + +float Length(const vec3_t v) +{ + int i; + float length = 0.0f; + + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); // FIXME + + return length; +} + +float Distance(const vec3_t v1, const vec3_t v2) +{ + vec3_t d; + VectorSubtract(v2,v1,d); + return Length(d); +} + +float VectorNormalize (vec3_t v) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; + +} + +void VectorInverse (vec3_t v) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[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; +} + + +int Q_log2(int val) +{ + int answer=0; + while (val>>=1) + answer++; + return answer; +} + +void VectorMatrix( vec3_t forward, vec3_t right, vec3_t up) +{ + vec3_t tmp; + + if (forward[0] == 0 && forward[1] == 0) + { + right[0] = 1; + right[1] = 0; + right[2] = 0; + up[0] = -forward[2]; + up[1] = 0; + up[2] = 0; + return; + } + + tmp[0] = 0; tmp[1] = 0; tmp[2] = 1.0; + CrossProduct( forward, tmp, right ); + VectorNormalize( right ); + CrossProduct( right, forward, up ); + VectorNormalize( up ); +} + + +void VectorAngles( const vec3_t forward, vec3_t angles ) +{ + float tmp, yaw, pitch; + + if (forward[1] == 0 && forward[0] == 0) + { + yaw = 0; + if (forward[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + yaw = (atan2(forward[1], forward[0]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + + tmp = sqrt (forward[0]*forward[0] + forward[1]*forward[1]); + pitch = (atan2(forward[2], tmp) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + angles[0] = pitch; + angles[1] = yaw; + angles[2] = 0; +} diff --git a/dmc/pm_shared/pm_movevars.h b/dmc/pm_shared/pm_movevars.h new file mode 100644 index 0000000..f4f46ca --- /dev/null +++ b/dmc/pm_shared/pm_movevars.h @@ -0,0 +1,47 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// pm_movevars.h +#if !defined( PM_MOVEVARSH ) +#define PM_MOVEVARSH + +// movevars_t // Physics variables. +typedef struct movevars_s movevars_t; + +struct movevars_s +{ + float gravity; // Gravity for map + float stopspeed; // Deceleration when not moving + float maxspeed; // Max allowed speed + float spectatormaxspeed; + float accelerate; // Acceleration factor + float airaccelerate; // Same for when in open air + float wateraccelerate; // Same for when in water + float friction; + float edgefriction; // Extra friction near dropofs + float waterfriction; // Less in water + float entgravity; // 1.0 + float bounce; // Wall bounce value. 1.0 + float stepsize; // sv_stepsize; + float maxvelocity; // maximum server velocity. + float zmax; // Max z-buffer range (for GL) + float waveHeight; // Water wave height (for GL) + qboolean footsteps; // Play footstep sounds + char skyName[32]; // Name of the sky map + float rollangle; + float rollspeed; + float skycolor_r; // Sky color + float skycolor_g; // + float skycolor_b; // + float skyvec_x; // Sky vector + float skyvec_y; // + float skyvec_z; // +}; + +extern movevars_t movevars; + +#endif \ No newline at end of file diff --git a/dmc/pm_shared/pm_shared.c b/dmc/pm_shared/pm_shared.c new file mode 100644 index 0000000..bf1c491 --- /dev/null +++ b/dmc/pm_shared/pm_shared.c @@ -0,0 +1,2798 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include +#include "mathlib.h" +#include "const.h" +#include "usercmd.h" +#include "pm_defs.h" +#include "pm_shared.h" +#include "pm_movevars.h" +#include "pm_debug.h" +#include // NULL +#include // sqrt +#include // strcpy +#include // atoi +#include // isspace + +#ifdef CLIENT_DLL + // Spectator Mode + int iJumpSpectator; +#ifndef DISABLE_JUMP_ORIGIN + float vJumpOrigin[3]; + float vJumpAngles[3]; +#else + extern float vJumpOrigin[3]; + extern float vJumpAngles[3]; +#endif +#endif + +static int pm_shared_initialized = 0; + +#pragma warning( disable : 4305 ) + +typedef enum {mod_brush, mod_sprite, mod_alias, mod_studio} modtype_t; + +playermove_t *pmove = NULL; + +typedef struct +{ + int planenum; + short children[2]; // negative numbers are contents +} dclipnode_t; + +typedef struct mplane_s +{ + vec3_t normal; // surface normal + float dist; // closest appoach to origin + byte type; // for texture axis selection and fast side tests + byte signbits; // signx + signy<<1 + signz<<1 + byte pad[2]; +} mplane_t; + +typedef struct hull_s +{ + dclipnode_t *clipnodes; + mplane_t *planes; + int firstclipnode; + int lastclipnode; + vec3_t clip_mins; + vec3_t clip_maxs; +} hull_t; + +// Ducking time +#define TIME_TO_DUCK 0.4 +#define VEC_DUCK_HULL_MIN -18 +#define VEC_DUCK_HULL_MAX 18 +#define VEC_DUCK_VIEW 12 +#define PM_DEAD_VIEWHEIGHT -8 +#define MAX_CLIMB_SPEED 200 +#define STUCK_MOVEUP 1 +#define STUCK_MOVEDOWN -1 +#define VEC_HULL_MIN -36 +#define VEC_HULL_MAX 36 +#define VEC_VIEW 28 +#define STOP_EPSILON 0.1 + +#define CTEXTURESMAX 512 // max number of textures loaded +#define CBTEXTURENAMEMAX 13 // only load first n chars of name + +#define CHAR_TEX_CONCRETE 'C' // texture types +#define CHAR_TEX_METAL 'M' +#define CHAR_TEX_DIRT 'D' +#define CHAR_TEX_VENT 'V' +#define CHAR_TEX_GRATE 'G' +#define CHAR_TEX_TILE 'T' +#define CHAR_TEX_SLOSH 'S' +#define CHAR_TEX_WOOD 'W' +#define CHAR_TEX_COMPUTER 'P' +#define CHAR_TEX_GLASS 'Y' +#define CHAR_TEX_FLESH 'F' + +#define STEP_CONCRETE 0 // default step sound +#define STEP_METAL 1 // metal floor +#define STEP_DIRT 2 // dirt, sand, rock +#define STEP_VENT 3 // ventillation duct +#define STEP_GRATE 4 // metal grating +#define STEP_TILE 5 // floor tiles +#define STEP_SLOSH 6 // shallow liquid puddle +#define STEP_WADE 7 // wading in liquid +#define STEP_LADDER 8 // climbing ladder + +#define PLAYER_FATAL_FALL_SPEED 1024// approx 60 feet +#define PLAYER_MAX_SAFE_FALL_SPEED 580// approx 20 feet +#define DAMAGE_FOR_FALL_SPEED (float) 100 / ( PLAYER_FATAL_FALL_SPEED - PLAYER_MAX_SAFE_FALL_SPEED )// damage per unit per second. +#define PLAYER_MIN_BOUNCE_SPEED 200 +#define PLAYER_FALL_PUNCH_THRESHHOLD (float)350 // won't punch player's screen/make scrape noise unless player falling at least this fast. + +#define PLAYER_LONGJUMP_SPEED 350 // how fast we longjump + +// double to float warning +#pragma warning(disable : 4244) +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) < (b)) ? (a) : (b)) +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 + +#define MAX_CLIENTS 32 + +#define CONTENTS_CURRENT_0 -9 +#define CONTENTS_CURRENT_90 -10 +#define CONTENTS_CURRENT_180 -11 +#define CONTENTS_CURRENT_270 -12 +#define CONTENTS_CURRENT_UP -13 +#define CONTENTS_CURRENT_DOWN -14 + +#define CONTENTS_TRANSLUCENT -15 + +static vec3_t rgv3tStuckTable[54]; +static int rgStuckLast[MAX_CLIENTS][2]; + +// Texture names +static int gcTextures = 0; +static char grgszTextureName[CTEXTURESMAX][CBTEXTURENAMEMAX]; +static char grgchTextureType[CTEXTURESMAX]; + +int g_onladder = 0; + +void PM_SwapTextures( int i, int j ) +{ + char chTemp; + char szTemp[ CBTEXTURENAMEMAX ]; + + strcpy( szTemp, grgszTextureName[ i ] ); + chTemp = grgchTextureType[ i ]; + + strcpy( grgszTextureName[ i ], grgszTextureName[ j ] ); + grgchTextureType[ i ] = grgchTextureType[ j ]; + + strcpy( grgszTextureName[ j ], szTemp ); + grgchTextureType[ j ] = chTemp; +} + +void PM_SortTextures( void ) +{ + // Bubble sort, yuck, but this only occurs at startup and it's only 512 elements... + // + int i, j; + + for ( i = 0 ; i < gcTextures; i++ ) + { + for ( j = i + 1; j < gcTextures; j++ ) + { + if ( stricmp( grgszTextureName[ i ], grgszTextureName[ j ] ) > 0 ) + { + // Swap + // + PM_SwapTextures( i, j ); + } + } + } +} + +void PM_InitTextureTypes() +{ + char buffer[512]; + int i, j; + byte *pMemFile; + int fileSize, filePos; + static qboolean bTextureTypeInit = false; + + if ( bTextureTypeInit ) + return; + + memset(&(grgszTextureName[0][0]), 0, CTEXTURESMAX * CBTEXTURENAMEMAX); + memset(grgchTextureType, 0, CTEXTURESMAX); + + gcTextures = 0; + memset(buffer, 0, 512); + + fileSize = pmove->COM_FileSize( "sound/materials.txt" ); + pMemFile = pmove->COM_LoadFile( "sound/materials.txt", 5, NULL ); + if ( !pMemFile ) + return; + + filePos = 0; + // for each line in the file... + while ( pmove->memfgets( pMemFile, fileSize, &filePos, buffer, 511 ) != NULL && (gcTextures < CTEXTURESMAX) ) + { + // skip whitespace + i = 0; + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // skip comment lines + if (buffer[i] == '/' || !isalpha(buffer[i])) + continue; + + // get texture type + grgchTextureType[gcTextures] = toupper(buffer[i++]); + + // skip whitespace + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // get sentence name + j = i; + while (buffer[j] && !isspace(buffer[j])) + j++; + + if (!buffer[j]) + continue; + + // null-terminate name and save in sentences array + j = min (j, CBTEXTURENAMEMAX-1+i); + buffer[j] = 0; + strcpy(&(grgszTextureName[gcTextures++][0]), &(buffer[i])); + } + + // Must use engine to free since we are in a .dll + pmove->COM_FreeFile ( pMemFile ); + + PM_SortTextures(); + + bTextureTypeInit = true; +} + +char PM_FindTextureType( char *name ) +{ + int left, right, pivot; + int val; + + assert( pm_shared_initialized ); + + left = 0; + right = gcTextures - 1; + + while ( left <= right ) + { + pivot = ( left + right ) / 2; + + val = strnicmp( name, grgszTextureName[ pivot ], CBTEXTURENAMEMAX-1 ); + if ( val == 0 ) + { + return grgchTextureType[ pivot ]; + } + else if ( val > 0 ) + { + left = pivot + 1; + } + else if ( val < 0 ) + { + right = pivot - 1; + } + } + + return CHAR_TEX_CONCRETE; +} +int PM_MapTextureTypeStepType(char chTextureType) +{ + switch (chTextureType) + { + default: + case CHAR_TEX_CONCRETE: return STEP_CONCRETE; + case CHAR_TEX_METAL: return STEP_METAL; + case CHAR_TEX_DIRT: return STEP_DIRT; + case CHAR_TEX_VENT: return STEP_VENT; + case CHAR_TEX_GRATE: return STEP_GRATE; + case CHAR_TEX_TILE: return STEP_TILE; + case CHAR_TEX_SLOSH: return STEP_SLOSH; + } +} + +/* +==================== +PM_CatagorizeTextureType + +Determine texture info for the texture we are standing on. +==================== +*/ +void PM_CatagorizeTextureType( void ) +{ + vec3_t start, end; + const char *pTextureName; + + VectorCopy( pmove->origin, start ); + VectorCopy( pmove->origin, end ); + + // Straight down + end[2] -= 64; + + // Fill in default values, just in case. + pmove->sztexturename[0] = '\0'; + pmove->chtexturetype = CHAR_TEX_CONCRETE; + + pTextureName = pmove->PM_TraceTexture( pmove->onground, start, end ); + if ( !pTextureName ) + return; + + // strip leading '-0' or '+0~' or '{' or '!' + if (*pTextureName == '-' || *pTextureName == '+') + pTextureName += 2; + + if (*pTextureName == '{' || *pTextureName == '!' || *pTextureName == '~' || *pTextureName == ' ') + pTextureName++; + // '}}' + + strcpy( pmove->sztexturename, pTextureName); + pmove->sztexturename[ CBTEXTURENAMEMAX - 1 ] = 0; + + // get texture type + pmove->chtexturetype = PM_FindTextureType( pmove->sztexturename ); +} + +void PM_UpdateStepSound( void ) +{ + int fWalking; + float fvol; + vec3_t knee; + vec3_t feet; + vec3_t center; + float height; + float speed; + float velrun; + float velwalk; + float flduck; + int fLadder; + int step; + + if ( pmove->flTimeStepSound > 0 ) + return; + + if ( pmove->flags & FL_FROZEN ) + return; + + PM_CatagorizeTextureType(); + + speed = Length( pmove->velocity ); + + // determine if we are on a ladder + fLadder = ( pmove->movetype == MOVETYPE_FLY );// IsOnLadder(); + + // UNDONE: need defined numbers for run, walk, crouch, crouch run velocities!!!! + if ( ( pmove->flags & FL_DUCKING) || fLadder ) + { + velwalk = 60; // These constants should be based on cl_movespeedkey * cl_forwardspeed somehow + velrun = 80; // UNDONE: Move walking to server + flduck = 100; + } + else + { + velwalk = 120; + velrun = 210; + flduck = 0; + } + + // If we're on a ladder or on the ground, and we're moving fast enough, + // play step sound. Also, if pmove->flTimeStepSound is zero, get the new + // sound right away - we just started moving in new level. + if ( (fLadder || ( pmove->onground != -1 ) ) && + ( Length( pmove->velocity ) > 0.0 ) && + ( speed >= velwalk || !pmove->flTimeStepSound ) ) + { + fWalking = speed < velrun; + + VectorCopy( pmove->origin, center ); + VectorCopy( pmove->origin, knee ); + VectorCopy( pmove->origin, feet ); + + height = pmove->player_maxs[ pmove->usehull ][ 2 ] - pmove->player_mins[ pmove->usehull ][ 2 ]; + + knee[2] = pmove->origin[2] - 0.3 * height; + feet[2] = pmove->origin[2] - 0.5 * height; + + if ( pmove->PM_PointContents ( knee, NULL ) == CONTENTS_WATER ) + { + step = STEP_WADE; + fvol = 0.65; + pmove->flTimeStepSound = 600; + } + else if ( pmove->PM_PointContents ( feet, NULL ) == CONTENTS_WATER ) + { + step = STEP_SLOSH; + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + } + else + { + // find texture under player, if different from current texture, + // get material type + step = PM_MapTextureTypeStepType( pmove->chtexturetype ); + + switch ( pmove->chtexturetype ) + { + default: + case CHAR_TEX_CONCRETE: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_METAL: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_DIRT: + fvol = fWalking ? 0.25 : 0.55; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_VENT: + fvol = fWalking ? 0.4 : 0.7; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_GRATE: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_TILE: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_SLOSH: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + } + } + + pmove->flTimeStepSound += flduck; // slower step time if ducking + } +} + +/* +================ +PM_AddToTouched + +Add's the trace result to touch list, if contact is not already in list. +================ +*/ +qboolean PM_AddToTouched(pmtrace_t tr, vec3_t impactvelocity) +{ + int i; + + for (i = 0; i < pmove->numtouch; i++) + { + if (pmove->touchindex[i].ent == tr.ent) + break; + } + if (i != pmove->numtouch) // Already in list. + return false; + + VectorCopy( impactvelocity, tr.deltavelocity ); + + if (pmove->numtouch >= MAX_PHYSENTS) + pmove->Con_DPrintf("Too many entities were touched!\n"); + + pmove->touchindex[pmove->numtouch++] = tr; + return true; +} + +/* +================ +PM_CheckVelocity + +See if the player has a bogus velocity value. +================ +*/ +void PM_CheckVelocity () +{ + int i; + +// +// bound velocity +// + for (i=0 ; i<3 ; i++) + { + // See if it's bogus. + if (IS_NAN(pmove->velocity[i])) + { + pmove->Con_Printf ("PM Got a NaN velocity %i\n", i); + pmove->velocity[i] = 0; + } + if (IS_NAN(pmove->origin[i])) + { + pmove->Con_Printf ("PM Got a NaN origin on %i\n", i); + pmove->origin[i] = 0; + } + + // Bound it. + if (pmove->velocity[i] > pmove->movevars->maxvelocity) + { + pmove->Con_DPrintf ("PM Got a velocity too high on %i\n", i); + pmove->velocity[i] = pmove->movevars->maxvelocity; + } + else if (pmove->velocity[i] < -pmove->movevars->maxvelocity) + { + pmove->Con_DPrintf ("PM Got a velocity too low on %i\n", i); + pmove->velocity[i] = -pmove->movevars->maxvelocity; + } + } +} + +/* +================== +PM_ClipVelocity + +Slide off of the impacting object +returns the blocked flags: +0x01 == floor +0x02 == step / wall +================== +*/ +int PM_ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce) +{ + float backoff; + float change; + float angle; + int i, blocked; + + angle = normal[ 2 ]; + + blocked = 0x00; // Assume unblocked. + if (angle > 0) // If the plane that is blocking us has a positive z component, then assume it's a floor. + blocked |= 0x01; // + if (!angle) // If the plane has no Z, it is vertical (wall/step) + blocked |= 0x02; // + + // Determine how far along plane to slide based on incoming direction. + // Scale by overbounce factor. + backoff = DotProduct (in, normal) * overbounce; + + for (i=0 ; i<3 ; i++) + { + change = normal[i]*backoff; + out[i] = in[i] - change; + // If out velocity is too small, zero it out. + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; + } + + // Return blocking flags. + return blocked; +} + +void PM_AddCorrectGravity () +{ + float ent_gravity; + + if ( pmove->waterjumptime ) + return; + + if (pmove->gravity) + ent_gravity = pmove->gravity; + else + ent_gravity = 1.0; + + // Add gravity so they'll be in the correct position during movement + // yes, this 0.5 looks wrong, but it's not. + pmove->velocity[2] -= (ent_gravity * pmove->movevars->gravity * 0.5 * pmove->frametime ); + pmove->velocity[2] += pmove->basevelocity[2] * pmove->frametime; + pmove->basevelocity[2] = 0; + + PM_CheckVelocity(); +} + + +void PM_FixupGravityVelocity () +{ + float ent_gravity; + + if ( pmove->waterjumptime ) + return; + + if (pmove->gravity) + ent_gravity = pmove->gravity; + else + ent_gravity = 1.0; + + // Get the correct velocity for the end of the dt + pmove->velocity[2] -= (ent_gravity * pmove->movevars->gravity * pmove->frametime * 0.5 ); + + PM_CheckVelocity(); +} + +/* +============ +PM_FlyMove + +The basic solid body movement clip that slides along multiple planes +============ +*/ +int PM_FlyMove (void) +{ + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity, original_velocity; + vec3_t new_velocity; + int i, j; + pmtrace_t trace; + vec3_t end; + float time_left, allFraction; + int blocked; + + numbumps = 4; // Bump up to four times + + blocked = 0; // Assume not blocked + numplanes = 0; // and not sliding along any planes + VectorCopy (pmove->velocity, original_velocity); // Store original velocity + VectorCopy (pmove->velocity, primal_velocity); + + allFraction = 0; + time_left = pmove->frametime; // Total time for this movement operation. + + for (bumpcount=0 ; bumpcountvelocity[0] && !pmove->velocity[1] && !pmove->velocity[2]) + break; + + // Assume we can move all the way from the current origin to the + // end point. + for (i=0 ; i<3 ; i++) + end[i] = pmove->origin[i] + time_left * pmove->velocity[i]; + + // See if we can make it from origin to end point. + trace = pmove->PM_PlayerTrace (pmove->origin, end, PM_NORMAL, -1 ); + + allFraction += trace.fraction; + // If we started in a solid object, or we were in solid space + // the whole way, zero out our velocity and return that we + // are blocked by floor and wall. + if (trace.allsolid) + { // entity is trapped in another solid + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf("Trapped 4\n"); + return 4; + } + + // If we moved some portion of the total distance, then + // copy the end position into the pmove->origin and + // zero the plane counter. + if (trace.fraction > 0) + { // actually covered some distance + VectorCopy (trace.endpos, pmove->origin); + VectorCopy (pmove->velocity, original_velocity); + numplanes = 0; + } + + // If we covered the entire distance, we are done + // and can return. + if (trace.fraction == 1) + break; // moved the entire distance + + //if (!trace.ent) + // Sys_Error ("PM_PlayerTrace: !trace.ent"); + + // Save entity that blocked us (since fraction was < 1.0) + // for contact + // Add it if it's not already in the list!!! + PM_AddToTouched(trace, pmove->velocity); + + // If the plane we hit has a high z component in the normal, then + // it's probably a floor + if (trace.plane.normal[2] > 0.7) + { + blocked |= 1; // floor + } + // If the plane has a zero z component in the normal, then it's a + // step or wall + if (!trace.plane.normal[2]) + { + blocked |= 2; // step / wall + //Con_DPrintf("Blocked by %i\n", trace.ent); + } + + // Reduce amount of pmove->frametime left by total time left * fraction + // that we covered. + time_left -= time_left * trace.fraction; + + // Did we run out of planes to clip against? + if (numplanes >= MAX_CLIP_PLANES) + { // this shouldn't really happen + // Stop our movement if so. + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf("Too many planes 4\n"); + + break; + } + + // Set up next clipping plane + VectorCopy (trace.plane.normal, planes[numplanes]); + numplanes++; +// + +// modify original_velocity so it parallels all of the clip planes +// + if ( pmove->movetype == MOVETYPE_WALK && + ((pmove->onground == -1) || (pmove->friction != 1)) ) // relfect player velocity + { + for ( i = 0; i < numplanes; i++ ) + { + if ( planes[i][2] > 0.7 ) + {// floor or slope + PM_ClipVelocity( original_velocity, planes[i], new_velocity, 1 ); + VectorCopy( new_velocity, original_velocity ); + } + else + PM_ClipVelocity( original_velocity, planes[i], new_velocity, 1.0 + pmove->movevars->bounce * (1-pmove->friction) ); + } + + VectorCopy( new_velocity, pmove->velocity ); + VectorCopy( new_velocity, original_velocity ); + } + else + { + for (i=0 ; ivelocity, + 1); + for (j=0 ; jvelocity, planes[j]) < 0) + break; // not ok + } + if (j == numplanes) // Didn't have to clip, so we're ok + break; + } + + // Did we go all the way through plane set + if (i != numplanes) + { // go along this plane + // pmove->velocity is set in clipping call, no need to set again. + ; + } + else + { // go along the crease + if (numplanes != 2) + { + //Con_Printf ("clip velocity, numplanes == %i\n",numplanes); + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf("Trapped 4\n"); + + break; + } + CrossProduct (planes[0], planes[1], dir); + d = DotProduct (dir, pmove->velocity); + VectorScale (dir, d, pmove->velocity ); + } + + // + // if original velocity is against the original velocity, stop dead + // to avoid tiny occilations in sloping corners + // + if (DotProduct (pmove->velocity, primal_velocity) <= 0) + { + //Con_DPrintf("Back\n"); + VectorCopy (vec3_origin, pmove->velocity); + break; + } + } + } + + if ( allFraction == 0 ) + { + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf( "Don't stick\n" ); + } + + return blocked; +} + +/* +============== +PM_Accelerate +============== +*/ +void PM_Accelerate (vec3_t wishdir, float wishspeed, float accel) +{ + int i; + float addspeed, accelspeed, currentspeed; + + // Dead player's don't accelerate + if (pmove->dead) + return; + + // If waterjumping, don't accelerate + if (pmove->waterjumptime) + return; + + // See if we are changing direction a bit + currentspeed = DotProduct (pmove->velocity, wishdir); + + // Reduce wishspeed by the amount of veer. + addspeed = wishspeed - currentspeed; + + // If not going to add any speed, done. + if (addspeed <= 0) + return; + + // Determine amount of accleration. + accelspeed = accel * pmove->frametime * wishspeed * pmove->friction; + + // Cap at addspeed + if (accelspeed > addspeed) + accelspeed = addspeed; + + // Adjust velocity. + for (i=0 ; i<3 ; i++) + { + pmove->velocity[i] += accelspeed * wishdir[i]; + } +} + +/* +===================== +PM_WalkMove + +Only used by players. Moves along the ground when player is a MOVETYPE_WALK. +====================== +*/ +void PM_WalkMove () +{ + int clip; + int oldonground; + int i; + + vec3_t wishvel; + float spd; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + + vec3_t dest, start; + vec3_t original, originalvel; + vec3_t down, downvel; + float downdist, updist; + + pmtrace_t trace; + + // Copy movement amounts + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + // Zero out z components of movement vectors + pmove->forward[2] = 0; + pmove->right[2] = 0; + + VectorNormalize (pmove->forward); // Normalize remainder of vectors. + VectorNormalize (pmove->right); // + + for (i=0 ; i<2 ; i++) // Determine x and y parts of velocity + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + + wishvel[2] = 0; // Zero out z part of velocity + + VectorCopy (wishvel, wishdir); // Determine maginitude of speed of move + wishspeed = VectorNormalize(wishdir); + +// +// Clamp to server defined max speed +// + if (wishspeed > pmove->maxspeed) + { + VectorScale (wishvel, pmove->maxspeed/wishspeed, wishvel); + wishspeed = pmove->maxspeed; + } + + // Set pmove velocity + pmove->velocity[2] = 0; + PM_Accelerate (wishdir, wishspeed, pmove->movevars->accelerate); + pmove->velocity[2] = 0; + + // Add in any base velocity to the current velocity. + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity ); + + spd = Length( pmove->velocity ); + + if (spd < 1.0f) + { + VectorClear( pmove->velocity ); + return; + } + + // If we are not moving, do nothing + //if (!pmove->velocity[0] && !pmove->velocity[1] && !pmove->velocity[2]) + // return; + + oldonground = pmove->onground; + +// first try just moving to the destination + dest[0] = pmove->origin[0] + pmove->velocity[0]*pmove->frametime; + dest[1] = pmove->origin[1] + pmove->velocity[1]*pmove->frametime; + dest[2] = pmove->origin[2]; + + // first try moving directly to the next spot + VectorCopy (dest, start); + trace = pmove->PM_PlayerTrace (pmove->origin, dest, PM_NORMAL, -1 ); + // If we made it all the way, then copy trace end + // as new player position. + if (trace.fraction == 1) + { + VectorCopy (trace.endpos, pmove->origin); + return; + } + + if (oldonground == -1 && // Don't walk up stairs if not on ground. + pmove->waterlevel == 0) + return; + + if (pmove->waterjumptime) // If we are jumping out of water, don't do anything more. + return; + + // Try sliding forward both on ground and up 16 pixels + // take the move that goes farthest + VectorCopy (pmove->origin, original); // Save out original pos & + VectorCopy (pmove->velocity, originalvel); // velocity. + + // Slide move + clip = PM_FlyMove (); + + // Copy the results out + VectorCopy (pmove->origin , down); + VectorCopy (pmove->velocity, downvel); + + // Reset original values. + VectorCopy (original, pmove->origin); + + VectorCopy (originalvel, pmove->velocity); + + // Start out up one stair height + VectorCopy (pmove->origin, dest); + dest[2] += pmove->movevars->stepsize; + + trace = pmove->PM_PlayerTrace (pmove->origin, dest, PM_NORMAL, -1 ); + // If we started okay and made it part of the way at least, + // copy the results to the movement start position and then + // run another move try. + if (!trace.startsolid && !trace.allsolid) + { + VectorCopy (trace.endpos, pmove->origin); + } + +// slide move the rest of the way. + clip = PM_FlyMove (); + +// Now try going back down from the end point +// press down the stepheight + VectorCopy (pmove->origin, dest); + dest[2] -= pmove->movevars->stepsize; + + trace = pmove->PM_PlayerTrace (pmove->origin, dest, PM_NORMAL, -1 ); + + // If we are not on the ground any more then + // use the original movement attempt + if ( trace.plane.normal[2] < 0.7) + goto usedown; + // If the trace ended up in empty space, copy the end + // over to the origin. + if (!trace.startsolid && !trace.allsolid) + { + VectorCopy (trace.endpos, pmove->origin); + } + // Copy this origion to up. + VectorCopy (pmove->origin, pmove->up); + + // decide which one went farther + downdist = (down[0] - original[0])*(down[0] - original[0]) + + (down[1] - original[1])*(down[1] - original[1]); + updist = (pmove->up[0] - original[0])*(pmove->up[0] - original[0]) + + (pmove->up[1] - original[1])*(pmove->up[1] - original[1]); + + if (downdist > updist) + { +usedown: + VectorCopy (down , pmove->origin); + VectorCopy (downvel, pmove->velocity); + } else // copy z value from slide move + pmove->velocity[2] = downvel[2]; + +} + +/* +================== +PM_Friction + +Handles both ground friction and water friction +================== +*/ +void PM_Friction (void) +{ + float *vel; + float speed, newspeed, control; + float friction; + float drop; + vec3_t newvel; + + // If we are in water jump cycle, don't apply friction + if (pmove->waterjumptime) + return; + + // Get velocity + vel = pmove->velocity; + + // Calculate speed + speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1] + vel[2]*vel[2]); + + // If too slow, return + if (speed < 0.1f) + { + return; + } + + drop = 0; + +// apply ground friction + if (pmove->onground != -1) // On an entity that is the ground + { + vec3_t start, stop; + pmtrace_t trace; + + start[0] = stop[0] = pmove->origin[0] + vel[0]/speed*16; + start[1] = stop[1] = pmove->origin[1] + vel[1]/speed*16; + start[2] = pmove->origin[2] + pmove->player_mins[pmove->usehull][2]; + stop[2] = start[2] - 34; + + trace = pmove->PM_PlayerTrace (start, stop, PM_NORMAL, -1 ); + + if (trace.fraction == 1.0) + friction = pmove->movevars->friction*pmove->movevars->edgefriction; + else + friction = pmove->movevars->friction; + + // Grab friction value. + //friction = pmove->movevars->friction; + + friction *= pmove->friction; // player friction? + + // Bleed off some speed, but if we have less than the bleed + // threshhold, bleed the theshold amount. + control = (speed < pmove->movevars->stopspeed) ? + pmove->movevars->stopspeed : speed; + // Add the amount to t'he drop amount. + drop += control*friction*pmove->frametime; + } + +// apply water friction +// if (pmove->waterlevel) +// drop += speed * pmove->movevars->waterfriction * waterlevel * pmove->frametime; + +// scale the velocity + newspeed = speed - drop; + if (newspeed < 0) + newspeed = 0; + + // Determine proportion of old speed we are using. + newspeed /= speed; + + // Adjust velocity according to proportion. + newvel[0] = vel[0] * newspeed; + newvel[1] = vel[1] * newspeed; + newvel[2] = vel[2] * newspeed; + + VectorCopy( newvel, pmove->velocity ); +} + +void PM_AirAccelerate (vec3_t wishdir, float wishspeed, float accel) +{ + int i; + float addspeed, accelspeed, currentspeed, wishspd = wishspeed; + + if (pmove->dead) + return; + if (pmove->waterjumptime) + return; + + // Cap speed + //wishspd = VectorNormalize (pmove->wishveloc); + + if (wishspd > 30) + wishspd = 30; + // Determine veer amount + currentspeed = DotProduct (pmove->velocity, wishdir); + // See how much to add + addspeed = wishspd - currentspeed; + // If not adding any, done. + if (addspeed <= 0) + return; + // Determine acceleration speed after acceleration + + // QUAKECLASSIC: accelspeed = accel * wishspeed * pmove->frametime * pmove->friction; + accelspeed = accel * wishspeed * pmove->frametime; + + // Cap it + if (accelspeed > addspeed) + accelspeed = addspeed; + + // Adjust pmove vel. + for (i=0 ; i<3 ; i++) + { + pmove->velocity[i] += accelspeed*wishdir[i]; + } +} + +/* +=================== +PM_WaterMove + +=================== +*/ +void PM_WaterMove (void) +{ + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + vec3_t start, dest; + vec3_t temp; + pmtrace_t trace; + + float speed, newspeed, addspeed, accelspeed; + +// +// user intentions +// + for (i=0 ; i<3 ; i++) + wishvel[i] = pmove->forward[i]*pmove->cmd.forwardmove + pmove->right[i]*pmove->cmd.sidemove; + + // Sinking after no other movement occurs + if (!pmove->cmd.forwardmove && !pmove->cmd.sidemove && !pmove->cmd.upmove) + wishvel[2] -= 60; // drift towards bottom + else // Go straight up by upmove amount. + wishvel[2] += pmove->cmd.upmove; + + // Copy it over and determine speed + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + // Cap speed. + if (wishspeed > pmove->maxspeed) + { + VectorScale (wishvel, pmove->maxspeed/wishspeed, wishvel); + wishspeed = pmove->maxspeed; + } + // Slow us down a bit. + // QUAKECLASSIC: wishspeed *= 0.8; + wishspeed *= 0.7; + + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity); +// Water friction + VectorCopy(pmove->velocity, temp); + speed = VectorNormalize(temp); + if (speed) + { + newspeed = speed - pmove->frametime * speed * pmove->movevars->friction * pmove->friction; + + if (newspeed < 0) + newspeed = 0; + VectorScale (pmove->velocity, newspeed/speed, pmove->velocity); + } + else + newspeed = 0; + +// +// water acceleration +// + if ( wishspeed < 0.1f ) + { + return; + } + + addspeed = wishspeed - newspeed; + if (addspeed > 0) + { + + VectorNormalize(wishvel); + accelspeed = pmove->movevars->accelerate * wishspeed * pmove->frametime * pmove->friction; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i = 0; i < 3; i++) + pmove->velocity[i] += accelspeed * wishvel[i]; + } + +// Now move +// assume it is a stair or a slope, so press down from stepheight above + VectorMA (pmove->origin, pmove->frametime, pmove->velocity, dest); + VectorCopy (dest, start); + start[2] += pmove->movevars->stepsize + 1; + trace = pmove->PM_PlayerTrace (start, dest, PM_NORMAL, -1 ); + if (!trace.startsolid && !trace.allsolid) // FIXME: check steep slope? + { // walked up the step, so just keep result and exit + VectorCopy (trace.endpos, pmove->origin); + return; + } + + // Try moving straight along out normal path. + PM_FlyMove (); +} + + +/* +=================== +PM_AirMove + +=================== +*/ +void PM_AirMove (void) +{ + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + + // Copy movement amounts + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + // Zero out z components of movement vectors + pmove->forward[2] = 0; + pmove->right[2] = 0; + // Renormalize + VectorNormalize (pmove->forward); + VectorNormalize (pmove->right); + + // Determine x and y parts of velocity + for (i=0 ; i<2 ; i++) + { + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + } + // Zero out z part of velocity + wishvel[2] = 0; + + // Determine maginitude of speed of move + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + // Clamp to server defined max speed + if (wishspeed > pmove->maxspeed) + { + VectorScale (wishvel, pmove->maxspeed/wishspeed, wishvel); + wishspeed = pmove->maxspeed; + } + + // QUAKECLASSIC: PM_AirAccelerate (wishdir, wishspeed, pmove->movevars->airaccelerate); + PM_AirAccelerate (wishdir, wishspeed, pmove->movevars->accelerate); + + // Add in any base velocity to the current velocity. + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity ); + + PM_FlyMove (); +} + +qboolean PM_InWater( void ) +{ + return ( pmove->waterlevel > 1 ); +} + +qboolean PM_CheckWater () +{ + vec3_t point; + int cont; + int truecont; + float height; + float heightover2; + + // Pick a spot just above the players feet. + point[0] = pmove->origin[0] + (pmove->player_mins[pmove->usehull][0] + pmove->player_maxs[pmove->usehull][0]) * 0.5; + point[1] = pmove->origin[1] + (pmove->player_mins[pmove->usehull][1] + pmove->player_maxs[pmove->usehull][1]) * 0.5; + point[2] = pmove->origin[2] + pmove->player_mins[pmove->usehull][2] + 1; + + // Assume that we are not in water at all. + pmove->waterlevel = 0; + pmove->watertype = CONTENTS_EMPTY; + + // Grab point contents. + cont = pmove->PM_PointContents (point, &truecont ); + // Are we under water? (not solid and not empty?) + if (cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + { + // Set water type + pmove->watertype = cont; + + // We are at least at level one + pmove->waterlevel = 1; + + height = (pmove->player_mins[pmove->usehull][2] + pmove->player_maxs[pmove->usehull][2]); + heightover2 = height * 0.5; + + // Now check a point that is at the player hull midpoint. + point[2] = pmove->origin[2] + heightover2; + cont = pmove->PM_PointContents (point, NULL ); + // If that point is also under water... + if (cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + { + // Set a higher water level. + pmove->waterlevel = 2; + + // Now check the eye position. (view_ofs is relative to the origin) + point[2] = pmove->origin[2] + pmove->view_ofs[2]; + + cont = pmove->PM_PointContents (point, NULL ); + if (cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + pmove->waterlevel = 3; // In over our eyes + } + + // Adjust velocity based on water current, if any. + if ( ( truecont <= CONTENTS_CURRENT_0 ) && + ( truecont >= CONTENTS_CURRENT_DOWN ) ) + { + // The deeper we are, the stronger the current. + static vec3_t current_table[] = + { + {1, 0, 0}, {0, 1, 0}, {-1, 0, 0}, + {0, -1, 0}, {0, 0, 1}, {0, 0, -1} + }; + + VectorMA (pmove->basevelocity, 50.0*pmove->waterlevel, current_table[CONTENTS_CURRENT_0 - truecont], pmove->basevelocity); + } + } + + return pmove->waterlevel > 1; +} + +/* +============= +PM_CatagorizePosition +============= +*/ +void PM_CatagorizePosition (void) +{ + vec3_t point; + pmtrace_t tr; + +// if the player hull point one unit down is solid, the player +// is on ground + +// see if standing on something solid + + // Doing this before we move may introduce a potential latency in water detection, but + // doing it after can get us stuck on the bottom in water if the amount we move up + // is less than the 1 pixel 'threshold' we're about to snap to. Also, we'll call + // this several times per frame, so we really need to avoid sticking to the bottom of + // water on each call, and the converse case will correct itself if called twice. + PM_CheckWater(); + + point[0] = pmove->origin[0]; + point[1] = pmove->origin[1]; + point[2] = pmove->origin[2] - 2; + + if (pmove->velocity[2] > 180) // Shooting up really fast. Definitely not on ground. + { + pmove->onground = -1; + } + else + { + // Try and move down. + tr = pmove->PM_PlayerTrace (pmove->origin, point, PM_NORMAL, -1 ); + // If we hit a steep plane, we are not on ground + if ( tr.plane.normal[2] < 0.7) + pmove->onground = -1; // too steep + else + pmove->onground = tr.ent; // Otherwise, point to index of ent under us. + + // If we are on something... + if (pmove->onground != -1) + { + // Then we are not in water jump sequence + pmove->waterjumptime = 0; + // If we could make the move, drop us down that 1 pixel + // QUAKECLASSIC: + //if (!tr.startsolid && !tr.allsolid) + if ( pmove->waterlevel < 2 && !tr.startsolid && !tr.allsolid ) + VectorCopy (tr.endpos, pmove->origin); + } + + // Standing on an entity other than the world + if (tr.ent > 0) // So signal that we are touching something. + { + PM_AddToTouched(tr, pmove->velocity); + } + } +} + +/* +================= +PM_GetRandomStuckOffsets + +When a player is stuck, it's costly to try and unstick them +Grab a test offset for the player based on a passed in index +================= +*/ +int PM_GetRandomStuckOffsets(int nIndex, int server, vec3_t offset) +{ + // Last time we did a full + int idx; + idx = rgStuckLast[nIndex][server]++; + + VectorCopy(rgv3tStuckTable[idx % 54], offset); + + return (idx % 54); +} + +void PM_ResetStuckOffsets(int nIndex, int server) +{ + rgStuckLast[nIndex][server] = 0; +} + +/* +================= +NudgePosition + +If pmove->origin is in a solid position, +try nudging slightly on all axis to +allow for the cut precision of the net coordinates +================= +*/ +#define PM_CHECKSTUCK_MINTIME 0.05 // Don't check again too quickly. + +int PM_CheckStuck (void) +{ + vec3_t base; + vec3_t offset; + vec3_t test; + int hitent; + int idx; + float fTime; + int i; + pmtrace_t traceresult; + + static float rgStuckCheckTime[MAX_CLIENTS][2]; // Last time we did a full + + // If position is okay, exit + hitent = pmove->PM_TestPlayerPosition (pmove->origin, &traceresult ); + if (hitent == -1 ) + { + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + return 0; + } + + VectorCopy (pmove->origin, base); + + // + // Deal with precision error in network. + // + if (!pmove->server) + { + // World or BSP model + if ( ( hitent == 0 ) || + ( pmove->physents[hitent].model != NULL ) ) + { + int nReps = 0; + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + do + { + i = PM_GetRandomStuckOffsets(pmove->player_index, pmove->server, offset); + + VectorAdd(base, offset, test); + if (pmove->PM_TestPlayerPosition (test, &traceresult ) == -1) + { + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + + VectorCopy ( test, pmove->origin ); + return 0; + } + nReps++; + } while (nReps < 54); + } + } + + // Only an issue on the client. + + if (pmove->server) + idx = 0; + else + idx = 1; + + fTime = pmove->Sys_FloatTime(); + // Too soon? + if (rgStuckCheckTime[pmove->player_index][idx] >= + ( fTime - PM_CHECKSTUCK_MINTIME ) ) + { + return 1; + } + rgStuckCheckTime[pmove->player_index][idx] = fTime; + + pmove->PM_StuckTouch( hitent, &traceresult ); + + i = PM_GetRandomStuckOffsets(pmove->player_index, pmove->server, offset); + + VectorAdd(base, offset, test); + if ( ( hitent = pmove->PM_TestPlayerPosition ( test, NULL ) ) == -1 ) + { + //Con_DPrintf("Nudged\n"); + + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + + if (i >= 27) + VectorCopy ( test, pmove->origin ); + + return 0; + } + + // If player is flailing while stuck in another player ( should never happen ), then see + // if we can't "unstick" them forceably. + if ( pmove->cmd.buttons & ( IN_JUMP | IN_DUCK | IN_ATTACK ) && ( pmove->physents[ hitent ].player != 0 ) ) + { + float x, y, z; + float xystep = 8.0; + float zstep = 18.0; + float xyminmax = xystep; + float zminmax = 4 * zstep; + + for ( z = 0; z <= zminmax; z += zstep ) + { + for ( x = -xyminmax; x <= xyminmax; x += xystep ) + { + for ( y = -xyminmax; y <= xyminmax; y += xystep ) + { + VectorCopy( base, test ); + test[0] += x; + test[1] += y; + test[2] += z; + + if ( pmove->PM_TestPlayerPosition ( test, NULL ) == -1 ) + { + VectorCopy( test, pmove->origin ); + return 0; + } + } + } + } + } + + //VectorCopy (base, pmove->origin); + + return 1; +} + +/* +=============== +PM_SpectatorMove +=============== +*/ +void PM_SpectatorMove (void) +{ + float speed, drop, friction, control, newspeed; + //float accel; + float currentspeed, addspeed, accelspeed; + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + // this routine keeps track of the spectators psoition + // there a two different main move types : track player or moce freely (OBS_ROAMING) + // doesn't need excate track position, only to generate PVS, so just copy + // targets position and real view position is calculated on client (saves server CPU) + +#ifdef CLIENT_DLL + // execute all jumps + if ( iJumpSpectator ) + { + VectorCopy( vJumpOrigin, pmove->origin ); + VectorCopy( vJumpAngles, pmove->angles ); + VectorCopy( vec3_origin, pmove->velocity ); + iJumpSpectator = 0; + return; + } +#endif + + if ( pmove->iuser1 == OBS_ROAMING) + { + // Move around in normal spectator method + + speed = Length (pmove->velocity); + if (speed < 1) + { + VectorCopy (vec3_origin, pmove->velocity) + } + else + { + drop = 0; + + friction = pmove->movevars->friction*1.5; // extra friction + control = speed < pmove->movevars->stopspeed ? pmove->movevars->stopspeed : speed; + drop += control*friction*pmove->frametime; + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + VectorScale (pmove->velocity, newspeed, pmove->velocity); + } + + // accelerate + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + VectorNormalize (pmove->forward); + VectorNormalize (pmove->right); + + for (i=0 ; i<3 ; i++) + { + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + } + wishvel[2] += pmove->cmd.upmove; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + // + // clamp to server defined max speed + // + if (wishspeed > pmove->movevars->spectatormaxspeed) + { + VectorScale (wishvel, pmove->movevars->spectatormaxspeed/wishspeed, wishvel); + wishspeed = pmove->movevars->spectatormaxspeed; + } + + currentspeed = DotProduct(pmove->velocity, wishdir); + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) + return; + + accelspeed = pmove->movevars->accelerate*pmove->frametime*wishspeed; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i=0 ; i<3 ; i++) + pmove->velocity[i] += accelspeed*wishdir[i]; + + // move + VectorMA (pmove->origin, pmove->frametime, pmove->velocity, pmove->origin); + } + else + { + // all other modes just track some kind of target, so spectator PVS = target PVS + + int target; + + // Find the client this player's targeting + for (target = 0; target < pmove->numphysent; target++) + { + if ( pmove->physents[target].info == pmove->iuser2 ) + break; + } + + if (target == pmove->numphysent) + return; + + // use targets position as own origin for PVS + VectorCopy( pmove->physents[target].angles, pmove->angles ); + VectorCopy( pmove->physents[target].origin, pmove->origin ); + + // no velocity + VectorCopy( vec3_origin, pmove->velocity ); + } +} + +/* +================== +PM_SplineFraction + +Use for ease-in, ease-out style interpolation (accel/decel) +Used by ducking code. +================== +*/ +float PM_SplineFraction( float value, float scale ) +{ + float valueSquared; + + value = scale * value; + valueSquared = value * value; + + // Nice little ease-in, ease-out spline-like curve + return 3 * valueSquared - 2 * valueSquared * value; +} + +void PM_FixPlayerCrouchStuck( int direction ) +{ + int hitent; + int i; + vec3_t test; + + hitent = pmove->PM_TestPlayerPosition ( pmove->origin, NULL ); + if (hitent == -1 ) + return; + + VectorCopy( pmove->origin, test ); + for ( i = 0; i < 36; i++ ) + { + pmove->origin[2] += direction; + hitent = pmove->PM_TestPlayerPosition ( pmove->origin, NULL ); + if (hitent == -1 ) + return; + } + + VectorCopy( test, pmove->origin ); // Failed +} + +void PM_WaterJump (void) +{ + if ( pmove->waterjumptime > 10000 ) + { + pmove->waterjumptime = 10000; + } + + if ( !pmove->waterjumptime ) + return; + + pmove->waterjumptime -= pmove->cmd.msec; + if ( pmove->waterjumptime < 0 || + !pmove->waterlevel ) + { + pmove->waterjumptime = 0; + pmove->flags &= ~FL_WATERJUMP; + } + + pmove->velocity[0] = pmove->movedir[0]; + pmove->velocity[1] = pmove->movedir[1]; +} + +/* +============ +PM_AddGravity + +============ +*/ +void PM_AddGravity () +{ + float ent_gravity; + + if (pmove->gravity) + ent_gravity = pmove->gravity; + else + ent_gravity = 1.0; + + // Add gravity incorrectly + pmove->velocity[2] -= (ent_gravity * pmove->movevars->gravity * pmove->frametime ); + pmove->velocity[2] += pmove->basevelocity[2] * pmove->frametime; + pmove->basevelocity[2] = 0; + PM_CheckVelocity(); +} +/* +============ +PM_PushEntity + +Does not change the entities velocity at all +============ +*/ +pmtrace_t PM_PushEntity (vec3_t push) +{ + pmtrace_t trace; + vec3_t end; + + VectorAdd (pmove->origin, push, end); + + trace = pmove->PM_PlayerTrace (pmove->origin, end, PM_NORMAL, -1 ); + + VectorCopy (trace.endpos, pmove->origin); + + // So we can run impact function afterwards. + if (trace.fraction < 1.0 && + !trace.allsolid) + { + PM_AddToTouched(trace, pmove->velocity); + } + + return trace; +} + +/* +============ +PM_Physics_Toss() + +Dead player flying through air., e.g. +============ +*/ +void PM_Physics_Toss() +{ + pmtrace_t trace; + vec3_t move; + float backoff; + + PM_CheckWater(); + + if (pmove->velocity[2] > 0) + pmove->onground = -1; + + // If on ground and not moving, return. + if ( pmove->onground != -1 ) + { + if (VectorCompare(pmove->basevelocity, vec3_origin) && + VectorCompare(pmove->velocity, vec3_origin)) + return; + } + + PM_CheckVelocity (); + +// add gravity + if ( pmove->movetype != MOVETYPE_FLY && + pmove->movetype != MOVETYPE_BOUNCEMISSILE && + pmove->movetype != MOVETYPE_FLYMISSILE ) + PM_AddGravity (); + +// move origin + // Base velocity is not properly accounted for since this entity will move again after the bounce without + // taking it into account + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity); + + PM_CheckVelocity(); + VectorScale (pmove->velocity, pmove->frametime, move); + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity); + + trace = PM_PushEntity (move); // Should this clear basevelocity + + PM_CheckVelocity(); + + if (trace.allsolid) + { + // entity is trapped in another solid + pmove->onground = trace.ent; + VectorCopy (vec3_origin, pmove->velocity); + return; + } + + if (trace.fraction == 1) + { + PM_CheckWater(); + return; + } + + + if (pmove->movetype == MOVETYPE_BOUNCE) + // QUAKECLASSIC: backoff = 2.0 - pmove->friction; + backoff = 1.5; + // QUAKECLASSIC: else if (pmove->movetype == MOVETYPE_BOUNCEMISSILE) + // QUAKECLASSIC: backoff = 2.0; + else + backoff = 1; + + PM_ClipVelocity (pmove->velocity, trace.plane.normal, pmove->velocity, backoff); + + // stop if on ground + if (trace.plane.normal[2] > 0.7) + { + float vel; + vec3_t base; + + VectorClear( base ); + + // QUAKECLASSIC: No Static friction + /* + if (pmove->velocity[2] < pmove->movevars->gravity * pmove->frametime) + { + // we're rolling on the ground, add static friction. + pmove->onground = trace.ent; + pmove->velocity[2] = 0; + } + */ + + vel = DotProduct( pmove->velocity, pmove->velocity ); + + // Con_DPrintf("%f %f: %.0f %.0f %.0f\n", vel, trace.fraction, ent->velocity[0], ent->velocity[1], ent->velocity[2] ); + + if (vel < (30 * 30) || (pmove->movetype != MOVETYPE_BOUNCE && pmove->movetype != MOVETYPE_BOUNCEMISSILE)) + { + pmove->onground = trace.ent; + VectorCopy (vec3_origin, pmove->velocity); + } + } + +// check for in water + PM_CheckWater(); +} + +/* +==================== +PM_NoClip + +==================== +*/ +void PM_NoClip() +{ + int i; + vec3_t wishvel; + float fmove, smove; + + // Copy movement amounts + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + VectorNormalize ( pmove->forward ); + VectorNormalize ( pmove->right ); + + for (i=0 ; i<3 ; i++) // Determine x and y parts of velocity + { + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + } + wishvel[2] += pmove->cmd.upmove; + + VectorMA (pmove->origin, pmove->frametime, wishvel, pmove->origin); + + // Zero out the velocity so that we don't accumulate a huge downward velocity from + // gravity, etc. + VectorClear( pmove->velocity ); + +} + + +// Only allow bunny jumping up to 1.7x server / player maxspeed setting +#define BUNNYJUMP_MAX_SPEED_FACTOR 1.7f + +//----------------------------------------------------------------------------- +// Purpose: Corrects bunny jumping ( where player initiates a bunny jump before other +// movement logic runs, thus making onground == -1 thus making PM_Friction get skipped and +// running PM_AirMove, which doesn't crop velocity to maxspeed like the ground / other +// movement logic does. +//----------------------------------------------------------------------------- +void PM_PreventMegaBunnyJumping( void ) +{ + // Current player speed + float spd; + // If we have to crop, apply this cropping fraction to velocity + float fraction; + // Speed at which bunny jumping is limited + float maxscaledspeed; + + maxscaledspeed = BUNNYJUMP_MAX_SPEED_FACTOR * pmove->maxspeed; + + // Don't divide by zero + if ( maxscaledspeed <= 0.0f ) + return; + + spd = Length( pmove->velocity ); + + if ( spd <= maxscaledspeed ) + return; + + fraction = ( maxscaledspeed / spd ) * 0.65; //Returns the modifier for the velocity + + VectorScale( pmove->velocity, fraction, pmove->velocity ); //Crop it down!. +} + + +/* +============= +PM_Jump +============= +*/ +void PM_Jump (void) +{ + if ( pmove->dead ) + { + pmove->oldbuttons |= IN_JUMP ; // don't jump again until released + return; + } + + if ( pmove->maxspeed == 1 ) + return; + + // See if we are waterjumping. If so, decrement count and return. + if ( pmove->waterjumptime ) + { + pmove->waterjumptime -= pmove->cmd.msec; + if (pmove->waterjumptime < 0) + { + pmove->waterjumptime = 0; + } + return; + } + + // If we are in the water most of the way... + if (pmove->waterlevel >= 2) + { // swimming, not jumping + pmove->onground = -1; + + if (pmove->watertype == CONTENTS_WATER) // We move up a certain amount + pmove->velocity[2] = 100; + else if (pmove->watertype == CONTENTS_SLIME) + pmove->velocity[2] = 80; + else // LAVA + pmove->velocity[2] = 50; + + // play swiming sound + if ( pmove->flSwimTime <= 0 ) + { + // Don't play sound again for 1 second + pmove->flSwimTime = 1000; + switch ( pmove->RandomLong( 0, 3 ) ) + { + case 0: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 1: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 2: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 3: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + } + } + + return; + } + + // No more effect + if ( pmove->onground == -1 ) + { + // Flag that we jumped. + // HACK HACK HACK + // Remove this when the game .dll no longer does physics code!!!! + pmove->oldbuttons |= IN_JUMP; // don't jump again until released + return; // in air, so no effect + } + + if ( pmove->oldbuttons & IN_JUMP ) + return; // don't pogo stick + + // In the air now. + pmove->onground = -1; + + PM_PreventMegaBunnyJumping(); + + pmove->PM_PlaySound( CHAN_BODY, "player/plyrjmp8.wav", 0.5, ATTN_NORM, 0, PITCH_NORM ); + + pmove->velocity[2] += 295; + + // Decay it for simulation + PM_FixupGravityVelocity(); + + // Flag that we jumped. + pmove->oldbuttons |= IN_JUMP; // don't jump again until released +} + +/* +============= +PM_CheckWaterJump +============= +*/ +#define WJ_HEIGHT 8 +void PM_CheckWaterJump (void) +{ + vec3_t vecStart, vecEnd; + vec3_t flatforward; + vec3_t flatvelocity; + float curspeed; + pmtrace_t tr; + int savehull; + + // Already water jumping. + if ( pmove->waterjumptime ) + return; + + // Don't hop out if we just jumped in + if ( pmove->velocity[2] < -180 ) + return; // only hop out if we are moving up + + // See if we are backing up + flatvelocity[0] = pmove->velocity[0]; + flatvelocity[1] = pmove->velocity[1]; + flatvelocity[2] = 0; + + // Must be moving + curspeed = VectorNormalize( flatvelocity ); + + // see if near an edge + flatforward[0] = pmove->forward[0]; + flatforward[1] = pmove->forward[1]; + flatforward[2] = 0; + VectorNormalize (flatforward); + + // Are we backing into water from steps or something? If so, don't pop forward + if ( curspeed != 0.0 && ( DotProduct( flatvelocity, flatforward ) < 0.0 ) ) + return; + + VectorCopy( pmove->origin, vecStart ); + vecStart[2] += WJ_HEIGHT; + + VectorMA ( vecStart, 24, flatforward, vecEnd ); + + // Trace, this trace should use the point sized collision hull + savehull = pmove->usehull; + pmove->usehull = 2; + tr = pmove->PM_PlayerTrace( vecStart, vecEnd, PM_NORMAL, -1 ); + if ( tr.fraction < 1.0 && fabs( tr.plane.normal[2] ) < 0.1f ) // Facing a near vertical wall? + { + vecStart[2] += pmove->player_maxs[ savehull ][2] - WJ_HEIGHT; + VectorMA( vecStart, 24, flatforward, vecEnd ); + VectorMA( vec3_origin, -50, tr.plane.normal, pmove->movedir ); + + tr = pmove->PM_PlayerTrace( vecStart, vecEnd, PM_NORMAL, -1 ); + if ( tr.fraction == 1.0 ) + { + pmove->waterjumptime = 2000; + pmove->velocity[2] = 225; + pmove->oldbuttons |= IN_JUMP; + pmove->flags |= FL_WATERJUMP; + } + } + + // Reset the collision hull + pmove->usehull = savehull; +} + +void PM_CheckFalling( void ) +{ + if ( pmove->onground != -1 && + !pmove->dead && + pmove->flFallVelocity >= PLAYER_FALL_PUNCH_THRESHHOLD ) + { + float fvol = 0.5; + + if ( pmove->waterlevel > 0 ) + { + } + else if ( pmove->flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED ) + { + pmove->PM_PlaySound( CHAN_VOICE, "player/pl_fallpain3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + fvol = 1.0; + } + else if ( pmove->flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED / 2 ) + { + fvol = 0.85; + } + else if ( pmove->flFallVelocity < PLAYER_MIN_BOUNCE_SPEED ) + { + fvol = 0; + } + + if ( fvol > 0.0 ) + { + // Play landing step right away + pmove->flTimeStepSound = 0; + + PM_UpdateStepSound(); + + // Knock the screen around a little bit, temporary effect + pmove->punchangle[ 2 ] = pmove->flFallVelocity * 0.013; // punch z axis + + if ( pmove->punchangle[ 0 ] > 8 ) + { + pmove->punchangle[ 0 ] = 8; + } + } + } + + if ( pmove->onground != -1 ) + { + pmove->flFallVelocity = 0; + } +} + +/* +================= +PM_PlayWaterSounds + +================= +*/ +void PM_PlayWaterSounds( void ) +{ + // Did we enter or leave water? + if ( ( pmove->oldwaterlevel == 0 && pmove->waterlevel != 0 ) || + ( pmove->oldwaterlevel != 0 && pmove->waterlevel == 0 ) ) + { + switch ( pmove->RandomLong(0,3) ) + { + case 0: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 1: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 2: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 3: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + } + } +} + +/* +=============== +PM_CalcRoll + +=============== +*/ +float PM_CalcRoll (vec3_t angles, vec3_t velocity, float rollangle, float rollspeed ) +{ + float sign; + float side; + float value; + vec3_t forward, right, up; + + AngleVectors (angles, forward, right, up); + + side = DotProduct (velocity, right); + + sign = side < 0 ? -1 : 1; + + side = fabs(side); + + value = rollangle; + + if (side < rollspeed) + { + side = side * value / rollspeed; + } + else + { + side = value; + } + + return side * sign; +} + +/* +============= +PM_DropPunchAngle + +============= +*/ +void PM_DropPunchAngle ( vec3_t punchangle ) +{ + float len; + + len = VectorNormalize ( punchangle ); + len -= (10.0 + len * 0.5) * pmove->frametime; + len = max( len, 0.0 ); + VectorScale ( punchangle, len, punchangle); +} + +/* +============== +PM_CheckParamters + +============== +*/ +void PM_CheckParamters( void ) +{ + float spd; + float maxspeed; + vec3_t v_angle; + + spd = ( pmove->cmd.forwardmove * pmove->cmd.forwardmove ) + + ( pmove->cmd.sidemove * pmove->cmd.sidemove ) + + ( pmove->cmd.upmove * pmove->cmd.upmove ); + spd = sqrt( spd ); + + maxspeed = pmove->clientmaxspeed; //atof( pmove->PM_Info_ValueForKey( pmove->physinfo, "maxspd" ) ); + if ( maxspeed != 0.0 ) + { + pmove->maxspeed = min( maxspeed, pmove->maxspeed ); + } + + if ( ( spd != 0.0 ) && + ( spd > pmove->maxspeed ) ) + { + float fRatio = pmove->maxspeed / spd; + pmove->cmd.forwardmove *= fRatio; + pmove->cmd.sidemove *= fRatio; + pmove->cmd.upmove *= fRatio; + } + + if ( pmove->flags & FL_FROZEN || + pmove->flags & FL_ONTRAIN || + pmove->dead ) + { + pmove->cmd.forwardmove = 0; + pmove->cmd.sidemove = 0; + pmove->cmd.upmove = 0; + } + + + PM_DropPunchAngle( pmove->punchangle ); + + // Take angles from command. + if ( !pmove->dead ) + { + VectorCopy ( pmove->cmd.viewangles, v_angle ); + VectorAdd( v_angle, pmove->punchangle, v_angle ); + + // Set up view angles. + pmove->angles[ROLL] = PM_CalcRoll ( v_angle, pmove->velocity, pmove->movevars->rollangle, pmove->movevars->rollspeed )*4; + pmove->angles[PITCH] = v_angle[PITCH]; + pmove->angles[YAW] = v_angle[YAW]; + } + else + { + VectorCopy( pmove->oldangles, pmove->angles ); + } + + // Set dead player view_offset + if ( pmove->dead ) + { + pmove->view_ofs[2] = PM_DEAD_VIEWHEIGHT; + } + + // Adjust client view angles to match values used on server. + if (pmove->angles[YAW] > 180.0f) + { + pmove->angles[YAW] -= 360.0f; + } + +} + +void PM_ReduceTimers( void ) +{ + if ( pmove->flTimeStepSound > 0 ) + { + pmove->flTimeStepSound -= pmove->cmd.msec; + if ( pmove->flTimeStepSound < 0 ) + { + pmove->flTimeStepSound = 0; + } + } + if ( pmove->flDuckTime > 0 ) + { + pmove->flDuckTime -= pmove->cmd.msec; + if ( pmove->flDuckTime < 0 ) + { + pmove->flDuckTime = 0; + } + } + if ( pmove->flSwimTime > 0 ) + { + pmove->flSwimTime -= pmove->cmd.msec; + if ( pmove->flSwimTime < 0 ) + { + pmove->flSwimTime = 0; + } + } +} + +/* +============= +PlayerMove + +Returns with origin, angles, and velocity modified in place. + +Numtouch and touchindex[] will be set if any of the physents +were contacted during the move. +============= +*/ +void PM_PlayerMove ( qboolean server ) +{ + physent_t *pLadder = NULL; + + // Are we running server code? + pmove->server = server; + + // Adjust speeds etc. + PM_CheckParamters(); + + // Assume we don't touch anything + pmove->numtouch = 0; + + // # of msec to apply movement + pmove->frametime = pmove->cmd.msec * 0.001; + + PM_ReduceTimers(); + + // Convert view angles to vectors + AngleVectors (pmove->angles, pmove->forward, pmove->right, pmove->up); + + // PM_ShowClipBox(); + + // Special handling for spectator and observers. (iuser1 is set if the player's in observer mode) + if ( pmove->spectator || pmove->iuser1 > 0 ) + { + PM_SpectatorMove(); + PM_CatagorizePosition(); + return; + } + + // Always try and unstick us unless we are in NOCLIP mode + if ( pmove->movetype != MOVETYPE_NOCLIP && pmove->movetype != MOVETYPE_NONE ) + { + if ( PM_CheckStuck() ) + { + return; // Can't move, we're stuck + } + } + + // Now that we are "unstuck", see where we are ( waterlevel and type, pmove->onground ). + PM_CatagorizePosition(); + + // Store off the starting water level + pmove->oldwaterlevel = pmove->waterlevel; + + // If we are not on ground, store off how fast we are moving down + if ( pmove->onground == -1 ) + { + pmove->flFallVelocity = -pmove->velocity[2]; + } + + g_onladder = 0; + + PM_UpdateStepSound(); + + // Don't run ladder code if dead or on a train + if ( !pmove->dead && !(pmove->flags & FL_ONTRAIN) ) + { + if ( pmove->movetype != MOVETYPE_WALK && + pmove->movetype != MOVETYPE_NOCLIP ) + { + // Clear ladder stuff unless player is noclipping + // it will be set immediately again next frame if necessary + pmove->movetype = MOVETYPE_WALK; + } + } + + // Slow down, I'm pulling it! (a box maybe) but only when I'm standing on ground + if ( ( pmove->onground != -1 ) && ( pmove->cmd.buttons & IN_USE) ) + { + VectorScale( pmove->velocity, 0.3, pmove->velocity ); + } + + // Handle movement + switch ( pmove->movetype ) + { + default: + pmove->Con_DPrintf("Bogus pmove player movetype %i on (%i) 0=cl 1=sv\n", pmove->movetype, pmove->server); + break; + + case MOVETYPE_NONE: + break; + + case MOVETYPE_NOCLIP: + PM_NoClip(); + break; + + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + PM_Physics_Toss(); + break; + + case MOVETYPE_FLY: + + PM_CheckWater(); + + // Was jump button pressed? + // If so, set velocity to 270 away from ladder. This is currently wrong. + // Also, set MOVE_TYPE to walk, too. + if ( pmove->cmd.buttons & IN_JUMP ) + { + if ( !pLadder ) + { + PM_Jump (); + } + } + else + { + pmove->oldbuttons &= ~IN_JUMP; + } + + // Perform the move accounting for any base velocity. + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity); + PM_FlyMove (); + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity); + break; + + case MOVETYPE_WALK: + if ( !PM_InWater() ) + { + PM_AddCorrectGravity(); + } + + // If we are leaping out of the water, just update the counters. + if ( pmove->waterjumptime ) + { + PM_WaterJump(); + PM_FlyMove(); + + // Make sure waterlevel is set correctly + PM_CheckWater(); + return; + } + + // If we are swimming in the water, see if we are nudging against a place we can jump up out + // of, and, if so, start out jump. Otherwise, if we are not moving up, then reset jump timer to 0 + if ( pmove->waterlevel >= 2 ) + { + if ( pmove->waterlevel == 2 ) + { + PM_CheckWaterJump(); + } + + // If we are falling again, then we must not trying to jump out of water any more. + if ( pmove->velocity[2] < 0 && pmove->waterjumptime ) + { + pmove->waterjumptime = 0; + } + + // Was jump button pressed? + if (pmove->cmd.buttons & IN_JUMP) + { + PM_Jump (); + } + else + { + pmove->oldbuttons &= ~IN_JUMP; + } + + // Perform regular water movement + PM_WaterMove(); + + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity); + + // Get a final position + PM_CatagorizePosition(); + } + else + + // Not underwater + { + // Was jump button pressed? + if ( pmove->cmd.buttons & IN_JUMP ) + { + if ( !pLadder ) + { + PM_Jump (); + } + } + else + { + pmove->oldbuttons &= ~IN_JUMP; + } + + // Fricion is handled before we add in any base velocity. That way, if we are on a conveyor, + // we don't slow when standing still, relative to the conveyor. + if ( pmove->onground != -1 ) + { + pmove->velocity[2] = 0.0; + PM_Friction(); + } + + // Make sure velocity is valid. + PM_CheckVelocity(); + + // Are we on ground now + if ( pmove->onground != -1 ) + { + PM_WalkMove(); + } + else + { + PM_AirMove(); // Take into account movement when in air. + } + + // Set final flags. + PM_CatagorizePosition(); + + // Now pull the base velocity back out. + // Base velocity is set if you are on a moving object, like + // a conveyor (or maybe another monster?) + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity ); + + // Make sure velocity is valid. + PM_CheckVelocity(); + + // Add any remaining gravitational component. + if ( !PM_InWater() ) + { + PM_FixupGravityVelocity(); + } + + // If we are on ground, no downward velocity. + if ( pmove->onground != -1 ) + { + pmove->velocity[2] = 0; + } + + // See if we landed on the ground with enough force to play + // a landing sound. + PM_CheckFalling(); + } + + // Did we enter or leave the water? + PM_PlayWaterSounds(); + break; + } +} + +void PM_CreateStuckTable( void ) +{ + float x, y, z; + int idx; + int i; + float zi[3]; + + memset(rgv3tStuckTable, 0, 54 * sizeof(vec3_t)); + + idx = 0; + // Little Moves. + x = y = 0; + // Z moves + for (z = -0.125 ; z <= 0.125 ; z += 0.125) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + x = z = 0; + // Y moves + for (y = -0.125 ; y <= 0.125 ; y += 0.125) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + y = z = 0; + // X moves + for (x = -0.125 ; x <= 0.125 ; x += 0.125) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + + // Remaining multi axis nudges. + for ( x = - 0.125; x <= 0.125; x += 0.250 ) + { + for ( y = - 0.125; y <= 0.125; y += 0.250 ) + { + for ( z = - 0.125; z <= 0.125; z += 0.250 ) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + } + } + + // Big Moves. + x = y = 0; + zi[0] = 0.0f; + zi[1] = 1.0f; + zi[2] = 6.0f; + + for (i = 0; i < 3; i++) + { + // Z moves + z = zi[i]; + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + + x = z = 0; + + // Y moves + for (y = -2.0f ; y <= 2.0f ; y += 2.0) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + y = z = 0; + // X moves + for (x = -2.0f ; x <= 2.0f ; x += 2.0f) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + + // Remaining multi axis nudges. + for (i = 0 ; i < 3; i++) + { + z = zi[i]; + + for (x = -2.0f ; x <= 2.0f ; x += 2.0f) + { + for (y = -2.0f ; y <= 2.0f ; y += 2.0) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + } + } +} + + + +/* +This modume implements the shared player physics code between any particular game and +the engine. The same PM_Move routine is built into the game .dll and the client .dll and is +invoked by each side as appropriate. There should be no distinction, internally, between server +and client. This will ensure that prediction behaves appropriately. +*/ + +void PM_Move ( struct playermove_s *ppmove, int server ) +{ + assert( pm_shared_initialized ); + + pmove = ppmove; + + pmove->player_mins[ pmove->usehull ][ 2 ] = -24; + pmove->player_maxs[ pmove->usehull ][ 2 ] = 32; + + PM_PlayerMove( ( server != 0 ) ? true : false ); + + if ( pmove->onground != -1 ) + { + pmove->flags |= FL_ONGROUND; + } + else + { + pmove->flags &= ~FL_ONGROUND; + } + + // In single player, reset friction after each movement to FrictionModifier Triggers work still. + if ( !pmove->multiplayer && ( pmove->movetype == MOVETYPE_WALK ) ) + { + pmove->friction = 1.0f; + } +} + +int PM_GetInfo( int ent ) +{ + if ( ent >= 0 && ent <= pmove->numvisent ) + { + return pmove->visents[ ent ].info; + } + return -1; +} + +void PM_Init( struct playermove_s *ppmove ) +{ + assert( !pm_shared_initialized ); + + pmove = ppmove; + + PM_CreateStuckTable(); + PM_InitTextureTypes(); + + pm_shared_initialized = 1; +} diff --git a/dmc/pm_shared/pm_shared.h b/dmc/pm_shared/pm_shared.h new file mode 100644 index 0000000..ce8a1c6 --- /dev/null +++ b/dmc/pm_shared/pm_shared.h @@ -0,0 +1,36 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +// +// pm_shared.h +// +#if !defined( PM_SHAREDH ) +#define PM_SHAREDH +#pragma once + +void PM_Init( struct playermove_s *ppmove ); +void PM_Move ( struct playermove_s *ppmove, int server ); +char PM_FindTextureType( char *name ); + +// Spectator Movement modes (stored in pev->iuser1, so the physics code can get at them) +#define OBS_NONE 0 +#define OBS_CHASE_LOCKED 1 +#define OBS_CHASE_FREE 2 +#define OBS_ROAMING 3 +#define OBS_IN_EYE 4 +#define OBS_MAP_FREE 5 +#define OBS_MAP_CHASE 6 + +#endif diff --git a/engine/APIProxy.h b/engine/APIProxy.h new file mode 100644 index 0000000..b4a07f8 --- /dev/null +++ b/engine/APIProxy.h @@ -0,0 +1,939 @@ +#ifndef __APIPROXY__ +#define __APIPROXY__ + +#include "archtypes.h" // DAL +#include "netadr.h" +#include "Sequence.h" + +#ifndef _WIN32 +#include "enums.h" +#endif + +#define MAX_ALIAS_NAME 32 + +typedef struct cmdalias_s +{ + struct cmdalias_s *next; + char name[MAX_ALIAS_NAME]; + char *value; +} cmdalias_t; + + +// ******************************************************** +// Functions exported by the client .dll +// ******************************************************** + +// Function type declarations for client exports +typedef int (*INITIALIZE_FUNC) ( struct cl_enginefuncs_s*, int ); +typedef void (*HUD_INIT_FUNC) ( void ); +typedef int (*HUD_VIDINIT_FUNC) ( void ); +typedef int (*HUD_REDRAW_FUNC) ( float, int ); +typedef int (*HUD_UPDATECLIENTDATA_FUNC) ( struct client_data_s*, float ); +typedef void (*HUD_RESET_FUNC) ( void ); +typedef void (*HUD_CLIENTMOVE_FUNC)( struct playermove_s *ppmove, qboolean server ); +typedef void (*HUD_CLIENTMOVEINIT_FUNC)( struct playermove_s *ppmove ); +typedef char (*HUD_TEXTURETYPE_FUNC)( char *name ); +typedef void (*HUD_IN_ACTIVATEMOUSE_FUNC) ( void ); +typedef void (*HUD_IN_DEACTIVATEMOUSE_FUNC) ( void ); +typedef void (*HUD_IN_MOUSEEVENT_FUNC) ( int mstate ); +typedef void (*HUD_IN_CLEARSTATES_FUNC) ( void ); +typedef void (*HUD_IN_ACCUMULATE_FUNC ) ( void ); +typedef void (*HUD_CL_CREATEMOVE_FUNC) ( float frametime, struct usercmd_s *cmd, int active ); +typedef int (*HUD_CL_ISTHIRDPERSON_FUNC) ( void ); +typedef void (*HUD_CL_GETCAMERAOFFSETS_FUNC )( float *ofs ); +typedef struct kbutton_s * (*HUD_KB_FIND_FUNC) ( const char *name ); +typedef void ( *HUD_CAMTHINK_FUNC )( void ); +typedef void ( *HUD_CALCREF_FUNC ) ( struct ref_params_s *pparams ); +typedef int ( *HUD_ADDENTITY_FUNC ) ( int type, struct cl_entity_s *ent, const char *modelname ); +typedef void ( *HUD_CREATEENTITIES_FUNC ) ( void ); +typedef void ( *HUD_DRAWNORMALTRIS_FUNC ) ( void ); +typedef void ( *HUD_DRAWTRANSTRIS_FUNC ) ( void ); +typedef void ( *HUD_STUDIOEVENT_FUNC ) ( const struct mstudioevent_s *event, const struct cl_entity_s *entity ); +typedef void ( *HUD_POSTRUNCMD_FUNC ) ( struct local_state_s *from, struct local_state_s *to, struct usercmd_s *cmd, int runfuncs, double time, unsigned int random_seed ); +typedef void ( *HUD_SHUTDOWN_FUNC ) ( void ); +typedef void ( *HUD_TXFERLOCALOVERRIDES_FUNC )( struct entity_state_s *state, const struct clientdata_s *client ); +typedef void ( *HUD_PROCESSPLAYERSTATE_FUNC )( struct entity_state_s *dst, const struct entity_state_s *src ); +typedef void ( *HUD_TXFERPREDICTIONDATA_FUNC ) ( struct entity_state_s *ps, const struct entity_state_s *pps, struct clientdata_s *pcd, const struct clientdata_s *ppcd, struct weapon_data_s *wd, const struct weapon_data_s *pwd ); +typedef void ( *HUD_DEMOREAD_FUNC ) ( int size, unsigned char *buffer ); +typedef int ( *HUD_CONNECTIONLESS_FUNC )( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); +typedef int ( *HUD_GETHULLBOUNDS_FUNC ) ( int hullnumber, float *mins, float *maxs ); +typedef void (*HUD_FRAME_FUNC) ( double ); +typedef int (*HUD_KEY_EVENT_FUNC ) ( int eventcode, int keynum, const char *pszCurrentBinding ); +typedef void (*HUD_TEMPENTUPDATE_FUNC) ( double frametime, double client_time, double cl_gravity, struct tempent_s **ppTempEntFree, struct tempent_s **ppTempEntActive, int ( *Callback_AddVisibleEntity )( struct cl_entity_s *pEntity ), void ( *Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp ) ); +typedef struct cl_entity_s *(*HUD_GETUSERENTITY_FUNC ) ( int index ); +typedef void (*HUD_VOICESTATUS_FUNC)(int entindex, qboolean bTalking); +typedef void (*HUD_DIRECTORMESSAGE_FUNC)( int iSize, void *pbuf ); +typedef int ( *HUD_STUDIO_INTERFACE_FUNC )( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio ); +typedef void (*HUD_CHATINPUTPOSITION_FUNC)( int *x, int *y ); +typedef int (*HUD_GETPLAYERTEAM)(int iplayer); +typedef void *(*CLIENTFACTORY)(); // this should be CreateInterfaceFn but that means including interface.h + // which is a C++ file and some of the client files a C only... + // so we return a void * which we then do a typecast on later. + + +// Pointers to the exported client functions themselves +typedef struct +{ + INITIALIZE_FUNC pInitFunc; + HUD_INIT_FUNC pHudInitFunc; + HUD_VIDINIT_FUNC pHudVidInitFunc; + HUD_REDRAW_FUNC pHudRedrawFunc; + HUD_UPDATECLIENTDATA_FUNC pHudUpdateClientDataFunc; + HUD_RESET_FUNC pHudResetFunc; + HUD_CLIENTMOVE_FUNC pClientMove; + HUD_CLIENTMOVEINIT_FUNC pClientMoveInit; + HUD_TEXTURETYPE_FUNC pClientTextureType; + HUD_IN_ACTIVATEMOUSE_FUNC pIN_ActivateMouse; + HUD_IN_DEACTIVATEMOUSE_FUNC pIN_DeactivateMouse; + HUD_IN_MOUSEEVENT_FUNC pIN_MouseEvent; + HUD_IN_CLEARSTATES_FUNC pIN_ClearStates; + HUD_IN_ACCUMULATE_FUNC pIN_Accumulate; + HUD_CL_CREATEMOVE_FUNC pCL_CreateMove; + HUD_CL_ISTHIRDPERSON_FUNC pCL_IsThirdPerson; + HUD_CL_GETCAMERAOFFSETS_FUNC pCL_GetCameraOffsets; + HUD_KB_FIND_FUNC pFindKey; + HUD_CAMTHINK_FUNC pCamThink; + HUD_CALCREF_FUNC pCalcRefdef; + HUD_ADDENTITY_FUNC pAddEntity; + HUD_CREATEENTITIES_FUNC pCreateEntities; + HUD_DRAWNORMALTRIS_FUNC pDrawNormalTriangles; + HUD_DRAWTRANSTRIS_FUNC pDrawTransparentTriangles; + HUD_STUDIOEVENT_FUNC pStudioEvent; + HUD_POSTRUNCMD_FUNC pPostRunCmd; + HUD_SHUTDOWN_FUNC pShutdown; + HUD_TXFERLOCALOVERRIDES_FUNC pTxferLocalOverrides; + HUD_PROCESSPLAYERSTATE_FUNC pProcessPlayerState; + HUD_TXFERPREDICTIONDATA_FUNC pTxferPredictionData; + HUD_DEMOREAD_FUNC pReadDemoBuffer; + HUD_CONNECTIONLESS_FUNC pConnectionlessPacket; + HUD_GETHULLBOUNDS_FUNC pGetHullBounds; + HUD_FRAME_FUNC pHudFrame; + HUD_KEY_EVENT_FUNC pKeyEvent; + HUD_TEMPENTUPDATE_FUNC pTempEntUpdate; + HUD_GETUSERENTITY_FUNC pGetUserEntity; + HUD_VOICESTATUS_FUNC pVoiceStatus; // Possibly null on old client dlls. + HUD_DIRECTORMESSAGE_FUNC pDirectorMessage; // Possibly null on old client dlls. + HUD_STUDIO_INTERFACE_FUNC pStudioInterface; // Not used by all clients + HUD_CHATINPUTPOSITION_FUNC pChatInputPosition; // Not used by all clients + HUD_GETPLAYERTEAM pGetPlayerTeam; // Not used by all clients + CLIENTFACTORY pClientFactory; +} cldll_func_t; + +// Function type declarations for client destination functions +typedef void (*DST_INITIALIZE_FUNC) ( struct cl_enginefuncs_s**, int *); +typedef void (*DST_HUD_INIT_FUNC) ( void ); +typedef void (*DST_HUD_VIDINIT_FUNC) ( void ); +typedef void (*DST_HUD_REDRAW_FUNC) ( float*, int* ); +typedef void (*DST_HUD_UPDATECLIENTDATA_FUNC) ( struct client_data_s**, float* ); +typedef void (*DST_HUD_RESET_FUNC) ( void ); +typedef void (*DST_HUD_CLIENTMOVE_FUNC)( struct playermove_s **, qboolean * ); +typedef void (*DST_HUD_CLIENTMOVEINIT_FUNC)( struct playermove_s ** ); +typedef void (*DST_HUD_TEXTURETYPE_FUNC)( char ** ); +typedef void (*DST_HUD_IN_ACTIVATEMOUSE_FUNC) ( void ); +typedef void (*DST_HUD_IN_DEACTIVATEMOUSE_FUNC) ( void ); +typedef void (*DST_HUD_IN_MOUSEEVENT_FUNC) ( int * ); +typedef void (*DST_HUD_IN_CLEARSTATES_FUNC) ( void ); +typedef void (*DST_HUD_IN_ACCUMULATE_FUNC ) ( void ); +typedef void (*DST_HUD_CL_CREATEMOVE_FUNC) ( float *, struct usercmd_s **, int * ); +typedef void (*DST_HUD_CL_ISTHIRDPERSON_FUNC) ( void ); +typedef void (*DST_HUD_CL_GETCAMERAOFFSETS_FUNC )( float ** ); +typedef void (*DST_HUD_KB_FIND_FUNC) ( const char ** ); +typedef void (*DST_HUD_CAMTHINK_FUNC )( void ); +typedef void (*DST_HUD_CALCREF_FUNC ) ( struct ref_params_s ** ); +typedef void (*DST_HUD_ADDENTITY_FUNC ) ( int *, struct cl_entity_s **, const char ** ); +typedef void (*DST_HUD_CREATEENTITIES_FUNC ) ( void ); +typedef void (*DST_HUD_DRAWNORMALTRIS_FUNC ) ( void ); +typedef void (*DST_HUD_DRAWTRANSTRIS_FUNC ) ( void ); +typedef void (*DST_HUD_STUDIOEVENT_FUNC ) ( const struct mstudioevent_s **, const struct cl_entity_s ** ); +typedef void (*DST_HUD_POSTRUNCMD_FUNC ) ( struct local_state_s **, struct local_state_s **, struct usercmd_s **, int *, double *, unsigned int * ); +typedef void (*DST_HUD_SHUTDOWN_FUNC ) ( void ); +typedef void (*DST_HUD_TXFERLOCALOVERRIDES_FUNC )( struct entity_state_s **, const struct clientdata_s ** ); +typedef void (*DST_HUD_PROCESSPLAYERSTATE_FUNC )( struct entity_state_s **, const struct entity_state_s ** ); +typedef void (*DST_HUD_TXFERPREDICTIONDATA_FUNC ) ( struct entity_state_s **, const struct entity_state_s **, struct clientdata_s **, const struct clientdata_s **, struct weapon_data_s **, const struct weapon_data_s ** ); +typedef void (*DST_HUD_DEMOREAD_FUNC ) ( int *, unsigned char ** ); +typedef void (*DST_HUD_CONNECTIONLESS_FUNC )( const struct netadr_s **, const char **, char **, int ** ); +typedef void (*DST_HUD_GETHULLBOUNDS_FUNC ) ( int *, float **, float ** ); +typedef void (*DST_HUD_FRAME_FUNC) ( double * ); +typedef void (*DST_HUD_KEY_EVENT_FUNC ) ( int *, int *, const char ** ); +typedef void (*DST_HUD_TEMPENTUPDATE_FUNC) ( double *, double *, double *, struct tempent_s ***, struct tempent_s ***, int ( **Callback_AddVisibleEntity )( struct cl_entity_s *pEntity ), void ( **Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp ) ); +typedef void (*DST_HUD_GETUSERENTITY_FUNC ) ( int * ); +typedef void (*DST_HUD_VOICESTATUS_FUNC)(int *, qboolean *); +typedef void (*DST_HUD_DIRECTORMESSAGE_FUNC)( int *, void ** ); +typedef void (*DST_HUD_STUDIO_INTERFACE_FUNC ) ( int *, struct r_studio_interface_s ***, struct engine_studio_api_s ** ); +typedef void (*DST_HUD_CHATINPUTPOSITION_FUNC)( int **, int ** ); +typedef void (*DST_HUD_GETPLAYERTEAM)(int); + +// Pointers to the client destination functions +typedef struct +{ + DST_INITIALIZE_FUNC pInitFunc; + DST_HUD_INIT_FUNC pHudInitFunc; + DST_HUD_VIDINIT_FUNC pHudVidInitFunc; + DST_HUD_REDRAW_FUNC pHudRedrawFunc; + DST_HUD_UPDATECLIENTDATA_FUNC pHudUpdateClientDataFunc; + DST_HUD_RESET_FUNC pHudResetFunc; + DST_HUD_CLIENTMOVE_FUNC pClientMove; + DST_HUD_CLIENTMOVEINIT_FUNC pClientMoveInit; + DST_HUD_TEXTURETYPE_FUNC pClientTextureType; + DST_HUD_IN_ACTIVATEMOUSE_FUNC pIN_ActivateMouse; + DST_HUD_IN_DEACTIVATEMOUSE_FUNC pIN_DeactivateMouse; + DST_HUD_IN_MOUSEEVENT_FUNC pIN_MouseEvent; + DST_HUD_IN_CLEARSTATES_FUNC pIN_ClearStates; + DST_HUD_IN_ACCUMULATE_FUNC pIN_Accumulate; + DST_HUD_CL_CREATEMOVE_FUNC pCL_CreateMove; + DST_HUD_CL_ISTHIRDPERSON_FUNC pCL_IsThirdPerson; + DST_HUD_CL_GETCAMERAOFFSETS_FUNC pCL_GetCameraOffsets; + DST_HUD_KB_FIND_FUNC pFindKey; + DST_HUD_CAMTHINK_FUNC pCamThink; + DST_HUD_CALCREF_FUNC pCalcRefdef; + DST_HUD_ADDENTITY_FUNC pAddEntity; + DST_HUD_CREATEENTITIES_FUNC pCreateEntities; + DST_HUD_DRAWNORMALTRIS_FUNC pDrawNormalTriangles; + DST_HUD_DRAWTRANSTRIS_FUNC pDrawTransparentTriangles; + DST_HUD_STUDIOEVENT_FUNC pStudioEvent; + DST_HUD_POSTRUNCMD_FUNC pPostRunCmd; + DST_HUD_SHUTDOWN_FUNC pShutdown; + DST_HUD_TXFERLOCALOVERRIDES_FUNC pTxferLocalOverrides; + DST_HUD_PROCESSPLAYERSTATE_FUNC pProcessPlayerState; + DST_HUD_TXFERPREDICTIONDATA_FUNC pTxferPredictionData; + DST_HUD_DEMOREAD_FUNC pReadDemoBuffer; + DST_HUD_CONNECTIONLESS_FUNC pConnectionlessPacket; + DST_HUD_GETHULLBOUNDS_FUNC pGetHullBounds; + DST_HUD_FRAME_FUNC pHudFrame; + DST_HUD_KEY_EVENT_FUNC pKeyEvent; + DST_HUD_TEMPENTUPDATE_FUNC pTempEntUpdate; + DST_HUD_GETUSERENTITY_FUNC pGetUserEntity; + DST_HUD_VOICESTATUS_FUNC pVoiceStatus; // Possibly null on old client dlls. + DST_HUD_DIRECTORMESSAGE_FUNC pDirectorMessage; // Possibly null on old client dlls. + DST_HUD_STUDIO_INTERFACE_FUNC pStudioInterface; // Not used by all clients + DST_HUD_CHATINPUTPOSITION_FUNC pChatInputPosition; // Not used by all clients + DST_HUD_GETPLAYERTEAM pGetPlayerTeam; // Not used by all clients +} cldll_func_dst_t; + + + + +// ******************************************************** +// Functions exported by the engine +// ******************************************************** + +// Function type declarations for engine exports +typedef HSPRITE (*pfnEngSrc_pfnSPR_Load_t ) ( const char *szPicName ); +typedef int (*pfnEngSrc_pfnSPR_Frames_t ) ( HSPRITE hPic ); +typedef int (*pfnEngSrc_pfnSPR_Height_t ) ( HSPRITE hPic, int frame ); +typedef int (*pfnEngSrc_pfnSPR_Width_t ) ( HSPRITE hPic, int frame ); +typedef void (*pfnEngSrc_pfnSPR_Set_t ) ( HSPRITE hPic, int r, int g, int b ); +typedef void (*pfnEngSrc_pfnSPR_Draw_t ) ( int frame, int x, int y, const struct rect_s *prc ); +typedef void (*pfnEngSrc_pfnSPR_DrawHoles_t ) ( int frame, int x, int y, const struct rect_s *prc ); +typedef void (*pfnEngSrc_pfnSPR_DrawAdditive_t ) ( int frame, int x, int y, const struct rect_s *prc ); +typedef void (*pfnEngSrc_pfnSPR_EnableScissor_t ) ( int x, int y, int width, int height ); +typedef void (*pfnEngSrc_pfnSPR_DisableScissor_t ) ( void ); +typedef struct client_sprite_s * (*pfnEngSrc_pfnSPR_GetList_t ) ( char *psz, int *piCount ); +typedef void (*pfnEngSrc_pfnFillRGBA_t ) ( int x, int y, int width, int height, int r, int g, int b, int a ); +typedef int (*pfnEngSrc_pfnGetScreenInfo_t ) ( struct SCREENINFO_s *pscrinfo ); +typedef void (*pfnEngSrc_pfnSetCrosshair_t ) ( HSPRITE hspr, wrect_t rc, int r, int g, int b ); +typedef struct cvar_s * (*pfnEngSrc_pfnRegisterVariable_t ) ( char *szName, char *szValue, int flags ); +typedef float (*pfnEngSrc_pfnGetCvarFloat_t ) ( char *szName ); +typedef char* (*pfnEngSrc_pfnGetCvarString_t ) ( char *szName ); +typedef int (*pfnEngSrc_pfnAddCommand_t ) ( char *cmd_name, void (*pfnEngSrc_function)(void) ); +typedef int (*pfnEngSrc_pfnHookUserMsg_t ) ( char *szMsgName, pfnUserMsgHook pfn ); +typedef int (*pfnEngSrc_pfnServerCmd_t ) ( char *szCmdString ); +typedef int (*pfnEngSrc_pfnClientCmd_t ) ( char *szCmdString ); +typedef void (*pfnEngSrc_pfnPrimeMusicStream_t ) ( char *szFilename, int looping ); +typedef void (*pfnEngSrc_pfnGetPlayerInfo_t ) ( int ent_num, struct hud_player_info_s *pinfo ); +typedef void (*pfnEngSrc_pfnPlaySoundByName_t ) ( char *szSound, float volume ); +typedef void (*pfnEngSrc_pfnPlaySoundByNameAtPitch_t ) ( char *szSound, float volume, int pitch ); +typedef void (*pfnEngSrc_pfnPlaySoundVoiceByName_t ) ( char *szSound, float volume, int pitch ); +typedef void (*pfnEngSrc_pfnPlaySoundByIndex_t ) ( int iSound, float volume ); +typedef void (*pfnEngSrc_pfnAngleVectors_t ) ( const float * vecAngles, float * forward, float * right, float * up ); +typedef struct client_textmessage_s*(*pfnEngSrc_pfnTextMessageGet_t ) ( const char *pName ); +typedef int (*pfnEngSrc_pfnDrawCharacter_t ) ( int x, int y, int number, int r, int g, int b ); +typedef int (*pfnEngSrc_pfnDrawConsoleString_t ) ( int x, int y, char *string ); +typedef void (*pfnEngSrc_pfnDrawSetTextColor_t ) ( float r, float g, float b ); +typedef void (*pfnEngSrc_pfnDrawConsoleStringLen_t )( const char *string, int *length, int *height ); +typedef void (*pfnEngSrc_pfnConsolePrint_t ) ( const char *string ); +typedef void (*pfnEngSrc_pfnCenterPrint_t ) ( const char *string ); +typedef int (*pfnEngSrc_GetWindowCenterX_t ) ( void ); +typedef int (*pfnEngSrc_GetWindowCenterY_t ) ( void ); +typedef void (*pfnEngSrc_GetViewAngles_t ) ( float * ); +typedef void (*pfnEngSrc_SetViewAngles_t ) ( float * ); +typedef int (*pfnEngSrc_GetMaxClients_t ) ( void ); +typedef void (*pfnEngSrc_Cvar_SetValue_t ) ( char *cvar, float value ); +typedef int (*pfnEngSrc_Cmd_Argc_t) (void); +typedef char * (*pfnEngSrc_Cmd_Argv_t ) ( int arg ); +typedef void (*pfnEngSrc_Con_Printf_t ) ( char *fmt, ... ); +typedef void (*pfnEngSrc_Con_DPrintf_t ) ( char *fmt, ... ); +typedef void (*pfnEngSrc_Con_NPrintf_t ) ( int pos, char *fmt, ... ); +typedef void (*pfnEngSrc_Con_NXPrintf_t ) ( struct con_nprint_s *info, char *fmt, ... ); +typedef const char * (*pfnEngSrc_PhysInfo_ValueForKey_t ) ( const char *key ); +typedef const char * (*pfnEngSrc_ServerInfo_ValueForKey_t )( const char *key ); +typedef float (*pfnEngSrc_GetClientMaxspeed_t ) ( void ); +typedef int (*pfnEngSrc_CheckParm_t ) ( char *parm, char **ppnext ); +typedef void (*pfnEngSrc_Key_Event_t ) ( int key, int down ); +typedef void (*pfnEngSrc_GetMousePosition_t ) ( int *mx, int *my ); +typedef int (*pfnEngSrc_IsNoClipping_t ) ( void ); +typedef struct cl_entity_s * (*pfnEngSrc_GetLocalPlayer_t ) ( void ); +typedef struct cl_entity_s * (*pfnEngSrc_GetViewModel_t ) ( void ); +typedef struct cl_entity_s * (*pfnEngSrc_GetEntityByIndex_t ) ( int idx ); +typedef float (*pfnEngSrc_GetClientTime_t ) ( void ); +typedef void (*pfnEngSrc_V_CalcShake_t ) ( void ); +typedef void (*pfnEngSrc_V_ApplyShake_t ) ( float *origin, float *angles, float factor ); +typedef int (*pfnEngSrc_PM_PointContents_t ) ( float *point, int *truecontents ); +typedef int (*pfnEngSrc_PM_WaterEntity_t ) ( float *p ); +typedef struct pmtrace_s * (*pfnEngSrc_PM_TraceLine_t ) ( float *start, float *end, int flags, int usehull, int ignore_pe ); +typedef struct model_s * (*pfnEngSrc_CL_LoadModel_t ) ( const char *modelname, int *index ); +typedef int (*pfnEngSrc_CL_CreateVisibleEntity_t ) ( int type, struct cl_entity_s *ent ); +typedef const struct model_s * (*pfnEngSrc_GetSpritePointer_t ) ( HSPRITE hSprite ); +typedef void (*pfnEngSrc_pfnPlaySoundByNameAtLocation_t ) ( char *szSound, float volume, float *origin ); +typedef unsigned short (*pfnEngSrc_pfnPrecacheEvent_t ) ( int type, const char* psz ); +typedef void (*pfnEngSrc_pfnPlaybackEvent_t ) ( int flags, const struct edict_s *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); +typedef void (*pfnEngSrc_pfnWeaponAnim_t ) ( int iAnim, int body ); +typedef float (*pfnEngSrc_pfnRandomFloat_t ) ( float flLow, float flHigh ); +typedef int32 (*pfnEngSrc_pfnRandomLong_t ) ( int32 lLow, int32 lHigh ); +typedef void (*pfnEngSrc_pfnHookEvent_t ) ( char *name, void ( *pfnEvent )( struct event_args_s *args ) ); +typedef int (*pfnEngSrc_Con_IsVisible_t) (); +typedef const char * (*pfnEngSrc_pfnGetGameDirectory_t ) ( void ); +typedef struct cvar_s * (*pfnEngSrc_pfnGetCvarPointer_t ) ( const char *szName ); +typedef const char * (*pfnEngSrc_Key_LookupBinding_t ) ( const char *pBinding ); +typedef const char * (*pfnEngSrc_pfnGetLevelName_t ) ( void ); +typedef void (*pfnEngSrc_pfnGetScreenFade_t ) ( struct screenfade_s *fade ); +typedef void (*pfnEngSrc_pfnSetScreenFade_t ) ( struct screenfade_s *fade ); +typedef void * (*pfnEngSrc_VGui_GetPanel_t ) ( ); +typedef void (*pfnEngSrc_VGui_ViewportPaintBackground_t ) (int extents[4]); +typedef byte* (*pfnEngSrc_COM_LoadFile_t ) ( char *path, int usehunk, int *pLength ); +typedef char* (*pfnEngSrc_COM_ParseFile_t ) ( char *data, char *token ); +typedef void (*pfnEngSrc_COM_FreeFile_t) ( void *buffer ); +typedef struct triangleapi_s * pTriAPI; +typedef struct efx_api_s * pEfxAPI; +typedef struct event_api_s * pEventAPI; +typedef struct demo_api_s * pDemoAPI; +typedef struct net_api_s * pNetAPI; +typedef struct IVoiceTweak_s * pVoiceTweak; +typedef int (*pfnEngSrc_IsSpectateOnly_t ) ( void ); +typedef struct model_s * (*pfnEngSrc_LoadMapSprite_t ) ( const char *filename ); +typedef void (*pfnEngSrc_COM_AddAppDirectoryToSearchPath_t ) ( const char *pszBaseDir, const char *appName ); +typedef int (*pfnEngSrc_COM_ExpandFilename_t) ( const char *fileName, char *nameOutBuffer, int nameOutBufferSize ); +typedef const char * (*pfnEngSrc_PlayerInfo_ValueForKey_t )( int playerNum, const char *key ); +typedef void (*pfnEngSrc_PlayerInfo_SetValueForKey_t )( const char *key, const char *value ); +typedef qboolean (*pfnEngSrc_GetPlayerUniqueID_t)(int iPlayer, char playerID[16]); +typedef int (*pfnEngSrc_GetTrackerIDForPlayer_t)(int playerSlot); +typedef int (*pfnEngSrc_GetPlayerForTrackerID_t)(int trackerID); +typedef int (*pfnEngSrc_pfnServerCmdUnreliable_t )( char *szCmdString ); +typedef void (*pfnEngSrc_GetMousePos_t )(struct tagPOINT *ppt); +typedef void (*pfnEngSrc_SetMousePos_t )(int x, int y); +typedef void (*pfnEngSrc_SetMouseEnable_t)(qboolean fEnable); +typedef struct cvar_s * (*pfnEngSrc_GetFirstCVarPtr_t)(); +typedef unsigned int (*pfnEngSrc_GetFirstCmdFunctionHandle_t)(); +typedef unsigned int (*pfnEngSrc_GetNextCmdFunctionHandle_t)(unsigned int cmdhandle); +typedef const char * (*pfnEngSrc_GetCmdFunctionName_t)(unsigned int cmdhandle); +typedef float (*pfnEngSrc_GetClientOldTime_t)(); +typedef float (*pfnEngSrc_GetServerGravityValue_t)(); +typedef struct model_s * (*pfnEngSrc_GetModelByIndex_t)( int index ); +typedef void (*pfnEngSrc_pfnSetFilterMode_t )( int mode ); +typedef void (*pfnEngSrc_pfnSetFilterColor_t )( float r, float g, float b ); +typedef void (*pfnEngSrc_pfnSetFilterBrightness_t )( float brightness ); +typedef sequenceEntry_s* (*pfnEngSrc_pfnSequenceGet_t )( const char *fileName, const char* entryName ); +typedef void (*pfnEngSrc_pfnSPR_DrawGeneric_t )( int frame, int x, int y, const struct rect_s *prc, int src, int dest, int w, int h ); +typedef sentenceEntry_s* (*pfnEngSrc_pfnSequencePickSentence_t )( const char *sentenceName, int pickMethod, int* entryPicked ); +// draw a complete string +typedef int (*pfnEngSrc_pfnDrawString_t ) ( int x, int y, const char *str, int r, int g, int b ); +typedef int (*pfnEngSrc_pfnDrawStringReverse_t ) ( int x, int y, const char *str, int r, int g, int b ); +typedef const char * (*pfnEngSrc_LocalPlayerInfo_ValueForKey_t )( const char *key ); +typedef int (*pfnEngSrc_pfnVGUI2DrawCharacter_t ) ( int x, int y, int ch, unsigned int font ); +typedef int (*pfnEngSrc_pfnVGUI2DrawCharacterAdd_t ) ( int x, int y, int ch, int r, int g, int b, unsigned int font); +typedef unsigned int (*pfnEngSrc_COM_GetApproxWavePlayLength ) ( const char * filename); +typedef void * (*pfnEngSrc_pfnGetCareerUI_t)(); +typedef void (*pfnEngSrc_Cvar_Set_t ) ( char *cvar, char *value ); +typedef int (*pfnEngSrc_pfnIsPlayingCareerMatch_t)(); +typedef double (*pfnEngSrc_GetAbsoluteTime_t) ( void ); +typedef void (*pfnEngSrc_pfnProcessTutorMessageDecayBuffer_t)(int *buffer, int bufferLength); +typedef void (*pfnEngSrc_pfnConstructTutorMessageDecayBuffer_t)(int *buffer, int bufferLength); +typedef void (*pfnEngSrc_pfnResetTutorMessageDecayData_t)(); +typedef void (*pfnEngSrc_pfnFillRGBABlend_t ) ( int x, int y, int width, int height, int r, int g, int b, int a ); +typedef int (*pfnEngSrc_pfnGetAppID_t) ( void ); +typedef cmdalias_t* (*pfnEngSrc_pfnGetAliases_t) ( void ); +typedef void (*pfnEngSrc_pfnVguiWrap2_GetMouseDelta_t) ( int *x, int *y ); + +// Pointers to the exported engine functions themselves +typedef struct cl_enginefuncs_s +{ + pfnEngSrc_pfnSPR_Load_t pfnSPR_Load; + pfnEngSrc_pfnSPR_Frames_t pfnSPR_Frames; + pfnEngSrc_pfnSPR_Height_t pfnSPR_Height; + pfnEngSrc_pfnSPR_Width_t pfnSPR_Width; + pfnEngSrc_pfnSPR_Set_t pfnSPR_Set; + pfnEngSrc_pfnSPR_Draw_t pfnSPR_Draw; + pfnEngSrc_pfnSPR_DrawHoles_t pfnSPR_DrawHoles; + pfnEngSrc_pfnSPR_DrawAdditive_t pfnSPR_DrawAdditive; + pfnEngSrc_pfnSPR_EnableScissor_t pfnSPR_EnableScissor; + pfnEngSrc_pfnSPR_DisableScissor_t pfnSPR_DisableScissor; + pfnEngSrc_pfnSPR_GetList_t pfnSPR_GetList; + pfnEngSrc_pfnFillRGBA_t pfnFillRGBA; + pfnEngSrc_pfnGetScreenInfo_t pfnGetScreenInfo; + pfnEngSrc_pfnSetCrosshair_t pfnSetCrosshair; + pfnEngSrc_pfnRegisterVariable_t pfnRegisterVariable; + pfnEngSrc_pfnGetCvarFloat_t pfnGetCvarFloat; + pfnEngSrc_pfnGetCvarString_t pfnGetCvarString; + pfnEngSrc_pfnAddCommand_t pfnAddCommand; + pfnEngSrc_pfnHookUserMsg_t pfnHookUserMsg; + pfnEngSrc_pfnServerCmd_t pfnServerCmd; + pfnEngSrc_pfnClientCmd_t pfnClientCmd; + pfnEngSrc_pfnGetPlayerInfo_t pfnGetPlayerInfo; + pfnEngSrc_pfnPlaySoundByName_t pfnPlaySoundByName; + pfnEngSrc_pfnPlaySoundByIndex_t pfnPlaySoundByIndex; + pfnEngSrc_pfnAngleVectors_t pfnAngleVectors; + pfnEngSrc_pfnTextMessageGet_t pfnTextMessageGet; + pfnEngSrc_pfnDrawCharacter_t pfnDrawCharacter; + pfnEngSrc_pfnDrawConsoleString_t pfnDrawConsoleString; + pfnEngSrc_pfnDrawSetTextColor_t pfnDrawSetTextColor; + pfnEngSrc_pfnDrawConsoleStringLen_t pfnDrawConsoleStringLen; + pfnEngSrc_pfnConsolePrint_t pfnConsolePrint; + pfnEngSrc_pfnCenterPrint_t pfnCenterPrint; + pfnEngSrc_GetWindowCenterX_t GetWindowCenterX; + pfnEngSrc_GetWindowCenterY_t GetWindowCenterY; + pfnEngSrc_GetViewAngles_t GetViewAngles; + pfnEngSrc_SetViewAngles_t SetViewAngles; + pfnEngSrc_GetMaxClients_t GetMaxClients; + pfnEngSrc_Cvar_SetValue_t Cvar_SetValue; + pfnEngSrc_Cmd_Argc_t Cmd_Argc; + pfnEngSrc_Cmd_Argv_t Cmd_Argv; + pfnEngSrc_Con_Printf_t Con_Printf; + pfnEngSrc_Con_DPrintf_t Con_DPrintf; + pfnEngSrc_Con_NPrintf_t Con_NPrintf; + pfnEngSrc_Con_NXPrintf_t Con_NXPrintf; + pfnEngSrc_PhysInfo_ValueForKey_t PhysInfo_ValueForKey; + pfnEngSrc_ServerInfo_ValueForKey_t ServerInfo_ValueForKey; + pfnEngSrc_GetClientMaxspeed_t GetClientMaxspeed; + pfnEngSrc_CheckParm_t CheckParm; + pfnEngSrc_Key_Event_t Key_Event; + pfnEngSrc_GetMousePosition_t GetMousePosition; + pfnEngSrc_IsNoClipping_t IsNoClipping; + pfnEngSrc_GetLocalPlayer_t GetLocalPlayer; + pfnEngSrc_GetViewModel_t GetViewModel; + pfnEngSrc_GetEntityByIndex_t GetEntityByIndex; + pfnEngSrc_GetClientTime_t GetClientTime; + pfnEngSrc_V_CalcShake_t V_CalcShake; + pfnEngSrc_V_ApplyShake_t V_ApplyShake; + pfnEngSrc_PM_PointContents_t PM_PointContents; + pfnEngSrc_PM_WaterEntity_t PM_WaterEntity; + pfnEngSrc_PM_TraceLine_t PM_TraceLine; + pfnEngSrc_CL_LoadModel_t CL_LoadModel; + pfnEngSrc_CL_CreateVisibleEntity_t CL_CreateVisibleEntity; + pfnEngSrc_GetSpritePointer_t GetSpritePointer; + pfnEngSrc_pfnPlaySoundByNameAtLocation_t pfnPlaySoundByNameAtLocation; + pfnEngSrc_pfnPrecacheEvent_t pfnPrecacheEvent; + pfnEngSrc_pfnPlaybackEvent_t pfnPlaybackEvent; + pfnEngSrc_pfnWeaponAnim_t pfnWeaponAnim; + pfnEngSrc_pfnRandomFloat_t pfnRandomFloat; + pfnEngSrc_pfnRandomLong_t pfnRandomLong; + pfnEngSrc_pfnHookEvent_t pfnHookEvent; + pfnEngSrc_Con_IsVisible_t Con_IsVisible; + pfnEngSrc_pfnGetGameDirectory_t pfnGetGameDirectory; + pfnEngSrc_pfnGetCvarPointer_t pfnGetCvarPointer; + pfnEngSrc_Key_LookupBinding_t Key_LookupBinding; + pfnEngSrc_pfnGetLevelName_t pfnGetLevelName; + pfnEngSrc_pfnGetScreenFade_t pfnGetScreenFade; + pfnEngSrc_pfnSetScreenFade_t pfnSetScreenFade; + pfnEngSrc_VGui_GetPanel_t VGui_GetPanel; + pfnEngSrc_VGui_ViewportPaintBackground_t VGui_ViewportPaintBackground; + pfnEngSrc_COM_LoadFile_t COM_LoadFile; + pfnEngSrc_COM_ParseFile_t COM_ParseFile; + pfnEngSrc_COM_FreeFile_t COM_FreeFile; + struct triangleapi_s *pTriAPI; + struct efx_api_s *pEfxAPI; + struct event_api_s *pEventAPI; + struct demo_api_s *pDemoAPI; + struct net_api_s *pNetAPI; + struct IVoiceTweak_s *pVoiceTweak; + pfnEngSrc_IsSpectateOnly_t IsSpectateOnly; + pfnEngSrc_LoadMapSprite_t LoadMapSprite; + pfnEngSrc_COM_AddAppDirectoryToSearchPath_t COM_AddAppDirectoryToSearchPath; + pfnEngSrc_COM_ExpandFilename_t COM_ExpandFilename; + pfnEngSrc_PlayerInfo_ValueForKey_t PlayerInfo_ValueForKey; + pfnEngSrc_PlayerInfo_SetValueForKey_t PlayerInfo_SetValueForKey; + pfnEngSrc_GetPlayerUniqueID_t GetPlayerUniqueID; + pfnEngSrc_GetTrackerIDForPlayer_t GetTrackerIDForPlayer; + pfnEngSrc_GetPlayerForTrackerID_t GetPlayerForTrackerID; + pfnEngSrc_pfnServerCmdUnreliable_t pfnServerCmdUnreliable; + pfnEngSrc_GetMousePos_t pfnGetMousePos; + pfnEngSrc_SetMousePos_t pfnSetMousePos; + pfnEngSrc_SetMouseEnable_t pfnSetMouseEnable; + pfnEngSrc_GetFirstCVarPtr_t GetFirstCvarPtr; + pfnEngSrc_GetFirstCmdFunctionHandle_t GetFirstCmdFunctionHandle; + pfnEngSrc_GetNextCmdFunctionHandle_t GetNextCmdFunctionHandle; + pfnEngSrc_GetCmdFunctionName_t GetCmdFunctionName; + pfnEngSrc_GetClientOldTime_t hudGetClientOldTime; + pfnEngSrc_GetServerGravityValue_t hudGetServerGravityValue; + pfnEngSrc_GetModelByIndex_t hudGetModelByIndex; + pfnEngSrc_pfnSetFilterMode_t pfnSetFilterMode; + pfnEngSrc_pfnSetFilterColor_t pfnSetFilterColor; + pfnEngSrc_pfnSetFilterBrightness_t pfnSetFilterBrightness; + pfnEngSrc_pfnSequenceGet_t pfnSequenceGet; + pfnEngSrc_pfnSPR_DrawGeneric_t pfnSPR_DrawGeneric; + pfnEngSrc_pfnSequencePickSentence_t pfnSequencePickSentence; + pfnEngSrc_pfnDrawString_t pfnDrawString; + pfnEngSrc_pfnDrawStringReverse_t pfnDrawStringReverse; + pfnEngSrc_LocalPlayerInfo_ValueForKey_t LocalPlayerInfo_ValueForKey; + pfnEngSrc_pfnVGUI2DrawCharacter_t pfnVGUI2DrawCharacter; + pfnEngSrc_pfnVGUI2DrawCharacterAdd_t pfnVGUI2DrawCharacterAdd; + pfnEngSrc_COM_GetApproxWavePlayLength COM_GetApproxWavePlayLength; + pfnEngSrc_pfnGetCareerUI_t pfnGetCareerUI; + pfnEngSrc_Cvar_Set_t Cvar_Set; + pfnEngSrc_pfnIsPlayingCareerMatch_t pfnIsCareerMatch; + pfnEngSrc_pfnPlaySoundVoiceByName_t pfnPlaySoundVoiceByName; + pfnEngSrc_pfnPrimeMusicStream_t pfnPrimeMusicStream; + pfnEngSrc_GetAbsoluteTime_t GetAbsoluteTime; + pfnEngSrc_pfnProcessTutorMessageDecayBuffer_t pfnProcessTutorMessageDecayBuffer; + pfnEngSrc_pfnConstructTutorMessageDecayBuffer_t pfnConstructTutorMessageDecayBuffer; + pfnEngSrc_pfnResetTutorMessageDecayData_t pfnResetTutorMessageDecayData; + pfnEngSrc_pfnPlaySoundByNameAtPitch_t pfnPlaySoundByNameAtPitch; + pfnEngSrc_pfnFillRGBABlend_t pfnFillRGBABlend; + pfnEngSrc_pfnGetAppID_t pfnGetAppID; + pfnEngSrc_pfnGetAliases_t pfnGetAliasList; + pfnEngSrc_pfnVguiWrap2_GetMouseDelta_t pfnVguiWrap2_GetMouseDelta; +} cl_enginefunc_t; + +// Function type declarations for engine destination functions +typedef void (*pfnEngDst_pfnSPR_Load_t ) ( const char ** ); +typedef void (*pfnEngDst_pfnSPR_Frames_t ) ( HSPRITE * ); +typedef void (*pfnEngDst_pfnSPR_Height_t ) ( HSPRITE *, int * ); +typedef void (*pfnEngDst_pfnSPR_Width_t ) ( HSPRITE *, int * ); +typedef void (*pfnEngDst_pfnSPR_Set_t ) ( HSPRITE *, int *, int *, int * ); +typedef void (*pfnEngDst_pfnSPR_Draw_t ) ( int *, int *, int *, const struct rect_s ** ); +typedef void (*pfnEngDst_pfnSPR_DrawHoles_t ) ( int *, int *, int *, const struct rect_s ** ); +typedef void (*pfnEngDst_pfnSPR_DrawAdditive_t ) ( int *, int *, int *, const struct rect_s ** ); +typedef void (*pfnEngDst_pfnSPR_EnableScissor_t ) ( int *, int *, int *, int * ); +typedef void (*pfnEngDst_pfnSPR_DisableScissor_t ) ( void ); +typedef void (*pfnEngDst_pfnSPR_GetList_t ) ( char **, int ** ); +typedef void (*pfnEngDst_pfnFillRGBA_t ) ( int *, int *, int *, int *, int *, int *, int *, int * ); +typedef void (*pfnEngDst_pfnGetScreenInfo_t ) ( struct SCREENINFO_s ** ); +typedef void (*pfnEngDst_pfnSetCrosshair_t ) ( HSPRITE *, struct rect_s *, int *, int *, int * ); +typedef void (*pfnEngDst_pfnRegisterVariable_t ) ( char **, char **, int * ); +typedef void (*pfnEngDst_pfnGetCvarFloat_t ) ( char ** ); +typedef void (*pfnEngDst_pfnGetCvarString_t ) ( char ** ); +typedef void (*pfnEngDst_pfnAddCommand_t ) ( char **, void (**pfnEngDst_function)(void) ); +typedef void (*pfnEngDst_pfnHookUserMsg_t ) ( char **, pfnUserMsgHook * ); +typedef void (*pfnEngDst_pfnServerCmd_t ) ( char ** ); +typedef void (*pfnEngDst_pfnClientCmd_t ) ( char ** ); +typedef void (*pfnEngDst_pfnPrimeMusicStream_t ) ( char **, int *); +typedef void (*pfnEngDst_pfnGetPlayerInfo_t ) ( int *, struct hud_player_info_s ** ); +typedef void (*pfnEngDst_pfnPlaySoundByName_t ) ( char **, float * ); +typedef void (*pfnEngDst_pfnPlaySoundByNameAtPitch_t ) ( char **, float *, int * ); +typedef void (*pfnEngDst_pfnPlaySoundVoiceByName_t ) (char **, float * ); +typedef void (*pfnEngDst_pfnPlaySoundByIndex_t ) ( int *, float * ); +typedef void (*pfnEngDst_pfnAngleVectors_t ) ( const float * *, float * *, float * *, float * * ); +typedef void (*pfnEngDst_pfnTextMessageGet_t ) ( const char ** ); +typedef void (*pfnEngDst_pfnDrawCharacter_t ) ( int *, int *, int *, int *, int *, int * ); +typedef void (*pfnEngDst_pfnDrawConsoleString_t ) ( int *, int *, char ** ); +typedef void (*pfnEngDst_pfnDrawSetTextColor_t ) ( float *, float *, float * ); +typedef void (*pfnEngDst_pfnDrawConsoleStringLen_t ) ( const char **, int **, int ** ); +typedef void (*pfnEngDst_pfnConsolePrint_t ) ( const char ** ); +typedef void (*pfnEngDst_pfnCenterPrint_t ) ( const char ** ); +typedef void (*pfnEngDst_GetWindowCenterX_t ) ( void ); +typedef void (*pfnEngDst_GetWindowCenterY_t ) ( void ); +typedef void (*pfnEngDst_GetViewAngles_t ) ( float ** ); +typedef void (*pfnEngDst_SetViewAngles_t ) ( float ** ); +typedef void (*pfnEngDst_GetMaxClients_t ) ( void ); +typedef void (*pfnEngDst_Cvar_SetValue_t ) ( char **, float * ); +typedef void (*pfnEngDst_Cmd_Argc_t) (void); +typedef void (*pfnEngDst_Cmd_Argv_t ) ( int * ); +typedef void (*pfnEngDst_Con_Printf_t ) ( char **); +typedef void (*pfnEngDst_Con_DPrintf_t ) ( char **); +typedef void (*pfnEngDst_Con_NPrintf_t ) ( int *, char ** ); +typedef void (*pfnEngDst_Con_NXPrintf_t ) ( struct con_nprint_s **, char **); +typedef void (*pfnEngDst_PhysInfo_ValueForKey_t ) ( const char ** ); +typedef void (*pfnEngDst_ServerInfo_ValueForKey_t ) ( const char ** ); +typedef void (*pfnEngDst_GetClientMaxspeed_t ) ( void ); +typedef void (*pfnEngDst_CheckParm_t ) ( char **, char *** ); +typedef void (*pfnEngDst_Key_Event_t ) ( int *, int * ); +typedef void (*pfnEngDst_GetMousePosition_t ) ( int **, int ** ); +typedef void (*pfnEngDst_IsNoClipping_t ) ( void ); +typedef void (*pfnEngDst_GetLocalPlayer_t ) ( void ); +typedef void (*pfnEngDst_GetViewModel_t ) ( void ); +typedef void (*pfnEngDst_GetEntityByIndex_t ) ( int * ); +typedef void (*pfnEngDst_GetClientTime_t ) ( void ); +typedef void (*pfnEngDst_V_CalcShake_t ) ( void ); +typedef void (*pfnEngDst_V_ApplyShake_t ) ( float **, float **, float * ); +typedef void (*pfnEngDst_PM_PointContents_t ) ( float **, int ** ); +typedef void (*pfnEngDst_PM_WaterEntity_t ) ( float ** ); +typedef void (*pfnEngDst_PM_TraceLine_t ) ( float **, float **, int *, int *, int * ); +typedef void (*pfnEngDst_CL_LoadModel_t ) ( const char **, int ** ); +typedef void (*pfnEngDst_CL_CreateVisibleEntity_t ) ( int *, struct cl_entity_s ** ); +typedef void (*pfnEngDst_GetSpritePointer_t ) ( HSPRITE * ); +typedef void (*pfnEngDst_pfnPlaySoundByNameAtLocation_t ) ( char **, float *, float ** ); +typedef void (*pfnEngDst_pfnPrecacheEvent_t ) ( int *, const char* * ); +typedef void (*pfnEngDst_pfnPlaybackEvent_t ) ( int *, const struct edict_s **, unsigned short *, float *, float **, float **, float *, float *, int *, int *, int *, int * ); +typedef void (*pfnEngDst_pfnWeaponAnim_t ) ( int *, int * ); +typedef void (*pfnEngDst_pfnRandomFloat_t ) ( float *, float * ); +typedef void (*pfnEngDst_pfnRandomLong_t ) ( int32 *, int32 * ); +typedef void (*pfnEngDst_pfnHookEvent_t ) ( char **, void ( **pfnEvent )( struct event_args_s *args ) ); +typedef void (*pfnEngDst_Con_IsVisible_t) (); +typedef void (*pfnEngDst_pfnGetGameDirectory_t ) ( void ); +typedef void (*pfnEngDst_pfnGetCvarPointer_t ) ( const char ** ); +typedef void (*pfnEngDst_Key_LookupBinding_t ) ( const char ** ); +typedef void (*pfnEngDst_pfnGetLevelName_t ) ( void ); +typedef void (*pfnEngDst_pfnGetScreenFade_t ) ( struct screenfade_s ** ); +typedef void (*pfnEngDst_pfnSetScreenFade_t ) ( struct screenfade_s ** ); +typedef void (*pfnEngDst_VGui_GetPanel_t ) ( ); +typedef void (*pfnEngDst_VGui_ViewportPaintBackground_t ) (int **); +typedef void (*pfnEngDst_COM_LoadFile_t ) ( char **, int *, int ** ); +typedef void (*pfnEngDst_COM_ParseFile_t ) ( char **, char ** ); +typedef void (*pfnEngDst_COM_FreeFile_t) ( void ** ); +typedef void (*pfnEngDst_IsSpectateOnly_t ) ( void ); +typedef void (*pfnEngDst_LoadMapSprite_t ) ( const char ** ); +typedef void (*pfnEngDst_COM_AddAppDirectoryToSearchPath_t ) ( const char **, const char ** ); +typedef void (*pfnEngDst_COM_ExpandFilename_t) ( const char **, char **, int * ); +typedef void (*pfnEngDst_PlayerInfo_ValueForKey_t ) ( int *, const char ** ); +typedef void (*pfnEngDst_PlayerInfo_SetValueForKey_t )( const char **, const char ** ); +typedef void (*pfnEngDst_GetPlayerUniqueID_t) (int *, char **); +typedef void (*pfnEngDst_GetTrackerIDForPlayer_t) (int *); +typedef void (*pfnEngDst_GetPlayerForTrackerID_t) (int *); +typedef void (*pfnEngDst_pfnServerCmdUnreliable_t ) ( char ** ); +typedef void (*pfnEngDst_GetMousePos_t ) (struct tagPOINT **); +typedef void (*pfnEngDst_SetMousePos_t ) (int *, int *); +typedef void (*pfnEngDst_SetMouseEnable_t ) (qboolean *); +typedef void (*pfnEngDst_pfnSetFilterMode_t) ( int * ); +typedef void (*pfnEngDst_pfnSetFilterColor_t) ( float *, float *, float * ); +typedef void (*pfnEngDst_pfnSetFilterBrightness_t) ( float * ); +typedef void (*pfnEngDst_pfnSequenceGet_t ) ( const char**, const char** ); +typedef void (*pfnEngDst_pfnSPR_DrawGeneric_t ) ( int *, int *, int *, const struct rect_s **, int *, int *, int *, int * ); +typedef void (*pfnEngDst_pfnSequencePickSentence_t ) ( const char**, int *, int ** ); +typedef void (*pfnEngDst_pfnDrawString_t ) ( int *, int *, const char *, int *, int *, int * ); +typedef void (*pfnEngDst_pfnDrawStringReverse_t ) ( int *, int *, const char *, int *, int *, int * ); +typedef void (*pfnEngDst_LocalPlayerInfo_ValueForKey_t )( const char **); +typedef void (*pfnEngDst_pfnVGUI2DrawCharacter_t ) ( int *, int *, int *, unsigned int * ); +typedef void (*pfnEngDst_pfnVGUI2DrawCharacterAdd_t ) ( int *, int *, int *, int *, int *, int *, unsigned int *); +typedef void (*pfnEngDst_pfnProcessTutorMessageDecayBuffer_t )(int **, int *); +typedef void (*pfnEngDst_pfnConstructTutorMessageDecayBuffer_t )(int **, int *); +typedef void (*pfnEngDst_pfnResetTutorMessageDecayData_t)(); +typedef void (*pfnEngDst_pfnFillRGBABlend_t ) ( int *, int *, int *, int *, int *, int *, int *, int * ); +typedef void (*pfnEngDst_pfnGetAppID_t ) ( void ); +typedef void (*pfnEngDst_pfnGetAliases_t ) ( void ); +typedef void (*pfnEngDst_pfnVguiWrap2_GetMouseDelta_t) ( int *x, int *y ); + + +// Pointers to the engine destination functions +typedef struct +{ + pfnEngDst_pfnSPR_Load_t pfnSPR_Load; + pfnEngDst_pfnSPR_Frames_t pfnSPR_Frames; + pfnEngDst_pfnSPR_Height_t pfnSPR_Height; + pfnEngDst_pfnSPR_Width_t pfnSPR_Width; + pfnEngDst_pfnSPR_Set_t pfnSPR_Set; + pfnEngDst_pfnSPR_Draw_t pfnSPR_Draw; + pfnEngDst_pfnSPR_DrawHoles_t pfnSPR_DrawHoles; + pfnEngDst_pfnSPR_DrawAdditive_t pfnSPR_DrawAdditive; + pfnEngDst_pfnSPR_EnableScissor_t pfnSPR_EnableScissor; + pfnEngDst_pfnSPR_DisableScissor_t pfnSPR_DisableScissor; + pfnEngDst_pfnSPR_GetList_t pfnSPR_GetList; + pfnEngDst_pfnFillRGBA_t pfnFillRGBA; + pfnEngDst_pfnGetScreenInfo_t pfnGetScreenInfo; + pfnEngDst_pfnSetCrosshair_t pfnSetCrosshair; + pfnEngDst_pfnRegisterVariable_t pfnRegisterVariable; + pfnEngDst_pfnGetCvarFloat_t pfnGetCvarFloat; + pfnEngDst_pfnGetCvarString_t pfnGetCvarString; + pfnEngDst_pfnAddCommand_t pfnAddCommand; + pfnEngDst_pfnHookUserMsg_t pfnHookUserMsg; + pfnEngDst_pfnServerCmd_t pfnServerCmd; + pfnEngDst_pfnClientCmd_t pfnClientCmd; + pfnEngDst_pfnGetPlayerInfo_t pfnGetPlayerInfo; + pfnEngDst_pfnPlaySoundByName_t pfnPlaySoundByName; + pfnEngDst_pfnPlaySoundByIndex_t pfnPlaySoundByIndex; + pfnEngDst_pfnAngleVectors_t pfnAngleVectors; + pfnEngDst_pfnTextMessageGet_t pfnTextMessageGet; + pfnEngDst_pfnDrawCharacter_t pfnDrawCharacter; + pfnEngDst_pfnDrawConsoleString_t pfnDrawConsoleString; + pfnEngDst_pfnDrawSetTextColor_t pfnDrawSetTextColor; + pfnEngDst_pfnDrawConsoleStringLen_t pfnDrawConsoleStringLen; + pfnEngDst_pfnConsolePrint_t pfnConsolePrint; + pfnEngDst_pfnCenterPrint_t pfnCenterPrint; + pfnEngDst_GetWindowCenterX_t GetWindowCenterX; + pfnEngDst_GetWindowCenterY_t GetWindowCenterY; + pfnEngDst_GetViewAngles_t GetViewAngles; + pfnEngDst_SetViewAngles_t SetViewAngles; + pfnEngDst_GetMaxClients_t GetMaxClients; + pfnEngDst_Cvar_SetValue_t Cvar_SetValue; + pfnEngDst_Cmd_Argc_t Cmd_Argc; + pfnEngDst_Cmd_Argv_t Cmd_Argv; + pfnEngDst_Con_Printf_t Con_Printf; + pfnEngDst_Con_DPrintf_t Con_DPrintf; + pfnEngDst_Con_NPrintf_t Con_NPrintf; + pfnEngDst_Con_NXPrintf_t Con_NXPrintf; + pfnEngDst_PhysInfo_ValueForKey_t PhysInfo_ValueForKey; + pfnEngDst_ServerInfo_ValueForKey_t ServerInfo_ValueForKey; + pfnEngDst_GetClientMaxspeed_t GetClientMaxspeed; + pfnEngDst_CheckParm_t CheckParm; + pfnEngDst_Key_Event_t Key_Event; + pfnEngDst_GetMousePosition_t GetMousePosition; + pfnEngDst_IsNoClipping_t IsNoClipping; + pfnEngDst_GetLocalPlayer_t GetLocalPlayer; + pfnEngDst_GetViewModel_t GetViewModel; + pfnEngDst_GetEntityByIndex_t GetEntityByIndex; + pfnEngDst_GetClientTime_t GetClientTime; + pfnEngDst_V_CalcShake_t V_CalcShake; + pfnEngDst_V_ApplyShake_t V_ApplyShake; + pfnEngDst_PM_PointContents_t PM_PointContents; + pfnEngDst_PM_WaterEntity_t PM_WaterEntity; + pfnEngDst_PM_TraceLine_t PM_TraceLine; + pfnEngDst_CL_LoadModel_t CL_LoadModel; + pfnEngDst_CL_CreateVisibleEntity_t CL_CreateVisibleEntity; + pfnEngDst_GetSpritePointer_t GetSpritePointer; + pfnEngDst_pfnPlaySoundByNameAtLocation_t pfnPlaySoundByNameAtLocation; + pfnEngDst_pfnPrecacheEvent_t pfnPrecacheEvent; + pfnEngDst_pfnPlaybackEvent_t pfnPlaybackEvent; + pfnEngDst_pfnWeaponAnim_t pfnWeaponAnim; + pfnEngDst_pfnRandomFloat_t pfnRandomFloat; + pfnEngDst_pfnRandomLong_t pfnRandomLong; + pfnEngDst_pfnHookEvent_t pfnHookEvent; + pfnEngDst_Con_IsVisible_t Con_IsVisible; + pfnEngDst_pfnGetGameDirectory_t pfnGetGameDirectory; + pfnEngDst_pfnGetCvarPointer_t pfnGetCvarPointer; + pfnEngDst_Key_LookupBinding_t Key_LookupBinding; + pfnEngDst_pfnGetLevelName_t pfnGetLevelName; + pfnEngDst_pfnGetScreenFade_t pfnGetScreenFade; + pfnEngDst_pfnSetScreenFade_t pfnSetScreenFade; + pfnEngDst_VGui_GetPanel_t VGui_GetPanel; + pfnEngDst_VGui_ViewportPaintBackground_t VGui_ViewportPaintBackground; + pfnEngDst_COM_LoadFile_t COM_LoadFile; + pfnEngDst_COM_ParseFile_t COM_ParseFile; + pfnEngDst_COM_FreeFile_t COM_FreeFile; + struct triangleapi_s *pTriAPI; + struct efx_api_s *pEfxAPI; + struct event_api_s *pEventAPI; + struct demo_api_s *pDemoAPI; + struct net_api_s *pNetAPI; + struct IVoiceTweak_s *pVoiceTweak; + pfnEngDst_IsSpectateOnly_t IsSpectateOnly; + pfnEngDst_LoadMapSprite_t LoadMapSprite; + pfnEngDst_COM_AddAppDirectoryToSearchPath_t COM_AddAppDirectoryToSearchPath; + pfnEngDst_COM_ExpandFilename_t COM_ExpandFilename; + pfnEngDst_PlayerInfo_ValueForKey_t PlayerInfo_ValueForKey; + pfnEngDst_PlayerInfo_SetValueForKey_t PlayerInfo_SetValueForKey; + pfnEngDst_GetPlayerUniqueID_t GetPlayerUniqueID; + pfnEngDst_GetTrackerIDForPlayer_t GetTrackerIDForPlayer; + pfnEngDst_GetPlayerForTrackerID_t GetPlayerForTrackerID; + pfnEngDst_pfnServerCmdUnreliable_t pfnServerCmdUnreliable; + pfnEngDst_GetMousePos_t pfnGetMousePos; + pfnEngDst_SetMousePos_t pfnSetMousePos; + pfnEngDst_SetMouseEnable_t pfnSetMouseEnable; + pfnEngDst_pfnSetFilterMode_t pfnSetFilterMode ; + pfnEngDst_pfnSetFilterColor_t pfnSetFilterColor ; + pfnEngDst_pfnSetFilterBrightness_t pfnSetFilterBrightness ; + pfnEngDst_pfnSequenceGet_t pfnSequenceGet; + pfnEngDst_pfnSPR_DrawGeneric_t pfnSPR_DrawGeneric; + pfnEngDst_pfnSequencePickSentence_t pfnSequencePickSentence; + pfnEngDst_pfnDrawString_t pfnDrawString; + pfnEngDst_pfnDrawString_t pfnDrawStringReverse; + pfnEngDst_LocalPlayerInfo_ValueForKey_t LocalPlayerInfo_ValueForKey; + pfnEngDst_pfnVGUI2DrawCharacter_t pfnVGUI2DrawCharacter; + pfnEngDst_pfnVGUI2DrawCharacterAdd_t pfnVGUI2DrawCharacterAdd; + pfnEngDst_pfnPlaySoundVoiceByName_t pfnPlaySoundVoiceByName; + pfnEngDst_pfnPrimeMusicStream_t pfnPrimeMusicStream; + pfnEngDst_pfnProcessTutorMessageDecayBuffer_t pfnProcessTutorMessageDecayBuffer; + pfnEngDst_pfnConstructTutorMessageDecayBuffer_t pfnConstructTutorMessageDecayBuffer; + pfnEngDst_pfnResetTutorMessageDecayData_t pfnResetTutorMessageDecayData; + pfnEngDst_pfnPlaySoundByNameAtPitch_t pfnPlaySoundByNameAtPitch; + pfnEngDst_pfnFillRGBABlend_t pfnFillRGBABlend; + pfnEngDst_pfnGetAppID_t pfnGetAppID; + pfnEngDst_pfnGetAliases_t pfnGetAliasList; + pfnEngDst_pfnVguiWrap2_GetMouseDelta_t pfnVguiWrap2_GetMouseDelta; +} cl_enginefunc_dst_t; + + +// ******************************************************** +// Functions exposed by the engine to the module +// ******************************************************** + +// Functions for ModuleS +typedef void (*PFN_KICKPLAYER)(int nPlayerSlot, int nReason); + +typedef struct modshelpers_s +{ + PFN_KICKPLAYER m_pfnKickPlayer; + + // reserved for future expansion + int m_nVoid1; + int m_nVoid2; + int m_nVoid3; + int m_nVoid4; + int m_nVoid5; + int m_nVoid6; + int m_nVoid7; + int m_nVoid8; + int m_nVoid9; +} modshelpers_t; + +// Functions for moduleC +typedef struct modchelpers_s +{ + // reserved for future expansion + int m_nVoid0; + int m_nVoid1; + int m_nVoid2; + int m_nVoid3; + int m_nVoid4; + int m_nVoid5; + int m_nVoid6; + int m_nVoid7; + int m_nVoid8; + int m_nVoid9; +} modchelpers_t; + + +// ******************************************************** +// Information about the engine +// ******************************************************** +typedef struct engdata_s +{ + cl_enginefunc_t *pcl_enginefuncs; // functions exported by the engine + cl_enginefunc_dst_t *pg_engdstAddrs; // destination handlers for engine exports + cldll_func_t *pcl_funcs; // client exports + cldll_func_dst_t *pg_cldstAddrs; // client export destination handlers + struct modfuncs_s *pg_modfuncs; // engine's pointer to module functions + struct cmd_function_s **pcmd_functions; // list of all registered commands + void *pkeybindings; // all key bindings (not really a void *, but easier this way) + void (*pfnConPrintf)(char *, ...); // dump to console + struct cvar_s **pcvar_vars; // pointer to head of cvar list + struct glwstate_t *pglwstate; // OpenGl information + void *(*pfnSZ_GetSpace)(struct sizebuf_s *, int); // pointer to SZ_GetSpace + struct modfuncs_s *pmodfuncs; // &g_modfuncs + void *pfnGetProcAddress; // &GetProcAddress + void *pfnGetModuleHandle; // &GetModuleHandle + struct server_static_s *psvs; // &svs + struct client_static_s *pcls; // &cls + void (*pfnSV_DropClient)(struct client_s *, qboolean, char *, ...); // pointer to SV_DropClient + void (*pfnNetchan_Transmit)(struct netchan_s *, int, byte *); // pointer to Netchan_Transmit + void (*pfnNET_SendPacket)(enum netsrc_s sock, int length, void *data, netadr_t to); // &NET_SendPacket + struct cvar_s *(*pfnCvarFindVar)(const char *pchName); // pointer to Cvar_FindVar + int *phinstOpenGlEarly; // &g_hinstOpenGlEarly + + // Reserved for future expansion + void *pVoid0; // reserved for future expan + void *pVoid1; // reserved for future expan + void *pVoid2; // reserved for future expan + void *pVoid3; // reserved for future expan + void *pVoid4; // reserved for future expan + void *pVoid5; // reserved for future expan + void *pVoid6; // reserved for future expan + void *pVoid7; // reserved for future expan + void *pVoid8; // reserved for future expan + void *pVoid9; // reserved for future expan +} engdata_t; + + +// ******************************************************** +// Functions exposed by the security module +// ******************************************************** +typedef void (*PFN_LOADMOD)(char *pchModule); +typedef void (*PFN_CLOSEMOD)(void); +typedef int (*PFN_NCALL)(int ijump, int cnArg, ...); + +typedef void (*PFN_GETCLDSTADDRS)(cldll_func_dst_t *pcldstAddrs); +typedef void (*PFN_GETENGDSTADDRS)(cl_enginefunc_dst_t *pengdstAddrs); +typedef void (*PFN_MODULELOADED)(void); + +typedef void (*PFN_PROCESSOUTGOINGNET)(struct netchan_s *pchan, struct sizebuf_s *psizebuf); +typedef qboolean (*PFN_PROCESSINCOMINGNET)(struct netchan_s *pchan, struct sizebuf_s *psizebuf); + +typedef void (*PFN_TEXTURELOAD)(char *pszName, int dxWidth, int dyHeight, char *pbData); +typedef void (*PFN_MODELLOAD)(struct model_s *pmodel, void *pvBuf); + +typedef void (*PFN_FRAMEBEGIN)(void); +typedef void (*PFN_FRAMERENDER1)(void); +typedef void (*PFN_FRAMERENDER2)(void); + +typedef void (*PFN_SETMODSHELPERS)(modshelpers_t *pmodshelpers); +typedef void (*PFN_SETMODCHELPERS)(modchelpers_t *pmodchelpers); +typedef void (*PFN_SETENGDATA)(engdata_t *pengdata); + +typedef void (*PFN_CONNECTCLIENT)(int iPlayer); +typedef void (*PFN_RECORDIP)(unsigned int pnIP); +typedef void (*PFN_PLAYERSTATUS)(unsigned char *pbData, int cbData); + +typedef void (*PFN_SETENGINEVERSION)(int nVersion); + +// typedef class CMachine *(*PFN_PCMACHINE)(void); +typedef int (*PFN_PCMACHINE)(void); +typedef void (*PFN_SETIP)(int ijump); +typedef void (*PFN_EXECUTE)(void); + +typedef struct modfuncs_s +{ + // Functions for the pcode interpreter + PFN_LOADMOD m_pfnLoadMod; + PFN_CLOSEMOD m_pfnCloseMod; + PFN_NCALL m_pfnNCall; + + // API destination functions + PFN_GETCLDSTADDRS m_pfnGetClDstAddrs; + PFN_GETENGDSTADDRS m_pfnGetEngDstAddrs; + + // Miscellaneous functions + PFN_MODULELOADED m_pfnModuleLoaded; // Called right after the module is loaded + + // Functions for processing network traffic + PFN_PROCESSOUTGOINGNET m_pfnProcessOutgoingNet; // Every outgoing packet gets run through this + PFN_PROCESSINCOMINGNET m_pfnProcessIncomingNet; // Every incoming packet gets run through this + + // Resource functions + PFN_TEXTURELOAD m_pfnTextureLoad; // Called as each texture is loaded + PFN_MODELLOAD m_pfnModelLoad; // Called as each model is loaded + + // Functions called every frame + PFN_FRAMEBEGIN m_pfnFrameBegin; // Called at the beginning of each frame cycle + PFN_FRAMERENDER1 m_pfnFrameRender1; // Called at the beginning of the render loop + PFN_FRAMERENDER2 m_pfnFrameRender2; // Called at the end of the render loop + + // Module helper transfer + PFN_SETMODSHELPERS m_pfnSetModSHelpers; + PFN_SETMODCHELPERS m_pfnSetModCHelpers; + PFN_SETENGDATA m_pfnSetEngData; + + // Which version of the module is this? + int m_nVersion; + + // Miscellaneous game stuff + PFN_CONNECTCLIENT m_pfnConnectClient; // Called whenever a new client connects + PFN_RECORDIP m_pfnRecordIP; // Secure master has reported a new IP for us + PFN_PLAYERSTATUS m_pfnPlayerStatus; // Called whenever we receive a PlayerStatus packet + + // Recent additions + PFN_SETENGINEVERSION m_pfnSetEngineVersion; // 1 = patched engine + + // reserved for future expansion + int m_nVoid2; + int m_nVoid3; + int m_nVoid4; + int m_nVoid5; + int m_nVoid6; + int m_nVoid7; + int m_nVoid8; + int m_nVoid9; +} modfuncs_t; + + +#define k_nEngineVersion15Base 0 +#define k_nEngineVersion15Patch 1 +#define k_nEngineVersion16Base 2 +#define k_nEngineVersion16Validated 3 // 1.6 engine with built-in validation + + +typedef struct validator_s +{ + int m_nRandomizer; // Random number to be XOR'd into all subsequent fields + int m_nSignature1; // First signature that identifies this structure + int m_nSignature2; // Second signature + int m_pbCode; // Beginning of the code block + int m_cbCode; // Size of the code block + int m_nChecksum; // Checksum of the code block + int m_nSpecial; // For engine, 1 if hw.dll, 0 if sw.dll. For client, pclfuncs checksum + int m_nCompensator; // Keeps the checksum correct +} validator_t; + + +#define k_nChecksumCompensator 0x36a8f09c // Don't change this value: it's hardcorded in cdll_int.cpp, + +#define k_nModuleVersionCur 0x43210004 + + +#endif // __APIPROXY__ diff --git a/engine/anorms.h b/engine/anorms.h new file mode 100644 index 0000000..d147aea --- /dev/null +++ b/engine/anorms.h @@ -0,0 +1,177 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +{-0.525731, 0.000000, 0.850651}, +{-0.442863, 0.238856, 0.864188}, +{-0.295242, 0.000000, 0.955423}, +{-0.309017, 0.500000, 0.809017}, +{-0.162460, 0.262866, 0.951056}, +{0.000000, 0.000000, 1.000000}, +{0.000000, 0.850651, 0.525731}, +{-0.147621, 0.716567, 0.681718}, +{0.147621, 0.716567, 0.681718}, +{0.000000, 0.525731, 0.850651}, +{0.309017, 0.500000, 0.809017}, +{0.525731, 0.000000, 0.850651}, +{0.295242, 0.000000, 0.955423}, +{0.442863, 0.238856, 0.864188}, +{0.162460, 0.262866, 0.951056}, +{-0.681718, 0.147621, 0.716567}, +{-0.809017, 0.309017, 0.500000}, +{-0.587785, 0.425325, 0.688191}, +{-0.850651, 0.525731, 0.000000}, +{-0.864188, 0.442863, 0.238856}, +{-0.716567, 0.681718, 0.147621}, +{-0.688191, 0.587785, 0.425325}, +{-0.500000, 0.809017, 0.309017}, +{-0.238856, 0.864188, 0.442863}, +{-0.425325, 0.688191, 0.587785}, +{-0.716567, 0.681718, -0.147621}, +{-0.500000, 0.809017, -0.309017}, +{-0.525731, 0.850651, 0.000000}, +{0.000000, 0.850651, -0.525731}, +{-0.238856, 0.864188, -0.442863}, +{0.000000, 0.955423, -0.295242}, +{-0.262866, 0.951056, -0.162460}, +{0.000000, 1.000000, 0.000000}, +{0.000000, 0.955423, 0.295242}, +{-0.262866, 0.951056, 0.162460}, +{0.238856, 0.864188, 0.442863}, +{0.262866, 0.951056, 0.162460}, +{0.500000, 0.809017, 0.309017}, +{0.238856, 0.864188, -0.442863}, +{0.262866, 0.951056, -0.162460}, +{0.500000, 0.809017, -0.309017}, +{0.850651, 0.525731, 0.000000}, +{0.716567, 0.681718, 0.147621}, +{0.716567, 0.681718, -0.147621}, +{0.525731, 0.850651, 0.000000}, +{0.425325, 0.688191, 0.587785}, +{0.864188, 0.442863, 0.238856}, +{0.688191, 0.587785, 0.425325}, +{0.809017, 0.309017, 0.500000}, +{0.681718, 0.147621, 0.716567}, +{0.587785, 0.425325, 0.688191}, +{0.955423, 0.295242, 0.000000}, +{1.000000, 0.000000, 0.000000}, +{0.951056, 0.162460, 0.262866}, +{0.850651, -0.525731, 0.000000}, +{0.955423, -0.295242, 0.000000}, +{0.864188, -0.442863, 0.238856}, +{0.951056, -0.162460, 0.262866}, +{0.809017, -0.309017, 0.500000}, +{0.681718, -0.147621, 0.716567}, +{0.850651, 0.000000, 0.525731}, +{0.864188, 0.442863, -0.238856}, +{0.809017, 0.309017, -0.500000}, +{0.951056, 0.162460, -0.262866}, +{0.525731, 0.000000, -0.850651}, +{0.681718, 0.147621, -0.716567}, +{0.681718, -0.147621, -0.716567}, +{0.850651, 0.000000, -0.525731}, +{0.809017, -0.309017, -0.500000}, +{0.864188, -0.442863, -0.238856}, +{0.951056, -0.162460, -0.262866}, +{0.147621, 0.716567, -0.681718}, +{0.309017, 0.500000, -0.809017}, +{0.425325, 0.688191, -0.587785}, +{0.442863, 0.238856, -0.864188}, +{0.587785, 0.425325, -0.688191}, +{0.688191, 0.587785, -0.425325}, +{-0.147621, 0.716567, -0.681718}, +{-0.309017, 0.500000, -0.809017}, +{0.000000, 0.525731, -0.850651}, +{-0.525731, 0.000000, -0.850651}, +{-0.442863, 0.238856, -0.864188}, +{-0.295242, 0.000000, -0.955423}, +{-0.162460, 0.262866, -0.951056}, +{0.000000, 0.000000, -1.000000}, +{0.295242, 0.000000, -0.955423}, +{0.162460, 0.262866, -0.951056}, +{-0.442863, -0.238856, -0.864188}, +{-0.309017, -0.500000, -0.809017}, +{-0.162460, -0.262866, -0.951056}, +{0.000000, -0.850651, -0.525731}, +{-0.147621, -0.716567, -0.681718}, +{0.147621, -0.716567, -0.681718}, +{0.000000, -0.525731, -0.850651}, +{0.309017, -0.500000, -0.809017}, +{0.442863, -0.238856, -0.864188}, +{0.162460, -0.262866, -0.951056}, +{0.238856, -0.864188, -0.442863}, +{0.500000, -0.809017, -0.309017}, +{0.425325, -0.688191, -0.587785}, +{0.716567, -0.681718, -0.147621}, +{0.688191, -0.587785, -0.425325}, +{0.587785, -0.425325, -0.688191}, +{0.000000, -0.955423, -0.295242}, +{0.000000, -1.000000, 0.000000}, +{0.262866, -0.951056, -0.162460}, +{0.000000, -0.850651, 0.525731}, +{0.000000, -0.955423, 0.295242}, +{0.238856, -0.864188, 0.442863}, +{0.262866, -0.951056, 0.162460}, +{0.500000, -0.809017, 0.309017}, +{0.716567, -0.681718, 0.147621}, +{0.525731, -0.850651, 0.000000}, +{-0.238856, -0.864188, -0.442863}, +{-0.500000, -0.809017, -0.309017}, +{-0.262866, -0.951056, -0.162460}, +{-0.850651, -0.525731, 0.000000}, +{-0.716567, -0.681718, -0.147621}, +{-0.716567, -0.681718, 0.147621}, +{-0.525731, -0.850651, 0.000000}, +{-0.500000, -0.809017, 0.309017}, +{-0.238856, -0.864188, 0.442863}, +{-0.262866, -0.951056, 0.162460}, +{-0.864188, -0.442863, 0.238856}, +{-0.809017, -0.309017, 0.500000}, +{-0.688191, -0.587785, 0.425325}, +{-0.681718, -0.147621, 0.716567}, +{-0.442863, -0.238856, 0.864188}, +{-0.587785, -0.425325, 0.688191}, +{-0.309017, -0.500000, 0.809017}, +{-0.147621, -0.716567, 0.681718}, +{-0.425325, -0.688191, 0.587785}, +{-0.162460, -0.262866, 0.951056}, +{0.442863, -0.238856, 0.864188}, +{0.162460, -0.262866, 0.951056}, +{0.309017, -0.500000, 0.809017}, +{0.147621, -0.716567, 0.681718}, +{0.000000, -0.525731, 0.850651}, +{0.425325, -0.688191, 0.587785}, +{0.587785, -0.425325, 0.688191}, +{0.688191, -0.587785, 0.425325}, +{-0.955423, 0.295242, 0.000000}, +{-0.951056, 0.162460, 0.262866}, +{-1.000000, 0.000000, 0.000000}, +{-0.850651, 0.000000, 0.525731}, +{-0.955423, -0.295242, 0.000000}, +{-0.951056, -0.162460, 0.262866}, +{-0.864188, 0.442863, -0.238856}, +{-0.951056, 0.162460, -0.262866}, +{-0.809017, 0.309017, -0.500000}, +{-0.864188, -0.442863, -0.238856}, +{-0.951056, -0.162460, -0.262866}, +{-0.809017, -0.309017, -0.500000}, +{-0.681718, 0.147621, -0.716567}, +{-0.681718, -0.147621, -0.716567}, +{-0.850651, 0.000000, -0.525731}, +{-0.688191, 0.587785, -0.425325}, +{-0.587785, 0.425325, -0.688191}, +{-0.425325, 0.688191, -0.587785}, +{-0.425325, -0.688191, -0.587785}, +{-0.587785, -0.425325, -0.688191}, +{-0.688191, -0.587785, -0.425325}, diff --git a/engine/cdll_int.h b/engine/cdll_int.h new file mode 100644 index 0000000..555800f --- /dev/null +++ b/engine/cdll_int.h @@ -0,0 +1,467 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cdll_int.h +// +// 4-23-98 +// JOHN: client dll interface declarations +// + +#ifndef CDLL_INT_H +#define CDLL_INT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "const.h" +#include "steam/steamtypes.h" +#include "ref_params.h" +#include "r_efx.h" +#include "studio_event.h" + +// this file is included by both the engine and the client-dll, +// so make sure engine declarations aren't done twice + +typedef int HSPRITE; // handle to a graphic + +#define SCRINFO_SCREENFLASH 1 +#define SCRINFO_STRETCHED 2 + +typedef struct SCREENINFO_s +{ + int iSize; + int iWidth; + int iHeight; + int iFlags; + int iCharHeight; + short charWidths[256]; +} SCREENINFO; + + +typedef struct client_data_s +{ + // fields that cannot be modified (ie. have no effect if changed) + vec3_t origin; + + // fields that can be changed by the cldll + vec3_t viewangles; + int iWeaponBits; +// int iAccessoryBits; + float fov; // field of view +} client_data_t; + +typedef struct client_sprite_s +{ + char szName[64]; + char szSprite[64]; + int hspr; + int iRes; + wrect_t rc; +} client_sprite_t; + + + +typedef struct hud_player_info_s +{ + char *name; + short ping; + byte thisplayer; // TRUE if this is the calling player + + byte spectator; + byte packetloss; + + char *model; + short topcolor; + short bottomcolor; + + uint64 m_nSteamID; +} hud_player_info_t; + + + +typedef struct module_s +{ + unsigned char ucMD5Hash[16]; // hash over code + qboolean fLoaded; // true if successfully loaded +} module_t; + + + + + + +#ifndef IN_BUTTONS_H +#include "in_buttons.h" +#endif + +#define CLDLL_INTERFACE_VERSION 7 + +extern void LoadSecurityModuleFromDisk(char * pszDllName); +extern void LoadSecurityModuleFromMemory( unsigned char * pCode, int nSize ); +extern void CloseSecurityModule(); + + +extern void ClientDLL_Init( void ); // from cdll_int.c +extern void ClientDLL_Shutdown( void ); +extern void ClientDLL_HudInit( void ); +extern void ClientDLL_HudVidInit( void ); +extern void ClientDLL_UpdateClientData( void ); +extern void ClientDLL_Frame( double time ); +extern void ClientDLL_HudRedraw( int intermission ); +extern void ClientDLL_MoveClient( struct playermove_s *ppmove ); +extern void ClientDLL_ClientMoveInit( struct playermove_s *ppmove ); +extern char ClientDLL_ClientTextureType( char *name ); + +extern void ClientDLL_CreateMove( float frametime, struct usercmd_s *cmd, int active ); +extern void ClientDLL_ActivateMouse( void ); +extern void ClientDLL_DeactivateMouse( void ); +extern void ClientDLL_MouseEvent( int mstate ); +extern void ClientDLL_ClearStates( void ); +extern int ClientDLL_IsThirdPerson( void ); +extern void ClientDLL_GetCameraOffsets( float *ofs ); +extern int ClientDLL_GraphKeyDown( void ); +extern struct kbutton_s *ClientDLL_FindKey( const char *name ); +extern void ClientDLL_CAM_Think( void ); +extern void ClientDLL_IN_Accumulate( void ); +extern void ClientDLL_CalcRefdef( struct ref_params_s *pparams ); +extern int ClientDLL_AddEntity( int type, struct cl_entity_s *ent ); +extern void ClientDLL_CreateEntities( void ); + +extern void ClientDLL_DrawNormalTriangles( void ); +extern void ClientDLL_DrawTransparentTriangles( void ); +extern void ClientDLL_StudioEvent( const struct mstudioevent_s *event, const struct cl_entity_s *entity ); +extern void ClientDLL_PostRunCmd( struct local_state_s *from, struct local_state_s *to, struct usercmd_s *cmd, int runfuncs, double time, unsigned int random_seed ); +extern void ClientDLL_TxferLocalOverrides( struct entity_state_s *state, const struct clientdata_s *client ); +extern void ClientDLL_ProcessPlayerState( struct entity_state_s *dst, const struct entity_state_s *src ); +extern void ClientDLL_TxferPredictionData ( struct entity_state_s *ps, const struct entity_state_s *pps, struct clientdata_s *pcd, const struct clientdata_s *ppcd, struct weapon_data_s *wd, const struct weapon_data_s *pwd ); +extern void ClientDLL_ReadDemoBuffer( int size, unsigned char *buffer ); +extern int ClientDLL_ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); +extern int ClientDLL_GetHullBounds( int hullnumber, float *mins, float *maxs ); + +extern void ClientDLL_VGui_ConsolePrint(const char* text); + +extern int ClientDLL_Key_Event( int down, int keynum, const char *pszCurrentBinding ); +extern void ClientDLL_TempEntUpdate( double ft, double ct, double grav, struct tempent_s **ppFreeTE, struct tempent_s **ppActiveTE, int ( *addTEntity )( struct cl_entity_s *pEntity ), void ( *playTESound )( struct tempent_s *pTemp, float damp ) ); +extern struct cl_entity_s *ClientDLL_GetUserEntity( int index ); +extern void ClientDLL_VoiceStatus(int entindex, qboolean bTalking); +extern void ClientDLL_DirectorMessage( int iSize, void *pbuf ); +extern void ClientDLL_ChatInputPosition( int *x, int *y ); + +//#include "server.h" // server_static_t define for apiproxy +#include "APIProxy.h" + +extern cldll_func_t cl_funcs; +extern cl_enginefunc_t cl_engsrcProxies; +extern cl_enginefunc_dst_t g_engdstAddrs; + +// Module exports +extern modfuncs_t g_modfuncs; +extern module_t g_module; + +// Macros for exported engine funcs +#define RecEngSPR_Load(a) (g_engdstAddrs.pfnSPR_Load(&a)) +#define RecEngSPR_Frames(a) (g_engdstAddrs.pfnSPR_Frames(&a)) +#define RecEngSPR_Height(a, b) (g_engdstAddrs.pfnSPR_Height(&a, &b)) +#define RecEngSPR_Width(a, b) (g_engdstAddrs.pfnSPR_Width(&a, &b)) +#define RecEngSPR_Set(a, b, c, d) (g_engdstAddrs.pfnSPR_Set(&a, &b, &c, &d)) +#define RecEngSPR_Draw(a, b, c, d) (g_engdstAddrs.pfnSPR_Draw(&a, &b, &c, &d)) +#define RecEngSPR_DrawHoles(a, b, c, d) (g_engdstAddrs.pfnSPR_DrawHoles(&a, &b, &c, &d)) +#define RecEngSPR_DrawAdditive(a, b, c, d) (g_engdstAddrs.pfnSPR_DrawAdditive(&a, &b, &c, &d)) +#define RecEngSPR_EnableScissor(a, b, c, d) (g_engdstAddrs.pfnSPR_EnableScissor(&a, &b, &c, &d)) +#define RecEngSPR_DisableScissor() (g_engdstAddrs.pfnSPR_DisableScissor()) +#define RecEngSPR_GetList(a, b) (g_engdstAddrs.pfnSPR_GetList(&a, &b)) +#define RecEngDraw_FillRGBA(a, b, c, d, e, f, g, h) (g_engdstAddrs.pfnFillRGBA(&a, &b, &c, &d, &e, &f, &g, &h)) +#define RecEnghudGetScreenInfo(a) (g_engdstAddrs.pfnGetScreenInfo(&a)) +#define RecEngSetCrosshair(a, b, c, d, e) (g_engdstAddrs.pfnSetCrosshair(&a, &b, &c, &d, &e)) +#define RecEnghudRegisterVariable(a, b, c) (g_engdstAddrs.pfnRegisterVariable(&a, &b, &c)) +#define RecEnghudGetCvarFloat(a) (g_engdstAddrs.pfnGetCvarFloat(&a)) +#define RecEnghudGetCvarString(a) (g_engdstAddrs.pfnGetCvarString(&a)) +#define RecEnghudAddCommand(a, b) (g_engdstAddrs.pfnAddCommand(&a, &b)) +#define RecEnghudHookUserMsg(a, b) (g_engdstAddrs.pfnHookUserMsg(&a, &b)) +#define RecEnghudServerCmd(a) (g_engdstAddrs.pfnServerCmd(&a)) +#define RecEnghudClientCmd(a) (g_engdstAddrs.pfnClientCmd(&a)) +#define RecEngPrimeMusicStream(a, b) (g_engdstAddrs.pfnPrimeMusicStream(&a, &b)) +#define RecEnghudGetPlayerInfo(a, b) (g_engdstAddrs.pfnGetPlayerInfo(&a, &b)) +#define RecEnghudPlaySoundByName(a, b) (g_engdstAddrs.pfnPlaySoundByName(&a, &b)) +#define RecEnghudPlaySoundByNameAtPitch(a, b, c) (g_engdstAddrs.pfnPlaySoundByNameAtPitch(&a, &b, &c)) +#define RecEnghudPlaySoundVoiceByName(a, b) (g_engdstAddrs.pfnPlaySoundVoiceByName(&a, &b)) +#define RecEnghudPlaySoundByIndex(a, b) (g_engdstAddrs.pfnPlaySoundByIndex(&a, &b)) +#define RecEngAngleVectors(a, b, c, d) (g_engdstAddrs.pfnAngleVectors(&a, &b, &c, &d)) +#define RecEngTextMessageGet(a) (g_engdstAddrs.pfnTextMessageGet(&a)) +#define RecEngTextMessageDrawCharacter(a, b, c, d, e, f) (g_engdstAddrs.pfnDrawCharacter(&a, &b, &c, &d, &e, &f)) +#define RecEngDrawConsoleString(a, b, c) (g_engdstAddrs.pfnDrawConsoleString(&a, &b, &c)) +#define RecEngDrawSetTextColor(a, b, c) (g_engdstAddrs.pfnDrawSetTextColor(&a, &b, &c)) +#define RecEnghudDrawConsoleStringLen(a, b, c) (g_engdstAddrs.pfnDrawConsoleStringLen(&a, &b, &c)) +#define RecEnghudConsolePrint(a) (g_engdstAddrs.pfnConsolePrint(&a)) +#define RecEnghudCenterPrint(a) (g_engdstAddrs.pfnCenterPrint(&a)) +#define RecEnghudCenterX() (g_engdstAddrs.GetWindowCenterX()) +#define RecEnghudCenterY() (g_engdstAddrs.GetWindowCenterY()) +#define RecEnghudGetViewAngles(a) (g_engdstAddrs.GetViewAngles(&a)) +#define RecEnghudSetViewAngles(a) (g_engdstAddrs.SetViewAngles(&a)) +#define RecEnghudGetMaxClients() (g_engdstAddrs.GetMaxClients()) +#define RecEngCvar_SetValue(a, b) (g_engdstAddrs.Cvar_SetValue(&a, &b)) +#define RecEngCmd_Argc() (g_engdstAddrs.Cmd_Argc()) +#define RecEngCmd_Argv(a) (g_engdstAddrs.Cmd_Argv(&a)) +#define RecEngCon_Printf(a) (g_engdstAddrs.Con_Printf(&a)) +#define RecEngCon_DPrintf(a) (g_engdstAddrs.Con_DPrintf(&a)) +#define RecEngCon_NPrintf(a, b) (g_engdstAddrs.Con_NPrintf(&a, &b)) +#define RecEngCon_NXPrintf(a, b) (g_engdstAddrs.Con_NXPrintf(&a, &b)) +#define RecEnghudPhysInfo_ValueForKey(a) (g_engdstAddrs.PhysInfo_ValueForKey(&a)) +#define RecEnghudServerInfo_ValueForKey(a) (g_engdstAddrs.ServerInfo_ValueForKey(&a)) +#define RecEnghudGetClientMaxspeed() (g_engdstAddrs.GetClientMaxspeed()) +#define RecEnghudCheckParm(a, b) (g_engdstAddrs.CheckParm(&a, &b)) +#define RecEngKey_Event(a, b) (g_engdstAddrs.Key_Event(&a, &b)) +#define RecEnghudGetMousePosition(a, b) (g_engdstAddrs.GetMousePosition(&a, &b)) +#define RecEnghudIsNoClipping() (g_engdstAddrs.IsNoClipping()) +#define RecEnghudGetLocalPlayer() (g_engdstAddrs.GetLocalPlayer()) +#define RecEnghudGetViewModel() (g_engdstAddrs.GetViewModel()) +#define RecEnghudGetEntityByIndex(a) (g_engdstAddrs.GetEntityByIndex(&a)) +#define RecEnghudGetClientTime() (g_engdstAddrs.GetClientTime()) +#define RecEngV_CalcShake() (g_engdstAddrs.V_CalcShake()) +#define RecEngV_ApplyShake(a, b, c) (g_engdstAddrs.V_ApplyShake(&a, &b, &c)) +#define RecEngPM_PointContents(a, b) (g_engdstAddrs.PM_PointContents(&a, &b)) +#define RecEngPM_WaterEntity(a) (g_engdstAddrs.PM_WaterEntity(&a)) +#define RecEngPM_TraceLine(a, b, c, d, e) (g_engdstAddrs.PM_TraceLine(&a, &b, &c, &d, &e)) +#define RecEngCL_LoadModel(a, b) (g_engdstAddrs.CL_LoadModel(&a, &b)) +#define RecEngCL_CreateVisibleEntity(a, b) (g_engdstAddrs.CL_CreateVisibleEntity(&a, &b)) +#define RecEnghudGetSpritePointer(a) (g_engdstAddrs.GetSpritePointer(&a)) +#define RecEnghudPlaySoundByNameAtLocation(a, b, c) (g_engdstAddrs.pfnPlaySoundByNameAtLocation(&a, &b, &c)) +#define RecEnghudPrecacheEvent(a, b) (g_engdstAddrs.pfnPrecacheEvent(&a, &b)) +#define RecEnghudPlaybackEvent(a, b, c, d, e, f, g, h, i, j, k, l) (g_engdstAddrs.pfnPlaybackEvent(&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l)) +#define RecEnghudWeaponAnim(a, b) (g_engdstAddrs.pfnWeaponAnim(&a, &b)) +#define RecEngRandomFloat(a, b) (g_engdstAddrs.pfnRandomFloat(&a, &b)) +#define RecEngRandomLong(a, b) (g_engdstAddrs.pfnRandomLong(&a, &b)) +#define RecEngCL_HookEvent(a, b) (g_engdstAddrs.pfnHookEvent(&a, &b)) +#define RecEngCon_IsVisible() (g_engdstAddrs.Con_IsVisible()) +#define RecEnghudGetGameDir() (g_engdstAddrs.pfnGetGameDirectory()) +#define RecEngCvar_FindVar(a) (g_engdstAddrs.pfnGetCvarPointer(&a)) +#define RecEngKey_NameForBinding(a) (g_engdstAddrs.Key_LookupBinding(&a)) +#define RecEnghudGetLevelName() (g_engdstAddrs.pfnGetLevelName()) +#define RecEnghudGetScreenFade(a) (g_engdstAddrs.pfnGetScreenFade(&a)) +#define RecEnghudSetScreenFade(a) (g_engdstAddrs.pfnSetScreenFade(&a)) +#define RecEngVGuiWrap_GetPanel() (g_engdstAddrs.VGui_GetPanel()) +#define RecEngVGui_ViewportPaintBackground(a) (g_engdstAddrs.VGui_ViewportPaintBackground(&a)) +#define RecEngCOM_LoadFile(a, b, c) (g_engdstAddrs.COM_LoadFile(&a, &b, &c)) +#define RecEngCOM_ParseFile(a, b) (g_engdstAddrs.COM_ParseFile(&a, &b)) +#define RecEngCOM_FreeFile(a) (g_engdstAddrs.COM_FreeFile(&a)) +#define RecEngCL_IsSpectateOnly() (g_engdstAddrs.IsSpectateOnly()) +#define RecEngR_LoadMapSprite(a) (g_engdstAddrs.LoadMapSprite(&a)) +#define RecEngCOM_AddAppDirectoryToSearchPath(a, b) (g_engdstAddrs.COM_AddAppDirectoryToSearchPath(&a, &b)) +#define RecEngClientDLL_ExpandFileName(a, b, c) (g_engdstAddrs.COM_ExpandFilename(&a, &b, &c)) +#define RecEngPlayerInfo_ValueForKey(a, b) (g_engdstAddrs.PlayerInfo_ValueForKey(&a, &b)) +#define RecEngPlayerInfo_SetValueForKey(a, b) (g_engdstAddrs.PlayerInfo_SetValueForKey(&a, &b)) +#define RecEngGetPlayerUniqueID(a, b) (g_engdstAddrs.GetPlayerUniqueID(&a, &b)) +#define RecEngGetTrackerIDForPlayer(a) (g_engdstAddrs.GetTrackerIDForPlayer(&a)) +#define RecEngGetPlayerForTrackerID(a) (g_engdstAddrs.GetPlayerForTrackerID(&a)) +#define RecEnghudServerCmdUnreliable(a) (g_engdstAddrs.pfnServerCmdUnreliable(&a)) +#define RecEngGetMousePos(a) (g_engdstAddrs.pfnGetMousePos(&a)) +#define RecEngSetMousePos(a, b) (g_engdstAddrs.pfnSetMousePos(&a, &b)) +#define RecEngSetMouseEnable(a) (g_engdstAddrs.pfnSetMouseEnable(&a)) +#define RecEngSetFilterMode(a) (g_engdstAddrs.pfnSetFilterMode(&a)) +#define RecEngSetFilterColor(a,b,c) (g_engdstAddrs.pfnSetFilterColor(&a,&b,&c)) +#define RecEngSetFilterBrightness(a) (g_engdstAddrs.pfnSetFilterBrightness(&a)) +#define RecEngSequenceGet(a,b) (g_engdstAddrs.pfnSequenceGet(&a,&b)) +#define RecEngSPR_DrawGeneric(a,b,c,d,e,f,g,h) (g_engdstAddrs.pfnSPR_DrawGeneric(&a, &b, &c, &d, &e, &f, &g, &h)) +#define RecEngSequencePickSentence(a,b,c) (g_engdstAddrs.pfnSequencePickSentence(&a, &b, &c)) +#define RecEngLocalPlayerInfo_ValueForKey(a) (g_engdstAddrs.LocalPlayerInfo_ValueForKey(&a)) +#define RecEngProcessTutorMessageDecayBuffer(a, b) (g_engdstAddrs.pfnProcessTutorMessageDecayBuffer(&a, &b)) +#define RecEngConstructTutorMessageDecayBuffer(a, b) (g_engdstAddrs.pfnConstructTutorMessageDecayBuffer(&a, &b)) +#define RecEngResetTutorMessageDecayBuffer() (g_engdstAddrs.pfnResetTutorMessageDecayBuffer()) +#define RecEngDraw_FillRGBABlend(a, b, c, d, e, f, g, h) (g_engdstAddrs.pfnFillRGBABlend(&a, &b, &c, &d, &e, &f, &g, &h)) + +// Dummy destination function for use when there's no security module +extern void NullDst(void); + +// Use this to init an engdst structure to point to NullDst +#define k_engdstNull \ +{ \ + (pfnEngDst_pfnSPR_Load_t) NullDst, \ + (pfnEngDst_pfnSPR_Frames_t) NullDst, \ + (pfnEngDst_pfnSPR_Height_t) NullDst, \ + (pfnEngDst_pfnSPR_Width_t) NullDst, \ + (pfnEngDst_pfnSPR_Set_t) NullDst, \ + (pfnEngDst_pfnSPR_Draw_t) NullDst, \ + (pfnEngDst_pfnSPR_DrawHoles_t) NullDst, \ + (pfnEngDst_pfnSPR_DrawAdditive_t) NullDst, \ + (pfnEngDst_pfnSPR_EnableScissor_t) NullDst, \ + (pfnEngDst_pfnSPR_DisableScissor_t) NullDst, \ + (pfnEngDst_pfnSPR_GetList_t) NullDst, \ + (pfnEngDst_pfnFillRGBA_t) NullDst, \ + (pfnEngDst_pfnGetScreenInfo_t) NullDst, \ + (pfnEngDst_pfnSetCrosshair_t) NullDst, \ + (pfnEngDst_pfnRegisterVariable_t) NullDst, \ + (pfnEngDst_pfnGetCvarFloat_t) NullDst, \ + (pfnEngDst_pfnGetCvarString_t) NullDst, \ + (pfnEngDst_pfnAddCommand_t) NullDst, \ + (pfnEngDst_pfnHookUserMsg_t) NullDst, \ + (pfnEngDst_pfnServerCmd_t) NullDst, \ + (pfnEngDst_pfnClientCmd_t) NullDst, \ + (pfnEngDst_pfnGetPlayerInfo_t) NullDst, \ + (pfnEngDst_pfnPlaySoundByName_t) NullDst, \ + (pfnEngDst_pfnPlaySoundByIndex_t) NullDst, \ + (pfnEngDst_pfnAngleVectors_t) NullDst, \ + (pfnEngDst_pfnTextMessageGet_t) NullDst, \ + (pfnEngDst_pfnDrawCharacter_t) NullDst, \ + (pfnEngDst_pfnDrawConsoleString_t) NullDst, \ + (pfnEngDst_pfnDrawSetTextColor_t) NullDst, \ + (pfnEngDst_pfnDrawConsoleStringLen_t) NullDst, \ + (pfnEngDst_pfnConsolePrint_t) NullDst, \ + (pfnEngDst_pfnCenterPrint_t) NullDst, \ + (pfnEngDst_GetWindowCenterX_t) NullDst, \ + (pfnEngDst_GetWindowCenterY_t) NullDst, \ + (pfnEngDst_GetViewAngles_t) NullDst, \ + (pfnEngDst_SetViewAngles_t) NullDst, \ + (pfnEngDst_GetMaxClients_t) NullDst, \ + (pfnEngDst_Cvar_SetValue_t) NullDst, \ + (pfnEngDst_Cmd_Argc_t) NullDst, \ + (pfnEngDst_Cmd_Argv_t) NullDst, \ + (pfnEngDst_Con_Printf_t) NullDst, \ + (pfnEngDst_Con_DPrintf_t) NullDst, \ + (pfnEngDst_Con_NPrintf_t) NullDst, \ + (pfnEngDst_Con_NXPrintf_t) NullDst, \ + (pfnEngDst_PhysInfo_ValueForKey_t) NullDst, \ + (pfnEngDst_ServerInfo_ValueForKey_t) NullDst, \ + (pfnEngDst_GetClientMaxspeed_t) NullDst, \ + (pfnEngDst_CheckParm_t) NullDst, \ + (pfnEngDst_Key_Event_t) NullDst, \ + (pfnEngDst_GetMousePosition_t) NullDst, \ + (pfnEngDst_IsNoClipping_t) NullDst, \ + (pfnEngDst_GetLocalPlayer_t) NullDst, \ + (pfnEngDst_GetViewModel_t) NullDst, \ + (pfnEngDst_GetEntityByIndex_t) NullDst, \ + (pfnEngDst_GetClientTime_t) NullDst, \ + (pfnEngDst_V_CalcShake_t) NullDst, \ + (pfnEngDst_V_ApplyShake_t) NullDst, \ + (pfnEngDst_PM_PointContents_t) NullDst, \ + (pfnEngDst_PM_WaterEntity_t) NullDst, \ + (pfnEngDst_PM_TraceLine_t) NullDst, \ + (pfnEngDst_CL_LoadModel_t) NullDst, \ + (pfnEngDst_CL_CreateVisibleEntity_t) NullDst, \ + (pfnEngDst_GetSpritePointer_t) NullDst, \ + (pfnEngDst_pfnPlaySoundByNameAtLocation_t) NullDst, \ + (pfnEngDst_pfnPrecacheEvent_t) NullDst, \ + (pfnEngDst_pfnPlaybackEvent_t) NullDst, \ + (pfnEngDst_pfnWeaponAnim_t) NullDst, \ + (pfnEngDst_pfnRandomFloat_t) NullDst, \ + (pfnEngDst_pfnRandomLong_t) NullDst, \ + (pfnEngDst_pfnHookEvent_t) NullDst, \ + (pfnEngDst_Con_IsVisible_t) NullDst, \ + (pfnEngDst_pfnGetGameDirectory_t) NullDst, \ + (pfnEngDst_pfnGetCvarPointer_t) NullDst, \ + (pfnEngDst_Key_LookupBinding_t) NullDst, \ + (pfnEngDst_pfnGetLevelName_t) NullDst, \ + (pfnEngDst_pfnGetScreenFade_t) NullDst, \ + (pfnEngDst_pfnSetScreenFade_t) NullDst, \ + (pfnEngDst_VGui_GetPanel_t) NullDst, \ + (pfnEngDst_VGui_ViewportPaintBackground_t) NullDst, \ + (pfnEngDst_COM_LoadFile_t) NullDst, \ + (pfnEngDst_COM_ParseFile_t) NullDst, \ + (pfnEngDst_COM_FreeFile_t) NullDst, \ + NULL, \ + NULL, \ + NULL, \ + NULL, \ + NULL, \ + NULL, \ + (pfnEngDst_IsSpectateOnly_t) NullDst, \ + (pfnEngDst_LoadMapSprite_t) NullDst, \ + (pfnEngDst_COM_AddAppDirectoryToSearchPath_t) NullDst, \ + (pfnEngDst_COM_ExpandFilename_t) NullDst, \ + (pfnEngDst_PlayerInfo_ValueForKey_t) NullDst, \ + (pfnEngDst_PlayerInfo_SetValueForKey_t) NullDst, \ + (pfnEngDst_GetPlayerUniqueID_t) NullDst, \ + (pfnEngDst_GetTrackerIDForPlayer_t) NullDst, \ + (pfnEngDst_GetPlayerForTrackerID_t) NullDst, \ + (pfnEngDst_pfnServerCmdUnreliable_t) NullDst, \ + (pfnEngDst_GetMousePos_t) NullDst, \ + (pfnEngDst_SetMousePos_t) NullDst, \ + (pfnEngDst_SetMouseEnable_t) NullDst, \ + (pfnEngDst_pfnSetFilterMode_t) NullDst, \ + (pfnEngDst_pfnSetFilterColor_t) NullDst, \ + (pfnEngDst_pfnSetFilterBrightness_t) NullDst, \ + (pfnEngDst_pfnSequenceGet_t) NullDst, \ + (pfnEngDst_pfnSPR_DrawGeneric_t) NullDst, \ + (pfnEngDst_pfnSequencePickSentence_t) NullDst, \ + (pfnEngDst_pfnDrawString_t) NullDst, \ + (pfnEngDst_pfnDrawStringReverse_t) NullDst, \ + (pfnEngDst_LocalPlayerInfo_ValueForKey_t) NullDst, \ + (pfnEngDst_pfnVGUI2DrawCharacter_t) NullDst, \ + (pfnEngDst_pfnVGUI2DrawCharacterAdd_t) NullDst, \ + (pfnEngDst_pfnPlaySoundVoiceByName_t) NullDst, \ + (pfnEngDst_pfnPrimeMusicStream_t) NullDst, \ + (pfnEngDst_pfnProcessTutorMessageDecayBuffer_t) NullDst, \ + (pfnEngDst_pfnConstructTutorMessageDecayBuffer_t) NullDst, \ + (pfnEngDst_pfnResetTutorMessageDecayData_t) NullDst, \ + (pfnEngDst_pfnPlaySoundByNameAtPitch_t) NullDst, \ + (pfnEngDst_pfnFillRGBABlend_t) NullDst, \ + (pfnEngDst_pfnGetAppID_t) NullDst, \ + (pfnEngDst_pfnGetAliases_t) NullDst, \ + (pfnEngDst_pfnVguiWrap2_GetMouseDelta_t) NullDst, \ +}; + +// Use this to init a cldll_func_dst structure to point to NullDst +#define k_cldstNull \ +{ \ + (DST_INITIALIZE_FUNC) NullDst, \ + (DST_HUD_INIT_FUNC) NullDst, \ + (DST_HUD_VIDINIT_FUNC) NullDst, \ + (DST_HUD_REDRAW_FUNC) NullDst, \ + (DST_HUD_UPDATECLIENTDATA_FUNC) NullDst, \ + (DST_HUD_RESET_FUNC) NullDst, \ + (DST_HUD_CLIENTMOVE_FUNC) NullDst, \ + (DST_HUD_CLIENTMOVEINIT_FUNC) NullDst, \ + (DST_HUD_TEXTURETYPE_FUNC) NullDst, \ + (DST_HUD_IN_ACTIVATEMOUSE_FUNC) NullDst, \ + (DST_HUD_IN_DEACTIVATEMOUSE_FUNC) NullDst, \ + (DST_HUD_IN_MOUSEEVENT_FUNC) NullDst, \ + (DST_HUD_IN_CLEARSTATES_FUNC) NullDst, \ + (DST_HUD_IN_ACCUMULATE_FUNC) NullDst, \ + (DST_HUD_CL_CREATEMOVE_FUNC) NullDst, \ + (DST_HUD_CL_ISTHIRDPERSON_FUNC) NullDst, \ + (DST_HUD_CL_GETCAMERAOFFSETS_FUNC) NullDst, \ + (DST_HUD_KB_FIND_FUNC) NullDst, \ + (DST_HUD_CAMTHINK_FUNC) NullDst, \ + (DST_HUD_CALCREF_FUNC) NullDst, \ + (DST_HUD_ADDENTITY_FUNC) NullDst, \ + (DST_HUD_CREATEENTITIES_FUNC) NullDst, \ + (DST_HUD_DRAWNORMALTRIS_FUNC) NullDst, \ + (DST_HUD_DRAWTRANSTRIS_FUNC) NullDst, \ + (DST_HUD_STUDIOEVENT_FUNC) NullDst, \ + (DST_HUD_POSTRUNCMD_FUNC) NullDst, \ + (DST_HUD_SHUTDOWN_FUNC) NullDst, \ + (DST_HUD_TXFERLOCALOVERRIDES_FUNC) NullDst, \ + (DST_HUD_PROCESSPLAYERSTATE_FUNC) NullDst, \ + (DST_HUD_TXFERPREDICTIONDATA_FUNC) NullDst, \ + (DST_HUD_DEMOREAD_FUNC) NullDst, \ + (DST_HUD_CONNECTIONLESS_FUNC) NullDst, \ + (DST_HUD_GETHULLBOUNDS_FUNC) NullDst, \ + (DST_HUD_FRAME_FUNC) NullDst, \ + (DST_HUD_KEY_EVENT_FUNC) NullDst, \ + (DST_HUD_TEMPENTUPDATE_FUNC) NullDst, \ + (DST_HUD_GETUSERENTITY_FUNC) NullDst, \ + (DST_HUD_VOICESTATUS_FUNC) NullDst, \ + (DST_HUD_DIRECTORMESSAGE_FUNC) NullDst, \ + (DST_HUD_STUDIO_INTERFACE_FUNC) NullDst, \ + (DST_HUD_CHATINPUTPOSITION_FUNC) NullDst, \ + (DST_HUD_GETPLAYERTEAM) NullDst, \ +} + +#ifdef __cplusplus +} +#endif + +#endif // CDLL_INT_H + \ No newline at end of file diff --git a/engine/custom.h b/engine/custom.h new file mode 100644 index 0000000..a9f963c --- /dev/null +++ b/engine/custom.h @@ -0,0 +1,103 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Customization.h + +#ifndef CUSTOM_H +#define CUSTOM_H +#ifdef _WIN32 +#pragma once +#endif + +#include "const.h" + +#define MAX_QPATH 64 // Must match value in quakedefs.h + +///////////////// +// Customization +// passed to pfnPlayerCustomization +// For automatic downloading. +typedef enum +{ + t_sound = 0, + t_skin, + t_model, + t_decal, + t_generic, + t_eventscript, + t_world, // Fake type for world, is really t_model +} resourcetype_t; + + +typedef struct +{ + int size; +} _resourceinfo_t; + +typedef struct resourceinfo_s +{ + _resourceinfo_t info[ 8 ]; +} resourceinfo_t; + +#define RES_FATALIFMISSING (1<<0) // Disconnect if we can't get this file. +#define RES_WASMISSING (1<<1) // Do we have the file locally, did we get it ok? +#define RES_CUSTOM (1<<2) // Is this resource one that corresponds to another player's customization + // or is it a server startup resource. +#define RES_REQUESTED (1<<3) // Already requested a download of this one +#define RES_PRECACHED (1<<4) // Already precached +#define RES_ALWAYS (1<<5) // download always even if available on client +#define RES_CHECKFILE (1<<7) // check file on client + +#include "crc.h" + +typedef struct resource_s +{ + char szFileName[MAX_QPATH]; // File name to download/precache. + resourcetype_t type; // t_sound, t_skin, t_model, t_decal. + int nIndex; // For t_decals + int nDownloadSize; // Size in Bytes if this must be downloaded. + unsigned char ucFlags; + +// For handling client to client resource propagation + unsigned char rgucMD5_hash[16]; // To determine if we already have it. + unsigned char playernum; // Which player index this resource is associated with, if it's a custom resource. + + unsigned char rguc_reserved[ 32 ]; // For future expansion + struct resource_s *pNext; // Next in chain. + struct resource_s *pPrev; +} resource_t; + +typedef struct customization_s +{ + qboolean bInUse; // Is this customization in use; + resource_t resource; // The resource_t for this customization + qboolean bTranslated; // Has the raw data been translated into a useable format? + // (e.g., raw decal .wad make into texture_t *) + int nUserData1; // Customization specific data + int nUserData2; // Customization specific data + void *pInfo; // Buffer that holds the data structure that references the data (e.g., the cachewad_t) + void *pBuffer; // Buffer that holds the data for the customization (the raw .wad data) + struct customization_s *pNext; // Next in chain +} customization_t; + +#define FCUST_FROMHPAK ( 1<<0 ) +#define FCUST_WIPEDATA ( 1<<1 ) +#define FCUST_IGNOREINIT ( 1<<2 ) + +void COM_ClearCustomizationList( struct customization_s *pHead, qboolean bCleanDecals); +qboolean COM_CreateCustomization( struct customization_s *pListHead, struct resource_s *pResource, int playernumber, int flags, + struct customization_s **pCustomization, int *nLumps ); +int COM_SizeofResourceList ( struct resource_s *pList, struct resourceinfo_s *ri ); + +#endif // CUSTOM_H diff --git a/engine/customentity.h b/engine/customentity.h new file mode 100644 index 0000000..0895bee --- /dev/null +++ b/engine/customentity.h @@ -0,0 +1,38 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef CUSTOMENTITY_H +#define CUSTOMENTITY_H + +// Custom Entities + +// Start/End Entity is encoded as 12 bits of entity index, and 4 bits of attachment (4:12) +#define BEAMENT_ENTITY(x) ((x)&0xFFF) +#define BEAMENT_ATTACHMENT(x) (((x)>>12)&0xF) + +// Beam types, encoded as a byte +enum +{ + BEAM_POINTS = 0, + BEAM_ENTPOINT, + BEAM_ENTS, + BEAM_HOSE, +}; + +#define BEAM_FSINE 0x10 +#define BEAM_FSOLID 0x20 +#define BEAM_FSHADEIN 0x40 +#define BEAM_FSHADEOUT 0x80 + +#endif //CUSTOMENTITY_H diff --git a/engine/edict.h b/engine/edict.h new file mode 100644 index 0000000..9a38993 --- /dev/null +++ b/engine/edict.h @@ -0,0 +1,36 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined EDICT_H +#define EDICT_H +#ifdef _WIN32 +#pragma once +#endif +#define MAX_ENT_LEAFS 48 + +#include "progdefs.h" + +struct edict_s +{ + qboolean free; + int serialnumber; + link_t area; // linked to a division node or leaf + + int headnode; // -1 to use normal leaf check + int num_leafs; + short leafnums[MAX_ENT_LEAFS]; + + float freetime; // sv.time when the object was freed + + void* pvPrivateData; // Alloced and freed by engine, used by DLLs + + entvars_t v; // C exported fields from progs + + // other fields from progs come immediately after +}; + +#endif diff --git a/engine/eiface.h b/engine/eiface.h new file mode 100644 index 0000000..9184c54 --- /dev/null +++ b/engine/eiface.h @@ -0,0 +1,532 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef EIFACE_H +#define EIFACE_H + +#include "archtypes.h" // DAL + +#ifdef HLDEMO_BUILD +#define INTERFACE_VERSION 001 +#else // !HLDEMO_BUILD, i.e., regular version of HL +#define INTERFACE_VERSION 140 +#endif // !HLDEMO_BUILD + +#include +#include "custom.h" +#include "cvardef.h" +#include "Sequence.h" +// +// Defines entity interface between engine and DLLs. +// This header file included by engine files and DLL files. +// +// Before including this header, DLLs must: +// include progdefs.h +// This is conveniently done for them in extdll.h +// + +/* +#ifdef _WIN32 +#define DLLEXPORT __stdcall +#else +#define DLLEXPORT __attribute__ ((visibility("default"))) +#endif +*/ + +typedef enum + { + at_notice, + at_console, // same as at_notice, but forces a ConPrintf, not a message box + at_aiconsole, // same as at_console, but only shown if developer level is 2! + at_warning, + at_error, + at_logged // Server print to console ( only in multiplayer games ). + } ALERT_TYPE; + +// 4-22-98 JOHN: added for use in pfnClientPrintf +typedef enum + { + print_console, + print_center, + print_chat, + } PRINT_TYPE; + +// For integrity checking of content on clients +typedef enum +{ + force_exactfile, // File on client must exactly match server's file + force_model_samebounds, // For model files only, the geometry must fit in the same bbox + force_model_specifybounds, // For model files only, the geometry must fit in the specified bbox + force_model_specifybounds_if_avail, // For Steam model files only, the geometry must fit in the specified bbox (if the file is available) +} FORCE_TYPE; + +// Returned by TraceLine +typedef struct + { + int fAllSolid; // if true, plane is not valid + int fStartSolid; // if true, the initial point was in a solid area + int fInOpen; + int fInWater; + float flFraction; // time completed, 1.0 = didn't hit anything + vec3_t vecEndPos; // final position + float flPlaneDist; + vec3_t vecPlaneNormal; // surface normal at impact + edict_t *pHit; // entity the surface is on + int iHitgroup; // 0 == generic, non zero is specific body part + } TraceResult; + +// CD audio status +typedef struct +{ + int fPlaying;// is sound playing right now? + int fWasPlaying;// if not, CD is paused if WasPlaying is true. + int fInitialized; + int fEnabled; + int fPlayLooping; + float cdvolume; + //BYTE remap[100]; + int fCDRom; + int fPlayTrack; +} CDStatus; + +#include "../common/crc.h" + + +// Engine hands this to DLLs for functionality callbacks +typedef struct enginefuncs_s +{ + int (*pfnPrecacheModel) (char* s); + int (*pfnPrecacheSound) (char* s); + void (*pfnSetModel) (edict_t *e, const char *m); + int (*pfnModelIndex) (const char *m); + int (*pfnModelFrames) (int modelIndex); + void (*pfnSetSize) (edict_t *e, const float *rgflMin, const float *rgflMax); + void (*pfnChangeLevel) (char* s1, char* s2); + void (*pfnGetSpawnParms) (edict_t *ent); + void (*pfnSaveSpawnParms) (edict_t *ent); + float (*pfnVecToYaw) (const float *rgflVector); + void (*pfnVecToAngles) (const float *rgflVectorIn, float *rgflVectorOut); + void (*pfnMoveToOrigin) (edict_t *ent, const float *pflGoal, float dist, int iMoveType); + void (*pfnChangeYaw) (edict_t* ent); + void (*pfnChangePitch) (edict_t* ent); + edict_t* (*pfnFindEntityByString) (edict_t *pEdictStartSearchAfter, const char *pszField, const char *pszValue); + int (*pfnGetEntityIllum) (edict_t* pEnt); + edict_t* (*pfnFindEntityInSphere) (edict_t *pEdictStartSearchAfter, const float *org, float rad); + edict_t* (*pfnFindClientInPVS) (edict_t *pEdict); + edict_t* (*pfnEntitiesInPVS) (edict_t *pplayer); + void (*pfnMakeVectors) (const float *rgflVector); + void (*pfnAngleVectors) (const float *rgflVector, float *forward, float *right, float *up); + edict_t* (*pfnCreateEntity) (void); + void (*pfnRemoveEntity) (edict_t* e); + edict_t* (*pfnCreateNamedEntity) (int className); + void (*pfnMakeStatic) (edict_t *ent); + int (*pfnEntIsOnFloor) (edict_t *e); + int (*pfnDropToFloor) (edict_t* e); + int (*pfnWalkMove) (edict_t *ent, float yaw, float dist, int iMode); + void (*pfnSetOrigin) (edict_t *e, const float *rgflOrigin); + void (*pfnEmitSound) (edict_t *entity, int channel, const char *sample, /*int*/float volume, float attenuation, int fFlags, int pitch); + void (*pfnEmitAmbientSound) (edict_t *entity, float *pos, const char *samp, float vol, float attenuation, int fFlags, int pitch); + void (*pfnTraceLine) (const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr); + void (*pfnTraceToss) (edict_t* pent, edict_t* pentToIgnore, TraceResult *ptr); + int (*pfnTraceMonsterHull) (edict_t *pEdict, const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr); + void (*pfnTraceHull) (const float *v1, const float *v2, int fNoMonsters, int hullNumber, edict_t *pentToSkip, TraceResult *ptr); + void (*pfnTraceModel) (const float *v1, const float *v2, int hullNumber, edict_t *pent, TraceResult *ptr); + const char *(*pfnTraceTexture) (edict_t *pTextureEntity, const float *v1, const float *v2 ); + void (*pfnTraceSphere) (const float *v1, const float *v2, int fNoMonsters, float radius, edict_t *pentToSkip, TraceResult *ptr); + void (*pfnGetAimVector) (edict_t* ent, float speed, float *rgflReturn); + void (*pfnServerCommand) (char* str); + void (*pfnServerExecute) (void); + void (*pfnClientCommand) (edict_t* pEdict, char* szFmt, ...); + void (*pfnParticleEffect) (const float *org, const float *dir, float color, float count); + void (*pfnLightStyle) (int style, char* val); + int (*pfnDecalIndex) (const char *name); + int (*pfnPointContents) (const float *rgflVector); + void (*pfnMessageBegin) (int msg_dest, int msg_type, const float *pOrigin, edict_t *ed); + void (*pfnMessageEnd) (void); + void (*pfnWriteByte) (int iValue); + void (*pfnWriteChar) (int iValue); + void (*pfnWriteShort) (int iValue); + void (*pfnWriteLong) (int iValue); + void (*pfnWriteAngle) (float flValue); + void (*pfnWriteCoord) (float flValue); + void (*pfnWriteString) (const char *sz); + void (*pfnWriteEntity) (int iValue); + void (*pfnCVarRegister) (cvar_t *pCvar); + float (*pfnCVarGetFloat) (const char *szVarName); + const char* (*pfnCVarGetString) (const char *szVarName); + void (*pfnCVarSetFloat) (const char *szVarName, float flValue); + void (*pfnCVarSetString) (const char *szVarName, const char *szValue); + void (*pfnAlertMessage) (ALERT_TYPE atype, char *szFmt, ...); + void (*pfnEngineFprintf) (void *pfile, char *szFmt, ...); + void* (*pfnPvAllocEntPrivateData) (edict_t *pEdict, int32 cb); + void* (*pfnPvEntPrivateData) (edict_t *pEdict); + void (*pfnFreeEntPrivateData) (edict_t *pEdict); + const char* (*pfnSzFromIndex) (int iString); + int (*pfnAllocString) (const char *szValue); + struct entvars_s* (*pfnGetVarsOfEnt) (edict_t *pEdict); + edict_t* (*pfnPEntityOfEntOffset) (int iEntOffset); + int (*pfnEntOffsetOfPEntity) (const edict_t *pEdict); + int (*pfnIndexOfEdict) (const edict_t *pEdict); + edict_t* (*pfnPEntityOfEntIndex) (int iEntIndex); + edict_t* (*pfnFindEntityByVars) (struct entvars_s* pvars); + void* (*pfnGetModelPtr) (edict_t* pEdict); + int (*pfnRegUserMsg) (const char *pszName, int iSize); + void (*pfnAnimationAutomove) (const edict_t* pEdict, float flTime); + void (*pfnGetBonePosition) (const edict_t* pEdict, int iBone, float *rgflOrigin, float *rgflAngles ); + uint32 (*pfnFunctionFromName) ( const char *pName ); + const char *(*pfnNameForFunction) ( uint32 function ); + void (*pfnClientPrintf) ( edict_t* pEdict, PRINT_TYPE ptype, const char *szMsg ); // JOHN: engine callbacks so game DLL can print messages to individual clients + void (*pfnServerPrint) ( const char *szMsg ); + const char *(*pfnCmd_Args) ( void ); // these 3 added + const char *(*pfnCmd_Argv) ( int argc ); // so game DLL can easily + int (*pfnCmd_Argc) ( void ); // access client 'cmd' strings + void (*pfnGetAttachment) (const edict_t *pEdict, int iAttachment, float *rgflOrigin, float *rgflAngles ); + void (*pfnCRC32_Init) (CRC32_t *pulCRC); + void (*pfnCRC32_ProcessBuffer) (CRC32_t *pulCRC, void *p, int len); + void (*pfnCRC32_ProcessByte) (CRC32_t *pulCRC, unsigned char ch); + CRC32_t (*pfnCRC32_Final) (CRC32_t pulCRC); + int32 (*pfnRandomLong) (int32 lLow, int32 lHigh); + float (*pfnRandomFloat) (float flLow, float flHigh); + void (*pfnSetView) (const edict_t *pClient, const edict_t *pViewent ); + float (*pfnTime) ( void ); + void (*pfnCrosshairAngle) (const edict_t *pClient, float pitch, float yaw); + byte * (*pfnLoadFileForMe) (char *filename, int *pLength); + void (*pfnFreeFile) (void *buffer); + void (*pfnEndSection) (const char *pszSectionName); // trigger_endsection + int (*pfnCompareFileTime) (char *filename1, char *filename2, int *iCompare); + void (*pfnGetGameDir) (char *szGetGameDir); + void (*pfnCvar_RegisterVariable) (cvar_t *variable); + void (*pfnFadeClientVolume) (const edict_t *pEdict, int fadePercent, int fadeOutSeconds, int holdTime, int fadeInSeconds); + void (*pfnSetClientMaxspeed) (const edict_t *pEdict, float fNewMaxspeed); + edict_t * (*pfnCreateFakeClient) (const char *netname); // returns NULL if fake client can't be created + void (*pfnRunPlayerMove) (edict_t *fakeclient, const float *viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, byte msec ); + int (*pfnNumberOfEntities) (void); + char* (*pfnGetInfoKeyBuffer) (edict_t *e); // passing in NULL gets the serverinfo + char* (*pfnInfoKeyValue) (char *infobuffer, char *key); + void (*pfnSetKeyValue) (char *infobuffer, char *key, char *value); + void (*pfnSetClientKeyValue) (int clientIndex, char *infobuffer, char *key, char *value); + int (*pfnIsMapValid) (char *filename); + void (*pfnStaticDecal) ( const float *origin, int decalIndex, int entityIndex, int modelIndex ); + int (*pfnPrecacheGeneric) (char* s); + int (*pfnGetPlayerUserId) (edict_t *e ); // returns the server assigned userid for this player. useful for logging frags, etc. returns -1 if the edict couldn't be found in the list of clients + void (*pfnBuildSoundMsg) (edict_t *entity, int channel, const char *sample, /*int*/float volume, float attenuation, int fFlags, int pitch, int msg_dest, int msg_type, const float *pOrigin, edict_t *ed); + int (*pfnIsDedicatedServer) (void);// is this a dedicated server? + cvar_t *(*pfnCVarGetPointer) (const char *szVarName); + unsigned int (*pfnGetPlayerWONId) (edict_t *e); // returns the server assigned WONid for this player. useful for logging frags, etc. returns -1 if the edict couldn't be found in the list of clients + + // YWB 8/1/99 TFF Physics additions + void (*pfnInfo_RemoveKey) ( char *s, const char *key ); + const char *(*pfnGetPhysicsKeyValue) ( const edict_t *pClient, const char *key ); + void (*pfnSetPhysicsKeyValue) ( const edict_t *pClient, const char *key, const char *value ); + const char *(*pfnGetPhysicsInfoString) ( const edict_t *pClient ); + unsigned short (*pfnPrecacheEvent) ( int type, const char*psz ); + void (*pfnPlaybackEvent) ( int flags, const edict_t *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); + + unsigned char *(*pfnSetFatPVS) ( float *org ); + unsigned char *(*pfnSetFatPAS) ( float *org ); + + int (*pfnCheckVisibility ) ( const edict_t *entity, unsigned char *pset ); + + void (*pfnDeltaSetField) ( struct delta_s *pFields, const char *fieldname ); + void (*pfnDeltaUnsetField) ( struct delta_s *pFields, const char *fieldname ); + void (*pfnDeltaAddEncoder) ( char *name, void (*conditionalencode)( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) ); + int (*pfnGetCurrentPlayer) ( void ); + int (*pfnCanSkipPlayer) ( const edict_t *player ); + int (*pfnDeltaFindField) ( struct delta_s *pFields, const char *fieldname ); + void (*pfnDeltaSetFieldByIndex) ( struct delta_s *pFields, int fieldNumber ); + void (*pfnDeltaUnsetFieldByIndex)( struct delta_s *pFields, int fieldNumber ); + + void (*pfnSetGroupMask) ( int mask, int op ); + + int (*pfnCreateInstancedBaseline) ( int classname, struct entity_state_s *baseline ); + void (*pfnCvar_DirectSet) ( struct cvar_s *var, char *value ); + + // Forces the client and server to be running with the same version of the specified file + // ( e.g., a player model ). + // Calling this has no effect in single player + void (*pfnForceUnmodified) ( FORCE_TYPE type, float *mins, float *maxs, const char *filename ); + + void (*pfnGetPlayerStats) ( const edict_t *pClient, int *ping, int *packet_loss ); + + void (*pfnAddServerCommand) ( char *cmd_name, void (*function) (void) ); + + // For voice communications, set which clients hear eachother. + // NOTE: these functions take player entity indices (starting at 1). + qboolean (*pfnVoice_GetClientListening)(int iReceiver, int iSender); + qboolean (*pfnVoice_SetClientListening)(int iReceiver, int iSender, qboolean bListen); + + const char *(*pfnGetPlayerAuthId) ( edict_t *e ); + + // PSV: Added for CZ training map +// const char *(*pfnKeyNameForBinding) ( const char* pBinding ); + + sequenceEntry_s* (*pfnSequenceGet) ( const char* fileName, const char* entryName ); + sentenceEntry_s* (*pfnSequencePickSentence) ( const char* groupName, int pickMethod, int *picked ); + + // LH: Give access to filesize via filesystem + int (*pfnGetFileSize) ( char *filename ); + + unsigned int (*pfnGetApproxWavePlayLen) (const char *filepath); + // MDC: Added for CZ career-mode + int (*pfnIsCareerMatch) ( void ); + + // BGC: return the number of characters of the localized string referenced by using "label" + int (*pfnGetLocalizedStringLength)(const char *label); + + // BGC: added to facilitate persistent storage of tutor message decay values for + // different career game profiles. Also needs to persist regardless of mp.dll being + // destroyed and recreated. + void (*pfnRegisterTutorMessageShown)(int mid); + int (*pfnGetTimesTutorMessageShown)(int mid); + void (*ProcessTutorMessageDecayBuffer)(int *buffer, int bufferLength); + void (*ConstructTutorMessageDecayBuffer)(int *buffer, int bufferLength); + void (*ResetTutorMessageDecayData)( void ); + + void (*pfnQueryClientCvarValue)( const edict_t *player, const char *cvarName ); + void (*pfnQueryClientCvarValue2)( const edict_t *player, const char *cvarName, int requestID ); + int (*pfnCheckParm)( const char *pchCmdLineToken, char **ppnext ); +} enginefuncs_t; + + +// ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT. INTERFACE VERSION IS FROZEN AT 138 + +// Passed to pfnKeyValue +typedef struct KeyValueData_s +{ + char *szClassName; // in: entity classname + char *szKeyName; // in: name of key + char *szValue; // in: value of key + int32 fHandled; // out: DLL sets to true if key-value pair was understood +} KeyValueData; + + +typedef struct +{ + char mapName[ 32 ]; + char landmarkName[ 32 ]; + edict_t *pentLandmark; + vec3_t vecLandmarkOrigin; +} LEVELLIST; +#define MAX_LEVEL_CONNECTIONS 16 // These are encoded in the lower 16bits of ENTITYTABLE->flags + +typedef struct +{ + int id; // Ordinal ID of this entity (used for entity <--> pointer conversions) + edict_t *pent; // Pointer to the in-game entity + + int location; // Offset from the base data of this entity + int size; // Byte size of this entity's data + int flags; // This could be a short -- bit mask of transitions that this entity is in the PVS of + string_t classname; // entity class name + +} ENTITYTABLE; + +#define FENTTABLE_PLAYER 0x80000000 +#define FENTTABLE_REMOVED 0x40000000 +#define FENTTABLE_MOVEABLE 0x20000000 +#define FENTTABLE_GLOBAL 0x10000000 + +typedef struct saverestore_s SAVERESTOREDATA; + +#ifdef _WIN32 +typedef +#endif +struct saverestore_s +{ + char *pBaseData; // Start of all entity save data + char *pCurrentData; // Current buffer pointer for sequential access + int size; // Current data size + int bufferSize; // Total space for data + int tokenSize; // Size of the linear list of tokens + int tokenCount; // Number of elements in the pTokens table + char **pTokens; // Hash table of entity strings (sparse) + int currentIndex; // Holds a global entity table ID + int tableCount; // Number of elements in the entity table + int connectionCount;// Number of elements in the levelList[] + ENTITYTABLE *pTable; // Array of ENTITYTABLE elements (1 for each entity) + LEVELLIST levelList[ MAX_LEVEL_CONNECTIONS ]; // List of connections from this level + + // smooth transition + int fUseLandmark; + char szLandmarkName[20];// landmark we'll spawn near in next level + vec3_t vecLandmarkOffset;// for landmark transitions + float time; + char szCurrentMapName[32]; // To check global entities + +} +#ifdef _WIN32 +SAVERESTOREDATA +#endif +; + +typedef enum _fieldtypes +{ + FIELD_FLOAT = 0, // Any floating point value + FIELD_STRING, // A string ID (return from ALLOC_STRING) + FIELD_ENTITY, // An entity offset (EOFFSET) + FIELD_CLASSPTR, // CBaseEntity * + FIELD_EHANDLE, // Entity handle + FIELD_EVARS, // EVARS * + FIELD_EDICT, // edict_t *, or edict_t * (same thing) + FIELD_VECTOR, // Any vector + FIELD_POSITION_VECTOR, // A world coordinate (these are fixed up across level transitions automagically) + FIELD_POINTER, // Arbitrary data pointer... to be removed, use an array of FIELD_CHARACTER + FIELD_INTEGER, // Any integer or enum + FIELD_FUNCTION, // A class function pointer (Think, Use, etc) + FIELD_BOOLEAN, // boolean, implemented as an int, I may use this as a hint for compression + FIELD_SHORT, // 2 byte integer + FIELD_CHARACTER, // a byte + FIELD_TIME, // a floating point time (these are fixed up automatically too!) + FIELD_MODELNAME, // Engine string that is a model name (needs precache) + FIELD_SOUNDNAME, // Engine string that is a sound name (needs precache) + + FIELD_TYPECOUNT, // MUST BE LAST +} FIELDTYPE; + +#if !defined(offsetof) && !defined(GNUC) +#define offsetof(s,m) (size_t)&(((s *)0)->m) +#endif + +#define _FIELD(type,name,fieldtype,count,flags) { fieldtype, #name, offsetof(type, name), count, flags } +#define DEFINE_FIELD(type,name,fieldtype) _FIELD(type, name, fieldtype, 1, 0) +#define DEFINE_ARRAY(type,name,fieldtype,count) _FIELD(type, name, fieldtype, count, 0) +#define DEFINE_ENTITY_FIELD(name,fieldtype) _FIELD(entvars_t, name, fieldtype, 1, 0 ) +#define DEFINE_ENTITY_GLOBAL_FIELD(name,fieldtype) _FIELD(entvars_t, name, fieldtype, 1, FTYPEDESC_GLOBAL ) +#define DEFINE_GLOBAL_FIELD(type,name,fieldtype) _FIELD(type, name, fieldtype, 1, FTYPEDESC_GLOBAL ) + + +#define FTYPEDESC_GLOBAL 0x0001 // This field is masked for global entity save/restore + +typedef struct +{ + FIELDTYPE fieldType; + char *fieldName; + int fieldOffset; + short fieldSize; + short flags; +} TYPEDESCRIPTION; + +#define ARRAYSIZE(p) (sizeof(p)/sizeof(p[0])) + +typedef struct +{ + // Initialize/shutdown the game (one-time call after loading of game .dll ) + void (*pfnGameInit) ( void ); + int (*pfnSpawn) ( edict_t *pent ); + void (*pfnThink) ( edict_t *pent ); + void (*pfnUse) ( edict_t *pentUsed, edict_t *pentOther ); + void (*pfnTouch) ( edict_t *pentTouched, edict_t *pentOther ); + void (*pfnBlocked) ( edict_t *pentBlocked, edict_t *pentOther ); + void (*pfnKeyValue) ( edict_t *pentKeyvalue, KeyValueData *pkvd ); + void (*pfnSave) ( edict_t *pent, SAVERESTOREDATA *pSaveData ); + int (*pfnRestore) ( edict_t *pent, SAVERESTOREDATA *pSaveData, int globalEntity ); + void (*pfnSetAbsBox) ( edict_t *pent ); + + void (*pfnSaveWriteFields) ( SAVERESTOREDATA *, const char *, void *, TYPEDESCRIPTION *, int ); + void (*pfnSaveReadFields) ( SAVERESTOREDATA *, const char *, void *, TYPEDESCRIPTION *, int ); + + void (*pfnSaveGlobalState) ( SAVERESTOREDATA * ); + void (*pfnRestoreGlobalState) ( SAVERESTOREDATA * ); + void (*pfnResetGlobalState) ( void ); + + qboolean (*pfnClientConnect) ( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); + + void (*pfnClientDisconnect) ( edict_t *pEntity ); + void (*pfnClientKill) ( edict_t *pEntity ); + void (*pfnClientPutInServer) ( edict_t *pEntity ); + void (*pfnClientCommand) ( edict_t *pEntity ); + void (*pfnClientUserInfoChanged)( edict_t *pEntity, char *infobuffer ); + + void (*pfnServerActivate) ( edict_t *pEdictList, int edictCount, int clientMax ); + void (*pfnServerDeactivate) ( void ); + + void (*pfnPlayerPreThink) ( edict_t *pEntity ); + void (*pfnPlayerPostThink) ( edict_t *pEntity ); + + void (*pfnStartFrame) ( void ); + void (*pfnParmsNewLevel) ( void ); + void (*pfnParmsChangeLevel) ( void ); + + // Returns string describing current .dll. E.g., TeamFotrress 2, Half-Life + const char *(*pfnGetGameDescription)( void ); + + // Notify dll about a player customization. + void (*pfnPlayerCustomization) ( edict_t *pEntity, customization_t *pCustom ); + + // Spectator funcs + void (*pfnSpectatorConnect) ( edict_t *pEntity ); + void (*pfnSpectatorDisconnect) ( edict_t *pEntity ); + void (*pfnSpectatorThink) ( edict_t *pEntity ); + + // Notify game .dll that engine is going to shut down. Allows mod authors to set a breakpoint. + void (*pfnSys_Error) ( const char *error_string ); + + void (*pfnPM_Move) ( struct playermove_s *ppmove, qboolean server ); + void (*pfnPM_Init) ( struct playermove_s *ppmove ); + char (*pfnPM_FindTextureType)( char *name ); + void (*pfnSetupVisibility)( struct edict_s *pViewEntity, struct edict_s *pClient, unsigned char **pvs, unsigned char **pas ); + void (*pfnUpdateClientData) ( const struct edict_s *ent, int sendweapons, struct clientdata_s *cd ); + int (*pfnAddToFullPack)( struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet ); + void (*pfnCreateBaseline) ( int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, vec3_t player_mins, vec3_t player_maxs ); + void (*pfnRegisterEncoders) ( void ); + int (*pfnGetWeaponData) ( struct edict_s *player, struct weapon_data_s *info ); + + void (*pfnCmdStart) ( const edict_t *player, const struct usercmd_s *cmd, unsigned int random_seed ); + void (*pfnCmdEnd) ( const edict_t *player ); + + // Return 1 if the packet is valid. Set response_buffer_size if you want to send a response packet. Incoming, it holds the max + // size of the response_buffer, so you must zero it out if you choose not to respond. + int (*pfnConnectionlessPacket ) ( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); + + // Enumerates player hulls. Returns 0 if the hull number doesn't exist, 1 otherwise + int (*pfnGetHullBounds) ( int hullnumber, float *mins, float *maxs ); + + // Create baselines for certain "unplaced" items. + void (*pfnCreateInstancedBaselines) ( void ); + + // One of the pfnForceUnmodified files failed the consistency check for the specified player + // Return 0 to allow the client to continue, 1 to force immediate disconnection ( with an optional disconnect message of up to 256 characters ) + int (*pfnInconsistentFile)( const struct edict_s *player, const char *filename, char *disconnect_message ); + + // The game .dll should return 1 if lag compensation should be allowed ( could also just set + // the sv_unlag cvar. + // Most games right now should return 0, until client-side weapon prediction code is written + // and tested for them. + int (*pfnAllowLagCompensation)( void ); +} DLL_FUNCTIONS; + +extern DLL_FUNCTIONS gEntityInterface; + +// Current version. +#define NEW_DLL_FUNCTIONS_VERSION 1 + +typedef struct +{ + // Called right before the object's memory is freed. + // Calls its destructor. + void (*pfnOnFreeEntPrivateData)(edict_t *pEnt); + void (*pfnGameShutdown)(void); + int (*pfnShouldCollide)( edict_t *pentTouched, edict_t *pentOther ); + void (*pfnCvarValue)( const edict_t *pEnt, const char *value ); + void (*pfnCvarValue2)( const edict_t *pEnt, int requestID, const char *cvarName, const char *value ); +} NEW_DLL_FUNCTIONS; +typedef int (*NEW_DLL_FUNCTIONS_FN)( NEW_DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ); + +// Pointers will be null if the game DLL doesn't support this API. +extern NEW_DLL_FUNCTIONS gNewDLLFunctions; + +typedef int (*APIFUNCTION)( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion ); +typedef int (*APIFUNCTION2)( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ); + +#endif EIFACE_H diff --git a/engine/progdefs.h b/engine/progdefs.h new file mode 100644 index 0000000..92a0500 --- /dev/null +++ b/engine/progdefs.h @@ -0,0 +1,224 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PROGDEFS_H +#define PROGDEFS_H +#ifdef _WIN32 +#pragma once +#endif + +typedef struct +{ + float time; + float frametime; + float force_retouch; + string_t mapname; + string_t startspot; + float deathmatch; + float coop; + float teamplay; + float serverflags; + float found_secrets; + vec3_t v_forward; + vec3_t v_up; + vec3_t v_right; + float trace_allsolid; + float trace_startsolid; + float trace_fraction; + vec3_t trace_endpos; + vec3_t trace_plane_normal; + float trace_plane_dist; + edict_t *trace_ent; + float trace_inopen; + float trace_inwater; + int trace_hitgroup; + int trace_flags; + int msg_entity; + int cdAudioTrack; + int maxClients; + int maxEntities; + const char *pStringBase; + + void *pSaveData; + vec3_t vecLandmarkOffset; +} globalvars_t; + + +typedef struct entvars_s +{ + string_t classname; + string_t globalname; + + vec3_t origin; + vec3_t oldorigin; + vec3_t velocity; + vec3_t basevelocity; + vec3_t clbasevelocity; // Base velocity that was passed in to server physics so + // client can predict conveyors correctly. Server zeroes it, so we need to store here, too. + vec3_t movedir; + + vec3_t angles; // Model angles + vec3_t avelocity; // angle velocity (degrees per second) + vec3_t punchangle; // auto-decaying view angle adjustment + vec3_t v_angle; // Viewing angle (player only) + + // For parametric entities + vec3_t endpos; + vec3_t startpos; + float impacttime; + float starttime; + + int fixangle; // 0:nothing, 1:force view angles, 2:add avelocity + float idealpitch; + float pitch_speed; + float ideal_yaw; + float yaw_speed; + + int modelindex; + string_t model; + + int viewmodel; // player's viewmodel + int weaponmodel; // what other players see + + vec3_t absmin; // BB max translated to world coord + vec3_t absmax; // BB max translated to world coord + vec3_t mins; // local BB min + vec3_t maxs; // local BB max + vec3_t size; // maxs - mins + + float ltime; + float nextthink; + + int movetype; + int solid; + + int skin; + int body; // sub-model selection for studiomodels + int effects; + + float gravity; // % of "normal" gravity + float friction; // inverse elasticity of MOVETYPE_BOUNCE + + int light_level; + + int sequence; // animation sequence + int gaitsequence; // movement animation sequence for player (0 for none) + float frame; // % playback position in animation sequences (0..255) + float animtime; // world time when frame was set + float framerate; // animation playback rate (-8x to 8x) + byte controller[4]; // bone controller setting (0..255) + byte blending[2]; // blending amount between sub-sequences (0..255) + + float scale; // sprite rendering scale (0..255) + + int rendermode; + float renderamt; + vec3_t rendercolor; + int renderfx; + + float health; + float frags; + int weapons; // bit mask for available weapons + float takedamage; + + int deadflag; + vec3_t view_ofs; // eye position + + int button; + int impulse; + + edict_t *chain; // Entity pointer when linked into a linked list + edict_t *dmg_inflictor; + edict_t *enemy; + edict_t *aiment; // entity pointer when MOVETYPE_FOLLOW + edict_t *owner; + edict_t *groundentity; + + int spawnflags; + int flags; + + int colormap; // lowbyte topcolor, highbyte bottomcolor + int team; + + float max_health; + float teleport_time; + float armortype; + float armorvalue; + int waterlevel; + int watertype; + + string_t target; + string_t targetname; + string_t netname; + string_t message; + + float dmg_take; + float dmg_save; + float dmg; + float dmgtime; + + string_t noise; + string_t noise1; + string_t noise2; + string_t noise3; + + float speed; + float air_finished; + float pain_finished; + float radsuit_finished; + + edict_t *pContainingEntity; + + int playerclass; + float maxspeed; + + float fov; + int weaponanim; + + int pushmsec; + + int bInDuck; + int flTimeStepSound; + int flSwimTime; + int flDuckTime; + int iStepLeft; + float flFallVelocity; + + int gamestate; + + int oldbuttons; + + int groupinfo; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; + edict_t *euser1; + edict_t *euser2; + edict_t *euser3; + edict_t *euser4; +} entvars_t; + + +#endif // PROGDEFS_H diff --git a/engine/progs.h b/engine/progs.h new file mode 100644 index 0000000..fe4796e --- /dev/null +++ b/engine/progs.h @@ -0,0 +1,82 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PROGS_H +#define PROGS_H + +#include "progdefs.h" + +// 16 simultaneous events, max +#define MAX_EVENT_QUEUE 64 + +#define DEFAULT_EVENT_RESENDS 1 + +#include "event_flags.h" + +typedef struct event_info_s event_info_t; + +#include "event_args.h" + +struct event_info_s +{ + unsigned short index; // 0 implies not in use + + short packet_index; // Use data from state info for entity in delta_packet . -1 implies separate info based on event + // parameter signature + short entity_index; // The edict this event is associated with + + float fire_time; // if non-zero, the time when the event should be fired ( fixed up on the client ) + + event_args_t args; + +// CLIENT ONLY + int flags; // Reliable or not, etc. + +}; + +typedef struct event_state_s event_state_t; + +struct event_state_s +{ + struct event_info_s ei[ MAX_EVENT_QUEUE ]; +}; + +#if !defined( ENTITY_STATEH ) +#include "entity_state.h" +#endif + +#if !defined( EDICT_H ) +#include "edict.h" +#endif + +#define STRUCT_FROM_LINK(l,t,m) ((t *)((byte *)l - (int)&(((t *)0)->m))) +#define EDICT_FROM_AREA(l) STRUCT_FROM_LINK(l,edict_t,area) + +//============================================================================ + +extern char *pr_strings; +extern globalvars_t gGlobalVariables; + +//============================================================================ + +edict_t *ED_Alloc (void); +void ED_Free (edict_t *ed); +void ED_LoadFromFile (char *data); + +edict_t *EDICT_NUM(int n); +int NUM_FOR_EDICT(const edict_t *e); + +#define PROG_TO_EDICT(e) ((edict_t *)((byte *)sv.edicts + e)) + +#endif // PROGS_H diff --git a/engine/shake.h b/engine/shake.h new file mode 100644 index 0000000..849c71a --- /dev/null +++ b/engine/shake.h @@ -0,0 +1,56 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef SHAKE_H +#define SHAKE_H + +// Screen / View effects + +// screen shake +extern int gmsgShake; + +// This structure is sent over the net to describe a screen shake event +typedef struct +{ + unsigned short amplitude; // FIXED 4.12 amount of shake + unsigned short duration; // FIXED 4.12 seconds duration + unsigned short frequency; // FIXED 8.8 noise frequency (low frequency is a jerk,high frequency is a rumble) +} ScreenShake; + +extern void V_ApplyShake( float *origin, float *angles, float factor ); +extern void V_CalcShake( void ); +extern int V_ScreenShake( const char *pszName, int iSize, void *pbuf ); +extern int V_ScreenFade( const char *pszName, int iSize, void *pbuf ); + +// Fade in/out +extern int gmsgFade; + +#define FFADE_IN 0x0000 // Just here so we don't pass 0 into the function +#define FFADE_OUT 0x0001 // Fade out (not in) +#define FFADE_MODULATE 0x0002 // Modulate (don't blend) +#define FFADE_STAYOUT 0x0004 // ignores the duration, stays faded out until new ScreenFade message received +#define FFADE_LONGFADE 0x0008 // used to indicate the fade can be longer than 16 seconds (added for czero) + + +// This structure is sent over the net to describe a screen fade event +typedef struct +{ + unsigned short duration; // FIXED 4.12 seconds duration + unsigned short holdTime; // FIXED 4.12 seconds duration until reset (fade & hold) + short fadeFlags; // flags + byte r, g, b, a; // fade to color ( max alpha ) +} ScreenFade; + +#endif // SHAKE_H + diff --git a/engine/studio.h b/engine/studio.h new file mode 100644 index 0000000..c8a168b --- /dev/null +++ b/engine/studio.h @@ -0,0 +1,368 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + + + + +#ifndef _STUDIO_H_ +#define _STUDIO_H_ + +/* +============================================================================== + +STUDIO MODELS + +Studio models are position independent, so the cache manager can move them. +============================================================================== +*/ + + +#define MAXSTUDIOTRIANGLES 20000 // TODO: tune this +#define MAXSTUDIOVERTS 2048 // TODO: tune this +#define MAXSTUDIOSEQUENCES 2048 // total animation sequences -- KSH incremented +#define MAXSTUDIOSKINS 100 // total textures +#define MAXSTUDIOSRCBONES 512 // bones allowed at source movement +#define MAXSTUDIOBONES 128 // total bones actually used +#define MAXSTUDIOMODELS 32 // sub-models per model +#define MAXSTUDIOBODYPARTS 32 +#define MAXSTUDIOGROUPS 16 +#define MAXSTUDIOANIMATIONS 2048 +#define MAXSTUDIOMESHES 256 +#define MAXSTUDIOEVENTS 1024 +#define MAXSTUDIOPIVOTS 256 +#define MAXSTUDIOCONTROLLERS 8 + +typedef struct +{ + int id; + int version; + + char name[64]; + int length; + + vec3_t eyeposition; // ideal eye position + vec3_t min; // ideal movement hull size + vec3_t max; + + vec3_t bbmin; // clipping bounding box + vec3_t bbmax; + + int flags; + + int numbones; // bones + int boneindex; + + int numbonecontrollers; // bone controllers + int bonecontrollerindex; + + int numhitboxes; // complex bounding boxes + int hitboxindex; + + int numseq; // animation sequences + int seqindex; + + int numseqgroups; // demand loaded sequences + int seqgroupindex; + + int numtextures; // raw textures + int textureindex; + int texturedataindex; + + int numskinref; // replaceable textures + int numskinfamilies; + int skinindex; + + int numbodyparts; + int bodypartindex; + + int numattachments; // queryable attachable points + int attachmentindex; + + int soundtable; + int soundindex; + int soundgroups; + int soundgroupindex; + + int numtransitions; // animation node to animation node transition graph + int transitionindex; +} studiohdr_t; + +// header for demand loaded sequence group data +typedef struct +{ + int id; + int version; + + char name[64]; + int length; +} studioseqhdr_t; + +// bones +typedef struct +{ + char name[32]; // bone name for symbolic links + int parent; // parent bone + int flags; // ?? + int bonecontroller[6]; // bone controller index, -1 == none + float value[6]; // default DoF values + float scale[6]; // scale for delta DoF values +} mstudiobone_t; + + +// bone controllers +typedef struct +{ + int bone; // -1 == 0 + int type; // X, Y, Z, XR, YR, ZR, M + float start; + float end; + int rest; // byte index value at rest + int index; // 0-3 user set controller, 4 mouth +} mstudiobonecontroller_t; + +// intersection boxes +typedef struct +{ + int bone; + int group; // intersection group + vec3_t bbmin; // bounding box + vec3_t bbmax; +} mstudiobbox_t; + +#if !defined( CACHE_USER ) && !defined( QUAKEDEF_H ) +#define CACHE_USER +typedef struct cache_user_s +{ + void *data; +} cache_user_t; +#endif + +// +// demand loaded sequence groups +// +typedef struct +{ + char label[32]; // textual name + char name[64]; // file name + int32 unused1; // was "cache" - index pointer + int unused2; // was "data" - hack for group 0 +} mstudioseqgroup_t; + +// sequence descriptions +typedef struct +{ + char label[32]; // sequence label + + float fps; // frames per second + int flags; // looping/non-looping flags + + int activity; + int actweight; + + int numevents; + int eventindex; + + int numframes; // number of frames per sequence + + int numpivots; // number of foot pivots + int pivotindex; + + int motiontype; + int motionbone; + vec3_t linearmovement; + int automoveposindex; + int automoveangleindex; + + vec3_t bbmin; // per sequence bounding box + vec3_t bbmax; + + int numblends; + int animindex; // mstudioanim_t pointer relative to start of sequence group data + // [blend][bone][X, Y, Z, XR, YR, ZR] + + int blendtype[2]; // X, Y, Z, XR, YR, ZR + float blendstart[2]; // starting value + float blendend[2]; // ending value + int blendparent; + + int seqgroup; // sequence group for demand loading + + int entrynode; // transition node at entry + int exitnode; // transition node at exit + int nodeflags; // transition rules + + int nextseq; // auto advancing sequences +} mstudioseqdesc_t; + +// events +#include "studio_event.h" +/* +typedef struct +{ + int frame; + int event; + int type; + char options[64]; +} mstudioevent_t; +*/ + +// pivots +typedef struct +{ + vec3_t org; // pivot point + int start; + int end; +} mstudiopivot_t; + +// attachment +typedef struct +{ + char name[32]; + int type; + int bone; + vec3_t org; // attachment point + vec3_t vectors[3]; +} mstudioattachment_t; + +typedef struct +{ + unsigned short offset[6]; +} mstudioanim_t; + +// animation frames +typedef union +{ + struct { + byte valid; + byte total; + } num; + short value; +} mstudioanimvalue_t; + + + +// body part index +typedef struct +{ + char name[64]; + int nummodels; + int base; + int modelindex; // index into models array +} mstudiobodyparts_t; + + + +// skin info +typedef struct +{ + char name[64]; + int flags; + int width; + int height; + int index; +} mstudiotexture_t; + + +// skin families +// short index[skinfamilies][skinref] + +// studio models +typedef struct +{ + char name[64]; + + int type; + + float boundingradius; + + int nummesh; + int meshindex; + + int numverts; // number of unique vertices + int vertinfoindex; // vertex bone info + int vertindex; // vertex vec3_t + int numnorms; // number of unique surface normals + int norminfoindex; // normal bone info + int normindex; // normal vec3_t + + int numgroups; // deformation groups + int groupindex; +} mstudiomodel_t; + + +// vec3_t boundingbox[model][bone][2]; // complex intersection info + + +// meshes +typedef struct +{ + int numtris; + int triindex; + int skinref; + int numnorms; // per mesh normals + int normindex; // normal vec3_t +} mstudiomesh_t; + +// triangles +#if 0 +typedef struct +{ + short vertindex; // index into vertex array + short normindex; // index into normal array + short s,t; // s,t position on skin +} mstudiotrivert_t; +#endif + +// lighting options +#define STUDIO_NF_FLATSHADE 0x0001 +#define STUDIO_NF_CHROME 0x0002 +#define STUDIO_NF_FULLBRIGHT 0x0004 +#define STUDIO_NF_NOMIPS 0x0008 +#define STUDIO_NF_ALPHA 0x0010 +#define STUDIO_NF_ADDITIVE 0x0020 +#define STUDIO_NF_MASKED 0x0040 + +// motion flags +#define STUDIO_X 0x0001 +#define STUDIO_Y 0x0002 +#define STUDIO_Z 0x0004 +#define STUDIO_XR 0x0008 +#define STUDIO_YR 0x0010 +#define STUDIO_ZR 0x0020 +#define STUDIO_LX 0x0040 +#define STUDIO_LY 0x0080 +#define STUDIO_LZ 0x0100 +#define STUDIO_AX 0x0200 +#define STUDIO_AY 0x0400 +#define STUDIO_AZ 0x0800 +#define STUDIO_AXR 0x1000 +#define STUDIO_AYR 0x2000 +#define STUDIO_AZR 0x4000 +#define STUDIO_TYPES 0x7FFF +#define STUDIO_RLOOP 0x8000 // controller that wraps shortest distance + +// sequence flags +#define STUDIO_LOOPING 0x0001 + +// bone flags +#define STUDIO_HAS_NORMALS 0x0001 +#define STUDIO_HAS_VERTICES 0x0002 +#define STUDIO_HAS_BBOX 0x0004 +#define STUDIO_HAS_CHROME 0x0008 // if any of the textures have chrome on them + +#define RAD_TO_STUDIO (32768.0/M_PI) +#define STUDIO_TO_RAD (M_PI/32768.0) + +#endif diff --git a/external/SDL2/SDL.h b/external/SDL2/SDL.h new file mode 100644 index 0000000..0cfcdc7 --- /dev/null +++ b/external/SDL2/SDL.h @@ -0,0 +1,163 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL.h + * + * Main include header for the SDL library + */ + +/** + * \mainpage Simple DirectMedia Layer (SDL) + * + * http://www.libsdl.org/ + * + * \section intro_sec Introduction + * + * This is the Simple DirectMedia Layer, a general API that provides low + * level access to audio, keyboard, mouse, joystick, 3D hardware via OpenGL, + * and 2D framebuffer across multiple platforms. + * + * SDL is written in C, but works with C++ natively, and has bindings to + * several other languages, including Ada, C#, Eiffel, Erlang, Euphoria, + * Guile, Haskell, Java, Lisp, Lua, ML, Objective C, Pascal, Perl, PHP, + * Pike, Pliant, Python, Ruby, and Smalltalk. + * + * This library is distributed under the zlib license, which can be + * found in the file "COPYING". This license allows you to use SDL + * freely for any purpose as long as you retain the copyright notice. + * + * The best way to learn how to use SDL is to check out the header files in + * the "include" subdirectory and the programs in the "test" subdirectory. + * The header files and test programs are well commented and always up to date. + * More documentation and FAQs are available online at: + * http://wiki.libsdl.org/ + * + * If you need help with the library, or just want to discuss SDL related + * issues, you can join the developers mailing list: + * http://www.libsdl.org/mailing-list.php + * + * Enjoy! + * Sam Lantinga (slouken@libsdl.org) + */ + +#ifndef _SDL_H +#define _SDL_H + +#include "SDL_main.h" +#include "SDL_stdinc.h" +#include "SDL_assert.h" +#include "SDL_atomic.h" +#include "SDL_audio.h" +#include "SDL_clipboard.h" +#include "SDL_cpuinfo.h" +#include "SDL_endian.h" +#include "SDL_error.h" +#include "SDL_events.h" +#include "SDL_joystick.h" +#include "SDL_gamecontroller.h" +#include "SDL_haptic.h" +#include "SDL_hints.h" +#include "SDL_loadso.h" +#include "SDL_log.h" +#include "SDL_messagebox.h" +#include "SDL_mutex.h" +#include "SDL_power.h" +#include "SDL_render.h" +#include "SDL_rwops.h" +#include "SDL_system.h" +#include "SDL_thread.h" +#include "SDL_timer.h" +#include "SDL_version.h" +#include "SDL_video.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/* As of version 0.5, SDL is loaded dynamically into the application */ + +/** + * \name SDL_INIT_* + * + * These are the flags which may be passed to SDL_Init(). You should + * specify the subsystems which you will be using in your application. + */ +/*@{*/ +#define SDL_INIT_TIMER 0x00000001 +#define SDL_INIT_AUDIO 0x00000010 +#define SDL_INIT_VIDEO 0x00000020 +#define SDL_INIT_JOYSTICK 0x00000200 +#define SDL_INIT_HAPTIC 0x00001000 +#define SDL_INIT_GAMECONTROLLER 0x00002000 /**< turn on game controller also implicitly does JOYSTICK */ +#define SDL_INIT_NOPARACHUTE 0x00100000 /**< Don't catch fatal signals */ +#define SDL_INIT_EVERYTHING ( \ + SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO | \ + SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER \ + ) +/*@}*/ + +/** + * This function initializes the subsystems specified by \c flags + * Unless the ::SDL_INIT_NOPARACHUTE flag is set, it will install cleanup + * signal handlers for some commonly ignored fatal signals (like SIGSEGV). + */ +extern DECLSPEC int SDLCALL SDL_Init(Uint32 flags); + +/** + * This function initializes specific SDL subsystems + */ +extern DECLSPEC int SDLCALL SDL_InitSubSystem(Uint32 flags); + +/** + * This function cleans up specific SDL subsystems + */ +extern DECLSPEC void SDLCALL SDL_QuitSubSystem(Uint32 flags); + +/** + * This function returns a mask of the specified subsystems which have + * previously been initialized. + * + * If \c flags is 0, it returns a mask of all initialized subsystems. + */ +extern DECLSPEC Uint32 SDLCALL SDL_WasInit(Uint32 flags); + +/** + * This function cleans up all initialized subsystems. You should + * call it upon all exit conditions. + */ +extern DECLSPEC void SDLCALL SDL_Quit(void); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_H */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_assert.h b/external/SDL2/SDL_assert.h new file mode 100644 index 0000000..e4620ae --- /dev/null +++ b/external/SDL2/SDL_assert.h @@ -0,0 +1,241 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef _SDL_assert_h +#define _SDL_assert_h + +#include "SDL_config.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +#ifndef SDL_ASSERT_LEVEL +#ifdef SDL_DEFAULT_ASSERT_LEVEL +#define SDL_ASSERT_LEVEL SDL_DEFAULT_ASSERT_LEVEL +#elif defined(_DEBUG) || defined(DEBUG) || \ + (defined(__GNUC__) && !defined(__OPTIMIZE__)) +#define SDL_ASSERT_LEVEL 2 +#else +#define SDL_ASSERT_LEVEL 1 +#endif +#endif /* SDL_ASSERT_LEVEL */ + +/* +These are macros and not first class functions so that the debugger breaks +on the assertion line and not in some random guts of SDL, and so each +assert can have unique static variables associated with it. +*/ + +#if defined(_MSC_VER) +/* Don't include intrin.h here because it contains C++ code */ + extern void __cdecl __debugbreak(void); + #define SDL_TriggerBreakpoint() __debugbreak() +#elif (defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))) + #define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "int $3\n\t" ) +#elif defined(HAVE_SIGNAL_H) + #include + #define SDL_TriggerBreakpoint() raise(SIGTRAP) +#else + /* How do we trigger breakpoints on this platform? */ + #define SDL_TriggerBreakpoint() +#endif + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 supports __func__ as a standard. */ +# define SDL_FUNCTION __func__ +#elif ((__GNUC__ >= 2) || defined(_MSC_VER)) +# define SDL_FUNCTION __FUNCTION__ +#else +# define SDL_FUNCTION "???" +#endif +#define SDL_FILE __FILE__ +#define SDL_LINE __LINE__ + +/* +sizeof (x) makes the compiler still parse the expression even without +assertions enabled, so the code is always checked at compile time, but +doesn't actually generate code for it, so there are no side effects or +expensive checks at run time, just the constant size of what x WOULD be, +which presumably gets optimized out as unused. +This also solves the problem of... + + int somevalue = blah(); + SDL_assert(somevalue == 1); + +...which would cause compiles to complain that somevalue is unused if we +disable assertions. +*/ + +#define SDL_disabled_assert(condition) \ + do { (void) sizeof ((condition)); } while (0) + +typedef enum +{ + SDL_ASSERTION_RETRY, /**< Retry the assert immediately. */ + SDL_ASSERTION_BREAK, /**< Make the debugger trigger a breakpoint. */ + SDL_ASSERTION_ABORT, /**< Terminate the program. */ + SDL_ASSERTION_IGNORE, /**< Ignore the assert. */ + SDL_ASSERTION_ALWAYS_IGNORE /**< Ignore the assert from now on. */ +} SDL_assert_state; + +typedef struct SDL_assert_data +{ + int always_ignore; + unsigned int trigger_count; + const char *condition; + const char *filename; + int linenum; + const char *function; + const struct SDL_assert_data *next; +} SDL_assert_data; + +#if (SDL_ASSERT_LEVEL > 0) + +/* Never call this directly. Use the SDL_assert* macros. */ +extern DECLSPEC SDL_assert_state SDLCALL SDL_ReportAssertion(SDL_assert_data *, + const char *, + const char *, int); + +/* the do {} while(0) avoids dangling else problems: + if (x) SDL_assert(y); else blah(); + ... without the do/while, the "else" could attach to this macro's "if". + We try to handle just the minimum we need here in a macro...the loop, + the static vars, and break points. The heavy lifting is handled in + SDL_ReportAssertion(), in SDL_assert.c. +*/ +#define SDL_enabled_assert(condition) \ + do { \ + while ( !(condition) ) { \ + static struct SDL_assert_data assert_data = { \ + 0, 0, #condition, 0, 0, 0, 0 \ + }; \ + const SDL_assert_state state = SDL_ReportAssertion(&assert_data, \ + SDL_FUNCTION, \ + SDL_FILE, \ + SDL_LINE); \ + if (state == SDL_ASSERTION_RETRY) { \ + continue; /* go again. */ \ + } else if (state == SDL_ASSERTION_BREAK) { \ + SDL_TriggerBreakpoint(); \ + } \ + break; /* not retrying. */ \ + } \ + } while (0) + +#endif /* enabled assertions support code */ + +/* Enable various levels of assertions. */ +#if SDL_ASSERT_LEVEL == 0 /* assertions disabled */ +# define SDL_assert(condition) SDL_disabled_assert(condition) +# define SDL_assert_release(condition) SDL_disabled_assert(condition) +# define SDL_assert_paranoid(condition) SDL_disabled_assert(condition) +#elif SDL_ASSERT_LEVEL == 1 /* release settings. */ +# define SDL_assert(condition) SDL_disabled_assert(condition) +# define SDL_assert_release(condition) SDL_enabled_assert(condition) +# define SDL_assert_paranoid(condition) SDL_disabled_assert(condition) +#elif SDL_ASSERT_LEVEL == 2 /* normal settings. */ +# define SDL_assert(condition) SDL_enabled_assert(condition) +# define SDL_assert_release(condition) SDL_enabled_assert(condition) +# define SDL_assert_paranoid(condition) SDL_disabled_assert(condition) +#elif SDL_ASSERT_LEVEL == 3 /* paranoid settings. */ +# define SDL_assert(condition) SDL_enabled_assert(condition) +# define SDL_assert_release(condition) SDL_enabled_assert(condition) +# define SDL_assert_paranoid(condition) SDL_enabled_assert(condition) +#else +# error Unknown assertion level. +#endif + + +typedef SDL_assert_state (SDLCALL *SDL_AssertionHandler)( + const SDL_assert_data* data, void* userdata); + +/** + * \brief Set an application-defined assertion handler. + * + * This allows an app to show its own assertion UI and/or force the + * response to an assertion failure. If the app doesn't provide this, SDL + * will try to do the right thing, popping up a system-specific GUI dialog, + * and probably minimizing any fullscreen windows. + * + * This callback may fire from any thread, but it runs wrapped in a mutex, so + * it will only fire from one thread at a time. + * + * Setting the callback to NULL restores SDL's original internal handler. + * + * This callback is NOT reset to SDL's internal handler upon SDL_Quit()! + * + * \return SDL_assert_state value of how to handle the assertion failure. + * + * \param handler Callback function, called when an assertion fails. + * \param userdata A pointer passed to the callback as-is. + */ +extern DECLSPEC void SDLCALL SDL_SetAssertionHandler( + SDL_AssertionHandler handler, + void *userdata); + +/** + * \brief Get a list of all assertion failures. + * + * Get all assertions triggered since last call to SDL_ResetAssertionReport(), + * or the start of the program. + * + * The proper way to examine this data looks something like this: + * + * + * const SDL_assert_data *item = SDL_GetAssertionReport(); + * while (item) { + * printf("'%s', %s (%s:%d), triggered %u times, always ignore: %s.\n", + * item->condition, item->function, item->filename, + * item->linenum, item->trigger_count, + * item->always_ignore ? "yes" : "no"); + * item = item->next; + * } + * + * + * \return List of all assertions. + * \sa SDL_ResetAssertionReport + */ +extern DECLSPEC const SDL_assert_data * SDLCALL SDL_GetAssertionReport(void); + +/** + * \brief Reset the list of all assertion failures. + * + * Reset list of all assertions triggered. + * + * \sa SDL_GetAssertionReport + */ +extern DECLSPEC void SDLCALL SDL_ResetAssertionReport(void); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_assert_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_atomic.h b/external/SDL2/SDL_atomic.h new file mode 100644 index 0000000..be60f99 --- /dev/null +++ b/external/SDL2/SDL_atomic.h @@ -0,0 +1,316 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_atomic.h + * + * Atomic operations. + * + * IMPORTANT: + * If you are not an expert in concurrent lockless programming, you should + * only be using the atomic lock and reference counting functions in this + * file. In all other cases you should be protecting your data structures + * with full mutexes. + * + * The list of "safe" functions to use are: + * SDL_AtomicLock() + * SDL_AtomicUnlock() + * SDL_AtomicIncRef() + * SDL_AtomicDecRef() + * + * Seriously, here be dragons! + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * + * You can find out a little more about lockless programming and the + * subtle issues that can arise here: + * http://msdn.microsoft.com/en-us/library/ee418650%28v=vs.85%29.aspx + * + * There's also lots of good information here: + * http://www.1024cores.net/home/lock-free-algorithms + * + * These operations may or may not actually be implemented using + * processor specific atomic operations. When possible they are + * implemented as true processor specific atomic operations. When that + * is not possible the are implemented using locks that *do* use the + * available atomic operations. + * + * All of the atomic operations that modify memory are full memory barriers. + */ + +#ifndef _SDL_atomic_h_ +#define _SDL_atomic_h_ + +#include "SDL_stdinc.h" +#include "SDL_platform.h" + +#include "begin_code.h" + +/* Need to do this here because intrin.h has C++ code in it */ +/* Visual Studio 2005 has a bug where intrin.h conflicts with winnt.h */ +#if defined(_MSC_VER) && (_MSC_VER >= 1500) +#include +#define HAVE_MSC_ATOMICS 1 +#endif + +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \name SDL AtomicLock + * + * The atomic locks are efficient spinlocks using CPU instructions, + * but are vulnerable to starvation and can spin forever if a thread + * holding a lock has been terminated. For this reason you should + * minimize the code executed inside an atomic lock and never do + * expensive things like API or system calls while holding them. + * + * The atomic locks are not safe to lock recursively. + * + * Porting Note: + * The spin lock functions and type are required and can not be + * emulated because they are used in the atomic emulation code. + */ +/*@{*/ + +typedef int SDL_SpinLock; + +/** + * \brief Try to lock a spin lock by setting it to a non-zero value. + * + * \param lock Points to the lock. + * + * \return SDL_TRUE if the lock succeeded, SDL_FALSE if the lock is already held. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_AtomicTryLock(SDL_SpinLock *lock); + +/** + * \brief Lock a spin lock by setting it to a non-zero value. + * + * \param lock Points to the lock. + */ +extern DECLSPEC void SDLCALL SDL_AtomicLock(SDL_SpinLock *lock); + +/** + * \brief Unlock a spin lock by setting it to 0. Always returns immediately + * + * \param lock Points to the lock. + */ +extern DECLSPEC void SDLCALL SDL_AtomicUnlock(SDL_SpinLock *lock); + +/*@}*//*SDL AtomicLock*/ + + +/** + * The compiler barrier prevents the compiler from reordering + * reads and writes to globally visible variables across the call. + */ +#if defined(_MSC_VER) && (_MSC_VER > 1200) +void _ReadWriteBarrier(void); +#pragma intrinsic(_ReadWriteBarrier) +#define SDL_CompilerBarrier() _ReadWriteBarrier() +#elif defined(__GNUC__) +#define SDL_CompilerBarrier() __asm__ __volatile__ ("" : : : "memory") +#else +#define SDL_CompilerBarrier() \ +{ SDL_SpinLock _tmp = 0; SDL_AtomicLock(&_tmp); SDL_AtomicUnlock(&_tmp); } +#endif + +/* Platform specific optimized versions of the atomic functions, + * you can disable these by defining SDL_DISABLE_ATOMIC_INLINE + */ +#if defined(SDL_ATOMIC_DISABLED) && SDL_ATOMIC_DISABLED +#define SDL_DISABLE_ATOMIC_INLINE +#endif +#ifndef SDL_DISABLE_ATOMIC_INLINE + +#ifdef HAVE_MSC_ATOMICS + +#define SDL_AtomicSet(a, v) _InterlockedExchange((long*)&(a)->value, (v)) +#define SDL_AtomicAdd(a, v) _InterlockedExchangeAdd((long*)&(a)->value, (v)) +#define SDL_AtomicCAS(a, oldval, newval) (_InterlockedCompareExchange((long*)&(a)->value, (newval), (oldval)) == (oldval)) +#define SDL_AtomicSetPtr(a, v) _InterlockedExchangePointer((a), (v)) +#if _M_IX86 +#define SDL_AtomicCASPtr(a, oldval, newval) (_InterlockedCompareExchange((long*)(a), (long)(newval), (long)(oldval)) == (long)(oldval)) +#else +#define SDL_AtomicCASPtr(a, oldval, newval) (_InterlockedCompareExchangePointer((a), (newval), (oldval)) == (oldval)) +#endif + +#elif defined(__MACOSX__) +#include + +#define SDL_AtomicCAS(a, oldval, newval) OSAtomicCompareAndSwap32Barrier((oldval), (newval), &(a)->value) +#ifdef __LP64__ +#define SDL_AtomicCASPtr(a, oldval, newval) OSAtomicCompareAndSwap64Barrier((int64_t)(oldval), (int64_t)(newval), (int64_t*)(a)) +#else +#define SDL_AtomicCASPtr(a, oldval, newval) OSAtomicCompareAndSwap32Barrier((int32_t)(oldval), (int32_t)(newval), (int32_t*)(a)) +#endif + +#elif defined(HAVE_GCC_ATOMICS) + +#define SDL_AtomicSet(a, v) __sync_lock_test_and_set(&(a)->value, v) +#define SDL_AtomicAdd(a, v) __sync_fetch_and_add(&(a)->value, v) +#define SDL_AtomicSetPtr(a, v) __sync_lock_test_and_set(a, v) +#define SDL_AtomicCAS(a, oldval, newval) __sync_bool_compare_and_swap(&(a)->value, oldval, newval) +#define SDL_AtomicCASPtr(a, oldval, newval) __sync_bool_compare_and_swap(a, oldval, newval) + +#endif + +#endif /* !SDL_DISABLE_ATOMIC_INLINE */ + + +/** + * \brief A type representing an atomic integer value. It is a struct + * so people don't accidentally use numeric operations on it. + */ +#ifndef SDL_atomic_t_defined +typedef struct { int value; } SDL_atomic_t; +#endif + +/** + * \brief Set an atomic variable to a new value if it is currently an old value. + * + * \return SDL_TRUE if the atomic variable was set, SDL_FALSE otherwise. + * + * \note If you don't know what this function is for, you shouldn't use it! +*/ +#ifndef SDL_AtomicCAS +extern DECLSPEC SDL_bool SDLCALL SDL_AtomicCAS(SDL_atomic_t *a, int oldval, int newval); +#endif + +/** + * \brief Set an atomic variable to a value. + * + * \return The previous value of the atomic variable. + */ +#ifndef SDL_AtomicSet +SDL_FORCE_INLINE int SDL_AtomicSet(SDL_atomic_t *a, int v) +{ + int value; + do { + value = a->value; + } while (!SDL_AtomicCAS(a, value, v)); + return value; +} +#endif + +/** + * \brief Get the value of an atomic variable + */ +#ifndef SDL_AtomicGet +SDL_FORCE_INLINE int SDL_AtomicGet(SDL_atomic_t *a) +{ + int value = a->value; + SDL_CompilerBarrier(); + return value; +} +#endif + +/** + * \brief Add to an atomic variable. + * + * \return The previous value of the atomic variable. + * + * \note This same style can be used for any number operation + */ +#ifndef SDL_AtomicAdd +SDL_FORCE_INLINE int SDL_AtomicAdd(SDL_atomic_t *a, int v) +{ + int value; + do { + value = a->value; + } while (!SDL_AtomicCAS(a, value, (value + v))); + return value; +} +#endif + +/** + * \brief Increment an atomic variable used as a reference count. + */ +#ifndef SDL_AtomicIncRef +#define SDL_AtomicIncRef(a) SDL_AtomicAdd(a, 1) +#endif + +/** + * \brief Decrement an atomic variable used as a reference count. + * + * \return SDL_TRUE if the variable reached zero after decrementing, + * SDL_FALSE otherwise + */ +#ifndef SDL_AtomicDecRef +#define SDL_AtomicDecRef(a) (SDL_AtomicAdd(a, -1) == 1) +#endif + +/** + * \brief Set a pointer to a new value if it is currently an old value. + * + * \return SDL_TRUE if the pointer was set, SDL_FALSE otherwise. + * + * \note If you don't know what this function is for, you shouldn't use it! +*/ +#ifndef SDL_AtomicCASPtr +extern DECLSPEC SDL_bool SDLCALL SDL_AtomicCASPtr(void* *a, void *oldval, void *newval); +#endif + +/** + * \brief Set a pointer to a value atomically. + * + * \return The previous value of the pointer. + */ +#ifndef SDL_AtomicSetPtr +SDL_FORCE_INLINE void* SDL_AtomicSetPtr(void* *a, void* v) +{ + void* value; + do { + value = *a; + } while (!SDL_AtomicCASPtr(a, value, v)); + return value; +} +#endif + +/** + * \brief Get the value of a pointer atomically. + */ +#ifndef SDL_AtomicGetPtr +SDL_FORCE_INLINE void* SDL_AtomicGetPtr(void* *a) +{ + void* value = *a; + SDL_CompilerBarrier(); + return value; +} +#endif + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif + +#include "close_code.h" + +#endif /* _SDL_atomic_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_audio.h b/external/SDL2/SDL_audio.h new file mode 100644 index 0000000..8a1bb70 --- /dev/null +++ b/external/SDL2/SDL_audio.h @@ -0,0 +1,509 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_audio.h + * + * Access to the raw audio mixing buffer for the SDL library. + */ + +#ifndef _SDL_audio_h +#define _SDL_audio_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_endian.h" +#include "SDL_mutex.h" +#include "SDL_thread.h" +#include "SDL_rwops.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \brief Audio format flags. + * + * These are what the 16 bits in SDL_AudioFormat currently mean... + * (Unspecified bits are always zero). + * + * \verbatim + ++-----------------------sample is signed if set + || + || ++-----------sample is bigendian if set + || || + || || ++---sample is float if set + || || || + || || || +---sample bit size---+ + || || || | | + 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00 + \endverbatim + * + * There are macros in SDL 2.0 and later to query these bits. + */ +typedef Uint16 SDL_AudioFormat; + +/** + * \name Audio flags + */ +/*@{*/ + +#define SDL_AUDIO_MASK_BITSIZE (0xFF) +#define SDL_AUDIO_MASK_DATATYPE (1<<8) +#define SDL_AUDIO_MASK_ENDIAN (1<<12) +#define SDL_AUDIO_MASK_SIGNED (1<<15) +#define SDL_AUDIO_BITSIZE(x) (x & SDL_AUDIO_MASK_BITSIZE) +#define SDL_AUDIO_ISFLOAT(x) (x & SDL_AUDIO_MASK_DATATYPE) +#define SDL_AUDIO_ISBIGENDIAN(x) (x & SDL_AUDIO_MASK_ENDIAN) +#define SDL_AUDIO_ISSIGNED(x) (x & SDL_AUDIO_MASK_SIGNED) +#define SDL_AUDIO_ISINT(x) (!SDL_AUDIO_ISFLOAT(x)) +#define SDL_AUDIO_ISLITTLEENDIAN(x) (!SDL_AUDIO_ISBIGENDIAN(x)) +#define SDL_AUDIO_ISUNSIGNED(x) (!SDL_AUDIO_ISSIGNED(x)) + +/** + * \name Audio format flags + * + * Defaults to LSB byte order. + */ +/*@{*/ +#define AUDIO_U8 0x0008 /**< Unsigned 8-bit samples */ +#define AUDIO_S8 0x8008 /**< Signed 8-bit samples */ +#define AUDIO_U16LSB 0x0010 /**< Unsigned 16-bit samples */ +#define AUDIO_S16LSB 0x8010 /**< Signed 16-bit samples */ +#define AUDIO_U16MSB 0x1010 /**< As above, but big-endian byte order */ +#define AUDIO_S16MSB 0x9010 /**< As above, but big-endian byte order */ +#define AUDIO_U16 AUDIO_U16LSB +#define AUDIO_S16 AUDIO_S16LSB +/*@}*/ + +/** + * \name int32 support + * + * New to SDL 1.3. + */ +/*@{*/ +#define AUDIO_S32LSB 0x8020 /**< 32-bit integer samples */ +#define AUDIO_S32MSB 0x9020 /**< As above, but big-endian byte order */ +#define AUDIO_S32 AUDIO_S32LSB +/*@}*/ + +/** + * \name float32 support + * + * New to SDL 1.3. + */ +/*@{*/ +#define AUDIO_F32LSB 0x8120 /**< 32-bit floating point samples */ +#define AUDIO_F32MSB 0x9120 /**< As above, but big-endian byte order */ +#define AUDIO_F32 AUDIO_F32LSB +/*@}*/ + +/** + * \name Native audio byte ordering + */ +/*@{*/ +#if SDL_BYTEORDER == SDL_LIL_ENDIAN +#define AUDIO_U16SYS AUDIO_U16LSB +#define AUDIO_S16SYS AUDIO_S16LSB +#define AUDIO_S32SYS AUDIO_S32LSB +#define AUDIO_F32SYS AUDIO_F32LSB +#else +#define AUDIO_U16SYS AUDIO_U16MSB +#define AUDIO_S16SYS AUDIO_S16MSB +#define AUDIO_S32SYS AUDIO_S32MSB +#define AUDIO_F32SYS AUDIO_F32MSB +#endif +/*@}*/ + +/** + * \name Allow change flags + * + * Which audio format changes are allowed when opening a device. + */ +/*@{*/ +#define SDL_AUDIO_ALLOW_FREQUENCY_CHANGE 0x00000001 +#define SDL_AUDIO_ALLOW_FORMAT_CHANGE 0x00000002 +#define SDL_AUDIO_ALLOW_CHANNELS_CHANGE 0x00000004 +#define SDL_AUDIO_ALLOW_ANY_CHANGE (SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_FORMAT_CHANGE|SDL_AUDIO_ALLOW_CHANNELS_CHANGE) +/*@}*/ + +/*@}*//*Audio flags*/ + +/** + * This function is called when the audio device needs more data. + * + * \param userdata An application-specific parameter saved in + * the SDL_AudioSpec structure + * \param stream A pointer to the audio data buffer. + * \param len The length of that buffer in bytes. + * + * Once the callback returns, the buffer will no longer be valid. + * Stereo samples are stored in a LRLRLR ordering. + */ +typedef void (SDLCALL * SDL_AudioCallback) (void *userdata, Uint8 * stream, + int len); + +/** + * The calculated values in this structure are calculated by SDL_OpenAudio(). + */ +typedef struct SDL_AudioSpec +{ + int freq; /**< DSP frequency -- samples per second */ + SDL_AudioFormat format; /**< Audio data format */ + Uint8 channels; /**< Number of channels: 1 mono, 2 stereo */ + Uint8 silence; /**< Audio buffer silence value (calculated) */ + Uint16 samples; /**< Audio buffer size in samples (power of 2) */ + Uint16 padding; /**< Necessary for some compile environments */ + Uint32 size; /**< Audio buffer size in bytes (calculated) */ + SDL_AudioCallback callback; + void *userdata; +} SDL_AudioSpec; + + +struct SDL_AudioCVT; +typedef void (SDLCALL * SDL_AudioFilter) (struct SDL_AudioCVT * cvt, + SDL_AudioFormat format); + +/** + * A structure to hold a set of audio conversion filters and buffers. + */ +typedef struct SDL_AudioCVT +{ + int needed; /**< Set to 1 if conversion possible */ + SDL_AudioFormat src_format; /**< Source audio format */ + SDL_AudioFormat dst_format; /**< Target audio format */ + double rate_incr; /**< Rate conversion increment */ + Uint8 *buf; /**< Buffer to hold entire audio data */ + int len; /**< Length of original audio buffer */ + int len_cvt; /**< Length of converted audio buffer */ + int len_mult; /**< buffer must be len*len_mult big */ + double len_ratio; /**< Given len, final size is len*len_ratio */ + SDL_AudioFilter filters[10]; /**< Filter list */ + int filter_index; /**< Current audio conversion function */ +} SDL_AudioCVT; + + +/* Function prototypes */ + +/** + * \name Driver discovery functions + * + * These functions return the list of built in audio drivers, in the + * order that they are normally initialized by default. + */ +/*@{*/ +extern DECLSPEC int SDLCALL SDL_GetNumAudioDrivers(void); +extern DECLSPEC const char *SDLCALL SDL_GetAudioDriver(int index); +/*@}*/ + +/** + * \name Initialization and cleanup + * + * \internal These functions are used internally, and should not be used unless + * you have a specific need to specify the audio driver you want to + * use. You should normally use SDL_Init() or SDL_InitSubSystem(). + */ +/*@{*/ +extern DECLSPEC int SDLCALL SDL_AudioInit(const char *driver_name); +extern DECLSPEC void SDLCALL SDL_AudioQuit(void); +/*@}*/ + +/** + * This function returns the name of the current audio driver, or NULL + * if no driver has been initialized. + */ +extern DECLSPEC const char *SDLCALL SDL_GetCurrentAudioDriver(void); + +/** + * This function opens the audio device with the desired parameters, and + * returns 0 if successful, placing the actual hardware parameters in the + * structure pointed to by \c obtained. If \c obtained is NULL, the audio + * data passed to the callback function will be guaranteed to be in the + * requested format, and will be automatically converted to the hardware + * audio format if necessary. This function returns -1 if it failed + * to open the audio device, or couldn't set up the audio thread. + * + * When filling in the desired audio spec structure, + * - \c desired->freq should be the desired audio frequency in samples-per- + * second. + * - \c desired->format should be the desired audio format. + * - \c desired->samples is the desired size of the audio buffer, in + * samples. This number should be a power of two, and may be adjusted by + * the audio driver to a value more suitable for the hardware. Good values + * seem to range between 512 and 8096 inclusive, depending on the + * application and CPU speed. Smaller values yield faster response time, + * but can lead to underflow if the application is doing heavy processing + * and cannot fill the audio buffer in time. A stereo sample consists of + * both right and left channels in LR ordering. + * Note that the number of samples is directly related to time by the + * following formula: \code ms = (samples*1000)/freq \endcode + * - \c desired->size is the size in bytes of the audio buffer, and is + * calculated by SDL_OpenAudio(). + * - \c desired->silence is the value used to set the buffer to silence, + * and is calculated by SDL_OpenAudio(). + * - \c desired->callback should be set to a function that will be called + * when the audio device is ready for more data. It is passed a pointer + * to the audio buffer, and the length in bytes of the audio buffer. + * This function usually runs in a separate thread, and so you should + * protect data structures that it accesses by calling SDL_LockAudio() + * and SDL_UnlockAudio() in your code. + * - \c desired->userdata is passed as the first parameter to your callback + * function. + * + * The audio device starts out playing silence when it's opened, and should + * be enabled for playing by calling \c SDL_PauseAudio(0) when you are ready + * for your audio callback function to be called. Since the audio driver + * may modify the requested size of the audio buffer, you should allocate + * any local mixing buffers after you open the audio device. + */ +extern DECLSPEC int SDLCALL SDL_OpenAudio(SDL_AudioSpec * desired, + SDL_AudioSpec * obtained); + +/** + * SDL Audio Device IDs. + * + * A successful call to SDL_OpenAudio() is always device id 1, and legacy + * SDL audio APIs assume you want this device ID. SDL_OpenAudioDevice() calls + * always returns devices >= 2 on success. The legacy calls are good both + * for backwards compatibility and when you don't care about multiple, + * specific, or capture devices. + */ +typedef Uint32 SDL_AudioDeviceID; + +/** + * Get the number of available devices exposed by the current driver. + * Only valid after a successfully initializing the audio subsystem. + * Returns -1 if an explicit list of devices can't be determined; this is + * not an error. For example, if SDL is set up to talk to a remote audio + * server, it can't list every one available on the Internet, but it will + * still allow a specific host to be specified to SDL_OpenAudioDevice(). + * + * In many common cases, when this function returns a value <= 0, it can still + * successfully open the default device (NULL for first argument of + * SDL_OpenAudioDevice()). + */ +extern DECLSPEC int SDLCALL SDL_GetNumAudioDevices(int iscapture); + +/** + * Get the human-readable name of a specific audio device. + * Must be a value between 0 and (number of audio devices-1). + * Only valid after a successfully initializing the audio subsystem. + * The values returned by this function reflect the latest call to + * SDL_GetNumAudioDevices(); recall that function to redetect available + * hardware. + * + * The string returned by this function is UTF-8 encoded, read-only, and + * managed internally. You are not to free it. If you need to keep the + * string for any length of time, you should make your own copy of it, as it + * will be invalid next time any of several other SDL functions is called. + */ +extern DECLSPEC const char *SDLCALL SDL_GetAudioDeviceName(int index, + int iscapture); + + +/** + * Open a specific audio device. Passing in a device name of NULL requests + * the most reasonable default (and is equivalent to calling SDL_OpenAudio()). + * + * The device name is a UTF-8 string reported by SDL_GetAudioDeviceName(), but + * some drivers allow arbitrary and driver-specific strings, such as a + * hostname/IP address for a remote audio server, or a filename in the + * diskaudio driver. + * + * \return 0 on error, a valid device ID that is >= 2 on success. + * + * SDL_OpenAudio(), unlike this function, always acts on device ID 1. + */ +extern DECLSPEC SDL_AudioDeviceID SDLCALL SDL_OpenAudioDevice(const char + *device, + int iscapture, + const + SDL_AudioSpec * + desired, + SDL_AudioSpec * + obtained, + int + allowed_changes); + + + +/** + * \name Audio state + * + * Get the current audio state. + */ +/*@{*/ +typedef enum +{ + SDL_AUDIO_STOPPED = 0, + SDL_AUDIO_PLAYING, + SDL_AUDIO_PAUSED +} SDL_AudioStatus; +extern DECLSPEC SDL_AudioStatus SDLCALL SDL_GetAudioStatus(void); + +extern DECLSPEC SDL_AudioStatus SDLCALL +SDL_GetAudioDeviceStatus(SDL_AudioDeviceID dev); +/*@}*//*Audio State*/ + +/** + * \name Pause audio functions + * + * These functions pause and unpause the audio callback processing. + * They should be called with a parameter of 0 after opening the audio + * device to start playing sound. This is so you can safely initialize + * data for your callback function after opening the audio device. + * Silence will be written to the audio device during the pause. + */ +/*@{*/ +extern DECLSPEC void SDLCALL SDL_PauseAudio(int pause_on); +extern DECLSPEC void SDLCALL SDL_PauseAudioDevice(SDL_AudioDeviceID dev, + int pause_on); +/*@}*//*Pause audio functions*/ + +/** + * This function loads a WAVE from the data source, automatically freeing + * that source if \c freesrc is non-zero. For example, to load a WAVE file, + * you could do: + * \code + * SDL_LoadWAV_RW(SDL_RWFromFile("sample.wav", "rb"), 1, ...); + * \endcode + * + * If this function succeeds, it returns the given SDL_AudioSpec, + * filled with the audio data format of the wave data, and sets + * \c *audio_buf to a malloc()'d buffer containing the audio data, + * and sets \c *audio_len to the length of that audio buffer, in bytes. + * You need to free the audio buffer with SDL_FreeWAV() when you are + * done with it. + * + * This function returns NULL and sets the SDL error message if the + * wave file cannot be opened, uses an unknown data format, or is + * corrupt. Currently raw and MS-ADPCM WAVE files are supported. + */ +extern DECLSPEC SDL_AudioSpec *SDLCALL SDL_LoadWAV_RW(SDL_RWops * src, + int freesrc, + SDL_AudioSpec * spec, + Uint8 ** audio_buf, + Uint32 * audio_len); + +/** + * Loads a WAV from a file. + * Compatibility convenience function. + */ +#define SDL_LoadWAV(file, spec, audio_buf, audio_len) \ + SDL_LoadWAV_RW(SDL_RWFromFile(file, "rb"),1, spec,audio_buf,audio_len) + +/** + * This function frees data previously allocated with SDL_LoadWAV_RW() + */ +extern DECLSPEC void SDLCALL SDL_FreeWAV(Uint8 * audio_buf); + +/** + * This function takes a source format and rate and a destination format + * and rate, and initializes the \c cvt structure with information needed + * by SDL_ConvertAudio() to convert a buffer of audio data from one format + * to the other. + * + * \return -1 if the format conversion is not supported, 0 if there's + * no conversion needed, or 1 if the audio filter is set up. + */ +extern DECLSPEC int SDLCALL SDL_BuildAudioCVT(SDL_AudioCVT * cvt, + SDL_AudioFormat src_format, + Uint8 src_channels, + int src_rate, + SDL_AudioFormat dst_format, + Uint8 dst_channels, + int dst_rate); + +/** + * Once you have initialized the \c cvt structure using SDL_BuildAudioCVT(), + * created an audio buffer \c cvt->buf, and filled it with \c cvt->len bytes of + * audio data in the source format, this function will convert it in-place + * to the desired format. + * + * The data conversion may expand the size of the audio data, so the buffer + * \c cvt->buf should be allocated after the \c cvt structure is initialized by + * SDL_BuildAudioCVT(), and should be \c cvt->len*cvt->len_mult bytes long. + */ +extern DECLSPEC int SDLCALL SDL_ConvertAudio(SDL_AudioCVT * cvt); + +#define SDL_MIX_MAXVOLUME 128 +/** + * This takes two audio buffers of the playing audio format and mixes + * them, performing addition, volume adjustment, and overflow clipping. + * The volume ranges from 0 - 128, and should be set to ::SDL_MIX_MAXVOLUME + * for full audio volume. Note this does not change hardware volume. + * This is provided for convenience -- you can mix your own audio data. + */ +extern DECLSPEC void SDLCALL SDL_MixAudio(Uint8 * dst, const Uint8 * src, + Uint32 len, int volume); + +/** + * This works like SDL_MixAudio(), but you specify the audio format instead of + * using the format of audio device 1. Thus it can be used when no audio + * device is open at all. + */ +extern DECLSPEC void SDLCALL SDL_MixAudioFormat(Uint8 * dst, + const Uint8 * src, + SDL_AudioFormat format, + Uint32 len, int volume); + +/** + * \name Audio lock functions + * + * The lock manipulated by these functions protects the callback function. + * During a SDL_LockAudio()/SDL_UnlockAudio() pair, you can be guaranteed that + * the callback function is not running. Do not call these from the callback + * function or you will cause deadlock. + */ +/*@{*/ +extern DECLSPEC void SDLCALL SDL_LockAudio(void); +extern DECLSPEC void SDLCALL SDL_LockAudioDevice(SDL_AudioDeviceID dev); +extern DECLSPEC void SDLCALL SDL_UnlockAudio(void); +extern DECLSPEC void SDLCALL SDL_UnlockAudioDevice(SDL_AudioDeviceID dev); +/*@}*//*Audio lock functions*/ + +/** + * This function shuts down audio processing and closes the audio device. + */ +extern DECLSPEC void SDLCALL SDL_CloseAudio(void); +extern DECLSPEC void SDLCALL SDL_CloseAudioDevice(SDL_AudioDeviceID dev); + +/** + * \return 1 if audio device is still functioning, zero if not, -1 on error. + */ +extern DECLSPEC int SDLCALL SDL_AudioDeviceConnected(SDL_AudioDeviceID dev); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_audio_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_bits.h b/external/SDL2/SDL_bits.h new file mode 100644 index 0000000..d678b78 --- /dev/null +++ b/external/SDL2/SDL_bits.h @@ -0,0 +1,94 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_bits.h + * + * Functions for fiddling with bits and bitmasks. + */ + +#ifndef _SDL_bits_h +#define _SDL_bits_h + +#include "SDL_stdinc.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \file SDL_bits.h + */ + +/** + * Get the index of the most significant bit. Result is undefined when called + * with 0. This operation can also be stated as "count leading zeroes" and + * "log base 2". + * + * \return Index of the most significant bit. + */ +SDL_FORCE_INLINE Sint8 +SDL_MostSignificantBitIndex32(Uint32 x) +{ +#if defined(__GNUC__) && __GNUC__ >= 4 + /* Count Leading Zeroes builtin in GCC. + * http://gcc.gnu.org/onlinedocs/gcc-4.3.4/gcc/Other-Builtins.html + */ + return 31 - __builtin_clz(x); +#else + /* Based off of Bit Twiddling Hacks by Sean Eron Anderson + * , released in the public domain. + * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog + */ + const Uint32 b[] = {0x2, 0xC, 0xF0, 0xFF00, 0xFFFF0000}; + const Uint8 S[] = {1, 2, 4, 8, 16}; + + Uint8 msbIndex = 0; + int i; + + for (i = 4; i >= 0; i--) + { + if (x & b[i]) + { + x >>= S[i]; + msbIndex |= S[i]; + } + } + + return msbIndex; +#endif +} + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_bits_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_blendmode.h b/external/SDL2/SDL_blendmode.h new file mode 100644 index 0000000..64a3530 --- /dev/null +++ b/external/SDL2/SDL_blendmode.h @@ -0,0 +1,60 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_blendmode.h + * + * Header file declaring the SDL_BlendMode enumeration + */ + +#ifndef _SDL_blendmode_h +#define _SDL_blendmode_h + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \brief The blend mode used in SDL_RenderCopy() and drawing operations. + */ +typedef enum +{ + SDL_BLENDMODE_NONE = 0x00000000, /**< No blending */ + SDL_BLENDMODE_BLEND = 0x00000001, /**< dst = (src * A) + (dst * (1-A)) */ + SDL_BLENDMODE_ADD = 0x00000002, /**< dst = (src * A) + dst */ + SDL_BLENDMODE_MOD = 0x00000004 /**< dst = src * dst */ +} SDL_BlendMode; + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_video_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_clipboard.h b/external/SDL2/SDL_clipboard.h new file mode 100644 index 0000000..9d86b6b --- /dev/null +++ b/external/SDL2/SDL_clipboard.h @@ -0,0 +1,75 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_clipboard.h + * + * Include file for SDL clipboard handling + */ + +#ifndef _SDL_clipboard_h +#define _SDL_clipboard_h + +#include "SDL_stdinc.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/* Function prototypes */ + +/** + * \brief Put UTF-8 text into the clipboard + * + * \sa SDL_GetClipboardText() + */ +extern DECLSPEC int SDLCALL SDL_SetClipboardText(const char *text); + +/** + * \brief Get UTF-8 text from the clipboard, which must be freed with SDL_free() + * + * \sa SDL_SetClipboardText() + */ +extern DECLSPEC char * SDLCALL SDL_GetClipboardText(void); + +/** + * \brief Returns a flag indicating whether the clipboard exists and contains a text string that is non-empty + * + * \sa SDL_GetClipboardText() + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasClipboardText(void); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_clipboard_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_config.h b/external/SDL2/SDL_config.h new file mode 100644 index 0000000..951a77d --- /dev/null +++ b/external/SDL2/SDL_config.h @@ -0,0 +1,51 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef _SDL_config_h +#define _SDL_config_h + +#include "SDL_platform.h" + +/** + * \file SDL_config.h + */ + +/* Add any platform that doesn't build using the configure system. */ +#if defined(__WIN32__) +#include "SDL_config_windows.h" +#elif defined(__MACOSX__) +#include "SDL_config_macosx.h" +#elif defined(__IPHONEOS__) +#include "SDL_config_iphoneos.h" +#elif defined(__ANDROID__) +#include "SDL_config_android.h" +#elif defined(__PSP__) +#include "SDL_config_psp.h" +#else +/* This is a minimal configuration just to get SDL running on new platforms */ +#include "SDL_config_minimal.h" +#endif /* platform config */ + +#ifdef USING_GENERATED_CONFIG_H +#error Wrong SDL_config.h, check your include path? +#endif + +#endif /* _SDL_config_h */ diff --git a/external/SDL2/SDL_config_android.h b/external/SDL2/SDL_config_android.h new file mode 100644 index 0000000..1676eb4 --- /dev/null +++ b/external/SDL2/SDL_config_android.h @@ -0,0 +1,136 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef _SDL_config_android_h +#define _SDL_config_android_h + +#include "SDL_platform.h" + +/** + * \file SDL_config_android.h + * + * This is a configuration that can be used to build SDL for Android + */ + +#include + +#define HAVE_ALLOCA_H 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STRING_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_MATH_H 1 +#define HAVE_SIGNAL_H 1 + +/* C library functions */ +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#define HAVE_GETENV 1 +#define HAVE_SETENV 1 +#define HAVE_PUTENV 1 +#define HAVE_SETENV 1 +#define HAVE_UNSETENV 1 +#define HAVE_QSORT 1 +#define HAVE_ABS 1 +#define HAVE_BCOPY 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 +#define HAVE_STRLEN 1 +#define HAVE_STRLCPY 1 +#define HAVE_STRLCAT 1 +#define HAVE_STRDUP 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_STRTOL 1 +#define HAVE_STRTOUL 1 +#define HAVE_STRTOLL 1 +#define HAVE_STRTOULL 1 +#define HAVE_STRTOD 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +#define HAVE_STRCASECMP 1 +#define HAVE_STRNCASECMP 1 +#define HAVE_SSCANF 1 +#define HAVE_SNPRINTF 1 +#define HAVE_VSNPRINTF 1 +#define HAVE_M_PI 1 +#define HAVE_ATAN 1 +#define HAVE_ATAN2 1 +#define HAVE_CEIL 1 +#define HAVE_COPYSIGN 1 +#define HAVE_COS 1 +#define HAVE_COSF 1 +#define HAVE_FABS 1 +#define HAVE_FLOOR 1 +#define HAVE_LOG 1 +#define HAVE_POW 1 +#define HAVE_SCALBN 1 +#define HAVE_SIN 1 +#define HAVE_SINF 1 +#define HAVE_SQRT 1 +#define HAVE_SIGACTION 1 +#define HAVE_SETJMP 1 +#define HAVE_NANOSLEEP 1 +#define HAVE_SYSCONF 1 + +#define SIZEOF_VOIDP 4 + +/* Enable various audio drivers */ +#define SDL_AUDIO_DRIVER_ANDROID 1 +#define SDL_AUDIO_DRIVER_DUMMY 1 + +/* Enable various input drivers */ +#define SDL_JOYSTICK_ANDROID 1 +#define SDL_HAPTIC_DUMMY 1 + +/* Enable various shared object loading systems */ +#define SDL_LOADSO_DLOPEN 1 + +/* Enable various threading systems */ +#define SDL_THREAD_PTHREAD 1 +#define SDL_THREAD_PTHREAD_RECURSIVE_MUTEX 1 + +/* Enable various timer systems */ +#define SDL_TIMER_UNIX 1 + +/* Enable various video drivers */ +#define SDL_VIDEO_DRIVER_ANDROID 1 + +/* Enable OpenGL ES */ +#define SDL_VIDEO_OPENGL_ES 1 +#define SDL_VIDEO_RENDER_OGL_ES 1 +#define SDL_VIDEO_RENDER_OGL_ES2 1 + +/* Enable system power support */ +#define SDL_POWER_ANDROID 1 + +#endif /* _SDL_config_android_h */ diff --git a/external/SDL2/SDL_config_iphoneos.h b/external/SDL2/SDL_config_iphoneos.h new file mode 100644 index 0000000..f7925d4 --- /dev/null +++ b/external/SDL2/SDL_config_iphoneos.h @@ -0,0 +1,151 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef _SDL_config_iphoneos_h +#define _SDL_config_iphoneos_h + +#include "SDL_platform.h" + +#ifdef __LP64__ +#define SIZEOF_VOIDP 8 +#else +#define SIZEOF_VOIDP 4 +#endif + +#define HAVE_GCC_ATOMICS 1 + +#define HAVE_ALLOCA_H 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STRING_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_MATH_H 1 +#define HAVE_SIGNAL_H 1 + +/* C library functions */ +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#define HAVE_GETENV 1 +#define HAVE_SETENV 1 +#define HAVE_PUTENV 1 +#define HAVE_SETENV 1 +#define HAVE_UNSETENV 1 +#define HAVE_QSORT 1 +#define HAVE_ABS 1 +#define HAVE_BCOPY 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 +#define HAVE_STRLEN 1 +#define HAVE_STRLCPY 1 +#define HAVE_STRLCAT 1 +#define HAVE_STRDUP 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_STRTOL 1 +#define HAVE_STRTOUL 1 +#define HAVE_STRTOLL 1 +#define HAVE_STRTOULL 1 +#define HAVE_STRTOD 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +#define HAVE_STRCASECMP 1 +#define HAVE_STRNCASECMP 1 +#define HAVE_SSCANF 1 +#define HAVE_SNPRINTF 1 +#define HAVE_VSNPRINTF 1 +#define HAVE_M_PI 1 +#define HAVE_ATAN 1 +#define HAVE_ATAN2 1 +#define HAVE_CEIL 1 +#define HAVE_COPYSIGN 1 +#define HAVE_COS 1 +#define HAVE_COSF 1 +#define HAVE_FABS 1 +#define HAVE_FLOOR 1 +#define HAVE_LOG 1 +#define HAVE_POW 1 +#define HAVE_SCALBN 1 +#define HAVE_SIN 1 +#define HAVE_SINF 1 +#define HAVE_SQRT 1 +#define HAVE_SIGACTION 1 +#define HAVE_SETJMP 1 +#define HAVE_NANOSLEEP 1 +#define HAVE_SYSCONF 1 +#define HAVE_SYSCTLBYNAME 1 + +/* enable iPhone version of Core Audio driver */ +#define SDL_AUDIO_DRIVER_COREAUDIO 1 +/* Enable the dummy audio driver (src/audio/dummy/\*.c) */ +#define SDL_AUDIO_DRIVER_DUMMY 1 + +/* Enable the stub haptic driver (src/haptic/dummy/\*.c) */ +#define SDL_HAPTIC_DISABLED 1 + +/* Enable Unix style SO loading */ +/* Technically this works, but it violates the iPhone developer agreement */ +/* #define SDL_LOADSO_DLOPEN 1 */ + +/* Enable the stub shared object loader (src/loadso/dummy/\*.c) */ +#define SDL_LOADSO_DISABLED 1 + +/* Enable various threading systems */ +#define SDL_THREAD_PTHREAD 1 +#define SDL_THREAD_PTHREAD_RECURSIVE_MUTEX 1 + +/* Enable various timer systems */ +#define SDL_TIMER_UNIX 1 + +/* Supported video drivers */ +#define SDL_VIDEO_DRIVER_UIKIT 1 +#define SDL_VIDEO_DRIVER_DUMMY 1 + +/* enable OpenGL ES */ +#define SDL_VIDEO_OPENGL_ES 1 +#define SDL_VIDEO_RENDER_OGL_ES 1 +#define SDL_VIDEO_RENDER_OGL_ES2 1 + +/* Enable system power support */ +#define SDL_POWER_UIKIT 1 + +/* enable iPhone keyboard support */ +#define SDL_IPHONE_KEYBOARD 1 + +/* enable joystick subsystem */ +#define SDL_JOYSTICK_DISABLED 0 + +/* Set max recognized G-force from accelerometer + See src/joystick/uikit/SDLUIAccelerationDelegate.m for notes on why this is needed + */ +#define SDL_IPHONE_MAX_GFORCE 5.0 + +#endif /* _SDL_config_iphoneos_h */ diff --git a/external/SDL2/SDL_config_macosx.h b/external/SDL2/SDL_config_macosx.h new file mode 100644 index 0000000..fece7fb --- /dev/null +++ b/external/SDL2/SDL_config_macosx.h @@ -0,0 +1,183 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef _SDL_config_macosx_h +#define _SDL_config_macosx_h + +#include "SDL_platform.h" + +/* This gets us MAC_OS_X_VERSION_MIN_REQUIRED... */ +#include + +/* This is a set of defines to configure the SDL features */ + +#ifdef __LP64__ + #define SIZEOF_VOIDP 8 +#else + #define SIZEOF_VOIDP 4 +#endif + +/* Useful headers */ +/* If we specified an SDK or have a post-PowerPC chip, then alloca.h exists. */ +#if ( (MAC_OS_X_VERSION_MIN_REQUIRED >= 1030) || (!defined(__POWERPC__)) ) +#define HAVE_ALLOCA_H 1 +#endif +#define HAVE_SYS_TYPES_H 1 +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STRING_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_MATH_H 1 +#define HAVE_SIGNAL_H 1 + +/* C library functions */ +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#define HAVE_GETENV 1 +#define HAVE_SETENV 1 +#define HAVE_PUTENV 1 +#define HAVE_UNSETENV 1 +#define HAVE_QSORT 1 +#define HAVE_ABS 1 +#define HAVE_BCOPY 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 +#define HAVE_STRLEN 1 +#define HAVE_STRLCPY 1 +#define HAVE_STRLCAT 1 +#define HAVE_STRDUP 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_STRTOL 1 +#define HAVE_STRTOUL 1 +#define HAVE_STRTOLL 1 +#define HAVE_STRTOULL 1 +#define HAVE_STRTOD 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +#define HAVE_STRCASECMP 1 +#define HAVE_STRNCASECMP 1 +#define HAVE_SSCANF 1 +#define HAVE_SNPRINTF 1 +#define HAVE_VSNPRINTF 1 +#define HAVE_CEIL 1 +#define HAVE_COPYSIGN 1 +#define HAVE_COS 1 +#define HAVE_COSF 1 +#define HAVE_FABS 1 +#define HAVE_FLOOR 1 +#define HAVE_LOG 1 +#define HAVE_POW 1 +#define HAVE_SCALBN 1 +#define HAVE_SIN 1 +#define HAVE_SINF 1 +#define HAVE_SQRT 1 +#define HAVE_SIGACTION 1 +#define HAVE_SETJMP 1 +#define HAVE_NANOSLEEP 1 +#define HAVE_SYSCONF 1 +#define HAVE_SYSCTLBYNAME 1 +#define HAVE_ATAN 1 +#define HAVE_ATAN2 1 + +/* Enable various audio drivers */ +#define SDL_AUDIO_DRIVER_COREAUDIO 1 +#define SDL_AUDIO_DRIVER_DISK 1 +#define SDL_AUDIO_DRIVER_DUMMY 1 + +/* Enable various input drivers */ +#define SDL_JOYSTICK_IOKIT 1 +#define SDL_HAPTIC_IOKIT 1 + +/* Enable various shared object loading systems */ +#define SDL_LOADSO_DLOPEN 1 + +/* Enable various threading systems */ +#define SDL_THREAD_PTHREAD 1 +#define SDL_THREAD_PTHREAD_RECURSIVE_MUTEX 1 + +/* Enable various timer systems */ +#define SDL_TIMER_UNIX 1 + +/* Enable various video drivers */ +#define SDL_VIDEO_DRIVER_COCOA 1 +#define SDL_VIDEO_DRIVER_DUMMY 1 +#undef SDL_VIDEO_DRIVER_X11 +#define SDL_VIDEO_DRIVER_X11_DYNAMIC "/usr/X11R6/lib/libX11.6.dylib" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XEXT "/usr/X11R6/lib/libXext.6.dylib" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XINERAMA "/usr/X11R6/lib/libXinerama.1.dylib" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2 "/usr/X11R6/lib/libXi.6.dylib" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR "/usr/X11R6/lib/libXrandr.2.dylib" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS "/usr/X11R6/lib/libXss.1.dylib" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XVIDMODE "/usr/X11R6/lib/libXxf86vm.1.dylib" +#define SDL_VIDEO_DRIVER_X11_XINERAMA 1 +#define SDL_VIDEO_DRIVER_X11_XRANDR 1 +#define SDL_VIDEO_DRIVER_X11_XSCRNSAVER 1 +#define SDL_VIDEO_DRIVER_X11_XSHAPE 1 +#define SDL_VIDEO_DRIVER_X11_XVIDMODE 1 +#define SDL_VIDEO_DRIVER_X11_HAS_XKBKEYCODETOKEYSYM 1 + +#ifdef MAC_OS_X_VERSION_10_8 +/* + * No matter the versions targeted, this is the 10.8 or later SDK, so you have + * to use the external Xquartz, which is a more modern Xlib. Previous SDKs + * used an older Xlib. + */ +#define SDL_VIDEO_DRIVER_X11_XINPUT2 1 +#define SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS 1 +#define SDL_VIDEO_DRIVER_X11_CONST_PARAM_XEXTADDDISPLAY 1 +#endif + +#ifndef SDL_VIDEO_RENDER_OGL +#define SDL_VIDEO_RENDER_OGL 1 +#endif + +/* Enable OpenGL support */ +#ifndef SDL_VIDEO_OPENGL +#define SDL_VIDEO_OPENGL 1 +#endif +#ifndef SDL_VIDEO_OPENGL_CGL +#define SDL_VIDEO_OPENGL_CGL 1 +#endif +#ifndef SDL_VIDEO_OPENGL_GLX +#define SDL_VIDEO_OPENGL_GLX 1 +#endif + +/* Enable system power support */ +#define SDL_POWER_MACOSX 1 + +/* Enable assembly routines */ +#define SDL_ASSEMBLY_ROUTINES 1 +#ifdef __ppc__ +#define SDL_ALTIVEC_BLITTERS 1 +#endif + +#endif /* _SDL_config_macosx_h */ diff --git a/external/SDL2/SDL_config_minimal.h b/external/SDL2/SDL_config_minimal.h new file mode 100644 index 0000000..cb6f0a5 --- /dev/null +++ b/external/SDL2/SDL_config_minimal.h @@ -0,0 +1,302 @@ +/* include/SDL_config.h. Generated from SDL_config.h.in by configure. */ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef _SDL_config_minimal_h +#define _SDL_config_minimal_h + +/** + * \file SDL_config.h.in + * + * This is a set of defines to configure the SDL features + */ + +/* General platform specific identifiers */ +#include "SDL_platform.h" + +/* Make sure that this isn't included by Visual C++ */ +#ifdef _MSC_VER +#error You should run hg revert SDL_config.h +#endif + +/* C language features */ +/* #undef const */ +/* #undef inline */ +/* #undef volatile */ + +/* C datatypes */ +#define SIZEOF_VOIDP 4 +#define HAVE_GCC_ATOMICS 1 +/* #undef HAVE_GCC_SYNC_LOCK_TEST_AND_SET */ +#define HAVE_PTHREAD_SPINLOCK 1 + +/* Comment this if you want to build without any C library requirements */ +#define HAVE_LIBC 1 +#if HAVE_LIBC + +/* Useful headers */ +#define HAVE_ALLOCA_H 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STDLIB_H 1 +#define HAVE_STDARG_H 1 +#define HAVE_MALLOC_H 1 +#define HAVE_MEMORY_H 1 +#define HAVE_STRING_H 1 +#define HAVE_STRINGS_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_MATH_H 1 +#define HAVE_ICONV_H 1 +#define HAVE_SIGNAL_H 1 +/* #undef HAVE_ALTIVEC_H */ +/* #undef HAVE_PTHREAD_NP_H */ +#define HAVE_LIBUDEV_H 1 +#define HAVE_DBUS_DBUS_H 1 + +/* C library functions */ +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#ifndef __WIN32__ /* Don't use C runtime versions of these on Windows */ +#define HAVE_GETENV 1 +#define HAVE_SETENV 1 +#define HAVE_PUTENV 1 +#define HAVE_UNSETENV 1 +#endif +#define HAVE_QSORT 1 +#define HAVE_ABS 1 +#define HAVE_BCOPY 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 +#define HAVE_STRLEN 1 +/* #undef HAVE_STRLCPY */ +/* #undef HAVE_STRLCAT */ +#define HAVE_STRDUP 1 +/* #undef HAVE__STRREV */ +/* #undef HAVE__STRUPR */ +/* #undef HAVE__STRLWR */ +/* #undef HAVE_INDEX */ +/* #undef HAVE_RINDEX */ +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +/* #undef HAVE_ITOA */ +/* #undef HAVE__LTOA */ +/* #undef HAVE__UITOA */ +/* #undef HAVE__ULTOA */ +#define HAVE_STRTOL 1 +#define HAVE_STRTOUL 1 +/* #undef HAVE__I64TOA */ +/* #undef HAVE__UI64TOA */ +#define HAVE_STRTOLL 1 +#define HAVE_STRTOULL 1 +#define HAVE_STRTOD 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +/* #undef HAVE__STRICMP */ +#define HAVE_STRCASECMP 1 +/* #undef HAVE__STRNICMP */ +#define HAVE_STRNCASECMP 1 +#define HAVE_SSCANF 1 +#define HAVE_SNPRINTF 1 +#define HAVE_VSNPRINTF 1 +#define HAVE_M_PI /**/ +#define HAVE_ATAN 1 +#define HAVE_ATAN2 1 +#define HAVE_CEIL 1 +#define HAVE_COPYSIGN 1 +#define HAVE_COS 1 +#define HAVE_COSF 1 +#define HAVE_FABS 1 +#define HAVE_FLOOR 1 +#define HAVE_LOG 1 +#define HAVE_POW 1 +#define HAVE_SCALBN 1 +#define HAVE_SIN 1 +#define HAVE_SINF 1 +#define HAVE_SQRT 1 +#define HAVE_FSEEKO 1 +#define HAVE_FSEEKO64 1 +#define HAVE_SIGACTION 1 +#define HAVE_SA_SIGACTION 1 +#define HAVE_SETJMP 1 +#define HAVE_NANOSLEEP 1 +#define HAVE_SYSCONF 1 +/* #undef HAVE_SYSCTLBYNAME */ +/* #undef HAVE_CLOCK_GETTIME */ +/* #undef HAVE_GETPAGESIZE */ +#define HAVE_MPROTECT 1 +#define HAVE_ICONV 1 +#define HAVE_PTHREAD_SETNAME_NP 1 +/* #undef HAVE_PTHREAD_SET_NAME_NP */ +#define HAVE_SEM_TIMEDWAIT 1 + +#else +/* We may need some replacement for stdarg.h here */ +#include +#endif /* HAVE_LIBC */ + +/* SDL internal assertion support */ +/* #undef SDL_DEFAULT_ASSERT_LEVEL */ + +/* Allow disabling of core subsystems */ +/* #undef SDL_ATOMIC_DISABLED */ +/* #undef SDL_AUDIO_DISABLED */ +/* #undef SDL_CPUINFO_DISABLED */ +/* #undef SDL_EVENTS_DISABLED */ +/* #undef SDL_FILE_DISABLED */ +/* #undef SDL_JOYSTICK_DISABLED */ +/* #undef SDL_HAPTIC_DISABLED */ +/* #undef SDL_LOADSO_DISABLED */ +/* #undef SDL_RENDER_DISABLED */ +/* #undef SDL_THREADS_DISABLED */ +/* #undef SDL_TIMERS_DISABLED */ +/* #undef SDL_VIDEO_DISABLED */ +/* #undef SDL_POWER_DISABLED */ + +/* Enable various audio drivers */ +#define SDL_AUDIO_DRIVER_ALSA 1 +#define SDL_AUDIO_DRIVER_ALSA_DYNAMIC "libasound.so.2" +/* #undef SDL_AUDIO_DRIVER_ARTS */ +/* #undef SDL_AUDIO_DRIVER_ARTS_DYNAMIC */ +#define SDL_AUDIO_DRIVER_PULSEAUDIO 1 +#define SDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC "libpulse-simple.so.0" +/* #undef SDL_AUDIO_DRIVER_BEOSAUDIO */ +/* #undef SDL_AUDIO_DRIVER_BSD */ +/* #undef SDL_AUDIO_DRIVER_COREAUDIO */ +#define SDL_AUDIO_DRIVER_DISK 1 +#define SDL_AUDIO_DRIVER_DUMMY 1 +/* #undef SDL_AUDIO_DRIVER_XAUDIO2 */ +/* #undef SDL_AUDIO_DRIVER_DSOUND */ +/* #undef SDL_AUDIO_DRIVER_ESD */ +/* #undef SDL_AUDIO_DRIVER_ESD_DYNAMIC */ +/* #undef SDL_AUDIO_DRIVER_NAS */ +/* #undef SDL_AUDIO_DRIVER_NAS_DYNAMIC */ +#define SDL_AUDIO_DRIVER_OSS 1 +/* #undef SDL_AUDIO_DRIVER_OSS_SOUNDCARD_H */ +/* #undef SDL_AUDIO_DRIVER_PAUDIO */ +/* #undef SDL_AUDIO_DRIVER_QSA */ +/* #undef SDL_AUDIO_DRIVER_SUNAUDIO */ +/* #undef SDL_AUDIO_DRIVER_WINMM */ +/* #undef SDL_AUDIO_DRIVER_FUSIONSOUND */ +/* #undef SDL_AUDIO_DRIVER_FUSIONSOUND_DYNAMIC */ + +/* Enable various input drivers */ +#define SDL_INPUT_LINUXEV 1 +/* #undef SDL_INPUT_TSLIB */ +/* #undef SDL_JOYSTICK_BEOS */ +/* #undef SDL_JOYSTICK_DINPUT */ +/* #undef SDL_JOYSTICK_DUMMY */ +/* #undef SDL_JOYSTICK_IOKIT */ +#define SDL_JOYSTICK_LINUX 1 +/* #undef SDL_JOYSTICK_WINMM */ +/* #undef SDL_JOYSTICK_USBHID */ +/* #undef SDL_JOYSTICK_USBHID_MACHINE_JOYSTICK_H */ +/* #undef SDL_HAPTIC_DUMMY */ +#define SDL_HAPTIC_LINUX 1 +/* #undef SDL_HAPTIC_IOKIT */ +/* #undef SDL_HAPTIC_DINPUT */ + +/* Enable various shared object loading systems */ +/* #undef SDL_LOADSO_BEOS */ +#define SDL_LOADSO_DLOPEN 1 +/* #undef SDL_LOADSO_DUMMY */ +/* #undef SDL_LOADSO_LDG */ +/* #undef SDL_LOADSO_WINDOWS */ + +/* Enable various threading systems */ +/* #undef SDL_THREAD_BEOS */ +#define SDL_THREAD_PTHREAD 1 +#define SDL_THREAD_PTHREAD_RECURSIVE_MUTEX 1 +/* #undef SDL_THREAD_PTHREAD_RECURSIVE_MUTEX_NP */ +/* #undef SDL_THREAD_WINDOWS */ + +/* Enable various timer systems */ +/* #undef SDL_TIMER_BEOS */ +/* #undef SDL_TIMER_DUMMY */ +#define SDL_TIMER_UNIX 1 +/* #undef SDL_TIMER_WINDOWS */ + +/* Enable various video drivers */ +/* #undef SDL_VIDEO_DRIVER_BWINDOW */ +/* #undef SDL_VIDEO_DRIVER_COCOA */ +/* #undef SDL_VIDEO_DRIVER_DIRECTFB */ +/* #undef SDL_VIDEO_DRIVER_DIRECTFB_DYNAMIC */ +#define SDL_VIDEO_DRIVER_DUMMY 1 +/* #undef SDL_VIDEO_DRIVER_WINDOWS */ +#define SDL_VIDEO_DRIVER_X11 1 +#define SDL_VIDEO_DRIVER_X11_DYNAMIC "libX11.so.6" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XEXT "libXext.so.6" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XCURSOR "libXcursor.so.1" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XINERAMA "libXinerama.so.1" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2 "libXi.so.6" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR "libXrandr.so.2" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS "libXss.so.1" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XVIDMODE "libXxf86vm.so.1" +#define SDL_VIDEO_DRIVER_X11_XCURSOR 1 +#define SDL_VIDEO_DRIVER_X11_XINERAMA 1 +#define SDL_VIDEO_DRIVER_X11_XINPUT2 1 +#define SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 1 +#define SDL_VIDEO_DRIVER_X11_XRANDR 1 +#define SDL_VIDEO_DRIVER_X11_XSCRNSAVER 1 +#define SDL_VIDEO_DRIVER_X11_XSHAPE 1 +#define SDL_VIDEO_DRIVER_X11_XVIDMODE 1 +#define SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS 1 +/* #undef SDL_VIDEO_DRIVER_X11_CONST_PARAM_XEXTADDDISPLAY */ +#define SDL_VIDEO_DRIVER_X11_HAS_XKBKEYCODETOKEYSYM 1 + +/* #undef SDL_VIDEO_RENDER_D3D */ +#define SDL_VIDEO_RENDER_OGL 1 +/* #undef SDL_VIDEO_RENDER_OGL_ES */ +/* #undef SDL_VIDEO_RENDER_OGL_ES2 */ +/* #undef SDL_VIDEO_RENDER_DIRECTFB */ + +/* Enable OpenGL support */ +#define SDL_VIDEO_OPENGL 1 +/* #undef SDL_VIDEO_OPENGL_ES */ +/* #undef SDL_VIDEO_OPENGL_BGL */ +/* #undef SDL_VIDEO_OPENGL_CGL */ +#define SDL_VIDEO_OPENGL_GLX 1 +/* #undef SDL_VIDEO_OPENGL_WGL */ +/* #undef SDL_VIDEO_OPENGL_OSMESA */ +/* #undef SDL_VIDEO_OPENGL_OSMESA_DYNAMIC */ + +/* Enable system power support */ +#define SDL_POWER_LINUX 1 +/* #undef SDL_POWER_WINDOWS */ +/* #undef SDL_POWER_MACOSX */ +/* #undef SDL_POWER_BEOS */ +/* #undef SDL_POWER_HARDWIRED */ + +/* Enable assembly routines */ +#define SDL_ASSEMBLY_ROUTINES 1 +/* #undef SDL_ALTIVEC_BLITTERS */ + +#endif /* _SDL_config_minimal_h */ diff --git a/external/SDL2/SDL_config_nintendods.h b/external/SDL2/SDL_config_nintendods.h new file mode 100644 index 0000000..7b5c21e --- /dev/null +++ b/external/SDL2/SDL_config_nintendods.h @@ -0,0 +1,129 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef _SDL_config_nintendods_h +#define _SDL_config_nintendods_h + +#include "SDL_platform.h" + +/* This is a set of defines to configure the SDL features */ + +#if !defined(_STDINT_H_) && (!defined(HAVE_STDINT_H) || !_HAVE_STDINT_H) +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef signed short int16_t; +typedef unsigned short uint16_t; +typedef signed int int32_t; +typedef unsigned int uint32_t; +typedef signed long long int64_t; +typedef unsigned long long uint64_t; + +/* LiF: __PTRDIFF_TYPE__ was causing errors of conflicting typedefs with the + shipping with devkitARM. copied a similar ifdef from it. */ +#ifndef __PTRDIFF_TYPE__ +typedef unsigned long uintptr_t; +#else +typedef unsigned __PTRDIFF_TYPE__ uintptr_t; +#endif +#endif /* !_STDINT_H_ && !HAVE_STDINT_H */ + +#define SIZEOF_VOIDP 4 + +/* Useful headers */ +#define HAVE_SYS_TYPES_H 1 +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STRING_H 1 +#define HAVE_CTYPE_H 1 + +/* C library functions */ +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#define HAVE_GETENV 1 +#define HAVE_SETENV 1 +#define HAVE_PUTENV 1 +#define HAVE_QSORT 1 +#define HAVE_ABS 1 +#define HAVE_BCOPY 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 +#define HAVE_STRLEN 1 +#define HAVE_STRDUP 1 +#define HAVE_INDEX 1 +#define HAVE_RINDEX 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_STRTOL 1 +#define HAVE_STRTOD 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +#define HAVE_STRICMP 1 +#define HAVE_STRCASECMP 1 +#define HAVE_SSCANF 1 +#define HAVE_SNPRINTF 1 +#define HAVE_VSNPRINTF 1 + +/* DS isn't that sophisticated */ +#define LACKS_SYS_MMAN_H 1 + +/* Enable various audio drivers */ +#define SDL_AUDIO_DRIVER_NDS 1 +/*#define SDL_AUDIO_DRIVER_DUMMY 1 TODO: uncomment this later*/ + +/* Enable various input drivers */ +#define SDL_JOYSTICK_NDS 1 +/*#define SDL_JOYSTICK_DUMMY 1 TODO: uncomment this later*/ + +/* DS has no dynamic linking afaik */ +#define SDL_LOADSO_DISABLED 1 + +/* Enable various threading systems */ +/*#define SDL_THREAD_NDS 1*/ +#define SDL_THREADS_DISABLED 1 + +/* Enable various timer systems */ +#define SDL_TIMER_NDS 1 + +/* Enable various video drivers */ +#define SDL_VIDEO_DRIVER_NDS 1 +#ifdef USE_HW_RENDERER +#define SDL_VIDEO_RENDER_NDS 1 +#else +#define SDL_VIDEO_RENDER_NDS 0 +#endif + +/* Enable system power support */ +#define SDL_POWER_NINTENDODS 1 + +/* Enable haptic support */ +#define SDL_HAPTIC_NDS 1 + +#define SDL_BYTEORDER SDL_LIL_ENDIAN + +#endif /* _SDL_config_nintendods_h */ diff --git a/external/SDL2/SDL_config_pandora.h b/external/SDL2/SDL_config_pandora.h new file mode 100644 index 0000000..ebd9b67 --- /dev/null +++ b/external/SDL2/SDL_config_pandora.h @@ -0,0 +1,124 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef _SDL_config_h +#define _SDL_config_h + +/* This is a set of defines to configure the SDL features */ + +/* General platform specific identifiers */ +#include "SDL_platform.h" + +#ifdef __LP64__ +#define SIZEOF_VOIDP 8 +#else +#define SIZEOF_VOIDP 4 +#endif + +#define SDL_BYTEORDER 1234 + +#define HAVE_ALLOCA_H 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STDLIB_H 1 +#define HAVE_STDARG_H 1 +#define HAVE_MALLOC_H 1 +#define HAVE_MEMORY_H 1 +#define HAVE_STRING_H 1 +#define HAVE_STRINGS_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_MATH_H 1 +#define HAVE_ICONV_H 1 +#define HAVE_SIGNAL_H 1 +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#define HAVE_GETENV 1 +#define HAVE_SETENV 1 +#define HAVE_PUTENV 1 +#define HAVE_UNSETENV 1 +#define HAVE_QSORT 1 +#define HAVE_ABS 1 +#define HAVE_BCOPY 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_STRLEN 1 +#define HAVE_STRDUP 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_STRTOL 1 +#define HAVE_STRTOUL 1 +#define HAVE_STRTOLL 1 +#define HAVE_STRTOULL 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +#define HAVE_STRCASECMP 1 +#define HAVE_STRNCASECMP 1 +#define HAVE_SSCANF 1 +#define HAVE_SNPRINTF 1 +#define HAVE_VSNPRINTF 1 +#define HAVE_M_PI 1 +#define HAVE_CEIL 1 +#define HAVE_COPYSIGN 1 +#define HAVE_COS 1 +#define HAVE_COSF 1 +#define HAVE_FABS 1 +#define HAVE_FLOOR 1 +#define HAVE_LOG 1 +#define HAVE_SCALBN 1 +#define HAVE_SIN 1 +#define HAVE_SINF 1 +#define HAVE_SQRT 1 +#define HAVE_SIGACTION 1 +#define HAVE_SETJMP 1 +#define HAVE_NANOSLEEP 1 + +#define SDL_AUDIO_DRIVER_DUMMY 1 +#define SDL_AUDIO_DRIVER_OSS 1 + +#define SDL_INPUT_LINUXEV 1 +#define SDL_INPUT_TSLIB 1 +#define SDL_JOYSTICK_LINUX 1 +#define SDL_HAPTIC_LINUX 1 + +#define SDL_LOADSO_DLOPEN 1 + +#define SDL_THREAD_PTHREAD 1 +#define SDL_THREAD_PTHREAD_RECURSIVE_MUTEX_NP 1 + +#define SDL_TIMER_UNIX 1 + +#define SDL_VIDEO_DRIVER_DUMMY 1 +#define SDL_VIDEO_DRIVER_X11 1 +#define SDL_VIDEO_DRIVER_PANDORA 1 +#define SDL_VIDEO_RENDER_OGL_ES 1 +#define SDL_VIDEO_OPENGL_ES 1 + +#endif /* _SDL_config_h */ diff --git a/external/SDL2/SDL_config_windows.h b/external/SDL2/SDL_config_windows.h new file mode 100644 index 0000000..c3f229f --- /dev/null +++ b/external/SDL2/SDL_config_windows.h @@ -0,0 +1,192 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef _SDL_config_windows_h +#define _SDL_config_windows_h + +#include "SDL_platform.h" + +/* This is a set of defines to configure the SDL features */ + +#if !defined(_STDINT_H_) && (!defined(HAVE_STDINT_H) || !_HAVE_STDINT_H) +#if defined(__GNUC__) || defined(__DMC__) || defined(__WATCOMC__) +#define HAVE_STDINT_H 1 +#elif defined(_MSC_VER) +typedef signed __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef signed __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef signed __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; +#ifndef _UINTPTR_T_DEFINED +#ifdef _WIN64 +typedef unsigned __int64 uintptr_t; +#else +typedef unsigned int uintptr_t; +#endif +#define _UINTPTR_T_DEFINED +#endif +/* Older Visual C++ headers don't have the Win64-compatible typedefs... */ +#if ((_MSC_VER <= 1200) && (!defined(DWORD_PTR))) +#define DWORD_PTR DWORD +#endif +#if ((_MSC_VER <= 1200) && (!defined(LONG_PTR))) +#define LONG_PTR LONG +#endif +#else /* !__GNUC__ && !_MSC_VER */ +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef signed short int16_t; +typedef unsigned short uint16_t; +typedef signed int int32_t; +typedef unsigned int uint32_t; +typedef signed long long int64_t; +typedef unsigned long long uint64_t; +#ifndef _SIZE_T_DEFINED_ +#define _SIZE_T_DEFINED_ +typedef unsigned int size_t; +#endif +typedef unsigned int uintptr_t; +#endif /* __GNUC__ || _MSC_VER */ +#endif /* !_STDINT_H_ && !HAVE_STDINT_H */ + +#ifdef _WIN64 +# define SIZEOF_VOIDP 8 +#else +# define SIZEOF_VOIDP 4 +#endif + +/* Enabled for SDL 1.2 (binary compatibility) */ +//#define HAVE_LIBC 1 +#ifdef HAVE_LIBC +/* Useful headers */ +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STRING_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_MATH_H 1 +#define HAVE_SIGNAL_H 1 + +/* C library functions */ +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#define HAVE_QSORT 1 +#define HAVE_ABS 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 +#define HAVE_STRLEN 1 +#define HAVE__STRREV 1 +#define HAVE__STRUPR 1 +#define HAVE__STRLWR 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_ITOA 1 +#define HAVE__LTOA 1 +#define HAVE__ULTOA 1 +#define HAVE_STRTOL 1 +#define HAVE_STRTOUL 1 +#define HAVE_STRTOLL 1 +#define HAVE_STRTOD 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +#define HAVE__STRICMP 1 +#define HAVE__STRNICMP 1 +#define HAVE_SSCANF 1 +#define HAVE_M_PI 1 +#define HAVE_ATAN 1 +#define HAVE_ATAN2 1 +#define HAVE_CEIL 1 +#define HAVE_COPYSIGN 1 +#define HAVE_COS 1 +#define HAVE_COSF 1 +#define HAVE_FABS 1 +#define HAVE_FLOOR 1 +#define HAVE_LOG 1 +#define HAVE_POW 1 +#define HAVE_SCALBN 1 +#define HAVE_SIN 1 +#define HAVE_SINF 1 +#define HAVE_SQRT 1 +#else +#define HAVE_STDARG_H 1 +#define HAVE_STDDEF_H 1 +#endif + +/* Enable various audio drivers */ +#define SDL_AUDIO_DRIVER_DSOUND 1 +#ifndef __GNUC__ +#define SDL_AUDIO_DRIVER_XAUDIO2 1 +#endif +#define SDL_AUDIO_DRIVER_WINMM 1 +#define SDL_AUDIO_DRIVER_DISK 1 +#define SDL_AUDIO_DRIVER_DUMMY 1 + +/* Enable various input drivers */ +#define SDL_JOYSTICK_DINPUT 1 +#define SDL_HAPTIC_DINPUT 1 + +/* Enable various shared object loading systems */ +#define SDL_LOADSO_WINDOWS 1 + +/* Enable various threading systems */ +#define SDL_THREAD_WINDOWS 1 + +/* Enable various timer systems */ +#define SDL_TIMER_WINDOWS 1 + +/* Enable various video drivers */ +#define SDL_VIDEO_DRIVER_DUMMY 1 +#define SDL_VIDEO_DRIVER_WINDOWS 1 + +#ifndef SDL_VIDEO_RENDER_D3D +#define SDL_VIDEO_RENDER_D3D 1 +#endif + +/* Enable OpenGL support */ +#ifndef SDL_VIDEO_OPENGL +#define SDL_VIDEO_OPENGL 1 +#endif +#ifndef SDL_VIDEO_OPENGL_WGL +#define SDL_VIDEO_OPENGL_WGL 1 +#endif +#ifndef SDL_VIDEO_RENDER_OGL +#define SDL_VIDEO_RENDER_OGL 1 +#endif + +/* Enable system power support */ +#define SDL_POWER_WINDOWS 1 + +/* Enable assembly routines (Win64 doesn't have inline asm) */ +#ifndef _WIN64 +#define SDL_ASSEMBLY_ROUTINES 1 +#endif + +#endif /* _SDL_config_windows_h */ diff --git a/external/SDL2/SDL_config_wiz.h b/external/SDL2/SDL_config_wiz.h new file mode 100644 index 0000000..2c72958 --- /dev/null +++ b/external/SDL2/SDL_config_wiz.h @@ -0,0 +1,119 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef _SDL_config_h +#define _SDL_config_h + +/* This is a set of defines to configure the SDL features */ + +/* General platform specific identifiers */ +#include "SDL_platform.h" + +#define SDL_BYTEORDER 1234 + +#define HAVE_ALLOCA_H 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STDLIB_H 1 +#define HAVE_STDARG_H 1 +#define HAVE_MALLOC_H 1 +#define HAVE_MEMORY_H 1 +#define HAVE_STRING_H 1 +#define HAVE_STRINGS_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_MATH_H 1 +#define HAVE_ICONV_H 1 +#define HAVE_SIGNAL_H 1 +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#define HAVE_GETENV 1 +#define HAVE_SETENV 1 +#define HAVE_PUTENV 1 +#define HAVE_UNSETENV 1 +#define HAVE_QSORT 1 +#define HAVE_ABS 1 +#define HAVE_BCOPY 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_STRLEN 1 +#define HAVE_STRDUP 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_STRTOL 1 +#define HAVE_STRTOUL 1 +#define HAVE_STRTOLL 1 +#define HAVE_STRTOULL 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +#define HAVE_STRCASECMP 1 +#define HAVE_STRNCASECMP 1 +#define HAVE_SSCANF 1 +#define HAVE_SNPRINTF 1 +#define HAVE_VSNPRINTF 1 +#define HAVE_M_PI 1 +#define HAVE_CEIL 1 +#define HAVE_COPYSIGN 1 +#define HAVE_COS 1 +#define HAVE_COSF 1 +#define HAVE_FABS 1 +#define HAVE_FLOOR 1 +#define HAVE_LOG 1 +#define HAVE_SCALBN 1 +#define HAVE_SIN 1 +#define HAVE_SINF 1 +#define HAVE_SQRT 1 +#define HAVE_SIGACTION 1 +#define HAVE_SETJMP 1 +#define HAVE_NANOSLEEP 1 +#define HAVE_POW 1 + +#define SDL_CDROM_DISABLED 1 +#define SDL_AUDIO_DRIVER_DUMMY 1 +#define SDL_AUDIO_DRIVER_OSS 1 + +#define SDL_INPUT_LINUXEV 1 +#define SDL_INPUT_TSLIB 1 +#define SDL_JOYSTICK_LINUX 1 +#define SDL_HAPTIC_LINUX 1 + +#define SDL_LOADSO_DLOPEN 1 + +#define SDL_THREAD_PTHREAD 1 +#define SDL_THREAD_PTHREAD_RECURSIVE_MUTEX_NP 1 + +#define SDL_TIMER_UNIX 1 + +#define SDL_VIDEO_DRIVER_DUMMY 1 +#define SDL_VIDEO_DRIVER_PANDORA 1 +#define SDL_VIDEO_RENDER_OGL_ES 1 +#define SDL_VIDEO_OPENGL_ES 1 + +#endif /* _SDL_config_h */ diff --git a/external/SDL2/SDL_copying.h b/external/SDL2/SDL_copying.h new file mode 100644 index 0000000..3a8fb75 --- /dev/null +++ b/external/SDL2/SDL_copying.h @@ -0,0 +1,20 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ diff --git a/external/SDL2/SDL_cpuinfo.h b/external/SDL2/SDL_cpuinfo.h new file mode 100644 index 0000000..a3b1012 --- /dev/null +++ b/external/SDL2/SDL_cpuinfo.h @@ -0,0 +1,150 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_cpuinfo.h + * + * CPU feature detection for SDL. + */ + +#ifndef _SDL_cpuinfo_h +#define _SDL_cpuinfo_h + +#include "SDL_stdinc.h" + +/* Need to do this here because intrin.h has C++ code in it */ +/* Visual Studio 2005 has a bug where intrin.h conflicts with winnt.h */ +#if defined(_MSC_VER) && (_MSC_VER >= 1500) +#include +#ifndef _WIN64 +#define __MMX__ +#define __3dNOW__ +#endif +#define __SSE__ +#define __SSE2__ +#elif defined(__MINGW64_VERSION_MAJOR) +#include +#else +#ifdef __ALTIVEC__ +#if HAVE_ALTIVEC_H && !defined(__APPLE_ALTIVEC__) +#include +#undef pixel +#endif +#endif +#ifdef __MMX__ +#include +#endif +#ifdef __3dNOW__ +#include +#endif +#ifdef __SSE__ +#include +#endif +#ifdef __SSE2__ +#include +#endif +#endif + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/* This is a guess for the cacheline size used for padding. + * Most x86 processors have a 64 byte cache line. + * The 64-bit PowerPC processors have a 128 byte cache line. + * We'll use the larger value to be generally safe. + */ +#define SDL_CACHELINE_SIZE 128 + +/** + * This function returns the number of CPU cores available. + */ +extern DECLSPEC int SDLCALL SDL_GetCPUCount(void); + +/** + * This function returns the L1 cache line size of the CPU + * + * This is useful for determining multi-threaded structure padding + * or SIMD prefetch sizes. + */ +extern DECLSPEC int SDLCALL SDL_GetCPUCacheLineSize(void); + +/** + * This function returns true if the CPU has the RDTSC instruction. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasRDTSC(void); + +/** + * This function returns true if the CPU has AltiVec features. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasAltiVec(void); + +/** + * This function returns true if the CPU has MMX features. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasMMX(void); + +/** + * This function returns true if the CPU has 3DNow! features. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_Has3DNow(void); + +/** + * This function returns true if the CPU has SSE features. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE(void); + +/** + * This function returns true if the CPU has SSE2 features. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE2(void); + +/** + * This function returns true if the CPU has SSE3 features. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE3(void); + +/** + * This function returns true if the CPU has SSE4.1 features. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE41(void); + +/** + * This function returns true if the CPU has SSE4.2 features. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE42(void); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_cpuinfo_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_endian.h b/external/SDL2/SDL_endian.h new file mode 100644 index 0000000..a1a11df --- /dev/null +++ b/external/SDL2/SDL_endian.h @@ -0,0 +1,243 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_endian.h + * + * Functions for reading and writing endian-specific values + */ + +#ifndef _SDL_endian_h +#define _SDL_endian_h + +#include "SDL_stdinc.h" + +/** + * \name The two types of endianness + */ +/*@{*/ +#define SDL_LIL_ENDIAN 1234 +#define SDL_BIG_ENDIAN 4321 +/*@}*/ + +#ifndef SDL_BYTEORDER /* Not defined in SDL_config.h? */ +#ifdef __linux__ +#include +#define SDL_BYTEORDER __BYTE_ORDER +#else /* __linux __ */ +#if defined(__hppa__) || \ + defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \ + (defined(__MIPS__) && defined(__MISPEB__)) || \ + defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \ + defined(__sparc__) +#define SDL_BYTEORDER SDL_BIG_ENDIAN +#else +#define SDL_BYTEORDER SDL_LIL_ENDIAN +#endif +#endif /* __linux __ */ +#endif /* !SDL_BYTEORDER */ + + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \file SDL_endian.h + */ +#if defined(__GNUC__) && defined(__i386__) && \ + !(__GNUC__ == 2 && __GNUC_MINOR__ == 95 /* broken gcc version */) +SDL_FORCE_INLINE Uint16 +SDL_Swap16(Uint16 x) +{ + __asm__("xchgb %b0,%h0": "=q"(x):"0"(x)); + return x; +} +#elif defined(__GNUC__) && defined(__x86_64__) +SDL_FORCE_INLINE Uint16 +SDL_Swap16(Uint16 x) +{ + __asm__("xchgb %b0,%h0": "=Q"(x):"0"(x)); + return x; +} +#elif defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__)) +SDL_FORCE_INLINE Uint16 +SDL_Swap16(Uint16 x) +{ + int result; + + __asm__("rlwimi %0,%2,8,16,23": "=&r"(result):"0"(x >> 8), "r"(x)); + return (Uint16)result; +} +#elif defined(__GNUC__) && (defined(__M68000__) || defined(__M68020__)) && !defined(__mcoldfire__) +SDL_FORCE_INLINE Uint16 +SDL_Swap16(Uint16 x) +{ + __asm__("rorw #8,%0": "=d"(x): "0"(x):"cc"); + return x; +} +#else +SDL_FORCE_INLINE Uint16 +SDL_Swap16(Uint16 x) +{ + return SDL_static_cast(Uint16, ((x << 8) | (x >> 8))); +} +#endif + +#if defined(__GNUC__) && defined(__i386__) +SDL_FORCE_INLINE Uint32 +SDL_Swap32(Uint32 x) +{ + __asm__("bswap %0": "=r"(x):"0"(x)); + return x; +} +#elif defined(__GNUC__) && defined(__x86_64__) +SDL_FORCE_INLINE Uint32 +SDL_Swap32(Uint32 x) +{ + __asm__("bswapl %0": "=r"(x):"0"(x)); + return x; +} +#elif defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__)) +SDL_FORCE_INLINE Uint32 +SDL_Swap32(Uint32 x) +{ + Uint32 result; + + __asm__("rlwimi %0,%2,24,16,23": "=&r"(result):"0"(x >> 24), "r"(x)); + __asm__("rlwimi %0,%2,8,8,15": "=&r"(result):"0"(result), "r"(x)); + __asm__("rlwimi %0,%2,24,0,7": "=&r"(result):"0"(result), "r"(x)); + return result; +} +#elif defined(__GNUC__) && (defined(__M68000__) || defined(__M68020__)) && !defined(__mcoldfire__) +SDL_FORCE_INLINE Uint32 +SDL_Swap32(Uint32 x) +{ + __asm__("rorw #8,%0\n\tswap %0\n\trorw #8,%0": "=d"(x): "0"(x):"cc"); + return x; +} +#else +SDL_FORCE_INLINE Uint32 +SDL_Swap32(Uint32 x) +{ + return SDL_static_cast(Uint32, ((x << 24) | ((x << 8) & 0x00FF0000) | + ((x >> 8) & 0x0000FF00) | (x >> 24))); +} +#endif + +#if defined(__GNUC__) && defined(__i386__) +SDL_FORCE_INLINE Uint64 +SDL_Swap64(Uint64 x) +{ + union + { + struct + { + Uint32 a, b; + } s; + Uint64 u; + } v; + v.u = x; + __asm__("bswapl %0 ; bswapl %1 ; xchgl %0,%1": "=r"(v.s.a), "=r"(v.s.b):"0"(v.s.a), + "1"(v.s. + b)); + return v.u; +} +#elif defined(__GNUC__) && defined(__x86_64__) +SDL_FORCE_INLINE Uint64 +SDL_Swap64(Uint64 x) +{ + __asm__("bswapq %0": "=r"(x):"0"(x)); + return x; +} +#else +SDL_FORCE_INLINE Uint64 +SDL_Swap64(Uint64 x) +{ + Uint32 hi, lo; + + /* Separate into high and low 32-bit values and swap them */ + lo = SDL_static_cast(Uint32, x & 0xFFFFFFFF); + x >>= 32; + hi = SDL_static_cast(Uint32, x & 0xFFFFFFFF); + x = SDL_Swap32(lo); + x <<= 32; + x |= SDL_Swap32(hi); + return (x); +} +#endif + + +SDL_FORCE_INLINE float +SDL_SwapFloat(float x) +{ + union + { + float f; + Uint32 ui32; + } swapper; + swapper.f = x; + swapper.ui32 = SDL_Swap32(swapper.ui32); + return swapper.f; +} + + +/** + * \name Swap to native + * Byteswap item from the specified endianness to the native endianness. + */ +/*@{*/ +#if SDL_BYTEORDER == SDL_LIL_ENDIAN +#define SDL_SwapLE16(X) (X) +#define SDL_SwapLE32(X) (X) +#define SDL_SwapLE64(X) (X) +#define SDL_SwapFloatLE(X) (X) +#define SDL_SwapBE16(X) SDL_Swap16(X) +#define SDL_SwapBE32(X) SDL_Swap32(X) +#define SDL_SwapBE64(X) SDL_Swap64(X) +#define SDL_SwapFloatBE(X) SDL_SwapFloat(X) +#else +#define SDL_SwapLE16(X) SDL_Swap16(X) +#define SDL_SwapLE32(X) SDL_Swap32(X) +#define SDL_SwapLE64(X) SDL_Swap64(X) +#define SDL_SwapFloatLE(X) SDL_SwapFloat(X) +#define SDL_SwapBE16(X) (X) +#define SDL_SwapBE32(X) (X) +#define SDL_SwapBE64(X) (X) +#define SDL_SwapFloatBE(X) (X) +#endif +/*@}*//*Swap to native*/ + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_endian_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_error.h b/external/SDL2/SDL_error.h new file mode 100644 index 0000000..81c64da --- /dev/null +++ b/external/SDL2/SDL_error.h @@ -0,0 +1,80 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_error.h + * + * Simple error message routines for SDL. + */ + +#ifndef _SDL_error_h +#define _SDL_error_h + +#include "SDL_stdinc.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/* Public functions */ +/* SDL_SetError() unconditionally returns -1. */ +extern DECLSPEC int SDLCALL SDL_SetError(const char *fmt, ...); +extern DECLSPEC const char *SDLCALL SDL_GetError(void); +extern DECLSPEC void SDLCALL SDL_ClearError(void); + +/** + * \name Internal error functions + * + * \internal + * Private error reporting function - used internally. + */ +/*@{*/ +#define SDL_OutOfMemory() SDL_Error(SDL_ENOMEM) +#define SDL_Unsupported() SDL_Error(SDL_UNSUPPORTED) +#define SDL_InvalidParamError(param) SDL_SetError("Parameter '%s' is invalid", (param)) +typedef enum +{ + SDL_ENOMEM, + SDL_EFREAD, + SDL_EFWRITE, + SDL_EFSEEK, + SDL_UNSUPPORTED, + SDL_LASTERROR +} SDL_errorcode; +/* SDL_Error() unconditionally returns -1. */ +extern DECLSPEC int SDLCALL SDL_Error(SDL_errorcode code); +/*@}*//*Internal error functions*/ + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_error_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_events.h b/external/SDL2/SDL_events.h new file mode 100644 index 0000000..205467e --- /dev/null +++ b/external/SDL2/SDL_events.h @@ -0,0 +1,690 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_events.h + * + * Include file for SDL event handling. + */ + +#ifndef _SDL_events_h +#define _SDL_events_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_video.h" +#include "SDL_keyboard.h" +#include "SDL_mouse.h" +#include "SDL_joystick.h" +#include "SDL_gamecontroller.h" +#include "SDL_quit.h" +#include "SDL_gesture.h" +#include "SDL_touch.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/* General keyboard/mouse state definitions */ +#define SDL_RELEASED 0 +#define SDL_PRESSED 1 + +/** + * \brief The types of events that can be delivered. + */ +typedef enum +{ + SDL_FIRSTEVENT = 0, /**< Unused (do not remove) */ + + /* Application events */ + SDL_QUIT = 0x100, /**< User-requested quit */ + + /* Window events */ + SDL_WINDOWEVENT = 0x200, /**< Window state change */ + SDL_SYSWMEVENT, /**< System specific event */ + + /* Keyboard events */ + SDL_KEYDOWN = 0x300, /**< Key pressed */ + SDL_KEYUP, /**< Key released */ + SDL_TEXTEDITING, /**< Keyboard text editing (composition) */ + SDL_TEXTINPUT, /**< Keyboard text input */ + + /* Mouse events */ + SDL_MOUSEMOTION = 0x400, /**< Mouse moved */ + SDL_MOUSEBUTTONDOWN, /**< Mouse button pressed */ + SDL_MOUSEBUTTONUP, /**< Mouse button released */ + SDL_MOUSEWHEEL, /**< Mouse wheel motion */ + + /* Joystick events */ + SDL_JOYAXISMOTION = 0x600, /**< Joystick axis motion */ + SDL_JOYBALLMOTION, /**< Joystick trackball motion */ + SDL_JOYHATMOTION, /**< Joystick hat position change */ + SDL_JOYBUTTONDOWN, /**< Joystick button pressed */ + SDL_JOYBUTTONUP, /**< Joystick button released */ + SDL_JOYDEVICEADDED, /**< A new joystick has been inserted into the system */ + SDL_JOYDEVICEREMOVED, /**< An opened joystick has been removed */ + + /* Game controller events */ + SDL_CONTROLLERAXISMOTION = 0x650, /**< Game controller axis motion */ + SDL_CONTROLLERBUTTONDOWN, /**< Game controller button pressed */ + SDL_CONTROLLERBUTTONUP, /**< Game controller button released */ + SDL_CONTROLLERDEVICEADDED, /**< A new Game controller has been inserted into the system */ + SDL_CONTROLLERDEVICEREMOVED, /**< An opened Game controller has been removed */ + SDL_CONTROLLERDEVICEREMAPPED, /**< The controller mapping was updated */ + + /* Touch events */ + SDL_FINGERDOWN = 0x700, + SDL_FINGERUP, + SDL_FINGERMOTION, + + /* Gesture events */ + SDL_DOLLARGESTURE = 0x800, + SDL_DOLLARRECORD, + SDL_MULTIGESTURE, + + /* Clipboard events */ + SDL_CLIPBOARDUPDATE = 0x900, /**< The clipboard changed */ + + /* Drag and drop events */ + SDL_DROPFILE = 0x1000, /**< The system requests a file open */ + + /** Events ::SDL_USEREVENT through ::SDL_LASTEVENT are for your use, + * and should be allocated with SDL_RegisterEvents() + */ + SDL_USEREVENT = 0x8000, + + /** + * This last event is only for bounding internal arrays + */ + SDL_LASTEVENT = 0xFFFF +} SDL_EventType; + +/** + * \brief Fields shared by every event + */ +typedef struct SDL_GenericEvent +{ + Uint32 type; + Uint32 timestamp; +} SDL_GenericEvent; + +/** + * \brief Window state change event data (event.window.*) + */ +typedef struct SDL_WindowEvent +{ + Uint32 type; /**< ::SDL_WINDOWEVENT */ + Uint32 timestamp; + Uint32 windowID; /**< The associated window */ + Uint8 event; /**< ::SDL_WindowEventID */ + Uint8 padding1; + Uint8 padding2; + Uint8 padding3; + Sint32 data1; /**< event dependent data */ + Sint32 data2; /**< event dependent data */ +} SDL_WindowEvent; + +/** + * \brief Keyboard button event structure (event.key.*) + */ +typedef struct SDL_KeyboardEvent +{ + Uint32 type; /**< ::SDL_KEYDOWN or ::SDL_KEYUP */ + Uint32 timestamp; + Uint32 windowID; /**< The window with keyboard focus, if any */ + Uint8 state; /**< ::SDL_PRESSED or ::SDL_RELEASED */ + Uint8 repeat; /**< Non-zero if this is a key repeat */ + Uint8 padding2; + Uint8 padding3; + SDL_Keysym keysym; /**< The key that was pressed or released */ +} SDL_KeyboardEvent; + +#define SDL_TEXTEDITINGEVENT_TEXT_SIZE (32) +/** + * \brief Keyboard text editing event structure (event.edit.*) + */ +typedef struct SDL_TextEditingEvent +{ + Uint32 type; /**< ::SDL_TEXTEDITING */ + Uint32 timestamp; + Uint32 windowID; /**< The window with keyboard focus, if any */ + char text[SDL_TEXTEDITINGEVENT_TEXT_SIZE]; /**< The editing text */ + Sint32 start; /**< The start cursor of selected editing text */ + Sint32 length; /**< The length of selected editing text */ +} SDL_TextEditingEvent; + + +#define SDL_TEXTINPUTEVENT_TEXT_SIZE (32) +/** + * \brief Keyboard text input event structure (event.text.*) + */ +typedef struct SDL_TextInputEvent +{ + Uint32 type; /**< ::SDL_TEXTINPUT */ + Uint32 timestamp; + Uint32 windowID; /**< The window with keyboard focus, if any */ + char text[SDL_TEXTINPUTEVENT_TEXT_SIZE]; /**< The input text */ +} SDL_TextInputEvent; + +/** + * \brief Mouse motion event structure (event.motion.*) + */ +typedef struct SDL_MouseMotionEvent +{ + Uint32 type; /**< ::SDL_MOUSEMOTION */ + Uint32 timestamp; + Uint32 windowID; /**< The window with mouse focus, if any */ + Uint32 which; /**< The mouse instance id, or SDL_TOUCH_MOUSEID */ + Uint8 state; /**< The current button state */ + Uint8 padding1; + Uint8 padding2; + Uint8 padding3; + Sint32 x; /**< X coordinate, relative to window */ + Sint32 y; /**< Y coordinate, relative to window */ + Sint32 xrel; /**< The relative motion in the X direction */ + Sint32 yrel; /**< The relative motion in the Y direction */ +} SDL_MouseMotionEvent; + +/** + * \brief Mouse button event structure (event.button.*) + */ +typedef struct SDL_MouseButtonEvent +{ + Uint32 type; /**< ::SDL_MOUSEBUTTONDOWN or ::SDL_MOUSEBUTTONUP */ + Uint32 timestamp; + Uint32 windowID; /**< The window with mouse focus, if any */ + Uint32 which; /**< The mouse instance id, or SDL_TOUCH_MOUSEID */ + Uint8 button; /**< The mouse button index */ + Uint8 state; /**< ::SDL_PRESSED or ::SDL_RELEASED */ + Uint8 padding1; + Uint8 padding2; + Sint32 x; /**< X coordinate, relative to window */ + Sint32 y; /**< Y coordinate, relative to window */ +} SDL_MouseButtonEvent; + +/** + * \brief Mouse wheel event structure (event.wheel.*) + */ +typedef struct SDL_MouseWheelEvent +{ + Uint32 type; /**< ::SDL_MOUSEWHEEL */ + Uint32 timestamp; + Uint32 windowID; /**< The window with mouse focus, if any */ + Uint32 which; /**< The mouse instance id, or SDL_TOUCH_MOUSEID */ + Sint32 x; /**< The amount scrolled horizontally */ + Sint32 y; /**< The amount scrolled vertically */ +} SDL_MouseWheelEvent; + +/** + * \brief Joystick axis motion event structure (event.jaxis.*) + */ +typedef struct SDL_JoyAxisEvent +{ + Uint32 type; /**< ::SDL_JOYAXISMOTION */ + Uint32 timestamp; + SDL_JoystickID which; /**< The joystick instance id */ + Uint8 axis; /**< The joystick axis index */ + Uint8 padding1; + Uint8 padding2; + Uint8 padding3; + Sint16 value; /**< The axis value (range: -32768 to 32767) */ + Uint16 padding4; +} SDL_JoyAxisEvent; + +/** + * \brief Joystick trackball motion event structure (event.jball.*) + */ +typedef struct SDL_JoyBallEvent +{ + Uint32 type; /**< ::SDL_JOYBALLMOTION */ + Uint32 timestamp; + SDL_JoystickID which; /**< The joystick instance id */ + Uint8 ball; /**< The joystick trackball index */ + Uint8 padding1; + Uint8 padding2; + Uint8 padding3; + Sint16 xrel; /**< The relative motion in the X direction */ + Sint16 yrel; /**< The relative motion in the Y direction */ +} SDL_JoyBallEvent; + +/** + * \brief Joystick hat position change event structure (event.jhat.*) + */ +typedef struct SDL_JoyHatEvent +{ + Uint32 type; /**< ::SDL_JOYHATMOTION */ + Uint32 timestamp; + SDL_JoystickID which; /**< The joystick instance id */ + Uint8 hat; /**< The joystick hat index */ + Uint8 value; /**< The hat position value. + * \sa ::SDL_HAT_LEFTUP ::SDL_HAT_UP ::SDL_HAT_RIGHTUP + * \sa ::SDL_HAT_LEFT ::SDL_HAT_CENTERED ::SDL_HAT_RIGHT + * \sa ::SDL_HAT_LEFTDOWN ::SDL_HAT_DOWN ::SDL_HAT_RIGHTDOWN + * + * Note that zero means the POV is centered. + */ + Uint8 padding1; + Uint8 padding2; +} SDL_JoyHatEvent; + +/** + * \brief Joystick button event structure (event.jbutton.*) + */ +typedef struct SDL_JoyButtonEvent +{ + Uint32 type; /**< ::SDL_JOYBUTTONDOWN or ::SDL_JOYBUTTONUP */ + Uint32 timestamp; + SDL_JoystickID which; /**< The joystick instance id */ + Uint8 button; /**< The joystick button index */ + Uint8 state; /**< ::SDL_PRESSED or ::SDL_RELEASED */ + Uint8 padding1; + Uint8 padding2; +} SDL_JoyButtonEvent; + +/** + * \brief Joystick device event structure (event.jdevice.*) + */ +typedef struct SDL_JoyDeviceEvent +{ + Uint32 type; /**< ::SDL_JOYDEVICEADDED or ::SDL_JOYDEVICEREMOVED */ + Uint32 timestamp; + Sint32 which; /**< The joystick device index for the ADDED event, instance id for the REMOVED event */ +} SDL_JoyDeviceEvent; + + +/** + * \brief Game controller axis motion event structure (event.caxis.*) + */ +typedef struct SDL_ControllerAxisEvent +{ + Uint32 type; /**< ::SDL_CONTROLLERAXISMOTION */ + Uint32 timestamp; + SDL_JoystickID which; /**< The joystick instance id */ + Uint8 axis; /**< The controller axis (SDL_GameControllerAxis) */ + Uint8 padding1; + Uint8 padding2; + Uint8 padding3; + Sint16 value; /**< The axis value (range: -32768 to 32767) */ + Uint16 padding4; +} SDL_ControllerAxisEvent; + + +/** + * \brief Game controller button event structure (event.cbutton.*) + */ +typedef struct SDL_ControllerButtonEvent +{ + Uint32 type; /**< ::SDL_CONTROLLERBUTTONDOWN or ::SDL_CONTROLLERBUTTONUP */ + Uint32 timestamp; + SDL_JoystickID which; /**< The joystick instance id */ + Uint8 button; /**< The controller button (SDL_GameControllerButton) */ + Uint8 state; /**< ::SDL_PRESSED or ::SDL_RELEASED */ + Uint8 padding1; + Uint8 padding2; +} SDL_ControllerButtonEvent; + + +/** + * \brief Controller device event structure (event.cdevice.*) + */ +typedef struct SDL_ControllerDeviceEvent +{ + Uint32 type; /**< ::SDL_CONTROLLERDEVICEADDED, ::SDL_CONTROLLERDEVICEREMOVED, or ::SDL_CONTROLLERDEVICEREMAPPED */ + Uint32 timestamp; + Sint32 which; /**< The joystick device index for the ADDED event, instance id for the REMOVED or REMAPPED event */ +} SDL_ControllerDeviceEvent; + + +/** + * \brief Touch finger event structure (event.tfinger.*) + */ +typedef struct SDL_TouchFingerEvent +{ + Uint32 type; /**< ::SDL_FINGERMOTION or ::SDL_FINGERDOWN or ::SDL_FINGERUP */ + Uint32 timestamp; + SDL_TouchID touchId; /**< The touch device id */ + SDL_FingerID fingerId; + float x; /**< Normalized in the range 0...1 */ + float y; /**< Normalized in the range 0...1 */ + float dx; /**< Normalized in the range 0...1 */ + float dy; /**< Normalized in the range 0...1 */ + float pressure; /**< Normalized in the range 0...1 */ +} SDL_TouchFingerEvent; + + +/** + * \brief Multiple Finger Gesture Event (event.mgesture.*) + */ +typedef struct SDL_MultiGestureEvent +{ + Uint32 type; /**< ::SDL_MULTIGESTURE */ + Uint32 timestamp; + SDL_TouchID touchId; /**< The touch device index */ + float dTheta; + float dDist; + float x; + float y; + Uint16 numFingers; + Uint16 padding; +} SDL_MultiGestureEvent; + + +/* (event.dgesture.*) */ +typedef struct SDL_DollarGestureEvent +{ + Uint32 type; /**< ::SDL_DOLLARGESTURE */ + Uint32 timestamp; + SDL_TouchID touchId; /**< The touch device id */ + SDL_GestureID gestureId; + Uint32 numFingers; + float error; + float x; /**< Normalized center of gesture */ + float y; /**< Normalized center of gesture */ +} SDL_DollarGestureEvent; + + +/** + * \brief An event used to request a file open by the system (event.drop.*) + * This event is disabled by default, you can enable it with SDL_EventState() + * \note If you enable this event, you must free the filename in the event. + */ +typedef struct SDL_DropEvent +{ + Uint32 type; /**< ::SDL_DROPFILE */ + Uint32 timestamp; + char *file; /**< The file name, which should be freed with SDL_free() */ +} SDL_DropEvent; + + +/** + * \brief The "quit requested" event + */ +typedef struct SDL_QuitEvent +{ + Uint32 type; /**< ::SDL_QUIT */ + Uint32 timestamp; +} SDL_QuitEvent; + + +/** + * \brief A user-defined event type (event.user.*) + */ +typedef struct SDL_UserEvent +{ + Uint32 type; /**< ::SDL_USEREVENT through ::SDL_NUMEVENTS-1 */ + Uint32 timestamp; + Uint32 windowID; /**< The associated window if any */ + Sint32 code; /**< User defined event code */ + void *data1; /**< User defined data pointer */ + void *data2; /**< User defined data pointer */ +} SDL_UserEvent; + + +struct SDL_SysWMmsg; +typedef struct SDL_SysWMmsg SDL_SysWMmsg; + +/** + * \brief A video driver dependent system event (event.syswm.*) + * This event is disabled by default, you can enable it with SDL_EventState() + * + * \note If you want to use this event, you should include SDL_syswm.h. + */ +typedef struct SDL_SysWMEvent +{ + Uint32 type; /**< ::SDL_SYSWMEVENT */ + Uint32 timestamp; + SDL_SysWMmsg *msg; /**< driver dependent data, defined in SDL_syswm.h */ +} SDL_SysWMEvent; + +/** + * \brief General event structure + */ +typedef union SDL_Event +{ + Uint32 type; /**< Event type, shared with all events */ + SDL_GenericEvent generic; /**< Generic event data */ + SDL_WindowEvent window; /**< Window event data */ + SDL_KeyboardEvent key; /**< Keyboard event data */ + SDL_TextEditingEvent edit; /**< Text editing event data */ + SDL_TextInputEvent text; /**< Text input event data */ + SDL_MouseMotionEvent motion; /**< Mouse motion event data */ + SDL_MouseButtonEvent button; /**< Mouse button event data */ + SDL_MouseWheelEvent wheel; /**< Mouse wheel event data */ + SDL_JoyAxisEvent jaxis; /**< Joystick axis event data */ + SDL_JoyBallEvent jball; /**< Joystick ball event data */ + SDL_JoyHatEvent jhat; /**< Joystick hat event data */ + SDL_JoyButtonEvent jbutton; /**< Joystick button event data */ + SDL_JoyDeviceEvent jdevice; /**< Joystick device change event data */ + SDL_ControllerAxisEvent caxis; /**< Game Controller axis event data */ + SDL_ControllerButtonEvent cbutton; /**< Game Controller button event data */ + SDL_ControllerDeviceEvent cdevice; /**< Game Controller device event data */ + SDL_QuitEvent quit; /**< Quit request event data */ + SDL_UserEvent user; /**< Custom event data */ + SDL_SysWMEvent syswm; /**< System dependent window event data */ + SDL_TouchFingerEvent tfinger; /**< Touch finger event data */ + SDL_MultiGestureEvent mgesture; /**< Gesture event data */ + SDL_DollarGestureEvent dgesture; /**< Gesture event data */ + SDL_DropEvent drop; /**< Drag and drop event data */ + + /* This is necessary for ABI compatibility between Visual C++ and GCC + Visual C++ will respect the push pack pragma and use 52 bytes for + this structure, and GCC will use the alignment of the largest datatype + within the union, which is 8 bytes. + + So... we'll add padding to force the size to be 56 bytes for both. + */ + Uint8 padding[56]; +} SDL_Event; + + +/* Function prototypes */ + +/** + * Pumps the event loop, gathering events from the input devices. + * + * This function updates the event queue and internal input device state. + * + * This should only be run in the thread that sets the video mode. + */ +extern DECLSPEC void SDLCALL SDL_PumpEvents(void); + +/*@{*/ +typedef enum +{ + SDL_ADDEVENT, + SDL_PEEKEVENT, + SDL_GETEVENT +} SDL_eventaction; + +/** + * Checks the event queue for messages and optionally returns them. + * + * If \c action is ::SDL_ADDEVENT, up to \c numevents events will be added to + * the back of the event queue. + * + * If \c action is ::SDL_PEEKEVENT, up to \c numevents events at the front + * of the event queue, within the specified minimum and maximum type, + * will be returned and will not be removed from the queue. + * + * If \c action is ::SDL_GETEVENT, up to \c numevents events at the front + * of the event queue, within the specified minimum and maximum type, + * will be returned and will be removed from the queue. + * + * \return The number of events actually stored, or -1 if there was an error. + * + * This function is thread-safe. + */ +extern DECLSPEC int SDLCALL SDL_PeepEvents(SDL_Event * events, int numevents, + SDL_eventaction action, + Uint32 minType, Uint32 maxType); +/*@}*/ + +/** + * Checks to see if certain event types are in the event queue. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasEvent(Uint32 type); +extern DECLSPEC SDL_bool SDLCALL SDL_HasEvents(Uint32 minType, Uint32 maxType); + +/** + * This function clears events from the event queue + */ +extern DECLSPEC void SDLCALL SDL_FlushEvent(Uint32 type); +extern DECLSPEC void SDLCALL SDL_FlushEvents(Uint32 minType, Uint32 maxType); + +/** + * \brief Polls for currently pending events. + * + * \return 1 if there are any pending events, or 0 if there are none available. + * + * \param event If not NULL, the next event is removed from the queue and + * stored in that area. + */ +extern DECLSPEC int SDLCALL SDL_PollEvent(SDL_Event * event); + +/** + * \brief Waits indefinitely for the next available event. + * + * \return 1, or 0 if there was an error while waiting for events. + * + * \param event If not NULL, the next event is removed from the queue and + * stored in that area. + */ +extern DECLSPEC int SDLCALL SDL_WaitEvent(SDL_Event * event); + +/** + * \brief Waits until the specified timeout (in milliseconds) for the next + * available event. + * + * \return 1, or 0 if there was an error while waiting for events. + * + * \param event If not NULL, the next event is removed from the queue and + * stored in that area. + */ +extern DECLSPEC int SDLCALL SDL_WaitEventTimeout(SDL_Event * event, + int timeout); + +/** + * \brief Add an event to the event queue. + * + * \return 1 on success, 0 if the event was filtered, or -1 if the event queue + * was full or there was some other error. + */ +extern DECLSPEC int SDLCALL SDL_PushEvent(SDL_Event * event); + +typedef int (SDLCALL * SDL_EventFilter) (void *userdata, SDL_Event * event); + +/** + * Sets up a filter to process all events before they change internal state and + * are posted to the internal event queue. + * + * The filter is protypted as: + * \code + * int SDL_EventFilter(void *userdata, SDL_Event * event); + * \endcode + * + * If the filter returns 1, then the event will be added to the internal queue. + * If it returns 0, then the event will be dropped from the queue, but the + * internal state will still be updated. This allows selective filtering of + * dynamically arriving events. + * + * \warning Be very careful of what you do in the event filter function, as + * it may run in a different thread! + * + * There is one caveat when dealing with the ::SDL_QUITEVENT event type. The + * event filter is only called when the window manager desires to close the + * application window. If the event filter returns 1, then the window will + * be closed, otherwise the window will remain open if possible. + * + * If the quit event is generated by an interrupt signal, it will bypass the + * internal queue and be delivered to the application at the next event poll. + */ +extern DECLSPEC void SDLCALL SDL_SetEventFilter(SDL_EventFilter filter, + void *userdata); + +/** + * Return the current event filter - can be used to "chain" filters. + * If there is no event filter set, this function returns SDL_FALSE. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_GetEventFilter(SDL_EventFilter * filter, + void **userdata); + +/** + * Add a function which is called when an event is added to the queue. + */ +extern DECLSPEC void SDLCALL SDL_AddEventWatch(SDL_EventFilter filter, + void *userdata); + +/** + * Remove an event watch function added with SDL_AddEventWatch() + */ +extern DECLSPEC void SDLCALL SDL_DelEventWatch(SDL_EventFilter filter, + void *userdata); + +/** + * Run the filter function on the current event queue, removing any + * events for which the filter returns 0. + */ +extern DECLSPEC void SDLCALL SDL_FilterEvents(SDL_EventFilter filter, + void *userdata); + +/*@{*/ +#define SDL_QUERY -1 +#define SDL_IGNORE 0 +#define SDL_DISABLE 0 +#define SDL_ENABLE 1 + +/** + * This function allows you to set the state of processing certain events. + * - If \c state is set to ::SDL_IGNORE, that event will be automatically + * dropped from the event queue and will not event be filtered. + * - If \c state is set to ::SDL_ENABLE, that event will be processed + * normally. + * - If \c state is set to ::SDL_QUERY, SDL_EventState() will return the + * current processing state of the specified event. + */ +extern DECLSPEC Uint8 SDLCALL SDL_EventState(Uint32 type, int state); +/*@}*/ +#define SDL_GetEventState(type) SDL_EventState(type, SDL_QUERY) + +/** + * This function allocates a set of user-defined events, and returns + * the beginning event number for that set of events. + * + * If there aren't enough user-defined events left, this function + * returns (Uint32)-1 + */ +extern DECLSPEC Uint32 SDLCALL SDL_RegisterEvents(int numevents); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_events_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_gamecontroller.h b/external/SDL2/SDL_gamecontroller.h new file mode 100644 index 0000000..318e51a --- /dev/null +++ b/external/SDL2/SDL_gamecontroller.h @@ -0,0 +1,298 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_gamecontroller.h + * + * Include file for SDL game controller event handling + */ + +#ifndef _SDL_gamecontroller_h +#define _SDL_gamecontroller_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_joystick.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \file SDL_gamecontroller.h + * + * In order to use these functions, SDL_Init() must have been called + * with the ::SDL_INIT_JOYSTICK flag. This causes SDL to scan the system + * for game controllers, and load appropriate drivers. + */ + +/* The gamecontroller structure used to identify an SDL game controller */ +struct _SDL_GameController; +typedef struct _SDL_GameController SDL_GameController; + + +typedef enum +{ + SDL_CONTROLLER_BINDTYPE_NONE = 0, + SDL_CONTROLLER_BINDTYPE_BUTTON, + SDL_CONTROLLER_BINDTYPE_AXIS, + SDL_CONTROLLER_BINDTYPE_HAT +} SDL_GameControllerBindType; + +/** + * Get the SDL joystick layer binding for this controller button/axis mapping + */ +typedef struct SDL_GameControllerButtonBind +{ + SDL_GameControllerBindType bindType; + union + { + int button; + int axis; + struct { + int hat; + int hat_mask; + } hat; + } value; + +} SDL_GameControllerButtonBind; + + +/** + * To count the number of game controllers in the system for the following: + * int nJoysticks = SDL_NumJoysticks(); + * int nGameControllers = 0; + * for ( int i = 0; i < nJoysticks; i++ ) { + * if ( SDL_IsGameController(i) ) { + * nGameControllers++; + * } + * } + * + * Using the SDL_HINT_GAMECONTROLLERCONFIG hint or the SDL_GameControllerAddMapping you can add support for controllers SDL is unaware of or cause an existing controller to have a different binding. The format is: + * guid,name,mappings + * + * Where GUID is the string value from SDL_JoystickGetGUIDString(), name is the human readable string for the device and mappings are controller mappings to joystick ones. + * Under Windows there is a reserved GUID of "xinput" that covers any XInput devices. + * The mapping format for joystick is: + * bX - a joystick button, index X + * hX.Y - hat X with value Y + * aX - axis X of the joystick + * Buttons can be used as a controller axis and vice versa. + * + * This string shows an example of a valid mapping for a controller + * "341a3608000000000000504944564944,Aferglow PS3 Controller,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftshoulder:b4,rightshoulder:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7", + * + */ + +/** + * Add or update an existing mapping configuration + * + * \return 1 if mapping is added, 0 if updated, -1 on error + */ +extern DECLSPEC int SDLCALL SDL_GameControllerAddMapping( const char* mappingSring ); + +/** + * Get a mapping string for a GUID + * + * \return the mapping string. Must be freed with SDL_free. Returns NULL if no mapping is available + */ +extern DECLSPEC char * SDLCALL SDL_GameControllerMappingForGUID( SDL_JoystickGUID guid ); + +/** + * Get a mapping string for an open GameController + * + * \return the mapping string. Must be freed with SDL_free. Returns NULL if no mapping is available + */ +extern DECLSPEC char * SDLCALL SDL_GameControllerMapping( SDL_GameController * gamecontroller ); + +/** + * Is the joystick on this index supported by the game controller interface? + */ +extern DECLSPEC SDL_bool SDLCALL SDL_IsGameController(int joystick_index); + + +/** + * Get the implementation dependent name of a game controller. + * This can be called before any controllers are opened. + * If no name can be found, this function returns NULL. + */ +extern DECLSPEC const char *SDLCALL SDL_GameControllerNameForIndex(int joystick_index); + +/** + * Open a game controller for use. + * The index passed as an argument refers to the N'th game controller on the system. + * This index is the value which will identify this controller in future controller + * events. + * + * \return A controller identifier, or NULL if an error occurred. + */ +extern DECLSPEC SDL_GameController *SDLCALL SDL_GameControllerOpen(int joystick_index); + +/** + * Return the name for this currently opened controller + */ +extern DECLSPEC const char *SDLCALL SDL_GameControllerName(SDL_GameController *gamecontroller); + +/** + * Returns SDL_TRUE if the controller has been opened and currently connected, + * or SDL_FALSE if it has not. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_GameControllerGetAttached(SDL_GameController *gamecontroller); + +/** + * Get the underlying joystick object used by a controller + */ +extern DECLSPEC SDL_Joystick *SDLCALL SDL_GameControllerGetJoystick(SDL_GameController *gamecontroller); + +/** + * Enable/disable controller event polling. + * + * If controller events are disabled, you must call SDL_GameControllerUpdate() + * yourself and check the state of the controller when you want controller + * information. + * + * The state can be one of ::SDL_QUERY, ::SDL_ENABLE or ::SDL_IGNORE. + */ +extern DECLSPEC int SDLCALL SDL_GameControllerEventState(int state); + +/** + * Update the current state of the open game controllers. + * + * This is called automatically by the event loop if any game controller + * events are enabled. + */ +extern DECLSPEC void SDLCALL SDL_GameControllerUpdate(void); + + +/** + * The list of axii available from a controller + */ +typedef enum +{ + SDL_CONTROLLER_AXIS_INVALID = -1, + SDL_CONTROLLER_AXIS_LEFTX, + SDL_CONTROLLER_AXIS_LEFTY, + SDL_CONTROLLER_AXIS_RIGHTX, + SDL_CONTROLLER_AXIS_RIGHTY, + SDL_CONTROLLER_AXIS_TRIGGERLEFT, + SDL_CONTROLLER_AXIS_TRIGGERRIGHT, + SDL_CONTROLLER_AXIS_MAX +} SDL_GameControllerAxis; + +/** + * turn this string into a axis mapping + */ +extern DECLSPEC SDL_GameControllerAxis SDLCALL SDL_GameControllerGetAxisFromString(const char *pchString); + +/** + * turn this axis enum into a string mapping + */ +extern DECLSPEC const char* SDLCALL SDL_GameControllerGetStringForAxis(SDL_GameControllerAxis axis); + +/** + * Get the SDL joystick layer binding for this controller button mapping + */ +extern DECLSPEC SDL_GameControllerButtonBind SDLCALL +SDL_GameControllerGetBindForAxis(SDL_GameController *gamecontroller, + SDL_GameControllerAxis axis); + +/** + * Get the current state of an axis control on a game controller. + * + * The state is a value ranging from -32768 to 32767. + * + * The axis indices start at index 0. + */ +extern DECLSPEC Sint16 SDLCALL +SDL_GameControllerGetAxis(SDL_GameController *gamecontroller, + SDL_GameControllerAxis axis); + +/** + * The list of buttons available from a controller + */ +typedef enum +{ + SDL_CONTROLLER_BUTTON_INVALID = -1, + SDL_CONTROLLER_BUTTON_A, + SDL_CONTROLLER_BUTTON_B, + SDL_CONTROLLER_BUTTON_X, + SDL_CONTROLLER_BUTTON_Y, + SDL_CONTROLLER_BUTTON_BACK, + SDL_CONTROLLER_BUTTON_GUIDE, + SDL_CONTROLLER_BUTTON_START, + SDL_CONTROLLER_BUTTON_LEFTSTICK, + SDL_CONTROLLER_BUTTON_RIGHTSTICK, + SDL_CONTROLLER_BUTTON_LEFTSHOULDER, + SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, + SDL_CONTROLLER_BUTTON_DPAD_UP, + SDL_CONTROLLER_BUTTON_DPAD_DOWN, + SDL_CONTROLLER_BUTTON_DPAD_LEFT, + SDL_CONTROLLER_BUTTON_DPAD_RIGHT, + SDL_CONTROLLER_BUTTON_MAX +} SDL_GameControllerButton; + +/** + * turn this string into a button mapping + */ +extern DECLSPEC SDL_GameControllerButton SDLCALL SDL_GameControllerGetButtonFromString(const char *pchString); + +/** + * turn this button enum into a string mapping + */ +extern DECLSPEC const char* SDLCALL SDL_GameControllerGetStringForButton(SDL_GameControllerButton button); + +/** + * Get the SDL joystick layer binding for this controller button mapping + */ +extern DECLSPEC SDL_GameControllerButtonBind SDLCALL +SDL_GameControllerGetBindForButton(SDL_GameController *gamecontroller, + SDL_GameControllerButton button); + + +/** + * Get the current state of a button on a game controller. + * + * The button indices start at index 0. + */ +extern DECLSPEC Uint8 SDLCALL SDL_GameControllerGetButton(SDL_GameController *gamecontroller, + SDL_GameControllerButton button); + +/** + * Close a controller previously opened with SDL_GameControllerOpen(). + */ +extern DECLSPEC void SDLCALL SDL_GameControllerClose(SDL_GameController *gamecontroller); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_gamecontroller_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_gesture.h b/external/SDL2/SDL_gesture.h new file mode 100644 index 0000000..458c367 --- /dev/null +++ b/external/SDL2/SDL_gesture.h @@ -0,0 +1,91 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_gesture.h + * + * Include file for SDL gesture event handling. + */ + +#ifndef _SDL_gesture_h +#define _SDL_gesture_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_video.h" + +#include "SDL_touch.h" + + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +typedef Sint64 SDL_GestureID; + +/* Function prototypes */ + +/** + * \brief Begin Recording a gesture on the specified touch, or all touches (-1) + * + * + */ +extern DECLSPEC int SDLCALL SDL_RecordGesture(SDL_TouchID touchId); + + +/** + * \brief Save all currently loaded Dollar Gesture templates + * + * + */ +extern DECLSPEC int SDLCALL SDL_SaveAllDollarTemplates(SDL_RWops *src); + +/** + * \brief Save a currently loaded Dollar Gesture template + * + * + */ +extern DECLSPEC int SDLCALL SDL_SaveDollarTemplate(SDL_GestureID gestureId,SDL_RWops *src); + + +/** + * \brief Load Dollar Gesture templates from a file + * + * + */ +extern DECLSPEC int SDLCALL SDL_LoadDollarTemplates(SDL_TouchID touchId, SDL_RWops *src); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_gesture_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_haptic.h b/external/SDL2/SDL_haptic.h new file mode 100644 index 0000000..c506c29 --- /dev/null +++ b/external/SDL2/SDL_haptic.h @@ -0,0 +1,1200 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_haptic.h + * + * \brief The SDL Haptic subsystem allows you to control haptic (force feedback) + * devices. + * + * The basic usage is as follows: + * - Initialize the Subsystem (::SDL_INIT_HAPTIC). + * - Open a Haptic Device. + * - SDL_HapticOpen() to open from index. + * - SDL_HapticOpenFromJoystick() to open from an existing joystick. + * - Create an effect (::SDL_HapticEffect). + * - Upload the effect with SDL_HapticNewEffect(). + * - Run the effect with SDL_HapticRunEffect(). + * - (optional) Free the effect with SDL_HapticDestroyEffect(). + * - Close the haptic device with SDL_HapticClose(). + * + * \par Simple rumble example: + * \code + * SDL_Haptic *haptic; + * + * // Open the device + * haptic = SDL_HapticOpen( 0 ); + * if (haptic == NULL) + * return -1; + * + * // Initialize simple rumble + * if (SDL_HapticRumbleInit( haptic ) != 0) + * return -1; + * + * // Play effect at 50% strength for 2 seconds + * if (SDL_HapticRumblePlay( haptic, 0.5, 2000 ) != 0) + * return -1; + * SDL_Delay( 2000 ); + * + * // Clean up + * SDL_HapticClose( haptic ); + * \endcode + * + * \par Complete example: + * \code + * int test_haptic( SDL_Joystick * joystick ) { + * SDL_Haptic *haptic; + * SDL_HapticEffect effect; + * int effect_id; + * + * // Open the device + * haptic = SDL_HapticOpenFromJoystick( joystick ); + * if (haptic == NULL) return -1; // Most likely joystick isn't haptic + * + * // See if it can do sine waves + * if ((SDL_HapticQuery(haptic) & SDL_HAPTIC_SINE)==0) { + * SDL_HapticClose(haptic); // No sine effect + * return -1; + * } + * + * // Create the effect + * memset( &effect, 0, sizeof(SDL_HapticEffect) ); // 0 is safe default + * effect.type = SDL_HAPTIC_SINE; + * effect.periodic.direction.type = SDL_HAPTIC_POLAR; // Polar coordinates + * effect.periodic.direction.dir[0] = 18000; // Force comes from south + * effect.periodic.period = 1000; // 1000 ms + * effect.periodic.magnitude = 20000; // 20000/32767 strength + * effect.periodic.length = 5000; // 5 seconds long + * effect.periodic.attack_length = 1000; // Takes 1 second to get max strength + * effect.periodic.fade_length = 1000; // Takes 1 second to fade away + * + * // Upload the effect + * effect_id = SDL_HapticNewEffect( haptic, &effect ); + * + * // Test the effect + * SDL_HapticRunEffect( haptic, effect_id, 1 ); + * SDL_Delay( 5000); // Wait for the effect to finish + * + * // We destroy the effect, although closing the device also does this + * SDL_HapticDestroyEffect( haptic, effect_id ); + * + * // Close the device + * SDL_HapticClose(haptic); + * + * return 0; // Success + * } + * \endcode + * + * You can also find out more information on my blog: + * http://bobbens.dyndns.org/journal/2010/sdl_haptic/ + * + * \author Edgar Simo Serra + */ + +#ifndef _SDL_haptic_h +#define _SDL_haptic_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_joystick.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { + /* *INDENT-ON* */ +#endif /* __cplusplus */ + +/** + * \typedef SDL_Haptic + * + * \brief The haptic structure used to identify an SDL haptic. + * + * \sa SDL_HapticOpen + * \sa SDL_HapticOpenFromJoystick + * \sa SDL_HapticClose + */ +struct _SDL_Haptic; +typedef struct _SDL_Haptic SDL_Haptic; + + +/** + * \name Haptic features + * + * Different haptic features a device can have. + */ +/*@{*/ + +/** + * \name Haptic effects + */ +/*@{*/ + +/** + * \brief Constant effect supported. + * + * Constant haptic effect. + * + * \sa SDL_HapticCondition + */ +#define SDL_HAPTIC_CONSTANT (1<<0) + +/** + * \brief Sine wave effect supported. + * + * Periodic haptic effect that simulates sine waves. + * + * \sa SDL_HapticPeriodic + */ +#define SDL_HAPTIC_SINE (1<<1) + +/** + * \brief Square wave effect supported. + * + * Periodic haptic effect that simulates square waves. + * + * \sa SDL_HapticPeriodic + */ +#define SDL_HAPTIC_SQUARE (1<<2) + +/** + * \brief Triangle wave effect supported. + * + * Periodic haptic effect that simulates triangular waves. + * + * \sa SDL_HapticPeriodic + */ +#define SDL_HAPTIC_TRIANGLE (1<<3) + +/** + * \brief Sawtoothup wave effect supported. + * + * Periodic haptic effect that simulates saw tooth up waves. + * + * \sa SDL_HapticPeriodic + */ +#define SDL_HAPTIC_SAWTOOTHUP (1<<4) + +/** + * \brief Sawtoothdown wave effect supported. + * + * Periodic haptic effect that simulates saw tooth down waves. + * + * \sa SDL_HapticPeriodic + */ +#define SDL_HAPTIC_SAWTOOTHDOWN (1<<5) + +/** + * \brief Ramp effect supported. + * + * Ramp haptic effect. + * + * \sa SDL_HapticRamp + */ +#define SDL_HAPTIC_RAMP (1<<6) + +/** + * \brief Spring effect supported - uses axes position. + * + * Condition haptic effect that simulates a spring. Effect is based on the + * axes position. + * + * \sa SDL_HapticCondition + */ +#define SDL_HAPTIC_SPRING (1<<7) + +/** + * \brief Damper effect supported - uses axes velocity. + * + * Condition haptic effect that simulates dampening. Effect is based on the + * axes velocity. + * + * \sa SDL_HapticCondition + */ +#define SDL_HAPTIC_DAMPER (1<<8) + +/** + * \brief Inertia effect supported - uses axes acceleration. + * + * Condition haptic effect that simulates inertia. Effect is based on the axes + * acceleration. + * + * \sa SDL_HapticCondition + */ +#define SDL_HAPTIC_INERTIA (1<<9) + +/** + * \brief Friction effect supported - uses axes movement. + * + * Condition haptic effect that simulates friction. Effect is based on the + * axes movement. + * + * \sa SDL_HapticCondition + */ +#define SDL_HAPTIC_FRICTION (1<<10) + +/** + * \brief Custom effect is supported. + * + * User defined custom haptic effect. + */ +#define SDL_HAPTIC_CUSTOM (1<<11) + +/*@}*//*Haptic effects*/ + +/* These last few are features the device has, not effects */ + +/** + * \brief Device can set global gain. + * + * Device supports setting the global gain. + * + * \sa SDL_HapticSetGain + */ +#define SDL_HAPTIC_GAIN (1<<12) + +/** + * \brief Device can set autocenter. + * + * Device supports setting autocenter. + * + * \sa SDL_HapticSetAutocenter + */ +#define SDL_HAPTIC_AUTOCENTER (1<<13) + +/** + * \brief Device can be queried for effect status. + * + * Device can be queried for effect status. + * + * \sa SDL_HapticGetEffectStatus + */ +#define SDL_HAPTIC_STATUS (1<<14) + +/** + * \brief Device can be paused. + * + * \sa SDL_HapticPause + * \sa SDL_HapticUnpause + */ +#define SDL_HAPTIC_PAUSE (1<<15) + + +/** + * \name Direction encodings + */ +/*@{*/ + +/** + * \brief Uses polar coordinates for the direction. + * + * \sa SDL_HapticDirection + */ +#define SDL_HAPTIC_POLAR 0 + +/** + * \brief Uses cartesian coordinates for the direction. + * + * \sa SDL_HapticDirection + */ +#define SDL_HAPTIC_CARTESIAN 1 + +/** + * \brief Uses spherical coordinates for the direction. + * + * \sa SDL_HapticDirection + */ +#define SDL_HAPTIC_SPHERICAL 2 + +/*@}*//*Direction encodings*/ + +/*@}*//*Haptic features*/ + +/* + * Misc defines. + */ + +/** + * \brief Used to play a device an infinite number of times. + * + * \sa SDL_HapticRunEffect + */ +#define SDL_HAPTIC_INFINITY 4294967295U + + +/** + * \brief Structure that represents a haptic direction. + * + * Directions can be specified by: + * - ::SDL_HAPTIC_POLAR : Specified by polar coordinates. + * - ::SDL_HAPTIC_CARTESIAN : Specified by cartesian coordinates. + * - ::SDL_HAPTIC_SPHERICAL : Specified by spherical coordinates. + * + * Cardinal directions of the haptic device are relative to the positioning + * of the device. North is considered to be away from the user. + * + * The following diagram represents the cardinal directions: + * \verbatim + .--. + |__| .-------. + |=.| |.-----.| + |--| || || + | | |'-----'| + |__|~')_____(' + [ COMPUTER ] + + + North (0,-1) + ^ + | + | + (1,0) West <----[ HAPTIC ]----> East (-1,0) + | + | + v + South (0,1) + + + [ USER ] + \|||/ + (o o) + ---ooO-(_)-Ooo--- + \endverbatim + * + * If type is ::SDL_HAPTIC_POLAR, direction is encoded by hundredths of a + * degree starting north and turning clockwise. ::SDL_HAPTIC_POLAR only uses + * the first \c dir parameter. The cardinal directions would be: + * - North: 0 (0 degrees) + * - East: 9000 (90 degrees) + * - South: 18000 (180 degrees) + * - West: 27000 (270 degrees) + * + * If type is ::SDL_HAPTIC_CARTESIAN, direction is encoded by three positions + * (X axis, Y axis and Z axis (with 3 axes)). ::SDL_HAPTIC_CARTESIAN uses + * the first three \c dir parameters. The cardinal directions would be: + * - North: 0,-1, 0 + * - East: -1, 0, 0 + * - South: 0, 1, 0 + * - West: 1, 0, 0 + * + * The Z axis represents the height of the effect if supported, otherwise + * it's unused. In cartesian encoding (1, 2) would be the same as (2, 4), you + * can use any multiple you want, only the direction matters. + * + * If type is ::SDL_HAPTIC_SPHERICAL, direction is encoded by two rotations. + * The first two \c dir parameters are used. The \c dir parameters are as + * follows (all values are in hundredths of degrees): + * - Degrees from (1, 0) rotated towards (0, 1). + * - Degrees towards (0, 0, 1) (device needs at least 3 axes). + * + * + * Example of force coming from the south with all encodings (force coming + * from the south means the user will have to pull the stick to counteract): + * \code + * SDL_HapticDirection direction; + * + * // Cartesian directions + * direction.type = SDL_HAPTIC_CARTESIAN; // Using cartesian direction encoding. + * direction.dir[0] = 0; // X position + * direction.dir[1] = 1; // Y position + * // Assuming the device has 2 axes, we don't need to specify third parameter. + * + * // Polar directions + * direction.type = SDL_HAPTIC_POLAR; // We'll be using polar direction encoding. + * direction.dir[0] = 18000; // Polar only uses first parameter + * + * // Spherical coordinates + * direction.type = SDL_HAPTIC_SPHERICAL; // Spherical encoding + * direction.dir[0] = 9000; // Since we only have two axes we don't need more parameters. + * \endcode + * + * \sa SDL_HAPTIC_POLAR + * \sa SDL_HAPTIC_CARTESIAN + * \sa SDL_HAPTIC_SPHERICAL + * \sa SDL_HapticEffect + * \sa SDL_HapticNumAxes + */ +typedef struct SDL_HapticDirection +{ + Uint8 type; /**< The type of encoding. */ + Sint32 dir[3]; /**< The encoded direction. */ +} SDL_HapticDirection; + + +/** + * \brief A structure containing a template for a Constant effect. + * + * The struct is exclusive to the ::SDL_HAPTIC_CONSTANT effect. + * + * A constant effect applies a constant force in the specified direction + * to the joystick. + * + * \sa SDL_HAPTIC_CONSTANT + * \sa SDL_HapticEffect + */ +typedef struct SDL_HapticConstant +{ + /* Header */ + Uint16 type; /**< ::SDL_HAPTIC_CONSTANT */ + SDL_HapticDirection direction; /**< Direction of the effect. */ + + /* Replay */ + Uint32 length; /**< Duration of the effect. */ + Uint16 delay; /**< Delay before starting the effect. */ + + /* Trigger */ + Uint16 button; /**< Button that triggers the effect. */ + Uint16 interval; /**< How soon it can be triggered again after button. */ + + /* Constant */ + Sint16 level; /**< Strength of the constant effect. */ + + /* Envelope */ + Uint16 attack_length; /**< Duration of the attack. */ + Uint16 attack_level; /**< Level at the start of the attack. */ + Uint16 fade_length; /**< Duration of the fade. */ + Uint16 fade_level; /**< Level at the end of the fade. */ +} SDL_HapticConstant; + +/** + * \brief A structure containing a template for a Periodic effect. + * + * The struct handles the following effects: + * - ::SDL_HAPTIC_SINE + * - ::SDL_HAPTIC_SQUARE + * - ::SDL_HAPTIC_TRIANGLE + * - ::SDL_HAPTIC_SAWTOOTHUP + * - ::SDL_HAPTIC_SAWTOOTHDOWN + * + * A periodic effect consists in a wave-shaped effect that repeats itself + * over time. The type determines the shape of the wave and the parameters + * determine the dimensions of the wave. + * + * Phase is given by hundredth of a cyle meaning that giving the phase a value + * of 9000 will displace it 25% of it's period. Here are sample values: + * - 0: No phase displacement. + * - 9000: Displaced 25% of it's period. + * - 18000: Displaced 50% of it's period. + * - 27000: Displaced 75% of it's period. + * - 36000: Displaced 100% of it's period, same as 0, but 0 is preffered. + * + * Examples: + * \verbatim + SDL_HAPTIC_SINE + __ __ __ __ + / \ / \ / \ / + / \__/ \__/ \__/ + + SDL_HAPTIC_SQUARE + __ __ __ __ __ + | | | | | | | | | | + | |__| |__| |__| |__| | + + SDL_HAPTIC_TRIANGLE + /\ /\ /\ /\ /\ + / \ / \ / \ / \ / + / \/ \/ \/ \/ + + SDL_HAPTIC_SAWTOOTHUP + /| /| /| /| /| /| /| + / | / | / | / | / | / | / | + / |/ |/ |/ |/ |/ |/ | + + SDL_HAPTIC_SAWTOOTHDOWN + \ |\ |\ |\ |\ |\ |\ | + \ | \ | \ | \ | \ | \ | \ | + \| \| \| \| \| \| \| + \endverbatim + * + * \sa SDL_HAPTIC_SINE + * \sa SDL_HAPTIC_SQUARE + * \sa SDL_HAPTIC_TRIANGLE + * \sa SDL_HAPTIC_SAWTOOTHUP + * \sa SDL_HAPTIC_SAWTOOTHDOWN + * \sa SDL_HapticEffect + */ +typedef struct SDL_HapticPeriodic +{ + /* Header */ + Uint16 type; /**< ::SDL_HAPTIC_SINE, ::SDL_HAPTIC_SQUARE, + ::SDL_HAPTIC_TRIANGLE, ::SDL_HAPTIC_SAWTOOTHUP or + ::SDL_HAPTIC_SAWTOOTHDOWN */ + SDL_HapticDirection direction; /**< Direction of the effect. */ + + /* Replay */ + Uint32 length; /**< Duration of the effect. */ + Uint16 delay; /**< Delay before starting the effect. */ + + /* Trigger */ + Uint16 button; /**< Button that triggers the effect. */ + Uint16 interval; /**< How soon it can be triggered again after button. */ + + /* Periodic */ + Uint16 period; /**< Period of the wave. */ + Sint16 magnitude; /**< Peak value. */ + Sint16 offset; /**< Mean value of the wave. */ + Uint16 phase; /**< Horizontal shift given by hundredth of a cycle. */ + + /* Envelope */ + Uint16 attack_length; /**< Duration of the attack. */ + Uint16 attack_level; /**< Level at the start of the attack. */ + Uint16 fade_length; /**< Duration of the fade. */ + Uint16 fade_level; /**< Level at the end of the fade. */ +} SDL_HapticPeriodic; + +/** + * \brief A structure containing a template for a Condition effect. + * + * The struct handles the following effects: + * - ::SDL_HAPTIC_SPRING: Effect based on axes position. + * - ::SDL_HAPTIC_DAMPER: Effect based on axes velocity. + * - ::SDL_HAPTIC_INERTIA: Effect based on axes acceleration. + * - ::SDL_HAPTIC_FRICTION: Effect based on axes movement. + * + * Direction is handled by condition internals instead of a direction member. + * The condition effect specific members have three parameters. The first + * refers to the X axis, the second refers to the Y axis and the third + * refers to the Z axis. The right terms refer to the positive side of the + * axis and the left terms refer to the negative side of the axis. Please + * refer to the ::SDL_HapticDirection diagram for which side is positive and + * which is negative. + * + * \sa SDL_HapticDirection + * \sa SDL_HAPTIC_SPRING + * \sa SDL_HAPTIC_DAMPER + * \sa SDL_HAPTIC_INERTIA + * \sa SDL_HAPTIC_FRICTION + * \sa SDL_HapticEffect + */ +typedef struct SDL_HapticCondition +{ + /* Header */ + Uint16 type; /**< ::SDL_HAPTIC_SPRING, ::SDL_HAPTIC_DAMPER, + ::SDL_HAPTIC_INERTIA or ::SDL_HAPTIC_FRICTION */ + SDL_HapticDirection direction; /**< Direction of the effect - Not used ATM. */ + + /* Replay */ + Uint32 length; /**< Duration of the effect. */ + Uint16 delay; /**< Delay before starting the effect. */ + + /* Trigger */ + Uint16 button; /**< Button that triggers the effect. */ + Uint16 interval; /**< How soon it can be triggered again after button. */ + + /* Condition */ + Uint16 right_sat[3]; /**< Level when joystick is to the positive side. */ + Uint16 left_sat[3]; /**< Level when joystick is to the negative side. */ + Sint16 right_coeff[3]; /**< How fast to increase the force towards the positive side. */ + Sint16 left_coeff[3]; /**< How fast to increase the force towards the negative side. */ + Uint16 deadband[3]; /**< Size of the dead zone. */ + Sint16 center[3]; /**< Position of the dead zone. */ +} SDL_HapticCondition; + +/** + * \brief A structure containing a template for a Ramp effect. + * + * This struct is exclusively for the ::SDL_HAPTIC_RAMP effect. + * + * The ramp effect starts at start strength and ends at end strength. + * It augments in linear fashion. If you use attack and fade with a ramp + * they effects get added to the ramp effect making the effect become + * quadratic instead of linear. + * + * \sa SDL_HAPTIC_RAMP + * \sa SDL_HapticEffect + */ +typedef struct SDL_HapticRamp +{ + /* Header */ + Uint16 type; /**< ::SDL_HAPTIC_RAMP */ + SDL_HapticDirection direction; /**< Direction of the effect. */ + + /* Replay */ + Uint32 length; /**< Duration of the effect. */ + Uint16 delay; /**< Delay before starting the effect. */ + + /* Trigger */ + Uint16 button; /**< Button that triggers the effect. */ + Uint16 interval; /**< How soon it can be triggered again after button. */ + + /* Ramp */ + Sint16 start; /**< Beginning strength level. */ + Sint16 end; /**< Ending strength level. */ + + /* Envelope */ + Uint16 attack_length; /**< Duration of the attack. */ + Uint16 attack_level; /**< Level at the start of the attack. */ + Uint16 fade_length; /**< Duration of the fade. */ + Uint16 fade_level; /**< Level at the end of the fade. */ +} SDL_HapticRamp; + +/** + * \brief A structure containing a template for the ::SDL_HAPTIC_CUSTOM effect. + * + * A custom force feedback effect is much like a periodic effect, where the + * application can define it's exact shape. You will have to allocate the + * data yourself. Data should consist of channels * samples Uint16 samples. + * + * If channels is one, the effect is rotated using the defined direction. + * Otherwise it uses the samples in data for the different axes. + * + * \sa SDL_HAPTIC_CUSTOM + * \sa SDL_HapticEffect + */ +typedef struct SDL_HapticCustom +{ + /* Header */ + Uint16 type; /**< ::SDL_HAPTIC_CUSTOM */ + SDL_HapticDirection direction; /**< Direction of the effect. */ + + /* Replay */ + Uint32 length; /**< Duration of the effect. */ + Uint16 delay; /**< Delay before starting the effect. */ + + /* Trigger */ + Uint16 button; /**< Button that triggers the effect. */ + Uint16 interval; /**< How soon it can be triggered again after button. */ + + /* Custom */ + Uint8 channels; /**< Axes to use, minimum of one. */ + Uint16 period; /**< Sample periods. */ + Uint16 samples; /**< Amount of samples. */ + Uint16 *data; /**< Should contain channels*samples items. */ + + /* Envelope */ + Uint16 attack_length; /**< Duration of the attack. */ + Uint16 attack_level; /**< Level at the start of the attack. */ + Uint16 fade_length; /**< Duration of the fade. */ + Uint16 fade_level; /**< Level at the end of the fade. */ +} SDL_HapticCustom; + +/** + * \brief The generic template for any haptic effect. + * + * All values max at 32767 (0x7FFF). Signed values also can be negative. + * Time values unless specified otherwise are in milliseconds. + * + * You can also pass ::SDL_HAPTIC_INFINITY to length instead of a 0-32767 + * value. Neither delay, interval, attack_length nor fade_length support + * ::SDL_HAPTIC_INFINITY. Fade will also not be used since effect never ends. + * + * Additionally, the ::SDL_HAPTIC_RAMP effect does not support a duration of + * ::SDL_HAPTIC_INFINITY. + * + * Button triggers may not be supported on all devices, it is advised to not + * use them if possible. Buttons start at index 1 instead of index 0 like + * they joystick. + * + * If both attack_length and fade_level are 0, the envelope is not used, + * otherwise both values are used. + * + * Common parts: + * \code + * // Replay - All effects have this + * Uint32 length; // Duration of effect (ms). + * Uint16 delay; // Delay before starting effect. + * + * // Trigger - All effects have this + * Uint16 button; // Button that triggers effect. + * Uint16 interval; // How soon before effect can be triggered again. + * + * // Envelope - All effects except condition effects have this + * Uint16 attack_length; // Duration of the attack (ms). + * Uint16 attack_level; // Level at the start of the attack. + * Uint16 fade_length; // Duration of the fade out (ms). + * Uint16 fade_level; // Level at the end of the fade. + * \endcode + * + * + * Here we have an example of a constant effect evolution in time: + * \verbatim + Strength + ^ + | + | effect level --> _________________ + | / \ + | / \ + | / \ + | / \ + | attack_level --> | \ + | | | <--- fade_level + | + +--------------------------------------------------> Time + [--] [---] + attack_length fade_length + + [------------------][-----------------------] + delay length + \endverbatim + * + * Note either the attack_level or the fade_level may be above the actual + * effect level. + * + * \sa SDL_HapticConstant + * \sa SDL_HapticPeriodic + * \sa SDL_HapticCondition + * \sa SDL_HapticRamp + * \sa SDL_HapticCustom + */ +typedef union SDL_HapticEffect +{ + /* Common for all force feedback effects */ + Uint16 type; /**< Effect type. */ + SDL_HapticConstant constant; /**< Constant effect. */ + SDL_HapticPeriodic periodic; /**< Periodic effect. */ + SDL_HapticCondition condition; /**< Condition effect. */ + SDL_HapticRamp ramp; /**< Ramp effect. */ + SDL_HapticCustom custom; /**< Custom effect. */ +} SDL_HapticEffect; + + +/* Function prototypes */ +/** + * \brief Count the number of joysticks attached to the system. + * + * \return Number of haptic devices detected on the system. + */ +extern DECLSPEC int SDLCALL SDL_NumHaptics(void); + +/** + * \brief Get the implementation dependent name of a Haptic device. + * + * This can be called before any joysticks are opened. + * If no name can be found, this function returns NULL. + * + * \param device_index Index of the device to get it's name. + * \return Name of the device or NULL on error. + * + * \sa SDL_NumHaptics + */ +extern DECLSPEC const char *SDLCALL SDL_HapticName(int device_index); + +/** + * \brief Opens a Haptic device for usage. + * + * The index passed as an argument refers to the N'th Haptic device on this + * system. + * + * When opening a haptic device, it's gain will be set to maximum and + * autocenter will be disabled. To modify these values use + * SDL_HapticSetGain() and SDL_HapticSetAutocenter(). + * + * \param device_index Index of the device to open. + * \return Device identifier or NULL on error. + * + * \sa SDL_HapticIndex + * \sa SDL_HapticOpenFromMouse + * \sa SDL_HapticOpenFromJoystick + * \sa SDL_HapticClose + * \sa SDL_HapticSetGain + * \sa SDL_HapticSetAutocenter + * \sa SDL_HapticPause + * \sa SDL_HapticStopAll + */ +extern DECLSPEC SDL_Haptic *SDLCALL SDL_HapticOpen(int device_index); + +/** + * \brief Checks if the haptic device at index has been opened. + * + * \param device_index Index to check to see if it has been opened. + * \return 1 if it has been opened or 0 if it hasn't. + * + * \sa SDL_HapticOpen + * \sa SDL_HapticIndex + */ +extern DECLSPEC int SDLCALL SDL_HapticOpened(int device_index); + +/** + * \brief Gets the index of a haptic device. + * + * \param haptic Haptic device to get the index of. + * \return The index of the haptic device or -1 on error. + * + * \sa SDL_HapticOpen + * \sa SDL_HapticOpened + */ +extern DECLSPEC int SDLCALL SDL_HapticIndex(SDL_Haptic * haptic); + +/** + * \brief Gets whether or not the current mouse has haptic capabilities. + * + * \return SDL_TRUE if the mouse is haptic, SDL_FALSE if it isn't. + * + * \sa SDL_HapticOpenFromMouse + */ +extern DECLSPEC int SDLCALL SDL_MouseIsHaptic(void); + +/** + * \brief Tries to open a haptic device from the current mouse. + * + * \return The haptic device identifier or NULL on error. + * + * \sa SDL_MouseIsHaptic + * \sa SDL_HapticOpen + */ +extern DECLSPEC SDL_Haptic *SDLCALL SDL_HapticOpenFromMouse(void); + +/** + * \brief Checks to see if a joystick has haptic features. + * + * \param joystick Joystick to test for haptic capabilities. + * \return 1 if the joystick is haptic, 0 if it isn't + * or -1 if an error ocurred. + * + * \sa SDL_HapticOpenFromJoystick + */ +extern DECLSPEC int SDLCALL SDL_JoystickIsHaptic(SDL_Joystick * joystick); + +/** + * \brief Opens a Haptic device for usage from a Joystick device. + * + * You must still close the haptic device seperately. It will not be closed + * with the joystick. + * + * When opening from a joystick you should first close the haptic device before + * closing the joystick device. If not, on some implementations the haptic + * device will also get unallocated and you'll be unable to use force feedback + * on that device. + * + * \param joystick Joystick to create a haptic device from. + * \return A valid haptic device identifier on success or NULL on error. + * + * \sa SDL_HapticOpen + * \sa SDL_HapticClose + */ +extern DECLSPEC SDL_Haptic *SDLCALL SDL_HapticOpenFromJoystick(SDL_Joystick * + joystick); + +/** + * \brief Closes a Haptic device previously opened with SDL_HapticOpen(). + * + * \param haptic Haptic device to close. + */ +extern DECLSPEC void SDLCALL SDL_HapticClose(SDL_Haptic * haptic); + +/** + * \brief Returns the number of effects a haptic device can store. + * + * On some platforms this isn't fully supported, and therefore is an + * aproximation. Always check to see if your created effect was actually + * created and do not rely solely on SDL_HapticNumEffects(). + * + * \param haptic The haptic device to query effect max. + * \return The number of effects the haptic device can store or + * -1 on error. + * + * \sa SDL_HapticNumEffectsPlaying + * \sa SDL_HapticQuery + */ +extern DECLSPEC int SDLCALL SDL_HapticNumEffects(SDL_Haptic * haptic); + +/** + * \brief Returns the number of effects a haptic device can play at the same + * time. + * + * This is not supported on all platforms, but will always return a value. + * Added here for the sake of completness. + * + * \param haptic The haptic device to query maximum playing effects. + * \return The number of effects the haptic device can play at the same time + * or -1 on error. + * + * \sa SDL_HapticNumEffects + * \sa SDL_HapticQuery + */ +extern DECLSPEC int SDLCALL SDL_HapticNumEffectsPlaying(SDL_Haptic * haptic); + +/** + * \brief Gets the haptic devices supported features in bitwise matter. + * + * Example: + * \code + * if (SDL_HapticQueryEffects(haptic) & SDL_HAPTIC_CONSTANT) { + * printf("We have constant haptic effect!"); + * } + * \endcode + * + * \param haptic The haptic device to query. + * \return Haptic features in bitwise manner (OR'd). + * + * \sa SDL_HapticNumEffects + * \sa SDL_HapticEffectSupported + */ +extern DECLSPEC unsigned int SDLCALL SDL_HapticQuery(SDL_Haptic * haptic); + + +/** + * \brief Gets the number of haptic axes the device has. + * + * \sa SDL_HapticDirection + */ +extern DECLSPEC int SDLCALL SDL_HapticNumAxes(SDL_Haptic * haptic); + +/** + * \brief Checks to see if effect is supported by haptic. + * + * \param haptic Haptic device to check on. + * \param effect Effect to check to see if it is supported. + * \return SDL_TRUE if effect is supported, SDL_FALSE if it isn't or -1 on error. + * + * \sa SDL_HapticQuery + * \sa SDL_HapticNewEffect + */ +extern DECLSPEC int SDLCALL SDL_HapticEffectSupported(SDL_Haptic * haptic, + SDL_HapticEffect * + effect); + +/** + * \brief Creates a new haptic effect on the device. + * + * \param haptic Haptic device to create the effect on. + * \param effect Properties of the effect to create. + * \return The id of the effect on success or -1 on error. + * + * \sa SDL_HapticUpdateEffect + * \sa SDL_HapticRunEffect + * \sa SDL_HapticDestroyEffect + */ +extern DECLSPEC int SDLCALL SDL_HapticNewEffect(SDL_Haptic * haptic, + SDL_HapticEffect * effect); + +/** + * \brief Updates the properties of an effect. + * + * Can be used dynamically, although behaviour when dynamically changing + * direction may be strange. Specifically the effect may reupload itself + * and start playing from the start. You cannot change the type either when + * running SDL_HapticUpdateEffect(). + * + * \param haptic Haptic device that has the effect. + * \param effect Effect to update. + * \param data New effect properties to use. + * \return The id of the effect on success or -1 on error. + * + * \sa SDL_HapticNewEffect + * \sa SDL_HapticRunEffect + * \sa SDL_HapticDestroyEffect + */ +extern DECLSPEC int SDLCALL SDL_HapticUpdateEffect(SDL_Haptic * haptic, + int effect, + SDL_HapticEffect * data); + +/** + * \brief Runs the haptic effect on it's assosciated haptic device. + * + * If iterations are ::SDL_HAPTIC_INFINITY, it'll run the effect over and over + * repeating the envelope (attack and fade) every time. If you only want the + * effect to last forever, set ::SDL_HAPTIC_INFINITY in the effect's length + * parameter. + * + * \param haptic Haptic device to run the effect on. + * \param effect Identifier of the haptic effect to run. + * \param iterations Number of iterations to run the effect. Use + * ::SDL_HAPTIC_INFINITY for infinity. + * \return 0 on success or -1 on error. + * + * \sa SDL_HapticStopEffect + * \sa SDL_HapticDestroyEffect + * \sa SDL_HapticGetEffectStatus + */ +extern DECLSPEC int SDLCALL SDL_HapticRunEffect(SDL_Haptic * haptic, + int effect, + Uint32 iterations); + +/** + * \brief Stops the haptic effect on it's assosciated haptic device. + * + * \param haptic Haptic device to stop the effect on. + * \param effect Identifier of the effect to stop. + * \return 0 on success or -1 on error. + * + * \sa SDL_HapticRunEffect + * \sa SDL_HapticDestroyEffect + */ +extern DECLSPEC int SDLCALL SDL_HapticStopEffect(SDL_Haptic * haptic, + int effect); + +/** + * \brief Destroys a haptic effect on the device. + * + * This will stop the effect if it's running. Effects are automatically + * destroyed when the device is closed. + * + * \param haptic Device to destroy the effect on. + * \param effect Identifier of the effect to destroy. + * + * \sa SDL_HapticNewEffect + */ +extern DECLSPEC void SDLCALL SDL_HapticDestroyEffect(SDL_Haptic * haptic, + int effect); + +/** + * \brief Gets the status of the current effect on the haptic device. + * + * Device must support the ::SDL_HAPTIC_STATUS feature. + * + * \param haptic Haptic device to query the effect status on. + * \param effect Identifier of the effect to query it's status. + * \return 0 if it isn't playing, ::SDL_HAPTIC_PLAYING if it is playing + * or -1 on error. + * + * \sa SDL_HapticRunEffect + * \sa SDL_HapticStopEffect + */ +extern DECLSPEC int SDLCALL SDL_HapticGetEffectStatus(SDL_Haptic * haptic, + int effect); + +/** + * \brief Sets the global gain of the device. + * + * Device must support the ::SDL_HAPTIC_GAIN feature. + * + * The user may specify the maxmimum gain by setting the environment variable + * ::SDL_HAPTIC_GAIN_MAX which should be between 0 and 100. All calls to + * SDL_HapticSetGain() will scale linearly using ::SDL_HAPTIC_GAIN_MAX as the + * maximum. + * + * \param haptic Haptic device to set the gain on. + * \param gain Value to set the gain to, should be between 0 and 100. + * \return 0 on success or -1 on error. + * + * \sa SDL_HapticQuery + */ +extern DECLSPEC int SDLCALL SDL_HapticSetGain(SDL_Haptic * haptic, int gain); + +/** + * \brief Sets the global autocenter of the device. + * + * Autocenter should be between 0 and 100. Setting it to 0 will disable + * autocentering. + * + * Device must support the ::SDL_HAPTIC_AUTOCENTER feature. + * + * \param haptic Haptic device to set autocentering on. + * \param autocenter Value to set autocenter to, 0 disables autocentering. + * \return 0 on success or -1 on error. + * + * \sa SDL_HapticQuery + */ +extern DECLSPEC int SDLCALL SDL_HapticSetAutocenter(SDL_Haptic * haptic, + int autocenter); + +/** + * \brief Pauses a haptic device. + * + * Device must support the ::SDL_HAPTIC_PAUSE feature. Call + * SDL_HapticUnpause() to resume playback. + * + * Do not modify the effects nor add new ones while the device is paused. + * That can cause all sorts of weird errors. + * + * \param haptic Haptic device to pause. + * \return 0 on success or -1 on error. + * + * \sa SDL_HapticUnpause + */ +extern DECLSPEC int SDLCALL SDL_HapticPause(SDL_Haptic * haptic); + +/** + * \brief Unpauses a haptic device. + * + * Call to unpause after SDL_HapticPause(). + * + * \param haptic Haptic device to pause. + * \return 0 on success or -1 on error. + * + * \sa SDL_HapticPause + */ +extern DECLSPEC int SDLCALL SDL_HapticUnpause(SDL_Haptic * haptic); + +/** + * \brief Stops all the currently playing effects on a haptic device. + * + * \param haptic Haptic device to stop. + * \return 0 on success or -1 on error. + */ +extern DECLSPEC int SDLCALL SDL_HapticStopAll(SDL_Haptic * haptic); + +/** + * \brief Checks to see if rumble is supported on a haptic device.. + * + * \param haptic Haptic device to check to see if it supports rumble. + * \return SDL_TRUE if effect is supported, SDL_FALSE if it isn't or -1 on error. + * + * \sa SDL_HapticRumbleInit + * \sa SDL_HapticRumblePlay + * \sa SDL_HapticRumbleStop + */ +extern DECLSPEC int SDLCALL SDL_HapticRumbleSupported(SDL_Haptic * haptic); + +/** + * \brief Initializes the haptic device for simple rumble playback. + * + * \param haptic Haptic device to initialize for simple rumble playback. + * \return 0 on success or -1 on error. + * + * \sa SDL_HapticOpen + * \sa SDL_HapticRumbleSupported + * \sa SDL_HapticRumblePlay + * \sa SDL_HapticRumbleStop + */ +extern DECLSPEC int SDLCALL SDL_HapticRumbleInit(SDL_Haptic * haptic); + +/** + * \brief Runs simple rumble on a haptic device + * + * \param haptic Haptic device to play rumble effect on. + * \param strength Strength of the rumble to play as a 0-1 float value. + * \param length Length of the rumble to play in miliseconds. + * \return 0 on success or -1 on error. + * + * \sa SDL_HapticRumbleSupported + * \sa SDL_HapticRumbleInit + * \sa SDL_HapticRumbleStop + */ +extern DECLSPEC int SDLCALL SDL_HapticRumblePlay(SDL_Haptic * haptic, float strength, Uint32 length ); + +/** + * \brief Stops the simple rumble on a haptic device. + * + * \param haptic Haptic to stop the rumble on. + * \return 0 on success or -1 on error. + * + * \sa SDL_HapticRumbleSupported + * \sa SDL_HapticRumbleInit + * \sa SDL_HapticRumblePlay + */ +extern DECLSPEC int SDLCALL SDL_HapticRumbleStop(SDL_Haptic * haptic); + + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_haptic_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_hints.h b/external/SDL2/SDL_hints.h new file mode 100644 index 0000000..315a331 --- /dev/null +++ b/external/SDL2/SDL_hints.h @@ -0,0 +1,290 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_hints.h + * + * Official documentation for SDL configuration variables + * + * This file contains functions to set and get configuration hints, + * as well as listing each of them alphabetically. + * + * The convention for naming hints is SDL_HINT_X, where "SDL_X" is + * the environment variable that can be used to override the default. + * + * In general these hints are just that - they may or may not be + * supported or applicable on any given platform, but they provide + * a way for an application or user to give the library a hint as + * to how they would like the library to work. + */ + +#ifndef _SDL_hints_h +#define _SDL_hints_h + +#include "SDL_stdinc.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \brief A variable controlling how 3D acceleration is used to accelerate the SDL 1.2 screen surface. + * + * SDL can try to accelerate the SDL 1.2 screen surface by using streaming + * textures with a 3D rendering engine. This variable controls whether and + * how this is done. + * + * This variable can be set to the following values: + * "0" - Disable 3D acceleration + * "1" - Enable 3D acceleration, using the default renderer. + * "X" - Enable 3D acceleration, using X where X is one of the valid rendering drivers. (e.g. "direct3d", "opengl", etc.) + * + * By default SDL tries to make a best guess for each platform whether + * to use acceleration or not. + */ +#define SDL_HINT_FRAMEBUFFER_ACCELERATION "SDL_FRAMEBUFFER_ACCELERATION" + +/** + * \brief A variable specifying which render driver to use. + * + * If the application doesn't pick a specific renderer to use, this variable + * specifies the name of the preferred renderer. If the preferred renderer + * can't be initialized, the normal default renderer is used. + * + * This variable is case insensitive and can be set to the following values: + * "direct3d" + * "opengl" + * "opengles2" + * "opengles" + * "software" + * + * The default varies by platform, but it's the first one in the list that + * is available on the current platform. + */ +#define SDL_HINT_RENDER_DRIVER "SDL_RENDER_DRIVER" + +/** + * \brief A variable controlling whether the OpenGL render driver uses shaders if they are available. + * + * This variable can be set to the following values: + * "0" - Disable shaders + * "1" - Enable shaders + * + * By default shaders are used if OpenGL supports them. + */ +#define SDL_HINT_RENDER_OPENGL_SHADERS "SDL_RENDER_OPENGL_SHADERS" + +/** + * \brief A variable controlling the scaling quality + * + * This variable can be set to the following values: + * "0" or "nearest" - Nearest pixel sampling + * "1" or "linear" - Linear filtering (supported by OpenGL and Direct3D) + * "2" or "best" - Anisotropic filtering (supported by Direct3D) + * + * By default nearest pixel sampling is used + */ +#define SDL_HINT_RENDER_SCALE_QUALITY "SDL_RENDER_SCALE_QUALITY" + +/** + * \brief A variable controlling whether updates to the SDL 1.2 screen surface should be synchronized with the vertical refresh, to avoid tearing. + * + * This variable can be set to the following values: + * "0" - Disable vsync + * "1" - Enable vsync + * + * By default SDL does not sync screen surface updates with vertical refresh. + */ +#define SDL_HINT_RENDER_VSYNC "SDL_RENDER_VSYNC" + +/** + * \brief A variable controlling whether the X11 VidMode extension should be used. + * + * This variable can be set to the following values: + * "0" - Disable XVidMode + * "1" - Enable XVidMode + * + * By default SDL will use XVidMode if it is available. + */ +#define SDL_HINT_VIDEO_X11_XVIDMODE "SDL_VIDEO_X11_XVIDMODE" + +/** + * \brief A variable controlling whether the X11 Xinerama extension should be used. + * + * This variable can be set to the following values: + * "0" - Disable Xinerama + * "1" - Enable Xinerama + * + * By default SDL will use Xinerama if it is available. + */ +#define SDL_HINT_VIDEO_X11_XINERAMA "SDL_VIDEO_X11_XINERAMA" + +/** + * \brief A variable controlling whether the X11 XRandR extension should be used. + * + * This variable can be set to the following values: + * "0" - Disable XRandR + * "1" - Enable XRandR + * + * By default SDL will not use XRandR because of window manager issues. + */ +#define SDL_HINT_VIDEO_X11_XRANDR "SDL_VIDEO_X11_XRANDR" + +/** + * \brief A variable controlling whether grabbing input grabs the keyboard + * + * This variable can be set to the following values: + * "0" - Grab will affect only the mouse + * "1" - Grab will affect mouse and keyboard + * + * By default SDL will not grab the keyboard so system shortcuts still work. + */ +#define SDL_HINT_GRAB_KEYBOARD "SDL_GRAB_KEYBOARD" + +/** + * \brief Minimize your SDL_Window if it loses key focus when in Fullscreen mode. Defaults to true. + * + */ +#define SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS "SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS" + + +/** + * \brief A variable controlling whether the idle timer is disabled on iOS. + * + * When an iOS app does not receive touches for some time, the screen is + * dimmed automatically. For games where the accelerometer is the only input + * this is problematic. This functionality can be disabled by setting this + * hint. + * + * This variable can be set to the following values: + * "0" - Enable idle timer + * "1" - Disable idle timer + */ +#define SDL_HINT_IDLE_TIMER_DISABLED "SDL_IOS_IDLE_TIMER_DISABLED" + +/** + * \brief A variable controlling which orientations are allowed on iOS. + * + * In some circumstances it is necessary to be able to explicitly control + * which UI orientations are allowed. + * + * This variable is a space delimited list of the following values: + * "LandscapeLeft", "LandscapeRight", "Portrait" "PortraitUpsideDown" + */ +#define SDL_HINT_ORIENTATIONS "SDL_IOS_ORIENTATIONS" + + +/** + * \brief A variable that lets you disable the detection and use of Xinput gamepad devices + * + * The variable can be set to the following values: + * "0" - Disable XInput timer (only uses direct input) + * "1" - Enable XInput timer (the default) + */ +#define SDL_HINT_XINPUT_ENABLED "SDL_XINPUT_ENABLED" + + +/** + * \brief A variable that lets you manually hint extra gamecontroller db entries + * + * The variable should be newline delimited rows of gamecontroller config data, see SDL_gamecontroller.h + * + * This hint must be set before calling SDL_Init(SDL_INIT_GAMECONTROLLER) + * You can update mappings after the system is initialized with SDL_GameControllerMappingForGUID() and SDL_GameControllerAddMapping() + */ +#define SDL_HINT_GAMECONTROLLERCONFIG "SDL_GAMECONTROLLERCONFIG" + + +/** + * \brief If set to 0 then never set the top most bit on a SDL Window, even if the video mode expects it. + * This is a debugging aid for developers and not expected to be used by end users. The default is "1" + * + * This variable can be set to the following values: + * "0" - don't allow topmost + * "1" - allow topmost + */ +#define SDL_HINT_ALLOW_TOPMOST "SDL_ALLOW_TOPMOST" + + + +/** + * \brief An enumeration of hint priorities + */ +typedef enum +{ + SDL_HINT_DEFAULT, + SDL_HINT_NORMAL, + SDL_HINT_OVERRIDE +} SDL_HintPriority; + + +/** + * \brief Set a hint with a specific priority + * + * The priority controls the behavior when setting a hint that already + * has a value. Hints will replace existing hints of their priority and + * lower. Environment variables are considered to have override priority. + * + * \return SDL_TRUE if the hint was set, SDL_FALSE otherwise + */ +extern DECLSPEC SDL_bool SDLCALL SDL_SetHintWithPriority(const char *name, + const char *value, + SDL_HintPriority priority); + +/** + * \brief Set a hint with normal priority + * + * \return SDL_TRUE if the hint was set, SDL_FALSE otherwise + */ +extern DECLSPEC SDL_bool SDLCALL SDL_SetHint(const char *name, + const char *value); + + +/** + * \brief Get a hint + * + * \return The string value of a hint variable. + */ +extern DECLSPEC const char * SDLCALL SDL_GetHint(const char *name); + +/** + * \brief Clear all hints + * + * This function is called during SDL_Quit() to free stored hints. + */ +extern DECLSPEC void SDLCALL SDL_ClearHints(void); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_hints_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_input.h b/external/SDL2/SDL_input.h new file mode 100644 index 0000000..38f6f40 --- /dev/null +++ b/external/SDL2/SDL_input.h @@ -0,0 +1,87 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_input.h + * + * Include file for lowlevel SDL input device handling. + * + * This talks about individual devices, and not the system cursor. If you + * just want to know when the user moves the pointer somewhere in your + * window, this is NOT the API you want. This one handles things like + * multi-touch, drawing tablets, and multiple, separate mice. + * + * The other API is in SDL_mouse.h + */ + +#ifndef _SDL_input_h +#define _SDL_input_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_video.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + + +/* Function prototypes */ + +/* !!! FIXME: real documentation + * - Redetect devices + * - This invalidates all existing device information from previous queries! + * - There is an implicit (re)detect upon SDL_Init(). + */ +extern DECLSPEC int SDLCALL SDL_RedetectInputDevices(void); + +/** + * \brief Get the number of mouse input devices available. + */ +extern DECLSPEC int SDLCALL SDL_GetNumInputDevices(void); + +/** + * \brief Gets the name of a device with the given index. + * + * \param index is the index of the device, whose name is to be returned. + * + * \return the name of the device with the specified index + */ +extern DECLSPEC const char *SDLCALL SDL_GetInputDeviceName(int index); + + +extern DECLSPEC int SDLCALL SDL_IsDeviceDisconnected(int index); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_mouse_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_joystick.h b/external/SDL2/SDL_joystick.h new file mode 100644 index 0000000..c948c05 --- /dev/null +++ b/external/SDL2/SDL_joystick.h @@ -0,0 +1,253 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_joystick.h + * + * Include file for SDL joystick event handling + * + * The term "device_index" identifies currently plugged in joystick devices between 0 and SDL_NumJoysticks, with the exact joystick + * behind a device_index changing as joysticks are plugged and unplugged. + * + * The term "instance_id" is the current instantiation of a joystick device in the system, if the joystick is removed and then re-inserted + * then it will get a new instance_id, instance_id's are monotonically increasing identifiers of a joystick plugged in. + * + * The term JoystickGUID is a stable 128-bit identifier for a joystick device that does not change over time, it identifies class of + * the device (a X360 wired controller for example). This identifier is platform dependent. + * + * + */ + +#ifndef _SDL_joystick_h +#define _SDL_joystick_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \file SDL_joystick.h + * + * In order to use these functions, SDL_Init() must have been called + * with the ::SDL_INIT_JOYSTICK flag. This causes SDL to scan the system + * for joysticks, and load appropriate drivers. + */ + +/* The joystick structure used to identify an SDL joystick */ +struct _SDL_Joystick; +typedef struct _SDL_Joystick SDL_Joystick; + +/* A structure that encodes the stable unique id for a joystick device */ +typedef struct { + Uint8 data[16]; +} SDL_JoystickGUID; + +typedef Sint32 SDL_JoystickID; + + +/* Function prototypes */ +/** + * Count the number of joysticks attached to the system right now + */ +extern DECLSPEC int SDLCALL SDL_NumJoysticks(void); + +/** + * Get the implementation dependent name of a joystick. + * This can be called before any joysticks are opened. + * If no name can be found, this function returns NULL. + */ +extern DECLSPEC const char *SDLCALL SDL_JoystickNameForIndex(int device_index); + +/** + * Open a joystick for use. + * The index passed as an argument refers tothe N'th joystick on the system. + * This index is the value which will identify this joystick in future joystick + * events. + * + * \return A joystick identifier, or NULL if an error occurred. + */ +extern DECLSPEC SDL_Joystick *SDLCALL SDL_JoystickOpen(int device_index); + +/** + * Return the name for this currently opened joystick. + * If no name can be found, this function returns NULL. + */ +extern DECLSPEC const char *SDLCALL SDL_JoystickName(SDL_Joystick * joystick); + +/** + * Return the GUID for the joystick at this index + */ +extern DECLSPEC SDL_JoystickGUID SDLCALL SDL_JoystickGetDeviceGUID(int device_index); + +/** + * Return the GUID for this opened joystick + */ +extern DECLSPEC SDL_JoystickGUID SDLCALL SDL_JoystickGetGUID(SDL_Joystick * joystick); + +/** + * Return a string representation for this guid. pszGUID must point to at least 33 bytes + * (32 for the string plus a NULL terminator). + */ +extern DECLSPEC void SDL_JoystickGetGUIDString(SDL_JoystickGUID guid, char *pszGUID, int cbGUID); + +/** + * convert a string into a joystick formatted guid + */ +extern DECLSPEC SDL_JoystickGUID SDLCALL SDL_JoystickGetGUIDFromString(const char *pchGUID); + +/** + * Returns SDL_TRUE if the joystick has been opened and currently connected, or SDL_FALSE if it has not. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_JoystickGetAttached(SDL_Joystick * joystick); + +/** + * Get the instance ID of an opened joystick or -1 if the joystick is invalid. + */ +extern DECLSPEC SDL_JoystickID SDLCALL SDL_JoystickInstanceID(SDL_Joystick * joystick); + +/** + * Get the number of general axis controls on a joystick. + */ +extern DECLSPEC int SDLCALL SDL_JoystickNumAxes(SDL_Joystick * joystick); + +/** + * Get the number of trackballs on a joystick. + * + * Joystick trackballs have only relative motion events associated + * with them and their state cannot be polled. + */ +extern DECLSPEC int SDLCALL SDL_JoystickNumBalls(SDL_Joystick * joystick); + +/** + * Get the number of POV hats on a joystick. + */ +extern DECLSPEC int SDLCALL SDL_JoystickNumHats(SDL_Joystick * joystick); + +/** + * Get the number of buttons on a joystick. + */ +extern DECLSPEC int SDLCALL SDL_JoystickNumButtons(SDL_Joystick * joystick); + +/** + * Update the current state of the open joysticks. + * + * This is called automatically by the event loop if any joystick + * events are enabled. + */ +extern DECLSPEC void SDLCALL SDL_JoystickUpdate(void); + +/** + * Enable/disable joystick event polling. + * + * If joystick events are disabled, you must call SDL_JoystickUpdate() + * yourself and check the state of the joystick when you want joystick + * information. + * + * The state can be one of ::SDL_QUERY, ::SDL_ENABLE or ::SDL_IGNORE. + */ +extern DECLSPEC int SDLCALL SDL_JoystickEventState(int state); + +/** + * Get the current state of an axis control on a joystick. + * + * The state is a value ranging from -32768 to 32767. + * + * The axis indices start at index 0. + */ +extern DECLSPEC Sint16 SDLCALL SDL_JoystickGetAxis(SDL_Joystick * joystick, + int axis); + +/** + * \name Hat positions + */ +/*@{*/ +#define SDL_HAT_CENTERED 0x00 +#define SDL_HAT_UP 0x01 +#define SDL_HAT_RIGHT 0x02 +#define SDL_HAT_DOWN 0x04 +#define SDL_HAT_LEFT 0x08 +#define SDL_HAT_RIGHTUP (SDL_HAT_RIGHT|SDL_HAT_UP) +#define SDL_HAT_RIGHTDOWN (SDL_HAT_RIGHT|SDL_HAT_DOWN) +#define SDL_HAT_LEFTUP (SDL_HAT_LEFT|SDL_HAT_UP) +#define SDL_HAT_LEFTDOWN (SDL_HAT_LEFT|SDL_HAT_DOWN) +/*@}*/ + +/** + * Get the current state of a POV hat on a joystick. + * + * The hat indices start at index 0. + * + * \return The return value is one of the following positions: + * - ::SDL_HAT_CENTERED + * - ::SDL_HAT_UP + * - ::SDL_HAT_RIGHT + * - ::SDL_HAT_DOWN + * - ::SDL_HAT_LEFT + * - ::SDL_HAT_RIGHTUP + * - ::SDL_HAT_RIGHTDOWN + * - ::SDL_HAT_LEFTUP + * - ::SDL_HAT_LEFTDOWN + */ +extern DECLSPEC Uint8 SDLCALL SDL_JoystickGetHat(SDL_Joystick * joystick, + int hat); + +/** + * Get the ball axis change since the last poll. + * + * \return 0, or -1 if you passed it invalid parameters. + * + * The ball indices start at index 0. + */ +extern DECLSPEC int SDLCALL SDL_JoystickGetBall(SDL_Joystick * joystick, + int ball, int *dx, int *dy); + +/** + * Get the current state of a button on a joystick. + * + * The button indices start at index 0. + */ +extern DECLSPEC Uint8 SDLCALL SDL_JoystickGetButton(SDL_Joystick * joystick, + int button); + +/** + * Close a joystick previously opened with SDL_JoystickOpen(). + */ +extern DECLSPEC void SDLCALL SDL_JoystickClose(SDL_Joystick * joystick); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_joystick_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_keyboard.h b/external/SDL2/SDL_keyboard.h new file mode 100644 index 0000000..e6aa484 --- /dev/null +++ b/external/SDL2/SDL_keyboard.h @@ -0,0 +1,219 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_keyboard.h + * + * Include file for SDL keyboard event handling + */ + +#ifndef _SDL_keyboard_h +#define _SDL_keyboard_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_keycode.h" +#include "SDL_video.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \brief The SDL keysym structure, used in key events. + */ +typedef struct SDL_Keysym +{ + SDL_Scancode scancode; /**< SDL physical key code - see ::SDL_Scancode for details */ + SDL_Keycode sym; /**< SDL virtual key code - see ::SDL_Keycode for details */ + Uint16 mod; /**< current key modifiers */ + Uint32 unicode; /**< \deprecated use SDL_TextInputEvent instead */ +} SDL_Keysym; + +/* Function prototypes */ + +/** + * \brief Get the window which currently has keyboard focus. + */ +extern DECLSPEC SDL_Window * SDLCALL SDL_GetKeyboardFocus(void); + +/** + * \brief Get a snapshot of the current state of the keyboard. + * + * \param numkeys if non-NULL, receives the length of the returned array. + * + * \return An array of key states. Indexes into this array are obtained by using ::SDL_Scancode values. + * + * \b Example: + * \code + * Uint8 *state = SDL_GetKeyboardState(NULL); + * if ( state[SDL_SCANCODE_RETURN] ) { + * printf(" is pressed.\n"); + * } + * \endcode + */ +extern DECLSPEC Uint8 *SDLCALL SDL_GetKeyboardState(int *numkeys); + +/** + * \brief Get the current key modifier state for the keyboard. + */ +extern DECLSPEC SDL_Keymod SDLCALL SDL_GetModState(void); + +/** + * \brief Set the current key modifier state for the keyboard. + * + * \note This does not change the keyboard state, only the key modifier flags. + */ +extern DECLSPEC void SDLCALL SDL_SetModState(SDL_Keymod modstate); + +/** + * \brief Get the key code corresponding to the given scancode according + * to the current keyboard layout. + * + * See ::SDL_Keycode for details. + * + * \sa SDL_GetKeyName() + */ +extern DECLSPEC SDL_Keycode SDLCALL SDL_GetKeyFromScancode(SDL_Scancode scancode); + +/** + * \brief Get the scancode corresponding to the given key code according to the + * current keyboard layout. + * + * See ::SDL_Scancode for details. + * + * \sa SDL_GetScancodeName() + */ +extern DECLSPEC SDL_Scancode SDLCALL SDL_GetScancodeFromKey(SDL_Keycode key); + +/** + * \brief Get a human-readable name for a scancode. + * + * \return A pointer to the name for the scancode. + * If the scancode doesn't have a name, this function returns + * an empty string (""). + * + * \sa SDL_Scancode + */ +extern DECLSPEC const char *SDLCALL SDL_GetScancodeName(SDL_Scancode scancode); + +/** + * \brief Get a scancode from a human-readable name + * + * \return scancode, or SDL_SCANCODE_UNKNOWN if the name wasn't recognized + * + * \sa SDL_Scancode + */ +extern DECLSPEC SDL_Scancode SDLCALL SDL_GetScancodeFromName(const char *name); + +/** + * \brief Get a human-readable name for a key. + * + * \return A pointer to a UTF-8 string that stays valid at least until the next + * call to this function. If you need it around any longer, you must + * copy it. If the key doesn't have a name, this function returns an + * empty string (""). + * + * \sa SDL_Key + */ +extern DECLSPEC const char *SDLCALL SDL_GetKeyName(SDL_Keycode key); + +/** + * \brief Get a key code from a human-readable name + * + * \return key code, or SDLK_UNKNOWN if the name wasn't recognized + * + * \sa SDL_Keycode + */ +extern DECLSPEC SDL_Keycode SDLCALL SDL_GetKeyFromName(const char *name); + +/** + * \brief Start accepting Unicode text input events. + * This function will show the on-screen keyboard if supported. + * + * \sa SDL_StopTextInput() + * \sa SDL_SetTextInputRect() + * \sa SDL_HasScreenKeyboardSupport() + */ +extern DECLSPEC void SDLCALL SDL_StartTextInput(void); + +/** + * \brief Return whether or not Unicode text input events are enabled. + * + * \sa SDL_StartTextInput() + * \sa SDL_StopTextInput() + */ +extern DECLSPEC SDL_bool SDLCALL SDL_IsTextInputActive(void); + +/** + * \brief Stop receiving any text input events. + * This function will hide the on-screen keyboard if supported. + * + * \sa SDL_StartTextInput() + * \sa SDL_HasScreenKeyboardSupport() + */ +extern DECLSPEC void SDLCALL SDL_StopTextInput(void); + +/** + * \brief Set the rectangle used to type Unicode text inputs. + * This is used as a hint for IME and on-screen keyboard placement. + * + * \sa SDL_StartTextInput() + */ +extern DECLSPEC void SDLCALL SDL_SetTextInputRect(SDL_Rect *rect); + +/** + * \brief Returns whether the platform has some screen keyboard support. + * + * \return SDL_TRUE if some keyboard support is available else SDL_FALSE. + * + * \note Not all screen keyboard functions are supported on all platforms. + * + * \sa SDL_IsScreenKeyboardShown() + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasScreenKeyboardSupport(void); + +/** + * \brief Returns whether the screen keyboard is shown for given window. + * + * \param window The window for which screen keyboard should be queried. + * + * \return SDL_TRUE if screen keyboard is shown else SDL_FALSE. + * + * \sa SDL_HasScreenKeyboardSupport() + */ +extern DECLSPEC SDL_bool SDLCALL SDL_IsScreenKeyboardShown(SDL_Window *window); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_keyboard_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_keycode.h b/external/SDL2/SDL_keycode.h new file mode 100644 index 0000000..70742b1 --- /dev/null +++ b/external/SDL2/SDL_keycode.h @@ -0,0 +1,341 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_keycode.h + * + * Defines constants which identify keyboard keys and modifiers. + */ + +#ifndef _SDL_keycode_h +#define _SDL_keycode_h + +#include "SDL_stdinc.h" +#include "SDL_scancode.h" + +/** + * \brief The SDL virtual key representation. + * + * Values of this type are used to represent keyboard keys using the current + * layout of the keyboard. These values include Unicode values representing + * the unmodified character that would be generated by pressing the key, or + * an SDLK_* constant for those keys that do not generate characters. + */ +typedef Sint32 SDL_Keycode; + +#define SDLK_SCANCODE_MASK (1<<30) +#define SDL_SCANCODE_TO_KEYCODE(X) (X | SDLK_SCANCODE_MASK) + +enum +{ + SDLK_UNKNOWN = 0, + + SDLK_RETURN = '\r', + SDLK_ESCAPE = '\033', + SDLK_BACKSPACE = '\b', + SDLK_TAB = '\t', + SDLK_SPACE = ' ', + SDLK_EXCLAIM = '!', + SDLK_QUOTEDBL = '"', + SDLK_HASH = '#', + SDLK_PERCENT = '%', + SDLK_DOLLAR = '$', + SDLK_AMPERSAND = '&', + SDLK_QUOTE = '\'', + SDLK_LEFTPAREN = '(', + SDLK_RIGHTPAREN = ')', + SDLK_ASTERISK = '*', + SDLK_PLUS = '+', + SDLK_COMMA = ',', + SDLK_MINUS = '-', + SDLK_PERIOD = '.', + SDLK_SLASH = '/', + SDLK_0 = '0', + SDLK_1 = '1', + SDLK_2 = '2', + SDLK_3 = '3', + SDLK_4 = '4', + SDLK_5 = '5', + SDLK_6 = '6', + SDLK_7 = '7', + SDLK_8 = '8', + SDLK_9 = '9', + SDLK_COLON = ':', + SDLK_SEMICOLON = ';', + SDLK_LESS = '<', + SDLK_EQUALS = '=', + SDLK_GREATER = '>', + SDLK_QUESTION = '?', + SDLK_AT = '@', + /* + Skip uppercase letters + */ + SDLK_LEFTBRACKET = '[', + SDLK_BACKSLASH = '\\', + SDLK_RIGHTBRACKET = ']', + SDLK_CARET = '^', + SDLK_UNDERSCORE = '_', + SDLK_BACKQUOTE = '`', + SDLK_a = 'a', + SDLK_b = 'b', + SDLK_c = 'c', + SDLK_d = 'd', + SDLK_e = 'e', + SDLK_f = 'f', + SDLK_g = 'g', + SDLK_h = 'h', + SDLK_i = 'i', + SDLK_j = 'j', + SDLK_k = 'k', + SDLK_l = 'l', + SDLK_m = 'm', + SDLK_n = 'n', + SDLK_o = 'o', + SDLK_p = 'p', + SDLK_q = 'q', + SDLK_r = 'r', + SDLK_s = 's', + SDLK_t = 't', + SDLK_u = 'u', + SDLK_v = 'v', + SDLK_w = 'w', + SDLK_x = 'x', + SDLK_y = 'y', + SDLK_z = 'z', + + SDLK_CAPSLOCK = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CAPSLOCK), + + SDLK_F1 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F1), + SDLK_F2 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F2), + SDLK_F3 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F3), + SDLK_F4 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F4), + SDLK_F5 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F5), + SDLK_F6 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F6), + SDLK_F7 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F7), + SDLK_F8 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F8), + SDLK_F9 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F9), + SDLK_F10 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F10), + SDLK_F11 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F11), + SDLK_F12 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F12), + + SDLK_PRINTSCREEN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_PRINTSCREEN), + SDLK_SCROLLLOCK = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_SCROLLLOCK), + SDLK_PAUSE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_PAUSE), + SDLK_INSERT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_INSERT), + SDLK_HOME = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_HOME), + SDLK_PAGEUP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_PAGEUP), + SDLK_DELETE = '\177', + SDLK_END = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_END), + SDLK_PAGEDOWN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_PAGEDOWN), + SDLK_RIGHT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_RIGHT), + SDLK_LEFT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_LEFT), + SDLK_DOWN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_DOWN), + SDLK_UP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_UP), + + SDLK_NUMLOCKCLEAR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_NUMLOCKCLEAR), + SDLK_KP_DIVIDE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_DIVIDE), + SDLK_KP_MULTIPLY = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MULTIPLY), + SDLK_KP_MINUS = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MINUS), + SDLK_KP_PLUS = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_PLUS), + SDLK_KP_ENTER = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_ENTER), + SDLK_KP_1 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_1), + SDLK_KP_2 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_2), + SDLK_KP_3 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_3), + SDLK_KP_4 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_4), + SDLK_KP_5 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_5), + SDLK_KP_6 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_6), + SDLK_KP_7 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_7), + SDLK_KP_8 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_8), + SDLK_KP_9 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_9), + SDLK_KP_0 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_0), + SDLK_KP_PERIOD = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_PERIOD), + + SDLK_APPLICATION = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_APPLICATION), + SDLK_POWER = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_POWER), + SDLK_KP_EQUALS = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_EQUALS), + SDLK_F13 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F13), + SDLK_F14 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F14), + SDLK_F15 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F15), + SDLK_F16 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F16), + SDLK_F17 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F17), + SDLK_F18 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F18), + SDLK_F19 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F19), + SDLK_F20 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F20), + SDLK_F21 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F21), + SDLK_F22 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F22), + SDLK_F23 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F23), + SDLK_F24 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F24), + SDLK_EXECUTE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_EXECUTE), + SDLK_HELP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_HELP), + SDLK_MENU = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_MENU), + SDLK_SELECT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_SELECT), + SDLK_STOP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_STOP), + SDLK_AGAIN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AGAIN), + SDLK_UNDO = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_UNDO), + SDLK_CUT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CUT), + SDLK_COPY = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_COPY), + SDLK_PASTE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_PASTE), + SDLK_FIND = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_FIND), + SDLK_MUTE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_MUTE), + SDLK_VOLUMEUP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_VOLUMEUP), + SDLK_VOLUMEDOWN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_VOLUMEDOWN), + SDLK_KP_COMMA = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_COMMA), + SDLK_KP_EQUALSAS400 = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_EQUALSAS400), + + SDLK_ALTERASE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_ALTERASE), + SDLK_SYSREQ = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_SYSREQ), + SDLK_CANCEL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CANCEL), + SDLK_CLEAR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CLEAR), + SDLK_PRIOR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_PRIOR), + SDLK_RETURN2 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_RETURN2), + SDLK_SEPARATOR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_SEPARATOR), + SDLK_OUT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_OUT), + SDLK_OPER = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_OPER), + SDLK_CLEARAGAIN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CLEARAGAIN), + SDLK_CRSEL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CRSEL), + SDLK_EXSEL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_EXSEL), + + SDLK_KP_00 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_00), + SDLK_KP_000 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_000), + SDLK_THOUSANDSSEPARATOR = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_THOUSANDSSEPARATOR), + SDLK_DECIMALSEPARATOR = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_DECIMALSEPARATOR), + SDLK_CURRENCYUNIT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CURRENCYUNIT), + SDLK_CURRENCYSUBUNIT = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CURRENCYSUBUNIT), + SDLK_KP_LEFTPAREN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_LEFTPAREN), + SDLK_KP_RIGHTPAREN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_RIGHTPAREN), + SDLK_KP_LEFTBRACE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_LEFTBRACE), + SDLK_KP_RIGHTBRACE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_RIGHTBRACE), + SDLK_KP_TAB = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_TAB), + SDLK_KP_BACKSPACE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_BACKSPACE), + SDLK_KP_A = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_A), + SDLK_KP_B = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_B), + SDLK_KP_C = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_C), + SDLK_KP_D = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_D), + SDLK_KP_E = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_E), + SDLK_KP_F = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_F), + SDLK_KP_XOR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_XOR), + SDLK_KP_POWER = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_POWER), + SDLK_KP_PERCENT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_PERCENT), + SDLK_KP_LESS = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_LESS), + SDLK_KP_GREATER = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_GREATER), + SDLK_KP_AMPERSAND = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_AMPERSAND), + SDLK_KP_DBLAMPERSAND = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_DBLAMPERSAND), + SDLK_KP_VERTICALBAR = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_VERTICALBAR), + SDLK_KP_DBLVERTICALBAR = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_DBLVERTICALBAR), + SDLK_KP_COLON = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_COLON), + SDLK_KP_HASH = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_HASH), + SDLK_KP_SPACE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_SPACE), + SDLK_KP_AT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_AT), + SDLK_KP_EXCLAM = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_EXCLAM), + SDLK_KP_MEMSTORE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MEMSTORE), + SDLK_KP_MEMRECALL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MEMRECALL), + SDLK_KP_MEMCLEAR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MEMCLEAR), + SDLK_KP_MEMADD = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MEMADD), + SDLK_KP_MEMSUBTRACT = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MEMSUBTRACT), + SDLK_KP_MEMMULTIPLY = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MEMMULTIPLY), + SDLK_KP_MEMDIVIDE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MEMDIVIDE), + SDLK_KP_PLUSMINUS = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_PLUSMINUS), + SDLK_KP_CLEAR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_CLEAR), + SDLK_KP_CLEARENTRY = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_CLEARENTRY), + SDLK_KP_BINARY = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_BINARY), + SDLK_KP_OCTAL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_OCTAL), + SDLK_KP_DECIMAL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_DECIMAL), + SDLK_KP_HEXADECIMAL = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_HEXADECIMAL), + + SDLK_LCTRL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_LCTRL), + SDLK_LSHIFT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_LSHIFT), + SDLK_LALT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_LALT), + SDLK_LGUI = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_LGUI), + SDLK_RCTRL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_RCTRL), + SDLK_RSHIFT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_RSHIFT), + SDLK_RALT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_RALT), + SDLK_RGUI = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_RGUI), + + SDLK_MODE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_MODE), + + SDLK_AUDIONEXT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AUDIONEXT), + SDLK_AUDIOPREV = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AUDIOPREV), + SDLK_AUDIOSTOP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AUDIOSTOP), + SDLK_AUDIOPLAY = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AUDIOPLAY), + SDLK_AUDIOMUTE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AUDIOMUTE), + SDLK_MEDIASELECT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_MEDIASELECT), + SDLK_WWW = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_WWW), + SDLK_MAIL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_MAIL), + SDLK_CALCULATOR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CALCULATOR), + SDLK_COMPUTER = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_COMPUTER), + SDLK_AC_SEARCH = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AC_SEARCH), + SDLK_AC_HOME = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AC_HOME), + SDLK_AC_BACK = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AC_BACK), + SDLK_AC_FORWARD = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AC_FORWARD), + SDLK_AC_STOP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AC_STOP), + SDLK_AC_REFRESH = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AC_REFRESH), + SDLK_AC_BOOKMARKS = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AC_BOOKMARKS), + + SDLK_BRIGHTNESSDOWN = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_BRIGHTNESSDOWN), + SDLK_BRIGHTNESSUP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_BRIGHTNESSUP), + SDLK_DISPLAYSWITCH = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_DISPLAYSWITCH), + SDLK_KBDILLUMTOGGLE = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KBDILLUMTOGGLE), + SDLK_KBDILLUMDOWN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KBDILLUMDOWN), + SDLK_KBDILLUMUP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KBDILLUMUP), + SDLK_EJECT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_EJECT), + SDLK_SLEEP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_SLEEP) +}; + +/** + * \brief Enumeration of valid key mods (possibly OR'd together). + */ +typedef enum +{ + KMOD_NONE = 0x0000, + KMOD_LSHIFT = 0x0001, + KMOD_RSHIFT = 0x0002, + KMOD_LCTRL = 0x0040, + KMOD_RCTRL = 0x0080, + KMOD_LALT = 0x0100, + KMOD_RALT = 0x0200, + KMOD_LGUI = 0x0400, + KMOD_RGUI = 0x0800, + KMOD_NUM = 0x1000, + KMOD_CAPS = 0x2000, + KMOD_MODE = 0x4000, + KMOD_RESERVED = 0x8000 +} SDL_Keymod; + +#define KMOD_CTRL (KMOD_LCTRL|KMOD_RCTRL) +#define KMOD_SHIFT (KMOD_LSHIFT|KMOD_RSHIFT) +#define KMOD_ALT (KMOD_LALT|KMOD_RALT) +#define KMOD_GUI (KMOD_LGUI|KMOD_RGUI) + +#endif /* _SDL_keycode_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_loadso.h b/external/SDL2/SDL_loadso.h new file mode 100644 index 0000000..edd32d5 --- /dev/null +++ b/external/SDL2/SDL_loadso.h @@ -0,0 +1,85 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_loadso.h + * + * System dependent library loading routines + * + * Some things to keep in mind: + * \li These functions only work on C function names. Other languages may + * have name mangling and intrinsic language support that varies from + * compiler to compiler. + * \li Make sure you declare your function pointers with the same calling + * convention as the actual library function. Your code will crash + * mysteriously if you do not do this. + * \li Avoid namespace collisions. If you load a symbol from the library, + * it is not defined whether or not it goes into the global symbol + * namespace for the application. If it does and it conflicts with + * symbols in your code or other shared libraries, you will not get + * the results you expect. :) + */ + +#ifndef _SDL_loadso_h +#define _SDL_loadso_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * This function dynamically loads a shared object and returns a pointer + * to the object handle (or NULL if there was an error). + * The 'sofile' parameter is a system dependent name of the object file. + */ +extern DECLSPEC void *SDLCALL SDL_LoadObject(const char *sofile); + +/** + * Given an object handle, this function looks up the address of the + * named function in the shared object and returns it. This address + * is no longer valid after calling SDL_UnloadObject(). + */ +extern DECLSPEC void *SDLCALL SDL_LoadFunction(void *handle, + const char *name); + +/** + * Unload a shared object from memory. + */ +extern DECLSPEC void SDLCALL SDL_UnloadObject(void *handle); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_loadso_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_log.h b/external/SDL2/SDL_log.h new file mode 100644 index 0000000..ba56e40 --- /dev/null +++ b/external/SDL2/SDL_log.h @@ -0,0 +1,215 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_log.h + * + * Simple log messages with categories and priorities. + * + * By default logs are quiet, but if you're debugging SDL you might want: + * + * SDL_LogSetAllPriority(SDL_LOG_PRIORITY_WARN); + * + * Here's where the messages go on different platforms: + * Windows: debug output stream + * Android: log output + * Others: standard error output (stderr) + */ + +#ifndef _SDL_log_h +#define _SDL_log_h + +#include "SDL_stdinc.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + + +/** + * \brief The maximum size of a log message + * + * Messages longer than the maximum size will be truncated + */ +#define SDL_MAX_LOG_MESSAGE 4096 + +/** + * \brief The predefined log categories + * + * By default the application category is enabled at the INFO level, + * the assert category is enabled at the WARN level, test is enabled + * at the VERBOSE level and all other categories are enabled at the + * CRITICAL level. + */ +enum +{ + SDL_LOG_CATEGORY_APPLICATION, + SDL_LOG_CATEGORY_ERROR, + SDL_LOG_CATEGORY_ASSERT, + SDL_LOG_CATEGORY_SYSTEM, + SDL_LOG_CATEGORY_AUDIO, + SDL_LOG_CATEGORY_VIDEO, + SDL_LOG_CATEGORY_RENDER, + SDL_LOG_CATEGORY_INPUT, + SDL_LOG_CATEGORY_TEST, + + /* Reserved for future SDL library use */ + SDL_LOG_CATEGORY_RESERVED1, + SDL_LOG_CATEGORY_RESERVED2, + SDL_LOG_CATEGORY_RESERVED3, + SDL_LOG_CATEGORY_RESERVED4, + SDL_LOG_CATEGORY_RESERVED5, + SDL_LOG_CATEGORY_RESERVED6, + SDL_LOG_CATEGORY_RESERVED7, + SDL_LOG_CATEGORY_RESERVED8, + SDL_LOG_CATEGORY_RESERVED9, + SDL_LOG_CATEGORY_RESERVED10, + + /* Beyond this point is reserved for application use, e.g. + enum { + MYAPP_CATEGORY_AWESOME1 = SDL_LOG_CATEGORY_CUSTOM, + MYAPP_CATEGORY_AWESOME2, + MYAPP_CATEGORY_AWESOME3, + ... + }; + */ + SDL_LOG_CATEGORY_CUSTOM +}; + +/** + * \brief The predefined log priorities + */ +typedef enum +{ + SDL_LOG_PRIORITY_VERBOSE = 1, + SDL_LOG_PRIORITY_DEBUG, + SDL_LOG_PRIORITY_INFO, + SDL_LOG_PRIORITY_WARN, + SDL_LOG_PRIORITY_ERROR, + SDL_LOG_PRIORITY_CRITICAL, + SDL_NUM_LOG_PRIORITIES +} SDL_LogPriority; + + +/** + * \brief Set the priority of all log categories + */ +extern DECLSPEC void SDLCALL SDL_LogSetAllPriority(SDL_LogPriority priority); + +/** + * \brief Set the priority of a particular log category + */ +extern DECLSPEC void SDLCALL SDL_LogSetPriority(int category, + SDL_LogPriority priority); + +/** + * \brief Get the priority of a particular log category + */ +extern DECLSPEC SDL_LogPriority SDLCALL SDL_LogGetPriority(int category); + +/** + * \brief Reset all priorities to default. + * + * \note This is called in SDL_Quit(). + */ +extern DECLSPEC void SDLCALL SDL_LogResetPriorities(void); + +/** + * \brief Log a message with SDL_LOG_CATEGORY_APPLICATION and SDL_LOG_PRIORITY_INFO + */ +extern DECLSPEC void SDLCALL SDL_Log(const char *fmt, ...); + +/** + * \brief Log a message with SDL_LOG_PRIORITY_VERBOSE + */ +extern DECLSPEC void SDLCALL SDL_LogVerbose(int category, const char *fmt, ...); + +/** + * \brief Log a message with SDL_LOG_PRIORITY_DEBUG + */ +extern DECLSPEC void SDLCALL SDL_LogDebug(int category, const char *fmt, ...); + +/** + * \brief Log a message with SDL_LOG_PRIORITY_INFO + */ +extern DECLSPEC void SDLCALL SDL_LogInfo(int category, const char *fmt, ...); + +/** + * \brief Log a message with SDL_LOG_PRIORITY_WARN + */ +extern DECLSPEC void SDLCALL SDL_LogWarn(int category, const char *fmt, ...); + +/** + * \brief Log a message with SDL_LOG_PRIORITY_ERROR + */ +extern DECLSPEC void SDLCALL SDL_LogError(int category, const char *fmt, ...); + +/** + * \brief Log a message with SDL_LOG_PRIORITY_CRITICAL + */ +extern DECLSPEC void SDLCALL SDL_LogCritical(int category, const char *fmt, ...); + +/** + * \brief Log a message with the specified category and priority. + */ +extern DECLSPEC void SDLCALL SDL_LogMessage(int category, + SDL_LogPriority priority, + const char *fmt, ...); + +/** + * \brief Log a message with the specified category and priority. + */ +extern DECLSPEC void SDLCALL SDL_LogMessageV(int category, + SDL_LogPriority priority, + const char *fmt, va_list ap); + +/** + * \brief The prototype for the log output function + */ +typedef void (*SDL_LogOutputFunction)(void *userdata, int category, SDL_LogPriority priority, const char *message); + +/** + * \brief Get the current log output function. + */ +extern DECLSPEC void SDLCALL SDL_LogGetOutputFunction(SDL_LogOutputFunction *callback, void **userdata); + +/** + * \brief This function allows you to replace the default log output + * function with one of your own. + */ +extern DECLSPEC void SDLCALL SDL_LogSetOutputFunction(SDL_LogOutputFunction callback, void *userdata); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_log_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_main.h b/external/SDL2/SDL_main.h new file mode 100644 index 0000000..8a7859f --- /dev/null +++ b/external/SDL2/SDL_main.h @@ -0,0 +1,98 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef _SDL_main_h +#define _SDL_main_h + +#include "SDL_stdinc.h" + +/** + * \file SDL_main.h + * + * Redefine main() on some platforms so that it is called by SDL. + */ + +#if defined(__WIN32__) || defined(__IPHONEOS__) || defined(__ANDROID__) +#ifndef SDL_MAIN_HANDLED +#define SDL_MAIN_NEEDED +#endif +#endif + +#ifdef __cplusplus +#define C_LINKAGE "C" +#else +#define C_LINKAGE +#endif /* __cplusplus */ + +/** + * \file SDL_main.h + * + * The application's main() function must be called with C linkage, + * and should be declared like this: + * \code + * #ifdef __cplusplus + * extern "C" + * #endif + * int main(int argc, char *argv[]) + * { + * } + * \endcode + */ + +#ifdef SDL_MAIN_NEEDED +#define main SDL_main +#endif + +/** + * The prototype for the application's main() function + */ +extern C_LINKAGE int SDL_main(int argc, char *argv[]); + + +#include "begin_code.h" +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +#ifdef __WIN32__ + +/** + * This can be called to set the application class at startup + */ +extern DECLSPEC int SDLCALL SDL_RegisterApp(char *name, Uint32 style, + void *hInst); +extern DECLSPEC void SDLCALL SDL_UnregisterApp(void); + +#endif /* __WIN32__ */ + + +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_main_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_messagebox.h b/external/SDL2/SDL_messagebox.h new file mode 100644 index 0000000..dd788cf --- /dev/null +++ b/external/SDL2/SDL_messagebox.h @@ -0,0 +1,147 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef _SDL_messagebox_h +#define _SDL_messagebox_h + +#include "SDL_stdinc.h" +#include "SDL_video.h" /* For SDL_Window */ + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \brief SDL_MessageBox flags. If supported will display warning icon, etc. + */ +typedef enum +{ + SDL_MESSAGEBOX_ERROR = 0x00000010, /**< error dialog */ + SDL_MESSAGEBOX_WARNING = 0x00000020, /**< warning dialog */ + SDL_MESSAGEBOX_INFORMATION = 0x00000040 /**< informational dialog */ +} SDL_MessageBoxFlags; + +/** + * \brief Flags for SDL_MessageBoxButtonData. + */ +typedef enum +{ + SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT = 0x00000001, /**< Marks the default button when return is hit */ + SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT = 0x00000002 /**< Marks the default button when escape is hit */ +} SDL_MessageBoxButtonFlags; + +/** + * \brief Individual button data. + */ +typedef struct +{ + Uint32 flags; /**< ::SDL_MessageBoxButtonFlags */ + int buttonid; /**< User defined button id (value returned via SDL_MessageBox) */ + const char * text; /**< The UTF-8 button text */ +} SDL_MessageBoxButtonData; + +/** + * \brief RGB value used in a message box color scheme + */ +typedef struct +{ + Uint8 r, g, b; +} SDL_MessageBoxColor; + +typedef enum +{ + SDL_MESSAGEBOX_COLOR_BACKGROUND, + SDL_MESSAGEBOX_COLOR_TEXT, + SDL_MESSAGEBOX_COLOR_BUTTON_BORDER, + SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND, + SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED, + SDL_MESSAGEBOX_COLOR_MAX +} SDL_MessageBoxColorType; + +/** + * \brief A set of colors to use for message box dialogs + */ +typedef struct +{ + SDL_MessageBoxColor colors[SDL_MESSAGEBOX_COLOR_MAX]; +} SDL_MessageBoxColorScheme; + +/** + * \brief MessageBox structure containing title, text, window, etc. + */ +typedef struct +{ + Uint32 flags; /**< ::SDL_MessageBoxFlags */ + SDL_Window *window; /**< Parent window, can be NULL */ + const char *title; /**< UTF-8 title */ + const char *message; /**< UTF-8 message text */ + + int numbuttons; + const SDL_MessageBoxButtonData *buttons; + + const SDL_MessageBoxColorScheme *colorScheme; /**< ::SDL_MessageBoxColorScheme, can be NULL to use system settings */ +} SDL_MessageBoxData; + +/** + * \brief Create a modal message box. + * + * \param messagebox The SDL_MessageBox structure with title, text, etc. + * + * \return -1 on error, otherwise 0 and buttonid contains user id of button + * hit or -1 if dialog was closed. + * + * \note This function should be called on the thread that created the parent + * window, or on the main thread if the messagebox has no parent. It will + * block execution of that thread until the user clicks a button or + * closes the messagebox. + */ +extern DECLSPEC int SDLCALL SDL_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid); + +/** + * \brief Create a simple modal message box + * + * \param flags ::SDL_MessageBoxFlags + * \param title UTF-8 title text + * \param message UTF-8 message text + * \param window The parent window, or NULL for no parent + * + * \return 0 on success, -1 on error + * + * \sa SDL_ShowMessageBox + */ +extern DECLSPEC int SDLCALL SDL_ShowSimpleMessageBox(Uint32 flags, const char *title, const char *message, SDL_Window *window); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_messagebox_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_mouse.h b/external/SDL2/SDL_mouse.h new file mode 100644 index 0000000..f6d6300 --- /dev/null +++ b/external/SDL2/SDL_mouse.h @@ -0,0 +1,223 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_mouse.h + * + * Include file for SDL mouse event handling. + */ + +#ifndef _SDL_mouse_h +#define _SDL_mouse_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_video.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +typedef struct SDL_Cursor SDL_Cursor; /* Implementation dependent */ + +/** + * \brief Cursor types for SDL_CreateSystemCursor. + */ +typedef enum +{ + SDL_SYSTEM_CURSOR_ARROW, // Arrow + SDL_SYSTEM_CURSOR_IBEAM, // I-beam + SDL_SYSTEM_CURSOR_WAIT, // Wait + SDL_SYSTEM_CURSOR_CROSSHAIR, // Crosshair + SDL_SYSTEM_CURSOR_WAITARROW, // Small wait cursor (or Wait if not available) + SDL_SYSTEM_CURSOR_SIZENWSE, // Double arrow pointing northwest and southeast + SDL_SYSTEM_CURSOR_SIZENESW, // Double arrow pointing northeast and southwest + SDL_SYSTEM_CURSOR_SIZEWE, // Double arrow pointing west and east + SDL_SYSTEM_CURSOR_SIZENS, // Double arrow pointing north and south + SDL_SYSTEM_CURSOR_SIZEALL, // Four pointed arrow pointing north, south, east, and west + SDL_SYSTEM_CURSOR_NO, // Slashed circle or crossbones + SDL_SYSTEM_CURSOR_HAND, // Hand + SDL_NUM_SYSTEM_CURSORS +} SDL_SystemCursor; + +/* Function prototypes */ + +/** + * \brief Get the window which currently has mouse focus. + */ +extern DECLSPEC SDL_Window * SDLCALL SDL_GetMouseFocus(void); + +/** + * \brief Retrieve the current state of the mouse. + * + * The current button state is returned as a button bitmask, which can + * be tested using the SDL_BUTTON(X) macros, and x and y are set to the + * mouse cursor position relative to the focus window for the currently + * selected mouse. You can pass NULL for either x or y. + */ +extern DECLSPEC Uint32 SDLCALL SDL_GetMouseState(int *x, int *y); + +/** + * \brief Retrieve the relative state of the mouse. + * + * The current button state is returned as a button bitmask, which can + * be tested using the SDL_BUTTON(X) macros, and x and y are set to the + * mouse deltas since the last call to SDL_GetRelativeMouseState(). + */ +extern DECLSPEC Uint32 SDLCALL SDL_GetRelativeMouseState(int *x, int *y); + +/** + * \brief Moves the mouse to the given position within the window. + * + * \param window The window to move the mouse into, or NULL for the current mouse focus + * \param x The x coordinate within the window + * \param y The y coordinate within the window + * + * \note This function generates a mouse motion event + */ +extern DECLSPEC void SDLCALL SDL_WarpMouseInWindow(SDL_Window * window, + int x, int y); + +/** + * \brief Set relative mouse mode. + * + * \param enabled Whether or not to enable relative mode + * + * \return 0 on success, or -1 if relative mode is not supported. + * + * While the mouse is in relative mode, the cursor is hidden, and the + * driver will try to report continuous motion in the current window. + * Only relative motion events will be delivered, the mouse position + * will not change. + * + * \note This function will flush any pending mouse motion. + * + * \sa SDL_GetRelativeMouseMode() + */ +extern DECLSPEC int SDLCALL SDL_SetRelativeMouseMode(SDL_bool enabled); + +/** + * \brief Query whether relative mouse mode is enabled. + * + * \sa SDL_SetRelativeMouseMode() + */ +extern DECLSPEC SDL_bool SDLCALL SDL_GetRelativeMouseMode(void); + +/** + * \brief Create a cursor, using the specified bitmap data and + * mask (in MSB format). + * + * The cursor width must be a multiple of 8 bits. + * + * The cursor is created in black and white according to the following: + * + * + * + * + * + * + *
data mask resulting pixel on screen
0 1 White
1 1 Black
0 0 Transparent
1 0 Inverted color if possible, black + * if not.
+ * + * \sa SDL_FreeCursor() + */ +extern DECLSPEC SDL_Cursor *SDLCALL SDL_CreateCursor(const Uint8 * data, + const Uint8 * mask, + int w, int h, int hot_x, + int hot_y); + +/** + * \brief Create a color cursor. + * + * \sa SDL_FreeCursor() + */ +extern DECLSPEC SDL_Cursor *SDLCALL SDL_CreateColorCursor(SDL_Surface *surface, + int hot_x, + int hot_y); + +/** + * \brief Create a system cursor. + * + * \sa SDL_FreeCursor() + */ +extern DECLSPEC SDL_Cursor *SDLCALL SDL_CreateSystemCursor(SDL_SystemCursor id); + +/** + * \brief Set the active cursor. + */ +extern DECLSPEC void SDLCALL SDL_SetCursor(SDL_Cursor * cursor); + +/** + * \brief Return the active cursor. + */ +extern DECLSPEC SDL_Cursor *SDLCALL SDL_GetCursor(void); + +/** + * \brief Frees a cursor created with SDL_CreateCursor(). + * + * \sa SDL_CreateCursor() + */ +extern DECLSPEC void SDLCALL SDL_FreeCursor(SDL_Cursor * cursor); + +/** + * \brief Toggle whether or not the cursor is shown. + * + * \param toggle 1 to show the cursor, 0 to hide it, -1 to query the current + * state. + * + * \return 1 if the cursor is shown, or 0 if the cursor is hidden. + */ +extern DECLSPEC int SDLCALL SDL_ShowCursor(int toggle); + +/** + * Used as a mask when testing buttons in buttonstate. + * - Button 1: Left mouse button + * - Button 2: Middle mouse button + * - Button 3: Right mouse button + */ +#define SDL_BUTTON(X) (1 << ((X)-1)) +#define SDL_BUTTON_LEFT 1 +#define SDL_BUTTON_MIDDLE 2 +#define SDL_BUTTON_RIGHT 3 +#define SDL_BUTTON_X1 4 +#define SDL_BUTTON_X2 5 +#define SDL_BUTTON_LMASK SDL_BUTTON(SDL_BUTTON_LEFT) +#define SDL_BUTTON_MMASK SDL_BUTTON(SDL_BUTTON_MIDDLE) +#define SDL_BUTTON_RMASK SDL_BUTTON(SDL_BUTTON_RIGHT) +#define SDL_BUTTON_X1MASK SDL_BUTTON(SDL_BUTTON_X1) +#define SDL_BUTTON_X2MASK SDL_BUTTON(SDL_BUTTON_X2) + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_mouse_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_mutex.h b/external/SDL2/SDL_mutex.h new file mode 100644 index 0000000..30519b8 --- /dev/null +++ b/external/SDL2/SDL_mutex.h @@ -0,0 +1,255 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef _SDL_mutex_h +#define _SDL_mutex_h + +/** + * \file SDL_mutex.h + * + * Functions to provide thread synchronization primitives. + */ + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * Synchronization functions which can time out return this value + * if they time out. + */ +#define SDL_MUTEX_TIMEDOUT 1 + +/** + * This is the timeout value which corresponds to never time out. + */ +#define SDL_MUTEX_MAXWAIT (~(Uint32)0) + + +/** + * \name Mutex functions + */ +/*@{*/ + +/* The SDL mutex structure, defined in SDL_mutex.c */ +struct SDL_mutex; +typedef struct SDL_mutex SDL_mutex; + +/** + * Create a mutex, initialized unlocked. + */ +extern DECLSPEC SDL_mutex *SDLCALL SDL_CreateMutex(void); + +/** + * Lock the mutex. + * + * \return 0, or -1 on error. + */ +#define SDL_mutexP(m) SDL_LockMutex(m) +extern DECLSPEC int SDLCALL SDL_LockMutex(SDL_mutex * mutex); + +/** + * Try to lock the mutex + * + * \return 0, SDL_MUTEX_TIMEDOUT, or -1 on error + */ +extern DECLSPEC int SDLCALL SDL_TryLockMutex(SDL_mutex * mutex); + +/** + * Unlock the mutex. + * + * \return 0, or -1 on error. + * + * \warning It is an error to unlock a mutex that has not been locked by + * the current thread, and doing so results in undefined behavior. + */ +#define SDL_mutexV(m) SDL_UnlockMutex(m) +extern DECLSPEC int SDLCALL SDL_UnlockMutex(SDL_mutex * mutex); + +/** + * Destroy a mutex. + */ +extern DECLSPEC void SDLCALL SDL_DestroyMutex(SDL_mutex * mutex); + +/*@}*//*Mutex functions*/ + + +/** + * \name Semaphore functions + */ +/*@{*/ + +/* The SDL semaphore structure, defined in SDL_sem.c */ +struct SDL_semaphore; +typedef struct SDL_semaphore SDL_sem; + +/** + * Create a semaphore, initialized with value, returns NULL on failure. + */ +extern DECLSPEC SDL_sem *SDLCALL SDL_CreateSemaphore(Uint32 initial_value); + +/** + * Destroy a semaphore. + */ +extern DECLSPEC void SDLCALL SDL_DestroySemaphore(SDL_sem * sem); + +/** + * This function suspends the calling thread until the semaphore pointed + * to by \c sem has a positive count. It then atomically decreases the + * semaphore count. + */ +extern DECLSPEC int SDLCALL SDL_SemWait(SDL_sem * sem); + +/** + * Non-blocking variant of SDL_SemWait(). + * + * \return 0 if the wait succeeds, ::SDL_MUTEX_TIMEDOUT if the wait would + * block, and -1 on error. + */ +extern DECLSPEC int SDLCALL SDL_SemTryWait(SDL_sem * sem); + +/** + * Variant of SDL_SemWait() with a timeout in milliseconds. + * + * \return 0 if the wait succeeds, ::SDL_MUTEX_TIMEDOUT if the wait does not + * succeed in the allotted time, and -1 on error. + * + * \warning On some platforms this function is implemented by looping with a + * delay of 1 ms, and so should be avoided if possible. + */ +extern DECLSPEC int SDLCALL SDL_SemWaitTimeout(SDL_sem * sem, Uint32 ms); + +/** + * Atomically increases the semaphore's count (not blocking). + * + * \return 0, or -1 on error. + */ +extern DECLSPEC int SDLCALL SDL_SemPost(SDL_sem * sem); + +/** + * Returns the current count of the semaphore. + */ +extern DECLSPEC Uint32 SDLCALL SDL_SemValue(SDL_sem * sem); + +/*@}*//*Semaphore functions*/ + + +/** + * \name Condition variable functions + */ +/*@{*/ + +/* The SDL condition variable structure, defined in SDL_cond.c */ +struct SDL_cond; +typedef struct SDL_cond SDL_cond; + +/** + * Create a condition variable. + * + * Typical use of condition variables: + * + * Thread A: + * SDL_LockMutex(lock); + * while ( ! condition ) { + * SDL_CondWait(cond, lock); + * } + * SDL_UnlockMutex(lock); + * + * Thread B: + * SDL_LockMutex(lock); + * ... + * condition = true; + * ... + * SDL_CondSignal(cond); + * SDL_UnlockMutex(lock); + * + * There is some discussion whether to signal the condition variable + * with the mutex locked or not. There is some potential performance + * benefit to unlocking first on some platforms, but there are some + * potential race conditions depending on how your code is structured. + * + * In general it's safer to signal the condition variable while the + * mutex is locked. + */ +extern DECLSPEC SDL_cond *SDLCALL SDL_CreateCond(void); + +/** + * Destroy a condition variable. + */ +extern DECLSPEC void SDLCALL SDL_DestroyCond(SDL_cond * cond); + +/** + * Restart one of the threads that are waiting on the condition variable. + * + * \return 0 or -1 on error. + */ +extern DECLSPEC int SDLCALL SDL_CondSignal(SDL_cond * cond); + +/** + * Restart all threads that are waiting on the condition variable. + * + * \return 0 or -1 on error. + */ +extern DECLSPEC int SDLCALL SDL_CondBroadcast(SDL_cond * cond); + +/** + * Wait on the condition variable, unlocking the provided mutex. + * + * \warning The mutex must be locked before entering this function! + * + * The mutex is re-locked once the condition variable is signaled. + * + * \return 0 when it is signaled, or -1 on error. + */ +extern DECLSPEC int SDLCALL SDL_CondWait(SDL_cond * cond, SDL_mutex * mutex); + +/** + * Waits for at most \c ms milliseconds, and returns 0 if the condition + * variable is signaled, ::SDL_MUTEX_TIMEDOUT if the condition is not + * signaled in the allotted time, and -1 on error. + * + * \warning On some platforms this function is implemented by looping with a + * delay of 1 ms, and so should be avoided if possible. + */ +extern DECLSPEC int SDLCALL SDL_CondWaitTimeout(SDL_cond * cond, + SDL_mutex * mutex, Uint32 ms); + +/*@}*//*Condition variable functions*/ + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_mutex_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_name.h b/external/SDL2/SDL_name.h new file mode 100644 index 0000000..511619a --- /dev/null +++ b/external/SDL2/SDL_name.h @@ -0,0 +1,11 @@ + +#ifndef _SDLname_h_ +#define _SDLname_h_ + +#if defined(__STDC__) || defined(__cplusplus) +#define NeedFunctionPrototypes 1 +#endif + +#define SDL_NAME(X) SDL_##X + +#endif /* _SDLname_h_ */ diff --git a/external/SDL2/SDL_opengl.h b/external/SDL2/SDL_opengl.h new file mode 100644 index 0000000..079ed74 --- /dev/null +++ b/external/SDL2/SDL_opengl.h @@ -0,0 +1,11132 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_opengl.h + * + * This is a simple file to encapsulate the OpenGL API headers. + */ + +#ifndef _SDL_opengl_h +#define _SDL_opengl_h + +#include "SDL_config.h" + +#ifndef __IPHONEOS__ + +#ifdef __WIN32__ +#define WIN32_LEAN_AND_MEAN +#ifndef NOMINMAX +#define NOMINMAX /* Don't defined min() and max() */ +#endif +#include +#endif + +#ifdef __HAIKU__ /* !!! FIXME: temp compiler warning fix... */ +#define NO_SDL_GLEXT 1 +#endif + +#ifdef __glext_h_ +/* Someone has already included glext.h */ +#define NO_SDL_GLEXT +#endif +#ifndef NO_SDL_GLEXT +#define __glext_h_ /* Don't let gl.h include glext.h */ +#endif +#if defined(__MACOSX__) +#include /* Header File For The OpenGL Library */ +#define __X_GL_H +#else +#include /* Header File For The OpenGL Library */ +#endif +#ifndef NO_SDL_GLEXT +#undef __glext_h_ +#endif + +/** + * \file SDL_opengl.h + * + * This file is included because glext.h is not available on some systems. + * If you don't want this version included, simply define ::NO_SDL_GLEXT. + * + * The latest version is available from: + * http://www.opengl.org/registry/ + */ + +/** + * \def NO_SDL_GLEXT + * + * Define this if you have your own version of glext.h and want to disable the + * version included in SDL_opengl.h. + */ + +#if !defined(NO_SDL_GLEXT) && !defined(GL_GLEXT_LEGACY) +/* *INDENT-OFF* */ +#ifndef __glext_h_ +#define __glext_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** Copyright (c) 2007-2010 The Khronos Group Inc. +** +** Permission is hereby granted, free of charge, to any person obtaining a +** copy of this software and/or associated documentation files (the +** "Materials"), to deal in the Materials without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Materials, and to +** permit persons to whom the Materials are furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be included +** in all copies or substantial portions of the Materials. +** +** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. +*/ + +/* Header file version number, required by OpenGL ABI for Linux */ +/* glext.h last updated $Date: 2010-08-03 01:30:25 -0700 (Tue, 03 Aug 2010) $ */ +/* Current version at http://www.opengl.org/registry/ */ +#define GL_GLEXT_VERSION 64 +/* Function declaration macros - to move into glplatform.h */ + +#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) +#define WIN32_LEAN_AND_MEAN 1 +#include +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef APIENTRYP +#define APIENTRYP APIENTRY * +#endif +#ifndef GLAPI +#define GLAPI extern +#endif + +/*************************************************************/ + +#ifndef GL_VERSION_1_2 +#define GL_UNSIGNED_BYTE_3_3_2 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#define GL_TEXTURE_BINDING_3D 0x806A +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 +#define GL_MAX_ELEMENTS_VERTICES 0x80E8 +#define GL_MAX_ELEMENTS_INDICES 0x80E9 +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_TEXTURE_BASE_LEVEL 0x813C +#define GL_TEXTURE_MAX_LEVEL 0x813D +#define GL_SMOOTH_POINT_SIZE_RANGE 0x0B12 +#define GL_SMOOTH_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_SMOOTH_LINE_WIDTH_RANGE 0x0B22 +#define GL_SMOOTH_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_ALIASED_LINE_WIDTH_RANGE 0x846E +#endif + +#ifndef GL_VERSION_1_2_DEPRECATED +#define GL_RESCALE_NORMAL 0x803A +#define GL_LIGHT_MODEL_COLOR_CONTROL 0x81F8 +#define GL_SINGLE_COLOR 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR 0x81FA +#define GL_ALIASED_POINT_SIZE_RANGE 0x846D +#endif + +#ifndef GL_ARB_imaging +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_BLEND_COLOR 0x8005 +#define GL_FUNC_ADD 0x8006 +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +#define GL_BLEND_EQUATION 0x8009 +#define GL_FUNC_SUBTRACT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#endif + +#ifndef GL_ARB_imaging_DEPRECATED +#define GL_CONVOLUTION_1D 0x8010 +#define GL_CONVOLUTION_2D 0x8011 +#define GL_SEPARABLE_2D 0x8012 +#define GL_CONVOLUTION_BORDER_MODE 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS 0x8015 +#define GL_REDUCE 0x8016 +#define GL_CONVOLUTION_FORMAT 0x8017 +#define GL_CONVOLUTION_WIDTH 0x8018 +#define GL_CONVOLUTION_HEIGHT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS 0x8023 +#define GL_HISTOGRAM 0x8024 +#define GL_PROXY_HISTOGRAM 0x8025 +#define GL_HISTOGRAM_WIDTH 0x8026 +#define GL_HISTOGRAM_FORMAT 0x8027 +#define GL_HISTOGRAM_RED_SIZE 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE 0x802C +#define GL_HISTOGRAM_SINK 0x802D +#define GL_MINMAX 0x802E +#define GL_MINMAX_FORMAT 0x802F +#define GL_MINMAX_SINK 0x8030 +#define GL_TABLE_TOO_LARGE 0x8031 +#define GL_COLOR_MATRIX 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS 0x80BB +#define GL_COLOR_TABLE 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE 0x80D2 +#define GL_PROXY_COLOR_TABLE 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE 0x80D5 +#define GL_COLOR_TABLE_SCALE 0x80D6 +#define GL_COLOR_TABLE_BIAS 0x80D7 +#define GL_COLOR_TABLE_FORMAT 0x80D8 +#define GL_COLOR_TABLE_WIDTH 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE 0x80DF +#define GL_CONSTANT_BORDER 0x8151 +#define GL_REPLICATE_BORDER 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR 0x8154 +#endif + +#ifndef GL_VERSION_1_3 +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_TEXTURE11 0x84CB +#define GL_TEXTURE12 0x84CC +#define GL_TEXTURE13 0x84CD +#define GL_TEXTURE14 0x84CE +#define GL_TEXTURE15 0x84CF +#define GL_TEXTURE16 0x84D0 +#define GL_TEXTURE17 0x84D1 +#define GL_TEXTURE18 0x84D2 +#define GL_TEXTURE19 0x84D3 +#define GL_TEXTURE20 0x84D4 +#define GL_TEXTURE21 0x84D5 +#define GL_TEXTURE22 0x84D6 +#define GL_TEXTURE23 0x84D7 +#define GL_TEXTURE24 0x84D8 +#define GL_TEXTURE25 0x84D9 +#define GL_TEXTURE26 0x84DA +#define GL_TEXTURE27 0x84DB +#define GL_TEXTURE28 0x84DC +#define GL_TEXTURE29 0x84DD +#define GL_TEXTURE30 0x84DE +#define GL_TEXTURE31 0x84DF +#define GL_ACTIVE_TEXTURE 0x84E0 +#define GL_MULTISAMPLE 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE 0x809F +#define GL_SAMPLE_COVERAGE 0x80A0 +#define GL_SAMPLE_BUFFERS 0x80A8 +#define GL_SAMPLES 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT 0x80AB +#define GL_TEXTURE_CUBE_MAP 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C +#define GL_COMPRESSED_RGB 0x84ED +#define GL_COMPRESSED_RGBA 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE 0x86A0 +#define GL_TEXTURE_COMPRESSED 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3 +#define GL_CLAMP_TO_BORDER 0x812D +#endif + +#ifndef GL_VERSION_1_3_DEPRECATED +#define GL_CLIENT_ACTIVE_TEXTURE 0x84E1 +#define GL_MAX_TEXTURE_UNITS 0x84E2 +#define GL_TRANSPOSE_MODELVIEW_MATRIX 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX 0x84E6 +#define GL_MULTISAMPLE_BIT 0x20000000 +#define GL_NORMAL_MAP 0x8511 +#define GL_REFLECTION_MAP 0x8512 +#define GL_COMPRESSED_ALPHA 0x84E9 +#define GL_COMPRESSED_LUMINANCE 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA 0x84EB +#define GL_COMPRESSED_INTENSITY 0x84EC +#define GL_COMBINE 0x8570 +#define GL_COMBINE_RGB 0x8571 +#define GL_COMBINE_ALPHA 0x8572 +#define GL_SOURCE0_RGB 0x8580 +#define GL_SOURCE1_RGB 0x8581 +#define GL_SOURCE2_RGB 0x8582 +#define GL_SOURCE0_ALPHA 0x8588 +#define GL_SOURCE1_ALPHA 0x8589 +#define GL_SOURCE2_ALPHA 0x858A +#define GL_OPERAND0_RGB 0x8590 +#define GL_OPERAND1_RGB 0x8591 +#define GL_OPERAND2_RGB 0x8592 +#define GL_OPERAND0_ALPHA 0x8598 +#define GL_OPERAND1_ALPHA 0x8599 +#define GL_OPERAND2_ALPHA 0x859A +#define GL_RGB_SCALE 0x8573 +#define GL_ADD_SIGNED 0x8574 +#define GL_INTERPOLATE 0x8575 +#define GL_SUBTRACT 0x84E7 +#define GL_CONSTANT 0x8576 +#define GL_PRIMARY_COLOR 0x8577 +#define GL_PREVIOUS 0x8578 +#define GL_DOT3_RGB 0x86AE +#define GL_DOT3_RGBA 0x86AF +#endif + +#ifndef GL_VERSION_1_4 +#define GL_BLEND_DST_RGB 0x80C8 +#define GL_BLEND_SRC_RGB 0x80C9 +#define GL_BLEND_DST_ALPHA 0x80CA +#define GL_BLEND_SRC_ALPHA 0x80CB +#define GL_POINT_FADE_THRESHOLD_SIZE 0x8128 +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT32 0x81A7 +#define GL_MIRRORED_REPEAT 0x8370 +#define GL_MAX_TEXTURE_LOD_BIAS 0x84FD +#define GL_TEXTURE_LOD_BIAS 0x8501 +#define GL_INCR_WRAP 0x8507 +#define GL_DECR_WRAP 0x8508 +#define GL_TEXTURE_DEPTH_SIZE 0x884A +#define GL_TEXTURE_COMPARE_MODE 0x884C +#define GL_TEXTURE_COMPARE_FUNC 0x884D +#endif + +#ifndef GL_VERSION_1_4_DEPRECATED +#define GL_POINT_SIZE_MIN 0x8126 +#define GL_POINT_SIZE_MAX 0x8127 +#define GL_POINT_DISTANCE_ATTENUATION 0x8129 +#define GL_GENERATE_MIPMAP 0x8191 +#define GL_GENERATE_MIPMAP_HINT 0x8192 +#define GL_FOG_COORDINATE_SOURCE 0x8450 +#define GL_FOG_COORDINATE 0x8451 +#define GL_FRAGMENT_DEPTH 0x8452 +#define GL_CURRENT_FOG_COORDINATE 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER 0x8456 +#define GL_FOG_COORDINATE_ARRAY 0x8457 +#define GL_COLOR_SUM 0x8458 +#define GL_CURRENT_SECONDARY_COLOR 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER 0x845D +#define GL_SECONDARY_COLOR_ARRAY 0x845E +#define GL_TEXTURE_FILTER_CONTROL 0x8500 +#define GL_DEPTH_TEXTURE_MODE 0x884B +#define GL_COMPARE_R_TO_TEXTURE 0x884E +#endif + +#ifndef GL_VERSION_1_5 +#define GL_BUFFER_SIZE 0x8764 +#define GL_BUFFER_USAGE 0x8765 +#define GL_QUERY_COUNTER_BITS 0x8864 +#define GL_CURRENT_QUERY 0x8865 +#define GL_QUERY_RESULT 0x8866 +#define GL_QUERY_RESULT_AVAILABLE 0x8867 +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_ARRAY_BUFFER_BINDING 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F +#define GL_READ_ONLY 0x88B8 +#define GL_WRITE_ONLY 0x88B9 +#define GL_READ_WRITE 0x88BA +#define GL_BUFFER_ACCESS 0x88BB +#define GL_BUFFER_MAPPED 0x88BC +#define GL_BUFFER_MAP_POINTER 0x88BD +#define GL_STREAM_DRAW 0x88E0 +#define GL_STREAM_READ 0x88E1 +#define GL_STREAM_COPY 0x88E2 +#define GL_STATIC_DRAW 0x88E4 +#define GL_STATIC_READ 0x88E5 +#define GL_STATIC_COPY 0x88E6 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_DYNAMIC_READ 0x88E9 +#define GL_DYNAMIC_COPY 0x88EA +#define GL_SAMPLES_PASSED 0x8914 +#endif + +#ifndef GL_VERSION_1_5_DEPRECATED +#define GL_VERTEX_ARRAY_BUFFER_BINDING 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING 0x889E +#define GL_FOG_COORD_SRC 0x8450 +#define GL_FOG_COORD 0x8451 +#define GL_CURRENT_FOG_COORD 0x8453 +#define GL_FOG_COORD_ARRAY_TYPE 0x8454 +#define GL_FOG_COORD_ARRAY_STRIDE 0x8455 +#define GL_FOG_COORD_ARRAY_POINTER 0x8456 +#define GL_FOG_COORD_ARRAY 0x8457 +#define GL_FOG_COORD_ARRAY_BUFFER_BINDING 0x889D +#define GL_SRC0_RGB 0x8580 +#define GL_SRC1_RGB 0x8581 +#define GL_SRC2_RGB 0x8582 +#define GL_SRC0_ALPHA 0x8588 +#define GL_SRC1_ALPHA 0x8589 +#define GL_SRC2_ALPHA 0x858A +#endif + +#ifndef GL_VERSION_2_0 +#define GL_BLEND_EQUATION_RGB 0x8009 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB 0x8626 +#define GL_VERTEX_PROGRAM_POINT_SIZE 0x8642 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645 +#define GL_STENCIL_BACK_FUNC 0x8800 +#define GL_STENCIL_BACK_FAIL 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803 +#define GL_MAX_DRAW_BUFFERS 0x8824 +#define GL_DRAW_BUFFER0 0x8825 +#define GL_DRAW_BUFFER1 0x8826 +#define GL_DRAW_BUFFER2 0x8827 +#define GL_DRAW_BUFFER3 0x8828 +#define GL_DRAW_BUFFER4 0x8829 +#define GL_DRAW_BUFFER5 0x882A +#define GL_DRAW_BUFFER6 0x882B +#define GL_DRAW_BUFFER7 0x882C +#define GL_DRAW_BUFFER8 0x882D +#define GL_DRAW_BUFFER9 0x882E +#define GL_DRAW_BUFFER10 0x882F +#define GL_DRAW_BUFFER11 0x8830 +#define GL_DRAW_BUFFER12 0x8831 +#define GL_DRAW_BUFFER13 0x8832 +#define GL_DRAW_BUFFER14 0x8833 +#define GL_DRAW_BUFFER15 0x8834 +#define GL_BLEND_EQUATION_ALPHA 0x883D +#define GL_MAX_VERTEX_ATTRIBS 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A +#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 0x8B49 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A +#define GL_MAX_VARYING_FLOATS 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D +#define GL_SHADER_TYPE 0x8B4F +#define GL_FLOAT_VEC2 0x8B50 +#define GL_FLOAT_VEC3 0x8B51 +#define GL_FLOAT_VEC4 0x8B52 +#define GL_INT_VEC2 0x8B53 +#define GL_INT_VEC3 0x8B54 +#define GL_INT_VEC4 0x8B55 +#define GL_BOOL 0x8B56 +#define GL_BOOL_VEC2 0x8B57 +#define GL_BOOL_VEC3 0x8B58 +#define GL_BOOL_VEC4 0x8B59 +#define GL_FLOAT_MAT2 0x8B5A +#define GL_FLOAT_MAT3 0x8B5B +#define GL_FLOAT_MAT4 0x8B5C +#define GL_SAMPLER_1D 0x8B5D +#define GL_SAMPLER_2D 0x8B5E +#define GL_SAMPLER_3D 0x8B5F +#define GL_SAMPLER_CUBE 0x8B60 +#define GL_SAMPLER_1D_SHADOW 0x8B61 +#define GL_SAMPLER_2D_SHADOW 0x8B62 +#define GL_DELETE_STATUS 0x8B80 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 +#define GL_VALIDATE_STATUS 0x8B83 +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_ATTACHED_SHADERS 0x8B85 +#define GL_ACTIVE_UNIFORMS 0x8B86 +#define GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87 +#define GL_SHADER_SOURCE_LENGTH 0x8B88 +#define GL_ACTIVE_ATTRIBUTES 0x8B89 +#define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT 0x8B8B +#define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#define GL_CURRENT_PROGRAM 0x8B8D +#define GL_POINT_SPRITE_COORD_ORIGIN 0x8CA0 +#define GL_LOWER_LEFT 0x8CA1 +#define GL_UPPER_LEFT 0x8CA2 +#define GL_STENCIL_BACK_REF 0x8CA3 +#define GL_STENCIL_BACK_VALUE_MASK 0x8CA4 +#define GL_STENCIL_BACK_WRITEMASK 0x8CA5 +#endif + +#ifndef GL_VERSION_2_0_DEPRECATED +#define GL_VERTEX_PROGRAM_TWO_SIDE 0x8643 +#define GL_POINT_SPRITE 0x8861 +#define GL_COORD_REPLACE 0x8862 +#define GL_MAX_TEXTURE_COORDS 0x8871 +#endif + +#ifndef GL_VERSION_2_1 +#define GL_PIXEL_PACK_BUFFER 0x88EB +#define GL_PIXEL_UNPACK_BUFFER 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF +#define GL_FLOAT_MAT2x3 0x8B65 +#define GL_FLOAT_MAT2x4 0x8B66 +#define GL_FLOAT_MAT3x2 0x8B67 +#define GL_FLOAT_MAT3x4 0x8B68 +#define GL_FLOAT_MAT4x2 0x8B69 +#define GL_FLOAT_MAT4x3 0x8B6A +#define GL_SRGB 0x8C40 +#define GL_SRGB8 0x8C41 +#define GL_SRGB_ALPHA 0x8C42 +#define GL_SRGB8_ALPHA8 0x8C43 +#define GL_COMPRESSED_SRGB 0x8C48 +#define GL_COMPRESSED_SRGB_ALPHA 0x8C49 +#endif + +#ifndef GL_VERSION_2_1_DEPRECATED +#define GL_CURRENT_RASTER_SECONDARY_COLOR 0x845F +#define GL_SLUMINANCE_ALPHA 0x8C44 +#define GL_SLUMINANCE8_ALPHA8 0x8C45 +#define GL_SLUMINANCE 0x8C46 +#define GL_SLUMINANCE8 0x8C47 +#define GL_COMPRESSED_SLUMINANCE 0x8C4A +#define GL_COMPRESSED_SLUMINANCE_ALPHA 0x8C4B +#endif + +#ifndef GL_VERSION_3_0 +#define GL_COMPARE_REF_TO_TEXTURE 0x884E +#define GL_CLIP_DISTANCE0 0x3000 +#define GL_CLIP_DISTANCE1 0x3001 +#define GL_CLIP_DISTANCE2 0x3002 +#define GL_CLIP_DISTANCE3 0x3003 +#define GL_CLIP_DISTANCE4 0x3004 +#define GL_CLIP_DISTANCE5 0x3005 +#define GL_CLIP_DISTANCE6 0x3006 +#define GL_CLIP_DISTANCE7 0x3007 +#define GL_MAX_CLIP_DISTANCES 0x0D32 +#define GL_MAJOR_VERSION 0x821B +#define GL_MINOR_VERSION 0x821C +#define GL_NUM_EXTENSIONS 0x821D +#define GL_CONTEXT_FLAGS 0x821E +#define GL_DEPTH_BUFFER 0x8223 +#define GL_STENCIL_BUFFER 0x8224 +#define GL_COMPRESSED_RED 0x8225 +#define GL_COMPRESSED_RG 0x8226 +#define GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT 0x0001 +#define GL_RGBA32F 0x8814 +#define GL_RGB32F 0x8815 +#define GL_RGBA16F 0x881A +#define GL_RGB16F 0x881B +#define GL_VERTEX_ATTRIB_ARRAY_INTEGER 0x88FD +#define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF +#define GL_MIN_PROGRAM_TEXEL_OFFSET 0x8904 +#define GL_MAX_PROGRAM_TEXEL_OFFSET 0x8905 +#define GL_CLAMP_READ_COLOR 0x891C +#define GL_FIXED_ONLY 0x891D +#define GL_MAX_VARYING_COMPONENTS 0x8B4B +#define GL_TEXTURE_1D_ARRAY 0x8C18 +#define GL_PROXY_TEXTURE_1D_ARRAY 0x8C19 +#define GL_TEXTURE_2D_ARRAY 0x8C1A +#define GL_PROXY_TEXTURE_2D_ARRAY 0x8C1B +#define GL_TEXTURE_BINDING_1D_ARRAY 0x8C1C +#define GL_TEXTURE_BINDING_2D_ARRAY 0x8C1D +#define GL_R11F_G11F_B10F 0x8C3A +#define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B +#define GL_RGB9_E5 0x8C3D +#define GL_UNSIGNED_INT_5_9_9_9_REV 0x8C3E +#define GL_TEXTURE_SHARED_SIZE 0x8C3F +#define GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH 0x8C76 +#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE 0x8C7F +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS 0x8C80 +#define GL_TRANSFORM_FEEDBACK_VARYINGS 0x8C83 +#define GL_TRANSFORM_FEEDBACK_BUFFER_START 0x8C84 +#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE 0x8C85 +#define GL_PRIMITIVES_GENERATED 0x8C87 +#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN 0x8C88 +#define GL_RASTERIZER_DISCARD 0x8C89 +#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS 0x8C8A +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS 0x8C8B +#define GL_INTERLEAVED_ATTRIBS 0x8C8C +#define GL_SEPARATE_ATTRIBS 0x8C8D +#define GL_TRANSFORM_FEEDBACK_BUFFER 0x8C8E +#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING 0x8C8F +#define GL_RGBA32UI 0x8D70 +#define GL_RGB32UI 0x8D71 +#define GL_RGBA16UI 0x8D76 +#define GL_RGB16UI 0x8D77 +#define GL_RGBA8UI 0x8D7C +#define GL_RGB8UI 0x8D7D +#define GL_RGBA32I 0x8D82 +#define GL_RGB32I 0x8D83 +#define GL_RGBA16I 0x8D88 +#define GL_RGB16I 0x8D89 +#define GL_RGBA8I 0x8D8E +#define GL_RGB8I 0x8D8F +#define GL_RED_INTEGER 0x8D94 +#define GL_GREEN_INTEGER 0x8D95 +#define GL_BLUE_INTEGER 0x8D96 +#define GL_RGB_INTEGER 0x8D98 +#define GL_RGBA_INTEGER 0x8D99 +#define GL_BGR_INTEGER 0x8D9A +#define GL_BGRA_INTEGER 0x8D9B +#define GL_SAMPLER_1D_ARRAY 0x8DC0 +#define GL_SAMPLER_2D_ARRAY 0x8DC1 +#define GL_SAMPLER_1D_ARRAY_SHADOW 0x8DC3 +#define GL_SAMPLER_2D_ARRAY_SHADOW 0x8DC4 +#define GL_SAMPLER_CUBE_SHADOW 0x8DC5 +#define GL_UNSIGNED_INT_VEC2 0x8DC6 +#define GL_UNSIGNED_INT_VEC3 0x8DC7 +#define GL_UNSIGNED_INT_VEC4 0x8DC8 +#define GL_INT_SAMPLER_1D 0x8DC9 +#define GL_INT_SAMPLER_2D 0x8DCA +#define GL_INT_SAMPLER_3D 0x8DCB +#define GL_INT_SAMPLER_CUBE 0x8DCC +#define GL_INT_SAMPLER_1D_ARRAY 0x8DCE +#define GL_INT_SAMPLER_2D_ARRAY 0x8DCF +#define GL_UNSIGNED_INT_SAMPLER_1D 0x8DD1 +#define GL_UNSIGNED_INT_SAMPLER_2D 0x8DD2 +#define GL_UNSIGNED_INT_SAMPLER_3D 0x8DD3 +#define GL_UNSIGNED_INT_SAMPLER_CUBE 0x8DD4 +#define GL_UNSIGNED_INT_SAMPLER_1D_ARRAY 0x8DD6 +#define GL_UNSIGNED_INT_SAMPLER_2D_ARRAY 0x8DD7 +#define GL_QUERY_WAIT 0x8E13 +#define GL_QUERY_NO_WAIT 0x8E14 +#define GL_QUERY_BY_REGION_WAIT 0x8E15 +#define GL_QUERY_BY_REGION_NO_WAIT 0x8E16 +#define GL_BUFFER_ACCESS_FLAGS 0x911F +#define GL_BUFFER_MAP_LENGTH 0x9120 +#define GL_BUFFER_MAP_OFFSET 0x9121 +/* Reuse tokens from ARB_depth_buffer_float */ +/* reuse GL_DEPTH_COMPONENT32F */ +/* reuse GL_DEPTH32F_STENCIL8 */ +/* reuse GL_FLOAT_32_UNSIGNED_INT_24_8_REV */ +/* Reuse tokens from ARB_framebuffer_object */ +/* reuse GL_INVALID_FRAMEBUFFER_OPERATION */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE */ +/* reuse GL_FRAMEBUFFER_DEFAULT */ +/* reuse GL_FRAMEBUFFER_UNDEFINED */ +/* reuse GL_DEPTH_STENCIL_ATTACHMENT */ +/* reuse GL_INDEX */ +/* reuse GL_MAX_RENDERBUFFER_SIZE */ +/* reuse GL_DEPTH_STENCIL */ +/* reuse GL_UNSIGNED_INT_24_8 */ +/* reuse GL_DEPTH24_STENCIL8 */ +/* reuse GL_TEXTURE_STENCIL_SIZE */ +/* reuse GL_TEXTURE_RED_TYPE */ +/* reuse GL_TEXTURE_GREEN_TYPE */ +/* reuse GL_TEXTURE_BLUE_TYPE */ +/* reuse GL_TEXTURE_ALPHA_TYPE */ +/* reuse GL_TEXTURE_DEPTH_TYPE */ +/* reuse GL_UNSIGNED_NORMALIZED */ +/* reuse GL_FRAMEBUFFER_BINDING */ +/* reuse GL_DRAW_FRAMEBUFFER_BINDING */ +/* reuse GL_RENDERBUFFER_BINDING */ +/* reuse GL_READ_FRAMEBUFFER */ +/* reuse GL_DRAW_FRAMEBUFFER */ +/* reuse GL_READ_FRAMEBUFFER_BINDING */ +/* reuse GL_RENDERBUFFER_SAMPLES */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER */ +/* reuse GL_FRAMEBUFFER_COMPLETE */ +/* reuse GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT */ +/* reuse GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT */ +/* reuse GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER */ +/* reuse GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER */ +/* reuse GL_FRAMEBUFFER_UNSUPPORTED */ +/* reuse GL_MAX_COLOR_ATTACHMENTS */ +/* reuse GL_COLOR_ATTACHMENT0 */ +/* reuse GL_COLOR_ATTACHMENT1 */ +/* reuse GL_COLOR_ATTACHMENT2 */ +/* reuse GL_COLOR_ATTACHMENT3 */ +/* reuse GL_COLOR_ATTACHMENT4 */ +/* reuse GL_COLOR_ATTACHMENT5 */ +/* reuse GL_COLOR_ATTACHMENT6 */ +/* reuse GL_COLOR_ATTACHMENT7 */ +/* reuse GL_COLOR_ATTACHMENT8 */ +/* reuse GL_COLOR_ATTACHMENT9 */ +/* reuse GL_COLOR_ATTACHMENT10 */ +/* reuse GL_COLOR_ATTACHMENT11 */ +/* reuse GL_COLOR_ATTACHMENT12 */ +/* reuse GL_COLOR_ATTACHMENT13 */ +/* reuse GL_COLOR_ATTACHMENT14 */ +/* reuse GL_COLOR_ATTACHMENT15 */ +/* reuse GL_DEPTH_ATTACHMENT */ +/* reuse GL_STENCIL_ATTACHMENT */ +/* reuse GL_FRAMEBUFFER */ +/* reuse GL_RENDERBUFFER */ +/* reuse GL_RENDERBUFFER_WIDTH */ +/* reuse GL_RENDERBUFFER_HEIGHT */ +/* reuse GL_RENDERBUFFER_INTERNAL_FORMAT */ +/* reuse GL_STENCIL_INDEX1 */ +/* reuse GL_STENCIL_INDEX4 */ +/* reuse GL_STENCIL_INDEX8 */ +/* reuse GL_STENCIL_INDEX16 */ +/* reuse GL_RENDERBUFFER_RED_SIZE */ +/* reuse GL_RENDERBUFFER_GREEN_SIZE */ +/* reuse GL_RENDERBUFFER_BLUE_SIZE */ +/* reuse GL_RENDERBUFFER_ALPHA_SIZE */ +/* reuse GL_RENDERBUFFER_DEPTH_SIZE */ +/* reuse GL_RENDERBUFFER_STENCIL_SIZE */ +/* reuse GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE */ +/* reuse GL_MAX_SAMPLES */ +/* Reuse tokens from ARB_framebuffer_sRGB */ +/* reuse GL_FRAMEBUFFER_SRGB */ +/* Reuse tokens from ARB_half_float_vertex */ +/* reuse GL_HALF_FLOAT */ +/* Reuse tokens from ARB_map_buffer_range */ +/* reuse GL_MAP_READ_BIT */ +/* reuse GL_MAP_WRITE_BIT */ +/* reuse GL_MAP_INVALIDATE_RANGE_BIT */ +/* reuse GL_MAP_INVALIDATE_BUFFER_BIT */ +/* reuse GL_MAP_FLUSH_EXPLICIT_BIT */ +/* reuse GL_MAP_UNSYNCHRONIZED_BIT */ +/* Reuse tokens from ARB_texture_compression_rgtc */ +/* reuse GL_COMPRESSED_RED_RGTC1 */ +/* reuse GL_COMPRESSED_SIGNED_RED_RGTC1 */ +/* reuse GL_COMPRESSED_RG_RGTC2 */ +/* reuse GL_COMPRESSED_SIGNED_RG_RGTC2 */ +/* Reuse tokens from ARB_texture_rg */ +/* reuse GL_RG */ +/* reuse GL_RG_INTEGER */ +/* reuse GL_R8 */ +/* reuse GL_R16 */ +/* reuse GL_RG8 */ +/* reuse GL_RG16 */ +/* reuse GL_R16F */ +/* reuse GL_R32F */ +/* reuse GL_RG16F */ +/* reuse GL_RG32F */ +/* reuse GL_R8I */ +/* reuse GL_R8UI */ +/* reuse GL_R16I */ +/* reuse GL_R16UI */ +/* reuse GL_R32I */ +/* reuse GL_R32UI */ +/* reuse GL_RG8I */ +/* reuse GL_RG8UI */ +/* reuse GL_RG16I */ +/* reuse GL_RG16UI */ +/* reuse GL_RG32I */ +/* reuse GL_RG32UI */ +/* Reuse tokens from ARB_vertex_array_object */ +/* reuse GL_VERTEX_ARRAY_BINDING */ +#endif + +#ifndef GL_VERSION_3_0_DEPRECATED +#define GL_CLAMP_VERTEX_COLOR 0x891A +#define GL_CLAMP_FRAGMENT_COLOR 0x891B +#define GL_ALPHA_INTEGER 0x8D97 +/* Reuse tokens from ARB_framebuffer_object */ +/* reuse GL_TEXTURE_LUMINANCE_TYPE */ +/* reuse GL_TEXTURE_INTENSITY_TYPE */ +#endif + +#ifndef GL_VERSION_3_1 +#define GL_SAMPLER_2D_RECT 0x8B63 +#define GL_SAMPLER_2D_RECT_SHADOW 0x8B64 +#define GL_SAMPLER_BUFFER 0x8DC2 +#define GL_INT_SAMPLER_2D_RECT 0x8DCD +#define GL_INT_SAMPLER_BUFFER 0x8DD0 +#define GL_UNSIGNED_INT_SAMPLER_2D_RECT 0x8DD5 +#define GL_UNSIGNED_INT_SAMPLER_BUFFER 0x8DD8 +#define GL_TEXTURE_BUFFER 0x8C2A +#define GL_MAX_TEXTURE_BUFFER_SIZE 0x8C2B +#define GL_TEXTURE_BINDING_BUFFER 0x8C2C +#define GL_TEXTURE_BUFFER_DATA_STORE_BINDING 0x8C2D +#define GL_TEXTURE_BUFFER_FORMAT 0x8C2E +#define GL_TEXTURE_RECTANGLE 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE 0x84F8 +#define GL_RED_SNORM 0x8F90 +#define GL_RG_SNORM 0x8F91 +#define GL_RGB_SNORM 0x8F92 +#define GL_RGBA_SNORM 0x8F93 +#define GL_R8_SNORM 0x8F94 +#define GL_RG8_SNORM 0x8F95 +#define GL_RGB8_SNORM 0x8F96 +#define GL_RGBA8_SNORM 0x8F97 +#define GL_R16_SNORM 0x8F98 +#define GL_RG16_SNORM 0x8F99 +#define GL_RGB16_SNORM 0x8F9A +#define GL_RGBA16_SNORM 0x8F9B +#define GL_SIGNED_NORMALIZED 0x8F9C +#define GL_PRIMITIVE_RESTART 0x8F9D +#define GL_PRIMITIVE_RESTART_INDEX 0x8F9E +/* Reuse tokens from ARB_copy_buffer */ +/* reuse GL_COPY_READ_BUFFER */ +/* reuse GL_COPY_WRITE_BUFFER */ +/* Reuse tokens from ARB_draw_instanced (none) */ +/* Reuse tokens from ARB_uniform_buffer_object */ +/* reuse GL_UNIFORM_BUFFER */ +/* reuse GL_UNIFORM_BUFFER_BINDING */ +/* reuse GL_UNIFORM_BUFFER_START */ +/* reuse GL_UNIFORM_BUFFER_SIZE */ +/* reuse GL_MAX_VERTEX_UNIFORM_BLOCKS */ +/* reuse GL_MAX_FRAGMENT_UNIFORM_BLOCKS */ +/* reuse GL_MAX_COMBINED_UNIFORM_BLOCKS */ +/* reuse GL_MAX_UNIFORM_BUFFER_BINDINGS */ +/* reuse GL_MAX_UNIFORM_BLOCK_SIZE */ +/* reuse GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS */ +/* reuse GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS */ +/* reuse GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT */ +/* reuse GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH */ +/* reuse GL_ACTIVE_UNIFORM_BLOCKS */ +/* reuse GL_UNIFORM_TYPE */ +/* reuse GL_UNIFORM_SIZE */ +/* reuse GL_UNIFORM_NAME_LENGTH */ +/* reuse GL_UNIFORM_BLOCK_INDEX */ +/* reuse GL_UNIFORM_OFFSET */ +/* reuse GL_UNIFORM_ARRAY_STRIDE */ +/* reuse GL_UNIFORM_MATRIX_STRIDE */ +/* reuse GL_UNIFORM_IS_ROW_MAJOR */ +/* reuse GL_UNIFORM_BLOCK_BINDING */ +/* reuse GL_UNIFORM_BLOCK_DATA_SIZE */ +/* reuse GL_UNIFORM_BLOCK_NAME_LENGTH */ +/* reuse GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS */ +/* reuse GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES */ +/* reuse GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER */ +/* reuse GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER */ +/* reuse GL_INVALID_INDEX */ +#endif + +#ifndef GL_VERSION_3_2 +#define GL_CONTEXT_CORE_PROFILE_BIT 0x00000001 +#define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002 +#define GL_LINES_ADJACENCY 0x000A +#define GL_LINE_STRIP_ADJACENCY 0x000B +#define GL_TRIANGLES_ADJACENCY 0x000C +#define GL_TRIANGLE_STRIP_ADJACENCY 0x000D +#define GL_PROGRAM_POINT_SIZE 0x8642 +#define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS 0x8C29 +#define GL_FRAMEBUFFER_ATTACHMENT_LAYERED 0x8DA7 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS 0x8DA8 +#define GL_GEOMETRY_SHADER 0x8DD9 +#define GL_GEOMETRY_VERTICES_OUT 0x8916 +#define GL_GEOMETRY_INPUT_TYPE 0x8917 +#define GL_GEOMETRY_OUTPUT_TYPE 0x8918 +#define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS 0x8DDF +#define GL_MAX_GEOMETRY_OUTPUT_VERTICES 0x8DE0 +#define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS 0x8DE1 +#define GL_MAX_VERTEX_OUTPUT_COMPONENTS 0x9122 +#define GL_MAX_GEOMETRY_INPUT_COMPONENTS 0x9123 +#define GL_MAX_GEOMETRY_OUTPUT_COMPONENTS 0x9124 +#define GL_MAX_FRAGMENT_INPUT_COMPONENTS 0x9125 +#define GL_CONTEXT_PROFILE_MASK 0x9126 +/* reuse GL_MAX_VARYING_COMPONENTS */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER */ +/* Reuse tokens from ARB_depth_clamp */ +/* reuse GL_DEPTH_CLAMP */ +/* Reuse tokens from ARB_draw_elements_base_vertex (none) */ +/* Reuse tokens from ARB_fragment_coord_conventions (none) */ +/* Reuse tokens from ARB_provoking_vertex */ +/* reuse GL_QUADS_FOLLOW_PROVOKING_VERTEX_CONVENTION */ +/* reuse GL_FIRST_VERTEX_CONVENTION */ +/* reuse GL_LAST_VERTEX_CONVENTION */ +/* reuse GL_PROVOKING_VERTEX */ +/* Reuse tokens from ARB_seamless_cube_map */ +/* reuse GL_TEXTURE_CUBE_MAP_SEAMLESS */ +/* Reuse tokens from ARB_sync */ +/* reuse GL_MAX_SERVER_WAIT_TIMEOUT */ +/* reuse GL_OBJECT_TYPE */ +/* reuse GL_SYNC_CONDITION */ +/* reuse GL_SYNC_STATUS */ +/* reuse GL_SYNC_FLAGS */ +/* reuse GL_SYNC_FENCE */ +/* reuse GL_SYNC_GPU_COMMANDS_COMPLETE */ +/* reuse GL_UNSIGNALED */ +/* reuse GL_SIGNALED */ +/* reuse GL_ALREADY_SIGNALED */ +/* reuse GL_TIMEOUT_EXPIRED */ +/* reuse GL_CONDITION_SATISFIED */ +/* reuse GL_WAIT_FAILED */ +/* reuse GL_TIMEOUT_IGNORED */ +/* reuse GL_SYNC_FLUSH_COMMANDS_BIT */ +/* reuse GL_TIMEOUT_IGNORED */ +/* Reuse tokens from ARB_texture_multisample */ +/* reuse GL_SAMPLE_POSITION */ +/* reuse GL_SAMPLE_MASK */ +/* reuse GL_SAMPLE_MASK_VALUE */ +/* reuse GL_MAX_SAMPLE_MASK_WORDS */ +/* reuse GL_TEXTURE_2D_MULTISAMPLE */ +/* reuse GL_PROXY_TEXTURE_2D_MULTISAMPLE */ +/* reuse GL_TEXTURE_2D_MULTISAMPLE_ARRAY */ +/* reuse GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY */ +/* reuse GL_TEXTURE_BINDING_2D_MULTISAMPLE */ +/* reuse GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY */ +/* reuse GL_TEXTURE_SAMPLES */ +/* reuse GL_TEXTURE_FIXED_SAMPLE_LOCATIONS */ +/* reuse GL_SAMPLER_2D_MULTISAMPLE */ +/* reuse GL_INT_SAMPLER_2D_MULTISAMPLE */ +/* reuse GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE */ +/* reuse GL_SAMPLER_2D_MULTISAMPLE_ARRAY */ +/* reuse GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY */ +/* reuse GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY */ +/* reuse GL_MAX_COLOR_TEXTURE_SAMPLES */ +/* reuse GL_MAX_DEPTH_TEXTURE_SAMPLES */ +/* reuse GL_MAX_INTEGER_SAMPLES */ +/* Don't need to reuse tokens from ARB_vertex_array_bgra since they're already in 1.2 core */ +#endif + +#ifndef GL_VERSION_3_3 +#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR 0x88FE +/* Reuse tokens from ARB_blend_func_extended */ +/* reuse GL_SRC1_COLOR */ +/* reuse GL_ONE_MINUS_SRC1_COLOR */ +/* reuse GL_ONE_MINUS_SRC1_ALPHA */ +/* reuse GL_MAX_DUAL_SOURCE_DRAW_BUFFERS */ +/* Reuse tokens from ARB_explicit_attrib_location (none) */ +/* Reuse tokens from ARB_occlusion_query2 */ +/* reuse GL_ANY_SAMPLES_PASSED */ +/* Reuse tokens from ARB_sampler_objects */ +/* reuse GL_SAMPLER_BINDING */ +/* Reuse tokens from ARB_shader_bit_encoding (none) */ +/* Reuse tokens from ARB_texture_rgb10_a2ui */ +/* reuse GL_RGB10_A2UI */ +/* Reuse tokens from ARB_texture_swizzle */ +/* reuse GL_TEXTURE_SWIZZLE_R */ +/* reuse GL_TEXTURE_SWIZZLE_G */ +/* reuse GL_TEXTURE_SWIZZLE_B */ +/* reuse GL_TEXTURE_SWIZZLE_A */ +/* reuse GL_TEXTURE_SWIZZLE_RGBA */ +/* Reuse tokens from ARB_timer_query */ +/* reuse GL_TIME_ELAPSED */ +/* reuse GL_TIMESTAMP */ +/* Reuse tokens from ARB_vertex_type_2_10_10_10_rev */ +/* reuse GL_INT_2_10_10_10_REV */ +#endif + +#ifndef GL_VERSION_4_0 +#define GL_SAMPLE_SHADING 0x8C36 +#define GL_MIN_SAMPLE_SHADING_VALUE 0x8C37 +#define GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET 0x8E5E +#define GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET 0x8E5F +#define GL_TEXTURE_CUBE_MAP_ARRAY 0x9009 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARRAY 0x900A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARRAY 0x900B +#define GL_SAMPLER_CUBE_MAP_ARRAY 0x900C +#define GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW 0x900D +#define GL_INT_SAMPLER_CUBE_MAP_ARRAY 0x900E +#define GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY 0x900F +/* Reuse tokens from ARB_texture_query_lod (none) */ +/* Reuse tokens from ARB_draw_buffers_blend (none) */ +/* Reuse tokens from ARB_draw_indirect */ +/* reuse GL_DRAW_INDIRECT_BUFFER */ +/* reuse GL_DRAW_INDIRECT_BUFFER_BINDING */ +/* Reuse tokens from ARB_gpu_shader5 */ +/* reuse GL_GEOMETRY_SHADER_INVOCATIONS */ +/* reuse GL_MAX_GEOMETRY_SHADER_INVOCATIONS */ +/* reuse GL_MIN_FRAGMENT_INTERPOLATION_OFFSET */ +/* reuse GL_MAX_FRAGMENT_INTERPOLATION_OFFSET */ +/* reuse GL_FRAGMENT_INTERPOLATION_OFFSET_BITS */ +/* reuse GL_MAX_VERTEX_STREAMS */ +/* Reuse tokens from ARB_gpu_shader_fp64 */ +/* reuse GL_DOUBLE_VEC2 */ +/* reuse GL_DOUBLE_VEC3 */ +/* reuse GL_DOUBLE_VEC4 */ +/* reuse GL_DOUBLE_MAT2 */ +/* reuse GL_DOUBLE_MAT3 */ +/* reuse GL_DOUBLE_MAT4 */ +/* reuse GL_DOUBLE_MAT2x3 */ +/* reuse GL_DOUBLE_MAT2x4 */ +/* reuse GL_DOUBLE_MAT3x2 */ +/* reuse GL_DOUBLE_MAT3x4 */ +/* reuse GL_DOUBLE_MAT4x2 */ +/* reuse GL_DOUBLE_MAT4x3 */ +/* Reuse tokens from ARB_shader_subroutine */ +/* reuse GL_ACTIVE_SUBROUTINES */ +/* reuse GL_ACTIVE_SUBROUTINE_UNIFORMS */ +/* reuse GL_ACTIVE_SUBROUTINE_UNIFORM_LOCATIONS */ +/* reuse GL_ACTIVE_SUBROUTINE_MAX_LENGTH */ +/* reuse GL_ACTIVE_SUBROUTINE_UNIFORM_MAX_LENGTH */ +/* reuse GL_MAX_SUBROUTINES */ +/* reuse GL_MAX_SUBROUTINE_UNIFORM_LOCATIONS */ +/* reuse GL_NUM_COMPATIBLE_SUBROUTINES */ +/* reuse GL_COMPATIBLE_SUBROUTINES */ +/* Reuse tokens from ARB_tessellation_shader */ +/* reuse GL_PATCHES */ +/* reuse GL_PATCH_VERTICES */ +/* reuse GL_PATCH_DEFAULT_INNER_LEVEL */ +/* reuse GL_PATCH_DEFAULT_OUTER_LEVEL */ +/* reuse GL_TESS_CONTROL_OUTPUT_VERTICES */ +/* reuse GL_TESS_GEN_MODE */ +/* reuse GL_TESS_GEN_SPACING */ +/* reuse GL_TESS_GEN_VERTEX_ORDER */ +/* reuse GL_TESS_GEN_POINT_MODE */ +/* reuse GL_ISOLINES */ +/* reuse GL_FRACTIONAL_ODD */ +/* reuse GL_FRACTIONAL_EVEN */ +/* reuse GL_MAX_PATCH_VERTICES */ +/* reuse GL_MAX_TESS_GEN_LEVEL */ +/* reuse GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS */ +/* reuse GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS */ +/* reuse GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS */ +/* reuse GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS */ +/* reuse GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS */ +/* reuse GL_MAX_TESS_PATCH_COMPONENTS */ +/* reuse GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS */ +/* reuse GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS */ +/* reuse GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS */ +/* reuse GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS */ +/* reuse GL_MAX_TESS_CONTROL_INPUT_COMPONENTS */ +/* reuse GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS */ +/* reuse GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS */ +/* reuse GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS */ +/* reuse GL_UNIFORM_BLOCK_REFERENCED_BY_TESS_CONTROL_SHADER */ +/* reuse GL_UNIFORM_BLOCK_REFERENCED_BY_TESS_EVALUATION_SHADER */ +/* reuse GL_TESS_EVALUATION_SHADER */ +/* reuse GL_TESS_CONTROL_SHADER */ +/* Reuse tokens from ARB_texture_buffer_object_rgb32 (none) */ +/* Reuse tokens from ARB_transform_feedback2 */ +/* reuse GL_TRANSFORM_FEEDBACK */ +/* reuse GL_TRANSFORM_FEEDBACK_BUFFER_PAUSED */ +/* reuse GL_TRANSFORM_FEEDBACK_BUFFER_ACTIVE */ +/* reuse GL_TRANSFORM_FEEDBACK_BINDING */ +/* Reuse tokens from ARB_transform_feedback3 */ +/* reuse GL_MAX_TRANSFORM_FEEDBACK_BUFFERS */ +/* reuse GL_MAX_VERTEX_STREAMS */ +#endif + +#ifndef GL_VERSION_4_1 +/* Reuse tokens from ARB_ES2_compatibility */ +/* reuse GL_FIXED */ +/* reuse GL_IMPLEMENTATION_COLOR_READ_TYPE */ +/* reuse GL_IMPLEMENTATION_COLOR_READ_FORMAT */ +/* reuse GL_LOW_FLOAT */ +/* reuse GL_MEDIUM_FLOAT */ +/* reuse GL_HIGH_FLOAT */ +/* reuse GL_LOW_INT */ +/* reuse GL_MEDIUM_INT */ +/* reuse GL_HIGH_INT */ +/* reuse GL_SHADER_COMPILER */ +/* reuse GL_NUM_SHADER_BINARY_FORMATS */ +/* reuse GL_MAX_VERTEX_UNIFORM_VECTORS */ +/* reuse GL_MAX_VARYING_VECTORS */ +/* reuse GL_MAX_FRAGMENT_UNIFORM_VECTORS */ +/* Reuse tokens from ARB_get_program_binary */ +/* reuse GL_PROGRAM_BINARY_RETRIEVABLE_HINT */ +/* reuse GL_PROGRAM_BINARY_LENGTH */ +/* reuse GL_NUM_PROGRAM_BINARY_FORMATS */ +/* reuse GL_PROGRAM_BINARY_FORMATS */ +/* Reuse tokens from ARB_separate_shader_objects */ +/* reuse GL_VERTEX_SHADER_BIT */ +/* reuse GL_FRAGMENT_SHADER_BIT */ +/* reuse GL_GEOMETRY_SHADER_BIT */ +/* reuse GL_TESS_CONTROL_SHADER_BIT */ +/* reuse GL_TESS_EVALUATION_SHADER_BIT */ +/* reuse GL_ALL_SHADER_BITS */ +/* reuse GL_PROGRAM_SEPARABLE */ +/* reuse GL_ACTIVE_PROGRAM */ +/* reuse GL_PROGRAM_PIPELINE_BINDING */ +/* Reuse tokens from ARB_shader_precision (none) */ +/* Reuse tokens from ARB_vertex_attrib_64bit - all are in GL 3.0 and 4.0 already */ +/* Reuse tokens from ARB_viewport_array - some are in GL 1.1 and ARB_provoking_vertex already */ +/* reuse GL_MAX_VIEWPORTS */ +/* reuse GL_VIEWPORT_SUBPIXEL_BITS */ +/* reuse GL_VIEWPORT_BOUNDS_RANGE */ +/* reuse GL_LAYER_PROVOKING_VERTEX */ +/* reuse GL_VIEWPORT_INDEX_PROVOKING_VERTEX */ +/* reuse GL_UNDEFINED_VERTEX */ +#endif + +#ifndef GL_ARB_multitexture +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 +#define GL_TEXTURE4_ARB 0x84C4 +#define GL_TEXTURE5_ARB 0x84C5 +#define GL_TEXTURE6_ARB 0x84C6 +#define GL_TEXTURE7_ARB 0x84C7 +#define GL_TEXTURE8_ARB 0x84C8 +#define GL_TEXTURE9_ARB 0x84C9 +#define GL_TEXTURE10_ARB 0x84CA +#define GL_TEXTURE11_ARB 0x84CB +#define GL_TEXTURE12_ARB 0x84CC +#define GL_TEXTURE13_ARB 0x84CD +#define GL_TEXTURE14_ARB 0x84CE +#define GL_TEXTURE15_ARB 0x84CF +#define GL_TEXTURE16_ARB 0x84D0 +#define GL_TEXTURE17_ARB 0x84D1 +#define GL_TEXTURE18_ARB 0x84D2 +#define GL_TEXTURE19_ARB 0x84D3 +#define GL_TEXTURE20_ARB 0x84D4 +#define GL_TEXTURE21_ARB 0x84D5 +#define GL_TEXTURE22_ARB 0x84D6 +#define GL_TEXTURE23_ARB 0x84D7 +#define GL_TEXTURE24_ARB 0x84D8 +#define GL_TEXTURE25_ARB 0x84D9 +#define GL_TEXTURE26_ARB 0x84DA +#define GL_TEXTURE27_ARB 0x84DB +#define GL_TEXTURE28_ARB 0x84DC +#define GL_TEXTURE29_ARB 0x84DD +#define GL_TEXTURE30_ARB 0x84DE +#define GL_TEXTURE31_ARB 0x84DF +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 +#endif + +#ifndef GL_ARB_transpose_matrix +#define GL_TRANSPOSE_MODELVIEW_MATRIX_ARB 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX_ARB 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX_ARB 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX_ARB 0x84E6 +#endif + +#ifndef GL_ARB_multisample +#define GL_MULTISAMPLE_ARB 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE_ARB 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_ARB 0x809F +#define GL_SAMPLE_COVERAGE_ARB 0x80A0 +#define GL_SAMPLE_BUFFERS_ARB 0x80A8 +#define GL_SAMPLES_ARB 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE_ARB 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT_ARB 0x80AB +#define GL_MULTISAMPLE_BIT_ARB 0x20000000 +#endif + +#ifndef GL_ARB_texture_env_add +#endif + +#ifndef GL_ARB_texture_cube_map +#define GL_NORMAL_MAP_ARB 0x8511 +#define GL_REFLECTION_MAP_ARB 0x8512 +#define GL_TEXTURE_CUBE_MAP_ARB 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARB 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARB 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB 0x851C +#endif + +#ifndef GL_ARB_texture_compression +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 +#define GL_COMPRESSED_LUMINANCE_ARB 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB +#define GL_COMPRESSED_INTENSITY_ARB 0x84EC +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT_ARB 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB 0x86A0 +#define GL_TEXTURE_COMPRESSED_ARB 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 +#endif + +#ifndef GL_ARB_texture_border_clamp +#define GL_CLAMP_TO_BORDER_ARB 0x812D +#endif + +#ifndef GL_ARB_point_parameters +#define GL_POINT_SIZE_MIN_ARB 0x8126 +#define GL_POINT_SIZE_MAX_ARB 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_ARB 0x8128 +#define GL_POINT_DISTANCE_ATTENUATION_ARB 0x8129 +#endif + +#ifndef GL_ARB_vertex_blend +#define GL_MAX_VERTEX_UNITS_ARB 0x86A4 +#define GL_ACTIVE_VERTEX_UNITS_ARB 0x86A5 +#define GL_WEIGHT_SUM_UNITY_ARB 0x86A6 +#define GL_VERTEX_BLEND_ARB 0x86A7 +#define GL_CURRENT_WEIGHT_ARB 0x86A8 +#define GL_WEIGHT_ARRAY_TYPE_ARB 0x86A9 +#define GL_WEIGHT_ARRAY_STRIDE_ARB 0x86AA +#define GL_WEIGHT_ARRAY_SIZE_ARB 0x86AB +#define GL_WEIGHT_ARRAY_POINTER_ARB 0x86AC +#define GL_WEIGHT_ARRAY_ARB 0x86AD +#define GL_MODELVIEW0_ARB 0x1700 +#define GL_MODELVIEW1_ARB 0x850A +#define GL_MODELVIEW2_ARB 0x8722 +#define GL_MODELVIEW3_ARB 0x8723 +#define GL_MODELVIEW4_ARB 0x8724 +#define GL_MODELVIEW5_ARB 0x8725 +#define GL_MODELVIEW6_ARB 0x8726 +#define GL_MODELVIEW7_ARB 0x8727 +#define GL_MODELVIEW8_ARB 0x8728 +#define GL_MODELVIEW9_ARB 0x8729 +#define GL_MODELVIEW10_ARB 0x872A +#define GL_MODELVIEW11_ARB 0x872B +#define GL_MODELVIEW12_ARB 0x872C +#define GL_MODELVIEW13_ARB 0x872D +#define GL_MODELVIEW14_ARB 0x872E +#define GL_MODELVIEW15_ARB 0x872F +#define GL_MODELVIEW16_ARB 0x8730 +#define GL_MODELVIEW17_ARB 0x8731 +#define GL_MODELVIEW18_ARB 0x8732 +#define GL_MODELVIEW19_ARB 0x8733 +#define GL_MODELVIEW20_ARB 0x8734 +#define GL_MODELVIEW21_ARB 0x8735 +#define GL_MODELVIEW22_ARB 0x8736 +#define GL_MODELVIEW23_ARB 0x8737 +#define GL_MODELVIEW24_ARB 0x8738 +#define GL_MODELVIEW25_ARB 0x8739 +#define GL_MODELVIEW26_ARB 0x873A +#define GL_MODELVIEW27_ARB 0x873B +#define GL_MODELVIEW28_ARB 0x873C +#define GL_MODELVIEW29_ARB 0x873D +#define GL_MODELVIEW30_ARB 0x873E +#define GL_MODELVIEW31_ARB 0x873F +#endif + +#ifndef GL_ARB_matrix_palette +#define GL_MATRIX_PALETTE_ARB 0x8840 +#define GL_MAX_MATRIX_PALETTE_STACK_DEPTH_ARB 0x8841 +#define GL_MAX_PALETTE_MATRICES_ARB 0x8842 +#define GL_CURRENT_PALETTE_MATRIX_ARB 0x8843 +#define GL_MATRIX_INDEX_ARRAY_ARB 0x8844 +#define GL_CURRENT_MATRIX_INDEX_ARB 0x8845 +#define GL_MATRIX_INDEX_ARRAY_SIZE_ARB 0x8846 +#define GL_MATRIX_INDEX_ARRAY_TYPE_ARB 0x8847 +#define GL_MATRIX_INDEX_ARRAY_STRIDE_ARB 0x8848 +#define GL_MATRIX_INDEX_ARRAY_POINTER_ARB 0x8849 +#endif + +#ifndef GL_ARB_texture_env_combine +#define GL_COMBINE_ARB 0x8570 +#define GL_COMBINE_RGB_ARB 0x8571 +#define GL_COMBINE_ALPHA_ARB 0x8572 +#define GL_SOURCE0_RGB_ARB 0x8580 +#define GL_SOURCE1_RGB_ARB 0x8581 +#define GL_SOURCE2_RGB_ARB 0x8582 +#define GL_SOURCE0_ALPHA_ARB 0x8588 +#define GL_SOURCE1_ALPHA_ARB 0x8589 +#define GL_SOURCE2_ALPHA_ARB 0x858A +#define GL_OPERAND0_RGB_ARB 0x8590 +#define GL_OPERAND1_RGB_ARB 0x8591 +#define GL_OPERAND2_RGB_ARB 0x8592 +#define GL_OPERAND0_ALPHA_ARB 0x8598 +#define GL_OPERAND1_ALPHA_ARB 0x8599 +#define GL_OPERAND2_ALPHA_ARB 0x859A +#define GL_RGB_SCALE_ARB 0x8573 +#define GL_ADD_SIGNED_ARB 0x8574 +#define GL_INTERPOLATE_ARB 0x8575 +#define GL_SUBTRACT_ARB 0x84E7 +#define GL_CONSTANT_ARB 0x8576 +#define GL_PRIMARY_COLOR_ARB 0x8577 +#define GL_PREVIOUS_ARB 0x8578 +#endif + +#ifndef GL_ARB_texture_env_crossbar +#endif + +#ifndef GL_ARB_texture_env_dot3 +#define GL_DOT3_RGB_ARB 0x86AE +#define GL_DOT3_RGBA_ARB 0x86AF +#endif + +#ifndef GL_ARB_texture_mirrored_repeat +#define GL_MIRRORED_REPEAT_ARB 0x8370 +#endif + +#ifndef GL_ARB_depth_texture +#define GL_DEPTH_COMPONENT16_ARB 0x81A5 +#define GL_DEPTH_COMPONENT24_ARB 0x81A6 +#define GL_DEPTH_COMPONENT32_ARB 0x81A7 +#define GL_TEXTURE_DEPTH_SIZE_ARB 0x884A +#define GL_DEPTH_TEXTURE_MODE_ARB 0x884B +#endif + +#ifndef GL_ARB_shadow +#define GL_TEXTURE_COMPARE_MODE_ARB 0x884C +#define GL_TEXTURE_COMPARE_FUNC_ARB 0x884D +#define GL_COMPARE_R_TO_TEXTURE_ARB 0x884E +#endif + +#ifndef GL_ARB_shadow_ambient +#define GL_TEXTURE_COMPARE_FAIL_VALUE_ARB 0x80BF +#endif + +#ifndef GL_ARB_window_pos +#endif + +#ifndef GL_ARB_vertex_program +#define GL_COLOR_SUM_ARB 0x8458 +#define GL_VERTEX_PROGRAM_ARB 0x8620 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE_ARB 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB_ARB 0x8626 +#define GL_PROGRAM_LENGTH_ARB 0x8627 +#define GL_PROGRAM_STRING_ARB 0x8628 +#define GL_MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB 0x862E +#define GL_MAX_PROGRAM_MATRICES_ARB 0x862F +#define GL_CURRENT_MATRIX_STACK_DEPTH_ARB 0x8640 +#define GL_CURRENT_MATRIX_ARB 0x8641 +#define GL_VERTEX_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_ARB 0x8643 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB 0x8645 +#define GL_PROGRAM_ERROR_POSITION_ARB 0x864B +#define GL_PROGRAM_BINDING_ARB 0x8677 +#define GL_MAX_VERTEX_ATTRIBS_ARB 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB 0x886A +#define GL_PROGRAM_ERROR_STRING_ARB 0x8874 +#define GL_PROGRAM_FORMAT_ASCII_ARB 0x8875 +#define GL_PROGRAM_FORMAT_ARB 0x8876 +#define GL_PROGRAM_INSTRUCTIONS_ARB 0x88A0 +#define GL_MAX_PROGRAM_INSTRUCTIONS_ARB 0x88A1 +#define GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A2 +#define GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A3 +#define GL_PROGRAM_TEMPORARIES_ARB 0x88A4 +#define GL_MAX_PROGRAM_TEMPORARIES_ARB 0x88A5 +#define GL_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A6 +#define GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A7 +#define GL_PROGRAM_PARAMETERS_ARB 0x88A8 +#define GL_MAX_PROGRAM_PARAMETERS_ARB 0x88A9 +#define GL_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AA +#define GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AB +#define GL_PROGRAM_ATTRIBS_ARB 0x88AC +#define GL_MAX_PROGRAM_ATTRIBS_ARB 0x88AD +#define GL_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AE +#define GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AF +#define GL_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B0 +#define GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B1 +#define GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B2 +#define GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B3 +#define GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB 0x88B4 +#define GL_MAX_PROGRAM_ENV_PARAMETERS_ARB 0x88B5 +#define GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB 0x88B6 +#define GL_TRANSPOSE_CURRENT_MATRIX_ARB 0x88B7 +#define GL_MATRIX0_ARB 0x88C0 +#define GL_MATRIX1_ARB 0x88C1 +#define GL_MATRIX2_ARB 0x88C2 +#define GL_MATRIX3_ARB 0x88C3 +#define GL_MATRIX4_ARB 0x88C4 +#define GL_MATRIX5_ARB 0x88C5 +#define GL_MATRIX6_ARB 0x88C6 +#define GL_MATRIX7_ARB 0x88C7 +#define GL_MATRIX8_ARB 0x88C8 +#define GL_MATRIX9_ARB 0x88C9 +#define GL_MATRIX10_ARB 0x88CA +#define GL_MATRIX11_ARB 0x88CB +#define GL_MATRIX12_ARB 0x88CC +#define GL_MATRIX13_ARB 0x88CD +#define GL_MATRIX14_ARB 0x88CE +#define GL_MATRIX15_ARB 0x88CF +#define GL_MATRIX16_ARB 0x88D0 +#define GL_MATRIX17_ARB 0x88D1 +#define GL_MATRIX18_ARB 0x88D2 +#define GL_MATRIX19_ARB 0x88D3 +#define GL_MATRIX20_ARB 0x88D4 +#define GL_MATRIX21_ARB 0x88D5 +#define GL_MATRIX22_ARB 0x88D6 +#define GL_MATRIX23_ARB 0x88D7 +#define GL_MATRIX24_ARB 0x88D8 +#define GL_MATRIX25_ARB 0x88D9 +#define GL_MATRIX26_ARB 0x88DA +#define GL_MATRIX27_ARB 0x88DB +#define GL_MATRIX28_ARB 0x88DC +#define GL_MATRIX29_ARB 0x88DD +#define GL_MATRIX30_ARB 0x88DE +#define GL_MATRIX31_ARB 0x88DF +#endif + +#ifndef GL_ARB_fragment_program +#define GL_FRAGMENT_PROGRAM_ARB 0x8804 +#define GL_PROGRAM_ALU_INSTRUCTIONS_ARB 0x8805 +#define GL_PROGRAM_TEX_INSTRUCTIONS_ARB 0x8806 +#define GL_PROGRAM_TEX_INDIRECTIONS_ARB 0x8807 +#define GL_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x8808 +#define GL_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x8809 +#define GL_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x880A +#define GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB 0x880B +#define GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB 0x880C +#define GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB 0x880D +#define GL_MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x880E +#define GL_MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x880F +#define GL_MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x8810 +#define GL_MAX_TEXTURE_COORDS_ARB 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 +#endif + +#ifndef GL_ARB_vertex_buffer_object +#define GL_BUFFER_SIZE_ARB 0x8764 +#define GL_BUFFER_USAGE_ARB 0x8765 +#define GL_ARRAY_BUFFER_ARB 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER_ARB 0x8893 +#define GL_ARRAY_BUFFER_BINDING_ARB 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB 0x8895 +#define GL_VERTEX_ARRAY_BUFFER_BINDING_ARB 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING_ARB 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING_ARB 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING_ARB 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING_ARB 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING_ARB 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING_ARB 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING_ARB 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING_ARB 0x889E +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB 0x889F +#define GL_READ_ONLY_ARB 0x88B8 +#define GL_WRITE_ONLY_ARB 0x88B9 +#define GL_READ_WRITE_ARB 0x88BA +#define GL_BUFFER_ACCESS_ARB 0x88BB +#define GL_BUFFER_MAPPED_ARB 0x88BC +#define GL_BUFFER_MAP_POINTER_ARB 0x88BD +#define GL_STREAM_DRAW_ARB 0x88E0 +#define GL_STREAM_READ_ARB 0x88E1 +#define GL_STREAM_COPY_ARB 0x88E2 +#define GL_STATIC_DRAW_ARB 0x88E4 +#define GL_STATIC_READ_ARB 0x88E5 +#define GL_STATIC_COPY_ARB 0x88E6 +#define GL_DYNAMIC_DRAW_ARB 0x88E8 +#define GL_DYNAMIC_READ_ARB 0x88E9 +#define GL_DYNAMIC_COPY_ARB 0x88EA +#endif + +#ifndef GL_ARB_occlusion_query +#define GL_QUERY_COUNTER_BITS_ARB 0x8864 +#define GL_CURRENT_QUERY_ARB 0x8865 +#define GL_QUERY_RESULT_ARB 0x8866 +#define GL_QUERY_RESULT_AVAILABLE_ARB 0x8867 +#define GL_SAMPLES_PASSED_ARB 0x8914 +#endif + +#ifndef GL_ARB_shader_objects +#define GL_PROGRAM_OBJECT_ARB 0x8B40 +#define GL_SHADER_OBJECT_ARB 0x8B48 +#define GL_OBJECT_TYPE_ARB 0x8B4E +#define GL_OBJECT_SUBTYPE_ARB 0x8B4F +#define GL_FLOAT_VEC2_ARB 0x8B50 +#define GL_FLOAT_VEC3_ARB 0x8B51 +#define GL_FLOAT_VEC4_ARB 0x8B52 +#define GL_INT_VEC2_ARB 0x8B53 +#define GL_INT_VEC3_ARB 0x8B54 +#define GL_INT_VEC4_ARB 0x8B55 +#define GL_BOOL_ARB 0x8B56 +#define GL_BOOL_VEC2_ARB 0x8B57 +#define GL_BOOL_VEC3_ARB 0x8B58 +#define GL_BOOL_VEC4_ARB 0x8B59 +#define GL_FLOAT_MAT2_ARB 0x8B5A +#define GL_FLOAT_MAT3_ARB 0x8B5B +#define GL_FLOAT_MAT4_ARB 0x8B5C +#define GL_SAMPLER_1D_ARB 0x8B5D +#define GL_SAMPLER_2D_ARB 0x8B5E +#define GL_SAMPLER_3D_ARB 0x8B5F +#define GL_SAMPLER_CUBE_ARB 0x8B60 +#define GL_SAMPLER_1D_SHADOW_ARB 0x8B61 +#define GL_SAMPLER_2D_SHADOW_ARB 0x8B62 +#define GL_SAMPLER_2D_RECT_ARB 0x8B63 +#define GL_SAMPLER_2D_RECT_SHADOW_ARB 0x8B64 +#define GL_OBJECT_DELETE_STATUS_ARB 0x8B80 +#define GL_OBJECT_COMPILE_STATUS_ARB 0x8B81 +#define GL_OBJECT_LINK_STATUS_ARB 0x8B82 +#define GL_OBJECT_VALIDATE_STATUS_ARB 0x8B83 +#define GL_OBJECT_INFO_LOG_LENGTH_ARB 0x8B84 +#define GL_OBJECT_ATTACHED_OBJECTS_ARB 0x8B85 +#define GL_OBJECT_ACTIVE_UNIFORMS_ARB 0x8B86 +#define GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB 0x8B87 +#define GL_OBJECT_SHADER_SOURCE_LENGTH_ARB 0x8B88 +#endif + +#ifndef GL_ARB_vertex_shader +#define GL_VERTEX_SHADER_ARB 0x8B31 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB 0x8B4A +#define GL_MAX_VARYING_FLOATS_ARB 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB 0x8B4D +#define GL_OBJECT_ACTIVE_ATTRIBUTES_ARB 0x8B89 +#define GL_OBJECT_ACTIVE_ATTRIBUTE_MAX_LENGTH_ARB 0x8B8A +#endif + +#ifndef GL_ARB_fragment_shader +#define GL_FRAGMENT_SHADER_ARB 0x8B30 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB 0x8B49 +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT_ARB 0x8B8B +#endif + +#ifndef GL_ARB_shading_language_100 +#define GL_SHADING_LANGUAGE_VERSION_ARB 0x8B8C +#endif + +#ifndef GL_ARB_texture_non_power_of_two +#endif + +#ifndef GL_ARB_point_sprite +#define GL_POINT_SPRITE_ARB 0x8861 +#define GL_COORD_REPLACE_ARB 0x8862 +#endif + +#ifndef GL_ARB_fragment_program_shadow +#endif + +#ifndef GL_ARB_draw_buffers +#define GL_MAX_DRAW_BUFFERS_ARB 0x8824 +#define GL_DRAW_BUFFER0_ARB 0x8825 +#define GL_DRAW_BUFFER1_ARB 0x8826 +#define GL_DRAW_BUFFER2_ARB 0x8827 +#define GL_DRAW_BUFFER3_ARB 0x8828 +#define GL_DRAW_BUFFER4_ARB 0x8829 +#define GL_DRAW_BUFFER5_ARB 0x882A +#define GL_DRAW_BUFFER6_ARB 0x882B +#define GL_DRAW_BUFFER7_ARB 0x882C +#define GL_DRAW_BUFFER8_ARB 0x882D +#define GL_DRAW_BUFFER9_ARB 0x882E +#define GL_DRAW_BUFFER10_ARB 0x882F +#define GL_DRAW_BUFFER11_ARB 0x8830 +#define GL_DRAW_BUFFER12_ARB 0x8831 +#define GL_DRAW_BUFFER13_ARB 0x8832 +#define GL_DRAW_BUFFER14_ARB 0x8833 +#define GL_DRAW_BUFFER15_ARB 0x8834 +#endif + +#ifndef GL_ARB_texture_rectangle +#define GL_TEXTURE_RECTANGLE_ARB 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_ARB 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_ARB 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_ARB 0x84F8 +#endif + +#ifndef GL_ARB_color_buffer_float +#define GL_RGBA_FLOAT_MODE_ARB 0x8820 +#define GL_CLAMP_VERTEX_COLOR_ARB 0x891A +#define GL_CLAMP_FRAGMENT_COLOR_ARB 0x891B +#define GL_CLAMP_READ_COLOR_ARB 0x891C +#define GL_FIXED_ONLY_ARB 0x891D +#endif + +#ifndef GL_ARB_half_float_pixel +#define GL_HALF_FLOAT_ARB 0x140B +#endif + +#ifndef GL_ARB_texture_float +#define GL_TEXTURE_RED_TYPE_ARB 0x8C10 +#define GL_TEXTURE_GREEN_TYPE_ARB 0x8C11 +#define GL_TEXTURE_BLUE_TYPE_ARB 0x8C12 +#define GL_TEXTURE_ALPHA_TYPE_ARB 0x8C13 +#define GL_TEXTURE_LUMINANCE_TYPE_ARB 0x8C14 +#define GL_TEXTURE_INTENSITY_TYPE_ARB 0x8C15 +#define GL_TEXTURE_DEPTH_TYPE_ARB 0x8C16 +#define GL_UNSIGNED_NORMALIZED_ARB 0x8C17 +#define GL_RGBA32F_ARB 0x8814 +#define GL_RGB32F_ARB 0x8815 +#define GL_ALPHA32F_ARB 0x8816 +#define GL_INTENSITY32F_ARB 0x8817 +#define GL_LUMINANCE32F_ARB 0x8818 +#define GL_LUMINANCE_ALPHA32F_ARB 0x8819 +#define GL_RGBA16F_ARB 0x881A +#define GL_RGB16F_ARB 0x881B +#define GL_ALPHA16F_ARB 0x881C +#define GL_INTENSITY16F_ARB 0x881D +#define GL_LUMINANCE16F_ARB 0x881E +#define GL_LUMINANCE_ALPHA16F_ARB 0x881F +#endif + +#ifndef GL_ARB_pixel_buffer_object +#define GL_PIXEL_PACK_BUFFER_ARB 0x88EB +#define GL_PIXEL_UNPACK_BUFFER_ARB 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING_ARB 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING_ARB 0x88EF +#endif + +#ifndef GL_ARB_depth_buffer_float +#define GL_DEPTH_COMPONENT32F 0x8CAC +#define GL_DEPTH32F_STENCIL8 0x8CAD +#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV 0x8DAD +#endif + +#ifndef GL_ARB_draw_instanced +#endif + +#ifndef GL_ARB_framebuffer_object +#define GL_INVALID_FRAMEBUFFER_OPERATION 0x0506 +#define GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING 0x8210 +#define GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE 0x8211 +#define GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE 0x8212 +#define GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE 0x8213 +#define GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE 0x8214 +#define GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE 0x8215 +#define GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE 0x8216 +#define GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE 0x8217 +#define GL_FRAMEBUFFER_DEFAULT 0x8218 +#define GL_FRAMEBUFFER_UNDEFINED 0x8219 +#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A +#define GL_MAX_RENDERBUFFER_SIZE 0x84E8 +#define GL_DEPTH_STENCIL 0x84F9 +#define GL_UNSIGNED_INT_24_8 0x84FA +#define GL_DEPTH24_STENCIL8 0x88F0 +#define GL_TEXTURE_STENCIL_SIZE 0x88F1 +#define GL_TEXTURE_RED_TYPE 0x8C10 +#define GL_TEXTURE_GREEN_TYPE 0x8C11 +#define GL_TEXTURE_BLUE_TYPE 0x8C12 +#define GL_TEXTURE_ALPHA_TYPE 0x8C13 +#define GL_TEXTURE_DEPTH_TYPE 0x8C16 +#define GL_UNSIGNED_NORMALIZED 0x8C17 +#define GL_FRAMEBUFFER_BINDING 0x8CA6 +#define GL_DRAW_FRAMEBUFFER_BINDING GL_FRAMEBUFFER_BINDING +#define GL_RENDERBUFFER_BINDING 0x8CA7 +#define GL_READ_FRAMEBUFFER 0x8CA8 +#define GL_DRAW_FRAMEBUFFER 0x8CA9 +#define GL_READ_FRAMEBUFFER_BINDING 0x8CAA +#define GL_RENDERBUFFER_SAMPLES 0x8CAB +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE 0x8CD3 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER 0x8CD4 +#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD +#define GL_MAX_COLOR_ATTACHMENTS 0x8CDF +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#define GL_COLOR_ATTACHMENT1 0x8CE1 +#define GL_COLOR_ATTACHMENT2 0x8CE2 +#define GL_COLOR_ATTACHMENT3 0x8CE3 +#define GL_COLOR_ATTACHMENT4 0x8CE4 +#define GL_COLOR_ATTACHMENT5 0x8CE5 +#define GL_COLOR_ATTACHMENT6 0x8CE6 +#define GL_COLOR_ATTACHMENT7 0x8CE7 +#define GL_COLOR_ATTACHMENT8 0x8CE8 +#define GL_COLOR_ATTACHMENT9 0x8CE9 +#define GL_COLOR_ATTACHMENT10 0x8CEA +#define GL_COLOR_ATTACHMENT11 0x8CEB +#define GL_COLOR_ATTACHMENT12 0x8CEC +#define GL_COLOR_ATTACHMENT13 0x8CED +#define GL_COLOR_ATTACHMENT14 0x8CEE +#define GL_COLOR_ATTACHMENT15 0x8CEF +#define GL_DEPTH_ATTACHMENT 0x8D00 +#define GL_STENCIL_ATTACHMENT 0x8D20 +#define GL_FRAMEBUFFER 0x8D40 +#define GL_RENDERBUFFER 0x8D41 +#define GL_RENDERBUFFER_WIDTH 0x8D42 +#define GL_RENDERBUFFER_HEIGHT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT 0x8D44 +#define GL_STENCIL_INDEX1 0x8D46 +#define GL_STENCIL_INDEX4 0x8D47 +#define GL_STENCIL_INDEX8 0x8D48 +#define GL_STENCIL_INDEX16 0x8D49 +#define GL_RENDERBUFFER_RED_SIZE 0x8D50 +#define GL_RENDERBUFFER_GREEN_SIZE 0x8D51 +#define GL_RENDERBUFFER_BLUE_SIZE 0x8D52 +#define GL_RENDERBUFFER_ALPHA_SIZE 0x8D53 +#define GL_RENDERBUFFER_DEPTH_SIZE 0x8D54 +#define GL_RENDERBUFFER_STENCIL_SIZE 0x8D55 +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56 +#define GL_MAX_SAMPLES 0x8D57 +#endif + +#ifndef GL_ARB_framebuffer_object_DEPRECATED +#define GL_INDEX 0x8222 +#define GL_TEXTURE_LUMINANCE_TYPE 0x8C14 +#define GL_TEXTURE_INTENSITY_TYPE 0x8C15 +#endif + +#ifndef GL_ARB_framebuffer_sRGB +#define GL_FRAMEBUFFER_SRGB 0x8DB9 +#endif + +#ifndef GL_ARB_geometry_shader4 +#define GL_LINES_ADJACENCY_ARB 0x000A +#define GL_LINE_STRIP_ADJACENCY_ARB 0x000B +#define GL_TRIANGLES_ADJACENCY_ARB 0x000C +#define GL_TRIANGLE_STRIP_ADJACENCY_ARB 0x000D +#define GL_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_ARB 0x8C29 +#define GL_FRAMEBUFFER_ATTACHMENT_LAYERED_ARB 0x8DA7 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_ARB 0x8DA8 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_COUNT_ARB 0x8DA9 +#define GL_GEOMETRY_SHADER_ARB 0x8DD9 +#define GL_GEOMETRY_VERTICES_OUT_ARB 0x8DDA +#define GL_GEOMETRY_INPUT_TYPE_ARB 0x8DDB +#define GL_GEOMETRY_OUTPUT_TYPE_ARB 0x8DDC +#define GL_MAX_GEOMETRY_VARYING_COMPONENTS_ARB 0x8DDD +#define GL_MAX_VERTEX_VARYING_COMPONENTS_ARB 0x8DDE +#define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS_ARB 0x8DDF +#define GL_MAX_GEOMETRY_OUTPUT_VERTICES_ARB 0x8DE0 +#define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_ARB 0x8DE1 +/* reuse GL_MAX_VARYING_COMPONENTS */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER */ +#endif + +#ifndef GL_ARB_half_float_vertex +#define GL_HALF_FLOAT 0x140B +#endif + +#ifndef GL_ARB_instanced_arrays +#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR_ARB 0x88FE +#endif + +#ifndef GL_ARB_map_buffer_range +#define GL_MAP_READ_BIT 0x0001 +#define GL_MAP_WRITE_BIT 0x0002 +#define GL_MAP_INVALIDATE_RANGE_BIT 0x0004 +#define GL_MAP_INVALIDATE_BUFFER_BIT 0x0008 +#define GL_MAP_FLUSH_EXPLICIT_BIT 0x0010 +#define GL_MAP_UNSYNCHRONIZED_BIT 0x0020 +#endif + +#ifndef GL_ARB_texture_buffer_object +#define GL_TEXTURE_BUFFER_ARB 0x8C2A +#define GL_MAX_TEXTURE_BUFFER_SIZE_ARB 0x8C2B +#define GL_TEXTURE_BINDING_BUFFER_ARB 0x8C2C +#define GL_TEXTURE_BUFFER_DATA_STORE_BINDING_ARB 0x8C2D +#define GL_TEXTURE_BUFFER_FORMAT_ARB 0x8C2E +#endif + +#ifndef GL_ARB_texture_compression_rgtc +#define GL_COMPRESSED_RED_RGTC1 0x8DBB +#define GL_COMPRESSED_SIGNED_RED_RGTC1 0x8DBC +#define GL_COMPRESSED_RG_RGTC2 0x8DBD +#define GL_COMPRESSED_SIGNED_RG_RGTC2 0x8DBE +#endif + +#ifndef GL_ARB_texture_rg +#define GL_RG 0x8227 +#define GL_RG_INTEGER 0x8228 +#define GL_R8 0x8229 +#define GL_R16 0x822A +#define GL_RG8 0x822B +#define GL_RG16 0x822C +#define GL_R16F 0x822D +#define GL_R32F 0x822E +#define GL_RG16F 0x822F +#define GL_RG32F 0x8230 +#define GL_R8I 0x8231 +#define GL_R8UI 0x8232 +#define GL_R16I 0x8233 +#define GL_R16UI 0x8234 +#define GL_R32I 0x8235 +#define GL_R32UI 0x8236 +#define GL_RG8I 0x8237 +#define GL_RG8UI 0x8238 +#define GL_RG16I 0x8239 +#define GL_RG16UI 0x823A +#define GL_RG32I 0x823B +#define GL_RG32UI 0x823C +#endif + +#ifndef GL_ARB_vertex_array_object +#define GL_VERTEX_ARRAY_BINDING 0x85B5 +#endif + +#ifndef GL_ARB_uniform_buffer_object +#define GL_UNIFORM_BUFFER 0x8A11 +#define GL_UNIFORM_BUFFER_BINDING 0x8A28 +#define GL_UNIFORM_BUFFER_START 0x8A29 +#define GL_UNIFORM_BUFFER_SIZE 0x8A2A +#define GL_MAX_VERTEX_UNIFORM_BLOCKS 0x8A2B +#define GL_MAX_GEOMETRY_UNIFORM_BLOCKS 0x8A2C +#define GL_MAX_FRAGMENT_UNIFORM_BLOCKS 0x8A2D +#define GL_MAX_COMBINED_UNIFORM_BLOCKS 0x8A2E +#define GL_MAX_UNIFORM_BUFFER_BINDINGS 0x8A2F +#define GL_MAX_UNIFORM_BLOCK_SIZE 0x8A30 +#define GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS 0x8A31 +#define GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS 0x8A32 +#define GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS 0x8A33 +#define GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT 0x8A34 +#define GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH 0x8A35 +#define GL_ACTIVE_UNIFORM_BLOCKS 0x8A36 +#define GL_UNIFORM_TYPE 0x8A37 +#define GL_UNIFORM_SIZE 0x8A38 +#define GL_UNIFORM_NAME_LENGTH 0x8A39 +#define GL_UNIFORM_BLOCK_INDEX 0x8A3A +#define GL_UNIFORM_OFFSET 0x8A3B +#define GL_UNIFORM_ARRAY_STRIDE 0x8A3C +#define GL_UNIFORM_MATRIX_STRIDE 0x8A3D +#define GL_UNIFORM_IS_ROW_MAJOR 0x8A3E +#define GL_UNIFORM_BLOCK_BINDING 0x8A3F +#define GL_UNIFORM_BLOCK_DATA_SIZE 0x8A40 +#define GL_UNIFORM_BLOCK_NAME_LENGTH 0x8A41 +#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS 0x8A42 +#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES 0x8A43 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER 0x8A44 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_GEOMETRY_SHADER 0x8A45 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER 0x8A46 +#define GL_INVALID_INDEX 0xFFFFFFFFu +#endif + +#ifndef GL_ARB_compatibility +/* ARB_compatibility just defines tokens from core 3.0 */ +#endif + +#ifndef GL_ARB_copy_buffer +#define GL_COPY_READ_BUFFER 0x8F36 +#define GL_COPY_WRITE_BUFFER 0x8F37 +#endif + +#ifndef GL_ARB_shader_texture_lod +#endif + +#ifndef GL_ARB_depth_clamp +#define GL_DEPTH_CLAMP 0x864F +#endif + +#ifndef GL_ARB_draw_elements_base_vertex +#endif + +#ifndef GL_ARB_fragment_coord_conventions +#endif + +#ifndef GL_ARB_provoking_vertex +#define GL_QUADS_FOLLOW_PROVOKING_VERTEX_CONVENTION 0x8E4C +#define GL_FIRST_VERTEX_CONVENTION 0x8E4D +#define GL_LAST_VERTEX_CONVENTION 0x8E4E +#define GL_PROVOKING_VERTEX 0x8E4F +#endif + +#ifndef GL_ARB_seamless_cube_map +#define GL_TEXTURE_CUBE_MAP_SEAMLESS 0x884F +#endif + +#ifndef GL_ARB_sync +#define GL_MAX_SERVER_WAIT_TIMEOUT 0x9111 +#define GL_OBJECT_TYPE 0x9112 +#define GL_SYNC_CONDITION 0x9113 +#define GL_SYNC_STATUS 0x9114 +#define GL_SYNC_FLAGS 0x9115 +#define GL_SYNC_FENCE 0x9116 +#define GL_SYNC_GPU_COMMANDS_COMPLETE 0x9117 +#define GL_UNSIGNALED 0x9118 +#define GL_SIGNALED 0x9119 +#define GL_ALREADY_SIGNALED 0x911A +#define GL_TIMEOUT_EXPIRED 0x911B +#define GL_CONDITION_SATISFIED 0x911C +#define GL_WAIT_FAILED 0x911D +#define GL_SYNC_FLUSH_COMMANDS_BIT 0x00000001 +#define GL_TIMEOUT_IGNORED 0xFFFFFFFFFFFFFFFFull +#endif + +#ifndef GL_ARB_texture_multisample +#define GL_SAMPLE_POSITION 0x8E50 +#define GL_SAMPLE_MASK 0x8E51 +#define GL_SAMPLE_MASK_VALUE 0x8E52 +#define GL_MAX_SAMPLE_MASK_WORDS 0x8E59 +#define GL_TEXTURE_2D_MULTISAMPLE 0x9100 +#define GL_PROXY_TEXTURE_2D_MULTISAMPLE 0x9101 +#define GL_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9102 +#define GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9103 +#define GL_TEXTURE_BINDING_2D_MULTISAMPLE 0x9104 +#define GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY 0x9105 +#define GL_TEXTURE_SAMPLES 0x9106 +#define GL_TEXTURE_FIXED_SAMPLE_LOCATIONS 0x9107 +#define GL_SAMPLER_2D_MULTISAMPLE 0x9108 +#define GL_INT_SAMPLER_2D_MULTISAMPLE 0x9109 +#define GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE 0x910A +#define GL_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910B +#define GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910C +#define GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910D +#define GL_MAX_COLOR_TEXTURE_SAMPLES 0x910E +#define GL_MAX_DEPTH_TEXTURE_SAMPLES 0x910F +#define GL_MAX_INTEGER_SAMPLES 0x9110 +#endif + +#ifndef GL_ARB_vertex_array_bgra +/* reuse GL_BGRA */ +#endif + +#ifndef GL_ARB_draw_buffers_blend +#endif + +#ifndef GL_ARB_sample_shading +#define GL_SAMPLE_SHADING_ARB 0x8C36 +#define GL_MIN_SAMPLE_SHADING_VALUE_ARB 0x8C37 +#endif + +#ifndef GL_ARB_texture_cube_map_array +#define GL_TEXTURE_CUBE_MAP_ARRAY_ARB 0x9009 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARRAY_ARB 0x900A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARRAY_ARB 0x900B +#define GL_SAMPLER_CUBE_MAP_ARRAY_ARB 0x900C +#define GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW_ARB 0x900D +#define GL_INT_SAMPLER_CUBE_MAP_ARRAY_ARB 0x900E +#define GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY_ARB 0x900F +#endif + +#ifndef GL_ARB_texture_gather +#define GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET_ARB 0x8E5E +#define GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET_ARB 0x8E5F +#endif + +#ifndef GL_ARB_texture_query_lod +#endif + +#ifndef GL_ARB_shading_language_include +#define GL_SHADER_INCLUDE_ARB 0x8DAE +#define GL_NAMED_STRING_LENGTH_ARB 0x8DE9 +#define GL_NAMED_STRING_TYPE_ARB 0x8DEA +#endif + +#ifndef GL_ARB_texture_compression_bptc +#define GL_COMPRESSED_RGBA_BPTC_UNORM_ARB 0x8E8C +#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB 0x8E8D +#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB 0x8E8E +#define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB 0x8E8F +#endif + +#ifndef GL_ARB_blend_func_extended +#define GL_SRC1_COLOR 0x88F9 +/* reuse GL_SRC1_ALPHA */ +#define GL_ONE_MINUS_SRC1_COLOR 0x88FA +#define GL_ONE_MINUS_SRC1_ALPHA 0x88FB +#define GL_MAX_DUAL_SOURCE_DRAW_BUFFERS 0x88FC +#endif + +#ifndef GL_ARB_explicit_attrib_location +#endif + +#ifndef GL_ARB_occlusion_query2 +#define GL_ANY_SAMPLES_PASSED 0x8C2F +#endif + +#ifndef GL_ARB_sampler_objects +#define GL_SAMPLER_BINDING 0x8919 +#endif + +#ifndef GL_ARB_shader_bit_encoding +#endif + +#ifndef GL_ARB_texture_rgb10_a2ui +#define GL_RGB10_A2UI 0x906F +#endif + +#ifndef GL_ARB_texture_swizzle +#define GL_TEXTURE_SWIZZLE_R 0x8E42 +#define GL_TEXTURE_SWIZZLE_G 0x8E43 +#define GL_TEXTURE_SWIZZLE_B 0x8E44 +#define GL_TEXTURE_SWIZZLE_A 0x8E45 +#define GL_TEXTURE_SWIZZLE_RGBA 0x8E46 +#endif + +#ifndef GL_ARB_timer_query +#define GL_TIME_ELAPSED 0x88BF +#define GL_TIMESTAMP 0x8E28 +#endif + +#ifndef GL_ARB_vertex_type_2_10_10_10_rev +/* reuse GL_UNSIGNED_INT_2_10_10_10_REV */ +#define GL_INT_2_10_10_10_REV 0x8D9F +#endif + +#ifndef GL_ARB_draw_indirect +#define GL_DRAW_INDIRECT_BUFFER 0x8F3F +#define GL_DRAW_INDIRECT_BUFFER_BINDING 0x8F43 +#endif + +#ifndef GL_ARB_gpu_shader5 +#define GL_GEOMETRY_SHADER_INVOCATIONS 0x887F +#define GL_MAX_GEOMETRY_SHADER_INVOCATIONS 0x8E5A +#define GL_MIN_FRAGMENT_INTERPOLATION_OFFSET 0x8E5B +#define GL_MAX_FRAGMENT_INTERPOLATION_OFFSET 0x8E5C +#define GL_FRAGMENT_INTERPOLATION_OFFSET_BITS 0x8E5D +/* reuse GL_MAX_VERTEX_STREAMS */ +#endif + +#ifndef GL_ARB_gpu_shader_fp64 +/* reuse GL_DOUBLE */ +#define GL_DOUBLE_VEC2 0x8FFC +#define GL_DOUBLE_VEC3 0x8FFD +#define GL_DOUBLE_VEC4 0x8FFE +#define GL_DOUBLE_MAT2 0x8F46 +#define GL_DOUBLE_MAT3 0x8F47 +#define GL_DOUBLE_MAT4 0x8F48 +#define GL_DOUBLE_MAT2x3 0x8F49 +#define GL_DOUBLE_MAT2x4 0x8F4A +#define GL_DOUBLE_MAT3x2 0x8F4B +#define GL_DOUBLE_MAT3x4 0x8F4C +#define GL_DOUBLE_MAT4x2 0x8F4D +#define GL_DOUBLE_MAT4x3 0x8F4E +#endif + +#ifndef GL_ARB_shader_subroutine +#define GL_ACTIVE_SUBROUTINES 0x8DE5 +#define GL_ACTIVE_SUBROUTINE_UNIFORMS 0x8DE6 +#define GL_ACTIVE_SUBROUTINE_UNIFORM_LOCATIONS 0x8E47 +#define GL_ACTIVE_SUBROUTINE_MAX_LENGTH 0x8E48 +#define GL_ACTIVE_SUBROUTINE_UNIFORM_MAX_LENGTH 0x8E49 +#define GL_MAX_SUBROUTINES 0x8DE7 +#define GL_MAX_SUBROUTINE_UNIFORM_LOCATIONS 0x8DE8 +#define GL_NUM_COMPATIBLE_SUBROUTINES 0x8E4A +#define GL_COMPATIBLE_SUBROUTINES 0x8E4B +/* reuse GL_UNIFORM_SIZE */ +/* reuse GL_UNIFORM_NAME_LENGTH */ +#endif + +#ifndef GL_ARB_tessellation_shader +#define GL_PATCHES 0x000E +#define GL_PATCH_VERTICES 0x8E72 +#define GL_PATCH_DEFAULT_INNER_LEVEL 0x8E73 +#define GL_PATCH_DEFAULT_OUTER_LEVEL 0x8E74 +#define GL_TESS_CONTROL_OUTPUT_VERTICES 0x8E75 +#define GL_TESS_GEN_MODE 0x8E76 +#define GL_TESS_GEN_SPACING 0x8E77 +#define GL_TESS_GEN_VERTEX_ORDER 0x8E78 +#define GL_TESS_GEN_POINT_MODE 0x8E79 +/* reuse GL_TRIANGLES */ +/* reuse GL_QUADS */ +#define GL_ISOLINES 0x8E7A +/* reuse GL_EQUAL */ +#define GL_FRACTIONAL_ODD 0x8E7B +#define GL_FRACTIONAL_EVEN 0x8E7C +/* reuse GL_CCW */ +/* reuse GL_CW */ +#define GL_MAX_PATCH_VERTICES 0x8E7D +#define GL_MAX_TESS_GEN_LEVEL 0x8E7E +#define GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS 0x8E7F +#define GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS 0x8E80 +#define GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS 0x8E81 +#define GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS 0x8E82 +#define GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS 0x8E83 +#define GL_MAX_TESS_PATCH_COMPONENTS 0x8E84 +#define GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS 0x8E85 +#define GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS 0x8E86 +#define GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS 0x8E89 +#define GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS 0x8E8A +#define GL_MAX_TESS_CONTROL_INPUT_COMPONENTS 0x886C +#define GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS 0x886D +#define GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS 0x8E1E +#define GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS 0x8E1F +#define GL_UNIFORM_BLOCK_REFERENCED_BY_TESS_CONTROL_SHADER 0x84F0 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_TESS_EVALUATION_SHADER 0x84F1 +#define GL_TESS_EVALUATION_SHADER 0x8E87 +#define GL_TESS_CONTROL_SHADER 0x8E88 +#endif + +#ifndef GL_ARB_texture_buffer_object_rgb32 +/* reuse GL_RGB32F */ +/* reuse GL_RGB32UI */ +/* reuse GL_RGB32I */ +#endif + +#ifndef GL_ARB_transform_feedback2 +#define GL_TRANSFORM_FEEDBACK 0x8E22 +#define GL_TRANSFORM_FEEDBACK_BUFFER_PAUSED 0x8E23 +#define GL_TRANSFORM_FEEDBACK_BUFFER_ACTIVE 0x8E24 +#define GL_TRANSFORM_FEEDBACK_BINDING 0x8E25 +#endif + +#ifndef GL_ARB_transform_feedback3 +#define GL_MAX_TRANSFORM_FEEDBACK_BUFFERS 0x8E70 +#define GL_MAX_VERTEX_STREAMS 0x8E71 +#endif + +#ifndef GL_ARB_ES2_compatibility +#define GL_FIXED 0x140C +#define GL_IMPLEMENTATION_COLOR_READ_TYPE 0x8B9A +#define GL_IMPLEMENTATION_COLOR_READ_FORMAT 0x8B9B +#define GL_LOW_FLOAT 0x8DF0 +#define GL_MEDIUM_FLOAT 0x8DF1 +#define GL_HIGH_FLOAT 0x8DF2 +#define GL_LOW_INT 0x8DF3 +#define GL_MEDIUM_INT 0x8DF4 +#define GL_HIGH_INT 0x8DF5 +#define GL_SHADER_COMPILER 0x8DFA +#define GL_NUM_SHADER_BINARY_FORMATS 0x8DF9 +#define GL_MAX_VERTEX_UNIFORM_VECTORS 0x8DFB +#define GL_MAX_VARYING_VECTORS 0x8DFC +#define GL_MAX_FRAGMENT_UNIFORM_VECTORS 0x8DFD +#endif + +#ifndef GL_ARB_get_program_binary +#define GL_PROGRAM_BINARY_RETRIEVABLE_HINT 0x8257 +#define GL_PROGRAM_BINARY_LENGTH 0x8741 +#define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE +#define GL_PROGRAM_BINARY_FORMATS 0x87FF +#endif + +#ifndef GL_ARB_separate_shader_objects +#define GL_VERTEX_SHADER_BIT 0x00000001 +#define GL_FRAGMENT_SHADER_BIT 0x00000002 +#define GL_GEOMETRY_SHADER_BIT 0x00000004 +#define GL_TESS_CONTROL_SHADER_BIT 0x00000008 +#define GL_TESS_EVALUATION_SHADER_BIT 0x00000010 +#define GL_ALL_SHADER_BITS 0xFFFFFFFF +#define GL_PROGRAM_SEPARABLE 0x8258 +#define GL_ACTIVE_PROGRAM 0x8259 +#define GL_PROGRAM_PIPELINE_BINDING 0x825A +#endif + +#ifndef GL_ARB_shader_precision +#endif + +#ifndef GL_ARB_vertex_attrib_64bit +/* reuse GL_RGB32I */ +/* reuse GL_DOUBLE_VEC2 */ +/* reuse GL_DOUBLE_VEC3 */ +/* reuse GL_DOUBLE_VEC4 */ +/* reuse GL_DOUBLE_MAT2 */ +/* reuse GL_DOUBLE_MAT3 */ +/* reuse GL_DOUBLE_MAT4 */ +/* reuse GL_DOUBLE_MAT2x3 */ +/* reuse GL_DOUBLE_MAT2x4 */ +/* reuse GL_DOUBLE_MAT3x2 */ +/* reuse GL_DOUBLE_MAT3x4 */ +/* reuse GL_DOUBLE_MAT4x2 */ +/* reuse GL_DOUBLE_MAT4x3 */ +#endif + +#ifndef GL_ARB_viewport_array +/* reuse GL_SCISSOR_BOX */ +/* reuse GL_VIEWPORT */ +/* reuse GL_DEPTH_RANGE */ +/* reuse GL_SCISSOR_TEST */ +#define GL_MAX_VIEWPORTS 0x825B +#define GL_VIEWPORT_SUBPIXEL_BITS 0x825C +#define GL_VIEWPORT_BOUNDS_RANGE 0x825D +#define GL_LAYER_PROVOKING_VERTEX 0x825E +#define GL_VIEWPORT_INDEX_PROVOKING_VERTEX 0x825F +#define GL_UNDEFINED_VERTEX 0x8260 +/* reuse GL_FIRST_VERTEX_CONVENTION */ +/* reuse GL_LAST_VERTEX_CONVENTION */ +/* reuse GL_PROVOKING_VERTEX */ +#endif + +#ifndef GL_ARB_cl_event +#define GL_SYNC_CL_EVENT_ARB 0x8240 +#define GL_SYNC_CL_EVENT_COMPLETE_ARB 0x8241 +#endif + +#ifndef GL_ARB_debug_output +#define GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB 0x8242 +#define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_ARB 0x8243 +#define GL_DEBUG_CALLBACK_FUNCTION_ARB 0x8244 +#define GL_DEBUG_CALLBACK_USER_PARAM_ARB 0x8245 +#define GL_DEBUG_SOURCE_API_ARB 0x8246 +#define GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB 0x8247 +#define GL_DEBUG_SOURCE_SHADER_COMPILER_ARB 0x8248 +#define GL_DEBUG_SOURCE_THIRD_PARTY_ARB 0x8249 +#define GL_DEBUG_SOURCE_APPLICATION_ARB 0x824A +#define GL_DEBUG_SOURCE_OTHER_ARB 0x824B +#define GL_DEBUG_TYPE_ERROR_ARB 0x824C +#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB 0x824D +#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB 0x824E +#define GL_DEBUG_TYPE_PORTABILITY_ARB 0x824F +#define GL_DEBUG_TYPE_PERFORMANCE_ARB 0x8250 +#define GL_DEBUG_TYPE_OTHER_ARB 0x8251 +#define GL_MAX_DEBUG_MESSAGE_LENGTH_ARB 0x9143 +#define GL_MAX_DEBUG_LOGGED_MESSAGES_ARB 0x9144 +#define GL_DEBUG_LOGGED_MESSAGES_ARB 0x9145 +#define GL_DEBUG_SEVERITY_HIGH_ARB 0x9146 +#define GL_DEBUG_SEVERITY_MEDIUM_ARB 0x9147 +#define GL_DEBUG_SEVERITY_LOW_ARB 0x9148 +#endif + +#ifndef GL_ARB_robustness +/* reuse GL_NO_ERROR */ +#define GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT_ARB 0x00000004 +#define GL_LOSE_CONTEXT_ON_RESET_ARB 0x8252 +#define GL_GUILTY_CONTEXT_RESET_ARB 0x8253 +#define GL_INNOCENT_CONTEXT_RESET_ARB 0x8254 +#define GL_UNKNOWN_CONTEXT_RESET_ARB 0x8255 +#define GL_RESET_NOTIFICATION_STRATEGY_ARB 0x8256 +#define GL_NO_RESET_NOTIFICATION_ARB 0x8261 +#endif + +#ifndef GL_ARB_shader_stencil_export +#endif + +#ifndef GL_EXT_abgr +#define GL_ABGR_EXT 0x8000 +#endif + +#ifndef GL_EXT_blend_color +#define GL_CONSTANT_COLOR_EXT 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR_EXT 0x8002 +#define GL_CONSTANT_ALPHA_EXT 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA_EXT 0x8004 +#define GL_BLEND_COLOR_EXT 0x8005 +#endif + +#ifndef GL_EXT_polygon_offset +#define GL_POLYGON_OFFSET_EXT 0x8037 +#define GL_POLYGON_OFFSET_FACTOR_EXT 0x8038 +#define GL_POLYGON_OFFSET_BIAS_EXT 0x8039 +#endif + +#ifndef GL_EXT_texture +#define GL_ALPHA4_EXT 0x803B +#define GL_ALPHA8_EXT 0x803C +#define GL_ALPHA12_EXT 0x803D +#define GL_ALPHA16_EXT 0x803E +#define GL_LUMINANCE4_EXT 0x803F +#define GL_LUMINANCE8_EXT 0x8040 +#define GL_LUMINANCE12_EXT 0x8041 +#define GL_LUMINANCE16_EXT 0x8042 +#define GL_LUMINANCE4_ALPHA4_EXT 0x8043 +#define GL_LUMINANCE6_ALPHA2_EXT 0x8044 +#define GL_LUMINANCE8_ALPHA8_EXT 0x8045 +#define GL_LUMINANCE12_ALPHA4_EXT 0x8046 +#define GL_LUMINANCE12_ALPHA12_EXT 0x8047 +#define GL_LUMINANCE16_ALPHA16_EXT 0x8048 +#define GL_INTENSITY_EXT 0x8049 +#define GL_INTENSITY4_EXT 0x804A +#define GL_INTENSITY8_EXT 0x804B +#define GL_INTENSITY12_EXT 0x804C +#define GL_INTENSITY16_EXT 0x804D +#define GL_RGB2_EXT 0x804E +#define GL_RGB4_EXT 0x804F +#define GL_RGB5_EXT 0x8050 +#define GL_RGB8_EXT 0x8051 +#define GL_RGB10_EXT 0x8052 +#define GL_RGB12_EXT 0x8053 +#define GL_RGB16_EXT 0x8054 +#define GL_RGBA2_EXT 0x8055 +#define GL_RGBA4_EXT 0x8056 +#define GL_RGB5_A1_EXT 0x8057 +#define GL_RGBA8_EXT 0x8058 +#define GL_RGB10_A2_EXT 0x8059 +#define GL_RGBA12_EXT 0x805A +#define GL_RGBA16_EXT 0x805B +#define GL_TEXTURE_RED_SIZE_EXT 0x805C +#define GL_TEXTURE_GREEN_SIZE_EXT 0x805D +#define GL_TEXTURE_BLUE_SIZE_EXT 0x805E +#define GL_TEXTURE_ALPHA_SIZE_EXT 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE_EXT 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE_EXT 0x8061 +#define GL_REPLACE_EXT 0x8062 +#define GL_PROXY_TEXTURE_1D_EXT 0x8063 +#define GL_PROXY_TEXTURE_2D_EXT 0x8064 +#define GL_TEXTURE_TOO_LARGE_EXT 0x8065 +#endif + +#ifndef GL_EXT_texture3D +#define GL_PACK_SKIP_IMAGES_EXT 0x806B +#define GL_PACK_IMAGE_HEIGHT_EXT 0x806C +#define GL_UNPACK_SKIP_IMAGES_EXT 0x806D +#define GL_UNPACK_IMAGE_HEIGHT_EXT 0x806E +#define GL_TEXTURE_3D_EXT 0x806F +#define GL_PROXY_TEXTURE_3D_EXT 0x8070 +#define GL_TEXTURE_DEPTH_EXT 0x8071 +#define GL_TEXTURE_WRAP_R_EXT 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE_EXT 0x8073 +#endif + +#ifndef GL_SGIS_texture_filter4 +#define GL_FILTER4_SGIS 0x8146 +#define GL_TEXTURE_FILTER4_SIZE_SGIS 0x8147 +#endif + +#ifndef GL_EXT_subtexture +#endif + +#ifndef GL_EXT_copy_texture +#endif + +#ifndef GL_EXT_histogram +#define GL_HISTOGRAM_EXT 0x8024 +#define GL_PROXY_HISTOGRAM_EXT 0x8025 +#define GL_HISTOGRAM_WIDTH_EXT 0x8026 +#define GL_HISTOGRAM_FORMAT_EXT 0x8027 +#define GL_HISTOGRAM_RED_SIZE_EXT 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE_EXT 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE_EXT 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE_EXT 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE_EXT 0x802C +#define GL_HISTOGRAM_SINK_EXT 0x802D +#define GL_MINMAX_EXT 0x802E +#define GL_MINMAX_FORMAT_EXT 0x802F +#define GL_MINMAX_SINK_EXT 0x8030 +#define GL_TABLE_TOO_LARGE_EXT 0x8031 +#endif + +#ifndef GL_EXT_convolution +#define GL_CONVOLUTION_1D_EXT 0x8010 +#define GL_CONVOLUTION_2D_EXT 0x8011 +#define GL_SEPARABLE_2D_EXT 0x8012 +#define GL_CONVOLUTION_BORDER_MODE_EXT 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE_EXT 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS_EXT 0x8015 +#define GL_REDUCE_EXT 0x8016 +#define GL_CONVOLUTION_FORMAT_EXT 0x8017 +#define GL_CONVOLUTION_WIDTH_EXT 0x8018 +#define GL_CONVOLUTION_HEIGHT_EXT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH_EXT 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT_EXT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE_EXT 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE_EXT 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE_EXT 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE_EXT 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS_EXT 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS_EXT 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS_EXT 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS_EXT 0x8023 +#endif + +#ifndef GL_SGI_color_matrix +#define GL_COLOR_MATRIX_SGI 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE_SGI 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE_SGI 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE_SGI 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE_SGI 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS_SGI 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS_SGI 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS_SGI 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS_SGI 0x80BB +#endif + +#ifndef GL_SGI_color_table +#define GL_COLOR_TABLE_SGI 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D2 +#define GL_PROXY_COLOR_TABLE_SGI 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D5 +#define GL_COLOR_TABLE_SCALE_SGI 0x80D6 +#define GL_COLOR_TABLE_BIAS_SGI 0x80D7 +#define GL_COLOR_TABLE_FORMAT_SGI 0x80D8 +#define GL_COLOR_TABLE_WIDTH_SGI 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE_SGI 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE_SGI 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE_SGI 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE_SGI 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE_SGI 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE_SGI 0x80DF +#endif + +#ifndef GL_SGIS_pixel_texture +#define GL_PIXEL_TEXTURE_SGIS 0x8353 +#define GL_PIXEL_FRAGMENT_RGB_SOURCE_SGIS 0x8354 +#define GL_PIXEL_FRAGMENT_ALPHA_SOURCE_SGIS 0x8355 +#define GL_PIXEL_GROUP_COLOR_SGIS 0x8356 +#endif + +#ifndef GL_SGIX_pixel_texture +#define GL_PIXEL_TEX_GEN_SGIX 0x8139 +#define GL_PIXEL_TEX_GEN_MODE_SGIX 0x832B +#endif + +#ifndef GL_SGIS_texture4D +#define GL_PACK_SKIP_VOLUMES_SGIS 0x8130 +#define GL_PACK_IMAGE_DEPTH_SGIS 0x8131 +#define GL_UNPACK_SKIP_VOLUMES_SGIS 0x8132 +#define GL_UNPACK_IMAGE_DEPTH_SGIS 0x8133 +#define GL_TEXTURE_4D_SGIS 0x8134 +#define GL_PROXY_TEXTURE_4D_SGIS 0x8135 +#define GL_TEXTURE_4DSIZE_SGIS 0x8136 +#define GL_TEXTURE_WRAP_Q_SGIS 0x8137 +#define GL_MAX_4D_TEXTURE_SIZE_SGIS 0x8138 +#define GL_TEXTURE_4D_BINDING_SGIS 0x814F +#endif + +#ifndef GL_SGI_texture_color_table +#define GL_TEXTURE_COLOR_TABLE_SGI 0x80BC +#define GL_PROXY_TEXTURE_COLOR_TABLE_SGI 0x80BD +#endif + +#ifndef GL_EXT_cmyka +#define GL_CMYK_EXT 0x800C +#define GL_CMYKA_EXT 0x800D +#define GL_PACK_CMYK_HINT_EXT 0x800E +#define GL_UNPACK_CMYK_HINT_EXT 0x800F +#endif + +#ifndef GL_EXT_texture_object +#define GL_TEXTURE_PRIORITY_EXT 0x8066 +#define GL_TEXTURE_RESIDENT_EXT 0x8067 +#define GL_TEXTURE_1D_BINDING_EXT 0x8068 +#define GL_TEXTURE_2D_BINDING_EXT 0x8069 +#define GL_TEXTURE_3D_BINDING_EXT 0x806A +#endif + +#ifndef GL_SGIS_detail_texture +#define GL_DETAIL_TEXTURE_2D_SGIS 0x8095 +#define GL_DETAIL_TEXTURE_2D_BINDING_SGIS 0x8096 +#define GL_LINEAR_DETAIL_SGIS 0x8097 +#define GL_LINEAR_DETAIL_ALPHA_SGIS 0x8098 +#define GL_LINEAR_DETAIL_COLOR_SGIS 0x8099 +#define GL_DETAIL_TEXTURE_LEVEL_SGIS 0x809A +#define GL_DETAIL_TEXTURE_MODE_SGIS 0x809B +#define GL_DETAIL_TEXTURE_FUNC_POINTS_SGIS 0x809C +#endif + +#ifndef GL_SGIS_sharpen_texture +#define GL_LINEAR_SHARPEN_SGIS 0x80AD +#define GL_LINEAR_SHARPEN_ALPHA_SGIS 0x80AE +#define GL_LINEAR_SHARPEN_COLOR_SGIS 0x80AF +#define GL_SHARPEN_TEXTURE_FUNC_POINTS_SGIS 0x80B0 +#endif + +#ifndef GL_EXT_packed_pixels +#define GL_UNSIGNED_BYTE_3_3_2_EXT 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4_EXT 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1_EXT 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8_EXT 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2_EXT 0x8036 +#endif + +#ifndef GL_SGIS_texture_lod +#define GL_TEXTURE_MIN_LOD_SGIS 0x813A +#define GL_TEXTURE_MAX_LOD_SGIS 0x813B +#define GL_TEXTURE_BASE_LEVEL_SGIS 0x813C +#define GL_TEXTURE_MAX_LEVEL_SGIS 0x813D +#endif + +#ifndef GL_SGIS_multisample +#define GL_MULTISAMPLE_SGIS 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_SGIS 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_SGIS 0x809F +#define GL_SAMPLE_MASK_SGIS 0x80A0 +#define GL_1PASS_SGIS 0x80A1 +#define GL_2PASS_0_SGIS 0x80A2 +#define GL_2PASS_1_SGIS 0x80A3 +#define GL_4PASS_0_SGIS 0x80A4 +#define GL_4PASS_1_SGIS 0x80A5 +#define GL_4PASS_2_SGIS 0x80A6 +#define GL_4PASS_3_SGIS 0x80A7 +#define GL_SAMPLE_BUFFERS_SGIS 0x80A8 +#define GL_SAMPLES_SGIS 0x80A9 +#define GL_SAMPLE_MASK_VALUE_SGIS 0x80AA +#define GL_SAMPLE_MASK_INVERT_SGIS 0x80AB +#define GL_SAMPLE_PATTERN_SGIS 0x80AC +#endif + +#ifndef GL_EXT_rescale_normal +#define GL_RESCALE_NORMAL_EXT 0x803A +#endif + +#ifndef GL_EXT_vertex_array +#define GL_VERTEX_ARRAY_EXT 0x8074 +#define GL_NORMAL_ARRAY_EXT 0x8075 +#define GL_COLOR_ARRAY_EXT 0x8076 +#define GL_INDEX_ARRAY_EXT 0x8077 +#define GL_TEXTURE_COORD_ARRAY_EXT 0x8078 +#define GL_EDGE_FLAG_ARRAY_EXT 0x8079 +#define GL_VERTEX_ARRAY_SIZE_EXT 0x807A +#define GL_VERTEX_ARRAY_TYPE_EXT 0x807B +#define GL_VERTEX_ARRAY_STRIDE_EXT 0x807C +#define GL_VERTEX_ARRAY_COUNT_EXT 0x807D +#define GL_NORMAL_ARRAY_TYPE_EXT 0x807E +#define GL_NORMAL_ARRAY_STRIDE_EXT 0x807F +#define GL_NORMAL_ARRAY_COUNT_EXT 0x8080 +#define GL_COLOR_ARRAY_SIZE_EXT 0x8081 +#define GL_COLOR_ARRAY_TYPE_EXT 0x8082 +#define GL_COLOR_ARRAY_STRIDE_EXT 0x8083 +#define GL_COLOR_ARRAY_COUNT_EXT 0x8084 +#define GL_INDEX_ARRAY_TYPE_EXT 0x8085 +#define GL_INDEX_ARRAY_STRIDE_EXT 0x8086 +#define GL_INDEX_ARRAY_COUNT_EXT 0x8087 +#define GL_TEXTURE_COORD_ARRAY_SIZE_EXT 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE_EXT 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE_EXT 0x808A +#define GL_TEXTURE_COORD_ARRAY_COUNT_EXT 0x808B +#define GL_EDGE_FLAG_ARRAY_STRIDE_EXT 0x808C +#define GL_EDGE_FLAG_ARRAY_COUNT_EXT 0x808D +#define GL_VERTEX_ARRAY_POINTER_EXT 0x808E +#define GL_NORMAL_ARRAY_POINTER_EXT 0x808F +#define GL_COLOR_ARRAY_POINTER_EXT 0x8090 +#define GL_INDEX_ARRAY_POINTER_EXT 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER_EXT 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER_EXT 0x8093 +#endif + +#ifndef GL_EXT_misc_attribute +#endif + +#ifndef GL_SGIS_generate_mipmap +#define GL_GENERATE_MIPMAP_SGIS 0x8191 +#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 +#endif + +#ifndef GL_SGIX_clipmap +#define GL_LINEAR_CLIPMAP_LINEAR_SGIX 0x8170 +#define GL_TEXTURE_CLIPMAP_CENTER_SGIX 0x8171 +#define GL_TEXTURE_CLIPMAP_FRAME_SGIX 0x8172 +#define GL_TEXTURE_CLIPMAP_OFFSET_SGIX 0x8173 +#define GL_TEXTURE_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8174 +#define GL_TEXTURE_CLIPMAP_LOD_OFFSET_SGIX 0x8175 +#define GL_TEXTURE_CLIPMAP_DEPTH_SGIX 0x8176 +#define GL_MAX_CLIPMAP_DEPTH_SGIX 0x8177 +#define GL_MAX_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8178 +#define GL_NEAREST_CLIPMAP_NEAREST_SGIX 0x844D +#define GL_NEAREST_CLIPMAP_LINEAR_SGIX 0x844E +#define GL_LINEAR_CLIPMAP_NEAREST_SGIX 0x844F +#endif + +#ifndef GL_SGIX_shadow +#define GL_TEXTURE_COMPARE_SGIX 0x819A +#define GL_TEXTURE_COMPARE_OPERATOR_SGIX 0x819B +#define GL_TEXTURE_LEQUAL_R_SGIX 0x819C +#define GL_TEXTURE_GEQUAL_R_SGIX 0x819D +#endif + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_CLAMP_TO_EDGE_SGIS 0x812F +#endif + +#ifndef GL_SGIS_texture_border_clamp +#define GL_CLAMP_TO_BORDER_SGIS 0x812D +#endif + +#ifndef GL_EXT_blend_minmax +#define GL_FUNC_ADD_EXT 0x8006 +#define GL_MIN_EXT 0x8007 +#define GL_MAX_EXT 0x8008 +#define GL_BLEND_EQUATION_EXT 0x8009 +#endif + +#ifndef GL_EXT_blend_subtract +#define GL_FUNC_SUBTRACT_EXT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT_EXT 0x800B +#endif + +#ifndef GL_EXT_blend_logic_op +#endif + +#ifndef GL_SGIX_interlace +#define GL_INTERLACE_SGIX 0x8094 +#endif + +#ifndef GL_SGIX_pixel_tiles +#define GL_PIXEL_TILE_BEST_ALIGNMENT_SGIX 0x813E +#define GL_PIXEL_TILE_CACHE_INCREMENT_SGIX 0x813F +#define GL_PIXEL_TILE_WIDTH_SGIX 0x8140 +#define GL_PIXEL_TILE_HEIGHT_SGIX 0x8141 +#define GL_PIXEL_TILE_GRID_WIDTH_SGIX 0x8142 +#define GL_PIXEL_TILE_GRID_HEIGHT_SGIX 0x8143 +#define GL_PIXEL_TILE_GRID_DEPTH_SGIX 0x8144 +#define GL_PIXEL_TILE_CACHE_SIZE_SGIX 0x8145 +#endif + +#ifndef GL_SGIS_texture_select +#define GL_DUAL_ALPHA4_SGIS 0x8110 +#define GL_DUAL_ALPHA8_SGIS 0x8111 +#define GL_DUAL_ALPHA12_SGIS 0x8112 +#define GL_DUAL_ALPHA16_SGIS 0x8113 +#define GL_DUAL_LUMINANCE4_SGIS 0x8114 +#define GL_DUAL_LUMINANCE8_SGIS 0x8115 +#define GL_DUAL_LUMINANCE12_SGIS 0x8116 +#define GL_DUAL_LUMINANCE16_SGIS 0x8117 +#define GL_DUAL_INTENSITY4_SGIS 0x8118 +#define GL_DUAL_INTENSITY8_SGIS 0x8119 +#define GL_DUAL_INTENSITY12_SGIS 0x811A +#define GL_DUAL_INTENSITY16_SGIS 0x811B +#define GL_DUAL_LUMINANCE_ALPHA4_SGIS 0x811C +#define GL_DUAL_LUMINANCE_ALPHA8_SGIS 0x811D +#define GL_QUAD_ALPHA4_SGIS 0x811E +#define GL_QUAD_ALPHA8_SGIS 0x811F +#define GL_QUAD_LUMINANCE4_SGIS 0x8120 +#define GL_QUAD_LUMINANCE8_SGIS 0x8121 +#define GL_QUAD_INTENSITY4_SGIS 0x8122 +#define GL_QUAD_INTENSITY8_SGIS 0x8123 +#define GL_DUAL_TEXTURE_SELECT_SGIS 0x8124 +#define GL_QUAD_TEXTURE_SELECT_SGIS 0x8125 +#endif + +#ifndef GL_SGIX_sprite +#define GL_SPRITE_SGIX 0x8148 +#define GL_SPRITE_MODE_SGIX 0x8149 +#define GL_SPRITE_AXIS_SGIX 0x814A +#define GL_SPRITE_TRANSLATION_SGIX 0x814B +#define GL_SPRITE_AXIAL_SGIX 0x814C +#define GL_SPRITE_OBJECT_ALIGNED_SGIX 0x814D +#define GL_SPRITE_EYE_ALIGNED_SGIX 0x814E +#endif + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_TEXTURE_MULTI_BUFFER_HINT_SGIX 0x812E +#endif + +#ifndef GL_EXT_point_parameters +#define GL_POINT_SIZE_MIN_EXT 0x8126 +#define GL_POINT_SIZE_MAX_EXT 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_EXT 0x8128 +#define GL_DISTANCE_ATTENUATION_EXT 0x8129 +#endif + +#ifndef GL_SGIS_point_parameters +#define GL_POINT_SIZE_MIN_SGIS 0x8126 +#define GL_POINT_SIZE_MAX_SGIS 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_SGIS 0x8128 +#define GL_DISTANCE_ATTENUATION_SGIS 0x8129 +#endif + +#ifndef GL_SGIX_instruments +#define GL_INSTRUMENT_BUFFER_POINTER_SGIX 0x8180 +#define GL_INSTRUMENT_MEASUREMENTS_SGIX 0x8181 +#endif + +#ifndef GL_SGIX_texture_scale_bias +#define GL_POST_TEXTURE_FILTER_BIAS_SGIX 0x8179 +#define GL_POST_TEXTURE_FILTER_SCALE_SGIX 0x817A +#define GL_POST_TEXTURE_FILTER_BIAS_RANGE_SGIX 0x817B +#define GL_POST_TEXTURE_FILTER_SCALE_RANGE_SGIX 0x817C +#endif + +#ifndef GL_SGIX_framezoom +#define GL_FRAMEZOOM_SGIX 0x818B +#define GL_FRAMEZOOM_FACTOR_SGIX 0x818C +#define GL_MAX_FRAMEZOOM_FACTOR_SGIX 0x818D +#endif + +#ifndef GL_SGIX_tag_sample_buffer +#endif + +#ifndef GL_FfdMaskSGIX +#define GL_TEXTURE_DEFORMATION_BIT_SGIX 0x00000001 +#define GL_GEOMETRY_DEFORMATION_BIT_SGIX 0x00000002 +#endif + +#ifndef GL_SGIX_polynomial_ffd +#define GL_GEOMETRY_DEFORMATION_SGIX 0x8194 +#define GL_TEXTURE_DEFORMATION_SGIX 0x8195 +#define GL_DEFORMATIONS_MASK_SGIX 0x8196 +#define GL_MAX_DEFORMATION_ORDER_SGIX 0x8197 +#endif + +#ifndef GL_SGIX_reference_plane +#define GL_REFERENCE_PLANE_SGIX 0x817D +#define GL_REFERENCE_PLANE_EQUATION_SGIX 0x817E +#endif + +#ifndef GL_SGIX_flush_raster +#endif + +#ifndef GL_SGIX_depth_texture +#define GL_DEPTH_COMPONENT16_SGIX 0x81A5 +#define GL_DEPTH_COMPONENT24_SGIX 0x81A6 +#define GL_DEPTH_COMPONENT32_SGIX 0x81A7 +#endif + +#ifndef GL_SGIS_fog_function +#define GL_FOG_FUNC_SGIS 0x812A +#define GL_FOG_FUNC_POINTS_SGIS 0x812B +#define GL_MAX_FOG_FUNC_POINTS_SGIS 0x812C +#endif + +#ifndef GL_SGIX_fog_offset +#define GL_FOG_OFFSET_SGIX 0x8198 +#define GL_FOG_OFFSET_VALUE_SGIX 0x8199 +#endif + +#ifndef GL_HP_image_transform +#define GL_IMAGE_SCALE_X_HP 0x8155 +#define GL_IMAGE_SCALE_Y_HP 0x8156 +#define GL_IMAGE_TRANSLATE_X_HP 0x8157 +#define GL_IMAGE_TRANSLATE_Y_HP 0x8158 +#define GL_IMAGE_ROTATE_ANGLE_HP 0x8159 +#define GL_IMAGE_ROTATE_ORIGIN_X_HP 0x815A +#define GL_IMAGE_ROTATE_ORIGIN_Y_HP 0x815B +#define GL_IMAGE_MAG_FILTER_HP 0x815C +#define GL_IMAGE_MIN_FILTER_HP 0x815D +#define GL_IMAGE_CUBIC_WEIGHT_HP 0x815E +#define GL_CUBIC_HP 0x815F +#define GL_AVERAGE_HP 0x8160 +#define GL_IMAGE_TRANSFORM_2D_HP 0x8161 +#define GL_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8162 +#define GL_PROXY_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8163 +#endif + +#ifndef GL_HP_convolution_border_modes +#define GL_IGNORE_BORDER_HP 0x8150 +#define GL_CONSTANT_BORDER_HP 0x8151 +#define GL_REPLICATE_BORDER_HP 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR_HP 0x8154 +#endif + +#ifndef GL_INGR_palette_buffer +#endif + +#ifndef GL_SGIX_texture_add_env +#define GL_TEXTURE_ENV_BIAS_SGIX 0x80BE +#endif + +#ifndef GL_EXT_color_subtable +#endif + +#ifndef GL_PGI_vertex_hints +#define GL_VERTEX_DATA_HINT_PGI 0x1A22A +#define GL_VERTEX_CONSISTENT_HINT_PGI 0x1A22B +#define GL_MATERIAL_SIDE_HINT_PGI 0x1A22C +#define GL_MAX_VERTEX_HINT_PGI 0x1A22D +#define GL_COLOR3_BIT_PGI 0x00010000 +#define GL_COLOR4_BIT_PGI 0x00020000 +#define GL_EDGEFLAG_BIT_PGI 0x00040000 +#define GL_INDEX_BIT_PGI 0x00080000 +#define GL_MAT_AMBIENT_BIT_PGI 0x00100000 +#define GL_MAT_AMBIENT_AND_DIFFUSE_BIT_PGI 0x00200000 +#define GL_MAT_DIFFUSE_BIT_PGI 0x00400000 +#define GL_MAT_EMISSION_BIT_PGI 0x00800000 +#define GL_MAT_COLOR_INDEXES_BIT_PGI 0x01000000 +#define GL_MAT_SHININESS_BIT_PGI 0x02000000 +#define GL_MAT_SPECULAR_BIT_PGI 0x04000000 +#define GL_NORMAL_BIT_PGI 0x08000000 +#define GL_TEXCOORD1_BIT_PGI 0x10000000 +#define GL_TEXCOORD2_BIT_PGI 0x20000000 +#define GL_TEXCOORD3_BIT_PGI 0x40000000 +#define GL_TEXCOORD4_BIT_PGI 0x80000000 +#define GL_VERTEX23_BIT_PGI 0x00000004 +#define GL_VERTEX4_BIT_PGI 0x00000008 +#endif + +#ifndef GL_PGI_misc_hints +#define GL_PREFER_DOUBLEBUFFER_HINT_PGI 0x1A1F8 +#define GL_CONSERVE_MEMORY_HINT_PGI 0x1A1FD +#define GL_RECLAIM_MEMORY_HINT_PGI 0x1A1FE +#define GL_NATIVE_GRAPHICS_HANDLE_PGI 0x1A202 +#define GL_NATIVE_GRAPHICS_BEGIN_HINT_PGI 0x1A203 +#define GL_NATIVE_GRAPHICS_END_HINT_PGI 0x1A204 +#define GL_ALWAYS_FAST_HINT_PGI 0x1A20C +#define GL_ALWAYS_SOFT_HINT_PGI 0x1A20D +#define GL_ALLOW_DRAW_OBJ_HINT_PGI 0x1A20E +#define GL_ALLOW_DRAW_WIN_HINT_PGI 0x1A20F +#define GL_ALLOW_DRAW_FRG_HINT_PGI 0x1A210 +#define GL_ALLOW_DRAW_MEM_HINT_PGI 0x1A211 +#define GL_STRICT_DEPTHFUNC_HINT_PGI 0x1A216 +#define GL_STRICT_LIGHTING_HINT_PGI 0x1A217 +#define GL_STRICT_SCISSOR_HINT_PGI 0x1A218 +#define GL_FULL_STIPPLE_HINT_PGI 0x1A219 +#define GL_CLIP_NEAR_HINT_PGI 0x1A220 +#define GL_CLIP_FAR_HINT_PGI 0x1A221 +#define GL_WIDE_LINE_HINT_PGI 0x1A222 +#define GL_BACK_NORMALS_HINT_PGI 0x1A223 +#endif + +#ifndef GL_EXT_paletted_texture +#define GL_COLOR_INDEX1_EXT 0x80E2 +#define GL_COLOR_INDEX2_EXT 0x80E3 +#define GL_COLOR_INDEX4_EXT 0x80E4 +#define GL_COLOR_INDEX8_EXT 0x80E5 +#define GL_COLOR_INDEX12_EXT 0x80E6 +#define GL_COLOR_INDEX16_EXT 0x80E7 +#define GL_TEXTURE_INDEX_SIZE_EXT 0x80ED +#endif + +#ifndef GL_EXT_clip_volume_hint +#define GL_CLIP_VOLUME_CLIPPING_HINT_EXT 0x80F0 +#endif + +#ifndef GL_SGIX_list_priority +#define GL_LIST_PRIORITY_SGIX 0x8182 +#endif + +#ifndef GL_SGIX_ir_instrument1 +#define GL_IR_INSTRUMENT1_SGIX 0x817F +#endif + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_CALLIGRAPHIC_FRAGMENT_SGIX 0x8183 +#endif + +#ifndef GL_SGIX_texture_lod_bias +#define GL_TEXTURE_LOD_BIAS_S_SGIX 0x818E +#define GL_TEXTURE_LOD_BIAS_T_SGIX 0x818F +#define GL_TEXTURE_LOD_BIAS_R_SGIX 0x8190 +#endif + +#ifndef GL_SGIX_shadow_ambient +#define GL_SHADOW_AMBIENT_SGIX 0x80BF +#endif + +#ifndef GL_EXT_index_texture +#endif + +#ifndef GL_EXT_index_material +#define GL_INDEX_MATERIAL_EXT 0x81B8 +#define GL_INDEX_MATERIAL_PARAMETER_EXT 0x81B9 +#define GL_INDEX_MATERIAL_FACE_EXT 0x81BA +#endif + +#ifndef GL_EXT_index_func +#define GL_INDEX_TEST_EXT 0x81B5 +#define GL_INDEX_TEST_FUNC_EXT 0x81B6 +#define GL_INDEX_TEST_REF_EXT 0x81B7 +#endif + +#ifndef GL_EXT_index_array_formats +#define GL_IUI_V2F_EXT 0x81AD +#define GL_IUI_V3F_EXT 0x81AE +#define GL_IUI_N3F_V2F_EXT 0x81AF +#define GL_IUI_N3F_V3F_EXT 0x81B0 +#define GL_T2F_IUI_V2F_EXT 0x81B1 +#define GL_T2F_IUI_V3F_EXT 0x81B2 +#define GL_T2F_IUI_N3F_V2F_EXT 0x81B3 +#define GL_T2F_IUI_N3F_V3F_EXT 0x81B4 +#endif + +#ifndef GL_EXT_compiled_vertex_array +#define GL_ARRAY_ELEMENT_LOCK_FIRST_EXT 0x81A8 +#define GL_ARRAY_ELEMENT_LOCK_COUNT_EXT 0x81A9 +#endif + +#ifndef GL_EXT_cull_vertex +#define GL_CULL_VERTEX_EXT 0x81AA +#define GL_CULL_VERTEX_EYE_POSITION_EXT 0x81AB +#define GL_CULL_VERTEX_OBJECT_POSITION_EXT 0x81AC +#endif + +#ifndef GL_SGIX_ycrcb +#define GL_YCRCB_422_SGIX 0x81BB +#define GL_YCRCB_444_SGIX 0x81BC +#endif + +#ifndef GL_SGIX_fragment_lighting +#define GL_FRAGMENT_LIGHTING_SGIX 0x8400 +#define GL_FRAGMENT_COLOR_MATERIAL_SGIX 0x8401 +#define GL_FRAGMENT_COLOR_MATERIAL_FACE_SGIX 0x8402 +#define GL_FRAGMENT_COLOR_MATERIAL_PARAMETER_SGIX 0x8403 +#define GL_MAX_FRAGMENT_LIGHTS_SGIX 0x8404 +#define GL_MAX_ACTIVE_LIGHTS_SGIX 0x8405 +#define GL_CURRENT_RASTER_NORMAL_SGIX 0x8406 +#define GL_LIGHT_ENV_MODE_SGIX 0x8407 +#define GL_FRAGMENT_LIGHT_MODEL_LOCAL_VIEWER_SGIX 0x8408 +#define GL_FRAGMENT_LIGHT_MODEL_TWO_SIDE_SGIX 0x8409 +#define GL_FRAGMENT_LIGHT_MODEL_AMBIENT_SGIX 0x840A +#define GL_FRAGMENT_LIGHT_MODEL_NORMAL_INTERPOLATION_SGIX 0x840B +#define GL_FRAGMENT_LIGHT0_SGIX 0x840C +#define GL_FRAGMENT_LIGHT1_SGIX 0x840D +#define GL_FRAGMENT_LIGHT2_SGIX 0x840E +#define GL_FRAGMENT_LIGHT3_SGIX 0x840F +#define GL_FRAGMENT_LIGHT4_SGIX 0x8410 +#define GL_FRAGMENT_LIGHT5_SGIX 0x8411 +#define GL_FRAGMENT_LIGHT6_SGIX 0x8412 +#define GL_FRAGMENT_LIGHT7_SGIX 0x8413 +#endif + +#ifndef GL_IBM_rasterpos_clip +#define GL_RASTER_POSITION_UNCLIPPED_IBM 0x19262 +#endif + +#ifndef GL_HP_texture_lighting +#define GL_TEXTURE_LIGHTING_MODE_HP 0x8167 +#define GL_TEXTURE_POST_SPECULAR_HP 0x8168 +#define GL_TEXTURE_PRE_SPECULAR_HP 0x8169 +#endif + +#ifndef GL_EXT_draw_range_elements +#define GL_MAX_ELEMENTS_VERTICES_EXT 0x80E8 +#define GL_MAX_ELEMENTS_INDICES_EXT 0x80E9 +#endif + +#ifndef GL_WIN_phong_shading +#define GL_PHONG_WIN 0x80EA +#define GL_PHONG_HINT_WIN 0x80EB +#endif + +#ifndef GL_WIN_specular_fog +#define GL_FOG_SPECULAR_TEXTURE_WIN 0x80EC +#endif + +#ifndef GL_EXT_light_texture +#define GL_FRAGMENT_MATERIAL_EXT 0x8349 +#define GL_FRAGMENT_NORMAL_EXT 0x834A +#define GL_FRAGMENT_COLOR_EXT 0x834C +#define GL_ATTENUATION_EXT 0x834D +#define GL_SHADOW_ATTENUATION_EXT 0x834E +#define GL_TEXTURE_APPLICATION_MODE_EXT 0x834F +#define GL_TEXTURE_LIGHT_EXT 0x8350 +#define GL_TEXTURE_MATERIAL_FACE_EXT 0x8351 +#define GL_TEXTURE_MATERIAL_PARAMETER_EXT 0x8352 +/* reuse GL_FRAGMENT_DEPTH_EXT */ +#endif + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_ALPHA_MIN_SGIX 0x8320 +#define GL_ALPHA_MAX_SGIX 0x8321 +#endif + +#ifndef GL_SGIX_impact_pixel_texture +#define GL_PIXEL_TEX_GEN_Q_CEILING_SGIX 0x8184 +#define GL_PIXEL_TEX_GEN_Q_ROUND_SGIX 0x8185 +#define GL_PIXEL_TEX_GEN_Q_FLOOR_SGIX 0x8186 +#define GL_PIXEL_TEX_GEN_ALPHA_REPLACE_SGIX 0x8187 +#define GL_PIXEL_TEX_GEN_ALPHA_NO_REPLACE_SGIX 0x8188 +#define GL_PIXEL_TEX_GEN_ALPHA_LS_SGIX 0x8189 +#define GL_PIXEL_TEX_GEN_ALPHA_MS_SGIX 0x818A +#endif + +#ifndef GL_EXT_bgra +#define GL_BGR_EXT 0x80E0 +#define GL_BGRA_EXT 0x80E1 +#endif + +#ifndef GL_SGIX_async +#define GL_ASYNC_MARKER_SGIX 0x8329 +#endif + +#ifndef GL_SGIX_async_pixel +#define GL_ASYNC_TEX_IMAGE_SGIX 0x835C +#define GL_ASYNC_DRAW_PIXELS_SGIX 0x835D +#define GL_ASYNC_READ_PIXELS_SGIX 0x835E +#define GL_MAX_ASYNC_TEX_IMAGE_SGIX 0x835F +#define GL_MAX_ASYNC_DRAW_PIXELS_SGIX 0x8360 +#define GL_MAX_ASYNC_READ_PIXELS_SGIX 0x8361 +#endif + +#ifndef GL_SGIX_async_histogram +#define GL_ASYNC_HISTOGRAM_SGIX 0x832C +#define GL_MAX_ASYNC_HISTOGRAM_SGIX 0x832D +#endif + +#ifndef GL_INTEL_texture_scissor +#endif + +#ifndef GL_INTEL_parallel_arrays +#define GL_PARALLEL_ARRAYS_INTEL 0x83F4 +#define GL_VERTEX_ARRAY_PARALLEL_POINTERS_INTEL 0x83F5 +#define GL_NORMAL_ARRAY_PARALLEL_POINTERS_INTEL 0x83F6 +#define GL_COLOR_ARRAY_PARALLEL_POINTERS_INTEL 0x83F7 +#define GL_TEXTURE_COORD_ARRAY_PARALLEL_POINTERS_INTEL 0x83F8 +#endif + +#ifndef GL_HP_occlusion_test +#define GL_OCCLUSION_TEST_HP 0x8165 +#define GL_OCCLUSION_TEST_RESULT_HP 0x8166 +#endif + +#ifndef GL_EXT_pixel_transform +#define GL_PIXEL_TRANSFORM_2D_EXT 0x8330 +#define GL_PIXEL_MAG_FILTER_EXT 0x8331 +#define GL_PIXEL_MIN_FILTER_EXT 0x8332 +#define GL_PIXEL_CUBIC_WEIGHT_EXT 0x8333 +#define GL_CUBIC_EXT 0x8334 +#define GL_AVERAGE_EXT 0x8335 +#define GL_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8336 +#define GL_MAX_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8337 +#define GL_PIXEL_TRANSFORM_2D_MATRIX_EXT 0x8338 +#endif + +#ifndef GL_EXT_pixel_transform_color_table +#endif + +#ifndef GL_EXT_shared_texture_palette +#define GL_SHARED_TEXTURE_PALETTE_EXT 0x81FB +#endif + +#ifndef GL_EXT_separate_specular_color +#define GL_LIGHT_MODEL_COLOR_CONTROL_EXT 0x81F8 +#define GL_SINGLE_COLOR_EXT 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR_EXT 0x81FA +#endif + +#ifndef GL_EXT_secondary_color +#define GL_COLOR_SUM_EXT 0x8458 +#define GL_CURRENT_SECONDARY_COLOR_EXT 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE_EXT 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE_EXT 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE_EXT 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER_EXT 0x845D +#define GL_SECONDARY_COLOR_ARRAY_EXT 0x845E +#endif + +#ifndef GL_EXT_texture_perturb_normal +#define GL_PERTURB_EXT 0x85AE +#define GL_TEXTURE_NORMAL_EXT 0x85AF +#endif + +#ifndef GL_EXT_multi_draw_arrays +#endif + +#ifndef GL_EXT_fog_coord +#define GL_FOG_COORDINATE_SOURCE_EXT 0x8450 +#define GL_FOG_COORDINATE_EXT 0x8451 +#define GL_FRAGMENT_DEPTH_EXT 0x8452 +#define GL_CURRENT_FOG_COORDINATE_EXT 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE_EXT 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE_EXT 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER_EXT 0x8456 +#define GL_FOG_COORDINATE_ARRAY_EXT 0x8457 +#endif + +#ifndef GL_REND_screen_coordinates +#define GL_SCREEN_COORDINATES_REND 0x8490 +#define GL_INVERTED_SCREEN_W_REND 0x8491 +#endif + +#ifndef GL_EXT_coordinate_frame +#define GL_TANGENT_ARRAY_EXT 0x8439 +#define GL_BINORMAL_ARRAY_EXT 0x843A +#define GL_CURRENT_TANGENT_EXT 0x843B +#define GL_CURRENT_BINORMAL_EXT 0x843C +#define GL_TANGENT_ARRAY_TYPE_EXT 0x843E +#define GL_TANGENT_ARRAY_STRIDE_EXT 0x843F +#define GL_BINORMAL_ARRAY_TYPE_EXT 0x8440 +#define GL_BINORMAL_ARRAY_STRIDE_EXT 0x8441 +#define GL_TANGENT_ARRAY_POINTER_EXT 0x8442 +#define GL_BINORMAL_ARRAY_POINTER_EXT 0x8443 +#define GL_MAP1_TANGENT_EXT 0x8444 +#define GL_MAP2_TANGENT_EXT 0x8445 +#define GL_MAP1_BINORMAL_EXT 0x8446 +#define GL_MAP2_BINORMAL_EXT 0x8447 +#endif + +#ifndef GL_EXT_texture_env_combine +#define GL_COMBINE_EXT 0x8570 +#define GL_COMBINE_RGB_EXT 0x8571 +#define GL_COMBINE_ALPHA_EXT 0x8572 +#define GL_RGB_SCALE_EXT 0x8573 +#define GL_ADD_SIGNED_EXT 0x8574 +#define GL_INTERPOLATE_EXT 0x8575 +#define GL_CONSTANT_EXT 0x8576 +#define GL_PRIMARY_COLOR_EXT 0x8577 +#define GL_PREVIOUS_EXT 0x8578 +#define GL_SOURCE0_RGB_EXT 0x8580 +#define GL_SOURCE1_RGB_EXT 0x8581 +#define GL_SOURCE2_RGB_EXT 0x8582 +#define GL_SOURCE0_ALPHA_EXT 0x8588 +#define GL_SOURCE1_ALPHA_EXT 0x8589 +#define GL_SOURCE2_ALPHA_EXT 0x858A +#define GL_OPERAND0_RGB_EXT 0x8590 +#define GL_OPERAND1_RGB_EXT 0x8591 +#define GL_OPERAND2_RGB_EXT 0x8592 +#define GL_OPERAND0_ALPHA_EXT 0x8598 +#define GL_OPERAND1_ALPHA_EXT 0x8599 +#define GL_OPERAND2_ALPHA_EXT 0x859A +#endif + +#ifndef GL_APPLE_specular_vector +#define GL_LIGHT_MODEL_SPECULAR_VECTOR_APPLE 0x85B0 +#endif + +#ifndef GL_APPLE_transform_hint +#define GL_TRANSFORM_HINT_APPLE 0x85B1 +#endif + +#ifndef GL_SGIX_fog_scale +#define GL_FOG_SCALE_SGIX 0x81FC +#define GL_FOG_SCALE_VALUE_SGIX 0x81FD +#endif + +#ifndef GL_SUNX_constant_data +#define GL_UNPACK_CONSTANT_DATA_SUNX 0x81D5 +#define GL_TEXTURE_CONSTANT_DATA_SUNX 0x81D6 +#endif + +#ifndef GL_SUN_global_alpha +#define GL_GLOBAL_ALPHA_SUN 0x81D9 +#define GL_GLOBAL_ALPHA_FACTOR_SUN 0x81DA +#endif + +#ifndef GL_SUN_triangle_list +#define GL_RESTART_SUN 0x0001 +#define GL_REPLACE_MIDDLE_SUN 0x0002 +#define GL_REPLACE_OLDEST_SUN 0x0003 +#define GL_TRIANGLE_LIST_SUN 0x81D7 +#define GL_REPLACEMENT_CODE_SUN 0x81D8 +#define GL_REPLACEMENT_CODE_ARRAY_SUN 0x85C0 +#define GL_REPLACEMENT_CODE_ARRAY_TYPE_SUN 0x85C1 +#define GL_REPLACEMENT_CODE_ARRAY_STRIDE_SUN 0x85C2 +#define GL_REPLACEMENT_CODE_ARRAY_POINTER_SUN 0x85C3 +#define GL_R1UI_V3F_SUN 0x85C4 +#define GL_R1UI_C4UB_V3F_SUN 0x85C5 +#define GL_R1UI_C3F_V3F_SUN 0x85C6 +#define GL_R1UI_N3F_V3F_SUN 0x85C7 +#define GL_R1UI_C4F_N3F_V3F_SUN 0x85C8 +#define GL_R1UI_T2F_V3F_SUN 0x85C9 +#define GL_R1UI_T2F_N3F_V3F_SUN 0x85CA +#define GL_R1UI_T2F_C4F_N3F_V3F_SUN 0x85CB +#endif + +#ifndef GL_SUN_vertex +#endif + +#ifndef GL_EXT_blend_func_separate +#define GL_BLEND_DST_RGB_EXT 0x80C8 +#define GL_BLEND_SRC_RGB_EXT 0x80C9 +#define GL_BLEND_DST_ALPHA_EXT 0x80CA +#define GL_BLEND_SRC_ALPHA_EXT 0x80CB +#endif + +#ifndef GL_INGR_color_clamp +#define GL_RED_MIN_CLAMP_INGR 0x8560 +#define GL_GREEN_MIN_CLAMP_INGR 0x8561 +#define GL_BLUE_MIN_CLAMP_INGR 0x8562 +#define GL_ALPHA_MIN_CLAMP_INGR 0x8563 +#define GL_RED_MAX_CLAMP_INGR 0x8564 +#define GL_GREEN_MAX_CLAMP_INGR 0x8565 +#define GL_BLUE_MAX_CLAMP_INGR 0x8566 +#define GL_ALPHA_MAX_CLAMP_INGR 0x8567 +#endif + +#ifndef GL_INGR_interlace_read +#define GL_INTERLACE_READ_INGR 0x8568 +#endif + +#ifndef GL_EXT_stencil_wrap +#define GL_INCR_WRAP_EXT 0x8507 +#define GL_DECR_WRAP_EXT 0x8508 +#endif + +#ifndef GL_EXT_422_pixels +#define GL_422_EXT 0x80CC +#define GL_422_REV_EXT 0x80CD +#define GL_422_AVERAGE_EXT 0x80CE +#define GL_422_REV_AVERAGE_EXT 0x80CF +#endif + +#ifndef GL_NV_texgen_reflection +#define GL_NORMAL_MAP_NV 0x8511 +#define GL_REFLECTION_MAP_NV 0x8512 +#endif + +#ifndef GL_EXT_texture_cube_map +#define GL_NORMAL_MAP_EXT 0x8511 +#define GL_REFLECTION_MAP_EXT 0x8512 +#define GL_TEXTURE_CUBE_MAP_EXT 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_EXT 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_EXT 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_EXT 0x851C +#endif + +#ifndef GL_SUN_convolution_border_modes +#define GL_WRAP_BORDER_SUN 0x81D4 +#endif + +#ifndef GL_EXT_texture_env_add +#endif + +#ifndef GL_EXT_texture_lod_bias +#define GL_MAX_TEXTURE_LOD_BIAS_EXT 0x84FD +#define GL_TEXTURE_FILTER_CONTROL_EXT 0x8500 +#define GL_TEXTURE_LOD_BIAS_EXT 0x8501 +#endif + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif + +#ifndef GL_EXT_vertex_weighting +#define GL_MODELVIEW0_STACK_DEPTH_EXT GL_MODELVIEW_STACK_DEPTH +#define GL_MODELVIEW1_STACK_DEPTH_EXT 0x8502 +#define GL_MODELVIEW0_MATRIX_EXT GL_MODELVIEW_MATRIX +#define GL_MODELVIEW1_MATRIX_EXT 0x8506 +#define GL_VERTEX_WEIGHTING_EXT 0x8509 +#define GL_MODELVIEW0_EXT GL_MODELVIEW +#define GL_MODELVIEW1_EXT 0x850A +#define GL_CURRENT_VERTEX_WEIGHT_EXT 0x850B +#define GL_VERTEX_WEIGHT_ARRAY_EXT 0x850C +#define GL_VERTEX_WEIGHT_ARRAY_SIZE_EXT 0x850D +#define GL_VERTEX_WEIGHT_ARRAY_TYPE_EXT 0x850E +#define GL_VERTEX_WEIGHT_ARRAY_STRIDE_EXT 0x850F +#define GL_VERTEX_WEIGHT_ARRAY_POINTER_EXT 0x8510 +#endif + +#ifndef GL_NV_light_max_exponent +#define GL_MAX_SHININESS_NV 0x8504 +#define GL_MAX_SPOT_EXPONENT_NV 0x8505 +#endif + +#ifndef GL_NV_vertex_array_range +#define GL_VERTEX_ARRAY_RANGE_NV 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_NV 0x851E +#define GL_VERTEX_ARRAY_RANGE_VALID_NV 0x851F +#define GL_MAX_VERTEX_ARRAY_RANGE_ELEMENT_NV 0x8520 +#define GL_VERTEX_ARRAY_RANGE_POINTER_NV 0x8521 +#endif + +#ifndef GL_NV_register_combiners +#define GL_REGISTER_COMBINERS_NV 0x8522 +#define GL_VARIABLE_A_NV 0x8523 +#define GL_VARIABLE_B_NV 0x8524 +#define GL_VARIABLE_C_NV 0x8525 +#define GL_VARIABLE_D_NV 0x8526 +#define GL_VARIABLE_E_NV 0x8527 +#define GL_VARIABLE_F_NV 0x8528 +#define GL_VARIABLE_G_NV 0x8529 +#define GL_CONSTANT_COLOR0_NV 0x852A +#define GL_CONSTANT_COLOR1_NV 0x852B +#define GL_PRIMARY_COLOR_NV 0x852C +#define GL_SECONDARY_COLOR_NV 0x852D +#define GL_SPARE0_NV 0x852E +#define GL_SPARE1_NV 0x852F +#define GL_DISCARD_NV 0x8530 +#define GL_E_TIMES_F_NV 0x8531 +#define GL_SPARE0_PLUS_SECONDARY_COLOR_NV 0x8532 +#define GL_UNSIGNED_IDENTITY_NV 0x8536 +#define GL_UNSIGNED_INVERT_NV 0x8537 +#define GL_EXPAND_NORMAL_NV 0x8538 +#define GL_EXPAND_NEGATE_NV 0x8539 +#define GL_HALF_BIAS_NORMAL_NV 0x853A +#define GL_HALF_BIAS_NEGATE_NV 0x853B +#define GL_SIGNED_IDENTITY_NV 0x853C +#define GL_SIGNED_NEGATE_NV 0x853D +#define GL_SCALE_BY_TWO_NV 0x853E +#define GL_SCALE_BY_FOUR_NV 0x853F +#define GL_SCALE_BY_ONE_HALF_NV 0x8540 +#define GL_BIAS_BY_NEGATIVE_ONE_HALF_NV 0x8541 +#define GL_COMBINER_INPUT_NV 0x8542 +#define GL_COMBINER_MAPPING_NV 0x8543 +#define GL_COMBINER_COMPONENT_USAGE_NV 0x8544 +#define GL_COMBINER_AB_DOT_PRODUCT_NV 0x8545 +#define GL_COMBINER_CD_DOT_PRODUCT_NV 0x8546 +#define GL_COMBINER_MUX_SUM_NV 0x8547 +#define GL_COMBINER_SCALE_NV 0x8548 +#define GL_COMBINER_BIAS_NV 0x8549 +#define GL_COMBINER_AB_OUTPUT_NV 0x854A +#define GL_COMBINER_CD_OUTPUT_NV 0x854B +#define GL_COMBINER_SUM_OUTPUT_NV 0x854C +#define GL_MAX_GENERAL_COMBINERS_NV 0x854D +#define GL_NUM_GENERAL_COMBINERS_NV 0x854E +#define GL_COLOR_SUM_CLAMP_NV 0x854F +#define GL_COMBINER0_NV 0x8550 +#define GL_COMBINER1_NV 0x8551 +#define GL_COMBINER2_NV 0x8552 +#define GL_COMBINER3_NV 0x8553 +#define GL_COMBINER4_NV 0x8554 +#define GL_COMBINER5_NV 0x8555 +#define GL_COMBINER6_NV 0x8556 +#define GL_COMBINER7_NV 0x8557 +/* reuse GL_TEXTURE0_ARB */ +/* reuse GL_TEXTURE1_ARB */ +/* reuse GL_ZERO */ +/* reuse GL_NONE */ +/* reuse GL_FOG */ +#endif + +#ifndef GL_NV_fog_distance +#define GL_FOG_DISTANCE_MODE_NV 0x855A +#define GL_EYE_RADIAL_NV 0x855B +#define GL_EYE_PLANE_ABSOLUTE_NV 0x855C +/* reuse GL_EYE_PLANE */ +#endif + +#ifndef GL_NV_texgen_emboss +#define GL_EMBOSS_LIGHT_NV 0x855D +#define GL_EMBOSS_CONSTANT_NV 0x855E +#define GL_EMBOSS_MAP_NV 0x855F +#endif + +#ifndef GL_NV_blend_square +#endif + +#ifndef GL_NV_texture_env_combine4 +#define GL_COMBINE4_NV 0x8503 +#define GL_SOURCE3_RGB_NV 0x8583 +#define GL_SOURCE3_ALPHA_NV 0x858B +#define GL_OPERAND3_RGB_NV 0x8593 +#define GL_OPERAND3_ALPHA_NV 0x859B +#endif + +#ifndef GL_MESA_resize_buffers +#endif + +#ifndef GL_MESA_window_pos +#endif + +#ifndef GL_EXT_texture_compression_s3tc +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif + +#ifndef GL_IBM_cull_vertex +#define GL_CULL_VERTEX_IBM 103050 +#endif + +#ifndef GL_IBM_multimode_draw_arrays +#endif + +#ifndef GL_IBM_vertex_array_lists +#define GL_VERTEX_ARRAY_LIST_IBM 103070 +#define GL_NORMAL_ARRAY_LIST_IBM 103071 +#define GL_COLOR_ARRAY_LIST_IBM 103072 +#define GL_INDEX_ARRAY_LIST_IBM 103073 +#define GL_TEXTURE_COORD_ARRAY_LIST_IBM 103074 +#define GL_EDGE_FLAG_ARRAY_LIST_IBM 103075 +#define GL_FOG_COORDINATE_ARRAY_LIST_IBM 103076 +#define GL_SECONDARY_COLOR_ARRAY_LIST_IBM 103077 +#define GL_VERTEX_ARRAY_LIST_STRIDE_IBM 103080 +#define GL_NORMAL_ARRAY_LIST_STRIDE_IBM 103081 +#define GL_COLOR_ARRAY_LIST_STRIDE_IBM 103082 +#define GL_INDEX_ARRAY_LIST_STRIDE_IBM 103083 +#define GL_TEXTURE_COORD_ARRAY_LIST_STRIDE_IBM 103084 +#define GL_EDGE_FLAG_ARRAY_LIST_STRIDE_IBM 103085 +#define GL_FOG_COORDINATE_ARRAY_LIST_STRIDE_IBM 103086 +#define GL_SECONDARY_COLOR_ARRAY_LIST_STRIDE_IBM 103087 +#endif + +#ifndef GL_SGIX_subsample +#define GL_PACK_SUBSAMPLE_RATE_SGIX 0x85A0 +#define GL_UNPACK_SUBSAMPLE_RATE_SGIX 0x85A1 +#define GL_PIXEL_SUBSAMPLE_4444_SGIX 0x85A2 +#define GL_PIXEL_SUBSAMPLE_2424_SGIX 0x85A3 +#define GL_PIXEL_SUBSAMPLE_4242_SGIX 0x85A4 +#endif + +#ifndef GL_SGIX_ycrcb_subsample +#endif + +#ifndef GL_SGIX_ycrcba +#define GL_YCRCB_SGIX 0x8318 +#define GL_YCRCBA_SGIX 0x8319 +#endif + +#ifndef GL_SGI_depth_pass_instrument +#define GL_DEPTH_PASS_INSTRUMENT_SGIX 0x8310 +#define GL_DEPTH_PASS_INSTRUMENT_COUNTERS_SGIX 0x8311 +#define GL_DEPTH_PASS_INSTRUMENT_MAX_SGIX 0x8312 +#endif + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_COMPRESSED_RGB_FXT1_3DFX 0x86B0 +#define GL_COMPRESSED_RGBA_FXT1_3DFX 0x86B1 +#endif + +#ifndef GL_3DFX_multisample +#define GL_MULTISAMPLE_3DFX 0x86B2 +#define GL_SAMPLE_BUFFERS_3DFX 0x86B3 +#define GL_SAMPLES_3DFX 0x86B4 +#define GL_MULTISAMPLE_BIT_3DFX 0x20000000 +#endif + +#ifndef GL_3DFX_tbuffer +#endif + +#ifndef GL_EXT_multisample +#define GL_MULTISAMPLE_EXT 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_EXT 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_EXT 0x809F +#define GL_SAMPLE_MASK_EXT 0x80A0 +#define GL_1PASS_EXT 0x80A1 +#define GL_2PASS_0_EXT 0x80A2 +#define GL_2PASS_1_EXT 0x80A3 +#define GL_4PASS_0_EXT 0x80A4 +#define GL_4PASS_1_EXT 0x80A5 +#define GL_4PASS_2_EXT 0x80A6 +#define GL_4PASS_3_EXT 0x80A7 +#define GL_SAMPLE_BUFFERS_EXT 0x80A8 +#define GL_SAMPLES_EXT 0x80A9 +#define GL_SAMPLE_MASK_VALUE_EXT 0x80AA +#define GL_SAMPLE_MASK_INVERT_EXT 0x80AB +#define GL_SAMPLE_PATTERN_EXT 0x80AC +#define GL_MULTISAMPLE_BIT_EXT 0x20000000 +#endif + +#ifndef GL_SGIX_vertex_preclip +#define GL_VERTEX_PRECLIP_SGIX 0x83EE +#define GL_VERTEX_PRECLIP_HINT_SGIX 0x83EF +#endif + +#ifndef GL_SGIX_convolution_accuracy +#define GL_CONVOLUTION_HINT_SGIX 0x8316 +#endif + +#ifndef GL_SGIX_resample +#define GL_PACK_RESAMPLE_SGIX 0x842C +#define GL_UNPACK_RESAMPLE_SGIX 0x842D +#define GL_RESAMPLE_REPLICATE_SGIX 0x842E +#define GL_RESAMPLE_ZERO_FILL_SGIX 0x842F +#define GL_RESAMPLE_DECIMATE_SGIX 0x8430 +#endif + +#ifndef GL_SGIS_point_line_texgen +#define GL_EYE_DISTANCE_TO_POINT_SGIS 0x81F0 +#define GL_OBJECT_DISTANCE_TO_POINT_SGIS 0x81F1 +#define GL_EYE_DISTANCE_TO_LINE_SGIS 0x81F2 +#define GL_OBJECT_DISTANCE_TO_LINE_SGIS 0x81F3 +#define GL_EYE_POINT_SGIS 0x81F4 +#define GL_OBJECT_POINT_SGIS 0x81F5 +#define GL_EYE_LINE_SGIS 0x81F6 +#define GL_OBJECT_LINE_SGIS 0x81F7 +#endif + +#ifndef GL_SGIS_texture_color_mask +#define GL_TEXTURE_COLOR_WRITEMASK_SGIS 0x81EF +#endif + +#ifndef GL_EXT_texture_env_dot3 +#define GL_DOT3_RGB_EXT 0x8740 +#define GL_DOT3_RGBA_EXT 0x8741 +#endif + +#ifndef GL_ATI_texture_mirror_once +#define GL_MIRROR_CLAMP_ATI 0x8742 +#define GL_MIRROR_CLAMP_TO_EDGE_ATI 0x8743 +#endif + +#ifndef GL_NV_fence +#define GL_ALL_COMPLETED_NV 0x84F2 +#define GL_FENCE_STATUS_NV 0x84F3 +#define GL_FENCE_CONDITION_NV 0x84F4 +#endif + +#ifndef GL_IBM_texture_mirrored_repeat +#define GL_MIRRORED_REPEAT_IBM 0x8370 +#endif + +#ifndef GL_NV_evaluators +#define GL_EVAL_2D_NV 0x86C0 +#define GL_EVAL_TRIANGULAR_2D_NV 0x86C1 +#define GL_MAP_TESSELLATION_NV 0x86C2 +#define GL_MAP_ATTRIB_U_ORDER_NV 0x86C3 +#define GL_MAP_ATTRIB_V_ORDER_NV 0x86C4 +#define GL_EVAL_FRACTIONAL_TESSELLATION_NV 0x86C5 +#define GL_EVAL_VERTEX_ATTRIB0_NV 0x86C6 +#define GL_EVAL_VERTEX_ATTRIB1_NV 0x86C7 +#define GL_EVAL_VERTEX_ATTRIB2_NV 0x86C8 +#define GL_EVAL_VERTEX_ATTRIB3_NV 0x86C9 +#define GL_EVAL_VERTEX_ATTRIB4_NV 0x86CA +#define GL_EVAL_VERTEX_ATTRIB5_NV 0x86CB +#define GL_EVAL_VERTEX_ATTRIB6_NV 0x86CC +#define GL_EVAL_VERTEX_ATTRIB7_NV 0x86CD +#define GL_EVAL_VERTEX_ATTRIB8_NV 0x86CE +#define GL_EVAL_VERTEX_ATTRIB9_NV 0x86CF +#define GL_EVAL_VERTEX_ATTRIB10_NV 0x86D0 +#define GL_EVAL_VERTEX_ATTRIB11_NV 0x86D1 +#define GL_EVAL_VERTEX_ATTRIB12_NV 0x86D2 +#define GL_EVAL_VERTEX_ATTRIB13_NV 0x86D3 +#define GL_EVAL_VERTEX_ATTRIB14_NV 0x86D4 +#define GL_EVAL_VERTEX_ATTRIB15_NV 0x86D5 +#define GL_MAX_MAP_TESSELLATION_NV 0x86D6 +#define GL_MAX_RATIONAL_EVAL_ORDER_NV 0x86D7 +#endif + +#ifndef GL_NV_packed_depth_stencil +#define GL_DEPTH_STENCIL_NV 0x84F9 +#define GL_UNSIGNED_INT_24_8_NV 0x84FA +#endif + +#ifndef GL_NV_register_combiners2 +#define GL_PER_STAGE_CONSTANTS_NV 0x8535 +#endif + +#ifndef GL_NV_texture_compression_vtc +#endif + +#ifndef GL_NV_texture_rectangle +#define GL_TEXTURE_RECTANGLE_NV 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_NV 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_NV 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_NV 0x84F8 +#endif + +#ifndef GL_NV_texture_shader +#define GL_OFFSET_TEXTURE_RECTANGLE_NV 0x864C +#define GL_OFFSET_TEXTURE_RECTANGLE_SCALE_NV 0x864D +#define GL_DOT_PRODUCT_TEXTURE_RECTANGLE_NV 0x864E +#define GL_RGBA_UNSIGNED_DOT_PRODUCT_MAPPING_NV 0x86D9 +#define GL_UNSIGNED_INT_S8_S8_8_8_NV 0x86DA +#define GL_UNSIGNED_INT_8_8_S8_S8_REV_NV 0x86DB +#define GL_DSDT_MAG_INTENSITY_NV 0x86DC +#define GL_SHADER_CONSISTENT_NV 0x86DD +#define GL_TEXTURE_SHADER_NV 0x86DE +#define GL_SHADER_OPERATION_NV 0x86DF +#define GL_CULL_MODES_NV 0x86E0 +#define GL_OFFSET_TEXTURE_MATRIX_NV 0x86E1 +#define GL_OFFSET_TEXTURE_SCALE_NV 0x86E2 +#define GL_OFFSET_TEXTURE_BIAS_NV 0x86E3 +#define GL_OFFSET_TEXTURE_2D_MATRIX_NV GL_OFFSET_TEXTURE_MATRIX_NV +#define GL_OFFSET_TEXTURE_2D_SCALE_NV GL_OFFSET_TEXTURE_SCALE_NV +#define GL_OFFSET_TEXTURE_2D_BIAS_NV GL_OFFSET_TEXTURE_BIAS_NV +#define GL_PREVIOUS_TEXTURE_INPUT_NV 0x86E4 +#define GL_CONST_EYE_NV 0x86E5 +#define GL_PASS_THROUGH_NV 0x86E6 +#define GL_CULL_FRAGMENT_NV 0x86E7 +#define GL_OFFSET_TEXTURE_2D_NV 0x86E8 +#define GL_DEPENDENT_AR_TEXTURE_2D_NV 0x86E9 +#define GL_DEPENDENT_GB_TEXTURE_2D_NV 0x86EA +#define GL_DOT_PRODUCT_NV 0x86EC +#define GL_DOT_PRODUCT_DEPTH_REPLACE_NV 0x86ED +#define GL_DOT_PRODUCT_TEXTURE_2D_NV 0x86EE +#define GL_DOT_PRODUCT_TEXTURE_CUBE_MAP_NV 0x86F0 +#define GL_DOT_PRODUCT_DIFFUSE_CUBE_MAP_NV 0x86F1 +#define GL_DOT_PRODUCT_REFLECT_CUBE_MAP_NV 0x86F2 +#define GL_DOT_PRODUCT_CONST_EYE_REFLECT_CUBE_MAP_NV 0x86F3 +#define GL_HILO_NV 0x86F4 +#define GL_DSDT_NV 0x86F5 +#define GL_DSDT_MAG_NV 0x86F6 +#define GL_DSDT_MAG_VIB_NV 0x86F7 +#define GL_HILO16_NV 0x86F8 +#define GL_SIGNED_HILO_NV 0x86F9 +#define GL_SIGNED_HILO16_NV 0x86FA +#define GL_SIGNED_RGBA_NV 0x86FB +#define GL_SIGNED_RGBA8_NV 0x86FC +#define GL_SIGNED_RGB_NV 0x86FE +#define GL_SIGNED_RGB8_NV 0x86FF +#define GL_SIGNED_LUMINANCE_NV 0x8701 +#define GL_SIGNED_LUMINANCE8_NV 0x8702 +#define GL_SIGNED_LUMINANCE_ALPHA_NV 0x8703 +#define GL_SIGNED_LUMINANCE8_ALPHA8_NV 0x8704 +#define GL_SIGNED_ALPHA_NV 0x8705 +#define GL_SIGNED_ALPHA8_NV 0x8706 +#define GL_SIGNED_INTENSITY_NV 0x8707 +#define GL_SIGNED_INTENSITY8_NV 0x8708 +#define GL_DSDT8_NV 0x8709 +#define GL_DSDT8_MAG8_NV 0x870A +#define GL_DSDT8_MAG8_INTENSITY8_NV 0x870B +#define GL_SIGNED_RGB_UNSIGNED_ALPHA_NV 0x870C +#define GL_SIGNED_RGB8_UNSIGNED_ALPHA8_NV 0x870D +#define GL_HI_SCALE_NV 0x870E +#define GL_LO_SCALE_NV 0x870F +#define GL_DS_SCALE_NV 0x8710 +#define GL_DT_SCALE_NV 0x8711 +#define GL_MAGNITUDE_SCALE_NV 0x8712 +#define GL_VIBRANCE_SCALE_NV 0x8713 +#define GL_HI_BIAS_NV 0x8714 +#define GL_LO_BIAS_NV 0x8715 +#define GL_DS_BIAS_NV 0x8716 +#define GL_DT_BIAS_NV 0x8717 +#define GL_MAGNITUDE_BIAS_NV 0x8718 +#define GL_VIBRANCE_BIAS_NV 0x8719 +#define GL_TEXTURE_BORDER_VALUES_NV 0x871A +#define GL_TEXTURE_HI_SIZE_NV 0x871B +#define GL_TEXTURE_LO_SIZE_NV 0x871C +#define GL_TEXTURE_DS_SIZE_NV 0x871D +#define GL_TEXTURE_DT_SIZE_NV 0x871E +#define GL_TEXTURE_MAG_SIZE_NV 0x871F +#endif + +#ifndef GL_NV_texture_shader2 +#define GL_DOT_PRODUCT_TEXTURE_3D_NV 0x86EF +#endif + +#ifndef GL_NV_vertex_array_range2 +#define GL_VERTEX_ARRAY_RANGE_WITHOUT_FLUSH_NV 0x8533 +#endif + +#ifndef GL_NV_vertex_program +#define GL_VERTEX_PROGRAM_NV 0x8620 +#define GL_VERTEX_STATE_PROGRAM_NV 0x8621 +#define GL_ATTRIB_ARRAY_SIZE_NV 0x8623 +#define GL_ATTRIB_ARRAY_STRIDE_NV 0x8624 +#define GL_ATTRIB_ARRAY_TYPE_NV 0x8625 +#define GL_CURRENT_ATTRIB_NV 0x8626 +#define GL_PROGRAM_LENGTH_NV 0x8627 +#define GL_PROGRAM_STRING_NV 0x8628 +#define GL_MODELVIEW_PROJECTION_NV 0x8629 +#define GL_IDENTITY_NV 0x862A +#define GL_INVERSE_NV 0x862B +#define GL_TRANSPOSE_NV 0x862C +#define GL_INVERSE_TRANSPOSE_NV 0x862D +#define GL_MAX_TRACK_MATRIX_STACK_DEPTH_NV 0x862E +#define GL_MAX_TRACK_MATRICES_NV 0x862F +#define GL_MATRIX0_NV 0x8630 +#define GL_MATRIX1_NV 0x8631 +#define GL_MATRIX2_NV 0x8632 +#define GL_MATRIX3_NV 0x8633 +#define GL_MATRIX4_NV 0x8634 +#define GL_MATRIX5_NV 0x8635 +#define GL_MATRIX6_NV 0x8636 +#define GL_MATRIX7_NV 0x8637 +#define GL_CURRENT_MATRIX_STACK_DEPTH_NV 0x8640 +#define GL_CURRENT_MATRIX_NV 0x8641 +#define GL_VERTEX_PROGRAM_POINT_SIZE_NV 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_NV 0x8643 +#define GL_PROGRAM_PARAMETER_NV 0x8644 +#define GL_ATTRIB_ARRAY_POINTER_NV 0x8645 +#define GL_PROGRAM_TARGET_NV 0x8646 +#define GL_PROGRAM_RESIDENT_NV 0x8647 +#define GL_TRACK_MATRIX_NV 0x8648 +#define GL_TRACK_MATRIX_TRANSFORM_NV 0x8649 +#define GL_VERTEX_PROGRAM_BINDING_NV 0x864A +#define GL_PROGRAM_ERROR_POSITION_NV 0x864B +#define GL_VERTEX_ATTRIB_ARRAY0_NV 0x8650 +#define GL_VERTEX_ATTRIB_ARRAY1_NV 0x8651 +#define GL_VERTEX_ATTRIB_ARRAY2_NV 0x8652 +#define GL_VERTEX_ATTRIB_ARRAY3_NV 0x8653 +#define GL_VERTEX_ATTRIB_ARRAY4_NV 0x8654 +#define GL_VERTEX_ATTRIB_ARRAY5_NV 0x8655 +#define GL_VERTEX_ATTRIB_ARRAY6_NV 0x8656 +#define GL_VERTEX_ATTRIB_ARRAY7_NV 0x8657 +#define GL_VERTEX_ATTRIB_ARRAY8_NV 0x8658 +#define GL_VERTEX_ATTRIB_ARRAY9_NV 0x8659 +#define GL_VERTEX_ATTRIB_ARRAY10_NV 0x865A +#define GL_VERTEX_ATTRIB_ARRAY11_NV 0x865B +#define GL_VERTEX_ATTRIB_ARRAY12_NV 0x865C +#define GL_VERTEX_ATTRIB_ARRAY13_NV 0x865D +#define GL_VERTEX_ATTRIB_ARRAY14_NV 0x865E +#define GL_VERTEX_ATTRIB_ARRAY15_NV 0x865F +#define GL_MAP1_VERTEX_ATTRIB0_4_NV 0x8660 +#define GL_MAP1_VERTEX_ATTRIB1_4_NV 0x8661 +#define GL_MAP1_VERTEX_ATTRIB2_4_NV 0x8662 +#define GL_MAP1_VERTEX_ATTRIB3_4_NV 0x8663 +#define GL_MAP1_VERTEX_ATTRIB4_4_NV 0x8664 +#define GL_MAP1_VERTEX_ATTRIB5_4_NV 0x8665 +#define GL_MAP1_VERTEX_ATTRIB6_4_NV 0x8666 +#define GL_MAP1_VERTEX_ATTRIB7_4_NV 0x8667 +#define GL_MAP1_VERTEX_ATTRIB8_4_NV 0x8668 +#define GL_MAP1_VERTEX_ATTRIB9_4_NV 0x8669 +#define GL_MAP1_VERTEX_ATTRIB10_4_NV 0x866A +#define GL_MAP1_VERTEX_ATTRIB11_4_NV 0x866B +#define GL_MAP1_VERTEX_ATTRIB12_4_NV 0x866C +#define GL_MAP1_VERTEX_ATTRIB13_4_NV 0x866D +#define GL_MAP1_VERTEX_ATTRIB14_4_NV 0x866E +#define GL_MAP1_VERTEX_ATTRIB15_4_NV 0x866F +#define GL_MAP2_VERTEX_ATTRIB0_4_NV 0x8670 +#define GL_MAP2_VERTEX_ATTRIB1_4_NV 0x8671 +#define GL_MAP2_VERTEX_ATTRIB2_4_NV 0x8672 +#define GL_MAP2_VERTEX_ATTRIB3_4_NV 0x8673 +#define GL_MAP2_VERTEX_ATTRIB4_4_NV 0x8674 +#define GL_MAP2_VERTEX_ATTRIB5_4_NV 0x8675 +#define GL_MAP2_VERTEX_ATTRIB6_4_NV 0x8676 +#define GL_MAP2_VERTEX_ATTRIB7_4_NV 0x8677 +#define GL_MAP2_VERTEX_ATTRIB8_4_NV 0x8678 +#define GL_MAP2_VERTEX_ATTRIB9_4_NV 0x8679 +#define GL_MAP2_VERTEX_ATTRIB10_4_NV 0x867A +#define GL_MAP2_VERTEX_ATTRIB11_4_NV 0x867B +#define GL_MAP2_VERTEX_ATTRIB12_4_NV 0x867C +#define GL_MAP2_VERTEX_ATTRIB13_4_NV 0x867D +#define GL_MAP2_VERTEX_ATTRIB14_4_NV 0x867E +#define GL_MAP2_VERTEX_ATTRIB15_4_NV 0x867F +#endif + +#ifndef GL_SGIX_texture_coordinate_clamp +#define GL_TEXTURE_MAX_CLAMP_S_SGIX 0x8369 +#define GL_TEXTURE_MAX_CLAMP_T_SGIX 0x836A +#define GL_TEXTURE_MAX_CLAMP_R_SGIX 0x836B +#endif + +#ifndef GL_SGIX_scalebias_hint +#define GL_SCALEBIAS_HINT_SGIX 0x8322 +#endif + +#ifndef GL_OML_interlace +#define GL_INTERLACE_OML 0x8980 +#define GL_INTERLACE_READ_OML 0x8981 +#endif + +#ifndef GL_OML_subsample +#define GL_FORMAT_SUBSAMPLE_24_24_OML 0x8982 +#define GL_FORMAT_SUBSAMPLE_244_244_OML 0x8983 +#endif + +#ifndef GL_OML_resample +#define GL_PACK_RESAMPLE_OML 0x8984 +#define GL_UNPACK_RESAMPLE_OML 0x8985 +#define GL_RESAMPLE_REPLICATE_OML 0x8986 +#define GL_RESAMPLE_ZERO_FILL_OML 0x8987 +#define GL_RESAMPLE_AVERAGE_OML 0x8988 +#define GL_RESAMPLE_DECIMATE_OML 0x8989 +#endif + +#ifndef GL_NV_copy_depth_to_color +#define GL_DEPTH_STENCIL_TO_RGBA_NV 0x886E +#define GL_DEPTH_STENCIL_TO_BGRA_NV 0x886F +#endif + +#ifndef GL_ATI_envmap_bumpmap +#define GL_BUMP_ROT_MATRIX_ATI 0x8775 +#define GL_BUMP_ROT_MATRIX_SIZE_ATI 0x8776 +#define GL_BUMP_NUM_TEX_UNITS_ATI 0x8777 +#define GL_BUMP_TEX_UNITS_ATI 0x8778 +#define GL_DUDV_ATI 0x8779 +#define GL_DU8DV8_ATI 0x877A +#define GL_BUMP_ENVMAP_ATI 0x877B +#define GL_BUMP_TARGET_ATI 0x877C +#endif + +#ifndef GL_ATI_fragment_shader +#define GL_FRAGMENT_SHADER_ATI 0x8920 +#define GL_REG_0_ATI 0x8921 +#define GL_REG_1_ATI 0x8922 +#define GL_REG_2_ATI 0x8923 +#define GL_REG_3_ATI 0x8924 +#define GL_REG_4_ATI 0x8925 +#define GL_REG_5_ATI 0x8926 +#define GL_REG_6_ATI 0x8927 +#define GL_REG_7_ATI 0x8928 +#define GL_REG_8_ATI 0x8929 +#define GL_REG_9_ATI 0x892A +#define GL_REG_10_ATI 0x892B +#define GL_REG_11_ATI 0x892C +#define GL_REG_12_ATI 0x892D +#define GL_REG_13_ATI 0x892E +#define GL_REG_14_ATI 0x892F +#define GL_REG_15_ATI 0x8930 +#define GL_REG_16_ATI 0x8931 +#define GL_REG_17_ATI 0x8932 +#define GL_REG_18_ATI 0x8933 +#define GL_REG_19_ATI 0x8934 +#define GL_REG_20_ATI 0x8935 +#define GL_REG_21_ATI 0x8936 +#define GL_REG_22_ATI 0x8937 +#define GL_REG_23_ATI 0x8938 +#define GL_REG_24_ATI 0x8939 +#define GL_REG_25_ATI 0x893A +#define GL_REG_26_ATI 0x893B +#define GL_REG_27_ATI 0x893C +#define GL_REG_28_ATI 0x893D +#define GL_REG_29_ATI 0x893E +#define GL_REG_30_ATI 0x893F +#define GL_REG_31_ATI 0x8940 +#define GL_CON_0_ATI 0x8941 +#define GL_CON_1_ATI 0x8942 +#define GL_CON_2_ATI 0x8943 +#define GL_CON_3_ATI 0x8944 +#define GL_CON_4_ATI 0x8945 +#define GL_CON_5_ATI 0x8946 +#define GL_CON_6_ATI 0x8947 +#define GL_CON_7_ATI 0x8948 +#define GL_CON_8_ATI 0x8949 +#define GL_CON_9_ATI 0x894A +#define GL_CON_10_ATI 0x894B +#define GL_CON_11_ATI 0x894C +#define GL_CON_12_ATI 0x894D +#define GL_CON_13_ATI 0x894E +#define GL_CON_14_ATI 0x894F +#define GL_CON_15_ATI 0x8950 +#define GL_CON_16_ATI 0x8951 +#define GL_CON_17_ATI 0x8952 +#define GL_CON_18_ATI 0x8953 +#define GL_CON_19_ATI 0x8954 +#define GL_CON_20_ATI 0x8955 +#define GL_CON_21_ATI 0x8956 +#define GL_CON_22_ATI 0x8957 +#define GL_CON_23_ATI 0x8958 +#define GL_CON_24_ATI 0x8959 +#define GL_CON_25_ATI 0x895A +#define GL_CON_26_ATI 0x895B +#define GL_CON_27_ATI 0x895C +#define GL_CON_28_ATI 0x895D +#define GL_CON_29_ATI 0x895E +#define GL_CON_30_ATI 0x895F +#define GL_CON_31_ATI 0x8960 +#define GL_MOV_ATI 0x8961 +#define GL_ADD_ATI 0x8963 +#define GL_MUL_ATI 0x8964 +#define GL_SUB_ATI 0x8965 +#define GL_DOT3_ATI 0x8966 +#define GL_DOT4_ATI 0x8967 +#define GL_MAD_ATI 0x8968 +#define GL_LERP_ATI 0x8969 +#define GL_CND_ATI 0x896A +#define GL_CND0_ATI 0x896B +#define GL_DOT2_ADD_ATI 0x896C +#define GL_SECONDARY_INTERPOLATOR_ATI 0x896D +#define GL_NUM_FRAGMENT_REGISTERS_ATI 0x896E +#define GL_NUM_FRAGMENT_CONSTANTS_ATI 0x896F +#define GL_NUM_PASSES_ATI 0x8970 +#define GL_NUM_INSTRUCTIONS_PER_PASS_ATI 0x8971 +#define GL_NUM_INSTRUCTIONS_TOTAL_ATI 0x8972 +#define GL_NUM_INPUT_INTERPOLATOR_COMPONENTS_ATI 0x8973 +#define GL_NUM_LOOPBACK_COMPONENTS_ATI 0x8974 +#define GL_COLOR_ALPHA_PAIRING_ATI 0x8975 +#define GL_SWIZZLE_STR_ATI 0x8976 +#define GL_SWIZZLE_STQ_ATI 0x8977 +#define GL_SWIZZLE_STR_DR_ATI 0x8978 +#define GL_SWIZZLE_STQ_DQ_ATI 0x8979 +#define GL_SWIZZLE_STRQ_ATI 0x897A +#define GL_SWIZZLE_STRQ_DQ_ATI 0x897B +#define GL_RED_BIT_ATI 0x00000001 +#define GL_GREEN_BIT_ATI 0x00000002 +#define GL_BLUE_BIT_ATI 0x00000004 +#define GL_2X_BIT_ATI 0x00000001 +#define GL_4X_BIT_ATI 0x00000002 +#define GL_8X_BIT_ATI 0x00000004 +#define GL_HALF_BIT_ATI 0x00000008 +#define GL_QUARTER_BIT_ATI 0x00000010 +#define GL_EIGHTH_BIT_ATI 0x00000020 +#define GL_SATURATE_BIT_ATI 0x00000040 +#define GL_COMP_BIT_ATI 0x00000002 +#define GL_NEGATE_BIT_ATI 0x00000004 +#define GL_BIAS_BIT_ATI 0x00000008 +#endif + +#ifndef GL_ATI_pn_triangles +#define GL_PN_TRIANGLES_ATI 0x87F0 +#define GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F1 +#define GL_PN_TRIANGLES_POINT_MODE_ATI 0x87F2 +#define GL_PN_TRIANGLES_NORMAL_MODE_ATI 0x87F3 +#define GL_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F4 +#define GL_PN_TRIANGLES_POINT_MODE_LINEAR_ATI 0x87F5 +#define GL_PN_TRIANGLES_POINT_MODE_CUBIC_ATI 0x87F6 +#define GL_PN_TRIANGLES_NORMAL_MODE_LINEAR_ATI 0x87F7 +#define GL_PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATI 0x87F8 +#endif + +#ifndef GL_ATI_vertex_array_object +#define GL_STATIC_ATI 0x8760 +#define GL_DYNAMIC_ATI 0x8761 +#define GL_PRESERVE_ATI 0x8762 +#define GL_DISCARD_ATI 0x8763 +#define GL_OBJECT_BUFFER_SIZE_ATI 0x8764 +#define GL_OBJECT_BUFFER_USAGE_ATI 0x8765 +#define GL_ARRAY_OBJECT_BUFFER_ATI 0x8766 +#define GL_ARRAY_OBJECT_OFFSET_ATI 0x8767 +#endif + +#ifndef GL_EXT_vertex_shader +#define GL_VERTEX_SHADER_EXT 0x8780 +#define GL_VERTEX_SHADER_BINDING_EXT 0x8781 +#define GL_OP_INDEX_EXT 0x8782 +#define GL_OP_NEGATE_EXT 0x8783 +#define GL_OP_DOT3_EXT 0x8784 +#define GL_OP_DOT4_EXT 0x8785 +#define GL_OP_MUL_EXT 0x8786 +#define GL_OP_ADD_EXT 0x8787 +#define GL_OP_MADD_EXT 0x8788 +#define GL_OP_FRAC_EXT 0x8789 +#define GL_OP_MAX_EXT 0x878A +#define GL_OP_MIN_EXT 0x878B +#define GL_OP_SET_GE_EXT 0x878C +#define GL_OP_SET_LT_EXT 0x878D +#define GL_OP_CLAMP_EXT 0x878E +#define GL_OP_FLOOR_EXT 0x878F +#define GL_OP_ROUND_EXT 0x8790 +#define GL_OP_EXP_BASE_2_EXT 0x8791 +#define GL_OP_LOG_BASE_2_EXT 0x8792 +#define GL_OP_POWER_EXT 0x8793 +#define GL_OP_RECIP_EXT 0x8794 +#define GL_OP_RECIP_SQRT_EXT 0x8795 +#define GL_OP_SUB_EXT 0x8796 +#define GL_OP_CROSS_PRODUCT_EXT 0x8797 +#define GL_OP_MULTIPLY_MATRIX_EXT 0x8798 +#define GL_OP_MOV_EXT 0x8799 +#define GL_OUTPUT_VERTEX_EXT 0x879A +#define GL_OUTPUT_COLOR0_EXT 0x879B +#define GL_OUTPUT_COLOR1_EXT 0x879C +#define GL_OUTPUT_TEXTURE_COORD0_EXT 0x879D +#define GL_OUTPUT_TEXTURE_COORD1_EXT 0x879E +#define GL_OUTPUT_TEXTURE_COORD2_EXT 0x879F +#define GL_OUTPUT_TEXTURE_COORD3_EXT 0x87A0 +#define GL_OUTPUT_TEXTURE_COORD4_EXT 0x87A1 +#define GL_OUTPUT_TEXTURE_COORD5_EXT 0x87A2 +#define GL_OUTPUT_TEXTURE_COORD6_EXT 0x87A3 +#define GL_OUTPUT_TEXTURE_COORD7_EXT 0x87A4 +#define GL_OUTPUT_TEXTURE_COORD8_EXT 0x87A5 +#define GL_OUTPUT_TEXTURE_COORD9_EXT 0x87A6 +#define GL_OUTPUT_TEXTURE_COORD10_EXT 0x87A7 +#define GL_OUTPUT_TEXTURE_COORD11_EXT 0x87A8 +#define GL_OUTPUT_TEXTURE_COORD12_EXT 0x87A9 +#define GL_OUTPUT_TEXTURE_COORD13_EXT 0x87AA +#define GL_OUTPUT_TEXTURE_COORD14_EXT 0x87AB +#define GL_OUTPUT_TEXTURE_COORD15_EXT 0x87AC +#define GL_OUTPUT_TEXTURE_COORD16_EXT 0x87AD +#define GL_OUTPUT_TEXTURE_COORD17_EXT 0x87AE +#define GL_OUTPUT_TEXTURE_COORD18_EXT 0x87AF +#define GL_OUTPUT_TEXTURE_COORD19_EXT 0x87B0 +#define GL_OUTPUT_TEXTURE_COORD20_EXT 0x87B1 +#define GL_OUTPUT_TEXTURE_COORD21_EXT 0x87B2 +#define GL_OUTPUT_TEXTURE_COORD22_EXT 0x87B3 +#define GL_OUTPUT_TEXTURE_COORD23_EXT 0x87B4 +#define GL_OUTPUT_TEXTURE_COORD24_EXT 0x87B5 +#define GL_OUTPUT_TEXTURE_COORD25_EXT 0x87B6 +#define GL_OUTPUT_TEXTURE_COORD26_EXT 0x87B7 +#define GL_OUTPUT_TEXTURE_COORD27_EXT 0x87B8 +#define GL_OUTPUT_TEXTURE_COORD28_EXT 0x87B9 +#define GL_OUTPUT_TEXTURE_COORD29_EXT 0x87BA +#define GL_OUTPUT_TEXTURE_COORD30_EXT 0x87BB +#define GL_OUTPUT_TEXTURE_COORD31_EXT 0x87BC +#define GL_OUTPUT_FOG_EXT 0x87BD +#define GL_SCALAR_EXT 0x87BE +#define GL_VECTOR_EXT 0x87BF +#define GL_MATRIX_EXT 0x87C0 +#define GL_VARIANT_EXT 0x87C1 +#define GL_INVARIANT_EXT 0x87C2 +#define GL_LOCAL_CONSTANT_EXT 0x87C3 +#define GL_LOCAL_EXT 0x87C4 +#define GL_MAX_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87C5 +#define GL_MAX_VERTEX_SHADER_VARIANTS_EXT 0x87C6 +#define GL_MAX_VERTEX_SHADER_INVARIANTS_EXT 0x87C7 +#define GL_MAX_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87C8 +#define GL_MAX_VERTEX_SHADER_LOCALS_EXT 0x87C9 +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CA +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_VARIANTS_EXT 0x87CB +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87CC +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_INVARIANTS_EXT 0x87CD +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCALS_EXT 0x87CE +#define GL_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CF +#define GL_VERTEX_SHADER_VARIANTS_EXT 0x87D0 +#define GL_VERTEX_SHADER_INVARIANTS_EXT 0x87D1 +#define GL_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87D2 +#define GL_VERTEX_SHADER_LOCALS_EXT 0x87D3 +#define GL_VERTEX_SHADER_OPTIMIZED_EXT 0x87D4 +#define GL_X_EXT 0x87D5 +#define GL_Y_EXT 0x87D6 +#define GL_Z_EXT 0x87D7 +#define GL_W_EXT 0x87D8 +#define GL_NEGATIVE_X_EXT 0x87D9 +#define GL_NEGATIVE_Y_EXT 0x87DA +#define GL_NEGATIVE_Z_EXT 0x87DB +#define GL_NEGATIVE_W_EXT 0x87DC +#define GL_ZERO_EXT 0x87DD +#define GL_ONE_EXT 0x87DE +#define GL_NEGATIVE_ONE_EXT 0x87DF +#define GL_NORMALIZED_RANGE_EXT 0x87E0 +#define GL_FULL_RANGE_EXT 0x87E1 +#define GL_CURRENT_VERTEX_EXT 0x87E2 +#define GL_MVP_MATRIX_EXT 0x87E3 +#define GL_VARIANT_VALUE_EXT 0x87E4 +#define GL_VARIANT_DATATYPE_EXT 0x87E5 +#define GL_VARIANT_ARRAY_STRIDE_EXT 0x87E6 +#define GL_VARIANT_ARRAY_TYPE_EXT 0x87E7 +#define GL_VARIANT_ARRAY_EXT 0x87E8 +#define GL_VARIANT_ARRAY_POINTER_EXT 0x87E9 +#define GL_INVARIANT_VALUE_EXT 0x87EA +#define GL_INVARIANT_DATATYPE_EXT 0x87EB +#define GL_LOCAL_CONSTANT_VALUE_EXT 0x87EC +#define GL_LOCAL_CONSTANT_DATATYPE_EXT 0x87ED +#endif + +#ifndef GL_ATI_vertex_streams +#define GL_MAX_VERTEX_STREAMS_ATI 0x876B +#define GL_VERTEX_STREAM0_ATI 0x876C +#define GL_VERTEX_STREAM1_ATI 0x876D +#define GL_VERTEX_STREAM2_ATI 0x876E +#define GL_VERTEX_STREAM3_ATI 0x876F +#define GL_VERTEX_STREAM4_ATI 0x8770 +#define GL_VERTEX_STREAM5_ATI 0x8771 +#define GL_VERTEX_STREAM6_ATI 0x8772 +#define GL_VERTEX_STREAM7_ATI 0x8773 +#define GL_VERTEX_SOURCE_ATI 0x8774 +#endif + +#ifndef GL_ATI_element_array +#define GL_ELEMENT_ARRAY_ATI 0x8768 +#define GL_ELEMENT_ARRAY_TYPE_ATI 0x8769 +#define GL_ELEMENT_ARRAY_POINTER_ATI 0x876A +#endif + +#ifndef GL_SUN_mesh_array +#define GL_QUAD_MESH_SUN 0x8614 +#define GL_TRIANGLE_MESH_SUN 0x8615 +#endif + +#ifndef GL_SUN_slice_accum +#define GL_SLICE_ACCUM_SUN 0x85CC +#endif + +#ifndef GL_NV_multisample_filter_hint +#define GL_MULTISAMPLE_FILTER_HINT_NV 0x8534 +#endif + +#ifndef GL_NV_depth_clamp +#define GL_DEPTH_CLAMP_NV 0x864F +#endif + +#ifndef GL_NV_occlusion_query +#define GL_PIXEL_COUNTER_BITS_NV 0x8864 +#define GL_CURRENT_OCCLUSION_QUERY_ID_NV 0x8865 +#define GL_PIXEL_COUNT_NV 0x8866 +#define GL_PIXEL_COUNT_AVAILABLE_NV 0x8867 +#endif + +#ifndef GL_NV_point_sprite +#define GL_POINT_SPRITE_NV 0x8861 +#define GL_COORD_REPLACE_NV 0x8862 +#define GL_POINT_SPRITE_R_MODE_NV 0x8863 +#endif + +#ifndef GL_NV_texture_shader3 +#define GL_OFFSET_PROJECTIVE_TEXTURE_2D_NV 0x8850 +#define GL_OFFSET_PROJECTIVE_TEXTURE_2D_SCALE_NV 0x8851 +#define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8852 +#define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_SCALE_NV 0x8853 +#define GL_OFFSET_HILO_TEXTURE_2D_NV 0x8854 +#define GL_OFFSET_HILO_TEXTURE_RECTANGLE_NV 0x8855 +#define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_2D_NV 0x8856 +#define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8857 +#define GL_DEPENDENT_HILO_TEXTURE_2D_NV 0x8858 +#define GL_DEPENDENT_RGB_TEXTURE_3D_NV 0x8859 +#define GL_DEPENDENT_RGB_TEXTURE_CUBE_MAP_NV 0x885A +#define GL_DOT_PRODUCT_PASS_THROUGH_NV 0x885B +#define GL_DOT_PRODUCT_TEXTURE_1D_NV 0x885C +#define GL_DOT_PRODUCT_AFFINE_DEPTH_REPLACE_NV 0x885D +#define GL_HILO8_NV 0x885E +#define GL_SIGNED_HILO8_NV 0x885F +#define GL_FORCE_BLUE_TO_ONE_NV 0x8860 +#endif + +#ifndef GL_NV_vertex_program1_1 +#endif + +#ifndef GL_EXT_shadow_funcs +#endif + +#ifndef GL_EXT_stencil_two_side +#define GL_STENCIL_TEST_TWO_SIDE_EXT 0x8910 +#define GL_ACTIVE_STENCIL_FACE_EXT 0x8911 +#endif + +#ifndef GL_ATI_text_fragment_shader +#define GL_TEXT_FRAGMENT_SHADER_ATI 0x8200 +#endif + +#ifndef GL_APPLE_client_storage +#define GL_UNPACK_CLIENT_STORAGE_APPLE 0x85B2 +#endif + +#ifndef GL_APPLE_element_array +#define GL_ELEMENT_ARRAY_APPLE 0x8A0C +#define GL_ELEMENT_ARRAY_TYPE_APPLE 0x8A0D +#define GL_ELEMENT_ARRAY_POINTER_APPLE 0x8A0E +#endif + +#ifndef GL_APPLE_fence +#define GL_DRAW_PIXELS_APPLE 0x8A0A +#define GL_FENCE_APPLE 0x8A0B +#endif + +#ifndef GL_APPLE_vertex_array_object +#define GL_VERTEX_ARRAY_BINDING_APPLE 0x85B5 +#endif + +#ifndef GL_APPLE_vertex_array_range +#define GL_VERTEX_ARRAY_RANGE_APPLE 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_APPLE 0x851E +#define GL_VERTEX_ARRAY_STORAGE_HINT_APPLE 0x851F +#define GL_VERTEX_ARRAY_RANGE_POINTER_APPLE 0x8521 +#define GL_STORAGE_CLIENT_APPLE 0x85B4 +#define GL_STORAGE_CACHED_APPLE 0x85BE +#define GL_STORAGE_SHARED_APPLE 0x85BF +#endif + +#ifndef GL_APPLE_ycbcr_422 +#define GL_YCBCR_422_APPLE 0x85B9 +#define GL_UNSIGNED_SHORT_8_8_APPLE 0x85BA +#define GL_UNSIGNED_SHORT_8_8_REV_APPLE 0x85BB +#endif + +#ifndef GL_S3_s3tc +#define GL_RGB_S3TC 0x83A0 +#define GL_RGB4_S3TC 0x83A1 +#define GL_RGBA_S3TC 0x83A2 +#define GL_RGBA4_S3TC 0x83A3 +#endif + +#ifndef GL_ATI_draw_buffers +#define GL_MAX_DRAW_BUFFERS_ATI 0x8824 +#define GL_DRAW_BUFFER0_ATI 0x8825 +#define GL_DRAW_BUFFER1_ATI 0x8826 +#define GL_DRAW_BUFFER2_ATI 0x8827 +#define GL_DRAW_BUFFER3_ATI 0x8828 +#define GL_DRAW_BUFFER4_ATI 0x8829 +#define GL_DRAW_BUFFER5_ATI 0x882A +#define GL_DRAW_BUFFER6_ATI 0x882B +#define GL_DRAW_BUFFER7_ATI 0x882C +#define GL_DRAW_BUFFER8_ATI 0x882D +#define GL_DRAW_BUFFER9_ATI 0x882E +#define GL_DRAW_BUFFER10_ATI 0x882F +#define GL_DRAW_BUFFER11_ATI 0x8830 +#define GL_DRAW_BUFFER12_ATI 0x8831 +#define GL_DRAW_BUFFER13_ATI 0x8832 +#define GL_DRAW_BUFFER14_ATI 0x8833 +#define GL_DRAW_BUFFER15_ATI 0x8834 +#endif + +#ifndef GL_ATI_pixel_format_float +#define GL_TYPE_RGBA_FLOAT_ATI 0x8820 +#define GL_COLOR_CLEAR_UNCLAMPED_VALUE_ATI 0x8835 +#endif + +#ifndef GL_ATI_texture_env_combine3 +#define GL_MODULATE_ADD_ATI 0x8744 +#define GL_MODULATE_SIGNED_ADD_ATI 0x8745 +#define GL_MODULATE_SUBTRACT_ATI 0x8746 +#endif + +#ifndef GL_ATI_texture_float +#define GL_RGBA_FLOAT32_ATI 0x8814 +#define GL_RGB_FLOAT32_ATI 0x8815 +#define GL_ALPHA_FLOAT32_ATI 0x8816 +#define GL_INTENSITY_FLOAT32_ATI 0x8817 +#define GL_LUMINANCE_FLOAT32_ATI 0x8818 +#define GL_LUMINANCE_ALPHA_FLOAT32_ATI 0x8819 +#define GL_RGBA_FLOAT16_ATI 0x881A +#define GL_RGB_FLOAT16_ATI 0x881B +#define GL_ALPHA_FLOAT16_ATI 0x881C +#define GL_INTENSITY_FLOAT16_ATI 0x881D +#define GL_LUMINANCE_FLOAT16_ATI 0x881E +#define GL_LUMINANCE_ALPHA_FLOAT16_ATI 0x881F +#endif + +#ifndef GL_NV_float_buffer +#define GL_FLOAT_R_NV 0x8880 +#define GL_FLOAT_RG_NV 0x8881 +#define GL_FLOAT_RGB_NV 0x8882 +#define GL_FLOAT_RGBA_NV 0x8883 +#define GL_FLOAT_R16_NV 0x8884 +#define GL_FLOAT_R32_NV 0x8885 +#define GL_FLOAT_RG16_NV 0x8886 +#define GL_FLOAT_RG32_NV 0x8887 +#define GL_FLOAT_RGB16_NV 0x8888 +#define GL_FLOAT_RGB32_NV 0x8889 +#define GL_FLOAT_RGBA16_NV 0x888A +#define GL_FLOAT_RGBA32_NV 0x888B +#define GL_TEXTURE_FLOAT_COMPONENTS_NV 0x888C +#define GL_FLOAT_CLEAR_COLOR_VALUE_NV 0x888D +#define GL_FLOAT_RGBA_MODE_NV 0x888E +#endif + +#ifndef GL_NV_fragment_program +#define GL_MAX_FRAGMENT_PROGRAM_LOCAL_PARAMETERS_NV 0x8868 +#define GL_FRAGMENT_PROGRAM_NV 0x8870 +#define GL_MAX_TEXTURE_COORDS_NV 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_NV 0x8872 +#define GL_FRAGMENT_PROGRAM_BINDING_NV 0x8873 +#define GL_PROGRAM_ERROR_STRING_NV 0x8874 +#endif + +#ifndef GL_NV_half_float +#define GL_HALF_FLOAT_NV 0x140B +#endif + +#ifndef GL_NV_pixel_data_range +#define GL_WRITE_PIXEL_DATA_RANGE_NV 0x8878 +#define GL_READ_PIXEL_DATA_RANGE_NV 0x8879 +#define GL_WRITE_PIXEL_DATA_RANGE_LENGTH_NV 0x887A +#define GL_READ_PIXEL_DATA_RANGE_LENGTH_NV 0x887B +#define GL_WRITE_PIXEL_DATA_RANGE_POINTER_NV 0x887C +#define GL_READ_PIXEL_DATA_RANGE_POINTER_NV 0x887D +#endif + +#ifndef GL_NV_primitive_restart +#define GL_PRIMITIVE_RESTART_NV 0x8558 +#define GL_PRIMITIVE_RESTART_INDEX_NV 0x8559 +#endif + +#ifndef GL_NV_texture_expand_normal +#define GL_TEXTURE_UNSIGNED_REMAP_MODE_NV 0x888F +#endif + +#ifndef GL_NV_vertex_program2 +#endif + +#ifndef GL_ATI_map_object_buffer +#endif + +#ifndef GL_ATI_separate_stencil +#define GL_STENCIL_BACK_FUNC_ATI 0x8800 +#define GL_STENCIL_BACK_FAIL_ATI 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL_ATI 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS_ATI 0x8803 +#endif + +#ifndef GL_ATI_vertex_attrib_array_object +#endif + +#ifndef GL_OES_read_format +#define GL_IMPLEMENTATION_COLOR_READ_TYPE_OES 0x8B9A +#define GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES 0x8B9B +#endif + +#ifndef GL_EXT_depth_bounds_test +#define GL_DEPTH_BOUNDS_TEST_EXT 0x8890 +#define GL_DEPTH_BOUNDS_EXT 0x8891 +#endif + +#ifndef GL_EXT_texture_mirror_clamp +#define GL_MIRROR_CLAMP_EXT 0x8742 +#define GL_MIRROR_CLAMP_TO_EDGE_EXT 0x8743 +#define GL_MIRROR_CLAMP_TO_BORDER_EXT 0x8912 +#endif + +#ifndef GL_EXT_blend_equation_separate +#define GL_BLEND_EQUATION_RGB_EXT 0x8009 +#define GL_BLEND_EQUATION_ALPHA_EXT 0x883D +#endif + +#ifndef GL_MESA_pack_invert +#define GL_PACK_INVERT_MESA 0x8758 +#endif + +#ifndef GL_MESA_ycbcr_texture +#define GL_UNSIGNED_SHORT_8_8_MESA 0x85BA +#define GL_UNSIGNED_SHORT_8_8_REV_MESA 0x85BB +#define GL_YCBCR_MESA 0x8757 +#endif + +#ifndef GL_EXT_pixel_buffer_object +#define GL_PIXEL_PACK_BUFFER_EXT 0x88EB +#define GL_PIXEL_UNPACK_BUFFER_EXT 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING_EXT 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING_EXT 0x88EF +#endif + +#ifndef GL_NV_fragment_program_option +#endif + +#ifndef GL_NV_fragment_program2 +#define GL_MAX_PROGRAM_EXEC_INSTRUCTIONS_NV 0x88F4 +#define GL_MAX_PROGRAM_CALL_DEPTH_NV 0x88F5 +#define GL_MAX_PROGRAM_IF_DEPTH_NV 0x88F6 +#define GL_MAX_PROGRAM_LOOP_DEPTH_NV 0x88F7 +#define GL_MAX_PROGRAM_LOOP_COUNT_NV 0x88F8 +#endif + +#ifndef GL_NV_vertex_program2_option +/* reuse GL_MAX_PROGRAM_EXEC_INSTRUCTIONS_NV */ +/* reuse GL_MAX_PROGRAM_CALL_DEPTH_NV */ +#endif + +#ifndef GL_NV_vertex_program3 +/* reuse GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB */ +#endif + +#ifndef GL_EXT_framebuffer_object +#define GL_INVALID_FRAMEBUFFER_OPERATION_EXT 0x0506 +#define GL_MAX_RENDERBUFFER_SIZE_EXT 0x84E8 +#define GL_FRAMEBUFFER_BINDING_EXT 0x8CA6 +#define GL_RENDERBUFFER_BINDING_EXT 0x8CA7 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL_EXT 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE_EXT 0x8CD3 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET_EXT 0x8CD4 +#define GL_FRAMEBUFFER_COMPLETE_EXT 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT 0x8CD9 +#define GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT 0x8CDA +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED_EXT 0x8CDD +#define GL_MAX_COLOR_ATTACHMENTS_EXT 0x8CDF +#define GL_COLOR_ATTACHMENT0_EXT 0x8CE0 +#define GL_COLOR_ATTACHMENT1_EXT 0x8CE1 +#define GL_COLOR_ATTACHMENT2_EXT 0x8CE2 +#define GL_COLOR_ATTACHMENT3_EXT 0x8CE3 +#define GL_COLOR_ATTACHMENT4_EXT 0x8CE4 +#define GL_COLOR_ATTACHMENT5_EXT 0x8CE5 +#define GL_COLOR_ATTACHMENT6_EXT 0x8CE6 +#define GL_COLOR_ATTACHMENT7_EXT 0x8CE7 +#define GL_COLOR_ATTACHMENT8_EXT 0x8CE8 +#define GL_COLOR_ATTACHMENT9_EXT 0x8CE9 +#define GL_COLOR_ATTACHMENT10_EXT 0x8CEA +#define GL_COLOR_ATTACHMENT11_EXT 0x8CEB +#define GL_COLOR_ATTACHMENT12_EXT 0x8CEC +#define GL_COLOR_ATTACHMENT13_EXT 0x8CED +#define GL_COLOR_ATTACHMENT14_EXT 0x8CEE +#define GL_COLOR_ATTACHMENT15_EXT 0x8CEF +#define GL_DEPTH_ATTACHMENT_EXT 0x8D00 +#define GL_STENCIL_ATTACHMENT_EXT 0x8D20 +#define GL_FRAMEBUFFER_EXT 0x8D40 +#define GL_RENDERBUFFER_EXT 0x8D41 +#define GL_RENDERBUFFER_WIDTH_EXT 0x8D42 +#define GL_RENDERBUFFER_HEIGHT_EXT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT_EXT 0x8D44 +#define GL_STENCIL_INDEX1_EXT 0x8D46 +#define GL_STENCIL_INDEX4_EXT 0x8D47 +#define GL_STENCIL_INDEX8_EXT 0x8D48 +#define GL_STENCIL_INDEX16_EXT 0x8D49 +#define GL_RENDERBUFFER_RED_SIZE_EXT 0x8D50 +#define GL_RENDERBUFFER_GREEN_SIZE_EXT 0x8D51 +#define GL_RENDERBUFFER_BLUE_SIZE_EXT 0x8D52 +#define GL_RENDERBUFFER_ALPHA_SIZE_EXT 0x8D53 +#define GL_RENDERBUFFER_DEPTH_SIZE_EXT 0x8D54 +#define GL_RENDERBUFFER_STENCIL_SIZE_EXT 0x8D55 +#endif + +#ifndef GL_GREMEDY_string_marker +#endif + +#ifndef GL_EXT_packed_depth_stencil +#define GL_DEPTH_STENCIL_EXT 0x84F9 +#define GL_UNSIGNED_INT_24_8_EXT 0x84FA +#define GL_DEPTH24_STENCIL8_EXT 0x88F0 +#define GL_TEXTURE_STENCIL_SIZE_EXT 0x88F1 +#endif + +#ifndef GL_EXT_stencil_clear_tag +#define GL_STENCIL_TAG_BITS_EXT 0x88F2 +#define GL_STENCIL_CLEAR_TAG_VALUE_EXT 0x88F3 +#endif + +#ifndef GL_EXT_texture_sRGB +#define GL_SRGB_EXT 0x8C40 +#define GL_SRGB8_EXT 0x8C41 +#define GL_SRGB_ALPHA_EXT 0x8C42 +#define GL_SRGB8_ALPHA8_EXT 0x8C43 +#define GL_SLUMINANCE_ALPHA_EXT 0x8C44 +#define GL_SLUMINANCE8_ALPHA8_EXT 0x8C45 +#define GL_SLUMINANCE_EXT 0x8C46 +#define GL_SLUMINANCE8_EXT 0x8C47 +#define GL_COMPRESSED_SRGB_EXT 0x8C48 +#define GL_COMPRESSED_SRGB_ALPHA_EXT 0x8C49 +#define GL_COMPRESSED_SLUMINANCE_EXT 0x8C4A +#define GL_COMPRESSED_SLUMINANCE_ALPHA_EXT 0x8C4B +#define GL_COMPRESSED_SRGB_S3TC_DXT1_EXT 0x8C4C +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT 0x8C4D +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT 0x8C4E +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F +#endif + +#ifndef GL_EXT_framebuffer_blit +#define GL_READ_FRAMEBUFFER_EXT 0x8CA8 +#define GL_DRAW_FRAMEBUFFER_EXT 0x8CA9 +#define GL_DRAW_FRAMEBUFFER_BINDING_EXT GL_FRAMEBUFFER_BINDING_EXT +#define GL_READ_FRAMEBUFFER_BINDING_EXT 0x8CAA +#endif + +#ifndef GL_EXT_framebuffer_multisample +#define GL_RENDERBUFFER_SAMPLES_EXT 0x8CAB +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT 0x8D56 +#define GL_MAX_SAMPLES_EXT 0x8D57 +#endif + +#ifndef GL_MESAX_texture_stack +#define GL_TEXTURE_1D_STACK_MESAX 0x8759 +#define GL_TEXTURE_2D_STACK_MESAX 0x875A +#define GL_PROXY_TEXTURE_1D_STACK_MESAX 0x875B +#define GL_PROXY_TEXTURE_2D_STACK_MESAX 0x875C +#define GL_TEXTURE_1D_STACK_BINDING_MESAX 0x875D +#define GL_TEXTURE_2D_STACK_BINDING_MESAX 0x875E +#endif + +#ifndef GL_EXT_timer_query +#define GL_TIME_ELAPSED_EXT 0x88BF +#endif + +#ifndef GL_EXT_gpu_program_parameters +#endif + +#ifndef GL_APPLE_flush_buffer_range +#define GL_BUFFER_SERIALIZED_MODIFY_APPLE 0x8A12 +#define GL_BUFFER_FLUSHING_UNMAP_APPLE 0x8A13 +#endif + +#ifndef GL_NV_gpu_program4 +#define GL_MIN_PROGRAM_TEXEL_OFFSET_NV 0x8904 +#define GL_MAX_PROGRAM_TEXEL_OFFSET_NV 0x8905 +#define GL_PROGRAM_ATTRIB_COMPONENTS_NV 0x8906 +#define GL_PROGRAM_RESULT_COMPONENTS_NV 0x8907 +#define GL_MAX_PROGRAM_ATTRIB_COMPONENTS_NV 0x8908 +#define GL_MAX_PROGRAM_RESULT_COMPONENTS_NV 0x8909 +#define GL_MAX_PROGRAM_GENERIC_ATTRIBS_NV 0x8DA5 +#define GL_MAX_PROGRAM_GENERIC_RESULTS_NV 0x8DA6 +#endif + +#ifndef GL_NV_geometry_program4 +#define GL_LINES_ADJACENCY_EXT 0x000A +#define GL_LINE_STRIP_ADJACENCY_EXT 0x000B +#define GL_TRIANGLES_ADJACENCY_EXT 0x000C +#define GL_TRIANGLE_STRIP_ADJACENCY_EXT 0x000D +#define GL_GEOMETRY_PROGRAM_NV 0x8C26 +#define GL_MAX_PROGRAM_OUTPUT_VERTICES_NV 0x8C27 +#define GL_MAX_PROGRAM_TOTAL_OUTPUT_COMPONENTS_NV 0x8C28 +#define GL_GEOMETRY_VERTICES_OUT_EXT 0x8DDA +#define GL_GEOMETRY_INPUT_TYPE_EXT 0x8DDB +#define GL_GEOMETRY_OUTPUT_TYPE_EXT 0x8DDC +#define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_EXT 0x8C29 +#define GL_FRAMEBUFFER_ATTACHMENT_LAYERED_EXT 0x8DA7 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT 0x8DA8 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_COUNT_EXT 0x8DA9 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER_EXT 0x8CD4 +#define GL_PROGRAM_POINT_SIZE_EXT 0x8642 +#endif + +#ifndef GL_EXT_geometry_shader4 +#define GL_GEOMETRY_SHADER_EXT 0x8DD9 +/* reuse GL_GEOMETRY_VERTICES_OUT_EXT */ +/* reuse GL_GEOMETRY_INPUT_TYPE_EXT */ +/* reuse GL_GEOMETRY_OUTPUT_TYPE_EXT */ +/* reuse GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_EXT */ +#define GL_MAX_GEOMETRY_VARYING_COMPONENTS_EXT 0x8DDD +#define GL_MAX_VERTEX_VARYING_COMPONENTS_EXT 0x8DDE +#define GL_MAX_VARYING_COMPONENTS_EXT 0x8B4B +#define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS_EXT 0x8DDF +#define GL_MAX_GEOMETRY_OUTPUT_VERTICES_EXT 0x8DE0 +#define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_EXT 0x8DE1 +/* reuse GL_LINES_ADJACENCY_EXT */ +/* reuse GL_LINE_STRIP_ADJACENCY_EXT */ +/* reuse GL_TRIANGLES_ADJACENCY_EXT */ +/* reuse GL_TRIANGLE_STRIP_ADJACENCY_EXT */ +/* reuse GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT */ +/* reuse GL_FRAMEBUFFER_INCOMPLETE_LAYER_COUNT_EXT */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_LAYERED_EXT */ +/* reuse GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER_EXT */ +/* reuse GL_PROGRAM_POINT_SIZE_EXT */ +#endif + +#ifndef GL_NV_vertex_program4 +#define GL_VERTEX_ATTRIB_ARRAY_INTEGER_NV 0x88FD +#endif + +#ifndef GL_EXT_gpu_shader4 +#define GL_SAMPLER_1D_ARRAY_EXT 0x8DC0 +#define GL_SAMPLER_2D_ARRAY_EXT 0x8DC1 +#define GL_SAMPLER_BUFFER_EXT 0x8DC2 +#define GL_SAMPLER_1D_ARRAY_SHADOW_EXT 0x8DC3 +#define GL_SAMPLER_2D_ARRAY_SHADOW_EXT 0x8DC4 +#define GL_SAMPLER_CUBE_SHADOW_EXT 0x8DC5 +#define GL_UNSIGNED_INT_VEC2_EXT 0x8DC6 +#define GL_UNSIGNED_INT_VEC3_EXT 0x8DC7 +#define GL_UNSIGNED_INT_VEC4_EXT 0x8DC8 +#define GL_INT_SAMPLER_1D_EXT 0x8DC9 +#define GL_INT_SAMPLER_2D_EXT 0x8DCA +#define GL_INT_SAMPLER_3D_EXT 0x8DCB +#define GL_INT_SAMPLER_CUBE_EXT 0x8DCC +#define GL_INT_SAMPLER_2D_RECT_EXT 0x8DCD +#define GL_INT_SAMPLER_1D_ARRAY_EXT 0x8DCE +#define GL_INT_SAMPLER_2D_ARRAY_EXT 0x8DCF +#define GL_INT_SAMPLER_BUFFER_EXT 0x8DD0 +#define GL_UNSIGNED_INT_SAMPLER_1D_EXT 0x8DD1 +#define GL_UNSIGNED_INT_SAMPLER_2D_EXT 0x8DD2 +#define GL_UNSIGNED_INT_SAMPLER_3D_EXT 0x8DD3 +#define GL_UNSIGNED_INT_SAMPLER_CUBE_EXT 0x8DD4 +#define GL_UNSIGNED_INT_SAMPLER_2D_RECT_EXT 0x8DD5 +#define GL_UNSIGNED_INT_SAMPLER_1D_ARRAY_EXT 0x8DD6 +#define GL_UNSIGNED_INT_SAMPLER_2D_ARRAY_EXT 0x8DD7 +#define GL_UNSIGNED_INT_SAMPLER_BUFFER_EXT 0x8DD8 +#endif + +#ifndef GL_EXT_draw_instanced +#endif + +#ifndef GL_EXT_packed_float +#define GL_R11F_G11F_B10F_EXT 0x8C3A +#define GL_UNSIGNED_INT_10F_11F_11F_REV_EXT 0x8C3B +#define GL_RGBA_SIGNED_COMPONENTS_EXT 0x8C3C +#endif + +#ifndef GL_EXT_texture_array +#define GL_TEXTURE_1D_ARRAY_EXT 0x8C18 +#define GL_PROXY_TEXTURE_1D_ARRAY_EXT 0x8C19 +#define GL_TEXTURE_2D_ARRAY_EXT 0x8C1A +#define GL_PROXY_TEXTURE_2D_ARRAY_EXT 0x8C1B +#define GL_TEXTURE_BINDING_1D_ARRAY_EXT 0x8C1C +#define GL_TEXTURE_BINDING_2D_ARRAY_EXT 0x8C1D +#define GL_MAX_ARRAY_TEXTURE_LAYERS_EXT 0x88FF +#define GL_COMPARE_REF_DEPTH_TO_TEXTURE_EXT 0x884E +/* reuse GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER_EXT */ +#endif + +#ifndef GL_EXT_texture_buffer_object +#define GL_TEXTURE_BUFFER_EXT 0x8C2A +#define GL_MAX_TEXTURE_BUFFER_SIZE_EXT 0x8C2B +#define GL_TEXTURE_BINDING_BUFFER_EXT 0x8C2C +#define GL_TEXTURE_BUFFER_DATA_STORE_BINDING_EXT 0x8C2D +#define GL_TEXTURE_BUFFER_FORMAT_EXT 0x8C2E +#endif + +#ifndef GL_EXT_texture_compression_latc +#define GL_COMPRESSED_LUMINANCE_LATC1_EXT 0x8C70 +#define GL_COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT 0x8C71 +#define GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT 0x8C72 +#define GL_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT 0x8C73 +#endif + +#ifndef GL_EXT_texture_compression_rgtc +#define GL_COMPRESSED_RED_RGTC1_EXT 0x8DBB +#define GL_COMPRESSED_SIGNED_RED_RGTC1_EXT 0x8DBC +#define GL_COMPRESSED_RED_GREEN_RGTC2_EXT 0x8DBD +#define GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT 0x8DBE +#endif + +#ifndef GL_EXT_texture_shared_exponent +#define GL_RGB9_E5_EXT 0x8C3D +#define GL_UNSIGNED_INT_5_9_9_9_REV_EXT 0x8C3E +#define GL_TEXTURE_SHARED_SIZE_EXT 0x8C3F +#endif + +#ifndef GL_NV_depth_buffer_float +#define GL_DEPTH_COMPONENT32F_NV 0x8DAB +#define GL_DEPTH32F_STENCIL8_NV 0x8DAC +#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV_NV 0x8DAD +#define GL_DEPTH_BUFFER_FLOAT_MODE_NV 0x8DAF +#endif + +#ifndef GL_NV_fragment_program4 +#endif + +#ifndef GL_NV_framebuffer_multisample_coverage +#define GL_RENDERBUFFER_COVERAGE_SAMPLES_NV 0x8CAB +#define GL_RENDERBUFFER_COLOR_SAMPLES_NV 0x8E10 +#define GL_MAX_MULTISAMPLE_COVERAGE_MODES_NV 0x8E11 +#define GL_MULTISAMPLE_COVERAGE_MODES_NV 0x8E12 +#endif + +#ifndef GL_EXT_framebuffer_sRGB +#define GL_FRAMEBUFFER_SRGB_EXT 0x8DB9 +#define GL_FRAMEBUFFER_SRGB_CAPABLE_EXT 0x8DBA +#endif + +#ifndef GL_NV_geometry_shader4 +#endif + +#ifndef GL_NV_parameter_buffer_object +#define GL_MAX_PROGRAM_PARAMETER_BUFFER_BINDINGS_NV 0x8DA0 +#define GL_MAX_PROGRAM_PARAMETER_BUFFER_SIZE_NV 0x8DA1 +#define GL_VERTEX_PROGRAM_PARAMETER_BUFFER_NV 0x8DA2 +#define GL_GEOMETRY_PROGRAM_PARAMETER_BUFFER_NV 0x8DA3 +#define GL_FRAGMENT_PROGRAM_PARAMETER_BUFFER_NV 0x8DA4 +#endif + +#ifndef GL_EXT_draw_buffers2 +#endif + +#ifndef GL_NV_transform_feedback +#define GL_BACK_PRIMARY_COLOR_NV 0x8C77 +#define GL_BACK_SECONDARY_COLOR_NV 0x8C78 +#define GL_TEXTURE_COORD_NV 0x8C79 +#define GL_CLIP_DISTANCE_NV 0x8C7A +#define GL_VERTEX_ID_NV 0x8C7B +#define GL_PRIMITIVE_ID_NV 0x8C7C +#define GL_GENERIC_ATTRIB_NV 0x8C7D +#define GL_TRANSFORM_FEEDBACK_ATTRIBS_NV 0x8C7E +#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE_NV 0x8C7F +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS_NV 0x8C80 +#define GL_ACTIVE_VARYINGS_NV 0x8C81 +#define GL_ACTIVE_VARYING_MAX_LENGTH_NV 0x8C82 +#define GL_TRANSFORM_FEEDBACK_VARYINGS_NV 0x8C83 +#define GL_TRANSFORM_FEEDBACK_BUFFER_START_NV 0x8C84 +#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE_NV 0x8C85 +#define GL_TRANSFORM_FEEDBACK_RECORD_NV 0x8C86 +#define GL_PRIMITIVES_GENERATED_NV 0x8C87 +#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_NV 0x8C88 +#define GL_RASTERIZER_DISCARD_NV 0x8C89 +#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_ATTRIBS_NV 0x8C8A +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS_NV 0x8C8B +#define GL_INTERLEAVED_ATTRIBS_NV 0x8C8C +#define GL_SEPARATE_ATTRIBS_NV 0x8C8D +#define GL_TRANSFORM_FEEDBACK_BUFFER_NV 0x8C8E +#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING_NV 0x8C8F +#define GL_LAYER_NV 0x8DAA +#define GL_NEXT_BUFFER_NV -2 +#define GL_SKIP_COMPONENTS4_NV -3 +#define GL_SKIP_COMPONENTS3_NV -4 +#define GL_SKIP_COMPONENTS2_NV -5 +#define GL_SKIP_COMPONENTS1_NV -6 +#endif + +#ifndef GL_EXT_bindable_uniform +#define GL_MAX_VERTEX_BINDABLE_UNIFORMS_EXT 0x8DE2 +#define GL_MAX_FRAGMENT_BINDABLE_UNIFORMS_EXT 0x8DE3 +#define GL_MAX_GEOMETRY_BINDABLE_UNIFORMS_EXT 0x8DE4 +#define GL_MAX_BINDABLE_UNIFORM_SIZE_EXT 0x8DED +#define GL_UNIFORM_BUFFER_EXT 0x8DEE +#define GL_UNIFORM_BUFFER_BINDING_EXT 0x8DEF +#endif + +#ifndef GL_EXT_texture_integer +#define GL_RGBA32UI_EXT 0x8D70 +#define GL_RGB32UI_EXT 0x8D71 +#define GL_ALPHA32UI_EXT 0x8D72 +#define GL_INTENSITY32UI_EXT 0x8D73 +#define GL_LUMINANCE32UI_EXT 0x8D74 +#define GL_LUMINANCE_ALPHA32UI_EXT 0x8D75 +#define GL_RGBA16UI_EXT 0x8D76 +#define GL_RGB16UI_EXT 0x8D77 +#define GL_ALPHA16UI_EXT 0x8D78 +#define GL_INTENSITY16UI_EXT 0x8D79 +#define GL_LUMINANCE16UI_EXT 0x8D7A +#define GL_LUMINANCE_ALPHA16UI_EXT 0x8D7B +#define GL_RGBA8UI_EXT 0x8D7C +#define GL_RGB8UI_EXT 0x8D7D +#define GL_ALPHA8UI_EXT 0x8D7E +#define GL_INTENSITY8UI_EXT 0x8D7F +#define GL_LUMINANCE8UI_EXT 0x8D80 +#define GL_LUMINANCE_ALPHA8UI_EXT 0x8D81 +#define GL_RGBA32I_EXT 0x8D82 +#define GL_RGB32I_EXT 0x8D83 +#define GL_ALPHA32I_EXT 0x8D84 +#define GL_INTENSITY32I_EXT 0x8D85 +#define GL_LUMINANCE32I_EXT 0x8D86 +#define GL_LUMINANCE_ALPHA32I_EXT 0x8D87 +#define GL_RGBA16I_EXT 0x8D88 +#define GL_RGB16I_EXT 0x8D89 +#define GL_ALPHA16I_EXT 0x8D8A +#define GL_INTENSITY16I_EXT 0x8D8B +#define GL_LUMINANCE16I_EXT 0x8D8C +#define GL_LUMINANCE_ALPHA16I_EXT 0x8D8D +#define GL_RGBA8I_EXT 0x8D8E +#define GL_RGB8I_EXT 0x8D8F +#define GL_ALPHA8I_EXT 0x8D90 +#define GL_INTENSITY8I_EXT 0x8D91 +#define GL_LUMINANCE8I_EXT 0x8D92 +#define GL_LUMINANCE_ALPHA8I_EXT 0x8D93 +#define GL_RED_INTEGER_EXT 0x8D94 +#define GL_GREEN_INTEGER_EXT 0x8D95 +#define GL_BLUE_INTEGER_EXT 0x8D96 +#define GL_ALPHA_INTEGER_EXT 0x8D97 +#define GL_RGB_INTEGER_EXT 0x8D98 +#define GL_RGBA_INTEGER_EXT 0x8D99 +#define GL_BGR_INTEGER_EXT 0x8D9A +#define GL_BGRA_INTEGER_EXT 0x8D9B +#define GL_LUMINANCE_INTEGER_EXT 0x8D9C +#define GL_LUMINANCE_ALPHA_INTEGER_EXT 0x8D9D +#define GL_RGBA_INTEGER_MODE_EXT 0x8D9E +#endif + +#ifndef GL_GREMEDY_frame_terminator +#endif + +#ifndef GL_NV_conditional_render +#define GL_QUERY_WAIT_NV 0x8E13 +#define GL_QUERY_NO_WAIT_NV 0x8E14 +#define GL_QUERY_BY_REGION_WAIT_NV 0x8E15 +#define GL_QUERY_BY_REGION_NO_WAIT_NV 0x8E16 +#endif + +#ifndef GL_NV_present_video +#define GL_FRAME_NV 0x8E26 +#define GL_FIELDS_NV 0x8E27 +#define GL_CURRENT_TIME_NV 0x8E28 +#define GL_NUM_FILL_STREAMS_NV 0x8E29 +#define GL_PRESENT_TIME_NV 0x8E2A +#define GL_PRESENT_DURATION_NV 0x8E2B +#endif + +#ifndef GL_EXT_transform_feedback +#define GL_TRANSFORM_FEEDBACK_BUFFER_EXT 0x8C8E +#define GL_TRANSFORM_FEEDBACK_BUFFER_START_EXT 0x8C84 +#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE_EXT 0x8C85 +#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING_EXT 0x8C8F +#define GL_INTERLEAVED_ATTRIBS_EXT 0x8C8C +#define GL_SEPARATE_ATTRIBS_EXT 0x8C8D +#define GL_PRIMITIVES_GENERATED_EXT 0x8C87 +#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_EXT 0x8C88 +#define GL_RASTERIZER_DISCARD_EXT 0x8C89 +#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS_EXT 0x8C8A +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS_EXT 0x8C8B +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS_EXT 0x8C80 +#define GL_TRANSFORM_FEEDBACK_VARYINGS_EXT 0x8C83 +#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE_EXT 0x8C7F +#define GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH_EXT 0x8C76 +#endif + +#ifndef GL_EXT_direct_state_access +#define GL_PROGRAM_MATRIX_EXT 0x8E2D +#define GL_TRANSPOSE_PROGRAM_MATRIX_EXT 0x8E2E +#define GL_PROGRAM_MATRIX_STACK_DEPTH_EXT 0x8E2F +#endif + +#ifndef GL_EXT_vertex_array_bgra +/* reuse GL_BGRA */ +#endif + +#ifndef GL_EXT_texture_swizzle +#define GL_TEXTURE_SWIZZLE_R_EXT 0x8E42 +#define GL_TEXTURE_SWIZZLE_G_EXT 0x8E43 +#define GL_TEXTURE_SWIZZLE_B_EXT 0x8E44 +#define GL_TEXTURE_SWIZZLE_A_EXT 0x8E45 +#define GL_TEXTURE_SWIZZLE_RGBA_EXT 0x8E46 +#endif + +#ifndef GL_NV_explicit_multisample +#define GL_SAMPLE_POSITION_NV 0x8E50 +#define GL_SAMPLE_MASK_NV 0x8E51 +#define GL_SAMPLE_MASK_VALUE_NV 0x8E52 +#define GL_TEXTURE_BINDING_RENDERBUFFER_NV 0x8E53 +#define GL_TEXTURE_RENDERBUFFER_DATA_STORE_BINDING_NV 0x8E54 +#define GL_TEXTURE_RENDERBUFFER_NV 0x8E55 +#define GL_SAMPLER_RENDERBUFFER_NV 0x8E56 +#define GL_INT_SAMPLER_RENDERBUFFER_NV 0x8E57 +#define GL_UNSIGNED_INT_SAMPLER_RENDERBUFFER_NV 0x8E58 +#define GL_MAX_SAMPLE_MASK_WORDS_NV 0x8E59 +#endif + +#ifndef GL_NV_transform_feedback2 +#define GL_TRANSFORM_FEEDBACK_NV 0x8E22 +#define GL_TRANSFORM_FEEDBACK_BUFFER_PAUSED_NV 0x8E23 +#define GL_TRANSFORM_FEEDBACK_BUFFER_ACTIVE_NV 0x8E24 +#define GL_TRANSFORM_FEEDBACK_BINDING_NV 0x8E25 +#endif + +#ifndef GL_ATI_meminfo +#define GL_VBO_FREE_MEMORY_ATI 0x87FB +#define GL_TEXTURE_FREE_MEMORY_ATI 0x87FC +#define GL_RENDERBUFFER_FREE_MEMORY_ATI 0x87FD +#endif + +#ifndef GL_AMD_performance_monitor +#define GL_COUNTER_TYPE_AMD 0x8BC0 +#define GL_COUNTER_RANGE_AMD 0x8BC1 +#define GL_UNSIGNED_INT64_AMD 0x8BC2 +#define GL_PERCENTAGE_AMD 0x8BC3 +#define GL_PERFMON_RESULT_AVAILABLE_AMD 0x8BC4 +#define GL_PERFMON_RESULT_SIZE_AMD 0x8BC5 +#define GL_PERFMON_RESULT_AMD 0x8BC6 +#endif + +#ifndef GL_AMD_texture_texture4 +#endif + +#ifndef GL_AMD_vertex_shader_tesselator +#define GL_SAMPLER_BUFFER_AMD 0x9001 +#define GL_INT_SAMPLER_BUFFER_AMD 0x9002 +#define GL_UNSIGNED_INT_SAMPLER_BUFFER_AMD 0x9003 +#define GL_TESSELLATION_MODE_AMD 0x9004 +#define GL_TESSELLATION_FACTOR_AMD 0x9005 +#define GL_DISCRETE_AMD 0x9006 +#define GL_CONTINUOUS_AMD 0x9007 +#endif + +#ifndef GL_EXT_provoking_vertex +#define GL_QUADS_FOLLOW_PROVOKING_VERTEX_CONVENTION_EXT 0x8E4C +#define GL_FIRST_VERTEX_CONVENTION_EXT 0x8E4D +#define GL_LAST_VERTEX_CONVENTION_EXT 0x8E4E +#define GL_PROVOKING_VERTEX_EXT 0x8E4F +#endif + +#ifndef GL_EXT_texture_snorm +#define GL_ALPHA_SNORM 0x9010 +#define GL_LUMINANCE_SNORM 0x9011 +#define GL_LUMINANCE_ALPHA_SNORM 0x9012 +#define GL_INTENSITY_SNORM 0x9013 +#define GL_ALPHA8_SNORM 0x9014 +#define GL_LUMINANCE8_SNORM 0x9015 +#define GL_LUMINANCE8_ALPHA8_SNORM 0x9016 +#define GL_INTENSITY8_SNORM 0x9017 +#define GL_ALPHA16_SNORM 0x9018 +#define GL_LUMINANCE16_SNORM 0x9019 +#define GL_LUMINANCE16_ALPHA16_SNORM 0x901A +#define GL_INTENSITY16_SNORM 0x901B +/* reuse GL_RED_SNORM */ +/* reuse GL_RG_SNORM */ +/* reuse GL_RGB_SNORM */ +/* reuse GL_RGBA_SNORM */ +/* reuse GL_R8_SNORM */ +/* reuse GL_RG8_SNORM */ +/* reuse GL_RGB8_SNORM */ +/* reuse GL_RGBA8_SNORM */ +/* reuse GL_R16_SNORM */ +/* reuse GL_RG16_SNORM */ +/* reuse GL_RGB16_SNORM */ +/* reuse GL_RGBA16_SNORM */ +/* reuse GL_SIGNED_NORMALIZED */ +#endif + +#ifndef GL_AMD_draw_buffers_blend +#endif + +#ifndef GL_APPLE_texture_range +#define GL_TEXTURE_RANGE_LENGTH_APPLE 0x85B7 +#define GL_TEXTURE_RANGE_POINTER_APPLE 0x85B8 +#define GL_TEXTURE_STORAGE_HINT_APPLE 0x85BC +#define GL_STORAGE_PRIVATE_APPLE 0x85BD +/* reuse GL_STORAGE_CACHED_APPLE */ +/* reuse GL_STORAGE_SHARED_APPLE */ +#endif + +#ifndef GL_APPLE_float_pixels +#define GL_HALF_APPLE 0x140B +#define GL_RGBA_FLOAT32_APPLE 0x8814 +#define GL_RGB_FLOAT32_APPLE 0x8815 +#define GL_ALPHA_FLOAT32_APPLE 0x8816 +#define GL_INTENSITY_FLOAT32_APPLE 0x8817 +#define GL_LUMINANCE_FLOAT32_APPLE 0x8818 +#define GL_LUMINANCE_ALPHA_FLOAT32_APPLE 0x8819 +#define GL_RGBA_FLOAT16_APPLE 0x881A +#define GL_RGB_FLOAT16_APPLE 0x881B +#define GL_ALPHA_FLOAT16_APPLE 0x881C +#define GL_INTENSITY_FLOAT16_APPLE 0x881D +#define GL_LUMINANCE_FLOAT16_APPLE 0x881E +#define GL_LUMINANCE_ALPHA_FLOAT16_APPLE 0x881F +#define GL_COLOR_FLOAT_APPLE 0x8A0F +#endif + +#ifndef GL_APPLE_vertex_program_evaluators +#define GL_VERTEX_ATTRIB_MAP1_APPLE 0x8A00 +#define GL_VERTEX_ATTRIB_MAP2_APPLE 0x8A01 +#define GL_VERTEX_ATTRIB_MAP1_SIZE_APPLE 0x8A02 +#define GL_VERTEX_ATTRIB_MAP1_COEFF_APPLE 0x8A03 +#define GL_VERTEX_ATTRIB_MAP1_ORDER_APPLE 0x8A04 +#define GL_VERTEX_ATTRIB_MAP1_DOMAIN_APPLE 0x8A05 +#define GL_VERTEX_ATTRIB_MAP2_SIZE_APPLE 0x8A06 +#define GL_VERTEX_ATTRIB_MAP2_COEFF_APPLE 0x8A07 +#define GL_VERTEX_ATTRIB_MAP2_ORDER_APPLE 0x8A08 +#define GL_VERTEX_ATTRIB_MAP2_DOMAIN_APPLE 0x8A09 +#endif + +#ifndef GL_APPLE_aux_depth_stencil +#define GL_AUX_DEPTH_STENCIL_APPLE 0x8A14 +#endif + +#ifndef GL_APPLE_object_purgeable +#define GL_BUFFER_OBJECT_APPLE 0x85B3 +#define GL_RELEASED_APPLE 0x8A19 +#define GL_VOLATILE_APPLE 0x8A1A +#define GL_RETAINED_APPLE 0x8A1B +#define GL_UNDEFINED_APPLE 0x8A1C +#define GL_PURGEABLE_APPLE 0x8A1D +#endif + +#ifndef GL_APPLE_row_bytes +#define GL_PACK_ROW_BYTES_APPLE 0x8A15 +#define GL_UNPACK_ROW_BYTES_APPLE 0x8A16 +#endif + +#ifndef GL_APPLE_rgb_422 +#define GL_RGB_422_APPLE 0x8A1F +/* reuse GL_UNSIGNED_SHORT_8_8_APPLE */ +/* reuse GL_UNSIGNED_SHORT_8_8_REV_APPLE */ +#endif + +#ifndef GL_NV_video_capture +#define GL_VIDEO_BUFFER_NV 0x9020 +#define GL_VIDEO_BUFFER_BINDING_NV 0x9021 +#define GL_FIELD_UPPER_NV 0x9022 +#define GL_FIELD_LOWER_NV 0x9023 +#define GL_NUM_VIDEO_CAPTURE_STREAMS_NV 0x9024 +#define GL_NEXT_VIDEO_CAPTURE_BUFFER_STATUS_NV 0x9025 +#define GL_VIDEO_CAPTURE_TO_422_SUPPORTED_NV 0x9026 +#define GL_LAST_VIDEO_CAPTURE_STATUS_NV 0x9027 +#define GL_VIDEO_BUFFER_PITCH_NV 0x9028 +#define GL_VIDEO_COLOR_CONVERSION_MATRIX_NV 0x9029 +#define GL_VIDEO_COLOR_CONVERSION_MAX_NV 0x902A +#define GL_VIDEO_COLOR_CONVERSION_MIN_NV 0x902B +#define GL_VIDEO_COLOR_CONVERSION_OFFSET_NV 0x902C +#define GL_VIDEO_BUFFER_INTERNAL_FORMAT_NV 0x902D +#define GL_PARTIAL_SUCCESS_NV 0x902E +#define GL_SUCCESS_NV 0x902F +#define GL_FAILURE_NV 0x9030 +#define GL_YCBYCR8_422_NV 0x9031 +#define GL_YCBAYCR8A_4224_NV 0x9032 +#define GL_Z6Y10Z6CB10Z6Y10Z6CR10_422_NV 0x9033 +#define GL_Z6Y10Z6CB10Z6A10Z6Y10Z6CR10Z6A10_4224_NV 0x9034 +#define GL_Z4Y12Z4CB12Z4Y12Z4CR12_422_NV 0x9035 +#define GL_Z4Y12Z4CB12Z4A12Z4Y12Z4CR12Z4A12_4224_NV 0x9036 +#define GL_Z4Y12Z4CB12Z4CR12_444_NV 0x9037 +#define GL_VIDEO_CAPTURE_FRAME_WIDTH_NV 0x9038 +#define GL_VIDEO_CAPTURE_FRAME_HEIGHT_NV 0x9039 +#define GL_VIDEO_CAPTURE_FIELD_UPPER_HEIGHT_NV 0x903A +#define GL_VIDEO_CAPTURE_FIELD_LOWER_HEIGHT_NV 0x903B +#define GL_VIDEO_CAPTURE_SURFACE_ORIGIN_NV 0x903C +#endif + +#ifndef GL_NV_copy_image +#endif + +#ifndef GL_EXT_separate_shader_objects +#define GL_ACTIVE_PROGRAM_EXT 0x8B8D +#endif + +#ifndef GL_NV_parameter_buffer_object2 +#endif + +#ifndef GL_NV_shader_buffer_load +#define GL_BUFFER_GPU_ADDRESS_NV 0x8F1D +#define GL_GPU_ADDRESS_NV 0x8F34 +#define GL_MAX_SHADER_BUFFER_ADDRESS_NV 0x8F35 +#endif + +#ifndef GL_NV_vertex_buffer_unified_memory +#define GL_VERTEX_ATTRIB_ARRAY_UNIFIED_NV 0x8F1E +#define GL_ELEMENT_ARRAY_UNIFIED_NV 0x8F1F +#define GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV 0x8F20 +#define GL_VERTEX_ARRAY_ADDRESS_NV 0x8F21 +#define GL_NORMAL_ARRAY_ADDRESS_NV 0x8F22 +#define GL_COLOR_ARRAY_ADDRESS_NV 0x8F23 +#define GL_INDEX_ARRAY_ADDRESS_NV 0x8F24 +#define GL_TEXTURE_COORD_ARRAY_ADDRESS_NV 0x8F25 +#define GL_EDGE_FLAG_ARRAY_ADDRESS_NV 0x8F26 +#define GL_SECONDARY_COLOR_ARRAY_ADDRESS_NV 0x8F27 +#define GL_FOG_COORD_ARRAY_ADDRESS_NV 0x8F28 +#define GL_ELEMENT_ARRAY_ADDRESS_NV 0x8F29 +#define GL_VERTEX_ATTRIB_ARRAY_LENGTH_NV 0x8F2A +#define GL_VERTEX_ARRAY_LENGTH_NV 0x8F2B +#define GL_NORMAL_ARRAY_LENGTH_NV 0x8F2C +#define GL_COLOR_ARRAY_LENGTH_NV 0x8F2D +#define GL_INDEX_ARRAY_LENGTH_NV 0x8F2E +#define GL_TEXTURE_COORD_ARRAY_LENGTH_NV 0x8F2F +#define GL_EDGE_FLAG_ARRAY_LENGTH_NV 0x8F30 +#define GL_SECONDARY_COLOR_ARRAY_LENGTH_NV 0x8F31 +#define GL_FOG_COORD_ARRAY_LENGTH_NV 0x8F32 +#define GL_ELEMENT_ARRAY_LENGTH_NV 0x8F33 +#define GL_DRAW_INDIRECT_UNIFIED_NV 0x8F40 +#define GL_DRAW_INDIRECT_ADDRESS_NV 0x8F41 +#define GL_DRAW_INDIRECT_LENGTH_NV 0x8F42 +#endif + +#ifndef GL_NV_texture_barrier +#endif + +#ifndef GL_AMD_shader_stencil_export +#endif + +#ifndef GL_AMD_seamless_cubemap_per_texture +/* reuse GL_TEXTURE_CUBE_MAP_SEAMLESS_ARB */ +#endif + +#ifndef GL_AMD_conservative_depth +#endif + +#ifndef GL_EXT_shader_image_load_store +#define GL_MAX_IMAGE_UNITS_EXT 0x8F38 +#define GL_MAX_COMBINED_IMAGE_UNITS_AND_FRAGMENT_OUTPUTS_EXT 0x8F39 +#define GL_IMAGE_BINDING_NAME_EXT 0x8F3A +#define GL_IMAGE_BINDING_LEVEL_EXT 0x8F3B +#define GL_IMAGE_BINDING_LAYERED_EXT 0x8F3C +#define GL_IMAGE_BINDING_LAYER_EXT 0x8F3D +#define GL_IMAGE_BINDING_ACCESS_EXT 0x8F3E +#define GL_IMAGE_1D_EXT 0x904C +#define GL_IMAGE_2D_EXT 0x904D +#define GL_IMAGE_3D_EXT 0x904E +#define GL_IMAGE_2D_RECT_EXT 0x904F +#define GL_IMAGE_CUBE_EXT 0x9050 +#define GL_IMAGE_BUFFER_EXT 0x9051 +#define GL_IMAGE_1D_ARRAY_EXT 0x9052 +#define GL_IMAGE_2D_ARRAY_EXT 0x9053 +#define GL_IMAGE_CUBE_MAP_ARRAY_EXT 0x9054 +#define GL_IMAGE_2D_MULTISAMPLE_EXT 0x9055 +#define GL_IMAGE_2D_MULTISAMPLE_ARRAY_EXT 0x9056 +#define GL_INT_IMAGE_1D_EXT 0x9057 +#define GL_INT_IMAGE_2D_EXT 0x9058 +#define GL_INT_IMAGE_3D_EXT 0x9059 +#define GL_INT_IMAGE_2D_RECT_EXT 0x905A +#define GL_INT_IMAGE_CUBE_EXT 0x905B +#define GL_INT_IMAGE_BUFFER_EXT 0x905C +#define GL_INT_IMAGE_1D_ARRAY_EXT 0x905D +#define GL_INT_IMAGE_2D_ARRAY_EXT 0x905E +#define GL_INT_IMAGE_CUBE_MAP_ARRAY_EXT 0x905F +#define GL_INT_IMAGE_2D_MULTISAMPLE_EXT 0x9060 +#define GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY_EXT 0x9061 +#define GL_UNSIGNED_INT_IMAGE_1D_EXT 0x9062 +#define GL_UNSIGNED_INT_IMAGE_2D_EXT 0x9063 +#define GL_UNSIGNED_INT_IMAGE_3D_EXT 0x9064 +#define GL_UNSIGNED_INT_IMAGE_2D_RECT_EXT 0x9065 +#define GL_UNSIGNED_INT_IMAGE_CUBE_EXT 0x9066 +#define GL_UNSIGNED_INT_IMAGE_BUFFER_EXT 0x9067 +#define GL_UNSIGNED_INT_IMAGE_1D_ARRAY_EXT 0x9068 +#define GL_UNSIGNED_INT_IMAGE_2D_ARRAY_EXT 0x9069 +#define GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY_EXT 0x906A +#define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_EXT 0x906B +#define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY_EXT 0x906C +#define GL_MAX_IMAGE_SAMPLES_EXT 0x906D +#define GL_IMAGE_BINDING_FORMAT_EXT 0x906E +#define GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT_EXT 0x00000001 +#define GL_ELEMENT_ARRAY_BARRIER_BIT_EXT 0x00000002 +#define GL_UNIFORM_BARRIER_BIT_EXT 0x00000004 +#define GL_TEXTURE_FETCH_BARRIER_BIT_EXT 0x00000008 +#define GL_SHADER_IMAGE_ACCESS_BARRIER_BIT_EXT 0x00000020 +#define GL_COMMAND_BARRIER_BIT_EXT 0x00000040 +#define GL_PIXEL_BUFFER_BARRIER_BIT_EXT 0x00000080 +#define GL_TEXTURE_UPDATE_BARRIER_BIT_EXT 0x00000100 +#define GL_BUFFER_UPDATE_BARRIER_BIT_EXT 0x00000200 +#define GL_FRAMEBUFFER_BARRIER_BIT_EXT 0x00000400 +#define GL_TRANSFORM_FEEDBACK_BARRIER_BIT_EXT 0x00000800 +#define GL_ATOMIC_COUNTER_BARRIER_BIT_EXT 0x00001000 +#define GL_ALL_BARRIER_BITS_EXT 0xFFFFFFFF +#endif + +#ifndef GL_EXT_vertex_attrib_64bit +/* reuse GL_DOUBLE */ +#define GL_DOUBLE_VEC2_EXT 0x8FFC +#define GL_DOUBLE_VEC3_EXT 0x8FFD +#define GL_DOUBLE_VEC4_EXT 0x8FFE +#define GL_DOUBLE_MAT2_EXT 0x8F46 +#define GL_DOUBLE_MAT3_EXT 0x8F47 +#define GL_DOUBLE_MAT4_EXT 0x8F48 +#define GL_DOUBLE_MAT2x3_EXT 0x8F49 +#define GL_DOUBLE_MAT2x4_EXT 0x8F4A +#define GL_DOUBLE_MAT3x2_EXT 0x8F4B +#define GL_DOUBLE_MAT3x4_EXT 0x8F4C +#define GL_DOUBLE_MAT4x2_EXT 0x8F4D +#define GL_DOUBLE_MAT4x3_EXT 0x8F4E +#endif + +#ifndef GL_NV_gpu_program5 +#define GL_MAX_GEOMETRY_PROGRAM_INVOCATIONS_NV 0x8E5A +#define GL_MIN_FRAGMENT_INTERPOLATION_OFFSET_NV 0x8E5B +#define GL_MAX_FRAGMENT_INTERPOLATION_OFFSET_NV 0x8E5C +#define GL_FRAGMENT_PROGRAM_INTERPOLATION_OFFSET_BITS_NV 0x8E5D +#define GL_MAX_PROGRAM_SUBROUTINE_PARAMETERS_NV 0x8F44 +#define GL_MAX_PROGRAM_SUBROUTINE_NUM_NV 0x8F45 +#endif + +#ifndef GL_NV_gpu_shader5 +#define GL_INT64_NV 0x140E +#define GL_UNSIGNED_INT64_NV 0x140F +#define GL_INT8_NV 0x8FE0 +#define GL_INT8_VEC2_NV 0x8FE1 +#define GL_INT8_VEC3_NV 0x8FE2 +#define GL_INT8_VEC4_NV 0x8FE3 +#define GL_INT16_NV 0x8FE4 +#define GL_INT16_VEC2_NV 0x8FE5 +#define GL_INT16_VEC3_NV 0x8FE6 +#define GL_INT16_VEC4_NV 0x8FE7 +#define GL_INT64_VEC2_NV 0x8FE9 +#define GL_INT64_VEC3_NV 0x8FEA +#define GL_INT64_VEC4_NV 0x8FEB +#define GL_UNSIGNED_INT8_NV 0x8FEC +#define GL_UNSIGNED_INT8_VEC2_NV 0x8FED +#define GL_UNSIGNED_INT8_VEC3_NV 0x8FEE +#define GL_UNSIGNED_INT8_VEC4_NV 0x8FEF +#define GL_UNSIGNED_INT16_NV 0x8FF0 +#define GL_UNSIGNED_INT16_VEC2_NV 0x8FF1 +#define GL_UNSIGNED_INT16_VEC3_NV 0x8FF2 +#define GL_UNSIGNED_INT16_VEC4_NV 0x8FF3 +#define GL_UNSIGNED_INT64_VEC2_NV 0x8FF5 +#define GL_UNSIGNED_INT64_VEC3_NV 0x8FF6 +#define GL_UNSIGNED_INT64_VEC4_NV 0x8FF7 +#define GL_FLOAT16_NV 0x8FF8 +#define GL_FLOAT16_VEC2_NV 0x8FF9 +#define GL_FLOAT16_VEC3_NV 0x8FFA +#define GL_FLOAT16_VEC4_NV 0x8FFB +/* reuse GL_PATCHES */ +#endif + +#ifndef GL_NV_shader_buffer_store +#define GL_SHADER_GLOBAL_ACCESS_BARRIER_BIT_NV 0x00000010 +/* reuse GL_READ_WRITE */ +/* reuse GL_WRITE_ONLY */ +#endif + +#ifndef GL_NV_tessellation_program5 +#define GL_MAX_PROGRAM_PATCH_ATTRIBS_NV 0x86D8 +#define GL_TESS_CONTROL_PROGRAM_NV 0x891E +#define GL_TESS_EVALUATION_PROGRAM_NV 0x891F +#define GL_TESS_CONTROL_PROGRAM_PARAMETER_BUFFER_NV 0x8C74 +#define GL_TESS_EVALUATION_PROGRAM_PARAMETER_BUFFER_NV 0x8C75 +#endif + +#ifndef GL_NV_vertex_attrib_integer_64bit +/* reuse GL_INT64_NV */ +/* reuse GL_UNSIGNED_INT64_NV */ +#endif + +#ifndef GL_NV_multisample_coverage +#define GL_COVERAGE_SAMPLES_NV 0x80A9 +#define GL_COLOR_SAMPLES_NV 0x8E20 +#endif + +#ifndef GL_AMD_name_gen_delete +#define GL_DATA_BUFFER_AMD 0x9151 +#define GL_PERFORMANCE_MONITOR_AMD 0x9152 +#define GL_QUERY_OBJECT_AMD 0x9153 +#define GL_VERTEX_ARRAY_OBJECT_AMD 0x9154 +#define GL_SAMPLER_OBJECT_AMD 0x9155 +#endif + +#ifndef GL_AMD_debug_output +#define GL_MAX_DEBUG_LOGGED_MESSAGES_AMD 0x9144 +#define GL_DEBUG_LOGGED_MESSAGES_AMD 0x9145 +#define GL_DEBUG_SEVERITY_HIGH_AMD 0x9146 +#define GL_DEBUG_SEVERITY_MEDIUM_AMD 0x9147 +#define GL_DEBUG_SEVERITY_LOW_AMD 0x9148 +#define GL_DEBUG_CATEGORY_API_ERROR_AMD 0x9149 +#define GL_DEBUG_CATEGORY_WINDOW_SYSTEM_AMD 0x914A +#define GL_DEBUG_CATEGORY_DEPRECATION_AMD 0x914B +#define GL_DEBUG_CATEGORY_UNDEFINED_BEHAVIOR_AMD 0x914C +#define GL_DEBUG_CATEGORY_PERFORMANCE_AMD 0x914D +#define GL_DEBUG_CATEGORY_SHADER_COMPILER_AMD 0x914E +#define GL_DEBUG_CATEGORY_APPLICATION_AMD 0x914F +#define GL_DEBUG_CATEGORY_OTHER_AMD 0x9150 +#endif + +#ifndef GL_NV_vdpau_interop +#define GL_SURFACE_STATE_NV 0x86EB +#define GL_SURFACE_REGISTERED_NV 0x86FD +#define GL_SURFACE_MAPPED_NV 0x8700 +#define GL_WRITE_DISCARD_NV 0x88BE +#endif + +#ifndef GL_AMD_transform_feedback3_lines_triangles +#endif + + +/*************************************************************/ + +#include +#ifndef GL_VERSION_2_0 +/* GL type for program/shader text */ +typedef char GLchar; +#endif + +#ifndef GL_VERSION_1_5 +/* GL types for handling large vertex buffer objects */ +#if defined(__APPLE__) +typedef long GLintptr; +typedef long GLsizeiptr; +#else +typedef ptrdiff_t GLintptr; +typedef ptrdiff_t GLsizeiptr; +#endif +#endif + +#ifndef GL_ARB_vertex_buffer_object +/* GL types for handling large vertex buffer objects */ +#if defined(__APPLE__) +typedef long GLintptrARB; +typedef long GLsizeiptrARB; +#else +typedef ptrdiff_t GLintptrARB; +typedef ptrdiff_t GLsizeiptrARB; +#endif +#endif + +#ifndef GL_ARB_shader_objects +/* GL types for program/shader text and shader object handles */ +typedef char GLcharARB; +#if defined(__APPLE__) +typedef void *GLhandleARB; +#else +typedef unsigned int GLhandleARB; +#endif +#endif + +/* GL type for "half" precision (s10e5) float data in host memory */ +#ifndef GL_ARB_half_float_pixel +typedef unsigned short GLhalfARB; +#endif + +#ifndef GL_NV_half_float +typedef unsigned short GLhalfNV; +#endif + +#ifndef GLEXT_64_TYPES_DEFINED +/* This code block is duplicated in glxext.h, so must be protected */ +#define GLEXT_64_TYPES_DEFINED +/* Define int32_t, int64_t, and uint64_t types for UST/MSC */ +/* (as used in the GL_EXT_timer_query extension). */ +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#include +#elif defined(__sun__) || defined(__digital__) +#include +#if defined(__STDC__) +#if defined(__arch64__) || defined(_LP64) +typedef long int int64_t; +typedef unsigned long int uint64_t; +#else +typedef long long int int64_t; +typedef unsigned long long int uint64_t; +#endif /* __arch64__ */ +#endif /* __STDC__ */ +#elif defined( __VMS ) || defined(__sgi) +#include +#elif defined(__SCO__) || defined(__USLC__) +#include +#elif defined(__UNIXOS2__) || defined(__SOL64__) +typedef long int int32_t; +typedef long long int int64_t; +typedef unsigned long long int uint64_t; +#elif defined(_WIN32) && defined(__GNUC__) +#include +#elif defined(_WIN32) +typedef __int32 int32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +#else +/* Fallback if nothing above works */ +#include +#endif +#endif + +#ifndef GL_EXT_timer_query +typedef int64_t GLint64EXT; +typedef uint64_t GLuint64EXT; +#endif + +#ifndef GL_ARB_sync +typedef int64_t GLint64; +typedef uint64_t GLuint64; +typedef struct __GLsync *GLsync; +#endif + +#ifndef GL_ARB_cl_event +/* These incomplete types let us declare types compatible with OpenCL's cl_context and cl_event */ +struct _cl_context; +struct _cl_event; +#endif + +#ifndef GL_ARB_debug_output +typedef void (APIENTRY *GLDEBUGPROCARB)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,GLvoid *userParam); +#endif + +#ifndef GL_AMD_debug_output +typedef void (APIENTRY *GLDEBUGPROCAMD)(GLuint id,GLenum category,GLenum severity,GLsizei length,const GLchar *message,GLvoid *userParam); +#endif + +#ifndef GL_NV_vdpau_interop +typedef GLintptr GLvdpauSurfaceNV; +#endif + +#ifndef GL_VERSION_1_2 +#define GL_VERSION_1_2 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendColor (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +GLAPI void APIENTRY glBlendEquation (GLenum mode); +GLAPI void APIENTRY glDrawRangeElements (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +GLAPI void APIENTRY glTexImage3D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glTexSubImage3D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glCopyTexSubImage3D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDCOLORPROC) (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +typedef void (APIENTRYP PFNGLBLENDEQUATIONPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +typedef void (APIENTRYP PFNGLTEXIMAGE3DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif + +#ifndef GL_VERSION_1_2_DEPRECATED +#define GL_VERSION_1_2_DEPRECATED 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorTable (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +GLAPI void APIENTRY glColorTableParameterfv (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glColorTableParameteriv (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glCopyColorTable (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glGetColorTable (GLenum target, GLenum format, GLenum type, GLvoid *table); +GLAPI void APIENTRY glGetColorTableParameterfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetColorTableParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glColorSubTable (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); +GLAPI void APIENTRY glCopyColorSubTable (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glConvolutionFilter1D (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *image); +GLAPI void APIENTRY glConvolutionFilter2D (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image); +GLAPI void APIENTRY glConvolutionParameterf (GLenum target, GLenum pname, GLfloat params); +GLAPI void APIENTRY glConvolutionParameterfv (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glConvolutionParameteri (GLenum target, GLenum pname, GLint params); +GLAPI void APIENTRY glConvolutionParameteriv (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glCopyConvolutionFilter1D (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyConvolutionFilter2D (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetConvolutionFilter (GLenum target, GLenum format, GLenum type, GLvoid *image); +GLAPI void APIENTRY glGetConvolutionParameterfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetConvolutionParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetSeparableFilter (GLenum target, GLenum format, GLenum type, GLvoid *row, GLvoid *column, GLvoid *span); +GLAPI void APIENTRY glSeparableFilter2D (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *row, const GLvoid *column); +GLAPI void APIENTRY glGetHistogram (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +GLAPI void APIENTRY glGetHistogramParameterfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetHistogramParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMinmax (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +GLAPI void APIENTRY glGetMinmaxParameterfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMinmaxParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glHistogram (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +GLAPI void APIENTRY glMinmax (GLenum target, GLenum internalformat, GLboolean sink); +GLAPI void APIENTRY glResetHistogram (GLenum target); +GLAPI void APIENTRY glResetMinmax (GLenum target); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPROC) (GLenum target, GLenum format, GLenum type, GLvoid *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOPYCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFPROC) (GLenum target, GLenum pname, GLfloat params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIPROC) (GLenum target, GLenum pname, GLint params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONFILTERPROC) (GLenum target, GLenum format, GLenum type, GLvoid *image); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSEPARABLEFILTERPROC) (GLenum target, GLenum format, GLenum type, GLvoid *row, GLvoid *column, GLvoid *span); +typedef void (APIENTRYP PFNGLSEPARABLEFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *row, const GLvoid *column); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMINMAXPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLHISTOGRAMPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLMINMAXPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLRESETHISTOGRAMPROC) (GLenum target); +typedef void (APIENTRYP PFNGLRESETMINMAXPROC) (GLenum target); +#endif + +#ifndef GL_VERSION_1_3 +#define GL_VERSION_1_3 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveTexture (GLenum texture); +GLAPI void APIENTRY glSampleCoverage (GLclampf value, GLboolean invert); +GLAPI void APIENTRY glCompressedTexImage3D (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glCompressedTexImage2D (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glCompressedTexImage1D (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glCompressedTexSubImage3D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glCompressedTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glCompressedTexSubImage1D (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glGetCompressedTexImage (GLenum target, GLint level, GLvoid *img); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLACTIVETEXTUREPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLSAMPLECOVERAGEPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEPROC) (GLenum target, GLint level, GLvoid *img); +#endif + +#ifndef GL_VERSION_1_3_DEPRECATED +#define GL_VERSION_1_3_DEPRECATED 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glClientActiveTexture (GLenum texture); +GLAPI void APIENTRY glMultiTexCoord1d (GLenum target, GLdouble s); +GLAPI void APIENTRY glMultiTexCoord1dv (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord1f (GLenum target, GLfloat s); +GLAPI void APIENTRY glMultiTexCoord1fv (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord1i (GLenum target, GLint s); +GLAPI void APIENTRY glMultiTexCoord1iv (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord1s (GLenum target, GLshort s); +GLAPI void APIENTRY glMultiTexCoord1sv (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord2d (GLenum target, GLdouble s, GLdouble t); +GLAPI void APIENTRY glMultiTexCoord2dv (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord2f (GLenum target, GLfloat s, GLfloat t); +GLAPI void APIENTRY glMultiTexCoord2fv (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord2i (GLenum target, GLint s, GLint t); +GLAPI void APIENTRY glMultiTexCoord2iv (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord2s (GLenum target, GLshort s, GLshort t); +GLAPI void APIENTRY glMultiTexCoord2sv (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord3d (GLenum target, GLdouble s, GLdouble t, GLdouble r); +GLAPI void APIENTRY glMultiTexCoord3dv (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord3f (GLenum target, GLfloat s, GLfloat t, GLfloat r); +GLAPI void APIENTRY glMultiTexCoord3fv (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord3i (GLenum target, GLint s, GLint t, GLint r); +GLAPI void APIENTRY glMultiTexCoord3iv (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord3s (GLenum target, GLshort s, GLshort t, GLshort r); +GLAPI void APIENTRY glMultiTexCoord3sv (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord4d (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +GLAPI void APIENTRY glMultiTexCoord4dv (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord4f (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +GLAPI void APIENTRY glMultiTexCoord4fv (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord4i (GLenum target, GLint s, GLint t, GLint r, GLint q); +GLAPI void APIENTRY glMultiTexCoord4iv (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord4s (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +GLAPI void APIENTRY glMultiTexCoord4sv (GLenum target, const GLshort *v); +GLAPI void APIENTRY glLoadTransposeMatrixf (const GLfloat *m); +GLAPI void APIENTRY glLoadTransposeMatrixd (const GLdouble *m); +GLAPI void APIENTRY glMultTransposeMatrixf (const GLfloat *m); +GLAPI void APIENTRY glMultTransposeMatrixd (const GLdouble *m); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCLIENTACTIVETEXTUREPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DPROC) (GLenum target, GLdouble s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FPROC) (GLenum target, GLfloat s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IPROC) (GLenum target, GLint s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SPROC) (GLenum target, GLshort s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXFPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXDPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXFPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXDPROC) (const GLdouble *m); +#endif + +#ifndef GL_VERSION_1_4 +#define GL_VERSION_1_4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparate (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +GLAPI void APIENTRY glMultiDrawArrays (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +GLAPI void APIENTRY glMultiDrawElements (GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount); +GLAPI void APIENTRY glPointParameterf (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPointParameterfv (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glPointParameteri (GLenum pname, GLint param); +GLAPI void APIENTRY glPointParameteriv (GLenum pname, const GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSPROC) (GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIVPROC) (GLenum pname, const GLint *params); +#endif + +#ifndef GL_VERSION_1_4_DEPRECATED +#define GL_VERSION_1_4_DEPRECATED 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFogCoordf (GLfloat coord); +GLAPI void APIENTRY glFogCoordfv (const GLfloat *coord); +GLAPI void APIENTRY glFogCoordd (GLdouble coord); +GLAPI void APIENTRY glFogCoorddv (const GLdouble *coord); +GLAPI void APIENTRY glFogCoordPointer (GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void APIENTRY glSecondaryColor3b (GLbyte red, GLbyte green, GLbyte blue); +GLAPI void APIENTRY glSecondaryColor3bv (const GLbyte *v); +GLAPI void APIENTRY glSecondaryColor3d (GLdouble red, GLdouble green, GLdouble blue); +GLAPI void APIENTRY glSecondaryColor3dv (const GLdouble *v); +GLAPI void APIENTRY glSecondaryColor3f (GLfloat red, GLfloat green, GLfloat blue); +GLAPI void APIENTRY glSecondaryColor3fv (const GLfloat *v); +GLAPI void APIENTRY glSecondaryColor3i (GLint red, GLint green, GLint blue); +GLAPI void APIENTRY glSecondaryColor3iv (const GLint *v); +GLAPI void APIENTRY glSecondaryColor3s (GLshort red, GLshort green, GLshort blue); +GLAPI void APIENTRY glSecondaryColor3sv (const GLshort *v); +GLAPI void APIENTRY glSecondaryColor3ub (GLubyte red, GLubyte green, GLubyte blue); +GLAPI void APIENTRY glSecondaryColor3ubv (const GLubyte *v); +GLAPI void APIENTRY glSecondaryColor3ui (GLuint red, GLuint green, GLuint blue); +GLAPI void APIENTRY glSecondaryColor3uiv (const GLuint *v); +GLAPI void APIENTRY glSecondaryColor3us (GLushort red, GLushort green, GLushort blue); +GLAPI void APIENTRY glSecondaryColor3usv (const GLushort *v); +GLAPI void APIENTRY glSecondaryColorPointer (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void APIENTRY glWindowPos2d (GLdouble x, GLdouble y); +GLAPI void APIENTRY glWindowPos2dv (const GLdouble *v); +GLAPI void APIENTRY glWindowPos2f (GLfloat x, GLfloat y); +GLAPI void APIENTRY glWindowPos2fv (const GLfloat *v); +GLAPI void APIENTRY glWindowPos2i (GLint x, GLint y); +GLAPI void APIENTRY glWindowPos2iv (const GLint *v); +GLAPI void APIENTRY glWindowPos2s (GLshort x, GLshort y); +GLAPI void APIENTRY glWindowPos2sv (const GLshort *v); +GLAPI void APIENTRY glWindowPos3d (GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glWindowPos3dv (const GLdouble *v); +GLAPI void APIENTRY glWindowPos3f (GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glWindowPos3fv (const GLfloat *v); +GLAPI void APIENTRY glWindowPos3i (GLint x, GLint y, GLint z); +GLAPI void APIENTRY glWindowPos3iv (const GLint *v); +GLAPI void APIENTRY glWindowPos3s (GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glWindowPos3sv (const GLshort *v); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFOGCOORDFPROC) (GLfloat coord); +typedef void (APIENTRYP PFNGLFOGCOORDFVPROC) (const GLfloat *coord); +typedef void (APIENTRYP PFNGLFOGCOORDDPROC) (GLdouble coord); +typedef void (APIENTRYP PFNGLFOGCOORDDVPROC) (const GLdouble *coord); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTERPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BPROC) (GLbyte red, GLbyte green, GLbyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BVPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DPROC) (GLdouble red, GLdouble green, GLdouble blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FPROC) (GLfloat red, GLfloat green, GLfloat blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IPROC) (GLint red, GLint green, GLint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SPROC) (GLshort red, GLshort green, GLshort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBPROC) (GLubyte red, GLubyte green, GLubyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBVPROC) (const GLubyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIPROC) (GLuint red, GLuint green, GLuint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIVPROC) (const GLuint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USPROC) (GLushort red, GLushort green, GLushort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USVPROC) (const GLushort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTERPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLWINDOWPOS2DPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVPROC) (const GLshort *v); +#endif + +#ifndef GL_VERSION_1_5 +#define GL_VERSION_1_5 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenQueries (GLsizei n, GLuint *ids); +GLAPI void APIENTRY glDeleteQueries (GLsizei n, const GLuint *ids); +GLAPI GLboolean APIENTRY glIsQuery (GLuint id); +GLAPI void APIENTRY glBeginQuery (GLenum target, GLuint id); +GLAPI void APIENTRY glEndQuery (GLenum target); +GLAPI void APIENTRY glGetQueryiv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetQueryObjectiv (GLuint id, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetQueryObjectuiv (GLuint id, GLenum pname, GLuint *params); +GLAPI void APIENTRY glBindBuffer (GLenum target, GLuint buffer); +GLAPI void APIENTRY glDeleteBuffers (GLsizei n, const GLuint *buffers); +GLAPI void APIENTRY glGenBuffers (GLsizei n, GLuint *buffers); +GLAPI GLboolean APIENTRY glIsBuffer (GLuint buffer); +GLAPI void APIENTRY glBufferData (GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage); +GLAPI void APIENTRY glBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid *data); +GLAPI void APIENTRY glGetBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, GLvoid *data); +GLAPI GLvoid* APIENTRY glMapBuffer (GLenum target, GLenum access); +GLAPI GLboolean APIENTRY glUnmapBuffer (GLenum target); +GLAPI void APIENTRY glGetBufferParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetBufferPointerv (GLenum target, GLenum pname, GLvoid* *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENQUERIESPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEQUERIESPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISQUERYPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINQUERYPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLENDQUERYPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETQUERYIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTIVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUIVPROC) (GLuint id, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer); +typedef void (APIENTRYP PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers); +typedef void (APIENTRYP PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers); +typedef GLboolean (APIENTRYP PFNGLISBUFFERPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage); +typedef void (APIENTRYP PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, GLvoid *data); +typedef GLvoid* (APIENTRYP PFNGLMAPBUFFERPROC) (GLenum target, GLenum access); +typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETBUFFERPOINTERVPROC) (GLenum target, GLenum pname, GLvoid* *params); +#endif + +#ifndef GL_VERSION_2_0 +#define GL_VERSION_2_0 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationSeparate (GLenum modeRGB, GLenum modeAlpha); +GLAPI void APIENTRY glDrawBuffers (GLsizei n, const GLenum *bufs); +GLAPI void APIENTRY glStencilOpSeparate (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +GLAPI void APIENTRY glStencilFuncSeparate (GLenum face, GLenum func, GLint ref, GLuint mask); +GLAPI void APIENTRY glStencilMaskSeparate (GLenum face, GLuint mask); +GLAPI void APIENTRY glAttachShader (GLuint program, GLuint shader); +GLAPI void APIENTRY glBindAttribLocation (GLuint program, GLuint index, const GLchar *name); +GLAPI void APIENTRY glCompileShader (GLuint shader); +GLAPI GLuint APIENTRY glCreateProgram (void); +GLAPI GLuint APIENTRY glCreateShader (GLenum type); +GLAPI void APIENTRY glDeleteProgram (GLuint program); +GLAPI void APIENTRY glDeleteShader (GLuint shader); +GLAPI void APIENTRY glDetachShader (GLuint program, GLuint shader); +GLAPI void APIENTRY glDisableVertexAttribArray (GLuint index); +GLAPI void APIENTRY glEnableVertexAttribArray (GLuint index); +GLAPI void APIENTRY glGetActiveAttrib (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +GLAPI void APIENTRY glGetActiveUniform (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +GLAPI void APIENTRY glGetAttachedShaders (GLuint program, GLsizei maxCount, GLsizei *count, GLuint *obj); +GLAPI GLint APIENTRY glGetAttribLocation (GLuint program, const GLchar *name); +GLAPI void APIENTRY glGetProgramiv (GLuint program, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetProgramInfoLog (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GLAPI void APIENTRY glGetShaderiv (GLuint shader, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetShaderInfoLog (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GLAPI void APIENTRY glGetShaderSource (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *source); +GLAPI GLint APIENTRY glGetUniformLocation (GLuint program, const GLchar *name); +GLAPI void APIENTRY glGetUniformfv (GLuint program, GLint location, GLfloat *params); +GLAPI void APIENTRY glGetUniformiv (GLuint program, GLint location, GLint *params); +GLAPI void APIENTRY glGetVertexAttribdv (GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetVertexAttribfv (GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVertexAttribiv (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribPointerv (GLuint index, GLenum pname, GLvoid* *pointer); +GLAPI GLboolean APIENTRY glIsProgram (GLuint program); +GLAPI GLboolean APIENTRY glIsShader (GLuint shader); +GLAPI void APIENTRY glLinkProgram (GLuint program); +GLAPI void APIENTRY glShaderSource (GLuint shader, GLsizei count, const GLchar* *string, const GLint *length); +GLAPI void APIENTRY glUseProgram (GLuint program); +GLAPI void APIENTRY glUniform1f (GLint location, GLfloat v0); +GLAPI void APIENTRY glUniform2f (GLint location, GLfloat v0, GLfloat v1); +GLAPI void APIENTRY glUniform3f (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GLAPI void APIENTRY glUniform4f (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLAPI void APIENTRY glUniform1i (GLint location, GLint v0); +GLAPI void APIENTRY glUniform2i (GLint location, GLint v0, GLint v1); +GLAPI void APIENTRY glUniform3i (GLint location, GLint v0, GLint v1, GLint v2); +GLAPI void APIENTRY glUniform4i (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GLAPI void APIENTRY glUniform1fv (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform2fv (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform3fv (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform4fv (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform1iv (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform2iv (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform3iv (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform4iv (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniformMatrix2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glValidateProgram (GLuint program); +GLAPI void APIENTRY glVertexAttrib1d (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttrib1dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib1f (GLuint index, GLfloat x); +GLAPI void APIENTRY glVertexAttrib1fv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib1s (GLuint index, GLshort x); +GLAPI void APIENTRY glVertexAttrib1sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib2d (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttrib2dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib2f (GLuint index, GLfloat x, GLfloat y); +GLAPI void APIENTRY glVertexAttrib2fv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib2s (GLuint index, GLshort x, GLshort y); +GLAPI void APIENTRY glVertexAttrib2sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib3d (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttrib3dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib3f (GLuint index, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glVertexAttrib3fv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib3s (GLuint index, GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glVertexAttrib3sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4Nbv (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttrib4Niv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttrib4Nsv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4Nub (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +GLAPI void APIENTRY glVertexAttrib4Nubv (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttrib4Nuiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttrib4Nusv (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttrib4bv (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttrib4d (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttrib4dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib4f (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glVertexAttrib4fv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib4iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttrib4s (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glVertexAttrib4sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4ubv (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttrib4uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttrib4usv (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttribPointer (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEPROC) (GLenum modeRGB, GLenum modeAlpha); +typedef void (APIENTRYP PFNGLDRAWBUFFERSPROC) (GLsizei n, const GLenum *bufs); +typedef void (APIENTRYP PFNGLSTENCILOPSEPARATEPROC) (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +typedef void (APIENTRYP PFNGLSTENCILFUNCSEPARATEPROC) (GLenum face, GLenum func, GLint ref, GLuint mask); +typedef void (APIENTRYP PFNGLSTENCILMASKSEPARATEPROC) (GLenum face, GLuint mask); +typedef void (APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (APIENTRYP PFNGLBINDATTRIBLOCATIONPROC) (GLuint program, GLuint index, const GLchar *name); +typedef void (APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader); +typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC) (void); +typedef GLuint (APIENTRYP PFNGLCREATESHADERPROC) (GLenum type); +typedef void (APIENTRYP PFNGLDELETEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLDELETESHADERPROC) (GLuint shader); +typedef void (APIENTRYP PFNGLDETACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef void (APIENTRYP PFNGLGETACTIVEATTRIBPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLGETATTACHEDSHADERSPROC) (GLuint program, GLsizei maxCount, GLsizei *count, GLuint *obj); +typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (APIENTRYP PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (APIENTRYP PFNGLGETSHADERSOURCEPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *source); +typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETUNIFORMFVPROC) (GLuint program, GLint location, GLfloat *params); +typedef void (APIENTRYP PFNGLGETUNIFORMIVPROC) (GLuint program, GLint location, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVPROC) (GLuint index, GLenum pname, GLvoid* *pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMPROC) (GLuint program); +typedef GLboolean (APIENTRYP PFNGLISSHADERPROC) (GLuint shader); +typedef void (APIENTRYP PFNGLLINKPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar* *string, const GLint *length); +typedef void (APIENTRYP PFNGLUSEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLUNIFORM1FPROC) (GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLUNIFORM2FPROC) (GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLUNIFORM3FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLUNIFORM4FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLUNIFORM1IPROC) (GLint location, GLint v0); +typedef void (APIENTRYP PFNGLUNIFORM2IPROC) (GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLUNIFORM3IPROC) (GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLUNIFORM4IPROC) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLUNIFORM1FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM2FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM3FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM4FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM1IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM2IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM3IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM4IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NBVPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NIVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NSVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUSVPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4BVPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4USVPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_VERSION_2_1 +#define GL_VERSION_2_1 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUniformMatrix2x3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix3x2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix2x4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix4x2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix3x4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix4x3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +#endif + +#ifndef GL_VERSION_3_0 +#define GL_VERSION_3_0 1 +/* OpenGL 3.0 also reuses entry points from these extensions: */ +/* ARB_framebuffer_object */ +/* ARB_map_buffer_range */ +/* ARB_vertex_array_object */ +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorMaski (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +GLAPI void APIENTRY glGetBooleani_v (GLenum target, GLuint index, GLboolean *data); +GLAPI void APIENTRY glGetIntegeri_v (GLenum target, GLuint index, GLint *data); +GLAPI void APIENTRY glEnablei (GLenum target, GLuint index); +GLAPI void APIENTRY glDisablei (GLenum target, GLuint index); +GLAPI GLboolean APIENTRY glIsEnabledi (GLenum target, GLuint index); +GLAPI void APIENTRY glBeginTransformFeedback (GLenum primitiveMode); +GLAPI void APIENTRY glEndTransformFeedback (void); +GLAPI void APIENTRY glBindBufferRange (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glBindBufferBase (GLenum target, GLuint index, GLuint buffer); +GLAPI void APIENTRY glTransformFeedbackVaryings (GLuint program, GLsizei count, const GLchar* *varyings, GLenum bufferMode); +GLAPI void APIENTRY glGetTransformFeedbackVarying (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +GLAPI void APIENTRY glClampColor (GLenum target, GLenum clamp); +GLAPI void APIENTRY glBeginConditionalRender (GLuint id, GLenum mode); +GLAPI void APIENTRY glEndConditionalRender (void); +GLAPI void APIENTRY glVertexAttribIPointer (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void APIENTRY glGetVertexAttribIiv (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribIuiv (GLuint index, GLenum pname, GLuint *params); +GLAPI void APIENTRY glVertexAttribI1i (GLuint index, GLint x); +GLAPI void APIENTRY glVertexAttribI2i (GLuint index, GLint x, GLint y); +GLAPI void APIENTRY glVertexAttribI3i (GLuint index, GLint x, GLint y, GLint z); +GLAPI void APIENTRY glVertexAttribI4i (GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glVertexAttribI1ui (GLuint index, GLuint x); +GLAPI void APIENTRY glVertexAttribI2ui (GLuint index, GLuint x, GLuint y); +GLAPI void APIENTRY glVertexAttribI3ui (GLuint index, GLuint x, GLuint y, GLuint z); +GLAPI void APIENTRY glVertexAttribI4ui (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glVertexAttribI1iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI2iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI3iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI4iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI1uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI2uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI3uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI4uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI4bv (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttribI4sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttribI4ubv (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttribI4usv (GLuint index, const GLushort *v); +GLAPI void APIENTRY glGetUniformuiv (GLuint program, GLint location, GLuint *params); +GLAPI void APIENTRY glBindFragDataLocation (GLuint program, GLuint color, const GLchar *name); +GLAPI GLint APIENTRY glGetFragDataLocation (GLuint program, const GLchar *name); +GLAPI void APIENTRY glUniform1ui (GLint location, GLuint v0); +GLAPI void APIENTRY glUniform2ui (GLint location, GLuint v0, GLuint v1); +GLAPI void APIENTRY glUniform3ui (GLint location, GLuint v0, GLuint v1, GLuint v2); +GLAPI void APIENTRY glUniform4ui (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +GLAPI void APIENTRY glUniform1uiv (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform2uiv (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform3uiv (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform4uiv (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glTexParameterIiv (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTexParameterIuiv (GLenum target, GLenum pname, const GLuint *params); +GLAPI void APIENTRY glGetTexParameterIiv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTexParameterIuiv (GLenum target, GLenum pname, GLuint *params); +GLAPI void APIENTRY glClearBufferiv (GLenum buffer, GLint drawbuffer, const GLint *value); +GLAPI void APIENTRY glClearBufferuiv (GLenum buffer, GLint drawbuffer, const GLuint *value); +GLAPI void APIENTRY glClearBufferfv (GLenum buffer, GLint drawbuffer, const GLfloat *value); +GLAPI void APIENTRY glClearBufferfi (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); +GLAPI const GLubyte * APIENTRY glGetStringi (GLenum name, GLuint index); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORMASKIPROC) (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +typedef void (APIENTRYP PFNGLGETBOOLEANI_VPROC) (GLenum target, GLuint index, GLboolean *data); +typedef void (APIENTRYP PFNGLGETINTEGERI_VPROC) (GLenum target, GLuint index, GLint *data); +typedef void (APIENTRYP PFNGLENABLEIPROC) (GLenum target, GLuint index); +typedef void (APIENTRYP PFNGLDISABLEIPROC) (GLenum target, GLuint index); +typedef GLboolean (APIENTRYP PFNGLISENABLEDIPROC) (GLenum target, GLuint index); +typedef void (APIENTRYP PFNGLBEGINTRANSFORMFEEDBACKPROC) (GLenum primitiveMode); +typedef void (APIENTRYP PFNGLENDTRANSFORMFEEDBACKPROC) (void); +typedef void (APIENTRYP PFNGLBINDBUFFERRANGEPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLBINDBUFFERBASEPROC) (GLenum target, GLuint index, GLuint buffer); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKVARYINGSPROC) (GLuint program, GLsizei count, const GLchar* *varyings, GLenum bufferMode); +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKVARYINGPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLCLAMPCOLORPROC) (GLenum target, GLenum clamp); +typedef void (APIENTRYP PFNGLBEGINCONDITIONALRENDERPROC) (GLuint id, GLenum mode); +typedef void (APIENTRYP PFNGLENDCONDITIONALRENDERPROC) (void); +typedef void (APIENTRYP PFNGLVERTEXATTRIBIPOINTERPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIIVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIUIVPROC) (GLuint index, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IPROC) (GLuint index, GLint x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IPROC) (GLuint index, GLint x, GLint y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IPROC) (GLuint index, GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IPROC) (GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIPROC) (GLuint index, GLuint x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIPROC) (GLuint index, GLuint x, GLuint y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIPROC) (GLuint index, GLuint x, GLuint y, GLuint z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIPROC) (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4BVPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UBVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4USVPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLGETUNIFORMUIVPROC) (GLuint program, GLint location, GLuint *params); +typedef void (APIENTRYP PFNGLBINDFRAGDATALOCATIONPROC) (GLuint program, GLuint color, const GLchar *name); +typedef GLint (APIENTRYP PFNGLGETFRAGDATALOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLUNIFORM1UIPROC) (GLint location, GLuint v0); +typedef void (APIENTRYP PFNGLUNIFORM2UIPROC) (GLint location, GLuint v0, GLuint v1); +typedef void (APIENTRYP PFNGLUNIFORM3UIPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2); +typedef void (APIENTRYP PFNGLUNIFORM4UIPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +typedef void (APIENTRYP PFNGLUNIFORM1UIVPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM2UIVPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM3UIVPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM4UIVPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLTEXPARAMETERIIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXPARAMETERIUIVPROC) (GLenum target, GLenum pname, const GLuint *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIUIVPROC) (GLenum target, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLCLEARBUFFERIVPROC) (GLenum buffer, GLint drawbuffer, const GLint *value); +typedef void (APIENTRYP PFNGLCLEARBUFFERUIVPROC) (GLenum buffer, GLint drawbuffer, const GLuint *value); +typedef void (APIENTRYP PFNGLCLEARBUFFERFVPROC) (GLenum buffer, GLint drawbuffer, const GLfloat *value); +typedef void (APIENTRYP PFNGLCLEARBUFFERFIPROC) (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); +typedef const GLubyte * (APIENTRYP PFNGLGETSTRINGIPROC) (GLenum name, GLuint index); +#endif + +#ifndef GL_VERSION_3_1 +#define GL_VERSION_3_1 1 +/* OpenGL 3.1 also reuses entry points from these extensions: */ +/* ARB_copy_buffer */ +/* ARB_uniform_buffer_object */ +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawArraysInstanced (GLenum mode, GLint first, GLsizei count, GLsizei primcount); +GLAPI void APIENTRY glDrawElementsInstanced (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount); +GLAPI void APIENTRY glTexBuffer (GLenum target, GLenum internalformat, GLuint buffer); +GLAPI void APIENTRY glPrimitiveRestartIndex (GLuint index); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDPROC) (GLenum mode, GLint first, GLsizei count, GLsizei primcount); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDPROC) (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount); +typedef void (APIENTRYP PFNGLTEXBUFFERPROC) (GLenum target, GLenum internalformat, GLuint buffer); +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTINDEXPROC) (GLuint index); +#endif + +#ifndef GL_VERSION_3_2 +#define GL_VERSION_3_2 1 +/* OpenGL 3.2 also reuses entry points from these extensions: */ +/* ARB_draw_elements_base_vertex */ +/* ARB_provoking_vertex */ +/* ARB_sync */ +/* ARB_texture_multisample */ +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetInteger64i_v (GLenum target, GLuint index, GLint64 *data); +GLAPI void APIENTRY glGetBufferParameteri64v (GLenum target, GLenum pname, GLint64 *params); +GLAPI void APIENTRY glFramebufferTexture (GLenum target, GLenum attachment, GLuint texture, GLint level); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETINTEGER64I_VPROC) (GLenum target, GLuint index, GLint64 *data); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERI64VPROC) (GLenum target, GLenum pname, GLint64 *params); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level); +#endif + +#ifndef GL_VERSION_3_3 +#define GL_VERSION_3_3 1 +/* OpenGL 3.3 also reuses entry points from these extensions: */ +/* ARB_blend_func_extended */ +/* ARB_sampler_objects */ +/* ARB_explicit_attrib_location, but it has none */ +/* ARB_occlusion_query2 (no entry points) */ +/* ARB_shader_bit_encoding (no entry points) */ +/* ARB_texture_rgb10_a2ui (no entry points) */ +/* ARB_texture_swizzle (no entry points) */ +/* ARB_timer_query */ +/* ARB_vertex_type_2_10_10_10_rev */ +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribDivisor (GLuint index, GLuint divisor); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIBDIVISORPROC) (GLuint index, GLuint divisor); +#endif + +#ifndef GL_VERSION_4_0 +#define GL_VERSION_4_0 1 +/* OpenGL 4.0 also reuses entry points from these extensions: */ +/* ARB_texture_query_lod (no entry points) */ +/* ARB_draw_indirect */ +/* ARB_gpu_shader5 (no entry points) */ +/* ARB_gpu_shader_fp64 */ +/* ARB_shader_subroutine */ +/* ARB_tessellation_shader */ +/* ARB_texture_buffer_object_rgb32 (no entry points) */ +/* ARB_texture_cube_map_array (no entry points) */ +/* ARB_texture_gather (no entry points) */ +/* ARB_transform_feedback2 */ +/* ARB_transform_feedback3 */ +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMinSampleShading (GLclampf value); +GLAPI void APIENTRY glBlendEquationi (GLuint buf, GLenum mode); +GLAPI void APIENTRY glBlendEquationSeparatei (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +GLAPI void APIENTRY glBlendFunci (GLuint buf, GLenum src, GLenum dst); +GLAPI void APIENTRY glBlendFuncSeparatei (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMINSAMPLESHADINGPROC) (GLclampf value); +typedef void (APIENTRYP PFNGLBLENDEQUATIONIPROC) (GLuint buf, GLenum mode); +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEIPROC) (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +typedef void (APIENTRYP PFNGLBLENDFUNCIPROC) (GLuint buf, GLenum src, GLenum dst); +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEIPROC) (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +#endif + +#ifndef GL_VERSION_4_1 +#define GL_VERSION_4_1 1 +/* OpenGL 4.1 also reuses entry points from these extensions: */ +/* ARB_ES2_compatibility */ +/* ARB_get_program_binary */ +/* ARB_separate_shader_objects */ +/* ARB_shader_precision (no entry points) */ +/* ARB_vertex_attrib_64bit */ +/* ARB_viewport_array */ +#endif + +#ifndef GL_ARB_multitexture +#define GL_ARB_multitexture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveTextureARB (GLenum texture); +GLAPI void APIENTRY glClientActiveTextureARB (GLenum texture); +GLAPI void APIENTRY glMultiTexCoord1dARB (GLenum target, GLdouble s); +GLAPI void APIENTRY glMultiTexCoord1dvARB (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord1fARB (GLenum target, GLfloat s); +GLAPI void APIENTRY glMultiTexCoord1fvARB (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord1iARB (GLenum target, GLint s); +GLAPI void APIENTRY glMultiTexCoord1ivARB (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord1sARB (GLenum target, GLshort s); +GLAPI void APIENTRY glMultiTexCoord1svARB (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord2dARB (GLenum target, GLdouble s, GLdouble t); +GLAPI void APIENTRY glMultiTexCoord2dvARB (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord2fARB (GLenum target, GLfloat s, GLfloat t); +GLAPI void APIENTRY glMultiTexCoord2fvARB (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord2iARB (GLenum target, GLint s, GLint t); +GLAPI void APIENTRY glMultiTexCoord2ivARB (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord2sARB (GLenum target, GLshort s, GLshort t); +GLAPI void APIENTRY glMultiTexCoord2svARB (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord3dARB (GLenum target, GLdouble s, GLdouble t, GLdouble r); +GLAPI void APIENTRY glMultiTexCoord3dvARB (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord3fARB (GLenum target, GLfloat s, GLfloat t, GLfloat r); +GLAPI void APIENTRY glMultiTexCoord3fvARB (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord3iARB (GLenum target, GLint s, GLint t, GLint r); +GLAPI void APIENTRY glMultiTexCoord3ivARB (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord3sARB (GLenum target, GLshort s, GLshort t, GLshort r); +GLAPI void APIENTRY glMultiTexCoord3svARB (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord4dARB (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +GLAPI void APIENTRY glMultiTexCoord4dvARB (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord4fARB (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +GLAPI void APIENTRY glMultiTexCoord4fvARB (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord4iARB (GLenum target, GLint s, GLint t, GLint r, GLint q); +GLAPI void APIENTRY glMultiTexCoord4ivARB (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord4sARB (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +GLAPI void APIENTRY glMultiTexCoord4svARB (GLenum target, const GLshort *v); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLCLIENTACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DARBPROC) (GLenum target, GLdouble s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FARBPROC) (GLenum target, GLfloat s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IARBPROC) (GLenum target, GLint s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SARBPROC) (GLenum target, GLshort s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DARBPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IARBPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SARBPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IARBPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IARBPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SVARBPROC) (GLenum target, const GLshort *v); +#endif + +#ifndef GL_ARB_transpose_matrix +#define GL_ARB_transpose_matrix 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glLoadTransposeMatrixfARB (const GLfloat *m); +GLAPI void APIENTRY glLoadTransposeMatrixdARB (const GLdouble *m); +GLAPI void APIENTRY glMultTransposeMatrixfARB (const GLfloat *m); +GLAPI void APIENTRY glMultTransposeMatrixdARB (const GLdouble *m); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); +#endif + +#ifndef GL_ARB_multisample +#define GL_ARB_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleCoverageARB (GLclampf value, GLboolean invert); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSAMPLECOVERAGEARBPROC) (GLclampf value, GLboolean invert); +#endif + +#ifndef GL_ARB_texture_env_add +#define GL_ARB_texture_env_add 1 +#endif + +#ifndef GL_ARB_texture_cube_map +#define GL_ARB_texture_cube_map 1 +#endif + +#ifndef GL_ARB_texture_compression +#define GL_ARB_texture_compression 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCompressedTexImage3DARB (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glCompressedTexImage2DARB (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glCompressedTexImage1DARB (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glCompressedTexSubImage3DARB (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glCompressedTexSubImage2DARB (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glCompressedTexSubImage1DARB (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *data); +GLAPI void APIENTRY glGetCompressedTexImageARB (GLenum target, GLint level, GLvoid *img); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEARBPROC) (GLenum target, GLint level, GLvoid *img); +#endif + +#ifndef GL_ARB_texture_border_clamp +#define GL_ARB_texture_border_clamp 1 +#endif + +#ifndef GL_ARB_point_parameters +#define GL_ARB_point_parameters 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfARB (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPointParameterfvARB (GLenum pname, const GLfloat *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERFARBPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVARBPROC) (GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_ARB_vertex_blend +#define GL_ARB_vertex_blend 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWeightbvARB (GLint size, const GLbyte *weights); +GLAPI void APIENTRY glWeightsvARB (GLint size, const GLshort *weights); +GLAPI void APIENTRY glWeightivARB (GLint size, const GLint *weights); +GLAPI void APIENTRY glWeightfvARB (GLint size, const GLfloat *weights); +GLAPI void APIENTRY glWeightdvARB (GLint size, const GLdouble *weights); +GLAPI void APIENTRY glWeightubvARB (GLint size, const GLubyte *weights); +GLAPI void APIENTRY glWeightusvARB (GLint size, const GLushort *weights); +GLAPI void APIENTRY glWeightuivARB (GLint size, const GLuint *weights); +GLAPI void APIENTRY glWeightPointerARB (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void APIENTRY glVertexBlendARB (GLint count); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLWEIGHTBVARBPROC) (GLint size, const GLbyte *weights); +typedef void (APIENTRYP PFNGLWEIGHTSVARBPROC) (GLint size, const GLshort *weights); +typedef void (APIENTRYP PFNGLWEIGHTIVARBPROC) (GLint size, const GLint *weights); +typedef void (APIENTRYP PFNGLWEIGHTFVARBPROC) (GLint size, const GLfloat *weights); +typedef void (APIENTRYP PFNGLWEIGHTDVARBPROC) (GLint size, const GLdouble *weights); +typedef void (APIENTRYP PFNGLWEIGHTUBVARBPROC) (GLint size, const GLubyte *weights); +typedef void (APIENTRYP PFNGLWEIGHTUSVARBPROC) (GLint size, const GLushort *weights); +typedef void (APIENTRYP PFNGLWEIGHTUIVARBPROC) (GLint size, const GLuint *weights); +typedef void (APIENTRYP PFNGLWEIGHTPOINTERARBPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXBLENDARBPROC) (GLint count); +#endif + +#ifndef GL_ARB_matrix_palette +#define GL_ARB_matrix_palette 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCurrentPaletteMatrixARB (GLint index); +GLAPI void APIENTRY glMatrixIndexubvARB (GLint size, const GLubyte *indices); +GLAPI void APIENTRY glMatrixIndexusvARB (GLint size, const GLushort *indices); +GLAPI void APIENTRY glMatrixIndexuivARB (GLint size, const GLuint *indices); +GLAPI void APIENTRY glMatrixIndexPointerARB (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCURRENTPALETTEMATRIXARBPROC) (GLint index); +typedef void (APIENTRYP PFNGLMATRIXINDEXUBVARBPROC) (GLint size, const GLubyte *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXUSVARBPROC) (GLint size, const GLushort *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXUIVARBPROC) (GLint size, const GLuint *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXPOINTERARBPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_ARB_texture_env_combine +#define GL_ARB_texture_env_combine 1 +#endif + +#ifndef GL_ARB_texture_env_crossbar +#define GL_ARB_texture_env_crossbar 1 +#endif + +#ifndef GL_ARB_texture_env_dot3 +#define GL_ARB_texture_env_dot3 1 +#endif + +#ifndef GL_ARB_texture_mirrored_repeat +#define GL_ARB_texture_mirrored_repeat 1 +#endif + +#ifndef GL_ARB_depth_texture +#define GL_ARB_depth_texture 1 +#endif + +#ifndef GL_ARB_shadow +#define GL_ARB_shadow 1 +#endif + +#ifndef GL_ARB_shadow_ambient +#define GL_ARB_shadow_ambient 1 +#endif + +#ifndef GL_ARB_window_pos +#define GL_ARB_window_pos 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWindowPos2dARB (GLdouble x, GLdouble y); +GLAPI void APIENTRY glWindowPos2dvARB (const GLdouble *v); +GLAPI void APIENTRY glWindowPos2fARB (GLfloat x, GLfloat y); +GLAPI void APIENTRY glWindowPos2fvARB (const GLfloat *v); +GLAPI void APIENTRY glWindowPos2iARB (GLint x, GLint y); +GLAPI void APIENTRY glWindowPos2ivARB (const GLint *v); +GLAPI void APIENTRY glWindowPos2sARB (GLshort x, GLshort y); +GLAPI void APIENTRY glWindowPos2svARB (const GLshort *v); +GLAPI void APIENTRY glWindowPos3dARB (GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glWindowPos3dvARB (const GLdouble *v); +GLAPI void APIENTRY glWindowPos3fARB (GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glWindowPos3fvARB (const GLfloat *v); +GLAPI void APIENTRY glWindowPos3iARB (GLint x, GLint y, GLint z); +GLAPI void APIENTRY glWindowPos3ivARB (const GLint *v); +GLAPI void APIENTRY glWindowPos3sARB (GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glWindowPos3svARB (const GLshort *v); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLWINDOWPOS2DARBPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVARBPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FARBPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVARBPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IARBPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVARBPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SARBPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVARBPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DARBPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVARBPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FARBPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVARBPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IARBPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVARBPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SARBPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVARBPROC) (const GLshort *v); +#endif + +#ifndef GL_ARB_vertex_program +#define GL_ARB_vertex_program 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttrib1dARB (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttrib1dvARB (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib1fARB (GLuint index, GLfloat x); +GLAPI void APIENTRY glVertexAttrib1fvARB (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib1sARB (GLuint index, GLshort x); +GLAPI void APIENTRY glVertexAttrib1svARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib2dARB (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttrib2dvARB (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib2fARB (GLuint index, GLfloat x, GLfloat y); +GLAPI void APIENTRY glVertexAttrib2fvARB (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib2sARB (GLuint index, GLshort x, GLshort y); +GLAPI void APIENTRY glVertexAttrib2svARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib3dARB (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttrib3dvARB (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib3fARB (GLuint index, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glVertexAttrib3fvARB (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib3sARB (GLuint index, GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glVertexAttrib3svARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4NbvARB (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttrib4NivARB (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttrib4NsvARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4NubARB (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +GLAPI void APIENTRY glVertexAttrib4NubvARB (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttrib4NuivARB (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttrib4NusvARB (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttrib4bvARB (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttrib4dARB (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttrib4dvARB (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib4fARB (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glVertexAttrib4fvARB (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib4ivARB (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttrib4sARB (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glVertexAttrib4svARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4ubvARB (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttrib4uivARB (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttrib4usvARB (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttribPointerARB (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); +GLAPI void APIENTRY glEnableVertexAttribArrayARB (GLuint index); +GLAPI void APIENTRY glDisableVertexAttribArrayARB (GLuint index); +GLAPI void APIENTRY glProgramStringARB (GLenum target, GLenum format, GLsizei len, const GLvoid *string); +GLAPI void APIENTRY glBindProgramARB (GLenum target, GLuint program); +GLAPI void APIENTRY glDeleteProgramsARB (GLsizei n, const GLuint *programs); +GLAPI void APIENTRY glGenProgramsARB (GLsizei n, GLuint *programs); +GLAPI void APIENTRY glProgramEnvParameter4dARB (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramEnvParameter4dvARB (GLenum target, GLuint index, const GLdouble *params); +GLAPI void APIENTRY glProgramEnvParameter4fARB (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glProgramEnvParameter4fvARB (GLenum target, GLuint index, const GLfloat *params); +GLAPI void APIENTRY glProgramLocalParameter4dARB (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramLocalParameter4dvARB (GLenum target, GLuint index, const GLdouble *params); +GLAPI void APIENTRY glProgramLocalParameter4fARB (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glProgramLocalParameter4fvARB (GLenum target, GLuint index, const GLfloat *params); +GLAPI void APIENTRY glGetProgramEnvParameterdvARB (GLenum target, GLuint index, GLdouble *params); +GLAPI void APIENTRY glGetProgramEnvParameterfvARB (GLenum target, GLuint index, GLfloat *params); +GLAPI void APIENTRY glGetProgramLocalParameterdvARB (GLenum target, GLuint index, GLdouble *params); +GLAPI void APIENTRY glGetProgramLocalParameterfvARB (GLenum target, GLuint index, GLfloat *params); +GLAPI void APIENTRY glGetProgramivARB (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetProgramStringARB (GLenum target, GLenum pname, GLvoid *string); +GLAPI void APIENTRY glGetVertexAttribdvARB (GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetVertexAttribfvARB (GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVertexAttribivARB (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribPointervARB (GLuint index, GLenum pname, GLvoid* *pointer); +GLAPI GLboolean APIENTRY glIsProgramARB (GLuint program); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DARBPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FARBPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SARBPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DARBPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FARBPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SARBPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DARBPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FARBPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SARBPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NBVARBPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NIVARBPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NSVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBARBPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBVARBPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUIVARBPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUSVARBPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4BVARBPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DARBPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FARBPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4IVARBPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SARBPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVARBPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UIVARBPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4USVARBPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERARBPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYARBPROC) (GLuint index); +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYARBPROC) (GLuint index); +typedef void (APIENTRYP PFNGLPROGRAMSTRINGARBPROC) (GLenum target, GLenum format, GLsizei len, const GLvoid *string); +typedef void (APIENTRYP PFNGLBINDPROGRAMARBPROC) (GLenum target, GLuint program); +typedef void (APIENTRYP PFNGLDELETEPROGRAMSARBPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLGENPROGRAMSARBPROC) (GLsizei n, GLuint *programs); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSTRINGARBPROC) (GLenum target, GLenum pname, GLvoid *string); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVARBPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVARBPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVARBPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVARBPROC) (GLuint index, GLenum pname, GLvoid* *pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMARBPROC) (GLuint program); +#endif + +#ifndef GL_ARB_fragment_program +#define GL_ARB_fragment_program 1 +/* All ARB_fragment_program entry points are shared with ARB_vertex_program. */ +#endif + +#ifndef GL_ARB_vertex_buffer_object +#define GL_ARB_vertex_buffer_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindBufferARB (GLenum target, GLuint buffer); +GLAPI void APIENTRY glDeleteBuffersARB (GLsizei n, const GLuint *buffers); +GLAPI void APIENTRY glGenBuffersARB (GLsizei n, GLuint *buffers); +GLAPI GLboolean APIENTRY glIsBufferARB (GLuint buffer); +GLAPI void APIENTRY glBufferDataARB (GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage); +GLAPI void APIENTRY glBufferSubDataARB (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid *data); +GLAPI void APIENTRY glGetBufferSubDataARB (GLenum target, GLintptrARB offset, GLsizeiptrARB size, GLvoid *data); +GLAPI GLvoid* APIENTRY glMapBufferARB (GLenum target, GLenum access); +GLAPI GLboolean APIENTRY glUnmapBufferARB (GLenum target); +GLAPI void APIENTRY glGetBufferParameterivARB (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetBufferPointervARB (GLenum target, GLenum pname, GLvoid* *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDBUFFERARBPROC) (GLenum target, GLuint buffer); +typedef void (APIENTRYP PFNGLDELETEBUFFERSARBPROC) (GLsizei n, const GLuint *buffers); +typedef void (APIENTRYP PFNGLGENBUFFERSARBPROC) (GLsizei n, GLuint *buffers); +typedef GLboolean (APIENTRYP PFNGLISBUFFERARBPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLBUFFERDATAARBPROC) (GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage); +typedef void (APIENTRYP PFNGLBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, GLvoid *data); +typedef GLvoid* (APIENTRYP PFNGLMAPBUFFERARBPROC) (GLenum target, GLenum access); +typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERARBPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETBUFFERPOINTERVARBPROC) (GLenum target, GLenum pname, GLvoid* *params); +#endif + +#ifndef GL_ARB_occlusion_query +#define GL_ARB_occlusion_query 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenQueriesARB (GLsizei n, GLuint *ids); +GLAPI void APIENTRY glDeleteQueriesARB (GLsizei n, const GLuint *ids); +GLAPI GLboolean APIENTRY glIsQueryARB (GLuint id); +GLAPI void APIENTRY glBeginQueryARB (GLenum target, GLuint id); +GLAPI void APIENTRY glEndQueryARB (GLenum target); +GLAPI void APIENTRY glGetQueryivARB (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetQueryObjectivARB (GLuint id, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetQueryObjectuivARB (GLuint id, GLenum pname, GLuint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENQUERIESARBPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEQUERIESARBPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISQUERYARBPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINQUERYARBPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLENDQUERYARBPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETQUERYIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTIVARBPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUIVARBPROC) (GLuint id, GLenum pname, GLuint *params); +#endif + +#ifndef GL_ARB_shader_objects +#define GL_ARB_shader_objects 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeleteObjectARB (GLhandleARB obj); +GLAPI GLhandleARB APIENTRY glGetHandleARB (GLenum pname); +GLAPI void APIENTRY glDetachObjectARB (GLhandleARB containerObj, GLhandleARB attachedObj); +GLAPI GLhandleARB APIENTRY glCreateShaderObjectARB (GLenum shaderType); +GLAPI void APIENTRY glShaderSourceARB (GLhandleARB shaderObj, GLsizei count, const GLcharARB* *string, const GLint *length); +GLAPI void APIENTRY glCompileShaderARB (GLhandleARB shaderObj); +GLAPI GLhandleARB APIENTRY glCreateProgramObjectARB (void); +GLAPI void APIENTRY glAttachObjectARB (GLhandleARB containerObj, GLhandleARB obj); +GLAPI void APIENTRY glLinkProgramARB (GLhandleARB programObj); +GLAPI void APIENTRY glUseProgramObjectARB (GLhandleARB programObj); +GLAPI void APIENTRY glValidateProgramARB (GLhandleARB programObj); +GLAPI void APIENTRY glUniform1fARB (GLint location, GLfloat v0); +GLAPI void APIENTRY glUniform2fARB (GLint location, GLfloat v0, GLfloat v1); +GLAPI void APIENTRY glUniform3fARB (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GLAPI void APIENTRY glUniform4fARB (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLAPI void APIENTRY glUniform1iARB (GLint location, GLint v0); +GLAPI void APIENTRY glUniform2iARB (GLint location, GLint v0, GLint v1); +GLAPI void APIENTRY glUniform3iARB (GLint location, GLint v0, GLint v1, GLint v2); +GLAPI void APIENTRY glUniform4iARB (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GLAPI void APIENTRY glUniform1fvARB (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform2fvARB (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform3fvARB (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform4fvARB (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform1ivARB (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform2ivARB (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform3ivARB (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform4ivARB (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniformMatrix2fvARB (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix3fvARB (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix4fvARB (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glGetObjectParameterfvARB (GLhandleARB obj, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetObjectParameterivARB (GLhandleARB obj, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetInfoLogARB (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog); +GLAPI void APIENTRY glGetAttachedObjectsARB (GLhandleARB containerObj, GLsizei maxCount, GLsizei *count, GLhandleARB *obj); +GLAPI GLint APIENTRY glGetUniformLocationARB (GLhandleARB programObj, const GLcharARB *name); +GLAPI void APIENTRY glGetActiveUniformARB (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +GLAPI void APIENTRY glGetUniformfvARB (GLhandleARB programObj, GLint location, GLfloat *params); +GLAPI void APIENTRY glGetUniformivARB (GLhandleARB programObj, GLint location, GLint *params); +GLAPI void APIENTRY glGetShaderSourceARB (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDELETEOBJECTARBPROC) (GLhandleARB obj); +typedef GLhandleARB (APIENTRYP PFNGLGETHANDLEARBPROC) (GLenum pname); +typedef void (APIENTRYP PFNGLDETACHOBJECTARBPROC) (GLhandleARB containerObj, GLhandleARB attachedObj); +typedef GLhandleARB (APIENTRYP PFNGLCREATESHADEROBJECTARBPROC) (GLenum shaderType); +typedef void (APIENTRYP PFNGLSHADERSOURCEARBPROC) (GLhandleARB shaderObj, GLsizei count, const GLcharARB* *string, const GLint *length); +typedef void (APIENTRYP PFNGLCOMPILESHADERARBPROC) (GLhandleARB shaderObj); +typedef GLhandleARB (APIENTRYP PFNGLCREATEPROGRAMOBJECTARBPROC) (void); +typedef void (APIENTRYP PFNGLATTACHOBJECTARBPROC) (GLhandleARB containerObj, GLhandleARB obj); +typedef void (APIENTRYP PFNGLLINKPROGRAMARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLUSEPROGRAMOBJECTARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLUNIFORM1FARBPROC) (GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLUNIFORM2FARBPROC) (GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLUNIFORM3FARBPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLUNIFORM4FARBPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLUNIFORM1IARBPROC) (GLint location, GLint v0); +typedef void (APIENTRYP PFNGLUNIFORM2IARBPROC) (GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLUNIFORM3IARBPROC) (GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLUNIFORM4IARBPROC) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLUNIFORM1FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM2FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM3FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM4FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM1IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM2IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM3IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM4IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERFVARBPROC) (GLhandleARB obj, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERIVARBPROC) (GLhandleARB obj, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETINFOLOGARBPROC) (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog); +typedef void (APIENTRYP PFNGLGETATTACHEDOBJECTSARBPROC) (GLhandleARB containerObj, GLsizei maxCount, GLsizei *count, GLhandleARB *obj); +typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONARBPROC) (GLhandleARB programObj, const GLcharARB *name); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMARBPROC) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +typedef void (APIENTRYP PFNGLGETUNIFORMFVARBPROC) (GLhandleARB programObj, GLint location, GLfloat *params); +typedef void (APIENTRYP PFNGLGETUNIFORMIVARBPROC) (GLhandleARB programObj, GLint location, GLint *params); +typedef void (APIENTRYP PFNGLGETSHADERSOURCEARBPROC) (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source); +#endif + +#ifndef GL_ARB_vertex_shader +#define GL_ARB_vertex_shader 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindAttribLocationARB (GLhandleARB programObj, GLuint index, const GLcharARB *name); +GLAPI void APIENTRY glGetActiveAttribARB (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +GLAPI GLint APIENTRY glGetAttribLocationARB (GLhandleARB programObj, const GLcharARB *name); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDATTRIBLOCATIONARBPROC) (GLhandleARB programObj, GLuint index, const GLcharARB *name); +typedef void (APIENTRYP PFNGLGETACTIVEATTRIBARBPROC) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONARBPROC) (GLhandleARB programObj, const GLcharARB *name); +#endif + +#ifndef GL_ARB_fragment_shader +#define GL_ARB_fragment_shader 1 +#endif + +#ifndef GL_ARB_shading_language_100 +#define GL_ARB_shading_language_100 1 +#endif + +#ifndef GL_ARB_texture_non_power_of_two +#define GL_ARB_texture_non_power_of_two 1 +#endif + +#ifndef GL_ARB_point_sprite +#define GL_ARB_point_sprite 1 +#endif + +#ifndef GL_ARB_fragment_program_shadow +#define GL_ARB_fragment_program_shadow 1 +#endif + +#ifndef GL_ARB_draw_buffers +#define GL_ARB_draw_buffers 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawBuffersARB (GLsizei n, const GLenum *bufs); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWBUFFERSARBPROC) (GLsizei n, const GLenum *bufs); +#endif + +#ifndef GL_ARB_texture_rectangle +#define GL_ARB_texture_rectangle 1 +#endif + +#ifndef GL_ARB_color_buffer_float +#define GL_ARB_color_buffer_float 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glClampColorARB (GLenum target, GLenum clamp); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCLAMPCOLORARBPROC) (GLenum target, GLenum clamp); +#endif + +#ifndef GL_ARB_half_float_pixel +#define GL_ARB_half_float_pixel 1 +#endif + +#ifndef GL_ARB_texture_float +#define GL_ARB_texture_float 1 +#endif + +#ifndef GL_ARB_pixel_buffer_object +#define GL_ARB_pixel_buffer_object 1 +#endif + +#ifndef GL_ARB_depth_buffer_float +#define GL_ARB_depth_buffer_float 1 +#endif + +#ifndef GL_ARB_draw_instanced +#define GL_ARB_draw_instanced 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawArraysInstancedARB (GLenum mode, GLint first, GLsizei count, GLsizei primcount); +GLAPI void APIENTRY glDrawElementsInstancedARB (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDARBPROC) (GLenum mode, GLint first, GLsizei count, GLsizei primcount); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDARBPROC) (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount); +#endif + +#ifndef GL_ARB_framebuffer_object +#define GL_ARB_framebuffer_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glIsRenderbuffer (GLuint renderbuffer); +GLAPI void APIENTRY glBindRenderbuffer (GLenum target, GLuint renderbuffer); +GLAPI void APIENTRY glDeleteRenderbuffers (GLsizei n, const GLuint *renderbuffers); +GLAPI void APIENTRY glGenRenderbuffers (GLsizei n, GLuint *renderbuffers); +GLAPI void APIENTRY glRenderbufferStorage (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetRenderbufferParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI GLboolean APIENTRY glIsFramebuffer (GLuint framebuffer); +GLAPI void APIENTRY glBindFramebuffer (GLenum target, GLuint framebuffer); +GLAPI void APIENTRY glDeleteFramebuffers (GLsizei n, const GLuint *framebuffers); +GLAPI void APIENTRY glGenFramebuffers (GLsizei n, GLuint *framebuffers); +GLAPI GLenum APIENTRY glCheckFramebufferStatus (GLenum target); +GLAPI void APIENTRY glFramebufferTexture1D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTexture3D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +GLAPI void APIENTRY glFramebufferRenderbuffer (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +GLAPI void APIENTRY glGetFramebufferAttachmentParameteriv (GLenum target, GLenum attachment, GLenum pname, GLint *params); +GLAPI void APIENTRY glGenerateMipmap (GLenum target); +GLAPI void APIENTRY glBlitFramebuffer (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +GLAPI void APIENTRY glRenderbufferStorageMultisample (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glFramebufferTextureLayer (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLboolean (APIENTRYP PFNGLISRENDERBUFFERPROC) (GLuint renderbuffer); +typedef void (APIENTRYP PFNGLBINDRENDERBUFFERPROC) (GLenum target, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLDELETERENDERBUFFERSPROC) (GLsizei n, const GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLGENRENDERBUFFERSPROC) (GLsizei n, GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETRENDERBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef GLboolean (APIENTRYP PFNGLISFRAMEBUFFERPROC) (GLuint framebuffer); +typedef void (APIENTRYP PFNGLBINDFRAMEBUFFERPROC) (GLenum target, GLuint framebuffer); +typedef void (APIENTRYP PFNGLDELETEFRAMEBUFFERSPROC) (GLsizei n, const GLuint *framebuffers); +typedef void (APIENTRYP PFNGLGENFRAMEBUFFERSPROC) (GLsizei n, GLuint *framebuffers); +typedef GLenum (APIENTRYP PFNGLCHECKFRAMEBUFFERSTATUSPROC) (GLenum target); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE1DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE3DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +typedef void (APIENTRYP PFNGLFRAMEBUFFERRENDERBUFFERPROC) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC) (GLenum target, GLenum attachment, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGENERATEMIPMAPPROC) (GLenum target); +typedef void (APIENTRYP PFNGLBLITFRAMEBUFFERPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURELAYERPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +#endif + +#ifndef GL_ARB_framebuffer_sRGB +#define GL_ARB_framebuffer_sRGB 1 +#endif + +#ifndef GL_ARB_geometry_shader4 +#define GL_ARB_geometry_shader4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramParameteriARB (GLuint program, GLenum pname, GLint value); +GLAPI void APIENTRY glFramebufferTextureARB (GLenum target, GLenum attachment, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTextureLayerARB (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +GLAPI void APIENTRY glFramebufferTextureFaceARB (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERIARBPROC) (GLuint program, GLenum pname, GLint value); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREARBPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURELAYERARBPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREFACEARBPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); +#endif + +#ifndef GL_ARB_half_float_vertex +#define GL_ARB_half_float_vertex 1 +#endif + +#ifndef GL_ARB_instanced_arrays +#define GL_ARB_instanced_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribDivisorARB (GLuint index, GLuint divisor); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIBDIVISORARBPROC) (GLuint index, GLuint divisor); +#endif + +#ifndef GL_ARB_map_buffer_range +#define GL_ARB_map_buffer_range 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLvoid* APIENTRY glMapBufferRange (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); +GLAPI void APIENTRY glFlushMappedBufferRange (GLenum target, GLintptr offset, GLsizeiptr length); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLvoid* (APIENTRYP PFNGLMAPBUFFERRANGEPROC) (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); +typedef void (APIENTRYP PFNGLFLUSHMAPPEDBUFFERRANGEPROC) (GLenum target, GLintptr offset, GLsizeiptr length); +#endif + +#ifndef GL_ARB_texture_buffer_object +#define GL_ARB_texture_buffer_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexBufferARB (GLenum target, GLenum internalformat, GLuint buffer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXBUFFERARBPROC) (GLenum target, GLenum internalformat, GLuint buffer); +#endif + +#ifndef GL_ARB_texture_compression_rgtc +#define GL_ARB_texture_compression_rgtc 1 +#endif + +#ifndef GL_ARB_texture_rg +#define GL_ARB_texture_rg 1 +#endif + +#ifndef GL_ARB_vertex_array_object +#define GL_ARB_vertex_array_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindVertexArray (GLuint array); +GLAPI void APIENTRY glDeleteVertexArrays (GLsizei n, const GLuint *arrays); +GLAPI void APIENTRY glGenVertexArrays (GLsizei n, GLuint *arrays); +GLAPI GLboolean APIENTRY glIsVertexArray (GLuint array); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDVERTEXARRAYPROC) (GLuint array); +typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSPROC) (GLsizei n, const GLuint *arrays); +typedef void (APIENTRYP PFNGLGENVERTEXARRAYSPROC) (GLsizei n, GLuint *arrays); +typedef GLboolean (APIENTRYP PFNGLISVERTEXARRAYPROC) (GLuint array); +#endif + +#ifndef GL_ARB_uniform_buffer_object +#define GL_ARB_uniform_buffer_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetUniformIndices (GLuint program, GLsizei uniformCount, const GLchar* *uniformNames, GLuint *uniformIndices); +GLAPI void APIENTRY glGetActiveUniformsiv (GLuint program, GLsizei uniformCount, const GLuint *uniformIndices, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetActiveUniformName (GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformName); +GLAPI GLuint APIENTRY glGetUniformBlockIndex (GLuint program, const GLchar *uniformBlockName); +GLAPI void APIENTRY glGetActiveUniformBlockiv (GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetActiveUniformBlockName (GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformBlockName); +GLAPI void APIENTRY glUniformBlockBinding (GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETUNIFORMINDICESPROC) (GLuint program, GLsizei uniformCount, const GLchar* *uniformNames, GLuint *uniformIndices); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMSIVPROC) (GLuint program, GLsizei uniformCount, const GLuint *uniformIndices, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMNAMEPROC) (GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformName); +typedef GLuint (APIENTRYP PFNGLGETUNIFORMBLOCKINDEXPROC) (GLuint program, const GLchar *uniformBlockName); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMBLOCKIVPROC) (GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC) (GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformBlockName); +typedef void (APIENTRYP PFNGLUNIFORMBLOCKBINDINGPROC) (GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); +#endif + +#ifndef GL_ARB_compatibility +#define GL_ARB_compatibility 1 +#endif + +#ifndef GL_ARB_copy_buffer +#define GL_ARB_copy_buffer 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCopyBufferSubData (GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOPYBUFFERSUBDATAPROC) (GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +#endif + +#ifndef GL_ARB_shader_texture_lod +#define GL_ARB_shader_texture_lod 1 +#endif + +#ifndef GL_ARB_depth_clamp +#define GL_ARB_depth_clamp 1 +#endif + +#ifndef GL_ARB_draw_elements_base_vertex +#define GL_ARB_draw_elements_base_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawElementsBaseVertex (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLint basevertex); +GLAPI void APIENTRY glDrawRangeElementsBaseVertex (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices, GLint basevertex); +GLAPI void APIENTRY glDrawElementsInstancedBaseVertex (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount, GLint basevertex); +GLAPI void APIENTRY glMultiDrawElementsBaseVertex (GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount, const GLint *basevertex); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWELEMENTSBASEVERTEXPROC) (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLint basevertex); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSBASEVERTEXPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices, GLint basevertex); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXPROC) (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount, GLint basevertex); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSBASEVERTEXPROC) (GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount, const GLint *basevertex); +#endif + +#ifndef GL_ARB_fragment_coord_conventions +#define GL_ARB_fragment_coord_conventions 1 +#endif + +#ifndef GL_ARB_provoking_vertex +#define GL_ARB_provoking_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProvokingVertex (GLenum mode); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROVOKINGVERTEXPROC) (GLenum mode); +#endif + +#ifndef GL_ARB_seamless_cube_map +#define GL_ARB_seamless_cube_map 1 +#endif + +#ifndef GL_ARB_sync +#define GL_ARB_sync 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLsync APIENTRY glFenceSync (GLenum condition, GLbitfield flags); +GLAPI GLboolean APIENTRY glIsSync (GLsync sync); +GLAPI void APIENTRY glDeleteSync (GLsync sync); +GLAPI GLenum APIENTRY glClientWaitSync (GLsync sync, GLbitfield flags, GLuint64 timeout); +GLAPI void APIENTRY glWaitSync (GLsync sync, GLbitfield flags, GLuint64 timeout); +GLAPI void APIENTRY glGetInteger64v (GLenum pname, GLint64 *params); +GLAPI void APIENTRY glGetSynciv (GLsync sync, GLenum pname, GLsizei bufSize, GLsizei *length, GLint *values); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLsync (APIENTRYP PFNGLFENCESYNCPROC) (GLenum condition, GLbitfield flags); +typedef GLboolean (APIENTRYP PFNGLISSYNCPROC) (GLsync sync); +typedef void (APIENTRYP PFNGLDELETESYNCPROC) (GLsync sync); +typedef GLenum (APIENTRYP PFNGLCLIENTWAITSYNCPROC) (GLsync sync, GLbitfield flags, GLuint64 timeout); +typedef void (APIENTRYP PFNGLWAITSYNCPROC) (GLsync sync, GLbitfield flags, GLuint64 timeout); +typedef void (APIENTRYP PFNGLGETINTEGER64VPROC) (GLenum pname, GLint64 *params); +typedef void (APIENTRYP PFNGLGETSYNCIVPROC) (GLsync sync, GLenum pname, GLsizei bufSize, GLsizei *length, GLint *values); +#endif + +#ifndef GL_ARB_texture_multisample +#define GL_ARB_texture_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage2DMultisample (GLenum target, GLsizei samples, GLint internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glTexImage3DMultisample (GLenum target, GLsizei samples, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glGetMultisamplefv (GLenum pname, GLuint index, GLfloat *val); +GLAPI void APIENTRY glSampleMaski (GLuint index, GLbitfield mask); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXIMAGE2DMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLint internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLTEXIMAGE3DMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLGETMULTISAMPLEFVPROC) (GLenum pname, GLuint index, GLfloat *val); +typedef void (APIENTRYP PFNGLSAMPLEMASKIPROC) (GLuint index, GLbitfield mask); +#endif + +#ifndef GL_ARB_vertex_array_bgra +#define GL_ARB_vertex_array_bgra 1 +#endif + +#ifndef GL_ARB_draw_buffers_blend +#define GL_ARB_draw_buffers_blend 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationiARB (GLuint buf, GLenum mode); +GLAPI void APIENTRY glBlendEquationSeparateiARB (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +GLAPI void APIENTRY glBlendFunciARB (GLuint buf, GLenum src, GLenum dst); +GLAPI void APIENTRY glBlendFuncSeparateiARB (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDEQUATIONIARBPROC) (GLuint buf, GLenum mode); +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEIARBPROC) (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +typedef void (APIENTRYP PFNGLBLENDFUNCIARBPROC) (GLuint buf, GLenum src, GLenum dst); +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEIARBPROC) (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +#endif + +#ifndef GL_ARB_sample_shading +#define GL_ARB_sample_shading 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMinSampleShadingARB (GLclampf value); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMINSAMPLESHADINGARBPROC) (GLclampf value); +#endif + +#ifndef GL_ARB_texture_cube_map_array +#define GL_ARB_texture_cube_map_array 1 +#endif + +#ifndef GL_ARB_texture_gather +#define GL_ARB_texture_gather 1 +#endif + +#ifndef GL_ARB_texture_query_lod +#define GL_ARB_texture_query_lod 1 +#endif + +#ifndef GL_ARB_shading_language_include +#define GL_ARB_shading_language_include 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glNamedStringARB (GLenum type, GLint namelen, const GLchar *name, GLint stringlen, const GLchar *string); +GLAPI void APIENTRY glDeleteNamedStringARB (GLint namelen, const GLchar *name); +GLAPI void APIENTRY glCompileShaderIncludeARB (GLuint shader, GLsizei count, const GLchar* *path, const GLint *length); +GLAPI GLboolean APIENTRY glIsNamedStringARB (GLint namelen, const GLchar *name); +GLAPI void APIENTRY glGetNamedStringARB (GLint namelen, const GLchar *name, GLsizei bufSize, GLint *stringlen, GLchar *string); +GLAPI void APIENTRY glGetNamedStringivARB (GLint namelen, const GLchar *name, GLenum pname, GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLNAMEDSTRINGARBPROC) (GLenum type, GLint namelen, const GLchar *name, GLint stringlen, const GLchar *string); +typedef void (APIENTRYP PFNGLDELETENAMEDSTRINGARBPROC) (GLint namelen, const GLchar *name); +typedef void (APIENTRYP PFNGLCOMPILESHADERINCLUDEARBPROC) (GLuint shader, GLsizei count, const GLchar* *path, const GLint *length); +typedef GLboolean (APIENTRYP PFNGLISNAMEDSTRINGARBPROC) (GLint namelen, const GLchar *name); +typedef void (APIENTRYP PFNGLGETNAMEDSTRINGARBPROC) (GLint namelen, const GLchar *name, GLsizei bufSize, GLint *stringlen, GLchar *string); +typedef void (APIENTRYP PFNGLGETNAMEDSTRINGIVARBPROC) (GLint namelen, const GLchar *name, GLenum pname, GLint *params); +#endif + +#ifndef GL_ARB_texture_compression_bptc +#define GL_ARB_texture_compression_bptc 1 +#endif + +#ifndef GL_ARB_blend_func_extended +#define GL_ARB_blend_func_extended 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindFragDataLocationIndexed (GLuint program, GLuint colorNumber, GLuint index, const GLchar *name); +GLAPI GLint APIENTRY glGetFragDataIndex (GLuint program, const GLchar *name); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDFRAGDATALOCATIONINDEXEDPROC) (GLuint program, GLuint colorNumber, GLuint index, const GLchar *name); +typedef GLint (APIENTRYP PFNGLGETFRAGDATAINDEXPROC) (GLuint program, const GLchar *name); +#endif + +#ifndef GL_ARB_explicit_attrib_location +#define GL_ARB_explicit_attrib_location 1 +#endif + +#ifndef GL_ARB_occlusion_query2 +#define GL_ARB_occlusion_query2 1 +#endif + +#ifndef GL_ARB_sampler_objects +#define GL_ARB_sampler_objects 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenSamplers (GLsizei count, GLuint *samplers); +GLAPI void APIENTRY glDeleteSamplers (GLsizei count, const GLuint *samplers); +GLAPI GLboolean APIENTRY glIsSampler (GLuint sampler); +GLAPI void APIENTRY glBindSampler (GLuint unit, GLuint sampler); +GLAPI void APIENTRY glSamplerParameteri (GLuint sampler, GLenum pname, GLint param); +GLAPI void APIENTRY glSamplerParameteriv (GLuint sampler, GLenum pname, const GLint *param); +GLAPI void APIENTRY glSamplerParameterf (GLuint sampler, GLenum pname, GLfloat param); +GLAPI void APIENTRY glSamplerParameterfv (GLuint sampler, GLenum pname, const GLfloat *param); +GLAPI void APIENTRY glSamplerParameterIiv (GLuint sampler, GLenum pname, const GLint *param); +GLAPI void APIENTRY glSamplerParameterIuiv (GLuint sampler, GLenum pname, const GLuint *param); +GLAPI void APIENTRY glGetSamplerParameteriv (GLuint sampler, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetSamplerParameterIiv (GLuint sampler, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetSamplerParameterfv (GLuint sampler, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetSamplerParameterIuiv (GLuint sampler, GLenum pname, GLuint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENSAMPLERSPROC) (GLsizei count, GLuint *samplers); +typedef void (APIENTRYP PFNGLDELETESAMPLERSPROC) (GLsizei count, const GLuint *samplers); +typedef GLboolean (APIENTRYP PFNGLISSAMPLERPROC) (GLuint sampler); +typedef void (APIENTRYP PFNGLBINDSAMPLERPROC) (GLuint unit, GLuint sampler); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIPROC) (GLuint sampler, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIVPROC) (GLuint sampler, GLenum pname, const GLint *param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERFPROC) (GLuint sampler, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERFVPROC) (GLuint sampler, GLenum pname, const GLfloat *param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIIVPROC) (GLuint sampler, GLenum pname, const GLint *param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIUIVPROC) (GLuint sampler, GLenum pname, const GLuint *param); +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIVPROC) (GLuint sampler, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIIVPROC) (GLuint sampler, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERFVPROC) (GLuint sampler, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIUIVPROC) (GLuint sampler, GLenum pname, GLuint *params); +#endif + +#ifndef GL_ARB_texture_rgb10_a2ui +#define GL_ARB_texture_rgb10_a2ui 1 +#endif + +#ifndef GL_ARB_texture_swizzle +#define GL_ARB_texture_swizzle 1 +#endif + +#ifndef GL_ARB_timer_query +#define GL_ARB_timer_query 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glQueryCounter (GLuint id, GLenum target); +GLAPI void APIENTRY glGetQueryObjecti64v (GLuint id, GLenum pname, GLint64 *params); +GLAPI void APIENTRY glGetQueryObjectui64v (GLuint id, GLenum pname, GLuint64 *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLQUERYCOUNTERPROC) (GLuint id, GLenum target); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTI64VPROC) (GLuint id, GLenum pname, GLint64 *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUI64VPROC) (GLuint id, GLenum pname, GLuint64 *params); +#endif + +#ifndef GL_ARB_vertex_type_2_10_10_10_rev +#define GL_ARB_vertex_type_2_10_10_10_rev 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexP2ui (GLenum type, GLuint value); +GLAPI void APIENTRY glVertexP2uiv (GLenum type, const GLuint *value); +GLAPI void APIENTRY glVertexP3ui (GLenum type, GLuint value); +GLAPI void APIENTRY glVertexP3uiv (GLenum type, const GLuint *value); +GLAPI void APIENTRY glVertexP4ui (GLenum type, GLuint value); +GLAPI void APIENTRY glVertexP4uiv (GLenum type, const GLuint *value); +GLAPI void APIENTRY glTexCoordP1ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glTexCoordP1uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glTexCoordP2ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glTexCoordP2uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glTexCoordP3ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glTexCoordP3uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glTexCoordP4ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glTexCoordP4uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glMultiTexCoordP1ui (GLenum texture, GLenum type, GLuint coords); +GLAPI void APIENTRY glMultiTexCoordP1uiv (GLenum texture, GLenum type, const GLuint *coords); +GLAPI void APIENTRY glMultiTexCoordP2ui (GLenum texture, GLenum type, GLuint coords); +GLAPI void APIENTRY glMultiTexCoordP2uiv (GLenum texture, GLenum type, const GLuint *coords); +GLAPI void APIENTRY glMultiTexCoordP3ui (GLenum texture, GLenum type, GLuint coords); +GLAPI void APIENTRY glMultiTexCoordP3uiv (GLenum texture, GLenum type, const GLuint *coords); +GLAPI void APIENTRY glMultiTexCoordP4ui (GLenum texture, GLenum type, GLuint coords); +GLAPI void APIENTRY glMultiTexCoordP4uiv (GLenum texture, GLenum type, const GLuint *coords); +GLAPI void APIENTRY glNormalP3ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glNormalP3uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glColorP3ui (GLenum type, GLuint color); +GLAPI void APIENTRY glColorP3uiv (GLenum type, const GLuint *color); +GLAPI void APIENTRY glColorP4ui (GLenum type, GLuint color); +GLAPI void APIENTRY glColorP4uiv (GLenum type, const GLuint *color); +GLAPI void APIENTRY glSecondaryColorP3ui (GLenum type, GLuint color); +GLAPI void APIENTRY glSecondaryColorP3uiv (GLenum type, const GLuint *color); +GLAPI void APIENTRY glVertexAttribP1ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI void APIENTRY glVertexAttribP1uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +GLAPI void APIENTRY glVertexAttribP2ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI void APIENTRY glVertexAttribP2uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +GLAPI void APIENTRY glVertexAttribP3ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI void APIENTRY glVertexAttribP3uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +GLAPI void APIENTRY glVertexAttribP4ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI void APIENTRY glVertexAttribP4uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXP2UIPROC) (GLenum type, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXP2UIVPROC) (GLenum type, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXP3UIPROC) (GLenum type, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXP3UIVPROC) (GLenum type, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXP4UIPROC) (GLenum type, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXP4UIVPROC) (GLenum type, const GLuint *value); +typedef void (APIENTRYP PFNGLTEXCOORDP1UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLTEXCOORDP1UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLTEXCOORDP2UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLTEXCOORDP2UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLTEXCOORDP3UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLTEXCOORDP3UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLTEXCOORDP4UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLTEXCOORDP4UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP1UIPROC) (GLenum texture, GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP1UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP2UIPROC) (GLenum texture, GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP2UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP3UIPROC) (GLenum texture, GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP3UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP4UIPROC) (GLenum texture, GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP4UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLNORMALP3UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLNORMALP3UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLCOLORP3UIPROC) (GLenum type, GLuint color); +typedef void (APIENTRYP PFNGLCOLORP3UIVPROC) (GLenum type, const GLuint *color); +typedef void (APIENTRYP PFNGLCOLORP4UIPROC) (GLenum type, GLuint color); +typedef void (APIENTRYP PFNGLCOLORP4UIVPROC) (GLenum type, const GLuint *color); +typedef void (APIENTRYP PFNGLSECONDARYCOLORP3UIPROC) (GLenum type, GLuint color); +typedef void (APIENTRYP PFNGLSECONDARYCOLORP3UIVPROC) (GLenum type, const GLuint *color); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP1UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP1UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP2UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP2UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP3UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP3UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP4UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP4UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +#endif + +#ifndef GL_ARB_draw_indirect +#define GL_ARB_draw_indirect 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawArraysIndirect (GLenum mode, const GLvoid *indirect); +GLAPI void APIENTRY glDrawElementsIndirect (GLenum mode, GLenum type, const GLvoid *indirect); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWARRAYSINDIRECTPROC) (GLenum mode, const GLvoid *indirect); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINDIRECTPROC) (GLenum mode, GLenum type, const GLvoid *indirect); +#endif + +#ifndef GL_ARB_gpu_shader5 +#define GL_ARB_gpu_shader5 1 +#endif + +#ifndef GL_ARB_gpu_shader_fp64 +#define GL_ARB_gpu_shader_fp64 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUniform1d (GLint location, GLdouble x); +GLAPI void APIENTRY glUniform2d (GLint location, GLdouble x, GLdouble y); +GLAPI void APIENTRY glUniform3d (GLint location, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glUniform4d (GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glUniform1dv (GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glUniform2dv (GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glUniform3dv (GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glUniform4dv (GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix2dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix3dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix4dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix2x3dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix2x4dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix3x2dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix3x4dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix4x2dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix4x3dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glGetUniformdv (GLuint program, GLint location, GLdouble *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLUNIFORM1DPROC) (GLint location, GLdouble x); +typedef void (APIENTRYP PFNGLUNIFORM2DPROC) (GLint location, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLUNIFORM3DPROC) (GLint location, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLUNIFORM4DPROC) (GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLUNIFORM1DVPROC) (GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORM2DVPROC) (GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORM3DVPROC) (GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORM4DVPROC) (GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X3DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X4DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X2DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X4DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X2DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X3DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLGETUNIFORMDVPROC) (GLuint program, GLint location, GLdouble *params); +#endif + +#ifndef GL_ARB_shader_subroutine +#define GL_ARB_shader_subroutine 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLint APIENTRY glGetSubroutineUniformLocation (GLuint program, GLenum shadertype, const GLchar *name); +GLAPI GLuint APIENTRY glGetSubroutineIndex (GLuint program, GLenum shadertype, const GLchar *name); +GLAPI void APIENTRY glGetActiveSubroutineUniformiv (GLuint program, GLenum shadertype, GLuint index, GLenum pname, GLint *values); +GLAPI void APIENTRY glGetActiveSubroutineUniformName (GLuint program, GLenum shadertype, GLuint index, GLsizei bufsize, GLsizei *length, GLchar *name); +GLAPI void APIENTRY glGetActiveSubroutineName (GLuint program, GLenum shadertype, GLuint index, GLsizei bufsize, GLsizei *length, GLchar *name); +GLAPI void APIENTRY glUniformSubroutinesuiv (GLenum shadertype, GLsizei count, const GLuint *indices); +GLAPI void APIENTRY glGetUniformSubroutineuiv (GLenum shadertype, GLint location, GLuint *params); +GLAPI void APIENTRY glGetProgramStageiv (GLuint program, GLenum shadertype, GLenum pname, GLint *values); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLint (APIENTRYP PFNGLGETSUBROUTINEUNIFORMLOCATIONPROC) (GLuint program, GLenum shadertype, const GLchar *name); +typedef GLuint (APIENTRYP PFNGLGETSUBROUTINEINDEXPROC) (GLuint program, GLenum shadertype, const GLchar *name); +typedef void (APIENTRYP PFNGLGETACTIVESUBROUTINEUNIFORMIVPROC) (GLuint program, GLenum shadertype, GLuint index, GLenum pname, GLint *values); +typedef void (APIENTRYP PFNGLGETACTIVESUBROUTINEUNIFORMNAMEPROC) (GLuint program, GLenum shadertype, GLuint index, GLsizei bufsize, GLsizei *length, GLchar *name); +typedef void (APIENTRYP PFNGLGETACTIVESUBROUTINENAMEPROC) (GLuint program, GLenum shadertype, GLuint index, GLsizei bufsize, GLsizei *length, GLchar *name); +typedef void (APIENTRYP PFNGLUNIFORMSUBROUTINESUIVPROC) (GLenum shadertype, GLsizei count, const GLuint *indices); +typedef void (APIENTRYP PFNGLGETUNIFORMSUBROUTINEUIVPROC) (GLenum shadertype, GLint location, GLuint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSTAGEIVPROC) (GLuint program, GLenum shadertype, GLenum pname, GLint *values); +#endif + +#ifndef GL_ARB_tessellation_shader +#define GL_ARB_tessellation_shader 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPatchParameteri (GLenum pname, GLint value); +GLAPI void APIENTRY glPatchParameterfv (GLenum pname, const GLfloat *values); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPATCHPARAMETERIPROC) (GLenum pname, GLint value); +typedef void (APIENTRYP PFNGLPATCHPARAMETERFVPROC) (GLenum pname, const GLfloat *values); +#endif + +#ifndef GL_ARB_texture_buffer_object_rgb32 +#define GL_ARB_texture_buffer_object_rgb32 1 +#endif + +#ifndef GL_ARB_transform_feedback2 +#define GL_ARB_transform_feedback2 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindTransformFeedback (GLenum target, GLuint id); +GLAPI void APIENTRY glDeleteTransformFeedbacks (GLsizei n, const GLuint *ids); +GLAPI void APIENTRY glGenTransformFeedbacks (GLsizei n, GLuint *ids); +GLAPI GLboolean APIENTRY glIsTransformFeedback (GLuint id); +GLAPI void APIENTRY glPauseTransformFeedback (void); +GLAPI void APIENTRY glResumeTransformFeedback (void); +GLAPI void APIENTRY glDrawTransformFeedback (GLenum mode, GLuint id); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDTRANSFORMFEEDBACKPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLDELETETRANSFORMFEEDBACKSPROC) (GLsizei n, const GLuint *ids); +typedef void (APIENTRYP PFNGLGENTRANSFORMFEEDBACKSPROC) (GLsizei n, GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISTRANSFORMFEEDBACKPROC) (GLuint id); +typedef void (APIENTRYP PFNGLPAUSETRANSFORMFEEDBACKPROC) (void); +typedef void (APIENTRYP PFNGLRESUMETRANSFORMFEEDBACKPROC) (void); +typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKPROC) (GLenum mode, GLuint id); +#endif + +#ifndef GL_ARB_transform_feedback3 +#define GL_ARB_transform_feedback3 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawTransformFeedbackStream (GLenum mode, GLuint id, GLuint stream); +GLAPI void APIENTRY glBeginQueryIndexed (GLenum target, GLuint index, GLuint id); +GLAPI void APIENTRY glEndQueryIndexed (GLenum target, GLuint index); +GLAPI void APIENTRY glGetQueryIndexediv (GLenum target, GLuint index, GLenum pname, GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKSTREAMPROC) (GLenum mode, GLuint id, GLuint stream); +typedef void (APIENTRYP PFNGLBEGINQUERYINDEXEDPROC) (GLenum target, GLuint index, GLuint id); +typedef void (APIENTRYP PFNGLENDQUERYINDEXEDPROC) (GLenum target, GLuint index); +typedef void (APIENTRYP PFNGLGETQUERYINDEXEDIVPROC) (GLenum target, GLuint index, GLenum pname, GLint *params); +#endif + +#ifndef GL_ARB_ES2_compatibility +#define GL_ARB_ES2_compatibility 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glReleaseShaderCompiler (void); +GLAPI void APIENTRY glShaderBinary (GLsizei count, const GLuint *shaders, GLenum binaryformat, const GLvoid *binary, GLsizei length); +GLAPI void APIENTRY glGetShaderPrecisionFormat (GLenum shadertype, GLenum precisiontype, GLint *range, GLint *precision); +GLAPI void APIENTRY glDepthRangef (GLclampf n, GLclampf f); +GLAPI void APIENTRY glClearDepthf (GLclampf d); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLRELEASESHADERCOMPILERPROC) (void); +typedef void (APIENTRYP PFNGLSHADERBINARYPROC) (GLsizei count, const GLuint *shaders, GLenum binaryformat, const GLvoid *binary, GLsizei length); +typedef void (APIENTRYP PFNGLGETSHADERPRECISIONFORMATPROC) (GLenum shadertype, GLenum precisiontype, GLint *range, GLint *precision); +typedef void (APIENTRYP PFNGLDEPTHRANGEFPROC) (GLclampf n, GLclampf f); +typedef void (APIENTRYP PFNGLCLEARDEPTHFPROC) (GLclampf d); +#endif + +#ifndef GL_ARB_get_program_binary +#define GL_ARB_get_program_binary 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetProgramBinary (GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, GLvoid *binary); +GLAPI void APIENTRY glProgramBinary (GLuint program, GLenum binaryFormat, const GLvoid *binary, GLsizei length); +GLAPI void APIENTRY glProgramParameteri (GLuint program, GLenum pname, GLint value); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETPROGRAMBINARYPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, GLvoid *binary); +typedef void (APIENTRYP PFNGLPROGRAMBINARYPROC) (GLuint program, GLenum binaryFormat, const GLvoid *binary, GLsizei length); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERIPROC) (GLuint program, GLenum pname, GLint value); +#endif + +#ifndef GL_ARB_separate_shader_objects +#define GL_ARB_separate_shader_objects 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUseProgramStages (GLuint pipeline, GLbitfield stages, GLuint program); +GLAPI void APIENTRY glActiveShaderProgram (GLuint pipeline, GLuint program); +GLAPI GLuint APIENTRY glCreateShaderProgramv (GLenum type, GLsizei count, const GLchar* *strings); +GLAPI void APIENTRY glBindProgramPipeline (GLuint pipeline); +GLAPI void APIENTRY glDeleteProgramPipelines (GLsizei n, const GLuint *pipelines); +GLAPI void APIENTRY glGenProgramPipelines (GLsizei n, GLuint *pipelines); +GLAPI GLboolean APIENTRY glIsProgramPipeline (GLuint pipeline); +GLAPI void APIENTRY glGetProgramPipelineiv (GLuint pipeline, GLenum pname, GLint *params); +GLAPI void APIENTRY glProgramUniform1i (GLuint program, GLint location, GLint v0); +GLAPI void APIENTRY glProgramUniform1iv (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform1f (GLuint program, GLint location, GLfloat v0); +GLAPI void APIENTRY glProgramUniform1fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform1d (GLuint program, GLint location, GLdouble v0); +GLAPI void APIENTRY glProgramUniform1dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform1ui (GLuint program, GLint location, GLuint v0); +GLAPI void APIENTRY glProgramUniform1uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform2i (GLuint program, GLint location, GLint v0, GLint v1); +GLAPI void APIENTRY glProgramUniform2iv (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform2f (GLuint program, GLint location, GLfloat v0, GLfloat v1); +GLAPI void APIENTRY glProgramUniform2fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform2d (GLuint program, GLint location, GLdouble v0, GLdouble v1); +GLAPI void APIENTRY glProgramUniform2dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform2ui (GLuint program, GLint location, GLuint v0, GLuint v1); +GLAPI void APIENTRY glProgramUniform2uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform3i (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +GLAPI void APIENTRY glProgramUniform3iv (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform3f (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GLAPI void APIENTRY glProgramUniform3fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform3d (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2); +GLAPI void APIENTRY glProgramUniform3dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform3ui (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +GLAPI void APIENTRY glProgramUniform3uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform4i (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GLAPI void APIENTRY glProgramUniform4iv (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform4f (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLAPI void APIENTRY glProgramUniform4fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform4d (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3); +GLAPI void APIENTRY glProgramUniform4dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform4ui (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +GLAPI void APIENTRY glProgramUniform4uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniformMatrix2fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2x3fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3x2fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2x4fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4x2fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3x4fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4x3fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2x3dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3x2dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2x4dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4x2dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3x4dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4x3dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glValidateProgramPipeline (GLuint pipeline); +GLAPI void APIENTRY glGetProgramPipelineInfoLog (GLuint pipeline, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLUSEPROGRAMSTAGESPROC) (GLuint pipeline, GLbitfield stages, GLuint program); +typedef void (APIENTRYP PFNGLACTIVESHADERPROGRAMPROC) (GLuint pipeline, GLuint program); +typedef GLuint (APIENTRYP PFNGLCREATESHADERPROGRAMVPROC) (GLenum type, GLsizei count, const GLchar* *strings); +typedef void (APIENTRYP PFNGLBINDPROGRAMPIPELINEPROC) (GLuint pipeline); +typedef void (APIENTRYP PFNGLDELETEPROGRAMPIPELINESPROC) (GLsizei n, const GLuint *pipelines); +typedef void (APIENTRYP PFNGLGENPROGRAMPIPELINESPROC) (GLsizei n, GLuint *pipelines); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMPIPELINEPROC) (GLuint pipeline); +typedef void (APIENTRYP PFNGLGETPROGRAMPIPELINEIVPROC) (GLuint pipeline, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IPROC) (GLuint program, GLint location, GLint v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FPROC) (GLuint program, GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DPROC) (GLuint program, GLint location, GLdouble v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIPROC) (GLuint program, GLint location, GLuint v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IPROC) (GLuint program, GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DPROC) (GLuint program, GLint location, GLdouble v0, GLdouble v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIPROC) (GLuint program, GLint location, GLuint v0, GLuint v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DPROC) (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DPROC) (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMPIPELINEPROC) (GLuint pipeline); +typedef void (APIENTRYP PFNGLGETPROGRAMPIPELINEINFOLOGPROC) (GLuint pipeline, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +#endif + +#ifndef GL_ARB_vertex_attrib_64bit +#define GL_ARB_vertex_attrib_64bit 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribL1d (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttribL2d (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttribL3d (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttribL4d (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttribL1dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL2dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL3dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL4dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribLPointer (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void APIENTRY glGetVertexAttribLdv (GLuint index, GLenum pname, GLdouble *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBLPOINTERPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLDVPROC) (GLuint index, GLenum pname, GLdouble *params); +#endif + +#ifndef GL_ARB_viewport_array +#define GL_ARB_viewport_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glViewportArrayv (GLuint first, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glViewportIndexedf (GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h); +GLAPI void APIENTRY glViewportIndexedfv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glScissorArrayv (GLuint first, GLsizei count, const GLint *v); +GLAPI void APIENTRY glScissorIndexed (GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height); +GLAPI void APIENTRY glScissorIndexedv (GLuint index, const GLint *v); +GLAPI void APIENTRY glDepthRangeArrayv (GLuint first, GLsizei count, const GLclampd *v); +GLAPI void APIENTRY glDepthRangeIndexed (GLuint index, GLclampd n, GLclampd f); +GLAPI void APIENTRY glGetFloati_v (GLenum target, GLuint index, GLfloat *data); +GLAPI void APIENTRY glGetDoublei_v (GLenum target, GLuint index, GLdouble *data); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVIEWPORTARRAYVPROC) (GLuint first, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVIEWPORTINDEXEDFPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h); +typedef void (APIENTRYP PFNGLVIEWPORTINDEXEDFVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLSCISSORARRAYVPROC) (GLuint first, GLsizei count, const GLint *v); +typedef void (APIENTRYP PFNGLSCISSORINDEXEDPROC) (GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLSCISSORINDEXEDVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLDEPTHRANGEARRAYVPROC) (GLuint first, GLsizei count, const GLclampd *v); +typedef void (APIENTRYP PFNGLDEPTHRANGEINDEXEDPROC) (GLuint index, GLclampd n, GLclampd f); +typedef void (APIENTRYP PFNGLGETFLOATI_VPROC) (GLenum target, GLuint index, GLfloat *data); +typedef void (APIENTRYP PFNGLGETDOUBLEI_VPROC) (GLenum target, GLuint index, GLdouble *data); +#endif + +#ifndef GL_ARB_cl_event +#define GL_ARB_cl_event 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLsync APIENTRY glCreateSyncFromCLeventARB (struct _cl_context * context, struct _cl_event * event, GLbitfield flags); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLsync (APIENTRYP PFNGLCREATESYNCFROMCLEVENTARBPROC) (struct _cl_context * context, struct _cl_event * event, GLbitfield flags); +#endif + +#ifndef GL_ARB_debug_output +#define GL_ARB_debug_output 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDebugMessageControlARB (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +GLAPI void APIENTRY glDebugMessageInsertARB (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); +GLAPI void APIENTRY glDebugMessageCallbackARB (GLDEBUGPROCARB callback, const GLvoid *userParam); +GLAPI GLuint APIENTRY glGetDebugMessageLogARB (GLuint count, GLsizei bufsize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDEBUGMESSAGECONTROLARBPROC) (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +typedef void (APIENTRYP PFNGLDEBUGMESSAGEINSERTARBPROC) (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); +typedef void (APIENTRYP PFNGLDEBUGMESSAGECALLBACKARBPROC) (GLDEBUGPROCARB callback, const GLvoid *userParam); +typedef GLuint (APIENTRYP PFNGLGETDEBUGMESSAGELOGARBPROC) (GLuint count, GLsizei bufsize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); +#endif + +#ifndef GL_ARB_robustness +#define GL_ARB_robustness 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLenum APIENTRY glGetGraphicsResetStatusARB (void); +GLAPI void APIENTRY glGetnMapdvARB (GLenum target, GLenum query, GLsizei bufSize, GLdouble *v); +GLAPI void APIENTRY glGetnMapfvARB (GLenum target, GLenum query, GLsizei bufSize, GLfloat *v); +GLAPI void APIENTRY glGetnMapivARB (GLenum target, GLenum query, GLsizei bufSize, GLint *v); +GLAPI void APIENTRY glGetnPixelMapfvARB (GLenum map, GLsizei bufSize, GLfloat *values); +GLAPI void APIENTRY glGetnPixelMapuivARB (GLenum map, GLsizei bufSize, GLuint *values); +GLAPI void APIENTRY glGetnPixelMapusvARB (GLenum map, GLsizei bufSize, GLushort *values); +GLAPI void APIENTRY glGetnPolygonStippleARB (GLsizei bufSize, GLubyte *pattern); +GLAPI void APIENTRY glGetnColorTableARB (GLenum target, GLenum format, GLenum type, GLsizei bufSize, GLvoid *table); +GLAPI void APIENTRY glGetnConvolutionFilterARB (GLenum target, GLenum format, GLenum type, GLsizei bufSize, GLvoid *image); +GLAPI void APIENTRY glGetnSeparableFilterARB (GLenum target, GLenum format, GLenum type, GLsizei rowBufSize, GLvoid *row, GLsizei columnBufSize, GLvoid *column, GLvoid *span); +GLAPI void APIENTRY glGetnHistogramARB (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, GLvoid *values); +GLAPI void APIENTRY glGetnMinmaxARB (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, GLvoid *values); +GLAPI void APIENTRY glGetnTexImageARB (GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, GLvoid *img); +GLAPI void APIENTRY glReadnPixelsARB (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, GLvoid *data); +GLAPI void APIENTRY glGetnCompressedTexImageARB (GLenum target, GLint lod, GLsizei bufSize, GLvoid *img); +GLAPI void APIENTRY glGetnUniformfvARB (GLuint program, GLint location, GLsizei bufSize, GLfloat *params); +GLAPI void APIENTRY glGetnUniformivARB (GLuint program, GLint location, GLsizei bufSize, GLint *params); +GLAPI void APIENTRY glGetnUniformuivARB (GLuint program, GLint location, GLsizei bufSize, GLuint *params); +GLAPI void APIENTRY glGetnUniformdvARB (GLuint program, GLint location, GLsizei bufSize, GLdouble *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLenum (APIENTRYP PFNGLGETGRAPHICSRESETSTATUSARBPROC) (void); +typedef void (APIENTRYP PFNGLGETNMAPDVARBPROC) (GLenum target, GLenum query, GLsizei bufSize, GLdouble *v); +typedef void (APIENTRYP PFNGLGETNMAPFVARBPROC) (GLenum target, GLenum query, GLsizei bufSize, GLfloat *v); +typedef void (APIENTRYP PFNGLGETNMAPIVARBPROC) (GLenum target, GLenum query, GLsizei bufSize, GLint *v); +typedef void (APIENTRYP PFNGLGETNPIXELMAPFVARBPROC) (GLenum map, GLsizei bufSize, GLfloat *values); +typedef void (APIENTRYP PFNGLGETNPIXELMAPUIVARBPROC) (GLenum map, GLsizei bufSize, GLuint *values); +typedef void (APIENTRYP PFNGLGETNPIXELMAPUSVARBPROC) (GLenum map, GLsizei bufSize, GLushort *values); +typedef void (APIENTRYP PFNGLGETNPOLYGONSTIPPLEARBPROC) (GLsizei bufSize, GLubyte *pattern); +typedef void (APIENTRYP PFNGLGETNCOLORTABLEARBPROC) (GLenum target, GLenum format, GLenum type, GLsizei bufSize, GLvoid *table); +typedef void (APIENTRYP PFNGLGETNCONVOLUTIONFILTERARBPROC) (GLenum target, GLenum format, GLenum type, GLsizei bufSize, GLvoid *image); +typedef void (APIENTRYP PFNGLGETNSEPARABLEFILTERARBPROC) (GLenum target, GLenum format, GLenum type, GLsizei rowBufSize, GLvoid *row, GLsizei columnBufSize, GLvoid *column, GLvoid *span); +typedef void (APIENTRYP PFNGLGETNHISTOGRAMARBPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, GLvoid *values); +typedef void (APIENTRYP PFNGLGETNMINMAXARBPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, GLvoid *values); +typedef void (APIENTRYP PFNGLGETNTEXIMAGEARBPROC) (GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, GLvoid *img); +typedef void (APIENTRYP PFNGLREADNPIXELSARBPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, GLvoid *data); +typedef void (APIENTRYP PFNGLGETNCOMPRESSEDTEXIMAGEARBPROC) (GLenum target, GLint lod, GLsizei bufSize, GLvoid *img); +typedef void (APIENTRYP PFNGLGETNUNIFORMFVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLfloat *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMIVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLint *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMUIVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLuint *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMDVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLdouble *params); +#endif + +#ifndef GL_ARB_shader_stencil_export +#define GL_ARB_shader_stencil_export 1 +#endif + +#ifndef GL_EXT_abgr +#define GL_EXT_abgr 1 +#endif + +#ifndef GL_EXT_blend_color +#define GL_EXT_blend_color 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendColorEXT (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDCOLOREXTPROC) (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +#endif + +#ifndef GL_EXT_polygon_offset +#define GL_EXT_polygon_offset 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPolygonOffsetEXT (GLfloat factor, GLfloat bias); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOLYGONOFFSETEXTPROC) (GLfloat factor, GLfloat bias); +#endif + +#ifndef GL_EXT_texture +#define GL_EXT_texture 1 +#endif + +#ifndef GL_EXT_texture3D +#define GL_EXT_texture3D 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage3DEXT (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glTexSubImage3DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXIMAGE3DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +#endif + +#ifndef GL_SGIS_texture_filter4 +#define GL_SGIS_texture_filter4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetTexFilterFuncSGIS (GLenum target, GLenum filter, GLfloat *weights); +GLAPI void APIENTRY glTexFilterFuncSGIS (GLenum target, GLenum filter, GLsizei n, const GLfloat *weights); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLfloat *weights); +typedef void (APIENTRYP PFNGLTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLsizei n, const GLfloat *weights); +#endif + +#ifndef GL_EXT_subtexture +#define GL_EXT_subtexture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexSubImage1DEXT (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glTexSubImage2DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +#endif + +#ifndef GL_EXT_copy_texture +#define GL_EXT_copy_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCopyTexImage1DEXT (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +GLAPI void APIENTRY glCopyTexImage2DEXT (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +GLAPI void APIENTRY glCopyTexSubImage1DEXT (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyTexSubImage2DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glCopyTexSubImage3DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE1DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE2DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif + +#ifndef GL_EXT_histogram +#define GL_EXT_histogram 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetHistogramEXT (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +GLAPI void APIENTRY glGetHistogramParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetHistogramParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMinmaxEXT (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +GLAPI void APIENTRY glGetMinmaxParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMinmaxParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glHistogramEXT (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +GLAPI void APIENTRY glMinmaxEXT (GLenum target, GLenum internalformat, GLboolean sink); +GLAPI void APIENTRY glResetHistogramEXT (GLenum target); +GLAPI void APIENTRY glResetMinmaxEXT (GLenum target); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETHISTOGRAMEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMINMAXEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLHISTOGRAMEXTPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLMINMAXEXTPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLRESETHISTOGRAMEXTPROC) (GLenum target); +typedef void (APIENTRYP PFNGLRESETMINMAXEXTPROC) (GLenum target); +#endif + +#ifndef GL_EXT_convolution +#define GL_EXT_convolution 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glConvolutionFilter1DEXT (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *image); +GLAPI void APIENTRY glConvolutionFilter2DEXT (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image); +GLAPI void APIENTRY glConvolutionParameterfEXT (GLenum target, GLenum pname, GLfloat params); +GLAPI void APIENTRY glConvolutionParameterfvEXT (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glConvolutionParameteriEXT (GLenum target, GLenum pname, GLint params); +GLAPI void APIENTRY glConvolutionParameterivEXT (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glCopyConvolutionFilter1DEXT (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyConvolutionFilter2DEXT (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetConvolutionFilterEXT (GLenum target, GLenum format, GLenum type, GLvoid *image); +GLAPI void APIENTRY glGetConvolutionParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetConvolutionParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetSeparableFilterEXT (GLenum target, GLenum format, GLenum type, GLvoid *row, GLvoid *column, GLvoid *span); +GLAPI void APIENTRY glSeparableFilter2DEXT (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *row, const GLvoid *column); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *image); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSEPARABLEFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *row, GLvoid *column, GLvoid *span); +typedef void (APIENTRYP PFNGLSEPARABLEFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *row, const GLvoid *column); +#endif + +#ifndef GL_SGI_color_matrix +#define GL_SGI_color_matrix 1 +#endif + +#ifndef GL_SGI_color_table +#define GL_SGI_color_table 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorTableSGI (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +GLAPI void APIENTRY glColorTableParameterfvSGI (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glColorTableParameterivSGI (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glCopyColorTableSGI (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glGetColorTableSGI (GLenum target, GLenum format, GLenum type, GLvoid *table); +GLAPI void APIENTRY glGetColorTableParameterfvSGI (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetColorTableParameterivSGI (GLenum target, GLenum pname, GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLGETCOLORTABLESGIPROC) (GLenum target, GLenum format, GLenum type, GLvoid *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, GLint *params); +#endif + +#ifndef GL_SGIX_pixel_texture +#define GL_SGIX_pixel_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTexGenSGIX (GLenum mode); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELTEXGENSGIXPROC) (GLenum mode); +#endif + +#ifndef GL_SGIS_pixel_texture +#define GL_SGIS_pixel_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTexGenParameteriSGIS (GLenum pname, GLint param); +GLAPI void APIENTRY glPixelTexGenParameterivSGIS (GLenum pname, const GLint *params); +GLAPI void APIENTRY glPixelTexGenParameterfSGIS (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPixelTexGenParameterfvSGIS (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glGetPixelTexGenParameterivSGIS (GLenum pname, GLint *params); +GLAPI void APIENTRY glGetPixelTexGenParameterfvSGIS (GLenum pname, GLfloat *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERISGISPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERFSGISPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, GLfloat *params); +#endif + +#ifndef GL_SGIS_texture4D +#define GL_SGIS_texture4D 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage4DSGIS (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glTexSubImage4DSGIS (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint woffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLenum format, GLenum type, const GLvoid *pixels); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXIMAGE4DSGISPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE4DSGISPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint woffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLenum format, GLenum type, const GLvoid *pixels); +#endif + +#ifndef GL_SGI_texture_color_table +#define GL_SGI_texture_color_table 1 +#endif + +#ifndef GL_EXT_cmyka +#define GL_EXT_cmyka 1 +#endif + +#ifndef GL_EXT_texture_object +#define GL_EXT_texture_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glAreTexturesResidentEXT (GLsizei n, const GLuint *textures, GLboolean *residences); +GLAPI void APIENTRY glBindTextureEXT (GLenum target, GLuint texture); +GLAPI void APIENTRY glDeleteTexturesEXT (GLsizei n, const GLuint *textures); +GLAPI void APIENTRY glGenTexturesEXT (GLsizei n, GLuint *textures); +GLAPI GLboolean APIENTRY glIsTextureEXT (GLuint texture); +GLAPI void APIENTRY glPrioritizeTexturesEXT (GLsizei n, const GLuint *textures, const GLclampf *priorities); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLboolean (APIENTRYP PFNGLARETEXTURESRESIDENTEXTPROC) (GLsizei n, const GLuint *textures, GLboolean *residences); +typedef void (APIENTRYP PFNGLBINDTEXTUREEXTPROC) (GLenum target, GLuint texture); +typedef void (APIENTRYP PFNGLDELETETEXTURESEXTPROC) (GLsizei n, const GLuint *textures); +typedef void (APIENTRYP PFNGLGENTEXTURESEXTPROC) (GLsizei n, GLuint *textures); +typedef GLboolean (APIENTRYP PFNGLISTEXTUREEXTPROC) (GLuint texture); +typedef void (APIENTRYP PFNGLPRIORITIZETEXTURESEXTPROC) (GLsizei n, const GLuint *textures, const GLclampf *priorities); +#endif + +#ifndef GL_SGIS_detail_texture +#define GL_SGIS_detail_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDetailTexFuncSGIS (GLenum target, GLsizei n, const GLfloat *points); +GLAPI void APIENTRY glGetDetailTexFuncSGIS (GLenum target, GLfloat *points); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDETAILTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETDETAILTEXFUNCSGISPROC) (GLenum target, GLfloat *points); +#endif + +#ifndef GL_SGIS_sharpen_texture +#define GL_SGIS_sharpen_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSharpenTexFuncSGIS (GLenum target, GLsizei n, const GLfloat *points); +GLAPI void APIENTRY glGetSharpenTexFuncSGIS (GLenum target, GLfloat *points); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSHARPENTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETSHARPENTEXFUNCSGISPROC) (GLenum target, GLfloat *points); +#endif + +#ifndef GL_EXT_packed_pixels +#define GL_EXT_packed_pixels 1 +#endif + +#ifndef GL_SGIS_texture_lod +#define GL_SGIS_texture_lod 1 +#endif + +#ifndef GL_SGIS_multisample +#define GL_SGIS_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleMaskSGIS (GLclampf value, GLboolean invert); +GLAPI void APIENTRY glSamplePatternSGIS (GLenum pattern); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSAMPLEMASKSGISPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLSAMPLEPATTERNSGISPROC) (GLenum pattern); +#endif + +#ifndef GL_EXT_rescale_normal +#define GL_EXT_rescale_normal 1 +#endif + +#ifndef GL_EXT_vertex_array +#define GL_EXT_vertex_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glArrayElementEXT (GLint i); +GLAPI void APIENTRY glColorPointerEXT (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +GLAPI void APIENTRY glDrawArraysEXT (GLenum mode, GLint first, GLsizei count); +GLAPI void APIENTRY glEdgeFlagPointerEXT (GLsizei stride, GLsizei count, const GLboolean *pointer); +GLAPI void APIENTRY glGetPointervEXT (GLenum pname, GLvoid* *params); +GLAPI void APIENTRY glIndexPointerEXT (GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +GLAPI void APIENTRY glNormalPointerEXT (GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +GLAPI void APIENTRY glTexCoordPointerEXT (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +GLAPI void APIENTRY glVertexPointerEXT (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLARRAYELEMENTEXTPROC) (GLint i); +typedef void (APIENTRYP PFNGLCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLDRAWARRAYSEXTPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLEDGEFLAGPOINTEREXTPROC) (GLsizei stride, GLsizei count, const GLboolean *pointer); +typedef void (APIENTRYP PFNGLGETPOINTERVEXTPROC) (GLenum pname, GLvoid* *params); +typedef void (APIENTRYP PFNGLINDEXPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLNORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +#endif + +#ifndef GL_EXT_misc_attribute +#define GL_EXT_misc_attribute 1 +#endif + +#ifndef GL_SGIS_generate_mipmap +#define GL_SGIS_generate_mipmap 1 +#endif + +#ifndef GL_SGIX_clipmap +#define GL_SGIX_clipmap 1 +#endif + +#ifndef GL_SGIX_shadow +#define GL_SGIX_shadow 1 +#endif + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_SGIS_texture_edge_clamp 1 +#endif + +#ifndef GL_SGIS_texture_border_clamp +#define GL_SGIS_texture_border_clamp 1 +#endif + +#ifndef GL_EXT_blend_minmax +#define GL_EXT_blend_minmax 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationEXT (GLenum mode); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDEQUATIONEXTPROC) (GLenum mode); +#endif + +#ifndef GL_EXT_blend_subtract +#define GL_EXT_blend_subtract 1 +#endif + +#ifndef GL_EXT_blend_logic_op +#define GL_EXT_blend_logic_op 1 +#endif + +#ifndef GL_SGIX_interlace +#define GL_SGIX_interlace 1 +#endif + +#ifndef GL_SGIX_pixel_tiles +#define GL_SGIX_pixel_tiles 1 +#endif + +#ifndef GL_SGIX_texture_select +#define GL_SGIX_texture_select 1 +#endif + +#ifndef GL_SGIX_sprite +#define GL_SGIX_sprite 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSpriteParameterfSGIX (GLenum pname, GLfloat param); +GLAPI void APIENTRY glSpriteParameterfvSGIX (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glSpriteParameteriSGIX (GLenum pname, GLint param); +GLAPI void APIENTRY glSpriteParameterivSGIX (GLenum pname, const GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSPRITEPARAMETERFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERFVSGIXPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERISGIXPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERIVSGIXPROC) (GLenum pname, const GLint *params); +#endif + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_SGIX_texture_multi_buffer 1 +#endif + +#ifndef GL_EXT_point_parameters +#define GL_EXT_point_parameters 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfEXT (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPointParameterfvEXT (GLenum pname, const GLfloat *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERFEXTPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVEXTPROC) (GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_SGIS_point_parameters +#define GL_SGIS_point_parameters 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfSGIS (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPointParameterfvSGIS (GLenum pname, const GLfloat *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERFSGISPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_SGIX_instruments +#define GL_SGIX_instruments 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLint APIENTRY glGetInstrumentsSGIX (void); +GLAPI void APIENTRY glInstrumentsBufferSGIX (GLsizei size, GLint *buffer); +GLAPI GLint APIENTRY glPollInstrumentsSGIX (GLint *marker_p); +GLAPI void APIENTRY glReadInstrumentsSGIX (GLint marker); +GLAPI void APIENTRY glStartInstrumentsSGIX (void); +GLAPI void APIENTRY glStopInstrumentsSGIX (GLint marker); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLint (APIENTRYP PFNGLGETINSTRUMENTSSGIXPROC) (void); +typedef void (APIENTRYP PFNGLINSTRUMENTSBUFFERSGIXPROC) (GLsizei size, GLint *buffer); +typedef GLint (APIENTRYP PFNGLPOLLINSTRUMENTSSGIXPROC) (GLint *marker_p); +typedef void (APIENTRYP PFNGLREADINSTRUMENTSSGIXPROC) (GLint marker); +typedef void (APIENTRYP PFNGLSTARTINSTRUMENTSSGIXPROC) (void); +typedef void (APIENTRYP PFNGLSTOPINSTRUMENTSSGIXPROC) (GLint marker); +#endif + +#ifndef GL_SGIX_texture_scale_bias +#define GL_SGIX_texture_scale_bias 1 +#endif + +#ifndef GL_SGIX_framezoom +#define GL_SGIX_framezoom 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFrameZoomSGIX (GLint factor); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFRAMEZOOMSGIXPROC) (GLint factor); +#endif + +#ifndef GL_SGIX_tag_sample_buffer +#define GL_SGIX_tag_sample_buffer 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTagSampleBufferSGIX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTAGSAMPLEBUFFERSGIXPROC) (void); +#endif + +#ifndef GL_SGIX_polynomial_ffd +#define GL_SGIX_polynomial_ffd 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeformationMap3dSGIX (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, GLdouble w1, GLdouble w2, GLint wstride, GLint worder, const GLdouble *points); +GLAPI void APIENTRY glDeformationMap3fSGIX (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, GLfloat w1, GLfloat w2, GLint wstride, GLint worder, const GLfloat *points); +GLAPI void APIENTRY glDeformSGIX (GLbitfield mask); +GLAPI void APIENTRY glLoadIdentityDeformationMapSGIX (GLbitfield mask); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDEFORMATIONMAP3DSGIXPROC) (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, GLdouble w1, GLdouble w2, GLint wstride, GLint worder, const GLdouble *points); +typedef void (APIENTRYP PFNGLDEFORMATIONMAP3FSGIXPROC) (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, GLfloat w1, GLfloat w2, GLint wstride, GLint worder, const GLfloat *points); +typedef void (APIENTRYP PFNGLDEFORMSGIXPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLLOADIDENTITYDEFORMATIONMAPSGIXPROC) (GLbitfield mask); +#endif + +#ifndef GL_SGIX_reference_plane +#define GL_SGIX_reference_plane 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glReferencePlaneSGIX (const GLdouble *equation); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLREFERENCEPLANESGIXPROC) (const GLdouble *equation); +#endif + +#ifndef GL_SGIX_flush_raster +#define GL_SGIX_flush_raster 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFlushRasterSGIX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFLUSHRASTERSGIXPROC) (void); +#endif + +#ifndef GL_SGIX_depth_texture +#define GL_SGIX_depth_texture 1 +#endif + +#ifndef GL_SGIS_fog_function +#define GL_SGIS_fog_function 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFogFuncSGIS (GLsizei n, const GLfloat *points); +GLAPI void APIENTRY glGetFogFuncSGIS (GLfloat *points); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFOGFUNCSGISPROC) (GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETFOGFUNCSGISPROC) (GLfloat *points); +#endif + +#ifndef GL_SGIX_fog_offset +#define GL_SGIX_fog_offset 1 +#endif + +#ifndef GL_HP_image_transform +#define GL_HP_image_transform 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glImageTransformParameteriHP (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glImageTransformParameterfHP (GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glImageTransformParameterivHP (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glImageTransformParameterfvHP (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glGetImageTransformParameterivHP (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetImageTransformParameterfvHP (GLenum target, GLenum pname, GLfloat *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERIHPPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERFHPPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, GLfloat *params); +#endif + +#ifndef GL_HP_convolution_border_modes +#define GL_HP_convolution_border_modes 1 +#endif + +#ifndef GL_SGIX_texture_add_env +#define GL_SGIX_texture_add_env 1 +#endif + +#ifndef GL_EXT_color_subtable +#define GL_EXT_color_subtable 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorSubTableEXT (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); +GLAPI void APIENTRY glCopyColorSubTableEXT (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOPYCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +#endif + +#ifndef GL_PGI_vertex_hints +#define GL_PGI_vertex_hints 1 +#endif + +#ifndef GL_PGI_misc_hints +#define GL_PGI_misc_hints 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glHintPGI (GLenum target, GLint mode); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLHINTPGIPROC) (GLenum target, GLint mode); +#endif + +#ifndef GL_EXT_paletted_texture +#define GL_EXT_paletted_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorTableEXT (GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +GLAPI void APIENTRY glGetColorTableEXT (GLenum target, GLenum format, GLenum type, GLvoid *data); +GLAPI void APIENTRY glGetColorTableParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetColorTableParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORTABLEEXTPROC) (GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEEXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *data); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +#endif + +#ifndef GL_EXT_clip_volume_hint +#define GL_EXT_clip_volume_hint 1 +#endif + +#ifndef GL_SGIX_list_priority +#define GL_SGIX_list_priority 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetListParameterfvSGIX (GLuint list, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetListParameterivSGIX (GLuint list, GLenum pname, GLint *params); +GLAPI void APIENTRY glListParameterfSGIX (GLuint list, GLenum pname, GLfloat param); +GLAPI void APIENTRY glListParameterfvSGIX (GLuint list, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glListParameteriSGIX (GLuint list, GLenum pname, GLint param); +GLAPI void APIENTRY glListParameterivSGIX (GLuint list, GLenum pname, const GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLLISTPARAMETERFSGIXPROC) (GLuint list, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLLISTPARAMETERISGIXPROC) (GLuint list, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, const GLint *params); +#endif + +#ifndef GL_SGIX_ir_instrument1 +#define GL_SGIX_ir_instrument1 1 +#endif + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_SGIX_calligraphic_fragment 1 +#endif + +#ifndef GL_SGIX_texture_lod_bias +#define GL_SGIX_texture_lod_bias 1 +#endif + +#ifndef GL_SGIX_shadow_ambient +#define GL_SGIX_shadow_ambient 1 +#endif + +#ifndef GL_EXT_index_texture +#define GL_EXT_index_texture 1 +#endif + +#ifndef GL_EXT_index_material +#define GL_EXT_index_material 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIndexMaterialEXT (GLenum face, GLenum mode); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLINDEXMATERIALEXTPROC) (GLenum face, GLenum mode); +#endif + +#ifndef GL_EXT_index_func +#define GL_EXT_index_func 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIndexFuncEXT (GLenum func, GLclampf ref); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLINDEXFUNCEXTPROC) (GLenum func, GLclampf ref); +#endif + +#ifndef GL_EXT_index_array_formats +#define GL_EXT_index_array_formats 1 +#endif + +#ifndef GL_EXT_compiled_vertex_array +#define GL_EXT_compiled_vertex_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glLockArraysEXT (GLint first, GLsizei count); +GLAPI void APIENTRY glUnlockArraysEXT (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLLOCKARRAYSEXTPROC) (GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLUNLOCKARRAYSEXTPROC) (void); +#endif + +#ifndef GL_EXT_cull_vertex +#define GL_EXT_cull_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCullParameterdvEXT (GLenum pname, GLdouble *params); +GLAPI void APIENTRY glCullParameterfvEXT (GLenum pname, GLfloat *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCULLPARAMETERDVEXTPROC) (GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLCULLPARAMETERFVEXTPROC) (GLenum pname, GLfloat *params); +#endif + +#ifndef GL_SGIX_ycrcb +#define GL_SGIX_ycrcb 1 +#endif + +#ifndef GL_SGIX_fragment_lighting +#define GL_SGIX_fragment_lighting 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFragmentColorMaterialSGIX (GLenum face, GLenum mode); +GLAPI void APIENTRY glFragmentLightfSGIX (GLenum light, GLenum pname, GLfloat param); +GLAPI void APIENTRY glFragmentLightfvSGIX (GLenum light, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glFragmentLightiSGIX (GLenum light, GLenum pname, GLint param); +GLAPI void APIENTRY glFragmentLightivSGIX (GLenum light, GLenum pname, const GLint *params); +GLAPI void APIENTRY glFragmentLightModelfSGIX (GLenum pname, GLfloat param); +GLAPI void APIENTRY glFragmentLightModelfvSGIX (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glFragmentLightModeliSGIX (GLenum pname, GLint param); +GLAPI void APIENTRY glFragmentLightModelivSGIX (GLenum pname, const GLint *params); +GLAPI void APIENTRY glFragmentMaterialfSGIX (GLenum face, GLenum pname, GLfloat param); +GLAPI void APIENTRY glFragmentMaterialfvSGIX (GLenum face, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glFragmentMaterialiSGIX (GLenum face, GLenum pname, GLint param); +GLAPI void APIENTRY glFragmentMaterialivSGIX (GLenum face, GLenum pname, const GLint *params); +GLAPI void APIENTRY glGetFragmentLightfvSGIX (GLenum light, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetFragmentLightivSGIX (GLenum light, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetFragmentMaterialfvSGIX (GLenum face, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetFragmentMaterialivSGIX (GLenum face, GLenum pname, GLint *params); +GLAPI void APIENTRY glLightEnviSGIX (GLenum pname, GLint param); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFRAGMENTCOLORMATERIALSGIXPROC) (GLenum face, GLenum mode); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTFSGIXPROC) (GLenum light, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTISGIXPROC) (GLenum light, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELFVSGIXPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELISGIXPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELIVSGIXPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALFSGIXPROC) (GLenum face, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALISGIXPROC) (GLenum face, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLLIGHTENVISGIXPROC) (GLenum pname, GLint param); +#endif + +#ifndef GL_IBM_rasterpos_clip +#define GL_IBM_rasterpos_clip 1 +#endif + +#ifndef GL_HP_texture_lighting +#define GL_HP_texture_lighting 1 +#endif + +#ifndef GL_EXT_draw_range_elements +#define GL_EXT_draw_range_elements 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawRangeElementsEXT (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSEXTPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +#endif + +#ifndef GL_WIN_phong_shading +#define GL_WIN_phong_shading 1 +#endif + +#ifndef GL_WIN_specular_fog +#define GL_WIN_specular_fog 1 +#endif + +#ifndef GL_EXT_light_texture +#define GL_EXT_light_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glApplyTextureEXT (GLenum mode); +GLAPI void APIENTRY glTextureLightEXT (GLenum pname); +GLAPI void APIENTRY glTextureMaterialEXT (GLenum face, GLenum mode); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLAPPLYTEXTUREEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLTEXTURELIGHTEXTPROC) (GLenum pname); +typedef void (APIENTRYP PFNGLTEXTUREMATERIALEXTPROC) (GLenum face, GLenum mode); +#endif + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_SGIX_blend_alpha_minmax 1 +#endif + +#ifndef GL_EXT_bgra +#define GL_EXT_bgra 1 +#endif + +#ifndef GL_SGIX_async +#define GL_SGIX_async 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glAsyncMarkerSGIX (GLuint marker); +GLAPI GLint APIENTRY glFinishAsyncSGIX (GLuint *markerp); +GLAPI GLint APIENTRY glPollAsyncSGIX (GLuint *markerp); +GLAPI GLuint APIENTRY glGenAsyncMarkersSGIX (GLsizei range); +GLAPI void APIENTRY glDeleteAsyncMarkersSGIX (GLuint marker, GLsizei range); +GLAPI GLboolean APIENTRY glIsAsyncMarkerSGIX (GLuint marker); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLASYNCMARKERSGIXPROC) (GLuint marker); +typedef GLint (APIENTRYP PFNGLFINISHASYNCSGIXPROC) (GLuint *markerp); +typedef GLint (APIENTRYP PFNGLPOLLASYNCSGIXPROC) (GLuint *markerp); +typedef GLuint (APIENTRYP PFNGLGENASYNCMARKERSSGIXPROC) (GLsizei range); +typedef void (APIENTRYP PFNGLDELETEASYNCMARKERSSGIXPROC) (GLuint marker, GLsizei range); +typedef GLboolean (APIENTRYP PFNGLISASYNCMARKERSGIXPROC) (GLuint marker); +#endif + +#ifndef GL_SGIX_async_pixel +#define GL_SGIX_async_pixel 1 +#endif + +#ifndef GL_SGIX_async_histogram +#define GL_SGIX_async_histogram 1 +#endif + +#ifndef GL_INTEL_parallel_arrays +#define GL_INTEL_parallel_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexPointervINTEL (GLint size, GLenum type, const GLvoid* *pointer); +GLAPI void APIENTRY glNormalPointervINTEL (GLenum type, const GLvoid* *pointer); +GLAPI void APIENTRY glColorPointervINTEL (GLint size, GLenum type, const GLvoid* *pointer); +GLAPI void APIENTRY glTexCoordPointervINTEL (GLint size, GLenum type, const GLvoid* *pointer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +typedef void (APIENTRYP PFNGLNORMALPOINTERVINTELPROC) (GLenum type, const GLvoid* *pointer); +typedef void (APIENTRYP PFNGLCOLORPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +#endif + +#ifndef GL_HP_occlusion_test +#define GL_HP_occlusion_test 1 +#endif + +#ifndef GL_EXT_pixel_transform +#define GL_EXT_pixel_transform 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTransformParameteriEXT (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glPixelTransformParameterfEXT (GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glPixelTransformParameterivEXT (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glPixelTransformParameterfvEXT (GLenum target, GLenum pname, const GLfloat *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_EXT_pixel_transform_color_table +#define GL_EXT_pixel_transform_color_table 1 +#endif + +#ifndef GL_EXT_shared_texture_palette +#define GL_EXT_shared_texture_palette 1 +#endif + +#ifndef GL_EXT_separate_specular_color +#define GL_EXT_separate_specular_color 1 +#endif + +#ifndef GL_EXT_secondary_color +#define GL_EXT_secondary_color 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSecondaryColor3bEXT (GLbyte red, GLbyte green, GLbyte blue); +GLAPI void APIENTRY glSecondaryColor3bvEXT (const GLbyte *v); +GLAPI void APIENTRY glSecondaryColor3dEXT (GLdouble red, GLdouble green, GLdouble blue); +GLAPI void APIENTRY glSecondaryColor3dvEXT (const GLdouble *v); +GLAPI void APIENTRY glSecondaryColor3fEXT (GLfloat red, GLfloat green, GLfloat blue); +GLAPI void APIENTRY glSecondaryColor3fvEXT (const GLfloat *v); +GLAPI void APIENTRY glSecondaryColor3iEXT (GLint red, GLint green, GLint blue); +GLAPI void APIENTRY glSecondaryColor3ivEXT (const GLint *v); +GLAPI void APIENTRY glSecondaryColor3sEXT (GLshort red, GLshort green, GLshort blue); +GLAPI void APIENTRY glSecondaryColor3svEXT (const GLshort *v); +GLAPI void APIENTRY glSecondaryColor3ubEXT (GLubyte red, GLubyte green, GLubyte blue); +GLAPI void APIENTRY glSecondaryColor3ubvEXT (const GLubyte *v); +GLAPI void APIENTRY glSecondaryColor3uiEXT (GLuint red, GLuint green, GLuint blue); +GLAPI void APIENTRY glSecondaryColor3uivEXT (const GLuint *v); +GLAPI void APIENTRY glSecondaryColor3usEXT (GLushort red, GLushort green, GLushort blue); +GLAPI void APIENTRY glSecondaryColor3usvEXT (const GLushort *v); +GLAPI void APIENTRY glSecondaryColorPointerEXT (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BEXTPROC) (GLbyte red, GLbyte green, GLbyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DEXTPROC) (GLdouble red, GLdouble green, GLdouble blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FEXTPROC) (GLfloat red, GLfloat green, GLfloat blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IEXTPROC) (GLint red, GLint green, GLint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SEXTPROC) (GLshort red, GLshort green, GLshort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBEXTPROC) (GLubyte red, GLubyte green, GLubyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBVEXTPROC) (const GLubyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIEXTPROC) (GLuint red, GLuint green, GLuint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIVEXTPROC) (const GLuint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USEXTPROC) (GLushort red, GLushort green, GLushort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USVEXTPROC) (const GLushort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_EXT_texture_perturb_normal +#define GL_EXT_texture_perturb_normal 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureNormalEXT (GLenum mode); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXTURENORMALEXTPROC) (GLenum mode); +#endif + +#ifndef GL_EXT_multi_draw_arrays +#define GL_EXT_multi_draw_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiDrawArraysEXT (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +GLAPI void APIENTRY glMultiDrawElementsEXT (GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSEXTPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSEXTPROC) (GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount); +#endif + +#ifndef GL_EXT_fog_coord +#define GL_EXT_fog_coord 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFogCoordfEXT (GLfloat coord); +GLAPI void APIENTRY glFogCoordfvEXT (const GLfloat *coord); +GLAPI void APIENTRY glFogCoorddEXT (GLdouble coord); +GLAPI void APIENTRY glFogCoorddvEXT (const GLdouble *coord); +GLAPI void APIENTRY glFogCoordPointerEXT (GLenum type, GLsizei stride, const GLvoid *pointer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFOGCOORDFEXTPROC) (GLfloat coord); +typedef void (APIENTRYP PFNGLFOGCOORDFVEXTPROC) (const GLfloat *coord); +typedef void (APIENTRYP PFNGLFOGCOORDDEXTPROC) (GLdouble coord); +typedef void (APIENTRYP PFNGLFOGCOORDDVEXTPROC) (const GLdouble *coord); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_REND_screen_coordinates +#define GL_REND_screen_coordinates 1 +#endif + +#ifndef GL_EXT_coordinate_frame +#define GL_EXT_coordinate_frame 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTangent3bEXT (GLbyte tx, GLbyte ty, GLbyte tz); +GLAPI void APIENTRY glTangent3bvEXT (const GLbyte *v); +GLAPI void APIENTRY glTangent3dEXT (GLdouble tx, GLdouble ty, GLdouble tz); +GLAPI void APIENTRY glTangent3dvEXT (const GLdouble *v); +GLAPI void APIENTRY glTangent3fEXT (GLfloat tx, GLfloat ty, GLfloat tz); +GLAPI void APIENTRY glTangent3fvEXT (const GLfloat *v); +GLAPI void APIENTRY glTangent3iEXT (GLint tx, GLint ty, GLint tz); +GLAPI void APIENTRY glTangent3ivEXT (const GLint *v); +GLAPI void APIENTRY glTangent3sEXT (GLshort tx, GLshort ty, GLshort tz); +GLAPI void APIENTRY glTangent3svEXT (const GLshort *v); +GLAPI void APIENTRY glBinormal3bEXT (GLbyte bx, GLbyte by, GLbyte bz); +GLAPI void APIENTRY glBinormal3bvEXT (const GLbyte *v); +GLAPI void APIENTRY glBinormal3dEXT (GLdouble bx, GLdouble by, GLdouble bz); +GLAPI void APIENTRY glBinormal3dvEXT (const GLdouble *v); +GLAPI void APIENTRY glBinormal3fEXT (GLfloat bx, GLfloat by, GLfloat bz); +GLAPI void APIENTRY glBinormal3fvEXT (const GLfloat *v); +GLAPI void APIENTRY glBinormal3iEXT (GLint bx, GLint by, GLint bz); +GLAPI void APIENTRY glBinormal3ivEXT (const GLint *v); +GLAPI void APIENTRY glBinormal3sEXT (GLshort bx, GLshort by, GLshort bz); +GLAPI void APIENTRY glBinormal3svEXT (const GLshort *v); +GLAPI void APIENTRY glTangentPointerEXT (GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void APIENTRY glBinormalPointerEXT (GLenum type, GLsizei stride, const GLvoid *pointer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTANGENT3BEXTPROC) (GLbyte tx, GLbyte ty, GLbyte tz); +typedef void (APIENTRYP PFNGLTANGENT3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLTANGENT3DEXTPROC) (GLdouble tx, GLdouble ty, GLdouble tz); +typedef void (APIENTRYP PFNGLTANGENT3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLTANGENT3FEXTPROC) (GLfloat tx, GLfloat ty, GLfloat tz); +typedef void (APIENTRYP PFNGLTANGENT3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLTANGENT3IEXTPROC) (GLint tx, GLint ty, GLint tz); +typedef void (APIENTRYP PFNGLTANGENT3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLTANGENT3SEXTPROC) (GLshort tx, GLshort ty, GLshort tz); +typedef void (APIENTRYP PFNGLTANGENT3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLBINORMAL3BEXTPROC) (GLbyte bx, GLbyte by, GLbyte bz); +typedef void (APIENTRYP PFNGLBINORMAL3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLBINORMAL3DEXTPROC) (GLdouble bx, GLdouble by, GLdouble bz); +typedef void (APIENTRYP PFNGLBINORMAL3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLBINORMAL3FEXTPROC) (GLfloat bx, GLfloat by, GLfloat bz); +typedef void (APIENTRYP PFNGLBINORMAL3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLBINORMAL3IEXTPROC) (GLint bx, GLint by, GLint bz); +typedef void (APIENTRYP PFNGLBINORMAL3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLBINORMAL3SEXTPROC) (GLshort bx, GLshort by, GLshort bz); +typedef void (APIENTRYP PFNGLBINORMAL3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLTANGENTPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLBINORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_EXT_texture_env_combine +#define GL_EXT_texture_env_combine 1 +#endif + +#ifndef GL_APPLE_specular_vector +#define GL_APPLE_specular_vector 1 +#endif + +#ifndef GL_APPLE_transform_hint +#define GL_APPLE_transform_hint 1 +#endif + +#ifndef GL_SGIX_fog_scale +#define GL_SGIX_fog_scale 1 +#endif + +#ifndef GL_SUNX_constant_data +#define GL_SUNX_constant_data 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFinishTextureSUNX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFINISHTEXTURESUNXPROC) (void); +#endif + +#ifndef GL_SUN_global_alpha +#define GL_SUN_global_alpha 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGlobalAlphaFactorbSUN (GLbyte factor); +GLAPI void APIENTRY glGlobalAlphaFactorsSUN (GLshort factor); +GLAPI void APIENTRY glGlobalAlphaFactoriSUN (GLint factor); +GLAPI void APIENTRY glGlobalAlphaFactorfSUN (GLfloat factor); +GLAPI void APIENTRY glGlobalAlphaFactordSUN (GLdouble factor); +GLAPI void APIENTRY glGlobalAlphaFactorubSUN (GLubyte factor); +GLAPI void APIENTRY glGlobalAlphaFactorusSUN (GLushort factor); +GLAPI void APIENTRY glGlobalAlphaFactoruiSUN (GLuint factor); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORBSUNPROC) (GLbyte factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORSSUNPROC) (GLshort factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORISUNPROC) (GLint factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORFSUNPROC) (GLfloat factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORDSUNPROC) (GLdouble factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUBSUNPROC) (GLubyte factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUSSUNPROC) (GLushort factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUISUNPROC) (GLuint factor); +#endif + +#ifndef GL_SUN_triangle_list +#define GL_SUN_triangle_list 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glReplacementCodeuiSUN (GLuint code); +GLAPI void APIENTRY glReplacementCodeusSUN (GLushort code); +GLAPI void APIENTRY glReplacementCodeubSUN (GLubyte code); +GLAPI void APIENTRY glReplacementCodeuivSUN (const GLuint *code); +GLAPI void APIENTRY glReplacementCodeusvSUN (const GLushort *code); +GLAPI void APIENTRY glReplacementCodeubvSUN (const GLubyte *code); +GLAPI void APIENTRY glReplacementCodePointerSUN (GLenum type, GLsizei stride, const GLvoid* *pointer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUISUNPROC) (GLuint code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUSSUNPROC) (GLushort code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUBSUNPROC) (GLubyte code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVSUNPROC) (const GLuint *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUSVSUNPROC) (const GLushort *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUBVSUNPROC) (const GLubyte *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEPOINTERSUNPROC) (GLenum type, GLsizei stride, const GLvoid* *pointer); +#endif + +#ifndef GL_SUN_vertex +#define GL_SUN_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColor4ubVertex2fSUN (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y); +GLAPI void APIENTRY glColor4ubVertex2fvSUN (const GLubyte *c, const GLfloat *v); +GLAPI void APIENTRY glColor4ubVertex3fSUN (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glColor4ubVertex3fvSUN (const GLubyte *c, const GLfloat *v); +GLAPI void APIENTRY glColor3fVertex3fSUN (GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glColor3fVertex3fvSUN (const GLfloat *c, const GLfloat *v); +GLAPI void APIENTRY glNormal3fVertex3fSUN (GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glNormal3fVertex3fvSUN (const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glColor4fNormal3fVertex3fSUN (GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glColor4fNormal3fVertex3fvSUN (const GLfloat *c, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fVertex3fSUN (GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fVertex3fvSUN (const GLfloat *tc, const GLfloat *v); +GLAPI void APIENTRY glTexCoord4fVertex4fSUN (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glTexCoord4fVertex4fvSUN (const GLfloat *tc, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fColor4ubVertex3fSUN (GLfloat s, GLfloat t, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fColor4ubVertex3fvSUN (const GLfloat *tc, const GLubyte *c, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fColor3fVertex3fSUN (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fColor3fVertex3fvSUN (const GLfloat *tc, const GLfloat *c, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fNormal3fVertex3fSUN (GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fNormal3fVertex3fvSUN (const GLfloat *tc, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fColor4fNormal3fVertex3fSUN (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fColor4fNormal3fVertex3fvSUN (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glTexCoord4fColor4fNormal3fVertex4fSUN (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glTexCoord4fColor4fNormal3fVertex4fvSUN (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiVertex3fSUN (GLuint rc, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiVertex3fvSUN (const GLuint *rc, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiColor4ubVertex3fSUN (GLuint rc, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiColor4ubVertex3fvSUN (const GLuint *rc, const GLubyte *c, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiColor3fVertex3fSUN (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiColor3fVertex3fvSUN (const GLuint *rc, const GLfloat *c, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiNormal3fVertex3fSUN (GLuint rc, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fSUN (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fVertex3fSUN (GLuint rc, GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fVertex3fvSUN (const GLuint *rc, const GLfloat *tc, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN (GLuint rc, GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *tc, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN (GLuint rc, GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX2FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX2FVSUNPROC) (const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX3FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX3FVSUNPROC) (const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLNORMAL3FVERTEX3FSUNPROC) (GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD4FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLTEXCOORD4FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4UBVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4UBVERTEX3FVSUNPROC) (const GLfloat *tc, const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVERTEX3FSUNPROC) (GLuint rc, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FSUNPROC) (GLuint rc, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FVSUNPROC) (const GLuint *rc, const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FSUNPROC) (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +#endif + +#ifndef GL_EXT_blend_func_separate +#define GL_EXT_blend_func_separate 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparateEXT (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEEXTPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#endif + +#ifndef GL_INGR_blend_func_separate +#define GL_INGR_blend_func_separate 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparateINGR (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEINGRPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#endif + +#ifndef GL_INGR_color_clamp +#define GL_INGR_color_clamp 1 +#endif + +#ifndef GL_INGR_interlace_read +#define GL_INGR_interlace_read 1 +#endif + +#ifndef GL_EXT_stencil_wrap +#define GL_EXT_stencil_wrap 1 +#endif + +#ifndef GL_EXT_422_pixels +#define GL_EXT_422_pixels 1 +#endif + +#ifndef GL_NV_texgen_reflection +#define GL_NV_texgen_reflection 1 +#endif + +#ifndef GL_SUN_convolution_border_modes +#define GL_SUN_convolution_border_modes 1 +#endif + +#ifndef GL_EXT_texture_env_add +#define GL_EXT_texture_env_add 1 +#endif + +#ifndef GL_EXT_texture_lod_bias +#define GL_EXT_texture_lod_bias 1 +#endif + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_EXT_texture_filter_anisotropic 1 +#endif + +#ifndef GL_EXT_vertex_weighting +#define GL_EXT_vertex_weighting 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexWeightfEXT (GLfloat weight); +GLAPI void APIENTRY glVertexWeightfvEXT (const GLfloat *weight); +GLAPI void APIENTRY glVertexWeightPointerEXT (GLsizei size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXWEIGHTFEXTPROC) (GLfloat weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTFVEXTPROC) (const GLfloat *weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTPOINTEREXTPROC) (GLsizei size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_NV_light_max_exponent +#define GL_NV_light_max_exponent 1 +#endif + +#ifndef GL_NV_vertex_array_range +#define GL_NV_vertex_array_range 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFlushVertexArrayRangeNV (void); +GLAPI void APIENTRY glVertexArrayRangeNV (GLsizei length, const GLvoid *pointer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFLUSHVERTEXARRAYRANGENVPROC) (void); +typedef void (APIENTRYP PFNGLVERTEXARRAYRANGENVPROC) (GLsizei length, const GLvoid *pointer); +#endif + +#ifndef GL_NV_register_combiners +#define GL_NV_register_combiners 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCombinerParameterfvNV (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glCombinerParameterfNV (GLenum pname, GLfloat param); +GLAPI void APIENTRY glCombinerParameterivNV (GLenum pname, const GLint *params); +GLAPI void APIENTRY glCombinerParameteriNV (GLenum pname, GLint param); +GLAPI void APIENTRY glCombinerInputNV (GLenum stage, GLenum portion, GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +GLAPI void APIENTRY glCombinerOutputNV (GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, GLboolean cdDotProduct, GLboolean muxSum); +GLAPI void APIENTRY glFinalCombinerInputNV (GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +GLAPI void APIENTRY glGetCombinerInputParameterfvNV (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetCombinerInputParameterivNV (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetCombinerOutputParameterfvNV (GLenum stage, GLenum portion, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetCombinerOutputParameterivNV (GLenum stage, GLenum portion, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetFinalCombinerInputParameterfvNV (GLenum variable, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetFinalCombinerInputParameterivNV (GLenum variable, GLenum pname, GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERFVNVPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERFNVPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERIVNVPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERINVPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLCOMBINERINPUTNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (APIENTRYP PFNGLCOMBINEROUTPUTNVPROC) (GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, GLboolean cdDotProduct, GLboolean muxSum); +typedef void (APIENTRYP PFNGLFINALCOMBINERINPUTNVPROC) (GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (APIENTRYP PFNGLGETCOMBINERINPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINERINPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETCOMBINEROUTPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINEROUTPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETFINALCOMBINERINPUTPARAMETERFVNVPROC) (GLenum variable, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFINALCOMBINERINPUTPARAMETERIVNVPROC) (GLenum variable, GLenum pname, GLint *params); +#endif + +#ifndef GL_NV_fog_distance +#define GL_NV_fog_distance 1 +#endif + +#ifndef GL_NV_texgen_emboss +#define GL_NV_texgen_emboss 1 +#endif + +#ifndef GL_NV_blend_square +#define GL_NV_blend_square 1 +#endif + +#ifndef GL_NV_texture_env_combine4 +#define GL_NV_texture_env_combine4 1 +#endif + +#ifndef GL_MESA_resize_buffers +#define GL_MESA_resize_buffers 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glResizeBuffersMESA (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLRESIZEBUFFERSMESAPROC) (void); +#endif + +#ifndef GL_MESA_window_pos +#define GL_MESA_window_pos 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWindowPos2dMESA (GLdouble x, GLdouble y); +GLAPI void APIENTRY glWindowPos2dvMESA (const GLdouble *v); +GLAPI void APIENTRY glWindowPos2fMESA (GLfloat x, GLfloat y); +GLAPI void APIENTRY glWindowPos2fvMESA (const GLfloat *v); +GLAPI void APIENTRY glWindowPos2iMESA (GLint x, GLint y); +GLAPI void APIENTRY glWindowPos2ivMESA (const GLint *v); +GLAPI void APIENTRY glWindowPos2sMESA (GLshort x, GLshort y); +GLAPI void APIENTRY glWindowPos2svMESA (const GLshort *v); +GLAPI void APIENTRY glWindowPos3dMESA (GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glWindowPos3dvMESA (const GLdouble *v); +GLAPI void APIENTRY glWindowPos3fMESA (GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glWindowPos3fvMESA (const GLfloat *v); +GLAPI void APIENTRY glWindowPos3iMESA (GLint x, GLint y, GLint z); +GLAPI void APIENTRY glWindowPos3ivMESA (const GLint *v); +GLAPI void APIENTRY glWindowPos3sMESA (GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glWindowPos3svMESA (const GLshort *v); +GLAPI void APIENTRY glWindowPos4dMESA (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glWindowPos4dvMESA (const GLdouble *v); +GLAPI void APIENTRY glWindowPos4fMESA (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glWindowPos4fvMESA (const GLfloat *v); +GLAPI void APIENTRY glWindowPos4iMESA (GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glWindowPos4ivMESA (const GLint *v); +GLAPI void APIENTRY glWindowPos4sMESA (GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glWindowPos4svMESA (const GLshort *v); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLWINDOWPOS2DMESAPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FMESAPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IMESAPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SMESAPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVMESAPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DMESAPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FMESAPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IMESAPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SMESAPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVMESAPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4DMESAPROC) (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLWINDOWPOS4DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4FMESAPROC) (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLWINDOWPOS4FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4IMESAPROC) (GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLWINDOWPOS4IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4SMESAPROC) (GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLWINDOWPOS4SVMESAPROC) (const GLshort *v); +#endif + +#ifndef GL_IBM_cull_vertex +#define GL_IBM_cull_vertex 1 +#endif + +#ifndef GL_IBM_multimode_draw_arrays +#define GL_IBM_multimode_draw_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiModeDrawArraysIBM (const GLenum *mode, const GLint *first, const GLsizei *count, GLsizei primcount, GLint modestride); +GLAPI void APIENTRY glMultiModeDrawElementsIBM (const GLenum *mode, const GLsizei *count, GLenum type, const GLvoid* const *indices, GLsizei primcount, GLint modestride); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMULTIMODEDRAWARRAYSIBMPROC) (const GLenum *mode, const GLint *first, const GLsizei *count, GLsizei primcount, GLint modestride); +typedef void (APIENTRYP PFNGLMULTIMODEDRAWELEMENTSIBMPROC) (const GLenum *mode, const GLsizei *count, GLenum type, const GLvoid* const *indices, GLsizei primcount, GLint modestride); +#endif + +#ifndef GL_IBM_vertex_array_lists +#define GL_IBM_vertex_array_lists 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorPointerListIBM (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +GLAPI void APIENTRY glSecondaryColorPointerListIBM (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +GLAPI void APIENTRY glEdgeFlagPointerListIBM (GLint stride, const GLboolean* *pointer, GLint ptrstride); +GLAPI void APIENTRY glFogCoordPointerListIBM (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +GLAPI void APIENTRY glIndexPointerListIBM (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +GLAPI void APIENTRY glNormalPointerListIBM (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +GLAPI void APIENTRY glTexCoordPointerListIBM (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +GLAPI void APIENTRY glVertexPointerListIBM (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLEDGEFLAGPOINTERLISTIBMPROC) (GLint stride, const GLboolean* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLINDEXPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLNORMALPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLVERTEXPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +#endif + +#ifndef GL_SGIX_subsample +#define GL_SGIX_subsample 1 +#endif + +#ifndef GL_SGIX_ycrcba +#define GL_SGIX_ycrcba 1 +#endif + +#ifndef GL_SGIX_ycrcb_subsample +#define GL_SGIX_ycrcb_subsample 1 +#endif + +#ifndef GL_SGIX_depth_pass_instrument +#define GL_SGIX_depth_pass_instrument 1 +#endif + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_3DFX_texture_compression_FXT1 1 +#endif + +#ifndef GL_3DFX_multisample +#define GL_3DFX_multisample 1 +#endif + +#ifndef GL_3DFX_tbuffer +#define GL_3DFX_tbuffer 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTbufferMask3DFX (GLuint mask); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTBUFFERMASK3DFXPROC) (GLuint mask); +#endif + +#ifndef GL_EXT_multisample +#define GL_EXT_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleMaskEXT (GLclampf value, GLboolean invert); +GLAPI void APIENTRY glSamplePatternEXT (GLenum pattern); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSAMPLEMASKEXTPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLSAMPLEPATTERNEXTPROC) (GLenum pattern); +#endif + +#ifndef GL_SGIX_vertex_preclip +#define GL_SGIX_vertex_preclip 1 +#endif + +#ifndef GL_SGIX_convolution_accuracy +#define GL_SGIX_convolution_accuracy 1 +#endif + +#ifndef GL_SGIX_resample +#define GL_SGIX_resample 1 +#endif + +#ifndef GL_SGIS_point_line_texgen +#define GL_SGIS_point_line_texgen 1 +#endif + +#ifndef GL_SGIS_texture_color_mask +#define GL_SGIS_texture_color_mask 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureColorMaskSGIS (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXTURECOLORMASKSGISPROC) (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +#endif + +#ifndef GL_SGIX_igloo_interface +#define GL_SGIX_igloo_interface 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIglooInterfaceSGIX (GLenum pname, const GLvoid *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLIGLOOINTERFACESGIXPROC) (GLenum pname, const GLvoid *params); +#endif + +#ifndef GL_EXT_texture_env_dot3 +#define GL_EXT_texture_env_dot3 1 +#endif + +#ifndef GL_ATI_texture_mirror_once +#define GL_ATI_texture_mirror_once 1 +#endif + +#ifndef GL_NV_fence +#define GL_NV_fence 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeleteFencesNV (GLsizei n, const GLuint *fences); +GLAPI void APIENTRY glGenFencesNV (GLsizei n, GLuint *fences); +GLAPI GLboolean APIENTRY glIsFenceNV (GLuint fence); +GLAPI GLboolean APIENTRY glTestFenceNV (GLuint fence); +GLAPI void APIENTRY glGetFenceivNV (GLuint fence, GLenum pname, GLint *params); +GLAPI void APIENTRY glFinishFenceNV (GLuint fence); +GLAPI void APIENTRY glSetFenceNV (GLuint fence, GLenum condition); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDELETEFENCESNVPROC) (GLsizei n, const GLuint *fences); +typedef void (APIENTRYP PFNGLGENFENCESNVPROC) (GLsizei n, GLuint *fences); +typedef GLboolean (APIENTRYP PFNGLISFENCENVPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTFENCENVPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLGETFENCEIVNVPROC) (GLuint fence, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLFINISHFENCENVPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLSETFENCENVPROC) (GLuint fence, GLenum condition); +#endif + +#ifndef GL_NV_evaluators +#define GL_NV_evaluators 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMapControlPointsNV (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLint uorder, GLint vorder, GLboolean packed, const GLvoid *points); +GLAPI void APIENTRY glMapParameterivNV (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glMapParameterfvNV (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glGetMapControlPointsNV (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLboolean packed, GLvoid *points); +GLAPI void APIENTRY glGetMapParameterivNV (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMapParameterfvNV (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMapAttribParameterivNV (GLenum target, GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMapAttribParameterfvNV (GLenum target, GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glEvalMapsNV (GLenum target, GLenum mode); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMAPCONTROLPOINTSNVPROC) (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLint uorder, GLint vorder, GLboolean packed, const GLvoid *points); +typedef void (APIENTRYP PFNGLMAPPARAMETERIVNVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMAPPARAMETERFVNVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETMAPCONTROLPOINTSNVPROC) (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLboolean packed, GLvoid *points); +typedef void (APIENTRYP PFNGLGETMAPPARAMETERIVNVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMAPPARAMETERFVNVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMAPATTRIBPARAMETERIVNVPROC) (GLenum target, GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMAPATTRIBPARAMETERFVNVPROC) (GLenum target, GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLEVALMAPSNVPROC) (GLenum target, GLenum mode); +#endif + +#ifndef GL_NV_packed_depth_stencil +#define GL_NV_packed_depth_stencil 1 +#endif + +#ifndef GL_NV_register_combiners2 +#define GL_NV_register_combiners2 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCombinerStageParameterfvNV (GLenum stage, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glGetCombinerStageParameterfvNV (GLenum stage, GLenum pname, GLfloat *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOMBINERSTAGEPARAMETERFVNVPROC) (GLenum stage, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINERSTAGEPARAMETERFVNVPROC) (GLenum stage, GLenum pname, GLfloat *params); +#endif + +#ifndef GL_NV_texture_compression_vtc +#define GL_NV_texture_compression_vtc 1 +#endif + +#ifndef GL_NV_texture_rectangle +#define GL_NV_texture_rectangle 1 +#endif + +#ifndef GL_NV_texture_shader +#define GL_NV_texture_shader 1 +#endif + +#ifndef GL_NV_texture_shader2 +#define GL_NV_texture_shader2 1 +#endif + +#ifndef GL_NV_vertex_array_range2 +#define GL_NV_vertex_array_range2 1 +#endif + +#ifndef GL_NV_vertex_program +#define GL_NV_vertex_program 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glAreProgramsResidentNV (GLsizei n, const GLuint *programs, GLboolean *residences); +GLAPI void APIENTRY glBindProgramNV (GLenum target, GLuint id); +GLAPI void APIENTRY glDeleteProgramsNV (GLsizei n, const GLuint *programs); +GLAPI void APIENTRY glExecuteProgramNV (GLenum target, GLuint id, const GLfloat *params); +GLAPI void APIENTRY glGenProgramsNV (GLsizei n, GLuint *programs); +GLAPI void APIENTRY glGetProgramParameterdvNV (GLenum target, GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetProgramParameterfvNV (GLenum target, GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetProgramivNV (GLuint id, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetProgramStringNV (GLuint id, GLenum pname, GLubyte *program); +GLAPI void APIENTRY glGetTrackMatrixivNV (GLenum target, GLuint address, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribdvNV (GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetVertexAttribfvNV (GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVertexAttribivNV (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribPointervNV (GLuint index, GLenum pname, GLvoid* *pointer); +GLAPI GLboolean APIENTRY glIsProgramNV (GLuint id); +GLAPI void APIENTRY glLoadProgramNV (GLenum target, GLuint id, GLsizei len, const GLubyte *program); +GLAPI void APIENTRY glProgramParameter4dNV (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramParameter4dvNV (GLenum target, GLuint index, const GLdouble *v); +GLAPI void APIENTRY glProgramParameter4fNV (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glProgramParameter4fvNV (GLenum target, GLuint index, const GLfloat *v); +GLAPI void APIENTRY glProgramParameters4dvNV (GLenum target, GLuint index, GLuint count, const GLdouble *v); +GLAPI void APIENTRY glProgramParameters4fvNV (GLenum target, GLuint index, GLuint count, const GLfloat *v); +GLAPI void APIENTRY glRequestResidentProgramsNV (GLsizei n, const GLuint *programs); +GLAPI void APIENTRY glTrackMatrixNV (GLenum target, GLuint address, GLenum matrix, GLenum transform); +GLAPI void APIENTRY glVertexAttribPointerNV (GLuint index, GLint fsize, GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void APIENTRY glVertexAttrib1dNV (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttrib1dvNV (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib1fNV (GLuint index, GLfloat x); +GLAPI void APIENTRY glVertexAttrib1fvNV (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib1sNV (GLuint index, GLshort x); +GLAPI void APIENTRY glVertexAttrib1svNV (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib2dNV (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttrib2dvNV (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib2fNV (GLuint index, GLfloat x, GLfloat y); +GLAPI void APIENTRY glVertexAttrib2fvNV (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib2sNV (GLuint index, GLshort x, GLshort y); +GLAPI void APIENTRY glVertexAttrib2svNV (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib3dNV (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttrib3dvNV (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib3fNV (GLuint index, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glVertexAttrib3fvNV (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib3sNV (GLuint index, GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glVertexAttrib3svNV (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4dNV (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttrib4dvNV (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib4fNV (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glVertexAttrib4fvNV (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib4sNV (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glVertexAttrib4svNV (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4ubNV (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +GLAPI void APIENTRY glVertexAttrib4ubvNV (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttribs1dvNV (GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribs1fvNV (GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glVertexAttribs1svNV (GLuint index, GLsizei count, const GLshort *v); +GLAPI void APIENTRY glVertexAttribs2dvNV (GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribs2fvNV (GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glVertexAttribs2svNV (GLuint index, GLsizei count, const GLshort *v); +GLAPI void APIENTRY glVertexAttribs3dvNV (GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribs3fvNV (GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glVertexAttribs3svNV (GLuint index, GLsizei count, const GLshort *v); +GLAPI void APIENTRY glVertexAttribs4dvNV (GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribs4fvNV (GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glVertexAttribs4svNV (GLuint index, GLsizei count, const GLshort *v); +GLAPI void APIENTRY glVertexAttribs4ubvNV (GLuint index, GLsizei count, const GLubyte *v); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLboolean (APIENTRYP PFNGLAREPROGRAMSRESIDENTNVPROC) (GLsizei n, const GLuint *programs, GLboolean *residences); +typedef void (APIENTRYP PFNGLBINDPROGRAMNVPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLDELETEPROGRAMSNVPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLEXECUTEPROGRAMNVPROC) (GLenum target, GLuint id, const GLfloat *params); +typedef void (APIENTRYP PFNGLGENPROGRAMSNVPROC) (GLsizei n, GLuint *programs); +typedef void (APIENTRYP PFNGLGETPROGRAMPARAMETERDVNVPROC) (GLenum target, GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMPARAMETERFVNVPROC) (GLenum target, GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMIVNVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSTRINGNVPROC) (GLuint id, GLenum pname, GLubyte *program); +typedef void (APIENTRYP PFNGLGETTRACKMATRIXIVNVPROC) (GLenum target, GLuint address, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVNVPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVNVPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVNVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVNVPROC) (GLuint index, GLenum pname, GLvoid* *pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLLOADPROGRAMNVPROC) (GLenum target, GLuint id, GLsizei len, const GLubyte *program); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4DNVPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4DVNVPROC) (GLenum target, GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4FNVPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4FVNVPROC) (GLenum target, GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERS4DVNVPROC) (GLenum target, GLuint index, GLuint count, const GLdouble *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERS4FVNVPROC) (GLenum target, GLuint index, GLuint count, const GLfloat *v); +typedef void (APIENTRYP PFNGLREQUESTRESIDENTPROGRAMSNVPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLTRACKMATRIXNVPROC) (GLenum target, GLuint address, GLenum matrix, GLenum transform); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERNVPROC) (GLuint index, GLint fsize, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DNVPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FNVPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SNVPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DNVPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FNVPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SNVPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DNVPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FNVPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SNVPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DNVPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FNVPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SNVPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBNVPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVNVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4UBVNVPROC) (GLuint index, GLsizei count, const GLubyte *v); +#endif + +#ifndef GL_SGIX_texture_coordinate_clamp +#define GL_SGIX_texture_coordinate_clamp 1 +#endif + +#ifndef GL_SGIX_scalebias_hint +#define GL_SGIX_scalebias_hint 1 +#endif + +#ifndef GL_OML_interlace +#define GL_OML_interlace 1 +#endif + +#ifndef GL_OML_subsample +#define GL_OML_subsample 1 +#endif + +#ifndef GL_OML_resample +#define GL_OML_resample 1 +#endif + +#ifndef GL_NV_copy_depth_to_color +#define GL_NV_copy_depth_to_color 1 +#endif + +#ifndef GL_ATI_envmap_bumpmap +#define GL_ATI_envmap_bumpmap 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexBumpParameterivATI (GLenum pname, const GLint *param); +GLAPI void APIENTRY glTexBumpParameterfvATI (GLenum pname, const GLfloat *param); +GLAPI void APIENTRY glGetTexBumpParameterivATI (GLenum pname, GLint *param); +GLAPI void APIENTRY glGetTexBumpParameterfvATI (GLenum pname, GLfloat *param); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXBUMPPARAMETERIVATIPROC) (GLenum pname, const GLint *param); +typedef void (APIENTRYP PFNGLTEXBUMPPARAMETERFVATIPROC) (GLenum pname, const GLfloat *param); +typedef void (APIENTRYP PFNGLGETTEXBUMPPARAMETERIVATIPROC) (GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETTEXBUMPPARAMETERFVATIPROC) (GLenum pname, GLfloat *param); +#endif + +#ifndef GL_ATI_fragment_shader +#define GL_ATI_fragment_shader 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glGenFragmentShadersATI (GLuint range); +GLAPI void APIENTRY glBindFragmentShaderATI (GLuint id); +GLAPI void APIENTRY glDeleteFragmentShaderATI (GLuint id); +GLAPI void APIENTRY glBeginFragmentShaderATI (void); +GLAPI void APIENTRY glEndFragmentShaderATI (void); +GLAPI void APIENTRY glPassTexCoordATI (GLuint dst, GLuint coord, GLenum swizzle); +GLAPI void APIENTRY glSampleMapATI (GLuint dst, GLuint interp, GLenum swizzle); +GLAPI void APIENTRY glColorFragmentOp1ATI (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +GLAPI void APIENTRY glColorFragmentOp2ATI (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +GLAPI void APIENTRY glColorFragmentOp3ATI (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +GLAPI void APIENTRY glAlphaFragmentOp1ATI (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +GLAPI void APIENTRY glAlphaFragmentOp2ATI (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +GLAPI void APIENTRY glAlphaFragmentOp3ATI (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +GLAPI void APIENTRY glSetFragmentShaderConstantATI (GLuint dst, const GLfloat *value); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLuint (APIENTRYP PFNGLGENFRAGMENTSHADERSATIPROC) (GLuint range); +typedef void (APIENTRYP PFNGLBINDFRAGMENTSHADERATIPROC) (GLuint id); +typedef void (APIENTRYP PFNGLDELETEFRAGMENTSHADERATIPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINFRAGMENTSHADERATIPROC) (void); +typedef void (APIENTRYP PFNGLENDFRAGMENTSHADERATIPROC) (void); +typedef void (APIENTRYP PFNGLPASSTEXCOORDATIPROC) (GLuint dst, GLuint coord, GLenum swizzle); +typedef void (APIENTRYP PFNGLSAMPLEMAPATIPROC) (GLuint dst, GLuint interp, GLenum swizzle); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP1ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP2ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP3ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP1ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP2ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP3ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +typedef void (APIENTRYP PFNGLSETFRAGMENTSHADERCONSTANTATIPROC) (GLuint dst, const GLfloat *value); +#endif + +#ifndef GL_ATI_pn_triangles +#define GL_ATI_pn_triangles 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPNTrianglesiATI (GLenum pname, GLint param); +GLAPI void APIENTRY glPNTrianglesfATI (GLenum pname, GLfloat param); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPNTRIANGLESIATIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPNTRIANGLESFATIPROC) (GLenum pname, GLfloat param); +#endif + +#ifndef GL_ATI_vertex_array_object +#define GL_ATI_vertex_array_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glNewObjectBufferATI (GLsizei size, const GLvoid *pointer, GLenum usage); +GLAPI GLboolean APIENTRY glIsObjectBufferATI (GLuint buffer); +GLAPI void APIENTRY glUpdateObjectBufferATI (GLuint buffer, GLuint offset, GLsizei size, const GLvoid *pointer, GLenum preserve); +GLAPI void APIENTRY glGetObjectBufferfvATI (GLuint buffer, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetObjectBufferivATI (GLuint buffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glFreeObjectBufferATI (GLuint buffer); +GLAPI void APIENTRY glArrayObjectATI (GLenum array, GLint size, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +GLAPI void APIENTRY glGetArrayObjectfvATI (GLenum array, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetArrayObjectivATI (GLenum array, GLenum pname, GLint *params); +GLAPI void APIENTRY glVariantArrayObjectATI (GLuint id, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +GLAPI void APIENTRY glGetVariantArrayObjectfvATI (GLuint id, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVariantArrayObjectivATI (GLuint id, GLenum pname, GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLuint (APIENTRYP PFNGLNEWOBJECTBUFFERATIPROC) (GLsizei size, const GLvoid *pointer, GLenum usage); +typedef GLboolean (APIENTRYP PFNGLISOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLUPDATEOBJECTBUFFERATIPROC) (GLuint buffer, GLuint offset, GLsizei size, const GLvoid *pointer, GLenum preserve); +typedef void (APIENTRYP PFNGLGETOBJECTBUFFERFVATIPROC) (GLuint buffer, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETOBJECTBUFFERIVATIPROC) (GLuint buffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLFREEOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLARRAYOBJECTATIPROC) (GLenum array, GLint size, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETARRAYOBJECTFVATIPROC) (GLenum array, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETARRAYOBJECTIVATIPROC) (GLenum array, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLVARIANTARRAYOBJECTATIPROC) (GLuint id, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETVARIANTARRAYOBJECTFVATIPROC) (GLuint id, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVARIANTARRAYOBJECTIVATIPROC) (GLuint id, GLenum pname, GLint *params); +#endif + +#ifndef GL_EXT_vertex_shader +#define GL_EXT_vertex_shader 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginVertexShaderEXT (void); +GLAPI void APIENTRY glEndVertexShaderEXT (void); +GLAPI void APIENTRY glBindVertexShaderEXT (GLuint id); +GLAPI GLuint APIENTRY glGenVertexShadersEXT (GLuint range); +GLAPI void APIENTRY glDeleteVertexShaderEXT (GLuint id); +GLAPI void APIENTRY glShaderOp1EXT (GLenum op, GLuint res, GLuint arg1); +GLAPI void APIENTRY glShaderOp2EXT (GLenum op, GLuint res, GLuint arg1, GLuint arg2); +GLAPI void APIENTRY glShaderOp3EXT (GLenum op, GLuint res, GLuint arg1, GLuint arg2, GLuint arg3); +GLAPI void APIENTRY glSwizzleEXT (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +GLAPI void APIENTRY glWriteMaskEXT (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +GLAPI void APIENTRY glInsertComponentEXT (GLuint res, GLuint src, GLuint num); +GLAPI void APIENTRY glExtractComponentEXT (GLuint res, GLuint src, GLuint num); +GLAPI GLuint APIENTRY glGenSymbolsEXT (GLenum datatype, GLenum storagetype, GLenum range, GLuint components); +GLAPI void APIENTRY glSetInvariantEXT (GLuint id, GLenum type, const GLvoid *addr); +GLAPI void APIENTRY glSetLocalConstantEXT (GLuint id, GLenum type, const GLvoid *addr); +GLAPI void APIENTRY glVariantbvEXT (GLuint id, const GLbyte *addr); +GLAPI void APIENTRY glVariantsvEXT (GLuint id, const GLshort *addr); +GLAPI void APIENTRY glVariantivEXT (GLuint id, const GLint *addr); +GLAPI void APIENTRY glVariantfvEXT (GLuint id, const GLfloat *addr); +GLAPI void APIENTRY glVariantdvEXT (GLuint id, const GLdouble *addr); +GLAPI void APIENTRY glVariantubvEXT (GLuint id, const GLubyte *addr); +GLAPI void APIENTRY glVariantusvEXT (GLuint id, const GLushort *addr); +GLAPI void APIENTRY glVariantuivEXT (GLuint id, const GLuint *addr); +GLAPI void APIENTRY glVariantPointerEXT (GLuint id, GLenum type, GLuint stride, const GLvoid *addr); +GLAPI void APIENTRY glEnableVariantClientStateEXT (GLuint id); +GLAPI void APIENTRY glDisableVariantClientStateEXT (GLuint id); +GLAPI GLuint APIENTRY glBindLightParameterEXT (GLenum light, GLenum value); +GLAPI GLuint APIENTRY glBindMaterialParameterEXT (GLenum face, GLenum value); +GLAPI GLuint APIENTRY glBindTexGenParameterEXT (GLenum unit, GLenum coord, GLenum value); +GLAPI GLuint APIENTRY glBindTextureUnitParameterEXT (GLenum unit, GLenum value); +GLAPI GLuint APIENTRY glBindParameterEXT (GLenum value); +GLAPI GLboolean APIENTRY glIsVariantEnabledEXT (GLuint id, GLenum cap); +GLAPI void APIENTRY glGetVariantBooleanvEXT (GLuint id, GLenum value, GLboolean *data); +GLAPI void APIENTRY glGetVariantIntegervEXT (GLuint id, GLenum value, GLint *data); +GLAPI void APIENTRY glGetVariantFloatvEXT (GLuint id, GLenum value, GLfloat *data); +GLAPI void APIENTRY glGetVariantPointervEXT (GLuint id, GLenum value, GLvoid* *data); +GLAPI void APIENTRY glGetInvariantBooleanvEXT (GLuint id, GLenum value, GLboolean *data); +GLAPI void APIENTRY glGetInvariantIntegervEXT (GLuint id, GLenum value, GLint *data); +GLAPI void APIENTRY glGetInvariantFloatvEXT (GLuint id, GLenum value, GLfloat *data); +GLAPI void APIENTRY glGetLocalConstantBooleanvEXT (GLuint id, GLenum value, GLboolean *data); +GLAPI void APIENTRY glGetLocalConstantIntegervEXT (GLuint id, GLenum value, GLint *data); +GLAPI void APIENTRY glGetLocalConstantFloatvEXT (GLuint id, GLenum value, GLfloat *data); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBEGINVERTEXSHADEREXTPROC) (void); +typedef void (APIENTRYP PFNGLENDVERTEXSHADEREXTPROC) (void); +typedef void (APIENTRYP PFNGLBINDVERTEXSHADEREXTPROC) (GLuint id); +typedef GLuint (APIENTRYP PFNGLGENVERTEXSHADERSEXTPROC) (GLuint range); +typedef void (APIENTRYP PFNGLDELETEVERTEXSHADEREXTPROC) (GLuint id); +typedef void (APIENTRYP PFNGLSHADEROP1EXTPROC) (GLenum op, GLuint res, GLuint arg1); +typedef void (APIENTRYP PFNGLSHADEROP2EXTPROC) (GLenum op, GLuint res, GLuint arg1, GLuint arg2); +typedef void (APIENTRYP PFNGLSHADEROP3EXTPROC) (GLenum op, GLuint res, GLuint arg1, GLuint arg2, GLuint arg3); +typedef void (APIENTRYP PFNGLSWIZZLEEXTPROC) (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +typedef void (APIENTRYP PFNGLWRITEMASKEXTPROC) (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +typedef void (APIENTRYP PFNGLINSERTCOMPONENTEXTPROC) (GLuint res, GLuint src, GLuint num); +typedef void (APIENTRYP PFNGLEXTRACTCOMPONENTEXTPROC) (GLuint res, GLuint src, GLuint num); +typedef GLuint (APIENTRYP PFNGLGENSYMBOLSEXTPROC) (GLenum datatype, GLenum storagetype, GLenum range, GLuint components); +typedef void (APIENTRYP PFNGLSETINVARIANTEXTPROC) (GLuint id, GLenum type, const GLvoid *addr); +typedef void (APIENTRYP PFNGLSETLOCALCONSTANTEXTPROC) (GLuint id, GLenum type, const GLvoid *addr); +typedef void (APIENTRYP PFNGLVARIANTBVEXTPROC) (GLuint id, const GLbyte *addr); +typedef void (APIENTRYP PFNGLVARIANTSVEXTPROC) (GLuint id, const GLshort *addr); +typedef void (APIENTRYP PFNGLVARIANTIVEXTPROC) (GLuint id, const GLint *addr); +typedef void (APIENTRYP PFNGLVARIANTFVEXTPROC) (GLuint id, const GLfloat *addr); +typedef void (APIENTRYP PFNGLVARIANTDVEXTPROC) (GLuint id, const GLdouble *addr); +typedef void (APIENTRYP PFNGLVARIANTUBVEXTPROC) (GLuint id, const GLubyte *addr); +typedef void (APIENTRYP PFNGLVARIANTUSVEXTPROC) (GLuint id, const GLushort *addr); +typedef void (APIENTRYP PFNGLVARIANTUIVEXTPROC) (GLuint id, const GLuint *addr); +typedef void (APIENTRYP PFNGLVARIANTPOINTEREXTPROC) (GLuint id, GLenum type, GLuint stride, const GLvoid *addr); +typedef void (APIENTRYP PFNGLENABLEVARIANTCLIENTSTATEEXTPROC) (GLuint id); +typedef void (APIENTRYP PFNGLDISABLEVARIANTCLIENTSTATEEXTPROC) (GLuint id); +typedef GLuint (APIENTRYP PFNGLBINDLIGHTPARAMETEREXTPROC) (GLenum light, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDMATERIALPARAMETEREXTPROC) (GLenum face, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDTEXGENPARAMETEREXTPROC) (GLenum unit, GLenum coord, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDTEXTUREUNITPARAMETEREXTPROC) (GLenum unit, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDPARAMETEREXTPROC) (GLenum value); +typedef GLboolean (APIENTRYP PFNGLISVARIANTENABLEDEXTPROC) (GLuint id, GLenum cap); +typedef void (APIENTRYP PFNGLGETVARIANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETVARIANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETVARIANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +typedef void (APIENTRYP PFNGLGETVARIANTPOINTERVEXTPROC) (GLuint id, GLenum value, GLvoid* *data); +typedef void (APIENTRYP PFNGLGETINVARIANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETINVARIANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETINVARIANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +#endif + +#ifndef GL_ATI_vertex_streams +#define GL_ATI_vertex_streams 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexStream1sATI (GLenum stream, GLshort x); +GLAPI void APIENTRY glVertexStream1svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glVertexStream1iATI (GLenum stream, GLint x); +GLAPI void APIENTRY glVertexStream1ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glVertexStream1fATI (GLenum stream, GLfloat x); +GLAPI void APIENTRY glVertexStream1fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glVertexStream1dATI (GLenum stream, GLdouble x); +GLAPI void APIENTRY glVertexStream1dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glVertexStream2sATI (GLenum stream, GLshort x, GLshort y); +GLAPI void APIENTRY glVertexStream2svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glVertexStream2iATI (GLenum stream, GLint x, GLint y); +GLAPI void APIENTRY glVertexStream2ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glVertexStream2fATI (GLenum stream, GLfloat x, GLfloat y); +GLAPI void APIENTRY glVertexStream2fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glVertexStream2dATI (GLenum stream, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexStream2dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glVertexStream3sATI (GLenum stream, GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glVertexStream3svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glVertexStream3iATI (GLenum stream, GLint x, GLint y, GLint z); +GLAPI void APIENTRY glVertexStream3ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glVertexStream3fATI (GLenum stream, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glVertexStream3fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glVertexStream3dATI (GLenum stream, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexStream3dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glVertexStream4sATI (GLenum stream, GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glVertexStream4svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glVertexStream4iATI (GLenum stream, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glVertexStream4ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glVertexStream4fATI (GLenum stream, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glVertexStream4fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glVertexStream4dATI (GLenum stream, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexStream4dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glNormalStream3bATI (GLenum stream, GLbyte nx, GLbyte ny, GLbyte nz); +GLAPI void APIENTRY glNormalStream3bvATI (GLenum stream, const GLbyte *coords); +GLAPI void APIENTRY glNormalStream3sATI (GLenum stream, GLshort nx, GLshort ny, GLshort nz); +GLAPI void APIENTRY glNormalStream3svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glNormalStream3iATI (GLenum stream, GLint nx, GLint ny, GLint nz); +GLAPI void APIENTRY glNormalStream3ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glNormalStream3fATI (GLenum stream, GLfloat nx, GLfloat ny, GLfloat nz); +GLAPI void APIENTRY glNormalStream3fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glNormalStream3dATI (GLenum stream, GLdouble nx, GLdouble ny, GLdouble nz); +GLAPI void APIENTRY glNormalStream3dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glClientActiveVertexStreamATI (GLenum stream); +GLAPI void APIENTRY glVertexBlendEnviATI (GLenum pname, GLint param); +GLAPI void APIENTRY glVertexBlendEnvfATI (GLenum pname, GLfloat param); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXSTREAM1SATIPROC) (GLenum stream, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1IATIPROC) (GLenum stream, GLint x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1FATIPROC) (GLenum stream, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1DATIPROC) (GLenum stream, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2SATIPROC) (GLenum stream, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2IATIPROC) (GLenum stream, GLint x, GLint y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2FATIPROC) (GLenum stream, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2DATIPROC) (GLenum stream, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3SATIPROC) (GLenum stream, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3IATIPROC) (GLenum stream, GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3FATIPROC) (GLenum stream, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3DATIPROC) (GLenum stream, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4SATIPROC) (GLenum stream, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4IATIPROC) (GLenum stream, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4FATIPROC) (GLenum stream, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4DATIPROC) (GLenum stream, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3BATIPROC) (GLenum stream, GLbyte nx, GLbyte ny, GLbyte nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3BVATIPROC) (GLenum stream, const GLbyte *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3SATIPROC) (GLenum stream, GLshort nx, GLshort ny, GLshort nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3IATIPROC) (GLenum stream, GLint nx, GLint ny, GLint nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3FATIPROC) (GLenum stream, GLfloat nx, GLfloat ny, GLfloat nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3DATIPROC) (GLenum stream, GLdouble nx, GLdouble ny, GLdouble nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLCLIENTACTIVEVERTEXSTREAMATIPROC) (GLenum stream); +typedef void (APIENTRYP PFNGLVERTEXBLENDENVIATIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLVERTEXBLENDENVFATIPROC) (GLenum pname, GLfloat param); +#endif + +#ifndef GL_ATI_element_array +#define GL_ATI_element_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glElementPointerATI (GLenum type, const GLvoid *pointer); +GLAPI void APIENTRY glDrawElementArrayATI (GLenum mode, GLsizei count); +GLAPI void APIENTRY glDrawRangeElementArrayATI (GLenum mode, GLuint start, GLuint end, GLsizei count); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLELEMENTPOINTERATIPROC) (GLenum type, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLDRAWELEMENTARRAYATIPROC) (GLenum mode, GLsizei count); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTARRAYATIPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count); +#endif + +#ifndef GL_SUN_mesh_array +#define GL_SUN_mesh_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawMeshArraysSUN (GLenum mode, GLint first, GLsizei count, GLsizei width); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWMESHARRAYSSUNPROC) (GLenum mode, GLint first, GLsizei count, GLsizei width); +#endif + +#ifndef GL_SUN_slice_accum +#define GL_SUN_slice_accum 1 +#endif + +#ifndef GL_NV_multisample_filter_hint +#define GL_NV_multisample_filter_hint 1 +#endif + +#ifndef GL_NV_depth_clamp +#define GL_NV_depth_clamp 1 +#endif + +#ifndef GL_NV_occlusion_query +#define GL_NV_occlusion_query 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenOcclusionQueriesNV (GLsizei n, GLuint *ids); +GLAPI void APIENTRY glDeleteOcclusionQueriesNV (GLsizei n, const GLuint *ids); +GLAPI GLboolean APIENTRY glIsOcclusionQueryNV (GLuint id); +GLAPI void APIENTRY glBeginOcclusionQueryNV (GLuint id); +GLAPI void APIENTRY glEndOcclusionQueryNV (void); +GLAPI void APIENTRY glGetOcclusionQueryivNV (GLuint id, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetOcclusionQueryuivNV (GLuint id, GLenum pname, GLuint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENOCCLUSIONQUERIESNVPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEOCCLUSIONQUERIESNVPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISOCCLUSIONQUERYNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINOCCLUSIONQUERYNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLENDOCCLUSIONQUERYNVPROC) (void); +typedef void (APIENTRYP PFNGLGETOCCLUSIONQUERYIVNVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETOCCLUSIONQUERYUIVNVPROC) (GLuint id, GLenum pname, GLuint *params); +#endif + +#ifndef GL_NV_point_sprite +#define GL_NV_point_sprite 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameteriNV (GLenum pname, GLint param); +GLAPI void APIENTRY glPointParameterivNV (GLenum pname, const GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERINVPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIVNVPROC) (GLenum pname, const GLint *params); +#endif + +#ifndef GL_NV_texture_shader3 +#define GL_NV_texture_shader3 1 +#endif + +#ifndef GL_NV_vertex_program1_1 +#define GL_NV_vertex_program1_1 1 +#endif + +#ifndef GL_EXT_shadow_funcs +#define GL_EXT_shadow_funcs 1 +#endif + +#ifndef GL_EXT_stencil_two_side +#define GL_EXT_stencil_two_side 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveStencilFaceEXT (GLenum face); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLACTIVESTENCILFACEEXTPROC) (GLenum face); +#endif + +#ifndef GL_ATI_text_fragment_shader +#define GL_ATI_text_fragment_shader 1 +#endif + +#ifndef GL_APPLE_client_storage +#define GL_APPLE_client_storage 1 +#endif + +#ifndef GL_APPLE_element_array +#define GL_APPLE_element_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glElementPointerAPPLE (GLenum type, const GLvoid *pointer); +GLAPI void APIENTRY glDrawElementArrayAPPLE (GLenum mode, GLint first, GLsizei count); +GLAPI void APIENTRY glDrawRangeElementArrayAPPLE (GLenum mode, GLuint start, GLuint end, GLint first, GLsizei count); +GLAPI void APIENTRY glMultiDrawElementArrayAPPLE (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +GLAPI void APIENTRY glMultiDrawRangeElementArrayAPPLE (GLenum mode, GLuint start, GLuint end, const GLint *first, const GLsizei *count, GLsizei primcount); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLELEMENTPOINTERAPPLEPROC) (GLenum type, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLDRAWELEMENTARRAYAPPLEPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTARRAYAPPLEPROC) (GLenum mode, GLuint start, GLuint end, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTARRAYAPPLEPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWRANGEELEMENTARRAYAPPLEPROC) (GLenum mode, GLuint start, GLuint end, const GLint *first, const GLsizei *count, GLsizei primcount); +#endif + +#ifndef GL_APPLE_fence +#define GL_APPLE_fence 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenFencesAPPLE (GLsizei n, GLuint *fences); +GLAPI void APIENTRY glDeleteFencesAPPLE (GLsizei n, const GLuint *fences); +GLAPI void APIENTRY glSetFenceAPPLE (GLuint fence); +GLAPI GLboolean APIENTRY glIsFenceAPPLE (GLuint fence); +GLAPI GLboolean APIENTRY glTestFenceAPPLE (GLuint fence); +GLAPI void APIENTRY glFinishFenceAPPLE (GLuint fence); +GLAPI GLboolean APIENTRY glTestObjectAPPLE (GLenum object, GLuint name); +GLAPI void APIENTRY glFinishObjectAPPLE (GLenum object, GLint name); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENFENCESAPPLEPROC) (GLsizei n, GLuint *fences); +typedef void (APIENTRYP PFNGLDELETEFENCESAPPLEPROC) (GLsizei n, const GLuint *fences); +typedef void (APIENTRYP PFNGLSETFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLISFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTFENCEAPPLEPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLFINISHFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTOBJECTAPPLEPROC) (GLenum object, GLuint name); +typedef void (APIENTRYP PFNGLFINISHOBJECTAPPLEPROC) (GLenum object, GLint name); +#endif + +#ifndef GL_APPLE_vertex_array_object +#define GL_APPLE_vertex_array_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindVertexArrayAPPLE (GLuint array); +GLAPI void APIENTRY glDeleteVertexArraysAPPLE (GLsizei n, const GLuint *arrays); +GLAPI void APIENTRY glGenVertexArraysAPPLE (GLsizei n, GLuint *arrays); +GLAPI GLboolean APIENTRY glIsVertexArrayAPPLE (GLuint array); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDVERTEXARRAYAPPLEPROC) (GLuint array); +typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSAPPLEPROC) (GLsizei n, const GLuint *arrays); +typedef void (APIENTRYP PFNGLGENVERTEXARRAYSAPPLEPROC) (GLsizei n, GLuint *arrays); +typedef GLboolean (APIENTRYP PFNGLISVERTEXARRAYAPPLEPROC) (GLuint array); +#endif + +#ifndef GL_APPLE_vertex_array_range +#define GL_APPLE_vertex_array_range 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexArrayRangeAPPLE (GLsizei length, GLvoid *pointer); +GLAPI void APIENTRY glFlushVertexArrayRangeAPPLE (GLsizei length, GLvoid *pointer); +GLAPI void APIENTRY glVertexArrayParameteriAPPLE (GLenum pname, GLint param); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXARRAYRANGEAPPLEPROC) (GLsizei length, GLvoid *pointer); +typedef void (APIENTRYP PFNGLFLUSHVERTEXARRAYRANGEAPPLEPROC) (GLsizei length, GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXARRAYPARAMETERIAPPLEPROC) (GLenum pname, GLint param); +#endif + +#ifndef GL_APPLE_ycbcr_422 +#define GL_APPLE_ycbcr_422 1 +#endif + +#ifndef GL_S3_s3tc +#define GL_S3_s3tc 1 +#endif + +#ifndef GL_ATI_draw_buffers +#define GL_ATI_draw_buffers 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawBuffersATI (GLsizei n, const GLenum *bufs); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWBUFFERSATIPROC) (GLsizei n, const GLenum *bufs); +#endif + +#ifndef GL_ATI_pixel_format_float +#define GL_ATI_pixel_format_float 1 +/* This is really a WGL extension, but defines some associated GL enums. + * ATI does not export "GL_ATI_pixel_format_float" in the GL_EXTENSIONS string. + */ +#endif + +#ifndef GL_ATI_texture_env_combine3 +#define GL_ATI_texture_env_combine3 1 +#endif + +#ifndef GL_ATI_texture_float +#define GL_ATI_texture_float 1 +#endif + +#ifndef GL_NV_float_buffer +#define GL_NV_float_buffer 1 +#endif + +#ifndef GL_NV_fragment_program +#define GL_NV_fragment_program 1 +/* Some NV_fragment_program entry points are shared with ARB_vertex_program. */ +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramNamedParameter4fNV (GLuint id, GLsizei len, const GLubyte *name, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glProgramNamedParameter4dNV (GLuint id, GLsizei len, const GLubyte *name, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramNamedParameter4fvNV (GLuint id, GLsizei len, const GLubyte *name, const GLfloat *v); +GLAPI void APIENTRY glProgramNamedParameter4dvNV (GLuint id, GLsizei len, const GLubyte *name, const GLdouble *v); +GLAPI void APIENTRY glGetProgramNamedParameterfvNV (GLuint id, GLsizei len, const GLubyte *name, GLfloat *params); +GLAPI void APIENTRY glGetProgramNamedParameterdvNV (GLuint id, GLsizei len, const GLubyte *name, GLdouble *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4FNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4DNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4FVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, const GLfloat *v); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4DVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, const GLdouble *v); +typedef void (APIENTRYP PFNGLGETPROGRAMNAMEDPARAMETERFVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMNAMEDPARAMETERDVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLdouble *params); +#endif + +#ifndef GL_NV_half_float +#define GL_NV_half_float 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertex2hNV (GLhalfNV x, GLhalfNV y); +GLAPI void APIENTRY glVertex2hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glVertex3hNV (GLhalfNV x, GLhalfNV y, GLhalfNV z); +GLAPI void APIENTRY glVertex3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glVertex4hNV (GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +GLAPI void APIENTRY glVertex4hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glNormal3hNV (GLhalfNV nx, GLhalfNV ny, GLhalfNV nz); +GLAPI void APIENTRY glNormal3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glColor3hNV (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +GLAPI void APIENTRY glColor3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glColor4hNV (GLhalfNV red, GLhalfNV green, GLhalfNV blue, GLhalfNV alpha); +GLAPI void APIENTRY glColor4hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glTexCoord1hNV (GLhalfNV s); +GLAPI void APIENTRY glTexCoord1hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glTexCoord2hNV (GLhalfNV s, GLhalfNV t); +GLAPI void APIENTRY glTexCoord2hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glTexCoord3hNV (GLhalfNV s, GLhalfNV t, GLhalfNV r); +GLAPI void APIENTRY glTexCoord3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glTexCoord4hNV (GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +GLAPI void APIENTRY glTexCoord4hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glMultiTexCoord1hNV (GLenum target, GLhalfNV s); +GLAPI void APIENTRY glMultiTexCoord1hvNV (GLenum target, const GLhalfNV *v); +GLAPI void APIENTRY glMultiTexCoord2hNV (GLenum target, GLhalfNV s, GLhalfNV t); +GLAPI void APIENTRY glMultiTexCoord2hvNV (GLenum target, const GLhalfNV *v); +GLAPI void APIENTRY glMultiTexCoord3hNV (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r); +GLAPI void APIENTRY glMultiTexCoord3hvNV (GLenum target, const GLhalfNV *v); +GLAPI void APIENTRY glMultiTexCoord4hNV (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +GLAPI void APIENTRY glMultiTexCoord4hvNV (GLenum target, const GLhalfNV *v); +GLAPI void APIENTRY glFogCoordhNV (GLhalfNV fog); +GLAPI void APIENTRY glFogCoordhvNV (const GLhalfNV *fog); +GLAPI void APIENTRY glSecondaryColor3hNV (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +GLAPI void APIENTRY glSecondaryColor3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glVertexWeighthNV (GLhalfNV weight); +GLAPI void APIENTRY glVertexWeighthvNV (const GLhalfNV *weight); +GLAPI void APIENTRY glVertexAttrib1hNV (GLuint index, GLhalfNV x); +GLAPI void APIENTRY glVertexAttrib1hvNV (GLuint index, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttrib2hNV (GLuint index, GLhalfNV x, GLhalfNV y); +GLAPI void APIENTRY glVertexAttrib2hvNV (GLuint index, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttrib3hNV (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z); +GLAPI void APIENTRY glVertexAttrib3hvNV (GLuint index, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttrib4hNV (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +GLAPI void APIENTRY glVertexAttrib4hvNV (GLuint index, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttribs1hvNV (GLuint index, GLsizei n, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttribs2hvNV (GLuint index, GLsizei n, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttribs3hvNV (GLuint index, GLsizei n, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttribs4hvNV (GLuint index, GLsizei n, const GLhalfNV *v); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEX2HNVPROC) (GLhalfNV x, GLhalfNV y); +typedef void (APIENTRYP PFNGLVERTEX2HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEX3HNVPROC) (GLhalfNV x, GLhalfNV y, GLhalfNV z); +typedef void (APIENTRYP PFNGLVERTEX3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEX4HNVPROC) (GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +typedef void (APIENTRYP PFNGLVERTEX4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLNORMAL3HNVPROC) (GLhalfNV nx, GLhalfNV ny, GLhalfNV nz); +typedef void (APIENTRYP PFNGLNORMAL3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLCOLOR3HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +typedef void (APIENTRYP PFNGLCOLOR3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLCOLOR4HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue, GLhalfNV alpha); +typedef void (APIENTRYP PFNGLCOLOR4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD1HNVPROC) (GLhalfNV s); +typedef void (APIENTRYP PFNGLTEXCOORD1HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD2HNVPROC) (GLhalfNV s, GLhalfNV t); +typedef void (APIENTRYP PFNGLTEXCOORD2HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD3HNVPROC) (GLhalfNV s, GLhalfNV t, GLhalfNV r); +typedef void (APIENTRYP PFNGLTEXCOORD3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD4HNVPROC) (GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +typedef void (APIENTRYP PFNGLTEXCOORD4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1HNVPROC) (GLenum target, GLhalfNV s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLFOGCOORDHNVPROC) (GLhalfNV fog); +typedef void (APIENTRYP PFNGLFOGCOORDHVNVPROC) (const GLhalfNV *fog); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTHNVPROC) (GLhalfNV weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTHVNVPROC) (const GLhalfNV *weight); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1HNVPROC) (GLuint index, GLhalfNV x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +#endif + +#ifndef GL_NV_pixel_data_range +#define GL_NV_pixel_data_range 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelDataRangeNV (GLenum target, GLsizei length, GLvoid *pointer); +GLAPI void APIENTRY glFlushPixelDataRangeNV (GLenum target); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELDATARANGENVPROC) (GLenum target, GLsizei length, GLvoid *pointer); +typedef void (APIENTRYP PFNGLFLUSHPIXELDATARANGENVPROC) (GLenum target); +#endif + +#ifndef GL_NV_primitive_restart +#define GL_NV_primitive_restart 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPrimitiveRestartNV (void); +GLAPI void APIENTRY glPrimitiveRestartIndexNV (GLuint index); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTNVPROC) (void); +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTINDEXNVPROC) (GLuint index); +#endif + +#ifndef GL_NV_texture_expand_normal +#define GL_NV_texture_expand_normal 1 +#endif + +#ifndef GL_NV_vertex_program2 +#define GL_NV_vertex_program2 1 +#endif + +#ifndef GL_ATI_map_object_buffer +#define GL_ATI_map_object_buffer 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLvoid* APIENTRY glMapObjectBufferATI (GLuint buffer); +GLAPI void APIENTRY glUnmapObjectBufferATI (GLuint buffer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLvoid* (APIENTRYP PFNGLMAPOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLUNMAPOBJECTBUFFERATIPROC) (GLuint buffer); +#endif + +#ifndef GL_ATI_separate_stencil +#define GL_ATI_separate_stencil 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStencilOpSeparateATI (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +GLAPI void APIENTRY glStencilFuncSeparateATI (GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSTENCILOPSEPARATEATIPROC) (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +typedef void (APIENTRYP PFNGLSTENCILFUNCSEPARATEATIPROC) (GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask); +#endif + +#ifndef GL_ATI_vertex_attrib_array_object +#define GL_ATI_vertex_attrib_array_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribArrayObjectATI (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLuint buffer, GLuint offset); +GLAPI void APIENTRY glGetVertexAttribArrayObjectfvATI (GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVertexAttribArrayObjectivATI (GLuint index, GLenum pname, GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIBARRAYOBJECTATIPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBARRAYOBJECTFVATIPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBARRAYOBJECTIVATIPROC) (GLuint index, GLenum pname, GLint *params); +#endif + +#ifndef GL_OES_read_format +#define GL_OES_read_format 1 +#endif + +#ifndef GL_EXT_depth_bounds_test +#define GL_EXT_depth_bounds_test 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDepthBoundsEXT (GLclampd zmin, GLclampd zmax); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDEPTHBOUNDSEXTPROC) (GLclampd zmin, GLclampd zmax); +#endif + +#ifndef GL_EXT_texture_mirror_clamp +#define GL_EXT_texture_mirror_clamp 1 +#endif + +#ifndef GL_EXT_blend_equation_separate +#define GL_EXT_blend_equation_separate 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationSeparateEXT (GLenum modeRGB, GLenum modeAlpha); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEEXTPROC) (GLenum modeRGB, GLenum modeAlpha); +#endif + +#ifndef GL_MESA_pack_invert +#define GL_MESA_pack_invert 1 +#endif + +#ifndef GL_MESA_ycbcr_texture +#define GL_MESA_ycbcr_texture 1 +#endif + +#ifndef GL_EXT_pixel_buffer_object +#define GL_EXT_pixel_buffer_object 1 +#endif + +#ifndef GL_NV_fragment_program_option +#define GL_NV_fragment_program_option 1 +#endif + +#ifndef GL_NV_fragment_program2 +#define GL_NV_fragment_program2 1 +#endif + +#ifndef GL_NV_vertex_program2_option +#define GL_NV_vertex_program2_option 1 +#endif + +#ifndef GL_NV_vertex_program3 +#define GL_NV_vertex_program3 1 +#endif + +#ifndef GL_EXT_framebuffer_object +#define GL_EXT_framebuffer_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glIsRenderbufferEXT (GLuint renderbuffer); +GLAPI void APIENTRY glBindRenderbufferEXT (GLenum target, GLuint renderbuffer); +GLAPI void APIENTRY glDeleteRenderbuffersEXT (GLsizei n, const GLuint *renderbuffers); +GLAPI void APIENTRY glGenRenderbuffersEXT (GLsizei n, GLuint *renderbuffers); +GLAPI void APIENTRY glRenderbufferStorageEXT (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetRenderbufferParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI GLboolean APIENTRY glIsFramebufferEXT (GLuint framebuffer); +GLAPI void APIENTRY glBindFramebufferEXT (GLenum target, GLuint framebuffer); +GLAPI void APIENTRY glDeleteFramebuffersEXT (GLsizei n, const GLuint *framebuffers); +GLAPI void APIENTRY glGenFramebuffersEXT (GLsizei n, GLuint *framebuffers); +GLAPI GLenum APIENTRY glCheckFramebufferStatusEXT (GLenum target); +GLAPI void APIENTRY glFramebufferTexture1DEXT (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTexture2DEXT (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTexture3DEXT (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +GLAPI void APIENTRY glFramebufferRenderbufferEXT (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +GLAPI void APIENTRY glGetFramebufferAttachmentParameterivEXT (GLenum target, GLenum attachment, GLenum pname, GLint *params); +GLAPI void APIENTRY glGenerateMipmapEXT (GLenum target); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLboolean (APIENTRYP PFNGLISRENDERBUFFEREXTPROC) (GLuint renderbuffer); +typedef void (APIENTRYP PFNGLBINDRENDERBUFFEREXTPROC) (GLenum target, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLDELETERENDERBUFFERSEXTPROC) (GLsizei n, const GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLGENRENDERBUFFERSEXTPROC) (GLsizei n, GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETRENDERBUFFERPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef GLboolean (APIENTRYP PFNGLISFRAMEBUFFEREXTPROC) (GLuint framebuffer); +typedef void (APIENTRYP PFNGLBINDFRAMEBUFFEREXTPROC) (GLenum target, GLuint framebuffer); +typedef void (APIENTRYP PFNGLDELETEFRAMEBUFFERSEXTPROC) (GLsizei n, const GLuint *framebuffers); +typedef void (APIENTRYP PFNGLGENFRAMEBUFFERSEXTPROC) (GLsizei n, GLuint *framebuffers); +typedef GLenum (APIENTRYP PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC) (GLenum target); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE1DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE3DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +typedef void (APIENTRYP PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC) (GLenum target, GLenum attachment, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGENERATEMIPMAPEXTPROC) (GLenum target); +#endif + +#ifndef GL_GREMEDY_string_marker +#define GL_GREMEDY_string_marker 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStringMarkerGREMEDY (GLsizei len, const GLvoid *string); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSTRINGMARKERGREMEDYPROC) (GLsizei len, const GLvoid *string); +#endif + +#ifndef GL_EXT_packed_depth_stencil +#define GL_EXT_packed_depth_stencil 1 +#endif + +#ifndef GL_EXT_stencil_clear_tag +#define GL_EXT_stencil_clear_tag 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStencilClearTagEXT (GLsizei stencilTagBits, GLuint stencilClearTag); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSTENCILCLEARTAGEXTPROC) (GLsizei stencilTagBits, GLuint stencilClearTag); +#endif + +#ifndef GL_EXT_texture_sRGB +#define GL_EXT_texture_sRGB 1 +#endif + +#ifndef GL_EXT_framebuffer_blit +#define GL_EXT_framebuffer_blit 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlitFramebufferEXT (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLITFRAMEBUFFEREXTPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +#endif + +#ifndef GL_EXT_framebuffer_multisample +#define GL_EXT_framebuffer_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glRenderbufferStorageMultisampleEXT (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +#endif + +#ifndef GL_MESAX_texture_stack +#define GL_MESAX_texture_stack 1 +#endif + +#ifndef GL_EXT_timer_query +#define GL_EXT_timer_query 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetQueryObjecti64vEXT (GLuint id, GLenum pname, GLint64EXT *params); +GLAPI void APIENTRY glGetQueryObjectui64vEXT (GLuint id, GLenum pname, GLuint64EXT *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETQUERYOBJECTI64VEXTPROC) (GLuint id, GLenum pname, GLint64EXT *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUI64VEXTPROC) (GLuint id, GLenum pname, GLuint64EXT *params); +#endif + +#ifndef GL_EXT_gpu_program_parameters +#define GL_EXT_gpu_program_parameters 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramEnvParameters4fvEXT (GLenum target, GLuint index, GLsizei count, const GLfloat *params); +GLAPI void APIENTRY glProgramLocalParameters4fvEXT (GLenum target, GLuint index, GLsizei count, const GLfloat *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERS4FVEXTPROC) (GLenum target, GLuint index, GLsizei count, const GLfloat *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERS4FVEXTPROC) (GLenum target, GLuint index, GLsizei count, const GLfloat *params); +#endif + +#ifndef GL_APPLE_flush_buffer_range +#define GL_APPLE_flush_buffer_range 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBufferParameteriAPPLE (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glFlushMappedBufferRangeAPPLE (GLenum target, GLintptr offset, GLsizeiptr size); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBUFFERPARAMETERIAPPLEPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFLUSHMAPPEDBUFFERRANGEAPPLEPROC) (GLenum target, GLintptr offset, GLsizeiptr size); +#endif + +#ifndef GL_NV_gpu_program4 +#define GL_NV_gpu_program4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramLocalParameterI4iNV (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glProgramLocalParameterI4ivNV (GLenum target, GLuint index, const GLint *params); +GLAPI void APIENTRY glProgramLocalParametersI4ivNV (GLenum target, GLuint index, GLsizei count, const GLint *params); +GLAPI void APIENTRY glProgramLocalParameterI4uiNV (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glProgramLocalParameterI4uivNV (GLenum target, GLuint index, const GLuint *params); +GLAPI void APIENTRY glProgramLocalParametersI4uivNV (GLenum target, GLuint index, GLsizei count, const GLuint *params); +GLAPI void APIENTRY glProgramEnvParameterI4iNV (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glProgramEnvParameterI4ivNV (GLenum target, GLuint index, const GLint *params); +GLAPI void APIENTRY glProgramEnvParametersI4ivNV (GLenum target, GLuint index, GLsizei count, const GLint *params); +GLAPI void APIENTRY glProgramEnvParameterI4uiNV (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glProgramEnvParameterI4uivNV (GLenum target, GLuint index, const GLuint *params); +GLAPI void APIENTRY glProgramEnvParametersI4uivNV (GLenum target, GLuint index, GLsizei count, const GLuint *params); +GLAPI void APIENTRY glGetProgramLocalParameterIivNV (GLenum target, GLuint index, GLint *params); +GLAPI void APIENTRY glGetProgramLocalParameterIuivNV (GLenum target, GLuint index, GLuint *params); +GLAPI void APIENTRY glGetProgramEnvParameterIivNV (GLenum target, GLuint index, GLint *params); +GLAPI void APIENTRY glGetProgramEnvParameterIuivNV (GLenum target, GLuint index, GLuint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4INVPROC) (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4IVNVPROC) (GLenum target, GLuint index, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERSI4IVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4UINVPROC) (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4UIVNVPROC) (GLenum target, GLuint index, const GLuint *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERSI4UIVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLuint *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4INVPROC) (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4IVNVPROC) (GLenum target, GLuint index, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERSI4IVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4UINVPROC) (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4UIVNVPROC) (GLenum target, GLuint index, const GLuint *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERSI4UIVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLuint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERIIVNVPROC) (GLenum target, GLuint index, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERIUIVNVPROC) (GLenum target, GLuint index, GLuint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERIIVNVPROC) (GLenum target, GLuint index, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERIUIVNVPROC) (GLenum target, GLuint index, GLuint *params); +#endif + +#ifndef GL_NV_geometry_program4 +#define GL_NV_geometry_program4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramVertexLimitNV (GLenum target, GLint limit); +GLAPI void APIENTRY glFramebufferTextureEXT (GLenum target, GLenum attachment, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTextureLayerEXT (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +GLAPI void APIENTRY glFramebufferTextureFaceEXT (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROGRAMVERTEXLIMITNVPROC) (GLenum target, GLint limit); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREEXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURELAYEREXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREFACEEXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); +#endif + +#ifndef GL_EXT_geometry_shader4 +#define GL_EXT_geometry_shader4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramParameteriEXT (GLuint program, GLenum pname, GLint value); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERIEXTPROC) (GLuint program, GLenum pname, GLint value); +#endif + +#ifndef GL_NV_vertex_program4 +#define GL_NV_vertex_program4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribI1iEXT (GLuint index, GLint x); +GLAPI void APIENTRY glVertexAttribI2iEXT (GLuint index, GLint x, GLint y); +GLAPI void APIENTRY glVertexAttribI3iEXT (GLuint index, GLint x, GLint y, GLint z); +GLAPI void APIENTRY glVertexAttribI4iEXT (GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glVertexAttribI1uiEXT (GLuint index, GLuint x); +GLAPI void APIENTRY glVertexAttribI2uiEXT (GLuint index, GLuint x, GLuint y); +GLAPI void APIENTRY glVertexAttribI3uiEXT (GLuint index, GLuint x, GLuint y, GLuint z); +GLAPI void APIENTRY glVertexAttribI4uiEXT (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glVertexAttribI1ivEXT (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI2ivEXT (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI3ivEXT (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI4ivEXT (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI1uivEXT (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI2uivEXT (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI3uivEXT (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI4uivEXT (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI4bvEXT (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttribI4svEXT (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttribI4ubvEXT (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttribI4usvEXT (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttribIPointerEXT (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void APIENTRY glGetVertexAttribIivEXT (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribIuivEXT (GLuint index, GLenum pname, GLuint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IEXTPROC) (GLuint index, GLint x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IEXTPROC) (GLuint index, GLint x, GLint y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IEXTPROC) (GLuint index, GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IEXTPROC) (GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIEXTPROC) (GLuint index, GLuint x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIEXTPROC) (GLuint index, GLuint x, GLuint y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIEXTPROC) (GLuint index, GLuint x, GLuint y, GLuint z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIEXTPROC) (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IVEXTPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IVEXTPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IVEXTPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IVEXTPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4BVEXTPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4SVEXTPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UBVEXTPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4USVEXTPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBIPOINTEREXTPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIIVEXTPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIUIVEXTPROC) (GLuint index, GLenum pname, GLuint *params); +#endif + +#ifndef GL_EXT_gpu_shader4 +#define GL_EXT_gpu_shader4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetUniformuivEXT (GLuint program, GLint location, GLuint *params); +GLAPI void APIENTRY glBindFragDataLocationEXT (GLuint program, GLuint color, const GLchar *name); +GLAPI GLint APIENTRY glGetFragDataLocationEXT (GLuint program, const GLchar *name); +GLAPI void APIENTRY glUniform1uiEXT (GLint location, GLuint v0); +GLAPI void APIENTRY glUniform2uiEXT (GLint location, GLuint v0, GLuint v1); +GLAPI void APIENTRY glUniform3uiEXT (GLint location, GLuint v0, GLuint v1, GLuint v2); +GLAPI void APIENTRY glUniform4uiEXT (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +GLAPI void APIENTRY glUniform1uivEXT (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform2uivEXT (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform3uivEXT (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform4uivEXT (GLint location, GLsizei count, const GLuint *value); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETUNIFORMUIVEXTPROC) (GLuint program, GLint location, GLuint *params); +typedef void (APIENTRYP PFNGLBINDFRAGDATALOCATIONEXTPROC) (GLuint program, GLuint color, const GLchar *name); +typedef GLint (APIENTRYP PFNGLGETFRAGDATALOCATIONEXTPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLUNIFORM1UIEXTPROC) (GLint location, GLuint v0); +typedef void (APIENTRYP PFNGLUNIFORM2UIEXTPROC) (GLint location, GLuint v0, GLuint v1); +typedef void (APIENTRYP PFNGLUNIFORM3UIEXTPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2); +typedef void (APIENTRYP PFNGLUNIFORM4UIEXTPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +typedef void (APIENTRYP PFNGLUNIFORM1UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM2UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM3UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM4UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +#endif + +#ifndef GL_EXT_draw_instanced +#define GL_EXT_draw_instanced 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawArraysInstancedEXT (GLenum mode, GLint start, GLsizei count, GLsizei primcount); +GLAPI void APIENTRY glDrawElementsInstancedEXT (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDEXTPROC) (GLenum mode, GLint start, GLsizei count, GLsizei primcount); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDEXTPROC) (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount); +#endif + +#ifndef GL_EXT_packed_float +#define GL_EXT_packed_float 1 +#endif + +#ifndef GL_EXT_texture_array +#define GL_EXT_texture_array 1 +#endif + +#ifndef GL_EXT_texture_buffer_object +#define GL_EXT_texture_buffer_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexBufferEXT (GLenum target, GLenum internalformat, GLuint buffer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXBUFFEREXTPROC) (GLenum target, GLenum internalformat, GLuint buffer); +#endif + +#ifndef GL_EXT_texture_compression_latc +#define GL_EXT_texture_compression_latc 1 +#endif + +#ifndef GL_EXT_texture_compression_rgtc +#define GL_EXT_texture_compression_rgtc 1 +#endif + +#ifndef GL_EXT_texture_shared_exponent +#define GL_EXT_texture_shared_exponent 1 +#endif + +#ifndef GL_NV_depth_buffer_float +#define GL_NV_depth_buffer_float 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDepthRangedNV (GLdouble zNear, GLdouble zFar); +GLAPI void APIENTRY glClearDepthdNV (GLdouble depth); +GLAPI void APIENTRY glDepthBoundsdNV (GLdouble zmin, GLdouble zmax); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDEPTHRANGEDNVPROC) (GLdouble zNear, GLdouble zFar); +typedef void (APIENTRYP PFNGLCLEARDEPTHDNVPROC) (GLdouble depth); +typedef void (APIENTRYP PFNGLDEPTHBOUNDSDNVPROC) (GLdouble zmin, GLdouble zmax); +#endif + +#ifndef GL_NV_fragment_program4 +#define GL_NV_fragment_program4 1 +#endif + +#ifndef GL_NV_framebuffer_multisample_coverage +#define GL_NV_framebuffer_multisample_coverage 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glRenderbufferStorageMultisampleCoverageNV (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLECOVERAGENVPROC) (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); +#endif + +#ifndef GL_EXT_framebuffer_sRGB +#define GL_EXT_framebuffer_sRGB 1 +#endif + +#ifndef GL_NV_geometry_shader4 +#define GL_NV_geometry_shader4 1 +#endif + +#ifndef GL_NV_parameter_buffer_object +#define GL_NV_parameter_buffer_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramBufferParametersfvNV (GLenum target, GLuint buffer, GLuint index, GLsizei count, const GLfloat *params); +GLAPI void APIENTRY glProgramBufferParametersIivNV (GLenum target, GLuint buffer, GLuint index, GLsizei count, const GLint *params); +GLAPI void APIENTRY glProgramBufferParametersIuivNV (GLenum target, GLuint buffer, GLuint index, GLsizei count, const GLuint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROGRAMBUFFERPARAMETERSFVNVPROC) (GLenum target, GLuint buffer, GLuint index, GLsizei count, const GLfloat *params); +typedef void (APIENTRYP PFNGLPROGRAMBUFFERPARAMETERSIIVNVPROC) (GLenum target, GLuint buffer, GLuint index, GLsizei count, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMBUFFERPARAMETERSIUIVNVPROC) (GLenum target, GLuint buffer, GLuint index, GLsizei count, const GLuint *params); +#endif + +#ifndef GL_EXT_draw_buffers2 +#define GL_EXT_draw_buffers2 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorMaskIndexedEXT (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +GLAPI void APIENTRY glGetBooleanIndexedvEXT (GLenum target, GLuint index, GLboolean *data); +GLAPI void APIENTRY glGetIntegerIndexedvEXT (GLenum target, GLuint index, GLint *data); +GLAPI void APIENTRY glEnableIndexedEXT (GLenum target, GLuint index); +GLAPI void APIENTRY glDisableIndexedEXT (GLenum target, GLuint index); +GLAPI GLboolean APIENTRY glIsEnabledIndexedEXT (GLenum target, GLuint index); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORMASKINDEXEDEXTPROC) (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +typedef void (APIENTRYP PFNGLGETBOOLEANINDEXEDVEXTPROC) (GLenum target, GLuint index, GLboolean *data); +typedef void (APIENTRYP PFNGLGETINTEGERINDEXEDVEXTPROC) (GLenum target, GLuint index, GLint *data); +typedef void (APIENTRYP PFNGLENABLEINDEXEDEXTPROC) (GLenum target, GLuint index); +typedef void (APIENTRYP PFNGLDISABLEINDEXEDEXTPROC) (GLenum target, GLuint index); +typedef GLboolean (APIENTRYP PFNGLISENABLEDINDEXEDEXTPROC) (GLenum target, GLuint index); +#endif + +#ifndef GL_NV_transform_feedback +#define GL_NV_transform_feedback 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginTransformFeedbackNV (GLenum primitiveMode); +GLAPI void APIENTRY glEndTransformFeedbackNV (void); +GLAPI void APIENTRY glTransformFeedbackAttribsNV (GLuint count, const GLint *attribs, GLenum bufferMode); +GLAPI void APIENTRY glBindBufferRangeNV (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glBindBufferOffsetNV (GLenum target, GLuint index, GLuint buffer, GLintptr offset); +GLAPI void APIENTRY glBindBufferBaseNV (GLenum target, GLuint index, GLuint buffer); +GLAPI void APIENTRY glTransformFeedbackVaryingsNV (GLuint program, GLsizei count, const GLint *locations, GLenum bufferMode); +GLAPI void APIENTRY glActiveVaryingNV (GLuint program, const GLchar *name); +GLAPI GLint APIENTRY glGetVaryingLocationNV (GLuint program, const GLchar *name); +GLAPI void APIENTRY glGetActiveVaryingNV (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +GLAPI void APIENTRY glGetTransformFeedbackVaryingNV (GLuint program, GLuint index, GLint *location); +GLAPI void APIENTRY glTransformFeedbackStreamAttribsNV (GLsizei count, const GLint *attribs, GLsizei nbuffers, const GLint *bufstreams, GLenum bufferMode); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBEGINTRANSFORMFEEDBACKNVPROC) (GLenum primitiveMode); +typedef void (APIENTRYP PFNGLENDTRANSFORMFEEDBACKNVPROC) (void); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKATTRIBSNVPROC) (GLuint count, const GLint *attribs, GLenum bufferMode); +typedef void (APIENTRYP PFNGLBINDBUFFERRANGENVPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLBINDBUFFEROFFSETNVPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset); +typedef void (APIENTRYP PFNGLBINDBUFFERBASENVPROC) (GLenum target, GLuint index, GLuint buffer); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKVARYINGSNVPROC) (GLuint program, GLsizei count, const GLint *locations, GLenum bufferMode); +typedef void (APIENTRYP PFNGLACTIVEVARYINGNVPROC) (GLuint program, const GLchar *name); +typedef GLint (APIENTRYP PFNGLGETVARYINGLOCATIONNVPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETACTIVEVARYINGNVPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKVARYINGNVPROC) (GLuint program, GLuint index, GLint *location); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKSTREAMATTRIBSNVPROC) (GLsizei count, const GLint *attribs, GLsizei nbuffers, const GLint *bufstreams, GLenum bufferMode); +#endif + +#ifndef GL_EXT_bindable_uniform +#define GL_EXT_bindable_uniform 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUniformBufferEXT (GLuint program, GLint location, GLuint buffer); +GLAPI GLint APIENTRY glGetUniformBufferSizeEXT (GLuint program, GLint location); +GLAPI GLintptr APIENTRY glGetUniformOffsetEXT (GLuint program, GLint location); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLUNIFORMBUFFEREXTPROC) (GLuint program, GLint location, GLuint buffer); +typedef GLint (APIENTRYP PFNGLGETUNIFORMBUFFERSIZEEXTPROC) (GLuint program, GLint location); +typedef GLintptr (APIENTRYP PFNGLGETUNIFORMOFFSETEXTPROC) (GLuint program, GLint location); +#endif + +#ifndef GL_EXT_texture_integer +#define GL_EXT_texture_integer 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexParameterIivEXT (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTexParameterIuivEXT (GLenum target, GLenum pname, const GLuint *params); +GLAPI void APIENTRY glGetTexParameterIivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTexParameterIuivEXT (GLenum target, GLenum pname, GLuint *params); +GLAPI void APIENTRY glClearColorIiEXT (GLint red, GLint green, GLint blue, GLint alpha); +GLAPI void APIENTRY glClearColorIuiEXT (GLuint red, GLuint green, GLuint blue, GLuint alpha); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXPARAMETERIIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXPARAMETERIUIVEXTPROC) (GLenum target, GLenum pname, const GLuint *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIUIVEXTPROC) (GLenum target, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLCLEARCOLORIIEXTPROC) (GLint red, GLint green, GLint blue, GLint alpha); +typedef void (APIENTRYP PFNGLCLEARCOLORIUIEXTPROC) (GLuint red, GLuint green, GLuint blue, GLuint alpha); +#endif + +#ifndef GL_GREMEDY_frame_terminator +#define GL_GREMEDY_frame_terminator 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFrameTerminatorGREMEDY (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFRAMETERMINATORGREMEDYPROC) (void); +#endif + +#ifndef GL_NV_conditional_render +#define GL_NV_conditional_render 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginConditionalRenderNV (GLuint id, GLenum mode); +GLAPI void APIENTRY glEndConditionalRenderNV (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBEGINCONDITIONALRENDERNVPROC) (GLuint id, GLenum mode); +typedef void (APIENTRYP PFNGLENDCONDITIONALRENDERNVPROC) (void); +#endif + +#ifndef GL_NV_present_video +#define GL_NV_present_video 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPresentFrameKeyedNV (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLuint key0, GLenum target1, GLuint fill1, GLuint key1); +GLAPI void APIENTRY glPresentFrameDualFillNV (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLenum target1, GLuint fill1, GLenum target2, GLuint fill2, GLenum target3, GLuint fill3); +GLAPI void APIENTRY glGetVideoivNV (GLuint video_slot, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVideouivNV (GLuint video_slot, GLenum pname, GLuint *params); +GLAPI void APIENTRY glGetVideoi64vNV (GLuint video_slot, GLenum pname, GLint64EXT *params); +GLAPI void APIENTRY glGetVideoui64vNV (GLuint video_slot, GLenum pname, GLuint64EXT *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPRESENTFRAMEKEYEDNVPROC) (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLuint key0, GLenum target1, GLuint fill1, GLuint key1); +typedef void (APIENTRYP PFNGLPRESENTFRAMEDUALFILLNVPROC) (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLenum target1, GLuint fill1, GLenum target2, GLuint fill2, GLenum target3, GLuint fill3); +typedef void (APIENTRYP PFNGLGETVIDEOIVNVPROC) (GLuint video_slot, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVIDEOUIVNVPROC) (GLuint video_slot, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLGETVIDEOI64VNVPROC) (GLuint video_slot, GLenum pname, GLint64EXT *params); +typedef void (APIENTRYP PFNGLGETVIDEOUI64VNVPROC) (GLuint video_slot, GLenum pname, GLuint64EXT *params); +#endif + +#ifndef GL_EXT_transform_feedback +#define GL_EXT_transform_feedback 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginTransformFeedbackEXT (GLenum primitiveMode); +GLAPI void APIENTRY glEndTransformFeedbackEXT (void); +GLAPI void APIENTRY glBindBufferRangeEXT (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glBindBufferOffsetEXT (GLenum target, GLuint index, GLuint buffer, GLintptr offset); +GLAPI void APIENTRY glBindBufferBaseEXT (GLenum target, GLuint index, GLuint buffer); +GLAPI void APIENTRY glTransformFeedbackVaryingsEXT (GLuint program, GLsizei count, const GLchar* *varyings, GLenum bufferMode); +GLAPI void APIENTRY glGetTransformFeedbackVaryingEXT (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBEGINTRANSFORMFEEDBACKEXTPROC) (GLenum primitiveMode); +typedef void (APIENTRYP PFNGLENDTRANSFORMFEEDBACKEXTPROC) (void); +typedef void (APIENTRYP PFNGLBINDBUFFERRANGEEXTPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLBINDBUFFEROFFSETEXTPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset); +typedef void (APIENTRYP PFNGLBINDBUFFERBASEEXTPROC) (GLenum target, GLuint index, GLuint buffer); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKVARYINGSEXTPROC) (GLuint program, GLsizei count, const GLchar* *varyings, GLenum bufferMode); +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKVARYINGEXTPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +#endif + +#ifndef GL_EXT_direct_state_access +#define GL_EXT_direct_state_access 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glClientAttribDefaultEXT (GLbitfield mask); +GLAPI void APIENTRY glPushClientAttribDefaultEXT (GLbitfield mask); +GLAPI void APIENTRY glMatrixLoadfEXT (GLenum mode, const GLfloat *m); +GLAPI void APIENTRY glMatrixLoaddEXT (GLenum mode, const GLdouble *m); +GLAPI void APIENTRY glMatrixMultfEXT (GLenum mode, const GLfloat *m); +GLAPI void APIENTRY glMatrixMultdEXT (GLenum mode, const GLdouble *m); +GLAPI void APIENTRY glMatrixLoadIdentityEXT (GLenum mode); +GLAPI void APIENTRY glMatrixRotatefEXT (GLenum mode, GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glMatrixRotatedEXT (GLenum mode, GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glMatrixScalefEXT (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glMatrixScaledEXT (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glMatrixTranslatefEXT (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glMatrixTranslatedEXT (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glMatrixFrustumEXT (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLAPI void APIENTRY glMatrixOrthoEXT (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLAPI void APIENTRY glMatrixPopEXT (GLenum mode); +GLAPI void APIENTRY glMatrixPushEXT (GLenum mode); +GLAPI void APIENTRY glMatrixLoadTransposefEXT (GLenum mode, const GLfloat *m); +GLAPI void APIENTRY glMatrixLoadTransposedEXT (GLenum mode, const GLdouble *m); +GLAPI void APIENTRY glMatrixMultTransposefEXT (GLenum mode, const GLfloat *m); +GLAPI void APIENTRY glMatrixMultTransposedEXT (GLenum mode, const GLdouble *m); +GLAPI void APIENTRY glTextureParameterfEXT (GLuint texture, GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glTextureParameterfvEXT (GLuint texture, GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glTextureParameteriEXT (GLuint texture, GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glTextureParameterivEXT (GLuint texture, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTextureImage1DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glTextureImage2DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glTextureSubImage1DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glTextureSubImage2DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glCopyTextureImage1DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +GLAPI void APIENTRY glCopyTextureImage2DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +GLAPI void APIENTRY glCopyTextureSubImage1DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyTextureSubImage2DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetTextureImageEXT (GLuint texture, GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +GLAPI void APIENTRY glGetTextureParameterfvEXT (GLuint texture, GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetTextureParameterivEXT (GLuint texture, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTextureLevelParameterfvEXT (GLuint texture, GLenum target, GLint level, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetTextureLevelParameterivEXT (GLuint texture, GLenum target, GLint level, GLenum pname, GLint *params); +GLAPI void APIENTRY glTextureImage3DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glTextureSubImage3DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glCopyTextureSubImage3DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glMultiTexParameterfEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glMultiTexParameterfvEXT (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glMultiTexParameteriEXT (GLenum texunit, GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glMultiTexParameterivEXT (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glMultiTexImage1DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glMultiTexImage2DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glMultiTexSubImage1DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glMultiTexSubImage2DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glCopyMultiTexImage1DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +GLAPI void APIENTRY glCopyMultiTexImage2DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +GLAPI void APIENTRY glCopyMultiTexSubImage1DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyMultiTexSubImage2DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetMultiTexImageEXT (GLenum texunit, GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +GLAPI void APIENTRY glGetMultiTexParameterfvEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMultiTexParameterivEXT (GLenum texunit, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMultiTexLevelParameterfvEXT (GLenum texunit, GLenum target, GLint level, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMultiTexLevelParameterivEXT (GLenum texunit, GLenum target, GLint level, GLenum pname, GLint *params); +GLAPI void APIENTRY glMultiTexImage3DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glMultiTexSubImage3DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +GLAPI void APIENTRY glCopyMultiTexSubImage3DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glBindMultiTextureEXT (GLenum texunit, GLenum target, GLuint texture); +GLAPI void APIENTRY glEnableClientStateIndexedEXT (GLenum array, GLuint index); +GLAPI void APIENTRY glDisableClientStateIndexedEXT (GLenum array, GLuint index); +GLAPI void APIENTRY glMultiTexCoordPointerEXT (GLenum texunit, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void APIENTRY glMultiTexEnvfEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glMultiTexEnvfvEXT (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glMultiTexEnviEXT (GLenum texunit, GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glMultiTexEnvivEXT (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glMultiTexGendEXT (GLenum texunit, GLenum coord, GLenum pname, GLdouble param); +GLAPI void APIENTRY glMultiTexGendvEXT (GLenum texunit, GLenum coord, GLenum pname, const GLdouble *params); +GLAPI void APIENTRY glMultiTexGenfEXT (GLenum texunit, GLenum coord, GLenum pname, GLfloat param); +GLAPI void APIENTRY glMultiTexGenfvEXT (GLenum texunit, GLenum coord, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glMultiTexGeniEXT (GLenum texunit, GLenum coord, GLenum pname, GLint param); +GLAPI void APIENTRY glMultiTexGenivEXT (GLenum texunit, GLenum coord, GLenum pname, const GLint *params); +GLAPI void APIENTRY glGetMultiTexEnvfvEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMultiTexEnvivEXT (GLenum texunit, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMultiTexGendvEXT (GLenum texunit, GLenum coord, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetMultiTexGenfvEXT (GLenum texunit, GLenum coord, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMultiTexGenivEXT (GLenum texunit, GLenum coord, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetFloatIndexedvEXT (GLenum target, GLuint index, GLfloat *data); +GLAPI void APIENTRY glGetDoubleIndexedvEXT (GLenum target, GLuint index, GLdouble *data); +GLAPI void APIENTRY glGetPointerIndexedvEXT (GLenum target, GLuint index, GLvoid* *data); +GLAPI void APIENTRY glCompressedTextureImage3DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glCompressedTextureImage2DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glCompressedTextureImage1DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glCompressedTextureSubImage3DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glCompressedTextureSubImage2DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glCompressedTextureSubImage1DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glGetCompressedTextureImageEXT (GLuint texture, GLenum target, GLint lod, GLvoid *img); +GLAPI void APIENTRY glCompressedMultiTexImage3DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glCompressedMultiTexImage2DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glCompressedMultiTexImage1DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glCompressedMultiTexSubImage3DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glCompressedMultiTexSubImage2DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glCompressedMultiTexSubImage1DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *bits); +GLAPI void APIENTRY glGetCompressedMultiTexImageEXT (GLenum texunit, GLenum target, GLint lod, GLvoid *img); +GLAPI void APIENTRY glNamedProgramStringEXT (GLuint program, GLenum target, GLenum format, GLsizei len, const GLvoid *string); +GLAPI void APIENTRY glNamedProgramLocalParameter4dEXT (GLuint program, GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glNamedProgramLocalParameter4dvEXT (GLuint program, GLenum target, GLuint index, const GLdouble *params); +GLAPI void APIENTRY glNamedProgramLocalParameter4fEXT (GLuint program, GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glNamedProgramLocalParameter4fvEXT (GLuint program, GLenum target, GLuint index, const GLfloat *params); +GLAPI void APIENTRY glGetNamedProgramLocalParameterdvEXT (GLuint program, GLenum target, GLuint index, GLdouble *params); +GLAPI void APIENTRY glGetNamedProgramLocalParameterfvEXT (GLuint program, GLenum target, GLuint index, GLfloat *params); +GLAPI void APIENTRY glGetNamedProgramivEXT (GLuint program, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetNamedProgramStringEXT (GLuint program, GLenum target, GLenum pname, GLvoid *string); +GLAPI void APIENTRY glNamedProgramLocalParameters4fvEXT (GLuint program, GLenum target, GLuint index, GLsizei count, const GLfloat *params); +GLAPI void APIENTRY glNamedProgramLocalParameterI4iEXT (GLuint program, GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glNamedProgramLocalParameterI4ivEXT (GLuint program, GLenum target, GLuint index, const GLint *params); +GLAPI void APIENTRY glNamedProgramLocalParametersI4ivEXT (GLuint program, GLenum target, GLuint index, GLsizei count, const GLint *params); +GLAPI void APIENTRY glNamedProgramLocalParameterI4uiEXT (GLuint program, GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glNamedProgramLocalParameterI4uivEXT (GLuint program, GLenum target, GLuint index, const GLuint *params); +GLAPI void APIENTRY glNamedProgramLocalParametersI4uivEXT (GLuint program, GLenum target, GLuint index, GLsizei count, const GLuint *params); +GLAPI void APIENTRY glGetNamedProgramLocalParameterIivEXT (GLuint program, GLenum target, GLuint index, GLint *params); +GLAPI void APIENTRY glGetNamedProgramLocalParameterIuivEXT (GLuint program, GLenum target, GLuint index, GLuint *params); +GLAPI void APIENTRY glTextureParameterIivEXT (GLuint texture, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTextureParameterIuivEXT (GLuint texture, GLenum target, GLenum pname, const GLuint *params); +GLAPI void APIENTRY glGetTextureParameterIivEXT (GLuint texture, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTextureParameterIuivEXT (GLuint texture, GLenum target, GLenum pname, GLuint *params); +GLAPI void APIENTRY glMultiTexParameterIivEXT (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glMultiTexParameterIuivEXT (GLenum texunit, GLenum target, GLenum pname, const GLuint *params); +GLAPI void APIENTRY glGetMultiTexParameterIivEXT (GLenum texunit, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMultiTexParameterIuivEXT (GLenum texunit, GLenum target, GLenum pname, GLuint *params); +GLAPI void APIENTRY glProgramUniform1fEXT (GLuint program, GLint location, GLfloat v0); +GLAPI void APIENTRY glProgramUniform2fEXT (GLuint program, GLint location, GLfloat v0, GLfloat v1); +GLAPI void APIENTRY glProgramUniform3fEXT (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GLAPI void APIENTRY glProgramUniform4fEXT (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLAPI void APIENTRY glProgramUniform1iEXT (GLuint program, GLint location, GLint v0); +GLAPI void APIENTRY glProgramUniform2iEXT (GLuint program, GLint location, GLint v0, GLint v1); +GLAPI void APIENTRY glProgramUniform3iEXT (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +GLAPI void APIENTRY glProgramUniform4iEXT (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GLAPI void APIENTRY glProgramUniform1fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform2fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform3fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform4fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform1ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform2ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform3ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform4ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniformMatrix2fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2x3fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3x2fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2x4fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4x2fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3x4fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4x3fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform1uiEXT (GLuint program, GLint location, GLuint v0); +GLAPI void APIENTRY glProgramUniform2uiEXT (GLuint program, GLint location, GLuint v0, GLuint v1); +GLAPI void APIENTRY glProgramUniform3uiEXT (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +GLAPI void APIENTRY glProgramUniform4uiEXT (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +GLAPI void APIENTRY glProgramUniform1uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform2uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform3uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform4uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glNamedBufferDataEXT (GLuint buffer, GLsizeiptr size, const GLvoid *data, GLenum usage); +GLAPI void APIENTRY glNamedBufferSubDataEXT (GLuint buffer, GLintptr offset, GLsizeiptr size, const GLvoid *data); +GLAPI GLvoid* APIENTRY glMapNamedBufferEXT (GLuint buffer, GLenum access); +GLAPI GLboolean APIENTRY glUnmapNamedBufferEXT (GLuint buffer); +GLAPI GLvoid* APIENTRY glMapNamedBufferRangeEXT (GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access); +GLAPI void APIENTRY glFlushMappedNamedBufferRangeEXT (GLuint buffer, GLintptr offset, GLsizeiptr length); +GLAPI void APIENTRY glNamedCopyBufferSubDataEXT (GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +GLAPI void APIENTRY glGetNamedBufferParameterivEXT (GLuint buffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetNamedBufferPointervEXT (GLuint buffer, GLenum pname, GLvoid* *params); +GLAPI void APIENTRY glGetNamedBufferSubDataEXT (GLuint buffer, GLintptr offset, GLsizeiptr size, GLvoid *data); +GLAPI void APIENTRY glTextureBufferEXT (GLuint texture, GLenum target, GLenum internalformat, GLuint buffer); +GLAPI void APIENTRY glMultiTexBufferEXT (GLenum texunit, GLenum target, GLenum internalformat, GLuint buffer); +GLAPI void APIENTRY glNamedRenderbufferStorageEXT (GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetNamedRenderbufferParameterivEXT (GLuint renderbuffer, GLenum pname, GLint *params); +GLAPI GLenum APIENTRY glCheckNamedFramebufferStatusEXT (GLuint framebuffer, GLenum target); +GLAPI void APIENTRY glNamedFramebufferTexture1DEXT (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glNamedFramebufferTexture2DEXT (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glNamedFramebufferTexture3DEXT (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +GLAPI void APIENTRY glNamedFramebufferRenderbufferEXT (GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +GLAPI void APIENTRY glGetNamedFramebufferAttachmentParameterivEXT (GLuint framebuffer, GLenum attachment, GLenum pname, GLint *params); +GLAPI void APIENTRY glGenerateTextureMipmapEXT (GLuint texture, GLenum target); +GLAPI void APIENTRY glGenerateMultiTexMipmapEXT (GLenum texunit, GLenum target); +GLAPI void APIENTRY glFramebufferDrawBufferEXT (GLuint framebuffer, GLenum mode); +GLAPI void APIENTRY glFramebufferDrawBuffersEXT (GLuint framebuffer, GLsizei n, const GLenum *bufs); +GLAPI void APIENTRY glFramebufferReadBufferEXT (GLuint framebuffer, GLenum mode); +GLAPI void APIENTRY glGetFramebufferParameterivEXT (GLuint framebuffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glNamedRenderbufferStorageMultisampleEXT (GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glNamedRenderbufferStorageMultisampleCoverageEXT (GLuint renderbuffer, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glNamedFramebufferTextureEXT (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level); +GLAPI void APIENTRY glNamedFramebufferTextureLayerEXT (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer); +GLAPI void APIENTRY glNamedFramebufferTextureFaceEXT (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLenum face); +GLAPI void APIENTRY glTextureRenderbufferEXT (GLuint texture, GLenum target, GLuint renderbuffer); +GLAPI void APIENTRY glMultiTexRenderbufferEXT (GLenum texunit, GLenum target, GLuint renderbuffer); +GLAPI void APIENTRY glProgramUniform1dEXT (GLuint program, GLint location, GLdouble x); +GLAPI void APIENTRY glProgramUniform2dEXT (GLuint program, GLint location, GLdouble x, GLdouble y); +GLAPI void APIENTRY glProgramUniform3dEXT (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glProgramUniform4dEXT (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramUniform1dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform2dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform3dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform4dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2x3dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2x4dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3x2dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3x4dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4x2dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4x3dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCLIENTATTRIBDEFAULTEXTPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLPUSHCLIENTATTRIBDEFAULTEXTPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLMATRIXLOADFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXLOADDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (APIENTRYP PFNGLMATRIXMULTFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXMULTDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (APIENTRYP PFNGLMATRIXLOADIDENTITYEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLMATRIXROTATEFEXTPROC) (GLenum mode, GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLMATRIXROTATEDEXTPROC) (GLenum mode, GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLMATRIXSCALEFEXTPROC) (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLMATRIXSCALEDEXTPROC) (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLMATRIXTRANSLATEFEXTPROC) (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLMATRIXTRANSLATEDEXTPROC) (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLMATRIXFRUSTUMEXTPROC) (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +typedef void (APIENTRYP PFNGLMATRIXORTHOEXTPROC) (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +typedef void (APIENTRYP PFNGLMATRIXPOPEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLMATRIXPUSHEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLMATRIXLOADTRANSPOSEFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXLOADTRANSPOSEDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (APIENTRYP PFNGLMATRIXMULTTRANSPOSEFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXMULTTRANSPOSEDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERFEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERFVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLCOPYTEXTUREIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXTUREIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETTEXTUREIMAGEEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERFVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXTURELEVELPARAMETERFVEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETTEXTURELEVELPARAMETERIVEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERFEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMULTITEXIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLMULTITEXIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLMULTITEXSUBIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLMULTITEXSUBIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLCOPYMULTITEXIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (APIENTRYP PFNGLCOPYMULTITEXIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (APIENTRYP PFNGLCOPYMULTITEXSUBIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYMULTITEXSUBIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETMULTITEXIMAGEEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXLEVELPARAMETERFVEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMULTITEXLEVELPARAMETERIVEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLMULTITEXIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLMULTITEXSUBIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLCOPYMULTITEXSUBIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLBINDMULTITEXTUREEXTPROC) (GLenum texunit, GLenum target, GLuint texture); +typedef void (APIENTRYP PFNGLENABLECLIENTSTATEINDEXEDEXTPROC) (GLenum array, GLuint index); +typedef void (APIENTRYP PFNGLDISABLECLIENTSTATEINDEXEDEXTPROC) (GLenum array, GLuint index); +typedef void (APIENTRYP PFNGLMULTITEXCOORDPOINTEREXTPROC) (GLenum texunit, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLMULTITEXENVFEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLMULTITEXENVFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLMULTITEXENVIEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLMULTITEXENVIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMULTITEXGENDEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLdouble param); +typedef void (APIENTRYP PFNGLMULTITEXGENDVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, const GLdouble *params); +typedef void (APIENTRYP PFNGLMULTITEXGENFEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLMULTITEXGENFVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLMULTITEXGENIEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLMULTITEXGENIVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXENVFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMULTITEXENVIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXGENDVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETMULTITEXGENFVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMULTITEXGENIVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETFLOATINDEXEDVEXTPROC) (GLenum target, GLuint index, GLfloat *data); +typedef void (APIENTRYP PFNGLGETDOUBLEINDEXEDVEXTPROC) (GLenum target, GLuint index, GLdouble *data); +typedef void (APIENTRYP PFNGLGETPOINTERINDEXEDVEXTPROC) (GLenum target, GLuint index, GLvoid* *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTUREIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTUREIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTUREIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXTUREIMAGEEXTPROC) (GLuint texture, GLenum target, GLint lod, GLvoid *img); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXSUBIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXSUBIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXSUBIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *bits); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDMULTITEXIMAGEEXTPROC) (GLenum texunit, GLenum target, GLint lod, GLvoid *img); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMSTRINGEXTPROC) (GLuint program, GLenum target, GLenum format, GLsizei len, const GLvoid *string); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4DEXTPROC) (GLuint program, GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4DVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4FEXTPROC) (GLuint program, GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4FVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERDVEXTPROC) (GLuint program, GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERFVEXTPROC) (GLuint program, GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMIVEXTPROC) (GLuint program, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMSTRINGEXTPROC) (GLuint program, GLenum target, GLenum pname, GLvoid *string); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERS4FVEXTPROC) (GLuint program, GLenum target, GLuint index, GLsizei count, const GLfloat *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4IEXTPROC) (GLuint program, GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4IVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLint *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERSI4IVEXTPROC) (GLuint program, GLenum target, GLuint index, GLsizei count, const GLint *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIEXTPROC) (GLuint program, GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLuint *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERSI4UIVEXTPROC) (GLuint program, GLenum target, GLuint index, GLsizei count, const GLuint *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERIIVEXTPROC) (GLuint program, GLenum target, GLuint index, GLint *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERIUIVEXTPROC) (GLuint program, GLenum target, GLuint index, GLuint *params); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIUIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLuint *params); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIUIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIUIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLuint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERIIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERIUIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FEXTPROC) (GLuint program, GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IEXTPROC) (GLuint program, GLint location, GLint v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIEXTPROC) (GLuint program, GLint location, GLuint v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLNAMEDBUFFERDATAEXTPROC) (GLuint buffer, GLsizeiptr size, const GLvoid *data, GLenum usage); +typedef void (APIENTRYP PFNGLNAMEDBUFFERSUBDATAEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, const GLvoid *data); +typedef GLvoid* (APIENTRYP PFNGLMAPNAMEDBUFFEREXTPROC) (GLuint buffer, GLenum access); +typedef GLboolean (APIENTRYP PFNGLUNMAPNAMEDBUFFEREXTPROC) (GLuint buffer); +typedef GLvoid* (APIENTRYP PFNGLMAPNAMEDBUFFERRANGEEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access); +typedef void (APIENTRYP PFNGLFLUSHMAPPEDNAMEDBUFFERRANGEEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr length); +typedef void (APIENTRYP PFNGLNAMEDCOPYBUFFERSUBDATAEXTPROC) (GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERIVEXTPROC) (GLuint buffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPOINTERVEXTPROC) (GLuint buffer, GLenum pname, GLvoid* *params); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERSUBDATAEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, GLvoid *data); +typedef void (APIENTRYP PFNGLTEXTUREBUFFEREXTPROC) (GLuint texture, GLenum target, GLenum internalformat, GLuint buffer); +typedef void (APIENTRYP PFNGLMULTITEXBUFFEREXTPROC) (GLenum texunit, GLenum target, GLenum internalformat, GLuint buffer); +typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEEXTPROC) (GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETNAMEDRENDERBUFFERPARAMETERIVEXTPROC) (GLuint renderbuffer, GLenum pname, GLint *params); +typedef GLenum (APIENTRYP PFNGLCHECKNAMEDFRAMEBUFFERSTATUSEXTPROC) (GLuint framebuffer, GLenum target); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURE1DEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURE2DEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURE3DEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERRENDERBUFFEREXTPROC) (GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGENERATETEXTUREMIPMAPEXTPROC) (GLuint texture, GLenum target); +typedef void (APIENTRYP PFNGLGENERATEMULTITEXMIPMAPEXTPROC) (GLenum texunit, GLenum target); +typedef void (APIENTRYP PFNGLFRAMEBUFFERDRAWBUFFEREXTPROC) (GLuint framebuffer, GLenum mode); +typedef void (APIENTRYP PFNGLFRAMEBUFFERDRAWBUFFERSEXTPROC) (GLuint framebuffer, GLsizei n, const GLenum *bufs); +typedef void (APIENTRYP PFNGLFRAMEBUFFERREADBUFFEREXTPROC) (GLuint framebuffer, GLenum mode); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERPARAMETERIVEXTPROC) (GLuint framebuffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC) (GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLECOVERAGEEXTPROC) (GLuint renderbuffer, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTUREEXTPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURELAYEREXTPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTUREFACEEXTPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLenum face); +typedef void (APIENTRYP PFNGLTEXTURERENDERBUFFEREXTPROC) (GLuint texture, GLenum target, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLMULTITEXRENDERBUFFEREXTPROC) (GLenum texunit, GLenum target, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DEXTPROC) (GLuint program, GLint location, GLdouble x); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DEXTPROC) (GLuint program, GLint location, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DEXTPROC) (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DEXTPROC) (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +#endif + +#ifndef GL_EXT_vertex_array_bgra +#define GL_EXT_vertex_array_bgra 1 +#endif + +#ifndef GL_EXT_texture_swizzle +#define GL_EXT_texture_swizzle 1 +#endif + +#ifndef GL_NV_explicit_multisample +#define GL_NV_explicit_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetMultisamplefvNV (GLenum pname, GLuint index, GLfloat *val); +GLAPI void APIENTRY glSampleMaskIndexedNV (GLuint index, GLbitfield mask); +GLAPI void APIENTRY glTexRenderbufferNV (GLenum target, GLuint renderbuffer); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETMULTISAMPLEFVNVPROC) (GLenum pname, GLuint index, GLfloat *val); +typedef void (APIENTRYP PFNGLSAMPLEMASKINDEXEDNVPROC) (GLuint index, GLbitfield mask); +typedef void (APIENTRYP PFNGLTEXRENDERBUFFERNVPROC) (GLenum target, GLuint renderbuffer); +#endif + +#ifndef GL_NV_transform_feedback2 +#define GL_NV_transform_feedback2 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindTransformFeedbackNV (GLenum target, GLuint id); +GLAPI void APIENTRY glDeleteTransformFeedbacksNV (GLsizei n, const GLuint *ids); +GLAPI void APIENTRY glGenTransformFeedbacksNV (GLsizei n, GLuint *ids); +GLAPI GLboolean APIENTRY glIsTransformFeedbackNV (GLuint id); +GLAPI void APIENTRY glPauseTransformFeedbackNV (void); +GLAPI void APIENTRY glResumeTransformFeedbackNV (void); +GLAPI void APIENTRY glDrawTransformFeedbackNV (GLenum mode, GLuint id); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDTRANSFORMFEEDBACKNVPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLDELETETRANSFORMFEEDBACKSNVPROC) (GLsizei n, const GLuint *ids); +typedef void (APIENTRYP PFNGLGENTRANSFORMFEEDBACKSNVPROC) (GLsizei n, GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISTRANSFORMFEEDBACKNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLPAUSETRANSFORMFEEDBACKNVPROC) (void); +typedef void (APIENTRYP PFNGLRESUMETRANSFORMFEEDBACKNVPROC) (void); +typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKNVPROC) (GLenum mode, GLuint id); +#endif + +#ifndef GL_ATI_meminfo +#define GL_ATI_meminfo 1 +#endif + +#ifndef GL_AMD_performance_monitor +#define GL_AMD_performance_monitor 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetPerfMonitorGroupsAMD (GLint *numGroups, GLsizei groupsSize, GLuint *groups); +GLAPI void APIENTRY glGetPerfMonitorCountersAMD (GLuint group, GLint *numCounters, GLint *maxActiveCounters, GLsizei counterSize, GLuint *counters); +GLAPI void APIENTRY glGetPerfMonitorGroupStringAMD (GLuint group, GLsizei bufSize, GLsizei *length, GLchar *groupString); +GLAPI void APIENTRY glGetPerfMonitorCounterStringAMD (GLuint group, GLuint counter, GLsizei bufSize, GLsizei *length, GLchar *counterString); +GLAPI void APIENTRY glGetPerfMonitorCounterInfoAMD (GLuint group, GLuint counter, GLenum pname, GLvoid *data); +GLAPI void APIENTRY glGenPerfMonitorsAMD (GLsizei n, GLuint *monitors); +GLAPI void APIENTRY glDeletePerfMonitorsAMD (GLsizei n, GLuint *monitors); +GLAPI void APIENTRY glSelectPerfMonitorCountersAMD (GLuint monitor, GLboolean enable, GLuint group, GLint numCounters, GLuint *counterList); +GLAPI void APIENTRY glBeginPerfMonitorAMD (GLuint monitor); +GLAPI void APIENTRY glEndPerfMonitorAMD (GLuint monitor); +GLAPI void APIENTRY glGetPerfMonitorCounterDataAMD (GLuint monitor, GLenum pname, GLsizei dataSize, GLuint *data, GLint *bytesWritten); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETPERFMONITORGROUPSAMDPROC) (GLint *numGroups, GLsizei groupsSize, GLuint *groups); +typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERSAMDPROC) (GLuint group, GLint *numCounters, GLint *maxActiveCounters, GLsizei counterSize, GLuint *counters); +typedef void (APIENTRYP PFNGLGETPERFMONITORGROUPSTRINGAMDPROC) (GLuint group, GLsizei bufSize, GLsizei *length, GLchar *groupString); +typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERSTRINGAMDPROC) (GLuint group, GLuint counter, GLsizei bufSize, GLsizei *length, GLchar *counterString); +typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERINFOAMDPROC) (GLuint group, GLuint counter, GLenum pname, GLvoid *data); +typedef void (APIENTRYP PFNGLGENPERFMONITORSAMDPROC) (GLsizei n, GLuint *monitors); +typedef void (APIENTRYP PFNGLDELETEPERFMONITORSAMDPROC) (GLsizei n, GLuint *monitors); +typedef void (APIENTRYP PFNGLSELECTPERFMONITORCOUNTERSAMDPROC) (GLuint monitor, GLboolean enable, GLuint group, GLint numCounters, GLuint *counterList); +typedef void (APIENTRYP PFNGLBEGINPERFMONITORAMDPROC) (GLuint monitor); +typedef void (APIENTRYP PFNGLENDPERFMONITORAMDPROC) (GLuint monitor); +typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERDATAAMDPROC) (GLuint monitor, GLenum pname, GLsizei dataSize, GLuint *data, GLint *bytesWritten); +#endif + +#ifndef GL_AMD_texture_texture4 +#define GL_AMD_texture_texture4 1 +#endif + +#ifndef GL_AMD_vertex_shader_tesselator +#define GL_AMD_vertex_shader_tesselator 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTessellationFactorAMD (GLfloat factor); +GLAPI void APIENTRY glTessellationModeAMD (GLenum mode); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTESSELLATIONFACTORAMDPROC) (GLfloat factor); +typedef void (APIENTRYP PFNGLTESSELLATIONMODEAMDPROC) (GLenum mode); +#endif + +#ifndef GL_EXT_provoking_vertex +#define GL_EXT_provoking_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProvokingVertexEXT (GLenum mode); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROVOKINGVERTEXEXTPROC) (GLenum mode); +#endif + +#ifndef GL_EXT_texture_snorm +#define GL_EXT_texture_snorm 1 +#endif + +#ifndef GL_AMD_draw_buffers_blend +#define GL_AMD_draw_buffers_blend 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncIndexedAMD (GLuint buf, GLenum src, GLenum dst); +GLAPI void APIENTRY glBlendFuncSeparateIndexedAMD (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +GLAPI void APIENTRY glBlendEquationIndexedAMD (GLuint buf, GLenum mode); +GLAPI void APIENTRY glBlendEquationSeparateIndexedAMD (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDFUNCINDEXEDAMDPROC) (GLuint buf, GLenum src, GLenum dst); +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEINDEXEDAMDPROC) (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +typedef void (APIENTRYP PFNGLBLENDEQUATIONINDEXEDAMDPROC) (GLuint buf, GLenum mode); +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEINDEXEDAMDPROC) (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +#endif + +#ifndef GL_APPLE_texture_range +#define GL_APPLE_texture_range 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureRangeAPPLE (GLenum target, GLsizei length, const GLvoid *pointer); +GLAPI void APIENTRY glGetTexParameterPointervAPPLE (GLenum target, GLenum pname, GLvoid* *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXTURERANGEAPPLEPROC) (GLenum target, GLsizei length, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERPOINTERVAPPLEPROC) (GLenum target, GLenum pname, GLvoid* *params); +#endif + +#ifndef GL_APPLE_float_pixels +#define GL_APPLE_float_pixels 1 +#endif + +#ifndef GL_APPLE_vertex_program_evaluators +#define GL_APPLE_vertex_program_evaluators 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glEnableVertexAttribAPPLE (GLuint index, GLenum pname); +GLAPI void APIENTRY glDisableVertexAttribAPPLE (GLuint index, GLenum pname); +GLAPI GLboolean APIENTRY glIsVertexAttribEnabledAPPLE (GLuint index, GLenum pname); +GLAPI void APIENTRY glMapVertexAttrib1dAPPLE (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +GLAPI void APIENTRY glMapVertexAttrib1fAPPLE (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +GLAPI void APIENTRY glMapVertexAttrib2dAPPLE (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +GLAPI void APIENTRY glMapVertexAttrib2fAPPLE (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBAPPLEPROC) (GLuint index, GLenum pname); +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBAPPLEPROC) (GLuint index, GLenum pname); +typedef GLboolean (APIENTRYP PFNGLISVERTEXATTRIBENABLEDAPPLEPROC) (GLuint index, GLenum pname); +typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB1DAPPLEPROC) (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB1FAPPLEPROC) (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB2DAPPLEPROC) (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB2FAPPLEPROC) (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +#endif + +#ifndef GL_APPLE_aux_depth_stencil +#define GL_APPLE_aux_depth_stencil 1 +#endif + +#ifndef GL_APPLE_object_purgeable +#define GL_APPLE_object_purgeable 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLenum APIENTRY glObjectPurgeableAPPLE (GLenum objectType, GLuint name, GLenum option); +GLAPI GLenum APIENTRY glObjectUnpurgeableAPPLE (GLenum objectType, GLuint name, GLenum option); +GLAPI void APIENTRY glGetObjectParameterivAPPLE (GLenum objectType, GLuint name, GLenum pname, GLint *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLenum (APIENTRYP PFNGLOBJECTPURGEABLEAPPLEPROC) (GLenum objectType, GLuint name, GLenum option); +typedef GLenum (APIENTRYP PFNGLOBJECTUNPURGEABLEAPPLEPROC) (GLenum objectType, GLuint name, GLenum option); +typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERIVAPPLEPROC) (GLenum objectType, GLuint name, GLenum pname, GLint *params); +#endif + +#ifndef GL_APPLE_row_bytes +#define GL_APPLE_row_bytes 1 +#endif + +#ifndef GL_APPLE_rgb_422 +#define GL_APPLE_rgb_422 1 +#endif + +#ifndef GL_NV_video_capture +#define GL_NV_video_capture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginVideoCaptureNV (GLuint video_capture_slot); +GLAPI void APIENTRY glBindVideoCaptureStreamBufferNV (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLintptrARB offset); +GLAPI void APIENTRY glBindVideoCaptureStreamTextureNV (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLenum target, GLuint texture); +GLAPI void APIENTRY glEndVideoCaptureNV (GLuint video_capture_slot); +GLAPI void APIENTRY glGetVideoCaptureivNV (GLuint video_capture_slot, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVideoCaptureStreamivNV (GLuint video_capture_slot, GLuint stream, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVideoCaptureStreamfvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVideoCaptureStreamdvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, GLdouble *params); +GLAPI GLenum APIENTRY glVideoCaptureNV (GLuint video_capture_slot, GLuint *sequence_num, GLuint64EXT *capture_time); +GLAPI void APIENTRY glVideoCaptureStreamParameterivNV (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLint *params); +GLAPI void APIENTRY glVideoCaptureStreamParameterfvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glVideoCaptureStreamParameterdvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLdouble *params); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBEGINVIDEOCAPTURENVPROC) (GLuint video_capture_slot); +typedef void (APIENTRYP PFNGLBINDVIDEOCAPTURESTREAMBUFFERNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLintptrARB offset); +typedef void (APIENTRYP PFNGLBINDVIDEOCAPTURESTREAMTEXTURENVPROC) (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLenum target, GLuint texture); +typedef void (APIENTRYP PFNGLENDVIDEOCAPTURENVPROC) (GLuint video_capture_slot); +typedef void (APIENTRYP PFNGLGETVIDEOCAPTUREIVNVPROC) (GLuint video_capture_slot, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVIDEOCAPTURESTREAMIVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVIDEOCAPTURESTREAMFVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVIDEOCAPTURESTREAMDVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, GLdouble *params); +typedef GLenum (APIENTRYP PFNGLVIDEOCAPTURENVPROC) (GLuint video_capture_slot, GLuint *sequence_num, GLuint64EXT *capture_time); +typedef void (APIENTRYP PFNGLVIDEOCAPTURESTREAMPARAMETERIVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLVIDEOCAPTURESTREAMPARAMETERFVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLVIDEOCAPTURESTREAMPARAMETERDVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLdouble *params); +#endif + +#ifndef GL_NV_copy_image +#define GL_NV_copy_image 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCopyImageSubDataNV (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOPYIMAGESUBDATANVPROC) (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); +#endif + +#ifndef GL_EXT_separate_shader_objects +#define GL_EXT_separate_shader_objects 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUseShaderProgramEXT (GLenum type, GLuint program); +GLAPI void APIENTRY glActiveProgramEXT (GLuint program); +GLAPI GLuint APIENTRY glCreateShaderProgramEXT (GLenum type, const GLchar *string); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLUSESHADERPROGRAMEXTPROC) (GLenum type, GLuint program); +typedef void (APIENTRYP PFNGLACTIVEPROGRAMEXTPROC) (GLuint program); +typedef GLuint (APIENTRYP PFNGLCREATESHADERPROGRAMEXTPROC) (GLenum type, const GLchar *string); +#endif + +#ifndef GL_NV_parameter_buffer_object2 +#define GL_NV_parameter_buffer_object2 1 +#endif + +#ifndef GL_NV_shader_buffer_load +#define GL_NV_shader_buffer_load 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMakeBufferResidentNV (GLenum target, GLenum access); +GLAPI void APIENTRY glMakeBufferNonResidentNV (GLenum target); +GLAPI GLboolean APIENTRY glIsBufferResidentNV (GLenum target); +GLAPI void APIENTRY glMakeNamedBufferResidentNV (GLuint buffer, GLenum access); +GLAPI void APIENTRY glMakeNamedBufferNonResidentNV (GLuint buffer); +GLAPI GLboolean APIENTRY glIsNamedBufferResidentNV (GLuint buffer); +GLAPI void APIENTRY glGetBufferParameterui64vNV (GLenum target, GLenum pname, GLuint64EXT *params); +GLAPI void APIENTRY glGetNamedBufferParameterui64vNV (GLuint buffer, GLenum pname, GLuint64EXT *params); +GLAPI void APIENTRY glGetIntegerui64vNV (GLenum value, GLuint64EXT *result); +GLAPI void APIENTRY glUniformui64NV (GLint location, GLuint64EXT value); +GLAPI void APIENTRY glUniformui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glGetUniformui64vNV (GLuint program, GLint location, GLuint64EXT *params); +GLAPI void APIENTRY glProgramUniformui64NV (GLuint program, GLint location, GLuint64EXT value); +GLAPI void APIENTRY glProgramUniformui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMAKEBUFFERRESIDENTNVPROC) (GLenum target, GLenum access); +typedef void (APIENTRYP PFNGLMAKEBUFFERNONRESIDENTNVPROC) (GLenum target); +typedef GLboolean (APIENTRYP PFNGLISBUFFERRESIDENTNVPROC) (GLenum target); +typedef void (APIENTRYP PFNGLMAKENAMEDBUFFERRESIDENTNVPROC) (GLuint buffer, GLenum access); +typedef void (APIENTRYP PFNGLMAKENAMEDBUFFERNONRESIDENTNVPROC) (GLuint buffer); +typedef GLboolean (APIENTRYP PFNGLISNAMEDBUFFERRESIDENTNVPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERUI64VNVPROC) (GLenum target, GLenum pname, GLuint64EXT *params); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERUI64VNVPROC) (GLuint buffer, GLenum pname, GLuint64EXT *params); +typedef void (APIENTRYP PFNGLGETINTEGERUI64VNVPROC) (GLenum value, GLuint64EXT *result); +typedef void (APIENTRYP PFNGLUNIFORMUI64NVPROC) (GLint location, GLuint64EXT value); +typedef void (APIENTRYP PFNGLUNIFORMUI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLGETUNIFORMUI64VNVPROC) (GLuint program, GLint location, GLuint64EXT *params); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMUI64NVPROC) (GLuint program, GLint location, GLuint64EXT value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMUI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +#endif + +#ifndef GL_NV_vertex_buffer_unified_memory +#define GL_NV_vertex_buffer_unified_memory 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBufferAddressRangeNV (GLenum pname, GLuint index, GLuint64EXT address, GLsizeiptr length); +GLAPI void APIENTRY glVertexFormatNV (GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glNormalFormatNV (GLenum type, GLsizei stride); +GLAPI void APIENTRY glColorFormatNV (GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glIndexFormatNV (GLenum type, GLsizei stride); +GLAPI void APIENTRY glTexCoordFormatNV (GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glEdgeFlagFormatNV (GLsizei stride); +GLAPI void APIENTRY glSecondaryColorFormatNV (GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glFogCoordFormatNV (GLenum type, GLsizei stride); +GLAPI void APIENTRY glVertexAttribFormatNV (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride); +GLAPI void APIENTRY glVertexAttribIFormatNV (GLuint index, GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glGetIntegerui64i_vNV (GLenum value, GLuint index, GLuint64EXT *result); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBUFFERADDRESSRANGENVPROC) (GLenum pname, GLuint index, GLuint64EXT address, GLsizeiptr length); +typedef void (APIENTRYP PFNGLVERTEXFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLNORMALFORMATNVPROC) (GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLCOLORFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLINDEXFORMATNVPROC) (GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLTEXCOORDFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLEDGEFLAGFORMATNVPROC) (GLsizei stride); +typedef void (APIENTRYP PFNGLSECONDARYCOLORFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLFOGCOORDFORMATNVPROC) (GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLVERTEXATTRIBFORMATNVPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride); +typedef void (APIENTRYP PFNGLVERTEXATTRIBIFORMATNVPROC) (GLuint index, GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLGETINTEGERUI64I_VNVPROC) (GLenum value, GLuint index, GLuint64EXT *result); +#endif + +#ifndef GL_NV_texture_barrier +#define GL_NV_texture_barrier 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureBarrierNV (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXTUREBARRIERNVPROC) (void); +#endif + +#ifndef GL_AMD_shader_stencil_export +#define GL_AMD_shader_stencil_export 1 +#endif + +#ifndef GL_AMD_seamless_cubemap_per_texture +#define GL_AMD_seamless_cubemap_per_texture 1 +#endif + +#ifndef GL_AMD_conservative_depth +#define GL_AMD_conservative_depth 1 +#endif + +#ifndef GL_EXT_shader_image_load_store +#define GL_EXT_shader_image_load_store 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindImageTextureEXT (GLuint index, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLint format); +GLAPI void APIENTRY glMemoryBarrierEXT (GLbitfield barriers); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDIMAGETEXTUREEXTPROC) (GLuint index, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLint format); +typedef void (APIENTRYP PFNGLMEMORYBARRIEREXTPROC) (GLbitfield barriers); +#endif + +#ifndef GL_EXT_vertex_attrib_64bit +#define GL_EXT_vertex_attrib_64bit 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribL1dEXT (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttribL2dEXT (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttribL3dEXT (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttribL4dEXT (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttribL1dvEXT (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL2dvEXT (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL3dvEXT (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL4dvEXT (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribLPointerEXT (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +GLAPI void APIENTRY glGetVertexAttribLdvEXT (GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glVertexArrayVertexAttribLOffsetEXT (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DEXTPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DEXTPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DEXTPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DEXTPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DVEXTPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DVEXTPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DVEXTPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DVEXTPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBLPOINTEREXTPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLDVEXTPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBLOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset); +#endif + +#ifndef GL_NV_gpu_program5 +#define GL_NV_gpu_program5 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramSubroutineParametersuivNV (GLenum target, GLsizei count, const GLuint *params); +GLAPI void APIENTRY glGetProgramSubroutineParameteruivNV (GLenum target, GLuint index, GLuint *param); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROGRAMSUBROUTINEPARAMETERSUIVNVPROC) (GLenum target, GLsizei count, const GLuint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSUBROUTINEPARAMETERUIVNVPROC) (GLenum target, GLuint index, GLuint *param); +#endif + +#ifndef GL_NV_gpu_shader5 +#define GL_NV_gpu_shader5 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUniform1i64NV (GLint location, GLint64EXT x); +GLAPI void APIENTRY glUniform2i64NV (GLint location, GLint64EXT x, GLint64EXT y); +GLAPI void APIENTRY glUniform3i64NV (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +GLAPI void APIENTRY glUniform4i64NV (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +GLAPI void APIENTRY glUniform1i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glUniform2i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glUniform3i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glUniform4i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glUniform1ui64NV (GLint location, GLuint64EXT x); +GLAPI void APIENTRY glUniform2ui64NV (GLint location, GLuint64EXT x, GLuint64EXT y); +GLAPI void APIENTRY glUniform3ui64NV (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +GLAPI void APIENTRY glUniform4ui64NV (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +GLAPI void APIENTRY glUniform1ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glUniform2ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glUniform3ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glUniform4ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glGetUniformi64vNV (GLuint program, GLint location, GLint64EXT *params); +GLAPI void APIENTRY glProgramUniform1i64NV (GLuint program, GLint location, GLint64EXT x); +GLAPI void APIENTRY glProgramUniform2i64NV (GLuint program, GLint location, GLint64EXT x, GLint64EXT y); +GLAPI void APIENTRY glProgramUniform3i64NV (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +GLAPI void APIENTRY glProgramUniform4i64NV (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +GLAPI void APIENTRY glProgramUniform1i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glProgramUniform2i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glProgramUniform3i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glProgramUniform4i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glProgramUniform1ui64NV (GLuint program, GLint location, GLuint64EXT x); +GLAPI void APIENTRY glProgramUniform2ui64NV (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y); +GLAPI void APIENTRY glProgramUniform3ui64NV (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +GLAPI void APIENTRY glProgramUniform4ui64NV (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +GLAPI void APIENTRY glProgramUniform1ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glProgramUniform2ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glProgramUniform3ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glProgramUniform4ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLUNIFORM1I64NVPROC) (GLint location, GLint64EXT x); +typedef void (APIENTRYP PFNGLUNIFORM2I64NVPROC) (GLint location, GLint64EXT x, GLint64EXT y); +typedef void (APIENTRYP PFNGLUNIFORM3I64NVPROC) (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +typedef void (APIENTRYP PFNGLUNIFORM4I64NVPROC) (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +typedef void (APIENTRYP PFNGLUNIFORM1I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM2I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM3I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM4I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM1UI64NVPROC) (GLint location, GLuint64EXT x); +typedef void (APIENTRYP PFNGLUNIFORM2UI64NVPROC) (GLint location, GLuint64EXT x, GLuint64EXT y); +typedef void (APIENTRYP PFNGLUNIFORM3UI64NVPROC) (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +typedef void (APIENTRYP PFNGLUNIFORM4UI64NVPROC) (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +typedef void (APIENTRYP PFNGLUNIFORM1UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM2UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM3UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM4UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLGETUNIFORMI64VNVPROC) (GLuint program, GLint location, GLint64EXT *params); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1I64NVPROC) (GLuint program, GLint location, GLint64EXT x); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2I64NVPROC) (GLuint program, GLint location, GLint64EXT x, GLint64EXT y); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3I64NVPROC) (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4I64NVPROC) (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +#endif + +#ifndef GL_NV_shader_buffer_store +#define GL_NV_shader_buffer_store 1 +#endif + +#ifndef GL_NV_tessellation_program5 +#define GL_NV_tessellation_program5 1 +#endif + +#ifndef GL_NV_vertex_attrib_integer_64bit +#define GL_NV_vertex_attrib_integer_64bit 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribL1i64NV (GLuint index, GLint64EXT x); +GLAPI void APIENTRY glVertexAttribL2i64NV (GLuint index, GLint64EXT x, GLint64EXT y); +GLAPI void APIENTRY glVertexAttribL3i64NV (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z); +GLAPI void APIENTRY glVertexAttribL4i64NV (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +GLAPI void APIENTRY glVertexAttribL1i64vNV (GLuint index, const GLint64EXT *v); +GLAPI void APIENTRY glVertexAttribL2i64vNV (GLuint index, const GLint64EXT *v); +GLAPI void APIENTRY glVertexAttribL3i64vNV (GLuint index, const GLint64EXT *v); +GLAPI void APIENTRY glVertexAttribL4i64vNV (GLuint index, const GLint64EXT *v); +GLAPI void APIENTRY glVertexAttribL1ui64NV (GLuint index, GLuint64EXT x); +GLAPI void APIENTRY glVertexAttribL2ui64NV (GLuint index, GLuint64EXT x, GLuint64EXT y); +GLAPI void APIENTRY glVertexAttribL3ui64NV (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +GLAPI void APIENTRY glVertexAttribL4ui64NV (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +GLAPI void APIENTRY glVertexAttribL1ui64vNV (GLuint index, const GLuint64EXT *v); +GLAPI void APIENTRY glVertexAttribL2ui64vNV (GLuint index, const GLuint64EXT *v); +GLAPI void APIENTRY glVertexAttribL3ui64vNV (GLuint index, const GLuint64EXT *v); +GLAPI void APIENTRY glVertexAttribL4ui64vNV (GLuint index, const GLuint64EXT *v); +GLAPI void APIENTRY glGetVertexAttribLi64vNV (GLuint index, GLenum pname, GLint64EXT *params); +GLAPI void APIENTRY glGetVertexAttribLui64vNV (GLuint index, GLenum pname, GLuint64EXT *params); +GLAPI void APIENTRY glVertexAttribLFormatNV (GLuint index, GLint size, GLenum type, GLsizei stride); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1I64NVPROC) (GLuint index, GLint64EXT x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2I64NVPROC) (GLuint index, GLint64EXT x, GLint64EXT y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3I64NVPROC) (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4I64NVPROC) (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1I64VNVPROC) (GLuint index, const GLint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2I64VNVPROC) (GLuint index, const GLint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3I64VNVPROC) (GLuint index, const GLint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4I64VNVPROC) (GLuint index, const GLint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64NVPROC) (GLuint index, GLuint64EXT x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2UI64NVPROC) (GLuint index, GLuint64EXT x, GLuint64EXT y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3UI64NVPROC) (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4UI64NVPROC) (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64VNVPROC) (GLuint index, const GLuint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2UI64VNVPROC) (GLuint index, const GLuint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3UI64VNVPROC) (GLuint index, const GLuint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4UI64VNVPROC) (GLuint index, const GLuint64EXT *v); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLI64VNVPROC) (GLuint index, GLenum pname, GLint64EXT *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLUI64VNVPROC) (GLuint index, GLenum pname, GLuint64EXT *params); +typedef void (APIENTRYP PFNGLVERTEXATTRIBLFORMATNVPROC) (GLuint index, GLint size, GLenum type, GLsizei stride); +#endif + +#ifndef GL_NV_multisample_coverage +#define GL_NV_multisample_coverage 1 +#endif + +#ifndef GL_AMD_name_gen_delete +#define GL_AMD_name_gen_delete 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenNamesAMD (GLenum identifier, GLuint num, GLuint *names); +GLAPI void APIENTRY glDeleteNamesAMD (GLenum identifier, GLuint num, const GLuint *names); +GLAPI GLboolean APIENTRY glIsNameAMD (GLenum identifier, GLuint name); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENNAMESAMDPROC) (GLenum identifier, GLuint num, GLuint *names); +typedef void (APIENTRYP PFNGLDELETENAMESAMDPROC) (GLenum identifier, GLuint num, const GLuint *names); +typedef GLboolean (APIENTRYP PFNGLISNAMEAMDPROC) (GLenum identifier, GLuint name); +#endif + +#ifndef GL_AMD_debug_output +#define GL_AMD_debug_output 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDebugMessageEnableAMD (GLenum category, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +GLAPI void APIENTRY glDebugMessageInsertAMD (GLenum category, GLenum severity, GLuint id, GLsizei length, const GLchar *buf); +GLAPI void APIENTRY glDebugMessageCallbackAMD (GLDEBUGPROCAMD callback, GLvoid *userParam); +GLAPI GLuint APIENTRY glGetDebugMessageLogAMD (GLuint count, GLsizei bufsize, GLenum *categories, GLuint *severities, GLuint *ids, GLsizei *lengths, GLchar *message); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDEBUGMESSAGEENABLEAMDPROC) (GLenum category, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +typedef void (APIENTRYP PFNGLDEBUGMESSAGEINSERTAMDPROC) (GLenum category, GLenum severity, GLuint id, GLsizei length, const GLchar *buf); +typedef void (APIENTRYP PFNGLDEBUGMESSAGECALLBACKAMDPROC) (GLDEBUGPROCAMD callback, GLvoid *userParam); +typedef GLuint (APIENTRYP PFNGLGETDEBUGMESSAGELOGAMDPROC) (GLuint count, GLsizei bufsize, GLenum *categories, GLuint *severities, GLuint *ids, GLsizei *lengths, GLchar *message); +#endif + +#ifndef GL_NV_vdpau_interop +#define GL_NV_vdpau_interop 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVDPAUInitNV (const GLvoid *vdpDevice, const GLvoid *getProcAddress); +GLAPI void APIENTRY glVDPAUFiniNV (void); +GLAPI GLvdpauSurfaceNV APIENTRY glVDPAURegisterVideoSurfaceNV (GLvoid *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); +GLAPI GLvdpauSurfaceNV APIENTRY glVDPAURegisterOutputSurfaceNV (GLvoid *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); +GLAPI void APIENTRY glVDPAUIsSurfaceNV (GLvdpauSurfaceNV surface); +GLAPI void APIENTRY glVDPAUUnregisterSurfaceNV (GLvdpauSurfaceNV surface); +GLAPI void APIENTRY glVDPAUGetSurfaceivNV (GLvdpauSurfaceNV surface, GLenum pname, GLsizei bufSize, GLsizei *length, GLint *values); +GLAPI void APIENTRY glVDPAUSurfaceAccessNV (GLvdpauSurfaceNV surface, GLenum access); +GLAPI void APIENTRY glVDPAUMapSurfacesNV (GLsizei numSurfaces, const GLvdpauSurfaceNV *surfaces); +GLAPI void APIENTRY glVDPAUUnmapSurfacesNV (GLsizei numSurface, const GLvdpauSurfaceNV *surfaces); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVDPAUINITNVPROC) (const GLvoid *vdpDevice, const GLvoid *getProcAddress); +typedef void (APIENTRYP PFNGLVDPAUFININVPROC) (void); +typedef GLvdpauSurfaceNV (APIENTRYP PFNGLVDPAUREGISTERVIDEOSURFACENVPROC) (GLvoid *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); +typedef GLvdpauSurfaceNV (APIENTRYP PFNGLVDPAUREGISTEROUTPUTSURFACENVPROC) (GLvoid *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); +typedef void (APIENTRYP PFNGLVDPAUISSURFACENVPROC) (GLvdpauSurfaceNV surface); +typedef void (APIENTRYP PFNGLVDPAUUNREGISTERSURFACENVPROC) (GLvdpauSurfaceNV surface); +typedef void (APIENTRYP PFNGLVDPAUGETSURFACEIVNVPROC) (GLvdpauSurfaceNV surface, GLenum pname, GLsizei bufSize, GLsizei *length, GLint *values); +typedef void (APIENTRYP PFNGLVDPAUSURFACEACCESSNVPROC) (GLvdpauSurfaceNV surface, GLenum access); +typedef void (APIENTRYP PFNGLVDPAUMAPSURFACESNVPROC) (GLsizei numSurfaces, const GLvdpauSurfaceNV *surfaces); +typedef void (APIENTRYP PFNGLVDPAUUNMAPSURFACESNVPROC) (GLsizei numSurface, const GLvdpauSurfaceNV *surfaces); +#endif + +#ifndef GL_AMD_transform_feedback3_lines_triangles +#define GL_AMD_transform_feedback3_lines_triangles 1 +#endif + + +#ifdef __cplusplus +} +#endif + +#endif +/* *INDENT-ON* */ +#endif /* NO_SDL_GLEXT */ + +#endif /* !__IPHONEOS__ */ + +#endif /* _SDL_opengl_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_opengles.h b/external/SDL2/SDL_opengles.h new file mode 100644 index 0000000..f33e190 --- /dev/null +++ b/external/SDL2/SDL_opengles.h @@ -0,0 +1,38 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_opengles.h + * + * This is a simple file to encapsulate the OpenGL ES 1.X API headers. + */ + +#ifdef __IPHONEOS__ +#include +#include +#else +#include +#include +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif diff --git a/external/SDL2/SDL_opengles2.h b/external/SDL2/SDL_opengles2.h new file mode 100644 index 0000000..3d172c3 --- /dev/null +++ b/external/SDL2/SDL_opengles2.h @@ -0,0 +1,38 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_opengles.h + * + * This is a simple file to encapsulate the OpenGL ES 2.0 API headers. + */ + +#ifdef __IPHONEOS__ +#include +#include +#else +#include +#include +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif diff --git a/external/SDL2/SDL_pixels.h b/external/SDL2/SDL_pixels.h new file mode 100644 index 0000000..e732152 --- /dev/null +++ b/external/SDL2/SDL_pixels.h @@ -0,0 +1,431 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_pixels.h + * + * Header for the enumerated pixel format definitions. + */ + +#ifndef _SDL_pixels_h +#define _SDL_pixels_h + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \name Transparency definitions + * + * These define alpha as the opacity of a surface. + */ +/*@{*/ +#define SDL_ALPHA_OPAQUE 255 +#define SDL_ALPHA_TRANSPARENT 0 +/*@}*/ + +/** Pixel type. */ +enum +{ + SDL_PIXELTYPE_UNKNOWN, + SDL_PIXELTYPE_INDEX1, + SDL_PIXELTYPE_INDEX4, + SDL_PIXELTYPE_INDEX8, + SDL_PIXELTYPE_PACKED8, + SDL_PIXELTYPE_PACKED16, + SDL_PIXELTYPE_PACKED32, + SDL_PIXELTYPE_ARRAYU8, + SDL_PIXELTYPE_ARRAYU16, + SDL_PIXELTYPE_ARRAYU32, + SDL_PIXELTYPE_ARRAYF16, + SDL_PIXELTYPE_ARRAYF32 +}; + +/** Bitmap pixel order, high bit -> low bit. */ +enum +{ + SDL_BITMAPORDER_NONE, + SDL_BITMAPORDER_4321, + SDL_BITMAPORDER_1234 +}; + +/** Packed component order, high bit -> low bit. */ +enum +{ + SDL_PACKEDORDER_NONE, + SDL_PACKEDORDER_XRGB, + SDL_PACKEDORDER_RGBX, + SDL_PACKEDORDER_ARGB, + SDL_PACKEDORDER_RGBA, + SDL_PACKEDORDER_XBGR, + SDL_PACKEDORDER_BGRX, + SDL_PACKEDORDER_ABGR, + SDL_PACKEDORDER_BGRA +}; + +/** Array component order, low byte -> high byte. */ +enum +{ + SDL_ARRAYORDER_NONE, + SDL_ARRAYORDER_RGB, + SDL_ARRAYORDER_RGBA, + SDL_ARRAYORDER_ARGB, + SDL_ARRAYORDER_BGR, + SDL_ARRAYORDER_BGRA, + SDL_ARRAYORDER_ABGR +}; + +/** Packed component layout. */ +enum +{ + SDL_PACKEDLAYOUT_NONE, + SDL_PACKEDLAYOUT_332, + SDL_PACKEDLAYOUT_4444, + SDL_PACKEDLAYOUT_1555, + SDL_PACKEDLAYOUT_5551, + SDL_PACKEDLAYOUT_565, + SDL_PACKEDLAYOUT_8888, + SDL_PACKEDLAYOUT_2101010, + SDL_PACKEDLAYOUT_1010102 +}; + +#define SDL_DEFINE_PIXELFOURCC(A, B, C, D) SDL_FOURCC(A, B, C, D) + +#define SDL_DEFINE_PIXELFORMAT(type, order, layout, bits, bytes) \ + ((1 << 28) | ((type) << 24) | ((order) << 20) | ((layout) << 16) | \ + ((bits) << 8) | ((bytes) << 0)) + +#define SDL_PIXELFLAG(X) (((X) >> 28) & 0x0F) +#define SDL_PIXELTYPE(X) (((X) >> 24) & 0x0F) +#define SDL_PIXELORDER(X) (((X) >> 20) & 0x0F) +#define SDL_PIXELLAYOUT(X) (((X) >> 16) & 0x0F) +#define SDL_BITSPERPIXEL(X) (((X) >> 8) & 0xFF) +#define SDL_BYTESPERPIXEL(X) \ + (SDL_ISPIXELFORMAT_FOURCC(X) ? \ + ((((X) == SDL_PIXELFORMAT_YUY2) || \ + ((X) == SDL_PIXELFORMAT_UYVY) || \ + ((X) == SDL_PIXELFORMAT_YVYU)) ? 2 : 1) : (((X) >> 0) & 0xFF)) + +#define SDL_ISPIXELFORMAT_INDEXED(format) \ + (!SDL_ISPIXELFORMAT_FOURCC(format) && \ + ((SDL_PIXELTYPE(format) == SDL_PIXELTYPE_INDEX1) || \ + (SDL_PIXELTYPE(format) == SDL_PIXELTYPE_INDEX4) || \ + (SDL_PIXELTYPE(format) == SDL_PIXELTYPE_INDEX8))) + +#define SDL_ISPIXELFORMAT_ALPHA(format) \ + (!SDL_ISPIXELFORMAT_FOURCC(format) && \ + ((SDL_PIXELORDER(format) == SDL_PACKEDORDER_ARGB) || \ + (SDL_PIXELORDER(format) == SDL_PACKEDORDER_RGBA) || \ + (SDL_PIXELORDER(format) == SDL_PACKEDORDER_ABGR) || \ + (SDL_PIXELORDER(format) == SDL_PACKEDORDER_BGRA))) + +/* The flag is set to 1 because 0x1? is not in the printable ASCII range */ +#define SDL_ISPIXELFORMAT_FOURCC(format) \ + ((format) && (SDL_PIXELFLAG(format) != 1)) + +/* Note: If you modify this list, update SDL_GetPixelFormatName() */ +enum +{ + SDL_PIXELFORMAT_UNKNOWN, + SDL_PIXELFORMAT_INDEX1LSB = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX1, SDL_BITMAPORDER_4321, 0, + 1, 0), + SDL_PIXELFORMAT_INDEX1MSB = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX1, SDL_BITMAPORDER_1234, 0, + 1, 0), + SDL_PIXELFORMAT_INDEX4LSB = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX4, SDL_BITMAPORDER_4321, 0, + 4, 0), + SDL_PIXELFORMAT_INDEX4MSB = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX4, SDL_BITMAPORDER_1234, 0, + 4, 0), + SDL_PIXELFORMAT_INDEX8 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX8, 0, 0, 8, 1), + SDL_PIXELFORMAT_RGB332 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED8, SDL_PACKEDORDER_XRGB, + SDL_PACKEDLAYOUT_332, 8, 1), + SDL_PIXELFORMAT_RGB444 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XRGB, + SDL_PACKEDLAYOUT_4444, 12, 2), + SDL_PIXELFORMAT_RGB555 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XRGB, + SDL_PACKEDLAYOUT_1555, 15, 2), + SDL_PIXELFORMAT_BGR555 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XBGR, + SDL_PACKEDLAYOUT_1555, 15, 2), + SDL_PIXELFORMAT_ARGB4444 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_ARGB, + SDL_PACKEDLAYOUT_4444, 16, 2), + SDL_PIXELFORMAT_RGBA4444 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_RGBA, + SDL_PACKEDLAYOUT_4444, 16, 2), + SDL_PIXELFORMAT_ABGR4444 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_ABGR, + SDL_PACKEDLAYOUT_4444, 16, 2), + SDL_PIXELFORMAT_BGRA4444 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_BGRA, + SDL_PACKEDLAYOUT_4444, 16, 2), + SDL_PIXELFORMAT_ARGB1555 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_ARGB, + SDL_PACKEDLAYOUT_1555, 16, 2), + SDL_PIXELFORMAT_RGBA5551 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_RGBA, + SDL_PACKEDLAYOUT_5551, 16, 2), + SDL_PIXELFORMAT_ABGR1555 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_ABGR, + SDL_PACKEDLAYOUT_1555, 16, 2), + SDL_PIXELFORMAT_BGRA5551 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_BGRA, + SDL_PACKEDLAYOUT_5551, 16, 2), + SDL_PIXELFORMAT_RGB565 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XRGB, + SDL_PACKEDLAYOUT_565, 16, 2), + SDL_PIXELFORMAT_BGR565 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XBGR, + SDL_PACKEDLAYOUT_565, 16, 2), + SDL_PIXELFORMAT_RGB24 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_ARRAYU8, SDL_ARRAYORDER_RGB, 0, + 24, 3), + SDL_PIXELFORMAT_BGR24 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_ARRAYU8, SDL_ARRAYORDER_BGR, 0, + 24, 3), + SDL_PIXELFORMAT_RGB888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_XRGB, + SDL_PACKEDLAYOUT_8888, 24, 4), + SDL_PIXELFORMAT_RGBX8888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_RGBX, + SDL_PACKEDLAYOUT_8888, 24, 4), + SDL_PIXELFORMAT_BGR888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_XBGR, + SDL_PACKEDLAYOUT_8888, 24, 4), + SDL_PIXELFORMAT_BGRX8888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_BGRX, + SDL_PACKEDLAYOUT_8888, 24, 4), + SDL_PIXELFORMAT_ARGB8888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_ARGB, + SDL_PACKEDLAYOUT_8888, 32, 4), + SDL_PIXELFORMAT_RGBA8888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_RGBA, + SDL_PACKEDLAYOUT_8888, 32, 4), + SDL_PIXELFORMAT_ABGR8888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_ABGR, + SDL_PACKEDLAYOUT_8888, 32, 4), + SDL_PIXELFORMAT_BGRA8888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_BGRA, + SDL_PACKEDLAYOUT_8888, 32, 4), + SDL_PIXELFORMAT_ARGB2101010 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_ARGB, + SDL_PACKEDLAYOUT_2101010, 32, 4), + + SDL_PIXELFORMAT_YV12 = /**< Planar mode: Y + V + U (3 planes) */ + SDL_DEFINE_PIXELFOURCC('Y', 'V', '1', '2'), + SDL_PIXELFORMAT_IYUV = /**< Planar mode: Y + U + V (3 planes) */ + SDL_DEFINE_PIXELFOURCC('I', 'Y', 'U', 'V'), + SDL_PIXELFORMAT_YUY2 = /**< Packed mode: Y0+U0+Y1+V0 (1 plane) */ + SDL_DEFINE_PIXELFOURCC('Y', 'U', 'Y', '2'), + SDL_PIXELFORMAT_UYVY = /**< Packed mode: U0+Y0+V0+Y1 (1 plane) */ + SDL_DEFINE_PIXELFOURCC('U', 'Y', 'V', 'Y'), + SDL_PIXELFORMAT_YVYU = /**< Packed mode: Y0+V0+Y1+U0 (1 plane) */ + SDL_DEFINE_PIXELFOURCC('Y', 'V', 'Y', 'U') +}; + +typedef struct SDL_Color +{ + Uint8 r; + Uint8 g; + Uint8 b; + Uint8 a; +} SDL_Color; +#define SDL_Colour SDL_Color + +typedef struct SDL_Palette +{ + int ncolors; + SDL_Color *colors; + Uint32 version; + int refcount; +} SDL_Palette; + +/** + * \note Everything in the pixel format structure is read-only. + */ +typedef struct SDL_PixelFormat +{ + Uint32 format; + SDL_Palette *palette; + Uint8 BitsPerPixel; + Uint8 BytesPerPixel; + Uint8 padding[2]; + Uint32 Rmask; + Uint32 Gmask; + Uint32 Bmask; + Uint32 Amask; + Uint8 Rloss; + Uint8 Gloss; + Uint8 Bloss; + Uint8 Aloss; + Uint8 Rshift; + Uint8 Gshift; + Uint8 Bshift; + Uint8 Ashift; + int refcount; + struct SDL_PixelFormat *next; +} SDL_PixelFormat; + +/** + * \brief Get the human readable name of a pixel format + */ +extern DECLSPEC const char* SDLCALL SDL_GetPixelFormatName(Uint32 format); + +/** + * \brief Convert one of the enumerated pixel formats to a bpp and RGBA masks. + * + * \return SDL_TRUE, or SDL_FALSE if the conversion wasn't possible. + * + * \sa SDL_MasksToPixelFormatEnum() + */ +extern DECLSPEC SDL_bool SDLCALL SDL_PixelFormatEnumToMasks(Uint32 format, + int *bpp, + Uint32 * Rmask, + Uint32 * Gmask, + Uint32 * Bmask, + Uint32 * Amask); + +/** + * \brief Convert a bpp and RGBA masks to an enumerated pixel format. + * + * \return The pixel format, or ::SDL_PIXELFORMAT_UNKNOWN if the conversion + * wasn't possible. + * + * \sa SDL_PixelFormatEnumToMasks() + */ +extern DECLSPEC Uint32 SDLCALL SDL_MasksToPixelFormatEnum(int bpp, + Uint32 Rmask, + Uint32 Gmask, + Uint32 Bmask, + Uint32 Amask); + +/** + * \brief Create an SDL_PixelFormat structure from a pixel format enum. + */ +extern DECLSPEC SDL_PixelFormat * SDLCALL SDL_AllocFormat(Uint32 pixel_format); + +/** + * \brief Free an SDL_PixelFormat structure. + */ +extern DECLSPEC void SDLCALL SDL_FreeFormat(SDL_PixelFormat *format); + +/** + * \brief Create a palette structure with the specified number of color + * entries. + * + * \return A new palette, or NULL if there wasn't enough memory. + * + * \note The palette entries are initialized to white. + * + * \sa SDL_FreePalette() + */ +extern DECLSPEC SDL_Palette *SDLCALL SDL_AllocPalette(int ncolors); + +/** + * \brief Set the palette for a pixel format structure. + */ +extern DECLSPEC int SDLCALL SDL_SetPixelFormatPalette(SDL_PixelFormat * format, + SDL_Palette *palette); + +/** + * \brief Set a range of colors in a palette. + * + * \param palette The palette to modify. + * \param colors An array of colors to copy into the palette. + * \param firstcolor The index of the first palette entry to modify. + * \param ncolors The number of entries to modify. + * + * \return 0 on success, or -1 if not all of the colors could be set. + */ +extern DECLSPEC int SDLCALL SDL_SetPaletteColors(SDL_Palette * palette, + const SDL_Color * colors, + int firstcolor, int ncolors); + +/** + * \brief Free a palette created with SDL_AllocPalette(). + * + * \sa SDL_AllocPalette() + */ +extern DECLSPEC void SDLCALL SDL_FreePalette(SDL_Palette * palette); + +/** + * \brief Maps an RGB triple to an opaque pixel value for a given pixel format. + * + * \sa SDL_MapRGBA + */ +extern DECLSPEC Uint32 SDLCALL SDL_MapRGB(const SDL_PixelFormat * format, + Uint8 r, Uint8 g, Uint8 b); + +/** + * \brief Maps an RGBA quadruple to a pixel value for a given pixel format. + * + * \sa SDL_MapRGB + */ +extern DECLSPEC Uint32 SDLCALL SDL_MapRGBA(const SDL_PixelFormat * format, + Uint8 r, Uint8 g, Uint8 b, + Uint8 a); + +/** + * \brief Get the RGB components from a pixel of the specified format. + * + * \sa SDL_GetRGBA + */ +extern DECLSPEC void SDLCALL SDL_GetRGB(Uint32 pixel, + const SDL_PixelFormat * format, + Uint8 * r, Uint8 * g, Uint8 * b); + +/** + * \brief Get the RGBA components from a pixel of the specified format. + * + * \sa SDL_GetRGB + */ +extern DECLSPEC void SDLCALL SDL_GetRGBA(Uint32 pixel, + const SDL_PixelFormat * format, + Uint8 * r, Uint8 * g, Uint8 * b, + Uint8 * a); + +/** + * \brief Calculate a 256 entry gamma ramp for a gamma value. + */ +extern DECLSPEC void SDLCALL SDL_CalculateGammaRamp(float gamma, Uint16 * ramp); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_pixels_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_platform.h b/external/SDL2/SDL_platform.h new file mode 100644 index 0000000..d03bd38 --- /dev/null +++ b/external/SDL2/SDL_platform.h @@ -0,0 +1,149 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_platform.h + * + * Try to get a standard set of platform defines. + */ + +#ifndef _SDL_platform_h +#define _SDL_platform_h + +#if defined(_AIX) +#undef __AIX__ +#define __AIX__ 1 +#endif +#if defined(__BEOS__) +#undef __BEOS__ +#define __BEOS__ 1 +#endif +#if defined(__HAIKU__) +#undef __HAIKU__ +#define __HAIKU__ 1 +#endif +#if defined(bsdi) || defined(__bsdi) || defined(__bsdi__) +#undef __BSDI__ +#define __BSDI__ 1 +#endif +#if defined(_arch_dreamcast) +#undef __DREAMCAST__ +#define __DREAMCAST__ 1 +#endif +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) +#undef __FREEBSD__ +#define __FREEBSD__ 1 +#endif +#if defined(hpux) || defined(__hpux) || defined(__hpux__) +#undef __HPUX__ +#define __HPUX__ 1 +#endif +#if defined(sgi) || defined(__sgi) || defined(__sgi__) || defined(_SGI_SOURCE) +#undef __IRIX__ +#define __IRIX__ 1 +#endif +#if defined(linux) || defined(__linux) || defined(__linux__) +#undef __LINUX__ +#define __LINUX__ 1 +#endif +#if defined(ANDROID) +#undef __ANDROID__ +#undef __LINUX__ /*do we need to do this?*/ +#define __ANDROID__ 1 +#endif + +#if defined(__APPLE__) +/* lets us know what version of Mac OS X we're compiling on */ +#include "AvailabilityMacros.h" +#include "TargetConditionals.h" +#if TARGET_OS_IPHONE +/* if compiling for iPhone */ +#undef __IPHONEOS__ +#define __IPHONEOS__ 1 +#undef __MACOSX__ +#else +/* if not compiling for iPhone */ +#undef __MACOSX__ +#define __MACOSX__ 1 +#endif /* TARGET_OS_IPHONE */ +#endif /* defined(__APPLE__) */ + +#if defined(__NetBSD__) +#undef __NETBSD__ +#define __NETBSD__ 1 +#endif +#if defined(__OpenBSD__) +#undef __OPENBSD__ +#define __OPENBSD__ 1 +#endif +#if defined(__OS2__) +#undef __OS2__ +#define __OS2__ 1 +#endif +#if defined(osf) || defined(__osf) || defined(__osf__) || defined(_OSF_SOURCE) +#undef __OSF__ +#define __OSF__ 1 +#endif +#if defined(__QNXNTO__) +#undef __QNXNTO__ +#define __QNXNTO__ 1 +#endif +#if defined(riscos) || defined(__riscos) || defined(__riscos__) +#undef __RISCOS__ +#define __RISCOS__ 1 +#endif +#if defined(__SVR4) +#undef __SOLARIS__ +#define __SOLARIS__ 1 +#endif +#if defined(WIN32) || defined(_WIN32) +#undef __WIN32__ +#define __WIN32__ 1 +#endif +#if defined(__PSP__) +#undef __PSP__ +#define __PSP__ 1 +#endif + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \brief Gets the name of the platform. + */ +extern DECLSPEC const char * SDLCALL SDL_GetPlatform (void); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_platform_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_power.h b/external/SDL2/SDL_power.h new file mode 100644 index 0000000..d796aee --- /dev/null +++ b/external/SDL2/SDL_power.h @@ -0,0 +1,79 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef _SDL_power_h +#define _SDL_power_h + +/** + * \file SDL_power.h + * + * Header for the SDL power management routines. + */ + +#include "SDL_stdinc.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \brief The basic state for the system's power supply. + */ +typedef enum +{ + SDL_POWERSTATE_UNKNOWN, /**< cannot determine power status */ + SDL_POWERSTATE_ON_BATTERY, /**< Not plugged in, running on the battery */ + SDL_POWERSTATE_NO_BATTERY, /**< Plugged in, no battery available */ + SDL_POWERSTATE_CHARGING, /**< Plugged in, charging battery */ + SDL_POWERSTATE_CHARGED /**< Plugged in, battery charged */ +} SDL_PowerState; + + +/** + * \brief Get the current power supply details. + * + * \param secs Seconds of battery life left. You can pass a NULL here if + * you don't care. Will return -1 if we can't determine a + * value, or we're not running on a battery. + * + * \param pct Percentage of battery life left, between 0 and 100. You can + * pass a NULL here if you don't care. Will return -1 if we + * can't determine a value, or we're not running on a battery. + * + * \return The state of the battery (if any). + */ +extern DECLSPEC SDL_PowerState SDLCALL SDL_GetPowerInfo(int *secs, int *pct); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_power_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_quit.h b/external/SDL2/SDL_quit.h new file mode 100644 index 0000000..5c7d343 --- /dev/null +++ b/external/SDL2/SDL_quit.h @@ -0,0 +1,58 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_quit.h + * + * Include file for SDL quit event handling. + */ + +#ifndef _SDL_quit_h +#define _SDL_quit_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +/** + * \file SDL_quit.h + * + * An ::SDL_QUIT event is generated when the user tries to close the application + * window. If it is ignored or filtered out, the window will remain open. + * If it is not ignored or filtered, it is queued normally and the window + * is allowed to close. When the window is closed, screen updates will + * complete, but have no effect. + * + * SDL_Init() installs signal handlers for SIGINT (keyboard interrupt) + * and SIGTERM (system termination request), if handlers do not already + * exist, that generate ::SDL_QUIT events as well. There is no way + * to determine the cause of an ::SDL_QUIT event, but setting a signal + * handler in your application will override the default generation of + * quit events for that signal. + * + * \sa SDL_Quit() + */ + +/* There are no functions directly affecting the quit event */ + +#define SDL_QuitRequested() \ + (SDL_PumpEvents(), (SDL_PeepEvents(NULL,0,SDL_PEEKEVENT,SDL_QUIT,SDL_QUIT) > 0)) + +#endif /* _SDL_quit_h */ diff --git a/external/SDL2/SDL_rect.h b/external/SDL2/SDL_rect.h new file mode 100644 index 0000000..a509c70 --- /dev/null +++ b/external/SDL2/SDL_rect.h @@ -0,0 +1,137 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_rect.h + * + * Header file for SDL_rect definition and management functions. + */ + +#ifndef _SDL_rect_h +#define _SDL_rect_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_pixels.h" +#include "SDL_rwops.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \brief The structure that defines a point + * + * \sa SDL_EnclosePoints + */ +typedef struct +{ + int x; + int y; +} SDL_Point; + +/** + * \brief A rectangle, with the origin at the upper left. + * + * \sa SDL_RectEmpty + * \sa SDL_RectEquals + * \sa SDL_HasIntersection + * \sa SDL_IntersectRect + * \sa SDL_UnionRect + * \sa SDL_EnclosePoints + */ +typedef struct SDL_Rect +{ + int x, y; + int w, h; +} SDL_Rect; + +/** + * \brief Returns true if the rectangle has no area. + */ +#define SDL_RectEmpty(X) ((!(X)) || ((X)->w <= 0) || ((X)->h <= 0)) + +/** + * \brief Returns true if the two rectangles are equal. + */ +#define SDL_RectEquals(A, B) (((A)) && ((B)) && \ + ((A)->x == (B)->x) && ((A)->y == (B)->y) && \ + ((A)->w == (B)->w) && ((A)->h == (B)->h)) + +/** + * \brief Determine whether two rectangles intersect. + * + * \return SDL_TRUE if there is an intersection, SDL_FALSE otherwise. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasIntersection(const SDL_Rect * A, + const SDL_Rect * B); + +/** + * \brief Calculate the intersection of two rectangles. + * + * \return SDL_TRUE if there is an intersection, SDL_FALSE otherwise. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_IntersectRect(const SDL_Rect * A, + const SDL_Rect * B, + SDL_Rect * result); + +/** + * \brief Calculate the union of two rectangles. + */ +extern DECLSPEC void SDLCALL SDL_UnionRect(const SDL_Rect * A, + const SDL_Rect * B, + SDL_Rect * result); + +/** + * \brief Calculate a minimal rectangle enclosing a set of points + * + * \return SDL_TRUE if any points were within the clipping rect + */ +extern DECLSPEC SDL_bool SDLCALL SDL_EnclosePoints(const SDL_Point * points, + int count, + const SDL_Rect * clip, + SDL_Rect * result); + +/** + * \brief Calculate the intersection of a rectangle and line segment. + * + * \return SDL_TRUE if there is an intersection, SDL_FALSE otherwise. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_IntersectRectAndLine(const SDL_Rect * + rect, int *X1, + int *Y1, int *X2, + int *Y2); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_rect_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_render.h b/external/SDL2/SDL_render.h new file mode 100644 index 0000000..591de81 --- /dev/null +++ b/external/SDL2/SDL_render.h @@ -0,0 +1,788 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_render.h + * + * Header file for SDL 2D rendering functions. + * + * This API supports the following features: + * * single pixel points + * * single pixel lines + * * filled rectangles + * * texture images + * + * The primitives may be drawn in opaque, blended, or additive modes. + * + * The texture images may be drawn in opaque, blended, or additive modes. + * They can have an additional color tint or alpha modulation applied to + * them, and may also be stretched with linear interpolation. + * + * This API is designed to accelerate simple 2D operations. You may + * want more functionality such as rotation and particle effects and + * in that case you should use SDL's OpenGL/Direct3D support or one + * of the many good 3D engines. + */ + +#ifndef _SDL_render_h +#define _SDL_render_h + +#include "SDL_stdinc.h" +#include "SDL_rect.h" +#include "SDL_video.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \brief Flags used when creating a rendering context + */ +typedef enum +{ + SDL_RENDERER_SOFTWARE = 0x00000001, /**< The renderer is a software fallback */ + SDL_RENDERER_ACCELERATED = 0x00000002, /**< The renderer uses hardware + acceleration */ + SDL_RENDERER_PRESENTVSYNC = 0x00000004, /**< Present is synchronized + with the refresh rate */ + SDL_RENDERER_TARGETTEXTURE = 0x00000008 /**< The renderer supports + rendering to texture */ +} SDL_RendererFlags; + +/** + * \brief Information on the capabilities of a render driver or context. + */ +typedef struct SDL_RendererInfo +{ + const char *name; /**< The name of the renderer */ + Uint32 flags; /**< Supported ::SDL_RendererFlags */ + Uint32 num_texture_formats; /**< The number of available texture formats */ + Uint32 texture_formats[16]; /**< The available texture formats */ + int max_texture_width; /**< The maximimum texture width */ + int max_texture_height; /**< The maximimum texture height */ +} SDL_RendererInfo; + +/** + * \brief The access pattern allowed for a texture. + */ +typedef enum +{ + SDL_TEXTUREACCESS_STATIC, /**< Changes rarely, not lockable */ + SDL_TEXTUREACCESS_STREAMING, /**< Changes frequently, lockable */ + SDL_TEXTUREACCESS_TARGET /**< Texture can be used as a render target */ +} SDL_TextureAccess; + +/** + * \brief The texture channel modulation used in SDL_RenderCopy(). + */ +typedef enum +{ + SDL_TEXTUREMODULATE_NONE = 0x00000000, /**< No modulation */ + SDL_TEXTUREMODULATE_COLOR = 0x00000001, /**< srcC = srcC * color */ + SDL_TEXTUREMODULATE_ALPHA = 0x00000002 /**< srcA = srcA * alpha */ +} SDL_TextureModulate; + +/** + * \brief Flip constants for SDL_RenderCopyEx + */ +typedef enum +{ + SDL_FLIP_NONE = 0x00000000, /**< Do not flip */ + SDL_FLIP_HORIZONTAL = 0x00000001, /**< flip horizontally */ + SDL_FLIP_VERTICAL = 0x00000002 /**< flip vertically */ +} SDL_RendererFlip; + +/** + * \brief A structure representing rendering state + */ +struct SDL_Renderer; +typedef struct SDL_Renderer SDL_Renderer; + +/** + * \brief An efficient driver-specific representation of pixel data + */ +struct SDL_Texture; +typedef struct SDL_Texture SDL_Texture; + + +/* Function prototypes */ + +/** + * \brief Get the number of 2D rendering drivers available for the current + * display. + * + * A render driver is a set of code that handles rendering and texture + * management on a particular display. Normally there is only one, but + * some drivers may have several available with different capabilities. + * + * \sa SDL_GetRenderDriverInfo() + * \sa SDL_CreateRenderer() + */ +extern DECLSPEC int SDLCALL SDL_GetNumRenderDrivers(void); + +/** + * \brief Get information about a specific 2D rendering driver for the current + * display. + * + * \param index The index of the driver to query information about. + * \param info A pointer to an SDL_RendererInfo struct to be filled with + * information on the rendering driver. + * + * \return 0 on success, -1 if the index was out of range. + * + * \sa SDL_CreateRenderer() + */ +extern DECLSPEC int SDLCALL SDL_GetRenderDriverInfo(int index, + SDL_RendererInfo * info); + +/** + * \brief Create a window and default renderer + * + * \param width The width of the window + * \param height The height of the window + * \param window_flags The flags used to create the window + * \param window A pointer filled with the window, or NULL on error + * \param renderer A pointer filled with the renderer, or NULL on error + * + * \return 0 on success, or -1 on error + */ +extern DECLSPEC int SDLCALL SDL_CreateWindowAndRenderer( + int width, int height, Uint32 window_flags, + SDL_Window **window, SDL_Renderer **renderer); + + +/** + * \brief Create a 2D rendering context for a window. + * + * \param window The window where rendering is displayed. + * \param index The index of the rendering driver to initialize, or -1 to + * initialize the first one supporting the requested flags. + * \param flags ::SDL_RendererFlags. + * + * \return A valid rendering context or NULL if there was an error. + * + * \sa SDL_CreateSoftwareRenderer() + * \sa SDL_GetRendererInfo() + * \sa SDL_DestroyRenderer() + */ +extern DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRenderer(SDL_Window * window, + int index, Uint32 flags); + +/** + * \brief Create a 2D software rendering context for a surface. + * + * \param surface The surface where rendering is done. + * + * \return A valid rendering context or NULL if there was an error. + * + * \sa SDL_CreateRenderer() + * \sa SDL_DestroyRenderer() + */ +extern DECLSPEC SDL_Renderer * SDLCALL SDL_CreateSoftwareRenderer(SDL_Surface * surface); + +/** + * \brief Get the renderer associated with a window. + */ +extern DECLSPEC SDL_Renderer * SDLCALL SDL_GetRenderer(SDL_Window * window); + +/** + * \brief Get information about a rendering context. + */ +extern DECLSPEC int SDLCALL SDL_GetRendererInfo(SDL_Renderer * renderer, + SDL_RendererInfo * info); + +/** + * \brief Create a texture for a rendering context. + * + * \param format The format of the texture. + * \param access One of the enumerated values in ::SDL_TextureAccess. + * \param w The width of the texture in pixels. + * \param h The height of the texture in pixels. + * + * \return The created texture is returned, or 0 if no rendering context was + * active, the format was unsupported, or the width or height were out + * of range. + * + * \sa SDL_QueryTexture() + * \sa SDL_UpdateTexture() + * \sa SDL_DestroyTexture() + */ +extern DECLSPEC SDL_Texture * SDLCALL SDL_CreateTexture(SDL_Renderer * renderer, + Uint32 format, + int access, int w, + int h); + +/** + * \brief Create a texture from an existing surface. + * + * \param surface The surface containing pixel data used to fill the texture. + * + * \return The created texture is returned, or 0 on error. + * + * \note The surface is not modified or freed by this function. + * + * \sa SDL_QueryTexture() + * \sa SDL_DestroyTexture() + */ +extern DECLSPEC SDL_Texture * SDLCALL SDL_CreateTextureFromSurface(SDL_Renderer * renderer, SDL_Surface * surface); + +/** + * \brief Query the attributes of a texture + * + * \param texture A texture to be queried. + * \param format A pointer filled in with the raw format of the texture. The + * actual format may differ, but pixel transfers will use this + * format. + * \param access A pointer filled in with the actual access to the texture. + * \param w A pointer filled in with the width of the texture in pixels. + * \param h A pointer filled in with the height of the texture in pixels. + * + * \return 0 on success, or -1 if the texture is not valid. + */ +extern DECLSPEC int SDLCALL SDL_QueryTexture(SDL_Texture * texture, + Uint32 * format, int *access, + int *w, int *h); + +/** + * \brief Set an additional color value used in render copy operations. + * + * \param texture The texture to update. + * \param r The red color value multiplied into copy operations. + * \param g The green color value multiplied into copy operations. + * \param b The blue color value multiplied into copy operations. + * + * \return 0 on success, or -1 if the texture is not valid or color modulation + * is not supported. + * + * \sa SDL_GetTextureColorMod() + */ +extern DECLSPEC int SDLCALL SDL_SetTextureColorMod(SDL_Texture * texture, + Uint8 r, Uint8 g, Uint8 b); + + +/** + * \brief Get the additional color value used in render copy operations. + * + * \param texture The texture to query. + * \param r A pointer filled in with the current red color value. + * \param g A pointer filled in with the current green color value. + * \param b A pointer filled in with the current blue color value. + * + * \return 0 on success, or -1 if the texture is not valid. + * + * \sa SDL_SetTextureColorMod() + */ +extern DECLSPEC int SDLCALL SDL_GetTextureColorMod(SDL_Texture * texture, + Uint8 * r, Uint8 * g, + Uint8 * b); + +/** + * \brief Set an additional alpha value used in render copy operations. + * + * \param texture The texture to update. + * \param alpha The alpha value multiplied into copy operations. + * + * \return 0 on success, or -1 if the texture is not valid or alpha modulation + * is not supported. + * + * \sa SDL_GetTextureAlphaMod() + */ +extern DECLSPEC int SDLCALL SDL_SetTextureAlphaMod(SDL_Texture * texture, + Uint8 alpha); + +/** + * \brief Get the additional alpha value used in render copy operations. + * + * \param texture The texture to query. + * \param alpha A pointer filled in with the current alpha value. + * + * \return 0 on success, or -1 if the texture is not valid. + * + * \sa SDL_SetTextureAlphaMod() + */ +extern DECLSPEC int SDLCALL SDL_GetTextureAlphaMod(SDL_Texture * texture, + Uint8 * alpha); + +/** + * \brief Set the blend mode used for texture copy operations. + * + * \param texture The texture to update. + * \param blendMode ::SDL_BlendMode to use for texture blending. + * + * \return 0 on success, or -1 if the texture is not valid or the blend mode is + * not supported. + * + * \note If the blend mode is not supported, the closest supported mode is + * chosen. + * + * \sa SDL_GetTextureBlendMode() + */ +extern DECLSPEC int SDLCALL SDL_SetTextureBlendMode(SDL_Texture * texture, + SDL_BlendMode blendMode); + +/** + * \brief Get the blend mode used for texture copy operations. + * + * \param texture The texture to query. + * \param blendMode A pointer filled in with the current blend mode. + * + * \return 0 on success, or -1 if the texture is not valid. + * + * \sa SDL_SetTextureBlendMode() + */ +extern DECLSPEC int SDLCALL SDL_GetTextureBlendMode(SDL_Texture * texture, + SDL_BlendMode *blendMode); + +/** + * \brief Update the given texture rectangle with new pixel data. + * + * \param texture The texture to update + * \param rect A pointer to the rectangle of pixels to update, or NULL to + * update the entire texture. + * \param pixels The raw pixel data. + * \param pitch The number of bytes between rows of pixel data. + * + * \return 0 on success, or -1 if the texture is not valid. + * + * \note This is a fairly slow function. + */ +extern DECLSPEC int SDLCALL SDL_UpdateTexture(SDL_Texture * texture, + const SDL_Rect * rect, + const void *pixels, int pitch); + +/** + * \brief Lock a portion of the texture for write-only pixel access. + * + * \param texture The texture to lock for access, which was created with + * ::SDL_TEXTUREACCESS_STREAMING. + * \param rect A pointer to the rectangle to lock for access. If the rect + * is NULL, the entire texture will be locked. + * \param pixels This is filled in with a pointer to the locked pixels, + * appropriately offset by the locked area. + * \param pitch This is filled in with the pitch of the locked pixels. + * + * \return 0 on success, or -1 if the texture is not valid or was not created with ::SDL_TEXTUREACCESS_STREAMING. + * + * \sa SDL_UnlockTexture() + */ +extern DECLSPEC int SDLCALL SDL_LockTexture(SDL_Texture * texture, + const SDL_Rect * rect, + void **pixels, int *pitch); + +/** + * \brief Unlock a texture, uploading the changes to video memory, if needed. + * + * \sa SDL_LockTexture() + */ +extern DECLSPEC void SDLCALL SDL_UnlockTexture(SDL_Texture * texture); + +/** + * \brief Determines whether a window supports the use of render targets + * + * \param renderer The renderer that will be checked + * + * \return SDL_TRUE if supported, SDL_FALSE if not. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_RenderTargetSupported(SDL_Renderer *renderer); + +/** + * \brief Set a texture as the current rendering target. + * + * \param texture The targeted texture, which must be created with the SDL_TEXTUREACCESS_TARGET flag, or NULL for the default render target + * + * \return 0 on success, or -1 on error + * + * \sa SDL_GetRenderTarget() + */ +extern DECLSPEC int SDLCALL SDL_SetRenderTarget(SDL_Renderer *renderer, + SDL_Texture *texture); + +/** + * \brief Get the current render target or NULL for the default render target. + * + * \return The current render target + * + * \sa SDL_SetRenderTarget() + */ +extern DECLSPEC SDL_Texture * SDLCALL SDL_GetRenderTarget(SDL_Renderer *renderer); + +/** + * \brief Set device independent resolution for rendering + * + * \param w The width of the logical resolution + * \param h The height of the logical resolution + * + * This function uses the viewport and scaling functionality to allow a fixed logical + * resolution for rendering, regardless of the actual output resolution. If the actual + * output resolution doesn't have the same aspect ratio the output rendering will be + * centered within the output display. + * + * If the output display is a window, mouse events in the window will be filtered + * and scaled so they seem to arrive within the logical resolution. + * + * \note If this function results in scaling or subpixel drawing by the + * rendering backend, it will be handled using the appropriate + * quality hints. + * + * \sa SDL_RenderGetLogicalSize() + * \sa SDL_RenderSetScale() + * \sa SDL_RenderSetViewport() + */ +extern DECLSPEC int SDLCALL SDL_RenderSetLogicalSize(SDL_Renderer * renderer, int w, int h); + +/** + * \brief Get device independent resolution for rendering + * + * \param w A pointer filled with the width of the logical resolution + * \param h A pointer filled with the height of the logical resolution + * + * \sa SDL_RenderSetLogicalSize() + */ +extern DECLSPEC void SDLCALL SDL_RenderGetLogicalSize(SDL_Renderer * renderer, int *w, int *y); + +/** + * \brief Set the drawing area for rendering on the current target. + * + * \param rect The rectangle representing the drawing area, or NULL to set the viewport to the entire target. + * + * The x,y of the viewport rect represents the origin for rendering. + * + * \note When the window is resized, the current viewport is automatically + * centered within the new window size. + * + * \sa SDL_RenderGetViewport() + * \sa SDL_RenderSetLogicalSize() + */ +extern DECLSPEC int SDLCALL SDL_RenderSetViewport(SDL_Renderer * renderer, + const SDL_Rect * rect); + +/** + * \brief Get the drawing area for the current target. + * + * \sa SDL_RenderSetViewport() + */ +extern DECLSPEC void SDLCALL SDL_RenderGetViewport(SDL_Renderer * renderer, + SDL_Rect * rect); + +/** + * \brief Set the drawing scale for rendering on the current target. + * + * \param scaleX The horizontal scaling factor + * \param scaleY The vertical scaling factor + * + * The drawing coordinates are scaled by the x/y scaling factors + * before they are used by the renderer. This allows resolution + * independent drawing with a single coordinate system. + * + * \note If this results in scaling or subpixel drawing by the + * rendering backend, it will be handled using the appropriate + * quality hints. For best results use integer scaling factors. + * + * \sa SDL_RenderGetScale() + * \sa SDL_RenderSetLogicalSize() + */ +extern DECLSPEC int SDLCALL SDL_RenderSetScale(SDL_Renderer * renderer, + float scaleX, float scaleY); + +/** + * \brief Get the drawing scale for the current target. + * + * \param scaleX A pointer filled in with the horizontal scaling factor + * \param scaleY A pointer filled in with the vertical scaling factor + * + * \sa SDL_RenderSetScale() + */ +extern DECLSPEC void SDLCALL SDL_RenderGetScale(SDL_Renderer * renderer, + float *scaleX, float *scaleY); + +/** + * \brief Set the color used for drawing operations (Rect, Line and Clear). + * + * \param r The red value used to draw on the rendering target. + * \param g The green value used to draw on the rendering target. + * \param b The blue value used to draw on the rendering target. + * \param a The alpha value used to draw on the rendering target, usually + * ::SDL_ALPHA_OPAQUE (255). + * + * \return 0 on success, or -1 on error + */ +extern DECLSPEC int SDL_SetRenderDrawColor(SDL_Renderer * renderer, + Uint8 r, Uint8 g, Uint8 b, + Uint8 a); + +/** + * \brief Get the color used for drawing operations (Rect, Line and Clear). + * + * \param r A pointer to the red value used to draw on the rendering target. + * \param g A pointer to the green value used to draw on the rendering target. + * \param b A pointer to the blue value used to draw on the rendering target. + * \param a A pointer to the alpha value used to draw on the rendering target, + * usually ::SDL_ALPHA_OPAQUE (255). + * + * \return 0 on success, or -1 on error + */ +extern DECLSPEC int SDL_GetRenderDrawColor(SDL_Renderer * renderer, + Uint8 * r, Uint8 * g, Uint8 * b, + Uint8 * a); + +/** + * \brief Set the blend mode used for drawing operations (Fill and Line). + * + * \param blendMode ::SDL_BlendMode to use for blending. + * + * \return 0 on success, or -1 on error + * + * \note If the blend mode is not supported, the closest supported mode is + * chosen. + * + * \sa SDL_GetRenderDrawBlendMode() + */ +extern DECLSPEC int SDLCALL SDL_SetRenderDrawBlendMode(SDL_Renderer * renderer, + SDL_BlendMode blendMode); + +/** + * \brief Get the blend mode used for drawing operations. + * + * \param blendMode A pointer filled in with the current blend mode. + * + * \return 0 on success, or -1 on error + * + * \sa SDL_SetRenderDrawBlendMode() + */ +extern DECLSPEC int SDLCALL SDL_GetRenderDrawBlendMode(SDL_Renderer * renderer, + SDL_BlendMode *blendMode); + +/** + * \brief Clear the current rendering target with the drawing color + * + * This function clears the entire rendering target, ignoring the viewport. + */ +extern DECLSPEC int SDLCALL SDL_RenderClear(SDL_Renderer * renderer); + +/** + * \brief Draw a point on the current rendering target. + * + * \param x The x coordinate of the point. + * \param y The y coordinate of the point. + * + * \return 0 on success, or -1 on error + */ +extern DECLSPEC int SDLCALL SDL_RenderDrawPoint(SDL_Renderer * renderer, + int x, int y); + +/** + * \brief Draw multiple points on the current rendering target. + * + * \param points The points to draw + * \param count The number of points to draw + * + * \return 0 on success, or -1 on error + */ +extern DECLSPEC int SDLCALL SDL_RenderDrawPoints(SDL_Renderer * renderer, + const SDL_Point * points, + int count); + +/** + * \brief Draw a line on the current rendering target. + * + * \param x1 The x coordinate of the start point. + * \param y1 The y coordinate of the start point. + * \param x2 The x coordinate of the end point. + * \param y2 The y coordinate of the end point. + * + * \return 0 on success, or -1 on error + */ +extern DECLSPEC int SDLCALL SDL_RenderDrawLine(SDL_Renderer * renderer, + int x1, int y1, int x2, int y2); + +/** + * \brief Draw a series of connected lines on the current rendering target. + * + * \param points The points along the lines + * \param count The number of points, drawing count-1 lines + * + * \return 0 on success, or -1 on error + */ +extern DECLSPEC int SDLCALL SDL_RenderDrawLines(SDL_Renderer * renderer, + const SDL_Point * points, + int count); + +/** + * \brief Draw a rectangle on the current rendering target. + * + * \param rect A pointer to the destination rectangle, or NULL to outline the entire rendering target. + * + * \return 0 on success, or -1 on error + */ +extern DECLSPEC int SDLCALL SDL_RenderDrawRect(SDL_Renderer * renderer, + const SDL_Rect * rect); + +/** + * \brief Draw some number of rectangles on the current rendering target. + * + * \param rects A pointer to an array of destination rectangles. + * \param count The number of rectangles. + * + * \return 0 on success, or -1 on error + */ +extern DECLSPEC int SDLCALL SDL_RenderDrawRects(SDL_Renderer * renderer, + const SDL_Rect * rects, + int count); + +/** + * \brief Fill a rectangle on the current rendering target with the drawing color. + * + * \param rect A pointer to the destination rectangle, or NULL for the entire + * rendering target. + * + * \return 0 on success, or -1 on error + */ +extern DECLSPEC int SDLCALL SDL_RenderFillRect(SDL_Renderer * renderer, + const SDL_Rect * rect); + +/** + * \brief Fill some number of rectangles on the current rendering target with the drawing color. + * + * \param rects A pointer to an array of destination rectangles. + * \param count The number of rectangles. + * + * \return 0 on success, or -1 on error + */ +extern DECLSPEC int SDLCALL SDL_RenderFillRects(SDL_Renderer * renderer, + const SDL_Rect * rects, + int count); + +/** + * \brief Copy a portion of the texture to the current rendering target. + * + * \param texture The source texture. + * \param srcrect A pointer to the source rectangle, or NULL for the entire + * texture. + * \param dstrect A pointer to the destination rectangle, or NULL for the + * entire rendering target. + * + * \return 0 on success, or -1 on error + */ +extern DECLSPEC int SDLCALL SDL_RenderCopy(SDL_Renderer * renderer, + SDL_Texture * texture, + const SDL_Rect * srcrect, + const SDL_Rect * dstrect); + +/** + * \brief Copy a portion of the source texture to the current rendering target, rotating it by angle around the given center + * + * \param texture The source texture. + * \param srcrect A pointer to the source rectangle, or NULL for the entire + * texture. + * \param dstrect A pointer to the destination rectangle, or NULL for the + * entire rendering target. + * \param angle An angle in degrees that indicates the rotation that will be applied to dstrect + * \param center A pointer to a point indicating the point around which dstrect will be rotated (if NULL, rotation will be done aroud dstrect.w/2, dstrect.h/2) + * \param flip An SDL_RendererFlip value stating which flipping actions should be performed on the texture + * + * \return 0 on success, or -1 on error + */ +extern DECLSPEC int SDLCALL SDL_RenderCopyEx(SDL_Renderer * renderer, + SDL_Texture * texture, + const SDL_Rect * srcrect, + const SDL_Rect * dstrect, + const double angle, + const SDL_Point *center, + const SDL_RendererFlip flip); + +/** + * \brief Read pixels from the current rendering target. + * + * \param rect A pointer to the rectangle to read, or NULL for the entire + * render target. + * \param format The desired format of the pixel data, or 0 to use the format + * of the rendering target + * \param pixels A pointer to be filled in with the pixel data + * \param pitch The pitch of the pixels parameter. + * + * \return 0 on success, or -1 if pixel reading is not supported. + * + * \warning This is a very slow operation, and should not be used frequently. + */ +extern DECLSPEC int SDLCALL SDL_RenderReadPixels(SDL_Renderer * renderer, + const SDL_Rect * rect, + Uint32 format, + void *pixels, int pitch); + +/** + * \brief Update the screen with rendering performed. + */ +extern DECLSPEC void SDLCALL SDL_RenderPresent(SDL_Renderer * renderer); + +/** + * \brief Destroy the specified texture. + * + * \sa SDL_CreateTexture() + * \sa SDL_CreateTextureFromSurface() + */ +extern DECLSPEC void SDLCALL SDL_DestroyTexture(SDL_Texture * texture); + +/** + * \brief Destroy the rendering context for a window and free associated + * textures. + * + * \sa SDL_CreateRenderer() + */ +extern DECLSPEC void SDLCALL SDL_DestroyRenderer(SDL_Renderer * renderer); + + +/** + * \brief Bind the texture to the current OpenGL/ES/ES2 context for use with + * OpenGL instructions. + * + * \param texture The SDL texture to bind + * \param texw A pointer to a float that will be filled with the texture width + * \param texh A pointer to a float that will be filled with the texture height + * + * \return 0 on success, or -1 if the operation is not supported + */ +extern DECLSPEC int SDLCALL SDL_GL_BindTexture(SDL_Texture *texture, float *texw, float *texh); + +/** + * \brief Unbind a texture from the current OpenGL/ES/ES2 context. + * + * \param texture The SDL texture to unbind + * + * \return 0 on success, or -1 if the operation is not supported + */ +extern DECLSPEC int SDLCALL SDL_GL_UnbindTexture(SDL_Texture *texture); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_render_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_revision.h b/external/SDL2/SDL_revision.h new file mode 100644 index 0000000..d70fd69 --- /dev/null +++ b/external/SDL2/SDL_revision.h @@ -0,0 +1,2 @@ +#define SDL_REVISION "hg-0:aaaaaaaaaaah" +#define SDL_REVISION_NUMBER 0 diff --git a/external/SDL2/SDL_rwops.h b/external/SDL2/SDL_rwops.h new file mode 100644 index 0000000..61c3092 --- /dev/null +++ b/external/SDL2/SDL_rwops.h @@ -0,0 +1,235 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_rwops.h + * + * This file provides a general interface for SDL to read and write + * data streams. It can easily be extended to files, memory, etc. + */ + +#ifndef _SDL_rwops_h +#define _SDL_rwops_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/* RWops Types */ +#define SDL_RWOPS_UNKNOWN 0 /* Unknown stream type */ +#define SDL_RWOPS_WINFILE 1 /* Win32 file */ +#define SDL_RWOPS_STDFILE 2 /* Stdio file */ +#define SDL_RWOPS_JNIFILE 3 /* Android asset */ +#define SDL_RWOPS_MEMORY 4 /* Memory stream */ +#define SDL_RWOPS_MEMORY_RO 5 /* Read-Only memory stream */ + +/** + * This is the read/write operation structure -- very basic. + */ +typedef struct SDL_RWops +{ + /** + * Return the size of the file in this rwops, or -1 if unknown + */ + Sint64 (SDLCALL * size) (struct SDL_RWops * context); + + /** + * Seek to \c offset relative to \c whence, one of stdio's whence values: + * RW_SEEK_SET, RW_SEEK_CUR, RW_SEEK_END + * + * \return the final offset in the data stream, or -1 on error. + */ + Sint64 (SDLCALL * seek) (struct SDL_RWops * context, Sint64 offset, + int whence); + + /** + * Read up to \c maxnum objects each of size \c size from the data + * stream to the area pointed at by \c ptr. + * + * \return the number of objects read, or 0 at error or end of file. + */ + size_t (SDLCALL * read) (struct SDL_RWops * context, void *ptr, + size_t size, size_t maxnum); + + /** + * Write exactly \c num objects each of size \c size from the area + * pointed at by \c ptr to data stream. + * + * \return the number of objects written, or 0 at error or end of file. + */ + size_t (SDLCALL * write) (struct SDL_RWops * context, const void *ptr, + size_t size, size_t num); + + /** + * Close and free an allocated SDL_RWops structure. + * + * \return 0 if successful or -1 on write error when flushing data. + */ + int (SDLCALL * close) (struct SDL_RWops * context); + + Uint32 type; + union + { +#if defined(ANDROID) + struct + { + void *fileNameRef; + void *inputStreamRef; + void *readableByteChannelRef; + void *readMethod; + void *assetFileDescriptorRef; + long position; + long size; + long offset; + int fd; + } androidio; +#elif defined(__WIN32__) + struct + { + SDL_bool append; + void *h; + struct + { + void *data; + size_t size; + size_t left; + } buffer; + } windowsio; +#endif + +#ifdef HAVE_STDIO_H + struct + { + SDL_bool autoclose; + FILE *fp; + } stdio; +#endif + struct + { + Uint8 *base; + Uint8 *here; + Uint8 *stop; + } mem; + struct + { + void *data1; + } unknown; + } hidden; + +} SDL_RWops; + + +/** + * \name RWFrom functions + * + * Functions to create SDL_RWops structures from various data streams. + */ +/*@{*/ + +extern DECLSPEC SDL_RWops *SDLCALL SDL_RWFromFile(const char *file, + const char *mode); + +#ifdef HAVE_STDIO_H +extern DECLSPEC SDL_RWops *SDLCALL SDL_RWFromFP(FILE * fp, + SDL_bool autoclose); +#else +extern DECLSPEC SDL_RWops *SDLCALL SDL_RWFromFP(void * fp, + SDL_bool autoclose); +#endif + +extern DECLSPEC SDL_RWops *SDLCALL SDL_RWFromMem(void *mem, int size); +extern DECLSPEC SDL_RWops *SDLCALL SDL_RWFromConstMem(const void *mem, + int size); + +/*@}*//*RWFrom functions*/ + + +extern DECLSPEC SDL_RWops *SDLCALL SDL_AllocRW(void); +extern DECLSPEC void SDLCALL SDL_FreeRW(SDL_RWops * area); + +#define RW_SEEK_SET 0 /**< Seek from the beginning of data */ +#define RW_SEEK_CUR 1 /**< Seek relative to current read point */ +#define RW_SEEK_END 2 /**< Seek relative to the end of data */ + +/** + * \name Read/write macros + * + * Macros to easily read and write from an SDL_RWops structure. + */ +/*@{*/ +#define SDL_RWsize(ctx) (ctx)->size(ctx) +#define SDL_RWseek(ctx, offset, whence) (ctx)->seek(ctx, offset, whence) +#define SDL_RWtell(ctx) (ctx)->seek(ctx, 0, RW_SEEK_CUR) +#define SDL_RWread(ctx, ptr, size, n) (ctx)->read(ctx, ptr, size, n) +#define SDL_RWwrite(ctx, ptr, size, n) (ctx)->write(ctx, ptr, size, n) +#define SDL_RWclose(ctx) (ctx)->close(ctx) +/*@}*//*Read/write macros*/ + + +/** + * \name Read endian functions + * + * Read an item of the specified endianness and return in native format. + */ +/*@{*/ +extern DECLSPEC Uint8 SDLCALL SDL_ReadU8(SDL_RWops * src); +extern DECLSPEC Uint16 SDLCALL SDL_ReadLE16(SDL_RWops * src); +extern DECLSPEC Uint16 SDLCALL SDL_ReadBE16(SDL_RWops * src); +extern DECLSPEC Uint32 SDLCALL SDL_ReadLE32(SDL_RWops * src); +extern DECLSPEC Uint32 SDLCALL SDL_ReadBE32(SDL_RWops * src); +extern DECLSPEC Uint64 SDLCALL SDL_ReadLE64(SDL_RWops * src); +extern DECLSPEC Uint64 SDLCALL SDL_ReadBE64(SDL_RWops * src); +/*@}*//*Read endian functions*/ + +/** + * \name Write endian functions + * + * Write an item of native format to the specified endianness. + */ +/*@{*/ +extern DECLSPEC size_t SDLCALL SDL_WriteU8(SDL_RWops * dst, Uint8 value); +extern DECLSPEC size_t SDLCALL SDL_WriteLE16(SDL_RWops * dst, Uint16 value); +extern DECLSPEC size_t SDLCALL SDL_WriteBE16(SDL_RWops * dst, Uint16 value); +extern DECLSPEC size_t SDLCALL SDL_WriteLE32(SDL_RWops * dst, Uint32 value); +extern DECLSPEC size_t SDLCALL SDL_WriteBE32(SDL_RWops * dst, Uint32 value); +extern DECLSPEC size_t SDLCALL SDL_WriteLE64(SDL_RWops * dst, Uint64 value); +extern DECLSPEC size_t SDLCALL SDL_WriteBE64(SDL_RWops * dst, Uint64 value); +/*@}*//*Write endian functions*/ + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_rwops_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_scancode.h b/external/SDL2/SDL_scancode.h new file mode 100644 index 0000000..26b8f36 --- /dev/null +++ b/external/SDL2/SDL_scancode.h @@ -0,0 +1,401 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_scancode.h + * + * Defines keyboard scancodes. + */ + +#ifndef _SDL_scancode_h +#define _SDL_scancode_h + +#include "SDL_stdinc.h" + +/** + * \brief The SDL keyboard scancode representation. + * + * Values of this type are used to represent keyboard keys, among other places + * in the \link SDL_Keysym::scancode key.keysym.scancode \endlink field of the + * SDL_Event structure. + * + * The values in this enumeration are based on the USB usage page standard: + * http://www.usb.org/developers/devclass_docs/Hut1_12v2.pdf + */ +typedef enum +{ + SDL_SCANCODE_UNKNOWN = 0, + + /** + * \name Usage page 0x07 + * + * These values are from usage page 0x07 (USB keyboard page). + */ + /*@{*/ + + SDL_SCANCODE_A = 4, + SDL_SCANCODE_B = 5, + SDL_SCANCODE_C = 6, + SDL_SCANCODE_D = 7, + SDL_SCANCODE_E = 8, + SDL_SCANCODE_F = 9, + SDL_SCANCODE_G = 10, + SDL_SCANCODE_H = 11, + SDL_SCANCODE_I = 12, + SDL_SCANCODE_J = 13, + SDL_SCANCODE_K = 14, + SDL_SCANCODE_L = 15, + SDL_SCANCODE_M = 16, + SDL_SCANCODE_N = 17, + SDL_SCANCODE_O = 18, + SDL_SCANCODE_P = 19, + SDL_SCANCODE_Q = 20, + SDL_SCANCODE_R = 21, + SDL_SCANCODE_S = 22, + SDL_SCANCODE_T = 23, + SDL_SCANCODE_U = 24, + SDL_SCANCODE_V = 25, + SDL_SCANCODE_W = 26, + SDL_SCANCODE_X = 27, + SDL_SCANCODE_Y = 28, + SDL_SCANCODE_Z = 29, + + SDL_SCANCODE_1 = 30, + SDL_SCANCODE_2 = 31, + SDL_SCANCODE_3 = 32, + SDL_SCANCODE_4 = 33, + SDL_SCANCODE_5 = 34, + SDL_SCANCODE_6 = 35, + SDL_SCANCODE_7 = 36, + SDL_SCANCODE_8 = 37, + SDL_SCANCODE_9 = 38, + SDL_SCANCODE_0 = 39, + + SDL_SCANCODE_RETURN = 40, + SDL_SCANCODE_ESCAPE = 41, + SDL_SCANCODE_BACKSPACE = 42, + SDL_SCANCODE_TAB = 43, + SDL_SCANCODE_SPACE = 44, + + SDL_SCANCODE_MINUS = 45, + SDL_SCANCODE_EQUALS = 46, + SDL_SCANCODE_LEFTBRACKET = 47, + SDL_SCANCODE_RIGHTBRACKET = 48, + SDL_SCANCODE_BACKSLASH = 49, /**< Located at the lower left of the return + * key on ISO keyboards and at the right end + * of the QWERTY row on ANSI keyboards. + * Produces REVERSE SOLIDUS (backslash) and + * VERTICAL LINE in a US layout, REVERSE + * SOLIDUS and VERTICAL LINE in a UK Mac + * layout, NUMBER SIGN and TILDE in a UK + * Windows layout, DOLLAR SIGN and POUND SIGN + * in a Swiss German layout, NUMBER SIGN and + * APOSTROPHE in a German layout, GRAVE + * ACCENT and POUND SIGN in a French Mac + * layout, and ASTERISK and MICRO SIGN in a + * French Windows layout. + */ + SDL_SCANCODE_NONUSHASH = 50, /**< ISO USB keyboards actually use this code + * instead of 49 for the same key, but all + * OSes I've seen treat the two codes + * identically. So, as an implementor, unless + * your keyboard generates both of those + * codes and your OS treats them differently, + * you should generate SDL_SCANCODE_BACKSLASH + * instead of this code. As a user, you + * should not rely on this code because SDL + * will never generate it with most (all?) + * keyboards. + */ + SDL_SCANCODE_SEMICOLON = 51, + SDL_SCANCODE_APOSTROPHE = 52, + SDL_SCANCODE_GRAVE = 53, /**< Located in the top left corner (on both ANSI + * and ISO keyboards). Produces GRAVE ACCENT and + * TILDE in a US Windows layout and in US and UK + * Mac layouts on ANSI keyboards, GRAVE ACCENT + * and NOT SIGN in a UK Windows layout, SECTION + * SIGN and PLUS-MINUS SIGN in US and UK Mac + * layouts on ISO keyboards, SECTION SIGN and + * DEGREE SIGN in a Swiss German layout (Mac: + * only on ISO keyboards), CIRCUMFLEX ACCENT and + * DEGREE SIGN in a German layout (Mac: only on + * ISO keyboards), SUPERSCRIPT TWO and TILDE in a + * French Windows layout, COMMERCIAL AT and + * NUMBER SIGN in a French Mac layout on ISO + * keyboards, and LESS-THAN SIGN and GREATER-THAN + * SIGN in a Swiss German, German, or French Mac + * layout on ANSI keyboards. + */ + SDL_SCANCODE_COMMA = 54, + SDL_SCANCODE_PERIOD = 55, + SDL_SCANCODE_SLASH = 56, + + SDL_SCANCODE_CAPSLOCK = 57, + + SDL_SCANCODE_F1 = 58, + SDL_SCANCODE_F2 = 59, + SDL_SCANCODE_F3 = 60, + SDL_SCANCODE_F4 = 61, + SDL_SCANCODE_F5 = 62, + SDL_SCANCODE_F6 = 63, + SDL_SCANCODE_F7 = 64, + SDL_SCANCODE_F8 = 65, + SDL_SCANCODE_F9 = 66, + SDL_SCANCODE_F10 = 67, + SDL_SCANCODE_F11 = 68, + SDL_SCANCODE_F12 = 69, + + SDL_SCANCODE_PRINTSCREEN = 70, + SDL_SCANCODE_SCROLLLOCK = 71, + SDL_SCANCODE_PAUSE = 72, + SDL_SCANCODE_INSERT = 73, /**< insert on PC, help on some Mac keyboards (but + does send code 73, not 117) */ + SDL_SCANCODE_HOME = 74, + SDL_SCANCODE_PAGEUP = 75, + SDL_SCANCODE_DELETE = 76, + SDL_SCANCODE_END = 77, + SDL_SCANCODE_PAGEDOWN = 78, + SDL_SCANCODE_RIGHT = 79, + SDL_SCANCODE_LEFT = 80, + SDL_SCANCODE_DOWN = 81, + SDL_SCANCODE_UP = 82, + + SDL_SCANCODE_NUMLOCKCLEAR = 83, /**< num lock on PC, clear on Mac keyboards + */ + SDL_SCANCODE_KP_DIVIDE = 84, + SDL_SCANCODE_KP_MULTIPLY = 85, + SDL_SCANCODE_KP_MINUS = 86, + SDL_SCANCODE_KP_PLUS = 87, + SDL_SCANCODE_KP_ENTER = 88, + SDL_SCANCODE_KP_1 = 89, + SDL_SCANCODE_KP_2 = 90, + SDL_SCANCODE_KP_3 = 91, + SDL_SCANCODE_KP_4 = 92, + SDL_SCANCODE_KP_5 = 93, + SDL_SCANCODE_KP_6 = 94, + SDL_SCANCODE_KP_7 = 95, + SDL_SCANCODE_KP_8 = 96, + SDL_SCANCODE_KP_9 = 97, + SDL_SCANCODE_KP_0 = 98, + SDL_SCANCODE_KP_PERIOD = 99, + + SDL_SCANCODE_NONUSBACKSLASH = 100, /**< This is the additional key that ISO + * keyboards have over ANSI ones, + * located between left shift and Y. + * Produces GRAVE ACCENT and TILDE in a + * US or UK Mac layout, REVERSE SOLIDUS + * (backslash) and VERTICAL LINE in a + * US or UK Windows layout, and + * LESS-THAN SIGN and GREATER-THAN SIGN + * in a Swiss German, German, or French + * layout. */ + SDL_SCANCODE_APPLICATION = 101, /**< windows contextual menu, compose */ + SDL_SCANCODE_POWER = 102, /**< The USB document says this is a status flag, + * not a physical key - but some Mac keyboards + * do have a power key. */ + SDL_SCANCODE_KP_EQUALS = 103, + SDL_SCANCODE_F13 = 104, + SDL_SCANCODE_F14 = 105, + SDL_SCANCODE_F15 = 106, + SDL_SCANCODE_F16 = 107, + SDL_SCANCODE_F17 = 108, + SDL_SCANCODE_F18 = 109, + SDL_SCANCODE_F19 = 110, + SDL_SCANCODE_F20 = 111, + SDL_SCANCODE_F21 = 112, + SDL_SCANCODE_F22 = 113, + SDL_SCANCODE_F23 = 114, + SDL_SCANCODE_F24 = 115, + SDL_SCANCODE_EXECUTE = 116, + SDL_SCANCODE_HELP = 117, + SDL_SCANCODE_MENU = 118, + SDL_SCANCODE_SELECT = 119, + SDL_SCANCODE_STOP = 120, + SDL_SCANCODE_AGAIN = 121, /**< redo */ + SDL_SCANCODE_UNDO = 122, + SDL_SCANCODE_CUT = 123, + SDL_SCANCODE_COPY = 124, + SDL_SCANCODE_PASTE = 125, + SDL_SCANCODE_FIND = 126, + SDL_SCANCODE_MUTE = 127, + SDL_SCANCODE_VOLUMEUP = 128, + SDL_SCANCODE_VOLUMEDOWN = 129, +/* not sure whether there's a reason to enable these */ +/* SDL_SCANCODE_LOCKINGCAPSLOCK = 130, */ +/* SDL_SCANCODE_LOCKINGNUMLOCK = 131, */ +/* SDL_SCANCODE_LOCKINGSCROLLLOCK = 132, */ + SDL_SCANCODE_KP_COMMA = 133, + SDL_SCANCODE_KP_EQUALSAS400 = 134, + + SDL_SCANCODE_INTERNATIONAL1 = 135, /**< used on Asian keyboards, see + footnotes in USB doc */ + SDL_SCANCODE_INTERNATIONAL2 = 136, + SDL_SCANCODE_INTERNATIONAL3 = 137, /**< Yen */ + SDL_SCANCODE_INTERNATIONAL4 = 138, + SDL_SCANCODE_INTERNATIONAL5 = 139, + SDL_SCANCODE_INTERNATIONAL6 = 140, + SDL_SCANCODE_INTERNATIONAL7 = 141, + SDL_SCANCODE_INTERNATIONAL8 = 142, + SDL_SCANCODE_INTERNATIONAL9 = 143, + SDL_SCANCODE_LANG1 = 144, /**< Hangul/English toggle */ + SDL_SCANCODE_LANG2 = 145, /**< Hanja conversion */ + SDL_SCANCODE_LANG3 = 146, /**< Katakana */ + SDL_SCANCODE_LANG4 = 147, /**< Hiragana */ + SDL_SCANCODE_LANG5 = 148, /**< Zenkaku/Hankaku */ + SDL_SCANCODE_LANG6 = 149, /**< reserved */ + SDL_SCANCODE_LANG7 = 150, /**< reserved */ + SDL_SCANCODE_LANG8 = 151, /**< reserved */ + SDL_SCANCODE_LANG9 = 152, /**< reserved */ + + SDL_SCANCODE_ALTERASE = 153, /**< Erase-Eaze */ + SDL_SCANCODE_SYSREQ = 154, + SDL_SCANCODE_CANCEL = 155, + SDL_SCANCODE_CLEAR = 156, + SDL_SCANCODE_PRIOR = 157, + SDL_SCANCODE_RETURN2 = 158, + SDL_SCANCODE_SEPARATOR = 159, + SDL_SCANCODE_OUT = 160, + SDL_SCANCODE_OPER = 161, + SDL_SCANCODE_CLEARAGAIN = 162, + SDL_SCANCODE_CRSEL = 163, + SDL_SCANCODE_EXSEL = 164, + + SDL_SCANCODE_KP_00 = 176, + SDL_SCANCODE_KP_000 = 177, + SDL_SCANCODE_THOUSANDSSEPARATOR = 178, + SDL_SCANCODE_DECIMALSEPARATOR = 179, + SDL_SCANCODE_CURRENCYUNIT = 180, + SDL_SCANCODE_CURRENCYSUBUNIT = 181, + SDL_SCANCODE_KP_LEFTPAREN = 182, + SDL_SCANCODE_KP_RIGHTPAREN = 183, + SDL_SCANCODE_KP_LEFTBRACE = 184, + SDL_SCANCODE_KP_RIGHTBRACE = 185, + SDL_SCANCODE_KP_TAB = 186, + SDL_SCANCODE_KP_BACKSPACE = 187, + SDL_SCANCODE_KP_A = 188, + SDL_SCANCODE_KP_B = 189, + SDL_SCANCODE_KP_C = 190, + SDL_SCANCODE_KP_D = 191, + SDL_SCANCODE_KP_E = 192, + SDL_SCANCODE_KP_F = 193, + SDL_SCANCODE_KP_XOR = 194, + SDL_SCANCODE_KP_POWER = 195, + SDL_SCANCODE_KP_PERCENT = 196, + SDL_SCANCODE_KP_LESS = 197, + SDL_SCANCODE_KP_GREATER = 198, + SDL_SCANCODE_KP_AMPERSAND = 199, + SDL_SCANCODE_KP_DBLAMPERSAND = 200, + SDL_SCANCODE_KP_VERTICALBAR = 201, + SDL_SCANCODE_KP_DBLVERTICALBAR = 202, + SDL_SCANCODE_KP_COLON = 203, + SDL_SCANCODE_KP_HASH = 204, + SDL_SCANCODE_KP_SPACE = 205, + SDL_SCANCODE_KP_AT = 206, + SDL_SCANCODE_KP_EXCLAM = 207, + SDL_SCANCODE_KP_MEMSTORE = 208, + SDL_SCANCODE_KP_MEMRECALL = 209, + SDL_SCANCODE_KP_MEMCLEAR = 210, + SDL_SCANCODE_KP_MEMADD = 211, + SDL_SCANCODE_KP_MEMSUBTRACT = 212, + SDL_SCANCODE_KP_MEMMULTIPLY = 213, + SDL_SCANCODE_KP_MEMDIVIDE = 214, + SDL_SCANCODE_KP_PLUSMINUS = 215, + SDL_SCANCODE_KP_CLEAR = 216, + SDL_SCANCODE_KP_CLEARENTRY = 217, + SDL_SCANCODE_KP_BINARY = 218, + SDL_SCANCODE_KP_OCTAL = 219, + SDL_SCANCODE_KP_DECIMAL = 220, + SDL_SCANCODE_KP_HEXADECIMAL = 221, + + SDL_SCANCODE_LCTRL = 224, + SDL_SCANCODE_LSHIFT = 225, + SDL_SCANCODE_LALT = 226, /**< alt, option */ + SDL_SCANCODE_LGUI = 227, /**< windows, command (apple), meta */ + SDL_SCANCODE_RCTRL = 228, + SDL_SCANCODE_RSHIFT = 229, + SDL_SCANCODE_RALT = 230, /**< alt gr, option */ + SDL_SCANCODE_RGUI = 231, /**< windows, command (apple), meta */ + + SDL_SCANCODE_MODE = 257, /**< I'm not sure if this is really not covered + * by any of the above, but since there's a + * special KMOD_MODE for it I'm adding it here + */ + + /*@}*//*Usage page 0x07*/ + + /** + * \name Usage page 0x0C + * + * These values are mapped from usage page 0x0C (USB consumer page). + */ + /*@{*/ + + SDL_SCANCODE_AUDIONEXT = 258, + SDL_SCANCODE_AUDIOPREV = 259, + SDL_SCANCODE_AUDIOSTOP = 260, + SDL_SCANCODE_AUDIOPLAY = 261, + SDL_SCANCODE_AUDIOMUTE = 262, + SDL_SCANCODE_MEDIASELECT = 263, + SDL_SCANCODE_WWW = 264, + SDL_SCANCODE_MAIL = 265, + SDL_SCANCODE_CALCULATOR = 266, + SDL_SCANCODE_COMPUTER = 267, + SDL_SCANCODE_AC_SEARCH = 268, + SDL_SCANCODE_AC_HOME = 269, + SDL_SCANCODE_AC_BACK = 270, + SDL_SCANCODE_AC_FORWARD = 271, + SDL_SCANCODE_AC_STOP = 272, + SDL_SCANCODE_AC_REFRESH = 273, + SDL_SCANCODE_AC_BOOKMARKS = 274, + + /*@}*//*Usage page 0x0C*/ + + /** + * \name Walther keys + * + * These are values that Christian Walther added (for mac keyboard?). + */ + /*@{*/ + + SDL_SCANCODE_BRIGHTNESSDOWN = 275, + SDL_SCANCODE_BRIGHTNESSUP = 276, + SDL_SCANCODE_DISPLAYSWITCH = 277, /**< display mirroring/dual display + switch, video mode switch */ + SDL_SCANCODE_KBDILLUMTOGGLE = 278, + SDL_SCANCODE_KBDILLUMDOWN = 279, + SDL_SCANCODE_KBDILLUMUP = 280, + SDL_SCANCODE_EJECT = 281, + SDL_SCANCODE_SLEEP = 282, + + SDL_SCANCODE_APP1 = 283, + SDL_SCANCODE_APP2 = 284, + + /*@}*//*Walther keys*/ + + /* Add any other keys here. */ + + SDL_NUM_SCANCODES = 512 /**< not a key, just marks the number of scancodes + for array bounds */ +} SDL_Scancode; + +#endif /* _SDL_scancode_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_shape.h b/external/SDL2/SDL_shape.h new file mode 100644 index 0000000..bd37242 --- /dev/null +++ b/external/SDL2/SDL_shape.h @@ -0,0 +1,147 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef _SDL_shape_h +#define _SDL_shape_h + +#include "SDL_stdinc.h" +#include "SDL_pixels.h" +#include "SDL_rect.h" +#include "SDL_surface.h" +#include "SDL_video.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** \file SDL_shape.h + * + * Header file for the shaped window API. + */ + +#define SDL_NONSHAPEABLE_WINDOW -1 +#define SDL_INVALID_SHAPE_ARGUMENT -2 +#define SDL_WINDOW_LACKS_SHAPE -3 + +/** + * \brief Create a window that can be shaped with the specified position, dimensions, and flags. + * + * \param title The title of the window, in UTF-8 encoding. + * \param x The x position of the window, ::SDL_WINDOWPOS_CENTERED, or + * ::SDL_WINDOWPOS_UNDEFINED. + * \param y The y position of the window, ::SDL_WINDOWPOS_CENTERED, or + * ::SDL_WINDOWPOS_UNDEFINED. + * \param w The width of the window. + * \param h The height of the window. + * \param flags The flags for the window, a mask of SDL_WINDOW_BORDERLESS with any of the following: + * ::SDL_WINDOW_OPENGL, ::SDL_WINDOW_INPUT_GRABBED, + * ::SDL_WINDOW_SHOWN, ::SDL_WINDOW_RESIZABLE, + * ::SDL_WINDOW_MAXIMIZED, ::SDL_WINDOW_MINIMIZED, + * ::SDL_WINDOW_BORDERLESS is always set, and ::SDL_WINDOW_FULLSCREEN is always unset. + * + * \return The window created, or NULL if window creation failed. + * + * \sa SDL_DestroyWindow() + */ +extern DECLSPEC SDL_Window * SDLCALL SDL_CreateShapedWindow(const char *title,unsigned int x,unsigned int y,unsigned int w,unsigned int h,Uint32 flags); + +/** + * \brief Return whether the given window is a shaped window. + * + * \param window The window to query for being shaped. + * + * \return SDL_TRUE if the window is a window that can be shaped, SDL_FALSE if the window is unshaped or NULL. + * \sa SDL_CreateShapedWindow + */ +extern DECLSPEC SDL_bool SDLCALL SDL_IsShapedWindow(const SDL_Window *window); + +/** \brief An enum denoting the specific type of contents present in an SDL_WindowShapeParams union. */ +typedef enum { + /** \brief The default mode, a binarized alpha cutoff of 1. */ + ShapeModeDefault, + /** \brief A binarized alpha cutoff with a given integer value. */ + ShapeModeBinarizeAlpha, + /** \brief A binarized alpha cutoff with a given integer value, but with the opposite comparison. */ + ShapeModeReverseBinarizeAlpha, + /** \brief A color key is applied. */ + ShapeModeColorKey +} WindowShapeMode; + +#define SDL_SHAPEMODEALPHA(mode) (mode == ShapeModeDefault || mode == ShapeModeBinarizeAlpha || mode == ShapeModeReverseBinarizeAlpha) + +/** \brief A union containing parameters for shaped windows. */ +typedef union { + /** \brief a cutoff alpha value for binarization of the window shape's alpha channel. */ + Uint8 binarizationCutoff; + SDL_Color colorKey; +} SDL_WindowShapeParams; + +/** \brief A struct that tags the SDL_WindowShapeParams union with an enum describing the type of its contents. */ +typedef struct SDL_WindowShapeMode { + /** \brief The mode of these window-shape parameters. */ + WindowShapeMode mode; + /** \brief Window-shape parameters. */ + SDL_WindowShapeParams parameters; +} SDL_WindowShapeMode; + +/** + * \brief Set the shape and parameters of a shaped window. + * + * \param window The shaped window whose parameters should be set. + * \param shape A surface encoding the desired shape for the window. + * \param shape_mode The parameters to set for the shaped window. + * + * \return 0 on success, SDL_INVALID_SHAPE_ARGUMENT on invalid an invalid shape argument, or SDL_NONSHAPEABLE_WINDOW + * if the SDL_Window* given does not reference a valid shaped window. + * + * \sa SDL_WindowShapeMode + * \sa SDL_GetShapedWindowMode. + */ +extern DECLSPEC int SDLCALL SDL_SetWindowShape(SDL_Window *window,SDL_Surface *shape,SDL_WindowShapeMode *shape_mode); + +/** + * \brief Get the shape parameters of a shaped window. + * + * \param window The shaped window whose parameters should be retrieved. + * \param shape_mode An empty shape-mode structure to fill, or NULL to check whether the window has a shape. + * + * \return 0 if the window has a shape and, provided shape_mode was not NULL, shape_mode has been filled with the mode + * data, SDL_NONSHAPEABLE_WINDOW if the SDL_Window given is not a shaped window, or SDL_WINDOW_LACKS_SHAPE if + * the SDL_Window* given is a shapeable window currently lacking a shape. + * + * \sa SDL_WindowShapeMode + * \sa SDL_SetWindowShape + */ +extern DECLSPEC int SDLCALL SDL_GetShapedWindowMode(SDL_Window *window,SDL_WindowShapeMode *shape_mode); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_shape_h */ diff --git a/external/SDL2/SDL_stdinc.h b/external/SDL2/SDL_stdinc.h new file mode 100644 index 0000000..f1e0edb --- /dev/null +++ b/external/SDL2/SDL_stdinc.h @@ -0,0 +1,795 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_stdinc.h + * + * This is a general header that includes C language support. + */ + +#ifndef _SDL_stdinc_h +#define _SDL_stdinc_h + +#include "SDL_config.h" + + +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_STDIO_H +#include +#endif +#if defined(STDC_HEADERS) +# include +# include +# include +#else +# if defined(HAVE_STDLIB_H) +# include +# elif defined(HAVE_MALLOC_H) +# include +# endif +# if defined(HAVE_STDDEF_H) +# include +# endif +# if defined(HAVE_STDARG_H) +# include +# endif +#endif +#ifdef HAVE_STRING_H +# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H) +# include +# endif +# include +#endif +#ifdef HAVE_STRINGS_H +# include +#endif +#if defined(HAVE_INTTYPES_H) +# include +#elif defined(HAVE_STDINT_H) +# include +#endif +#ifdef HAVE_CTYPE_H +# include +#endif +#ifdef HAVE_MATH_H +# include +#endif +#if defined(HAVE_ICONV) && defined(HAVE_ICONV_H) +# include +#endif + +/** + * The number of elements in an array. + */ +#define SDL_arraysize(array) (sizeof(array)/sizeof(array[0])) +#define SDL_TABLESIZE(table) SDL_arraysize(table) + +/** + * \name Cast operators + * + * Use proper C++ casts when compiled as C++ to be compatible with the option + * -Wold-style-cast of GCC (and -Werror=old-style-cast in GCC 4.2 and above). + */ +/*@{*/ +#ifdef __cplusplus +#define SDL_reinterpret_cast(type, expression) reinterpret_cast(expression) +#define SDL_static_cast(type, expression) static_cast(expression) +#else +#define SDL_reinterpret_cast(type, expression) ((type)(expression)) +#define SDL_static_cast(type, expression) ((type)(expression)) +#endif +/*@}*//*Cast operators*/ + +/* Define a four character code as a Uint32 */ +#define SDL_FOURCC(A, B, C, D) \ + ((SDL_static_cast(Uint32, SDL_static_cast(Uint8, (A))) << 0) | \ + (SDL_static_cast(Uint32, SDL_static_cast(Uint8, (B))) << 8) | \ + (SDL_static_cast(Uint32, SDL_static_cast(Uint8, (C))) << 16) | \ + (SDL_static_cast(Uint32, SDL_static_cast(Uint8, (D))) << 24)) + +/** + * \name Basic data types + */ +/*@{*/ + +typedef enum +{ + SDL_FALSE = 0, + SDL_TRUE = 1 +} SDL_bool; + +/** + * \brief A signed 8-bit integer type. + */ +typedef int8_t Sint8; +/** + * \brief An unsigned 8-bit integer type. + */ +typedef uint8_t Uint8; +/** + * \brief A signed 16-bit integer type. + */ +typedef int16_t Sint16; +/** + * \brief An unsigned 16-bit integer type. + */ +typedef uint16_t Uint16; +/** + * \brief A signed 32-bit integer type. + */ +typedef int32_t Sint32; +/** + * \brief An unsigned 32-bit integer type. + */ +typedef uint32_t Uint32; + +/** + * \brief A signed 64-bit integer type. + */ +typedef int64_t Sint64; +/** + * \brief An unsigned 64-bit integer type. + */ +typedef uint64_t Uint64; + +/*@}*//*Basic data types*/ + + +#define SDL_COMPILE_TIME_ASSERT(name, x) \ + typedef int SDL_dummy_ ## name[(x) * 2 - 1] +/** \cond */ +#ifndef DOXYGEN_SHOULD_IGNORE_THIS +SDL_COMPILE_TIME_ASSERT(uint8, sizeof(Uint8) == 1); +SDL_COMPILE_TIME_ASSERT(sint8, sizeof(Sint8) == 1); +SDL_COMPILE_TIME_ASSERT(uint16, sizeof(Uint16) == 2); +SDL_COMPILE_TIME_ASSERT(sint16, sizeof(Sint16) == 2); +SDL_COMPILE_TIME_ASSERT(uint32, sizeof(Uint32) == 4); +SDL_COMPILE_TIME_ASSERT(sint32, sizeof(Sint32) == 4); +SDL_COMPILE_TIME_ASSERT(uint64, sizeof(Uint64) == 8); +SDL_COMPILE_TIME_ASSERT(sint64, sizeof(Sint64) == 8); +#endif /* DOXYGEN_SHOULD_IGNORE_THIS */ +/** \endcond */ + +/* Check to make sure enums are the size of ints, for structure packing. + For both Watcom C/C++ and Borland C/C++ the compiler option that makes + enums having the size of an int must be enabled. + This is "-b" for Borland C/C++ and "-ei" for Watcom C/C++ (v11). +*/ + +/** \cond */ +#ifndef DOXYGEN_SHOULD_IGNORE_THIS +#if !defined(__ANDROID__) + /* TODO: include/SDL_stdinc.h:174: error: size of array 'SDL_dummy_enum' is negative */ +typedef enum +{ + DUMMY_ENUM_VALUE +} SDL_DUMMY_ENUM; + +SDL_COMPILE_TIME_ASSERT(enum, sizeof(SDL_DUMMY_ENUM) == sizeof(int)); +#endif +#endif /* DOXYGEN_SHOULD_IGNORE_THIS */ +/** \endcond */ + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +#if defined(HAVE_ALLOCA) && !defined(alloca) +# if defined(HAVE_ALLOCA_H) +# include +# elif defined(__GNUC__) +# define alloca __builtin_alloca +# elif defined(_MSC_VER) +# include +# define alloca _alloca +# elif defined(__WATCOMC__) +# include +# elif defined(__BORLANDC__) +# include +# elif defined(__DMC__) +# include +# elif defined(__AIX__) +#pragma alloca +# elif defined(__MRC__) +void *alloca(unsigned); +# else +char *alloca(); +# endif +#endif +#ifdef HAVE_ALLOCA +#define SDL_stack_alloc(type, count) (type*)alloca(sizeof(type)*(count)) +#define SDL_stack_free(data) +#else +#define SDL_stack_alloc(type, count) (type*)SDL_malloc(sizeof(type)*(count)) +#define SDL_stack_free(data) SDL_free(data) +#endif + + +/* SDL stdinc inline functions: + + The theory here is that by default we forcibly inline what we can, and your + app will use the inline version by default. However we expose a non-inline + version too, so the symbol is always available in the library even if your app + bypassed the inline version. The SDL_*_inline versions aren't guaranteed to + exist, so never call them directly; use SDL_* instead, and trust the system + to give you the right thing. + + The benefit here is that you can dlsym() these functions, which you + couldn't if you had macros, you can link against a foreign build of SDL + even if you configured differently, and you can drop the unconfigured SDL + headers into a project without #defining HAVE_MALLOC (etc) and still link. +*/ + +extern DECLSPEC void *SDLCALL SDL_malloc(size_t size); +#ifdef HAVE_MALLOC +SDL_FORCE_INLINE void *SDL_malloc_inline(size_t size) { return malloc(size); } +#define SDL_malloc SDL_malloc_inline +#endif + +extern DECLSPEC void *SDLCALL SDL_calloc(size_t nmemb, size_t size); +#ifdef HAVE_CALLOC +SDL_FORCE_INLINE void *SDL_calloc_inline(size_t nmemb, size_t size) { return calloc(nmemb, size); } +#define SDL_calloc SDL_calloc_inline +#endif + +extern DECLSPEC void *SDLCALL SDL_realloc(void *mem, size_t size); +#ifdef HAVE_REALLOC +SDL_FORCE_INLINE void *SDL_realloc_inline(void *mem, size_t size) { return realloc(mem, size); } +#define SDL_realloc SDL_realloc_inline +#endif + +extern DECLSPEC void SDLCALL SDL_free(void *mem); +#ifdef HAVE_FREE +SDL_FORCE_INLINE void SDL_free_inline(void *mem) { free(mem); } +#define SDL_free SDL_free_inline +#endif + +extern DECLSPEC char *SDLCALL SDL_getenv(const char *name); +#ifdef HAVE_GETENV +SDL_FORCE_INLINE char *SDL_getenv_inline(const char *name) { return getenv(name); } +#define SDL_getenv SDL_getenv_inline +#endif + +extern DECLSPEC int SDLCALL SDL_setenv(const char *name, const char *value, int overwrite); +#ifdef HAVE_SETENV +SDL_FORCE_INLINE int SDL_setenv_inline(const char *name, const char *value, int overwrite) { return setenv(name, value, overwrite); } +#define SDL_setenv SDL_setenv_inline +#endif + +extern DECLSPEC void SDLCALL SDL_qsort(void *base, size_t nmemb, size_t size, int (*compare) (const void *, const void *)); +#ifdef HAVE_QSORT +SDL_FORCE_INLINE void SDL_qsort_inline(void *base, size_t nmemb, size_t size, int (*compare) (const void *, const void *)) { return qsort(base, nmemb, size, compare); } +#define SDL_qsort SDL_qsort_inline +#endif + +extern DECLSPEC int SDLCALL SDL_abs(int x); +#ifdef HAVE_ABS +SDL_FORCE_INLINE int SDL_abs_inline(int x) { return abs(x); } +#else +SDL_FORCE_INLINE int SDL_abs_inline(int x) { return ((x) < 0 ? -(x) : (x)); } +#endif +#define SDL_abs SDL_abs_inline + +/* !!! FIXME: these have side effects. You probably shouldn't use them. */ +/* !!! FIXME: Maybe we do forceinline functions of SDL_mini, SDL_minf, etc? */ +#define SDL_min(x, y) (((x) < (y)) ? (x) : (y)) +#define SDL_max(x, y) (((x) > (y)) ? (x) : (y)) + +extern DECLSPEC int SDLCALL SDL_isdigit(int x); +extern DECLSPEC int SDLCALL SDL_isspace(int x); +extern DECLSPEC int SDLCALL SDL_toupper(int x); +extern DECLSPEC int SDLCALL SDL_tolower(int x); +#ifdef HAVE_CTYPE_H +SDL_FORCE_INLINE int SDL_isdigit_inline(int x) { return isdigit(x); } +SDL_FORCE_INLINE int SDL_isspace_inline(int x) { return isspace(x); } +SDL_FORCE_INLINE int SDL_toupper_inline(int x) { return toupper(x); } +SDL_FORCE_INLINE int SDL_tolower_inline(int x) { return tolower(x); } +#else +SDL_FORCE_INLINE int SDL_isdigit_inline(int x) { return ((x) >= '0') && ((x) <= '9'); } +SDL_FORCE_INLINE int SDL_isspace_inline(int x) { return ((x) == ' ') || ((x) == '\t') || ((x) == '\r') || ((x) == '\n'); } +SDL_FORCE_INLINE int SDL_toupper_inline(int x) { return ((x) >= 'a') && ((x) <= 'z') ? ('A'+((x)-'a')) : (x); } +SDL_FORCE_INLINE int SDL_tolower_inline(int x) { return ((x) >= 'A') && ((x) <= 'Z') ? ('a'+((x)-'A')) : (x); } +#endif +#define SDL_isdigit SDL_isdigit_inline +#define SDL_isspace SDL_isspace_inline +#define SDL_toupper SDL_toupper_inline +#define SDL_tolower SDL_tolower_inline + +extern DECLSPEC void *SDLCALL SDL_memset(void *dst, int c, size_t len); +#ifdef HAVE_MEMSET +SDL_FORCE_INLINE void *SDL_memset_inline(void *dst, int c, size_t len) { return memset(dst, c, len); } +#define SDL_memset SDL_memset_inline +#endif + +#define SDL_zero(x) SDL_memset(&(x), 0, sizeof((x))) +#define SDL_zerop(x) SDL_memset((x), 0, sizeof(*(x))) + +/* !!! FIXME: does this _really_ beat memset() on any modern platform? */ +SDL_FORCE_INLINE void SDL_memset4(void *dst, int val, size_t len) +{ +#if defined(__GNUC__) && defined(i386) + int u0, u1, u2; + __asm__ __volatile__ ( + "cld \n\t" + "rep ; stosl \n\t" + : "=&D" (u0), "=&a" (u1), "=&c" (u2) + : "0" (dst), "1" (val), "2" (SDL_static_cast(Uint32, len)) + : "memory" + ); +/* !!! FIXME: amd64? */ +#else + size_t _n = (len + 3) / 4; + Uint32 *_p = SDL_static_cast(Uint32 *, dst); + Uint32 _val = (val); + if (len == 0) + return; + switch (len % 4) + { + case 0: do { *_p++ = _val; + case 3: *_p++ = _val; + case 2: *_p++ = _val; + case 1: *_p++ = _val; + } while ( --_n ); + } +#endif +} + + +extern DECLSPEC void *SDLCALL SDL_memcpy(void *dst, const void *src, size_t len); +#if defined(__MACOSX__) +SDL_FORCE_INLINE void *SDL_memcpy_inline(void *dst, const void *src, size_t len) +{ + /* We can count on memcpy existing on Mac OS X and being well-tuned. */ + return memcpy(dst, src, len); +} +#define SDL_memcpy SDL_memcpy_inline +#elif defined(__GNUC__) && defined(i386) && !defined(__WIN32__) +SDL_FORCE_INLINE void *SDL_memcpy_inline(void *dst, const void *src, size_t len) +{ + /* !!! FIXME: does this _really_ beat memcpy() on any modern platform? */ + /* !!! FIXME: shouldn't we just force the inputs to ecx/edi/esi instead of this tapdance with outputs? */ + /* !!! FIXME: amd64? */ + int u0, u1, u2; + __asm__ __volatile__ ( + "cld \n\t" + "rep ; movsl \n\t" + "testb $2,%b4 \n\t" + "je 1f \n\t" + "movsw \n" + "1:\ttestb $1,%b4 \n\t" + "je 2f \n\t" + "movsb \n" + "2:" + : "=&c" (u0), "=&D" (u1), "=&S" (u2) + : "0" (SDL_static_cast(unsigned, len)/4), "q" (len), "1" (dst), "2" (src) + : "memory" + ); + return dst; +} +#define SDL_memcpy SDL_memcpy_inline +#elif defined(HAVE_MEMCPY) +SDL_FORCE_INLINE void *SDL_memcpy_inline(void *dst, const void *src, size_t len) +{ + return memcpy(dst, src, len); +} +#define SDL_memcpy SDL_memcpy_inline +#elif defined(HAVE_BCOPY) /* !!! FIXME: is there _really_ ever a time where you have bcopy and not memcpy? */ +SDL_FORCE_INLINE void *SDL_memcpy_inline(void *dst, const void *src, size_t len) +{ + bcopy(src, dst, len); + return dst; +} +#define SDL_memcpy SDL_memcpy_inline +#endif + + +SDL_FORCE_INLINE void *SDL_memcpy4(void *dst, const void *src, size_t dwords) +{ +#if defined(__GNUC__) && defined(i386) + /* !!! FIXME: does this _really_ beat memcpy() on any modern platform? */ + /* !!! FIXME: shouldn't we just force the inputs to ecx/edi/esi instead of this tapdance with outputs? */ + int ecx, edi, esi; + __asm__ __volatile__ ( + "cld \n\t" + "rep ; movsl \n\t" + : "=&c" (ecx), "=&D" (edi), "=&S" (esi) + : "0" (SDL_static_cast(unsigned, dwords)), "1" (dst), "2" (src) + : "memory" + ); + return dst; +#else + return SDL_memcpy(dst, src, dwords * 4); +#endif +} + +extern DECLSPEC void *SDLCALL SDL_memmove(void *dst, const void *src, size_t len); +#ifdef HAVE_MEMMOVE +SDL_FORCE_INLINE void *SDL_memmove_inline(void *dst, const void *src, size_t len) { return memmove(dst, src, len); } +#define SDL_memmove SDL_memmove_inline +#endif + +extern DECLSPEC int SDLCALL SDL_memcmp(const void *s1, const void *s2, size_t len); +#ifdef HAVE_MEMCMP +SDL_FORCE_INLINE int SDL_memcmp_inline(const void *s1, const void *s2, size_t len) { return memcmp(s1, s2, len); } +#define SDL_memcmp SDL_memcmp_inline +#endif + +extern DECLSPEC size_t SDLCALL SDL_strlen(const char *str); +#ifdef HAVE_STRLEN +SDL_FORCE_INLINE size_t SDL_strlen_inline(const char *str) { return strlen(str); } +#define SDL_strlen SDL_strlen_inline +#endif + +extern DECLSPEC size_t SDLCALL SDL_wcslen(const wchar_t *wstr); +#ifdef HAVE_WCSLEN +SDL_FORCE_INLINE size_t SDL_wcslen_inline(const wchar_t *wstr) { return wcslen(wstr); } +#define SDL_wcslen SDL_wcslen_inline +#endif + +extern DECLSPEC size_t SDLCALL SDL_wcslcpy(wchar_t *dst, const wchar_t *src, size_t maxlen); +#ifdef HAVE_WCSLCPY +SDL_FORCE_INLINE size_t SDL_wcslcpy_inline(wchar_t *dst, const wchar_t *src, size_t maxlen) { return wcslcpy(dst, src, maxlen); } +#define SDL_wcslcpy SDL_wcslcpy_inline +#endif + +extern DECLSPEC size_t SDLCALL SDL_wcslcat(wchar_t *dst, const wchar_t *src, size_t maxlen); +#ifdef HAVE_WCSLCAT +SDL_FORCE_INLINE size_t SDL_wcslcat_inline(wchar_t *dst, const wchar_t *src, size_t maxlen) { return wcslcat(dst, src, maxlen); } +#define SDL_wcslcat SDL_wcslcat_inline +#endif + +extern DECLSPEC size_t SDLCALL SDL_strlcpy(char *dst, const char *src, size_t maxlen); +#ifdef HAVE_STRLCPY +SDL_FORCE_INLINE size_t SDL_strlcpy_inline(char *dst, const char *src, size_t maxlen) { return strlcpy(dst, src, maxlen); } +#define SDL_strlcpy SDL_strlcpy_inline +#else +#endif + +extern DECLSPEC size_t SDLCALL SDL_utf8strlcpy(char *dst, const char *src, size_t dst_bytes); + +extern DECLSPEC size_t SDLCALL SDL_strlcat(char *dst, const char *src, size_t maxlen); +#ifdef HAVE_STRLCAT +SDL_FORCE_INLINE size_t SDL_strlcat_inline(char *dst, const char *src, size_t maxlen) { return strlcat(dst, src, maxlen); } +#define SDL_strlcat SDL_strlcat_inline +#endif + +extern DECLSPEC char *SDLCALL SDL_strdup(const char *str); +#ifdef HAVE_STRDUP +SDL_FORCE_INLINE char *SDL_strdup_inline(const char *str) { return strdup(str); } +#define SDL_strdup SDL_strdup_inline +#endif + +extern DECLSPEC char *SDLCALL SDL_strrev(char *str); +#ifdef HAVE__STRREV +SDL_FORCE_INLINE char *SDL_strrev_inline(char *str) { return _strrev(str); } +#define SDL_strrev SDL_strrev_inline +#endif + +extern DECLSPEC char *SDLCALL SDL_strupr(char *str); +#ifdef HAVE__STRUPR +SDL_FORCE_INLINE char *SDL_strupr_inline(char *str) { return _strupr(str); } +#define SDL_strupr SDL_strupr_inline +#endif + +extern DECLSPEC char *SDLCALL SDL_strlwr(char *str); +#ifdef HAVE__STRLWR +SDL_FORCE_INLINE char *SDL_strlwr_inline(char *str) { return _strlwr(str); } +#define SDL_strlwr SDL_strlwr_inline +#endif + +extern DECLSPEC char *SDLCALL SDL_strchr(const char *str, int c); +#ifdef HAVE_STRCHR +SDL_FORCE_INLINE char *SDL_strchr_inline(const char *str, int c) { return (char*)strchr(str, c); } +#define SDL_strchr SDL_strchr_inline +#elif defined(HAVE_INDEX) /* !!! FIXME: is there anywhere that has this but not strchr? */ +SDL_FORCE_INLINE char *SDL_strchr_inline(const char *str, int c) { return index(str, c); } +#define SDL_strchr SDL_strchr_inline +#endif + +extern DECLSPEC char *SDLCALL SDL_strrchr(const char *str, int c); +#ifdef HAVE_STRRCHR +SDL_FORCE_INLINE char *SDL_strrchr_inline(const char *str, int c) { return (char*)strrchr(str, c); } +#define SDL_strrchr SDL_strrchr_inline +#elif defined(HAVE_RINDEX) /* !!! FIXME: is there anywhere that has this but not strrchr? */ +SDL_FORCE_INLINE char *SDL_strrchr_inline(const char *str, int c) { return (char*)rindex(str, c); } +#define SDL_strrchr SDL_strrchr_inline +#endif + +extern DECLSPEC char *SDLCALL SDL_strstr(const char *haystack, const char *needle); +#ifdef HAVE_STRSTR +SDL_FORCE_INLINE char *SDL_strstr_inline(const char *haystack, const char *needle) { return (char*)strstr(haystack, needle); } +#define SDL_strstr SDL_strstr_inline +#endif + +extern DECLSPEC char *SDLCALL SDL_ltoa(long value, char *str, int radix); +#ifdef HAVE__LTOA +SDL_FORCE_INLINE char *SDL_ltoa_inline(long value, char *str, int radix) { return _ltoa(value, str, radix); } +#define SDL_ltoa SDL_ltoa_inline +#endif + +extern DECLSPEC char *SDLCALL SDL_itoa(int value, char *str, int radix); +#ifdef HAVE_ITOA +SDL_FORCE_INLINE char *SDL_itoa_inline(int value, char *str, int radix) { return itoa(value, str, radix); } +#else +SDL_FORCE_INLINE char *SDL_itoa_inline(int value, char *str, int radix) { return SDL_ltoa((long)value, str, radix); } +#endif +#define SDL_itoa SDL_itoa_inline + +extern DECLSPEC char *SDLCALL SDL_ultoa(unsigned long value, char *str, int radix); +#ifdef HAVE__ULTOA +SDL_FORCE_INLINE char *SDL_ultoa_inline(unsigned long value, char *str, int radix) { return _ultoa(value, str, radix); } +#define SDL_ultoa SDL_ultoa_inline +#endif + +extern DECLSPEC char *SDLCALL SDL_uitoa(unsigned int value, char *str, int radix); +#ifdef HAVE__UITOA +SDL_FORCE_INLINE char *SDL_uitoa_inline(unsigned int value, char *str, int radix) { return _uitoa(value, str, radix); } +#else +SDL_FORCE_INLINE char *SDL_uitoa_inline(unsigned int value, char *str, int radix) { return SDL_ultoa((unsigned long)value, str, radix); } +#endif +#define SDL_uitoa SDL_uitoa_inline + + +extern DECLSPEC long SDLCALL SDL_strtol(const char *str, char **endp, int base); +#ifdef HAVE_STRTOL +SDL_FORCE_INLINE long SDL_strtol_inline(const char *str, char **endp, int base) { return strtol(str, endp, base); } +#define SDL_strtol SDL_strtol_inline +#endif + +extern DECLSPEC unsigned long SDLCALL SDL_strtoul(const char *str, char **endp, int base); +#ifdef HAVE_STRTOUL +SDL_FORCE_INLINE unsigned long SDLCALL SDL_strtoul_inline(const char *str, char **endp, int base) { return strtoul(str, endp, base); } +#define SDL_strtoul SDL_strtoul_inline +#endif + +extern DECLSPEC char *SDLCALL SDL_lltoa(Sint64 value, char *str, int radix); +#ifdef HAVE__I64TOA +SDL_FORCE_INLINE char *SDL_lltoa_inline(Sint64 value, char *str, int radix) { return _i64toa(value, str, radix); } +#define SDL_lltoa SDL_lltoa_inline +#endif + +extern DECLSPEC char *SDLCALL SDL_ulltoa(Uint64 value, char *str, int radix); +#ifdef HAVE__UI64TOA +SDL_FORCE_INLINE char *SDL_ulltoa_inline(Uint64 value, char *str, int radix) { return _ui64toa(value, str, radix); } +#define SDL_ulltoa SDL_ulltoa_inline +#endif + +extern DECLSPEC Sint64 SDLCALL SDL_strtoll(const char *str, char **endp, int base); +#ifdef HAVE_STRTOLL +SDL_FORCE_INLINE Sint64 SDL_strtoll_inline(const char *str, char **endp, int base) { return strtoll(str, endp, base); } +#define SDL_strtoll SDL_strtoll_inline +#endif + +extern DECLSPEC Uint64 SDLCALL SDL_strtoull(const char *str, char **endp, int base); +#ifdef HAVE_STRTOULL +SDL_FORCE_INLINE Uint64 SDL_strtoull_inline(const char *str, char **endp, int base) { return strtoull(str, endp, base); } +#define SDL_strtoull SDL_strtoull_inline +#endif + +extern DECLSPEC double SDLCALL SDL_strtod(const char *str, char **endp); +#ifdef HAVE_STRTOD +SDL_FORCE_INLINE double SDL_strtod_inline(const char *str, char **endp) { return strtod(str, endp); } +#define SDL_strtod SDL_strtod_inline +#endif + +extern DECLSPEC int SDLCALL SDL_atoi(const char *str); +#ifdef HAVE_ATOI +SDL_FORCE_INLINE int SDL_atoi_inline(const char *str) { return atoi(str); } +#else +SDL_FORCE_INLINE int SDL_atoi_inline(const char *str) { return SDL_strtol(str, NULL, 0); } +#endif +#define SDL_atoi SDL_atoi_inline + +extern DECLSPEC double SDLCALL SDL_atof(const char *str); +#ifdef HAVE_ATOF +SDL_FORCE_INLINE double SDL_atof_inline(const char *str) { return (double) atof(str); } +#else +SDL_FORCE_INLINE double SDL_atof_inline(const char *str) { return SDL_strtod(str, NULL); } +#endif +#define SDL_atof SDL_atof_inline + + +extern DECLSPEC int SDLCALL SDL_strcmp(const char *str1, const char *str2); +#ifdef HAVE_STRCMP +SDL_FORCE_INLINE int SDL_strcmp_inline(const char *str1, const char *str2) { return strcmp(str1, str2); } +#define SDL_strcmp SDL_strcmp_inline +#endif + +extern DECLSPEC int SDLCALL SDL_strncmp(const char *str1, const char *str2, size_t maxlen); +#ifdef HAVE_STRNCMP +SDL_FORCE_INLINE int SDL_strncmp_inline(const char *str1, const char *str2, size_t maxlen) { return strncmp(str1, str2, maxlen); } +#define SDL_strncmp SDL_strncmp_inline +#endif + +extern DECLSPEC int SDLCALL SDL_strcasecmp(const char *str1, const char *str2); +#ifdef HAVE_STRCASECMP +SDL_FORCE_INLINE int SDL_strcasecmp_inline(const char *str1, const char *str2) { return strcasecmp(str1, str2); } +#define SDL_strcasecmp SDL_strcasecmp_inline +#elif defined(HAVE__STRICMP) +SDL_FORCE_INLINE int SDL_strcasecmp_inline(const char *str1, const char *str2) { return _stricmp(str1, str2); } +#define SDL_strcasecmp SDL_strcasecmp_inline +#endif + +extern DECLSPEC int SDLCALL SDL_strncasecmp(const char *str1, const char *str2, size_t len); +#ifdef HAVE_STRNCASECMP +SDL_FORCE_INLINE int SDL_strncasecmp_inline(const char *str1, const char *str2, size_t len) { return strncasecmp(str1, str2, len); } +#define SDL_strncasecmp SDL_strncasecmp_inline +#elif defined(HAVE__STRNICMP) +SDL_FORCE_INLINE int SDL_strncasecmp_inline(const char *str1, const char *str2, size_t len) { return _strnicmp(str1, str2, len); } +#define SDL_strncasecmp SDL_strncasecmp_inline +#endif + +/* Not doing SDL_*_inline functions for these, because of the varargs. */ +extern DECLSPEC int SDLCALL SDL_sscanf(const char *text, const char *fmt, ...); +#ifdef HAVE_SSCANF +#define SDL_sscanf sscanf +#endif + +extern DECLSPEC int SDLCALL SDL_snprintf(char *text, size_t maxlen, const char *fmt, ...); +#ifdef HAVE_SNPRINTF +#define SDL_snprintf snprintf +#endif + +extern DECLSPEC int SDLCALL SDL_vsnprintf(char *text, size_t maxlen, const char *fmt, va_list ap); +#ifdef HAVE_VSNPRINTF +SDL_FORCE_INLINE int SDL_vsnprintf_inline(char *text, size_t maxlen, const char *fmt, va_list ap) { return vsnprintf(text, maxlen, fmt, ap); } +#define SDL_vsnprintf SDL_vsnprintf_inline +#endif + +#ifndef HAVE_M_PI +#ifndef M_PI +#define M_PI 3.14159265358979323846264338327950288 /* pi */ +#endif +#endif + +extern DECLSPEC double SDLCALL SDL_atan(double x); +#ifdef HAVE_ATAN +SDL_FORCE_INLINE double SDL_atan_inline(double x) { return atan(x); } +#define SDL_atan SDL_atan_inline +#endif + +extern DECLSPEC double SDLCALL SDL_atan2(double x, double y); +#ifdef HAVE_ATAN2 +SDL_FORCE_INLINE double SDL_atan2_inline(double x, double y) { return atan2(x, y); } +#define SDL_atan2 SDL_atan2_inline +#endif + +extern DECLSPEC double SDLCALL SDL_ceil(double x); +#ifdef HAVE_CEIL +SDL_FORCE_INLINE double SDL_ceil_inline(double x) { return ceil(x); } +#else +SDL_FORCE_INLINE double SDL_ceil_inline(double x) { return (double)(int)((x)+0.5); } +#endif +#define SDL_ceil SDL_ceil_inline + +extern DECLSPEC double SDLCALL SDL_copysign(double x, double y); +#ifdef HAVE_COPYSIGN +SDL_FORCE_INLINE double SDL_copysign_inline(double x, double y) { return copysign(x, y); } +#define SDL_copysign SDL_copysign_inline +#endif + +extern DECLSPEC double SDLCALL SDL_cos(double x); +#ifdef HAVE_COS +SDL_FORCE_INLINE double SDL_cos_inline(double x) { return cos(x); } +#define SDL_cos SDL_cos_inline +#endif + +extern DECLSPEC float SDLCALL SDL_cosf(float x); +#ifdef HAVE_COSF +SDL_FORCE_INLINE float SDL_cosf_inline(float x) { return cosf(x); } +#else +SDL_FORCE_INLINE float SDL_cosf_inline(float x) { return (float)SDL_cos((double)x); } +#endif +#define SDL_cosf SDL_cosf_inline + +extern DECLSPEC double SDLCALL SDL_fabs(double x); +#ifdef HAVE_FABS +SDL_FORCE_INLINE double SDL_fabs_inline(double x) { return fabs(x); } +#define SDL_fabs SDL_fabs_inline +#endif + +extern DECLSPEC double SDLCALL SDL_floor(double x); +#ifdef HAVE_FLOOR +SDL_FORCE_INLINE double SDL_floor_inline(double x) { return floor(x); } +#define SDL_floor SDL_floor_inline +#endif + +extern DECLSPEC double SDLCALL SDL_log(double x); +#ifdef HAVE_LOG +SDL_FORCE_INLINE double SDL_log_inline(double x) { return log(x); } +#define SDL_log SDL_log_inline +#endif + +extern DECLSPEC double SDLCALL SDL_pow(double x, double y); +#ifdef HAVE_POW +SDL_FORCE_INLINE double SDL_pow_inline(double x, double y) { return pow(x, y); } +#define SDL_pow SDL_pow_inline +#endif + +extern DECLSPEC double SDLCALL SDL_scalbn(double x, int n); +#ifdef HAVE_SCALBN +SDL_FORCE_INLINE double SDL_scalbn_inline(double x, int n) { return scalbn(x, n); } +#define SDL_scalbn SDL_scalbn_inline +#endif + +extern DECLSPEC double SDLCALL SDL_sin(double x); +#ifdef HAVE_SIN +SDL_FORCE_INLINE double SDL_sin_inline(double x) { return sin(x); } +#define SDL_sin SDL_sin_inline +#endif + +extern DECLSPEC float SDLCALL SDL_sinf(float x); +#ifdef HAVE_SINF +SDL_FORCE_INLINE float SDL_sinf_inline(float x) { return sinf(x); } +#else +SDL_FORCE_INLINE float SDL_sinf_inline(float x) { return (float)SDL_sin((double)x); } +#endif +#define SDL_sinf SDL_sinf_inline + +extern DECLSPEC double SDLCALL SDL_sqrt(double x); +#ifdef HAVE_SQRT +SDL_FORCE_INLINE double SDL_sqrt_inline(double x) { return sqrt(x); } +#define SDL_sqrt SDL_sqrt_inline +#endif + +/* The SDL implementation of iconv() returns these error codes */ +#define SDL_ICONV_ERROR (size_t)-1 +#define SDL_ICONV_E2BIG (size_t)-2 +#define SDL_ICONV_EILSEQ (size_t)-3 +#define SDL_ICONV_EINVAL (size_t)-4 + +/* SDL_iconv_* are now always real symbols/types, not macros or inlined. */ +typedef struct _SDL_iconv_t *SDL_iconv_t; +extern DECLSPEC SDL_iconv_t SDLCALL SDL_iconv_open(const char *tocode, + const char *fromcode); +extern DECLSPEC int SDLCALL SDL_iconv_close(SDL_iconv_t cd); +extern DECLSPEC size_t SDLCALL SDL_iconv(SDL_iconv_t cd, const char **inbuf, + size_t * inbytesleft, char **outbuf, + size_t * outbytesleft); +/** + * This function converts a string between encodings in one pass, returning a + * string that must be freed with SDL_free() or NULL on error. + */ +extern DECLSPEC char *SDLCALL SDL_iconv_string(const char *tocode, + const char *fromcode, + const char *inbuf, + size_t inbytesleft); +#define SDL_iconv_utf8_locale(S) SDL_iconv_string("", "UTF-8", S, SDL_strlen(S)+1) +#define SDL_iconv_utf8_ucs2(S) (Uint16 *)SDL_iconv_string("UCS-2-INTERNAL", "UTF-8", S, SDL_strlen(S)+1) +#define SDL_iconv_utf8_ucs4(S) (Uint32 *)SDL_iconv_string("UCS-4-INTERNAL", "UTF-8", S, SDL_strlen(S)+1) + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_stdinc_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_surface.h b/external/SDL2/SDL_surface.h new file mode 100644 index 0000000..275beb6 --- /dev/null +++ b/external/SDL2/SDL_surface.h @@ -0,0 +1,502 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_surface.h + * + * Header file for ::SDL_surface definition and management functions. + */ + +#ifndef _SDL_surface_h +#define _SDL_surface_h + +#include "SDL_stdinc.h" +#include "SDL_pixels.h" +#include "SDL_rect.h" +#include "SDL_blendmode.h" +#include "SDL_rwops.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \name Surface flags + * + * These are the currently supported flags for the ::SDL_surface. + * + * \internal + * Used internally (read-only). + */ +/*@{*/ +#define SDL_SWSURFACE 0 /**< Just here for compatibility */ +#define SDL_PREALLOC 0x00000001 /**< Surface uses preallocated memory */ +#define SDL_RLEACCEL 0x00000002 /**< Surface is RLE encoded */ +#define SDL_DONTFREE 0x00000004 /**< Surface is referenced internally */ +/*@}*//*Surface flags*/ + +/** + * Evaluates to true if the surface needs to be locked before access. + */ +#define SDL_MUSTLOCK(S) (((S)->flags & SDL_RLEACCEL) != 0) + +/** + * \brief A collection of pixels used in software blitting. + * + * \note This structure should be treated as read-only, except for \c pixels, + * which, if not NULL, contains the raw pixel data for the surface. + */ +typedef struct SDL_Surface +{ + Uint32 flags; /**< Read-only */ + SDL_PixelFormat *format; /**< Read-only */ + int w, h; /**< Read-only */ + int pitch; /**< Read-only */ + void *pixels; /**< Read-write */ + + /** Application data associated with the surface */ + void *userdata; /**< Read-write */ + + /** information needed for surfaces requiring locks */ + int locked; /**< Read-only */ + void *lock_data; /**< Read-only */ + + /** clipping information */ + SDL_Rect clip_rect; /**< Read-only */ + + /** info for fast blit mapping to other surfaces */ + struct SDL_BlitMap *map; /**< Private */ + + /** Reference count -- used when freeing surface */ + int refcount; /**< Read-mostly */ +} SDL_Surface; + +/** + * \brief The type of function used for surface blitting functions. + */ +typedef int (*SDL_blit) (struct SDL_Surface * src, SDL_Rect * srcrect, + struct SDL_Surface * dst, SDL_Rect * dstrect); + +/** + * Allocate and free an RGB surface. + * + * If the depth is 4 or 8 bits, an empty palette is allocated for the surface. + * If the depth is greater than 8 bits, the pixel format is set using the + * flags '[RGB]mask'. + * + * If the function runs out of memory, it will return NULL. + * + * \param flags The \c flags are obsolete and should be set to 0. + */ +extern DECLSPEC SDL_Surface *SDLCALL SDL_CreateRGBSurface + (Uint32 flags, int width, int height, int depth, + Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask); +extern DECLSPEC SDL_Surface *SDLCALL SDL_CreateRGBSurfaceFrom(void *pixels, + int width, + int height, + int depth, + int pitch, + Uint32 Rmask, + Uint32 Gmask, + Uint32 Bmask, + Uint32 Amask); +extern DECLSPEC void SDLCALL SDL_FreeSurface(SDL_Surface * surface); + +/** + * \brief Set the palette used by a surface. + * + * \return 0, or -1 if the surface format doesn't use a palette. + * + * \note A single palette can be shared with many surfaces. + */ +extern DECLSPEC int SDLCALL SDL_SetSurfacePalette(SDL_Surface * surface, + SDL_Palette * palette); + +/** + * \brief Sets up a surface for directly accessing the pixels. + * + * Between calls to SDL_LockSurface() / SDL_UnlockSurface(), you can write + * to and read from \c surface->pixels, using the pixel format stored in + * \c surface->format. Once you are done accessing the surface, you should + * use SDL_UnlockSurface() to release it. + * + * Not all surfaces require locking. If SDL_MUSTLOCK(surface) evaluates + * to 0, then you can read and write to the surface at any time, and the + * pixel format of the surface will not change. + * + * No operating system or library calls should be made between lock/unlock + * pairs, as critical system locks may be held during this time. + * + * SDL_LockSurface() returns 0, or -1 if the surface couldn't be locked. + * + * \sa SDL_UnlockSurface() + */ +extern DECLSPEC int SDLCALL SDL_LockSurface(SDL_Surface * surface); +/** \sa SDL_LockSurface() */ +extern DECLSPEC void SDLCALL SDL_UnlockSurface(SDL_Surface * surface); + +/** + * Load a surface from a seekable SDL data stream (memory or file). + * + * If \c freesrc is non-zero, the stream will be closed after being read. + * + * The new surface should be freed with SDL_FreeSurface(). + * + * \return the new surface, or NULL if there was an error. + */ +extern DECLSPEC SDL_Surface *SDLCALL SDL_LoadBMP_RW(SDL_RWops * src, + int freesrc); + +/** + * Load a surface from a file. + * + * Convenience macro. + */ +#define SDL_LoadBMP(file) SDL_LoadBMP_RW(SDL_RWFromFile(file, "rb"), 1) + +/** + * Save a surface to a seekable SDL data stream (memory or file). + * + * If \c freedst is non-zero, the stream will be closed after being written. + * + * \return 0 if successful or -1 if there was an error. + */ +extern DECLSPEC int SDLCALL SDL_SaveBMP_RW + (SDL_Surface * surface, SDL_RWops * dst, int freedst); + +/** + * Save a surface to a file. + * + * Convenience macro. + */ +#define SDL_SaveBMP(surface, file) \ + SDL_SaveBMP_RW(surface, SDL_RWFromFile(file, "wb"), 1) + +/** + * \brief Sets the RLE acceleration hint for a surface. + * + * \return 0 on success, or -1 if the surface is not valid + * + * \note If RLE is enabled, colorkey and alpha blending blits are much faster, + * but the surface must be locked before directly accessing the pixels. + */ +extern DECLSPEC int SDLCALL SDL_SetSurfaceRLE(SDL_Surface * surface, + int flag); + +/** + * \brief Sets the color key (transparent pixel) in a blittable surface. + * + * \param surface The surface to update + * \param flag Non-zero to enable colorkey and 0 to disable colorkey + * \param key The transparent pixel in the native surface format + * + * \return 0 on success, or -1 if the surface is not valid + * + * You can pass SDL_RLEACCEL to enable RLE accelerated blits. + */ +extern DECLSPEC int SDLCALL SDL_SetColorKey(SDL_Surface * surface, + int flag, Uint32 key); + +/** + * \brief Gets the color key (transparent pixel) in a blittable surface. + * + * \param surface The surface to update + * \param key A pointer filled in with the transparent pixel in the native + * surface format + * + * \return 0 on success, or -1 if the surface is not valid or colorkey is not + * enabled. + */ +extern DECLSPEC int SDLCALL SDL_GetColorKey(SDL_Surface * surface, + Uint32 * key); + +/** + * \brief Set an additional color value used in blit operations. + * + * \param surface The surface to update. + * \param r The red color value multiplied into blit operations. + * \param g The green color value multiplied into blit operations. + * \param b The blue color value multiplied into blit operations. + * + * \return 0 on success, or -1 if the surface is not valid. + * + * \sa SDL_GetSurfaceColorMod() + */ +extern DECLSPEC int SDLCALL SDL_SetSurfaceColorMod(SDL_Surface * surface, + Uint8 r, Uint8 g, Uint8 b); + + +/** + * \brief Get the additional color value used in blit operations. + * + * \param surface The surface to query. + * \param r A pointer filled in with the current red color value. + * \param g A pointer filled in with the current green color value. + * \param b A pointer filled in with the current blue color value. + * + * \return 0 on success, or -1 if the surface is not valid. + * + * \sa SDL_SetSurfaceColorMod() + */ +extern DECLSPEC int SDLCALL SDL_GetSurfaceColorMod(SDL_Surface * surface, + Uint8 * r, Uint8 * g, + Uint8 * b); + +/** + * \brief Set an additional alpha value used in blit operations. + * + * \param surface The surface to update. + * \param alpha The alpha value multiplied into blit operations. + * + * \return 0 on success, or -1 if the surface is not valid. + * + * \sa SDL_GetSurfaceAlphaMod() + */ +extern DECLSPEC int SDLCALL SDL_SetSurfaceAlphaMod(SDL_Surface * surface, + Uint8 alpha); + +/** + * \brief Get the additional alpha value used in blit operations. + * + * \param surface The surface to query. + * \param alpha A pointer filled in with the current alpha value. + * + * \return 0 on success, or -1 if the surface is not valid. + * + * \sa SDL_SetSurfaceAlphaMod() + */ +extern DECLSPEC int SDLCALL SDL_GetSurfaceAlphaMod(SDL_Surface * surface, + Uint8 * alpha); + +/** + * \brief Set the blend mode used for blit operations. + * + * \param surface The surface to update. + * \param blendMode ::SDL_BlendMode to use for blit blending. + * + * \return 0 on success, or -1 if the parameters are not valid. + * + * \sa SDL_GetSurfaceBlendMode() + */ +extern DECLSPEC int SDLCALL SDL_SetSurfaceBlendMode(SDL_Surface * surface, + SDL_BlendMode blendMode); + +/** + * \brief Get the blend mode used for blit operations. + * + * \param surface The surface to query. + * \param blendMode A pointer filled in with the current blend mode. + * + * \return 0 on success, or -1 if the surface is not valid. + * + * \sa SDL_SetSurfaceBlendMode() + */ +extern DECLSPEC int SDLCALL SDL_GetSurfaceBlendMode(SDL_Surface * surface, + SDL_BlendMode *blendMode); + +/** + * Sets the clipping rectangle for the destination surface in a blit. + * + * If the clip rectangle is NULL, clipping will be disabled. + * + * If the clip rectangle doesn't intersect the surface, the function will + * return SDL_FALSE and blits will be completely clipped. Otherwise the + * function returns SDL_TRUE and blits to the surface will be clipped to + * the intersection of the surface area and the clipping rectangle. + * + * Note that blits are automatically clipped to the edges of the source + * and destination surfaces. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_SetClipRect(SDL_Surface * surface, + const SDL_Rect * rect); + +/** + * Gets the clipping rectangle for the destination surface in a blit. + * + * \c rect must be a pointer to a valid rectangle which will be filled + * with the correct values. + */ +extern DECLSPEC void SDLCALL SDL_GetClipRect(SDL_Surface * surface, + SDL_Rect * rect); + +/** + * Creates a new surface of the specified format, and then copies and maps + * the given surface to it so the blit of the converted surface will be as + * fast as possible. If this function fails, it returns NULL. + * + * The \c flags parameter is passed to SDL_CreateRGBSurface() and has those + * semantics. You can also pass ::SDL_RLEACCEL in the flags parameter and + * SDL will try to RLE accelerate colorkey and alpha blits in the resulting + * surface. + */ +extern DECLSPEC SDL_Surface *SDLCALL SDL_ConvertSurface + (SDL_Surface * src, SDL_PixelFormat * fmt, Uint32 flags); +extern DECLSPEC SDL_Surface *SDLCALL SDL_ConvertSurfaceFormat + (SDL_Surface * src, Uint32 pixel_format, Uint32 flags); + +/** + * \brief Copy a block of pixels of one format to another format + * + * \return 0 on success, or -1 if there was an error + */ +extern DECLSPEC int SDLCALL SDL_ConvertPixels(int width, int height, + Uint32 src_format, + const void * src, int src_pitch, + Uint32 dst_format, + void * dst, int dst_pitch); + +/** + * Performs a fast fill of the given rectangle with \c color. + * + * If \c rect is NULL, the whole surface will be filled with \c color. + * + * The color should be a pixel of the format used by the surface, and + * can be generated by the SDL_MapRGB() function. + * + * \return 0 on success, or -1 on error. + */ +extern DECLSPEC int SDLCALL SDL_FillRect + (SDL_Surface * dst, const SDL_Rect * rect, Uint32 color); +extern DECLSPEC int SDLCALL SDL_FillRects + (SDL_Surface * dst, const SDL_Rect * rects, int count, Uint32 color); + +/** + * Performs a fast blit from the source surface to the destination surface. + * + * This assumes that the source and destination rectangles are + * the same size. If either \c srcrect or \c dstrect are NULL, the entire + * surface (\c src or \c dst) is copied. The final blit rectangles are saved + * in \c srcrect and \c dstrect after all clipping is performed. + * + * \return If the blit is successful, it returns 0, otherwise it returns -1. + * + * The blit function should not be called on a locked surface. + * + * The blit semantics for surfaces with and without alpha and colorkey + * are defined as follows: + * \verbatim + RGBA->RGB: + SDL_SRCALPHA set: + alpha-blend (using alpha-channel). + SDL_SRCCOLORKEY ignored. + SDL_SRCALPHA not set: + copy RGB. + if SDL_SRCCOLORKEY set, only copy the pixels matching the + RGB values of the source colour key, ignoring alpha in the + comparison. + + RGB->RGBA: + SDL_SRCALPHA set: + alpha-blend (using the source per-surface alpha value); + set destination alpha to opaque. + SDL_SRCALPHA not set: + copy RGB, set destination alpha to source per-surface alpha value. + both: + if SDL_SRCCOLORKEY set, only copy the pixels matching the + source colour key. + + RGBA->RGBA: + SDL_SRCALPHA set: + alpha-blend (using the source alpha channel) the RGB values; + leave destination alpha untouched. [Note: is this correct?] + SDL_SRCCOLORKEY ignored. + SDL_SRCALPHA not set: + copy all of RGBA to the destination. + if SDL_SRCCOLORKEY set, only copy the pixels matching the + RGB values of the source colour key, ignoring alpha in the + comparison. + + RGB->RGB: + SDL_SRCALPHA set: + alpha-blend (using the source per-surface alpha value). + SDL_SRCALPHA not set: + copy RGB. + both: + if SDL_SRCCOLORKEY set, only copy the pixels matching the + source colour key. + \endverbatim + * + * You should call SDL_BlitSurface() unless you know exactly how SDL + * blitting works internally and how to use the other blit functions. + */ +#define SDL_BlitSurface SDL_UpperBlit + +/** + * This is the public blit function, SDL_BlitSurface(), and it performs + * rectangle validation and clipping before passing it to SDL_LowerBlit() + */ +extern DECLSPEC int SDLCALL SDL_UpperBlit + (SDL_Surface * src, const SDL_Rect * srcrect, + SDL_Surface * dst, SDL_Rect * dstrect); + +/** + * This is a semi-private blit function and it performs low-level surface + * blitting only. + */ +extern DECLSPEC int SDLCALL SDL_LowerBlit + (SDL_Surface * src, SDL_Rect * srcrect, + SDL_Surface * dst, SDL_Rect * dstrect); + +/** + * \brief Perform a fast, low quality, stretch blit between two surfaces of the + * same pixel format. + * + * \note This function uses a static buffer, and is not thread-safe. + */ +extern DECLSPEC int SDLCALL SDL_SoftStretch(SDL_Surface * src, + const SDL_Rect * srcrect, + SDL_Surface * dst, + const SDL_Rect * dstrect); + +#define SDL_BlitScaled SDL_UpperBlitScaled + +/** + * This is the public scaled blit function, SDL_BlitScaled(), and it performs + * rectangle validation and clipping before passing it to SDL_LowerBlitScaled() + */ +extern DECLSPEC int SDLCALL SDL_UpperBlitScaled + (SDL_Surface * src, const SDL_Rect * srcrect, + SDL_Surface * dst, SDL_Rect * dstrect); + +/** + * This is a semi-private blit function and it performs low-level surface + * scaled blitting only. + */ +extern DECLSPEC int SDLCALL SDL_LowerBlitScaled + (SDL_Surface * src, SDL_Rect * srcrect, + SDL_Surface * dst, SDL_Rect * dstrect); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_surface_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_system.h b/external/SDL2/SDL_system.h new file mode 100644 index 0000000..5ae9e40 --- /dev/null +++ b/external/SDL2/SDL_system.h @@ -0,0 +1,106 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_system.h + * + * Include file for platform specific SDL API functions + */ + +#ifndef _SDL_system_h +#define _SDL_system_h + +#include "SDL_stdinc.h" + +#if defined(__IPHONEOS__) && __IPHONEOS__ +#include "SDL_video.h" +#include "SDL_keyboard.h" +#endif + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/* Platform specific functions for iOS */ +#if defined(__IPHONEOS__) && __IPHONEOS__ + +extern DECLSPEC int SDLCALL SDL_iPhoneSetAnimationCallback(SDL_Window * window, int interval, void (*callback)(void*), void *callbackParam); +extern DECLSPEC void SDLCALL SDL_iPhoneSetEventPump(SDL_bool enabled); + +#endif /* __IPHONEOS__ */ + + +/* Platform specific functions for Android */ +#if defined(__ANDROID__) && __ANDROID__ + +/* Get the JNI environment for the current thread + This returns JNIEnv*, but the prototype is void* so we don't need jni.h + */ +extern DECLSPEC void * SDLCALL SDL_AndroidGetJNIEnv(); + +/* Get the SDL Activity object for the application + This returns jobject, but the prototype is void* so we don't need jni.h + */ +extern DECLSPEC void * SDLCALL SDL_AndroidGetActivity(); + +/* See the official Android developer guide for more information: + http://developer.android.com/guide/topics/data/data-storage.html +*/ +#define SDL_ANDROID_EXTERNAL_STORAGE_READ 0x01 +#define SDL_ANDROID_EXTERNAL_STORAGE_WRITE 0x02 + +/* Get the path used for internal storage for this application. + This path is unique to your application and cannot be written to + by other applications. + */ +extern DECLSPEC const char * SDLCALL SDL_AndroidGetInternalStoragePath(); + +/* Get the current state of external storage, a bitmask of these values: + SDL_ANDROID_EXTERNAL_STORAGE_READ + SDL_ANDROID_EXTERNAL_STORAGE_WRITE + If external storage is currently unavailable, this will return 0. +*/ +extern DECLSPEC int SDLCALL SDL_AndroidGetExternalStorageState(); + +/* Get the path used for external storage for this application. + This path is unique to your application, but is public and can be + written to by other applications. + */ +extern DECLSPEC const char * SDLCALL SDL_AndroidGetExternalStoragePath(); + +#endif /* __ANDROID__ */ + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_system_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_syswm.h b/external/SDL2/SDL_syswm.h new file mode 100644 index 0000000..786e12e --- /dev/null +++ b/external/SDL2/SDL_syswm.h @@ -0,0 +1,241 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_syswm.h + * + * Include file for SDL custom system window manager hooks. + */ + +#ifndef _SDL_syswm_h +#define _SDL_syswm_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_video.h" +#include "SDL_version.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \file SDL_syswm.h + * + * Your application has access to a special type of event ::SDL_SYSWMEVENT, + * which contains window-manager specific information and arrives whenever + * an unhandled window event occurs. This event is ignored by default, but + * you can enable it with SDL_EventState(). + */ +#ifdef SDL_PROTOTYPES_ONLY +struct SDL_SysWMinfo; +#else + +#if defined(SDL_VIDEO_DRIVER_WINDOWS) +#define WIN32_LEAN_AND_MEAN +#include +#endif + +/* This is the structure for custom window manager events */ +#if defined(SDL_VIDEO_DRIVER_X11) +#if defined(__APPLE__) && defined(__MACH__) +/* conflicts with Quickdraw.h */ +#define Cursor X11Cursor +#endif + +#include +#include + +#if defined(__APPLE__) && defined(__MACH__) +/* matches the re-define above */ +#undef Cursor +#endif + +#endif /* defined(SDL_VIDEO_DRIVER_X11) */ + +#if defined(SDL_VIDEO_DRIVER_DIRECTFB) +#include +#endif + +#if defined(SDL_VIDEO_DRIVER_COCOA) +#ifdef __OBJC__ +#include +#else +typedef struct _NSWindow NSWindow; +#endif +#endif + +#if defined(SDL_VIDEO_DRIVER_UIKIT) +#ifdef __OBJC__ +#include +#else +typedef struct _UIWindow UIWindow; +#endif +#endif + +/** + * These are the various supported windowing subsystems + */ +typedef enum +{ + SDL_SYSWM_UNKNOWN, + SDL_SYSWM_WINDOWS, + SDL_SYSWM_X11, + SDL_SYSWM_DIRECTFB, + SDL_SYSWM_COCOA, + SDL_SYSWM_UIKIT, +} SDL_SYSWM_TYPE; + +/** + * The custom event structure. + */ +struct SDL_SysWMmsg +{ + SDL_version version; + SDL_SYSWM_TYPE subsystem; + union + { +#if defined(SDL_VIDEO_DRIVER_WINDOWS) + struct { + HWND hwnd; /**< The window for the message */ + UINT msg; /**< The type of message */ + WPARAM wParam; /**< WORD message parameter */ + LPARAM lParam; /**< LONG message parameter */ + } win; +#endif +#if defined(SDL_VIDEO_DRIVER_X11) + struct { + XEvent event; + } x11; +#endif +#if defined(SDL_VIDEO_DRIVER_DIRECTFB) + struct { + DFBEvent event; + } dfb; +#endif +#if defined(SDL_VIDEO_DRIVER_COCOA) + struct + { + /* No Cocoa window events yet */ + } cocoa; +#endif +#if defined(SDL_VIDEO_DRIVER_UIKIT) + struct + { + /* No UIKit window events yet */ + } uikit; +#endif + /* Can't have an empty union */ + int dummy; + } msg; +}; + +/** + * The custom window manager information structure. + * + * When this structure is returned, it holds information about which + * low level system it is using, and will be one of SDL_SYSWM_TYPE. + */ +struct SDL_SysWMinfo +{ + SDL_version version; + SDL_SYSWM_TYPE subsystem; + union + { +#if defined(SDL_VIDEO_DRIVER_WINDOWS) + struct + { + HWND window; /**< The window handle */ + } win; +#endif +#if defined(SDL_VIDEO_DRIVER_X11) + struct + { + Display *display; /**< The X11 display */ + Window window; /**< The X11 window */ + } x11; +#endif +#if defined(SDL_VIDEO_DRIVER_DIRECTFB) + struct + { + IDirectFB *dfb; /**< The directfb main interface */ + IDirectFBWindow *window; /**< The directfb window handle */ + IDirectFBSurface *surface; /**< The directfb client surface */ + } dfb; +#endif +#if defined(SDL_VIDEO_DRIVER_COCOA) + struct + { + NSWindow *window; /* The Cocoa window */ + } cocoa; +#endif +#if defined(SDL_VIDEO_DRIVER_UIKIT) + struct + { + UIWindow *window; /* The UIKit window */ + } uikit; +#endif + /* Can't have an empty union */ + int dummy; + } info; +}; + +#endif /* SDL_PROTOTYPES_ONLY */ + +typedef struct SDL_SysWMinfo SDL_SysWMinfo; + +/* Function prototypes */ +/** + * \brief This function allows access to driver-dependent window information. + * + * \param window The window about which information is being requested + * \param info This structure must be initialized with the SDL version, and is + * then filled in with information about the given window. + * + * \return SDL_TRUE if the function is implemented and the version member of + * the \c info struct is valid, SDL_FALSE otherwise. + * + * You typically use this function like this: + * \code + * SDL_SysWMinfo info; + * SDL_VERSION(&info.version); + * if ( SDL_GetWindowWMInfo(&info) ) { ... } + * \endcode + */ +extern DECLSPEC SDL_bool SDLCALL SDL_GetWindowWMInfo(SDL_Window * window, + SDL_SysWMinfo * info); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_syswm_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_test.h b/external/SDL2/SDL_test.h new file mode 100644 index 0000000..15e7689 --- /dev/null +++ b/external/SDL2/SDL_test.h @@ -0,0 +1,72 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_test.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +#ifndef _SDL_test_h +#define _SDL_test_h + +#include "SDL.h" +#include "SDL_test_common.h" +#include "SDL_test_font.h" +#include "SDL_test_random.h" +#include "SDL_test_fuzzer.h" +#include "SDL_test_crc32.h" +#include "SDL_test_md5.h" +#include "SDL_test_log.h" +#include "SDL_test_assert.h" +#include "SDL_test_harness.h" +#include "SDL_test_images.h" +#include "SDL_test_compare.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/* Global definitions */ + +/* + * Note: Maximum size of SDLTest log message is less than SDLs limit + * to ensure we can fit additional information such as the timestamp. + */ +#define SDLTEST_MAX_LOGMESSAGE_LENGTH 3584 + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_test_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_test_assert.h b/external/SDL2/SDL_test_assert.h new file mode 100644 index 0000000..17b6d26 --- /dev/null +++ b/external/SDL2/SDL_test_assert.h @@ -0,0 +1,109 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_test_assert.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +/* + * + * Assert API for test code and test cases + * + */ + +#ifndef _SDL_test_assert_h +#define _SDL_test_assert_h + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \brief Fails the assert. + */ +#define ASSERT_FAIL 0 + +/** + * \brief Passes the assert. + */ +#define ASSERT_PASS 1 + +/** + * \brief Assert that logs and break execution flow on failures. + * + * \param assertCondition Evaluated condition or variable to assert; fail (==0) or pass (!=0). + * \param assertDescription Message to log with the assert describing it. + */ +void SDLTest_Assert(int assertCondition, const char *assertDescription, ...); + +/** + * \brief Assert for test cases that logs but does not break execution flow on failures. Updates assertion counters. + * + * \param assertCondition Evaluated condition or variable to assert; fail (==0) or pass (!=0). + * \param assertDescription Message to log with the assert describing it. + * + * \returns Returns the assertCondition so it can be used to externally to break execution flow if desired. + */ +int SDLTest_AssertCheck(int assertCondition, const char *assertDescription, ...); + +/** + * \brief Explicitely pass without checking an assertion condition. Updates assertion counter. + * + * \param assertDescription Message to log with the assert describing it. + */ +void SDLTest_AssertPass(const char *assertDescription, ...); + +/** + * \brief Resets the assert summary counters to zero. + */ +void SDLTest_ResetAssertSummary(); + +/** + * \brief Logs summary of all assertions (total, pass, fail) since last reset as INFO or ERROR. + */ +void SDLTest_LogAssertSummary(); + + +/** + * \brief Converts the current assert summary state to a test result. + * + * \returns TEST_RESULT_PASSED, TEST_RESULT_FAILED, or TEST_RESULT_NO_ASSERT + */ +int SDLTest_AssertSummaryToTestResult(); + +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_test_assert_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_test_common.h b/external/SDL2/SDL_test_common.h new file mode 100644 index 0000000..9e9122f --- /dev/null +++ b/external/SDL2/SDL_test_common.h @@ -0,0 +1,186 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_test_common.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +/* Ported from original test\common.h file. */ + +#ifndef _SDL_test_common_h +#define _SDL_test_common_h + +#include "SDL.h" + +#if defined(__PSP__) +#define DEFAULT_WINDOW_WIDTH 480 +#define DEFAULT_WINDOW_HEIGHT 272 +#else +#define DEFAULT_WINDOW_WIDTH 640 +#define DEFAULT_WINDOW_HEIGHT 480 +#endif + +#define VERBOSE_VIDEO 0x00000001 +#define VERBOSE_MODES 0x00000002 +#define VERBOSE_RENDER 0x00000004 +#define VERBOSE_EVENT 0x00000008 +#define VERBOSE_AUDIO 0x00000010 + +typedef struct +{ + /* SDL init flags */ + char **argv; + Uint32 flags; + Uint32 verbose; + + /* Video info */ + const char *videodriver; + int display; + const char *window_title; + const char *window_icon; + Uint32 window_flags; + int window_x; + int window_y; + int window_w; + int window_h; + int window_minW; + int window_minH; + int window_maxW; + int window_maxH; + int depth; + int refresh_rate; + int num_windows; + SDL_Window **windows; + + /* Renderer info */ + const char *renderdriver; + Uint32 render_flags; + SDL_bool skip_renderer; + SDL_Renderer **renderers; + + /* Audio info */ + const char *audiodriver; + SDL_AudioSpec audiospec; + + /* GL settings */ + int gl_red_size; + int gl_green_size; + int gl_blue_size; + int gl_alpha_size; + int gl_buffer_size; + int gl_depth_size; + int gl_stencil_size; + int gl_double_buffer; + int gl_accum_red_size; + int gl_accum_green_size; + int gl_accum_blue_size; + int gl_accum_alpha_size; + int gl_stereo; + int gl_multisamplebuffers; + int gl_multisamplesamples; + int gl_retained_backing; + int gl_accelerated; + int gl_major_version; + int gl_minor_version; +} SDLTest_CommonState; + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/* Function prototypes */ + +/** + * \brief Parse command line parameters and create common state. + * + * \param argv Array of command line parameters + * \param flags Flags indicating which subsystem to initialize (i.e. SDL_INIT_VIDEO | SDL_INIT_AUDIO) + * + * \returns Returns a newly allocated common state object. + */ +SDLTest_CommonState *SDLTest_CommonCreateState(char **argv, Uint32 flags); + +/** + * \brief Process one common argument. + * + * \param state The common state describing the test window to create. + * \param index The index of the argument to process in argv[]. + * + * \returns The number of arguments processed (i.e. 1 for --fullscreen, 2 for --video [videodriver], or -1 on error. + */ +int SDLTest_CommonArg(SDLTest_CommonState * state, int index); + +/** + * \brief Returns common usage information + * + * \param state The common state describing the test window to create. + * + * \returns String with usage information + */ +const char *SDLTest_CommonUsage(SDLTest_CommonState * state); + +/** + * \brief Open test window. + * + * \param state The common state describing the test window to create. + * + * \returns True if initialization succeeded, false otherwise + */ +SDL_bool SDLTest_CommonInit(SDLTest_CommonState * state); + +/** + * \brief Common event handler for test windows. + * + * \param state The common state used to create test window. + * \param event The event to handle. + * \param done Flag indicating we are done. + * + */ +void SDLTest_CommonEvent(SDLTest_CommonState * state, SDL_Event * event, int *done); + +/** + * \brief Close test window. + * + * \param state The common state used to create test window. + * + */ +void SDLTest_CommonQuit(SDLTest_CommonState * state); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_test_common_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_test_compare.h b/external/SDL2/SDL_test_compare.h new file mode 100644 index 0000000..e145f69 --- /dev/null +++ b/external/SDL2/SDL_test_compare.h @@ -0,0 +1,73 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_test_compare.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +/* + + Defines comparison functions (i.e. for surfaces). + +*/ + +#ifndef _SDL_test_compare_h +#define _SDL_test_compare_h + +#include "SDL.h" + +#include "SDL_test_images.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \brief Compares a surface and with reference image data for equality + * + * \param surface Surface used in comparison + * \param referenceSurface Test Surface used in comparison + * \param allowable_error Allowable difference (squared) in blending accuracy. + * + * \returns 0 if comparison succeeded, >0 (=number of pixels where comparison failed) if comparison failed, -1 if any of the surfaces were NULL, -2 if the surface sizes differ. + */ +int SDLTest_CompareSurfaces(SDL_Surface *surface, SDL_Surface *referenceSurface, int allowable_error); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_test_compare_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_test_crc32.h b/external/SDL2/SDL_test_crc32.h new file mode 100644 index 0000000..0685a37 --- /dev/null +++ b/external/SDL2/SDL_test_crc32.h @@ -0,0 +1,128 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_test_crc32.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +/* + + Implements CRC32 calculations (default output is Perl String::CRC32 compatible). + +*/ + +#ifndef _SDL_test_crc32_h +#define _SDL_test_crc32_h + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + + +/* ------------ Definitions --------- */ + +/* Definition shared by all CRC routines */ + +#ifndef CrcUint32 + #define CrcUint32 unsigned int +#endif +#ifndef CrcUint8 + #define CrcUint8 unsigned char +#endif + +#ifdef ORIGINAL_METHOD + #define CRC32_POLY 0x04c11db7 /* AUTODIN II, Ethernet, & FDDI */ +#else + #define CRC32_POLY 0xEDB88320 /* Perl String::CRC32 compatible */ +#endif + +/** + * Data structure for CRC32 (checksum) computation + */ + typedef struct { + CrcUint32 crc32_table[256]; /* CRC table */ + } SDLTest_Crc32Context; + +/* ---------- Function Prototypes ------------- */ + +/** + * /brief Initialize the CRC context + * + * Note: The function initializes the crc table required for all crc calculations. + * + * /param crcContext pointer to context variable + * + * /returns 0 for OK, -1 on error + * + */ + int SDLTest_Crc32Init(SDLTest_Crc32Context * crcContext); + + +/** + * /brief calculate a crc32 from a data block + * + * /param crcContext pointer to context variable + * /param inBuf input buffer to checksum + * /param inLen length of input buffer + * /param crc32 pointer to Uint32 to store the final CRC into + * + * /returns 0 for OK, -1 on error + * + */ +int SDLTest_crc32Calc(SDLTest_Crc32Context * crcContext, CrcUint8 *inBuf, CrcUint32 inLen, CrcUint32 *crc32); + +/* Same routine broken down into three steps */ +int SDLTest_Crc32CalcStart(SDLTest_Crc32Context * crcContext, CrcUint32 *crc32); +int SDLTest_Crc32CalcEnd(SDLTest_Crc32Context * crcContext, CrcUint32 *crc32); +int SDLTest_Crc32CalcBuffer(SDLTest_Crc32Context * crcContext, CrcUint8 *inBuf, CrcUint32 inLen, CrcUint32 *crc32); + + +/** + * /brief clean up CRC context + * + * /param crcContext pointer to context variable + * + * /returns 0 for OK, -1 on error + * +*/ + +int SDLTest_Crc32Done(SDLTest_Crc32Context * crcContext); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_test_crc32_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_test_font.h b/external/SDL2/SDL_test_font.h new file mode 100644 index 0000000..d894601 --- /dev/null +++ b/external/SDL2/SDL_test_font.h @@ -0,0 +1,66 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_test_font.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +#ifndef _SDL_test_font_h +#define _SDL_test_font_h + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/* Function prototypes */ + +/** + * \brief Draw a string in the currently set font. + * + * \param renderer The renderer to draw on. + * \param x The X coordinate of the upper left corner of the string. + * \param y The Y coordinate of the upper left corner of the string. + * \param s The string to draw. + * + * \returns Returns 0 on success, -1 on failure. + */ +int SDLTest_DrawString(SDL_Renderer * renderer, int x, int y, const char *s); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_test_font_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_test_fuzzer.h b/external/SDL2/SDL_test_fuzzer.h new file mode 100644 index 0000000..db30a1d --- /dev/null +++ b/external/SDL2/SDL_test_fuzzer.h @@ -0,0 +1,388 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_test_fuzzer.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +/* + + Data generators for fuzzing test data in a reproducible way. + +*/ + +#ifndef _SDL_test_fuzzer_h +#define _SDL_test_fuzzer_h + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + + +/* + Based on GSOC code by Markus Kauppila +*/ + + +/** + * \file + * Note: The fuzzer implementation uses a static instance of random context + * internally which makes it thread-UNsafe. + */ + +/** + * Initializes the fuzzer for a test + * + * /param execKey Execution "Key" that initializes the random number generator uniquely for the test. + * + */ +void SDLTest_FuzzerInit(Uint64 execKey); + + +/** + * Returns a random Uint8 + * + * \returns Generated integer + */ +Uint8 SDLTest_RandomUint8(); + +/** + * Returns a random Sint8 + * + * \returns Generated signed integer + */ +Sint8 SDLTest_RandomSint8(); + + +/** + * Returns a random Uint16 + * + * \returns Generated integer + */ +Uint16 SDLTest_RandomUint16(); + +/** + * Returns a random Sint16 + * + * \returns Generated signed integer + */ +Sint16 SDLTest_RandomSint16(); + + +/** + * Returns a random integer + * + * \returns Generated integer + */ +Sint32 SDLTest_RandomSint32(); + + +/** + * Returns a random positive integer + * + * \returns Generated integer + */ +Uint32 SDLTest_RandomUint32(); + +/** + * Returns random Uint64. + * + * \returns Generated integer + */ +Uint64 SDLTest_RandomUint64(); + + +/** + * Returns random Sint64. + * + * \returns Generated signed integer + */ +Sint64 SDLTest_RandomSint64(); + +/** + * \returns random float in range [0.0 - 1.0[ + */ +float SDLTest_RandomUnitFloat(); + +/** + * \returns random double in range [0.0 - 1.0[ + */ +double SDLTest_RandomUnitDouble(); + +/** + * \returns random float. + * + */ +float SDLTest_RandomFloat(); + +/** + * \returns random double. + * + */ +double SDLTest_RandomDouble(); + +/** + * Returns a random boundary value for Uint8 within the given boundaries. + * Boundaries are inclusive, see the usage examples below. If validDomain + * is true, the function will only return valid boundaries, otherwise non-valid + * boundaries are also possible. + * If boundary1 > boundary2, the values are swapped + * + * Usage examples: + * RandomUint8BoundaryValue(10, 20, SDL_TRUE) returns 10, 11, 19 or 20 + * RandomUint8BoundaryValue(1, 20, SDL_FALSE) returns 0 or 21 + * RandomUint8BoundaryValue(0, 99, SDL_FALSE) returns 100 + * RandomUint8BoundaryValue(0, 255, SDL_FALSE) returns 0 (error set) + * + * \param boundary1 Lower boundary limit + * \param boundary2 Upper boundary limit + * \param validDomain Should the generated boundary be valid (=within the bounds) or not? + * + * \returns Random boundary value for the given range and domain or 0 with error set + */ +Uint8 SDLTest_RandomUint8BoundaryValue(Uint8 boundary1, Uint8 boundary2, SDL_bool validDomain); + +/** + * Returns a random boundary value for Uint16 within the given boundaries. + * Boundaries are inclusive, see the usage examples below. If validDomain + * is true, the function will only return valid boundaries, otherwise non-valid + * boundaries are also possible. + * If boundary1 > boundary2, the values are swapped + * + * Usage examples: + * RandomUint16BoundaryValue(10, 20, SDL_TRUE) returns 10, 11, 19 or 20 + * RandomUint16BoundaryValue(1, 20, SDL_FALSE) returns 0 or 21 + * RandomUint16BoundaryValue(0, 99, SDL_FALSE) returns 100 + * RandomUint16BoundaryValue(0, 0xFFFF, SDL_FALSE) returns 0 (error set) + * + * \param boundary1 Lower boundary limit + * \param boundary2 Upper boundary limit + * \param validDomain Should the generated boundary be valid (=within the bounds) or not? + * + * \returns Random boundary value for the given range and domain or 0 with error set + */ +Uint16 SDLTest_RandomUint16BoundaryValue(Uint16 boundary1, Uint16 boundary2, SDL_bool validDomain); + +/** + * Returns a random boundary value for Uint32 within the given boundaries. + * Boundaries are inclusive, see the usage examples below. If validDomain + * is true, the function will only return valid boundaries, otherwise non-valid + * boundaries are also possible. + * If boundary1 > boundary2, the values are swapped + * + * Usage examples: + * RandomUint32BoundaryValue(10, 20, SDL_TRUE) returns 10, 11, 19 or 20 + * RandomUint32BoundaryValue(1, 20, SDL_FALSE) returns 0 or 21 + * RandomUint32BoundaryValue(0, 99, SDL_FALSE) returns 100 + * RandomUint32BoundaryValue(0, 0xFFFFFFFF, SDL_FALSE) returns 0 (with error set) + * + * \param boundary1 Lower boundary limit + * \param boundary2 Upper boundary limit + * \param validDomain Should the generated boundary be valid (=within the bounds) or not? + * + * \returns Random boundary value for the given range and domain or 0 with error set + */ +Uint32 SDLTest_RandomUint32BoundaryValue(Uint32 boundary1, Uint32 boundary2, SDL_bool validDomain); + +/** + * Returns a random boundary value for Uint64 within the given boundaries. + * Boundaries are inclusive, see the usage examples below. If validDomain + * is true, the function will only return valid boundaries, otherwise non-valid + * boundaries are also possible. + * If boundary1 > boundary2, the values are swapped + * + * Usage examples: + * RandomUint64BoundaryValue(10, 20, SDL_TRUE) returns 10, 11, 19 or 20 + * RandomUint64BoundaryValue(1, 20, SDL_FALSE) returns 0 or 21 + * RandomUint64BoundaryValue(0, 99, SDL_FALSE) returns 100 + * RandomUint64BoundaryValue(0, 0xFFFFFFFFFFFFFFFF, SDL_FALSE) returns 0 (with error set) + * + * \param boundary1 Lower boundary limit + * \param boundary2 Upper boundary limit + * \param validDomain Should the generated boundary be valid (=within the bounds) or not? + * + * \returns Random boundary value for the given range and domain or 0 with error set + */ +Uint64 SDLTest_RandomUint64BoundaryValue(Uint64 boundary1, Uint64 boundary2, SDL_bool validDomain); + +/** + * Returns a random boundary value for Sint8 within the given boundaries. + * Boundaries are inclusive, see the usage examples below. If validDomain + * is true, the function will only return valid boundaries, otherwise non-valid + * boundaries are also possible. + * If boundary1 > boundary2, the values are swapped + * + * Usage examples: + * RandomSint8BoundaryValue(-10, 20, SDL_TRUE) returns -11, -10, 19 or 20 + * RandomSint8BoundaryValue(-100, -10, SDL_FALSE) returns -101 or -9 + * RandomSint8BoundaryValue(SINT8_MIN, 99, SDL_FALSE) returns 100 + * RandomSint8BoundaryValue(SINT8_MIN, SINT8_MAX, SDL_FALSE) returns SINT8_MIN (== error value) with error set + * + * \param boundary1 Lower boundary limit + * \param boundary2 Upper boundary limit + * \param validDomain Should the generated boundary be valid (=within the bounds) or not? + * + * \returns Random boundary value for the given range and domain or SINT8_MIN with error set + */ +Sint8 SDLTest_RandomSint8BoundaryValue(Sint8 boundary1, Sint8 boundary2, SDL_bool validDomain); + + +/** + * Returns a random boundary value for Sint16 within the given boundaries. + * Boundaries are inclusive, see the usage examples below. If validDomain + * is true, the function will only return valid boundaries, otherwise non-valid + * boundaries are also possible. + * If boundary1 > boundary2, the values are swapped + * + * Usage examples: + * RandomSint16BoundaryValue(-10, 20, SDL_TRUE) returns -11, -10, 19 or 20 + * RandomSint16BoundaryValue(-100, -10, SDL_FALSE) returns -101 or -9 + * RandomSint16BoundaryValue(SINT16_MIN, 99, SDL_FALSE) returns 100 + * RandomSint16BoundaryValue(SINT16_MIN, SINT16_MAX, SDL_FALSE) returns SINT16_MIN (== error value) with error set + * + * \param boundary1 Lower boundary limit + * \param boundary2 Upper boundary limit + * \param validDomain Should the generated boundary be valid (=within the bounds) or not? + * + * \returns Random boundary value for the given range and domain or SINT16_MIN with error set + */ +Sint16 SDLTest_RandomSint16BoundaryValue(Sint16 boundary1, Sint16 boundary2, SDL_bool validDomain); + +/** + * Returns a random boundary value for Sint32 within the given boundaries. + * Boundaries are inclusive, see the usage examples below. If validDomain + * is true, the function will only return valid boundaries, otherwise non-valid + * boundaries are also possible. + * If boundary1 > boundary2, the values are swapped + * + * Usage examples: + * RandomSint32BoundaryValue(-10, 20, SDL_TRUE) returns -11, -10, 19 or 20 + * RandomSint32BoundaryValue(-100, -10, SDL_FALSE) returns -101 or -9 + * RandomSint32BoundaryValue(SINT32_MIN, 99, SDL_FALSE) returns 100 + * RandomSint32BoundaryValue(SINT32_MIN, SINT32_MAX, SDL_FALSE) returns SINT32_MIN (== error value) + * + * \param boundary1 Lower boundary limit + * \param boundary2 Upper boundary limit + * \param validDomain Should the generated boundary be valid (=within the bounds) or not? + * + * \returns Random boundary value for the given range and domain or SINT32_MIN with error set + */ +Sint32 SDLTest_RandomSint32BoundaryValue(Sint32 boundary1, Sint32 boundary2, SDL_bool validDomain); + +/** + * Returns a random boundary value for Sint64 within the given boundaries. + * Boundaries are inclusive, see the usage examples below. If validDomain + * is true, the function will only return valid boundaries, otherwise non-valid + * boundaries are also possible. + * If boundary1 > boundary2, the values are swapped + * + * Usage examples: + * RandomSint64BoundaryValue(-10, 20, SDL_TRUE) returns -11, -10, 19 or 20 + * RandomSint64BoundaryValue(-100, -10, SDL_FALSE) returns -101 or -9 + * RandomSint64BoundaryValue(SINT64_MIN, 99, SDL_FALSE) returns 100 + * RandomSint64BoundaryValue(SINT64_MIN, SINT64_MAX, SDL_FALSE) returns SINT64_MIN (== error value) and error set + * + * \param boundary1 Lower boundary limit + * \param boundary2 Upper boundary limit + * \param validDomain Should the generated boundary be valid (=within the bounds) or not? + * + * \returns Random boundary value for the given range and domain or SINT64_MIN with error set + */ +Sint64 SDLTest_RandomSint64BoundaryValue(Sint64 boundary1, Sint64 boundary2, SDL_bool validDomain); + + +/** + * Returns integer in range [min, max] (inclusive). + * Min and max values can be negative values. + * If Max in smaller tham min, then the values are swapped. + * Min and max are the same value, that value will be returned. + * + * \param min Minimum inclusive value of returned random number + * \param max Maximum inclusive value of returned random number + * + * \returns Generated random integer in range + */ +Sint32 SDLTest_RandomIntegerInRange(Sint32 min, Sint32 max); + + +/** + * Generates random null-terminated string. The minimum length for + * the string is 1 character, maximum length for the string is 255 + * characters and it can contain ASCII characters from 32 to 126. + * + * Note: Returned string needs to be deallocated. + * + * \returns Newly allocated random string; or NULL if length was invalid or string could not be allocated. + */ +char * SDLTest_RandomAsciiString(); + + +/** + * Generates random null-terminated string. The maximum length for + * the string is defined by the maxLength parameter. + * String can contain ASCII characters from 32 to 126. + * + * Note: Returned string needs to be deallocated. + * + * \param maxLength The maximum length of the generated string. + * + * \returns Newly allocated random string; or NULL if maxLength was invalid or string could not be allocated. + */ +char * SDLTest_RandomAsciiStringWithMaximumLength(int maxLength); + + +/** + * Generates random null-terminated string. The length for + * the string is defined by the size parameter. + * String can contain ASCII characters from 32 to 126. + * + * Note: Returned string needs to be deallocated. + * + * \param size The length of the generated string + * + * \returns Newly allocated random string; or NULL if size was invalid or string could not be allocated. + */ +char * SDLTest_RandomAsciiStringOfSize(int size); + +/** + * Returns the invocation count for the fuzzer since last ...FuzzerInit. + */ +int SDLTest_GetFuzzerInvocationCount(); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_test_fuzzer_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_test_harness.h b/external/SDL2/SDL_test_harness.h new file mode 100644 index 0000000..e5fe673 --- /dev/null +++ b/external/SDL2/SDL_test_harness.h @@ -0,0 +1,126 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_test_harness.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +/* + Defines types for test case definitions and the test execution harness API. + + Based on original GSOC code by Markus Kauppila +*/ + +#ifndef _SDL_test_harness_h +#define _SDL_test_harness_h + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + + +//! Definitions for test case structures +#define TEST_ENABLED 1 +#define TEST_DISABLED 0 + +//! Definition of all the possible test return values of the test case method +#define TEST_ABORTED -1 +#define TEST_COMPLETED 0 +#define TEST_SKIPPED 1 + +//! Definition of all the possible test results for the harness +#define TEST_RESULT_PASSED 0 +#define TEST_RESULT_FAILED 1 +#define TEST_RESULT_NO_ASSERT 2 +#define TEST_RESULT_SKIPPED 3 +#define TEST_RESULT_SETUP_FAILURE 4 + +//!< Function pointer to a test case setup function (run before every test) +typedef void (*SDLTest_TestCaseSetUpFp)(void *arg); + +//!< Function pointer to a test case function +typedef void (*SDLTest_TestCaseFp)(void *arg); + +//!< Function pointer to a test case teardown function (run after every test) +typedef void (*SDLTest_TestCaseTearDownFp)(void *arg); + +/** + * Holds information about a single test case. + */ +typedef struct SDLTest_TestCaseReference { + /*!< Func2Stress */ + SDLTest_TestCaseFp testCase; + /*!< Short name (or function name) "Func2Stress" */ + char *name; + /*!< Long name or full description "This test pushes func2() to the limit." */ + char *description; + /*!< Set to TEST_ENABLED or TEST_DISABLED (test won't be run) */ + int enabled; +} SDLTest_TestCaseReference; + +/** + * Holds information about a test suite (multiple test cases). + */ +typedef struct SDLTest_TestSuiteReference { + /*!< "PlatformSuite" */ + char *name; + /*!< The function that is run before each test. NULL skips. */ + SDLTest_TestCaseSetUpFp testSetUp; + /*!< The test cases that are run as part of the suite. Last item should be NULL. */ + const SDLTest_TestCaseReference **testCases; + /*!< The function that is run after each test. NULL skips. */ + SDLTest_TestCaseTearDownFp testTearDown; +} SDLTest_TestSuiteReference; + + +/** + * \brief Execute a test suite using the given run seed and execution key. + * + * \param testSuites Suites containing the test case. + * \param userRunSeed Custom run seed provided by user, or NULL to autogenerate one. + * \param userExecKey Custom execution key provided by user, or 0 to autogenerate one. + * \param filter Filter specification. NULL disables. Case sensitive. + * \param testIterations Number of iterations to run each test case. + * + * \returns Test run result; 0 when all tests passed, 1 if any tests failed. + */ +int SDLTest_RunSuites(SDLTest_TestSuiteReference *testSuites[], const char *userRunSeed, Uint64 userExecKey, const char *filter, int testIterations); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_test_harness_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_test_images.h b/external/SDL2/SDL_test_images.h new file mode 100644 index 0000000..ea72229 --- /dev/null +++ b/external/SDL2/SDL_test_images.h @@ -0,0 +1,82 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_test_images.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +/* + + Defines some images for tests. + +*/ + +#ifndef _SDL_test_images_h +#define _SDL_test_images_h + +#include "SDL.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + *Type for test images. + */ +typedef struct SDLTest_SurfaceImage_s { + int width; + int height; + unsigned int bytes_per_pixel; /* 3:RGB, 4:RGBA */ + const char *pixel_data; +} SDLTest_SurfaceImage_t; + +/* Test images */ +SDL_Surface *SDLTest_ImageBlit(); +SDL_Surface *SDLTest_ImageBlitColor(); +SDL_Surface *SDLTest_ImageBlitAlpha(); +SDL_Surface *SDLTest_ImageBlitBlendAdd(); +SDL_Surface *SDLTest_ImageBlitBlend(); +SDL_Surface *SDLTest_ImageBlitBlendMod(); +SDL_Surface *SDLTest_ImageBlitBlendNone(); +SDL_Surface *SDLTest_ImageBlitBlendAll(); +SDL_Surface *SDLTest_ImageFace(); +SDL_Surface *SDLTest_ImagePrimitives(); +SDL_Surface *SDLTest_ImagePrimitivesBlend(); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_test_images_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_test_log.h b/external/SDL2/SDL_test_log.h new file mode 100644 index 0000000..c6dbad6 --- /dev/null +++ b/external/SDL2/SDL_test_log.h @@ -0,0 +1,71 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_test_log.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +/* + * + * Wrapper to log in the TEST category + * + */ + +#ifndef _SDL_test_log_h +#define _SDL_test_log_h + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \brief Prints given message with a timestamp in the TEST category and INFO priority. + * + * \param fmt Message to be logged + */ +void SDLTest_Log(const char *fmt, ...); + +/** + * \brief Prints given message with a timestamp in the TEST category and the ERROR priority. + * + * \param fmt Message to be logged + */ +void SDLTest_LogError(const char *fmt, ...); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_test_log_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_test_md5.h b/external/SDL2/SDL_test_md5.h new file mode 100644 index 0000000..8949f4b --- /dev/null +++ b/external/SDL2/SDL_test_md5.h @@ -0,0 +1,133 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_test_md5.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +/* + *********************************************************************** + ** Header file for implementation of MD5 ** + ** RSA Data Security, Inc. MD5 Message-Digest Algorithm ** + ** Created: 2/17/90 RLR ** + ** Revised: 12/27/90 SRD,AJ,BSK,JT Reference C version ** + ** Revised (for MD5): RLR 4/27/91 ** + ** -- G modified to have y&~z instead of y&z ** + ** -- FF, GG, HH modified to add in last register done ** + ** -- Access pattern: round 2 works mod 5, round 3 works mod 3 ** + ** -- distinct additive constant for each step ** + ** -- round 4 added, working mod 7 ** + *********************************************************************** +*/ + +/* + *********************************************************************** + ** Message-digest routines: ** + ** To form the message digest for a message M ** + ** (1) Initialize a context buffer mdContext using MD5Init ** + ** (2) Call MD5Update on mdContext and M ** + ** (3) Call MD5Final on mdContext ** + ** The message digest is now in mdContext->digest[0...15] ** + *********************************************************************** +*/ + +#ifndef _SDL_test_md5_h +#define _SDL_test_md5_h + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/* ------------ Definitions --------- */ + +/* typedef a 32-bit type */ + typedef unsigned long int MD5UINT4; + +/* Data structure for MD5 (Message-Digest) computation */ + typedef struct { + MD5UINT4 i[2]; /* number of _bits_ handled mod 2^64 */ + MD5UINT4 buf[4]; /* scratch buffer */ + unsigned char in[64]; /* input buffer */ + unsigned char digest[16]; /* actual digest after Md5Final call */ + } SDLTest_Md5Context; + +/* ---------- Function Prototypes ------------- */ + +/** + * /brief initialize the context + * + * /param mdContext pointer to context variable + * + * Note: The function initializes the message-digest context + * mdContext. Call before each new use of the context - + * all fields are set to zero. + */ + void SDLTest_Md5Init(SDLTest_Md5Context * mdContext); + + +/** + * /brief update digest from variable length data + * + * /param mdContext pointer to context variable + * /param inBuf pointer to data array/string + * /param inLen length of data array/string + * + * Note: The function updates the message-digest context to account + * for the presence of each of the characters inBuf[0..inLen-1] + * in the message whose digest is being computed. +*/ + + void SDLTest_Md5Update(SDLTest_Md5Context * mdContext, unsigned char *inBuf, + unsigned int inLen); + + +/* + * /brief complete digest computation + * + * /param mdContext pointer to context variable + * + * Note: The function terminates the message-digest computation and + * ends with the desired message digest in mdContext.digest[0..15]. + * Always call before using the digest[] variable. +*/ + + void SDLTest_Md5Final(SDLTest_Md5Context * mdContext); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_test_md5_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_test_random.h b/external/SDL2/SDL_test_random.h new file mode 100644 index 0000000..8b20b6a --- /dev/null +++ b/external/SDL2/SDL_test_random.h @@ -0,0 +1,119 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_test_random.h + * + * Include file for SDL test framework. + * + * This code is a part of the SDL2_test library, not the main SDL library. + */ + +/* + + A "32-bit Multiply with carry random number generator. Very fast. + Includes a list of recommended multipliers. + + multiply-with-carry generator: x(n) = a*x(n-1) + carry mod 2^32. + period: (a*2^31)-1 + +*/ + +#ifndef _SDL_test_random_h +#define _SDL_test_random_h + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/* --- Definitions */ + +/* + * Macros that return a random number in a specific format. + */ +#define SDLTest_RandomInt(c) ((int)SDLTest_Random(c)) + +/* + * Context structure for the random number generator state. + */ + typedef struct { + unsigned int a; + unsigned int x; + unsigned int c; + unsigned int ah; + unsigned int al; + } SDLTest_RandomContext; + + +/* --- Function prototypes */ + +/** + * \brief Initialize random number generator with two integers. + * + * Note: The random sequence of numbers returned by ...Random() is the + * same for the same two integers and has a period of 2^31. + * + * \param rndContext pointer to context structure + * \param xi integer that defines the random sequence + * \param ci integer that defines the random sequence + * + */ + void SDLTest_RandomInit(SDLTest_RandomContext * rndContext, unsigned int xi, + unsigned int ci); + +/** + * \brief Initialize random number generator based on current system time. + * + * \param rndContext pointer to context structure + * + */ + void SDLTest_RandomInitTime(SDLTest_RandomContext *rndContext); + + +/** + * \brief Initialize random number generator based on current system time. + * + * Note: ...RandomInit() or ...RandomInitTime() must have been called + * before using this function. + * + * \param rndContext pointer to context structure + * + * \returns A random number (32bit unsigned integer) + * + */ + unsigned int SDLTest_Random(SDLTest_RandomContext *rndContext); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_test_random_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_thread.h b/external/SDL2/SDL_thread.h new file mode 100644 index 0000000..273419b --- /dev/null +++ b/external/SDL2/SDL_thread.h @@ -0,0 +1,182 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef _SDL_thread_h +#define _SDL_thread_h + +/** + * \file SDL_thread.h + * + * Header for the SDL thread management routines. + */ + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +/* Thread synchronization primitives */ +#include "SDL_mutex.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/* The SDL thread structure, defined in SDL_thread.c */ +struct SDL_Thread; +typedef struct SDL_Thread SDL_Thread; + +/* The SDL thread ID */ +typedef unsigned long SDL_threadID; + +/* The SDL thread priority + * + * Note: On many systems you require special privileges to set high priority. + */ +typedef enum { + SDL_THREAD_PRIORITY_LOW, + SDL_THREAD_PRIORITY_NORMAL, + SDL_THREAD_PRIORITY_HIGH +} SDL_ThreadPriority; + +/* The function passed to SDL_CreateThread() + It is passed a void* user context parameter and returns an int. + */ +typedef int (SDLCALL * SDL_ThreadFunction) (void *data); + +#if defined(__WIN32__) && !defined(HAVE_LIBC) +/** + * \file SDL_thread.h + * + * We compile SDL into a DLL. This means, that it's the DLL which + * creates a new thread for the calling process with the SDL_CreateThread() + * API. There is a problem with this, that only the RTL of the SDL.DLL will + * be initialized for those threads, and not the RTL of the calling + * application! + * + * To solve this, we make a little hack here. + * + * We'll always use the caller's _beginthread() and _endthread() APIs to + * start a new thread. This way, if it's the SDL.DLL which uses this API, + * then the RTL of SDL.DLL will be used to create the new thread, and if it's + * the application, then the RTL of the application will be used. + * + * So, in short: + * Always use the _beginthread() and _endthread() of the calling runtime + * library! + */ +#define SDL_PASSED_BEGINTHREAD_ENDTHREAD +#include /* This has _beginthread() and _endthread() defined! */ + +typedef uintptr_t(__cdecl * pfnSDL_CurrentBeginThread) (void *, unsigned, + unsigned (__stdcall * + func) (void + *), + void *arg, unsigned, + unsigned *threadID); +typedef void (__cdecl * pfnSDL_CurrentEndThread) (unsigned code); + +/** + * Create a thread. + */ +extern DECLSPEC SDL_Thread *SDLCALL +SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data, + pfnSDL_CurrentBeginThread pfnBeginThread, + pfnSDL_CurrentEndThread pfnEndThread); + +/** + * Create a thread. + */ +#define SDL_CreateThread(fn, name, data) SDL_CreateThread(fn, name, data, _beginthreadex, _endthreadex) + +#else + +/** + * Create a thread. + * + * Thread naming is a little complicated: Most systems have very small + * limits for the string length (BeOS has 32 bytes, Linux currently has 16, + * Visual C++ 6.0 has nine!), and possibly other arbitrary rules. You'll + * have to see what happens with your system's debugger. The name should be + * UTF-8 (but using the naming limits of C identifiers is a better bet). + * There are no requirements for thread naming conventions, so long as the + * string is null-terminated UTF-8, but these guidelines are helpful in + * choosing a name: + * + * http://stackoverflow.com/questions/149932/naming-conventions-for-threads + * + * If a system imposes requirements, SDL will try to munge the string for + * it (truncate, etc), but the original string contents will be available + * from SDL_GetThreadName(). + */ +extern DECLSPEC SDL_Thread *SDLCALL +SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data); + +#endif + +/** + * Get the thread name, as it was specified in SDL_CreateThread(). + * This function returns a pointer to a UTF-8 string that names the + * specified thread, or NULL if it doesn't have a name. This is internal + * memory, not to be free()'d by the caller, and remains valid until the + * specified thread is cleaned up by SDL_WaitThread(). + */ +extern DECLSPEC const char *SDLCALL SDL_GetThreadName(SDL_Thread *thread); + +/** + * Get the thread identifier for the current thread. + */ +extern DECLSPEC SDL_threadID SDLCALL SDL_ThreadID(void); + +/** + * Get the thread identifier for the specified thread. + * + * Equivalent to SDL_ThreadID() if the specified thread is NULL. + */ +extern DECLSPEC SDL_threadID SDLCALL SDL_GetThreadID(SDL_Thread * thread); + +/** + * Set the priority for the current thread + */ +extern DECLSPEC int SDLCALL SDL_SetThreadPriority(SDL_ThreadPriority priority); + +/** + * Wait for a thread to finish. + * + * The return code for the thread function is placed in the area + * pointed to by \c status, if \c status is not NULL. + */ +extern DECLSPEC void SDLCALL SDL_WaitThread(SDL_Thread * thread, int *status); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_thread_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_timer.h b/external/SDL2/SDL_timer.h new file mode 100644 index 0000000..4a2a272 --- /dev/null +++ b/external/SDL2/SDL_timer.h @@ -0,0 +1,108 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef _SDL_timer_h +#define _SDL_timer_h + +/** + * \file SDL_timer.h + * + * Header for the SDL time management routines. + */ + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \brief Get the number of milliseconds since the SDL library initialization. + * + * \note This value wraps if the program runs for more than ~49 days. + */ +extern DECLSPEC Uint32 SDLCALL SDL_GetTicks(void); + +/** + * \brief Get the current value of the high resolution counter + */ +extern DECLSPEC Uint64 SDLCALL SDL_GetPerformanceCounter(void); + +/** + * \brief Get the count per second of the high resolution counter + */ +extern DECLSPEC Uint64 SDLCALL SDL_GetPerformanceFrequency(void); + +/** + * \brief Wait a specified number of milliseconds before returning. + */ +extern DECLSPEC void SDLCALL SDL_Delay(Uint32 ms); + +/** + * Function prototype for the timer callback function. + * + * The callback function is passed the current timer interval and returns + * the next timer interval. If the returned value is the same as the one + * passed in, the periodic alarm continues, otherwise a new alarm is + * scheduled. If the callback returns 0, the periodic alarm is cancelled. + */ +typedef Uint32 (SDLCALL * SDL_TimerCallback) (Uint32 interval, void *param); + +/** + * Definition of the timer ID type. + */ +typedef int SDL_TimerID; + +/** + * \brief Add a new timer to the pool of timers already running. + * + * \return A timer ID, or NULL when an error occurs. + */ +extern DECLSPEC SDL_TimerID SDLCALL SDL_AddTimer(Uint32 interval, + SDL_TimerCallback callback, + void *param); + +/** + * \brief Remove a timer knowing its ID. + * + * \return A boolean value indicating success or failure. + * + * \warning It is not safe to remove a timer multiple times. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_RemoveTimer(SDL_TimerID id); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_timer_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_touch.h b/external/SDL2/SDL_touch.h new file mode 100644 index 0000000..55176a1 --- /dev/null +++ b/external/SDL2/SDL_touch.h @@ -0,0 +1,90 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_touch.h + * + * Include file for SDL touch event handling. + */ + +#ifndef _SDL_touch_h +#define _SDL_touch_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_video.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +typedef Sint64 SDL_TouchID; +typedef Sint64 SDL_FingerID; + +typedef struct SDL_Finger +{ + SDL_FingerID id; + float x; + float y; + float pressure; +} SDL_Finger; + +/* Used as the device ID for mouse events simulated with touch input */ +#define SDL_TOUCH_MOUSEID ((Uint32)-1) + + +/* Function prototypes */ + +/** + * \brief Get the number of registered touch devices. + */ +extern DECLSPEC int SDLCALL SDL_GetNumTouchDevices(); + +/** + * \brief Get the touch ID with the given index, or 0 if the index is invalid. + */ +extern DECLSPEC SDL_TouchID SDLCALL SDL_GetTouchDevice(int index); + +/** + * \brief Get the number of active fingers for a given touch device. + */ +extern DECLSPEC int SDLCALL SDL_GetNumTouchFingers(SDL_TouchID touchID); + +/** + * \brief Get the finger object of the given touch, with the given index. + */ +extern DECLSPEC SDL_Finger * SDLCALL SDL_GetTouchFinger(SDL_TouchID touchID, int index); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_touch_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_types.h b/external/SDL2/SDL_types.h new file mode 100644 index 0000000..636df1e --- /dev/null +++ b/external/SDL2/SDL_types.h @@ -0,0 +1,29 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_types.h + * + * \deprecated + */ + +/* DEPRECATED */ +#include "SDL_stdinc.h" diff --git a/external/SDL2/SDL_version.h b/external/SDL2/SDL_version.h new file mode 100644 index 0000000..e32ea20 --- /dev/null +++ b/external/SDL2/SDL_version.h @@ -0,0 +1,166 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_version.h + * + * This header defines the current SDL version. + */ + +#ifndef _SDL_version_h +#define _SDL_version_h + +#include "SDL_stdinc.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \brief Information the version of SDL in use. + * + * Represents the library's version as three levels: major revision + * (increments with massive changes, additions, and enhancements), + * minor revision (increments with backwards-compatible changes to the + * major revision), and patchlevel (increments with fixes to the minor + * revision). + * + * \sa SDL_VERSION + * \sa SDL_GetVersion + */ +typedef struct SDL_version +{ + Uint8 major; /**< major version */ + Uint8 minor; /**< minor version */ + Uint8 patch; /**< update version */ +} SDL_version; + +/* Printable format: "%d.%d.%d", MAJOR, MINOR, PATCHLEVEL +*/ +#define SDL_MAJOR_VERSION 2 +#define SDL_MINOR_VERSION 0 +#define SDL_PATCHLEVEL 0 + +/** + * \brief Macro to determine SDL version program was compiled against. + * + * This macro fills in a SDL_version structure with the version of the + * library you compiled against. This is determined by what header the + * compiler uses. Note that if you dynamically linked the library, you might + * have a slightly newer or older version at runtime. That version can be + * determined with SDL_GetVersion(), which, unlike SDL_VERSION(), + * is not a macro. + * + * \param x A pointer to a SDL_version struct to initialize. + * + * \sa SDL_version + * \sa SDL_GetVersion + */ +#define SDL_VERSION(x) \ +{ \ + (x)->major = SDL_MAJOR_VERSION; \ + (x)->minor = SDL_MINOR_VERSION; \ + (x)->patch = SDL_PATCHLEVEL; \ +} + +/** + * This macro turns the version numbers into a numeric value: + * \verbatim + (1,2,3) -> (1203) + \endverbatim + * + * This assumes that there will never be more than 100 patchlevels. + */ +#define SDL_VERSIONNUM(X, Y, Z) \ + ((X)*1000 + (Y)*100 + (Z)) + +/** + * This is the version number macro for the current SDL version. + */ +#define SDL_COMPILEDVERSION \ + SDL_VERSIONNUM(SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL) + +/** + * This macro will evaluate to true if compiled with SDL at least X.Y.Z. + */ +#define SDL_VERSION_ATLEAST(X, Y, Z) \ + (SDL_COMPILEDVERSION >= SDL_VERSIONNUM(X, Y, Z)) + +/** + * \brief Get the version of SDL that is linked against your program. + * + * If you are linking to SDL dynamically, then it is possible that the + * current version will be different than the version you compiled against. + * This function returns the current version, while SDL_VERSION() is a + * macro that tells you what version you compiled with. + * + * \code + * SDL_version compiled; + * SDL_version linked; + * + * SDL_VERSION(&compiled); + * SDL_GetVersion(&linked); + * printf("We compiled against SDL version %d.%d.%d ...\n", + * compiled.major, compiled.minor, compiled.patch); + * printf("But we linked against SDL version %d.%d.%d.\n", + * linked.major, linked.minor, linked.patch); + * \endcode + * + * This function may be called safely at any time, even before SDL_Init(). + * + * \sa SDL_VERSION + */ +extern DECLSPEC void SDLCALL SDL_GetVersion(SDL_version * ver); + +/** + * \brief Get the code revision of SDL that is linked against your program. + * + * Returns an arbitrary string (a hash value) uniquely identifying the + * exact revision of the SDL library in use, and is only useful in comparing + * against other revisions. It is NOT an incrementing number. + */ +extern DECLSPEC const char *SDLCALL SDL_GetRevision(void); + +/** + * \brief Get the revision number of SDL that is linked against your program. + * + * Returns a number uniquely identifying the exact revision of the SDL + * library in use. It is an incrementing number based on commits to + * hg.libsdl.org. + */ +extern DECLSPEC int SDLCALL SDL_GetRevisionNumber(void); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_version_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/SDL_video.h b/external/SDL2/SDL_video.h new file mode 100644 index 0000000..4873da3 --- /dev/null +++ b/external/SDL2/SDL_video.h @@ -0,0 +1,934 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_video.h + * + * Header file for SDL video functions. + */ + +#ifndef _SDL_video_h +#define _SDL_video_h + +#include "SDL_stdinc.h" +#include "SDL_pixels.h" +#include "SDL_rect.h" +#include "SDL_surface.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * \brief The structure that defines a display mode + * + * \sa SDL_GetNumDisplayModes() + * \sa SDL_GetDisplayMode() + * \sa SDL_GetDesktopDisplayMode() + * \sa SDL_GetCurrentDisplayMode() + * \sa SDL_GetClosestDisplayMode() + * \sa SDL_SetWindowDisplayMode() + * \sa SDL_GetWindowDisplayMode() + */ +typedef struct +{ + Uint32 format; /**< pixel format */ + int w; /**< width */ + int h; /**< height */ + int refresh_rate; /**< refresh rate (or zero for unspecified) */ + void *driverdata; /**< driver-specific data, initialize to 0 */ +} SDL_DisplayMode; + +/** + * \brief The type used to identify a window + * + * \sa SDL_CreateWindow() + * \sa SDL_CreateWindowFrom() + * \sa SDL_DestroyWindow() + * \sa SDL_GetWindowData() + * \sa SDL_GetWindowFlags() + * \sa SDL_GetWindowGrab() + * \sa SDL_GetWindowPosition() + * \sa SDL_GetWindowSize() + * \sa SDL_GetWindowTitle() + * \sa SDL_HideWindow() + * \sa SDL_MaximizeWindow() + * \sa SDL_MinimizeWindow() + * \sa SDL_RaiseWindow() + * \sa SDL_RestoreWindow() + * \sa SDL_SetWindowData() + * \sa SDL_SetWindowFullscreen() + * \sa SDL_SetWindowGrab() + * \sa SDL_SetWindowIcon() + * \sa SDL_SetWindowPosition() + * \sa SDL_SetWindowSize() + * \sa SDL_SetWindowBordered() + * \sa SDL_SetWindowTitle() + * \sa SDL_ShowWindow() + */ +typedef struct SDL_Window SDL_Window; + +/** + * \brief The flags on a window + * + * \sa SDL_GetWindowFlags() + */ +typedef enum +{ + SDL_WINDOW_FULLSCREEN = 0x00000001, /**< fullscreen window */ + SDL_WINDOW_OPENGL = 0x00000002, /**< window usable with OpenGL context */ + SDL_WINDOW_SHOWN = 0x00000004, /**< window is visible */ + SDL_WINDOW_HIDDEN = 0x00000008, /**< window is not visible */ + SDL_WINDOW_BORDERLESS = 0x00000010, /**< no window decoration */ + SDL_WINDOW_RESIZABLE = 0x00000020, /**< window can be resized */ + SDL_WINDOW_MINIMIZED = 0x00000040, /**< window is minimized */ + SDL_WINDOW_MAXIMIZED = 0x00000080, /**< window is maximized */ + SDL_WINDOW_INPUT_GRABBED = 0x00000100, /**< window has grabbed input focus */ + SDL_WINDOW_INPUT_FOCUS = 0x00000200, /**< window has input focus */ + SDL_WINDOW_MOUSE_FOCUS = 0x00000400, /**< window has mouse focus */ + SDL_WINDOW_FULLSCREEN_DESKTOP = ( SDL_WINDOW_FULLSCREEN | 0x00001000 ), + SDL_WINDOW_FOREIGN = 0x00000800 /**< window not created by SDL */ +} SDL_WindowFlags; + +/** + * \brief Used to indicate that you don't care what the window position is. + */ +#define SDL_WINDOWPOS_UNDEFINED_MASK 0x1FFF0000 +#define SDL_WINDOWPOS_UNDEFINED_DISPLAY(X) (SDL_WINDOWPOS_UNDEFINED_MASK|(X)) +#define SDL_WINDOWPOS_UNDEFINED SDL_WINDOWPOS_UNDEFINED_DISPLAY(0) +#define SDL_WINDOWPOS_ISUNDEFINED(X) \ + (((X)&0xFFFF0000) == SDL_WINDOWPOS_UNDEFINED_MASK) + +/** + * \brief Used to indicate that the window position should be centered. + */ +#define SDL_WINDOWPOS_CENTERED_MASK 0x2FFF0000 +#define SDL_WINDOWPOS_CENTERED_DISPLAY(X) (SDL_WINDOWPOS_CENTERED_MASK|(X)) +#define SDL_WINDOWPOS_CENTERED SDL_WINDOWPOS_CENTERED_DISPLAY(0) +#define SDL_WINDOWPOS_ISCENTERED(X) \ + (((X)&0xFFFF0000) == SDL_WINDOWPOS_CENTERED_MASK) + +/** + * \brief Event subtype for window events + */ +typedef enum +{ + SDL_WINDOWEVENT_NONE, /**< Never used */ + SDL_WINDOWEVENT_SHOWN, /**< Window has been shown */ + SDL_WINDOWEVENT_HIDDEN, /**< Window has been hidden */ + SDL_WINDOWEVENT_EXPOSED, /**< Window has been exposed and should be + redrawn */ + SDL_WINDOWEVENT_MOVED, /**< Window has been moved to data1, data2 + */ + SDL_WINDOWEVENT_RESIZED, /**< Window has been resized to data1xdata2 */ + SDL_WINDOWEVENT_SIZE_CHANGED, /**< The window size has changed, either as a result of an API call or through the system or user changing the window size. */ + SDL_WINDOWEVENT_MINIMIZED, /**< Window has been minimized */ + SDL_WINDOWEVENT_MAXIMIZED, /**< Window has been maximized */ + SDL_WINDOWEVENT_RESTORED, /**< Window has been restored to normal size + and position */ + SDL_WINDOWEVENT_ENTER, /**< Window has gained mouse focus */ + SDL_WINDOWEVENT_LEAVE, /**< Window has lost mouse focus */ + SDL_WINDOWEVENT_FOCUS_GAINED, /**< Window has gained keyboard focus */ + SDL_WINDOWEVENT_FOCUS_LOST, /**< Window has lost keyboard focus */ + SDL_WINDOWEVENT_CLOSE /**< The window manager requests that the + window be closed */ +} SDL_WindowEventID; + +/** + * \brief An opaque handle to an OpenGL context. + */ +typedef void *SDL_GLContext; + +/** + * \brief OpenGL configuration attributes + */ +typedef enum +{ + SDL_GL_RED_SIZE, + SDL_GL_GREEN_SIZE, + SDL_GL_BLUE_SIZE, + SDL_GL_ALPHA_SIZE, + SDL_GL_BUFFER_SIZE, + SDL_GL_DOUBLEBUFFER, + SDL_GL_DEPTH_SIZE, + SDL_GL_STENCIL_SIZE, + SDL_GL_ACCUM_RED_SIZE, + SDL_GL_ACCUM_GREEN_SIZE, + SDL_GL_ACCUM_BLUE_SIZE, + SDL_GL_ACCUM_ALPHA_SIZE, + SDL_GL_STEREO, + SDL_GL_MULTISAMPLEBUFFERS, + SDL_GL_MULTISAMPLESAMPLES, + SDL_GL_ACCELERATED_VISUAL, + SDL_GL_RETAINED_BACKING, + SDL_GL_CONTEXT_MAJOR_VERSION, + SDL_GL_CONTEXT_MINOR_VERSION, + SDL_GL_CONTEXT_EGL, + SDL_GL_CONTEXT_FLAGS, + SDL_GL_CONTEXT_PROFILE_MASK, + SDL_GL_SHARE_WITH_CURRENT_CONTEXT +} SDL_GLattr; + +typedef enum +{ + SDL_GL_CONTEXT_PROFILE_CORE = 0x0001, + SDL_GL_CONTEXT_PROFILE_COMPATIBILITY = 0x0002, + SDL_GL_CONTEXT_PROFILE_ES = 0x0004 +} SDL_GLprofile; + +typedef enum +{ + SDL_GL_CONTEXT_DEBUG_FLAG = 0x0001, + SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG = 0x0002, + SDL_GL_CONTEXT_ROBUST_ACCESS_FLAG = 0x0004, + SDL_GL_CONTEXT_RESET_ISOLATION_FLAG = 0x0008 +} SDL_GLcontextFlag; + + +/* Function prototypes */ + +/** + * \brief Get the number of video drivers compiled into SDL + * + * \sa SDL_GetVideoDriver() + */ +extern DECLSPEC int SDLCALL SDL_GetNumVideoDrivers(void); + +/** + * \brief Get the name of a built in video driver. + * + * \note The video drivers are presented in the order in which they are + * normally checked during initialization. + * + * \sa SDL_GetNumVideoDrivers() + */ +extern DECLSPEC const char *SDLCALL SDL_GetVideoDriver(int index); + +/** + * \brief Initialize the video subsystem, optionally specifying a video driver. + * + * \param driver_name Initialize a specific driver by name, or NULL for the + * default video driver. + * + * \return 0 on success, -1 on error + * + * This function initializes the video subsystem; setting up a connection + * to the window manager, etc, and determines the available display modes + * and pixel formats, but does not initialize a window or graphics mode. + * + * \sa SDL_VideoQuit() + */ +extern DECLSPEC int SDLCALL SDL_VideoInit(const char *driver_name); + +/** + * \brief Shuts down the video subsystem. + * + * This function closes all windows, and restores the original video mode. + * + * \sa SDL_VideoInit() + */ +extern DECLSPEC void SDLCALL SDL_VideoQuit(void); + +/** + * \brief Returns the name of the currently initialized video driver. + * + * \return The name of the current video driver or NULL if no driver + * has been initialized + * + * \sa SDL_GetNumVideoDrivers() + * \sa SDL_GetVideoDriver() + */ +extern DECLSPEC const char *SDLCALL SDL_GetCurrentVideoDriver(void); + +/** + * \brief Returns the number of available video displays. + * + * \sa SDL_GetDisplayBounds() + */ +extern DECLSPEC int SDLCALL SDL_GetNumVideoDisplays(void); + +/** + * \brief Get the name of a display in UTF-8 encoding + * + * \return The name of a display, or NULL for an invalid display index. + * + * \sa SDL_GetNumVideoDisplays() + */ +extern DECLSPEC const char * SDLCALL SDL_GetDisplayName(int displayIndex); + +/** + * \brief Get the desktop area represented by a display, with the primary + * display located at 0,0 + * + * \return 0 on success, or -1 if the index is out of range. + * + * \sa SDL_GetNumVideoDisplays() + */ +extern DECLSPEC int SDLCALL SDL_GetDisplayBounds(int displayIndex, SDL_Rect * rect); + +/** + * \brief Returns the number of available display modes. + * + * \sa SDL_GetDisplayMode() + */ +extern DECLSPEC int SDLCALL SDL_GetNumDisplayModes(int displayIndex); + +/** + * \brief Fill in information about a specific display mode. + * + * \note The display modes are sorted in this priority: + * \li bits per pixel -> more colors to fewer colors + * \li width -> largest to smallest + * \li height -> largest to smallest + * \li refresh rate -> highest to lowest + * + * \sa SDL_GetNumDisplayModes() + */ +extern DECLSPEC int SDLCALL SDL_GetDisplayMode(int displayIndex, int modeIndex, + SDL_DisplayMode * mode); + +/** + * \brief Fill in information about the desktop display mode. + */ +extern DECLSPEC int SDLCALL SDL_GetDesktopDisplayMode(int displayIndex, SDL_DisplayMode * mode); + +/** + * \brief Fill in information about the current display mode. + */ +extern DECLSPEC int SDLCALL SDL_GetCurrentDisplayMode(int displayIndex, SDL_DisplayMode * mode); + + +/** + * \brief Get the closest match to the requested display mode. + * + * \param mode The desired display mode + * \param closest A pointer to a display mode to be filled in with the closest + * match of the available display modes. + * + * \return The passed in value \c closest, or NULL if no matching video mode + * was available. + * + * The available display modes are scanned, and \c closest is filled in with the + * closest mode matching the requested mode and returned. The mode format and + * refresh_rate default to the desktop mode if they are 0. The modes are + * scanned with size being first priority, format being second priority, and + * finally checking the refresh_rate. If all the available modes are too + * small, then NULL is returned. + * + * \sa SDL_GetNumDisplayModes() + * \sa SDL_GetDisplayMode() + */ +extern DECLSPEC SDL_DisplayMode * SDLCALL SDL_GetClosestDisplayMode(int displayIndex, const SDL_DisplayMode * mode, SDL_DisplayMode * closest); + +/** + * \brief Get the display index associated with a window. + * + * \return the display index of the display containing the center of the + * window, or -1 on error. + */ +extern DECLSPEC int SDLCALL SDL_GetWindowDisplayIndex(SDL_Window * window); + +/** + * \brief Set the display mode used when a fullscreen window is visible. + * + * By default the window's dimensions and the desktop format and refresh rate + * are used. + * + * \param mode The mode to use, or NULL for the default mode. + * + * \return 0 on success, or -1 if setting the display mode failed. + * + * \sa SDL_GetWindowDisplayMode() + * \sa SDL_SetWindowFullscreen() + */ +extern DECLSPEC int SDLCALL SDL_SetWindowDisplayMode(SDL_Window * window, + const SDL_DisplayMode + * mode); + +/** + * \brief Fill in information about the display mode used when a fullscreen + * window is visible. + * + * \sa SDL_SetWindowDisplayMode() + * \sa SDL_SetWindowFullscreen() + */ +extern DECLSPEC int SDLCALL SDL_GetWindowDisplayMode(SDL_Window * window, + SDL_DisplayMode * mode); + +/** + * \brief Get the pixel format associated with the window. + */ +extern DECLSPEC Uint32 SDLCALL SDL_GetWindowPixelFormat(SDL_Window * window); + +/** + * \brief Create a window with the specified position, dimensions, and flags. + * + * \param title The title of the window, in UTF-8 encoding. + * \param x The x position of the window, ::SDL_WINDOWPOS_CENTERED, or + * ::SDL_WINDOWPOS_UNDEFINED. + * \param y The y position of the window, ::SDL_WINDOWPOS_CENTERED, or + * ::SDL_WINDOWPOS_UNDEFINED. + * \param w The width of the window. + * \param h The height of the window. + * \param flags The flags for the window, a mask of any of the following: + * ::SDL_WINDOW_FULLSCREEN, ::SDL_WINDOW_OPENGL, + * ::SDL_WINDOW_SHOWN, ::SDL_WINDOW_BORDERLESS, + * ::SDL_WINDOW_RESIZABLE, ::SDL_WINDOW_MAXIMIZED, + * ::SDL_WINDOW_MINIMIZED, ::SDL_WINDOW_INPUT_GRABBED. + * + * \return The id of the window created, or zero if window creation failed. + * + * \sa SDL_DestroyWindow() + */ +extern DECLSPEC SDL_Window * SDLCALL SDL_CreateWindow(const char *title, + int x, int y, int w, + int h, Uint32 flags); + +/** + * \brief Create an SDL window from an existing native window. + * + * \param data A pointer to driver-dependent window creation data + * + * \return The id of the window created, or zero if window creation failed. + * + * \sa SDL_DestroyWindow() + */ +extern DECLSPEC SDL_Window * SDLCALL SDL_CreateWindowFrom(const void *data); + +/** + * \brief Get the numeric ID of a window, for logging purposes. + */ +extern DECLSPEC Uint32 SDLCALL SDL_GetWindowID(SDL_Window * window); + +/** + * \brief Get a window from a stored ID, or NULL if it doesn't exist. + */ +extern DECLSPEC SDL_Window * SDLCALL SDL_GetWindowFromID(Uint32 id); + +/** + * \brief Get the window flags. + */ +extern DECLSPEC Uint32 SDLCALL SDL_GetWindowFlags(SDL_Window * window); + +/** + * \brief Set the title of a window, in UTF-8 format. + * + * \sa SDL_GetWindowTitle() + */ +extern DECLSPEC void SDLCALL SDL_SetWindowTitle(SDL_Window * window, + const char *title); + +/** + * \brief Get the title of a window, in UTF-8 format. + * + * \sa SDL_SetWindowTitle() + */ +extern DECLSPEC const char *SDLCALL SDL_GetWindowTitle(SDL_Window * window); + +/** + * \brief Set the icon for a window. + * + * \param icon The icon for the window. + */ +extern DECLSPEC void SDLCALL SDL_SetWindowIcon(SDL_Window * window, + SDL_Surface * icon); + +/** + * \brief Associate an arbitrary named pointer with a window. + * + * \param window The window to associate with the pointer. + * \param name The name of the pointer. + * \param userdata The associated pointer. + * + * \return The previous value associated with 'name' + * + * \note The name is case-sensitive. + * + * \sa SDL_GetWindowData() + */ +extern DECLSPEC void* SDLCALL SDL_SetWindowData(SDL_Window * window, + const char *name, + void *userdata); + +/** + * \brief Retrieve the data pointer associated with a window. + * + * \param window The window to query. + * \param name The name of the pointer. + * + * \return The value associated with 'name' + * + * \sa SDL_SetWindowData() + */ +extern DECLSPEC void *SDLCALL SDL_GetWindowData(SDL_Window * window, + const char *name); + +/** + * \brief Set the position of a window. + * + * \param window The window to reposition. + * \param x The x coordinate of the window, ::SDL_WINDOWPOS_CENTERED, or + ::SDL_WINDOWPOS_UNDEFINED. + * \param y The y coordinate of the window, ::SDL_WINDOWPOS_CENTERED, or + ::SDL_WINDOWPOS_UNDEFINED. + * + * \note The window coordinate origin is the upper left of the display. + * + * \sa SDL_GetWindowPosition() + */ +extern DECLSPEC void SDLCALL SDL_SetWindowPosition(SDL_Window * window, + int x, int y); + +/** + * \brief Get the position of a window. + * + * \param x Pointer to variable for storing the x position, may be NULL + * \param y Pointer to variable for storing the y position, may be NULL + * + * \sa SDL_SetWindowPosition() + */ +extern DECLSPEC void SDLCALL SDL_GetWindowPosition(SDL_Window * window, + int *x, int *y); + +/** + * \brief Set the size of a window's client area. + * + * \param w The width of the window, must be >0 + * \param h The height of the window, must be >0 + * + * \note You can't change the size of a fullscreen window, it automatically + * matches the size of the display mode. + * + * \sa SDL_GetWindowSize() + */ +extern DECLSPEC void SDLCALL SDL_SetWindowSize(SDL_Window * window, int w, + int h); + +/** + * \brief Get the size of a window's client area. + * + * \param w Pointer to variable for storing the width, may be NULL + * \param h Pointer to variable for storing the height, may be NULL + * + * \sa SDL_SetWindowSize() + */ +extern DECLSPEC void SDLCALL SDL_GetWindowSize(SDL_Window * window, int *w, + int *h); + +/** + * \brief Set the minimum size of a window's client area. + * + * \param min_w The minimum width of the window, must be >0 + * \param min_h The minimum height of the window, must be >0 + * + * \note You can't change the minimum size of a fullscreen window, it + * automatically matches the size of the display mode. + * + * \sa SDL_GetWindowMinimumSize() + * \sa SDL_SetWindowMaximumSize() + */ +extern DECLSPEC void SDLCALL SDL_SetWindowMinimumSize(SDL_Window * window, + int min_w, int min_h); + +/** + * \brief Get the minimum size of a window's client area. + * + * \param w Pointer to variable for storing the minimum width, may be NULL + * \param h Pointer to variable for storing the minimum height, may be NULL + * + * \sa SDL_GetWindowMaximumSize() + * \sa SDL_SetWindowMinimumSize() + */ +extern DECLSPEC void SDLCALL SDL_GetWindowMinimumSize(SDL_Window * window, + int *w, int *h); + +/** + * \brief Set the maximum size of a window's client area. + * + * \param max_w The maximum width of the window, must be >0 + * \param max_h The maximum height of the window, must be >0 + * + * \note You can't change the maximum size of a fullscreen window, it + * automatically matches the size of the display mode. + * + * \sa SDL_GetWindowMaximumSize() + * \sa SDL_SetWindowMinimumSize() + */ +extern DECLSPEC void SDLCALL SDL_SetWindowMaximumSize(SDL_Window * window, + int max_w, int max_h); + +/** + * \brief Get the maximum size of a window's client area. + * + * \param w Pointer to variable for storing the maximum width, may be NULL + * \param h Pointer to variable for storing the maximum height, may be NULL + * + * \sa SDL_GetWindowMinimumSize() + * \sa SDL_SetWindowMaximumSize() + */ +extern DECLSPEC void SDLCALL SDL_GetWindowMaximumSize(SDL_Window * window, + int *w, int *h); + +/** + * \brief Set the border state of a window. + * + * This will add or remove the window's SDL_WINDOW_BORDERLESS flag and + * add or remove the border from the actual window. This is a no-op if the + * window's border already matches the requested state. + * + * \param window The window of which to change the border state. + * \param bordered SDL_FALSE to remove border, SDL_TRUE to add border. + * + * \note You can't change the border state of a fullscreen window. + * + * \sa SDL_GetWindowFlags() + */ +extern DECLSPEC void SDLCALL SDL_SetWindowBordered(SDL_Window * window, + SDL_bool bordered); + +/** + * \brief Show a window. + * + * \sa SDL_HideWindow() + */ +extern DECLSPEC void SDLCALL SDL_ShowWindow(SDL_Window * window); + +/** + * \brief Hide a window. + * + * \sa SDL_ShowWindow() + */ +extern DECLSPEC void SDLCALL SDL_HideWindow(SDL_Window * window); + +/** + * \brief Raise a window above other windows and set the input focus. + */ +extern DECLSPEC void SDLCALL SDL_RaiseWindow(SDL_Window * window); + +/** + * \brief Make a window as large as possible. + * + * \sa SDL_RestoreWindow() + */ +extern DECLSPEC void SDLCALL SDL_MaximizeWindow(SDL_Window * window); + +/** + * \brief Minimize a window to an iconic representation. + * + * \sa SDL_RestoreWindow() + */ +extern DECLSPEC void SDLCALL SDL_MinimizeWindow(SDL_Window * window); + +/** + * \brief Restore the size and position of a minimized or maximized window. + * + * \sa SDL_MaximizeWindow() + * \sa SDL_MinimizeWindow() + */ +extern DECLSPEC void SDLCALL SDL_RestoreWindow(SDL_Window * window); + +/** + * \brief Set a window's fullscreen state. + * + * \return 0 on success, or -1 if setting the display mode failed. + * + * \sa SDL_SetWindowDisplayMode() + * \sa SDL_GetWindowDisplayMode() + */ +extern DECLSPEC int SDLCALL SDL_SetWindowFullscreen(SDL_Window * window, + Uint32 flags); + +/** + * \brief Get the SDL surface associated with the window. + * + * \return The window's framebuffer surface, or NULL on error. + * + * A new surface will be created with the optimal format for the window, + * if necessary. This surface will be freed when the window is destroyed. + * + * \note You may not combine this with 3D or the rendering API on this window. + * + * \sa SDL_UpdateWindowSurface() + * \sa SDL_UpdateWindowSurfaceRects() + */ +extern DECLSPEC SDL_Surface * SDLCALL SDL_GetWindowSurface(SDL_Window * window); + +/** + * \brief Copy the window surface to the screen. + * + * \return 0 on success, or -1 on error. + * + * \sa SDL_GetWindowSurface() + * \sa SDL_UpdateWindowSurfaceRects() + */ +extern DECLSPEC int SDLCALL SDL_UpdateWindowSurface(SDL_Window * window); + +/** + * \brief Copy a number of rectangles on the window surface to the screen. + * + * \return 0 on success, or -1 on error. + * + * \sa SDL_GetWindowSurface() + * \sa SDL_UpdateWindowSurfaceRect() + */ +extern DECLSPEC int SDLCALL SDL_UpdateWindowSurfaceRects(SDL_Window * window, + const SDL_Rect * rects, + int numrects); + +/** + * \brief Set a window's input grab mode. + * + * \param grabbed This is SDL_TRUE to grab input, and SDL_FALSE to release input. + * + * \sa SDL_GetWindowGrab() + */ +extern DECLSPEC void SDLCALL SDL_SetWindowGrab(SDL_Window * window, + SDL_bool grabbed); + +/** + * \brief Get a window's input grab mode. + * + * \return This returns SDL_TRUE if input is grabbed, and SDL_FALSE otherwise. + * + * \sa SDL_SetWindowGrab() + */ +extern DECLSPEC SDL_bool SDLCALL SDL_GetWindowGrab(SDL_Window * window); + +/** + * \brief Set the brightness (gamma correction) for a window. + * + * \return 0 on success, or -1 if setting the brightness isn't supported. + * + * \sa SDL_GetWindowBrightness() + * \sa SDL_SetWindowGammaRamp() + */ +extern DECLSPEC int SDLCALL SDL_SetWindowBrightness(SDL_Window * window, float brightness); + +/** + * \brief Get the brightness (gamma correction) for a window. + * + * \return The last brightness value passed to SDL_SetWindowBrightness() + * + * \sa SDL_SetWindowBrightness() + */ +extern DECLSPEC float SDLCALL SDL_GetWindowBrightness(SDL_Window * window); + +/** + * \brief Set the gamma ramp for a window. + * + * \param red The translation table for the red channel, or NULL. + * \param green The translation table for the green channel, or NULL. + * \param blue The translation table for the blue channel, or NULL. + * + * \return 0 on success, or -1 if gamma ramps are unsupported. + * + * Set the gamma translation table for the red, green, and blue channels + * of the video hardware. Each table is an array of 256 16-bit quantities, + * representing a mapping between the input and output for that channel. + * The input is the index into the array, and the output is the 16-bit + * gamma value at that index, scaled to the output color precision. + * + * \sa SDL_GetWindowGammaRamp() + */ +extern DECLSPEC int SDLCALL SDL_SetWindowGammaRamp(SDL_Window * window, + const Uint16 * red, + const Uint16 * green, + const Uint16 * blue); + +/** + * \brief Get the gamma ramp for a window. + * + * \param red A pointer to a 256 element array of 16-bit quantities to hold + * the translation table for the red channel, or NULL. + * \param green A pointer to a 256 element array of 16-bit quantities to hold + * the translation table for the green channel, or NULL. + * \param blue A pointer to a 256 element array of 16-bit quantities to hold + * the translation table for the blue channel, or NULL. + * + * \return 0 on success, or -1 if gamma ramps are unsupported. + * + * \sa SDL_SetWindowGammaRamp() + */ +extern DECLSPEC int SDLCALL SDL_GetWindowGammaRamp(SDL_Window * window, + Uint16 * red, + Uint16 * green, + Uint16 * blue); + +/** + * \brief Destroy a window. + */ +extern DECLSPEC void SDLCALL SDL_DestroyWindow(SDL_Window * window); + + +/** + * \brief Returns whether the screensaver is currently enabled (default on). + * + * \sa SDL_EnableScreenSaver() + * \sa SDL_DisableScreenSaver() + */ +extern DECLSPEC SDL_bool SDLCALL SDL_IsScreenSaverEnabled(void); + +/** + * \brief Allow the screen to be blanked by a screensaver + * + * \sa SDL_IsScreenSaverEnabled() + * \sa SDL_DisableScreenSaver() + */ +extern DECLSPEC void SDLCALL SDL_EnableScreenSaver(void); + +/** + * \brief Prevent the screen from being blanked by a screensaver + * + * \sa SDL_IsScreenSaverEnabled() + * \sa SDL_EnableScreenSaver() + */ +extern DECLSPEC void SDLCALL SDL_DisableScreenSaver(void); + + +/** + * \name OpenGL support functions + */ +/*@{*/ + +/** + * \brief Dynamically load an OpenGL library. + * + * \param path The platform dependent OpenGL library name, or NULL to open the + * default OpenGL library. + * + * \return 0 on success, or -1 if the library couldn't be loaded. + * + * This should be done after initializing the video driver, but before + * creating any OpenGL windows. If no OpenGL library is loaded, the default + * library will be loaded upon creation of the first OpenGL window. + * + * \note If you do this, you need to retrieve all of the GL functions used in + * your program from the dynamic library using SDL_GL_GetProcAddress(). + * + * \sa SDL_GL_GetProcAddress() + * \sa SDL_GL_UnloadLibrary() + */ +extern DECLSPEC int SDLCALL SDL_GL_LoadLibrary(const char *path); + +/** + * \brief Get the address of an OpenGL function. + */ +extern DECLSPEC void *SDLCALL SDL_GL_GetProcAddress(const char *proc); + +/** + * \brief Unload the OpenGL library previously loaded by SDL_GL_LoadLibrary(). + * + * \sa SDL_GL_LoadLibrary() + */ +extern DECLSPEC void SDLCALL SDL_GL_UnloadLibrary(void); + +/** + * \brief Return true if an OpenGL extension is supported for the current + * context. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_GL_ExtensionSupported(const char + *extension); + +/** + * \brief Set an OpenGL window attribute before window creation. + */ +extern DECLSPEC int SDLCALL SDL_GL_SetAttribute(SDL_GLattr attr, int value); + +/** + * \brief Get the actual value for an attribute from the current context. + */ +extern DECLSPEC int SDLCALL SDL_GL_GetAttribute(SDL_GLattr attr, int *value); + +/** + * \brief Create an OpenGL context for use with an OpenGL window, and make it + * current. + * + * \sa SDL_GL_DeleteContext() + */ +extern DECLSPEC SDL_GLContext SDLCALL SDL_GL_CreateContext(SDL_Window * + window); + +/** + * \brief Set up an OpenGL context for rendering into an OpenGL window. + * + * \note The context must have been created with a compatible window. + */ +extern DECLSPEC int SDLCALL SDL_GL_MakeCurrent(SDL_Window * window, + SDL_GLContext context); + +/** + * \brief Set the swap interval for the current OpenGL context. + * + * \param interval 0 for immediate updates, 1 for updates synchronized with the + * vertical retrace. If the system supports it, you may + * specify -1 to allow late swaps to happen immediately + * instead of waiting for the next retrace. + * + * \return 0 on success, or -1 if setting the swap interval is not supported. + * + * \sa SDL_GL_GetSwapInterval() + */ +extern DECLSPEC int SDLCALL SDL_GL_SetSwapInterval(int interval); + +/** + * \brief Get the swap interval for the current OpenGL context. + * + * \return 0 if there is no vertical retrace synchronization, 1 if the buffer + * swap is synchronized with the vertical retrace, and -1 if late + * swaps happen immediately instead of waiting for the next retrace. + * If the system can't determine the swap interval, or there isn't a + * valid current context, this will return 0 as a safe default. + * + * \sa SDL_GL_SetSwapInterval() + */ +extern DECLSPEC int SDLCALL SDL_GL_GetSwapInterval(void); + +/** + * \brief Swap the OpenGL buffers for a window, if double-buffering is + * supported. + */ +extern DECLSPEC void SDLCALL SDL_GL_SwapWindow(SDL_Window * window); + +/** + * \brief Delete an OpenGL context. + * + * \sa SDL_GL_CreateContext() + */ +extern DECLSPEC void SDLCALL SDL_GL_DeleteContext(SDL_GLContext context); + +/*@}*//*OpenGL support functions*/ + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_video_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/external/SDL2/begin_code.h b/external/SDL2/begin_code.h new file mode 100644 index 0000000..4e613f9 --- /dev/null +++ b/external/SDL2/begin_code.h @@ -0,0 +1,150 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file begin_code.h + * + * This file sets things up for C dynamic library function definitions, + * static inlined functions, and structures aligned at 4-byte alignment. + * If you don't like ugly C preprocessor code, don't look at this file. :) + */ + +/* This shouldn't be nested -- included it around code only. */ +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +/* Some compilers use a special export keyword */ +#ifndef DECLSPEC +# if defined(__BEOS__) || defined(__HAIKU__) +# if defined(__GNUC__) +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC __declspec(export) +# endif +# elif defined(__WIN32__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# else +# define DECLSPEC +# endif +# endif +#endif + +/* By default SDL uses the C calling convention */ +#ifndef SDLCALL +#if defined(__WIN32__) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif /* SDLCALL */ + +/* Removed DECLSPEC on Symbian OS because SDL cannot be a DLL in EPOC */ +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif /* __SYMBIAN32__ */ + +/* Force structure packing at 4 byte alignment. + This is necessary if the header is included in code which has structure + packing set to an alternate value, say for loading structures from disk. + The packing is reset to the previous value in close_code.h + */ +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 +/* Use 8-byte alignment on 64-bit architectures, so pointers are aligned */ +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif /* Compiler needs structure packing set */ + +/* Set up compiler-specific options for inlining functions */ +#ifndef SDL_INLINE_OKAY +#ifdef __GNUC__ +#define SDL_INLINE_OKAY +#else +/* Add any special compiler-specific cases here */ +#if defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#ifndef __inline__ +#define __inline__ __inline +#endif +#define SDL_INLINE_OKAY +#else +#if !defined(__MRC__) && !defined(_SGI_SOURCE) +#ifndef __inline__ +#define __inline__ inline +#endif +#define SDL_INLINE_OKAY +#endif /* Not a funky compiler */ +#endif /* Visual C++ */ +#endif /* GNU C */ +#endif /* SDL_INLINE_OKAY */ + +/* If inlining isn't supported, remove "__inline__", turning static + inlined functions into static functions (resulting in code bloat + in all files which include the offending header files) +*/ +#ifndef SDL_INLINE_OKAY +#define __inline__ +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static inline +#else +#define SDL_FORCE_INLINE static __inline__ +#endif +#endif + +/* Apparently this is needed by several Windows compilers */ +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif /* NULL */ +#endif /* ! Mac OS X - breaks precompiled headers */ diff --git a/external/SDL2/close_code.h b/external/SDL2/close_code.h new file mode 100644 index 0000000..4100603 --- /dev/null +++ b/external/SDL2/close_code.h @@ -0,0 +1,37 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file close_code.h + * + * This file reverses the effects of begin_code.h and should be included + * after you finish any function and structure declarations in your headers + */ + +#undef _begin_code_h + +/* Reset structure packing at previous byte alignment */ +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif /* Compiler needs structure packing set */ diff --git a/external/SDL2/merge_sdl2.sh b/external/SDL2/merge_sdl2.sh new file mode 100755 index 0000000..de45805 --- /dev/null +++ b/external/SDL2/merge_sdl2.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +p4 integ -i -d -Dt //steam/rel/client/src/external/SDL2/... //ValveGames/main/GoldSrc/external/SDL2/... +p4 integ -i -d -Dt //steam/rel/client/client/osx32/libsdl... //ValveGames/rel/goldsource/hl1/game/libsdl... +p4 integ -i -d -Dt //steam/rel/client/client/osx32/libsdl... //ValveGames/main/GoldSrc/linux/libsdl... +p4 integ -i -d -Dt //steam/rel/client/client/ubuntu12_32/libsdl... //ValveGames/rel/goldsource/hl1/game/libsdl... +p4 integ -i -d -Dt //steam/rel/client/client/ubuntu12_32/libsdl... //ValveGames/main/GoldSrc/linux/libsdl... + +p4 integ -i -d -Dt //steam/rel/client/client/sdl... //ValveGames/rel/goldsource/hl1/game/sdl... +p4 resolve -am //ValveGames/main/GoldSrc/external/SDL2/... +p4 resolve -at //ValveGames/rel/goldsource/hl1/game/sdl... //ValveGames/rel/goldsource/hl1/game/libsdl... //ValveGames/main/GoldSrc/linux/libsdl... diff --git a/filecopy.bat b/filecopy.bat new file mode 100755 index 0000000..3985593 --- /dev/null +++ b/filecopy.bat @@ -0,0 +1,4 @@ +rem @echo off +rem echo %~f2 +p4 edit %~f2 +@copy %1 %~f2 diff --git a/game_shared/GameEvent.h b/game_shared/GameEvent.h new file mode 100644 index 0000000..26df6ca --- /dev/null +++ b/game_shared/GameEvent.h @@ -0,0 +1,282 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003 +// Author: Matthew D. Campbell (matt@turtlerockstudios.com), 2003 + +#ifndef GAME_EVENT_H +#define GAME_EVENT_H + +// +// NOTE: This enum is used by both the client and server, so only add events to the +// end of the list to remain maximally compatible with existing clients. +// + +// Define some event types used in various places (CZ bot code, career mode, and tutor, initially) +enum GameEventType +{ + EVENT_INVALID = 0, + + EVENT_WEAPON_FIRED, + EVENT_WEAPON_FIRED_ON_EMPTY, + EVENT_WEAPON_RELOADED, + EVENT_HE_GRENADE_EXPLODED, + EVENT_FLASHBANG_GRENADE_EXPLODED, + EVENT_SMOKE_GRENADE_EXPLODED, + EVENT_GRENADE_BOUNCED, + EVENT_BEING_SHOT_AT, ///< this will only trigger for the local player in single player games. + EVENT_PLAYER_BLINDED_BY_FLASHBANG, + + EVENT_PLAYER_FOOTSTEP, + EVENT_PLAYER_JUMPED, + EVENT_PLAYER_DIED, + EVENT_PLAYER_LANDED_FROM_HEIGHT, + EVENT_PLAYER_TOOK_DAMAGE, + + EVENT_HOSTAGE_DAMAGED, + EVENT_HOSTAGE_KILLED, + + EVENT_DOOR, + EVENT_BREAK_GLASS, + EVENT_BREAK_WOOD, + EVENT_BREAK_METAL, + EVENT_BREAK_FLESH, + EVENT_BREAK_CONCRETE, + + EVENT_BOMB_PLANTED, + EVENT_BOMB_DROPPED, + EVENT_BOMB_PICKED_UP, + EVENT_BOMB_BEEP, ///< timer beep when counting down to detonation + EVENT_BOMB_DEFUSING, + EVENT_BOMB_DEFUSE_ABORTED, + EVENT_BOMB_DEFUSED, + EVENT_BOMB_EXPLODED, + + EVENT_HOSTAGE_USED, + EVENT_HOSTAGE_RESCUED, + EVENT_ALL_HOSTAGES_RESCUED, + + EVENT_VIP_ESCAPED, + EVENT_VIP_ASSASSINATED, + + EVENT_TERRORISTS_WIN, + EVENT_CTS_WIN, + EVENT_ROUND_DRAW, + EVENT_ROUND_WIN, ///< win/loss relative to the client + EVENT_ROUND_LOSS, ///< win/loss relative to the client + EVENT_ROUND_START, + EVENT_PLAYER_SPAWNED, + EVENT_CLIENT_CORPSE_SPAWNED, + EVENT_BUY_TIME_START, + EVENT_PLAYER_LEFT_BUY_ZONE, ///< triggered by tutor system when the local player leaves the buy zone/buy time expires for the first time. + EVENT_DEATH_CAMERA_START, + EVENT_KILL_ALL, ///< everybody on one team has been killed + EVENT_ROUND_TIME, + + // client-relative events for career mode: has the player killed with certain weapons, etc? + + EVENT_DIE, + EVENT_KILL, ///< someone has been killed + EVENT_HEADSHOT, ///< subset of EVENT_KILL + EVENT_KILL_FLASHBANGED, + + // Tutor Buy menu events + EVENT_TUTOR_BUY_MENU_OPENNED, + EVENT_TUTOR_AUTOBUY, + EVENT_PLAYER_BOUGHT_SOMETHING, + EVENT_TUTOR_NOT_BUYING_ANYTHING, + EVENT_TUTOR_NEED_TO_BUY_PRIMARY_WEAPON, + EVENT_TUTOR_NEED_TO_BUY_PRIMARY_AMMO, + EVENT_TUTOR_NEED_TO_BUY_SECONDARY_AMMO, + EVENT_TUTOR_NEED_TO_BUY_ARMOR, + EVENT_TUTOR_NEED_TO_BUY_DEFUSE_KIT, + EVENT_TUTOR_NEED_TO_BUY_GRENADE, + + EVENT_CAREER_TASK_DONE, + + // radio messages (these must be kept in sync with actual radio) ------------------------------------- + + EVENT_START_RADIO_1, ///< radio messages between this and RADIO_2 and part of Radio1() + + EVENT_RADIO_COVER_ME, + EVENT_RADIO_YOU_TAKE_THE_POINT, + EVENT_RADIO_HOLD_THIS_POSITION, + EVENT_RADIO_REGROUP_TEAM, + EVENT_RADIO_FOLLOW_ME, + EVENT_RADIO_TAKING_FIRE, + + EVENT_START_RADIO_2, ///< radio messages between this and RADIO_3 are part of Radio2() + + EVENT_RADIO_GO_GO_GO, + EVENT_RADIO_TEAM_FALL_BACK, + EVENT_RADIO_STICK_TOGETHER_TEAM, + EVENT_RADIO_GET_IN_POSITION_AND_WAIT, + EVENT_RADIO_STORM_THE_FRONT, + EVENT_RADIO_REPORT_IN_TEAM, + + EVENT_START_RADIO_3, ///< radio messages above this are part of Radio3() + + EVENT_RADIO_AFFIRMATIVE, + EVENT_RADIO_ENEMY_SPOTTED, + EVENT_RADIO_NEED_BACKUP, + EVENT_RADIO_SECTOR_CLEAR, + EVENT_RADIO_IN_POSITION, + EVENT_RADIO_REPORTING_IN, + EVENT_RADIO_GET_OUT_OF_THERE, + EVENT_RADIO_NEGATIVE, + EVENT_RADIO_ENEMY_DOWN, + + EVENT_END_RADIO, + + EVENT_NEW_MATCH, ///< sent at the start of a new map/match. + EVENT_PLAYER_CHANGED_TEAM, ///< sent when a player changes teams. + EVENT_BULLET_IMPACT, + EVENT_GAME_COMMENCE, + EVENT_WEAPON_ZOOMED, + + EVENT_HOSTAGE_CALLED_FOR_HELP, ///< hostage yelled to a CT + + NUM_GAME_EVENTS +}; + +#ifdef DEFINE_EVENT_NAMES +// NOTE: These must be kept in sync with the GameEventType enum +const char *GameEventName[ NUM_GAME_EVENTS+1 ] = +{ + "EVENT_INVALID", + + "EVENT_WEAPON_FIRED", + "EVENT_WEAPON_FIRED_ON_EMPTY", + "EVENT_WEAPON_RELOADED", + "EVENT_HE_GRENADE_EXPLODED", + "EVENT_FLASHBANG_GRENADE_EXPLODED", + "EVENT_SMOKE_GRENADE_EXPLODED", + "EVENT_GRENADE_BOUNCED", + "EVENT_BEING_SHOT_AT", + "EVENT_PLAYER_BLINDED_BY_FLASHBANG", + + "EVENT_PLAYER_FOOTSTEP", + "EVENT_PLAYER_JUMPED", + "EVENT_PLAYER_DIED", + "EVENT_PLAYER_LANDED_FROM_HEIGHT", + "EVENT_PLAYER_TOOK_DAMAGE", + + "EVENT_HOSTAGE_DAMAGED", + "EVENT_HOSTAGE_KILLED", + + "EVENT_DOOR", + "EVENT_BREAK_GLASS", + "EVENT_BREAK_WOOD", + "EVENT_BREAK_METAL", + "EVENT_BREAK_FLESH", + "EVENT_BREAK_CONCRETE", + + "EVENT_BOMB_PLANTED", + "EVENT_BOMB_DROPPED", + "EVENT_BOMB_PICKED_UP", + "EVENT_BOMB_BEEP", + "EVENT_BOMB_DEFUSING", + "EVENT_BOMB_DEFUSE_ABORTED", + "EVENT_BOMB_DEFUSED", + "EVENT_BOMB_EXPLODED", + + "EVENT_HOSTAGE_USED", + "EVENT_HOSTAGE_RESCUED", + + "EVENT_ALL_HOSTAGES_RESCUED", + + "EVENT_VIP_ESCAPED", + "EVENT_VIP_ASSASSINATED", + + "EVENT_TERRORISTS_WIN", + "EVENT_CTS_WIN", + "EVENT_ROUND_DRAW", + + "EVENT_ROUND_WIN", + "EVENT_ROUND_LOSS", + "EVENT_ROUND_START", + "EVENT_PLAYER_SPAWNED", + "EVENT_CLIENT_CORPSE_SPAWNED", + "EVENT_BUY_TIME_START", + "EVENT_PLAYER_LEFT_BUY_ZONE", + "EVENT_DEATH_CAMERA_START", + "EVENT_KILL_ALL", + "EVENT_ROUND_TIME", + + // client-relative events for career mode: has the player killed with certain weapons, etc? + + "EVENT_DIE", + "EVENT_KILL", + "EVENT_HEADSHOT", + "EVENT_KILL_FLASHBANGED", + + // Tutor Buy menu events + "EVENT_TUTOR_BUY_MENU_OPENNED", + "EVENT_TUTOR_AUTOBUY", + "EVENT_PLAYER_BOUGHT_SOMETHING", + "EVENT_TUTOR_NOT_BUYING_ANYTHING", + "EVENT_TUTOR_NEED_TO_BUY_PRIMARY_WEAPON", + "EVENT_TUTOR_NEED_TO_BUY_PRIMARY_AMMO", + "EVENT_TUTOR_NEED_TO_BUY_SECONDARY_AMMO", + "EVENT_TUTOR_NEED_TO_BUY_ARMOR", + "EVENT_TUTOR_NEED_TO_BUY_DEFUSE_KIT", + "EVENT_TUTOR_NEED_TO_BUY_GRENADE", + + "EVENT_CAREER_TASK_DONE", + + // radio messages (these must be kept in sync with actual radio) ------------------------------------- + + "EVENT_START_RADIO_1", + + "EVENT_RADIO_COVER_ME", + "EVENT_RADIO_YOU_TAKE_THE_POINT", + "EVENT_RADIO_HOLD_THIS_POSITION", + "EVENT_RADIO_REGROUP_TEAM", + "EVENT_RADIO_FOLLOW_ME", + "EVENT_RADIO_TAKING_FIRE", + + "EVENT_START_RADIO_2", + + "EVENT_RADIO_GO_GO_GO", + "EVENT_RADIO_TEAM_FALL_BACK", + "EVENT_RADIO_STICK_TOGETHER_TEAM", + "EVENT_RADIO_GET_IN_POSITION_AND_WAIT", + "EVENT_RADIO_STORM_THE_FRONT", + "EVENT_RADIO_REPORT_IN_TEAM", + + "EVENT_START_RADIO_3", + + "EVENT_RADIO_AFFIRMATIVE", + "EVENT_RADIO_ENEMY_SPOTTED", + "EVENT_RADIO_NEED_BACKUP", + "EVENT_RADIO_SECTOR_CLEAR", + "EVENT_RADIO_IN_POSITION", + "EVENT_RADIO_REPORTING_IN", + "EVENT_RADIO_GET_OUT_OF_THERE", + "EVENT_RADIO_NEGATIVE", + "EVENT_RADIO_ENEMY_DOWN", + + "EVENT_END_RADIO", + + "EVENT_NEW_MATCH", + "EVENT_PLAYER_CHANGED_TEAM", + "EVENT_BULLET_IMPACT", + "EVENT_GAME_COMMENCE", + "EVENT_WEAPON_ZOOMED", + + "EVENT_HOSTAGE_CALLED_FOR_HELP", + + NULL // must be NULL-terminated +}; +#else +extern const char *GameEventName[ NUM_GAME_EVENTS ]; +#endif + +/// convert name to GameEventType +extern GameEventType NameToGameEvent( const char *name ); + +#endif // GAME_EVENT_H diff --git a/game_shared/bitvec.h b/game_shared/bitvec.h new file mode 100644 index 0000000..09fdece --- /dev/null +++ b/game_shared/bitvec.h @@ -0,0 +1,181 @@ +//========= Copyright � 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef BITVEC_H +#define BITVEC_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "archtypes.h" // DAL +#include +#include + + +class CBitVecAccessor +{ +public: + CBitVecAccessor(uint32 *pDWords, int iBit); + + void operator=(int val); + operator uint32(); + +private: + uint32 *m_pDWords; + int m_iBit; +}; + + +// CBitVec allows you to store a list of bits and do operations on them like they were +// an atomic type. +template +class CBitVec +{ +public: + + CBitVec(); + + // Set all values to the specified value (0 or 1..) + void Init(int val = 0); + + // Access the bits like an array. + CBitVecAccessor operator[](int i); + + // Operations on other bit vectors. + CBitVec& operator=(CBitVec const &other); + bool operator==(CBitVec const &other); + bool operator!=(CBitVec const &other); + + // Get underlying dword representations of the bits. + int GetNumDWords(); + uint32 GetDWord(int i); + void SetDWord(int i, uint32 val); + + int GetNumBits(); + +private: + + enum {NUM_DWORDS = NUM_BITS/32 + !!(NUM_BITS & 31)}; + uint32 m_DWords[NUM_DWORDS]; +}; + + + +// ------------------------------------------------------------------------ // +// CBitVecAccessor inlines. +// ------------------------------------------------------------------------ // + +inline CBitVecAccessor::CBitVecAccessor(uint32 *pDWords, int iBit) +{ + m_pDWords = pDWords; + m_iBit = iBit; +} + + +inline void CBitVecAccessor::operator=(int val) +{ + if(val) + m_pDWords[m_iBit >> 5] |= (1 << (m_iBit & 31)); + else + m_pDWords[m_iBit >> 5] &= ~(uint32)(1 << (m_iBit & 31)); +} + +inline CBitVecAccessor::operator uint32() +{ + return m_pDWords[m_iBit >> 5] & (1 << (m_iBit & 31)); +} + + + +// ------------------------------------------------------------------------ // +// CBitVec inlines. +// ------------------------------------------------------------------------ // + +template +inline int CBitVec::GetNumBits() +{ + return NUM_BITS; +} + + +template +inline CBitVec::CBitVec() +{ + for(int i=0; i < NUM_DWORDS; i++) + m_DWords[i] = 0; +} + + +template +inline void CBitVec::Init(int val) +{ + for(int i=0; i < GetNumBits(); i++) + { + (*this)[i] = val; + } +} + + +template +inline CBitVec& CBitVec::operator=(CBitVec const &other) +{ + memcpy(m_DWords, other.m_DWords, sizeof(m_DWords)); + return *this; +} + + +template +inline CBitVecAccessor CBitVec::operator[](int i) +{ + assert(i >= 0 && i < GetNumBits()); + return CBitVecAccessor(m_DWords, i); +} + + +template +inline bool CBitVec::operator==(CBitVec const &other) +{ + for(int i=0; i < NUM_DWORDS; i++) + if(m_DWords[i] != other.m_DWords[i]) + return false; + + return true; +} + + +template +inline bool CBitVec::operator!=(CBitVec const &other) +{ + return !(*this == other); +} + + +template +inline int CBitVec::GetNumDWords() +{ + return NUM_DWORDS; +} + +template +inline uint32 CBitVec::GetDWord(int i) +{ + assert(i >= 0 && i < NUM_DWORDS); + return m_DWords[i]; +} + + +template +inline void CBitVec::SetDWord(int i, uint32 val) +{ + assert(i >= 0 && i < NUM_DWORDS); + m_DWords[i] = val; +} + + +#endif // BITVEC_H + diff --git a/game_shared/bot/bot.cpp b/game_shared/bot/bot.cpp new file mode 100644 index 0000000..359c771 --- /dev/null +++ b/game_shared/bot/bot.cpp @@ -0,0 +1,626 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// Author: Michael S. Booth (mike@turtlerockstudios.com), Leon Hartwig, 2003 + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "soundent.h" +#include "gamerules.h" +#include "player.h" +#include "client.h" +#include "pm_shared.h" + +#include "bot.h" +#include "bot_util.h" + +DLL_GLOBAL float g_flBotCommandInterval = 1.0 / 30.0; // 30 times per second, just like human clients +DLL_GLOBAL float g_flBotFullThinkInterval = 1.0 / 10.0; // full AI only 10 times per second + + +//-------------------------------------------------------------------------------------------------------------- +CBot::CBot( void ) +{ + // the profile will be attached after this instance is constructed + m_profile = NULL; + + // assign this bot a unique ID + static unsigned int nextID = 1; + + // wraparound (highly unlikely) + if (nextID == 0) + ++nextID; + + m_id = nextID; + ++nextID; + + m_postureStackIndex = 0; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Prepare bot for action + */ +bool CBot::Initialize( const BotProfile *profile ) +{ + m_profile = profile; + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +void CBot::Spawn( void ) +{ + // Let CBasePlayer set some things up + CBasePlayer::Spawn(); + + // Make sure everyone knows we are a bot + pev->flags |= ( FL_CLIENT | FL_FAKECLIENT ); + + // Bots use their own thinking mechanism + SetThink( NULL ); + pev->nextthink = -1; + + m_flNextBotThink = gpGlobals->time + g_flBotCommandInterval; + m_flNextFullBotThink = gpGlobals->time + g_flBotFullThinkInterval; + m_flPreviousCommandTime = gpGlobals->time; + + m_isRunning = true; + m_isCrouching = false; + m_postureStackIndex = 0; + + m_jumpTimestamp = 0.0f; + + // Command interface variable initialization + ResetCommand(); + + // Allow derived classes to setup at spawn time + SpawnBot(); +} + + +//-------------------------------------------------------------------------------------------------------------- +Vector CBot::GetAutoaimVector( float flDelta ) +{ + UTIL_MakeVectors( pev->v_angle + pev->punchangle ); + + return gpGlobals->v_forward; +} + + +//-------------------------------------------------------------------------------------------------------------- +void CBot::BotThink( void ) +{ + if ( gpGlobals->time >= m_flNextBotThink ) + { + m_flNextBotThink = gpGlobals->time + g_flBotCommandInterval; + + Upkeep(); + + if ( gpGlobals->time >= m_flNextFullBotThink ) + { + m_flNextFullBotThink = gpGlobals->time + g_flBotFullThinkInterval; + + ResetCommand(); + Update(); + } + + ExecuteCommand(); + } +} + + +//-------------------------------------------------------------------------------------------------------------- +void CBot::MoveForward( void ) +{ + m_forwardSpeed = GetMoveSpeed(); + SetBits( m_buttonFlags, IN_FORWARD ); + + // make mutually exclusive + ClearBits( m_buttonFlags, IN_BACK ); +} + + +//-------------------------------------------------------------------------------------------------------------- +void CBot::MoveBackward( void ) +{ + m_forwardSpeed = -GetMoveSpeed(); + SetBits( m_buttonFlags, IN_BACK ); + + // make mutually exclusive + ClearBits( m_buttonFlags, IN_FORWARD ); +} + +//-------------------------------------------------------------------------------------------------------------- +void CBot::StrafeLeft( void ) +{ + m_strafeSpeed = -GetMoveSpeed(); + SetBits( m_buttonFlags, IN_MOVELEFT ); + + // make mutually exclusive + ClearBits( m_buttonFlags, IN_MOVERIGHT ); +} + +//-------------------------------------------------------------------------------------------------------------- +void CBot::StrafeRight( void ) +{ + m_strafeSpeed = GetMoveSpeed(); + SetBits( m_buttonFlags, IN_MOVERIGHT ); + + // make mutually exclusive + ClearBits( m_buttonFlags, IN_MOVELEFT ); +} + +//-------------------------------------------------------------------------------------------------------------- +bool CBot::Jump( bool mustJump ) +{ + if (IsJumping() || IsCrouching()) + return false; + + if (!mustJump) + { + const float minJumpInterval = 0.9f; // 1.5f; + if (gpGlobals->time - m_jumpTimestamp < minJumpInterval) + return false; + } + + // still need sanity check for jumping frequency + const float sanityInterval = 0.3f; + if (gpGlobals->time - m_jumpTimestamp < sanityInterval) + return false; + + // jump + SetBits( m_buttonFlags, IN_JUMP ); + m_jumpTimestamp = gpGlobals->time; + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Zero any MoveForward(), Jump(), etc + */ +void CBot::ClearMovement( void ) +{ + ResetCommand(); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Returns true if we are in the midst of a jump + */ +bool CBot::IsJumping( void ) +{ + // if long time after last jump, we can't be jumping + if (gpGlobals->time - m_jumpTimestamp > 3.0f) + return false; + + // if we just jumped, we're still jumping + if (gpGlobals->time - m_jumpTimestamp < 1.0f) + return true; + + // a little after our jump, we're jumping until we hit the ground + if (FBitSet( pev->flags, FL_ONGROUND )) + return false; + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +void CBot::Crouch( void ) +{ + m_isCrouching = true; +} + +//-------------------------------------------------------------------------------------------------------------- +void CBot::StandUp( void ) +{ + m_isCrouching = false; +} + + +//-------------------------------------------------------------------------------------------------------------- +void CBot::UseEnvironment( void ) +{ + SetBits( m_buttonFlags, IN_USE ); +} + + +//-------------------------------------------------------------------------------------------------------------- +void CBot::PrimaryAttack( void ) +{ + SetBits( m_buttonFlags, IN_ATTACK ); +} + +//-------------------------------------------------------------------------------------------------------------- +void CBot::ClearPrimaryAttack( void ) +{ + ClearBits( m_buttonFlags, IN_ATTACK ); +} + +//-------------------------------------------------------------------------------------------------------------- +void CBot::TogglePrimaryAttack( void ) +{ + if (FBitSet( m_buttonFlags, IN_ATTACK )) + { + ClearBits( m_buttonFlags, IN_ATTACK ); + } + else + { + SetBits( m_buttonFlags, IN_ATTACK ); + } +} + + +//-------------------------------------------------------------------------------------------------------------- +void CBot::SecondaryAttack( void ) +{ + SetBits( m_buttonFlags, IN_ATTACK2 ); +} + +//-------------------------------------------------------------------------------------------------------------- +void CBot::Reload( void ) +{ + SetBits( m_buttonFlags, IN_RELOAD ); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Returns ratio of ammo left to max ammo (1 = full clip, 0 = empty) + */ +float CBot::GetActiveWeaponAmmoRatio( void ) const +{ + CBasePlayerWeapon *gun = GetActiveWeapon(); + + if ( !gun ) + return 0.0f; + + // weapons with no ammo are always full + if (gun->m_iClip < 0) + return 1.0f; + + return (float)gun->m_iClip / (float)gun->iMaxClip(); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if active weapon has an empty clip + */ +bool CBot::IsActiveWeaponClipEmpty( void ) const +{ + CBasePlayerWeapon *gun = GetActiveWeapon(); + + if (gun && gun->m_iClip == 0) + return true; + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if active weapon has no ammo at all + */ +bool CBot::IsActiveWeaponOutOfAmmo( void ) const +{ + CBasePlayerWeapon *gun = GetActiveWeapon(); + + if (gun == NULL) + return true; + + if (gun->m_iClip < 0) + return false; + + if (gun->m_iClip == 0 && m_rgAmmo[ gun->m_iPrimaryAmmoType ] <= 0) + return true; + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if looking thru weapon's scope + */ +bool CBot::IsUsingScope( void ) const +{ + // if our field of view is less than 90, we're looking thru a scope (maybe only true for CS...) + if (m_iFOV < 90.0f) + return true; + + return false; +} + + +//-------------------------------------------------------------------------------------------------------------- +void CBot::ExecuteCommand( void ) +{ + byte adjustedMSec; + + // Adjust msec to command time interval + adjustedMSec = ThrottledMsec(); + + // player model is "munged" + pev->angles = pev->v_angle; + pev->angles.x /= -3.0; + + // save the command time + m_flPreviousCommandTime = gpGlobals->time; + + if (m_isCrouching) + SetBits( m_buttonFlags, IN_DUCK ); + + // Run the command + (*g_engfuncs.pfnRunPlayerMove)( edict(), pev->v_angle, m_forwardSpeed, m_strafeSpeed, m_verticalSpeed, + m_buttonFlags, 0, adjustedMSec ); +} + + +//-------------------------------------------------------------------------------------------------------------- +void CBot::ResetCommand( void ) +{ + m_forwardSpeed = 0.0; + m_strafeSpeed = 0.0; + m_verticalSpeed = 0.0; + m_buttonFlags = 0; +} + + +//-------------------------------------------------------------------------------------------------------------- +byte CBot::ThrottledMsec( void ) const +{ + int iNewMsec; + + // Estimate Msec to use for this command based on time passed from the previous command + iNewMsec = (int)( (gpGlobals->time - m_flPreviousCommandTime) * 1000 ); + if (iNewMsec > 255) // Doh, bots are going to be slower than they should if this happens. + iNewMsec = 255; // Upgrade that CPU or use less bots! + + return (byte)iNewMsec; +} + +//-------------------------------------------------------------------------------------------------------------- + +// Nasty Hack. See client.cpp/ClientCommand() +const char *BotArgs[4] = { NULL }; +bool UseBotArgs = false; + +/** + * Do a "client command" - useful for invoking menu choices, etc. + */ +void CBot::ClientCommand( const char *cmd, const char *arg1, const char *arg2, const char *arg3 ) +{ + BotArgs[0] = cmd; + BotArgs[1] = arg1; + BotArgs[2] = arg2; + BotArgs[3] = arg3; + + UseBotArgs = true; + ::ClientCommand( ENT( pev ) ); + UseBotArgs = false; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Returns TRUE if given entity is our enemy + */ +bool CBot::IsEnemy( CBaseEntity *ent ) const +{ + // only Players (real and AI) can be enemies + if (!ent->IsPlayer()) + return false; + + // corpses are no threat + if (!ent->IsAlive()) + return false; + + CBasePlayer *player = static_cast( ent ); + + // if they are on our team, they are our friends + if (player->m_iTeam == m_iTeam) + return false; + + // yep, we hate 'em + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return number of enemies left alive + */ +int CBot::GetEnemiesRemaining( void ) const +{ + int count = 0; + + for ( int i = 1; i <= gpGlobals->maxClients; ++i ) + { + CBaseEntity *player = UTIL_PlayerByIndex( i ); + + if (player == NULL) + continue; + + if (FNullEnt( player->pev )) + continue; + + if (FStrEq( STRING( player->pev->netname ), "" )) + continue; + + if (!IsEnemy( player )) + continue; + + if (!player->IsAlive()) + continue; + + count++; + } + + return count; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return number of friends left alive + */ +int CBot::GetFriendsRemaining( void ) const +{ + int count = 0; + + for ( int i = 1; i <= gpGlobals->maxClients; ++i ) + { + CBaseEntity *player = UTIL_PlayerByIndex( i ); + + if (player == NULL) + continue; + + if (FNullEnt( player->pev )) + continue; + + if (FStrEq( STRING( player->pev->netname ), "" )) + continue; + + if (IsEnemy( player )) + continue; + + if (!player->IsAlive()) + continue; + + if (player == static_cast( const_cast( this ) )) + continue; + + count++; + } + + return count; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if the local player is currently in observer mode watching this bot. + */ +bool CBot::IsLocalPlayerWatchingMe( void ) const +{ + // avoid crash during spawn + if (pev == NULL) + return false; + + int myIndex = const_cast(this)->entindex(); + + CBasePlayer *player = UTIL_GetLocalPlayer(); + if (player == NULL) + return false; + + if (player->pev->flags & FL_SPECTATOR || player->m_iTeam == SPECTATOR) + { + if (player->pev->iuser2 == myIndex) + { + switch( player->pev->iuser1 ) + { + case OBS_IN_EYE: + case OBS_CHASE_LOCKED: + case OBS_CHASE_FREE: + return true; + } + } + } + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Output message to console + */ +void CBot::Print( char *format, ... ) const +{ + va_list varg; + char buffer[1024]; + + // prefix the message with the bot's name + sprintf( buffer, "%s: ", STRING(pev->netname) ); + (*g_engfuncs.pfnServerPrint)( buffer ); + + va_start( varg, format ); + vsprintf( buffer, format, varg ); + va_end( varg ); + + (*g_engfuncs.pfnServerPrint)( buffer ); +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Output message to console if we are being watched by the local player + */ +void CBot::PrintIfWatched( char *format, ... ) const +{ + if (cv_bot_debug.value == 0) + return; + + if ((IsLocalPlayerWatchingMe() && (cv_bot_debug.value == 1 || cv_bot_debug.value == 3)) || + (cv_bot_debug.value == 2 || cv_bot_debug.value == 4)) + { + va_list varg; + char buffer[1024]; + + // prefix the message with the bot's name (this can be NULL if bot was just added) + const char *name; + if (pev == NULL) + name = "(NULL pev)"; + else + name = STRING(pev->netname); + sprintf( buffer, "%s: ", (name) ? name : "(NULL netname)" ); + (*g_engfuncs.pfnServerPrint)( buffer ); + + va_start( varg, format ); + vsprintf( buffer, format, varg ); + va_end( varg ); + + (*g_engfuncs.pfnServerPrint)( buffer ); + } +} + +//-------------------------------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------------------------------- + +ActiveGrenade::ActiveGrenade( int weaponID, CGrenade *grenadeEntity ) +{ + m_id = weaponID; + m_entity = grenadeEntity; + m_detonationPosition = grenadeEntity->pev->origin; + m_dieTimestamp = 0.0f; +} + +//-------------------------------------------------------------------------------------------------------------- +void ActiveGrenade::OnEntityGone( void ) ///< called when the grenade in the world goes away +{ + if (m_id == WEAPON_SMOKEGRENADE) + { + // smoke lingers after grenade is gone + const float smokeLingerTime = 4.0f; + m_dieTimestamp = gpGlobals->time + smokeLingerTime; + } + + m_entity = NULL; +} + +//-------------------------------------------------------------------------------------------------------------- +bool ActiveGrenade::IsValid( void ) const ///< return true if this grenade is valid +{ + if (m_entity) + return true; + + if (gpGlobals->time > m_dieTimestamp) + return false; + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +const Vector *ActiveGrenade::GetPosition( void ) const +{ + return &m_entity->pev->origin; +} + diff --git a/game_shared/bot/bot.h b/game_shared/bot/bot.h new file mode 100644 index 0000000..4ef9514 --- /dev/null +++ b/game_shared/bot/bot.h @@ -0,0 +1,386 @@ +//========= Copyright � 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef BOT_H +#define BOT_H + +#if !defined ( _WIN32 ) +#define MAX_OSPATH PATH_MAX +#else +// stolen from quakedef.h +#define MAX_OSPATH 260 +#endif + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" + +#include "bot_manager.h" +#include "bot_util.h" +#include "bot_constants.h" + +extern DLL_GLOBAL float g_flBotCommandInterval; +extern DLL_GLOBAL float g_flBotFullThinkInterval; + +extern DLL_GLOBAL CBotManager *TheBots; + +class BotProfile; + + +//-------------------------------------------------------------------------------------------------------- +template T * CreateBot( const BotProfile *profile ) +{ + edict_t * pentBot; + + if ( UTIL_ClientsInGame() >= gpGlobals->maxClients ) + { + CONSOLE_ECHO( "Unable to create bot: Server is full (%d/%d clients).\n", UTIL_ClientsInGame(), gpGlobals->maxClients ); + return NULL; + } + + char netname[64]; + + UTIL_ConstructBotNetName(netname, 64, profile); + + pentBot = CREATE_FAKE_CLIENT( netname ); + + if ( FNullEnt( pentBot ) ) + { + CONSOLE_ECHO( "Unable to create bot: pfnCreateFakeClient() returned null.\n" ); + return NULL; + } + else + { + T * pBot = NULL; + + FREE_PRIVATE( pentBot ); + pBot = GetClassPtr( (T *)VARS( pentBot ) ); + + // initialize the bot + pBot->Initialize( profile ); + + return pBot; + } +} + +//---------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------- +/** + * The base bot class from which bots for specific games are derived + */ +class CBot : public CBasePlayer +{ +public: + CBot( void ); ///< constructor initializes all values to zero + virtual bool Initialize( const BotProfile *profile ); ///< (EXTEND) prepare bot for action + + unsigned int GetID( void ) const { return m_id; } ///< return bot's unique ID + + virtual BOOL IsBot( void ) { return true; } + + virtual void SpawnBot( void ) = 0; + virtual void Upkeep( void ) = 0; ///< lightweight maintenance, invoked frequently + virtual void Update( void ) = 0; ///< heavyweight algorithms, invoked less often + + + virtual void Run( void ); + virtual void Walk( void ); + bool IsRunning( void ) const { return m_isRunning; } + + virtual void Crouch( void ); + virtual void StandUp( void ); + bool IsCrouching( void ) const { return m_isCrouching; } + + void PushPostureContext( void ); ///< push the current posture context onto the top of the stack + void PopPostureContext( void ); ///< restore the posture context to the next context on the stack + + virtual void MoveForward( void ); + virtual void MoveBackward( void ); + virtual void StrafeLeft( void ); + virtual void StrafeRight( void ); + + #define MUST_JUMP true + virtual bool Jump( bool mustJump = false ); ///< returns true if jump was started + bool IsJumping( void ); ///< returns true if we are in the midst of a jump + float GetJumpTimestamp( void ) const { return m_jumpTimestamp; } ///< return time last jump began + + virtual void ClearMovement( void ); ///< zero any MoveForward(), Jump(), etc + + //------------------------------------------------------------------------------------ + // Weapon interface + // + virtual void UseEnvironment( void ); + virtual void PrimaryAttack( void ); + virtual void ClearPrimaryAttack( void ); + virtual void TogglePrimaryAttack( void ); + virtual void SecondaryAttack( void ); + virtual void Reload( void ); + + float GetActiveWeaponAmmoRatio( void ) const; ///< returns ratio of ammo left to max ammo (1 = full clip, 0 = empty) + bool IsActiveWeaponClipEmpty( void ) const; ///< return true if active weapon has any empty clip + bool IsActiveWeaponOutOfAmmo( void ) const; ///< return true if active weapon has no ammo at all + bool IsActiveWeaponReloading( void ) const; ///< is the weapon in the middle of a reload + bool IsActiveWeaponRecoilHigh( void ) const; ///< return true if active weapon's bullet spray has become large and inaccurate + CBasePlayerWeapon *GetActiveWeapon( void ) const; ///< return the weapon the bot is currently using + bool IsUsingScope( void ) const; ///< return true if looking thru weapon's scope + + + //------------------------------------------------------------------------------------ + // Event hooks + // + + /// invoked when injured by something (EXTEND) + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) + { + return CBasePlayer::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); + } + + /// invoked when killed (EXTEND) + virtual void Killed( entvars_t *pevAttacker, int iGib ) + { + CBasePlayer::Killed( pevAttacker, iGib ); + } + + virtual void OnTouchingWeapon( CWeaponBox *box ) { } ///< invoked when in contact with a CWeaponBox + + /// invoked when event occurs in the game (some events have NULL entities) + virtual void OnEvent( GameEventType event, CBaseEntity *entity = NULL, CBaseEntity *other = NULL ) { } + + //------------------------------------------------------------------------------------ + // Vision + // + + enum VisiblePartType + { + NONE = 0x00, + CHEST = 0x01, + HEAD = 0x02, + LEFT_SIDE = 0x04, ///< the left side of the object from our point of view (not their left side) + RIGHT_SIDE = 0x08, ///< the right side of the object from our point of view (not their right side) + FEET = 0x10 + }; + + #define CHECK_FOV true + virtual bool IsVisible( const Vector *pos, bool testFOV = false ) const = 0; ///< return true if we can see the point + virtual bool IsVisible( CBasePlayer *player, bool testFOV = false, unsigned char *visParts = NULL ) const = 0; ///< return true if we can see any part of the player + + virtual bool IsEnemyPartVisible( VisiblePartType part ) const = 0; ///< if enemy is visible, return the part we see + + virtual bool IsPlayerFacingMe( CBasePlayer *enemy ) const; ///< return true if player is facing towards us + virtual bool IsPlayerLookingAtMe( CBasePlayer *enemy ) const; ///< returns true if other player is pointing right at us + + + //------------------------------------------------------------------------------------ + // Information query + // + + bool IsEnemy( CBaseEntity *ent ) const; ///< returns TRUE if given entity is our enemy + int GetEnemiesRemaining( void ) const; ///< return number of enemies left alive + int GetFriendsRemaining( void ) const; ///< return number of friends left alive + + bool IsLocalPlayerWatchingMe( void ) const; ///< return true if local player is observing this bot + + void Print( char *format, ... ) const; ///< output message to console + void PrintIfWatched( char *format, ... ) const; ///< output message to console if we are being watched by the local player + + virtual void ExecuteCommand( void ); + virtual void SetModel( const char *modelName ); + virtual Vector GetAutoaimVector( float delta ); + + void Spawn( void ); + void BotThink( void ); + bool IsNetClient( void ) const { return FALSE; } + int Save( CSave &save ) const { return 0; } + int Restore( CRestore &restore ) const { return 0; } + virtual void Think( void ) { } + + const BotProfile *GetProfile( void ) const { return m_profile; } ///< return our personality profile + +protected: + // Do a "client command" - useful for invoking menu choices, etc. + void ClientCommand( const char *cmd, const char *arg1 = NULL, const char *arg2 = NULL, const char *arg3 = NULL ); + + const BotProfile *m_profile; ///< the "personality" profile of this bot + +private: + unsigned int m_id; ///< unique bot ID + + // Think mechanism variables + float m_flNextBotThink; + float m_flNextFullBotThink; + + // Command interface variables + float m_flPreviousCommandTime; + + bool m_isRunning; ///< run/walk mode + bool m_isCrouching; ///< true if crouching (ducking) + float m_forwardSpeed; + float m_strafeSpeed; + float m_verticalSpeed; + unsigned short m_buttonFlags; ///< bitfield of movement buttons + + float m_jumpTimestamp; ///< time when we last began a jump + + /// the PostureContext represents the current settings of walking and crouching + struct PostureContext + { + bool isRunning; + bool isCrouching; + }; + enum { MAX_POSTURE_STACK = 8 }; + PostureContext m_postureStack[ MAX_POSTURE_STACK ]; + int m_postureStackIndex; ///< index of top of stack + + void ResetCommand( void ); + byte ThrottledMsec( void ) const; + + float GetMoveSpeed( void ); ///< returns current movement speed (for walk/run) +}; + + +//----------------------------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------------------------- +// +// Inlines +// + +//-------------------------------------------------------------------------------------------------------------- +inline void CBot::SetModel( const char *modelName ) +{ + SET_CLIENT_KEY_VALUE( entindex(), GET_USERINFO(edict()), "model", (char *)modelName ); +} + +//----------------------------------------------------------------------------------------------------------- +inline float CBot::GetMoveSpeed( void ) +{ + if (m_isRunning || m_isCrouching) + return pev->maxspeed; + + // should be 0.52, but when bots strafe, they break the run/walk threshold + return 0.4f * pev->maxspeed; +} + +//----------------------------------------------------------------------------------------------------------- +inline void CBot::Run( void ) +{ + m_isRunning = true; +} + +//----------------------------------------------------------------------------------------------------------- +inline void CBot::Walk( void ) +{ + m_isRunning = false; +} + +//----------------------------------------------------------------------------------------------------------- +inline CBasePlayerWeapon *CBot::GetActiveWeapon( void ) const +{ + return static_cast( m_pActiveItem ); +} + +//----------------------------------------------------------------------------------------------------------- +inline bool CBot::IsActiveWeaponReloading( void ) const +{ + CBasePlayerWeapon *gun = GetActiveWeapon(); + if (gun == NULL) + return false; + + return (gun->m_fInReload || gun->m_fInSpecialReload) ? true : false; +} + +//----------------------------------------------------------------------------------------------------------- +inline bool CBot::IsActiveWeaponRecoilHigh( void ) const +{ + CBasePlayerWeapon *gun = GetActiveWeapon(); + if (gun == NULL) + return false; + + const float highRecoil = 0.4f; + return (gun->m_flAccuracy > highRecoil) ? true : false; +} + +//----------------------------------------------------------------------------------------------------------- +inline void CBot::PushPostureContext( void ) +{ + if (m_postureStackIndex == MAX_POSTURE_STACK) + { + if (pev) + PrintIfWatched( "PushPostureContext() overflow error!\n" ); + return; + } + + m_postureStack[ m_postureStackIndex ].isRunning = m_isRunning; + m_postureStack[ m_postureStackIndex ].isCrouching = m_isCrouching; + ++m_postureStackIndex; +} + +//----------------------------------------------------------------------------------------------------------- +inline void CBot::PopPostureContext( void ) +{ + if (m_postureStackIndex == 0) + { + if (pev) + PrintIfWatched( "PopPostureContext() underflow error!\n" ); + m_isRunning = true; + m_isCrouching = false; + return; + } + + --m_postureStackIndex; + m_isRunning = m_postureStack[ m_postureStackIndex ].isRunning; + m_isCrouching = m_postureStack[ m_postureStackIndex ].isCrouching; +} + +//----------------------------------------------------------------------------------------------------------- +inline bool CBot::IsPlayerFacingMe( CBasePlayer *other ) const +{ + Vector toOther = other->pev->origin - pev->origin; + + // compute the unit vector along our other player's + UTIL_MakeVectors( other->pev->v_angle + other->pev->punchangle ); + Vector otherDir = gpGlobals->v_forward; + + if (otherDir.x * toOther.x + otherDir.y * toOther.y < 0.0f) + return true; + + return false; +} + +//----------------------------------------------------------------------------------------------------------- +inline bool CBot::IsPlayerLookingAtMe( CBasePlayer *other ) const +{ + Vector toOther = other->pev->origin - pev->origin; + toOther.NormalizeInPlace(); + + // compute the unit vector along our other player's + UTIL_MakeVectors( other->pev->v_angle + other->pev->punchangle ); + Vector otherDir = gpGlobals->v_forward; + + // other player must be pointing nearly right at us to be "looking at" us + const float lookAtCos = 0.9f; + if (otherDir.x * toOther.x + otherDir.y * toOther.y < -lookAtCos) + { + // other player must have clear line of sight to us to be "looking at" us + Vector vec(other->EyePosition()); + if (IsVisible( &vec )) + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------------------------- + +extern void InstallBotControl( void ); +extern void Bot_ServerCommand( void ); +extern void Bot_RegisterCvars( void ); + + +#endif // BOT_H diff --git a/game_shared/bot/bot_constants.h b/game_shared/bot/bot_constants.h new file mode 100644 index 0000000..db3bd7e --- /dev/null +++ b/game_shared/bot/bot_constants.h @@ -0,0 +1,46 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// Author: Matthew D. Campbell (matt@turtlerockstudios.com), 2003 + +#ifndef BOT_CONSTANTS_H +#define BOT_CONSTANTS_H + +// +// We'll define our own version of this, because everyone else does. :P +// This needs to stay in sync with MAX_CLIENTS, but there's no header with the #define. Nice. +// +#define BOT_MAX_CLIENTS 32 + +/// version number is MAJOR.MINOR +#define BOT_VERSION_MAJOR 1 +#define BOT_VERSION_MINOR 50 + +//-------------------------------------------------------------------------------------------------------- +/** + * Difficulty levels + */ +enum BotDifficultyType +{ + BOT_EASY = 0, + BOT_NORMAL = 1, + BOT_HARD = 2, + BOT_EXPERT = 3, + + NUM_DIFFICULTY_LEVELS +}; + +#ifdef DEFINE_DIFFICULTY_NAMES + char *BotDifficultyName[] = + { + "EASY", "NORMAL", "HARD", "EXPERT", NULL + }; +#else + extern char *BotDifficultyName[]; +#endif + +#endif // BOT_CONSTANTS_H diff --git a/game_shared/bot/bot_manager.cpp b/game_shared/bot/bot_manager.cpp new file mode 100644 index 0000000..fa8221b --- /dev/null +++ b/game_shared/bot/bot_manager.cpp @@ -0,0 +1,474 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003 + +#pragma warning( disable : 4530 ) // STL uses exceptions, but we are not compiling with them - ignore warning + +#define DEFINE_EVENT_NAMES + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "soundent.h" +#include "gamerules.h" +#include "player.h" +#include "client.h" +#include "perf_counter.h" + +#include "bot.h" +#include "bot_manager.h" +#include "nav_area.h" +#include "bot_util.h" +#include "hostage.h" + +#include "tutor.h" + +const float smokeRadius = 115.0f; ///< for smoke grenades + + +//#define CHECK_PERFORMANCE +#ifdef CHECK_PERFORMANCE + // crude performance timing + static CPerformanceCounter perfCounter; + + struct PerfInfo + { + float frameTime; + float botThinkTime; + }; + + #define MAX_PERF_DATA 50000 + static PerfInfo perfData[ MAX_PERF_DATA ]; + static int perfDataCount = 0; + static int perfFileIndex = 0; +#endif + + + +/** + * Convert name to GameEventType + * @todo Find more appropriate place for this function + */ +GameEventType NameToGameEvent( const char *name ) +{ + for( int i=0; GameEventName[i]; ++i ) + if (!stricmp( GameEventName[i], name )) + return static_cast( i ); + + return EVENT_INVALID; +} + + +//-------------------------------------------------------------------------------------------------------------- +CBotManager::CBotManager() +{ + InitBotTrig(); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Invoked when the round is restarting + */ +void CBotManager::RestartRound( void ) +{ +#ifdef CHECK_PERFORMANCE + // dump previous round's performance + char filename[80]; + sprintf( filename, "perfdata%02X.txt", perfFileIndex++ ); + FILE *fp = fopen( filename, "w" ); + + if (fp) + { + for( int p=0; pIsValid()) + { + delete ag; + iter = m_activeGrenadeList.erase( iter ); + continue; + } + else + { + ++iter; + } + + const Vector *pos = ag->GetDetonationPosition(); + + UTIL_DrawBeamPoints( *pos, *pos + Vector( 0, 0, 50 ), 1, 255, 100, 0 ); + + lastEdge = Vector( smokeRadius + pos->x, pos->y, pos->z ); + float angle; + for( angle=0.0f; angle <= 180.0f; angle += 22.5f ) + { + edge.x = smokeRadius * BotCOS( angle ) + pos->x; + edge.y = pos->y; + edge.z = smokeRadius * BotSIN( angle ) + pos->z; + + UTIL_DrawBeamPoints( edge, lastEdge, 1, 255, 50, 0 ); + + lastEdge = edge; + } + + lastEdge = Vector( pos->x, smokeRadius + pos->y, pos->z ); + for( angle=0.0f; angle <= 180.0f; angle += 22.5f ) + { + edge.x = pos->x; + edge.y = smokeRadius * BotCOS( angle ) + pos->y; + edge.z = smokeRadius * BotSIN( angle ) + pos->z; + + UTIL_DrawBeamPoints( edge, lastEdge, 1, 255, 50, 0 ); + + lastEdge = edge; + } + } + } + + + // + // Process each active bot + // + +#ifdef CHECK_PERFORMANCE + static double lastTime = 0.0f; + double startTime = perfCounter.GetCurTime(); +#endif + + for( int i = 1; i <= gpGlobals->maxClients; ++i ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if (!pPlayer) + continue; + + if (pPlayer->IsBot() && IsEntityValid( pPlayer )) + { + CBot *pBot = static_cast( pPlayer ); + + pBot->BotThink(); + } + } + +#ifdef CHECK_PERFORMANCE + if (perfDataCount < MAX_PERF_DATA) + { + if (lastTime > 0.0f) + { + double endTime = perfCounter.GetCurTime(); + + perfData[ perfDataCount ].frameTime = (float)(startTime - lastTime); + perfData[ perfDataCount ].botThinkTime = (float)(endTime - startTime); + ++perfDataCount; + } + + lastTime = startTime; + } +#endif +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return the filename for this map's "nav map" file + */ +const char *CBotManager::GetNavMapFilename( void ) const +{ + static char filename[256]; + sprintf( filename, "maps\\%s.nav", STRING( gpGlobals->mapname ) ); + return filename; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Invoked when given player does given event (some events have NULL player). + * Events are propogated to all bots. + * + * @todo This has become the game-wide event dispatcher. We should restructure this. + */ +void CBotManager::OnEvent( GameEventType event, CBaseEntity *entity, CBaseEntity *other ) +{ + // propogate event to all bots + for ( int i=1; i <= gpGlobals->maxClients; ++i ) + { + CBasePlayer *player = static_cast( UTIL_PlayerByIndex( i ) ); + + if (player == NULL) + continue; + + if (FNullEnt( player->pev )) + continue; + + if (FStrEq( STRING( player->pev->netname ), "" )) + continue; + + if (!player->IsBot()) + continue; + + // do not send self-generated event + if (entity == player) + continue; + + CBot *bot = static_cast( player ); + bot->OnEvent( event, entity, other ); + } + + if (TheTutor) + TheTutor->OnEvent( event, entity, other ); + + if (g_pHostages) + g_pHostages->OnEvent( event, entity, other ); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Add an active grenade to the bot's awareness + */ +void CBotManager::AddGrenade( int type, CGrenade *grenade ) +{ + ActiveGrenade *ag = new ActiveGrenade( type, grenade ); + m_activeGrenadeList.push_back( ag ); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * The grenade entity in the world is going away + */ +void CBotManager::RemoveGrenade( CGrenade *grenade ) +{ + for( ActiveGrenadeList::iterator iter = m_activeGrenadeList.begin(); iter != m_activeGrenadeList.end(); ++iter ) + { + ActiveGrenade *ag = *iter; + + if (ag->IsEntity( grenade )) + { + ag->OnEntityGone(); + return; + } + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Destroy any invalid active grenades + */ +void CBotManager::ValidateActiveGrenades( void ) +{ + ActiveGrenadeList::iterator iter = m_activeGrenadeList.begin(); + while( iter != m_activeGrenadeList.end() ) + { + ActiveGrenade *ag = *iter; + + if (!ag->IsValid()) + { + delete ag; + iter = m_activeGrenadeList.erase( iter ); + } + else + { + ++iter; + } + } +} + +//-------------------------------------------------------------------------------------------------------------- +void CBotManager::DestroyAllGrenades( void ) +{ + for( ActiveGrenadeList::iterator iter = m_activeGrenadeList.begin(); iter != m_activeGrenadeList.end(); ++iter ) + delete *iter; + + m_activeGrenadeList.clear(); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if position is inside a smoke cloud + */ +bool CBotManager::IsInsideSmokeCloud( const Vector *pos ) +{ + ActiveGrenadeList::iterator iter = m_activeGrenadeList.begin(); + while( iter != m_activeGrenadeList.end() ) + { + ActiveGrenade *ag = *iter; + + // lazy validation + if (!ag->IsValid()) + { + delete ag; + iter = m_activeGrenadeList.erase( iter ); + continue; + } + else + { + ++iter; + } + + if (ag->GetID() == WEAPON_SMOKEGRENADE) + { + const Vector *smokeOrigin = ag->GetDetonationPosition(); + + if ((*smokeOrigin - *pos).IsLengthLessThan( smokeRadius )) + return true; + } + } + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if line intersects smoke volume + * Determine the length of the line of sight covered by each smoke cloud, + * and sum them (overlap is additive for obstruction). + * If the overlap exceeds the threshold, the bot can't see through. + */ +bool CBotManager::IsLineBlockedBySmoke( const Vector *from, const Vector *to ) +{ + const float smokeRadiusSq = smokeRadius * smokeRadius; + float totalSmokedLength = 0.0f; // distance along line of sight covered by smoke + + // compute unit vector and length of line of sight segment + Vector sightDir = *to - *from; + float sightLength = sightDir.NormalizeInPlace(); + + + ActiveGrenadeList::iterator iter = m_activeGrenadeList.begin(); + while( iter != m_activeGrenadeList.end() ) + { + ActiveGrenade *ag = *iter; + + // lazy validation + if (!ag->IsValid()) + { + delete ag; + iter = m_activeGrenadeList.erase( iter ); + continue; + } + else + { + ++iter; + } + + if (ag->GetID() == WEAPON_SMOKEGRENADE) + { + const Vector *smokeOrigin = ag->GetDetonationPosition(); + + Vector toGrenade = *smokeOrigin - *from; + + float alongDist = DotProduct( toGrenade, sightDir ); + + // compute closest point to grenade along line of sight ray + Vector close; + + // constrain closest point to line segment + if (alongDist < 0.0f) + close = *from; + else if (alongDist >= sightLength) + close = *to; + else + close = *from + sightDir * alongDist; + + // if closest point is within smoke radius, the line overlaps the smoke cloud + Vector toClose = close - *smokeOrigin; + float lengthSq = toClose.LengthSquared(); + + if (lengthSq < smokeRadiusSq) + { + // some portion of the ray intersects the cloud + + float fromSq = toGrenade.LengthSquared(); + float toSq = (*smokeOrigin - *to).LengthSquared(); + + if (fromSq < smokeRadiusSq) + { + if (toSq < smokeRadiusSq) + { + // both 'from' and 'to' lie within the cloud + // entire length is smoked + totalSmokedLength += (*to - *from).Length(); + } + else + { + // 'from' is inside the cloud, 'to' is outside + // compute half of total smoked length as if ray crosses entire cloud chord + float halfSmokedLength = sqrt( smokeRadiusSq - lengthSq ); + + if (alongDist > 0.0f) + { + // ray goes thru 'close' + totalSmokedLength += halfSmokedLength + (close - *from).Length(); + } + else + { + // ray starts after 'close' + totalSmokedLength += halfSmokedLength - (close - *from).Length(); + } + + } + } + else if (toSq < smokeRadiusSq) + { + // 'from' is outside the cloud, 'to' is inside + // compute half of total smoked length as if ray crosses entire cloud chord + float halfSmokedLength = sqrt( smokeRadiusSq - lengthSq ); + + Vector v = *to - *smokeOrigin; + if (DotProduct( v, sightDir ) > 0.0f) + { + // ray goes thru 'close' + totalSmokedLength += halfSmokedLength + (close - *to).Length(); + } + else + { + // ray ends before 'close' + totalSmokedLength += halfSmokedLength - (close - *to).Length(); + } + } + else + { + // 'from' and 'to' lie outside of the cloud - the line of sight completely crosses it + // determine the length of the chord that crosses the cloud + float smokedLength = 2.0f * sqrt( smokeRadiusSq - lengthSq ); + + totalSmokedLength += smokedLength; + } + } + } + } + + // define how much smoke a bot can see thru + const float maxSmokedLength = 0.7f * smokeRadius; + + // return true if the total length of smoke-covered line-of-sight is too much + return (totalSmokedLength > maxSmokedLength); +} diff --git a/game_shared/bot/bot_manager.h b/game_shared/bot/bot_manager.h new file mode 100644 index 0000000..26fa1ea --- /dev/null +++ b/game_shared/bot/bot_manager.h @@ -0,0 +1,107 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003 + +#ifndef BASE_CONTROL_H +#define BASE_CONTROL_H + +#pragma warning( disable : 4530 ) // STL uses exceptions, but we are not compiling with them - ignore warning + +#include "extdll.h" +#include "util.h" +#include +#include "GameEvent.h" // Game event enum used by career mode, tutor system, and bots + +#ifndef _WIN32 +// DAL undefs max and min +#ifndef max +#define max(a,b) ((a) > (b) ? (a) : (b)) +#endif +#ifndef min +#define min(a,b) ((a) < (b) ? (a) : (b)) +#endif +#endif + + +class CNavArea; + + +//-------------------------------------------------------------------------------------------------------------- +class CGrenade; + +/** + * An ActiveGrenade is a representation of a grenade in the world + * NOTE: Currently only used for smoke grenade line-of-sight testing + * @todo Use system allow bots to avoid HE and Flashbangs + */ +class ActiveGrenade +{ +public: + ActiveGrenade( int weaponID, CGrenade *grenadeEntity ); + + void OnEntityGone( void ); ///< called when the grenade in the world goes away + bool IsValid( void ) const ; ///< return true if this grenade is valid + bool IsEntity( CGrenade *grenade ) const { return (grenade == m_entity) ? true : false; } + int GetID( void ) const { return m_id; } + const Vector *GetDetonationPosition( void ) const { return &m_detonationPosition; } + const Vector *GetPosition( void ) const; + +private: + int m_id; ///< weapon id + CGrenade *m_entity; ///< the entity + Vector m_detonationPosition; ///< the location where the grenade detonated (smoke) + float m_dieTimestamp; ///< time this should go away after m_entity is NULL +}; + +typedef std::list ActiveGrenadeList; + + +//-------------------------------------------------------------------------------------------------------------- +/** + * This class manages all active bots, propagating events to them and updating them. + */ +class CBotManager +{ +public: + CBotManager(); + + virtual void ClientDisconnect( CBasePlayer * pPlayer ) = 0; + virtual BOOL ClientCommand( CBasePlayer * pPlayer, const char * pcmd ) = 0; + + virtual void ServerActivate( void ) = 0; + virtual void ServerDeactivate( void ) = 0; + virtual void ServerCommand( const char * pcmd ) = 0; + virtual void AddServerCommand( const char *cmd ) = 0; + virtual void AddServerCommands( void ) = 0; + + virtual void RestartRound( void ); ///< (EXTEND) invoked when a new round begins + virtual void StartFrame( void ); ///< (EXTEND) called each frame + + const char *GetNavMapFilename( void ) const; ///< return the filename for this map's "nav" file + + /** + * Invoked when event occurs in the game (some events have NULL entity). + * Events are propogated to all bots. + */ + virtual void OnEvent( GameEventType event, CBaseEntity *entity = NULL, CBaseEntity *other = NULL ); + + virtual unsigned int GetPlayerPriority( CBasePlayer *player ) const = 0; ///< return priority of player (0 = max pri) + + + void AddGrenade( int type, CGrenade *grenade ); ///< add an active grenade to the bot's awareness + void RemoveGrenade( CGrenade *grenade ); ///< the grenade entity in the world is going away + void ValidateActiveGrenades( void ); ///< destroy any invalid active grenades + void DestroyAllGrenades( void ); + bool IsLineBlockedBySmoke( const Vector *from, const Vector *to ); ///< return true if line intersects smoke volume + bool IsInsideSmokeCloud( const Vector *pos ); ///< return true if position is inside a smoke cloud + +private: + ActiveGrenadeList m_activeGrenadeList;///< the list of active grenades the bots are aware of +}; + +#endif diff --git a/game_shared/bot/bot_profile.cpp b/game_shared/bot/bot_profile.cpp new file mode 100644 index 0000000..84e59aa --- /dev/null +++ b/game_shared/bot/bot_profile.cpp @@ -0,0 +1,703 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003 + +#pragma warning( disable : 4530 ) // STL uses exceptions, but we are not compiling with them - ignore warning + +#define DEFINE_DIFFICULTY_NAMES +#include "bot_profile.h" +#include "shared_util.h" +#include "simple_checksum.h" + +BotProfileManager *TheBotProfiles = NULL; + +//-------------------------------------------------------------------------------------------------------------- +// A little explanation is in order here. This file is in game_shared. It is supposed to be able to be compiled +// into multiple dlls. However, all the basic utility functions (loading files, parsing strings, etc) are +// accessed different ways in different dlls. To combat this, we need to redirect some calls depending on +// the dll we're in. +#ifdef GAMEUI_EXPORTS + #include "../GameUI/EngineInterface.h" + #include "../cstrike/dlls/weapontype.h" + #include "../cstrike/dlls/weapontype.cpp" /// @TODO: remove this CStrike peculiarity from a game_shared file!!! + void Career_Printf(const char *fmt, ...); + #define CONSOLE_ECHO Career_Printf + #define LOAD_FILE_FOR_ME(name, len) (engine->COM_LoadFile( (name), 5, (len) )) + #define FREE_FILE (engine->COM_FreeFile) + #define UTIL_IsGame( x ) 0 +#else + #include "extdll.h" + #include "util.h" + #include "cbase.h" + #include "weapons.h" + #include "soundent.h" + #include "gamerules.h" + #include "player.h" + #include "client.h" + #include "cmd.h" + #include "pm_shared.h" + #include "bot.h" + #include "bot_util.h" +#endif + +//-------------------------------------------------------------------------------------------------------- +/** + * Generates a filename-decorated skin name + */ +static const char * GetDecoratedSkinName( const char *name, const char *filename ) +{ +#ifdef _WIN32 + const int BufLen = _MAX_PATH + 64; +#else + const int BufLen = MAX_PATH + 64; +#endif + static char buf[BufLen]; + snprintf( buf, BufLen, "%s/%s", filename, name ); + return buf; +} + +//-------------------------------------------------------------------------------------------------------------- +const char* BotProfile::GetWeaponPreferenceAsString( int i ) const +{ + if ( i < 0 || i >= m_weaponPreferenceCount ) + return NULL; + + return WeaponIDToAlias( m_weaponPreference[ i ] ); +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if this profile has a primary weapon preference + */ +bool BotProfile::HasPrimaryPreference( void ) const +{ + for( int i=0; i( filename ), &dataLength ); + const char *dataFile = dataPointer; + + if (dataFile == NULL) + { + if ( UTIL_IsGame( "czero" ) ) + { + CONSOLE_ECHO( "WARNING: Cannot access bot profile database '%s'\n", filename ); + } + return; + } + + // compute simple checksum + if (checksum) + { + *checksum = ComputeSimpleChecksum( (const unsigned char *)dataPointer, dataLength ); + } + + // keep list of templates used for inheritance + BotProfileList templateList; + + BotProfile defaultProfile; + + // + // Parse the BotProfile.db into BotProfile instances + // + while( true ) + { + dataFile = SharedParse( dataFile ); + if (!dataFile) + break; + + char *token = SharedGetToken(); + + bool isDefault = (!stricmp( token, "Default" )); + bool isTemplate = (!stricmp( token, "Template" )); + bool isCustomSkin = (!stricmp( token, "Skin" )); + + if ( isCustomSkin ) + { + const int BufLen = 64; + char skinName[BufLen]; + + // get skin name + dataFile = SharedParse( dataFile ); + if (!dataFile) + { + CONSOLE_ECHO( "Error parsing %s - expected skin name\n", filename ); + FREE_FILE( dataPointer ); + return; + } + token = SharedGetToken(); + snprintf( skinName, BufLen, "%s", token ); + + // get attribute name + dataFile = SharedParse( dataFile ); + if (!dataFile) + { + CONSOLE_ECHO( "Error parsing %s - expected 'Model'\n", filename ); + FREE_FILE( dataPointer ); + return; + } + token = SharedGetToken(); + if (stricmp( "Model", token )) + { + CONSOLE_ECHO( "Error parsing %s - expected 'Model'\n", filename ); + FREE_FILE( dataPointer ); + return; + } + + // eat '=' + dataFile = SharedParse( dataFile ); + if (!dataFile) + { + CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename ); + FREE_FILE( dataPointer ); + return; + } + token = SharedGetToken(); + if (strcmp( "=", token )) + { + CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename ); + FREE_FILE( dataPointer ); + return; + } + + // get attribute value + dataFile = SharedParse( dataFile ); + if (!dataFile) + { + CONSOLE_ECHO( "Error parsing %s - expected attribute value\n", filename ); + FREE_FILE( dataPointer ); + return; + } + token = SharedGetToken(); + + const char *decoratedName = GetDecoratedSkinName( skinName, filename ); + bool skinExists = GetCustomSkinIndex( decoratedName ) > 0; + if ( m_nextSkin < NumCustomSkins && !skinExists ) + { + // decorate the name + m_skins[ m_nextSkin ] = CloneString( decoratedName ); + + // construct the model filename + m_skinModelnames[ m_nextSkin ] = CloneString( token ); + m_skinFilenames[ m_nextSkin ] = new char[ strlen(token)*2 + strlen("models/player//.mdl") + 1 ]; + sprintf( m_skinFilenames[ m_nextSkin ], "models/player/%s/%s.mdl", token, token ); + ++m_nextSkin; + } + + // eat 'End' + dataFile = SharedParse( dataFile ); + if (!dataFile) + { + CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename ); + FREE_FILE( dataPointer ); + return; + } + token = SharedGetToken(); + if (strcmp( "End", token )) + { + CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename ); + FREE_FILE( dataPointer ); + return; + } + + continue; // it's just a custom skin - no need to do inheritance on a bot profile, etc. + } + + // encountered a new profile + BotProfile *profile; + + if (isDefault) + { + profile = &defaultProfile; + } + else + { + profile = new BotProfile; + + // always inherit from Default + *profile = defaultProfile; + } + + // do inheritance in order of appearance + if (!isTemplate && !isDefault) + { + const BotProfile *inherit = NULL; + + // template names are separated by "+" + while(true) + { + char *c = strchr( token, '+' ); + if (c) + *c = '\000'; + + // find the given template name + for( BotProfileList::iterator iter = templateList.begin(); iter != templateList.end(); ++iter ) + { + if (!stricmp( (*iter)->GetName(), token )) + { + inherit = *iter; + break; + } + } + + if (inherit == NULL) + { + CONSOLE_ECHO( "Error parsing '%s' - invalid template reference '%s'\n", filename, token ); + FREE_FILE( dataPointer ); + return; + } + + // inherit the data + profile->Inherit( inherit, &defaultProfile ); + + if (c == NULL) + break; + + token = c+1; + } + } + + + // get name of this profile + if (!isDefault) + { + dataFile = SharedParse( dataFile ); + if (!dataFile) + { + CONSOLE_ECHO( "Error parsing '%s' - expected name\n", filename ); + FREE_FILE( dataPointer ); + return; + } + profile->m_name = CloneString( SharedGetToken() ); + + /** + * HACK HACK + * Until we have a generalized means of storing bot preferences, we're going to hardcode the bot's + * preference towards silencers based on his name. + */ + if ( profile->m_name[0] % 2 ) + { + profile->m_prefersSilencer = true; + } + } + + // read attributes for this profile + bool isFirstWeaponPref = true; + while( true ) + { + // get next token + dataFile = SharedParse( dataFile ); + if (!dataFile) + { + CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename ); + FREE_FILE( dataPointer ); + return; + } + token = SharedGetToken(); + + // check for End delimiter + if (!stricmp( token, "End" )) + break; + + // found attribute name - keep it + char attributeName[64]; + strcpy( attributeName, token ); + + // eat '=' + dataFile = SharedParse( dataFile ); + if (!dataFile) + { + CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename ); + FREE_FILE( dataPointer ); + return; + } + + token = SharedGetToken(); + if (strcmp( "=", token )) + { + CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename ); + FREE_FILE( dataPointer ); + return; + } + + // get attribute value + dataFile = SharedParse( dataFile ); + if (!dataFile) + { + CONSOLE_ECHO( "Error parsing %s - expected attribute value\n", filename ); + FREE_FILE( dataPointer ); + return; + } + token = SharedGetToken(); + + // store value in appropriate attribute + if (!stricmp( "Aggression", attributeName )) + { + profile->m_aggression = atof(token) / 100.0f; + } + else if (!stricmp( "Skill", attributeName )) + { + profile->m_skill = atof(token) / 100.0f; + } + else if (!stricmp( "Skin", attributeName )) + { + profile->m_skin = atoi(token); + if ( profile->m_skin == 0 ) + { + // atoi() failed - try to look up a custom skin by name + profile->m_skin = GetCustomSkinIndex( token, filename ); + } + } + else if (!stricmp( "Teamwork", attributeName )) + { + profile->m_teamwork = atof(token) / 100.0f; + } + else if (!stricmp( "Cost", attributeName )) + { + profile->m_cost = atoi(token); + } + else if (!stricmp( "VoicePitch", attributeName )) + { + profile->m_voicePitch = atoi(token); + } + else if (!stricmp( "VoiceBank", attributeName )) + { + profile->m_voiceBank = FindVoiceBankIndex( token ); + } + else if (!stricmp( "WeaponPreference", attributeName )) + { + // weapon preferences override parent prefs + if (isFirstWeaponPref) + { + isFirstWeaponPref = false; + profile->m_weaponPreferenceCount = 0; + } + + if (!stricmp( token, "none" )) + { + profile->m_weaponPreferenceCount = 0; + } + else + { + if (profile->m_weaponPreferenceCount < BotProfile::MAX_WEAPON_PREFS) + { + profile->m_weaponPreference[ profile->m_weaponPreferenceCount++ ] = AliasToWeaponID( token ); + } + } + } + else if (!stricmp( "ReactionTime", attributeName )) + { + profile->m_reactionTime = atof(token); + +#ifndef GAMEUI_EXPORTS + // subtract off latency due to "think" update rate. + // In GameUI, we don't really care. + profile->m_reactionTime -= g_flBotFullThinkInterval; +#endif + + } + else if (!stricmp( "AttackDelay", attributeName )) + { + profile->m_attackDelay = atof(token); + } + else if (!stricmp( "Difficulty", attributeName )) + { + // override inheritance + profile->m_difficultyFlags = 0; + + // parse bit flags + while(true) + { + char *c = strchr( token, '+' ); + if (c) + *c = '\000'; + + for( int i=0; im_difficultyFlags |= (1 << i); + + if (c == NULL) + break; + + token = c+1; + } + } + else if (!stricmp( "Team", attributeName )) + { + if ( !stricmp( token, "T" ) ) + { + profile->m_teams = BOT_TEAM_T; + } + else if ( !stricmp( token, "CT" ) ) + { + profile->m_teams = BOT_TEAM_CT; + } + else + { + profile->m_teams = BOT_TEAM_ANY; + } + } + else + { + CONSOLE_ECHO( "Error parsing %s - unknown attribute '%s'\n", filename, attributeName ); + } + } + + if (!isDefault) + { + if (isTemplate) + { + // add to template list + templateList.push_back( profile ); + } + else + { + // add profile to the master list + m_profileList.push_back( profile ); + } + } + } + + FREE_FILE( dataPointer ); + + // free the templates + for( BotProfileList::iterator iter = templateList.begin(); iter != templateList.end(); ++iter ) + delete *iter; +} + +//-------------------------------------------------------------------------------------------------------------- +BotProfileManager::~BotProfileManager( void ) +{ + Reset(); + + VoiceBankList::iterator it; + for ( it = m_voiceBanks.begin(); it != m_voiceBanks.end(); ++it ) + { + delete[] *it; + } + m_voiceBanks.clear(); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Free all bot profiles + */ +void BotProfileManager::Reset( void ) +{ + for( BotProfileList::iterator iter = m_profileList.begin(); iter != m_profileList.end(); ++iter ) + delete *iter; + + m_profileList.clear(); + + for (int i=0; i LastCustomSkin ) + { + return NULL; + } + + return m_skins[ index - FirstCustomSkin ]; +} + +//-------------------------------------------------------------------------------------------------------- +/** + * Returns custom skin filename at a particular index + */ +const char * BotProfileManager::GetCustomSkinFname( int index ) +{ + if ( index < FirstCustomSkin || index > LastCustomSkin ) + { + return NULL; + } + + return m_skinFilenames[ index - FirstCustomSkin ]; +} + +//-------------------------------------------------------------------------------------------------------- +/** + * Returns custom skin modelname at a particular index + */ +const char * BotProfileManager::GetCustomSkinModelname( int index ) +{ + if ( index < FirstCustomSkin || index > LastCustomSkin ) + { + return NULL; + } + + return m_skinModelnames[ index - FirstCustomSkin ]; +} + +//-------------------------------------------------------------------------------------------------------- +/** + * Looks up a custom skin index by filename-decorated name (will decorate the name if filename is given) + */ +int BotProfileManager::GetCustomSkinIndex( const char *name, const char *filename ) +{ + const char * skinName = name; + if ( filename ) + { + skinName = GetDecoratedSkinName( name, filename ); + } + + for (int i=0; iIsDifficulty( difficulty ) && !UTIL_IsNameTaken( profile->GetName() ) && profile->IsValidForTeam( team )) + ++validCount; + } + + if (validCount == 0) + return NULL; + + // select one at random + int which = RANDOM_LONG( 0, validCount-1 ); + for( iter = m_profileList.begin(); iter != m_profileList.end(); ++iter ) + { + const BotProfile *profile = *iter; + + if (profile->IsDifficulty( difficulty ) && !UTIL_IsNameTaken( profile->GetName() ) && profile->IsValidForTeam( team )) + if (which-- == 0) + return profile; + } + + return NULL; +#endif; +} + diff --git a/game_shared/bot/bot_profile.h b/game_shared/bot/bot_profile.h new file mode 100644 index 0000000..ada47a5 --- /dev/null +++ b/game_shared/bot/bot_profile.h @@ -0,0 +1,220 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003 + +#ifndef _BOT_PROFILE_H_ +#define _BOT_PROFILE_H_ + +#pragma warning( disable : 4786 ) // long STL names get truncated in browse info. +#ifdef LINUX +#include +#include +#endif + +#undef min +#undef max +#include +#include +#include "bot_constants.h" + +enum +{ + FirstCustomSkin = 100, + NumCustomSkins = 100, + LastCustomSkin = FirstCustomSkin + NumCustomSkins - 1, +}; + +enum BotProfileTeamType +{ + BOT_TEAM_T, + BOT_TEAM_CT, + BOT_TEAM_ANY +}; + +//-------------------------------------------------------------------------------------------------------------- +/** + * A BotProfile describes the "personality" of a given bot + */ +class BotProfile +{ +public: + BotProfile( void ) + { + m_name = NULL; + m_aggression = 0.0f; + m_skill = 0.0f; + m_teamwork = 0.0f; + m_weaponPreferenceCount = 0; + m_cost = 0; + m_skin = 0; + m_difficultyFlags = 0; + m_voicePitch = 100; + m_reactionTime = 0.3f; + m_attackDelay = 0.0f; + m_teams = BOT_TEAM_ANY; + m_voiceBank = 0; + m_prefersSilencer = false; + } + + const char *GetName( void ) const { return m_name; } ///< return bot's name + float GetAggression( void ) const { return m_aggression; } + float GetSkill( void ) const { return m_skill; } + float GetTeamwork( void ) const { return m_teamwork; } + + int GetWeaponPreference( int i ) const { return m_weaponPreference[ i ]; } + const char *GetWeaponPreferenceAsString( int i ) const; + int GetWeaponPreferenceCount( void ) const { return m_weaponPreferenceCount; } + bool HasPrimaryPreference( void ) const; ///< return true if this profile has a primary weapon preference + bool HasPistolPreference( void ) const; ///< return true if this profile has a pistol weapon preference + + int GetCost( void ) const { return m_cost; } + int GetSkin( void ) const { return m_skin; } + bool IsDifficulty( BotDifficultyType diff ) const; ///< return true if this profile can be used for the given difficulty level + int GetVoicePitch( void ) const { return m_voicePitch; } + float GetReactionTime( void ) const { return m_reactionTime; } + float GetAttackDelay( void ) const { return m_attackDelay; } + int GetVoiceBank() const { return m_voiceBank; } + + bool IsValidForTeam( BotProfileTeamType team ) const; + + bool PrefersSilencer() const { return m_prefersSilencer; } +private: + friend class BotProfileManager; ///< for loading profiles + + void Inherit( const BotProfile *parent, const BotProfile *baseline ); ///< copy values from parent if they differ from baseline + + char *m_name; ///< the bot's name + float m_aggression; ///< percentage: 0 = coward, 1 = berserker + float m_skill; ///< percentage: 0 = terrible, 1 = expert + float m_teamwork; ///< percentage: 0 = rogue, 1 = complete obeyance to team, lots of comm + + enum { MAX_WEAPON_PREFS = 16 }; + int m_weaponPreference[ MAX_WEAPON_PREFS ]; ///< which weapons this bot likes to use, in order of priority + int m_weaponPreferenceCount; + + int m_cost; ///< reputation point cost for career mode + int m_skin; ///< "skin" index + unsigned char m_difficultyFlags; ///< bits set correspond to difficulty levels this is valid for + int m_voicePitch; ///< the pitch shift for bot chatter (100 = normal) + float m_reactionTime; ///< our reaction time in seconds + float m_attackDelay; ///< time in seconds from when we notice an enemy to when we open fire + BotProfileTeamType m_teams; ///< teams for which this profile is valid + + bool m_prefersSilencer; ///< does the bot prefer to use silencers? + + int m_voiceBank; ///< Index of the BotChatter.db voice bank this profile uses (0 is the default) +}; +typedef std::list BotProfileList; + + +inline bool BotProfile::IsDifficulty( BotDifficultyType diff ) const +{ + return (m_difficultyFlags & (1 << diff)) ? true : false; +} + +/** + * Copy in data from parent if it differs from the baseline + */ +inline void BotProfile::Inherit( const BotProfile *parent, const BotProfile *baseline ) +{ + if (parent->m_aggression != baseline->m_aggression) + m_aggression = parent->m_aggression; + + if (parent->m_skill != baseline->m_skill) + m_skill = parent->m_skill; + + if (parent->m_teamwork != baseline->m_teamwork) + m_teamwork = parent->m_teamwork; + + if (parent->m_weaponPreferenceCount != baseline->m_weaponPreferenceCount) + { + m_weaponPreferenceCount = parent->m_weaponPreferenceCount; + for( int i=0; im_weaponPreferenceCount; ++i ) + m_weaponPreference[i] = parent->m_weaponPreference[i]; + } + + if (parent->m_cost != baseline->m_cost) + m_cost = parent->m_cost; + + if (parent->m_skin != baseline->m_skin) + m_skin = parent->m_skin; + + if (parent->m_difficultyFlags != baseline->m_difficultyFlags) + m_difficultyFlags = parent->m_difficultyFlags; + + if (parent->m_voicePitch != baseline->m_voicePitch) + m_voicePitch = parent->m_voicePitch; + + if (parent->m_reactionTime != baseline->m_reactionTime) + m_reactionTime = parent->m_reactionTime; + + if (parent->m_attackDelay != baseline->m_attackDelay) + m_attackDelay = parent->m_attackDelay; + + if (parent->m_teams != baseline->m_teams) + m_teams = parent->m_teams; + + if (parent->m_voiceBank != baseline->m_voiceBank) + m_voiceBank = parent->m_voiceBank; +} + + + + +//-------------------------------------------------------------------------------------------------------------- +/** + * The BotProfileManager defines the interface to accessing BotProfiles + */ +class BotProfileManager +{ +public: + BotProfileManager( void ); + ~BotProfileManager( void ); + + void Init( const char *filename, unsigned int *checksum = NULL ); + void Reset( void ); + + /// given a name, return a profile + const BotProfile *GetProfile( const char *name, BotProfileTeamType team ) const + { + for( BotProfileList::const_iterator iter = m_profileList.begin(); iter != m_profileList.end(); ++iter ) + if ( !stricmp( name, (*iter)->GetName() ) && (*iter)->IsValidForTeam( team ) ) + return *iter; + + return NULL; + } + + const BotProfileList *GetProfileList( void ) const { return &m_profileList; } ///< return list of all profiles + + const BotProfile *GetRandomProfile( BotDifficultyType difficulty, BotProfileTeamType team ) const; ///< return random unused profile that matches the given difficulty level + + const char * GetCustomSkin( int index ); ///< Returns custom skin name at a particular index + const char * GetCustomSkinModelname( int index ); ///< Returns custom skin modelname at a particular index + const char * GetCustomSkinFname( int index ); ///< Returns custom skin filename at a particular index + int GetCustomSkinIndex( const char *name, const char *filename = NULL ); ///< Looks up a custom skin index by name + + typedef std::vector VoiceBankList; + const VoiceBankList* GetVoiceBanks() const { return &m_voiceBanks; } + int FindVoiceBankIndex( const char *filename ); ///< return index of the (custom) bot phrase db, inserting it if needed + +protected: + BotProfileList m_profileList; ///< the list of all bot profiles + + VoiceBankList m_voiceBanks; + + char *m_skins[ NumCustomSkins ]; ///< Custom skin names + char *m_skinModelnames[ NumCustomSkins ]; ///< Custom skin modelnames + char *m_skinFilenames[ NumCustomSkins ]; ///< Custom skin filenames + int m_nextSkin; ///< Next custom skin to allocate +}; + +/// the global singleton for accessing BotProfiles +extern BotProfileManager *TheBotProfiles; + + +#endif // _BOT_PROFILE_H_ diff --git a/game_shared/bot/bot_util.cpp b/game_shared/bot/bot_util.cpp new file mode 100644 index 0000000..3f6a3f7 --- /dev/null +++ b/game_shared/bot/bot_util.cpp @@ -0,0 +1,859 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003 + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" + +#include "bot.h" +#include "bot_util.h" +#include "bot_profile.h" +#include "nav.h" + +static short s_iBeamSprite = 0; + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if given name is already in use by another player + */ +bool UTIL_IsNameTaken( const char *name, bool ignoreHumans ) +{ + for ( int i = 1; i <= gpGlobals->maxClients; ++i ) + { + CBaseEntity * player = UTIL_PlayerByIndex( i ); + + if (player == NULL) + continue; + + if (FNullEnt( player->pev )) + continue; + + if (FStrEq( STRING( player->pev->netname ), "" )) + continue; + + if (player->IsPlayer() && (((CBasePlayer *)player)->IsBot() == TRUE)) + { + // bots can have prefixes so we need to check the name + // against the profile name instead. + CBot *bot = (CBot *)player; + if (FStrEq(name, bot->GetProfile()->GetName())) + { + return true; + } + } + else + { + if (!ignoreHumans) + { + if (FStrEq( name, STRING( player->pev->netname ) )) + return true; + } + } + } + + return false; +} + + +//-------------------------------------------------------------------------------------------------------------- +int UTIL_ClientsInGame( void ) +{ + int iCount = 0; + + for ( int iIndex = 1; iIndex <= gpGlobals->maxClients; iIndex++ ) + { + CBaseEntity * pPlayer = UTIL_PlayerByIndex( iIndex ); + + if ( pPlayer == NULL ) + continue; + + if ( FNullEnt( pPlayer->pev ) ) + continue; + + if ( FStrEq( STRING( pPlayer->pev->netname ), "" ) ) + continue; + + iCount++; + } + + return iCount; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return number of active players (not spectators) in the game + */ +int UTIL_ActivePlayersInGame( void ) +{ + int iCount = 0; + + for (int iIndex = 1; iIndex <= gpGlobals->maxClients; iIndex++ ) + { + CBaseEntity *entity = UTIL_PlayerByIndex( iIndex ); + + if ( entity == NULL ) + continue; + + if ( FNullEnt( entity->pev ) ) + continue; + + if ( FStrEq( STRING( entity->pev->netname ), "" ) ) + continue; + + CBasePlayer *player = static_cast( entity ); + + // ignore spectators + if (player->m_iTeam != TERRORIST && player->m_iTeam != CT) + continue; + + if (player->m_iJoiningState != JOINED) + continue; + + iCount++; + } + + return iCount; +} + + + +//-------------------------------------------------------------------------------------------------------------- +int UTIL_HumansInGame( bool ignoreSpectators ) +{ + int iCount = 0; + + for (int iIndex = 1; iIndex <= gpGlobals->maxClients; iIndex++ ) + { + CBaseEntity *entity = UTIL_PlayerByIndex( iIndex ); + + if ( entity == NULL ) + continue; + + if ( FNullEnt( entity->pev ) ) + continue; + + if ( FStrEq( STRING( entity->pev->netname ), "" ) ) + continue; + + CBasePlayer *player = static_cast( entity ); + + if (player->IsBot()) + continue; + + if (ignoreSpectators && player->m_iTeam != TERRORIST && player->m_iTeam != CT) + continue; + + if (ignoreSpectators && player->m_iJoiningState != JOINED) + continue; + + iCount++; + } + + /* + if ( IS_DEDICATED_SERVER() && !ignoreSpectators ) + { + // If we're counting humans, including spectators, don't count the dedicated server + --iCount; + } + */ + + return iCount; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return the number of non-bots on the given team + */ +int UTIL_HumansOnTeam( int teamID, bool isAlive ) +{ + int iCount = 0; + + for (int iIndex = 1; iIndex <= gpGlobals->maxClients; iIndex++ ) + { + CBaseEntity *entity = UTIL_PlayerByIndex( iIndex ); + + if ( entity == NULL ) + continue; + + if ( FNullEnt( entity->pev ) ) + continue; + + if ( FStrEq( STRING( entity->pev->netname ), "" ) ) + continue; + + CBasePlayer *player = static_cast( entity ); + + if (player->IsBot()) + continue; + + if (player->m_iTeam != teamID) + continue; + + if (isAlive && !player->IsAlive()) + continue; + + iCount++; + } + + return iCount; +} + + +//-------------------------------------------------------------------------------------------------------------- +int UTIL_BotsInGame( void ) +{ + int iCount = 0; + + for (int iIndex = 1; iIndex <= gpGlobals->maxClients; iIndex++ ) + { + CBasePlayer *pPlayer = static_cast(UTIL_PlayerByIndex( iIndex )); + + if ( pPlayer == NULL ) + continue; + + if ( FNullEnt( pPlayer->pev ) ) + continue; + + if ( FStrEq( STRING( pPlayer->pev->netname ), "" ) ) + continue; + + if ( !pPlayer->IsBot() ) + continue; + + iCount++; + } + + return iCount; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Kick a bot from the given team. If no bot exists on the team, return false. + */ +bool UTIL_KickBotFromTeam( TeamName kickTeam ) +{ + int i; + + // try to kick a dead bot first + for ( i = 1; i <= gpGlobals->maxClients; ++i ) + { + CBasePlayer *player = static_cast( UTIL_PlayerByIndex( i ) ); + + if (player == NULL) + continue; + + if (FNullEnt( player->pev )) + continue; + + const char *name = STRING( player->pev->netname ); + if (FStrEq( name, "" )) + continue; + + if (!player->IsBot()) + continue; + + if (!player->IsAlive() && player->m_iTeam == kickTeam) + { + // its a bot on the right team - kick it + SERVER_COMMAND( UTIL_VarArgs( "kick \"%s\"\n", STRING( player->pev->netname ) ) ); + + return true; + } + } + + // no dead bots, kick any bot on the given team + for ( i = 1; i <= gpGlobals->maxClients; ++i ) + { + CBasePlayer *player = static_cast( UTIL_PlayerByIndex( i ) ); + + if (player == NULL) + continue; + + if (FNullEnt( player->pev )) + continue; + + const char *name = STRING( player->pev->netname ); + if (FStrEq( name, "" )) + continue; + + if (!player->IsBot()) + continue; + + if (player->m_iTeam == kickTeam) + { + // its a bot on the right team - kick it + SERVER_COMMAND( UTIL_VarArgs( "kick \"%s\"\n", STRING( player->pev->netname ) ) ); + + return true; + } + } + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if all of the members of the given team are bots + */ +bool UTIL_IsTeamAllBots( int team ) +{ + int botCount = 0; + + for( int i=1; i <= gpGlobals->maxClients; ++i ) + { + CBasePlayer *player = static_cast( UTIL_PlayerByIndex( i ) ); + + if (player == NULL) + continue; + + // skip players on other teams + if (player->m_iTeam != team) + continue; + + if (FNullEnt( player->pev )) + continue; + + if (FStrEq( STRING( player->pev->netname ), "" )) + continue; + + // if not a bot, fail the test + if (!FBitSet( player->pev->flags, FL_FAKECLIENT )) + return false; + + // is a bot on given team + ++botCount; + } + + // if team is empty, there are no bots + return (botCount) ? true : false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return the closest active player to the given position. + * If 'distance' is non-NULL, the distance to the closest player is returned in it. + */ +extern CBasePlayer *UTIL_GetClosestPlayer( const Vector *pos, float *distance ) +{ + CBasePlayer *closePlayer = NULL; + float closeDistSq = 999999999999.9f; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *player = static_cast( UTIL_PlayerByIndex( i ) ); + + if (!IsEntityValid( player )) + continue; + + if (!player->IsAlive()) + continue; + + float distSq = (player->pev->origin - *pos).LengthSquared(); + if (distSq < closeDistSq) + { + closeDistSq = distSq; + closePlayer = static_cast( player ); + } + } + + if (distance) + *distance = sqrt( closeDistSq ); + + return closePlayer; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return the closest active player on the given team to the given position. + * If 'distance' is non-NULL, the distance to the closest player is returned in it. + */ +extern CBasePlayer *UTIL_GetClosestPlayer( const Vector *pos, int team, float *distance ) +{ + CBasePlayer *closePlayer = NULL; + float closeDistSq = 999999999999.9f; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *player = static_cast( UTIL_PlayerByIndex( i ) ); + + if (!IsEntityValid( player )) + continue; + + if (!player->IsAlive()) + continue; + + if (player->m_iTeam != team) + continue; + + float distSq = (player->pev->origin - *pos).LengthSquared(); + if (distSq < closeDistSq) + { + closeDistSq = distSq; + closePlayer = static_cast( player ); + } + } + + if (distance) + *distance = sqrt( closeDistSq ); + + return closePlayer; +} + +//-------------------------------------------------------------------------------------------------------------- +// returns the string to be used for the bot name prefix. +const char * UTIL_GetBotPrefix() +{ + return cv_bot_prefix.string; +} + +//-------------------------------------------------------------------------------------------------------------- +// Takes the bot pointer and constructs the net name using the current bot name prefix. +void UTIL_ConstructBotNetName(char *name, int nameLength, const BotProfile *profile) +{ + if (profile == NULL) + { + name[0] = 0; + return; + } + + // if there is no bot prefix just use the profile name. + if ((UTIL_GetBotPrefix() == NULL) || (strlen(UTIL_GetBotPrefix()) == 0)) + { + strncpy(name, profile->GetName(), nameLength); + return; + } + + _snprintf(name, nameLength, "%s %s", UTIL_GetBotPrefix(), profile->GetName()); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if anyone on the given team can see the given spot + */ +bool UTIL_IsVisibleToTeam( const Vector &spot, int team, float maxRange ) +{ + for( int i = 1; i <= gpGlobals->maxClients; ++i ) + { + CBasePlayer *player = static_cast( UTIL_PlayerByIndex( i ) ); + + if (player == NULL) + continue; + + if (FNullEnt( player->pev )) + continue; + + if (FStrEq( STRING( player->pev->netname ), "" )) + continue; + + if (!player->IsAlive()) + continue; + + if (player->m_iTeam != team) + continue; + + if (maxRange > 0.0f && (spot - player->Center()).IsLengthGreaterThan( maxRange )) + continue; + + TraceResult result; + UTIL_TraceLine( player->EyePosition(), spot, ignore_monsters, ignore_glass, ENT( player->pev ), &result ); + + if (result.flFraction == 1.0f) + return true; + } + + return false; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return the local player + */ +CBasePlayer *UTIL_GetLocalPlayer( void ) +{ + if ( IS_DEDICATED_SERVER() ) + { + return NULL; + } + return static_cast( UTIL_PlayerByIndex( 1 ) ); +} + + +//------------------------------------------------------------------------------------------------------------ +// Some types of entities have no origin set, so we use this instead. +Vector UTIL_ComputeOrigin( entvars_t * pevVars ) +{ + if ( ( pevVars->origin.x == 0.0 ) && ( pevVars->origin.y == 0.0 ) && ( pevVars->origin.z == 0.0 ) ) + return ( pevVars->absmax + pevVars->absmin ) * 0.5; + else + return pevVars->origin; +} + + +Vector UTIL_ComputeOrigin( CBaseEntity * pEntity ) +{ + return UTIL_ComputeOrigin( pEntity->pev ); +} + + +Vector UTIL_ComputeOrigin( edict_t * pentEdict ) +{ + return UTIL_ComputeOrigin( VARS( pentEdict ) ); +} + + +//------------------------------------------------------------------------------------------------------------ +void UTIL_DrawBeamFromEnt( int iIndex, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecEnd ); // vecEnd = origin??? + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( iIndex ); + WRITE_COORD( vecEnd.x ); + WRITE_COORD( vecEnd.y ); + WRITE_COORD( vecEnd.z ); + WRITE_SHORT( s_iBeamSprite ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( iLifetime ); // life + WRITE_BYTE( 10 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( bRed ); // r, g, b + WRITE_BYTE( bGreen ); // r, g, b + WRITE_BYTE( bBlue ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); +} + + +//------------------------------------------------------------------------------------------------------------ +void UTIL_DrawBeamPoints( Vector vecStart, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecStart ); + WRITE_BYTE( TE_BEAMPOINTS ); + WRITE_COORD( vecStart.x ); + WRITE_COORD( vecStart.y ); + WRITE_COORD( vecStart.z ); + WRITE_COORD( vecEnd.x ); + WRITE_COORD( vecEnd.y ); + WRITE_COORD( vecEnd.z ); + WRITE_SHORT( s_iBeamSprite ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( iLifetime ); // life + WRITE_BYTE( 10 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( bRed ); // r, g, b + WRITE_BYTE( bGreen ); // r, g, b + WRITE_BYTE( bBlue ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); +} + + +//------------------------------------------------------------------------------------------------------------ +void CONSOLE_ECHO( char * pszMsg, ... ) +{ + va_list argptr; + static char szStr[1024]; + + va_start( argptr, pszMsg ); + vsprintf( szStr, pszMsg, argptr ); + va_end( argptr ); + + (*g_engfuncs.pfnServerPrint)( szStr ); +} + + +//------------------------------------------------------------------------------------------------------------ +void CONSOLE_ECHO_LOGGED( char * pszMsg, ... ) +{ + va_list argptr; + static char szStr[1024]; + + va_start( argptr, pszMsg ); + vsprintf( szStr, pszMsg, argptr ); + va_end( argptr ); + + (*g_engfuncs.pfnServerPrint)( szStr ); + UTIL_LogPrintf( szStr ); +} + + +//------------------------------------------------------------------------------------------------------------ +void BotPrecache( void ) +{ + s_iBeamSprite = PRECACHE_MODEL( "sprites/smoke.spr" ); + PRECACHE_SOUND( "buttons/bell1.wav" ); + PRECACHE_SOUND( "buttons/blip1.wav" ); + PRECACHE_SOUND( "buttons/blip2.wav" ); + PRECACHE_SOUND( "buttons/button11.wav" ); + PRECACHE_SOUND( "buttons/latchunlocked2.wav" ); + PRECACHE_SOUND( "buttons/lightswitch2.wav" ); + PRECACHE_SOUND( "ambience/quail1.wav" ); + + /// @todo This is for the Tutor - move it somewhere sane + PRECACHE_SOUND( "events/tutor_msg.wav" ); + PRECACHE_SOUND( "events/enemy_died.wav" ); + PRECACHE_SOUND( "events/friend_died.wav" ); + + /// @todo This is for the Career mode UI - move it somewhere sane + PRECACHE_SOUND( "events/task_complete.wav" ); + +#ifdef TERRORSTRIKE + /// @todo Zombie mode experiment + PRECACHE_SOUND( "zombie/attack1.wav" ); + PRECACHE_SOUND( "zombie/attack2.wav" ); + PRECACHE_SOUND( "zombie/attack3.wav" ); + PRECACHE_SOUND( "zombie/attack4.wav" ); + PRECACHE_SOUND( "zombie/attack5.wav" ); + PRECACHE_SOUND( "zombie/bark1.wav" ); + PRECACHE_SOUND( "zombie/bark2.wav" ); + PRECACHE_SOUND( "zombie/bark3.wav" ); + PRECACHE_SOUND( "zombie/bark4.wav" ); + PRECACHE_SOUND( "zombie/bark5.wav" ); + PRECACHE_SOUND( "zombie/bark6.wav" ); + PRECACHE_SOUND( "zombie/bark7.wav" ); + PRECACHE_SOUND( "zombie/breathing1.wav" ); + PRECACHE_SOUND( "zombie/breathing2.wav" ); + PRECACHE_SOUND( "zombie/breathing3.wav" ); + PRECACHE_SOUND( "zombie/breathing4.wav" ); + PRECACHE_SOUND( "zombie/groan1.wav" ); + PRECACHE_SOUND( "zombie/groan2.wav" ); + PRECACHE_SOUND( "zombie/groan3.wav" ); + PRECACHE_SOUND( "zombie/hiss1.wav" ); + PRECACHE_SOUND( "zombie/hiss2.wav" ); + PRECACHE_SOUND( "zombie/hiss3.wav" ); + PRECACHE_SOUND( "ambience/the_horror2.wav" ); + PRECACHE_SOUND( "scientist/scream20.wav" ); + PRECACHE_SOUND( "zombie/human_hurt1.wav" ); + PRECACHE_SOUND( "zombie/human_hurt2.wav" ); + PRECACHE_SOUND( "zombie/human_hurt3.wav" ); + PRECACHE_SOUND( "zombie/human_hurt4.wav" ); + PRECACHE_SOUND( "zombie/shout_reloading1.wav" ); + PRECACHE_SOUND( "zombie/shout_reloading2.wav" ); + PRECACHE_SOUND( "zombie/shout_reloading3.wav" ); + PRECACHE_SOUND( "zombie/deep_heartbeat.wav" ); + PRECACHE_SOUND( "zombie/deep_heartbeat_fast.wav" ); + PRECACHE_SOUND( "zombie/deep_heartbeat_very_fast.wav" ); + PRECACHE_SOUND( "zombie/deep_heartbeat_stopping.wav" ); + PRECACHE_SOUND( "zombie/zombie_step1.wav" ); + PRECACHE_SOUND( "zombie/zombie_step2.wav" ); + PRECACHE_SOUND( "zombie/zombie_step3.wav" ); + PRECACHE_SOUND( "zombie/zombie_step4.wav" ); + PRECACHE_SOUND( "zombie/zombie_step5.wav" ); + PRECACHE_SOUND( "zombie/zombie_step6.wav" ); + PRECACHE_SOUND( "zombie/zombie_step7.wav" ); + PRECACHE_SOUND( "zombie/fear1.wav" ); + PRECACHE_SOUND( "zombie/fear2.wav" ); + PRECACHE_SOUND( "zombie/fear3.wav" ); + PRECACHE_SOUND( "zombie/fear4.wav" ); +#endif // TERRORSTRIKE +} + +//------------------------------------------------------------------------------------------------------------ +#define COS_TABLE_SIZE 256 +static float cosTable[ COS_TABLE_SIZE ]; + +void InitBotTrig( void ) +{ + for( int i=0; i( entity ); + if (entity == NULL || !player->IsPlayer()) + player = NULL; + + const float ShortRange = 1000.0f; + const float NormalRange = 2000.0f; + switch( event ) + { + /// @todo Check weapon type (knives are pretty quiet) + /// @todo Use actual volume, account for silencers, etc. + case EVENT_WEAPON_FIRED: + { + if (player->m_pActiveItem == NULL) + return false; + + switch( player->m_pActiveItem->m_iId ) + { + // silent "firing" + case WEAPON_HEGRENADE: + case WEAPON_SMOKEGRENADE: + case WEAPON_FLASHBANG: + case WEAPON_SHIELDGUN: + case WEAPON_C4: + return false; + + // quiet + case WEAPON_KNIFE: + case WEAPON_TMP: + *range = ShortRange; + break; + + // M4A1 - check for silencer + case WEAPON_M4A1: + { + CBasePlayerWeapon *pWeapon = static_cast(player->m_pActiveItem); + if ( pWeapon->m_iWeaponState & WPNSTATE_M4A1_SILENCER_ON ) + { + *range = ShortRange; + } + else + { + *range = NormalRange; + } + } + break; + + // USP - check for silencer + case WEAPON_USP: + { + CBasePlayerWeapon *pWeapon = static_cast(player->m_pActiveItem); + if ( pWeapon->m_iWeaponState & WPNSTATE_USP_SILENCER_ON ) + { + *range = ShortRange; + } + else + { + *range = NormalRange; + } + } + break; + + // loud + case WEAPON_AWP: + *range = 99999.0f; + break; + + // normal + default: + *range = NormalRange; + break; + } + + *priority = PRIORITY_HIGH; + *isHostile = true; + return true; + } + + case EVENT_HE_GRENADE_EXPLODED: + *range = 99999.0f; + *priority = PRIORITY_HIGH; + *isHostile = true; + return true; + + case EVENT_FLASHBANG_GRENADE_EXPLODED: + *range = 1000.0f; + *priority = PRIORITY_LOW; + *isHostile = true; + return true; + + case EVENT_SMOKE_GRENADE_EXPLODED: + *range = 1000.0f; + *priority = PRIORITY_LOW; + *isHostile = true; + return true; + + case EVENT_GRENADE_BOUNCED: + *range = 500.0f; + *priority = PRIORITY_LOW; + *isHostile = true; + return true; + + case EVENT_BREAK_GLASS: + case EVENT_BREAK_WOOD: + case EVENT_BREAK_METAL: + case EVENT_BREAK_FLESH: + case EVENT_BREAK_CONCRETE: + *range = 1100.0f; + *priority = PRIORITY_MEDIUM; + *isHostile = true; + return true; + + case EVENT_DOOR: + *range = 1100.0f; + *priority = PRIORITY_MEDIUM; + *isHostile = false; + return true; + + case EVENT_WEAPON_FIRED_ON_EMPTY: + case EVENT_PLAYER_FOOTSTEP: + case EVENT_WEAPON_RELOADED: + case EVENT_WEAPON_ZOOMED: + case EVENT_PLAYER_LANDED_FROM_HEIGHT: + *range = 1100.0f; + *priority = PRIORITY_LOW; + *isHostile = false; + return true; + + case EVENT_HOSTAGE_USED: + case EVENT_HOSTAGE_CALLED_FOR_HELP: + *range = 1200.0f; + *priority = PRIORITY_MEDIUM; + *isHostile = false; + return true; + } + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Send a "hint" message to all players, dead or alive. + */ +void HintMessageToAllPlayers( const char *message ) +{ + hudtextparms_t textParms; + + textParms.x = -1.0f; + textParms.y = -1.0f; + textParms.fadeinTime = 1.0f; + textParms.fadeoutTime = 5.0f; + textParms.holdTime = 5.0f; + textParms.fxTime = 0.0f; + textParms.r1 = 100; + textParms.g1 = 255; + textParms.b1 = 100; + textParms.r2 = 255; + textParms.g2 = 255; + textParms.b2 = 255; + textParms.effect = 0; + textParms.channel = 0; + + UTIL_HudMessageAll( textParms, message ); +} + diff --git a/game_shared/bot/bot_util.h b/game_shared/bot/bot_util.h new file mode 100644 index 0000000..782fc51 --- /dev/null +++ b/game_shared/bot/bot_util.h @@ -0,0 +1,326 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef BOT_UTIL_H +#define BOT_UTIL_H + + +#include "eiface.h" +#include "player.h" +#include "shared_util.h" +#include "GameEvent.h" + +//-------------------------------------------------------------------------------------------------------------- +enum PriorityType +{ + PRIORITY_LOW, PRIORITY_MEDIUM, PRIORITY_HIGH, PRIORITY_UNINTERRUPTABLE +}; + + +extern cvar_t cv_bot_traceview; +extern cvar_t cv_bot_stop; +extern cvar_t cv_bot_show_nav; +extern cvar_t cv_bot_show_danger; +extern cvar_t cv_bot_nav_edit; +extern cvar_t cv_bot_nav_zdraw; +extern cvar_t cv_bot_walk; +extern cvar_t cv_bot_difficulty; +extern cvar_t cv_bot_debug; +extern cvar_t cv_bot_quicksave; +extern cvar_t cv_bot_quota; +extern cvar_t cv_bot_quota_match; +extern cvar_t cv_bot_prefix; +extern cvar_t cv_bot_allow_rogues; +extern cvar_t cv_bot_allow_pistols; +extern cvar_t cv_bot_allow_shotguns; +extern cvar_t cv_bot_allow_sub_machine_guns; +extern cvar_t cv_bot_allow_rifles; +extern cvar_t cv_bot_allow_machine_guns; +extern cvar_t cv_bot_allow_grenades; +extern cvar_t cv_bot_allow_snipers; +extern cvar_t cv_bot_allow_shield; +extern cvar_t cv_bot_join_team; +extern cvar_t cv_bot_join_after_player; +extern cvar_t cv_bot_auto_vacate; +extern cvar_t cv_bot_zombie; +extern cvar_t cv_bot_defer_to_human; +extern cvar_t cv_bot_chatter; +extern cvar_t cv_bot_profile_db; + +#ifdef TERRORSTRIKE +extern cvar_t cv_zombie_near_spawn; +extern cvar_t cv_zombie_far_spawn; +extern cvar_t cv_zombie_relocate; +extern cvar_t cv_zombie_min_spawn_time; +extern cvar_t cv_zombie_max_spawn_time; +#endif + +#define RAD_TO_DEG( deg ) ((deg) * 180.0 / M_PI) +#define DEG_TO_RAD( rad ) ((rad) * M_PI / 180.0) + +#define SIGN( num ) (((num) < 0) ? -1 : 1) +#define ABS( num ) (SIGN(num) * (num)) + + +#define CREATE_FAKE_CLIENT ( *g_engfuncs.pfnCreateFakeClient ) +#define GET_USERINFO ( *g_engfuncs.pfnGetInfoKeyBuffer ) +#define SET_KEY_VALUE ( *g_engfuncs.pfnSetKeyValue ) +#define SET_CLIENT_KEY_VALUE ( *g_engfuncs.pfnSetClientKeyValue ) + +class BotProfile; + +extern void BotPrecache( void ); +extern int UTIL_ClientsInGame( void ); + +extern bool UTIL_IsNameTaken( const char *name, bool ignoreHumans = false ); ///< return true if given name is already in use by another player + +// return number of active players (not spectators) in the game +extern int UTIL_ActivePlayersInGame( void ); + +#define IGNORE_SPECTATORS true +extern int UTIL_HumansInGame( bool ignoreSpectators = false ); + +#define IS_ALIVE true +extern int UTIL_HumansOnTeam( int teamID, bool isAlive = false ); + +extern int UTIL_BotsInGame( void ); +extern bool UTIL_IsTeamAllBots( int team ); +extern Vector UTIL_ComputeOrigin( entvars_t * pevVars ); +extern Vector UTIL_ComputeOrigin( CBaseEntity * pEntity ); +extern Vector UTIL_ComputeOrigin( edict_t * pentEdict ); +extern void UTIL_DrawBeamFromEnt( int iIndex, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue ); +extern void UTIL_DrawBeamPoints( Vector vecStart, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue ); +extern CBasePlayer *UTIL_GetClosestPlayer( const Vector *pos, float *distance = NULL ); +extern CBasePlayer *UTIL_GetClosestPlayer( const Vector *pos, int team, float *distance = NULL ); +extern CBasePlayer *UTIL_GetLocalPlayer( void ); +extern bool UTIL_KickBotFromTeam( TeamName kickTeam ); ///< kick a bot from the given team. If no bot exists on the team, return false. + +extern bool UTIL_IsVisibleToTeam( const Vector &spot, int team, float maxRange = -1.0f ); ///< return true if anyone on the given team can see the given spot + +extern const char * UTIL_GetBotPrefix(); ///< returns the bot prefix string. +extern void UTIL_ConstructBotNetName(char *name, int nameLength, const BotProfile *bot); + +/** + * Echos text to the console, and prints it on the client's screen. This is NOT tied to the developer cvar. + * If you are adding debugging output in cstrike, use UTIL_DPrintf() (debug.h) instead. + */ +extern void CONSOLE_ECHO( char * pszMsg, ... ); +extern void CONSOLE_ECHO_LOGGED( char * pszMsg, ... ); + +extern void InitBotTrig( void ); +extern float BotCOS( float angle ); +extern float BotSIN( float angle ); + +/// determine if this event is audible, and if so, return its audible range and priority +bool IsGameEventAudible( GameEventType event, CBaseEntity *entity, CBaseEntity *other, float *range, PriorityType *priority, bool *isHostile ); + +extern void HintMessageToAllPlayers( const char *message ); + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Simple class for tracking intervals of game time + */ +class IntervalTimer +{ +public: + IntervalTimer( void ) + { + m_timestamp = -1.0f; + } + + void Reset( void ) + { + m_timestamp = gpGlobals->time; + } + + void Start( void ) + { + m_timestamp = gpGlobals->time; + } + + void Invalidate( void ) + { + m_timestamp = -1.0f; + } + + bool HasStarted( void ) const + { + return (m_timestamp > 0.0f); + } + + /// if not started, elapsed time is very large + float GetElapsedTime( void ) const + { + return (HasStarted()) ? (gpGlobals->time - m_timestamp) : 99999.9f; + } + + bool IsLessThen( float duration ) const + { + return (gpGlobals->time - m_timestamp < duration) ? true : false; + } + + bool IsGreaterThen( float duration ) const + { + return (gpGlobals->time - m_timestamp > duration) ? true : false; + } + +private: + float m_timestamp; +}; + +//-------------------------------------------------------------------------------------------------------------- +/** + * Simple class for counting down a short interval of time + */ +class CountdownTimer +{ +public: + CountdownTimer( void ) + { + m_timestamp = -1.0f; + m_duration = 0.0f; + } + + void Reset( void ) + { + m_timestamp = gpGlobals->time + m_duration; + } + + void Start( float duration ) + { + m_timestamp = gpGlobals->time + duration; + m_duration = duration; + } + + void Invalidate( void ) + { + m_timestamp = -1.0f; + } + + bool HasStarted( void ) const + { + return (m_timestamp > 0.0f); + } + + bool IsElapsed( void ) const + { + return (gpGlobals->time > m_timestamp); + } + +private: + float m_duration; + float m_timestamp; +}; + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if the given entity is valid + */ +inline bool IsEntityValid( CBaseEntity *entity ) +{ + if (entity == NULL) + return false; + + if (FNullEnt( entity->pev )) + return false; + + if (FStrEq( STRING( entity->pev->netname ), "" )) + return false; + + if (entity->pev->flags & FL_DORMANT) + return false; + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Given two line segments: startA to endA, and startB to endB, return true if they intesect + * and put the intersection point in "result". + * Note that this computes the intersection of the 2D (x,y) projection of the line segments. + */ +inline bool IsIntersecting2D( const Vector &startA, const Vector &endA, + const Vector &startB, const Vector &endB, + Vector *result = NULL ) +{ + float denom = (endA.x - startA.x) * (endB.y - startB.y) - (endA.y - startA.y) * (endB.x - startB.x); + if (denom == 0.0f) + { + // parallel + return false; + } + + float numS = (startA.y - startB.y) * (endB.x - startB.x) - (startA.x - startB.x) * (endB.y - startB.y); + if (numS == 0.0f) + { + // coincident + return true; + } + + float numT = (startA.y - startB.y) * (endA.x - startA.x) - (startA.x - startB.x) * (endA.y - startA.y); + + float s = numS / denom; + if (s < 0.0f || s > 1.0f) + { + // intersection is not within line segment of startA to endA + return false; + } + + float t = numT / denom; + if (t < 0.0f || t > 1.0f) + { + // intersection is not within line segment of startB to endB + return false; + } + + // compute intesection point + if (result) + *result = startA + s * (endA - startA); + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Iterate over all active players in the game, invoking functor on each. + * If functor returns false, stop iteration and return false. + */ +template < typename Functor > +bool ForEachPlayer( Functor &func ) +{ + for( int i=1; i<=gpGlobals->maxClients; ++i ) + { + CBasePlayer *player = static_cast( UTIL_PlayerByIndex( i ) ); + + if (!IsEntityValid( player )) + continue; + + if (!player->IsPlayer()) + continue; + + if (func( player ) == false) + return false; + } + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * For zombie game + */ +inline bool IsZombieGame( void ) +{ +#ifdef TERRORSTRIKE + return true; +#else + return false; +#endif +} + +#endif diff --git a/game_shared/bot/improv.h b/game_shared/bot/improv.h new file mode 100644 index 0000000..29eb0d5 --- /dev/null +++ b/game_shared/bot/improv.h @@ -0,0 +1,118 @@ +// improv.h +// Improv interface +// Author: Michael S. Booth (mike@turtlerockstudios.com), November 2003 + +#ifndef _IMPROV_H_ +#define _IMPROV_H_ + +#include "nav_path.h" + +class CBaseEntity; + + +//-------------------------------------------------------------------------------------------------------- +/** + * Improv-specific events + */ +class IImprovEvent +{ +public: + virtual void OnMoveToSuccess( const Vector &goal ) { } ///< invoked when an improv reaches its MoveTo goal + + enum MoveToFailureType + { + FAIL_INVALID_PATH, + FAIL_STUCK, + FAIL_FELL_OFF, + }; + virtual void OnMoveToFailure( const Vector &goal, MoveToFailureType reason ) { } ///< invoked when an improv fails to reach a MoveTo goal + + virtual void OnInjury( float amount ) { } ///< invoked when the improv is injured +}; + +//-------------------------------------------------------------------------------------------------------- +/** + * The Improv interface definition + * + * An "Improv" is an improvisational actor that simulates the + * behavor of a human in an unscripted, "make it up as you go" manner. + */ +class CImprov : public IImprovEvent +{ +public: + virtual ~CImprov() { } + + virtual bool IsAlive( void ) const = 0; ///< return true if this improv is alive + + virtual void MoveTo( const Vector &goal ) = 0; ///< move improv towards far-away goal (pathfind) + virtual void LookAt( const Vector &target ) = 0; ///< define desired view target + virtual void ClearLookAt( void ) = 0; ///< remove view goal + virtual void FaceTo( const Vector &goal ) = 0; ///< orient body towards goal + virtual void ClearFaceTo( void ) = 0; ///< remove body orientation goal + + virtual bool IsAtMoveGoal( float error = 20.0f ) const = 0; ///< return true if improv is standing on its movement goal + virtual bool HasLookAt( void ) const = 0; ///< return true if improv has a look at goal + virtual bool HasFaceTo( void ) const = 0; ///< return true if improv has a face to goal + virtual bool IsAtFaceGoal( void ) const = 0; ///< return true if improv is facing towards its face goal + virtual bool IsFriendInTheWay( const Vector &goalPos ) const = 0; ///< return true if a friend is blocking our line to the given goal position + virtual bool IsFriendInTheWay( CBaseEntity *myFriend, const Vector &goalPos ) const = 0; ///< return true if the given friend is blocking our line to the given goal position + + virtual void MoveForward( void ) = 0; + virtual void MoveBackward( void ) = 0; + virtual void StrafeLeft( void ) = 0; + virtual void StrafeRight( void ) = 0; + virtual bool Jump( void ) = 0; + virtual void Crouch( void ) = 0; + virtual void StandUp( void ) = 0; ///< "un-crouch" + + virtual void TrackPath( const Vector &pathGoal, float deltaT ) = 0; ///< move along path by following "pathGoal" + + virtual void StartLadder( const CNavLadder *ladder, NavTraverseType how, const Vector *approachPos, const Vector *departPos ) = 0; ///< invoked when a ladder is encountered while following a path + virtual bool TraverseLadder( const CNavLadder *ladder, NavTraverseType how, const Vector *approachPos, const Vector *departPos, float deltaT ) = 0; ///< traverse given ladder + + virtual bool GetSimpleGroundHeightWithFloor( const Vector *pos, float *height, Vector *normal = NULL ) = 0; ///< find "simple" ground height, treating current nav area as part of the floor + + virtual void Run( void ) = 0; + virtual void Walk( void ) = 0; + virtual void Stop( void ) = 0; + + virtual float GetMoveAngle( void ) const = 0; ///< return direction of movement + virtual float GetFaceAngle( void ) const = 0; ///< return direction of view + + virtual const Vector &GetFeet( void ) const = 0; ///< return position of "feet" - point below centroid of improv at feet level + virtual const Vector &GetCentroid( void ) const = 0; + virtual const Vector &GetEyes( void ) const = 0; + + virtual bool IsRunning( void ) const = 0; + virtual bool IsWalking( void ) const = 0; + virtual bool IsStopped( void ) const = 0; + + virtual bool IsCrouching( void ) const = 0; + virtual bool IsJumping( void ) const = 0; + virtual bool IsUsingLadder( void ) const = 0; + virtual bool IsOnGround( void ) const = 0; + virtual bool IsMoving( void ) const = 0; ///< if true, improv is walking, crawling, running somewhere + + virtual bool CanRun( void ) const = 0; + virtual bool CanCrouch( void ) const = 0; + virtual bool CanJump( void ) const = 0; + + #define CHECK_FOV true + virtual bool IsVisible( const Vector &pos, bool testFOV = false ) const = 0; ///< return true if improv can see position + + virtual bool IsPlayerLookingAtMe( CBasePlayer *other, float cosTolerance = 0.95f ) const = 0; ///< return true if 'other' is looking right at me + virtual CBasePlayer *IsAnyPlayerLookingAtMe( int team = 0, float cosTolerance = 0.95f ) const = 0; ///< return player on given team that is looking right at me (team == 0 means any team), NULL otherwise + + virtual CBasePlayer *GetClosestPlayerByTravelDistance( int team = 0, float *range = NULL ) const = 0; ///< return actual travel distance to closest player on given team (team == 0 means any team) + + virtual CNavArea *GetLastKnownArea( void ) const = 0; + + virtual void OnUpdate( float deltaT ) = 0; ///< a less frequent, full update 'tick' + virtual void OnUpkeep( float deltaT ) = 0; ///< a frequent, lightweight update 'tick' + virtual void OnReset( void ) = 0; ///< reset improv to initial state + virtual void OnGameEvent( GameEventType event, CBaseEntity *entity, CBaseEntity *other ) = 0; ///< invoked when an event occurs in the game + virtual void OnTouch( CBaseEntity *other ) = 0; ///< "other" has touched us +}; + + +#endif // _IMPROV_H_ diff --git a/game_shared/bot/nav.h b/game_shared/bot/nav.h new file mode 100644 index 0000000..78635d7 --- /dev/null +++ b/game_shared/bot/nav.h @@ -0,0 +1,363 @@ +// nav.h +// AI Navigation data structures +// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003 + +#ifndef _NAV_H_ +#define _NAV_H_ + +#pragma warning( disable : 4530 ) // STL uses exceptions, but we are not compiling with them - ignore warning + +const float GenerationStepSize = 25.0f; // (30) was 20, but bots can't fit always fit +const float StepHeight = 18.0f; ///< if delta Z is greater than this, we have to jump to get up +const float JumpHeight = 41.8f; ///< if delta Z is less than this, we can jump up on it +const float JumpCrouchHeight = 58.0f; ///< (48) if delta Z is less than or equal to this, we can jumpcrouch up on it + +// Strictly speaking, you CAN get up a slope of 1.643 (about 59 degrees), but you move very, very slowly +// This slope will represent the slope you can navigate without much slowdown +const float MaxSlope = 1.4f; ///< rise/run - if greater than this, we can't move up it (de_survivor canyon ramps) + +// instead of MaxSlope, we are using the following max Z component of a unit normal +const float MaxUnitZSlope = 0.7f; + +const float BotRadius = 10.0f; ///< circular extent that contains bot +const float DeathDrop = 200.0f; ///< (300) distance at which we will die if we fall - should be about 600, and pay attention to fall damage during pathfind + +const float HalfHumanWidth = 16.0f; +const float HalfHumanHeight = 36.0f; +const float HumanHeight = 72.0f; + +#define NAV_MAGIC_NUMBER 0xFEEDFACE ///< to help identify nav files + +/** + * A place is a named group of navigation areas + */ +typedef unsigned int Place; +#define UNDEFINED_PLACE 0 // ie: "no place" +#define ANY_PLACE 0xFFFF + +enum NavErrorType +{ + NAV_OK, + NAV_CANT_ACCESS_FILE, + NAV_INVALID_FILE, + NAV_BAD_FILE_VERSION, + NAV_CORRUPT_DATA, +}; + +enum NavAttributeType +{ + NAV_CROUCH = 0x01, ///< must crouch to use this node/area + NAV_JUMP = 0x02, ///< must jump to traverse this area + NAV_PRECISE = 0x04, ///< do not adjust for obstacles, just move along area + NAV_NO_JUMP = 0x08, ///< inhibit discontinuity jumping +}; + +enum NavDirType +{ + NORTH = 0, + EAST = 1, + SOUTH = 2, + WEST = 3, + + NUM_DIRECTIONS +}; + +/** + * Defines possible ways to move from one area to another + */ +enum NavTraverseType +{ + // NOTE: First 4 directions MUST match NavDirType + GO_NORTH = 0, + GO_EAST, + GO_SOUTH, + GO_WEST, + GO_LADDER_UP, + GO_LADDER_DOWN, + GO_JUMP, + + NUM_TRAVERSE_TYPES +}; + +enum NavCornerType +{ + NORTH_WEST = 0, + NORTH_EAST = 1, + SOUTH_EAST = 2, + SOUTH_WEST = 3, + + NUM_CORNERS +}; + +enum NavRelativeDirType +{ + FORWARD = 0, + RIGHT, + BACKWARD, + LEFT, + UP, + DOWN, + + NUM_RELATIVE_DIRECTIONS +}; + +struct Extent +{ + Vector lo, hi; + + float SizeX( void ) const { return hi.x - lo.x; } + float SizeY( void ) const { return hi.y - lo.y; } + float SizeZ( void ) const { return hi.z - lo.z; } + float Area( void ) const { return SizeX() * SizeY(); } + + /// return true if 'pos' is inside of this extent + bool Contains( const Vector *pos ) const + { + return (pos->x >= lo.x && pos->x <= hi.x && + pos->y >= lo.y && pos->y <= hi.y && + pos->z >= lo.z && pos->z <= hi.z); + } +}; + +struct Ray +{ + Vector from, to; +}; + +class CNavArea; +class CNavNode; + +extern Extent NodeMapExtent; + + +//-------------------------------------------------------------------------------------------------------------- +inline NavDirType OppositeDirection( NavDirType dir ) +{ + switch( dir ) + { + case NORTH: return SOUTH; + case SOUTH: return NORTH; + case EAST: return WEST; + case WEST: return EAST; + } + + return NORTH; +} + +//-------------------------------------------------------------------------------------------------------------- +inline NavDirType DirectionLeft( NavDirType dir ) +{ + switch( dir ) + { + case NORTH: return WEST; + case SOUTH: return EAST; + case EAST: return NORTH; + case WEST: return SOUTH; + } + + return NORTH; +} + +//-------------------------------------------------------------------------------------------------------------- +inline NavDirType DirectionRight( NavDirType dir ) +{ + switch( dir ) + { + case NORTH: return EAST; + case SOUTH: return WEST; + case EAST: return SOUTH; + case WEST: return NORTH; + } + + return NORTH; +} + +//-------------------------------------------------------------------------------------------------------------- +inline void AddDirectionVector( Vector *v, NavDirType dir, float amount ) +{ + switch( dir ) + { + case NORTH: v->y -= amount; return; + case SOUTH: v->y += amount; return; + case EAST: v->x += amount; return; + case WEST: v->x -= amount; return; + } +} + +//-------------------------------------------------------------------------------------------------------------- +inline float DirectionToAngle( NavDirType dir ) +{ + switch( dir ) + { + case NORTH: return 270.0f; + case SOUTH: return 90.0f; + case EAST: return 0.0f; + case WEST: return 180.0f; + } + + return 0.0f; +} + +//-------------------------------------------------------------------------------------------------------------- +inline NavDirType AngleToDirection( float angle ) +{ + while( angle < 0.0f ) + angle += 360.0f; + + while( angle > 360.0f ) + angle -= 360.0f; + + if (angle < 45 || angle > 315) + return EAST; + + if (angle >= 45 && angle < 135) + return SOUTH; + + if (angle >= 135 && angle < 225) + return WEST; + + return NORTH; +} + +//-------------------------------------------------------------------------------------------------------------- +inline void DirectionToVector2D( NavDirType dir, Vector2D *v ) +{ + switch( dir ) + { + case NORTH: v->x = 0.0f; v->y = -1.0f; break; + case SOUTH: v->x = 0.0f; v->y = 1.0f; break; + case EAST: v->x = 1.0f; v->y = 0.0f; break; + case WEST: v->x = -1.0f; v->y = 0.0f; break; + } +} + +//-------------------------------------------------------------------------------------------------------------- +inline void SnapToGrid( Vector *pos ) +{ + int cx = pos->x / GenerationStepSize; + int cy = pos->y / GenerationStepSize; + pos->x = cx * GenerationStepSize; + pos->y = cy * GenerationStepSize; +} + +//-------------------------------------------------------------------------------------------------------------- +inline void SnapToGrid( float *value ) +{ + int c = *value / GenerationStepSize; + *value = c * GenerationStepSize; +} + +//-------------------------------------------------------------------------------------------------------------- +inline float NormalizeAngle( float angle ) +{ + while( angle < -180.0f ) + angle += 360.0f; + + while( angle > 180.0f ) + angle -= 360.0f; + + return angle; +} + +//-------------------------------------------------------------------------------------------------------------- +inline float NormalizeAnglePositive( float angle ) +{ + while( angle < 0.0f ) + angle += 360.0f; + + while( angle >= 360.0f ) + angle -= 360.0f; + + return angle; +} + +//-------------------------------------------------------------------------------------------------------------- +inline float AngleDifference( float a, float b ) +{ + float angleDiff = a - b; + + while (angleDiff > 180.0f) + angleDiff -= 360.0f; + while (angleDiff < -180.0f) + angleDiff += 360.0f; + + return angleDiff; +} + +//-------------------------------------------------------------------------------------------------------------- +inline bool AnglesAreEqual( float a, float b, float tolerance = 5.0f ) +{ + if (abs( AngleDifference( a, b ) ) < tolerance) + return true; + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +inline bool VectorsAreEqual( const Vector *a, const Vector *b, float tolerance = 0.1f ) +{ + if (abs(a->x - b->x) < tolerance && + abs(a->y - b->y) < tolerance && + abs(a->z - b->z) < tolerance) + return true; + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if given entity can be ignored when moving + */ +#define WALK_THRU_DOORS 0x01 +#define WALK_THRU_BREAKABLES 0x02 +inline bool IsEntityWalkable( entvars_t *entity, unsigned int flags ) +{ + // if we hit a door, assume its walkable because it will open when we touch it + if (FClassnameIs( entity, "func_door" ) || FClassnameIs( entity, "func_door_rotating" )) + return (flags & WALK_THRU_DOORS) ? true : false; + + // if we hit a breakable object, assume its walkable because we will shoot it when we touch it + if (FClassnameIs( entity, "func_breakable" ) && entity->takedamage == DAMAGE_YES) + return (flags & WALK_THRU_BREAKABLES) ? true : false; + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Check LOS, ignoring any entities that we can walk through + */ +inline bool IsWalkableTraceLineClear( Vector &from, Vector &to, unsigned int flags = 0 ) +{ + TraceResult result; + edict_t *ignore = NULL; + Vector useFrom = from; + + while(true) + { + UTIL_TraceLine( useFrom, to, ignore_monsters, ignore, &result ); + + // if we hit a walkable entity, try again + if (result.flFraction != 1.0f && IsEntityWalkable( VARS( result.pHit ), flags )) + { + ignore = result.pHit; + + // start from just beyond where we hit to avoid infinite loops + Vector dir = to - from; + dir.NormalizeInPlace(); + useFrom = result.vecEndPos + 5.0f * dir; + } + else + { + break; + } + } + + if (result.flFraction == 1.0f) + return true; + + return false; +} + + +#endif // _NAV_H_ diff --git a/game_shared/bot/nav_area.cpp b/game_shared/bot/nav_area.cpp new file mode 100644 index 0000000..af380f2 --- /dev/null +++ b/game_shared/bot/nav_area.cpp @@ -0,0 +1,5219 @@ +// nav_area.cpp +// AI Navigation areas +// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003 + +#pragma warning( disable : 4530 ) // STL uses exceptions, but we are not compiling with them - ignore warning +#pragma warning( disable : 4786 ) // long STL names get truncated in browse info. + +#include +#include +#include + +#include +#include +#include + +#ifdef _WIN32 +#include + +#else +#include +#define _write write +#define _close close +#define MAX_OSPATH PATH_MAX +#endif + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "gamerules.h" +#include "bot_util.h" + +/// @todo Abstract hostages and cs-bots out of here +#include "cs_bot.h" +#include "cs_bot_manager.h" +#include "hostage.h" + +#include "nav.h" +#include "nav_node.h" +#include "nav_area.h" + +#include "pm_shared.h" // for OBS_ROAMING + +extern void HintMessageToAllPlayers( const char *message ); + +unsigned int CNavArea::m_nextID = 1; +NavAreaList TheNavAreaList; + +NavLadderList TheNavLadderList; + +unsigned int CNavArea::m_masterMarker = 1; +CNavArea *CNavArea::m_openList = NULL; + +bool CNavArea::m_isReset = false; +static float lastDrawTimestamp = 0.0f; + +//-------------------------------------------------------------------------------------------------------------- +/** + * This list contains all "good-sized" areas used to compute "approach points" + */ +static NavAreaList goodSizedAreaList; + +static void buildGoodSizedList( void ) +{ + const float minSize = 200.0f; // 150 + + NavAreaList::iterator iter; + for( iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter ) + { + CNavArea *area = *iter; + + // skip the small areas + const Extent *extent = area->GetExtent(); + if (extent->SizeX() < minSize || extent->SizeY() < minSize) + continue; + + goodSizedAreaList.push_back( area ); + } +} + +//-------------------------------------------------------------------------------------------------------------- + +HidingSpotList TheHidingSpotList; +unsigned int HidingSpot::m_nextID = 1; +unsigned int HidingSpot::m_masterMarker = 0; + +void DestroyHidingSpots( void ) +{ + // remove all hiding spot references from the nav areas + for( NavAreaList::iterator areaIter = TheNavAreaList.begin(); areaIter != TheNavAreaList.end(); ++areaIter ) + { + CNavArea *area = *areaIter; + + area->m_hidingSpotList.clear(); + } + + HidingSpot::m_nextID = 0; + + // free all the HidingSpots + for( HidingSpotList::iterator iter = TheHidingSpotList.begin(); iter != TheHidingSpotList.end(); ++iter ) + delete *iter; + + TheHidingSpotList.clear(); +} + +/** + * For use when loading from a file + */ +HidingSpot::HidingSpot( void ) +{ + m_pos = Vector( 0, 0, 0 ); + m_id = 0; + m_flags = 0; + + TheHidingSpotList.push_back( this ); +} + +/** + * For use when generating - assigns unique ID + */ +HidingSpot::HidingSpot( const Vector *pos, unsigned char flags ) +{ + m_pos = *pos; + m_id = m_nextID++; + m_flags = flags; + + TheHidingSpotList.push_back( this ); +} + +void HidingSpot::Save( int fd, unsigned int version ) const +{ + _write( fd, &m_id, sizeof(unsigned int) ); + _write( fd, &m_pos, 3 * sizeof(float) ); + _write( fd, &m_flags, sizeof(unsigned char) ); +} + +void HidingSpot::Load( SteamFile *file, unsigned int version ) +{ + file->Read( &m_id, sizeof(unsigned int) ); + file->Read( &m_pos, 3 * sizeof(float) ); + file->Read( &m_flags, sizeof(unsigned char) ); + + // update next ID to avoid ID collisions by later spots + if (m_id >= m_nextID) + m_nextID = m_id+1; +} + +/** + * Given a HidingSpot ID, return the associated HidingSpot + */ +HidingSpot *GetHidingSpotByID( unsigned int id ) +{ + for( HidingSpotList::iterator iter = TheHidingSpotList.begin(); iter != TheHidingSpotList.end(); ++iter ) + { + HidingSpot *spot = *iter; + + if (spot->GetID() == id) + return spot; + } + + return NULL; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * To keep constructors consistent + */ +void CNavArea::Initialize( void ) +{ + m_marker = 0; + m_parent = NULL; + m_parentHow = GO_NORTH; + m_attributeFlags = 0; + m_place = 0; + + for ( int i=0; ix < otherCorner->x) + { + m_extent.lo.x = corner->x; + m_extent.hi.x = otherCorner->x; + } + else + { + m_extent.hi.x = corner->x; + m_extent.lo.x = otherCorner->x; + } + + if (corner->y < otherCorner->y) + { + m_extent.lo.y = corner->y; + m_extent.hi.y = otherCorner->y; + } + else + { + m_extent.hi.y = corner->y; + m_extent.lo.y = otherCorner->y; + } + + m_extent.lo.z = corner->z; + m_extent.hi.z = corner->z; + + m_center.x = (m_extent.lo.x + m_extent.hi.x)/2.0f; + m_center.y = (m_extent.lo.y + m_extent.hi.y)/2.0f; + m_center.z = (m_extent.lo.z + m_extent.hi.z)/2.0f; + + m_neZ = corner->z; + m_swZ = otherCorner->z; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * + */ +CNavArea::CNavArea( const Vector *nwCorner, const Vector *neCorner, const Vector *seCorner, const Vector *swCorner ) +{ + Initialize(); + + m_extent.lo = *nwCorner; + m_extent.hi = *seCorner; + + m_center.x = (m_extent.lo.x + m_extent.hi.x)/2.0f; + m_center.y = (m_extent.lo.y + m_extent.hi.y)/2.0f; + m_center.z = (m_extent.lo.z + m_extent.hi.z)/2.0f; + + m_neZ = neCorner->z; + m_swZ = swCorner->z; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Constructor used during generation phase. + */ +CNavArea::CNavArea( CNavNode *nwNode, CNavNode *neNode, CNavNode *seNode, CNavNode *swNode ) +{ + Initialize(); + + m_extent.lo = *nwNode->GetPosition(); + m_extent.hi = *seNode->GetPosition(); + + m_center.x = (m_extent.lo.x + m_extent.hi.x)/2.0f; + m_center.y = (m_extent.lo.y + m_extent.hi.y)/2.0f; + m_center.z = (m_extent.lo.z + m_extent.hi.z)/2.0f; + + m_neZ = neNode->GetPosition()->z; + m_swZ = swNode->GetPosition()->z; + + m_node[ NORTH_WEST ] = nwNode; + m_node[ NORTH_EAST ] = neNode; + m_node[ SOUTH_EAST ] = seNode; + m_node[ SOUTH_WEST ] = swNode; + + // mark internal nodes as part of this area + AssignNodes( this ); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Destructor + */ +CNavArea::~CNavArea() +{ + // if we are resetting the system, don't bother cleaning up - all areas are being destroyed + if (m_isReset) + return; + + // tell the other areas we are going away + NavAreaList::iterator iter; + for( iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter ) + { + CNavArea *area = *iter; + + if (area == this) + continue; + + area->OnDestroyNotify( this ); + } + + // unhook from ladders + for( int i=0; iOnDestroyNotify( this ); + } + } + + // remove the area from the grid + TheNavAreaGrid.RemoveNavArea( this ); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * This is invoked when an area is going away. + * Remove any references we have to it. + */ +void CNavArea::OnDestroyNotify( CNavArea *dead ) +{ + NavConnect con; + con.area = dead; + for( int d=0; dm_id, dirName[ dir ] ); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Disconnect this area from given area + */ +void CNavArea::Disconnect( CNavArea *area ) +{ + NavConnect connect; + connect.area = area; + + for( int dir = 0; dirGetPosition(); + m_extent.hi = *m_node[ SOUTH_EAST ]->GetPosition(); + + m_center.x = (m_extent.lo.x + m_extent.hi.x)/2.0f; + m_center.y = (m_extent.lo.y + m_extent.hi.y)/2.0f; + m_center.z = (m_extent.lo.z + m_extent.hi.z)/2.0f; + + m_neZ = m_node[ NORTH_EAST ]->GetPosition()->z; + m_swZ = m_node[ SOUTH_WEST ]->GetPosition()->z; + + // reassign the adjacent area's internal nodes to the final area + adjArea->AssignNodes( this ); + + // merge adjacency links - we gain all the connections that adjArea had + MergeAdjacentConnections( adjArea ); + + // remove subsumed adjacent area + TheNavAreaList.remove( adjArea ); + delete adjArea; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * For merging with "adjArea" - pick up all of "adjArea"s connections + */ +void CNavArea::MergeAdjacentConnections( CNavArea *adjArea ) +{ + // merge adjacency links - we gain all the connections that adjArea had + NavConnectList::iterator iter; + int dir; + for( dir = 0; dirm_connect[ dir ].begin(); iter != adjArea->m_connect[ dir ].end(); ++iter ) + { + NavConnect connect = *iter; + + if (connect.area != adjArea && connect.area != this) + ConnectTo( connect.area, (NavDirType)dir ); + } + } + + // remove any references from this area to the adjacent area, since it is now part of us + for( dir = 0; dirm_connect[ dir ].begin(); iter != area->m_connect[ dir ].end(); ++iter ) + { + NavConnect connect = *iter; + + if (connect.area == adjArea) + { + connected = true; + break; + } + } + + if (connected) + { + // remove all references to adjArea + NavConnect connect; + connect.area = adjArea; + area->m_connect[dir].remove( connect ); + + // remove all references to the new area + connect.area = this; + area->m_connect[dir].remove( connect ); + + // add a single connection to the new area + connect.area = this; + area->m_connect[dir].push_back( connect ); + } + } + } + +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Assign internal nodes to the given area + * NOTE: "internal" nodes do not include the east or south border nodes + */ +void CNavArea::AssignNodes( CNavArea *area ) +{ + CNavNode *horizLast = m_node[ NORTH_EAST ]; + + for( CNavNode *vertNode = m_node[ NORTH_WEST ]; vertNode != m_node[ SOUTH_WEST ]; vertNode = vertNode->GetConnectedNode( SOUTH ) ) + { + for( CNavNode *horizNode = vertNode; horizNode != horizLast; horizNode = horizNode->GetConnectedNode( EAST ) ) + { + horizNode->AssignArea( area ); + } + + horizLast = horizLast->GetConnectedNode( SOUTH ); + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Split this area into two areas at the given edge. + * Preserve all adjacency connections. + * NOTE: This does not update node connections, only areas. + */ +bool CNavArea::SplitEdit( bool splitAlongX, float splitEdge, CNavArea **outAlpha, CNavArea **outBeta ) +{ + CNavArea *alpha = NULL; + CNavArea *beta = NULL; + + if (splitAlongX) + { + // +-----+->X + // | A | + // +-----+ + // | B | + // +-----+ + // | + // Y + + // don't do split if at edge of area + if (splitEdge <= m_extent.lo.y + 1.0f) + return false; + + if (splitEdge >= m_extent.hi.y - 1.0f) + return false; + + alpha = new CNavArea; + alpha->m_extent.lo = m_extent.lo; + + alpha->m_extent.hi.x = m_extent.hi.x; + alpha->m_extent.hi.y = splitEdge; + alpha->m_extent.hi.z = GetZ( &alpha->m_extent.hi ); + + beta = new CNavArea; + beta->m_extent.lo.x = m_extent.lo.x; + beta->m_extent.lo.y = splitEdge; + beta->m_extent.lo.z = GetZ( &beta->m_extent.lo ); + + beta->m_extent.hi = m_extent.hi; + + alpha->ConnectTo( beta, SOUTH ); + beta->ConnectTo( alpha, NORTH ); + + FinishSplitEdit( alpha, SOUTH ); + FinishSplitEdit( beta, NORTH ); + } + else + { + // +--+--+->X + // | | | + // | A|B | + // | | | + // +--+--+ + // | + // Y + + // don't do split if at edge of area + if (splitEdge <= m_extent.lo.x + 1.0f) + return false; + + if (splitEdge >= m_extent.hi.x - 1.0f) + return false; + + alpha = new CNavArea; + alpha->m_extent.lo = m_extent.lo; + + alpha->m_extent.hi.x = splitEdge; + alpha->m_extent.hi.y = m_extent.hi.y; + alpha->m_extent.hi.z = GetZ( &alpha->m_extent.hi ); + + beta = new CNavArea; + beta->m_extent.lo.x = splitEdge; + beta->m_extent.lo.y = m_extent.lo.y; + beta->m_extent.lo.z = GetZ( &beta->m_extent.lo ); + + beta->m_extent.hi = m_extent.hi; + + alpha->ConnectTo( beta, EAST ); + beta->ConnectTo( alpha, WEST ); + + FinishSplitEdit( alpha, EAST ); + FinishSplitEdit( beta, WEST ); + } + + // new areas inherit attributes from original area + alpha->SetAttributes( GetAttributes() ); + beta->SetAttributes( GetAttributes() ); + + // new areas inherit place from original area + alpha->SetPlace( GetPlace() ); + beta->SetPlace( GetPlace() ); + + // return new areas + if (outAlpha) + *outAlpha = alpha; + + if (outBeta) + *outBeta = beta; + + // remove original area + TheNavAreaList.remove( this ); + delete this; + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if given area is connected in given direction + * if dir == NUM_DIRECTIONS, check all directions (direction is unknown) + * @todo Formalize "asymmetric" flag on connections + */ +bool CNavArea::IsConnected( const CNavArea *area, NavDirType dir ) const +{ + // we are connected to ourself + if (area == this) + return true; + + NavConnectList::const_iterator iter; + + if (dir == NUM_DIRECTIONS) + { + // search all directions + for( int d=0; dm_topBehindArea == area || + ladder->m_topForwardArea == area || + ladder->m_topLeftArea == area || + ladder->m_topRightArea == area) + return true; + } + + for( liter = m_ladder[ LADDER_DOWN ].begin(); liter != m_ladder[ LADDER_DOWN ].end(); ++liter ) + { + CNavLadder *ladder = *liter; + + if (ladder->m_bottomArea == area) + return true; + } + } + else + { + // check specific direction + for( iter = m_connect[ dir ].begin(); iter != m_connect[ dir ].end(); ++iter ) + { + if (area == (*iter).area) + return true; + } + } + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Compute change in height from this area to given area + * @todo This is approximate for now + */ +float CNavArea::ComputeHeightChange( const CNavArea *area ) +{ + float ourZ = GetZ( GetCenter() ); + float areaZ = area->GetZ( area->GetCenter() ); + + return areaZ - ourZ; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Given the portion of the original area, update its internal data + * The "ignoreEdge" direction defines the side of the original area that the new area does not include + */ +void CNavArea::FinishSplitEdit( CNavArea *newArea, NavDirType ignoreEdge ) +{ + newArea->m_center.x = (newArea->m_extent.lo.x + newArea->m_extent.hi.x)/2.0f; + newArea->m_center.y = (newArea->m_extent.lo.y + newArea->m_extent.hi.y)/2.0f; + newArea->m_center.z = (newArea->m_extent.lo.z + newArea->m_extent.hi.z)/2.0f; + + newArea->m_neZ = GetZ( newArea->m_extent.hi.x, newArea->m_extent.lo.y ); + newArea->m_swZ = GetZ( newArea->m_extent.lo.x, newArea->m_extent.hi.y ); + + // connect to adjacent areas + for( int d=0; dIsOverlappingX( adj )) + { + newArea->ConnectTo( adj, (NavDirType)d ); + + // add reciprocal connection if needed + if (adj->IsConnected( this, OppositeDirection( (NavDirType)d ))) + adj->ConnectTo( newArea, OppositeDirection( (NavDirType)d ) ); + } + break; + + case EAST: + case WEST: + if (newArea->IsOverlappingY( adj )) + { + newArea->ConnectTo( adj, (NavDirType)d ); + + // add reciprocal connection if needed + if (adj->IsConnected( this, OppositeDirection( (NavDirType)d ))) + adj->ConnectTo( newArea, OppositeDirection( (NavDirType)d ) ); + } + break; + } + } + } + + TheNavAreaList.push_back( newArea ); + TheNavAreaGrid.AddNavArea( newArea ); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Create a new area between this area and given area + */ +bool CNavArea::SpliceEdit( CNavArea *other ) +{ + CNavArea *newArea = NULL; + Vector nw, ne, se, sw; + + if (m_extent.lo.x > other->m_extent.hi.x) + { + // 'this' is east of 'other' + float top = max( m_extent.lo.y, other->m_extent.lo.y ); + float bottom = min( m_extent.hi.y, other->m_extent.hi.y ); + + nw.x = other->m_extent.hi.x; + nw.y = top; + nw.z = other->GetZ( &nw ); + + se.x = m_extent.lo.x; + se.y = bottom; + se.z = GetZ( &se ); + + ne.x = se.x; + ne.y = nw.y; + ne.z = GetZ( &ne ); + + sw.x = nw.x; + sw.y = se.y; + sw.z = other->GetZ( &sw ); + + newArea = new CNavArea( &nw, &ne, &se, &sw ); + + this->ConnectTo( newArea, WEST ); + newArea->ConnectTo( this, EAST ); + + other->ConnectTo( newArea, EAST ); + newArea->ConnectTo( other, WEST ); + } + else if (m_extent.hi.x < other->m_extent.lo.x) + { + // 'this' is west of 'other' + float top = max( m_extent.lo.y, other->m_extent.lo.y ); + float bottom = min( m_extent.hi.y, other->m_extent.hi.y ); + + nw.x = m_extent.hi.x; + nw.y = top; + nw.z = GetZ( &nw ); + + se.x = other->m_extent.lo.x; + se.y = bottom; + se.z = other->GetZ( &se ); + + ne.x = se.x; + ne.y = nw.y; + ne.z = other->GetZ( &ne ); + + sw.x = nw.x; + sw.y = se.y; + sw.z = GetZ( &sw ); + + newArea = new CNavArea( &nw, &ne, &se, &sw ); + + this->ConnectTo( newArea, EAST ); + newArea->ConnectTo( this, WEST ); + + other->ConnectTo( newArea, WEST ); + newArea->ConnectTo( other, EAST ); + } + else // 'this' overlaps in X + { + if (m_extent.lo.y > other->m_extent.hi.y) + { + // 'this' is south of 'other' + float left = max( m_extent.lo.x, other->m_extent.lo.x ); + float right = min( m_extent.hi.x, other->m_extent.hi.x ); + + nw.x = left; + nw.y = other->m_extent.hi.y; + nw.z = other->GetZ( &nw ); + + se.x = right; + se.y = m_extent.lo.y; + se.z = GetZ( &se ); + + ne.x = se.x; + ne.y = nw.y; + ne.z = other->GetZ( &ne ); + + sw.x = nw.x; + sw.y = se.y; + sw.z = GetZ( &sw ); + + newArea = new CNavArea( &nw, &ne, &se, &sw ); + + this->ConnectTo( newArea, NORTH ); + newArea->ConnectTo( this, SOUTH ); + + other->ConnectTo( newArea, SOUTH ); + newArea->ConnectTo( other, NORTH ); + } + else if (m_extent.hi.y < other->m_extent.lo.y) + { + // 'this' is north of 'other' + float left = max( m_extent.lo.x, other->m_extent.lo.x ); + float right = min( m_extent.hi.x, other->m_extent.hi.x ); + + nw.x = left; + nw.y = m_extent.hi.y; + nw.z = GetZ( &nw ); + + se.x = right; + se.y = other->m_extent.lo.y; + se.z = other->GetZ( &se ); + + ne.x = se.x; + ne.y = nw.y; + ne.z = GetZ( &ne ); + + sw.x = nw.x; + sw.y = se.y; + sw.z = other->GetZ( &sw ); + + newArea = new CNavArea( &nw, &ne, &se, &sw ); + + this->ConnectTo( newArea, SOUTH ); + newArea->ConnectTo( this, NORTH ); + + other->ConnectTo( newArea, NORTH ); + newArea->ConnectTo( other, SOUTH ); + } + else + { + // areas overlap + return false; + } + } + + // if both areas have the same place, the new area inherits it + if (GetPlace() == other->GetPlace()) + { + newArea->SetPlace( GetPlace() ); + } + else if (GetPlace() == UNDEFINED_PLACE) + { + newArea->SetPlace( other->GetPlace() ); + } + else if (other->GetPlace() == UNDEFINED_PLACE) + { + newArea->SetPlace( GetPlace() ); + } + else + { + // both have valid, but different places - pick on at random + if (RANDOM_LONG( 0, 100 ) < 50) + newArea->SetPlace( GetPlace() ); + else + newArea->SetPlace( other->GetPlace() ); + } + + TheNavAreaList.push_back( newArea ); + TheNavAreaGrid.AddNavArea( newArea ); + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Merge this area and given adjacent area + */ +bool CNavArea::MergeEdit( CNavArea *adj ) +{ + // can only merge if attributes of both areas match + + + // check that these areas can be merged + const float tolerance = 1.0f; + bool merge = false; + if (ABS( m_extent.lo.x - adj->m_extent.lo.x ) < tolerance && + ABS( m_extent.hi.x - adj->m_extent.hi.x ) < tolerance) + merge = true; + + if (ABS( m_extent.lo.y - adj->m_extent.lo.y ) < tolerance && + ABS( m_extent.hi.y - adj->m_extent.hi.y ) < tolerance) + merge = true; + + if (merge == false) + return false; + + Extent origExtent = m_extent; + + // update extent + if (m_extent.lo.x > adj->m_extent.lo.x || m_extent.lo.y > adj->m_extent.lo.y) + m_extent.lo = adj->m_extent.lo; + + if (m_extent.hi.x < adj->m_extent.hi.x || m_extent.hi.y < adj->m_extent.hi.y) + m_extent.hi = adj->m_extent.hi; + + m_center.x = (m_extent.lo.x + m_extent.hi.x)/2.0f; + m_center.y = (m_extent.lo.y + m_extent.hi.y)/2.0f; + m_center.z = (m_extent.lo.z + m_extent.hi.z)/2.0f; + + if (m_extent.hi.x > origExtent.hi.x || m_extent.lo.y < origExtent.lo.y) + m_neZ = adj->GetZ( m_extent.hi.x, m_extent.lo.y ); + else + m_neZ = GetZ( m_extent.hi.x, m_extent.lo.y ); + + if (m_extent.lo.x < origExtent.lo.x || m_extent.hi.y > origExtent.hi.y) + m_swZ = adj->GetZ( m_extent.lo.x, m_extent.hi.y ); + else + m_swZ = GetZ( m_extent.lo.x, m_extent.hi.y ); + + // merge adjacency links - we gain all the connections that adjArea had + MergeAdjacentConnections( adj ); + + // remove subsumed adjacent area + TheNavAreaList.remove( adj ); + delete adj; + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +void ApproachAreaAnalysisPrep( void ) +{ + // collect "good-sized" areas for computing approach areas + buildGoodSizedList(); +} + +//-------------------------------------------------------------------------------------------------------------- +void CleanupApproachAreaAnalysisPrep( void ) +{ + goodSizedAreaList.clear(); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Destroy ladder representations + */ +void DestroyLadders( void ) +{ + while( !TheNavLadderList.empty() ) + { + CNavLadder *ladder = TheNavLadderList.front(); + TheNavLadderList.pop_front(); + delete ladder; + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Free navigation map data. + */ +void DestroyNavigationMap( void ) +{ + CNavArea::m_isReset = true; + + // remove each element of the list and delete them + while( !TheNavAreaList.empty() ) + { + CNavArea *area = TheNavAreaList.front(); + TheNavAreaList.pop_front(); + delete area; + } + + CNavArea::m_isReset = false; + + // destroy ladder representations + DestroyLadders(); + + // destroy all hiding spots + DestroyHidingSpots(); + + // destroy navigation nodes created during map learning + CNavNode *node, *next; + for( node = CNavNode::m_list; node; node = next ) + { + next = node->m_next; + delete node; + } + CNavNode::m_list = NULL; + + // reset the grid + TheNavAreaGrid.Reset(); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Strip the "analyzed" data out of all navigation areas + */ +void StripNavigationAreas( void ) +{ + NavAreaList::iterator iter; + for( iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter ) + { + CNavArea *area = *iter; + + area->Strip(); + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Remove "analyzed" data from nav area + */ +void CNavArea::Strip( void ) +{ + m_approachCount = 0; + m_spotEncounterList.clear(); // memory leak +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Start at given position and find first area in given direction + */ +inline CNavArea *FindFirstAreaInDirection( const Vector *start, NavDirType dir, float range, float beneathLimit, CBaseEntity *traceIgnore = NULL, Vector *closePos = NULL ) +{ + CNavArea *area = NULL; + + Vector pos = *start; + + int end = (int)((range / GenerationStepSize) + 0.5f); + + for( int i=1; i<=end; i++ ) + { + AddDirectionVector( &pos, dir, GenerationStepSize ); + + // make sure we dont look thru the wall + TraceResult result; + + if (traceIgnore) + UTIL_TraceLine( *start, pos, ignore_monsters, ENT( traceIgnore->pev ), &result ); + else + UTIL_TraceLine( *start, pos, ignore_monsters, NULL, &result ); + + if (result.flFraction != 1.0f) + break; + + area = TheNavAreaGrid.GetNavArea( &pos, beneathLimit ); + if (area) + { + if (closePos) + { + closePos->x = pos.x; + closePos->y = pos.y; + closePos->z = area->GetZ( pos.x, pos.y ); + } + + break; + } + } + + return area; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Determine if we can "jump down" from given point + */ +inline bool testJumpDown( const Vector *fromPos, const Vector *toPos ) +{ + float dz = fromPos->z - toPos->z; + + // drop can't be too far, or too short (or nonexistant) + if (dz <= JumpCrouchHeight || dz >= DeathDrop) + return false; + + // + // Check LOS out and down + // + // ------+ + // | + // F | + // | + // T + // + + Vector from( fromPos->x, fromPos->y, fromPos->z + HumanHeight ); + Vector to( toPos->x, toPos->y, from.z ); + + TraceResult result; + UTIL_TraceLine( from, to, ignore_monsters, NULL, &result ); + if (result.flFraction != 1.0f || result.fStartSolid) + return false; + + from = to; + to.z = toPos->z + 2.0f; + UTIL_TraceLine( from, to, ignore_monsters, NULL, &result ); + if (result.flFraction != 1.0f || result.fStartSolid) + return false; + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +inline CNavArea *findJumpDownArea( const Vector *fromPos, NavDirType dir ) +{ + Vector start( fromPos->x, fromPos->y, fromPos->z + HalfHumanHeight ); + AddDirectionVector( &start, dir, GenerationStepSize/2.0f ); + + Vector toPos; + CNavArea *downArea = FindFirstAreaInDirection( &start, dir, 4.0f * GenerationStepSize, DeathDrop, NULL, &toPos ); + + if (downArea && testJumpDown( fromPos, &toPos )) + return downArea; + + return NULL; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Define connections between adjacent generated areas + */ +void ConnectGeneratedAreas( void ) +{ + CONSOLE_ECHO( " Connecting navigation areas...\n" ); + + for( NavAreaList::iterator iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter ) + { + CNavArea *area = *iter; + + // scan along edge nodes, stepping one node over into the next area + // for now, only use bi-directional connections + + // north edge + CNavNode *node; + for( node = area->m_node[ NORTH_WEST ]; node != area->m_node[ NORTH_EAST ]; node = node->GetConnectedNode( EAST ) ) + { + CNavNode *adj = node->GetConnectedNode( NORTH ); + + if (adj && adj->GetArea() && adj->GetConnectedNode( SOUTH ) == node) + { + area->ConnectTo( adj->GetArea(), NORTH ); + } + else + { + CNavArea *downArea = findJumpDownArea( node->GetPosition(), NORTH ); + if (downArea && downArea != area) + area->ConnectTo( downArea, NORTH ); + } + } + + // west edge + for( node = area->m_node[ NORTH_WEST ]; node != area->m_node[ SOUTH_WEST ]; node = node->GetConnectedNode( SOUTH ) ) + { + CNavNode *adj = node->GetConnectedNode( WEST ); + + if (adj && adj->GetArea() && adj->GetConnectedNode( EAST ) == node) + { + area->ConnectTo( adj->GetArea(), WEST ); + } + else + { + CNavArea *downArea = findJumpDownArea( node->GetPosition(), WEST ); + if (downArea && downArea != area) + area->ConnectTo( downArea, WEST ); + } + } + + // south edge - this edge's nodes are actually part of adjacent areas + // move one node north, and scan west to east + /// @todo This allows one-node-wide areas - do we want this? + node = area->m_node[ SOUTH_WEST ]; + node = node->GetConnectedNode( NORTH ); + if (node) + { + CNavNode *end = area->m_node[ SOUTH_EAST ]->GetConnectedNode( NORTH ); + /// @todo Figure out why cs_backalley gets a NULL node in here... + for( ; node && node != end; node = node->GetConnectedNode( EAST ) ) + { + CNavNode *adj = node->GetConnectedNode( SOUTH ); + + if (adj && adj->GetArea() && adj->GetConnectedNode( NORTH ) == node) + { + area->ConnectTo( adj->GetArea(), SOUTH ); + } + else + { + CNavArea *downArea = findJumpDownArea( node->GetPosition(), SOUTH ); + if (downArea && downArea != area) + area->ConnectTo( downArea, SOUTH ); + } + } + } + + // east edge - this edge's nodes are actually part of adjacent areas + node = area->m_node[ NORTH_EAST ]; + node = node->GetConnectedNode( WEST ); + if (node) + { + CNavNode *end = area->m_node[ SOUTH_EAST ]->GetConnectedNode( WEST ); + for( ; node && node != end; node = node->GetConnectedNode( SOUTH ) ) + { + CNavNode *adj = node->GetConnectedNode( EAST ); + + if (adj && adj->GetArea() && adj->GetConnectedNode( WEST ) == node) + { + area->ConnectTo( adj->GetArea(), EAST ); + } + else + { + CNavArea *downArea = findJumpDownArea( node->GetPosition(), EAST ); + if (downArea && downArea != area) + area->ConnectTo( downArea, EAST ); + } + } + } + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Merge areas together to make larger ones (must remain rectangular - convex). + * Areas can only be merged if their attributes match. + */ +void MergeGeneratedAreas( void ) +{ + CONSOLE_ECHO( " Merging navigation areas...\n" ); + + bool merged; + + do + { + merged = false; + + for( NavAreaList::iterator iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter ) + { + CNavArea *area = *iter; + + // north edge + NavConnectList::iterator citer; + for( citer = area->m_connect[ NORTH ].begin(); citer != area->m_connect[ NORTH ].end(); ++citer ) + { + CNavArea *adjArea = (*citer).area; + + if (area->m_node[ NORTH_WEST ] == adjArea->m_node[ SOUTH_WEST ] && + area->m_node[ NORTH_EAST ] == adjArea->m_node[ SOUTH_EAST ] && + area->GetAttributes() == adjArea->GetAttributes() && + area->IsCoplanar( adjArea )) + { + // merge vertical + area->m_node[ NORTH_WEST ] = adjArea->m_node[ NORTH_WEST ]; + area->m_node[ NORTH_EAST ] = adjArea->m_node[ NORTH_EAST ]; + + merged = true; + //CONSOLE_ECHO( " Merged (north) areas #%d and #%d\n", area->m_id, adjArea->m_id ); + + area->FinishMerge( adjArea ); + + // restart scan - iterator is invalidated + break; + } + } + + if (merged) + break; + + // south edge + for( citer = area->m_connect[ SOUTH ].begin(); citer != area->m_connect[ SOUTH ].end(); ++citer ) + { + CNavArea *adjArea = (*citer).area; + + if (adjArea->m_node[ NORTH_WEST ] == area->m_node[ SOUTH_WEST ] && + adjArea->m_node[ NORTH_EAST ] == area->m_node[ SOUTH_EAST ] && + area->GetAttributes() == adjArea->GetAttributes() && + area->IsCoplanar( adjArea )) + { + // merge vertical + area->m_node[ SOUTH_WEST ] = adjArea->m_node[ SOUTH_WEST ]; + area->m_node[ SOUTH_EAST ] = adjArea->m_node[ SOUTH_EAST ]; + + merged = true; + //CONSOLE_ECHO( " Merged (south) areas #%d and #%d\n", area->m_id, adjArea->m_id ); + + area->FinishMerge( adjArea ); + + // restart scan - iterator is invalidated + break; + } + + } + + if (merged) + break; + + + // west edge + for( citer = area->m_connect[ WEST ].begin(); citer != area->m_connect[ WEST ].end(); ++citer ) + { + CNavArea *adjArea = (*citer).area; + + if (area->m_node[ NORTH_WEST ] == adjArea->m_node[ NORTH_EAST ] && + area->m_node[ SOUTH_WEST ] == adjArea->m_node[ SOUTH_EAST ] && + area->GetAttributes() == adjArea->GetAttributes() && + area->IsCoplanar( adjArea )) + { + // merge horizontal + area->m_node[ NORTH_WEST ] = adjArea->m_node[ NORTH_WEST ]; + area->m_node[ SOUTH_WEST ] = adjArea->m_node[ SOUTH_WEST ]; + + merged = true; + //CONSOLE_ECHO( " Merged (west) areas #%d and #%d\n", area->m_id, adjArea->m_id ); + + area->FinishMerge( adjArea ); + + // restart scan - iterator is invalidated + break; + } + + } + + if (merged) + break; + + // east edge + for( citer = area->m_connect[ EAST ].begin(); citer != area->m_connect[ EAST ].end(); ++citer ) + { + CNavArea *adjArea = (*citer).area; + + if (adjArea->m_node[ NORTH_WEST ] == area->m_node[ NORTH_EAST ] && + adjArea->m_node[ SOUTH_WEST ] == area->m_node[ SOUTH_EAST ] && + area->GetAttributes() == adjArea->GetAttributes() && + area->IsCoplanar( adjArea )) + { + // merge horizontal + area->m_node[ NORTH_EAST ] = adjArea->m_node[ NORTH_EAST ]; + area->m_node[ SOUTH_EAST ] = adjArea->m_node[ SOUTH_EAST ]; + + merged = true; + //CONSOLE_ECHO( " Merged (east) areas #%d and #%d\n", area->m_id, adjArea->m_id ); + + area->FinishMerge( adjArea ); + + // restart scan - iterator is invalidated + break; + } + } + + if (merged) + break; + } + } + while( merged ); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if area is more or less square. + * This is used when merging to prevent long, thin, areas being created. + */ +inline bool IsAreaRoughlySquare( const CNavArea *area ) +{ + float aspect = area->GetSizeX() / area->GetSizeY(); + + const float maxAspect = 3.01; + const float minAspect = 1.0f / maxAspect; + if (aspect < minAspect || aspect > maxAspect) + return false; + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Recursively chop area in half along X until child areas are roughly square + */ +void SplitX( CNavArea *area ) +{ + if (IsAreaRoughlySquare( area )) + return; + + float split = area->GetSizeX(); + split /= 2.0f; + split += area->GetExtent()->lo.x; + + SnapToGrid( &split ); + + const float epsilon = 0.1f; + if (abs(split - area->GetExtent()->lo.x) < epsilon || + abs(split - area->GetExtent()->hi.x) < epsilon) + { + // too small to subdivide + return; + } + + CNavArea *alpha, *beta; + if (area->SplitEdit( false, split, &alpha, &beta )) + { + // split each new area until square + SplitX( alpha ); + SplitX( beta ); + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Recursively chop area in half along Y until child areas are roughly square + */ +void SplitY( CNavArea *area ) +{ + if (IsAreaRoughlySquare( area )) + return; + + float split = area->GetSizeY(); + split /= 2.0f; + split += area->GetExtent()->lo.y; + + SnapToGrid( &split ); + + const float epsilon = 0.1f; + if (abs(split - area->GetExtent()->lo.y) < epsilon || + abs(split - area->GetExtent()->hi.y) < epsilon) + { + // too small to subdivide + return; + } + + CNavArea *alpha, *beta; + if (area->SplitEdit( true, split, &alpha, &beta )) + { + // split each new area until square + SplitY( alpha ); + SplitY( beta ); + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Split any long, thin, areas into roughly square chunks. + */ +void SquareUpAreas( void ) +{ + NavAreaList::iterator iter = TheNavAreaList.begin(); + + while( iter != TheNavAreaList.end() ) + { + CNavArea *area = *iter; + ++iter; + + if (!IsAreaRoughlySquare( area )) + { + // chop this area into square pieces + if (area->GetSizeX() > area->GetSizeY()) + SplitX( area ); + else + SplitY( area ); + } + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Check if an rectangular area of the given size can be + * made starting from the given node as the NW corner. + * Only consider fully connected nodes for this check. + * All of the nodes within the test area must have the same attributes. + * All of the nodes must be approximately co-planar w.r.t the NW node's normal, with the + * exception of 1x1 areas which can be any angle. + */ +bool TestArea( CNavNode *node, int width, int height ) +{ + Vector normal = *node->GetNormal(); + float d = -DotProduct( normal, *node->GetPosition() ); + + const float offPlaneTolerance = 5.0f; + + CNavNode *vertNode, *horizNode; + + vertNode = node; + for( int y=0; yGetAttributes() != node->GetAttributes()) + return false; + + if (horizNode->IsCovered()) + return false; + + if (!horizNode->IsClosedCell()) + return false; + + horizNode = horizNode->GetConnectedNode( EAST ); + if (horizNode == NULL) + return false; + + // nodes must lie on/near the plane + if (width > 1 || height > 1) + { + float dist = abs(DotProduct( *horizNode->GetPosition(), normal ) + d); + if (dist > offPlaneTolerance) + return false; + } + } + + vertNode = vertNode->GetConnectedNode( SOUTH ); + if (vertNode == NULL) + return false; + + // nodes must lie on/near the plane + if (width > 1 || height > 1) + { + float dist = abs(DotProduct( *vertNode->GetPosition(), normal ) + d); + if (dist > offPlaneTolerance) + return false; + } + } + + // check planarity of southern edge + if (width > 1 || height > 1) + { + horizNode = vertNode; + + for( int x=0; xGetConnectedNode( EAST ); + if (horizNode == NULL) + return false; + + // nodes must lie on/near the plane + float dist = abs(DotProduct( *horizNode->GetPosition(), normal ) + d); + if (dist > offPlaneTolerance) + return false; + } + } + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Create a nav area, and mark all nodes it overlaps as "covered" + * NOTE: Nodes on the east and south edges are not included. + * Returns number of nodes covered by this area, or -1 for error; + */ +int BuildArea( CNavNode *node, int width, int height ) +{ + //CONSOLE_ECHO( "BuildArea( #%d, %d, %d )\n", node->GetID(), width, height ); + + CNavNode *nwNode = node; + CNavNode *neNode = NULL; + CNavNode *swNode = NULL; + CNavNode *seNode = NULL; + + CNavNode *vertNode = node; + CNavNode *horizNode; + + int coveredNodes = 0; + + for( int y=0; yCover(); + ++coveredNodes; + + horizNode = horizNode->GetConnectedNode( EAST ); + } + + if (y == 0) + neNode = horizNode; + + vertNode = vertNode->GetConnectedNode( SOUTH ); + } + + swNode = vertNode; + + horizNode = vertNode; + for( int x=0; xGetConnectedNode( EAST ); + } + seNode = horizNode; + + if (!nwNode || !neNode || !seNode || !swNode) + { + CONSOLE_ECHO( "ERROR: BuildArea - NULL node.\n" ); + return -1; + } + + CNavArea *area = new CNavArea( nwNode, neNode, seNode, swNode ); + TheNavAreaList.push_back( area ); + + // since all internal nodes have the same attributes, set this area's attributes + area->SetAttributes( node->GetAttributes() ); + +// fprintf( fp, "f %d %d %d %d\n", nwNode->m_id, neNode->m_id, seNode->m_id, swNode->m_id ); + + return coveredNodes; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * For each ladder in the map, create a navigation representation of it. + */ +void BuildLadders( void ) +{ + // remove any left-over ladders + DestroyLadders(); + + TraceResult result; + CBaseEntity *entity = UTIL_FindEntityByClassname( NULL, "func_ladder" ); + while( entity && !FNullEnt( entity->edict() ) ) + { + CNavLadder *ladder = new CNavLadder; + + // compute top & bottom of ladder + ladder->m_top.x = (entity->pev->absmin.x + entity->pev->absmax.x) / 2.0f; + ladder->m_top.y = (entity->pev->absmin.y + entity->pev->absmax.y) / 2.0f; + ladder->m_top.z = entity->pev->absmax.z; + + ladder->m_bottom.x = ladder->m_top.x; + ladder->m_bottom.y = ladder->m_top.y; + ladder->m_bottom.z = entity->pev->absmin.z; + + // determine facing - assumes "normal" runged ladder + float xSize = entity->pev->absmax.x - entity->pev->absmin.x; + float ySize = entity->pev->absmax.y - entity->pev->absmin.y; + if (xSize > ySize) + { + // ladder is facing north or south - determine which way + // "pull in" traceline from bottom and top in case ladder abuts floor and/or ceiling + Vector from = ladder->m_bottom + Vector( 0.0f, GenerationStepSize, GenerationStepSize ); + Vector to = ladder->m_top + Vector( 0.0f, GenerationStepSize, -GenerationStepSize ); + + UTIL_TraceLine( from, to, ignore_monsters, ENT( entity->pev ), &result ); + + if (result.flFraction != 1.0f || result.fStartSolid) + ladder->m_dir = NORTH; + else + ladder->m_dir = SOUTH; + } + else + { + // ladder is facing east or west - determine which way + Vector from = ladder->m_bottom + Vector( GenerationStepSize, 0.0f, GenerationStepSize ); + Vector to = ladder->m_top + Vector( GenerationStepSize, 0.0f, -GenerationStepSize ); + + UTIL_TraceLine( from, to, ignore_monsters, ENT( entity->pev ), &result ); + + if (result.flFraction != 1.0f || result.fStartSolid) + ladder->m_dir = WEST; + else + ladder->m_dir = EAST; + } + + // adjust top and bottom of ladder to make sure they are reachable + // (cs_office has a crate right in front of the base of a ladder) + Vector along = ladder->m_top - ladder->m_bottom; + float length = along.NormalizeInPlace(); + Vector on, out; + const float minLadderClearance = 32.0f; + + // adjust bottom to bypass blockages + const float inc = 10.0f; + float t; + for( t = 0.0f; t <= length; t += inc ) + { + on = ladder->m_bottom + t * along; + + out = on; + AddDirectionVector( &out, ladder->m_dir, minLadderClearance ); + + UTIL_TraceLine( on, out, ignore_monsters, ENT( entity->pev ), &result ); + + if (result.flFraction == 1.0f && !result.fStartSolid) + { + // found viable ladder bottom + ladder->m_bottom = on; + break; + } + } + + // adjust top to bypass blockages + for( t = 0.0f; t <= length; t += inc ) + { + on = ladder->m_top - t * along; + + out = on; + AddDirectionVector( &out, ladder->m_dir, minLadderClearance ); + + UTIL_TraceLine( on, out, ignore_monsters, ENT( entity->pev ), &result ); + + if (result.flFraction == 1.0f && !result.fStartSolid) + { + // found viable ladder top + ladder->m_top = on; + break; + } + } + + ladder->m_length = (ladder->m_top - ladder->m_bottom).Length(); + + DirectionToVector2D( ladder->m_dir, &ladder->m_dirVector ); + + ladder->m_entity = entity; + const float nearLadderRange = 75.0f; // 50 + + // + // Find naviagtion area at bottom of ladder + // + + // get approximate postion of player on ladder + Vector center = ladder->m_bottom + Vector( 0, 0, GenerationStepSize ); + AddDirectionVector( ¢er, ladder->m_dir, HalfHumanWidth ); + + ladder->m_bottomArea = TheNavAreaGrid.GetNearestNavArea( ¢er, true ); + if (!ladder->m_bottomArea) + { + ALERT( at_console, "ERROR: Unconnected ladder bottom at ( %g, %g, %g )\n", ladder->m_bottom.x, ladder->m_bottom.y, ladder->m_bottom.z ); + } + else + { + // store reference to ladder in the area + ladder->m_bottomArea->AddLadderUp( ladder ); + } + + // + // Find adjacent navigation areas at the top of the ladder + // + + // get approximate postion of player on ladder + center = ladder->m_top + Vector( 0, 0, GenerationStepSize ); + AddDirectionVector( ¢er, ladder->m_dir, HalfHumanWidth ); + + // find "ahead" area + ladder->m_topForwardArea = FindFirstAreaInDirection( ¢er, OppositeDirection( ladder->m_dir ), nearLadderRange, 120.0f, entity ); + if (ladder->m_topForwardArea == ladder->m_bottomArea) + ladder->m_topForwardArea = NULL; + + // find "left" area + ladder->m_topLeftArea = FindFirstAreaInDirection( ¢er, DirectionLeft( ladder->m_dir ), nearLadderRange, 120.0f, entity ); + if (ladder->m_topLeftArea == ladder->m_bottomArea) + ladder->m_topLeftArea = NULL; + + // find "right" area + ladder->m_topRightArea = FindFirstAreaInDirection( ¢er, DirectionRight( ladder->m_dir ), nearLadderRange, 120.0f, entity ); + if (ladder->m_topRightArea == ladder->m_bottomArea) + ladder->m_topRightArea = NULL; + + // find "behind" area - must look farther, since ladder is against the wall away from this area + ladder->m_topBehindArea = FindFirstAreaInDirection( ¢er, ladder->m_dir, 2.0f*nearLadderRange, 120.0f, entity ); + if (ladder->m_topBehindArea == ladder->m_bottomArea) + ladder->m_topBehindArea = NULL; + + // can't include behind area, since it is not used when going up a ladder + if (!ladder->m_topForwardArea && !ladder->m_topLeftArea && !ladder->m_topRightArea) + ALERT( at_console, "ERROR: Unconnected ladder top at ( %g, %g, %g )\n", ladder->m_top.x, ladder->m_top.y, ladder->m_top.z ); + + // store reference to ladder in the area(s) + if (ladder->m_topForwardArea) + ladder->m_topForwardArea->AddLadderDown( ladder ); + + if (ladder->m_topLeftArea) + ladder->m_topLeftArea->AddLadderDown( ladder ); + + if (ladder->m_topRightArea) + ladder->m_topRightArea->AddLadderDown( ladder ); + + if (ladder->m_topBehindArea) + ladder->m_topBehindArea->AddLadderDown( ladder ); + + // adjust top of ladder to highest connected area + float topZ = -99999.9f; + bool topAdjusted = false; + CNavArea *topAreaList[4]; + topAreaList[0] = ladder->m_topForwardArea; + topAreaList[1] = ladder->m_topLeftArea; + topAreaList[2] = ladder->m_topRightArea; + topAreaList[3] = ladder->m_topBehindArea; + + for( int a=0; a<4; ++a ) + { + CNavArea *topArea = topAreaList[a]; + if (topArea == NULL) + continue; + + Vector close; + topArea->GetClosestPointOnArea( &ladder->m_top, &close ); + if (topZ < close.z) + { + topZ = close.z; + topAdjusted = true; + } + } + + if (topAdjusted) + ladder->m_top.z = topZ; + + // + // Determine whether this ladder is "dangling" or not + // "Dangling" ladders are too high to go up + // + ladder->m_isDangling = false; + if (ladder->m_bottomArea) + { + Vector bottomSpot; + ladder->m_bottomArea->GetClosestPointOnArea( &ladder->m_bottom, &bottomSpot ); + if (ladder->m_bottom.z - bottomSpot.z > HumanHeight) + ladder->m_isDangling = true; + } + + // add ladder to global list + TheNavLadderList.push_back( ladder ); + + entity = UTIL_FindEntityByClassname( entity, "func_ladder" ); + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Mark all areas that require a jump to get through them. + * This currently relies on jump areas having extreme slope. + */ +void MarkJumpAreas( void ) +{ + for( NavAreaList::iterator iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter ) + { + CNavArea *area = *iter; + Vector u, v; + + // compute our unit surface normal + u.x = area->m_extent.hi.x - area->m_extent.lo.x; + u.y = 0.0f; + u.z = area->m_neZ - area->m_extent.lo.z; + + v.x = 0.0f; + v.y = area->m_extent.hi.y - area->m_extent.lo.y; + v.z = area->m_swZ - area->m_extent.lo.z; + + Vector normal = CrossProduct( u, v ); + normal.NormalizeInPlace(); + + if (normal.z < MaxUnitZSlope) + area->SetAttributes( area->GetAttributes() | NAV_JUMP ); + } +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * This function uses the CNavNodes that have been sampled from the map to + * generate CNavAreas - rectangular areas of "walkable" space. These areas + * are connected to each other, allowing the AI to know how to move from + * area to area. + * + * This is a "greedy" algorithm that attempts to cover the walkable area + * with the fewest, largest, rectangles. + */ +void GenerateNavigationAreaMesh( void ) +{ + // haven't yet seen a map use larger than 30... + int tryWidth = 50; + int tryHeight = 50; + int uncoveredNodes = CNavNode::GetListLength(); + + while( uncoveredNodes > 0 ) + { + for( CNavNode *node = CNavNode::GetFirst(); node; node = node->GetNext() ) + { + if (node->IsCovered()) + continue; + + if (TestArea( node, tryWidth, tryHeight )) + { + int covered = BuildArea( node, tryWidth, tryHeight ); + if (covered < 0) + { + CONSOLE_ECHO( "GenerateNavigationAreaMesh: Error - Data corrupt.\n" ); + return; + } + + uncoveredNodes -= covered; + } + } + + if (tryWidth >= tryHeight) + --tryWidth; + else + --tryHeight; + + if (tryWidth <= 0 || tryHeight <= 0) + break; + } + + Extent extent; + extent.lo.x = 9999999999.9f; + extent.lo.y = 9999999999.9f; + extent.hi.x = -9999999999.9f; + extent.hi.y = -9999999999.9f; + + // compute total extent + NavAreaList::iterator iter; + for( iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter ) + { + CNavArea *area = *iter; + const Extent *areaExtent = area->GetExtent(); + + if (areaExtent->lo.x < extent.lo.x) + extent.lo.x = areaExtent->lo.x; + if (areaExtent->lo.y < extent.lo.y) + extent.lo.y = areaExtent->lo.y; + if (areaExtent->hi.x > extent.hi.x) + extent.hi.x = areaExtent->hi.x; + if (areaExtent->hi.y > extent.hi.y) + extent.hi.y = areaExtent->hi.y; + } + + // add the areas to the grid + TheNavAreaGrid.Initialize( extent.lo.x, extent.hi.x, extent.lo.y, extent.hi.y ); + + for( iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter ) + TheNavAreaGrid.AddNavArea( *iter ); + + + ConnectGeneratedAreas(); + MergeGeneratedAreas(); + SquareUpAreas(); + MarkJumpAreas(); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if 'pos' is within 2D extents of area. + */ +bool CNavArea::IsOverlapping( const Vector *pos ) const +{ + if (pos->x >= m_extent.lo.x && pos->x <= m_extent.hi.x && + pos->y >= m_extent.lo.y && pos->y <= m_extent.hi.y) + return true; + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if 'area' overlaps our 2D extents + */ +bool CNavArea::IsOverlapping( const CNavArea *area ) const +{ + if (area->m_extent.lo.x < m_extent.hi.x && area->m_extent.hi.x > m_extent.lo.x && + area->m_extent.lo.y < m_extent.hi.y && area->m_extent.hi.y > m_extent.lo.y) + return true; + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if 'area' overlaps our X extent + */ +bool CNavArea::IsOverlappingX( const CNavArea *area ) const +{ + if (area->m_extent.lo.x < m_extent.hi.x && area->m_extent.hi.x > m_extent.lo.x) + return true; + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if 'area' overlaps our Y extent + */ +bool CNavArea::IsOverlappingY( const CNavArea *area ) const +{ + if (area->m_extent.lo.y < m_extent.hi.y && area->m_extent.hi.y > m_extent.lo.y) + return true; + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if given point is on or above this area, but no others + */ +bool CNavArea::Contains( const Vector *pos ) const +{ + // check 2D overlap + if (!IsOverlapping( pos )) + return false; + + // the point overlaps us, check that it is above us, but not above any areas that overlap us + float ourZ = GetZ( pos ); + + // if we are above this point, fail + if (ourZ > pos->z) + return false; + + for( NavAreaList::const_iterator iter = m_overlapList.begin(); iter != m_overlapList.end(); ++iter ) + { + const CNavArea *area = *iter; + + // skip self + if (area == this) + continue; + + // check 2D overlap + if (!area->IsOverlapping( pos )) + continue; + + float theirZ = area->GetZ( pos ); + if (theirZ > pos->z) + { + // they are above the point + continue; + } + + if (theirZ > ourZ) + { + // we are below an area that is closer underneath the point + return false; + } + } + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if this area and given area are approximately co-planar + */ +bool CNavArea::IsCoplanar( const CNavArea *area ) const +{ + Vector u, v; + + // compute our unit surface normal + u.x = m_extent.hi.x - m_extent.lo.x; + u.y = 0.0f; + u.z = m_neZ - m_extent.lo.z; + + v.x = 0.0f; + v.y = m_extent.hi.y - m_extent.lo.y; + v.z = m_swZ - m_extent.lo.z; + + Vector normal = CrossProduct( u, v ); + normal.NormalizeInPlace(); + + + // compute their unit surface normal + u.x = area->m_extent.hi.x - area->m_extent.lo.x; + u.y = 0.0f; + u.z = area->m_neZ - area->m_extent.lo.z; + + v.x = 0.0f; + v.y = area->m_extent.hi.y - area->m_extent.lo.y; + v.z = area->m_swZ - area->m_extent.lo.z; + + Vector otherNormal = CrossProduct( u, v ); + otherNormal.NormalizeInPlace(); + + // can only merge areas that are nearly planar, to ensure areas do not differ from underlying geometry much + const float tolerance = 0.99f; // 0.7071f; // 0.9 + if (DotProduct( normal, otherNormal ) > tolerance) + return true; + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return Z of area at (x,y) of 'pos' + * Trilinear interpolation of Z values at quad edges. + * NOTE: pos->z is not used. + */ +float CNavArea::GetZ( const Vector *pos ) const +{ + float dx = m_extent.hi.x - m_extent.lo.x; + float dy = m_extent.hi.y - m_extent.lo.y; + + // guard against division by zero due to degenerate areas + if (dx == 0.0f || dy == 0.0f) + return m_neZ; + + float u = (pos->x - m_extent.lo.x) / dx; + float v = (pos->y - m_extent.lo.y) / dy; + + // clamp Z values to (x,y) volume + if (u < 0.0f) + u = 0.0f; + else if (u > 1.0f) + u = 1.0f; + + if (v < 0.0f) + v = 0.0f; + else if (v > 1.0f) + v = 1.0f; + + float northZ = m_extent.lo.z + u * (m_neZ - m_extent.lo.z); + float southZ = m_swZ + u * (m_extent.hi.z - m_swZ); + + return northZ + v * (southZ - northZ); +} + +float CNavArea::GetZ( float x, float y ) const +{ + Vector pos( x, y, 0.0f ); + return GetZ( &pos ); +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return closest point to 'pos' on 'area'. + * Returned point is in 'close'. + */ +void CNavArea::GetClosestPointOnArea( const Vector *pos, Vector *close ) const +{ + const Extent *extent = GetExtent(); + + if (pos->x < extent->lo.x) + { + if (pos->y < extent->lo.y) + { + // position is north-west of area + *close = extent->lo; + } + else if (pos->y > extent->hi.y) + { + // position is south-west of area + close->x = extent->lo.x; + close->y = extent->hi.y; + } + else + { + // position is west of area + close->x = extent->lo.x; + close->y = pos->y; + } + } + else if (pos->x > extent->hi.x) + { + if (pos->y < extent->lo.y) + { + // position is north-east of area + close->x = extent->hi.x; + close->y = extent->lo.y; + } + else if (pos->y > extent->hi.y) + { + // position is south-east of area + *close = extent->hi; + } + else + { + // position is east of area + close->x = extent->hi.x; + close->y = pos->y; + } + } + else if (pos->y < extent->lo.y) + { + // position is north of area + close->x = pos->x; + close->y = extent->lo.y; + } + else if (pos->y > extent->hi.y) + { + // position is south of area + close->x = pos->x; + close->y = extent->hi.y; + } + else + { + // position is inside of area - it is the 'closest point' to itself + *close = *pos; + } + + close->z = GetZ( close ); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return shortest distance squared between point and this area + */ +float CNavArea::GetDistanceSquaredToPoint( const Vector *pos ) const +{ + const Extent *extent = GetExtent(); + + if (pos->x < extent->lo.x) + { + if (pos->y < extent->lo.y) + { + // position is north-west of area + return (extent->lo - *pos).LengthSquared(); + } + else if (pos->y > extent->hi.y) + { + // position is south-west of area + Vector d; + d.x = extent->lo.x - pos->x; + d.y = extent->hi.y - pos->y; + d.z = m_swZ - pos->z; + return d.LengthSquared(); + } + else + { + // position is west of area + float d = extent->lo.x - pos->x; + return d * d; + } + } + else if (pos->x > extent->hi.x) + { + if (pos->y < extent->lo.y) + { + // position is north-east of area + Vector d; + d.x = extent->hi.x - pos->x; + d.y = extent->lo.y - pos->y; + d.z = m_neZ - pos->z; + return d.LengthSquared(); + } + else if (pos->y > extent->hi.y) + { + // position is south-east of area + return (extent->hi - *pos).LengthSquared(); + } + else + { + // position is east of area + float d = pos->z - extent->hi.x; + return d * d; + } + } + else if (pos->y < extent->lo.y) + { + // position is north of area + float d = extent->lo.y - pos->y; + return d * d; + } + else if (pos->y > extent->hi.y) + { + // position is south of area + float d = pos->y - extent->hi.y; + return d * d; + } + else + { + // position is inside of 2D extent of area - find delta Z + float z = GetZ( pos ); + float d = z - pos->z; + return d * d; + } +} + + + +//-------------------------------------------------------------------------------------------------------------- +CNavArea *CNavArea::GetRandomAdjacentArea( NavDirType dir ) const +{ + int count = m_connect[ dir ].size(); + int which = RANDOM_LONG( 0, count-1 ); + + int i = 0; + NavConnectList::const_iterator iter; + for( iter = m_connect[ dir ].begin(); iter != m_connect[ dir ].end(); ++iter ) + { + if (i == which) + return (*iter).area; + + i++; + } + + return NULL; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Compute "portal" between to adjacent areas. + * Return center of portal opening, and half-width defining sides of portal from center. + * NOTE: center->z is unset. + */ +void CNavArea::ComputePortal( const CNavArea *to, NavDirType dir, Vector *center, float *halfWidth ) const +{ + if (dir == NORTH || dir == SOUTH) + { + if (dir == NORTH) + center->y = m_extent.lo.y; + else + center->y = m_extent.hi.y; + + float left = max( m_extent.lo.x, to->m_extent.lo.x ); + float right = min( m_extent.hi.x, to->m_extent.hi.x ); + + // clamp to our extent in case areas are disjoint + if (left < m_extent.lo.x) + left = m_extent.lo.x; + else if (left > m_extent.hi.x) + left = m_extent.hi.x; + + if (right < m_extent.lo.x) + right = m_extent.lo.x; + else if (right > m_extent.hi.x) + right = m_extent.hi.x; + + center->x = (left + right)/2.0f; + *halfWidth = (right - left)/2.0f; + } + else // EAST or WEST + { + if (dir == WEST) + center->x = m_extent.lo.x; + else + center->x = m_extent.hi.x; + + float top = max( m_extent.lo.y, to->m_extent.lo.y ); + float bottom = min( m_extent.hi.y, to->m_extent.hi.y ); + + // clamp to our extent in case areas are disjoint + if (top < m_extent.lo.y) + top = m_extent.lo.y; + else if (top > m_extent.hi.y) + top = m_extent.hi.y; + + if (bottom < m_extent.lo.y) + bottom = m_extent.lo.y; + else if (bottom > m_extent.hi.y) + bottom = m_extent.hi.y; + + center->y = (top + bottom)/2.0f; + *halfWidth = (bottom - top)/2.0f; + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Compute closest point within the "portal" between to adjacent areas. + */ +void CNavArea::ComputeClosestPointInPortal( const CNavArea *to, NavDirType dir, const Vector *fromPos, Vector *closePos ) const +{ + const float margin = GenerationStepSize/2.0f; + + if (dir == NORTH || dir == SOUTH) + { + if (dir == NORTH) + closePos->y = m_extent.lo.y; + else + closePos->y = m_extent.hi.y; + + float left = max( m_extent.lo.x, to->m_extent.lo.x ); + float right = min( m_extent.hi.x, to->m_extent.hi.x ); + + // clamp to our extent in case areas are disjoint + if (left < m_extent.lo.x) + left = m_extent.lo.x; + else if (left > m_extent.hi.x) + left = m_extent.hi.x; + + if (right < m_extent.lo.x) + right = m_extent.lo.x; + else if (right > m_extent.hi.x) + right = m_extent.hi.x; + + // keep margin if against edge + const float leftMargin = (to->IsEdge( WEST )) ? (left + margin) : left; + const float rightMargin = (to->IsEdge( EAST )) ? (right - margin) : right; + + // limit x to within portal + if (fromPos->x < leftMargin) + closePos->x = leftMargin; + else if (fromPos->x > rightMargin) + closePos->x = rightMargin; + else + closePos->x = fromPos->x; + + } + else // EAST or WEST + { + if (dir == WEST) + closePos->x = m_extent.lo.x; + else + closePos->x = m_extent.hi.x; + + float top = max( m_extent.lo.y, to->m_extent.lo.y ); + float bottom = min( m_extent.hi.y, to->m_extent.hi.y ); + + // clamp to our extent in case areas are disjoint + if (top < m_extent.lo.y) + top = m_extent.lo.y; + else if (top > m_extent.hi.y) + top = m_extent.hi.y; + + if (bottom < m_extent.lo.y) + bottom = m_extent.lo.y; + else if (bottom > m_extent.hi.y) + bottom = m_extent.hi.y; + + // keep margin if against edge + const float topMargin = (to->IsEdge( NORTH )) ? (top + margin) : top; + const float bottomMargin = (to->IsEdge( SOUTH )) ? (bottom - margin) : bottom; + + // limit y to within portal + if (fromPos->y < topMargin) + closePos->y = topMargin; + else if (fromPos->y > bottomMargin) + closePos->y = bottomMargin; + else + closePos->y = fromPos->y; + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if there are no bi-directional links on the given side + */ +bool CNavArea::IsEdge( NavDirType dir ) const +{ + for( NavConnectList::const_iterator it = m_connect[ dir ].begin(); it != m_connect[ dir ].end(); ++it ) + { + const NavConnect connect = *it; + + if (connect.area->IsConnected( this, OppositeDirection( dir ) )) + return false; + } + + return true; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return direction from this area to the given point + */ +NavDirType CNavArea::ComputeDirection( Vector *point ) const +{ + if (point->x >= m_extent.lo.x && point->x <= m_extent.hi.x) + { + if (point->y < m_extent.lo.y) + return NORTH; + else if (point->y > m_extent.hi.y) + return SOUTH; + } + else if (point->y >= m_extent.lo.y && point->y <= m_extent.hi.y) + { + if (point->x < m_extent.lo.x) + return WEST; + else if (point->x > m_extent.hi.x) + return EAST; + } + + // find closest direction + Vector to = *point - m_center; + + if (abs(to.x) > abs(to.y)) + { + if (to.x > 0.0f) + return EAST; + return WEST; + } + else + { + if (to.y > 0.0f) + return SOUTH; + return NORTH; + } + + return NUM_DIRECTIONS; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Draw area for debugging + */ +void CNavArea::Draw( byte red, byte green, byte blue, int duration ) +{ + Vector nw, ne, sw, se; + + nw = m_extent.lo; + se = m_extent.hi; + ne.x = se.x; + ne.y = nw.y; + ne.z = m_neZ; + sw.x = nw.x; + sw.y = se.y; + sw.z = m_swZ; + + nw.z += cv_bot_nav_zdraw.value; + ne.z += cv_bot_nav_zdraw.value; + sw.z += cv_bot_nav_zdraw.value; + se.z += cv_bot_nav_zdraw.value; + + float border = 2.0f; + nw.x += border; + nw.y += border; + ne.x -= border; + ne.y += border; + sw.x += border; + sw.y -= border; + se.x -= border; + se.y -= border; + + UTIL_DrawBeamPoints( nw, ne, duration, red, green, blue ); + UTIL_DrawBeamPoints( ne, se, duration, red, green, blue ); + UTIL_DrawBeamPoints( se, sw, duration, red, green, blue ); + UTIL_DrawBeamPoints( sw, nw, duration, red, green, blue ); + + if (GetAttributes() & NAV_CROUCH) + UTIL_DrawBeamPoints( nw, se, duration, red, green, blue ); + + if (GetAttributes() & NAV_JUMP) + { + UTIL_DrawBeamPoints( nw, se, duration, red, green, blue ); + UTIL_DrawBeamPoints( ne, sw, duration, red, green, blue ); + } + + if (GetAttributes() & NAV_PRECISE) + { + float size = 8.0f; + Vector up( m_center.x, m_center.y - size, m_center.z + cv_bot_nav_zdraw.value ); + Vector down( m_center.x, m_center.y + size, m_center.z + cv_bot_nav_zdraw.value ); + UTIL_DrawBeamPoints( up, down, duration, red, green, blue ); + + Vector left( m_center.x - size, m_center.y, m_center.z + cv_bot_nav_zdraw.value ); + Vector right( m_center.x + size, m_center.y, m_center.z + cv_bot_nav_zdraw.value ); + UTIL_DrawBeamPoints( left, right, duration, red, green, blue ); + } + + if (GetAttributes() & NAV_NO_JUMP) + { + float size = 8.0f; + Vector up( m_center.x, m_center.y - size, m_center.z + cv_bot_nav_zdraw.value ); + Vector down( m_center.x, m_center.y + size, m_center.z + cv_bot_nav_zdraw.value ); + Vector left( m_center.x - size, m_center.y, m_center.z + cv_bot_nav_zdraw.value ); + Vector right( m_center.x + size, m_center.y, m_center.z + cv_bot_nav_zdraw.value ); + UTIL_DrawBeamPoints( up, right, duration, red, green, blue ); + UTIL_DrawBeamPoints( right, down, duration, red, green, blue ); + UTIL_DrawBeamPoints( down, left, duration, red, green, blue ); + UTIL_DrawBeamPoints( left, up, duration, red, green, blue ); + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Draw selected corner for debugging + */ +void CNavArea::DrawMarkedCorner( NavCornerType corner, byte red, byte green, byte blue, int duration ) +{ + Vector nw, ne, sw, se; + + nw = m_extent.lo; + se = m_extent.hi; + ne.x = se.x; + ne.y = nw.y; + ne.z = m_neZ; + sw.x = nw.x; + sw.y = se.y; + sw.z = m_swZ; + + nw.z += cv_bot_nav_zdraw.value; + ne.z += cv_bot_nav_zdraw.value; + sw.z += cv_bot_nav_zdraw.value; + se.z += cv_bot_nav_zdraw.value; + + float border = 2.0f; + nw.x += border; + nw.y += border; + ne.x -= border; + ne.y += border; + sw.x += border; + sw.y -= border; + se.x -= border; + se.y -= border; + + switch( corner ) + { + case NORTH_WEST: + UTIL_DrawBeamPoints( nw + Vector( 0, 0, 10 ), nw, duration, red, green, blue ); + break; + case NORTH_EAST: + UTIL_DrawBeamPoints( ne + Vector( 0, 0, 10 ), ne, duration, red, green, blue ); + break; + case SOUTH_EAST: + UTIL_DrawBeamPoints( se + Vector( 0, 0, 10 ), se, duration, red, green, blue ); + break; + case SOUTH_WEST: + UTIL_DrawBeamPoints( sw + Vector( 0, 0, 10 ), sw, duration, red, green, blue ); + break; + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Add to open list in decreasing value order + */ +void CNavArea::AddToOpenList( void ) +{ + // mark as being on open list for quick check + m_openMarker = m_masterMarker; + + // if list is empty, add and return + if (m_openList == NULL) + { + m_openList = this; + this->m_prevOpen = NULL; + this->m_nextOpen = NULL; + return; + } + + // insert self in ascending cost order + CNavArea *area, *last = NULL; + for( area = m_openList; area; area = area->m_nextOpen ) + { + if (this->GetTotalCost() < area->GetTotalCost()) + break; + + last = area; + } + + if (area) + { + // insert before this area + this->m_prevOpen = area->m_prevOpen; + if (this->m_prevOpen) + this->m_prevOpen->m_nextOpen = this; + else + m_openList = this; + + this->m_nextOpen = area; + area->m_prevOpen = this; + } + else + { + // append to end of list + last->m_nextOpen = this; + + this->m_prevOpen = last; + this->m_nextOpen = NULL; + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * A smaller value has been found, update this area on the open list + * @todo "bubbling" does unnecessary work, since the order of all other nodes will be unchanged - only this node is altered + */ +void CNavArea::UpdateOnOpenList( void ) +{ + // since value can only decrease, bubble this area up from current spot + while( m_prevOpen && + this->GetTotalCost() < m_prevOpen->GetTotalCost() ) + { + // swap position with predecessor + CNavArea *other = m_prevOpen; + CNavArea *before = other->m_prevOpen; + CNavArea *after = this->m_nextOpen; + + this->m_nextOpen = other; + this->m_prevOpen = before; + + other->m_prevOpen = this; + other->m_nextOpen = after; + + if (before) + before->m_nextOpen = this; + else + m_openList = this; + + if (after) + after->m_prevOpen = other; + } +} + +//-------------------------------------------------------------------------------------------------------------- +void CNavArea::RemoveFromOpenList( void ) +{ + if (m_prevOpen) + m_prevOpen->m_nextOpen = m_nextOpen; + else + m_openList = m_nextOpen; + + if (m_nextOpen) + m_nextOpen->m_prevOpen = m_prevOpen; + + // zero is an invalid marker + m_openMarker = 0; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Clears the open and closed lists for a new search + */ +void CNavArea::ClearSearchLists( void ) +{ + // effectively clears all open list pointers and closed flags + CNavArea::MakeNewMarker(); + + m_openList = NULL; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return the coordinates of the area's corner. + * NOTE: Do not retain the returned pointer - it is temporary. + */ +const Vector *CNavArea::GetCorner( NavCornerType corner ) const +{ + static Vector pos; + + switch( corner ) + { + case NORTH_WEST: + return &m_extent.lo; + + case NORTH_EAST: + pos.x = m_extent.hi.x; + pos.y = m_extent.lo.y; + pos.z = m_neZ; + return &pos; + + case SOUTH_WEST: + pos.x = m_extent.lo.x; + pos.y = m_extent.hi.y; + pos.z = m_swZ; + return &pos; + + case SOUTH_EAST: + return &m_extent.hi; + } + + return NULL; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Returns true if an existing hiding spot is too close to given position + */ +bool CNavArea::IsHidingSpotCollision( const Vector *pos ) const +{ + const float collisionRange = 30.0f; + + for( HidingSpotList::const_iterator iter = m_hidingSpotList.begin(); iter != m_hidingSpotList.end(); ++iter ) + { + const HidingSpot *spot = *iter; + + if ((*spot->GetPosition() - *pos).IsLengthLessThan( collisionRange )) + return true; + } + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +bool IsHidingSpotInCover( const Vector *spot ) +{ + int coverCount = 0; + TraceResult result; + + Vector from = *spot; + from.z += HalfHumanHeight; + + Vector to; + + // if we are crouched underneath something, that counts as good cover + to = from + Vector( 0, 0, 20.0f ); + UTIL_TraceLine( from, to, ignore_monsters, NULL, &result ); + if (result.flFraction != 1.0f) + return true; + + const float coverRange = 100.0f; + const float inc = M_PI / 8.0f; + + for( float angle = 0.0f; angle < 2.0f * M_PI; angle += inc ) + { + to = from + Vector( coverRange * cos(angle), coverRange * sin(angle), HalfHumanHeight ); + + UTIL_TraceLine( from, to, ignore_monsters, NULL, &result ); + + // if traceline hit something, it hit "cover" + if (result.flFraction != 1.0f) + ++coverCount; + } + + // if more than half of the circle has no cover, the spot is not "in cover" + const int halfCover = 8; + if (coverCount < halfCover) + return false; + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Analyze local area neighborhood to find "hiding spots" for this area + */ +void CNavArea::ComputeHidingSpots( void ) +{ + struct + { + float lo, hi; + } + extent; + + // "jump areas" cannot have hiding spots + if (GetAttributes() & NAV_JUMP) + return; + + int cornerCount[NUM_CORNERS]; + for( int i=0; iIsConnected( this, OppositeDirection( static_cast( d ) ) ) == false) + continue; + + // ignore jump areas + if (connect.area->GetAttributes() & NAV_JUMP) + continue; + + if (isHoriz) + { + if (connect.area->m_extent.lo.x < extent.lo) + extent.lo = connect.area->m_extent.lo.x; + + if (connect.area->m_extent.hi.x > extent.hi) + extent.hi = connect.area->m_extent.hi.x; + } + else + { + if (connect.area->m_extent.lo.y < extent.lo) + extent.lo = connect.area->m_extent.lo.y; + + if (connect.area->m_extent.hi.y > extent.hi) + extent.hi = connect.area->m_extent.hi.y; + } + } + + switch( d ) + { + case NORTH: + if (extent.lo - m_extent.lo.x >= cornerSize) + ++cornerCount[ NORTH_WEST ]; + + if (m_extent.hi.x - extent.hi >= cornerSize) + ++cornerCount[ NORTH_EAST ]; + break; + + case SOUTH: + if (extent.lo - m_extent.lo.x >= cornerSize) + ++cornerCount[ SOUTH_WEST ]; + + if (m_extent.hi.x - extent.hi >= cornerSize) + ++cornerCount[ SOUTH_EAST ]; + break; + + case EAST: + if (extent.lo - m_extent.lo.y >= cornerSize) + ++cornerCount[ NORTH_EAST ]; + + if (m_extent.hi.y - extent.hi >= cornerSize) + ++cornerCount[ SOUTH_EAST ]; + break; + + case WEST: + if (extent.lo - m_extent.lo.y >= cornerSize) + ++cornerCount[ NORTH_WEST ]; + + if (m_extent.hi.y - extent.hi >= cornerSize) + ++cornerCount[ SOUTH_WEST ]; + break; + } + } + + // if a corner count is 2, then it really is a corner (walls on both sides) + float offset = 12.5f; + + if (cornerCount[ NORTH_WEST ] == 2) + { + Vector pos = *GetCorner( NORTH_WEST ) + Vector( offset, offset, 0.0f ); + + m_hidingSpotList.push_back( new HidingSpot( &pos, (IsHidingSpotInCover( &pos )) ? HidingSpot::IN_COVER : 0 ) ); + } + + if (cornerCount[ NORTH_EAST ] == 2) + { + Vector pos = *GetCorner( NORTH_EAST ) + Vector( -offset, offset, 0.0f ); + if (!IsHidingSpotCollision( &pos )) + m_hidingSpotList.push_back( new HidingSpot( &pos, (IsHidingSpotInCover( &pos )) ? HidingSpot::IN_COVER : 0 ) ); + } + + if (cornerCount[ SOUTH_WEST ] == 2) + { + Vector pos = *GetCorner( SOUTH_WEST ) + Vector( offset, -offset, 0.0f ); + if (!IsHidingSpotCollision( &pos )) + m_hidingSpotList.push_back( new HidingSpot( &pos, (IsHidingSpotInCover( &pos )) ? HidingSpot::IN_COVER : 0 ) ); + } + + if (cornerCount[ SOUTH_EAST ] == 2) + { + Vector pos = *GetCorner( SOUTH_EAST ) + Vector( -offset, -offset, 0.0f ); + if (!IsHidingSpotCollision( &pos )) + m_hidingSpotList.push_back( new HidingSpot( &pos, (IsHidingSpotInCover( &pos )) ? HidingSpot::IN_COVER : 0 ) ); + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Determine how much walkable area we can see from the spot, and how far away we can see. + */ +void ClassifySniperSpot( HidingSpot *spot ) +{ + Vector eye = *spot->GetPosition() + Vector( 0, 0, HalfHumanHeight ); // assume we are crouching + Vector walkable; + TraceResult result; + + Extent sniperExtent; + float farthestRangeSq = 0.0f; + const float minSniperRangeSq = 1000.0f * 1000.0f; + bool found = false; + + for( NavAreaList::iterator iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter ) + { + CNavArea *area = *iter; + + const Extent *extent = area->GetExtent(); + + // scan this area + for( walkable.y = extent->lo.y + GenerationStepSize/2.0f; walkable.y < extent->hi.y; walkable.y += GenerationStepSize ) + { + for( walkable.x = extent->lo.x + GenerationStepSize/2.0f; walkable.x < extent->hi.x; walkable.x += GenerationStepSize ) + { + walkable.z = area->GetZ( &walkable ) + HalfHumanHeight; + + // check line of sight + UTIL_TraceLine( eye, walkable, ignore_monsters, ignore_glass, NULL, &result ); + + if (result.flFraction == 1.0f && !result.fStartSolid) + { + // can see this spot + + // keep track of how far we can see + float rangeSq = (eye - walkable).LengthSquared(); + if (rangeSq > farthestRangeSq) + { + farthestRangeSq = rangeSq; + + if (rangeSq >= minSniperRangeSq) + { + // this is a sniper spot + // determine how good of a sniper spot it is by keeping track of the snipable area + if (found) + { + if (walkable.x < sniperExtent.lo.x) + sniperExtent.lo.x = walkable.x; + if (walkable.x > sniperExtent.hi.x) + sniperExtent.hi.x = walkable.x; + + if (walkable.y < sniperExtent.lo.y) + sniperExtent.lo.y = walkable.y; + if (walkable.y > sniperExtent.hi.y) + sniperExtent.hi.y = walkable.y; + } + else + { + sniperExtent.lo = walkable; + sniperExtent.hi = walkable; + found = true; + } + } + } + } + } + } + } + + if (found) + { + // if we can see a large snipable area, it is an "ideal" spot + float snipableArea = sniperExtent.Area(); + + const float minIdealSniperArea = 200.0f * 200.0f; + const float longSniperRangeSq = 1500.0f * 1500.0f; + + if (snipableArea >= minIdealSniperArea || farthestRangeSq >= longSniperRangeSq) + spot->SetFlags( HidingSpot::IDEAL_SNIPER_SPOT ); + else + spot->SetFlags( HidingSpot::GOOD_SNIPER_SPOT ); + } +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Analyze local area neighborhood to find "sniper spots" for this area + */ +void CNavArea::ComputeSniperSpots( void ) +{ + if (cv_bot_quicksave.value > 0.0f) + return; + + for( HidingSpotList::iterator iter = m_hidingSpotList.begin(); iter != m_hidingSpotList.end(); ++iter ) + { + HidingSpot *spot = *iter; + + ClassifySniperSpot( spot ); + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Given the areas we are moving between, return the spots we will encounter + */ +SpotEncounter *CNavArea::GetSpotEncounter( const CNavArea *from, const CNavArea *to ) +{ + if (from && to) + { + SpotEncounter *e; + + for( SpotEncounterList::iterator iter = m_spotEncounterList.begin(); iter != m_spotEncounterList.end(); ++iter ) + { + e = &(*iter); + + if (e->from.area == from && e->to.area == to) + return e; + } + } + + return NULL; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Add spot encounter data when moving from area to area + */ +void CNavArea::AddSpotEncounters( const CNavArea *from, NavDirType fromDir, const CNavArea *to, NavDirType toDir ) +{ + SpotEncounter e; + + e.from.area = const_cast( from ); + e.fromDir = fromDir; + + e.to.area = const_cast( to ); + e.toDir = toDir; + + float halfWidth; + ComputePortal( to, toDir, &e.path.to, &halfWidth ); + ComputePortal( from, fromDir, &e.path.from, &halfWidth ); + + const float eyeHeight = HalfHumanHeight; + e.path.from.z = from->GetZ( &e.path.from ) + eyeHeight; + e.path.to.z = to->GetZ( &e.path.to ) + eyeHeight; + + // step along ray and track which spots can be seen + Vector dir = e.path.to - e.path.from; + float length = dir.NormalizeInPlace(); + + // create unique marker to flag used spots + HidingSpot::ChangeMasterMarker(); + + const float stepSize = 25.0f; // 50 + const float seeSpotRange = 2000.0f; // 3000 + TraceResult result; + + Vector eye, delta; + HidingSpot *spot; + SpotOrder spotOrder; + + // step along path thru this area + bool done = false; + for( float along = 0.0f; !done; along += stepSize ) + { + // make sure we check the endpoint of the path segment + if (along >= length) + { + along = length; + done = true; + } + + // move the eyepoint along the path segment + eye = e.path.from + along * dir; + + // check each hiding spot for visibility + for( HidingSpotList::iterator iter = TheHidingSpotList.begin(); iter != TheHidingSpotList.end(); ++iter ) + { + spot = *iter; + + // only look at spots with cover (others are out in the open and easily seen) + if (!spot->HasGoodCover()) + continue; + + if (spot->IsMarked()) + continue; + + const Vector *spotPos = spot->GetPosition(); + + delta.x = spotPos->x - eye.x; + delta.y = spotPos->y - eye.y; + delta.z = (spotPos->z + eyeHeight) - eye.z; + + // check if in range + if (delta.IsLengthGreaterThan( seeSpotRange )) + continue; + + // check if we have LOS + UTIL_TraceLine( eye, Vector( spotPos->x, spotPos->y, spotPos->z + HalfHumanHeight ), ignore_monsters, ignore_glass, NULL, &result ); + if (result.flFraction != 1.0f) + continue; + + // if spot is in front of us along our path, ignore it + delta.NormalizeInPlace(); + float dot = DotProduct( dir, delta ); + if (dot < 0.7071f && dot > -0.7071f) + { + // we only want to keep spots that BECOME visible as we walk past them + // therefore, skip ALL visible spots at the start of the path segment + if (along > 0.0f) + { + // add spot to encounter + spotOrder.spot = spot; + spotOrder.t = along/length; + e.spotList.push_back( spotOrder ); + } + } + + // mark spot as encountered + spot->Mark(); + } + } + + // add encounter to list + m_spotEncounterList.push_back( e ); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Compute "spot encounter" data. This is an ordered list of spots to look at + * for each possible path thru a nav area. + */ +void CNavArea::ComputeSpotEncounters( void ) +{ + m_spotEncounterList.clear(); + + if (cv_bot_quicksave.value > 0.0f) + return; + + // for each adjacent area + for( int fromDir=0; fromDirarea, (NavDirType)fromDir, toCon->area, (NavDirType)toDir ); + } + } + } + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Decay the danger values + */ +void CNavArea::DecayDanger( void ) +{ + // one kill == 1.0, which we will forget about in two minutes + const float decayRate = 1.0f / 120.0f; + + for( int i=0; itime - m_dangerTimestamp[i]; + float decayAmount = decayRate * deltaT; + + m_danger[i] -= decayAmount; + if (m_danger[i] < 0.0f) + m_danger[i] = 0.0f; + + // update timestamp + m_dangerTimestamp[i] = gpGlobals->time; + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Increase the danger of this area for the given team + */ +void CNavArea::IncreaseDanger( int teamID, float amount ) +{ + // before we add the new value, decay what's there + DecayDanger(); + + m_danger[ teamID ] += amount; + m_dangerTimestamp[ teamID ] = gpGlobals->time; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return the danger of this area (decays over time) + */ +float CNavArea::GetDanger( int teamID ) +{ + DecayDanger(); + return m_danger[ teamID ]; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Increase the danger of nav areas containing and near the given position + */ +void IncreaseDangerNearby( int teamID, float amount, CNavArea *startArea, const Vector *pos, float maxRadius ) +{ + if (startArea == NULL) + return; + + CNavArea::MakeNewMarker(); + CNavArea::ClearSearchLists(); + + startArea->AddToOpenList(); + startArea->SetTotalCost( 0.0f ); + startArea->Mark(); + startArea->IncreaseDanger( teamID, amount ); + + while( !CNavArea::IsOpenListEmpty() ) + { + // get next area to check + CNavArea *area = CNavArea::PopOpenList(); + + // area has no hiding spots, explore adjacent areas + for( int dir=0; dirGetAdjacentCount( (NavDirType)dir ); + for( int i=0; iGetAdjacentArea( (NavDirType)dir, i ); + + if (!adjArea->IsMarked()) + { + // compute distance from danger source + float cost = (*adjArea->GetCenter() - *pos).Length(); + if (cost <= maxRadius) + { + adjArea->AddToOpenList(); + adjArea->SetTotalCost( cost ); + adjArea->Mark(); + adjArea->IncreaseDanger( teamID, amount * cost/maxRadius ); + } + } + } + } + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Show danger levels for debugging + */ +void DrawDanger( void ) +{ + for( NavAreaList::iterator iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter ) + { + CNavArea *area = *iter; + + Vector center = *area->GetCenter(); + Vector top; + center.z = area->GetZ( ¢er ); + + float danger = area->GetDanger( 0 ); + if (danger > 0.1f) + { + top.x = center.x; + top.y = center.y; + top.z = center.z + 10.0f * danger; + UTIL_DrawBeamPoints( center, top, 3.0f, 255, 0, 0 ); + } + + danger = area->GetDanger( 1 ); + if (danger > 0.1f) + { + top.x = center.x; + top.y = center.y; + top.z = center.z + 10.0f * danger; + UTIL_DrawBeamPoints( center, top, 3.0f, 0, 0, 255 ); + } + } +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * If a player is at the given spot, return true + */ +bool IsSpotOccupied( CBaseEntity *me, const Vector *pos ) +{ + const float closeRange = 75.0f; // 50 + + // is there a player in this spot + float range; + CBasePlayer *player = UTIL_GetClosestPlayer( pos, &range ); + + if (player != me) + { + if (player && range < closeRange) + return true; + } + + // is there is a hostage in this spot + if (g_pHostages) + { + CHostage *hostage = g_pHostages->GetClosestHostage( *pos, &range ); + if (hostage && hostage != me && range < closeRange) + return true; + } + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +class CollectHidingSpotsFunctor +{ +public: + CollectHidingSpotsFunctor( CBaseEntity *me, const Vector *origin, float range, unsigned char flags, Place place = UNDEFINED_PLACE, bool useCrouchAreas = true ) + { + m_me = me; + m_count = 0; + m_origin = origin; + m_range = range; + m_flags = flags; + m_place = place; + m_useCrouchAreas = useCrouchAreas; + } + + enum { MAX_SPOTS = 256 }; + + bool operator() ( CNavArea *area ) + { + // if a place is specified, only consider hiding spots from areas in that place + if (m_place != UNDEFINED_PLACE && area->GetPlace() != m_place) + return true; + + // collect all the hiding spots in this area + const HidingSpotList *list = area->GetHidingSpotList(); + + for( HidingSpotList::const_iterator iter = list->begin(); iter != list->end() && m_count < MAX_SPOTS; ++iter ) + { + const HidingSpot *spot = *iter; + + if (m_useCrouchAreas == false) + { + CNavArea *area = TheNavAreaGrid.GetNavArea( spot->GetPosition() ); + if (area && (area->GetAttributes() & NAV_CROUCH)) + continue; + } + + // make sure hiding spot is in range + if (m_range > 0.0f) + if ((*spot->GetPosition() - *m_origin).IsLengthGreaterThan( m_range )) + continue; + + // if a Player is using this hiding spot, don't consider it + if (IsSpotOccupied( m_me, spot->GetPosition() )) + { + // player is in hiding spot + /// @todo Check if player is moving or sitting still + continue; + } + + // only collect hiding spots with matching flags + if (m_flags & spot->GetFlags()) + { + m_hidingSpot[ m_count++ ] = spot->GetPosition(); + } + } + + // if we've filled up, stop searching + if (m_count == MAX_SPOTS) + return false; + + return true; + } + + /** + * Remove the spot at index "i" + */ + void RemoveSpot( int i ) + { + if (m_count == 0) + return; + + for( int j=i+1; jmaxClients; ++p ) + { + CBasePlayer *player = static_cast( UTIL_PlayerByIndex( p ) ); + + if (!IsEntityValid( player )) + continue; + + if (player == ignore) + continue; + + if (!player->IsAlive()) + continue; + + if (ignoreTeam && player->m_iTeam == ignoreTeam) + continue; + + // compute player's unit aiming vector + UTIL_MakeVectors( player->pev->v_angle + player->pev->punchangle ); + + const float longRange = 5000.0f; + Vector playerTarget = player->pev->origin + longRange * gpGlobals->v_forward; + + Vector result; + if (IsIntersecting2D( start, finish, player->pev->origin, playerTarget, &result )) + { + // simple check to see if intersection lies in the Z range of the path + float loZ, hiZ; + + if (start.z < finish.z) + { + loZ = start.z; + hiZ = finish.z; + } + else + { + loZ = finish.z; + hiZ = start.z; + } + + if (result.z >= loZ && result.z <= hiZ + HumanHeight) + return true; + } + } + + return false; +} + +//-------------------------------------------------------------------------------------------------------------------- +/** + * Select a nearby retreat spot. + * Don't pick a hiding spot that a Player is currently occupying. + * If "avoidTeam" is nonzero, avoid getting close to members of that team. + */ +const Vector *FindNearbyRetreatSpot( CBaseEntity *me, const Vector *start, CNavArea *startArea, float maxRange, int avoidTeam, bool useCrouchAreas ) +{ + if (startArea == NULL) + return NULL; + + // collect hiding spots with decent "cover" + CollectHidingSpotsFunctor collector( me, start, maxRange, HidingSpot::IN_COVER, UNDEFINED_PLACE, useCrouchAreas ); + SearchSurroundingAreas( startArea, start, collector, maxRange ); + + if (collector.m_count == 0) + return NULL; + + // find the closest unoccupied hiding spot that crosses the least lines of fire and has the best cover + for( int i=0; imaxClients; ++i ) + { + CBasePlayer *player = static_cast( UTIL_PlayerByIndex( i ) ); + + if (player == ignore) + continue; + + if (!IsEntityValid( player )) + continue; + + if (!player->IsPlayer()) + continue; + + if (!player->IsAlive()) + continue; + + if (teamID == 0 || player->m_iTeam == teamID) + if (Contains( &player->pev->origin )) + ++count; + } + + return count; +} + +//-------------------------------------------------------------------------------------------------------------- +static CNavArea *markedArea = NULL; +static CNavArea *lastSelectedArea = NULL; +static NavCornerType markedCorner = NUM_CORNERS; + +static bool isCreatingNavArea = false; ///< if true, we are manually creating a new nav area +static bool isAnchored = false; +static Vector anchor; + +static bool isPlaceMode = false; ///< if true, we are in place editing mode +static bool isPlacePainting = false; ///< if true, we set an area's place by pointing at it + +static float editTimestamp = 0.0f; + +CNavArea *GetMarkedArea( void ) +{ + return markedArea; +} + +/** + * Draw navigation areas and edit them + */ +void EditNavAreasReset( void ) +{ + markedArea = NULL; + lastSelectedArea = NULL; + isCreatingNavArea = false; + editTimestamp = 0.0f; + isPlacePainting = false; + lastDrawTimestamp = 0.0f; +} + +void DrawHidingSpots( const CNavArea *area ) +{ + const HidingSpotList *list = area->GetHidingSpotList(); + for( HidingSpotList::const_iterator iter = list->begin(); iter != list->end(); ++iter ) + { + const HidingSpot *spot = *iter; + + int r, g, b; + + if (spot->IsIdealSniperSpot()) + { + r = 255; g = 0; b = 0; + } + else if (spot->IsGoodSniperSpot()) + { + r = 255; g = 0; b = 255; + } + else if (spot->HasGoodCover()) + { + r = 0; g = 255; b = 0; + } + else + { + r = 0; g = 0; b = 1; + } + + UTIL_DrawBeamPoints( *spot->GetPosition(), *spot->GetPosition() + Vector( 0, 0, 50 ), 3, r, g, b ); + } +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Draw ourselves and adjacent areas + */ +void CNavArea::DrawConnectedAreas( void ) +{ + CBasePlayer *player = UTIL_GetLocalPlayer(); + if (player == NULL) + return; + + CCSBotManager *ctrl = static_cast( TheBots ); + const float maxRange = 500.0f; + + // draw self + if (isPlaceMode) + { + if (GetPlace() == 0) + Draw( 50, 0, 0, 3 ); + else if (GetPlace() != ctrl->GetNavPlace()) + Draw( 0, 0, 200, 3 ); + else + Draw( 0, 255, 0, 3 ); + } + else + { + Draw( 255, 255, 0, 3 ); + DrawHidingSpots( this ); + } + + // randomize order of directions to make sure all connected areas are + // drawn, since we may have too many to render all at once + int dirSet[ NUM_DIRECTIONS ]; + int i; + for( i=0; i= NUM_DIRECTIONS) + nextI = 0; + + int tmp = dirSet[nextI]; + dirSet[nextI] = dirSet[swapI]; + dirSet[swapI] = tmp; + } + + // draw connected areas + for( i=0; iGetPlace() == 0) + adj->Draw( 50, 0, 0, 3 ); + else if (adj->GetPlace() != ctrl->GetNavPlace()) + adj->Draw( 0, 0, 200, 3 ); + else + adj->Draw( 0, 255, 0, 3 ); + } + else + { + if (adj->IsDegenerate()) + { + static IntervalTimer blink; + static bool blinkOn = false; + + if (blink.GetElapsedTime() > 1.0f) + { + blink.Reset(); + blinkOn = !blinkOn; + } + + if (blinkOn) + adj->Draw( 255, 255, 255, 3 ); + else + adj->Draw( 255, 0, 255, 3 ); + } + else + { + adj->Draw( 255, 0, 0, 3 ); + } + + DrawHidingSpots( adj ); + + Vector from, to; + Vector hookPos; + float halfWidth; + float size = 5.0f; + ComputePortal( adj, dir, &hookPos, &halfWidth ); + + switch( dir ) + { + case NORTH: + from = hookPos + Vector( 0.0f, size, 0.0f ); + to = hookPos + Vector( 0.0f, -size, 0.0f ); + break; + case SOUTH: + from = hookPos + Vector( 0.0f, -size, 0.0f ); + to = hookPos + Vector( 0.0f, size, 0.0f ); + break; + case EAST: + from = hookPos + Vector( -size, 0.0f, 0.0f ); + to = hookPos + Vector( +size, 0.0f, 0.0f ); + break; + case WEST: + from = hookPos + Vector( size, 0.0f, 0.0f ); + to = hookPos + Vector( -size, 0.0f, 0.0f ); + break; + } + + from.z = GetZ( &from ) + cv_bot_nav_zdraw.value; + to.z = adj->GetZ( &to ) + cv_bot_nav_zdraw.value; + + Vector drawTo; + adj->GetClosestPointOnArea( &to, &drawTo ); + + if (adj->IsConnected( this, OppositeDirection( dir ) ) ) + UTIL_DrawBeamPoints( from, drawTo, 3, 0, 255, 255 ); + else + UTIL_DrawBeamPoints( from, drawTo, 3, 0, 0, 255 ); + } + } + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Raise/lower a corner + */ +void CNavArea::RaiseCorner( NavCornerType corner, int amount ) +{ + if ( corner == NUM_CORNERS ) + { + m_extent.lo.z += amount; + m_extent.hi.z += amount; + m_neZ += amount; + m_swZ += amount; + } + else + { + switch (corner) + { + case NORTH_WEST: + m_extent.lo.z += amount; + break; + case NORTH_EAST: + m_neZ += amount; + break; + case SOUTH_WEST: + m_swZ += amount; + break; + case SOUTH_EAST: + m_extent.hi.z += amount; + break; + } + } + + m_center.x = (m_extent.lo.x + m_extent.hi.x)/2.0f; + m_center.y = (m_extent.lo.y + m_extent.hi.y)/2.0f; + m_center.z = (m_extent.lo.z + m_extent.hi.z)/2.0f; +} + +/** + * Flood fills all areas with current place + */ +class PlaceFloodFillFunctor +{ +public: + PlaceFloodFillFunctor( CNavArea *area ) + { + m_initialPlace = area->GetPlace(); + } + + bool operator() ( CNavArea *area ) + { + CCSBotManager *ctrl = static_cast( TheBots ); + + if (area->GetPlace() != m_initialPlace) + return false; + + area->SetPlace( ctrl->GetNavPlace() ); + + return true; + } + +private: + unsigned int m_initialPlace; +}; + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Draw navigation areas and edit them + */ +void EditNavAreas( NavEditCmdType cmd ) +{ + CCSBotManager *ctrl = static_cast( TheBots ); + + CBasePlayer *player = UTIL_GetLocalPlayer(); + if (player == NULL) + return; + + // don't draw too often on fast video cards or the areas may not appear (odd video effect) + float drawTimestamp = gpGlobals->time; + const float maxDrawRate = 0.05f; + + bool doDraw; + if (drawTimestamp - lastDrawTimestamp < maxDrawRate) + { + doDraw = false; + } + else + { + doDraw = true; + lastDrawTimestamp = drawTimestamp; + } + + + const float maxRange = 1000.0f; // 500 + + int beamTime = 1; + + if (doDraw) + { + // show ladder connections + for( NavLadderList::iterator iter = TheNavLadderList.begin(); iter != TheNavLadderList.end(); ++iter ) + { + CNavLadder *ladder = *iter; + + float dx = player->pev->origin.x - ladder->m_bottom.x; + float dy = player->pev->origin.y - ladder->m_bottom.y; + if (dx*dx + dy*dy > maxRange*maxRange) + continue; + + + UTIL_DrawBeamPoints( ladder->m_top, ladder->m_bottom, beamTime, 255, 0, 255 ); + + Vector bottom = ladder->m_bottom; + Vector top = ladder->m_top; + + AddDirectionVector( &top, ladder->m_dir, HalfHumanWidth ); + AddDirectionVector( &bottom, ladder->m_dir, HalfHumanWidth ); + + UTIL_DrawBeamPoints( top, bottom, beamTime, 0, 0, 255 ); + + if (ladder->m_bottomArea) + UTIL_DrawBeamPoints( bottom + Vector( 0, 0, GenerationStepSize ), *ladder->m_bottomArea->GetCenter(), beamTime, 0, 0, 255 ); + + if (ladder->m_topForwardArea) + UTIL_DrawBeamPoints( top, *ladder->m_topForwardArea->GetCenter(), beamTime, 0, 0, 255 ); + + if (ladder->m_topLeftArea) + UTIL_DrawBeamPoints( top, *ladder->m_topLeftArea->GetCenter(), beamTime, 0, 0, 255 ); + + if (ladder->m_topRightArea) + UTIL_DrawBeamPoints( top, *ladder->m_topRightArea->GetCenter(), beamTime, 0, 0, 255 ); + + if (ladder->m_topBehindArea) + UTIL_DrawBeamPoints( top, *ladder->m_topBehindArea->GetCenter(), beamTime, 0, 0, 255 ); + } + + // draw approach points for marked area + if (cv_bot_traceview.value == 3 && markedArea) + { + Vector ap; + float halfWidth; + for( int i=0; iGetApproachInfoCount(); ++i ) + { + const CNavArea::ApproachInfo *info = markedArea->GetApproachInfo( i ); + + // compute approach point + if (info->hereToNextHow <= GO_WEST) + { + info->here.area->ComputePortal( info->next.area, (NavDirType)info->hereToNextHow, &ap, &halfWidth ); + ap.z = info->next.area->GetZ( &ap ); + } + else + { + // use the area's center as an approach point + ap = *info->here.area->GetCenter(); + } + + UTIL_DrawBeamPoints( ap + Vector( 0, 0, 50 ), ap + Vector( 10, 0, 0 ), beamTime, 255, 100, 0 ); + UTIL_DrawBeamPoints( ap + Vector( 0, 0, 50 ), ap + Vector( -10, 0, 0 ), beamTime, 255, 100, 0 ); + UTIL_DrawBeamPoints( ap + Vector( 0, 0, 50 ), ap + Vector( 0, 10, 0 ), beamTime, 255, 100, 0 ); + UTIL_DrawBeamPoints( ap + Vector( 0, 0, 50 ), ap + Vector( 0, -10, 0 ), beamTime, 255, 100, 0 ); + } + } + } + + Vector dir; + UTIL_MakeVectorsPrivate( player->pev->v_angle, dir, NULL, NULL ); + + Vector from = player->pev->origin + player->pev->view_ofs; // eye position + Vector to = from + maxRange * dir; + + TraceResult result; + UTIL_TraceLine( from, to, ignore_monsters, ignore_glass, ENT( player->pev ), &result ); + + if (result.flFraction != 1.0f) + { + // draw cursor + Vector cursor = result.vecEndPos; + float cursorSize = 10.0f; + + if (doDraw) + { + UTIL_DrawBeamPoints( cursor + Vector( 0, 0, cursorSize ), cursor, beamTime, 255, 255, 255 ); + UTIL_DrawBeamPoints( cursor + Vector( cursorSize, 0, 0 ), cursor + Vector( -cursorSize, 0, 0 ), beamTime, 255, 255, 255 ); + UTIL_DrawBeamPoints( cursor + Vector( 0, cursorSize, 0 ), cursor + Vector( 0, -cursorSize, 0 ), beamTime, 255, 255, 255 ); + + // show surface normal + // UTIL_DrawBeamPoints( cursor + 50.0f * result.vecPlaneNormal, cursor, beamTime, 255, 0, 255 ); + + } + + if (isCreatingNavArea) + { + if (isAnchored) + { + // show drag rectangle + if (doDraw) + { + float z = anchor.z + 2.0f; + UTIL_DrawBeamPoints( Vector( cursor.x, cursor.y, z ), Vector( anchor.x, cursor.y, z ), beamTime, 0, 255, 255 ); + UTIL_DrawBeamPoints( Vector( anchor.x, anchor.y, z ), Vector( anchor.x, cursor.y, z ), beamTime, 0, 255, 255 ); + UTIL_DrawBeamPoints( Vector( anchor.x, anchor.y, z ), Vector( cursor.x, anchor.y, z ), beamTime, 0, 255, 255 ); + UTIL_DrawBeamPoints( Vector( cursor.x, cursor.y, z ), Vector( cursor.x, anchor.y, z ), beamTime, 0, 255, 255 ); + } + } + else + { + // anchor starting corner + anchor = cursor; + isAnchored = true; + } + } + + // find the area the player is pointing at + CNavArea *area = TheNavAreaGrid.GetNearestNavArea( &result.vecEndPos ); + + if (area) + { + // if area changed, print its ID + if (area != lastSelectedArea) + { + lastSelectedArea = area; + + char buffer[80]; + char attrib[80]; + char locName[80]; + + if (area->GetPlace()) + { + const char *name = TheBotPhrases->IDToName( area->GetPlace() ); + if (name) + strcpy( locName, name ); + else + strcpy( locName, "ERROR" ); + } + else + { + locName[0] = '\000'; + } + + if (isPlaceMode) + { + attrib[0] = '\000'; + } + else + { + sprintf( attrib, "%s%s%s%s", + (area->GetAttributes() & NAV_CROUCH) ? "CROUCH " : "", + (area->GetAttributes() & NAV_JUMP) ? "JUMP " : "", + (area->GetAttributes() & NAV_PRECISE) ? "PRECISE " : "", + (area->GetAttributes() & NAV_NO_JUMP) ? "NO_JUMP " : "" ); + } + + sprintf( buffer, "Area #%d %s %s\n", area->GetID(), locName, attrib ); + + UTIL_SayTextAll( buffer, player ); + + // do "place painting" + if (isPlacePainting) + { + if (area->GetPlace() != ctrl->GetNavPlace()) + { + area->SetPlace( ctrl->GetNavPlace() ); + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/lightswitch2.wav", 1, ATTN_NORM, 0, 100 ); + } + } + } + + if (isPlaceMode) + { + area->DrawConnectedAreas(); + + switch( cmd ) + { + case EDIT_TOGGLE_PLACE_MODE: + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/blip1.wav", 1, ATTN_NORM, 0, 100 ); + isPlaceMode = false; + return; + + case EDIT_TOGGLE_PLACE_PAINTING: + { + if (isPlacePainting) + { + isPlacePainting = false; + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/latchunlocked2.wav", 1, ATTN_NORM, 0, 100 ); + } + else + { + isPlacePainting = true; + + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/lightswitch2.wav", 1, ATTN_NORM, 0, 100 ); + + // paint the initial area + area->SetPlace( ctrl->GetNavPlace() ); + } + break; + } + + case EDIT_PLACE_PICK: + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/blip1.wav", 1, ATTN_NORM, 0, 100 ); + ctrl->SetNavPlace( area->GetPlace() ); + break; + + case EDIT_PLACE_FLOODFILL: + PlaceFloodFillFunctor pff( area ); + SearchSurroundingAreas( area, area->GetCenter(), pff ); + break; + } + } + else // normal editing mode + { + // draw the "marked" area + if (markedArea && doDraw) + { + markedArea->Draw( 0, 255, 255, beamTime ); + if ( markedCorner != NUM_CORNERS ) + markedArea->DrawMarkedCorner( markedCorner, 0, 0, 255, beamTime ); + + if (cv_bot_traceview.value == 11) + { + // draw areas connected to the marked area + markedArea->DrawConnectedAreas(); + } + } + + + // draw split line + const Extent *extent = area->GetExtent(); + + float yaw = player->pev->v_angle.y; + while( yaw > 360.0f ) + yaw -= 360.0f; + + while( yaw < 0.0f ) + yaw += 360.0f; + + float splitEdge; + bool splitAlongX; + + if ((yaw < 45.0f || yaw > 315.0f) || (yaw > 135.0f && yaw < 225.0f)) + { + splitEdge = GenerationStepSize * (int)(result.vecEndPos.y/GenerationStepSize); + + from.x = extent->lo.x; + from.y = splitEdge; + from.z = area->GetZ( &from ) + cv_bot_nav_zdraw.value; + + to.x = extent->hi.x; + to.y = splitEdge; + to.z = area->GetZ( &to ) + cv_bot_nav_zdraw.value; + + splitAlongX = true; + } + else + { + splitEdge = GenerationStepSize * (int)(result.vecEndPos.x/GenerationStepSize); + + from.x = splitEdge; + from.y = extent->lo.y; + from.z = area->GetZ( &from ) + cv_bot_nav_zdraw.value; + + to.x = splitEdge; + to.y = extent->hi.y; + to.z = area->GetZ( &to ) + cv_bot_nav_zdraw.value; + + splitAlongX = false; + } + + if (doDraw) + UTIL_DrawBeamPoints( from, to, beamTime, 255, 255, 255 ); + + // draw the area we are pointing at and all connected areas + if (doDraw && (cv_bot_traceview.value != 11 || markedArea == NULL)) + area->DrawConnectedAreas(); + + + // do area-dependant edit commands, if any + switch( cmd ) + { + case EDIT_TOGGLE_PLACE_MODE: + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/blip1.wav", 1, ATTN_NORM, 0, 100 ); + isPlaceMode = true; + return; + + case EDIT_DELETE: + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/blip1.wav", 1, ATTN_NORM, 0, 100 ); + TheNavAreaList.remove( area ); + delete area; + return; + + case EDIT_ATTRIB_CROUCH: + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/bell1.wav", 1, ATTN_NORM, 0, 100 ); + area->SetAttributes( area->GetAttributes() ^ NAV_CROUCH ); + break; + + case EDIT_ATTRIB_JUMP: + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/bell1.wav", 1, ATTN_NORM, 0, 100 ); + area->SetAttributes( area->GetAttributes() ^ NAV_JUMP ); + break; + + case EDIT_ATTRIB_PRECISE: + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/bell1.wav", 1, ATTN_NORM, 0, 100 ); + area->SetAttributes( area->GetAttributes() ^ NAV_PRECISE ); + break; + + case EDIT_ATTRIB_NO_JUMP: + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/bell1.wav", 1, ATTN_NORM, 0, 100 ); + area->SetAttributes( area->GetAttributes() ^ NAV_NO_JUMP ); + break; + + case EDIT_SPLIT: + if (area->SplitEdit( splitAlongX, splitEdge )) + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "weapons/knife_hitwall1.wav", 1, ATTN_NORM, 0, 100 ); + else + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/button11.wav", 1, ATTN_NORM, 0, 100 ); + break; + + case EDIT_MERGE: + if (markedArea) + { + if (area->MergeEdit( markedArea )) + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/blip1.wav", 1, ATTN_NORM, 0, 100 ); + else + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/button11.wav", 1, ATTN_NORM, 0, 100 ); + } + else + { + HintMessageToAllPlayers( "To merge, mark an area, highlight a second area, then invoke the merge command" ); + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/button11.wav", 1, ATTN_NORM, 0, 100 ); + } + break; + + case EDIT_MARK: + if (markedArea) + { + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/blip1.wav", 1, ATTN_NORM, 0, 100 ); + markedArea = NULL; + } + else + { + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/blip2.wav", 1, ATTN_NORM, 0, 100 ); + markedArea = area; + + int connected = 0; + connected += markedArea->GetAdjacentCount( NORTH ); + connected += markedArea->GetAdjacentCount( SOUTH ); + connected += markedArea->GetAdjacentCount( EAST ); + connected += markedArea->GetAdjacentCount( WEST ); + + char buffer[80]; + sprintf( buffer, "Marked Area is connected to %d other Areas\n", connected ); + UTIL_SayTextAll( buffer, player ); + } + break; + + case EDIT_MARK_UNNAMED: + if (markedArea) + { + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/blip1.wav", 1, ATTN_NORM, 0, 100 ); + markedArea = NULL; + } + else + { + markedArea = NULL; + for( NavAreaList::iterator iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter ) + { + CNavArea *area = *iter; + if ( area->GetPlace() == 0 ) + { + markedArea = area; + break; + } + } + if ( !markedArea ) + { + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/blip1.wav", 1, ATTN_NORM, 0, 100 ); + } + else + { + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/blip2.wav", 1, ATTN_NORM, 0, 100 ); + + int connected = 0; + connected += markedArea->GetAdjacentCount( NORTH ); + connected += markedArea->GetAdjacentCount( SOUTH ); + connected += markedArea->GetAdjacentCount( EAST ); + connected += markedArea->GetAdjacentCount( WEST ); + + int totalUnnamedAreas = 0; + for( NavAreaList::iterator iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter ) + { + CNavArea *area = *iter; + if ( area->GetPlace() == 0 ) + { + ++totalUnnamedAreas; + } + } + + char buffer[80]; + sprintf( buffer, "Marked Area is connected to %d other Areas - there are %d total unnamed areas\n", connected, totalUnnamedAreas ); + UTIL_SayTextAll( buffer, player ); + } + } + break; + + case EDIT_WARP_TO_MARK: + if (markedArea) + { + CBasePlayer *pLocalPlayer = UTIL_GetLocalPlayer(); + if ( pLocalPlayer && pLocalPlayer->m_iTeam == SPECTATOR && pLocalPlayer->pev->iuser1 == OBS_ROAMING ) + { + Vector origin = *markedArea->GetCenter() + Vector( 0, 0, 0.75f * HumanHeight ); + UTIL_SetOrigin( pLocalPlayer->pev, origin ); + } + } + else + { + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/button11.wav", 1, ATTN_NORM, 0, 100 ); + } + break; + + case EDIT_CONNECT: + if (markedArea) + { + NavDirType dir = markedArea->ComputeDirection( &cursor ); + if (dir == NUM_DIRECTIONS) + { + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/button11.wav", 1, ATTN_NORM, 0, 100 ); + } + else + { + markedArea->ConnectTo( area, dir ); + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/blip1.wav", 1, ATTN_NORM, 0, 100 ); + } + } + else + { + HintMessageToAllPlayers( "To connect areas, mark an area, highlight a second area, then invoke the connect command. Make sure the cursor is directly north, south, east, or west of the marked area." ); + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/button11.wav", 1, ATTN_NORM, 0, 100 ); + } + break; + + case EDIT_DISCONNECT: + if (markedArea) + { + markedArea->Disconnect( area ); + area->Disconnect( markedArea ); + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/blip1.wav", 1, ATTN_NORM, 0, 100 ); + } + else + { + HintMessageToAllPlayers( "To disconnect areas, mark an area, highlight a second area, then invoke the disconnect command. This will remove all connections between the two areas." ); + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/button11.wav", 1, ATTN_NORM, 0, 100 ); + } + break; + + case EDIT_SPLICE: + if (markedArea) + { + if (area->SpliceEdit( markedArea )) + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/blip1.wav", 1, ATTN_NORM, 0, 100 ); + else + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/button11.wav", 1, ATTN_NORM, 0, 100 ); + } + else + { + HintMessageToAllPlayers( "To splice, mark an area, highlight a second area, then invoke the splice command to create an area between them" ); + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/button11.wav", 1, ATTN_NORM, 0, 100 ); + } + break; + + case EDIT_SELECT_CORNER: + if (markedArea) + { + int corner = (markedCorner + 1) % (NUM_CORNERS + 1); + markedCorner = (NavCornerType)corner; + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/blip1.wav", 1, ATTN_NORM, 0, 100 ); + } + else + { + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/button11.wav", 1, ATTN_NORM, 0, 100 ); + } + break; + + case EDIT_RAISE_CORNER: + if (markedArea) + { + markedArea->RaiseCorner( markedCorner, 1 ); + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/blip1.wav", 1, ATTN_NORM, 0, 100 ); + } + else + { + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/button11.wav", 1, ATTN_NORM, 0, 100 ); + } + break; + + case EDIT_LOWER_CORNER: + if (markedArea) + { + markedArea->RaiseCorner( markedCorner, -1 ); + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/blip1.wav", 1, ATTN_NORM, 0, 100 ); + } + else + { + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/button11.wav", 1, ATTN_NORM, 0, 100 ); + } + break; + } + } + } + + // do area-independant edit commands, if any + switch( cmd ) + { + case EDIT_BEGIN_AREA: + { + if (isCreatingNavArea) + { + isCreatingNavArea = false; + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/button11.wav", 1, ATTN_NORM, 0, 100 ); + } + else + { + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/blip2.wav", 1, ATTN_NORM, 0, 100 ); + isCreatingNavArea = true; + isAnchored = false; + } + break; + } + + case EDIT_END_AREA: + { + if (isCreatingNavArea) + { + // create the new nav area + CNavArea *newArea = new CNavArea( &anchor, &cursor ); + TheNavAreaList.push_back( newArea ); + TheNavAreaGrid.AddNavArea( newArea ); + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/blip1.wav", 1, ATTN_NORM, 0, 100 ); + + // if we have a marked area, inter-connect the two + if (markedArea) + { + const Extent *extent = markedArea->GetExtent(); + + if (anchor.x > extent->hi.x && cursor.x > extent->hi.x) + { + markedArea->ConnectTo( newArea, EAST ); + newArea->ConnectTo( markedArea, WEST ); + } + else if (anchor.x < extent->lo.x && cursor.x < extent->lo.x) + { + markedArea->ConnectTo( newArea, WEST ); + newArea->ConnectTo( markedArea, EAST ); + } + else if (anchor.y > extent->hi.y && cursor.y > extent->hi.y) + { + markedArea->ConnectTo( newArea, SOUTH ); + newArea->ConnectTo( markedArea, NORTH ); + } + else if (anchor.y < extent->lo.y && cursor.y < extent->lo.y) + { + markedArea->ConnectTo( newArea, NORTH ); + newArea->ConnectTo( markedArea, SOUTH ); + } + + // propogate marked area to new area + markedArea = newArea; + } + + isCreatingNavArea = false; + } + else + { + EMIT_SOUND_DYN( ENT(UTIL_GetLocalPlayer()->pev), CHAN_ITEM, "buttons/button11.wav", 1, ATTN_NORM, 0, 100 ); + } + break; + } + } + } + + + // if our last command was not mark (or no command), clear the mark area + if (cmd != EDIT_MARK && cmd != EDIT_BEGIN_AREA && cmd != EDIT_END_AREA && + cmd != EDIT_MARK_UNNAMED && cmd != EDIT_WARP_TO_MARK && + cmd != EDIT_SELECT_CORNER && cmd != EDIT_RAISE_CORNER && cmd != EDIT_LOWER_CORNER && + cmd != EDIT_NONE) + markedArea = NULL; + + // if our last command was not affecting the corner, clear the corner selection + if (cmd != EDIT_SELECT_CORNER && cmd != EDIT_RAISE_CORNER && cmd != EDIT_LOWER_CORNER && cmd != EDIT_NONE) + markedCorner = NUM_CORNERS; + + + if (isCreatingNavArea && cmd != EDIT_BEGIN_AREA && cmd != EDIT_END_AREA && cmd != EDIT_NONE) + isCreatingNavArea = false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return the ground height below this point in "height". + * Return false if position is invalid (outside of map, in a solid area, etc). + */ +bool GetGroundHeight( const Vector *pos, float *height, Vector *normal ) +{ + Vector to; + to.x = pos->x; + to.y = pos->y; + to.z = pos->z - 9999.9f; + + float offset; + Vector from; + TraceResult result; + edict_t *ignore = NULL; + float ground = 0.0f; + + const float maxOffset = 100.0f; + const float inc = 10.0f; + + #define MAX_GROUND_LAYERS 16 + struct GroundLayerInfo + { + float ground; + Vector normal; + } + layer[ MAX_GROUND_LAYERS ]; + int layerCount = 0; + + for( offset = 1.0f; offset < maxOffset; offset += inc ) + { + from = *pos + Vector( 0, 0, offset ); + + UTIL_TraceLine( from, to, ignore_monsters, dont_ignore_glass, ignore, &result ); + + // if the trace came down thru a door, ignore the door and try again + // also ignore breakable floors + if (result.pHit) + { + if (FClassnameIs( VARS( result.pHit ), "func_door" ) || + FClassnameIs( VARS( result.pHit ), "func_door_rotating" ) || + (FClassnameIs( VARS( result.pHit ), "func_breakable" ) && VARS( result.pHit )->takedamage == DAMAGE_YES)) + { + ignore = result.pHit; + // keep incrementing to avoid infinite loop if more than one entity is along the traceline... + /// @todo Deal with multiple ignore entities in a single TraceLine() + //offset -= inc; + continue; + } + } + + if (result.fStartSolid == false) + { + // if we didnt start inside a solid area, the trace hit a ground layer + + // if this is a new ground layer, add it to the set + if (layerCount == 0 || result.vecEndPos.z > layer[ layerCount-1 ].ground) + { + layer[ layerCount ].ground = result.vecEndPos.z; + layer[ layerCount ].normal = result.vecPlaneNormal; + ++layerCount; + + if (layerCount == MAX_GROUND_LAYERS) + break; + } + } + } + + if (layerCount == 0) + return false; + + // find the lowest layer that allows a player to stand or crouch upon it + int i; + for( i=0; i= HalfHumanHeight) + break; + } + + *height = layer[ i ].ground; + + if (normal) + *normal = layer[ i ].normal; + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return the "simple" ground height below this point in "height". + * This function is much faster, but less tolerant. Make sure the give position is "well behaved". + * Return false if position is invalid (outside of map, in a solid area, etc). + */ +bool GetSimpleGroundHeight( const Vector *pos, float *height, Vector *normal ) +{ + Vector to; + to.x = pos->x; + to.y = pos->y; + to.z = pos->z - 9999.9f; + + TraceResult result; + + UTIL_TraceLine( *pos, to, ignore_monsters, dont_ignore_glass, NULL, &result ); + + if (result.fStartSolid) + return false; + + *height = result.vecEndPos.z; + + if (normal) + *normal = result.vecPlaneNormal; + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +enum { MAX_BLOCKED_AREAS = 256 }; +static unsigned int BlockedID[ MAX_BLOCKED_AREAS ]; +static int BlockedIDCount = 0; + +/** + * Shortest path cost, paying attention to "blocked" areas + */ +class ApproachAreaCost +{ +public: + float operator() ( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder ) + { + // check if this area is "blocked" + for( int i=0; iGetID() == BlockedID[i]) + return -1.0f; + + if (fromArea == NULL) + { + // first area in path, no cost + return 0.0f; + } + else + { + // compute distance travelled along path so far + float dist; + + if (ladder) + dist = ladder->m_length; + else + dist = (*area->GetCenter() - *fromArea->GetCenter()).Length(); + + float cost = dist + fromArea->GetCostSoFar(); + + return cost; + } + } +}; + +/** + * Can we see this area? + * For now, if we can see any corner, we can see the area + * @todo Need to check LOS to more than the corners for large and/or long areas + */ +inline bool IsAreaVisible( const Vector *pos, const CNavArea *area ) +{ + Vector corner; + TraceResult result; + + for( int c=0; cGetCorner( (NavCornerType)c ); + corner.z += 0.75f * HumanHeight; + + UTIL_TraceLine( *pos, corner, ignore_monsters, NULL, &result ); + if (result.flFraction == 1.0f) + { + // we can see this area + return true; + } + } + + return false; +} + +/** + * Determine the set of "approach areas". + * An approach area is an area representing a place where players + * move into/out of our local neighborhood of areas. + */ +void CNavArea::ComputeApproachAreas( void ) +{ + m_approachCount = 0; + + if (cv_bot_quicksave.value > 0.0f) + return; + + // use the center of the nav area as the "view" point + Vector eye = m_center; + if (GetGroundHeight( &eye, &eye.z ) == false) + return; + + // approximate eye position + if (GetAttributes() & NAV_CROUCH) + eye.z += 0.9f * HalfHumanHeight; + else + eye.z += 0.9f * HumanHeight; + + enum { MAX_PATH_LENGTH = 256 }; + CNavArea *path[ MAX_PATH_LENGTH ]; + + // + // In order to enumerate all of the approach areas, we need to + // run the algorithm many times, once for each "far away" area + // and keep the union of the approach area sets + // + NavAreaList::iterator iter; + for( iter = goodSizedAreaList.begin(); iter != goodSizedAreaList.end(); ++iter ) + { + CNavArea *farArea = *iter; + + BlockedIDCount = 0; + + // if we can see 'farArea', try again - the whole point is to go "around the bend", so to speak + if (IsAreaVisible( &eye, farArea )) + continue; + + // make first path to far away area + ApproachAreaCost cost; + if (NavAreaBuildPath( this, farArea, NULL, cost ) == false) + continue; + + // + // Keep building paths to farArea and blocking them off until we + // cant path there any more. + // As areas are blocked off, all exits will be enumerated. + // + while( m_approachCount < MAX_APPROACH_AREAS ) + { + // find number of areas on path + int count = 0; + CNavArea *area; + for( area = farArea; area; area = area->GetParent() ) + ++count; + + if (count > MAX_PATH_LENGTH) + count = MAX_PATH_LENGTH; + + // build path in correct order - from eye outwards + int i = count; + for( area = farArea; i && area; area = area->GetParent() ) + path[ --i ] = area; + + // traverse path to find first area we cannot see (skip the first area) + for( i=1; iGetID(); + + if (block == 0) + break; + + // store new approach area if not already in set + int a; + for( a=0; a= 2) ? path[block-2] : NULL; + + m_approach[ m_approachCount ].here.area = path[block-1]; + m_approach[ m_approachCount ].prevToHereHow = path[block-1]->GetParentHow(); + + m_approach[ m_approachCount ].next.area = path[block]; + m_approach[ m_approachCount ].hereToNextHow = path[block]->GetParentHow(); + + ++m_approachCount; + } + + // we are done with this path + break; + } + + // find another path to 'farArea' + ApproachAreaCost cost; + if (NavAreaBuildPath( this, farArea, NULL, cost ) == false) + { + // can't find a path to 'farArea' means all exits have been already tested and blocked + break; + } + } + } +} + + +//-------------------------------------------------------------------------------------------------------------- + +/** + * The singleton for accessing the grid + */ +CNavAreaGrid TheNavAreaGrid; + + +CNavAreaGrid::CNavAreaGrid( void ) : m_cellSize( 300.0f ) +{ + m_grid = NULL; + Reset(); +} + +CNavAreaGrid::~CNavAreaGrid() +{ + delete [] m_grid; + m_grid = NULL; +} + +/** + * Clear the grid + */ +void CNavAreaGrid::Reset( void ) +{ + if (m_grid) + delete [] m_grid; + + m_grid = NULL; + m_gridSizeX = 0; + m_gridSizeY = 0; + + // clear the hash table + for( int i=0; iGetExtent(); + + int loX = WorldToGridX( extent->lo.x ); + int loY = WorldToGridY( extent->lo.y ); + int hiX = WorldToGridX( extent->hi.x ); + int hiY = WorldToGridY( extent->hi.y ); + + for( int y = loY; y <= hiY; ++y ) + for( int x = loX; x <= hiX; ++x ) + m_grid[ x + y*m_gridSizeX ].push_back( const_cast( area ) ); + + // add to hash table + int key = ComputeHashKey( area->GetID() ); + + if (m_hashTable[key]) + { + // add to head of list in this slot + area->m_prevHash = NULL; + area->m_nextHash = m_hashTable[key]; + m_hashTable[key]->m_prevHash = area; + m_hashTable[key] = area; + } + else + { + // first entry in this slot + m_hashTable[key] = area; + area->m_nextHash = NULL; + area->m_prevHash = NULL; + } + + ++m_areaCount; +} + +/** + * Remove an area from the grid + */ +void CNavAreaGrid::RemoveNavArea( CNavArea *area ) +{ + // add to grid + const Extent *extent = area->GetExtent(); + + int loX = WorldToGridX( extent->lo.x ); + int loY = WorldToGridY( extent->lo.y ); + int hiX = WorldToGridX( extent->hi.x ); + int hiY = WorldToGridY( extent->hi.y ); + + for( int y = loY; y <= hiY; ++y ) + for( int x = loX; x <= hiX; ++x ) + m_grid[ x + y*m_gridSizeX ].remove( area ); + + // remove from hash table + int key = ComputeHashKey( area->GetID() ); + + if (area->m_prevHash) + { + area->m_prevHash->m_nextHash = area->m_nextHash; + } + else + { + // area was at start of list + m_hashTable[key] = area->m_nextHash; + + if (m_hashTable[key]) + m_hashTable[key]->m_prevHash = NULL; + } + + if (area->m_nextHash) + { + area->m_nextHash->m_prevHash = area->m_prevHash; + } + + --m_areaCount; +} + +/** + * Given a position, return the nav area that IsOverlapping and is *immediately* beneath it + */ +CNavArea *CNavAreaGrid::GetNavArea( const Vector *pos, float beneathLimit ) const +{ + if (m_grid == NULL) + return NULL; + + // get list in cell that contains position + int x = WorldToGridX( pos->x ); + int y = WorldToGridY( pos->y ); + NavAreaList *list = &m_grid[ x + y*m_gridSizeX ]; + + + // search cell list to find correct area + CNavArea *use = NULL; + float useZ = -99999999.9f; + Vector testPos = *pos + Vector( 0, 0, 5 ); + + for( NavAreaList::iterator iter = list->begin(); iter != list->end(); ++iter ) + { + CNavArea *area = *iter; + + // check if position is within 2D boundaries of this area + if (area->IsOverlapping( &testPos )) + { + // project position onto area to get Z + float z = area->GetZ( &testPos ); + + // if area is above us, skip it + if (z > testPos.z) + continue; + + // if area is too far below us, skip it + if (z < pos->z - beneathLimit) + continue; + + // if area is higher than the one we have, use this instead + if (z > useZ) + { + use = area; + useZ = z; + } + } + } + + return use; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Given a position in the world, return the nav area that is closest + * and at the same height, or beneath it. + * Used to find initial area if we start off of the mesh. + */ +CNavArea *CNavAreaGrid::GetNearestNavArea( const Vector *pos, bool anyZ ) const +{ + if (m_grid == NULL) + return NULL; + + CNavArea *close = NULL; + float closeDistSq = 99999999.9f; + + // quick check + close = GetNavArea( pos ); + if (close) + return close; + + // ensure source position is well behaved + Vector source; + source.x = pos->x; + source.y = pos->y; + if (GetGroundHeight( pos, &source.z ) == false) + return NULL; + + source.z += HalfHumanHeight; + + /// @todo Step incrementally using grid for speed + + // find closest nav area + for( NavAreaList::iterator iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter ) + { + CNavArea *area = *iter; + + Vector areaPos; + area->GetClosestPointOnArea( &source, &areaPos ); + + float distSq = (areaPos - source).LengthSquared(); + + // keep the closest area + if (distSq < closeDistSq) + { + // check LOS to area + if (!anyZ) + { + TraceResult result; + UTIL_TraceLine( source, areaPos + Vector( 0, 0, HalfHumanHeight ), ignore_monsters, ignore_glass, NULL, &result ); + if (result.flFraction != 1.0f) + continue; + } + + closeDistSq = distSq; + close = area; + } + } + + return close; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Given an ID, return the associated area + */ +CNavArea *CNavAreaGrid::GetNavAreaByID( unsigned int id ) const +{ + if (id == 0) + return NULL; + + int key = ComputeHashKey( id ); + + for( CNavArea *area = m_hashTable[key]; area; area = area->m_nextHash ) + if (area->GetID() == id) + return area; + + return NULL; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return radio chatter place for given coordinate + */ +unsigned int CNavAreaGrid::GetPlace( const Vector *pos ) const +{ + CNavArea *area = GetNearestNavArea( pos, true ); + + if (area) + return area->GetPlace(); + + return UNDEFINED_PLACE; +} + + diff --git a/game_shared/bot/nav_area.h b/game_shared/bot/nav_area.h new file mode 100644 index 0000000..4b79518 --- /dev/null +++ b/game_shared/bot/nav_area.h @@ -0,0 +1,1231 @@ +// nav_area.h +// Navigation areas +// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003 + +#ifndef _NAV_AREA_H_ +#define _NAV_AREA_H_ + +#include +#include "nav.h" +#include "steam_util.h" + +class CNavArea; + +void DestroyHidingSpots( void ); +void StripNavigationAreas( void ); +bool SaveNavigationMap( const char *filename ); +NavErrorType LoadNavigationMap( void ); +void DestroyNavigationMap( void ); + +//------------------------------------------------------------------------------------------------------------------- +/** + * Used when building a path to determine the kind of path to build + */ +enum RouteType +{ + FASTEST_ROUTE, + SAFEST_ROUTE, +}; + + +//------------------------------------------------------------------------------------------------------------------- +/** + * The NavConnect union is used to refer to connections to areas + */ +union NavConnect +{ + unsigned int id; + CNavArea *area; + + bool operator==( const NavConnect &other ) const + { + return (area == other.area) ? true : false; + } +}; +typedef std::list NavConnectList; + +//-------------------------------------------------------------------------------------------------------------- +enum LadderDirectionType +{ + LADDER_UP = 0, + LADDER_DOWN, + + NUM_LADDER_DIRECTIONS +}; + +/** + * The NavLadder class encapsulates traversable ladders, and their connections to NavAreas + * @todo Deal with ladders that allow jumping off to areas in the middle + */ +class CNavLadder +{ +public: + CNavLadder( void ) + { + m_topForwardArea = NULL; + m_topRightArea = NULL; + m_topLeftArea = NULL; + m_topBehindArea = NULL; + m_bottomArea = NULL; + m_entity = NULL; + } + + Vector m_top; ///< world coords of the top of the ladder + Vector m_bottom; ///< world coords of the top of the ladder + float m_length; ///< the length of the ladder + NavDirType m_dir; ///< which way the ladder faces (ie: surface normal of climbable side) + Vector2D m_dirVector; ///< unit vector representation of m_dir + CBaseEntity *m_entity; ///< the ladder itself + + CNavArea *m_topForwardArea; ///< the area at the top of the ladder + CNavArea *m_topLeftArea; + CNavArea *m_topRightArea; + CNavArea *m_topBehindArea; ///< area at top of ladder "behind" it - only useful for descending + CNavArea *m_bottomArea; ///< the area at the bottom of the ladder + + bool m_isDangling; ///< if true, the bottom of the ladder is hanging too high to climb up + + void OnDestroyNotify( CNavArea *dead ) ///< invoked when given area is going away + { + if (dead == m_topForwardArea) + m_topForwardArea = NULL; + + if (dead == m_topLeftArea) + m_topLeftArea = NULL; + + if (dead == m_topRightArea) + m_topRightArea = NULL; + + if (dead == m_topBehindArea) + m_topBehindArea = NULL; + + if (dead == m_bottomArea) + m_bottomArea = NULL; + } +}; +typedef std::list NavLadderList; +extern NavLadderList TheNavLadderList; + +//-------------------------------------------------------------------------------------------------------------- +/** + * A HidingSpot is a good place for a bot to crouch and wait for enemies + */ +class HidingSpot +{ +public: + HidingSpot( void ); ///< for use when loading from a file + HidingSpot( const Vector *pos, unsigned char flags ); ///< for use when generating - assigns unique ID + + enum + { + IN_COVER = 0x01, ///< in a corner with good hard cover nearby + GOOD_SNIPER_SPOT = 0x02, ///< had at least one decent sniping corridor + IDEAL_SNIPER_SPOT = 0x04 ///< can see either very far, or a large area, or both + }; + + bool HasGoodCover( void ) const { return (m_flags & IN_COVER) ? true : false; } ///< return true if hiding spot in in cover + bool IsGoodSniperSpot( void ) const { return (m_flags & GOOD_SNIPER_SPOT) ? true : false; } + bool IsIdealSniperSpot( void ) const { return (m_flags & IDEAL_SNIPER_SPOT) ? true : false; } + + void SetFlags( unsigned char flags ) { m_flags |= flags; } ///< FOR INTERNAL USE ONLY + unsigned char GetFlags( void ) const { return m_flags; } + + void Save( int fd, unsigned int version ) const; + void Load( SteamFile *file, unsigned int version ); + + const Vector *GetPosition( void ) const { return &m_pos; } ///< get the position of the hiding spot + unsigned int GetID( void ) const { return m_id; } + + void Mark( void ) { m_marker = m_masterMarker; } + bool IsMarked( void ) const { return (m_marker == m_masterMarker) ? true : false; } + static void ChangeMasterMarker( void ) { ++m_masterMarker; } + +private: + friend void DestroyHidingSpots( void ); + + Vector m_pos; ///< world coordinates of the spot + unsigned int m_id; ///< this spot's unique ID + unsigned int m_marker; ///< this spot's unique marker + + unsigned char m_flags; ///< bit flags + + static unsigned int m_nextID; ///< used when allocating spot ID's + static unsigned int m_masterMarker; ///< used to mark spots +}; +typedef std::list HidingSpotList; +extern HidingSpotList TheHidingSpotList; + +extern HidingSpot *GetHidingSpotByID( unsigned int id ); + +//-------------------------------------------------------------------------------------------------------------- +/** + * Stores a pointer to an interesting "spot", and a parametric distance along a path + */ +struct SpotOrder +{ + float t; ///< parametric distance along ray where this spot first has LOS to our path + union + { + HidingSpot *spot; ///< the spot to look at + unsigned int id; ///< spot ID for save/load + }; +}; +typedef std::list SpotOrderList; + +/** + * This struct stores possible path segments thru a CNavArea, and the dangerous spots + * to look at as we traverse that path segment. + */ +struct SpotEncounter +{ + NavConnect from; + NavDirType fromDir; + NavConnect to; + NavDirType toDir; + Ray path; ///< the path segment + SpotOrderList spotList; ///< list of spots to look at, in order of occurrence +}; +typedef std::list SpotEncounterList; + + +//------------------------------------------------------------------------------------------------------------------- +/** + * A CNavArea is a rectangular region defining a walkable area in the map + */ +class CNavArea +{ +public: + CNavArea( CNavNode *nwNode, CNavNode *neNode, CNavNode *seNode, CNavNode *swNode ); + CNavArea( void ); + CNavArea( const Vector *corner, const Vector *otherCorner ); + CNavArea( const Vector *nwCorner, const Vector *neCorner, const Vector *seCorner, const Vector *swCorner ); + + ~CNavArea(); + + void ConnectTo( CNavArea *area, NavDirType dir ); ///< connect this area to given area in given direction + void Disconnect( CNavArea *area ); ///< disconnect this area from given area + + void Save( FILE *fp ) const; + void Save( int fd, unsigned int version ); + void Load( SteamFile *file, unsigned int version ); + NavErrorType PostLoad( void ); + + unsigned int GetID( void ) const { return m_id; } + + void SetAttributes( unsigned char bits ) { m_attributeFlags = bits; } + unsigned char GetAttributes( void ) const { return m_attributeFlags; } + + void SetPlace( Place place ) { m_place = place; } ///< set place descriptor + Place GetPlace( void ) const { return m_place; } ///< get place descriptor + + bool IsOverlapping( const Vector *pos ) const; ///< return true if 'pos' is within 2D extents of area. + bool IsOverlapping( const CNavArea *area ) const; ///< return true if 'area' overlaps our 2D extents + bool IsOverlappingX( const CNavArea *area ) const; ///< return true if 'area' overlaps our X extent + bool IsOverlappingY( const CNavArea *area ) const; ///< return true if 'area' overlaps our Y extent + int GetPlayerCount( int teamID = 0, CBasePlayer *ignore = NULL ) const; ///< return number of players with given teamID in this area (teamID == 0 means any/all) + float GetZ( const Vector *pos ) const; ///< return Z of area at (x,y) of 'pos' + float GetZ( float x, float y ) const; ///< return Z of area at (x,y) of 'pos' + bool Contains( const Vector *pos ) const; ///< return true if given point is on or above this area, but no others + bool IsCoplanar( const CNavArea *area ) const; ///< return true if this area and given area are approximately co-planar + void GetClosestPointOnArea( const Vector *pos, Vector *close ) const; ///< return closest point to 'pos' on this area - returned point in 'close' + float GetDistanceSquaredToPoint( const Vector *pos ) const; ///< return shortest distance between point and this area + bool IsDegenerate( void ) const; ///< return true if this area is badly formed + + bool IsEdge( NavDirType dir ) const; ///< return true if there are no bi-directional links on the given side + + int GetAdjacentCount( NavDirType dir ) const { return m_connect[ dir ].size(); } ///< return number of connected areas in given direction + CNavArea *GetAdjacentArea( NavDirType dir, int i ) const; /// return the i'th adjacent area in the given direction + CNavArea *GetRandomAdjacentArea( NavDirType dir ) const; + + const NavConnectList *GetAdjacentList( NavDirType dir ) const { return &m_connect[dir]; } + bool IsConnected( const CNavArea *area, NavDirType dir ) const; ///< return true if given area is connected in given direction + float ComputeHeightChange( const CNavArea *area ); ///< compute change in height from this area to given area + + const NavLadderList *GetLadderList( LadderDirectionType dir ) const { return &m_ladder[dir]; } + + void ComputePortal( const CNavArea *to, NavDirType dir, Vector *center, float *halfWidth ) const; ///< compute portal to adjacent area + void ComputeClosestPointInPortal( const CNavArea *to, NavDirType dir, const Vector *fromPos, Vector *closePos ) const; ///< compute closest point within the "portal" between to adjacent areas + NavDirType ComputeDirection( Vector *point ) const; ///< return direction from this area to the given point + + //- for hunting algorithm --------------------------------------------------------------------------- + void SetClearedTimestamp( int teamID ) { m_clearedTimestamp[ teamID ] = gpGlobals->time; } ///< set this area's "clear" timestamp to now + float GetClearedTimestamp( int teamID ) { return m_clearedTimestamp[ teamID ]; } ///< get time this area was marked "clear" + + //- hiding spots ------------------------------------------------------------------------------------ + const HidingSpotList *GetHidingSpotList( void ) const { return &m_hidingSpotList; } + void ComputeHidingSpots( void ); ///< analyze local area neighborhood to find "hiding spots" in this area - for map learning + void ComputeSniperSpots( void ); ///< analyze local area neighborhood to find "sniper spots" in this area - for map learning + + SpotEncounter *GetSpotEncounter( const CNavArea *from, const CNavArea *to ); ///< given the areas we are moving between, return the spots we will encounter + void ComputeSpotEncounters( void ); ///< compute spot encounter data - for map learning + + //- "danger" ---------------------------------------------------------------------------------------- + void IncreaseDanger( int teamID, float amount ); ///< increase the danger of this area for the given team + float GetDanger( int teamID ); ///< return the danger of this area (decays over time) + + float GetSizeX( void ) const { return m_extent.hi.x - m_extent.lo.x; } + float GetSizeY( void ) const { return m_extent.hi.y - m_extent.lo.y; } + const Extent *GetExtent( void ) const { return &m_extent; } + const Vector *GetCenter( void ) const { return &m_center; } + const Vector *GetCorner( NavCornerType corner ) const; + + //- approach areas ---------------------------------------------------------------------------------- + struct ApproachInfo + { + NavConnect here; ///< the approach area + NavConnect prev; ///< the area just before the approach area on the path + NavTraverseType prevToHereHow; + NavConnect next; ///< the area just after the approach area on the path + NavTraverseType hereToNextHow; + }; + const ApproachInfo *GetApproachInfo( int i ) const { return &m_approach[i]; } + int GetApproachInfoCount( void ) const { return m_approachCount; } + void ComputeApproachAreas( void ); ///< determine the set of "approach areas" - for map learning + + //- A* pathfinding algorithm ------------------------------------------------------------------------ + static void MakeNewMarker( void ) { ++m_masterMarker; if (m_masterMarker == 0) m_masterMarker = 1; } + void Mark( void ) { m_marker = m_masterMarker; } + BOOL IsMarked( void ) const { return (m_marker == m_masterMarker) ? true : false; } + + void SetParent( CNavArea *parent, NavTraverseType how = NUM_TRAVERSE_TYPES ) { m_parent = parent; m_parentHow = how; } + CNavArea *GetParent( void ) const { return m_parent; } + NavTraverseType GetParentHow( void ) const { return m_parentHow; } + + bool IsOpen( void ) const; ///< true if on "open list" + void AddToOpenList( void ); ///< add to open list in decreasing value order + void UpdateOnOpenList( void ); ///< a smaller value has been found, update this area on the open list + void RemoveFromOpenList( void ); + static bool IsOpenListEmpty( void ); + static CNavArea *PopOpenList( void ); ///< remove and return the first element of the open list + + bool IsClosed( void ) const; ///< true if on "closed list" + void AddToClosedList( void ); ///< add to the closed list + void RemoveFromClosedList( void ); + + static void ClearSearchLists( void ); ///< clears the open and closed lists for a new search + + void SetTotalCost( float value ) { m_totalCost = value; } + float GetTotalCost( void ) const { return m_totalCost; } + + void SetCostSoFar( float value ) { m_costSoFar = value; } + float GetCostSoFar( void ) const { return m_costSoFar; } + + //- editing ----------------------------------------------------------------------------------------- + void Draw( byte red, byte green, byte blue, int duration = 50 ); ///< draw area for debugging & editing + void DrawConnectedAreas( void ); + void DrawMarkedCorner( NavCornerType corner, byte red, byte green, byte blue, int duration = 50 ); + bool SplitEdit( bool splitAlongX, float splitEdge, CNavArea **outAlpha = NULL, CNavArea **outBeta = NULL ); ///< split this area into two areas at the given edge + bool MergeEdit( CNavArea *adj ); ///< merge this area and given adjacent area + bool SpliceEdit( CNavArea *other ); ///< create a new area between this area and given area + void RaiseCorner( NavCornerType corner, int amount ); ///< raise/lower a corner (or all corners if corner == NUM_CORNERS) + + //- ladders ----------------------------------------------------------------------------------------- + void AddLadderUp( CNavLadder *ladder ) { m_ladder[ LADDER_UP ].push_back( ladder ); } + void AddLadderDown( CNavLadder *ladder ) { m_ladder[ LADDER_DOWN ].push_back( ladder ); } + +private: + friend void ConnectGeneratedAreas( void ); + friend void MergeGeneratedAreas( void ); + friend void MarkJumpAreas( void ); + friend bool SaveNavigationMap( const char *filename ); + friend NavErrorType LoadNavigationMap( void ); + friend void DestroyNavigationMap( void ); + friend void DestroyHidingSpots( void ); + friend void StripNavigationAreas( void ); + friend class CNavAreaGrid; + friend class CCSBotManager; + + void Initialize( void ); ///< to keep constructors consistent + static bool m_isReset; ///< if true, don't bother cleaning up in destructor since everything is going away + + static unsigned int m_nextID; ///< used to allocate unique IDs + unsigned int m_id; ///< unique area ID + Extent m_extent; ///< extents of area in world coords (NOTE: lo.z is not necessarily the minimum Z, but corresponds to Z at point (lo.x, lo.y), etc + Vector m_center; ///< centroid of area + unsigned char m_attributeFlags; ///< set of attribute bit flags (see NavAttributeType) + Place m_place; ///< place descriptor + + /// height of the implicit corners + float m_neZ; + float m_swZ; + + enum { MAX_AREA_TEAMS = 2 }; + + //- for hunting ------------------------------------------------------------------------------------- + float m_clearedTimestamp[ MAX_AREA_TEAMS ]; ///< time this area was last "cleared" of enemies + + //- "danger" ---------------------------------------------------------------------------------------- + float m_danger[ MAX_AREA_TEAMS ]; ///< danger of this area, allowing bots to avoid areas where they died in the past - zero is no danger + float m_dangerTimestamp[ MAX_AREA_TEAMS ]; ///< time when danger value was set - used for decaying + void DecayDanger( void ); + + //- hiding spots ------------------------------------------------------------------------------------ + HidingSpotList m_hidingSpotList; + bool IsHidingSpotCollision( const Vector *pos ) const; ///< returns true if an existing hiding spot is too close to given position + + //- encounter spots --------------------------------------------------------------------------------- + SpotEncounterList m_spotEncounterList; ///< list of possible ways to move thru this area, and the spots to look at as we do + void AddSpotEncounters( const CNavArea *from, NavDirType fromDir, const CNavArea *to, NavDirType toDir ); ///< add spot encounter data when moving from area to area + + //- approach areas ---------------------------------------------------------------------------------- + enum { MAX_APPROACH_AREAS = 16 }; + ApproachInfo m_approach[ MAX_APPROACH_AREAS ]; + unsigned char m_approachCount; + + void Strip( void ); ///< remove "analyzed" data from nav area + + //- A* pathfinding algorithm ------------------------------------------------------------------------ + static unsigned int m_masterMarker; + unsigned int m_marker; ///< used to flag the area as visited + CNavArea *m_parent; ///< the area just prior to this on in the search path + NavTraverseType m_parentHow; ///< how we get from parent to us + float m_totalCost; ///< the distance so far plus an estimate of the distance left + float m_costSoFar; ///< distance travelled so far + + static CNavArea *m_openList; + CNavArea *m_nextOpen, *m_prevOpen; ///< only valid if m_openMarker == m_masterMarker + unsigned int m_openMarker; ///< if this equals the current marker value, we are on the open list + + //- connections to adjacent areas ------------------------------------------------------------------- + NavConnectList m_connect[ NUM_DIRECTIONS ]; ///< a list of adjacent areas for each direction + NavLadderList m_ladder[ NUM_LADDER_DIRECTIONS ]; ///< list of ladders leading up and down from this area + + //--------------------------------------------------------------------------------------------------- + CNavNode *m_node[ NUM_CORNERS ]; ///< nav nodes at each corner of the area + + void FinishMerge( CNavArea *adjArea ); ///< recompute internal data once nodes have been adjusted during merge + void MergeAdjacentConnections( CNavArea *adjArea ); ///< for merging with "adjArea" - pick up all of "adjArea"s connections + void AssignNodes( CNavArea *area ); ///< assign internal nodes to the given area + + void FinishSplitEdit( CNavArea *newArea, NavDirType ignoreEdge ); ///< given the portion of the original area, update its internal data + + std::list m_overlapList; ///< list of areas that overlap this area + + void OnDestroyNotify( CNavArea *dead ); ///< invoked when given area is going away + + CNavArea *m_prevHash, *m_nextHash; ///< for hash table in CNavAreaGrid +}; + +typedef std::list NavAreaList; +extern NavAreaList TheNavAreaList; + + +// +// Inlines +// + +inline bool CNavArea::IsDegenerate( void ) const +{ + return (m_extent.lo.x >= m_extent.hi.x || m_extent.lo.y >= m_extent.hi.y); +} + +inline CNavArea *CNavArea::GetAdjacentArea( NavDirType dir, int i ) const +{ + NavConnectList::const_iterator iter; + for( iter = m_connect[dir].begin(); iter != m_connect[dir].end(); ++iter ) + { + if (i == 0) + return (*iter).area; + --i; + } + + return NULL; +} + +inline bool CNavArea::IsOpen( void ) const +{ + return (m_openMarker == m_masterMarker) ? true : false; +} + +inline bool CNavArea::IsOpenListEmpty( void ) +{ + return (m_openList) ? false : true; +} + +inline CNavArea *CNavArea::PopOpenList( void ) +{ + if (m_openList) + { + CNavArea *area = m_openList; + + // disconnect from list + area->RemoveFromOpenList(); + + return area; + } + + return NULL; +} + +inline bool CNavArea::IsClosed( void ) const +{ + if (IsMarked() && !IsOpen()) + return true; + + return false; +} + +inline void CNavArea::AddToClosedList( void ) +{ + Mark(); +} + +inline void CNavArea::RemoveFromClosedList( void ) +{ + // since "closed" is defined as visited (marked) and not on open list, do nothing +} + +//-------------------------------------------------------------------------------------------------------------- + +/** + * The CNavAreaGrid is used to efficiently access navigation areas by world position. + * Each cell of the grid contains a list of areas that overlap it. + * Given a world position, the corresponding grid cell is ( x/cellsize, y/cellsize ). + */ +class CNavAreaGrid +{ +public: + CNavAreaGrid( void ); + ~CNavAreaGrid(); + + void Reset( void ); ///< clear the grid to empty + void Initialize( float minX, float maxX, float minY, float maxY ); ///< clear and reset the grid to the given extents + void AddNavArea( CNavArea *area ); ///< add an area to the grid + void RemoveNavArea( CNavArea *area ); ///< remove an area from the grid + unsigned int GetNavAreaCount( void ) const { return m_areaCount; } ///< return total number of nav areas + + CNavArea *GetNavArea( const Vector *pos, float beneathLimt = 120.0f ) const; ///< given a position, return the nav area that IsOverlapping and is *immediately* beneath it + CNavArea *GetNavAreaByID( unsigned int id ) const; + CNavArea *GetNearestNavArea( const Vector *pos, bool anyZ = false ) const; + + Place GetPlace( const Vector *pos ) const; ///< return radio chatter place for given coordinate + +private: + const float m_cellSize; + NavAreaList *m_grid; + int m_gridSizeX; + int m_gridSizeY; + float m_minX; + float m_minY; + unsigned int m_areaCount; ///< total number of nav areas + + enum { HASH_TABLE_SIZE = 256 }; + CNavArea *m_hashTable[ HASH_TABLE_SIZE ]; ///< hash table to optimize lookup by ID + inline int ComputeHashKey( unsigned int id ) const ///< returns a hash key for the given nav area ID + { + return id & 0xFF; + } + + + inline int WorldToGridX( float wx ) const + { + int x = (wx - m_minX) / m_cellSize; + if (x < 0) + x = 0; + else if (x >= m_gridSizeX) + x = m_gridSizeX-1; + + return x; + } + + inline int WorldToGridY( float wy ) const + { + int y = (wy - m_minY) / m_cellSize; + if (y < 0) + y = 0; + else if (y >= m_gridSizeY) + y = m_gridSizeY-1; + + return y; + } +}; + +extern CNavAreaGrid TheNavAreaGrid; + +//-------------------------------------------------------------------------------------------------------------- +// +// Function prototypes +// +extern NavErrorType LoadNavigationMap( void ); +extern void GenerateNavigationAreaMesh( void ); + +extern void SanityCheckNavigationMap( const char *mapName ); ///< Performs a lightweight sanity-check of the specified map's nav mesh + +extern void ApproachAreaAnalysisPrep( void ); +extern void CleanupApproachAreaAnalysisPrep( void ); + +extern void BuildLadders( void ); + +extern bool TestArea( CNavNode *node, int width, int height ); +extern int BuildArea( CNavNode *node, int width, int height ); + +extern bool GetGroundHeight( const Vector *pos, float *height, Vector *normal = NULL ); +extern bool GetSimpleGroundHeight( const Vector *pos, float *height, Vector *normal = NULL ); + +class CBasePlayer; +class CBaseEntity; + +extern bool IsSpotOccupied( CBaseEntity *me, const Vector *pos ); // if a player is at the given spot, return true + +extern const Vector *FindNearbyHidingSpot( CBaseEntity *me, const Vector *pos, CNavArea *currentArea, float maxRange = 1000.0f, bool isSniper = false, bool useNearest = false ); +extern const Vector *FindRandomHidingSpot( CBaseEntity *me, Place place, bool isSniper = false ); + +#define NO_CROUCH_SPOTS false +extern const Vector *FindNearbyRetreatSpot( CBaseEntity *me, const Vector *start, CNavArea *startArea, float maxRange = 1000.0f, int avoidTeam = 0, bool useCrouchAreas = true ); + +/// return true if moving from "start" to "finish" will cross a player's line of fire. +extern bool IsCrossingLineOfFire( const Vector &start, const Vector &finish, CBaseEntity *ignore = NULL, int ignoreTeam = 0 ); + +extern void IncreaseDangerNearby( int teamID, float amount, CNavArea *area, const Vector *pos, float maxRadius ); +extern void DrawDanger( void ); + +enum NavEditCmdType +{ + EDIT_NONE, + EDIT_DELETE, ///< delete current area + EDIT_SPLIT, ///< split current area + EDIT_MERGE, ///< merge adjacent areas + EDIT_JOIN, ///< define connection between areas + EDIT_BREAK, ///< break connection between areas + EDIT_MARK, ///< mark an area for further operations + EDIT_ATTRIB_CROUCH, ///< toggle crouch attribute on current area + EDIT_ATTRIB_JUMP, ///< toggle jump attribute on current area + EDIT_ATTRIB_PRECISE, ///< toggle precise attribute on current area + EDIT_ATTRIB_NO_JUMP, ///< toggle inhibiting discontinuity jumping in current area + EDIT_BEGIN_AREA, ///< begin creating a new nav area + EDIT_END_AREA, ///< end creation of the new nav area + EDIT_CONNECT, ///< connect marked area to selected area + EDIT_DISCONNECT, ///< disconnect marked area from selected area + EDIT_SPLICE, ///< create new area in between marked and selected areas + EDIT_TOGGLE_PLACE_MODE, ///< switch between normal and place editing + EDIT_TOGGLE_PLACE_PAINTING, ///< switch between "painting" places onto areas + EDIT_PLACE_FLOODFILL, ///< floodfill areas out from current area + EDIT_PLACE_PICK, ///< "pick up" the place at the current area + EDIT_MARK_UNNAMED, ///< mark an unnamed area for further operations + EDIT_WARP_TO_MARK, ///< warp a spectating local player to the selected mark + EDIT_SELECT_CORNER, ///< select a corner on the current area + EDIT_RAISE_CORNER, ///< raise a corner on the current area + EDIT_LOWER_CORNER, ///< lower a corner on the current area +}; + +extern void EditNavAreasReset( void ); +extern void EditNavAreas( NavEditCmdType cmd ); +extern CNavArea *GetMarkedArea( void ); + + + +//-------------------------------------------------------------------------------------------------------------- +// +// Templates +// + +//-------------------------------------------------------------------------------------------------------------- +/** + * Functor used with NavAreaBuildPath() + */ +class ShortestPathCost +{ +public: + float operator() ( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder ) + { + if (fromArea == NULL) + { + // first area in path, no cost + return 0.0f; + } + else + { + // compute distance travelled along path so far + float dist; + + if (ladder) + dist = ladder->m_length; + else + dist = (*area->GetCenter() - *fromArea->GetCenter()).Length(); + + float cost = dist + fromArea->GetCostSoFar(); + + // if this is a "crouch" area, add penalty + if (area->GetAttributes() & NAV_CROUCH) + { + const float crouchPenalty = 20.0f; // 10 + cost += crouchPenalty * dist; + } + + // if this is a "jump" area, add penalty + if (area->GetAttributes() & NAV_JUMP) + { + const float jumpPenalty = 5.0f; + cost += jumpPenalty * dist; + } + + return cost; + } + } +}; + +//-------------------------------------------------------------------------------------------------------------- +/** + * Find path from startArea to goalArea via an A* search, using supplied cost heuristic. + * If cost functor returns -1 for an area, that area is considered a dead end. + * This doesn't actually build a path, but the path is defined by following parent + * pointers back from goalArea to startArea. + * If 'closestArea' is non-NULL, the closest area to the goal is returned (useful if the path fails). + * If 'goalArea' is NULL, will compute a path as close as possible to 'goalPos'. + * If 'goalPos' is NULL, will use the center of 'goalArea' as the goal position. + * Returns true if a path exists. + */ +template< typename CostFunctor > +bool NavAreaBuildPath( CNavArea *startArea, CNavArea *goalArea, const Vector *goalPos, CostFunctor &costFunc, CNavArea **closestArea = NULL ) +{ + if (closestArea) + *closestArea = NULL; + + if (startArea == NULL) + return false; + + // + // If goalArea is NULL, this function will return the closest area to the goal. + // However, if there is also no goal, we can't do anything. + // + if (goalArea == NULL && goalPos == NULL) + { + return false; + } + + startArea->SetParent( NULL ); + + // if we are already in the goal area, build trivial path + if (startArea == goalArea) + { + goalArea->SetParent( NULL ); + + if (closestArea) + *closestArea = goalArea; + + return true; + } + + // determine actual goal position + Vector actualGoalPos = (goalPos) ? *goalPos : *goalArea->GetCenter(); + + // start search + CNavArea::ClearSearchLists(); + + // compute estimate of path length + /// @todo Cost might work as "manhattan distance" + startArea->SetTotalCost( (*startArea->GetCenter() - actualGoalPos).Length() ); + + float initCost = costFunc( startArea, NULL, NULL ); + if (initCost < 0.0f) + return false; + startArea->SetCostSoFar( initCost ); + + startArea->AddToOpenList(); + + // keep track of the area we visit that is closest to the goal + if (closestArea) + *closestArea = startArea; + float closestAreaDist = startArea->GetTotalCost(); + + // do A* search + while( !CNavArea::IsOpenListEmpty() ) + { + // get next area to check + CNavArea *area = CNavArea::PopOpenList(); + + // check if we have found the goal area + if (area == goalArea) + { + if (closestArea) + *closestArea = goalArea; + + return true; + } + + // search adjacent areas + bool searchFloor = true; + int dir = NORTH; + const NavConnectList *floorList = area->GetAdjacentList( NORTH ); + NavConnectList::const_iterator floorIter = floorList->begin(); + + bool ladderUp = true; + const NavLadderList *ladderList = NULL; + NavLadderList::const_iterator ladderIter; + enum { AHEAD = 0, LEFT, RIGHT, BEHIND, NUM_TOP_DIRECTIONS }; + int ladderTopDir; + + while(true) + { + CNavArea *newArea; + NavTraverseType how; + const CNavLadder *ladder = NULL; + + // + // Get next adjacent area - either on floor or via ladder + // + if (searchFloor) + { + // if exhausted adjacent connections in current direction, begin checking next direction + if (floorIter == floorList->end()) + { + ++dir; + + if (dir == NUM_DIRECTIONS) + { + // checked all directions on floor - check ladders next + searchFloor = false; + + ladderList = area->GetLadderList( LADDER_UP ); + ladderIter = ladderList->begin(); + ladderTopDir = AHEAD; + } + else + { + // start next direction + floorList = area->GetAdjacentList( (NavDirType)dir ); + floorIter = floorList->begin(); + } + + continue; + } + + newArea = (*floorIter).area; + how = (NavTraverseType)dir; + ++floorIter; + } + else // search ladders + { + if (ladderIter == ladderList->end()) + { + if (!ladderUp) + { + // checked both ladder directions - done + break; + } + else + { + // check down ladders + ladderUp = false; + ladderList = area->GetLadderList( LADDER_DOWN ); + ladderIter = ladderList->begin(); + } + continue; + } + + if (ladderUp) + { + ladder = *ladderIter; + + // cannot use this ladder if the ladder bottom is hanging above our head + if (ladder->m_isDangling) + { + ++ladderIter; + continue; + } + + // do not use BEHIND connection, as its very hard to get to when going up a ladder + if (ladderTopDir == AHEAD) + newArea = ladder->m_topForwardArea; + else if (ladderTopDir == LEFT) + newArea = ladder->m_topLeftArea; + else if (ladderTopDir == RIGHT) + newArea = ladder->m_topRightArea; + else + { + ++ladderIter; + continue; + } + + how = GO_LADDER_UP; + ++ladderTopDir; + } + else + { + newArea = (*ladderIter)->m_bottomArea; + how = GO_LADDER_DOWN; + ladder = (*ladderIter); + ++ladderIter; + } + + if (newArea == NULL) + continue; + } + + // don't backtrack + if (newArea == area) + continue; + + float newCostSoFar = costFunc( newArea, area, ladder ); + + // check if cost functor says this area is a dead-end + if (newCostSoFar < 0.0f) + continue; + + if ((newArea->IsOpen() || newArea->IsClosed()) && newArea->GetCostSoFar() <= newCostSoFar) + { + // this is a worse path - skip it + continue; + } + else + { + // compute estimate of distance left to go + float newCostRemaining = (*newArea->GetCenter() - actualGoalPos).Length(); + + // track closest area to goal in case path fails + if (closestArea && newCostRemaining < closestAreaDist) + { + *closestArea = newArea; + closestAreaDist = newCostRemaining; + } + + newArea->SetParent( area, how ); + newArea->SetCostSoFar( newCostSoFar ); + newArea->SetTotalCost( newCostSoFar + newCostRemaining ); + + if (newArea->IsClosed()) + newArea->RemoveFromClosedList(); + + if (newArea->IsOpen()) + { + // area already on open list, update the list order to keep costs sorted + newArea->UpdateOnOpenList(); + } + else + { + newArea->AddToOpenList(); + } + } + } + + // we have searched this area + area->AddToClosedList(); + } + + return false; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Compute distance between two areas. Return -1 if can't reach 'endArea' from 'startArea'. + */ +template< typename CostFunctor > +float NavAreaTravelDistance( CNavArea *startArea, CNavArea *endArea, CostFunctor &costFunc ) +{ + if (startArea == NULL) + return -1.0f; + + if (endArea == NULL) + return -1.0f; + + if (startArea == endArea) + return 0.0f; + + // compute path between areas using given cost heuristic + if (NavAreaBuildPath( startArea, endArea, NULL, costFunc ) == false) + return -1.0f; + + // compute distance along path + float distance = 0.0f; + for( CNavArea *area = endArea; area->GetParent(); area = area->GetParent() ) + { + distance += (*area->GetCenter() - *area->GetParent()->GetCenter()).Length(); + } + + return distance; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Compute distance from area to position. Return -1 if can't reach position. + */ +template< typename CostFunctor > +float NavAreaTravelDistance( const Vector *startPos, CNavArea *startArea, const Vector *goalPos, CostFunctor &costFunc ) +{ + if (startArea == NULL || startPos == NULL || goalPos == NULL) + return -1.0f; + + // compute path between areas using given cost heuristic + CNavArea *goalArea = NULL; + if (NavAreaBuildPath( startArea, TheNavAreaGrid.GetNearestNavArea( goalPos ), goalPos, costFunc, &goalArea ) == false) + return -1.0f; + + if (goalArea == NULL) + return -1.0f; + + // compute distance along path + if (goalArea->GetParent() == NULL) + { + return (*goalPos - *startPos).Length(); + } + else + { + CNavArea *area = goalArea->GetParent(); + + float distance = (*goalPos - *area->GetCenter()).Length(); + + for( ; area->GetParent(); area = area->GetParent() ) + { + distance += (*area->GetCenter() - *area->GetParent()->GetCenter()).Length(); + } + + return distance; + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Do a breadth-first search, invoking functor on each area. + * If functor returns 'true', continue searching from this area. + * If functor returns 'false', the area's adjacent areas are not explored (dead end). + * If 'maxRange' is 0 or less, no range check is done (all areas will be examined). + * + * NOTE: Returns all areas that overlap range, even partially + * + * @todo Use ladder connections + */ + +// helper function +inline void AddAreaToOpenList( CNavArea *area, CNavArea *parent, const Vector *startPos, float maxRange ) +{ + if (area == NULL) + return; + + if (!area->IsMarked()) + { + area->Mark(); + area->SetTotalCost( 0.0f ); + area->SetParent( parent ); + + if (maxRange > 0.0f) + { + // make sure this area overlaps range + Vector closePos; + area->GetClosestPointOnArea( startPos, &closePos ); + if ((closePos - *startPos).Make2D().IsLengthLessThan( maxRange )) + { + // compute approximate distance along path to limit travel range, too + float distAlong = parent->GetCostSoFar(); + distAlong += (*area->GetCenter() - *parent->GetCenter()).Length(); + area->SetCostSoFar( distAlong ); + + // allow for some fudge due to large size areas + if (distAlong <= 1.5f * maxRange) + area->AddToOpenList(); + } + } + else + { + // infinite range + area->AddToOpenList(); + } + } +} + + +template < typename Functor > +void SearchSurroundingAreas( CNavArea *startArea, const Vector *startPos, Functor &func, float maxRange = -1.0f ) +{ + if (startArea == NULL || startPos == NULL) + return; + + CNavArea::MakeNewMarker(); + CNavArea::ClearSearchLists(); + + startArea->AddToOpenList(); + startArea->SetTotalCost( 0.0f ); + startArea->SetCostSoFar( 0.0f ); + startArea->SetParent( NULL ); + startArea->Mark(); + + while( !CNavArea::IsOpenListEmpty() ) + { + // get next area to check + CNavArea *area = CNavArea::PopOpenList(); + + // invoke functor on area + if (func( area )) + { + // explore adjacent floor areas + for( int dir=0; dirGetAdjacentCount( (NavDirType)dir ); + for( int i=0; iGetAdjacentArea( (NavDirType)dir, i ); + + AddAreaToOpenList( adjArea, area, startPos, maxRange ); + } + } + + + // explore adjacent areas connected by ladders + NavLadderList::const_iterator ladderIt; + + // check up ladders + const NavLadderList *ladderList = area->GetLadderList( LADDER_UP ); + if (ladderList) + { + for( ladderIt = ladderList->begin(); ladderIt != ladderList->end(); ++ladderIt ) + { + const CNavLadder *ladder = *ladderIt; + + // cannot use this ladder if the ladder bottom is hanging above our head + if (ladder->m_isDangling) + { + continue; + } + + // do not use BEHIND connection, as its very hard to get to when going up a ladder + AddAreaToOpenList( ladder->m_topForwardArea, area, startPos, maxRange ); + AddAreaToOpenList( ladder->m_topLeftArea, area, startPos, maxRange ); + AddAreaToOpenList( ladder->m_topRightArea, area, startPos, maxRange ); + } + } + + // check down ladders + ladderList = area->GetLadderList( LADDER_DOWN ); + if (ladderList) + { + for( ladderIt = ladderList->begin(); ladderIt != ladderList->end(); ++ladderIt ) + { + const CNavLadder *ladder = *ladderIt; + + AddAreaToOpenList( ladder->m_bottomArea, area, startPos, maxRange ); + } + } + } + } +} + + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Apply the functor to all navigation areas + */ +template < typename Functor > +void ForAllAreas( Functor &func ) +{ + NavAreaList::iterator iter; + for( iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter ) + { + CNavArea *area = *iter; + func( area ); + } +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Fuctor that returns lowest cost for farthest away areas + * For use with FindMinimumCostArea() + */ +class FarAwayFunctor +{ +public: + float operator() ( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder ) + { + if (area == fromArea) + return 9999999.9f; + + return 1.0f/(*fromArea->GetCenter() - *area->GetCenter()).Length(); + } +}; + +/** + * Fuctor that returns lowest cost for areas farthest from given position + * For use with FindMinimumCostArea() + */ +class FarAwayFromPositionFunctor +{ +public: + FarAwayFromPositionFunctor( const Vector *pos ) + { + m_pos = pos; + } + + float operator() ( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder ) + { + return 1.0f/(*m_pos - *area->GetCenter()).Length(); + } + +private: + const Vector *m_pos; +}; + + +/** + * Pick a low-cost area of "decent" size + */ +template< typename CostFunctor > +CNavArea *FindMinimumCostArea( CNavArea *startArea, CostFunctor &costFunc ) +{ + const float minSize = 150.0f; + + // collect N low-cost areas of a decent size + enum { NUM_CHEAP_AREAS = 32 }; + struct + { + CNavArea *area; + float cost; + } + cheapAreaSet[ NUM_CHEAP_AREAS ]; + int cheapAreaSetCount = 0; + + NavAreaList::iterator iter; + for( iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter ) + { + CNavArea *area = *iter; + + // skip the small areas + const Extent *extent = area->GetExtent(); + if (extent->hi.x - extent->lo.x < minSize || extent->hi.y - extent->lo.y < minSize) + continue; + + // compute cost of this area + float cost = costFunc( area, startArea, NULL ); + + if (cheapAreaSetCount < NUM_CHEAP_AREAS) + { + cheapAreaSet[ cheapAreaSetCount ].area = area; + cheapAreaSet[ cheapAreaSetCount++ ].cost = cost; + } + else + { + // replace most expensive cost if this is cheaper + int expensive = 0; + for( int i=1; i cheapAreaSet[expensive].cost) + expensive = i; + + if (cheapAreaSet[expensive].cost > cost) + { + cheapAreaSet[expensive].area = area; + cheapAreaSet[expensive].cost = cost; + } + } + } + + if (cheapAreaSetCount) + { + // pick one of the areas at random + return cheapAreaSet[ RANDOM_LONG( 0, cheapAreaSetCount-1 ) ].area; + } + else + { + // degenerate case - no decent sized areas - pick a random area + int numAreas = TheNavAreaList.size(); + int which = RANDOM_LONG( 0, numAreas-1 ); + + NavAreaList::iterator iter; + for( iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter ) + if (which-- == 0) + break; + + return *iter; + } +} + + +#endif // _NAV_AREA_H_ diff --git a/game_shared/bot/nav_file.cpp b/game_shared/bot/nav_file.cpp new file mode 100644 index 0000000..b18bc25 --- /dev/null +++ b/game_shared/bot/nav_file.cpp @@ -0,0 +1,1080 @@ +// nav_file.cpp +// Reading and writing nav files +// Author: Michael S. Booth (mike@turtlerockstudios.com), January-September 2003 + +#pragma warning( disable : 4530 ) // STL uses exceptions, but we are not compiling with them - ignore warning +#pragma warning( disable : 4786 ) // long STL names get truncated in browse info. + +#include +#include +#include + +#include +#include +#include + +#ifdef _WIN32 +#include + +#else +#include +#define _write write +#define _close close +#define MAX_OSPATH PATH_MAX +#endif + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "gamerules.h" + +#include "bot_util.h" + +/// @todo Abstract these out of here (TheBotPhrases) +#include "cs_bot.h" +#include "cs_bot_manager.h" + +#include "nav.h" +#include "nav_node.h" +#include "nav_area.h" + + +// +// The 'place directory' is used to save and load places from +// nav files in a size-efficient manner that also allows for the +// order of the place ID's to change without invalidating the +// nav files. +// +// The place directory is stored in the nav file as a list of +// place name strings. Each nav area then contains an index +// into that directory, or zero if no place has been assigned to +// that area. +// +class PlaceDirectory +{ +public: + + typedef unsigned short EntryType; + + void Reset( void ) + { + m_directory.clear(); + } + + /// return true if this place is already in the directory + bool IsKnown( Place place ) const + { + std::vector::const_iterator it = std::find( m_directory.begin(), m_directory.end(), place ); + + return (it != m_directory.end()); + } + + /// return the directory entry corresponding to this Place (0 = no entry) + EntryType GetEntry( Place place ) const + { + if (place == UNDEFINED_PLACE) + return 0; + + std::vector::const_iterator it = std::find( m_directory.begin(), m_directory.end(), place ); + + if (it == m_directory.end()) + { + assert( false && "PlaceDirectory::GetEntry failure" ); + return 0; + } + + return 1 + (it - m_directory.begin()); + } + + /// add the place to the directory if not already known + void AddPlace( Place place ) + { + if (place == UNDEFINED_PLACE) + return; + + assert( place < 1000 ); + + if (IsKnown( place )) + return; + + m_directory.push_back( place ); + } + + /// given an entry, return the Place + Place EntryToPlace( EntryType entry ) const + { + if (entry == 0) + return UNDEFINED_PLACE; + + int i = entry-1; + + if (i > m_directory.size()) + { + assert( false && "PlaceDirectory::EntryToPlace: Invalid entry" ); + return UNDEFINED_PLACE; + } + + return m_directory[ i ]; + } + + /// store the directory + void Save( int fd ) + { + // store number of entries in directory + EntryType count = m_directory.size(); + _write( fd, &count, sizeof(EntryType) ); + + // store entries + std::vector::iterator it; + for( it = m_directory.begin(); it != m_directory.end(); ++it ) + { + const char *placeName = TheBotPhrases->IDToName( *it ); + + // store string length followed by string itself + unsigned short len = strlen(placeName)+1; + _write( fd, &len, sizeof(unsigned short) ); + _write( fd, placeName, len ); + } + } + + /// load the directory + void Load( SteamFile *file ) + { + // read number of entries + EntryType count; + file->Read( &count, sizeof(EntryType) ); + + m_directory.reserve( count ); + + // read each entry + char placeName[256]; + unsigned short len; + for( int i=0; iRead( &len, sizeof(unsigned short) ); + file->Read( placeName, len ); + + AddPlace( TheBotPhrases->NameToID( placeName ) ); + } + } + +private: + std::vector m_directory; +}; + +static PlaceDirectory placeDirectory; + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Replace extension with "bsp" + */ +char *GetBspFilename( const char *navFilename ) +{ + static char bspFilename[256]; + + sprintf( bspFilename, "maps\\%s.bsp", STRING( gpGlobals->mapname ) ); + + int len = strlen( bspFilename ); + if (len < 3) + return NULL; + + bspFilename[ len-3 ] = 'b'; + bspFilename[ len-2 ] = 's'; + bspFilename[ len-1 ] = 'p'; + + return bspFilename; +} + +//-------------------------------------------------------------------------------------------------------------- +void CNavArea::Save( FILE *fp ) const +{ + fprintf( fp, "v %f %f %f\n", m_extent.lo.x, m_extent.lo.y, m_extent.lo.z ); + fprintf( fp, "v %f %f %f\n", m_extent.hi.x, m_extent.lo.y, m_neZ ); + fprintf( fp, "v %f %f %f\n", m_extent.hi.x, m_extent.hi.y, m_extent.hi.z ); + fprintf( fp, "v %f %f %f\n", m_extent.lo.x, m_extent.hi.y, m_swZ ); + + static int base = 1; + fprintf( fp, "\n\ng %04dArea%s%s%s%s\n", m_id, + (GetAttributes() & NAV_CROUCH) ? "CROUCH" : "", + (GetAttributes() & NAV_JUMP) ? "JUMP" : "", + (GetAttributes() & NAV_PRECISE) ? "PRECISE" : "", + (GetAttributes() & NAV_NO_JUMP) ? "NO_JUMP" : "" ); + fprintf( fp, "f %d %d %d %d\n\n", base, base+1, base+2, base+3 ); + base += 4; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Save a navigation area to the opened binary stream + */ +void CNavArea::Save( int fd, unsigned int version ) +{ + // save ID + _write( fd, &m_id, sizeof(unsigned int) ); + + // save attribute flags + _write( fd, &m_attributeFlags, sizeof(unsigned char) ); + + // save extent of area + _write( fd, &m_extent, 6*sizeof(float) ); + + // save heights of implicit corners + _write( fd, &m_neZ, sizeof(float) ); + _write( fd, &m_swZ, sizeof(float) ); + + // save connections to adjacent areas + // in the enum order NORTH, EAST, SOUTH, WEST + for( int d=0; dm_id, sizeof(unsigned int) ); + } + } + + // + // Store hiding spots for this area + // + unsigned char count; + if (m_hidingSpotList.size() > 255) + { + count = 255; + CONSOLE_ECHO( "Warning: NavArea #%d: Truncated hiding spot list to 255\n", m_id ); + } + else + { + count = m_hidingSpotList.size(); + } + _write( fd, &count, sizeof(unsigned char) ); + + // store HidingSpot objects + unsigned int saveCount = 0; + for( HidingSpotList::iterator iter = m_hidingSpotList.begin(); iter != m_hidingSpotList.end(); ++iter ) + { + HidingSpot *spot = *iter; + + spot->Save( fd, version ); + + // overflow check + if (++saveCount == count) + break; + } + + // + // Save the approach areas for this area + // + + // save number of approach areas + _write( fd, &m_approachCount, sizeof(unsigned char) ); + if (cv_bot_debug.value > 0.0f) + CONSOLE_ECHO( " m_approachCount = %d\n", m_approachCount ); + + // save approach area info + unsigned char type; + unsigned int zero = 0; + for( int a=0; am_id, sizeof(unsigned int) ); + else + _write( fd, &zero, sizeof(unsigned int) ); + + if (m_approach[a].prev.area) + _write( fd, &m_approach[a].prev.area->m_id, sizeof(unsigned int) ); + else + _write( fd, &zero, sizeof(unsigned int) ); + type = (unsigned char)m_approach[a].prevToHereHow; + _write( fd, &type, sizeof(unsigned char) ); + + if (m_approach[a].next.area) + _write( fd, &m_approach[a].next.area->m_id, sizeof(unsigned int) ); + else + _write( fd, &zero, sizeof(unsigned int) ); + type = (unsigned char)m_approach[a].hereToNextHow; + _write( fd, &type, sizeof(unsigned char) ); + } + + // + // Save encounter spots for this area + // + { + // save number of encounter paths for this area + unsigned int count = m_spotEncounterList.size(); + _write( fd, &count, sizeof(unsigned int) ); + + if (cv_bot_debug.value > 0.0f) + CONSOLE_ECHO( " m_spotEncounterList.size() = %d\n", count ); + + SpotEncounter *e; + for( SpotEncounterList::iterator iter = m_spotEncounterList.begin(); iter != m_spotEncounterList.end(); ++iter ) + { + e = &(*iter); + + if (e->from.area) + _write( fd, &e->from.area->m_id, sizeof(unsigned int) ); + else + _write( fd, &zero, sizeof(unsigned int) ); + + unsigned char dir = e->fromDir; + _write( fd, &dir, sizeof(unsigned char) ); + + if (e->to.area) + _write( fd, &e->to.area->m_id, sizeof(unsigned int) ); + else + _write( fd, &zero, sizeof(unsigned int) ); + + dir = e->toDir; + _write( fd, &dir, sizeof(unsigned char) ); + + // write list of spots along this path + unsigned char spotCount; + if (e->spotList.size() > 255) + { + spotCount = 255; + CONSOLE_ECHO( "Warning: NavArea #%d: Truncated encounter spot list to 255\n", m_id ); + } + else + { + spotCount = e->spotList.size(); + } + _write( fd, &spotCount, sizeof(unsigned char) ); + + saveCount = 0; + for( SpotOrderList::iterator oiter = e->spotList.begin(); oiter != e->spotList.end(); ++oiter ) + { + SpotOrder *order = &(*oiter); + + // order->spot may be NULL if we've loaded a nav mesh that has been edited but not re-analyzed + unsigned int id = (order->spot) ? order->spot->GetID() : 0; + _write( fd, &id, sizeof(unsigned int) ); + + unsigned char t = 255 * order->t; + _write( fd, &t, sizeof(unsigned char) ); + + // overflow check + if (++saveCount == spotCount) + break; + } + } + } + + // store place dictionary entry + PlaceDirectory::EntryType entry = placeDirectory.GetEntry( GetPlace() ); + _write( fd, &entry, sizeof(entry) ); + +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Load a navigation area from the file + */ +void CNavArea::Load( SteamFile *file, unsigned int version ) +{ + // load ID + file->Read( &m_id, sizeof(unsigned int) ); + + // update nextID to avoid collisions + if (m_id >= m_nextID) + m_nextID = m_id+1; + + // load attribute flags + file->Read( &m_attributeFlags, sizeof(unsigned char) ); + + // load extent of area + file->Read( &m_extent, 6*sizeof(float) ); + + m_center.x = (m_extent.lo.x + m_extent.hi.x)/2.0f; + m_center.y = (m_extent.lo.y + m_extent.hi.y)/2.0f; + m_center.z = (m_extent.lo.z + m_extent.hi.z)/2.0f; + + // load heights of implicit corners + file->Read( &m_neZ, sizeof(float) ); + file->Read( &m_swZ, sizeof(float) ); + + // load connections (IDs) to adjacent areas + // in the enum order NORTH, EAST, SOUTH, WEST + for( int d=0; dRead( &count, sizeof(unsigned int) ); + + for( unsigned int i=0; iRead( &connect.id, sizeof(unsigned int) ); + + m_connect[d].push_back( connect ); + } + } + + // + // Load hiding spots + // + + // load number of hiding spots + unsigned char hidingSpotCount; + file->Read( &hidingSpotCount, sizeof(unsigned char) ); + + if (version == 1) + { + // load simple vector array + Vector pos; + for( int h=0; hRead( &pos, 3 * sizeof(float) ); + + // create new hiding spot and put on master list + HidingSpot *spot = new HidingSpot( &pos, HidingSpot::IN_COVER ); + + m_hidingSpotList.push_back( spot ); + } + } + else + { + // load HidingSpot objects for this area + for( int h=0; hLoad( file, version ); + + m_hidingSpotList.push_back( spot ); + } + } + + // + // Load number of approach areas + // + file->Read( &m_approachCount, sizeof(unsigned char) ); + + // load approach area info (IDs) + unsigned char type; + for( int a=0; aRead( &m_approach[a].here.id, sizeof(unsigned int) ); + + file->Read( &m_approach[a].prev.id, sizeof(unsigned int) ); + file->Read( &type, sizeof(unsigned char) ); + m_approach[a].prevToHereHow = (NavTraverseType)type; + + file->Read( &m_approach[a].next.id, sizeof(unsigned int) ); + file->Read( &type, sizeof(unsigned char) ); + m_approach[a].hereToNextHow = (NavTraverseType)type; + } + + + // + // Load encounter paths for this area + // + unsigned int count; + file->Read( &count, sizeof(unsigned int) ); + + if (version < 3) + { + // old data, read and discard + for( unsigned int e=0; eRead( &encounter.from.id, sizeof(unsigned int) ); + file->Read( &encounter.to.id, sizeof(unsigned int) ); + + file->Read( &encounter.path.from.x, 3 * sizeof(float) ); + file->Read( &encounter.path.to.x, 3 * sizeof(float) ); + + // read list of spots along this path + unsigned char spotCount; + file->Read( &spotCount, sizeof(unsigned char) ); + + for( int s=0; sRead( &pos, 3*sizeof(float) ); + file->Read( &pos, sizeof(float) ); + } + } + return; + } + + for( unsigned int e=0; eRead( &encounter.from.id, sizeof(unsigned int) ); + + unsigned char dir; + file->Read( &dir, sizeof(unsigned char) ); + encounter.fromDir = static_cast( dir ); + + file->Read( &encounter.to.id, sizeof(unsigned int) ); + + file->Read( &dir, sizeof(unsigned char) ); + encounter.toDir = static_cast( dir ); + + // read list of spots along this path + unsigned char spotCount; + file->Read( &spotCount, sizeof(unsigned char) ); + + SpotOrder order; + for( int s=0; sRead( &order.id, sizeof(unsigned int) ); + + unsigned char t; + file->Read( &t, sizeof(unsigned char) ); + + order.t = (float)t/255.0f; + + encounter.spotList.push_back( order ); + } + + m_spotEncounterList.push_back( encounter ); + } + + if (version < 5) + return; + + // + // Load Place data + // + PlaceDirectory::EntryType entry; + file->Read( &entry, sizeof(entry) ); + + // convert entry to actual Place + SetPlace( placeDirectory.EntryToPlace( entry ) ); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Convert loaded IDs to pointers + * Make sure all IDs are converted, even if corrupt data is encountered. + */ +NavErrorType CNavArea::PostLoad( void ) +{ + NavErrorType error = NAV_OK; + + // connect areas together + for( int d=0; did; + connect->area = TheNavAreaGrid.GetNavAreaByID( id ); + if (id && connect->area == NULL) + { + CONSOLE_ECHO( "ERROR: Corrupt navigation data. Cannot connect Navigation Areas.\n" ); + error = NAV_CORRUPT_DATA; + } + } + } + + // resolve approach area IDs + for( int a=0; afrom.area = TheNavAreaGrid.GetNavAreaByID( e->from.id ); + if (e->from.area == NULL) + { + CONSOLE_ECHO( "ERROR: Corrupt navigation data. Missing \"from\" Navigation Area for Encounter Spot.\n" ); + error = NAV_CORRUPT_DATA; + } + + e->to.area = TheNavAreaGrid.GetNavAreaByID( e->to.id ); + if (e->to.area == NULL) + { + CONSOLE_ECHO( "ERROR: Corrupt navigation data. Missing \"to\" Navigation Area for Encounter Spot.\n" ); + error = NAV_CORRUPT_DATA; + } + + if (e->from.area && e->to.area) + { + // compute path + float halfWidth; + ComputePortal( e->to.area, e->toDir, &e->path.to, &halfWidth ); + ComputePortal( e->from.area, e->fromDir, &e->path.from, &halfWidth ); + + const float eyeHeight = HalfHumanHeight; + e->path.from.z = e->from.area->GetZ( &e->path.from ) + eyeHeight; + e->path.to.z = e->to.area->GetZ( &e->path.to ) + eyeHeight; + } + + // resolve HidingSpot IDs + for( SpotOrderList::iterator oiter = e->spotList.begin(); oiter != e->spotList.end(); ++oiter ) + { + SpotOrder *order = &(*oiter); + + order->spot = GetHidingSpotByID( order->id ); + if (order->spot == NULL) + { + CONSOLE_ECHO( "ERROR: Corrupt navigation data. Missing Hiding Spot\n" ); + error = NAV_CORRUPT_DATA; + } + } + } + + // build overlap list + /// @todo Optimize this + for( NavAreaList::iterator oiter = TheNavAreaList.begin(); oiter != TheNavAreaList.end(); ++oiter ) + { + CNavArea *area = *oiter; + + if (area == this) + continue; + + if (IsOverlapping( area )) + m_overlapList.push_back( area ); + } + + return error; +} + + +//-------------------------------------------------------------------------------------------------------------- +/* +============ +COM_FixSlashes + +Changes all '/' characters into '\' characters, in place. +============ +*/ +inline void COM_FixSlashes( char *pname ) +{ +#ifdef _WIN32 + while ( *pname ) + { + if ( *pname == '/' ) + *pname = '\\'; + pname++; + } +#else + while ( *pname ) + { + if ( *pname == '\\' ) + *pname = '/'; + pname++; + } +#endif +} + +/** + * Store AI navigation data to a file + */ +bool SaveNavigationMap( const char *filename ) +{ + if (filename == NULL) + return false; + + // + // Store the NAV file + // + COM_FixSlashes( const_cast(filename) ); + +#ifdef WIN32 + int fd = _open( filename, _O_BINARY | _O_CREAT | _O_WRONLY, _S_IREAD | _S_IWRITE ); +#else +#define _write write + int fd = creat( filename, S_IRUSR | S_IWUSR | S_IRGRP ); +#endif + + if (fd < 0) + return false; + + // store "magic number" to help identify this kind of file + unsigned int magic = NAV_MAGIC_NUMBER; + _write( fd, &magic, sizeof(unsigned int) ); + + // store version number of file + // 1 = hiding spots as plain vector array + // 2 = hiding spots as HidingSpot objects + // 3 = Encounter spots use HidingSpot ID's instead of storing vector again + // 4 = Includes size of source bsp file to verify nav data correlation + // ---- Beta Release at V4 ----- + // 5 = Added Place info + unsigned int version = 5; + _write( fd, &version, sizeof(unsigned int) ); + + + // get size of source bsp file and store it in the nav file + // so we can test if the bsp changed since the nav file was made + char *bspFilename = GetBspFilename( filename ); + if (bspFilename == NULL) + return false; + + unsigned int bspSize = (unsigned int)GET_FILE_SIZE( bspFilename ); + CONSOLE_ECHO( "Size of bsp file '%s' is %u bytes.\n", bspFilename, bspSize ); + + _write( fd, &bspSize, sizeof(unsigned int) ); + + + // + // Build a directory of the Places in this map + // + placeDirectory.Reset(); + + NavAreaList::iterator it; + for( it = TheNavAreaList.begin(); it != TheNavAreaList.end(); ++it ) + { + CNavArea *area = *it; + + Place place = area->GetPlace(); + + if (place) + { + placeDirectory.AddPlace( place ); + } + } + + placeDirectory.Save( fd ); + + + // + // Store navigation areas + // + + // store number of areas + unsigned int count = TheNavAreaList.size(); + _write( fd, &count, sizeof(unsigned int) ); + + // store each area + for( it = TheNavAreaList.begin(); it != TheNavAreaList.end(); ++it ) + { + CNavArea *area = *it; + + area->Save( fd, version ); + } + + _close( fd ); + + +#ifdef _WIN32 + // output a simple Wavefront file to visualize the generated areas in 3DSMax + FILE *fp = fopen( "c:\\tmp\\nav.obj", "w" ); + if (fp) + { + for( NavAreaList::iterator iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter ) + (*iter)->Save( fp ); + + fclose( fp ); + } +#endif + + + return true; +} + + +//-------------------------------------------------------------------------------------------------------------- +// +// Load place map +// This is legacy code - Places are stored directly in the nav file now +// +void LoadLocationFile( const char *filename ) +{ + char locFilename[256]; + strcpy( locFilename, filename ); + + char *dot = strchr( locFilename, '.' ); + if (dot) + { + strcpy( dot, ".loc" ); + + int locDataLength; + char *locDataFile = (char *)LOAD_FILE_FOR_ME( const_cast( locFilename ), &locDataLength ); + char *locData = locDataFile; + + if (locData) + { + CONSOLE_ECHO( "Loading legacy 'location file' '%s'\n", locFilename ); + + // read directory + locData = MP_COM_Parse( locData ); + int dirSize = atoi( MP_COM_GetToken() ); + + if (dirSize) + { + std::vector directory; + + directory.reserve( dirSize ); + + for( int i=0; iNameToID( MP_COM_GetToken() ) ); + } + + // read places for each nav area + unsigned int areaID, locDirIndex; + while(true) + { + locData = MP_COM_Parse( locData ); + if (locData == NULL) + break; + + areaID = atoi( MP_COM_GetToken() ); + + locData = MP_COM_Parse( locData ); + locDirIndex = atoi( MP_COM_GetToken() ); + + CNavArea *area = TheNavAreaGrid.GetNavAreaByID( areaID ); + unsigned int place = (locDirIndex > 0) ? directory[ locDirIndex-1 ] : UNDEFINED_PLACE; + + if (area) + area->SetPlace( place ); + } + } + + FREE_FILE( locDataFile ); + } + } +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Performs a lightweight sanity-check of the specified map's nav mesh + */ +void SanityCheckNavigationMap( const char *mapName ) +{ + if ( !mapName ) + { + CONSOLE_ECHO( "ERROR: navigation file not specified.\n" ); + return; + } + + // nav filename is derived from map filename + const int BufLen = MAX_OSPATH; + char bspFilename[MAX_OSPATH]; + char navFilename[MAX_OSPATH]; + snprintf( bspFilename, MAX_OSPATH, "maps\\%s.bsp", mapName ); + snprintf( navFilename, MAX_OSPATH, "maps\\%s.nav", mapName ); + + SteamFile navFile( navFilename ); + + if (!navFile.IsValid()) + { + CONSOLE_ECHO( "ERROR: navigation file %s does not exist.\n", navFilename ); + return; + } + + // check magic number + bool result; + unsigned int magic; + result = navFile.Read( &magic, sizeof(unsigned int) ); + if (!result || magic != NAV_MAGIC_NUMBER) + { + CONSOLE_ECHO( "ERROR: Invalid navigation file '%s'.\n", navFilename ); + return; + } + + // read file version number + unsigned int version; + result = navFile.Read( &version, sizeof(unsigned int) ); + if (!result || version > 5) + { + CONSOLE_ECHO( "ERROR: Unknown version in navigation file %s.\n", navFilename ); + return; + } + + if (version >= 4) + { + // get size of source bsp file and verify that the bsp hasn't changed + unsigned int saveBspSize; + navFile.Read( &saveBspSize, sizeof(unsigned int) ); + + // verify size + if (bspFilename == NULL) + { + CONSOLE_ECHO( "ERROR: No map corresponds to navigation file %s.\n", navFilename ); + return; + } + + unsigned int bspSize = (unsigned int)GET_FILE_SIZE( bspFilename ); + + if (bspSize != saveBspSize) + { + // this nav file is out of date for this bsp file + CONSOLE_ECHO( "ERROR: Out-of-date navigation data in navigation file %s.\n", navFilename ); + return; + } + } + CONSOLE_ECHO( "navigation file %s passes the sanity check.\n", navFilename ); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Load AI navigation data from a file + */ +NavErrorType LoadNavigationMap( void ) +{ + // since the navigation map is destroyed on map change, + // if it exists it has already been loaded for this map + if (!TheNavAreaList.empty()) + return NAV_OK; + + // nav filename is derived from map filename + char filename[256]; + sprintf( filename, "maps\\%s.nav", STRING( gpGlobals->mapname ) ); + + + // free previous navigation map data + DestroyNavigationMap(); + placeDirectory.Reset(); + + CNavArea::m_nextID = 1; + + SteamFile navFile( filename ); + + if (!navFile.IsValid()) + return NAV_CANT_ACCESS_FILE; + + // check magic number + bool result; + unsigned int magic; + result = navFile.Read( &magic, sizeof(unsigned int) ); + if (!result || magic != NAV_MAGIC_NUMBER) + { + CONSOLE_ECHO( "ERROR: Invalid navigation file '%s'.\n", filename ); + return NAV_INVALID_FILE; + } + + // read file version number + unsigned int version; + result = navFile.Read( &version, sizeof(unsigned int) ); + if (!result || version > 5) + { + CONSOLE_ECHO( "ERROR: Unknown navigation file version.\n" ); + return NAV_BAD_FILE_VERSION; + } + + if (version >= 4) + { + // get size of source bsp file and verify that the bsp hasn't changed + unsigned int saveBspSize; + navFile.Read( &saveBspSize, sizeof(unsigned int) ); + + // verify size + char *bspFilename = GetBspFilename( filename ); + if (bspFilename == NULL) + return NAV_INVALID_FILE; + + unsigned int bspSize = (unsigned int)GET_FILE_SIZE( bspFilename ); + + if (bspSize != saveBspSize) + { + // this nav file is out of date for this bsp file + char *msg = "*** WARNING ***\nThe AI navigation data is from a different version of this map.\nThe CPU players will likely not perform well.\n"; + HintMessageToAllPlayers( msg ); + CONSOLE_ECHO( "\n-----------------\n" ); + CONSOLE_ECHO( msg ); + CONSOLE_ECHO( "-----------------\n\n" ); + } + } + + // load Place directory + if (version >= 5) + { + placeDirectory.Load( &navFile ); + } + + // get number of areas + unsigned int count; + result = navFile.Read( &count, sizeof(unsigned int) ); + + Extent extent; + extent.lo.x = 9999999999.9f; + extent.lo.y = 9999999999.9f; + extent.hi.x = -9999999999.9f; + extent.hi.y = -9999999999.9f; + + // load the areas and compute total extent + for( unsigned int i=0; iLoad( &navFile, version ); + TheNavAreaList.push_back( area ); + + const Extent *areaExtent = area->GetExtent(); + + // check validity of nav area + if (areaExtent->lo.x >= areaExtent->hi.x || areaExtent->lo.y >= areaExtent->hi.y) + CONSOLE_ECHO( "WARNING: Degenerate Navigation Area #%d at ( %g, %g, %g )\n", + area->GetID(), area->m_center.x, area->m_center.y, area->m_center.z ); + + if (areaExtent->lo.x < extent.lo.x) + extent.lo.x = areaExtent->lo.x; + if (areaExtent->lo.y < extent.lo.y) + extent.lo.y = areaExtent->lo.y; + if (areaExtent->hi.x > extent.hi.x) + extent.hi.x = areaExtent->hi.x; + if (areaExtent->hi.y > extent.hi.y) + extent.hi.y = areaExtent->hi.y; + } + + // add the areas to the grid + TheNavAreaGrid.Initialize( extent.lo.x, extent.hi.x, extent.lo.y, extent.hi.y ); + + NavAreaList::iterator iter; + for( iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter ) + TheNavAreaGrid.AddNavArea( *iter ); + + + // allow areas to connect to each other, etc + for( iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter ) + { + CNavArea *area = *iter; + area->PostLoad(); + } + + // load legacy location file (Places) + if (version < 5) + { + LoadLocationFile( filename ); + } + + // + // Set up all the ladders + // + BuildLadders(); + + return NAV_OK; +} diff --git a/game_shared/bot/nav_node.cpp b/game_shared/bot/nav_node.cpp new file mode 100644 index 0000000..3a4267f --- /dev/null +++ b/game_shared/bot/nav_node.cpp @@ -0,0 +1,111 @@ +// nav_node.cpp +// AI Navigation Nodes +// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003 + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "bot_util.h" +#include "nav_node.h" + +NavDirType Opposite[ NUM_DIRECTIONS ] = { SOUTH, WEST, NORTH, EAST }; + +CNavNode *CNavNode::m_list = NULL; +unsigned int CNavNode::m_listLength = 0; + +Extent NodeMapExtent; + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Constructor + */ +CNavNode::CNavNode( const Vector *pos, const Vector *normal, CNavNode *parent ) +{ + m_pos = *pos; + m_normal = *normal; + + static unsigned int nextID = 1; + m_id = nextID++; + + for( int i=0; ix, pos->y, pos->z ); +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Create a connection FROM this node TO the given node, in the given direction + */ +void CNavNode::ConnectTo( CNavNode *node, NavDirType dir ) +{ + m_to[ dir ] = node; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return node at given position. + * @todo Need a hash table to make this lookup fast + */ +const CNavNode *CNavNode::GetNode( const Vector *pos ) +{ + const float tolerance = 0.45f * GenerationStepSize; // 1.0f + + for( const CNavNode *node = m_list; node; node = node->m_next ) + { + float dx = ABS( node->m_pos.x - pos->x ); + float dy = ABS( node->m_pos.y - pos->y ); + float dz = ABS( node->m_pos.z - pos->z ); + + if (dx < tolerance && dy < tolerance && dz < tolerance) + return node; + } + + return NULL; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if this node is bidirectionally linked to + * another node in the given direction + */ +BOOL CNavNode::IsBiLinked( NavDirType dir ) const +{ + if (m_to[ dir ] && + m_to[ dir ]->m_to[ Opposite[dir] ] == this) + return true; + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if this node is the NW corner of a quad of nodes + * that are all bidirectionally linked. + */ +BOOL CNavNode::IsClosedCell( void ) const +{ + if (IsBiLinked( SOUTH ) && + IsBiLinked( EAST ) && + m_to[ EAST ]->IsBiLinked( SOUTH ) && + m_to[ SOUTH ]->IsBiLinked( EAST ) && + m_to[ EAST ]->m_to[ SOUTH ] == m_to[ SOUTH ]->m_to[ EAST ]) + return true; + + return false; +} + diff --git a/game_shared/bot/nav_node.h b/game_shared/bot/nav_node.h new file mode 100644 index 0000000..19fd1bc --- /dev/null +++ b/game_shared/bot/nav_node.h @@ -0,0 +1,113 @@ +// nav_node.h +// AI Navigation Nodes +// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003 + +#ifndef _NAV_NODE_H_ +#define _NAV_NODE_H_ + +#include "nav.h" + +//-------------------------------------------------------------------------------------------------------------- +/** + * Navigation Nodes. + * These Nodes encapsulate world locations, and ways to get from one location to an adjacent one. + * Note that these links are not necessarily commutative (falling off of a ledge, for example). + */ +class CNavNode +{ +public: + CNavNode( const Vector *pos, const Vector *normal, CNavNode *parent = NULL ); + + static const CNavNode *GetNode( const Vector *pos ); ///< return navigation node at the position, or NULL if none exists + + CNavNode *GetConnectedNode( NavDirType dir ) const; ///< get navigation node connected in given direction, or NULL if cant go that way + const Vector *GetPosition( void ) const; + const Vector *GetNormal( void ) const { return &m_normal; } + unsigned int GetID( void ) const { return m_id; } + + static CNavNode *GetFirst( void ) { return m_list; } + static unsigned int GetListLength( void ) { return m_listLength; } + CNavNode *GetNext( void ) { return m_next; } + + void ConnectTo( CNavNode *node, NavDirType dir ); ///< create a connection FROM this node TO the given node, in the given direction + CNavNode *GetParent( void ) const; + + void MarkAsVisited( NavDirType dir ); ///< mark the given direction as having been visited + BOOL HasVisited( NavDirType dir ); ///< return TRUE if the given direction has already been searched + BOOL IsBiLinked( NavDirType dir ) const; ///< node is bidirectionally linked to another node in the given direction + BOOL IsClosedCell( void ) const; ///< node is the NW corner of a bi-linked quad of nodes + + void Cover( void ) { m_isCovered = true; } ///< @todo Should pass in area that is covering + BOOL IsCovered( void ) const { return m_isCovered; } ///< return true if this node has been covered by an area + + void AssignArea( CNavArea *area ); ///< assign the given area to this node + CNavArea *GetArea( void ) const; ///< return associated area + + void SetAttributes( unsigned char bits ) { m_attributeFlags = bits; } + unsigned char GetAttributes( void ) const { return m_attributeFlags; } + +private: + friend void DestroyNavigationMap( void ); + + Vector m_pos; ///< position of this node in the world + Vector m_normal; ///< surface normal at this location + CNavNode *m_to[ NUM_DIRECTIONS ]; ///< links to north, south, east, and west. NULL if no link + unsigned int m_id; ///< unique ID of this node + unsigned char m_attributeFlags; ///< set of attribute bit flags (see NavAttributeType) + + static CNavNode *m_list; ///< the master list of all nodes for this map + static unsigned int m_listLength; + CNavNode *m_next; ///< next link in master list + + // below are only needed when generating + unsigned char m_visited; ///< flags for automatic node generation. If direction bit is clear, that direction hasn't been explored yet. + CNavNode *m_parent; ///< the node prior to this in the search, which we pop back to when this node's search is done (a stack) + BOOL m_isCovered; ///< true when this node is "covered" by a CNavArea + CNavArea *m_area; ///< the area this node is contained within +}; + +//-------------------------------------------------------------------------------------------------------------- +// +// Inlines +// + +inline CNavNode *CNavNode::GetConnectedNode( NavDirType dir ) const +{ + return m_to[ dir ]; +} + +inline const Vector *CNavNode::GetPosition( void ) const +{ + return &m_pos; +} + +inline CNavNode *CNavNode::GetParent( void ) const +{ + return m_parent; +} + +inline void CNavNode::MarkAsVisited( NavDirType dir ) +{ + m_visited |= (1 << dir); +} + +inline BOOL CNavNode::HasVisited( NavDirType dir ) +{ + if (m_visited & (1 << dir)) + return true; + + return false; +} + +inline void CNavNode::AssignArea( CNavArea *area ) +{ + m_area = area; +} + +inline CNavArea *CNavNode::GetArea( void ) const +{ + return m_area; +} + + +#endif // _NAV_NODE_H_ diff --git a/game_shared/bot/nav_path.cpp b/game_shared/bot/nav_path.cpp new file mode 100644 index 0000000..22e5f50 --- /dev/null +++ b/game_shared/bot/nav_path.cpp @@ -0,0 +1,1192 @@ +// nav_path.cpp +// Encapsulation of a path through space +// Author: Michael S. Booth (mike@turtlerockstudios.com), November 2003 + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "soundent.h" +#include "gamerules.h" +#include "player.h" +#include "client.h" +#include "cmd.h" + +#include "nav.h" +#include "nav_path.h" +#include "bot_util.h" +#include "improv.h" + +//-------------------------------------------------------------------------------------------------------------- +/** + * Determine actual path positions + */ +bool CNavPath::ComputePathPositions( void ) +{ + if (m_segmentCount == 0) + return false; + + // start in first area's center + m_path[0].pos = *m_path[0].area->GetCenter(); + m_path[0].ladder = NULL; + m_path[0].how = NUM_TRAVERSE_TYPES; + + for( int i=1; ihow <= GO_WEST) // walk along the floor to the next area + { + to->ladder = NULL; + + // compute next point, keeping path as straight as possible + from->area->ComputeClosestPointInPortal( to->area, (NavDirType)to->how, &from->pos, &to->pos ); + + // move goal position into the goal area a bit + const float stepInDist = 5.0f; // how far to "step into" an area - must be less than min area size + AddDirectionVector( &to->pos, (NavDirType)to->how, stepInDist ); + + // we need to walk out of "from" area, so keep Z where we can reach it + to->pos.z = from->area->GetZ( &to->pos ); + + // if this is a "jump down" connection, we must insert an additional point on the path + if (to->area->IsConnected( from->area, NUM_DIRECTIONS ) == false) + { + // this is a "jump down" link + + // compute direction of path just prior to "jump down" + Vector2D dir; + DirectionToVector2D( (NavDirType)to->how, &dir ); + + // shift top of "jump down" out a bit to "get over the ledge" + const float pushDist = 25.0f; + to->pos.x += pushDist * dir.x; + to->pos.y += pushDist * dir.y; + + // insert a duplicate node to represent the bottom of the fall + if (m_segmentCount < MAX_PATH_SEGMENTS-1) + { + // copy nodes down + for( int j=m_segmentCount; j>i; --j ) + m_path[j] = m_path[j-1]; + + // path is one node longer + ++m_segmentCount; + + // move index ahead into the new node we just duplicated + ++i; + + m_path[i].pos.x = to->pos.x + pushDist * dir.x; + m_path[i].pos.y = to->pos.y + pushDist * dir.y; + + // put this one at the bottom of the fall + m_path[i].pos.z = to->area->GetZ( &m_path[i].pos ); + } + } + } + else if (to->how == GO_LADDER_UP) // to get to next area, must go up a ladder + { + // find our ladder + const NavLadderList *list = from->area->GetLadderList( LADDER_UP ); + NavLadderList::const_iterator iter; + for( iter = list->begin(); iter != list->end(); ++iter ) + { + CNavLadder *ladder = *iter; + + // can't use "behind" area when ascending... + if (ladder->m_topForwardArea == to->area || + ladder->m_topLeftArea == to->area || + ladder->m_topRightArea == to->area) + { + to->ladder = ladder; + to->pos = ladder->m_bottom; + AddDirectionVector( &to->pos, ladder->m_dir, 2.0f*HalfHumanWidth ); + break; + } + } + + if (iter == list->end()) + { + //PrintIfWatched( "ERROR: Can't find ladder in path\n" ); + return false; + } + } + else if (to->how == GO_LADDER_DOWN) // to get to next area, must go down a ladder + { + // find our ladder + const NavLadderList *list = from->area->GetLadderList( LADDER_DOWN ); + NavLadderList::const_iterator iter; + for( iter = list->begin(); iter != list->end(); ++iter ) + { + CNavLadder *ladder = *iter; + + if (ladder->m_bottomArea == to->area) + { + to->ladder = ladder; + to->pos = ladder->m_top; + AddDirectionVector( &to->pos, OppositeDirection( ladder->m_dir ), 2.0f*HalfHumanWidth ); + break; + } + } + + if (iter == list->end()) + { + //PrintIfWatched( "ERROR: Can't find ladder in path\n" ); + return false; + } + } + } + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if position is at the end of the path + */ +bool CNavPath::IsAtEnd( const Vector &pos ) const +{ + if (!IsValid()) + return false; + + const float epsilon = 20.0f; + return (pos - GetEndpoint()).IsLengthLessThan( epsilon ); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return length of path from start to finish + */ +float CNavPath::GetLength( void ) const +{ + float length = 0.0f; + for( int i=1; i= distAlong) + { + // desired point is on this segment of the path + float delta = distAlong - lengthSoFar; + float t = delta / segmentLength; + + *pointOnPath = m_path[i].pos + t * dir; + + return true; + } + + lengthSoFar += segmentLength; + } + + *pointOnPath = m_path[ GetSegmentCount()-1 ].pos; + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return the node index closest to the given distance along the path without going over - returns (-1) if error + */ +int CNavPath::GetSegmentIndexAlongPath( float distAlong ) const +{ + if (!IsValid()) + return -1; + + if (distAlong <= 0.0f) + { + return 0; + } + + float lengthSoFar = 0.0f; + Vector dir; + for( int i=1; i distAlong) + { + return i-1; + } + } + + return GetSegmentCount()-1; +} + + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Compute closest point on path to given point + * NOTE: This does not do line-of-sight tests, so closest point may be thru the floor, etc + */ +bool CNavPath::FindClosestPointOnPath( const Vector *worldPos, int startIndex, int endIndex, Vector *close ) const +{ + if (!IsValid() || close == NULL) + return false; + + Vector along, toWorldPos; + Vector pos; + const Vector *from, *to; + float length; + float closeLength; + float closeDistSq = 9999999999.9; + float distSq; + + for( int i=startIndex; i<=endIndex; ++i ) + { + from = &m_path[i-1].pos; + to = &m_path[i].pos; + + // compute ray along this path segment + along = *to - *from; + + // make it a unit vector along the path + length = along.NormalizeInPlace(); + + // compute vector from start of segment to our point + toWorldPos = *worldPos - *from; + + // find distance of closest point on ray + closeLength = DotProduct( toWorldPos, along ); + + // constrain point to be on path segment + if (closeLength <= 0.0f) + pos = *from; + else if (closeLength >= length) + pos = *to; + else + pos = *from + closeLength * along; + + distSq = (pos - *worldPos).LengthSquared(); + + // keep the closest point so far + if (distSq < closeDistSq) + { + closeDistSq = distSq; + *close = pos; + } + } + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Build trivial path when start and goal are in the same nav area + */ +bool CNavPath::BuildTrivialPath( const Vector *start, const Vector *goal ) +{ + m_segmentCount = 0; + + CNavArea *startArea = TheNavAreaGrid.GetNearestNavArea( start ); + if (startArea == NULL) + return false; + + CNavArea *goalArea = TheNavAreaGrid.GetNearestNavArea( goal ); + if (goalArea == NULL) + return false; + + m_segmentCount = 2; + + m_path[0].area = startArea; + m_path[0].pos.x = start->x; + m_path[0].pos.y = start->y; + m_path[0].pos.z = startArea->GetZ( start ); + m_path[0].ladder = NULL; + m_path[0].how = NUM_TRAVERSE_TYPES; + + m_path[1].area = goalArea; + m_path[1].pos.x = goal->x; + m_path[1].pos.y = goal->y; + m_path[1].pos.z = goalArea->GetZ( goal ); + m_path[1].ladder = NULL; + m_path[1].how = NUM_TRAVERSE_TYPES; + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Draw the path for debugging. + */ +void CNavPath::Draw( void ) +{ + if (!IsValid()) + return; + + for( int i=1; i anchor) + { + // remove redundant nodes between anchor and nextAnchor + int removeCount = nextAnchor - anchor - 1; + if (removeCount > 0) + { + for( int i=nextAnchor; iIsValid() == false) + return; + + const CNavPath::PathSegment *node = (*m_path)[ m_segmentIndex ]; + + if (node == NULL) + { + m_improv->OnMoveToFailure( m_path->GetEndpoint(), IImprovEvent::FAIL_INVALID_PATH ); + m_path->Invalidate(); + return; + } + + // handle ladders + if (node->ladder) + { + const Vector *approachPos = NULL; + const Vector *departPos = NULL; + + if (m_segmentIndex) + approachPos = &(*m_path)[ m_segmentIndex-1 ]->pos; + + if (m_segmentIndex < m_path->GetSegmentCount()-1) + departPos = &(*m_path)[ m_segmentIndex+1 ]->pos; + + if (!m_isLadderStarted) + { + // set up ladder movement + m_improv->StartLadder( node->ladder, node->how, approachPos, departPos ); + m_isLadderStarted = true; + } + + // move improv along ladder + if (m_improv->TraverseLadder( node->ladder, node->how, approachPos, departPos, deltaT )) + { + // completed ladder + ++m_segmentIndex; + } + return; + } + + // reset ladder init flag + m_isLadderStarted = false; + + // + // Check if we reached the end of the path + // + const float closeRange = 20.0f; + if ((m_improv->GetFeet() - node->pos).IsLengthLessThan( closeRange )) + { + ++m_segmentIndex; + + if (m_segmentIndex >= m_path->GetSegmentCount()) + { + m_improv->OnMoveToSuccess( m_path->GetEndpoint() ); + m_path->Invalidate(); + return; + } + } + + + m_goal = node->pos; + + const float aheadRange = 300.0f; + m_segmentIndex = FindPathPoint( aheadRange, &m_goal, &m_behindIndex ); + if (m_segmentIndex >= m_path->GetSegmentCount()) + m_segmentIndex = m_path->GetSegmentCount()-1; + + + bool isApproachingJumpArea = false; + + // + // Crouching + // + if (!m_improv->IsUsingLadder()) + { + // because hostage crouching is not really supported by the engine, + // if we are standing in a crouch area, we must crouch to avoid collisions + if (m_improv->GetLastKnownArea() && + m_improv->GetLastKnownArea()->GetAttributes() & NAV_CROUCH && + !(m_improv->GetLastKnownArea()->GetAttributes() & NAV_JUMP)) + { + m_improv->Crouch(); + } + + // if we are approaching a crouch area, crouch + // if there are no crouch areas coming up, stand + const float crouchRange = 50.0f; + bool didCrouch = false; + for( int i=m_segmentIndex; iGetSegmentCount(); ++i ) + { + const CNavArea *to = (*m_path)[i]->area; + + // if there is a jump area on the way to the crouch area, don't crouch as it messes up the jump + if (to->GetAttributes() & NAV_JUMP) + { + isApproachingJumpArea = true; + break; + } + + Vector close; + to->GetClosestPointOnArea( &m_improv->GetCentroid(), &close ); + + if ((close - m_improv->GetFeet()).Make2D().IsLengthGreaterThan( crouchRange )) + break; + + if (to->GetAttributes() & NAV_CROUCH) + { + m_improv->Crouch(); + didCrouch = true; + break; + } + + } + + if (!didCrouch && !m_improv->IsJumping()) + { + // no crouch areas coming up + m_improv->StandUp(); + } + + } // end crouching logic + + + if (m_isDebug) + { + m_path->Draw(); + UTIL_DrawBeamPoints( m_improv->GetCentroid(), m_goal + Vector( 0, 0, StepHeight ), 1, 255, 0, 255 ); + UTIL_DrawBeamPoints( m_goal + Vector( 0, 0, StepHeight ), m_improv->GetCentroid(), 1, 255, 0, 255 ); + } + + // check if improv becomes stuck + m_stuckMonitor.Update( m_improv ); + + + // if improv has been stuck for too long, give up + const float giveUpTime = 2.0f; + if (m_stuckMonitor.GetDuration() > giveUpTime) + { + m_improv->OnMoveToFailure( m_path->GetEndpoint(), IImprovEvent::FAIL_STUCK ); + m_path->Invalidate(); + return; + } + + + // if our goal is high above us, we must have fallen + if (m_goal.z - m_improv->GetFeet().z > JumpCrouchHeight) + { + const float closeRange = 75.0f; + Vector2D to( m_improv->GetFeet().x - m_goal.x, m_improv->GetFeet().y - m_goal.y ); + if (to.IsLengthLessThan( closeRange )) + { + // we can't reach the goal position + // check if we can reach the next node, in case this was a "jump down" situation + const CNavPath::PathSegment *nextNode = (*m_path)[ m_behindIndex+1 ]; + if (m_behindIndex >=0 && nextNode) + { + if (nextNode->pos.z - m_improv->GetFeet().z > JumpCrouchHeight) + { + // the next node is too high, too - we really did fall of the path + m_improv->OnMoveToFailure( m_path->GetEndpoint(), IImprovEvent::FAIL_FELL_OFF ); + m_path->Invalidate(); + return; + } + } + else + { + // fell trying to get to the last node in the path + m_improv->OnMoveToFailure( m_path->GetEndpoint(), IImprovEvent::FAIL_FELL_OFF ); + m_path->Invalidate(); + return; + } + } + } + + + // avoid small obstacles + if (avoidObstacles && !isApproachingJumpArea && !m_improv->IsJumping() && m_segmentIndex < m_path->GetSegmentCount()-1) + { + FeelerReflexAdjustment( &m_goal ); + + // currently, this is only used for hostages, and their collision physics stinks + // do more feeler checks to avoid short obstacles + /* + const float inc = 0.25f; + for( float t = 0.5f; t < 1.0f; t += inc ) + { + FeelerReflexAdjustment( &m_goal, t * StepHeight ); + } + */ + + } + + // move improv along path + m_improv->TrackPath( m_goal, deltaT ); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return the closest point to our current position on our current path + * If "local" is true, only check the portion of the path surrounding m_pathIndex. + */ +int CNavPathFollower::FindOurPositionOnPath( Vector *close, bool local ) const +{ + if (!m_path->IsValid()) + return -1; + + Vector along, toFeet; + Vector feet = m_improv->GetFeet(); + Vector eyes = m_improv->GetEyes(); + Vector pos; + const Vector *from, *to; + float length; + float closeLength; + float closeDistSq = 9999999999.9; + int closeIndex = -1; + float distSq; + + int start, end; + + if (local) + { + start = m_segmentIndex - 3; + if (start < 1) + start = 1; + + end = m_segmentIndex + 3; + if (end > m_path->GetSegmentCount()) + end = m_path->GetSegmentCount(); + } + else + { + start = 1; + end = m_path->GetSegmentCount(); + } + + for( int i=start; ipos; + to = &(*m_path)[i]->pos; + + // compute ray along this path segment + along = *to - *from; + + // make it a unit vector along the path + length = along.NormalizeInPlace(); + + // compute vector from start of segment to our point + toFeet = feet - *from; + + // find distance of closest point on ray + closeLength = DotProduct( toFeet, along ); + + // constrain point to be on path segment + if (closeLength <= 0.0f) + pos = *from; + else if (closeLength >= length) + pos = *to; + else + pos = *from + closeLength * along; + + distSq = (pos - feet).LengthSquared(); + + // keep the closest point so far + if (distSq < closeDistSq) + { + // don't use points we cant see + Vector probe = pos + Vector( 0, 0, HalfHumanHeight ); + if (!IsWalkableTraceLineClear( eyes, probe, WALK_THRU_DOORS | WALK_THRU_BREAKABLES )) + continue; + + // don't use points we cant reach + //if (!IsStraightLinePathWalkable( &pos )) + // continue; + + closeDistSq = distSq; + if (close) + *close = pos; + closeIndex = i-1; + } + } + + return closeIndex; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Compute a point a fixed distance ahead along our path. + * Returns path index just after point. + */ +int CNavPathFollower::FindPathPoint( float aheadRange, Vector *point, int *prevIndex ) +{ + // find path index just past aheadRange + int afterIndex; + + // finds the closest point on local area of path, and returns the path index just prior to it + Vector close; + int startIndex = FindOurPositionOnPath( &close, true ); + + if (prevIndex) + *prevIndex = startIndex; + + if (startIndex <= 0) + { + // went off the end of the path + // or next point in path is unwalkable (ie: jump-down) + // keep same point + return m_segmentIndex; + } + + // if we are crouching, just follow the path exactly + if (m_improv->IsCrouching()) + { + // we want to move to the immediately next point along the path from where we are now + int index = startIndex+1; + if (index >= m_path->GetSegmentCount()) + index = m_path->GetSegmentCount()-1; + + *point = (*m_path)[ index ]->pos; + + // if we are very close to the next point in the path, skip ahead to the next one to avoid wiggling + // we must do a 2D check here, in case the goal point is floating in space due to jump down, etc + const float closeEpsilon = 20.0f; // 10 + while ((*point - close).Make2D().IsLengthLessThan( closeEpsilon )) + { + ++index; + + if (index >= m_path->GetSegmentCount()) + { + index = m_path->GetSegmentCount()-1; + break; + } + + *point = (*m_path)[ index ]->pos; + } + + return index; + } + + // make sure we use a node a minimum distance ahead of us, to avoid wiggling + while (startIndex < m_path->GetSegmentCount()-1) + { + Vector pos = (*m_path)[ startIndex+1 ]->pos; + + // we must do a 2D check here, in case the goal point is floating in space due to jump down, etc + const float closeEpsilon = 20.0f; + if ((pos - close).Make2D().IsLengthLessThan( closeEpsilon )) + { + ++startIndex; + } + else + { + break; + } + } + + // if we hit a ladder or jump area, must stop (dont use ladder behind us) + if (startIndex > m_segmentIndex && startIndex < m_path->GetSegmentCount() && + ((*m_path)[ startIndex ]->ladder || (*m_path)[ startIndex ]->area->GetAttributes() & NAV_JUMP)) + { + *point = (*m_path)[ startIndex ]->pos; + return startIndex; + } + + // we need the point just *ahead* of us + ++startIndex; + if (startIndex >= m_path->GetSegmentCount()) + startIndex = m_path->GetSegmentCount()-1; + + // if we hit a ladder or jump area, must stop + if (startIndex < m_path->GetSegmentCount() && + ((*m_path)[ startIndex ]->ladder || (*m_path)[ startIndex ]->area->GetAttributes() & NAV_JUMP)) + { + *point = (*m_path)[ startIndex ]->pos; + return startIndex; + } + + // note direction of path segment we are standing on + Vector initDir = (*m_path)[ startIndex ]->pos - (*m_path)[ startIndex-1 ]->pos; + initDir.NormalizeInPlace(); + + Vector feet = m_improv->GetFeet(); + Vector eyes = m_improv->GetEyes(); + float rangeSoFar = 0; + + // this flag is true if our ahead point is visible + bool visible = true; + + Vector prevDir = initDir; + + // step along the path until we pass aheadRange + bool isCorner = false; + int i; + for( i=startIndex; iGetSegmentCount(); ++i ) + { + Vector pos = (*m_path)[i]->pos; + Vector to = pos - (*m_path)[i-1]->pos; + Vector dir = to.Normalize(); + + // don't allow path to double-back from our starting direction (going upstairs, down curved passages, etc) + if (DotProduct( dir, initDir ) < 0.0f) // -0.25f + { + --i; + break; + } + + // if the path turns a corner, we want to move towards the corner, not into the wall/stairs/etc + if (DotProduct( dir, prevDir ) < 0.5f) + { + isCorner = true; + --i; + break; + } + prevDir = dir; + + // don't use points we cant see + Vector probe = pos + Vector( 0, 0, HalfHumanHeight ); + if (!IsWalkableTraceLineClear( eyes, probe, WALK_THRU_BREAKABLES )) + { + // presumably, the previous point is visible, so we will interpolate + visible = false; + break; + } + + // if we encounter a ladder or jump area, we must stop + if (i < m_path->GetSegmentCount() && + ((*m_path)[ i ]->ladder || (*m_path)[ i ]->area->GetAttributes() & NAV_JUMP)) + break; + + // Check straight-line path from our current position to this position + // Test for un-jumpable height change, or unrecoverable fall + //if (!IsStraightLinePathWalkable( &pos )) + //{ + // --i; + // break; + //} + + Vector along = (i == startIndex) ? (pos - feet) : (pos - (*m_path)[i-1]->pos); + rangeSoFar += along.Length2D(); + + // stop if we have gone farther than aheadRange + if (rangeSoFar >= aheadRange) + break; + } + + if (i < startIndex) + afterIndex = startIndex; + else if (i < m_path->GetSegmentCount()) + afterIndex = i; + else + afterIndex = m_path->GetSegmentCount()-1; + + + // compute point on the path at aheadRange + if (afterIndex == 0) + { + *point = (*m_path)[0]->pos; + } + else + { + // interpolate point along path segment + const Vector *afterPoint = &(*m_path)[ afterIndex ]->pos; + const Vector *beforePoint = &(*m_path)[ afterIndex-1 ]->pos; + + Vector to = *afterPoint - *beforePoint; + float length = to.Length2D(); + + float t = 1.0f - ((rangeSoFar - aheadRange) / length); + + if (t < 0.0f) + t = 0.0f; + else if (t > 1.0f) + t = 1.0f; + + *point = *beforePoint + t * to; + + // if afterPoint wasn't visible, slide point backwards towards beforePoint until it is + if (!visible) + { + const float sightStepSize = 25.0f; + float dt = sightStepSize / length; + + Vector probe = *point + Vector( 0, 0, HalfHumanHeight ); + while( t > 0.0f && !IsWalkableTraceLineClear( eyes, probe, WALK_THRU_BREAKABLES ) ) + { + t -= dt; + *point = *beforePoint + t * to; + } + + if (t <= 0.0f) + *point = *beforePoint; + } + } + + // if position found is too close to us, or behind us, force it farther down the path so we don't stop and wiggle + if (!isCorner) + { + const float epsilon = 50.0f; + Vector2D toPoint; + Vector2D centroid( m_improv->GetCentroid().x, m_improv->GetCentroid().y ); + + toPoint.x = point->x - centroid.x; + toPoint.y = point->y - centroid.y; + + if (DotProduct( toPoint, initDir.Make2D() ) < 0.0f || toPoint.IsLengthLessThan( epsilon )) + { + int i; + for( i=startIndex; iGetSegmentCount(); ++i ) + { + toPoint.x = (*m_path)[i]->pos.x - centroid.x; + toPoint.y = (*m_path)[i]->pos.y - centroid.y; + if ((*m_path)[i]->ladder || (*m_path)[i]->area->GetAttributes() & NAV_JUMP || toPoint.IsLengthGreaterThan( epsilon )) + { + *point = (*m_path)[i]->pos; + startIndex = i; + break; + } + } + + if (i == m_path->GetSegmentCount()) + { + *point = m_path->GetEndpoint(); + startIndex = m_path->GetSegmentCount()-1; + } + } + } + + // m_pathIndex should always be the next point on the path, even if we're not moving directly towards it + if (startIndex < m_path->GetSegmentCount()) + return startIndex; + + return m_path->GetSegmentCount()-1; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Do reflex avoidance movements if our "feelers" are touched + * @todo Parameterize feeler spacing + */ +void CNavPathFollower::FeelerReflexAdjustment( Vector *goalPosition, float height ) +{ + // if we are in a "precise" area, do not do feeler adjustments + if (m_improv->GetLastKnownArea() && m_improv->GetLastKnownArea()->GetAttributes() & NAV_PRECISE) + return; + + Vector dir( BotCOS( m_improv->GetMoveAngle() ), BotSIN( m_improv->GetMoveAngle() ), 0.0f ); + dir.z = 0.0f; + dir.NormalizeInPlace(); + + Vector lat( -dir.y, dir.x, 0.0f ); + + const float feelerOffset = (m_improv->IsCrouching()) ? 20.0f : 25.0f; // 15, 20 + const float feelerLengthRun = 50.0f; // 100 - too long for tight hallways (cs_747) + const float feelerLengthWalk = 30.0f; + + const float feelerHeight = (height > 0.0f) ? height : StepHeight + 0.1f; // if obstacle is lower than StepHeight, we'll walk right over it + + float feelerLength = (m_improv->IsRunning()) ? feelerLengthRun : feelerLengthWalk; + + feelerLength = (m_improv->IsCrouching()) ? 20.0f : feelerLength; + + // + // Feelers must follow floor slope + // + float ground; + Vector normal; + if (m_improv->GetSimpleGroundHeightWithFloor( &m_improv->GetEyes(), &ground, &normal ) == false) + return; + + // get forward vector along floor + dir = CrossProduct( lat, normal ); + + // correct the sideways vector + lat = CrossProduct( dir, normal ); + + + Vector feet = m_improv->GetFeet(); + feet.z += feelerHeight; + + Vector from = feet + feelerOffset * lat; + Vector to = from + feelerLength * dir; + + bool leftClear = IsWalkableTraceLineClear( from, to, WALK_THRU_DOORS | WALK_THRU_BREAKABLES ); + + // draw debug beams + if (m_isDebug) + { + if (leftClear) + UTIL_DrawBeamPoints( from, to, 1, 0, 255, 0 ); + else + UTIL_DrawBeamPoints( from, to, 1, 255, 0, 0 ); + } + + from = feet - feelerOffset * lat; + to = from + feelerLength * dir; + + bool rightClear = IsWalkableTraceLineClear( from, to, WALK_THRU_DOORS | WALK_THRU_BREAKABLES ); + + // draw debug beams + if (m_isDebug) + { + if (rightClear) + UTIL_DrawBeamPoints( from, to, 1, 0, 255, 0 ); + else + UTIL_DrawBeamPoints( from, to, 1, 255, 0, 0 ); + } + + + + const float avoidRange = (m_improv->IsCrouching()) ? 150.0f : 300.0f; + + if (!rightClear) + { + if (leftClear) + { + // right hit, left clear - veer left + *goalPosition = *goalPosition + avoidRange * lat; + //*goalPosition = m_improv->GetFeet() + avoidRange * lat; + + //m_improv->StrafeLeft(); + } + } + else if (!leftClear) + { + // right clear, left hit - veer right + *goalPosition = *goalPosition - avoidRange * lat; + //*goalPosition = m_improv->GetFeet() - avoidRange * lat; + + //m_improv->StrafeRight(); + } + +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Reset the stuck-checker. + */ +CStuckMonitor::CStuckMonitor( void ) +{ + m_isStuck = false; + m_avgVelIndex = 0; + m_avgVelCount = 0; +} + +/** + * Reset the stuck-checker. + */ +void CStuckMonitor::Reset( void ) +{ + m_isStuck = false; + m_avgVelIndex = 0; + m_avgVelCount = 0; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Test if the improv has become stuck + */ +void CStuckMonitor::Update( CImprov *improv ) +{ + if (m_isStuck) + { + // improv is stuck - see if it has moved far enough to be considered unstuck + const float unstuckRange = 75.0f; + if ((improv->GetCentroid() - m_stuckSpot).IsLengthGreaterThan( unstuckRange )) + { + // no longer stuck + Reset(); + //PrintIfWatched( "UN-STUCK\n" ); + } + } + else + { + // check if improv has become stuck + + // compute average velocity over a short period (for stuck check) + Vector vel = improv->GetCentroid() - m_lastCentroid; + + // if we are jumping, ignore Z + //if (improv->IsJumping()) + // vel.z = 0.0f; + + // ignore Z unless we are on a ladder (which is only Z) + if (!improv->IsUsingLadder()) + vel.z = 0.0f; + + // cannot be Length2D, or will break ladder movement (they are only Z) + float moveDist = vel.Length(); + + float deltaT = gpGlobals->time - m_lastTime; + if (deltaT <= 0.0f) + return; + + m_lastTime = gpGlobals->time; + + // compute current velocity + m_avgVel[ m_avgVelIndex++ ] = moveDist/deltaT; + + if (m_avgVelIndex == MAX_VEL_SAMPLES) + m_avgVelIndex = 0; + + if (m_avgVelCount < MAX_VEL_SAMPLES) + { + ++m_avgVelCount; + } + else + { + // we have enough samples to know if we're stuck + + float avgVel = 0.0f; + for( int t=0; tIsUsingLadder()) ? 10.0f : 20.0f; + + if (avgVel < stuckVel) + { + // note when and where we initially become stuck + m_stuckTimer.Start(); + m_stuckSpot = improv->GetCentroid(); + m_isStuck = true; + } + } + } + + // always need to track this + m_lastCentroid = improv->GetCentroid(); +} + diff --git a/game_shared/bot/nav_path.h b/game_shared/bot/nav_path.h new file mode 100644 index 0000000..4305e6d --- /dev/null +++ b/game_shared/bot/nav_path.h @@ -0,0 +1,230 @@ +// nav_path.h +// Navigation Path encapsulation +// Author: Michael S. Booth (mike@turtlerockstudios.com), November 2003 + +#ifndef _NAV_PATH_H_ +#define _NAV_PATH_H_ + +#pragma warning( disable : 4530 ) // STL uses exceptions, but we are not compiling with them - ignore warning + +#include "nav_area.h" +#include "bot_util.h" + +class CImprov; + +//-------------------------------------------------------------------------------------------------------- +/** + * The CNavPath class encapsulates a path through space + */ +class CNavPath +{ +public: + CNavPath( void ) + { + m_segmentCount = 0; + } + + struct PathSegment + { + CNavArea *area; ///< the area along the path + NavTraverseType how; ///< how to enter this area from the previous one + Vector pos; ///< our movement goal position at this point in the path + const CNavLadder *ladder; ///< if "how" refers to a ladder, this is it + }; + + const PathSegment * operator[] ( int i ) { return (i >= 0 && i < m_segmentCount) ? &m_path[i] : NULL; } + int GetSegmentCount( void ) const { return m_segmentCount; } + const Vector &GetEndpoint( void ) const { return m_path[ m_segmentCount-1 ].pos; } + bool IsAtEnd( const Vector &pos ) const; ///< return true if position is at the end of the path + + float GetLength( void ) const; ///< return length of path from start to finish + bool GetPointAlongPath( float distAlong, Vector *pointOnPath ) const; ///< return point a given distance along the path - if distance is out of path bounds, point is clamped to start/end + + /// return the node index closest to the given distance along the path without going over - returns (-1) if error + int GetSegmentIndexAlongPath( float distAlong ) const; + + bool IsValid( void ) const { return (m_segmentCount > 0); } + void Invalidate( void ) { m_segmentCount = 0; } + + void Draw( void ); ///< draw the path for debugging + + /// compute closest point on path to given point + bool FindClosestPointOnPath( const Vector *worldPos, int startIndex, int endIndex, Vector *close ) const; + + void Optimize( void ); + + /** + * Compute shortest path from 'start' to 'goal' via A* algorithm + */ + template< typename CostFunctor > + bool Compute( const Vector *start, const Vector *goal, CostFunctor &costFunc ) + { + Invalidate(); + + if (start == NULL || goal == NULL) + return false; + + CNavArea *startArea = TheNavAreaGrid.GetNearestNavArea( start ); + if (startArea == NULL) + return false; + + CNavArea *goalArea = TheNavAreaGrid.GetNavArea( goal ); + + // if we are already in the goal area, build trivial path + if (startArea == goalArea) + { + BuildTrivialPath( start, goal ); + return true; + } + + // make sure path end position is on the ground + Vector pathEndPosition = *goal; + if (goalArea) + pathEndPosition.z = goalArea->GetZ( &pathEndPosition ); + else + GetGroundHeight( &pathEndPosition, &pathEndPosition.z ); + + // + // Compute shortest path to goal + // + CNavArea *closestArea; + bool pathToGoalExists = NavAreaBuildPath( startArea, goalArea, goal, costFunc, &closestArea ); + + CNavArea *effectiveGoalArea = (pathToGoalExists) ? goalArea : closestArea; + + // + // Build path by following parent links + // + + // get count + int count = 0; + CNavArea *area; + for( area = effectiveGoalArea; area; area = area->GetParent() ) + ++count; + + // save room for endpoint + if (count > MAX_PATH_SEGMENTS-1) + count = MAX_PATH_SEGMENTS-1; + + if (count == 0) + return false; + + if (count == 1) + { + BuildTrivialPath( start, goal ); + return true; + } + + // build path + m_segmentCount = count; + for( area = effectiveGoalArea; count && area; area = area->GetParent() ) + { + --count; + m_path[ count ].area = area; + m_path[ count ].how = area->GetParentHow(); + } + + // compute path positions + if (ComputePathPositions() == false) + { + //PrintIfWatched( "Error building path\n" ); + Invalidate(); + return false; + } + + // append path end position + m_path[ m_segmentCount ].area = effectiveGoalArea; + m_path[ m_segmentCount ].pos = pathEndPosition; + m_path[ m_segmentCount ].ladder = NULL; + m_path[ m_segmentCount ].how = NUM_TRAVERSE_TYPES; + ++m_segmentCount; + + return true; + } + +private: + enum { MAX_PATH_SEGMENTS = 256 }; + PathSegment m_path[ MAX_PATH_SEGMENTS ]; + int m_segmentCount; + + bool ComputePathPositions( void ); ///< determine actual path positions + bool BuildTrivialPath( const Vector *start, const Vector *goal ); ///< utility function for when start and goal are in the same area + + int FindNextOccludedNode( int anchor ); ///< used by Optimize() +}; + +//-------------------------------------------------------------------------------------------------------- +/** + * Monitor improv movement and determine if it becomes stuck + */ +class CStuckMonitor +{ +public: + CStuckMonitor( void ); + + void Reset( void ); + void Update( CImprov *improv ); + bool IsStuck( void ) const { return m_isStuck; } + + float GetDuration( void ) const { return (m_isStuck) ? m_stuckTimer.GetElapsedTime() : 0.0f; } + +private: + bool m_isStuck; ///< if true, we are stuck + Vector m_stuckSpot; ///< the location where we became stuck + IntervalTimer m_stuckTimer; ///< how long we have been stuck + + enum { MAX_VEL_SAMPLES = 5 }; + float m_avgVel[ MAX_VEL_SAMPLES ]; + int m_avgVelIndex; + int m_avgVelCount; + Vector m_lastCentroid; + float m_lastTime; +}; + +//-------------------------------------------------------------------------------------------------------- +/** + * The CNavPathFollower class implements path following behavior + */ +class CNavPathFollower +{ +public: + CNavPathFollower( void ); + + void SetImprov( CImprov *improv ) { m_improv = improv; } + void SetPath( CNavPath *path ) { m_path = path; } + + void Reset( void ); + + #define DONT_AVOID_OBSTACLES false + void Update( float deltaT, bool avoidObstacles = true ); ///< move improv along path + void Debug( bool status ) { m_isDebug = status; } ///< turn debugging on/off + + bool IsStuck( void ) const { return m_stuckMonitor.IsStuck(); } ///< return true if improv is stuck + void ResetStuck( void ) { m_stuckMonitor.Reset(); } + float GetStuckDuration( void ) const { return m_stuckMonitor.GetDuration(); } ///< return how long we've been stuck + + void FeelerReflexAdjustment( Vector *goalPosition, float height = -1.0f ); ///< adjust goal position if "feelers" are touched + +private: + CImprov *m_improv; ///< who is doing the path following + + CNavPath *m_path; ///< the path being followed + + int m_segmentIndex; ///< the point on the path the improv is moving towards + int m_behindIndex; ///< index of the node on the path just behind us + Vector m_goal; ///< last computed follow goal + + bool m_isLadderStarted; + + bool m_isDebug; + + int FindOurPositionOnPath( Vector *close, bool local ) const; ///< return the closest point to our current position on current path + int FindPathPoint( float aheadRange, Vector *point, int *prevIndex ); ///< compute a point a fixed distance ahead along our path. + + CStuckMonitor m_stuckMonitor; +}; + + + +#endif // _NAV_PATH_H_ + diff --git a/game_shared/bot/simple_state_machine.h b/game_shared/bot/simple_state_machine.h new file mode 100644 index 0000000..61a1b42 --- /dev/null +++ b/game_shared/bot/simple_state_machine.h @@ -0,0 +1,85 @@ +// simple_state_machine.h +// Simple finite state machine encapsulation +// Author: Michael S. Booth (mike@turtlerockstudios.com), November 2003 + +#ifndef _SIMPLE_STATE_MACHINE_H_ +#define _SIMPLE_STATE_MACHINE_H_ + +//-------------------------------------------------------------------------------------------------------- +/** + * Encapsulation of a finite-state-machine state + */ +template < typename T > +class SimpleState +{ +public: + SimpleState( void ) + { + m_parent = NULL; + } + + virtual ~SimpleState() { } + + virtual void OnEnter( T userData ) { } ///< when state is entered + virtual void OnUpdate( T userData ) { } ///< state behavior + virtual void OnExit( T userData ) { } ///< when state exited + virtual const char *GetName( void ) const = 0; ///< return state name + + void SetParent( SimpleState *parent ) { m_parent = parent; } + SimpleState *GetParent( void ) const { return m_parent; } + +private: + SimpleState *m_parent; ///< the parent state that contains this state +}; + +//-------------------------------------------------------------------------------------------------------- +/** + * Encapsulation of a finite state machine + */ +template < typename T, typename S > +class SimpleStateMachine +{ +public: + SimpleStateMachine( void ) + { + m_state = NULL; + } + + void Reset( T userData ) + { + m_userData = userData; + m_state = NULL; + } + + /// change behavior state - WARNING: not re-entrant. Do not SetState() from within OnEnter() or OnExit() + void SetState( S *newState ) + { + if (m_state) + m_state->OnExit( m_userData ); + + newState->OnEnter( m_userData ); + + m_state = newState; + m_stateTimer.Start(); + } + + float GetStateDuration( void ) const { return m_stateTimer.GetElapsedTime(); } ///< how long have we been in the current state + + bool IsState( const S *state ) const { return (state == m_state); } ///< return true if given state is current state of machine + + /// execute current state of machine + void Update( void ) + { + if (m_state) + m_state->OnUpdate( m_userData ); + } + +protected: + S *m_state; ///< current behavior state + IntervalTimer m_stateTimer; ///< how long have we been in the current state + T m_userData; +}; + + +#endif // _SIMPLE_STATE_MACHINE_H_ + diff --git a/game_shared/perf_counter.h b/game_shared/perf_counter.h new file mode 100644 index 0000000..468d6cc --- /dev/null +++ b/game_shared/perf_counter.h @@ -0,0 +1,176 @@ +//========= Copyright � 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// Extracted from dbg.c (Michael S. Booth) + +#ifndef _PERF_COUNTER_H_ +#define _PERF_COUNTER_H_ + +#ifdef _WIN32 + #include // Currently needed for IsBadReadPtr and IsBadWritePtr + #include + #include +#else + #include + #include + #include + #include + #ifdef OSX + #include + #else + #include + #endif + #define MAX_PATH PATH_MAX + #include +#endif + +#include +//#include +#include +#include +#include +#include +#include + + +//----------------------------------------------------------------------------- +// Purpose: quick hacky timer class +//----------------------------------------------------------------------------- +class CPerformanceCounter +{ +public: + CPerformanceCounter(); + void InitializePerformanceCounter(); + double GetCurTime(); + +private: + int m_iLowShift; + double m_flPerfCounterFreq; + double m_flCurrentTime; + double m_flLastCurrentTime; +}; + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +inline CPerformanceCounter::CPerformanceCounter() +{ + InitializePerformanceCounter(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline void CPerformanceCounter::InitializePerformanceCounter() +{ +#ifdef _WIN32 + LARGE_INTEGER performanceFreq; + QueryPerformanceFrequency(&performanceFreq); + + // get 32 out of the 64 time bits such that we have around + // 1 microsecond resolution + unsigned int lowpart, highpart; + lowpart = (unsigned int)performanceFreq.LowPart; + highpart = (unsigned int)performanceFreq.HighPart; + m_iLowShift = 0; + + while (highpart || (lowpart > 2000000.0)) + { + m_iLowShift++; + lowpart >>= 1; + lowpart |= (highpart & 1) << 31; + highpart >>= 1; + } + + m_flPerfCounterFreq = 1.0 / (double)lowpart; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: returns the current time +//----------------------------------------------------------------------------- +inline double CPerformanceCounter::GetCurTime() +{ +#ifdef _WIN32 + static int sametimecount; + static unsigned int oldtime; + static int first = 1; + LARGE_INTEGER PerformanceCount; + unsigned int temp, t2; + double time; + + QueryPerformanceCounter(&PerformanceCount); + if (m_iLowShift == 0) + { + temp = (unsigned int)PerformanceCount.LowPart; + } + else + { + temp = ((unsigned int)PerformanceCount.LowPart >> m_iLowShift) | + ((unsigned int)PerformanceCount.HighPart << (32 - m_iLowShift)); + } + + if (first) + { + oldtime = temp; + first = 0; + } + else + { + // check for turnover or backward time + if ((temp <= oldtime) && ((oldtime - temp) < 0x10000000)) + { + oldtime = temp; // so we can't get stuck + } + else + { + t2 = temp - oldtime; + + time = (double)t2 * m_flPerfCounterFreq; + oldtime = temp; + + m_flCurrentTime += time; + + if (m_flCurrentTime == m_flLastCurrentTime) + { + sametimecount++; + + if (sametimecount > 100000) + { + m_flCurrentTime += 1.0; + sametimecount = 0; + } + } + else + { + sametimecount = 0; + } + + m_flLastCurrentTime = m_flCurrentTime; + } + } + + return m_flCurrentTime; + +#else + struct timeval tp; + static int secbase = 0; + + gettimeofday( &tp, NULL ); + + if ( !secbase ) + { + secbase = tp.tv_sec; + return ( tp.tv_usec / 1000000.0 ); + } + + return ( ( tp.tv_sec - secbase ) + tp.tv_usec / 1000000.0 ); +#endif /* _WIN32 */ +} + +#endif // _PERF_COUNTER_H_ diff --git a/game_shared/shared_util.cpp b/game_shared/shared_util.cpp new file mode 100644 index 0000000..eb810d0 --- /dev/null +++ b/game_shared/shared_util.cpp @@ -0,0 +1,261 @@ +//========= Copyright � 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: dll-agnostic routines (no dll dependencies here) +// +// $NoKeywords: $ +//============================================================================= + +// Author: Matthew D. Campbell (matt@turtlerockstudios.com), 2003 + +#include +#include +#include "shared_util.h" + +#ifndef _WIN32 +#define _vsnwprintf vswprintf +#endif + +static char s_shared_token[ 1500 ]; +static char s_shared_quote = '\"'; + +//-------------------------------------------------------------------------------------------------------------- +/** + * Wrapper for vgui::localize()->Find() that doesn't return NULL + */ +#ifdef CLIENT_DLL +#include +#include // for localize() +#include +wchar_t* SharedFindString( char *asciiIdentifier ) +{ + const int BufLen = 1024; + const int NumBuffers = 4; + static wchar_t string[NumBuffers][BufLen]; + static int curstring = 0; + + wchar_t *identifier = vgui::localize()->Find( asciiIdentifier ); + if ( !identifier ) + { + identifier = string[curstring]; + curstring = ( curstring + 1 ) % NumBuffers; + + vgui::localize()->ConvertANSIToUnicode( asciiIdentifier, identifier, BufLen*2 ); + } + + return identifier; +} +#endif + +//-------------------------------------------------------------------------------------------------------------- +wchar_t * SharedWVarArgs(wchar_t *format, ...) +{ + va_list argptr; + const int BufLen = 1024; + const int NumBuffers = 4; + static wchar_t string[NumBuffers][BufLen]; + static int curstring = 0; + + curstring = ( curstring + 1 ) % NumBuffers; + + va_start (argptr, format); + _vsnwprintf( string[curstring], BufLen, format, argptr ); + va_end (argptr); + + return string[curstring]; +} + +//-------------------------------------------------------------------------------------------------------------- +char * SharedVarArgs(char *format, ...) +{ + va_list argptr; + const int BufLen = 1024; + const int NumBuffers = 4; + static char string[NumBuffers][BufLen]; + static int curstring = 0; + + curstring = ( curstring + 1 ) % NumBuffers; + + va_start (argptr, format); +#ifdef _WIN32 + _vsnprintf( string[curstring], BufLen, format, argptr ); +#else + vsnprintf( string[curstring], BufLen, format, argptr ); +#endif + va_end (argptr); + + return string[curstring]; +} + +//-------------------------------------------------------------------------------------------------------------- +char * BufPrintf(char *buf, int& len, const char *fmt, ...) +{ + if (len <= 0) + return NULL; + + va_list argptr; + + va_start(argptr, fmt); + vsnprintf(buf, len, fmt, argptr); + va_end(argptr); + + len -= strlen(buf); + return buf + strlen(buf); +} + +//-------------------------------------------------------------------------------------------------------------- +wchar_t * BufWPrintf(wchar_t *buf, int& len, const wchar_t *fmt, ...) +{ + if (len <= 0) + return NULL; + + va_list argptr; + + va_start(argptr, fmt); + _vsnwprintf(buf, len, fmt, argptr); + va_end(argptr); + + len -= wcslen(buf); + return buf + wcslen(buf); +} + +//-------------------------------------------------------------------------------------------------------------- +const wchar_t * NumAsWString( int val ) +{ + const int BufLen = 16; + const int NumBuffers = 4; + static wchar_t string[NumBuffers][BufLen]; + static int curstring = 0; + + curstring = ( curstring + 1 ) % NumBuffers; + + int len = BufLen; + BufWPrintf( string[curstring], len, L"%d", val ); + return string[curstring]; +} + +//-------------------------------------------------------------------------------------------------------------- +const char * NumAsString( int val ) +{ + const int BufLen = 16; + const int NumBuffers = 4; + static char string[NumBuffers][BufLen]; + static int curstring = 0; + + curstring = ( curstring + 1 ) % NumBuffers; + + int len = BufLen; + BufPrintf( string[curstring], len, "%d", val ); + return string[curstring]; +} + +//-------------------------------------------------------------------------------------------------------- +/** + * Returns the token parsed by SharedParse() + */ +char *SharedGetToken( void ) +{ + return s_shared_token; +} + +//-------------------------------------------------------------------------------------------------------- +/** + * Returns the token parsed by SharedParse() + */ +void SharedSetQuoteChar( char c ) +{ + s_shared_quote = c; +} + +//-------------------------------------------------------------------------------------------------------- +/** + * Parse a token out of a string + */ +const char *SharedParse( const char *data ) +{ + int c; + int len; + + len = 0; + s_shared_token[0] = 0; + + if (!data) + return NULL; + +// skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + return NULL; // end of file; + data++; + } + +// skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + + +// handle quoted strings specially + if (c == s_shared_quote) + { + data++; + while (1) + { + c = *data++; + if (c==s_shared_quote || !c) + { + s_shared_token[len] = 0; + return data; + } + s_shared_token[len] = c; + len++; + } + } + +// parse single characters + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c == ',' ) + { + s_shared_token[len] = c; + len++; + s_shared_token[len] = 0; + return data+1; + } + +// parse a regular word + do + { + s_shared_token[len] = c; + data++; + len++; + c = *data; + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c == ',' ) + break; + } while (c>32); + + s_shared_token[len] = 0; + return data; +} + +//-------------------------------------------------------------------------------------------------------- +/** + * Returns true if additional data is waiting to be processed on this line + */ +bool SharedTokenWaiting( const char *buffer ) +{ + const char *p; + + p = buffer; + while ( *p && *p!='\n') + { + if ( !isspace( *p ) || isalnum( *p ) ) + return true; + + p++; + } + + return false; +} diff --git a/game_shared/shared_util.h b/game_shared/shared_util.h new file mode 100644 index 0000000..7bf1c8d --- /dev/null +++ b/game_shared/shared_util.h @@ -0,0 +1,120 @@ +//========= Copyright � 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: dll-agnostic routines (no dll dependencies here) +// +// $NoKeywords: $ +//============================================================================= + +// Author: Matthew D. Campbell (matt@turtlerockstudios.com), 2003 + +#ifndef SHARED_UTIL_H +#define SHARED_UTIL_H +#ifdef LINUX +#include +#include +#endif +#include + +//-------------------------------------------------------------------------------------------------------- +/** + * Returns the token parsed by SharedParse() + */ +char *SharedGetToken( void ); + +//-------------------------------------------------------------------------------------------------------- +/** + * Sets the character used to delimit quoted strings. Default is '\"'. Be sure to set it back when done. + */ +void SharedSetQuoteChar( char c ); + +//-------------------------------------------------------------------------------------------------------- +/** + * Parse a token out of a string + */ +const char *SharedParse( const char *data ); + +//-------------------------------------------------------------------------------------------------------- +/** + * Returns true if additional data is waiting to be processed on this line + */ +bool SharedTokenWaiting( const char *buffer ); + +//-------------------------------------------------------------------------------------------------------- +/** + * Simple utility function to allocate memory and duplicate a string + */ +inline char *CloneString( const char *str ) +{ + if ( !str ) + { + char *cloneStr = new char[1]; + cloneStr[0] = '\0'; + return cloneStr; + } + char *cloneStr = new char [ strlen(str)+1 ]; + strcpy( cloneStr, str ); + return cloneStr; +} + +//-------------------------------------------------------------------------------------------------------- +/** + * Simple utility function to allocate memory and duplicate a wide string + */ +inline wchar_t *CloneWString( const wchar_t *str ) +{ + if ( !str ) + { + wchar_t *cloneStr = new wchar_t[1]; + cloneStr[0] = L'\0'; + return cloneStr; + } + wchar_t *cloneStr = new wchar_t [ wcslen(str)+1 ]; + wcscpy( cloneStr, str ); + return cloneStr; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * snprintf-alike that allows multiple prints into a buffer + */ +char * BufPrintf(char *buf, int& len, const char *fmt, ...); + +//-------------------------------------------------------------------------------------------------------------- +/** + * wide char version of BufPrintf + */ +wchar_t * BufWPrintf(wchar_t *buf, int& len, const wchar_t *fmt, ...); + +//-------------------------------------------------------------------------------------------------------------- +/** + * convenience function that prints an int into a static wchar_t* + */ +const wchar_t * NumAsWString( int val ); + +//-------------------------------------------------------------------------------------------------------------- +/** + * convenience function that prints an int into a static char* + */ +const char * NumAsString( int val ); + +//-------------------------------------------------------------------------------------------------------------- +/** + * convenience function that composes a string into a static char* + */ +char * SharedVarArgs(char *format, ...); + +//-------------------------------------------------------------------------------------------------------------- +/** + * convenience function that composes a string into a static wchar_t* (Win32-only) + */ +wchar_t * SharedWVarArgs(wchar_t *format, ...); + +//-------------------------------------------------------------------------------------------------------------- +/** + * Wrapper for vgui::localize()->Find() that doesn't return NULL (client-only) + */ +#ifdef CLIENT_DLL +wchar_t* SharedFindString( char *asciiIdentifier ); +#endif + +#endif // SHARED_UTIL_H diff --git a/game_shared/simple_checksum.h b/game_shared/simple_checksum.h new file mode 100644 index 0000000..1cd0d11 --- /dev/null +++ b/game_shared/simple_checksum.h @@ -0,0 +1,25 @@ +// simple_checksum.h +// Functions to compute a simple checksum value for a file +// Author: Michael S. Booth, Turtle Rock Studios (www.turtlerockstudios.com), September 2003 + +#ifndef _SIMPLE_CHECKSUM_H_ +#define _SIMPLE_CHECKSUM_H_ + +/** + * Compute a simple checksum for the given data. + * Each byte in the data is multiplied by its position to track re-ordering changes + */ +inline unsigned int ComputeSimpleChecksum( const unsigned char *dataPointer, int dataLength ) +{ + unsigned int checksum = 0; + + for( int i=1; i<=dataLength; ++i ) + { + checksum += (*dataPointer) * i; + ++dataPointer; + } + + return checksum; +} + +#endif // _SIMPLE_CHECKSUM_H_ diff --git a/game_shared/steam_util.h b/game_shared/steam_util.h new file mode 100644 index 0000000..74cb4b3 --- /dev/null +++ b/game_shared/steam_util.h @@ -0,0 +1,58 @@ +// steam_util.h +// Steam utility classes +// Author: Michael S. Booth (mike@turtlerockstudios.com), April 2003 + +#ifndef _STEAM_UTIL_H_ +#define _STEAM_UTIL_H_ + +//-------------------------------------------------------------------------------------------------------------- +/** + * Used to load a file via Steam + */ +class SteamFile +{ +public: + SteamFile( const char *filename ); + ~SteamFile(); + + bool IsValid( void ) const { return (m_fileData) ? true : false; } ///< returns true if this file object is attached to a file + bool Read( void *data, int length ); ///< read 'length' bytes from the file + +private: + byte *m_fileData; ///< the file read into memory + int m_fileDataLength; ///< the length of the file + + byte *m_cursor; ///< where we are in the file + int m_bytesLeft; ///< the number of bytes left in the file +}; + +inline SteamFile::SteamFile( const char *filename ) +{ + m_fileData = (byte *)LOAD_FILE_FOR_ME( const_cast( filename ), &m_fileDataLength ); + m_cursor = m_fileData; + m_bytesLeft = m_fileDataLength; +} + +inline SteamFile::~SteamFile() +{ + if (m_fileData) + FREE_FILE( m_fileData ); +} + +inline bool SteamFile::Read( void *data, int length ) +{ + if (length > m_bytesLeft || m_cursor == NULL || m_bytesLeft <= 0) + return false; + + byte *readCursor = static_cast( data ); + + for( int i=0; i +#include +#include "vgui_checkbutton2.h" +#include "vgui_loadtga.h" + + +#define EXTRA_X 5 + + +using namespace vgui; + + + +CCheckButton2::CCheckButton2() : + m_Label(""), + m_pChecked(NULL), + m_pUnchecked(NULL), + m_pHandler(NULL), + m_CheckboxPanel(NULL) +{ + m_bOwnImages = false; + m_bChecked = false; + m_pChecked = m_pUnchecked = NULL; + m_bCheckboxLeft = true; + + m_Label.setParent(this); + m_Label.setFgColor(255,255,255,0); + m_Label.setBgColor(0,0,0,255); // background is not drawn and foreground is white + m_Label.addInputSignal(this); + + m_CheckboxPanel.setParent(this); + m_CheckboxPanel.addInputSignal(this); + + setPaintBackgroundEnabled(false); +} + + +CCheckButton2::~CCheckButton2() +{ + DeleteImages(); +} + + +void CCheckButton2::SetImages(char const *pChecked, char const *pUnchecked) +{ + DeleteImages(); + + m_pChecked = vgui_LoadTGA(pChecked); + m_pUnchecked = vgui_LoadTGA(pUnchecked); + m_bOwnImages = true; + + SetupControls(); +} + + +void CCheckButton2::SetImages(Image *pChecked, Image *pUnchecked) +{ + DeleteImages(); + + m_pChecked = pChecked; + m_pUnchecked = pUnchecked; + m_bOwnImages = false; + + SetupControls(); +} + + +void CCheckButton2::DeleteImages() +{ + if(m_bOwnImages) + { + delete m_pChecked; + delete m_pUnchecked; + } + + m_pChecked = NULL; + m_pUnchecked = NULL; + m_bOwnImages = false; + + SetupControls(); +} + + +void CCheckButton2::SetCheckboxLeft(bool bLeftAlign) +{ + m_bCheckboxLeft = bLeftAlign; + SetupControls(); +} + + +bool CCheckButton2::GetCheckboxLeft() +{ + return m_bCheckboxLeft; +} + + +void CCheckButton2::SetText(char const *pText, ...) +{ + char str[512]; + + va_list marker; + va_start(marker, pText); + _vsnprintf(str, sizeof(str), pText, marker); + va_end(marker); + + m_Label.setText(str); + SetupControls(); +} + + +void CCheckButton2::SetTextColor(int r, int g, int b, int a) +{ + m_Label.setFgColor(r, g, b, a); + repaint(); +} + + +void CCheckButton2::SetHandler(ICheckButton2Handler *pHandler) +{ + m_pHandler = pHandler; +} + + +bool CCheckButton2::IsChecked() +{ + return m_bChecked; +} + + +void CCheckButton2::SetChecked(bool bChecked) +{ + m_bChecked = bChecked; + SetupControls(); +} + + +void CCheckButton2::internalMousePressed(MouseCode code) +{ + m_bChecked = !m_bChecked; + + if(m_pHandler) + m_pHandler->StateChanged(this); + + SetupControls(); +} + + +void CCheckButton2::SetupControls() +{ + // Initialize the checkbutton bitmap. + Image *pBitmap = m_bChecked ? m_pChecked : m_pUnchecked; + + Panel *controls[2] = {&m_CheckboxPanel, &m_Label}; + int controlSizes[2][2]; + + controlSizes[0][0] = controlSizes[0][1] = 0; + if(pBitmap) + pBitmap->getSize(controlSizes[0][0], controlSizes[0][1]); + + m_CheckboxPanel.setImage(pBitmap); + m_CheckboxPanel.setSize(controlSizes[0][0], controlSizes[0][1]); + + + // Get the label's size. + m_Label.getSize(controlSizes[1][0], controlSizes[1][1]); + m_Label.setContentAlignment(Label::a_west); + + + // Position the controls. + int iLeftControl = !m_bCheckboxLeft; + int iBiggestY = controlSizes[0][1] > controlSizes[1][0] ? 0 : 1; + controls[iLeftControl]->setPos(0, (controlSizes[iBiggestY][1] - controlSizes[iLeftControl][1]) / 2); + controls[!iLeftControl]->setPos(controlSizes[iLeftControl][0] + EXTRA_X, (controlSizes[iBiggestY][1] - controlSizes[!iLeftControl][1]) / 2); + + + // Fit this control to the sizes of the subcontrols. + setSize(controlSizes[0][0] + controlSizes[1][0] + EXTRA_X, (controlSizes[0][1] > controlSizes[1][1]) ? controlSizes[0][1] : controlSizes[1][1]); + repaint(); +} + + +void CCheckButton2::mousePressed(MouseCode code, Panel *panel) +{ + internalMousePressed(code); +} + + + + + diff --git a/game_shared/vgui_checkbutton2.h b/game_shared/vgui_checkbutton2.h new file mode 100644 index 0000000..1a2702e --- /dev/null +++ b/game_shared/vgui_checkbutton2.h @@ -0,0 +1,101 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_CHECKBUTTON2_H +#define VGUI_CHECKBUTTON2_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "VGUI_Label.h" +#include "VGUI_ImagePanel.h" +#include "vgui_defaultinputsignal.h" + + +namespace vgui +{ + + +class CCheckButton2; + + +class ICheckButton2Handler +{ +public: + virtual void StateChanged(CCheckButton2 *pButton) = 0; +}; + + +// VGUI checkbox class. +// - Provides access to the checkbox images. +// - Provides an easy callback mechanism for state changes. +// - Default background is invisible, and default text color is white. +class CCheckButton2 : public Panel, public CDefaultInputSignal +{ +public: + + CCheckButton2(); + ~CCheckButton2(); + + // Initialize the button with these. + void SetImages(char const *pChecked, char const *pUnchecked); + void SetImages(Image *pChecked, Image *pUnchecked); // If you use this, the button will never delete the images. + void DeleteImages(); + + // The checkbox can be to the left or the right of the text (default is left). + void SetCheckboxLeft(bool bLeftAlign); + bool GetCheckboxLeft(); + + // Set the label text. + void SetText(char const *pText, ...); + void SetTextColor(int r, int g, int b, int a); + + // You can register for change notification here. + void SetHandler(ICheckButton2Handler *pHandler); + + // Get/set the check state. + bool IsChecked(); + void SetChecked(bool bChecked); + + + +// Panel overrides. +public: + + virtual void internalMousePressed(MouseCode code); + + +protected: + + void SetupControls(); + + +// InputSignal overrides. +protected: + virtual void mousePressed(MouseCode code,Panel* panel); + + +public: + ICheckButton2Handler *m_pHandler; + + bool m_bCheckboxLeft; + Label m_Label; + ImagePanel m_CheckboxPanel; + + Image *m_pChecked; + Image *m_pUnchecked; + bool m_bOwnImages; + + bool m_bChecked; +}; + + +} + + +#endif // VGUI_CHECKBUTTON2_H diff --git a/game_shared/vgui_defaultinputsignal.h b/game_shared/vgui_defaultinputsignal.h new file mode 100644 index 0000000..e933060 --- /dev/null +++ b/game_shared/vgui_defaultinputsignal.h @@ -0,0 +1,39 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_DEFAULTINPUTSIGNAL_H +#define VGUI_DEFAULTINPUTSIGNAL_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "VGUI_InputSignal.h" + + +namespace vgui +{ + // This class derives from vgui::InputSignal and implements empty defaults for all of its functions. + class CDefaultInputSignal : public vgui::InputSignal + { + public: + virtual void cursorMoved(int x,int y,Panel* panel) {} + virtual void cursorEntered(Panel* panel) {} + virtual void cursorExited(Panel* panel) {} + virtual void mousePressed(MouseCode code,Panel* panel) {} + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {} + virtual void mouseReleased(MouseCode code,Panel* panel) {} + virtual void mouseWheeled(int delta,Panel* panel) {} + virtual void keyPressed(KeyCode code,Panel* panel) {} + virtual void keyTyped(KeyCode code,Panel* panel) {} + virtual void keyReleased(KeyCode code,Panel* panel) {} + virtual void keyFocusTicked(Panel* panel) {} + }; +} + + +#endif // VGUI_DEFAULTINPUTSIGNAL_H diff --git a/game_shared/vgui_grid.cpp b/game_shared/vgui_grid.cpp new file mode 100644 index 0000000..5a1af15 --- /dev/null +++ b/game_shared/vgui_grid.cpp @@ -0,0 +1,398 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include "vgui_grid.h" + + +using namespace vgui; + + +#define AssertCheck(expr, msg) \ + if(!(expr))\ + {\ + assert(!msg);\ + return 0;\ + } + + + +// ------------------------------------------------------------------------------ // +// CGrid::CGridEntry. +// ------------------------------------------------------------------------------ // + +CGrid::CGridEntry::CGridEntry() +{ + m_pPanel = NULL; + m_bUnderline = false; +} + +CGrid::CGridEntry::~CGridEntry() +{ +} + + +// ------------------------------------------------------------------------------ // +// CGrid. +// ------------------------------------------------------------------------------ // + +CGrid::CGrid() +{ + Clear(); +} + + +CGrid::~CGrid() +{ + Term(); +} + + +bool CGrid::SetDimensions(int xCols, int yRows) +{ + Term(); + + m_GridEntries = new CGridEntry[xCols * yRows]; + m_Widths = new int[xCols*2 + yRows*2]; + m_Heights = m_Widths + xCols; + m_ColOffsets = m_Heights + yRows; + m_RowOffsets = m_ColOffsets + xCols; + + if(!m_GridEntries || !m_Widths) + { + Term(); + return false; + } + + memset(m_Widths, 0, sizeof(int) * (xCols*2 + yRows*2)); + + m_xCols = xCols; + m_yRows = yRows; + return true; +} + + +void CGrid::Term() +{ + delete [] m_GridEntries; + delete [] m_Widths; + Clear(); +} + + +Panel* CGrid::GetEntry(int x, int y) +{ + return GridEntry(x, y)->m_pPanel; +} + + +bool CGrid::SetEntry(int x, int y, Panel *pPanel) +{ + CGridEntry *pEntry = GridEntry(x, y); + if(!pEntry) + return false; + + if(pEntry->m_pPanel) + pEntry->m_pPanel->setParent(NULL); + + pEntry->m_pPanel = pPanel; + if(pPanel) + pPanel->setParent(this); + + m_bDirty = true; + return true; +} + + +int CGrid::GetXSpacing() +{ + return m_xSpacing; +} + + +int CGrid::GetYSpacing() +{ + return m_ySpacing; +} + + +void CGrid::SetSpacing(int xSpacing, int ySpacing) +{ + if(xSpacing != m_xSpacing) + { + m_xSpacing = xSpacing; + CalcColOffsets(0); + m_bDirty = true; + } + + if(ySpacing != m_ySpacing) + { + m_ySpacing = ySpacing; + CalcRowOffsets(0); + m_bDirty = true; + } +} + + +bool CGrid::SetColumnWidth(int iColumn, int width) +{ + AssertCheck(iColumn >= 0 && iColumn < m_xCols, "CGrid::SetColumnWidth : invalid location specified"); + m_Widths[iColumn] = width; + CalcColOffsets(iColumn+1); + m_bDirty = true; + return true; +} + + +bool CGrid::SetRowHeight(int iRow, int height) +{ + AssertCheck(iRow >= 0 && iRow < m_yRows, "CGrid::SetColumnWidth : invalid location specified"); + m_Heights[iRow] = height; + CalcRowOffsets(iRow+1); + m_bDirty = true; + return true; +} + + +int CGrid::GetColumnWidth(int iColumn) +{ + AssertCheck(iColumn >= 0 && iColumn < m_xCols, "CGrid::GetColumnWidth: invalid location specified"); + return m_Widths[iColumn]; +} + + +int CGrid::GetRowHeight(int iRow) +{ + AssertCheck(iRow >= 0 && iRow < m_yRows, "CGrid::GetRowHeight: invalid location specified"); + return m_Heights[iRow]; +} + + +int CGrid::CalcFitColumnWidth(int iColumn) +{ + AssertCheck(iColumn >= 0 && iColumn < m_xCols, "CGrid::CalcFitColumnWidth: invalid location specified"); + + int maxSize = 0; + for(int i=0; i < m_yRows; i++) + { + Panel *pPanel = GridEntry(iColumn, i)->m_pPanel; + if(!pPanel) + continue; + + int w, h; + pPanel->getSize(w,h); + if(w > maxSize) + maxSize = w; + } + + return maxSize; +} + + +int CGrid::CalcFitRowHeight(int iRow) +{ + AssertCheck(iRow >= 0 && iRow < m_yRows, "CGrid::CalcFitRowHeight: invalid location specified"); + + int maxSize = 0; + for(int i=0; i < m_xCols; i++) + { + Panel *pPanel = GridEntry(i, iRow)->m_pPanel; + if(!pPanel) + continue; + + int w, h; + pPanel->getSize(w,h); + if(h > maxSize) + maxSize = h; + } + + return maxSize; +} + + +void CGrid::AutoSetRowHeights() +{ + for(int i=0; i < m_yRows; i++) + SetRowHeight(i, CalcFitRowHeight(i)); +} + + +bool CGrid::GetEntryBox( + int col, int row, int &x, int &y, int &w, int &h) +{ + AssertCheck(col >= 0 && col < m_xCols && row >= 0 && row < m_yRows, "CGrid::GetEntryBox: invalid location specified"); + + x = m_ColOffsets[col]; + w = m_Widths[col]; + + y = m_RowOffsets[row]; + h = m_Heights[row]; + return true; +} + + +bool CGrid::CopyColumnWidths(CGrid *pOther) +{ + if(!pOther || pOther->m_xCols != m_xCols) + return false; + + for(int i=0; i < m_xCols; i++) + m_Widths[i] = pOther->m_Widths[i]; + + CalcColOffsets(0); + m_bDirty = true; + return true; +} + + +void CGrid::RepositionContents() +{ + for(int x=0; x < m_xCols; x++) + { + for(int y=0; y < m_yRows; y++) + { + Panel *pPanel = GridEntry(x,y)->m_pPanel; + if(!pPanel) + continue; + + pPanel->setBounds( + m_ColOffsets[x], + m_RowOffsets[y], + m_Widths[x], + m_Heights[y]); + } + } + + m_bDirty = false; +} + + +int CGrid::CalcDrawHeight() +{ + if(m_yRows > 0) + { + return m_RowOffsets[m_yRows-1] + m_Heights[m_yRows - 1] + m_ySpacing; + } + else + { + return 0; + } +} + + +void CGrid::paint() +{ + if(m_bDirty) + RepositionContents(); + + Panel::paint(); + + // walk the grid looking for underlined rows + int x = 0, y = 0; + for (int row = 0; row < m_yRows; row++) + { + CGridEntry *cell = GridEntry(0, row); + + y += cell->m_pPanel->getTall() + m_ySpacing; + if (cell->m_bUnderline) + { + drawSetColor(cell->m_UnderlineColor[0], cell->m_UnderlineColor[1], cell->m_UnderlineColor[2], cell->m_UnderlineColor[3]); + drawFilledRect(0, y - (cell->m_iUnderlineOffset + 1), getWide(), y - cell->m_iUnderlineOffset); + } + } +} + +void CGrid::paintBackground() +{ + Panel::paintBackground(); +} + +//----------------------------------------------------------------------------- +// Purpose: sets underline color for a particular row +//----------------------------------------------------------------------------- +void CGrid::SetRowUnderline(int row, bool enabled, int offset, int r, int g, int b, int a) +{ + CGridEntry *cell = GridEntry(0, row); + cell->m_bUnderline = enabled; + if (enabled) + { + cell->m_iUnderlineOffset = offset; + cell->m_UnderlineColor[0] = r; + cell->m_UnderlineColor[1] = g; + cell->m_UnderlineColor[2] = b; + cell->m_UnderlineColor[3] = a; + } +} + +void CGrid::Clear() +{ + m_xCols = m_yRows = 0; + m_Widths = NULL; + m_GridEntries = NULL; + m_xSpacing = m_ySpacing = 0; + m_bDirty = false; +} + + +CGrid::CGridEntry* CGrid::GridEntry(int x, int y) +{ + AssertCheck(x >= 0 && x < m_xCols && y >= 0 && y < m_yRows, "CGrid::GridEntry: invalid location specified"); + return &m_GridEntries[y*m_xCols + x]; +} + + +void CGrid::CalcColOffsets(int iStart) +{ + int cur = m_xSpacing; + if(iStart != 0) + cur += m_ColOffsets[iStart-1] + m_Widths[iStart-1]; + + for(int i=iStart; i < m_xCols; i++) + { + m_ColOffsets[i] = cur; + cur += m_Widths[i] + m_xSpacing; + } +} + + +void CGrid::CalcRowOffsets(int iStart) +{ + int cur = m_ySpacing; + if(iStart != 0) + cur += m_RowOffsets[iStart-1]; + + for(int i=iStart; i < m_yRows; i++) + { + m_RowOffsets[i] = cur; + cur += m_Heights[i] + m_ySpacing; + } +} + +bool CGrid::getCellAtPoint(int worldX, int worldY, int &row, int &col) +{ + row = -1; col = -1; + for(int x=0; x < m_xCols; x++) + { + for(int y=0; y < m_yRows; y++) + { + Panel *pPanel = GridEntry(x,y)->m_pPanel; + if (!pPanel) + continue; + + if (pPanel->isWithin(worldX, worldY)) + { + col = x; + row = y; + return true; + } + } + } + + return false; +} + + diff --git a/game_shared/vgui_grid.h b/game_shared/vgui_grid.h new file mode 100644 index 0000000..ba83ffe --- /dev/null +++ b/game_shared/vgui_grid.h @@ -0,0 +1,122 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_GRID_H +#define VGUI_GRID_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "VGUI_Panel.h" + + +namespace vgui +{ + +// The grid control simply manages a grid of panels. You can adjust column sizes and spacings and +// configure and fill the panels however you want. +// To use this control, call SetDimensions, SetSpacing and fill the controls. +class CGrid : public Panel +{ +public: + CGrid(); + virtual ~CGrid(); + + bool SetDimensions(int xCols, int yRows); // Set how many columns and rows in the grid. + void Term(); + + Panel* GetEntry(int x, int y); // Get the panel associated with a grid entry. + bool SetEntry(int x, int y, Panel *pPanel); + + int GetXSpacing(); + int GetYSpacing(); + void SetSpacing(int xSpacing, int ySpacing); // Set spacing between rows and columns. + + bool SetColumnWidth(int iColumn, int width); // Set a column's width. + bool SetRowHeight(int iRow, int height); // Set a row's height. + + int GetColumnWidth(int iColumn); + int GetRowHeight(int iRow); + + int CalcFitColumnWidth(int iColumn); // Returns the maximum width of all panels in the column. + int CalcFitRowHeight(int iRow); // Returns the maximum height of all panels in the row. + + int CalcDrawHeight(); // Returns how many pixels high the grid control should be + // for all of its contents to be visible (based on its row heights + // and y spacing). + + void AutoSetRowHeights(); // Just does SetRowHeight(iRow, CalcFitRowHeight(iRow)) for all rows. + + bool GetEntryBox( // Returns the bounding box for the specified entry. + int col, int row, int &x, int &y, int &w, int &h); + + bool CopyColumnWidths(CGrid *pOther); // Copy the column widths from the other grid. Fails if the + // column count is different. + + void RepositionContents(); // Sets the size and position of all the grid entries based + // on current spacings and row/column widths. + // You usually only want to call this while setting up the control + // if you want to get the position or dimensions of the child + // controls. This will set them. + + void SetRowUnderline(int row, bool enabled, int offset, int r, int g, int b, int a); // sets underline color for a particular row + + // returns the true if found, false otherwise + bool getCellAtPoint(int worldX, int worldY, int &row, int &col); + +// Panel overrides. +public: + + virtual void paint(); + virtual void paintBackground(); + +protected: + + class CGridEntry + { + public: + CGridEntry(); + ~CGridEntry(); + + Panel *m_pPanel; + + bool m_bUnderline; + short m_UnderlineColor[4]; + int m_iUnderlineOffset; + }; + + void Clear(); + CGridEntry* GridEntry(int x, int y); + + void CalcColOffsets(int iStart); + void CalcRowOffsets(int iStart); + + +protected: + + bool m_bDirty; // Set when controls will need to be repositioned. + + int m_xCols; + int m_yRows; + + int m_xSpacing; + int m_ySpacing; + + int *m_Widths; + int *m_Heights; + int *m_ColOffsets; + int *m_RowOffsets; + + CGridEntry *m_GridEntries; + +}; + +}; + + +#endif // VGUI_GRID_H diff --git a/game_shared/vgui_helpers.cpp b/game_shared/vgui_helpers.cpp new file mode 100644 index 0000000..c269475 --- /dev/null +++ b/game_shared/vgui_helpers.cpp @@ -0,0 +1,45 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "vgui_helpers.h" + + +using namespace vgui; + + +void AlignPanel(Panel *pChild, Panel *pParent, int alignment) +{ + int w, h, cw, ch; + pParent->getSize(w, h); + pChild->getSize(cw, ch); + + int xCenter = (w - cw) / 2; + int yCenter = (h - ch) / 2; + + if(alignment == Label::a_west) + pChild->setPos(0, yCenter); + else if(alignment == Label::a_northwest) + pChild->setPos(0,0); + else if(alignment == Label::a_north) + pChild->setPos(xCenter, 0); + else if(alignment == Label::a_northeast) + pChild->setPos(w - cw, 0); + else if(alignment == Label::a_east) + pChild->setPos(w - cw, yCenter); + else if(alignment == Label::a_southeast) + pChild->setPos(w - cw, h - ch); + else if(alignment == Label::a_south) + pChild->setPos(xCenter, h - ch); + else if(alignment == Label::a_southwest) + pChild->setPos(0, h - ch); + else if(alignment == Label::a_center) + pChild->setPos(xCenter, yCenter); +} + + + + diff --git a/game_shared/vgui_helpers.h b/game_shared/vgui_helpers.h new file mode 100644 index 0000000..6dae8bb --- /dev/null +++ b/game_shared/vgui_helpers.h @@ -0,0 +1,31 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_HELPERS_H +#define VGUI_HELPERS_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "VGUI_Panel.h" +#include "VGUI_Label.h" + + +inline int PanelTop(vgui::Panel *pPanel) {int x,y,w,h; pPanel->getBounds(x,y,w,h); return y;} +inline int PanelLeft(vgui::Panel *pPanel) {int x,y,w,h; pPanel->getBounds(x,y,w,h); return x;} +inline int PanelRight(vgui::Panel *pPanel) {int x,y,w,h; pPanel->getBounds(x,y,w,h); return x+w;} +inline int PanelBottom(vgui::Panel *pPanel) {int x,y,w,h; pPanel->getBounds(x,y,w,h); return y+h;} +inline int PanelWidth(vgui::Panel *pPanel) {int x,y,w,h; pPanel->getBounds(x,y,w,h); return w;} +inline int PanelHeight(vgui::Panel *pPanel) {int x,y,w,h; pPanel->getBounds(x,y,w,h); return h;} + +// Places child at the requested position inside pParent. iAlignment is from Label::Alignment. +void AlignPanel(vgui::Panel *pChild, vgui::Panel *pParent, int alignment); + + +#endif // VGUI_HELPERS_H + diff --git a/game_shared/vgui_listbox.cpp b/game_shared/vgui_listbox.cpp new file mode 100644 index 0000000..6986d90 --- /dev/null +++ b/game_shared/vgui_listbox.cpp @@ -0,0 +1,207 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "vgui_listbox.h" + + + +using namespace vgui; + + +CListBox::CListBox() : Panel(0, 0, 0, 0), + m_ItemsPanel(0,0,0,0), + m_ScrollBar(0, 0, 0, 0, true), + m_Slider(0, 0, 10, 40, true) +{ + m_Signal.m_pListBox = this; + + m_ItemsPanel.setParent(this); + m_ItemsPanel.setBgColor(0,0,0,255); + + m_Slider.setRangeWindow(50); + m_Slider.setRangeWindowEnabled(true); + + m_ScrollBar.setParent(this); + m_ScrollBar.addIntChangeSignal(&m_Signal); + m_ScrollBar.setSlider(&m_Slider); + m_ScrollBar.setButtonPressedScrollValue(1); + + m_Items.m_pNext = m_Items.m_pPrev = &m_Items; + m_ItemOffset = 0; + m_iScrollMax = -1; +} + +CListBox::~CListBox() +{ + Term(); +} + +void CListBox::Init() +{ + Term(); +} + +void CListBox::Term() +{ + m_ItemOffset = 0; + + // Free the LBItems. + LBItem *pNext; + for(LBItem *pItem=m_Items.m_pNext; pItem != &m_Items; pItem=pNext) + { + pItem->m_pPanel->setParent(NULL); // detach the panel from us + pNext = pItem->m_pNext; + delete pItem; + } + m_Items.m_pPrev = m_Items.m_pNext = &m_Items; +} + +void CListBox::AddItem(Panel* panel) +{ + // Add the item. + LBItem *pItem = new LBItem; + if(!pItem) + return; + + pItem->m_pPanel = panel; + pItem->m_pPanel->setParent(&m_ItemsPanel); + + pItem->m_pPrev = m_Items.m_pPrev; + pItem->m_pNext = &m_Items; + pItem->m_pNext->m_pPrev = pItem->m_pPrev->m_pNext = pItem; + + m_ScrollBar.setRange(0, GetScrollMax()); + m_Slider.setRangeWindow(50); + m_Slider.setRangeWindowEnabled(true); + + InternalLayout(); +} + +int CListBox::GetNumItems() +{ + int count=0; + for(LBItem *pItem=m_Items.m_pNext; pItem != &m_Items; pItem=pItem->m_pNext) + ++count; + + return count; +} + +int CListBox::GetItemWidth() +{ + int wide, tall; + m_ItemsPanel.getSize(wide, tall); + return wide; +} + +int CListBox::GetScrollPos() +{ + return m_ItemOffset; +} + +void CListBox::SetScrollPos(int pos) +{ + int maxItems = GetScrollMax(); + if(maxItems < 0) + return; + + m_ItemOffset = (pos < 0) ? 0 : ((pos > maxItems) ? maxItems : pos); + InternalLayout(); +} + +void CListBox::setPos(int x, int y) +{ + Panel::setPos(x, y); + InternalLayout(); +} + +void CListBox::setSize(int wide,int tall) +{ + Panel::setSize(wide,tall); + InternalLayout(); +} + +void CListBox::setPixelScroll(int value) +{ + m_ItemOffset = m_ScrollBar.getValue(); + InternalLayout(); +} + +void CListBox::InternalLayout() +{ + int x, y, wide, tall; + getBounds(x, y, wide, tall); + + // Reposition the main panel and the scrollbar. + m_ItemsPanel.setBounds(0, 0, wide-15, tall); + m_ScrollBar.setBounds(wide-15, 0, 15, tall); + + bool bNeedScrollbar = false; + + // Reposition the items. + int curItem = 0; + int curY = 0; + int maxItem = GetScrollMax(); + for(LBItem *pItem=m_Items.m_pNext; pItem != &m_Items; pItem=pItem->m_pNext) + { + if(curItem < m_ItemOffset) + { + pItem->m_pPanel->setVisible(false); + bNeedScrollbar = true; + } + else if (curItem >= maxItem) + { + // item is past the end of the items we care about + pItem->m_pPanel->setVisible(false); + } + else + { + pItem->m_pPanel->setVisible(true); + + int itemWidth, itemHeight; + pItem->m_pPanel->getSize(itemWidth, itemHeight); + + // Don't change the item's height but change its width to fit the listbox. + pItem->m_pPanel->setBounds(0, curY, wide, itemHeight); + + curY += itemHeight; + + if (curY > tall) + { + bNeedScrollbar = true; + } + } + + ++curItem; + } + + m_ScrollBar.setVisible(bNeedScrollbar); + + repaint(); +} + +void CListBox::paintBackground() +{ +} + +void CListBox::SetScrollRange(int maxScroll) +{ + m_iScrollMax = maxScroll; + m_ScrollBar.setRange(0, maxScroll); + InternalLayout(); +} + +int CListBox::GetScrollMax() +{ + if (m_iScrollMax < 0) + { + return GetNumItems() - 1; + } + + return m_iScrollMax; +} + + diff --git a/game_shared/vgui_listbox.h b/game_shared/vgui_listbox.h new file mode 100644 index 0000000..9e9b724 --- /dev/null +++ b/game_shared/vgui_listbox.h @@ -0,0 +1,115 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VOICE_LISTBOX_H +#define VOICE_LISTBOX_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "VGUI_Panel.h" +#include "VGUI_IntChangeSignal.h" + +#include "vgui_slider2.h" +#include "vgui_scrollbar2.h" + + +namespace vgui +{ + +// Listbox class used by voice code. Based off of vgui's list panel but with some modifications: +// - This listbox clips its child items to its rectangle. +// - You can access things like the scrollbar and find out the item width. +// - The scrollbar scrolls one element at a time and the range is correct. + +// Note: this listbox does not provide notification when items are +class CListBox : public Panel +{ +public: + + CListBox(); + ~CListBox(); + + void Init(); + void Term(); + + // Add an item to the listbox. This automatically sets the item's parent to the listbox + // and resizes the item's width to fit within the listbox. + void AddItem(Panel *pPanel); + + // Get the number of items currently in the listbox. + int GetNumItems(); + + // Get the width that listbox items will be set to (this changes if you resize the listbox). + int GetItemWidth(); + + // Get/set the scrollbar position (position says which element is at the top of the listbox). + int GetScrollPos(); + void SetScrollPos(int pos); + + // sets the last item the listbox should scroll to + // scroll to GetNumItems() if not set + void SetScrollRange(int maxScroll); + + // returns the maximum value the scrollbar can scroll to + int GetScrollMax(); + +// vgui overrides. +public: + + virtual void setPos(int x, int y); + virtual void setSize(int wide,int tall); + virtual void setPixelScroll(int value); + virtual void paintBackground(); + + +protected: + + class LBItem + { + public: + Panel *m_pPanel; + LBItem *m_pPrev, *m_pNext; + }; + + class ListBoxSignal : public IntChangeSignal + { + public: + void intChanged(int value,Panel* panel) + { + m_pListBox->setPixelScroll(-value); + } + + vgui::CListBox *m_pListBox; + }; + + +protected: + + void InternalLayout(); + + +protected: + + // All the items.. + LBItem m_Items; + + Panel m_ItemsPanel; + + int m_ItemOffset; // where we're scrolled to + Slider2 m_Slider; + ScrollBar2 m_ScrollBar; + ListBoxSignal m_Signal; + + int m_iScrollMax; +}; + +} + + +#endif // VOICE_LISTBOX_H diff --git a/game_shared/vgui_loadtga.cpp b/game_shared/vgui_loadtga.cpp new file mode 100644 index 0000000..07d9ce7 --- /dev/null +++ b/game_shared/vgui_loadtga.cpp @@ -0,0 +1,93 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "../cl_dll/wrect.h" +#include "../cl_dll/cl_dll.h" +#include "VGUI.h" +#include "vgui_loadtga.h" +#include "VGUI_InputStream.h" + + +// ---------------------------------------------------------------------- // +// Helper class for loading tga files. +// ---------------------------------------------------------------------- // +class MemoryInputStream : public vgui::InputStream +{ +public: + MemoryInputStream() + { + m_pData = NULL; + m_DataLen = m_ReadPos = 0; + } + + virtual void seekStart(bool& success) {m_ReadPos=0; success=true;} + virtual void seekRelative(int count,bool& success) {m_ReadPos+=count; success=true;} + virtual void seekEnd(bool& success) {m_ReadPos=m_DataLen; success=true;} + virtual int getAvailable(bool& success) {success=false; return 0;} // This is what vgui does for files... + + virtual uchar readUChar(bool& success) + { + if(m_ReadPos>=0 && m_ReadPos +#include +#include +#include + +using namespace vgui; + + +namespace +{ +class FooDefaultScrollBarIntChangeSignal : public IntChangeSignal +{ +public: + FooDefaultScrollBarIntChangeSignal(ScrollBar2* scrollBar) + { + _scrollBar=scrollBar; + } + virtual void intChanged(int value,Panel* panel) + { + _scrollBar->fireIntChangeSignal(); + } +protected: + ScrollBar2* _scrollBar; +}; + +class FooDefaultButtonSignal : public ActionSignal +{ +public: + ScrollBar2* _scrollBar; + int _buttonIndex; +public: + FooDefaultButtonSignal(ScrollBar2* scrollBar,int buttonIndex) + { + _scrollBar=scrollBar; + _buttonIndex=buttonIndex; + } +public: + virtual void actionPerformed(Panel* panel) + { + _scrollBar->doButtonPressed(_buttonIndex); + } +}; + +} + +//----------------------------------------------------------------------------- +// Purpose: Default scrollbar button +// Draws in new scoreboard style +//----------------------------------------------------------------------------- +class ScrollBarButton : public Button +{ +private: + LineBorder m_Border; + +public: + ScrollBarButton(const char *filename, int x, int y, int wide, int tall) : m_Border(Color(60, 60, 60, 0)), Button("", x, y, wide, tall) + { + Image *image = vgui_LoadTGA(filename); + if (image) + { + image->setColor(Color(140, 140, 140, 0)); + setImage(image); + } + + setBorder(&m_Border); + } + + virtual void paintBackground() + { + int wide,tall; + getPaintSize(wide,tall); + + // fill the background + drawSetColor(0, 0, 0, 0); + drawFilledRect(0, 0, wide, tall); + } +}; + + + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +// Input : x - +// y - +// wide - +// tall - +// vertical - +//----------------------------------------------------------------------------- +ScrollBar2::ScrollBar2(int x,int y,int wide,int tall,bool vertical) : Panel(x,y,wide,tall) +{ + _slider=null; + _button[0]=null; + _button[1]=null; + + if(vertical) + { + setSlider(new Slider2(0,wide-1,wide,(tall-(wide*2))+2,true)); + setButton(new ScrollBarButton("gfx/vgui/arrowup.tga",0,0,wide,wide),0); + setButton(new ScrollBarButton("gfx/vgui/arrowdown.tga",0,tall-wide,wide,wide),1); + } + else + { + // untested code + setSlider(new Slider2(tall,0,wide-(tall*2),tall,false)); + setButton(new ScrollBarButton("gfx/vgui/320_arrowlt.tga",0,0,tall+1,tall+1),0); + setButton(new ScrollBarButton("gfx/vgui/320_arrowrt.tga",wide-tall,0,tall+1,tall+1),1); + } + + setPaintBorderEnabled(true); + setPaintBackgroundEnabled(true); + setPaintEnabled(true); + setButtonPressedScrollValue(15); + + validate(); + } + +void ScrollBar2::setSize(int wide,int tall) +{ + Panel::setSize(wide,tall); + + if(_slider==null) + { + return; + } + + if(_button[0]==null) + { + return; + } + + if(_button[1]==null) + { + return; + } + + getPaintSize(wide,tall); + + if(_slider->isVertical()) + { + _slider->setBounds(0,wide,wide,tall-(wide*2)); + //_slider->setBounds(0,0,wide,tall); + _button[0]->setBounds(0,0,wide,wide); + _button[1]->setBounds(0,tall-wide,wide,wide); + } + else + { + _slider->setBounds(tall,0,wide-(tall*2),tall); + //_slider->setBounds(0,0,wide,tall); + _button[0]->setBounds(0,0,tall,tall); + _button[1]->setBounds((wide-tall),0,tall,tall); + } +} + +void ScrollBar2::performLayout() +{ +} + +void ScrollBar2::setValue(int value) +{ + _slider->setValue(value); +} + +int ScrollBar2::getValue() +{ + return _slider->getValue(); +} + +void ScrollBar2::addIntChangeSignal(IntChangeSignal* s) +{ + _intChangeSignalDar.putElement(s); + _slider->addIntChangeSignal(new FooDefaultScrollBarIntChangeSignal(this)); +} + +void ScrollBar2::setRange(int min,int max) +{ + _slider->setRange(min,max); +} + +void ScrollBar2::fireIntChangeSignal() +{ + for(int i=0;i<_intChangeSignalDar.getCount();i++) + { + _intChangeSignalDar[i]->intChanged(_slider->getValue(),this); + } +} + +bool ScrollBar2::isVertical() +{ + return _slider->isVertical(); +} + +bool ScrollBar2::hasFullRange() +{ + return _slider->hasFullRange(); +} + +//LEAK: new and old slider will leak +void ScrollBar2::setButton(Button* button,int index) +{ + if(_button[index]!=null) + { + removeChild(_button[index]); + } + _button[index]=button; + addChild(_button[index]); + + _button[index]->addActionSignal(new FooDefaultButtonSignal(this,index)); + + validate(); + + //_button[index]->setVisible(false); +} + +Button* ScrollBar2::getButton(int index) +{ + return _button[index]; +} + +//LEAK: new and old slider will leak +void ScrollBar2::setSlider(Slider2 *slider) +{ + if(_slider!=null) + { + removeChild(_slider); + } + _slider=slider; + addChild(_slider); + + _slider->addIntChangeSignal(new FooDefaultScrollBarIntChangeSignal(this)); + + validate(); +} + +Slider2 *ScrollBar2::getSlider() +{ + return _slider; +} + +void ScrollBar2::doButtonPressed(int buttonIndex) +{ + if(buttonIndex==0) + { + _slider->setValue(_slider->getValue()-_buttonPressedScrollValue); + } + else + { + _slider->setValue(_slider->getValue()+_buttonPressedScrollValue); + } + +} + +void ScrollBar2::setButtonPressedScrollValue(int value) +{ + _buttonPressedScrollValue=value; +} + +void ScrollBar2::setRangeWindow(int rangeWindow) +{ + _slider->setRangeWindow(rangeWindow); +} + +void ScrollBar2::setRangeWindowEnabled(bool state) +{ + _slider->setRangeWindowEnabled(state); +} + +void ScrollBar2::validate() +{ + if(_slider!=null) + { + int buttonOffset=0; + + for(int i=0;i<2;i++) + { + if(_button[i]!=null) + { + if(_button[i]->isVisible()) + { + if(_slider->isVertical()) + { + buttonOffset+=_button[i]->getTall(); + } + else + { + buttonOffset+=_button[i]->getWide(); + } + } + } + } + + _slider->setButtonOffset(buttonOffset); + } + + int wide,tall; + getSize(wide,tall); + setSize(wide,tall); +} diff --git a/game_shared/vgui_scrollbar2.h b/game_shared/vgui_scrollbar2.h new file mode 100644 index 0000000..ad2d2d0 --- /dev/null +++ b/game_shared/vgui_scrollbar2.h @@ -0,0 +1,62 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_SCROLLBAR2_H +#define VGUI_SCROLLBAR2_H +#ifdef _WIN32 +#pragma once +#endif + +#include +#include +#include + +namespace vgui +{ + +class IntChangeSignal; +class Button; +class Slider2; + +//----------------------------------------------------------------------------- +// Purpose: Hacked up version of the vgui scrollbar +//----------------------------------------------------------------------------- +class VGUIAPI ScrollBar2 : public Panel +{ +public: + ScrollBar2(int x,int y,int wide,int tall,bool vertical); +public: + virtual void setValue(int value); + virtual int getValue(); + virtual void addIntChangeSignal(IntChangeSignal* s); + virtual void setRange(int min,int max); + virtual void setRangeWindow(int rangeWindow); + virtual void setRangeWindowEnabled(bool state); + virtual void setSize(int wide,int tall); + virtual bool isVertical(); + virtual bool hasFullRange(); + virtual void setButton(Button *button,int index); + virtual Button* getButton(int index); + virtual void setSlider(Slider2 *slider); + virtual Slider2 *getSlider(); + virtual void doButtonPressed(int buttonIndex); + virtual void setButtonPressedScrollValue(int value); + virtual void validate(); +public: //bullshit public + virtual void fireIntChangeSignal(); +protected: + virtual void performLayout(); +protected: + Button* _button[2]; + Slider2 *_slider; + Dar _intChangeSignalDar; + int _buttonPressedScrollValue; +}; + +} + +#endif // VGUI_SCROLLBAR2_H diff --git a/game_shared/vgui_slider2.cpp b/game_shared/vgui_slider2.cpp new file mode 100644 index 0000000..76c6b2f --- /dev/null +++ b/game_shared/vgui_slider2.cpp @@ -0,0 +1,436 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: New version of the slider bar +// +// $NoKeywords: $ +//============================================================================= + +#include "vgui_slider2.h" + +#include +#include +#include +#include + +using namespace vgui; + +namespace +{ +class FooDefaultSliderSignal : public InputSignal +{ +private: + Slider2* _slider; +public: + FooDefaultSliderSignal(Slider2* slider) + { + _slider=slider; + } +public: + void cursorMoved(int x,int y,Panel* panel) + { + _slider->privateCursorMoved(x,y,panel); + } + void cursorEntered(Panel* panel){} + void cursorExited(Panel* panel){} + void mouseDoublePressed(MouseCode code,Panel* panel){} + void mousePressed(MouseCode code,Panel* panel) + { + _slider->privateMousePressed(code,panel); + } + void mouseReleased(MouseCode code,Panel* panel) + { + _slider->privateMouseReleased(code,panel); + } + void mouseWheeled(int delta,Panel* panel){} + void keyPressed(KeyCode code,Panel* panel){} + void keyTyped(KeyCode code,Panel* panel){} + void keyReleased(KeyCode code,Panel* panel){} + void keyFocusTicked(Panel* panel){} +}; +} + +Slider2::Slider2(int x,int y,int wide,int tall,bool vertical) : Panel(x,y,wide,tall) +{ + _vertical=vertical; + _dragging=false; + _value=0; + _range[0]=0; + _range[1]=299; + _rangeWindow=0; + _rangeWindowEnabled=false; + _buttonOffset=0; + recomputeNobPosFromValue(); + addInputSignal(new FooDefaultSliderSignal(this)); +} + +void Slider2::setSize(int wide,int tall) +{ + Panel::setSize(wide,tall); + recomputeNobPosFromValue(); +} + +bool Slider2::isVertical() +{ + return _vertical; +} + +void Slider2::setValue(int value) +{ + int oldValue=_value; + + if(value<_range[0]) + { + value=_range[0]; + } + + if(value>_range[1]) + { + value=_range[1]; + } + + _value=value; + recomputeNobPosFromValue(); + + if(_value!=oldValue) + { + fireIntChangeSignal(); + } +} + +int Slider2::getValue() +{ + return _value; +} + +void Slider2::recomputeNobPosFromValue() +{ + int wide,tall; + + getPaintSize(wide,tall); + + float fwide=(float)wide; + float ftall=(float)tall; + float frange=(float)(_range[1]-_range[0]); + float fvalue=(float)(_value-_range[0]); + float fper=fvalue/frange; + float frangewindow=(float)(_rangeWindow); + + if(frangewindow<0) + { + frangewindow=0; + } + + if(!_rangeWindowEnabled) + { + frangewindow=frange; + } + + if ( frangewindow > 0 ) + { + if(_vertical) + { + float fnobsize=frangewindow; + float freepixels = ftall - fnobsize; + + float firstpixel = freepixels * fper; + + _nobPos[0]=(int)( firstpixel ); + _nobPos[1]=(int)( firstpixel + fnobsize ); + + if(_nobPos[1]>tall) + { + _nobPos[0]=tall-((int)fnobsize); + _nobPos[1]=tall; + } + } + else + { + float fnobsize=frangewindow; + float freepixels = fwide - fnobsize; + + float firstpixel = freepixels * fper; + + _nobPos[0]=(int)( firstpixel ); + _nobPos[1]=(int)( firstpixel + fnobsize ); + + if(_nobPos[1]>wide) + { + _nobPos[0]=wide-((int)fnobsize); + _nobPos[1]=wide; + } + } + } + + repaint(); +} + +void Slider2::recomputeValueFromNobPos() +{ + int wide,tall; + getPaintSize(wide,tall); + + float fwide=(float)wide; + float ftall=(float)tall; + float frange=(float)(_range[1]-_range[0]); + float fvalue=(float)(_value-_range[0]); + float fnob=(float)_nobPos[0]; + float frangewindow=(float)(_rangeWindow); + + if(frangewindow<0) + { + frangewindow=0; + } + + if(!_rangeWindowEnabled) + { + frangewindow=frange; + } + + if ( frangewindow > 0 ) + { + if(_vertical) + { + float fnobsize=frangewindow; + fvalue=frange*(fnob/(ftall-fnobsize)); + } + else + { + float fnobsize=frangewindow; + fvalue=frange*(fnob/(fwide-fnobsize)); + } + } + // Take care of rounding issues. + _value=(int)(fvalue+_range[0]+0.5); + + // Clamp final result + _value = ( _value < _range[1] ) ? _value : _range[1]; +} + +bool Slider2::hasFullRange() +{ + int wide,tall; + getPaintSize(wide,tall); + + float fwide=(float)wide; + float ftall=(float)tall; + float frange=(float)(_range[1]-_range[0]); + float frangewindow=(float)(_rangeWindow); + + if(frangewindow<0) + { + frangewindow=0; + } + + if(!_rangeWindowEnabled) + { + frangewindow=frange; + } + + if ( frangewindow > 0 ) + { + if(_vertical) + { + if( frangewindow <= ( ftall + _buttonOffset ) ) + { + return true; + } + } + else + { + if( frangewindow <= ( fwide + _buttonOffset ) ) + { + return true; + } + } + } + + return false; +} + +void Slider2::addIntChangeSignal(IntChangeSignal* s) +{ + _intChangeSignalDar.putElement(s); +} + +void Slider2::fireIntChangeSignal() +{ + for(int i=0;i<_intChangeSignalDar.getCount();i++) + { + _intChangeSignalDar[i]->intChanged(getValue(),this); + } +} + +void Slider2::paintBackground() +{ + int wide,tall; + getPaintSize(wide,tall); + + if (_vertical) + { + // background behind slider + drawSetColor(40, 40, 40, 0); + drawFilledRect(0, 0, wide, tall); + + // slider front + drawSetColor(0, 0, 0, 0); + drawFilledRect(0,_nobPos[0],wide,_nobPos[1]); + + // slider border + drawSetColor(60, 60, 60, 0); + drawFilledRect(0,_nobPos[0],wide,_nobPos[0]+1); // top + drawFilledRect(0,_nobPos[1],wide,_nobPos[1]+1); // bottom + drawFilledRect(0,_nobPos[0]+1,1,_nobPos[1]); // left + drawFilledRect(wide-1,_nobPos[0]+1,wide,_nobPos[1]); // right + } + else + { + //!! doesn't work + + drawSetColor(Scheme::sc_secondary3); + drawFilledRect(0,0,wide,tall); + + drawSetColor(Scheme::sc_black); + drawOutlinedRect(0,0,wide,tall); + + drawSetColor(Scheme::sc_primary2); + drawFilledRect(_nobPos[0],0,_nobPos[1],tall); + + drawSetColor(Scheme::sc_black); + drawOutlinedRect(_nobPos[0],0,_nobPos[1],tall); + } +} + +void Slider2::setRange(int min,int max) +{ + if(maxmax) + { + min=max; + } + + _range[0]=min; + _range[1]=max; +} + +void Slider2::getRange(int& min,int& max) +{ + min=_range[0]; + max=_range[1]; +} + +void Slider2::privateCursorMoved(int x,int y,Panel* panel) +{ + if(!_dragging) + { + return; + } + + getApp()->getCursorPos(x,y); + screenToLocal(x,y); + + int wide,tall; + getPaintSize(wide,tall); + + if(_vertical) + { + _nobPos[0]=_nobDragStartPos[0]+(y-_dragStartPos[1]); + _nobPos[1]=_nobDragStartPos[1]+(y-_dragStartPos[1]); + + if(_nobPos[1]>tall) + { + _nobPos[0]=tall-(_nobPos[1]-_nobPos[0]); + _nobPos[1]=tall; + } + + if(_nobPos[0]<0) + { + _nobPos[1]=_nobPos[1]-_nobPos[0]; + _nobPos[0]=0; + } + } + else + { + _nobPos[0]=_nobDragStartPos[0]+(x-_dragStartPos[0]); + _nobPos[1]=_nobDragStartPos[1]+(x-_dragStartPos[0]); + + if(_nobPos[1]>wide) + { + _nobPos[0]=wide-(_nobPos[1]-_nobPos[0]); + _nobPos[1]=wide; + } + + if(_nobPos[0]<0) + { + _nobPos[1]=_nobPos[1]-_nobPos[0]; + _nobPos[0]=0; + } + } + + recomputeValueFromNobPos(); + repaint(); + fireIntChangeSignal(); +} + +void Slider2::privateMousePressed(MouseCode code,Panel* panel) +{ + int x,y; + getApp()->getCursorPos(x,y); + screenToLocal(x,y); + + if(_vertical) + { + if((y>=_nobPos[0])&&(y<_nobPos[1])) + { + _dragging=true; + getApp()->setMouseCapture(this); + _nobDragStartPos[0]=_nobPos[0]; + _nobDragStartPos[1]=_nobPos[1]; + _dragStartPos[0]=x; + _dragStartPos[1]=y; + } + } + else + { + if((x>=_nobPos[0])&&(x<_nobPos[1])) + { + _dragging=true; + getApp()->setMouseCapture(this); + _nobDragStartPos[0]=_nobPos[0]; + _nobDragStartPos[1]=_nobPos[1]; + _dragStartPos[0]=x; + _dragStartPos[1]=y; + } + } + +} + +void Slider2::privateMouseReleased(MouseCode code,Panel* panel) +{ + _dragging=false; + getApp()->setMouseCapture(null); +} + +void Slider2::getNobPos(int& min, int& max) +{ + min=_nobPos[0]; + max=_nobPos[1]; +} + +void Slider2::setRangeWindow(int rangeWindow) +{ + _rangeWindow=rangeWindow; +} + +void Slider2::setRangeWindowEnabled(bool state) +{ + _rangeWindowEnabled=state; +} + +void Slider2::setButtonOffset(int buttonOffset) +{ + _buttonOffset=buttonOffset; +} diff --git a/game_shared/vgui_slider2.h b/game_shared/vgui_slider2.h new file mode 100644 index 0000000..4edb87f --- /dev/null +++ b/game_shared/vgui_slider2.h @@ -0,0 +1,67 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_SLIDER2_H +#define VGUI_SLIDER2_H +#ifdef _WIN32 +#pragma once +#endif + +#include +#include +#include + +namespace vgui +{ + +enum MouseCode; +class IntChangeSignal; + +class VGUIAPI Slider2 : public Panel +{ +private: + bool _vertical; + bool _dragging; + int _nobPos[2]; + int _nobDragStartPos[2]; + int _dragStartPos[2]; + Dar _intChangeSignalDar; + int _range[2]; + int _value; + int _rangeWindow; + bool _rangeWindowEnabled; + int _buttonOffset; +public: + Slider2(int x,int y,int wide,int tall,bool vertical); +public: + virtual void setValue(int value); + virtual int getValue(); + virtual bool isVertical(); + virtual void addIntChangeSignal(IntChangeSignal* s); + virtual void setRange(int min,int max); + virtual void getRange(int& min,int& max); + virtual void setRangeWindow(int rangeWindow); + virtual void setRangeWindowEnabled(bool state); + virtual void setSize(int wide,int tall); + virtual void getNobPos(int& min, int& max); + virtual bool hasFullRange(); + virtual void setButtonOffset(int buttonOffset); +private: + virtual void recomputeNobPosFromValue(); + virtual void recomputeValueFromNobPos(); +public: //bullshit public + virtual void privateCursorMoved(int x,int y,Panel* panel); + virtual void privateMousePressed(MouseCode code,Panel* panel); + virtual void privateMouseReleased(MouseCode code,Panel* panel); +protected: + virtual void fireIntChangeSignal(); + virtual void paintBackground(); +}; + +} + +#endif // VGUI_SLIDER2_H diff --git a/game_shared/voice_banmgr.cpp b/game_shared/voice_banmgr.cpp new file mode 100644 index 0000000..915e854 --- /dev/null +++ b/game_shared/voice_banmgr.cpp @@ -0,0 +1,197 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include +#include "voice_banmgr.h" + + +#define BANMGR_FILEVERSION 1 +char const *g_pBanMgrFilename = "voice_ban.dt"; + + + +// Hash a player ID to a byte. +unsigned char HashPlayerID(char const playerID[16]) +{ + unsigned char curHash = 0; + + for(int i=0; i < 16; i++) + curHash += (unsigned char)playerID[i]; + + return curHash; +} + + + +CVoiceBanMgr::CVoiceBanMgr() +{ + Clear(); +} + + +CVoiceBanMgr::~CVoiceBanMgr() +{ + Term(); +} + + +bool CVoiceBanMgr::Init(char const *pGameDir) +{ + Term(); + + char filename[512]; + _snprintf(filename, sizeof(filename), "%s/%s", pGameDir, g_pBanMgrFilename); + + // Load in the squelch file. + FILE *fp = fopen(filename, "rb"); + if(fp) + { + int version; + fread(&version, 1, sizeof(version), fp); + if(version == BANMGR_FILEVERSION) + { + fseek(fp, 0, SEEK_END); + int nIDs = (ftell(fp) - sizeof(version)) / 16; + fseek(fp, sizeof(version), SEEK_SET); + + for(int i=0; i < nIDs; i++) + { + char playerID[16]; + fread(playerID, 1, 16, fp); + AddBannedPlayer(playerID); + } + } + + fclose(fp); + } + + return true; +} + + +void CVoiceBanMgr::Term() +{ + // Free all the player structures. + for(int i=0; i < 256; i++) + { + BannedPlayer *pListHead = &m_PlayerHash[i]; + BannedPlayer *pNext; + for(BannedPlayer *pCur=pListHead->m_pNext; pCur != pListHead; pCur=pNext) + { + pNext = pCur->m_pNext; + delete pCur; + } + } + + Clear(); +} + + +void CVoiceBanMgr::SaveState(char const *pGameDir) +{ + // Save the file out. + char filename[512]; + _snprintf(filename, sizeof(filename), "%s/%s", pGameDir, g_pBanMgrFilename); + + FILE *fp = fopen(filename, "wb"); + if(fp) + { + int version = BANMGR_FILEVERSION; + fwrite(&version, 1, sizeof(version), fp); + + for(int i=0; i < 256; i++) + { + BannedPlayer *pListHead = &m_PlayerHash[i]; + for(BannedPlayer *pCur=pListHead->m_pNext; pCur != pListHead; pCur=pCur->m_pNext) + { + fwrite(pCur->m_PlayerID, 1, 16, fp); + } + } + + fclose(fp); + } +} + + +bool CVoiceBanMgr::GetPlayerBan(char const playerID[16]) +{ + return !!InternalFindPlayerSquelch(playerID); +} + + +void CVoiceBanMgr::SetPlayerBan(char const playerID[16], bool bSquelch) +{ + if(bSquelch) + { + // Is this guy already squelched? + if(GetPlayerBan(playerID)) + return; + + AddBannedPlayer(playerID); + } + else + { + BannedPlayer *pPlayer = InternalFindPlayerSquelch(playerID); + if(pPlayer) + { + pPlayer->m_pPrev->m_pNext = pPlayer->m_pNext; + pPlayer->m_pNext->m_pPrev = pPlayer->m_pPrev; + delete pPlayer; + } + } +} + + +void CVoiceBanMgr::ForEachBannedPlayer(void (*callback)(char id[16])) +{ + for(int i=0; i < 256; i++) + { + for(BannedPlayer *pCur=m_PlayerHash[i].m_pNext; pCur != &m_PlayerHash[i]; pCur=pCur->m_pNext) + { + callback(pCur->m_PlayerID); + } + } +} + + +void CVoiceBanMgr::Clear() +{ + // Tie off the hash table entries. + for(int i=0; i < 256; i++) + m_PlayerHash[i].m_pNext = m_PlayerHash[i].m_pPrev = &m_PlayerHash[i]; +} + + +CVoiceBanMgr::BannedPlayer* CVoiceBanMgr::InternalFindPlayerSquelch(char const playerID[16]) +{ + int index = HashPlayerID(playerID); + BannedPlayer *pListHead = &m_PlayerHash[index]; + for(BannedPlayer *pCur=pListHead->m_pNext; pCur != pListHead; pCur=pCur->m_pNext) + { + if(memcmp(playerID, pCur->m_PlayerID, 16) == 0) + return pCur; + } + + return NULL; +} + + +CVoiceBanMgr::BannedPlayer* CVoiceBanMgr::AddBannedPlayer(char const playerID[16]) +{ + BannedPlayer *pNew = new BannedPlayer; + if(!pNew) + return NULL; + + int index = HashPlayerID(playerID); + memcpy(pNew->m_PlayerID, playerID, 16); + pNew->m_pNext = &m_PlayerHash[index]; + pNew->m_pPrev = m_PlayerHash[index].m_pPrev; + pNew->m_pPrev->m_pNext = pNew->m_pNext->m_pPrev = pNew; + return pNew; +} + diff --git a/game_shared/voice_banmgr.h b/game_shared/voice_banmgr.h new file mode 100644 index 0000000..610ad6c --- /dev/null +++ b/game_shared/voice_banmgr.h @@ -0,0 +1,57 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VOICE_BANMGR_H +#define VOICE_BANMGR_H +#ifdef _WIN32 +#pragma once +#endif + + +// This class manages the (persistent) list of squelched players. +class CVoiceBanMgr +{ +public: + + CVoiceBanMgr(); + ~CVoiceBanMgr(); + + // Init loads the list of squelched players from disk. + bool Init(char const *pGameDir); + void Term(); + + // Saves the state into voice_squelch.dt. + void SaveState(char const *pGameDir); + + bool GetPlayerBan(char const playerID[16]); + void SetPlayerBan(char const playerID[16], bool bSquelch); + + // Call your callback for each banned player. + void ForEachBannedPlayer(void (*callback)(char id[16])); + + +protected: + + class BannedPlayer + { + public: + char m_PlayerID[16]; + BannedPlayer *m_pPrev, *m_pNext; + }; + + void Clear(); + BannedPlayer* InternalFindPlayerSquelch(char const playerID[16]); + BannedPlayer* AddBannedPlayer(char const playerID[16]); + + +protected: + + BannedPlayer m_PlayerHash[256]; +}; + + +#endif // VOICE_BANMGR_H diff --git a/game_shared/voice_common.h b/game_shared/voice_common.h new file mode 100644 index 0000000..5517d8e --- /dev/null +++ b/game_shared/voice_common.h @@ -0,0 +1,24 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VOICE_COMMON_H +#define VOICE_COMMON_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "bitvec.h" + + +#define VOICE_MAX_PLAYERS 32 // (todo: this should just be set to MAX_CLIENTS). +#define VOICE_MAX_PLAYERS_DW ((VOICE_MAX_PLAYERS / 32) + !!(VOICE_MAX_PLAYERS & 31)) + +typedef CBitVec CPlayerBitVec; + + +#endif // VOICE_COMMON_H diff --git a/game_shared/voice_gamemgr.cpp b/game_shared/voice_gamemgr.cpp new file mode 100644 index 0000000..b73fdd7 --- /dev/null +++ b/game_shared/voice_gamemgr.cpp @@ -0,0 +1,274 @@ +//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "archtypes.h" // DAL +#include "voice_gamemgr.h" +#include +#include +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" + + + +#define UPDATE_INTERVAL 0.3 + + +// These are stored off as CVoiceGameMgr is created and deleted. +CPlayerBitVec g_PlayerModEnable; // Set to 1 for each player if the player wants to use voice in this mod. + // (If it's zero, then the server reports that the game rules are saying the + // player can't hear anyone). + +CPlayerBitVec g_BanMasks[VOICE_MAX_PLAYERS]; // Tells which players don't want to hear each other. + // These are indexed as clients and each bit represents a client + // (so player entity is bit+1). + +CPlayerBitVec g_SentGameRulesMasks[VOICE_MAX_PLAYERS]; // These store the masks we last sent to each client so we can determine if +CPlayerBitVec g_SentBanMasks[VOICE_MAX_PLAYERS]; // we need to resend them. +CPlayerBitVec g_bWantModEnable; + +cvar_t voice_serverdebug = {"voice_serverdebug", "0"}; + +// Set game rules to allow all clients to talk to each other. +// Muted players still can't talk to each other. +cvar_t sv_alltalk = {"sv_alltalk", "0", FCVAR_SERVER}; + +// ------------------------------------------------------------------------ // +// Static helpers. +// ------------------------------------------------------------------------ // + +// Find a player with a case-insensitive name search. +static CBasePlayer* FindPlayerByName(const char *pTestName) +{ + for(int i=1; i <= gpGlobals->maxClients; i++) + { + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex(i); + if(pEdict) + { + CBaseEntity *pEnt = CBaseEntity::Instance(pEdict); + if(pEnt && pEnt->IsPlayer()) + { + const char *pNetName = STRING(pEnt->pev->netname); + if(stricmp(pNetName, pTestName) == 0) + { + return (CBasePlayer*)pEnt; + } + } + } + } + + return NULL; +} + +static void VoiceServerDebug( char const *pFmt, ... ) +{ + char msg[4096]; + va_list marker; + + if( !voice_serverdebug.value ) + return; + + va_start( marker, pFmt ); + _vsnprintf( msg, sizeof(msg), pFmt, marker ); + va_end( marker ); + + ALERT( at_console, "%s", msg ); +} + + + +// ------------------------------------------------------------------------ // +// CVoiceGameMgr. +// ------------------------------------------------------------------------ // + +CVoiceGameMgr::CVoiceGameMgr() +{ + m_UpdateInterval = 0; + m_nMaxPlayers = 0; +} + + +CVoiceGameMgr::~CVoiceGameMgr() +{ +} + + +bool CVoiceGameMgr::Init( + IVoiceGameMgrHelper *pHelper, + int maxClients) +{ + m_pHelper = pHelper; + m_nMaxPlayers = VOICE_MAX_PLAYERS < maxClients ? VOICE_MAX_PLAYERS : maxClients; + g_engfuncs.pfnPrecacheModel("sprites/voiceicon.spr"); + + m_msgPlayerVoiceMask = REG_USER_MSG( "VoiceMask", VOICE_MAX_PLAYERS_DW*4 * 2 ); + m_msgRequestState = REG_USER_MSG( "ReqState", 0 ); + + // register voice_serverdebug if it hasn't been registered already + if ( !CVAR_GET_POINTER( "voice_serverdebug" ) ) + CVAR_REGISTER( &voice_serverdebug ); + + if( !CVAR_GET_POINTER( "sv_alltalk" ) ) + CVAR_REGISTER( &sv_alltalk ); + + return true; +} + + +void CVoiceGameMgr::SetHelper(IVoiceGameMgrHelper *pHelper) +{ + m_pHelper = pHelper; +} + + +void CVoiceGameMgr::Update(double frametime) +{ + // Only update periodically. + m_UpdateInterval += frametime; + if(m_UpdateInterval < UPDATE_INTERVAL) + return; + + UpdateMasks(); +} + + +void CVoiceGameMgr::ClientConnected(edict_t *pEdict) +{ + int index = ENTINDEX(pEdict) - 1; + + // Clear out everything we use for deltas on this guy. + g_bWantModEnable[index] = true; + g_SentGameRulesMasks[index].Init(0); + g_SentBanMasks[index].Init(0); +} + +// Called to determine if the Receiver has muted (blocked) the Sender +// Returns true if the receiver has blocked the sender +bool CVoiceGameMgr::PlayerHasBlockedPlayer(CBasePlayer *pReceiver, CBasePlayer *pSender) +{ + int iReceiverIndex, iSenderIndex; + + if ( !pReceiver || !pSender ) + return false; + + iReceiverIndex = pReceiver->entindex() - 1; + iSenderIndex = pSender->entindex() - 1; + + if ( iReceiverIndex < 0 || iReceiverIndex >= m_nMaxPlayers || iSenderIndex < 0 || iSenderIndex >= m_nMaxPlayers ) + return false; + + return ( g_BanMasks[iReceiverIndex][iSenderIndex] ? true : false ); +} + +bool CVoiceGameMgr::ClientCommand(CBasePlayer *pPlayer, const char *cmd) +{ + int playerClientIndex = pPlayer->entindex() - 1; + if(playerClientIndex < 0 || playerClientIndex >= m_nMaxPlayers) + { + VoiceServerDebug( "CVoiceGameMgr::ClientCommand: cmd %s from invalid client (%d)\n", cmd, playerClientIndex ); + return true; + } + + bool bBan = stricmp(cmd, "vban") == 0; + if(bBan && CMD_ARGC() >= 2) + { + for(int i=1; i < CMD_ARGC(); i++) + { + uint32 mask = 0; + sscanf(CMD_ARGV(i), "%x", &mask); + + if(i <= VOICE_MAX_PLAYERS_DW) + { + VoiceServerDebug( "CVoiceGameMgr::ClientCommand: vban (0x%x) from %d\n", mask, playerClientIndex ); + g_BanMasks[playerClientIndex].SetDWord(i-1, mask); + } + else + { + VoiceServerDebug( "CVoiceGameMgr::ClientCommand: invalid index (%d)\n", i ); + } + } + + // Force it to update the masks now. + //UpdateMasks(); + return true; + } + else if(stricmp(cmd, "VModEnable") == 0 && CMD_ARGC() >= 2) + { + VoiceServerDebug( "CVoiceGameMgr::ClientCommand: VModEnable (%d)\n", !!atoi(CMD_ARGV(1)) ); + g_PlayerModEnable[playerClientIndex] = !!atoi(CMD_ARGV(1)); + g_bWantModEnable[playerClientIndex] = false; + //UpdateMasks(); + return true; + } + else + { + return false; + } +} + + +void CVoiceGameMgr::UpdateMasks() +{ + m_UpdateInterval = 0; + + bool bAllTalk = !!(sv_alltalk.value); + + for(int iClient=0; iClient < m_nMaxPlayers; iClient++) + { + CBaseEntity *pEnt = UTIL_PlayerByIndex(iClient+1); + if(!pEnt || !pEnt->IsPlayer()) + continue; + + // Request the state of their "VModEnable" cvar. + if(g_bWantModEnable[iClient]) + { + MESSAGE_BEGIN(MSG_ONE, m_msgRequestState, NULL, pEnt->pev); + MESSAGE_END(); + } + + CBasePlayer *pPlayer = (CBasePlayer*)pEnt; + + CPlayerBitVec gameRulesMask; + if( g_PlayerModEnable[iClient] ) + { + // Build a mask of who they can hear based on the game rules. + for(int iOtherClient=0; iOtherClient < m_nMaxPlayers; iOtherClient++) + { + CBaseEntity *pEnt = UTIL_PlayerByIndex(iOtherClient+1); + if(pEnt && (bAllTalk || m_pHelper->CanPlayerHearPlayer(pPlayer, (CBasePlayer*)pEnt)) ) + { + gameRulesMask[iOtherClient] = true; + } + } + } + + // If this is different from what the client has, send an update. + if(gameRulesMask != g_SentGameRulesMasks[iClient] || + g_BanMasks[iClient] != g_SentBanMasks[iClient]) + { + g_SentGameRulesMasks[iClient] = gameRulesMask; + g_SentBanMasks[iClient] = g_BanMasks[iClient]; + + MESSAGE_BEGIN(MSG_ONE, m_msgPlayerVoiceMask, NULL, pPlayer->pev); + int dw; + for(dw=0; dw < VOICE_MAX_PLAYERS_DW; dw++) + { + WRITE_LONG(gameRulesMask.GetDWord(dw)); + WRITE_LONG(g_BanMasks[iClient].GetDWord(dw)); + } + MESSAGE_END(); + } + + // Tell the engine. + for(int iOtherClient=0; iOtherClient < m_nMaxPlayers; iOtherClient++) + { + bool bCanHear = gameRulesMask[iOtherClient] && !g_BanMasks[iClient][iOtherClient]; + g_engfuncs.pfnVoice_SetClientListening(iClient+1, iOtherClient+1, bCanHear); + } + } +} diff --git a/game_shared/voice_gamemgr.h b/game_shared/voice_gamemgr.h new file mode 100644 index 0000000..906444d --- /dev/null +++ b/game_shared/voice_gamemgr.h @@ -0,0 +1,82 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VOICE_GAMEMGR_H +#define VOICE_GAMEMGR_H + +#ifdef _WIN32 +#pragma once +#endif + + +#include "voice_common.h" + + +class CGameRules; +class CBasePlayer; + + +class IVoiceGameMgrHelper +{ +public: + virtual ~IVoiceGameMgrHelper() {} + + // Called each frame to determine which players are allowed to hear each other. This overrides + // whatever squelch settings players have. + virtual bool CanPlayerHearPlayer(CBasePlayer *pListener, CBasePlayer *pTalker) = 0; +}; + + +// CVoiceGameMgr manages which clients can hear which other clients. +class CVoiceGameMgr +{ +public: + CVoiceGameMgr(); + virtual ~CVoiceGameMgr(); + + bool Init( + IVoiceGameMgrHelper *m_pHelper, + int maxClients + ); + + void SetHelper(IVoiceGameMgrHelper *pHelper); + + // Updates which players can hear which other players. + // If gameplay mode is DM, then only players within the PVS can hear each other. + // If gameplay mode is teamplay, then only players on the same team can hear each other. + // Player masks are always applied. + void Update(double frametime); + + // Called when a new client connects (unsquelches its entity for everyone). + void ClientConnected(struct edict_s *pEdict); + + // Called on ClientCommand. Checks for the squelch and unsquelch commands. + // Returns true if it handled the command. + bool ClientCommand(CBasePlayer *pPlayer, const char *cmd); + + // Called to determine if the Receiver has muted (blocked) the Sender + // Returns true if the receiver has blocked the sender + bool PlayerHasBlockedPlayer(CBasePlayer *pReceiver, CBasePlayer *pSender); + + +private: + + // Force it to update the client masks. + void UpdateMasks(); + + +private: + int m_msgPlayerVoiceMask; + int m_msgRequestState; + + IVoiceGameMgrHelper *m_pHelper; + int m_nMaxPlayers; + double m_UpdateInterval; // How long since the last update. +}; + + +#endif // VOICE_GAMEMGR_H diff --git a/game_shared/voice_status.cpp b/game_shared/voice_status.cpp new file mode 100644 index 0000000..c538469 --- /dev/null +++ b/game_shared/voice_status.cpp @@ -0,0 +1,467 @@ + +#include +#include + +#include "wrect.h" +#include "cl_dll.h" +#include "cl_util.h" +#include "cl_entity.h" +#include "const.h" + +#include "parsemsg.h" // BEGIN_READ(), ... + +#include "voice_status.h" + +#pragma warning( disable : 4800 ) // disable forcing int to bool performance warning + + +static CVoiceStatus *g_pInternalVoiceStatus = NULL; + +// ---------------------------------------------------------------------- // +// The voice manager for the client. +// ---------------------------------------------------------------------- // +CVoiceStatus g_VoiceStatus; + +CVoiceStatus* GetClientVoice() +{ + return &g_VoiceStatus; +} + + + + +int __MsgFunc_VoiceMask(const char *pszName, int iSize, void *pbuf) +{ + if(g_pInternalVoiceStatus) + g_pInternalVoiceStatus->HandleVoiceMaskMsg(iSize, pbuf); + + return 1; +} + +int __MsgFunc_ReqState(const char *pszName, int iSize, void *pbuf) +{ + if(g_pInternalVoiceStatus) + g_pInternalVoiceStatus->HandleReqStateMsg(iSize, pbuf); + + return 1; +} + + + + +int g_BannedPlayerPrintCount; +void ForEachBannedPlayer(char id[16]) +{ + char str[256]; + sprintf(str, "Ban %d: %2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x\n", + g_BannedPlayerPrintCount++, + id[0], id[1], id[2], id[3], + id[4], id[5], id[6], id[7], + id[8], id[9], id[10], id[11], + id[12], id[13], id[14], id[15] + ); +#ifdef _WIN32 + strupr(str); +#endif + gEngfuncs.pfnConsolePrint(str); +} + + +void ShowBannedCallback() +{ + if(g_pInternalVoiceStatus) + { + g_BannedPlayerPrintCount = 0; + gEngfuncs.pfnConsolePrint("------- BANNED PLAYERS -------\n"); + g_pInternalVoiceStatus->GetBanMgr()->ForEachBannedPlayer(ForEachBannedPlayer); + gEngfuncs.pfnConsolePrint("------------------------------\n"); + } +} + + + + + + + + + +CVoiceStatus::CVoiceStatus() +{ + m_bBanMgrInitialized = false; + m_LastUpdateServerState = 0; + + m_bTalking = m_bServerAcked = false; + + m_bServerModEnable = -1; + + m_pchGameDir = NULL; +} + + +CVoiceStatus::~CVoiceStatus() +{ + + g_pInternalVoiceStatus = NULL; + + if(m_pchGameDir) + { + if(m_bBanMgrInitialized) + { + m_BanMgr.SaveState(m_pchGameDir); + } + + free(m_pchGameDir); + } + +} + + + +void CVoiceStatus::Init( IVoiceStatusHelper *pHelper) +{ + // Setup the voice_modenable cvar. + gEngfuncs.pfnRegisterVariable("voice_modenable", "1", FCVAR_ARCHIVE); + + gEngfuncs.pfnRegisterVariable("voice_clientdebug", "0", 0); + + gEngfuncs.pfnAddCommand("voice_showbanned", ShowBannedCallback); + + // Cache the game directory for use when we shut down + const char *pchGameDirT = gEngfuncs.pfnGetGameDirectory(); + m_pchGameDir = (char *)malloc(strlen(pchGameDirT) + 1); + + if(m_pchGameDir) + { + strcpy(m_pchGameDir, pchGameDirT); + } + + if(m_pchGameDir) + { + m_BanMgr.Init(m_pchGameDir); + m_bBanMgrInitialized = true; + } + + assert(!g_pInternalVoiceStatus); + g_pInternalVoiceStatus = this; + + m_bInSquelchMode = false; + + m_pHelper = pHelper; + + HOOK_MESSAGE(VoiceMask); + HOOK_MESSAGE(ReqState); + + + GetClientVoiceHud()->Init(pHelper,this); + + + +} + + +void CVoiceStatus::Frame(double frametime) +{ + // check server banned players once per second + if(gEngfuncs.GetClientTime() - m_LastUpdateServerState > 1) + { + UpdateServerState(false); + } + + + +} + +void CVoiceStatus::StartSquelchMode() +{ + if(m_bInSquelchMode) + return; + + m_bInSquelchMode = true; +} + +void CVoiceStatus::StopSquelchMode() +{ + m_bInSquelchMode = false; +} + +bool CVoiceStatus::IsInSquelchMode() +{ + return m_bInSquelchMode; +} + +void CVoiceStatus::UpdateServerState(bool bForce) +{ + // Can't do anything when we're not in a level. + char const *pLevelName = gEngfuncs.pfnGetLevelName(); + if( pLevelName[0] == 0 ) + { + if( gEngfuncs.pfnGetCvarFloat("voice_clientdebug") ) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::UpdateServerState: pLevelName[0]==0\n" ); + } + + return; + } + + int bCVarModEnable = !!gEngfuncs.pfnGetCvarFloat("voice_modenable"); + if(bForce || m_bServerModEnable != bCVarModEnable) + { + m_bServerModEnable = bCVarModEnable; + + char str[256]; + _snprintf(str, sizeof(str), "VModEnable %d", m_bServerModEnable); + ServerCmd(str); + + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char msg[256]; + sprintf(msg, "CVoiceStatus::UpdateServerState: Sending '%s'\n", str); + gEngfuncs.pfnConsolePrint(msg); + } + } + + char str[2048]; + sprintf(str, "vban"); + bool bChange = false; + + for(uint32 dw=0; dw < VOICE_MAX_PLAYERS_DW; dw++) + { + uint32 serverBanMask = 0; + uint32 banMask = 0; + for(uint32 i=0; i < 32; i++) + { + char playerID[16]; + if(!gEngfuncs.GetPlayerUniqueID(i+1, playerID)) + continue; + + if(m_BanMgr.GetPlayerBan(playerID)) + banMask |= 1 << i; + + if(m_ServerBannedPlayers[dw*32 + i]) + serverBanMask |= 1 << i; + } + + if(serverBanMask != banMask) + bChange = true; + + // Ok, the server needs to be updated. + char numStr[512]; + sprintf(numStr, " %x", banMask); + strcat(str, numStr); + } + + if(bChange || bForce) + { + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char msg[256]; + sprintf(msg, "CVoiceStatus::UpdateServerState: Sending '%s'\n", str); + gEngfuncs.pfnConsolePrint(msg); + } + + gEngfuncs.pfnServerCmdUnreliable(str); // Tell the server.. + } + else + { + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::UpdateServerState: no change\n" ); + } + } + + m_LastUpdateServerState = gEngfuncs.GetClientTime(); +} + + + +int CVoiceStatus::GetSpeakerStatus(int iPlayer) +{ + bool bTalking = static_cast(m_VoicePlayers[iPlayer]); + + + char playerID[16]; + qboolean id = gEngfuncs.GetPlayerUniqueID( iPlayer+1, playerID ); + if(!id) + return VOICE_NEVERSPOKEN; + + bool bBanned = m_BanMgr.GetPlayerBan( playerID ); + bool bNeverSpoken = !m_VoiceEnabledPlayers[iPlayer]; + + + if(bBanned) + { + return VOICE_BANNED; + } + else if(bNeverSpoken) + { + return VOICE_NEVERSPOKEN; + } + else if(bTalking) + { + return VOICE_TALKING; + } + else + return VOICE_NOTTALKING; + +} + + + +void CVoiceStatus::HandleVoiceMaskMsg(int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + uint32 dw; + for(dw=0; dw < VOICE_MAX_PLAYERS_DW; dw++) + { + m_AudiblePlayers.SetDWord(dw, (uint32)READ_LONG()); + m_ServerBannedPlayers.SetDWord(dw, (uint32)READ_LONG()); + + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char str[256]; + gEngfuncs.pfnConsolePrint("CVoiceStatus::HandleVoiceMaskMsg\n"); + + sprintf(str, " - m_AudiblePlayers[%d] = %lu\n", dw, m_AudiblePlayers.GetDWord(dw)); + gEngfuncs.pfnConsolePrint(str); + + sprintf(str, " - m_ServerBannedPlayers[%d] = %lu\n", dw, m_ServerBannedPlayers.GetDWord(dw)); + gEngfuncs.pfnConsolePrint(str); + } + } + + m_bServerModEnable = READ_BYTE(); +} + +void CVoiceStatus::HandleReqStateMsg(int iSize, void *pbuf) +{ + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint("CVoiceStatus::HandleReqStateMsg\n"); + } + + UpdateServerState(true); +} + + +void CVoiceStatus::UpdateSpeakerStatus(int entindex, bool bTalking) +{ + + const char *levelName = gEngfuncs.pfnGetLevelName(); + if (levelName && levelName[0]) + { + + if( gEngfuncs.pfnGetCvarFloat("voice_clientdebug") ) + { + char msg[256]; + _snprintf( msg, sizeof(msg), "CVoiceStatus::UpdateSpeakerStatus: ent %d talking = %d\n", entindex, bTalking ); + gEngfuncs.pfnConsolePrint( msg ); + } + + // Is it the local player talking? + if( entindex == -1 ) + { + m_bTalking = bTalking; + if( bTalking ) + { + // Enable voice for them automatically if they try to talk. + gEngfuncs.pfnClientCmd( "voice_modenable 1" ); + } + if ( !gEngfuncs.GetLocalPlayer() ) + { + return; + } + + int entindex = gEngfuncs.GetLocalPlayer()->index; + GetClientVoiceHud()->UpdateSpeakerStatus(-2,bTalking); + + m_VoicePlayers[entindex-1] = m_bTalking; + m_VoiceEnabledPlayers[entindex-1]= true; + } + else if( entindex == -2 ) + { + m_bServerAcked = bTalking; + } + else if(entindex >= 0 && entindex <= VOICE_MAX_PLAYERS) + { + int iClient = entindex - 1; + if(iClient < 0) + return; + + GetClientVoiceHud()->UpdateSpeakerStatus(entindex,bTalking); + + if(bTalking) + { + m_VoicePlayers[iClient] = true; + m_VoiceEnabledPlayers[iClient] = true; + } + else + { + m_VoicePlayers[iClient] = false; + } + } + + GetClientVoiceHud()->RepositionLabels(); + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: returns true if the target client has been banned +// Input : playerID - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CVoiceStatus::IsPlayerBlocked(int iPlayer) +{ + char playerID[16]; + if (!gEngfuncs.GetPlayerUniqueID(iPlayer, playerID)) + return false; + + return m_BanMgr.GetPlayerBan(playerID); +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the player can't hear the other client due to game rules (eg. the other team) +// Input : playerID - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CVoiceStatus::IsPlayerAudible(int iPlayer) +{ + return !!m_AudiblePlayers[iPlayer-1]; +} + +//----------------------------------------------------------------------------- +// Purpose: blocks/unblocks the target client from being heard +// Input : playerID - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +void CVoiceStatus::SetPlayerBlockedState(int iPlayer, bool blocked) +{ + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::SetPlayerBlockedState part 1\n" ); + } + + char playerID[16]; + if (!gEngfuncs.GetPlayerUniqueID(iPlayer, playerID)) + return; + + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::SetPlayerBlockedState part 2\n" ); + } + + // Squelch or (try to) unsquelch this player. + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char str[256]; + sprintf(str, "CVoiceStatus::SetPlayerBlockedState: setting player %d ban to %d\n", iPlayer, !m_BanMgr.GetPlayerBan(playerID)); + gEngfuncs.pfnConsolePrint(str); + } + + m_BanMgr.SetPlayerBan( playerID, blocked ); + UpdateServerState(false); +} + diff --git a/game_shared/voice_status.h b/game_shared/voice_status.h new file mode 100644 index 0000000..c6091f3 --- /dev/null +++ b/game_shared/voice_status.h @@ -0,0 +1,205 @@ +//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VOICE_STATUS_H +#define VOICE_STATUS_H +#pragma once + +#include "voice_common.h" +#include "voice_banmgr.h" + + + +// This is provided by each mod to access data that may not be the same across mods. +class IVoiceStatusHelper +{ +public: + virtual ~IVoiceStatusHelper() {} + + // Get RGB color for voice status text about this player. + virtual void GetPlayerTextColor(int entindex, int color[3]) = 0; + + // Return the height above the bottom that the voice ack icons should be drawn at. + virtual int GetAckIconHeight() = 0; + + // Return true if the voice manager is allowed to show speaker labels + // (mods usually return false when the scoreboard is up). + virtual bool CanShowSpeakerLabels() = 0; + + // return a pre-translated string for the player's location. Defaults to empty string for games without locations + virtual const char * GetPlayerLocation(int entindex) { return ""; } +}; + + +class IVoiceStatus +{ +public: + + // returns the state of this player using the enum above + virtual int GetSpeakerStatus(int iPlayer) = 0; + virtual bool IsTalking() = 0; + virtual bool ServerAcked() = 0; + +}; + + + +class IVoiceHud +{ +public: + + virtual int Init(IVoiceStatusHelper *pHelper, IVoiceStatus *pStatus)=0; + + // ackPosition is the bottom position of where CVoiceStatus will draw the voice acknowledgement labels. + virtual int VidInit() = 0; + + // Call from the HUD_CreateEntities function so it can add sprites above player heads. + virtual void CreateEntities() = 0; + + // Sets a player's location (can be a #-prefixed string for localization). + virtual void UpdateLocation(int entindex, const char *location) = 0; + + virtual void UpdateSpeakerStatus(int entindex, bool bTalking) = 0; + + virtual void RepositionLabels() = 0; + +}; + + +class CVoiceStatus: public IVoiceStatusHelper, public IVoiceStatus +{ + +public: + + CVoiceStatus(); + ~CVoiceStatus(); + + void Init(IVoiceStatusHelper *pHelper); + + // Called when a player starts or stops talking. + // entindex is -1 to represent the local client talking (before the data comes back from the server). + // When the server acknowledges that the local client is talking, then entindex will be gEngfuncs.GetLocalPlayer(). + // entindex is -2 to represent the local client's voice being acked by the server. + void UpdateSpeakerStatus(int entindex, bool bTalking); + + // returns the state of this player using the enum above + int GetSpeakerStatus(int iPlayer); + + + // Called when the server registers a change to who this client can hear. + void HandleVoiceMaskMsg(int iSize, void *pbuf); + + // The server sends this message initially to tell the client to send their state. + void HandleReqStateMsg(int iSize, void *pbuf); + + + void Frame(double frametime); + + // When you enter squelch mode, pass in + void StartSquelchMode(); + void StopSquelchMode(); + bool IsInSquelchMode(); + + // returns true if the target client has been banned + // playerIndex is of range 1..maxplayers + bool IsPlayerBlocked(int iPlayerIndex); + + // returns false if the player can't hear the other client due to game rules (eg. the other team) + bool IsPlayerAudible(int iPlayerIndex); + + // blocks the target client from being heard + void SetPlayerBlockedState(int iPlayerIndex, bool blocked); + + void UpdateServerState(bool bForce); + + virtual bool CanShowSpeakerLabels() + { + return m_pHelper->CanShowSpeakerLabels(); + } + + virtual void GetPlayerTextColor(int entindex, int color[3]) + { + m_pHelper->GetPlayerTextColor(entindex,color); + } + + virtual int GetAckIconHeight() + { + return m_pHelper->GetAckIconHeight(); + } + + bool IsTalking() + { + return m_bTalking; + } + + bool ServerAcked() + { + return m_bServerAcked; + } + + CVoiceBanMgr *GetBanMgr() { return &m_BanMgr; } + + + + enum + { + VOICE_TALKING=1, // start from one because ImageList's don't use pos 0 + VOICE_BANNED, + VOICE_NEVERSPOKEN, + VOICE_NOTTALKING, + }; // various voice states + + +private: + float m_LastUpdateServerState; // Last time we called this function. + int m_bServerModEnable; // What we've sent to the server about our "voice_modenable" cvar. + + CPlayerBitVec m_VoicePlayers; // Who is currently talking. Indexed by client index. + + // This is the gamerules-defined list of players that you can hear. It is based on what teams people are on + // and is totally separate from the ban list. Indexed by client index. + CPlayerBitVec m_AudiblePlayers; + + // Players who have spoken at least once in the game so far + CPlayerBitVec m_VoiceEnabledPlayers; + + // This is who the server THINKS we have banned (it can become incorrect when a new player arrives on the server). + // It is checked periodically, and the server is told to squelch or unsquelch the appropriate players. + CPlayerBitVec m_ServerBannedPlayers; + + + IVoiceStatusHelper *m_pHelper; // Each mod provides an implementation of this. + + // Squelch mode stuff. + bool m_bInSquelchMode; + + + bool m_bTalking; // Set to true when the client thinks it's talking. + bool m_bServerAcked; // Set to true when the server knows the client is talking. + + + CVoiceBanMgr m_BanMgr; // Tracks which users we have squelched and don't want to hear. + + bool m_bBanMgrInitialized; + + // Cache the game directory for use when we shut down + char * m_pchGameDir; + + + +}; + +CVoiceStatus* GetClientVoice(); + +// Get the (global) voice manager. +IVoiceHud* GetClientVoiceHud(); + + + + + +#endif // VOICE_STATUS_H \ No newline at end of file diff --git a/game_shared/voice_status_hud.cpp b/game_shared/voice_status_hud.cpp new file mode 100644 index 0000000..0701b37 --- /dev/null +++ b/game_shared/voice_status_hud.cpp @@ -0,0 +1,412 @@ +//========= Copyright 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// There are hud.h's coming out of the woodwork so this ensures that we get the right one. +#if defined(THREEWAVE) || defined(DMC_BUILD) + #include "../dmc/cl_dll/hud.h" +#elif defined(CZERO) + #include "../czero/cl_dll/hud.h" +#elif defined(CSTRIKE) + #include "../cstrike/cl_dll/hud.h" +#elif defined(DOD) + #include "../dod/cl_dll/hud.h" +#elif defined(BLUESHIFT) + #include "../blueshift/cl_dll/hud.h" +#else + #include "../cl_dll/hud.h" +#endif + +#include "cl_util.h" +#include +#include +#include +#include "parsemsg.h" +#include "demo.h" +#include "demo_api.h" +#include "r_efx.h" +#include "entity_types.h" +#include "shared_util.h" + +#include "voice_status.h" +#include "voice_status_hud.h" + +using namespace vgui; + +#include +#include + +#include + +extern int cam_thirdperson; + + +#define VOICE_MODEL_INTERVAL 0.3 +//#define SCOREBOARD_BLINK_FREQUENCY 0.3 // How often to blink the scoreboard icons. +#define SQUELCHOSCILLATE_PER_SECOND 2.0f + + + +// ---------------------------------------------------------------------- // +// The voice manager for the client. +// ---------------------------------------------------------------------- // +CVoiceStatusHud g_VoiceStatusHud; + +IVoiceHud* GetClientVoiceHud() +{ + return &g_VoiceStatusHud; +} + + + +// ---------------------------------------------------------------------- // +// CVoiceLabel. +// ---------------------------------------------------------------------- // +void CVoiceLabel::SetLocation( const char *location ) +{ + if ( !location || !*location ) + { + if ( m_locationString ) + { + delete[] m_locationString; + m_locationString = NULL; + RebuildLabelText(); + } + return; + } + + const wchar_t *newLocation = vgui::localize()->Find( location ); + if ( newLocation ) + { + // localized version + if ( m_locationString && wcscmp( newLocation, m_locationString ) ) + { + delete[] m_locationString; + m_locationString = NULL; + } + + if ( !m_locationString ) + { + m_locationString = CloneWString( newLocation ); + RebuildLabelText(); + } + } + else + { + // just convert the ANSI version to Unicode + wchar_t *tmpBuf = new wchar_t[ strlen(location) + 1 ]; + localize()->ConvertANSIToUnicode( location, tmpBuf, sizeof(tmpBuf) ); + + if ( m_locationString && wcscmp( tmpBuf, m_locationString ) ) + { + delete[] m_locationString; + m_locationString = NULL; + } + + if ( !m_locationString ) + { + m_locationString = CloneWString( tmpBuf ); + RebuildLabelText(); + } + + delete[] tmpBuf; + } +} + +void CVoiceLabel::SetPlayerName( const char *name ) +{ + if ( m_playerName ) + { + delete[] m_playerName; + m_playerName = NULL; + } + + if ( name ) + { + m_playerName = CloneString( name ); + } + + RebuildLabelText(); +} + +void CVoiceLabel::RebuildLabelText() +{ + const int BufLen = 512; + wchar_t buf[BufLen] = L""; + if ( m_playerName ) + { + wchar_t wsPlayer[BufLen] = L""; + + localize()->ConvertANSIToUnicode(m_playerName, wsPlayer, sizeof(wsPlayer)); + + const wchar_t *formatStr = L"%ls "; + if ( m_locationString ) + { + formatStr = localize()->Find( "#Voice_Location" ); + if ( !formatStr ) + formatStr = L"%ls/%ls "; + } + _snwprintf( buf, BufLen, formatStr, wsPlayer, m_locationString ); + } + m_pLabel->SetText( buf ); + //gEngfuncs.Con_DPrintf( "CVoiceLabel::RebuildLabelText() - [%ls]\n", buf ); +} + + + +// ---------------------------------------------------------------------- // +// CVoiceStatus. +// ---------------------------------------------------------------------- // + +CVoiceStatusHud::CVoiceStatusHud() +{ +} + + +CVoiceStatusHud::~CVoiceStatusHud() +{ +} + + +int CVoiceStatusHud::Init(IVoiceStatusHelper *pHelper, IVoiceStatus *pStatus) +{ + m_VoiceHeadModel = NULL; + + m_pHelper = pHelper; + m_pStatus = pStatus; + + gHUD.AddHudElem(this); + m_iFlags = HUD_ACTIVE; + + m_pLocalPlayerTalkIcon = new vgui::ImagePanel( NULL, "LocalPlayerIcon"); + m_pLocalPlayerTalkIcon->SetParent( gViewPortInterface->GetViewPortPanel() ); + m_pLocalPlayerTalkIcon->SetVisible( false ); + m_pLocalPlayerTalkIcon->SetImage( scheme()->GetImage("gfx/vgui/icntlk_pl", false) ); + + return 1; +} + + +int CVoiceStatusHud::VidInit() +{ + // Figure out the voice head model height. + m_VoiceHeadModelHeight = 45; + char *pFile = (char *)gEngfuncs.COM_LoadFile("scripts/voicemodel.txt", 5, NULL); + if(pFile) + { + char token[4096]; + gEngfuncs.COM_ParseFile(pFile, token); + if(token[0] >= '0' && token[0] <= '9') + { + m_VoiceHeadModelHeight = (float)atof(token); + } + + gEngfuncs.COM_FreeFile(pFile); + } + + m_VoiceHeadModel = gEngfuncs.pfnSPR_Load("sprites/voiceicon.spr"); + return TRUE; +} + +void CVoiceStatusHud::CreateEntities() +{ + if(!m_VoiceHeadModel) + return; + + cl_entity_t *localPlayer = gEngfuncs.GetLocalPlayer(); + + int iOutModel = 0; + for(int i=0; i < VOICE_MAX_PLAYERS; i++) + { + if(m_pStatus->GetSpeakerStatus(i)!=CVoiceStatus::VOICE_TALKING) + { + continue; + } + + cl_entity_s *pClient = gEngfuncs.GetEntityByIndex(i+1); + + // Don't show an icon if the player is not in our PVS. + if(!pClient || pClient->curstate.messagenum < localPlayer->curstate.messagenum) + continue; + + // Don't show an icon for dead or spectating players (ie: invisible entities). + if(pClient->curstate.effects & EF_NODRAW) + continue; + + // Don't show an icon for the local player unless we're in thirdperson mode. + if(pClient == localPlayer && !cam_thirdperson) + continue; + + cl_entity_s *pEnt = &m_VoiceHeadModels[iOutModel]; + ++iOutModel; + + memset(pEnt, 0, sizeof(*pEnt)); + + pEnt->curstate.rendermode = kRenderTransAdd; + pEnt->curstate.renderamt = 255; + pEnt->baseline.renderamt = 255; + pEnt->curstate.renderfx = kRenderFxNoDissipation; + pEnt->curstate.framerate = 1; + pEnt->curstate.frame = 0; + pEnt->model = (struct model_s*)gEngfuncs.GetSpritePointer(m_VoiceHeadModel); + pEnt->angles[0] = pEnt->angles[1] = pEnt->angles[2] = 0; + pEnt->curstate.scale = 0.5f; + + pEnt->origin[0] = pEnt->origin[1] = 0; + pEnt->origin[2] = 45; + + VectorAdd(pEnt->origin, pClient->origin, pEnt->origin); + + // Tell the engine. + gEngfuncs.CL_CreateVisibleEntity(ET_NORMAL, pEnt); + } +} + + + + +void CVoiceStatusHud::UpdateLocation(int entindex, const char *location) +{ + int iClient = entindex - 1; + if(iClient < 0) + return; + + CVoiceLabel *pLabel = FindVoiceLabel( iClient ); + if ( !pLabel ) + return; + + pLabel->SetLocation( location ); + + RepositionLabels(); +} + +CVoiceLabel* CVoiceStatusHud::FindVoiceLabel(int clientindex) +{ + for(int i=0; i < m_Labels.Count(); i++) + { + if(m_Labels[i]->GetClientIndex() == clientindex) + return m_Labels[i]; + } + + return NULL; +} + + +CVoiceLabel* CVoiceStatusHud::GetFreeVoiceLabel() +{ + CVoiceLabel *lab = FindVoiceLabel(-1); + if( !lab ) + { + lab = new CVoiceLabel(); + m_Labels.AddToTail( lab ); + } + return lab; +} + + +void CVoiceStatusHud::RepositionLabels() +{ + // find starting position to draw from, along right-hand side of screen + int y = ScreenHeight / 2; + + // Reposition active labels. + for(int i = 0; i < m_Labels.Count(); i++) + { + CVoiceLabel *pLabel = m_Labels[i]; + + int textWide, textTall; + pLabel->GetContentSize( textWide, textTall ); + pLabel->SetBounds( ScreenWidth - textWide - 8, y ); // if you adjust the x pos also play with VoiceVGUILabel in voice_status_hud.h + + y += textTall + 2; + } +} + + +void CVoiceStatusHud::UpdateSpeakerStatus(int entindex, bool bTalking) +{ + if( entindex == -2 ) // this is the local player + { + if( bTalking ) + { + int sizeX,sizeY; + IImage *image = m_pLocalPlayerTalkIcon->GetImage(); + if( image ) + { + image->GetContentSize( sizeX, sizeY ); + + int local_xPos = ScreenWidth - sizeX - 10; + int local_yPos = ScreenHeight - m_pHelper->GetAckIconHeight() - sizeY; + m_pLocalPlayerTalkIcon->SetPos( local_xPos, local_yPos ); + m_pLocalPlayerTalkIcon->SetVisible( true ); + } + } + else + { + m_pLocalPlayerTalkIcon->SetVisible( false ); + } + + } + else // a remote player, draw a label for them + { + if(entindex >= 0 && entindex <= MAX_PLAYERS) + { + int iClient = entindex - 1; + if(iClient < 0) + return; + + CVoiceLabel *pLabel = FindVoiceLabel(iClient); + if(bTalking) + { + // If we don't have a label for this guy yet, then create one. + if(!pLabel) + { + if(pLabel = GetFreeVoiceLabel()) + { + // Get the name from the engine. + hud_player_info_t info; + memset(&info, 0, sizeof(info)); + gEngfuncs.pfnGetPlayerInfo(entindex, &info); + + int color[3]; + m_pHelper->GetPlayerTextColor( entindex, color ); + + pLabel->SetFgColor( Color(255, 255, 255, 255) ); + pLabel->SetBgColor( Color(color[0], color[1], color[2], 180) ); + pLabel->SetPlayerName( info.name ); + pLabel->SetLocation( m_pHelper->GetPlayerLocation( entindex ) ); + pLabel->SetClientIndex( iClient ); + if ( m_pHelper ) + { + if ( m_pHelper->CanShowSpeakerLabels() ) + { + pLabel->SetVisible(true); + } + } + else + { + pLabel->SetVisible( true ); + } + + } + } + } + else + { + // If we have a label for this guy, kill it. + if(pLabel) + { + pLabel->SetVisible(false); + pLabel->SetClientIndex( -1 ); + } + } + } + } + + RepositionLabels(); +} + + diff --git a/game_shared/voice_status_hud.h b/game_shared/voice_status_hud.h new file mode 100644 index 0000000..e19824d --- /dev/null +++ b/game_shared/voice_status_hud.h @@ -0,0 +1,158 @@ +//========= Copyright � 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VOICE_STATUS_HUD_H +#define VOICE_STATUS_HUD_H +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#ifdef CZERO +#include "vgui/hl_base/iviewport.h" +#else +#include +#endif + +#include "voice_common.h" +#include "cl_entity.h" +#include "voice_banmgr.h" + + +//----------------------------------------------------------------------------- +// Purpose: a label which displays a name and a speaking icon +//----------------------------------------------------------------------------- +class CVoiceLabel +{ +public: + CVoiceLabel() + { + m_pLabel = new VoiceVGUILabel( NULL, "VoiceLabel", ""); + m_pLabel->SetParent( gViewPortInterface->GetViewPortPanel() ); + m_pLabel->SetProportional(true); + m_pLabel->SetScheme("ClientScheme"); + vgui::SETUP_PANEL(m_pLabel); + m_clientindex = -1; // -1 means unassigned + m_locationString = NULL; + m_playerName = NULL; + } + + ~CVoiceLabel() + { + m_pLabel->MarkForDeletion(); + } + + // pass throughs for various label calls + void SetFgColor( Color c ) { m_pLabel->SetFgColor( c ); } + void SetBgColor( Color c ) { m_pLabel->SetBgColor( c ); } + void SetVisible( bool state ) { m_pLabel->SetVisible( state ); } + bool GetVisible() { return m_pLabel->IsVisible(); } + + void GetContentSize( int &wide, int &tall ) { m_pLabel->GetContentSize( wide, tall ); if( tall < 32 ) tall =32; } + void SetBounds( int x, int y ) { + m_pLabel->SetPos( x, y ); + int wide, tall; + m_pLabel->GetContentSize( wide, tall ); + m_pLabel->SetSize( wide, tall ); + } + + void SetClientIndex( int in ) { m_clientindex = in; } + int GetClientIndex() { return m_clientindex; } + + void SetLocation( const char *location ); + void SetPlayerName( const char *name ); + +private: + + //----------------------------------------------------------------------------- + // Purpose: inner class that overrides ApplySchemeSettings() for the label so an image can be loaded, also saves colors away + // so ApplySchemeSettings() doesn't override them + //----------------------------------------------------------------------------- + class VoiceVGUILabel : public vgui::Label + { + public: + VoiceVGUILabel( vgui::Panel *parent, const char *name, const char *text): Label(parent, name, text) {} + + + private: + // VGUI2 overrides + virtual void ApplySchemeSettings(vgui::IScheme *pScheme) + { + Label::ApplySchemeSettings(pScheme); + SetTextImageIndex(1); + SetImagePreOffset( 1, 2); // shift the text over a little + // you need to load the image here, after Label::ApplySchemeSettings()(as applysettings nulls out all existing images) + SetImageAtIndex( 0, vgui::scheme()->GetImage( "gfx/vgui/speaker4", false), 1 ); + } + + }; + + void RebuildLabelText(); + + VoiceVGUILabel *m_pLabel; // the label with the user name and icon + int m_clientindex; // Client index of the speaker. -1 if this label isn't being used. + wchar_t *m_locationString; // localized location string. NULL if the location is "". + char *m_playerName; +}; + + + +//----------------------------------------------------------------------------- +// Purpose: Handles the displaying of labels on the hud and icons above players in game when they talk +//----------------------------------------------------------------------------- +class CVoiceStatusHud : public IVoiceHud, public CHudBase +{ +public: + CVoiceStatusHud(); + virtual ~CVoiceStatusHud(); + + // CHudBase overrides. + // Initialize the cl_dll's voice manager. + virtual int Init(IVoiceStatusHelper *pHelper, IVoiceStatus *pStatus); + + // ackPosition is the bottom position of where CVoiceStatus will draw the voice acknowledgement labels. + virtual int VidInit(); + + // Call from the HUD_CreateEntities function so it can add sprites above player heads. + void CreateEntities(); + + void UpdateLocation(int entindex, const char *location); + + void UpdateSpeakerStatus(int entindex, bool bTalking); + + + CVoiceLabel* FindVoiceLabel(int clientindex); // Find a CVoiceLabel representing the specified speaker. + // Returns NULL if none. + // entindex can be -1 if you want a currently-unused voice label. + CVoiceLabel* GetFreeVoiceLabel(); // Get an unused voice label. Returns NULL if none. + void RepositionLabels(); + + +private: + + cl_entity_s m_VoiceHeadModels[VOICE_MAX_PLAYERS]; // These aren't necessarily in the order of players. They are just + // a place for it to put data in during CreateEntities. + HSPRITE m_VoiceHeadModel; // Voice head model (goes above players who are speaking). + float m_VoiceHeadModelHeight; // Height above their head to place the model. + + IVoiceStatusHelper *m_pHelper; + IVoiceStatus *m_pStatus; + + // Labels telling who is speaking. + CUtlVector m_Labels; + + vgui::ImagePanel *m_pLocalPlayerTalkIcon; +}; + + + +#endif // VOICE_STATUS_HUD_H diff --git a/game_shared/voice_vgui_tweakdlg.cpp b/game_shared/voice_vgui_tweakdlg.cpp new file mode 100644 index 0000000..07f02ad --- /dev/null +++ b/game_shared/voice_vgui_tweakdlg.cpp @@ -0,0 +1,289 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "../cl_dll/hud.h" +#include "../cl_dll/cl_util.h" +#include "../cl_dll/vgui_teamfortressviewport.h" + + +#include "vgui_actionsignal.h" +#include "voice_vgui_tweakdlg.h" +#include "voice_vgui_tweakdlg.h" +#include "vgui_panel.h" +#include "vgui_scrollbar.h" +#include "vgui_slider.h" +#include "ivoicetweak.h" +#include "vgui_button.h" +#include "vgui_checkbutton2.h" +#include "vgui_helpers.h" + + +#define ITEM_BORDER 40 // Border between text and scrollbars on left and right. +#define VOICETWEAK_TRANSPARENCY 150 + + +class TweakScroller +{ +public: + TweakScroller(); + void Init(Panel *pParent, char *pText, int yPos); + + // Get/set value. Values are 0-1. + float GetValue(); + void SetValue(float val); + +public: + Label m_Label; + ScrollBar m_Scroll; + Slider m_Slider; +}; + + +class CVoiceVGUITweakDlg : public CMenuPanel, public ICheckButton2Handler +{ +typedef CMenuPanel BaseClass; + +public: + CVoiceVGUITweakDlg(); + ~CVoiceVGUITweakDlg(); + +// CMenuPanel overrides. +public: + virtual void Open(); + virtual void Close(); + + +// ICheckButton2Handler overrides. +public: + + virtual void StateChanged(CCheckButton2 *pButton); + + + +// Panel overrides. +public: + virtual void paintBackground(); + + +private: + + int m_DlgWidth; + int m_DlgHeight; + + Label m_Label; + + IVoiceTweak *m_pVoiceTweak; // Engine voice tweak API. + + TweakScroller m_MicVolume; + TweakScroller m_SpeakerVolume; + + CCheckButton2 m_VoiceModEnable; + + Button m_Button_OK; +}; + + + +bool g_bTweakDlgOpen = false; + +bool IsTweakDlgOpen() +{ + return g_bTweakDlgOpen; +} + + + +// ------------------------------------------------------------------------ // +// Global functions. +// ------------------------------------------------------------------------ // + +static CVoiceVGUITweakDlg g_VoiceTweakDlg; +CMenuPanel* GetVoiceTweakDlg() +{ + return &g_VoiceTweakDlg; +} + + +class CVoiceTweakOKButton : public ActionSignal +{ +public: + virtual void actionPerformed(Panel *pPanel) + { + gViewPort->HideVGUIMenu(); + } +}; +CVoiceTweakOKButton g_OKButtonSignal; + + + +// ------------------------------------------------------------------------ // +// TweakScroller +// ------------------------------------------------------------------------ // + +TweakScroller::TweakScroller() : + m_Label(""), + m_Scroll(0,0,0,0,false), + m_Slider(0,0,10,10,false) +{ +} + + +void TweakScroller::Init(Panel *pParent, char *pText, int yPos) +{ + int parentWidth, parentHeight; + pParent->getSize(parentWidth, parentHeight); + + // Setup the volume scroll bar. + m_Label.setParent(pParent); + m_Label.setFont(Scheme::sf_primary1); + m_Label.setContentAlignment(vgui::Label::a_northwest); + m_Label.setBgColor(0, 0, 0, 255); + m_Label.setFgColor(255,255,255,0); + m_Label.setPos(ITEM_BORDER, yPos); + m_Label.setSize(parentWidth/2-ITEM_BORDER, 20); + m_Label.setText(pText); + m_Label.setVisible(true); + + m_Slider.setRangeWindow(10); + m_Slider.setRangeWindowEnabled(true); + + m_Scroll.setPos(parentWidth/2+ITEM_BORDER, yPos); + m_Scroll.setSize(parentWidth/2-ITEM_BORDER*2, 20); + m_Scroll.setSlider(&m_Slider); + m_Scroll.setParent(pParent); + m_Scroll.setRange(0, 100); + m_Scroll.setFgColor(255,255,255,0); + m_Scroll.setBgColor(255,255,255,0); +} + + +float TweakScroller::GetValue() +{ + return m_Scroll.getValue() / 100.0f; +} + + +void TweakScroller::SetValue(float val) +{ + m_Scroll.setValue((int)(val * 100.0f)); +} + + +// ------------------------------------------------------------------------ // +// CVoiceVGUITweakDlg implementation. +// ------------------------------------------------------------------------ // + +CVoiceVGUITweakDlg::CVoiceVGUITweakDlg() + : CMenuPanel(VOICETWEAK_TRANSPARENCY, false, 0, 0, 0, 0), + m_Button_OK("",0,0), + m_Label("") +{ + m_pVoiceTweak = NULL; + m_Button_OK.addActionSignal(&g_OKButtonSignal); + m_Label.setBgColor(255,255,255,200); +} + + +CVoiceVGUITweakDlg::~CVoiceVGUITweakDlg() +{ +} + + +void CVoiceVGUITweakDlg::Open() +{ + if(g_bTweakDlgOpen) + return; + + g_bTweakDlgOpen = true; + + m_DlgWidth = ScreenWidth; + m_DlgHeight = ScreenHeight; + + m_pVoiceTweak = gEngfuncs.pVoiceTweak; + + // Tell the engine to start voice tweak mode (pipe voice output right to speakers). + m_pVoiceTweak->StartVoiceTweakMode(); + + // Set our size. + setPos((ScreenWidth - m_DlgWidth) / 2, (ScreenHeight - m_DlgHeight) / 2); + setSize(m_DlgWidth, m_DlgHeight); + + int curY = ITEM_BORDER; + m_MicVolume.Init(this, gHUD.m_TextMessage.BufferedLocaliseTextString("#Mic_Volume"), curY); + m_MicVolume.SetValue(m_pVoiceTweak->GetControlFloat(MicrophoneVolume)); + curY = PanelBottom(&m_MicVolume.m_Label); + + m_SpeakerVolume.Init(this, gHUD.m_TextMessage.BufferedLocaliseTextString("#Speaker_Volume"), curY); + m_SpeakerVolume.SetValue(m_pVoiceTweak->GetControlFloat(OtherSpeakerScale)); + curY = PanelBottom(&m_SpeakerVolume.m_Label); + + m_VoiceModEnable.setParent(this); + m_VoiceModEnable.SetImages("gfx/vgui/checked.tga", "gfx/vgui/unchecked.tga"); + m_VoiceModEnable.SetText("Enable Voice In This Mod"); + m_VoiceModEnable.setPos(ITEM_BORDER, curY); + m_VoiceModEnable.SetCheckboxLeft(false); + m_VoiceModEnable.SetChecked(!!gEngfuncs.pfnGetCvarFloat("voice_modenable")); + m_VoiceModEnable.SetHandler(this); + + // Setup the OK button. + int buttonWidth, buttonHeight; + m_Button_OK.setText(gHUD.m_TextMessage.BufferedLocaliseTextString("#Menu_OK")); + m_Button_OK.getSize(buttonWidth, buttonHeight); + m_Button_OK.setPos((m_DlgWidth - buttonWidth) / 2, m_DlgHeight - buttonHeight - 3); + m_Button_OK.setParent(this); + + // Put the label on the top. + m_Label.setBgColor(0, 0, 0, 255); + m_Label.setFgColor(255,255,255,0); + m_Label.setText(gHUD.m_TextMessage.BufferedLocaliseTextString("#Voice_Properties")); + int labelWidth, labelHeight; + m_Label.getSize(labelWidth, labelHeight); + m_Label.setPos((m_DlgWidth - labelWidth) / 2, 5); + m_Label.setParent(this); + + BaseClass::Open(); +} + + +void CVoiceVGUITweakDlg::Close() +{ + m_pVoiceTweak->EndVoiceTweakMode(); + g_bTweakDlgOpen = false; + + BaseClass::Close(); +} + + +void CVoiceVGUITweakDlg::paintBackground() +{ + BaseClass::paintBackground(); + + // Draw our border. + int w,h; + getSize(w,h); + + drawSetColor(128,128,128,1); + drawOutlinedRect(0, 0, w, h); + + float volume = m_MicVolume.GetValue(); + m_pVoiceTweak->SetControlFloat(MicrophoneVolume, volume); + + m_pVoiceTweak->SetControlFloat(OtherSpeakerScale, m_SpeakerVolume.GetValue()); +} + + +void CVoiceVGUITweakDlg::StateChanged(CCheckButton2 *pButton) +{ + if(pButton == &m_VoiceModEnable) + { + if(pButton->IsChecked()) + gEngfuncs.pfnClientCmd("voice_modenable 1"); + else + gEngfuncs.pfnClientCmd("voice_modenable 0"); + } +} + diff --git a/game_shared/voice_vgui_tweakdlg.h b/game_shared/voice_vgui_tweakdlg.h new file mode 100644 index 0000000..deb83dc --- /dev/null +++ b/game_shared/voice_vgui_tweakdlg.h @@ -0,0 +1,25 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VOICE_VGUI_TWEAKDLG_H +#define VOICE_VGUI_TWEAKDLG_H +#ifdef _WIN32 +#pragma once +#endif + + +class CMenuPanel; + + +// Returns true if the tweak dialog is currently up. +bool IsTweakDlgOpen(); + +// Returns a global instance of the tweak dialog. +CMenuPanel* GetVoiceTweakDlg(); + + +#endif // VOICE_VGUI_TWEAKDLG_H diff --git a/lib/public/SDL2.lib b/lib/public/SDL2.lib new file mode 100644 index 0000000000000000000000000000000000000000..cc89dba06d8c2b222473d6f6e38f7754a30c9529 GIT binary patch literal 108368 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%0Ab!m$1s8Wv|`-mt=UH(_n0ney!QFFIl6vm1s{ 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

_}>} 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

B9zLp|lGv&&M%>q0wKo|pBDR*fLhWV!W-aUK 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 literal 0 HcmV?d00001 diff --git a/linux/Makefile b/linux/Makefile new file mode 100644 index 0000000..a1c8d39 --- /dev/null +++ b/linux/Makefile @@ -0,0 +1,170 @@ + +# +# Half-life Makefile for x86 Linux +# +# + +OS:=$(shell uname) +HOSTNAME:=$(shell hostname) + +ifeq "$(CFG)" "" +CFG=release +endif + +SOURCE_DIR=.. +BUILD_DIR=$(CFG) + +ENGINE_SRC_DIR=$(SOURCE_DIR)/engine +COMMON_SRC_DIR=$(SOURCE_DIR)/common +PM_SRC_DIR=$(SOURCE_DIR)/pm_shared +GAME_SHARED_SRC_DIR=$(SOURCE_DIR)/game_shared +GAMEDB_SRC_DIR=$(COMMON_SRC_DIR)/gamedb +PUBLIC_SRC_DIR=$(SOURCE_DIR)/public +DBG_SRC_DIR=$(SOURCE_DIR)/dbg + + +BUILD_OBJ_DIR=$(BUILD_DIR)/obj + +ARCH=i386 +ELF-GC-DYNSTR=./elf-gc-dynstr + +ifeq ($(OS),Linux) + CC="/valve/bin/gcc-4.6 -m32" + CPLUS="/valve/bin/g++-4.6 -m32" + CPP_LIB:=-L$(shell /valve/bin/g++-4.6 -m32 -print-file-name=libstdc++.so | xargs dirname) -lstdc++ -ldl -lpthread +endif + +ifeq ($(OS),Darwin) + OSXVER := $(shell sw_vers -productVersion) + DEVELOPER_DIR := $(shell /usr/bin/xcode-select -print-path) + ifeq (,$(findstring 10.7, $(OSXVER))) + BUILDING_ON_LION := 0 + COMPILER_BIN_DIR := $(DEVELOPER_DIR)/usr/bin + SDK_DIR := $(DEVELOPER_DIR)/SDKs + else + BUILDING_ON_LION := 1 + COMPILER_BIN_DIR := $(DEVELOPER_DIR)/Toolchains/XcodeDefault.xctoolchain/usr/bin + SDK_DIR := $(DEVELOPER_DIR)/Platforms/MacOSX.platform/Developer/SDKs + endif + + #SDKROOT ?= $(SDK_DIR)/MacOSX10.6.sdk + SDKROOT ?= $(SDK_DIR)/MacOSX10.8.sdk + + ARCH_FLAGS ?= -arch i386 -m32 -march=prescott -gdwarf-2 -g2 -Wno-typedef-redefinition -momit-leaf-frame-pointer -mtune=core2 + CPP_LIB=-lstdc++ -lpthread + + ifeq ($(origin AR), default) + AR = libtool -static -o + endif + ifeq ($(origin CC), default) + CC ="$(COMPILER_BIN_DIR)/clang -Qunused-arguments -isysroot $(SDKROOT) -mmacosx-version-min=10.5 -fasm-blocks -I$(SDKROOT)/usr/include/malloc $(ARCH_FLAGS)" + endif + ifeq ($(origin CXX), default) + CPLUS ="$(COMPILER_BIN_DIR)/clang++ -Qunused-arguments -isysroot $(SDKROOT) -mmacosx-version-min=10.5 -fasm-blocks -I$(SDKROOT)/usr/include/malloc $(ARCH_FLAGS)" + endif + LINK ?= $(CXX) +endif + +CLINK=$(CC) + +ifeq "$(CFG)" "release" +ARCH_CFLAGS_I486=-O3 -gdwarf-2 -g2 +ARCH_CFLAGS_I686=-O3 -gdwarf-2 -g2 +ARCH_CFLAGS_AMD=-O3 -gdwarf-2 -g2 +ARCH_CFLAGS_AMD64=-m64 -O3 -gdwarf-2 -g2 +else +ARCH_CFLAGS_I486=-gdwarf-2 -g2 +ARCH_CFLAGS_I686=-gdwarf-2 -g2 +ARCH_CFLAGS_AMD=-gdwarf-2 -g2 +ARCH_CFLAGS_AMD64=-m64 -gdwarf-2 -g2 +endif + +ifeq ($(OS),Linux) +ARCH_CFLAGS_I486+=-march=pentium-m -mfpmath=387 +ARCH_CFLAGS_I686+=-march=pentium-m -mfpmath=387 +ARCH_CFLAGS_AMD+=-march=k6 -mfpmath=387 +endif + +ifeq ($(OS),Darwin) +# force 387 for FP math so the precision between win32 and linux and osx match +ARCH_CFLAGS_I486+=-march=pentium-m -mfpmath=387 +ARCH_CFLAGS_I686+=-march=pentium-m -mfpmath=387 +ARCH_CFLAGS_AMD+=-mfpmath=387 +endif + + +ARCH_CFLAGS="$(ARCH_CFLAGS_I486)" + +BASE_CFLAGS=-fpermissive -fno-strict-aliasing -DNDEBUG -DPOSIX -D_POSIX -DLINUX -D_LINUX -DGNUC -DHL1 -Dstricmp=strcasecmp -D_strnicmp=strncasecmp -Dstrnicmp=strncasecmp -D_snprintf=snprintf -DQUIVER -DQUAKE2 -DVALVE_DLL -D_alloca=alloca -fno-exceptions -fexpensive-optimizations -D_vsnprintf=vsnprintf -DNO_MALLOC_OVERRIDE -Werror=return-type +BASE_CFLAGS+=-w + +ifeq ($(OS),Darwin) + BASE_CFLAGS += -DOSX -D_OSX -fvisibility=hidden +else + BASE_CFLAGS+= -DLINUX -D_LINUX +endif + +DEDICATED_CFLAGS="-DDEDICATED -DSWDS" +ifeq ($(OS),Darwin) +SHLIBEXT=dylib +SHLIBCFLAGS= +SHLIBLDFLAGS="-shared -gdwarf-2 -g2" +else +SHLIBEXT=so +SHLIBCFLAGS= +SHLIBLDFLAGS="-shared -gdwarf-2 -g2 -Wl,-Map,$@_map.txt" +endif +AR=ar +LIBEXT=a +MAKE+= -j8 + +MAKE_HL_LIB=$(MAKE) -f Makefile.hldll +MAKE_DMC_LIB=$(MAKE) -f Makefile.dmcdll +MAKE_RICOCHET_LIB=$(MAKE) -f Makefile.ricochetdll +MAKE_HL_CDLL=$(MAKE) -f Makefile.hl_cdll +MAKE_DMC_CDLL=$(MAKE) -f Makefile.dmc_cdll +MAKE_RICOCHET_CDLL=$(MAKE) -f Makefile.ricochet_cdll + +############################################################################# +# SETUP AND BUILD +############################################################################# + +all: build_dir targets + +TARGETS= + +TARGETS+= \ + hl \ + dmc \ + ricochet \ + hl_cdll \ + dmc_cdll \ + ricochet_cdll \ + +build_dir: + -mkdir $(BUILD_DIR); + cd $(BUILD_DIR) + +targets: $(TARGETS) + + +hl_cdll: build_dir + $(MAKE_HL_CDLL) ARCH=i686 CC=$(CC) CPLUS=$(CPLUS) CPP_LIB="$(CPP_LIB)" BUILD_DIR=$(BUILD_DIR) BUILD_OBJ_DIR=$(BUILD_OBJ_DIR) SOURCE_DIR=$(SOURCE_DIR) ENGINE_SRC_DIR=$(ENGINE_SRC_DIR) COMMON_SRC_DIR=$(COMMON_SRC_DIR) BASE_CFLAGS="$(BASE_CFLAGS)" PUBLIC_SRC_DIR=$(PUBLIC_SRC_DIR) DBG_SRC_DIR=$(DBG_SRC_DIR) ARCH_CFLAGS="$(ARCH_CFLAGS_I686)" GAME_SHARED_SRC_DIR=$(GAME_SHARED_SRC_DIR) CLINK=$(CLINK) PM_SRC_DIR=$(PM_SRC_DIR) SHLIBEXT=$(SHLIBEXT) SHLIBCFLAGS=$(SHLIBCFLAGS) SHLIBLDFLAGS=$(SHLIBLDFLAGS) CFG=$(CFG) OS=$(OS) + +dmc_cdll: build_dir + $(MAKE_DMC_CDLL) ARCH=i686 CC=$(CC) CPLUS=$(CPLUS) CPP_LIB="$(CPP_LIB)" BUILD_DIR=$(BUILD_DIR) BUILD_OBJ_DIR=$(BUILD_OBJ_DIR) SOURCE_DIR=$(SOURCE_DIR) ENGINE_SRC_DIR=$(ENGINE_SRC_DIR) COMMON_SRC_DIR=$(COMMON_SRC_DIR) BASE_CFLAGS="$(BASE_CFLAGS)" PUBLIC_SRC_DIR=$(PUBLIC_SRC_DIR) DBG_SRC_DIR=$(DBG_SRC_DIR) ARCH_CFLAGS="$(ARCH_CFLAGS_I686)" GAME_SHARED_SRC_DIR=$(GAME_SHARED_SRC_DIR) CLINK=$(CLINK) PM_SRC_DIR=$(PM_SRC_DIR) SHLIBEXT=$(SHLIBEXT) SHLIBCFLAGS=$(SHLIBCFLAGS) SHLIBLDFLAGS=$(SHLIBLDFLAGS) CFG=$(CFG) OS=$(OS) + +ricochet_cdll: build_dir + $(MAKE_RICOCHET_CDLL) ARCH=i686 CC=$(CC) CPLUS=$(CPLUS) CPP_LIB="$(CPP_LIB)" BUILD_DIR=$(BUILD_DIR) BUILD_OBJ_DIR=$(BUILD_OBJ_DIR) SOURCE_DIR=$(SOURCE_DIR) ENGINE_SRC_DIR=$(ENGINE_SRC_DIR) COMMON_SRC_DIR=$(COMMON_SRC_DIR) BASE_CFLAGS="$(BASE_CFLAGS)" PUBLIC_SRC_DIR=$(PUBLIC_SRC_DIR) DBG_SRC_DIR=$(DBG_SRC_DIR) ARCH_CFLAGS="$(ARCH_CFLAGS_I686)" GAME_SHARED_SRC_DIR=$(GAME_SHARED_SRC_DIR) CLINK=$(CLINK) PM_SRC_DIR=$(PM_SRC_DIR) SHLIBEXT=$(SHLIBEXT) SHLIBCFLAGS=$(SHLIBCFLAGS) SHLIBLDFLAGS=$(SHLIBLDFLAGS) CFG=$(CFG) OS=$(OS) + +hl: build_dir + $(MAKE_HL_LIB) CC=$(CC) ARCH=$(ARCH) CPP_LIB="$(CPP_LIB)" BUILD_DIR=$(BUILD_DIR) BUILD_OBJ_DIR=$(BUILD_OBJ_DIR) SOURCE_DIR=$(SOURCE_DIR) ENGINE_SRC_DIR=$(ENGINE_SRC_DIR) COMMON_SRC_DIR=$(COMMON_SRC_DIR) PM_SRC_DIR=$(PM_SRC_DIR) GAME_SHARED_SRC_DIR=$(GAME_SHARED_SRC_DIR) GAMEDB_SRC_DIR=$(GAMEDB_SRC_DIR) BASE_CFLAGS="$(BASE_CFLAGS)" SHLIBEXT=$(SHLIBEXT) SHLIBCFLAGS=$(SHLIBCFLAGS) SHLIBLDFLAGS=$(SHLIBLDFLAGS) PUBLIC_SRC_DIR=$(PUBLIC_SRC_DIR) CFG=$(CFG) OS=$(OS) ARCH_CFLAGS="$(ARCH_CFLAGS_I686)" + +dmc: build_dir + $(MAKE_DMC_LIB) CC=$(CC) CPLUS=$(CPLUS) CPP_LIB="$(CPP_LIB)" ARCH=$(ARCH) BUILD_DIR=$(BUILD_DIR) BUILD_OBJ_DIR=$(BUILD_OBJ_DIR) SOURCE_DIR=$(SOURCE_DIR) ENGINE_SRC_DIR=$(ENGINE_SRC_DIR) COMMON_SRC_DIR=$(COMMON_SRC_DIR) GAME_SHARED_SRC_DIR=$(GAME_SHARED_SRC_DIR) BASE_CFLAGS="$(BASE_CFLAGS)" SHLIBEXT=$(SHLIBEXT) SHLIBCFLAGS=$(SHLIBCFLAGS) SHLIBLDFLAGS=$(SHLIBLDFLAGS) PUBLIC_SRC_DIR=$(PUBLIC_SRC_DIR) CFG=$(CFG) OS=$(OS) CLINK=$(CLINK) ARCH_CFLAGS="$(ARCH_CFLAGS_I686)" + +ricochet: build_dir + $(MAKE_RICOCHET_LIB) CC=$(CC) CPLUS=$(CPLUS) CPP_LIB="$(CPP_LIB)" ARCH=$(ARCH) BUILD_DIR=$(BUILD_DIR) BUILD_OBJ_DIR=$(BUILD_OBJ_DIR) SOURCE_DIR=$(SOURCE_DIR) ENGINE_SRC_DIR=$(ENGINE_SRC_DIR) COMMON_SRC_DIR=$(COMMON_SRC_DIR) GAME_SHARED_SRC_DIR=$(GAME_SHARED_SRC_DIR) BASE_CFLAGS="$(BASE_CFLAGS)" SHLIBEXT=$(SHLIBEXT) SHLIBCFLAGS=$(SHLIBCFLAGS) SHLIBLDFLAGS=$(SHLIBLDFLAGS) PUBLIC_SRC_DIR=$(PUBLIC_SRC_DIR) CFG=$(CFG) OS=$(OS) CLINK=$(CLINK) ARCH_CFLAGS="$(ARCH_CFLAGS_I686)" + +clean: + -rm -rf $(BUILD_OBJ_DIR) diff --git a/linux/Makefile.dmc_cdll b/linux/Makefile.dmc_cdll new file mode 100644 index 0000000..edcfe81 --- /dev/null +++ b/linux/Makefile.dmc_cdll @@ -0,0 +1,164 @@ +# +# dmc client Makefile for x86 Linux +# +# + +DMC_SRC_DIR=$(SOURCE_DIR)/dmc/cl_dll +GAME_SHARED_SRC_DIR=$(SOURCE_DIR)/game_shared +PM_SHARED_SRC_DIR=$(DMC_SRC_DIR)/../pm_shared + +DMC_OBJ_DIR=$(BUILD_OBJ_DIR)/dmc_client +PUBLIC_OBJ_DIR=$(DMC_OBJ_DIR)/public +COMMON_OBJ_DIR=$(DMC_OBJ_DIR)/common +GAME_SHARED_OBJ_DIR=$(DMC_OBJ_DIR)/game_shared +PM_SHARED_OBJ_DIR=$(DMC_OBJ_DIR)/pm_shared + +CFLAGS=$(BASE_CFLAGS) $(ARCH_CFLAGS) -DCLIENT_DLL -DDMC_BUILD -I/usr/include/malloc -D_snwprintf=swprintf \ + -DDISABLE_JUMP_ORIGIN -DDISABLE_VEC_ORIGIN -D_MAX_PATH=PATH_MAX + +INCLUDEDIRS=-I$(PUBLIC_SRC_DIR) -I../utils/vgui/include -I$(DMC_SRC_DIR)/../dlls \ + -I../engine -I$(COMMON_SRC_DIR) -I../utils/common -I$(DMC_SRC_DIR)/../pm_shared -I$(DMC_SRC_DIR) -I../game_shared -I../external + +ifeq ($(OS),Darwin) +LDFLAGS=$(SHLIBLDFLAGS) $(CPP_LIB) -framework Carbon $(CFG)/vgui.dylib -L. -lSDL2-2.0.0 +else +LDFLAGS=$(SHLIBLDFLAGS) $(CPP_LIB) vgui.so -L. libSDL2-2.0.so.0 +endif + +DO_CC=$(CPLUS) $(INCLUDEDIRS) $(CFLAGS) -o $@ -c $< +DO_PUBLIC_CC=$(CPLUS) $(COMMON_INCLUDEDIRS) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< +DO_COMMON_CC=$(CPLUS) $(INCLUDEDIRS) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< +DO_PM_SHARED_CC=$(CC) $(INCLUDEDIRS) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< + +##################################################################### + +DMC_OBJS = \ + $(DMC_OBJ_DIR)/ev_common.o \ + $(DMC_OBJ_DIR)/CTF_FlagStatus.o \ + $(DMC_OBJ_DIR)/CTF_HudMessage.o \ + $(DMC_OBJ_DIR)/DMC_Teleporters.o \ + $(DMC_OBJ_DIR)/ev_hldm.o \ + $(DMC_OBJ_DIR)/quake/quake_baseentity.o \ + $(DMC_OBJ_DIR)/quake/quake_events.o \ + $(DMC_OBJ_DIR)/quake/quake_objects.o \ + $(DMC_OBJ_DIR)/quake/quake_weapons.o \ + $(DMC_OBJ_DIR)/studio_util.o \ + $(DMC_OBJ_DIR)/vgui_SpectatorPanel.o \ + $(DMC_OBJ_DIR)/ammo.o \ + $(DMC_OBJ_DIR)/ammo_secondary.o \ + $(DMC_OBJ_DIR)/ammohistory.o \ + $(DMC_OBJ_DIR)/battery.o \ + $(DMC_OBJ_DIR)/cdll_int.o \ + $(DMC_OBJ_DIR)/com_weapons.o \ + $(DMC_OBJ_DIR)/death.o \ + $(DMC_OBJ_DIR)/demo.o \ + $(DMC_OBJ_DIR)/entity.o \ + $(DMC_OBJ_DIR)/events.o \ + $(DMC_OBJ_DIR)/GameStudioModelRenderer.o \ + $(DMC_OBJ_DIR)/geiger.o \ + $(DMC_OBJ_DIR)/health.o \ + $(DMC_OBJ_DIR)/hud.o \ + $(DMC_OBJ_DIR)/hud_msg.o \ + $(DMC_OBJ_DIR)/hud_redraw.o \ + $(DMC_OBJ_DIR)/hud_spectator.o \ + $(DMC_OBJ_DIR)/hud_update.o \ + $(DMC_OBJ_DIR)/hud_servers.o \ + $(DMC_OBJ_DIR)/in_camera.o \ + $(DMC_OBJ_DIR)/input.o \ + $(DMC_OBJ_DIR)/inputw32.o \ + $(DMC_OBJ_DIR)/menu.o \ + $(DMC_OBJ_DIR)/message.o \ + $(DMC_OBJ_DIR)/saytext.o \ + $(DMC_OBJ_DIR)/status_icons.o \ + $(DMC_OBJ_DIR)/statusbar.o \ + $(DMC_OBJ_DIR)/StudioModelRenderer.o \ + $(DMC_OBJ_DIR)/text_message.o \ + $(DMC_OBJ_DIR)/train.o \ + $(DMC_OBJ_DIR)/tri.o \ + $(DMC_OBJ_DIR)/util.o \ + $(DMC_OBJ_DIR)/view.o \ + $(DMC_OBJ_DIR)/voice_status.o \ + $(DMC_OBJ_DIR)/vgui_int.o \ + $(DMC_OBJ_DIR)/vgui_ScorePanel.o \ + $(DMC_OBJ_DIR)/vgui_ServerBrowser.o \ + $(DMC_OBJ_DIR)/vgui_viewport.o \ + $(DMC_OBJ_DIR)/vgui_CustomObjects.o \ + $(DMC_OBJ_DIR)/vgui_MOTDWindow.o \ + $(DMC_OBJ_DIR)/vgui_SchemeManager.o \ + $(DMC_OBJ_DIR)/dlls/quake_weapons_all.o \ + $(DMC_OBJ_DIR)/dlls/quake_gun.o \ + + +PUBLIC_OBJS = \ + $(PUBLIC_OBJ_DIR)/interface.o \ + +COMMON_OBJS = \ + $(COMMON_OBJ_DIR)/parsemsg.o \ + +GAME_SHARED_OBJS = \ + $(GAME_SHARED_OBJ_DIR)/voice_banmgr.o \ + $(GAME_SHARED_OBJ_DIR)/vgui_checkbutton2.o \ + $(GAME_SHARED_OBJ_DIR)/vgui_grid.o \ + $(GAME_SHARED_OBJ_DIR)/vgui_helpers.o \ + $(GAME_SHARED_OBJ_DIR)/vgui_listbox.o \ + $(GAME_SHARED_OBJ_DIR)/vgui_loadtga.o \ + $(GAME_SHARED_OBJ_DIR)/vgui_scrollbar2.o \ + $(GAME_SHARED_OBJ_DIR)/vgui_slider2.o \ + +PM_SHARED_OBJS = \ + $(PM_SHARED_OBJ_DIR)/pm_debug.o \ + $(PM_SHARED_OBJ_DIR)/pm_shared.o \ + $(PM_SHARED_OBJ_DIR)/pm_math.o \ + + +all: client_dmc.$(SHLIBEXT) + +client_dmc.$(SHLIBEXT): $(DMC_OBJS) $(PUBLIC_OBJS) $(COMMON_OBJS) $(GAME_SHARED_OBJS) $(PM_SHARED_OBJS) + $(CLINK) -o $(BUILD_DIR)/$@ $(DMC_OBJS) $(PUBLIC_OBJS) $(COMMON_OBJS) $(GAME_SHARED_OBJS) $(PM_SHARED_OBJS) $(LDFLAGS) $(CPP_LIB) + p4 edit ../../game/mod/cl_dlls/client.$(SHLIBEXT) + cp $(BUILD_DIR)/$@ ../../game/mod/cl_dlls/client.$(SHLIBEXT) + ./gendbg.sh ../../game/mod/cl_dlls/client.$(SHLIBEXT) + + +$(DMC_OBJ_DIR): + mkdir -p $(DMC_OBJ_DIR) + mkdir -p $(DMC_OBJ_DIR)/dlls + mkdir -p $(DMC_OBJ_DIR)/quake + +$(PUBLIC_OBJ_DIR): + mkdir -p $(PUBLIC_OBJ_DIR) + +$(COMMON_OBJ_DIR): + mkdir -p $(COMMON_OBJ_DIR) + +$(GAME_SHARED_OBJ_DIR): + mkdir -p $(GAME_SHARED_OBJ_DIR) + +$(PM_SHARED_OBJ_DIR): + mkdir -p $(PM_SHARED_OBJ_DIR) + +$(DMC_OBJ_DIR)/%.o: $(DMC_SRC_DIR)/%.cpp $(filter-out $(wildcard $(DMC_OBJ_DIR)), $(DMC_OBJ_DIR)) + $(DO_CC) + +$(DMC_OBJ_DIR)/%.o: $(DMC_SRC_DIR)/../%.cpp $(filter-out $(wildcard $(DMC_OBJ_DIR)), $(DMC_OBJ_DIR)) + $(DO_CC) + +$(DMC_OBJ_DIR)/%.o: $(DMC_SRC_DIR)/quake/%.cpp $(filter-out $(wildcard $(DMC_OBJ_DIR)), $(DMC_OBJ_DIR)) + $(DO_CC) + +$(PUBLIC_OBJ_DIR)/%.o : $(PUBLIC_SRC_DIR)/%.cpp $(filter-out $(wildcard $(PUBLIC_OBJ_DIR)), $(PUBLIC_OBJ_DIR)) + $(DO_PUBLIC_CC) + +$(COMMON_OBJ_DIR)/%.o : $(COMMON_SRC_DIR)/%.cpp $(filter-out $(wildcard $(COMMON_OBJ_DIR)), $(COMMON_OBJ_DIR)) + $(DO_COMMON_CC) + +$(GAME_SHARED_OBJ_DIR)/%.o : $(GAME_SHARED_SRC_DIR)/%.cpp $(filter-out $(wildcard $(GAME_SHARED_OBJ_DIR)), $(GAME_SHARED_OBJ_DIR)) + $(DO_COMMON_CC) + +$(PM_SHARED_OBJ_DIR)/%.o : $(PM_SHARED_SRC_DIR)/%.c $(filter-out $(wildcard $(PM_SHARED_OBJ_DIR)), $(PM_SHARED_OBJ_DIR)) + $(DO_PM_SHARED_CC) + + +clean: + -rm -rf $(DMC_OBJ_DIR) + -rm -f client.$(SHLIBEXT) diff --git a/linux/Makefile.dmcdll b/linux/Makefile.dmcdll new file mode 100644 index 0000000..92f942a --- /dev/null +++ b/linux/Makefile.dmcdll @@ -0,0 +1,114 @@ +# +# DMC game library Makefile for x86 Linux +# +# May 2001 by Leon Hartwig (hartwig@valvesoftware.com) +# +OS:=$(shell uname) + +DMCDLL_SRC_DIR=$(SOURCE_DIR)/dmc/dlls +PM_SRC_DIR=$(SOURCE_DIR)/dmc/pm_shared + +DMCDLL_OBJ_DIR=$(BUILD_OBJ_DIR)/dmcdll +PM_OBJ_DIR=$(DMCDLL_OBJ_DIR)/pm_shared +GAME_SHARED_OBJ_DIR=$(DMCDLL_OBJ_DIR)/game_shared + +#full optimization +CFLAGS=$(BASE_CFLAGS) $(ARCH_CFLAGS) + +DMCDLL_INCLUDEDIRS=-I$(ENGINE_SRC_DIR) -I$(COMMON_SRC_DIR) -I$(PM_SRC_DIR) -I$(GAME_SHARED_SRC_DIR) -I$(PUBLIC_SRC_DIR) +PM_INCLUDEDIRS=-I$(COMMON_SRC_DIR) -I$(PUBLIC_SRC_DIR) +GAME_SHARED_INCLUDEDIRS=-I$(DMCDLL_SRC_DIR) -I$(ENGINE_SRC_DIR) -I$(COMMON_SRC_DIR) -I$(PUBLIC_SRC_DIR) + +LDFLAGS= + +DO_DMCDLL_CC=$(CC) $(DMCDLL_INCLUDEDIRS) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< +DO_PM_CC=$(CC) $(PM_INCLUDEDIRS) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< +DO_GAME_SHARED_CC=$(CC) $(GAME_SHARED_INCLUDEDIRS) $(CFLAGS) $(SHLIBFLAGS) -o $@ -c $< + +############################################################################# + +DMCDLL_OBJS = \ + $(DMCDLL_OBJ_DIR)/animating.o \ + $(DMCDLL_OBJ_DIR)/animation.o \ + $(DMCDLL_OBJ_DIR)/bmodels.o \ + $(DMCDLL_OBJ_DIR)/buttons.o \ + $(DMCDLL_OBJ_DIR)/cbase.o \ + $(DMCDLL_OBJ_DIR)/client.o \ + $(DMCDLL_OBJ_DIR)/combat.o \ + $(DMCDLL_OBJ_DIR)/doors.o \ + $(DMCDLL_OBJ_DIR)/effects.o \ + $(DMCDLL_OBJ_DIR)/explode.o \ + $(DMCDLL_OBJ_DIR)/func_break.o \ + $(DMCDLL_OBJ_DIR)/func_tank.o \ + $(DMCDLL_OBJ_DIR)/game.o \ + $(DMCDLL_OBJ_DIR)/gamerules.o \ + $(DMCDLL_OBJ_DIR)/globals.o \ + $(DMCDLL_OBJ_DIR)/h_ai.o \ + $(DMCDLL_OBJ_DIR)/h_export.o \ + $(DMCDLL_OBJ_DIR)/lights.o \ + $(DMCDLL_OBJ_DIR)/maprules.o \ + $(DMCDLL_OBJ_DIR)/monsters.o \ + $(DMCDLL_OBJ_DIR)/monsterstate.o \ + $(DMCDLL_OBJ_DIR)/multiplay_gamerules.o \ + $(DMCDLL_OBJ_DIR)/nodes.o \ + $(DMCDLL_OBJ_DIR)/observer.o \ + $(DMCDLL_OBJ_DIR)/pathcorner.o \ + $(DMCDLL_OBJ_DIR)/plane.o \ + $(DMCDLL_OBJ_DIR)/plats.o \ + $(DMCDLL_OBJ_DIR)/player.o \ + $(DMCDLL_OBJ_DIR)/quake_gun.o \ + $(DMCDLL_OBJ_DIR)/quake_items.o \ + $(DMCDLL_OBJ_DIR)/quake_nail.o \ + $(DMCDLL_OBJ_DIR)/quake_player.o \ + $(DMCDLL_OBJ_DIR)/quake_rocket.o \ + $(DMCDLL_OBJ_DIR)/quake_weapons_all.o \ + $(DMCDLL_OBJ_DIR)/schedule.o \ + $(DMCDLL_OBJ_DIR)/singleplay_gamerules.o \ + $(DMCDLL_OBJ_DIR)/skill.o \ + $(DMCDLL_OBJ_DIR)/sound.o \ + $(DMCDLL_OBJ_DIR)/spectator.o \ + $(DMCDLL_OBJ_DIR)/subs.o \ + $(DMCDLL_OBJ_DIR)/teamplay_gamerules.o \ + $(DMCDLL_OBJ_DIR)/threewave_gamerules.o \ + $(DMCDLL_OBJ_DIR)/triggers.o \ + $(DMCDLL_OBJ_DIR)/util.o \ + $(DMCDLL_OBJ_DIR)/weapons.o \ + $(DMCDLL_OBJ_DIR)/world.o + +PM_OBJS = \ + $(PM_OBJ_DIR)/pm_shared.o \ + $(PM_OBJ_DIR)/pm_math.o \ + $(PM_OBJ_DIR)/pm_debug.o + +GAME_SHARED_OBJS = \ + $(GAME_SHARED_OBJ_DIR)/voice_gamemgr.o + +all: dirs dmc.$(SHLIBEXT) + +dirs: + -mkdir $(BUILD_OBJ_DIR) + -mkdir $(DMCDLL_OBJ_DIR) + -mkdir $(PM_OBJ_DIR) + -mkdir $(GAME_SHARED_OBJ_DIR) + +dmc.$(SHLIBEXT): $(DMCDLL_OBJS) $(PM_OBJS) $(GAME_SHARED_OBJS) + $(CLINK) $(SHLIBLDFLAGS) -o $(BUILD_DIR)/$@ $(DMCDLL_OBJS) $(PM_OBJS) $(GAME_SHARED_OBJS) $(LDFLAGS) $(CPP_LIB) + p4 edit ../../game/mod/dlls/$@ + cp $(BUILD_DIR)/$@ ../../game/mod/dlls + ./gendbg.sh ../../game/dmc/mod/dmc.$(SHLIBEXT) + +$(DMCDLL_OBJ_DIR)/%.o : $(DMCDLL_SRC_DIR)/%.cpp + $(DO_DMCDLL_CC) + +$(PM_OBJ_DIR)/%.o : $(PM_SRC_DIR)/%.c + $(DO_PM_CC) + +$(GAME_SHARED_OBJ_DIR)/%.o : $(GAME_SHARED_SRC_DIR)/%.cpp + $(DO_GAME_SHARED_CC) + +clean: + -rm -rf $(GAME_SHARED_OBJ_DIR) + -rm -rf $(PM_OBJ_DIR) + -rm -rf $(DMCDLL_OBJ_DIR) + -rm -f dmc_$(ARCH).$(SHLIBEXT) + diff --git a/linux/Makefile.hl_cdll b/linux/Makefile.hl_cdll new file mode 100644 index 0000000..90ca211 --- /dev/null +++ b/linux/Makefile.hl_cdll @@ -0,0 +1,195 @@ +# +# launcher Makefile for x86 Linux +# +# + +HL_SRC_DIR=$(SOURCE_DIR)/cl_dll +HL_SERVER_SRC_DIR=$(SOURCE_DIR)/dlls +GAME_SHARED_SRC_DIR=$(SOURCE_DIR)/game_shared +PM_SHARED_SRC_DIR=$(SOURCE_DIR)/pm_shared + +HL1_OBJ_DIR=$(BUILD_OBJ_DIR)/hl1_client +PUBLIC_OBJ_DIR=$(HL1_OBJ_DIR)/public +COMMON_OBJ_DIR=$(HL1_OBJ_DIR)/common +GAME_SHARED_OBJ_DIR=$(HL1_OBJ_DIR)/game_shared +HL1_SERVER_OBJ_DIR=$(HL1_OBJ_DIR)/server +PM_SHARED_OBJ_DIR=$(HL1_OBJ_DIR)/pm_shared + +CFLAGS=$(BASE_CFLAGS) $(ARCH_CFLAGS) -DCLIENT_DLL -DCLIENT_WEAPONS -DHL_DLL -I/usr/include/malloc -D_snwprintf=swprintf \ + -DDISABLE_JUMP_ORIGIN -DDISABLE_VEC_ORIGIN + +INCLUDEDIRS=-I$(HL_SRC_DIR) -I../dlls -I../tfc -I$(COMMON_SRC_DIR) -I$(PUBLIC_SRC_DIR) -I../pm_shared -I../engine -I../utils/vgui/include -I ../game_shared -I../external + +ifeq ($(OS),Darwin) +LDFLAGS=$(SHLIBLDFLAGS) $(CPP_LIB) -framework Carbon $(CFG)/vgui.dylib -L. -lSDL2-2.0.0 +else +LDFLAGS=$(SHLIBLDFLAGS) $(CPP_LIB) -L$(CFG) vgui.so -L. libSDL2-2.0.so.0 +endif + +DO_CC=$(CPLUS) $(INCLUDEDIRS) $(CFLAGS) -o $@ -c $< +DO_PUBLIC_CC=$(CPLUS) $(COMMON_INCLUDEDIRS) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< +DO_COMMON_CC=$(CPLUS) $(INCLUDEDIRS) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< +DO_PM_SHARED_CC=$(CC) $(INCLUDEDIRS) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< + +##################################################################### + +HL1_OBJS = \ + $(HL1_OBJ_DIR)/hud_spectator.o \ + $(HL1_OBJ_DIR)/ev_hldm.o \ + $(HL1_OBJ_DIR)/hl/hl_baseentity.o \ + $(HL1_OBJ_DIR)/hl/hl_events.o \ + $(HL1_OBJ_DIR)/hl/hl_objects.o \ + $(HL1_OBJ_DIR)/hl/hl_weapons.o \ + $(HL1_OBJ_DIR)/hud.o \ + $(HL1_OBJ_DIR)/inputw32.o \ + $(HL1_OBJ_DIR)/ammo.o \ + $(HL1_OBJ_DIR)/ammo_secondary.o \ + $(HL1_OBJ_DIR)/ammohistory.o \ + $(HL1_OBJ_DIR)/battery.o \ + $(HL1_OBJ_DIR)/cdll_int.o \ + $(HL1_OBJ_DIR)/com_weapons.o \ + $(HL1_OBJ_DIR)/death.o \ + $(HL1_OBJ_DIR)/demo.o \ + $(HL1_OBJ_DIR)/entity.o \ + $(HL1_OBJ_DIR)/ev_common.o \ + $(HL1_OBJ_DIR)/events.o \ + $(HL1_OBJ_DIR)/flashlight.o \ + $(HL1_OBJ_DIR)/GameStudioModelRenderer.o \ + $(HL1_OBJ_DIR)/geiger.o \ + $(HL1_OBJ_DIR)/health.o \ + $(HL1_OBJ_DIR)/hud_bench.o \ + $(HL1_OBJ_DIR)/hud_benchtrace.o \ + $(HL1_OBJ_DIR)/hud_msg.o \ + $(HL1_OBJ_DIR)/hud_redraw.o \ + $(HL1_OBJ_DIR)/hud_update.o \ + $(HL1_OBJ_DIR)/in_camera.o \ + $(HL1_OBJ_DIR)/input.o \ + $(HL1_OBJ_DIR)/interpolation.o \ + $(HL1_OBJ_DIR)/menu.o \ + $(HL1_OBJ_DIR)/message.o \ + $(HL1_OBJ_DIR)/saytext.o \ + $(HL1_OBJ_DIR)/status_icons.o \ + $(HL1_OBJ_DIR)/statusbar.o \ + $(HL1_OBJ_DIR)/studio_util.o \ + $(HL1_OBJ_DIR)/StudioModelRenderer.o \ + $(HL1_OBJ_DIR)/text_message.o \ + $(HL1_OBJ_DIR)/train.o \ + $(HL1_OBJ_DIR)/tri.o \ + $(HL1_OBJ_DIR)/util.o \ + $(HL1_OBJ_DIR)/view.o \ + $(HL1_OBJ_DIR)/vgui_int.o \ + $(HL1_OBJ_DIR)/vgui_ClassMenu.o \ + $(HL1_OBJ_DIR)/vgui_ConsolePanel.o \ + $(HL1_OBJ_DIR)/vgui_ControlConfigPanel.o \ + $(HL1_OBJ_DIR)/vgui_CustomObjects.o \ + $(HL1_OBJ_DIR)/vgui_MOTDWindow.o \ + $(HL1_OBJ_DIR)/vgui_SchemeManager.o \ + $(HL1_OBJ_DIR)/vgui_ScorePanel.o \ + $(HL1_OBJ_DIR)/vgui_ServerBrowser.o \ + $(HL1_OBJ_DIR)/vgui_TeamFortressViewport.o \ + $(HL1_OBJ_DIR)/vgui_SpectatorPanel.o \ + $(HL1_OBJ_DIR)/vgui_teammenu.o \ + $(HL1_OBJ_DIR)/voice_status.o \ + $(HL1_OBJ_DIR)/hud_servers.o \ + + +DLL_OBJS = \ + $(HL1_SERVER_OBJ_DIR)/crossbow.o \ + $(HL1_SERVER_OBJ_DIR)/crowbar.o \ + $(HL1_SERVER_OBJ_DIR)/egon.o \ + $(HL1_SERVER_OBJ_DIR)/gauss.o \ + $(HL1_SERVER_OBJ_DIR)/handgrenade.o \ + $(HL1_SERVER_OBJ_DIR)/hornetgun.o \ + $(HL1_SERVER_OBJ_DIR)/mp5.o \ + $(HL1_SERVER_OBJ_DIR)/python.o \ + $(HL1_SERVER_OBJ_DIR)/rpg.o \ + $(HL1_SERVER_OBJ_DIR)/satchel.o \ + $(HL1_SERVER_OBJ_DIR)/shotgun.o \ + $(HL1_SERVER_OBJ_DIR)/squeakgrenade.o \ + $(HL1_SERVER_OBJ_DIR)/tripmine.o \ + $(HL1_SERVER_OBJ_DIR)/wpn_shared/hl_wpn_glock.o \ + + +PUBLIC_OBJS = \ + $(PUBLIC_OBJ_DIR)/interface.o \ + +COMMON_OBJS = \ + $(COMMON_OBJ_DIR)/parsemsg.o \ + +GAME_SHARED_OBJS = \ + $(GAME_SHARED_OBJ_DIR)/vgui_checkbutton2.o \ + $(GAME_SHARED_OBJ_DIR)/vgui_grid.o \ + $(GAME_SHARED_OBJ_DIR)/vgui_helpers.o \ + $(GAME_SHARED_OBJ_DIR)/vgui_listbox.o \ + $(GAME_SHARED_OBJ_DIR)/vgui_loadtga.o \ + $(GAME_SHARED_OBJ_DIR)/vgui_scrollbar2.o \ + $(GAME_SHARED_OBJ_DIR)/vgui_slider2.o \ + $(GAME_SHARED_OBJ_DIR)/voice_banmgr.o \ + +PM_SHARED_OBJS = \ + $(PM_SHARED_OBJ_DIR)/pm_debug.o \ + $(PM_SHARED_OBJ_DIR)/pm_shared.o \ + $(PM_SHARED_OBJ_DIR)/pm_math.o \ + + + +all: client.$(SHLIBEXT) + +client.$(SHLIBEXT): $(HL1_OBJS) $(PUBLIC_OBJS) $(COMMON_OBJS) $(GAME_SHARED_OBJS) $(DLL_OBJS) $(PM_SHARED_OBJS) + $(CLINK) -o $(BUILD_DIR)/$@ $(HL1_OBJS) $(PUBLIC_OBJS) $(COMMON_OBJS) $(GAME_SHARED_OBJS) $(DLL_OBJS) $(PM_SHARED_OBJS) $(LDFLAGS) $(CPP_LIB) + p4 edit ../../game/mod/cl_dlls/$@ + cp $(BUILD_DIR)/$@ ../../game/mod/cl_dlls + ./gendbg.sh ../../game/mod/cl_dlls/client.$(SHLIBEXT) + +$(HL1_OBJ_DIR): + mkdir -p $(HL1_OBJ_DIR) + mkdir -p $(HL1_OBJ_DIR)/hl + mkdir -p $(HL1_OBJ_DIR)/dlls/wpn_shared + mkdir -p $(HL1_OBJ_DIR)/VGUI + +$(HL1_SERVER_OBJ_DIR): + mkdir -p $(HL1_SERVER_OBJ_DIR) + mkdir -p $(HL1_SERVER_OBJ_DIR)/wpn_shared + +$(PUBLIC_OBJ_DIR): + mkdir -p $(PUBLIC_OBJ_DIR) + +$(COMMON_OBJ_DIR): + mkdir -p $(COMMON_OBJ_DIR) + +$(GAME_SHARED_OBJ_DIR): + mkdir -p $(GAME_SHARED_OBJ_DIR) + +$(PM_SHARED_OBJ_DIR): + mkdir -p $(PM_SHARED_OBJ_DIR) + +$(HL1_OBJ_DIR)/%.o: $(HL_SRC_DIR)/%.cpp $(filter-out $(wildcard $(HL1_OBJ_DIR)), $(HL1_OBJ_DIR)) + $(DO_CC) + +$(HL1_SERVER_OBJ_DIR)/%.o: $(HL_SERVER_SRC_DIR)/%.cpp $(filter-out $(wildcard $(HL1_SERVER_OBJ_DIR)), $(HL1_SERVER_OBJ_DIR)) + $(DO_CC) + +$(HL1_OBJ_DIR)/%.o: $(HL_SRC_DIR)/hl/%.cpp $(filter-out $(wildcard $(HL1_OBJ_DIR)), $(HL1_OBJ_DIR)) + $(DO_CC) + +$(HL1_OBJ_DIR)/%.o: $(HL_SRC_DIR)/dlls/wpn_shared/%.cpp $(filter-out $(wildcard $(HL1_OBJ_DIR)), $(HL1_OBJ_DIR)) + $(DO_CC) + +$(HL1_OBJ_DIR)/%.o: $(HL_SRC_DIR)/VGUI/%.cpp $(filter-out $(wildcard $(HL1_OBJ_DIR)), $(HL1_OBJ_DIR)) + $(DO_CC) + +$(PUBLIC_OBJ_DIR)/%.o : $(PUBLIC_SRC_DIR)/%.cpp $(filter-out $(wildcard $(PUBLIC_OBJ_DIR)), $(PUBLIC_OBJ_DIR)) + $(DO_PUBLIC_CC) + +$(COMMON_OBJ_DIR)/%.o : $(COMMON_SRC_DIR)/%.cpp $(filter-out $(wildcard $(COMMON_OBJ_DIR)), $(COMMON_OBJ_DIR)) + $(DO_COMMON_CC) + +$(GAME_SHARED_OBJ_DIR)/%.o : $(GAME_SHARED_SRC_DIR)/%.cpp $(filter-out $(wildcard $(GAME_SHARED_OBJ_DIR)), $(GAME_SHARED_OBJ_DIR)) + $(DO_COMMON_CC) + +$(PM_SHARED_OBJ_DIR)/%.o : $(PM_SHARED_SRC_DIR)/%.c $(filter-out $(wildcard $(PM_SHARED_OBJ_DIR)), $(PM_SHARED_OBJ_DIR)) + $(DO_PM_SHARED_CC) + +clean: + -rm -rf $(HL1_OBJ_DIR) + -rm -f client.$(SHLIBEXT) diff --git a/linux/Makefile.hldll b/linux/Makefile.hldll new file mode 100644 index 0000000..95f7cac --- /dev/null +++ b/linux/Makefile.hldll @@ -0,0 +1,175 @@ +# +# HL game library Makefile for x86 Linux +# +# Feb 2001 by Leon Hartwig (hartwig@valvesoftware.com) +# + +HLDLL_SRC_DIR=$(SOURCE_DIR)/dlls +HLWPN_SRC_DIR=$(HLDLL_SRC_DIR)/wpn_shared + +HLDLL_OBJ_DIR=$(BUILD_OBJ_DIR)/hldll +HLWPN_OBJ_DIR=$(HLDLL_OBJ_DIR)/wpn_shared +PM_OBJ_DIR=$(HLDLL_OBJ_DIR)/pm_shared +GAME_SHARED_OBJ_DIR=$(HLDLL_OBJ_DIR)/game_shared + +#CFLAGS=$(BASE_CFLAGS) $(ARCH_CFLAGS) $(SHLIBCFLAGS) -DCLIENT_WEAPONS +CFLAGS=$(BASE_CFLAGS) $(ARCH_CFLAGS) -DCLIENT_WEAPONS +#-O3 -ffast-math -fno-strength-reduce + +HLDLL_INCLUDEDIRS=-I$(ENGINE_SRC_DIR) -I$(COMMON_SRC_DIR) -I$(PM_SRC_DIR) -I$(GAME_SHARED_SRC_DIR) -I$(PUBLIC_SRC_DIR) +HLWPN_INCLUDEDIRS=-I$(HLDLL_SRC_DIR) -I$(ENGINE_SRC_DIR) -I$(COMMON_SRC_DIR) -I$(PM_SRC_DIR) -I$(PUBLIC_SRC_DIR) +PM_INCLUDEDIRS=-I$(COMMON_SRC_DIR) -I$(PUBLIC_SRC_DIR) +GAME_SHARED_INCLUDEDIRS=-I$(HLDLL_SRC_DIR) -I$(ENGINE_SRC_DIR) -I$(COMMON_SRC_DIR) -I$(PM_SRC_DIR) -I$(PUBLIC_SRC_DIR) + +LDFLAGS= -lm -lstdc++ + +DO_HLDLL_CC=$(CC) $(HLDLL_INCLUDEDIRS) $(CFLAGS) -o $@ -c $< +DO_HLWPN_CC=$(CC) $(HLWPN_INCLUDEDIRS) $(CFLAGS) -o $@ -c $< +DO_PM_CC=$(CC) $(PM_INCLUDEDIRS) $(CFLAGS) -o $@ -c $< +DO_GAME_SHARED_CC=$(CC) $(GAME_SHARED_INCLUDEDIRS) $(CFLAGS) -o $@ -c $< + +##################################################################### + +HLDLL_OBJS = \ + $(HLDLL_OBJ_DIR)/aflock.o \ + $(HLDLL_OBJ_DIR)/agrunt.o \ + $(HLDLL_OBJ_DIR)/airtank.o \ + $(HLDLL_OBJ_DIR)/animating.o \ + $(HLDLL_OBJ_DIR)/animation.o \ + $(HLDLL_OBJ_DIR)/apache.o \ + $(HLDLL_OBJ_DIR)/barnacle.o \ + $(HLDLL_OBJ_DIR)/barney.o \ + $(HLDLL_OBJ_DIR)/bigmomma.o \ + $(HLDLL_OBJ_DIR)/bloater.o \ + $(HLDLL_OBJ_DIR)/bmodels.o \ + $(HLDLL_OBJ_DIR)/bullsquid.o \ + $(HLDLL_OBJ_DIR)/buttons.o \ + $(HLDLL_OBJ_DIR)/cbase.o \ + $(HLDLL_OBJ_DIR)/client.o \ + $(HLDLL_OBJ_DIR)/combat.o \ + $(HLDLL_OBJ_DIR)/controller.o \ + $(HLDLL_OBJ_DIR)/crossbow.o \ + $(HLDLL_OBJ_DIR)/crowbar.o \ + $(HLDLL_OBJ_DIR)/defaultai.o \ + $(HLDLL_OBJ_DIR)/doors.o \ + $(HLDLL_OBJ_DIR)/effects.o \ + $(HLDLL_OBJ_DIR)/egon.o \ + $(HLDLL_OBJ_DIR)/explode.o \ + $(HLDLL_OBJ_DIR)/flyingmonster.o \ + $(HLDLL_OBJ_DIR)/func_break.o \ + $(HLDLL_OBJ_DIR)/func_tank.o \ + $(HLDLL_OBJ_DIR)/game.o \ + $(HLDLL_OBJ_DIR)/gamerules.o \ + $(HLDLL_OBJ_DIR)/gargantua.o \ + $(HLDLL_OBJ_DIR)/gauss.o \ + $(HLDLL_OBJ_DIR)/genericmonster.o \ + $(HLDLL_OBJ_DIR)/ggrenade.o \ + $(HLDLL_OBJ_DIR)/globals.o \ + $(HLDLL_OBJ_DIR)/gman.o \ + $(HLDLL_OBJ_DIR)/h_ai.o \ + $(HLDLL_OBJ_DIR)/h_battery.o \ + $(HLDLL_OBJ_DIR)/h_cine.o \ + $(HLDLL_OBJ_DIR)/h_cycler.o \ + $(HLDLL_OBJ_DIR)/h_export.o \ + $(HLDLL_OBJ_DIR)/handgrenade.o \ + $(HLDLL_OBJ_DIR)/hassassin.o \ + $(HLDLL_OBJ_DIR)/headcrab.o \ + $(HLDLL_OBJ_DIR)/healthkit.o \ + $(HLDLL_OBJ_DIR)/hgrunt.o \ + $(HLDLL_OBJ_DIR)/hornet.o \ + $(HLDLL_OBJ_DIR)/hornetgun.o \ + $(HLDLL_OBJ_DIR)/houndeye.o \ + $(HLDLL_OBJ_DIR)/ichthyosaur.o \ + $(HLDLL_OBJ_DIR)/islave.o \ + $(HLDLL_OBJ_DIR)/items.o \ + $(HLDLL_OBJ_DIR)/leech.o \ + $(HLDLL_OBJ_DIR)/lights.o \ + $(HLDLL_OBJ_DIR)/maprules.o \ + $(HLDLL_OBJ_DIR)/monstermaker.o \ + $(HLDLL_OBJ_DIR)/monsters.o \ + $(HLDLL_OBJ_DIR)/monsterstate.o \ + $(HLDLL_OBJ_DIR)/mortar.o \ + $(HLDLL_OBJ_DIR)/mp5.o \ + $(HLDLL_OBJ_DIR)/nihilanth.o \ + $(HLDLL_OBJ_DIR)/nodes.o \ + $(HLDLL_OBJ_DIR)/observer.o \ + $(HLDLL_OBJ_DIR)/osprey.o \ + $(HLDLL_OBJ_DIR)/pathcorner.o \ + $(HLDLL_OBJ_DIR)/plane.o \ + $(HLDLL_OBJ_DIR)/plats.o \ + $(HLDLL_OBJ_DIR)/player.o \ + $(HLDLL_OBJ_DIR)/python.o \ + $(HLDLL_OBJ_DIR)/rat.o \ + $(HLDLL_OBJ_DIR)/roach.o \ + $(HLDLL_OBJ_DIR)/rpg.o \ + $(HLDLL_OBJ_DIR)/satchel.o \ + $(HLDLL_OBJ_DIR)/schedule.o \ + $(HLDLL_OBJ_DIR)/scientist.o \ + $(HLDLL_OBJ_DIR)/scripted.o \ + $(HLDLL_OBJ_DIR)/shotgun.o \ + $(HLDLL_OBJ_DIR)/skill.o \ + $(HLDLL_OBJ_DIR)/sound.o \ + $(HLDLL_OBJ_DIR)/soundent.o \ + $(HLDLL_OBJ_DIR)/spectator.o \ + $(HLDLL_OBJ_DIR)/squadmonster.o \ + $(HLDLL_OBJ_DIR)/squeakgrenade.o \ + $(HLDLL_OBJ_DIR)/subs.o \ + $(HLDLL_OBJ_DIR)/talkmonster.o \ + $(HLDLL_OBJ_DIR)/teamplay_gamerules.o \ + $(HLDLL_OBJ_DIR)/multiplay_gamerules.o \ + $(HLDLL_OBJ_DIR)/singleplay_gamerules.o \ + $(HLDLL_OBJ_DIR)/tempmonster.o \ + $(HLDLL_OBJ_DIR)/tentacle.o \ + $(HLDLL_OBJ_DIR)/triggers.o \ + $(HLDLL_OBJ_DIR)/tripmine.o \ + $(HLDLL_OBJ_DIR)/turret.o \ + $(HLDLL_OBJ_DIR)/util.o \ + $(HLDLL_OBJ_DIR)/weapons.o \ + $(HLDLL_OBJ_DIR)/world.o \ + $(HLDLL_OBJ_DIR)/xen.o \ + $(HLDLL_OBJ_DIR)/zombie.o + +HLWPN_OBJS = \ + $(HLWPN_OBJ_DIR)/hl_wpn_glock.o + +PM_OBJS = \ + $(PM_OBJ_DIR)/pm_shared.o \ + $(PM_OBJ_DIR)/pm_math.o \ + $(PM_OBJ_DIR)/pm_debug.o + +GAME_SHARED_OBJS = \ + $(GAME_SHARED_OBJ_DIR)/voice_gamemgr.o + +all: dirs hl.$(SHLIBEXT) + +dirs: + -mkdir $(BUILD_OBJ_DIR) + -mkdir $(HLDLL_OBJ_DIR) + -mkdir $(HLWPN_OBJ_DIR) + -mkdir $(PM_OBJ_DIR) + -mkdir $(GAME_SHARED_OBJ_DIR) + +hl.$(SHLIBEXT): $(HLDLL_OBJS) $(HLWPN_OBJS) $(PM_OBJS) $(GAME_SHARED_OBJS) + $(CC) $(LDFLAGS) $(SHLIBLDFLAGS) -o $(BUILD_DIR)/$@ $(HLDLL_OBJS) $(HLWPN_OBJS) $(PM_OBJS) $(GAME_SHARED_OBJS) + p4 edit ../../game/mod/dlls/hl.$(SHLIBEXT) + cp $(BUILD_DIR)/$@ ../../game/mod/dlls/hl.$(SHLIBEXT) + ./gendbg.sh ../../game/mod/dlls/hl.$(SHLIBEXT) + +$(HLWPN_OBJ_DIR)/%.o : $(HLWPN_SRC_DIR)/%.cpp + $(DO_HLWPN_CC) + +$(HLDLL_OBJ_DIR)/%.o : $(HLDLL_SRC_DIR)/%.cpp + $(DO_HLDLL_CC) + +$(PM_OBJ_DIR)/%.o : $(PM_SRC_DIR)/%.c + $(DO_PM_CC) + +$(GAME_SHARED_OBJ_DIR)/%.o : $(GAME_SHARED_SRC_DIR)/%.cpp + $(DO_GAME_SHARED_CC) + +clean: + -rm -rf $(GAME_SHARED_OBJ_DIR) + -rm -rf $(PM_OBJ_DIR) + -rm -rf $(HLWPN_OBJ_DIR) + -rm -rf $(HLDLL_OBJ_DIR) + -rm -f hl_$(ARCH).$(SHLIBEXT) diff --git a/linux/Makefile.ricochet_cdll b/linux/Makefile.ricochet_cdll new file mode 100644 index 0000000..80b7df3 --- /dev/null +++ b/linux/Makefile.ricochet_cdll @@ -0,0 +1,163 @@ +# +# ricochet client Makefile for x86 Linux +# +# + +RICOCHET_SRC_DIR=$(SOURCE_DIR)/ricochet/cl_dll +GAME_SHARED_SRC_DIR=$(SOURCE_DIR)/game_shared +PM_SHARED_SRC_DIR=$(RICOCHET_SRC_DIR)/../pm_shared + +RICOCHET_OBJ_DIR=$(BUILD_OBJ_DIR)/ricochet_client +PUBLIC_OBJ_DIR=$(RICOCHET_OBJ_DIR)/public +COMMON_OBJ_DIR=$(RICOCHET_OBJ_DIR)/common +GAME_SHARED_OBJ_DIR=$(RICOCHET_OBJ_DIR)/game_shared +PM_SHARED_OBJ_DIR=$(RICOCHET_OBJ_DIR)/pm_shared + +CFLAGS=$(BASE_CFLAGS) $(ARCH_CFLAGS) -DCLIENT_DLL -I/usr/include/malloc -D_snwprintf=swprintf \ + -DDISABLE_JUMP_ORIGIN -DDISABLE_VEC_ORIGIN -D_MAX_PATH=PATH_MAX + +INCLUDEDIRS=-I$(RICOCHET_SRC_DIR) -I$(RICOCHET_SRC_DIR)/../dlls -I../engine -I$(PUBLIC_SRC_DIR) -I$(COMMON_SRC_DIR) \ + -I../game_shared -I$(RICOCHET_SRC_DIR)/../pm_shared -I../utils/vgui/include -I../utils/common -I../external + +ifeq ($(OS),Darwin) +LDFLAGS=$(SHLIBLDFLAGS) $(CPP_LIB) -framework Carbon $(CFG)/vgui.dylib -L. -lSDL2-2.0.0 +else +LDFLAGS=$(SHLIBLDFLAGS) $(CPP_LIB) vgui.so -L. libSDL2-2.0.so.0 +endif + +DO_CC=$(CPLUS) $(INCLUDEDIRS) $(CFLAGS) -o $@ -c $< +DO_PUBLIC_CC=$(CPLUS) $(COMMON_INCLUDEDIRS) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< +DO_COMMON_CC=$(CPLUS) $(INCLUDEDIRS) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< +DO_PM_SHARED_CC=$(CC) $(INCLUDEDIRS) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< + +##################################################################### + +RICOCHET_OBJS = \ + $(RICOCHET_OBJ_DIR)/ev_common.o \ + $(RICOCHET_OBJ_DIR)/ev_hldm.o \ + $(RICOCHET_OBJ_DIR)/hl/hl_baseentity.o \ + $(RICOCHET_OBJ_DIR)/hl/hl_events.o \ + $(RICOCHET_OBJ_DIR)/hl/hl_objects.o \ + $(RICOCHET_OBJ_DIR)/hl/hl_weapons.o \ + $(RICOCHET_OBJ_DIR)/disc_weapon_disc.o \ + $(RICOCHET_OBJ_DIR)/Ricochet_JumpPads.o \ + $(RICOCHET_OBJ_DIR)/ammo.o \ + $(RICOCHET_OBJ_DIR)/ammo_secondary.o \ + $(RICOCHET_OBJ_DIR)/ammohistory.o \ + $(RICOCHET_OBJ_DIR)/battery.o \ + $(RICOCHET_OBJ_DIR)/cdll_int.o \ + $(RICOCHET_OBJ_DIR)/com_weapons.o \ + $(RICOCHET_OBJ_DIR)/death.o \ + $(RICOCHET_OBJ_DIR)/demo.o \ + $(RICOCHET_OBJ_DIR)/entity.o \ + $(RICOCHET_OBJ_DIR)/events.o \ + $(RICOCHET_OBJ_DIR)/flashlight.o \ + $(RICOCHET_OBJ_DIR)/GameStudioModelRenderer.o \ + $(RICOCHET_OBJ_DIR)/geiger.o \ + $(RICOCHET_OBJ_DIR)/health.o \ + $(RICOCHET_OBJ_DIR)/hud.o \ + $(RICOCHET_OBJ_DIR)/hud_msg.o \ + $(RICOCHET_OBJ_DIR)/hud_redraw.o \ + $(RICOCHET_OBJ_DIR)/hud_servers.o \ + $(RICOCHET_OBJ_DIR)/hud_update.o \ + $(RICOCHET_OBJ_DIR)/in_camera.o \ + $(RICOCHET_OBJ_DIR)/input.o \ + $(RICOCHET_OBJ_DIR)/inputw32.o \ + $(RICOCHET_OBJ_DIR)/menu.o \ + $(RICOCHET_OBJ_DIR)/message.o \ + $(RICOCHET_OBJ_DIR)/saytext.o \ + $(RICOCHET_OBJ_DIR)/status_icons.o \ + $(RICOCHET_OBJ_DIR)/statusbar.o \ + $(RICOCHET_OBJ_DIR)/StudioModelRenderer.o \ + $(RICOCHET_OBJ_DIR)/text_message.o \ + $(RICOCHET_OBJ_DIR)/train.o \ + $(RICOCHET_OBJ_DIR)/tri.o \ + $(RICOCHET_OBJ_DIR)/util.o \ + $(RICOCHET_OBJ_DIR)/view.o \ + $(RICOCHET_OBJ_DIR)/vgui_int.o \ + $(RICOCHET_OBJ_DIR)/vgui_ConsolePanel.o \ + $(RICOCHET_OBJ_DIR)/vgui_ControlConfigPanel.o \ + $(RICOCHET_OBJ_DIR)/vgui_CustomObjects.o \ + $(RICOCHET_OBJ_DIR)/vgui_discobjects.o \ + $(RICOCHET_OBJ_DIR)/vgui_MOTDWindow.o \ + $(RICOCHET_OBJ_DIR)/vgui_ScorePanel.o \ + $(RICOCHET_OBJ_DIR)/vgui_ServerBrowser.o \ + $(RICOCHET_OBJ_DIR)/vgui_TeamFortressViewport.o \ + $(RICOCHET_OBJ_DIR)/vgui_SchemeManager.o \ + $(RICOCHET_OBJ_DIR)/studio_util.o \ + $(RICOCHET_OBJ_DIR)/voice_status.o \ + +PUBLIC_OBJS = \ + $(PUBLIC_OBJ_DIR)/interface.o \ + +COMMON_OBJS = \ + $(COMMON_OBJ_DIR)/parsemsg.o \ + +GAME_SHARED_OBJS = \ + $(GAME_SHARED_OBJ_DIR)/voice_banmgr.o \ + $(GAME_SHARED_OBJ_DIR)/vgui_checkbutton2.o \ + $(GAME_SHARED_OBJ_DIR)/vgui_grid.o \ + $(GAME_SHARED_OBJ_DIR)/vgui_helpers.o \ + $(GAME_SHARED_OBJ_DIR)/vgui_listbox.o \ + $(GAME_SHARED_OBJ_DIR)/vgui_loadtga.o \ + $(GAME_SHARED_OBJ_DIR)/vgui_scrollbar2.o \ + $(GAME_SHARED_OBJ_DIR)/vgui_slider2.o \ +# $(GAME_SHARED_OBJ_DIR)/voice_status.o \ + +PM_SHARED_OBJS = \ + $(PM_SHARED_OBJ_DIR)/pm_debug.o \ + $(PM_SHARED_OBJ_DIR)/pm_shared.o \ + $(PM_SHARED_OBJ_DIR)/pm_math.o \ + + +all: client_ricochet.$(SHLIBEXT) + +client_ricochet.$(SHLIBEXT): $(RICOCHET_OBJS) $(PUBLIC_OBJS) $(COMMON_OBJS) $(GAME_SHARED_OBJS) $(PM_SHARED_OBJS) + $(CLINK) -o $(BUILD_DIR)/$@ $(RICOCHET_OBJS) $(PUBLIC_OBJS) $(COMMON_OBJS) $(GAME_SHARED_OBJS) $(PM_SHARED_OBJS) $(LDFLAGS) $(CPP_LIB) + p4 edit ../../game/mod/cl_dlls/client.$(SHLIBEXT) + cp $(BUILD_DIR)/$@ ../../game/mod/cl_dlls/client.$(SHLIBEXT) + ./gendbg.sh ../../game/mod/cl_dlls/client.$(SHLIBEXT) + +$(RICOCHET_OBJ_DIR): + mkdir -p $(RICOCHET_OBJ_DIR) + mkdir -p $(RICOCHET_OBJ_DIR)/dlls + mkdir -p $(RICOCHET_OBJ_DIR)/dlls/wpn_shared + mkdir -p $(RICOCHET_OBJ_DIR)/hl + +$(PUBLIC_OBJ_DIR): + mkdir -p $(PUBLIC_OBJ_DIR) + +$(COMMON_OBJ_DIR): + mkdir -p $(COMMON_OBJ_DIR) + +$(GAME_SHARED_OBJ_DIR): + mkdir -p $(GAME_SHARED_OBJ_DIR) + +$(PM_SHARED_OBJ_DIR): + mkdir -p $(PM_SHARED_OBJ_DIR) + +$(RICOCHET_OBJ_DIR)/%.o: $(RICOCHET_SRC_DIR)/%.cpp $(filter-out $(wildcard $(RICOCHET_OBJ_DIR)), $(RICOCHET_OBJ_DIR)) + $(DO_CC) + +$(RICOCHET_OBJ_DIR)/%.o: $(RICOCHET_SRC_DIR)/hl/%.cpp $(filter-out $(wildcard $(RICOCHET_OBJ_DIR)), $(RICOCHET_OBJ_DIR)) + $(DO_CC) + +$(RICOCHET_OBJ_DIR)/%.o: $(RICOCHET_SRC_DIR)/../dlls/wpn_shared/%.cpp $(filter-out $(wildcard $(RICOCHET_OBJ_DIR)), $(RICOCHET_OBJ_DIR)) + $(DO_CC) + +$(PUBLIC_OBJ_DIR)/%.o : $(PUBLIC_SRC_DIR)/%.cpp $(filter-out $(wildcard $(PUBLIC_OBJ_DIR)), $(PUBLIC_OBJ_DIR)) + $(DO_PUBLIC_CC) + +$(COMMON_OBJ_DIR)/%.o : $(COMMON_SRC_DIR)/%.cpp $(filter-out $(wildcard $(COMMON_OBJ_DIR)), $(COMMON_OBJ_DIR)) + $(DO_COMMON_CC) + +$(GAME_SHARED_OBJ_DIR)/%.o : $(GAME_SHARED_SRC_DIR)/%.cpp $(filter-out $(wildcard $(GAME_SHARED_OBJ_DIR)), $(GAME_SHARED_OBJ_DIR)) + $(DO_COMMON_CC) + +$(PM_SHARED_OBJ_DIR)/%.o : $(PM_SHARED_SRC_DIR)/%.c $(filter-out $(wildcard $(PM_SHARED_OBJ_DIR)), $(PM_SHARED_OBJ_DIR)) + $(DO_PM_SHARED_CC) + + +clean: + -rm -rf $(RICOCHET_OBJ_DIR) + -rm -f client_ricochet.$(SHLIBEXT) diff --git a/linux/Makefile.ricochetdll b/linux/Makefile.ricochetdll new file mode 100644 index 0000000..4a422c6 --- /dev/null +++ b/linux/Makefile.ricochetdll @@ -0,0 +1,128 @@ +# +# Ricochet game library Makefile for x86 Linux +# +# June 2001 by Leon Hartwig (hartwig@valvesoftware.com) +# +OS:=$(shell uname) + +RICOCHETDLL_SRC_DIR=$(SOURCE_DIR)/ricochet/dlls +RICOCHETWPN_SRC_DIR=$(RICOCHETDLL_SRC_DIR)/wpn_shared +PM_SRC_DIR=$(SOURCE_DIR)/ricochet/pm_shared + +RICOCHETDLL_OBJ_DIR=$(BUILD_OBJ_DIR)/ricochetdll +RICOCHETWPN_OBJ_DIR=$(RICOCHETDLL_OBJ_DIR)/wpn_shared +PM_OBJ_DIR=$(RICOCHETDLL_OBJ_DIR)/pm_shared +GAME_SHARED_OBJ_DIR=$(RICOCHETDLL_OBJ_DIR)/game_shared + +#CFLAGS=$(BASE_CFLAGS) -g +CFLAGS=$(BASE_CFLAGS) -O3 -ffast-math -fno-strength-reduce + +RICOCHETDLL_INCLUDEDIRS=-I$(ENGINE_SRC_DIR) -I$(COMMON_SRC_DIR) -I$(PM_SRC_DIR) -I$(GAME_SHARED_SRC_DIR) -I$(PUBLIC_SRC_DIR) +RICOCHETWPN_INCLUDEDIRS=-I$(RICOCHETDLL_SRC_DIR) -I$(ENGINE_SRC_DIR) -I$(COMMON_SRC_DIR) -I$(PM_SRC_DIR) -I$(PUBLIC_SRC_DIR) +PM_INCLUDEDIRS=-I$(COMMON_SRC_DIR) -I$(PUBLIC_SRC_DIR) +GAME_SHARED_INCLUDEDIRS=-I$(RICOCHETDLL_SRC_DIR) -I$(ENGINE_SRC_DIR) -I$(COMMON_SRC_DIR) -I$(PUBLIC_SRC_DIR) + +LDFLAGS= + +DO_RICOCHETDLL_CC=$(CC) $(RICOCHETDLL_INCLUDEDIRS) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< +DO_RICOCHETWPN_CC=$(CC) $(RICOCHETWPN_INCLUDEDIRS) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< +DO_PM_CC=$(CC) $(PM_INCLUDEDIRS) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< +DO_GAME_SHARED_CC=$(CC) $(GAME_SHARED_INCLUDEDIRS) $(CFLAGS) $(SHLIBFLAGS) -o $@ -c $< + +##################################################################### + +RICOCHETDLL_OBJS = \ + $(RICOCHETDLL_OBJ_DIR)/airtank.o \ + $(RICOCHETDLL_OBJ_DIR)/animating.o \ + $(RICOCHETDLL_OBJ_DIR)/animation.o \ + $(RICOCHETDLL_OBJ_DIR)/bmodels.o \ + $(RICOCHETDLL_OBJ_DIR)/buttons.o \ + $(RICOCHETDLL_OBJ_DIR)/cbase.o \ + $(RICOCHETDLL_OBJ_DIR)/client.o \ + $(RICOCHETDLL_OBJ_DIR)/combat.o \ + $(RICOCHETDLL_OBJ_DIR)/disc_arena.o \ + $(RICOCHETDLL_OBJ_DIR)/disc_powerups.o \ + $(RICOCHETDLL_OBJ_DIR)/doors.o \ + $(RICOCHETDLL_OBJ_DIR)/effects.o \ + $(RICOCHETDLL_OBJ_DIR)/explode.o \ + $(RICOCHETDLL_OBJ_DIR)/func_break.o \ + $(RICOCHETDLL_OBJ_DIR)/func_tank.o \ + $(RICOCHETDLL_OBJ_DIR)/game.o \ + $(RICOCHETDLL_OBJ_DIR)/gamerules.o \ + $(RICOCHETDLL_OBJ_DIR)/ggrenade.o \ + $(RICOCHETDLL_OBJ_DIR)/globals.o \ + $(RICOCHETDLL_OBJ_DIR)/h_ai.o \ + $(RICOCHETDLL_OBJ_DIR)/h_battery.o \ + $(RICOCHETDLL_OBJ_DIR)/h_cycler.o \ + $(RICOCHETDLL_OBJ_DIR)/h_export.o \ + $(RICOCHETDLL_OBJ_DIR)/healthkit.o \ + $(RICOCHETDLL_OBJ_DIR)/items.o \ + $(RICOCHETDLL_OBJ_DIR)/lights.o \ + $(RICOCHETDLL_OBJ_DIR)/maprules.o \ + $(RICOCHETDLL_OBJ_DIR)/mortar.o \ + $(RICOCHETDLL_OBJ_DIR)/mpstubb.o \ + $(RICOCHETDLL_OBJ_DIR)/multiplay_gamerules.o \ + $(RICOCHETDLL_OBJ_DIR)/observer.o \ + $(RICOCHETDLL_OBJ_DIR)/pathcorner.o \ + $(RICOCHETDLL_OBJ_DIR)/plane.o \ + $(RICOCHETDLL_OBJ_DIR)/plats.o \ + $(RICOCHETDLL_OBJ_DIR)/player.o \ + $(RICOCHETDLL_OBJ_DIR)/singleplay_gamerules.o \ + $(RICOCHETDLL_OBJ_DIR)/skill.o \ + $(RICOCHETDLL_OBJ_DIR)/sound.o \ + $(RICOCHETDLL_OBJ_DIR)/soundent.o \ + $(RICOCHETDLL_OBJ_DIR)/spectator.o \ + $(RICOCHETDLL_OBJ_DIR)/subs.o \ + $(RICOCHETDLL_OBJ_DIR)/teamplay_gamerules.o \ + $(RICOCHETDLL_OBJ_DIR)/triggers.o \ + $(RICOCHETDLL_OBJ_DIR)/util.o \ + $(RICOCHETDLL_OBJ_DIR)/weapons.o \ + $(RICOCHETDLL_OBJ_DIR)/world.o \ + $(RICOCHETDLL_OBJ_DIR)/xen.o + +RICOCHETWPN_OBJS = \ + $(RICOCHETWPN_OBJ_DIR)/disc_weapon_disc.o + +PM_OBJS = \ + $(PM_OBJ_DIR)/pm_shared.o \ + $(PM_OBJ_DIR)/pm_math.o \ + $(PM_OBJ_DIR)/pm_debug.o + +GAME_SHARED_OBJS = \ + $(GAME_SHARED_OBJ_DIR)/voice_gamemgr.o + +all: dirs ricochet.$(SHLIBEXT) + +dirs: + -mkdir $(BUILD_OBJ_DIR) + -mkdir $(RICOCHETDLL_OBJ_DIR) + -mkdir $(RICOCHETWPN_OBJ_DIR) + -mkdir $(PM_OBJ_DIR) + -mkdir $(GAME_SHARED_OBJ_DIR) + +ricochet.$(SHLIBEXT): $(RICOCHETDLL_OBJS) $(RICOCHETWPN_OBJS) $(PM_OBJS) $(GAME_SHARED_OBJS) + $(CLINK) $(SHLIBLDFLAGS) -o $(BUILD_DIR)/$@ $(RICOCHETDLL_OBJS) $(RICOCHETWPN_OBJS) $(PM_OBJS) $(GAME_SHARED_OBJS) $(LDFLAGS) $(CPP_LIB) + p4 edit ../../game/mod/dlls/$@ + cp $(BUILD_DIR)/$@ ../../game/mod/dlls + ./gendbg.sh ../../game/mod/dlls/$@ + +$(RICOCHETWPN_OBJ_DIR)/%.o : $(RICOCHETWPN_SRC_DIR)/%.cpp + $(DO_RICOCHETWPN_CC) + +$(RICOCHETDLL_OBJ_DIR)/%.o : $(RICOCHETDLL_SRC_DIR)/%.cpp + $(DO_RICOCHETDLL_CC) + +$(PM_OBJ_DIR)/%.o : $(PM_SRC_DIR)/%.c + $(DO_PM_CC) + +$(GAME_SHARED_OBJ_DIR)/%.o : $(GAME_SHARED_SRC_DIR)/%.cpp + $(DO_GAME_SHARED_CC) + +clean: + -rm -rf $(GAME_SHARED_OBJ_DIR) + -rm -rf $(PM_OBJ_DIR) + -rm -rf $(RICOCHETWPN_OBJ_DIR) + -rm -rf $(RICOCHETDLL_OBJ_DIR) + -rm -f ricochet_$(ARCH).$(SHLIBEXT) + -rm -f ricochet.$(SHLIBEXT) + diff --git a/linux/gendbg.sh b/linux/gendbg.sh new file mode 100755 index 0000000..9d0bf78 --- /dev/null +++ b/linux/gendbg.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +UNAME=`uname` +if [ "$UNAME" == "Darwin" ]; then + p4 edit $1.dSYM/... + dsymutil $1 + p4 revert -a $1.dSYM/... + exit 0; +fi + +OBJCOPY=/valve/bin/objcopy + +function usage { + echo "$0 /path/to/input/file [-o /path/to/output/file ]" + echo "" +} + +if [ $# == 0 ]; then + usage + exit 2 +fi + +if [ $(basename $1) == $1 ]; then + INFILEDIR=$PWD +else + INFILEDIR=$(cd ${1%/*} && echo $PWD) +fi +INFILE=$(basename $1) + +OUTFILEDIR=$INFILEDIR +OUTFILE=$INFILE.dbg + +while getopts "o:" opt; do + case $opt in + o) + OUTFILEDIR=$(cd ${OPTARG%/*} && echo $PWD) + OUTFILE=$(basename $OPTARG) + ;; + esac +done + +if [ "$OUTFILEDIR" != "$INFILEDIR" ]; then + INFILE=${INFILEDIR}/${INFILE} + OUTFILE=${OUTFILEDIR}/${OUTFILE} +fi + +pushd "$INFILEDIR" +p4 edit "$OUTFILE" +$OBJCOPY "$INFILE" "$OUTFILE" +$OBJCOPY --add-gnu-debuglink="$OUTFILE" "$INFILE" +p4 revert -a "$OUTFILE" +popd + diff --git a/linux/libSDL2-2.0.0.dylib b/linux/libSDL2-2.0.0.dylib new file mode 100644 index 0000000000000000000000000000000000000000..61b5c81e800812b6fdf0aa1964068122b1fea9cc GIT binary patch literal 1408108 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 z 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�sBh^WBMR=Akq%;&Xw8tiL}Z~BSM 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 $8S$mS}zv!;-n9t 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

k^594k*?j5KSF7E5q^7VPPNTxyJf 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 gh#l^oe^k> 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 B4blJr literal 0 HcmV?d00001 diff --git a/linux/libSDL2-2.0.0.dylib.dSYM/Contents/Info.plist b/linux/libSDL2-2.0.0.dylib.dSYM/Contents/Info.plist new file mode 100644 index 0000000..6550add --- /dev/null +++ b/linux/libSDL2-2.0.0.dylib.dSYM/Contents/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleIdentifier + com.apple.xcode.dsym.libSDL2-2.0.0.dylib + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + dSYM + CFBundleSignature + ???? + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/linux/libSDL2-2.0.0.dylib.dSYM/Contents/Resources/DWARF/libsdl2-2.0.0.dylib b/linux/libSDL2-2.0.0.dylib.dSYM/Contents/Resources/DWARF/libsdl2-2.0.0.dylib new file mode 100644 index 0000000000000000000000000000000000000000..1e9d51f825c1fabc490b316a908ab10a72e10cb6 GIT binary patch literal 1149526 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Ța@iDTOVjbZyWYaUbg?PfbHvd!zKFDP--9(k}r@n1F+U9%Mj* zNQA6paU;gHBpglz-i#yq8ICFZndFsT@1?l&b^>;|e0P<+UMQ-0?@%L`zC2ZF<4C6JB<~ zw;5uE`@2Np*K9tQ4aP&#_`5~ndmOLS+(GE>c=8)ZzLrj(y3MQ^@;X)hi%u!#7_tIv zZ>v1okd{Wexp7hnI7B(eSMidS31;_X~TIz8}B6z}Lt?~r3` z!|_V_pB89srE7duNl5)IHHdsh;JCEq3sC=%zxvxne^cV$O02Ha;C`Qc(XGGYSZ+e` z$5y6g=>EXvXi?oCMRI>&)r^F0O9<;tfVGJLh*7&9e(#HzvT)M=tkPS!NIdgl;&dC` zFL3xLv1ix+Q}izeM_4Xq)_2+g_)?PAwE70YmxBv>i+Hk2@s%XSL+Vxe{Z|uWuC!k5{z$Y5vHAO8BV1Q0(^+@_R8qDRjlY z1{TMhV8VVy3-?b9dks+mE^@M;m@rwGe>ov^X)Q_8YIA9woRGDWM94nLt4d;7=$oz< z!~VmPBrZ;pSeQRI32{6xNqJJ;AA!k6F}W$^_h=w{Z%K!YD~k*|Ep{P}!Obay`tlu# zdP`{iU3+W27FGVOcG*VtxJscZoaqiEZcB17ZSZeMdOP@zJ&}sgAktL{-^s3CJeAZ7 zek&w@$4MN=AzJ;-?4bMq2-V|-?bm8Mz3bm#)S~$PxpK7hluGX!{-_kD=Pfgdp1p?c zMYAQ((A_+MjTu3$!HRHl?uezzpV>0ngGwU&jhV0KQ( z)Z~}IbGCTynee=Ym~T0rCezrWOjU0|d$MQ`^v@wLYfi58PBrN(FOu&8Nsm>z+)$$D z{3{8WJF56m(r*;56VXJiNjWOwIA8`RWVgz%)tKjrwVFSU1C05cO_2t_%T-1g89l0$DAA!-i;VGoZxTROo3 zr@>%XF*q>rRhok!q?R2duLFXHBY;Tj>ow&(gU~Ucd3(n6jwO*xzV{rD62jgs}cWk=^8vwY(zxfE!@< ztUS3~GYVEuICgKz>(Yd-)v>!YVK+N}a6(vbU;FUugvM@W{v`Y#;?MOP%l(tgf3n1W zi*!B})ARVTBaxhMVp~l+*WcCYzOHfv6zAHBwZ^RhYwGA7PxInS;iDY%{Qs@KeV%_56%TD>NjML4;BPZ1AG zQshT`1b0Z_#QMVohbM4C{;do7vJ6S<=gNOkBEb0i69kV-;9k;76UTUG%a{LF5aa8M zJMu@oyh&CxftAgz+? z?Cw3&w>qnF>ynx?+bQ$0M42=5UnTMC?-HXY60&Laj|e{LZ|_RD{^?S9NdD||__B)B z@>k<%!%_c|2wqDymXGn9CjURmziZ&KIw9XX^|z6yXW(kk7<46djci~eOA&ba##_e4 zt6WbaxuF2Vv91Zu$9m<*Mm+jY$tTdHl{23(=JV%*pHRp@qV5x zjInG~4sE2@JuaPb+f9_?9QU?Lo4h7rt?u{T6U1HVgoT@EpWx)AujqVgPpu1nI}VxR zM=>+;OPEpoK4z!*Gt9C$Lp|&qS7XL;Z_M&|E6i*>3bP{K2QwG{1hY$gBxYs22s0m_ ziCKs*!K{j}$E=Q5Vb;WtVAjSjV%Ej$G3(>MV>ZO$4{6776=t`%7iRZ(2xgCXC(Oop zZ_K9n$Cy3i*_h4o&oO(&zrgGrFT-q!ufyyU--)?d{4nO`@e7!Jxf_K$~R4v6=_92if+92C#N92}p9IV8RUbE|k2=GO7knA^nfV-AhK#oRV7?8A4; zaUaa>;$fJ>;&GVU$5SwOh>ybDF+LGr6kMi*=SCpNaKLKfV_0Y(IVw>l{CR z6Kkm-e~Q)a#}W76&-LR5tn>W1Ki2tvJQC{yKi(JXLO-5{b&($*gLSbV{{m~7AFsf= z#E);sy3~&!#k$OoU&H#9AAgK>IX_JKBd)6ZaW||Lu*SN=k4Io#>Bk3PUFF9!v99*x zpJUzP$7f^R>c>}N-R8#+VExvQ-@>}xkH5nDogddvj6I=jP;-&?}qh| zAO9HZ_kKJN>tQmD^@tx|jrAxzvDW$VAF&?u@5k?Bz2L{e!L$xP?t%4^AO8UBWwMF&3S6*W z_2c8QUi0Jgv0nG%8?pXK0bspF0bsrD$A80m$B%tF`FH)e8tXkj-W+SaAMb$mK0L8L z@Z+Cgag^4Iby5(YhP5P!FULALh<}H5N)SJRb!rg5i*;HMe~oo|5a)hOtqbCwSicD3 z?Xb=a;vZt26~xo9ei_8aVx7%iG}bvmd=1vpAYP5t9>jmZIyZ>_igjKP$3LOg1#t`3 z1wlL#>%t(OgmqC6w_#lz#AjhG3*zNimjv-`SeFLzLs*vu@rziCGhXL=ZJhZKbV8i@ zsYaV7Og|om8N~ZzhVdNCOnf$G6yJ>5$?Lpjv%DSwoDi?aUWC~xz8JGCUWM5?eg!j*zs4+&o2O{l8i`pE{{%A^ACK83UXEGmWwmXPzI+r8 zX-4Zk;J+P?U*O1&!jZ{pO`54V9{02Fz&|%+n%lW6a|e*yq|go^z3c%N=@&99j_yTQ zMxnQsoY1Qo7*04 z_cW9{=Ir7221?d4cTc=u8p??$8|T{fw78sjvT?o@q1sd|+uAnHho!`92eWa$3s9La zV>_6QbM0Un+%Di&jy<4uvJ&E+xDJJt;|Y+YAMcz%-bWWWT_0P=oTmI7VD1UaJ6Be|!-uQPe#j@Z#KOd)T=oB))t3T5;B6rjWsMDb?=KO`H1 zJx3FS+b&+LPvJRs@#5nYF4e`0oSuHt?nm5ix%`|^gXbz+Ubqq& zBvGgd={IudKz*57#=Zz!ekhI;dL>_GO~prFW>wsSqmH0_t{3JQ5pHGq*b+ENkUB;I z3VRRlJ7=;e}4^>XQ^db!N>a=o7@;#a1Zz2IPa zxqMT-EZhUnfj6tQd7%6ZK=T7sA{5b4_Us1E1ZqZbVHSW<(HOnVn zJg!u%aeU%MjawBYd3)SyTpuco<@^b2NWVfDgHKvRx-x~_cT+>^naEpl_~xE>tpYm~ z@#3a}zqF|$UN*t+A&S+3S4{9?K3+A!i}`rX1TVIn*EbdXkDChqhHEM>Bbp?tSchm2 z;;88m?fp$f`(RViJ~Yvadij%yR@BR%T?L$Obr%S23%f+f^=Yx>zZE;p)qCB(5|oUM2C3IBHt7v145l8w^yKmSTnR z!Q4tMCa<5M;Q~QsYNGiN9Q!Rr0W#onzojTa1kU;`#fs1g%!PwuO(+AaMedV`Qn7bG zni1S9I%G;-dM(P7TxuxFq)P(G)HRh!FTEm@vTe0PFTEmDQAWMQnkCJ5Nz;;gfHWI7 zO;fL>mg+&TrIzYJuceY^u?Fh3RMIR~LA{n*nxI=afwo+XLcK$;W_8wKW!;Xls!Y+Y?M;MQx1(E3s1&))jrWWJ#mM zI%>fLXiC#K09>k_1HsbHpWtCpC&J?{cy#XIamROf+-W?xQkIZbJb~j8;xu%`S@WGZ zYb{Pou@8L!Ox_-dlScL+n7nByK9yNc{1BKcH5!Wz{~oNtcuJB~&lgC~4_^Svm3<9I zred@HtSo>f3V5D~r3!cfi~`n^@O#9GUj$15e+DRO`effASlN?(+v2b%`$po>lYOBa z-zb&Q0~n~MF%os;`={SYZi-8;nD0X^xt5;9+5k!n^E&jL65E$T@P9}e#Aw`k5js+sU3z@DQE@TP3&E6iG0(?>OY8B^z~s5u zEBqW>TaxER;JT7L9|x{SrXMDvB)_&@ao#{?+=$k%qc~25v{bayz@?&{4lWh#3^1Y{ z4i_Es#J>O|+EFP!6UoBGZMqh5XKZulJV=7|0w%q6PT zrIT<}G!J5WjH6G7@E1T_D*T1uQsFNGmkNI|7~!vl%Vnq{UIs>Z31dCsC18Zr<4-2+ zrQl|S(eKhI>56H{aH||0GW-UbQW$P|#Sj}gPBx-aV4t13_H{8zKLTBFh z!*f^%&o>-TU2W5BKYr6;y+Wl~Zv2+R>xiY9W&F0on!su97r*21Q-o{o7QgGT>W`*G z@p}%xK)B{1@p^|}18d?CzwhwdV2$PR2M&J#wsG`Bhd%;qjEn!|@ZZ52f8swo{54oZ zNcn#+zdJk{tP+hsb6Az|Na1e{^LPY6MU&OCoa=m)S|+Q7Vr0B>I(g{aQhpf^)8WdNyOcC_H=G6QBJ;rnzM_bK zVmb0Q4llPET`gQlhEocvocCwE$FoVl@H+QVLdk`&;SeB}X`k)K^i2zS+jj`N;+V$G z`*1HD_8kJdhweKBwp-kH2s3c#JB0LOgJZ$h@)s_~xS79noqjHEdt_r74lU)9XFI~* zfOUU6ybXtzf6?CVIs?0bfxPI>c0DJiGCEljpY-^Uw&Uiz()z8C;r&Uf|L^^Z}RVVKXp! zn3>AZ4@@5Bf-O&5fXTzS6B4{7m^>Vt()S0Ghhm-wff*8uc^(YzSCZ!;;4Mq?ycKu= zdH$R{E345z$+L2rRFfzhx|Gvu^CI{J2uFA5M4z3N>SSMVsZJ(&h(g$bt3wJken53v_2ptXL?S@G^-B?$(g>u{VjSA z^#LI{E7sr}!PMAd&R2pd-D1vf0uLz3`OV-#1xb%g z#UIN!E5JFdrI_A0x<}7%xgzDU7nq83eTshwW`=iriuVRH!+S8r`+!*`J(l9}U@F?H zDc%=MMf)_x`+=!gUx4?Oc>9B!{T6?e?;R_w)gl$A3-|-!1He?Au3*#8fnX}iAn*o- z{|HRCFggvN2p)=RJ26$uQ?kQz`x3MTr2aTl?I1iq72Hc1<#8EwlBw7h@@7KDwXd4??mPxHHrY{i0_k9D#WN{6mf9`fxCsj?Z5Fr9JD}xJSaHb51v7 zcLn)78)Ka)93Z%hlS2`aBPKfJGjqD}sHPVTF!G6bO2}Z4)riSJ%?1Jv5vUQfQph0! zHDVr6qk6!8FPr8%%P?a0d)X!53MjDxcg0*s3>M1g-Q4S6l(J&~y_v@_fu;`j(EE_l z^NDyqepyMH+2K-5QqbYa021`w2D9D`X%*at$A`Eny@3cLq4aqY^<=Ov?Wl41+wu4i zSa*61^D^sM+@y|v1Mz77Op86evLn&4lG2mP8Lm=Sk7lO62v7ffU$Xm}w$1sK?u6v$ zCeFe+3ctXz#*@N!PvgS9>d1Hd%)%uKyGXxtu3L3az5$Di2lN)2?5<}lll*#X#jf34 zvZIAfEZFs!OUD_Sz=h|*Oq0O z?J5>Oz;5~(MT}!DmbWov@8aV8_szb&?#;L2dPF;y#$XK&& zpItX^EU+pw)f7K7zdngqe|gwHd&?Z_2Bo-1lH$^5D!r|r z>qv2QlHzQa;%-TbGwbX@jNOwIr{y0?;<*&ZBq>g=e~J;(ADg+aMJXOqKl*3DJu?sV zVoYpb!5p>rG9qcyVdVWSL0lk28a}v)< zupp6OX1z7XqZ6`eq_}|;A5)6wBq@$3wTDYmJUdBop-b`XB*nJ;%!JS-u{23SHEUEC zPW#34N)~+2Kz7vg9XYrn;kU@~yCUJYF#lmf==fcg@MDHodlmftC4Q?C85hH^;e`&K zcWkPaJ5#OXFG~m=&$|+yv+MP+w10Qz6)T9DB+;po4enh?;;AHwX4tPTN#e;QiPpL+ zr0FL!t|86N?~)L@B%Us6I?FQ7f2Ks!yjtRIqa@x-l2}8`BVX*u#k)mKKdU6(O_FHK zznl=dB-W>zuD2xKPs^NK+(Z)lD2ab1NwmU#LrD_frb*;aOF|q^FLI4S+TIA0sba#9 z=~x{UgVvWixS0@R|IMA zwJ?e2y4fp}ZgyI|X5;=m zx;AjR8ejjY#Jn!h{FRru>Yo+7K3HMIW9r`!ydk*8;OP3l2>vFx(cq~1F9mN5ZUgFJ zv-)6H@XBD7;Z-NpYxc$GNSd+hX)~trdQLGXsu@l(i(jtv?t8VPP!3Bp(N?FO6mHnR zV@b8vX?6un8{YCHUVU#dn;B@bZ{Jf5x}6lZkmmt`uet0fD3uDT*KUYEA-K|ppX2lU zCL#5jR&xtp)HbkJeF+b}I~46>bx&1yD`Vm6-ri~5)4M~_-cF{Z?(Ln{y+H?8Gd+|b z7r5DhrXJ@pmq_ct`N{O7t?2)FBejRfA<518zzl=RQjY;*I;re?S z@38$c%2VCi4}IHlYQLEBtU8_Hv`r#>WpcGLIn3D1!lFKW-H1-u%(UOGcnllo{7PZ_e=~X@0omritIfT<=@^jQ3J8}Og_UW)7V5oNsoS$0-8R; zv*}w5FPkBRM`H1{_e(|q`|#%oz3e~>Rs*w#OH|+`50@NZp$B8y!zEJ$8&AR1`Xfo} zaGYMY)q*a?6j93kO!;QDbDU|s2X{%dD}Y}1S`jophbf{o+5_@6neE|{e*jBjz6|uT zZ;Pg}g8rc+$jg2$-+lwt$2(0Z(7B`-FIz=WCKQ1^TypRx@td{*mQri5-~|<8>fw@5 zZBaG6;xha@-W`#0fI58$JlB(oSzVeilKK$DykdU^2Xtw8#x^B|+=#Zh%UCzF6xMSilCq3-a|43j1f@MUqSP{t z!!bLw)TC6Jdf>;RK;iuk#r$4<87bRBq6$T2&y6g{@qO%?BvI2$LQ8dclp;6YziAw! zl;LH6ZQ*Zj8g3%G@ZVebSN}8I%dS_D?OE7!Bf3@Q@t0&pJh-BXN2YOC%o356`6j8; zME0_MM3hzOXR^{`rm=0)2xEaw`=ka}F%_ZWJW9Lcxe+S>wzAl5(bYgT$ub~ispl`$ z@B}GKT@6ejWvRz6)TYX8-z%rr3)GGTDOx>!p{66qT|m8jq2?mUE{7hzkZJ|l<oQq(l+(dck$DQri(6 zMRf#9V}k51g+Ie>&y7e!ne1gU8&_do$zR&auQT6wFiT3tD#mx4;eDd4Xkl#Bn;Gu8 z5!1GA;&{G2H)84*X4kK%Z((-*iV7EI*RQDY0+?ODqRNHY^(*RJm|ef3(uLXelh#c) z!p&@)BUKTz>!;^NOwkHw*RQB}VRrrW+=yvjm|edQ!D^#r!b5#~Zls8Z`S#pM5f69I zjTG@H-<}(h9xeW8?jUSeBF`Ag<3j?ax~z1V$qta&I0kcT{?hUoCf`t_xJKiC#}tE_ zR6ZR|I%zXMGICMbTO#-2C@IIifv!JLK{=O|?Qse)KN(*3RB$G%piE=X_`mUZsL1lW zJRTE|ik;X6>v&7V>Ic^iOIxG6eORm=z|z(TENzY4O=L%~ST%s9tr1w-8iBM%y(OZ- zstibL)LSA}Er7H}y(MC821skvTOwA7Kw6{T5?QV~0HihQEs+$`8ugY)%8J&gw?tA% zYwQ73>rz}=quvrx;R=p+Z;7aM$~xW>k)j3lmPlvZ_LhkBm&uNinLJ}RLGEx8rk=42 zXHZ`I!8^N9{uTek?07~*y=|vnrtfqcv{P^E{nA}hLG*nR9JEQ=$z4I$=fVa%o)IzF z@r;N;x5uleD&~=_ei=&_I38lw4hr{R_lJsPsNQtNm$WU^q#qaoUO?bPvT$NnEG{kVpqalV%9u3hfuT#mRA%;sH4KZBuXo%F; zspQd+GZeq%(U9#Go;(`jo;=Lz_%M?dAwRg-?3H}iAm-WrrdM5&GH5d$e&q_nO!i5c z6?gS2{jR~NI=*XAQDvL0Ql)1BSy!pF4n9kvu2Si{28+9y6^6cRI9H*YTO-zY4Hnw* zUBg<1GSkwKnuap9q7m!628+woibkwM2jx4vwKd}O&>>6xq!H`824y}={iG4=y9R@t zK_uTbJfeg+gV1zRIo5X#q0Vu1o>fuBTI_Tdd->djn08R~vZ<_iZpBUG46oayq1-g4 zNnH=LQzvd3>q7S9C{b*Z4nlb3rRlrI;Lf&)iU#88t*8} z%!V{{o@M5X6wY^|f>4k?TgjvPNWL2E} zX0I<(N4c4-4sbJB9ouHII;?f8oF%Za(^z{-^%3IgDOFnlEh8xVFG?d*u}vS&=75=& zJ2AC9jCGm7^IO)YSeFTWzvYn>PXPyh%Zn*K6daP)ha@3Z(O&Qrr{sXDLK_or5-_Ia zV@#!(xtTc$X?y|J$|iFQm^oIwTY_%|Gso(h;@iN?v3jNWw_xU2{lUY6Kh!o!iMz&B{k?7tp(J{)F&U_#jq831_ zZJU>W=PpE5H{ibMwl+2Kp5Hluw18H#EEQmnCRs{x?aa z!kyxD?%PH*E&m$c1$Y(ik@qrP0^$54{|~%7$$PE5FH^EQmRl$ED~Bc)UKG0Ap$UbR zzb5hJHOdYTx%Y>Wze3kS*hS2DeUo1h;4eP_qn*FPn~Hm-lVVKa_g_MN)s{v*+LYmS z7T@o@+ayK2L2#F4aDPV}w(y&g^1Sy<8OEirb>Q?9`T^L-I!@>?RgI5$Zq9(1gN2ga#aMXnf%tp@B{{lTg1-Eu^)d_<~5byAU>k3H=yN37M;$zc zz_|q6TnrrV0(p>q@bLt`LBQ_srZEq4fj9E-%mO@rT@0A%0(g;z?W+5Tmx+J>3GVoH zOh5hyW)SD8S7F=_GvkFSiJfp}jXJq9mzC7KM*OO*@HI?z(>;%`BS+2IFsKF>VwmU1FIUzv(MWrd?$=dI2vh=r!Zy5n zLAuXb^{#%E@T25C(*Lta-@^OA^{V;#PZDS0c!iHuFu&1KC@^pE_Y_~-AWrX3iqZ4r zGJG3{KEA7FL&iHlBJ6I=Ayo=F~kB2pv!o0(9 z?0C7@&T(wl!0QQPTXR3*cPnTir-FFRec$zG=*2(A$-gN7b`nq+z6U}bEB&$|UwBCI z7Kwu-d7=c(WbWVbGj68uNv3c8jGKxpIN0>dRwBB5{3b<8#_T}r+D?A97K?4TO-Pzpz<5oH!Zw`3)9)oX z^6}1re>fJ$GCB6caUG7v<8d6!pG%}k&V<7>oSAZ^lRa6^MvY?~@-#`a3F$cs?|C!) z{Orwg>!nDYol66qR^te~=#5g$7+Q-eR}p1mZVl!dDD_iH6xoA7r4yVgP2?U<;b}JQ zS_kBC%nMGpY1cC;Ji|VTdlC4&LVsbiuvgQ#XWHWWU7&OsoNe>+zW}A=;2fKme+raB zgQYev{{krO1no92{}w0>1n1hkJfh-QLKoP)JO=(wp%>cAoOz&S;Ue4HssY}o&`UJY z$aMosJHe$v%dUQ|8ECBd?vurJpET|jT21E$0#z!(mF_3zq{eOW%SwNlp3l-9`dRh5 znNXwZ>)>$*;>f>)D--UV`wR9EJi=YjZs3hm_0BJ*2d~ML?WLaClzuKAQcWgC>0xeJ z3d>P?n7cZKS(F~yXUj%hftEvl2oWxsacU{~#WaOXG`z@|Z=x;mT?PJ4B6XTj68MAy zy&tJen1yD2Pk;WRkIKBWe%Hkt4A5VA7|nO{Rb9M8HH^x*mP zrg|M{)|{!c7fhSZm6*|c4Vo%a96y~hfBy7&ZG&dQ&G4bK+U9qlqo>cCKWpwB=fMGE zFobn{_>NZbKKj-tOPTFpN;iPLox zBn~M*(fbR6&Sa7F*EEyJ|ppGaw$z>EBTp~uk1?o4T zj>_*PxS=M>-D4SE5k(&$d_#4VN%5cl-(mlQAaWvZVBQ`+Pu|H(mCFkXK+VzXWO>Kx zR7Xo$nuH@ZA&W)k2lX{YUZS}qsES5fId)$VMMp>R#wa=_svAz(?pf<{oeKu27^`Zd z&CZOXuhJwoY)<_S!yT%kLitreZzo@KUwHl&o~j$wR}*Q{K?em5^+8ijm`SQ05;y->7LDq^_nkZ~D}dhLi>-?P~sld52AzI=!Q5wa!{NeRfGx zn?G|(Yf12t)925hGGqFoa~GC0KIQyma=vwWFtC;qTN!m%)r?=Figm4CLiMht+)fU9 zP$#Ii8{6BX=)9ZMMkv~xkscJ~C%Ot5uaCwZ6h((bh4n;is>Qcg?Lqf0AkJi>yd3%M zLDm(+#b_6kNk7fqFbZz&#hICSQq*0Q~wR}|JeNROL^t{WkENViMezqXZC5@b$!0L98k&<4dr);42TOcEirZg0Ir1l^we%LY z8N-(>DYeLW#k|?(>=uW!CKNy}h^}$Zn3ucUHUl7G3gL)Wk zjN%1RjzTyWruALLw-EHL4hB{S^|eqeh#Ev!`0t$Yq$v7v6t9b-TWMHT)xiL#-CLNv zhVPgfM(~?Z){j0TzYp;1g+9ywj>fxIx3@2kw!I{Zeyi4+G|ExiKsQoN`u!0wZFC*T zoeUmD@dUsWiUArXcUjtn&z4EEpuJayp zD)OV)q#N4s0}_3V9=xJlx^74+;&%9cX%#luqHac%V`!m~y&qK`Zk4hfVYaT0w$xZs z7!fp+STI9rH8Tv>RZEJw{7z-{%BU+-2ep$IqV9cG%o`N6sJo!zoku*?;1o*2JTPX8+OS(%`Lz4En*K`O}UVykP#k!O5&- zFx{!^MGu?OHf6S}E%Rs1n1kEqCaNgj2=X$#Dk?AEg$!>~9rRIS>{UG?>KpXw5=E%I z(N|@OgWh;XA%T8c;6p|dd2_2}%oXbBEx8_+%qkWu>@AAD$a$bR*U$j_27S9kepvX_ z_zW}c4f)U7vx+}+7J717k@%x_B9$$aNYJ3(wyv6aN_*7%&M5k0$=qU%$hJ2@d)ASa z#_A|bHGByt{R8t2_4UN0-@eCc;700S&zh*GMu9>N48!?%M-5?BQ0>#}sB)}}s_u!Z z8QrOx?aSAln}JjEl)iqTI_-@BO3bjfmTG zi4s@rLY!}{?RLc!T~+-OZjmqeFA~U19yEm5&;dI;*JP zIZ^b=YO+#n5{pIkk)uMvMwNT1J?eKr)EHEEOLeiNy>#dr!k%Av677gmNm=J!iuygT zSrv+_P}Vza6dd{Yq*?ZMBl5-82$L)`!mOOMZaFiXUSaFMKWwSO{^r6OP0sE$>D>D*g*{s`PDjzp z$}dl`r^65{!(<-F2wYm|tqS=yar5P2k6=gU03)cWObzyAc2Gl^*Vm(yU21|n`g)DZ z7kHEYV-*}crETt!v;N)U>8CEVV?;}Ev7cJ8Co-+0A~w^~DN5#P@k`9kj=rf_aoD|5 z8{gIuydU`+i&<6BS|Wc>*kXmf{BIebZQ0x5zs{a@QuAA9&6z!S>Jc5S?JcF!yDAFG z!-03d64WLYZ7+P^Bkf%IJ=DuB$QSL%HErtQWCg3oh|0r?%hS2xQCTVc>;FQc1=D8D z{hr0@|JS3?^r;KxO-~j85~-w+zq3$gz?xzdHjlBTbOPz;emcKx-mEz@7`j*+*c?ad zxr1uDL%BMyhB;bkHK@n2rE8(K{k#<{IlF#pGgPJk=W9kDhdE|LESBlvtE*QY5_GMJ zYBxq*H%7JXQCH?dlcJu7FoR$=(sf-_xSzg?xqnd5T%(%)UWE4&w-3=Yb84#p9u7?h zm2x1}TFo74I|X%p#EWlwmnY^XF!!!G$y0ffzSj}||>-D_-@cWG3#Eqru zImN&6oyh-3+K^gBb0YEn7n--A$wc8WBVyt(Y-!NVr7&cFVqfo4jaVvmEJY?Oqf->@L+wjkn0BqM6Xq#)R;TE$+kS#0^u z(YC=TwpcbsO^-%|O)$Dd6##ns0U`CSoJMb)N;a<(2!1=>qt4jg@--KSc00_8@l zp`w;2=w3}hlF7^(y7q7bY1L^5LMt#)qB)wJD~^LzmeJ#-Bgo`~iF6Xe5Qzb}z*b1m z7frK{IykCeKz|9Hv=nvX9Rhj#e@(tZ)Kknmim+)# z%0fIz6)15cSaTio*5c%C)K6f(taDY=Sgwge`7u!TuD0pJ{jlocQs^QbM8U|KsEbzm zBWi=OZl{cOu;zTpPT8o*FnhX_F`HH$by*l$xwNG64o(~!Bda{BviC*RQY{nh8%eIE zn(c_LJ~AkeF^k}%anmIfg5B}>0rT9i{wuQHL9J$M+O|LkR+97@!3BObwPW47d!r^* zbldf?c8@}p-Q8<6QB@mJ`-tv|`aBL7x&;;eMq{JByt-;;rMJ-RHXaI-+!^v)pJYl&^tJvPU51BMyk7FY#h`29cEf z*0oW$V4ntDy;ZPez>4WUD+_id`UC@fO~waQ2Rpc~X#C+6%>9&d&+^sLV1!3@H4`Vj z+dw!boms3A@fmKORmFld4u-gLV}_AT)JGkmf`(s3`O2Wir7>B4FIXC;D0&d)U-zVq zc!h%BAlNMhe3Y05J)@xYNlZ<++1=o}J@%1fmmBJZ`t#_jzP8SU^W%^0s7 zef3XXwk|BC{=uUW_x@^$t@Zfws6bO`r>#7BQ%P4fe!h*gPccJuJz>XCyMmzb3^#Z- zXbjTy{r~D8is%>wyklT@5iP)1n6OmESd+ z`Yo2Y)*wk;0{2sQrtD_!nwm?T2F<8@K7G+)t)zklpYO$zR z8~+iuq1wA#lkP5TRs8R};e&1d`v1d8_tc}?{{6f=opdMr>eJ^;n>8glpxR{CU7V4Z zZZ$5Iu1<5!fm5q=h@_j`NbkH{gIUK}(~x5!UR$QN6P5?NYev_D9@TpBt<wQxn8!&@Hx8P*IdgaRY<;$0A8d6tlOC~5c(zb?F)A&}8!{27Z2FI!4mq7L11~)Te zgVjv6UF7e?g|@nZuzhQgk-mUlY{?hw37WJU+gGk+OTxN5e@WWqEas*SbKmc4kije+|Vzzx2uUVZ|bJ8BKc?IhPG$$Ayj6EI~p7w5p-i>6xM$l)oHo)JzO^a z&v@U*!`6toeNb(O{~M8tT;TOTVwQ4DxtLI6e;n;hq7(tg zp&idxYYCE_)GrOjZs+)af3=WSD`igf&!h3i%Jar`VlICg>NhG!GlCr<6)|b|; zWJbL7T}Nte{cV!?l3^)VJ!WzwX-FXs1EnKY&)KJJic*Y)VK zrZ&wxY>Jgc1s^r7*7SBTDy>mYv)@xSER^#Vt$s^;h4j?_GD=RT`E=biT9a7kQpJqR zbuP{|iq6Gc*14z}#dK({dr7VZY`7doe|3!NzQbrj8a+59b-`#t1B@incQ`G+0#3bc zB9``3tRmZ~N?TUb!LVZK|8*39`G2T;6TrHvYX3j?TsFQ05;EMIsZ(0oGPJY-T4qWB zK~T{;JZ%d~Ybm8qprLJA(DyJ>aC~agio)}0B0dL{lz55QL?)g|ZeJyn3x;Vdp$(FsEH&<}nxMgF4Qeml^i5q=H&xcjtSvt7za!jq1_o zdh?V~Vgx$hn_7ifCN-N=E-`bX+D9waL-sGD3)97kcUx@N^yXIzN|T_Rg-aMPcu4y^ z>##Ie()@>elQf70@X>zD1)cY^$ooCkdovz2GvtnrtOo$gR_MMN`G^djY(BkN^LFRd zfnJld+C6gDY7c@@c>bdg*e-rmhaH+0O8}#Y0hd4;`I^T{S|jfbttX> zN$*5|nM!oAz}=a_Wanx-1HF_lO*kSUha8gLPavJHz^QKzV%Zk= zz~%IxF&QH8QLok?-tjEm<7k`KXyePjzfVyjj{abyM4LUf(b$IT+E?YqY|4ZNaaS0K zxrsdej0bJXj&_N>(`|1p$GUZ!if-mum9!Z%t(|nL*^1SWe#&bJ2YAc-sq{UUaaJuk z2P@+fY)y@BTL0VDRd39%d>55}U8M4vt};PPa^?2BsjN98tP7+4Jj~S=vsYVPfZLyX z?)*H}|0}2-Bi!O{RshvnthzrQ?fL@M?=Dh(kt3b)t1q)6e0P13%9{4Jq{ndE%FWmd z)4I2S@Bs6dsQmMw^3;^{(@Oj-Vp3#GH*CG9pmMP8=gU<7b5iAMTiKhMD73xzP<@LF z7egIcFrJPKvQpE};?hzvc3(1qX5Mu#mA@LH3>EAQSGlBu?c$Ku^kYRTTg=ttHS)-I zs{b*l9#b)kyLmhn+q;A6?@kvsvs3SZA{t`KE-6q7r;wCRREd%MLG+aXQA{P;oUueh zT7RCT5@lbd@*_!=tEFD8-t644Xt=qnse^k<))V#eTGRQ^*^WorzMPi*!UMAb+0s$OWTmR6cw4^#2?K}Emi z&v!T{((<-w^*GJh`5ln_xAoK3URj+vRY;o0)0wd4Uv6m{DAMjRWy~$zOzFgiZhHip zei6_VYv@;n@lcV(Y*KSeche?(uDpQMos%a>L zSbLG)l~lP}>-b9c`2VH)uY&5nA{;-V*4+CzRXc5JJ+k)mUDa`PXzx#`dP`8%SA?U- zZ{1x#rQV%+^=7+z6Hy?GqIw)f==vE*9*>ZOUD_85(O9w~bp4#lPbF1umfK8Lgx+6J z`F$0BbP-d8`OYFR6H=tIZxMDqLFG>-RjyW+w3J1t-b3{rL3LjdE{;U&yM4`LCR%5PB4^`Wvc#JE-cbyz@YtgI0}VmcX3# z{+3!#1hu9(JN-JV4P${(D6G}>J8J!pjFIB&mSM@}v1*A7hX+am8SJOYrs~YR3E@~{=9$ovWx-YM)#agYT zO6kVz8l&=iDvJop_3)CK(6yh+w*{4bO>jnRED%&-+bn;IT3-oj`I<0aq}q&_?;r~6 zEoKi@dRMV})ykw5(S|UZnd;Q-6%NT91`S1wfGPVoH@xvnpXW*w2^1?Gte|Xv7OFm6 z6SmrK^@1s{VIgRg1OS6V{Ubw)Idde<823#oO!&Z^_Dw zUqt1HgUWuZ`cX6M77~+jZS3Do|DssEX03Nf)P}Zl>is#d9;=9?eZ;|jI;l{LS}Le_ zOKq(=mK9_T=7M3iSP-+!>@df6o3pyjqyaOX8CrJp46yy8a+C?c$#rB^w=HD@P&m&M z3{tcg^O+h8tSJI^QU?=S44@av;ZEuf0d5`;#BS2L1pxh7gMOVY^LQ!hHk;|)F4r5- z*R4e!Q|eY1zzmWWOz5ZYaWIpK6qP>Q?to6FWZ=C3u1{+(TZPki)s>LsW~L0FRuE$H z>}DPmikZgaPjnYz@F{QSkk<4K4nqmbg=KSy1@JNY9Tss>h{fk)aUEE0&6MUN;G~e3 zI5%;g3i0_|b+Uy#Ju(5V0=@#Aa8`u3IERHeeI5rJzwwYk~`M8t^J;A5Gh+dTJ;VrYl^6WT#1e^rD&A0wm4fFNhS*-$n z55lU&M}u$B;VaP|0_`kUfYTxibSPZyFrgma#kp|lry3J`IC^EFztBB=mctrxn!{_6 zmOQ{7&Muw6htKZN?C{yl01lWLn&1q4c+Y^Hl+E+uesZepcm#J(lU4EGQ-jEKHI5l9%anY%fbEEp#14DIwY;}O{h zl>yv-nA)N$w`9k3S5n8d)G3Mj6(hs0k5MuQ91r$THr8^PBVvf~Jf2{yRo_PC& zr6-1^FGjybVYx`g!s`Okk4-AgN5Dw|FL~TVJSE~AJRN+!1^5cN3iyg}!fF9;aSju4 zwk-nZ4=0buW5i{Ilc=)?y!z*Bn&UD4iSX^eJPouyd`X^u6yr1EG{S2!CUBd}a{<@Z zhcA)aEvA;AMAyEug^{l%8?c#OD=aE5&TBYOBk zmI2#;&PKada^Aq+PT=-A_3(OnxY9_0*QwXiKYq{Eu@2L{P@ozu0ZLq zinm1WMwWy3h622K4dQ@1+_eF2%cYMtkJk`4&7X{W@k(&tdgMfS_R-jObP)XzC&s&9 zCSL*}Elv)Jl30E@)Qn765+Oxa9M zBi6bJEZwsc71r_*Hh!)Vv;6qaY!|j%g$-QNh;>~D-k-x=?%1f1yAXG1#4MKuie`BY zahEh=W&aNDXD7sSh~4j=p~sBzo*;pcmY_rCIn}_8RKE>s1`DW3l0-;R%!tJ#Qc5l% zMM2j~ps4i*D0=Fs{I&@lbWA54x{#(qBUT_Mzihs~INCxA1DPcnhrKsJLk70GA^nZ?#_Fnp~5Lq6rg zk~3e#FI+q1nmJ=T{nZTw*X%uR{>uE zPM0su=grA+oE&Z0E#Um&obh-J+AErwh}ulp3ptJRo5=CVe}R2V0rp}XM?B|nP3E*X z-?`i;Qn2fzP;k%O#9pt^g{`fAueX5<+0c`8j@|6U&f3xg&|3g5n07Q#{kGOrXoG`Lum*C3;7CgkNKD_fBwxJE4CU;{1t9CZL9IOHU~Ym6TIRUm&eS z3K!oEh5HUrUICp>Y6)^1PkJGZfeaIAEc+5P-qLDq*A(V|M)THikRzJTc6&DF+BFia zw;136DE5cuxgUQ9X!(}`_Y94~C~j&5&aUF1!!6Wy5+;@ckRi-sApHnq*FAuI915{m z#VLfuAqa}oI3yx*AKG!B+pl~=JMRVRJq~Izp+ivWLmrA~#VsI#0?0nSZQFr-*YTz3 z9Re=r6Xw%A6j1H_9t8?;1k~RLjvE|~64VRNK!|4u`0^2ILx(}1;-nCd&&$Xlcy2wR zG%o=+Mfl0%DB`OSqtDsk17IAP0A~Sj0amyx%3t8a#bqH@pU?hpg7xMT$Kx~NG{73j z(_$~mZJg&q{65z$4}$-DPR=FUQ+QmPI&P%%0q%g7sBInZxe_R(A&|khq2U>1K-VqS z&$4tg2DAAGgnORSR0cm&C3Ss2OzBl#m2?aw)R%2qlvDI7coQ z)OoQ#D0)e8ikN)qOm#`46Eq=LD*3X>)r?%}I5CAXdPxpNjyfJ*;M0|;HNC$~gc2iu&EU z)MGimZPd(X%rxpRKIvN)Uya_f%X#J5f>c5X; zEdv7BXLmpZ?A;H5y%7Op#RwqGVRCRvG8t`u0?M~yWW?mav4>*_0xvQqNdQTyt)kp? zJ`I>phhSpT@TJLVb%@hWR1UDnB@YaG{Y};p77@h(&`|wnXc&!Xhy;>D10r!!v4Y{c z2tC1JVIzM5!o0p0NvIzU*0&AmA9nU-VPX@q;LjYmxEwJ9vuU^7C4o;!|lVSat zxF6p;PLP0IQvvXB2p|-34gs!Tl<pwCMFGEnlb$? z;*FBLpkeQk(D2oWhQNX>=+2?R=~+yNiqI2yoxxdv>>K2Wo+4`EyB!3i-kEC# zN_n!QB&rBzTvo1MoO%%caq%%gy-7#*2b9M2=^!bs<|Vf2@5d;ONi?6*c<)WH!GZ0~ zi;MzUj~oZKpT;?ebxAl7#xY~1T`2Fk!A*c)-3t8AMeuXw9^*&Qi`iOhS*(vww6L8g zfaBX=OKgSb%0vGG;J+_|AM5lIP@w1bQar?4C9d}gE#%5igwIo;<0}uu_{?vs98M+C z-2M^w*6J(zm(Xr4r-SP+F`k2Q2>sgqQ%q_5Ovrbfzu5HzeM8G4Fz)zzjJw>vWYI*r z_?);KYy;OHB1z`AXil37Q_)sUYFo#9p%&OX6O=~Rc~JQNZ#ZeNfOt$ zjYLDb2l?8il|uuGu&Q4Utgn7E)=n`29RN+O7Xjy^5u8#qAt)y+D=s8fg1%j^0Pef> z0zW)dz8-Nxu`e|)Lb*Af-RxOW`7K^7`6bgTs8)i48pzO>B-!c`kjq{T_K zGlE~pQH&>H$ZdnLC44Rt@r*15&+U4fQJ_1yt?``sxET0P9vC0-)+ubGf|hcvX6Gtw zizV_}b}6*n{9ugNn7!3W-uw&uiv3Zv-Qum6h^uu4xP~LS7Hhwp7EEfxSWAX&nTU1Q zO0eGgP>jR;)_ndrEV~?B4@J_PD2Iiuo7Bb$Egfs`@g>soZ&3N`xJ1G>KiB%$3&!)Y z)o%vEcOwWne4cB4tPi7vkFCBEARm9YSi?+|jH`ffV+0|Y48&=jMxw)SyBbhGjzGnH z?728seC!1i^0CX;0%stClSh*E5Eo3)$M&`Z>!aT;E}#RTY4950JUcO(;(BM}xe`8h z`Fh}v#_5ack?&)XntE}rtuNsTM>a@)5zKt~Is1SF3vI(V-l=ONkay_p{{pK{3p*Qd zpww1h3(WUMFvp`d-p^f6F6htWaMAe|Fn;*EF)ngt9^*q2%9VTEN+-mKi~k+?zbM6# zFm%%!t%R zl62$RqC~ZA1>BeQit{2el7x&FupKiXCUr8rBn3#xyePb`4+8JQ5xhL|1k4whE}iUc zTRR_D6lU3nfcb0$Ge>X-LF3{N1Mi0syot~l*VC9!E=q69t-$~44-#bx=S~iRWc?d4 z`9^joLC$;sOLC5I6!H*avFho532jW~p?&d3ltsBI`zV-h{b7uonA#%!5YKbqX5?ew z_~pTK6Spe~o+7Xp3G(d!I24S#KYMhX)(x@ewsD9(v+@j+Iye+qb_u~=X=fZcq_2d0C zE)SO%jeSZqfd@&Taqk__`0e4CY?5`Nq=ZV#DXt}nda~JhyfI`AIFY=c7>(o{@`LYj(d(u5>b_9bZ99MST8NvKe}l61`J zTTI7fH-f(jN_5w~aBgG85sb|J0mDLvfvdu)&+-fO2jN>I{@@p z1SsZu4}hHN`+%}Nf>N3s#ET?L#4OA22i(>l6_*heUCeUggkzTeLEw!gl80^v0@G#8 zf0l?@TD}I%AI516d&azhgP^hf0pQ&g!OIh2Q5xfVYSpj=y=~tB{zreDC{w?K+Gx;9 z*1u8U^ITq`Xe3enEy=kQ4>8Ut3xzZ2u0Q+FMDSHSU z4@5W$TO`C%l$*F+N$^yGhf45IyS@zt-~Hcl+AO3ZmskIGLHosRZ35obUEuvlgg1%v z5OXg7A?Bj~$J)toEMHWQM!pCB9goNOF4nps_UDc7wjYA`ug?$P@z#&`(*#X$Ze#+o zseTL^Z~sY5HZlJpC_$eJFJjg*ycb^3^A6r4@BJ~f{3dFr(t6@>#w=|}M0inX2b{Ks zHbDZ4Wd(|APuowSs_&;U2_k4e*>65 z#0iPX!mif2U_fg+Bn?0*C6^-+*2SG4EEOSXb5ZduX`p zXT^mv0U820?SBBy`+PXLBFUi#p&Vo7VnxGsG1>z8{1KSX#%YVoXB4=jsLL=Hp^O~A zHq)IetANhMW5E1w1T!u-L6icG3SuBTUxMs{F9EgfDWE>`^8_gh?txQU{WLIpe3)!G ziz%51%%Rjt6~TtGwe7B-{LR-NM+g+<;4uI7i+={&=YHWhn4H%y5pj$MF7xCaNxe|d z{C7h3{~z$5_PGImuH0k%2zsvU!xr#4NW{_hcW~VLM2v@6$9^7o=uM>=;P^5sXpuY~ zNP>2An7tFe|G6;GeAhaBw&WI|r538Cy1RKBHvd@Pn$zQh7h`uF3MDs<#yHLG8M*Bh z_la4kvev{&RRjco79mbCAE)1fz2)HiRwUj07L4;B$0#|3{5qBcjAE2^W|mjE;{?pr zm0*7No*1|J?JC@U57UckW8U^)xoxcm=YTKaB5fVxHblsUjXGlOPK(F?LOBEy@P%)C zsFWmL*lL27;YmIcPl0^=zB^J6tv~YRlj~FGVZt?nwBz>%+xA7g$SVuHh#mLG>y_}Q z)lH!9{-tZ4Tz@(N^nqb+Z3fDIpNY5wDIjHBzuG?uP!Gik3VX-AVK<)U#h8rcV31d~ z1wEqBuJ{p0%M{>!E`k?o{ye?7JrJ-i^@7pyeQxhm;Jxox#RYZ{^i{V2?@6D&T+tP! z??8NT+YDg;FivZ(EJ5IK1QPXc#N--zhy4ES%Xjfi$v480E8h@ztB^aY&Iydl;EMyFs3{!szI-czCPXTJ#>A~uXFP=JumLg@rYAkdkn z24>K2mY9bcCot$b6IwnI(UPnbAti+pi%}xEg^u!|0*0=$p=k8CG3g|0O-R!}NIGrj zLCc4IT1skCNJ*Y_0@2V!v{Q~&<%SE9)MAa76Jq9;chT3l;YH`m;owWZbJFCThnaFw zZmag`nr6Tuv{_fm**A9Bo|{VXk8$X|2q+(kplE}Qs@g+uPG{&o8bRR*7NV4*V0OI% zFu#ky%+$h&114tgD}k~rf>M;2qY*bOxdR@Wl1XZR6~I3I`{MGEfH+K90wS@&5jNpa zzBqfY2G%F-1d9L+P7mz87((B@H%4eo_c&a&Z2_=Y@kRayqp=GzV^Y$5|VaaX<3}ABn%90X+YViL#a8xHH00$d{Fw zwI9tQxUfaScJaAM#MQqFT#x+GwT72xQOaFpU_PK=*;Yvr|BVavtZ zZ#;8yjPLe=C%3*&j1RVZ+4{VX&ve*7|c1Mq&4NS==VG*G4GkH9VGm8j$U=dK%p`HeV@ zF~J=GjdYW9iaNZR_DIqkx_sdwKeTcJ-nsW((D=ST6*Dr(k)iC7`in`^zfsOhHFFG2 zF~{l~me%)5w!R$b>3L1B9Cm;ukZ_2lHITD2F4&WzEZAoZ9Cg$9IE;~O61@}Ll&}NKD-MoPPq~&;@Ia+&k?(K5kcc0UKPQGfz z!afqT<1OHQZ-h6Wa}u1nn606T-xDq6-buM_8n*0s4zcyW!Qb~xjPH2s{^R%_=>hNW zo;%+!$!mS5384 zoZx7c<=tLcnOC-?tc?19sjB3Y7iIH*jq}&9TRDG4$7O5QmPPm0T)pzj zj>}e+mA&T0FTeDvHP^0MH*ck?>F27eFI%(X>UmcHnSaKs-Y#_nwW(kN2F+%`ZZ0;J z>BGEUFJ)>7Gd6q2DrnLM^g=@@pv0jrE=00mcPTI&3j|FXnPL#q`RjWwvs+xo~T|@*E8W zYe*z<=BRgxE-_4*y3>1nKtdy?y_eRRsh|TG$CLx?0`JIrbNpsAf5;ruZBG55Ic>A4 zyiwHP39wx{b~~fjjb{OFYBrljuP((8?$q1}6mFJ)(k!wXE&bjp$&jWB(kX)E)nrZT zreF?r#kZq|DqKuU-_CP^VTveg6+!p2UDfrz zOV@8$x8~~A{7Acc>o%=zU+aE$Ld*P?0Y8&QF=Wt? z*$%Qo9j4|Ia|F|!te6?nB6QRo)3clId}Q6`4r7+xY05X6%Ar0}*UL?^4wY1*zbJ$0 z9i8PsczUHR=_Ydjqk-ZVbB|KX=(^$MZnU<;ei+m1y**X84U{MuAj>HS)-9%dDL3An zLDic@-3X_J7ns%$LLzdkvEk%(6OOyd^1Xi1#FUk z^#=NgH#sZ3#$hL!HEb%k!Oa49dn2QQ>PJncLftbvW~NxPsvlZ~#%l{Q-kEiy!&u?% zCe`ivGdyj(5Hkl%V+!jUhK2_QhLPY7%7Ab{|5JM!FQcmvtwDHy$mmy)tgADpba~CrQH<~?t$@m?wP&T4B8|!3$ywW2 zV9sER7{Fz2LNGdE#>-~ZXq9Y^)aB$l%x(`~rhHrC53e~h{1}jEH|%;32GF+PHg{nG z__zAKDHydO(~N}S9s|A2G>?VXSlvDvo!U$LOlr)V;dtm8hKENyE6Yhuq70L#Cf~k8 z=NV1*PRM0v`%R__;sINiA*dP>aSjwuQxQJ$L8|4j>mB8Gn=M z;$moc$jadVurOZPX$l@n8}F6v9c!+>e66!b{tsu$6f1Q81bV(>rFAAmOwDlSG&4s` zrq-MJ1PP`RYeC>JJEeAn#D~0U$I0@aQgavsHmSnZ^rmH{aMfrO_3e+ROlo7$J6cC5 z|2vE6EK{p#j>=7xSCtdI8G!HRd)o8=En_7AP4~(Mi!=Iv4lP^Ip7M$_#jZ?7M-aR1 z)x+Pzn6Zq}E|o)LD00dF0iVr!RZcXWPXOT~3E&6y@P85>mW}eTFX3UE9)2m|VW%G6 zpYU){4|gU!EZf7wM-m>k>EVwO9(LNASVhEW`z=za&BR1GXlqko3I$9$dqW`O5j}XS zM9r38Qs8f{fY)RdOAbleV$iD4F!!q5`_QT+5=Kbpv;?`3gk1WhY!GhRu3N%+cGhh{VQyb`fqx^SJp9p z|0e_^6iXUPth{nf`%-|=(Kb2p% zYUPGl@s4$uuUNS%y3x+y@k&1?wcxK<(H?tz)vERDSFB$3rnT3)Dt18m8(LcXbWO!s z^7tB2+UVtlbB}v* zjW?ya8}_+j5}a4seEc$orIbCD_EBO{+R=P6}f zqf~R&h8`C)G8eiTd2QGNm?*aI2`W8gAq8-+)?dSk9gEi}ii9(um_h@fwjVXW1 zRKCH~jCBwSFB&$dd2L`A20qD}Q|n9%1KwfN#L(EAkx_`wTM3ZRGJ1mEbcJL%>T#n< ztqz>S;y;4^W{18mD|!{;$=55;P~QDGm>D1nRg^}sm_dl47wz0f?S5C=F3(h~UuOiJ7VqcojTkQO65088 z2X*&7+*P2Jk`40DhS;kIUG{<3=yW1Yv~#45VFt`eqN0*9x%?~N3>eCh&{AMzG9iqo zfYA?(sWNj6XokER0=3k|L4meEQQ!dz99{v3-Xuq9yY7CMyUi8WB5pMG3>O}+nv>02|B809J0?Ppt1!IUQtZDi%@H2GGrQ2xLdl@n8HR zxwm&s*f_tiNr5005jlV$Klj4Y;SB!jRqHp97;(NkNEJ0@u@36(DqlFO*^(|v&k7#|EQ^;-?>rUxjL2}Gim02N%lXkJZ*oY+Pg3} zS=(JJI?Ty35>*=TWmS8DH>bwT-OG?WP^7ZIQ$-1NFlq3%6EyXWXpdRaaz>xiGK}T)M;DEv%C)$9yG5cgLxX$A>jCB&yV#&9eNPju{HTXg_s3Azw(<4$`3!GT z%FMmN)T9r;K_tB>8}!aC!ZUz+h=+SoUSbMj`_2fn=Z2n6;O)IR!Bto>0W)xOihy!p@XEW_l=s0Jv>2_ z46P@7v#X8yll0wBbM)84?GsMXrl4r%@E&i9Q?lv?9=(@GlPbJL^~T(Yew&JJ`raH- z0V&f&MeDxLB8eIG-np(?XCu|V=O7-H=@{(kCFmg<@u8MA@%Y_{x3R)&t2YfxP2*BC zw})&5J#?;jp7Ii09d6rI3aht{_VkRBiBK1|nGO^ryhGQ@TG6cq4>M*NK2 zD*1ak&zy0RUw(WK+CGgYyPW`uSAJpf%~{@!sk$I5)zU&s|4Xr!Zs$q*^R61HfQoZ$ zTcg8d(N6N3s!i(YAiUeFqVF*l0~W}^hfhR>R@r^Kv3NGrKi~pL zLM&B^K7Rr&~^HGtPsV0T^kxk%v%M=`GCWJ|ya?4-gI>1LW>Q!=`Sd zk{Tq;NOWj&*2dlBed9fT*Rj<6jH`J(HK?f!hRQP$6xIkvbGznHd^^QcBr`o{aYl6S zs}6+E76XkfK{(Pfm&Xs=$72$PFa0*%{z1G}B)@h&```Gp!x&#O2q0u13*wW#D)eMn zsNyVmw)3Zq?klk8)*hYi z9VU$en?qzYU$l&G%=yAk8;_ZlX(`5BVJ5eK9sq6y0Cj7LTND8pMI_Z;7af5}DfS1c z4ULq+9US3hY}mu7CABD^rn41bf5!n$ar833ruWeH*%befJRvpXKMBVQ?jpKV+MkLgE0qS^OtkXHtF;&LkKfMGmBxdvS(!ZO8i9TsPId<+HBR<4adE zY=I1$zY49mKi~8}ZkMbpu%DXx?g255IyXB7#wtn{3E~rgjfjht^<_e*Ei9 z<+trG2I$yUs*`LQ_k@Cnf`3Z>XQ^25Ci&#px1@@y= zw{>Q?Zj+a51QLS_8v-xzW)ez#+dC!Y%}FiUX;RYz_S#ORmJ;ufOB6l#CS%r65zCL_ zEbCyX0FC!?hlec5qd)Djlx+>2KzBE`>;ye+(kLb<7X^`vPX~q8u{e3ziL`Vz6Kb|! zdGoVdg3ATA0&`(*jH!F6B!^HshiA z9%J8*i9nJj|uqZKrLXPxNBW7j+$P9OGYV3|*->+9~uHoG(joTXMXpZN6kJ4FOJ zI6NUDEw}7;sBujChz)9Bf_j)oG^CeCyKmO)cQjlpYlD-oI~mK0-(=H8kmLjqq_#T% z8{oEn#J8~3XH)&BRF64^7TtTofs~fv%5V&J${i!)Z@}(Up?wL3dU}FldsXZPEFwJO z4!s*qddPTN^hx?iVTT&Eyay#bDib*9-gBt*L0bvggI>cGILg+I?yw=zeF*zO+d7&% z-4BqX>XG&yqo7Uljrm2rRhC2Jv=^+gzMyV$GxU;uc^|YO759vi88q7gLJ>+o3@?OCxmvf zg=3{rDPL*T*%>Z&;B6!Z4V0L3FW+ve)&NpDFV=3Dt$v$E0e{&Afd6=oAwV#j&0#yJ z#!yVvyse_gZ|5#m&|J8ajRk1eK={vh>G^N%bL8rjh#?Gurx!Ss4ySKD7kl_PDH#th;MmlXEv!7$bk8{M3BWP z-;Rw9{pQM@C(Dll#)~RqVM4*!FjS1hn4#q&8gsT*jcyKtgI3*|TWf0E$WSqWmL}PR zrxG20I-p<0et;S%<>dUPidJfDGWT|W)smlcTKo#i^6qAZzk;UW2Zlk=eACdc67u~4 zRt%(b#l+F00nV0JQi@j%5_6(Pgd>#-#b*=H$EV`^RZ6GHXkwv(DAcszddZ^z(NA;` z#vJQ4CIaqn+ukRp8fqXBdXO$tqMDeVUF!6 zFH-}@cASD4PCXbH#P-)xbO%LQFcM@2yL3+z`1nTQBUuKvxK$oeiSIbFXl6T423f92 z?ErMlkS8WHjEBXpH)_0@51_VNL5&N`J+L7q4h?1*?#bv_=VBIcl=F4kcb?e-G=+RpzM5<(jkUc46R|A% zag<_sQW}29u4Sk}#BSqE-y|)+4CqQ~`MN%utLrbr3VLOKn6T?FJw}Y4=6LB%Yp%X* z?Iz#whCU!>^nFw88~wbYpa1hf5`r3fhIZbsUl;)NOEh{<5hi#xW1unF0AJ$hYWeoj z(LM=0er;RA-Xo~m!i8+*_U>+1F85xna_lmKSoCJm$5IL+5}RluLAMpWidwhxjm8Gw zb-y&8CV$eSWC>d6qwk1Wfn950wx!k_?@ZnmFnS{6s`%Qvwtu6@ypl!wRfKVK5|lo> zY-i>85zOUlu3S|lsC!`}loxoldQO7eEj|xBLy@)UzTU<+kmLk2cA3|kX^%&-9h2?U zrD6E$Ey9A+A4mAxvU8MMO~u^26sVdnL4yHG>pOMEDa$Y8$pD$3qx zy&LA5Ri!lwk(pUzCrfHL%y(W*sqayW6))2?Tm)u=rka&k-V;PWyHx5Yl&XqKH5Qaw zzLrwIrc|AhI|zxD%=-a^F=SPLn7+ir^^mrMU z%I$5Z#*N6|HzCY?4lmPx5|P%9*Z_tQfG}nYi3fZ|AcetesQPhNby~evY#&FL-C+lj zAiL9hL)~tr$bCD$YaPY!@rxr}H5pDhholQOW1*m}JWBUO6*sDykFZP6J`;Q9fMy-gy$)#McAM%=RJ+So z8zb|iJZ_DvvRqH@4W9sxXa16=L7qY5M{N{I7*U%t(1r8O1~FFIwbXuq+Gq|L;<*my zV%;6$F5014&COn`d(f{3yIgT*&cWnAXt|CD!>)#wXs!0I=AB^Y$ zGo`|ObS8k)_7?8`*1xObhZx%Q!MAwFrE+H~Jv;P2Z+!oM<(fBLRaUm@(iIz4Tz!HY za$mJ#!xfjduUoTm#RjcW1d>cr*;_$ya;v$P_2q9xlj@zlGEEY_YUZ@)&mAT(`Q`v>Oy1p^?LF>)R^6qY}2}N<`vJNNN zeQWH=IhLQs>&UWtvWLSiGIgsZp)>3Hux1Q2*)}rdormG^&Ql2MogqF?c6?rEqrC&* zvwiKRQhc`FBsGIi#=FNf$sX_r#q52n(wLX!r&Zo^Cgr^hds3>#OzoJ#=+-iqq)i`6 zDK={J?7TJ)T3@@$`b9Xsnzse#ghQQM^OvPxe0JRLQ(jx(s}@kgWvZ1^6W4I z@788vM!Vz=t<|eq)=QnKb;C0LRT_?SWZR>Vj1?~CtJ7b zs!Q9~tSxD6+HOGub*gnsh1fTDL4rC^P2B+LUBY?IuhAr!l}UADT%%~Z9Jbfm?3DCK z$zkP8y$h^R;7c2_NSxRuHu7{nipPe0pP>D!w>zzqo`44C6>EV`OJNKv6)(bQ2h9u`hzlJDYc!>i# zX`+qzX7&-p`-c2(^{1fYZX2R_OB>+(tZtb!CjsXDNbeEOS=Z9!y`lz%!%SAGE$y20 zqrKB=cuL}>*=x``H>MgZ)^O)_Qh!ZY&+%p*!SAD*NRhOOz4P$xI)e|joPj;k^qo{y zGecv6J}V6F<#(T-F0&Im%w%N8%G~6 z>Z8wcCqs~8d$vKd)IhLUsRR+s*`oJxF{57 zvlV9(^>NEp?6j43O!ba*1rYGoqIZD?qj`m259>-1U^V8{X{Y%VEoQ`=EB%yzljC{) zw)Jsgf=20$V{jp3g_UNfP}Vy^8hD`HV*Q${OIeZDFQJZZ6*&{kezzC{-)R|K)47r+ z0o<7iPTH_}Q1CNL!GsOjdK-$CB4OmqknjLKlO-yX>mlLL2gg{Qa#V(}6V{K|Ddj&z znxaTko~rgybLbvoD|6^lB1fVQ`+#0_m`2k~QWKjWwt+`vPbrd?Zt?q2bu|CcZKHd< zyYY`3=?FV{GCQL+hbiv@*U_)AOitJduE+YcU#GdPQjE53htcVFn9-aW8xeuD`J~!MA@s#-fu|+cBG0pl*FTZl_TK~w7;x@kRey|*C1pX$D@W8kSSHfZ^`DE|e5iX)J!3Y>vxM-`kA6&jfVWwz|bpy1seJE@`LOxXPd zNOq`eu@pMBc?Z8YlM`e`xiV`~FljvsPO#QbP(h5wFZTDnMEaK9&lUl)*mmQEKd*M! zzVLC49y1|kA2&V7%p04j*c7l2uvzdZ_Fro1&%iqqW!TMA8O`uahXDv#Iso2n#P606 z`FnO3PhzWA#%9jRMJiM3XVuCch9}L5*(__2`)k$RkNt};OEN3!&$EW~T7{GJ2Qog& zmkxo*2EX2`vISNr-v zxy|T2VIV}Q4VXsdGMibd6X84f*DYZq7)=Rlb_Gec5E!CNla`w4I!fUzm{2rx9|Itj z1W1OtDcKaUv zz=A&Yp4>&b?J9UkElX3I0;#LB z!=ffUwNphwl5*}4w^4gZWDEcc#36I)Yf0B+n`wjvG&jwq)n-=NwWI)3q;qDJ?jl<^ zeVM7-gqg5HSF5nQsLsaWl;JQGmZ>pxK_!$V;UT@5{UA0XsT@{2vu6{j`^P|({T$MN z=Q6FVVROB=oQ=fHG@G+b^%#~4^Tu~Mc~w)bOYB=P! zz1;nP1I}G+%QsWmzMwBH|07SIa8GUT)>@Wet)^SZ&QH{QsZ^13hk~8U`>1y>_26Ft z+096lscE~O=tgf5N;e#!*sG%7wM9qG@mtOLcVd{nAt7&(@Xaz0c}2xL_fuS(09@ig zwp8?}$!bk`(IQ%QX)UT<-+^h}Z_EcN$YKqg zr9GaF9!fPDnY@p+1N9th%@lkex~4adKr>o(@2~cr zTEXw)OaTlH={M1>`6Zj*;At4&aavbgZdO65r(Bg~Yf>}$*7zftrNS2PCDN*<6Bt-r=aS6Q5~bfX$*@iA?qiO`+=exrI`sEqy9l)c@RE!kRHh^8Yc%1#Up9+QS;`bdk#;F>ED=nq?)hO}`esTKV$s7(?IV{hpBSH8{ zbY*`s+@lr7oZYrvBi!6gXJA~XJP-T^=WNwO(9MTig5G2)Cp+9wj37A-=V5ax(_RqT zURj9EqqONJi+!u9*7}aRj6yn24YU2perk#=po2LwpN_pv(D8lfz=TF41Bz0AhlZ2d zO=1mOCkmCAh6CIkJAMYWbG8YYMgQb+yRON;65f*ma34ipX--NVsvmeJVwF z#)bs(qBC4k8N;RJN_12)RWwe{C^6CT!miB9St{jnB~g0iIpt2GB~+}hQktZ78Y28V z&V8mEtfGT{D!ZDRoH>x3BH#7J4K*N*k zM{yJG&onc+!GZ^qKAfenz{+w=LATU(cHKLe12LCsOyWDnwj&|sOV%XW)jJwha$0V} zXS6%5Y$o-7==|{1y1s$H|F!A^^B>G z{l2MuR#ub06E&0DphNm*waO^~ ze7S)COd@Lz*lj)5lpS(tu~p%nJV}4Xc|9#9W3rrnxHNks8^rLJHdqKw8JR=T9TZKm zD$5UYdUenD?&me>NDRfU_hIMCy>szk{!x%vKdnS!U8PMyVO}0a*a~r2vrIZ$(@3m5 ztMuN;6^+1ru-1>T1Y0++@^8|HGGINa3?sf}_W!=VWyAd6-Pfm(aJzR8vL+y7Tx&2U zT-!ZtH_#Z2>-I6Me-HLd6JBzLNO!6?`v|rYky{gBxgC@@_@wGvoc>nm zxfH09<40~yrC33F1z>JXvAs=K(cMepx0U#C1-xzkm3bqEoItBL#&OH(aQxF?tbtJ; zVCDcPV;8u_W4bOC{GBc6ytL*tDOinCh;TpjD?5W?dQBXsP`!%$!k*Qm+jdKtcyM!kEMc%UzQUBQ>(pJf48Kzg!e0x^I>9*l3N}^!3rCU-qMW+d9 z@umM$D`;XrbDqNMlsZVjj@cvOd?|LG2MOIQC!7ke88dJ7Xuv0l;!=Eq715F}IWA)v zj{UZr$A0HOce2PMr0t~;@^*wi;pSXm+jl{un6}rjxFAy~<-IQl@*iN)HRsVZ=YY>- zX?;1zHRU<3DZ^hyo3eT_NbVv3BJ#DwKa;sa>7pX4*xiRFYE=YZoKqLqypdM{Wha@A z;{wfN=9y6i(2{|nV}Lw?uyh*SblBkLlDFeXZ7saRO)cv^D zw7w3&e}Yan<6EsU%})wEK}-qYp}sii!B0nb9DMasz~2J+NSLfuPJ+B3YlXR>K9Bp6 zWkA`69dW{>-J9>XMD``aqLCr)9AtTCylKumT*1{_CmLMMU)#Rw>eW}qCX=+?-UVL% zGtQ}uJr4F#{1ZbkN8P2XuZ*HFwr}LmL9KFUfrf{a#R4^dmTKHh9JyOSLIlKWQQ&rn zJ6mA&m6vy1y;6r|B6&q}doQ6$HHE+e<*8C=n@A?^KGJ4{IX_AB3WNe0TaDuP>>QSU z4~=Osu`iXy8w?EK!=rx;xrq^am?Pm}pfRSq`nEGX?c0H`!A7Mk?gg6CZh+M2voXW} zS%wQN9?-bB2Qx{8pm0uCT9M!8o>}4=S!;c`O%3!hi=*L~?B;w;q~n~)w2@o}Qf04J zdIx_zR4B3MvrbPX!M_qTXOQpD1v<5^$`*7U4kE$%haS<}ceX2h81Wfx1lwp2mPF+=e%z zr@!XuOYDh)=Btdx?9xLbJ3H~sw2aa#Ve2RXv)}tvs!p`DWLXk-qQX`QLt{4u+*@>b z85tJ{rE?RNDU~9*@9*jECWKJZkbMs#SQ1lIM52xbYc-_!kpvAQ-$=Z~ni>m;tu&@5D>cmrW}$lvu+vYb0N5>y#)j-9PgvdI3f64C_1ce`I(kz zFx&Q40PJ@FghOF9LAaGD`>yPI`?Iq{%AG1j?48}f6PV@vjfXM#N-t#1dgvep4$Q?Z*> zY!c6I=vb_UW5+hcDzx1|g&(>KtUS9A4f)`1JS+4#eVc^+VR4e#Zxfnh)IXDlnn9aq z58buXDK3ktn+vl8LyC}?PCzLMpLtgbpC~S$tPtZ6MXqfcx$yG}yIg=U-A_lp1KK{q zXcK16^=VUZcd)e8X9vh*BMjHZ3OAQ2=%njP|BKu|$963!UzEp1R;G)r*vTxJPGDxW z<%SbceQv7X2RDCm+$i^L;Ml5>SwkVb1&yB_W)atg=AEjI)3*Ur*YNZQk#+(K15m50+NUOesb_aD&6Frs^{sW+_!hCD}j5Yj{CIbCg(o4_c#;O)?FApvv;TacdPyH!-A&{ za$^j#uG0;{bFv)ZY~+&>7?8tX77}+Y4SaK4ntR$Dmhnz)u-94D_RAfjG5CkB_gYg1 zv*(Lv@&6#6eT?%EX=wInLdF1^P&zF;)YFSwdwA#ybO8SwOjUl8YCm(;WCWF^rYGA; z)ZPB9W-u836h*aN9t_M-GgNN;xILOki`29+X_5KYd#V0Px|myzvutUArux&={V(d` zuyJN@vrF7@rn!G;fK>OaAf{OS8EOr=V8AAD&QNybV*V#^wvNY7-&EgD4ye3X4rn<~ z(zjuwPh*9lG@4|zT_68?;)*T7{_eKVQmUU)kf+5w>Km2bsp^C*O4DklA$5uTD+)5N z0~lXIyyb)mat8(oR-XxCDB!1*cB#O(t;ZlAuzj2N@&>JIu_=caIs4bi=GXKNc&^nZ z!;MNJneOmV>p?yJ2~Yif*t~TA6xhdq0YqqbN(TQ>t*`5P#(H_pQ*SH-;b5zSUzmtj4vDWXC*%UF3@r}gg%6l zO|Q^`!5JMK!@|LOW4PLjX$%O;zRNT{MjPA6;S{Z0V&^++uGLojH1{deXs&z8dGTYl z?sS2k5s0u(X_l9*gi{upFz=j_t31-R+29vI{39e(VUG}pz!_SXZJvrPm}&hYWqv>z z^#IML;un3bXxAg1%5ERIKB0}S{^LPC{;caeuc^nwFoQ!EwNon`Gh40c96QfL5H+tE zHP~r5NUYr&vxPUl!zi&w6d;>02{0T*SK#V<+aWf&b04zO~Br;PF5U{?w0w_SG~ z7|a0(VW}`#3B~D(;m#wDPkHC2yzQx!cT%yhOF578+LZU|l=l;gyrS6GTPX5Q9=<$9 za1O#^U!AF_(9tRH91MBRC(8M@Y+!PCeFs808KxMaUv`8RejOO04?98&zYd7dvY$if zw@I=Vqx8d|I4b28el2z)JwN3=K#>!ReZ4u7)*Y0=w-$a~6O}=hfXV$tqV1tXd-VdT zClUzl1;hN5SNOFgB=iRqS(Ne$zcxlh3{d99DX;MB-=i{+2$4t2=T+kogyP*sq&To(*V0Ba(#9c0QT76>sZ-)jC2xmfiCmk^CiCs?WRWwq5uu_DpUq;~N~M2lZ)8Av9$u97 zpKikP{k4i#cKoy4)Rk+lj-5nW;F9uvRw);?>-C8C8zdfZJ_eMTE3L(pLEr2@W`@LZN@UyZm*X@R07W;iV2~30F3C zE(6ktgT%yTQ9{(j1Tc&x>qz5OF=c5trCzJ#)nrbaf2+nA_O$tv6y04xm>bS3w!Q)Q zf5p7O&-G#yU9qcl__S+u`&a`_?8?AWGoY;GVW_r@B#(8HO(_(Utvqyx`0ZIKPQXtQ z*V)@|V3};nyO3qsDepVnzKGj1ti5WrE|Qw0*NpcPg3u))ny>_pmJ&av;%nj+xjo0- z&VlDj3r&9M0L?`X4V_l8Ck%lJV_(mdqWuf5cXG1SPR3<%)WI_q4X$0eGjeDNo)wk@ zUW~Ys<*ZPkmXxKX^Mf#jp#Me5ht%?L91)fI}wW}DmP5XNTF+@^y&!}Mtwx? z#2`Ce3jpU>wBAv4?VE1@#JYp=n;$`?7UXFf4)SJh&vUoqe)Aq~Uznh2=a$m6Z&2}- z@rq8PG%U@5=WYv4k$mWvTrvRpvzM6^saGT1+Re7}h`PR9 zalE2ar@?%9?zYerDQ(+r(Sd4N40TDZh|yBAT#UGq{%BhM{;W`HjKssd;s)F)?@6_T&p_=$$AdWs+KX9ILdXfv`J>vwHENC@O6i-Cu zMCXkN|LpN~4xJFO*H5T0W>5rA5VUik0puK|YrP7k`#i~u=b&_dgTc!RmF`E}eq*81 z-3;Bz&dn$?_GJA;vdY~?MWyV*icaIY^5Hp!6PoyEk;XnIg~9% zT*-1)*PwPHswNuSC!{=Rnh5>32^B_nMD4^NJ4FisM~cSV#K}nA_s_~mwXa=swSTgf zj(o_^Koz8@N~NThe~b>}&zhX0S)$xLpQcY<&Z?pOe4k>|Xr|<~(M*XhqnVP|s@!Xt z5%gzDUaOgsSB86AI&TBHn<+WZpDAHs#FUiRM_-#m-=G0L!{sb4x%*lFE|-&)+Br6B z%I?;jKyGE#D6Fj1k?uy7gET_hXQ9HMT|fjj5$?!P2A1QYROfwM*1OAhMRzS3ywt%@ zNOblA$xr|1LQ>*nrmKDnms8zkcXZc6VwaH*sq8Kw`T2QP*9H0c`AYbaT!J8FL7Q{jgL{|4LqCcMmEd1Y;oh3DEI+N?za3)y zv^Js+h{;C8YyM1l{rgpx@;un;Q=%*?d-zY_=EUC!0;1 z-LOYuQrpFq+tmAaSOr(RjI`!;-Yb>XEZ-L-pKoUos8@E@gxA~#bDqy>j`W|n0Qa(W zD>gk(8Sk8hw3?w^{!JSD11?{8k}QL4P0954S@HCD@3*M_HF?8|fV$c(mDPG6 zc0z_Z?!`MP?oWzymYkap&cildj{EG~9Jh`*4YTo`4+6i%rnu?riv)_reY~@swKaCF z0B4_ZskiB9jV#s@39!LW$!Mit@ar@DbT>~}KM;hBdN@6`?~X_9=T6?*z0>8>D8ue_ znV*a7^e=f@ARL>zL+|Aw8BXuxtuLwlrtB^y-et}$rdVPfOiaauG`J_{D~Zk%mTJXm zIL6YT&z)XFKm%n?kf_VGx|gX2OU`TTjI|@@Bu5T$0G0`d&uT>-O){38I}Sz~2&tZ3 zKT9W25?kC5n97mIX$QR^ESY5{9THe(DbKhpQznOH_Gl$>&4|HqUHKF*P)cTGfgh`g zWey&kNXaLTAood_EF6+u) z%MnE-b2vx|cQd1|cT{iGooc6{{6$}bzXi%SoNCN&MA*4tb|iQLUaZHvZQ^hzr>|nY z9*&^_$KW=_PO|T&|I~AF48@nq{QuQ+_**yD`v*DapHJqm;CbG+ZupCU{Jk2*H*y#v zb^On+-=hv%p2#6%EbV2J67$gZSdL(MUdtaS;lFX-zGFoSGd*v|K*4^FXILuVpmm42 z|J;s_?X0LB2p0(K1ODfjcRgE6_uRfh?QLMy$UFaiiIz>`&UBw|P7&x07I^im-q)d= z?%t}o_ER~>nT2VF6RMey#ina9w!cAye@;qrW1~-^@GX`#Q?WJs%5JOm_VV09y&| z0HEpYEvyXh3l_##Kg~nV$z`FgW|;kTY^}QcKirK3!S)IrMZ-dKQ}zZcwquYSZwzEt zh+OXM4BIC5$bGW>*k5GX`V3&c0hq}ZJp7SeZJ__}iYP!vER| zZsxrK8FQeSD%q8P{?hC1Eb!xuR#T+~DJ%qIk#&nVO(!V31BtoC%Dxt_o?Fk_wPNgv z`Bya)8)j>@cjP)=j4>K43T*j5(7vAx8OOcZ3I7)cZQ0*Jdw&7iI=GFFgFt<9d36Vf zyj<+Pyh?8vvqvz)F}lY(^YFo+L%!)@34Evjr7S(>2q2y~orSJjC2!ZD{{9&o?67rU z8|SiBYbhkVI_v>6{!-5NLjdworb-ar`3}g36M^jSGxa*&?HY9rEsdNlJ?JpCTG@hj zvvhNsHrZaPH@R1OA7v81=E2eZoH{M- z7d`7l={t2cA%4T(1hc#n(w}mq>mV;)CLL1Fo*7m=)B9PGbr!_E-fqa&D^BL>TnI*I z{s^(<2!Py*DLFPYB^=b_cckTd_z51mUTwC-mYH_xnU38KY!H29n>kud&su)oRS~S1 zuBO}vpt8h5V(dg0#SW<`SXli#n<6pK#XbIiVLRg54SMH9xIncawUVEEiBc`m=*K_* zVMOxOs~hWNl{!X)y*Vv4-2a^mQqFJY_#eAM8m&O*(JEHX1WVvqjY{O5_1>?|O5O)E z8TMTC1^UoA8!-Qky=IGjxp%pJzaKhk|3-Oz1M9MR8<{q1b#oKD#E0H0jG|56shw)> zGs|Ya0ivma)uXHFy?VvKus!$D%ryX7;^i{=M3!*feBscIo_BJ?%2t`}_K2 zW6@&hb{E*&+zZf7%Fr!lrC-ZZ6BcmK+h?YyU%@zs0BZpU2WVwVKAEGVmOw3(5~2Fe z`Y-IjzbWl+-LW%xb*?MWz9!hCFzI= z(DIfadw^*0T?1MjtifhUkWNo;xP#+BK{+LT^&KG0gb4S}r)QYLD4kBz)+$|q7W&q@ zMl+SQP;ap&U^{J0UWGGZv@Ob*>)d988TD-I>41(Su@o!<=Bx=($T~6Yf9D4y92qj; z@J_(tAFHTch`))0F@A-zu$RFa*e!;`5P1W!6v#%za_L?TvGK#+re^bsGx)`FuBv_j zFzz|so2mU~3{t$yvhL{_qCmCowh-@DqC|FWlU+=H@Ax^<^&2kZz~F!8E3wwBTzmCK z*QujkN_?sqaquKs=3ctdk$RO5^1O}~VbQqV^;D|^1@-z%q=OS-H9gw|G{C<&x}Klj zxP(?fA&%C7+QwuY7oFva=blNsd$ht>Rt z=~bFK0S;ezC$E-a-)$P%$7(NXma#vlkGCGMGj6MybxFM7OE?9<7Su(op!pD(<}rdt z!?thk>+4|{rAv+{G3K@og>1*@fR&on!vNoAj=7jiHqE=smeponhpFj+0Qz>5#>LqV zPK)K>dHvqV>oYc*#w9QGW17Ur&6`hRRMl*SD~_|R*$zcbY&Fp{i05@3ykCdoZ;|A~ zA~~z=qm`R1w^!H`ytmVCs>~M*FEiO+xi2k?YRzjd5W0;h5EZk69nm}E|1Rz$@xeIHqQ`1J;T0P0hJS)wd8d`JBR?W>3_XR$>@ru=dN#Hz)AL>MSDRcF zoplW6SH`2`&TSxB3~$YLA@y4+Mh~0fEyT)g%dElKq|amc^hG`tI#Bs}^xz2OJW-?qE%~#*M>REBe0<{aEdFu%%W02ZMTyG#jmJ)}c`NldR-Lsj)F& zb!0B?>$?QowUp6ucMvIcz6OWt6*|;?Hr8A?eysUVaXtS`Pq%)&`P&(>@!CC|ZZK?)S2HfGW3tJZ2Lsnr z_OCQhx?n7TaxLbGT?_(%Mjd{MTF51`U(z4X>{5x(fP#<+6btpbBxYqMBRyQ1!b==$D}urW9N7U4uvN2$@B!|b-o@HGZX+y z!Ew&^8z<_2+x?<`4xwcUWuJf?ZL(|{Hq!{e#=40Gv}LNf;a#yl1b^SYLa(OfuGUwxcZfc=#*0Na9>BDP&jo!c2+ z(pHL%K#4Hao*cEIX&oAh(eQ}?P{b4ikw=5y_WhRtPX`Xg;2~!P;KhJ!v%O{GWSnUofF* zliYjLbWc}kX=&KXVq1bJf*?i;NEf7~gixhzrtNJ3{c9x!l+iJ18J%&VL2((!mN@D- zl2W4MLW@eAg%)(2xU}FlQ=1}+%lyB;=RN1#dvlX6G-a93K$@I$-tBp}XMdm9_d6%i zHm+X1YPDSt5h`eXhUmhG>xDEF`i8;IVGWe6;+5O4W7_;>mGHK6iKnU~zAYer+o~9v zDkTPTPIubXuJm^-kK9@P=C@LI2VCk*_SczBuC~QZgKGSM!#@PsbwmJp5_JdarH}&c zSN{JbKj!{%ifm=#kHYI{ZF4c){jNqR31Lvu5OG_^NUf5~2TUSEm1!=>CJ>?17G}rr zmCrleN&lcvtX}kr;HHhbt%-znyHVW4$A>$X4k*!;+yH#x9nUF=XQ@=LbBlt?k#3Uk z6f{paObKA3O9*{zQb2qKTZlp)R4k*416~neah-K)dLi-#qZtOI0l`hV#_L*5Kt)8f z8ya-H%G9LX2g8TS4@dUY?m)7fK01!f48urb12txEr*rN<$yJM>LAQ4GzY zs&0J?Z$!}OL$Jl)Z?lW7azP^N8_70ZtzF)@JVvnpM8UcWTe~m9= zZeD4<{_nzGqaY4@-9>ux`+i$uHHsS&Uejh|r&%{y1MDhzh&G#{nHH?=q{EXZe=PpR zrb3=1mvT*+9urt&gJMKM*fMExyi6Tua5rT7$0`zbel40^9p& zS78HmsxH4}lE{FQN0fns(I5ADS`#r4=AOkJ7=>m1SNV*EQ0KQvf1)aPSvdxI9^1eO z8zNh5>ZsVvY~R6b`xDVFd74duPTJQc9un+?I(UF8{0I;5`Fx*yLPC0N9S!xlFSA*w zGT)bXC4#RTBpUbJMCE6>&3tYSJ!5_Hggu)4mtM5=XU0SD0 zTUCYhXq);kpgh=d<^N&WKp{YqXW^5?rKq5nx^(N>2qS

Jm@jPIX7{M56C^or@ir z_&nu47NizFbYF0x4;SGN&89UrTxJtI85C@Ua@iG`78;)Ps*l`8CNK1oH0Ufa2r?r9`R+GmI z9!lPC;3$}2g-6^Q#23rAKLLCP{VfHIyotvQnx4e0;s@NzZ|J(lU9Z9QAaDJZ`IL{_ zH49`0bsHtesVVcWu046fHcx;ppcK2Plm`Z+3NB>B4J_3*u1a$m5=iUkXZ&TALUeu;cuVF-UTj;Mz z6K#PY3@}EW=m@zqGylDG`>hx;&^Xf^Z06j#>*cVlc59G>azl0&bKbO7_ZWo#iRU@> z3xsomeLKC)jpK6WWYtX!le{6a$E@wK%B)|G@nGQ>nJ4+5Usabp~VZ8mOW?DYU_@Mh#w0xHO(|N>h$uF6fFR`rQ zj}a(Nu_(gD4gUo+@AZr1Y%U~jLXwn92P;2o z5Fc%#vwc~9DkaHTPiQ60Bs3-ZSV=uvN_(kJ;RCPdn(*Xp4~eHb5PLBEhPwCpx_z-R zQ89|N`7*at=J}{Z_W`2}MJ|hwhT2{#dk*Dt3QaFZ!P+A8zJ*qo~2I`+z?o~=ZgD*{R7TU9VWKnn~kXYY%s>t>8?M~#(U^A zjZ7VZvlIBHbejOvgHUQ^OD|L|{~zIjd)cQTU5rd@eQ_FCTrD(9scd-Kc8j<1Ae+fz zcDjF{=Y1YD&gzvk2osZ6`@0lo#y+P^N=Hd4p*4|4xf~fC_!JQHI*z>}cCIr48COE| zE2T#&b{4OqmqrjZF;c7b`nsH2XW-SF6G=lEGbfVvyObLejM}#lLzo^@tzp79gE%vT zUxjJNtAzG$!uR~qlpSjJ407EQO<#+mlgG&&P6poT| z;P@t+WDH8N+(78g8U%U^{nX3WVHBz^^8?`Tgk==eyfj_=wz|p5^$#KApF}T9=_msF zg2NQr*tbw(VXO@s4oJ`EgXB)J*@=Kc$G~*58Z)%T08xN-kq`4BXAqVNecaJaU|8l5^VgjLh<`IJTem^Mh_w;le1HyXGYP}? zJ7MAVRuvKaEH_4Mjb!NB745=sA+**|Z@ZC*uX|ACHFoHlUHL#$ z^Y4bR?-~TMfBor4f;!DFKfC&-rOQHNus#$L{_c@<@E&AUVasZ<{-W9*O}6KdWKBOv zvSKtvx8^Vux3{;s2`%;;ENQrM6!ACjKj==>-Owm&&PW88ff3&cM&vNRPDe^b5n)6f z>plKC*zd!Mg@Yr00luxDL2sTobZ;hr`8QdIlD0O8m!Nvx5IpZtp8gEg?tuRwDBMd( z8!<_VOVv_pfPYqMwl9^VDed!t>^q*~3o=oBE#^}UCKj_G+bo9{rrtv~jW!1ONB7HE z)_thThHGf!SGhL&?ft{K23FC)0RL#9ToWCQZ-}loETV-E_(gn(UcgMoy*SkPsuvpH z0@~Q{R@&eoxA`7dCEARPY*&RDCQRX3i+*g&Sw%(#Biy=#ioCIfcA}c%{!6P;^aDn^ z0R5nLE;9SI5xwrYfvUC}n$D{#2h=JHH(K}`pfddo;gTk*`gN`9V+9xy^#{OU@S(dMk>2=krXU;p&#Ge-FeEx?v-@n3CMcL1Q+cX@xmvwGY`5G zv@IbqqAyz#5Eyi!IybMtYp0t29HYiz6Nx9VRskdKr=&uHp};2!u$6pB)`n#fk>u;PVk-w+R8Jp3CCPt02&q0+zZyEMv?-hS5yWIo)s)k<)Rskl#@{xCSLouS@S^G2${@*%>Ux-Gh6kq^;!UD3}>$d14~? zHQ;Scnz{J5oWd_p_LKyhM%k?C6o^Z#j}#C~{sbWJ2Y&zvtI2JN0D(IfAjV%$Z*em^ z-E@oqotq+t$@YWx1pjDYc<_+~$O7NKJXWWqjH}YQ-*@mjIdDIp9UbV)H$&g|`~pmSl_Jj@U(B?J%IcB}8IC(Z zSEqPHfMj-%F3(0gw%gWJZgI2XSlkn}@^H!=p-aD&}S(9$R%bP0Twf)&fje<6yn0OwF@Ti( zS!$ql|88%5&#xP;O00d64hGW-l6DI-{4=t9^|DGq_Ece!yIY{5}DA-9$|I{Aj7(i`HOIcZukHr z@NHHoRFu8YmvZNLNJ6sM4|{Ss?&}4dVb=$#gnP173D`}Q6fh~%R5iCzR|jt(JPJEN z+qzoY8i#W3Po#)hx_4t!m$R?L2a6QX4K*p3-e@FGWbY%|=V@(U+osy(<=TvPRpK~e zYH(are3aje5t9=6`F$8D+n~EZiJxEJt|~|Zvco7@X3LjDed!5~mb#M-n{oNJ2RGqe z*>V@?qzUV@K5bp(Cd;Q_m$HyUB_M*Mu+S8j?{G7BwqWbYm7O75tgoNK4Cx*zhox(r zeA1vvdRGjRq}TpY2Jb+XSK*Cndy>JD4o?viPEX5D28)LWfMh-FT9tEMhMSb;*#!;r z+(bqsOEO%J*{-Tg;*)#ES;0q=qbT$0v^yi2#f1iq!^i~gt*(+wGJ|FKZp`(E^k}WL z{)?194O57&Cm5Z&hbncv9pYm0TsY4g+5xp2a*y(0C=4&cHJRJmo{sbp0bXlGKXhbe z4U-_De{wAIFL{8v{$yvEk|*WWB?COOjVj3W5irF?KlEjEaFzHXkPfzpMRD$;wtO~JbfGn872&b5j6w# zZ4L0xT^lUbEm|pErp4r4oFM!lM?R9bW4e1j;u=l_H)AsIK0r%9{BN3bTWmLKUdwZe+6~`^|ut5iU6*vZ4qw)IUqBPak4=qVr+kC)4^bw%~*itGBI- zp?&y%))rem4?*<#0y_AN&y%-d2k{GAZ+H=cNcTYyYOfGMyvSb+$|3~OU2_sE-p_jx z+wGUUM*mmXJENHgIb6;Hi*$tw%}&QWcKClU`!v*$D8Hnt>8BHx$WlE?+b7x~A#T`k z4kPuLA1Tjj<(XjoNQtWkk50BL?Vj8g<5FX*X%&BRamFzs9r(L3%x_Ido2L@*j^$Zi z#2mkbNeoX#KI)954cZ> zLT-FRxuE9;y6`om)QLD5O=;V*1upIr{fzR-Pq_Lg+?5(-L1od%{HRLAv8So&xS8BY zh5MxaMw(H$6lnv%8g?< zUHjh#@QR^l!25AbmfrUQNAeiDI62%@I$nv7yKZF15r1}STk&3szbwQ#+%J_wt;mj5 zbny#V((#mff&I!`A@PzdS_jR4p`oOmmm_A`uomE2aSnRrVwQ$m>B5uNh2x1?DsHEl zKSs^Gq?o1koq+NsPG7weG0Tz-0Dmw7{(mKAslAgf?lisCU|^OpMKm>}FglDY*yWJ_}oUYmr0sQ+B zY>o@haui+6aUU;`XNkI)o1cg+`v_h9vB_?Ofmw>V^=L_cOhbuF$q{3gNHiKG-jrKd zBH(iK9AcKPEdbYLz`atXq1I2*h4&%nA75#x;Zrp8`KXzflxOMw450kXKshSRlE1Ew zH)bhbapAUW|9gOc0PV|h!7NA7#bd@SQ5TCa%aRVd_FNS_d93~K#UvZP zNf+ObscdlcD#g4|#H)T=LrFVFj9KzoxWUobM!*&EC`)z$+%FBdLB%YC+=W9)KlR_E z3tz&FG$@#*c%;H%&W1G|KsE^C(dlbMq5+vEc`F@qyGaVwMY^a0{gai3N;YJJFK- z4-F;l9162UBXT?pDTk7NYI^{#CG8jKvDt!-XBQ5oc`x}HUHFc5;c$ADXtE?$_P@2_ zY0T}2a2mV!(#(HH%^WjkISNpUR@9Pb0Od28Bf%^KPD=mXA0&A@Z@O~h7;d}zUjh8n z5%7b6Szc%tj~TNJDE(CYhAwWeKJJ*M`+$a$c3zH{rRQ0I`{;;QGG^)eJze;jb>Vno zmexPe%-uB_DVljnF-z^A0p$?`<#5u_3*C0d=GSm2%u*5zElK`Q1pNO>%+h@VUHrF^ z!YqTTSE+v$^*&F%M~q3L=^rGD0gnu?d=(2fB3O=J9u z3o-IB`Kv^|GWI@t8JG)=H`OuoC{Y)4^AibpW9Z`3bn%$^fbt6i<)|=A{<=Een5B5dh11nc==bF3h=wv) z8ruQa-C??T%$OzWV$q7MKZ7p*j|F@=2CH(kSmPy`YbRQgXK5&FU!Ity;v9hckpcHg z#Vq^JrwgB%aJ(@~*BqMJ6*cpcVwQ#r0OgK};pRM~#`cA7yW=PQ)LjJdUyp$QUn%{R zTtXK=JZZ3amSWy0#4O#HYA9*vhpjrHEG5UkxZ9nR3*aC4W*5L(q?H#pO*`ZX6>P za{<6V6#+lknB^$Cc+5OY@%)5ctavM3d~oVuVU}XvD8ek)Yba^wh%rk(*Ls|#p`IH6 z?ypYvi}aOg3+lRwF6_2041%5|n#SCY2&b|2W}10EYUahjED#xdM>C944bKm`0?W0s@n;xS{EsEfJz3A@<6hAw`4+HuD$tyv8v?YtZ@ zOZ%+=_vz`cWX!VRopj;9tqaE!voySmW;UIwk$S0ZK|LD)WtV~S%G0xSZ3OsxW`t<* zefygIhdO;WXCYOEaH~nt3sJmI#y_O^mqU4*|+g43wk7Ecxr| zc=Id~@VONi67;T*0Q@Ir9Vg6k6kR-Ko+avHZhpcpwr`<}&(X!f!7RnRQG{9U*HG3S z1!l=-=>`Y0M8M_dIm9f@4*=W~2Hc=xmO<{qp=?79pQQ_1P9G%9Qan=OG}eBOX1*RZ z^I~9@2$bB)4MFMnJfJ*ppd1xu$)A+tjaee#b1N>~cC8%%|Iiu739}qU7mpdUL|x3y zPuRsJU#5#6JoC6?mfEjqC~4>AC=Jy;1aSXlz`as2OT`Yl@RhTUH)iR1gl3+Knt4ev zOY_$OWz*T==6r?Oh8n&F@c$A4|G!cis(p+uerEPyVU}XvD5Nm^zoVg~og>C9`7GVw z@GKErTzMjIJ*)Uv$L9 zxx!k)|_@T{j%J-I~;YR zW8ubMy?#}5n03wC8<(%5REPpPe#crvPOuR!Y)H>aGH}Y0ptjyhy zOe^sf7qj97pKw1Snj6XAZh%PidOA4(TfdrS6{ zv@&0jq|RC`Qm&b%r;fXvD~Ned(D#k5O1A-U@%0@00Rx^Eq=&iE z-?^#QWPY^qO1q}PU&AD~F=|*HmGP-fS{)BQOh7s0rLRm0*B%wsppU=9J6)UlH zJ;O5tC~ws;Y1(yPKoftNKc+cjgGIdIE4bvL$(3Jc<3ir*kv!3p5y8p2!qtj`u3eC- zahXSnK~9>=XPfBqdLBf z-p?NN`Cx|W- ztEB!5R6qH*MYWQ9T9D#`1`q?cqOZ5y%MJaXbfoD2WyRv%wEUJ^47V@S%oTBXF&QYj z2|fG}(uMFXM;A0jT^hoFVs{e@x>jtL!WO$7JdE=X!wxc#LipSaHP`M**NU}V59|8h zav)B86daDvE-o_MY3P& zv$vm4_Rq&%)B@JRD(FJ(8RU6@pt~t1kM4;UYMqxo%zbB&yD`DEO6Q(+iKb|}dnkc4 z^SxM_38jV;OT}xJ9x1XZ#^Emw`Z1yqViQ9(A`&rn38_R8T^NY#%;Bk!(X2a@@zu4h zY!1ljd&~S~ZzkU9*0+JJ^3|J2TA1_WVA+ z5nOUYF)o2dHTRL*0kTLG<12bwjV!Sb(P`4pr{u2GFaW`;lelnzF6Y zr4Y?mP}f%orR;D$lduuh#9!*o&)TD`duevOHJck`H{xMWSxb9O&zWx7(jKfzYp6pD zU_}E(|Lh@#Vn_S}4+x4jJetI1&;TS-aow7#1LXs0Vv@6**=;VgG^^KNSI*4-?t zQOVVx=gx|X1FqzH#%Tu_0Wk;WVxtJ5yQ?<>v+gP?{1X+X0kg)r%Uv0QmxRS?(Mkt1XoqVvDBrbq3ltX&-g4;!YsFp zD9F-v4OzbIvlxa*!A7WQ{f5xNYdHQ2uW?Z~chTA>!6}i6OO7(GZ(UtxR;Gq>-QY%5ckcJ9GP=R^2C96(S9v~FZqKT} z<#U58JUMAyM*0^B*X(Bi9&TGS+TBWe$dV)r3=_Tska6I`XWh`<;1^hG|0$?OpYZHc zZrB!Gq@u5vGN=?-hxB~=0d`O60ju_p5dWa$>SGjVbJCAkk{rh&38uagsO^ZmAxGWq zV)DyE#&JhWXA5Y~>yP5!54Ux;6@P{1#`ve}ChGi_XH{6+C?NW;w8ht*S0R&6J1tZe zye1`CmTl#00G!3{MDBKRuL@?S7YQ?*xX7OM=&s9wI#8`mF3}my)snXX;iq6?Dup50 zWrh`Z*+RG9lXkkPTsl+NCZYh6(yEazY`7Ir;iRwC zo+!WBen+ph$s2D%uQVRzh{wjB?bxx^4HN6(Yo%0_zo__HVZX`)Y!?VFx))RLG3p`J zXk9^{?uXMER))@m9wkfH0{VsF*aN+LSZor75hLhPX|Nsh^Ve=sZQ>qaY%G8cF2`-fa;{}trxw6Rx!s-2y|!>YOT8NSj7X zQ}~w^w4=$Dt>X(LEt0|Vfy2y3Ba1-G$To$q@6d3B2zviI3SpD>_*aMmW+CKz>erLj z1>Ru?OlAj6MGxQW&Y|Kx)HybVj#Ig-QNm{P$;_0f3VdPM@{-%tGWg^S31@DCl(Je;T3N%m!Yr7p z?LZN?HR?#+J1Fujz{35^L%+Y0Nv|GX5CCEf$=ojNadaz8UW~lAQnAAo7}mH zyBXOAmq@dGamrn~jlcD5y*F?}7Z2_-|G}M~yzKLCw)*$#Ad?W|BJ*HVq{!}hH}HOk zF0$w+af7+EBG*1T!Ikc1clsat$wo!X4^xy1LYrmndnos$m1E6oH;`uB9oa{uce+Gd zgq1s#b+2y%HRR|=$zRRZq8wWa{&lB_D1*rCYL!gY%|vY4UHLr09n_&4*#VQ87-e;! z_>=;vIt?Cr*tmm7^2pVO7K;2Kgm6q)fJPw~n%fn5;GE=61+n_|6X00%Ga-@3&^j)us#VT4@GjfV9s$`7L81)|1h~=#$gT$wJgW z{4)dd)M|H8@{ONoO`RjUB{j4Eox{WR@1-lBrz>dN#wUVN36#%|x)RLg;g>P?C0OLq zcE|h3{w1j3g@D^Hl z;8mLJvR9<;DMHLKqK`hrCeFTVZlEPrq!eF}4Sv5%Al$}2`3NGlv}>!nr`%}aO>;{q@9ovBB2A>Z zkdr1Tpem(WSKeS`^_D#3)c!%>`%$4_Wazd~KHrLoE0TeD@{*=FDP1rPn&yw#{Q^0@ zq>ZZmrDdi)VG-|OLv3au3qcOu2e#w#_K*;CDE9|hSz=t(7pXvpicGO(!aTK`2G&)O zl>9G`y~OwS!dGNF_fgLT+1wU}|JsrNWz5w`JgH#_Yi`|4@jpRRO3Q-lH7_vH%C!@= zLKA_hIV7X)_mcB(J)BaC(5r_*v;0w|roxk#hqZuKBWCUE|C(~zIqF|nj%;F}xEowG zSP>qi1&pfz$heBy4^hR=uxZ*-(^P?kt-rkGx7V)BANK>ocK2|0Zl``-j6M*s5l8%C z3V$Um9JgdG_;%vf!UA=FLxEi(@d#T}TfGCe6b^ik3jR1OSlCjo9xa$?inDO2YVQZ4t!Si1L0m?)xrv)Rwy4dp<6W6_ zCTw{P8I&>@3(WdEsk$KbVJ-WL;EM@yl765Y$(@xxK)OMXag~@F(KsB2Nwejua{Z zI#f^~?s8*?H@ex^yE9mxvUwL3z&!;el4XX!cXuBYo1YMK8an@W|Gj2_ajOM=rQJ#E z4Y`VLz9qo2qtzzM<1RExCmG+7bkQQoHo6HOAWG|Xmt@_o$bOV?PI#pY>gEC!lO;f8I}B$^x2Yq&a_O>u@+A!umQ)z1CS4U<{~8F8dO z*?0W@CS3)`jTa6i#@s$iLCs+CjiCKbaPV)vKWF~$gT1|YSoQXzmSmqnyTxctOtmc7 z0Yd~C@O*!-a1kpoM3+5Z0sc=^iW#_o?Ak($7c5w?P#aw2oOQ0vK%dVQ#u%n`U9O!i zh)q;v6SA|RKc(&Ye_ZYI!TAcMfFh?)LAACB2Cd*?2X4aThsufz;`d~1cWwbNGAxpkSr^V*b>6z*@dIlq= z&)}p1gcCCKPw3=qzGHhrT#nT#JzuBn6Ta+Z(FJkxknYHy#mbRf2}EYw3ejl$KU1o8 zgeHD2q>g%>TrsFNLsfRZs$UdITQov(Y89KZ1}@imo@ zckOhs0y#Nhzw-P(ggY9%kO_8BBIFoVSz6c?ULYU&kq9KrB9MP4|PX{OGGd?BXp^jg%49YiAAz)0lSieqoSMQ|LKd@6c!J&Pbyfg-zWR8k39Ro zko_4?v-5zXqkw1JMmQY%l~D#xb^!*?kiiABvZ>hrEbn`5Ycan(ZcTL(U?gy(_{@;z zEP0YK{RymDjp_Hmhm_xG*k@g|z`B*^*QO0n#d#3hQUOidp1wPgzh>%w00TR;Uk`!} zBqg8s_0?2|^^HlJvI1PWNh3XUvkRVdN%4Du66(Yno*@k!LanJHw4rhsvhF{q^FD7a ztgVPSK~ zJ>ZCm#KG^1w5xfF%#|q^0+D2o8ITCcI}<1p zO1cxoqDuSm>lu^Xte_@c^XC8<)8wAcfp;ipvh z6VM~hCRQC7$t=(HpJNr}?erdH?BLcfmQ~7zaZUm$<3ZZrr!jHQ0D=TmkIrRmhRG39 z^MaSL=hoZkY4{?|fVSu-Hiv}IlfSxNSGuVaP`AS0PzQLG-)u1Uu3QeW2886XYac-U zlIv0d%CEmU35Y0FMr3uQD3xEkl!iAEGHOUCt<#mg2}v)ey?c zXUV{ls$2(;BCv0DC#YLvM5RV@#Km)-b?MDKvcaSVb}al#OgGb#Z@HG8JWG4Vz_V`G zrSE@`;R3n*H%<=>63E4`7?v%Nu^#=h-GW=C7HQscR6cNqw2S#z;n#Fjx91|)vEZ~N zgY%``(B@N>Fdhta2O5du>4)!gIJuG&4%QAO&|4u$N-W;uMQhY4WD`}El~>&HPNO^9 zv_*1+r({2He1hvOwHyue*vwl8?)M{LeKEzrj94tK{SC!hxk*g?RLJ>Ab4gdc_pqn{ zFK|nlh}G>@naBJh^R(8Z zJ6#1Xu@Mlu^NfQr0hQDuMfH?SH1zxbKn)-DH9&=Jo z>*I0j`X7q?`_VSkT`~+7_oy1GgB?x&@n{>WPf}`g|2QDr1I}v(a|ob*2xY$5e;FYH z<~5%>R5-GC^`AZ7W_iR!*}vC+_8eBqZ=mZ0O8lt*68$vLdLr3>?XyP=7i^H28PO}4 zzThcHrirb`kc6m=`JI=gq)-vHxyK z4P_pW@K>($lG?nx8D{@Dcb9VSx7?jt$FJ8etd5N@^&?6C4__JUl%!NdoST*Oi++>Z zmGrz%vQ8TN!5Kx`ja(*1k)_QuwFOy{54tm?bsEwuCNIq0t=#RFJJiC(L|(Jhm&`92 zP4;`aTTlc#+6pGl$pt_HH6yp4-XfUc!oqP(Q}?6T()2z9(*@u5_&e^Sym%Gp3~Z$c z8%`mi+*>!6CN`Qd;Mp1PCoRTop$KS*&(QpY=8P^C*nAWP8pcuJ`66*6OVzZJ)-OaG zaK~=va7?w|Iq9NyJf-i4-6}^rrPa&in@cp_uj2j1<>EGK`l>0`?OtOay}0!?SGk-# z$Vnp13*ERmyfM%+0kB_?RAd!uTHq45Mo6WrmIiklf9EYJUOE!E6Lj7*vg;1YU%2xz z)qAPiYU6Eg-dmvVl83yYv}j{cnpfUh65qzJc{TRxCG5Hn*$A>9)9&sD(?-~J4?lKvBGkhr2CAu4;80U^W#y?IlN@s zOTJN%m5}-AQPv;$thUkRHr6h;NsevB9#k7&2I*4y|w*7GAli4+}mD>I2o`Q*G|P3TQ{zv3qvuznuO;X~cJ9s4;?7PtbY3vp6Svwk$o4z3 zp|N5K1zQ~_7KX42x4XcO#DrA7Rry-LnTN?&awhqDy{eC4GsmY zCcZw^nfFwY9k6j-LN5siGo;fc`TbUR-T;Nr-&r9XKTZhVkYDCHw060M-mHc$ zkyT-ucH;@VXb6;Xg0R7%oMTG_pfVoaS>E4BimPewwe*fsIbvs2}Om-S;@C` z;0Bd!vXVmhkyYjb3QNg9i=$h?ZkOCuh!Qp^{hru`=X4M^bwe7)Bv1O8yIANX*$L02 zqq{WehHP`mHWw_?CJviusHck0Q3W%u?BXk4r29-b1vsoE#WxONaqhK+zH6sT_PSRsa6@+W>S+l6 zo#tbVhmAXN!6C9+!MyAfmEAl<>`Y%$vq(FP{?l&AW4+7`Tgx{kb&UL&U2;NRgNez} zl(t?%7w$k2&6JMGXfM2KGmxaQe9ZV)_p0aovwC&fAKj~dZ?C-hA2;m}?!@Qqo16S+ z7jSlowj(-P`v$81CI}tn?3o#NQr6XGU2wB(we09*aJmMkGM#nFtVV^=%Gl^YMhRhr z(USdd_68d=Eq5$~Ll)Att!{|6L;SuCLWng78Xd`C?U&Q`=jhfLW10eWEVYa3P+$#V z+cP6H9V|`lb%E?a(GKi*BeL5G{mE`6S5RXYHDX{*L2Fba)P`Ut+Ap#L?R3MX?>A;k zEl6w#R+!H{yEHQIyoqXl=&PZ>{Z=E*Z|r$r(m;Xz6gUZSWRm35AiJfPV^vi(^nqRI zyHoz~Zdbvk-_85jQEvPk{?0Dc>qPO4SHgC2O(J z;7&Jc8;{g_f{Y;i9ZtPfh!=a@3HS|+O}GhjNITotj04;WI}f@cOly!bZw^LssOMBV z<^%VS3eb^E?D2*#xr+1+a2}i}KqJa=`Z{5x^ZodNP6AFbziJoI)TEl2F5v2PqZS!v zvn@4Ujt9o>YZz>}GmwRMP_h?W1br4?Of0fsAL*M+O%YE+$2B(3MMv`?Yb9?1utxwa zL~=W|LWWA$bD|XgCka}XB?HecrWKY?Y}V;k**(52>j2&?*c6pTIeJnsLS^BWhRAaP z^m^i^=;H+K23RKH+|4d=S+rp47Et9MtV&is92twFH`=|;N_;Pdy8xU|qE=3<9`8`% zy}87I#FnV!ekE?PM5!ET-EcS?MNesVWw6sDC3}91-G!ko@{3tW;jbP^VYafS!>}9A zy-XOZKEJNo#nknbuM0h*vI+NtNOpKcQXL_ZHaFpZ zkLIM)3sFZYs6!&Xt9)2UhJ!mv@s;?@Phm}J=S~j)Vkouulf%EI!JUa}5RRlG@jxyx zHD;fucV(i~d+_%U_JNt<6u})K!fa>+B{}O!L5x9AQaee>--WCj>?7?irZQPPrL6Y* zBG^{2Ti6`zm8bnq!R=Zb>q#q$+L`CV-??2>*F;+#2~(fRaUQw=ON+K;feY3%B(kjf zm`MQEq0HYVbD6>ghyy9WK>L;auLCC6-AuB=;q+g+S;@Z~Fu6m?&snlRLJI+tHr}Qa za=hEd&sC+1~H_Cr`9yae${w+TL)F}VM422xQDGueg zzg20*Q!G;0CTiT6(Db~GI<_Bqo6T=0|M$s%VmP8m_3={gQsR&D5{<);aeB!uB>&Rd zie`h-F+HeP@;`b38_r~&Kj0?jGY?s^Xc7w|&1=O9O6>9_#`|?ADIel2W4xWGHc4hn zldJNQOkU3`R#N!qzOYma8l&p!XG7sfMRFocL|0TqAvoCer^ZECRyJK>Q>binZAFJB zS5fc*UvMNsQYbyIDZd?$98cULr|rOAu?Cf(}F3LuIgF; za(e!YrIbGRn4F2wS_gz0WZXgxQtN8K`ICY30*DIxs(B3++z0a%fCbSM;l%*T+ZYk= z4kiB`$)#m`gOumDdi3qV!fzF8`SxYLF`jbf7k}}pMV#K8TkTksh%DzOZwqP@=%h+H zl;0H1dr6jZzogv6vV)RQgJ9fDLHJ6&st9GXZH3{IFQbE$_5y7}nIkk>r-4ru! zM5ota3&rr@dk|;}qo)z#Z?q}DYV>pe;g242;uf{c%S$WcVajP^pq1gLo1+kV@jKCx z3N$RzpY&+BCTnu)QCbR#oBrzL-AojGhP>2pZQCM_7N!KG8}5?*>md|F`Xv_ zJ>hJi3OkwhxWWy}yU`P;l+Y%ofgLvP%zU{-a15x2n|DpSMODJ-dw?n4e+TV!`gYRM z6o(uP?pZnO(#b(p1-!K@)5$83l`TB{j6Ru2y9x6du`P2XUTXLsuoPG?m9i$|G3V_)BjF2<-`YY4+mZ*5eU1a*rfaJA7$^SE8 za;uU*06EMtJNb*HTge|CFuCGxl0R$7eiMt;(%uj)&R-)Nl<_M*qnrU*^g9q^2vZlX z2973Vf-_`y#$obQqye?+q^cH3)qPCI|C)&34U8 z$G4%CBL8DW#KGjSo$>F&o<8aa2Uc*$Mx(R1jqQ$mh}^jn@<l;^l(}O*Fk|`2cw~4lPLw7EdGo5p{1EjDXcK%+Sxv{_y z%=#F4#p1Wcf)kW~w3dH*_?GRU)vOz{D^}c=$2+W9y=+!q%B<5)%S&9oa@j4}8yh{i zE3+6}ydfkOk@KR7gfS1J<6Oit@h7O3DfmeEmOG;!eu&SgM;Txe6cr(SEjXaXHw{zC zW`=2^A_(}}E7(VfH?!*a`-LEEA~Qkn9zDJRm`=Bb+48RH{=Rh;5*QJ1@8kJy5M3{5T$|{o~dp45M};_B}$mP zyaPnIr)aN!-Dctl7}&h^mbcjY8TZ_zMAH<{gdelxwh+T}g?(n^5X-VdL&I$f;YkH_ zCb2!X08Q6^n0()52q!{SH11G#&pL{OL$MoNR=pPo{-&mz!Dz9|ef{51Y`-z3&**R{ zUQ&h}WH7_U0QVJUHbm_EmG}K{J&CfIUYGnW-|pfYG>Nkp5ed-OZ(FU*x5wbd#1)M8~?6^m8Vf<%u45q{J%t z`Eb|QAkD}?g8`&9p|6l1DetzrsXKVH6>7qaj5xUFf@+B9?}GC(@;zYTh17Dx$7tnz ztua=UROBJA*`eIPKxx)HUoE>*cG)_cOS)O+DZpdMRl~GHO75h5Uu=HG7UMgSZ>_SPzEKR;&&qP zhhcJJOt2Fq!X|RaC~uMXLrufwuay4Rzn@H{AMM&O;vKCQ!6nIA-JNXR9i4945)EkW z7Cw)&&*6Um2HAs>*Qhs#?%FM@mfpCAoqfxy=EjwN=|Qvn*Brajpa!7Bn0p}|Q)T|v zIX4?-Da1l%xLetn4YExpho)@QE*$PeZaDo_rLV{*rahcCmkeiu(ER+D+)vT3Me`p= zM0g)NBPjVWXzDQi@MLD$s%5K|z8FYgKHf5oD`s83{D##_SKl`4VzCPASFL{gnpqdG z%C5X|=~^8{HwHpqXI;{`l(Xe)`Xzt{bN`FyMZjhQcjUt6=F66^?WYsAqz&raZ3-*2 zYp$68rW@YYXgnYjcE$YpYgaE{*;L?@6LCKw{)=8iq!;o|;aiT?3O~fGR+J%RAj8)J z-YNWMU}|YM-2xoIDBNlvqoxh+3$trit!})fap{`IhE=PS)jH6ylv$3Pe?Yuf%zy3j zWtv*%+~+Y~UQ3r%KhIeC8W~^N60_5yy;r7!%ZQMhj<9CyC&)Y2@>aofZD%`hDp#ht z>mqEmUs;4eLEo|uD-!dWRvBK(oI$BZdI2LMUzN_4^K^wAjc{(o2<{%#DZ&UrKR`V{ zrk+|bv^2-N-B8T376vJfhp}Ioer?+hdwAFmDlBRGdo8X2CRz5c;oDC$1VKrLq5Z$j zc7bYfozqKjh_^ z|Juu<>eub6-Jm6!9?FxKl=FKxbg`LZ>(C~u7$1ef2ke3_?8 zRvGK^s@s~GwVO*$5MCPq0lu+uWj1P1&zC~RNN}Lw)Jl2~P2yDMSa-0kM@UI)|(P@^VxZM4mY8Y&a=ACQ7@gUHA)idVko-S0Us?nFV(3NRvc<=AH zze)s`t21u}k0S>X$ssv3xKetu#U|PR0N_XCw4NVYuImd_J5TYe=5bi3QvE|;6mno3 z*tHlXFjSrw`>F*yY=+7n1*R_KdGi=5ZDfA@&ip^_>i8n`9ETD+9vd!Pvux=~(VR8c z-?()3`sHDuqbufLxpbu=1?KvS`FV)N-eZ2u-Y#4VLv}-UZR3qGXXd}JUcFTO3@>7j z;Qu4$DCDA|SFn&Wglt;)mSfYx4>6k-WeC}{@U?(V3%?y9o0gAN4Q+*}(w<`^YHtzZ zw3ou}MrhP085*@=@MzSJND)71pCcr0C5urlpT!Su_y@M?qn@WC6q%jCh>k-kNeUq} zGvi0GXCOovJ)6r)%Y#Fru=onrZf%mpeQekctq?s8|FB|O{M1yNn#>-|GR@JiKeC@ z371YDDB*yZOmKt&opQ!G9HH8;^7RY;D={`g4OhLs zhN$<{_&oFmef?*j7la>Tcm`PA>K0wi5pJ<1O-l>SziN&$X<((onU=a$;-^5Crz!2xw~v_hE;%BYs>lIcYl%#jY5QyM;&e;K4OsW8MJrVz-0 zCY|o`bfw>=VYz9kX?vn{ylpCWQu>FKe!VaKA@UL03%&(I{T>_&`4GH_zw5c6Uow8%ih;!Ztt_?~~;`a+;ULxzk zq1c(1F%$EW3Uo)H^T{LM#+Qg8$om#+y7RaQW-^a$Ku*zH#8ZT?IW8jn5OWbxhLDTk zHBZk3gVLYyVxRN51^A2t?#MFa3tBPvU^yf1!CqqS!3ss(gHJNa*;$b4DJhNUYT<8rUbu3c*ZEon-8Mg3!JpQ*Ncz^oT+ zBzVu6!l(QST@&%vu}dyU#=DVSr2uyzQWQrQ2d_`NvQ6$L+ik(sQXyeFG$XgJ?u}nK zSbaLaaX1uA1EHD2(>RX$$`uhF!E2Yc!s!Gh-(o6W&<@QR|BUZ@8R|X{slaV(u}T!YQh|8*KIu^&?fe;82#9u;eAJq_i(7|>rY2u6 zE^^#&AldT{KuXbaM8}c-m5zDCnw$}d{*a!#r}%&KFVZd#X?4lJQ%r{oT-hh00GW0A zwu><$2)!cPLERsW*u!R}^^kT2TI-bi3k9>C6kNz)t-}ChYMa|7V?-iB_Y>rAAwQ?e z;Ey;hQ^{HKted=!;l)}g_Xf1%ce;6MXEe73?Up5ToZ=Faf8reeia*R^duITq<=z!f zWW6iny)Cx9Kj#Yz@_bcU znb~nL{}paK9;N#K1eQNB2CNk-uJFB|GJ+@dUZD!e913nd$~-}9?Q761WXFY3cPn~G zd4ev&vlYaVe#Ir|8i#a!3^7)0|ZconOwonM=m-ilLAlN-fBs+|#{>Qb(A zj7fk67zgW}b;F@pxY+E3#U7V1bxaK^Ea|e`#mSqbEIWsDHNsBjqK(Gi-77{qRAtru zlq$O+v@jkJPU_SRf9uFhbnkHl5Gwq)sLy+pE{lf0whIrmyw-LJ@e*`gipcnER>MBp z{0N<;O~b~B5Oc6ZY_iNDbzn@;9p&JBnlgXo%b+kj*TTD~fX<(X24cpsOWC%P4O)lY z1hfppMCi1f8`?8u_=?ZKF*~~e)EpYCB&*W4JFLeX*@BsM5T(hWY z*j7S`oJHYpaH^Mcx}Rl?ehT)JwicT_cAK599)W_(Z-eTQ{^Co{HhRBOQ@5L~A*5>+ z&r$pl1b{gv0Jxi}L3BcdD34xrDBB%mqm@xL%7aQ+7`jQqx65*3gVA5F5}`4v<9C$! z2qlmlWqeteSIEjN*KC%2(kKwFQ~vG_a@u(hiMh=pO zst>DKj$w)V7ZL^ozu+ETM&*k6uW7t}dE@GarE71F&}}jI3m9>&(I^by5g8)sy64$` zwN>sl-tNYoaRGt7WCCs3k`?|Oy@|J~R2zzeEp80gP3(@Zl#RFWAE-kobGej+5^isl zLlH>dveuo;sck+tp*gBb4Jif3PU5B&Zy-h`vQSPwiQq}_ZHN>0|A`7^a-WLk?lA1)a+(lf|7kN6g&K%f_ud) z($g`!X7#KPvlkrgFjmoTx)>+r;h>jHPu^D$KLnGOr}1VZ=Z=#g{t5xa7&gSGc|A7m z`xWyIJ6+k>9O07JEML1~X>;GNIfm}Y&2stZu@G?OnWPgK|s3;jJ!!n_Xx4%;J9y$WS%bfl{?{c!N}AsLtuF8Ao%r(cEtwY8yA-W+{< zm^^K5n>I;253jV^?`Ze4a9#vioj8(~PR7$i49v6)Q8l^}sD-&^*Bry~Cin}~$`2C% zOV+$Lg)k>*y8Jx|Y<((@AVbPQpLf3-?$1;brzW2>2cApFttyFXlV}<;47~fpcRit# zudrX|wg#g-57<)*B>OxM2vZemQyHy_G7@@880S&7RO?d80>y5eJ*HP9q0f;8<-Hs& zoDrO>UR}gTsjZxLM|pF4Sx0MH@@5~kn+V|LrT;w2+xUpaKrO@XyVn9RoF~?wsA7Z6+l%dcC)Tm3?^#P5$YBhJMXH?N` zYwS|b@U=i)9e&H#>%^7k^y(qOvs`(~lq=?6v1;w|o0i8;@-`@uCfWw8n0w}PKicb+ z*%ghemvW#8QfakZwJN(zJUH3askPxN@b*U}t;mI;SBSmLU$(ljQ4YUUAW~H?{KkPJ z?y#MZai^HRF}=yl``4s`)hPmZ=l_6D=YZ}dqE9w|cs$2UwSbY3=>8d3veBJvH*B0D zcRhGuQyoy%^(hgQydRP&6#A8btj(}XfJb|xuGlrKD-Ddlgd<0;$0HP-L+H*}mm%Rd zJ^^>YgcKMd7vHL+-;%_MbD%nzTc)wN_shR<7V{y}lmk z`*n%FD#jesS6j_cTpC^u0Eklx$GKq~S>Nw9IN~Oxb&nkWloUDq6(;Rh(uaJh!!*z{ zo~(c0Zv%C;B+2nnI;z43HYn-KBw;fkiN)kPEOy8uYyvjurT!9CLMNikE60)GTHUf( zzNhf@Nv)n!{0h@u9$H~x_ZlYB#1HyyyL-!(3!h|qGA_oAypWaj z-#&>L)dog*{-sAr#Fp|~tstp>GD*MhH>q7of9^M_WC}?w7~Myd!E)+axbooqG&cXI z`L~Z1K%9OUPdLTPabb|otsH0LEiJlYa6t=0#XiFNj>a+N-nA$RQ&&_s3FnN#7Xu#? zBI^?eIJk#X^A`k91=_94Q{?v8608Y^kowguS0U@#H47-70l**%iW3mol}Ok!ixK{L z2;raC`8NCLLB|oqwhKgdG>;;AmXOnjmPS%V%>if$~vqCtaUb51#(I9lZNC1%@IGnDo*%W-h z7evpECKxbvPn7vvaqGHiZo|yY=aBgeJ~J*jmzh8O4o&N}&MmAq9ji5xK>WS@avkSV z?lE5uH2PXKaw_C=^Uj@|4(nSe+<6`6QAqKe=~7gP61x<}J60z{m7C8e->(m!ukO|4 z`=ien50M~d-9f~{O2eM+SLXK;lgO`P-_z}y@ce$Y&LP*P!{w@*ORkR<=PK%W*mlW0 z^8bBt{=&A~mFvqsS3lheJyKuOPv5#Ppya>%lCUFwtthe-HrrbHZ1GptauWt69)@sr z*HdnvF9*%MP_rl|=w6D{tDSSPsXWW_lfux=Rz$VCQTgefbeRCkjf2r;9W|<4rw&@Q zbT$e#rQG6&d^I&&-5lZaW0%I>5%cJ-P1aifBn!+D=sgH0NP2{=fq&(M@p{{|}gzOMxK?P^^@MkS@U2lZq& z%mmSf&0`5p3}y_cKh@$xs38mnRB|~r{DK-hp=DcE6V@(;pQ7zEsDwomJ#iDbjLwg} ziIA6=u!d~WBMed|n!JWLQqe}jPW#<9Zyrw4L$1Be zA3_dgx5m}0SFN^0#I!ZbZf=y%d?@wwXbF7Z3l4Zun&)llz8&9{iIW9uK_RhIo@3Tp zITwZpa!VR0r+5lP0*rM>b@~_{g;QY0gTbqPRNx7smn=VydU5lp#lVC!7#EDKqzx{* z=c;d_;1F*+W>?-kyktl+j3L??%=UC}KVQwmW{jeFe-|c{t4!t3spEAgd;b=WiOKim zTAdf;Vax)o%X?YP;1a)sY&5}ZIQ5+$eapWyyOKtm0d7c;e2NiVqt6P(SEU%vb$If` z@t<3-wpKpo^Fso{{5xEQC_pN+doK|(L>3(QSJS}fUHLV1U1K$LQUe|ah-cxgRQvt+ zuUX9skiApZ>O8i!;Vo7$oI4vA?@k~{;mc6srgzek_hh)i=JRVuJ)bWDdWe?B3XT)s z>K8}6nZt##pS~wgt#YFYPd|d5!nBn3%C&f8jRJGB6i}Vx^tDfXuaD76%(^`h$}d?! z@o>L$>6k1#*=5oFRob7m3mKWIRc>UsIq~!`O+8qKDab{%S2kQ5izi~gQhaD8v0}N2 z$ogrpsPCbewqX%@|E-^C=qGQ*V)E|udB=;(vYqktK`}KjD5bqy-LMYU4P3#nna<53 zM_qbR_Ni#*Lr0V5>!_x!@7_k{$XfVU7FmdI+AQYwEs&GLU`rp=YZ(V(~cIdq*5D*@wrJI&>n`nFF{@kFn@1Y=>gsqbc zCAHg2Z)MjbYAXhe9TZOo_a@}VmCbJHL{@-Ob89-M6Tc87vU^cAp=aXCc>7z`>;Jk3 z7fPr|i%vYf%az?vu~S0!BfhF>$qh8V!*pN}ebM8|qEct9bEjwB8CiGEb~Jy);Hvg# zPEascO9Ctp=5exL7z_Orkq^a^?TQptJ=%)9D>^1oh4P)B;sG7psN`4Pt+Qx}KP`85krGG>Q*=XHa9KnF3A!E0gj(r zeg)!8%`#`!w`SM-n+2=IFUXC2*drv2OFfF+{E~zl_AGB#j(C(Yy+|C%g_%c#i!(Yb z2Ta3sE5|&(oLBuI!7-V;VNwq0c)AMU{%MbTK)48XW9=&e@E;*Q062j$We_tqj!bA6 zTH_QxS8*XT5S^BOl&|M$EIH9N9Un)QoX##`chC)LBQsfbh-;V}eF72{GH-aw6G-2_ z>zghHa1ZazlXANL0zjnhW=zn3lq4^@Lu0KgNml?paX`yOSJ^422aOf3^tX<91yXh3 z@Hv7@%{1pkNLP5x;4BNWi5z5VzDn*J*c!Yik!}+U&w`qneA~4MNFxvAHQ%$==CGd# z^L1GOJI`)A^Ait)KVGpVHXEZicc?^|e>yx-j_zKX<-^~+7$Wh&^y1Pt^=tl~b@TEY zUs}L~u3x@x0|PK71K7|H#iJB+T)8ijY5S~}7fFY5)C(q-up@X2sCe`-;_WYe(+mGO?koOjqL{l}h5-(+%B-y~P<}t1hPGfa$Jb#R~1SG4w^R_(cwbDM1;un_s6DRTRNaDh)o+&N)x-@}r`z^w!FL_0# z*Af=5K%95Ms~BT01+ih9#EXFg*anKNMmAXMl@2+s^L#!BFU|oRKs}Rkj zC@nVV!8?38%7*7tdgHsanCdU|Cb&m}?Paj-j5Q)$PwDPGrK5ZJd!cf!jF+}H zBSyDH-qwoQ*&E?;v59!;pAc6kA)RYfDG)CqKE&m_waZI_!0vq(wap z!;afRd{L%ma|rCVC7c_4IAjxi!09BrSa$r;$kWYOpWYF9;?uXK3FQ81epET};K8(z(_X2<1`&)(yUi06Os{`**cFc*Z;iH|=HB9}r0kF|5T@%>PIfsqX!Y%>ba)=p=+ zpEK*NU_}FSr@dG{GIwz4&YX{u2Yaxihfiv8Eb_Tl+%)SAc>u${y1KM4@AvJBhTMoN zMy9v@?!ePq55gB@`^=vkL3cy0`Ccf)^sEP1p?#*+j6=0Kiv%NvJa z+Z4-Z$~B`W&Ntr zP`U7jcN}&4y_UIO0XL2{aEDvI91&0C{SRyk;CqFS)JOt{!200$m}98TS28R`QP zJwr=(YfD^>ZRLK6zu?g1pv1-4Xx@XJ`T>cv6F8*6^)Ub&yk7x8{a~4Kb7V)BM`a5h z_!11%@x)Kj@EZ{)y_%F$&AKgepOPNSasdzV)7(A_?dlB2@$5S#Zp)(gykfr%64+2IzjfYpt1_(fp33%>UbGGH((!+Q_o zcn1?3S@K_OcH_VJ9*%rv97BT}b?0F+uoBa_v9Xc46`!mbfobh7z3{Fa(i-s;cd_ko z+jWf#47;Ort2L+4Y<$CnShh=q6JIi zwwJf5y8057;dqn*&-UYT8CM;%VC3_r1#-6;SCpwyACzhOW)Ft&UM1VTXO%qt0x|Q4 znQ#z?l^SNfubMhUTjJdgxdPIUyNSF^7C>N_wAlt;hFE@tnn7RX4M2DoPe}@g4&VvI z1#oxPv#N(9i1?D8ojwHJ#Q^R9gDZM7Guf8(%yhc7In}c4|I8Ydvt4QLMij3@RAZqV z{(p9E^#4!V^m+3C+0*0yZ%o>FidH!0-2URtiwsj>R-}-(xaoy4{|Sy0xmfI9bqUXZ z>`rsU#0v8uZ(@bVl%0uHKa)9UVugftE5-pgHoK#BthX+N&BuBRG*>Suxp8pkc_~HK zr}$=xE_QO*eyk3=r1)|oJhWka_ZBBJuuL-q-hzOz@x%C_5GFn^bpo$)HRdxXz*7=zbIeqD1$UD} zc}E?VzyCELz!lgB#*Yd8aUYSx%6M2Hsx^sYhCRY_&3<3OpTjdm=2Qt!aIZmbK1Oaf z#<$tb5@}-U(9%&im6m?%%nPPp3$`D?CYxw%KZBnrWHNsV5T$@af@fg^w8e$oItS z!SGv&Qu4h;>8Qlk14?`S$C}mE%yrIBST?M5nl?@8A^0SzU-#yNcPK;M{fKZirpXvV z1JMU?H1m9%>l(NQ;;3Ps`RXRY$-Rc)URCM9uK>b9mR(NfcnDJvU|;M1mR=0{vk5#p z2FHj!ERT^7XW*kqSZcoN!qO2O%{H>yjfGHL7wyNLd8eTmx^Sx&^4U+Ek7`H!7@60_ zM^?@!tDXYy|1{3SN{2;c5KcUQw@F^Snmr^@Jv31{G;tEH!X7;o&%iBD9F0LzvdNcU zX#s`>3h>(MHKhY1B9&50M#Gsz0j1#BJc%gy8h&9dvKAj?H+jI*$S==FdBC5e^uFkq<^c(>#tK2+MD51L~~&`AN6rJ3%275qP$ ztYE{Wl%KJf>PB2jXYyL(hS2!oBQ_0EeRzxX~ zcp@1kl&{2NJ!w-uxh->YPc3DJT!0kHhrWO7L+`^ZB02K~=LZXdSeOz;Y8O`CK#IRN zJ37$*{cl6Wyy75!L+QgS&c6`7s`&rIG~#@AsXRc9uONTrRc8MLK9TF-n?9p;@Cbh; zyYFa{t8m!0C-Q>am6ktw&@1-lx~b>@^u>=A;K0QStah;DWR8kGy8dR#x8pY2ZarP?EB`lU zUx8U7PLbMvAh|GZeE5XY>i5t@Nl_GA$gyMZ*2g-g;Jc+T{hMM_nGNoYVct6M`ylel zBU7*Z3dEC-FMcxt{Ls3?9Cg z%dYlwm#ARp-YaHOQCA`bo{1{1O?BZDNPMwUAn#@?Lc9;v{Q=r{fP8a8x@zC4vrH}o`POAQ}Js!uPe!pHyeHC-)P`egyr(o9cBYYh(ace%WX*(33GsIjpNdvRe`2+FEL42%^7WzkexqFXM?cIEK~L=8T2wQ_Qj-c^pcN~unxQikiM zq-Byy(XyF3*!(P2KnaVxGSCY81z+ z(PIh|8L8DWV`Y0Dap7ZXIhw6lMRTCg2V1zc)r+MuqnYKpI}(2hdUO!ciAZ%Htvi%# z1|O}b9tBVh1}N2jT7{1vW(~BvLMC*GhHMnIxi;EK3qB{4S=Q5SMj;|5YQ$fHDuxj~ z4wia^xaDfPa`{w8M_nqLYFUc&!gQVzaImeA^hYK6%mC;p{3WROmLzTUqw>_J+uAYk zva?aiRZIChNUc@P)UFvu5t*={J--zFEt74}ucB9iG#a1&gArIT{96dI z1f9Dz(Gu*JpP`Y=$+U5ALcwhGzYkD6c!*bBZAi$2=lig9FD2kK9i8Hnq$ar*ksJne zb0(Lj&9p-Ss^)%1hxkBE*OCm~;D;BavR(9*A2zMJul6IvP=%UlZ%K8~Sw2>aE(k?S z?}wlRsX^D#Ad1q=>&bWb^4TWZ70l?f%hPi*Ez5{CG!8YUvl4;;&B0%S-q;aJ1i$bf zC)&pBD&admfC_Yd0Gi*`!LjqdpHLNeK4c`~8%p$;vhj0BG<#MT8xaSd%*~>NF8`T6)k^{T9r}Zw_en-V@VWR( zEp3dINzRv-b$quHGIW&UNXrpv$fxo>Ir^f2sBpjjCa$^^o-KByXkmyzCn&_#?6Us0 z5T{oaF}3gVZUKA1d3Pu@Ua_hU3=NmLBSfI%6%sbF3wJ6qvpt(L<;EIvXDUx`DPbS*b~XFW}l4c+M$+8$d`oV(igl_Ejc0chcDnG?88t)`}*1>Ba-2f=Ru z;xuhn5PfS?9O=i?ZXNAiX*|x{nS)r%(q)7z-_)H=cjKI`5y)`{`VF+rk=3fuOqVI4 zqP5kGJhz@s@`+X0Bf|&HUFu7qF2VZ~n)#*a0$4Mbp3`!9LT{y-NDbeBj(DhPfUWAnC3Ev;dYMO`AZy z`D}CW>E!%U(SWBy$0lfXIniv0Sq(T7kI&A`rjO`JceSjlMbox-cckef@Ny)0XB!&N z3?Le+R;{w9y`xpSM%96hbXf?6HB5CP7N(x9mWzG0#-6Tpt4qZ4iXC-dbI$A4u*%{+Ehjptr{=|Y)}f}rD``z z4KPXR42{zv=9iq0({uR-XgBoAX9=@Fh#L9a6vywvE%DL*MD0iPqB&ZD^Se#8HO-uy ztZk@lDsO38Sw?@<3iRt^A2sTXpgxY%V0C6iS5ySO;PP-&Mn#{bleBW9rIS>uHCQM; zMKU)_%}Q9>(9f`zprB|>P0};k2GP<<8lY`an0A?@y%|&}mn$r7=!3M)qNI~FMB6P| zI!VKHWEM&%CGFRg8kRQnU+B1ul1|bgIzpqRle8WD@2D1=BxygU)UdRnr#Fw6PEx-u z;%behlXR=n-JH+yI^%9zMM?YgJK$m$=G;<~GPK}l}eQE06angqVqtd8r+)V^`jhW?b& z3)41zsdIWU=_C!R2&Zi|QyE=U871x0H?NMDPST|_ib*GF>sis#rY|j;9Vcz*SJlKz zC+Y3|ib*GF{J6ADUwlI8eovh=#dqi`78q%E-zH6= z!!_t;#by8Cpu(*9)Fd?@9L|bMf9sHFY16f~UKA&7=m#DaFP)?dnu5q$}rA^m* zHWepr=ubDtODE|MEybjh^ki$awCP%>r{kmzeZM8~(n;E+t(Y{*t@Og?H(e`>$ylM1 zG%aT6o3}@&ZMxP2N)7iGLqF{B0_pjFZz!WTmW9#|&=2g4mrl}4UB#r6G`Bli+VrI} zkBE~t^e40N(n(6>i%BPGT2HjJ=}UVrkCQg^y;j6aC#ig8G3g{tSrsj9`qH;bby`g8 z+|VC5GG01KM`Pu@ptm^EDEHCP(xxxHqttNPhJNZX@zPN5$Ht{?`r03r?)G!vWbSu- zNV;Lt6rcX&2?c38)Ff4&R7^TakDMGWZTix8r^HDc`jpe+rIVCBy_j^8ZaX7d+VrJc z&Ww{b^qbF$mrl}+XBU%B()H&=OPjv5^xQaULm&6Ec5ABY15bL z&yP#n(Ep|M!ro%~(q$JElSV(kupn)W0>3Xka8XD)K%esSc|Ki zXIRSgla9NhqzvuwyQ8I&wDg`hDNL{L4NIARG3LG~DMR~MX>QG%{&36vaZ)JPZ^CJr zelYlfC@Dj`S7~lqru`3oFit8-KPk%W^ zO3KjodNx`rNk1sX)duA;gTBhI3eW5_gTgRuj zUkoMY6LT*|%S~yaC;t>D2jZ5mM$478(D>Kl$;oE(UMSEASVwFr}1 zXw4gOav&~zGn}5W-Sm&YM9C@f*teqPQ1lo77AFVd$hV{AQ1mJ9#L0m;``u_c6n*>m z;^aX5T8VDGa1l`STi%b81F`jkg7m!QP5SqTAvvFT=HH{`Q1rik9480j#D7G~q3Bhg z#>s(r;6J0~Q1l6(#mRw~_&iz;MSnm^&M?yQftdXwET>(f^sA`!lz8da(Q+vIC*Q=$ zfjI5o(Q+vI0pG^Sf!O+=XgL&pukYgIKpgmev>b~52PFmTMLW~+KNQN@xjk+CQ#d`3 zc+r2ONf@Hnua$(NSC+)df!NqPS`I}YhvRC|>48XnqUBI8 z_bbV*7n4sAyZaW%!P_&-ZS>tnq4a#>$s0$@q3AF5kCOv&_<(3R6g|0VoE(VNn?=i^ z=vxnrlLPT{C3@|kML^MS926%9;^M&t>3K73`gll4&L`%FMa!Y+zui1e4#a_5M$4h- zJ8l&x2V&XQ(Q+vI#@ocnfw)eIUcG1$Q1nZ;jgtehZo7i?baqhQLT_vzlJkkJ!=vR; z^gBny$$>~CqvcTa!8^stfw0=7yWClY| zj}6HfLL&}R2WDA-QQWI1MDc_0`7U986f?0~6h8<Pr-u1atTz;P z%8Q6VxP9+1KZ><hm zr^m%$lp1{=%V0(~9bZu-2F}C4$~cCGa#|froL^28t3r}&dQAnvqR`q{2CbmF>bMxR zg4V?|m8MK0i*ABZnR#4r6u`!g>x>zDAsCz+B(qzf0AUdcB z0~hbSSO%@2;kXXLcFEcSKM5U!x5qP>E;jw(*ceO&y%5Ww6}0$}*ci&`^H?G(==ei} zlIryNN_ya;A`H_0u?$*46Az1vK`Ur&JcFsAx~8}oOl8x$Vho(g?!|F2z=}~Sj))36 zuYhQ3-04BXn}gB;22T6-SO%@2=`C?FXa&6x&tNKOacf)*T0x)3GFU$#9Tx*CXyB4K zA}VNg0TJG#-+Lyu1tmQqSIpW-qE<@X(kMx-kaf{SQyJauQIc8_ba)ie+WJVMX;2!z zEJ_j;aC-p(?xbG$IfH|E}l3D?;L=#N~9M%;jsTJ^HBvC7%I};4L|5 z(Mg-)4RwTSYAc#5s_Uw1o4VSYn{k{zpFk1~L2P1g6Zf1Wppt@e$1`umP~7!4(N-bw0Cqg`IKh4^YjS_UXsfEn~kE+r2hn* zuQqsb(7yc)Vq@TP(vzs91Pyzs7fnD`+JJCo`EFC)d_MM0SO9P!xlBvYB`$^=gy}iB z4AX#{Co~4t!cojl&8>;9~AE>}hgadIWhO16_YHJUov;E-Y>baQ}bgd6GG|Zrf z0>LExDG;7a{D~=z4(w%<@a>-fkfd!DFy)$=#{H=*Ze&dcjB++D#g#RdOwv^j0!mFa zz2qP!_GxHjM`|+N90Zc|m;$&^B$0RtPC!p{M5)RNo1)+5AY3=UbHcXz{_S9v}qE&aM z)lu7MS#c|uA6`b4;NA%JD2$%u!nZ4o%Ul94Nab6W5^ZbZs7{+48MH}J6Qi)_=r;;V zkZQjXqJnx=wW$=8`_tZGv(snX;!6P=?31@(TmsfR_XZyh0%9<2qXRdURQ z!i9Ngt2SuVW)#k_RJSi#g1bRoxOq7g>eDbg;5h)f5FEFWFx5kEd)#yRrXpX#qtiDw z%;+haPgAOA6-O~m97?6J{k>KR5SnmBjxJInhR#w$rFQ|jw~_3fvT>Ur9yE|`7iry1 z&@>4;2g-QZL?WCGkb)p@`=?jQ71UF-YzT|4VB0|tDkyiMEroti6w-Qg52VgiH|+!q zW9o;aW#l9uX=q7xv3Zl!9#Gt5KoXppX~Ct|&wT-#o*~$)uw3_+g8$Metr!_rd#bw8?HoOmvgNaV`tWCfl4m_c}~PpHkb8f=FFUNJpvv0^5InRtD`mRD|^ za)KaPjOm>D!-sn9U`bLvzYgOpBDAl48lv#vo1w-zmHAZ-WsRBnjWf&XM?g^>gge9* z(jTsd>D^_Fe{oRSxi*7~!}Pg!Qwd3D*vYF<+w8dioWlbK%}PK4jqGs+fN8f^bw<#4D^t%PF=~{rhlyFG~Z05CjKn@)sP4!Ad1%!!sU|RA~h3laCv{u>G z0|Q{DBa>Ci$U!c}ye^`o6-vS_ens9?1PLoLXs{7ycT=Hmg&LM-Rw(p4g^WWWB{i+V zU84bi33~qS1l(S6rvk4Hh|oRP;Fta7zjBx&r1ckA|9A2ov2W_T`jk@o~h_ zL|^GR7o8~a+B@(li&2zwdeHQ><>@+5&^ERBsZPs}g*2Wk==&hbf;HHn zIFu2Xj6aowJo=$1tuX^jXgI3L=A(WAD?xEt*`Mals!$zW5&&w`o%Bfn!FpLP&+xwL zHo&XNv|!rSqhTIL2wtH(^wZdj5XqoYVTB;6Jlk%X^w5lQ)V7m=ig>JZX+koG)9 zc$SRdTTT#p@2kT^@4HMy5Dy_Vt>DY*EnKB^x2wFoin=EkZz^s%kQkukpf?J`IKvH*|@JC-5dHQ9bJiIWqpo9VtPxcPODR z(FeyOgxsam={^GOK#JXw^nyFFtP93hN@$CuD!p#*bBWI1-C9faxS2_nwpd)ke-R^vWX>x{W`4%<;%5qNmQn$-k4NEe*+7v z1@81cEgkL6EmBp&H5&h$pxrPxj7=LRr<~VTC0r%#kiv zn{hx-I=jkB^;XvcPKrWR?A^dmqUB0rTV>MlNY96)DmuECrdVp^5HHgz<&h3klAl&( z2Wm}xvO7a!KJ8Pb8TZHLyh|Nxe%HiepDAWuL$T;Y^|ejKHm0&|aP7@Ydg)d=RH?|7 z%RrYX?zM;!q{joXK?)Bw@#9ajoHk%{Q+g85+6rVMxsS=YK+nMk3;rU(74&hr6j4;=$)Du74W@=zCRJTu* z#>n~93qUo4%k8cspvgI7fx$Fa=3sP76m8-B18FNLb{29*rHUL8rm(ncW9USv?_E* zrex}8Rnm{@Vj?!}K90~R^&3IN$nNO|sk%LfwZB!eY$Ix6aUpTFCH$&ab9+7qZ>p&|gB6cX z`VM%PyaU>lSz?H}be={*Wkq?>?BzTyeQ(wIC3G3O?&YXU40`nMnU(YCFt`&48E?b( zUHa6ZvH)aOO2(m@nQv(tZ^L0po!_d=5nd)|v@x6N%JHJFnN|K^&_$j`5OvF?NR z05>oC+UE#!w8VfxW%CCEx{7HwS`5Mk+KnfwKur=>6F%pHj@~SXp^vJgV}N6x!p~qe zEirG)y$8sPm&XM3@os2hU~D9prr?ZG+5;SOb`OcIa#3KpfD6#c(|GITx6`JI-Ag&kQijoF;em;132W zG+Nx7ufSh|hTK;|r$7;|LdZ+gvz#>(0$zP|Pb(xLi6kxFKLX}jxa*)Og4Vzvr3Q{UtT(-Zfo{#ZWXHIq_qbJH z93%Rx)`8Dvns^lYnO92TWt>XfIx(2BBy7nww?!Cg%a3nvdG zB5|6GzXbi`wBGb8#=o0EQ{%@tIn)Ygu}U$5zdg4%^@eU)!bw`^%Ni*7cXl$1PvkJY zQ2+cZ09zF|1J(-6K_hgE3pz$9iK2PTYFYMWYg3>j^S}apRKl}Qn~c>YIeN{7ZKYz$ z!$Zb$Gpu!S9MAYHZY;#x1}d-+Wvo`jw+mQ^(+nK&+Rg0GxSY09_JJjWc+f)p6QJ9! zc9U{4%#KM*#ieh`%>Bedk~iOfv5+Dbmlp|!!#W|lpdq@!fUcGK(&e+gfGT+2wxB8G zzHl$P2!9EB5@$SK!pP&!hlMpPXe`6_9e2E@Y$l&MGUKg^@T|Qt+ulZ}Vw5!H*-v5* zL%SJ9cLkkUg0;Z{!b18Sx_~2t|782*+pqv`<++36SjpF=_w=S$FyKx`hzC?Vdo@k` zW-Z|~q7Tt2I>#^!X5sXX0ERgvd=OfLAr_V9N5I?C`HqY%7a%$MQF74vaBtckvc!#(*WlK>c<$yz>sRa;GngV?sRb$5hhl_)`L=*N*mO(vhNCtmL6Sj#K zLJ=#N=}zInUIxpdaBaS9KJrj~Hl_wBG5M|Wupws^7QkTkcZ1mz%#ZrNfDpClO=vJ1 z`?c^GF}SARWhS5NE!a>R~hltKHh*G#r3piL^I@fHiq z!TjcVoy(=u4w~aA;-q9bouh?_qVo~W5~zr{$RW=xGodh|@=fPss51dWIG@4)1TwZ2 z#yk@zO+k^SxFpWjmKgDIqMYWX80tcw!ck8NMm-#ZI*!P~=V4?sy|6NA$&Q9#D??T& zjT-#ZPH`lT@y^j+jB_2KBgY$<=QmOV4s9i7qnrt)P1)U{E#J+HZjuA@XyA>#1ihRo zrGrNiU98F=(39rUo;s+xC-KnZO=B%EhpV8pO{spNt;;pRi~}&jm@%2pNdJFb!yJ(> z)ds!JTxA$VwW%By(6F0DXKQOSFSTxGSa`;V=^Out75QLo85f2wU+chV%%;j&^%amR zmrVdpN#sF*{=QZl&q9dV@IF&Os6Z?d$(Mx$lc#dpYPeS|lc;kHKqj z+$QeJpt{*FSPwT{>Mh2gs>At~*^6ekRHwT!Q3@*!hMFysuF0G|Xc&yiG+K03@#%(e zw2>0D70*{gMK9zvIq8AAUN0}@)LDwmuUkdyc&Zzhw5gy^(QRT`Os2l`)`;9T(cu)? zlWyRtY$)AG3Hq9+v+LzGIjQ4m<@$JOE;M#5ms>p#LUk8sq(4A=#Il%7y#kftvDl%$ zo}qRk#`^}#(DP6T8^Ue!Gk)X-ano4Xks`6W74>r{`1P^UHLyHIm`vtw-#Ln@a4@)O zFB-eS5_I#p4dFF8=`&#ic&RsRW4KV7UpJda?7BWiIu^DxHZGH?vnEEdL=GI)g5}8# zmY`E6ZwRl+$=hWcz)OGIy$F|EJ#+S0A0sU+FT!FnwPa6+1+^mPc0EIF2&Ow;t*=jp z7Vo`b+$KK{?XyAL^uV;Dd~QYUJbnGFR8~=x$z-mgGK#6#Q2S@q220S?85_cDa&pPc z4dA7VXBFXct0+;kK1SMXzalIqQ`>sm62_@BeW}<`dumQpq>&P|`P>cRH95)7+W=nb zt}DXjR?lA#SRW&OTwjF6Wa{4y4vXCtS&tF{?dtURUdwRWTg9lCTwcuh|3Z{Gl3y7%xRTyFJ@>{uToP3$bf zVluT)m&2l+#w&BAfBkW0gC%H>BQ}KBiIwdE82Q{g~ox^ue*Q3??%-9v5ba92z>9=AW>E z{B-q+>*q9i`01qea?%ec$MU!p(tc`jHd=mKEQ86+NvAsuS`)@B9_5qX3xvQ48;b}2fy4vewuv6`Z-M=j=XZcoYa0*ERS0uYgZR% zqleeTGMLN|{y|q`{qW8``pBIutTlQDn3r4WZUFA)o84>qUKw$12oSG8|GbwGH3;5v zphO}l%XinjQzyd_L4tOk)`t$nTCtmMQvb{@EJIJ+nC`+{2X1UWC=B93b<&17E~?~npmk`G2S0n=nGpP zIP9xXQfosb>u@0>j|U)DD2RUHq-3dgI#eSdySh2r$yOPnx6E@CGY6)X@vjPcTO)Pt zys5gQE{WqFXsdA2KNYD>bweG|EUjh}te{_O81>2IDI1$6GrcUvX`jvTL{6%kcWdZT z^PBfJX&4r;f#dfiIu*a&?qgNPGX=QqR@{T`z+Zy)Jfjaii`KJ)3gfSazi^(zc~ic$ zem}TOsT7oJA$*;V(fM8A5#)D@%;=JnIC@a)RUKenl+25pU~;mOwCkZ(8@f|nMiR$V zBtD$Mkz=Vn`82*IUB!3O`5zwrMW#!C)`#)~{7e2g;KE-#=aa47II5IR%Y8r2pgEQz zxZdog2ej#N{ejsn{GP&I3kbEcJ>Q8|{Im9lAW>2pta~HentuA_ilU>N!&*Y3pDWBo zb`rZ5m;eU_I#xwwfhz_RY*fsu%lpta$g6ElJ}=OKuQu}_ zNTTZG>_j%RqKxnkloy&zjc6xJOVayJXc9+Gw*yJgMc4JA>!97I1L4Y@!$*#|2a9QI z^&VC-m5za9d;AzXEOC7mXX!lxU%jI@l!&TQemlemYm@jCoVK85ACC5m}9PM<3>pO1QR8_-S|5Hcn$qOCGORSO?|*L>5SikTCT7EM_tgE=A6O z(vh(gfK|=k7bJ>S$D1cG(<~x`Lhn{?30)-7NGH%V=XK$V03P(Y#S%>#(o)OPs~R(A zoB15rxG4ShQURWG&e)DX4MD&CrVllhBV}w~bL!|Jcn0CX_SSU9-eg_olN_8bQCKiJ z3-+DQh*u}FxUlB(57wPHq+nqAPCJcL7-zsLQ@QpQL(@w}-99mZf9S{M<<^Mg=)XQq z?<$F%AHStnK!q#$BjPzaLkY-$5~N#x@AD%Ghmxb6wGkkQb(lCGml9J(%e8Dhrf8CKpa~Y!J zkkENbG@4A)I!HGtT~IXG6-vPHmAzJ41-&YNdFE z@hBe^%2eWcEnM?%r2|G3#3uKDFHzKsNzi{4^47L@wRiS(3UTip3o`2xf2l+-&$X$Q zp5)shNryOnq~;IL$Y`4_RcJO8ghJ;jRG(_kS?qqrBq zrxcXIeNyKhh4m(ec9zY?nzZ4EN~80HIy<8))!czJc2=SZI}9{cDNu#X>XPOuNlb~H zRlZ9Rx%q}iS=yVksq8BH)`yW*eryiLA7drxd;DeJ?CpWkSa83iU< zbfr&15&_b^K8bOy_qTi$2g%cTz)J7h_1Q%;Yc0ZpT6s&NLUDL1S$XE6rmGtdEanuh z$??jDU~=68&rEm%nQo6p4_nl{6(IuR@V&1DxcsIy;nB+baazChqnAFH?YDf)`WOm`>u;S`hP#YHnHPSglZj|| z$7O&ApdtE188EqT&n-1cOE%bSjYt}4bPaJP<}I;eX;9pf({>6)zrH(Rstd@jeOyqH;YChY_-N`hVUu*8-+d5e4!L~_K_7j zn+&GM(s@kzmcqzg6Uia7ndc)(5cZyc{XUL^FWh4Yau1Q^V)1eV{S?tClV{jHHQmeM3pM83Tz(`U`Yg8_A)GKMkHJMa1tOy0~4_I}mvBLF$hq{94kETlmsX zdA#EtFHDM`+_EpdINV=9vz02Ps=~?;$8SMrRNq`cLPheB(D>IOZdXvc=Fr{-gpRZk z=4iZv776s!`2|Gjigzif6=?+zfCg?HOH?!vrf$`k0qj>s%1Te`x`wUrV9RjH14SXqoG9gwfh_J(;Z*d0pMY>N`}cL zd-kQrHY55*8*O%~8?R4Gx86f*Sh^IWIkLCJ4VSdTnaX7#>Bx4q(}$o4$7vPY1Xy7= zDrO#S%^4Cc3vhCbVkqQ&+pCmj>#W8K@mtQ^8T~X_$g_k%6nr7b$CXH+wz?NUETwtO|_*=ns&hM6+lh={v@L(K%JXjVHoJC0B$;&l7^_w zG|AxBs^W_RxIF$Yg`rCW1a-$$==lH*J<{Op@Zg!}jn%-Kq~NXcY>|#GL@RponHEfG z-~inhpw*@LjpBC$*!-?;K{r!Vt8ruH4KBN9xozP(c{*s{d7={nGWB?Erz0(0_tppk zHx&(3iyDXzY0=&h1mkd03rM1bwT`Yp*d0{XXuW6oOyo2f!^-7z2OkzuV51JI=}Y){ z3avwkJ1^?Hj`tg6N(M~d`+s=9zVrhuBG!4RdVX!~!Y1bdeP%lWe|WlaQD6GqP~;UM zj?J*T9<}Qk|0BTV`qnNNzDwZ56>!~MHpE5e<1azS;Qgut2s zg)jABbSh^GM1S=N{)9h)=uO!KlLoKyu!`ll2+pepoF{rlGWGT8eQ68yx>k&$>Y9fA zE9O^Y3lmP|ISwF7LGyE`gL`V3FNeSB5V%7rKRdfNV!q6+@IDUAdQ9OP0Y`O=PaSro ziakNh!Lciz?t<4P#la#!>bU>KeQ721^#J@yMR?_z;v!CpEWE$Sp7hJUREb9AzQmn` zCkOMI4~LSIBO4a7?>^DT%E!AX9w#t54}S@I;_|-4uMKj4uFAAzQapzZ9&WhSBcV(| z624ESQlTRe_!^9oq42|}( z?F`Y|8I)CXu-MdV(_PT0qf9v*T#PCgh94M6(EV>?JfYbufmCPaV@;|x%}-^vFKNea zFm=QFR%aT5;6<>7QVi@F%X?p$ES4R*GyF2?aA+ZKSq8;;tr%~6R}OD&aV3K@2@@9^ zYikDYD|M#2S}RwXcbqa=i1ZW_S(?h#rtlz#JZZRy9x2A#DU9_gyc~{s!0%N7Wu&u;ADmHdFNotTNZHS%~Ckb3Ogy z!Lqb?n@2J)*CP>`&e67*x*8ew?fkR>8%}>1T@9=rJtvjJcLmJY;xR>gW8NC%75M!S zJdeMl1Oa}F!U)XGsJgci(yJb=D%G9it!k17Yp>ULtE(~hOtK9~giITe|{n0^)( zLA2y@j}VTGZUV8Up{Zf+JiK=_V_scFeRb2k`ldM*jWg=$9hRKMw#vMO$aT4m)aBTD z8`1USiGB;jBx*Ew;FAuQdo^b&n!Q^75RVbC9R{fBh;zJJb4ca`7}t9wQ=uFazO@iY zTEEtUeFFY$$JSmABKIh_|1k`ytV2Aq_caW<+r#**g2uz|O_==hqPl_yq4wQUE6mmN zU;N?w5=U-C=Zto{TEVtUvsZ5*pdsCjm&;w6aVYF=&2L?jsc~N6!T6^?c@k4>P`JG=80Ikw(#bnx^QJTu}07p~iB=PeU(O2Um8p)(BwgftF?0$5)fT!gkR z!+%vg{}Mjkp&tuD%~w>F+P!_O$YycfmuO7c>uuj85g#D#Mz9R8SW`bi7tiSeA0Fn8KkRA`0F z03wH=@Zn9uwl?8BJ)YCgrO*C+JiDJ&pM7(g07T{^*TP$7Vlk27Me&Sy77n{Exk}9S zaF`-*K1B$TYu76xwc(0-|Mx@wM%&0oj z@<;rL9OH;xUoh^~v{sl~FLWqR8(@sq>Dopv-Matx*@CTqsZ z88K?alpqZA{DfZdNU+I<-5lMj@|}K5ki%PdgmG_Yd{@u8_ac+iQojo0vX!rgsrZo; zlRZz&Ju!8fPloTKMf$)4or6&`;OU!)+@n#~dT7DnD1KDRuuE<{^H0ql0ov0!AoZAs zdl~hPmhI+Qe2?ZIDxj~!RBY|(dHN@?3~2HJ*T! zmxHCJHTX-=C%g2c#n6h6A;b<&!$SD-9Hl-Y_%6({#ZAvt0z2DGFhOjjMu_oQ)f4LWtW7L>+u=$d^E2s1m z_F(R6ab{_y_*72L49Igu20NLhjz4ej2MuoUw2Y$+HLY|sLT-FH=a_g88Y@9Z&+SJSPsDm5IPJ!m@%0zr ztIzVVXRDX!y(;Ymu845)m5(HD)7i$Fzw?7Xy%PP}>_Z6U;qkZ_29yDw3m0*rz}#a>SoW4MJ5jE3(w&j;(=raT2bl{vct{-U(HtK1Z$U?;Ec7LS%CBM%w z$OuYxcq53F)P6GX1|8Rr2Eo=n1l?d8*X$9>(5QxT2*R?u#cp8`md`Ym9 zJG#8xaqr82&aiC2K+&DbO6VLbYamW4uz_xGb@9M9ET$(v5)zeuY_IP4>}K;+K2ARc z3N4tNztMeTh!)uF9tX7)6!kZ}FAve2P4CY`L^aB~sXN`#Po{i4ie$rFH^1#kF26|d zM`Q!MkE36VEGW(&arh`YoelKr;grOIfWw*qr2lQ}CCd z9Wj~1CvrUR1FYVrISy2}%xgG^%HSMeBG|I5J13IIisW54^rQDdIRzoeAUIyvK;CTNTVXA8VMao(iY!Q&Ho13%aXim2B#B3RpSAp#%Om34b_* zdQ(687Uf}4;vQ2+dR}&>^$%F+1`*<4f5Ga>Sp2uAy(?edl&2(q&*$H= zg2qubXEGghA7BooWa+O77j#qdO?VcYoP^wHxKb>K_eNhtJ~XyB@UuZ;I~D0T5#R_G zPPkvBAm2ab1IN!6gpnw1f+!M_O+Mc7UN9dfnokvMz_C}^{n%P-(ar6-5!i={c?+6W zHTPA=P#jjeAKCH>`WOOQP`o8BWs(`j7Nt%gs{v;dg3bWOu}qqR>bd{{js;I>->5j( z{v8UJ(*yX$^(B}2s1#nP9~!D5d)2e4Abgq)^Vj0wvhXhUZ{de>4DLlOd*R#Ofw>&( zD$HZUztXVufV(xIliJ4-)G60eEKkc8!50A*S#QR<=Tv9TI|f%MF;WdzAwD6DXT9^x z4s({S4{*-M!n8~Z--?Dm4Zvh?i<%he8Z|loS%0IBB*srdMAI}|t6lMh1aE+rz}bo` zLsW_E_kf$4cwT_-&^IklnOOmU8WL`3%3eyzZtR}C*~z>6DU;!U*#Jv3P#nsS3T7_Y#K6PX8-0Oe2XhV0pg-%D-xp0yz zLGE!8oZDg3&0E+WSJi=ybb<~f5o3V&Ogs*_OldgES^A5j#@2kHAzgIpt0pEdALziM z)3yq@HS|>kX5Sexe7KT=!+*HPM7+b`<-uIy7Q8u(%jcB}cxe^vh{Rc$$?)q4I9jka z3}tmL;p7r||Hwtv`8@%h!Ba=Pt%;U96PW(I4nH{y+E0wV|BZs)n9HKc612J;V|qw* z-}(gA%3VM@(bouQC;CyVTQaJhh)>-!FL$Et@S9o7XBs2lNw7{CN8n3@T8*hz_g+s!cWcf6C8!) z`Aq{#q2E&!9>=E!mQ4OjVPtX()E&g8l%d?i@tfno^{GAiXJHfYh@K|3{t@&_J3ax% z3WbXq9d`X_~~^bt>J~r%Qf7rtmSk>fYS`Bb6kuo?qGLxYOP=5Vs4#n?G8z| zmOHhHr3#j78>0$N?C#}fmXo^%0LsA`y@hG~n_e=Yl=MwSQPR&0EOCCTFyfqp%7K_v z>wNsyYDK~5SNKcN0XULaf#LTvge;>>8zex3P&%$y&I3EPT(9NCn~#i2YIh^RFj9L# zamn24iX(G;Fr}~=z67Qbf6ruzco1@eA0odsD+LK~Zi%ZiK^HvUi%!C%_8pU5$-pQ0 z&5X#vM9r>5z^0uuh78mwF8sW&t{FZAKNsNk?#$XoV%=s}T~#fXE!jOWI$;~+t&ozXNf_ReJYvDO+ihVM<0n@hN8XO#oE1!X@T;~VBJQWc zge)BzQK9VOdzI`N6!!H!RLsXtuCf*6MmVO(gmjWS@SBT^vG*f>7HY>n(4so%m#ab* zMR*DV@R4w5l)jLkHoos9q?#kMWC;pg(jV>#mgVsCB>aTf%o@B7^8kEJ`SBEfaF**A zWH8bFNS8QF=nI6ZmiBZlqp|1(<`?&ldi*75*h{@=0*YtVfi$;O2k0IRIwU2_Jdr6Z z-gdyWcpU3xsJsMK;JA1Td`}#=_Iv&L2xuo-qc}I{%?g*(UHHYNFz&rMGBqPGM={u3 z#juWv&ojZ{T>t>JCwX7YLG+jeNVxFfY*#82~dr$QP;% z=XX(HiS}TK<5reo?25ov_?TOSccS4>eAxJ^CgRb$;)ldscsGTs=FM%apEqaDjQXaE zgK8S6+~VE^UccBE7NK2kX?}c_^BWuI&86!s#f#A`4CxXeHof%1uuJV1qPk_ME=sLc zDlgQUs#qC9PEv$t>W1f^A>>Cza5Qp3ZD+2HHnFRU3>c|dVQ2v$3_#JLi;Nw`t6^Mu$ATk(6OEjV;}{u^|#JVWbjj z@ukA{SukZ-sx*oGF_!Y0Qj{G9;B6R=z8YH|scaw`UJi{R9d1e4Ot(pspC=P$4|zlp zLth9D%ybj-wez)@NF5E@jjDx3Z{DG57&`wk#aTN)t#CQLj9(m^tDV!T8(pkRF;6?M z0Dz{IRE}q&1n3Oqv)cI=2z%Q3Y6Mg}+f^;_v3CB_7vtdfe$dgw$3|-B%N$)B(9Zvj zBW$GFvp@~ci-r$!b*-zNH*!^{OBm!5+>YigZSlc0mvDtr4&L8aufCc( zE($!|2Yu~4#8;U^vWHi|~Ck+*`F^fg0X78lG)+rmsy!J0D^P-|u`QkCgo3EYESAHM4ToGS8_f;+E zBbzJYY3ETcV0441U!)~G2I>Zv)y}I}!f0n!zN`hTb`}MUu&14WY2-vZU#oJ^&bPX9 zt`fe~Rl+V|jZ1K=e1fXakapfXRtJ9=OZi+WzIL{IRuS4+KJkpgMvzX5)y37Gc783O zowFhBJPc|mLBmTD#P7f)p|DvWir(Z|IqO4c=lvCD?cAtvIW5AkLhbxn1h!bUxTl@l z0oYVsCK@>=VNvJUNHEnA9S>Hccjj~P|=X@Y~QvF5qddl z+i{X-N5cmw9P*ue#i{L$U4q*KFOMVGJ^4Z{Z13t9;=NUy2i)Y9QQ!$a=qut)qEz-U zAM~{I@e$Y=KIWF~>uC5#A9ht(xv$Gzc%{OHzO%ij?Q2V4JO9PXczV{S)mt4L($0ri ziYMh3=H8;zM?+M%4Br){mV|UMpHSry=xgUaLfZN25W?qIS;}syzFQF9LiHvN8!V`8 zYFt=1gN9qky4xoe0vEV|QRmNvp}ztWr4B{HXI@pqf2N#P!+)z`Ps48n#SA&o@Pk-M zOMtd^^|h$(fK3>h!Nxw4DK~UX=cXg|F-&)_o4*&!QOM5tYi{5J4VcZ-Ny81F2~F3K z-Pb;=Abh(GJNw$7j4&~{``UeWG??ATexe}!Cxp!mL-wd;?>aHk$T+y}UH1=DM4~JP z-zSV`>FOx(z5vJ`mTV4x77hOtfa!=)ZieZ24VsqG7jg%WHar1v=P_D4yl-!IfT$?~ z>jE~n!PEyGV;NXS$d+u~SQE{xIjns7KHWj%4C3#Eo1rF!S{)*qHh(@E*X{F$@r6-J zWY;L{cEiIb8CDm|DVF9nEoUG2HLNiv=%%s+y|=Tu0dMuz{)AX0Aij{NG%(9PgGuPq zGx1&l4hDhAKa?y3zb=(uN}qts^J~6FK>eDjyoL>F0CT@8LveoCRBnfW0PFFvyx{2U-%4X%ZX zv8%x+LpjK5@P+unX|RWQIev2z*nv>pVOE8oR;6J=FKfsY;X4g!U+Rbbcd1Cb05doM z&|SgcA2ev%a_Q5?%sVEd_GyLW@N3HQkv^i_mOC8~d z*&DTwDeKa~5kA%NM!$|k2k4{7A*VANM>xtMINs=Qqv2;14tb+T=|jpP!pSbdHHw?- zOUNSO5_WJ2&YD0^41TQQ0dMrnDDZn9^u5tj)l@*-2Y=>+o;Uhk1XeQKv?L458)2q% zbVb7}eAso_?u>>XRJhO^{hh@<-_iFtcThVO<*AObw2(JC$x^%+-NKw*lzMT9>K5&V zqSV)w>ieF()rR`W07d+jft4ZTG)26%f}gv9X_6Pi5T7c+fE17LT_#I_{-K;Q_Fc#C zH8qVP!aA4WR-Jt#S|nUTekap!!hYWlYGK0^zJbJA!}ka$DH1WUza^e@k0p5?;pZWw zpPF=2ch|yvampq{`1kXT*tDHN9T1AKR$DX!D^2{1Mj(rjNNz_DPdX5)l!K8E18F(2FF7sp`SfVMzrxrVGG_*g;s z0~>Z6!4Y~s!*vArR?Iqr-xq|pD>Zo#>pNNlYNZ+xG(UYO@x%a&d%*FmFh!h1-kIl8 z7RK~&7|+s`|i{>03S=BAQP1 zk7!(d%Wi8iy=R}rw_K~RTSBYGT4Rs=kFgYBo7x)kRJ zgO~Y1$HUl8b(C#26Vnt$LmtL2;|SL}1jobpBpUu&;gBP7?I=^!5aCvr;40aDM>`TO zVYW+fJd7)2@Ea5lco^HLQVf7Q`JnGi{5lG}-v>PpW28=h5T%Dr@G-Xtmqf#>eb_a_ zZ$`uKDO~7bd}?veAMibl**YPM@-Pmtw2+5!u%&o0x`nx?DD{yL)h*h8ic%9g@ALhM zT|&qdMSKrqO$fPN5zoVT)CJ7Q_$~}>j2R@Xlk61_;n}1FXj}dXeJ5@K=6s_x?5$Pp zZsf$oO;b6TxLK~8>zO1cI1##psV>2-@+ad6&nqG9e;m1s4yVvA@<~eZJ(IH)iSSJN z?rNfmAPux6&okLSgtRJR>ioaEnaE6tJ(KJEvS)Jj*$LVOy|`9g5cdFZQXL4hL!k}? zW@RVfC%1||8e|_*)*v4mW4D86r%$O6&gfa0Oa~6eZ>J7}u?rJ4WZjDu1>vh~*l{o9 zCJXZ5x)*;hG5HerXku?CFhc{@@#wAQ-iA-N;Z=B(rc(>DYabJX>r>4D=J`}}5m2A1 zTY1HES)~EB@;4w@=x@9dU~xSczToT|D&N z0DdtL1y%QRlc85PHbJp5bf)?Tfm6YkDrn+0{cfCk04_FJG0&&j8vy!`^tu^LlmN|D zK3G|ED9jJ@TELaT;0=DzvDD+#nFw0yDn&z<`o=iI9S*^<)Za$Kz1*3-dpzKdIKl%i z!PQ1{+^M}wnC}uCOZ}@D{BD1S@0%?9b)|r%p5)KweM|jN6!?S>dRBSjaMMv-OTDL$ zxkb1(8ot$sU32zPH2j6ag_im|i+gs}FZTQqCXZ2;`Y=liS?V@R@nUof^V_1-XF^oB zXm-8a&yP#pY)6w@-%{@tLaG$;E%j|7rsVj|~SnB;%4wkynm2(a9zSisImwsHrY?t6x`O9$x`z@)kr9RDi#34(4 zo>F{E{R>4R%<<6Crb!}5+gXxlsSgSvM<`Q39W;N7BiKXc5|v|6rfupZ;o!3r z57>?eqrl(!pl?v->ApC`eefV3^bE@55!kan=9X;_-Pz_ioi*p_KJ4n%qoU!H6)rR= z=UCiR|Gq)_s0i%~OA8s4?<~cO(arVzL8jh)qu^2x3sK#o{h}!KYNh%H<+%{@sv^EY zsnD%@AF)@ZJcH7tEROZKEDYTY2m?^2es?ih0>l@nxGP(OBDbgz_6*7^MovcYTPlZ9 zY>$G8oNG5;(*6Aq;Z2v|R_YR6Z4(KXuv`gYgEB&o1cWK$l;Rtd$%;f+joac$4_lIF zP`(WzgLGBUn2=j^>CZPP*T~fGlxGvP5~c@c->+#p03lC19*lr$$HVc{HdBrzOfwzL zKb>Z}5MdSuuPXL!MKjJPvlFVs>WORxoosL&GxByFdfRQqfuj%};;Y_Rj!| zJJI`hm?BHm!%I!B3S$}@#^}v-_PCdrXnNe6 z%4f~~dw!Vh`FNe4s(CN-gN}K>FA{x0(U2A2T_>F(!Ze5AnDd!2*f;O*6rp`$X(99ewWWA5x`mlH+>E1W^L|i>>K5(7 zqSVWk>YMl9hmaQ(@s0V^5hg!AvY#TJd0*%PX1rb$hOPyK0V!tsRwhe;?p4l^HD05$ z&=BEfm*7_2?(P)QB}`L7*ckskmhz=id^7#MA`xbK`A(*iB1p$sl4qtL2qAXDYU-{l zFbgF7?H-^M{U@#uHWgz$X&Iy=b5d={gX~^Y}kH9 zN-XX_l&IF}djvyvQfD*{t|teRVTxGXLt^mOFrKBSqQJKTpl5M6*O`vViU*Ghz;vn3 zUbIYW0zuO^ChOF|uo!G_#XLJW1Av%5G~rw(N`Uwp9`_Gx2kl)DHGDVtWHyVp`$5MJ zPFD9HG7dUN(U7^jBaZNZLvZY1FYD_EZ^Le^aLC*}5J!02CAg|!zWVqQl}k9xB{=r( z?il=G#RGP5lFkPL;9frHn>>E6mm7npq}~4d3g- zuCe$c8vahKRFA<~tk^2Bb`L$1+(0lvGYJci7>as$tLH z-rvZHzHU@G=<7wUoMARe&sH}#WIqpZ3GNX1OB~^SC4}wgdFla&DZfyPZ$GbAB*I8; zr;cs}X|yGI_A?bij#R`He3<&3CdBrWAH7P@n1LlU96I&qKx_kCh2Po$U*c!stmJ!O zIxEQ|4$tZygMeDyU*ISACb3HQBA|zRcf>qXR%fN5$4e;i)B_GV+rz#03DrFH@Xr!G z+{-WEaDr@z`5QT$UO5fJFHX)J=PlDIn+fd-H-ROji}1HRSp;2#S9BSdoV2RLz~A4- zzbgDCX!Z^z^d<7nPdb57=Ij^ZcaOKc{1H>q>2y8tXKTT+J^K<<6bMpyP)|Z>{V@l{l)a*p$&_=^AM)}eF z6`}P;GkAOnJ%()8JB4Zv6zaYWDb&joO6Yhn@~bg6H+-Vc&&~4U(O~qTr5biEp;^fG z=A3nHDQ*GNj+ad=q3MX`MW@Jb22Sj;PueaGJ;my8U z&AhFHe+HtdDxL#VkYmeBn-G&2Oz&U<7}G*AB?Ndsr`-De z&gS<#zklBMdEb3(%{`xU`#tyEbMN<-un3ps!_V+=Xn5a{4}VJGbjI;ni@W)OpK&}K z)(bU*x9~l0yX9qO9FMUa7h_nMS00r6#tb(s+BXi${l0SjjN{(tO8z}^tRjBK@yrbJ z5=GpM<4qwT_U<>c&`$xOL$dkz8)7qI{;WRjjN{1jBu{R}@c;nu%I4(p#AL#puKqF; zi(@e8Qln%T4+$AzmA`WkqcMdkPzM@NyIX~i9bS--a|H{;lTzNG2T z|Bfpnb$-6h;W>W9ybez30kn@2V|zf|%Z`YRcC(Mej29>L6_yT0%nu);xell8l%?NN6(ODR=SM_{rRH^f{=LbmH}5U^?-l3cLrR+hPt}bBKBM$zsquV+9Ri zjg>N-gi{4p6&~VyEGa9YvGQwhQ;crSB+MHtZvf(|{++(+$@nFoVyqk&RsC7EYPISw zOX&ZvTUhB{R7!(udz;oK6Jxe&*Sq`-$Kke8l|IJOky-M+T$C=Fq*SbNa#~`n+{aoc zBFpp~9CGDBdnJ~jf> zSXs&gpRS-kv+`#kV-;#ybL2hASuc%D^1~&QFw>GH1PbaD6e3|=^@j!ac23+DZnJ64=1RformBQto%p<074=6}TU zM~fAN>}H^%5{Om2IOoo?lr{Bjt(Fz@z2g_jt`^VMnpBGw@KG9-f|Y**PBGT#eB< zO-IpRd=i5q2z;!kc`_p^febwjP^u7~3oK9wk3+ZGV(xeSA?8`Iw?A(v4sNGA*eS2* zq2kP4(O0EPr>BPtJ0?dSkH;}=jh<|og~{v0Tw zVm}f=?1k0}I0w=!F>@c7RzNOe_dMR@ri{~OdJZ;LoGwq4CM%O;He(-Pk0eRo3Oq@~ zc=Z^PfftXUd?cFg#tJoUmSKHG#cU$`d=apq6)$2$EoF4@J;YQDu>m=AyA4=q*zoQ93oc8?hg&= zyjidbIdLF_1lJ3o^6)^%N3lwDficI!8ghfm=^$oM!5Eb%zswRb|SsTeEUcZbAvO4#}7ol4KO@$%TIYMP9IU`4w%0LF1G z_`8(0q4%QnR2EA=Q)xdCz7z=64cj2E1wnO#g+=<;;aa@ncZZliHI1R+k9@LiGX#Qs zq84{3PPIq^MevZbOQaUx3f$KzHn}ooYw^zseToi{VU5k{&=_Jp#kdwf91@>Y!j}7x z!z4wn#q)F;?`rYF0H9j@kkU5vrVgE!rc*Uk8efa{d0?;$R}6%9U#ajs+@Ka5>8c4PnHop zWbO`^ujJW?T2#tbx&3-)>91OTU?LuUKh1T3ovavFc{?Q5m9Uj`ozh(8w;ye5Oa=EB z0YFv0?-=o5L!XY)Q&}v1zS6$R%Rs0OxE8A*s1i_%&sU$`M1K<3>FoNU=4JQAisnVhV>k{jlOxr=L`K$b1GaU#Itv#Q0%}*pkBJR#yA)RhACw?+ZDi{e8u_P9G79 z@wtf@qX?V=*D1YWL&-TYriW}SKy~`?M2zo9=)0>Jv(Oen;Q8JSC$l4{D=rP+x^f99 zSKEsqw6>oPXF8rg4_GjsKMYm4#r)*ihw|G7^xTN6ep-ap>Zcuogj4k|Dm=s*h%zBs zgqp}xbnc8ZsMUS6t!||sZRw!;H*%`~s<8TR35l;LVTJGkVfF6}YMXU29bTvWP^`cQKytWzvH`I`E_ZYuPsMD(8&*8Tr)(f_?|i)bSC zDXZrr^h=7`iPSxG8f57^mDW|?w$5*4B6UYH+u@1Sb-F_)nlsrqjUw>0cq`0KCAr5= zD`5ke-(iMB0_XsHbEyD2k=l8$(3gUIl5SQ*vh_vh`_qnn6EN-A_QD-6yl0_1ZZS7q zd#Je;W!@JGZy$tq^XH@DZ8v`z+yoDq*J*P|H~&#Y{Z1)6QWdm0EWOXtK{tP!PG&=6 z;d;eb%l;`O?xziCJL;_oeLl)VPg{1n)v=Lb^VFDpp(FR0cLr|S8kJVyzW@s~;~P;g zuu`u()O- zTc5w+^-BBx1wL2b4N+4D7^fEsD3kgb`*aqp=fR}A3XH!(Vw7)rZtKSpb%s`$T%tFv zlzYa9K3TcEuL)e2>14Ow#hXJnl84S(1MmLTEx&ILg;vf4>l>PSc~3OUqPHaF2_bpX z&YVmj@+zQtK}_2B<$BE(GmDaGh8o?ao@s-2X=>fB15@Wy;Per@N3KvA<~R5sv*&-6 z<~mjDM{_W(q^*IHoc3xpCj^bwR^gSJ!IW_F4p?^<7h%II-9tgY@ ztF)&9aV6UZLQ6KHIQ52|3J;kzxR#pzwbo{Rkqq`T)I8;q+68^lPEUv?gnN?-;B^W8 zHA@HD$*!e`+UcZl4!AEQ`pJawgW-hmySkq3Y(TsAZHRaLDN>G@QB zC+K`2RB@~eMnF)-K^HXPOhs}Pn2IDS(9jb07IO$5X`<7GgIeG};FA)d7WgUL6#tMp zMB7^`**?iB6xoI(OjhN;Bcbgnk+4GVrVPa9+|D)zRl4mBLuOK&g1i52Z3}e&2Aq^-ss49I)JK%Ejd6I= z7-w2KkocB%;IJ`XqYXsGWcwM5HJqp|w`n>{1y9DlJ0z}C!dB^>O3zXK+Xuoa9#S-$ zm@J3&|45zc+Q659CV6Cmr+G37Bn19BaHe(qVqifX4|5%l;ufn-fgR^D|X{+B&`#w5#I}fY3UIESY((#CtsIFmuy(W60bf zpCqVP!c88B%$pTuK_z)47=n5ooL*3kik^t=y{sVWeDLCge!8UtLH%fsp#Endmn^9F zNwvn4(4$->ej+4pR>Ic9-Odr8ET~5-VJq=!rLE_2R7WVdEzndoET~-&R0$B&s}yI= zaE;t9#G*80pzaV;N>+6im@QJ`I|kAl!zN9CBW;D21L^RmOt zv)=T7W}Vt44XO=4*ihMi$z^*#uEHb3vw@C5wLo&Q7jCMAA@giSS&CkyFuJ8(`I3(K zA7D_83iT_c?D+B6LxgMTMN0?W@{QW#!tvt^hefDt(>h2c`bcoKl~NTu~aX)CB<4Fsu|RASjPIb)y}*Tg>I7hnr0-4bW_ad~&wW z^w};dPKBg5f2EMKaQVW#DWd*PDcfZKozN%c6vmobVH2)t0eQT+J|yUrEv0UIu+hez zLt~qB(mHr_0MLm4_e$H)_BgAhzj2h`H1mD9b<@n&oEiHCe#U++oRlY)uaB``e)#VZ zSk3Xf!OvwX+713#Kzh~l4a}9@;CEvEhOc@)RKH@&PVW=#Dh@vbI0DMH*-qjxVd%vK z*Xj!(f>!?pT4Rg3vwFCBB}V1D=_tp!w)-H$I~D2ZwG#_EeN9n8ns3v9;lZ-sDaH+U zp|&~`yFj=M)j?>6))gn=dWyoLH@tA&3fxoQRmut%_Et03($kg>dddrPdI}bzA`xn- z2w5T61TFg${6X%wsh55Fmb%`rMx z(2W)+DJku%7t;TI9(c0~GnZW#0qKEC?sUPepsR}c9Txj_*)jK4&>J#LQTjj4$Msgw zOSJ>|D0!0qafL%^y$T{tc?>Zctrj4exPlkYe;*2S{{bkSlj-9@s7zB2C!C~_dN#pB zrT~|hzpEq4-brJn&pxMY>3eJaglf&M(uUZ@S^?Zy-WU?sDq-dF4y9Su{UvqzP~F>i z4oUx$s(XWau%YY8%!m#e>Ol7ial#zD6)vg<9y7iUg6aWd#xK~1yYq*iJN_?V!HlQP z^?c3#!_7PH>n$FtPisUDnH97LMb87xeh?HrG|H85`g;CcM14moTdxmSO=D@YUMXx? zr}Wl*ioHgAJ*zwWG7ne!PHibBF1Dhs8>@~@8evb^2v@2cx-s{K0YHs#NYF||-y2m! z8MBQ-*Qlh%$m4-&y>7zkn&W98v^h{EdiLY|=NxW61C8=NttBoUW}Tc07ic9U-mQdR zoPQ3gYKuASg@>Cy@;XBhZRG7Ma&bz|G+%Sv;V zen$yar}u=i4Rkdtd~0h2xDFA;NHzOGWpx0)k;U;aeIEFW2y{b)-O(pmaqy6?Cut$< z(~C;lXAe1lJ6o#@=IyHRE?RFaOm~~j!g8y<&~2`oFDiq*&aAdNtNeyBOEwJ=ovfRR*b9Z2_dndge~=3l;%3Kz0avu z+XD%pI=eWj@nf}4ZEUlu$hrIOK-Hvr_99@aXD?RU!S5lt90=7T4}@2OpbEo4_%i2H zazQOHgM4o>=VNbB9gFVt!GJ>Y7?rPZZ<2p(AqSfr-a!dvW?TZ7`(Y zQk>ectMHJaeJoz~Z;B|pZ`rovakkyGFVH3}3LDD)Gqm@F*ozgj^?R$*+**fe$8fE6 z&j6s-a#!qX{}@YJuVwp7r_(mqfC#iGYuA5L4*{;)^%VL2k!obm zUDJ<1r$*nM4}T~2{21{XVqXEq^hl@Eul2i-trM}YD}mO}laOx1!*dm|TQh#Fw6spR5BaX{6Ps*s zXNBF#13#po7*n4Hm8zIqfvJl5PdKstoVR};3N@iSmrsO~YGbU@==Zzk5zK-1fMUQ_ zK}ZyK0YH$?JjIxTW1gR+))Ma!|!bwe3;U;*l$8yLbz{&k%u1&b%*F!Gtm(ZJs@-foO8I;VxigYH_SXJ{>*FB zGLxzi96*XBAtn>%6!mA1WIhr!i3hjg<3NynV00;ae{@vS-t&N9MB2+8;zOAa zevsrVMhD)7_GJC=EZUL}So9rkdKg=Kqr zKKyzQhvyl;mJfeVVc&&(@g94e6fciAmf?k2d(%O=@5*q)jQ-%D+@CAgAG3Vm@sdlA zoTvy5>e9ci&LFQ<#2vhRcL+$RA7-K70z!A{)FX)ib&;t?P0gP&DvqpMdCLML@ahYIYY6nILY(IR!5$1CcjJy?&WA74- zq9=eLb%;@P=N3QGO~b7t9ew2@?MZ`c4{q{3WS$O}AOAiHipK~y+`aD*DU1V3It=l6 zV;|(UTnx-N=_K1iJX${t^N+N=X93glK31nEQ5{tj3`di1WGG|6SqCXz~=8mTqhaN0VoVjIf6td;cIr$T%Tn1f$930Q_l+qxrqMy)zH|lL!51 z@|*`qJ`mi4FZQ4tIsTY~9j^8V1rDwnvt~&GU*>&>O8UBd_y&ao%W8Ocxn@3Pac7G9 zVxORPN!}=9Z*mRdQ`691>gq#ShpR0An8+3is_pwdsoilZx5V zSi_ubwAF3OBI!p5;Qt1kqUU(N1cIW+VD$qy;bJBHa|+9Z`5if3^bqG>zk%r`ohHqJ z|1pB6n%!1sp*3&bj$(V?4*qNeBkqM3ukZei4s}u_kH00tt2j7qI){caGxU@QDy;YV zofTuh(Q1?ec6GKWU~zE~RcF4L2h!&9Wv<|QQ@ou4tAGb+32goyv@fEeRf=?+y$YC) zv#)@Yd_xoROd6GJ%ntm&#q2I0VVbDMDNy8vhxex_G1b{|DI$Q|(GJH8X_znn_1G;;Q(hb)cX#tTiURr#0NcDUsZU>efif9G1~k>xkFXDu1Gt8C{4TW_&!SqYr}V_K!odz zhiUWMb;f^A=+7(7_4*AEV%GTey}POa1h}pg-Y?6MFE<15Vixb3YxBT=ia@tg_}hH= zaFsR5p`ly4sHT*>N=a#K{Z#)z{UQ*7XN8|(>}?1+7#+Kz1pK<**L021FT#oX=B2n3 zbYajlrG+ob$8}xriy1CQcCxN1hNAluh&1IV#2~;b^dG_<^zT`_+lPt=PVL~6Z#KCejRKNLW^&6t}cBmX&;!2DKOHPZ|cvcFj{GpROPLLq2Rz zm}-BxAnqRJyq@s&Jn)X3?qL;r*gam5=^igTSoip5hAGl=_e=+1T*rhLWw_iuzLV2E z-k#GvJ_C%q%ZU5dbAQa#69_%S&}PCd=GPcH?~ihgYiAi=SQuVkXmw>MgXZFimT^BF_`mL@jOhsE=_XEy~hr4YL2wTrOwv-&G3#18p}F`x#dy%S8#0)neoC>hA^A&<1-kA?)E(iZ*XXnoMLuL6s5L4& zy%|w`rK|<;PYL})O9vJJ^=fY70DFvLcFg|oguWZPd@z3UAdGbujwir%*rre8ltRGa z5IG|;-_Gob1c}G z=U{iyaoAh6M`gy?z^c6{!;~g|YCf*R_My7E;ca0aws$BzRuL1M!DR(Q5k5>ypSbY4uNJkS}wH;q`u}+iW)*z2+2c6FLv}a%XPud>wzNYfXc0 zEtpI2Qz(_Odt=iq!aY$NZSj&?1BYcVXJCAt4~hl&wTw?S@Blb}jUa7cXv^6a^S}=t zVO|H#a4C?9+KkQjWlRpQt+iY84O;e^Zgy8%s~DfE&9(Lc^KI})CMPSV>lDG0LPhY( zP!SmI$YG5=M!PT@8zt?+Py}yQzo8=dkYe2TjrK?;=vp+V2#y0mJsyhSJjF>7T&VDn z*#Q^Hrbx+4BI->_Sw-;Egtk{uhEsv597SNyOIt;7x^_5A+fL6~l=^wv$Je4B%6(b2 zLcEf~9Pa&Z%7$S4U0J+q?j3b$RTcEmuGcrN`(Kg|wRARK8ZQ`?(^m@cJCBuo!h~G1m4ICR*Wn+{bP2 zdTHdDw8HN*$QJZjR2jP9D73;7Evi0(pkl>53csOh*aI4>VGchNwQ94shP7(wo~xMV z<^5@T)Z!Y@g|QOf;`wU0>k~EE-1SNJDDKa*o9>sO%y#!T+U6VLw}vY}&0#PECTi@w zC!C0a`{5%QW@nY)`mR#Df%`mugwJo*{Y!EyvCXvbL2RP7tJT~v7vPK1L`@oL7H(MD zD~+Y5#6aJ8piT)eGx#Ed#G8H7W|t~D0-nRbOtoTu23NhlA)${ro3JC=RbkCVp@_c^St|pyrZ?Gu{%${zq6w zxsS_2o(d%%4F=HA=0~jK|KYn>ei_1r7pf54C-CczQZ4>lI0Ef&&|Rr%8>^`G(?D4F z&oTeDBJ?}Bod_L|erv_)bhyD_A*nIW&c70vh2&Wbv-7)fiX}-WfSe_H1Ufxx2HOT( zbL;))g4&dqFl_tL55!Zq+c;phL_J74APvhjJn$g10|X5Z7-Zh6e#1fLBZ_gRwDZY& z(|sZTZjgBp2pwd$Do%sU1q$O-68(ohAaenTbdXtA4*{-&jQt88aCk6zbv6X!Z_eUfbEJMF4kGEAzl=aP$js!! z3lVI-tlx7bM)LzDBP(+gJ)deS0$-rtMR}yt#nT&eu(S2ss5d=_j+5xTofr%BYKy)5 zY~tv!P{0Qd>#mU74GW(HrNe?=3xNYRtGzaEZtD7x?e=rRdZ6*s-WegaMabNC_`zWL z4E#=)QA2C%{aRbE4tE~cV05kVE*SQOwb$pio3jkSd-(l1b(^oJZjlfqil#X1Vd?ZEZ zKi}_!$-2dyg^y{sqf_4FE`jd*3lOUNqJtQh0jKh^()+R!p;r8v#=Y^`$hAyI~SVLWO_+7n0aWA-?K`{iv8Wh9AU@KxT@4d9 z{KHI1slUu_Rd&Ay5h=UpviTv(?w9N)-F6h47=fkvFa}83Jr6FY?B0SKlAN-85w;Ur zon}rs281fUv&rq0-P=K_vg^VUsMqH^t76;z0VJ8kd~U()B(ngjY#P5XM`5*=_>TS; zGCj=Z&%4<@)mUw|&8OMz=JL0Sw|29OBP9oLgUoFJc=G;Z&oTcBoiR}p*@5MV3xipm z+<*I#=DrwhKaM##>qvC?e}%g+R_u21O2?nHJ92mXDukUQ-UE>(^}vSL90*lD_MB6w zV!o*2&5ie96>cWZ0YR~_@}UQVDHbRnvSSG+7k?~AA#l(Nd^D3yOp zZ?$xweA*CIiZ4_?&sNMTn-YYG{8)MhbgCAfp?(5fZLQ~LIjmr=8-U-C#k=N2{UjDd z-!(%K=oHLaK72(48xnphl*DMx)!A-nL9OIqUs23kP~W+?MC7$kpqfrahoMwFQyR|R z-<{y9n!cV1#j5F{_m#*Z3+h4zLksF~wVXrjmHE7WJwK zXXs}!M$O9jZ=-0%f;$#HPxLC;@lLN^$V94FUrC;kh2C1baR6P=ygdZO&iH!{8La=& zW2Y_<<%O)7KNDCIZAkR7u(%5)nXnRl4%0ps!X8bSE+G`zEvp8rfqR49tfAlFo$q8P zck5fd^C#piu?C~)?_^RIHxGf*ad|G`i4vH9v>w5Wk)3@wr%nGhpmDaC( zI$WF}-JEN#jVOD=zSTR2CVJ@+mJamLQjTuguNc?oM?>OPC9FoeCec_QvoyB$HCMD! z7FDr?YMCD1qr$QBy#fT44$9Zw6T%9g-bD`-4{NU*HL?I#efRHK4u|;d0Ng}+N!Kcy zdEjLc==9zD^Wl$2u-T4roEl8^-A(rtlDEs7Ta*eF#@(a!KUEm7%L6~4>MGQoKhD8+ zIsIjqrQF-ySW3d$*GOkG(4B9i;|e*t^Nabo>drk!r=eISy;k8!cYajCu-MN;@1#^i z?5)&e`8(iy;c~j`W+qbIbrQNX)`jHj;4lcgO0-uPw7}RSIGlt23A6gpkbE#US;LS# z4?lKY@O5z4=ed82`SfjOs&JpCv;FAQ$Rhz3#zc8NF>trwhMcqA(q&xCoF~B@ERa2u zNk3dg^z(}cZ{k|FmDHSZp-Kl;<1#~yWPvXXP zR1FE*F6~qkZIf2jL=!W@$u6YH7{Pei6)uWL>gf9s^=qZ9KDIBSSb9mdcW49J&n+SL zQPta4>pnTr!p~EhmF7!91j>7 zTvmYpqHv@DZ&xss-uLNpZHWC7VPzKLFYu%dZvOK7&B!yYNR42&<@F~aG_lLtlY7-) zXir{2K3UIR1((ya?*b7jN1FLTIm(RBYn$IuXf@mJ0t?hxIj4iLR?a!XkhOZo+1V7B zd*Rg#v-6zSW{iYN?zLOy`u1CB)BEYp#w}0>$9IUzq0(@UXtwYPaay{j1mm=kV|I{hE_lTT3EpCk(sr=VX~? z71$VFw!wx#xC%};PtNUyrEJV=4@PJ7;z4}UR&%?=$eR7&@Q3?gU! z{+7rKYepRr-jn5U2>W158q>G4{<~&0Z)Rf(M?PwS#=Y)F1xXEkYzV^z7=7u zMMH+Z8aKyd3=qY2I$Tb1je`gk*FMltTz&TA71z5#TV-MjwBDxnP=(jtgxF?22}PVSPiK=HgNM?t+yF{^y` zLM?N(>2*@iSDKa42SKDg{5JIv;HoQroaL~tI6BftEDwzTWft$6r{{sMh(M?RZpnwg z6~X4BNF|vkDj8Xizsv)VfM)R$#e15~g*lkLwAc-K#;yhA+LQkUJwwrYrsxd&4poAy zJ^3D8DD_;H@n0w$*^~ECvp2LIzml^+`A1;vXT+TfpzGZd=zL7iMXQd$<+N&@2~}}V zCO>NQJqvC-*?zBhqRIC41c#ID5g4tZT7DvEsO8JVvgJ)Utg%p4Q4hYCYLRf^?HM^3 ztzN3S6TJe}$JR0}SyBT?^^o#=kg6GvoX(bIx2d~xzRad2f3LxwL~}lrNWG5ti&q+J zpk*Q8Ox_m6t+JDyBg}V!J_az|1pR4XJ=6}=z&ANes+eTy!4Fb@1v}JP)X!q0R7T3H zg~2N5ddAuL8Fq?*{fON()EHg`2!@)!WPpa6r-$b4-<&`Vu0zeUPon(mnD`Lbc-DOi zxs}*vUI$9Y$JQyta37j}3DSRP8aWlMfDcVO@f&_C-eQir{YZ0L(HN+9Z)ENUp?dud(6$`>UOF_g0$ic7l&3;8 zsyf+!6^KxDuLlj${TTc4jrCK|wpaWHZr5-R#h_`YLZ`xQyURt4vx#%Dz}$$>V3?h+ zBiL5_xg@08Adh8%wMNQyEU!Ejx{?PxEBh~GO61`>c54s5l|{PcH^VKnw8$sQ&pOHZ zA-e*}In+rGEoV6+<>{g1%tIuo!*L0&4T-}b62u8d(_?@+1O6c()PO%jaT+tvQ+UW+ z2p7d5MS5;Ty-F!NX3`!z@@wh4LsFOy#>}zPvKZ~0BY$?xJRU+6(q}8J9pyrh!RW|i z-Ad#ow6OPQIUM5k1Mqibacuv1xa25Ewrfs^KsRb$oDa7m*nHy=VT|S%Aajk%lh1%Z zF)A-0C#uP7(YGoh*<#iBhb(ljGsAH5ovIRHy2kUZJn&a%OK7fB-E{TvtyyflV)l~9 zc0#Y)FnemV<8{8C?kN^>_c~s*O(OO~{ho6K@#Au^bo9myQ^fSM`M3`C)7#T1x#nJ} za5U8agMw1$Qg>HeAmKyoYQ=P&`udpsfs(i%#eb;z+ed3Q=r%X=AyC=nqkktqP~yME z??i2)({C;69{xfTqixp=KPF|Ey$m@;-eb})Z$E?SXnFhj45bGDr!J(ZWsUf$kE7hl zNchgc{eHOTX)eU~r|{0?j{rij)pZ>7qn%#nD-5uQsb!pqFyHlnxpNqrUC;LY32r^x zcf^R)4Tr*K*<+M;SHPSYdN89A+k8JJ|10wQywMVyS48%@s6Uoh>_YnE|p$2*Mf+<>xY08#&DP7N@G`}z1}ppsu#XDr-f@Z4E=jE zhRQAG$7dX6o_M%FsTZ1K_S_|pp>}JQJ1_Y!n1VP%$Ug5Fz7a_$EwXP!?jp3VMj*Q>q6?FT=JKa|QfVqGI&M!9?>T8W| zV}+Zkj=x*XPVA(rZ5jMn>@(ntQdz;q=S}lUrC_t+(sCv6JS4WUcQEt*nMdF<=Ck;J zi+SC7N0}c$CY}W%p|uWwDO|^ZEK(MA3`lxd~}q)66+PG4=2b zc!&~I9I2Qw@V1y|4jpC2A*UNa(xj+zP^32yD=m`p-U*&fK%w^oq5^T%ekLU7>WwrF zTMaKHUYdwy(!nR2ipAat%-7pTB8m=4Itlz52(qlO!1_HnT_SyaSVZY)izKFNDz0~i zK6)4rs1!YT`x6)xg3fN7g8CwNj}B_fl66H zpmSl=Zb*q=5Qh_Mh0{Q5<5SZ`vWd*kBkFfHLgHbEIPL-KVHuh8;GuscXhza=cPiYN z10vh&*ctGjgIRyFu~P|Z^Bo-ZqRn-U1*);qs-cHJIdzdYoKITAR?q`A94nhAW9xAP z_#9I?pK05^n$Lzl>0;xGdG5j4v}rcnf=1hA?BUjbgxe`VU5~jDm^!}-X9?T+X{UVw zJKD@`048f}PSOq=*B?V!pa*Gk8T2ZRV`$f2$iaFcW}Xbn>*HrJWo@Ar4M$f4T4NWf z-n-wGMzJSP9A#d1wlQY{DNlyx`BbM10bJy})m+JOz z)mL#v2E6Qxz&_sz((335V(>2qA2jWw!(|cxUI;+DE}0oSs>Rw&W!k(0ZW4ODJA#q7 zP@2J2>LUmG&7Pqf)vCo(87hkIQ>#qvnuPN|;Z)`JdvaDLC&u_Nz`ka86;F(#2&+i# zR18ez$tsiPF(3-1>iTM>(^>NeGd{?ECb{oov98BAd6Xx2lt*nZaSyG~5n|@3FcvzU zeXXXU!^9!XBJq?w@Yw)cI-1hD{LCs0z18NzYzy*4hmIrQ699(idYv|`GVb;_18_t4 z7eGj=`=JxH)1D8-oB^+PpyM z7k)j!y?Uo%eR*SnmYIKm#d^dh*)zvth@xOTNvcAxhH*g9A9);041YR$9NaYZ#BeYJ zr@tNfxuD|Pk@p`T$7HSqKph5Q-vB3$7`a3Iqyc^ntUOsLO|{pT%B%1vEvHg-A_63ii=KIv9JiA$cJfl!U04&i$#9NcNlcO*Z+;yHID_YT5) zvgJ)qOjjo>m14E7cO*Z{a$JmIVZL)v?vFCuunZ@iE+sCwm_zOv%JnxQ@6I3%MQ9CJ zs_QcueSC1t&6J|_(+6A&HrQHJAp3sA!65*&W zJafp9F&;9)y8X%^#_h_;-h}*;Go&C{&TEw8??|?b*g1D39|yyVWHE(eZG? z#i^bSw+^ZHn5smNZYDQ7(|J^%s)K(U!R`v@+qQ(h9elS#LKuM;Hl9)0_W)L zWOdYB1XpEB+zq(ZxZg`K8RZ9<&(5dAiMS_r6)TfdMe~)I{2|Dxfc;Bh11U_KZ$fhQ z!s+D=y>j~QM>w!lF|O?@bYSVnX2Apea79Xx6zb@dfQb; zs~xH4AzP{+vtVGoeh-+P-+T|-wZd7=PeALe=1w?uR&x?|J;+ps?sSN!G8G_hD$@Xw zGnM%Y`w6Bp=iqxY+q@YwxnVi+REBl}bAYgLJ%G`~sSuwYd{j|=A! z{{||aM||jb(GfNx^9caxlF}=0W}+}to1PpmO;!slaBmE-k}>3D0=a3WyF7l+_pG|1IVN zZ$HX>9|I~3U|(&0EM~%7IG-_V4vd68!cP!8!JrWI3F5nOleNJH#2p}X;D0odAe_zA zA_&5<%=%AoQdsxN;%x|nhXUVul(_@hCA*_u=WAKZ@X70M97UC>=vvk%0Mct&zr|d+ zmh~7!!`HH&P1V6$iD=)r+e);HiFDf+J#jQq!!5SW@y05Sv5;{aU0fI%sq>_XPmQj=*+Xv+y|d=l@lx?zr!Nj%jWv6o&roV*z?ySGJCur;ACb*2(sx3Lj!~s0btyP-gkoo3QAc9jNU*QMnMth@ipjXEV8-2Efj4q)L@#&3jMwxdu*dpqe zRx?ezh0iVPD`wHYtGr6Lby1}HO(c`&^6GrogemrHsf=^s>lxjg2xGF}j3Qg^thX1+ zw1DmcE%Fs+R9P&#R9moD>1oLxs=Lv}tyCO^hQb7l?%|>w62cCu9z+Ev`UrNfI;0f_is-b(L5dGJo4+mxJfWiqMATx) z$rU!8W>T{8NBM!YIge5&T0PhxkutDsE5e4aOe<@iZJMk5apDlYud`vG+v!a6TxZkF z@dI4AMOYxyYtlz4bb+~b3}4N;^`-h6Mzg+IT*3108YetPr?u#&Y}+zHriz=bzFxp* znsCq4ry;I@IV@wl5CY)>bsNQW$3}vAO_MhJjW!n>c*n`Oz43V5w9!TP9B!{IH$04v z2c6s;z~#MVSjyBI!-u>B*uSuBs8crZ$aQlO-^rHiR2;LwUum5fg!?ZS`O-$G#e3NK z0hNn-;b}8zCQQ_+n6j6xi#32bsAM#wC=0xTZ?@& zCO;$cGb}%p4SFttUPmCe-Q>B323^J%wasR`XQ<<$c=UtK7+A`mZ@1R4IDA;Ok}>844HaJGN5z$qLD;3~kzeb_X86nB54-lmNJ&R(~G{ms}#vV}}_ zHguI7*vG!ImEIDU1d7gK(EV7tW4M41Y+Yecj>P5kg^g$&ksvM#gcg8pVklbrZFHAf zhKwuRw<=YJFPZz+*VG=|r=7kyF*#lw=M;-240WUGoUN9pWi%_6YQ>_AfHOFFH8q8? zuPELs!?o#RwK`cf3!U{j$OYvT*+DUo{JcR!t~cvz$eg%Z`nyPM5lQyV#md&)BE+w) zHOKBc*#Ql7E=q^Dy0qKW1?HFW(bLF>MCM_u0j-ZRDPP0oZ@o?$ zEatEC*K+xbC}{rB!?Or&+V!L^Ds8(11DharIK4;+<_4`L-WSs$j?3kBgUu&c`J-pww>>LVdO;-Vk+h2BOU zW?TJbmox_dkjHLxF!IN4?utb9a%{48XGv?IGC5YC!r}pX&Yn8TIWno!lZY`|9GMy8 zXd}2hQJT!6v%}SiEGk`&N*_g0K*HsM*7n5Ac*vy%01nVua>Z7<0t=ySaE*F%se^;w zgrZDsLZzdDiY>rOXGxa7++GI_RClu>0&Ii9(Okr!D6wIiM&GeGwT8dOJz=o<6JrZv z_rxGFgmxW9cegUa-#%=K!E+2PG@3Vsa0thNEzK+a7uslJRE=dOpy) zXB*vB>n^e?)DQ)1yTwlTwq@!FmXNzFjU47wvXnHRA!s6^*rc%1;t6_PoDf#BNu)7` znOhjH)lI{6+6$!6R+}62gKaVNLz#EJrm&3}gA0ako;Ap16;<8rGEIIZ5zNm>c|sS$ z73&T0Kr04yJ3rC>Bgss64{&6ZNo1nOMy|_*OGiA<$owWrfu0iL1RCVh*y&yZ-ev~7 zy@!WB7jPV_k0k;%L9r!_xw6bE#ir2uBoS8w(RSt5`{1X9U?*Qn>k6y5;E)tGMqqpc z>0x(W8=^!jc|L%?G~}biD?_Wz<^pnpwUoNr-V5j^)(l7A#0Z1gETXN?@5i`8b`{kO zIbfZ)IyAFmJ5PRfApz4A+S*9Q8GNET8`$pM>6s14A_iu-TrOZ2$V*0q=w4HD_LnIX zz6lb6J`+CzOJKS~1bVdaqtQA?t2d9wK|vQEMzV z_Ss;tFgLLX!=fPhF!F<8ak;gyfFk(}_)Vmvuz2VDlB1rXhS%Ilv3P-Fw$V}_2CERd z0l=WsiPsU#@7N1=)nW|}9bcm$0aT|5pAw@@zd}NHtnbGrt={Cus+AW>+SK;7*2bw& zEXX?=y@J$5ZGDY4GMUNMHg-Ambap2_T2qKwyg5WUNga@Y!&av^mO8x6O1$+VN|w=D zbnUQZ2ZV5OXBEEX_3PY?3^h-Efw%Pbbb?rKE#R3Co-2}*){893RxGt7CY zhLE`xm@M@U3Tm)i*N0r_bO@X6YzL+Ne{1l{k4lN;kYNk~WjS8w2^A#04L@i-Lm#}s zw7=|$Y6b0y6({M~EFU89fCJ3S5KD&4b9v7qt&3om(#OXX$~qYxO_*C6GY&>k^`tR{ zX=-h00P)G#1g#+w?9`I9f`M&=2Wc1Tq)V1`6biy=KXe7)$ZBF9QguOv2tjcV&y1EQ z>-gR>HKhi-AXAm$J!O1t^?s&?XKL09X*rEC%y$4yQGX3GB^?+|$}xH_lO*V&Uy9RV z>_hrd>L)XzyvbI&`0Oju@os~Vtr1zL_cMp2CA4e zv)PZ$m1wqkQU}s0v_F_7oW*HS#aVzmON5|xWG0PSB$^4kUUl^0<#N{Q^s#`}mFp|b z71$z>fqgLJ(VJ=7K!b6e1smoR=JlEbR=BW>muR9)Fw`l%XuORp1QQ-@DXjwZ6Iu=7 zmS0#9{lbM*@e{G9IgENy{S^AaSl85Y%4C=X4{u6Pe-*{Ch!qT4D%Lt-V+^AWThJUY zRaK}l>ux!V#Fn_Ba?NtR7S8elZS9yC#ah8wXEnRyyG)^DNljn?$a4Kik>&XTCCl^U zPL}6~rz|fT$Q&DUz(wh5HP(7kkq{0qJ@ab^RC-8msdVDCilLlrh9#Wr^XM#D0|8wT z55X*vuN%)Pq^y|PlEbMzW$0!allFa&=K!$9o7Uho^ zOmV_HFj7sg!zc;EQmmTI@ea+}IaWH)AO_r4%Gow7skO1fCAGTIz=aPu4`LQ!7UP3P z55Ss!2C2afD2NqJg_XiA6#2o}gHKZn_3fs;Xu4P-#IkOjunS8Wy;NN$4C*S)EZKsF%*!HeMU456#y% zw^Px%3h*Hf%H7~AY#$3MWX?Y_B+d(ob3+0Rk~m8cIsQGH+jO+Eu_|<2q(OxhNy`r8XHZ(6mxkJQkhDB6FVvv4C{%vAwjLxc&r8!H&Y-kBFAP<2BAsoon3oqSFD)A? zTQ1`G_K#&F=A~)*_RkNMpO$a`Y$(r5)AH?~A1XgB-~QQ9o|mTO+dn^4ep^~ zzWsx8Wq4^?xv~+2%1_I;e>N@8OVjf0pC2keE#LmxP@b2j<=a0$RDN2%{j;GwFHOt0 ze}1SWt=iV}>|l_QjSSDF6>ucJAgv594V5b!8p`wXLJdmG%S{@}w|_Q=o|hMDP+DGY z(onwrvuSx=UZ_E7dAUhL`S#DI<#~Cb2BqcY#)U%98z>tYUS6n7T0tn`rJ;QL2j$XG zo|hMDP+B>%q4D|l&!**hXs`S#Bbm7kVx|7_IMb#Z?mlL zY}~%hV&5JQUiF`rSK(}VV}^_0&6Lve#*EZ|w!Cig#1WIMvOz@X!Ke9tIvgbe-A6s^*KM#ifvgLrkv>br4W=^DVS$-&EPF=jFh>Kjt{ zoQx zr{z5}r|_Pcw7h3z3$N8B_I)Hs2<4eMWZ&f4H;DQ*3xlS;fN*UaI{WZr^1*Tx=wY}j5}#o6;dc9eHD(>~b+ z+$T>P(Xn|l$c&)4I01>!4G0@G-iAnL@o|;i^U?&$6|9Y?pvffTJVWj zz}JFBq<(p&v|28eBuc79N(#9t%6a=`q<~ZSE##cCitW@*Fb(bEE^sb<%u-EU1QrUL zlHd!{{y0}c325r=CN4Ob$KCbqcHKwB!eEcG(Je4;J9SdKBww_Zm=4Xj;HKMI!jbRA zCXSR6!-m1`biNY_=aq4W5(l5LyV&22coQcn_|TiFCj$d2ur^sd}_w&L=OeXH1}YorGVNr@k;XQ)<$U>v}KcA)i3gaQHEIu}<)XqFwCd<|t-$eu7{>niU3jB|;sQB7zS}y_ z8J)nLDT?DJoxmwy3E&;@IRTD$wY>lB;!85U?o(0ZYPT7dHoTNi5o$47|EHq*Rte#kFo*6AjeJe$^bo&Qi zBQ2OVXbs273YIgohGT8Gvjg`aL5r|IuBTdIc%m>lTCC4Z?3|dKoruBVn3T!B3nC5Z zNIAvnNNFrbmg1=7fSBX%h`HsMe0)sqj7hlxz(pnZO*k?sk(`5f+^IOKi(;}GleL(f zj>(yrl(W_@q1l)#67INiFO(xMjy;P+JFdvIBSrWfDSKWVd2Srlc`^COm=xo~g*`v! z$^ih!#eG&YdVzq$ig4jRRqB8a8yPO_tW6E$O2!DD9!{hw$Ip0qVkW8d;zcvKnJtd0 zQY=l6RFOL|` zsu*Nrs4zJ`o^&R;3o`ar%Vr4`TB}SJ5}7Y0GEqu|qLfHQDG`fOA{V7ZFiMGJloHV> zC9+Z4nn*`!YnsEYNfvRN#RLX{YO$D9D=^$-o-H$}zUx ztT>bO^wDCasCUq~kj05ag;BuKj8bH&ic=s7jFD`xGzYjugUVQ_6o(T%vvaCGTHa+Z zGI7}hJdXQM%2Sm@T@WMNQ-DrY((VJWIDu<*;>-d~S_??hTtE_;0+L7-kVLM4B$5Rr zku4yJbOA}^3rHehK=OJJ_uv7&dw6Q9f;&v)hSg9-O-;_`R7swyDQ!(Ep|mxrgVNTd z3QD?t*|lS7Yf=HFtx5SyTa)6Kwk4%6ZA%)gv@L0`(zZ0G+mf6XcNYsYX&=~8tRy{U zyf`rviwj=Zq?@oKoks`3V`O-2xSaItnTgS4{6z-hP=(2 zO;%>cGu>X$Z0CeD<)~<&A_T*=?Pn(~0OeJzPOvZySCYnvcuBiNyrhXDUea0-FA=eb zm-M%Ymk3_OOJp$OCE{2ao)}AtOi)r%f|3Ffl$490q&Ng6r64HDJV8mG2}-g$TtPDp z>&(G*K|IM>EnXB06&b>KIyRE(9OcZHFvY&a5=u&6D^3kpho^CUSrp@BD)6{}KJmhd z=JcLIm7cbZVk_>>K%^-XQDrE>rgu!v)P^TUF`voJ=V-Bjc{lFB&E*zms@38|Vb9D& zIqkp7sm+XJnaBu@l!!7N^TFX^HJ@b-yhIJyKxF%6Ss4Qp-UryC;3UQw`Hia@NHX66 zlGqDCviV3RKJc?U6$)VaLl9)6fblw$lS;8x%Yb9JVJ~lJ$F1STDz$Yc#OO$6FcX)I z@r_?2dFIJTn+SLGatbU-w*&-Kr7m(RZ5pcgCh912e*Y$5i0YV2ZzNRDNhXNz>|e( zy{j$sterYji93qBhjV$A!gMvMz)EdLIW=}G(6Vu)6+Ai?Bh_qF)tsoRnW(CnsK&8g z5l7Bf&rB3|C%IsFs#@HY<2%P-p&M=J`kEZyS^HwVI9kTIlC^|pXJ_LG#)r#E9YQP% zGnMS%022hOZ0UF~T&UMDM<^t7RlwP)4nWdzaRvb5*#KPkm&i6noHg4DxTzYKb*58e zig|K!=lF1SXQFyWSaVN6C1vd@&r7F2qvaaab#1mhotA9p$Y{9&>uq{+Y^;*BwB==! zvYa%b;^VW%YNc3At<|Zi#G=7$L56g??a=ljDSGmHSG_#3Yq+AfC#5_ZyLNMXdWz zdIytZ0qUL;Tt-PWmM~3)~ngQ8+jW~wP5D8ynbhWez}Yb81b37*@Xj_`z;)x zd&PRVnY+n3=Rj$uvrpnJOfNU_1$qIO#P{hNq9okoDEu>dl%b@J_6&GrVc-Qu(3?v8uxN*@8#CrOU=1g1!gZUCBx-pxZjqX|+gLGuDZA;bzZK1kS#S1*h3is0_DXdfkQ`!`Uc1o5 zm{;Er1W!=lZD8yUpzq)Y*X6a%#tOu|&Tp--)8|l^3hSqcX zzOglCyv7_ipgyQRG?)Nie@H*%~0Cgn;t08m`??UNN3 zvnA{NLJQT4dqRwS;t>Sz$7YuUU@lnPWoO@0xTfAyWB7L9Si5i=D=r*lO+YaU-RiB; z<)O6M2eL|Teyo7SAwgn3cEap~pvlt)w%q*ALGbQ@49!|Dysa?G7m%6)HV^ch`UXR- ziJQo21ZNr>J80`ch}0))LgH;Fb#UF0j^|tC`42&36&gG2^9W-VNsgZ{;L`HlrtIPi zG9d_1&Q#_RxoJ6@*i$H!zVQ>8W6ZQvSrknllo&7i~32u=uHs@Yfwyhzrp;K0W? zE?+Lly|xqp#iH9?%5=pyZH1FqQ;29SD?C7J;QnPBXQ=bmv9%0+ex9BMsbO;m^c5** z_trJY&RReY zyxksB=gti=mEBWIREy%AqgOUabKv%1JpLojh2~m+S@|q);>O;R)%jLi{qUnd}Y3m_82)p^b+NP`!Bc%0Gcc zk6$@zt=n0I)g+HlU>L49m+;yK9*Aq!H}HBx-RfPa7_Aw(P!}W2{65<0s24et$RjDG z@U+&lS-_4r+^>w+tISdxZ@jH8;{IG4-%=aR5>WH~P4XpIPNULUqU*px3&snx{2E;Y2Zcg#`T{x zqky<9VDM~oIS}Hb>K{G(C4`g>_nO`nK@*FYtXd!oBdKI zpRANYE1GHv@7tlD>RrebmUnk9&=yaoxJ3xVfJ9FSU>4P%GYfsQxz#LgHMsn75!;AU zU$`rrw2G)mGOQ$BX%_W9LHD%Nt<2awtm=Fd)~?xv{bLT0>j1gd@SjWsp!l@%q@+}% zsB4KjZ0i)nP2kc?pedKN21p*9pixXdP7zSEDZ7>r$PZthx+#~VN{ish51oj@URpJ@ z8CK%3I5Y+FuAb^#G=?_W4cm^&DL8c*tTmvgsdsYY9dk@J>R5U2ptRB>xkVJtVdDbEsYOB9n2b18 zdOgL(NDYh>^;U1dHZ~X(N+Go5jBE^DcXGGlOKv&c64xn3C}9I|KUwvz$Jxa3o56S> zmpE1ziu9iP>XO53KzDI`kU9($Xjn9r%PyEOkOkj~&m47ZXDJ$X>30`)>xUD;`MZk1 z-pOBN1f<*&n42T=NF!h~*QOVq zu#$)B$6oE#9^Gwk-(y;pLH{{Vo~dv%`Kl zB!~u^+iKWiSgz(IK|uy3Q!8IwoS3y68KRV!RjlP@j##s@K#n~RK{hx{Ehi7ea9>TM zAQQTGCjxia@5=m@BxGsi{4y-8eyUWR5rhOL=@@gBPqwZ)$2w~pegY?uI<62W`y84;Nkb~A}*(w+~IMRx) z>J+MNClExq>L&}y5p=12yICub4PKw^lclGzvU{q56%)!m4dRS2#rB9x62)e~)`4>A zfoBQ|wpu<)dA=@vJHxh5>AN&B!S?a8%asLjPOPtZuFV3=ax6XUNn{+WulkxEQq(+& z6~Y-sdJ%|O1~(ikQE-}Nm`ySu@xL{b;9QksOH|vO+PD}#HZWsBud$hR;|P10==4h@ zN&YQ0S8-)}EE{z-P#9!>EklkrA7w@=LM;El3zpwjpwgz`1Lgr~H}(Q!5XaIulGq2W zQx!0c#A}7>F^T{~8a!B5oJX42^0AQa1`ZY3F>sc~U@Wm>N=Go)a#cY26fVTLzF{mB z7Fv3J3t`69na4>sjFk-zPMr-jRe2SQ=z6Y`oRy+(dL?2cBq~LHk3u*BQ7Pgf2LDP! z^sptp_r~uv%&@~HIYMD5eST#`Gr(?_1EyRSl&8rmFQhmZE=CtWtwJHGo>`}JR&v@4 zQ+rH`>2{Whz2I9MJE?YKB%lO3vWZkUetr&TF zqus;Xz4#yI6`r@G`0DbOD~4MvTG#t@xGXmpZGQOD=K-`?UhG|Js;0ly^f61_mmd)C z6Cv{pYj`ghf7)ga$NF4Jz{HnZa~YVIfk&Rm&jG{;_0mJO4Gu82sUUd$0^A-j14 zF748o<0B#fkN)92ZEF*6dYo-8H9Z_s#T5Qh{7`G;-(BrJR?Y?S-#9% zx|#3W<`zMw%AIZex>|iNy%?q8tp`%ccn^-sYxZ7d_HLTJ`_0}p1Hbc)F4l@$mzqn@ zHZ45+ye@gP#CK}HF~PHoa*h>7aMblS3(B(ZFf4|I2$|ONfPQY{fwQ=&1Q{GtG!d~Lr?3`Bvwr;~W^<^dZ43G=HG)qmmJM=d zbuIu93z=e&3VdL}(7xeZ%g(jI-woE+9I$MG{kG)RVHwo3F`Cm~3-zV()pp}vI2wNU zby@B*Pe0~~cQM{=4Pq%06Jior;33F@M!3j@NXqc)f{R*&N(D*mTRkxELpjg#S%lM( z_-dWS{zjwQbUvais7i5J!agDU_HitxS`k6S+B&=jwccH9u-(*LV3B2S7p*tD2W%Ts zs$Q-*Rqrt#cOlj;^u^!nnXp$;L`lyABK0a= zOTS&xmuD$%$h7&+U;?1Hcnq4vndTwvSr}-WdQ*^*1UA@79rmgry?Gr zoKwf=Mu{^Y!-!jwCOEENCs$dP1QE64b5H(6weu&HAcKyBk7JlW@~slE9^;-VYRVkg zmIM^#6({3{4WD_@X(R?=v{u^%aasru=cc|{7vk0t!s&h({>9t?pJ)|i!g4q~y?Wb+ zmCQdI@^e6blotO-3O9x z8}^Eg2EA)ckEiyfHq=eIkNWHcf4;HbhIp0CeH~)sgL$a8^T+o@sD7e2&}$0xCT*|p6pG$TgipqzU$NIgUYKq!D2rmU4aCbA zvj2-<=}@r^-R1F$%~04^^&U;($S-U_QT%}=*xTTb<%Mn?&%eV2gkwn@bKWs0NYxZ{ z$Kq_A2LK)2V`nKw%jrM9o~ST&xrZ4}bn?CzP_vkj&ij9@(MK0imV zb5of}U7^i+7YG$`9<_)+s5yA5&oLJVzbhs$GT7D(Z`+=xkq2AfThGh&oxs+&%m1#e zntx<-D`g!ITduF*wiQ|~PJG$GE@LimoH2o|ZmE?L%5-mtP&zqj71Hr*ysm+EV)5*VP}vm;*Lzh zMay3Vltv~i^dTOVOQ9 zW$gRF4sH1n%$p5DvlLO^z!W`7-BW%HmLN1xV4c@Bg%qAp8U0-}enjz+i5&tjqL!Qm zB~eRMI(;Hftcg@(QzXG4Kx8xk5Rhw2W7A8V&yK}*n;A-8M@scYzYWxG0rY+YW)G{8BTalr zrQcwp3yP-uqz=FlA=O{$At0!aY+nQ$-*vE2Q>Bt(#DK=D+;EXuEyl-EySS=o5#RHc z+GEYtAPl5Wv=cG#xvk&ChL-*8txpeGV`I`%n>y4iFLl5TbyZ$K=Oq{2pISECTFFl0qi z;j6S~2NSQQc5S}ZqYutYZ4|43HNJ&y|D3o%5gTmFQso_rpifGeKq}0mJiXCZFwv^seH3~X~~l8sH4+pOL5XyKhFz*+y_aH zYgJ@!@Cg?tIzSI5CKh@n+fE5SP6<_=gI)iJ**!6u<;y&v7P)>{Yj0d8f zH8msPjF`0MMsaJQcXNRstII~kfIXXBJzT>R?|L8G3Gn5>6=FFoa*rqVE?xC*{)%Qd zvUyVn{Rey)G$Nv~lioq-MH7k#ibE*DfcKf1QkZ1m1A?G7de+I37WB+7jVz%2?G}H8 zeY1R~0_d~5TC6z^_2CT=Thi?m<4qOaNNW+Ev<~-KQHL0xdRnm^^dkB8=xSnr%P|kj znFyQ(+f4i%4nO-EO>lr$J20|zNaQ=2H9w!?v)u23&;dstCwYhOSfIW1MBWWxv;vvC zhN%QJ#A8xpnpnep`e38t7K_YNGZJhTfGWW=#YFQz?W2;7Zwbp#7@7qAsO9Ggb*~n? z*dqFp6e?h|XJ3cLs}bWWlm=*spwU5m`GzywujnmGq`>E*e8hvP$>V#84?sJV@hATs zi1L`j5x@W89&h%l`2BZ}ctqHeS6`$u{UVX^>ly)tti330h}JJh8vJ^+LKf}rdrfHl z8kTpblxiAPOdEZPw0bNv0^fE!&GzwDUki4Sx9$(n9Ezw9j@IW24f?_q3lRX9wyv7& zj^^W?b|QujB`^`A`K4`lhG^)1E7-KMWgU*Rh}m93mmikn6Z?H9n-RkLeJ9%y!uoY( zT%)9pn-DZ$jWJb|F#l^0wSj>iD6%oYDV+xQjW{%YV;zaEHOWY@&l*kJw-a zJ=uOwa`r~IGqlm03i_t*Xr$-hFFGUHPx!Dlr4R2&CW8bvDrkJDZwcUomcD!p($~ow z@I0Xw|>k+AWnKX%T9ah z9AumA+TYUutWU6zKf&4$h1dEWf8X=>J%4}W?{A#9rkF<-`2b6~!fD%6d9LHn1NRzM@;v&)*7iQyMPl_>aMaNu3AD7r)73*T zS6D)#`eO4Rop9(64W%LP-=QwBRW!6oP`#l6^YTu%z;Z&ChR7*fArx|8%lhNN&n@PH zQ5BXaCZN#QF{?XZ;I%6~FP?|uDzs%#s6Q+2LK)NO8@#$-fcX6>iFTl+T_QFUo z+=tVzOv=pdI?7&w^e4l>2GWy;O@GV?4L9@nXqU;7tnP|WC&edcqh5y_vQ_nSdZZ1v z|B-@4n#8V|UC$}jcjMEoq(4rCXn>``^y2wA!aN%oTW(jQcxFH~zKrRKn`0|Dj!Q!p z4tE;6?bDxRw>YW~i#jnvx2^ME2xZ3d^Bh2xO(whq7?&rwTW{1>KYvnrFB^-R(;$&P z3R7j`rXk-7&2xyeSpm1u#|t}-AtqoebT0&TJ3$IotNPST{jwMO7j4P#L!bUOC4%i2 zb{Z2SCJ)By^gLhTej*+TP%}_rdIG00M4A&N$ik#(RjKQzDK7dk3JmQeEY>B{bieD&bwA`)1psr60BO!rSCEFrZ2hDyS4DYRO zH-*N*x?PQ&ZZ@J(b;YB-AFlB^C1(3&Rt~+@mg;V|^n~V8<4H;=+JW+bj#`3XsaBT( zD9dw%Pm=tdZGgqdE&c6g#)ScNMrx0oB}7&FWRF+-lG(IZv9&+mYzLLuI{W?uoAtAy z`HJk_e;!`N&5<_zhCOvy1GS?*;JezIQ%1lsnuDHfy^Jo#{B+hi(O(wHQCL@G4bHTF4DzzPihRxg4g29D$&}R zL82*&vKU>|N&zR(u^tH+;$Mij4^S(!B@mC%iOXCE9hNxyr=o4C)1X*{Uz;@&Qe=gF zePy$Cjaai;K@WFkkzK`gIM|u#M!R*uzo3w+J2CLpZC$l-X#PV|i{W8764}ncCr>bV zK~cZdmm6fW7B{qeW;WsR+-2978J*|pmL1K|sNBPBn$%I!Xcs3wuIU52)1Pm)<6AwI z#b&oGByPJ^Lg(uCL#M#f2O7Z`af3#3QamEyxvMh@GfBlu^vMV;t)Z&#VOayfG{o*@ zlu}rPAODt5xN;hE7?T)6eAyyBPjMJomwLu!qMt^JA-3Z5HUWeE08H&m4VrKWt+N|j zSooVbl{&3YuW;y}<=2vfip^Y(4_NA})>$~(ri-9Lm+%HRkzp;W01t6VZ28q9EeGwd zEXP?l5OM^RUWc6ry40iOCtW}=hVGXYC;qCy_FyOrHZutDX+C;p zxc#%@Z6sZ8^@p!D(_2#$dzOwpfut&m@~$W^i}JLX+!d3{VuFs? zE<`oTPqqD~iYNndcQhBnj8yLhkMO%%_@oy8EhTtgtWxk@3bZ%HE$p0t9j;azGQLt( z3h$5FiIsR#M~vE)0V&#g5;3{;q{^))oLf)WTTkG9D#6>XWUo@~u#@Pv(j?8c9V_LA zX2$HR52Hq?>=Y9crDP}VWNJEc|5m0P^*9@7%XKLBAC|RU@zdywjn5xo_>VeR zome9!qthrRFGq8$YIq7&KijYKG8&FetY`W|RsOMGC9u5+l@^U}jD-Ocs%_@jNFD*J zl##Ut{{S;KIKr=ePwY3Lr&W-YVuqc=@IQWU!NKrJ4Ky4sxE}N>`?-Ng9~qn2=D;mr z3!5>FVKas`lv|iXxrIHH+fz@N9}!YElLc=2LtoPe=l2nk!~6*1-~=ZO&dzFKUTLU* zy>EQIE9f|*F+AniNviDO)x&*)5w!>Fkg%ODX%sDQy)KtC8f^8(`bKYx5&I3;d>W9% z$3(k?yDZt&I1Ak=!Mhp@QQ~IG-(V!ykYpq8jnyJ$sCuX#vwW-KI*1iKf$9ip09l~x zX%d1Oz=WYD(Cc>Ipx)$IwTPqADNp~UqAL*lytYTc7T?yxV8Q-4Ja^YODn~2#{pgXc zxK=hRIS+9jFSJpnd3V+N&`dpa3BrJPIdqvrr#aLyHfZY@1Pm)>$+9m37H7*Pn!gn~ zB!0LuwnV4IkMF48)S@@F-COMsO8unN*UB?pUZCI=lv%RZ%JJsxm*De;>*WVf{>Ib? zs%@A0#@dgU^g0@%x?@O)=wpqD`S(T?987(n;q7#9to?XNucIKUJBEUYZZ#tDgnW$j z6FT@=`l|7-q4o{1W zqc#D*Uf03Y2O8c^cMJusbjM42Q?rhtAZlqf0;mfZudhg%J^n0x)%aIZzPSyrm39yr z`<1rPv7g{rW7-7#dVL2|A839%-5YB^UecSIbqooIr$xpQ0SQ8{uNJSTKG5)XsmD;z zN_V`Z*HI9~9791ww;GXWLk_%-gq$n~n(3>?=L9*BQ@*(kua$NX8M&?;XrUv+bKqEG z+64T1eFsw?Xns508*4va(wmxf3<-y)MMh1FFdL$KW|Xg|KG5)XsmD;zN_V`Z*HI9~ z9791ww;BuN!0evH}zn;2k^3il} zto?XN2fN>{u7!Yh^x;OdN%OUu4yN5}bUW3X>OEG<8=7;t+tG@{l?Q5GpW%_{VA{Rr zwo|>S-eaY_*6FC|aHk{ED}PmAs?)SkY)PvlEuR~!pb^tLheIUnLQDnC2|1QV6FF$& zUlqyYh6SF*cW9&WpR?3qp0ddi*KqtrCJYN{x?gPu_60AsW()`N*>g0xU<@DfkQ>dB&+rAOxSX-0 zs-r@$=1F%4H{Yh54L|1aI|{vZ?5TLFdi1u8mAFkyqr>QV0-U(LebsqlK@cPgh2 zaIJc(=A@neMCp&=z;v|9QxWfdKz3#u6*q0<2z&Jv8=pT@;J;Gfj}-Vl1>~;H62DIY zF6lbtBn5u8mNRa4SmH;}+5V;mJI8|QekH{{_wVgjJ+|-rRV$qpY2N7KNLxu|{Zae8 zfV^v;mipEW+ATZeRV%d};|cBbUUsLOa%%_2J_?jvPw&^8BduDJk2`#>`^YUPmO_WR zSU_TPBU=VM5ivoyY~+1i!yIcte_RLDC9nq_Y#kC`ybgBQ<08X+Ho*omLHHYIdb!>8 z47r)rLlap0tP8T)`Gj8*80$pau9U-9h~hBzrnp;R#d@iqQ~h-G<8j9iYHN2kn6PFH zQ(~kn#?|3cE&88zs{FtqZrn|Vjy+_L|(YDU*%IF ztduP@`kr9AAiP&RK5njL2=ezy@%g0q`(5$*9n+fIUR*xs4=&>D%YnG`z;aH;FefSC zbC?ktFr~a0uEAcRixzIFS9uhmhb{Y3gohlv2gzG_7@*mMp5wVZB=xSp#CkxgyUg zpUt78bOI?Laim2RTbqGHbP}gSJ6V#-6$FQP*n|3oBKNkIaclKhrK?{g@O zqli7u0U$=*I*6uR$!J#m$V5R|WApL?7sjj>SsHPhr`&$h&xbJmS0nrqfZ8QgJ6XdY z6Qi?-Ne)KIHB=hBNL}aRtOo(bTFNY)QexxVI(v?>ZG2bR49r-kWKmiJT&1?t>HX9V zaFyCb=}bqWFGpn)PI^g9kX1ui%HJ8|*2D){DhvNMz(OsAfV<97c+JW2F+S@36^2<+ zwU=S82kH>+5$a-K0@Vi7?%0-k)A3F}m-;!?kFRIVjJ1_JB5bjSh3fX3cixCMg#~}4 zGh$ZsC5M+QEE6&9v)cgbg3aqFprW8?Z+|&3)8NPU#Sx-mh zZ0t#Et`CXWgONYCD7_;jBmfVGG$y+tx5$HHPgV=5ClMbHekl_b1kgx#0UyNmjQ@#3 zsjQYHcfSV9#rvyh2Vw#hhhp4sQ9Z^4253F%ca|IpygQLXPqgJ^kpQZ!t({D_M)vyr z{7FPj^0byX8>^LF2V};@L^j%~rDI(Dt1@x-Z)Mtp8B8aMa$7Q!DFl$LagJ`F@;%)HZBmYlW~6ufHQl zzTB?*%W?H)C3dS4{kEEy_Y=5Q2A^}ehEtQ7xT2fqqL@V5fw~8?M)vDqVZ7=w7lfE{v=}9>0Vm9vg#Ov~~KO$$5)&ZD~%(vQLp^)nM089wjybhdwLPW z({gwkW3w^cu+JJnw5bvX5q}Gt8IKk5Y#gB!!B|!i4W}+vv_Z_{i+w8ROiszrBrP#Q zPo+3_L@>a?KnB(y7YsDejK*t%6?P7R1*?sgc}S=~HrSxWco$JHM25yQgMfOaG*E#mbV@tbYjm5vzR8Tu08m-U@qbx=5h6%y?s2T>yJRO?^wOWGbVb!D&7YJIG9P6 zt`%bKwD(V?H+Z>UtrteR21>J`zf45!lfStLRZmE&Fy}HAKeEM7TLyoZu;7L#+ORT< z4S8{lNoLP(w0VClvr5ZS8(&hANZ6?MR~PWi@{13ZwNW~R7#&-~`%fuvK)ZW_K&^a+ z?|Hh#Wcg_`#ecot4nzu0{t%(qmXK1*I$nTBNi~L&CGFbI>=iVZtPI@X0~+nLC9gB; zI=qhd47Y(ow;O<(2OaM@i_Q7jP;d97@5{UM^P2|b^y=Zf0WeeI2E=_{H9%+m2V8Y~ zL@N92{PzAsBNa~z-E@0QhFS|c-R{HnUb`H+-#Ulyx6umOZz&bto%@4C%+%|LtNY%d zbA6jyI!H~e#dv&A zDTm^;{-QiezBOtsCwT4}%3UCfyFJM2DR6Vc%^`oSsQi!O@<)1hIRN0aX|LsF&@P! z9zUS)10A>%(#zXM7dBNAS+Ysa1tjEnS~s^Z{%u5aXz+c?HAqmB3mDbDiXFXZX+8cD zq5bZX`qWTcORrSXHai@&_|z~8vz;}66R4J+;;O!=)hag9rPf^Nlmg}PVx*Q?*N&B- znhumO(7flELPZ?Q8kO$nR)*$S_E-&zG|5vdt8S!jF2`R!;DrVEq%3cnfbs=Grj@Ih zX3^Gx6&HS!{-IYk6C4=8zWgsdHz=_6nVCwntQh$NMdndH9EYT<+s!WHH_NoK-bWb) zX0~!L)_m%%HljpS@X3D?%hFx{inVMg|C0BgZ)=ZnXhms9o<7{)$HH|ey7PC>7o_)M{6i{Vs%@AWRwJ_2e9_aikJWJNmjWC!w_ z7R@cGNeAO?)vRhF#G}zMXCBq6D1cdk(A+xQ zP0N!l-{~W??1`N6hA{zZOR8F(z+h3VBdY#7+;HLd!}L2^&q24>E7mOOT4~3RnZfMj z9S43mOf?&h(uXtoRJySQRfkBOV9gYJGdm@->6=QmnVLS9pr<}ADaS`0hsyke-+C9R zyQ5XQ1%p!eV5hcjw-WkLG}D5%OaQ5VU|OE)xkaNCxd*eQpbc%Xk5p(cgpJVcW$;-e zcV;gs-YNgzA?FEwndXBYQ_9?p7Cd#O1NDx3HV4tRQ!8C<{pDnXLtD;P-80N`_K0?+HI+EFLlCx!hJE*O8NnsS98uQ;v6)MQ)j(hG>})SmGX<>2$7kplrX;ID*;=vbphL} zDLf@|a}G!wAmXUpuWsQ3-@PjhF~S>bB>#HQ~H4wIdhOXc}O$SIhVvwfh zEQwm zpqE)!{nb@@mR-;O^0tJb)|1yYg7xH8fej^NJ;~3zYu(7bedNTtv=i%k)xPf9PTP2z z$GP+4h;tBgt)|2c3|1!=p2)t9;QhPuzt?Q-Xg8TLIJ43fQwkV}#`;3eV-DZt-G@J7 z-}VgZ5S0zH=BzjPtMhP%XYbsffOy66VT?0+Actc)A5`VwQ_4?;X+B0`)x|MLj*`!? zgo!9y>p_5?Oh!$`P&U?G2qIW;PA%qOzcW!9d8VGvT*o}bE)SKGA+Xg?QLR7~hV@lz zO|v!~2bG|CzM{Q03CbVXJPQ~MLD~xUVO`)*QZEQA8V5je;G_6#hgAMpJmPXlr0GXZ zk+WH;&z&*e5Jy&}g=D8^=Dz=v$8r>-+5DJy@LtA-?Pp-Cev-MUK zX*r=>%sAbM#O3Lap1G(NVqoHaDoL_jXOH({4m0EyQ3W?&6#wJ@GDMyAT3qWHP4Yib zqt1EZPAdsVZo4@zJo-82^q>D?A;HJ#aJ-OTx){iZG?HMt%9=CXP1n4J{8}yl)C|*{ zr+kjbYK0W1{l4B@63yT7@`hfUAe!y=GN83p$XA0}?rWZ8LDjono0!U99sTj%jYb{K zsD)opjp$fW&*1PQ^yy`Llge|3WBHz+pEm93sb37s3Z=ZZI@jrhZ-orU6%QE!mM7f`)&(b;{+sivrMIQ-~S zW`t$gmiM;ooF4%X4}43bvp(|!lZ!eCWj&S33URPS(hqM z%8$4XW=Cgq!1neYc6N_-x3&5X5nhIfc35rH{ApCOQa~vxz3_K#a1HN6C+?w%pqMgs zY3TC-j^(KFJlFPIOu@UUqnBFvS=0=ZQW-V+`{v{~^uN4QG%y=M-89mJE*@Jl80LgW z2S;~Cn~eBwIm5%MpV0A?H|9U1k}E*4quF-;Lw$MN&E}Am_xOJ0_k-m5^!#?v#{#}w z&2injT3*k%OrW#svAh)1XcUCf370#Sw09H!uyLGxGy-_Es|Cp=_sYfZziMW7WE~+pb04-H(&~I3+$NHGZ+T;c=()oJ+V_FQ%5j7YivLoAfFXy zrVY@8Y++7iuWEs;Tb|LLJGZ!Bolg5pq0H@*iWQ188L~~}ubs4goJ0mFAb3T^_jIph zq133uQE5#Gz|-&JIl3&~3tBB4@1?C(X3p)MN3OSZn@P0WEKA$f-c6ZCXqz@Tz>^AP zx4sPzvYXbovAFwH?32VoVt6BhRhMSQOjqMFORo>tH4lW0zO81HGQ6>i-y1)c+1BI% zf9KC`KRKE4WOA)Y+m%cw{h$-ayv8T3HMU4NII6Bug++$sZzITzb^%lKN~A9yJqhDp zSU+>q(%E7RZQIPUdE8pcWPmNM>5s$Eotjc}`ZU6Io~ygN)A{_bbJe!mj5f3Rk4)8& zd^=f=lc<3Jemb2l)yTm0sh*b8H7G2VK$v~lB4>QUWia3ck?(l~l5wrD-CNrGGqO)y zj5L9p=wQXR40fr~R*V0}yS-$%u#>H$T%$SX`US{@aIKChT-*txVl)Wd(CX5jH)d@J zGGv+2fomR!0+b zB;KJ#V>)U$un=8;nT^Y?Mgem_@UoOimQ*Bgb#`%JM;?V2s|ifdZWx$X`dQh5)$pK` zUJEd0AGzq2JCaUt*fwS!fBT=${^!4Odo7C=*e&BQ;=uJ1P9xhbgov%qC!hWM++?_f z-=uj`+u6~KQznJ!jL*lR<(F?;+kF%=ZL948ho1(LkIStz@aE|{f|)$jcPC2_GBk`J zRI#`r{D)M+vL#DcY?%h{bUl;?+~7P*SH53^W?krEPv zWdu#04EZU(qJPfEoYrg=@(s4Jv0-q5nA56OZD6UfAdm6E!dn|MBsbhLQyve$O1(3M zxr*&;)$>|9PssIN1~-H-51dY}Mp)9Au%jMZ%gAhN2)JN_W6Km`P-60vbD0o8#Pd+s2?c^<3#% z!w|0Tc!TyTu_v1DNxU0AxrDCy)XMQ~i!>K9xT;C;sXXTzY#?DAyCpV}prj&8)n+TZ zV9sWca;Cw1rkdN;Ai0ZsN(kL~@xY!DU5jj+Spf!MaWs!f{RL)w#WNQDr`y2|3aXdt zgIoSSDV8hki%_}g;E#6D7GDnW%6`kX5WSD6k~LjQW6YnYgDE^)8XY|?6=Iz98ugR)c^B{UiEy zbS4II1UF$-@X!pb2TIrkFz2xQRvVC2)fcFr!}v$Yg0V7&Rk2A3Ol9A2A-(Dh#j6rukp^?oZl zZ*7Utf0>@_P&%wBA!n&!Ni~Xn+|ksNm6@yII)h!aYpBS!_>t9BM{}|yXo|5FrK(rj zv#>-qX8#hyO{3?3j~4F@V4c!nzfwGCshaW6MlL$S_fP6U?4f}_=|f1WV`;WgBq0qk z->hKjKojY8gqUkO7hfyF;mFZWV!_~dJSOOhI5pBz#s)5x%xY96aGS}aMNv6y3{wRy zELrvY3gX78AEvOTW2Rd4{r^1@+rwy#-_4^)h zL)eRPG|!>~U=4_od#G>O{bZ0K*pvE`IF#>8v;%c`pR4o~)l|QO%F$^gfaWy|iPXGi z)GB#}*0`yiP31H{lQBq4S2zBMob0me`J5pfjynu(w$dBL(j&RHXvS#+tTvZoNJx~{ z@>cz@FJf@s5In5<@UoD%&MafXLe$Y6txG8e3txD_AqK_eZQk(BeqIluFDxHO;j0nu69&NLn(PF@?x$tc9W2}WRK5s`8dxR*JlE1x{NX@~*-zi6cc!Umu zAK6pcI$_ezW}L)gwi)N_YhsZiY9`ej+d>4YI9g%D{WzLDPba3W(RR$X&4wUv@0aO( z?d?a{sPM%TMDsNR#3NDCJm~cB42>jcV)V-oL`G0@G&@yE@2MEXzcxsZ5b4o!Qw%W} zk@PJDh#*jA+7s_&ilRDS=4;-VX6Qf_VH$Pt4=KfU0E&$I6r7SnDe$65KSC+STq@Pc zq#ce`v0iX++?+>Hx25y5&x)hg)8DV2pZ$#NlvySuB{uyc?_5?$_tNemMntgPXqgh& zCTT%nt_@YQU8q*=F4>)kfTM0D6ArX(Z}-FWCV>uiRL&9YkzQDk-z8I$j515_h3q$~ z=naMgEOv%h=Y1aY=G6agc!eR)UD3VNVS04w(v^JCn=mDfFJn!dln!fsb#_HA8y5

e&G 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 literal 0 HcmV?d00001 diff --git a/linux/libSDL2-2.0.so.0 b/linux/libSDL2-2.0.so.0 new file mode 100755 index 0000000000000000000000000000000000000000..1a68c1f71fa57732b8e18910577dd98990a5a648 GIT binary patch literal 923556 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Ξu?g zcn4j+f`(Xug5|M70?`6TV?6e2B9aslLFEdLL+|q^ItU>O(p;~0kH-Q|Q7Eg>kuCBl zW!0j59hXNsW*(ui?vK?&qET2!ifXs!h!IuqZIlgonpTr8I|BH4VND4s@jYRb7$v5# z_wq<#?^l2qi30A`B#^Y~jRL*Nj8b|uGHx~goOYwip<G8f~pjj|QEeDK`AuJg0^XVZMg3+a6j7fcorOquN6 zFoI5JWlPcUoF^rpsi*F}ncx4ZV2<$#$>_&MW=!3X?aLg}&`!&o- zwoV}nrij_f+PGnUX(~MYA|YOMZYnH`F(3>O+UB+j%RyTefR z=MWT@HCL>SZxqEj=_}wCL>-TvTg?$mz{D?)p*%Y-V!=dbJ zWsspSqBw}ymrZeM5YdNnC=pcGi+v4l z$wvGK5y7BlG!>j|?J69e^PK5~_GTgspV)gA|5PA^p~OyvC!8H}w4rDH6p1atXhbVQ z`CbnLon-4zfMkY8wq#1pM^io6+clM|t%hfKM(Uyj3Q-~^3?D}*al7#>{~i8nD%?!l ze`@BT-UinHo0MjS4>ewKEGPo&Fb!UM3@BayPf!v=?N#}#L|wnsyZv`&N%B2+sUdJf z{4EIR-Qa&$Hz_gkUZXl(QZ~;FCeg{q0>%1Gw7-EC ziF-vVFIA(J0UB&f99;fAa>IjX*pt@ZaD~e!=gWqicTYcJ$-yjzJoW8h$swX82jviQ zefqe5C~~#bhx#C#MOd39hxZx0A3;(mAZZjN4OpEJhF0&FH%ebmhaR$qk&~fq+7OeI zN=gB?dW+2WD!yaFsQI^gvrW{fAPb`}6K$4(rr#iDAMbxEx=4qL-4sPZOYPZOyh+&= z{_GvIq-%Zvt6V72wo;iS{!CM$x72{WRA3jx zN}1-msD4aNyrOJJ+1|`?v_R)WHVDG0_&MlxA6MKH?;VzH&!=K`&ay853)Rn4pMnXCqXA#a~yG zU3pP2<9chc!}p7{TxTs7{XCB3QFbhky@RdA@=j{mepdaqQ2pAE{IX%;ZZD|cHl}`U zebX`(QxPX-BmOIhh-M@13nGSQBkm_6HYQr%WdE9{l>OiQrq`o(TDA|ZU)$^>@e>Go z$`;7(}n;&(TL!mhL_f%IJeo=9Dce)9Tbz z*tPrnLG+b03!)EZw&;B@1<8jgXem?PGxC+oC*PG05IJre*6;HoEaLn3vqOxyFsrWKSM^PAE>Y4;)HNO^tsp zb!m~rdfd9qe+cqNiMwQI<=NXtwrm>;gmEFeXH4$O^a4mUI9qz6=j7~S=UN|DB{Nus z$aE0W)2sePyBlp8)#)*0cJpI$FJI~{mLh8T%E>6|WZrFzIiRNu&(Jw0{ygGS$tU1r zt7nv6%W1^8$qvQfXmRyy@p6dbj&QW-;(ON7qDRkpuKfWa9v0q6lV^SY(Rtn+kjFE5 z+HA$?Uz@EM{ma7fUXliB`LVzx_wz9@vrD|^C~C6x27O?#LLJwj*lN|)st0zLsWj?E z)tfp8TGAsEKXP^}h+jA_a_ig+=SOe-KC8IN%5c#aPl?~8E=Pz7!-|o@c2h2f=>$5u*0eA@G{p;3y3-d8745jrjsZ+dwak#%WfWK z_vl)VQpndTho+zl&^#(mehv?Wc|{-s*Vy~;X{-&-5FG!=+l>7B; zPX4L_Sp}zRm2D_%2U0m7#QjnxT)(S>70L&5J{)_mDA8$2+1tI&+>dhFb2g~MYYSgU z()h&>3`^vL9^+-WrlQT$DfB?rv+0&YBO5l{{Cx7Q5LxXVp^xQGtYq@NT2VHPgP_YltNmB5%0d=+k^~wo=0sxlQ{K{n!WF&s7 z>&r@WB9ghv6^gG`u3Vb<&{-Dcvumqy($yBYcC$}GZcshCNdc3?b=3+Q+Y?**QoLj6 z3#geLP;)xe_!(@qV(>&=g92La%iQf6YBGC#WBXr`uw?5wqE>4WCP(G2__bnyBu4d2 z*w;W9ljn%DC!a*P!oc5q?38kSlVZx82!vq8bL2FUmXE>aOZ0T_{qG9@uk*l9-LVul zpSG->-w|P%O5K+2b@u0aiQ#hZnQm$gdio6AFdgybQxEl4g%VqcFBnXOyG(Qq9EKJF zTmmA$)?(Un30AIImDqx@l(lYg*6KsylUL@WeBmtwSuqH~vV(T3W z{4?_aJ(V|A*K~#I`G!&f3N>ZqeCQk;yh0*2_-Tn-S-a!quH&Qv(z>=$DGZ7=Y2Z9< zc(Fot1P`V|ro?>e5#*w^3%n`Up-0&(t(^}9h~;#qNJ*E^-}FA77FM>P0}E~)?zjdr zOVW~Hc-nL__*_?*SftLsRoXn?@ui~$acRi!pa zCmO6@wxl&R&}LGq=F%g~t5{`TyP!Z?I_AB^kUH7AXoS&{rEp%mO4))PAL}lAk>qIh zlMpjq;^CVp_No|4mMHn#_exjcOjYkVf4*Zr;Tt?I3kp}-WjS;{8DCvt_h0B-I0(iH&^kIs@k$|nii*#4r**(ns`4bLc|&FE?Xl`ZHT`M=IoRZ{!7LVaCT+h5J4j#DDwxO zv%nVd527iWHDU|bChrgQQo z6-cpore1iEQrwKSN1MrqFr7j$@Vlk&)AXJ8NEMHe*O$P8A!T2)|3jIH`2aN9jLe$O zGnry!`ezaw3ieH>%ninoV6=}@Z;mK73 zOBdP-sm2ZESR~&-`hS?x(0iHay#q0u>RoC`{b1M;^IjTMc`L7k10UPw#ur+?F*a@x zi;mdlW(0TFH&yum0%9MB!?52diz_Dh(wgRoBY#mvL$?)lOv#pc3QL6nGC9 zIM*tc+%(HGF0}Ra)|1?21%z(+Hm(SB{aKTg{j%911`@EOO*w@dO;tNt~Fu(9K`$jt`fm-4n|W=B|&Q%?JSV zzcBGmKhB#B&g=*YQs1m3#lkIacyTroO6+$dQtePs2n9tt(={B!Zz&pUY+qR6{xHuS z$}kQ@c?QwBWg9T4$ zrJTH8^sVX#9glm(>7T%FcT!`Hn7!L)w$;!`WR~IEppuh*L)mMK!dP+?w7^?)1$u+gt+*Tiwqpy4 zW02CC)m*&Qq)=|B=BLv17dsWsyl`8!KMbh$M+MakE6GDOspAbc#Ex4uspU#u*HHqVlV2EZ5Bd>TdFsOj!Rvz z`u2whkva~c&;Y?jWHBR$KP$~@I_Y&}ZfvIcm++92n2$nVz*}|zAqO2VLMxMXD@8}! z;jn9sj`4V0D6gSn?)?z^fqsG7&jpAo4nEhai0fA;-;p!k{lRCFlvE2F2cHZy-G7K%2NYRtR~BL)IT0&Kk6SgWEVkj{4$Da zmP41ch5?lf6!|*Bh?4X>h`8ZIUF5>LD91A@tE7A>;w3WK)MALxnro3$8wT#Hg3H}0 z%VV-Mow_JBX>ir%4dB20puDE>WkZGd{|R>*=+A z$x6`Xz3JV^XtrsLr7MRjM^{-vykSLp9SKZhSqeH+E6LVdLNpd!9EhRK7>V_dVS*tK z`Bc?H)dUBF7j5W`#+yP7WjP%Ygy{V72~e&>gG@tN&UdFpDb6JY?oN*Yq+^C0pB^<& z(a!3;&Yhbx*3X;G{w-_lO4h$l+`OF#^BkWk=Lz3!JjgGvG(SHMSv;w4zz?FcouXx1 zo;>@EKZ2YW7T5ZHXD5}!Us3#Xg6XVFu2Rv}ksBv2!I967kGgCFgrp4ejR2l1i0FYC zlzZlYqRyhIv$KTxM`^2M;Eo$#i1|D%$P($h?jb8vB`eQeGoZ&FKub`f>Fg{CiaDK| z5PaSNO$JgP+#JYo+2+w~r5q5`SEdV*f~SlqoXBssL+D|w&H86=uHW~{Gq|tseO9T_ zOO<}lAhkpfyjDGM+1}ihiIXxT-I_o3N1~^WXM*iQG-Tp!lUYiXUeP;ozv;EE$Ay#r zWfZGHz3{FOojHznU98eybt%RNdu*N$S1yb$dWQ|!Byj8U95b6%;Fa;6hGA@*bM$Pw z5SF+6zQi^CikrXG$)_{)Cq0A3;zRH?+4}Hdinv039ZGprs_IXS&X@=AZIudRt3ZZ? z)$5^I*zbkyolNuiDQJ~u7y3h&pgT++_>FC2+*+CUMgSsDs1A}}Hj7)K*7{qbLlOrd zILvj23}?pcAcWRqrW^WCrVD>OonMQyz0*w*Upzz+AC1*R2dz~Km>y2b7heaXmd)x! zS^a_IZv5hawjqj}=taCaWBqoanUzATAwz-c4;&vakaglaLFj0s2747oh8#!SE8>$CZ?A*t=>_yrurlL(aGoq|qRm_NO7x0|Mg$kxiBO zcby5+pK8~m*;HDS4pN1!h8uXRfAepoOn-Uchel1_;clQ0=G-7*e*isGO?+(30LWMO zK|Vk=8^{mTTa5*zi`Faqw!Y|UW-5rb*HjP4AL+~gBnkJ9D+wirItweDIaQSl^Q{Im zwXrbYnWLSgrocT9WsA~824_E;LCQ8<#KiMRPJB>|M@;bklK~)0ojK(+1*_o%>cLqU zb>`%y$MM}K8D4yc7rWzu zIZDm%2ic2@l1y<4Lj8du@f+$742@q|e;^z`pO$S(H=M2{7-smjx)xAn8!G1%SdAZm zG;RDk=o>gQbI{&inwZJfI)>F{NkQf@aPhGt)c^6n`#%26|M-ai(M$Dxs27SntMN^t zz@`m+z-SNlyG7XVTA+xkK}yaE6dHRHtF-qT6{_%Asr#qw5W2o^_AUzxh^dnwdQBp!OK@0uz=_aRx?o(M3S1ThQ}9V zUKsqtLPFj%&4FG&$Dn+Dzv;S)+{;uWn%Xln=7eU(oKRyY@AK7xb(O0*45PP|gD+F7 zWDLbUj3WtvSB!PgmnJ~<6r=5A>+F9H)!mgkR~kl8N~#_J}WQ8A8j> zOD%h7C}AB()V!>Sx=3nSlZlC@maQAEm^^1*fitf#bl90!)K)F$;cO3^vgdnHNmCx+ z|AHMX2{cuivad&WGTi}TBMf1G-g^XL{|aGOc%uzrf1j?3ze|_YNgvve)0KNHx@boc zFM(JmTR#I;-u>f@AY8(EJpp$<7#48HF%NL(Eb{<&_WInR{_~tVHR^os(SSjcR^}J7 zE$|cre$9t{lB3%AB9i_2`ipbo=hs){T)&iIB208x{rL%k!M74LT3E8EmT>Wxvci(G z3xl@!WkyTci3AOMQ}*|RFGT~s7(MY*2DZu8%R~T1wE*5);ZG>}dL&`KeMkM24n+OA+V32P*Mi6!hU2FM9chH0{{6;9QJ=iwt@iH$8f5H1ZTk!IMr1gh4=pe zz*=Li{mB3_09xJz;A2!uf03g{Wze44BXR^aa*P~o7qh6G^2MvPFYM!)g8tjlKD_{q znV}mmzA#pcI;zWh%>D$fUPV>q*P`~Ackt4<_Mxb#`?ap zb5pu9g0I3_YpwZ-ARJvJlUrjWHEu3$zslrhT!KA7)2#9i(x3Eh-| zjnNZKzf$C^k@wmn@|WlXmWB>GDstBC>&RWl8uJJ~V506?WZfgV^u9gLEU}y7Qxf4?rY*VGjvM}8(=!KYvnpK{j!Dc+4~3M57;P2Y(eU_mn!WhGvE*F zx3PErb=`KoVpnne1IGz$IgLf4C;ZeV{G}>92+y)7Y za@YE|+W@NRkJM*}+YF*0!&7TXLWcJiS$Ar(Sx4ICC)3)o7g~?VS~;nuGryIaqa!%; zRB-3DB%ASejmcfD&Z^~LerRoXIM#9B>^ZFH)DF^K3@ZnNbkU_cLW8|n+q*YeKVG9+ zX_UugcPHyqMN9^9w#a5WjBa}k)Ub`Oy~|z4lSHQjt3!)mZ11yI>Fa@PYVFudJ+e4+ zfD3gx+Pm!@lef$Ef^b(5?kC)`w%g8W6zncL5`=k#?AdF1@~S6>=8S5|-Mw%!+xc8w zNZ9dWPJY`v3X*YPvVRM$+@qyGDBEA^6;rh+<+p5xPLS zuoP*pPzdvaI$0_AM?1q_l(lbo}RHn~$S3fka%vDNTt9(7xgqUFJ+ z=rLBqyF8-Ns|mD?ys3(0SRNPP8ZrhMk)koVNZKO^%395?R9dN?dx$@WXHH}p&fie} z20=R82DRN!$HL@!H=z*J2Q+R_i{>vcagK_lY0*o7395@hCCq4#Qi-$GP|h356K6G& zR_A(F!`Jkk0`y#BE%`prk_|)JSF9x$LV{Yf^3!_8SW9w<@n=oh^Zy;%t3+;1hSRI8 zhA52Vc#pv0$!G!`uzmL@dWZ_0Q7GSDv|*0k|M<>~j@id3(aF12)saGsC>fCoyxg>Kd5rF!7{?h99cL@zhy_){RSEjFLI{O zoBWym%D(cLCp>Gr?D;`hAB0UoxRh`U+M~<9M^DPR%4}F>$FqMuB}iI2T1CV_vbtXC zdXd(T{xT;DHFvB$ItBRMI$+X08C=k{oxiPJ?gPTaxjL^D^WdcO_ocBjQ`_#Kx1qCRyPIXFy3Kc#wo zD$tt zUcyFC>DSFHPS8>@SstNW>6?h|=t<8bpkr$$q0R<&TFfTOMacubFD!-iWn%p!IpuT5 z)86ncI!4N5!2*3%*y~4F8*s@kz{MAj@Q}nI})#oBPF9f|Va>S-` zv2DAUeRKuEe0JBTXC4B$uV8fgv0nmTUHhuql;c`RpvD^?JiunbCr%2FTI1|-T9AZE z=*Df8d#uLEd_l%*IGedoD$e25w?O1M#j2wkz0HqGx8M2xXWPyVRq z9mW%sq?lAid#n;oSm2h4I;kS2mL_FC0q!oO{Fo`~x^y%b+K;bOby?yQnt)^QT&qTZ z%jt=;P&03&Z#gqDvb$#GF7xuE_vw~SG%^}Bzrk&dsR!pTMw`v{SMt59z1mBG8>&RH$XHn2^vcTjEOO`VoJ>8Qd-DeTKqwBt<6a@2~ zU$X&6ZS4cRsL-xNb+mVw3$NkX(R{E~A9NGj{d&+@7WV#;i!QBv$o>F2z1R>np^nYHAHAnDfF&3-^I)>2z9aDv@m(a^lB z-gG(#d(PX_7m~Eg6xA2K_?dlT*J8x3MPf&y-t>Q>V!G^pc4jtXud?1?I_6-6!)hhU z3YCGf(y!IEb` zvnJ?AhSr2OJa-D;XqiQ3mWXHh0+*CkKEG&qYQh(CMtR&h6D9pdjKq)^+OU47%}tEm zOo$bc7G+@OcFT4Eg3;vcSR@l0wWji*-H|hjP2%Rpjy$rz-tyka(0-G>Bo@NsldUMB zeU&W>B9*V-^hV{H#O74Vmy}&C+JIuIB6&zpiHEVxBYv!XD8waN8+Bb>UgmC=iYL$# z^VfUSR&@Qr+$DE6<9t6OJ)%v;D=C0c=SBl{!FS<@d(_ zRUlDxgAL@&{n<_eFR(UqGU{wby*p3lq;iiNUQT8jVE1pu=ES(N-cU13&E_rsf$^mt zb?C_=h#7StwA0!6%8p#mIq=Fhb1oiQn-N(+3A;j~p|a+5IpSb4n9Q7owx&Y{BE+)9y5N?W$4VcAn*}Lj+&VkI>s>;yM98!GqGnBaD%i7?EQd3LDr?FsB5Z7O+56} zgTMRTXlKp7?JWlfwHz1;os&f8sI^w@B<{M@-@1M4Qu#Tdb*Y-?Rh9_|Vg=`|)y=P= zN+zAhSSGYGH9pk2(^t;4WYbJ+kOi>`jAwqXT7zi1K=+{rjRno^d*{hB zXgg-Gv0X-ObhbOe*HfuFeKmGXDOoizUSj6_uxvtQx@?L4cMEG$4W{fcsWw%zG;Iw#fekP53ROxFsl~X zsI~6=5ZjGg$aH01wfGU*Pi2)N4H66@CXL#Uia(fQoXq*74Dw?;tbLt zl=Cl6O?q)g=gwn@A_;1`r>}_w;>b)aD38p2^UBVhnb$kLuC1CO(DVtyPfP$TLUBsxOzM z2VYciPQ!9BA0DdtptUCykU#>eS#tkldIhKx=YhP|ZDA&)#!CYT6wi*u^gFao#xWZ~Y0U@Mu`GUgL&Jzmg zB5U1ejI3}r^`}z4P&?bHtOF=Jp1v`st6fkJ_9k{n7b|OO0y=Ruj$36LP*&_Z(>O_cm66h>9zw}SW@zQQ z>z6J%sk@=IfQsmfwivxt*>>wY-M@G>2dR_5rX!Vm63xC+_OI@zXL|_7P9V` zQn@a%Pva9fQQFwPsU3lS6oB2Qbv+alLRKg^QgY#` zlv4|OY9p!qHDqn>&&qJUalRT!RMk$%g38on3>Jb!B2-cY}}3SAGpTXB0jx_ls~kUsYf%>T5L; zxHd1X!(W0P5`r5~(<^e-xMP0hKV~nQt+CheD9hlqd}3+aJ#wRlG61E1T$FE|5)B{Q5tziK6{BQG7D(zMa3d-N{)99d`V$ z=J{cAe(+u&yf+b2U&WYt=WVauU*bkQfQXKU;D{#?e1b=!l_gG-t{Z< z)fjq#X<{a&&GK21_w@O)O@8p2A-z;x9nGZB38tJijcL^^{37TJl1#{IMtKW0W;Lu+ z)pzK$wMPHGc1|j!;`~)4OiH<$D)(lFpeWE?V$?p zX>MrVL<9k8BvTHjNPPWY1EeB)|<{1C*V4dt{vUGQJ zJY7ygGXgh0&-6&!x3rKP>^bQtz-qr6r5nnFBq~X+`-SGxwt8cWbASSF0A{!m#VlpP zwAO1sPc{CdS51xAX{<`d65n*!6vo1_7iqp>;joS#dsQtbw9rzs9oRcN<(D^DD{kYp z-gZ9g2QxumL0gItmWng4aUNo3m6`W5Hr8t14Wy$J{+l$SWRcnb`r5Oq3OQ~_pLj7SJ@6B zcS`)BUexGf```r&qb%1j_Z<1cx3XMj&pW7hcX?w7nj5`>&ZJ5XXNHgo3qDXEqa3X`&{lpt7bKalmC&$?mQK# zH*`2X9t!M(`SFvVG|Lh#M!H^MqRoCSCzGW6GEiD|wN?uw5OmOcYvOCETns<(H+>;Y-*?OzP7O&&Z9{}+}UX)+u}WAc*c zoq|>d8?$rvs&W=6xx|5#@hh`=7O{r7u$ZL9E!(KYUaQ`SzL^K}b9A|yH1(hDgD>1# zC?v@Pb*-E#vaH+8sAm36EnnaT$X@xykHhM4D`<~t?WsnKn)Y;avDk6m`cG_66F<2< z-JR)cPv2I1x`_7l!%vd+pB$iFJ)^G(8J3F*p!RkP@uq!!QpRZ{pq(X;ZeQnf#AbRH zMLGIIzRuq4Nl)}k>91j4FJ!tr4_7(OaaxyU2US$$-WuV! zmaSoK3G1sOXW|E}j()MT-mtq~d@kGD*|I6R2bMH%ny@EqINo^`#~^p&WW$kjEqPoe z*CkULS7q4!f%+1t=&h>=if1~lV6S7LH}4!Zgirm&IJcEzq?MzC&hTr6cl_|GXk#Gq?)j8jte?X8;T>mCJol3P zbzgDKWJ#Fbt4N7QoTbEg?FPPfTVo<;8EH{V+JAgDu79_&Pkw0PRD1PwLj<-?%qJu8 zwM+{rz|I1*FH^XprnaZD{y%;uiOoqN?8bSbKav2T@Lljcgjvt;=URyPL+J z<$gcLaXS~|TcFxtiq+qwhPxo?%tR&URlJrjruutMSiB$En|RdOU4NiA@q7E*`G+$P z($Y*>HXGTfiuJC~rtUA#hN8am_<*v^9P??_(C!xkX0jrP(CBvYDj0b|Um-@pzoFdA zUf47gf`{>RX46<5OVY0BubtI&3?6>7AE(bl-*x9Ds+Il!KE@(3jvE-_UrV`$U&9l% z?hnPU3*pzGrb|b<35cL9y`VmEWiUu3BG2(rpnBa{z@VeSfS!U46X2PFY zlxeoi^Jn^{yTWSx18?^HNGNfhB!4q+?tHGniho|>j0o#&eRKL=#{?1vXBDOFnSpw# zG#X=qG0xoiF$M$;&kM$vo%!YNEn#PVg<9?WD)*MCGr!uMUxFSicITJUsI7@>n;YGY$<6m|`CzdJd=kqgX_LbhJ z;2gC>>*1BOfmrDI!8%}Plyk9Er2n1Xy_6JM@F-X4-x{7A34DjHGDe@XplBZ%&nS^Zfrp zY?Y>fP-*fBYY25!V>MxvevlxdZ0@*X2L-+Ct0=9im^ZcnbL?AmeyY89z@D01&Nd=a z$J}w6N-K8fl)B^OxKiSdqpqw5O^#{TNukZ@ybZtR&D5FIfcrv@sW-Mmo5z852Y{lZ zi;@+u9?Vl9H@yyPAz4L{%dLu1;FF#Y=_*Z;^)Q534^z7#kL8^hNutn2z|IGil>%0& zfG<)o6>ugjGG_%E`5-}2Ize&WajIXxJ1$Cp6U5X)kqnpgWSIh{6I|eRQFY1IC9A1m z|CAfn9F~(vyQrs=I$PaV;}yJ#hu0CpV=?L;61tP+BwWq}jXXPfjPj@tMyhm70JJ_o zmb{dI8S|NtF_q5>rz>

uQTq{$`*uX_PZ1LRP~B;SuEThjjyqrjm5#Qz2tZf59ku z$h4$r@{pPmr;B4)`lDo=MRMFZ^j=njX4!mrcMIMWwfG8`9Zf(+Cz#o5b!g?*!%QjL zEu1U(kvl~cRXOXdhg+Z?l1jX@EV#Xro&3C54VnriTDxu}k`kVL*S~9v$2=Yl2L@Cf z1^{1Vi&O|C;~WEa7?mfGyhJH%F&`o*5e*D8H#?rGQjCaF>*4)&+hF^hkDN8u!);lj zoZ|-)_3tvUak&*Sz>_p-TpmTT)TXBPHx8lqepqXg_oG$YQCX<3y5k^AV*c~DhgHOMI?dC*it!#O;cwew~gM+3d0v+t{H%*P-arpFsxu z705bdIneYz8iWJ#jF6z2sW_uC>U@nci9LC>X)_NuuBcz|^7;iYDuc97{0q_-3tLTW zw%9>9i!cI^sbV$dELS6&ls+h#=UZEUIED9+9x6zj_UVXP2wsN3aOY>1av;*<7ifSN+5PZN0hL;CU!@jTzO$t`Q%d+7|b ze>bq2A3RM8;~OhYk6^szopR3qix>shP)g;ycWH*2eSHVxqogqYlVS#HWSG@>Hs305 zDvF<+vX=+l>;|-BU`E@EB7>~PQw8NRtPgAKetGV?i(dqWv$#u=uvjBUXK{~te$_m` zX`VaGvuB<)h;i@IAjVnzfqw7WZ{nXZF{{jTvw2>l-)BX!;kw3YLfkZhm@M^e20saN z+6;C*xiEFH(7TuAIg5q6&5Tr8)gRMlhN=FOFzR31;${W>gI~Rqi(B@O49bjPFyoWs zJ($;utF3#o|jqC46<~wCL-=qQzNHfCvSJ_h0O`s!7)qryEWcL2KIZ;8ELCS z=qIUTItk{G19A*v~QBRM#4&god9y6@zWOx-qD#mv_G=%%$fuvmL&F7BMo-$(pb zX%5FM+ZH=_Nxn8$hdY)?WXOE6GGBaj<|<_~nJ*rgSpvDaI`T=FBYzjOL}+t$bRcGY zw9gUM$KkdL(R3Xj!z;foBXqtn*{f<`Xg`zdY1@H5)rVus?_yOUwe;E4U8d!*#V&Q1 zX;1Xw=wEOf{VUnbk>c2#!}gCm-NldaPQJ7I@ zf2?&K0-S6;=~-WHgyFn&D97x_Eb&L;&d_7FDRh}YN$>2VbINYo^i7%)FW10*`p>}C zkxKDcjn_P!WY4QPk5bQ#)pRk}AtE%5+ToV6PrHNf5Kuunil%K!wf45ZA;-0Ld$_)$ z)Ec`U;bFs~q?DFRHD%1KsK&QM@`5A=Q{y7P6)L5b<#Q}zdeeyKWvu4}`R!mk z9g7+{oOf?}3L-8^j(K+@@yXW9pEg>mn(}B|A!|W~VV@o5x2fKde%%oU2EkvEC1|g5 zIMptG&cK!j0#lEdkir>?;d>f`J)I-irrPtD%rZM5q1ekeB&>eQG$yla4q(Gmu2H~| z_(}b`VJ4QWy{h=@t@QHyg6Y2IO;1Pza-vG%`*N8nbcT1DDL`iao{)Oem(KR!+xYON zC!{C_Bp)By^n|oX5XJDuxs;7~U@>z5HzG^P)?%N!A{8^+H6+fU=rL!hngvhA&Qdjt zU{h>A(GO$k1`3U-0C7F;oo#@=@?;5zeIj98;Se_t8han-@2O zY}2#qgEnJOZQ?u|pa1mXriIZu%VQKu_lFgExzc=d2FO+E?l5x` zQT#_2sppTMbbg3{7IY&Pt-Y!gUDxUwJ|K)c+$s6z856*nlJ8ztaGq~sUM6?0Lgb>b z<1vXR1m)8Lq%NoA!}Q>j-+ZI2;6Ec~T&eaDaayTnB?jXv&OCQxoEhY4m@{p6>LY4R+P0p94 z1$9rSdLe&&YO3!!=`78i7|U55>wV-t&$l@H1k^X!na?=SH$VG7^a$6rL2*u0ajLDx zZ-XqY_4NRgFbcE5I8hk0Hp{=X8PEWMcEQp==qYgw1)|I`D9wR5&Mt2)>!|6I0ezma zNzBmeXZsEWMzf(%>dQn)-_f41(ark#F^wmKN;=R-rMy8FZSwN%n z%t61+%g*lCqTi$M4Ya5}_+)MxAu{iH)bt!hZk&0szhl0i3kcsp_sw@T3@0$()e^Gi zyKjsYdJ7En&-&2Knh)J?1z7y`5Kd_5>j{dT?M+Lyt=T!)v{XjDv!`jP4p?q#8pf}h zl|NPAU!Z&Fz=Hz$pR#C&7_^VCpil#sVf-bSy3~WFTO6Gj$6Ro(_1JB~+BA$3=|!lU zhKfWYjw3=e=(TC6ndT5c#CgLGyeq?6pG#ucoF?vzROqhQVXIIOKyCF@^;#YPZR z#<{`z*PuiphkyQ9dIi}WVjwvfR$nVQ3g~G^05z+NP+92RZXj0*@gvN!SB&ZJSe#;fL*yu^u}Ny(Nv*IOc)0^hC3DUUg8 zyitEeU3}*QW4k8aO@%)^i-dO1u2HL)-#Np6XIEMC=0P2f0aw_z`O+}%52Wav-WHbY z@ybZ$uEZAfus`dj^8-q=;QGa^4C~rdME|FIf9%_(r|`*KNKC{M6D)O%Lyy^UZr{+;uB)(6{+-Y!)UQr)yc-_m!Ta{ z7Bpu63uDv34DimT0|Zbta5ODmqfqyMIQz{wI;+%5O<}hEfx^sYZ8^>i_Kx^h1DP0` zlc=U_GikYy#<{(t-JhD~v>{PsP8>9&ubV#5uy=l$P8(dE$0Z3Bi8qg>SjY5J>nqkH zv~0@WAJpHeq{}pCWw*pjG2%?NjuD9o)ADc4(}c}E6eyr`sMRQccFC?Z04=|F;c$0J zpll*p>EV`#9$IJ~?YM?^+_TFW`)1&(VJ6qg*qXX&cxM4t>-ccz)G%-3Bb`&(oUv(q zv~y~dX=i(79snv2BsLls1y=jqv2RLPs3V*)vY7g-Xgu|HR5OnSOYWcJlR25f$W@W| zYvh*uFMn_3s#MYn???5?)kx&(3)vT{doj!P0 zgSo9{5=ZookC3gRG95pcWToc~(gIsChF(a-c%xWP@!wf&D+ZvP9T#x?ESajvBp-S0 zd|q(grL5^<_W3zu*_?VOX9+n=SPDa|YhI%XK5pcBuhsY-=3wXTru)H(#*8DYJ8w5# zklxD~In;Ev^Hjl8EnyYm1ehT;r?m>7VZpr8lq$0kuU~xbMnnSfnI6pFWc>;j9Q{dR z!-gZO*n695O19qfJ0xd$>=9yE{1e5lf8MJ5P)wtUZuT7!AV^aQ6iyGq89_KR2xkT1 z>>!*MgmHyx>bFaD{K<&nR^zRKK>gIj8i6|AYIG$~3yUgWkB{u;vZky^J&GX6jTonO zfSE$NQ>q1Yn=+Wy!x=88H#690z%Z4j=P#rA0|%l@7_gRlv-crvT)EoVPIb!fuQdKU ztq@KTl!;o-o8_9tlnHQtwmVi*I?w%qme1mRtaZm}U7%()tXiW|(ID96sB6{rYHt6o z3BI{kgb^RpA_vrcEj2rTK;2K^eL-i-HJfnn`bDe(Af3%Ld;hgbuab*ezTLr#6ZO5j z{5P<^s7R)2Qg@i`#yo1DM;;z`^Lf?^rr*xAs9!yRdA{}3FG7A@PuF*N`eHM7@O4PRhnf+QU+F%u|_B&=4UDo2%+ z)~TVAg}OiuKft6-`h6x_eHA#tI};&Kw!Zuuqlagp24_OnEY*$9IO*?l%q=Ka)W{0e zekIkr83vn06G$u#mX*}l%?OdW3fG@uXZ{_zVxNH3ungi1WBvB1LV5Wg4wNgaAw?Rx z;BwaWD_a(h7}cVc<#{2FMwb2oZm~P8lHY}w(|eaX@3p*FX!nNV=laXpzQcsSq#eJN zb<|+dw8eRuK06a}-s9i|A49P~zia@5fEv)5;ImELv*HVFen(f6^$+nUWE}M5Zma<3 zdE*VzF9#F;)~8B*f0E~9U}f&gE>A`G(hgUcQOIu)hh*!t|Ay-*C>mV?}69vUBdEUR`^yJIA-5clnJyt?9x{$EnP2rcYP<=D9v!q+kR1h+_WIfLa zd$jjHa{WGSFpuEX zIQ3+nank8ZsLw8&EZ9w7@Gf}m-eLIt@sMVt4BHfV)9PdTOT1;9@P{Da{)=Dr_Y-Qu z1qfb$A2FOaDfyoeqP!`aX_)S;0#$()4*iCN!`Mtz_czL#k-?J8S@r>1K#TQQI~Q!l z;ukZmQ0Z9>uSrBEEuO^_O071fonGGDtAJQQzbOI zV!0P+?T9I4>_BLVz-E7i#8>QI)pmHsoe5ThTooV~*($Eg^Y0|nvx9}b?;X?In#=$) zJuxcV`<#rS=SfwhR4v;W6;eh=fX3ztqdQhp+G?=K`^8E|c#+Z( z8viEM+(~;rGB>K41ahG@DfHkc0o1)HTe6HHt4Q+Jz2|4~BS`Q|pYE^c)Ad~TR;%TG)JNc&YuK81659}SrIf&XoC=gY2dh*p!;V%1F;l8O3fmC-veEKZJ zr(g42Kt|RY`=Gj1QF=OZNv&w&Fh|VUqT1+7qAz?fcx!ypmpA)f!m;#Y$mJ&!vXPRT zjf8^wz}rG?Ryw$dIcSsjy>B8ln{6LJ*q;Sn>c*UOoY0@r9Y$B3Y<==S2llIG#&hUE zPmHev$DhhoPuRa|{8#bR^d)F6bM*h&kdci^?Ce|EIXx?usb$93UV=&MU-1SqXBH?a zLht0d%+GzQZop7VwtiYv`BuZ_sb$9Y&r6JP>xyV5=epx3(N&Ce<4A9udSWt~(6?im zfL!mBo3%)8p^Nup`i+If)L$?l(iFEbSN2nZ$=2WfM<3!Z^&vjrM|`q7ekzEMH)7=@ zVkS0==R||YOwDF(l9Byri0|Jondi9%*+oL8W-LPx!KWk@*2B9(I@U^!Dk|wfkg8MV z6`_5h-cT>Pq&ijgM-rBPKj&(9I%kvTda-t3_WdhXDPU$XQ*|<)}hwi!a>%zU$N$Hvo7TxZOwSmn$cy=U2n~8wXW;5zPD0e z=e}&sSZ`hTs+D=gT6BV9W^A>-x6R7*Sc|TU=zZ26YgXEt`;v9tU#vxz^}U_eqPYxMU} z{5Px{sH#KztsCARYJK<3pq6L6Y0cPyAUt7UeK^>fvDe!CV6)yfuP~9Bx2zjp8)41O zScf_du)hwqGH)ATbIS)=H@pKhQ>j#TK;(?g0vb>?qu06txSO9g&^G_OkLmRhSu~k9 zthrT#tW39c14V)6>;9T8#`iMTqO+|<-y`}T6$r_pOi+k)9a<^*^-D%^4ECUa(5}`H z6fG#Iil(pJnN1FXyiZ$<3|f&H{S*dq0WP`-_^=kEec}-iem;MHOpD6)&8~4#`TAA zUpF>IY$j2hYcX@ZPCqL`&CCoPOhr#b*r#LTm5{ZL=b1bg>w;$t-_XI3G39|c9f6Ih zRoJ z14n^f@7yZgNZLgqrz!@6^9F@ph9+&p4Ke{Me5{Ov?B~N!olpKk>oNIO{WCcW49eNc zax2&H3<5%7=m0pPp+j!T1J{ozX$l?WcPWe`?e_>+zjZI6uL^KqCEVo7;x7Dqxvv`Bmpwpz)eyU7sN`ICycJclFUM;9m4pfQ@;BAl z;|r6sJG4I3euO+R8-e`P@u$IMIdUdhJ3^j{k10>HaVV0K86Z#V*B`JNwtu3M*^}au zLQEu0k=G4oEsf`xe=~7uNE8|!L*44g_X#SA23b1pP0|zS7)g|Bro>eF(K<%rq^9&( z{_$?~?g2UmCLjfThGUA$=gU z7D5c-d@F*u88A`G&}`UQ;fsp~!D>XM(4udXNYkfyc%KRd)Y+tyH6I&x^Y5nH zAtx^pzD^(bRg*d4BlVDGc#xugT?G5xzv}}12r^) zyKo?Gq6~Kor4f-%2?o+W>9b*Ha@bxiiv~qRldTkw8+BMroy?Q7X5gE_L)gKDIntnSu{W$mFQAD<~md1N}YM9(@n~eoES-%r?witE1;TU z!8`z~G?{8whEAV!IUEFlz>y3An{bgDYpuH=m6L2U5~i*0l0Wl#!A06OA`__Aedx{s zJ>3K%?=Y$<^b)|sWQn@Z_!8D;Bupk2p2*f?7T@w|`~Wt#3O3OnlzfB6mgS(MX+O2waMAZ@_2>p0+$vU*DHn*vE=s--vvzUf8)edi}9hSv3g5;5= z+7V5)5xZI3Kzcv-kC2R}Fr%Bs4{jPh0LAERVMd3V#)q56XA3i$!i*l&G=50acohct z*}@e5Lp&5_^w6g9xlQBy3KJ&E6h;rjn#K=r8lNr9XbLkr(lmZV(|F8!H6t1W-XKhH z(-^j4wmWI8w@Lg>b@qpvSx2sOIMP~bjn)w3U0S%9D$MrG>JQ6_*S8}y@OR80px@G~ z29eq2qRdoJpZWjV`omVkT;I~0&4+oZ{HszE{y+B420pH;%>S8Y3Q0RM1Dl1o1_?51 zYNMtaHI=A=P20!@EN&@PtJ~dw{@uVvEtp`@QX3}0j>D8H2)gQ8-&WLWVZj!$YElX* zE-x)_r6_Bu3(T}a0R`HwkpK7hoOAEoNzwwo?*5m4KF!>F&bjC9d4JA1&mqXDNRLGY zmf_IzdTb$1PKO10BeRi<9O1GpXoC6XBjLwOuvNu(TVpqqU7xRU*Re`IL#ZMO*#-BD z3G+2Yr2T0zP8y;vF%QXvE-oYu8tHwL^tX7fc|%11gRMt%<;#HyZ-|gcFBE|eC?@=5 zNFvud5*ecuzB$|MnDCQSQ&q{%FMZtXm@q${30p!s+x!~(R?iOt)Gqgh$j*yV1+et& z_<7aS`x;R?$7o+ul_e;{xu8Re_QAau*q>>xKhv`3#IrM84fKa0&>_#GU57{_xyp^K z3SEE?9gp@y>z(GiG_5LoP9l2_AqrCtLwj-ln%>wYc8xaA|3zMJT!7quAP$?Bdg$17 zPr3*8ynz3W0JoKN<%wOlh+Vh9uJ;;pwuS6kIzdxLWrnr?tBT@r`!F8;P-f=JTp}GS+5-ULQ*UM_+*%j+Jrb@+hd63I7iecPJNVzm ze+e4t0jNFSm$YiS$a7xw%hFe#*76)N$2Bl zjy;Ar)aE0^GQ}HX_ZVq6+q^eWNu!+lI&jHzb4+o+4K6@K#@0MgO;ar4Cmjh&enYNi zLzyB~lU#oWn?oGg-sEQA=}nLFbzDl6%=L2i(06%7E2BSp~jj| zTOGW`d2777S{G#0uAEz$y=VB*D$GG{5GTPZnw%LrY$h5&a!^ilK56i($^w4HZ|f_W zNWha8c+!C9CkCDpV^$>h67Dm1!hPLR5^BUCP&w|8!G8LpZypo&qy76iZ==&ygmvu^ z_V?y1qVq4E0ra0o>cjwmCytqatzUxvn)Q^;dOW6Ssmaw`MN-Ywnya7yuli*Htd$PD z8PYmaHouzDn$?ufHK${b>K6>jp5g5s?X4pcD@Z@LTekp5jq3~bJ?AhBeEn>I1z~CPLwH!*c?``{t5Iz(e3d~Y|>(#t` z7>ZX*+x3V&Ehh-q>uug$@CY~W%&_J?zG1RycP!jctdrHMant@u<2sLKnf8HC3hgJG z9tX11XJ%deQT_(1Fh#XC7ymbsYNy8kO|u^VHWjC`v(%+T_AvKN_ax%<(&s8(Yhnem zH23if`NqvXzxGw!zM3udT%@4wjb?nNfJ9w#@Qs25!bDSnlF`BcPX4!(R!;?W`M6YP z&5XQI^-)w;Oxn!m#*h=W1BrRGCJRejR+b2DwKl4d7{|qijmruuLPN+~4 z3+Y@!>UcWW_`PZqIdCmFR~5UBSv!c?BCpb09c?#2+ifBUYHnbPWbCD=LMzQO;6;;iLDZ2ijM4`3MdS$~z@)y4 zF@z5$%^J&Y4@&n@1PVDoeS`y~b8$(#^wjv(#sR)Xs|m*eQrW|@C&#ms=nt$T$_DC7 zY@nYHJsY^%eOg6zsqE}3PFH46mOkp>d1H#P5tA*&P*WbXe8MBkQTSe2cU+%Q+>b3%to6oiQb9%RU*Y06FM->;Y+y>C44 z*#7iR%;HCsGyI4srV3nZCVD1PO{4-WQ`vo_Rb@w*8|et~ zu@^d%*1~9&SCiauN;$ep6t zG*#f&k^RwPKESOxkKi3q#WuFzV zd3xKZIZ#p@;s~1&G@%Ht*;w^K3>x8xjW~Z=C9r(wZNvK~#cqR(G^^SCsg;2Iowq8n z$V}L52<)ZNrx<-)1fiWIa-@hP6WL)>1l}tr=_2tfb&ohHH4qz^Q{@yG#s`$EgnZP{ zkqA>6_4h(F#6}uqAJyp0$HBVf;D9>K7=#Tco=d_Z9=zXNP^Yp^vVcS>SrM?$AeW<0g$ zYd(tGvO<@2=V*1ug^ObA^lIp8=KOea;1)=ZT|Ns%Li~zhr}*vSw-mm|$%%NjhW)HJ zx!pnA**FQTdyYu1x`oH`n(=Ms=GW#gtf*s`+*44gw3hB4;C`rJgW?^1Q}?`V+iv#Q zvJYh{7s2;Ev28c0Pg;R#`?F7QC&TKB0>EHZ_6PNR#o57EHr;HupgcW-II8G<$(|a3 z>t6b*v&C(D`}Ssr>+jsjX{aSB=?O+Fqp z$tS)^eTFD9|;bb$SWCs8E3HUDpBn|?}vvsJlI{Z?y=E2XgK zK2^bDCC@1Ar2mCI*H}7;KzgmEA7{^imELnC@cSMebBDPAeFO72ZBT1}z&5Datqe|?m+9vSStSZ07cHF}7-b0H>KDo2Zg}$6=n0W? zZTjo>r0`BxT5c-*QrD^s>{821YO=0it}T@vu!19K+mzS-OgU)6_JYwYgF2F*+(M2Y z%?c9yXxjK7pSjBu@|$mEU)xRE*|y9bz0d*npd<2D0bmL17{3TtE-4VPF>i*r>(1`;!=H#HRI8x`;X?32T3KzXAbCvJW*Khf) z?g_N(;IdL$iXY(RZBF~x&Q!W!eSS+15{eCUK_E6iP-yBGB>`lh5cznuM!C(FxbkH)(0i zg3%mCcQ7IBxx980^}313ikg#yD-oiGaYgp_jS}l3nA6{3GV6-p17&^vIpIeve|}~0 zXJtsEh?L8li#L(@W6+_Zcx2f(xeqnxmNyl@sc!FaQva{>*nFl{i zAfnmz#sA>;Mo*y%CGRP83#o*gv}QoAZ_!+NGXn~w5*a}6aRC`9cIr{VOVIJ&mvU;X zue=A3pKm>2!mSxB$9Jn$*Kl0|ttdXtOa6ru z$1i>q5{HOF#37;(amc}98dXRUhQ>q}6%8vilo~F)mKw6NC1nQxnegyMPN(Q@(5{_G z*5u$@fFi7B(Q!o3Eb-Z=W>+PXgD+FUrCa%1OsUwucyjRmecDmIdTIHDe)|G9{GX6( z#n$ssxRaLim%jg>Z1Seq1()+iZPz6j?S-8^3wanXWt~ztC*V$`r@cJ!#Wlmh~ zPp?*C34HtZhrDKCAQ?(IMcJJ`Xf1rynw+L4akm58cpJTBra`lcLS}^T?BLJCqDDLL z)!TmF8~UG-OzVBCk8}ElEZfs{03x6HwHJk-hljc|F|8(?^)32*l{-GxcU$)LBXdww zz&V8pnj?#9L3U0;Z^?B$$A&cKzrLRK=cEL92|L(mp-D+MW#`l=5qu~dXJu;9BZX$eE2H*a@_%(1}EtpQbQ2dxcBA8gL~g(N?-5mPy?f3s3-ea(>KOOV$af49m|#ydboh( z&T$8FcDjAj;K}xxb5p(VwgXq!(aGhfWe!YQ-P@Nr5bv7_wpVef?rdRw#VZW!D^B2E zxz*B$nT1_kus1qiJGA2!D4~8{2*R0Np31n``j}x)ST$9T%{p{7EqiXZg3qr#Q6KG1 zqFLJ8v+4zWTIss#KP}D$^&PP{7CyCE{H67S)p$U4=%tMi{J0hIyzy|Xe77t)*b>9O z;fL}44OfpSw{Cc6s(OS~jh(gCBPYJS_dScbHkjy-J?WSKqCNfc9hvsexI1|{HO!*e z&5KdkEQed77RrZ6<%$(qN@FJl)rM(Y=3SQ_Ju3Tloc+cv{I>V|Zcu+O?oofy^K=g5 zsF^LCgeRF}Lbo=A4rjzmPk_bg+xIk|M#AyXY1@p>bZZ)&VBQb%>*9E*d>^;f_KO`Ux)3 zz|AJ}@Pga2-ol?(i9RoR|@G zdSB}%`zkltg~9LrcJz9K&(PANa))QFki?jDP8a{tATP(SI8J@;Dyxt+?cB%O3!mcC z+?iZ7*E;JNLE zm#RVHp;P&pZMoT;YVCVt?l2pZPqkGpaI6}Q27!h9iqm?BDH???g#eMo!h+6TVV$)= z@EAO<+WMf+K2w2w$}+9%mbquZQAAzgQsCcz!>qH6d%KH2uC$5zXnR4yCW#L+J$D$r zeXZ;CiMiRKUUB+br#ZZn-;p`(OkYLD$GQ@d(H*g>(K85KuT#7?=4V@%O6hqwfKtMq zuNW%S7k-N-+JD3Mx{Xkq96b;Z?M|dpa{WbzQ*X#1#@3XfHu3ZuCw z9!+(u=FCR=Q_&_GZqU42d=uZT|7+bkM%~gn0!FJttHN997>>1bbDwC>&1ucG1v+UP z$LdEfm&XD^L`@M&1C}80KZ)_37aKcwB{!>NoFi5G%oq!CM*0ujO_|;!@|pCf9o3$> zQM#J-OjW4f@GCVI_S@->-$PpW->~Q(Z8qzf#~egoI#<5~mygr@j9TEQhPs9SOR4;s zP5C)6?p2y|aX%h4!Lqw~w0LK@d3n@SW)taxuDZ`%uBcJjeTCI1y16UFEAw-zJe&(} z#W0AYb?CPI{1^tHm}-E*5T*%pCB87gMXv*>xjAYMo6oD#*n|yb5{J`VZ=!M z-dPn$2`08~^m19wK&KAh25Ze+(R=6Ue7*?8M<5FR8Fjt?hE6r9eLkfK08YLWEl zXNJ_dPTKo{5gEH*UNC2Wd4#j?0Q3jr>~BQ({bO);4J3DREu5?{_ev}YF*s%>dpa9M zU|#L%bn(An9JwR2Na63v)=(~p@d{UZuF{X@IS>#5M5;jQ{u{3E0wQi7>jEm9>k;7} zAh3MOrBGL$8C)|idFU@Jk1#^w86RKnj2m!co_Q~u$s08Wkh)0<@ z0m>XMz2dXN7CC=Ms)+oX?z@i#FTMg}N?1qm%vlxuIaT|s8hAM7*U1!7GPWi!`8aKo zurGP>$vn;6pSk^a`}Jm8Z9+vvb} zvA*`a!KhgOk{9crjf(Y^Wn%pt$gp@Wf0Bcr2evzA#doxEnU_ucr#?waa!@x#GG{VXnY;0nCtKf$Bou_Omr_ZgU9{|)W`U|2U7wQxc1N5%^Zg-7921o)c` z{N{1dE+ekkLCEt>Qe}4#I9;qq*NVY_9QV%7I`;qZqmTO#Ot#33Zt`PcmM(07^Hkonn)O_7k(jo&K& z8@FH3k>tXEa#v*WOK=O%KMDhnlb|37Bt#7c3*r#WRy~E%F_>7l!X>b;-R_FL_xqdimEiih?Y6iY}4$Zg#u#%-=6TrExMrVgUyw`GGVV zeT7v&;3g&zaT61MS$Sg`k@5jiM_9^$P1`~056VZFKw0^+`e6m5^OppO))JuM@2}2W z7vTnHAA%eF)4D$vH|XCi!h609;m%({{}0j^kHZ3Uh^h|5%g}mY8oVMb;BeW1?l4_w zT}(T60{&MN(vMKT#tQEILHNtBS?qFhy0ftMDlwN2ya(pOs!mQwzQVK|7HoLy$}N$W zx~Ooj2wu6Xm7D*Ld7v`#Kw;6hm@3_x9eKev%WosFzJ$?6Y!>!smAp}(_%7q0FK^Uo zD=t8hZr>YqN3A;#t+eEhTI2QB&qejtT$$e5E4@`dDfBvR7Hf6B5=PiOI=3zAe{o6m zvZfih==aG4@U?=PujX90e!6VyQ12VFTl`LtX>QYCa^Pl$<@N|B2Y%$9%{Q$GOKfo2 zb6Ecak}K4)gr)DtrOMFA6ErefS6nxj5hp(^%zOQ@meK88YOUl5vRNyCn?#Gd@6)Cea0Lz-gW23G*K5pkML#! zk0+6LLzu|BFembQ&@L8lfG7O`Fjo}kih<2;wmX>VQ*pC1n=-#@qEqd;x$Suta89Yk z*U^wWr7rieG`kyR^X1O2Cp0zQfa%%oK1}5C?#jL1ce(5KdglZe?I~Ixb`s>VOXNXa zspPu5A=PwjdnM*J_1W1nAsMZ+{rYd{n+Ky~ zgP%G0bC+MGn$NfkOW`v`hD2odldEh`|E$f)b!`5i&L_WwFLTyL=}EdVGH)T5tMapU zTEnN_B?m@7PKyYGnEVoG_e`R%ZXPaZSBbE2QjnJ58q?gYjU?;jVa-2gOw&v~u)@4d ziQK7)+)1Kq1$Ul)QrvcD#B(vaGI|AND$14sE;R;x#$7L%?|wbPcRzgy8GAS*a};}; zxeNO4iW2Vc!*`kMQ>t_ZdMNkqM1IN3GY>sB?Kk<}^y_)Z_H99;fFj4mwzCI#V32XM z<373WjLns2Yy@BmXnw&hxidCDeP8yBA))W2>=|2gXKc)!x;1wJAD3?%B{jP}*LT;r z)Z7Jk<@&aiCs&@aId|$%?9{t5AG{@&x!{)3zHbXL&Q*VNv$T__&IOa>u0rjZt)Uy3 z_bCTd3vCy#fAi6DbPG40{2GofI?X}*S=SZb@>$+yT^Gda1~C{~MPbEP*dpMg{s)rz zsBZ~jH=ho|vel9A3)g>9tJqgg>U&vci{Vv}@ukFMEnBZlRv9wIQ?N6tmb@xF`At5$ zQRrJ&?Q-uTImq3vTvoP~{}S>gf7yvJ3%>og*Xe;vwl4oyJ=EqiCwy8DZ1Z^d+j^+a zXa3^~J*4xQFI}pKhJ0p@rEuTbn?A3UrhMkDU(iEyK6BNV^w5&eJo+zsXw7Hd{8c@) zqwnV?Yl0s9k(ls>;}nS^O%HJ zbGq>PulsLKuG2^Ir{}NP=QEdunf1y%+%gYa<-Yv8Ff*;p-LkN*Im&0A9A+v==D@9% z`By&km0_j<Ss!N3Rpxgr^Aev~8D`E?<|@nlg3o;DtHC5JQ06(7`AwgBbC|hM zne!}jz0X`5W-e0ZTP*VipLt1`*`>_Cvdn+?ndgR?-O79hy31d)!)KlmX7(s^r)56q zGh4#UCCa?PG9UMuFAFn!mHDrh`K-@;7LMV7y;PZsbeX>6~FmwPWJij zVZN*)<=SIvS3CJ@7W@4F3<&)~ZHB~RSOU`dye(ng z8kaXOf6YZcZ*7?Ob(hzYzvfb(w>r$b(&g3VulX0B*Bj=kpDc);P`LVEjH~(;^$y8e zG>IQlkm0Nxgh-x3`(zv0so+r{12+EiZ$}H?yj*3L$1|rcf(3s|P4MLmbPy`$UV=?N zUnn1<}&j$U(LNp6uLCHT=<-s zq>TVHH|cXq?XA{5kptf(YjwW&RTYK1QDF?_Yym8P>BBI>!J&NS7yKq?ZAAtmEb825 z0;xP~fvPARy$U>~8@bh6H9L3oz9Mtobv#X4{?gnmE~gvpmIlsmk2Z&8h9+fab>%a! z_dRLx{nNHi)iTmn%eNJ|?+fMp?u?_814q%vY@u-5FR+nk!`YeC*$MZf9eXmI&#`Se zTD@-%pDMH;V?HjPg=H>yr&Pg6U$9nnc|@zyvS`GbE!$fm{BT;;xu#Y1vOpNwUGcs1 zJ{h%#zU=LxE28$$r^@W1k1)uhY+A-gyWHmq2#eoCO_v%H2)XmvtifrYLd05PMr-sz zKJ!jL<=+6a9F#X&`UiY^K1?67^z(drUzonx(pUTR_lN0QEd3ikeO8!$3+do_Rq+*= z5BbbqkCW-Jz_k<*{cDMDm>AN;bEwBtHZ(l}#^GeM_~J5UoL*Xlhl&#L`?)-TFsIzzX#_>svx+4a^v}dWTy|2i!!LOP5*W~6i zJ)Ut*f(kR6d(0n(&C=#=mUh`Jymu~ezwtI22@r6(YPm_!OF@uy&w9WSPu+IEk0yOm z_S|-b=^j{%;uV}O@niY6$(5Wfp|i;>dwV|cH??MOdcm~$lE~`#F~;t~eNW4xZi>JB z0+?|IItlxp-hrd(*(hcH9{Zlajn>HK7jU&@^xYjSfwuq`okds*D<7F1f0Bs`rt4#g z+}XN4XCa$U7w-6g&Xf8_9i(i}&es9*w(Mza>l*gtHTyGWindm;#>^OA1rO(D<4f;T zHkg%#y81%G{0h^ z{MqsAHL$noPj4GYQ}z0OL!S5~iqk7i#OSA0S2VM7qPpfQlzBZR)odHx(y{t>e$Aj( zx|~w-50Ks&blcJieWn3@Mw){kgN?Z*pr20(=Vsi&&VQi`!HcMBUiMZ1w;;P6tb2jZ zv0mpKdtVv82#waDU zjh-Ex>|KPWv7St;*-^e*nvKlB@6F+&qlt_*98EMoGFszCp6j`>1uRYcoei>l2htv# zrA23T99Jv8353fYa30j;x=r8>*M&PIPp&&XmEG!;fWE&Coz(33cZ!v;sc;?cs;uqn zoaH~up3`2Or@eb8%lB2~PRJ?VNJ|!H*Msco;E-8yCMBG%(ff@(-!orqaT)5VoGQ&1 zBB8wBYbDk}@heZWbXG7zD`Fz?yu+PBE^4Ei#iG}rz?Ius*hjC3fB$xE2ga{At13I| z-rOuyqn|TdruXM(-K*a58izJ2vV_2&-+*$%?JCZ5Ygzp{vXtm{CFRc8$9&MqD++mN0OC0p=RARIv=m#UF6{o)FKV8UUTN=GaXL< z2o0lCFGZOg3Y>llLIHMz*=Yqi{%LOB>Q?o7ZgXaHoj+vWSQy1i)qlg)Z$sZB&=@N= zikX$~`D9amRw8#siVtYAZj?)(`O#ieZ7Qx&PwYY4*`AH}-Aq>t-&-M_&TX6zJdSIu z`gc>k2H(TT0&VUpIxB6i_e~|E^ozibl-G{Twj;Uu%u7G#%*Tvn^fd_Bb7&@d*F?#J zeZTvB%buTL?)fj@>C$e>Xa2^gyv3zlo6jurDX(-XU(ILClY~C=>(zAbuzcpDzQix} zJ{#eZ^$B-a0>`-H13A;q$lPx!JWpjZ9EC#Uo=7C0FlHLc(aQxhe%ACv2savCA^`KP(= za+-_Cc3w^H^mwkC-kLQtj}MVJ7R>B1C9-5=!B&SKIGkwM>ET_I2vnA?8h*5LasRHJ zmDNuy?th?SS@lozeTQX>nc>RhJ0Bl@b`ssMvB;i@%!8HL$1~5+xoY+4nMzKQ!`pS? z*5DDOtS&yyYwjd*UAma!W_p4aSLe>FLDQ_sokQP%gA67kpld0dT>n#UyW|=Z!lk70 zi8m7Vbsfns78pEfa$Y z^aS6OAe^cHQU8h+D>z!3qUS%t$kii#|0s{NRYO_Du1aAQFGu7iH)y5rl@-e>Du(wF z&^3Noa^1+&4-W4>oPt$3vUW1o;eC_GWW~wK>^!b46=R1!5DBn$xKKH~bBcyWV6wX5 z-Ic?;CRcCG)uef?=e2t4V(vK~o|N36z1i8V|9jj2bIyLIhp0Y)43|;z5?u-jJ}RPB z7{pO~3`^$~7H7E*cvAE)!Ruh$tENrXffi-%`B_L0QIZ{*wimK6as~C`6}{gUr4AFF z>hTi#*Nww%VrLp|6YI_4c5VKQ!|)~!K_S|(0Jc}zy+8G$RrnZ{DSks7%zp~p)}yE= z(R-dA!T6Ir#&7E1Wf*?|jITL5xlVL3(*Llb`Y5R0`1a&FO!^SPL20PuI#JfsyM}j9 zVX64xDee_gnIi1o?q1VG-kegseQ|cCp?56%VpC!%zvBct3Vs1#B7hek;o*6qB1f>Pq9i!r)?l?{J>Gt?+IPchb<~m!g(~D1v_!u0 zP!)=f4n@HtC1L1TCv>!HFcO*8G{|@{vW%z7-Xr;o_&5hNT)euto8;t%mh65BBAD!c zurVpo?`vL`-UURigM3 z4x}L-6R0K`omBZEhG_8%ob4b{!HsK$*0p2|gOkMz$p}AvH=jC%tEq6-C#7(`tjQFv zb`*>bigiw`X9XJA!Z8Zh1%bkKhm;%n$iK=Q<<3u_OFln^>uf$O(G6dj!bPmk2Ta4O z8s1eYg)89|uEU+eweKJb7Z-bSZntt%wzgXmkefxh+cWArUz z={kL@m!4Wo_l61dtw%EtRieJtdVQe0=Lr(QdnCivHs9rEQ z8b+#>vP1QYuv*D=+p}BkI~qy|l}!Dbimrt)Lf4Xvb&A&{)58MYOS;F4U62O)XPpHI zpF6P7Wu%7v63U0y>-wPvaf1f9!M~CwHLS@N#G(qAd-iJBs;C+^5g{K+4GXEFK2*aT z$|LPK<79>g+7-gpv@w|;b>p-#C;p6*rH!#5!U&{|9S$+-u`=75?)|u)po6)AGPZbl zHzaUJpo$R+6ID#8KRTHe1vq3}?t#iR>fWJCW+3Un+%%XQ1nD1`4d#~6U~oULU_kL93B`#p)Yp=km%eYHc=1bq8V zJmSu%!qKBl(ACbjdgd9GVR75LOGzYSP7> zLS^SxsVaU;>lAQvl>g#9)q#)1t9r*v)q9IHx}fkVh0*9TziI$KT2NbBkpXQ&-}aSM zu7{45&TS??8f+Io+88ZvC;J-AZFajbS6Z9`z8%7CmKaM~3eIe#m5*N}a`Rp@BPX?( zBAny!LsivJOrWN9aK`?rmG)P-Hx>fRJYghP<$zW4x+gw0#BK4fiq9p4#|ax zr%W!gL_YCv{CH1U{Wy}22cEKoG&l2=r30@4oS@_)(;`yJbTzBa(f#d6PQg~rf~wy6 zp5Pyl(Xjq3)ULhR!qX4wdX2+#M<&TZ+;aNTh zygnNKeqsFkpt5YhdpVJmfg4pZURyC?ZlUPL~H}qD6qQc#s_U8dZdhEaVh`UW}S| z!v)7qQ@p40rv}dUZO++_D>3xmz>2DH{r7$dUe8;a`%{HEmkx$@2@vQH$} zKimJ1K}Cy|5>7zRUbga+o|v5Y|3(jkI3PFX4OWz%NP9- zJ6~KWoN+!9F5hrBZ!93K_+8e~$fyd$Y#(lt;^*9gTUGHcS%c)BwxQLV4p6Gs&WjfX3Xl;bcb(I9kxabo!>?ThWN<{yr88M&1rQc zKKx*9N!C`|Elru54l_)QEKa4o%+-Az5xM(b6Sv6SPJ2M^zMqF;2Y*;%b3G+M=|P+( zTBNGd66|EkM@4T6ysmMgx5_P2jTgOKIpr3qYRW~gOhl9xapF7`^GGg`d~L7YMmGl?wCfgZKP$l^`eBcD?W z|8J-jzFS)Lxxz8ysL^J(XFrFSet$$vN6_owlDev*cWp#cdt|ajOW{eNqM2_&crt#a zCq70}%ZSsx6jdP6T_2P^kX-+i+qVYys0wXo>Ms}M{SODiqP(Ccqp)>3Rxz3$`nM^e(J z-MwOYb-ec&w)DX1Dhj7Brj^ZV&|I(z`!?_aPOsXQ7@Y#mjK)?c2cAgM{!%VtO0XK; zlRGPwxl!xVy*v#*!k=UZFT>C17N%v>p2@CFub{@?aVu7l^*0g>A>Dfjx|!p5 znTWTya;*j8&&PEIR*&ujcL`EimgwW;`n7iCf%E}tRr*rWm*zjroxR%L(xFRvu-1E+ zD79;f#Km@vLFVB~T&qj6{v3oF;FU4zK#U$Mdl%{lruu7AlIu@9)^7*Cb2l%?-?@vw zx}UW7&b>U<-}!JgJr+fXM!qMn>Wg;K5} zIdF<`Qm*rf?ajCTfRB3s$TAeq?8dYp>u_hSwVqtR(V@@A^je>_6jag~s64kmZ?#Pq z!`gH)kX}I|npLkkS_aa_M1QEoa_NEYq7`DWsgxfJm{iZEV*gedpy)uq#qRNV9#^}^m-Bd;dwd00zKnv$kz^~s>rXp@ z$2H^<#P;|?a=okT&H|)hdzfo3!D))3;O2s{b)PUBUBp#x#nfO<#ZX$;U1oEH>{;rH zqQ20gl~WfY-CC+`H)nvTD|j;upxZ;WQhhSO{-_ZDewBzPkGn#pK^-+iKd*E z{!~-G=ubc8i=O|KQ}jNwP`X=oqqzN+oTtA7Za!# z`+8ZW(+DdsHkCwT)tNm%aGuPfq>=jUM485N=Ywux)0fz$66(JbW+wI|fasE8oD)n8 zMV+8)$XB}MrJl(@E5RpNSj*Jv_15%kI;V0Pk*nGyCgn`7F%eQD{`~XY^5Q)IN$|Px z#d-dd%ya$!-}8)%Yl&_U)|Ks-ybEE4ZMFs$gP4;1keJdhV(Kt55mOHeX#&%Xxx47H1?dI#+nBK?>8>KMf0|PFUa87(6gvc`aVbTkE(Ss|Y7k`N= zWn`)kL3Xx0^6pKo45;FPPdiIZMG$4#xCtb&STSI@t;{oVU>+s0oJcW=CH`%43E{!7 zgpw1*W}dBaxiZhLOHvsju+f`C0-F+uUlDl6X<89b`?eyG>(G9Y4M$Bl2uu|{`p3OOFv=OZof*j%pCk4 zJqY6Def-OCchp=D<{NetVqQNC-TGM1!ju(MMU8@tHpMN)2eVXYvBmuRBhJ4s*m||l zyZF@uA%<61J^0;P4p@I6LAHCt{qfz>8|$q9_!8DxYx$#bvV`G!o||Z#!qbC|lQZV# z*3mdyueXJeC?%}n(~uI}jA`gANTx;Mb>QO>3(B7!jM$Emx_b{8u{z*Kyq3q@cWhC^ z#%+CbDD9PF#{GA%ws9-3($fUP*K(l4bB6yun2E5EsgsT%u z{Q0MLBJ^DH$DYF_FJKNS`#*6G<-Yx+;$hDVm_y3`cb`Mst{D*#L?RrcDj5?#n5snB zVRQY5>e8|cD#po)&5&X4L(<7is4h==>5%(S4QZmqP-1TMMtvwW9X~1gQIe=si}^}? zZDNVyrNAhSY*E9{B~M;$wwax17{E-9-8!X*4P*U>3l~jecp0I|8T&F}2Ui)=!`M`^b z6WqQ4Rjihb^$n?bg!PGSN&<})De8a|DaJLuNGX#RYgEM62dpzlmBmvx8YITbpP{g% z^@EafiMzjplAaE_(JTfJ_Mxn;o5@qn9SD`~@$ zJ4+*VbBwoWox2?gw+E}0`*K|7OD*LUO36;j zY%(|OpYL-#0c-|awVTMI^zZi>NAWips?TkunczH#HM$$mjTYZ!rNysDXo!L9;DEdG zGL)_2sAH{B1Ode);t%GhP|LA9V)9h1;jtu6<-2jlo?H?bW~slTS-CAjtL$P)QBp9; zI!hwd#=;t{Nlyv5-2BAx2TZnEmX~cbC?QWd*=bHoFwrXGCpSOkKT$>?4*5i57s~L7 zH$^0OCG<{MUUTPOn6;9fKuo-q0jP~irpC`3s}3ub;?}YGRQc`@kzZZkFPWl_I%%l^ z-1+eM^>_8lk&F84O|X8~USvr<7^Ju4OuDMK`oQe432d+{h-fdgqjxXPKdF+#U>#a= zPOjSq8#24gdCBrMtNpib54!Z!h~rHxD8kvrc>>3qc+YfJTm3$&AztM*&TKkN!qfbZ z-WzAlnXP0ww%9H1;+Z*|QhmqArbM`XvL<}P{rKo#P_gSXJzY^M3G0toPvX7clMHk* zIUdI>TuWOeRqWeya{da_)}5E>8}rAhTC;}4%{}wy z8@D*&v~*kC_5k8Dv)k7MpQ$g0_zWqkNn`2QfxeSpW(9A-Xs!UoZ~;l9DA$zNQY&aaIq;N)jS#R7XSrIT&1Qo2#6GGNcj+m&H>D zoV&>y8Ra3{9Fq3|%_aW>{kA#p#cp4l2q2KiKqp3H9PUzc|ka*JS?( zv6EuE&Z(3(D~w->5^p=`rk&)#uAj*d7Oq5%+qg5N&sm0&Nj5(f7-P$85IHh_A_kR& ziH;_(;Foev2)H#UQ{TPR5|=)h+f02mrA)ID|E)++^X{I~Yz0>UPO47{u`i8)3 zCMF^~&ilY@VoGi^Ng6Nb91l0QnaNluW&CH#jJVFc^qau;Iw9CWgw-W}<0a8s;c99N z%|bJViKr?n-PR90Xqqb|fr^axkEzd2s%S8G?i%l7Q}>*YOg ztjuF~SY)s$c%2>mc(Jy5_|Advfk}}@#Dt?^3jluI5 zeqMS$fJy0%u3}N=M4fdOB;BIz!{tebJy!a910GJK#R{55pbqd;yxUEMCAqzg#rQoz zy!B;;{nNC;_&ay7!T4HRgrRmJ5q7HthQza`mKY=TdS2t6oAfNOklvHo>aB6J^kMNEZwbn-bYp}OTU*L#)6z4>yrcB)ThT}a^N?lYQN|Ea;Mi0-T?}- z!0GO^!?`#w-9#HWC+R;A(n_jmJ5K0Z0-sSy;o|Gr!Mb^w?R3sBPrD{Rn%5Wa%yIDh#2!X zzLsUGWt64O7g|f*71T1BTJo6{{&uXhUa3pW+(Z=4-&UFxbr&TdIe2-{Q0|?zgEv3_ zKKD&2?EMz)>2j_7wXXtR6@`kYH!wt|!E06#fG?@S@4 z2zVDMHXDGN3$o4fk09H8{|K^uo=2`6Ej*Z%bf5PAldu*4(p)liC0^mC zI=dxq2Wk2ML}%PQfapvH4VmSS`aVmQ3j*jXy7WAc z_xJonN6d)9uef0myuYWE6rg-0N!ZRL4(fL2d}hb(Zn{Feznq;wl!~=d_ z&#E1KB%k@tj;BiVT2xf7eC8^Ttv`GP9D_bb`<>5p-&AVJXk)Hg(nW10H0$?Y7OO%3 z;6g;9T>r0x3WbM3!~PpSe=8i(?`w84L--|rE_Nq2&tw3)%hFrjGZS`M`YxV{x0L@d zo8m#IUxRze^+WV?kyeUy|7{7{$@RPYpOkJ*Y7^{sh{u_JpWeVD;2CKfU793Ma^MkR zvMVs%73a%V*uAo+UuK25dUD@!Gd4}zH2lnQOik;I!nDH7P04`^biITtLP>oPmIO71 zRDs*nX5pXi3&KBLI~m#-jb%rY15Gf2;nCw}?3=ca3iB^H8bVkagO|4E8;_zRozrfd zwh2>=u#=F$Li(?Z4~-P~ZPPn6U;}cN%ksq23)2gf2OM-AX}d3@-pq98X{?M$s$FFe zsP~c=%PdT{>Rf57mShGrQzt0`ZlgP&+ML`lpS_D*1`7PHqdZ4G(lo6>Q&R&!6?;w~ z3P1<@5!x{C+G7f9PL3`rXr0MJa^Q8UwVhk_)=_W1<{hz3R0yKmp^Zf{sB{{HzsFK) zu5GS4p5Qe2_S)039$!wq^Q_n_Gi?i_~2Ol~ALR#;nbz$$q7|rZiVe|eY zAZ%f`{!6DRMma&byqd8k2mTtDEce6t3Dhc_2F4^AW~};d4ff!GO>VxiqVlX7V5*s7 zeg@B}qpBMxk)pCdSs>uy<}XsQ ziBT{ctWU1Hv22+T5)iW+lj`VUZ0bC_Qr_ldt)XB%IWUv8x9FVo5s=Ky$q)^Dgh^hq zgt-9IH9yW9uVLrTs>#ngqaynZgrl4glkOkY!muz954~n&F&Nzt#MlPf=qfUf759t3 z<q%gtzLuuJ{1_$Hvl9t0Ba>zdkvz3<8P{=WFzP zg5k_M#BlN*F$C8bHu0`42OH&k)eVhuHlqaS7T;Xme;%V0hA8COxKTEQqx>(O)^P}X z2vlu2YAmW=>`?XQ8`H@k+n1p%oqo|TB+n>Y@Trdvt2(phQTcmwFzl{{@^u?M=#oEYz zIoZwpDgsyQ2{g`%fyi3Nu@jT{t_7ekf}-1q{5O{<(DNwRGS)<+b+H9Hbmy+OiTiu& zZCGhdVkC4T;3NwP4AK&DzgT~1)78*wiwPkOwkHSU;BoOII&C--Vb!lp1+2PDtlBL| zO}kOT!#K8cR*g8lUp$ycX;1pc;|hK}XO433qn`evmGULTpLvwYVTH@&*>V-Wj4WS5 zg+mftnC{Zip)T$8+o0_n=j$$%ZpX}foF{Qfj@|6Uz0u+q%US`UYIRNIOj;UdFG^?5 z8%EmG%{=;0HhY>WwUXDEq@`ce=LQ-ct+#v&^KXu^`>|_g42}T~25k@UI7_%smJm{mZ(J}4dR7+vpwI(}- zME@Mxbb>Z_FP@5f)_vNQZuA8fxB?4RfRVQ>&WdVUIggb60lb3=ySOxs!VqRu(5BE58D9%L!amf9E6cgVtPJ{GfsF#0M4u?slYo zzz^EWXg<5m5q#5~J6-G{7XWCEF^EP7$Y%E9-Y9T&!3^5O46f6k!XA3KMJqTM+y;QFfW7xt%X5vSvIIkwQvURIF5$FYSa4(z=F z*q4r1{3O-sPa~}aY+(jkf-wMS3@9x#WcKV1vWF)$DqPCKnGhLwey9s&JfX{YLO0Wl zRG42^LaSD~-BXTMVhKVkaSySGHi|6qXw_0eD{%~=mC#FQ)d5=d8d|lN(5lm;)q)6G zbxw#@trMYDi$kln0IimSR-I*NMQ6o7OcZr^v}zVwVW&WS+0EhwPH3Px%XINZUWG~F@T}Y6nF^K(AwwB=s6(g%PKdAQACIA2DK{Y|kSi26X3ux!?8k)A z=UjCvF$UYZg>Bu!wl_S<_)G{X3L9VmzvMXTb=bDlVH-!2q%FFw*`7s>mqRTy6d;`B zke?~ZAuoKy-n?`Y&b3Gmbt8vbk^|pTY=APH>oJ^L0?vt1Ne;D_@J{kaNG4?3t#>_B;@tqDx&-r{;7<;>a|WsrAj^C>!28(_MzB3k^{l z8zkF&olZ6}kVYv;4!MlOb=6~Ik4>5}(D=%x2_%au2w5J;!j82?Oz=J!$o5^(Sg%nf&} zDljG^3hF2hUWX)zpcH7Z&>=}z($+A6D4kB{-Ko9KKpq(YC1Frh1DPa=QpPEGFJlQ5 zM<8iV;QSEactFrOn#BgFXuN87_T=ZS+WnuzAym8X6GzOccFhh# zP?89$%Mny}(i2pVsdfv>NJ)Y>qTUG>f{kDy*aS+O_@rRdS)vcH)^s;#%Z(?cG)21{ zDMdlyqHvY-Ly=OqfYE8B)Nu%Ys2k&rAC{m)J0U4Sv<~*(ucXM8#tb1+*MMV&sC>o@ z7sO=KN#|?UJu=NKJpEEI`v)Cd3Jvq|pj#&2;PcbU7f(3^=ngy*fUfb-ywE^5mJ8DJ z2wgol8=MClG`eE)K+)zgbhXfBg4O`msaQJ4>ujntbmiGyNe-55s&RDH>?XXmWK(hZ zEayC_*^y0KKv&&k=?aTaU|4 z=RPdD_pULD&6g|!yf(%Ffa|0Umb%wGd5v+Mn| zwdLo;ILe7tW)39hs7bzSgh|jR!tRKoHD8sntRTnVH?^M4vqC~aCIMS9XHZGSa$ehR z^V;^5NdzfZcxoEp_%is%a+QfB!t+$FBoVxFtM z&~Lm%?=tvxkAWXn#0wM)Ufr5lEO?o5aIs+OR@6KyiG>Eo+e@yt=PwpQbt}-v#)t(L zhGbrMn^&EVDHG%$Q@HTYccxjTAWXz=O^ z;APG=U=`4std93&W5v(QE#qW!B=r$Vh%%X)xiMCJwS$C7+ENW){B^Lmq_Ys4 ztX2MNzRN0qFJ4;K7PLIMRLk_sw7+!)pj+9gofYo%V*bq0%mJ44bs3>s(yz^;o)-{a zb=oQ+(xC9oD<0R0pe3v#cP9t5>xs7>-n!Vc#HYMWhdyYi{HdaD!0IMO!1$UWEu@kkgYhaW5%4RY}W~j|~zP2K^ zKe<77;?dCJZ0iw&L+9VX$4z|POwBDZ>fAHTJ#99twKl!X>Q8LP1GDY3J ze*WqGnuX*#V5X@^u1erkpt2q`xysYD$gbgPk$qHa)5DV(QrGkzyWQ%wK)QCgP|4Wp z>=yLN^r&X|(aPbSQ>wRCKNfo=cI)D7jE`m(`<}{d?({3?H`~hjaG8s(w*NtM`L8Q_ z354+hx`B7v`m?UQa82=iTP<}1{5K@Iq0M)(+zf5L!AQ#nxP-BF6Vw!|oX&P@(Hz9g zEtdwHy_Ml~BhVdOoj`vVu+E<vn~3*6td7fk%|@}>!$oBU3Pfuot*iTZWJME&wwYcDw33t-;0g_f60(ABTQ_40k9NP~sL zI3jO79Cj6G?5i%sZf4Cs_S4LxvCOmTY7gI)btZ}h4J?^Vhy_IrDvAZkffJ}Gm#Npt zmnH=BY-8H)+)HklmMy%pVi}ui_Dvz6(c#(M{kwJot>FXQbzgIgyX*e-v{MT>>OkKb z^YJI=+yqvEL~da@Ip-dSUDaDlm-anUSzU0rbz&mj_u1mV2q9oJ(}8Qq7?_hKaH8() z@bLcQW^9?ZMZ0=#m6yIF2I1lTIttQjnKr`Tl;7DSK43d}_-zIC#Vs1gaf?=^iXZUX zG>%)eDOI^4RxM1YD}Y~`wS`r`me4ybMF;8~w`lOQw?u5CJKL%o54*^YQw^lbR-V_8 z(Hx_b>yOssT8RU`ars zv!T$r5hip!6ciY1-4MePEtP(iwQOl~TooCjq3*SJH$Nsfyk-m*79&GWB%@f^IUyGA zH_kNYw&7ys@B@>pw~WO??SzSE8c~#5)1!Tt6pwOi>(2uhQ!<%thgmwGIEpSePaEcM zfQwBaK&!Be-xeVPzkzJ(K{h$b70M=Z157oBXOb(#)N)*G2Nzif`bViZW@;uJF4mWD z5tf-vq=8KrMFa^kU3kC2JjNaZs2w{CcS z8BDm!JIWc3b})I}1e{^GoHJ}2%NdIBW^snY>0nALCx0tE)ZPcz*%sf~RytceO?d}p z8qxhFZ^mGEfNF3|qpgf-Xp%cTpxQm4nmt=60jix+%`_>dVG2or_Xa3=ky?}+0aSyO zsRjY+$MV`3pc);Zno5B3Orr#-dUe5FVt+fJ!YN=>2znXs5LG6`JL<(d8sHuE$$|H) zfhg~2G~Uq!?_e>YD!V1Z$YBj5Pe14+v~rAr?1O>CqdAGp1Mra=c*=G*(yBOvsS->b z3~4*T7NWXR7GqJqVC7^7G$4?HFHs#k1wq%U5*$b2T#4-M$qlDgj0NLJMEx8O#%OL7 zjN8M}K5sD2s$dX_4Co|7x(Y-3g97IxIHOD8WcaMXo0hujEZI7LGKy7~QIs809JNH+-4Si`T9YpEQx&ms9m#N$AFP~q=DyKQmyRE@KWamF>y z_-Nln#p=-E4%bvL*S2Zf`J3{7bK{zxTiV9x;*H1)XT>28wa=}2+BTDiS{Oj$z#;>k z%ou2K4_qsMidW{;LQ7c-rp);kOpq2|rv-%bxk;?$C=R#lOH;0HRT`qccM^5UNz|j?vgg4?p4@3DZl`+_2393yMK1S%h)XW_RI!n6lo&xE6O)(Bacm&$ zp0R=a+?e(QhgQ+n>5_iZjDxC^gK>S>i}2u})m@1s#fK>>stKRj+ivRlT$M%?ieROz^5ggs~q{+#S#H%n?0jk8MQjF{waff|p1@WrkzLGuh7?$ZNSe|5|QQpPxS~R=h~3KD|8848@8KaCUb{Zf>FF zvH!x6x%+eG$n1hc;KO9&We}(?@L#UQHO)3<3(w1wS+e*h@?`eHfP2PyGHqqtc`|V{>Q07NX$EXFO6bYN6NyU? z(BjFQl--Tp&q!)e+Otm%KRC7e?&@24*UjYJfj1Mkf%j&zZ~*fy_h#Mzbf~RLx@2uW z@s3zAxq&Yzhx?#W&dklo!zRqck~4Et;8P2onQshyeU0OMebQd!*F}^3u5)HC37wf5 zG5m1wK8|QeE%qSgOz!JJzLEoNlJoW9WW|d+ymM;xW7YczclA^6&fF$<=G;G=JM+AV zJM(U&mtoJ_@xhrp^OcOXj8FD5x}M}E%NQLaz~qrTv)j2dL9reLvjgppQR#-Kqi>=c zGQ&|wMm>ZNO|5>m`cXME-w<(TzLf8RiGMh7W)=cx<`2d?GkvcXPt>c>nYjdKX1AP~ z?9-eMoWic&J1RN#uduEBl5%I}q;hBG)QOy#AL6^RDT3dISa7p*W{$^#&8ab1kQ{iW zc&<~RiM82Qg@%YbE%wuFP2kf!GP`y0aA9h4!w^hpas>sdvfKL~-U-nUk4|P>aU5wa znO(=RWb&7?-_7I&o$F-amN_`5Mjf1ZH%FbL>Dl4ElW^bakd>V0C*t5-S-jXeILlW) zf>|tcaK2X?n7mWTjxDy?9^}BObZ}n<3`%5UY+@o`MTv<-eH8%{!NCc+>x9b#5*@}w z8l119(a&f{0%gA5&1w_q*pqFRe@XgV=wC9og*iOsGL*y9IW2l|S~PoyC+)z`@GpJT zX`Lm9XM;H{8Uc!PcrG5^4W#a%mU?%|LE!QfwDxPgf>Ti?x2G%DOrU-iGMv-G{GL;@ zkI8|7movLh57p-Vg5qzXopzJ$EF4#D}+s;qXi(jZYU2G?>gr8>ZIdWO`8Rzlr z4Tw1Kcm|wmi39@H-X@ySI#{nL`V;baV(sCcE63F;+jkZ?KBr{2j6qd2-YBY88LG-h z(fJagY68w%Yp4o~AA_n9FDNe1@u*tv1--ENGx`1ym(yWQp~##NVVBCDk0NYK8NwdQ zBVR(;(9emD7DAw1;piu@vVS4Oxmjqa~KI zzr<1$uGjo(vxTo5x1+}?`qj#squjV{F+8Iuxx9k_FX|cnlsu!|I;Z44qwQs$QBAx8 zw##+h!H<5O?-P|&dRDpblU%dY=|>58ol2!c@{9(+D|tpgBFCixZ=7coP^BHWX{N%S zEGsLtNLK~GgnHwgq_>%q)HxV&A^4?=Rve6|(1{q|Ho$KZ$$`TU?j!|_xI#U-@kLq- zUdeSgmHec)%U3AAvrntR%{oz;Tz6Y`%hM0Cv{F1g^YC#%hRYnt8Ubq7N4%wP6y!K1>bX#dc}v-lB&fcQymCw1Nl936a@E7mTe>9lmIfT?Ht#Kk zj-h2;tvoPVav^d-BJ=5HIf0}+AfqySn>kK{*`R>QN~WXS>vn6|lqCF=I3L>sZdM-D ztnhhF1G6%*_if^Ni8@d}#K4QE(CHEnX4;}U!U$RyL^WPV7(#U~(-e~fvC`<5B(jJw zQxmF8O~6@buGB#Av|_80>n-_HbA)PH--_Q2g)s=`UN2=Tz_9-+TASa!Uycs72t{Mnup%NN>S<_k4W>^ZQlRTa;X zx79xI-qw~fZ|ivHX-3}G=m%|OAB^|seg!PTxCd9Bhqv|h&fD7UjWw@2ggpPg(c&4- z+j@u+Go82f5G77TWrWbXJ)4`Lm6!SD+cJ%8dbwFuJWo7;W6eRKz}sp_gtztP(A(Iw}ubLx}!j zmX4H30mmmUS?>5m*tYQr@2T>_I{B`xXbQd4rMlx(jW7}0PD_$}t`Epn#Iy6c2K>VW zMO8pB@;#=0H~w*OG4Z?ekJLE+Q8(5J>&@Hk&PNo2G3rk1MWr$GcB?sI#~Qac%QeXo zRXotz%^vbR1o0t{v|FtScnBN@9uirv33&)Z{X_APM7VX~!s4sOx@7&R>on?!tD)Q_ z`y597He7pU;||p7xb4rY@|%U8(^_FJ(1QPP)=E`m;IEbY5l5e+&Lv~qw4R>irZsvh zbJHFzmqEx1bWpp@O&ijpyW4kgC`yZW@GUwfZrY!SphGEbwQ!Vj4Ec3#WgXDTslF}p z(~cBB=@@~jX6zU&zD53Kud3<&c=>4~?`7Mk^f9rbIMB`b+I;uKbwnb((MhVg;Xnp$qXpwQ^#&*x+u72 zHZ4>h%8oXaIJ2p2GOSFu@>B*vx|Q!0S*l&M+%A7_*3Fj9{wH))q1n$93x^sjA2;$t zFe$#6i>26t%A~MPtza#&1DGgw`ZN+=h>EiNm?%}1Tz4|^{z|+siaDc*6pM7uPj|EN z_gJdxx|1Vg+{#AWD0f?%f=O9y4#EpXf=LleimzMHNV6Yj3_Q7lwjs3Gr8)YxpXExJ%wuE2U7Vl zZ5z?F(SyN{yo{}{vL-({Tc5*8OkX=DFpk&yQ)AFxfb<@*p8 z1)F5|I&DxEB0>YoP@JH3<%Jg%x5FB2axOAtWOCk+ns9RdAA9ctA9q#f{b!niP9QQ7 z0|X2-&{0zoAjK#NjTkV!Y`2tv1>+huuGt#cfQ6kvWP1yfK*wQP+@i9p>$|{C;zO=lZ>$?>Xmt zmI5rJvFSuO_PM5T>-l@ zh^>o%ER<{yMKu-0hKIX;ZejIoVCQgsnPL6LGwCC?e}YTK{y=Nj*Cu?q7E`Vgdi032sYIS*4eJ5nYL)xVShuX{(3vgUodkKQd5 zSNo@4>`-`3;!;GnoiM8-Ox3BI?KH6q9~IQDn?}CsB=QWNr)uX0yUV>?F5Hjhn#I@L zpfXJpFDvzqg+h6~uWeag!WN zV2ob)!Nhly7fEI@37jCkS$yJNm+;U(L}|B@M#ynRl2I0YrcH_R`Iaq@6&H>SIFsSC z*2p}~?&rzzez#^Wp3pOC9;GqKIUrcg2XQYlKC_L+VL1NQ(3yN(%txF?O;py+vxet< zp4ABY2D!jw$25d{Kc+Si`CdEW71Oo7wSqUckMlj7DY0U9RqtzpV z4xT!s-f09SWDsSL%4Q`pH|r9b&ge)VU2v#5*~@gYAbK84;F$g?xfpSdS{h3dD{c|9`PGRzhXO6nP!-a1zKk{MiDEC+@VlGe z;$?!sX7%&ag#8=|dxqE$*XG|kMS9vI-tCPn270nA0fmqUuxdy(tOKC*0J(B( z_!~pM9*8=cQN}zyU=(oZh+_Hn{73;0%`H|(_#X<$nc`TCr&~z&a)`!qh$kxIkqdX} z4{^Tb5szYqjjatDsm>>!$>cH+2;ovjq8}G;-6Nq!Z5x+kpfTJ#Lye3eA(V46xCDfD zLqgK{QU}c$GLjk)(TI3tDfr~k>5<1gBQqzE6}qB1C!|kZ&iVmsC`N9|* zDIW`0GS4FaV7fqGOpU-o{70fMx4Vm_m?ntIYy$^Wp;16hYv02mY`R7GeGX#Nxmg|Z zzOO)AG{&;&zKvtp*>tse3IOhQeC;7bCY%uB8)+GSO-wDY8#C#DP7oOv873biHI@~{ zFFh;dxp+Qfp=5?-HLT3BtPam(WmTxI8XqrSFhIlrc$wkj#f$Buz>mTUpA?D9aFfgX z8AC4Rg zuPQfbl%;(PZ91%Y;g{lX0e`WI7Avb0CwSNqmc%ZaVhTILlBB7yBx%~5v7Qtx%XUru zvH(vRn;3GfCl?;8X{7a3<5^Dt32YHAC`s91iPi;WJ$c;mLdJTkb=Ffu)_U@zD|yHL z6(%Ovdh(4cB7cf1n)O)%!)~M&WYEyW_$3GxB>aYu@q0Y4V_HTX03J9>79M;ynw_rC zC_b2Fw44^4WmM}~My>ykF|>WZ6=Y;MW0#Q8VGiLDoH5E#u=I=al8r{qDDIFZ@wvkV zT3P}P$V`@Es&a}>#_MkRjCX0D{tORAa~z8DxTNN<#3j{#GMD)AXa6uRsbP*mKxz{# zip7v&lzqa@{kUS?X$h&Wxa;y-*n>#U0cVYXv!a5)2o4qPrXNA%H z7#ONi|9CZez3cE{LV{z#j>xXj)R}x(+o1NH8pC3D433Q#w2bnG^ zb<%sxN-t!gG?Q5YoeomzGHnm}W8G!x8CoD5%|rRlwyb#jC^o_9iC_9g05C=$#^6N9 zZ19{|F9XA;hST%4r0_=%<7?VXl{*Y6(GfnwF$HJ7e^6~OW;?j>ZZ;)Sl`)5#5I`oD&_SoEUgsX~y#nG>FMMU;+Kjw6Rzqo@U#&D{C0BSk3mtXqGg$Gul5|}|IaZsoIdc}m~!(lxdP$o zd-uDJMr73`8*Y*=HyJwU1bWtR{sTm^FBo$~@);N(F{ZbH^soN}S)b|dos#wGxT`8> zlBCt;%OMp=a75}77y4*K>UW*~9Fh7421mYmsh~_2XerO=RO&j3`%$U2Ld>YtJDF6w zb?XZPsm%H1P zPC+!f<4T?Jec_e+K;b;aYtp7wifM~Tojg~WTa^@LdjnN*|G;V<;ojH9v~H=Lv|m`- zJ0);khANjGO{-5;x7wO~W<{Hm7Q++b`-se>3y^yIRnBE0rwU|pf)q7dZs7 z(SweRW7fOGt2kFZ|nHVe178voQ_-o`wZNZINL+k)5vbAr=VE)>7Eh1Kz>CCgWZ>~<_cR7{? z3l+R^wNlY$o?7^}l?Au(P09pPEavL$iOOS66ntH7DcqugcQPa@*0oCNQyYD1Pixns z;h_ku86KKS3=dRU#E@*9Q&|eX(yjJ-FqL~lgZ5w>Ui$akN`bK}cw@C;aJ!;~3rcz0 zK`LI;zIRNZYFi37GIH77Qus${@6Ryo+?DYk_N*0?c`M__1hzW9##PE`TP!z;N6+9beabLtCahO2!&zE?sywK)%qy%61^Da)Ku_d>3HrboFefzfk_? z8vb}Vp@wR8C)FM4b^fw>2B8e`U}V4C?ErCPkJ9zZT$GALvEU^2lD08x_D85&=kzDfou^Ll4 zdaXrH^$C2%^ErDW&t~Mr3|yz3^6s&iK995+ytne+!TZ5))sRPy5oZ?7>1Ae#R58{! zPrRzkdGvHAjCW_x=~*w|W)@-W(v3_Fi~#qg%VEpzXtEmTkwyB-zmf4ht&@M_$dSwM zml1+a>f?^cVtVH8*tnH9Qz9+i3j8f3;Bojm4^t)Xx($Bi`XNRr6y33LTQ-MO236eb zRpAk`r($jWMYVf5lsQA(Uym&2i#SG4X_@o2UY;Q)y56hrH=E=D{<>O3Bm@Ao-@1uc ze<9`^K4~yq?a;!jTrs@n+^|t&Mi`$K^NF)GP2t|HvZCZrb1jBfOf3P(wQR;hq^o7T zZOxE|+{`l)m9$#HFD>qgymD7%+W0LuGpim(Zc&~htZtqntofN$8t3SrVQVkdti5zX zY{V9tIITqZX6Kj4&Az%gv-8Y|u^i_8k6MoQoE#nGX!N!?WiEZn#{YF++9SSe*;|uo zgD7>U6D=EI&bNBIt=`}`7ifOI?t1adH+l>Ewi%5TEw6ktacZ535WgNW1eBsN4-29a z4+}uU<%4ds*QQ8Nv(29 zMN{49aW}bnRLw{?w#l5@zmm)msT_{XY5TuK=F}o{z;LTYj;=-Q)M)*o2H%gCdDF}A zjOOhS_*SuRwRSt1Bf*ekxVn3S5C=vleB)J&tAD&M<{?QmhRpF~m2!A8M?7ko^X!d- zTrRgmnXzfRHJolqo2}Z-o0T>MdOfJ9siUwjYHFcNlP?&QP~T#{VDC!QSbu;JqK0dG zXa$p(YkU4t?TnB!6mUBS=0mP%sYC7@kXVtpk)D#yV-?HSc`aF;C!r%DBn>H@r#lfn zotLwzOnOuFC*3E})JD(RJ)IZzbY43`XpTpJ(Szdz-dA8(h+fv=im9O*KY#G@T zYM#!M_>s-vQr~J2mNrwZDE(O z(}O~fqe%&-JUytwP~SIsczRF*OaDmCsJ8rxmGs~U)#lOiL1^-ut~&L3gdSASMDluK zH7(W2oE*w@`s|>@*=(inR#4F)>A@TcY+ZMH&_Lygvsrh9sx9B@{A)}z%H#v57vo~NH} zj`rF!7Mts~5%7GcbB%z#WKB46ep9+brK7HNw@O2~Geo&8tD~CYb49tC%co?acnVQs z#Ms;uT*qxf{wHZIybG$#5^Hf{#_tS*(qY!fL(^-Y8|* z2*mFKb?F;$p8<}1+s4ymqg)5`nN4S02cxhl&0^TZuKBHY>DwHt0qOS(a9rhl2%1A3 zqvd%EK}-Iw!Mg-{@JS z(L4-_AG22JUydJZG8@DGy!@z5NPl^L^z74U#y-ukv>dfgmVI_PxcI@)#9jXDpxnTs(@kBU8O9eY;NzrO@~RvwZ)?QXO`l|36g z_8P&SwEQQrXRTd+(r%ZZXv&e{&|e9q<=i@?gVoN2Va1;}Gw5_JR*ScpBgddaUdoCy zh0AODbj)#gFq;{Q3fl}N_f9R@Jd{u!`j=zXr9bYS8^0s6m@lrp^4ZN;e&aH=ycT)W ztIdRYO@9Zbca7uJmvC;)xl@Y`ptv>Ktqe|mnUU%DvP6rTkkmI1Gp=RZcAK&!uG((ptWrn^wcO*{@ zr)O&sYBVnbE_C8uWG=iy-r^m+$<{d;tH|5hv6>_*RK4kYfybZNky^JBs9N8~2XWA` zl)8mF^~hDRmN&Oj;Ca5gOFfUUTRN(DOGjbb-?4+ta%|{M6P75`&+&A+X@ukXJZy=> z8LYoa;LzS@EEa1S_w9C+o}$KoPpi;&!FHp!3O!m^)%_K0$HUiESeeXzWqiDy?KlTL zJGQQ>{UdEh*_<8D?y-HGzU^4&=CV##X{SNHuKI}G>V&S!tTl{@?Bz~Zt%f)bNtO+N z%u>UzSig0MuJaH*T5ow~W#liUw<3qsTQV!j@fR^GsrOIPTbPxob*H!TEKAc{?JUHb zDROA^hQ}QqFaJ^K-NCI(4!t!VdNcd8%nRC(;bHg(;~&dDb6eL8I>;Px>FVW)r z0T$njf_mX*s*UVv1>UVyuw-tW+&1cK$<(Jfxoe9tk*-lp?-F<4S9K;zZY2h%#c$_X zgGSLVk_z)*QZuKP=Gfj49fFm6Q3r0_ZD8DyM)Z~z`>M6Lhwh6sbl_-$LJATj%poAIpjR6in!v=dlEvwY!p?{nRF3#J zTz60$?!B8X#?#c(99}WIBw75k1R=!~M6LEHg5j>CWHlx2cCVqe2DlRqf&gwihN1!P zI9{w%=Lf^O*^A1U0?YBnn8P~Ep@D~&{@jH^`qtxG%@p^Vg z1~f$-=y`uXMsfOEVq1~K^%w$15=#|GLnQIDgN=+CuZOYlvVUh-bd(qf{eWvu-sqr6 zsImoz2#9o>5B>(sqB6!Bn$2u=O_tk4@LKw0gsPy_qp~8zxld}WFNTM5#i!OQ)!Wq@suQNeB+|JG;@0cALK?V;4 zT{%lfX1(kiw7eL z&~m?vOp&mwO-fm3DTC09y$GV~p`RWveCR{{PlvKMDD5gr#e-iVObr*^h1jq1V<*mA zVfQ&1b_YIfRUkKSO>Q}4k(?*~uW&af)lBNAtA3DMyURg549j^FL}L|NW(%pG;xW7JuN=U%#4S4L96FSV4oXzXC=mq^QDlf% zAVvkl_M_Mmw-E{)fSYtW+_y1k zIm>5Di^PmNA2|k}c~a!l63nGvln8|F+BDN5B+zhj!LYGbam1oMB~A~oykH#O%3v<_ z@Dr*WNj~dx$mhX4@_8BZ8CO^u@%B6YAU#i;El@pgPd}%jd;ZVS&jy(%IrIatq%YP7 zBcfG<(9o$2MA2kR5dIOksWKLf=-_QdeQ|_)EvO+bAGMK8vh*(IXXS~bJg7E=SKce& z_B<`VH8q7}Sg;jwi9Jbc;622pH#;bia>?+GAm#+DYs9>oc;&s_+=O zPQb~qY80vIGjfJi`RcAf%T?x?(7>Y^`NJ~kGKf%o-RG&aaok0T zbhem%kk9S7i?ck;+^4@ z>l7qbjEk)h3a@;p3;wDEAX(b~oI;|6KwVk?j!@CPpt-o{k@zH^rLS;os~Fi59J+2# zqFb?R)Bm7v8r(FDf_X*YqnXWq|GM`8iJnowK*LLq0R=SRX#+M070xS#9J}R;ng_>A z8+b-RvU!+p1`Qxusu{*N7G8N3gGy%yb-WF#-wmpiK@-4k3ju9&$K>6Cl{2I>X(ra` zNAc|Xe)_-UcNFGlEu;mvRKHEywzG8QUwjIgDDt2scPau_w1vn6$Fm*$S;mz*ZlScqe~(ZT@+|4d4cK?Gxdbky6^ zm+I7_O&BVGvB{Z7P0k$bYN_SYT3!LWi>f9s*r{0<5)OzQcU#mTvg8jc&;k7=ulD9J zx{Inha~GA(VZ7QcntA#hy!0N4lonvMeG2 zAYB70y?X4%sZkl8#2U%cq9+ZI4tdSJCPjOA26y^(u~{BnWTzgTNEx=1pDQJhH5$?% z)66%odPvFZ$_NuVy-K#2<;`KCgXFa!i`Es{;@&Jsz#3(X&w3Ch?IxOO-#q$4-_jlG z)7$X#GTi}lkj^Bh2e4D56q)wT3174xE-+)?7qv;s%3Qi#fdR}h9rPSpp}#D5pR0p2 zZ^PZouG6%4Y?LG~wm@kB)SLkqZShCu2+z&A9?=#J$L9}QsMEg?pI_}fY*?r8n8#x?h}w_gDM#8rI_|>a z7kA~E2qOr3mTqul21#Jj+8fjFcje5JfHw@`7E1q*P~#aA@{s+Tyk6PgmgiHlow8|Z z3sL0FCDfmdGT3r{>%RNk-zN)RIo}_kKJ$RhfOqkGaU92dUh->oFX1*2z06N8bYVv? zg$11ltQ>y^$gEIi8O!9`AAjB5eBS|?w}pHA97nYCt2Nx);%UDsd~A$F(`ijZaxSBm zHD@>9mvX);rSnxAC}X~bulb&^^ALpQ_BhG8J=zh^t+}2s#d&SZOpWCU`=mBxn4T(i zp0L`CC+y(HGn<>3d$#7!b90R64ovGd`sPTL6~a;0&T@7RvVF9%zR=sh1{)xovX-HT(rP#7S1PjFAQov zFFD$r^$Y6cB0ZSFAU(m`=zsc0R`roMTK%upP(PB(^8@zo5aDV-glUbTkulf20q^&y{y96(CxA z*Y9F$XE^Ve7tb@#J0>Cvui#z?-xCJ8Vd&@Fu3MJA2}Uv+`JwL`3+kMm`JXBnszE>F zfAYo`x4S_ZTw8eA`xzV@1qa`oM-;*KqfZKzKho9 z4KT2i@c-upUXHyOi0vQldd+UVw?1~tcO;K)uQ|(b-`%9vMIfWPD7S)c&Q8Q&UCq5 zvT>|_k(f&wvjMh88kis+*lsp(=`7H(in(OYGFe)>v_0c2>tvFFHh)8cuo!1q=U2vQ zRvu(N)Q^A&Lc8S&n+8>o$nd2p%6yku&(k1eILA}QM6f8;!v*7{C-4AFcb>Ey_u0Ru zm#HmaRRydvGJ={7$$9Rxy>JG1a?#()q|LhPY}Q@s1tFGWtP$3haofN{PdBg56&xAS zz*l$Wj)zeb9F0nal5{v`F;&Qmp1rl@S+^$S2@cJ;+mH(YSPn;d&~QfE#rMulC0o{A ze4n{mr$jSuU0%)cwXb*{G>9g=Vp@&|4PY;1ZpYd$qhmE^k8Y{F;Wv{J-E{4A^w9E!tPke*nR(M4=_$^ob{I5uO~$2G>0GjP=8}DTnqp}Rb>o!LXqw`jngrQl z(9|u?tJdPYvbyW1O&Vu}S8W#4H82KnssU1sdDU?L*29>+vwg1`WOROs^QuKMUNyWZ z@~Yva5d+IFBbF@}GxIJVO@%)5UNR=w^L=Pi92@Ypg*?An$oI9GYt1Ok^P=g7CiPcm zeH+t%E7CeBljpkDew=Zwc}(7r$K-OF*~w|1jp8;jte75R&LSkk#(cGTE?=|OL6cJH z(AZ2qEMpFW_HTjnm>gVt-nEFD&GoK1W^yiSd2MQ)cg-B1;&pKJgN%30kOU|^?^^ZS zdDl3Pmg`-^_)7#F&#w`?h#Ow?kXIpR+nTt7kLU7wgYmj}+_~ABGQ6%)X54Ho88@4! zN*Hl?=|@2abRjFlX0t7Fvt_M}VWffcw3(IRJZ;4pPuowKMIYL~10eEUZ5p5_)ba)> zs`UnVymPu0Yrk;uaMw4|vou*h488dG&grJXnA5FAPPgn}PSIe@@0Qhzdq7o4$~uJ$ z0(~f_n{;3?9=Hc2jz7^;c}$#snqVasdN9&VSC~JC@gsnIUh~`p8gKrtTEAK z4modRpLbJ|9Q^{_jT+fkCARU&<&12)d~&t{pBp;NDaZ2vpIwhKsNz^>#^L+#>CN)A z*h~g_B^O=8B)RBHy?A6MuOu)euOz3^f17d9p>ed#;Vv-p3z!w-6|n0O(s8-yW_o6i zTByrwVRV6D#zn{EN-nzEjEl}DS6=PzGWu`eM}f7RBKKW%tr_zNFI~TL;(60(&zi)f zs>1HcD!#g z4#F8dWyrF`LaCS9ANho1gVT?!Ig=H0V>Ghdc?d>&7Q7*hoR_4(C=)MZEFpIeHbfT@2s3$+IV>S3^Ke}B zkicIBf^#TK*64|HhW+)W#kb_?8B0^y<_m9lFGB}zczF)rmRyIgH@pWAGQ4wX=dWdK zKj=JSo-Xn%g)y-)>ml}l8PyF#9U<@ozKCkAqU%Vzn~zxGx?>Kv-M*iVy)+>&+Q!!H*F?c zPdDC);ezN1$uIr){Jq18#W1Hz0>c(lJ_BhV)*Wj7BHU@gR6RI}uNRyBJ1I#Qmc4{$ zCr~TqDQ9DdXFJbea#|!FjHGV(%p1d#AI4Ah_ozzS*rN76Opb`#rxRxytfccu!U;u( zc#*d!%2OId!QVB(u~v9mD?F{0xH<6Zh95(^)x|f**3PoXJjqzuzr8gar~PqPy)hhL zGxTgS_Uym9l-*Za%EJyUt*N=})aV%~J5h*n(s~KoH^VF4dEq6q1R-~shXg~etNRs6oTnh*1!rPoYvg^bkze=2 zvKWRw#otNo|^78@(il-cxhyq zV}fknjI9ezp%>9B5=Zip6TLwmglq}%n87||9<#vOES@D`qd3W-TWSs^nOx9{YOA5P z)T&Rvq5Fk?NGt08teJwlA=!F@10~Iw2e1h$aB{;Kq z&H}ki&{&4zh1l1ftXuA5+uc74pZkJDyI|n4eSv6wc*Qj(80YGKN&k*|UK?^vHJV$V(BroOY-=-}K(&oCiTUkV|i{&Zpg)6`0T5z7_* zslxspM-=rd;e@~%b$bifh-dpbx`T~$@Vxl`o@cCw`>CrGSOGZpRlLsCqUzY|bgUZR zguu8W&27TEEzjCuW+ODadA4oCuuIL4(vA)87Ik--lfix+R$q?S_0-mvVfs>CXSY?W z6T@`EyQ!KW;`ERU<-koz;4wo{V6z1S@;3u+a***a6W`yzt5EmezwS9f*8r)a{yfn5 zH1%GntY=_bsYg?yC&j-JJ!#0Z5Til8H1h&6v9~Jr7R2;b5z|``)0-luw*t{~GsFbl z_dioOx96E1;B7wCw5zaaKPwlaq^eTaK5rGEED?-kE{66>gGbBgFAS6?*yZ-j1lmC|f%_GY zc^5>pf&ht?86b}q*PIY|(I`rzH}_P3YMhZ<9vn67+)i7hg~W8@gBQKqCmoToK(QsB z?s;wp6X5>0jz}z`YYk_`b+55*xLwf@;oUA}6!8h#D^epV?P!nEincIRQJH^-?Fgdx zdbGRng3`q82F5OWR2Sc$?a}cFMKOYPXa5n2D`|AjaRo;1wJ3^>g%T(9EWm;bXbc>% zt*bu8J>&q@bHFe+eQ*EEg+;Hp3#7u+AM1Z{oH4`Pcqof!NG|qL38Je4(WS^h_?hq< ziq>j9*;R+gf@+wsn#j^dQxjP~@fHAMJVGQCEJ+^C6#g24CAwC6zI96B%=L++455cUuluJ^rl_O+ZxS*F=cM{92qE!*}}S3`Kk1L2h~sn_8ZFWLsFIFt>-5tZx#4n`c(H?Xr!x0&PJ zbVzsGq&&RaZq)q7P`b(BGGUzF&BJ^BV9zrexN9}=d+tV#1YQwv#|K_DH@Ca}B|Kg3 zYag$+OYs38k&iN_%1kqcz+weYqVPr5KZ{@<6dd0D*HRhB;YwDeIr4$B^F{LmMb0`YJyHO1D6^wN;b)pRbLB`U?7IJZqNIy&O zHptTqF}U?bpt#c1*y_cy)o}XoI%vj7m7?U(cJ7)Ea^0tw3>to7AbTh(H_>sFU>K%tonv!1cR+e zTu{nJTaP;h=Co z-lfvQUU2$Ew}3Q&F~MuaK3Xa9PB7M5jvAMvbR6=>8}bU?^vmTX5wqFy*5%{{GIM}5 zdCSP_Ex%=>Uq{^KB9+<8MM}+#f>B)@8r|rp+e7FLRb_*CSbzO!8Qct0wBA`^?Px}K zx0?l2RhLu&hX&&3i9?)ayJxe%=OYJ=246u@P!$w)PEb%3R0TyrRZz58j64-)WCl+` zb(V{Ik-ay^2V3bfr`VZx<(Zd}?7cZY)Rfi5g(>%zDH#e-tIXb;lN-ZC=EBHY&FC*h z;3KF7!<30S1)>G`tn)!EjLCskklD;7j}tF5$Pyw3FcD-6=X@!AEP3m$S#Rx0QtH}Uf>uR#dnt4!k(rtd~{Y>h62A<;x z2L4TjW59#+bsmyi-{gr5112{|zODCJk=aWBo=d+7nWj&7@bfM;9bZ4`A>dZD`1Nto zlNT#y^Ab{hfj9Bv5_t1%&SV0w9XV)b^M2_?3B7kC0%>0{Ai^uIC^5muu~q8K$ho;k zFJxeI)RC+-EMgwe6uF3`Q%u&KCSFarxD<2a^F=*Fz1HTNuFdE_SbO`Apgk9JLYE8U zb5ewgWc6LvvW=XUQ`E~H!*-+a$cEwZIbOq(%y!GL!8!d-+3N?b+X%Ngu>d$5?atvT z@@(NL@@(ZPI&9-9I&Amml$RBsilArrtzE3toG4x*Msid*2S_0U8}%!3S&7R?6iZYV z!SDQxMy0crQJ#UOi3^S5+PCf^TU#PXwqTuFxG2100}apcMP6g(vZiJyPv>OiX_F@o zz33)7#h-gw(+ux(PSCK&;tN1Z`XzY;bAWWS3(2-I(4V*hhav65T^_6m9~EB3tm_Yg z*TUjcp$)-n7aALQeUFJ{y|gE=+Lc&X;y$<9@Ew#I>d}ZmX+AW@7LO+Vn&>VAC8TE- zN+qMAnsYvceuEJ(GeRCtvI zv|<fkBz>hvZ?2r*A*x0=Xnaaz_Ujd_}-8FfikgMlA-&Qq66 zMDw!FaaoPbI<3|YUw1R%bi;RkjZR_;gCxPP#lf$2>vQ7l*+^G7g#J6h*$#iF<{Cpn zsDVxT)Zrvur^!rcWo~>t1eVYe)h|4W!ki~VV6TrPFx^lK*vJTgFJxXXe z5BQ#oefr(x=!J`A{dov7#{_^LEev=6+@mim^NiYQ_bL_S>7f8$Yc#C6@e_c>B5h>3 z*@MN*thD=?Nju{*pI-dYFDRI}u=wqVuWTij?NH-K+97Q$n*%}Vxf#L5=m7&P2Rs}Y z@IT09*_bzK@185Jd&`3n-0gsY87x>ZWa*>uG<>8Z;bA6l`k&LpoN2)m*L ziDY40coeq9L&B@5KzQxQ+G8%D-yDy0LbAi|$dWX4`Ov5hxp*a+Rts2nIe5KsG@BAdaI zhd&oO0v+BnuquGHK+tbUD~hn`hQa4!5J5bd`4EC3GastZyDR~0i5k6YKE(Uq>eO#D zx<7Gs5SuDQ(#(eO;TttOPMja9-xpr?eZ5{>S^r{q**ADCJioereK@&XuNT+UKN3!= zTFDod((vMjZ!D#{#q%RG*}d2Ay;cwszK*NaUfln z`$HKd_LkaUrwOVwH*6ZC*mP^7bFAkBRdhvdUTU5y>nbJpR7w;_Rd*xqL$^TeordG}ZlaE8(+b z10~WEV9Cr{R%hx*9~IHHy*XZ&GB3_z18psm;t~}T2fb58S8magG!qNGKTw*BdtBur z`-5zKs=Cz#ZDz-IJWZ_U_y$giY|tqYJ9+i_V6rLlY2k_Ir-UY+pA?RGw(<MmiUml>wI=AboDYNTI+d*fM zUz@P(Y95}pw<>?Mwp(|Gz5zdlSaBOm-LSbxK}}koS^B0tY%0-gQoF?KY@x94;f39a zkIAQ0P2DQsfeV>lIeD1B6>&C)54{@=s%Xaz*dYp2UJ*ds)z$MkS{HwSd3a@b#Z@KA z&M#{%*sj)%*PcaSl?~Fol$$}hS$XBUHpJF;+Uz$m9hpElmFX5rB_GF27s2dwNq9$0 z7dj*c9!^|Tnk>DT&Gj%aL~=8q>)joh;aMB9yxgaqH|)IV+Zp{50>Jj!ot=$4GIkGK zU`NH>6+D2WQP$E2_)+Ku^Emn4LuKW;iZq$VBXNx4}a)6WBep76Pn4x9~5YE7h~dUa2hSzo~W|}YrMu^^GD5K ztj5&>X7K#@i*#BE86tUnI6Z})(+2^W(X>n>Br!3`V95M0l&(1`(Gz-p5zO1I<5k&iM&2x@}^0f5*L=~ z+{oX7{QAw|n?EW5bvRBdJ{C)*DgMBud%aT_e;TjlQR|_2jS%8Mz{NC~UWzCg!EcjS zlU5-u(n;a-YnSZ-**1r0^S6wv^8 z$4=T}drfWFz{f-kkAn1Vnj7ZHNSbre>4N?VJZF~7Y4OzUBAbaVL z)crpZvR6>Y9}ZdD6f2T?#M?tV8n)j9UOplYBl}kP!&zXRssEfMW%$wG=PE-H`;eGD(PU05S^VB$J7;ANf!zSF_sPU~g929yE?pn)~$z}wJ);Z@!hDjVm< zvmv-RLOGU3a`0s_+_$fmZUPLR76e4e;01t2CWYkfXQ8^->Tw>hs$5~sYEK3q;Z@G0 z4s1-8hUQ8ej;r6?wP(zBGb2}P&{kIa3ky#7=P1Cb5E@(41DmQGPiSl352R{dX0)D* zYxUgtQK-O|1Kb6o1EX%Vo(dE!gVX4+s?X2{YUjgrJpO7{^GWT$t=^mTdo-271eJHA zr}A{U$QU&ND~}f*%(VNP;q5pcdGsZ9beqKMVZtK)D}?+ZA$zts<}hpfGeeebjo6Y)chbc%AX}L*X(Y?RlSXd` z*>6iJ0?6=+e8^Ug4Ox$WV(S9wyD%~iR~)Cmo>6aaFT2wlGs?HKPWHsNhkFb0Z^ZNOQhiM22)$W_8#%sDSYAI# zrwHavdNos|{;_cK2BG)@1x%<+2DN2Z$N3+#<3(c~=Xa-aX_xcS?WRd|Or{zpm71}z zm+H5D(b-B1d1?D&-ErK`NTts4{}8Ixd_2m;{Cye*nKf0)#+<9C^Vw+lzmDuBP&{+zd_U%Kgd=Lrd{O zX#zPQ7%RFotj!SW^W&`u0SBDH)6(HM%4#eVKr`YT4!PFyPMo?3=XTVwQ|P z^YaBrCT~FT_124Ja(%{k<5Ttql>Asg;a^z6;TU6k_@c+qwDXY1UX+;x*>zl8K}V$u ztz)6UX5Mv~Wu{}))(LnhOyHcZHg>)XTR^Kb{YXZRd!xqhi6!%(10$k)JavN(0HaQ+ znh~9v^%(u@?%NoR*i=Z_y~<^8YlPz)y21yt1)~{Up8LCaED9J*j~;WSNS|Se@qpeelEV5Wjb*GkN!|h5LqaYREe! zCx^C3%gHf=r#8Ab@$@&k|LfUobl=JT=np`zWnfpVIfo3|hGG@)v2|xjJ?VTUEtHhcUHih>lo7|Wo;W$PCtY1?$nHs35XsD*^iPO>wCchT za`vQmg>5H!J3Gn4y#>k>Wf1w>&(-Nv6T_FM-s8xh3M1sJPH%_!%td=AFUf>3C#a?G z)z4VF2igT6!hQEml6(Sv74-ECd^-iY@ILB<)(-?=ZTAX|zxLw9^^wV|*|XoY=)A-g zl~Y*aT=ap$>#MN?nif5WN?YLMe<4e35ULy zT`c$(^$p#&-rJuRJ%?Xk^sCe+zg3h0@=d7H*=Ys+bRcL*?SkBT2LAH}Krlbjj7Pb5 zoG5mBaB+7Cm-kbG$Mgs|j}%U?b`I$B#K+{Cu1-v^K^oaHFSb_FD=q3B~NlkYHvRiIpY{A4W7Va4)SlnHFWHd0a9@`eH z^@5-t1D64HZR{;hG&dz!s1@5c7KK+FnYfm%(XRw-C$l!jjU!W;wk&>XV1QSRIGFes z)s659B^sOJ<0h{jpu9bGbf`3S0R-GL@Y-`A3zeI=(a#-61y)Q#TH@?2;~869`iV~s zP^2sswj%v10uEIzc_Z&tp0ZE4EH6PjJp4u$*DoK6T zsy~kEIgR~e1M`t|WPEIG2?Q`8>BR4@-HDsjMRo6=L3Lco@rFq1NChV1o4vEuhDSV#gJUe-o@$BF!U0%&o7xh+#S6&JEJfewubzpyCvn0E=E>|WL zEH{Ms3rYfS`9QX@yBOZ^P_IDH^U{-yD^&yy_YBdC!ZZld?*j45wrW1HQ-I4Gzo(=- zY*BE%TR&9Iynb6x{!!~Ud06vNShhDJ%hGS;!29U|2lJw5#`pEC72Hj1$>07skY#Yk z2p$@3kk;f3N(aEyb`H}G?%2*^ANAkmDlF&Kh#VootH(E`yl!wAO2C;qaEJRDLo+bn~Z`nt7EjTKBe`v4s3@au1a-jYk;AyV2rzyNP|X~o`c`P9325TNAHs|UJmo5-&L4GwJE^n?($Y1`A= z)(qai?EyY4)`$C^yxCC3*EWzjk8l0Ik@{}IwC=9biOD=Z@oU~l=I-D8mI?>>|3QAW z{HFg&jPk-ej7BEC1x@Dk=*b5usjh5$VcXiljb5t&rGs?!gt8qfKlsp-%n5$8cJMwv zwgI^oec?IpH~plv3~qelrSMn#2iHEng~T@fSpCGWpGt4v7QSgC^=#j^{fWVigPVGR z@Bw}gZlu(VSHsmZoE;}TkyZu$RzXW{1vh?26+Aw;u@{fpk6mTCg~d@GlBr6R>yZ$@ zMA8uYH+hKD_^`yi8sG9-fck)B=A^zZo?kC6zGMPiOp+AKG~V+#(DfrabNjZzO&Y@B zMmLD`2gQBcwr}fQ9lansHVf6XeK03M0d}?zTA?<_vIdx(94|qE+qYScmUKDUf5rd9 z>EGef@GY%ABy`WfIlnPY)Qo6vK^wKAjSjF$M}1GsY!We>3F?&6uQoGSlu`$yEr+hk zO}3Un)tE-4X$i)qn^etyh>+#75IHJ&xwcp~@{~(uwO2&nPpa&Cw>pgGn0}TthclP@ zhP!U1gj8UKstEED8T#)jUBz7+MwXj^;2c`#bj`0WjX!5fydm|CS`D*#JHzZMPD~A@ zCNRAC)NLBt1{gFkl_Q$L*y@r=Yw6Efv1fM2FMTp`VJOif38Gxj@KnN=H2TDaC4Czg z{(XGvvrkNYHh!u7eKj%l$;lgS4m3CpCj8_M`r5T4UNd+>a9e-o=e!`v*MyjN&fXiJ z7t)Vn??*9?xY(K!dNJj~lCGbN1`?Ns5;sVK(1pJHXgR#{hVlHAIO6q4_gc*B5$=^4 z>JrBkYdpMhNhtm52)qWwJ{gREwcIbb}Mgt9zYNN3|?_aXf zpkfc&|BoNdbjs_$AwGR3wP$hvEhgVR19P4L_m`G{`}n2XQWfl&icfvAZ*}6*ZQZOhWStr-35Qe>**%XS?jSY{qDVB!}hw?Yq8lEfT(5GJJchu z);lyLuhu(e_Pko}IMu7p_&tFqL}EW&o$ZW6(P#2dG+ZMT{bVo~MGJ_s^ZyYPy?FAN zC_08Me*f2?=)%ElXR;_V@mZ7lG}5?d;GNq{OEjV#b`aIV*|q;-w0l#Qc5lkk?oCbVOp{NjTIMiyn>k$nZgO*IFgNY@ry;f| zOkOj*jw6p|ZZ(>f*$9SS^|jo8U=;QYT>KdQ#}T0UChhDg7#HqaK?|t4%Ea}W-?Sty z3??{n%(j$Em?B>yPXMRPNWUV)QE515t|sjqEnO2{@nB#>U>}npXq~Y7I%1}&lLE(T zmcTEb6&M09$G7k+^5*ZpvuDk?=(xE&0@1*?*<;YAmJ2T^!M|YlGN@Eb{mkM;{hDL# znOZQ8`EE7yU1rZ&kAuKhR>kzc2_CZP^lB`Qo`Gu~wc$nFJSFJOs(*xd$j+6&VmW7c zIq`T-pp$;qt&!ZU|7clH&SpJ@tZ16%lX)T#n#*&F-sR8TX8euNH1BXht~ z0|3{KusWV{mdalzXQ?nPXQ>b$)Kr?W-H$$!##JB@(50f~JXN%er;5sLs-p5~sAw_a za(RTDb1*aeI+mK^)0#L|<@NnCukV*x-{Ukk{GK=1plHXhXunBG_fN!Xn^s1Be##Z6 zkTRP)g`yR)g-wAIp|U1i8Ohk^y-D@Y%)OyBQ7GC3f3nrJ8ITU$=g6=;RfciWGqCB` z29_GRo=+gJij?R{m`qBh%E4{?UP0h!?`w!;|LcL%9dzCzEnbsczT`x_ikAkH%a1!< zFCk__lk`%YT)x6yN|MX_?4>lh{J$#osVuqtb}Ln$T>g~3R3w-0u$M@3dF&+RsZ1_^ z_I-M(PA)(Hy?UXoZ$$J`n_T{_)AUl8T>i^b_0o`B{=ss+G$xn7|J{0NN-qDn)iZ-n z_A)EEe4M>BCl|CPf79---8W;>S!s!LC`ky35TJNycV)(EWsbGCPN! z^(WO;AZX}OF~&ddWckROY9B``x$ZHJ_z_TI|KnEUhzeo#c`(5&_#L3NLi~{a9LBNd z=}{Dr>^~}~OkHUeOrQeLQ%*k%pwe=q36UNZ9w+0TwAk9pPYgH=&q{sJilrW=U9mT7 zXqp?s1oJ6gDN|QWBvO4caSOxSULHxkgdihV1^-uWKZPp0x0l2TjxZtc;+FknFU7{@ zqC2LYfqga+wS>X`ZTAh{w{7G0BcHtQH=CFMt{HrKaP79=@^_;qK&uD)pV&ge3rct@ zeQkB`>cKTnY#MxMaK~?+8@!*?2cLTR+DcOUpV;`+OW_*}@ff;aL#2bO^YdM|ZSdYf z0;0%c+rYW^-*N7JQvqZ-m;RkM*vbEI^0$GxM;X%%Y`CAv{}|gJSa@xwg`1Xv=D`74 zJ$?IjP2mRb8@xaB^~PKH>QC(+-?)8{?;D4Xl&xg%^9irBibz23QRDA$Z6h4is7m89RZm8Gl048si9oJ{FagA046UVfLZeg045qK`?&$; zKwx2;KlJOa0VqS)o@5h3hmN6Z7r9>oLUiB{^!FO zp19VFu`DO;?SCPRx(;%B7qsJ+lsPOPoen~g zBz`QJq-an?-QYu+BrkGR#V4-q$hqt78gPqtsh`n5Zp4BM}g z*t$Z)RaZ&hoN1Bh|`5vA}@_a8(?c~?)0QREaf*DuB&q|V2%_N?xhO=^3O%=~K`OPt@=C^pB z!SevmGx6vSbAIS7u+S-890yq1G(Z6y1mvjFSEW!D`AIpQ6v{QJ5o#i35-HS(U{^{t zDV3zqR+IWqCrOEr@;;?7GP+O7L{bGGKVaBIP}#&_k+GN+l_(lOB4V(%{r78=Q4YgHxw$aMmdePMtc#b*h@z z>hLOJuB@_{@m@?AwK0jk1#6(80KMlwXRq0@j>;-^!9L=PEVo!K$_?3De&{T;gvc&Y z!GZpds_6`g5`CIPO)#9Ap{A0tYnsMpg{XiGa7I;ireD?RhgB6P_M^fem>X5qS$C+;3Dfz(gsWLbkik-1(;q%t5Aa#3^0 zrbYj}a=vF>f^0AB$wi&59ppHvJ(Psvrg|<2MQ;WmPE<~{VPER4Q=cStgM(SxS&7Ohf z8^GZmk#CZMOMQcTm|utOIHl+@=8J)F-|5&F48Mc#1(VrWjQnd3E#6-d?)#jq?3el0 zsc(a^+ps^M+-Uyuw%^E$-ZJ(ISEKCJ63P`vjJKbtKRpI+>CjP$-2HL&MMrr(wHmhD7+Yst>^FVP%KoX$pK0zFOM$YOlrO8f7U zU_v1`jwldVFAFj^Pn^zCkopG}J)w2E#vqr!ySu9CNI9=^*6}J^6cx)`sdJO_rd6CD zyU%js%dJmp$jlX7_I5IW=UxKwDxF~m>1;t9m)b|(`NpvIS{s5v9D0HIIhn5*&yZPN z6Si#bvUq>Uc@+mYQh`Wum?tYk!@^A|4?RnG=;`2!DItL3pUeUiq%`t>O=<>B6E7CL z_N``~(8&Ka;XcJ2v=)k~NMWM04wC9frfl(Xn$PO4icTTLFP`4dmczk~@tmCFH0bro zrpO}tplSL-o|>jFa9s57x^ zd)!Au`mwwW574y*>8oYEjzP}!(4+uQKyRQf^`0f(vshDWdb*a!!AW{kSR|e0oyj}X zr_u*m$JDJVghppdGAn&I+4(&dQc{cFMOB_BOV9)Ssi9-WM$pHRXo zA3^5=PJQTTYV;^oJrRBe=|TEy6jv7~52rs(Z*Q@Q@t5(E^$bj3i!4DVs~pb=&mhl= zIP=;IOLDzH|6ut~aQW?f@*UoHf5vR}e#r9e8nAlv>iZ>FifKQE<&&4LXJEa{XZ5{B zU+ueGN0HQPV7O=Cf4K~ebWD>4`G6G2R6ZbeSUwn-I;kSqOT!YIb@G%qtb?cV+0{n?n|*IZ&$;f@*uFQT?^UCHdsy`X$M;HYQlmem zi|soQCD(A*Bb5DsOPRiBu6it4^N}$kc@k=mL#ZtFhd!Ftqa*pU3vWucKlBISRFY z@dAeJC8T_q_Cxvc^?ntCZQr*+Q(C)AlRs5yPG8+_&-)$yJ_0rM4BXUjbY}E(2EEVH z*Z@o zwur<1Absc=NKgcd2^~cnJWc8{J_E?rK8y_@-?#jqaQXA{)kAk#TIjKS?^jn2g1Zxa znfI~F$RZy@DJLb<<5`eNrRur<8qroK z9{!P7a2*_a23xr6Q{ic;z|LgT85yI9@tTzVAhf3U^+7W&g-qwCb&|6>HN~pm`Ae(5 zk?NaN{Y6&2wA@s^tzn<#jDI?nie^mOEomo7kPNPhvzEy`G_?cq$uP>3!LwuDMpRdS zdFlyp+cS`K^=G>Od8o^}AMv|Sjb_dH9bZ7ZdS4f;WTX4YUiVq8=crbWivk$y^@*Zc zdg{Cu({>}!m^jWlXE=F&BU9Z84Yk*u#y449W;imu<7sAH(+AMMwBc>6J%4e-hp&5_ zgj689)f|7?d^_RVk0dXySTZLv;peg}l@z|VW^sSXe+LP1cI|tW`#S{T8lRlmxcK!6 z*G^2%tXTYdm<_yZN{9-7-BUjoEyrJ()Xy3Csk_tHLIYNLHtDlUGP5<4tC=JpRkvV- z^bB0^i+of?hW7(Xq3R^PIaF2Tqbjf$q#CB4Kd^r!ri|!ot?uXVrf!cZ%YA`ri-{Gf zV=c#8T|<;7@aa>j7rmH6iP-bq`+D^C!N07PKP3G51&vtq)M(g5J; z`mKOmp@lxHvm|v7la8K&d8$+Ap;n&I`*5@}E=Nvr>W(+|TMPV3HD@%X-&+so-ycC}SDam>mlU|6UpZ(o@~5vUp8C51612H z@Z&M^&4wegqUur0w`7cb(-@`Y^9FH;<@}V(SplRYK&CRbuVDVET_0O_WKD8*U|pbK z@p)%NKOhu*OhyzywLPutjy!|Ud$)1!g?J?Ix(hwL>gA^PxJ%4R|P&zAp;L*#!~UjDze{BPZL@G%bf zmV(cBWmaU9ig5IVK{l~X_rvl1MX!eYo`$EzYwHSXa68-^SU1)EUR-xZxL3!X^idH1 zA-YKKzLar=&agsze-nGGVDD2T%|f`A{41PVnv4m5{FDzzj*n84yO1z=m-I?)J@E)x z0667>Q&yyK9d4dnC;e35cM{*dK1guHT0Qe|(JroL`v zXHynGO03}q{7$Q*=L+S;Z=%T(jY`?L@TA1@I^HI)u0OkL{7^-G?G4d)Yuna_uGd^C zm3Z;@g0D_9B(ENh|I~`^iJnluZs8NMyJ~NUM{ju3m3nn(!}?g8$eDlfQug2P+dsTM zrZ)6%=m(y-ev4TQ^6ySScy|jQ6PAT8UYxoE82Eiw>#c~+#Byu&;f0YzLUkqT>G3&T zMMG7IhMxL!I2dPJ&0h5@(|A?3hMs4{Wo&(Mc1*|fW$0O7Qe9=;sh~S1sg5-9aa9n9 zapI;@3=7ru-;V?f61JbSEES@Uh>$YYxp15E@w)&OQ-Ux|ZQ@*iXB@H0)7iRrQTcek&f7D8qWW27E0^W;9P=J<{3h8AYNMqX-= zM<;cvYOt*n?Zd*f5(ZSH-a@_FPC?zO{^^#1kN0rA}e z@szuTym0SQs&_bv*WQIS61T5e@ICfBTJ}5zgIk02teXJH28vmtFZX)23>`*Lw1;QL zPK@tPwcw0M*pLJ~(HFuw^$gF+dxqAeHbOLUzn6V~2P}sECHb)mPOS%4gPNe%GtY>A z0|H@ZBcPAYi!WFI<3>$3Z}(yFJV3MqM1H~}BxJkrIj;+!wl1VEqRH&Xncl|>^kg{a zx=g2&$*=7uRj&hZe!{gv8e=-f`<6$8VznKY9>71P zzTw&-&9Rdup+#r?%Ges@g*=J_FbLk;!L6lO|5wbgOcLntNbL9}@pU#!*nhC{YI0f`1raiR<%=$;B@u9eqA^wx7Or6=>{J06$b5B<1Y_As7*p{LgzHeCD0}M ze+6_9E4$Gl;aR%5-hS~?koajgOec~{#EQuwID7@2oN-sg^j)b1aKn20W#uxtX8@`7 z_RFQ0TRYKrCqJM!t<#jAqVlU>0iwZEpq!yZtkh*XCHVopk(|wUTP9yB5nHUztyX1v zOEibP%Kj;niOOVxsfBxBh4uDJJ&Bptb8wi64bU_2<_~>1Ox{DK=7N=GOdTg)vVtUk z+7|==q4~NEjMQcaG7%LQGpRv50?F8u&Rz{BW2xC}CZiKy*rmD*sH>xA-=zXK#pbI3 zLR768=?%rQ-5yWtmYDuW(3!dbjJuz${Pc8g9Q(XyV2TF7q%m9e4Pk1A1 zef@dS)8e}m^VqoG^}B4=9XpZGS&b@n*c{4!?&q^DYn4uB+jZB`>l=-O=5k6v!f4!X zhUHIQgoouqNPOMUFP)++Pd$&_)H86~9oEGfM{mj7Lx$+CQzU!OgRZi4m z9AR2mf+V0Ssa-8{q<$bhV3)uBgrA%Vpw97GDBS+4x@be29*W;>M3V9ezVozrxI3&XT3wG-l%Qn zDO>r<^R%t)kz!S(9@ox|M;CBMa)2Y}4dK4`06)ONEmF<_^GVOWSdyB8g6|pF`F+5} zQC0e08(Ub$UfI2y-7P%wn{rxI2TFb|+VNet%s^?Tl1NVr3bZm7UddK$*bbpG!1kfkzag+^Byn<3QPXwwCqQyTWt~Og-i04ljXKptk^f1o} zv`_45-X^+~>yM8&$So3@c)iAb5|N~2t{)2{Z#14XcD6F2&DmH;~u^dWJx*g6AVtg-jU`m5=8&mQjUd-!!RFN zKr)2P^Hi8GbhKJ)h=3`7+{XC&!*q^7EJ4pS?9Ek^4 zIP_PC``$oB0t5(n6bWmeigO^A_YE&xWWXwOz?yggbc*mvQT568_tdRB=(^*m5ky_I z&%Qvr$QTVLd$W-H0+r5$O8JsATtjlur_-(&og6`fx=ZN~u&?REB;w39HMoX8t$%i~F3Nabf(>IWI2qc1){#^_!cR)0MJm~W1!^fYK zMA@2kKU}6_VNcW{>^bVdV##2yGTe8HW8O-Ky~iv$h?_D^xW?D{P+TcaPI z{BVArt|u|ao@B8sGl*yj!WiNxNj1n4;U(v^eB5t`ZO^v3m~-74M|cH+Z33HEGM~rD z?o_wuusLq!B^NP$>L8Piq=>`Yzu+6IaqrR*!aFUweqkuw`_3aMa)$vDn8fZa;Z*fn zvU1@-hjs^rdtX8Vu!W0DfeXXE4M#Kh{lReWP9!)HO2U2h!aJtOosn;nJ<(kP6g6i` zbd*xnw|SF$x#&eMJy@jbZh04#b8Um(3yvk9*T=sprjr{~@DA&%*U7@GLR53V(r>kN z7Etc6flhja4;nF%2s9JcKkvFEA6Ul+ytsoauL_X)4sw7X50(ldRmdo)SHEos&b#Ca&{icK+==8NM0X6UMH)MrH3jQc$6B{!AY+Y19SE!hVf&5DdQt#*dr}GMJmsT9G9!j9U4_tLAipjv3|9 z`n*K-GF~fq1?4kzqeC=Ny9{cs<)Vm9p<~_8h*OFQ5?2TeN^GI+GM^(KTtqF zde#1&hClbMee26e7^vH9vE>CyASjl4EMc2YE}}hk|bKSa9nI1 zAy}f5lC}3_MbR^eBH|8Me(uT5$=LN6;%HmClTUsGvg_Xo>~1<2h9??V-z9uu)Fw{s z%GOp!B$?vzZ{g)yNpDHG?RQn));sqLDd(7g&_)G;&HN1@o!Vpfj)Sv$cNGIyXrCBK zzpxr7t(S>@$~$11Et6i4@5d>APC)w{S|%N+e=t1zK?u5oy$^-IFwiBV$7%l12JLl> z+z7!D$9DWP?e#90Lb&gGNvr?O-nqb6RbBZ%H@U)%+~HjHwx%^okn1#&%rw}{Owd?^ zMTq|_wzk%SwXG$HfYb*_(RQpB zP#}jCP+NIb!vFi*`<(m8140Oh^ZEOc-1FFHKh|rnwf5R;-)$tETbP8kfXB^9qfpRO zPfJQ!;kSy;`4ImFo5d`|n=e3y=@RjGF&|s;XSlhO_*)8q^2OigcV)%jw@U!uFDy)N z)9kZ?;z%=5n*QrXn(38erI~R0zFK^<|r65mEKnhDQ-Nle!l;6LSQP;L}@yLv^_VXDOdr4%KmSRN7hK z7|93=o8!28DR5|`CxMe~PXdSbj#uDFIP@fNtgsMyBXIuCpwtQ+;V4Jo2+N_@L87J; z@FV;qa6}OxT?!FXn4yoBCy*eBnH4I*SsG$DyzGj7!XapK+se{kG zPwESM3f_oaBcAonJYb02t&h3$*c8Qdeo#|52c2^huA;%pJPMj#D+;3P zB)(Mr;lF&cU#>}fDUY4yvkNnmc$nEpa1hxqE;L4P{=G-=Nz)6-DqQ%#sUvyF;4 zCNJRbn0=)w8K1fTN^CJ~CKYH}l*!L!<%Iz8KUF)O95xZ<-&QCKTyr&u5iL~g3}$P( zFvXUQ1w3dxI<~Bu$SZ3$LKydenTzR-8a-pUZ8S5pQz?_ST2iGAcG8}{aVzDg7G+W) zP0`DFm>%Y07PiNfL3}xAVhQNbAaq!xKxOCmxtiY@fB>JG^ixgVq@fn6J1zgLY1Oe79nnOTIP42w^#5Oh8ja+i0r(*l!yAy8)u6z<(dVO2fZN_ z%jKfG^*%d=T4tq95oyjWno$(ZbIa#s38$b~H&^RbOZv|r7QsXTt6+&Rod^s$`Yv(9 zOT`wMQHolfCByX0HdC=%+ZkPP$-#=(#W4f8PDlSfT{{BtwN!0cyQAVYLcb<#P?49# z9M*EerkqOY(9;xFXVcL$L+jY-XyFYJO?8&tP-u?V{$NOFdT+u9z_YW9Nr!tKM~XvX z6GEINU%bv)HdufBX~&+rZVvXX2;h$%e#%^Zo9>wxa@fmg&3_D(p)K(0)7FAFvhi|gVu4Za@Le*KG!IP`D zr0~KmehmS)n2yZC2YGPuI~7M7Sq#{B#ui^!NWk{gB6dfjmxxD5>N(YE(c(oQ&+NIW zC>GF~BrxP8Ity8g7H4Zo5+1FntD5fsYOkXX)IY|7aca>eQS7qT0>E%KJkcQn?LSecf z4ngX*uSYL8di3&Z3zLtBr9*!m9yB`i#DJ4mr#6U}U*|=|Yto?8wSP};Y|E#i)ez~F zihU!WaV9*&)H0QR8Gfz(u`f$PQhcVOPU|?J(0X5gZoHjtt)e$ZcP8_0^Ccwt$?DG2 zrCsL^F=Hfj9wMbQQjV-S@<5k$V=z3+hi0(xeQ8EiWK4|&5(*A|DA zD^HdK^Q%bL7i?KGf?nx4f~t!p0XU}fe0n-MF7#BMNg;zF7c4R<5+styf<^h%wDhwK zHu8#jrJL*7>Xd%yuW7EpdxI+;8Kav58k0S@Uzcwe}-(w|eNE&dx@4X~eT^ zuK5GGaQvISCU-S9gi7f@m}W4|T5F~G?~DiQJ96T{*qC+7UqR>pOiA~Ks39_urM}J@ zYsOG(xXJ{aBNraxC5v#oI~!Y}O`-LdSVOCEC6(@1rR0{umIowf&{75@?f=DaH+qM3 z9Nyv0IAI#%O&#Vp+H^?2PRlDfJ^Nr(7TF?os(ENkb35im`l2#41*jjOW&L zbwQ|2us5;L6E@JZsXYHA6QE!S9|!O<+dgNd-oSx#6}oKo)n=1x@%q&3Onb$zH*FoV zfAhc#^rvFI!-mqCt!<};)|&^)0U0Y^KO+^sniyD;22XAL8S;LX#0;{+A{$e8N=9>e zcPE;h1_JJC&mv%#w;!TFOL@wh&*LULJ;>MJa2_{H>AJZ&gBwYA-8e1R2wfRO>;w@3 z=@^f|TZ)q3NznB>N)~J`x?l)@@=?Nr{z1thuUnLC=W*}E=+Wkh?!@Q+1)oNIPJuQh zgss2DS+Zs&1ExP6AFBldFE?|YzFbK$Z&)^tEySS2;gZ?x&Kew~&Z(JQhn!=EzOu7ig(qk5 z`SKv|uUIPq(kvjxi*X{e!A%y^+=_LaU^qeNUruMU_@{hplb%_4FGfwN{z~e#rY#z% zJNiontZ5%Es95WZ3KmnEwhIL2l`d=|1EfeK^y&qAU_UKhn+ku$o^>?6@qT}{rvuY+ ztRZBW^l}d4vv$y7An(sVBql?i&jNR&c4TlD0Lgn!1%VI3{ZX%s0!qiyjniOcv4PE> z1tR13*G$KxB~hdQ{BAkM_SZuLa4VNA%%z)Zb9clVG%DkZ{B-4gzJW^!gs-AbdL)cs zuA|!jRKfV8m;rL2&kDjBmheDE1-3}wMqk2QQOm8&iD~@GC|{(nZ3INM-q-M4rulTn zXck@G>_ zu(`$>$9He*HLfhxo3XG>A!Dyt2RofLZY*OWJYeQS`OVc$->A_K2i-$^rXucqzxlMl zjIe7KGP)P>nRS~_H*Rwa+En~e1!`DKId?1&J=h!QM>tC__+^1V&+=p`PcR$|_N>v( z3}*1%a=;o6Mu)gImb~96Ly+CNS0Fp966X+%x6T?4`d0ezw;*y*;(TXzSLN3sm%HU=YJz85Mmtm1m~o_6LlL{$ZZ=q@veJ!e<;>*~#{wwR9sD zJ*#vxMNH9{;TeI^S$0JM6Dj8Vt#VZ9QThty^O!23Zc;sCpk`*jc_W^X+FW>CYO}TN z7A*6-AZ|=o&Y}NmolGcN<1^Ugl5M_dEo1}f+L}#=^19R2T+Z#vPrGzw^NYD%`I70%lRWE5_5D6Bfh-TcjJBEA zX&qE%RaxKqT|N*FM3uc+3w~45b94g7G!gO-pipUX^vKAM>d%r2r*cWE&8VF2q&ve( zp@i~D3R*zSf_`Xa(>AMo-WfK!W<^^@FKx5Rr}+YFH@w7}uu~S?kK&?S@6*z?OF|$F zzBKFy3*oJY{qi6(9ppUy`wT>a!FVQ*H(x7M=ZCz9t*5m=WTxA090b!cs=hxsGB(*! zLS|ON%Zt9=_bBEl_b&kytfpzcN5C#*@$~_@z@G~-kr5iQ@HRC1&`J%e;lJwmJdehaF!RQ9&g(A(msUgGfg}68``sB z&(O6qE*KgK?dRmr$5LpV2N#2 zKoJP+N$+2Hyl*wuenA7-E^Rk1^gDrrA@x+|db;sA<0ILyYev(Sp^+KGP7iq;&>3{f zBwD)Z7L848%oOwnp}ffsw}A;NDu`p3$`RrF}2$+psgW{{nN-7Fs`SWYdnJ z5e+mINV$2O{A?Xz+w{#?13o$#N=4|lk$~ppQv^?0> zp@SIz-dTQO%4^zUz^VRVO8M6_MTpp8GdFlcch6|rPNT0e5bps#G|QhCa*@YNUEP}E zoj%jniy7Mbil`x#cSl<4L_k{hx)W)IdrMlAAgzY3r1h7V{$fc>7XfL7hceJ^NsC$_uOpKd z#3F&yiL})FfVAv&7t#uMBCX+&mYM6j5ms4mW>>!iPE221;gNXU_eV_0_eZP~S;3$S zZ}lm&qD{UB>7*?0$yYXAljh;>9GK($6}_A}KnoDF-1z6*%G;Z^b#!zf!in|NbnVF%C#gad}$ia$n0_epR7Vf^?%oL#GLIOZ8EXiWz;F zYNz5C@y$iE6UCj>LZPNNn|7GtKG(F}47X#$?qQR7YRCI}ifawnx?vmFF;E5UO-om5 zTFcieZ_rqCbq-By`!;398VtZ}l$40neEdA`0VLB}jtNUD)SxgGN`-Zn6^vnrY~Jyia*SP0KGviaX1zQg0ycEl*BM9m-tK zRB!M%&)l$UXom!5IJDcI2F?0dW2Rb98u#*X%!G3AFmYC;@T8kAYs#eSp@SQCYJLh2 zeJ!+Z2Kk3M+gh03sSvbjOj6TAH~ll4AK??J4opdN-NpjCCEgj$GKQ{GfjuU8My=zEXo9~UyT{^?AdE97oA59gsj^_q|Q?}{=Y zSq`?&L%Y-t&}`6due+c<(nqu_eQ*Zt2cGQ)?XU3g*wL zOB3low0G*LMSJAvXtx`~rnX)4q+;WX8V44bnQ8&?y=EJH4Wu@Yq47GS z%GD;1O82%fDp-{*q@1Zr&P;jbOs$0mh)K9oA@K=!Oto7VO|B^AeS>?80!c4TM=!$% zksu6yFX|!uQZwS&_TqH-;n2a6J8xy9giB^Vmeqwe0q$Lz$6VLClj3hPja>?MCuw8z!)WPE{!YZ zMA3#1kf?}&M|y(dZbur#&zH8nx!T0flNf)h*@loJn0Dq`8%+B;TxEi3+qX2`65Fh$ z#^w>kz&G6zwKoZK6Sp^KD>`1?Gqz|gEmf&=MU$1*ThOZ_l5KpVu3`h7t3!B{(H(7! zAO@tp5fc<}SFGVQw!`c$(0<=4tN>d{b;a`|_i+SM%ie{xEJX8tS?cN5-I$GMlEk%+ zrI=PqR z>9n-@Bmhv!+n$33mBOMdxmgoO8S*s&shGGhY!4ohOnICp&Ppp@%QSKR7bQ}f zP$-1iGuR~Vt6~>IB~Pk&Tb*4GEl1IqDr1;Y4X3;Yk*<_Y*^owfhp%QWjTO+9tbl4^ z?Nxult!cYlCat?qBOkZF6x!XoTY2Nd0ys@prcDS?8Ne|8(jc8X|5ma_r@WqtM6P32 zsb$rBGBEHS!~5n_^gBSaE^)B^Kt8z)#;c+6cX3;nuKhyFORZKwMEKs&&Pzy7cm z`FSD;$NwmtdWP*f6l7cAPYarM$k0x$^9}BUVj+%tiG&W?w{~4ee=81%s}h9rgbvu! zOF^iSHEwG-RkqT(W1@wYWP%T_YQVKm;JTYt zHz=_Vi6hP*l&&lewT0G=eErr^XPMxztUko#3JY%&96m|$k*_a!CABiSwJ@}vm>CPK z`oi?+#bLgC?zUYV^WFFm_0@U5S9Ey>>h*LehtEdqR%!T!hzzGy8pf||AB);zxooxJ zvew}eE*{mYiqU?M3q*Prx8QL(uq~@tovwaR$c*3T^9{%p&n&+|x&*rB_rxnp>6YP@ zt)Q$Dgq91TbO&sTLD(~D7^K!Rh&J;z8}&M15R>ULk3p1!Qw%aX%OKS-$W2sA=ROOA z2s4%G$|e|OQkr}Vt!*W6X@VUbN@@-@Xge$37n7(wVb=G|J7K$NtNvEJo}rW1Oil?Yp{3-7CQ&lVy6C$x_LPNR{i+bHT zLZt6v@&5hb78>=oizgw7%rrR;Pef*#ywu$I(_|Gl5P?5UR+T%n>V z@i}n=5J+G`&dg zt9s2=W$AfRbIQexUl6;_k}Q>kor8p3@T0KVLPfu}#EP+C&7{MgWfSJHu4#vvb@njp zJm0$Wv=EZl6krswV(bUIBM2#4cJo7YSMyG}m7<#0)cb^U$E+?=v=YwQF3y>t$a;9Z zeM5i33&pK_csb24Dp1Ua>w@3*E8s+T0-WfwTfnJ6KLAcie7tvP90qV!37*w9zEw(a z$Mq<|_a_z!(N`4Qi+cJ)F3HBc{ZI-!&V~TvT%C7ss1avl_1>{plsU~hGmeZ%IwB=S z?umc-3E3I0FllJ^h?K|HBXM(6nYR`Hlw1y++{Sm>H#1ScHdFc(WmdxCP4kprdjK>0 zUQ=X%_?yS+^cB@j(?%cu23K_?d}=sT4SxQ9G}J4q*VIK-QF7HRyIuBLsRp%Hs@9m( zDbMCXU6Os4%^#y<=`Vin!{j3&5d{D0Iq1fD;MQ1*gmMVrw(mZe8N`K@g$1%BpvmW# z(d4yO>t_m2u2x2f@eTcg0_*~VWY>d3A3v?LXOr-V`iXMRt|J--z1yq$~c3mmt2yEJ^0{8 zj#D4KuT=-IvG&4?y{$-#Z<`A?n0<>2Q`5fIX_!M!%jfm&EhS*87R1JijgEvwd?x-qRY-$6b# z1C zd=gqaa^BtE(G?c$H?VX zt%D*p8Jm>R3hmCDfz#Du=SsS&)b^DPxA2J!I$7vd?PFFCC|Ep3JY>et-k|k%Vs8+T z-T)E;Qg!D_1=Y}V@M2Yk>ip}Vx2g5c1RiKi-<`1rJkCgsk#JGv?cbyylT7xwDK-n3 zb{|bNh`BQgD`vkmzMN!7&(U}P%e^5S zi+_SuhO>%9lagDrQqcP79~d726g?|w0Y4lYz? zt_4RtStzj)NU`5Cr&)H%skh0|&CK`w9m zBBxGy(<*~881Gb70AC{#tHu30K^C2y81O&8<0S|9t@fJrGPYk7J z7Sb_IwVDyDXfH}n>_N+wnrkK4Mu*y3MEVJqrRW-?s1I#2-MMl#HNCESCQ3gRpI1>= zl)*)~W=1tRNm68=;B!ipt2$MMTK-1GJ}N0EGbNQaN%9vwY5+H3pBc@!7>)RMZXu41 z_N8oeY2M8PmS-Ntq7p1=#w24>v%^6!6$gTq_7waou_(M7cZs)I#-*e!!XW3Hn&Gyf z@mA;qZ#e~Lqz24LU00YM6ZSXhitGOLZ=noX z@?29F*B9O%K9_Ika&&}XvYZ%Ig3I**>0;il)2W#2HXIB%u+V#6wPq*R(4vCOB(5 z;GAo!z3IAzZV`QT&K)lxZ?gJ*oxW$Ltkg^?jUh|Lf_NU&uo%*GP{+ACw?9WWy5XC5 z^+e;lZwmL6hl%Pd)wj1UN?58-<BZuZ;T(NlXr6aFkJ*|jzU zv`wja#*@Dm3h{}<`;CML*ib(>Bxx|1=VxU8(@*jga;&H4q-F9ZQU&wIZJIX}fuvcN z>lRKHK5Ta-InBS*bGtLC`BARZKPj7RCte#I*;pGOVif1+)D`^-tg*Xotn#AMfOP!zc&l}p4u>-uk07 zeUQUzhZhOW->?&aVI*meEynQHph*{P|rU#dFTie+bY`E)|p1nWVvOjMV+2 z-ip`QKe;ZswTR1vF6MGkmBNKsz-L@nvm}FD5Vaz7%Z*8iClTRVip!MKnA+cs%uTIK zM;Ez2H;s(HGbX#fRq%}dJHc&FxL?$Z(7H=5YTS6q4Piq6ZqcssGM5#jVf^B{Pp~@> zi`BoYYWzNT)&5;|C=_=YD`G)kIHAw+d|6%^|xZ#4dc>+2}%KH~TDQ}9^-2ajOMhR~Y6`LB?ep8~< zd^IrXsvdDGht2c3U+Mf@UW<~pdGyfM?=5ys1|lh|J5g*zme#8D)q_*$jRT!4*R>ufA_8k2TcZez*^14P&17ChV!j5p!<<_LDTdw31Pf^A zs}9@w=l+K(0Hg)E(ho6JwAO zcYfTxCgEORCA;)~g}`iotF)k!%Gvt*!fuxMuMi0EE^8hNzanh7e1$xx)Y@ zoG>ZpV|)ix{)QdU>Mt;Pr*d^|SmDuiwhN5E3acbKw%}O-C7ft{p~#+n9pyIyM1rnA zoZIy>uuvsaSkS+`)V)x4`Z&4mqV716YTO-X`oA*Mf0~aWjk#5XkusUL@ zL?!ej9lo4foq`gf=QH??t{$Bj$SjzOC-#OD0~-fv)Aiy>iGe^~X8S-eoO)iXz|iyI zboFIyu9?3WGkwyC5C)xo3l~o;TtA&JRum-xjoKL*PoShKNhK|Str8x_QADE*J0^_9 z@EzU+=O$N`WsFJj8BUylxKurIXvh>Ipz5*^+>;@t79c}72% zLEDQxpzV>aX!~>tC^MKlUA#vU-^&vQ-s6lD*}b7EJ!UWu;;Gj%q?Y1%D%>EUR&YyN zd#AaXAzB1AGy;(b@hb-H%!a^?V2!crHzqYY7>UMl4$ zwm3czBta(}Lf-f|hpWd3I2K_DM`BQpl|twqZv2{$6Tc6y37ssJ`>ufHi2hpmJS+!kV^w1lyZ?joD20|Bp8m9wj=6y zZ_sIxVZbN`PG?9V;#vldC$i)pPgJaA1kuoX;H@LvVc@lvfujt=>?e-%9c=Kv?&*MC z?%{^wu57%C{>LSK_!T_|g6%<0U&zq1l&D%|JcH(u$8Nb>FCH$@UevjXl;+&Inu23) zojf6N1uQ(R0hy>kF(^vAI69gg5;umUC6W|hA}3mu{1x1m>z1K>nlDP==t1}&Qt<=` z3ix@D;pg#*K}I^ghEqD$Fg&!E@^Ml@N=u>j0=G6X;t6wVT4zTpv@;0qF0ukX+W2i+ zEQfMRie}Ln)*<2U5};8kDk_D$6=<4VRhfZ^NUdBDA>T6!ZWVdFY~pa>lW=ER%+4eL z%f<>?st+*RjcAS<(HzTX@+cVfncPS2h$A3(U}si;z6aDkltnFhcI(?d^c`uPr%gcX zlu2sPoDiBdQZyv+>3%St9nXPJ}+Jkj+=j{|Ai9$0F-+wwEiKJZtzqOFLJ z$S=mXV}=HI;eixo8E(w^IZwg~?>_U@k%!b}M5(hq9Vpo8X(v3Wt2cR%aVs(4Vya7vh_SQQ5{pore4oHEi%DyErEfCYs+k>&B@B3GsCqRKF#M`^q0<@YsmfB z2glf8U*SKuS>z&$5l=GBf|$ioJ;?}+c!DCHLUsZ!!+9ZYviz@TiRwIVLIUJ7Th40@ z=sfek(y)ZG!KE7V^HQjcdiwte3*bn%hRh$Z<%e z4hFA*p=-s^wJ`K6tRe*7lMl7E;R9@X>_5i9$|BEkobJ15ek9cRH}2)tZVZMU@x-EA zA_H$8;?|8`^va#d{bx1|TR7rU`8yi^;nFkp!?|!Dc}!v3nIerZ;5qE zkM8n5VqZO+>M-X|wSO5OWE3GIL&LXwotCpGl+@Wp_M{MVM#cVngzx)C8;BO-_P(Gx(!(ihx^hS%jn(^+15{fyLw08+b*ma)3ikM|aWA3fG;y#@=i=YiyZ z@^3NFywN;L-&EjDwlD7s;Q4vr8IgA-S(ykWi7N@9nK^d_XqL3y_aB1h)DIYFR{GF9 z!yP51AcmLS(r4Uy49Or0*mcDJKE;1H@bOzp9|YwzEX1Ciefhyn7M&$m>amiJQ_lO~VxZu_c8WyGi~<6WOG%H}6TCO9Yz(;1JQ zArts3^+xh>L>X7Dha}7pWa0G9MFfb|cO-Ybnc`dzp7?+}mn;f6lGOJ#Ztc%f zZ21ct+14L-mJ6@yebw!sNE@e*#p85#ZCoY~^!JY;gif%`9rzGG|8 zX}JnGn)GXoq5U`d^N9ZZU;cOtM9BPp)c-Z{5!@@dGG|)JP`7lZfV%N(|2Y8Z&4Yb# z?iQ71AStUn?$hLo9GXNSL!ceNjTUbx%!2ZyKem+o83L^P+99V!g2TI*KajNK0zcaM z%FjRApX=?lwBcEy* z$SgiPcUC{)&n&smI;Q5GmTQ5rna@RY?a%4&UF|q-7#@@f*eyN?u07$rW%Q! z_hV^VK8dD{=!s;o60ul`WH}~g6@Zxgi?a&8?u#ClG*WaM8^c+4;m9`{N_(RS{BJL8 zlwDLMWk_DKLMnnreDTPA4d<|yQh8ZsEv3R&lPYr6q+=}$*qxRZ@#lZ?2a+)uXk&{z3S;1eqnuD2qg7fm`X3I+1?M?BvC zTm41;K5Fj1Vg7zje?7CMM;B)Ax-JwcgFJ}KcLO#u>uLu;R51TpY)owX<`QA!L;nCa zh)cmEW_<9dAyE=U@>45!yjhqq-msbFE!k= z{9>3HWR*bA!MB4qQ~tZ|d^<%{aV4%I+&o42&F1@;wDAOqK5>+p2upr@cAWjv-;}T` z>*EHnSsyk=xpAGbZy~=()^8>!mHgcKMWD!dXp?$ICkyG0-^riT7hM)M{sTU-a~XZa zIK?z$DADFmi(WIOrcpp!mn9S4{YEG+5c&xY;q{8wCakePh&3S!WX+>9T-+_>!~8k^Et z{$8_MF(x`x>oWPP6}a~yZM===y4RUAW4c?hz?;a5#hAGHzRQXQ%QD`7Xgb2_hCDIe zh~A2cVxP_t6JHCciPVW)orDf6!2eji;P9lEV5SG7 zU48sV;5_RCzt4ObEHUM*Ddns(Vc}@qDQA^Q@t%F`s?Lk+TUUQ;zC6~h{+GV$O89KF zH|t^DlfB+%T=<1 zt>Q4yYMBq>3b#?vP@S||6N52YYrHvO7!*)Q7S>pvayy9G&1}M6p#7KBWR=|N^ti!N zavz8gP92iF556q{F=a5e|4C)N{06oaA8+) zDN;I|*r6Vmg0;Cz(M4sHI!b(pIz%_$h-JF@1OGC+8XV1A4ffZ5vx~vO@^J2IFwsgV z7%U|oGr%6Ci-UZ+88XK_Xog+^_IeR+p~B?VW;_> z0$?d<>Xe|c5}(2z&35x9|FW=gl$iz4M)NGEcp@;IQut7o#}j_k!RSOuS9_o{p&7WL zS02&c6v0ZeU%s{UHq06>Lgdrda1q?zVGWm4hu+3ndqv&c>0kCE+^{$+71CF!07q?c z>%$E3T#o3?3cMw!iogr|0`L2D$mf$!_?N+y+OxpRNIDyF)y3gH)h z@dpiljLTDkcLUIU*%x%s;Dn6KJO0VltKExvV|V>ldLb$3!ObrYaO1<(yIEFHa5ECU zHFGloAzy5Wf0Mb{_h86vY?yj6qX4Y5Q&*Jm}OL# zP{w-(UyP#0(5z6kl|wV!LtK}lH*v(60zCwe;(#S~%#`Rhw2MZ(FBn_GxCM@?yX4Hl z)-y0vy**O!ncvBt<6CVP&-OlyN1U2+9UJD`Oqg9*Fj47Dn4NhN_SSc`d3*WgKZAMS z7CrW%?=~Oz1;r2P%brlIm~-E&&>ezvUR!8%LT}#tu(!aN%0~ze>@9wJhz%40m2@)4 zdr#}1F?Qier1if;2=oiNw*N=pX!RlFL!T*bwC#N#Smd-nA4juaG9UMa&_ALtdm{8s zF{Z%t_+{C-9E*Aa@N43EsW&5wFu*&7{90FtH-f`dPwI#dvTwP!1eK##Sm~YQyu*G0 z*KtVik!ApWk^4_`7H*%1KAHXE3_Zh3y+`r6`toEF^>((N_Y0OJw0bwsccGMJokUOX z!L>kNo=k9EVMJ^{OnH5k@vBa@)GIMkuaB7hrWKieV1!Tgm$2=!#{$K!mm1u;$lDLK z-S)5YC69Qg@g+yxy4>9bH=#!7Y#(SCvG7d0!=T3QFgQay4A?@jpPd79QH4M9n7?!2 z=8gW&0ssD+YyEo)Hk*QWOF0!vIi2kQSQ#G zg(X&Eojqsqc?&)%48}6weO>DR zmgtwds@#bHd|+q=+dbIdO5`Ij@OSj^!tCggdphuQN4o4z%=2>_4|X?Ym~OQaO-1r^ z8*g~GrLY&+d#w`M2u5j@{ zvLs;RRb$8lj+Q6!)8x(=qfaZqAR(e9s}|TtY+TCPDE3j=DGC`agP8N!0LIP%P({3e zExh0z3T)b9;Igi@tth#lPUA1F5|M27VR7S4Nu+ z(Ra?Y4wu@ZPN6IB;dydZ5V~@$IFy__(V-hs5<7&iR3&&FJzBnHp3>Qy2CpsX!AD~S#JR2t^x&hR5yEAzD9CQ6wU&`K;+nvNGVi=jK_AB? zdc4hYT=~&9R+cp@xvtM`6+W#&U=JLDz`Tq3#Nm+G?f`5fA&vsx&V@PwH!qrkAqS47 z6AapA8di=SaBQF*WPCtS+;R32C!K2O!ADo-eG3=MqAi;v8#v3PU`4@-Nb6$9+hjb2 zJz+%Q3P;6AexWWH$q9KV^G0?zR>d@AVqi2E=97Bf>#l^J7ZFirl3{ikyj-A54sm$7>>2CfntJaaZyLpW!aGUbAT5k!+mCS|VhwwIHz3M}l zTpsf;`TRweDbU-H6Lp-IlvUppwOi)vZ%9w0zs1PQv5OhV1m@CESrON0N(pTeB_T%B zHf0>rIWmD-K_1cOsjSyru$f22D_eIJCAXCpYbSGMMx~%4?JL&WC*5p$2ZL~yIcQ^b z?&V=pmrV?Fnvt_ixvl8X7~f#wJ!BUy9L`gc(`}1XJZ?40U7?*MC?{K8#ZHC*XI-N+ z+ZaNiuH}50&>=!fxkUf%*E@;)jfFO~9vVouVyz@Of1D)CCIK!r_#Wt>hKjYR*48bN ziqC~dd8l3*RgccX_R+rG?4Mx*Gk+k?Y zgx`B`H@CuDem9@D`k1=;bAyBv+0DPk?CMQ7(UhunVr?}$EAD&;(*j_g$liRVYnsH% zdM!`YvplulXiL*fpf9w^xoZWmQ#l&QkYQusqY1-0Cm3OoELSWT!Cd0mh$*M2W!r>Tb3RSA=!Wv>qx{18_pwF1sDUP0cte%vc=)}}x^3VkJq$^K{HA89dPO7yF!#)qw(eXv#8q~aVE{Y>$Hf8W`+i^nQ_u18fT<);CU`sEhCI1?KCd-FpgfrVmbn6 zPdzTqwqGAFXXn(?SI*9nEypbAv)}gtD&<+IbgHL!a4{TG z?)?s(ugj?KWo;x_ocO0nvQ?c!17sNbdq`6(PRJ|`+gy(I8FO{KJ5whXk$|||ompx3 zk($K`$L4xOU$l|;nAkF44=UiE1Q#a#MQxXOdu&+Z?Pnz%YN~X@0i=ppF%|{{ut?XXx&?)}tgj6~T}_ znw)xDf##cXKOZCAIo3Zd(A4{vfaW})^uG!;L*)WJM$mlQdK?9s7yU~Py)h^A!ky0Y z6VIOB=cQCbxS(V$*y$N1-bHdgo*bd!ZSmXc4g^}p=vhAHz3n2M^4^wtU()iae5ecy zf9iPjvrN9zIuq{iuRdqjB!rh`$F!2jiFX&sawR1R>w(wv(U4ygyT zkZS+;&g-jx#%8sKY|cV=o1Bvtw%+^tfip^gUzbK^%C+2iiM=KVFkYe`nr)8R!Z z-%j%%nFDPS+^~}1UTN%W{fK+8dSxrf6Rhei#R47kCa(j_Un1#0^x&2Kp z=rf>_;*9ePa(ZYsV`YCFN>4%d%2T`SWpY;8zrtOD0bJab)X^Vae!Y7}o3x6g}TYkGpZ4yQQfheWC2w@;;jwPn}B51)_@ zo1^9AIU#^RN$E0a&}P7DB&AbwX?K3C^>Cp@b?bpbl7eoxm?jT*cbN8btj2WQ83>Mp zc}OXe>?mv)m+UBN7{pn1PRnwT(ca)HZLM40w5_9~qX04?ouz+G)EKu#m5B|zhSfCf z2!%ogp*>B{7!=kBe<$h7LrqVaHp^*~7!giBx~%U_dr@VbW0_R(8h|^jW#KT(!Y=@B z?+*UpF7#PX$v|&Z65;DjB2d_jWVe5XK2AzgaZ>u`0sKT#9m&<=tguthU28{SatEOu z%Ip^~7%z1MKrDF}?NK@1lZSEHq!?B5Fo7v&i=E3;yIXgcsG5dzS5z+w0-9<2*o7F`0UhWxl3u;>L#j)XjV2B^77v;i#US-UE@4AmA|%Kn z9u@^urQT@WRYIrLzB!B8z0hgiNY@m~a#*AhNxfmOhjSe?P^9O6t+Z@ey<&IJz0mHU zeIq7{{hq3QE~^u%i@LP$wERerIa0SE#go%5LvPNV{|sH203Qr}h$fRb@;zA|&BE;S z^!^0H%zKH3EgGQ+4qGjLBS56zp6h+Bd5O8FL#ulKVpo=O){f z+Ocdy8-GjFwJmrd3g|4SF+kTkzmdGW0}E4oT6e@+cgI2<;;7ofVGL*JKDWAX=+hf^ zhdSKa#L#Cp?9tmo$TPJYpG~-NWoRWKTROz9l*kYckgQmf+Ryo#7dgu>q%OzweV^$& zLnk5J@)e=|8}<*~t%3MG=X-VEMf}50mB=Th$PIwg^0FbbYE1?E9!DZ@G{iB0<#AKB zW|y{*fN`A#M$7;snzrX^0~dh7m^DJT;j{vO$O}#@7-j(x4BHI)xzOVwdp_9;7cf_> zPQA*I!7i3~?~rAB#bB#6Snsuf)XXtk&a%0eHFYdvpicAmB^+m#xbrxj%AIIZ_fCvM zRjs=vb0g$vZ*HQ9J(nVyzI8xqH{#f---jTkKsdRr$gNH!w|9sfEGe37*~vqN4O5bb ziW&wd4>1|OKx3Xb*I7Oy*;?o#NhpgKF#TIkG!-GWSEoW zycJlpMt*cb>W_|&)Z6=t)PE9E&oW5e3_i{I9lgQqg3U!24B<~cW_fTdm_3P=^bWJV z>rjuF)v*i~n~y#6(qA%|-HxGXFq=TFsqR)!x;mpLl~_F~8t6$8=}FOCJ*h0Yr2{>+ zR8p6t@}wn=7W$3UQSN5MJSRO2K`4X~U~%y`KbFBM7FIE4w$h+=`{~>sg9HFzkX&~XK%neb32+j-n~?wmBms&t5};TTU_oCb zz%f>okpO(RO&#zh03Cqvk4Xaby#`+baAeT*w!-%+wCbVf39TBRn8(TG?lq;Dj4^iv z^it~HsL5iYd%ZbKr6DoNozGcwcs3^&FP@Ybj<;RE7dp$XybLX%EV-~G^sXoNZIJH} z`WsIW7Ecvqb(L0CsaBCPqx@@o+U)pr{eaZUdh8^Z7ftWLdD9PV2YI|E<(7D~A{I;mfx4gs?9d)TCQqLRSiGY4?u1uI7XKMxrWQ5 zVCQ)oASCQG`ImfH#3dh|TG*+wF+1w&^H42gys+?uVh2~G-E1JRj<*pq0_zj7;ZeYf zGr@F_58p>=(^seYC)9DufpxRrKe~Fc-|G_%)?e9B4}tY2gpNP2`;-k`$9cdMm|c|Q1ve7dB?pQF&|7*N7yuOTT+1|HpC+9m*sqQ zmQxJuU9H3inlXhzsr)vmoSoq2VK~AtEgr8{?gLwLXip`psPnvGlPxy845ZqdW1>F# zI&XPZ3Q*xhlFZcQv~Z3kpy_LkK6%|?DjAc>Xk-ZonDmOg7IgZW34igV(+GjgevPuR zc)WR(AxI>lKvvjCv0Dim|j5R%HMP!;2b4FXU18QJVJVbxVIRW78C**ozXoR zjZaKdI)S4SQI~R^NXWkTJ63Hr{HtBW&v=DQ<;x%Twr6t-SgM`Gp0tek$$L*j2p?_p z%19r*&m_^P_HH1S16XCq;@A-Ten8Z-oY0OI69i!n=QS9~lM;FFyO9xj@8jNn<;Cn5 zZBrty($v=1f$vsu%E=Xw|7GKw`iJb>znqKoW0M8%Qb^vB_#$AnqjN?#B|Gs(P$I(} zA6MK?z#Z&m$>)wv#RL9`Wcj00iGV{QSq`x!a(HBqN$J*C`D=oZfOh{N?g%sJ%O-DS ze@WTd#2lRn3O9$gzIC&63yC!|vaq>EA~3jnN%&L>8rHc+fanBRw}+L5W)H# zqwQ|SF|%vmCIvSb?`qt&o8Pu#D>=(nFww@`U=Lhfmpm?|Hjj()z{OC`zce~AR$Jn( zz<=aTx9pg)pIwAe^qkXih@wv%P=?xU^nVg&K=?e9><>0Ee@JrS}WrU{49U6 z2I>_U91AdyM?8yKbVXsK@>CF8dj?y=5@g%3rpQ-y7{;M zWntr$W?JSLoHi~v28X{g2%^R(G}J%sQ9dB+@yYc8MY{Na%JBhdv8_@V0x(Zlvs;*Z z7$kOwxrRZM1#2z0fpCV?xr#qoN1dv8AGLK?h11-eMgIIAkRQw)5INYB+0ZTeyB6z2 z06~FHB=EAOQNh;rFCl@C&|~cPQ-=he&31Ek&`qBNIHzW(A%U%i1lV(L(3|T9>qyb4O;8_qRPD4~J-PI5)pgC&bx0I-#xI`dWmxC6-pjxyr6&?G~QuW}43Dat!{E5;;ps!<#Yv*vA56(n7!~1^UlqyZMrj1vV>v$RycA zU+WL9@lK;QO@LNvw)~a$jes`JVq>BXwQi0Oon_3)QH6vsg;sh4{1R@=dssdP^AbyU zlr7AKU+_PyM+@&g0eSbmZf}^%KK*q1N z;;TcGVFghDYThaiGTdbEe z<8C=72Kp=GtLYzv=E{8UOP!oR$Aa9vipKY2554#0Z2+>~Zv*Pg;@=U3%~_o!)v*l8 z`lTN1n{f zvt7E8czN$5-sB(~elGLml<9_ba^42;^I0%6++*UJ-`LGlCMsdpV41+@EzgZga4T#y z!W8$$I7Y7@kucT0vD}?s3A@YKeYbD?o})N}dr&ZYiZQ6NF$S0gqZ99DI!1|^lsE_b zsWy|MbQ%WsME*@ek*Y>pIUi+AmzX+fnRUbo@L{pmpW8V9Q5+&Hvz{*Xf z4d2%ftsls?zLcf#F0FHZnpMVBc#YM4)YZL=w41`?tv0H{zwo~+Y#5bWFo{1vN@=;2 z(%mSTyqrScrjpH3VaJ&jyzlWYSi#Fr%WRzVi0)b4FaHQQe(Gymvd85g!R$PDLOfU~ zV#8k#b=FgI$gt~5prK)%rVPf>e2!xM05(892kid49r`&y;g5DH{~SQ}V;gE<41O?U zB1iZw13^-8O}29+aNThEV|%jl6?mhagQF>B@=kW+FPKE4SpHF}o>G-P)>TQVpv#;5 ze$wU7+2{zm-0}fq1YE5(!%|s;_)V||uT~n%GFgKxlA559tK|?dE`zImhrrcZs)(5~ z=c^2ziO)!#VYUL~QKHkLGm`;toqf z8HTaJC}4rzRPf6IV|O0e&1=alSEcKuC|K&sRq8qwfT&1bro8Buu5xpEh6}xK^&?zn zo4Sr0F8`!z@=~Pd@}iaYU>c>`cvO1e#4hD};KMHEI&q{(}wc<{?c`{_>}9NoPxJu%P~Ec4|rxiZgXUw5VK zyw_dnTCcST165yRL`uJKdvlZZS_;R1$q23HDW|fWU67TtWtpj=WuVcu`xD1M#Ue!l zvxlz$rbr;`B*YWLCZp$>*q6M8YDq^K#Jq=hP3TMBd&x?f!@NhJ68WOU=F_qSfo?T+ zh<%SJV-D!_KH>ehQJLRbi7F}aH4G6+3GZiCy5<{{odooW7V+wkGSB3QtyNYr6iL6| zzeH@^gW`3{imhK+H<-Ho3I7tY^`Kee?oX1QW-PhfBN1{Zp8$17pMJV2*~0|XP03DE zP*6gXkoPx;Ht9LNi8Zqv7tdk1+^Qf5%W^6Za9pPX@Z97fu7c4l%jIehCeLTW`=P-> zFzd^Fk?I&<91p>-8RZI9(LTv&Lt2-NhrBDaJbxq^(y2$?NRdt@x{)KDO4v~$vAhI2 zHL|3ibWc>zo3LMf^cer>5u*))v4pb{u~Scju~4FC3>m3Ni9jlLsw3guX*`GhILc3H ztv{myA2ZI&O{l;r%w+%2uj&wFQEWmj`phKj6ohc{nWXb{+l8=p*@!m@BdrG?IkqOC z_MT&F0&1tkQK&uQ%`luBKoqrrFZy7F4Oc)~e+qzan&VW1ejPJwe4NiuMNC&A-Y90MnE>3Z|p|h3VN$G&ZuOvh7O;58&1t`z_phEKa&NGa+1kS<`_~QHUtFoZ0 zwB?D0F*H?Znu6E&1`k%yvv!>?o9KXnZI)mY;rdJ#<}rXYT4u* zc+A3EKFmRG_gXV57u^4&Uoej{?sT*Y*0&WTSCG~!LS~@Gr&@Oug${%c;kGOzTS6Xe zJ_ZoHKL7$t%!#(O`$WvQEHzb>I3ft!fHexCrt ziPk+uqGo3fuJw4IDD0vVY^iK=v7hY^ioIv4<@Ty-*iC-~& z6^z*BeCN={9eZ2L8p5eVBMzqy?9&#al9ab^gC-9?Dd)aY+Lm@K>l9m_$8CC|m|L4j zt{LEx;>oJFE}quB~bUklg_4JBhSzho^(F7&RKZ6pQ)|eb|0fh zC#_wlR5CmLK=@nIC)GJ)g>Tj-Kokw4ZYLv5bswCY3RYPk*uE(R<{1VwVMp zsvcPJ$703nEGt%Z$BGJgfEBxaH~R?hp0%t z54)F-Ui8YH$^BMF(-_kEK{TIr7i@ z4=*|xN`LXW`U8iZD^>>ONhf$yNz2~GU!{{9IXHRI{9?*%E2PZm+%mIET6Q(wLjbA{ z19o(b?|(xTwNyXORA0Z7>K7y&KCUazYapFei;ET(qqR>4Y)uQ5Is*%IoVkf96RI^w zn-)f8**nd1O_lnrX<^(vYB`s}$@%diPRm&W{WY<4vbK!(2RF0A>NIx%*L3pz3G;;5 zmCP``SIisr$-O14JcJvCf@@;Impf>YYrb(>9_CfsjF%3O^EsXT_<08ow>+EvV&%up zaJ+j=Me3A$O>`tthb<}9bbV~(?3mMXtEn|Uk}%Vj8@aaQ{S;UG#*_`@zsA!SEgV8m zSJ7TN89!TNGl0l;RT-n)>!RtTdV9$=w4ltT?!wq5^J(E6U0)v`Ihz(7u5Amaab;U5 zX!t+H2M1jjPbdHHSpoxK0t`MfycZ#^Hupt4v~;N*+ASZoLtDYid-p6=z7ynsn zx~%P%Hv0Eiz)C{yNf&cHtZ~mj#wN?t_VqW`TrkTKp3YI z(20PeC2Ihfd!fli=YtccfdS{9SkpEG&RqkFJBoLA0cT%6@|2$;z+(F^f-CQE&SR4| zA@kR!KEDLDuzE7qpT`3f|mT=f3{Q@6&1dgY{f(IRO^8v zD2*)c0S(4{8Vo~&)E{>5c;9bDf4|d?xx-uB(^Jm?ApvX8pw!clp=ghsdmT+%47j`j z#ZRYRGgSBg%UU=9bsVK5js+av-4>%kLh(#e)a!ZfRq)3Or+C|oFH1Pxc?ZG?E785W z#GO;>4s>shxs&Dml>E6MBY&dl>jxuu!l|c{kAr=#S`QVaFc!BAOwTUDl~@0UP2x}b z@z&i#LLD~4Er-OLl<&&X7K0%68-jos%+62JcPpu5>#hN*ePW*dkVY}ix9vQHG>VG% z&G4-QlhE>ie`%{7F8MOUR|d}$HDGTMWB>}<*Yd|3E_uN38}_01EC1I_`aJ)%|9%9& zh=~Z|B8;fW5Y|x2Cs)Ods0e7QX^Uvv{gb_X6Sdhw53d|yH3l}R^*7Z^}oDy zSK2xu13vMzb-^D*ThD(E+L~A5&eT%GTqX32xpTD~A-Odkwc5^Dk@OrZw+?lZTRR4( zXG#aFe;mCloSdErzv=5zQMqT_=1XcsPrp69ndyu}PY6J%a2Fg57uD`ey+64+tfbs0 z^{H_`F8rrsY1A7n)(t1xHHP+o=a1K5pIEbw`x?K*y()3LNv6%zZ8RN!`AiY-0eiQ7 zeJikU{mAVD3esPA_~AoZT@HJnphR1sDosKvZG3rZ+0HcAKIxpPpP9!khK~zrp8; ziV%_-h;FdtaZPCCeQnxq^gnEmo|NTIfb7FJ-Z%(|h7I=Ge;HhPMWS^7v9~g(jK}dN z**A5$H}$#S?BxyfUzGVT?_iwAjH+yX5|(hQ+nO*Lng5ayt}=YqoAUy+tRJ?rs#3CQ zW&(|>ii4fqx^idPL?s<{oMq2aDlU5dFj2uWeBq z{j>L)LbSPLo!u3qlYD<=6L0FQ!*-HR%JqRyWHhz(T$c**>HisM%kycWUkjg(vgt*8 z=!XC4-`a!}YW3TMlKQQFil;|y^#Z>W7+76g`ml39q7#J!=@LMLXy5QB3U+{8E3u`AFCg63v-*WY9aU2L|vwQhdOzO%xxX zN!8tn3ViVY2wAF20HKpld>HzB5sr+x2mJDU`lrmNEzK~Av;1O>&j*beSZCnmb-D|1 zY7+v+uz9)A;hBvjGvn+5IOeNk2aoq0G2s5#&xNW2BJfrr3f>+V_9jVcO-fXf{k;kn zFH=-Z89vG1v-u>ss@nIY)|ajDikxLnKqjYKas*BFLxYF4rxVzb+(ArI{rSPO=;_dt zAtL-m#Sl|5#N>UOYEM37yz>k~`fU_LdP}WJ2Hia)_du(e4~~UW`N(@`|BkTQ;HlpL zcAtrHeLILeZX(jinJGO5?XGKSKdX^ck>8`vWlC3a)3x+EyTMMSC!>*NN>AW<*V2Eqd+=2Hb}oa` zEni1Nwg=@HZDrU@tHSN_RgLJ+oMn|2OmouLd_5+slSs+ys>f)iHPzt_LB!#B;AnzwL@^v*||cc-;3LT&M` zFx%?h!tQnE-8^nLyxY~(^3E{3c1}8JNY>|4_i^g?F*Xn7R(Cwi@_s3eZ@W2r@E$>c z;~y_P;&@(+G4r~jxf@@EVrYFk^5`?a_ZOqPpF{$*;OM6r^3oI5d#}-G)BQ7%&wOh% z^Ba8k$qAro*KDosALuMJGw=zO4&H9V-u=_xLX&jtg!Z?yPH?QkdxXhVb|SdCXK7{~ zr@fJ9IWE>-O0=9WdLh$Jlg4>MCe?lwUjHl|&YbLe%Fc>uqoAEIIp`Fe(eWcY}EjJQBC_7Q(pU>{QC<3zQMo$PI-z?ZT~*kkMeJIYPVZ6^ckX}{I2!= z8enN@R;!x5sgkbkrfa*~A4u)Kt9=<4Pw;O;YPad%XSx1-XMj2_TL1UH%O6WNwQbk` zQe<<^3_~^(bx<>8!;+*s!qQE(4rMf}`Zew~JU++ATgX1ET(isMS=f%(YvprYe^PCyMl;=7-yCVcZzV^Q+~NyL1d zIqC@ohn$dJfC1-&zB&AESRMI@VSHU#bvgWHlf^1G$aWlu8hZHushaQ&{}6149)7>l zd2;xlQKoPX|Ibi5I?G)in%a~;ldJ!NYT~dAe}jT#4*w&{GSA`Pi;Wn3_>X7|z;}13 z?+(lGpH{1g;dg$X@ehu1{%p_wtn7835>^~h426r@E|1!dKtDij*B&3UP`S|H9P`?S zVwd)KG#(^q0=iSm--6rRs&vwaWu3mQ^se0I-zX0NZgWieKMaCojo$EY*W~Yz(mai1 zFJg#%#PF|II$;j~kC;gjaydY&SO5Qc!|zi~_*VbWk+@ZA7#zO7I|jq=TqU-(vvFVu z&xv0;D8dT@=YhDD2rPy@=qiESn5tg@yFO^t^?k4_%?}@XJKrLTr*oAC9U5{RU@PHT zg!4do5TGcW)FsM)Kq>`UWH;htZW?xqH>@~YQ?z4hr#gQrN%;<}C+C1HRMH)P!8& zS1eenx&t8sfNpIUob;A`t7KlX?|$I$;KhXw1uyom@iCa1TI^jN7@HKmS5oj^`6zM_ z=GN6~@0B50t7^q<6|hvgtbn6p85SxAcFO(%y6|hd!^fz@!1Ibh2`B4wiD8wX{jz9U zw5I=H!k4{B*4ERJ$QZG=1!EL;vS5&K>aP?8$HNV}avtmEU$}Q@9TtF* zr=UZo2!kjorxy?)RJHveSXjFh27N~?s&ebx#$6z^j~6x4L4lB)4hv*VRe%SVR>^_m0E+D8VLi_*stX~eiA8v%=h+(h z_z+xeU_M+56bjE zqMAN1@|NVshn#%dM!AI?7N_&RT|&&MeH&{|=TgmYLAG6vp)$Eq?E(WeJ$Uma`?6L zp&Z8TDp2o2FCoi#6t;*_<5%Gi2qUJ8tlz9eTaO5)aqX&l`w zIl<^=i6p*kg=nqv3}&qFi>7x;5|@|amK)S)0BGR;67@$Tq@G&lVAv0}0V3CH=>mZY zDK17fnYO!?s+SkWtWr*7yj^B8l?>1$QxS|`36a3BD2DM1HLR#6c-P7%{fGt-fk}nM z36*WdIA4fDm1|DkP~E*5)K&+ovw}#1^zb0{*YClTp4~3 z#-(^sDGaL;CE7_z1SR$*>g%zYGzGSa(jY8fr<8Nd3D1OVhFLB@k?$3d`u#cOJrHL3 zNqnzxq~tw5TRsgC6D3+eR%)K{#0%U2jT7mAH@a^)tAZ75CN44VK1I-o^7QiXbnKun z4v5lt(ic473=dyNtmP4@twMmP?}>P|i?KRtEssiV75hVfRLR<=+QyJYbeEyexKa&z zm$aq~zku(kBrd^38Upm~y&>5cAi@FL@OK)2*W+*IDaug(&OrWt27+g9U9&IdyRon9 zTSVVEEn?fbgtncJ$C2n1NT6&x^|<0n*>)PNxhd;{4D5tSWE9=Qh&PN&0^la8066BO zjm@~A4sQ~8O!DTdOQ1_;T-+z1+Ec;385(y~U@vWjm`NJO9T&mtv?-?+EpQEMVatgj z%v6HJF+r>}@0Ozeb*`rY{HddVH9F3qPjJ3wlMj&v!Dj=#vM0kgmrI@7z6B9g*kTnT zBhSzZgbl~-Ij;LSTB{a;_pV_G*DcdmmwmX#q=C9fCwq5Q<+jDEZR`wsXnBhpyytXr znKzB^D^>0!(583bam>2-p&}&IRD)29X$4(8ggqIvE-Jh(^xesvMHb69W?ek(b@4U5 zX6Pb@$I*8odMW@!ih-2h)*S)H?$FjVtlWu?Ar05S0IF;9fUx2t!vlkN09zx&hPIs{ zfJYj*;K24p$_B_CGqgDdb6xJhop(riJLoAjL89OUTI(_bD;(}e=R*~k7O}z-`>!PMEEb{FhZd3FLFKX)FhBL!5qq>U| zEZC9GSBjb0a*I`_sqOgAY0Cai=@8#_fU`XT-@)6u9{fUspUOW7-C1s3qtdd9+Y1lK zc)8;>J(K4M-kyj7hRYqWsF4QMx{?alu*Ze+Xp;bDE4>9(PA5dRfZpv!82b*+3je({;@LoSER;0r=@mA61(ns)61No;! zEqmA|{%5$llCL@zz^?eJwCjWAr1?tu`KqYqtLijgRR_MRPV-gDpC2nI(}`GDhNtf* zEf-w$;v|Z2Hd075ND~UMd*s0MGf`C^wlzIVl-vX+^4&vI%*X%~QvsuheWgMP&QoYW z!bnS#j0m~9LK~j^l(GOR9TJVu1&2Zwy(jFS#P5qGAasUL(3zr)dy93i<&LnHGUxqQ z=~yj6A81bRPXUk=5o{TvuQ=s0$1E=tN-1UlxllK?` zi8q9Ze+>)a83Ryj_<02+1Q3WKMIvIf;Oqhih}noBCx`4nLZUNG)W=R1*ldHPVxqS! zFWBt>COoFlRZ>eZ(>(FwIZsNu+>?^PqKoq)sv4F=|{|AGxS{|AC0C5 zFqp{zV1b^$t3IMMXcWQE^E};t;{n&H1g=dYAk`KC7qV(+PXgFxdhMfnwi38%9pJK= z$g~8sR(qh8^g!$DV+UHDTZLKmDb`36Gvq{_&{7dUK(mWvpvUo{gKG9m{y*j`CSQ=y z{;K~EE$pZ_O^}$d0x8hGq)L#k5myp~`dIrk6*y@qaLxhGVJE~4##5?x&{GO?5Wc?F zqrOGQ2IeG;eXXCd5BnK=kR!#|*UC|~Nko-lkzVvZ48|VBMaEFT*_RuEqP-uG{tsZU zBK%PJ)vt#>u?~O@w#PKs|bF|tXENu z0anZ>P86-#x5V+|@U7Vl>Hh#AY=_7}14nQGU4;iAhY`_oZqZ_4Davzk%v*r(nJ$X* z?bpPBRWwF4UPQ6boS$Nm6@Z~Rs)*ti0X;@N)~D2L5vUFy4Y~L59ngP0%;0kUfr86C0T;;* zp)5gUZA89;9O!pP%z)*BS?GR7umN^NSP~fWDo2F8GIL+8uQI?;Dia(}!aJP-hII2} zNx!EIc1xsX(9Rp)!Y*PnAg-^zfj5rgb#BiP^s^C!V9>BEpWQ zJi=aobj>FF-|(Tb$H)49b}J2fS7l((gW=n{DP~<2wUg?n{D*0|$+vp%2D=S?H1=}WC^|&r@S{1Iu+5Vcd@!u@xZ`p3ZltjJx+we{}YqY}I1(UAZ-txt_{HR>-J9%z!qY z&q6Rckbc31!(?s0AOFJ)LuNX;^I<)=ovWRa&*&0~W7UOr13yu#U{CWtw|O~;PEpDP ztnM{WKSgyqvVb4aIA;=GLJA#@iG_JQ<1C zz@WUzIwJ2_M_Mk7$jewrs!PZKzd+!R;x~%Oybv!tyav)9pH-AzT}hN44Y>@?I~E<| zephschmaekSF2T!^_E2G)k6chTr`ka6)`2{x55d5K0ir`mQxXGNYhggaaT>h4;5*b zaU$(>S+QF4{^7tJ%+dNP7BUKsG_uIun382xMe6(yx}ZEw<# zSleUpSiH7xA8KtCg>fmVP#@^>sfRaD_jmV1+CEL;ZL-R`}0K0mc=cq>RLb-mg#> zMxO^C^uF#^_!u=GuJHHNEDtRVD<^;!H~}CEcZt#nl3@IYLMSfz84As~c*!^N zR4L24XoBH_nM*PRIzdYYB^jlk3HrN4gVYFoQX$bO_4)&a0%`J-ji)uOz|@vHWCL-8 zpoLY)JL@BwJ2j<5#Y;5=Vhz@!w0I3(cVO4$A@(~DBVt!-Y{m*7?;-X-(n;s_j@Tde z5WD|yAhtgE|D^Qp#N)Fyb_0f|hf}{YT99F9uc6is|dNnn!!H*%akugFjZeE19A=%0? zVuS22p*u^0R46`9-)sMf{VU&lUGiZn!U{@uV5#ZHgS~3RAyh|&e;pM5PeZVCJT{H) zvyJWrf!?j5z@vDm?A;m(FduwSWXGe?W2RqS41Km?3*X0 z#*nM48T4s5rd;Mymkq?U+lxhCIA}muOY*qV2<5m$D5vD>!G=!PiGme6N~{F_`FDvr zc*xm}73^GEJfiQ9W(gd`x7Q920}f!1+%7JcE(Tle?}<^tzqtP__K6`EcQnKL2g%QM zfFR^fZaP~jz}IJ>?JoeG4Wj-ZZAX_$h|^%J_-Zj$?-Zr>Xgl7bhJO%6Z~MlB)`dNF zG+1K)QndSizZYT6f`RFalq5UTg@Lk@kRLJ4lQKambj?gA|fNm}^8 zwF36fOK|2l2r4YVnTI6GQ1IVBk}=Tt*bOCk@0=6LsxRTea!z2{Zu$ZUM{;yj;%g_o z>66Q-aGQ35r*PBp5kVCn(SY~}q%Tt;641FJL?Vs&hp@FYw&!VNQp0Q{MB-?=-5K2% z^1h%+Gp?z_s|59~0dU4j?^^4Ni-7(#(~F~$nbeH#mrxmE+d?G0!p+mx|41lQ7FasgNXpkW$PSq!dn?v(Zd4 zAR$WhQ)h;!PNn2w`jg8Q3(A}zPheKUPVx%j0Yj{yV@qT|j)v$-GEXN;LPNiGlMb~A zRF1{i7nd9Sb|s5&@E7Pu4E_N};u`+ozpXTi^zHeBeynUe$b)}WFE{$ERvbT9ezC*I z4}b6%E8&cTe^spl66qYQ!GpU^GtpU^Di&KY?1ZCFs*GRUiaT}AGt6ceN{0zY(={B` zc$v?h_5wCOh=xjm_w5MZARMDp#wqa;oqmd!AyDET{t{lJ8sZX8)sF{n2{}xx*h5NP z!@&m?!f^0k!VZpb@aK4#x8qP@9%XL5@~0?-;oxs|v`Is+ytCbRs#W01Kdv9q&?#7j z((V410(7p}a~`dFeAu)K_yKtU!zR6=xqy#o>|b;#gw5Y5fnjXuZ6VFFKPzny8Y+ns zX3$Vcm_`Q;m4yCq`80-7IuVCTU*X0N#H?c=ZN}|+9E#$I@dZO(gXQ8&-0)1sm!O8S z%$5z%q_;F`C!(3J@EW8tY&X8e5HV zteUouAI+TY-yFdhcU;lsS;BNf(DHd@l1NjwQJTa}aL{6jpD}+#W$zlZ<=*Ab1~y{m zYV}wk+%3<8qfz;fM$a!o)U_{TgGVTf^ks>;I3lRaF*1~~Osj!yCaM?xYlXx_`&#AV4mb(+Oy>))Lb5r)CUDu_oiL=iM zXM>$RZOzvF;OuiE@?3At56wO&D$f~feq{DMRVQi9kItT_>cp)1vDx!f9iZ~O>e;3>Yks23`W58Z*$+a+iUH-;#l;!7{k01G)wcWQ8>^vf`V~Iw z%KhYj_U3P1Q60$sx+{0ZV|(+~jj_&r_TjGFQ-8!CoJiLlEH@h0IL+X8mB8_va$8@^ z^#(yMLiStf6pXpgn4g@DAEWas7Mvf)%P=(m!M;x{*qetR?p-}bch3x@fH`wMQ!XJt zzPKc}pmtZT_XZ5)#%dIvhriozgi4{`HG{^Y%|r5g?QM6ZIo0>zcUv7i(^) zcT42UdtMWdWnM56l)v-%JIEAp*TiWod5SStQU%u_97^O8g5kTwg!NeGL>tXd|=bjQWnez$bHrgujq@D!{&q26aBGdE)*VS64b!p-}`fA{~^`c~zFT3)i!3r}lUcT#2( zF9zvo2H06=Th~T&&n^W(@CG`%@$;{*Rn9>e=U_Rx zq{cbXB;x^sNh~u#sQ1}W_2%k6Gq@_(8#q&ZpMQ+ct>A62JKa`kbpHv12B!3I?XBP{ zqkGA!N|PBL_yP#t$DR^x9aYg}`#vl{#XbC=(X_^duQz(6gZo)5x=)pFCP2f&9%ibLBS+pG@i4$s|{vzoL4e$HyS5OB=kO1q(F!=n=^YrfZ@k6zH3^2vA8|MU*W-w_q?f}rr)`hCqSMN7x;$^f))UeK>`2wVOp`=8jd=sU~xn5^O3+y*oIi|bCH?5F{>b6CO8jc z_55&G?d?^kTN8}#McDmUF)c7S-2JE(FuFf3W%NGnmwh)nn=tmkQ|Aa&fr61^$!0w@;hU7FzMTE+$n@qk5N@?yZKD-3lnX*1c|GD zK8@K}{6qkYkB^xQufd!jH6O$_ndirG;ZPHb(^w<=$u?Nm5l@o}PZt9gP?*SVC zc=`+`opA6pX`dxPlX3Ah4#Z4b3u=w-YwKa4jqp!ApM~eBem)z|F{#!RSMUcsMpC?= zVQ0*jtkbLuqeO$;)&NO703xlhIfMf)TeC^5 z7{K$U_hOb|%p7av+ieECgw<}#LdRqRtLFg`R#u%#L|8ze2c^S{Xy@bE_Zq9A+E);{ z)qJeip2%%U_(7E0qA8i$7WnM-ZVy%OMK(Z;&=PWd!Y%0_{I2(2gn4PWdKF(2hBvowDx}(5`j& zv<@)aY+dDmcA9@`(9Y;*f_ANXZkl5)h?@@)*A@tPFM(gF98=~RoGUZem{EnwSa6}} zLhSdfN39B@kq1Fcj>43=Blm0oeT&zrUy39FAb#wkw6mjhmM&}O! z2RM_%vO$WL8}s{sV_^%l8*8ABc4Iy_P51^7i4evRB1t#q5vK9a-hg&Ey|5k+PjT)5 z_yv?YN46?u7AedS>snAbWVaJ~8?o`tWOAFUR)ozns+J|pWj^3HI=8}SGqIJB za=EqKAxCwZ9Mz+V%QZP_U=;+I&ml=dw40A6waC|nG>JDHvKTbU3S!X_b_1_!wx`Ha zv)R|XB{Z|oBee>?H+t}$5ctl3el=pRNWUa43a+@@BG=uS`*P1_utJERTT09%MLQdx zD~iUvu+73Lz)~&bEKYJ}-=^R{2Ke8k;6D~=Yr((-{(WE+*e9!xEh~aAK@8c)q8AD= z7I8i?U=Q>rq}YupX;u{M#w{2FK|yHjS@b?g< z#w@}RNAYRIx4QHg)JzCo*b`9E&ub}xxvXf|%JuU^!yu;S7Y!%lHAKVH0jfdJ3o@X5 zj2InX#Un-t1dmid1p)_JR=wUm;-W^!qpZLqu8~LF^8jBYv7Pw}B`Dn5hFOzyh{#nJ7#mjvrvziI&Gj}J zP-Asp8T1bMa6wK^p?4aUcmT3ZQL+pzBK{0`%F61c^1;7d=vLip{3FqT_ z2nzqJXl0|Kl?TdYH?kFoljXVWB$t$Ho3c+3kn*_fQ3#Mf#jXz}Kz>RKkPE?ta!(*b z_G5J@Kyv@FN^1)6g-cl=LsFd4E2fUsf#t`4CMBiPg#>kQ`SD`xnxCdQi1h}SACED* zuLAi*TGdU2MkPNkapXtET$GU?TO9dONtkaA`Edp5CC{=kk{@{j`{lM%S5Bs2nQ z`ZYyOBRC8j@W|}}(oUlE2Xx_i2np5``F_MYLx_A)CGvq^~pbg4hwVNM1V zPz2N(XD4G#$FJ}KWg6keeIj77#@Q7CDZqrp+sX)00 zkOtN(R3ZX~JTAy+S}%ws3~>R+^F@5E@Tf~ctZ1c*1$h^b{4X30SFBW!Cl*n6&C^Be zShQG)=1X-nN(oiODeb2`IBKc#Q^S=i&n=QFM{Fqk4Y}e04TV=IDH4Xldjti7&a5LyXA7Q2?*MML3573~k9r@T28Br)j8h(Gq33n5xj zgFckG@M9POI~dYjSTFpOXD&(@+6Y z;l)7xFi`(@msok<~-HP&{AVkpvjXO6VM!GteYI$3{8z0ZJVK~kqWw~6A1#-p%39d*RXq7HpETdvhI zq0I!;d%)5qR?8Ht!2h?5nSe5Da}m-;mCmQxGZWlQycNlVR$glVsnX1A!&fJbnL`mQBvR_waZ@2B(wLG+hcaN&=Bc@KC6P{Q zf$DI3qPN_$EIm|kW==j6 zt!Np=bBW0_2mV?VGAo8Hw8lj(ly;1sNyq4!@{FD>%IKM((X$dg!06dUuuz=(zbP8- zH+rVMldaI`>3gAMF3Q9>;-H%LKB3HoRiU`bBYqN3ex-etLtbn^-XWhbI{twWu9d(= z*3a9hekbvWi5_10j(}?~vV_uzDV9*p%obQe{Wed}5~?jdzSEl$04B(4EldW9x&jjU4M651+X7g|D7io{*vk+|tf zcD+JbZCZx5geC>(YeDatu~(#fb^LWKp=rkwn(L5W})lpt%A0)sQ}Bzi>|lPCnm9pVwe2Qb!Rkx}e3{-UQA*<%_w#jIJc(A=|+HmHIE zvu2}@0~Xs%2A|VDniR~EhO|G6*HD3ji=|HfLf0*B4RGpjiBmZ9ON8SQ*hjO1Pi({` z<=8l=m0*I>6LQ=N%G*cZOI*SNE570ibq3Mw-@?+;GpH5Y+AJT)8Pu%KppPj@woaWv z%|&NWorsIrfo8j2kaD4ml;ieM&=6tR#3k?0*+)m=I!m1=Bw;eA_&03VS*;?5REuMe zW>X?*iAW5IcNng-B@!-OXJJ*N^MIg}F_i0Up>PW-=V^Z4*simbW1_}6j&OVETxVe{ zy{VFdl+pbfeqa)Kd_=CZ(W+wC*~qY5XQ4?i<2tLUqaxdH64%)R%O_3=h&bb}vns32 zp>&<~n?8YyU@693XUAP-Dh`d1F(v|wg9bRctF0mfIP8?nb z5!A*VUdJ6?X+vS~Wueh9sf>oc@X2w9*MS^f{es)L!z;K~vDdx{9bOOEOcV24qf4vOXr3iu-kjfndn7iUhK4b#fv3RF!ll%o zwUvnjAg|J-qQfl-afq9X1fHP}rivmX^cAU+h9SSA32Lv2AK zT7N_ubL*WCm-V2yte*psOoBTL^JKXyrNU59dir7>u%{IWv=OcX9N7Oz2X@T2$h3#? zAWEPpIu+5~n5%NwR7Sw}@3X4_D6~3h`>gi8=X?s*POn>`%ci7O;C@M#6>M8Lz)eNu z7I)E{Ty;&t#8s4%gqD(qZAggbyOrhpCaBzvPymYZa7T4Ao-4`TrB zIzQ-{=t2TDocy3}rd%DTA^AZWnATDlrqRMM4F_;T1%L}(7y%br90G34aRg==ct1u` zGKh(A8*)FozAdpW)q=n+LQl*EKx9 zQ8V>6fV8&aH$nyYn zmgFQla*G7bjmDD~`ncl@FGN+|_}rP?5LL#G7iK06ne{qJQ^M%D9)m!S$k_T>(%~32 zqLKFP9E`&(s1P}(vS*xtFcl=npwxUr4grE1qe(%4bhMe|9k-NgehdN&= zXe$thbbT^Or;fnWhrSBUo1<6>h9>>0`aGEvE;68QKSdCu8y%68N%_b=HXT_I^jG zd`Wbjg^UYW1&1HO263G*H<@zb2^F$GLSj#-6h;NGLPmwL=gbb*T8>fqccIq}E~9c8 znqJ~Xt~Th%Ri-Ijq9a$ESq0^DuvLuQTY`8#Fg3ok{g+51g|_IlvPCb^OwF+>dUc6n zY8C=oTLB+rYU=sx1f^yelu8Jxb=aIb#pWcKxb$N#n?pv7jYuRvevw!~vh_-KnV<6* zx9>!=*s}!C4s=l&(r^yBp)_+BN)KylZj(so|g}s0o;N??A}2ig;Y_mh+Le)D5p}$ zK9$I%z#DIuD9(wI-3wV-$~j$v4Q`bWgmY?DoYO+ZIn^u9skMl6s&_c2R(qCU;$== zfErm6WdLwN^r1pd2q#6QA$^R7Nl?_$cLk1gl_;LW2m0|gmnw`yVV;&-_IVPxA*p2P zMO%=La56rF6b8rViFi`-qzeCB-LDT>^aC0h3_N43PCN1IEK|x1l_g61*a|-a?ttyx zt?}5=Xb5#0K%mF6tIpT}KVCsp=FSaUfREyf+`6$5;D17Z8nys08ED?-o@~5ms8oQL zbi~ZL@Sh6eVt}{>3gVcB#eq1LFW!;3Lp*juICg%5ngkr4I-`=fr#hKNf(6hE9g#2B zi08143raNXk~6yq#9C>J{szDcJq#e&$97@gQ7nRZVX@2oFpHAj=qrF|0>4pAkqHlI z?*{yaOW@Z{2G1*UnD{vE4+dC^&lLRLizi1P5=2Kpu0jY76M~U8O+gwn9RY-uk_2^e z4tpTTrIaLhYbUEReq{Bsss5L$nl?bScYNA@njf5dz5W}tPGYZHbMn4wf!;X+)TzaG zX1c`;jD?Q%Udk5QhFfw!enz`1?LljnCbX)^5> z{R&;;K$kBmRN%#NKo?;j6uK~JGUe~$BvH&pPLZ)#{?_;Al*->k!9nD2WfK}g{&u{} zk@(QZC2_Pziu_H(#n%CPWS~mq@1iVhlA2stGVoFfKQHx9Xh9*`)p)`tmMB`nenks$ zpg{fNX(FXK;Zw8_H{OOug_-iC_^_ib^NOgx5zKNv=cm6X6Eteco|%#%$qX&oPZyl@ zB>NGY)87=)#wnnDU7;bpPOHhM79X!Pq$fS4c%4rv{s2%KO7UAjH$Wh$6#s-G5Mysk z2ga6jId<-G#o0Vd5hPKCuTeSUhBB%HVoug13m0dmw~NByenx^k-PDtwV!mvR+myj!ldoQ<8v_tqjCyUwTBs@2gO2w@9R zOKj|QAXaiq9Dc>t?kJ!2C2?|C&2kSb+Su#-Hg-9s?7I{Q@{GDqf#AWgv14ut<^^Bg=U<&(lIbB`evI}I;`@)EvGS=cll$vwwAUn9y^Nrp|6VO$Y!bbJ@U zkBD%76I`WW02hAJn*$fm zXkBJu)=Pjd?|OZ$`gy!2c5J|!hs;`i>2BEQ{3z6u4!PkXT^kOl`SJF)I_DDJ5GD{R za_u<|UzQ^~`Vu9OX}N@#)+CsIC7pGe1V~%V1f5DOX5~m!R9Va%67^kmmXsk;39P4U zF^lE4GxMF0s7WL{ax*Xa);egh!hSM^EAa|%=8crSnU~ko0lCMsRty*j1^Qc{5)2mH!i38OW7WN=8J6pO zb>izVO>B;?vy-{^1*3+6g7eW-c@*UBtl;(JrfL`?{kI%$#A<^ACpG@bA} z33%4b`a&SXfYZ=vF99QqOguQ4_*uw6RnZ!WLH1gpj7vr6q)r{s&8o zc(sfogiAHAmQt#QrHWU(1R%VS1Q5Rs{B>LhY8}^sI){6!_qh&S?{aTE71>C!>i}b0 z^Y@DDz}L_$5QeS;Oa;$J4}=?BsJOwi6*t(RxWR=*++c&l4KB2&2!3Af;%6K^ z9nk&2Qx%0C?0#U27;u8jUIxx8xn30Ob;RcFNB>@`3}%m6hhF@ zz5_SQ$ulOt1M6_GSf^0#$>Jp6O8;oNUrh-(q0UX&o1kWB?5ZfQW5AI&$fe6!$9=?8 zIuz({z^m}cc`j7FySg8TLeO+{yP-A=yWcsHs*x zMxqbBo?^FBMeG*uXBFjJ%Ko0vdhfEhF%bN(A;~A-Ff74ee4u@Te+XaKY?6DoY?GUf zSwMpMfg<#{rRRf`db9-NO8<0zrmeb2!lwu$#USLu-on92lm%XSh%kZWjQ}pN=t^vv zMCFWwHIVjsO(I^3v@ezpfQKi{2NiPxx5qwVEL3zrp5Uwa#)5mUCVX0X)N$;vDB?@y)bF?WEI>bEHbPV0K!}6X{F5GRhk= z&#+S~Y%k?VMkLbTCxqJ*=_!qmM+2y601qL=98H99g9IqjOKC|oKq)$esOz*=T?P zk*W%^fj>fkyD>;KfaM{S6A-{zy+NY^4zKVoV!H_#>R$hP72fkau?7&{i^BoFhq}IS zfQ!^oOHaOiYz7yx#cTU=;`cLD&mVc!6^BMksxl6D2R zy?9b^3sk~1fKHbqk06+#GgM^Gyh(8>W{A)=31{LCOC=%lRtjeE3%LWnR zadP|Zd?l^~jxWHZS_;)c*9joh)-`+)egTNsp%US*Q|Jq!H43yDOoZQtbv>ve{4wT3 z^hSe>_NXt0`(do(AW}TcErlD>Qs@KVnD8fsxgjAlBr<#taV+GQ;Ty2#fcHkFw1P$> z0eeUqpHPl(EH#&um*1Zx)(l5}|2`__){UwBP9x*+<#+LU_Q~%g{aSvP*Z}%6-lM2| z6jXkZ0uyQ=Cb@!lsCtbV^=#`8syf4vw!Pp0s7Q<4l1C?P1B4xcV2q`0Cohl^478`R zPudOxPJ)oOBRW*rbyW_*Rk`5ukDp@i71d|zyK-s~x+;5W5w@&cBAjCM1Z1UrFXgy- zTUCdWihRf45(<^v5%z@kup`JvyfQWcL7q+ystJC60zV&-B>!KBNs>Pz;{d?P(j9D{ zd8p$629>;rhyxHAc_flITn8Ps*1&Q2(&}&}Zzm35Km-GWg*;oL-)h#hJk>824A%pHiEiKZ6kHpxhsBm;+ua1xo&el$KTU z*U|$9@w=yztn*t@^2b51oH&^%ID!hM#`uPORt%-x->0?vu&BWA z&+pRKODhYt`)kmPWU4b(&!yQV+*Q94Kebh}t<#O}`IU&SF(K9MhS!SuqS3vpy07>7 z7#ObV-IE}tqE9nG`ira=9DsMYw|z#2FUt8^rJNT9G)sNT`C3mo&$FL)hFYbZk16H+ zv1Adz04n8ttVlV}m|IcK$7~xw;+NtE2{wp2hqe>TH%PF-{n&`YV1p}BlEa9`JaHeE zW{6;end-3j2OA7-xToTs*Vva2HE2W*p3$ZA8^n(P2=)VP-+`y|+l2-W)Q-7_r zT-}BfYH*Z*&)9?-tRsBUCw%y!1~c)A;`4_NHP9U5!LZ{41V<8TKvR!jxp2Ut2E#Mx zJJ5X40u2T+@KdcsDMlHO4k*jtH#9&Wn1NpsKaD!j;C3*LAg4nI8vKKBmt!7i@D@_A z5e6E32M67-0}W_*#x}0;d1=syn01mhczyr{A}r9AG4SFKtPRpy%jkCy=-n2p?t_;A zjzleiA{Pnwjk20JPp*wLdG^L(UC+Mh{r(~49yV}XV45NyTpq#Ks=Ac88GYM zB|6T4i4O2A*$5k1UKD3=i^KUOeQ^d7Ucfkmj1y;2tKtmkZg7cGSELwca3a2T+zq}! z?o+uN^eTo(yBj1ZE6iM?9Lvd$m%1AySX{>4K+0V)oX#Ig>k(Hj)07&DU8Ws8Oc@=^ z6>|jBiB4}kh~I&8lEnD{V59cB)CYk+1db1a>u})s2`o(3Yw;c5YO@PvIc>qG4+6an z#7x>q;otE=pa?g59|SRTjFArlo!5LO(-Hb0e28BYP$aAX@WQYHI%-fnxTIWI!MEjX zX?Fc;$HNK~mUsaKR1p=#6-by4t=>|4WQP?DsuCGVSb^4$G&*ko6!@FzfUm*~#9)I2 zm5xI`k+57?0WcLuyBk)Z^&{f5Pu7503;ivY4J)t)LN(||hU=NYumT^N>7W6KP-uc< zc;kf?jOQsFU7o_%UsM5AI8MStn&L`K#Rx9oVT%NX!-*<@J`W}%A1}`@!4!~zC^QO- zTOw;0Ws;+&=FJ^dFfx|}i7N115{8Z{ctc(;K;yKz&*rmQ0ubcj!W$4D+0 zpcgWD0Vj7QeG!l}9Oi{WGxu<=6GRNAg&AK?EHrb)pc46Ah`2ri)FX4KvX ze;`*k9PfksP%$^Q-Un~g=mBV<$weT`I85ZOvl(Ir=(Yf#W_4FD7>Gj|J+Kh+{NrOF zxsOK=jQlPEW0biEG%H~)b;uOe^Ai``u+EVE5hE>6`{KH8i0RbA27g|*GlV9dz@@NJlTfh z^{=9J3Z-1Fejbt6|9ipx0BJ_*^?y7*gVG$XH3pu>((C^LjeT0KADP!b@8|)?IRdYL zrt0wfi`0WnJT&+By`H@OU3G2HXDNZZzb`>V*gYj2uYWjqfm15<`WH=3xIiKvNM%E0 zDpl}|d;Qap*#sy(a_;ZD7o^X8$vRCrx|9#zzXqmz>iTYA29ZCJ(7|sEDm*|)ZLyYw z>i8lVo9A&s#QwnY^_PY(UtbE^b*P;Ek0AMag3-MIyYMQbLS6a#jhgg-45IUo4DbG7 zNy7)vyZ_+D(vN%hL(efl>b=9|-GBTjC2g@EEO9065oG!Qi(;vn<^L|lG!H+^|DW-} zLCf-ARx%#QwO?t8js(e=uKnKxlM5z)6e<31KnsfBUxufRNX()Adr~;W#fP5`J8qhU z63_mkoC=O_zT?_|A{ylpANKtChnwR6SxU>JmbuRxuFRb*lDX-`pW$WS1+M+fz?UgX zZW&g|Ei>A+Kc#>>qnM@`mQ-B&@f!g9$RwuAbFn3*%P8yKQrdihXimqk6RMzs%OrgOjVurR&(>)nO#q5QrDn1^{f$< zw!6Bo{aK75U%hz}Z5I=&sKiGy%5*Z2gJQoe$2L{8UPeXh#n3FZ_?gmZy*M2D>9Ueh z4*l?UWVRj2)(d5ga_Eoxj04ZW*rqcP<3sypfJSaz!u1k*!#ecCMRC|Rb4p<&bLby4 z6Z*JA|F}bcB=8cPH->R{fa}l?S2fpw153IuTL3KS4m6Lzp?}OQ>*EgnbXIpQ>$ITP zY0LWIxj6tAiR)8HB-GtJ!l3aOzd3-K$>PgRif#^IhG?9$We8M)JcW}m+kbNauY6Du zM>OZufgKp*n*(%a0XS-z5Bs)uKV19CGpMa?aZLAKKEOkJc>wjM4eU@Gn;JGh^ zsO!$2LrJtW5aqxe`o}K{IADkVwb+coa}3@%j2wgT>qi`c@+kL`0Td({w3Lrk%;&8V zrOrz(94t*T_&LY#05cono`CZXq2GayhaQGs|8rQ_!#h%|CRt4seG)3>w`RgO8^veZ<<>Iri{}2 zK>Yl#bcOjiy(?;HwT5X`EU0pC^UvkVzkepxgY1r+n=iUzJTJ^Bv2Z&V0VrEGO`2l( zX8J}NzG=$_J3=dHfk7PQP6mr8%&d5gOr9Yzt%ltj0+=}sK}qodh7w2Ow|`UeJ1%ZT z!sfH^jrQ@6C_^a&6D1cw(z)x8Y)C!oEz#UhQki;}z|=d@(TELzS}JA@S`QSZg5YS2Q;ShfXaQE+>ilo-S{}$Qpq}b!-mcv71dZ z!^6h4p$-@X2PB4fZ9iaxm-8KjYXZgt2F3#hMj&zH&~$+oqBafx&=)cH!}v7;5&KPg zO~CpgMfgt_iSQeGpO3UZM?DO@0zljb3e8780KZJHd6z;F9)(Q#R`#;L&kkRR|C>W7 z#9t3~5VIb}Wq$|#rT}f{DR3l>a!M~7x(o`T{FC1CnuC4cKcd*3}A6u&5cNB#G3$c2@- zha-Wc^!bXjfDFw5e7c_2noa8RUUPvH z@>>3GfVWezkNj>!-VN|7U?S(cr2P#WN`Sq06AcVyt9O)x<97ofU#+^E0$IGvneNB$ z2Eh6F46yU?-VHFQ4c~$05rh(qnN5E@lweFk3Bo+YMtC>Cw{fbK-VIQ2x1b7kXDES~ z{&g||&-7m)w`wspQDFK9KT!U10H03)Gsr&=6bZSv58`ToD|kR1(A5A_M8|OGuLk&@ zVkAZqNWg77sDT8ogK5z8Z|pDB#xjs#Ha!S*4!{8d39vO{yh{6ama52s?gwy^2u^BA z2D!J)B^Z`mf^s_m{vb2s-VJa9cY2t&bIeCm^{`pg$_LA6v#3?Ek^f2Fby#5pIpJu= z!w8ZGcsIZx-UiM|a_GYdlOv6f0SnhHfcmD84RtJP)Apj!go! zLKVqSwiyq~4Z)lMI>Z}QBaHC&xgh*E2)biO6&C~@LQw32kn+1AG@#w3+65t{To9HH z!v(>m?f&3_Qhx;MG#q~f_c{O!Ur2hhlRDq^M{utMkkB?qAJPh|{#JDhhq@A=SRW!T z{J7sk+V!phz~ADJAg@6M-{1J3X*cO$?sDla4 zR62{Hg9(lmorMGw+p!l+_%yTElosAawwA7|GeVz2{R4%+1E?4Nw!MX7?Ij=z$Lk_N%q?mF&Asz$8w? zl=&C)5pYp*!y!@x;C|<1-}CS8X9P#0DhmghU4gdW=hn4+h`!b6Jc;%K56(u5PJ3zp~AyNnO4 z@9m#}!Dn*Y=3sznv#+Of)bYlwlZ;u5BF3x*31e1MCfD1L+t84EA<)K5Yjx(+f# z+TZ7_L)#@BT@PM#HRa76y*nyp(U+iUCU|BS524eP`HV^CHjb{oCp~?S9aZ6$A1HR% zNt*i8biVCzXCJV(iHU9B#7}Z!+gI32z~vL;MN4V+<+jXWhZuP#w^gz7XhR zZbTDxb{Y$>@Mn47N9PcKqJ6-hj^yV6b5=tw)+BD-DSsK=7uI84F1J3X|CGC*uF!Rj z?uN5b`AV&aYnCm^{?J>_& zJZ9ao#84^eRGOz0kvAlhC~fJQo7XSQd2!~|{OQpcy)%g@sxZmPWtdV0ep~r#-I2gz zU>HmBZz29&f;SmmBcV0>wE5-jV}Lq0-;g}B%INqZ&S*XcjgB8oP>+>H#{@jq?85g2 zX;knxEChJdvj>X^=f8^V3D}25H1k9d15nF2dv2Z4@m0LE7Q}!WwH*BqREKAvW=?f@ z20FYrBpsfC4!<=~CQ`45ix={DTK}f_3%HL5vS$J~!p3UQ!+Uw~JLi;}`0xs+#Em7m z(kY3uh05%4Oo+EczIIP9zR6_I(#~}6Q+=#2*Mob8>Gs{fshW@Hwk*x{!$z_cvovn| zD^{Vir!5X_qd9|mWP0$ z8`x}34r~UfW~{4YAoHCjuvCNH>1wtFyLV{_1Qh%j6ThGQ+Q5)!V~f}b*Xh%G$P*f)2U^n@khcR;QCZrW9{cQ9*-Dew3cOUMl@v+~TLFb@Dr_a>Ox<2m z&-lYA7#F;3d zH3aAkk^C6*a6d92F=TgREYiqK8&uUr4A;77xk42p4xve z(>s4UwjuJw7Nh%of_eCfXR9~k`D6}-&-R4B$}}>@-N*uYaooB(JbgVYKd}povDAuw z-sq0L3p2tFU3{W-bwu7yFMf;Ny7(mP>ZrUux%jQxIr|peJ&i^)Ob%PvlSR@$NMA+o zOL{AMult*-`!nuV9iAVaE_YnZ^Z>~i%O&U(KbTzfXm^t5N2DHlIe+>S0Bo-wilm+z z!1+w4xqRhw^4s&F>-jE~H z^#&RlU(3k0_G4rqCC1%ovMFvY2u~-mgpVnRG_+fmTI*mthL6Hv2-r)!JJDJYacgqf z%GM-4o-|_-MzP@JCw91Pp`8fY@o;l9M*(ECs?Bgx#<3_o{UI4c$yciPXmSQvWu7c0 z6{J%un%sHIL7tekAnGmwA)@ROV9}bU%>cKK!OkchY`y*D9(V3gG!AR>kokw0z0??J z**PK*grNPg13i1NRpw-SF?J2J8lz(#4Nf5#D7MV{K-A?Q69M9J%;7N4jau{KU9C<{ z^8%WP&%<74!6IDdFsK(YM_Ll3_cEne{rX>InOhZea9I&^(CRP;m)UKom|M48L`ev@ zzW)@Tena1V?|ljt^tLeOXo5!U|XNc_W{`u0Q{0{%&AN zi>lYm=LSCGw@&|N^lwVl$?#nq&se90T!7B}KOMd2_xwxNrX9)v{2?>Fxvs+KWUSue z+-m`&<5kQKPhg5(!V@ah@q5g_8GLN#%W5s^b3MnLi8&b^y+=uP!^pjcS2aB~kJoH+ zS~l)}1$)zs$;Ywq6c^M|UAxqT&Q8h?mnBJPG z4L_f@$><=%c^KU-$>t}nz(Ik6Gbb3geFE!wqiE^O3E+3@vBWD;RP#7h37vUk)i>~! zxu>i4*uX1j#DO{iC2M{#ushd7ev9CQ5vTGOk>-}G70|T?sxk2Es_TNe4OJe@0yL>D zoK0>|ZpLPh7$qm$TJtzm5)H=z`lUfIM;IA!0${na4HHA1TC0HqW2-#2!W9T`jx^zO z!}zilFD}D6)L~zb#rE(K8~c#j*h|>UD{HEox_rw+`7Ov*l+d?-ro4eTplr?RS3^ZAcx*6llw}yZK-$O6WUAaH#u|EMyXHO=l ziej6$Ld?X}Z?`YPQ{N&GHRJ9cj7bR0eEG82}56%}Qjy-|PoVc&-rx|P#?O?+NcL#1Te{t73JndN!{X76KrF=hqSy%_8~ z?47xx_qA!}gEhNn?j~U42cOjeyupjw(Tu0LShT8rJR z>A7zj;NadZARHKFZN&{hh?#hSX`!g1uO778{t9>ywc{0dbq~t(_fg!tml<>AGd$QY zVe)9N^u@36!r>FX0C;L&+xVpL33s9mVBRO^;eS`1M-1*Jz5ARn5Z2t7wHW)s+-N{feFFol5;IqBR9(?Q=&s!3uiVW{sa{>_(Uq44*I@i)KY zg!uQW6G&%3M4S(9M1gfZM2rQl50K0O7itoD)BP5%-|zrH5iAFWob| zxK9v)kO4>p432I48!yX>tMF zB;f?OM#jo602GS2QnGyyC&6xe+IS^4g^e+#)j@0d640=#wo@2jty@t*j)(#RCU?1> z+)H8a;w(V>8Ka67}n@se50cI3) zW9KVfx!#ZM&l`=qty_N~n~|jug4<@3dF(_4lHFSC)<3H^A#UmZ2o7@`7k>djnDVBk zM^}XTHrDqC9DnU=??OL)r=Sn(_RakG@$Av~I1j5{>_cm!CE)jtmo$mYw-vrurz&ea*lRhwHTO-xv(`Xlt^)+D8 zV2H+}(Rl+VAcZ^M>J@&BRO}OB>q5G5#H~+9K&vuXv0d3O!`e+2JO$fbdgs+(;kHyk zQDAh;!d!Cw7}(q70qn%@aO7hZ66hyRnWz#3Ct+c(ufF_!b2GlnCUVcd3dMCo8u%mn zlm;Hd10gxRvJytl3_8pK)H>dVodV|^uln?7omO|=BO|{CYwJv>2Ompe+&7(n<~pBS zIGq%xgDuzqpzpdMZ|*Vsh*uaI`b@zHoZFXgvta=EHY?;hG&*&Im2egsE%IX`~-9aOTM$&0Q(VNY)Mm7tX^ud*?7Tg7-QPmv&C!-QwkWXehDDmvjMwP(e`cZpR~90zb#x3ac)Qt zz_<#)VSKd5_%zmtEQoL%T)BsH`{x*|Zo|g}xQP#t+nl-4v}f)WwFicWA<{7eOAzb3 z09zl}g8{{muu;4v64}}28nfTH8VwuUD~#<8A^eNrUkv{e_?N;zylSYU(S1jt?N^u} z+|4HhHs-d@X`3aJ;=&u9*8mQU>#*_`g_b?0z4Ld@eC%D^3~0o#SZYM|$-cE%_ni+A zpi?U=AuSMl(kqX~lRXv7)0K<;i;@8UmX!H4G^#*VFABuq2K;NJIE;TSMhZF%1-nP1 zZs73%gSJxKd#rQ{%k&k!Ovb$tE>mxAFN~~Rc;vp#eAIjisFEy3rjSsA*1{e+USZZOS2}oeNJ{tZ@w}c>6)K6-<4Yv1gPiw z^KCDC>(%j59G7Zjw=Krff%st89n4g~0RYru>e@vj;G{1ZMNO>v^eOza~- z;->2f4$eHy7ce>h;%ji22hA>lA@HI%y_o=q2f37)GIPZCyL0Tt9NSLxP3I0AkLdc- zo#uf-TAEkeYh+4Uz)Uj2vpoY z9aTsZE_5J*G$HP%3GD*(Sj+^PO(~4N8}ycB2EhL>dhI6OCp2OW4w`)-qvLY~f4B?H zd`edD7`1R`I16X@?wvru%AN|KADTYciax;icS2wd9I>*|$MypU8sA;PC3IT#may~h zuyRTl^Abm~^M3*T{Jo`Mu0Zdoan*V~+XN}#9e{Wj#kmroUAgJfMku?59!}drICpDReuUdvlwZ22=2WAD>uKE540fxl{XOeh|I$U5UX;DYxOFm4v#$1|RM|zB zp4xWt+OGC*>Tae?$u2N%xmlW9q{|woWX~{e{f3mCrOVzsCHs!9_LtVE!OfeJjdivE zO8<2#hWeOv+=x!l_=og(Pn(*3(YR%&G?dk4O;fYKGj6>^sz0F1PM_Mg7N509dlkXN zvDy2sJ-4g#~aD+HS`u_vqnWG$nhhaqB(O(;77_K0v3H>4w^FiF54;0|T&F&p zq>GkeIq#NASL>2bp%UYY(e|Bca^Yi9=?hZn`+B;^q0$OfX_oG85xQdvm9F-Ax@0j% zNewYt+AT#Nxb&h$Mn|($dYgdAMSvTlGs8-26;7o4{sdN~eeKtK1V|e|6X-+K4Bo#5 zIMNDX;cZM~6NehH?FOTJ4H)HX;T>0nh}NCBOmhQ%fh!9)Vtb$ngD(>P4_+7l$DYzl zgiD|zs^648nL`msYWaC-Tf8#PJLFM{sB!1-dppkWKmO7lJV zjha(Y5;Fs`fHsrB`)bVe)tIPioX#2)!X}%ipk~yZ$nRB)s)qL9j+<}QHFVqi!Jrm? zsp@ZY%H8_KEds&vqCyDug}{Hcuf6Ynz_{v`X%Q2EZw8yE;P`cIH32OoN`sEv0Kn=p`6?s)X zi0TvE>hGA^o@cAsTeZ`a+hkRC3YwgZCKs^|o9g&GYy#SnQLt;a6?E$@mU`%?<8;)6 zFuRcLsteIcJJjpL?Jv33rt?WIw}*&iT)e}9g}Vjd&abNOjdVW56)-!xW+z5|=6 zSJPSbN*pd&bvDvLg;@JuA`PogXgdkx0rIp>#Z*wrW+SIg$>j~Bj_9cNy`&v#9&b}1avU54WDbq^;%uX19~fJ2^VuB7 z^eJ~aey!eP;3wqvoy`FSq(%p8u%C{5Py?GG9j!4szpFkt3cx@uSO>Q2PE`~{WfPC^ z1n49`jeah2TUmwz`yW;56xrm<@yG2F<#A61PI`^1_mH1(o&%wZ;|LG$L)F~gxY7A8 zJhboIf_{z8lkm*0rsDS=avixm^hA_i?J5>k;?U*C{a|+WhIj7Zv+TFg`G0uk_fO+j zwwp#&`$1jGl&YA(6t(U@=BvplQYUOn-)EtCY+qY{FQ`v8d3l%OGNbtAl~dr^#lRfK zFHJ2W{HF0s4)h8Wr{Ft+{jk8{KQiNgkHf&+b6*;hf?M|n9t}q47x1%ZBlQ|a=PFis zD!1)bcx?m$NH}48gR|{rF9jY&gapi(cz^S=&>>un2kC;|73q~tp{~|ray=7`<~6bgL~tkWyLzmz>)ZhT z_{+*g)9(WqRD3Vc=Dx_|54<=LjnpB-#fGMn(a1@IHgfVnjnH+v-fW6(XqtgWrVZN2 zjDZ@Nwy_CnD4d2(;$pP1iAFPcmq29G!88L+F5xse(Y8TQ)Wr4t;`Y-h`i~#YvKQlb z@%M8(D(x#-Vq5aJkUFj0z6$b1``X0afDNi9|0i(^l)A69Rqu>h*QVeyF?sgcZGXvO zc#y`GQX49FZhzm`DGjnzwMQS=enc)(q-I zSZAs;j(J__IZz?MbXdhAn$5HI=GhyY#D=u7NepoiFvU8_qlaTo0lGOPB~dy3{^%(5 z+s6?#s}cPcxRm=-2z$=ki5L^bYolQk%wA!oX71PD&3^cXa|1NEbk(nzy(c@PD@~nK zMf+A@&m0KXL32k*q|nmPy}*PDNO;CMkC@FNE0_dDVH}ldqgf)}&hI-9&oa;}+IMf5 z3-OOX5rjs<;-5Lxns@`38VAGme~{9W}?D616LS{7C*ZNm9c^wK-Ihe!RYUt?7 z{X%oetE#Cd7CT5+?yI`+pHR5-Ni~a?Cvg_v{;_`+Ntp%K)t$v1gr3;{*HD9KX27nWF`eo0r=88~zFm?%a$`M0VOxwu;6XI9b>9 zm>b%k+k*8sUxdSkxu-Wjp?Yuiiz|=rs$GxLin1@y4VW8p&sBAWPYZ0Se$jlS_qky8 zBj$@M&#l}ruPRT~&zj&_6e-)j$$qf!)&)4~=fS)`7mDo*LpSdWWMen)3$}&nXM)>Y zrUf=X1}BZG+?H3@`stu965wO>23%`l4xo++?yL)wbt|JuD#h!q6yO>T8Oami19{U8 z=1WHROVG%ovrYd$ZSMjfRdw}$XOaN|Mki{DQKLj1HAt+9P1{6*1{y-70fG&eT6{`d zOR1$6bw<%*$S{fKco;=RpSFLWYVWqvV%1Xb5&{IgAY#4MOTk;uI3jopmlpDVe|w)Z zNznG~|M|RcJ}~>7eO-I+wbx#2?X}kd?Fz6oj*_9!*%w*8kJVbltu5~BSnP_d{^A&u zby{eQ$vY``Og+~NR?o^C1E(EX)f-v0DfE71)z-)=l=q>onu6%%hoYBni5|;MxQJ5_ zO|C~h5grQ&@jr^CIhOV0e`N)Ht@VbF&`dvcPdEp|LyYP{wq_8mz|9$(+xmSgwDF-3 z{2HRh^(Ow4nOtv9Gs(~swR+M|V{-eg6Z2}{|Q0=pa(-YcMl&co3nXZP@X!n4}~x1Ezqo;^TJ>9cPS-({=g?f7RYH~GY8|2}-r zArp#id4p+!$>0^>aFP*Oy+Am}#tUFHAlMi;|4X6ZRY4Y@{4A^nwE6*{T>xy8M;XAl zS#;8b;w)^(8Q4yE_U$ZWB?humAUj|`ify3fvz0*m*6{a+L$B%V1-=TQ!29hm;n&yh zhLSLvtdl5egfhq(7AhLbX=4w+bmWGiB68&$hUILS2)$ibVDKR%t10(f`c*W&bB3|@ zIFFZ>vqG%f^3KC$FIq=LeRXapgw+;`tX@>YG^ZZ=(f62OY3mjhobWB#stXolx9G$Dh=U&3L0s^;JAf zYPhh!Stf_&RNy_xu893JcZ?3|E-uN{-T8F)I4Z58uM9@T1}vY3{)fHp94UhrI|R9m zVuc1JI|g|)Sj7>}|3Blq)U08I&^JWy@qy4bkguY;PL8WF>85WK@sTm8TM%nV$ z|B0ki!>uIP{tlfnbdDMF}vcXR60`G4Cm*~Dx&9q0`$Y`&imSH`vx87V>=noXl zL5aiS|2o8qDK6`&6D7*=voU|r#5znb+7)?8*v$oo!?e?sB=>Qiu};yzAD>f$4I8F# zFz-QbourLOj;{`x%{9_K63JF(U=x~|{5oi+fEcs9er{5=tz5$YNv-i<4v#9-w;WL#A^Q3 z$TueJ?@RB6mJX~pa_wmv49&`bo-VaLYg32EvuQR@V`Q4ZpQ&t&&Ad1iS&h%8 z(K?6$Nl-m=Ik%q6xisAoIU8siu z_S`(V1!}TM>Cod`uA@wadW{v>N6a8>M(LV&xSqHbK<4Payttc^&H{G$Str;O#gy8Z z6KxF4RzYF9q#yZ(8R@gkcJu%BNHUP=dw|qAAP6nS*QGZjwe!xxv#UJ2G&d;3?zMd6MeadGeyz(ffib=8fW;>}d;ESR1N zc4;*59?+|Q!rt4oI))?Xeuk$n{R1-Cr*HFQiP@FNM~Ut+zs(Y}LZ>R~9f{sR;f!|! z4%$+sa_?M|{~vdTb6x@t$ZdV)nwQ@DTl%seyyU&K>E-mRFGXCP5xw}(0@Ow?&B3>2 z?<3MhlD*{BInn%=l5e8YocY&+=KK-JheUaPX)C$GCNa1T93>do2s8uSiMP?{lW3BAuS0PhiQGG(Xp**rKAtV9bV)A$*^(`n6zw7KQc&{Kd{^LS*+ zq?6iJ~V(!|f`{SJl?=Y~U@ z;W279My76bCWp;qVcpDF%iDcI(#3)L{**zQz7-suq zu8ZC2`C>;x&tcf(o?8&wY#pu@Q22UnOcc8QpI{yjzJByA_pr)g!xbdRYGhIRx-&o`8E4u=ezk_U8+eh*)bOGaPi-TaZ9%I zTfF#XiM2Vgd&Y8mbJ_J2B1chVsw(3@S@IWO#BWKfx+34SgKCcd+KfXXGdj-5{0vVm z@|ldZipHRiSYxak#vo<^jLWy)VyM0jV*vBPAg3YUnOR`UCD)Y-v(Ns%g?+*D`DG(G1Qxy5lYtvfTZ;)K+~Eesj8?^ zwHXl8#iSTa(H-(y=DkjKVPmwdgDWQyGGbg|k@=&%V=y4~x>J^`XEr=xP%psyS9&sf zyogmAxqh1-MMOo?>rMal)QBe)ZjPAuBG(v@TPChB)Wb?D#$FpPleLORlR{uFL83J# zb)_}WEE!=2D0K@}o2T2>GS}hAZS~QUBg@v=*n-II_0dy^HxH*qZa7(>f(SKWq_URr zP^5>^k!8jm2UZp)jdG`%G#Mukj`I_$JvwIm=BP}ktur~@$(lA%3l;A|8opx=l7J~Q+`l9GR5O2;2LJvNe|x} zi*@XebjZ*}GoWSX>z~_~(_Lp5GtR^n3S%Ry)-qE?V<1OA-rbjH6AY*wTNfQtxoIp+ zPUL^OoTp*jI)AOfLdO?bb_Gw#r)B$@?}Sa8@nyn%rp~w^6pGgK2{DeUol0UwznlE? zNOr4$z?+Af=OZ_K3l74}$}$~RnsK>d8czaK_!?O@-o_V2ZulZk1`kOniYzPRHFHNky@LSvg@Thj6zuzNj&L&U@U25puGV|{R&rJ3t?o1^&H*FKT=;CU%c zYnc2oPs;;_4@W+a$)_aL2&#Pml6+cknwxZn*WEp0ha&Ai;a9UU1~g=7!(&j8ty=56 zKc*mxA8%svLu*M7n>u!{SD{$VCku?JBKv^>*}H>*IE_a3!$_r%5=d{sk=@C|fEo&H zlLysMkWMK7-^t$wxS!1b2@ng?v&h)KHFkyI1-t5;5VL-APW(!P8$6#1EX!3Jbip9v z;eor8vYgm=D5iZIBqi(UocMQxVpue`f7KQf9 z735P|>`U2zE~XN$F0{_mqOIIfxhZpa&Bu|Z_qaBmTw5s05jzzb)V;JIgbcsvOEo=_ zX?qZ2i>`iAHTP$WzXZFrzg5RHr9HF-sxHz_Q|L73!*qURtb|5A0)RUYrOk4*@`EA@yMC1UTku`L_wql}vhA>Su{IelY^x+`k)fu@Q(`;K7bSu>wJ@|Xuc3$uI~!wliTn|B zb3qa41RkJt`X5pXmO?&rhmbJMONph$5UE;;b3(&QF;tVIz|)g=BIU#B6#Wf zQWEuu1o~AaGL+a6N#btS@RCY(P0@3zo1%*&9bX_zHG>f8DCeni%~>tcR<21f7K1z3 zK1y}5lUrm1eP?&=9F<4#jIPnPbnO_$-re%detM*NT|lULUC3y}*wGjH@mlJNmldFR zdx>ULTOlyLnReMW!rM}FJy{m;KTP^UO%7p9lSR0Qa8S`>RaApH-fmq$06a~!u#ncm zg#WhqE39Eapk;CP|8c9oQZdtNAwV@I)@g+q#jD2qoXSnrEzu>BTXip^JpF_~oLs#7 znUnIpl?6;~elgGxh7}TD;h?wKon|3IN@Ab)?&tKC(e7)GISuoP8AFGw9Mqw#FsMdP zT>-%7Y^WkJB*?;q@1_xujcgfUGnQ-jGH@fjZYu&? zPb4deuf9=nwL3LmQb~$*GukvFsiHy>$Y}B0SvoPUQn%8lk*z*D4uuxZ zWg@Gd3dqGc7R?c=aNCpiXl2KN%63R7qdAU_m(d5aw9f{mn&LOHl&8d(X=uh( zZsKKX^IZbu%w^gaW9@5n9~&=|dIx7#iYPs3<+8S~V9mt^ksIbvlF8&i735hZg1T;wSsQvEg%&1#3^|16_1*^5#EI@j|7}z@ z@?cOwUnEXeDP(9F?P^Ye32Lb`{$+#T;vC4LnIM$8hiHS=gHIc@d$f3c+_@GYiX^Sv zI}u>WSe{&0@8kCJ3~K7Tj7(GrqDq3chb+<}PZ0Gmgs36xu%@1)ll@U*(!8~Uq6!qX ziYWK;LZh*LlaC;b;w z^JD%?p;GQKFM3-168iU{8n2`|A*VNtZMk=mX{E%rQfGEh$#%$dE6A(KxQFp@)*3Y$ z%eqm*U>ixu$cI4++@suSVF$mejfGwext@`bf+pXOH%VVcf6d z?4+;O0%7ErOas91mw0b5*rts5)100OTCiH!B|!lLNoLLcphsWk0sk=n7hp`Hd&VvP zU@<}PR8s{0htN3RgeO)6&_@gC=0a&rhB=(<8+#LTQm@?Wl^goXX=v)U8PsPQJD*<~W7>Un1?^gn2;~b3cJUv0x_sY3HsZGLgr;hJ3%0v6J#6H=L=cnFV8Kv5Zbp&W8;H zCTZ|S)ns4YVXEn$$dR-R8SZ?a$Gl1TKAdAYjlbbFMa?Q0JDr-g^30ObX;ILZ-VnqV z`v}F)G4YlcG(`Ao7HF}4IPRiR1OB9f>{o+Y+$*GUX$;_55(YPFLwlL*=9;G?sa3YY zI#q1fawsQr3N&Xd1hNl=VbWXB-&esX)k@fuUzFNN`xTVqt(FnHlcM77$f;o#ERj{i zLS1=Ni*UYVyxOCnvp}=jfJTP2Rf%_WaBU2qL3j=^-eX1A`!f((b++-aS`t~cD|CQb z@1Rz9Nm1wk#p_%;gz6=+qA{mJ8g-pMf`b5WMdWf_GxBfp?w&&g}>Ip>#-<{zs$r0*iG(^Ug-@Wo_`s>~sUf zYF$YB*FZFfLeCi}IuB!F2LiEJRSyKB{8Of^NmC}Dx?n~f(^^)E5m)6_6!BmfYHeq4 zXG`>~ab^x1PdBk*zSei`5Y}@4E-$s1qo|#s9=%3ZbEMVGMXE+~@3SZVo=T_czn{lK zDC>GlWhzE{=4wr}^C)fKXLZgBbxzAo_T;g=qUevCnDz2dd|CQoIK5P|$e;Cpyg6vr zS#fhgSFOAY6+#y7P=MB0j(kxY*GCtmFqw2LVM^c12?@(?He6N5ljJmN(>YQJ-mTu- zb>h^Dh?jb=y>9l<(zJ|0XksA+)Kp9%W2%K~_w7Y?-(H+r3@>h%a60ZE#uNiGh2$E5 z`~DF}-c=Z8c{7U8pemt3?-CkIC9m}Rc_f9>$I-0Aq0;kjm>OmWHGGa5QpsvRt4C`3 z1M$NaB5eRsDl>{6drXr3WwR{y#%Y0c@4OyrPcPXbWJNE-9ob1Zte_2B#K>@2J(c{C z-$;^1Oq9P4xWLmmbzrXAB4_>`thcy;vVB)46m2bT9qQ zLgyP1!gXn|HIxs$3KCpg8@&^L*RC|Xf{H&+>Mpm)N=Q0#ETj!m&c=#O)Fw~5YPv*{4-cYiTu=1nxb zo*JZnkm{k>`G7@syz{hHQ!$UDsQLnTeiiXEn%sp|ol~c{jquY-oj%o{tv$7Uu~O&# zdur1L&WtqiLgPqUN zooaPs<}?d?+ulCs{n&BIO}T?WFZ4k-XqMc*e;}Gsksb7{u|fkw#KJJG^WdRA#sKi7 zYY@@!lXT}|cWRY;Wj%`g*Kcs9*C+RfV$;o0^Vt!^nb1G@s7EG}?7agZVX$&G84bE4SOk zw8ZbsJYYxqNlS(1c*hwCo^}ib3#m88bV|9i81mgNIxdPNuZix(C2+nSpVQ-_5?I#` z38*?cI+9`U*t$H@?kVjoU@zs&mmHl*MYQIRA*N<$WZHg?&5$&hR2?pLo~_jHDyGc6 zxL^zk9PLhf&Yd46s@T;@Y`In$Ddjw?`Rlha>Z5lm))~!jNr+3iwt&8+P#oIt7b+R8 z!t=`&IQvzD1nnHbFkjZE_5PXS~t6^o-Bvg~cDp$f@Kme|I?<8rvVc(T|M`h^3)ofu^Pj zi_<5variHaH1MatMI@@&XfT3+Y@k#6cf+8!LDclmq5h%$qlNdS0?B+uReG57Y^9~N zphd4SuXnDLT-=DZ5=@g?Pm4k;LAoh}kT0-ZrcYH-&gW+q_>BjxWgDrcRbGs=PiUn7 z6nc{MU)bgY-22&U!v3#Z3+};6fOj!g!6I;QvHT18zB)z#MTI?FtOCSpP*cawwdM;U zby)uaX+0YtL8Bx@xTydNLNf-IZDDCCwOn9OfqGSHdIc$K!UshP&$^XcvE$_>(*9e9 zs)puZTTlc>RyCA!EJb#0K_%#~Yh#C#&_qIod>58{YP*xfnc*nLw#zuvf&O;OZM&Zt z&~EJW?ksKFZBOPp6Y76^84Dy{z+Uf4wj7(w_N6I_S$SQIRO8c<^mYHjs$!r!#aTPH z!v@;%T)yt$vU8qO+05EtI*tGZ6W!}N48>f?RKysHMf7s97Wo;#BG&O#E!v9=DR_f1 za<4R`;6)xqFjgoR1miU&ooy(=#ZoQ^pUXrM&Nsz%q#*?7sJLc-WZ8b@=CEH%nv11H zE>~7_vT$j^=mRzHMV9?WS)_`uz_fx9s8){6iu^8>k>iHn>jf4`wi5GI2{r+d&gT?6 zzF_p88ZWXe!3(=UZ|hm~u9omjkf4G&Dj0+iMN#kW7X~iUVCi0jMkW~P0J#EWv-?OH zCp|z{o|aoo9EEJOVp`>Zf;3l~zMN!0tajc@H`>Rt^ggXriLBMsX;w@nY%!^ps09rY z`LLQV5jS)FA$Z5tp0E>i_U5oW}UJQMmLM`d^S4gmmtE1SY6WyDV{yx-D0?Ro> zdO7YmaZQ7BFsp*1c$QamkH&QJuw1vHWN;qCc5gtpjQ&p*vdr9OaY-YL-EgcnMR<=TynE=!-&}7137|ti(egeMrt{c%_Yb zUMiLL=(|J;3A=b5yDa)9ud&I(-7b2JV;OV9C(?1R|JHBg>EG~A*89`7l-Ylj;l#u? zu}+{EHK)WVxtZgB3^m}JwLNOxN437_KVst$S+y*>g%TiCaaTH*K%_w@n zc<{Gh@>WD;WRbVhfR7+ps4rC%_QjvXq3XcqsVbaWhJ7TJL$Wwq_8#BZXW=xr4~in4 zO~9OLk8V*9X{mC}1G>|uW%l7+JvN@=IYBkcqJL4&711}9YlQ`TMO2HKH+7D*SL~aB z9vGEq$M?RqmE#?#OPqU-xV0JpLC8Tz}(ur_t z1&+9RG`A^=yxlzqb?HiwG7i-~3(_+c%*I z1xg6$#iBosG+k(quA`#94ee2hUJ5t(p-#utojMB8oq7WP>G2MQ_VJhgV}ILs62qI{ z_DbHE6_qfwM)aG7#4MPJg1<~`(y7j%=}sjV2pg3zx$*m!jJ(5EaZt7_TDO0joi(x7 zc)Ga!iVO8rd1h<@A$~4m7p9Vz`^fz(Q;GHdYG)-_7`lGvPt?GjBXaE&%|Ui`Tu24! z*O9>!d*w1AmY>)wM+x!UjAQWh8>CpO()GNEz-3D;RY9mY(j9tdIDV)*(-@)8lo9$+ zOIX~Qq6>#Ru)H(2A~1*oEqR~px$&?8oFeqZc<>9$!KiX zyV2?QG#Jw8Hej(6gPMidJ}G@ZU5%+_2Iwh-#LqFHlA*yK>jHrOOx3gQDW%(_6Y6|VR;{aeixDAin0 zEsuK3*OOoUw5EMjQ;(qR<0^s0JLlDhgEjfv<|ky51?4#|F7k%e{hYQRn7AyG=cLeofF5 zDQ}Y1xv@AKkKqL~JtP7Ahut8=4(u0JBB#`$FlI)}}@bZ|XME9TYw#RM)h@I!= zC->&Go(ouQ)qU|VwJi$u#m8|xxG1+Tc2wqbZHvNv@zdKD4e5)YiUwHG@#9g_)I1)4 zGnKr_UyDbplUvgb>?*Nzw*F!;iH~X(5g?aJ2raVWDMseT;ckID(J}&EbO%xK4HMH*K}c62X9dQ zm0$#jDGO?|e0L&WKCADkt5+pVWRo8@E3{CxSQuxgv?9b|>ClLE05iwI_^*nlNzbQ;4`h5Y51exxzxm{>9g6~qTYx{>GQ;}JD(3msXR8a58D}9 zQjB6xf=k%1!i>>%EG*?qyq!~sBru}4-Ar_DtXMfg8h_i2dO45VoRS0<7;Xj6eN+`& zm-!te-C#i-A3nBjfS8l71DB4$|BFz^;1>|;7<>+)EH31pF07vUoj!*JtmAf-0}XA# z0zIfCm&R;S86PmEhB7asw@M3or6dO&UE%kNS+riPb^Y65mH=$usO9Mo*rum^B$;d` zG_rd9@LEo7*3$FZaBO0b?L<~Dtr(G8t&yA)v~~)u9Sp&lka(r1b)+rKTGXg6yXRue zLBF}ts0GKG`6(P7T_^(?%1_uM#vaPWQ2@+7>*zCRu7<(<*6fOn7+af&f8>-WGCNtZ zsfBs;q;UiHV?$lAbY)`c%KG9PU1~tqu1HUSDA91_&y9_2DTs*0PS5Ta?3%AOZSy5A z^4Rtf3AJ|0sk+M>l{cXC38g}_HU84nIMddsy;B#4MJe>X zQY|Q%#MBdNudO9CCNY}XV1@(Qy^ej*n8cis%$MmEei&31zYAK#cuvqRU3bvtx#U3# z?MfP*o_}Hh;ScW2BjdY#ebjs=(&!YplIDbk*qBJ8y{hSo4d+`C_ToDh-G?}PMpK!8 z556m{E0y>abq-W+c6PO|iJzdeqk^dXjc%;;xLX!K-g#jRIzBWB;mE|E%ze(DlOvZ9V*E9 zIM`J@R`ZP*8)|0)JeK1!=nPWiXlCjhBudV~`A!BESW0!EEj9JZkO>D7Oz{;p0;_CA` zU`4pen^b@zh`ht8uVn1>@Z#~9*mrD-{~7E&bL`CU;?u`Y&0k#R7LFZ>zf?NRxR=Jb zm$?}e&9X~>4)gOl)T?c;M{tnL>BWZ34eFT@8g#sGKIv+Dl1mHCSom39ty^O^Q|@-! zr5p#AW@l2^nU&A^I?WGLKip{rCZ2#}Z^q0aIE>VxJwuJdq&4wF=v8x1UAPUB4m0P! z=5Nzjk&^T;gL7wK?975l`!1Fx9f(Gz?n=KY3Z_L_{JSFI%4Om9GhaOlap=Ds|FtKlAqKHTvyH*`jc>iGL6Wzb39+0;X{|3E_j_{hPpk~9>!`=3KcQN>A z=Jx2Q6H=9rRzd|9t3Xmi$v6&kbD)l2>-H)*&1Kj8!25}aRo5(KN+^?0XPUfaru5lL zxXVmL$2NvsDkkp64NZlY3Rbt{O(NBk-$2JT^}1Z)-lD8H)NxXJz=QSnp{ar!&F8>M z9ypvR5J+Te?uWk;p>V!i>zImond^ptvy>{q$YL)C9?xXaRU$r~9?Ygthi}#DyIZA( zo*G7_*xU9;v)g$a#o?*ygUk5>NIue(wMx26yUm5vzL2^sbh>l<;a!li6U~}oyARXZ z3cLBk0~GKEb08e5W!8kdb%a9gdP3oLBcZn9Cc;|6W(Ps%XRP|&s)k-?%iVAnWT1Pvm@gkA9<{wPj|U#uXoWE zHP2st8G8CYY{lMJtL1;pyVJC)yLPdo&5D(PYG$&ekF!*fx7aA$1fA?VT%2S%#EE4N z##Ix`ok<1Orpz7zQ$tv{&?RiSElUrbDuEZ=q9W7nC^@k2 zJ0VB$*ED(-NM=Kp40SaV!m*2ZC{BfMGF63_x-)W|LU#rxeqnb;K2`zli~`Oa-5J~^ zC{!5`O6zGte(Gk!2ym#FZBvWOWHFq;xv*CEL&KR++ia@6?G9djmn35LN*HMG1gIz2 zru!0fN0h%sv+^;9o3aHpMy5^tPoYUcveR0UIEtSkaN##J)C$u|2#n!Um{Tb0PWM_ARd zW6^kan4eH-&HAgq0KeN8D@(QfJ}0!5Jw8=5-5udXw7E^=fH&WnX3nkH>e<<56()T@ zO;qqzDzMBvC{*iM%6<%x2YEixt^JS+dsutFxA$ZJR&-{iL9asZ+hAX!`zZAbQp6}n zgY|-or_ODHym!&rHJh(K%k5M@%;EQ`cntdc&{Wl*L+`tn6gp?Rm+;whlmf+k2;IGe z&z={!=axE?f!kUX9K3)p%7XN&H!lm)Gd@ULk*X5i?ce*~gA^4=WdQn`79o|Xh%uXmf?ya7U#NuG$XZU(FrxXu0D>hWxWj* zyCyH85o7t4+JQ*#p<;V~cHsJ_WvxduRNgGIWOfC7P!AKE=)O|z>I>}mnr#qi8jH>Y8_Hr3QP=@AJm(ZD-HC$~EKAH=hw3reIY3I7tPF1!J!fm=E$lCo*Miuot-( z6*}H0i+KO8h&ew;^>Mw?j<2?uQ8TkT_B&@6p3HFYRA19v9ckY}Q`pwkY>w}cP}aa z2a~sI2BwM~ntC69X5a3=j)DE=G`kk3O}K6ft%biWZZ$-bZMFnZEPB`6y-kB~{f+z@ z`UsIOV(+D4bM8)DR^K4nc_$2w*aKbXmAGelvQ%;e&fgpe*N@Nv`n1C){U^OmCGX+x zhl+nSD4za~<-_s&i($n1VzXcJzVv~QkLh!XFqH2PAWfos?tF`os-odxsQ}qXi!6gv zwAA)AZ@EE2I1jcS3Ni>2dIorI zG;9#2=4O8v?jMKcK;e%A&GppPF}^v0ey^_4zF94w^&kO*@UZvI_k#s@x1j+Q-e&lp zM0eYF=-*;C8qBsNM@g~`$P><3vRf zYLblL&T1w=lYuZWN$hi}TW@Udcoq8^6%+qsO~S15#Ooo{Wr=tZ-)9YY~{RwtJuy*n1bo{@VxD zI_6*1dU1cPsDSNO`%PQnJKmF`cU_JuL!n_gda=`Po5v{gR9w>>jt|QmL4Y5%xpGvMAi{;C}#6(+4tM9k&m@0pIi6EFeQrp|XR z1U#8qiV1kgkmSDDT$rvAscX$B4vmfMAH`Pc4fbZ#BQ;*^efp8F5q;w~5S1OVp>0>A zlpWL4VDygeq*BtKGD3mOEVnzK8EukT+0Ahf8-Nq=}FcZ4XE~6mBE}#5Mk^I>A;oG4WF9QTO`x)IvBCF6D7Em3XDE5}EWF~qX zc90_^y$w?yv^UgM=Z;^1Xy9DAfOd`a=X1Z=H8xl30&@?=rpWfrHX*YhRU?_vPXmx% zK4*iD&76G{dq4jb?Ln4)LZ}upx22K~pL*aR^6*cDkIJoFV+J5+{DMrE_dy1{-kSa% zqhn|G>mX^O`|xiXw_yeiQ^~D9odJ%h1Gawq7c{0DgM2Ah=RgL9m;aWQ(QMhb*H`h@ zz;M~uJb}ls{DYCSv+Q$tyFiLW_s_raNkp=rs{xssEJe?ae~CWN9Xyq>W~R_^(4%zK z#s2^qAYS@&Gi9f`ORyS@zoF%9xT=11+=ad&8;wC!Hp3@}ZN16L?zHY5Lj~@&h*|5M zX`p{|F3>NIw)78_W}-qEz#v)eAevtt5B&~IfrpWW+`t3hIZ*L@yX$a5Qlv3{s*9$; zx;TzxlYzDlWhz_=^IIBO#mDDYF(08VTntiisti7wdNNb-m=GQwpsUB{V+bu(;gDM* z*-*7I20@Tax7oLGyrD16wwFrkMym34-$!S9`cpIjiFdWez0ZQTiSBJ*M~!@!K@`EU z!kjue?2V)ER!I%RI`O2FgfHeieJ&)g#}}Kmu59dInYW%v7VmW6fP&c_CJ44u54Cj4 z634Y_{BoL+H3*7F4B=R`C+I7-@M}^F+wEWGU+p%N_F9$DahTZ6dS<{Jh#E+y8W~D4_nxstp4BDpL#H zi?kbSvK7Yc1c=+MiHSVrgsCmZHu@0RE7v5}X=IJAPyB32a&IVBs8b=2gOQeN3XI<` zr4%K%M^abr=d$?a+md^S#a>SC&5u2wHkXI!`tr2Y+JW(2r2Smp#>ucEcFZ_q!*S#| z8F%2SUG6j2gwm&z!Kmr%M+w4FLFWDX_snoVNU3DblNKxf=XZ8)V|m#W>q@*5>E+a4uI9uAykdFt3 zEB+zX#?=g^e)#KHPk_b--TTq(9w;w3bSLK&w4gziz608Ky@CbH;L|x=a}uV z8Ok%ee@i@${z4{)TbenD{pe!_Whn_pCdhe=M6r(+cDlt!Wy$xJ0x+l8lc_=oL+2B})K4I7%d)qjI$;rG%><|uLoRLI6BPD() z^SEVv=Dry)J|*5(a030u`38mxSBIzpKX;khPhIz}1}lFcmsxDux11u@>ef|F96us3 zL)`A(T)AoYdZd_~$O>HkaM1XydzD-fKk1}VXMQHRsnBUGH(Z$==)M;+e@t%QNn|D+ zFrJUR5pWBY2EaGI`Y#4)Fw;3;kS=2t*+H@c!RJgEh#hVnUo^4tb}+t@ZK((BMgQ@C z-O})bS}F-zDjnQX`t_hs7nqhJ|JNslxyFY_pI{gGC(s zFUDjq+=x5#La`qbJKULbnV-XxC;mee>x}<~B{s5)+dN+) z%PwVUTp+&iEgn-%@H17M)d{`MdjA6WGYtu+5Tbp`9+1k_* z>NXcMvbr`P0o~(t0JyGOi_fn9k#wv#$ zFDe0`VJ;|gWKY9|bUG)Q4+Gr-cDW66?L$AtT*GE2PFu-64Rgs2BVb>>OaEp#>L-op z`UZ2)_kU*`gezaCoi7EXE?(m{UdWt{LWYq=kz6xH3B?0zW9U0?fx*sHa+|-g4x@QL zD$vn=1cThKoAP$N7N|$o0Y{nlH3P??7LJN6$^8a!ObpZ`@v*Ef(NV27@ftZ+k5g>* zI3>gi8*)-|xYPn@4BP4dBS^}0F*JbWqJBt-_r8^dk@#4YI>8 z1lgNDC40XV+&_v@*k*HZ)}JWu31Vo(@{ex=U3(mLfbJGsuNJAc9Cl@}ro@>z$8DIy z2E%ub&uquP zfM_q@l*as{f~I-awV?o~yp@I$$Kk4566@4}k@5QbHwQI|p-Tf&y=eh(nI|k~`Y@6+ z{C_h*>7dTC(9^ZmVEIvga!y`O&N%&8xelbY_9n8^GJC&oE*Ce|oT_TrC?KQ#%;g14 z<)<>Y4;lT9tU26;Z8Z|X0o@e#goWrT%NI#8se5EK z2jZ`zxY)s7U(P0GB+R33FyiD(kFb)>)YLQ-McNy9b0?LbtP_ZF`iZokNEF*kIn!6- zJqeZs@>mN)ceIv2>Vs?*<79MJ7W zluy_7L$eapSf2hXsZfKe%1t#hizN;{mHv$i?DCGG5py-pKp#%`Qr_25Hi2-7?gLE+ zA?QVn^5iT#P$xblv0XFcg7_~a{;tQ$-kPUlC5hJ$aI7)>aj-x6`o!eEA@;JlcN1;s z2=O4zijkCW&7p4plh*MrZp5|m?qw^0Yf+p z*lK&%w#8@e?x|xYv}-F!estxR6tB% z=`!BdX)5Qf;OQPct-PJ5JM0LoW{*sC&j+AEBQXD8j6eutpB;g4%#Og}Tr3Jkpq3Hn zS7K=O2pV9Na5~yh{ia#~>;^O#5qM)!w+D{L~ z5-xZ?2Dnnm)1|0mgj3o3Ui&o0H>HxF`$Wn%QjFg>f5L@@=s#?4N`>9@s4Ux?*r47F zAkROd9)EK2Vmg}+U2L>P8E0zLX)VM11YL<_yW$cx6;s7xT#=fJ_UL}sUdCE6|pyzb>eb{<|XgPB2is4)s8U$CC>@jv0| zIYJ@!&^j>wbOzoxyDpYy~GafCf!>_a9^>;o-0>@REh4lxha@k6(_l! zCKFbJJ#Bi@pSye|zf>}W%}P8NOori5K4jw1{|v5RxosuxS75gAPpQC1a&8}QnmYN3#HNR>9)qm)UC65gX=M(=y}%~CgW_}N;)Q6|54bj?H1ZIML8H=DIQ|O z{3UH$ur+7arjnl*HnIi({3lGo%c81N8OmOqj|f=gWAuZ| zRR_i><_d97;^`bVkhV8>_colKAF1CXe0|O=La9#?qWxov0SD_(}lk2&iNkn}s;F8P`` zJor2S@WwL0*~0xBFSq77j;z*PwAJYp^F-W6r&F8~PmemC;_P|4&*?0(Prq|IrEVhb4yRL?$J4D&r>>juw9M($ z{dAs|I-RBF$!zsbXVkvT*BEq`*_W@GmvZ|u)x1cnP`So9n07j=>`RqNX|^x&-{N#u z+m~a^ORas8$5^Mc(Z1yHvbzgwq`r8W(^+RD^-aL!-rOrrcRK5B%u9;V!u!e-6lTq* zlE3uvrUqU={eUSEj%~JS>-@Cil&0@oTePb6ZUV{J|Hd;r3-A-p2b@yLgpVX`w}1oY zV7d;*j?3(R{CwgiWSmB$51t7toc^4i zW|EqgL-7Y^>1mXnX6xxQdituK4$;&3dJ5_3YkJzvoILnB)v`lRcee4Qi_s7MR!^F3 zfAsFOxP@$rgK-8ieRF?Wn^4U}who9C%9+UT4u}-$naGv_kwQTedCq`Hp`eL8Z9t^3 z&_wD><*B6Sqt~N|q~?S+m2~{cxl2(yk)sU7OTWdF<#~!h#YFexXB&dnFP}+W^!ebt zAU+2ECO0-VAf(H+b6DE#+%pICbC%BK*Get@N}?~)rnW?#<#1ugx6nAj#Add7rFifF z`!@Lu!VRELqI-M*hzuh9BShc8rZ;weYU#VQ%vWxk4L}!m^hsTq!^O2414rz$MltGB z_?uRc#(DPICXAyDbFbZUFgrNb$J~?_2olu)EBCKKxI^ z>~a5V+~9$Xw2Qb76!iv3AS3%L)F$yA_EztvHglqH&TYKsmg)sFL90Vir+s@7bEXk& z%B@%bC!2Er@tdt@C8MKezm5bpc44)(OtXY!fI-v;OT>qZ`8`ql{~NsW!D$S{8Wvbf zkY|XIGGvo7wqm9h-|jZRv&M$HGhq|eyq6i`;jUr+t z5tibXd!?XZqI>IDrpvd7v z&(*rXl8Qa{k5F%@H**3vmEHO?3u*jFO~%Yt8lQn#(3xm(sNg+3M#o<^Tv<`nt0uK) zBD9RPI#C9i#+vCw6I*Iy>w?$<6I-WPz$mWw;p)(K3%^j}E`5_Ea$n$I7}?bNIlKUI zVbysqGt{*=$-?FkuM1>^Sx;`LuX$t}g_dmZuRuWmOvuWp^t7o;{q?kMGa+!ArgS1n zQzMo8vTSa}>Q8wbM);-$+V>0(qv${f@2|%3H)?u6tQk1H9b^-dEmr6o+od*FXUL0OmH){Yw=bc=moSaQ54F3^;qQ4dCJIJvVlQHg_{;cTl~lwSPZ2 zT;QR`gNZr}xb3dA)U;Qn_WHGky>VZ{rq$ko`YtNX0a^N%i{Sg~PJ4i&era0N7UW~b zsix|ezUU9PEQ0-gCe}>PNBDb8D0Wo;p74Fx_pIE>p!LQ+>_L)MYY(>Zi|G0~)2{oy zrk`?+FT;7Sw#)2s>&-;}9&IAuzo2DdA+$zVn8*2tcREyYKwPfkjut%)Ae-p=R0nIL z(~L?Wc9PK(eCS@cMgVMrBKU*+Otw_SU3#|?5j-^dTl(koL<7oSzFR-hj@oWk-W=8`}!W!u*uY68q3ssuxOuonZOJC#h6n2`Z-?NzrD=q_FW&O z)BA^G?7Q(F+Pgf`KA(gEGT!cI?4`JJ_b#y+m3^5bAgb=lhjiswCn9fxKdePD)P~}? z6SV}q%Mci0d7kz6y}lU*5)uAPX2;6sD@+aeDJbk|6R|UB*w&(ezn-?)z+s>E5P*op zNyM{H%B~DOZ3chMlSDlI^|VQ_(@xaWCc#dBJ#7;E^wQHN5l??TZ4&Y9Q)%`onthUR zXHzBF*(Zs0_DQ_8UV7TZfolqS+QeF0sKig(R58)EL|yi&KKmruM}Hy_cu!yww-aL# z%DsOZDN>C5n$TS+Mg6&{`Kg-=Z`Z?u)Xi`Gj~*6!Z&Spm1E%#GFpE35l`6EWq>_*O?EEXz zhYT=t_I}9Ll1g^^B>$0vlS^1a{dK025Bl}Az`j_MPxGy_dwpCxOt^NR#kJnpF@3ms zSQE&K`I$SvNaS1jmg$aO{Pk*q^s3y z_T8Tnm%f^sy_PF|gY)!H)DUS)l_uX1spJs@s&P#<@ds4R5BzFoDXYHY7tn}TIz<() zuY^Y68!ey_k@g`Vqj*Un(bPRI*C0{GPQ!&gK+f&Og?%pm#5?1~%?WLcJLq&K8Ge3qp{tJr5*%(GjMNvpZUH5*$}&Si+0>okb19Arw% z`|NqpO@QKP^iFCS$ATJV@0I*Tyc)>hA)!sN*2E=ud_%nkcp2;f5V5 zH|$WP^91#ZtNA3za;w?Q8ndvBsb!oldi-CbPCmt2o7?zLGa1OA=mBp@|sr27K4!_z_ z@y9a138r;FRM}4J7r}c!^Iy&S3Dk@uOco>a2rV-|PzfU`S&nESBb4Yq@eFVcE|L8U zZ{67~_el4ijlw3*R09^|c`J7Z;!dS0{>Ep`c2@6|AkH{FfF~^VUW@a#J^*$LYXntI z36!UPS9XuBahJcSCbq`T5T^qWby;SV4d)nXX>V0MEST%5LF|%M(h31g_5xNLZflwQ zt;#*$&plV|C6CtPW8vo}eepZ<7&~{Pw+Nv)(R~_#SFY))4;KRiwn^dSo{~#Pn2Im;Pii{Y73 z?&WfMAYW$r+A+-{VdRP|+1Z{I8#MWLS5I`GECBFBLU&{UVQet+E(Xwd&0{C1l{5}a z{MN^r)_0cM){V9TM)i!gb8=#*rIO*z`z3?uGa`60v2J8=A)$H!X?;Blh$$Yh zwJ(bBb={(x+5ed8eM7Do=CPT+7T+kQ+^aLqe=x>$z78I)Ufo)^5De8E^IQxg$8$;Q;*h4n-?0oD2t6NQ zGj@6+tB2NpBUV`Z&Dhx5`LVeHPAB`0jNEW2iwEasC2$J*A|ulxmk*83;-y7S`-5Un z24(-4JY+8>``tFXyjUoEJ=y<4_RJlh21tbfS$}X!azmcz#F0weklddaxnX60{WC;m z=w@=wNLF5>v!JU$pN;4$?0PFa{9~m!?Z1t&OQmH7;{#Rd7cxLSytIw#fGet8Lu@Yf zRGyj#^t7BT?SP(sop@P9AT)tk4r{1ds-7pZ(I@%QCn);9Z1mZF^aw?dqa91Jn!F_} z!9;iEX-uFDnim^)+!vY~%k%Axmd3O}s!!+>1X>Yc@VUS##KcKlgZ zZq=s*bwjT_Q(0RYJE68bHlHowyyU(k<-av@?OKvM>x+uHlVY;M(Zz~v=|gMJia)_8 znl}*_e^=BTvS&2&0r~qg<|cs!6*We#6%#;h<lS=4-F39bW`#-1ncD|g-4&i1aA z@$80hp+0x*?{C4rSq4uLeB!3uTLaUW=)RP;gLYXpby^qAv~5`1ucj7?@??SfVgS?^ z0BWCO+$)uM+eNYhh)(-g5Y-p<4FIWfN9Kb7tQ#)}tT&AmTy9``?UX?P(+r@moNRW5X6*u7*fgx@AwQ`! ztM9HEk?ww)W@~j7Wtod2!YJ>>gk^*}v?>=mJvHC3OlBxUdsLySeqU7^N64#$cEnsR zvS*I+en|{N@>yA?(amL4;9OiN3pL(a^@cqaHA_qpD`gp9=2Vo#dx~8j+>32>2i`D|fYi%uYfP@MzGD0H(~ zd1_Z7&82ni%40r{%WQ0c_amBpQ1lcd^=F)&AN;{MD2?Jt5XQ62dB(-c>w`b=T<%_8 zL=NuEJCE;{vnuwy^3j+|=@TK~EDO8T`^op|X%BNNaj1;8WdB^iVX5efTDIC-!~DD^ zBETX30wZmhrSm@QUZVRKCxJM^1;)1s4;bdcgm0p&D`B9bcOw{`l;SL)LaR;D`4sIj zlR*E_G`#pd5P(C3)V;6pq_Mk`{w6nYHTs`0UhnaVFrVUWLlh8(#;_j9iI*~@hIXcs zx4(hffP$kI3}m+lDUnjs`;xFp$qj|i6B2RuB()&&3D*3K%>XZts@pwt1&S6{{Os*@z-$iE6fYg&r zYG4yx;*VCX*9q&F=sxR2L$l>$vvV&kdv`$_b#hPd>_j@N3r zcTOnBJ!))j+)GXhhcFm{AMY0#@BS!QkDI6kbZ>_)c?ssrx{ZF4*ax=r1H$3mp9;a8 za&I|JGi>EfuCRIy>Fz{Pf?{`~B}i!AgvAprMVe?S(nLd%QaG+NJ4?A2Wof7y+=1cH zM?G+s3U=paJsTgZ-XG=yznaRz?cF01O!auq^0&Ed$W7M&g5Ncx8Jg>-t~IIYT;{@G z(3CFsTy_!FoKA}bLC24|P_-C4V-P&wD_o>;Ut1c^@or~QlX6*TCC^^T%H4O8LIuWh z#Ar{;+}L%c9lckc;x=7Z-LYwr9GO~Iv0f|dr#5u-n%!IGo!qy?3ZkN_wv!9Z4l+O^ ze~W2gk{XE704Kjj#eRm{@@R9%)+=YYD;^b5=-9gGBqqdn2yhy;{OHG(TSjjgms7_P z;_VPgqjOL8jC5NL6qEbC{@f*uN_+i*67bE>)}OY8q46WlQXgu-xqp=Huj%Uu3;8u* zf?3+2I7-794#9=j-TrHyo1-^;8%jhoA4t=P1q+tYay!1oZgO8=tQelH$c^2|KrT8} z8DSwM42m3u4Q0oPU6>cV*x{76@W0jysvEgYx#(487ZXTNaH;aYX_e(=TQ$}EzqESa zjb;*h7acYb&dig^wIkiwGf1<>gF{$ywK~8D=$?^-pn}ZrEE=t13KHGlINr`Od*7jW zVbyV{e6eP~guVYix{Y=qAjz*>l*K9azQy|^=$h#MmRgTR=c|BrHdbi!P-xKU zkk87kwIK|;SotkSS?d9X1 zM8uB9hb9%b?F+>}R>|oUns@*7p&9wuUg!f@;p_suDr)#V&>8kE3UmfGU46+ss4X$^ z?rjW}cL!@J(S4qvdpN3tBzM^9x~hh>7Pw0Tofla;t}JTHj+e?g#Vkow&WpZ22`+NnMAiq+YYEBj08AVif!+D{o= zvJ)6+KU&--dF=|2D<~dm*IlvxD(+HO3dd5#&-9dS+&X55p$4m-G;BFvz7mpxdS78? zEUzuZGu!*QEK>71wz41tudh$hr_`B9f z1HD}fEik2XseZN~cn7X0eYY7~*-0#0zK#BNY`Ss@kIT(tr1M6S+m;;4C$D#JO}w7d z#?A&U)IOvdl8B}0iwD%8gCJ^{>8@C=8*=F6JEtl6v1x_Q=B@*yD&HUdeqwJq;G_C9+b&6xtE-Sn>@I#R}qoYBR>Jz!L_Ycwm1CR`XT!S{wv$B zK9TLk9( zIBQcAw$u*4{4=QukJS#jJd&Dxd)qs+L;#0G8rF8FCTx-+e7^S%+;5`$wxi&Zp7?S| zo=fHwT2yypxo$2TK-eo;X3{*1B6noz=l^v%0QTCTC& zaE;s*3Ao0w_f}nlyUPy*MlWEvH&^h^C?9Ef$2h#>H{l&8@Hw3LhsO86b@n!L7Cc{r zPP5IXH_W`s_q}=DGgGND?J}=v^BN6a|H`Y{+e+y3pt!#0dn#gQ<3G#%#^p6BI5H`V zFXi4!Fe%afUq>>8#t&Ge^F_8K?`gO#xtDr^7x!KRy3UABa4)Z-!{%h|uTw%fV~56f zB)M&;BN=AvEIR{ zLt&()Q@=Q$D}9kv{plw5JF1q0@9{b=GNRXsqlVn4^#w! zF7_%+1|5YIyHiWKeRix>K}bL>@s2R5QB*W02kjkVrXbS(59**HUYUYOyCi*TKg4a- zS2o*U)$1l+r{az=(#|;|+_P z0Y37LF`>~EpG9>+Lj3PuquFh;TB-$Jp((z$kmC16+ZZbxgS9JoINC`NS-rM$$JmDa zt4|ND$!jQ>$A_9vn&;?ioq-@D=G|=2b7xEXXZlOH3WJv1YSGA9rYtz{B#D?%D%A;g^SI-8{3#WwTSyB z1Axm7fa_+BTj1hb1d$4K3FSDJBR zxFja4Z^vlNddbp8#MlYN0LaR<0CsK5%bQRVdF-iBe&n$iLe1d;u!aE1JJ!G%e@ZYa z!mkEB1fwS*rbN)r1M{oZ)>*+g-fPr4zBu@NKzbHh?XsFajxS=B_RT{3CxEyC6Goyv6lK+GyMafyyn!n_4mR$X-oPT5` znw+KNG&J#F<*Z{(nw*78{??NejQYch2eO`Wc*tm`Cf7k;*p09K$$b^MT`BYGqtRfbuVId4s=y+ELqL&bcliG{ z13`EfE|{wccCdVQboWvY5Un3u7KcOCI^(?i>$_k-FautJ7IY@=wql-_qH%< zFg|B@7CP_ntw$b5b~cCj99XI)dUR?*<3Cf4$Foi0xUxL5dc>OlhqkwYucEl#$8+HV z7l>@os8>xT>eWU~HL+0>i8f#of{iuR@S;YgEmEpcQ@zn>BP8yPWL*}~pi-+9tuLr3 zs3;&P2|z;RH*B2jBxSq{7Rv!NExNs%2OrSQ?a38ieEWHa>GtSb@hjPGrH? z1!y|Sv;#clJtv;2&Tn~5tbcldGtLPGIN9`r_QP=Sq+y>PjNqO0#Gh$z1~0Hj?dt4A ztM8+|S$ILf9*$pZR(+a33x<|7j!4qmdgS4&^oh%$6dt{b1#5&-3|wx6oN(m@9%0-^ z!SPqx)z@5w8t~>osw$GvVb!<3*$+o2v_PBiGfnw0UVK@2viV9bLbA4zHz0Zn8W09y zIH5NLP2?p%7(F&`N%d~rO6&|$^I_HQe8qX`#8IKM6!#>C8}+RtkY1OKLLDzstO5+hq&sDW2m|w0-FcXSF0*n3EgO9#{5$4i)10!6xmuMC5x62~13KaQ z)_cfBRAe3{E5?K9?2_jMa&fY>v6C0i1rbzNb>W1_W++4SqK3*Lm`w_{xmEk74L!Bj zWT+gH8DGf0NisSC8QD7^*mtiZF#jKI-mlrXmTv{$8u8&{{^{w~zL5&Qx~h zt8f<~=luK_iL^e7Jy@f0#nI3xQnh(p;wVmWwzh#d0^DAf^tUEOULMnP(ZUAI{>YyH&=yEg=%)&0kyQH+96N2jg zD{KLqQwb;bnsD6<(bsC?~6@*^{Ku?C@(|=y|6q^!!4|omgi7(d~{uPL(1o3o75i7jUT1 zI1cA4aqVqMcbT0fnQiHrP{KaQ4Ay!w>Lz3GgwUNQk=F$9YBS3U zv3kHaxH3lk7Azg%edUKJ4(OV~O4Mk5>jdTkQ-BFPg}ori;ZJ5!8rLj}FzKwh(X7Uq z_!Caa#_SF?ZI*#X-2* zKJa*X??z9;V3YfpI1j-(2XBA;f(g$lrihMd1;szNVd0wte3Sj)se7$di@z0j=PqQl znZS2}We0D1BE!#%ur=fb&6ZMfC&IR*({HB56_g<5~{8&Fm|AchITtW2_>ElLv{r4BygsJ3{}2i$~IwMQ}@B>O=$+BOJ9xVQTfQ zrzDnWIBwN)OB65K!iu(>%Io13e6)i!pY1~G=giP$^zVs!O85AN#wpo>(?R!@V4 z+udW?s;yXc_jY4%4mhsB*E;Ad8hP;%R&DnzO{vb>voyW>Lb7%Gf9%ch_5b7dV@kE# zILntOnldYo{LUQ88+E)G_uuFd?*mm8c2@crqbTE@ndBb&uF# zGmb8#M-m_B***ElPh#84YCHlp!W|GO>U||F))=nMyCykSvN=j+gSXE1`N+mCSzjD3 zRh$k-+mAhtnonCJBgXnS3Q=)5(Q`?l`f7D>O!ZYp92-1$J^$TXbc@*F`Mmx)Hh4Za zuB{mRp7D1m#VRg@RK>Xz3O_EAzrX4daB3;cxHw(fxytUjac^9h)czN6+uo!75!MFY z!l0&c{`2=z8(ACqWx~#l6_FIX9yeuqdIg>m9i!yGcB8%V?GNL(UH;L=jbD8j|HtZ& zHf}I$Z~VR!$tc5< z*eQdY>h_S&!A9#_E6CNkBMS>x_-Y;J4IzL{B>3b6J)sQb@JcX^oz8R_?N?{GII(#c zZ)1R$+SUhcuCuK-|GGULh9PO~-%Op_irKBox70pM^4;<;$#<;vd=VW!wlEu*W@e%O z;5}rKKPO){a^^!{JheI(Po7$xgC|d|=4TfnJY};q<_NsQ*q(!47ML3P6gul2;JX1X z0zkwxAm6BBSR3Rj0Fp^At!4%JFSU z6aYhc{HVlkp^@ZQWyr-{rLrg?y=wc{R73r?MRZ&lm$C-_u@9P96j=+z(nT4x8?ZhJ zJ)rW*Tu8I@QBKczdLidN5afEZsQ^7EcLPfRiC}k`c0XuA79DtuG_|GQ!laNIW*Dp^ zvs#oHS=6CHTlzgJRx+Ie;{lZzD@#uUehwKLcKuQcEk(gyVU8@H{0dQ+1^6Px?OqIf zt8eYx+mk~R*=p5QHc>2k@yiBxRrav+XrGwIPo0WwTWV3Dukct19!QaHAjQ{Ewos{d zVPiT|-}~*mOfYlA$&I^fm9RM*P|WXJrgNR zeLus+<4v6Q%Rr^6kCs3c-u#mOZk}=AESR;rHqi|IwQr>~IrbGIO|p_Ejn;sor=zc? z3L;6^R)S{+^5Qd-Pkca>M*ZH7&xIV9SyO;Wnc?7>P9i+T{_spl%t0#jQef6?0Ts2= z_a44rq^Hg4THjd;O`!kl=m`UIzck&7&JR0-n=vprLSFG(3+JIecRlUCvzCz%CZqC> z+Pr%PGB8tcl{wbLk|BrkWCcO74<4|QY(7YuyrH3JzqY$$2=X6l3e#WMkXbhaKXL6- zt9=?>S<9E-Nu36*`{E&}whV!02_z%Xhe(QbUvEKDt@}nwAWhb2oL7-w!R+c=XOIn7 zHbp`-h+=j9`jJLz#C{7Fr6RDijN+Jhb%x^)C~t}rL0D8b9mH+Ig@|&LV#f`V(YZ=q z+#@#w(LBc#>MkGd7^CY@FBjVWq=iGZ^iQ2O@}~l*n~nz;@bYu<{onxu;=-eSFXNUO zlgO8o@Rf|ifBaQaw-re1X)BQRN?%)r=lu)~2p{3hn*nlO<*vnA7%CR`c{Vxo1*jI2 zT}Gpl*(YIVQXlEfQGnl}Sv!fbXTLZ!pWimaZ{XtGs2oM_{|pQDC|B&BL8pW(U+|jG z($hZ{w-PjUzInpVIB>N%0Bd$T0Jot$1n22u7<)UK`3;;67nm%4EfB5HmV4ysn0Sbi za+t5eAK1I-3R>oT>zYkTKQS8|H^b(xEW;U~{~cWjzJDMozVm~%s(qZ`c?2rVSY}t! z?eObN41&eb=@>au$2Z$74_gvP!*28l<8xu1X4eSo)5yA7CW&T|sCos8EMJk}wU>46 zZgG(b3(wXN9N<1k|8(c6E8%&M)4$xgtj{%)EgG>kOup}U^;|H46b{c@9iG**?1eB+ zkJyv(T9l^!UNO?msFV8E`*(|WFx*o8=k7eurFv`244@|cEpj^DR?7&L7!5HC=i3@+ zelI3FE%P!70GZyO2pZ&<5=dx}SBVH3x%eAoiLXKCpnyMUkhpFXZFM-x;xhc3_rOw1|Ep8IE2On|xv8!-*Gzkw zFfUfj3!tkKF3xWM?t}Wb0%(b79$1dhV)vml&|WLztgqYwfMa_9r5v8hUjk@lcF_bkg^zm@_T4^Uko#L`HAuc z-}^ilW0MhUH$tsS!Ee$BL-|s1r~pe}?8v+HpoD+NnlD_8!}e)kEZm=yCJS0&CeH}% zEQj_EA1!0$BR^+UdI62xU4ljauEV|ce36SQyA78~fu3{n$OO0eD<~`N7ovMr)cc?+ zx)3dpHo;{n6ffLZ+*@AHdkK0Rl;ChD!FDHsAxj3)N*Phgpa$DS_+t6)-Jp=8>g64X z5jWQV{Y&eHKwjI;ySOtYuPs{0g5q=zs4Q)>s)<|BU&Z%HS&nctlY|^ec$*M*IOs(r zTn)l{d|c9lTUpaEDc~1k(nk6nB=(i(h$-G=zBq$|Pe?ZI46zT|#mXp>9`tUS23~q; zuZ{G&&j>V(!v_B#0{zs<`X=zAKws>mOO?kTB*Ae^K%eg>DzWrq zk3&zvA@_K8CdHV5{6PXZTz<}xL{cpMED2x{`)3l!z^ool!2K+aQd#;Cr{?q687?s- z2YIFUvDH+8@>qHqDD1w_CJGdr2?uL6l}f}_jM#~4W5m4@Q6>@BGh)3HajQg>OT-s1B}p|h!-VdyhNmaiU{6Bz>+^85tR~= z%?OOMB!b(`tfnf7Nc#*COPz?{Nkmj4@)^OiWytCxiKvl?3P$iI0!Cy@#4L%ZWyI}H zL|7u8l!zx8F~*6IeE>~!Bw{%uhB*;mzyhqMnG*3UihG6=@vcNnmxyglaikN$&lRku z84{8DPegDN38ngrL_8u9m1M>3(~P)FBI+b!0wY#PMDzx$sZqdOvUyd3qphaN0!}C8 z323%HQrtqMHGYU0|qrH>)c-w3ERe2Ayrh(FoQIDaZW07Z%4 zj-RSo9S&o!Z(X|8KfFO(yuvq>ivP?TFZt#+r*Li~rs?ZwJEc7+Kn*_V1ZIN4!GkMz z8goBphdBRM>}4nz z-^ZV0x`%>i(sjz{M#e(YadtMbIbsBGG=@~E+U!+<4^C~vFk^+=ndu$hMz4U=lj)DR zFn?k;SsWTY9fj#^Vxt2ECM0!GvvPM`6O#RBv#edCJ@pO-{Uodt8^h6m$9Y~cK95Vn za0V|UxR@>jn~C_FuEqF;2tox~!zpw$7~sgtfQ`8}2RK}0;qutGW>t!b&wR_$%@TYA zj4KgSjjD8`CSq2F%^KX;o?+sWM11~Rf}2z_jG9axi%tQ(nYZYqCC2V|! zd16|eo1@2Zr1>%ehMkSCZpwNz%FeB_U}ISi*~QgZhAJuR>A<}j%dk-aB#mXg^cHaL z&05J@ee=@z4KBB_4mV1!%|r5eHKB1vO>i8(X~S=%sOg8_z45!B@W5to;jxCY#ZN|Z zDu5?1L8vt-co^#fCO&W;A2Ld?Mv!cdPc_CTqwrXk(woD1>u>uQ1q#dNNM&OA_L&$y z)wh27C3>f2;9CZ1)CY;3O>D4lK+SBbjf>e-3%zB$PHE?icp4EHl5p#4Io_}xrp-Gl zuqJPb*1)mKYI~f~8@E@W9n#kKXQ7|wa#V#xKc&L+AmUxX|# zN(8+U^Mrlu!WCWwPDK~x~E8p24&4@qgb~ZH=UMs|ihu(5M}bk?43@$_ zg=gGN9Ih6HoYf{Rrb8WQ*&(B7Gsx8%L(yhtD|O;;a&++SD%B-zMO)CVQBh8sE#0J% zveC;oG(+f`W7TxVDh`q4<}NMu!}zMcbrM`AEUAl;Q^~nAmPx3B-3D3?)>wX zYf$7iF*$=ftfIdfhi+x~xcW{`L@y`hq=n*}JK?p<@O!E{Z-r;Rjng|rTI^>CDF!1} z*)Iw|a|FH-0=Mdy`O(=4kY$`P0d7?0LZO4isuFFE>}`zLLh0TGJ4}Io&{hsgRYQ|# zV}dLy6YZ(DGcBW4?zF$bY=yCl!LCyVfIn#b92NSN%f|l-+O@PwN39&fkj*Zzkv8nM zS|^?(55@-^wX@d!={eeAyoPu`&H)eRFpN48QT8j?Tji`oU~Y5==SBw9x%PCnvQ^mA z2+G)l;0`7UGo&667-lBk%e4g#oG|QhR>@eGQ=_%FosU3zU9^&qdaYqNx|jLdPishKVhxGgTgW~os^Jl;(;9Yx9xr&GpWlm-PtK=r zY=3k7yV|@DmE5()PY$})bT89 zFw$^K<|H_^+XN9O!Q($n(1EiUewah4lc36%z|yy)hpW4DZBUE-6r)E=|49ArZ8d$5 zkXE!Lt^tQ6_7mXht_&ojR$M8SUqeG>)9q~HsGrr2w|p?$Etv4f52JquhcVz_=`$QT zZ1h9%bD?Nu(8KnLK^td+*cn)&dS(F}g!NG`{Mi*#JhkPXU9!}<608xN2r*bCh^ zeA&+4xQlMLll2XxTcqe%C2b)6p~6ZWuD>AllrHK&cS~L6P!A+ECn-+tUEeMBBM$XH z(aprqA~nP)=$3S;LpqeOycwU_> z^0yDc0*cgB(j<%+I(k$OaosrE85=m<6Kisn)MU+xE`1R6gNsvdIZnMD=xo{s0=**` zeX(s)49WPj6n3F zgUVHnd*%`~HRzCj6mRt(T`b@1Jgjq13mgLuPk*Wf4=tjQzTs+ZVq1Yt;zaVv(f)IY-du;cHDOefTIWpheWO^kqoL*_#$CAcrnu`$bS; zH5)fLn$v`Z@d3VSu0AIAcJxRtZdbqg2K$27`G=o7e`(ATGK4V+BwJ%xaNO#fWO4AX zU}oNwY}}a2JrK)LiCEQQN7xgDTxIvaY1^B@FFS_yZj41%g1LFLF)gy3fsv_J^5KEE zjmdKC0)(9plqExOjuQm!A_Qr^lnjSAusvW$MkaFZYzm_~JDZlHCdBMwAG0H7#jPY% zEMGGrnZmXRhWUaPTMat!l!ohX8Xm0;T6PAlics1k2!H5hw8k?);%|UfS*N}arx2VA z98RXX845|ZR8_D`xAcxL;Ps_hD=X^|!ICMMq4IOT#}Eq;LDMlAoF;0){HJSL#Io*p zWF7y1%X$|I==TjGYe1Xy9dLTo=B(!Wr8HIb8gDxe=2-{FEju<}=TIizMn8`6*eL0A z-S-3^V2tfcNP=@f;P$!?!UPVAK3dX$NKLT+`(b+S0Y?Yv5JW)`{|#(|tUr{f=bWg^ z8O8S*jB1vsRwwELM)71xMm;4_JDpDHBu1q;QTIqx%t;ku)VJv0nd&Bq%6Fo+XCmq! zPE@%>jd7yZFp3KobYF$`@H5%e|FvG+SDEYev=}WP6`Dh9UWQFM0sP24aioChz!;h4 zJf*+E{5%K7g4N?MZ84h^u?Iydn-Q>OT*bKWKRTP5l}W^2XM3cLECpcc{UgrO^!yW? zh3H%fV2ye?yP9I;l7A8dSeiaTkg%nnC&)o-)P=G}JrKiCsXsb?tWndGyKB^x!O~9w zg}qdbgkox$Sjm#Zi07RMwM@)*Z0Q#=;(jMWEfcdfW$!<-1UOw2yh(kC>}W0hQGy#3h-G5-wU+)e!Bz!gnV25i z(w7i?QGr+{P80A$f`1lJYuEt)U^n9jw^PvA+&y&p_E#`A?0;E{riJA*bJAECxydW= zoKaJ6EUVDHa?H)S5O$)=bt&|(@1S62L6tc^$jvBkIIRc|2$j;64n8*1u4P(u63%W0 zWLD0z|I~d5+(}oCSe~5hd20$lIr`#h%BrgstZUgE?0NTOA_d;ANE}efx9PZ}wt6HI zNVh_9cVQxy-HDI?xCKReC-$a(ij$C;{{a0G3Jni>8C)#hx8txtwcsYJWZ?KXIOf^R zEBc_HryKuwY$lE04nL@tCoAo#r%}5@{|@b{FNV$6MZxDHbvFtgg_){4Tk<{jgHDpW zY^8}DJUH0vN1e-_shlo0K7HFMWW&9Q+zSb@w_<)e7@kjSc!`?(p)Sj)m3%514zdDC zGw~daC(i|9#YFHd!!sSvQart_k4WcL7|P9|v1l3<$}N)Wq#cN`4P?SbEE~O8XM2Y54Gn~{qB?x_$97MgnHHC=mR)9mAg+p1Nv*7-qp zrx_bmQz>TS9f4YeaWx7&P;U$Aodwk&wG}}b>}qspV>QGI*&qCrVod@gykLP#(Y?cP z7D~(w5>uWK^SH!ZCNZOZF=2a>#N;8yDP(6;g%IGiGqMZY|4kT=P>eGji4rydLn2miXH%uGXkq(0NtmV*MjX))Fl+*DoLoRg#TTbS) z7KU%WD@se$Zev2u|HRa*zV!h`_~XhvIU%Pfgh9DtaOg5m_2ryv|3MNSrxK2Z0YqRa zWAIGJlm3|gmj0L~N`Fifr9Ys6>KH z5RS()1JD0$#?yTnqy3*m-R7x`yKfVZ_*4?MM@oXwp%TnUNN}1$4-`RSLtmLdxDNx2`qo+95w1!gTrGrpcZ6#b2xki672OeTNFbajgxQKv z6=G8Y!PP>L(jBMG34~`0;g`#O>e!M%kSYYr6hWdo>;%Ggj0fslA610iH;8S%1R3`0 zlAuy0C`5z!@jd2FVdOT1KMJFh{AJkf%PDM^2|*7@X$FVHF6Z>4p?55guhn_yVqLKmmt@UNrH1!g8zSgZ%8s$PCsn-myF+7){QQb zeG-Q4&wfH(OjCr1)>gzyzb*u9K72?;uQJuya6w{TLDxa%Ny# zB<8{H@)+x55w;f!;SK&g#`_3z?T3Y+R1u6t|M0&X&Q?lBZllbWjQ-iujasS_Bnk=P z>x%Gy;~t_3k~|ZpS}*TTk{X{R8TJGr%u<9u&e&>&fos<13xgfaK5tOx%OPy13Bh}c z;Lt`EOGx<7qgjRrRKo7PL!&Q2hW)Z67^@QexUx)6D9ay(!AbtIO!eh}{$B`s97?2V z2?+*Cg4K(C+L@j}aNsDhe?@R;?L3l@@GVJr+mA^&BO&2rNqB)u*u7exOh|C0BslEH znegsFFx(yCrUb(Cg>c70Ukl%yK#(Q`?c&a-> zJArV85Y~1_xXnkHYdtVE8p#tENu6YgbP$cH7@Az z5oFk345TuqD}uuRXim3Z7%7_z*^dgNOTehJDaq(sHxApn#GLJyAt(f4X9V(FAxKpO zi8U8WAj}iO_64GbM8aerVTPR|gi93RkL#gQgn`@JzMn$1)GG!`Z>hc`vXpLSPD?6n{I1D+wx2hCBt}Trg25xVp3xl&2gTnvF#Z>qhh3)f%(Y9B8 zEn=AA_*{&;C@(1ByYS?bP~;5Vl_z!piOl>wJW{_I*Nl zp&}fQ8vozFW{63Ks(-}$FUfGtE24w{H{~|^bdzDX^`mZXSBy(glK=c2z+@l8uzjmA z%uozf$1>HYyRbb%2>K|3#M+paK$t0nAHVEt4bu|{`U}AuiXf5GBMF3`^<^Qa`8mx< zAZQVSTNFX}F7nBQ1XCr!&_gAdnULTbNieWGS!X2>o+E_zOH%6YMVXV3pqC_=rxGZI z&rKlM5@P8eR0M}^^79iCJ}(KcI+Q4j5)#}Y2{I3rpgAGIa7pm5Ifp3h(u4$INw7jC zNUZYZ34}ZPu+WcpN4PS9aG4O+C_<&aRS5)73BjmC$+$KlK~xf)(p}aYe1sYHFNN^? z**)=X3SMh!i*a%WKG!S3M^XG{G8L`y|K0pw#Ho+4B=wKIX>U%goP7ZgGF zO=ovPf)$dW*3Ypcf#6voxbjdO_a`K%kp!ptIVRoHwK$$@j}n5uAYh$WVDHUXRCNT; zF?gnnfLDm>Hiti+S09FHfvo3VKEP1_heY4F?_z!(4A7{C|*+!(-3 zFJ%T_{HKTvCC=2UkI1lK)*(un>JvI_uj@&*G#)BJI3dAPl3m=b> zDq;6cFDoIzC6XX;s029)367TptDZYVMdv0Y*d7r3KU9KzH-Y+?WTB+EfhqI?L}7p4 zl)fRfUL1z(0u3E~u+>g4#Jg7hYtzfTP`MWx?S(45&=@Z?)(egILX}>q$_qukP>mO= z^+I)CDCUJ4z0hPYG}Q}D^Fq_T&?8=Gh8KF$3(fRGv%Jt8FErN+&G$l!yil_jTIz+C zd!dzHXq6XQ>xDLWp-o0am&FEqmoJ?VvJdZAff zXpR?}>xJffp+#P(*$XZ8Ld(6-N-wm^3$67+8@$jaFSOYUZSg|37ux29c6y=RUZ}$h z?e{`S7&kcf=Y>LEDA^08c%f7;6!t=CUMSrQMZ8dk7s~WPSzai|3*~yDd@oesg$lh; zi5DvMLSUTCZr8t;WFy-<}Gih7|MFO=`)QR`uKUMS{;8okhD zFErH)<$7GEdDwI>^oSSA@#toF*ppsprWcy!g|fU9b3AOW7n<*d7I~p&FO=z}UFu=W zz0gW8w8{&u^+FrG&?Yam*$Zv)Lbey$=7n~8q1|4n!wc>ALLAaMw(EsBmQ)xAi7LdQ znhIrjCE{pIVH{?u5XVs}6!GXd6jE5ahjEmnFb-Q(h~p3y;s8U1IC4-S4hd9`88uj#vUd;0SOrgDu+YT8 z0yoynAVI#5$H55=&kw}jb8@a#`_wgZzHiT-PD62axPGig6WqAprnBX!5baxqXrn4b zD^wv`mkQBVRERoOA!<>Ds5^$Nrb2}99X__|+t04THhdYlxNwZ{{Q{Sh&Y`}a@Sg-8 z?Sr2nJXYWeA3TTf#R8ABk))s`~l%X0+08>pA!xVT%5&rsp%3bAylePl> zhrm%EJb-Ykz%@QNo$xGyYklxJgdY&N&IcD0o*;0{2VX(>_X0Qi;7Y<}0#EkAejj?UgZB}>PT;vdxOY3?%LJb9gHs9T3%tk&pG5d%ft!8s1v>!`5O}E% z&LZ`9@3QdAeegMiHw(Pd2M;Cuw!o`=@Fj#_7I>`>9!2;s0&noazabnGc#{vlneYt) zZ}!0x2#*waiw~Yc_!k1(KKL($GX&n|gP$WjP~e?DxS8<2l`Q;jAH0_EKLzga!Cw+y zDe!(DypQm!0w-bk;WqBR+W`Mn;Ghpap711rLq0g0aFxKxKKOjXmkXTYgD)XGRNzz} zJeu(70*8I@IKrs{r}^Mp2!H<$3!m4Rqzeq7)z zAKXm%9)WXw@JhnB2%PJKKOtNpaJ~=zig1y@1wME`;WGp-^uhhM13p^d5+8gd;Us}e zeefxS<146>G9R2vc#Xj2KDdzZLV-v7;7bTUDR6}kt{`j)JjMtAf$*&YkM+S(!q*5q z-Ur`FxJ2MeA3TY0w!l?BcpBkj1&;dQCkXcxxW)(1CcN!!7QWU8FDCqfz;!-&HQ_e} zj``qE2tOlmqYvIjc&fmYeeidLZx?u~4-S0=__qR2^T9_G{-waveeh|7a|C|G2j>y~ ziNG^_a4F&50zc`4e?xc&PTn{4nLhYt!W#sh<%1^?ZV`Bn51vl=If3W;;O7W82|V8i zzfQPT;6*;TjqtSsH~ZiAaMffg(Q+@Esgs&Gk?1O(!xLn{gAACOH^94@#!9|2m5jf(5 z%L!`&XZYYN33t53!e{#6YYBfUaF!3QB)mf493LDd{EEQ2KG-1qh`{+i_(8%A0vGt; zX9(XYaG?*LPk5BTB|dl=;R1n6eeef_PZhY#2it@X7r0#D0`}kfUc&!c%EFKK!AE`# zc#FUlKKQ4E-xYX_4?c_VYXXn;!50vIOyKc8xRh|Cz?D9@obVq7uJXZG5xzp;s1Lq@ z@Pz``2%Im)nLs#G;94JS5I$1iIv+fZ@PSqqKIVfTC;X+rjXwA#!m9+H?1PsOULf#P zfpbNY4+uXY@H8K66E+2&?t}LejtcyUz&XO?h;IO2CGZR%93fmR@RL4xFyS)=p6P?j z2!{opC2*GTzLs!6;5k0{7Q$PXP$zSJaE$QZ1)lGNn+Puwc##i&g78xUHw&C8S-wVi zioi>K@G`=;3B23~uO~c4;FUi3Tf)NyUgd-P;b1xYEP>bh;A03MC-4RzoIyA!@FpKT zl<@Wz7Jjo2zJl;Nfw%bJ>j^Iw*!IEogl7u8%?Ce3_&$Mm`rsD{-y!gBAH10G?*#7f z!K(?E3cTM3Zzg=Uz+5=7nv}kG583m-#xXEO`W^%WP+BD_&x zF1A=r8B&);gqH}+^%fT%L-=`tx%lG3w-cTwFjr!%rid`H2;V6%7h|lZbb%iuJWgP) z$GGt8gf9`8i!&~~nsBbbT&ZzkoA3z&bJ@m)dwdJHpTJzhap7YL?^?{lb3w<2&mz1@ zV6N_1P0DMQ5`IhIQcCQ=BMHAKa0y@>#*V)@F5ocxz~|}!5uT`SXKmTjDN8tNu}6-a zb5Ff^_)D(&lupGN1pG7~SDCfpGi9z2W$(dhQ>8eVwgP8U@Gxf{Fv0hi5nLiy&NsWx zK@&W(!#yERj&(3_mohMQxWu$b49q|ta?Z#=Fo<`DB^AR@;tj)a3z%ioz_2xj{PAzsE(foRxO#78NDu^34ztn3ZB`0c=dN2Z9 z&6xQlj^ht(=-h0M3@u1!X>n-JeiUPU%Z^@i0>3Sl7=-CH|i5}p^@vs(>=26v77dGn!~cdE=1$IeH|1yEEnQq z$(Sr_U#MS7E0N<~mc=4tp_m?cUW;cEp5yq$L3TWpjaF_gic}&f^@!Fpde>8yN<{8s zM`0V}#4qtGGNza-KjBw`Qc189BrabI?+M7jQGBo74|8F(iux_0;w z7HDcSa%ZZ+MdP*D9%@g1RN_QMv;*~{R*UsO+f+XyT0=gP>Wd;KxUuW57^HGedb(>eC3H@Dg2B$kV2ADe zSIoxOA$az!Y6f}H!4?`&)MZ#9Kr|A7lY@=J8e04X95`nVi<)JbdDEmmwT6cQv3WQy z#DEPc8OG zb8Mn!?A1o96~^c);1|CKg9sSVw~2`&i&&)632Lv+624Y-I))AfX5q^rC$%U(fYj&~ zfWH)Ct7rt8-P{%n<~!+eRWq)gxOuxdDvS3&EX1*Mmf{XJsR%kPSNe%`$OedM5gW2R z4;o}wF&M9!>J~|C5-!;yr@4wYCt|O^4#l_%n6KgF;H_SKI4}bQZ%O|=Rcp8gK%B=B z%K7T{^{eofZyA(RY8IuScSte5Idw_>zB8+#_$m8vUY&|cGSHAap;RCLE!T=xOO5K3 z_$pFLjhyobsDvF2Zf!v>OxKS0MDZ#3F?iHit>F>)!}v@1Gk8=5etT#WB*`ECj#b!XH4BjxP)$Sf!Ne z@r*8z925MzI3CAArStWUQ$66iXEA=F#$fzzjT!do9$V)M#htF;&j&ePP@UFrbn$T< zkw}3itJdI2(Hq_rK7eo!b+wmc^l*sHwEc!(s7{KvAt5`Koh^D&2H^M&Jda#~#<1Ew z%AEzYhIW({d^$i3eeLFBt~6h{CA6A>XbFrHpLK=0_KF0wM-(zgfv{O{mI3&+m7Hj?NBUWBiT^GtK|T>JTDdph%$ zX$_y!iCr@e5$ox`hSks;urEWYHbJm98LOk*XrEa(e~l(j^aKz<@(oZKD&J-S9Q{JM z{6!~X)qx79{O%;1SY$UI=}dP?5MT#>!Cn1e+()nBYHKKJJw68-MKRRO3JHYu2yU^J z?#gV4F&AH|zrKQ0cOaEnG?qAgf{EVRQVzrJf$OeO1-wECo#Y5s`ZVPp$}w!M#9nRm zrRU5?^-YT;g9vWw2Pz;gDqyw7B4PYAht(pK#*n}4=>Sl6cIsVa&ro0{9>_k)ytoDm z;q4aPDI~rTE(5a6g2K9!g}BJ#RR_LM^Hs+p{-Q`1S0NXCo=$m|;?X4!&a6qwe%qW_ zgIjJMXaUtqrV*(&Ak>{yeue*qZpSh3O|L=u-$TW);Cz-y<^qvI2A6QxWlX~K@l!F- z&EAv!cJ>}tfvM?Na@XiFE8xM~&|1_r)K#oIBp_St&hhLxwA-Guu zy*9FTwy+Zla7A^+k7h%LBH_%W3Zjvc_(^n+Bck9JG=d53;hV37fz%LUR9j$m+K=xL zUxrMD4HP!$Rn+bv+{14FaVJ7YBE%N+CS*#ASD-#|YRc2EtkgFL!AddF>qrRtDe9Dsq&UwadB-3j5ac_;oyNW_Z4ePhub zb$9eY4P6HaQ%YnTJd?>%d3kugeLijDE%wE{g@ds&6jB@1?MvEN<&Z94Yd970nC6XP zkq!IQRZ=abn5&kGr@@*nX|;v`=&TWeI4-Q~l*4%T|s@p1BH^uYOwag3J#8gK7-15+bYDQF$T|YSL`P$ z_GMIN7|TGGHhd9OS3_yQt)Y412k~nBa4u+&gD-Db%K{8y0SY6L zx|)=J(W8V@VIwt?_Fxhhu5{2QyiuM3|Tgs{B*m$KTOtKl!+&wN$$Vi!dHR z7*V;W?!ezN5afcOT)RXaZ%8{zhYL%SPkky;Q@G#^CX-_h<>vWhb7&?WA#-R@G)6xM zXHtXFmH0J%S&CEEJa{s#;drQ?W(aY)a3aK4yr~v7)xsZ|rKqNP!ums~O?yPSeeuuJZ zv#JXBWS?giN6lQbI0LOKm8M-*XcniUk>WITDUw;7iKru3m#oLz=b*ZZGg(+&U7%qV zOG{+rEtsphb;;lNqoJ9_Y%%t$Z(~o0v)7ytcm$T8;fb}`tMw&4_0RTWz(9v#40LCl zwJ*SX=bopftp-Hk9KJ*>N49j*st;HV2Z&!jl!bS(eWTnmSLH(Qnj|^nL zCooT=M}0&9@R^>@m|86VU=Dzw`Ri0@n6;ORSnnnVB;u%QE{WNY6ScMZIjD7sQIx?` z@YQ`7zKah`J&VRkea0?hb%8-A$5@WqhK7A2qYm8CKDZFwQW2c7)_6DyWZlVisSqTf z+~ONpz`8d`mEKk)CBo(3xF-zM$_JC!(l^|N!~H>;Jr82)bkqJSeHPGon+XNpgUEDgiS{UAZiqBLf(d$A~6^f;6{Of!eF)1v20HH*>_l)n4W+WZg# zL6=n5Ru;C1!#r4Wx>t|5gZL3N;jVrr{x5X2y!Id|{wzY4UQY9XL}$H&=G}F-1uh40 zB91vYBFl+LN{nzT<86!x<3CNv^xr69$D5$Mo+B=0cBa_jsS7Zo)E{*+`3puI@r|T` zQXuB@;NCvlUn}{TE4tO}*2*Z%OPmCEP@LXiFjc*BS+$aG?5^sbteRx#!K)(r3uaOM zCbYVYbKC0Py6k)A0HgObw)5(*>JJ=-F;DcAIf3>NX1RLD)zPE!=C)4n_`b7xhZ$>q z1Ob&QtGT@y?*iJDzsiI?+LuY@y^uMYNC6O2*5=`hSmS96xIUHLY@(6vdfr){uRp*x z8vUucNY4We~dz=S&=%*WvVAEKhwG`pH)e(*w=xZW!#y9p-uav?&dQCriELCBIpHJ4nifq~^xFeG{%jR4INk>T0kG zTfqmAAgTT{)WR=VMr)DP2*6m4BjjiHi1rxw)XYFM@T8+w1M_8Ur+{t0Jwe_vvTm*g zz5Vf}dvT3J{Zdwk)Q;X9(mTQ!+JQ5!!*QCz=0#{2Q5=L0YdjrEARcu-Sk=PB>6ST{ zl%*I%e2?O0v=8B=(U?42gi7YnA`N?L-~{QH@q-4ByT%;$L+0Yu(OZE$RRh}OA{v*i zZWK!YL0e%(%lh0ftdF6Ke+w-KeADbS^gIY8I~jyzSEXfGh2n>3kHkyjX;uN4r=(p(}Aj zz2)|dig zJoLJ>@d7;-p~wNGdH_a=SI>pVYg5ic&AL}m>~Jn@iRS<$3-}WZgXZ=`ZJ;KO$C`}M zro-MEUPOn^@}Q}!sWU%vXJtJp2`iB<3Gt}3#P~TXzRHP*2}Dz&n<%UPFX&OoW7l6% z;T;%!K;dHkFt28W`42(Xg9M#XriBaIST$%D2$B%NkhO4gu*-%HJSH|&i9Uk_9BaOS ziZC#;zYh|zB=JW8Kt-&B9;hMOEi^Ty27>|^Lq2{&BPCAG1O8lZ1KTnD|i?6Wt?SP7c?$n<1N8PA!GUJL-%u5HYm`sx+AdD z7`hWNg~rg`kh4@@62Xw7pn8R)5l0IsMh%7qbb#iNpj_|L)2yk2-PB%EcOs2<7Oam! zD;9~-QsMa#n~{Wiz%zoP@tKYm%x9YOJIp6mNjSrD*6<`AsgTnTZ~O6XPJ$uk+FKqK zHv>}Gg*iH5yi!Hi5eDkGa2^;B#TAXBIb0} z`4}xN@j-!>)q8uY)pwXKeF3V2EAXc)dwhN5h!GYwa|q9r|HmF8@%7h zwxpj!2`HA(591Prh%t;i-x{f(QC!Pr!!m}Y7^BjRQK`nTbYmFjh6#)-Ihn>n$sG7k z(^qS&;Nf8I84zp1(O{$YMS&-KNK~41C{68g32U?0!vN`CAtqn<(&wW1yt;`h;FwHaw73~R_Y#WV7Qm>UKKssb z4r?p;i$Di@i5UR9>dPz%R^N}@!Ya4agxt8Z!pTjIOe#@4rwrr&;?Je_VVJMkX|Vv?ncCGPT0=!coB{(g z4~~=R`u>cz203!gVQGQo6usvqVML^w!%|OIeP@a}EX4_mRy?U4%`AsiBa@15Dn6gg zhw%+U*hFiDC4ns-WQj+&?5sayV}dFG)P`A3mNMvW42y-ZH*j>K+9Mpoz=k!Fs^KOK zza(E#yVQcJ5S|*u?zXI_cyA>G*KaYm11tvnC&=Kdh{?C9v0OQu?)8{{vBiLmH0t#z zes*Vk;2ywWVPJUn4rcO*mQKMXtAIO0{X|(m#Ns(ffay_zmq)Dv=|TIdzbm_8aE z4fjkUxpD`SBX!_{Epv4s&>B8cX=C`2t~I>R5tQnCvfsuaypc51T3{FD@mCP{Z(Y}D z@TBXKJmQTcbu+|ESWYLRiZ$JnA#VjIBV?gj+t=ZxAG|0n7m39A#fyC$P_!AK#kR^z zND}kBXFUBNk(?~O1Pz`0q&(eZ`t451l#V#Q5mdGR}$v$ROt+U)DP)9R3DP7CTb9GEqgLt=(M8}NwmhQ2L)EI-s_H0YEn+e)*LWa>PmlVH>B>FmMTG&U#h;N~V2-92$YU%E3^2;jibTWmV#1CgNP^a)7a20sPp71?Fzj<8S zSagl7ZLGKS5nMljhBBY!Yvt&z*=yPgQ*)?Fo%e%h+`JQI5qrQ>ngppQFi%`Dhz47M zCuU-D&0xZXqR&1g>syfpoLxh&b$H-)%2P~#?gOyL?S`#89~oG|#lG82BVd_nPx}Q& zF43Pssge}k1w=^h1vV&JCTK!bA_n93kiU=}7MTwwroaair#i&gWaZ_5P#$P9KS=h` z!z@nqI|q@nwhqqWWsu@s_%YktALXi&zAe*kLe1B=7F{i0Po!b?Q)pfn#!C722`89$ zq~)C(J<%GLoBeisKXmNPxp`H~qXV%blNYbv!S~zh%LimrATO}xCQn4eaY>FCMLi zEJ0F0k|xwV#Qlj_g|Wjmh*O&Apxk`tb1>ZgwaTp29x0iHm|1CL43sw(3v5=|etloy z*2Ar`14xL_NPTIkEP|N5&9Y>8wTL-B#VkuTZ%x(@q(@KB?$i&2qu0P@WY-HOiPR5A zKw?AvXJZxDD`^8?2;pPZDm zv$I!HAbnu`PmmgA&qvvVhAbPW+bw7X_?>|%C6;ZY_2}yJ8#*VPrssyEBcPNFy*WAi zZ44wb+xr{K@I$ZJ9ghBjmA|+8E0i^pM9>`MOt(AlgPLRkSQefVBnYM-9^cCWN)#^* zZVk4#L4EqRAbzGoZDDO*anKkN#AARlB&5xo&@a1r9A1k2Oq+LO>NtGl5HWjOsY!t~ z2DYp$Y5A8X*xs+P??wRu=I-LB349;Wi0F(lc%Jw%7G1y?#jORsbs)WT=PLGfd#5Iu_D z9qmBfu??8|WK@Bffzp&%iZ!wfAt-zISuOAu=VMp~>QewSM~8wrA}AE4LZLN$!&O9) z*}PlkK$vlY3|Uu+$QZvlqHl2IMS<2jK}**i(UU}UEjAy1v^(*$M11VER`Ipu32PZ` zo&|PXK;}OLxkZWpEPcAe`RS)+Gf->|z7pz=^ST!$zTsuYt9cw5pj%-%u&y=bQS3`Y z5vx%2vEWe2=)9Qj49;Pm(6fS0}J?Y&^rRHZx#9yq9X#8czpD zqz~pJJ=U=)v;8JYh#V?7aPE(IY;c+>{4st30rf5gmiL&wFj&D*V=g6Zjt+n^ZcRf2 z*J6tuS;^&H2!SMHwZ$0N$0D>t1p6Vl2tky!84v!WC>eY@qWoNm5+##5#W0FgOF<4j z$V&T(3a)4q2FRtxC<~*MW6c15R2V}eShF&QW^mnSXeJ`Y8y984&{-7xsz5WtuNwS9 zOG8MN1Y5b^Eu%yT~85X;RT&XTmx&e=R z4O}OXG0@dT7*mn}A1VDDg{d&BLU@eH%hF<3fiJ^!H*4Alu$xI9T(w(^l^}w{%oWw` zj5omX&lVbyql*>jLLEIbPn0bMdhTru;Z&*?N^DpL3XbT1fd&p`Q(U(CIy@5Vwc&oL z*BIlu#a){cM3U^z_7UUs31QT4y-K2H^ia2+Pv9}71uN)+CM z9^!DYYJY{rHHW9>m4vGAg@tec3K?oq?h2zU%`Bo@9b*RBi=c9HELG9W2GtTS7lwcF+4TjgK~N zc%`iOfzAzj_{Q%$KdOk7e4}NuU!Eq*6jXX0g)5eEJmg^8LeAyL)_!F=UVg^J&5CHA z*c1(-JG$6UU9>@P`eWG7d1lIBwR293-G>^+QrKKsjlm)~Yp}h)nK~)&8!dJNBKZ<< zIeIHaflejF)wN4a6m*e@i1Bp8B``E9_tmEXD|J%74*cZ1mr}xFvuXU+B^zQGOUwei z!HOPbUX;QOdp`kY-jPaX+^vG_8X*a&V?FuPS!Sn^{yH7gs%EXQ98L4rxuW=N{Eg}@ zL2hvdHHF5G^T^F#TUf*gkcg+>{533x4UYPt;@okaWfG#hH_u;ED5C} zG)F=aYi=Y1p#}KfgcXLb+wh$VjR6x<6h+JeGi#8bqUn_|!^-8aY2rX(Px<_%d?-x5 zfWH=rD>WA3#hIjAiU-n_s9z43Qi9VWWqibB?t=uQNupdUzzY6i+rjHalED-{|MUR2 z-;EF8&)8)AsVsv|$K#ue<)F~+#n)L}l&ZpTl}mnA2JB^jBS(tP#K^pVGYZ$Rrh0&- zFL3(AC(zs0FXb>qMuR6~5}t%%Ong4)!^pp_AO%QLTR|!xK-q=qPJRc*Z}TZ$ydPQWVGd_Pu>IHtNZh8+NB;~6vpO z0bU6{G^7r!=FpH1a2Z97dPN6buYAR$Aan+V8$pB;tb~d=gYelA$JekUj3uOPWye>8 z^rH0pk!gVUX!ASJB6s;|t6JcKQ+MMb;;|M&&~!X)kL-p>l&c7zzfeHB{pCZD?fpQb0(GA;)^r$ zR!mIB^9+10H|6(ePpFv;(DTh^7KHo3VD;|*Ag?jVOBFPBG@EKS>goD=_*E=sB4esW zw!fN1Irz!ZB8yI24;!$}4J5JjLdQCVGonRJZVz>Wo*k+ezITL8Nyqi8od6xz9 zZp)l7>R`RS{x{44@CiipW8~m!3NHn7%-|g6XLYrJjYKEh)m0R?Ra^nB)wd>>CbWuN-TE-jVqhqb{p| zmc1PtzaiJLh|0$ZN3>8ZTA(POLjzp(au-URUTzYduKR5{$R6Tau6(W1o9(1nYy%4> zvK4(PBjM(>80SY~6IyJMFp%sVI+%8jy_K{rvi6aq#ohxEG#LTUBukj4Yx9QieGscW z-)ZdfIwqAfEN8_J)Jf{I2K8Z8KG#CjTdPgr56_Yd{{`w?`+5FmBy9I$<_+W7^%pS~ z)_Clr1MEAuJ0pdf$7qr9c7P;Y0YWURVxVoHGOm=!E@fGI$cY)ElFel@6d%mKhZFK@w=?(tjU=+!4#w z%pChIiM|cds5kiKDzhwVo`uqspk~tXEX4B%$JU_g)yT`~V)?cljg)PRRm^T@31*)D z-^($D(3j@mkR9h+ieF03-v<_um}*P; z_5exT%*2>HLT%&BnxOrw^{DV=_CHyjZ<(*z5Ct2U)7sAkN7Mjilp`<-YGFsW)}U+o ziCJ9L&{_S7FY+7Cbr@OwPujvc5Q6oUuNEX1mfr)Woz+eDoy=v~vckxjN#jt^3^X7- zPEn7DG>;TE4}i(iOh8E`b@RT&+qz7kGYHU`FLS*AsuH+MkW_9y!xB8FWUH#LKmd2`6Ru}{nQJ>k;Eo7W|qwUO~y2W5fWvVQ)Euq^Ro zs}&{==W=FK8Y$sOo&UN5*gT*|fRC^X1&Q3T(+bg6LMg`%U<(Y#w%@qMbj^+s7cuo3}kZsEGCCNLmCfR3I6fCR=E-f3pnO(yobkqp<4}^QIkD_QMBnL*j zI?4yY=lXM$Y;OA+3A0ll=u>S$T!n}Nn%=#+m_U&1PzJwWwek=>RiwfNLCJ zrUT5(0W>QhM}vAomqRVjEwB!K4bbu)iFeL}LL4dgL7&A+0ntAbt zory44zOn`X_F9{(QesU;fKOA4l)C+O@&Gui?*D|NSvYzTduyn4%Y_Ow>Z6()Z*= z%vHnz9}Nt+!#qQNA;tdZ(-`1?RP*6L*tPFU4sex*UEf*3vn3IBjlEBS;;^g#UIhRx zwm@0f^(_nNsMsL&`uxvgy}x&FLqbIQ48zg!nakBOq;0{N7vza7#E~=)M`CpAA|&%%QV|_xSYB#yd-p|Rr6?i${TKxD z3bO9I!CO;dUCUqHb_z$gSug6dQs|LCY==C};*ZO0L*Dt`?f!<*>%6FF+|#L%Z9f{Q zOeOqB`<_VNTiUTHHMDJ4W$QLmUwyyDwf-B(D3AUJUbOd2g$x8nCz*G@Y#&oD8jy98 zc{*dSr-RGMds{e}+tr~F^@V{gvKojrB;Pb7r9vXJY7+C*E9XcVHI!I2N~#8^38~Sq ziQvlVfK<^Rhq8Q#c6=>2ERUTE7MV;-gn{g%lyt|H91pda97`Q1Yog7JukAfQQ9f-? zVlOXMnIes$oi4q@_H5&~IUy@$MI}G?n+CBcvj?yFObgCS!m+#5DB#4fxU@g zy9v5f5pWFMaealix;RXX!l`v_nUbwf9ZoVl(FNh@g_-)i3Zyr24Zo~}6j^c-iPB(c zF|puDgx@U+mdZ4Pb!EMY+jlppB7l~J-%SKd%X82@45|pf@%DuGD0wS#P>yDRSrJ)i zWezGf1xpo?g;wRDa_t676_J5nTP^7NR3%igSWpl)X>gpP5krjVO)TNJz-W*QE~fuR zVuPiM$lzI8n}hz^po+*quN^Ds`uU~7QWL>a@OQR@INExkAw4(-XeA{z3R(^Y*h}q) zv{~uI5FzR^tNgvgq$O5JT<&KzPqI=G%S?GJGi721@ug~vm6DT(l!q9zj?<5rhP%Cz zIPVcr$b$UN4MPjtvV^B+U<@$H8$e%%i4h!#dn|o0VP`PblO@`T1FpU2_w@++J?6p2 zM4AGCQDZs}3KKO(Mgz}K_KT#84FqP}vKECg60N~_biHfLj?PsyK@BInwYSR6n+IA- z%_!d8-`2iH<(dKty$9o2Wglv>?;nmVjRw(7BWsi+>qcTA>n1%iJ$S#++Z<=Uoy=St z2-L884^N~3R@7$0EqQFOd$@?B73D=y&R3tn0CU@w)H{867iNLTb??|6?6H0=IB`={jyx1j>>OY8ah%MR~ z(7t(~AqFbTfU5IAk<7IT3N`^8K}xJl+7Yq#Wdlb zuQjHQZ$Ni2(eA4}TfvqJ^{EL8HdKmlI_VNDvFUqV@UsqhM0@MlSw=3R>t)tGs(n7( zMyL@x-kHm@h2$C>#)f5W-CGjGNXBth(&ey|oJ*#$)Ry#UGO*T0w|@_GI8&L{HS)9r z5hE)zndBLj+5m^lra|*>5}5$UPb-S#>QSfx2@V2V8|2E1o||iB)jRHpJfY6y$qKbr zIGO3BDq=3`;#qAzbGE%4MguJsvUD3Sk$2^yJocA=$RznK@+L_wwpr>8Ou|jJKs>g$ z!6@|K481GK8SR7rAI^8a2q}tOqsYQI@)!v%kwZrixfR1fDc_A#enFAXS|n>pRzR=h z-Ew?c!22C=_ZZk9<*5cz?P}x5m}77Op(n4^&cUdIYVK+*m$f{R<}kZRoJ8t({a#Li zyl#rKNxg=?@;0`~Ai&S^__Phej6+=XLG}KiI`dI?Rj*?wLX{y{LsdcgHN{--`rQ81 z%cw9FRU7@*VX9@{9HoX*7p;r*s+;1F@7f*jma- z{+Oycl)@bg*i^dKIrPpE`)Hk(LGc;;XccBwd(WjRKZSi{A{9)uHHRNda;`UGP7JP} zAvJOKyx_a)}X2i}8o@u%FCsn0gaV9#&96xW9{ zkFHLr!exPoouhqK7sLv$I#^=$!uMw`vI5IX`bb=~W$-OD_ z`zJOuOiY-u-Jq*du;b6J^ww0whV~tZNqf(?#>N79owfh5S>ZbfEK=i6_-yt>i8e`k zjEEG2Rls6~S~Q?63uYYr3X{=?zSU1JGVjUdv+a zO`v5un=uaVm`_f(Wr3^_K=PCjyuk&{v#YJN@ICsKhAvxDm+F$ z+0S^!n(W_=HOO$XLtEq7uhEfMLqLD=UXHhzGb`%HmBOVWpl=-y;{VFyCl)W`i}(#Z zg6mcM3b954^$&|`;1>bhySJl5$d;3d>llT`TU8WCk|Cxx?D`)`QW>(WQMX?7eLz}@ z+{z^O_euv$fk z>tw}d=l~IKHU@1JAxA3}CvKH0J`hPzW4#-tv#B*)Ysu4TvAp59rM&OhV2QB=ZGE0P zxTS&ql6+KIoh5^yRs;UmSm4YTyN?mHzy!@l?23P2^{HjzGCfo0SiqswHWKWOc}69c z-6x7c+P+5p!VFyDe}DF1{5K53f6F`xc@%f@YdX!2tp!s--`zCLQ(!s*HH}n1NAS*J zX-*1|;g(pLe1i-sAfOzDyxdrsc8P^zo*R9*#?76VpK7LvhWlI?pn4!4IP$ zT|?sQnB-SSS1gs*IJ(xfMkAQCv95tkH- zo4;rpyf$c_6U;9Un#;nL;jr;58*sVeo&50NG3#((i)@-Bid^m$EzI5?cTncLh8FRq6~HJGX2@qDvUGRot`^Tt~sQQv62Lw78rZ zkt9G*4G%wuC-QGN-wTTUKL3(L-%X*H80iMuH$0g3_jv7LCT2r>&nVSqK^1Ecf%hCt z(;Y{t7Sy!{(CD}GA!)5&*V|wg?b?RY!q{YtT~H*H1sN?=!shZ!tmL4;zkH4~)5A3U zpmhZ8*Z&8kw)gz>7-MAv{lz@6Nkm27+TlEdMdg|#n1_Nb_HLf=0%(IhbZyE&a8cQ~ z?-nOJA5qjr<>S7~nasNh!;n&u#3dk^e#;UcJWJT%5Y1eUN*~58VL^)q^wOX~J*vc# za~EBLmxL}4)9@3`I3L=3L1|_MpfaHcyNWTE@N`E0r*bZA&-I=xy00V8Qass+)>&>H z%MHvc;FOfmL~k6Mm$Y`$_o`WCeJ@Lv%cDOsdbanR{{_5?Z=N8vZriphMd_^LQJ=AA zt2pYggvTr44~~(8kfp`zu8SM~I|kfOz%^GauQ!)5P=BHK@Uw1gyik_A zH&0`ucgJ(YHFIWX%rgoU2VTDfhmD-xRtuB_-b7g9HXI3SFl|LmK^Q{G42%4=+4Em|BOaAx0z)~_W^0ty_7DXiAr&1Vv-?v zUj|sSX04<*&Sm!W9T1UtNAjoHX-rJ!D8smB^dK@P$X92%-JwlC~Ds-9T+WS z8ra_RMTiQ$x_Auu9|j!InS1)2%Ym;^Qbzrr=$0SQ9_Y2uJ41=lZ0ha>lpm&8+I8ZsfF-DZcCPg$G)wSR1r$rXp1CL3T3@^zFeec<<51 zarY~B(JjGUuktK>J3K?@=ZfXa)D$Lct^zY7jE;F%L<Y#15ZcB6^(G@!37=ZHBXUk74KAw2}yhUc@7Fg@(I3>qORp{Rwh-#o)|IR0lr5 zz|}eMg-%}Lo3}pxmU7E`7U%xKAn?lu)^sWcFBt@W*udHr7=v#Z1paRWYr++SR}KQV z8Mr5>>>OC(9{@Eb#69(2EjkrN?8U((>!1Ht9y9a@v_8K(u{TlWV@~q}Li)R(j89Xa_&g5xc86xmu5u;T=Oc&ChLbN||ibNJUuL9EVrfLa>bRP)+zqYtb)s@x`ZZ1nt( zdvmw1Et9uRk?Csp{QB)GtaYK{_ISl*_=dJn2U5-2iE{~s1h;GWV&IrWx`D0^Kw1Kh z2iJkFWs2z3;AZfvm339>SbMpSm%w0lbJ1RqHqd2bokbe${oa}dHdoEm#$~Xi%gH~6 zo4Ybeg6i~kOeS|hj=TtNU;~fSXRQoAETyF1Vn$56{Quf+@x(ovh5dj&pGzehX_ieG z0%gp9&&f>_Bt0iEA;>kymr*EM+PFx>BbU7mw*HX*y52c2$K?K=la5L@MSXX0)?GqK z8?J6e)!Qg3<|R8ekvKGVhu#2bqW{566ySC0A9jgXq1U)OfU9D_;|7>%fU*(*`UZ<~ z++zUQAfY!}fax*dR|ZfEh2E(GoEihJG{7taOcme=1EjtmcFnNJ?q4zlGks-{aEiR-@916*= zB1u)v%y2vu*)*j_a&sDWiK+A*8B+sR+1O^I?FYKVroPwA29OoR`EO_ly(DcTiqmss zLU6l;nLF%1$ZpOcfM{{An|I<~slpz}E(v%WcX}DAdorL={~ENIdBW5-+( zXj#^{$A~sxE6;kh56=qQRlu^ZY%1Qf+OiV3+9SWHMQ7xSf}!)HOX(HuJyldAx{{$4~iFtjYNqx6h2|1k0K}(Co4KFn3-o6UD*2nqbb@V16A& za8)qBo<}wAVhE`jvuelMqSP>YfBl_>J65-)_6kVjFX*V}SVs+Xf~C^$(SM^| zD7GP(R^x9dX=~fEINn=EQ#P(9Er$dWJZz=z4^EHOh~qyzo6B8toABhH&3rYEZzWRO zhC`?=+?MxGR?&2|vqRQGQ{7Xwig!)LR*_%^3AK4`CQnf~3u0xCGlb^8F#)G*1NNWj zUmMID3%l!rd38J*f_e2k zCOQVn3Y-gp1^;F|(fBVrU z`>6}akm)jwBB1YRzDQ7)n!#^F>YMsHLF#;dC)t*&WRqJ+mh_E_xVxgPecZ0rC%h+g zg)|q>zN5Q2horv59DiLQVZJ}vxXnkbp2qK>iN70z+n*>v82aw_xL&ca?`}Pc`fh7~ zqK)#E`gf%6+u+1cZiVO{Of~WU?T`3r3ihnNp!O<&WsOy5(u57}`e_|3ZmJ+fBcFlf zAV2(0kuE&I*)70;^tS(47st_xXj?68oeO^`=IDFrp% zxj%@#*FN%zRCmM)>9%~zrIm-=%|N587=K1Ynl*704-EPXU66_w6I?+I!5lfa!qgbk zI0p_U)VK>TcdX$nTXL5Am(SCZxOi?FBA8qv18s*oqV>y4SXu*AsH?eK-D&hH*6&=% z@u*n`)e1e%Egf!nf#-R)FmB(S3-YMLk*Pr&9!Qz^n6P99#6oT zv{p2^t{VKVZfXddHP=TYFNkN%^{Fq!jpH>{6#7l&>2pNBeOvZVAN6zF5GAzzwFu^= zho~@ynuX`(98Vh=>CM6FmnKjYkAu;-SAC%{4H6U#h}QMI>%tN?6NvaCi%0ty#U`;-;{X9iPxQ zGAKfCV=VS!m@;ptg95EkZ9y`Hy^_#0h?dHj7HupPuQjTRXQJbNs>WwZveE9N7^36? z7@8~gg6tn_lYY-E#2xE&R3GoFv@!yL(U-Tk7mQbC`u0xvg759F;Gqff! zE!0WH+j9;giR*m3FJ5)QXUe$#YxykNZx3gv(h5fWxo75DCS-|Hg#3$^vASnkCT`fu zM(Q3{=2q@`mx`5IF%=tn_lw~);Cu}BJOCwFUqT*re~HzERGXf$N_YI2k<0W;OE9s7 zGz)~{Vz8(0|42&mCs-|T(o}+RP6olh?3(|8L|UUmHFH`)6SyHNL+a)$6%NHCJFb+rsT3 zyvmC-Pbsl(TldATXN=5GK9A(H91*g^Gz`FdzDH6|tS#2bMsB50! z8NYJKpBE2R`fV2@YS{EzN2k|6cB#n*nb7K5#(31lQ_him?H2z}eKmD~j zelzdM*P(bK?|PfdN3PfMJ}G`r>D@npm|}| zafqgo+RiED9#^kdhQs`EBx}8jf#+(dh>3?_jiIUzRhIc-O{eibmf3bBI+KFd`qdOh zHQhl-^-5Ho(Der84!x3$z_Y^Cpw;ZayNRK3pfr6COAD+92o9E~zdcAqGfN-;H526e z^yUg>N*|;-voupo%1(sKK@vdROmS|Ml(_eFmzuHM21OZTnYz_u-_PJ#6LR7S1zjC- ziXN0z<{)Cbz_ofMOB9Bf1P7ecLi60OtyZReW}y&JgeT{JD6CJOVYa%q6v{f)s8xb1D#xt~gA zfmIm&)lhCT@DwHvf(T*KS?u-a6Nmn`Xvuc2#ppj6M?>ZkF>mXF=z`g5tgcG=C%9He#jBX#TlAYJC{9RoFGx=aMwcm*~%Dv2I3ye4TAlNbUyO zLcN#jor^#4rG4*ni4kKEhOt6ikw?JgjsVGDjsS=gp`A8(Qlz~dPw{+8jGKH;UE>MpGsVxmv{n+)8JPx{X9ZuQK0M!Bm9>PzvAC2S+Zm8@x239^g9Co|S$nCHxL>B8u*7fLD@ zl^~VwVjUFTO#s*LRN+c?e-v?!4cKbVLf0DJ2WH(@B zL=P~XAdl@I+bWco7YB<~?;5_aeg`)L>T2hSnX&MXPF&cV%*WMLU(H-<*nQKvX;HJg z92#8iZh%_2R3A(%&}hRoLoVS$eg_VbghiR#ou!5P+5%yPWrtAyh; zyPYOKi@571fd3lXwo{O}b#8(D%W$i`=Yx?nL?c?JWnOT4Di{+?bT`nOMUeRvq9#gL zO+fT`{ujfam)&UTZLN=XeM^XL6CxTXxY)jeSEsb6pKtEW&Nm=Ua{+E5wMhum4oyPx z{YKZJ*VTrn8v1&xNVIw!k*hL8w+g=4;M24NH!=-9M>hbl6?M637iy5u7Zdm#M=3h~%r_nX z52CQpKBS;J;-P+fGpkk_b7)1LHctGR&bfnWNpb5b;caGSsQ=Ikw#xHo&2q5y^S&l9 z9}(h`=XIngYJGAiS>^3%JwHF&BFDqH*e5@y*hbomwun=#_?#4nk|NJu2E7?JJc}uh zFEl4IRrpVs+t+6P`gzgM5G20;SZv(uP(n6$F_g&*)TUQ=H|BfM`)Sj_d=}CnqIFKe zlJV9iP!cdJk0#B3O>Hcr?rO$0C#xCfA$snrgn3b=6}yRL=QSbbxtHbCo@Wv!)KnUjXn^AM z2M`pmb)0ADcF&-5@^C}S?-KzH^cYPFvz>dKD8KI zls3n^Qz_kJF<4LpSboA zrE+SZk$O(7)VuY|23{wr+H{7`^9EOEy}^^Js)jfhM||xA19pdIxG>qL3qFdbwHfGg zBTAZrZ>1rvs6_?f2kIxa+@A+C5N*?GF& zM>(D63Z9x1OlW`&^+=X>AZrwQQ?+u}PX<4jC;y1XLBzaTV|XxvQM@iJ^YI(85K7v@ z4Z%bibNHjHK{bin3x#GY1>+9{sT&R!H!uX&gLpGhj@PJ|SM`EHI4u<%6`WpUXwGg> ziT-=hZLd4~;Rz**DLM=9zdXcSJ|V_PUY~LQyUrGu;KSFO$d7Bun=g0QIOvg6W!8z} zW{$3y2KQCrTC3vdu*cUC(F|;Jt#+~K}*8GdZx3GibE%kutG{ycnQ`!~CbgbIHRI1>-bxn) zbAGOGoG_S;gWB~RwP1a4dL=I4&R-+GK})5!<+CU}kp8jWvt(8#JKn+R5-zWjm7U+= zIxOZ8aT(#li%>-Ze4N!hDiuq6cbJnA1L#2~3w)J+6_GV0p+hwitR)CBpVuktGo5mX z$#Le|BxK}$R8K~5I(tQLvp%|P;gr)hRc_pE9w57y5*CSjc&;xE)VI&J^d&e^NLkLUguc19gPjhwZ{h+ZrIolPsMRD zKUNY)bFXY36hly~%*?-}t^N~JInI+!IV5f>_W+g3^B+?wk8Rw=w|1;e;|B+xK>2@W zp=%{2B+ds+Iwb5blI(<2=r0C*f zK+uciOQ+}$v5wVC{uEIxIG(a#fpxxBsRr*zeRe&cN8dphWjWo7th%q6z(b*>S?g$H zlf~C?YNeadKVGsnGwzJ9^ZAwyt7_xx-RGtHZgxW&OAmEvK7<^5oMxC;p}ptKPl|L| zfmY^Iu|cH8nu)=S|*NH8k>#Ni(uEzDVS`yYP;b*<2^`xo|E-S;D&@P7trP{exd@=9m zwtVkr18H@USvAYt*Q_834O{J{wH{Iq!LoKLl%Hp1{H)TG!yLr?V&?*`rN@a`|Kw&6 z$#GGYjjO|v>EbG~aShqHGJkmTzQ$Ir(4C;`cUxue%h73Jx>&~H$)$miRD*A`3hcG` zJS%x0%X@{_tB*idCp$Taq<$(RMBdLvo*KpEQcCcy^}gt4^(PG{B~{V6$6ESE#ov3X zJJI5rD&w6jn1pw-9-)!VrAg*bzb1og!pF#g7J_2)A4Ps~G25pYd$d3_eMHo{XZmH> z1D<1fL=!b-ZkBCa$*Z_%+=M`zjH^wED~+Goxrp9LW7C7aFWHphk9kvy>GY=1J1iEp zSrg5myAVX5G4_7mVz3FovI;a)Sp0My26YyfcP!XDk9ke)yEf>N{kzwszqSERS9+tYz>-gNl>hYTcuH>nZtywtlhGVcCtF0JLTfNBbg}= z%T@*>{o-CE3%RB&e|{MUx?ELBz}+Kkk3%12!sF5ReRtXJsGD)iwfCH}%i6mdeNv_K zzj!D{%=YSvxPk;`3$5xd{GoSK+#IjbG^ZjZwW7 z^!A5VP)l&9rXp(_t4`L>Df&5;b*d@}xLkw7yfuwF*enMca>LVH6MRm9ja|gx`F&h; zK$Tm)UPJr9v31ZnR&?^LGkTp%NVE2_xzrzF5yH@4!mP2n;debl&z9k%AH&i|qlSsq zCvP0i28U}dgPvaR2Ff%c@@~w8kn>oqsxV;+zJcIeIlcGqw8cI_EnR?xaVm{$><@{fM4?lv^Qr4@)5bX5h-b!;~3-ZJEo zQgV2^N|R^og7#Wo#K5uf)ATg&riO78UNer8SYBdrn48l+9KwiEmAp5#J72~2UK#%6 zC*JFYslB-OU2~@D7h%iGGoGsT_}(?*y{m9B0=}S^Je77ed>y}PWh_`oDxnzHQJXZD z@S*Wa^?`~v``w4B(!0=AL4!tn6inNxFE>njenwUK%MFb@%7|4(n9#`bDHd@DLjz-% zu=tPitw=etqubt#(dF^;-{DkS_Wi*XTb?Vn%oWR*TmF4<-}@j#y)^BZRbw{gwRe4V zC>^Q2=cyeJ{6C`TAy5iP1-2wR0U%(xWVWl7{6K60XqT}A_SD#qxW^l!y1~1;$ zuMf;Qq>FFmX1nr2uUDj$vjwVVaXT3*z!fz?WJZtD^Q)urlU2X%A0W@xl@P!SgFsyg zz4vvp)w(*O0}hlTI7nvASk#c3Q*7W_X1#1u)qxtXNpr9y!1XaMVIp$y9>gs&>~!8V zx?gIi`*IcDu71qX9Ec!N5j%;0>QR=7x|Jm@;!ZGW^nq>0cvf{Q~BU2R}n;xo!70Je@mrWbc z7qHhBdLQoI`jQr+);1LsniFlS8;A{0 zBRmXjyKzY$$w!XZz~f(obCpZ*{Hm505>?ax_O44bheYL2DHi+V=jqMc z+|b=W%pad}FO}W>L;dmdgYl|H$5TUTes| zn}ilPG)2tb+S}r69v|v#@35Dt`*rV9;~Wruz-+;Y`uQ@Kj7v!(T+eajb=l-a2mv_` zjC!)bpT|H4IVk#l7ILG5?C(lBIJ!0qxg`rpxNMctrCH#eSzuh+A<@(99lkpX^6x z9UBz!TLTL!5TpubIFhHZW2uk^KkE1A7Y9H3*q>h#oaoOlML$*)+L!7}ybp_8?@)jJ zWJv4vl1e_F_7$&xNU}pa-1y*tP;yg*lRoZPDpVh=@Rvlin}5Bgh29&ddcF1j!`l>A z=ND2ue!Q{^bMg_;Ir&I$pfI%sbg7s56i+`yNfr712n%0uXuW2|cV^F?y>o6=Mf;Xd zaLyEN>iY$Zs3e^UZDt9vejtU`S>=SIH2uezcX99HQxvgG$DPdNvON8tBtsCr%|mpp z{)Vul=Q(=G%hKePFP~feazpXU=hnX5Q2O$@b%+K%s+5lAk8f}<=#e4*_(u04(e%ep zbT1Oi@snJ*&>!DINS&@Th{;mN>ung+-8yH%$mgTyH|M%or^r*3$?xEH4mPCbdH z9~(|Sjp3szY@NU~vYM+p7=y)=@a}R6M~X#VcxKZcG)H6g@yi;kj_X*}dJ0vmXj^d9 z2?edk5r5RU`cF8yt**TEcNm`aA16DXrg_AG_SO2f0%ebl4#qW*en|1m^}>XY4|Zq+ z$q_tRigg}eRqNKIv<5FugIyzs#MHlkGrtr+<(Lz?x1bv*RUJ3tF>cWg$Bw|rd1d(M zcXgk(b~+-Hb^5q8RzJx{pV+=-8&D^io@s^q=<(r;rNrzpBqQCkx2TMEPpFrTVebTzT`)LKO~bbpvoY$dUD zQAgV`4c;qB#ab;JyLTmGwBr>?NmGt#%#rYAMOG`a$!|duPdTR9Z;@-8xIMey9PYP_ z<+Jqnj*!m??J>-IrEtV!!Ib3qS zFYru>65Ssb`4by^8zxF5ye);^dxd_>B=6Bt^zX6ysnSpNDDR&Wlbz4YkBTiMDf6$> zm2D69SFcag9o_$!59w^*QII6S~z zmqzJyp|W>>{EYOg{h7JY+-TUBWy-AR!<2EKy%YERWT10X7}W7hHg(f)TPXC7j3a9;QY#olo)kwOV3DPszQO8qyQhPm z5kvo3BRpjWot}lZ#n8VSwA`RaW}$b*&~AgeIU$ZYEA@pKb~CWhJDn;7=TuR>uJKkq z9sGc{ZE>o_!fV#}j198G>hAssTZ*k4gJe*KH-m!D5Axdh_{!y0~^R^|{~VoBy? zlU#X1Hp%zmB$G(uD2(fQ$-F!|m&U1%$fja{Kpr2K$M9h}_?h$b`0(o({`ygwQi4ks zXFcbMaf_ z_!f(o`Q_qw#_@F)e`7X2v9}|k(&A-$x%7L+@x>M|tINe77{_lLEqY{dx%km>{Bst+ zDjR=%9KVYA(ED=y_TCuXPZ8cao%g;&8TCrs=TP~UL3YLE8giKNRG!+)%(RTfHIWak zwf=>Zb1|};^N?i{ZbpG&&jqK;#pVjIqiXpDd?8anaqGI^T4`uDFN54-;1LJgd0bfLzuYq&%kk9ycu;X;$b zuA^M2CG0xUg{FjEUv(kQah~r&Rbkf;T&N=K`i%=!hFw3C%F~8W9MtKX;dkgQ77%e- zUYn7~@B!=J(;6gny{>;>8>c#^I+H3mqfruPaMR1I%$Me%(9a+Z9xmy<1mdn$GClQt@&65Xa! zZBDKBwO0U)T>qg;5LKh7?Aj7L~zOuZ#y< zly*%iRfD2REJ{vWMKvnw!y{Cp^thrXD(ZEMa!xC43?u4Ei>iuKwJ2(pMahA$j8hc# zM~kY7qoynBmlh=#ol;dPYAI3PqPk(pCBwu&?y?OYjTlw1k8RT(ecn8KwhKr1) z*>8+_C&lr1kTqN?14Ok6y))zZpAp~QE5ku6a-laXj$f>z;{C>QUv_wPnCbe!2V*2W zvCBe3??nZ5w@>Ijs^CTo-mc(U3*HF6^aB=bQ}9lL_;0++Vg-2)Hb8nU7K|dl7DtX( z%!zrU#+Dd*AIo5>}|xmUWOx9c0pC41ZJH?f$#T{DtP4suY8dT-Y$ z$t8!_>t$H!-mX+~$q0LGL&m&ai<3*_;si4s@^(#+`~Gz^sEfDj+~kr1gB}9K-md+U zOGF%rxj(|n-EOakysy^-l1tupP%dXQRFS>Td{?jMCzq&t+9e z+NW14_`JQ=vQyIAbwqMWx4q6}H=4I=T5`$b_WCV$3V6F_CYSu#LH{n#x3{b0a(#I8 ztaGbA8R$`9MLB`PnQ;`@Yd?L_0`vECfzy6YC$pG3REh z<=midxu$0V<&|;FbD6;ML*khEhsJ>wnV6p+mPstob?)xC9~`eq$zgvt*{IY|``S?- zW!A#nYS$T-q(Di$o8)5Femmyf{@AK$>%nrOw%S+c;Ein+z2!K%h_>LopYi8aJaom^l;!=u3l5qj6%xGWwr0?AV zX}v}ul2njBmv*po?KdjPRbNsy>;Fa_?y;?UtY(%64=4|xvf3lM2f@QXBGLw)#a&V1 zvx<6$^!(i)Q<}NZRHUNk)3sPS2%0~p=5I1rEv5uh?HPiGX^e5-5sB#(Nzs2}c($*( zks!-n`qo5o^xjy}b<@j51<@Et@G>9!DvO>o$W=fB=AaMnMJ^a$`Y>WVfDcS&YC*DT z8%1s=6dfiWq?kPB@g>P%uRF}g>9Q@K&bAWEDUmwi&2v+JEwj)j zJzo@cwhHQYoYq!`KeZ&4+RAtbjy1xYC~Nts`v!z|iTO0!)?|1nI-;;5LSnVYdOt%{a?x@8}k%U-4IldvIS=uk_?g=lfsn=@H+*nX0U z%B-soH-sx+G~yd@IW=N^bnJ;_=)A%!u-n6z(6N<3hX1Ugm8#Swl{*IRe{%8#rGEd; zE!}Sp(>LeV`y0A94l<~PIosB4MA2@YYb? z+Yt&aJGvG8_IDa6L1nsAO{INZwwRPlA=KYhPwO2Vw+~}>eq4@=lT<=)-!Ikv?6#)y zX#W?iNwt~AT;IB>GPPfb3zNE;u>Mv~51=&kSGGyGst`CQLhXj)IEO+~QKO2q(o-b5 z^pvb9&Wzi3Jz5|an-P|~6>BH6B+Tv(&C2L7!~XR1t_zi`DdT0T;}8ey9AC<4m+t-T z(!7LJgVaj5s;aGSFzL?+np!lt&xoky-Q;n4;MA$`_BsNF7auNpHe3K zU!W$)>n_hzuG0R1w0YfSjwRk-iCuRw##E^PNrep8HHg+-eiN7ZCz$Lsh(%u$NB=TE z+IrF+hI!Rud^OEIc5^HGIfri8luXe;41Sl8b3 z&0Gma`<>;Vq5a_)^9NEoc30yd+W(dxZP(~Hp^+4}_k8lK>AG6lE4oQijKJ_wnpjt= zPK%J^!9w&=TwsSV!}O0~KKkr$4)1oFt-a^B;LUHK>1t!rZXj)51NBP9KtkJlo_IRjR9~>EZ>*h)B?FRtn(Vl^>{+1r9R5zukCs3h`iE%9 z_MS_G-n-2iWOi@o7wJ;H76Y#fth=gB+PD5yBD>Ubjk8?kd|AL<@aq9Wjt06u1`+3` z=2q3MtW?}P{Oqz-Kx%tz^mQIlY5Rd(=Xe&`YVYx%(iR19a%GF^Dw!Y7 z?CkTd_-x;LHOe&lFU#1XjBX6*m_#(xN!1di?I7mcK&dh(FNiKj49#n+9R)$xonq;x z)T!*%wTOFc6MOL8$}t}VKdKn>zbml721fj|b>DQF+`*43$L!?O)nnF;=uLKL>Jgk> zjaB;w!ftiX2)&gG^&?QJ{#rvqzgQ+e7ff70(fSK-vy+XPt@ZENhV!{GkaY7h>0B07 zM+HY4MFN{usC*WSMM-H5D89a0gBX4y(6$q}M+(RV|D^ z!s0PvIW0&|YoJfRlTBDz(P1gLbv+{-S6Vp&hBdWW2|d>A^Zy6|+997IpeQEbCI`NT z8fxp8{s`LQkPFVr#ml&r)fSZqiLw~60`)uQA3L8euP7sKG`vhW@k+`fm5Eku1a|QHJ83i z)lEp2n~?bI8lF}eNWMUqL^pHSkOudJ1&i6sEK2=pC4{WhX*#N)%K6jE2&ry9m%>lu zL}{u89l?##_ekuAv9HCqVBoEgfUZg%=FhA4@74?76yY~)DfGJ($ZIJF_wSZW_dTcn z_ao}4o$JtdNnG9U@U$jPGl3nL&EXA^Hk(WOMVQztveH`sI(<@63S&+{Fxq?48`yh< z+xs3A8SP%m9K=4+Wl~QvRsM~G;6s1(b$Jc6b)d5Kc}H>k8euY#a)@?=9oKp(51z5ZGmra59_=Gxj6NuVS8;}_^bXGaxu4eDsQyn0AjW(imM+q?0^5r~c zGidO!K~pso*R$D9xX*0wLpCieq+YtVcWvpo1y!}F+miR~k-RURyl>hkvvD0II}Qha z=~IM@+yzR6hHwqmKaX6z-b+3gP8Qe zg^&B`jp=3VQd!$kCExJ~o?qcv$n$ibLwJ6bXED!7JPUYE=2^t^44x%C&*Yil*}{{3 zEa_lkMOc#_vz`mM>I&wM)T3~}^>Y}tt|JlBz8e`y; zx|gUK*@8&^W; zqq1we`***8U~xgg$@)p2v0+6)8>gst={Grfb@Gu7bPVB+>pJc*h4bpV>%m-IZ8I?S z45(*XmwLgxaOx547bw)lhUMPs1dlp6U+$`STA{u;Qs-_XtneQ@+D&iDPhvB~qP-nR zE5+sGHZDkwHk*l#P%c37j?+uJKXkOWf9QzcxH6yk$C8h1^pV%Nkv>iMX?cYIjZO>E zV}s`Ew48$4W@p!F5m?`MfBPDBS?j@LWY}gKP);{GdpIp_z)OzkevJmK&NSfiEbGE) zpJ~AB_i_!WPZxfw0Ur;iCW7xL*wn5DeDvt=#tr!0;&D;(YM%_FBR06G0U!PU+JNKm zsl!m~3)X-!AFjmT+1n;vq`!=IN`FsV9XRH|)koqrdkv}l(3Yt>?5wAVZ>?OyYHB_;GbW}QNRlPqsl*S)>D91(rZo zA>+zXrdd_9u8#af{Wj7l5p)i8ZMRn;wb#wOx-SjkGGS52vO+2}4Rk#RF!c83Y^;<{ z=>1lBGr*^mQ>k9f1YS}=v#UlbIncFLuj*J<1uN~Zit4rC8iLV3aTGJu40LS}K$*5F zATB;=xFcTOpyS0KLSbPgHokwS@hP^XlO3PHGS)yz&xdSC5q%%r;@f5es5oxS6<|sX z=rzDP13X8~qLX64?FJC7p||gm01k}-KQ(}84ZX1fjEDgX3?PoMSyiaEaZQT~zt8}V zj}@Z1o&YL zc-;VMhS0l3fOBJjxyOywtau7=S`4_ya!5o%?*##>V}QF7SfUhq4XQ^n23%=5J~lwV za_qp_j|gxju%wf_i^PssTqH}$=z1cLQsiG4`Yq8loQqWPqSNGUT}uTc+br@pZD4hC|*p|TzI z7ZhM?RYLjR#3h2RZHgP^m+>(;B9};geN+6q0xTN3uIE; z!pr;iWF!&`yA?_07_Ap2%x{MBTR7bG;Y*~f2=>wN7&Nn&I5nJ8J;Fj>6nYuUjvc?y ztBwqeuO4Scbm~83tDZL^YaX`{6qOTsdbg@ob=7G(KH9ZZ1;VBe8_Wp3M)&V7MkG6b zqw1z`siwFEt;-lv1Vg5a$9=%kA1MKJ+P#-XN!JR>NMA;+eeX^MFC>^Aw+;Dv1b&37M9wK`s`{iDs1iN0>U0nV(}X-vusVD&bDuyjmv%(?ap-7 zWh+vyqF8dxWiW;IY3~`P?Dte{7Q@_itii3W{N%DH5z3Qn_K^&AW($PH!S@|P7H2!| zCN5Z*fLVj)!s2vcDePsxAq-h|F+-9*!8#0anl3IjB2M^2TvIOqy7GFomft&O6+Q{R z`rK6FkLRwIQh$6&&_o-S>LysOasi)T6g0^TOzT%hSC67O32?iM+J3%$Jc`?mxe&6- zh=MGh2}y@#rlT)o3c*ySKAwZy3|#t5e@se0&-1}~BC3*|ac3|+G1A#FUDCLmB?dtz znP%RZWWS4(X%0;?`BO5<4rN}SN!Au8Ya-doO0i>IcQlkelJ!qU+_|TY5lpLGw!B!j ziq~AJE!)_Cv_R|A;FvLcWkI38`pGwSz4WMH5!*|ZIyoq{(6+uXn>odYr7PsWPByM{ z#dO#;?qGy@3s$RjO(iP0*3jC-2?kC^!UMpW z>Mh{G_UMEjcDAsXvxRB<9N4Eb54X&y9=^aFXBOUA2jpdLu6Aaea1Oc;H}uwD~8Z8StgP@M2`7g^n9@z9gS9*T=uN`xSFMyW|5gq2fC5` zFzqHD<2EEabai2Pk*wn(7gzT-S^dKLQc9=@9;~_z=+3_vYGI&;tD-9Wg~k5kE^Q1o zSXBTFc$NQtq7D3D7d#- zVp5PEFVO(fQ1+9B9;bf_iHnXL<_~Uvt(MBv>&i|}J1-3C?>Ok`_zYICoO%QSxSk@YlEN%9dlohtS@C1iU) zd`6EH8)vfE7T=Ze1v_`-L?dL^BM`!{Mx!#UOv%kR)yldCjG<<{aflOkV&iT@G@gho zz5TN&ek<48JMK-vR|&@4UfWz-=-mGCWjs$v1bf<4ay&G)Ci;%_KcNe2rx>|^YlzIJ z^DBZHw7Ph94zxbsYu+N`fQ>0Gq`}OE?ukRr8lg_^s~R$-dd)ylGG};afwG-?%@!BX zv)g{s7qDgGYtf-b>r)RyEBcW3EeQe}D>lvF=OlAZE!=~<(n^A_RC{kSR`nH)Ss$EL z;+FV%MJ*BA%ls@eDGv-z>apO^?vFSX;jq%9$lE!4z6 zIJfzE_?d+UqzR*=wk zY~B*~Rv5n(+gnlmR%&m>@mr<6mBw!kdTV2i9xQF^+w@i(zddd^OEYhUf>mbTtn`M= zTd{4{sRRpzR)Fd8(jBWEF>Q}mwrvq5?Q2x$^LnC(cSF1iRKx8ULX07F@p%~8iYa0w z?0eeHMmhh7%dfqs<{^A+YyD^G)Eb_nCx(%$nZA{}IPSZC9ge%-NnbRV_BNLlkt)P_ z7>{)w@;Wn=zmAY&jm(!{vAHCyS=Ig85Ot_UhL292Iw<3i27wWHH-ni@T)VZDj8-{W znj3^eHEd5ScTVz6DLF1Fvm$zE-~+meDHB8^`xWNsQLwp+f21O<;y1(s8!(BAW<2a9lkmA0`t>|2)}*e2)Rv#OZ4F3$1{%FVMyV{}M+cT00Y!NcDhePzmD zxAX1pU3)Mf_8&ZY^+lU*yqe0a8@++=d%jXSx;qBT0k~l{ANw>6FE%9DCRh9-r-mx3 zq6HAc>E4Rxq5ipG)zO~}yma?6ovY;JC5MMu|7!1EcSk|N{c&+`e!95P8)JH&K0A@7 zz}LIqOgchv0zJm@LL818cD~NiS(MDHLiJ%f^_%fUEP%c3J;yvi>p#lkO7H#hbZcP$ zGYz~`c3eTS^L`#$`T1BrZ_OU$^B%YE()S8`cdX`0^L4;6C6b-@5lG%QY_`{5nd~@{ zv6OvMM9n@~yUTu*;C<;Vet|b!$3_Q%yj5+<&U+Q=Z7TSheo4TkeB2<+Bx85;xS8)Q z*}P%4KXoJ?S%d*M+-ywuiHa&o-gn0S;mR*k+?Y*x{F6=363a~#(<-uQ*qt@)pzK>I zZ&lg1QM?^RjxwILJon*wd=^^HTRpM+^Q_FqR`7O2_KhWmX*Hy&G6+TJBce&!RgeqVt(Tk~RU1*~RZ=4F5Y`G?yE1W;0k$ zTur`wUaT@+^b!j%n&Cy9241Lz5OwXHBa}#Xlu;0&;$%mq3za524sjt`@(XIo`GXt$cnHjBBps3FDd%arWp9$@^9%?^u^F#q^5NkG1bo zBU(!)C#V@=bn_8|YXi@ltFv^&^X8+nZxGX5lYOhq@pjCrKiqY6EV&)cuyf3+*|&Oc z7Nt)VYkQkY&n-xE+s(krZ24*KjUd$@(x=6#(_d6lW6T98yyVhJ{PKM~QDA|cTG7484aT!U{MepByOHrz5_RUP{3UA^HgjHJE)99}am05&l(G*}zW8qXX zKt^qh!how>dBqlufz8-TpDu`g{P73o;918D7tv1UJbfccnMO?H(U%yvZQ&uo)oUuj zI3^t|U!zTZV|I<$m|VV^lk_Y>pWb+@3zJ-GttNr8*x$nwS?RdXV#$?C)^*r>)1j(pkeL(f-*e-hf<=aIK zwz-UhNd6en(AuVL1LC;p*PHGfmC*oyR@>j~MMddb31x=8PRyhBo)hk|xrDZlWl44= z_lXu!JsUH_U#st=%|9r7piZNF{&F@cw(k-%7mbpsJzahxcELFhCQv! z9{eDhHsqHnHYM*J7IgGvihbu3A)S9Xj*o58*~YH<)46q)Q=Y-{CoNSA^CdSqh&C2x zY@XrdlWYjv@*Z(AgA_vu!NKAF>sViXj9uvRo8b1{?W9&NhiEoNRC`Z3q&`B6a#hPq z{I;D~kYd4~@|Q2r%Ruol|CnX&T@X}YmZto+M|R;S@R8mo>i#1#m? zs+l1B{q1X1s5P7-_aYJ(fh#n5Z_&8^#n&1cgHlE>Ay3ZG8->eXm5!+hmVcy*6Z5GW zkCY|Me#5--klF;x%0WT;aa_N)rRKY|VS7)-T{ds25|LMzW5!>@WYxTDg{)>R8s8i)}HpGIeoGB4+Sg!Bkejbl0uT+n#t&_*$R!`&_3-wAKgg}xha5aSg1{%u2+FAM{dU|pMV8NPmXWe){;#M)}=}b&2-7Rc@-nKS)^5@A<=B-*Jgj zFWRu)_mt~hchk7-J>M2po@5cC%EP-G;%7-^0|JP{C2OU5Rrps*Yga;p{`<8 zC`;MG(Hg{-nbCwsFrLiJt2$a`jBlMaySKTB=}^jf``Vgn8jul_7_0D$J51gy)J^Rc z+|1PG0{9|m$Nm@b=$+6gl3iQe=HzWs^Z;ya z@A>{6)SJ(GLf;$|Zdp_TjrXhXQr~lerpDxb4Mnp-zmK3iZODj8o+%+WlB*rbwZX#1 zmAg?EUh1+H;=;#+1|s2mq@_svK;5-X(V28XhoV#LvT96;tI@?^W7MH_85n2J-fRHU+0+!RxB zrG`7(lnE71dOx74+k1ZZN7$0m1y=C4p(xW#?M;cnlGRTCLBrz5HsQEyo2J4h98R92 zRdB`eV8_l35B?gLo}l!;M@@|Ge$9)O_pzf|@sA{$Ofurl}lj z)IXi1R>jn$Wujyn#&O8~W|aFGMqGi2n_E@OMI5(ui4PQYxp% zFR=w_r@f{9*G?R_uz2zFIRiEHCdI{GO0gMDoAY3CUs89#%@V`vD_cim?^q~BNgbrQ}n+q*BZlZgO>MP^ihtl8hvngt1 zD#4{6`-5`W+9EmA{XOH2`s_3Z`Z`ghBh7oH+0ysIzoY6XNc4A7%n|h?TTA;Ysdn<- z;b7T^xKZGE+dtMHm%|Unx4F`YWPPujgR?Sv3wdbo`R_l(&ZxY;r#^@Vu-%%n5*ErZ z8;;BPl!DamRH&k@t|0T}(@JcKo~iz8hO|EVxgkAWNSj{ur*1aAs0Ac{<9oOJPwadq zSh(4OlV1vOsw?ubottn<9^Lv<;d|lugS^e#Mz3KV=t%F4Z9$WKUS+}L&3KPlYkHM8 z&Ni)4TcRRtJjXKq1W~7-*hnZ#^`H+v7zn?;-HaS3O*K)0(RJ7Ft<&ohKn;Eg>m4XR zhXn9V~v7aiyu8_N|k! z)S{mvV(kB?D26g72`paKngu6|yJ~DMSeFS>7u=u);e%DRJlq<=yG~&J_qe8zV04@t zUv_I>qe851(hZgmB@Ky;X3Lc zx525(-XhJm`fjv~oWlpAOx6?QtSk4HoIS8Jj!!LE$rTht45@Kt*uHO_-c|!trKdvq zO3mWJ2TI};$xg1d9e}I#P@+F-Ry>ktc(p|#Hu|`iRg#hY&l)I}8>H<-oV!{+;@{SU z$yp_G({IDGP#krmuF+bSmo7*y>wqlyiR->#{(rm(>;tU~+1{F9&AD{j*5reK;$5#zq^> zspl+62}bee+x6U6SItqxo~$w=?&HU8SoEbhQ7@(yGY^-*FNC<6N~v?>rFrsZ`nm77 z+?mju*3?^ui?K9-qWyPPvYf4Ucs!Qd zCf7R3P3(OEJ=(2p_TAdPMzjyo7Mp03_MVQLZR}|bI7@*Tlo|CHQSsB6Doq9AUGH$? zAU6oRbuZUxp)(pIvwtloE_bw6rmrEZw`w;{Yqk56Ik`^W^C*iRK$@g$UqN|cM?SA` zC*!AMgfP7!qt#*Bqi+Ie$X6aMLR^*h_by+g@MMj<&;sFQ>|Q%8NTIfNw4R1UvEQTh z2v_q#$0~ZYr`LT7GNaOZrCUB7C7;HijIQD=O4!G-~eB$kxa!nIo(tg z)&2F?a;p0a*hxsURCmSN#&>@Su`XxQByAL`J@u@VISen#JpNe+hTu(jv zsp!*O>f4DH2k22MFS^o2|Ljw#_j1u~M2kt8)Ke@v*`d8bpUUxy_9ddA`Q)cS;%6q; zL2)kmT|brU);L!gAbN^O!bVy@;O~6z_MIEU#yvi1t2?}PtfKaKbz~o7_6qJ ziKCvzk8!KQCu`$e>WjCurN;I)nfQVwBCglEBrmUKUmm817o<7tJwQ_Z^*zbOh8OT6 zf&SO|fQSL8Miq>GS?2U?KYDgUoXsya>@0>XdjPE%m+Kd&|HvEseb7Xp5Q8afY~YKz z1%20IeV0ey$6jvlIr!JPgTH+A=4#V!(QdD6+R7<8w}3X@l5W2-FR5Ic^o%WtIbFT{ zXv?b0zc%@6yI&tZ`kBJDboYv*g9kzBPO+}r$-34sTM-Bo?|HUP1|@aTKQN&9{_|k; z+F22KpkJx;rIb;Y(HNPn2Wl#4~9`N+m)ScI}LW#$0%5EfBW~fgDHPkd@ z=+J3e@RWvQM-}eSyl436ZSK5T_eJZ4fq}a$n*FvEo_t`~gXMigy;BnCE-7}ABg?;> zr)Jm{(xw<{k!P-=)~7Q%3=`jMQ*Uc!FtsFTEe#g(5$od4O@8ZUzjd4ciNEkwziF#K zb$f4W-+BBVr6UME(nX(+SY3B~VH<-MgtzM*AM~dz^-HoIhv;L&k?a8Ir z5==hyg!k^j{^XZh_W|>m_2U+9{o&AYt=lhaH6iOuT{Zb7|Gh^lNgA|nJ9^e$)1y-`;%E z)NPmV87$mN;wRr2*MC`(h}Mxo>-IDH66`H!tAMvKLI?PdrH03K_tsSEw%C8k+sI$I z6^u3o*bbk2n_hz8eC9GYfaP*^vR~qgQ9)A_Ox>Et*=M1@b0C%MG+ z&)atYy*>?MBmY#W&y$LiB!$86GC$b)uD^k+k#)JmNej1Mwi{7!B)y*Z9^JZazkW)ioT;1BUF5oIa^&~txDAoUUvWF)Q>4sDly}>c zuhRl=Dsihwv{#rPjS%@e5w)o>TB$akG?n6B_19iL@}#C$#p>;?BmIpWiD=z+z+?n$ zJ2*#g+wU>qT1&Pk-v;v*f18kPQ0Z@a3tuYubVFoiys3m)6=H6!UPdeUt=k#dPN3Dt zJ#*P^*7CjS9%_a?kSCjK;k2=H2ZAZ_=uO{9qLZ4oWyErwaqm^cvUNLR*($MY+IHFH zHpT3_?5fGHK3qu-fA!J-4|{I{7*%!l51){kF(8f;#WpIA7bHLw$b>bCO73KX#B9za zA)t1g$xM<7lbJXRB;rC!ZL4%~;cZ>8HHxhZ)@rd@ZCs+bQ&+6k7By0M|v_E>`Ls$r*gIIoaF-jTYikv~2 z!U>7xM=v0uLU2WeK=wR_$p1a?F0uwkF^L1T6~+nZyMWk#r#?_(_49uAN_?0lyVbU4K=Pb{utt+RZXg!&F*)zFqw(- z4_DLqhnClIXTO<0yJ;_OyvGckjZK-t+~qYMr`uiOYGhttBpPSFXu!LcMFa6@FY|`h zdwXM^V0$Bm|;(liblj zHv(Ef!@!i6ZSXP?4Jh8Yk{eUz=EjP1{dsuiFdA%c4@3igB{ub~^JOHYrbIcz>x%}RXDP)B@q|K3T4Z91FA!fJ2!xf8Hx^fd;b1)foJ25Q zjI6+6j z{$RLWX^Td>6u&o);)YR|X1hKbjFW9VGS)5@2&^T$VAyEa6kZ#StPcxu3Q!0nFO40L z-$-{L>J{}Q7*oQLxDrcrlf-c{>tWKD(4UC5dWme#mSnOg#MwNgg_oqM&L>4AZWYj`Ylk-+4uoP0AeX`)UDyyIW0p{SzUDt*djSsSz1XtU9J!FMT4Z8iq3jml+8pD=?zwF6iLaMX0JXs$tx6Ot39ZqV(w>U3bpQXMzZsJ zS(B$6I`uH?;kN7}j+}PXH@=y3^fAXyKhA!hcoRSz1fAJ-ofAGT}{rJ+$F27>S zfBfX9S6+4XHCwO!+0U=*zy5}8H~!+6H{E>8t=n(=)$PB&p4VP~W9ZGd-X4DE z-S^)A;IDt%`{BNiKK|s>{RjSjaO5AKeg4lc{$KxL$^AkQO<+Op#{Pz3WF)!{@+z9b z!X#}QP1NG!E)Z%mCwrrN8de*B#9{(jK-4!d+L3y+z21kQ^%&}?Qomv<=rE8KWCLOU z7o*h}TEzJ#=xES%(D9&|pj=QMXg-J@cv}Xd#Fm1pKnfhL(T{|>St zBW?vXBcVqiLtY0xjKmHgvCks09Eq(#Lhn5sh=d-3gzlY&{6j(;k8V|O|kD_@DHwdxr=XBA`lXEXMPAY;Bb{J)UClhHtH zq&-YuU!@HL9wpw}9Z=>{k8YK-xq|YpEYj5-3iJrQa5K8Y2RC|0F^11JKD4!NkF&g? ztQHMtLarlL#aNtit80cNHsjiLQo75J^-{RdRzp38i_w-yh%D&xb+#L2i&`Xg~iZXf|jW$OE(gXFk zkpQR-)DG$ZK`dD(i0WYo)CEGd0z=bs$t% z6>XDxI>-fbgBn3ipk@%NvWj*~T@6|TLVQ)kRYjcDZ-G3Zvq8vn6?v|@J%Tt%L%$a58Wt|HG>ic)u3xYTS3=?eg^tE=sHk8=z7o% zplzUkCyZai{T-k?LB9d*0Nn-pE$D90J)qx#?giZk`aNg>bU)|;(1W0dK!c!%L63lT zf*u7u26`Oy1n5c7Q=q3oyFh;cJp+0c^c?7U&T>P#c0?Gp6nrL+j=ups9 z&|x4PG*Ax**+94=RXqZ9Bxo84cTlU}0O1>~=75d{9RvEGOvBfHf1Lwg=fJ<812J^V zsDH**vT7W5!}N6`8fFzR?hfDsdlo*fC$qCznYTMuL(@!^1+0>0y;$X9R=I>#E@hR= zSY;urEP|B-1p9)8tRae?mK(EFn6v^!LLtH3;7!B=7_-y#J%;-2fu?R&9tdH)FLdRY z#$>KwdqN=;5Mo>W{Fp1mzLtRas>)>}So9P@;=6 zOe#cPX&Ez5kJhQmT&H`n!;y#eg?L5Oi_FH%tSfL13NYM?E;Gsn^TU{xW|*Lhva(RX z8&&cZXS+9u*~O?EPAYonujaEOnPLZ&jUACh488Z5JJ5}JnmD{F53~lmyrGmqSt1$@ zgj;)?Fk^@$NR2z;6MD_pxw`jxx?+Q_IA2#ZdVRXG6tjh~Zf|QqH*o5TQe9D|E6R06 zg|4X76)p8HUHL6tk*h0C(G|;e#q6XasX9+LIxndz){Rck6>eipRo!%kuBZ)kxsh?n zv_S6)(445jfhZP(-s$%nbjZwj)Y}?27}X|1@nCnT*I-m0To**O)^hx${A5*0^IT|9 zXRtV-xh%!JU`kit8uun+rj@nsa%G^$i@_Zgzo@QCSv;z#+#NwwU#i0j1W_thG*!vE zE!~M~u1JScO)jO1oQj#Ux(7YfQZv62_0x?=R2n7QQqSb*VB(T97@=q)#$R!-*32u|5z;28Wgk5 zP$)(8jC=3m>rK8R(13l1ZmiYH7yhi^WhA{N*sHb4kGoRu&@7S5UJ_xWDw*8 z%>^9}dJpCggV44iWwdaHnURQCUz;yx8nk6MntOGvs>E_zB_>bi=3y=%BIi+%uVE~k z;QNPMf+)8Iv7Vqu4l7*=F^#RvS&w;!*1#MF4^tBjv?7D`GWpGw^8+K=Ji%mD>L)+= z{ES5xp7+WmRl^50cMlwL=Cnn#f6;%6qIAJ<$+}Z|mwZ_C{;5sk zx4Wd=wfwasJ{m09lljz7{FRTDtiEFAmRVKLms}z2-!A$2qdm_~zWU*k+k1X=+|F~J zE;0S%r&nEd)hi{^|9u)B)DZJi&puQ3!9Q!3OMCHG&Aex*r` zCfN5+d287P<*%J}@X%j1F8|`&Ck0j<_4kqw>nf_}Uhrf|;MFItsQ&J*k__i@H=iAU zuB3MMk56xE+FinLjlBBXu2)Mk7t!y{lKmNHOnW_l`ThUQEdKN4$M%*;e|~z(>Q4?1 z9v{9jTMO z3q0(l%ZiwLQxV(xX%XA(`3{>qGQbWVe=^&{f65NO(#*2+KV(zZY-U}@cd=(KSjr|p zHHTH!-_3s3R>J1L?_u}ONwBv+c#NI8;Uo6_s_7UpI*)0y!klxE$?M4>)2s-(NzI<-82vTA!Z^rzRcO2SS_YvGDu%zxR)u1|GR%$H33_&RBk%YwO1P3oc)N=}}J| zeO<;smajkkA(Ve}NsTD~8%rj^!%UC~WC4xyLphBIxu0Jir!Y2NS$UA^-Pxx<-E%VO zHyyhV@nEWwdu}1=XMX$5r9gJvC&4AqE`pNyoMd+1?q}8%zw-8}A8bA8lHs@4EI)(`|^Nka} zc=Vj#mpt;pm1q~9Kz=+3+V}uy7J8SMIqphe+ldcLd|}awN4;p2X`0xf%*R|UrJdSQ z>c%(XtBe^_YDnW!nPiq*8PJ}Sd(EA)-m860#COIozCQ>9-x%~1F*QtWzwTCib&T#P z(8h|E#wJ&Vv#hMb?e;V}T`MXu&xXN@mqogJD|)atN}4{$ZrP_vZ4nS1 z9=)gIq4=&6MX03^3j)an->5D+`j!+e_@o1^@I|64>dGrzo@V#zx-yw#(m>l&BID&x zN+a!pNeZMN`>DdTejcM04D>TlMI4xeE}Q(wD97drGD zYr|F`z5gaQYoO0y-1|VBwhb|Y6=NF-a4`(wOFTSR{1fwSkovGzd87bm^D{g1nc zA80tK<^30?y!61<3k%eDJfD}e2f~49u$8T-@l@3{J8P=SJ^0&HVKk_3sBk$OtLp2> zu)e9$Q(x(+t*EVcttNxIdXHFHA+@{FS$3MI9vgHjYwA~}OqwX)b)!aSX-$QvvA*6@ zLp2yn>XgygVdqCZfP&OmKx)Zj?I8#WhB;MTm?BtMJ~xk5)Dd$zE3a}(MSG|;(2g-H z5E7!v6zM{ci^@c}6>2%s(XcEMLT48m!o_V+$%zMzHAV)(7;P&e71dGftqGBFMOZVf zz$m;uge52g&x!!X*|g`1EaKEEpewsh>fG3vhnBECL{)fI&>!z0b;Rqh^3$?aycgP9 zFC6vM(hN*{s6HC+hyX>x`#5$517flpI8zQlV#NxtMo>H+L zMAIY_L$#g~?~25!*AA7tHHc=D5`bitMIurELUI62MR=X2(h5QvC%0%&NN|=aDAMD* z5yN;ZK%C8(<_Yu&@0x>w^|bYjWW(ty^}w9IAs%X+@TEbn!vf9x_!h*`MPpoz4SqXY z<7l@LJb43qW4(sp){#`MbVB^xG}RGaQKQ5q_B)NH<)N~Sy(PPNG=Vw(y*)}(7M)O>P9IfL=c8{%8i|N6`s?ZP%9f(voMCL zSfZr74fLdH1dTr0Lu|okF0p89zHAiprCmOGDNQ`>p#@YU3?Un1iw2l>y+RyP z-^vi=R6(w_p)QFng!=Bl)VEq|k5F&Z_7*C>UPWd(WwQ%=f#Q@*QNpqzC-KxIQn)aD zP}200pKOyKtdvpm+ek)D-B>NwrGX66zBOb}jy#M;dSwIx(G*isGFjiojVD)%e4y`m zE2ax*tzC*uF*Ye?OvcU4o3cUd;cX9jP^+=~4!fOCRiLA`B1DmHh(<_a zVIf-qYz>CBtaB#|Ud><0CmOz!r=0)qg$;>m7o6}U8O;g(Fr;cBoFmzyLHU0qVYE3q z6(mJB(7JFgWf(Oa5O)z3N1n)n3G#o!_$R_alJ-d&B~N14N;{V+B_elF6DMD8w{ zJKwRuv2@{*c?uNTU*s>byH=E@?JG(1#3go|$Rb)aw_riR(#7)>1%5e@K}x}bB}Gf& zjUm((<1rx2MMVW7?81UY=?tagF(?ZcI0_2&kSL;Ks~ZW85^tr?pjL+{)xC*z^OR`2 z4=f~RNFP4-VwkjAk<&!NT=I#Ug2L8BpBTWupaVA@R7kwWD5i9;h2aJxuZ8aEtDSW+Xq#QL8SRV zW*9ww0AqQ+BmiT4M6doUGHHzaug)ZjkKQx-SF&id`!9>V(e1yOJ)+F?_5)o*H;HvpLQ(57p zNijFe#eOeV8p5tgbXk4+w*Is(Ip#9&JIH5%ETAbME9fYY|K5Xq60qy{2l+XmMosVCHjVK9sU!TmhmP>IQ%1OT@(BOrkP-f>WrRO$9^u$1&WJKHG6cPT zi2Qdvb~)2OcAotAdZzrR%fgxGjPM|6E@%m;6jTptiHvYN@SCv_{#JB^-vjyq=xoqp zP!{OVu)i!Z!mk3I1L_6!fi4EU2fh&SZ|g_+e$Xz^ZqQ#qZ-Xkv~7{2GDBI zw?J*6Fz7#eMtCFed*_YtEg(1OY)}{IT+rroM|c(Rhv$#*feT0Yw?R8DKpa5JL7AY( zVHdt+gy(}YKua!0TE9QSPX-+ZdhL57{Ftjo_z@s0XbQ*zngsgZl}JDE7SN5Le$c(3 zJ3-fiW`ceNYPx2G|NXiVUI^;_`3OJ#XCwUbYe)DCTSxdb&??&FPsWM zV>jacBI5e|2><1)Bm9Oxjd15HNZ%ik7w?Yn?srD`Z-z(sfwxEa_3w}H`JnFiUh1un?ksS+f>4>+$Q+9ahu@Z&TWE!2e%3S0d5og zJ9!Q{XS;ZgLb#jfsDyiXj^H2WIf8#L&k_6wc#h!bJcsyAj4R~al*JWNm~32833IqY z{3eAf#Ba*u3h|o?xI+AQtX7YP0xyg={|@B+cVlhf)EGwtF<3gK>E zq!RAoMS_2r7YY8oyh!jL;6;L;^CIHUV%!N}Sy|kv5Zbs?CCuSY;?GjJllZgpxRdy^ z3b>Q_vx>M=@H=@mv@ENdS1W`rUab}W?8%V8ijB-U!xN4;cEo{Fkd71 z_wqG@{{UYj_&Hxg{8JhC0oc?m?o$YD+@})ea3AqcRk)A%r{-}V@lP$_KH{HR#C?L_ z$vdHCQ>%HWLg?b1D&ZR5DfoT7Q}B24PQf4Joq~S@?-cx-IOgNwelw3Lgj;w_CEUtm zf`1#23I6RoCir*onBX7aF~Pr+Z-8?)br;{D5bow1RKh)cgWw(^v}{^6->wk4_;!_W z4c{*KeSEv%@8sJBe~fPz{2Tao!M}-P(>UC3<~tO^EqsScxRvh^{M+~r!M~mF5d1s% z4#7XbcL@HSd;rebv|W5aA>7ReRKh)cK=2Rq0l~kQ4+#DPd_eGXK0y3AjPC@noGiXm zA++(GDq#-aN&Gnq-%0#Ad3-1F=M?ar#Gg~dcM5(d$A)z1tNAX4(8YJDglqUN!SCa{ z1b-*rCHQ0LXp#K}zDw|L;=7?`Ih*-zg>VbstrBkKy9NI?zFY8b=eq^}4!&FP5AfZB ze<$Aq=PYLz-=h%j=6h7aJ$#SgALe@m|6aaF@E_oN1V86{h<`fc*z69>;=>A|jSs7Y zIeeJ-rz?Dz_^0RbVd9@&z=w%{dJ!KM{7$|XS~k6!?^Ot0e6LElhVK>pKE7A*ck;c0 zKgRb8{tbMu;NQd#z&V?~nIBLHx9|fh;Z}Y?@NeS>1pjt^K=AM22L%5BKOp#ba`6G5 zzKe?w+4S9<3;sQv3;toe*GK;C({w}nx1Y&xxWeSOSY`5?>earDeN2Ai z`kDMz4Kn%70lyPoqp_9W03}Pd$l0eGH8=BKLlANdzK?*P|1<9q~=JO@S+UKx-UnTQGbri z>%knEXG7@Yn8<;`vj`Q1+Xz*K=MZ}F?I-M0cphQD!V3ro6<$O*gl{7u`bO1+N*;F+ zs(E}3p%>o_!oED-N!XvqV}yfwd;{SS+EhaHsWuZT1$+ykTEMpwdePPp_7(8$g#874 z2jO4=A0Qk;T_i+bY!{(Y#CH>_MSKsT7cxrNSH$-c_80L3go8z#6AqydCpvHF!x1V@ zZX;BkJcrQh@d3gi7vD*UzUD4M zWewj=sIK9A2)%3gFkv6+B4Pg;et>Xr4Hp7Gw1$h*qSQy73UkHBZG@_i=MZ{*Tp{d3 zAC9o!#|sDteY}Wp$j6<841F6yrIWh|)lR;K(A&v@_%1>P-$X)nfbSvn4)9^Zz5z~$f>}Sl8H9raoD&WW zaD4yq+sU&Cm7Uy1sA5b-=-tT`!oHn6kFb9yFCZMmn1gU=CwCHJ>{U&u?BXs$6@76+ z?=J2m?AygV3Hx{P7~vrLs)R$k_$ES(*)|g@yZIJE6@55D?{2=0ux~ftPT0Sj?;spR zABS*gH{VH!vED91We?v?sN&mC=-tDI3H$c&y@dUH_yNK}d>aXe_Hc0$oDK7=>Ch{~ z+(xKk%t+`R<_cloFwZ0GALa#wgBVj04h?fBA=}HV36;IvMW|w|LFnDfeT02`c_(52 zULGSHL|>e6XfNMHNRL(!DhK!$LKS^fLhk{-jj-)}5Qi@zTJe?9!C;qlkQ z|F4JtV)*>OcKEN2{Y4+(OaGMCj(2--X>9|z=v1?QV%w|86H_q-cH)pT?dk98#t{NK zQLY<~+Om&3`232myL)2$)XlTv)=ykeO=ojxLzU#D$kU!IeWU(Y?Q9$Ec6B5n4-wLZ zA7TS)Y7?K)Ew*~;_sNP4S#*9RCPHqgYN@EH6xaOXND~fW6o?yp5vQ>>;u7ESY!*+* zvv@)tTq!)tD{OvVq+J1Q3>(Ia7cU;q^^zq^#u%5baE)h-qf_L|xZXI&wQ$0xaV=mz z0SCe;7%y;`J7HchCbAU9xEvJ5xE%0m$=|r5q)sfnr`X*OdnPM~^_wXkbn>!E}Cw?l$t-$is zDE9EC)n(eU?2XHI-pC8&78Z!WH#&i{|SqnfFE#hL8Y=;Cd-7kr+AGkCb@O57{2P*a1G5z@6L zBWPu`WBFbs99XaWA={bes`83@Pi<9QRc+Op3Qv8Vr?S4R$?d7Bce}MX`g_CPu3)P| zM@Z%S33LQo=5q4#0Hj4fW;&r8U0O7UINwCu$Cb8_w_V(@FoAt{G}1;rJaNuK`>O(*GSOOSDb}KNGNixiX?MJvm8}c-Z+kN`{)*`2@9yAuBmpEZLXuRMZ{c4p>`XA zcAPf#oC4yJ)aZFD9^TM>!`=C~C9APYX$-V>gmG@GJs60wI(S;;q*QdF?@yg|?Ma8E zXRQ7-5xTPmhxTzWzoE9?-3Xy?ANY))1KI$(2=rsnRiGO{w}I{kJp_6R^fKse(8nMS zntDIJjiA|}MIaSa3-W=^g8kA5KI7j9{TTEU(6yj1Kv@rd#*Y9^2h9dKK*gYPP#vfR z&-;0nlHS$$ojtG)&@2UTU&3P!Q%W#X2w9tpj|41z)r z2}vVe)1E-1&0=^{BQ6}_c?MiZCYYs?e2d}wy$}vVYv#Jn-<{}Y{%%zCFn}Ba1S`t2 zI)ZmR;1^zp5UC$0Db!|A3k=HjE1epHvGIjJ>9w`X+ucpy2lTS=WfG6T&@~Ql=U9Y8 zocJvXIB*Z4!%-M;EF>&gjIJzx^I8@ybtq+M`sh-~Ky;qB*yot%EA|!4>n-*zn%7h8 zTR2bTL$Pn!JdrKMzQTF*U`VlV={!nnaff3;0aTPyQWru^i6`}9s72CAvv8g<*NT0M z=gAD!GOpOSL^`1SDfSgYBsLCSJkJ^W7q}4rCP(4SeX3Bb} zRjTpxL#rRCIE&IM4r;jI)+>%roT$+AW2s-ZB?WD*qAfx0h*oAE4#VO`5RCXLmlZga z6=KpIQHUD}!1!Ca;QQUKQxF^VNA(p318N zlB$Kt7TKYTRFNh~g;H9QO{GziASdafG)}Yo-`4U>C{RmB3DmOj^2o2{(PHtbav0=k zkwKpRe9IcHGP>XlasH|_cH^deWN%tN%EngDahjJjOcvtG!Svvg*)vL< z!~>oS#JF{(vjNX~dR92A>O8g17CGfop6Kf8)uzr;lM|lurrO%o9ygwR!uy)eTHHH8 zb8T`e29Gx77A`3CFYtABv-RS{QTlk%Pk!nXZ(`J_O+g6@@(Gi+|8c7GVn5>f4tm!`=} z%BBA_AtN3*)!5`rPBOcw8PvJlb>(G^xX#1f;K7Bbm9>q?^OTWW7_lU03^x)-qz3+m3S6i-9(y!^Dv2xnM~CKeuBff3Yh;)jRm7EZcsWr)B@X+&Q8dt$ZR7dp zqiK1{3^A>TI*<&reN>cjJ2_^uQufK2!PGRdC>xnTvBpt`XF^dCDDiPjJ*J)!HM}iG zR|V0fJxUChC?=~hmXPS#2D$?zzCOY*3Fs9MLoSF#7A$13u;0_&+U240NNU8Sdr~LF zC79Gg=puBHtj>ugcg)LUQb+U2A4M}IbaY9sxHD3>>R9g)Q}{6t#W2#AU_n zfsXYE^QlQ+QjU>L9-3*7#U`-SL}#pjC@K$DWr7jhkut_MX_AVsc60n#o1{rN5*Ct? zG+6J|)@$S&xy~B5(^HQ5+!d@llxQ!;8c~$)Xu;}pntri^;XXbuE_q|#5Ke3ppLjn` z3MAxhOng0*4=Di|Yfq{DGJE2uwF(bi&NzYpGPgvDN@wG^OXaF?H(^}0oS)DiijQ`U zB}veP?kGHiJ41M>k|V#w41$NA1jYnX$UmVwF_rAWWV+0Cc?G|`N>Z7d5YHB(O3(_U zxZ-sRE~`N4U;$1@lB|z-jW6WKDvlH`+y<7Ln5VyGD=I8GNPty*0t~W&2GD~WYVkC* z$Bng{aqYF$0BJA7PPRUxsWrC3;@)3`Gs+z@4PRDmoeGOKZNYZwa+Et{oLm{fdek~} zWo1RvJRRlsD_gcv?#XW%)s$F{j%UgZx^}V!mw_i!OfHfsk=`Yj4&}Y5qy40`>z5kg zdZAR>)BJ=01iKP={yX_Hx4as2d_O4`Lcq{sYXUY-Jd>f0A5Rg!?E2!#qHc;?7+-z5 zB@>H%4T(@J;1nMulxNDV=4zCals!cao~76kQCtOu2lrBLbU(&AGPsfVN2SF?_p00L zp$k4kkw`b{>3XbAYIm=~0)Eq~))@071E#yE-AOUIgLDU}QZD*D;sq4>awmPyx+xdt z$Ak;|syP^fkXj1Cprf$>X_Mh-)=51z9Nz9=nj0-HX=b`ds9T69O2b2()X^9Ir;JZa0ed9Py`Yww!nFFGLDM*qZ553z$iwzWr<}fV` z&5T+)YSv`4opLK$Km)J*=%?({qo#S5jG0!qQ4HN4>e_o~M56m9dJK)U)Q7Go6J;x^ z21^MdE32t$D6Mz8${Fq)E5x-xg++9+V&a7z6fJ9U(o^GZd~jA`ML4GESJfJIWg%S3PX@3>VqAf4JX~K-yeVB< z8*3?}Ee8Q{^BY|*FP4Xrrcsg+ET+K~u1~INtDx6ip`+KUR*5BU(qKbGx!j=uo!q4; zUjq_xl@|h+2II(Fx_e$MCbvjaaltLj!6zy%b}B(9pTg12DiNo#CT>huXiO{0bd|WI zS=Z?CD;IYL>psZ)9dVnYmauYpJ8{ynOb4E1qPN+Qvvh@?3-T$i%5KrpS%>*1Y3Ihw zIj$OS5jhh(o!-6mvle|brp5&=nh!1WDyho1&|f`Hq^4AgG?9i+h!5E3+QY=odlRG|S0w)N@-2##1UUVPF@khSOrdO_h0JiM$WZ=)BvSw-^> zVahbtLU|~2wo+_~Ljr0rphU4od#Qv-8(Ev^mY(W5-D_l@%NxXQU)F+)p<6qQnWJqd zs_2Q!M-AWvgBCs3^xT#v9`zXaBx_R@+M-5$tG(#_w=@I-Yc)p=xNBdCT|*+S3B8CT z;w91~G|B7|kERs9$jMsWngXo-T6xW}Ge#D-_G`65H)PfCDbAY(^QhZ(|K zRjaw7T^JOfRkdlNB~0K16H5_HPqv7Q{zVg{Yh<-a)u+bDsE~W{zCjC_iGrl2H+k>A zxOJE=fuz9kj$m4|(-g)9v<7jO`eX@7tuE5I40g1THlf?%^-B?K3Zsnm;#uX5b|<}& zhFSq>(pOGxu2i!YL>y1rCB+L`+3t$e&|4eAiPk2j8&;yZH!`{cT@c`;iI6x0hi0Ky z4ZZ21tYe;*3U6F|20Sg5Av~r46M-wr%5bGGb|66py?za_#sL*co-m-t_DCDA!NUu9 zU@?IPT)1v=qi{N+XbtdSLraAal4zhYqGbme2xVi07Pv)oCG_%pL~W`;yOzp|J^`X7 zL5m_9l5~SaysePZLyg90K_NTYROmUNWoi|gcEqHrOly;I?W;jYmA|SMUoiTnl7jr9 zPmu159;V1?57 zTGGs@ozy>QxFs0-BE3~H`BnsL(F{h1Vl-~XwnHI+Mnn0KDUw+wSmoR;bW~bWBR@S= zvLO)#m~@@=u`2oP3dq(APFybGW73VwMUF%>D7|#K(3Qc66KSI6Qfd;iZ%yoJwl3;s zCk;s_f{<>gpC+^@cNu~-cZU5sAEn1=rAHyjL4HsG$w-oci+XNprf9k&i72L-$Ta0? znlZe8DO zw2+LpN*0;H!kxe?f@mO69q@cEj@{rk-axGvbJ8u9g-e@*e)MnxNEQ0cq!KSXjk40s zM;VLv9_aZLgGZxdmy`f z3JPc-xm2jp=A<;TT9Up%SZdYBV32Z)JLUaR(k>KEW3iO0p0>iJ>$;>qjsWtpm3w}O zx|H(OFE^t(_~LxA&vk8}SLwxcekZ+Lrk`-qUaq9s*mi1zajvOX!H8*r(unQGcwtvO z&?R0UYr}a4nt77@hcR7?48{-#K49QZv&l5ak0Ee~Ca)~OS1(9g`as?Tp zpouO+=FuW0ZGxMp8@hYD6uf5?YJqVq4@(A?=;xehsONLu4>}vP1hjAc=ll)O?Vt-l zt3c$=p`p&F<1Cox80>CXC*AAzI+=^!%yar|?BbMNO|%vBh!x<EaOb8MZeB6vK{cpBJEeCT)J>YY4(jHA;q=~NaB~>k9t%7c zh+BTdjc=2Ilc9ebh|Nyw{Xhh$J^(x!_#p5U;6uPufrG&1z=we)z(;^8{_g}jfsX=9 zfsX;pfR6*qflmN2HL7}nmB4Ah6~H5bD}k-RDj=m1X;mqGNGC2ZV(dc1Zxyf>dP*zO zth#~qz|(;^d#E-7PY1eyxJFjJ5QvTQ>g_=6C01_%HUVz~HUn=3t^)oL*aG}HuoZ}l zz}RZwRX|)Pt6l><1Nal*nZRp-X92GSehc^?Ko9Vzz_Wo@194r7`ZFLFAk|+1$=^GG zLzrO|={oM#UY$Cu#Tv)#V`hcjE zxY&-d1h5^r4tO?jJunFD0k#2qfdSyTzz*OA!0o_`fJXtZ2EGY=9rycc} zH-R5$_!jUj=;@}aw}I~ihk@?`-vRzr!wY~J8BArPekSO@0XhUk@_s0Y=JJmNQGLKR z3wAUJRhgMV(?G|7=$62vKy>`~n;<*rc#r~`0XhLR6GRK-vq7^!^Fg_wIUon<2oT*e zmJgZ=IubM&bU29WWuTKl*gD3RfKCKKW?3ExX{UUmyrcRn zZsL9&h&-bFp*$0A03?Uvhk77>kbm(*Jz*O`eV_|Ls4v2Q0M(V`uN^AL9?3DuFv%~; zF3By)EXk|*osGX<(8Zwdfi40q2bF+S&?z7%s1$S=s2p@Ds1gJjrFKF5%J4S;s*qZ0 zC#a1OZQ*Ia8qldAvntB zG9?)?mbVZ3R?yWT)H6|EsQjpmszH?BR5nxwN5Y=Ufy#%H+nF=y$FJJeDAT z=L0SPZP4`N!PoMYs@MM$@-<$b>t?z9RChCMS|d^2aC-BpuEBYeU+Nph0#BX&qP16qd3!#%5L;ePw&(T{GfVEZOt^$A)uo`-vE6Rlmq&zUt95`0)u7Lir^>cm?NVlv0 z`RDwHnts0KUbp-2z!mU&{)rR+`;zE0!POP;2NPZMqw~XJiDUkJ$NYkY4q?>|bubnw zEL>7Fzkn4kUGm&ByMFM!zF=GHt|ymZ{OgeiWHHW#%1uv_mWwsRlyOT{9nM+SI!7D3 zoOR`{(S~5FtuL=&73J^;Bfpun9WJ$PKr3BCLDJB(1v5;37anDbw~B!$P2QwU{^%>{ z$cUs|7#L$>J`Kl;^;xM2++qBP`6wC0qD4zItQ*cBq~FrabjikcQ}xQ+PIlq^o8YNPYyQB^7DezEsO?1q2? z%ogHFTN!4VB$nJlm_plD9@2VY}`y8*hqYXfu`Ru3MtJv18$ zf9kN?mmcCBbuc&)PM!%ye#%Le71aMgakobzsIi6@cI8-p0R^xK&m=+!XldO~lS3unZ8!6C7^6$@{u~M5l1jP8ugn*=J#N10-ywYX~l53X0Z z8Yi?|S?^ji%CfSlrp8_7sxSm3j&4_!f}Nz%H3 zc8`c;lYiLxt$i;WTvfGH1zY@BhoyCEq0*lz78jh&Sv}O&{lB;q<+2@!^9vb2q1*!77S!_kIvPw!|`xg}?`X`5Ij6sFpldfG+g!POfb5x)nQ zi-e^2x~0JvvEf&2>XA+~J$C$J2Z``enzUgRgke)xg5FSXiUp!#?TX%t$9(gG_Hd*t zu)wh5!17_7FO2hz#_3<~y87}e7qH2q7Z1qX%#2R*xPG7p?y=XVvX!FM^xmgF>kMQ--G|8`@ z)@PFy)F6YY3M9qP)3~}JCBgGl;va63$8TbL&4#+;lR%ju6Nu(DETA)v{U`s$ z^ndbeK$n6p0-XzrfG)HDlYbYq0n`l&fVP6bmi#I3umv8|oQJM1gnA>)UPqubC!wop zJg2K^oT{sd_gT2V6z*v}sjJ19gd&OvlRt!%0Ha#C(agyo-3@i>Rk-bjzvNzzn^8T{ z_>#Q&ulzP5K5be&sclz5 z8$?)na}`@zURyQaE5XNV%gd`+xg38r&|0Ln*220X;RtPcz-BZ&!ptIAI>QA;RDGB- zw*h-P(X`QM4t+m2DrO+U8rMhWulM%OQ(VFJjyN4%qBZlBaSaZnB~2@_mQ1b{(btii ztIX@avY^6IJ{BZN=7U+C<%rWI7FymKg2oW7?!kgVMNygB_c-3 z)l#_v-33gH7&V$rDc0Ceh@ku3ehSJIJG&{?-BV8DJJ2sZW(iSk6nQIwU6Hb$c%6e6n`io#E6u|*8iIA~g0 zQC7Metvm%s^LXAgljwxCI;tqm$ha}yB;`nWqe%mj)72$nrwK#SK(x@7Elvd-HC!@YMQ8ulM%$G;7T%W1idbnl=U?84x_nK zw9bZ5e8KJr?l>}pK@|`+p`AoS8q@Lj&9}A-S;p~j^jFm!ty7;>~BLX}}s9Q5$L40NlHMyQ8R1W;LXDm*d z&Pl3rg{lC5kIz_=G$l^@P01K<##-9ZUqx_CPx6H_&P=Mw{?H74wwH8ce=~&ZZpO))%@G+hGuo1_$e&fvR`rNO*P4p<(TZq zaD3+Y*wOE}!EvMGmyVkqZ#v#~yyJM-@wwxA$2P|=95*>`aop;-&2hWq4##gCcRB8M z{LXQoW5Dr%;~~c*jz=AjJDzkr<#^h$%kc-tGmhsSyB#k(UUlqoyyke_@rGl_@s?xQ z@vh@N$NP>C9DjBE&GDh*6UQ}uXrP67m7O%x;X|?z*0ZW^u-O^zR!t_|pkK=(eEHf=9SY}yrEwe3i zEORY+mJ=-}S>{>hTNYUIEe=b8Wuaw}WwB+6WvOMErO;AjDb~0t5~{^%DYcYY$}JU^ zO3Mn%N=ucc+H#tu#!_pkv(#G}ET>ys7PqC*(qy^Pvdwa{<#x-xmisMFTb{Q(1b;3! z{ls*&X{+ga)2~dwHr;9Zt?3@qz33mmY#K7XYuanN!t_(qHKuD#H<)fW-C_ET>2A~S zO!vY571Nuh_e>u`LI$VX8JQFxgDmrrD-xrlU-=Oh=k>O*y8c zO~;syHBC1iXR@1)H|3iY(+ty0(+Q?;n2s>bGo57mrfIHezA4W%$8@6UyP4n5yg2ic z%v&%X_(A56GH(U`7=+dN%%c=MT$g!U=C3ku&wMiT(#)qbcV#}6`RmL-2zzpab^6T9 zGq1?plKCH*Kgs-Q=9QUOWnP_mP3G3jYcqeA`SZ-{GW#=cfa`6UH)j4K^Ou=7W!`My zCH^}y@67y7=8nv}GJl(Scji5rzstNg^S;dAXAWfEpZP%MgP9LyKAbt2`AFu@%ttdH z%X~cZiBbH@2{TT>iu(!2onSxVc&NVW=bw4@%)rc(W-gwIb_|4-A@R3jW~D$fxjv~P z9a>vW|LvKZX6DU2aps(vb4R(5?k4(sd9sFII^(h#m(RFjM#|;PS#7g_kvn;#kuoxC*`KNG2B!pDJI3N9HL|?la(pT zH04M|+Rs*U759wB8BH^qXRMmhGGq0OH8ad2=`qTI^ml$3YB9Ny9ocA z%CqL@%+H%&Fu!QtZGOr8NAt_(SImDhziR%o`7h=@=GV-xo8K@GncpL#u-Jw)F_>k=AL}qpaVs ze$$#`J=%JV^;qk4>v2}Q^?0jdonf76J;6H5nroeHonxJA&9k0pJ;^%HI^Vj$ns0Sj z3#<#Ri>!;SORP(+%dCagB5N@XkMUb>EwQRrr?u2tW-Yf?SSzh7tShZm)@ti%)*5TA zaMfTv-RiQst&P?uYqNEgwZ*zxN3?96OawmJLP*>_~$nf;sW z9octf|2Er_eMojz_V2PMXHUsKG<$0HVcFK~!?SJK+1W>Ad$WDnt=azUKz3Vpdv-^5 zFuOB*ZFVTTD?6MW$?ncRCp(%Q%Z_I!vTw@18KK-NLittp?W4lDJNq7laBuc~*}u;o z$i6@Of$RsfAIcu2jV1W00#|4M4EOGi8)|iYHl+xGq;-y%^l_pvo1Nx! z%%$e2xy&3hmz(3}3Uk6-X**%p1+8oBPZz^S8}z^Cokn`8(z&^F`)n^LNchE2ygMS=)2A=WQ?8UbOADy=42N z?Pc35wm;ckwf))l7uz1&Yqr;IZ`g)xZ`$6ny=@z|y<>aV_MYv1+XuG4+WuzSYx~f) z&-Rh+W7{XTPi^~c;%QVpV+?rxzDtm^H}0F4@ymTT?Yk>uIGLwE7pgn=ZMHv`ao4{2 z8F%lyXWwr#-VvtH<8NY;!5jADB%jfAsr~tVFYGg#q^pEZGXxBNBhh6SL}bXziR)p{V(=C_SfvM+uyJc+26FkWq;c~Y=6i8uKhjx z`}Pm)f3^S3zSsVteV_d!JGN|Oern%uKVbj6{h)oseqBa?#_buu&X}BeLgsZ!zjD2D zgR)JzQTc`POXVizX5|*;R%N@=P3PCWewxA$W8~xscQYul`Vhe!a&PL4c5;TB&h%nz zgk9-6ui{M|QpEgNT)(SZpHmlG(Y2Xr8Coj4g^G%lvYkHL#=ULSgiHq=13DH&zgVD4 ztU#n<)_}vg7#4c9Ti64b?(V?hIpLD7<4CzAUQE9jO!U-u;nw4xFZEZrVCn8@b|B?@ zx?KXN%qF&tM|;P&#`=VRR6KNlg;h+q3kEF91Ei=~F@`qge+%XAtxsWexWRnSrw z2EyxTA>6@8#KN1Gq#- z+%g~npmPr%L@wGZ-KT8nj(|}Nv}<3BU2L?SA%v8lA_%HH()DNyI*1^Gz!Y;Z9*H6% zl=&V^SO&7JEKCZT@t#WU9k~I1X~GO}CXE1++M)+ZiI9OlrDWN|q=ZOcbT` zBQ2Lw&Z^3BUwcDSqem}e=JorrWJYHg4cjfmZl~NB$(Tr=p}f1bBjXT!#NL3 zy~_(g^n2P;ZZWZmRe6~#ZH=BLk`ULF+vF|)YzUzR3aWh+bn0%lV~`*HGBxeMkBpHPm1JCW9zSNH===yy2FY9R#>AO~ zlP0sH33*pSnzN*TseFjhWswv5C&LxeksdB~&qTTeUs{Cqypw$R6p854ZFw@Hlhz(+>n0t_DmMS}O6Hlxv}0_k+sZ zSibnLZNKogb_er)_&`Ed7#;6IE5vX`MpQgafyrtt@$1@rTA$Xes6HXh6kQU6R$4sA zL?T-MlwNM|5FvP5A?UbZ5xW>7VVpG!2lVi}yD)k|1HKEK z>)6k;u!%gjXg@Cm{$cTcz6JOk@Fw8YCHwjBfp*}tz_WmF0UMU?=gP?#F%=?QU`z3S zejRW<@Im0zllSv!SR5?_s=z}}fxo~@fiD8z1y09?^<$Rr=Vt<|f#(5#4SW{JO7?Tp za?Hj7X8|_=3xRI}e*j#jB0S)gz^8$EPNV}Xjb7kf;8nntz~_J+z{#ci`S*cN;7!0@ z;N!qMfbRm|17@k%M_PvPfQ7(9;3dE~@O9u0;3vSxfpf}{|3DR(<%GY$>A+H;16T$u z2bKd{fGdEZ|3li@z|}qe|NnGO$d+UaA$Dw;A%uK|%*oo;rg2OxTie=X+GJ~6$QFyW zEfyM?%uFVPA%qadvNeV<#9|?gh0wXFPP_ik>vK-0b8~(Ae*67fyEjrn@su?&IG|k_$17L+h88t56j?@m)b-PJQp^>*{}nyfRW=F zXD|uwhM90&8akMDIsFOu!!Z*WUsmb`XTU7@<~24^2lvAkSU#O`GSNT+>I2heQ4e_O zZ2AS3WZ6V3Tr|ffqPckf(>$B7LSruWgfn3+Y+6D%4;*|2vtjm4=;3C#6^7o7y`WqcpLSDPrx$x3#^7`+|D?L%V7un8OC0QeOHh#Y=hCa0e`d zqwk~ra4qbBzr$D_e7T~GeuXc?QrH9QVAeXDXosIdGdFak-ER}=a0e`bDGxAC;JZ-X zI5M`J`oXtg0UY%p?S=QiR`?YRzlL$T9{a!_VLqJy5bcG3!ghE|1$LZnFdTr{Fz;dJ zEo_6k;OQF}m+*TSH-q!Jk6;hDbR+hFQy#@1z%`qwSEj-6&SUg1oc}oe1|y$foWbLt zq+W33)6A!t2E!LH4Hj49mtf8_%qMusv&>^y_#FK(%V2nA3;hLWy+FUhgK8)b4ttsM zX5%+&DG$C2bK#s<@z3z&*YG>=7Z{d>-RtNtc*Gmj3qJTJ^@79dsh8wC)N76b2gZDc z)vy>&eh>S=$oJ_l_$f5c#gA;mKCrt1`@q|`Gmc@;4*E-SC*$H;{QF0YcersE{u567 znDGcN`h@Y5&GS^BQh&IjiTcAIKBN9{+ZT*8xcN)`-gP{$)y%pAYrdtw;B|ZGFBsE8 ze?h}{tON6?7qr5w_fmgY+e-c6`X3pGaB&;^JNUXZ()6;i&!eYYtaZ z+VMm1$Umq*%>9%4!*~Cp{xIin>OY_L?O*B-4L0fz|FTnm_`ZYwheb~Me*yELi~g7F zrvG7g5Az9T_tH*yzF@rGU@-I=?7{-$Om>kECm&=N)iCs6yJ&;;(45OWKg2FF;mu)o zQ2{p{Y8TD$wIOy9eIxTa+%7WUvcrgn#~w~R+!9IrLj2Pac99MfhuK9LJbk!b?1JVf zy9ir^?nt{xg?o>(ixPNWv|Tj9jAMz<^j{p;+EnM z&$Npyc=|bZQ4Y=LQZM+*d3ND~Locw4_l0p!j36+5q=B) z$co+IQB$!Sy!$Hb2Ioz)ixzm+bi0Tw;Q6{tyGVh5WZ6X_JndS$sDZD-Hh5k(_PLdL z3)5iEb#_q%$Ii2hTKEIp2TeJ45wpx-cotgW5ew|17)IS-7j=?1+C@7&ej(#!Ipa7F zd&AReyWf$47X*J^zezAseCV7`#$cMpN?zW2rxVsd4!EyIuFL=s2 z>;*U7kG+bp_k-90hCYNH;I0kW0Y0-4JHYcE#SV9H#{jgz+csebIP5X(0PU~^zE_DI z?qobYjvZj*X1geWxm9+t1%COYU9`gC)pilJlKJ}#;~akW9QK0SpT}PCjF+$%Joy#u zRZM+f!w&F;I_v;PzD@ta=ia4%;rUyc533A@4`Bve@gDO5M!nB`fKJ#6w{61?tMQ)= z*a3d{0d{~3cVGwjWg~Wg!#~0fYnXq#umk+!6YKy#Y{Cxk?A_P_p7j-WDB=0nZ|Gn6 z<{tVNp3%xUhj0AIIKPYMPk*9+VK2;s2mMU{!slQ!{PY+4_inD`{!0JC;@{|Bc;D~z zFI?VEzrpkVq(9bTufORJxc?u<8?3R>PMG4P{&0(ndX=)?3F-w$m>i-KzJ0JmG{fk_ z93t`__T`ZdkqT!W;Shyz*f58vf#%^3(FVVQ(f1k*t|*5{gGV3b5Jm9gqaC6awnsa} zK4>`3AIr=8>w@&V}|Pj-kzxFyaZ^5B9q9ike(agIZ@ z!n@C-ymjoK$2x=+=3e3uCGY@jfGfw5&;8gt#UYa5R#*V@##0{{KEWY6pn0N0#6Q4( z0p`L#QypRpY?wrS;r*97#F%o*PjiTDIOZ~ksDkq@r`@pg3Wtb&5Px(f@$iAGh=-?4 zBOd-fo%r>Pk4)m>DKqI`xDB?z`)ARPhv7GME5MU@BY>Ghr3XgRj71*Z?bF^n;WKi(m&_x1RKm(GL&NPw>-+9ikl$-#|H) z>>pq}?1m-qghv=RFc)}JN9li_=Pt$KO3cmLm<-qXQ8EDTR(s_e<3MaxWcs3@NSq1H^MylC@hAb!Af|{yR-wI2AkkXTbXaqah&&_L*&3+uoT|EJoA5RQjs@LpICqd&$^*3fR40yn`-*aa)#Kc7%72huLsDtb}Ln zVLrl*a7-=jgIRE53-yAnuoV`3NBpbU8>YdG@2MXwfVFTF+zmTm_-l-(z4$el0h8c; zFda6)T-XDPVNxshfp@}M_zK(we}--F*dLf*a2$+!oqaTngD=7q*a0(Pb{qM?%3m07 z(E2BKu7iIwUSamX)DM<*k{>K^(Qj|iFTJ!EIuBqc7%NBz2@#=hVhot*{ObIoK(h;Vc+a&psLE!RHPk9F7QcinzB8hNodZ zw82Wa^iZeR2e-ngcUY%~Izh+$%@DWUfUmoWa1@MLwNC$_FaEh?^+1Ee|{27+Ro6Xb@ zZiP|X@FOP@59h#qIC~^|_!w-3n?}*j2F5eA!Z%}S51erl{rmy`W;EgOLs$VrP9c0d z`-9V*q6AJjoqS*^4Bdf$KEo-}pzSQDsDeKyP>)9TH|J6hXoDp%>pxCW506cxy|D5E zr-2XJH%s9!9n@Fa9Jycp9|A5||J7z&iN-U!?ni^Dee7 zkqal=yTlea!PzC+;cs2E|3~Ig5ApC)SC^=Qr}t70oCZUG!XLv}_%=*|pC6!~;Khb+ zu@x4Bvtgjd3-Upb!$$G}xE6+RDhU@I(# zQBkx5rodL13k~~N2ca1@z!caCvtjI!ve3dGNOryG0E=Vq~{yfibWHCc((RSa)Fx+z?AY!p$d<9?l)j zID(-klV1npYD~AthcBK&|H8&ou`67A8ut8~^XGAl2e|fh#sNI@jBe2cC!I+-|8U+K z#=>i0B3ug7;4+v6AD-1Ms^F&i*aZ&HCH=pwqqow(@WkcxD~!LL{^{iW`U-T=2Fqbb zQMYJ>TkfDAVCS8*%f|V>mEEEMeo{<-!G~617kJ)Xj3+ziG4CNCSP3iOKlfq}Xepx{ z2kY(q*bTn=0P*mN^~A%(3gVrdKi@z+oV1a6IO1vI;o`T6@1kDN3V+zqEz00MU(qix z`B(a-oBI9EID#Mk(JhMLxgCrPIOHG3O%LPdU*h2}4&vdy9^&Eh!+L}h-X7B<5?$;c z;(J5^9C=QUsDr!D<@@5jl$Y2evS3(JkEnnTT-+m?VBRHsbMOG?sV4S_GB|${;qZe? zdqkvQ-!g@GI2UHaLRbLT!%|oSE8%==k7$LHZ|D(bLx|yy8z~QNT1h;dxrTUn#uLQz z5JLEq#KYU_h=a-ZE|?sZsOsFuX@C8`1sd+_ne1EZu+K2B*FNW z9+3xM+}k6n;9)=TeRWv!Bkc|iG3?=+iWc|--&f3sul~w+(BbXB_lOpF(4VyPpb*1} zfAQUPxUGZs!6W|d5nJH2&K}VQbL>4L=HL*+1zqwRjUk3lT|J^0o_>IShLxg6w8Jk$ zc;5|=wX_&rA|2L*x&)ezN>9m&kyh9PSdO@bgHQ*b1K- z<`SK7(UC4OhR00o(Jqk*XB_JiWw7@+muP^Op5PKrxNZdRJ{=Nb*nFZ($u1EY5n_06ic6%x z(U-eK0UUpYOKgE>rt@AG_~|s4h&r6{ag9r)!l^S{q7Y7+td4KB>A%^1?xr7xS$a9HexN9lzA%a=F3$`8Z zDZpOCLJY?)$6oN%+p!nCcLnx>&)8hD)oZaAeEeSQCI5XN z_TuGEh3l{vw3K5nxZ@%01%Il*UU29mE)mDepF%hBPFooEm`jwymP(gsgwHkDp66X+7tDImCFIMa?tIB565xzl><^uzlHsI zOl!$I*dG?Xi~V8td)Ob2X~6#Q)g9QM$FZVzVt<(Q5%!1YeuDks_)oDvjQb4x^T=2C z7uX-J`x^U0M>F<^|Jj56;e_w8KaX}jvlsiroj+iIxVa7c!_F~g?UXcTTITm>lNj&3O2(0(|d*aB>d=^v>&!7^olB2dTy`S4KF^wS458vF*L(e_y^2{qmy{= z0DKwlf-x8I4uX>@2gbqFvGgOXhxM=~h43->U6=tUj_2J5@caq%7rYL(!O)4lBK8#Q z0Mp?MuoO1IN_cH5?Sj>?6UI&=-%~>jY0v^ch6Qj^8u`KIDdcw=e)Dq5gC|>QH=GCC z;c4l;B0r9L!5aAM)x9F@bo}8A`U|FJk{@h^Rj^+h8l~fT8grhO4p}7jX1-)DMoIM?b^op$o3Q zp8h#2#BfXw{R6ka1~_Uy^*S57-9UTca##b8&n17j^hU! zTFzsgSjIeo_rWIE1)~!~3@euRiY)j&EQJ>p(vNT>Y=c8?r@Zq+4EMu)7_)-3yistc~smhvS9`+hY!M9SiYKZ1RsN8JP?_%rdL>D6)b`O zDWN~$cIbo;-bFqaF@NtS9A3Vb{KtkEro(tx0dwG^un@MwYB;5o`3l#;cK8M~T#SE% zW_aN}^bgz*E8z5d8K-aybit4^+HVOlybTLs#{Iow3*22!zbA(nG9N?_W7ab-;fD|P zihc0ehq3o1_~{MwFMJWRzHE{K#^fP=5cEaCb zR0`|hX7YoDRkRPj0Yk^Le|i$X1iN4kjDL#$gtx+a_yXJqBc7(;CgAU33M_@C@L5<1 zZ+M1&hc7(GJe$b+xrKQK--C7V=;wPy3)~80Qt^K;GCrUS*207u>J3kMiTX~$@4$FC z7pB7RUM3y1!`<-eSC~JO@dGd$9$t$dgD1cW7zek&&9Dhx^eX)U>tXz**zq-V@CCRL zPJEqt3om_(eA5^YFbkUMsV6)Mmcy~I7M8+x*Z?D^P!AXnhrG?Wgr~ziXoY2P3v7cC z?_lT4LJW_*adESAG^VCp%X6IMn7BGw>Hqv@C{f8 zmwrGzT(lj(bOq}$EP=5*s5iU{w!+mgd@6pZk^Y6Pun0uVEfG@x%*bZIL{3ZQ=4ef`S zFzGAW3)^8Ew0zC@oE~DRg_*Dp=D~NHnP0H-8_JnMf5BMz6HJ6{Fb#%&OaH+8;YN7! z9{dB$fiCz0G-uMkFaaKbsW7L7dc$?F9Cm!ic!2*x!%Y11_sk2J2Q%Pdd$B9Dz-o9I ztcUl(CfEaA@ak5|nZ^3rM*Cpo&(wc5$2Bk-j(~CSN|*sx!UFi^KJta%!g`qT8-4)3 zwIBbLMZdITFZlf*^gn$6Pv$pl?W8?(=zkmWun|_novLk!=*L}(m}9-adWU?JQHABFYsE7$@B zbiu^%10rTYh+#fVfZh;eq9S}`$5$u39Fmhpt;WHQyhYUX;tnfmZ2Mb{td=%EePhb=L z3wFRUQRo-3e}nPxerSas!93`KWpL(^*d5*rcf%d96B>>>AfobECtw1c3)A6Rm=9ZE z1w8j?>;`AS7PtnwU=55}O#NXJ?1q_eR5arqrooNydAJq+0$brB$1vWPu#Ul4xB#ZW zwJ;mj!eaOxtb!*Tdw~1Q*w?}~SO7zpa(n}0;rlQJ?uXeh<~a0l3ao;+!v^>|Y=f;Z zG#|UgpobU06nF#7hUKsrz74CO12(`@jz>#z>)gU#^Jk?7$iFuDN$025&q%z$6Q0(bycz>`OzhpDg`-U^*?GmO5K_QE81 zR4jIfvtbdehE?!0*Z}{5ZE(~{^xHD#GmM2d!4$Y2W<$eh`VF21tKnSO2!Da?@aU82 zx8>9a#=*rf4ZaWaU=J*V31gTq@J84KH^C10DU2+noKu)D@GNMBv(93?LhCr%3y(>m zo$wi$a2xxf@%SS+2bRLOuOeSqbT$2OJNr+V0@JUd{;&;hg)y@j7b_S~uo#Y+O@Bc9 z9Q;NR=TTq`Or1+RV92%jIrzy^`U|!~C!BT@^}GXn+)Tf~a<~tEaSQ!_Cw{7c_QPex z%yU?_=72D-LQn(N9hKJtG{D&vQsABdX&;navK0LgXeBis#1%F#d{3`s> z{p1I?!YVlV0qP0wgORJ52QUc^DQ6zQj`h?Ru6c<1!YvyaXKUEUKZ>1TH{1t@KSur~ z?5AM{Tmy??{u9_A*28wVwhFu7h5vXGdqB%m^cS2Dx58rB3eSE9yWGvX2Gig-uo&L( zEcS(GKgT#+%Q_0<;pi>&ADjluVAU(s3!Yj_KbErZfEKtMro;6x2fhRgW%#R%2lxu? zgsWb|?)R`Sd!6wBL*HP%hQ)8uPxrD{_5Jo=4z8I#!FK8kqw35?yuzWPWZEYJ$mVctq2Y1nIW)s3Mha01&mSd3JzNg=!82oph~Lb3f_ZT9 zNkVLd4%i46juyfR^I`ZC?4M2+A`zCsY`7j4z>TmJMvf7p6+Q*Ss%Srqht^X_4}XVc zaO$Z-?1EEHr~OZ|A3uY5c98Emh1IYfw!zeMg@}Bb z^NIfvA_?w3kNjYi!TlGqY^ouoFf8P_@X%1)jloQC4F6^B=@dmr@gpK^LPYrF!-wXF z7O)w)@YJ(oN5&Cs;lG@JIt5MQqFY4RYGZOl_)62b2(xkDkceDS2gt4+xfkt;)`=#q#?0?>qQN4G=!o=p`=|3&j}U>T9dmjcU3B<*hg zd(GASrc-BxU@8cD6^m(kN?n zO+*-}w#j(@OCZe{d#8}U<)36KVX=e_(?Z-~nS_lb%;Xc6N7xYo>52(EoUl+&I@#|` zU2Y2R6tX?C4O%^xxT_)iy^i>3;xCn%Yi+#bu!AP8JZN?38k6zMA={F-C+|qcma=6T zB-uy$bGkak44E(K1N(gyJ>uK($wQ4(WzaTkxSe8mM1&>}EeHzH+MZ(MzNBvdcFOng zo=Dh*6es^=ALSC3N|3$Wj~2&=IzA_{~088M0E z@_oHydOF3Qo^}S=eq2ORh_Pr$usV6D)JM{vU%5KPvE-vuwuN_mDfQBBR`fLoI>iWn zOTDyPq-Xe83Cbh>MdBayi8o(2#BJE*p_%g6Hrdw<(Sd%sBWWx7G>~Tpd7kIVvwsdQ z>Ystc$?`i$+e6w$PuigVnGn$wV$4&v&zM55ZDUYuC)*v=;Dd-z*=H#X+(n{OtQas) zhEtHml82_CDMnw5UXD}M*c=};HplyqOk}biyU3@3e9qRly@d`|)WYrS!Vki47c7>NDb~ew?8Z*&t~$J`K#JK!GBt6KDic6 zCz|mdjaJVU?s`TNwww54LTutcGM+z~_ddcdBrHb;sOMC?bc>44nV_n>EGOw;o=YOn zbeSiAax7X2yN|FrGJtaW$0APDyBxUZc_C>VNW0FHc9jtyHo@(~vi)3`G1KkPKER{7 zZB>W%7$eWNd9=R%QO?6=;?Ei&-bwtV0pg<%;hxR`;uDF#b%6K`;@1rjUqJjb1H@Ml z|DH~KH0`S+{u|=Y@U}l!Tc9-i6Sto@vy9_U_R&7VoPpM)eb)$;3r;#>7rsYqzv5v5K!sJw! ze==+rVaErAwGuWYAgq%x2YunI4Lg*3fe6DrYJW1n7{a~?NEc7o)_|}S!k!NZ%OGq6 zVGJ|vPud`tuu{TuWQe*SEym1X8_4y(8f_ighdo-Kc~u;^2QrhUne->p?!I}b%@5-Y zx04`Vrg4#G)&Ob7$aF)vr;aq*`s`zNZ{y6X)|==Bu@Pj(2_4#z`k>#*(JbHnQ$vY*6C~i}&b! z_eTqSHl%V~B#<_Nw0-*+Y1>r7&L*thqxZ4TB0u}E!wskIVJxD!GVR-k#b{qfyVIlf zEnDtBrvR?1FY?6e=_`r9UPmwMwAB-T zuBUu&zenEaH$G)q?WE1q$xGTj{4jU?B2Qj{cDOEJEtcchg628$JYG5u^*FYe?o*Fr z(^Xn{&_GTis|s0{Lv3QMqAtc9MwIsuNp2PWcIn#pNSo2Wh5jOsex-4_wm!(Uqm#H6 z;?D8Jtu`*^5Y7Gl%}AW=zr@YPHz?aPA!2KYu}10bc^#~BVw%Y+hkUz-*~D3%GCh4e zzwbCNf$&n&95UP{p7BrPJvO{1XdbxzSOdBObhA7Za@HGSEG8H2tseZ&Ei`M^}cfGTJ77^|Zxz zk12JjI=V)5mmkABB-rN#+D+=_>*%CITzi5|jL^}k>M7@4BDy7`Y+{C|p5A$9zTMB= zRS=&|+Q&(IopiVXeW)FekBi7NEz!nK!0tmk3{trIEaf;;hnj9s&ocL(L4HuT;FSKg zz(N(H2wOndTD@by2@y4inyyz*YN_YH4DzTw%_he38-HqEhCh`)CXeuM37;#&1NuVx z`V9YJsA;OugvF`%4N@~pp=m~wgeF_3oaqWpI15@Xngu$VEQQ8`W;2>aIvTgn^iC_S zJ^5(9L$g>%bA3QLa(%5vbL{E7%R`~@j3JA$FnI4IwQXqUp}l_~?do8yw4<3#YdzXP zJ0=qTBjML6@{~525zq!{`&=}`&lsHTE72sQxn8H7nTm3Dqsd1z7~6-@*eW!+I_1n& zl#_&}10V$UrK?b1Q`*@Wx$xtYA9j^_DH(&*Vv+9i=RD@Ze_`H!Xs&Hp-=W#3k!`3X%vzXR6` zk6q%8Il+A^wJm6mJ7@4dQ}%UqH2ZF}eS0dOzP{SGuVva4(mqVuYxynf=#zGaZ(1|a zd8BP3tzO^CH07k}B+WoJsw2(u2{v&#>HFu`B)_@SLYh?4{IBys+BNbR#w41-__P!> zZ=$L2)I~XWO4T+hLVMJ?HnDji?fPJ??Du-Kg=n)pTAzN;@a=b*b{}bLNc-RHX(nyd zv8-*R9jN~kNi+061KTl^G-r}#AR85tW;$v9*Eu0=REy>wH1qi#I47h-pRHJT_Mv$n z&D{!(*T3B!+|P-mk3EiYaUSne8%SFctd;$qg*FH6Y>(E*M$-aqR7%=MNc-P>q3nY? z(thNVukVVdW>RoJaae{^@mZg1NqawOGd*ehk3Ed;^_gR1c|0rI+)CQ_Nt@%JujiOD-m`c1 z9!$H}|KsT=G=09^$7VAF>tZ48ndkp^`^viHl6Ed>`|=I!7uBAz63qiX`3GuLcHWKV z9W;ZPQ(-5t{-c@W>8HSQ0_`kqmW1Yn3;w&!WZQE{dm(B2+V1I7@3FnR?frI;nvH03 z(exc#_-i<3*T#{*rU}i{Xm0bgBT%zcp$X+;T?d-wI-11+8tD@f(VTZ-e>uMW<@G0v zgMEV3=A$h@+gF}ayGX6ALHjz|6`r~(wM*67cC_99hdN7Jj4@-sr2l;$Nlhl2MQHl= zu7S32A5Zq%PmZZFG*6=WZ+<}LUr*ZiNxO#MKKl;u+`cWqZc@{M=E#d|VwH|&d4NXq z|0i<%h^DU`WuGrs_jwlDmwd`oYHw9*H=^xEdzYuKO6_fGZ8O@5V+UVn*+)?$@vmr? z==4!eKp#m>Dw-xVef!ivTQ3OENLv@8iM;rKvvoCTPbck5o;vyVg}fc$X)S;M^9(I$ zo*kqnaun~Nvhc23WjR^KZx!XFp!pijw>p}p08Ikv3een^Y!erR@Qce< z*BnS%gKotoHgTSw?sT;J9hNg`&gYBjs zAK7TOTspY%QGsS^+Th1W6T0*%gC8GZqq&ZA+2F^A1>N?`2R}Xv(4Bk5;KxS|x*=B% zethgh_jNk&6js{JV%!uk8s(UZIhlRzRqA82aS;|{g+gOR^9GvjI_1O&yxA(8MU_<+z9})3AVPBHPoB=ACIaak!4=pa6~ZH)b65!x_A*SgFBJEejB# zk-7|YKVQTCNKdy^tt&%!>I~j-tepS&sejeFMs)vV+LZTwak@Ul^h2;t_KD#X&MnUx z+_*?U6Ek~o<01#m-Dn0gE;hlI2(mMJ4ZBj>#p z&G|Rl#E(i1{-QZRghuL0(EYT~CiW_IWg(_7)w-?d?#{D``;`8Id4H#RpW#IJ!xE08 z^mI#tb+S+5Pv=Px(oTBfu(L96Zbsf#~3QYx?xSRF%85ZR}bQhWyH2ZbR z`9V>R;SA0%++q_qDEmFnG$)|jWgjG<`LKZVJ35;50FAV74w}we^~MGE)fpF+=r%34 zi9O1)(0vwAn5-ZFHKR+u%_bhwv+v!(_LaKGGdbtJf^+72y5+$-*(a&!I_|KEkN6!t zF097)6n$TaX3d>^FF{B1jzUv|=D3wMaX?4YsnE2c`58^8j^=lTCYnY)S!@&2l>MG% zniA0MvJcYuU*jr0ACsl@F-2&8UClioI^{g3D5n-pW{FMkc!r{{vrG>uH2cu(yvrsW zN==^W4}~V?Ebd8Ji=Wle{H)Me(M-I@CcaSiwZ*to(bvUjenIn&j;2{`9-HNv?D~2q`XO7Guljm6>oLaQ-gjSDOJv~u^Sn(wsj!{A-|!K|S|Yz!curqEQP*@@;u9nDsSW;dGIFWST=9Zk7H6Lvo3p!rj&vGBN=-|eB=wZ$YfD{E}x zI~~mz3QZoGxRZQ>@Ky4|3toBZbFo;tlX*lG$~ zgJn4_Xg+vTZ~taBsrGM?7jpkcJ^H0FE=9j>LG$7^zPF~Mxhz1#l4xi}^Th{zYcW*S9;WyOq5cgbnQT)uPHyi` zuCeQsH8r3tsYyd~$S%$W^E+@Ikv`$kfRd!92+cAy59w&`QD|z>e23-}9ZiEmvk%QJ zAKSzuI+}F~jrR8jJB?g~p2J=}+|PW-+SjR*WY3Q=7P2IesjrG{yL-LvwAD zO?<9Xw?;+X+R=o3W)qL;l=GmX9P?PN!GF&4i8`7I3Qanik}r5pQaN_AOqq(YQ-bEr zF9+8L^=MkYvWd%-)`Sv@$ zS*cUb=ZbPl(cHP0a|k-^c~8-vt!R$X^a-74Mzq<)oq9f@K}+VTGmw&EIH7D#wq-6s;IPyU^_YV{rc0 za0%BL|KwgFWjR*UOhq{fXjc8jvyw`U#n`22PY#;&4)yta4o&1%)o+6($Bz7-vH2gH zI8>(`;V;qFttK=!G)|=^&-ACFoX~Mxv+LB`_gIztocKA`~3=?B>d2=B&1)c^(2njmzMnf7pI}bqyb|Bo9gI&% zM`J#Aa6UnPvwi1rc5$!H{4Vx)jhc_EM>C4={I1i{tWjt>&=jG0RY#+`eiu85ee>~l z?>k%k?>&n*u2qzifo3k6Hf1^L>vyGShVi{(^))1m@*0x-hW#^W5|v}yVmd?Nf93b? zpC4ftpX$`@14VnrOy+!(nQyKt%gHhY-UcSulT0*qC)&k)rN(NSt*BcWnmxS}=pnU>mEl?jnl=!u>302ICi!(CkO^AoUFH2en_wLz9*?IKNPZ#)al1WjSiU(2VARiw5Tx!Y||d zO=Ab=7c6MbzIbqcAs@|V%i#7G)o8v<9-LojL9=(<;MVZS%X!Xt{NVgT3YzmK+I6li zXDP2O7oh1*9h_g-g60gq>#n`HCTQMenQRKb(2AyO%HaJ%w3Yj&_-6bXow<5jz+9Dn zAq~w@R}S7U6r-EUcj#5m+skXRcLbCr{X!j@XRfh}N}V~eUa^L?qd9UqV^v49L7_2U z!LcryFr_cH7!N2k>1eK*VHZ#7l(SKxDM9lQnpkByS*BwHY%BYq9?e^sdV683QFVOW zfu?LG-;!6BV>NA7loLCZ=Ywb4|L1!RGgCk zt96a&j=t6|?8@;|(uMd4U%iJpB@#&bJkpu!mRd zC*&&vO-%tC7mv=0?v|VFVw0Y3omy9d?z>yRnb7X)ffp%A*aiNJ)j9cv(cZhnPG@)x=YajIer(x4LM|+Q5=lwwVk4u8bk*uc$ z-TE?)`IT)$*Amb;G*VZ9F6Dl^I9fSwD?>~})aBKn8}*=FysqdM%?~`Q?w5V&Ha}z+ zAL{v!x7Gf`oI!mz;ve0IFUbX))T*LiG&)fO7rH<~S;PMhlnuxBY#y;qG%1CWK+OuEcn2X2Ws~&b=gsw2Ezfgsy&fb zZ_UxXs4QICEFFE}8yu?*RQI!$a_wHga`emI~-qh_Li+b(A7l`r3cXR<2qjndkW{?~WebLp2){qI&*K&(nVN?=;v&#en6PD$AF1CTbSPaog3`47Bp)y@ZbhmoBwdw0EH0z;9jO zcCT7nigqvBPxZA{<2JRn5$%#4gZDAA&Y`o}>!RH=P`QfxUZvK8c5$O!`RuQ!uPnw- zg6k~pT!{AYopy1GzMZV5Fdxh{KPIY1}dK$XY}+=^s7JR z_)TA*XL>_lpE!qSqdv1M?+N$RpUvMeT^~_^{*BX;yY?>PS?;&B<`7g)d=&9d>DhK% zL@e)uRhY)K(5P(4q~L~7&KOz#bv&zLvwPo1rqyMk_tmjesTH4N;94+A<4C)JwAY02 zkAPzg&zP|o?aGQRRaR?yaJ3@Ix}0)m*g3DqZ}}&~stL0a7NLc>!|Dl}91zw-SV}-x z8)0Ju!kmO9284yr%tQcgVogSi&nQrcGeJq=l}Ci47| zJjZzQ)avE)zVZ&@f7gkZV<2)q&ovNV>CyX-fq0|)x{*ZKd)8xvo?le7x28fi*vyP>6a_@GxWxgnY;?g z_cih@@#L%7%GmETq~EI~&9S|95hv5|XW_qE!pyi|pKg#Z{qlK5 zy6j6Qd1R4C-`?3jk38e%piJEJDV{H8E(Jl{P9N z-`Eg`2+{X*R^v~~KGx<^Bl@4wuO=^@wJH93rQCR#${|YM5tGX`6{BOI^EPJVt;$R% z=-FK!Gv$--4D#KnUssFqZE6+JfzoHxpxuY|y@9mqkCEi7+G!!5&>Ibgt4t1Yp zF$P9Z z=wwyppA3u4;~WfOW*NYr42vafKVieQ5O-K2VZRf0v?oligK32QLD+a1Ms+oR&P(|{ zpJABE4{a}PCc1#M2Or_^es5)^^!uUPl827dl4yJLjiebynsnd%Zt&(er!PM%@r|Ut zfV8)I(gw{5d1LXV%27Tu*wb)rkbGhm^Bkef(=)Ez>k}W}@O+q!V-U|TtG7~@d&Bh*>$j>U4~-Sl>L7Vc|>o3~@l1nJ}sC8zVB01j3FWOpZObotJpOEn+3^6yj37amEQUQs$pa z+*LmLOS={kb|qomp7aX4abZ>I%C1yelYHcQ+DyJS^3C-0ry}1h9;{MkY(^iwlr|pK zKle4;a}?(p%i4TRB26M`o+rI`yc;vTiP%?r#z+6h{Ny~zNB2Cs+dVq(JU1I}?w_yj zJ#r;!1!)J^zvtw5X+Df%G<$j@xd|!@sc;6kXuw^`7mkY|(g0>ay9<<>Dt_|+vu%OnW zmvb}rCf1>29Rg@}_S!4{CO`kmH)MQV`jq~EAk)@AGH4>7n!tRtU6hZbCSHz{jik38 z=Md+5(raUo53+d1;HB=-<(|)tq`ii;!(>|iWWTi#HjS`C8Gt|b*4r!(jeW}FoRCk& zwX&mc=3YRw$wAs%`<2AWa#BdUg|x5ur_<+rPR3ewgi@1iGtL=oCiG5vkw>-YLH z@LOe*ht3OfeB>khYZrN~A+M>PddU7#or8!W-bvc$NqdG*TC?Z7Rnk{Q-@^MKh?m=Z z`6t8T2z!yRzCM;^S_qT*Op=NE*1TM=jjrmmk(KCN(tb$VyFFm$3;XNuh#Y`oUd?y=T`eRO(&W-G^3@4L(I9H;F5L>FJKKNe1<2l z{_(;l-^YqLUfp3eq z?wsV?WOj((J-*6++;RG{CuqTS@8NxpBjxdRB6+2-=~y_@p}g1MdtR_P*n;tBi_ji5 z%He%iw{JbjK3 zeQJGK|6G>3$U^SFM3)w%!!h~DFe(TrByRJ^5(swCCH3F@$A%;x&KUw?@b_;%tm%pBEGVKKj0O zE1>T@pKkN|0O@u#KVFZn1>HHaTqfTl{CE^UcN6X)T-!Gg&e#34@OHwZPVpSa6TZ|F z9?C}a9Kr+B#}IB+q)#Ax5#ie2fP8NAqAs_2E=>sH{&m;oAv6%M*U1Cw(E| zdkH_=6TZ+BUQT!y;eqwpLikaqI>agN^yce4=^F??nQ%`(nEU#nh44!VKi`x8O`i0f zgkMYeSWme8Hd_+E!&h(&PWV#&y`q%|ua&-0`i(U7TjOY_N8i7G8h`K`PsRu2KfU{) zbhIT>dxk^2sCQ0B-UwfyE_;&B0xI)uCEw`(ID}z9f4@YTgXu_hdD4F;7E#|s&Iu^` ziXmajL@Fn$cjv(tf3*zq@!oPEOCMZJUL_Ye#L03kmYbHlITb*9z9a~)!8-2qzFSCF zdZ9!7tk*|u7!NYtaf38J^9FQwMtv>BHTszB_Zy7eAiUZK7=DoW$zf%lq0~s2aCl)?wjBchk5e# zj%$nYPQP)jed*RatQnR*gM60XzqEE2l5gcihxkdhL(y*SNSf0@a-7J$&KA;VraHtE z-uCgm@QJHK*O*LWLWXQl-jU4b!9PeID#uhaX}_4{@cG6dX;Wod@Vf1ByHT~kf)hEPxa)ftr4E%4GUo<#GgvMw4*FD zU=3g^8!)2e#@=V&AnU!0JT4#)+`MkRb*}$k9emJ%-zFP#F}{*KoAmRv7`1b#K}XN5 zTZ$tYMM<*mC6x0K<>c#^6K`Z*1s&uk4^`yoUf)~Db3b{Oc=Gg)TeDFco8A+uSNV<2 z%>H5P9(S>;`2P5%{pYPU+hGyU{Jh#dJKbekNjrtKEHNrOy8VaWK1hC9$H$P!ZwB>w^b;r;Px>k=+C%}^U0w!NImDcjvl9TU5%3>ikd}| zRV(@aaYg?%gj~Wp3F{;*%;+vFV6GWyXTW4Lb_Dr&PVf5Jjd2(hLiVx_*=tz0u5<{w zZc8nt*88eUf*U@?lEf6J&msKy+U4{RlC69qx3(iBga%b z+KE>=#MMD%at+X{blW4ENwb)=`JO&f_&|&4eD(f->7=lR`&ccHeD9g&5KK+~cE=mF z)x$d{iI#P)B>fMh-{eUz=RrK%mAw;&i zDeH2|+V3sP#~u^>`)Hn@1^n%ywf`Q2;ZO1^_T=S1uj9Fs;J-FsyH=P=KBvy%*jnFT z<{2MR&H+=N=0=0Aswn415%sL4EZbacrdO7B;`}XjVTtO()Hi#nL**Vd@m`)$xQ=V1 z`s;m`+?A^)J(qA*rZt$!w~Tye&GVc?U_1uQWo^vXlBS3>Pmw-&%$k|`0X8$TKL}W? zWgA@N^F8@&Gr99ojM*&HI&~YkuAyv0E?K2>0e{j0hv?9=k9;xeKk9sq>Kz$JPs-x7 zb+3W)cIL7+QMUhHg0Apc$mDqMAniFfIs~Uc1JW8f5ATzh{f&3Yk$Y_KKit?e9mW@6AQC5zU*PG2=hi%%++Uf5S;lC7O|o9Nzbn1Zq6*C-I&f zlbYRV@)tYAk&6Bw7m;cj8t}-X)P%8kU9}WnsiWaecsZ!Nr!%A`3C-f0xUR0FiBM?r zWI4AuM4nQUW}2taRH0dSE7zSA<5adM%oJR=W;BZmxyGw3$7*^&QBL^%95>(Y5Hl5X zdR#=J=}NbK{QAIxCZve#(@IUMN%cAiA&%v@{FCRAG6;K+uv(?f z;%;>NC~sG?b9h_^`JEjzt>kbgAWR+u)ROlfcRK{jVBj3~Kd$w^+^QXmbfCRxZU6lo zvaX@!tX+f^4sqwF@PizC)tZ#f+%#1?CJS1<$V%?bvdDY#gZ<}W`uDrqT}+p6zB>Rg)g+}#ya3-zIh+*~W%skCL}gRE^2ImBuFmVa^l7faaXgmL(! z^|x>T7?1n^k_#`zCIMpJ3 zQ6t*F(aQ1_YvKa`65YpmE{{evcl-KVeI?K|QCq!&y4$@rq_YquZs@;`9N7LV0xBcN zNePqVD{%cFV zI!}A!ja=&q>R^rmWgo{qWH8)-9{AP~bF2)N=Z4+ys`f5L+Fm7##Gjz4)6tX#jfLs- zPVo7lYVsMo5ntxXr+*$@-@j@TC;M_YX_HBt=U=AmnjGK0l)l7J!9AIzUFu1z=o5?a z`rs}}B7G9tdbIzSZ&pZsA^M0-4sr7U^WAiY=3W9ueDYAKuScI!$#oosukIVa9Lxt- z9<7D{T<9-(oO{(2KH8(l@4cd~fF}+Wp|Ss(mLC*^XlJm|N`- zSLo#-_lqB@t0Cum1Nx;~`uBgnHk9ur;SmbIy^B1bo`}wc?lW|+c>BNqILdUt(eFUY z>ORVqhqYRr6E|?o_o72Qc_p47L_p1!wm>P9scwQC)9 z==Y;<57RB5`xrQ-q{zWk8!E$tyqBCik&obu-gEdo>(JjP#-{#5{V38Vk@o!ez1LyP z@;Z#{*L1=!CVV!(b^69)yfV0E=_D^hI~VPC{V|#4eljO$O0aiSHcPYDKJsn)fOS~k zUKUfEdYDRkjoHXP?^B2PTwkAJ>Q?LJ7|lWdK(j+!dq{Af$i3y?)ID&JN?j$oclJ8O zZXMmZ6d9as!|m-xDG_LX1U9 zyBEY3o{ zW=_18oaq$5jo=^Zey9mCja3^SiR{a4@;Lo+r`Q>#lZW&b_?zGv;r8=Y=wG|mDXNdu z)n}V3)Orh&HuNXl>J-isbo3Jdb*EbiKQrqD!gEFx{%q$-Qg~x_4TgVmZI%pR`2>VJH3I^gf@U z@Vi;YUzMiN^zoIf8|e4xueo`q7uCb8i1PE$SO4S`>H6a|QRkUI_v6$-UfZ}vIe3kC z2fKL6mv!i*4h26u2fEKA(YQj{2gW*eVfUzm%awJ=p$-L)8w}U~>J;xO`cZDwIMp5C z){IMKklz8Kyje6b=vw~}Symfm)$emEug#5%h;e7^w_kRj_Vq7IyTsuiG~d50wo?Y{ zW}X%N-6_7;-^=9btY2<#9Asb9bhR_&bcC|g-EXZ3$VMBx?bL1QpH6Xx{+^*M#Hig1 z6STNC)3&%L42C&&r}$BS?B=QOnHfKn8?F^Q5%tNwaf-BD5lUAFzpoBfrydtv3-_^L zSQX>Z=@d)!+s1lzk9uOeAHWXi^5mh#^jQ}97I!zMYwjNb-V4`!sQ9@?%VsCxK+iq3nI^V^4XiL(aS z-C{rXysPb>4)4ix`?nE z!bWN#^a(5{>>a;6*gLK?u`J~J9qY%Cb}MPOlUAz(YTPovQiQh^5sWSh* zc+Y)DW*JsZe-S@Z{_cx6PV-(Pv=AIin(&A&aiu3s!1){=bCOGy_q<2)P`NJnoWhXy z&LNNE$V2tb81Fb*skDnRM+OHO#(kc3EBS6G-!%tO2=x!z&&Ykc-i?!{+qC_fB5Q5E zhEC+43zeFFBl@XF zb@|`#)_2`m_{;C|muvbK^sgTMf78qH8}lq<`j{^9uBZL}cDEQ`2r|6e=cJ>14Bd|d z_1jK$zsWIMjz0NV^_Z3MwZvaaympSZZ_HlTKjwE6e>L$Jddk=4#7*w_M8Z0WUrhXw z9=mwYrCE(_e%HY8G~UZzoHR*78~+^VvybZ<=w7RM($z^m zHlc6g$#!leZ7FH{e2HvFJWo{1xh?BbPkbfus&5eW%_&nA2L}o#D)V%qpD?0JJg1mD z<08^cr8Fa86RPz=0w-SIi0xADT|MPmO|OOo7fYEI{>w*y!RRji^Ih7zuU=4hB6`_3 zb>xwHYM0OR!??!<{bQh+_zdFBr1knWpL_D1#AgvNZQ_mhc~406^Y|6xsE!=e+)@qn2+`u^4{$A3qj`wvW%}O%_i@mR{BEOC%eeE^ECZ7w|nCC z+8q7@V*rwiar*hEdE#aNc>8pT+a^)}kG=Dcvts%m|LmP@?T^aJN{B92HYyt{ zA>8!CB3bkc(Uw}9wjcT>3L9Bb*hoT%k`O`?Lg<~|gph=IhxG295R%%xYrfCd%$YlL z?>%$h_WS*OzmM-9JKL+7*Uxj#>zwmCXU@z$1E~UjOZ4rwPj$(A`jYRd&`(NXcR>oi zjG?O(Kbvbg#O6k5?Xc-DORlee_rO0ffG;{Np5r;0n^$5?tbTKcd$o(4$aX`v+ux9t ze2qr7C$fcH$9&{#x7F|mB;<=9=*7nKCGdv?=!Y@64*ocwzKYq5_|5|q$-^#WCI;9L ze%=z+d;<6~$0>q82fi%FNEvQrF;kw+NL`e`n+fkg;c-Y?7y@k;w1Gm{Cv!QcC`(ipRjqJ zRwpdKj`Gjpoc%9eV2_6Oe_u!PUV=_5bo~9!-+o8KKRN}!8vbzs{BGD^0{Gj0{=bu&4R~{i1Q9n(*2lo+oPH6cE)bzQsVG&d##tb4(OwifGuTg ze-@)(i@wgEQtop2IfcReY48t$AJlFZ!9Owue+~R&1MKIL{xnaJiA%;IST`5lksX;QD&54g@seFaeX|0V5bUH5w_s-tcCHDI8HH-GCMjr$e*SXieVz7N z#|7&bnMdmVCHh0q{~i5PlFy#e1B$_piaUr5rdM5kZpz1G3OX@Xh$W`wm~}xn){BL$VH*Gg0_z+azudKSJ`t3TC3wgbp734PYLMo zs7RT5W8 z`mALiV7~;A)ZkI+4YYWTx%4f2wa-ktG|juY5zj2;MJ~7ceip^@SGDn zm`kcVvGcs9uXRJ`;^}*>TCGlD=vR%-M0CP4_F9~@>AaAjBV}BS@FH}IwPh@n79sJ_ zU&ab3NoCw6cJA0~t#H|779DLVa>z@JX zycw@z_nm5VoaC zWD!Om^M~+VP;J1p%a8sU&(eOH2kH+nzsc6>OS@fz{spV{#`gcC-F9)>t!l96s|Kr@ zuiI{ow^++S@7JurbcdKXG}5Lk`HrA}_J@0|#Z3eC$C@G)d{2zO*&(f01*|lkb`C77`Gj)$eb7!1ef}PNxd#x`6?5zA7cI@(F zXVxC>2h(-=DKbX|mH!={-$v`FC+(yhy#k}gx@W)S^tw3niHYV4{niztzX<(F`_)+H zzJdA`X5V1_ZRnpKuCYGfKTv<7xee!OCNz?M!79F!ctVZ!K`2l^Vm_tUFF~ao{lOh; ztQUgyJDJmQx+eV#lZp18$1AY8vQv%qN-CQH@0ICnO1sGY2Xp6BYXbUHv3XZOyC_Dl z{PY@Yx|!@B`Hjva8okMgR-*S>mm2Gf;4&2I--FfvicbHFJ^h;zQm+QjrdxEYu^!U& z1Jy2z%(wORBKi^Z#~0UF1A_Bk5b&uad)%!=f95&=`}!-}+!)sFg4mCgYg|?g7^^wb@_@EIr)}g`UaEV!>ABXMoqrdy68tY9> zKT!2CzZh zb#-u@9AN&bACK+wqyO2m8msMrf%zX`4%5FE-&@L${#zf^SSJMcr$Tv3tqGH|Carxo zDEA`Uusipo8mqa+FEai%*4)3ee2+Eobv4%50G(kPof33j|G36FQImgui(nqB`Q71E zbl&*1#@a`lPTNpj{EWiBA6t&j%=I3`f<+bJUW_;#Er#Cw0rW)&VZQk0Ny)}88hEBV$8F#cg1?JTnofYVe`=-Xa zNL!w6=Ghuw??mUG%{5krHl4PiXnb*OU+1sox4z%j&)5CYY4m-K_WMvW9=@r`+YEFb z_@TzSHNe-`Yka*Dou9VVSS_{XDKML9{I(07&;C=Reg4j)NOO)xr{Dv&+WN7^>J{MY zZVBa)xx@f;F8is*>Y>eBq1h^Vu4>QCXQ4Oz=NfB2ZCb_Vc8#92yS3I{tykP$kIs6twqCI$a~;p$8r9Y-mUKqv zlznQgbZz@?8>-R7lF{g7XV%s$ZZAORsjS+1#gdKaoYAqCD$(h1Xl=dX_fm8YI;_@et&MX>oqS4ub20Y zYOO+T-iplDn!NQ!r?h>om8Q*GPxDWW&NOt|b*Qb^ykrGB7CQBr1MWoUkrQgIueIeV zF+bJhE&nstw>#Eav$W-@FmKoRx<5LLPOe|vo`KHor_@?qw0Y}fo}|g!N_6h)TpO@A zNQriDkX`7EKO-p4b<)PUf(`6x5UI6VY4cWM9-ztF0CZ-aS!-?A>hv}@Yjmp7IjeiE z_W6fAHk_^T^(u5;EUvW%2E^??nz+3Ooo~*rweq!jD>4t(_zp9A%z=x7ocDLd_4r6-zv-f1%MSLTAjtTI)k?d3u|xH03Err}5zW#gMt^R16K8#}#Ss3D=?X zVp*+qhBj}V%o8x9k&!)vV}TEBJTM|;Wbtpc6*D(V+Q z=ArY_$Xf0CXxFCf$D=9FdUP(ix_&Vv^DCA@$JK9a?2OLN@%4)#qtV%XP5s*S0(2g~ zu72~7jp$6ARKKyY@g~-SudiPWiJ-IS#-Mq21;4OKZU^JgslBOwbG}9Be1CJTRiJIx zMdo3ecCZPZ&u^)puXDdK8*={EPiX=GIydX?;D~yhr2fp6K+RSHH2b5}g|#tzQgTiq5F{^_z!m zL+AK~wXr>^6351LO*?40nKt@Ft+hqlcDk8gYII7_S+S^oF=Q$_J)f?%?$G+W!n|4I z>*eS?{%rkxy#t+f&($x6gumtYCC}F{hV(|~wx#q1t>4<385+M$LucqqwbsA2I^E2# zH99NMY4LLXV#rQ(UU;R}x-Y=jcW8W_{~hnPyk2XSY4cXZw;qyXe1CM#dZT_ZWCl7H zuB=}SS&2^jcWSLGw0Y}fUaZO6E_5ziRcnPqM$$7j*@vUKCWaJz&wC!L>lZ@?p!4&Z zT5G=6*Q3q*6MWs9>{O#uy0+GeXmr%`vr^|a%ykTJ3`=bG80wwh>HKVz=_UTC00T z3cKbm{R2-_#KtOYOx;#%UB$ILGfj9;nq5id*2nkz}Vx{N(F5wEV z4X^H!bBXt>1p7VX!fm|EkKPSRFQKo6l7ExHkEj!>q!YRmvD4yvL4S7r_*jx{y3N7t(BYIb$_pBx@d5@c|MB>(i@L4Y?zJY^vUv$IU z**^k1zi8~JGKUGUn$W@rQZ#QPupLGcwH({iNT(z%YPHsk<8C@PXwwOJ?IBd1oDk_0 z0J}&lI~0w5FVJgyvY9I`WvED}?YBiX{3G)VWQTGs?ZlInokD%Wu%$njAb$n&ES!kWdUSY9my~yD+|+eK+E)t2*FNRT zU~vRHuC2pv&&+7RT4SNTnN)&d`|K(*j)knm z;K|5;gS5Eula~3`CS-deyGF_S?W?{R zaXxjF=U~A_S^bV>9CH0hUy6cK=(C{zmkB zYV_IG&)@y*n34V~V*>J*EBVCwv^#p-M3cEAvYpX8_^@bf?;%c&NzdNR(bK|O^k~NvD=kKDA zA04&cReANwNHB@TTxO7Lofs=6qhW94OVK^0AZq;)To;kh27M7856DymyUVfrd7G%U zZ%{0?_w##AA4}1fJ`u&nW9>C{C%b~g%H_xw{K0RGke!KMLLa=%FDrBJ-pCdm7maE5yh8( z=~LT~U54yY!TqPYaSXQ($1s5-qd`dH1vSBt?GAz#Ej&92zKwGJmZvIm3Lle2ss=2*|%`qYrwBY z_A6xDDOo%3-2c0ImvM3_GFy-t>XQl6-<6DAQv0`>@TKmyAb(I%G}V1;B$03sfr?Z=?W+El!n2g=4Pvl;S>sSorhTkqnclkiLTWY6%79;;8@+t0j z3PV%0^`rk;49OM&>3jM9Nk?zY_O~sWhYr$=iK5pTy<6(3C+&Y6dYjR^hHEKzVtV#U zk*`g6&!I{sWcX&3m8h=3&b}u_t(L)kx>Pc+nQIV(H1XaH*&R5ky83FR?fp3-HN4Z> zDQZbOi`C%-r{biJOX0r*|1m#bHmFfmi)rEX+o9hchHvY#XJXNf$AlrIA9pMTSa zwDZx(Z%1BtudHH>d~{AzW%epdQq^?T$ad#ORPwfov<^AdyH~Qu4{ofL>+MQ#8Edh5?fc2 z*7(k(#dUn1Lvafx{bBDw_GM&WSF-VKD;!cEW%BhhmUt4{7L6i-;*`0#jy3bsqE=dP znM-7k7fqQRa#GFqN$#QKTjKFj(t3up#s|*}D?@7A?Bs}ir`DNbxA9-RpA#V-rzk_f zw&-+1O+3M-#K|Gp{jy8cx{2$AI*PnD;Y&o*m{|#AbhqI7ff~*@q zVr3~ck7I!H<)7Z@i%a%`Y^=(9#z{&$+=1QI-J-GYf!k%1W%Q(g=#5GeNAu~_&AUgf z+qrhfxWZ6rOwW^6>wn0PM*i=ND^lL6$j?Onjo^M43H?JGJNgIpJBh=au={6^s5Ly5 z-E~^KOgJ?CIba?jWhf4%8N1JlS`Tn7K1%AVJm5$Vn2cPkP0VRy^RU~gSdF>ZjkSgF zjb7E0r1Z(P$X^V?3Jib-s}w61K3XUP<9v?o}3`Kj)liY+p1f>vCv! zLAzI_nK+iQl#noEq^Tr~T+$~De!Q~@JEN7In9t;?PRvVPa<1|aJEnI+FYnx_b$3Ae zELSF^?~Dbb(Hn_g^?ymve+MpYc@cW6(fdg0CABZvLzD=v8DH&*Q-Wi==LAfgq|Wmi zr5SB{Mq}TAO7tzsB$O>gn@Dgj_Tz+@l!@)p*naB&f^BIhYq5Rod0sn_`rQoe2xzBs z9Y6LCjjP{teDy1~T4bad1?b80iX1XFw1svMw5~#k>xrmHQ}#=|49H};?z#~zr$am z^b_mWetsQyH<5hrb#;8r+A*V;A1|NVD{75WcI|u-cGS?q7uCI(QHab$sCLgS_uhj~hHAQyQ>Se+(~Of# zqSk{deY=eGE|q85(%!kIt&6V*Aiv;(lxxN%0pA1<+4)GC6FcjXOR>A=B5xe`VwSvq z;A>m)69T*c?L;@Pchp*`{GM3=U&%r>JXleOhv5kq_zj8_~b6U(|Xgpq$(V#3k&$7m~{c za=)NATV<1*W$*E>z6TS4_=EZF#|q7 zC-Hq6`f~?*b11K_$9p{*xz4~+WS>EH1=j)Pjf8ZgK8-w#$}aTVltyFkTuGgkgl>qd z*A`9EjN6dW)rqv{PVnc!KbvdGLqZ$7F}`;><7WjrFZpc7%2+reu8h)#=OFtAvZZ?a zq2%XuvR#(14WpL}Y{t$8>>MU`IK=iYXq*2BYa>e~>Jklnlz zW@Jn;1Dcmz=ZmmAd@ys5;PzE&PSww$rOj+Zf8-D^CTQl5k&yiqEGb`h`UU)e?GkE#Nzo8j#X?+~#KuOIIOmUFoWdNb&{{Z`ams3hQ9&;eh| zNC-uDoy))5spGhXf zAX(W+7~}1`LSK;Rk|W>Z_7UtcEsxFRCt=r~ce`_J^$iM%DMj#ahQF`mmqTd9&~AX% z)TS7AoE-pdBD4uY!%h`(wuO8&LE8N4uJ$^8B{J_J^I(8XC+G{1>2g&xw&%a+AIjsK z?u{2Lw*KS1W3(CFsXpCAAB~T9kv(_H+ds|t2%WL9Jn`&B-tR8uv57Ce>%8Rbdw`zk zWe?Z9gEd+nc=!~tEA4(V@?(+blgj#b&zcGSO50&eP+5WgNu#`ZuqLLJhI(n$hb6aF zryg_js55LHiG4Yw{pLfv71|L(aNDnX4lVp{@Na`3)V_znzX^UXrSH`r6EB}ze6O$A z?Po4}bI_AE;HDd9I<7*dowt?9EJnu8uWDmq=lf@?C2yPIKbL}U9FS(bkb)nE|55;7 z>L3DtIs5@CkD59twBH^{8dm!0s=$a~XBIYBVso&v8Q*5bTtc^&7v+4NSn{Ok8Kl6Tj8j^JLsvjUQFYR<1>Af*V{~Wb5{6+A;gg=Mtfc!89b6=#%kmzqi|M;=q zGj2_JD$}_6)u=o3pB8DxE$A;zmA?MrkgZ>i{$BK7O{H&t?lY;Q*zX`MLVxVIs1*&? zFAVKW&c5`8E$E&w{(mk`N;~ev#|*B-X54!k-Wv4EV5%v>_ugJTx`on3I-_|(jF`9cp$4AEC$FM|2^Dg1iMeRJS5H7 zhP*%iMrEJ z>9--6Z+j^BK@+3aDy{=!aKyQhN{V6S+N5qsPQM$bXOYg2q!ZL%65kK%l5<5;e-S&I zu+!pNZw)@N-zD3*RBI>yFvel*xbK@K+Ogk>h+Ei9ysL2LyA{~E1Uo#Y3-;d}ZC%;- zw~Ntld0o`H1$FU%Vm^|~!;l*nNRsb&lS-Nh=KGhgol%6tzy`IuF$l(@1O*=@*5dQL2G=X3FM zM;YHXBD)jWG9??|Rz`;gP+z_#Yu8uf!&$?g9JQ7y9nE-J$U8!sDN^9$3VYlcg565& zE>U)4<3M5P?zkHu_)@l6$Q##3W8Yhj$tOIM_4~3jdTY^ZiQX8Mjwb&Rd2K;c0DF9G zd_ImBidS_B%8j)MAZX=s<;W(g{#=V1t)V%M;6~463p2+`=yJ6 zzKQqm(w#JPalw2{Uq1maaNA27NmD20c$&&&kw-)Ar={Bcdsb`O+bs#}KI*%Q(k_<5 zPp|U&ve4E*`xPD6-{RX%(0+vW-^L4R^ zVt4FxejBCoplJ&we6TCYtoWd9L7GuC!~Yv?uaEHkK<{(*A`1*a{+K(W)|OaWo_y%j z_)b-h%naneLB4qg2tK|g*+w5sokT9qnq z3Gp{HAwFiK94v$^Ks8)B1X+CzOG4kp^-c4tkS{da zb3Wr(-cfmw-+l&9T@%>w34ZC1-KXZ#SAzR*Wt!PVZy0^4hq>68@o>U3 zW_zC*c8YM@8$ZgEwV*Y~E=IN|*G}I-Yp*FB;q5Md~9GQZEF-mwZk}=5A!%by1NC z5BJ*LT=?@-@K?ZJ3O}fvo8Z45pf7oh!v7F{Dqn_=OEYSb399o5{OEtNF$9@5kLvR& z`Irj-c=)OEu?U%g0XD>!Yv7l|*X2|A+u#pN!OtvAGpx9u^w+?jlR|$R{QFbrXLd+49)quIi&94g@Lz!w&dI!M~QYynY+sCv%TyzJN?n9tz>#3_qw0 zCGh74@WUiK8vaZ0gZwBmvyk}`nL%88`HcPa#Eu=ykvVXFaC_MZzXg0JfDK7w9{g+I2i4e3Ra~}`p zZ-(Cneo*<0lhTY+Qs{@_UlgD(bsT{|1b(VI9)irH0XC$Lr^0_BMIIKwe?J9(75ptJ z>~DepM+$y=C+5WqgWGXF{C(kHKwjN=?4NUnAr~Xl8JUaYWa8)F706s2kdF9a2K?(% z@E60sIR$?${2B0r`oa$Qccjp7d~%vGI|aWj{6_-#;)9;>7r{@}7DglU12RGFxf=do z@O5R7G?u`(;0KML;>Y#K;9r;`{aNrYf*<6grSL0Kq`w~i_!Rbc!Jm*qKkw8u<3{*FF{226bqaoO_>ZRG zkAwdL{Gjs9g8x4JApb9gzdi+jJ^Zgzq`wRP&nftMooQn!>=(h$TojzY-thN>e~PdE z!{faBx^eK2g@3%CU*Yj*!9NYYuKk43SqlGh_^JB9Mr3YACa8_?fj>0`zr|_XucY92 zf?owcs1EzXeW3i!L>2ep$;@T~xT82u>x z%qP8l?3{aabpsAxWWuMX8GVom%BRS5L*{m5g2o5QYdJD6BNJ2~)8H?MA5@k_@ZUDfG9&e?NtO<{A9f2YyhWFM$7b3Vt#CpHrk?4*$;-`qSWNJ{9brMewuW2er2~ z@Q;8Wl>cq;Pe{SfjHDUeQt%7l_kkZ&KgIAbPr)yTewb!4{i(41lfz0y(c@P_oyAYS*2gMbUDMaS;6f)A*dm|G?CdgOg;5T|Y*cY?l zXTT4t%cbxSOrgIXev1_PyWqD8(3djioyl_>`0le|CoT+$^JOPwMyE)lKl~{Hd`V*> z{JY^#<=U;U#NS}eMdr5v8+QDIZ!Qk@(74FI&7sv$ly9oR% zQt(URPfo$Fgnuvmpt_z1e_;y#O876Q;BSV%CV(%`7>sV*C&O=-$#vYb9`mTo*h7ZZ z_A`dI$bPvrYW=idlI&;u#XV>_&;OXkdH)yP>eW%}pOPl&Bt0jR{rW<$CqJM-T%K<% zLjRz5y?p~T`jODoWOXUy7Ia6TJAmu>GRE(dC$hQS`OW#?kc}YQ8QBj+Rvx73%EW%I zS$rW;`;-=yZa^hAe)!z`Eo1z1$U=T_lJM?={i7b=yRrPxN%Fl0JEa?=R)lNGi@lca zy$5?fo!5R3R`|P+9f$0!l+UJm1U{RR=Z>9v@DAyhUVX&pJ1jq)O2|0(x#*2XujN;~ zOO_6z_u0-g^L>5VW|d7(dJ5QE`yftFmAnIc$j91mqSi#NN_ z`mOhTS)RAAg|;4A3D=3f4V~lL_ZGIuM3LWx{3{xH^~`W|2nQsUqf5?shzZr;l&z#V z&A9lxsP#c`pV*mYE(`1vqBaW~-~AS~nn=DlvT!^Vqa2w zcdDd!a-HYDXER6sUB3^x@#OxnYB-BlpoI=d(z~)_lczeD0^VGFz9vh}fm-XKHkDwDihj}wPCp8Yp z2Qe<)yfstXN>C{WhG653v^uMK&^rSBvM#hq>rC@DPIkRrpX*4gJiX3(KZW1q zCy1IhZ5HUGi{0M}&P_AEYE);5Sve$jcZRkG+6*DkCgWpZp}b$75CgA;;EUa;$vkE% zb{c2Y#m$X#)#DD~FM!_!{$X58IuhIczcW|`KL`G`iXVGVF%r7kH-y`JuJ1&)Ke7_X zlJguM6Q5(JE$8=SY(a-d20?AHB;dCRwx5Qef6+d5vG-r&{iOT-N9gQCM@2|_F?L?V z&M}Pw@tg$J$9xqI@#`n|r3!uu($9tKmrdn23Iq%O8!n7^O7?_$_Lc25pT_Ek zL|J}qpx({y$=$=--F_QNeXRFHOJ4gZY@LjU|Ee0cjVR=cgGx52(|MDyV=d_tb>C4T+p{Y_!XLVSh9Z)zZ1Lf zWA_-zFRxpa@k1PGN0}w8Z$Ljs=yB~kbf&*?L|_*oTZ`FhUEuF85`&$jnR@~2 zcIfb^Ah15o{q%!UOH@kGpVP8Vd(SES(eNLIe-PK=XFHawWj|?$)$r%Se^&Logji|z zrOPD@Dt1}dpx3NbU2MPG1iettcrE*P22pg1&~fj7y?AhGTqqpwHF-O&q6>L;jo#g= z42fw?OGv92xpH)#Mu*qc>~i~cm_p<-mq{p=%sk^pOPPM#ZFMQOT8HcY>-&#VpPP|w zkL*gWgZd|LPiwlTGd71W;yrovb@%6X|A+r3{PXq+v|pWO{+X^#g2gO_-2+hzMjgY5KeRzg;4E-%gziU(KJzpOv&=(t%u~B$z zomCk$#`b8sIW1_@B=lvRT#1c}c6C<2p!UbTWXSBNZ-4p`(R|n*){|}_?74g)1-_`L zF19zTrY=~=c|q&9&`fQ6*KTxNNZIC+R-Y5=V&BN{r$rxdw(jt5SDg7jI;H5`6Xa97 zUe3|ii`gkn*IpvzrJ!G$@!&~y!QVe1h@}PHt%yEQfsG$J)j9iz$BnV^dzsqfKeEQj zb=DEV?W;t-{-Oz7q35)2cG}!d?7oLxmOk};*lsV)^!`Cb>ZPbZa!5g3AZfooqwg%Ztkv%|UIV8 zXuC4aJScE&N_=Hp#@fqetW8Nj;gC6c9<YlC0>NU7$MG1tg+L{?s<7qm;o*-__V!QJ@j*)Rw-lXU&>}7dh z!}s0GirvPSrx{0<)>+4L9V_=xr)Nn!XbZm({y4>tl{?H0uFq^@L8UL2qg#${`y}1C z&otQmb{4W%AshE?tg=`akA1gJ{I?R>>yhoH?AmoT*prnj;(`3|-@d3sZWlWDp~GX4 z;J)y+wzKQ+t@S@*)l*4Q{4sGL>u`hWlJ`>PCEb`Sk&|`C1;~yFP`6VN=&#* z&P8#}%~!d~&JOHM#m-&Ij=!x*WMSXDgnJ5VGT|0X@?KQR8Ul9yTU|>1lp^~9vX`hd z?fUWRk{OJuAGJ~?_Gh82H(}ZH(*egvK3vy`;}ML z#ome1lx<0v2X0A&DY_A09QvoB|3x|o{aK@5*~ok`aQ#f?E6cI*$?!VshoCakq0Kw= z^Dy&J-MVe}oLubY4&iqNqrCZqls6yRWzf!+F;bsj=ESdP8=%-6f_~Z6v|p~}5ZXj& z{h--!0RHUh{|2ug809 zCNdUwf_59U>0E1lB7O44mGoNqcMtLi=!J9B|` z*y?d@opo8za|7uYWAvTd?5FQ9A$woDgn-Fo<<;6k1#AyX-Xg&0D|u(>hC1tkpt%Q= zz9Urzb!+F+AD3d|;v4H?`>JZ{p)lZ6EA~CfF7)q0e<8M{?Z*5a4zVkP&*3~u_ItP- z@xpTU@k4i)(oIU+-p@t9qRQl06KeS_a}w2w5pzjvN@ZQ_x9=@Ua|yIb(8LdtcYBOd zdzVT6*1^9I{uaeg%AZVhrfJ(KlbxiDbjjfj91Ydp-=Om_eit#N&YG>#viqj<`|Qb{ zAK#6wTUQ`I9{Dqsyyth>Nmgx<1g{$~2ifV!F7?~4lHYURoG$CXOhgmrYD_m1=4#@j z&Di-9J1;0Z8h?}qd^6D=hr?I#yV0BKV&7{Ke;4|`eI#44x%b;Wk^KPKDwUQa%dAp` z5m&Y?{E5gOeY3Y7@fWmTuUC~#}Bd>-;8X(=2h@}B6J>5 zI{tk)LU%ZOX|U_YKV%$?Aba60b+J7$H0>+GW?M;9AsJ65qk99owZZd>&AD<*outhK zZMc4sTH5@2>>fUiHHTRK6ZS%|pFOWgHY<4o@@JqRtHWBN4g`xgl*W=y? zc6cPuw~>|h?)I?=b6dNML6W}F4VgEP$rKq5X&3#Wy#Vc2A&`c)T}(=@0?FSjbd5W> z4~hAoePGV5O25@K&q-U|<=rmQ_>dFXmB<#}S!Yd4mc7O=D}81MvNP_gv&iD<*o;DH7YG{^~}+}I(ue)~vmA1NCbu;1@lA8qFF=Im(%UJT+>*P;*cOhQ z&|JB@4s>Ygt`O&25B6xT+<%yy>W;`6Iu5-^l*>n%H_6#R_WzK zWIsL9qq%ZND4M?<!D2p<*mzZv~g;${C90FGud_j-0<7=bYxy{N?!Z43Fm8 z`$*~f%W=b*j-0<7H4%sAFGp>6kLKE|?Fx+oySS8NZm~ym>Hj&)q4~>k+BqK0mFsu5 zL-Uv8#-1L{m1}#hL-Uv89p2HGy!gxU?RgH(UycsE!zyw@bL~CGJKed}9xmk=$~%89 z&6Qiy*P;2#VernF$ocD|CGXG)&0mhbEX=qx*WQmx*I$m=yk{YD{&FnkogJb1%aM77 zM|166U7BWuE91*?DbIFXnk!e$^DLqH%W*i*09=|YcO=g|gqDs!+@pduVVCC09l(0I z(EQ~nXT8j&xpHr^t|~NtIgG14nk$#ZT2h$uy11nM_E?YR(tjD_(ER0?KHj6ba+Tv8 zTDllgN6j@J&6S%z!J+xfaq=XO=E@DZ)}i^!(e_4<=E~JghBhU>9O*ZCG*|ANDGtqF zjt6h{Xs+A}rRy)p!?!qcLUZLNOm%4fa)epzlf3xb)v_vw<}b&=(>whJN!|P=GrS;=+Rtzhb?euuK&c|-X}epYwxQkJeq5- z-E$tzwYTkQhvxcO?7jG+M|16kpZ934z4Kr7Xs*4SWzZ_Ub|v;MeBGnD_MTqh(Oi2k zzv0nbd;d|oVn+@+pTE+PlWW)Bg>QN^krUsJecPkC_6~i^q511$%PNoN+Ux#~M|15x zu*Rdg_KH?JG=F`ZzSg6;_ICc$qq+8O|Jb9s_L_g_(ERn$VS`6=?UjA%(Oi2!ZSrWY zz2O@fQxFhb4)NJb|MFeekrV%H-5f6`w38IgUmvG`=g9f%qx@Ts=Grs2cr-VE z-zr^yeeAc@krSGmzo(S0D<|i?|HRALw0}D^e|;RV!=t(O4&Cn2-27d&)1$fhoBESO z^Vi47T^`M~_pe_)nrp8~jYo6sb>9uGGQK{>MLn8pZ+oprbL~A|=h0kyyOplLKI;B* zd{<#H=gFvTzl^+U9lsF zocEmW$jP;9FV9C4Iq_d@BwkKv@0{V#{Pi)CkFopj1H|roT|647Qy*t}G&g^rb#rL` z`uOx5kLKp@recrg+B?3)qq+9p>*>(^^>IQUkLKFzaG^(Y?X@4^(Oi44_6ynbd%@+9 zvRypTqq+88x!j|PocO2FAdlwSt5dpSM-DlEG}w`oYuDb>O4pSWdsBzT%L#2GABG86 zI_ZJh_9m5kG}qo;WgboBM7P}tkLKp@q^lg7zdn{+?a^F&KaTWhuD!(*J(_E;+c<~j zua8zYdNkMGnUg%4Yp=y^9?iA)`c2Rh`p4yWcr@4E@);h@wHLn2qq+8)-RaO=Kg&5< z?a^F&8+H3rcM|16cuXJ5Mi;a^uJ90vE?QK`OuAH22_vXt#aq(ERoBAe)tl z4}|90+pxu>LAHH=$B!P(&EG5A9GbsAtY18uYwww#Jeq6o`Q0APwYTOs*K;EE_l`{N zAcy=a^R!&!G3Q*F{+o^GRygPCb@n-2dCKoX4_80woTnVT%f@FuCg;jbqv#Jg=Ne<2 zbLHpGIrrq zT;0A#=;6vq&bj&r=RD|MbFMs*8qQ?`JI6U!Zg$RmK1b-`%E`{TdYf~e(l=z& zXZ|eb>P(~Bw9l10?Q^ctD_!v1$_Jfu?)UaNTzPyWp@*yQcg|Bn88$w12Iny43h!f| zD{r*VxyDz{x$=}uq37n#cFxs*+UIa}pDdw=r#$SOXV%D>{0z>v&z0}m=Uk(8j^MeK z)17nfcIRAuL1UqZD_?NVQ)->_%+e;%r(_x*Ip@lryu22kl54!{oGZKSYvZ|ZI_K)s za|I7qzUG{(kKE73XWk*_nVH50=UjPNGr@C>@0@exu>Ea3_h;u^eNCR=;mW_9bM@#0 zY<$WV&Ot~2@3M{cK#T9Qjg5euE4E}CXFuJ@xB$2q7z@ZzSkuTjrM8jr2e32h&=%J< zGIssd$XLNIN0aDbd6chyZ5+y?{#q_XrKjF|vT-@j1pLA7>l8PRcQ^gg*ip3tSIO1MUUp z1B-y?fMtLjk#lp5N;#jGW8A}e-uXGkFP!(h*2LHbYy_4AYXCVapKW4n2Fh17F%~JB zwE8YbJLAu}>fBeI%Q!cMKaF!^&U5%7rdVqHQnuaR)^jZ_d`~B544c!;FyzVY_sB~Q za{+lJc+vSyZ2xzHm&eFDba^6&G z7&V-q6{COixNF~RW9da%Ms;zvu?ZN^DBCFCL@C~BVq8aVm&4oH&3WFu@{=rM*M=s> z*^z8xGv_rxai44>g0D7m{Uma??w@UJ;rg^gvW@BCY~#EOvW?znIr`ORwz1FQ#0t`^ zIVjth*C)%UydlfZ={j@6&f4{&ALFkJGNqG`>BXy!6gC-t3)a>`%T9UTPZakzWgJ z`HZ%}`55xE`K%_!C6lv^6|J)Qh6iOqHhNu_vEV$%u4Hs8b-WwMV9wthXbp4(dH{Wa zGGGEw1SO_cwRs$P=t-x*|gAUOgXbp4(dH{WaGGGEw1&? zz-}Ppe)NIXKu4el&=)8JCID5yY+xa<3|I|p0JZ|Vfee1k(Hv+EbOd?;eStDy0#F6a z1{MO#fYrbTU@Nd2$aoNapf%7D=mGQv%76(#6)+oE2rL6u0~>&?z-}O8F8V-gpd-)& z=nIqq6M!mUHn0#_2CN1)09%3GK*mGp1FeCMKo6iVPzFo@s({(RLSPxN8rT4A1$F}& z52FvX208*gfWAN(Faf9nW&;a>Wx#4+1F#j?4P-omKF}KI2=oB@0%gDipbD4`ECiMT ztAP!`R$w=fF%Ny9HP8|00rUmRfC)eqFdJA1ECW^p8-T6AZXn}P^nunuN1zAL7bpWJ z09C+jU?H#!SPg6dwgS6>jK|OiS_2({9zb894442^0keUHz%pPpumRW#>;^LCqYtzO zIs!d_zCal;0jL6I0}Fv=z-nLvuoc)1WGp}*Xbp4(dH{WaGGGEw1Wk7!2=B*Xz)OT2O2!k;DH7YGWk7!2=B*Xz)OT2O2!k;DH7YG^!c2f_HkK9Am(%*~SO(x98;;wXbIz-(SQ& z*)4O72ioNrlaYV@gKT3p@DZ>P_!ig>{0>BcMjw(E@C){K15?SvwZQS~$RGUU;nhL6 zfHd$n&`r*b*Q3k%arg%UGq~Od$OZNXnga&`ErC|R5x`ME0dOpE9B=|~63`Xs1@s2K zW$))pIS&RJf0Jzt<2(kK2#f#*0@nk#0CxiS0rP+-fn~s(z#8CV;7j0pUj}UlpfB}y zBj;Oyh460Yd=KD19tM9Rf#&De_j){V>o0(BfE~bZKz)uq z$cM1g1PB8ifYX6qKtG@q7zT_1t_SV}?gQolPXaFhuK}xokARK9x4?GbcOVL6kmr4Y zgMcG|V}VXU7vLP=5@0wm3AhcI3p@v`1l|WW0^5N-KqGvf2e`-4Tz3FE1KojMz{S8& zU=;BG;`mRu9OGKjx*50wxDR*~cp7*acnw$yybr7gz5%uZKLdM#v`^?0z(GI(a3atZ zxDXf!Tm_5<8jc1JG%$o`!_Sz!?5=@4tH#xDD!`UpE0?S(SAp^Av^rb zHw!}h?tefi_kaV^TQqN;-lA{wbka)?_W&jURew8ezIkBpvFzAs-rU}#8T*^g+sANe zqgBR#>*b@#X3iW#OFWwF-7Log7y4*Q?$L`p+E_=-7~jW7Q*wpwGk&J8zOlbMx}q^*)-C z+jN6RbMvyK(nnKr!*6zJUL9?(@@Q^ejA=fa(w%ghM|1P?;dCEO$$c`zqq%umc&Cr1 z1=H=fjd^9Du>P0kS3N-nh7rFnHU<9mZtU0kLKp(t6e^t(mnW3kLKp(m>M5V$=zA&(cHW|Q|F^8xhh6DdmQuCQR{S%=H_K; zBOguaF51VVxp{ds+edTday*)wm&r|hG$j||yJ}8eygGV!e~;$o<@-DzP3h(w=+WG~ zq#x{~DY?u;Jer%AO)Y&iCHGV-Mf26ssYiM=H!q9xeKe)}V{4D*=B2ickEY~GkM(G7 zUT$jVqba#d3x!q{*N^s`;L+T?^gGc4#DY?p1J(`=BA*cChO0FQ{ z(!4r)sjEkG^OD!yM^n0;&+=$)UQRs6M^kd$&-G|-UfQ1Lqba$X^A*ikM>8+-Xl`Eq z?Cql|-H!b{nwys%m-uK(?#W9%nwyt@Ugo1IxyMS~FE*+PSE^H5$V_jPu}@}Jc246a zP4~^+ui5^22Q)vh#X$!j((=&5T7?fk;>i4?S|44|=9pvKwrhV}VTa>SC_1s@Nu5qU z<y!--8}V{s#~YscKh@hcieeb^~}5P znRV~%`{vyLz=Lxidiarfk3Kek!Q%^`SoGvmPcMGv+2@u#|H6w)Ut0F^@>gDcZN=+v zyt(qNx8GUykJaz4`R9A@ul?Y|kJf$s$*1c-+wl3uFTVV0)7RhpYxB3?eZS?0t^eNk zpY1>H`03|gcK-U?@4Nok{pX&&HMP;Yzbqq^o{^c|xM^;)yyh(qZh2Vvi2T+C$Fyx< zczjXEPN#G}J#uEZ9>wSM>{W7M@4o#n8E|>&;2~w@S5=H0J!ag5iPufOq4K7wRnu;t zacA}2vu4kEVD7{79$T<*(Nl|`UGl=xWy@b(@y5!xSFK+2-r5h>eX@ST#xFO0v-!I% zTeofB@$=5#cJ1C%6Sa)=%*MHSEn0^23)&VIb?O}HR@}3s&&8Jw8aiym)#IE`LP<}O&g^!0yy@Yy$8f8K2w`yFvYw?1XpOuPT7*Vcahvt{HLkC?gaOUvl<6t_l} z5qX7ge!benSP2+>gDeab0-b=)!0AA$qp%gbI4c0DCraK)HWW5@n5$8N^ zjM4tek>lG>7&>O`uu&u9kb&^V46P7(=VEllIHUcrk;C|Z+|Y^qUj_ne)Q~~r1{v*# zmJci&Gib!nf#pNcbTOm-72`&Y8B21`>8dMWih^x>%qTk_gGLOyg0x3rlmFV=2{}dw zk45#0Q6okS9VyI_;|C5II(Yn*6~jgjPgN~(yhFem0TRO2D_FIk`lcedd^1Ska6oEP zQ|v^8jg)A}8M*6p(KY3NDApN_6fExN`E1LIL@9p~wl{l^e*l zmOzjgf!FfsyDsbBX9tBdD3 zOPxvnw1e+s z+-a%OM;%;B;q*lZclx9BIR~e|a7f>h!?eGY%7yeJ8+XQN=_?K{tC1YEO*u?sU;8X= z-@%<;E^XYwrB)2PEjzg52=|+vrv1fKr+qs7JUQ8I(7_L|arawsrr`u0X;%*aK>O_e zXBsWsOZ#jZ2iY|+__0RDV^EqI*>*dbv%)Z>p9_9C=UE#J<8p=jW1!^rJit8!9|3f| z%rs8mEc%l;KXZR0yB!N&1%B+hOyga}pUwHuZJ9=28jSFt1K;_tOgml*{x5_skPLO{Cd_--g@do)6{%7Eo zdqc)xx~kxO%HBBPu0}>!>Hi9T#=VV<8x>vy{^-I?;|qoFN0%Rec$P6$;m3f#Lwjha z@H4?%?Z~w2LG1SiKlW79s8Rf@!1Mo+8LOY`z}-X2dwT-@H2C{1vSRJ?E%5Vy%ru4| zEB4oePrTTSwf}Fyr{$XVa|hx71nwSU-va;Y!A3??$|d|<{4nW(OydT4f*%9!9wOfv z{HV^RF+ln6Jn;8@^>qpOHNCQo$;gWSm<0XV;CrzzNl&=`P z>5*B+VM=~Dc+YdQ>^LFzE5Uzc-s1MpS>QFzz4kpHe9!bovGkq+-}mlD_B>AXUjaY* zNYnVIvcDcYe`X^?BBSvC1@0b_pWnc%F32*LaV`9P7&5M-{1PbzKM*|2*WQl=FStK5 z)*g$%_wLTLnifI%n{B>~m5E~zX_s1VHFA)1*f{)3_GQL*$?+Nlb41r&_&mz)0^7-J4R%OQ8 zQxW)2UuW8LCb4%exO+%?mnGmM6Yxs#0sCbcE0Gobd%&m9G-LJiEchkaS;mEm&oZ=e zfxVMq#pR*I>%fTzEJ%5e`U)d(hI9IjjYVhy*HfnEWZ+?P4t~87%3}e2M-vFL- zuo)Xq|D7ODs5Fiwo);*4R9)442Po>C;KL&#T@r-HgRPlyS zfE%ZE$cn|+Sqb`!!S8uE(|A_tFHhjV2VPo|W%N?~FTp2K{&I!?1m32d*Pk0RH7dQ+ zE028Y{E~Yb#p*X7+?`J~;|}r|aQBe%o($f-*fh@OTFTQCd;{_JCWT)Le&f4_aiYRU zgLk98T>o7Q{(L^;rSktw@M|Y#8y6}0`QYtv+K~$9^)lngOU;B39fPajCU4QHZ-?rQ^j#uet z5;{&`JaqXjoKQk)D*xb1X-{Dr=Y2i!=Y8@14Dh4wFpWbL|KbGw5#ZCio3Z#g8T^T< zW~{&51Kw{!w!Ky$ZUIQHE6 z;&0$*5fABxwtp?~&(Fy+#w&b(Zb8P+U_P(>kq_=3Qhz6c-^KWK71xq}FYsn9O{1;C z2Z8U?!;H1h(cqVDWBr10MdWV+|AqKkpyclapTCGPUE%kEzxtW8z9aHagSRcrGA1bg zT5$I`f{Sh7*U!l`p5|KQ_xSjd-y8}2>Ja=;@b~u5GB&I79SeSduRc!(f9ilN;~8Y_ z@+Ih(g7?3~o9_$_5PQ#pH(_lwU-91re};JP zj=vv*7tp@DD*jjCzu=$WRepa0@3T46_*n5n+!B7r{YrbqKNLLY*ev5$#XlW<-W0FC zE&}hEo@G=k{$OzT5dU1CfLABrkAkl~&5ZfyIq>Y7%vkx?f`7&QZXLFyyx)WGPy1S> z@EY){@4fb#CzD8ZNcye8D}3|g6TpY#4|o1~26)rkm~S!96#Ex|7nxbcXmkZ10`4AS zZvyy>#INJI7XDQ5QQRLz6h0fgwJ+X34j!TZ%6*c^zXI+aVs9OIPwo$QCh%hX zBg3fpV>bBjl&47H|ErI40h6Sv?s(w<&Tf37xC;SXMFB;T-Q}?;0`uG*n2}+6c4xDw zO-*;rOtalxO;`6kco^{=G#Yh36kG)qq8l}UL5P466%-VW;3F8tC&Yk)elZxrisX0h zJ-6ys&BXrrKKkGH*1hMR_c^y_uK};YzFod>Blusae~;DiFM+@Ef>psuihlqexD!98 z`t|_Web({i_uz}Xeo7C8&Jg+835?192Z6(fdxNtSzXH4q{kTr?5csy;J*mGw2Rsix zUB@o~|LMKG!Gmf)mx50tDSfw&?*ZS9z3r#x{~~w_{yBO50PH@(zyB!UC&0Vkf@H0Nz6Kv&K89x&I7VLk$;*H?P@YiordW$+uj-_8|CqUVr0J@I7As z{sefQ`A)vQ6q=7+-;>gJC^&h0Pg<`W5B?PSwClH=3Vx$!&!>akN9cV6c&{g)xG;V( zxI&TQ^zSn88uafdo)vyv4L<2By=nZv0sO#+_6u&(@h^dICI7^%%=~YI-ADHS8Ti!C z_Xd~oUDo@b!g%jmH(SQ>RRz2e{EL5p2m1V};IpyMD-@qum_J>>NdbQVyc>HP*5^M7 z9zBW9t?KK|X8x*RyN=%tKKJdjD9qUhvnw zeDi+rZ>SHBV@&A#E%?|gdxInN{CmN-9JDHUm5#3^kzWlxj^9T}Vy_=r|3vWCU75X? zz)$~FZ`zNE3iIC#{w4lqNca0-Vf z>lqVzF9j#~<86ws1uy=XB7=|gV{QPukF56{@I4gaXY*a2zaM-M-L2aQh=PApnEyv` zME~wFo&StOxu=8Q-%)%pxV0nehaC+bCm(yGj`xHA+w0eifp=U>UtGs);BS%NU8wPE z0sQH=_5>Xre=pd5g#N1v`19Z;uO7S|JVkuo$y_oz{@lL z{cQ00o_-$;-u^85xk}$L;Lj32oPE3+91{Qjr2Cx-{u%n<#%VzRP*`fPYTEuA}ta3f}r@aw;9a1ANM+tRM6f@FnCo zlRExeu=~jR`@fL-n&+RxcUk{H@W;LTj~9X8{K?+nojQIz_|}`rH?+PQ0^f_h+^XZR z2QS=6j;(kGd>iqyq5Pf)Uk!gLtIU3H1%HNqty}-YV85SuHTbJvBWKY0{{(&q{^EGW zcY>$sZ(X44{}lYIS7rA-Pl7LgZBKB9&VT+o`W5KsixnRYe%4{Df~NBGWU%`Pf5z6) zk0t-u#CPG(4)A}G4{udm2XFBF)%oC;;qOk=@ehE%_ncK}z4l4)!Q^|Jb^I2v`^bLZ zS{DR=q@Hy9{R({8nyg>(2k?o{<33F3-S0)@6OZ%;U*uV#Zw>e)^h?6J;1`2`htA?jz64f)7D|*YRDRzX%+We_Ww>7kHNb+Vd1&4gNU& zgO4cwI(RSlBTjzzf`7n#=U*QJyN~SmNANz^&@a>TJyQgw9~oZ{KKjYtVCO!To-N>y zaQ`*Un9%n|@MiSkFBK=?-CqCkP2l6HpI)xxyTJeW;;cXW&)}#1CpnFd-vwSxeeLY$ ze(>Ym2fF)(7Be9Fnt|1`V)9`MZFy}=zl7T*FsmL~a8I9%EP8{qTFS7umS@GrnW zB%Xdu@#EmV)Ng%?_j^eYe1Lw#C5jIPul3^HQQ&F(xwGF5;5U)aiJOu22f-oA>GXLL zd?ozbuHzN3`>f;30(dp`#xURI`S*YidQFz!Tmw$XhrX%fH-XE@Q=XOiUk5+mi#Oi^ z|MLC4!A1Mp@n3`YKOvLnQ{c_qFJGq5AMny3Xj0E2Mzh{A;Dx7pgTGOH8h9`9#ns;v zGXEnz!Gez0z`y5y#_9Kk;1cm-wT{0HJpMU)!Yc3ggFAbAgO{uPJ^^+gk^e2=HDAik z_dW1KAI&h;bvJV+o&O5(74#RLQhXZNeT1Le z3%CJ(E%sPuOz8b|s}7{xt=BQvu%%-cI~`p|1BM@E7Q(IDhac_(}Yg%ZK;J zF&_S4Z*Yk6ZyorI6SH{nTCn>F{Zrs^?nhqFccFJD_^^edbHMjeuO7&Ep(g-rfl1G|sR zKNWm1`seKZ4DfgHFH1_#4)E9L|Nbj;WxYE1UgE_8O3yslePq3Nfp4dty^!xRe>ZsV zdGv`C?*adT`{8pGe-->g?(bZD`Zo9(*UPi^`3J%4##RMySNs&%yR4Hbo?RkIq&HWKBoMaM#&*u;-01BYr(e@kKOrR z0d^lFPw?S`s{&NY?DvMk_=N?075EGAf1S?%Okw;E@aM?)KC0vQfsgq;`m6kW6ud+9 zMaTDlC13O-^sEKH9eZ>3_!971?DZ3j2|r#9-bTLre#P6sTgcDPQ``jiUC|r-R`C+} zSNHY?&)&~IAMo;Zc_M1W^U-9j5w#5-jc3QABnfAuZ3d6k7W=x@S{#(imCjNtDtF>; zs};4&^|*4{X`4q!`UlDzc$d4?nQKR3wcP0PPIbB4WO!#beymw*s$J_u{X-i!7?dQH zusLJC4!5JI)NRj%mB`@DQD>^wti}t2wWL)Km&W31I-ozjjyJT;+>)C++^yG>3M)0u z?_0uFr&f7gw=py`6ID8iTX>+`Zu1g$ab-Jwq*;v?-OPyy)Kvn6MI5ZD!HB258h8yg5WTnAqzE+7gZygylBm6Qn&!LBF^^Ts0l`CNq zRT?cbLnJWOXGP9zWs;HnV7msjX4XVhiQCo99KPGO+-I3iT(76%4?CT9S>CPB$X=fg z8r@i7hSd&_{Qi(7MLXfkpu8t9ynxhfQTqs#S`lmx8;x)x)WbNP9gf?LuwxcVYN~2= z$Gw5DUg_4uPLzMPG#4+FYK>MsQZwj}Q-wg3+iKM)R^72GmFnN1{PlnkJ%Ax}qJg*` zx8Y+FTiyv*>$Mgv>;z&d{#o+IQ?<(FPmpCtl1ctrVWf$Q^r0a0sYO^8Ox8Me`;%4G z;c-LEmM|%mhBgGPcCFc&DXwWroiwIvrgNI{+G13-Vu#5 zV1>G|*dOclbvtndGiu}q(=-xA}5sig`; zq934Tw!-ik(b9ArwyUKMGRYIRIjmV%(btLj!;Q)I(kM>L%sHzYwU>n5PS34-IJ1Rw zDMyRGdaEYljt1f;PN816WOZQpEQjb@tXV~|OO!fs%X%cUk?d%#%vh}XZCIFX?A5>; z+?qGv;BURsS~B0oh4j_ygX|Z!CnFS=^VyAL8gw?IhPd{k)GQej-3HEQME(dyxQUqX zu^ErByOHKhY{u9nt~gCFlQ9wXd@~5DxPf(>gY9r(wAM_O)X8Buu5?phJi(#m?{zS& zX>~E47ZCSVGHf(-Ak4A2n?#mMP6sQBdnZvRQf6@^OXL{tsL^edYUf9(0wz&?nJwT4 zJP~ef@rOH>dR9s;SI!S1sV6YLF$rc+zBs3TGqX`A@ySc(8vNw{V%8chkA*uU^|$7n z)+S5wOlKi%N4X%hd9*A}Kdg?{rrTlLYQls>AC-qQFdK#Qe)w24*ZeV(>}%b^acXY0 zdR_by=cv?UKa{OE6roVvmi1y8SHs0+Ts3CB5c<~PEE;hk8!N{gpZluSv9Q&`R?MIc zgyZd6Os=q$%FCXo(_UIBtq?6Pb*D?jTMf@P&KVQpOi2{&j88Z&V|{umiGE3Kwi(v5 zL2J*@q5fa&Xr<7TI|VAy-|ckbrX&fT&Z>X4j>L@lSx#<=0pWarigd`u;+bl=Ou6CH z#OD6K0OdtkMT)ImkB3-@<0QgLIcbzFx1nTE_@^x8Au5TfJCWBb#Llw$+qhP|P~46a zPsZKKoXs@MGa7el8ive)ioPxE#5Uy{MT=K*UO3Tezl#iJS?3Y3Im=_~J|>8vqeJUY z2&(mh(pm{x!+`}FAeIu;bGgx*2w@~cHd=9cW-Y1KX4N-xQsZ|r&xLYKM5wbex2AsA zCUjngXUMm5F#*M3mK%>3BKWTU)lNwB+01>KR6*pXdGiU!nFTnKwf15PtJ7eKJmlD> zjtut`F4Gxk$+j~I3*21h%JNQSB@48Rh(t{CUSq__b!q8`p z2|cy4H|toAZEdzvWuL<7cv*}&7XgtZ$qO066^PnGA~aR&%;ii&G7cBxBv&k|d#22W z0yMz<-yVE2Y|la|by3{zkkLzeCx6*lTtqhU(j}T1D5f zOf~u`5nX~sb-EaevkL3P`Hb=FDNTh8lf*Dt>Va~3F@ZbTq+Wu8E8e@5JXvNYC-1noiQKwZ~Mg%sqaqxBVg37Tm9w+v!mL6YrCfh^>l>@ll zoB`Q{%Xn%Nyo7F2AS1`Auv3{cTSyQrnY=`IR5xuMjU)+kP|zfR7uhu`p{2i$$~v!& z^R1SPNsdd)5TLTLpOT@P_E~ayi=iRYopch~mO3TGSzl-iW#r#JQOH!13jZfLGbf~} z;5JvoY%H6aAP&t1i!0cz3~f_kyJh_RNK@G^TOhQ)dZ)H6s*o`?_4FW`U&C zFrkmOMQxf+idoNCxv_g{?OYNs>sp`BL)&W6f_QulR59%m89(j_};^q2Cme{8(0zQ@umHQ9Jf_)`ku zZeNlh21>ArC_M^{Gy16eSzu{5>80OABGk_{{mx_8ts{CYY4VvyHe(Gz;6Ea@ni~$xXlYMJyK$ zsMm7|0n1ul8m@=4_VCP_h`g*KH3a5n49**pc}6-F!u$M?*Jl}Ob{muN7%ozG5^}|> zm~M~d9cFB@9H|bvR)Xy7$wDK344Zsgndw9r;PUc^8p#xWX7%*6+Ay@o8K_604a~-2 zZQW$%awK(l`SWPrf>#JzbFuaZeLcvlD_eSbff0&SV_2#bK-Gm@8EM_7r7A}_zFWGe z#iUyutah3!apH=OS$u^xHl3nLwW+M9Nho|@4v8zC^gP0ml>nwPHnv^5leEQi!AvYt zdOevGnM9uBi$>IJS}TgQD@_+3oXWlVoHfWR3;6OG!}D zIw@7z&29db7-gm8_V)W|d5|($+OQ;3nwjtj46`PqjT=W0+>`n*s>>Zm#vE<#ndeNo zJ4{kcZf5pas3fLF*1bJbOo_Ct6_Yei*r4E4&aCPjhxFJHV%n82(ty=j`SrJJvvZv$ zcQj^6%Oq2t7+mzRCO0jYYe`%=>7??!y>!VIKhZpt^1O+ogbR29F~k;0MmAOBWyT9z z`dcz!wwJ@ujjiRKP^G!NjNnM-8Shp?806dx5iH*z(DO<^d!w{%!fF+-;N2#Z4@itw2c7tq`??X(Ex!-YGVe z^(%88Z|QsWnY{N-Qxqw63ZBVRV`)*XzzKE8IpWB%X_oU(a}`Zhl^B=YrdMyTIkJl% z8<6dVli?4tOH+p}VN<#W92MSIyZ`?!jo*ot{VZk3gb-Y#jc;1CN{HpH7bEFjHmmGG zy3vf~_L1^F&MBEZDVeh{)DS7n1}?QE2};guFVmE6XJ!)=r!DoZO_K*{X(bP*S8=j5 zM^;arE7xs%&}r9r)Si(l6CnDpDwn1vG1mjCIMY%kp+QCd>uVasNm$AK zCQrkzZBct#`h{jOOPikrnI~5$W6YIkwBVIN^G+GeQeqROA-5`$$tT88BgZh~R(NSj zY2832s-=cz`!T62G~QA#FC>*S`Wr}S_Kz&*$>j_fuh~xhntvsmR_4xq$zGHaSx3{V zEvfT3p%k9Dysx+;2Zf>LUk(!jl<$rssX?P2<~Qlhmq>hFxyIvZTrt-;mKj6RQ!o!m zChME8W%F^?ywNL7_l+(j=s5pcm`z#D(^=AOceh!INe$B$VZKxs{+hY@pRGK@%ABUr znjDzExtEJEZ6!tbeO^FaTU};QIaSKcWn$E7)HwCN^{JVZg;EJorhKy`yQHNMxvfKx zxe}df^lH7zOdVyezpecW%ffD>Wwy4G8IK|#a$vi9dW9>>>R5<)NczR9%~FJtxT0m1 zb%;)~ge30aI!np&pae6lIpk`Lj76yzF!o%yj9Vr|ZG3qhQETs(e@IVT*}^QTN2$T9 z)!MSu(^~LA+*%r1%(n|x$iQ8C2F;dNP~}~@I}?PJ9$6zvo_0f^I85~emVaL2WKWP2 zqbMy`sB#u;&fODf|G+vCWtu&Y*ICy9C^5k-Y`CQUX|z1FNWv^OpRY@>WOo>=EU9+* zRcq?b&Fw-}oJ%pgcd2DX8ZgVc500O#uIuJ>z>$zzvL4v$uSGNq*c%UBA zY)E;!ym4qwuu^^Pn2}|BgOq(RAESwU*j8PmH% z8-36M8zd9yUO27Gls~OGh`j_}YZ7J~<~TWo3r41H_hM2NPN&SsLd8(i6k?`LlcZL8 zE?t{q(6W7t!aGB13f9@Uw|{c>l%5@aShDG6B0Y_5G$XT@!H84K#iUUqSLWLjyUN@i zD{V!pi5Svyq(Ffat>KYfMI4f2nU_ut(6V+fFIn;X_Glj+Z*wi@DnR-R<6F&BW;M5y ze}4D=ftipBzHd>r)bu~3q_hXh#3~JWskj^E*{DO)sq2-D>w`3X{mUNvui5^w5VtVqV@>lZ;wUZycqtsY?Ke%V3* zXs+;@0L|VxZs}a_Ma>iM;$1q}GqGMKI|O2!UE(fm{9dD|wa8AiFl9idoc2P*$;Z5I zmqffCCGw`c_l0G7tS>%gSA>p(P+_imvMxp@o-}vOS}x`)JvA@RQ;?BuTiGC-Eu?aG zv!}+=$n|HOm?*H*D+#@keL+foUa_Gqbq|BuHn&%!=Cv{-Q186Fs+s!H2UQkS>#Lrerg|?Do3FkNNP@K>TM7n7#BbwqRY1!+-#_ay=>%vY)^Sw9A zIHXHUfttv^eLP)UNY0$9TJ{cP`cp2rL8eOqgjB-?>p6;StR+dyy^Wl%)z8bXybx@Y b$-HM|WiRiQeLS=GjhFV5(HqU@oR9fG$>AC3 literal 0 HcmV?d00001 diff --git a/linux/libSDL2-2.0.so.0.dbg b/linux/libSDL2-2.0.so.0.dbg new file mode 100755 index 0000000000000000000000000000000000000000..9eebf5791e5d1661670be1295b247aa71d39aa01 GIT binary patch literal 3566049 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�`D@}-EE$YfKCAwp}eMNVxPxUUK>cc(0m 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 <69d451%^L$xykq@J> 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

`qh^ruFi;Fd}sk`qnGC3+p_l& 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 DG2ez)N8^EQZDK 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�J#Z=Bqx*jz#|?B6etx8|b>LF4 z1DApw_*Wfx8E3c-TvG>Tkkx_fa{NoM>%f2F17taJLyr7ImSZ=?#U_L0=q+(;vK+rH zev_<@+!1dft7CV?SIO$=J#o>u!0I@DNffU8t-#^BKjKFPSZMak4s=Nqm{Cj@~CuKLxBkkXf7uj1GLnJy5ZrNB1MAkP?<> z7qJdhEEu>GRxB8}6IL(CJK=jc!*!rWLEZ_skkx^j1$iet3w9l-B~CvTEJtd~kwRoS zR!7{KEJy2#my+f9Q{q!(b)=p+&or<)R$ttNtd2GiPavz~4aHl*;ch4KhaPY^M;gnK z+kBxMYa%Z4Hdu}}6*nTw@u$U|$?8Zm@k?ZNthsn1SsiU5o=?^uXes^}j1FAlC&dE_ zdUSssjvZL8P}DjwAlQKc!43>o2g<)=9eAbSd+0!GvN|xN;N5I7F*Cug1Fwo#lGTCN z3U)>Z_kzRy@YX{)0Gg3u1-IUZU$PDX8Sbfo3?~)zm>qu~eU9m|F(;xKP6}o?$udmV z&jP-!3}dHscO&wS5*&>HF2U(aXc^cgI77UPtORE&!Qp0T!O8h1&{x%&z+xFQzY+PJ)08dt$44rwV$^uF_fjHA5`_ z9L?}lFvC;949_XUT(d31UzOowWO?|TG8zwd8J-uE3H=drvb!92bftl(a-f((UtDLaERoW~gp zaU;ddu?jL3;zlY6b`{(wu0dADnG10vbtbE#`^AgE;b}-^IV(`c4;JFa`jIcxr6!9w z-CR{LiaiJy@|br)0i*dM6;9^`z6->k#u7bj<;C-@sP(&G@2fGT2 zif@sXaWQ3_Zys0`6&JS#hr9obzaE3bWt^ytC-Q}=C?fs@j2;YT52_aOm_7fb!451Z zM680U!3wGdE2yms%Fnk7>ZpR&WK~dC8NUj46+9)LM^?u5l<^m2Ra9Sm2ONGDsSLp3 zGH$4h%PhbP)q_Ujc3@OcvJxt2U&v$jIex|e2FG$F$tq|cte}0cf-b7y9L{h(=&B0R zzh@P6Q^wW6u7d93=49RAdMM+U$+`jd6t5(!qF&;YWL4Z-oMRy>9DNR7LEvyVs6MKw z1_XBl>nnbStcv@Idz00}{^H?aIPh^}I557D$Lb&c4Ih?cIXT%J7$0?W#l6X@XqqaTNLIyfi}#S_$UEX}i@|biy0|GgycFr4z~L&I zsfs4@g{pX#cpX`{^x5K5WZlx=6=z-|2kLPKRu}SE{lmZGn;uwBO)&>n2OU@)bYPtv zn2Iyp416jF){y1EdR2TB>>T(^e1WWrK37Fqmx5LC25~*I9N8!yN0wuo#M{8(f06DF z=WPbQR7KbLLRGw3Twobkj%*P>Mb->_C4Lc%8Q9MmI0FY*J^m!dSS*)JH3!ZF9XJzo z;5RvN8)rBN&dY(&a&zE!Ra_D59QZ@ro~(*4sG^}{ReVvrnJh>C6vwUr%dx-2HNoN4 zNcUG9uAL%vljT@Q{3cnB<`ut3)(nNk>%efZcMUjLzOYC4M*WHZhJ)pGCC$O|K?lnR9jqz` zD}7)NR+EG6$a1i{92*994%QIAM^?o(Rq+aCkKa<3+2cV zaXYdcdsRG^EJt4xuOZ9vq2ldi&EPQcPhdDWjx)HTut)b!UBS0pu>52hb8tn_!4*LV zSIfaJADe?8$-z-%Ik-lSEd@IV*NS(NRq@BF_&2f~`9xg&6R;dxC+i0$DS-S$q!+ z2j}xYDg98`qkDb+!4EQh8XL3mF>~;Tpo2dI9Xufi$Kecj9Xu%qmyzY*DLM8l*g1Gw ze1ohn*w3iqyz9aGg8r_`hFVq+G7sQ9ja_pk`53(HnQ+$^!$Nv&P^f_1^xg;(|)(l@3 zS0HPKuZWv~(Sf&~L!_o5g#I}<3+{UHlYLW{)=D2gL0iC#f$JxRR@A|EI~YgEJqW?bIEc%BHlq( zN0P)pk=3ze@ny0)nj+5d1y~=^q>AIf=)mXP1NDk{bU({Y3p)X=;A^rv@U$HN9qc;LOnjRxN1Dr#{9l6QSPOAGvK(zGewQrApAjD=t0S$% zSvG^!vDV^hWOcNS_$9JB-d4N{96oa$zx3gT?SW_I$Qiy+j%gpF2WAC3 zFkc<0{FQZJfjaOkSsi#!jt>XB4lEQeB+HRSa^x$r99t|-_cd6KE)mxv%kib+@nm&m znfPn6I<{PVhpdjS5I?aMtd74g9smwkL8+^8I7e2=kyU)59Q#2016huKDE7Vq%kkCX z2w5HZNL-n$j;#?lBdeop#XZQ{10Rcrg3*CX&C!AVMLfFy;%zTxDwcnac3^+71N(y= zIIIq2+-4m(QY0Rqco!k713wg*g3k>)gIx!XiU*O^fn#!HCODjmx2}l8Ir5V-+yw#Q zI`KcF{!zqZcGqwm$MUH1HY0xoGyKCcbRSb*R)*1!DX%EO#@j8yzm?DnV3*)k@!Mo2 z_>U4?1`dCQcit9F@R|}l4gm=kZGi;S7xkDN|HjKDEEla{38pU^xR9qW8n}?(Uz8W} z&O6M)2a3|d5oB5TU{PAQ1?)1+B0fk~hFOd9LVgV#u8Ozrjl)@(y(llN#da#gp|mi* zsK@Mz-NkPZ$MVi-hVj7+?HvJ4~2urN6M zJKp|UG{a)aE8mUnlg;})-tTF zjGBR6hBd_9$lCZdmEi=kJgp_(0S+%jD%)|mjaf$--{cE*DXS|kyjvB_u7wKP6!n<> zf_wO*7Fe!d$tq|Qte{P>f{v;73~nOAgkh? z;^W|O4Wv2^hr2<2tBSIH57rHAx3~~l74H$3C2Jq;71sl!hsF6Vm2*WsR(~*EY)pGB zAF65&oC`W|F6h7oIdBMPI0r7ufvaRW@TV$%~#dtqyci0?ws2J}juaf0J&SJcuYy>+89v1H-tD;AW z@qTiJtcr7qQ;vY;NN#a2vK-4JUJed_k{&;1fplF(am9H5ImQ>N;*dDq4`9teUU4#6 zGY}TH1jB(bZQwvfIDl}+GsedB#PZSV=0L@u0~Lb~RF?y%aE68PdPA_EC+h2 z;_YDPKyUFevL0lgS4FqUdeH47F8LE!755c)CCib1;#p)l)?d7bEJt4u-vfu^P|6G( z?m_iMRb1*gSP!xT#kI+DWRSQmS&qFV?nl-Py(}I@)(j06&jiE4)BN2DGm3e1uloJ5 zF{`lLww5_KBk16apo4SeVEhSlaGo5jLY9N`F&r-~Pm<;X(uFJw8k zNL=hBSdK0h_aw{lCGLE(IlNSk?+KJ$a3s}xZD}A9Q|JW5?PKP6t5zyBZtIiz~NyJ z;1^px2tgGeQN_8=g5}5$;wof0c2wMxEJu%t-zLlPAH^%kn!%sMUxMM_v6gT!LvfGp z&C80bIhIe?F$Xgg4_wJJ6c1dxPmK<5c7wQZ8+Tzd1a;%Pc zFIkS(bq%c4G_&>mMWQZK8LY8B%ibs&;=xgF_ zWH~-meBTAIIxhrN z_m?!V4lE0HU|FyOAE*OkaE9x^hw8u*vO2I@j_(1x4tyj&MV2FLyXB zUn0xVP2#xAU^)JUxCU7r`BL19td4CK_aUpJTf`&D+5=ySXM)jz>D&YV7We4>iafC~ zA7J@>L+il5!4CWz?7%H`px_njz-@J)Dp?)4BgZ>~T?g)pN0a5qJvp+NEXQIJcwPUM zEJr7Oog?=p@H#)4 zFO*}M#T&?S^nUR#WI6tTIO{)Pb>u;DL9#lQMO>Dwj%F3tCuddqUr08#%4!8Gs}-!QzAAeQXSm85sIt{$%~nHIasccq zYb3r(R%MMYnDA>@or1$v)>M`CfuhPNzRuH{y2{?* zDX44|d)6VrV^y!k;fDmUKB$RR)*)C~hhSx0RawG+R#`VyR)?(0x~q~gU{_fW@hq|` z>#52%fz`9yNFfF(cs&!UA_G-+0b)$}VXW4`;VSE`%I?46S4J@xPwVX}D-MB`RqlYw zUQ6&;)kC4!n5I~7+|(+2Em+xW!OBLevh6s-^=ytmj_%4P&Bn-Q#Rt}5$r%PO0v%7&3u*?d*95$t-lK)jEvE8Baj>+7Ayao#4Nt`C7dYHiYq5Or?tpcjUn0&A zmYsa@n+&r!p{Ip;3Ziq3e;a5j)g{ig7pcItI+fwt`UH=TeU%r_`dHu8%v@U^bZvdm zwJ+pa&b#K?mvXHXS*~rCOU=N}wJqX7WV!T}TzZEr7rz$o2dkh&q?3Zf`zoP4GLmbL z+%wloL3y0&*9ommtwA;14y)sEIG4VWO9P=e7q^M0f%PVQaYu&vCSievS;H`zwdbI8 zr@GC#c8&^M8_HQboZ!*18)1Cb9Fs2Q@8;&(;h<}WgRcD~*GA$Dw~LO;wS{E4c0w+l z20PbIitmx-(kZ!A&;!fG)8Zyz74#ocy3LeNCG?X{1vYhnFsKSqtB_Tgfor?@Kaz4MdUULJf!LUA8Pdh% zYGtnFP7FLhsRbmkW!pfK{-UKQ@McpGC)q;tkXM8*QM%6edg{T%v zeA3jpQ8f`tX{v=2TbjC?YIrSHOTs4iBv(`&xDLfV=@kK0BCEUg#Yf2Megkpg2f&(+hT{HUnY92ZFUH~3OT2*j)DSO(=+qCOd`h)`V&>Mq zdLXK9hO&)ngT!K{#y)80?t}6j)rN^RO|3*VTnZM=#^JixSl#Oj#dWud_)W5&^P7s- zll7ecwD>Go1u)p-IK0M*tL$yEWU(Zkgi?iSlf-XLtr}IULwSm7)5PeH^h5N+ z>vli!NwT`vU)}Svfz{m?#I4Ed{s8ejvZmuj@ma9U%3ct=7l+qB@fhY)-7cEl%!-3j zi0TW8*G+93RU=T!P#uu?2&&b&)1zuxC{?Jwm{{7>Z>WaXVfAPs+uMWGy~|KscV7~R za)7nBUl!LQYi|!0_Xn#02HVKD8I(BN-ewL&r+y7(C)Jk{KQr}sRNW8d0M(ZhkC>YG zA)KpjABA#?>R{LHT2#>O$GNv>C3@`i@FKA>uVVf5v)1id!EVnAc6+|MeFbN@EBOL- zJ9|#+_Iv7XDX{DILUA*)y0=K(8$ecf7mGh9tNTmDnH~mfI+lu?fn`<$q}&9DwELUn0kys7bzAaeKwR%^m0 z*S+QHUQ;NpyDP-~$lBZQi)WLyw^xd{fmHy5o#ERoPaJ1&a~Yyje}Qs=>WajrrWVd+ z>R(X)rTTv2H>P%>s&3zgk^yOYD_yteP(imxl|#2TCwlDkfnvB9V0}zS>-Oegw>Jm7 zy-nS&o!h#-UES_V*52Ns?k)toZtoPYBkPlrUFzOZvOY=qR-8W%Sl!(%?n+ko_lQ@L zH644!m&lr)ed5xO+SWlyU$W#~iHFCJbV zj8~z!Pj?Q8SCBP1-;0lsHCYG6UI?t~${}$QS=W`r;+9}d8sBUK)A%m&Z%bnYM5lfQ zWe3#*iP

+6)__>OLrksD7WAV(QJP`V*Ajs2)tLZ)&-`7MXw8?IzVji9Jnyk*cQD zL;3vkZr))xr5{qkls4oY=xU@!Nzd7oUJXv^)!>xg(3Exw+mznal#U{6 zN^fZ@mxA4t-WKm9YpU*Os(vGD>h6k5<^yXg?}=X`YieU6e1_jg)>M1qBKg6Z`dD#q zuo`iHaeQHh!@H9>q?2#;8i-EK4J9AdyNR<*JsnkxK}n{1FLAx8ISSahk3%U(H70V{ z)CN(tI+UlVdXcN9j*qGhp|qqL8_Chx&uSyp@Sj+nh!(i1N+03Z5`RK*QbNFWeFpcz)nU=Qr%CvZlU-cmY_A zxPy{1CZgm>B)*Gp$S)9`dOwt$RFfi=P0e1^)Z9=CP)&}sGqplgEes`@YD#2?sokUM z<4`J6O^wVlby`%d38g;O5|Q<$en~Z)E`lHFNV02bDNWVyP~6m&7UwDk)>M`e;|E*( zseMe`kF0CysV@$%S7b*wzYztK%<7k+ z45QjRa?aEyQI&sfa1zz$Bk8*P=Z=V~GoZXnwNE78)GwpzVkj%A_Kj3F^=4FE4dpYc z{UYs5O-e>q;f5*rNeLWos$S4kJqyK6-2m}KvaY2sia#T3Y6psskaaB`B#ub|>stDf zI2kN+_-6d`h29I1Uo4GhAUd@zl+IKKL}D=vn&Bx?m4D>0Kh+l_1x)=ms=f$i2-Sg+ zN~Ye6szafSraCCn*3>GgcwbHF8&KY&`jVT{!BjA%hxudklOrA{G@(>%%!gP%(%YtV za&Sr~2dDHMO=+PLHl@=wrPauq(ixh{p9^uHh)(Sb zbZe*9Km80r-C{wAH0+7U~|H_6&D%f!VV1M51zT-=PT>-Y-sFtChgG8=Jt3nLjZ zYT5(uLv-rbPW9LjmBOClXi?G#lnLE#^Y^p-}3 znmQ+{#=@SgIJ{+%Ii?#%lHHL%-VN5s#OHSIrW+DDSL z8;*+ilC@ioi64FvtljjZxG!0|?I-bSvUcNf@m;cZ>j`nqa$xP|lj1kPZg=5g(iMmI zL*&Bqev=PE^miAOK~#@MGWYS-*z%?hgEE@xu}Cpf%S6>lP^MD-F;dgieo^&ZC=00~ ze^XaQ)s;}zQav7d-PDs&^;0ODsh)_;H#K_&%Zh)|azE9RkuOZGMm0REG%nj{ubcMM zn)Vl=xZQ9@{2p1m<*axgS-a_H@h!4;+b`lrD}uEf&xxy(_45V4iu;kZn|~9}1I&~S852>Dsj4(A_B~#Zy*+lhhWPzy_qUu&C-%|ZK@};SRqUv5K zhp7G%Id1BzsQLqxlT^<|Zkqa6R6PgfcdEZe@?d-Dicq{VveNFl4CMyZ-`wu%Kn1&N z4}anyU6S`9tS|T^ejpd?QwP}YN|zM)Iv`!r1e_K9Iv{frzYch?itVoZllXN&gsk25 zKoY+W=mvJX>p}4=WKDaPq|P{hE?K)FtN0hPc1t#Kq$*guDZBUuvUXbz@#kdi#)rf) z)xg@VImNZf+RYD(CxO-E3n=*?9A1{By8V5_zK7`4|EN5MBWqGOQ}0IA3{bLC&6YIU z)Wqs`?n6*QRI?{7GPOliEes_>HAm7{rjCfJWuTO&`cTp-Q$LKVHK5d^nltH+sXs>5 zr=hf_`fyU{1;3(733+KnOcLp8y=cjXmVChOi67WW3rcqY>jhnFkqYfEzmM5i`~(uQj8q|>GzjjElX z^r4z3>8`1d*0OU4LV2C)qe*!&#M%Q-N7Zpq-liItRL;~1QT1IY%czEuo-y^CsQMw4 z4OH_c4KnpERqd{?pzNg@PSSfmQ5y<&SAE{Qnk0GLSu4xqCtt9Bd64a{Cc)j+B)Gd; zYIpsOGu+oN&uDk$sAIdUm3C8Uu-jd&#m&i@_BNXK7s=WUZN*=bwOiVWGt>oZH$5xv zK-O++FJ3^_ZtNhwMAmNYD1Q7Yuy%7N@oQi;xg$#MjKgb_wCqK{72iN~YHuh5sJ2bo zX6n_b`YMzWRNE!}Y-*8ucJ4$dlc_$NostHdTAFHjdN~Xw+UutMIZb;PC~h}&5lsjaEW4ei`5Pl~KIH)26w~Zt@gV*0&-mTbSgrs&8== zZjvr$@yk}(!eC_!gOx2;WixSxt89fTTTj+q`F&M#4D2dfDZWKkWvf(K)~0@Cz3}#Z zad@ke`XU2W)(|58?*vw-;c%6GsLEc2qRJ?aZLq6sn|K*nm2FpL--6Y%yGS7xDR|qH z>LLSGb^{{*_$gLv;c%7hRAqUZ`IS-3!_#)U${vTn%3AXOcN|OdSk=RoVq-dB{h3#+ zvSY!@js+__rOFQB4A-;Ms_ZIRm7P%~k2JT+&Wg*DRoTy~>?yD+TZa@rXNo^36+#B8 zYy!lX@Gh*Lz~L%8r^-HqqMlLQ%hS%e%8u|9R5rU2D!ZEGv8w-Y+{5~$Ay(PdU}aZ> zmEBNfFSfACZmP0rWL0)cmFx$*p4}FoCF`zrN0nu0X_ej;*95E5r;tu#9NwLzEXYWe z&W7mJW>8vFy_*y<^*gHJH?g`Ihr2?@B=dPF<1=^@J+FA;!eG4#Up$#%Vv=95FpVHO z*QP>wn=1b72Y>wcWh!uOXf3#wGufkKTPx!WDy)C=s=1akIq+niGkF5eiauFCnw$=p zyOp^Xm&~iv<7BxOO6Jw+8L)FLulN;#y!8bQ3AvWyydT)V*?+C15# zW8YTAJYv1XFmtVW(6#14*V@Rn%+H!@ZRJ{VvRrE?m!1MU*Pa#kCd;Mva%m!2E_M)a z0L!(9KtR%>Q5jApGblmS#bJJI=&H(^?a|JYXgF=4G6k6 zSg!T#V6MF)*T$3O+7P+44eVTdRs0KCPus7_rA!^cdKw=pu1A)O!^D%xa(TG;AXo*b zs}`$2f$qJQoME_MaPCg#YBs2OaCk$L3z^y^s^*1KgzB*5s-_N$s!32DqdGjfy{R8i z4S#}F{n2whwT@5+jzMuxwIjvpaouqp8YNC8YtBcDTZ2^qgKgm3j7Z*YZ!?+~Eva8Y z*+F$=@-L=ti>mve9HKfZITr0ww=+D4bJgvipq!;T+I71o6?D5+b#!}fvd2yzu7U3Y zVEw_7*6q2$ZqE&Ndy%?*8E3dF%VKpqTNms05_PvE*mZlU_-V4bw@lr8fvoN>7k@%l z_g9Foku@Ffi=XJ~o3#unufX9gOTKCa_kifkS_|cKs>_pejPUK48CAc4vX$zJA71T30~!*y?!y7xE~*WC}q&B*$i=0oudWKG9v@m#R_$zWx1 zc&m~>vA5X?(W&L2RHgbs^7p3RiK;cA)S>!e@?}#?b;r5tb|WayP+jf1-JJ@$U7jshN72 z8i7)V>i*;~s@1twqiR_wRj7WKT;9~aRKx4AdbAe0t?qrV?!61eb@!lnJK6obj`#{$ zd;73BZ*RW<2HVKD`967}y-jV1u7Izh?4)`yd5fuIqw0Pr2dExOK56O>s_OPpD5t0% zcHO>01>GJ}9o_yn*<+_i)WP?PpHCNaevEbd-(a`@4R-sMx;+PHxV?Q_-QGx6x9_OC zC%~@Tcg6R}>fSwdFJB+9x*Lk~JN%;$Oh(c2lI>0*7}mdH-m?V~_Mj z1gY(zbfp@T@~5daqH0el{iu2=51?9gdtg)@3}pz_*pviQ*H8_gz-mp{*>Pf2V_H8H`kfxU@MWsF59}2qtJO9^1D8*x^4?cw-4Z`}Q@z(87O5n*llrjNl zMW3wWQ_=x9;|$mBf+>8mK1o)$3#IVMI_?D&tDg!eEKVltQ?MfHUSqO8B`YePK~{H* ziBFQ%{o>+61HhV&1aUXArYBLn5-c;1v3^G{L3HXjQ1((SnsUq3(2J%X zfbt{NVkwV~^UtjvRZl|sjcW0fvZfA*s+XZ$r<#z`+|+qd^&XUrb)hDv3@~*U)$qqy zorxB>PpXnM8UH|WpJXMA;|GE@IVs|XWKCA8craPll@j8`WL;NEiuZ$M4&Q7O(@09W zX=&Vq=+v!Hc2P}E$%A3g#FQFj>UU6%P)$jB+|(DM>Io>nQ%y~2X6o9gdKt=XswGlh zF!dr;O=&umpACms(oJchm!M!uGxFCNHcIh0q2JcW|9ZlDnKx`o8wIDdQE*C|Yf4Yx z3^%1MG^Mx6n$nh<%6u=wC{5`z;wofKRVz(Zd$OjkwRj0xQ`tuR8(CA^R$Oc_SX13j z+>5NKe^$H}tVTRo58wU3;k8N`Il;I3Iz*@DhLVqJ>y-CQE&7V71)&tD+9qYQsV$>w zNhps|ZJTn!)M-(*5|o-$+ojwxb$?WC2&FmIXHy=X=x5~(!TW|UVD(M3z)e*LO;u?q zZt6OUJCHS%oy0@Qn%d6dIb?l4`Vm*PviZ(^bKgzLw%~LQ6Hq4|8Ds$4NG&uLY;{wcwPF)Rdma8E#5PX-e;rHKn69 zmF0)RC{5`YaYM4EYOJQJKUq^ZPW%~JQ#oFIkF2SkAg(nGtf`(To

rzaicSRwKLy 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

Lr*L&2rU23j` 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≷X0$rMRJ@A8&MAC;Pg{E1Kt)bUY4eWHToc0wP? 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%0AKT7+ygd#vd_YwJZZE`3 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%0D@TlgC@~97fi*NGB`BK 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 z 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 F76llQ3pP4R-KR2CDga68AM-6mMfX756iph_^Le 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 literal 0 HcmV?d00001 diff --git a/linux/libSDL2.dylib b/linux/libSDL2.dylib new file mode 120000 index 0000000..0384b98 --- /dev/null +++ b/linux/libSDL2.dylib @@ -0,0 +1 @@ +libSDL2-2.0.0.dylib \ No newline at end of file diff --git a/linux/libSDL2.so b/linux/libSDL2.so new file mode 120000 index 0000000..027f051 --- /dev/null +++ b/linux/libSDL2.so @@ -0,0 +1 @@ +libSDL2-2.0.so.0 \ No newline at end of file diff --git a/linux/release/vgui.dylib b/linux/release/vgui.dylib new file mode 100755 index 0000000000000000000000000000000000000000..5f4bd6167a6eb92f0ae2bf36e35949a196c49876 GIT binary patch literal 431988 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 + +#pragma warning(disable : 4244) +#pragma warning(disable : 4305) + +extern playermove_t *pmove; + +// Expand debugging BBOX particle hulls by this many units. +#define BOX_GAP 0.0f + +static int PM_boxpnt[6][4] = +{ + { 0, 4, 6, 2 }, // +X + { 0, 1, 5, 4 }, // +Y + { 0, 2, 3, 1 }, // +Z + { 7, 5, 1, 3 }, // -X + { 7, 3, 2, 6 }, // -Y + { 7, 6, 4, 5 }, // -Z +}; + +void PM_ShowClipBox( void ) +{ +#if defined( _DEBUG ) + vec3_t org; + vec3_t offset = { 0, 0, 0 }; + + if ( !pmove->runfuncs ) + return; + + // More debugging, draw the particle bbox for player and for the entity we are looking directly at. + // aslo prints entity info to the console overlay. + //if ( !pmove->server ) + // return; + + // Draw entity in center of view + // Also draws the normal to the clip plane that intersects our movement ray. Leaves a particle + // trail at the intersection point. + PM_ViewEntity(); + + VectorCopy( pmove->origin, org ); + + if ( pmove->server ) + { + VectorAdd( org, offset, org ); + } + else + { + VectorSubtract( org, offset, org ); + } + + // Show our BBOX in particles. + PM_DrawBBox( pmove->player_mins[pmove->usehull], pmove->player_maxs[pmove->usehull], org, pmove->server ? 132 : 0, 0.1 ); + + PM_ParticleLine( org, org, pmove->server ? 132 : 0, 0.1, 5.0 ); +/* + { + int i; + for ( i = 0; i < pmove->numphysent; i++ ) + { + if ( pmove->physents[ i ].info >= 1 && pmove->physents[ i ].info <= 4 ) + { + PM_DrawBBox( pmove->player_mins[pmove->usehull], pmove->player_maxs[pmove->usehull], pmove->physents[i].origin, 132, 0.1 ); + } + } + } +*/ +#endif +} + +/* +=============== +PM_ParticleLine(vec3_t start, vec3_t end, int color, float life) + +================ +*/ +void PM_ParticleLine(vec3_t start, vec3_t end, int pcolor, float life, float vert) +{ + float linestep = 2.0f; + float curdist; + float len; + vec3_t curpos; + vec3_t diff; + int i; + // Determine distance; + + VectorSubtract(end, start, diff); + + len = VectorNormalize(diff); + + curdist = 0; + while (curdist <= len) + { + for (i = 0; i < 3; i++) + curpos[i] = start[i] + curdist * diff[i]; + + pmove->PM_Particle( curpos, pcolor, life, 0, vert); + curdist += linestep; + } + +} + +/* +================ +PM_DrawRectangle(vec3_t tl, vec3_t br) + +================ +*/ +void PM_DrawRectangle(vec3_t tl, vec3_t bl, vec3_t tr, vec3_t br, int pcolor, float life) +{ + PM_ParticleLine(tl, bl, pcolor, life, 0); + PM_ParticleLine(bl, br, pcolor, life, 0); + PM_ParticleLine(br, tr, pcolor, life, 0); + PM_ParticleLine(tr, tl, pcolor, life, 0); +} + +/* +================ +PM_DrawPhysEntBBox(int num) + +================ +*/ +void PM_DrawPhysEntBBox(int num, int pcolor, float life) +{ + physent_t *pe; + vec3_t org; + int j; + vec3_t tmp; + vec3_t p[8]; + float gap = BOX_GAP; + vec3_t modelmins, modelmaxs; + + if (num >= pmove->numphysent || + num <= 0) + return; + + pe = &pmove->physents[num]; + + if (pe->model) + { + VectorCopy(pe->origin, org); + + pmove->PM_GetModelBounds( pe->model, modelmins, modelmaxs ); + for (j = 0; j < 8; j++) + { + tmp[0] = (j & 1) ? modelmins[0] - gap : modelmaxs[0] + gap; + tmp[1] = (j & 2) ? modelmins[1] - gap : modelmaxs[1] + gap; + tmp[2] = (j & 4) ? modelmins[2] - gap : modelmaxs[2] + gap; + + VectorCopy(tmp, p[j]); + } + + // If the bbox should be rotated, do that + if (pe->angles[0] || pe->angles[1] || pe->angles[2]) + { + vec3_t forward, right, up; + + AngleVectorsTranspose(pe->angles, forward, right, up); + for (j = 0; j < 8; j++) + { + VectorCopy(p[j], tmp); + p[j][0] = DotProduct ( tmp, forward ); + p[j][1] = DotProduct ( tmp, right ); + p[j][2] = DotProduct ( tmp, up ); + } + } + + // Offset by entity origin, if any. + for (j = 0; j < 8; j++) + VectorAdd(p[j], org, p[j]); + + for (j = 0; j < 6; j++) + { + PM_DrawRectangle( + p[PM_boxpnt[j][1]], + p[PM_boxpnt[j][0]], + p[PM_boxpnt[j][2]], + p[PM_boxpnt[j][3]], + pcolor, life); + } + } + else + { + for (j = 0; j < 8; j++) + { + tmp[0] = (j & 1) ? pe->mins[0] : pe->maxs[0]; + tmp[1] = (j & 2) ? pe->mins[1] : pe->maxs[1]; + tmp[2] = (j & 4) ? pe->mins[2] : pe->maxs[2]; + + VectorAdd(tmp, pe->origin, tmp); + VectorCopy(tmp, p[j]); + } + + for (j = 0; j < 6; j++) + { + PM_DrawRectangle( + p[PM_boxpnt[j][1]], + p[PM_boxpnt[j][0]], + p[PM_boxpnt[j][2]], + p[PM_boxpnt[j][3]], + pcolor, life); + } + + } +} + +/* +================ +PM_DrawBBox(vec3_t mins, vec3_t maxs, vec3_t origin, int pcolor, float life) + +================ +*/ +void PM_DrawBBox(vec3_t mins, vec3_t maxs, vec3_t origin, int pcolor, float life) +{ + int j; + + vec3_t tmp; + vec3_t p[8]; + float gap = BOX_GAP; + + for (j = 0; j < 8; j++) + { + tmp[0] = (j & 1) ? mins[0] - gap : maxs[0] + gap; + tmp[1] = (j & 2) ? mins[1] - gap : maxs[1] + gap ; + tmp[2] = (j & 4) ? mins[2] - gap : maxs[2] + gap ; + + VectorAdd(tmp, origin, tmp); + VectorCopy(tmp, p[j]); + } + + for (j = 0; j < 6; j++) + { + PM_DrawRectangle( + p[PM_boxpnt[j][1]], + p[PM_boxpnt[j][0]], + p[PM_boxpnt[j][2]], + p[PM_boxpnt[j][3]], + pcolor, life); + } +} + + +#ifndef DEDICATED + +/* +================ +PM_ViewEntity + +Shows a particle trail from player to entity in crosshair. +Shows particles at that entities bbox + +Tries to shoot a ray out by about 128 units. +================ +*/ +void PM_ViewEntity( void ) +{ + vec3_t forward, right, up; + float raydist = 256.0f; + vec3_t origin; + vec3_t end; + int i; + pmtrace_t trace; + int pcolor = 77; + float fup; + +#if 0 + if ( !pm_showclip.value ) + return; +#endif + + AngleVectors (pmove->angles, forward, right, up); // Determine movement angles + + VectorCopy( pmove->origin, origin); + + fup = 0.5*( pmove->player_mins[pmove->usehull][2] + pmove->player_maxs[pmove->usehull][2] ); + fup += pmove->view_ofs[2]; + fup -= 4; + + for (i = 0; i < 3; i++) + { + end[i] = origin[i] + raydist * forward[i]; + } + + trace = pmove->PM_PlayerTrace( origin, end, PM_STUDIO_BOX, -1 ); + + if (trace.ent > 0) // Not the world + { + pcolor = 111; + } + + // Draw the hull or bbox. + if (trace.ent > 0) + { + PM_DrawPhysEntBBox(trace.ent, pcolor, 0.3f); + } +} + +#endif diff --git a/pm_shared/pm_debug.h b/pm_shared/pm_debug.h new file mode 100644 index 0000000..cdbda84 --- /dev/null +++ b/pm_shared/pm_debug.h @@ -0,0 +1,24 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PM_DEBUG_H +#define PM_DEBUG_H +#pragma once + +void PM_ViewEntity( void ); +void PM_DrawBBox(vec3_t mins, vec3_t maxs, vec3_t origin, int pcolor, float life); +void PM_ParticleLine(vec3_t start, vec3_t end, int pcolor, float life, float vert); +void PM_ShowClipBox( void ); + +#endif // PMOVEDBG_H diff --git a/pm_shared/pm_defs.h b/pm_shared/pm_defs.h new file mode 100644 index 0000000..37fa80a --- /dev/null +++ b/pm_shared/pm_defs.h @@ -0,0 +1,223 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// pm_defs.h +#if !defined( PM_DEFSH ) +#define PM_DEFSH +#pragma once + +#include "archtypes.h" // DAL +#define MAX_PHYSENTS 600 // Must have room for all entities in the world. +#define MAX_MOVEENTS 64 +#define MAX_CLIP_PLANES 5 + +#define PM_NORMAL 0x00000000 +#define PM_STUDIO_IGNORE 0x00000001 // Skip studio models +#define PM_STUDIO_BOX 0x00000002 // Use boxes for non-complex studio models (even in traceline) +#define PM_GLASS_IGNORE 0x00000004 // Ignore entities with non-normal rendermode +#define PM_WORLD_ONLY 0x00000008 // Only trace against the world + +// Values for flags parameter of PM_TraceLine +#define PM_TRACELINE_PHYSENTSONLY 0 +#define PM_TRACELINE_ANYVISIBLE 1 + + +#include "pm_info.h" + +// PM_PlayerTrace results. +#include "pmtrace.h" + +#if !defined ( USERCMD_H ) +#include "usercmd.h" +#endif + +// physent_t +typedef struct physent_s +{ + char name[32]; // Name of model, or "player" or "world". + int player; + vec3_t origin; // Model's origin in world coordinates. + struct model_s *model; // only for bsp models + struct model_s *studiomodel; // SOLID_BBOX, but studio clip intersections. + vec3_t mins, maxs; // only for non-bsp models + int info; // For client or server to use to identify (index into edicts or cl_entities) + vec3_t angles; // rotated entities need this info for hull testing to work. + + int solid; // Triggers and func_door type WATER brushes are SOLID_NOT + int skin; // BSP Contents for such things like fun_door water brushes. + int rendermode; // So we can ignore glass + + // Complex collision detection. + float frame; + int sequence; + byte controller[4]; + byte blending[2]; + + int movetype; + int takedamage; + int blooddecal; + int team; + int classnumber; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; +} physent_t; + + +typedef struct playermove_s +{ + int player_index; // So we don't try to run the PM_CheckStuck nudging too quickly. + qboolean server; // For debugging, are we running physics code on server side? + + qboolean multiplayer; // 1 == multiplayer server + float time; // realtime on host, for reckoning duck timing + float frametime; // Duration of this frame + + vec3_t forward, right, up; // Vectors for angles + // player state + vec3_t origin; // Movement origin. + vec3_t angles; // Movement view angles. + vec3_t oldangles; // Angles before movement view angles were looked at. + vec3_t velocity; // Current movement direction. + vec3_t movedir; // For waterjumping, a forced forward velocity so we can fly over lip of ledge. + vec3_t basevelocity; // Velocity of the conveyor we are standing, e.g. + + // For ducking/dead + vec3_t view_ofs; // Our eye position. + float flDuckTime; // Time we started duck + qboolean bInDuck; // In process of ducking or ducked already? + + // For walking/falling + int flTimeStepSound; // Next time we can play a step sound + int iStepLeft; + + float flFallVelocity; + vec3_t punchangle; + + float flSwimTime; + + float flNextPrimaryAttack; + + int effects; // MUZZLE FLASH, e.g. + + int flags; // FL_ONGROUND, FL_DUCKING, etc. + int usehull; // 0 = regular player hull, 1 = ducked player hull, 2 = point hull + float gravity; // Our current gravity and friction. + float friction; + int oldbuttons; // Buttons last usercmd + float waterjumptime; // Amount of time left in jumping out of water cycle. + qboolean dead; // Are we a dead player? + int deadflag; + int spectator; // Should we use spectator physics model? + int movetype; // Our movement type, NOCLIP, WALK, FLY + + int onground; + int waterlevel; + int watertype; + int oldwaterlevel; + + char sztexturename[256]; + char chtexturetype; + + float maxspeed; + float clientmaxspeed; // Player specific maxspeed + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; + // world state + // Number of entities to clip against. + int numphysent; + physent_t physents[MAX_PHYSENTS]; + // Number of momvement entities (ladders) + int nummoveent; + // just a list of ladders + physent_t moveents[MAX_MOVEENTS]; + + // All things being rendered, for tracing against things you don't actually collide with + int numvisent; + physent_t visents[ MAX_PHYSENTS ]; + + // input to run through physics. + usercmd_t cmd; + + // Trace results for objects we collided with. + int numtouch; + pmtrace_t touchindex[MAX_PHYSENTS]; + + char physinfo[ MAX_PHYSINFO_STRING ]; // Physics info string + + struct movevars_s *movevars; + vec3_t player_mins[ 4 ]; + vec3_t player_maxs[ 4 ]; + + // Common functions + const char *(*PM_Info_ValueForKey) ( const char *s, const char *key ); + void (*PM_Particle)( float *origin, int color, float life, int zpos, int zvel); + int (*PM_TestPlayerPosition) (float *pos, pmtrace_t *ptrace ); + void (*Con_NPrintf)( int idx, char *fmt, ... ); + void (*Con_DPrintf)( char *fmt, ... ); + void (*Con_Printf)( char *fmt, ... ); + double (*Sys_FloatTime)( void ); + void (*PM_StuckTouch)( int hitent, pmtrace_t *ptraceresult ); + int (*PM_PointContents) (float *p, int *truecontents /*filled in if this is non-null*/ ); + int (*PM_TruePointContents) (float *p); + int (*PM_HullPointContents) ( struct hull_s *hull, int num, float *p); + pmtrace_t (*PM_PlayerTrace) (float *start, float *end, int traceFlags, int ignore_pe ); + struct pmtrace_s *(*PM_TraceLine)( float *start, float *end, int flags, int usehulll, int ignore_pe ); + int32 (*RandomLong)( int32 lLow, int32 lHigh ); + float (*RandomFloat)( float flLow, float flHigh ); + int (*PM_GetModelType)( struct model_s *mod ); + void (*PM_GetModelBounds)( struct model_s *mod, float *mins, float *maxs ); + void *(*PM_HullForBsp)( physent_t *pe, float *offset ); + float (*PM_TraceModel)( physent_t *pEnt, float *start, float *end, trace_t *trace ); + int (*COM_FileSize)(char *filename); + byte *(*COM_LoadFile) (char *path, int usehunk, int *pLength); + void (*COM_FreeFile) ( void *buffer ); + char *(*memfgets)( byte *pMemFile, int fileSize, int *pFilePos, char *pBuffer, int bufferSize ); + + // Functions + // Run functions for this frame? + qboolean runfuncs; + void (*PM_PlaySound) ( int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch ); + const char *(*PM_TraceTexture) ( int ground, float *vstart, float *vend ); + void (*PM_PlaybackEventFull) ( int flags, int clientindex, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); + + pmtrace_t (*PM_PlayerTraceEx) (float *start, float *end, int traceFlags, int (*pfnIgnore)( physent_t *pe ) ); + int (*PM_TestPlayerPositionEx) (float *pos, pmtrace_t *ptrace, int (*pfnIgnore)( physent_t *pe ) ); + struct pmtrace_s *(*PM_TraceLineEx)( float *start, float *end, int flags, int usehulll, int (*pfnIgnore)( physent_t *pe ) ); +} playermove_t; + +#endif diff --git a/pm_shared/pm_info.h b/pm_shared/pm_info.h new file mode 100644 index 0000000..d114426 --- /dev/null +++ b/pm_shared/pm_info.h @@ -0,0 +1,24 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Physics info string definition +#if !defined( PM_INFOH ) +#define PM_INFOH +#ifdef _WIN32 +#pragma once +#endif + +#define MAX_PHYSINFO_STRING 256 + +#endif // PM_INFOH diff --git a/pm_shared/pm_materials.h b/pm_shared/pm_materials.h new file mode 100644 index 0000000..97aef40 --- /dev/null +++ b/pm_shared/pm_materials.h @@ -0,0 +1,34 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( PM_MATERIALSH ) +#define PM_MATERIALSH +#pragma once + +#define CBTEXTURENAMEMAX 13 // only load first n chars of name + +#define CHAR_TEX_CONCRETE 'C' // texture types +#define CHAR_TEX_METAL 'M' +#define CHAR_TEX_DIRT 'D' +#define CHAR_TEX_VENT 'V' +#define CHAR_TEX_GRATE 'G' +#define CHAR_TEX_TILE 'T' +#define CHAR_TEX_SLOSH 'S' +#define CHAR_TEX_WOOD 'W' +#define CHAR_TEX_COMPUTER 'P' +#define CHAR_TEX_GLASS 'Y' +#define CHAR_TEX_FLESH 'F' +#define CHAR_TEX_SNOW 'N' + +#endif // !PM_MATERIALSH diff --git a/pm_shared/pm_math.c b/pm_shared/pm_math.c new file mode 100644 index 0000000..35208ad --- /dev/null +++ b/pm_shared/pm_math.c @@ -0,0 +1,433 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// pm_math.c -- math primitives + +#include "mathlib.h" +#include "const.h" +#include + +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 + +#pragma warning(disable : 4244) + +#ifndef DISABLE_VEC_ORIGIN +vec3_t vec3_origin = {0,0,0}; +#endif +int nanmask = 255<<23; + +float anglemod(float a) +{ + a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + +void AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + if (forward) + { + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + } + if (right) + { + right[0] = (-1*sr*sp*cy+-1*cr*-sy); + right[1] = (-1*sr*sp*sy+-1*cr*cy); + right[2] = -1*sr*cp; + } + if (up) + { + up[0] = (cr*sp*cy+-sr*-sy); + up[1] = (cr*sp*sy+-sr*cy); + up[2] = cr*cp; + } +} + +void AngleVectorsTranspose (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + if (forward) + { + forward[0] = cp*cy; + forward[1] = (sr*sp*cy+cr*-sy); + forward[2] = (cr*sp*cy+-sr*-sy); + } + if (right) + { + right[0] = cp*sy; + right[1] = (sr*sp*sy+cr*cy); + right[2] = (cr*sp*sy+-sr*cy); + } + if (up) + { + up[0] = -sp; + up[1] = sr*cp; + up[2] = cr*cp; + } +} + +#ifndef DISABLE_VEC_FUNCS +void AngleMatrix (const vec3_t angles, float (*matrix)[4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + // matrix = (YAW * PITCH) * ROLL + matrix[0][0] = cp*cy; + matrix[1][0] = cp*sy; + matrix[2][0] = -sp; + matrix[0][1] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[2][1] = sr*cp; + matrix[0][2] = (cr*sp*cy+-sr*-sy); + matrix[1][2] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + +void AngleIMatrix (const vec3_t angles, float matrix[3][4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + // matrix = (YAW * PITCH) * ROLL + matrix[0][0] = cp*cy; + matrix[0][1] = cp*sy; + matrix[0][2] = -sp; + matrix[1][0] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[1][2] = sr*cp; + matrix[2][0] = (cr*sp*cy+-sr*-sy); + matrix[2][1] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} +#endif + +void NormalizeAngles( float *angles ) +{ + int i; + // Normalize angles + for ( i = 0; i < 3; i++ ) + { + if ( angles[i] > 180.0 ) + { + angles[i] -= 360.0; + } + else if ( angles[i] < -180.0 ) + { + angles[i] += 360.0; + } + } +} + +/* +=================== +InterpolateAngles + +Interpolate Euler angles. +FIXME: Use Quaternions to avoid discontinuities +Frac is 0.0 to 1.0 ( i.e., should probably be clamped, but doesn't have to be ) +=================== +*/ +void InterpolateAngles( float *start, float *end, float *output, float frac ) +{ + int i; + float ang1, ang2; + float d; + + NormalizeAngles( start ); + NormalizeAngles( end ); + + for ( i = 0 ; i < 3 ; i++ ) + { + ang1 = start[i]; + ang2 = end[i]; + + d = ang2 - ang1; + if ( d > 180 ) + { + d -= 360; + } + else if ( d < -180 ) + { + d += 360; + } + + output[i] = ang1 + d * frac; + } + + NormalizeAngles( output ); +} + + +/* +=================== +AngleBetweenVectors + +=================== +*/ +float AngleBetweenVectors( const vec3_t v1, const vec3_t v2 ) +{ + float angle; + float l1 = Length( v1 ); + float l2 = Length( v2 ); + + if ( !l1 || !l2 ) + return 0.0f; + + angle = acos( DotProduct( v1, v2 ) ) / (l1*l2); + angle = ( angle * 180.0f ) / M_PI; + + return angle; +} + +#ifndef DISABLE_VEC_FUNCS +void VectorTransform (const vec3_t in1, float in2[3][4], vec3_t out) +{ + out[0] = DotProduct(in1, in2[0]) + in2[0][3]; + out[1] = DotProduct(in1, in2[1]) + in2[1][3]; + out[2] = DotProduct(in1, in2[2]) + in2[2][3]; +} + +int VectorCompare (const vec3_t v1, const vec3_t v2) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (v1[i] != v2[i]) + return 0; + + return 1; +} + +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]; +} +#endif + + +vec_t _DotProduct (vec3_t v1, vec3_t v2) +{ + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]-vecb[0]; + out[1] = veca[1]-vecb[1]; + out[2] = veca[2]-vecb[2]; +} + +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]+vecb[0]; + out[1] = veca[1]+vecb[1]; + out[2] = veca[2]+vecb[2]; +} + +void _VectorCopy (vec3_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +#ifndef DISABLE_VEC_FUNCS +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]; +} +#endif + +double sqrt(double x); + +#ifndef DISABLE_VEC_FUNCS +float Length(const vec3_t v) +{ + int i; + float length = 0.0f; + + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); // FIXME + + return length; +} +#endif + +float Distance(const vec3_t v1, const vec3_t v2) +{ + vec3_t d; + VectorSubtract(v2,v1,d); + return Length(d); +} + +#ifndef DISABLE_VEC_FUNCS +float VectorNormalize (vec3_t v) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; + +} + +void VectorInverse (vec3_t v) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[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; +} +#endif + + +int Q_log2(int val) +{ + int answer=0; + while (val>>=1) + answer++; + return answer; +} + +void VectorMatrix( vec3_t forward, vec3_t right, vec3_t up) +{ + vec3_t tmp; + + if (forward[0] == 0 && forward[1] == 0) + { + right[0] = 1; + right[1] = 0; + right[2] = 0; + up[0] = -forward[2]; + up[1] = 0; + up[2] = 0; + return; + } + + tmp[0] = 0; tmp[1] = 0; tmp[2] = 1.0; + CrossProduct( forward, tmp, right ); + VectorNormalize( right ); + CrossProduct( right, forward, up ); + VectorNormalize( up ); +} + + +#ifndef DISABLE_VEC_FUNCS +void VectorAngles( const vec3_t forward, vec3_t angles ) +{ + float tmp, yaw, pitch; + + if (forward[1] == 0 && forward[0] == 0) + { + yaw = 0; + if (forward[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + yaw = (atan2(forward[1], forward[0]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + + tmp = sqrt (forward[0]*forward[0] + forward[1]*forward[1]); + pitch = (atan2(forward[2], tmp) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + angles[0] = pitch; + angles[1] = yaw; + angles[2] = 0; +} +#endif diff --git a/pm_shared/pm_movevars.h b/pm_shared/pm_movevars.h new file mode 100644 index 0000000..f4f46ca --- /dev/null +++ b/pm_shared/pm_movevars.h @@ -0,0 +1,47 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// pm_movevars.h +#if !defined( PM_MOVEVARSH ) +#define PM_MOVEVARSH + +// movevars_t // Physics variables. +typedef struct movevars_s movevars_t; + +struct movevars_s +{ + float gravity; // Gravity for map + float stopspeed; // Deceleration when not moving + float maxspeed; // Max allowed speed + float spectatormaxspeed; + float accelerate; // Acceleration factor + float airaccelerate; // Same for when in open air + float wateraccelerate; // Same for when in water + float friction; + float edgefriction; // Extra friction near dropofs + float waterfriction; // Less in water + float entgravity; // 1.0 + float bounce; // Wall bounce value. 1.0 + float stepsize; // sv_stepsize; + float maxvelocity; // maximum server velocity. + float zmax; // Max z-buffer range (for GL) + float waveHeight; // Water wave height (for GL) + qboolean footsteps; // Play footstep sounds + char skyName[32]; // Name of the sky map + float rollangle; + float rollspeed; + float skycolor_r; // Sky color + float skycolor_g; // + float skycolor_b; // + float skyvec_x; // Sky vector + float skyvec_y; // + float skyvec_z; // +}; + +extern movevars_t movevars; + +#endif \ No newline at end of file diff --git a/pm_shared/pm_shared.c b/pm_shared/pm_shared.c new file mode 100644 index 0000000..8d58657 --- /dev/null +++ b/pm_shared/pm_shared.c @@ -0,0 +1,3367 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include +#include "mathlib.h" +#include "const.h" +#include "usercmd.h" +#include "pm_defs.h" +#include "pm_shared.h" +#include "pm_movevars.h" +#include "pm_debug.h" +#include // NULL +#include // sqrt +#include // strcpy +#include // atoi +#include // isspace + +#ifdef CLIENT_DLL + // Spectator Mode + int iJumpSpectator; +#ifndef DISABLE_JUMP_ORIGIN + float vJumpOrigin[3]; + float vJumpAngles[3]; +#else + extern float vJumpOrigin[3]; + extern float vJumpAngles[3]; +#endif +#endif + +static int pm_shared_initialized = 0; + +#pragma warning( disable : 4305 ) + +typedef enum {mod_brush, mod_sprite, mod_alias, mod_studio} modtype_t; + +playermove_t *pmove = NULL; + +typedef struct +{ + int planenum; + short children[2]; // negative numbers are contents +} dclipnode_t; + +typedef struct mplane_s +{ + vec3_t normal; // surface normal + float dist; // closest appoach to origin + byte type; // for texture axis selection and fast side tests + byte signbits; // signx + signy<<1 + signz<<1 + byte pad[2]; +} mplane_t; + +typedef struct hull_s +{ + dclipnode_t *clipnodes; + mplane_t *planes; + int firstclipnode; + int lastclipnode; + vec3_t clip_mins; + vec3_t clip_maxs; +} hull_t; + +// Ducking time +#define TIME_TO_DUCK 0.4 +#define VEC_DUCK_HULL_MIN -18 +#define VEC_DUCK_HULL_MAX 18 +#define VEC_DUCK_VIEW 12 +#define PM_DEAD_VIEWHEIGHT -8 +#define MAX_CLIMB_SPEED 200 +#define STUCK_MOVEUP 1 +#define STUCK_MOVEDOWN -1 +#define VEC_HULL_MIN -36 +#define VEC_HULL_MAX 36 +#define VEC_VIEW 28 +#define STOP_EPSILON 0.1 + +#define CTEXTURESMAX 512 // max number of textures loaded +#define CBTEXTURENAMEMAX 13 // only load first n chars of name + +#define CHAR_TEX_CONCRETE 'C' // texture types +#define CHAR_TEX_METAL 'M' +#define CHAR_TEX_DIRT 'D' +#define CHAR_TEX_VENT 'V' +#define CHAR_TEX_GRATE 'G' +#define CHAR_TEX_TILE 'T' +#define CHAR_TEX_SLOSH 'S' +#define CHAR_TEX_WOOD 'W' +#define CHAR_TEX_COMPUTER 'P' +#define CHAR_TEX_GLASS 'Y' +#define CHAR_TEX_FLESH 'F' + +#define STEP_CONCRETE 0 // default step sound +#define STEP_METAL 1 // metal floor +#define STEP_DIRT 2 // dirt, sand, rock +#define STEP_VENT 3 // ventillation duct +#define STEP_GRATE 4 // metal grating +#define STEP_TILE 5 // floor tiles +#define STEP_SLOSH 6 // shallow liquid puddle +#define STEP_WADE 7 // wading in liquid +#define STEP_LADDER 8 // climbing ladder + +#define PLAYER_FATAL_FALL_SPEED 1024// approx 60 feet +#define PLAYER_MAX_SAFE_FALL_SPEED 580// approx 20 feet +#define DAMAGE_FOR_FALL_SPEED (float) 100 / ( PLAYER_FATAL_FALL_SPEED - PLAYER_MAX_SAFE_FALL_SPEED )// damage per unit per second. +#define PLAYER_MIN_BOUNCE_SPEED 200 +#define PLAYER_FALL_PUNCH_THRESHHOLD (float)350 // won't punch player's screen/make scrape noise unless player falling at least this fast. + +#define PLAYER_LONGJUMP_SPEED 350 // how fast we longjump + +#define PLAYER_DUCKING_MULTIPLIER 0.333 + +// double to float warning +#pragma warning(disable : 4244) +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) < (b)) ? (a) : (b)) +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 + +#define MAX_CLIENTS 32 + +#define CONTENTS_CURRENT_0 -9 +#define CONTENTS_CURRENT_90 -10 +#define CONTENTS_CURRENT_180 -11 +#define CONTENTS_CURRENT_270 -12 +#define CONTENTS_CURRENT_UP -13 +#define CONTENTS_CURRENT_DOWN -14 + +#define CONTENTS_TRANSLUCENT -15 + +static vec3_t rgv3tStuckTable[54]; +static int rgStuckLast[MAX_CLIENTS][2]; + +// Texture names +static int gcTextures = 0; +static char grgszTextureName[CTEXTURESMAX][CBTEXTURENAMEMAX]; +static char grgchTextureType[CTEXTURESMAX]; + +int g_onladder = 0; + +void PM_SwapTextures( int i, int j ) +{ + char chTemp; + char szTemp[ CBTEXTURENAMEMAX ]; + + strcpy( szTemp, grgszTextureName[ i ] ); + chTemp = grgchTextureType[ i ]; + + strcpy( grgszTextureName[ i ], grgszTextureName[ j ] ); + grgchTextureType[ i ] = grgchTextureType[ j ]; + + strcpy( grgszTextureName[ j ], szTemp ); + grgchTextureType[ j ] = chTemp; +} + +void PM_SortTextures( void ) +{ + // Bubble sort, yuck, but this only occurs at startup and it's only 512 elements... + // + int i, j; + + for ( i = 0 ; i < gcTextures; i++ ) + { + for ( j = i + 1; j < gcTextures; j++ ) + { + if ( stricmp( grgszTextureName[ i ], grgszTextureName[ j ] ) > 0 ) + { + // Swap + // + PM_SwapTextures( i, j ); + } + } + } +} + +void PM_InitTextureTypes() +{ + char buffer[512]; + int i, j; + byte *pMemFile; + int fileSize, filePos; + static qboolean bTextureTypeInit = false; + + if ( bTextureTypeInit ) + return; + + memset(&(grgszTextureName[0][0]), 0, CTEXTURESMAX * CBTEXTURENAMEMAX); + memset(grgchTextureType, 0, CTEXTURESMAX); + + gcTextures = 0; + memset(buffer, 0, 512); + + fileSize = pmove->COM_FileSize( "sound/materials.txt" ); + pMemFile = pmove->COM_LoadFile( "sound/materials.txt", 5, NULL ); + if ( !pMemFile ) + return; + + filePos = 0; + // for each line in the file... + while ( pmove->memfgets( pMemFile, fileSize, &filePos, buffer, 511 ) != NULL && (gcTextures < CTEXTURESMAX) ) + { + // skip whitespace + i = 0; + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // skip comment lines + if (buffer[i] == '/' || !isalpha(buffer[i])) + continue; + + // get texture type + grgchTextureType[gcTextures] = toupper(buffer[i++]); + + // skip whitespace + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // get sentence name + j = i; + while (buffer[j] && !isspace(buffer[j])) + j++; + + if (!buffer[j]) + continue; + + // null-terminate name and save in sentences array + j = min (j, CBTEXTURENAMEMAX-1+i); + buffer[j] = 0; + strcpy(&(grgszTextureName[gcTextures++][0]), &(buffer[i])); + } + + // Must use engine to free since we are in a .dll + pmove->COM_FreeFile ( pMemFile ); + + PM_SortTextures(); + + bTextureTypeInit = true; +} + +char PM_FindTextureType( char *name ) +{ + int left, right, pivot; + int val; + + assert( pm_shared_initialized ); + + left = 0; + right = gcTextures - 1; + + while ( left <= right ) + { + pivot = ( left + right ) / 2; + + val = strnicmp( name, grgszTextureName[ pivot ], CBTEXTURENAMEMAX-1 ); + if ( val == 0 ) + { + return grgchTextureType[ pivot ]; + } + else if ( val > 0 ) + { + left = pivot + 1; + } + else if ( val < 0 ) + { + right = pivot - 1; + } + } + + return CHAR_TEX_CONCRETE; +} + +void PM_PlayStepSound( int step, float fvol ) +{ + static int iSkipStep = 0; + int irand; + vec3_t hvel; + + pmove->iStepLeft = !pmove->iStepLeft; + + if ( !pmove->runfuncs ) + { + return; + } + + irand = pmove->RandomLong(0,1) + ( pmove->iStepLeft * 2 ); + + // FIXME mp_footsteps needs to be a movevar + if ( pmove->multiplayer && !pmove->movevars->footsteps ) + return; + + VectorCopy( pmove->velocity, hvel ); + hvel[2] = 0.0; + + if ( pmove->multiplayer && ( !g_onladder && Length( hvel ) <= 220 ) ) + return; + + // irand - 0,1 for right foot, 2,3 for left foot + // used to alternate left and right foot + // FIXME, move to player state + + switch (step) + { + default: + case STEP_CONCRETE: + switch (irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_step1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_step3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_step2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_step4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_METAL: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_metal1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_metal3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_metal2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_metal4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_DIRT: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_dirt1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_dirt3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_dirt2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_dirt4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_VENT: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_duct1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_duct3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_duct2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_duct4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_GRATE: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_grate1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_grate3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_grate2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_grate4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_TILE: + if ( !pmove->RandomLong(0,4) ) + irand = 4; + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_tile1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_tile3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_tile2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_tile4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 4: pmove->PM_PlaySound( CHAN_BODY, "player/pl_tile5.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_SLOSH: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_slosh1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_slosh3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_slosh2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_slosh4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_WADE: + if ( iSkipStep == 0 ) + { + iSkipStep++; + break; + } + + if ( iSkipStep++ == 3 ) + { + iSkipStep = 0; + } + + switch (irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_LADDER: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_ladder1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_ladder3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_ladder2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_ladder4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + } +} + +int PM_MapTextureTypeStepType(char chTextureType) +{ + switch (chTextureType) + { + default: + case CHAR_TEX_CONCRETE: return STEP_CONCRETE; + case CHAR_TEX_METAL: return STEP_METAL; + case CHAR_TEX_DIRT: return STEP_DIRT; + case CHAR_TEX_VENT: return STEP_VENT; + case CHAR_TEX_GRATE: return STEP_GRATE; + case CHAR_TEX_TILE: return STEP_TILE; + case CHAR_TEX_SLOSH: return STEP_SLOSH; + } +} + +/* +==================== +PM_CatagorizeTextureType + +Determine texture info for the texture we are standing on. +==================== +*/ +void PM_CatagorizeTextureType( void ) +{ + vec3_t start, end; + const char *pTextureName; + + VectorCopy( pmove->origin, start ); + VectorCopy( pmove->origin, end ); + + // Straight down + end[2] -= 64; + + // Fill in default values, just in case. + pmove->sztexturename[0] = '\0'; + pmove->chtexturetype = CHAR_TEX_CONCRETE; + + pTextureName = pmove->PM_TraceTexture( pmove->onground, start, end ); + if ( !pTextureName ) + return; + + // strip leading '-0' or '+0~' or '{' or '!' + if (*pTextureName == '-' || *pTextureName == '+') + pTextureName += 2; + + if (*pTextureName == '{' || *pTextureName == '!' || *pTextureName == '~' || *pTextureName == ' ') + pTextureName++; + // '}}' + + strcpy( pmove->sztexturename, pTextureName); + pmove->sztexturename[ CBTEXTURENAMEMAX - 1 ] = 0; + + // get texture type + pmove->chtexturetype = PM_FindTextureType( pmove->sztexturename ); +} + +void PM_UpdateStepSound( void ) +{ + int fWalking; + float fvol; + vec3_t knee; + vec3_t feet; + vec3_t center; + float height; + float speed; + float velrun; + float velwalk; + float flduck; + int fLadder; + int step; + + if ( pmove->flTimeStepSound > 0 ) + return; + + if ( pmove->flags & FL_FROZEN ) + return; + + PM_CatagorizeTextureType(); + + speed = Length( pmove->velocity ); + + // determine if we are on a ladder + fLadder = ( pmove->movetype == MOVETYPE_FLY );// IsOnLadder(); + + // UNDONE: need defined numbers for run, walk, crouch, crouch run velocities!!!! + if ( ( pmove->flags & FL_DUCKING) || fLadder ) + { + velwalk = 60; // These constants should be based on cl_movespeedkey * cl_forwardspeed somehow + velrun = 80; // UNDONE: Move walking to server + flduck = 100; + } + else + { + velwalk = 120; + velrun = 210; + flduck = 0; + } + + // If we're on a ladder or on the ground, and we're moving fast enough, + // play step sound. Also, if pmove->flTimeStepSound is zero, get the new + // sound right away - we just started moving in new level. + if ( (fLadder || ( pmove->onground != -1 ) ) && + ( Length( pmove->velocity ) > 0.0 ) && + ( speed >= velwalk || !pmove->flTimeStepSound ) ) + { + fWalking = speed < velrun; + + VectorCopy( pmove->origin, center ); + VectorCopy( pmove->origin, knee ); + VectorCopy( pmove->origin, feet ); + + height = pmove->player_maxs[ pmove->usehull ][ 2 ] - pmove->player_mins[ pmove->usehull ][ 2 ]; + + knee[2] = pmove->origin[2] - 0.3 * height; + feet[2] = pmove->origin[2] - 0.5 * height; + + // find out what we're stepping in or on... + if (fLadder) + { + step = STEP_LADDER; + fvol = 0.35; + pmove->flTimeStepSound = 350; + } + else if ( pmove->PM_PointContents ( knee, NULL ) == CONTENTS_WATER ) + { + step = STEP_WADE; + fvol = 0.65; + pmove->flTimeStepSound = 600; + } + else if ( pmove->PM_PointContents ( feet, NULL ) == CONTENTS_WATER ) + { + step = STEP_SLOSH; + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + } + else + { + // find texture under player, if different from current texture, + // get material type + step = PM_MapTextureTypeStepType( pmove->chtexturetype ); + + switch ( pmove->chtexturetype ) + { + default: + case CHAR_TEX_CONCRETE: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_METAL: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_DIRT: + fvol = fWalking ? 0.25 : 0.55; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_VENT: + fvol = fWalking ? 0.4 : 0.7; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_GRATE: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_TILE: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_SLOSH: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + } + } + + pmove->flTimeStepSound += flduck; // slower step time if ducking + + // play the sound + // 35% volume if ducking + if ( pmove->flags & FL_DUCKING ) + { + fvol *= 0.35; + } + + PM_PlayStepSound( step, fvol ); + } +} + +/* +================ +PM_AddToTouched + +Add's the trace result to touch list, if contact is not already in list. +================ +*/ +qboolean PM_AddToTouched(pmtrace_t tr, vec3_t impactvelocity) +{ + int i; + + for (i = 0; i < pmove->numtouch; i++) + { + if (pmove->touchindex[i].ent == tr.ent) + break; + } + if (i != pmove->numtouch) // Already in list. + return false; + + VectorCopy( impactvelocity, tr.deltavelocity ); + + if (pmove->numtouch >= MAX_PHYSENTS) + pmove->Con_DPrintf("Too many entities were touched!\n"); + + pmove->touchindex[pmove->numtouch++] = tr; + return true; +} + +/* +================ +PM_CheckVelocity + +See if the player has a bogus velocity value. +================ +*/ +void PM_CheckVelocity () +{ + int i; + +// +// bound velocity +// + for (i=0 ; i<3 ; i++) + { + // See if it's bogus. + if (IS_NAN(pmove->velocity[i])) + { + pmove->Con_Printf ("PM Got a NaN velocity %i\n", i); + pmove->velocity[i] = 0; + } + if (IS_NAN(pmove->origin[i])) + { + pmove->Con_Printf ("PM Got a NaN origin on %i\n", i); + pmove->origin[i] = 0; + } + + // Bound it. + if (pmove->velocity[i] > pmove->movevars->maxvelocity) + { + pmove->Con_DPrintf ("PM Got a velocity too high on %i\n", i); + pmove->velocity[i] = pmove->movevars->maxvelocity; + } + else if (pmove->velocity[i] < -pmove->movevars->maxvelocity) + { + pmove->Con_DPrintf ("PM Got a velocity too low on %i\n", i); + pmove->velocity[i] = -pmove->movevars->maxvelocity; + } + } +} + +/* +================== +PM_ClipVelocity + +Slide off of the impacting object +returns the blocked flags: +0x01 == floor +0x02 == step / wall +================== +*/ +int PM_ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce) +{ + float backoff; + float change; + float angle; + int i, blocked; + + angle = normal[ 2 ]; + + blocked = 0x00; // Assume unblocked. + if (angle > 0) // If the plane that is blocking us has a positive z component, then assume it's a floor. + blocked |= 0x01; // + if (!angle) // If the plane has no Z, it is vertical (wall/step) + blocked |= 0x02; // + + // Determine how far along plane to slide based on incoming direction. + // Scale by overbounce factor. + backoff = DotProduct (in, normal) * overbounce; + + for (i=0 ; i<3 ; i++) + { + change = normal[i]*backoff; + out[i] = in[i] - change; + // If out velocity is too small, zero it out. + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; + } + + // Return blocking flags. + return blocked; +} + +void PM_AddCorrectGravity () +{ + float ent_gravity; + + if ( pmove->waterjumptime ) + return; + + if (pmove->gravity) + ent_gravity = pmove->gravity; + else + ent_gravity = 1.0; + + // Add gravity so they'll be in the correct position during movement + // yes, this 0.5 looks wrong, but it's not. + pmove->velocity[2] -= (ent_gravity * pmove->movevars->gravity * 0.5 * pmove->frametime ); + pmove->velocity[2] += pmove->basevelocity[2] * pmove->frametime; + pmove->basevelocity[2] = 0; + + PM_CheckVelocity(); +} + + +void PM_FixupGravityVelocity () +{ + float ent_gravity; + + if ( pmove->waterjumptime ) + return; + + if (pmove->gravity) + ent_gravity = pmove->gravity; + else + ent_gravity = 1.0; + + // Get the correct velocity for the end of the dt + pmove->velocity[2] -= (ent_gravity * pmove->movevars->gravity * pmove->frametime * 0.5 ); + + PM_CheckVelocity(); +} + +/* +============ +PM_FlyMove + +The basic solid body movement clip that slides along multiple planes +============ +*/ +int PM_FlyMove (void) +{ + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity, original_velocity; + vec3_t new_velocity; + int i, j; + pmtrace_t trace; + vec3_t end; + float time_left, allFraction; + int blocked; + + numbumps = 4; // Bump up to four times + + blocked = 0; // Assume not blocked + numplanes = 0; // and not sliding along any planes + VectorCopy (pmove->velocity, original_velocity); // Store original velocity + VectorCopy (pmove->velocity, primal_velocity); + + allFraction = 0; + time_left = pmove->frametime; // Total time for this movement operation. + + for (bumpcount=0 ; bumpcountvelocity[0] && !pmove->velocity[1] && !pmove->velocity[2]) + break; + + // Assume we can move all the way from the current origin to the + // end point. + for (i=0 ; i<3 ; i++) + end[i] = pmove->origin[i] + time_left * pmove->velocity[i]; + + // See if we can make it from origin to end point. + trace = pmove->PM_PlayerTrace (pmove->origin, end, PM_NORMAL, -1 ); + + allFraction += trace.fraction; + // If we started in a solid object, or we were in solid space + // the whole way, zero out our velocity and return that we + // are blocked by floor and wall. + if (trace.allsolid) + { // entity is trapped in another solid + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf("Trapped 4\n"); + return 4; + } + + // If we moved some portion of the total distance, then + // copy the end position into the pmove->origin and + // zero the plane counter. + if (trace.fraction > 0) + { // actually covered some distance + VectorCopy (trace.endpos, pmove->origin); + VectorCopy (pmove->velocity, original_velocity); + numplanes = 0; + } + + // If we covered the entire distance, we are done + // and can return. + if (trace.fraction == 1) + break; // moved the entire distance + + //if (!trace.ent) + // Sys_Error ("PM_PlayerTrace: !trace.ent"); + + // Save entity that blocked us (since fraction was < 1.0) + // for contact + // Add it if it's not already in the list!!! + PM_AddToTouched(trace, pmove->velocity); + + // If the plane we hit has a high z component in the normal, then + // it's probably a floor + if (trace.plane.normal[2] > 0.7) + { + blocked |= 1; // floor + } + // If the plane has a zero z component in the normal, then it's a + // step or wall + if (!trace.plane.normal[2]) + { + blocked |= 2; // step / wall + //Con_DPrintf("Blocked by %i\n", trace.ent); + } + + // Reduce amount of pmove->frametime left by total time left * fraction + // that we covered. + time_left -= time_left * trace.fraction; + + // Did we run out of planes to clip against? + if (numplanes >= MAX_CLIP_PLANES) + { // this shouldn't really happen + // Stop our movement if so. + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf("Too many planes 4\n"); + + break; + } + + // Set up next clipping plane + VectorCopy (trace.plane.normal, planes[numplanes]); + numplanes++; +// + +// modify original_velocity so it parallels all of the clip planes +// + if ( pmove->movetype == MOVETYPE_WALK && + ((pmove->onground == -1) || (pmove->friction != 1)) ) // relfect player velocity + { + for ( i = 0; i < numplanes; i++ ) + { + if ( planes[i][2] > 0.7 ) + {// floor or slope + PM_ClipVelocity( original_velocity, planes[i], new_velocity, 1 ); + VectorCopy( new_velocity, original_velocity ); + } + else + PM_ClipVelocity( original_velocity, planes[i], new_velocity, 1.0 + pmove->movevars->bounce * (1-pmove->friction) ); + } + + VectorCopy( new_velocity, pmove->velocity ); + VectorCopy( new_velocity, original_velocity ); + } + else + { + for (i=0 ; ivelocity, + 1); + for (j=0 ; jvelocity, planes[j]) < 0) + break; // not ok + } + if (j == numplanes) // Didn't have to clip, so we're ok + break; + } + + // Did we go all the way through plane set + if (i != numplanes) + { // go along this plane + // pmove->velocity is set in clipping call, no need to set again. + ; + } + else + { // go along the crease + if (numplanes != 2) + { + //Con_Printf ("clip velocity, numplanes == %i\n",numplanes); + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf("Trapped 4\n"); + + break; + } + CrossProduct (planes[0], planes[1], dir); + d = DotProduct (dir, pmove->velocity); + VectorScale (dir, d, pmove->velocity ); + } + + // + // if original velocity is against the original velocity, stop dead + // to avoid tiny occilations in sloping corners + // + if (DotProduct (pmove->velocity, primal_velocity) <= 0) + { + //Con_DPrintf("Back\n"); + VectorCopy (vec3_origin, pmove->velocity); + break; + } + } + } + + if ( allFraction == 0 ) + { + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf( "Don't stick\n" ); + } + + return blocked; +} + +/* +============== +PM_Accelerate +============== +*/ +void PM_Accelerate (vec3_t wishdir, float wishspeed, float accel) +{ + int i; + float addspeed, accelspeed, currentspeed; + + // Dead player's don't accelerate + if (pmove->dead) + return; + + // If waterjumping, don't accelerate + if (pmove->waterjumptime) + return; + + // See if we are changing direction a bit + currentspeed = DotProduct (pmove->velocity, wishdir); + + // Reduce wishspeed by the amount of veer. + addspeed = wishspeed - currentspeed; + + // If not going to add any speed, done. + if (addspeed <= 0) + return; + + // Determine amount of accleration. + accelspeed = accel * pmove->frametime * wishspeed * pmove->friction; + + // Cap at addspeed + if (accelspeed > addspeed) + accelspeed = addspeed; + + // Adjust velocity. + for (i=0 ; i<3 ; i++) + { + pmove->velocity[i] += accelspeed * wishdir[i]; + } +} + +/* +===================== +PM_WalkMove + +Only used by players. Moves along the ground when player is a MOVETYPE_WALK. +====================== +*/ +void PM_WalkMove () +{ + int clip; + int oldonground; + int i; + + vec3_t wishvel; + float spd; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + + vec3_t dest, start; + vec3_t original, originalvel; + vec3_t down, downvel; + float downdist, updist; + + pmtrace_t trace; + + // Copy movement amounts + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + // Zero out z components of movement vectors + pmove->forward[2] = 0; + pmove->right[2] = 0; + + VectorNormalize (pmove->forward); // Normalize remainder of vectors. + VectorNormalize (pmove->right); // + + for (i=0 ; i<2 ; i++) // Determine x and y parts of velocity + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + + wishvel[2] = 0; // Zero out z part of velocity + + VectorCopy (wishvel, wishdir); // Determine maginitude of speed of move + wishspeed = VectorNormalize(wishdir); + +// +// Clamp to server defined max speed +// + if (wishspeed > pmove->maxspeed) + { + VectorScale (wishvel, pmove->maxspeed/wishspeed, wishvel); + wishspeed = pmove->maxspeed; + } + + // Set pmove velocity + pmove->velocity[2] = 0; + PM_Accelerate (wishdir, wishspeed, pmove->movevars->accelerate); + pmove->velocity[2] = 0; + + // Add in any base velocity to the current velocity. + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity ); + + spd = Length( pmove->velocity ); + + if (spd < 1.0f) + { + VectorClear( pmove->velocity ); + return; + } + + // If we are not moving, do nothing + //if (!pmove->velocity[0] && !pmove->velocity[1] && !pmove->velocity[2]) + // return; + + oldonground = pmove->onground; + +// first try just moving to the destination + dest[0] = pmove->origin[0] + pmove->velocity[0]*pmove->frametime; + dest[1] = pmove->origin[1] + pmove->velocity[1]*pmove->frametime; + dest[2] = pmove->origin[2]; + + // first try moving directly to the next spot + VectorCopy (dest, start); + trace = pmove->PM_PlayerTrace (pmove->origin, dest, PM_NORMAL, -1 ); + // If we made it all the way, then copy trace end + // as new player position. + if (trace.fraction == 1) + { + VectorCopy (trace.endpos, pmove->origin); + return; + } + + if (oldonground == -1 && // Don't walk up stairs if not on ground. + pmove->waterlevel == 0) + return; + + if (pmove->waterjumptime) // If we are jumping out of water, don't do anything more. + return; + + // Try sliding forward both on ground and up 16 pixels + // take the move that goes farthest + VectorCopy (pmove->origin, original); // Save out original pos & + VectorCopy (pmove->velocity, originalvel); // velocity. + + // Slide move + clip = PM_FlyMove (); + + // Copy the results out + VectorCopy (pmove->origin , down); + VectorCopy (pmove->velocity, downvel); + + // Reset original values. + VectorCopy (original, pmove->origin); + + VectorCopy (originalvel, pmove->velocity); + + // Start out up one stair height + VectorCopy (pmove->origin, dest); + dest[2] += pmove->movevars->stepsize; + + trace = pmove->PM_PlayerTrace (pmove->origin, dest, PM_NORMAL, -1 ); + // If we started okay and made it part of the way at least, + // copy the results to the movement start position and then + // run another move try. + if (!trace.startsolid && !trace.allsolid) + { + VectorCopy (trace.endpos, pmove->origin); + } + +// slide move the rest of the way. + clip = PM_FlyMove (); + +// Now try going back down from the end point +// press down the stepheight + VectorCopy (pmove->origin, dest); + dest[2] -= pmove->movevars->stepsize; + + trace = pmove->PM_PlayerTrace (pmove->origin, dest, PM_NORMAL, -1 ); + + // If we are not on the ground any more then + // use the original movement attempt + if ( trace.plane.normal[2] < 0.7) + goto usedown; + // If the trace ended up in empty space, copy the end + // over to the origin. + if (!trace.startsolid && !trace.allsolid) + { + VectorCopy (trace.endpos, pmove->origin); + } + // Copy this origion to up. + VectorCopy (pmove->origin, pmove->up); + + // decide which one went farther + downdist = (down[0] - original[0])*(down[0] - original[0]) + + (down[1] - original[1])*(down[1] - original[1]); + updist = (pmove->up[0] - original[0])*(pmove->up[0] - original[0]) + + (pmove->up[1] - original[1])*(pmove->up[1] - original[1]); + + if (downdist > updist) + { +usedown: + VectorCopy (down , pmove->origin); + VectorCopy (downvel, pmove->velocity); + } else // copy z value from slide move + pmove->velocity[2] = downvel[2]; + +} + +/* +================== +PM_Friction + +Handles both ground friction and water friction +================== +*/ +void PM_Friction (void) +{ + float *vel; + float speed, newspeed, control; + float friction; + float drop; + vec3_t newvel; + + // If we are in water jump cycle, don't apply friction + if (pmove->waterjumptime) + return; + + // Get velocity + vel = pmove->velocity; + + // Calculate speed + speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1] + vel[2]*vel[2]); + + // If too slow, return + if (speed < 0.1f) + { + return; + } + + drop = 0; + +// apply ground friction + if (pmove->onground != -1) // On an entity that is the ground + { + vec3_t start, stop; + pmtrace_t trace; + + start[0] = stop[0] = pmove->origin[0] + vel[0]/speed*16; + start[1] = stop[1] = pmove->origin[1] + vel[1]/speed*16; + start[2] = pmove->origin[2] + pmove->player_mins[pmove->usehull][2]; + stop[2] = start[2] - 34; + + trace = pmove->PM_PlayerTrace (start, stop, PM_NORMAL, -1 ); + + if (trace.fraction == 1.0) + friction = pmove->movevars->friction*pmove->movevars->edgefriction; + else + friction = pmove->movevars->friction; + + // Grab friction value. + //friction = pmove->movevars->friction; + + friction *= pmove->friction; // player friction? + + // Bleed off some speed, but if we have less than the bleed + // threshhold, bleed the theshold amount. + control = (speed < pmove->movevars->stopspeed) ? + pmove->movevars->stopspeed : speed; + // Add the amount to t'he drop amount. + drop += control*friction*pmove->frametime; + } + +// apply water friction +// if (pmove->waterlevel) +// drop += speed * pmove->movevars->waterfriction * waterlevel * pmove->frametime; + +// scale the velocity + newspeed = speed - drop; + if (newspeed < 0) + newspeed = 0; + + // Determine proportion of old speed we are using. + newspeed /= speed; + + // Adjust velocity according to proportion. + newvel[0] = vel[0] * newspeed; + newvel[1] = vel[1] * newspeed; + newvel[2] = vel[2] * newspeed; + + VectorCopy( newvel, pmove->velocity ); +} + +void PM_AirAccelerate (vec3_t wishdir, float wishspeed, float accel) +{ + int i; + float addspeed, accelspeed, currentspeed, wishspd = wishspeed; + + if (pmove->dead) + return; + if (pmove->waterjumptime) + return; + + // Cap speed + //wishspd = VectorNormalize (pmove->wishveloc); + + if (wishspd > 30) + wishspd = 30; + // Determine veer amount + currentspeed = DotProduct (pmove->velocity, wishdir); + // See how much to add + addspeed = wishspd - currentspeed; + // If not adding any, done. + if (addspeed <= 0) + return; + // Determine acceleration speed after acceleration + + accelspeed = accel * wishspeed * pmove->frametime * pmove->friction; + // Cap it + if (accelspeed > addspeed) + accelspeed = addspeed; + + // Adjust pmove vel. + for (i=0 ; i<3 ; i++) + { + pmove->velocity[i] += accelspeed*wishdir[i]; + } +} + +/* +=================== +PM_WaterMove + +=================== +*/ +void PM_WaterMove (void) +{ + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + vec3_t start, dest; + vec3_t temp; + pmtrace_t trace; + + float speed, newspeed, addspeed, accelspeed; + +// +// user intentions +// + for (i=0 ; i<3 ; i++) + wishvel[i] = pmove->forward[i]*pmove->cmd.forwardmove + pmove->right[i]*pmove->cmd.sidemove; + + // Sinking after no other movement occurs + if (!pmove->cmd.forwardmove && !pmove->cmd.sidemove && !pmove->cmd.upmove) + wishvel[2] -= 60; // drift towards bottom + else // Go straight up by upmove amount. + wishvel[2] += pmove->cmd.upmove; + + // Copy it over and determine speed + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + // Cap speed. + if (wishspeed > pmove->maxspeed) + { + VectorScale (wishvel, pmove->maxspeed/wishspeed, wishvel); + wishspeed = pmove->maxspeed; + } + // Slow us down a bit. + wishspeed *= 0.8; + + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity); +// Water friction + VectorCopy(pmove->velocity, temp); + speed = VectorNormalize(temp); + if (speed) + { + newspeed = speed - pmove->frametime * speed * pmove->movevars->friction * pmove->friction; + + if (newspeed < 0) + newspeed = 0; + VectorScale (pmove->velocity, newspeed/speed, pmove->velocity); + } + else + newspeed = 0; + +// +// water acceleration +// + if ( wishspeed < 0.1f ) + { + return; + } + + addspeed = wishspeed - newspeed; + if (addspeed > 0) + { + + VectorNormalize(wishvel); + accelspeed = pmove->movevars->accelerate * wishspeed * pmove->frametime * pmove->friction; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i = 0; i < 3; i++) + pmove->velocity[i] += accelspeed * wishvel[i]; + } + +// Now move +// assume it is a stair or a slope, so press down from stepheight above + VectorMA (pmove->origin, pmove->frametime, pmove->velocity, dest); + VectorCopy (dest, start); + start[2] += pmove->movevars->stepsize + 1; + trace = pmove->PM_PlayerTrace (start, dest, PM_NORMAL, -1 ); + if (!trace.startsolid && !trace.allsolid) // FIXME: check steep slope? + { // walked up the step, so just keep result and exit + VectorCopy (trace.endpos, pmove->origin); + return; + } + + // Try moving straight along out normal path. + PM_FlyMove (); +} + + +/* +=================== +PM_AirMove + +=================== +*/ +void PM_AirMove (void) +{ + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + + // Copy movement amounts + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + // Zero out z components of movement vectors + pmove->forward[2] = 0; + pmove->right[2] = 0; + // Renormalize + VectorNormalize (pmove->forward); + VectorNormalize (pmove->right); + + // Determine x and y parts of velocity + for (i=0 ; i<2 ; i++) + { + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + } + // Zero out z part of velocity + wishvel[2] = 0; + + // Determine maginitude of speed of move + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + // Clamp to server defined max speed + if (wishspeed > pmove->maxspeed) + { + VectorScale (wishvel, pmove->maxspeed/wishspeed, wishvel); + wishspeed = pmove->maxspeed; + } + + PM_AirAccelerate (wishdir, wishspeed, pmove->movevars->airaccelerate); + + // Add in any base velocity to the current velocity. + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity ); + + PM_FlyMove (); +} + +qboolean PM_InWater( void ) +{ + return ( pmove->waterlevel > 1 ); +} + +/* +============= +PM_CheckWater + +Sets pmove->waterlevel and pmove->watertype values. +============= +*/ +qboolean PM_CheckWater () +{ + vec3_t point; + int cont; + int truecont; + float height; + float heightover2; + + // Pick a spot just above the players feet. + point[0] = pmove->origin[0] + (pmove->player_mins[pmove->usehull][0] + pmove->player_maxs[pmove->usehull][0]) * 0.5; + point[1] = pmove->origin[1] + (pmove->player_mins[pmove->usehull][1] + pmove->player_maxs[pmove->usehull][1]) * 0.5; + point[2] = pmove->origin[2] + pmove->player_mins[pmove->usehull][2] + 1; + + // Assume that we are not in water at all. + pmove->waterlevel = 0; + pmove->watertype = CONTENTS_EMPTY; + + // Grab point contents. + cont = pmove->PM_PointContents (point, &truecont ); + // Are we under water? (not solid and not empty?) + if (cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + { + // Set water type + pmove->watertype = cont; + + // We are at least at level one + pmove->waterlevel = 1; + + height = (pmove->player_mins[pmove->usehull][2] + pmove->player_maxs[pmove->usehull][2]); + heightover2 = height * 0.5; + + // Now check a point that is at the player hull midpoint. + point[2] = pmove->origin[2] + heightover2; + cont = pmove->PM_PointContents (point, NULL ); + // If that point is also under water... + if (cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + { + // Set a higher water level. + pmove->waterlevel = 2; + + // Now check the eye position. (view_ofs is relative to the origin) + point[2] = pmove->origin[2] + pmove->view_ofs[2]; + + cont = pmove->PM_PointContents (point, NULL ); + if (cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + pmove->waterlevel = 3; // In over our eyes + } + + // Adjust velocity based on water current, if any. + if ( ( truecont <= CONTENTS_CURRENT_0 ) && + ( truecont >= CONTENTS_CURRENT_DOWN ) ) + { + // The deeper we are, the stronger the current. + static vec3_t current_table[] = + { + {1, 0, 0}, {0, 1, 0}, {-1, 0, 0}, + {0, -1, 0}, {0, 0, 1}, {0, 0, -1} + }; + + VectorMA (pmove->basevelocity, 50.0*pmove->waterlevel, current_table[CONTENTS_CURRENT_0 - truecont], pmove->basevelocity); + } + } + + return pmove->waterlevel > 1; +} + +/* +============= +PM_CatagorizePosition +============= +*/ +void PM_CatagorizePosition (void) +{ + vec3_t point; + pmtrace_t tr; + +// if the player hull point one unit down is solid, the player +// is on ground + +// see if standing on something solid + + // Doing this before we move may introduce a potential latency in water detection, but + // doing it after can get us stuck on the bottom in water if the amount we move up + // is less than the 1 pixel 'threshold' we're about to snap to. Also, we'll call + // this several times per frame, so we really need to avoid sticking to the bottom of + // water on each call, and the converse case will correct itself if called twice. + PM_CheckWater(); + + point[0] = pmove->origin[0]; + point[1] = pmove->origin[1]; + point[2] = pmove->origin[2] - 2; + + if (pmove->velocity[2] > 180) // Shooting up really fast. Definitely not on ground. + { + pmove->onground = -1; + } + else + { + // Try and move down. + tr = pmove->PM_PlayerTrace (pmove->origin, point, PM_NORMAL, -1 ); + // If we hit a steep plane, we are not on ground + if ( tr.plane.normal[2] < 0.7) + pmove->onground = -1; // too steep + else + pmove->onground = tr.ent; // Otherwise, point to index of ent under us. + + // If we are on something... + if (pmove->onground != -1) + { + // Then we are not in water jump sequence + pmove->waterjumptime = 0; + // If we could make the move, drop us down that 1 pixel + if (pmove->waterlevel < 2 && !tr.startsolid && !tr.allsolid) + VectorCopy (tr.endpos, pmove->origin); + } + + // Standing on an entity other than the world + if (tr.ent > 0) // So signal that we are touching something. + { + PM_AddToTouched(tr, pmove->velocity); + } + } +} + +/* +================= +PM_GetRandomStuckOffsets + +When a player is stuck, it's costly to try and unstick them +Grab a test offset for the player based on a passed in index +================= +*/ +int PM_GetRandomStuckOffsets(int nIndex, int server, vec3_t offset) +{ + // Last time we did a full + int idx; + idx = rgStuckLast[nIndex][server]++; + + VectorCopy(rgv3tStuckTable[idx % 54], offset); + + return (idx % 54); +} + +void PM_ResetStuckOffsets(int nIndex, int server) +{ + rgStuckLast[nIndex][server] = 0; +} + +/* +================= +NudgePosition + +If pmove->origin is in a solid position, +try nudging slightly on all axis to +allow for the cut precision of the net coordinates +================= +*/ +#define PM_CHECKSTUCK_MINTIME 0.05 // Don't check again too quickly. + +int PM_CheckStuck (void) +{ + vec3_t base; + vec3_t offset; + vec3_t test; + int hitent; + int idx; + float fTime; + int i; + pmtrace_t traceresult; + + static float rgStuckCheckTime[MAX_CLIENTS][2]; // Last time we did a full + + // If position is okay, exit + hitent = pmove->PM_TestPlayerPosition (pmove->origin, &traceresult ); + if (hitent == -1 ) + { + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + return 0; + } + + VectorCopy (pmove->origin, base); + + // + // Deal with precision error in network. + // + if (!pmove->server) + { + // World or BSP model + if ( ( hitent == 0 ) || + ( pmove->physents[hitent].model != NULL ) ) + { + int nReps = 0; + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + do + { + i = PM_GetRandomStuckOffsets(pmove->player_index, pmove->server, offset); + + VectorAdd(base, offset, test); + if (pmove->PM_TestPlayerPosition (test, &traceresult ) == -1) + { + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + + VectorCopy ( test, pmove->origin ); + return 0; + } + nReps++; + } while (nReps < 54); + } + } + + // Only an issue on the client. + + if (pmove->server) + idx = 0; + else + idx = 1; + + fTime = pmove->Sys_FloatTime(); + // Too soon? + if (rgStuckCheckTime[pmove->player_index][idx] >= + ( fTime - PM_CHECKSTUCK_MINTIME ) ) + { + return 1; + } + rgStuckCheckTime[pmove->player_index][idx] = fTime; + + pmove->PM_StuckTouch( hitent, &traceresult ); + + i = PM_GetRandomStuckOffsets(pmove->player_index, pmove->server, offset); + + VectorAdd(base, offset, test); + if ( ( hitent = pmove->PM_TestPlayerPosition ( test, NULL ) ) == -1 ) + { + //Con_DPrintf("Nudged\n"); + + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + + if (i >= 27) + VectorCopy ( test, pmove->origin ); + + return 0; + } + + // If player is flailing while stuck in another player ( should never happen ), then see + // if we can't "unstick" them forceably. + if ( pmove->cmd.buttons & ( IN_JUMP | IN_DUCK | IN_ATTACK ) && ( pmove->physents[ hitent ].player != 0 ) ) + { + float x, y, z; + float xystep = 8.0; + float zstep = 18.0; + float xyminmax = xystep; + float zminmax = 4 * zstep; + + for ( z = 0; z <= zminmax; z += zstep ) + { + for ( x = -xyminmax; x <= xyminmax; x += xystep ) + { + for ( y = -xyminmax; y <= xyminmax; y += xystep ) + { + VectorCopy( base, test ); + test[0] += x; + test[1] += y; + test[2] += z; + + if ( pmove->PM_TestPlayerPosition ( test, NULL ) == -1 ) + { + VectorCopy( test, pmove->origin ); + return 0; + } + } + } + } + } + + //VectorCopy (base, pmove->origin); + + return 1; +} + +/* +=============== +PM_SpectatorMove +=============== +*/ +void PM_SpectatorMove (void) +{ + float speed, drop, friction, control, newspeed; + //float accel; + float currentspeed, addspeed, accelspeed; + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + // this routine keeps track of the spectators psoition + // there a two different main move types : track player or moce freely (OBS_ROAMING) + // doesn't need excate track position, only to generate PVS, so just copy + // targets position and real view position is calculated on client (saves server CPU) + + if ( pmove->iuser1 == OBS_ROAMING) + { + +#ifdef CLIENT_DLL + // jump only in roaming mode + if ( iJumpSpectator ) + { + VectorCopy( vJumpOrigin, pmove->origin ); + VectorCopy( vJumpAngles, pmove->angles ); + VectorCopy( vec3_origin, pmove->velocity ); + iJumpSpectator = 0; + return; + } +#endif + // Move around in normal spectator method + + speed = Length (pmove->velocity); + if (speed < 1) + { + VectorCopy (vec3_origin, pmove->velocity) + } + else + { + drop = 0; + + friction = pmove->movevars->friction*1.5; // extra friction + control = speed < pmove->movevars->stopspeed ? pmove->movevars->stopspeed : speed; + drop += control*friction*pmove->frametime; + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + VectorScale (pmove->velocity, newspeed, pmove->velocity); + } + + // accelerate + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + VectorNormalize (pmove->forward); + VectorNormalize (pmove->right); + + for (i=0 ; i<3 ; i++) + { + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + } + wishvel[2] += pmove->cmd.upmove; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + // + // clamp to server defined max speed + // + if (wishspeed > pmove->movevars->spectatormaxspeed) + { + VectorScale (wishvel, pmove->movevars->spectatormaxspeed/wishspeed, wishvel); + wishspeed = pmove->movevars->spectatormaxspeed; + } + + currentspeed = DotProduct(pmove->velocity, wishdir); + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) + return; + + accelspeed = pmove->movevars->accelerate*pmove->frametime*wishspeed; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i=0 ; i<3 ; i++) + pmove->velocity[i] += accelspeed*wishdir[i]; + + // move + VectorMA (pmove->origin, pmove->frametime, pmove->velocity, pmove->origin); + } + else + { + // all other modes just track some kind of target, so spectator PVS = target PVS + + int target; + + // no valid target ? + if ( pmove->iuser2 <= 0) + return; + + // Find the client this player's targeting + for (target = 0; target < pmove->numphysent; target++) + { + if ( pmove->physents[target].info == pmove->iuser2 ) + break; + } + + if (target == pmove->numphysent) + return; + + // use targets position as own origin for PVS + VectorCopy( pmove->physents[target].angles, pmove->angles ); + VectorCopy( pmove->physents[target].origin, pmove->origin ); + + // no velocity + VectorCopy( vec3_origin, pmove->velocity ); + } +} + +/* +================== +PM_SplineFraction + +Use for ease-in, ease-out style interpolation (accel/decel) +Used by ducking code. +================== +*/ +float PM_SplineFraction( float value, float scale ) +{ + float valueSquared; + + value = scale * value; + valueSquared = value * value; + + // Nice little ease-in, ease-out spline-like curve + return 3 * valueSquared - 2 * valueSquared * value; +} + +void PM_FixPlayerCrouchStuck( int direction ) +{ + int hitent; + int i; + vec3_t test; + + hitent = pmove->PM_TestPlayerPosition ( pmove->origin, NULL ); + if (hitent == -1 ) + return; + + VectorCopy( pmove->origin, test ); + for ( i = 0; i < 36; i++ ) + { + pmove->origin[2] += direction; + hitent = pmove->PM_TestPlayerPosition ( pmove->origin, NULL ); + if (hitent == -1 ) + return; + } + + VectorCopy( test, pmove->origin ); // Failed +} + +void PM_UnDuck( void ) +{ + int i; + pmtrace_t trace; + vec3_t newOrigin; + + VectorCopy( pmove->origin, newOrigin ); + + if ( pmove->onground != -1 ) + { + for ( i = 0; i < 3; i++ ) + { + newOrigin[i] += ( pmove->player_mins[1][i] - pmove->player_mins[0][i] ); + } + } + + trace = pmove->PM_PlayerTrace( newOrigin, newOrigin, PM_NORMAL, -1 ); + + if ( !trace.startsolid ) + { + pmove->usehull = 0; + + // Oh, no, changing hulls stuck us into something, try unsticking downward first. + trace = pmove->PM_PlayerTrace( newOrigin, newOrigin, PM_NORMAL, -1 ); + if ( trace.startsolid ) + { + // See if we are stuck? If so, stay ducked with the duck hull until we have a clear spot + //Con_Printf( "unstick got stuck\n" ); + pmove->usehull = 1; + return; + } + + pmove->flags &= ~FL_DUCKING; + pmove->bInDuck = false; + pmove->view_ofs[2] = VEC_VIEW; + pmove->flDuckTime = 0; + + VectorCopy( newOrigin, pmove->origin ); + + // Recatagorize position since ducking can change origin + PM_CatagorizePosition(); + } +} + +void PM_Duck( void ) +{ + int i; + float time; + float duckFraction; + + int buttonsChanged = ( pmove->oldbuttons ^ pmove->cmd.buttons ); // These buttons have changed this frame + int nButtonPressed = buttonsChanged & pmove->cmd.buttons; // The changed ones still down are "pressed" + + int duckchange = buttonsChanged & IN_DUCK ? 1 : 0; + int duckpressed = nButtonPressed & IN_DUCK ? 1 : 0; + + if ( pmove->cmd.buttons & IN_DUCK ) + { + pmove->oldbuttons |= IN_DUCK; + } + else + { + pmove->oldbuttons &= ~IN_DUCK; + } + + // Prevent ducking if the iuser3 variable is set + if ( pmove->iuser3 || pmove->dead ) + { + // Try to unduck + if ( pmove->flags & FL_DUCKING ) + { + PM_UnDuck(); + } + return; + } + + if ( pmove->flags & FL_DUCKING ) + { + pmove->cmd.forwardmove *= PLAYER_DUCKING_MULTIPLIER; + pmove->cmd.sidemove *= PLAYER_DUCKING_MULTIPLIER; + pmove->cmd.upmove *= PLAYER_DUCKING_MULTIPLIER; + } + + if ( ( pmove->cmd.buttons & IN_DUCK ) || ( pmove->bInDuck ) || ( pmove->flags & FL_DUCKING ) ) + { + if ( pmove->cmd.buttons & IN_DUCK ) + { + if ( (nButtonPressed & IN_DUCK ) && !( pmove->flags & FL_DUCKING ) ) + { + // Use 1 second so super long jump will work + pmove->flDuckTime = 1000; + pmove->bInDuck = true; + } + + time = max( 0.0, ( 1.0 - (float)pmove->flDuckTime / 1000.0 ) ); + + if ( pmove->bInDuck ) + { + // Finish ducking immediately if duck time is over or not on ground + if ( ( (float)pmove->flDuckTime / 1000.0 <= ( 1.0 - TIME_TO_DUCK ) ) || + ( pmove->onground == -1 ) ) + { + pmove->usehull = 1; + pmove->view_ofs[2] = VEC_DUCK_VIEW; + pmove->flags |= FL_DUCKING; + pmove->bInDuck = false; + + // HACKHACK - Fudge for collision bug - no time to fix this properly + if ( pmove->onground != -1 ) + { + for ( i = 0; i < 3; i++ ) + { + pmove->origin[i] -= ( pmove->player_mins[1][i] - pmove->player_mins[0][i] ); + } + // See if we are stuck? + PM_FixPlayerCrouchStuck( STUCK_MOVEUP ); + + // Recatagorize position since ducking can change origin + PM_CatagorizePosition(); + } + } + else + { + float fMore = (VEC_DUCK_HULL_MIN - VEC_HULL_MIN); + + // Calc parametric time + duckFraction = PM_SplineFraction( time, (1.0/TIME_TO_DUCK) ); + pmove->view_ofs[2] = ((VEC_DUCK_VIEW - fMore ) * duckFraction) + (VEC_VIEW * (1-duckFraction)); + } + } + } + else + { + // Try to unduck + PM_UnDuck(); + } + } +} + +void PM_LadderMove( physent_t *pLadder ) +{ + vec3_t ladderCenter; + trace_t trace; + qboolean onFloor; + vec3_t floor; + vec3_t modelmins, modelmaxs; + + if ( pmove->movetype == MOVETYPE_NOCLIP ) + return; + +#if defined( _TFC ) + // this is how TFC freezes players, so we don't want them climbing ladders + if ( pmove->maxspeed <= 1.0 ) + return; +#endif + + pmove->PM_GetModelBounds( pLadder->model, modelmins, modelmaxs ); + + VectorAdd( modelmins, modelmaxs, ladderCenter ); + VectorScale( ladderCenter, 0.5, ladderCenter ); + + pmove->movetype = MOVETYPE_FLY; + + // On ladder, convert movement to be relative to the ladder + + VectorCopy( pmove->origin, floor ); + floor[2] += pmove->player_mins[pmove->usehull][2] - 1; + + if ( pmove->PM_PointContents( floor, NULL ) == CONTENTS_SOLID ) + onFloor = true; + else + onFloor = false; + + pmove->gravity = 0; + pmove->PM_TraceModel( pLadder, pmove->origin, ladderCenter, &trace ); + if ( trace.fraction != 1.0 ) + { + float forward = 0, right = 0; + vec3_t vpn, v_right; + float flSpeed = MAX_CLIMB_SPEED; + + // they shouldn't be able to move faster than their maxspeed + if ( flSpeed > pmove->maxspeed ) + { + flSpeed = pmove->maxspeed; + } + + AngleVectors( pmove->angles, vpn, v_right, NULL ); + + if ( pmove->flags & FL_DUCKING ) + { + flSpeed *= PLAYER_DUCKING_MULTIPLIER; + } + + if ( pmove->cmd.buttons & IN_BACK ) + { + forward -= flSpeed; + } + if ( pmove->cmd.buttons & IN_FORWARD ) + { + forward += flSpeed; + } + if ( pmove->cmd.buttons & IN_MOVELEFT ) + { + right -= flSpeed; + } + if ( pmove->cmd.buttons & IN_MOVERIGHT ) + { + right += flSpeed; + } + + if ( pmove->cmd.buttons & IN_JUMP ) + { + pmove->movetype = MOVETYPE_WALK; + VectorScale( trace.plane.normal, 270, pmove->velocity ); + } + else + { + if ( forward != 0 || right != 0 ) + { + vec3_t velocity, perp, cross, lateral, tmp; + float normal; + + //ALERT(at_console, "pev %.2f %.2f %.2f - ", + // pev->velocity.x, pev->velocity.y, pev->velocity.z); + // Calculate player's intended velocity + //Vector velocity = (forward * gpGlobals->v_forward) + (right * gpGlobals->v_right); + VectorScale( vpn, forward, velocity ); + VectorMA( velocity, right, v_right, velocity ); + + + // Perpendicular in the ladder plane + // Vector perp = CrossProduct( Vector(0,0,1), trace.vecPlaneNormal ); + // perp = perp.Normalize(); + VectorClear( tmp ); + tmp[2] = 1; + CrossProduct( tmp, trace.plane.normal, perp ); + VectorNormalize( perp ); + + + // decompose velocity into ladder plane + normal = DotProduct( velocity, trace.plane.normal ); + // This is the velocity into the face of the ladder + VectorScale( trace.plane.normal, normal, cross ); + + + // This is the player's additional velocity + VectorSubtract( velocity, cross, lateral ); + + // This turns the velocity into the face of the ladder into velocity that + // is roughly vertically perpendicular to the face of the ladder. + // NOTE: It IS possible to face up and move down or face down and move up + // because the velocity is a sum of the directional velocity and the converted + // velocity through the face of the ladder -- by design. + CrossProduct( trace.plane.normal, perp, tmp ); + VectorMA( lateral, -normal, tmp, pmove->velocity ); + if ( onFloor && normal > 0 ) // On ground moving away from the ladder + { + VectorMA( pmove->velocity, MAX_CLIMB_SPEED, trace.plane.normal, pmove->velocity ); + } + //pev->velocity = lateral - (CrossProduct( trace.vecPlaneNormal, perp ) * normal); + } + else + { + VectorClear( pmove->velocity ); + } + } + } +} + +physent_t *PM_Ladder( void ) +{ + int i; + physent_t *pe; + hull_t *hull; + int num; + vec3_t test; + + for ( i = 0; i < pmove->nummoveent; i++ ) + { + pe = &pmove->moveents[i]; + + if ( pe->model && (modtype_t)pmove->PM_GetModelType( pe->model ) == mod_brush && pe->skin == CONTENTS_LADDER ) + { + + hull = (hull_t *)pmove->PM_HullForBsp( pe, test ); + num = hull->firstclipnode; + + // Offset the test point appropriately for this hull. + VectorSubtract ( pmove->origin, test, test); + + // Test the player's hull for intersection with this model + if ( pmove->PM_HullPointContents (hull, num, test) == CONTENTS_EMPTY) + continue; + + return pe; + } + } + + return NULL; +} + + + +void PM_WaterJump (void) +{ + if ( pmove->waterjumptime > 10000 ) + { + pmove->waterjumptime = 10000; + } + + if ( !pmove->waterjumptime ) + return; + + pmove->waterjumptime -= pmove->cmd.msec; + if ( pmove->waterjumptime < 0 || + !pmove->waterlevel ) + { + pmove->waterjumptime = 0; + pmove->flags &= ~FL_WATERJUMP; + } + + pmove->velocity[0] = pmove->movedir[0]; + pmove->velocity[1] = pmove->movedir[1]; +} + +/* +============ +PM_AddGravity + +============ +*/ +void PM_AddGravity () +{ + float ent_gravity; + + if (pmove->gravity) + ent_gravity = pmove->gravity; + else + ent_gravity = 1.0; + + // Add gravity incorrectly + pmove->velocity[2] -= (ent_gravity * pmove->movevars->gravity * pmove->frametime ); + pmove->velocity[2] += pmove->basevelocity[2] * pmove->frametime; + pmove->basevelocity[2] = 0; + PM_CheckVelocity(); +} +/* +============ +PM_PushEntity + +Does not change the entities velocity at all +============ +*/ +pmtrace_t PM_PushEntity (vec3_t push) +{ + pmtrace_t trace; + vec3_t end; + + VectorAdd (pmove->origin, push, end); + + trace = pmove->PM_PlayerTrace (pmove->origin, end, PM_NORMAL, -1 ); + + VectorCopy (trace.endpos, pmove->origin); + + // So we can run impact function afterwards. + if (trace.fraction < 1.0 && + !trace.allsolid) + { + PM_AddToTouched(trace, pmove->velocity); + } + + return trace; +} + +/* +============ +PM_Physics_Toss() + +Dead player flying through air., e.g. +============ +*/ +void PM_Physics_Toss() +{ + pmtrace_t trace; + vec3_t move; + float backoff; + + PM_CheckWater(); + + if (pmove->velocity[2] > 0) + pmove->onground = -1; + + // If on ground and not moving, return. + if ( pmove->onground != -1 ) + { + if (VectorCompare(pmove->basevelocity, vec3_origin) && + VectorCompare(pmove->velocity, vec3_origin)) + return; + } + + PM_CheckVelocity (); + +// add gravity + if ( pmove->movetype != MOVETYPE_FLY && + pmove->movetype != MOVETYPE_BOUNCEMISSILE && + pmove->movetype != MOVETYPE_FLYMISSILE ) + PM_AddGravity (); + +// move origin + // Base velocity is not properly accounted for since this entity will move again after the bounce without + // taking it into account + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity); + + PM_CheckVelocity(); + VectorScale (pmove->velocity, pmove->frametime, move); + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity); + + trace = PM_PushEntity (move); // Should this clear basevelocity + + PM_CheckVelocity(); + + if (trace.allsolid) + { + // entity is trapped in another solid + pmove->onground = trace.ent; + VectorCopy (vec3_origin, pmove->velocity); + return; + } + + if (trace.fraction == 1) + { + PM_CheckWater(); + return; + } + + + if (pmove->movetype == MOVETYPE_BOUNCE) + backoff = 2.0 - pmove->friction; + else if (pmove->movetype == MOVETYPE_BOUNCEMISSILE) + backoff = 2.0; + else + backoff = 1; + + PM_ClipVelocity (pmove->velocity, trace.plane.normal, pmove->velocity, backoff); + + // stop if on ground + if (trace.plane.normal[2] > 0.7) + { + float vel; + vec3_t base; + + VectorClear( base ); + if (pmove->velocity[2] < pmove->movevars->gravity * pmove->frametime) + { + // we're rolling on the ground, add static friction. + pmove->onground = trace.ent; + pmove->velocity[2] = 0; + } + + vel = DotProduct( pmove->velocity, pmove->velocity ); + + // Con_DPrintf("%f %f: %.0f %.0f %.0f\n", vel, trace.fraction, ent->velocity[0], ent->velocity[1], ent->velocity[2] ); + + if (vel < (30 * 30) || (pmove->movetype != MOVETYPE_BOUNCE && pmove->movetype != MOVETYPE_BOUNCEMISSILE)) + { + pmove->onground = trace.ent; + VectorCopy (vec3_origin, pmove->velocity); + } + else + { + VectorScale (pmove->velocity, (1.0 - trace.fraction) * pmove->frametime * 0.9, move); + trace = PM_PushEntity (move); + } + VectorSubtract( pmove->velocity, base, pmove->velocity ) + } + +// check for in water + PM_CheckWater(); +} + +/* +==================== +PM_NoClip + +==================== +*/ +void PM_NoClip() +{ + int i; + vec3_t wishvel; + float fmove, smove; +// float currentspeed, addspeed, accelspeed; + + // Copy movement amounts + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + VectorNormalize ( pmove->forward ); + VectorNormalize ( pmove->right ); + + for (i=0 ; i<3 ; i++) // Determine x and y parts of velocity + { + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + } + wishvel[2] += pmove->cmd.upmove; + + VectorMA (pmove->origin, pmove->frametime, wishvel, pmove->origin); + + // Zero out the velocity so that we don't accumulate a huge downward velocity from + // gravity, etc. + VectorClear( pmove->velocity ); + +} + +// Only allow bunny jumping up to 1.7x server / player maxspeed setting +#define BUNNYJUMP_MAX_SPEED_FACTOR 1.7f + +//----------------------------------------------------------------------------- +// Purpose: Corrects bunny jumping ( where player initiates a bunny jump before other +// movement logic runs, thus making onground == -1 thus making PM_Friction get skipped and +// running PM_AirMove, which doesn't crop velocity to maxspeed like the ground / other +// movement logic does. +//----------------------------------------------------------------------------- +void PM_PreventMegaBunnyJumping( void ) +{ + // Current player speed + float spd; + // If we have to crop, apply this cropping fraction to velocity + float fraction; + // Speed at which bunny jumping is limited + float maxscaledspeed; + + maxscaledspeed = BUNNYJUMP_MAX_SPEED_FACTOR * pmove->maxspeed; + + // Don't divide by zero + if ( maxscaledspeed <= 0.0f ) + return; + + spd = Length( pmove->velocity ); + + if ( spd <= maxscaledspeed ) + return; + + fraction = ( maxscaledspeed / spd ) * 0.65; //Returns the modifier for the velocity + + VectorScale( pmove->velocity, fraction, pmove->velocity ); //Crop it down!. +} + +/* +============= +PM_Jump +============= +*/ +void PM_Jump (void) +{ + int i; + qboolean tfc = false; + + qboolean cansuperjump = false; + + if (pmove->dead) + { + pmove->oldbuttons |= IN_JUMP ; // don't jump again until released + return; + } + + tfc = atoi( pmove->PM_Info_ValueForKey( pmove->physinfo, "tfc" ) ) == 1 ? true : false; + + // Spy that's feigning death cannot jump + if ( tfc && + ( pmove->deadflag == ( DEAD_DISCARDBODY + 1 ) ) ) + { + return; + } + + // See if we are waterjumping. If so, decrement count and return. + if ( pmove->waterjumptime ) + { + pmove->waterjumptime -= pmove->cmd.msec; + if (pmove->waterjumptime < 0) + { + pmove->waterjumptime = 0; + } + return; + } + + // If we are in the water most of the way... + if (pmove->waterlevel >= 2) + { // swimming, not jumping + pmove->onground = -1; + + if (pmove->watertype == CONTENTS_WATER) // We move up a certain amount + pmove->velocity[2] = 100; + else if (pmove->watertype == CONTENTS_SLIME) + pmove->velocity[2] = 80; + else // LAVA + pmove->velocity[2] = 50; + + // play swiming sound + if ( pmove->flSwimTime <= 0 ) + { + // Don't play sound again for 1 second + pmove->flSwimTime = 1000; + switch ( pmove->RandomLong( 0, 3 ) ) + { + case 0: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 1: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 2: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 3: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + } + } + + return; + } + + // No more effect + if ( pmove->onground == -1 ) + { + // Flag that we jumped. + // HACK HACK HACK + // Remove this when the game .dll no longer does physics code!!!! + pmove->oldbuttons |= IN_JUMP; // don't jump again until released + return; // in air, so no effect + } + + if ( pmove->oldbuttons & IN_JUMP ) + return; // don't pogo stick + + // In the air now. + pmove->onground = -1; + + PM_PreventMegaBunnyJumping(); + + if ( tfc ) + { + pmove->PM_PlaySound( CHAN_BODY, "player/plyrjmp8.wav", 0.5, ATTN_NORM, 0, PITCH_NORM ); + } + else + { + PM_PlayStepSound( PM_MapTextureTypeStepType( pmove->chtexturetype ), 1.0 ); + } + + // See if user can super long jump? + cansuperjump = atoi( pmove->PM_Info_ValueForKey( pmove->physinfo, "slj" ) ) == 1 ? true : false; + + // Acclerate upward + // If we are ducking... + if ( ( pmove->bInDuck ) || ( pmove->flags & FL_DUCKING ) ) + { + // Adjust for super long jump module + // UNDONE -- note this should be based on forward angles, not current velocity. + if ( cansuperjump && + ( pmove->cmd.buttons & IN_DUCK ) && + ( pmove->flDuckTime > 0 ) && + Length( pmove->velocity ) > 50 ) + { + pmove->punchangle[0] = -5; + + for (i =0; i < 2; i++) + { + pmove->velocity[i] = pmove->forward[i] * PLAYER_LONGJUMP_SPEED * 1.6; + } + + pmove->velocity[2] = sqrt(2 * 800 * 56.0); + } + else + { + pmove->velocity[2] = sqrt(2 * 800 * 45.0); + } + } + else + { + pmove->velocity[2] = sqrt(2 * 800 * 45.0); + } + + // Decay it for simulation + PM_FixupGravityVelocity(); + + // Flag that we jumped. + pmove->oldbuttons |= IN_JUMP; // don't jump again until released +} + +/* +============= +PM_CheckWaterJump +============= +*/ +#define WJ_HEIGHT 8 +void PM_CheckWaterJump (void) +{ + vec3_t vecStart, vecEnd; + vec3_t flatforward; + vec3_t flatvelocity; + float curspeed; + pmtrace_t tr; + int savehull; + + // Already water jumping. + if ( pmove->waterjumptime ) + return; + + // Don't hop out if we just jumped in + if ( pmove->velocity[2] < -180 ) + return; // only hop out if we are moving up + + // See if we are backing up + flatvelocity[0] = pmove->velocity[0]; + flatvelocity[1] = pmove->velocity[1]; + flatvelocity[2] = 0; + + // Must be moving + curspeed = VectorNormalize( flatvelocity ); + + // see if near an edge + flatforward[0] = pmove->forward[0]; + flatforward[1] = pmove->forward[1]; + flatforward[2] = 0; + VectorNormalize (flatforward); + + // Are we backing into water from steps or something? If so, don't pop forward + if ( curspeed != 0.0 && ( DotProduct( flatvelocity, flatforward ) < 0.0 ) ) + return; + + VectorCopy( pmove->origin, vecStart ); + vecStart[2] += WJ_HEIGHT; + + VectorMA ( vecStart, 24, flatforward, vecEnd ); + + // Trace, this trace should use the point sized collision hull + savehull = pmove->usehull; + pmove->usehull = 2; + tr = pmove->PM_PlayerTrace( vecStart, vecEnd, PM_NORMAL, -1 ); + if ( tr.fraction < 1.0 && fabs( tr.plane.normal[2] ) < 0.1f ) // Facing a near vertical wall? + { + vecStart[2] += pmove->player_maxs[ savehull ][2] - WJ_HEIGHT; + VectorMA( vecStart, 24, flatforward, vecEnd ); + VectorMA( vec3_origin, -50, tr.plane.normal, pmove->movedir ); + + tr = pmove->PM_PlayerTrace( vecStart, vecEnd, PM_NORMAL, -1 ); + if ( tr.fraction == 1.0 ) + { + pmove->waterjumptime = 2000; + pmove->velocity[2] = 225; + pmove->oldbuttons |= IN_JUMP; + pmove->flags |= FL_WATERJUMP; + } + } + + // Reset the collision hull + pmove->usehull = savehull; +} + +void PM_CheckFalling( void ) +{ + if ( pmove->onground != -1 && + !pmove->dead && + pmove->flFallVelocity >= PLAYER_FALL_PUNCH_THRESHHOLD ) + { + float fvol = 0.5; + + if ( pmove->waterlevel > 0 ) + { + } + else if ( pmove->flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED ) + { + // NOTE: In the original game dll , there were no breaks after these cases, causing the first one to + // cascade into the second + //switch ( RandomLong(0,1) ) + //{ + //case 0: + //pmove->PM_PlaySound( CHAN_VOICE, "player/pl_fallpain2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + //break; + //case 1: + pmove->PM_PlaySound( CHAN_VOICE, "player/pl_fallpain3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + // break; + //} + fvol = 1.0; + } + else if ( pmove->flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED / 2 ) + { + qboolean tfc = false; + tfc = atoi( pmove->PM_Info_ValueForKey( pmove->physinfo, "tfc" ) ) == 1 ? true : false; + + if ( tfc ) + { + pmove->PM_PlaySound( CHAN_VOICE, "player/pl_fallpain3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + } + + fvol = 0.85; + } + else if ( pmove->flFallVelocity < PLAYER_MIN_BOUNCE_SPEED ) + { + fvol = 0; + } + + if ( fvol > 0.0 ) + { + // Play landing step right away + pmove->flTimeStepSound = 0; + + PM_UpdateStepSound(); + + // play step sound for current texture + PM_PlayStepSound( PM_MapTextureTypeStepType( pmove->chtexturetype ), fvol ); + + // Knock the screen around a little bit, temporary effect + pmove->punchangle[ 2 ] = pmove->flFallVelocity * 0.013; // punch z axis + + if ( pmove->punchangle[ 0 ] > 8 ) + { + pmove->punchangle[ 0 ] = 8; + } + } + } + + if ( pmove->onground != -1 ) + { + pmove->flFallVelocity = 0; + } +} + +/* +================= +PM_PlayWaterSounds + +================= +*/ +void PM_PlayWaterSounds( void ) +{ + // Did we enter or leave water? + if ( ( pmove->oldwaterlevel == 0 && pmove->waterlevel != 0 ) || + ( pmove->oldwaterlevel != 0 && pmove->waterlevel == 0 ) ) + { + switch ( pmove->RandomLong(0,3) ) + { + case 0: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 1: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 2: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 3: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + } + } +} + +/* +=============== +PM_CalcRoll + +=============== +*/ +float PM_CalcRoll (vec3_t angles, vec3_t velocity, float rollangle, float rollspeed ) +{ + float sign; + float side; + float value; + vec3_t forward, right, up; + + AngleVectors (angles, forward, right, up); + + side = DotProduct (velocity, right); + + sign = side < 0 ? -1 : 1; + + side = fabs(side); + + value = rollangle; + + if (side < rollspeed) + { + side = side * value / rollspeed; + } + else + { + side = value; + } + + return side * sign; +} + +/* +============= +PM_DropPunchAngle + +============= +*/ +void PM_DropPunchAngle ( vec3_t punchangle ) +{ + float len; + + len = VectorNormalize ( punchangle ); + len -= (10.0 + len * 0.5) * pmove->frametime; + len = max( len, 0.0 ); + VectorScale ( punchangle, len, punchangle); +} + +/* +============== +PM_CheckParamters + +============== +*/ +void PM_CheckParamters( void ) +{ + float spd; + float maxspeed; + vec3_t v_angle; + + spd = ( pmove->cmd.forwardmove * pmove->cmd.forwardmove ) + + ( pmove->cmd.sidemove * pmove->cmd.sidemove ) + + ( pmove->cmd.upmove * pmove->cmd.upmove ); + spd = sqrt( spd ); + + maxspeed = pmove->clientmaxspeed; //atof( pmove->PM_Info_ValueForKey( pmove->physinfo, "maxspd" ) ); + if ( maxspeed != 0.0 ) + { + pmove->maxspeed = min( maxspeed, pmove->maxspeed ); + } + + if ( ( spd != 0.0 ) && + ( spd > pmove->maxspeed ) ) + { + float fRatio = pmove->maxspeed / spd; + pmove->cmd.forwardmove *= fRatio; + pmove->cmd.sidemove *= fRatio; + pmove->cmd.upmove *= fRatio; + } + + if ( pmove->flags & FL_FROZEN || + pmove->flags & FL_ONTRAIN || + pmove->dead ) + { + pmove->cmd.forwardmove = 0; + pmove->cmd.sidemove = 0; + pmove->cmd.upmove = 0; + } + + + PM_DropPunchAngle( pmove->punchangle ); + + // Take angles from command. + if ( !pmove->dead ) + { + VectorCopy ( pmove->cmd.viewangles, v_angle ); + VectorAdd( v_angle, pmove->punchangle, v_angle ); + + // Set up view angles. + pmove->angles[ROLL] = PM_CalcRoll ( v_angle, pmove->velocity, pmove->movevars->rollangle, pmove->movevars->rollspeed )*4; + pmove->angles[PITCH] = v_angle[PITCH]; + pmove->angles[YAW] = v_angle[YAW]; + } + else + { + VectorCopy( pmove->oldangles, pmove->angles ); + } + + // Set dead player view_offset + if ( pmove->dead ) + { + pmove->view_ofs[2] = PM_DEAD_VIEWHEIGHT; + } + + // Adjust client view angles to match values used on server. + if (pmove->angles[YAW] > 180.0f) + { + pmove->angles[YAW] -= 360.0f; + } + +} + +void PM_ReduceTimers( void ) +{ + if ( pmove->flTimeStepSound > 0 ) + { + pmove->flTimeStepSound -= pmove->cmd.msec; + if ( pmove->flTimeStepSound < 0 ) + { + pmove->flTimeStepSound = 0; + } + } + if ( pmove->flDuckTime > 0 ) + { + pmove->flDuckTime -= pmove->cmd.msec; + if ( pmove->flDuckTime < 0 ) + { + pmove->flDuckTime = 0; + } + } + if ( pmove->flSwimTime > 0 ) + { + pmove->flSwimTime -= pmove->cmd.msec; + if ( pmove->flSwimTime < 0 ) + { + pmove->flSwimTime = 0; + } + } +} + +/* +============= +PlayerMove + +Returns with origin, angles, and velocity modified in place. + +Numtouch and touchindex[] will be set if any of the physents +were contacted during the move. +============= +*/ +void PM_PlayerMove ( qboolean server ) +{ + physent_t *pLadder = NULL; + + // Are we running server code? + pmove->server = server; + + // Adjust speeds etc. + PM_CheckParamters(); + + // Assume we don't touch anything + pmove->numtouch = 0; + + // # of msec to apply movement + pmove->frametime = pmove->cmd.msec * 0.001; + + PM_ReduceTimers(); + + // Convert view angles to vectors + AngleVectors (pmove->angles, pmove->forward, pmove->right, pmove->up); + + // PM_ShowClipBox(); + + // Special handling for spectator and observers. (iuser1 is set if the player's in observer mode) + if ( pmove->spectator || pmove->iuser1 > 0 ) + { + PM_SpectatorMove(); + PM_CatagorizePosition(); + return; + } + + // Always try and unstick us unless we are in NOCLIP mode + if ( pmove->movetype != MOVETYPE_NOCLIP && pmove->movetype != MOVETYPE_NONE ) + { + if ( PM_CheckStuck() ) + { + return; // Can't move, we're stuck + } + } + + // Now that we are "unstuck", see where we are ( waterlevel and type, pmove->onground ). + PM_CatagorizePosition(); + + // Store off the starting water level + pmove->oldwaterlevel = pmove->waterlevel; + + // If we are not on ground, store off how fast we are moving down + if ( pmove->onground == -1 ) + { + pmove->flFallVelocity = -pmove->velocity[2]; + } + + g_onladder = 0; + // Don't run ladder code if dead or on a train + if ( !pmove->dead && !(pmove->flags & FL_ONTRAIN) ) + { + pLadder = PM_Ladder(); + if ( pLadder ) + { + g_onladder = 1; + } + } + + PM_UpdateStepSound(); + + PM_Duck(); + + // Don't run ladder code if dead or on a train + if ( !pmove->dead && !(pmove->flags & FL_ONTRAIN) ) + { + if ( pLadder ) + { + PM_LadderMove( pLadder ); + } + else if ( pmove->movetype != MOVETYPE_WALK && + pmove->movetype != MOVETYPE_NOCLIP ) + { + // Clear ladder stuff unless player is noclipping + // it will be set immediately again next frame if necessary + pmove->movetype = MOVETYPE_WALK; + } + } + +#if !defined( _TFC ) + // Slow down, I'm pulling it! (a box maybe) but only when I'm standing on ground + if ( ( pmove->onground != -1 ) && ( pmove->cmd.buttons & IN_USE) ) + { + VectorScale( pmove->velocity, 0.3, pmove->velocity ); + } +#endif + + // Handle movement + switch ( pmove->movetype ) + { + default: + pmove->Con_DPrintf("Bogus pmove player movetype %i on (%i) 0=cl 1=sv\n", pmove->movetype, pmove->server); + break; + + case MOVETYPE_NONE: + break; + + case MOVETYPE_NOCLIP: + PM_NoClip(); + break; + + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + PM_Physics_Toss(); + break; + + case MOVETYPE_FLY: + + PM_CheckWater(); + + // Was jump button pressed? + // If so, set velocity to 270 away from ladder. This is currently wrong. + // Also, set MOVE_TYPE to walk, too. + if ( pmove->cmd.buttons & IN_JUMP ) + { + if ( !pLadder ) + { + PM_Jump (); + } + } + else + { + pmove->oldbuttons &= ~IN_JUMP; + } + + // Perform the move accounting for any base velocity. + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity); + PM_FlyMove (); + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity); + break; + + case MOVETYPE_WALK: + if ( !PM_InWater() ) + { + PM_AddCorrectGravity(); + } + + // If we are leaping out of the water, just update the counters. + if ( pmove->waterjumptime ) + { + PM_WaterJump(); + PM_FlyMove(); + + // Make sure waterlevel is set correctly + PM_CheckWater(); + return; + } + + // If we are swimming in the water, see if we are nudging against a place we can jump up out + // of, and, if so, start out jump. Otherwise, if we are not moving up, then reset jump timer to 0 + if ( pmove->waterlevel >= 2 ) + { + if ( pmove->waterlevel == 2 ) + { + PM_CheckWaterJump(); + } + + // If we are falling again, then we must not trying to jump out of water any more. + if ( pmove->velocity[2] < 0 && pmove->waterjumptime ) + { + pmove->waterjumptime = 0; + } + + // Was jump button pressed? + if (pmove->cmd.buttons & IN_JUMP) + { + PM_Jump (); + } + else + { + pmove->oldbuttons &= ~IN_JUMP; + } + + // Perform regular water movement + PM_WaterMove(); + + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity); + + // Get a final position + PM_CatagorizePosition(); + } + else + + // Not underwater + { + // Was jump button pressed? + if ( pmove->cmd.buttons & IN_JUMP ) + { + if ( !pLadder ) + { + PM_Jump (); + } + } + else + { + pmove->oldbuttons &= ~IN_JUMP; + } + + // Fricion is handled before we add in any base velocity. That way, if we are on a conveyor, + // we don't slow when standing still, relative to the conveyor. + if ( pmove->onground != -1 ) + { + pmove->velocity[2] = 0.0; + PM_Friction(); + } + + // Make sure velocity is valid. + PM_CheckVelocity(); + + // Are we on ground now + if ( pmove->onground != -1 ) + { + PM_WalkMove(); + } + else + { + PM_AirMove(); // Take into account movement when in air. + } + + // Set final flags. + PM_CatagorizePosition(); + + // Now pull the base velocity back out. + // Base velocity is set if you are on a moving object, like + // a conveyor (or maybe another monster?) + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity ); + + // Make sure velocity is valid. + PM_CheckVelocity(); + + // Add any remaining gravitational component. + if ( !PM_InWater() ) + { + PM_FixupGravityVelocity(); + } + + // If we are on ground, no downward velocity. + if ( pmove->onground != -1 ) + { + pmove->velocity[2] = 0; + } + + // See if we landed on the ground with enough force to play + // a landing sound. + PM_CheckFalling(); + } + + // Did we enter or leave the water? + PM_PlayWaterSounds(); + break; + } +} + +void PM_CreateStuckTable( void ) +{ + float x, y, z; + int idx; + int i; + float zi[3]; + + memset(rgv3tStuckTable, 0, 54 * sizeof(vec3_t)); + + idx = 0; + // Little Moves. + x = y = 0; + // Z moves + for (z = -0.125 ; z <= 0.125 ; z += 0.125) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + x = z = 0; + // Y moves + for (y = -0.125 ; y <= 0.125 ; y += 0.125) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + y = z = 0; + // X moves + for (x = -0.125 ; x <= 0.125 ; x += 0.125) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + + // Remaining multi axis nudges. + for ( x = - 0.125; x <= 0.125; x += 0.250 ) + { + for ( y = - 0.125; y <= 0.125; y += 0.250 ) + { + for ( z = - 0.125; z <= 0.125; z += 0.250 ) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + } + } + + // Big Moves. + x = y = 0; + zi[0] = 0.0f; + zi[1] = 1.0f; + zi[2] = 6.0f; + + for (i = 0; i < 3; i++) + { + // Z moves + z = zi[i]; + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + + x = z = 0; + + // Y moves + for (y = -2.0f ; y <= 2.0f ; y += 2.0) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + y = z = 0; + // X moves + for (x = -2.0f ; x <= 2.0f ; x += 2.0f) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + + // Remaining multi axis nudges. + for (i = 0 ; i < 3; i++) + { + z = zi[i]; + + for (x = -2.0f ; x <= 2.0f ; x += 2.0f) + { + for (y = -2.0f ; y <= 2.0f ; y += 2.0) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + } + } +} + + + +/* +This modume implements the shared player physics code between any particular game and +the engine. The same PM_Move routine is built into the game .dll and the client .dll and is +invoked by each side as appropriate. There should be no distinction, internally, between server +and client. This will ensure that prediction behaves appropriately. +*/ + +void PM_Move ( struct playermove_s *ppmove, int server ) +{ + assert( pm_shared_initialized ); + + pmove = ppmove; + + PM_PlayerMove( ( server != 0 ) ? true : false ); + + if ( pmove->onground != -1 ) + { + pmove->flags |= FL_ONGROUND; + } + else + { + pmove->flags &= ~FL_ONGROUND; + } + + // In single player, reset friction after each movement to FrictionModifier Triggers work still. + if ( !pmove->multiplayer && ( pmove->movetype == MOVETYPE_WALK ) ) + { + pmove->friction = 1.0f; + } +} + +int PM_GetVisEntInfo( int ent ) +{ + if ( ent >= 0 && ent <= pmove->numvisent ) + { + return pmove->visents[ ent ].info; + } + return -1; +} + +int PM_GetPhysEntInfo( int ent ) +{ + if ( ent >= 0 && ent <= pmove->numphysent) + { + return pmove->physents[ ent ].info; + } + return -1; +} + +void PM_Init( struct playermove_s *ppmove ) +{ + assert( !pm_shared_initialized ); + + pmove = ppmove; + + PM_CreateStuckTable(); + PM_InitTextureTypes(); + + pm_shared_initialized = 1; +} diff --git a/pm_shared/pm_shared.h b/pm_shared/pm_shared.h new file mode 100644 index 0000000..ce8a1c6 --- /dev/null +++ b/pm_shared/pm_shared.h @@ -0,0 +1,36 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +// +// pm_shared.h +// +#if !defined( PM_SHAREDH ) +#define PM_SHAREDH +#pragma once + +void PM_Init( struct playermove_s *ppmove ); +void PM_Move ( struct playermove_s *ppmove, int server ); +char PM_FindTextureType( char *name ); + +// Spectator Movement modes (stored in pev->iuser1, so the physics code can get at them) +#define OBS_NONE 0 +#define OBS_CHASE_LOCKED 1 +#define OBS_CHASE_FREE 2 +#define OBS_ROAMING 3 +#define OBS_IN_EYE 4 +#define OBS_MAP_FREE 5 +#define OBS_MAP_CHASE 6 + +#endif diff --git a/public/FileSystem.h b/public/FileSystem.h new file mode 100644 index 0000000..337d594 --- /dev/null +++ b/public/FileSystem.h @@ -0,0 +1,188 @@ +//========= Copyright � 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef FILESYSTEM_H +#define FILESYSTEM_H +#ifdef _WIN32 +#pragma once +#endif + +#include "archtypes.h" // DAL +#include "interface.h" + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +typedef void * FileHandle_t; +typedef int FileFindHandle_t; +typedef int WaitForResourcesHandle_t; + + +//----------------------------------------------------------------------------- +// Enums used by the interface +//----------------------------------------------------------------------------- +#ifndef FILESYSTEM_INTERNAL_H +typedef enum +{ + FILESYSTEM_SEEK_HEAD = 0, + FILESYSTEM_SEEK_CURRENT, + FILESYSTEM_SEEK_TAIL, +} FileSystemSeek_t; + +enum +{ + FILESYSTEM_INVALID_FIND_HANDLE = -1 +}; + +typedef enum +{ + // Don't print anything + FILESYSTEM_WARNING_QUIET = 0, + + // On shutdown, report names of files left unclosed + FILESYSTEM_WARNING_REPORTUNCLOSED, + + // Report number of times a file was opened, closed + FILESYSTEM_WARNING_REPORTUSAGE, + + // Report all open/close events to console ( !slow! ) + FILESYSTEM_WARNING_REPORTALLACCESSES +} FileWarningLevel_t; + +#define FILESYSTEM_INVALID_HANDLE ( FileHandle_t )0 +#endif + +// turn off any windows defines +#undef GetCurrentDirectory + +//----------------------------------------------------------------------------- +// Purpose: Main file system interface +//----------------------------------------------------------------------------- +class IFileSystem : public IBaseInterface +{ +public: + // Mount and unmount the filesystem + virtual void Mount(void) = 0; + virtual void Unmount(void) = 0; + + // Remove all search paths (including write path?) + virtual void RemoveAllSearchPaths( void ) = 0; + + // Add paths in priority order (mod dir, game dir, ....) + // If one or more .pak files are in the specified directory, then they are + // added after the file system path + // If the path is the relative path to a .bsp file, then any previous .bsp file + // override is cleared and the current .bsp is searched for an embedded PAK file + // and this file becomes the highest priority search path ( i.e., it's looked at first + // even before the mod's file system path ). + virtual void AddSearchPath( const char *pPath, const char *pathID ) = 0; + virtual bool RemoveSearchPath( const char *pPath ) = 0; + + // Deletes a file + virtual void RemoveFile( const char *pRelativePath, const char *pathID ) = 0; + + // this isn't implementable on STEAM as is. + virtual void CreateDirHierarchy( const char *path, const char *pathID ) = 0; + + // File I/O and info + virtual bool FileExists( const char *pFileName ) = 0; + virtual bool IsDirectory( const char *pFileName ) = 0; + + // opens a file + // if pathID is NULL, all paths will be searched for the file + virtual FileHandle_t Open( const char *pFileName, const char *pOptions, const char *pathID = 0L ) = 0; + + virtual void Close( FileHandle_t file ) = 0; + + virtual void Seek( FileHandle_t file, int pos, FileSystemSeek_t seekType ) = 0; + virtual unsigned int Tell( FileHandle_t file ) = 0; + + virtual unsigned int Size( FileHandle_t file ) = 0; + virtual unsigned int Size( const char *pFileName ) = 0; + + virtual long GetFileTime( const char *pFileName ) = 0; + virtual void FileTimeToString( char* pStrip, int maxCharsIncludingTerminator, long fileTime ) = 0; + + virtual bool IsOk( FileHandle_t file ) = 0; + + virtual void Flush( FileHandle_t file ) = 0; + virtual bool EndOfFile( FileHandle_t file ) = 0; + + virtual int Read( void* pOutput, int size, FileHandle_t file ) = 0; + virtual int Write( void const* pInput, int size, FileHandle_t file ) = 0; + virtual char *ReadLine( char *pOutput, int maxChars, FileHandle_t file ) = 0; + virtual int FPrintf( FileHandle_t file, char *pFormat, ... ) = 0; + + // direct filesystem buffer access + // returns a handle to a buffer containing the file data + // this is the optimal way to access the complete data for a file, + // since the file preloader has probably already got it in memory + virtual void *GetReadBuffer( FileHandle_t file, int *outBufferSize, bool failIfNotInCache ) = 0; + virtual void ReleaseReadBuffer( FileHandle_t file, void *readBuffer ) = 0; + + // FindFirst/FindNext + virtual const char *FindFirst( const char *pWildCard, FileFindHandle_t *pHandle, const char *pathID = 0L ) = 0; + virtual const char *FindNext( FileFindHandle_t handle ) = 0; + virtual bool FindIsDirectory( FileFindHandle_t handle ) = 0; + virtual void FindClose( FileFindHandle_t handle ) = 0; + + virtual void GetLocalCopy( const char *pFileName ) = 0; + + virtual const char *GetLocalPath( const char *pFileName, char *pLocalPath, int localPathBufferSize ) = 0; + + // Note: This is sort of a secondary feature; but it's really useful to have it here + virtual char *ParseFile( char* pFileBytes, char* pToken, bool* pWasQuoted ) = 0; + + // Returns true on success ( based on current list of search paths, otherwise false if + // it can't be resolved ) + virtual bool FullPathToRelativePath( const char *pFullpath, char *pRelative ) = 0; + + // Gets the current working directory + virtual bool GetCurrentDirectory( char* pDirectory, int maxlen ) = 0; + + // Dump to printf/OutputDebugString the list of files that have not been closed + virtual void PrintOpenedFiles( void ) = 0; + + virtual void SetWarningFunc( void (*pfnWarning)( const char *fmt, ... ) ) = 0; + virtual void SetWarningLevel( FileWarningLevel_t level ) = 0; + + virtual void LogLevelLoadStarted( const char *name ) = 0; + virtual void LogLevelLoadFinished( const char *name ) = 0; + virtual int HintResourceNeed( const char *hintlist, int forgetEverything ) = 0; + virtual int PauseResourcePreloading( void ) = 0; + virtual int ResumeResourcePreloading( void ) = 0; + virtual int SetVBuf( FileHandle_t stream, char *buffer, int mode, long size ) = 0; + virtual void GetInterfaceVersion( char *p, int maxlen ) = 0; + virtual bool IsFileImmediatelyAvailable(const char *pFileName) = 0; + + // starts waiting for resources to be available + // returns FILESYSTEM_INVALID_HANDLE if there is nothing to wait on + virtual WaitForResourcesHandle_t WaitForResources( const char *resourcelist ) = 0; + // get progress on waiting for resources; progress is a float [0, 1], complete is true on the waiting being done + // returns false if no progress is available + // any calls after complete is true or on an invalid handle will return false, 0.0f, true + virtual bool GetWaitForResourcesProgress( WaitForResourcesHandle_t handle, float *progress /* out */ , bool *complete /* out */ ) = 0; + // cancels a progress call + virtual void CancelWaitForResources( WaitForResourcesHandle_t handle ) = 0; + // returns true if the appID has all its caches fully preloaded + virtual bool IsAppReadyForOfflinePlay( int appID ) = 0; + + // interface for custom pack files > 4Gb + virtual bool AddPackFile( const char *fullpath, const char *pathID ) = 0; + + // open a file but force the data to come from the steam cache, NOT from disk + virtual FileHandle_t OpenFromCacheForRead( const char *pFileName, const char *pOptions, const char *pathID = 0L ) = 0; + + virtual void AddSearchPathNoWrite( const char *pPath, const char *pathID ) = 0; +}; + +// Steam3/Src compat +#define IBaseFileSystem IFileSystem + +#define FILESYSTEM_INTERFACE_VERSION "VFileSystem009" + +#endif // FILESYSTEM_H diff --git a/public/archtypes.h b/public/archtypes.h new file mode 100644 index 0000000..2434a8a --- /dev/null +++ b/public/archtypes.h @@ -0,0 +1,21 @@ +// +// Word size dependent definitions +// DAL 1/03 +// +#ifndef ARCHTYPES_H +#define ARCHTYPES_H + +#include "steam/steamtypes.h" + +#ifndef _WIN32 +#define MAX_PATH PATH_MAX +#include +#include +#include +#include +#define _S_IREAD S_IREAD +#define _S_IWRITE S_IWRITE +typedef long unsigned int ulong; +#endif + +#endif // ARCHTYPES_H diff --git a/public/cl_dll/IGameClientExports.h b/public/cl_dll/IGameClientExports.h new file mode 100644 index 0000000..d45d53a --- /dev/null +++ b/public/cl_dll/IGameClientExports.h @@ -0,0 +1,34 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef IGAMECLIENTEXPORTS_H +#define IGAMECLIENTEXPORTS_H +#ifdef _WIN32 +#pragma once +#endif + +#include "interface.h" + +//----------------------------------------------------------------------------- +// Purpose: Exports a set of functions for the GameUI interface to interact with the game client +//----------------------------------------------------------------------------- +class IGameClientExports : public IBaseInterface +{ +public: + // returns the name of the server the user is connected to, if any + virtual const char *GetServerHostName() = 0; + + // ingame voice manipulation + virtual bool IsPlayerGameVoiceMuted(int playerIndex) = 0; + virtual void MutePlayerGameVoice(int playerIndex) = 0; + virtual void UnmutePlayerGameVoice(int playerIndex) = 0; +}; + +#define GAMECLIENTEXPORTS_INTERFACE_VERSION "GameClientExports001" + + +#endif // IGAMECLIENTEXPORTS_H diff --git a/public/interface.cpp b/public/interface.cpp new file mode 100644 index 0000000..32ca96a --- /dev/null +++ b/public/interface.cpp @@ -0,0 +1,270 @@ +#include +#include +#include +#include "interface.h" + +#if !defined ( _WIN32 ) +// Linux doesn't have this function so this emulates its functionality +// +// +void *GetModuleHandle(const char *name) +{ + void *handle; + + + if( name == NULL ) + { + // hmm, how can this be handled under linux.... + // is it even needed? + return NULL; + } + + if( (handle=dlopen(name, RTLD_NOW))==NULL) + { + //printf("Error:%s\n",dlerror()); + // couldn't open this file + return NULL; + } + + // read "man dlopen" for details + // in short dlopen() inc a ref count + // so dec the ref count by performing the close + dlclose(handle); + return handle; +} +#endif + +// ------------------------------------------------------------------------------------ // +// InterfaceReg. +// ------------------------------------------------------------------------------------ // +InterfaceReg *InterfaceReg::s_pInterfaceRegs = NULL; + + +InterfaceReg::InterfaceReg( InstantiateInterfaceFn fn, const char *pName ) : + m_pName(pName) +{ + m_CreateFn = fn; + m_pNext = s_pInterfaceRegs; + s_pInterfaceRegs = this; +} + + + +// ------------------------------------------------------------------------------------ // +// CreateInterface. +// ------------------------------------------------------------------------------------ // +EXPORT_FUNCTION IBaseInterface *CreateInterface( const char *pName, int *pReturnCode ) +{ + InterfaceReg *pCur; + + for(pCur=InterfaceReg::s_pInterfaceRegs; pCur; pCur=pCur->m_pNext) + { + if(strcmp(pCur->m_pName, pName) == 0) + { + if ( pReturnCode ) + { + *pReturnCode = IFACE_OK; + } + return pCur->m_CreateFn(); + } + } + + if ( pReturnCode ) + { + *pReturnCode = IFACE_FAILED; + } + return NULL; +} + +#ifdef LINUX +static IBaseInterface *CreateInterfaceLocal( const char *pName, int *pReturnCode ) +{ + InterfaceReg *pCur; + + for(pCur=InterfaceReg::s_pInterfaceRegs; pCur; pCur=pCur->m_pNext) + { + if(strcmp(pCur->m_pName, pName) == 0) + { + if ( pReturnCode ) + { + *pReturnCode = IFACE_OK; + } + return pCur->m_CreateFn(); + } + } + + if ( pReturnCode ) + { + *pReturnCode = IFACE_FAILED; + } + return NULL; +} +#endif + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include "windows.h" +#endif + +//----------------------------------------------------------------------------- +// Purpose: returns a pointer to a function, given a module +// Input : pModuleName - module name +// *pName - proc name +//----------------------------------------------------------------------------- +//static hlds_run wants to use this function +static void *Sys_GetProcAddress( const char *pModuleName, const char *pName ) +{ + return GetProcAddress( GetModuleHandle(pModuleName), pName ); +} + +//----------------------------------------------------------------------------- +// Purpose: returns a pointer to a function, given a module +// Input : pModuleName - module name +// *pName - proc name +//----------------------------------------------------------------------------- +// hlds_run wants to use this function +void *Sys_GetProcAddress( void *pModuleHandle, const char *pName ) +{ +#if defined ( _WIN32 ) + return GetProcAddress( (HINSTANCE)pModuleHandle, pName ); +#else + return GetProcAddress( pModuleHandle, pName ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Loads a DLL/component from disk and returns a handle to it +// Input : *pModuleName - filename of the component +// Output : opaque handle to the module (hides system dependency) +//----------------------------------------------------------------------------- +CSysModule *Sys_LoadModule( const char *pModuleName ) +{ +#if defined ( _WIN32 ) + HMODULE hDLL = LoadLibrary( pModuleName ); +#else + HMODULE hDLL = NULL; + char szAbsoluteModuleName[1024]; + szAbsoluteModuleName[0] = 0; + if ( pModuleName[0] != '/' ) + { + char szCwd[1024]; + char szAbsoluteModuleName[1024]; + + getcwd( szCwd, sizeof( szCwd ) ); + if ( szCwd[ strlen( szCwd ) - 1 ] == '/' ) + szCwd[ strlen( szCwd ) - 1 ] = 0; + + _snprintf( szAbsoluteModuleName, sizeof(szAbsoluteModuleName), "%s/%s", szCwd, pModuleName ); + + hDLL = dlopen( szAbsoluteModuleName, RTLD_NOW ); + } + else + { + _snprintf( szAbsoluteModuleName, sizeof(szAbsoluteModuleName), "%s", pModuleName ); + hDLL = dlopen( pModuleName, RTLD_NOW ); + } +#endif + + if( !hDLL ) + { + char str[512]; +#if defined ( _WIN32 ) + _snprintf( str, sizeof(str), "%s.dll", pModuleName ); + hDLL = LoadLibrary( str ); +#elif defined(OSX) + printf("Error:%s\n",dlerror()); + _snprintf( str, sizeof(str), "%s.dylib", szAbsoluteModuleName ); + hDLL = dlopen(str, RTLD_NOW); +#else + printf("Error:%s\n",dlerror()); + _snprintf( str, sizeof(str), "%s.so", szAbsoluteModuleName ); + hDLL = dlopen(str, RTLD_NOW); +#endif + } + + return reinterpret_cast(hDLL); +} + +//----------------------------------------------------------------------------- +// Purpose: Unloads a DLL/component from +// Input : *pModuleName - filename of the component +// Output : opaque handle to the module (hides system dependency) +//----------------------------------------------------------------------------- +void Sys_UnloadModule( CSysModule *pModule ) +{ + if ( !pModule ) + return; + + HMODULE hDLL = reinterpret_cast(pModule); +#if defined ( _WIN32 ) + FreeLibrary( hDLL ); +#else + dlclose((void *)hDLL); +#endif + +} + +//----------------------------------------------------------------------------- +// Purpose: returns a pointer to a function, given a module +// Input : module - windows HMODULE from Sys_LoadModule() +// *pName - proc name +// Output : factory for this module +//----------------------------------------------------------------------------- +CreateInterfaceFn Sys_GetFactory( CSysModule *pModule ) +{ + if ( !pModule ) + return NULL; + + HMODULE hDLL = reinterpret_cast(pModule); +#if defined ( _WIN32 ) + return reinterpret_cast(GetProcAddress( hDLL, CREATEINTERFACE_PROCNAME )); +#else +// Linux gives this error: +//../public/interface.cpp: In function `IBaseInterface *(*Sys_GetFactory +//(CSysModule *)) (const char *, int *)': +//../public/interface.cpp:154: ISO C++ forbids casting between +//pointer-to-function and pointer-to-object +// +// so lets get around it :) + return (CreateInterfaceFn)(GetProcAddress( hDLL, CREATEINTERFACE_PROCNAME )); +#endif +} + + + +//----------------------------------------------------------------------------- +// Purpose: returns the instance of this module +// Output : interface_instance_t +//----------------------------------------------------------------------------- +CreateInterfaceFn Sys_GetFactoryThis( void ) +{ +#ifdef LINUX + return CreateInterfaceLocal; +#else + return CreateInterface; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: returns the instance of the named module +// Input : *pModuleName - name of the module +// Output : interface_instance_t - instance of that module +//----------------------------------------------------------------------------- +CreateInterfaceFn Sys_GetFactory( const char *pModuleName ) +{ +#if defined ( _WIN32 ) + return static_cast( Sys_GetProcAddress( pModuleName, CREATEINTERFACE_PROCNAME ) ); +#else +// Linux gives this error: +//../public/interface.cpp: In function `IBaseInterface *(*Sys_GetFactory +//(const char *)) (const char *, int *)': +//../public/interface.cpp:186: invalid static_cast from type `void *' to +//type `IBaseInterface *(*) (const char *, int *)' +// +// so lets use the old style cast. + return (CreateInterfaceFn)( Sys_GetProcAddress( pModuleName, CREATEINTERFACE_PROCNAME ) ); +#endif +} + + + diff --git a/public/interface.h b/public/interface.h new file mode 100644 index 0000000..aaa9c10 --- /dev/null +++ b/public/interface.h @@ -0,0 +1,149 @@ + +// This header defines the interface convention used in the valve engine. +// To make an interface and expose it: +// 1. Derive from IBaseInterface. +// 2. The interface must be ALL pure virtuals, and have no data members. +// 3. Define a name for it. +// 4. In its implementation file, use EXPOSE_INTERFACE or EXPOSE_SINGLE_INTERFACE. + +// Versioning +// There are two versioning cases that are handled by this: +// 1. You add functions to the end of an interface, so it is binary compatible with the previous interface. In this case, +// you need two EXPOSE_INTERFACEs: one to expose your class as the old interface and one to expose it as the new interface. +// 2. You update an interface so it's not compatible anymore (but you still want to be able to expose the old interface +// for legacy code). In this case, you need to make a new version name for your new interface, and make a wrapper interface and +// expose it for the old interface. + +#if _MSC_VER >= 1300 // VC7 +#include "tier1/interface.h" +#else + +#ifndef INTERFACE_H +#define INTERFACE_H + +#if !defined ( _WIN32 ) + +#include // dlopen,dlclose, et al +#include + +#define HMODULE void * +#define GetProcAddress dlsym + +#define _snprintf snprintf + +#endif + +void *Sys_GetProcAddress( void *pModuleHandle, const char *pName ); + +// All interfaces derive from this. +class IBaseInterface +{ +public: + + virtual ~IBaseInterface() {} +}; + + +#define CREATEINTERFACE_PROCNAME "CreateInterface" +typedef IBaseInterface* (*CreateInterfaceFn)(const char *pName, int *pReturnCode); + + +typedef IBaseInterface* (*InstantiateInterfaceFn)(); + + +// Used internally to register classes. +class InterfaceReg +{ +public: + InterfaceReg(InstantiateInterfaceFn fn, const char *pName); + +public: + + InstantiateInterfaceFn m_CreateFn; + const char *m_pName; + + InterfaceReg *m_pNext; // For the global list. + static InterfaceReg *s_pInterfaceRegs; +}; + + +// Use this to expose an interface that can have multiple instances. +// e.g.: +// EXPOSE_INTERFACE( CInterfaceImp, IInterface, "MyInterface001" ) +// This will expose a class called CInterfaceImp that implements IInterface (a pure class) +// clients can receive a pointer to this class by calling CreateInterface( "MyInterface001" ) +// +// In practice, the shared header file defines the interface (IInterface) and version name ("MyInterface001") +// so that each component can use these names/vtables to communicate +// +// A single class can support multiple interfaces through multiple inheritance +// +// Use this if you want to write the factory function. +#define EXPOSE_INTERFACE_FN(functionName, interfaceName, versionName) \ + static InterfaceReg __g_Create##className##_reg(functionName, versionName); + +#define EXPOSE_INTERFACE(className, interfaceName, versionName) \ + static IBaseInterface* __Create##className##_interface() {return (interfaceName *)new className;}\ + static InterfaceReg __g_Create##className##_reg(__Create##className##_interface, versionName ); + +// Use this to expose a singleton interface with a global variable you've created. +#define EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, globalVarName) \ + static IBaseInterface* __Create##className##interfaceName##_interface() {return (IBaseInterface *)&globalVarName;}\ + static InterfaceReg __g_Create##className##interfaceName##_reg(__Create##className##interfaceName##_interface, versionName); + +// Use this to expose a singleton interface. This creates the global variable for you automatically. +#define EXPOSE_SINGLE_INTERFACE(className, interfaceName, versionName) \ + static className __g_##className##_singleton;\ + EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, __g_##className##_singleton) + + +#ifdef WIN32 + #define EXPORT_FUNCTION __declspec(dllexport) +#else + #define EXPORT_FUNCTION __attribute__ ((visibility("default"))) +#endif + + +// This function is automatically exported and allows you to access any interfaces exposed with the above macros. +// if pReturnCode is set, it will return one of the following values +// extend this for other error conditions/code +enum +{ + IFACE_OK = 0, + IFACE_FAILED +}; + + +extern "C" +{ + EXPORT_FUNCTION IBaseInterface* CreateInterface(const char *pName, int *pReturnCode); +}; + + +extern CreateInterfaceFn Sys_GetFactoryThis( void ); + + +//----------------------------------------------------------------------------- +// UNDONE: This is obsolete, use the module load/unload/get instead!!! +//----------------------------------------------------------------------------- +extern CreateInterfaceFn Sys_GetFactory( const char *pModuleName ); + + +// load/unload components +class CSysModule; + +//----------------------------------------------------------------------------- +// Load & Unload should be called in exactly one place for each module +// The factory for that module should be passed on to dependent components for +// proper versioning. +//----------------------------------------------------------------------------- +extern CSysModule *Sys_LoadModule( const char *pModuleName ); +extern void Sys_UnloadModule( CSysModule *pModule ); + +extern CreateInterfaceFn Sys_GetFactory( CSysModule *pModule ); + + +#endif +#endif // MSVC 6.0 + + diff --git a/public/keydefs.h b/public/keydefs.h new file mode 100644 index 0000000..590367b --- /dev/null +++ b/public/keydefs.h @@ -0,0 +1,124 @@ +// keydefs.h +#ifndef KEYDEFS_H +#define KEYDEFS_H +#ifdef _WIN32 +#pragma once +#endif + +// +// these are the key numbers that should be passed to Key_Event +// +#define K_TAB 9 +#define K_ENTER 13 +#define K_ESCAPE 27 +#define K_SPACE 32 + +// normal keys should be passed as lowercased ascii + +#define K_BACKSPACE 127 +#define K_UPARROW 128 +#define K_DOWNARROW 129 +#define K_LEFTARROW 130 +#define K_RIGHTARROW 131 + +#define K_ALT 132 +#define K_CTRL 133 +#define K_SHIFT 134 +#define K_F1 135 +#define K_F2 136 +#define K_F3 137 +#define K_F4 138 +#define K_F5 139 +#define K_F6 140 +#define K_F7 141 +#define K_F8 142 +#define K_F9 143 +#define K_F10 144 +#define K_F11 145 +#define K_F12 146 +#define K_INS 147 +#define K_DEL 148 +#define K_PGDN 149 +#define K_PGUP 150 +#define K_HOME 151 +#define K_END 152 + +#define K_KP_HOME 160 +#define K_KP_UPARROW 161 +#define K_KP_PGUP 162 +#define K_KP_LEFTARROW 163 +#define K_KP_5 164 +#define K_KP_RIGHTARROW 165 +#define K_KP_END 166 +#define K_KP_DOWNARROW 167 +#define K_KP_PGDN 168 +#define K_KP_ENTER 169 +#define K_KP_INS 170 +#define K_KP_DEL 171 +#define K_KP_SLASH 172 +#define K_KP_MINUS 173 +#define K_KP_PLUS 174 +#define K_CAPSLOCK 175 +#define K_KP_MUL 176 +#define K_WIN 177 + + +// +// joystick buttons +// +#define K_JOY1 203 +#define K_JOY2 204 +#define K_JOY3 205 +#define K_JOY4 206 + +// +// aux keys are for multi-buttoned joysticks to generate so they can use +// the normal binding process +// +#define K_AUX1 207 +#define K_AUX2 208 +#define K_AUX3 209 +#define K_AUX4 210 +#define K_AUX5 211 +#define K_AUX6 212 +#define K_AUX7 213 +#define K_AUX8 214 +#define K_AUX9 215 +#define K_AUX10 216 +#define K_AUX11 217 +#define K_AUX12 218 +#define K_AUX13 219 +#define K_AUX14 220 +#define K_AUX15 221 +#define K_AUX16 222 +#define K_AUX17 223 +#define K_AUX18 224 +#define K_AUX19 225 +#define K_AUX20 226 +#define K_AUX21 227 +#define K_AUX22 228 +#define K_AUX23 229 +#define K_AUX24 230 +#define K_AUX25 231 +#define K_AUX26 232 +#define K_AUX27 233 +#define K_AUX28 234 +#define K_AUX29 235 +#define K_AUX30 236 +#define K_AUX31 237 +#define K_AUX32 238 +#define K_MWHEELDOWN 239 +#define K_MWHEELUP 240 + +#define K_PAUSE 255 + +// +// mouse buttons generate virtual keys +// +#define K_MOUSE1 241 +#define K_MOUSE2 242 +#define K_MOUSE3 243 +#define K_MOUSE4 244 +#define K_MOUSE5 245 + +#endif // KEYDEFS_H \ No newline at end of file diff --git a/public/particleman.h b/public/particleman.h new file mode 100644 index 0000000..0f9b702 --- /dev/null +++ b/public/particleman.h @@ -0,0 +1,101 @@ +#ifndef PARTICLEMAN_H +#define PARTICLEMAN_H + +#include "interface.h" +#include "pman_triangleffect.h" + +#define PARTICLEMAN_INTERFACE "create_particleman" + +#ifdef _WIN32 +#define PARTICLEMAN_DLLNAME "cl_dlls/particleman.dll" +#elif defined(OSX) +#define PARTICLEMAN_DLLNAME "cl_dlls/particleman.dylib" +#elif defined(LINUX) +#define PARTICLEMAN_DLLNAME "cl_dlls/particleman.so" +#else +#error +#endif + +class CBaseParticle; + +class IParticleMan : public IBaseInterface +{ + +protected: + virtual ~IParticleMan() {} + +public: + + virtual void SetUp( cl_enginefunc_t *pEnginefuncs ) = 0; + virtual void Update ( void ) = 0; + virtual void SetVariables ( float flGravity, Vector vViewAngles ) = 0; + virtual void ResetParticles ( void ) = 0; + virtual void ApplyForce ( Vector vOrigin, Vector vDirection, float flRadius, float flStrength, float flDuration ) = 0; + virtual void AddCustomParticleClassSize ( unsigned long lSize ) = 0; + + //Use this if you want to create a new particle without any overloaded functions, Think, Touch, etc. + //Just call this function, set the particle's behavior and let it rip. + virtual CBaseParticle *CreateParticle( Vector org, Vector normal, model_s * sprite, float size, float brightness, const char *classname ) = 0; + + //Use this to take a block from the mempool for custom particles ( used in new ). + virtual char *RequestNewMemBlock( int iSize ) = 0; + + //These ones are used along a custom Create for new particles you want to override their behavior. + //You can call these whenever you want, but they are mainly used by CBaseParticle. + virtual void CoreInitializeSprite ( CCoreTriangleEffect *pParticle, Vector org, Vector normal, model_s *sprite, float size, float brightness ) = 0; //Only use this for TrianglePlanes + virtual void CoreThink( CCoreTriangleEffect *pParticle, float time ) = 0; + virtual void CoreDraw( CCoreTriangleEffect *pParticle ) = 0; + virtual void CoreAnimate( CCoreTriangleEffect *pParticle, float time ) = 0; + virtual void CoreAnimateAndDie( CCoreTriangleEffect *pParticle, float time ) = 0; + virtual void CoreExpand ( CCoreTriangleEffect *pParticle, float time ) = 0; + virtual void CoreContract ( CCoreTriangleEffect *pParticle, float time ) = 0; + virtual void CoreFade ( CCoreTriangleEffect *pParticle, float time ) = 0; + virtual void CoreSpin ( CCoreTriangleEffect *pParticle, float time ) = 0; + virtual void CoreCalculateVelocity( CCoreTriangleEffect *pParticle, float time ) = 0; + virtual void CoreCheckCollision( CCoreTriangleEffect *pParticle, float time ) = 0; + virtual void CoreTouch ( CCoreTriangleEffect *pParticle, Vector pos, Vector normal, int index ) = 0; + virtual void CoreDie ( CCoreTriangleEffect *pParticle ) = 0; + virtual void CoreForce ( CCoreTriangleEffect *pParticle ) = 0; + virtual bool CoreCheckVisibility ( CCoreTriangleEffect *pParticle ) = 0; + virtual void SetRender( int iRender ) = 0; +}; + +extern IParticleMan *g_pParticleMan; + +class CBaseParticle : public CCoreTriangleEffect +{ +public: + virtual void Think( float time ){ g_pParticleMan->CoreThink( this, time ); } + virtual void Draw( void ) { g_pParticleMan->CoreDraw( this ); } + virtual void Animate( float time ) { g_pParticleMan->CoreAnimate( this, time ); } + virtual void AnimateAndDie( float time ) { g_pParticleMan->CoreAnimateAndDie( this, time ); } + virtual void Expand( float time ) { g_pParticleMan->CoreExpand( this, time ); } + virtual void Contract( float time ) { g_pParticleMan->CoreContract( this, time ); } + virtual void Fade( float time ) { g_pParticleMan->CoreFade( this, time ); } + virtual void Spin( float time ) { g_pParticleMan->CoreSpin( this, time ); } + virtual void CalculateVelocity( float time ) { g_pParticleMan->CoreCalculateVelocity( this, time ); } + virtual void CheckCollision( float time ) { g_pParticleMan->CoreCheckCollision( this, time ); } + virtual void Touch(Vector pos, Vector normal, int index) { g_pParticleMan->CoreTouch( this, pos, normal, index ); } + virtual void Die ( void ) { g_pParticleMan->CoreDie( this ); } + virtual void Force ( void ) { g_pParticleMan->CoreForce( this ); } + virtual bool CheckVisibility ( void ) { return g_pParticleMan->CoreCheckVisibility( this ); } + + virtual void InitializeSprite( Vector org, Vector normal, model_s *sprite, float size, float brightness ) + { + g_pParticleMan->CoreInitializeSprite ( this, org, normal, sprite, size, brightness ); + } + + void * operator new( size_t size ) //this asks for a new block of memory from the MiniMem class + { + return( g_pParticleMan->RequestNewMemBlock( size ) ); + } +#ifdef POSIX + void * operator new( size_t size, const std::nothrow_t&) throw() //this asks for a new block of memory from the MiniMem class + { + return( g_pParticleMan->RequestNewMemBlock( size ) ); + } +#endif +}; + + +#endif //PARTICLEMAN_H diff --git a/public/pman_particlemem.h b/public/pman_particlemem.h new file mode 100644 index 0000000..6ccb971 --- /dev/null +++ b/public/pman_particlemem.h @@ -0,0 +1,197 @@ +#ifndef PARTICLEMEM_H__ +#define PARTICLEMEM_H__ + +#ifdef _WIN32 +#pragma once +#endif + +#include + +class CCoreTriangleEffect; + +#define TRIANGLE_FPS 30 + +typedef struct visibleparticles_s +{ + CCoreTriangleEffect *pVisibleParticle; +} visibleparticles_t; + +//--------------------------------------------------------------------------- +// Memory block record. +class MemoryBlock +{ +private: + char *m_pData; + //bool m_bBlockIsInUse; +public: + MemoryBlock(long lBlockSize) + : next(NULL), prev(NULL) + //m_bBlockIsInUse(false) // Initialize block to 'free' state. + { + // Allocate memory here. + m_pData = new char[lBlockSize]; + } + + virtual ~MemoryBlock() + { + // Free memory. + delete[] m_pData; + } + + inline char *Memory(void) { return m_pData; } + + MemoryBlock * next; + MemoryBlock * prev; +}; + +class MemList +{ +public: + MemList() : m_pHead(NULL) {} + + ~MemList() { Reset(); } + + void Push(MemoryBlock * newItem) + { + if(!m_pHead) + { + m_pHead = newItem; + newItem->next = NULL; + newItem->prev = NULL; + return; + } + + MemoryBlock * temp = m_pHead; + m_pHead = newItem; + m_pHead->next = temp; + m_pHead->prev = NULL; + + temp->prev = m_pHead; + } + + + MemoryBlock * Front( void ) + { + return(m_pHead); + } + + MemoryBlock * Pop( void ) + { + if(!m_pHead) + return(NULL); + + MemoryBlock * temp = m_pHead; + + m_pHead = m_pHead->next; + + if(m_pHead) + m_pHead->prev = NULL; + + temp->next = NULL; + temp->prev = NULL; + + return(temp); + } + + void Delete( MemoryBlock * pItem) + { + + if(m_pHead == pItem) + { + MemoryBlock * temp = m_pHead; + + m_pHead = m_pHead->next; + if(m_pHead) + m_pHead->prev = NULL; + + temp->next = NULL; + temp->prev = NULL; + return; + } + + MemoryBlock * prev = pItem->prev; + MemoryBlock * next = pItem->next; + + if(prev) + prev->next = next; + + if(next) + next->prev = prev; + + pItem->next = NULL; + pItem->prev = NULL; + } + + void Reset( void ) + { + while(m_pHead) + Delete(m_pHead); + } + +private: + MemoryBlock * m_pHead; +}; + + +// Some helpful typedefs. +typedef std::vector VectorOfMemoryBlocks; +typedef VectorOfMemoryBlocks::iterator MemoryBlockIterator; + +// Mini memory manager - singleton. +class CMiniMem +{ +private: + // Main memory pool. Array is fine, but vectors are + // easier. :) + static VectorOfMemoryBlocks m_vecMemoryPool; + // Size of memory blocks in pool. + static long m_lMemoryBlockSize; + static long m_lMaxBlocks; + static long m_lMemoryPoolSize; + static CMiniMem *_instance; + + int m_iTotalParticles; + int m_iParticlesDrawn; + +protected: + // private constructor and destructor. + CMiniMem(long lMemoryPoolSize, long lMaxBlockSize); + virtual ~CMiniMem(); + + // ------------ Memory pool manager calls. + // Find a free block and mark it as "in use". Return NULL + // if no free blocks found. + char *AllocateFreeBlock(void); +public: + + // Return a pointer to usable block of memory. + char *newBlock(void); + + // Mark a target memory item as no longer "in use". + void deleteBlock(MemoryBlock *p); + + // Return the remaining capacity of the memory pool as a percent. + long PercentUsed(void); + + void ProcessAll( void ); //Processes all + + void Reset( void ); //clears memory, setting all particles to not used. + + static int ApplyForce( Vector vOrigin, Vector vDirection, float flRadius, float flStrength ); + + static CMiniMem *Instance(void); + static long MaxBlockSize(void); + + bool CheckSize( int iSize ); + + int GetTotalParticles( void ) { return m_iTotalParticles; } + int GetDrawnParticles( void ) { return m_iParticlesDrawn; } + void IncreaseParticlesDrawn( void ){ m_iParticlesDrawn++; } + + void Shutdown( void ); + + visibleparticles_t *m_pVisibleParticles; +}; + + +#endif//PARTICLEMEM_H__ \ No newline at end of file diff --git a/public/pman_triangleffect.h b/public/pman_triangleffect.h new file mode 100644 index 0000000..6af4c2b --- /dev/null +++ b/public/pman_triangleffect.h @@ -0,0 +1,209 @@ +#ifndef TRIANGLEEFFECT_H__ +#define TRIANGLEEFFECT_H__ + +#ifdef _WIN32 +#pragma once +#endif + +#define TRI_COLLIDEWORLD 0x00000020 +#define TRI_COLLIDEALL 0x00001000 // will collide with world and slideboxes +#define TRI_COLLIDEKILL 0x00004000 // tent is removed upon collision with anything +#define TRI_SPIRAL 0x00008000 +#define TRI_ANIMATEDIE 0x00016000 //animate once and then die +#define TRI_WATERTRACE 0x00032000 + + +#define CULL_FRUSTUM_POINT ( 1 << 0 ) +#define CULL_FRUSTUM_SPHERE ( 1 << 1 ) +#define CULL_FRUSTUM_PLANE ( 1 << 2 ) +#define CULL_PVS ( 1 << 3 ) + +#define LIGHT_NONE ( 1 << 4 ) +#define LIGHT_COLOR ( 1 << 5 ) +#define LIGHT_INTENSITY ( 1 << 6 ) + +#define RENDER_FACEPLAYER ( 1 << 7 ) // m_vAngles == Player view angles +#define RENDER_FACEPLAYER_ROTATEZ ( 1 << 8 ) //Just like above but m_vAngles.z is untouched so the sprite can rotate. + + +#include "pman_particlemem.h" + +//pure virtual baseclass +class CCoreTriangleEffect +{ +private: + int m_iRenderFlags; + float m_flNextPVSCheck; + bool m_bInPVS; + + int m_iCollisionFlags; + float m_flPlayerDistance; //Used for sorting the particles, DO NOT TOUCH. + +public: + + void * operator new(size_t size) + { + // Requested size should match size of class. + if ( size != sizeof( CCoreTriangleEffect ) ) +#ifdef _WIN32 + throw "Error in requested size of new particle class instance."; +#else + return NULL; +#endif + + return((CCoreTriangleEffect *) CMiniMem::Instance()->newBlock()); + + }//this asks for a new block of memory from the MiniMen class + + virtual void Think( float time ) = 0; + virtual bool CheckVisibility ( void ) = 0; + virtual void Draw( void ) = 0; + virtual void Animate( float time ) = 0; + virtual void AnimateAndDie( float time ) = 0; + virtual void Expand( float time ) = 0; + virtual void Contract( float time ) = 0; + virtual void Fade( float time ) = 0; + virtual void Spin( float time ) = 0; + virtual void CalculateVelocity( float time ) = 0; + virtual void CheckCollision( float time ) = 0; + virtual void Touch(Vector pos, Vector normal, int index) = 0; + virtual void Die ( void ) = 0; + virtual void InitializeSprite( Vector org, Vector normal, model_s * sprite, float size, float brightness ) = 0; + virtual void Force ( void ) = 0; + + float m_flSize; //scale of object + float m_flScaleSpeed; //speed at which object expands + float m_flContractSpeed; //speed at which object expands + + float m_flStretchX; + float m_flStretchY; + + float m_flBrightness; //transparency of object + float m_flFadeSpeed; //speed at which object fades + + float m_flTimeCreated; //time object was instanced + float m_flDieTime; //time to remove an object + + float m_flGravity; //how effected by gravity is this object + float m_flAfterDampGrav; + float m_flDampingVelocity; + float m_flDampingTime; + + int m_iFramerate; + int m_iNumFrames; + int m_iFrame; + int m_iRendermode; + + Vector m_vOrigin; //object's position + Vector m_vAngles; //normal angles of object + + Vector m_vAVelocity; + + Vector m_vVelocity; + + Vector m_vLowLeft; + Vector m_vLowRight; + Vector m_vTopLeft; + + Vector m_vColor; + float m_flMass; + + model_s * m_pTexture; + + float m_flBounceFactor; + + char m_szClassname[32]; + + bool m_bInWater; + bool m_bAffectedByForce; + + int m_iAfterDampFlags; + + void SetLightFlag ( int iFlag ) + { + m_iRenderFlags &= ~( LIGHT_NONE | LIGHT_INTENSITY | LIGHT_COLOR ); + m_iRenderFlags |= iFlag; + } + + void SetCullFlag( int iFlag ) + { + m_iRenderFlags &= ~( CULL_PVS | CULL_FRUSTUM_POINT | CULL_FRUSTUM_PLANE | CULL_FRUSTUM_SPHERE ); + m_iRenderFlags |= iFlag; + } + + int GetRenderFlags( void ) + { + return m_iRenderFlags; + } + + bool GetParticlePVS ( void ) + { + return m_bInPVS; + } + + void SetParticlePVS ( bool bPVSStat ) + { + m_bInPVS = bPVSStat; + } + + float GetNextPVSCheck( void ) + { + return m_flNextPVSCheck; + } + + void SetNextPVSCheck( float flTime ) + { + m_flNextPVSCheck = flTime; + } + + void SetCollisionFlags ( int iFlag ) + { + m_iCollisionFlags |= iFlag; + } + + void ClearCollisionFlags ( int iFlag ) + { + m_iCollisionFlags &= ~iFlag; + } + + int GetCollisionFlags ( void ) + { + return m_iCollisionFlags; + } + + void SetRenderFlag( int iFlag ) + { + m_iRenderFlags |= iFlag; + } + + float GetPlayerDistance ( void ) { return m_flPlayerDistance; } + void SetPlayerDistance ( float flDistance ) { m_flPlayerDistance = flDistance; } + +protected: + float m_flOriginalSize; + Vector m_vOriginalAngles; + float m_flOriginalBrightness; + Vector m_vPrevOrigin; + + float m_flNextCollisionTime; + +protected: + static bool CheckSize(int size) + { + // This check will help prevent a class frome being defined later, + // that is larger than the max size MemoryPool is expecting, + // from being successfully allocated. + if (size > (unsigned long) CMiniMem::Instance()->MaxBlockSize()) + { +#ifdef _WIN32 + throw "New particle class is larger than memory pool max size, update lMaxParticleClassSize() function."; +#endif + return(false); + } + + return(true); + } +}; + + +#endif//TRIANGLEEFFECT_H__ diff --git a/public/steam/steamtypes.h b/public/steam/steamtypes.h new file mode 100644 index 0000000..1606616 --- /dev/null +++ b/public/steam/steamtypes.h @@ -0,0 +1,177 @@ +//========= Copyright � 1996-2008, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +//============================================================================= + +#ifndef STEAMTYPES_H +#define STEAMTYPES_H +#ifdef _WIN32 +#pragma once +#endif + +// Steam-specific types. Defined here so this header file can be included in other code bases. +#if defined( __GNUC__ ) && !defined(POSIX) + #if __GNUC__ < 4 + #error "Steamworks requires GCC 4.X (4.2 or 4.4 have been tested)" + #endif + #define POSIX 1 +#endif + +#if defined(__x86_64__) || defined(_WIN64) +#define X64BITS +#endif + +// Make sure VALVE_BIG_ENDIAN gets set on PS3, may already be set previously in Valve internal code. +#if !defined(VALVE_BIG_ENDIAN) && defined(_PS3) +#define VALVE_BIG_ENDIAN +#endif + +typedef unsigned char uint8; +typedef signed char int8; + +#if defined( _WIN32 ) + +typedef __int16 int16; +typedef unsigned __int16 uint16; +typedef __int32 int32; +typedef unsigned __int32 uint32; +typedef __int64 int64; +typedef unsigned __int64 uint64; + +#ifdef X64BITS +typedef __int64 intp; // intp is an integer that can accomodate a pointer +typedef unsigned __int64 uintp; // (ie, sizeof(intp) >= sizeof(int) && sizeof(intp) >= sizeof(void *) +#else +typedef __int32 intp; +typedef unsigned __int32 uintp; +#endif + +#else // _WIN32 + +typedef short int16; +typedef unsigned short uint16; +typedef int int32; +typedef unsigned int uint32; +typedef long long int64; +typedef unsigned long long uint64; +#ifdef X64BITS +typedef long long intp; +typedef unsigned long long uintp; +#else +typedef int intp; +typedef unsigned int uintp; +#endif + +#endif // else _WIN32 + +#ifdef __cplusplus +const int k_cubSaltSize = 8; +#else +#define k_cubSaltSize 8 +#endif + +typedef uint8 Salt_t[ k_cubSaltSize ]; + +//----------------------------------------------------------------------------- +// GID (GlobalID) stuff +// This is a globally unique identifier. It's guaranteed to be unique across all +// racks and servers for as long as a given universe persists. +//----------------------------------------------------------------------------- +// NOTE: for GID parsing/rendering and other utils, see gid.h +typedef uint64 GID_t; + +#ifdef __cplusplus +const GID_t k_GIDNil = 0xfffffffffffffffful; +#else +#define k_GIDNil 0xffffffffffffffffull; +#endif + +// For convenience, we define a number of types that are just new names for GIDs +typedef GID_t JobID_t; // Each Job has a unique ID +typedef GID_t TxnID_t; // Each financial transaction has a unique ID + +#ifdef __cplusplus +const GID_t k_TxnIDNil = k_GIDNil; +const GID_t k_TxnIDUnknown = 0; +#else +#define k_TxnIDNil k_GIDNil; +#define k_TxnIDUnknown 0; +#endif + +// this is baked into client messages and interfaces as an int, +// make sure we never break this. +typedef uint32 PackageId_t; +#ifdef __cplusplus +const PackageId_t k_uPackageIdFreeSub = 0x0; +const PackageId_t k_uPackageIdInvalid = 0xFFFFFFFF; +#else +#define k_uPackageIdFreeSub 0x0; +#define k_uPackageIdInvalid 0xFFFFFFFF; +#endif + +// this is baked into client messages and interfaces as an int, +// make sure we never break this. +typedef uint32 AppId_t; +#ifdef __cplusplus +const AppId_t k_uAppIdInvalid = 0x0; +#else +#define k_uAppIdInvalid 0x0; +#endif + +typedef uint64 AssetClassId_t; +#ifdef __cplusplus +const AssetClassId_t k_ulAssetClassIdInvalid = 0x0; +#else +#define k_ulAssetClassIdInvalid 0x0; +#endif + +typedef uint32 PhysicalItemId_t; +#ifdef __cplusplus +const PhysicalItemId_t k_uPhysicalItemIdInvalid = 0x0; +#else +#define k_uPhysicalItemIdInvalid 0x0; +#endif + + +// this is baked into client messages and interfaces as an int, +// make sure we never break this. AppIds and DepotIDs also presently +// share the same namespace, but since we'd like to change that in the future +// I've defined it seperately here. +typedef uint32 DepotId_t; +#ifdef __cplusplus +const DepotId_t k_uDepotIdInvalid = 0x0; +#else +#define k_uDepotIdInvalid 0x0; +#endif + +// RTime32 +// We use this 32 bit time representing real world time. +// It offers 1 second resolution beginning on January 1, 1970 (Unix time) +typedef uint32 RTime32; + +typedef uint32 CellID_t; +#ifdef __cplusplus +const CellID_t k_uCellIDInvalid = 0xFFFFFFFF; +#else +#define k_uCellIDInvalid 0x0; +#endif + +// handle to a Steam API call +typedef uint64 SteamAPICall_t; +#ifdef __cplusplus +const SteamAPICall_t k_uAPICallInvalid = 0x0; +#else +#define k_uAPICallInvalid 0x0; +#endif + +typedef uint32 AccountID_t; + +typedef uint32 PartnerId_t; +#ifdef __cplusplus +const PartnerId_t k_uPartnerIdInvalid = 0; +#else +#define k_uPartnerIdInvalid 0x0; +#endif + +#endif // STEAMTYPES_H diff --git a/ricochet/cl_dll/Exports.h b/ricochet/cl_dll/Exports.h new file mode 100644 index 0000000..3b66c4f --- /dev/null +++ b/ricochet/cl_dll/Exports.h @@ -0,0 +1,224 @@ +// CL_DLLEXPORT is the client version of dllexport. It's turned off for secure clients. +#ifdef _WIN32 +#define CL_DLLEXPORT __declspec(dllexport) +#else +#define CL_DLLEXPORT __attribute__ ((visibility("default"))) +#endif + +extern "C" +{ + // From hl_weapons + void CL_DLLEXPORT HUD_PostRunCmd( struct local_state_s *from, struct local_state_s *to, struct usercmd_s *cmd, int runfuncs, double time, unsigned int random_seed ); + + // From cdll_int + int CL_DLLEXPORT Initialize( cl_enginefunc_t *pEnginefuncs, int iVersion ); + int CL_DLLEXPORT HUD_VidInit( void ); + void CL_DLLEXPORT HUD_Init( void ); + int CL_DLLEXPORT HUD_Redraw( float flTime, int intermission ); + int CL_DLLEXPORT HUD_UpdateClientData( client_data_t *cdata, float flTime ); + void CL_DLLEXPORT HUD_Reset ( void ); + void CL_DLLEXPORT HUD_PlayerMove( struct playermove_s *ppmove, int server ); + void CL_DLLEXPORT HUD_PlayerMoveInit( struct playermove_s *ppmove ); + char CL_DLLEXPORT HUD_PlayerMoveTexture( char *name ); + int CL_DLLEXPORT HUD_ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); + int CL_DLLEXPORT HUD_GetHullBounds( int hullnumber, float *mins, float *maxs ); + void CL_DLLEXPORT HUD_Frame( double time ); + void CL_DLLEXPORT HUD_VoiceStatus(int entindex, qboolean bTalking); + void CL_DLLEXPORT HUD_DirectorMessage( int iSize, void *pbuf ); + void CL_DLLEXPORT HUD_ChatInputPosition( int *x, int *y ); + int CL_DLLEXPORT HUD_GetPlayerTeam(int iplayer); + + // From demo + void CL_DLLEXPORT Demo_ReadBuffer( int size, unsigned char *buffer ); + + // From entity + int CL_DLLEXPORT HUD_AddEntity( int type, struct cl_entity_s *ent, const char *modelname ); + void CL_DLLEXPORT HUD_CreateEntities( void ); + void CL_DLLEXPORT HUD_StudioEvent( const struct mstudioevent_s *event, const struct cl_entity_s *entity ); + void CL_DLLEXPORT HUD_TxferLocalOverrides( struct entity_state_s *state, const struct clientdata_s *client ); + void CL_DLLEXPORT HUD_ProcessPlayerState( struct entity_state_s *dst, const struct entity_state_s *src ); + void CL_DLLEXPORT HUD_TxferPredictionData ( struct entity_state_s *ps, const struct entity_state_s *pps, struct clientdata_s *pcd, const struct clientdata_s *ppcd, struct weapon_data_s *wd, const struct weapon_data_s *pwd ); + void CL_DLLEXPORT HUD_TempEntUpdate( double frametime, double client_time, double cl_gravity, struct tempent_s **ppTempEntFree, struct tempent_s **ppTempEntActive, int ( *Callback_AddVisibleEntity )( struct cl_entity_s *pEntity ), void ( *Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp ) ); + struct cl_entity_s CL_DLLEXPORT *HUD_GetUserEntity( int index ); + + // From in_camera + void CL_DLLEXPORT CAM_Think( void ); + int CL_DLLEXPORT CL_IsThirdPerson( void ); + void CL_DLLEXPORT CL_CameraOffset( float *ofs ); + + // From input + struct kbutton_s CL_DLLEXPORT *KB_Find( const char *name ); + void CL_DLLEXPORT CL_CreateMove ( float frametime, struct usercmd_s *cmd, int active ); + void CL_DLLEXPORT HUD_Shutdown( void ); + int CL_DLLEXPORT HUD_Key_Event( int eventcode, int keynum, const char *pszCurrentBinding ); + + // From inputw32 + void CL_DLLEXPORT IN_ActivateMouse( void ); + void CL_DLLEXPORT IN_DeactivateMouse( void ); + void CL_DLLEXPORT IN_MouseEvent (int mstate); + void CL_DLLEXPORT IN_Accumulate (void); + void CL_DLLEXPORT IN_ClearStates (void); + + // From tri + void CL_DLLEXPORT HUD_DrawNormalTriangles( void ); + void CL_DLLEXPORT HUD_DrawTransparentTriangles( void ); + + // From view + void CL_DLLEXPORT V_CalcRefdef( struct ref_params_s *pparams ); + + // From GameStudioModelRenderer + int CL_DLLEXPORT HUD_GetStudioModelInterface( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio ); +} + +extern modfuncs_t g_modfuncs; +//extern cldll_func_dst_t *g_pcldstAddrs; + +/* +// Macros for the client receiving calls from the engine +#define RecClInitialize(a, b) (g_pcldstAddrs->pInitFunc(&a, &b)) +#define RecClHudInit() (g_pcldstAddrs->pHudInitFunc()) +#define RecClHudVidInit() (g_pcldstAddrs->pHudVidInitFunc()) +#define RecClHudRedraw(a, b) (g_pcldstAddrs->pHudRedrawFunc(&a, &b)) +#define RecClHudUpdateClientData(a, b) (g_pcldstAddrs->pHudUpdateClientDataFunc(&a, &b)) +#define RecClHudReset() (g_pcldstAddrs->pHudResetFunc()) +#define RecClClientMove(a, b) (g_pcldstAddrs->pClientMove(&a, &b)) +#define RecClClientMoveInit(a) (g_pcldstAddrs->pClientMoveInit(&a)) +#define RecClClientTextureType(a) (g_pcldstAddrs->pClientTextureType(&a)) +#define RecClIN_ActivateMouse() (g_pcldstAddrs->pIN_ActivateMouse()) +#define RecClIN_DeactivateMouse() (g_pcldstAddrs->pIN_DeactivateMouse()) +#define RecClIN_MouseEvent(a) (g_pcldstAddrs->pIN_MouseEvent(&a)) +#define RecClIN_ClearStates() (g_pcldstAddrs->pIN_ClearStates()) +#define RecClIN_Accumulate() (g_pcldstAddrs->pIN_Accumulate()) +#define RecClCL_CreateMove(a, b, c) (g_pcldstAddrs->pCL_CreateMove(&a, &b, &c)) +#define RecClCL_IsThirdPerson() (g_pcldstAddrs->pCL_IsThirdPerson()) +#define RecClCL_GetCameraOffsets(a) (g_pcldstAddrs->pCL_GetCameraOffsets(&a)) +#define RecClFindKey(a) (g_pcldstAddrs->pFindKey(&a)) +#define RecClCamThink() (g_pcldstAddrs->pCamThink()) +#define RecClCalcRefdef(a) (g_pcldstAddrs->pCalcRefdef(&a)) +#define RecClAddEntity(a, b, c) (g_pcldstAddrs->pAddEntity(&a, &b, &c)) +#define RecClCreateEntities() (g_pcldstAddrs->pCreateEntities()) +#define RecClDrawNormalTriangles() (g_pcldstAddrs->pDrawNormalTriangles()) +#define RecClDrawTransparentTriangles() (g_pcldstAddrs->pDrawTransparentTriangles()) +#define RecClStudioEvent(a, b) (g_pcldstAddrs->pStudioEvent(&a, &b)) +#define RecClPostRunCmd(a, b, c, d, e, f) (g_pcldstAddrs->pPostRunCmd(&a, &b, &c, &d, &e, &f)) +#define RecClShutdown() (g_pcldstAddrs->pShutdown()) +#define RecClTxferLocalOverrides(a, b) (g_pcldstAddrs->pTxferLocalOverrides(&a, &b)) +#define RecClProcessPlayerState(a, b) (g_pcldstAddrs->pProcessPlayerState(&a, &b)) +#define RecClTxferPredictionData(a, b, c, d, e, f) (g_pcldstAddrs->pTxferPredictionData(&a, &b, &c, &d, &e, &f)) +#define RecClReadDemoBuffer(a, b) (g_pcldstAddrs->pReadDemoBuffer(&a, &b)) +#define RecClConnectionlessPacket(a, b, c, d) (g_pcldstAddrs->pConnectionlessPacket(&a, &b, &c, &d)) +#define RecClGetHullBounds(a, b, c) (g_pcldstAddrs->pGetHullBounds(&a, &b, &c)) +#define RecClHudFrame(a) (g_pcldstAddrs->pHudFrame(&a)) +#define RecClKeyEvent(a, b, c) (g_pcldstAddrs->pKeyEvent(&a, &b, &c)) +#define RecClTempEntUpdate(a, b, c, d, e, f, g) (g_pcldstAddrs->pTempEntUpdate(&a, &b, &c, &d, &e, &f, &g)) +#define RecClGetUserEntity(a) (g_pcldstAddrs->pGetUserEntity(&a)) +#define RecClVoiceStatus(a, b) (g_pcldstAddrs->pVoiceStatus(&a, &b)) +#define RecClDirectorMessage(a, b) (g_pcldstAddrs->pDirectorMessage(&a, &b)) +#define RecClStudioInterface(a, b, c) (g_pcldstAddrs->pStudioInterface(&a, &b, &c)) +#define RecClChatInputPosition(a, b) (g_pcldstAddrs->pChatInputPosition(&a, &b)) + +*/ +// Macros for calling into the engine +#define CallEngSPR_Load(a) (gEngfuncs.pfnSPR_Load(a)) +#define CallEngSPR_Frames(a) (gEngfuncs.pfnSPR_Frames(a)) +#define CallEngSPR_Height(a, b) (gEngfuncs.pfnSPR_Height(a, b)) +#define CallEngSPR_Width(a, b) (gEngfuncs.pfnSPR_Width(a, b)) +#define CallEngSPR_Set(a, b, c, d) (gEngfuncs.pfnSPR_Set(a, b, c, d)) +#define CallEngSPR_Draw(a, b, c, d) (gEngfuncs.pfnSPR_Draw(a, b, c, d)) +#define CallEngSPR_DrawHoles(a, b, c, d) (gEngfuncs.pfnSPR_DrawHoles(a, b, c, d)) +#define CallEngSPR_DrawAdditive(a, b, c, d) (gEngfuncs.pfnSPR_DrawAdditive(a, b, c, d)) +#define CallEngSPR_EnableScissor(a, b, c, d) (gEngfuncs.pfnSPR_EnableScissor(a, b, c, d)) +#define CallEngSPR_DisableScissor() (gEngfuncs.pfnSPR_DisableScissor()) +#define CallEngSPR_GetList(a, b) (gEngfuncs.pfnSPR_GetList(a, b)) +#define CallEngDraw_FillRGBA(a, b, c, d, e, f, g, h) (gEngfuncs.pfnFillRGBA(a, b, c, d, e, f, g, h)) +#define CallEngDraw_FillRGBABlend(a, b, c, d, e, f, g, h) (gEngfuncs.pfnFillRGBABlend(a, b, c, d, e, f, g, h)) +#define CallEnghudGetScreenInfo(a) (gEngfuncs.pfnGetScreenInfo(a)) +#define CallEngSetCrosshair(a, b, c, d, e) (gEngfuncs.pfnSetCrosshair(a, b, c, d, e)) +#define CallEnghudRegisterVariable(a, b, c) (gEngfuncs.pfnRegisterVariable(a, b, c)) +#define CallEnghudGetCvarFloat(a) (gEngfuncs.pfnGetCvarFloat(a)) +#define CallEnghudGetCvarString(a) (gEngfuncs.pfnGetCvarString(a)) +#define CallEnghudAddCommand(a, b) (gEngfuncs.pfnAddCommand(a, b)) +#define CallEnghudHookUserMsg(a, b) (gEngfuncs.pfnHookUserMsg(a, b)) +#define CallEnghudServerCmd(a) (gEngfuncs.pfnServerCmd(a)) +#define CallEnghudClientCmd(a) (gEngfuncs.pfnClientCmd(a)) +#define CallEngPrimeMusicStream(a, b) (gEngfuncs.pfnPrimeMusicStream(a, b)) +#define CallEnghudGetPlayerInfo(a, b) (gEngfuncs.pfnGetPlayerInfo(a, b)) +#define CallEnghudPlaySoundByName(a, b) (gEngfuncs.pfnPlaySoundByName(a, b)) +#define CallEnghudPlaySoundByNameAtPitch(a, b, c) (gEngfuncs.pfnPlaySoundByNameAtPitch(a, b, c)) +#define CallEnghudPlaySoundVoiceByName(a, b, c) (gEngfuncs.pfnPlaySoundVoiceByName(a, b, c)) +#define CallEnghudPlaySoundByIndex(a, b) (gEngfuncs.pfnPlaySoundByIndex(a, b)) +#define CallEngAngleVectors(a, b, c, d) (gEngfuncs.pfnAngleVectors(a, b, c, d)) +#define CallEngTextMessageGet(a) (gEngfuncs.pfnTextMessageGet(a)) +#define CallEngTextMessageDrawCharacter(a, b, c, d, e, f) (gEngfuncs.pfnDrawCharacter(a, b, c, d, e, f)) +#define CallEngDraw_String(a, b, c) (gEngfuncs.pfnDrawConsoleString(a, b, c)) +#define CallEngDraw_SetTextColor(a, b, c) (gEngfuncs.pfnDrawSetTextColor(a, b, c)) +#define CallEnghudDrawConsoleStringLen(a, b, c) (gEngfuncs.pfnDrawConsoleStringLen(a, b, c)) +#define CallEnghudConsolePrint(a) (gEngfuncs.pfnConsolePrint(a)) +#define CallEnghudCenterPrint(a) (gEngfuncs.pfnCenterPrint(a)) +#define CallEnghudCenterX() (gEngfuncs.GetWindowCenterX()) +#define CallEnghudCenterY() (gEngfuncs.GetWindowCenterY()) +#define CallEnghudGetViewAngles(a) (gEngfuncs.GetViewAngles(a)) +#define CallEnghudSetViewAngles(a) (gEngfuncs.SetViewAngles(a)) +#define CallEnghudGetMaxClients() (gEngfuncs.GetMaxClients()) +#define CallEngCvar_SetValue(a, b) (gEngfuncs.Cvar_SetValue(a, b)) +#define CallEngCmd_Argc() (gEngfuncs.Cmd_Argc()) +#define CallEngCmd_Argv(a) (gEngfuncs.Cmd_Argv(a)) +#define CallEnghudPhysInfo_ValueForKey(a) (gEngfuncs.PhysInfo_ValueForKey(a)) +#define CallEnghudServerInfo_ValueForKey(a) (gEngfuncs.ServerInfo_ValueForKey(a)) +#define CallEnghudGetClientMaxspeed() (gEngfuncs.GetClientMaxspeed()) +#define CallEnghudCheckParm(a, b) (gEngfuncs.CheckParm(a, b)) +#define CallEngKey_Event(a, b) (gEngfuncs.Key_Event(a, b)) +#define CallEnghudGetMousePosition(a, b) (gEngfuncs.GetMousePosition(a, b)) +#define CallEnghudIsNoClipping() (gEngfuncs.IsNoClipping()) +#define CallEnghudGetLocalPlayer() (gEngfuncs.GetLocalPlayer()) +#define CallEnghudGetViewModel() (gEngfuncs.GetViewModel()) +#define CallEnghudGetEntityByIndex(a) (gEngfuncs.GetEntityByIndex(a)) +#define CallEnghudGetClientTime() (gEngfuncs.GetClientTime()) +#define CallEngV_CalcShake() (gEngfuncs.V_CalcShake()) +#define CallEngV_ApplyShake(a, b, c) (gEngfuncs.V_ApplyShake(a, b, c)) +#define CallEngPM_PointContents(a, b) (gEngfuncs.PM_PointContents(a, b)) +#define CallEngPM_WaterEntity(a) (gEngfuncs.PM_WaterEntity(a)) +#define CallEngPM_TraceLine(a, b, c, d, e) (gEngfuncs.PM_TraceLine(a, b, c, d, e)) +#define CallEngCL_LoadModel(a, b) (gEngfuncs.CL_LoadModel(a, b)) +#define CallEngCL_CreateVisibleEntity(a, b) (gEngfuncs.CL_CreateVisibleEntity(a, b)) +#define CallEnghudGetSpritePointer(a) (gEngfuncs.GetSpritePointer(a)) +#define CallEnghudPlaySoundByNameAtLocation(a, b, c) (gEngfuncs.pfnPlaySoundByNameAtLocation(a, b, c)) +#define CallEnghudPrecacheEvent(a, b) (gEngfuncs.pfnPrecacheEvent(a, b)) +#define CallEnghudPlaybackEvent(a, b, c, d, e, f, g, h, i, j, k, l) (gEngfuncs.pfnPlaybackEvent(a, b, c, d, e, f, g, h, i, j, k, l)) +#define CallEnghudWeaponAnim(a, b) (gEngfuncs.pfnWeaponAnim(a, b)) +#define CallEngRandomFloat(a, b) (gEngfuncs.pfnRandomFloat(a, b)) +#define CallEngRandomLong(a, b) (gEngfuncs.pfnRandomLong(a, b)) +#define CallEngCL_HookEvent(a, b) (gEngfuncs.pfnHookEvent(a, b)) +#define CallEngCon_IsVisible() (gEngfuncs.Con_IsVisible()) +#define CallEnghudGetGameDir() (gEngfuncs.pfnGetGameDirectory()) +#define CallEngCvar_FindVar(a) (gEngfuncs.pfnGetCvarPointer(a)) +#define CallEngKey_NameForBinding(a) (gEngfuncs.Key_LookupBinding(a)) +#define CallEnghudGetLevelName() (gEngfuncs.pfnGetLevelName()) +#define CallEnghudGetScreenFade(a) (gEngfuncs.pfnGetScreenFade(a)) +#define CallEnghudSetScreenFade(a) (gEngfuncs.pfnSetScreenFade(a)) +#define CallEngVGuiWrap_GetPanel() (gEngfuncs.VGui_GetPanel()) +#define CallEngVGui_ViewportPaintBackground(a) (gEngfuncs.VGui_ViewportPaintBackground(a)) +#define CallEngCOM_LoadFile(a, b, c) (gEngfuncs.COM_LoadFile(a, b, c)) +#define CallEngCOM_ParseFile(a, b) (gEngfuncs.COM_ParseFile(a, b)) +#define CallEngCOM_FreeFile(a) (gEngfuncs.COM_FreeFile(a)) +#define CallEngCL_IsSpectateOnly() (gEngfuncs.IsSpectateOnly()) +#define CallEngR_LoadMapSprite(a) (gEngfuncs.LoadMapSprite(a)) +#define CallEngCOM_AddAppDirectoryToSearchPath(a, b) (gEngfuncs.COM_AddAppDirectoryToSearchPath(a, b)) +#define CallEngClientDLL_ExpandFileName(a, b, c)(gEngfuncs.COM_ExpandFilename(a, b, c)) +#define CallEngPlayerInfo_ValueForKey(a, b) (gEngfuncs.PlayerInfo_ValueForKey(a, b)) +#define CallEngPlayerInfo_SetValueForKey(a, b) (gEngfuncs.PlayerInfo_SetValueForKey(a, b)) +#define CallEngGetPlayerUniqueID(a, b) (gEngfuncs.GetPlayerUniqueID(a, b)) +#define CallEngGetTrackerIDForPlayer(a) (gEngfuncs.GetTrackerIDForPlayer(a)) +#define CallEngGetPlayerForTrackerID(a) (gEngfuncs.GetPlayerForTrackerID(a)) +#define CallEnghudServerCmdUnreliable(a) (gEngfuncs.pfnServerCmdUnreliable(a)) +#define CallEngGetMousePos(a) (gEngfuncs.pfnGetMousePos(a)) +#define CallEngSetMousePos(a, b) (gEngfuncs.pfnSetMousePos(a, b)) +#define CallEngSetMouseEnable(a) (gEngfuncs.pfnSetMouseEnable(a)) +#define CallEngLocalPlayerInfo_ValueForKey(a) (gEngfuncs.LocalPlayerInfo_ValueForKey(a)) + +#if 0 +inline float CVAR_GET_FLOAT( const char *x ) { return CallEnghudGetCvarFloat( (char*)x ); } +inline char* CVAR_GET_STRING( const char *x ) { return CallEnghudGetCvarString( (char*)x ); } +inline struct cvar_s *CVAR_CREATE( const char *cv, const char *val, const int flags ) { return CallEnghudRegisterVariable( (char*)cv, (char*)val, flags ); } +#endif + diff --git a/ricochet/cl_dll/GameStudioModelRenderer.cpp b/ricochet/cl_dll/GameStudioModelRenderer.cpp new file mode 100644 index 0000000..69885d3 --- /dev/null +++ b/ricochet/cl_dll/GameStudioModelRenderer.cpp @@ -0,0 +1,981 @@ +#include +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include "studio.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "dlight.h" +#include "triangleapi.h" + +#include +#include +#include +#include + +#include "studio_util.h" +#include "r_studioint.h" + +#include "StudioModelRenderer.h" +#include "GameStudioModelRenderer.h" + +void Ricochet_GetSequence( int *seq, int *gaitseq ); +void Ricochet_GetOrientation( float *o, float *a ); + +float g_flStartScaleTime; +int iPrevRenderState; +int iRenderStateChanged; + +// Global engine <-> studio model rendering code interface +extern engine_studio_api_t IEngineStudio; + +typedef struct +{ + vec3_t origin; + vec3_t angles; + + vec3_t realangles; + + float animtime; + float frame; + int sequence; + int gaitsequence; + float framerate; + + int m_fSequenceLoops; + int m_fSequenceFinished; + + byte controller[ 4 ]; + byte blending[ 2 ]; + + latchedvars_t lv; +} client_anim_state_t; + +static client_anim_state_t g_state; +static client_anim_state_t g_clientstate; + +// The renderer object, created on the stack. +CGameStudioModelRenderer g_StudioRenderer; +/* +==================== +CGameStudioModelRenderer + +==================== +*/ +CGameStudioModelRenderer::CGameStudioModelRenderer( void ) +{ + // If you want to predict animations locally, set this to TRUE + // NOTE: The animation code is somewhat broken, but gives you a sense for how + // to do client side animation of the predicted player in a third person game. + m_bLocal = false; +} + +/* +==================== +StudioSetupBones + +==================== +*/ +void CGameStudioModelRenderer::StudioSetupBones ( void ) +{ + int i; + double f; + + mstudiobone_t *pbones; + mstudioseqdesc_t *pseqdesc; + mstudioanim_t *panim; + + static float pos[MAXSTUDIOBONES][3]; + static vec4_t q[MAXSTUDIOBONES]; + float bonematrix[3][4]; + + static float pos2[MAXSTUDIOBONES][3]; + static vec4_t q2[MAXSTUDIOBONES]; + static float pos3[MAXSTUDIOBONES][3]; + static vec4_t q3[MAXSTUDIOBONES]; + static float pos4[MAXSTUDIOBONES][3]; + static vec4_t q4[MAXSTUDIOBONES]; + + // Use default bone setup for nonplayers + if ( !m_pCurrentEntity->player ) + { + CStudioModelRenderer::StudioSetupBones(); + return; + } + + // Bound sequence number. + if ( m_pCurrentEntity->curstate.sequence >= m_pStudioHeader->numseq ) + { + m_pCurrentEntity->curstate.sequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + if ( m_pPlayerInfo && m_pPlayerInfo->gaitsequence != 0 ) + { + f = m_pPlayerInfo->gaitframe; + } + else + { + f = StudioEstimateFrame( pseqdesc ); + } + + // Discwar knows how to do three way blending + if ( pseqdesc->numblends == 3 ) + { + float s; + + // Get left anim + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + + // Blending is 0-127 == Left to Middle, 128 to 255 == Middle to right + if ( m_pCurrentEntity->curstate.blending[0] <= 127 ) + { + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + + // Scale 0-127 blending up to 0-255 + s = m_pCurrentEntity->curstate.blending[0]; + s = ( s * 2.0 ); + } + else + { + + // Skip ahead to middle + panim += m_pStudioHeader->numbones; + + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + + // Scale 127-255 blending up to 0-255 + s = m_pCurrentEntity->curstate.blending[0]; + s = 2.0 * ( s - 127.0 ); + } + + // Normalize interpolant + s /= 255.0; + + // Go to middle or right + panim += m_pStudioHeader->numbones; + + StudioCalcRotations( pos2, q2, pseqdesc, panim, f ); + + // Spherically interpolate the bones + StudioSlerpBones( q, pos, q2, pos2, s ); + } + else + { + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + } + + // Are we in the process of transitioning from one sequence to another. + if ( m_fDoInterp && + m_pCurrentEntity->latched.sequencetime && + ( m_pCurrentEntity->latched.sequencetime + 0.2 > m_clTime ) && + ( m_pCurrentEntity->latched.prevsequence < m_pStudioHeader->numseq )) + { + // blend from last sequence + static float pos1b[MAXSTUDIOBONES][3]; + static vec4_t q1b[MAXSTUDIOBONES]; + float s; + + // Blending value into last sequence + unsigned char prevseqblending = m_pCurrentEntity->latched.prevseqblending[ 0 ]; + + // Point at previous sequenece + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->latched.prevsequence; + + // Know how to do three way blends + if ( pseqdesc->numblends == 3 ) + { + float s; + + // Get left animation + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + + if ( prevseqblending <= 127 ) + { + // Set up bones based on final frame of previous sequence + StudioCalcRotations( pos1b, q1b, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + s = prevseqblending; + s = ( s * 2.0 ); + } + else + { + // Skip to middle blend + panim += m_pStudioHeader->numbones; + + StudioCalcRotations( pos1b, q1b, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + s = prevseqblending; + s = 2.0 * ( s - 127.0 ); + } + + // Normalize + s /= 255.0; + + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos2, q2, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + // Interpolate bones + StudioSlerpBones( q1b, pos1b, q2, pos2, s ); + } + else + { + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + // clip prevframe + StudioCalcRotations( pos1b, q1b, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + } + + // Now blend last frame of previous sequence with current sequence. + s = 1.0 - (m_clTime - m_pCurrentEntity->latched.sequencetime) / 0.2; + StudioSlerpBones( q, pos, q1b, pos1b, s ); + } + else + { + m_pCurrentEntity->latched.prevframe = f; + } + + // Now convert quaternions and bone positions into matrices + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + QuaternionMatrix( q[i], bonematrix ); + + bonematrix[0][3] = pos[i][0]; + bonematrix[1][3] = pos[i][1]; + bonematrix[2][3] = pos[i][2]; + + if (pbones[i].parent == -1) + { + if ( IEngineStudio.IsHardware() ) + { + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + } + else + { + ConcatTransforms ((*m_paliastransform), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + } + + // Apply client-side effects to the transformation matrix + StudioFxTransform( m_pCurrentEntity, (*m_pbonetransform)[i] ); + } + else + { + ConcatTransforms ((*m_pbonetransform)[pbones[i].parent], bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_plighttransform)[pbones[i].parent], bonematrix, (*m_plighttransform)[i]); + } + } +} + +/* +==================== +StudioEstimateGait + +==================== +*/ +void CGameStudioModelRenderer::StudioEstimateGait( entity_state_t *pplayer ) +{ + float dt; + vec3_t est_velocity; + + dt = (m_clTime - m_clOldTime); + dt = max( 0.0, dt ); + dt = min( 1.0, dt ); + + if (dt == 0 || m_pPlayerInfo->renderframe == m_nFrameCount) + { + m_flGaitMovement = 0; + return; + } + + // VectorAdd( pplayer->velocity, pplayer->prediction_error, est_velocity ); + if ( m_fGaitEstimation ) + { + VectorSubtract( m_pCurrentEntity->origin, m_pPlayerInfo->prevgaitorigin, est_velocity ); + VectorCopy( m_pCurrentEntity->origin, m_pPlayerInfo->prevgaitorigin ); + m_flGaitMovement = Length( est_velocity ); + if (dt <= 0 || m_flGaitMovement / dt < 5) + { + m_flGaitMovement = 0; + est_velocity[0] = 0; + est_velocity[1] = 0; + } + } + else + { + VectorCopy( pplayer->velocity, est_velocity ); + m_flGaitMovement = Length( est_velocity ) * dt; + } + + if (est_velocity[1] == 0 && est_velocity[0] == 0) + { + float flYawDiff = m_pCurrentEntity->angles[YAW] - m_pPlayerInfo->gaityaw; + flYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360; + if (flYawDiff > 180) + flYawDiff -= 360; + if (flYawDiff < -180) + flYawDiff += 360; + + if (dt < 0.25) + flYawDiff *= dt * 4; + else + flYawDiff *= dt; + + m_pPlayerInfo->gaityaw += flYawDiff; + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - (int)(m_pPlayerInfo->gaityaw / 360) * 360; + + m_flGaitMovement = 0; + } + else + { + m_pPlayerInfo->gaityaw = (atan2(est_velocity[1], est_velocity[0]) * 180 / M_PI); + if (m_pPlayerInfo->gaityaw > 180) + m_pPlayerInfo->gaityaw = 180; + if (m_pPlayerInfo->gaityaw < -180) + m_pPlayerInfo->gaityaw = -180; + } + +} + +/* +==================== +StudioProcessGait + +==================== +*/ +void CGameStudioModelRenderer::StudioProcessGait( entity_state_t *pplayer ) +{ + mstudioseqdesc_t *pseqdesc; + float dt; + float flYaw; // view direction relative to movement + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + m_pCurrentEntity->angles[PITCH] = 0; + m_pCurrentEntity->latched.prevangles[PITCH] = m_pCurrentEntity->angles[PITCH]; + + dt = (m_clTime - m_clOldTime); + dt = max( 0.0, dt ); + dt = min( 1.0, dt ); + + StudioEstimateGait( pplayer ); + + // calc side to side turning + flYaw = m_pCurrentEntity->angles[YAW] - m_pPlayerInfo->gaityaw; + + flYaw = fmod( flYaw, 360.0 ); + + if (flYaw < -180) + { + flYaw = flYaw + 360; + } + else if (flYaw > 180) + { + flYaw = flYaw - 360; + } + + float maxyaw = 120.0; + + if (flYaw > maxyaw) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - 180; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw - 180; + } + else if (flYaw < -maxyaw) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw + 180; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw + 180; + } + + float blend_yaw = ( flYaw / 90.0 ) * 128.0 + 127.0; + blend_yaw = min( 255.0, blend_yaw ); + blend_yaw = max( 0.0, blend_yaw ); + + blend_yaw = 255.0 - blend_yaw; + + m_pCurrentEntity->curstate.blending[0] = (int)(blend_yaw); + m_pCurrentEntity->latched.prevblending[0] = m_pCurrentEntity->curstate.blending[0]; + m_pCurrentEntity->latched.prevseqblending[0] = m_pCurrentEntity->curstate.blending[0]; + + m_pCurrentEntity->angles[YAW] = m_pPlayerInfo->gaityaw; + if (m_pCurrentEntity->angles[YAW] < -0) + { + m_pCurrentEntity->angles[YAW] += 360; + } + m_pCurrentEntity->latched.prevangles[YAW] = m_pCurrentEntity->angles[YAW]; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + pplayer->gaitsequence; + + // Calc gait frame + if (pseqdesc->linearmovement[0] > 0) + { + m_pPlayerInfo->gaitframe += (m_flGaitMovement / pseqdesc->linearmovement[0]) * pseqdesc->numframes; + } + else + { + m_pPlayerInfo->gaitframe += pseqdesc->fps * dt * m_pCurrentEntity->curstate.framerate; + } + + // Do modulo + m_pPlayerInfo->gaitframe = m_pPlayerInfo->gaitframe - (int)(m_pPlayerInfo->gaitframe / pseqdesc->numframes) * pseqdesc->numframes; + if (m_pPlayerInfo->gaitframe < 0) + { + m_pPlayerInfo->gaitframe += pseqdesc->numframes; + } +} + +/* +============================== +SavePlayerState + +For local player, in third person, we need to store real render data and then + setup for with fake/client side animation data +============================== +*/ +void CGameStudioModelRenderer::SavePlayerState( entity_state_t *pplayer ) +{ + client_anim_state_t *st; + cl_entity_t *ent = IEngineStudio.GetCurrentEntity(); + assert( ent ); + if ( !ent ) + return; + + st = &g_state; + + st->angles = ent->curstate.angles; + st->origin = ent->curstate.origin; + + st->realangles = ent->angles; + + st->sequence = ent->curstate.sequence; + st->gaitsequence = pplayer->gaitsequence; + st->animtime = ent->curstate.animtime; + st->frame = ent->curstate.frame; + st->framerate = ent->curstate.framerate; + memcpy( st->blending, ent->curstate.blending, 2 ); + memcpy( st->controller, ent->curstate.controller, 4 ); + + st->lv = ent->latched; +} + +void GetSequenceInfo( void *pmodel, client_anim_state_t *pev, float *pflFrameRate, float *pflGroundSpeed ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return; + + mstudioseqdesc_t *pseqdesc; + + if (pev->sequence >= pstudiohdr->numseq) + { + *pflFrameRate = 0.0; + *pflGroundSpeed = 0.0; + return; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + if (pseqdesc->numframes > 1) + { + *pflFrameRate = 256 * pseqdesc->fps / (pseqdesc->numframes - 1); + *pflGroundSpeed = sqrt( pseqdesc->linearmovement[0]*pseqdesc->linearmovement[0]+ pseqdesc->linearmovement[1]*pseqdesc->linearmovement[1]+ pseqdesc->linearmovement[2]*pseqdesc->linearmovement[2] ); + *pflGroundSpeed = *pflGroundSpeed * pseqdesc->fps / (pseqdesc->numframes - 1); + } + else + { + *pflFrameRate = 256.0; + *pflGroundSpeed = 0.0; + } +} + +int GetSequenceFlags( void *pmodel, client_anim_state_t *pev ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq ) + return 0; + + mstudioseqdesc_t *pseqdesc; + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + return pseqdesc->flags; +} + +float StudioFrameAdvance ( client_anim_state_t *st, float framerate, float flInterval ) +{ + if (flInterval == 0.0) + { + flInterval = (gEngfuncs.GetClientTime() - st->animtime); + if (flInterval <= 0.001) + { + st->animtime = gEngfuncs.GetClientTime(); + return 0.0; + } + } + if (!st->animtime) + flInterval = 0.0; + + st->frame += flInterval * framerate * st->framerate; + st->animtime = gEngfuncs.GetClientTime(); + + if (st->frame < 0.0 || st->frame >= 256.0) + { + if ( st->m_fSequenceLoops ) + st->frame -= (int)(st->frame / 256.0) * 256.0; + else + st->frame = (st->frame < 0.0) ? 0 : 255; + st->m_fSequenceFinished = TRUE; // just in case it wasn't caught in GetEvents + } + + return flInterval; +} + +/* +============================== +SetupClientAnimation + +Called to set up local player's animation values +============================== +*/ +void CGameStudioModelRenderer::SetupClientAnimation( entity_state_t *pplayer ) +{ + static double oldtime; + double curtime, dt; + + client_anim_state_t *st; + float fr, gs; + + cl_entity_t *ent = IEngineStudio.GetCurrentEntity(); + assert( ent ); + if ( !ent ) + return; + + curtime = gEngfuncs.GetClientTime(); + dt = curtime - oldtime; + dt = min( 1.0, max( 0.0, dt ) ); + + oldtime = curtime; + st = &g_clientstate; + + st->framerate = 1.0; + + int oldseq = st->sequence; + Ricochet_GetSequence( &st->sequence, &st->gaitsequence ); + Ricochet_GetOrientation( (float *)&st->origin, (float *)&st->angles ); + st->realangles = st->angles; + + if ( st->sequence != oldseq ) + { + st->frame = 0.0; + st->lv.prevsequence = oldseq; + st->lv.sequencetime = st->animtime; + + memcpy( st->lv.prevseqblending, st->blending, 2 ); + memcpy( st->lv.prevcontroller, st->controller, 4 ); + } + + void *pmodel = (studiohdr_t *)IEngineStudio.Mod_Extradata( ent->model ); + + GetSequenceInfo( pmodel, st, &fr, &gs ); + st->m_fSequenceLoops = ((GetSequenceFlags( pmodel, st ) & STUDIO_LOOPING) != 0); + StudioFrameAdvance( st, fr, dt ); + + ent->angles = st->realangles; + ent->curstate.angles = st->angles; + ent->curstate.origin = st->origin; + + ent->curstate.sequence = st->sequence; + pplayer->gaitsequence = st->gaitsequence; + ent->curstate.animtime = st->animtime; + ent->curstate.frame = st->frame; + ent->curstate.framerate = st->framerate; + memcpy( ent->curstate.blending, st->blending, 2 ); + memcpy( ent->curstate.controller, st->controller, 4 ); + + ent->latched = st->lv; +} + +/* +============================== +RestorePlayerState + +Called to restore original player state information +============================== +*/ +void CGameStudioModelRenderer::RestorePlayerState( entity_state_t *pplayer ) +{ + client_anim_state_t *st; + cl_entity_t *ent = IEngineStudio.GetCurrentEntity(); + assert( ent ); + if ( !ent ) + return; + + st = &g_clientstate; + + st->angles = ent->curstate.angles; + st->origin = ent->curstate.origin; + st->realangles = ent->angles; + + st->sequence = ent->curstate.sequence; + st->gaitsequence = pplayer->gaitsequence; + st->animtime = ent->curstate.animtime; + st->frame = ent->curstate.frame; + st->framerate = ent->curstate.framerate; + memcpy( st->blending, ent->curstate.blending, 2 ); + memcpy( st->controller, ent->curstate.controller, 4 ); + + st->lv = ent->latched; + + st = &g_state; + + ent->curstate.angles = st->angles; + ent->curstate.origin = st->origin; + ent->angles = st->realangles; + + ent->curstate.sequence = st->sequence; + pplayer->gaitsequence = st->gaitsequence; + ent->curstate.animtime = st->animtime; + ent->curstate.frame = st->frame; + ent->curstate.framerate = st->framerate; + memcpy( ent->curstate.blending, st->blending, 2 ); + memcpy( ent->curstate.controller, st->controller, 4 ); + + ent->latched = st->lv; +} + +/* +============================== +StudioDrawPlayer + +============================== +*/ +int CGameStudioModelRenderer::StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + int iret = 0; + + bool isLocalPlayer = false; + + // Set up for client? + if ( m_bLocal && IEngineStudio.GetCurrentEntity() == gEngfuncs.GetLocalPlayer() ) + { + isLocalPlayer = true; + } + + if ( isLocalPlayer ) + { + // Store original data + SavePlayerState( pplayer ); + + // Copy in client side animation data + SetupClientAnimation( pplayer ); + } + + // Call real draw function + iret = _StudioDrawPlayer( flags, pplayer ); + + // Restore for client? + if ( isLocalPlayer ) + { + // Restore the original data for the player + RestorePlayerState( pplayer ); + } + + return iret; +} + +/* +==================== +_StudioDrawPlayer + +==================== +*/ +int CGameStudioModelRenderer::_StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + alight_t lighting; + vec3_t dir; + + m_pCurrentEntity = IEngineStudio.GetCurrentEntity(); + IEngineStudio.GetTimes( &m_nFrameCount, &m_clTime, &m_clOldTime ); + IEngineStudio.GetViewInfo( m_vRenderOrigin, m_vUp, m_vRight, m_vNormal ); + IEngineStudio.GetAliasScale( &m_fSoftwareXScale, &m_fSoftwareYScale ); + + m_nPlayerIndex = pplayer->number - 1; + + if (m_nPlayerIndex < 0 || m_nPlayerIndex >= gEngfuncs.GetMaxClients()) + return 0; + + m_pRenderModel = IEngineStudio.SetupPlayerModel( m_nPlayerIndex ); + if (m_pRenderModel == NULL) + return 0; + + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (m_pRenderModel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + IEngineStudio.SetRenderModel( m_pRenderModel ); + + if (pplayer->gaitsequence) + { + vec3_t orig_angles; + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + + VectorCopy( m_pCurrentEntity->angles, orig_angles ); + + StudioProcessGait( pplayer ); + + m_pPlayerInfo->gaitsequence = pplayer->gaitsequence; + m_pPlayerInfo = NULL; + + StudioSetUpTransform( 0 ); + VectorCopy( orig_angles, m_pCurrentEntity->angles ); + } + else + { + m_pCurrentEntity->curstate.controller[0] = 127; + m_pCurrentEntity->curstate.controller[1] = 127; + m_pCurrentEntity->curstate.controller[2] = 127; + m_pCurrentEntity->curstate.controller[3] = 127; + m_pCurrentEntity->latched.prevcontroller[0] = m_pCurrentEntity->curstate.controller[0]; + m_pCurrentEntity->latched.prevcontroller[1] = m_pCurrentEntity->curstate.controller[1]; + m_pCurrentEntity->latched.prevcontroller[2] = m_pCurrentEntity->curstate.controller[2]; + m_pCurrentEntity->latched.prevcontroller[3] = m_pCurrentEntity->curstate.controller[3]; + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + m_pPlayerInfo->gaitsequence = 0; + + StudioSetUpTransform( 0 ); + } + + if (flags & STUDIO_RENDER) + { + // see if the bounding box lets us trivially reject, also sets + if (!IEngineStudio.StudioCheckBBox ()) + return 0; + + (*m_pModelsDrawn)++; + (*m_pStudioModelCount)++; // render data cache cookie + + if (m_pStudioHeader->numbodyparts == 0) + return 1; + } + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + StudioSetupBones( ); + StudioSaveBones( ); + m_pPlayerInfo->renderframe = m_nFrameCount; + + m_pPlayerInfo = NULL; + + if (flags & STUDIO_EVENTS) + { + StudioCalcAttachments( ); + IEngineStudio.StudioClientEvents( ); + // copy attachments into global entity array + if ( m_pCurrentEntity->index > 0 ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( m_pCurrentEntity->index ); + + memcpy( ent->attachment, m_pCurrentEntity->attachment, sizeof( vec3_t ) * 4 ); + } + } + + if (flags & STUDIO_RENDER) + { + /* + if (m_pCvarHiModels->value && m_pRenderModel != m_pCurrentEntity->model ) + { + // show highest resolution multiplayer model + m_pCurrentEntity->curstate.body = 255; + } + + if (!(m_pCvarDeveloper->value == 0 && gEngfuncs.GetMaxClients() == 1 ) && ( m_pRenderModel == m_pCurrentEntity->model ) ) + { + m_pCurrentEntity->curstate.body = 1; // force helmet + } + */ + + lighting.plightvec = dir; + IEngineStudio.StudioDynamicLight(m_pCurrentEntity, &lighting ); + + IEngineStudio.StudioEntityLight( &lighting ); + + // model and frame independant + IEngineStudio.StudioSetupLighting (&lighting); + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + + // get remap colors + m_nTopColor = m_pPlayerInfo->topcolor; + if (m_nTopColor < 0) + m_nTopColor = 0; + if (m_nTopColor > 360) + m_nTopColor = 360; + m_nBottomColor = m_pPlayerInfo->bottomcolor; + if (m_nBottomColor < 0) + m_nBottomColor = 0; + if (m_nBottomColor > 360) + m_nBottomColor = 360; + + IEngineStudio.StudioSetRemapColors( m_nTopColor, m_nBottomColor ); + + StudioRenderModel( ); + m_pPlayerInfo = NULL; + + if (pplayer->weaponmodel) + { + cl_entity_t saveent = *m_pCurrentEntity; + + model_t *pweaponmodel = IEngineStudio.GetModelByIndex( pplayer->weaponmodel ); + + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (pweaponmodel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + + StudioMergeBones( pweaponmodel); + + IEngineStudio.StudioSetupLighting (&lighting); + + StudioRenderModel( ); + + StudioCalcAttachments( ); + + *m_pCurrentEntity = saveent; + } + } + + return 1; +} + +/* +==================== +Studio_FxTransform + +==================== +*/ +void CGameStudioModelRenderer::StudioFxTransform( cl_entity_t *ent, float transform[3][4] ) +{ + switch( ent->curstate.renderfx ) + { + case kRenderFxDistort: + case kRenderFxHologram: + if ( gEngfuncs.pfnRandomLong(0,49) == 0 ) + { + int axis = gEngfuncs.pfnRandomLong(0,1); + if ( axis == 1 ) // Choose between x & z + axis = 2; + VectorScale( transform[axis], gEngfuncs.pfnRandomFloat(1,1.484), transform[axis] ); + } + else if ( gEngfuncs.pfnRandomLong(0,49) == 0 ) + { + float offset; + int axis = gEngfuncs.pfnRandomLong(0,1); + if ( axis == 1 ) // Choose between x & z + axis = 2; + offset = gEngfuncs.pfnRandomFloat(-10,10); + transform[gEngfuncs.pfnRandomLong(0,2)][3] += offset; + } + break; + case kRenderFxExplode: + { + if ( iRenderStateChanged ) + { + g_flStartScaleTime = m_clTime; + iRenderStateChanged = FALSE; + } + + // Make the Model continue to shrink + float flTimeDelta = m_clTime - g_flStartScaleTime; + if ( flTimeDelta > 0 ) + { + float flScale = 0.001; + // Goes almost all away + if ( flTimeDelta <= 2.0 ) + flScale = 1.0 - (flTimeDelta / 2.0); + + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + transform[i][j] *= flScale; + } + } + } + break; + } +} + +//////////////////////////////////// +// Hooks to class implementation +//////////////////////////////////// + +/* +==================== +R_StudioDrawPlayer + +==================== +*/ +int R_StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + return g_StudioRenderer.StudioDrawPlayer( flags, pplayer ); +} + +/* +==================== +R_StudioDrawModel + +==================== +*/ +int R_StudioDrawModel( int flags ) +{ + return g_StudioRenderer.StudioDrawModel( flags ); +} + +/* +==================== +R_StudioInit + +==================== +*/ +void R_StudioInit( void ) +{ + g_StudioRenderer.Init(); +} + +// The simple drawing interface we'll pass back to the engine +r_studio_interface_t studio = +{ + STUDIO_INTERFACE_VERSION, + R_StudioDrawModel, + R_StudioDrawPlayer, +}; + +/* +==================== +HUD_GetStudioModelInterface + +Export this function for the engine to use the studio renderer class to render objects. +==================== +*/ +extern "C" int EXPORT HUD_GetStudioModelInterface( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio ) +{ + if ( version != STUDIO_INTERFACE_VERSION ) + return 0; + + // Point the engine to our callbacks + *ppinterface = &studio; + + // Copy in engine helper functions + memcpy( &IEngineStudio, pstudio, sizeof( IEngineStudio ) ); + + // Initialize local variables, etc. + R_StudioInit(); + + // Success + return 1; +} diff --git a/ricochet/cl_dll/GameStudioModelRenderer.h b/ricochet/cl_dll/GameStudioModelRenderer.h new file mode 100644 index 0000000..64f6c8b --- /dev/null +++ b/ricochet/cl_dll/GameStudioModelRenderer.h @@ -0,0 +1,48 @@ +#if !defined( GAMESTUDIOMODELRENDERER_H ) +#define GAMESTUDIOMODELRENDERER_H +#if defined( _WIN32 ) +#pragma once +#endif + +/* +==================== +CGameStudioModelRenderer + +==================== +*/ +class CGameStudioModelRenderer : public CStudioModelRenderer +{ +public: + CGameStudioModelRenderer( void ); + + // Set up model bone positions + virtual void StudioSetupBones ( void ); + + // Estimate gait frame for player + virtual void StudioEstimateGait ( entity_state_t *pplayer ); + + // Process movement of player + virtual void StudioProcessGait ( entity_state_t *pplayer ); + + // Player drawing code + virtual int StudioDrawPlayer( int flags, entity_state_t *pplayer ); + virtual int _StudioDrawPlayer( int flags, entity_state_t *pplayer ); + + // Apply special effects to transform matrix + virtual void StudioFxTransform( cl_entity_t *ent, float transform[3][4] ); + +private: + // For local player, in third person, we need to store real render data and then + // setup for with fake/client side animation data + void SavePlayerState( entity_state_t *pplayer ); + // Called to set up local player's animation values + void SetupClientAnimation( entity_state_t *pplayer ); + // Called to restore original player state information + void RestorePlayerState( entity_state_t *pplayer ); + +private: + // Private data + bool m_bLocal; +}; + +#endif // GAMESTUDIOMODELRENDERER_H \ No newline at end of file diff --git a/ricochet/cl_dll/Ricochet_BSPFile.h b/ricochet/cl_dll/Ricochet_BSPFile.h new file mode 100644 index 0000000..8e21ea0 --- /dev/null +++ b/ricochet/cl_dll/Ricochet_BSPFile.h @@ -0,0 +1,41 @@ +#if !defined( RICOCHET_BSPFILE_H ) +#define RICOCHET_BSPFILE_H +#ifdef _WIN32 +#pragma once +#endif + +// MINI-version of BSPFILE.H to support Ricochet entity lump extraction stuff. + +#define BSPVERSION 30 + +typedef struct +{ + int fileofs, filelen; +} lump_t; + +#define LUMP_ENTITIES 0 +#define LUMP_PLANES 1 +#define LUMP_TEXTURES 2 +#define LUMP_VERTEXES 3 +#define LUMP_VISIBILITY 4 +#define LUMP_NODES 5 +#define LUMP_TEXINFO 6 +#define LUMP_FACES 7 +#define LUMP_LIGHTING 8 +#define LUMP_CLIPNODES 9 +#define LUMP_LEAFS 10 +#define LUMP_MARKSURFACES 11 +#define LUMP_EDGES 12 +#define LUMP_SURFEDGES 13 +#define LUMP_MODELS 14 + +#define HEADER_LUMPS 15 + +typedef struct +{ + int version; + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + + +#endif // RICOCHET_BSPFILE_H \ No newline at end of file diff --git a/ricochet/cl_dll/Ricochet_JumpPads.cpp b/ricochet/cl_dll/Ricochet_JumpPads.cpp new file mode 100644 index 0000000..c5ca9e5 --- /dev/null +++ b/ricochet/cl_dll/Ricochet_JumpPads.cpp @@ -0,0 +1,580 @@ +#include "extdll.h" +#include "entity_state.h" +#include "pm_defs.h" +#include "pm_movevars.h" +#include "hud_iface.h" +#include "com_model.h" +#include "event_api.h" +#include "com_weapons.h" +#include "event_flags.h" +#include "Ricochet_BSPFile.h" + +extern "C" playermove_t *pmove; +extern int g_runfuncs; + +// Don't support more than MAX_PADS pads ( map still can load, but we'll just have some pads that don't predict. ) +#define MAX_PADS 256 + +// We only care about two kinds of entities for now: Jump pads and their targets +// FIXME: After loading, store a pointer from pad to target instead of looking up all the time. +typedef enum +{ + // Entity is a jump pad + RIC_PAD = 0, + // Entity is a target + RIC_TARGET +} ric_padtype_t; + +typedef struct +{ + // Type of entity + ric_padtype_t type; + + // Classname + char classname[ 32 ]; + + // Model name + char modelname[ 32 ]; + + // What this entity targets + char target[ 32 ]; + + // If entity is a target, the name tag it uses + char targetname[ 32 ]; + + // Orientation of the pad + float angles[3]; + + // Target origin + float origin[3]; + + // Bounding box of the pad + float absmin[3]; + float absmax[3]; + + // Model associated with the pad + struct model_s *model; + + float height; +} ric_pad_t; + +// Pad/Target entity database +static ric_pad_t s_pads[ MAX_PADS ]; +static int s_num_pads = 0; + +// We'll use this for playing the jump sounds locally. +static unsigned short s_usJump; + +/* +============================== +Ricochet_SetKeyValue + +Fill in key/values fro the pad +============================== +*/ +void Ricochet_SetKeyValue( ric_pad_t *pad, const char *key, const char *value ) +{ + float x, y, z; + + if ( !stricmp( key, "classname" ) ) + { + strcpy( pad->classname, value ); + } + else if ( !stricmp( key, "target" ) ) + { + strcpy( pad->target, value ); + } + else if ( !stricmp( key, "targetname" ) ) + { + strcpy( pad->targetname, value ); + } + else if ( !stricmp( key, "model" ) ) + { + strcpy( pad->modelname, value ); + } + else if ( !stricmp( key, "height" ) ) + { + pad->height = atof( value ); + } + else if ( !stricmp( key, "angles" ) ) + { + if ( sscanf( value, "%f %f %f", &x, &y, &z ) == 3 ) + { + pad->angles[ 0 ] = x ; + pad->angles[ 1 ] = y; + pad->angles[ 2 ] = z; + } + } + else if ( !stricmp( key, "origin" ) ) + { + if ( sscanf( value, "%f %f %f", &x, &y, &z ) == 3 ) + { + pad->origin[ 0 ] = x; + pad->origin[ 1 ] = y; + pad->origin[ 2 ] = z; + } + } +} + +/* +============================== +Ricochet_ParsePad + +Evaluate Key/Value pairs for the entity +============================== +*/ +char *Ricochet_ParsePad( char *buffer, ric_pad_t *pad, int *error ) +{ + char key[256]; + char token[ 1024 ]; + int n; + + memset( pad, 0, sizeof( *pad ) ); + + while (1) + { + // Parse key + buffer = gEngfuncs.COM_ParseFile ( buffer, token ); + if ( token[0] == '}' ) + break; + + // Ran out of input buffer? + if ( !buffer ) + { + *error = 1; + break; + } + + // Store off the key + strcpy ( key, token ); + + // Fix heynames with trailing spaces + n = strlen( key ); + while (n && key[n-1] == ' ') + { + key[n-1] = 0; + n--; + } + + // Parse value + buffer = gEngfuncs.COM_ParseFile ( buffer, token ); + + // Ran out of buffer? + if (!buffer) + { + *error = 1; + break; + } + + // Hit the end instead of a value? + if ( token[0] == '}' ) + { + *error = 1; + break; + } + + // Assign k/v pair + Ricochet_SetKeyValue( pad, key, token ); + } + + // Return what's left in the stream + return buffer; +} + +/* +============================== +Ricochet_ProcessEnts + +Parse through entity lump looking for pads or targets +============================== +*/ +void Ricochet_ProcessEnts( char *buffer ) +{ + int i; + char token[ 1024 ]; + ric_pad_t *pad = NULL; + int error = 0; + + // parse entities from entity lump of .bsp file + while (1) + { + // parse the opening brace + buffer = gEngfuncs.COM_ParseFile ( buffer, token ); + if (!buffer) + break; + + // Didn't find opening brace? + if ( token[0] != '{' ) + { + gEngfuncs.Con_Printf ("Ricochet_ProcessEnts: found %s when expecting {\n", token ); + return; + } + + // Assume we're filling in this pad + pad = &s_pads[ s_num_pads ]; + + // Fill in data + buffer = Ricochet_ParsePad( buffer, pad, &error ); + + // Check for errors and abort if any + if ( error ) + { + gEngfuncs.Con_Printf ("Ricochet_ProcessEnts: error parsing entities\n" ); + return; + } + + // Check classname + if ( stricmp( pad->classname, "trigger_jump" ) && stricmp( pad->classname, "info_target" ) ) + continue; + + // Set type based on classname + if ( !stricmp( pad->classname, "trigger_jump" ) ) + { + pad->type = RIC_PAD; + } + else + { + pad->type = RIC_TARGET; + } + + // Load up the model + pad->model = gEngfuncs.CL_LoadModel( pad->modelname, NULL ); + if ( pad->model ) + { + // Fill in abs bbox + for ( i = 0; i < 3; i++ ) + { + pad->absmin[ i ] = pad->model->mins[ i ] - 1.0; + pad->absmax[ i ] = pad->model->maxs[ i ] + 1.0; + } + } + + // If we got to here, we're using the entity + s_num_pads++; + + // No more room... + if ( s_num_pads >= MAX_PADS ) + break; + } +} + +/* +============================== +Ricochet_LoadEntityLump + +Open the .bsp and read in the entity lump +============================== +*/ +char *Ricochet_LoadEntityLump( const char *filename ) +{ + int i; + dheader_t header; + int size = 0; + lump_t *curLump; + char *fileBuffer = NULL, *buffer, *entlump; + + fileBuffer = (char *)gEngfuncs.COM_LoadFile((char *)filename, 5, &size); + if (size < sizeof(dheader_t)) + return NULL; + + // Read in the .bsp header + memcpy(&header, fileBuffer, sizeof(dheader_t)); + + // Check the version + i = header.version; + if ( i != 29 && i != 30) + { + gEngfuncs.COM_FreeFile(fileBuffer); + gEngfuncs.Con_Printf("Ricochet_LoadEntityLump: Map [%s] has incorrect BSP version (%i should be %i).\n", filename, i, BSPVERSION); + return NULL; + } + + // Get entity lump + curLump = &header.lumps[ LUMP_ENTITIES ]; + // and entity lump size + size = curLump->filelen; + + // Jump to it + entlump = fileBuffer + curLump->fileofs; + + // Allocate sufficient memmory + buffer = (char *)malloc( size + 1 ); + if ( !buffer ) + { + gEngfuncs.COM_FreeFile(fileBuffer); + gEngfuncs.Con_Printf("Ricochet_LoadEntityLump: Couldn't allocate %i bytes\n", size + 1 ); + return NULL; + } + + // Read in the entity lump + memcpy( buffer, entlump, size ); + + // Terminate the string + buffer[ size ] = '\0'; + + if (fileBuffer) + { + gEngfuncs.COM_FreeFile(fileBuffer); + } + + return buffer; +} + +/* +============================== +Ricochet_LoadJumpPads + +Load in the .bsp file and process the entities +============================== +*/ +void Ricochet_LoadJumpPads( const char *map ) +{ + char *buffer = NULL; + char filename[ 256 ]; + + sprintf( filename, "%s/%s", gEngfuncs.pfnGetGameDirectory(), map ); + + // TODO: Fix Slashes? + + // Reset count + s_num_pads = 0; + + // Load entity lump + buffer = Ricochet_LoadEntityLump( filename ); + if ( !buffer ) + return; + + // Process buffer and extract pads/targets + Ricochet_ProcessEnts( buffer ); + + // Discard buffer + free( buffer ); +} + +/* +============================== +Ricochet_FindTarget + +Search entity list for target matching "name" +============================== +*/ +ric_pad_t *Ricochet_FindTarget( const char *name, int numpads, ric_pad_t *pads ) +{ + int i; + ric_pad_t *target; + + // Find the target + for ( i = 0; i < numpads; i++ ) + { + target = &pads[ i ]; + if ( !target ) + continue; + + if ( stricmp( target->targetname, name ) ) + continue; + + return target; + } + + return NULL; +} + +/* +============================== +Ricochet_PadTouched + +Register impact ( impart velocity on player and if final function call, play appropriate jump sound ) +============================== +*/ +void Ricochet_PadTouched( int numpads, ric_pad_t *pads, ric_pad_t *pad, struct local_state_s *player ) +{ + int i; + ric_pad_t *target; + float origin[ 3 ]; + pmtrace_t tr; + float flGravity = pmove->movevars->gravity; + + float vecMidPoint[3]; + float end[ 3 ]; + + // Ricochet jump pads use default jump height + float flHeight = 150; + + float zero[ 3 ] = { 0.0, 0.0, 0.0 }; + + // Find the target + target = Ricochet_FindTarget( pad->target, numpads, pads ); + + // Target now points to target pad + for ( i = 0; i < 3; i++ ) + { + origin[ i ] = player->playerstate.origin[ i ]; + + // Get a rough idea of how high to launch + vecMidPoint[ i ] = origin[ i ] + ( target->origin[ i ] - origin[ i ]) * 0.5; + end[ i ] = vecMidPoint[ i ]; + } + + if ( pad->height != 0.0 ) + { + flHeight = pad->height; + } + + // Move up by height + end[ 2 ] += flHeight; + + // See if we can reach the apex from the midpoint + gEngfuncs.pEventAPI->EV_PlayerTrace( vecMidPoint, end, PM_STUDIO_BOX, -1, &tr ); + + // Use the end point of the trace as the midpoint of the actual toss + for ( i = 0; i < 3; i++ ) + { + vecMidPoint[i] = tr.endpos[i]; + } + + // Subtract some units so we don't hit the ceiling) + vecMidPoint[2] -= 15; + + // How high should we travel to reach the apex + float distance1 = fabs(vecMidPoint[2] - origin[2]); + float distance2 = fabs(vecMidPoint[2] - target->origin[2]); + + // How long will it take to travel this distance + float time1 = sqrt( distance1 / (0.5 * flGravity) ); + float time2 = sqrt( distance2 / (0.5 * flGravity) ); + if (time1 < 0.1) + return; + + // Determine how hard to launch to get there in time. + float vecTargetVel[3]; + + for ( i = 0; i < 3; i++ ) + { + vecTargetVel[ i ] = (target->origin[ i ] - origin[ i ]) / (time1 + time2); + } + + // Adjust upward velocity needed + vecTargetVel[ 2 ] = flGravity * time1; + + // Fill in needed velocity + for ( i = 0; i < 3; i++ ) + { + player->client.velocity[i] = vecTargetVel[i]; + } + + // Play sound if appropriate + if ( s_usJump && g_runfuncs ) + { + gEngfuncs.pfnPlaybackEvent( FEV_NOTHOST, NULL, s_usJump, 0.0, zero, zero, 0.0, 0.0, 0, 0, 0, 0 ); + } +} + +/* +============================== +Ricochet_TouchPads + +See if player's resting position impacts any jump pads +============================== +*/ +void Ricochet_TouchPads ( struct local_state_s *player, ric_pad_t *pads, int numpads ) +{ + int i, j; + ric_pad_t *pad; + float absmin[3], absmax[3]; + physent_t pe; + hull_t *hull; + int num; + float test[3]; + float pmins[ 3 ] = { 16, 16, 36 }; + + // Determine player's bbox + for ( j = 0; j < 3; j++ ) + { + absmin[ j ] = player->playerstate.origin[ j ] - pmins[ j ]; + absmax[ j ] = player->playerstate.origin[ j ] + pmins[ j ]; + } + + // Cycle through pads looking for a match + for ( i = 0; i < numpads; i++ ) + { + pad = &pads[ i ]; + if ( !pad ) + continue; + + // Target entities don't make us jump + if ( pad->type != RIC_PAD ) + continue; + + // Trivial reject? + if ( absmin[0] > pad->absmax[0] + || absmin[1] > pad->absmax[1] + || absmin[2] > pad->absmax[2] + || absmax[0] < pad->absmin[0] + || absmax[1] < pad->absmin[1] + || absmax[2] < pad->absmin[2] ) + continue; + + // Set up physent for the test case + pe.model = pad->model; + pe.origin = pad->origin; + + // Use standing player hull + pmove->usehull = 0; + + // Make sure it's a brush model, of course + if ( !pe.model || (modtype_t)pmove->PM_GetModelType( pe.model ) != mod_brush ) + continue; + + // Get the hull + hull = (hull_t *)pmove->PM_HullForBsp( &pe, test ); + num = hull->firstclipnode; + + // Offset the origin by the offset appropriate for this hull. + for ( j = 0; j < 3; j++ ) + { + test[ j ] = player->playerstate.origin[ j ] - test[ j ]; + } + + // Test the player's hull for intersection with this model + if ( pmove->PM_HullPointContents ( hull, num, test ) != CONTENTS_SOLID ) + { + continue; + } + + // TOUCHED!!! + Ricochet_PadTouched( numpads, pads, pad, player ); + + // Only touch one pad at a time + break; + } +} + +/* +============================== +Ricochet_CheckJumpPads + +Load data if needed, otherwise just run checks on player's final position to see if jump pad needs + to impart velocity on the player. +============================== +*/ +void Ricochet_CheckJumpPads( struct local_state_s *from, struct local_state_s *to ) +{ + static char current_level[ 128 ]; + + // See if we've changed to a new map + if ( stricmp( current_level, gEngfuncs.pfnGetLevelName() ) ) + { + strcpy( current_level, gEngfuncs.pfnGetLevelName() ); + Ricochet_LoadJumpPads( current_level ); + + // Grab sound event + s_usJump = gEngfuncs.pfnPrecacheEvent( 1, "events/jump.sc" ); + } + + // Not while spectating + if ( to->client.iuser1 ) + return; + + // Run test + Ricochet_TouchPads( to, s_pads, s_num_pads ); +} \ No newline at end of file diff --git a/ricochet/cl_dll/Ricochet_JumpPads.h b/ricochet/cl_dll/Ricochet_JumpPads.h new file mode 100644 index 0000000..7243df9 --- /dev/null +++ b/ricochet/cl_dll/Ricochet_JumpPads.h @@ -0,0 +1,9 @@ +#if !defined( RICHOCHET_JUMPADS_H ) +#define RICHOCHET_JUMPADS_H +#ifdef _WIN32 +#pragma once +#endif + +void Ricochet_CheckJumpPads( struct local_state_s *from, struct local_state_s *to ); + +#endif // RICHOCHET_JUMPADS_H \ No newline at end of file diff --git a/ricochet/cl_dll/StudioModelRenderer.cpp b/ricochet/cl_dll/StudioModelRenderer.cpp new file mode 100644 index 0000000..d346723 --- /dev/null +++ b/ricochet/cl_dll/StudioModelRenderer.cpp @@ -0,0 +1,1607 @@ +// studio_model.cpp +// routines for setting up to draw 3DStudio models + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include "studio.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "dlight.h" +#include "triangleapi.h" + +#include +#include +#include +#include + +#include "studio_util.h" +#include "r_studioint.h" + +#include "StudioModelRenderer.h" +#include "GameStudioModelRenderer.h" + +// Global engine <-> studio model rendering code interface +engine_studio_api_t IEngineStudio; + +///////////////////// +// Implementation of CStudioModelRenderer.h + +/* +==================== +Init + +==================== +*/ +void CStudioModelRenderer::Init( void ) +{ + // Set up some variables shared with engine + m_pCvarHiModels = IEngineStudio.GetCvar( "cl_himodels" ); + m_pCvarDeveloper = IEngineStudio.GetCvar( "developer" ); + m_pCvarDrawEntities = IEngineStudio.GetCvar( "r_drawentities" ); + + m_pChromeSprite = IEngineStudio.GetChromeSprite(); + + IEngineStudio.GetModelCounters( &m_pStudioModelCount, &m_pModelsDrawn ); + + // Get pointers to engine data structures + m_pbonetransform = (float (*)[MAXSTUDIOBONES][3][4])IEngineStudio.StudioGetBoneTransform(); + m_plighttransform = (float (*)[MAXSTUDIOBONES][3][4])IEngineStudio.StudioGetLightTransform(); + m_paliastransform = (float (*)[3][4])IEngineStudio.StudioGetAliasTransform(); + m_protationmatrix = (float (*)[3][4])IEngineStudio.StudioGetRotationMatrix(); +} + +/* +==================== +CStudioModelRenderer + +==================== +*/ +CStudioModelRenderer::CStudioModelRenderer( void ) +{ + m_fDoInterp = 1; + m_fGaitEstimation = 1; + m_pCurrentEntity = NULL; + m_pCvarHiModels = NULL; + m_pCvarDeveloper = NULL; + m_pCvarDrawEntities = NULL; + m_pChromeSprite = NULL; + m_pStudioModelCount = NULL; + m_pModelsDrawn = NULL; + m_protationmatrix = NULL; + m_paliastransform = NULL; + m_pbonetransform = NULL; + m_plighttransform = NULL; + m_pStudioHeader = NULL; + m_pBodyPart = NULL; + m_pSubModel = NULL; + m_pPlayerInfo = NULL; + m_pRenderModel = NULL; +} + +/* +==================== +~CStudioModelRenderer + +==================== +*/ +CStudioModelRenderer::~CStudioModelRenderer( void ) +{ +} + +/* +==================== +StudioCalcBoneAdj + +==================== +*/ +void CStudioModelRenderer::StudioCalcBoneAdj( float dadt, float *adj, const byte *pcontroller1, const byte *pcontroller2, byte mouthopen ) +{ + int i, j; + float value; + mstudiobonecontroller_t *pbonecontroller; + + pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex); + + for (j = 0; j < m_pStudioHeader->numbonecontrollers; j++) + { + i = pbonecontroller[j].index; + if (i <= 3) + { + // check for 360% wrapping + if (pbonecontroller[j].type & STUDIO_RLOOP) + { + if (abs(pcontroller1[i] - pcontroller2[i]) > 128) + { + int a, b; + a = (pcontroller1[j] + 128) % 256; + b = (pcontroller2[j] + 128) % 256; + value = ((a * dadt) + (b * (1 - dadt)) - 128) * (360.0/256.0) + pbonecontroller[j].start; + } + else + { + value = ((pcontroller1[i] * dadt + (pcontroller2[i]) * (1.0 - dadt))) * (360.0/256.0) + pbonecontroller[j].start; + } + } + else + { + value = (pcontroller1[i] * dadt + pcontroller2[i] * (1.0 - dadt)) / 255.0; + if (value < 0) value = 0; + if (value > 1.0) value = 1.0; + value = (1.0 - value) * pbonecontroller[j].start + value * pbonecontroller[j].end; + } + } + else + { + value = mouthopen / 64.0; + if (value > 1.0) value = 1.0; + value = (1.0 - value) * pbonecontroller[j].start + value * pbonecontroller[j].end; + } + switch(pbonecontroller[j].type & STUDIO_TYPES) + { + case STUDIO_XR: + case STUDIO_YR: + case STUDIO_ZR: + adj[j] = value * (M_PI / 180.0); + break; + case STUDIO_X: + case STUDIO_Y: + case STUDIO_Z: + adj[j] = value; + break; + } + } +} + + +/* +==================== +StudioCalcBoneQuaterion + +==================== +*/ +void CStudioModelRenderer::StudioCalcBoneQuaterion( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *q ) +{ + int j, k; + vec4_t q1, q2; + vec3_t angle1, angle2; + mstudioanimvalue_t *panimvalue; + + for (j = 0; j < 3; j++) + { + if (panim->offset[j+3] == 0) + { + angle2[j] = angle1[j] = pbone->value[j+3]; // default; + } + else + { + panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[j+3]); + k = frame; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + while (panimvalue->num.total <= k) + { + k -= panimvalue->num.total; + panimvalue += panimvalue->num.valid + 1; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + } + // Bah, missing blend! + if (panimvalue->num.valid > k) + { + angle1[j] = panimvalue[k+1].value; + + if (panimvalue->num.valid > k + 1) + { + angle2[j] = panimvalue[k+2].value; + } + else + { + if (panimvalue->num.total > k + 1) + angle2[j] = angle1[j]; + else + angle2[j] = panimvalue[panimvalue->num.valid+2].value; + } + } + else + { + angle1[j] = panimvalue[panimvalue->num.valid].value; + if (panimvalue->num.total > k + 1) + { + angle2[j] = angle1[j]; + } + else + { + angle2[j] = panimvalue[panimvalue->num.valid + 2].value; + } + } + angle1[j] = pbone->value[j+3] + angle1[j] * pbone->scale[j+3]; + angle2[j] = pbone->value[j+3] + angle2[j] * pbone->scale[j+3]; + } + + if (pbone->bonecontroller[j+3] != -1) + { + angle1[j] += adj[pbone->bonecontroller[j+3]]; + angle2[j] += adj[pbone->bonecontroller[j+3]]; + } + } + + if (!VectorCompare( angle1, angle2 )) + { + AngleQuaternion( angle1, q1 ); + AngleQuaternion( angle2, q2 ); + QuaternionSlerp( q1, q2, s, q ); + } + else + { + AngleQuaternion( angle1, q ); + } +} + +/* +==================== +StudioCalcBonePosition + +==================== +*/ +void CStudioModelRenderer::StudioCalcBonePosition( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *pos ) +{ + int j, k; + mstudioanimvalue_t *panimvalue; + + for (j = 0; j < 3; j++) + { + pos[j] = pbone->value[j]; // default; + if (panim->offset[j] != 0) + { + panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[j]); + /* + if (i == 0 && j == 0) + Con_DPrintf("%d %d:%d %f\n", frame, panimvalue->num.valid, panimvalue->num.total, s ); + */ + + k = frame; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + // find span of values that includes the frame we want + while (panimvalue->num.total <= k) + { + k -= panimvalue->num.total; + panimvalue += panimvalue->num.valid + 1; + // DEBUG + if (panimvalue->num.total < panimvalue->num.valid) + k = 0; + } + // if we're inside the span + if (panimvalue->num.valid > k) + { + // and there's more data in the span + if (panimvalue->num.valid > k + 1) + { + pos[j] += (panimvalue[k+1].value * (1.0 - s) + s * panimvalue[k+2].value) * pbone->scale[j]; + } + else + { + pos[j] += panimvalue[k+1].value * pbone->scale[j]; + } + } + else + { + // are we at the end of the repeating values section and there's another section with data? + if (panimvalue->num.total <= k + 1) + { + pos[j] += (panimvalue[panimvalue->num.valid].value * (1.0 - s) + s * panimvalue[panimvalue->num.valid + 2].value) * pbone->scale[j]; + } + else + { + pos[j] += panimvalue[panimvalue->num.valid].value * pbone->scale[j]; + } + } + } + if ( pbone->bonecontroller[j] != -1 && adj ) + { + pos[j] += adj[pbone->bonecontroller[j]]; + } + } +} + +/* +==================== +StudioSlerpBones + +==================== +*/ +void CStudioModelRenderer::StudioSlerpBones( vec4_t q1[], float pos1[][3], vec4_t q2[], float pos2[][3], float s ) +{ + int i; + vec4_t q3; + float s1; + + if (s < 0) s = 0; + else if (s > 1.0) s = 1.0; + + s1 = 1.0 - s; + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + QuaternionSlerp( q1[i], q2[i], s, q3 ); + q1[i][0] = q3[0]; + q1[i][1] = q3[1]; + q1[i][2] = q3[2]; + q1[i][3] = q3[3]; + pos1[i][0] = pos1[i][0] * s1 + pos2[i][0] * s; + pos1[i][1] = pos1[i][1] * s1 + pos2[i][1] * s; + pos1[i][2] = pos1[i][2] * s1 + pos2[i][2] * s; + } +} + +/* +==================== +StudioGetAnim + +==================== +*/ +mstudioanim_t *CStudioModelRenderer::StudioGetAnim( model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc ) +{ + mstudioseqgroup_t *pseqgroup; + cache_user_t *paSequences; + + pseqgroup = (mstudioseqgroup_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqgroupindex) + pseqdesc->seqgroup; + + if (pseqdesc->seqgroup == 0) + { + return (mstudioanim_t *)((byte *)m_pStudioHeader + pseqdesc->animindex); + } + + paSequences = (cache_user_t *)m_pSubModel->submodels; + + if (paSequences == NULL) + { + paSequences = (cache_user_t *)IEngineStudio.Mem_Calloc( 16, sizeof( cache_user_t ) ); // UNDONE: leak! + m_pSubModel->submodels = (dmodel_t *)paSequences; + } + + if (!IEngineStudio.Cache_Check( (struct cache_user_s *)&(paSequences[pseqdesc->seqgroup]))) + { + gEngfuncs.Con_DPrintf("loading %s\n", pseqgroup->name ); + IEngineStudio.LoadCacheFile( pseqgroup->name, (struct cache_user_s *)&paSequences[pseqdesc->seqgroup] ); + } + return (mstudioanim_t *)((byte *)paSequences[pseqdesc->seqgroup].data + pseqdesc->animindex); +} + +/* +==================== +StudioPlayerBlend + +==================== +*/ +void CStudioModelRenderer::StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ) +{ + // calc up/down pointing + *pBlend = (*pPitch * 3); + if (*pBlend < pseqdesc->blendstart[0]) + { + *pPitch -= pseqdesc->blendstart[0] / 3.0; + *pBlend = 0; + } + else if (*pBlend > pseqdesc->blendend[0]) + { + *pPitch -= pseqdesc->blendend[0] / 3.0; + *pBlend = 255; + } + else + { + if (pseqdesc->blendend[0] - pseqdesc->blendstart[0] < 0.1) // catch qc error + *pBlend = 127; + else + *pBlend = 255 * (*pBlend - pseqdesc->blendstart[0]) / (pseqdesc->blendend[0] - pseqdesc->blendstart[0]); + *pPitch = 0; + } +} + +/* +==================== +StudioSetUpTransform + +==================== +*/ +void CStudioModelRenderer::StudioSetUpTransform (int trivial_accept) +{ + int i; + vec3_t angles; + vec3_t modelpos; + + VectorCopy( m_pCurrentEntity->origin, modelpos ); + +// TODO: should really be stored with the entity instead of being reconstructed +// TODO: should use a look-up table +// TODO: could cache lazily, stored in the entity + angles[ROLL] = m_pCurrentEntity->curstate.angles[ROLL]; + angles[PITCH] = m_pCurrentEntity->curstate.angles[PITCH]; + angles[YAW] = m_pCurrentEntity->curstate.angles[YAW]; + + if (m_pCurrentEntity->curstate.movetype == MOVETYPE_STEP) + { + float f = 0; + float d; + + // don't do it if the goalstarttime hasn't updated in a while. + + // NOTE: Because we need to interpolate multiplayer characters, the interpolation time limit + // was increased to 1.0 s., which is 2x the max lag we are accounting for. + + if ( ( m_clTime < m_pCurrentEntity->curstate.animtime + 1.0f ) && + ( m_pCurrentEntity->curstate.animtime != m_pCurrentEntity->latched.prevanimtime ) ) + { + f = (m_clTime - m_pCurrentEntity->curstate.animtime) / (m_pCurrentEntity->curstate.animtime - m_pCurrentEntity->latched.prevanimtime); + } + + if (m_fDoInterp) + { + // ugly hack to interpolate angle, position. current is reached 0.1 seconds after being set + f = f - 1.0; + } + else + { + f = 0; + } + + for (i = 0; i < 3; i++) + { + modelpos[i] += (m_pCurrentEntity->origin[i] - m_pCurrentEntity->latched.prevorigin[i]) * f; + } + + for (i = 0; i < 3; i++) + { + float ang1, ang2; + + ang1 = m_pCurrentEntity->angles[i]; + ang2 = m_pCurrentEntity->latched.prevangles[i]; + + d = ang1 - ang2; + if (d > 180) + { + d -= 360; + } + else if (d < -180) + { + d += 360; + } + + angles[i] += d * f; + } + } + else if ( m_pCurrentEntity->curstate.movetype != MOVETYPE_NONE ) + { + VectorCopy( m_pCurrentEntity->angles, angles ); + } + + angles[PITCH] = -angles[PITCH]; + AngleMatrix (angles, (*m_protationmatrix)); + + if ( !IEngineStudio.IsHardware() ) + { + static float viewmatrix[3][4]; + + VectorCopy (m_vRight, viewmatrix[0]); + VectorCopy (m_vUp, viewmatrix[1]); + VectorInverse (viewmatrix[1]); + VectorCopy (m_vNormal, viewmatrix[2]); + + (*m_protationmatrix)[0][3] = modelpos[0] - m_vRenderOrigin[0]; + (*m_protationmatrix)[1][3] = modelpos[1] - m_vRenderOrigin[1]; + (*m_protationmatrix)[2][3] = modelpos[2] - m_vRenderOrigin[2]; + + ConcatTransforms (viewmatrix, (*m_protationmatrix), (*m_paliastransform)); + + // do the scaling up of x and y to screen coordinates as part of the transform + // for the unclipped case (it would mess up clipping in the clipped case). + // Also scale down z, so 1/z is scaled 31 bits for free, and scale down x and y + // correspondingly so the projected x and y come out right + // FIXME: make this work for clipped case too? + if (trivial_accept) + { + for (i=0 ; i<4 ; i++) + { + (*m_paliastransform)[0][i] *= m_fSoftwareXScale * + (1.0 / (ZISCALE * 0x10000)); + (*m_paliastransform)[1][i] *= m_fSoftwareYScale * + (1.0 / (ZISCALE * 0x10000)); + (*m_paliastransform)[2][i] *= 1.0 / (ZISCALE * 0x10000); + + } + } + } + + (*m_protationmatrix)[0][3] = modelpos[0]; + (*m_protationmatrix)[1][3] = modelpos[1]; + (*m_protationmatrix)[2][3] = modelpos[2]; +} + + +/* +==================== +StudioEstimateInterpolant + +==================== +*/ +float CStudioModelRenderer::StudioEstimateInterpolant( void ) +{ + float dadt = 1.0; + + if ( m_fDoInterp && ( m_pCurrentEntity->curstate.animtime >= m_pCurrentEntity->latched.prevanimtime + 0.01 ) ) + { + dadt = (m_clTime - m_pCurrentEntity->curstate.animtime) / 0.1; + if (dadt > 2.0) + { + dadt = 2.0; + } + } + return dadt; +} + +/* +==================== +StudioCalcRotations + +==================== +*/ +void CStudioModelRenderer::StudioCalcRotations ( float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f ) +{ + int i; + int frame; + mstudiobone_t *pbone; + + float s; + float adj[MAXSTUDIOCONTROLLERS]; + float dadt; + + if (f > pseqdesc->numframes - 1) + { + f = 0; // bah, fix this bug with changing sequences too fast + } + // BUG ( somewhere else ) but this code should validate this data. + // This could cause a crash if the frame # is negative, so we'll go ahead + // and clamp it here + else if ( f < -0.01 ) + { + f = -0.01; + } + + frame = (int)f; + + dadt = StudioEstimateInterpolant( ); + s = (f - frame); + + // add in programtic controllers + pbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + StudioCalcBoneAdj( dadt, adj, m_pCurrentEntity->curstate.controller, m_pCurrentEntity->latched.prevcontroller, m_pCurrentEntity->mouth.mouthopen ); + + for (i = 0; i < m_pStudioHeader->numbones; i++, pbone++, panim++) + { + StudioCalcBoneQuaterion( frame, s, pbone, panim, adj, q[i] ); + + StudioCalcBonePosition( frame, s, pbone, panim, adj, pos[i] ); + } + + if (pseqdesc->motiontype & STUDIO_X) + { + pos[pseqdesc->motionbone][0] = 0.0; + } + if (pseqdesc->motiontype & STUDIO_Y) + { + pos[pseqdesc->motionbone][1] = 0.0; + } + if (pseqdesc->motiontype & STUDIO_Z) + { + pos[pseqdesc->motionbone][2] = 0.0; + } + + s = 0 * ((1.0 - (f - (int)(f))) / (pseqdesc->numframes)) * m_pCurrentEntity->curstate.framerate; + + if (pseqdesc->motiontype & STUDIO_LX) + { + pos[pseqdesc->motionbone][0] += s * pseqdesc->linearmovement[0]; + } + if (pseqdesc->motiontype & STUDIO_LY) + { + pos[pseqdesc->motionbone][1] += s * pseqdesc->linearmovement[1]; + } + if (pseqdesc->motiontype & STUDIO_LZ) + { + pos[pseqdesc->motionbone][2] += s * pseqdesc->linearmovement[2]; + } +} + +/* +==================== +Studio_FxTransform + +==================== +*/ +void CStudioModelRenderer::StudioFxTransform( cl_entity_t *ent, float transform[3][4] ) +{ + switch( ent->curstate.renderfx ) + { + case kRenderFxDistort: + case kRenderFxHologram: + if ( gEngfuncs.pfnRandomLong(0,49) == 0 ) + { + int axis = gEngfuncs.pfnRandomLong(0,1); + if ( axis == 1 ) // Choose between x & z + axis = 2; + VectorScale( transform[axis], gEngfuncs.pfnRandomFloat(1,1.484), transform[axis] ); + } + else if ( gEngfuncs.pfnRandomLong(0,49) == 0 ) + { + float offset; + int axis = gEngfuncs.pfnRandomLong(0,1); + if ( axis == 1 ) // Choose between x & z + axis = 2; + offset = gEngfuncs.pfnRandomFloat(-10,10); + transform[gEngfuncs.pfnRandomLong(0,2)][3] += offset; + } + break; + case kRenderFxExplode: + { + float scale; + + scale = 1.0 + ( m_clTime - ent->curstate.animtime) * 10.0; + if ( scale > 2 ) // Don't blow up more than 200% + scale = 2; + transform[0][1] *= scale; + transform[1][1] *= scale; + transform[2][1] *= scale; + } + break; + + } +} + +/* +==================== +StudioEstimateFrame + +==================== +*/ +float CStudioModelRenderer::StudioEstimateFrame( mstudioseqdesc_t *pseqdesc ) +{ + double dfdt, f; + + if ( m_fDoInterp ) + { + if ( m_clTime < m_pCurrentEntity->curstate.animtime ) + { + dfdt = 0; + } + else + { + dfdt = (m_clTime - m_pCurrentEntity->curstate.animtime) * m_pCurrentEntity->curstate.framerate * pseqdesc->fps; + + } + } + else + { + dfdt = 0; + } + + if (pseqdesc->numframes <= 1) + { + f = 0; + } + else + { + f = (m_pCurrentEntity->curstate.frame * (pseqdesc->numframes - 1)) / 256.0; + } + + f += dfdt; + + if (pseqdesc->flags & STUDIO_LOOPING) + { + if (pseqdesc->numframes > 1) + { + f -= (int)(f / (pseqdesc->numframes - 1)) * (pseqdesc->numframes - 1); + } + if (f < 0) + { + f += (pseqdesc->numframes - 1); + } + } + else + { + if (f >= pseqdesc->numframes - 1.001) + { + f = pseqdesc->numframes - 1.001; + } + if (f < 0.0) + { + f = 0.0; + } + } + return f; +} + +/* +==================== +StudioSetupBones + +==================== +*/ +void CStudioModelRenderer::StudioSetupBones ( void ) +{ + int i; + double f; + + mstudiobone_t *pbones; + mstudioseqdesc_t *pseqdesc; + mstudioanim_t *panim; + + static float pos[MAXSTUDIOBONES][3]; + static vec4_t q[MAXSTUDIOBONES]; + float bonematrix[3][4]; + + static float pos2[MAXSTUDIOBONES][3]; + static vec4_t q2[MAXSTUDIOBONES]; + static float pos3[MAXSTUDIOBONES][3]; + static vec4_t q3[MAXSTUDIOBONES]; + static float pos4[MAXSTUDIOBONES][3]; + static vec4_t q4[MAXSTUDIOBONES]; + + if (m_pCurrentEntity->curstate.sequence >= m_pStudioHeader->numseq) + { + m_pCurrentEntity->curstate.sequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + f = StudioEstimateFrame( pseqdesc ); + + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + + if (pseqdesc->numblends > 1) + { + float s; + float dadt; + + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos2, q2, pseqdesc, panim, f ); + + dadt = StudioEstimateInterpolant(); + s = (m_pCurrentEntity->curstate.blending[0] * dadt + m_pCurrentEntity->latched.prevblending[0] * (1.0 - dadt)) / 255.0; + + StudioSlerpBones( q, pos, q2, pos2, s ); + + if (pseqdesc->numblends == 4) + { + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos3, q3, pseqdesc, panim, f ); + + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos4, q4, pseqdesc, panim, f ); + + s = (m_pCurrentEntity->curstate.blending[0] * dadt + m_pCurrentEntity->latched.prevblending[0] * (1.0 - dadt)) / 255.0; + StudioSlerpBones( q3, pos3, q4, pos4, s ); + + s = (m_pCurrentEntity->curstate.blending[1] * dadt + m_pCurrentEntity->latched.prevblending[1] * (1.0 - dadt)) / 255.0; + StudioSlerpBones( q, pos, q3, pos3, s ); + } + } + + if (m_fDoInterp && + m_pCurrentEntity->latched.sequencetime && + ( m_pCurrentEntity->latched.sequencetime + 0.2 > m_clTime ) && + ( m_pCurrentEntity->latched.prevsequence < m_pStudioHeader->numseq )) + { + // blend from last sequence + static float pos1b[MAXSTUDIOBONES][3]; + static vec4_t q1b[MAXSTUDIOBONES]; + float s; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->latched.prevsequence; + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + // clip prevframe + StudioCalcRotations( pos1b, q1b, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + if (pseqdesc->numblends > 1) + { + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos2, q2, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + s = (m_pCurrentEntity->latched.prevseqblending[0]) / 255.0; + StudioSlerpBones( q1b, pos1b, q2, pos2, s ); + + if (pseqdesc->numblends == 4) + { + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos3, q3, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + panim += m_pStudioHeader->numbones; + StudioCalcRotations( pos4, q4, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); + + s = (m_pCurrentEntity->latched.prevseqblending[0]) / 255.0; + StudioSlerpBones( q3, pos3, q4, pos4, s ); + + s = (m_pCurrentEntity->latched.prevseqblending[1]) / 255.0; + StudioSlerpBones( q1b, pos1b, q3, pos3, s ); + } + } + + s = 1.0 - (m_clTime - m_pCurrentEntity->latched.sequencetime) / 0.2; + StudioSlerpBones( q, pos, q1b, pos1b, s ); + } + else + { + m_pCurrentEntity->latched.prevframe = f; + } + + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + // calc gait animation + if (m_pPlayerInfo && m_pPlayerInfo->gaitsequence != 0) + { + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pPlayerInfo->gaitsequence; + + panim = StudioGetAnim( m_pRenderModel, pseqdesc ); + StudioCalcRotations( pos2, q2, pseqdesc, panim, m_pPlayerInfo->gaitframe ); + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + if (strcmp( pbones[i].name, "Bip01 Spine") == 0) + break; + memcpy( pos[i], pos2[i], sizeof( pos[i] )); + memcpy( q[i], q2[i], sizeof( q[i] )); + } + } + + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + QuaternionMatrix( q[i], bonematrix ); + + bonematrix[0][3] = pos[i][0]; + bonematrix[1][3] = pos[i][1]; + bonematrix[2][3] = pos[i][2]; + + if (pbones[i].parent == -1) + { + if ( IEngineStudio.IsHardware() ) + { + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_pbonetransform)[i]); + + // MatrixCopy should be faster... + //ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + MatrixCopy( (*m_pbonetransform)[i], (*m_plighttransform)[i] ); + } + else + { + ConcatTransforms ((*m_paliastransform), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + } + + // Apply client-side effects to the transformation matrix + StudioFxTransform( m_pCurrentEntity, (*m_pbonetransform)[i] ); + } + else + { + ConcatTransforms ((*m_pbonetransform)[pbones[i].parent], bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_plighttransform)[pbones[i].parent], bonematrix, (*m_plighttransform)[i]); + } + } +} + + +/* +==================== +StudioSaveBones + +==================== +*/ +void CStudioModelRenderer::StudioSaveBones( void ) +{ + int i; + + mstudiobone_t *pbones; + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + m_nCachedBones = m_pStudioHeader->numbones; + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + strcpy( m_nCachedBoneNames[i], pbones[i].name ); + MatrixCopy( (*m_pbonetransform)[i], m_rgCachedBoneTransform[i] ); + MatrixCopy( (*m_plighttransform)[i], m_rgCachedLightTransform[i] ); + } +} + + +/* +==================== +StudioMergeBones + +==================== +*/ +void CStudioModelRenderer::StudioMergeBones ( model_t *m_pSubModel ) +{ + int i, j; + double f; + int do_hunt = true; + + mstudiobone_t *pbones; + mstudioseqdesc_t *pseqdesc; + mstudioanim_t *panim; + + static float pos[MAXSTUDIOBONES][3]; + float bonematrix[3][4]; + static vec4_t q[MAXSTUDIOBONES]; + + if (m_pCurrentEntity->curstate.sequence >= m_pStudioHeader->numseq) + { + m_pCurrentEntity->curstate.sequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + f = StudioEstimateFrame( pseqdesc ); + + panim = StudioGetAnim( m_pSubModel, pseqdesc ); + StudioCalcRotations( pos, q, pseqdesc, panim, f ); + + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + + for (i = 0; i < m_pStudioHeader->numbones; i++) + { + for (j = 0; j < m_nCachedBones; j++) + { + if (stricmp(pbones[i].name, m_nCachedBoneNames[j]) == 0) + { + MatrixCopy( m_rgCachedBoneTransform[j], (*m_pbonetransform)[i] ); + MatrixCopy( m_rgCachedLightTransform[j], (*m_plighttransform)[i] ); + break; + } + } + if (j >= m_nCachedBones) + { + QuaternionMatrix( q[i], bonematrix ); + + bonematrix[0][3] = pos[i][0]; + bonematrix[1][3] = pos[i][1]; + bonematrix[2][3] = pos[i][2]; + + if (pbones[i].parent == -1) + { + if ( IEngineStudio.IsHardware() ) + { + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_pbonetransform)[i]); + + // MatrixCopy should be faster... + //ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + MatrixCopy( (*m_pbonetransform)[i], (*m_plighttransform)[i] ); + } + else + { + ConcatTransforms ((*m_paliastransform), bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + } + + // Apply client-side effects to the transformation matrix + StudioFxTransform( m_pCurrentEntity, (*m_pbonetransform)[i] ); + } + else + { + ConcatTransforms ((*m_pbonetransform)[pbones[i].parent], bonematrix, (*m_pbonetransform)[i]); + ConcatTransforms ((*m_plighttransform)[pbones[i].parent], bonematrix, (*m_plighttransform)[i]); + } + } + } +} + +/* +==================== +StudioDrawModel + +==================== +*/ +int CStudioModelRenderer::StudioDrawModel( int flags ) +{ + alight_t lighting; + vec3_t dir; + + m_pCurrentEntity = IEngineStudio.GetCurrentEntity(); + IEngineStudio.GetTimes( &m_nFrameCount, &m_clTime, &m_clOldTime ); + IEngineStudio.GetViewInfo( m_vRenderOrigin, m_vUp, m_vRight, m_vNormal ); + IEngineStudio.GetAliasScale( &m_fSoftwareXScale, &m_fSoftwareYScale ); + + if (m_pCurrentEntity->curstate.renderfx == kRenderFxDeadPlayer) + { + entity_state_t deadplayer; + + int result; + int save_interp; + + if (m_pCurrentEntity->curstate.renderamt <= 0 || m_pCurrentEntity->curstate.renderamt > gEngfuncs.GetMaxClients() ) + return 0; + + // get copy of player + deadplayer = *(IEngineStudio.GetPlayerState( m_pCurrentEntity->curstate.renderamt - 1 )); //cl.frames[cl.parsecount & CL_UPDATE_MASK].playerstate[m_pCurrentEntity->curstate.renderamt-1]; + + // clear weapon, movement state + deadplayer.number = m_pCurrentEntity->curstate.renderamt; + deadplayer.weaponmodel = 0; + deadplayer.gaitsequence = 0; + + deadplayer.movetype = MOVETYPE_NONE; + VectorCopy( m_pCurrentEntity->curstate.angles, deadplayer.angles ); + VectorCopy( m_pCurrentEntity->curstate.origin, deadplayer.origin ); + + save_interp = m_fDoInterp; + m_fDoInterp = 0; + + // draw as though it were a player + result = StudioDrawPlayer( flags, &deadplayer ); + + m_fDoInterp = save_interp; + return result; + } + + m_pRenderModel = m_pCurrentEntity->model; + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (m_pRenderModel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + IEngineStudio.SetRenderModel( m_pRenderModel ); + + StudioSetUpTransform( 0 ); + + if (flags & STUDIO_RENDER) + { + // see if the bounding box lets us trivially reject, also sets + if (!IEngineStudio.StudioCheckBBox ()) + return 0; + + (*m_pModelsDrawn)++; + (*m_pStudioModelCount)++; // render data cache cookie + + if (m_pStudioHeader->numbodyparts == 0) + return 1; + } + + if (m_pCurrentEntity->curstate.movetype == MOVETYPE_FOLLOW) + { + StudioMergeBones( m_pRenderModel ); + } + else + { + StudioSetupBones( ); + } + StudioSaveBones( ); + + if (flags & STUDIO_EVENTS) + { + StudioCalcAttachments( ); + IEngineStudio.StudioClientEvents( ); + // copy attachments into global entity array + if ( m_pCurrentEntity->index > 0 ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( m_pCurrentEntity->index ); + + memcpy( ent->attachment, m_pCurrentEntity->attachment, sizeof( vec3_t ) * 4 ); + } + } + + if (flags & STUDIO_RENDER) + { + lighting.plightvec = dir; + IEngineStudio.StudioDynamicLight(m_pCurrentEntity, &lighting ); + + IEngineStudio.StudioEntityLight( &lighting ); + + // model and frame independant + IEngineStudio.StudioSetupLighting (&lighting); + + // get remap colors + m_nTopColor = m_pCurrentEntity->curstate.colormap & 0xFF; + m_nBottomColor = (m_pCurrentEntity->curstate.colormap & 0xFF00) >> 8; + + IEngineStudio.StudioSetRemapColors( m_nTopColor, m_nBottomColor ); + + StudioRenderModel( ); + } + + return 1; +} + +/* +==================== +StudioEstimateGait + +==================== +*/ +void CStudioModelRenderer::StudioEstimateGait( entity_state_t *pplayer ) +{ + float dt; + vec3_t est_velocity; + + dt = (m_clTime - m_clOldTime); + if (dt < 0) + dt = 0; + else if (dt > 1.0) + dt = 1; + + if (dt == 0 || m_pPlayerInfo->renderframe == m_nFrameCount) + { + m_flGaitMovement = 0; + return; + } + + // VectorAdd( pplayer->velocity, pplayer->prediction_error, est_velocity ); + if ( m_fGaitEstimation ) + { + VectorSubtract( m_pCurrentEntity->origin, m_pPlayerInfo->prevgaitorigin, est_velocity ); + VectorCopy( m_pCurrentEntity->origin, m_pPlayerInfo->prevgaitorigin ); + m_flGaitMovement = Length( est_velocity ); + if (dt <= 0 || m_flGaitMovement / dt < 5) + { + m_flGaitMovement = 0; + est_velocity[0] = 0; + est_velocity[1] = 0; + } + } + else + { + VectorCopy( pplayer->velocity, est_velocity ); + m_flGaitMovement = Length( est_velocity ) * dt; + } + + if (est_velocity[1] == 0 && est_velocity[0] == 0) + { + float flYawDiff = m_pCurrentEntity->angles[YAW] - m_pPlayerInfo->gaityaw; + flYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360; + if (flYawDiff > 180) + flYawDiff -= 360; + if (flYawDiff < -180) + flYawDiff += 360; + + if (dt < 0.25) + flYawDiff *= dt * 4; + else + flYawDiff *= dt; + + m_pPlayerInfo->gaityaw += flYawDiff; + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - (int)(m_pPlayerInfo->gaityaw / 360) * 360; + + m_flGaitMovement = 0; + } + else + { + m_pPlayerInfo->gaityaw = (atan2(est_velocity[1], est_velocity[0]) * 180 / M_PI); + if (m_pPlayerInfo->gaityaw > 180) + m_pPlayerInfo->gaityaw = 180; + if (m_pPlayerInfo->gaityaw < -180) + m_pPlayerInfo->gaityaw = -180; + } + +} + +/* +==================== +StudioProcessGait + +==================== +*/ +void CStudioModelRenderer::StudioProcessGait( entity_state_t *pplayer ) +{ + mstudioseqdesc_t *pseqdesc; + float dt; + int iBlend; + float flYaw; // view direction relative to movement + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; + + StudioPlayerBlend( pseqdesc, &iBlend, &m_pCurrentEntity->angles[PITCH] ); + + m_pCurrentEntity->latched.prevangles[PITCH] = m_pCurrentEntity->angles[PITCH]; + m_pCurrentEntity->curstate.blending[0] = iBlend; + m_pCurrentEntity->latched.prevblending[0] = m_pCurrentEntity->curstate.blending[0]; + m_pCurrentEntity->latched.prevseqblending[0] = m_pCurrentEntity->curstate.blending[0]; + + dt = (m_clTime - m_clOldTime); + if (dt < 0) + dt = 0; + else if (dt > 1.0) + dt = 1; + + StudioEstimateGait( pplayer ); + + // calc side to side turning + flYaw = m_pCurrentEntity->angles[YAW] - m_pPlayerInfo->gaityaw; + flYaw = flYaw - (int)(flYaw / 360) * 360; + if (flYaw < -180) + flYaw = flYaw + 360; + if (flYaw > 180) + flYaw = flYaw - 360; + + if (flYaw > 120) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - 180; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw - 180; + } + else if (flYaw < -120) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw + 180; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw + 180; + } + + // adjust torso + m_pCurrentEntity->curstate.controller[0] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->curstate.controller[1] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->curstate.controller[2] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->curstate.controller[3] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); + m_pCurrentEntity->latched.prevcontroller[0] = m_pCurrentEntity->curstate.controller[0]; + m_pCurrentEntity->latched.prevcontroller[1] = m_pCurrentEntity->curstate.controller[1]; + m_pCurrentEntity->latched.prevcontroller[2] = m_pCurrentEntity->curstate.controller[2]; + m_pCurrentEntity->latched.prevcontroller[3] = m_pCurrentEntity->curstate.controller[3]; + + m_pCurrentEntity->angles[YAW] = m_pPlayerInfo->gaityaw; + if (m_pCurrentEntity->angles[YAW] < -0) + m_pCurrentEntity->angles[YAW] += 360; + m_pCurrentEntity->latched.prevangles[YAW] = m_pCurrentEntity->angles[YAW]; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + pplayer->gaitsequence; + + // calc gait frame + if (pseqdesc->linearmovement[0] > 0) + { + m_pPlayerInfo->gaitframe += (m_flGaitMovement / pseqdesc->linearmovement[0]) * pseqdesc->numframes; + } + else + { + m_pPlayerInfo->gaitframe += pseqdesc->fps * dt; + } + + // do modulo + m_pPlayerInfo->gaitframe = m_pPlayerInfo->gaitframe - (int)(m_pPlayerInfo->gaitframe / pseqdesc->numframes) * pseqdesc->numframes; + if (m_pPlayerInfo->gaitframe < 0) + m_pPlayerInfo->gaitframe += pseqdesc->numframes; +} + +/* +==================== +StudioDrawPlayer + +==================== +*/ +int CStudioModelRenderer::StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + alight_t lighting; + vec3_t dir; + + m_pCurrentEntity = IEngineStudio.GetCurrentEntity(); + IEngineStudio.GetTimes( &m_nFrameCount, &m_clTime, &m_clOldTime ); + IEngineStudio.GetViewInfo( m_vRenderOrigin, m_vUp, m_vRight, m_vNormal ); + IEngineStudio.GetAliasScale( &m_fSoftwareXScale, &m_fSoftwareYScale ); + + m_nPlayerIndex = pplayer->number - 1; + + if (m_nPlayerIndex < 0 || m_nPlayerIndex >= gEngfuncs.GetMaxClients()) + return 0; + + m_pRenderModel = IEngineStudio.SetupPlayerModel( m_nPlayerIndex ); + if (m_pRenderModel == NULL) + return 0; + + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (m_pRenderModel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + IEngineStudio.SetRenderModel( m_pRenderModel ); + + if (pplayer->gaitsequence) + { + vec3_t orig_angles; + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + + VectorCopy( m_pCurrentEntity->angles, orig_angles ); + + StudioProcessGait( pplayer ); + + m_pPlayerInfo->gaitsequence = pplayer->gaitsequence; + m_pPlayerInfo = NULL; + + StudioSetUpTransform( 0 ); + VectorCopy( orig_angles, m_pCurrentEntity->angles ); + } + else + { + m_pCurrentEntity->curstate.controller[0] = 127; + m_pCurrentEntity->curstate.controller[1] = 127; + m_pCurrentEntity->curstate.controller[2] = 127; + m_pCurrentEntity->curstate.controller[3] = 127; + m_pCurrentEntity->latched.prevcontroller[0] = m_pCurrentEntity->curstate.controller[0]; + m_pCurrentEntity->latched.prevcontroller[1] = m_pCurrentEntity->curstate.controller[1]; + m_pCurrentEntity->latched.prevcontroller[2] = m_pCurrentEntity->curstate.controller[2]; + m_pCurrentEntity->latched.prevcontroller[3] = m_pCurrentEntity->curstate.controller[3]; + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + m_pPlayerInfo->gaitsequence = 0; + + StudioSetUpTransform( 0 ); + } + + if (flags & STUDIO_RENDER) + { + // see if the bounding box lets us trivially reject, also sets + if (!IEngineStudio.StudioCheckBBox ()) + return 0; + + (*m_pModelsDrawn)++; + (*m_pStudioModelCount)++; // render data cache cookie + + if (m_pStudioHeader->numbodyparts == 0) + return 1; + } + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + StudioSetupBones( ); + StudioSaveBones( ); + m_pPlayerInfo->renderframe = m_nFrameCount; + + m_pPlayerInfo = NULL; + + if (flags & STUDIO_EVENTS) + { + StudioCalcAttachments( ); + IEngineStudio.StudioClientEvents( ); + // copy attachments into global entity array + if ( m_pCurrentEntity->index > 0 ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( m_pCurrentEntity->index ); + + memcpy( ent->attachment, m_pCurrentEntity->attachment, sizeof( vec3_t ) * 4 ); + } + } + + if (flags & STUDIO_RENDER) + { + if (m_pCvarHiModels->value && m_pRenderModel != m_pCurrentEntity->model ) + { + // show highest resolution multiplayer model + m_pCurrentEntity->curstate.body = 255; + } + + if (!(m_pCvarDeveloper->value == 0 && gEngfuncs.GetMaxClients() == 1 ) && ( m_pRenderModel == m_pCurrentEntity->model ) ) + { + m_pCurrentEntity->curstate.body = 1; // force helmet + } + + lighting.plightvec = dir; + IEngineStudio.StudioDynamicLight(m_pCurrentEntity, &lighting ); + + IEngineStudio.StudioEntityLight( &lighting ); + + // model and frame independant + IEngineStudio.StudioSetupLighting (&lighting); + + m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); + + // get remap colors + m_nTopColor = m_pPlayerInfo->topcolor; + if (m_nTopColor < 0) + m_nTopColor = 0; + if (m_nTopColor > 360) + m_nTopColor = 360; + m_nBottomColor = m_pPlayerInfo->bottomcolor; + if (m_nBottomColor < 0) + m_nBottomColor = 0; + if (m_nBottomColor > 360) + m_nBottomColor = 360; + + IEngineStudio.StudioSetRemapColors( m_nTopColor, m_nBottomColor ); + + StudioRenderModel( ); + m_pPlayerInfo = NULL; + + if (pplayer->weaponmodel) + { + cl_entity_t saveent = *m_pCurrentEntity; + + model_t *pweaponmodel = IEngineStudio.GetModelByIndex( pplayer->weaponmodel ); + + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata (pweaponmodel); + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + + StudioMergeBones( pweaponmodel); + + IEngineStudio.StudioSetupLighting (&lighting); + + StudioRenderModel( ); + + StudioCalcAttachments( ); + + *m_pCurrentEntity = saveent; + } + } + + return 1; +} + +/* +==================== +StudioCalcAttachments + +==================== +*/ +void CStudioModelRenderer::StudioCalcAttachments( void ) +{ + int i; + mstudioattachment_t *pattachment; + + if ( m_pStudioHeader->numattachments > 4 ) + { + gEngfuncs.Con_DPrintf( "Too many attachments on %s\n", m_pCurrentEntity->model->name ); + exit( -1 ); + } + + // calculate attachment points + pattachment = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); + for (i = 0; i < m_pStudioHeader->numattachments; i++) + { + VectorTransform( pattachment[i].org, (*m_plighttransform)[pattachment[i].bone], m_pCurrentEntity->attachment[i] ); + } +} + +/* +==================== +StudioRenderModel + +==================== +*/ +void CStudioModelRenderer::StudioRenderModel( void ) +{ + IEngineStudio.SetChromeOrigin(); + IEngineStudio.SetForceFaceFlags( 0 ); + + if ( m_pCurrentEntity->curstate.renderfx == kRenderFxGlowShell ) + { + m_pCurrentEntity->curstate.renderfx = kRenderFxNone; + StudioRenderFinal( ); + + if ( !IEngineStudio.IsHardware() ) + { + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + } + + IEngineStudio.SetForceFaceFlags( STUDIO_NF_CHROME ); + + gEngfuncs.pTriAPI->SpriteTexture( m_pChromeSprite, 0 ); + m_pCurrentEntity->curstate.renderfx = kRenderFxGlowShell; + + StudioRenderFinal( ); + if ( !IEngineStudio.IsHardware() ) + { + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + } + } + else + { + StudioRenderFinal( ); + } +} + +/* +==================== +StudioRenderFinal_Software + +==================== +*/ +void CStudioModelRenderer::StudioRenderFinal_Software( void ) +{ + int i; + + // Note, rendermode set here has effect in SW + IEngineStudio.SetupRenderer( 0 ); + + if (m_pCvarDrawEntities->value == 2) + { + IEngineStudio.StudioDrawBones( ); + } + else if (m_pCvarDrawEntities->value == 3) + { + IEngineStudio.StudioDrawHulls( ); + } + else + { + for (i=0 ; i < m_pStudioHeader->numbodyparts ; i++) + { + IEngineStudio.StudioSetupModel( i, (void **)&m_pBodyPart, (void **)&m_pSubModel ); + IEngineStudio.StudioDrawPoints( ); + } + } + + if (m_pCvarDrawEntities->value == 4) + { + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + IEngineStudio.StudioDrawHulls( ); + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + } + + if (m_pCvarDrawEntities->value == 5) + { + IEngineStudio.StudioDrawAbsBBox( ); + } + + IEngineStudio.RestoreRenderer(); +} + +/* +==================== +StudioRenderFinal_Hardware + +==================== +*/ +void CStudioModelRenderer::StudioRenderFinal_Hardware( void ) +{ + int i; + int rendermode; + + rendermode = IEngineStudio.GetForceFaceFlags() ? kRenderTransAdd : m_pCurrentEntity->curstate.rendermode; + IEngineStudio.SetupRenderer( rendermode ); + + if (m_pCvarDrawEntities->value == 2) + { + IEngineStudio.StudioDrawBones(); + } + else if (m_pCvarDrawEntities->value == 3) + { + IEngineStudio.StudioDrawHulls(); + } + else + { + for (i=0 ; i < m_pStudioHeader->numbodyparts ; i++) + { + IEngineStudio.StudioSetupModel( i, (void **)&m_pBodyPart, (void **)&m_pSubModel ); + + if (m_fDoInterp) + { + // interpolation messes up bounding boxes. + m_pCurrentEntity->trivial_accept = 0; + } + + IEngineStudio.GL_SetRenderMode( rendermode ); + IEngineStudio.StudioDrawPoints(); + IEngineStudio.GL_StudioDrawShadow(); + } + } + + if ( m_pCvarDrawEntities->value == 4 ) + { + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + IEngineStudio.StudioDrawHulls( ); + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + } + + IEngineStudio.RestoreRenderer(); +} + +/* +==================== +StudioRenderFinal + +==================== +*/ +void CStudioModelRenderer::StudioRenderFinal(void) +{ + if ( IEngineStudio.IsHardware() ) + { + StudioRenderFinal_Hardware(); + } + else + { + StudioRenderFinal_Software(); + } +} + diff --git a/ricochet/cl_dll/StudioModelRenderer.h b/ricochet/cl_dll/StudioModelRenderer.h new file mode 100644 index 0000000..fae3a8e --- /dev/null +++ b/ricochet/cl_dll/StudioModelRenderer.h @@ -0,0 +1,182 @@ +#if !defined ( STUDIOMODELRENDERER_H ) +#define STUDIOMODELRENDERER_H +#if defined( _WIN32 ) +#pragma once +#endif + +/* +==================== +CStudioModelRenderer + +==================== +*/ +class CStudioModelRenderer +{ +public: + // Construction/Destruction + CStudioModelRenderer( void ); + virtual ~CStudioModelRenderer( void ); + + // Initialization + virtual void Init( void ); + +public: + // Public Interfaces + virtual int StudioDrawModel ( int flags ); + virtual int StudioDrawPlayer ( int flags, struct entity_state_s *pplayer ); + +public: + // Local interfaces + // + + // Look up animation data for sequence + virtual mstudioanim_t *StudioGetAnim ( model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc ); + + // Interpolate model position and angles and set up matrices + virtual void StudioSetUpTransform (int trivial_accept); + + // Set up model bone positions + virtual void StudioSetupBones ( void ); + + // Find final attachment points + virtual void StudioCalcAttachments ( void ); + + // Save bone matrices and names + virtual void StudioSaveBones( void ); + + // Merge cached bones with current bones for model + virtual void StudioMergeBones ( model_t *m_pSubModel ); + + // Determine interpolation fraction + virtual float StudioEstimateInterpolant( void ); + + // Determine current frame for rendering + virtual float StudioEstimateFrame ( mstudioseqdesc_t *pseqdesc ); + + // Apply special effects to transform matrix + virtual void StudioFxTransform( cl_entity_t *ent, float transform[3][4] ); + + // Spherical interpolation of bones + virtual void StudioSlerpBones ( vec4_t q1[], float pos1[][3], vec4_t q2[], float pos2[][3], float s ); + + // Compute bone adjustments ( bone controllers ) + virtual void StudioCalcBoneAdj ( float dadt, float *adj, const byte *pcontroller1, const byte *pcontroller2, byte mouthopen ); + + // Get bone quaternions + virtual void StudioCalcBoneQuaterion ( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *q ); + + // Get bone positions + virtual void StudioCalcBonePosition ( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *pos ); + + // Compute rotations + virtual void StudioCalcRotations ( float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f ); + + // Send bones and verts to renderer + virtual void StudioRenderModel ( void ); + + // Finalize rendering + virtual void StudioRenderFinal (void); + + // GL&D3D vs. Software renderer finishing functions + virtual void StudioRenderFinal_Software ( void ); + virtual void StudioRenderFinal_Hardware ( void ); + + // Player specific data + // Determine pitch and blending amounts for players + virtual void StudioPlayerBlend ( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ); + + // Estimate gait frame for player + virtual void StudioEstimateGait ( entity_state_t *pplayer ); + + // Process movement of player + virtual void StudioProcessGait ( entity_state_t *pplayer ); + +public: + + // Client clock + double m_clTime; + // Old Client clock + double m_clOldTime; + + // Do interpolation? + int m_fDoInterp; + // Do gait estimation? + int m_fGaitEstimation; + + // Current render frame # + int m_nFrameCount; + + // Cvars that studio model code needs to reference + // + // Use high quality models? + cvar_t *m_pCvarHiModels; + // Developer debug output desired? + cvar_t *m_pCvarDeveloper; + // Draw entities bone hit boxes, etc? + cvar_t *m_pCvarDrawEntities; + + // The entity which we are currently rendering. + cl_entity_t *m_pCurrentEntity; + + // The model for the entity being rendered + model_t *m_pRenderModel; + + // Player info for current player, if drawing a player + player_info_t *m_pPlayerInfo; + + // The index of the player being drawn + int m_nPlayerIndex; + + // The player's gait movement + float m_flGaitMovement; + + // Pointer to header block for studio model data + studiohdr_t *m_pStudioHeader; + + // Pointers to current body part and submodel + mstudiobodyparts_t *m_pBodyPart; + mstudiomodel_t *m_pSubModel; + + // Palette substition for top and bottom of model + int m_nTopColor; + int m_nBottomColor; + + // + // Sprite model used for drawing studio model chrome + model_t *m_pChromeSprite; + + // Caching + // Number of bones in bone cache + int m_nCachedBones; + // Names of cached bones + char m_nCachedBoneNames[ MAXSTUDIOBONES ][ 32 ]; + // Cached bone & light transformation matrices + float m_rgCachedBoneTransform [ MAXSTUDIOBONES ][ 3 ][ 4 ]; + float m_rgCachedLightTransform[ MAXSTUDIOBONES ][ 3 ][ 4 ]; + + // Software renderer scale factors + float m_fSoftwareXScale, m_fSoftwareYScale; + + // Current view vectors and render origin + float m_vUp[ 3 ]; + float m_vRight[ 3 ]; + float m_vNormal[ 3 ]; + + float m_vRenderOrigin[ 3 ]; + + // Model render counters ( from engine ) + int *m_pStudioModelCount; + int *m_pModelsDrawn; + + // Matrices + // Model to world transformation + float (*m_protationmatrix)[ 3 ][ 4 ]; + // Model to view transformation + float (*m_paliastransform)[ 3 ][ 4 ]; + + // Concatenated bone and light transforms + float (*m_pbonetransform) [ MAXSTUDIOBONES ][ 3 ][ 4 ]; + float (*m_plighttransform)[ MAXSTUDIOBONES ][ 3 ][ 4 ]; +}; + +#endif // STUDIOMODELRENDERER_H \ No newline at end of file diff --git a/ricochet/cl_dll/ammo.cpp b/ricochet/cl_dll/ammo.cpp new file mode 100644 index 0000000..c7573c8 --- /dev/null +++ b/ricochet/cl_dll/ammo.cpp @@ -0,0 +1,930 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Ammo.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "ammohistory.h" + +WEAPON *gpActiveSel; // NULL means off, 1 means just the menu bar, otherwise + // this points to the active weapon menu item +WEAPON *gpLastSel; // Last weapon menu selection + +client_sprite_t *GetSpriteList(client_sprite_t *pList, const char *psz, int iRes, int iCount); + +WeaponsResource gWR; + +int g_weaponselect = 0; + +void WeaponsResource :: LoadAllWeaponSprites( void ) +{ + for (int i = 0; i < MAX_WEAPONS; i++) + { + if ( rgWeapons[i].iId ) + LoadWeaponSprites( &rgWeapons[i] ); + } +} + +int WeaponsResource :: CountAmmo( int iId ) +{ + if ( iId < 0 ) + return 0; + + return riAmmo[iId]; +} + +int WeaponsResource :: HasAmmo( WEAPON *p ) +{ + if ( !p ) + return FALSE; + + // weapons with no max ammo can always be selected + if ( p->iMax1 == -1 ) + return TRUE; + + return (p->iAmmoType == -1) || p->iClip > 0 || CountAmmo(p->iAmmoType) + || CountAmmo(p->iAmmo2Type) || ( p->iFlags & WEAPON_FLAGS_SELECTONEMPTY ); +} + + +void WeaponsResource :: LoadWeaponSprites( WEAPON *pWeapon ) +{ + int i, iRes; + + if (ScreenWidth < 640) + iRes = 320; + else + iRes = 640; + + char sz[128]; + + if ( !pWeapon ) + return; + + memset( &pWeapon->rcActive, 0, sizeof(wrect_t) ); + memset( &pWeapon->rcInactive, 0, sizeof(wrect_t) ); + memset( &pWeapon->rcAmmo, 0, sizeof(wrect_t) ); + memset( &pWeapon->rcAmmo2, 0, sizeof(wrect_t) ); + pWeapon->hInactive = 0; + pWeapon->hActive = 0; + pWeapon->hAmmo = 0; + pWeapon->hAmmo2 = 0; + + sprintf(sz, "sprites/%s.txt", pWeapon->szName); + client_sprite_t *pList = SPR_GetList(sz, &i); + + if (!pList) + return; + + client_sprite_t *p; + + p = GetSpriteList( pList, "crosshair", iRes, i ); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hCrosshair = SPR_Load(sz); + pWeapon->rcCrosshair = p->rc; + } + else + pWeapon->hCrosshair = NULL; + + p = GetSpriteList(pList, "autoaim", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hAutoaim = SPR_Load(sz); + pWeapon->rcAutoaim = p->rc; + } + else + pWeapon->hAutoaim = 0; + + p = GetSpriteList( pList, "zoom", iRes, i ); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hZoomedCrosshair = SPR_Load(sz); + pWeapon->rcZoomedCrosshair = p->rc; + } + else + { + pWeapon->hZoomedCrosshair = pWeapon->hCrosshair; //default to non-zoomed crosshair + pWeapon->rcZoomedCrosshair = pWeapon->rcCrosshair; + } + + p = GetSpriteList(pList, "zoom_autoaim", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hZoomedAutoaim = SPR_Load(sz); + pWeapon->rcZoomedAutoaim = p->rc; + } + else + { + pWeapon->hZoomedAutoaim = pWeapon->hZoomedCrosshair; //default to zoomed crosshair + pWeapon->rcZoomedAutoaim = pWeapon->rcZoomedCrosshair; + } + + p = GetSpriteList(pList, "weapon", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hInactive = SPR_Load(sz); + pWeapon->rcInactive = p->rc; + + gHR.iHistoryGap = max( gHR.iHistoryGap, pWeapon->rcActive.bottom - pWeapon->rcActive.top ); + } + else + pWeapon->hInactive = 0; + + p = GetSpriteList(pList, "weapon_s", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hActive = SPR_Load(sz); + pWeapon->rcActive = p->rc; + } + else + pWeapon->hActive = 0; + + p = GetSpriteList(pList, "ammo", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hAmmo = SPR_Load(sz); + pWeapon->rcAmmo = p->rc; + + gHR.iHistoryGap = max( gHR.iHistoryGap, pWeapon->rcActive.bottom - pWeapon->rcActive.top ); + } + else + pWeapon->hAmmo = 0; + + p = GetSpriteList(pList, "ammo2", iRes, i); + if (p) + { + sprintf(sz, "sprites/%s.spr", p->szSprite); + pWeapon->hAmmo2 = SPR_Load(sz); + pWeapon->rcAmmo2 = p->rc; + + gHR.iHistoryGap = max( gHR.iHistoryGap, pWeapon->rcActive.bottom - pWeapon->rcActive.top ); + } + else + pWeapon->hAmmo2 = 0; + +} + +// Returns the first weapon for a given slot. +WEAPON *WeaponsResource :: GetFirstPos( int iSlot ) +{ + WEAPON *pret = NULL; + + for (int i = 0; i < MAX_WEAPON_POSITIONS; i++) + { + if ( rgSlots[iSlot][i] && HasAmmo( rgSlots[iSlot][i] ) ) + { + pret = rgSlots[iSlot][i]; + break; + } + } + + return pret; +} + + +WEAPON* WeaponsResource :: GetNextActivePos( int iSlot, int iSlotPos ) +{ + if ( iSlotPos >= MAX_WEAPON_POSITIONS || iSlot >= MAX_WEAPON_SLOTS ) + return NULL; + + WEAPON *p = gWR.rgSlots[ iSlot ][ iSlotPos+1 ]; + + if ( !p || !gWR.HasAmmo(p) ) + return GetNextActivePos( iSlot, iSlotPos + 1 ); + + return p; +} + + +int giBucketHeight, giBucketWidth, giABHeight, giABWidth; // Ammo Bar width and height + +HSPRITE ghsprBuckets; // Sprite for top row of weapons menu + +DECLARE_MESSAGE(m_Ammo, CurWeapon ); // Current weapon and clip +DECLARE_MESSAGE(m_Ammo, WeaponList); // new weapon type +DECLARE_MESSAGE(m_Ammo, AmmoX); // update known ammo type's count +DECLARE_MESSAGE(m_Ammo, AmmoPickup); // flashes an ammo pickup record +DECLARE_MESSAGE(m_Ammo, WeapPickup); // flashes a weapon pickup record +DECLARE_MESSAGE(m_Ammo, HideWeapon); // hides the weapon, ammo, and crosshair displays temporarily +DECLARE_MESSAGE(m_Ammo, ItemPickup); + +DECLARE_COMMAND(m_Ammo, Slot1); +DECLARE_COMMAND(m_Ammo, Slot2); +DECLARE_COMMAND(m_Ammo, Slot3); +DECLARE_COMMAND(m_Ammo, Slot4); +DECLARE_COMMAND(m_Ammo, Slot5); +DECLARE_COMMAND(m_Ammo, Slot6); +DECLARE_COMMAND(m_Ammo, Slot7); +DECLARE_COMMAND(m_Ammo, Slot8); +DECLARE_COMMAND(m_Ammo, Slot9); +DECLARE_COMMAND(m_Ammo, Slot10); +DECLARE_COMMAND(m_Ammo, Close); +DECLARE_COMMAND(m_Ammo, NextWeapon); +DECLARE_COMMAND(m_Ammo, PrevWeapon); + +// width of ammo fonts +#define AMMO_SMALL_WIDTH 10 +#define AMMO_LARGE_WIDTH 20 + +#define HISTORY_DRAW_TIME "5" + +int CHudAmmo::Init(void) +{ + gHUD.AddHudElem(this); + + HOOK_MESSAGE(CurWeapon); + HOOK_MESSAGE(WeaponList); + HOOK_MESSAGE(AmmoPickup); + HOOK_MESSAGE(WeapPickup); + HOOK_MESSAGE(ItemPickup); + HOOK_MESSAGE(HideWeapon); + HOOK_MESSAGE(AmmoX); + + HOOK_COMMAND("slot1", Slot1); + HOOK_COMMAND("slot2", Slot2); + HOOK_COMMAND("slot3", Slot3); + HOOK_COMMAND("slot4", Slot4); + HOOK_COMMAND("slot5", Slot5); + HOOK_COMMAND("slot6", Slot6); + HOOK_COMMAND("slot7", Slot7); + HOOK_COMMAND("slot8", Slot8); + HOOK_COMMAND("slot9", Slot9); + HOOK_COMMAND("slot10", Slot10); + HOOK_COMMAND("cancelselect", Close); + HOOK_COMMAND("invnext", NextWeapon); + HOOK_COMMAND("invprev", PrevWeapon); + + Reset(); + + CVAR_CREATE( "hud_drawhistory_time", HISTORY_DRAW_TIME, 0 ); + CVAR_CREATE( "hud_fastswitch", "0", FCVAR_ARCHIVE ); // controls whether or not weapons can be selected in one keypress + + m_iFlags |= HUD_ACTIVE; //!!! + + gWR.Init(); + gHR.Init(); + + return 1; +}; + +void CHudAmmo::Reset(void) +{ + m_fFade = 0; + m_iFlags |= HUD_ACTIVE; //!!! + + gpActiveSel = NULL; + gHUD.m_iHideHUDDisplay = 0; + + gWR.Reset(); + gHR.Reset(); + + // VidInit(); + +} + +int CHudAmmo::VidInit(void) +{ + // Load sprites for buckets (top row of weapon menu) + m_HUD_bucket0 = gHUD.GetSpriteIndex( "bucket1" ); + m_HUD_selection = gHUD.GetSpriteIndex( "selection" ); + + ghsprBuckets = gHUD.GetSprite(m_HUD_bucket0); + giBucketWidth = gHUD.GetSpriteRect(m_HUD_bucket0).right - gHUD.GetSpriteRect(m_HUD_bucket0).left; + giBucketHeight = gHUD.GetSpriteRect(m_HUD_bucket0).bottom - gHUD.GetSpriteRect(m_HUD_bucket0).top; + + gHR.iHistoryGap = max( gHR.iHistoryGap, gHUD.GetSpriteRect(m_HUD_bucket0).bottom - gHUD.GetSpriteRect(m_HUD_bucket0).top); + + // If we've already loaded weapons, let's get new sprites + gWR.LoadAllWeaponSprites(); + + if (ScreenWidth >= 640) + { + giABWidth = 20; + giABHeight = 4; + } + else + { + giABWidth = 10; + giABHeight = 2; + } + + return 1; +} + +// +// Think: +// Used for selection of weapon menu item. +// +void CHudAmmo::Think(void) +{ + if ( gHUD.m_fPlayerDead ) + return; + + if ( gHUD.m_iWeaponBits != gWR.iOldWeaponBits ) + { + gWR.iOldWeaponBits = gHUD.m_iWeaponBits; + + for (int i = MAX_WEAPONS-1; i > 0; i-- ) + { + WEAPON *p = gWR.GetWeapon(i); + + if ( p ) + { + if ( gHUD.m_iWeaponBits & ( 1 << p->iId ) ) + gWR.PickupWeapon( p ); + else + gWR.DropWeapon( p ); + } + } + } + + if (!gpActiveSel) + return; + + // has the player selected one? + if (gHUD.m_iKeyBits & IN_ATTACK) + { + if (gpActiveSel != (WEAPON *)1) + { + ServerCmd(gpActiveSel->szName); + g_weaponselect = gpActiveSel->iId; + } + + gpLastSel = gpActiveSel; + gpActiveSel = NULL; + gHUD.m_iKeyBits &= ~IN_ATTACK; + + PlaySound("common/wpn_select.wav", 1); + } + +} + +// +// Helper function to return a Ammo pointer from id +// + +HSPRITE* WeaponsResource :: GetAmmoPicFromWeapon( int iAmmoId, wrect_t& rect ) +{ + for ( int i = 0; i < MAX_WEAPONS; i++ ) + { + if ( rgWeapons[i].iAmmoType == iAmmoId ) + { + rect = rgWeapons[i].rcAmmo; + return &rgWeapons[i].hAmmo; + } + else if ( rgWeapons[i].iAmmo2Type == iAmmoId ) + { + rect = rgWeapons[i].rcAmmo2; + return &rgWeapons[i].hAmmo2; + } + } + + return NULL; +} + + +// Menu Selection Code + +void WeaponsResource :: SelectSlot( int iSlot, int fAdvance, int iDirection ) +{ + // Discwar has no weapons to switch to + return; + + if ( gHUD.m_Menu.m_fMenuDisplayed && (fAdvance == FALSE) && (iDirection == 1) ) + { // menu is overriding slot use commands + gHUD.m_Menu.SelectMenuItem( iSlot + 1 ); // slots are one off the key numbers + return; + } + + if ( iSlot > MAX_WEAPON_SLOTS ) + return; + + if ( gHUD.m_fPlayerDead || gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL ) ) + return; + + if (!(gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT)) )) + return; + + if ( ! ( gHUD.m_iWeaponBits & ~(1<<(WEAPON_SUIT)) )) + return; + + WEAPON *p = NULL; + bool fastSwitch = CVAR_GET_FLOAT( "hud_fastswitch" ) != 0; + + if ( (gpActiveSel == NULL) || (gpActiveSel == (WEAPON *)1) || (iSlot != gpActiveSel->iSlot) ) + { + PlaySound( "common/wpn_hudon.wav", 1 ); + p = GetFirstPos( iSlot ); + + if ( p && fastSwitch ) // check for fast weapon switch mode + { + // if fast weapon switch is on, then weapons can be selected in a single keypress + // but only if there is only one item in the bucket + WEAPON *p2 = GetNextActivePos( p->iSlot, p->iSlotPos ); + if ( !p2 ) + { // only one active item in bucket, so change directly to weapon + ServerCmd( p->szName ); + g_weaponselect = p->iId; + return; + } + } + } + else + { + PlaySound("common/wpn_moveselect.wav", 1); + if ( gpActiveSel ) + p = GetNextActivePos( gpActiveSel->iSlot, gpActiveSel->iSlotPos ); + if ( !p ) + p = GetFirstPos( iSlot ); + } + + + if ( !p ) // no selection found + { + // just display the weapon list, unless fastswitch is on just ignore it + if ( !fastSwitch ) + gpActiveSel = (WEAPON *)1; + else + gpActiveSel = NULL; + } + else + gpActiveSel = p; +} + + +//------------------------------------------------------------------------ +// Message Handlers +//------------------------------------------------------------------------ + +// +// AmmoX -- Update the count of a known type of ammo +// +int CHudAmmo::MsgFunc_AmmoX(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + int iIndex = READ_BYTE(); + int iCount = READ_BYTE(); + + gWR.SetAmmo( iIndex, abs(iCount) ); + + return 1; +} + +int CHudAmmo::MsgFunc_AmmoPickup( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + int iIndex = READ_BYTE(); + int iCount = READ_BYTE(); + + // Add ammo to the history + gHR.AddToHistory( HISTSLOT_AMMO, iIndex, abs(iCount) ); + + return 1; +} + +int CHudAmmo::MsgFunc_WeapPickup( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + int iIndex = READ_BYTE(); + + // Add the weapon to the history + gHR.AddToHistory( HISTSLOT_WEAP, iIndex ); + + return 1; +} + +int CHudAmmo::MsgFunc_ItemPickup( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + const char *szName = READ_STRING(); + + // Add the weapon to the history + gHR.AddToHistory( HISTSLOT_ITEM, szName ); + + return 1; +} + + +int CHudAmmo::MsgFunc_HideWeapon( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + gHUD.m_iHideHUDDisplay = READ_BYTE(); + + if ( gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL ) ) + { + static wrect_t nullrc; + gpActiveSel = NULL; + SetCrosshair( 0, nullrc, 0, 0, 0 ); + } + else + { + if ( m_pWeapon ) + SetCrosshair( m_pWeapon->hCrosshair, m_pWeapon->rcCrosshair, 255, 255, 255 ); + } + + return 1; +} + +// +// CurWeapon: Update hud state with the current weapon and clip count. Ammo +// counts are updated with AmmoX. Server assures that the Weapon ammo type +// numbers match a real ammo type. +// +int CHudAmmo::MsgFunc_CurWeapon(const char *pszName, int iSize, void *pbuf ) +{ + static wrect_t nullrc; + int fOnTarget = FALSE; + + BEGIN_READ( pbuf, iSize ); + + int iState = READ_BYTE(); + int iId = READ_CHAR(); + int iClip = READ_CHAR(); + + // detect if we're also on target + if ( iState > 1 ) + { + fOnTarget = TRUE; + } + + if ( iId < 1 ) + { + SetCrosshair(0, nullrc, 0, 0, 0); + return 0; + } + + // Is player dead??? + if ((iId == -1) && (iClip == -1)) + { + gHUD.m_fPlayerDead = TRUE; + gpActiveSel = NULL; + return 1; + } + gHUD.m_fPlayerDead = FALSE; + + WEAPON *pWeapon = gWR.GetWeapon( iId ); + + if ( !pWeapon ) + return 0; + + if ( iClip < -1 ) + pWeapon->iClip = abs(iClip); + else + pWeapon->iClip = iClip; + + + if ( iState == 0 ) // we're not the current weapon, so update no more + return 1; + + m_pWeapon = pWeapon; + + if ( !(gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL )) ) + { + if ( gHUD.m_iFOV >= 90 ) + { // normal crosshairs + if (fOnTarget && m_pWeapon->hAutoaim) + SetCrosshair(m_pWeapon->hAutoaim, m_pWeapon->rcAutoaim, 255, 255, 255); + else + SetCrosshair(m_pWeapon->hCrosshair, m_pWeapon->rcCrosshair, 255, 255, 255); + } + else + { // zoomed crosshairs + if (fOnTarget && m_pWeapon->hZoomedAutoaim) + SetCrosshair(m_pWeapon->hZoomedAutoaim, m_pWeapon->rcZoomedAutoaim, 255, 255, 255); + else + SetCrosshair(m_pWeapon->hZoomedCrosshair, m_pWeapon->rcZoomedCrosshair, 255, 255, 255); + } + } + + m_fFade = 200.0f; //!!! + m_iFlags |= HUD_ACTIVE; + + return 1; +} + +// +// WeaponList -- Tells the hud about a new weapon type. +// +int CHudAmmo::MsgFunc_WeaponList(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + WEAPON Weapon; + + strcpy( Weapon.szName, READ_STRING() ); + Weapon.iAmmoType = (int)READ_CHAR(); + + Weapon.iMax1 = READ_BYTE(); + if (Weapon.iMax1 == 255) + Weapon.iMax1 = -1; + + Weapon.iAmmo2Type = READ_CHAR(); + Weapon.iMax2 = READ_BYTE(); + if (Weapon.iMax2 == 255) + Weapon.iMax2 = -1; + + Weapon.iSlot = READ_CHAR(); + Weapon.iSlotPos = READ_CHAR(); + Weapon.iId = READ_CHAR(); + Weapon.iFlags = READ_BYTE(); + Weapon.iClip = 0; + + gWR.AddWeapon( &Weapon ); + + return 1; + +} + +//------------------------------------------------------------------------ +// Command Handlers +//------------------------------------------------------------------------ + +void CHudAmmo::UserCmd_Slot1(void) +{ + gWR.SelectSlot(0, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot2(void) +{ + gWR.SelectSlot(1, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot3(void) +{ + gWR.SelectSlot(2, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot4(void) +{ + gWR.SelectSlot(3, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot5(void) +{ + gWR.SelectSlot(4, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot6(void) +{ + gWR.SelectSlot(5, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot7(void) +{ + gWR.SelectSlot(6, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot8(void) +{ + gWR.SelectSlot(7, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot9(void) +{ + gWR.SelectSlot(8, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot10(void) +{ + gWR.SelectSlot(9, FALSE, 1); +} + +void CHudAmmo::UserCmd_Close(void) +{ + if (gpActiveSel) + { + gpLastSel = gpActiveSel; + gpActiveSel = NULL; + PlaySound("common/wpn_hudoff.wav", 1); + } + else + ClientCmd("escape"); +} + + +// Selects the next item in the weapon menu +void CHudAmmo::UserCmd_NextWeapon(void) +{ + if ( gHUD.m_fPlayerDead || (gHUD.m_iHideHUDDisplay & (HIDEHUD_WEAPONS | HIDEHUD_ALL)) ) + return; + + if ( !gpActiveSel || gpActiveSel == (WEAPON*)1 ) + gpActiveSel = m_pWeapon; + + int pos = 0; + int slot = 0; + if ( gpActiveSel ) + { + pos = gpActiveSel->iSlotPos + 1; + slot = gpActiveSel->iSlot; + } + + for ( int loop = 0; loop <= 1; loop++ ) + { + for ( ; slot < MAX_WEAPON_SLOTS; slot++ ) + { + for ( ; pos < MAX_WEAPON_POSITIONS; pos++ ) + { + WEAPON *wsp = gWR.GetWeaponSlot( slot, pos ); + + if ( wsp && gWR.HasAmmo(wsp) ) + { + gpActiveSel = wsp; + return; + } + } + + pos = 0; + } + + slot = 0; // start looking from the first slot again + } + + gpActiveSel = NULL; +} + +// Selects the previous item in the menu +void CHudAmmo::UserCmd_PrevWeapon(void) +{ + if ( gHUD.m_fPlayerDead || (gHUD.m_iHideHUDDisplay & (HIDEHUD_WEAPONS | HIDEHUD_ALL)) ) + return; + + if ( !gpActiveSel || gpActiveSel == (WEAPON*)1 ) + gpActiveSel = m_pWeapon; + + int pos = MAX_WEAPON_POSITIONS-1; + int slot = MAX_WEAPON_SLOTS-1; + if ( gpActiveSel ) + { + pos = gpActiveSel->iSlotPos - 1; + slot = gpActiveSel->iSlot; + } + + for ( int loop = 0; loop <= 1; loop++ ) + { + for ( ; slot >= 0; slot-- ) + { + for ( ; pos >= 0; pos-- ) + { + WEAPON *wsp = gWR.GetWeaponSlot( slot, pos ); + + if ( wsp && gWR.HasAmmo(wsp) ) + { + gpActiveSel = wsp; + return; + } + } + + pos = MAX_WEAPON_POSITIONS-1; + } + + slot = MAX_WEAPON_SLOTS-1; + } + + gpActiveSel = NULL; +} + + + +//------------------------------------------------------------------------- +// Drawing code +//------------------------------------------------------------------------- + +int CHudAmmo::Draw(float flTime) +{ + // No ammo in discwar + return 1; +} + + +// +// Draws the ammo bar on the hud +// +int DrawBar(int x, int y, int width, int height, float f) +{ + int r, g, b; + + if (f < 0) + f = 0; + if (f > 1) + f = 1; + + if (f) + { + int w = f * width; + + // Always show at least one pixel if we have ammo. + if (w <= 0) + w = 1; + UnpackRGB(r, g, b, RGB_GREENISH); + FillRGBA(x, y, w, height, r, g, b, 255); + x += w; + width -= w; + } + + UnpackRGB(r, g, b, RGB_YELLOWISH); + + FillRGBA(x, y, width, height, r, g, b, 128); + + return (x + width); +} + + + +void DrawAmmoBar(WEAPON *p, int x, int y, int width, int height) +{ + if ( !p ) + return; + + if (p->iAmmoType != -1) + { + if (!gWR.CountAmmo(p->iAmmoType)) + return; + + float f = (float)gWR.CountAmmo(p->iAmmoType)/(float)p->iMax1; + + x = DrawBar(x, y, width, height, f); + + + // Do we have secondary ammo too? + + if (p->iAmmo2Type != -1) + { + f = (float)gWR.CountAmmo(p->iAmmo2Type)/(float)p->iMax2; + + x += 5; //!!! + + DrawBar(x, y, width, height, f); + } + } +} + + + + +// +// Draw Weapon Menu +// +int CHudAmmo::DrawWList(float flTime) +{ + // No ammo in discwar + return 0; +} + + +/* ================================= + GetSpriteList + +Finds and returns the matching +sprite name 'psz' and resolution 'iRes' +in the given sprite list 'pList' +iCount is the number of items in the pList +================================= */ +client_sprite_t *GetSpriteList(client_sprite_t *pList, const char *psz, int iRes, int iCount) +{ + if (!pList) + return NULL; + + int i = iCount; + client_sprite_t *p = pList; + + while(i--) + { + if ((!strcmp(psz, p->szName)) && (p->iRes == iRes)) + return p; + p++; + } + + return NULL; +} diff --git a/ricochet/cl_dll/ammo.h b/ricochet/cl_dll/ammo.h new file mode 100644 index 0000000..a3125ab --- /dev/null +++ b/ricochet/cl_dll/ammo.h @@ -0,0 +1,62 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef __AMMO_H__ +#define __AMMO_H__ + +#define MAX_WEAPON_NAME 128 + + +#define WEAPON_FLAGS_SELECTONEMPTY 1 + +#define WEAPON_IS_ONTARGET 0x40 + +struct WEAPON +{ + char szName[MAX_WEAPON_NAME]; + int iAmmoType; + int iAmmo2Type; + int iMax1; + int iMax2; + int iSlot; + int iSlotPos; + int iFlags; + int iId; + int iClip; + + int iCount; // # of itesm in plist + + HSPRITE hActive; + wrect_t rcActive; + HSPRITE hInactive; + wrect_t rcInactive; + HSPRITE hAmmo; + wrect_t rcAmmo; + HSPRITE hAmmo2; + wrect_t rcAmmo2; + HSPRITE hCrosshair; + wrect_t rcCrosshair; + HSPRITE hAutoaim; + wrect_t rcAutoaim; + HSPRITE hZoomedCrosshair; + wrect_t rcZoomedCrosshair; + HSPRITE hZoomedAutoaim; + wrect_t rcZoomedAutoaim; +}; + +typedef int AMMO; + + +#endif \ No newline at end of file diff --git a/ricochet/cl_dll/ammo_secondary.cpp b/ricochet/cl_dll/ammo_secondary.cpp new file mode 100644 index 0000000..d71b2ee --- /dev/null +++ b/ricochet/cl_dll/ammo_secondary.cpp @@ -0,0 +1,159 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ammo_secondary.cpp +// +// implementation of CHudAmmoSecondary class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE( m_AmmoSecondary, SecAmmoVal ); +DECLARE_MESSAGE( m_AmmoSecondary, SecAmmoIcon ); + +int CHudAmmoSecondary :: Init( void ) +{ + HOOK_MESSAGE( SecAmmoVal ); + HOOK_MESSAGE( SecAmmoIcon ); + + gHUD.AddHudElem(this); + m_HUD_ammoicon = 0; + + for ( int i = 0; i < MAX_SEC_AMMO_VALUES; i++ ) + m_iAmmoAmounts[i] = -1; // -1 means don't draw this value + + Reset(); + + return 1; +} + +void CHudAmmoSecondary :: Reset( void ) +{ + m_fFade = 0; +} + +int CHudAmmoSecondary :: VidInit( void ) +{ + return 1; +} + +int CHudAmmoSecondary :: Draw(float flTime) +{ + if ( (gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL )) ) + return 1; + + // draw secondary ammo icons above normal ammo readout + int a, x, y, r, g, b, AmmoWidth; + UnpackRGB( r, g, b, RGB_YELLOWISH ); + a = (int) max( MIN_ALPHA, m_fFade ); + if (m_fFade > 0) + m_fFade -= (gHUD.m_flTimeDelta * 20); // slowly lower alpha to fade out icons + ScaleColors( r, g, b, a ); + + AmmoWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; + + y = ScreenHeight - (gHUD.m_iFontHeight*4); // this is one font height higher than the weapon ammo values + x = ScreenWidth - AmmoWidth; + + if ( m_HUD_ammoicon ) + { + // Draw the ammo icon + x -= (gHUD.GetSpriteRect(m_HUD_ammoicon).right - gHUD.GetSpriteRect(m_HUD_ammoicon).left); + y -= (gHUD.GetSpriteRect(m_HUD_ammoicon).top - gHUD.GetSpriteRect(m_HUD_ammoicon).bottom); + + SPR_Set( gHUD.GetSprite(m_HUD_ammoicon), r, g, b ); + SPR_DrawAdditive( 0, x, y, &gHUD.GetSpriteRect(m_HUD_ammoicon) ); + } + else + { // move the cursor by the '0' char instead, since we don't have an icon to work with + x -= AmmoWidth; + y -= (gHUD.GetSpriteRect(gHUD.m_HUD_number_0).top - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).bottom); + } + + // draw the ammo counts, in reverse order, from right to left + for ( int i = MAX_SEC_AMMO_VALUES-1; i >= 0; i-- ) + { + if ( m_iAmmoAmounts[i] < 0 ) + continue; // negative ammo amounts imply that they shouldn't be drawn + + // half a char gap between the ammo number and the previous pic + x -= (AmmoWidth / 2); + + // draw the number, right-aligned + x -= (gHUD.GetNumWidth( m_iAmmoAmounts[i], DHN_DRAWZERO ) * AmmoWidth); + gHUD.DrawHudNumber( x, y, DHN_DRAWZERO, m_iAmmoAmounts[i], r, g, b ); + + if ( i != 0 ) + { + // draw the divider bar + x -= (AmmoWidth / 2); + FillRGBA(x, y, (AmmoWidth/10), gHUD.m_iFontHeight, r, g, b, a); + } + } + + return 1; +} + +// Message handler for Secondary Ammo Value +// accepts one value: +// string: sprite name +int CHudAmmoSecondary :: MsgFunc_SecAmmoIcon( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + m_HUD_ammoicon = gHUD.GetSpriteIndex( READ_STRING() ); + + return 1; +} + +// Message handler for Secondary Ammo Icon +// Sets an ammo value +// takes two values: +// byte: ammo index +// byte: ammo value +int CHudAmmoSecondary :: MsgFunc_SecAmmoVal( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int index = READ_BYTE(); + if ( index < 0 || index >= MAX_SEC_AMMO_VALUES ) + return 1; + + m_iAmmoAmounts[index] = READ_BYTE(); + m_iFlags |= HUD_ACTIVE; + + // check to see if there is anything left to draw + int count = 0; + for ( int i = 0; i < MAX_SEC_AMMO_VALUES; i++ ) + { + count += max( 0, m_iAmmoAmounts[i] ); + } + + if ( count == 0 ) + { // the ammo fields are all empty, so turn off this hud area + m_iFlags &= ~HUD_ACTIVE; + return 1; + } + + // make the icons light up + m_fFade = 200.0f; + + return 1; +} + + diff --git a/ricochet/cl_dll/ammohistory.cpp b/ricochet/cl_dll/ammohistory.cpp new file mode 100644 index 0000000..e9cbbd4 --- /dev/null +++ b/ricochet/cl_dll/ammohistory.cpp @@ -0,0 +1,190 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ammohistory.cpp +// + + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "ammohistory.h" + +HistoryResource gHR; + +#define AMMO_PICKUP_GAP (gHR.iHistoryGap+5) +#define AMMO_PICKUP_PICK_HEIGHT (32 + (gHR.iHistoryGap * 2)) +#define AMMO_PICKUP_HEIGHT_MAX (ScreenHeight - 100) + +#define MAX_ITEM_NAME 32 +int HISTORY_DRAW_TIME = 5; + +// keep a list of items +struct ITEM_INFO +{ + char szName[MAX_ITEM_NAME]; + HSPRITE spr; + wrect_t rect; +}; + +void HistoryResource :: AddToHistory( int iType, int iId, int iCount ) +{ + if ( iType == HISTSLOT_AMMO && !iCount ) + return; // no amount, so don't add + + if ( (((AMMO_PICKUP_GAP * iCurrentHistorySlot) + AMMO_PICKUP_PICK_HEIGHT) > AMMO_PICKUP_HEIGHT_MAX) || (iCurrentHistorySlot >= MAX_HISTORY) ) + { // the pic would have to be drawn too high + // so start from the bottom + iCurrentHistorySlot = 0; + } + + HIST_ITEM *freeslot = &rgAmmoHistory[iCurrentHistorySlot++]; // default to just writing to the first slot + HISTORY_DRAW_TIME = CVAR_GET_FLOAT( "hud_drawhistory_time" ); + + freeslot->type = iType; + freeslot->iId = iId; + freeslot->iCount = iCount; + freeslot->DisplayTime = gHUD.m_flTime + HISTORY_DRAW_TIME; +} + +void HistoryResource :: AddToHistory( int iType, const char *szName, int iCount ) +{ + if ( iType != HISTSLOT_ITEM ) + return; + + if ( (((AMMO_PICKUP_GAP * iCurrentHistorySlot) + AMMO_PICKUP_PICK_HEIGHT) > AMMO_PICKUP_HEIGHT_MAX) || (iCurrentHistorySlot >= MAX_HISTORY) ) + { // the pic would have to be drawn too high + // so start from the bottom + iCurrentHistorySlot = 0; + } + + HIST_ITEM *freeslot = &rgAmmoHistory[iCurrentHistorySlot++]; // default to just writing to the first slot + + // I am really unhappy with all the code in this file + + int i = gHUD.GetSpriteIndex( szName ); + if ( i == -1 ) + return; // unknown sprite name, don't add it to history + + freeslot->iId = i; + freeslot->type = iType; + freeslot->iCount = iCount; + + HISTORY_DRAW_TIME = CVAR_GET_FLOAT( "hud_drawhistory_time" ); + freeslot->DisplayTime = gHUD.m_flTime + HISTORY_DRAW_TIME; +} + + +void HistoryResource :: CheckClearHistory( void ) +{ + for ( int i = 0; i < MAX_HISTORY; i++ ) + { + if ( rgAmmoHistory[i].type ) + return; + } + + iCurrentHistorySlot = 0; +} + +// +// Draw Ammo pickup history +// +int HistoryResource :: DrawAmmoHistory( float flTime ) +{ + for ( int i = 0; i < MAX_HISTORY; i++ ) + { + if ( rgAmmoHistory[i].type ) + { + rgAmmoHistory[i].DisplayTime = min( rgAmmoHistory[i].DisplayTime, gHUD.m_flTime + HISTORY_DRAW_TIME ); + + if ( rgAmmoHistory[i].DisplayTime <= flTime ) + { // pic drawing time has expired + memset( &rgAmmoHistory[i], 0, sizeof(HIST_ITEM) ); + CheckClearHistory(); + } + else if ( rgAmmoHistory[i].type == HISTSLOT_AMMO ) + { + wrect_t rcPic; + HSPRITE *spr = gWR.GetAmmoPicFromWeapon( rgAmmoHistory[i].iId, rcPic ); + + int r, g, b; + UnpackRGB(r,g,b, RGB_YELLOWISH); + float scale = (rgAmmoHistory[i].DisplayTime - flTime) * 80; + ScaleColors(r, g, b, min(scale, 255) ); + + // Draw the pic + int ypos = ScreenHeight - (AMMO_PICKUP_PICK_HEIGHT + (AMMO_PICKUP_GAP * i)); + int xpos = ScreenWidth - 24; + if ( spr && *spr ) // weapon isn't loaded yet so just don't draw the pic + { // the dll has to make sure it has sent info the weapons you need + SPR_Set( *spr, r, g, b ); + SPR_DrawAdditive( 0, xpos, ypos, &rcPic ); + } + + // Draw the number + gHUD.DrawHudNumberString( xpos - 10, ypos, xpos - 100, rgAmmoHistory[i].iCount, r, g, b ); + } + else if ( rgAmmoHistory[i].type == HISTSLOT_WEAP ) + { + WEAPON *weap = gWR.GetWeapon( rgAmmoHistory[i].iId ); + + if ( !weap ) + return 1; // we don't know about the weapon yet, so don't draw anything + + int r, g, b; + UnpackRGB(r,g,b, RGB_YELLOWISH); + + if ( !gWR.HasAmmo( weap ) ) + UnpackRGB(r,g,b, RGB_REDISH); // if the weapon doesn't have ammo, display it as red + + float scale = (rgAmmoHistory[i].DisplayTime - flTime) * 80; + ScaleColors(r, g, b, min(scale, 255) ); + + int ypos = ScreenHeight - (AMMO_PICKUP_PICK_HEIGHT + (AMMO_PICKUP_GAP * i)); + int xpos = ScreenWidth - (weap->rcInactive.right - weap->rcInactive.left); + SPR_Set( weap->hInactive, r, g, b ); + SPR_DrawAdditive( 0, xpos, ypos, &weap->rcInactive ); + } + else if ( rgAmmoHistory[i].type == HISTSLOT_ITEM ) + { + int r, g, b; + + if ( !rgAmmoHistory[i].iId ) + continue; // sprite not loaded + + wrect_t rect = gHUD.GetSpriteRect( rgAmmoHistory[i].iId ); + + UnpackRGB(r,g,b, RGB_YELLOWISH); + float scale = (rgAmmoHistory[i].DisplayTime - flTime) * 80; + ScaleColors(r, g, b, min(scale, 255) ); + + int ypos = ScreenHeight - (AMMO_PICKUP_PICK_HEIGHT + (AMMO_PICKUP_GAP * i)); + int xpos = ScreenWidth - (rect.right - rect.left) - 10; + + SPR_Set( gHUD.GetSprite( rgAmmoHistory[i].iId ), r, g, b ); + SPR_DrawAdditive( 0, xpos, ypos, &rect ); + } + } + } + + + return 1; +} + + diff --git a/ricochet/cl_dll/ammohistory.h b/ricochet/cl_dll/ammohistory.h new file mode 100644 index 0000000..a717508 --- /dev/null +++ b/ricochet/cl_dll/ammohistory.h @@ -0,0 +1,143 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ammohistory.h +// + +// this is the max number of items in each bucket +#define MAX_WEAPON_POSITIONS MAX_WEAPON_SLOTS + +class WeaponsResource +{ +private: + // Information about weapons & ammo + WEAPON rgWeapons[MAX_WEAPONS]; // Weapons Array + + // counts of weapons * ammo + WEAPON* rgSlots[MAX_WEAPON_SLOTS+1][MAX_WEAPON_POSITIONS+1]; // The slots currently in use by weapons. The value is a pointer to the weapon; if it's NULL, no weapon is there + int riAmmo[MAX_AMMO_TYPES]; // count of each ammo type + +public: + void Init( void ) + { + memset( rgWeapons, 0, sizeof rgWeapons ); + Reset(); + } + + void Reset( void ) + { + iOldWeaponBits = 0; + memset( rgSlots, 0, sizeof rgSlots ); + memset( riAmmo, 0, sizeof riAmmo ); + } + +///// WEAPON ///// + int iOldWeaponBits; + + WEAPON *GetWeapon( int iId ) { return &rgWeapons[iId]; } + void AddWeapon( WEAPON *wp ) + { + rgWeapons[ wp->iId ] = *wp; + LoadWeaponSprites( &rgWeapons[ wp->iId ] ); + } + + void PickupWeapon( WEAPON *wp ) + { + rgSlots[ wp->iSlot ][ wp->iSlotPos ] = wp; + } + + void DropWeapon( WEAPON *wp ) + { + rgSlots[ wp->iSlot ][ wp->iSlotPos ] = NULL; + } + + void DropAllWeapons( void ) + { + for ( int i = 0; i < MAX_WEAPONS; i++ ) + { + if ( rgWeapons[i].iId ) + DropWeapon( &rgWeapons[i] ); + } + } + + WEAPON* GetWeaponSlot( int slot, int pos ) { return rgSlots[slot][pos]; } + + void LoadWeaponSprites( WEAPON* wp ); + void LoadAllWeaponSprites( void ); + WEAPON* GetFirstPos( int iSlot ); + void SelectSlot( int iSlot, int fAdvance, int iDirection ); + WEAPON* GetNextActivePos( int iSlot, int iSlotPos ); + + int HasAmmo( WEAPON *p ); + +///// AMMO ///// + AMMO GetAmmo( int iId ) { return riAmmo[ iId ]; } + + void SetAmmo( int iId, int iCount ) { riAmmo[ iId ] = iCount; } + + int CountAmmo( int iId ); + + HSPRITE* GetAmmoPicFromWeapon( int iAmmoId, wrect_t& rect ); +}; + +extern WeaponsResource gWR; + + +#define MAX_HISTORY 12 +enum { + HISTSLOT_EMPTY, + HISTSLOT_AMMO, + HISTSLOT_WEAP, + HISTSLOT_ITEM, +}; + +class HistoryResource +{ +private: + struct HIST_ITEM { + int type; + float DisplayTime; // the time at which this item should be removed from the history + int iCount; + int iId; + }; + + HIST_ITEM rgAmmoHistory[MAX_HISTORY]; + +public: + + void Init( void ) + { + Reset(); + } + + void Reset( void ) + { + memset( rgAmmoHistory, 0, sizeof rgAmmoHistory ); + } + + int iHistoryGap; + int iCurrentHistorySlot; + + void AddToHistory( int iType, int iId, int iCount = 0 ); + void AddToHistory( int iType, const char *szName, int iCount = 0 ); + + void CheckClearHistory( void ); + int DrawAmmoHistory( float flTime ); +}; + +extern HistoryResource gHR; + + + diff --git a/ricochet/cl_dll/battery.cpp b/ricochet/cl_dll/battery.cpp new file mode 100644 index 0000000..f5e9d39 --- /dev/null +++ b/ricochet/cl_dll/battery.cpp @@ -0,0 +1,79 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// battery.cpp +// +// implementation of CHudBattery class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +DECLARE_MESSAGE(m_Battery, Battery) + +int CHudBattery::Init(void) +{ + m_iBat = 0; + m_fFade = 0; + m_iFlags = 0; + + HOOK_MESSAGE(Battery); + + gHUD.AddHudElem(this); + + return 1; +}; + + +int CHudBattery::VidInit(void) +{ + int HUD_suit_empty = gHUD.GetSpriteIndex( "suit_empty" ); + int HUD_suit_full = gHUD.GetSpriteIndex( "suit_full" ); + + m_hSprite1 = m_hSprite2 = 0; // delaying get sprite handles until we know the sprites are loaded + m_prc1 = &gHUD.GetSpriteRect( HUD_suit_empty ); + m_prc2 = &gHUD.GetSpriteRect( HUD_suit_full ); + m_iHeight = m_prc2->bottom - m_prc1->top; + m_fFade = 0; + return 1; +}; + +int CHudBattery:: MsgFunc_Battery(const char *pszName, int iSize, void *pbuf ) +{ + m_iFlags |= HUD_ACTIVE; + + + BEGIN_READ( pbuf, iSize ); + int x = READ_SHORT(); + + if (x != m_iBat) + { + m_fFade = FADE_TIME; + m_iBat = x; + } + + return 1; +} + + +int CHudBattery::Draw(float flTime) +{ + // No Armor in Discwar + return 1; +} \ No newline at end of file diff --git a/ricochet/cl_dll/camera.h b/ricochet/cl_dll/camera.h new file mode 100644 index 0000000..607142b --- /dev/null +++ b/ricochet/cl_dll/camera.h @@ -0,0 +1,31 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Camera.h -- defines and such for a 3rd person camera +// NOTE: must include quakedef.h first + +#ifndef _CAMERA_H_ +#define _CAMEA_H_ + +// pitch, yaw, dist +extern vec3_t cam_ofs; +// Using third person camera +extern int cam_thirdperson; + +void CAM_Init( void ); +void CAM_ClearStates( void ); +void CAM_StartMouseMove(void); +void CAM_EndMouseMove(void); + +#endif // _CAMERA_H_ diff --git a/ricochet/cl_dll/cdll_int.cpp b/ricochet/cl_dll/cdll_int.cpp new file mode 100644 index 0000000..c4d9af4 --- /dev/null +++ b/ricochet/cl_dll/cdll_int.cpp @@ -0,0 +1,278 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cdll_int.c +// +// this implementation handles the linking of the engine to the DLL +// + +#include "hud.h" +#include "cl_util.h" +#include +#include "netadr.h" +#include "vgui_SchemeManager.h" + +#include "interface.h" + +cl_enginefunc_t gEngfuncs; +CHud gHUD ; +TeamFortressViewport *gViewPort = NULL; + +extern "C" +{ +#include "pm_shared.h" +} + +#include "hud_servers.h" +#include "vgui_int.h" + +CSysModule *g_hTrackerModule = NULL; +#ifdef _WIN32 +#endif +void InitInput (void); +void EV_HookEvents( void ); +void IN_Commands( void ); + +/* +========================== + Initialize + +Called when the DLL is first loaded. +========================== +*/ +extern "C" +{ +int EXPORT Initialize( cl_enginefunc_t *pEnginefuncs, int iVersion ); +int EXPORT HUD_VidInit( void ); +int EXPORT HUD_Init( void ); +int EXPORT HUD_Redraw( float flTime, int intermission ); +int EXPORT HUD_UpdateClientData( client_data_t *cdata, float flTime ); +int EXPORT HUD_Reset ( void ); +void EXPORT HUD_PlayerMove( struct playermove_s *ppmove, int server ); +void EXPORT HUD_PlayerMoveInit( struct playermove_s *ppmove ); +char EXPORT HUD_PlayerMoveTexture( char *name ); +int EXPORT HUD_ConnectionlessPacket( struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); +int EXPORT HUD_GetHullBounds( int hullnumber, float *mins, float *maxs ); +void EXPORT HUD_Frame( double time ); +void EXPORT HUD_PostRunCmd( struct local_state_s *from, struct local_state_s *to, struct usercmd_s *cmd, int runfuncs, double time, unsigned int random_seed ); +void EXPORT HUD_VoiceStatus(int entindex, qboolean bTalking); +} + +/* +================================ +HUD_GetHullBounds + + Engine calls this to enumerate player collision hulls, for prediction. Return 0 if the hullnumber doesn't exist. +================================ +*/ +int EXPORT HUD_GetHullBounds( int hullnumber, float *mins, float *maxs ) +{ + int iret = 0; + + switch ( hullnumber ) + { + case 0: // Normal player + mins = Vector(-16, -16, -36); + maxs = Vector(16, 16, 36); + iret = 1; + break; + case 1: // Crouched player + mins = Vector(-16, -16, -18 ); + maxs = Vector(16, 16, 18 ); + iret = 1; + break; + case 2: // Point based hull + mins = Vector( 0, 0, 0 ); + maxs = Vector( 0, 0, 0 ); + iret = 1; + break; + } + + return iret; +} + +/* +================================ +HUD_ConnectionlessPacket + + Return 1 if the packet is valid. Set response_buffer_size if you want to send a response packet. Incoming, it holds the max + size of the response_buffer, so you must zero it out if you choose not to respond. +================================ +*/ +int EXPORT HUD_ConnectionlessPacket( struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ) +{ + // Parse stuff from args + int max_buffer_size = *response_buffer_size; + + // Zero it out since we aren't going to respond. + // If we wanted to response, we'd write data into response_buffer + *response_buffer_size = 0; + + // Since we don't listen for anything here, just respond that it's a bogus message + // If we didn't reject the message, we'd return 1 for success instead. + return 0; +} + +void EXPORT HUD_PlayerMoveInit( struct playermove_s *ppmove ) +{ + PM_Init( ppmove ); +} + +char EXPORT HUD_PlayerMoveTexture( char *name ) +{ + return PM_FindTextureType( name ); +} + +void EXPORT HUD_PlayerMove( struct playermove_s *ppmove, int server ) +{ + PM_Move( ppmove, server ); +} + +int EXPORT Initialize( cl_enginefunc_t *pEnginefuncs, int iVersion ) +{ + gEngfuncs = *pEnginefuncs; + + //!!! mwh UNDONE We need to think about our versioning strategy. Do we want to try to be compatible + // with previous versions, especially when we're only 'bonus' functionality? Should it be the engine + // that decides if the DLL is compliant? + + if (iVersion != CLDLL_INTERFACE_VERSION) + return 0; + + memcpy(&gEngfuncs, pEnginefuncs, sizeof(cl_enginefunc_t)); + + EV_HookEvents(); + + return 1; +} + + +/* +========================== + HUD_VidInit + +Called when the game initializes +and whenever the vid_mode is changed +so the HUD can reinitialize itself. +========================== +*/ + +int EXPORT HUD_VidInit( void ) +{ + gHUD.VidInit(); + + VGui_Startup(); + + return 1; +} + +/* +========================== + HUD_Init + +Called whenever the client connects +to a server. Reinitializes all +the hud variables. +========================== +*/ + +int EXPORT HUD_Init( void ) +{ + InitInput(); + gHUD.Init(); + Scheme_Init(); + return 1; +} + + +/* +========================== + HUD_Redraw + +called every screen frame to +redraw the HUD. +=========================== +*/ + +int EXPORT HUD_Redraw( float time, int intermission ) +{ + gHUD.Redraw( time, intermission ); + + return 1; +} + + +/* +========================== + HUD_UpdateClientData + +called every time shared client +dll/engine data gets changed, +and gives the cdll a chance +to modify the data. + +returns 1 if anything has been changed, 0 otherwise. +========================== +*/ + +int EXPORT HUD_UpdateClientData(client_data_t *pcldata, float flTime ) +{ + IN_Commands(); + + return gHUD.UpdateClientData(pcldata, flTime ); +} + +/* +========================== + HUD_Reset + +Called at start and end of demos to restore to "non"HUD state. +========================== +*/ + +int EXPORT HUD_Reset( void ) +{ + gHUD.VidInit(); + return 1; +} + + +/* +========================== +HUD_Frame + +Called by engine every frame that client .dll is loaded +========================== +*/ + +void EXPORT HUD_Frame( double time ) +{ + ServersThink( time ); + + GetClientVoiceMgr()->Frame(time); +} + +/* +========================== +HUD_VoiceStatus + +Called when a player starts or stops talking. +========================== +*/ + +void EXPORT HUD_VoiceStatus(int entindex, qboolean bTalking) +{ + GetClientVoiceMgr()->UpdateSpeakerStatus(entindex, bTalking); +} \ No newline at end of file diff --git a/ricochet/cl_dll/cl_dll.dsp b/ricochet/cl_dll/cl_dll.dsp new file mode 100644 index 0000000..58b0ddb --- /dev/null +++ b/ricochet/cl_dll/cl_dll.dsp @@ -0,0 +1,555 @@ +# Microsoft Developer Studio Project File - Name="cl_dll" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=cl_dll - 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 "cl_dll.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 "cl_dll.mak" CFG="cl_dll - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "cl_dll - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "cl_dll - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$Goldsrc/discwar/cl_dll", HGEBAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "cl_dll - 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" /YX /c +# ADD CPP /nologo /MT /W3 /GX /Zi /O2 /I "..\\" /I "..\dlls" /I ".\\" /I "..\..\game_shared" /I "..\..\engine" /I "..\..\public" /I "..\..\common" /I "..\pm_shared" /I "..\..\utils\vgui\include" /I "..\..\external" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "CLIENT_DLL" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /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 /subsystem:windows /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib winmm.lib ../../utils/vgui/lib/win32_vc6/vgui.lib wsock32.lib ..\..\lib\public\sdl2.lib /nologo /subsystem:windows /dll /map /debug /machine:I386 /out:".\Release\client.dll" +# SUBTRACT LINK32 /pdb:none +# Begin Custom Build - Copying to d:\quiver\ricochet\cl_dlls +InputDir=.\Release +ProjDir=. +InputPath=.\Release\client.dll +SOURCE="$(InputPath)" + +BuildCmds= \ + call ..\..\filecopy.bat $(InputPath) $(ProjDir)\..\..\..\game\mod\cl_dlls\client.dll \ + call ..\..\filecopy.bat $(InputDir)\client.pdb $(ProjDir)\..\..\..\game\mod\cl_dlls\client.pdb \ + + +"$(ProjDir)\..\..\..\game\mod\cl_dlls\client.dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"$(ProjDir)\..\..\..\game\mod\cl_dlls\client.pdb" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) +# End Custom Build + +!ELSEIF "$(CFG)" == "cl_dll - 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" /YX /c +# ADD CPP /nologo /G5 /MTd /W3 /Gm /GX /ZI /Od /I "..\\" /I "..\dlls" /I ".\\" /I "..\..\game_shared" /I "..\..\engine" /I "..\..\public" /I "..\..\common" /I "..\pm_shared" /I "..\..\utils\vgui\include" /I "..\..\external" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "CLIENT_DLL" /FR /YX /FD /c +# ADD BASE MTL /nologo /D "_DEBUG" /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 gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 +# ADD LINK32 oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib winmm.lib ../../utils/vgui/lib/win32_vc6/vgui.lib wsock32.lib ..\..\lib\public\sdl2.lib /nologo /subsystem:windows /dll /debug /machine:I386 /out:".\Debug\client.dll" +# SUBTRACT LINK32 /pdb:none +# Begin Custom Build - Copying to \quiver\ricochet\cl_dlls +ProjDir=. +InputPath=.\Debug\client.dll +SOURCE="$(InputPath)" + +"$(ProjDir)\..\..\..\game\mod\cl_dlls\client.dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + call ..\..\filecopy.bat $(InputPath) $(ProjDir)\..\..\..\game\mod\cl_dlls\client.dll + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "cl_dll - Win32 Release" +# Name "cl_dll - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Group "hl" + +# PROP Default_Filter "*.cpp" +# Begin Source File + +SOURCE=..\dlls\wpn_shared\disc_weapon_disc.cpp +# End Source File +# Begin Source File + +SOURCE=.\ev_hldm.cpp +# End Source File +# Begin Source File + +SOURCE=.\hl\hl_baseentity.cpp +# End Source File +# Begin Source File + +SOURCE=.\hl\hl_events.cpp +# End Source File +# Begin Source File + +SOURCE=.\hl\hl_objects.cpp +# End Source File +# Begin Source File + +SOURCE=.\hl\hl_weapons.cpp +# End Source File +# Begin Source File + +SOURCE=.\Ricochet_JumpPads.cpp +# End Source File +# End Group +# Begin Source File + +SOURCE=.\ammo.cpp +# End Source File +# Begin Source File + +SOURCE=.\ammo_secondary.cpp +# End Source File +# Begin Source File + +SOURCE=.\ammohistory.cpp +# End Source File +# Begin Source File + +SOURCE=.\battery.cpp +# End Source File +# Begin Source File + +SOURCE=.\cdll_int.cpp +# End Source File +# Begin Source File + +SOURCE=.\com_weapons.cpp +# End Source File +# Begin Source File + +SOURCE=.\death.cpp +# End Source File +# Begin Source File + +SOURCE=.\demo.cpp +# End Source File +# Begin Source File + +SOURCE=.\entity.cpp +# End Source File +# Begin Source File + +SOURCE=.\ev_common.cpp +# End Source File +# Begin Source File + +SOURCE=.\events.cpp +# End Source File +# Begin Source File + +SOURCE=.\flashlight.cpp +# End Source File +# Begin Source File + +SOURCE=.\GameStudioModelRenderer.cpp +# End Source File +# Begin Source File + +SOURCE=.\geiger.cpp +# End Source File +# Begin Source File + +SOURCE=.\health.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud_msg.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud_redraw.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud_servers.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud_update.cpp +# End Source File +# Begin Source File + +SOURCE=.\in_camera.cpp +# End Source File +# Begin Source File + +SOURCE=.\input.cpp +# End Source File +# Begin Source File + +SOURCE=.\inputw32.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\public\interface.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu.cpp +# End Source File +# Begin Source File + +SOURCE=.\message.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\common\parsemsg.cpp +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_debug.c +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_math.c +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_shared.c +# End Source File +# Begin Source File + +SOURCE=.\saytext.cpp +# End Source File +# Begin Source File + +SOURCE=.\status_icons.cpp +# End Source File +# Begin Source File + +SOURCE=.\statusbar.cpp +# End Source File +# Begin Source File + +SOURCE=.\studio_util.cpp +# End Source File +# Begin Source File + +SOURCE=.\StudioModelRenderer.cpp +# End Source File +# Begin Source File + +SOURCE=.\text_message.cpp +# End Source File +# Begin Source File + +SOURCE=.\train.cpp +# End Source File +# Begin Source File + +SOURCE=.\tri.cpp +# End Source File +# Begin Source File + +SOURCE=.\util.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_checkbutton2.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_ConsolePanel.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_ControlConfigPanel.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_CustomObjects.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_discobjects.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_grid.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_helpers.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_int.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_listbox.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_loadtga.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_MOTDWindow.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_SchemeManager.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_ScorePanel.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_scrollbar2.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_ServerBrowser.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vgui_slider2.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_TeamFortressViewport.cpp +# End Source File +# Begin Source File + +SOURCE=.\view.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\voice_banmgr.cpp +# End Source File +# Begin Source File + +SOURCE=.\voice_status.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=.\ammo.h +# End Source File +# Begin Source File + +SOURCE=.\ammohistory.h +# End Source File +# Begin Source File + +SOURCE=.\camera.h +# End Source File +# Begin Source File + +SOURCE=.\cl_dll.h +# End Source File +# Begin Source File + +SOURCE=.\cl_util.h +# End Source File +# Begin Source File + +SOURCE=.\com_weapons.h +# End Source File +# Begin Source File + +SOURCE=.\demo.h +# End Source File +# Begin Source File + +SOURCE=.\ev_hldm.h +# End Source File +# Begin Source File + +SOURCE=.\eventscripts.h +# End Source File +# Begin Source File + +SOURCE=.\GameStudioModelRenderer.h +# End Source File +# Begin Source File + +SOURCE=.\health.h +# End Source File +# Begin Source File + +SOURCE=.\hud.h +# End Source File +# Begin Source File + +SOURCE=.\hud_iface.h +# End Source File +# Begin Source File + +SOURCE=.\hud_servers.h +# End Source File +# Begin Source File + +SOURCE=.\hud_servers_priv.h +# End Source File +# Begin Source File + +SOURCE=.\in_defs.h +# End Source File +# Begin Source File + +SOURCE=.\kbutton.h +# End Source File +# Begin Source File + +SOURCE=..\..\common\parsemsg.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_debug.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_defs.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_info.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_materials.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_movevars.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_shared.h +# End Source File +# Begin Source File + +SOURCE=.\Ricochet_BSPFile.h +# End Source File +# Begin Source File + +SOURCE=.\Ricochet_JumpPads.h +# End Source File +# Begin Source File + +SOURCE=.\studio_util.h +# End Source File +# Begin Source File + +SOURCE=.\StudioModelRenderer.h +# End Source File +# Begin Source File + +SOURCE=.\util_vector.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_ConsolePanel.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_ControlConfigPanel.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_discobjects.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_int.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_SchemeManager.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_ScorePanel.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_ServerBrowser.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_TeamFortressViewport.h +# End Source File +# Begin Source File + +SOURCE=.\view.h +# End Source File +# Begin Source File + +SOURCE=.\wrect.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/ricochet/cl_dll/cl_dll.h b/ricochet/cl_dll/cl_dll.h new file mode 100644 index 0000000..2662c28 --- /dev/null +++ b/ricochet/cl_dll/cl_dll.h @@ -0,0 +1,42 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cl_dll.h +// + +// 4-23-98 JOHN + +// +// This DLL is linked by the client when they first initialize. +// This DLL is responsible for the following tasks: +// - Loading the HUD graphics upon initialization +// - Drawing the HUD graphics every frame +// - Handling the custum HUD-update packets +// +typedef unsigned char byte; +typedef unsigned short word; +typedef float vec_t; +typedef int (*pfnUserMsgHook)(const char *pszName, int iSize, void *pbuf); + +#include "util_vector.h" +#ifdef _WIN32 +#define EXPORT _declspec( dllexport ) +#else +#define EXPORT __attribute__ ((visibility("default"))) +#endif +#include "../engine/cdll_int.h" +#include "../dlls/cdll_dll.h" + +extern cl_enginefunc_t gEngfuncs; diff --git a/ricochet/cl_dll/cl_util.h b/ricochet/cl_dll/cl_util.h new file mode 100644 index 0000000..017327e --- /dev/null +++ b/ricochet/cl_dll/cl_util.h @@ -0,0 +1,189 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// util.h +// + +#include "cvardef.h" + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +#include // for safe_sprintf() +#include // " +#include + +// Macros to hook function calls into the HUD object +#define HOOK_MESSAGE(x) gEngfuncs.pfnHookUserMsg(#x, __MsgFunc_##x ); + +#define DECLARE_MESSAGE(y, x) int __MsgFunc_##x(const char *pszName, int iSize, void *pbuf) \ + { \ + return gHUD.y.MsgFunc_##x(pszName, iSize, pbuf ); \ + } + + +#define HOOK_COMMAND(x, y) gEngfuncs.pfnAddCommand( x, __CmdFunc_##y ); +#define DECLARE_COMMAND(y, x) void __CmdFunc_##x( void ) \ + { \ + gHUD.y.UserCmd_##x( ); \ + } + +inline float CVAR_GET_FLOAT( const char *x ) { return gEngfuncs.pfnGetCvarFloat( (char*)x ); } +inline char* CVAR_GET_STRING( const char *x ) { return gEngfuncs.pfnGetCvarString( (char*)x ); } +inline struct cvar_s *CVAR_CREATE( const char *cv, const char *val, const int flags ) { return gEngfuncs.pfnRegisterVariable( (char*)cv, (char*)val, flags ); } + +#define SPR_Load (*gEngfuncs.pfnSPR_Load) +#define SPR_Set (*gEngfuncs.pfnSPR_Set) +#define SPR_Frames (*gEngfuncs.pfnSPR_Frames) +#define SPR_GetList (*gEngfuncs.pfnSPR_GetList) + +// SPR_Draw draws a the current sprite as solid +#define SPR_Draw (*gEngfuncs.pfnSPR_Draw) +// SPR_DrawHoles draws the current sprites, with color index255 not drawn (transparent) +#define SPR_DrawHoles (*gEngfuncs.pfnSPR_DrawHoles) +// SPR_DrawAdditive adds the sprites RGB values to the background (additive transulency) +#define SPR_DrawAdditive (*gEngfuncs.pfnSPR_DrawAdditive) + +// SPR_EnableScissor sets a clipping rect for HUD sprites. (0,0) is the top-left hand corner of the screen. +#define SPR_EnableScissor (*gEngfuncs.pfnSPR_EnableScissor) +// SPR_DisableScissor disables the clipping rect +#define SPR_DisableScissor (*gEngfuncs.pfnSPR_DisableScissor) +// +#define FillRGBA (*gEngfuncs.pfnFillRGBA) + + +// ScreenHeight returns the height of the screen, in pixels +#define ScreenHeight (gHUD.m_scrinfo.iHeight) +// ScreenWidth returns the width of the screen, in pixels +#define ScreenWidth (gHUD.m_scrinfo.iWidth) + +#define GetScreenInfo (*gEngfuncs.pfnGetScreenInfo) +#define ServerCmd (*gEngfuncs.pfnServerCmd) +#define ClientCmd (*gEngfuncs.pfnClientCmd) +#define SetCrosshair (*gEngfuncs.pfnSetCrosshair) +#define AngleVectors (*gEngfuncs.pfnAngleVectors) + + +// Gets the height & width of a sprite, at the specified frame +inline int SPR_Height( HSPRITE x, int f ) { return gEngfuncs.pfnSPR_Height(x, f); } +inline int SPR_Width( HSPRITE x, int f ) { return gEngfuncs.pfnSPR_Width(x, f); } + +inline client_textmessage_t *TextMessageGet( const char *pName ) { return gEngfuncs.pfnTextMessageGet( pName ); } +inline int TextMessageDrawChar( int x, int y, int number, int r, int g, int b ) +{ + return gEngfuncs.pfnDrawCharacter( x, y, number, r, g, b ); +} + +inline int DrawConsoleString( int x, int y, const char *string ) +{ + return gEngfuncs.pfnDrawConsoleString( x, y, (char*) string ); +} + +inline void GetConsoleStringSize( const char *string, int *width, int *height ) +{ + gEngfuncs.pfnDrawConsoleStringLen( string, width, height ); +} + +inline int ConsoleStringLen( const char *string ) +{ + int _width, _height; + GetConsoleStringSize( string, &_width, &_height ); + return _width; +} + +inline void ConsolePrint( const char *string ) +{ + gEngfuncs.pfnConsolePrint( string ); +} + +inline void CenterPrint( const char *string ) +{ + gEngfuncs.pfnCenterPrint( string ); +} + + +inline char *safe_strcpy( char *dst, const char *src, int len_dst) +{ + if( len_dst <= 0 ) + { + return NULL; // this is bad + } + + strncpy(dst,src,len_dst); + dst[ len_dst - 1 ] = '\0'; + + return dst; +} + +inline int safe_sprintf( char *dst, int len_dst, const char *format, ...) +{ + if( len_dst <= 0 ) + { + return -1; // this is bad + } + + va_list v; + + va_start(v, format); + + _vsnprintf(dst,len_dst,format,v); + + va_end(v); + + dst[ len_dst - 1 ] = '\0'; + + return 0; +} + +// returns the players name of entity no. +#define GetPlayerInfo (*gEngfuncs.pfnGetPlayerInfo) + +// sound functions +inline void PlaySound( char *szSound, float vol ) { gEngfuncs.pfnPlaySoundByName( szSound, vol ); } +inline void PlaySound( int iSound, float vol ) { gEngfuncs.pfnPlaySoundByIndex( iSound, vol ); } + +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#define fabs(x) ((x) > 0 ? (x) : 0 - (x)) + +void ScaleColors( int &r, int &g, int &b, int a ); + +#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 VectorClear(a) { a[0]=0.0;a[1]=0.0;a[2]=0.0;} +float Length(const float *v); +void VectorMA (const float *veca, float scale, const float *vecb, float *vecc); +void VectorScale (const float *in, float scale, float *out); +float VectorNormalize (float *v); +void VectorInverse ( float *v ); + +extern vec3_t vec3_origin; +// disable 'possible loss of data converting float to int' warning message +#pragma warning( disable: 4244 ) +// disable 'truncation from 'const double' to 'float' warning message +#pragma warning( disable: 4305 ) + +inline void UnpackRGB(int &r, int &g, int &b, unsigned long ulRGB)\ +{\ + r = (ulRGB & 0xFF0000) >>16;\ + g = (ulRGB & 0xFF00) >> 8;\ + b = ulRGB & 0xFF;\ +} + +HSPRITE LoadSprite(const char *pszName); diff --git a/ricochet/cl_dll/com_weapons.cpp b/ricochet/cl_dll/com_weapons.cpp new file mode 100644 index 0000000..677ecbe --- /dev/null +++ b/ricochet/cl_dll/com_weapons.cpp @@ -0,0 +1,278 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +// Com_Weapons.cpp +// Shared weapons common/shared functions +#include +#include "hud.h" +#include "cl_util.h" +#include "com_weapons.h" + +#include "const.h" +#include "entity_state.h" +#include "r_efx.h" + +// g_runfuncs is true if this is the first time we've "predicated" a particular movement/firing +// command. If it is 1, then we should play events/sounds etc., otherwise, we just will be +// updating state info, but not firing events +int g_runfuncs = 0; + +// During our weapon prediction processing, we'll need to reference some data that is part of +// the final state passed into the postthink functionality. We'll set this pointer and then +// reset it to NULL as appropriate +struct local_state_s *g_finalstate = NULL; + +/* +==================== +COM_Log + +Log debug messages to file ( appends ) +==================== +*/ +void COM_Log( char *pszFile, char *fmt, ...) +{ + va_list argptr; + char string[1024]; + FILE *fp; + char *pfilename; + + if ( !pszFile ) + { + pfilename = "c:\\hllog.txt"; + } + else + { + pfilename = pszFile; + } + + va_start (argptr,fmt); + vsprintf (string, fmt,argptr); + va_end (argptr); + + fp = fopen( pfilename, "a+t"); + if (fp) + { + fprintf(fp, "%s", string); + fclose(fp); + } +} + +// remember the current animation for the view model, in case we get out of sync with +// server. +static int g_currentanim; + +/* +===================== +HUD_SendWeaponAnim + +Change weapon model animation +===================== +*/ +void HUD_SendWeaponAnim( int iAnim, int body, int force ) +{ + // Don't actually change it. + if ( !g_runfuncs && !force ) + return; + + g_currentanim = iAnim; + + // Tell animation system new info + gEngfuncs.pfnWeaponAnim( iAnim, body ); +} + +/* +===================== +HUD_GetWeaponAnim + +Retrieve current predicted weapon animation +===================== +*/ +int HUD_GetWeaponAnim( void ) +{ + return g_currentanim; +} + +/* +===================== +HUD_PlaySound + +Play a sound, if we are seeing this command for the first time +===================== +*/ +void HUD_PlaySound( char *sound, float volume ) +{ + if ( !g_runfuncs || !g_finalstate ) + return; + + gEngfuncs.pfnPlaySoundByNameAtLocation( sound, volume, (float *)&g_finalstate->playerstate.origin ); +} + +/* +===================== +HUD_PlaybackEvent + +Directly queue up an event on the client +===================== +*/ +void HUD_PlaybackEvent( int flags, const edict_t *pInvoker, unsigned short eventindex, float delay, + float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ) +{ + vec3_t org; + vec3_t ang; + + if ( !g_runfuncs || !g_finalstate ) + return; + + // Weapon prediction events are assumed to occur at the player's origin + org = g_finalstate->playerstate.origin; + ang = v_angles; + gEngfuncs.pfnPlaybackEvent( flags, pInvoker, eventindex, delay, (float *)&org, (float *)&ang, fparam1, fparam2, iparam1, iparam2, bparam1, bparam2 ); +} + +/* +===================== +HUD_SetMaxSpeed + +===================== +*/ +void HUD_SetMaxSpeed( const edict_t *ed, float speed ) +{ +} + + +/* +===================== +UTIL_WeaponTimeBase + +Always 0.0 on client, even if not predicting weapons ( won't get called + in that case ) +===================== +*/ +float UTIL_WeaponTimeBase( void ) +{ + return 0.0; +} + +static unsigned int glSeed = 0; + +unsigned int seed_table[ 256 ] = +{ + 28985, 27138, 26457, 9451, 17764, 10909, 28790, 8716, 6361, 4853, 17798, 21977, 19643, 20662, 10834, 20103, + 27067, 28634, 18623, 25849, 8576, 26234, 23887, 18228, 32587, 4836, 3306, 1811, 3035, 24559, 18399, 315, + 26766, 907, 24102, 12370, 9674, 2972, 10472, 16492, 22683, 11529, 27968, 30406, 13213, 2319, 23620, 16823, + 10013, 23772, 21567, 1251, 19579, 20313, 18241, 30130, 8402, 20807, 27354, 7169, 21211, 17293, 5410, 19223, + 10255, 22480, 27388, 9946, 15628, 24389, 17308, 2370, 9530, 31683, 25927, 23567, 11694, 26397, 32602, 15031, + 18255, 17582, 1422, 28835, 23607, 12597, 20602, 10138, 5212, 1252, 10074, 23166, 19823, 31667, 5902, 24630, + 18948, 14330, 14950, 8939, 23540, 21311, 22428, 22391, 3583, 29004, 30498, 18714, 4278, 2437, 22430, 3439, + 28313, 23161, 25396, 13471, 19324, 15287, 2563, 18901, 13103, 16867, 9714, 14322, 15197, 26889, 19372, 26241, + 31925, 14640, 11497, 8941, 10056, 6451, 28656, 10737, 13874, 17356, 8281, 25937, 1661, 4850, 7448, 12744, + 21826, 5477, 10167, 16705, 26897, 8839, 30947, 27978, 27283, 24685, 32298, 3525, 12398, 28726, 9475, 10208, + 617, 13467, 22287, 2376, 6097, 26312, 2974, 9114, 21787, 28010, 4725, 15387, 3274, 10762, 31695, 17320, + 18324, 12441, 16801, 27376, 22464, 7500, 5666, 18144, 15314, 31914, 31627, 6495, 5226, 31203, 2331, 4668, + 12650, 18275, 351, 7268, 31319, 30119, 7600, 2905, 13826, 11343, 13053, 15583, 30055, 31093, 5067, 761, + 9685, 11070, 21369, 27155, 3663, 26542, 20169, 12161, 15411, 30401, 7580, 31784, 8985, 29367, 20989, 14203, + 29694, 21167, 10337, 1706, 28578, 887, 3373, 19477, 14382, 675, 7033, 15111, 26138, 12252, 30996, 21409, + 25678, 18555, 13256, 23316, 22407, 16727, 991, 9236, 5373, 29402, 6117, 15241, 27715, 19291, 19888, 19847 +}; + +unsigned int U_Random( void ) +{ + glSeed *= 69069; + glSeed += seed_table[ glSeed & 0xff ]; + + return ( ++glSeed & 0x0fffffff ); +} + +void U_Srand( unsigned int seed ) +{ + glSeed = seed_table[ seed & 0xff ]; +} + +/* +===================== +UTIL_SharedRandomLong +===================== +*/ +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ) +{ + + unsigned int range; + + U_Srand( (int)seed + low + high ); + + range = high - low + 1; + if ( !(range - 1) ) + { + return low; + } + else + { + int offset; + int rnum; + + rnum = U_Random(); + + offset = rnum % range; + + return (low + offset); + } +} + +/* +===================== +UTIL_SharedRandomFloat +===================== +*/ +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ) +{ + // + unsigned int range; + + U_Srand( (int)seed + *(int *)&low + *(int *)&high ); + + U_Random(); + U_Random(); + + range = high - low; + if ( !range ) + { + return low; + } + else + { + int tensixrand; + float offset; + + tensixrand = U_Random() & 65535; + + offset = (float)tensixrand / 65536.0; + + return (low + offset * range ); + } +} + +/* +====================== +stub_* + +stub functions for such things as precaching. So we don't have to modify weapons code that + is compiled into both game and client .dlls. +====================== +*/ +int stub_PrecacheModel ( char* s ) { return 0; } +int stub_PrecacheSound ( char* s ) { return 0; } +unsigned short stub_PrecacheEvent ( int type, const char *s ) { return 0; } +const char *stub_NameForFunction ( uint32 function ) { return "func"; } +void stub_SetModel ( edict_t *e, const char *m ) {} diff --git a/ricochet/cl_dll/com_weapons.h b/ricochet/cl_dll/com_weapons.h new file mode 100644 index 0000000..dca35ee --- /dev/null +++ b/ricochet/cl_dll/com_weapons.h @@ -0,0 +1,41 @@ +// com_weapons.h +// Shared weapons common function prototypes +#if !defined( COM_WEAPONSH ) +#define COM_WEAPONSH +#ifdef _WIN32 +#pragma once +#endif + +#include "hud_iface.h" + +extern "C" +{ + void EXPORT HUD_PostRunCmd( struct local_state_s *from, struct local_state_s *to, struct usercmd_s *cmd, int runfuncs, double time, unsigned int random_seed ); +} + +void COM_Log( char *pszFile, char *fmt, ...); +int CL_IsDead( void ); + +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ); +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ); + +int HUD_GetWeaponAnim( void ); +void HUD_SendWeaponAnim( int iAnim, int body, int force ); +void HUD_PlaySound( char *sound, float volume ); +void HUD_PlaybackEvent( int flags, const struct edict_s *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); +void HUD_SetMaxSpeed( const struct edict_s *ed, float speed ); +int stub_PrecacheModel( char* s ); +int stub_PrecacheSound( char* s ); +unsigned short stub_PrecacheEvent( int type, const char *s ); +const char *stub_NameForFunction ( uint32 function ); +void stub_SetModel ( struct edict_s *e, const char *m ); + + +extern cvar_t *cl_lw; + +extern int g_runfuncs; +extern vec3_t v_angles; +extern float g_lastFOV; +extern struct local_state_s *g_finalstate; + +#endif \ No newline at end of file diff --git a/ricochet/cl_dll/death.cpp b/ricochet/cl_dll/death.cpp new file mode 100644 index 0000000..f945726 --- /dev/null +++ b/ricochet/cl_dll/death.cpp @@ -0,0 +1,307 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// death notice +// +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "vgui_TeamFortressViewport.h" + +DECLARE_MESSAGE( m_DeathNotice, DeathMsg ); + +struct DeathNoticeItem { + char szKiller[MAX_PLAYER_NAME_LENGTH*2]; + char szVictim[MAX_PLAYER_NAME_LENGTH*2]; + int iId; // the index number of the associated sprite + int iSuicide; + int iTeamKill; + int iNonPlayerKill; + float flDisplayTime; + float *KillerColor; + float *VictimColor; +}; + +#define MAX_DEATHNOTICES 4 +static int DEATHNOTICE_DISPLAY_TIME = 6; + +#define DEATHNOTICE_TOP 20 + +DeathNoticeItem rgDeathNoticeList[ MAX_DEATHNOTICES + 1 ]; + +float g_ColorBlue[3] = { 0.6, 0.8, 1.0 }; +float g_ColorRed[3] = { 1.0, 0.6, 0.3 }; +float g_ColorGreen[3] = { 0.6, 1.0, 0.6 }; +float g_ColorYellow[3] = { 1.0, 0.7, 0.0 }; + +float *GetClientColor( int clientIndex ) +{ + const char *teamName = g_PlayerExtraInfo[clientIndex].teamname; + + if ( !teamName || *teamName == 0 ) + return NULL; + + if ( !stricmp( "blue", teamName ) ) + return g_ColorBlue; + else if ( !stricmp( "red", teamName ) ) + return g_ColorRed; + else if ( !stricmp( "green", teamName ) ) + return g_ColorGreen; + else if ( !stricmp( "yellow", teamName ) ) + return g_ColorYellow; + + return NULL; +} + + +int CHudDeathNotice :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( DeathMsg ); + + CVAR_CREATE( "hud_deathnotice_time", "6", 0 ); + + return 1; +} + + +void CHudDeathNotice :: InitHUDData( void ) +{ + memset( rgDeathNoticeList, 0, sizeof(rgDeathNoticeList) ); +} + + +int CHudDeathNotice :: VidInit( void ) +{ + m_HUD_d_skull = gHUD.GetSpriteIndex( "d_skull" ); + + return 1; +} + +int CHudDeathNotice :: Draw( float flTime ) +{ + int x, y, r, g, b; + + for ( int i = 0; i < MAX_DEATHNOTICES; i++ ) + { + if ( rgDeathNoticeList[i].iId == 0 ) + break; // we've gone through them all + + if ( rgDeathNoticeList[i].flDisplayTime < flTime ) + { // display time has expired + // remove the current item from the list + memmove( &rgDeathNoticeList[i], &rgDeathNoticeList[i+1], sizeof(DeathNoticeItem) * (MAX_DEATHNOTICES - i) ); + i--; // continue on the next item; stop the counter getting incremented + continue; + } + + rgDeathNoticeList[i].flDisplayTime = min( rgDeathNoticeList[i].flDisplayTime, gHUD.m_flTime + DEATHNOTICE_DISPLAY_TIME ); + + // Draw the death notice + + // Make it a bigger increment in 640, 'cos the Discwar death sprites are 32 tall, not 16 in 640 + if ( ScreenHeight >= 640 ) + y = DEATHNOTICE_TOP + (36 * i); //!!! + else + y = DEATHNOTICE_TOP + (20 * i); //!!! + + int id = (rgDeathNoticeList[i].iId == -1) ? m_HUD_d_skull : rgDeathNoticeList[i].iId; + x = ScreenWidth - ConsoleStringLen(rgDeathNoticeList[i].szVictim) - (gHUD.GetSpriteRect(id).right - gHUD.GetSpriteRect(id).left); + + if ( !rgDeathNoticeList[i].iSuicide ) + { + x -= (5 + ConsoleStringLen( rgDeathNoticeList[i].szKiller ) ); + + // Draw killers name + if ( rgDeathNoticeList[i].KillerColor ) + gEngfuncs.pfnDrawSetTextColor( rgDeathNoticeList[i].KillerColor[0], rgDeathNoticeList[i].KillerColor[1], rgDeathNoticeList[i].KillerColor[2] ); + x = 5 + DrawConsoleString( x, y, rgDeathNoticeList[i].szKiller ); + } + + r = 255; g = 80; b = 0; + if ( rgDeathNoticeList[i].iTeamKill ) + { + r = 10; g = 240; b = 10; // display it in sickly green + } + + // Draw death weapon + SPR_Set( gHUD.GetSprite(id), r, g, b ); + SPR_DrawAdditive( 0, x, y, &gHUD.GetSpriteRect(id) ); + + x += (gHUD.GetSpriteRect(id).right - gHUD.GetSpriteRect(id).left); + + // Draw victims name (if it was a player that was killed) + if (rgDeathNoticeList[i].iNonPlayerKill == FALSE) + { + if ( rgDeathNoticeList[i].VictimColor ) + gEngfuncs.pfnDrawSetTextColor( rgDeathNoticeList[i].VictimColor[0], rgDeathNoticeList[i].VictimColor[1], rgDeathNoticeList[i].VictimColor[2] ); + x = DrawConsoleString( x, y, rgDeathNoticeList[i].szVictim ); + } + } + + return 1; +} + +// This message handler may be better off elsewhere +int CHudDeathNotice :: MsgFunc_DeathMsg( const char *pszName, int iSize, void *pbuf ) +{ + m_iFlags |= HUD_ACTIVE; + + BEGIN_READ( pbuf, iSize ); + + int killer = READ_BYTE(); + int victim = READ_BYTE(); + + char killedwith[32]; + strcpy( killedwith, "d_" ); + strncat( killedwith, READ_STRING(), 32 ); + + if (gViewPort) + gViewPort->DeathMsg( killer, victim ); + + int i; + for ( i = 0; i < MAX_DEATHNOTICES; i++ ) + { + if ( rgDeathNoticeList[i].iId == 0 ) + break; + } + if ( i == MAX_DEATHNOTICES ) + { // move the rest of the list forward to make room for this item + memmove( rgDeathNoticeList, rgDeathNoticeList+1, sizeof(DeathNoticeItem) * MAX_DEATHNOTICES ); + i = MAX_DEATHNOTICES - 1; + } + + if (gViewPort) + gViewPort->GetAllPlayersInfo(); + + // Get the Killer's name + char *killer_name = g_PlayerInfoList[ killer ].name; + if ( !killer_name ) + { + killer_name = ""; + rgDeathNoticeList[i].szKiller[0] = 0; + } + else + { + rgDeathNoticeList[i].KillerColor = GetClientColor( killer ); + strncpy( rgDeathNoticeList[i].szKiller, killer_name, MAX_PLAYER_NAME_LENGTH ); + rgDeathNoticeList[i].szKiller[MAX_PLAYER_NAME_LENGTH-1] = 0; + } + + // Get the Victim's name + char *victim_name = NULL; + // If victim is -1, the killer killed a specific, non-player object (like a sentrygun) + if ( ((char)victim) != -1 ) + victim_name = g_PlayerInfoList[ victim ].name; + if ( !victim_name ) + { + victim_name = ""; + rgDeathNoticeList[i].szVictim[0] = 0; + } + else + { + rgDeathNoticeList[i].VictimColor = GetClientColor( victim ); + strncpy( rgDeathNoticeList[i].szVictim, victim_name, MAX_PLAYER_NAME_LENGTH ); + rgDeathNoticeList[i].szVictim[MAX_PLAYER_NAME_LENGTH-1] = 0; + } + + // Is it a non-player object kill? + if ( ((char)victim) == -1 ) + { + rgDeathNoticeList[i].iNonPlayerKill = TRUE; + + // Store the object's name in the Victim slot (skip the d_ bit) + strcpy( rgDeathNoticeList[i].szVictim, killedwith+2 ); + } + else + { + if ( killer == victim || killer == 0 ) + rgDeathNoticeList[i].iSuicide = TRUE; + + if ( !strcmp( killedwith, "d_teammate" ) ) + rgDeathNoticeList[i].iTeamKill = TRUE; + } + + // Find the sprite in the list + int spr = gHUD.GetSpriteIndex( killedwith ); + + rgDeathNoticeList[i].iId = spr; + + DEATHNOTICE_DISPLAY_TIME = CVAR_GET_FLOAT( "hud_deathnotice_time" ); + rgDeathNoticeList[i].flDisplayTime = gHUD.m_flTime + DEATHNOTICE_DISPLAY_TIME; + + if (rgDeathNoticeList[i].iNonPlayerKill) + { + ConsolePrint( rgDeathNoticeList[i].szKiller ); + ConsolePrint( " killed a " ); + ConsolePrint( rgDeathNoticeList[i].szVictim ); + ConsolePrint( "\n" ); + } + else + { + // record the death notice in the console + if ( rgDeathNoticeList[i].iSuicide ) + { + ConsolePrint( rgDeathNoticeList[i].szVictim ); + + if ( !strcmp( killedwith, "d_world" ) ) + { + ConsolePrint( " died" ); + } + else + { + ConsolePrint( " killed self" ); + } + } + else if ( rgDeathNoticeList[i].iTeamKill ) + { + ConsolePrint( rgDeathNoticeList[i].szKiller ); + ConsolePrint( " killed his teammate " ); + ConsolePrint( rgDeathNoticeList[i].szVictim ); + } + else + { + ConsolePrint( rgDeathNoticeList[i].szKiller ); + ConsolePrint( " killed " ); + ConsolePrint( rgDeathNoticeList[i].szVictim ); + } + + if ( killedwith && *killedwith && (*killedwith > 13 ) && strcmp( killedwith, "d_world" ) && !rgDeathNoticeList[i].iTeamKill ) + { + ConsolePrint( " with " ); + + // replace the code names with the 'real' names + if ( !strcmp( killedwith+2, "egon" ) ) + strcpy( killedwith, "d_gluon gun" ); + if ( !strcmp( killedwith+2, "gauss" ) ) + strcpy( killedwith, "d_tau cannon" ); + + ConsolePrint( killedwith+2 ); // skip over the "d_" part + } + + ConsolePrint( "\n" ); + } + + return 1; +} + + + + diff --git a/ricochet/cl_dll/demo.cpp b/ricochet/cl_dll/demo.cpp new file mode 100644 index 0000000..bb65936 --- /dev/null +++ b/ricochet/cl_dll/demo.cpp @@ -0,0 +1,68 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "hud.h" +#include "cl_util.h" +#include "demo.h" +#include "demo_api.h" +#include + +extern "C" +{ + void EXPORT Demo_ReadBuffer( int size, unsigned char *buffer ); +} + +/* +===================== +Demo_WriteBuffer + +Write some data to the demo stream +===================== +*/ +void Demo_WriteBuffer( int type, int size, unsigned char *buffer ) +{ + int pos = 0; + unsigned char buf[ 32 * 1024 ]; + *( int * )&buf[pos] = type; + pos+=sizeof( int ); + + memcpy( &buf[pos], buffer, size ); + + // Write full buffer out + gEngfuncs.pDemoAPI->WriteBuffer( size + sizeof( int ), buf ); +} + +/* +===================== +Demo_ReadBuffer + +Engine wants us to parse some data from the demo stream +===================== +*/ +void EXPORT Demo_ReadBuffer( int size, unsigned char *buffer ) +{ + int type; + int i = 0; + + type = *( int * )buffer; + i += sizeof( int ); + switch ( type ) + { + case TYPE_USER: + break; + default: + gEngfuncs.Con_DPrintf( "Unknown demo buffer type, skipping.\n" ); + break; + } +} \ No newline at end of file diff --git a/ricochet/cl_dll/demo.h b/ricochet/cl_dll/demo.h new file mode 100644 index 0000000..f538e82 --- /dev/null +++ b/ricochet/cl_dll/demo.h @@ -0,0 +1,27 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( DEMOH ) +#define DEMOH +#pragma once + +// Types of demo messages we can write/parse +enum +{ + TYPE_USER = 0, +}; + +void Demo_WriteBuffer( int type, int size, unsigned char *buffer ); + +#endif \ No newline at end of file diff --git a/ricochet/cl_dll/entity.cpp b/ricochet/cl_dll/entity.cpp new file mode 100644 index 0000000..635b712 --- /dev/null +++ b/ricochet/cl_dll/entity.cpp @@ -0,0 +1,641 @@ +/*** +* +* Copyright (c) 1999, 2000, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Client side entity management functions +#include + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_types.h" +#include "studio_event.h" // def. of mstudioevent_t +#include "r_efx.h" +#include "event_api.h" +#include "pm_defs.h" +#include "pmtrace.h" + +#include + +extern vec3_t v_origin; +extern int iPrevRenderState; +extern int iRenderStateChanged; + +void Game_AddObjects( void ); + +extern "C" +{ + int EXPORT HUD_AddEntity( int type, struct cl_entity_s *ent, const char *modelname ); + void EXPORT HUD_CreateEntities( void ); + void EXPORT HUD_StudioEvent( const struct mstudioevent_s *event, const struct cl_entity_s *entity ); + void EXPORT HUD_TxferLocalOverrides( struct entity_state_s *state, const struct clientdata_s *client ); + void EXPORT HUD_ProcessPlayerState( struct entity_state_s *dst, const struct entity_state_s *src ); + void EXPORT HUD_TxferPredictionData ( struct entity_state_s *ps, const struct entity_state_s *pps, struct clientdata_s *pcd, const struct clientdata_s *ppcd, struct weapon_data_s *wd, const struct weapon_data_s *pwd ); + void EXPORT HUD_TempEntUpdate( double frametime, double client_time, double cl_gravity, struct tempent_s **ppTempEntFree, struct tempent_s **ppTempEntActive, int ( *Callback_AddVisibleEntity )( struct cl_entity_s *pEntity ), void ( *Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp ) ); + struct cl_entity_s EXPORT *HUD_GetUserEntity( int index ); +} + +/* +======================== +HUD_AddEntity + Return 0 to filter entity from visible list for rendering +======================== +*/ +int EXPORT HUD_AddEntity( int type, struct cl_entity_s *ent, const char *modelname ) +{ + switch ( type ) + { + case ET_NORMAL: + case ET_PLAYER: + case ET_BEAM: + case ET_TEMPENTITY: + case ET_FRAGMENTED: + default: + break; + } + + return 1; +} + +/* +========================= +HUD_TxferLocalOverrides + +The server sends us our origin with extra precision as part of the clientdata structure, not during the normal +playerstate update in entity_state_t. In order for these overrides to eventually get to the appropriate playerstate +structure, we need to copy them into the state structure at this point. +========================= +*/ +void EXPORT HUD_TxferLocalOverrides( struct entity_state_s *state, const struct clientdata_s *client ) +{ + VectorCopy( client->origin, state->origin ); + + // Spectator + state->iuser1 = client->iuser1; + state->iuser2 = client->iuser2; +} + +/* +========================= +HUD_ProcessPlayerState + +We have received entity_state_t for this player over the network. We need to copy appropriate fields to the +playerstate structure +========================= +*/ +void EXPORT HUD_ProcessPlayerState( struct entity_state_s *dst, const struct entity_state_s *src ) +{ + cl_entity_t *player = gEngfuncs.GetLocalPlayer(); // Get the local player's index + + // Copy in network data + VectorCopy( src->origin, dst->origin ); + VectorCopy( src->angles, dst->angles ); + + VectorCopy( src->velocity, dst->velocity ); + + dst->frame = src->frame; + dst->modelindex = src->modelindex; + dst->skin = src->skin; + dst->effects = src->effects; + dst->weaponmodel = src->weaponmodel; + dst->movetype = src->movetype; + dst->sequence = src->sequence; + dst->animtime = src->animtime; + + dst->solid = src->solid; + + dst->rendermode = src->rendermode; + dst->renderamt = src->renderamt; + dst->rendercolor.r = src->rendercolor.r; + dst->rendercolor.g = src->rendercolor.g; + dst->rendercolor.b = src->rendercolor.b; + dst->renderfx = src->renderfx; + + // Hack to find out when our render state changes. + // Needed because we need the previous render state when we flip to thirdperson + if ( dst->number == player->index ) + { + if ( iPrevRenderState != dst->renderfx ) + iRenderStateChanged = TRUE; + iPrevRenderState = dst->renderfx; + } + + dst->framerate = src->framerate; + dst->body = src->body; + + memcpy( &dst->controller[0], &src->controller[0], 4 * sizeof( byte ) ); + memcpy( &dst->blending[0], &src->blending[0], 2 * sizeof( byte ) ); + + VectorCopy( src->basevelocity, dst->basevelocity ); + + dst->friction = src->friction; + dst->gravity = src->gravity; + dst->gaitsequence = src->gaitsequence; + dst->spectator = src->spectator; + dst->usehull = src->usehull; + dst->playerclass = src->playerclass; + dst->team = src->team; + dst->colormap = src->colormap; + + // Save off some data so other areas of the Client DLL can get to it + if ( dst->number == player->index ) + { + g_iPlayerClass = dst->playerclass; + g_iTeamNumber = dst->team; + g_iUser1 = src->iuser1; + g_iUser2 = src->iuser2; + } +} + +/* +========================= +HUD_TxferPredictionData + +Because we can predict an arbitrary number of frames before the server responds with an update, we need to be able to copy client side prediction data in + from the state that the server ack'd receiving, which can be anywhere along the predicted frame path ( i.e., we could predict 20 frames into the future and the server ack's + up through 10 of those frames, so we need to copy persistent client-side only state from the 10th predicted frame to the slot the server + update is occupying. +========================= +*/ +void EXPORT HUD_TxferPredictionData ( struct entity_state_s *ps, const struct entity_state_s *pps, struct clientdata_s *pcd, const struct clientdata_s *ppcd, struct weapon_data_s *wd, const struct weapon_data_s *pwd ) +{ + ps->oldbuttons = pps->oldbuttons; + ps->flFallVelocity = pps->flFallVelocity; + ps->iStepLeft = pps->iStepLeft; + ps->playerclass = pps->playerclass; + + ps->sequence = pps->sequence; + ps->gaitsequence = pps->gaitsequence; + + pcd->viewmodel = ppcd->viewmodel; + pcd->m_iId = ppcd->m_iId; + pcd->ammo_shells = ppcd->ammo_shells; + pcd->ammo_nails = ppcd->ammo_nails; + pcd->ammo_cells = ppcd->ammo_cells; + pcd->ammo_rockets = ppcd->ammo_rockets; + pcd->m_flNextAttack = ppcd->m_flNextAttack; + pcd->fov = ppcd->fov; + pcd->weaponanim = ppcd->weaponanim; + pcd->tfstate = ppcd->tfstate; + pcd->maxspeed = ppcd->maxspeed; + + pcd->deadflag = ppcd->deadflag; + + // Spectator + pcd->iuser1 = ppcd->iuser1; + pcd->iuser2 = ppcd->iuser2; + + pcd->fuser1 = ppcd->fuser1; + pcd->fuser2 = ppcd->fuser2; + pcd->fuser3 = ppcd->fuser3; + + memcpy( wd, pwd, 32 * sizeof( weapon_data_t ) ); +} + +/* +========================= +HUD_CreateEntities + +Gives us a chance to add additional entities to the render this frame +========================= +*/ +void EXPORT HUD_CreateEntities( void ) +{ + // e.g., create a persistent cl_entity_t somewhere. + // Load an appropriate model into it ( gEngfuncs.CL_LoadModel ) + // Call gEngfuncs.CL_CreateVisibleEntity to add it to the visedicts list + + // Add in any game specific objects + Game_AddObjects(); + + GetClientVoiceMgr()->CreateEntities(); +} + +/* +========================= +HUD_StudioEvent + +The entity's studio model description indicated an event was +fired during this frame, handle the event by it's tag ( e.g., muzzleflash, sound ) +========================= +*/ +void EXPORT HUD_StudioEvent( const struct mstudioevent_s *event, const struct cl_entity_s *entity ) +{ + switch( event->event ) + { + case 5001: + gEngfuncs.pEfxAPI->R_MuzzleFlash( (float *)&entity->attachment[0], atoi( event->options) ); + break; + case 5011: + gEngfuncs.pEfxAPI->R_MuzzleFlash( (float *)&entity->attachment[1], atoi( event->options) ); + break; + case 5021: + gEngfuncs.pEfxAPI->R_MuzzleFlash( (float *)&entity->attachment[2], atoi( event->options) ); + break; + case 5031: + gEngfuncs.pEfxAPI->R_MuzzleFlash( (float *)&entity->attachment[3], atoi( event->options) ); + break; + case 5002: + gEngfuncs.pEfxAPI->R_SparkEffect( (float *)&entity->attachment[0], atoi( event->options), -100, 100 ); + break; + // Client side sound + case 5004: + gEngfuncs.pfnPlaySoundByNameAtLocation( (char *)event->options, 1.0, (float *)&entity->attachment[0] ); + break; + default: + break; + } +} + +/* +================= +CL_UpdateTEnts + +Simulation and cleanup of temporary entities +================= +*/ +void EXPORT HUD_TempEntUpdate ( + double frametime, // Simulation time + double client_time, // Absolute time on client + double cl_gravity, // True gravity on client + TEMPENTITY **ppTempEntFree, // List of freed temporary ents + TEMPENTITY **ppTempEntActive, // List + int ( *Callback_AddVisibleEntity )( cl_entity_t *pEntity ), + void ( *Callback_TempEntPlaySound )( TEMPENTITY *pTemp, float damp ) ) +{ + static int gTempEntFrame = 0; + int i; + TEMPENTITY *pTemp, *pnext, *pprev; + float freq, gravity, gravitySlow, life, fastFreq; + + // Nothing to simulate + if ( !*ppTempEntActive ) + return; + + // in order to have tents collide with players, we have to run the player prediction code so + // that the client has the player list. We run this code once when we detect any COLLIDEALL + // tent, then set this BOOL to true so the code doesn't get run again if there's more than + // one COLLIDEALL ent for this update. (often are). + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( -1 ); + + // !!!BUGBUG -- This needs to be time based + gTempEntFrame = (gTempEntFrame+1) & 31; + + pTemp = *ppTempEntActive; + + // !!! Don't simulate while paused.... This is sort of a hack, revisit. + if ( frametime <= 0 ) + { + while ( pTemp ) + { + if ( !(pTemp->flags & FTENT_NOMODEL ) ) + { + Callback_AddVisibleEntity( &pTemp->entity ); + } + pTemp = pTemp->next; + } + goto finish; + } + + pprev = NULL; + freq = client_time * 0.01; + fastFreq = client_time * 5.5; + gravity = -frametime * cl_gravity; + gravitySlow = gravity * 0.5; + + while ( pTemp ) + { + int active; + + active = 1; + + life = pTemp->die - client_time; + pnext = pTemp->next; + if ( life < 0 ) + { + if ( pTemp->flags & FTENT_FADEOUT ) + { + if (pTemp->entity.curstate.rendermode == kRenderNormal) + pTemp->entity.curstate.rendermode = kRenderTransTexture; + pTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt * ( 1 + life * pTemp->fadeSpeed ); + if ( pTemp->entity.curstate.renderamt <= 0 ) + active = 0; + + } + else + active = 0; + } + if ( !active ) // Kill it + { + pTemp->next = *ppTempEntFree; + *ppTempEntFree = pTemp; + if ( !pprev ) // Deleting at head of list + *ppTempEntActive = pnext; + else + pprev->next = pnext; + } + else + { + pprev = pTemp; + + VectorCopy( pTemp->entity.origin, pTemp->entity.prevstate.origin ); + + if ( pTemp->flags & FTENT_SPARKSHOWER ) + { + // Adjust speed if it's time + // Scale is next think time + if ( client_time > pTemp->entity.baseline.scale ) + { + // Show Sparks + gEngfuncs.pEfxAPI->R_SparkEffect( pTemp->entity.origin, 8, -200, 200 ); + + // Reduce life + pTemp->entity.baseline.framerate -= 0.1; + + if ( pTemp->entity.baseline.framerate <= 0.0 ) + { + pTemp->die = client_time; + } + else + { + // So it will die no matter what + pTemp->die = client_time + 0.5; + + // Next think + pTemp->entity.baseline.scale = client_time + 0.1; + } + } + } + else if ( pTemp->flags & FTENT_PLYRATTACHMENT ) + { + cl_entity_t *pClient; + + pClient = gEngfuncs.GetEntityByIndex( pTemp->clientIndex ); + + VectorAdd( pClient->origin, pTemp->tentOffset, pTemp->entity.origin ); + } + else if ( pTemp->flags & FTENT_SINEWAVE ) + { + pTemp->x += pTemp->entity.baseline.origin[0] * frametime; + pTemp->y += pTemp->entity.baseline.origin[1] * frametime; + + pTemp->entity.origin[0] = pTemp->x + sin( pTemp->entity.baseline.origin[2] + client_time * pTemp->entity.prevstate.frame ) * (10*pTemp->entity.curstate.framerate); + pTemp->entity.origin[1] = pTemp->y + sin( pTemp->entity.baseline.origin[2] + fastFreq + 0.7 ) * (8*pTemp->entity.curstate.framerate); + pTemp->entity.origin[2] += pTemp->entity.baseline.origin[2] * frametime; + } + else if ( pTemp->flags & FTENT_SPIRAL ) + { + float s, c; + s = sin( pTemp->entity.baseline.origin[2] + fastFreq ); + c = cos( pTemp->entity.baseline.origin[2] + fastFreq ); + + pTemp->entity.origin[0] += pTemp->entity.baseline.origin[0] * frametime + 8 * sin( client_time * 20 + (int)pTemp ); + pTemp->entity.origin[1] += pTemp->entity.baseline.origin[1] * frametime + 4 * sin( client_time * 30 + (int)pTemp ); + pTemp->entity.origin[2] += pTemp->entity.baseline.origin[2] * frametime; + } + else + { + for ( i = 0; i < 3; i++ ) + pTemp->entity.origin[i] += pTemp->entity.baseline.origin[i] * frametime; + } + + if ( pTemp->flags & FTENT_SPRANIMATE ) + { + pTemp->entity.curstate.frame += frametime * pTemp->entity.curstate.framerate; + if ( pTemp->entity.curstate.frame >= pTemp->frameMax ) + { + pTemp->entity.curstate.frame = pTemp->entity.curstate.frame - (int)(pTemp->entity.curstate.frame); + + if ( !(pTemp->flags & FTENT_SPRANIMATELOOP) ) + { + // this animating sprite isn't set to loop, so destroy it. + pTemp->die = client_time; + pTemp = pnext; + continue; + } + } + } + else if ( pTemp->flags & FTENT_SPRCYCLE ) + { + pTemp->entity.curstate.frame += frametime * 10; + if ( pTemp->entity.curstate.frame >= pTemp->frameMax ) + { + pTemp->entity.curstate.frame = pTemp->entity.curstate.frame - (int)(pTemp->entity.curstate.frame); + } + } +// Experiment +#if 0 + if ( pTemp->flags & FTENT_SCALE ) + pTemp->entity.curstate.framerate += 20.0 * (frametime / pTemp->entity.curstate.framerate); +#endif + + if ( pTemp->flags & FTENT_ROTATE ) + { + pTemp->entity.angles[0] += pTemp->entity.baseline.angles[0] * frametime; + pTemp->entity.angles[1] += pTemp->entity.baseline.angles[1] * frametime; + pTemp->entity.angles[2] += pTemp->entity.baseline.angles[2] * frametime; + + VectorCopy( pTemp->entity.angles, pTemp->entity.latched.prevangles ); + } + + if ( pTemp->flags & (FTENT_COLLIDEALL | FTENT_COLLIDEWORLD) ) + { + vec3_t traceNormal; + float traceFraction = 1; + + if ( pTemp->flags & FTENT_COLLIDEALL ) + { + pmtrace_t pmtrace; + physent_t *pe; + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + + gEngfuncs.pEventAPI->EV_PlayerTrace( pTemp->entity.prevstate.origin, pTemp->entity.origin, PM_STUDIO_BOX, -1, &pmtrace ); + + + if ( pmtrace.fraction != 1 ) + { + pe = gEngfuncs.pEventAPI->EV_GetPhysent( pmtrace.ent ); + + if ( !pmtrace.ent || ( pe->info != pTemp->clientIndex ) ) + { + traceFraction = pmtrace.fraction; + VectorCopy( pmtrace.plane.normal, traceNormal ); + + if ( pTemp->hitcallback ) + { + (*pTemp->hitcallback)( pTemp, &pmtrace ); + } + } + } + } + else if ( pTemp->flags & FTENT_COLLIDEWORLD ) + { + pmtrace_t pmtrace; + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + + gEngfuncs.pEventAPI->EV_PlayerTrace( pTemp->entity.prevstate.origin, pTemp->entity.origin, PM_STUDIO_BOX | PM_WORLD_ONLY, -1, &pmtrace ); + + if ( pmtrace.fraction != 1 ) + { + traceFraction = pmtrace.fraction; + VectorCopy( pmtrace.plane.normal, traceNormal ); + + if ( pTemp->flags & FTENT_SPARKSHOWER ) + { + // Chop spark speeds a bit more + // + VectorScale( pTemp->entity.baseline.origin, 0.6, pTemp->entity.baseline.origin ); + + if ( Length( pTemp->entity.baseline.origin ) < 10 ) + { + pTemp->entity.baseline.framerate = 0.0; + } + } + + if ( pTemp->hitcallback ) + { + (*pTemp->hitcallback)( pTemp, &pmtrace ); + } + } + } + + if ( traceFraction != 1 ) // Decent collision now, and damping works + { + float proj, damp; + + // Place at contact point + VectorMA( pTemp->entity.prevstate.origin, traceFraction*frametime, pTemp->entity.baseline.origin, pTemp->entity.origin ); + // Damp velocity + damp = pTemp->bounceFactor; + if ( pTemp->flags & (FTENT_GRAVITY|FTENT_SLOWGRAVITY) ) + { + damp *= 0.5; + if ( traceNormal[2] > 0.9 ) // Hit floor? + { + if ( pTemp->entity.baseline.origin[2] <= 0 && pTemp->entity.baseline.origin[2] >= gravity*3 ) + { + damp = 0; // Stop + pTemp->flags &= ~(FTENT_ROTATE|FTENT_GRAVITY|FTENT_SLOWGRAVITY|FTENT_COLLIDEWORLD|FTENT_SMOKETRAIL); + pTemp->entity.angles[0] = 0; + pTemp->entity.angles[2] = 0; + } + } + } + + if (pTemp->hitSound) + { + Callback_TempEntPlaySound(pTemp, damp); + } + + if (pTemp->flags & FTENT_COLLIDEKILL) + { + // die on impact + pTemp->flags &= ~FTENT_FADEOUT; + pTemp->die = client_time; + } + else + { + // Reflect velocity + if ( damp != 0 ) + { + proj = DotProduct( pTemp->entity.baseline.origin, traceNormal ); + VectorMA( pTemp->entity.baseline.origin, -proj*2, traceNormal, pTemp->entity.baseline.origin ); + // Reflect rotation (fake) + + pTemp->entity.angles[1] = -pTemp->entity.angles[1]; + } + + if ( damp != 1 ) + { + + VectorScale( pTemp->entity.baseline.origin, damp, pTemp->entity.baseline.origin ); + VectorScale( pTemp->entity.angles, 0.9, pTemp->entity.angles ); + } + } + } + } + + + if ( (pTemp->flags & FTENT_FLICKER) && gTempEntFrame == pTemp->entity.curstate.effects ) + { + dlight_t *dl = gEngfuncs.pEfxAPI->CL_AllocDlight (0); + VectorCopy (pTemp->entity.origin, dl->origin); + dl->radius = 60; + dl->color.r = 255; + dl->color.g = 120; + dl->color.b = 0; + dl->die = client_time + 0.01; + } + + if ( pTemp->flags & FTENT_SMOKETRAIL ) + { + gEngfuncs.pEfxAPI->R_RocketTrail (pTemp->entity.prevstate.origin, pTemp->entity.origin, 1); + } + + if ( pTemp->flags & FTENT_GRAVITY ) + pTemp->entity.baseline.origin[2] += gravity; + else if ( pTemp->flags & FTENT_SLOWGRAVITY ) + pTemp->entity.baseline.origin[2] += gravitySlow; + + if ( pTemp->flags & FTENT_CLIENTCUSTOM ) + { + if ( pTemp->callback ) + { + ( *pTemp->callback )( pTemp, frametime, client_time ); + } + } + + // Cull to PVS (not frustum cull, just PVS) + if ( !(pTemp->flags & FTENT_NOMODEL ) ) + { + if ( !Callback_AddVisibleEntity( &pTemp->entity ) ) + { + if ( !(pTemp->flags & FTENT_PERSIST) ) + { + pTemp->die = client_time; // If we can't draw it this frame, just dump it. + pTemp->flags &= ~FTENT_FADEOUT; // Don't fade out, just die + } + } + } + } + pTemp = pnext; + } + +finish: + // Restore state info + gEngfuncs.pEventAPI->EV_PopPMStates(); +} + +/* +================= +HUD_GetUserEntity + +If you specify negative numbers for beam start and end point entities, then + the engine will call back into this function requesting a pointer to a cl_entity_t + object that describes the entity to attach the beam onto. + +Indices must start at 1, not zero. +================= +*/ +cl_entity_t EXPORT *HUD_GetUserEntity( int index ) +{ +return NULL; +} diff --git a/ricochet/cl_dll/ev_common.cpp b/ricochet/cl_dll/ev_common.cpp new file mode 100644 index 0000000..2d2e37e --- /dev/null +++ b/ricochet/cl_dll/ev_common.cpp @@ -0,0 +1,198 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// shared event functions +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" + +#include "r_efx.h" + +#include "eventscripts.h" +#include "event_api.h" + +/* +================= +GetEntity + +Return's the requested cl_entity_t +================= +*/ +struct cl_entity_s *GetEntity( int idx ) +{ + return gEngfuncs.GetEntityByIndex( idx ); +} + +/* +================= +GetViewEntity + +Return's the current weapon/view model +================= +*/ +struct cl_entity_s *GetViewEntity( void ) +{ + return gEngfuncs.GetViewModel(); +} + +/* +================= +EV_CreateTracer + +Creates a tracer effect +================= +*/ +void EV_CreateTracer( float *start, float *end ) +{ + gEngfuncs.pEfxAPI->R_TracerEffect( start, end ); +} + +/* +================= +EV_IsPlayer + +Is the entity's index in the player range? +================= +*/ +qboolean EV_IsPlayer( int idx ) +{ + if ( idx >= 1 && idx <= gEngfuncs.GetMaxClients() ) + return true; + + return false; +} + +/* +================= +EV_IsLocal + +Is the entity == the local player +================= +*/ +qboolean EV_IsLocal( int idx ) +{ + return gEngfuncs.pEventAPI->EV_IsLocal( idx - 1 ) ? true : false; +} + +/* +================= +EV_GetGunPosition + +Figure out the height of the gun +================= +*/ +void EV_GetGunPosition( event_args_t *args, float *pos, float *origin ) +{ + int idx; + vec3_t view_ofs; + + idx = args->entindex; + + VectorClear( view_ofs ); + view_ofs[2] = DEFAULT_VIEWHEIGHT; + + if ( EV_IsPlayer( idx ) ) + { + if ( EV_IsLocal( idx ) ) + { + // Grab predicted result for local player + gEngfuncs.pEventAPI->EV_LocalPlayerViewheight( view_ofs ); + } + else if ( args->ducking == 1 ) + { + view_ofs[2] = VEC_DUCK_VIEW; + } + } + + VectorAdd( origin, view_ofs, pos ); +} + +/* +================= +EV_EjectBrass + +Bullet shell casings +================= +*/ +void EV_EjectBrass( float *origin, float *velocity, float rotation, int model, int soundtype ) +{ + vec3_t endpos; + VectorClear( endpos ); + endpos[1] = rotation; + gEngfuncs.pEfxAPI->R_TempModel( origin, velocity, endpos, 2.5, model, soundtype ); +} + +/* +================= +EV_GetDefaultShellInfo + +Determine where to eject shells from +================= +*/ +void EV_GetDefaultShellInfo( event_args_t *args, float *origin, float *velocity, float *ShellVelocity, float *ShellOrigin, float *forward, float *right, float *up, float forwardScale, float upScale, float rightScale ) +{ + int i; + vec3_t view_ofs; + float fR, fU; + + int idx; + + idx = args->entindex; + + VectorClear( view_ofs ); + view_ofs[2] = DEFAULT_VIEWHEIGHT; + + if ( EV_IsPlayer( idx ) ) + { + if ( EV_IsLocal( idx ) ) + { + gEngfuncs.pEventAPI->EV_LocalPlayerViewheight( view_ofs ); + } + else if ( args->ducking == 1 ) + { + view_ofs[2] = VEC_DUCK_VIEW; + } + } + + fR = gEngfuncs.pfnRandomFloat( 50, 70 ); + fU = gEngfuncs.pfnRandomFloat( 100, 150 ); + + for ( i = 0; i < 3; i++ ) + { + ShellVelocity[i] = velocity[i] + right[i] * fR + up[i] * fU + forward[i] * 25; + ShellOrigin[i] = origin[i] + view_ofs[i] + up[i] * upScale + forward[i] * forwardScale + right[i] * rightScale; + } +} + +/* +================= +EV_MuzzleFlash + +Flag weapon/view model for muzzle flash +================= +*/ +void EV_MuzzleFlash( void ) +{ + // Add muzzle flash to current weapon model + cl_entity_t *ent = GetViewEntity(); + if ( !ent ) + { + return; + } + + // Or in the muzzle flash + ent->curstate.effects |= EF_MUZZLEFLASH; +} \ No newline at end of file diff --git a/ricochet/cl_dll/ev_hldm.cpp b/ricochet/cl_dll/ev_hldm.cpp new file mode 100644 index 0000000..11dbf66 --- /dev/null +++ b/ricochet/cl_dll/ev_hldm.cpp @@ -0,0 +1,150 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "entity_types.h" +#include "usercmd.h" +#include "pm_defs.h" +#include "pm_materials.h" + +#include "eventscripts.h" +#include "ev_hldm.h" + +#include "r_efx.h" +#include "event_api.h" +#include "event_args.h" +#include "in_defs.h" + +#include + +extern "C" +{ +// RICOCHET +void EV_FireDisc( struct event_args_s *args ); +void EV_TriggerJump( struct event_args_s *args ); +void EV_TrainPitchAdjust( struct event_args_s *args ); +} + +/* +============================== +EV_TriggerJump + +Plays the jump pad sound +============================== +*/ +void EV_TriggerJump( event_args_t *args ) +{ + int idx; + idx = args->entindex; + vec3_t origin; + + VectorCopy( args->origin, origin ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_AUTO, "triggerjump.wav", 1.0, ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); +} + +/* +============================== +EV_FireDisc + +Play's disc firing animation and play's appropriate sound effect +============================== +*/ +void EV_FireDisc( event_args_t *args ) +{ + int idx; + + idx = args->entindex; + vec3_t origin; + int decap; + + VectorCopy( args->origin, origin ); + decap = args->bparam1 ? 1 : 0; + + if ( EV_IsLocal( idx ) ) + { + // Add muzzle flash to current weapon model + gEngfuncs.pEventAPI->EV_WeaponAnimation( DISC_THROW1, 2 ); + } + + if ( decap ) + { + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/altfire.wav", 0.8, ATTN_NORM, 0, 100 ); + } + else + { + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/cbar_miss1.wav", 0.8, ATTN_NORM, 0, 100 ); + } +} + +#define SND_CHANGE_PITCH (1<<7) + +/* +============================== +EV_TrainPitchAdjust + +Do we support trains in Ricochet? +============================== +*/ +void EV_TrainPitchAdjust( event_args_t *args ) +{ + int idx; + vec3_t origin; + + unsigned short us_params; + int noise; + float m_flVolume; + int pitch; + int stop; + + char sz[ 256 ]; + + idx = args->entindex; + + VectorCopy( args->origin, origin ); + + us_params = (unsigned short)args->iparam1; + stop = args->bparam1; + + m_flVolume = (float)(us_params & 0x003f)/40.0; + noise = (int)(((us_params) >> 12 ) & 0x0007); + pitch = (int)( 10.0 * (float)( ( us_params >> 6 ) & 0x003f ) ); + + switch ( noise ) + { + case 1: strcpy( sz, "plats/ttrain1.wav"); break; + case 2: strcpy( sz, "plats/ttrain2.wav"); break; + case 3: strcpy( sz, "plats/ttrain3.wav"); break; + case 4: strcpy( sz, "plats/ttrain4.wav"); break; + case 5: strcpy( sz, "plats/ttrain6.wav"); break; + case 6: strcpy( sz, "plats/ttrain7.wav"); break; + default: + // no sound + strcpy( sz, "" ); + return; + } + + if ( stop ) + { + gEngfuncs.pEventAPI->EV_StopSound( idx, CHAN_STATIC, sz ); + } + else + { + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_STATIC, sz, m_flVolume, ATTN_NORM, SND_CHANGE_PITCH, pitch ); + } +} diff --git a/ricochet/cl_dll/ev_hldm.h b/ricochet/cl_dll/ev_hldm.h new file mode 100644 index 0000000..8d3aec7 --- /dev/null +++ b/ricochet/cl_dll/ev_hldm.h @@ -0,0 +1,16 @@ +#if !defined ( EV_HLDMH ) +#define EV_HLDMH + +enum disc_e +{ + DISC_IDLE = 0, + DISC_FIDGET, + DISC_PINPULL, + DISC_THROW1, // toss + DISC_THROW2, // medium + DISC_THROW3, // hard + DISC_HOLSTER, + DISC_DRAW +}; + +#endif // EV_HLDMH \ No newline at end of file diff --git a/ricochet/cl_dll/events.cpp b/ricochet/cl_dll/events.cpp new file mode 100644 index 0000000..04c8f57 --- /dev/null +++ b/ricochet/cl_dll/events.cpp @@ -0,0 +1,30 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "hud.h" +#include "cl_util.h" + +void Game_HookEvents( void ); + +/* +=================== +EV_HookEvents + +See if game specific code wants to hook any events. +=================== +*/ +void EV_HookEvents( void ) +{ + Game_HookEvents(); +} \ No newline at end of file diff --git a/ricochet/cl_dll/eventscripts.h b/ricochet/cl_dll/eventscripts.h new file mode 100644 index 0000000..0ac3251 --- /dev/null +++ b/ricochet/cl_dll/eventscripts.h @@ -0,0 +1,80 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// eventscripts.h +#if !defined ( EVENTSCRIPTSH ) +#define EVENTSCRIPTSH + +// defaults for clientinfo messages +#define DEFAULT_VIEWHEIGHT 28 +#define VEC_DUCK_VIEW 12 + +#define FTENT_FADEOUT 0x00000080 + +#define DMG_GENERIC 0 // generic damage was done +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FREEZE (1 << 4) // frozen +#define DMG_FALL (1 << 5) // fell too far +#define DMG_BLAST (1 << 6) // explosive blast damage +#define DMG_CLUB (1 << 7) // crowbar, punch, headbutt +#define DMG_SHOCK (1 << 8) // electric shock +#define DMG_SONIC (1 << 9) // sound pulse shockwave +#define DMG_ENERGYBEAM (1 << 10) // laser or other high energy beam +#define DMG_NEVERGIB (1 << 12) // with this bit OR'd in, no damage type will be able to gib victims upon death +#define DMG_ALWAYSGIB (1 << 13) // with this bit OR'd in, any damage type can be made to gib victims upon death. + +// time-based damage +//mask off TF-specific stuff too +#define DMG_TIMEBASED (~(0xff003fff)) // mask for time-based damage + +#define DMG_DROWN (1 << 14) // Drowning +#define DMG_FIRSTTIMEBASED DMG_DROWN + +#define DMG_PARALYZE (1 << 15) // slows affected creature down +#define DMG_NERVEGAS (1 << 16) // nerve toxins, very bad +#define DMG_POISON (1 << 17) // blood poisioning +#define DMG_RADIATION (1 << 18) // radiation exposure +#define DMG_DROWNRECOVER (1 << 19) // drowning recovery +#define DMG_ACID (1 << 20) // toxic chemicals or acid burns +#define DMG_SLOWBURN (1 << 21) // in an oven +#define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer +#define DMG_MORTAR (1 << 23) // Hit by air raid (done to distinguish grenade from mortar) + +//TF ADDITIONS +#define DMG_IGNITE (1 << 24) // Players hit by this begin to burn +#define DMG_RADIUS_MAX (1 << 25) // Radius damage with this flag doesn't decrease over distance +#define DMG_RADIUS_QUAKE (1 << 26) // Radius damage is done like Quake. 1/2 damage at 1/2 radius. +#define DMG_IGNOREARMOR (1 << 27) // Damage ignores target's armor +#define DMG_AIMED (1 << 28) // Does Hit location damage +#define DMG_WALLPIERCING (1 << 29) // Blast Damages ents through walls + +#define DMG_CALTROP (1<<30) +#define DMG_HALLUC (1<<31) + +// Some of these are HL/TFC specific? +void EV_EjectBrass( float *origin, float *velocity, float rotation, int model, int soundtype ); +void EV_GetGunPosition( struct event_args_s *args, float *pos, float *origin ); +void EV_GetDefaultShellInfo( struct event_args_s *args, float *origin, float *velocity, float *ShellVelocity, float *ShellOrigin, float *forward, float *right, float *up, float forwardScale, float upScale, float rightScale ); +qboolean EV_IsLocal( int idx ); +qboolean EV_IsPlayer( int idx ); +void EV_CreateTracer( float *start, float *end ); + +struct cl_entity_s *GetEntity( int idx ); +struct cl_entity_s *GetViewEntity( void ); +void EV_MuzzleFlash( void ); + +#endif // EVENTSCRIPTSH diff --git a/ricochet/cl_dll/flashlight.cpp b/ricochet/cl_dll/flashlight.cpp new file mode 100644 index 0000000..48a73f4 --- /dev/null +++ b/ricochet/cl_dll/flashlight.cpp @@ -0,0 +1,100 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// flashlight.cpp +// +// implementation of CHudFlashlight class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + + + +DECLARE_MESSAGE(m_Flash, FlashBat) +DECLARE_MESSAGE(m_Flash, Flashlight) + +#define BAT_NAME "sprites/%d_Flashlight.spr" + +int CHudFlashlight::Init(void) +{ + m_fFade = 0; + m_fOn = 0; + + HOOK_MESSAGE(Flashlight); + HOOK_MESSAGE(FlashBat); + + m_iFlags |= HUD_ACTIVE; + + gHUD.AddHudElem(this); + + return 1; +}; + +void CHudFlashlight::Reset(void) +{ + m_fFade = 0; + m_fOn = 0; +} + +int CHudFlashlight::VidInit(void) +{ + int HUD_flash_empty = gHUD.GetSpriteIndex( "flash_empty" ); + int HUD_flash_full = gHUD.GetSpriteIndex( "flash_full" ); + int HUD_flash_beam = gHUD.GetSpriteIndex( "flash_beam" ); + + m_hSprite1 = gHUD.GetSprite(HUD_flash_empty); + m_hSprite2 = gHUD.GetSprite(HUD_flash_full); + m_hBeam = gHUD.GetSprite(HUD_flash_beam); + m_prc1 = &gHUD.GetSpriteRect(HUD_flash_empty); + m_prc2 = &gHUD.GetSpriteRect(HUD_flash_full); + m_prcBeam = &gHUD.GetSpriteRect(HUD_flash_beam); + m_iWidth = m_prc2->right - m_prc2->left; + + return 1; +}; + +int CHudFlashlight:: MsgFunc_FlashBat(const char *pszName, int iSize, void *pbuf ) +{ + + + BEGIN_READ( pbuf, iSize ); + int x = READ_BYTE(); + m_iBat = x; + m_flBat = ((float)x)/100.0; + + return 1; +} + +int CHudFlashlight:: MsgFunc_Flashlight(const char *pszName, int iSize, void *pbuf ) +{ + + BEGIN_READ( pbuf, iSize ); + m_fOn = READ_BYTE(); + int x = READ_BYTE(); + m_iBat = x; + m_flBat = ((float)x)/100.0; + + return 1; +} + +int CHudFlashlight::Draw(float flTime) +{ + return 1; +} \ No newline at end of file diff --git a/ricochet/cl_dll/geiger.cpp b/ricochet/cl_dll/geiger.cpp new file mode 100644 index 0000000..c50d695 --- /dev/null +++ b/ricochet/cl_dll/geiger.cpp @@ -0,0 +1,184 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Geiger.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include + +#include "parsemsg.h" + +DECLARE_MESSAGE(m_Geiger, Geiger ) + +int CHudGeiger::Init(void) +{ + HOOK_MESSAGE( Geiger ); + + m_iGeigerRange = 0; + m_iFlags = 0; + + gHUD.AddHudElem(this); + + srand( (unsigned)time( NULL ) ); + + return 1; +}; + +int CHudGeiger::VidInit(void) +{ + return 1; +}; + +int CHudGeiger::MsgFunc_Geiger(const char *pszName, int iSize, void *pbuf) +{ + + BEGIN_READ( pbuf, iSize ); + + // update geiger data + m_iGeigerRange = READ_BYTE(); + m_iGeigerRange = m_iGeigerRange << 2; + + m_iFlags |= HUD_ACTIVE; + + return 1; +} + +int CHudGeiger::Draw (float flTime) +{ + int pct; + float flvol; + int rg[3]; + int i; + + if (m_iGeigerRange < 1000 && m_iGeigerRange > 0) + { + // peicewise linear is better than continuous formula for this + if (m_iGeigerRange > 800) + { + pct = 0; //Con_Printf ( "range > 800\n"); + } + else if (m_iGeigerRange > 600) + { + pct = 2; + flvol = 0.4; //Con_Printf ( "range > 600\n"); + rg[0] = 1; + rg[1] = 1; + i = 2; + } + else if (m_iGeigerRange > 500) + { + pct = 4; + flvol = 0.5; //Con_Printf ( "range > 500\n"); + rg[0] = 1; + rg[1] = 2; + i = 2; + } + else if (m_iGeigerRange > 400) + { + pct = 8; + flvol = 0.6; //Con_Printf ( "range > 400\n"); + rg[0] = 1; + rg[1] = 2; + rg[2] = 3; + i = 3; + } + else if (m_iGeigerRange > 300) + { + pct = 8; + flvol = 0.7; //Con_Printf ( "range > 300\n"); + rg[0] = 2; + rg[1] = 3; + rg[2] = 4; + i = 3; + } + else if (m_iGeigerRange > 200) + { + pct = 28; + flvol = 0.78; //Con_Printf ( "range > 200\n"); + rg[0] = 2; + rg[1] = 3; + rg[2] = 4; + i = 3; + } + else if (m_iGeigerRange > 150) + { + pct = 40; + flvol = 0.80; //Con_Printf ( "range > 150\n"); + rg[0] = 3; + rg[1] = 4; + rg[2] = 5; + i = 3; + } + else if (m_iGeigerRange > 100) + { + pct = 60; + flvol = 0.85; //Con_Printf ( "range > 100\n"); + rg[0] = 3; + rg[1] = 4; + rg[2] = 5; + i = 3; + } + else if (m_iGeigerRange > 75) + { + pct = 80; + flvol = 0.9; //Con_Printf ( "range > 75\n"); + //gflGeigerDelay = cl.time + GEIGERDELAY * 0.75; + rg[0] = 4; + rg[1] = 5; + rg[2] = 6; + i = 3; + } + else if (m_iGeigerRange > 50) + { + pct = 90; + flvol = 0.95; //Con_Printf ( "range > 50\n"); + rg[0] = 5; + rg[1] = 6; + i = 2; + } + else + { + pct = 95; + flvol = 1.0; //Con_Printf ( "range < 50\n"); + rg[0] = 5; + rg[1] = 6; + i = 2; + } + + flvol = (flvol * ((rand() & 127)) / 255) + 0.25; // UTIL_RandomFloat(0.25, 0.5); + + if ((rand() & 127) < pct || (rand() & 127) < pct) + { + //S_StartDynamicSound (-1, 0, rgsfx[rand() % i], r_origin, flvol, 1.0, 0, 100); + char sz[256]; + + int j = rand() & 1; + if (i > 2) + j += rand() & 1; + + sprintf(sz, "player/geiger%d.wav", j + 1); + PlaySound(sz, flvol); + + } + } + + return 1; +} diff --git a/ricochet/cl_dll/health.cpp b/ricochet/cl_dll/health.cpp new file mode 100644 index 0000000..73d8be1 --- /dev/null +++ b/ricochet/cl_dll/health.cpp @@ -0,0 +1,425 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Health.cpp +// +// implementation of CHudHealth class +// + +#include "stdio.h" +#include "stdlib.h" +#include "math.h" + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" +#include "vgui_TeamFortressViewport.h" +#include + + +DECLARE_MESSAGE(m_Health, Health ) +DECLARE_MESSAGE(m_Health, Damage ) + +#define PAIN_NAME "sprites/%d_pain.spr" +#define DAMAGE_NAME "sprites/%d_dmg.spr" + +int giDmgHeight, giDmgWidth; + +int giDmgFlags[NUM_DMG_TYPES] = +{ + DMG_POISON, + DMG_ACID, + DMG_FREEZE|DMG_SLOWFREEZE, + DMG_DROWN, + DMG_BURN|DMG_SLOWBURN, + DMG_NERVEGAS, + DMG_RADIATION, + DMG_SHOCK, + DMG_CALTROP, + DMG_TRANQ, + DMG_CONCUSS, + DMG_HALLUC +}; + +int CHudHealth::Init(void) +{ + HOOK_MESSAGE(Health); + HOOK_MESSAGE(Damage); + m_iHealth = 100; + m_fFade = 0; + m_iFlags = 0; + m_bitsDamage = 0; + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 0; + giDmgHeight = 0; + giDmgWidth = 0; + + memset(m_dmg, 0, sizeof(DAMAGE_IMAGE) * NUM_DMG_TYPES); + + + gHUD.AddHudElem(this); + return 1; +} + +void CHudHealth::Reset( void ) +{ + // make sure the pain compass is cleared when the player respawns + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 0; + + + // force all the flashing damage icons to expire + m_bitsDamage = 0; + for ( int i = 0; i < NUM_DMG_TYPES; i++ ) + { + m_dmg[i].fExpire = 0; + } +} + +int CHudHealth::VidInit(void) +{ + m_hSprite = 0; + + m_HUD_dmg_bio = gHUD.GetSpriteIndex( "dmg_bio" ) + 1; + m_HUD_cross = gHUD.GetSpriteIndex( "cross" ); + + giDmgHeight = gHUD.GetSpriteRect(m_HUD_dmg_bio).right - gHUD.GetSpriteRect(m_HUD_dmg_bio).left; + giDmgWidth = gHUD.GetSpriteRect(m_HUD_dmg_bio).bottom - gHUD.GetSpriteRect(m_HUD_dmg_bio).top; + return 1; +} + +int CHudHealth:: MsgFunc_Health(const char *pszName, int iSize, void *pbuf ) +{ + // TODO: update local health data + BEGIN_READ( pbuf, iSize ); + int x = READ_BYTE(); + + m_iFlags |= HUD_ACTIVE; + + // Only update the fade if we've changed health + if (x != m_iHealth) + { + m_fFade = FADE_TIME; + m_iHealth = x; + } + + return 1; +} + + +int CHudHealth:: MsgFunc_Damage(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int armor = READ_BYTE(); // armor + int damageTaken = READ_BYTE(); // health + long bitsDamage = READ_LONG(); // damage bits + + vec3_t vecFrom; + + for ( int i = 0 ; i < 3 ; i++) + vecFrom[i] = READ_COORD(); + + UpdateTiles(gHUD.m_flTime, bitsDamage); + + // Actually took damage? + if ( damageTaken > 0 || armor > 0 ) + CalcDamageDirection(vecFrom); + + return 1; +} + + +// Returns back a color from the +// Green <-> Yellow <-> Red ramp +void CHudHealth::GetPainColor( int &r, int &g, int &b ) +{ + int iHealth = m_iHealth; + + if (iHealth > 25) + iHealth -= 25; + else if ( iHealth < 0 ) + iHealth = 0; +#if 0 + g = iHealth * 255 / 100; + r = 255 - g; + b = 0; +#else + if (m_iHealth > 25) + { + UnpackRGB(r,g,b, RGB_YELLOWISH); + } + else + { + r = 250; + g = 0; + b = 0; + } +#endif +} + +int CHudHealth::Draw(float flTime) +{ + // No Health in Discwar + if ( gHUD.m_iHideHUDDisplay & HIDEHUD_HEALTH ) + return 1; + + if ( !m_hSprite ) + m_hSprite = LoadSprite(PAIN_NAME); + + return DrawDamage(flTime); +} + +void CHudHealth::CalcDamageDirection(vec3_t vecFrom) +{ + vec3_t forward, right, up; + float side, front; + vec3_t vecOrigin, vecAngles; + + if (!vecFrom[0] && !vecFrom[1] && !vecFrom[2]) + { + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 0; + return; + } + + + memcpy(vecOrigin, gHUD.m_vecOrigin, sizeof(vec3_t)); + memcpy(vecAngles, gHUD.m_vecAngles, sizeof(vec3_t)); + + + VectorSubtract (vecFrom, vecOrigin, vecFrom); + + float flDistToTarget = vecFrom.Length(); + + vecFrom = vecFrom.Normalize(); + AngleVectors (vecAngles, forward, right, up); + + front = DotProduct (vecFrom, right); + side = DotProduct (vecFrom, forward); + + if (flDistToTarget <= 50) + { + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 1; + } + else + { + if (side > 0) + { + if (side > 0.3) + m_fAttackFront = max(m_fAttackFront, side); + } + else + { + float f = fabs(side); + if (f > 0.3) + m_fAttackRear = max(m_fAttackRear, f); + } + + if (front > 0) + { + if (front > 0.3) + m_fAttackRight = max(m_fAttackRight, front); + } + else + { + float f = fabs(front); + if (f > 0.3) + m_fAttackLeft = max(m_fAttackLeft, f); + } + } +} + +int CHudHealth::DrawPain(float flTime) +{ + if (!(m_fAttackFront || m_fAttackRear || m_fAttackLeft || m_fAttackRight)) + return 1; + + int r, g, b; + int x, y, a, shade; + + // TODO: get the shift value of the health + a = 255; // max brightness until then + + float fFade = gHUD.m_flTimeDelta * 2; + + // SPR_Draw top + if (m_fAttackFront > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackFront, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 - SPR_Width(m_hSprite, 0)/2; + y = ScreenHeight/2 - SPR_Height(m_hSprite,0) * 3; + SPR_DrawAdditive(0, x, y, NULL); + m_fAttackFront = max( 0, m_fAttackFront - fFade ); + } else + m_fAttackFront = 0; + + if (m_fAttackRight > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackRight, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 + SPR_Width(m_hSprite, 1) * 2; + y = ScreenHeight/2 - SPR_Height(m_hSprite,1)/2; + SPR_DrawAdditive(1, x, y, NULL); + m_fAttackRight = max( 0, m_fAttackRight - fFade ); + } else + m_fAttackRight = 0; + + if (m_fAttackRear > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackRear, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 - SPR_Width(m_hSprite, 2)/2; + y = ScreenHeight/2 + SPR_Height(m_hSprite,2) * 2; + SPR_DrawAdditive(2, x, y, NULL); + m_fAttackRear = max( 0, m_fAttackRear - fFade ); + } else + m_fAttackRear = 0; + + if (m_fAttackLeft > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackLeft, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 - SPR_Width(m_hSprite, 3) * 3; + y = ScreenHeight/2 - SPR_Height(m_hSprite,3)/2; + SPR_DrawAdditive(3, x, y, NULL); + + m_fAttackLeft = max( 0, m_fAttackLeft - fFade ); + } else + m_fAttackLeft = 0; + + return 1; +} + +int CHudHealth::DrawDamage(float flTime) +{ + int r, g, b, a; + DAMAGE_IMAGE *pdmg; + + if (!m_bitsDamage) + return 1; + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + a = (int)( fabs(sin(flTime*2)) * 256.0); + + ScaleColors(r, g, b, a); + + // Draw all the items + for (int i = 0; i < NUM_DMG_TYPES; i++) + { + if (m_bitsDamage & giDmgFlags[i]) + { + pdmg = &m_dmg[i]; + SPR_Set(gHUD.GetSprite(m_HUD_dmg_bio + i), r, g, b ); + + // Discwar: Hack. Freeze is the only icon we use. Just place it directly above the disc ammo + int iX = (ScreenWidth - DISC_ICON_WIDTH) / 2; + int iXPos = iX - DISC_ICON_SPACER; + SPR_DrawAdditive(0, iXPos + (DISC_ICON_SPACER), ScreenHeight - YRES(92), &gHUD.GetSpriteRect(m_HUD_dmg_bio + i)); + } + } + +/* + // check for bits that should be expired + for ( i = 0; i < NUM_DMG_TYPES; i++ ) + { + DAMAGE_IMAGE *pdmg = &m_dmg[i]; + + if ( m_bitsDamage & giDmgFlags[i] ) + { + pdmg->fExpire = min( flTime + DMG_IMAGE_LIFE, pdmg->fExpire ); + + if ( pdmg->fExpire <= flTime // when the time has expired + && a < 40 ) // and the flash is at the low point of the cycle + { + pdmg->fExpire = 0; + + int y = pdmg->y; + pdmg->x = pdmg->y = 0; + + // move everyone above down + for (int j = 0; j < NUM_DMG_TYPES; j++) + { + pdmg = &m_dmg[j]; + if ((pdmg->y) && (pdmg->y < y)) + pdmg->y += giDmgHeight; + + } + + m_bitsDamage &= ~giDmgFlags[i]; // clear the bits + } + } + } +*/ + return 1; +} + + +void CHudHealth::UpdateTiles(float flTime, long bitsDamage) +{ + DAMAGE_IMAGE *pdmg; + + // Which types are new? + long bitsOn = ~m_bitsDamage & bitsDamage; + + for (int i = 0; i < NUM_DMG_TYPES; i++) + { + pdmg = &m_dmg[i]; + + // Is this one already on? + if (m_bitsDamage & giDmgFlags[i]) + { + pdmg->fExpire = flTime + DMG_IMAGE_LIFE; // extend the duration + if (!pdmg->fBaseline) + pdmg->fBaseline = flTime; + } + + // Are we just turning it on? + if (bitsOn & giDmgFlags[i]) + { + // put this one at the bottom + pdmg->x = giDmgWidth/8; + pdmg->y = ScreenHeight - giDmgHeight * 2; + pdmg->fExpire=flTime + DMG_IMAGE_LIFE; + + // move everyone else up + for (int j = 0; j < NUM_DMG_TYPES; j++) + { + if (j == i) + continue; + + pdmg = &m_dmg[j]; + if (pdmg->y) + pdmg->y -= giDmgHeight; + + } + pdmg = &m_dmg[i]; + } + } + + // damage bits are only turned on here; they are turned off when the draw time has expired (in DrawDamage()) + m_bitsDamage |= bitsDamage; +} diff --git a/ricochet/cl_dll/health.h b/ricochet/cl_dll/health.h new file mode 100644 index 0000000..dd59897 --- /dev/null +++ b/ricochet/cl_dll/health.h @@ -0,0 +1,128 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#define DMG_IMAGE_LIFE 2 // seconds that image is up + +#define DMG_IMAGE_POISON 0 +#define DMG_IMAGE_ACID 1 +#define DMG_IMAGE_COLD 2 +#define DMG_IMAGE_DROWN 3 +#define DMG_IMAGE_BURN 4 +#define DMG_IMAGE_NERVE 5 +#define DMG_IMAGE_RAD 6 +#define DMG_IMAGE_SHOCK 7 +//tf defines +#define DMG_IMAGE_CALTROP 8 +#define DMG_IMAGE_TRANQ 9 +#define DMG_IMAGE_CONCUSS 10 +#define DMG_IMAGE_HALLUC 11 +#define NUM_DMG_TYPES 12 +// instant damage + +#define DMG_GENERIC 0 // generic damage was done +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FREEZE (1 << 4) // frozen +#define DMG_FALL (1 << 5) // fell too far +#define DMG_BLAST (1 << 6) // explosive blast damage +#define DMG_CLUB (1 << 7) // crowbar, punch, headbutt +#define DMG_SHOCK (1 << 8) // electric shock +#define DMG_SONIC (1 << 9) // sound pulse shockwave +#define DMG_ENERGYBEAM (1 << 10) // laser or other high energy beam +#define DMG_NEVERGIB (1 << 12) // with this bit OR'd in, no damage type will be able to gib victims upon death +#define DMG_ALWAYSGIB (1 << 13) // with this bit OR'd in, any damage type can be made to gib victims upon death. + + +// time-based damage +//mask off TF-specific stuff too +#define DMG_TIMEBASED (~(0xff003fff)) // mask for time-based damage + + +#define DMG_DROWN (1 << 14) // Drowning +#define DMG_FIRSTTIMEBASED DMG_DROWN + +#define DMG_PARALYZE (1 << 15) // slows affected creature down +#define DMG_NERVEGAS (1 << 16) // nerve toxins, very bad +#define DMG_POISON (1 << 17) // blood poisioning +#define DMG_RADIATION (1 << 18) // radiation exposure +#define DMG_DROWNRECOVER (1 << 19) // drowning recovery +#define DMG_ACID (1 << 20) // toxic chemicals or acid burns +#define DMG_SLOWBURN (1 << 21) // in an oven +#define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer +#define DMG_MORTAR (1 << 23) // Hit by air raid (done to distinguish grenade from mortar) + +//TF ADDITIONS +#define DMG_IGNITE (1 << 24) // Players hit by this begin to burn +#define DMG_RADIUS_MAX (1 << 25) // Radius damage with this flag doesn't decrease over distance +#define DMG_RADIUS_QUAKE (1 << 26) // Radius damage is done like Quake. 1/2 damage at 1/2 radius. +#define DMG_IGNOREARMOR (1 << 27) // Damage ignores target's armor +#define DMG_AIMED (1 << 28) // Does Hit location damage +#define DMG_WALLPIERCING (1 << 29) // Blast Damages ents through walls + +#define DMG_CALTROP (1<<30) +#define DMG_HALLUC (1<<31) + +// TF Healing Additions for TakeHealth +#define DMG_IGNORE_MAXHEALTH DMG_IGNITE +// TF Redefines since we never use the originals +#define DMG_NAIL DMG_SLASH +#define DMG_NOT_SELF DMG_FREEZE + + +#define DMG_TRANQ DMG_MORTAR +#define DMG_CONCUSS DMG_SONIC + + + +typedef struct +{ + float fExpire; + float fBaseline; + int x, y; +} DAMAGE_IMAGE; + +// +//----------------------------------------------------- +// +class CHudHealth: public CHudBase +{ +public: + virtual int Init( void ); + virtual int VidInit( void ); + virtual int Draw(float fTime); + virtual void Reset( void ); + int MsgFunc_Health(const char *pszName, int iSize, void *pbuf); + int MsgFunc_Damage(const char *pszName, int iSize, void *pbuf); + int m_iHealth; + int m_HUD_dmg_bio; + int m_HUD_cross; + float m_fAttackFront, m_fAttackRear, m_fAttackLeft, m_fAttackRight; + void GetPainColor( int &r, int &g, int &b ); + float m_fFade; + + int m_bitsDamage; + +private: + HSPRITE m_hSprite; + HSPRITE m_hDamage; + + DAMAGE_IMAGE m_dmg[NUM_DMG_TYPES]; + int DrawPain(float fTime); + int DrawDamage(float fTime); + void CalcDamageDirection(vec3_t vecFrom); + void UpdateTiles(float fTime, long bits); +}; diff --git a/ricochet/cl_dll/hl/hl_baseentity.cpp b/ricochet/cl_dll/hl/hl_baseentity.cpp new file mode 100644 index 0000000..16809e9 --- /dev/null +++ b/ricochet/cl_dll/hl/hl_baseentity.cpp @@ -0,0 +1,248 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +/* +========================== +This file contains "stubs" of class member implementations so that we can predict certain + weapons client side. From time to time you might find that you need to implement part of the + these functions. If so, cut it from here, paste it in hl_weapons.cpp or somewhere else and + add in the functionality you need. +========================== +*/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "nodes.h" + +// Globals used by game logic +const Vector g_vecZero = Vector( 0, 0, 0 ); +int gmsgWeapPickup = 0; +enginefuncs_t g_engfuncs; +globalvars_t *gpGlobals; + +ItemInfo CBasePlayerItem::ItemInfoArray[MAX_WEAPONS]; + +void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volume, float attenuation, int flags, int pitch) { } + +// CBaseEntity Stubs +int CBaseEntity :: TakeHealth( float flHealth, int bitsDamageType ) { return 1; } +int CBaseEntity :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) { return 1; } +CBaseEntity *CBaseEntity::GetNextTarget( void ) { return NULL; } +int CBaseEntity::Save( CSave &save ) { return 1; } +int CBaseEntity::Restore( CRestore &restore ) { return 1; } +void CBaseEntity::SetObjectCollisionBox( void ) { } +int CBaseEntity :: Intersects( CBaseEntity *pOther ) { return 0; } +void CBaseEntity :: MakeDormant( void ) { } +int CBaseEntity :: IsDormant( void ) { return 0; } +BOOL CBaseEntity :: IsInWorld( void ) { return TRUE; } +int CBaseEntity::ShouldToggle( USE_TYPE useType, BOOL currentState ) { return 0; } +int CBaseEntity :: DamageDecal( int bitsDamageType ) { return -1; } +CBaseEntity * CBaseEntity::Create( char *szName, const Vector &vecOrigin, const Vector &vecAngles, edict_t *pentOwner ) { return NULL; } +void CBaseEntity::SUB_Remove( void ) { } + +// CBaseDelay Stubs +void CBaseDelay :: KeyValue( struct KeyValueData_s * ) { } +int CBaseDelay::Restore( class CRestore & ) { return 1; } +int CBaseDelay::Save( class CSave & ) { return 1; } + +// CBaseAnimating Stubs +int CBaseAnimating::Restore( class CRestore & ) { return 1; } +int CBaseAnimating::Save( class CSave & ) { return 1; } + +// DEBUG Stubs +edict_t *DBG_EntOfVars( const entvars_t *pev ) { return NULL; } +void DBG_AssertFunction(BOOL fExpr, const char* szExpr, const char* szFile, int szLine, const char* szMessage) { } + +// UTIL_* Stubs +void UTIL_PrecacheOther( const char *szClassname ) { } +void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, int amount ) { } +void UTIL_DecalTrace( TraceResult *pTrace, int decalNumber ) { } +void UTIL_GunshotDecalTrace( TraceResult *pTrace, int decalNumber ) { } +BOOL UTIL_IsValidEntity( edict_t *pent ) { return TRUE; } +void UTIL_SetOrigin( entvars_t *, const Vector &org ) { } +BOOL UTIL_GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) { return TRUE; } +void UTIL_LogPrintf(char *,...) { } +void UTIL_ClientPrintAll( int,char const *,char const *,char const *,char const *,char const *) { } +void ClientPrint( entvars_t *client, int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) { } + +// CBaseToggle Stubs +int CBaseToggle::Restore( class CRestore & ) { return 1; } +int CBaseToggle::Save( class CSave & ) { return 1; } +void CBaseToggle :: KeyValue( struct KeyValueData_s * ) { } + +// CGrenade Stubs +void CGrenade::BounceSound( void ) { } +void CGrenade::Explode( Vector, Vector ) { } +void CGrenade::Explode( TraceResult *, int ) { } +void CGrenade::Killed( entvars_t *, int ) { } +void CGrenade::Spawn( void ) { } + +CBaseEntity* CBaseMonster :: CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ) { return NULL; } +void CBaseMonster :: Look ( int iDistance ) { } +float CBaseAnimating :: StudioFrameAdvance ( float flInterval ) { return 0.0; } +int CBaseMonster::IRelationship ( CBaseEntity *pTarget ) { return 0; } +CBaseEntity *CBaseMonster :: BestVisibleEnemy ( void ) { return NULL; } +BOOL CBaseMonster :: FInViewCone ( CBaseEntity *pEntity ) { return FALSE; } +BOOL CBaseMonster :: FInViewCone ( Vector *pOrigin ) { return FALSE; } +BOOL CBaseEntity :: FVisible ( CBaseEntity *pEntity ) { return FALSE; } +BOOL CBaseEntity :: FVisible ( const Vector &vecOrigin ) { return FALSE; } +void CBaseMonster :: MakeIdealYaw( Vector vecTarget ) { } +float CBaseMonster::ChangeYaw ( int yawSpeed ) { return 0; } +int CBaseAnimating :: LookupActivity ( int activity ) { return 0; } +int CBaseAnimating :: LookupActivityHeaviest ( int activity ) { return 0; } + +BOOL CBaseAnimating :: GetSequenceFlags( ) { return FALSE; } +void CBaseAnimating :: DispatchAnimEvents ( float flInterval ) { } +float CBaseAnimating :: SetBoneController ( int iController, float flValue ) { return 0.0; } +void CBaseAnimating :: InitBoneControllers ( void ) { } +float CBaseAnimating :: SetBlending ( int iBlender, float flValue ) { return 0; } +void CBaseAnimating :: GetBonePosition ( int iBone, Vector &origin, Vector &angles ) { } +void CBaseAnimating :: GetAttachment ( int iAttachment, Vector &origin, Vector &angles ) { } +int CBaseAnimating :: FindTransition( int iEndingSequence, int iGoalSequence, int *piDir ) { return -1; } +void CBaseAnimating :: GetAutomovement( Vector &origin, Vector &angles, float flInterval ) { } +void CBaseAnimating :: SetBodygroup( int iGroup, int iValue ) { } +int CBaseAnimating :: GetBodygroup( int iGroup ) { return 0; } +void CBaseEntity::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) { } +void CBaseEntity::FireBullets(ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq, int iDamage, entvars_t *pevAttacker ) { } +void CBaseEntity :: TraceBleed( float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) { } +void CBaseMonster :: MakeDamageBloodDecal ( int cCount, float flNoise, TraceResult *ptr, const Vector &vecDir ) { } +void CBaseMonster::ReportAIState( void ) { } +void CBaseMonster :: KeyValue( KeyValueData *pkvd ) { } +BOOL CBaseMonster :: FCheckAITrigger ( void ) { return FALSE; } +void CBaseMonster::CorpseFallThink( void ) { } +void CBaseMonster :: MonsterInitDead( void ) { } +void CBaseMonster :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) { } +BOOL CBaseMonster :: ShouldFadeOnDeath( void ) { return FALSE; } +void CBaseMonster :: RadiusDamage(entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) { } +void CBaseMonster :: RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) { } +void CBaseMonster::FadeMonster( void ) { } +void CBaseMonster :: GibMonster( void ) { } +BOOL CBaseMonster :: HasHumanGibs( void ) { return FALSE; } +BOOL CBaseMonster :: HasAlienGibs( void ) { return FALSE; } +Activity CBaseMonster :: GetDeathActivity ( void ) { return (Activity)0; } +void CBaseMonster::BecomeDead( void ) {} +void CBaseMonster :: Killed( entvars_t *pevAttacker, int iGib ) {} +int CBaseMonster :: TakeHealth (float flHealth, int bitsDamageType) { return 0; } +int CBaseMonster :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { return 0; } + +int TrainSpeed(int iSpeed, int iMax) { return 0; } +void CBasePlayer :: DeathSound( void ) { } +int CBasePlayer :: TakeHealth( float flHealth, int bitsDamageType ) { return 0; } +void CBasePlayer :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) { } +int CBasePlayer :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { return 0; } +void CBasePlayer::RemoveAllItems( BOOL removeSuit ) { } +void CBasePlayer::WaterMove() { } +BOOL CBasePlayer::IsOnLadder( void ) { return FALSE; } +void CBasePlayer::PlayerDeathThink(void) { } +void CBasePlayer::StartDeathCam( void ) { } +void CBasePlayer::StartObserver( Vector vecPosition, Vector vecViewAngle ) { } +void CBasePlayer::PlayerUse ( void ) { } +void CBasePlayer::Jump() { } +void CBasePlayer::Duck( ) { } +int CBasePlayer::Classify ( void ) { return 0; } +void CBasePlayer :: PlayStepSound(int step, float fvol) { } +void CBasePlayer :: UpdateStepSound( void ) { } +void CBasePlayer::PreThink(void) { } +void CBasePlayer::CheckTimeBasedDamage() { } +void CBasePlayer :: UpdateGeigerCounter( void ) { } +void CBasePlayer::CheckSuitUpdate() { } +void CBasePlayer::SetSuitUpdate(char *name, int fgroup, int iNoRepeatTime) { } +void CBasePlayer :: UpdatePlayerSound ( void ) { } +void CBasePlayer :: Precache( void ) { } +int CBasePlayer::Save( CSave &save ) { return 0; } +void CBasePlayer::RenewItems(void) { } +int CBasePlayer::Restore( CRestore &restore ) { return 0; } +void CBasePlayer::SelectNextItem( int iItem ) { } +BOOL CBasePlayer::HasWeapons( void ) { return FALSE; } +void CBasePlayer::SelectPrevItem( int iItem ) { } +CBaseEntity *FindEntityForward( CBaseEntity *pMe ) { return NULL; } +BOOL CBasePlayer :: FlashlightIsOn( void ) { return FALSE; } +void CBasePlayer :: FlashlightTurnOn( void ) { } +void CBasePlayer :: FlashlightTurnOff( void ) { } +void CBasePlayer :: ForceClientDllUpdate( void ) { } +void CBasePlayer::ImpulseCommands( ) { } +void CBasePlayer::CheatImpulseCommands( int iImpulse ) { } +int CBasePlayer::AddPlayerItem( CBasePlayerItem *pItem ) { return FALSE; } +int CBasePlayer::RemovePlayerItem( CBasePlayerItem *pItem ) { return FALSE; } +void CBasePlayer::ItemPreFrame() { } +void CBasePlayer::ItemPostFrame() { } +int CBasePlayer::AmmoInventory( int iAmmoIndex ) { return -1; } +int CBasePlayer::GetAmmoIndex(const char *psz) { return -1; } +void CBasePlayer::SendAmmoUpdate(void) { } +void CBasePlayer :: UpdateClientData( void ) { } +BOOL CBasePlayer :: FBecomeProne ( void ) { return TRUE; } +void CBasePlayer :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) { } +void CBasePlayer :: BarnacleVictimReleased ( void ) { } +int CBasePlayer :: Illumination( void ) { return 0; } +void CBasePlayer :: EnableControl(BOOL fControl) { } +Vector CBasePlayer :: GetAutoaimVector( float flDelta ) { return g_vecZero; } +Vector CBasePlayer :: AutoaimDeflection( Vector &vecSrc, float flDist, float flDelta ) { return g_vecZero; } +void CBasePlayer :: ResetAutoaim( ) { } +void CBasePlayer :: SetCustomDecalFrames( int nFrames ) { } +int CBasePlayer :: GetCustomDecalFrames( void ) { return -1; } +void CBasePlayer::DropPlayerItem ( char *pszItemName ) { } +BOOL CBasePlayer::HasPlayerItem( CBasePlayerItem *pCheckItem ) { return FALSE; } +BOOL CBasePlayer :: SwitchWeapon( CBasePlayerItem *pWeapon ) { return FALSE; } +Vector CBasePlayer :: GetGunPosition( void ) { return g_vecZero; } +const char *CBasePlayer::TeamID( void ) { return ""; } +int CBasePlayer :: GiveAmmo( int iCount, char *szName, int iMax ) { return 0; } +void CBasePlayer::AddPoints( int score, BOOL bAllowNegativeScore ) { } +void CBasePlayer::AddPointsToTeam( int score, BOOL bAllowNegativeScore ) { } +void CBasePlayer::RemoveAllPowerups( void ) {} +bool CBasePlayer::HasPowerup(int) { return 0; } + +void ClearMultiDamage(void) { } +void ApplyMultiDamage(entvars_t *pevInflictor, entvars_t *pevAttacker ) { } +void AddMultiDamage( entvars_t *pevInflictor, CBaseEntity *pEntity, float flDamage, int bitsDamageType) { } +void SpawnBlood(Vector vecSpot, int bloodColor, float flDamage) { } +int DamageDecal( CBaseEntity *pEntity, int bitsDamageType ) { return 0; } +void DecalGunshot( TraceResult *pTrace, int iBulletType ) { } +void EjectBrass ( const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int model, int soundtype ) { } +void AddAmmoNameToAmmoRegistry( const char *szAmmoname ) { } +int CBasePlayerItem::Restore( class CRestore & ) { return 1; } +int CBasePlayerItem::Save( class CSave & ) { return 1; } +int CBasePlayerWeapon::Restore( class CRestore & ) { return 1; } +int CBasePlayerWeapon::Save( class CSave & ) { return 1; } +void CBasePlayerItem :: SetObjectCollisionBox( void ) { } +void CBasePlayerItem :: FallInit( void ) { } +void CBasePlayerItem::FallThink ( void ) { } +void CBasePlayerItem::Materialize( void ) { } +void CBasePlayerItem::AttemptToMaterialize( void ) { } +void CBasePlayerItem :: CheckRespawn ( void ) { } +CBaseEntity* CBasePlayerItem::Respawn( void ) { return NULL; } +void CBasePlayerItem::DefaultTouch( CBaseEntity *pOther ) { } +void CBasePlayerItem::DestroyItem( void ) { } +int CBasePlayerItem::AddToPlayer( CBasePlayer *pPlayer ) { return TRUE; } +void CBasePlayerItem::Drop( void ) { } +void CBasePlayerItem::Kill( void ) { } +void CBasePlayerItem::Holster( int skiplocal ) { } +void CBasePlayerItem::AttachToPlayer ( CBasePlayer *pPlayer ) { } +int CBasePlayerWeapon::AddDuplicate( CBasePlayerItem *pOriginal ) { return 0; } +int CBasePlayerWeapon::AddToPlayer( CBasePlayer *pPlayer ) { return FALSE; } +int CBasePlayerWeapon::UpdateClientData( CBasePlayer *pPlayer ) { return 0; } +BOOL CBasePlayerWeapon :: AddPrimaryAmmo( int iCount, char *szName, int iMaxClip, int iMaxCarry ) { return TRUE; } +BOOL CBasePlayerWeapon :: AddSecondaryAmmo( int iCount, char *szName, int iMax ) { return TRUE; } +BOOL CBasePlayerWeapon :: IsUseable( void ) { return TRUE; } +int CBasePlayerWeapon::PrimaryAmmoIndex( void ) { return -1; } +int CBasePlayerWeapon::SecondaryAmmoIndex( void ) { return -1; } +void CBasePlayerAmmo::Spawn( void ) { } +CBaseEntity* CBasePlayerAmmo::Respawn( void ) { return this; } +void CBasePlayerAmmo::Materialize( void ) { } +void CBasePlayerAmmo :: DefaultTouch( CBaseEntity *pOther ) { } +int CBasePlayerWeapon::ExtractAmmo( CBasePlayerWeapon *pWeapon ) { return 0; } +int CBasePlayerWeapon::ExtractClipAmmo( CBasePlayerWeapon *pWeapon ) { return 0; } +void CBasePlayerWeapon::RetireWeapon( void ) { } \ No newline at end of file diff --git a/ricochet/cl_dll/hl/hl_events.cpp b/ricochet/cl_dll/hl/hl_events.cpp new file mode 100644 index 0000000..0906ab3 --- /dev/null +++ b/ricochet/cl_dll/hl/hl_events.cpp @@ -0,0 +1,45 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "../hud.h" +#include "../cl_util.h" +#include "event_api.h" + +extern "C" +{ +// RICOCHET +void EV_TriggerJump( struct event_args_s *args ); +void EV_FireDisc( struct event_args_s *args ); +void EV_TrainPitchAdjust( struct event_args_s *args ); +} + +/* +====================== +Game_HookEvents + +Associate script file name with callback functions. Callback's must be extern "C" so + the engine doesn't get confused about name mangling stuff. Note that the format is + always the same. Of course, a clever mod team could actually embed parameters, behavior + into the actual .sc files and create a .sc file parser and hook their functionality through + that.. i.e., a scripting system. + +That was what we were going to do, but we ran out of time...oh well. +====================== +*/ +void Game_HookEvents( void ) +{ + gEngfuncs.pfnHookEvent( "events/train.sc", EV_TrainPitchAdjust ); + gEngfuncs.pfnHookEvent( "events/firedisc.sc", EV_FireDisc ); + gEngfuncs.pfnHookEvent( "events/jump.sc", EV_TriggerJump ); +} \ No newline at end of file diff --git a/ricochet/cl_dll/hl/hl_objects.cpp b/ricochet/cl_dll/hl/hl_objects.cpp new file mode 100644 index 0000000..204d0bc --- /dev/null +++ b/ricochet/cl_dll/hl/hl_objects.cpp @@ -0,0 +1,28 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "../hud.h" +#include "../cl_util.h" +#include "../demo.h" + +/* +===================== +Game_AddObjects + +Add game specific, client-side objects here +===================== +*/ +void Game_AddObjects( void ) +{ +} \ No newline at end of file diff --git a/ricochet/cl_dll/hl/hl_weapons.cpp b/ricochet/cl_dll/hl/hl_weapons.cpp new file mode 100644 index 0000000..59404eb --- /dev/null +++ b/ricochet/cl_dll/hl/hl_weapons.cpp @@ -0,0 +1,1423 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "disc_weapon.h" +#include "discwar.h" +#include "nodes.h" +#include "player.h" + +#include "usercmd.h" +#include "entity_state.h" +#include "demo_api.h" +#include "pm_defs.h" +#include "event_api.h" +#include "r_efx.h" + +#include "../hud_iface.h" +#include "../com_weapons.h" +#include "../demo.h" + +#include "r_studioint.h" + +#include "../Ricochet_JumpPads.h" +#include "studio.h" +#include "com_model.h" + +// Global engine <-> studio model rendering code interface +extern engine_studio_api_t IEngineStudio; + +extern globalvars_t *gpGlobals; + +// Pool of client side entities/entvars_t +static entvars_t ev[ 32 ]; +static int num_ents = 0; + +// The entity we'll use to represent the local client +static CBasePlayer player; + +// Local version of game .dll global variables ( time, etc. ) +static globalvars_t Globals; + +static CBasePlayerWeapon *g_pWpns[ 32 ]; + +// For storing predicted sequence and gaitsequence and origin/angles data +static int g_rseq = 0, g_gaitseq = 0; +static vec3_t g_clorg, g_clang; + +// HLDM Weapon placeholder entities. +CDiscWeapon g_Disc; +extern int g_iCannotFire; + +/* +====================== +AlertMessage + +Print debug messages to console +====================== +*/ +void AlertMessage( ALERT_TYPE atype, char *szFmt, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start (argptr, szFmt); + vsprintf (string, szFmt,argptr); + va_end (argptr); + + gEngfuncs.Con_Printf( "cl: " ); + gEngfuncs.Con_Printf( string ); +} + +/* +===================== +HUD_PrepEntity + +Links the raw entity to an entvars_s holder. If a player is passed in as the owner, then +we set up the m_pPlayer field. +===================== +*/ +void HUD_PrepEntity( CBaseEntity *pEntity, CBasePlayer *pWeaponOwner ) +{ + memset( &ev[ num_ents ], 0, sizeof( entvars_t ) ); + pEntity->pev = &ev[ num_ents++ ]; + + pEntity->Precache(); + pEntity->Spawn(); + + if ( pWeaponOwner ) + { + ItemInfo info; + + ((CBasePlayerWeapon *)pEntity)->m_pPlayer = pWeaponOwner; + + ((CBasePlayerWeapon *)pEntity)->GetItemInfo( &info ); + + g_pWpns[ info.iId ] = (CBasePlayerWeapon *)pEntity; + } +} + +/* +===================== +CBaseEntity :: Killed + +If weapons code "kills" an entity, just set its effects to EF_NODRAW +===================== +*/ +void CBaseEntity :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->effects |= EF_NODRAW; +} + +/* +===================== +CBasePlayerWeapon :: DefaultReload +===================== +*/ +BOOL CBasePlayerWeapon :: DefaultReload( int iClipSize, int iAnim, float fDelay ) +{ +#if 0 // FIXME, need to know primary ammo to get this right + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + return FALSE; + + int j = min(iClipSize - m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); + + if (j == 0) + return FALSE; +#endif + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + fDelay; + + //!!UNDONE -- reload sound goes here !!! + SendWeaponAnim( iAnim ); + + m_fInReload = TRUE; + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 3; + return TRUE; +} + +/* +===================== +CBasePlayerWeapon :: CanDeploy +===================== +*/ +BOOL CBasePlayerWeapon :: CanDeploy( void ) +{ + BOOL bHasAmmo = 0; + + if ( !pszAmmo1() ) + { + // this weapon doesn't use ammo, can always deploy. + return TRUE; + } + + if ( pszAmmo1() ) + { + bHasAmmo |= (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] != 0); + } + if ( pszAmmo2() ) + { + bHasAmmo |= (m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] != 0); + } + if (m_iClip > 0) + { + bHasAmmo |= 1; + } + if (!bHasAmmo) + { + return FALSE; + } + + return TRUE; +} + +/* +===================== +CBasePlayerWeapon :: DefaultDeploy + +===================== +*/ +BOOL CBasePlayerWeapon :: DefaultDeploy( char *szViewModel, char *szWeaponModel, int iAnim, char *szAnimExt, int skiplocal ) +{ + if ( !CanDeploy() ) + return FALSE; + + gEngfuncs.CL_LoadModel( szViewModel, &m_pPlayer->pev->viewmodel ); + + SendWeaponAnim( iAnim ); + + m_pPlayer->m_flNextAttack = 0.5; + m_flTimeWeaponIdle = 1.0; + return TRUE; +} + +/* +===================== +CBasePlayerWeapon :: PlayEmptySound + +===================== +*/ +BOOL CBasePlayerWeapon :: PlayEmptySound( void ) +{ + if (m_iPlayEmptySound) + { + HUD_PlaySound( "weapons/357_cock1.wav", 0.8 ); + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +/* +===================== +CBasePlayerWeapon :: ResetEmptySound + +===================== +*/ +void CBasePlayerWeapon :: ResetEmptySound( void ) +{ + m_iPlayEmptySound = 1; +} + +/* +===================== +CBasePlayerWeapon::Holster + +Put away weapon +===================== +*/ +void CBasePlayerWeapon::Holster( int skiplocal /* = 0 */ ) +{ + m_fInReload = FALSE; // cancel any reload in progress. + m_pPlayer->pev->viewmodel = 0; +} + +/* +===================== +CBasePlayerWeapon::SendWeaponAnim + +Animate weapon model +===================== +*/ +void CBasePlayerWeapon::SendWeaponAnim( int iAnim, int skiplocal ) +{ + m_pPlayer->pev->weaponanim = iAnim; + + int body = 0; + + HUD_SendWeaponAnim( iAnim, body, 0 ); +} + +/* +===================== +CBasePlayerWeapon::ItemPostFrame + +Handles weapon firing, reloading, etc. +===================== +*/ +void CBasePlayerWeapon::ItemPostFrame( void ) +{ + if ((m_fInReload) && (m_pPlayer->m_flNextAttack <= 0.0)) + { +#if 0 // FIXME, need ammo on client to make this work right + // complete the reload. + int j = min( iMaxClip() - m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); + + // Add them to the clip + m_iClip += j; + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= j; +#else + m_iClip += 10; +#endif + m_fInReload = FALSE; + } + + if ((m_pPlayer->pev->button & IN_ATTACK2) && (m_flNextSecondaryAttack <= 0.0)) + { + if ( pszAmmo2() && !m_pPlayer->m_rgAmmo[SecondaryAmmoIndex()] ) + { + m_fFireOnEmpty = TRUE; + } + + SecondaryAttack(); + m_pPlayer->pev->button &= ~IN_ATTACK2; + } + else if ((m_pPlayer->pev->button & IN_ATTACK) && (m_flNextPrimaryAttack <= 0.0)) + { + if ( (m_iClip == 0 && pszAmmo1()) || (iMaxClip() == -1 && !m_pPlayer->m_rgAmmo[PrimaryAmmoIndex()] ) ) + { + m_fFireOnEmpty = TRUE; + } + + PrimaryAttack(); + } + else if ( m_pPlayer->pev->button & IN_RELOAD && iMaxClip() != WEAPON_NOCLIP && !m_fInReload ) + { + // reload when reload is pressed, or if no buttons are down and weapon is empty. + Reload(); + } + else if ( !(m_pPlayer->pev->button & (IN_ATTACK|IN_ATTACK2) ) ) + { + // no fire buttons down + + m_fFireOnEmpty = FALSE; + + // weapon is useable. Reload if empty and weapon has waited as long as it has to after firing + if ( m_iClip == 0 && !(iFlags() & ITEM_FLAG_NOAUTORELOAD) && m_flNextPrimaryAttack < 0.0 ) + { + Reload(); + return; + } + + WeaponIdle( ); + return; + } + + // catch all + if ( ShouldWeaponIdle() ) + { + WeaponIdle(); + } +} + +/* +===================== +CBasePlayer::SelectItem + + Switch weapons +===================== +*/ +void CBasePlayer::SelectItem(const char *pstr) +{ + if (!pstr) + return; + + CBasePlayerItem *pItem = NULL; + + if (!pItem) + return; + + + if (pItem == m_pActiveItem) + return; + + if (m_pActiveItem) + m_pActiveItem->Holster( ); + + m_pLastItem = m_pActiveItem; + m_pActiveItem = pItem; + + if (m_pActiveItem) + { + m_pActiveItem->Deploy( ); + } +} + +/* +===================== +CBasePlayer::SelectLastItem + +===================== +*/ +void CBasePlayer::SelectLastItem(void) +{ + if (!m_pLastItem) + { + return; + } + + if ( m_pActiveItem && !m_pActiveItem->CanHolster() ) + { + return; + } + + if (m_pActiveItem) + m_pActiveItem->Holster( ); + + CBasePlayerItem *pTemp = m_pActiveItem; + m_pActiveItem = m_pLastItem; + m_pLastItem = pTemp; + m_pActiveItem->Deploy( ); +} + +/* +===================== +CBasePlayer::Killed + +===================== +*/ +void CBasePlayer::Killed( entvars_t *pevAttacker, int iGib ) +{ + // Holster weapon immediately, to allow it to cleanup + if (m_pActiveItem) + m_pActiveItem->Holster( ); +} + +/* +===================== +CBasePlayer::Spawn + +===================== +*/ +void CBasePlayer::Spawn( void ) +{ + if (m_pActiveItem) + m_pActiveItem->Deploy( ); +} + +/* +===================== +UTIL_TraceLine + +Don't actually trace, but act like the trace didn't hit anything. +===================== +*/ +void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, edict_t *pentIgnore, TraceResult *ptr ) +{ + memset( ptr, 0, sizeof( *ptr ) ); + ptr->flFraction = 1.0; +} + +/* +===================== +UTIL_ParticleBox + +For debugging, draw a box around a player made out of particles +===================== +*/ +void UTIL_ParticleBox( CBasePlayer *player, float *mins, float *maxs, float life, unsigned char r, unsigned char g, unsigned char b ) +{ + int i; + vec3_t mmin, mmax; + + for ( i = 0; i < 3; i++ ) + { + mmin[ i ] = player->pev->origin[ i ] + mins[ i ]; + mmax[ i ] = player->pev->origin[ i ] + maxs[ i ]; + } + + gEngfuncs.pEfxAPI->R_ParticleBox( (float *)&mmin, (float *)&mmax, 5.0, 0, 255, 0 ); +} + +/* +===================== +UTIL_ParticleBoxes + +For debugging, draw boxes for other collidable players +===================== +*/ +void UTIL_ParticleBoxes( void ) +{ + int idx; + physent_t *pe; + cl_entity_t *player; + vec3_t mins, maxs; + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + player = gEngfuncs.GetLocalPlayer(); + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( player->index - 1 ); + + for ( idx = 1; idx < 100; idx++ ) + { + pe = gEngfuncs.pEventAPI->EV_GetPhysent( idx ); + if ( !pe ) + break; + + if ( pe->info >= 1 && pe->info <= gEngfuncs.GetMaxClients() ) + { + mins = pe->origin + pe->mins; + maxs = pe->origin + pe->maxs; + + gEngfuncs.pEfxAPI->R_ParticleBox( (float *)&mins, (float *)&maxs, 0, 0, 255, 2.0 ); + } + } + + gEngfuncs.pEventAPI->EV_PopPMStates(); +} + +/* +===================== +UTIL_ParticleLine + +For debugging, draw a line made out of particles +===================== +*/ +void UTIL_ParticleLine( CBasePlayer *player, float *start, float *end, float life, unsigned char r, unsigned char g, unsigned char b ) +{ + gEngfuncs.pEfxAPI->R_ParticleLine( start, end, r, g, b, life ); +} + +/* +===================== +CBasePlayerWeapon::PrintState + +For debugging, print out state variables to log file +===================== +*/ +void CBasePlayerWeapon::PrintState( void ) +{ + COM_Log( "c:\\hl.log", "%.4f ", gpGlobals->time ); + COM_Log( "c:\\hl.log", "%.4f ", m_pPlayer->m_flNextAttack ); + COM_Log( "c:\\hl.log", "%.4f ", m_flNextPrimaryAttack ); + COM_Log( "c:\\hl.log", "%.4f ", m_flTimeWeaponIdle - gpGlobals->time); + COM_Log( "c:\\hl.log", "%i ", m_iClip ); +} + +/* +===================== +HUD_InitClientWeapons + +Set up weapons, player and functions needed to run weapons code client-side. +===================== +*/ +void HUD_InitClientWeapons( void ) +{ + static int initialized = 0; + if ( initialized ) + return; + + initialized = 1; + + // Set up pointer ( dummy object ) + gpGlobals = &Globals; + + // Fill in current time ( probably not needed ) + gpGlobals->time = gEngfuncs.GetClientTime(); + + // Fake functions + g_engfuncs.pfnPrecacheModel = stub_PrecacheModel; + g_engfuncs.pfnPrecacheSound = stub_PrecacheSound; + g_engfuncs.pfnPrecacheEvent = stub_PrecacheEvent; + g_engfuncs.pfnNameForFunction = stub_NameForFunction; + g_engfuncs.pfnSetModel = stub_SetModel; + g_engfuncs.pfnSetClientMaxspeed = HUD_SetMaxSpeed; + + // Handled locally + g_engfuncs.pfnPlaybackEvent = HUD_PlaybackEvent; + g_engfuncs.pfnAlertMessage = AlertMessage; + + // Pass through to engine + g_engfuncs.pfnPrecacheEvent = gEngfuncs.pfnPrecacheEvent; + g_engfuncs.pfnRandomFloat = gEngfuncs.pfnRandomFloat; + g_engfuncs.pfnRandomLong = gEngfuncs.pfnRandomLong; + + // Allocate a slot for the local player + HUD_PrepEntity( &player , NULL ); + + // Allocate slot(s) for each weapon that we are going to be predicting + HUD_PrepEntity( &g_Disc , &player ); +} + +/* +============================== +LookupSequence + +Find sequence # of named sequence +============================== +*/ +int LookupSequence( void *pmodel, const char *label ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + for (int i = 0; i < pstudiohdr->numseq; i++) + { + if (stricmp( pseqdesc[i].label, label ) == 0) + return i; + } + + return -1; +} + +/* +============================== +LookupSequence + +============================== +*/ +int CBaseAnimating :: LookupSequence ( const char *label ) +{ + cl_entity_t *current; + + current = gEngfuncs.GetLocalPlayer(); + if ( !current || !current->model ) + return 0; + + return ::LookupSequence( (studiohdr_t *)IEngineStudio.Mod_Extradata( current->model ), label ); +} + +/* +============================== +GetSequenceInfo + +============================== +*/ +void GetSequenceInfo( void *pmodel, entvars_t *pev, float *pflFrameRate, float *pflGroundSpeed ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return; + + mstudioseqdesc_t *pseqdesc; + + if (pev->sequence >= pstudiohdr->numseq) + { + *pflFrameRate = 0.0; + *pflGroundSpeed = 0.0; + return; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + if (pseqdesc->numframes > 1) + { + *pflFrameRate = 256 * pseqdesc->fps / (pseqdesc->numframes - 1); + *pflGroundSpeed = sqrt( pseqdesc->linearmovement[0]*pseqdesc->linearmovement[0]+ pseqdesc->linearmovement[1]*pseqdesc->linearmovement[1]+ pseqdesc->linearmovement[2]*pseqdesc->linearmovement[2] ); + *pflGroundSpeed = *pflGroundSpeed * pseqdesc->fps / (pseqdesc->numframes - 1); + } + else + { + *pflFrameRate = 256.0; + *pflGroundSpeed = 0.0; + } +} + +/* +============================== +GetSequenceFlags + +============================== +*/ +int GetSequenceFlags( void *pmodel, entvars_t *pev ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq ) + return 0; + + mstudioseqdesc_t *pseqdesc; + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + return pseqdesc->flags; +} + +/* +============================== +ResetSequenceInfo + +============================== +*/ +void CBaseAnimating :: ResetSequenceInfo ( ) +{ + cl_entity_t *current; + + current = gEngfuncs.GetLocalPlayer(); + if ( !current || !current->model ) + return; + + void *pmodel = (studiohdr_t *)IEngineStudio.Mod_Extradata( current->model ); + + GetSequenceInfo( pmodel, pev, &m_flFrameRate, &m_flGroundSpeed ); + m_fSequenceLoops = ((GetSequenceFlags() & STUDIO_LOOPING) != 0); + pev->animtime = gpGlobals->time; + pev->framerate = 1.0; + m_fSequenceFinished = FALSE; + m_flLastEventCheck = gpGlobals->time; +} + +/* +============================== +UTIL_MakeVectors + +============================== +*/ +void UTIL_MakeVectors( const Vector &vecAngles ) +{ + gEngfuncs.pfnAngleVectors ( (float *)&vecAngles, gpGlobals->v_forward, gpGlobals->v_right, gpGlobals->v_up); +} + +/* +============================== +GetFallAnimation + +============================== +*/ +int CBasePlayer::GetFallAnimation( void ) +{ + Vector vecNormVel = pev->velocity; + vecNormVel.Normalize(); + int fallAnim; + + UTIL_MakeVectors( pev->angles ); + float flDot = DotProduct( vecNormVel, gpGlobals->v_forward ); + float flSideDot = DotProduct( vecNormVel, gpGlobals->v_right ); + // Choose a falling animation based upon the velocity vector + if ( flDot < -0.6 ) + fallAnim = LookupSequence( "fall_b" ); + else if ( flSideDot < -0.6 ) + fallAnim = LookupSequence( "fall_r" ); + else if ( flSideDot > 0.6 ) + fallAnim = LookupSequence( "fall_l" ); + else + fallAnim = LookupSequence( "fall_f" ); + + return fallAnim; +} + +#define WALK_SPEED 100 +// Set the activity based on an event or current state + +/* +============================== +SetAnimation + +============================== +*/ +void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) +{ + int animDesired; + float speed; + + speed = pev->velocity.Length2D(); + + switch (playerAnim) + { + case PLAYER_JUMP: + m_IdealActivity = ACT_HOP; + break; + + case PLAYER_SUPERJUMP: + m_IdealActivity = ACT_LEAP; + break; + + case PLAYER_ATTACK1: + m_IdealActivity = ACT_BASE_THROW; + break; + + case PLAYER_FALL: + m_IdealActivity = ACT_FALL; + break; + + case PLAYER_IDLE: + case PLAYER_WALK: + if ( !FBitSet( pev->flags, FL_ONGROUND ) && (m_Activity == ACT_HOP) ) // Still jumping + { + m_IdealActivity = m_Activity; + } + else + { + m_IdealActivity = ACT_BASE_WALK; + } + break; + } + + Vector vecNormVel; + float flDot, flSideDot, flVelDot; + bool bInReverse; + int iFrame; + + // Decide which sequence to play based upon the activity + switch (m_IdealActivity) + { + case ACT_DIEFORWARD: + case ACT_FALL: + default: + if ( m_Activity == m_IdealActivity) + return; + m_Activity = ACT_FALL; + + animDesired = GetFallAnimation(); + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + pev->gaitsequence = 0; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + + case ACT_LEAP: + UTIL_MakeVectors( pev->angles ); + vecNormVel = pev->velocity; + vecNormVel.Normalize(); + flDot = DotProduct( vecNormVel, gpGlobals->v_forward ); + if ( flDot < -0.6 ) + { + // Use non-blended backflip + animDesired = LookupSequence( "backflip" ); + m_Activity = m_IdealActivity; + pev->gaitsequence = 0; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + } + else + { + // Use blended longjump + animDesired = LookupSequence( "longjump" ); + m_Activity = ACT_LEAP; + pev->gaitsequence = animDesired; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + } + break; + + case ACT_DIE_HEADSHOT: + animDesired = LookupSequence( "die_simple" ); + m_Activity = m_IdealActivity; + + pev->gaitsequence = 0; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + + case ACT_HOP: + iFrame = pev->frame / 18; + if ( iFrame >= 2 && iFrame <= 11 ) + animDesired = LookupSequence( "jump" ); + else + animDesired = LookupSequence( "jumpl" ); + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + + case ACT_BASE_THROW: + // No throw animation during backflip + if ( pev->sequence == LookupSequence( "backflip" ) ) + return; + + // If we're in the air, we need to use the blended longjump throw + if ( pev->sequence == LookupSequence( "longjump" ) ) + { + // Use blended longjump + animDesired = LookupSequence( "longjump_throw" ); + m_Activity = ACT_FLINCH_CLOCKWISE; + pev->gaitsequence = animDesired; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + } + + animDesired = GetThrowAnim(); + m_Activity = m_IdealActivity; + m_flThrowTime = 0.25; + break; + + case ACT_BASE_WALK: + UTIL_MakeVectors( pev->angles ); + bInReverse = ( pev->sequence == LookupSequence("base_reverse") ); + vecNormVel = pev->velocity; + vecNormVel.Normalize(); + flDot = DotProduct( vecNormVel, gpGlobals->v_forward ); + flSideDot = DotProduct( vecNormVel, gpGlobals->v_right ); + flVelDot = DotProduct( m_vecOldVelocity, vecNormVel ); + + if ( ( m_flBackupTime <= 0 ) && (m_Activity != ACT_BASE_THROW) || m_fSequenceFinished ) + { + if ( speed == 0 ) + { + animDesired = LookupSequence( "base_stand" ); + } + else if ( flDot < -0.6 ) + { + animDesired = LookupSequence( "base_backup" ); + } + else if ( ( flVelDot <= 0 ) && ( flDot <= 0.6 ) ) + { + animDesired = LookupSequence( "base_reverse" ); + m_flBackupTime = 0.7; + pev->effects |= EF_NOINTERP; + } + else + { + if ( speed > WALK_SPEED ) + animDesired = LookupSequence( "base_run" ); + else + animDesired = LookupSequence( "base_walk" ); + } + + if (animDesired == -1) + { + animDesired = 0; + } + m_Activity = ACT_BASE_WALK; + } + else if ( bInReverse ) + { + // Don't play the backup run if we're still in the backup run + if ( DotProduct( m_vecOldVelocity, vecNormVel ) < 0 ) + { + m_flBackupTime = 0; + if ( speed > WALK_SPEED ) + animDesired = LookupSequence( "base_run" ); + else + animDesired = LookupSequence( "base_walk" ); + pev->effects |= EF_NOINTERP; + } + else + { + animDesired = pev->sequence; + } + } + else + { + animDesired = pev->sequence; + } + break; + } + + // Set gait animation + if ( m_flBackupTime > 0 ) + { + pev->gaitsequence = LookupSequence( "base_backup" ); + } + else + { + if ( speed > WALK_SPEED ) + { + pev->gaitsequence = LookupSequence( "base_run" ); + } + else if (speed > 0) + { + pev->gaitsequence = LookupSequence( "base_walk" ); + } + } + + // Idle? + if (speed <= 0) + { + pev->gaitsequence = LookupSequence( "base_stand" ); + } + + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + // Reset to first frame of desired animation + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); +} + +int CBasePlayer::GetThrowAnim( void ) +{ + int throwAnim; + + if ( pev->velocity.Length2D() == 0 ) + throwAnim = LookupSequence( "base_stand_throw" ); + else + throwAnim = LookupSequence( "base_throw" ); + + return throwAnim; +} + +int CBasePlayer::GetHoldAnim( void ) +{ + int holdAnim; + + // Choose hold anim based upon powerups. + // Multiple Powerups can be had, in which case the one considered more dangerous has the animation. + if ( m_iPowerups & POW_TRIPLE ) + holdAnim = LookupSequence( "triple_ready" ); + else if ( m_iPowerups & POW_FAST ) + holdAnim = LookupSequence( "kill_ready" ); + else if ( m_iPowerups & POW_HARD ) + holdAnim = LookupSequence( "hard_ready" ); + else if ( m_iPowerups & POW_FREEZE ) + holdAnim = LookupSequence( "freeze_ready" ); + else + holdAnim = LookupSequence( "base_ready" ); + + return holdAnim; +} + +/* +============================== +PostThink + +============================== +*/ +void CBasePlayer::PostThink() +{ + if ( !g_runfuncs ) + return; + + // select the proper animation for the player character + if ( !CL_IsDead() && (m_flTouchedByJumpPad < gpGlobals->time) ) + { + if (!pev->velocity.x && !pev->velocity.y) + SetAnimation( PLAYER_IDLE ); + else if ((pev->velocity.x || pev->velocity.y) && (FBitSet(pev->flags, FL_ONGROUND))) + SetAnimation( PLAYER_WALK ); + else if (pev->waterlevel > 1) + SetAnimation( PLAYER_WALK ); + } + + if ( !CL_IsDead() && ( m_flThrowTime <= 0.0 ) ) + { + m_Activity = ACT_BASE_WALK; + m_flThrowTime = 0.0; + if (!pev->velocity.x && !pev->velocity.y) + { + SetAnimation( PLAYER_IDLE ); + } + else + { + SetAnimation( PLAYER_WALK ); + } + } + + // Store old velocity for use in backpedalling animations + m_vecOldVelocity = pev->velocity; + m_vecOldVelocity.Normalize(); +} + +/* +===================== +HUD_WeaponsPostThink + +Run Weapon firing code on client +===================== +*/ +void HUD_WeaponsPostThink( local_state_s *from, local_state_s *to, usercmd_t *cmd, double time, unsigned int random_seed ) +{ + int i; + int buttonsChanged; + CBasePlayerWeapon *pWeapon = NULL; + CBasePlayerWeapon *pCurrent; + weapon_data_t nulldata, *pfrom, *pto; + static int lasthealth; + + memset( &nulldata, 0, sizeof( nulldata ) ); + + HUD_InitClientWeapons(); + + // Get current clock + gpGlobals->time = time; + + // Fill in data based on selected weapon + // FIXME, make this a method in each weapon? where you pass in an entity_state_t *? + switch ( from->client.m_iId ) + { + case WEAPON_DISC: + pWeapon = &g_Disc; + break; + } + + // We are not predicting the current weapon, just bow out here. + if ( !pWeapon ) + return; + + for ( i = 0; i < 32; i++ ) + { + pCurrent = g_pWpns[ i ]; + if ( !pCurrent ) + { + continue; + } + + pfrom = &from->weapondata[ i ]; + + pCurrent->m_fInReload = pfrom->m_fInReload; + pCurrent->m_iClip = pfrom->m_iClip; + pCurrent->m_flNextPrimaryAttack = pfrom->m_flNextPrimaryAttack; + pCurrent->m_flNextSecondaryAttack = pfrom->m_flNextSecondaryAttack; + pCurrent->m_flTimeWeaponIdle = pfrom->m_flTimeWeaponIdle; + + // Ricochet uses m_iClip to transmit current/primary ammo to client + if ( pWeapon == pCurrent ) + { + player.m_rgAmmo[pCurrent->m_iPrimaryAmmoType] = pfrom->m_iClip; + } + } + + // For random weapon events, use this seed to seed random # generator + player.random_seed = random_seed; + + // Get old buttons from previous state. + player.m_afButtonLast = from->playerstate.oldbuttons; + + // Which buttsons chave changed + buttonsChanged = (player.m_afButtonLast ^ cmd->buttons); // These buttons have changed this frame + + // Debounced button codes for pressed/released + // The changed ones still down are "pressed" + player.m_afButtonPressed = buttonsChanged & cmd->buttons; + // The ones not down are "released" + player.m_afButtonReleased = buttonsChanged & (~cmd->buttons); + + // Set player variables that weapons code might check/alter + player.pev->button = cmd->buttons; + + player.pev->velocity = from->client.velocity; + player.pev->flags = from->client.flags; + + player.pev->deadflag = from->client.deadflag; + player.pev->waterlevel = from->client.waterlevel; + player.pev->maxspeed = from->client.maxspeed; + player.pev->fov = from->client.fov; + player.pev->weaponanim = from->client.weaponanim; + player.pev->viewmodel = from->client.viewmodel; + player.m_flNextAttack = from->client.m_flNextAttack; + player.m_flBackupTime = from->client.fuser1; + player.m_Activity = (Activity)(int)from->client.fuser2; + player.m_flThrowTime = from->client.fuser3; + + player.m_vecOldVelocity = from->client.vuser1; + + player.pev->sequence = from->playerstate.sequence; + player.pev->gaitsequence = from->playerstate.gaitsequence; + player.pev->angles = from->playerstate.angles; + + // Point to current weapon object + if ( from->client.m_iId ) + { + player.m_pActiveItem = g_pWpns[ from->client.m_iId ]; + } + + // Store pointer to our destination entity_state_t so we can get our origin, etc. from it + // for setting up events on the client + g_finalstate = to; + + // Don't go firing anything if we have died. + // Or if we don't have a weapon model deployed + if ( ( player.pev->deadflag != ( DEAD_DISCARDBODY + 1 ) ) && !CL_IsDead() && player.pev->viewmodel ) + { + if ( player.m_flNextAttack <= 0 ) + { + pWeapon->ItemPostFrame(); + } + } + + // If we are running events/etc. go ahead and see if we + // managed to die between last frame and this one + // If so, run the appropriate player killed or spawn function + if ( g_runfuncs ) + { + if ( to->client.health <= 0 && lasthealth > 0 ) + { + player.Killed( NULL, 0 ); + } + else if ( to->client.health > 0 && lasthealth <= 0 ) + { + player.Spawn(); + } + + lasthealth = to->client.health; + } + + // Fix up animations, etc. + player.PostThink(); + + // Assume that we are not going to switch weapons + to->client.m_iId = from->client.m_iId; + + // Now see if we issued a changeweapon command ( and we're not dead ) + if ( cmd->weaponselect && ( player.pev->deadflag != ( DEAD_DISCARDBODY + 1 ) ) ) + { + // Switched to a different weapon? + if ( from->weapondata[ cmd->weaponselect ].m_iId == cmd->weaponselect ) + { + CBasePlayerWeapon *pNew = g_pWpns[ cmd->weaponselect ]; + if ( pNew && ( pNew != pWeapon ) ) + { + // Put away old weapon + if (player.m_pActiveItem) + player.m_pActiveItem->Holster( ); + + player.m_pLastItem = player.m_pActiveItem; + player.m_pActiveItem = pNew; + + // Deploy new weapon + if (player.m_pActiveItem) + { + player.m_pActiveItem->Deploy( ); + } + + // Update weapon id so we can predict things correctly. + to->client.m_iId = cmd->weaponselect; + } + } + } + + // Copy in results of predcition code + to->client.viewmodel = player.pev->viewmodel; + to->client.fov = player.pev->fov; + to->client.weaponanim = player.pev->weaponanim; + to->client.m_flNextAttack = player.m_flNextAttack; + to->client.maxspeed = player.pev->maxspeed; + to->client.fuser1 = player.m_flBackupTime; + to->client.fuser2 = (float)(int)player.m_Activity; + to->client.fuser3 = player.m_flThrowTime; + + to->client.vuser1 = player.m_vecOldVelocity; + + to->playerstate.sequence = player.pev->sequence; + to->playerstate.gaitsequence = player.pev->gaitsequence; + + // Make sure that weapon animation matches what the game .dll is telling us + // over the wire ( fixes some animation glitches ) + if ( g_runfuncs && ( HUD_GetWeaponAnim() != to->client.weaponanim ) ) + { + int body = 2; + // Force a fixed anim down to viewmodel + HUD_SendWeaponAnim( to->client.weaponanim, body, 1 ); + } + + for ( i = 0; i < 32; i++ ) + { + pCurrent = g_pWpns[ i ]; + + pto = &to->weapondata[ i ]; + + if ( !pCurrent ) + { + memset( pto, 0, sizeof( weapon_data_t ) ); + continue; + } + + pto->m_fInReload = pCurrent->m_fInReload; + pto->m_iClip = pCurrent->m_iClip; + pto->m_flNextPrimaryAttack = pCurrent->m_flNextPrimaryAttack; + pto->m_flNextSecondaryAttack = pCurrent->m_flNextSecondaryAttack; + pto->m_flTimeWeaponIdle = pCurrent->m_flTimeWeaponIdle; + + // Decrement weapon counters, server does this at same time ( during post think, after doing everything else ) + pto->m_flNextReload -= cmd->msec / 1000.0; + pto->m_fNextAimBonus -= cmd->msec / 1000.0; + pto->m_flNextPrimaryAttack -= cmd->msec / 1000.0; + pto->m_flNextSecondaryAttack -= cmd->msec / 1000.0; + pto->m_flTimeWeaponIdle -= cmd->msec / 1000.0; + + if ( pto->m_flPumpTime != -9999 ) + { + pto->m_flPumpTime -= cmd->msec / 1000.0; + if ( pto->m_flPumpTime < -0.001 ) + pto->m_flPumpTime = -0.001; + } + + if ( pto->m_fNextAimBonus < -1.0 ) + { + pto->m_fNextAimBonus = -1.0; + } + + if ( pto->m_flNextPrimaryAttack < -1.0 ) + { + pto->m_flNextPrimaryAttack = -1.0; + } + + if ( pto->m_flNextSecondaryAttack < -0.001 ) + { + pto->m_flNextSecondaryAttack = -0.001; + } + + if ( pto->m_flTimeWeaponIdle < -0.001 ) + { + pto->m_flTimeWeaponIdle = -0.001; + } + + if ( pto->m_flNextReload < -0.001 ) + { + pto->m_flNextReload = -0.001; + } + } + + // m_flNextAttack is now part of the weapons, but is part of the player instead + to->client.m_flNextAttack -= cmd->msec / 1000.0; + if ( to->client.m_flNextAttack < -0.001 ) + { + to->client.m_flNextAttack = -0.001; + } + + to->client.fuser1 -= cmd->msec / 1000.0; + if ( to->client.fuser1 < -0.001 ) + { + to->client.fuser1 = -0.001; + } + + to->client.fuser3 -= cmd->msec / 1000.0; + if ( to->client.fuser3 < -0.001 ) + { + to->client.fuser3 = -0.001; + } + + // Wipe it so we can't use it after this frame + g_finalstate = NULL; +} + +/* +============================== +Ricochet_GetSequence + +============================== +*/ +void Ricochet_GetSequence( int *seq, int *gaitseq ) +{ + *seq = g_rseq; + *gaitseq = g_gaitseq; +} + +/* +============================== +Ricochet_SetSequence + +============================== +*/ +void Ricochet_SetSequence( int seq, int gaitseq ) +{ + g_rseq = seq; + g_gaitseq = gaitseq; +} + +/* +============================== +Ricochet_SetOrientation + +============================== +*/ +void Ricochet_SetOrientation( vec3_t o, vec3_t a ) +{ + g_clorg = o; + g_clang = a; +} + +/* +============================== +Ricochet_GetOrientation + +============================== +*/ +void Ricochet_GetOrientation( float *o, float *a ) +{ + int i; + + for ( i = 0; i < 3; i++ ) + { + o[ i ] = g_clorg[ i ]; + a[ i ] = g_clang[ i ]; + } +} + + +/* +===================== +HUD_PostRunCmd + +Client calls this during prediction, after it has moved the player and updated any info changed into to-> +time is the current client clock based on prediction +cmd is the command that caused the movement, etc +runfuncs is 1 if this is the first time we've predicted this command. If so, sounds and effects should play, otherwise, they should +be ignored +===================== +*/ +void EXPORT HUD_PostRunCmd( struct local_state_s *from, struct local_state_s *to, struct usercmd_s *cmd, int runfuncs, double time, unsigned int random_seed ) +{ + g_runfuncs = runfuncs; + + // We'll always do prediction of disc throwing + if ( cl_lw && cl_lw->value ) + { + // Allowed to fire? + if ( g_iCannotFire == FALSE ) + HUD_WeaponsPostThink( from, to, cmd, time, random_seed ); + } + else + { + to->client.fov = g_lastFOV; + } + + // Store of final sequence, etc. for client side animation + if ( g_runfuncs ) + { + Ricochet_SetSequence( to->playerstate.sequence, to->playerstate.gaitsequence ); + Ricochet_SetOrientation( to->playerstate.origin, cmd->viewangles ); + } + + // See if we stepped on a jump pad + Ricochet_CheckJumpPads( from, to ); + + // All games can use FOV state + g_lastFOV = to->client.fov; +} diff --git a/ricochet/cl_dll/hud.cpp b/ricochet/cl_dll/hud.cpp new file mode 100644 index 0000000..a0e6da1 --- /dev/null +++ b/ricochet/cl_dll/hud.cpp @@ -0,0 +1,590 @@ + /*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud.cpp +// +// implementation of CHud class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" +#include "hud_servers.h" +#include "vgui_TeamFortressViewport.h" + +#include "demo.h" +#include "demo_api.h" + +#include "vgui_ScorePanel.h" + +extern TeamFortressViewport *gViewPort; + + +class CHLVoiceStatusHelper : public IVoiceStatusHelper +{ +public: + virtual void GetPlayerTextColor(int entindex, int color[3]) + { + color[0] = color[1] = color[2] = 255; + + /* if( entindex >= 0 && entindex < sizeof(g_PlayerExtraInfo)/sizeof(g_PlayerExtraInfo[0]) ) + { + int iTeam = g_PlayerExtraInfo[entindex].teamnumber; + + if ( iTeam < 0 ) + { + iTeam = 0; + } + + iTeam = iTeam % iNumberOfTeamColors; + + color[0] = iTeamColors[iTeam][0]; + color[1] = iTeamColors[iTeam][1]; + color[2] = iTeamColors[iTeam][2]; + }*/ + } + + virtual void UpdateCursorState() + { + gViewPort->UpdateCursorState(); + } + + virtual int GetAckIconHeight() + { + return ScreenHeight - gHUD.m_iFontHeight*3 - 6; + } + + virtual bool CanShowSpeakerLabels() + { + if( gViewPort && gViewPort->m_pScoreBoard ) + return !gViewPort->m_pScoreBoard->isVisible(); + else + return false; + } +}; +static CHLVoiceStatusHelper g_VoiceStatusHelper; + +extern client_sprite_t *GetSpriteList(client_sprite_t *pList, const char *psz, int iRes, int iCount); + +extern cvar_t *sensitivity; +cvar_t *cl_lw = NULL; + +void ShutdownInput (void); + +void __CmdFunc_ToggleServerBrowser( void ) +{ + if ( gViewPort ) + { + gViewPort->ToggleServerBrowser(); + } +} + +//DECLARE_MESSAGE(m_Logo, Logo) +int __MsgFunc_Logo(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_Logo(pszName, iSize, pbuf ); +} + +//DECLARE_MESSAGE(m_Logo, Logo) +int __MsgFunc_ResetHUD(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_ResetHUD(pszName, iSize, pbuf ); +} + +int __MsgFunc_InitHUD(const char *pszName, int iSize, void *pbuf) +{ + gHUD.MsgFunc_InitHUD( pszName, iSize, pbuf ); + return 1; +} + +int __MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_SetFOV( pszName, iSize, pbuf ); +} + +int __MsgFunc_Concuss(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_Concuss( pszName, iSize, pbuf ); +} + +int __MsgFunc_GameMode(const char *pszName, int iSize, void *pbuf ) +{ + return gHUD.MsgFunc_GameMode( pszName, iSize, pbuf ); +} + +void __CmdFunc_OpenCommandMenu(void) +{ + if ( gViewPort ) + { + gViewPort->ShowCommandMenu(); + } +} + +void __CmdFunc_CloseCommandMenu(void) +{ + if ( gViewPort ) + { + gViewPort->InputSignalHideCommandMenu(); + } +} + +int __MsgFunc_StartRnd(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_StartRnd( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_EndRnd(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_EndRnd( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_ScoreInfo(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_ScoreInfo( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_TeamScore(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_TeamScore( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_TeamInfo(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_TeamInfo( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_Powerup(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_Powerup( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_Reward(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_Reward( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_Frozen(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_Frozen( pszName, iSize, pbuf ); + return 0; +} + +// This is called every time the DLL is loaded +void CHud :: Init( void ) +{ + HOOK_MESSAGE( Logo ); + HOOK_MESSAGE( ResetHUD ); + HOOK_MESSAGE( GameMode ); + HOOK_MESSAGE( InitHUD ); + HOOK_MESSAGE( SetFOV ); + HOOK_MESSAGE( Concuss ); + + HOOK_MESSAGE( ScoreInfo ); + HOOK_MESSAGE( TeamScore ); + HOOK_MESSAGE( TeamInfo ); + + // Discwar + HOOK_MESSAGE( StartRnd ); + HOOK_MESSAGE( EndRnd ); + HOOK_MESSAGE( Powerup ); + HOOK_MESSAGE( Reward ); + HOOK_MESSAGE( Frozen ); + + HOOK_COMMAND( "+commandmenu", OpenCommandMenu ); + HOOK_COMMAND( "-commandmenu", CloseCommandMenu ); + + m_iLogo = 0; + m_iFOV = 0; + + CVAR_CREATE( "zoom_sensitivity_ratio", "1.2", 0 ); + default_fov = CVAR_CREATE( "default_fov", "90", 0 ); + m_pCvarStealMouse = CVAR_CREATE( "hud_capturemouse", "1", FCVAR_ARCHIVE ); + cl_lw = gEngfuncs.pfnGetCvarPointer( "cl_lw" ); + + m_pSpriteList = NULL; + + // Clear any old HUD list + if ( m_pHudList ) + { + HUDLIST *pList; + while ( m_pHudList ) + { + pList = m_pHudList; + m_pHudList = m_pHudList->pNext; + free( pList ); + } + m_pHudList = NULL; + } + + // In case we get messages before the first update -- time will be valid + m_flTime = 1.0; + + m_Ammo.Init(); + m_Health.Init(); + m_Geiger.Init(); + m_Train.Init(); + m_Battery.Init(); + m_Flash.Init(); + m_Message.Init(); + m_StatusBar.Init(); + m_DeathNotice.Init(); + m_AmmoSecondary.Init(); + m_TextMessage.Init(); + m_StatusIcons.Init(); + + m_SayText.Init(); + m_Menu.Init(); + + GetClientVoiceMgr()->Init(&g_VoiceStatusHelper, (vgui::Panel **)&gViewPort); + + ServersInit(); + + MsgFunc_ResetHUD(0, 0, NULL ); +} + +// CHud destructor +// cleans up memory allocated for m_rg* arrays +CHud :: ~CHud() +{ + delete [] m_rghSprites; + delete [] m_rgrcRects; + delete [] m_rgszSpriteNames; + + if ( m_pHudList ) + { + HUDLIST *pList; + while ( m_pHudList ) + { + pList = m_pHudList; + m_pHudList = m_pHudList->pNext; + free( pList ); + } + m_pHudList = NULL; + } + + ServersShutdown(); +} + +// GetSpriteIndex() +// searches through the sprite list loaded from hud.txt for a name matching SpriteName +// returns an index into the gHUD.m_rghSprites[] array +// returns 0 if sprite not found +int CHud :: GetSpriteIndex( const char *SpriteName ) +{ + // look through the loaded sprite name list for SpriteName + for ( int i = 0; i < m_iSpriteCount; i++ ) + { + if ( strncmp( SpriteName, m_rgszSpriteNames + (i * MAX_SPRITE_NAME_LENGTH), MAX_SPRITE_NAME_LENGTH ) == 0 ) + return i; + } + + return -1; // invalid sprite +} + +void CHud :: VidInit( void ) +{ + m_scrinfo.iSize = sizeof(m_scrinfo); + GetScreenInfo(&m_scrinfo); + + // ---------- + // Load Sprites + // --------- +// m_hsprFont = LoadSprite("sprites/%d_font.spr"); + + m_hsprLogo = 0; + m_hsprCursor = 0; + + if (ScreenWidth < 640) + m_iRes = 320; + else + m_iRes = 640; + + // Only load this once + if ( !m_pSpriteList ) + { + // we need to load the hud.txt, and all sprites within + m_pSpriteList = SPR_GetList("sprites/hud.txt", &m_iSpriteCountAllRes); + + if (m_pSpriteList) + { + // count the number of sprites of the appropriate res + m_iSpriteCount = 0; + client_sprite_t *p = m_pSpriteList; + int j; + for ( j = 0; j < m_iSpriteCountAllRes; j++ ) + { + if ( p->iRes == m_iRes ) + m_iSpriteCount++; + p++; + } + + // allocated memory for sprite handle arrays + m_rghSprites = new HSPRITE[m_iSpriteCount]; + m_rgrcRects = new wrect_t[m_iSpriteCount]; + m_rgszSpriteNames = new char[m_iSpriteCount * MAX_SPRITE_NAME_LENGTH]; + + p = m_pSpriteList; + int index = 0; + for ( j = 0; j < m_iSpriteCountAllRes; j++ ) + { + if ( p->iRes == m_iRes ) + { + char sz[256]; + sprintf(sz, "sprites/%s.spr", p->szSprite); + m_rghSprites[index] = SPR_Load(sz); + m_rgrcRects[index] = p->rc; + strncpy( &m_rgszSpriteNames[index * MAX_SPRITE_NAME_LENGTH], p->szName, MAX_SPRITE_NAME_LENGTH ); + + index++; + } + + p++; + } + } + } + else + { + // we have already have loaded the sprite reference from hud.txt, but + // we need to make sure all the sprites have been loaded (we've gone through a transition, or loaded a save game) + client_sprite_t *p = m_pSpriteList; + int index = 0; + for ( int j = 0; j < m_iSpriteCountAllRes; j++ ) + { + if ( p->iRes == m_iRes ) + { + char sz[256]; + sprintf( sz, "sprites/%s.spr", p->szSprite ); + m_rghSprites[index] = SPR_Load(sz); + index++; + } + + p++; + } + } + + // assumption: number_1, number_2, etc, are all listed and loaded sequentially + m_HUD_number_0 = GetSpriteIndex( "number_0" ); + + m_iFontHeight = m_rgrcRects[m_HUD_number_0].bottom - m_rgrcRects[m_HUD_number_0].top; + + m_Ammo.VidInit(); + m_Health.VidInit(); + m_Geiger.VidInit(); + m_Train.VidInit(); + m_Battery.VidInit(); + m_Flash.VidInit(); + m_Message.VidInit(); + m_StatusBar.VidInit(); + m_DeathNotice.VidInit(); + m_SayText.VidInit(); + m_Menu.VidInit(); + m_AmmoSecondary.VidInit(); + m_TextMessage.VidInit(); + m_StatusIcons.VidInit(); + + GetClientVoiceMgr()->VidInit(); +} + +int CHud::MsgFunc_Logo(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + // update Train data + m_iLogo = READ_BYTE(); + + return 1; +} + +float g_lastFOV = 0.0; + +/* +============ +COM_FileBase +============ +*/ +// Extracts the base name of a file (no path, no extension, assumes '/' as path separator) +void COM_FileBase ( const char *in, char *out) +{ + int len, start, end; + + len = strlen( in ); + + // scan backward for '.' + end = len - 1; + while ( end && in[end] != '.' && in[end] != '/' && in[end] != '\\' ) + end--; + + if ( in[end] != '.' ) // no '.', copy to end + end = len-1; + else + end--; // Found ',', copy to left of '.' + + + // Scan backward for '/' + start = len-1; + while ( start >= 0 && in[start] != '/' && in[start] != '\\' ) + start--; + + if ( in[start] != '/' && in[start] != '\\' ) + start = 0; + else + start++; + + // Length of new sting + len = end - start + 1; + + // Copy partial string + strncpy( out, &in[start], len ); + // Terminate it + out[len] = 0; +} + +/* +================= +HUD_IsGame + +================= +*/ +int HUD_IsGame( const char *game ) +{ + const char *gamedir; + char gd[ 1024 ]; + + gamedir = gEngfuncs.pfnGetGameDirectory(); + if ( gamedir && gamedir[0] ) + { + COM_FileBase( gamedir, gd ); + if ( !stricmp( gd, game ) ) + return 1; + } + return 0; +} + +/* +===================== +HUD_GetFOV + +Returns last FOV +===================== +*/ +float HUD_GetFOV( void ) +{ +/* + if ( gEngfuncs.pDemoAPI->IsRecording() ) + { + // Write it + int i = 0; + unsigned char buf[ 100 ]; + + // Active + *( float * )&buf[ i ] = g_lastFOV; + i += sizeof( float ); + + Demo_WriteBuffer( TYPE_ZOOM, i, buf ); + } + + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + { + g_lastFOV = g_demozoom; + } +*/ + return g_lastFOV; +} + +int CHud::MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + int newfov = READ_BYTE(); + int def_fov = CVAR_GET_FLOAT( "default_fov" ); + + if ( newfov == 0 ) + { + m_iFOV = def_fov; + } + else + { + m_iFOV = newfov; + } + + // the clients fov is actually set in the client data update section of the hud + + // Set a new sensitivity + if ( m_iFOV == def_fov ) + { + // reset to saved sensitivity + m_flMouseSensitivity = 0; + } + else + { + // set a new sensitivity that is proportional to the change from the FOV default + m_flMouseSensitivity = sensitivity->value * ((float)newfov / (float)def_fov) * CVAR_GET_FLOAT("zoom_sensitivity_ratio"); + } + + return 1; +} + + +void CHud::AddHudElem(CHudBase *phudelem) +{ + HUDLIST *pdl, *ptemp; + +//phudelem->Think(); + + if (!phudelem) + return; + + pdl = (HUDLIST *)malloc(sizeof(HUDLIST)); + if (!pdl) + return; + + memset(pdl, 0, sizeof(HUDLIST)); + pdl->p = phudelem; + + if (!m_pHudList) + { + m_pHudList = pdl; + return; + } + + ptemp = m_pHudList; + + while (ptemp->pNext) + ptemp = ptemp->pNext; + + ptemp->pNext = pdl; +} + +float CHud::GetSensitivity( void ) +{ + return m_flMouseSensitivity; +} diff --git a/ricochet/cl_dll/hud.h b/ricochet/cl_dll/hud.h new file mode 100644 index 0000000..4d82c2b --- /dev/null +++ b/ricochet/cl_dll/hud.h @@ -0,0 +1,586 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud.h +// +// class CHud declaration +// +// CHud handles the message, calculation, and drawing the HUD +// + + +#define RGB_YELLOWISH 0x00FFA000 //255,160,0 +#define RGB_REDISH 0x00FF1010 //255,160,0 +#define RGB_GREENISH 0x0000A000 //0,160,0 + +#include "wrect.h" +#include "cl_dll.h" +#include "ammo.h" + +#define DHN_DRAWZERO 1 +#define DHN_2DIGITS 2 +#define DHN_3DIGITS 4 +#define MIN_ALPHA 100 + +#define HUDELEM_ACTIVE 1 + +typedef struct { + int x, y; +} POSITION; + +typedef struct { + unsigned char r,g,b,a; +} RGBA; + + +#define HUD_ACTIVE 1 +#define HUD_INTERMISSION 2 + +#define MAX_PLAYER_NAME_LENGTH 32 +#define MAX_MOTD_LENGTH 1024 + +#ifndef _WIN32 +#define _cdecl +#endif +// +//----------------------------------------------------- +// +class CHudBase +{ +public: + POSITION m_pos; + int m_type; + int m_iFlags; // active, moving, + virtual int Init( void ) {return 0;} + virtual int VidInit( void ) {return 0;} + virtual int Draw(float flTime) {return 0;} + virtual void Think(void) {return;} + virtual void Reset(void) {return;} + virtual void InitHUDData( void ) {} // called every time a server is connected to + +}; + +struct HUDLIST { + CHudBase *p; + HUDLIST *pNext; +}; + +#include "voice_status.h" + +// +//----------------------------------------------------- +// +class CHudAmmo: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + void Think(void); + void Reset(void); + int DrawWList(float flTime); + int MsgFunc_CurWeapon(const char *pszName, int iSize, void *pbuf); + int MsgFunc_WeaponList(const char *pszName, int iSize, void *pbuf); + int MsgFunc_AmmoX(const char *pszName, int iSize, void *pbuf); + int MsgFunc_AmmoPickup( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_WeapPickup( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_ItemPickup( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_HideWeapon( const char *pszName, int iSize, void *pbuf ); + + void _cdecl UserCmd_Slot1( void ); + void _cdecl UserCmd_Slot2( void ); + void _cdecl UserCmd_Slot3( void ); + void _cdecl UserCmd_Slot4( void ); + void _cdecl UserCmd_Slot5( void ); + void _cdecl UserCmd_Slot6( void ); + void _cdecl UserCmd_Slot7( void ); + void _cdecl UserCmd_Slot8( void ); + void _cdecl UserCmd_Slot9( void ); + void _cdecl UserCmd_Slot10( void ); + void _cdecl UserCmd_Close( void ); + void _cdecl UserCmd_NextWeapon( void ); + void _cdecl UserCmd_PrevWeapon( void ); + +private: + float m_fFade; + RGBA m_rgba; + WEAPON *m_pWeapon; + int m_HUD_bucket0; + int m_HUD_selection; + +}; + +// +//----------------------------------------------------- +// + +class CHudAmmoSecondary: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + void Reset( void ); + int Draw(float flTime); + + int MsgFunc_SecAmmoVal( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_SecAmmoIcon( const char *pszName, int iSize, void *pbuf ); + +private: + enum { + MAX_SEC_AMMO_VALUES = 4 + }; + + int m_HUD_ammoicon; // sprite indices + int m_iAmmoAmounts[MAX_SEC_AMMO_VALUES]; + float m_fFade; +}; + + +#include "health.h" + + +#define FADE_TIME 100 + + +// +//----------------------------------------------------- +// +class CHudGeiger: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_Geiger(const char *pszName, int iSize, void *pbuf); + +private: + int m_iGeigerRange; + +}; + +// +//----------------------------------------------------- +// +class CHudTrain: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_Train(const char *pszName, int iSize, void *pbuf); + +private: + HSPRITE m_hSprite; + int m_iPos; + +}; + +// +//----------------------------------------------------- +// +class CHudStatusBar : public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw( float flTime ); + void Reset( void ); + void ParseStatusString( int line_num ); + + int MsgFunc_StatusText( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_StatusValue( const char *pszName, int iSize, void *pbuf ); + +protected: + enum { + MAX_STATUSTEXT_LENGTH = 128, + MAX_STATUSBAR_VALUES = 8, + MAX_STATUSBAR_LINES = 2, + }; + + char m_szStatusText[MAX_STATUSBAR_LINES][MAX_STATUSTEXT_LENGTH]; // a text string describing how the status bar is to be drawn + char m_szStatusBar[MAX_STATUSBAR_LINES][MAX_STATUSTEXT_LENGTH]; // the constructed bar that is drawn + int m_iStatusValues[MAX_STATUSBAR_VALUES]; // an array of values for use in the status bar + + int m_bReparseString; // set to TRUE whenever the m_szStatusBar needs to be recalculated +}; + +// +//----------------------------------------------------- +// +enum +{ + MAX_PLAYERS = 64, + MAX_TEAMS = 64, + MAX_TEAM_NAME = 16, +}; + +struct extra_player_info_t +{ + short frags; + short deaths; + short playerclass; + short teamnumber; + char teamname[MAX_TEAM_NAME]; +}; + +struct team_info_t +{ + char name[MAX_TEAM_NAME]; + short frags; + short deaths; + short ping; + short packetloss; + short ownteam; + short players; + int already_drawn; + int scores_overriden; + int teamnumber; +}; + +extern hud_player_info_t g_PlayerInfoList[MAX_PLAYERS+1]; // player info from the engine +extern extra_player_info_t g_PlayerExtraInfo[MAX_PLAYERS+1]; // additional player info sent directly to the client dll +extern team_info_t g_TeamInfo[MAX_TEAMS+1]; +extern int g_IsSpectator[MAX_PLAYERS+1]; + + +// +//----------------------------------------------------- +// +class CHudDeathNotice : public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + int Draw( float flTime ); + int MsgFunc_DeathMsg( const char *pszName, int iSize, void *pbuf ); + +private: + int m_HUD_d_skull; // sprite index of skull icon +}; + +// +//----------------------------------------------------- +// +class CHudMenu : public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + void Reset( void ); + int Draw( float flTime ); + int MsgFunc_ShowMenu( const char *pszName, int iSize, void *pbuf ); + + void SelectMenuItem( int menu_item ); + + int m_fMenuDisplayed; + int m_bitsValidSlots; + float m_flShutoffTime; + int m_fWaitingForMore; +}; + +// +//----------------------------------------------------- +// +class CHudSayText : public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + int Draw( float flTime ); + int MsgFunc_SayText( const char *pszName, int iSize, void *pbuf ); + void SayTextPrint( const char *pszBuf, int iBufSize, int clientIndex = -1 ); + void EnsureTextFitsInOneLineAndWrapIfHaveTo( int line ); +}; + +// +//----------------------------------------------------- +// +class CHudBattery: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_Battery(const char *pszName, int iSize, void *pbuf ); + +private: + HSPRITE m_hSprite1; + HSPRITE m_hSprite2; + wrect_t *m_prc1; + wrect_t *m_prc2; + int m_iBat; + float m_fFade; + int m_iHeight; // width of the battery innards +}; + + +// +//----------------------------------------------------- +// +class CHudFlashlight: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + void Reset( void ); + int MsgFunc_Flashlight(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_FlashBat(const char *pszName, int iSize, void *pbuf ); + +private: + HSPRITE m_hSprite1; + HSPRITE m_hSprite2; + HSPRITE m_hBeam; + wrect_t *m_prc1; + wrect_t *m_prc2; + wrect_t *m_prcBeam; + float m_flBat; + int m_iBat; + int m_fOn; + float m_fFade; + int m_iWidth; // width of the battery innards +}; + +// +//----------------------------------------------------- +// +const int maxHUDMessages = 16; +struct message_parms_t +{ + client_textmessage_t *pMessage; + float time; + int x, y; + int totalWidth, totalHeight; + int width; + int lines; + int lineLength; + int length; + int r, g, b; + int text; + int fadeBlend; + float charTime; + float fadeTime; +}; + +// +//----------------------------------------------------- +// + +class CHudTextMessage: public CHudBase +{ +public: + int Init( void ); + static char *LocaliseTextString( const char *msg, char *dst_buffer, int buffer_size ); + static char *BufferedLocaliseTextString( const char *msg ); + char *LookupString( const char *msg_name, int *msg_dest = NULL ); + int MsgFunc_TextMsg(const char *pszName, int iSize, void *pbuf); +}; + +// +//----------------------------------------------------- +// + +class CHudMessage: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_HudText(const char *pszName, int iSize, void *pbuf); + int MsgFunc_GameTitle(const char *pszName, int iSize, void *pbuf); + + float FadeBlend( float fadein, float fadeout, float hold, float localTime ); + int XPosition( float x, int width, int lineWidth ); + int YPosition( float y, int height ); + + void MessageAdd( const char *pName, float time ); + void MessageDrawScan( client_textmessage_t *pMessage, float time ); + void MessageScanStart( void ); + void MessageScanNextChar( void ); + void Reset( void ); + +private: + client_textmessage_t *m_pMessages[maxHUDMessages]; + float m_startTime[maxHUDMessages]; + message_parms_t m_parms; + float m_gameTitleTime; + client_textmessage_t *m_pGameTitle; + + int m_HUD_title_life; + int m_HUD_title_half; +}; + +// +//----------------------------------------------------- +// +#define MAX_SPRITE_NAME_LENGTH 24 + +class CHudStatusIcons: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + void Reset( void ); + int Draw(float flTime); + int MsgFunc_StatusIcon(const char *pszName, int iSize, void *pbuf); + + enum { + MAX_ICONSPRITENAME_LENGTH = MAX_SPRITE_NAME_LENGTH, + MAX_ICONSPRITES = 4, + }; + + + //had to make these public so CHud could access them (to enable concussion icon) + //could use a friend declaration instead... + void EnableIcon( char *pszIconName, unsigned char red, unsigned char green, unsigned char blue ); + void DisableIcon( char *pszIconName ); + +private: + + typedef struct + { + char szSpriteName[MAX_ICONSPRITENAME_LENGTH]; + HSPRITE spr; + wrect_t rc; + unsigned char r, g, b; + } icon_sprite_t; + + icon_sprite_t m_IconList[MAX_ICONSPRITES]; + +}; + + +// +//----------------------------------------------------- +// +typedef struct cvar_s cvar_t; + +class CHud +{ +private: + HUDLIST *m_pHudList; + HSPRITE m_hsprLogo; + int m_iLogo; + client_sprite_t *m_pSpriteList; + int m_iSpriteCount; + int m_iSpriteCountAllRes; + float m_flMouseSensitivity; + int m_iConcussionEffect; + +public: + + HSPRITE m_hsprCursor; + float m_flTime; // the current client time + float m_fOldTime; // the time at which the HUD was last redrawn + double m_flTimeDelta; // the difference between flTime and fOldTime + Vector m_vecOrigin; + Vector m_vecAngles; + int m_iKeyBits; + int m_iHideHUDDisplay; + int m_iFOV; + int m_Teamplay; + int m_iRes; + cvar_t *m_pCvarStealMouse; + + int m_iFontHeight; + int DrawHudNumber(int x, int y, int iFlags, int iNumber, int r, int g, int b ); + int DrawHudString(int x, int y, int iMaxX, char *szString, int r, int g, int b ); + int DrawHudStringReverse( int xpos, int ypos, int iMinX, char *szString, int r, int g, int b ); + int DrawHudNumberString( int xpos, int ypos, int iMinX, int iNumber, int r, int g, int b ); + int GetNumWidth(int iNumber, int iFlags); + +private: + // the memory for these arrays are allocated in the first call to CHud::VidInit(), when the hud.txt and associated sprites are loaded. + // freed in ~CHud() + HSPRITE *m_rghSprites; /*[HUD_SPRITE_COUNT]*/ // the sprites loaded from hud.txt + wrect_t *m_rgrcRects; /*[HUD_SPRITE_COUNT]*/ + char *m_rgszSpriteNames; /*[HUD_SPRITE_COUNT][MAX_SPRITE_NAME_LENGTH]*/ + + struct cvar_s *default_fov; +public: + HSPRITE GetSprite( int index ) + { + return (index < 0) ? 0 : m_rghSprites[index]; + } + + wrect_t& GetSpriteRect( int index ) + { + return m_rgrcRects[index]; + } + + + int GetSpriteIndex( const char *SpriteName ); // gets a sprite index, for use in the m_rghSprites[] array + + CHudAmmo m_Ammo; + CHudHealth m_Health; + CHudGeiger m_Geiger; + CHudBattery m_Battery; + CHudTrain m_Train; + CHudFlashlight m_Flash; + CHudMessage m_Message; + CHudStatusBar m_StatusBar; + CHudDeathNotice m_DeathNotice; + CHudSayText m_SayText; + CHudMenu m_Menu; + CHudAmmoSecondary m_AmmoSecondary; + CHudTextMessage m_TextMessage; + CHudStatusIcons m_StatusIcons; + + void Init( void ); + void VidInit( void ); + void Think(void); + int Redraw( float flTime, int intermission ); + int UpdateClientData( client_data_t *cdata, float time ); + + CHud() : m_iSpriteCount(0), m_pHudList(NULL) {} + ~CHud(); // destructor, frees allocated memory + + // user messages + int _cdecl MsgFunc_Damage(const char *pszName, int iSize, void *pbuf ); + int _cdecl MsgFunc_GameMode(const char *pszName, int iSize, void *pbuf ); + int _cdecl MsgFunc_Logo(const char *pszName, int iSize, void *pbuf); + int _cdecl MsgFunc_ResetHUD(const char *pszName, int iSize, void *pbuf); + void _cdecl MsgFunc_InitHUD( const char *pszName, int iSize, void *pbuf ); + int _cdecl MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf); + int _cdecl MsgFunc_Concuss( const char *pszName, int iSize, void *pbuf ); + // Screen information + SCREENINFO m_scrinfo; + + int m_iWeaponBits; + int m_fPlayerDead; + int m_iIntermission; + + // sprite indexes + int m_HUD_number_0; + + + void AddHudElem(CHudBase *p); + + float GetSensitivity(); +}; + +extern CHud gHUD; + +class TeamFortressViewport; + +extern TeamFortressViewport *gViewPort; + +extern int g_iPlayerClass; +extern int g_iTeamNumber; +extern int g_iUser1; +extern int g_iUser2; diff --git a/ricochet/cl_dll/hud_iface.h b/ricochet/cl_dll/hud_iface.h new file mode 100644 index 0000000..d336741 --- /dev/null +++ b/ricochet/cl_dll/hud_iface.h @@ -0,0 +1,31 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( HUD_IFACEH ) +#define HUD_IFACEH +#pragma once + +#ifdef _WIN32 +#define EXPORT _declspec( dllexport ) +#else +#define EXPORT __attribute__ ((visibility("default"))) +#endif +#define DLLEXPORT EXPORT + +typedef int (*pfnUserMsgHook)(const char *pszName, int iSize, void *pbuf); +#include "wrect.h" +#include "../engine/cdll_int.h" +extern cl_enginefunc_t gEngfuncs; + +#endif \ No newline at end of file diff --git a/ricochet/cl_dll/hud_msg.cpp b/ricochet/cl_dll/hud_msg.cpp new file mode 100644 index 0000000..0c83bd3 --- /dev/null +++ b/ricochet/cl_dll/hud_msg.cpp @@ -0,0 +1,117 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud_msg.cpp +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include "vgui_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_discobjects.h" + +/// USER-DEFINED SERVER MESSAGE HANDLERS + +int g_iArenaMode = 0; + +int CHud :: MsgFunc_ResetHUD(const char *pszName, int iSize, void *pbuf ) +{ + ASSERT( iSize == 0 ); + + // clear all hud data + HUDLIST *pList = m_pHudList; + + while ( pList ) + { + if ( pList->p ) + pList->p->Reset(); + pList = pList->pNext; + } + + // reset sensitivity + m_flMouseSensitivity = 0; + + // reset concussion effect + m_iConcussionEffect = 0; + + return 1; +} + +void CHud :: MsgFunc_InitHUD( const char *pszName, int iSize, void *pbuf ) +{ + // prepare all hud data + HUDLIST *pList = m_pHudList; + + while (pList) + { + if ( pList->p ) + pList->p->InitHUDData(); + pList = pList->pNext; + } +} + + +int CHud :: MsgFunc_GameMode(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + g_iArenaMode = READ_BYTE(); + + if (gViewPort) + { + gViewPort->m_pDiscEndRound->setVisible( false ); + gViewPort->m_pDiscStartRound->setVisible ( false ); + } + + return 1; +} + + +int CHud :: MsgFunc_Damage(const char *pszName, int iSize, void *pbuf ) +{ + int armor, blood; + Vector from; + int i; + float count; + + BEGIN_READ( pbuf, iSize ); + armor = READ_BYTE(); + blood = READ_BYTE(); + + for (i=0 ; i<3 ; i++) + from[i] = READ_COORD(); + + count = (blood * 0.5) + (armor * 0.5); + + if (count < 10) + count = 10; + + // TODO: kick viewangles, show damage visually + + return 1; +} + +int CHud :: MsgFunc_Concuss( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + m_iConcussionEffect = READ_BYTE(); + if (m_iConcussionEffect) + this->m_StatusIcons.EnableIcon("dmg_concuss",255,160,0); + else + this->m_StatusIcons.DisableIcon("dmg_concuss"); + return 1; +} + diff --git a/ricochet/cl_dll/hud_redraw.cpp b/ricochet/cl_dll/hud_redraw.cpp new file mode 100644 index 0000000..519112e --- /dev/null +++ b/ricochet/cl_dll/hud_redraw.cpp @@ -0,0 +1,264 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud_redraw.cpp +// +#include +#include "hud.h" +#include "cl_util.h" + + +#define MAX_LOGO_FRAMES 56 + +int grgLogoFrame[MAX_LOGO_FRAMES] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 13, 13, 13, 13, 13, 12, 11, 10, 9, 8, 14, 15, + 16, 17, 18, 19, 20, 20, 20, 20, 20, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 29, 29, 29, 29, 29, 28, 27, 26, 25, 24, 30, 31 +}; + +extern int g_iVisibleMouse; + +// Think +void CHud::Think(void) +{ + m_scrinfo.iSize = sizeof(m_scrinfo); + GetScreenInfo(&m_scrinfo); + + HUDLIST *pList = m_pHudList; + while (pList) + { + if (pList->p->m_iFlags & HUD_ACTIVE) + pList->p->Think(); + pList = pList->pNext; + } + + // think about default fov + if ( m_iFOV == 0 ) + { // only let players adjust up in fov, and only if they are not overriden by something else + m_iFOV = max( default_fov->value, 90 ); + } +} + +// Redraw +// step through the local data, placing the appropriate graphics & text as appropriate +// returns 1 if they've changed, 0 otherwise +int CHud :: Redraw( float flTime, int intermission ) +{ + m_fOldTime = m_flTime; // save time of previous redraw + m_flTime = flTime; + m_flTimeDelta = (double)m_flTime - m_fOldTime; + + // Clock was reset, reset delta + if ( m_flTimeDelta < 0 ) + m_flTimeDelta = 0; + + m_iIntermission = intermission; + + // if no redrawing is necessary + // return 0; + + HUDLIST *pList = m_pHudList; + + while (pList) + { + if ( !intermission ) + { + if ((pList->p->m_iFlags & HUD_ACTIVE) && !(m_iHideHUDDisplay & HIDEHUD_ALL)) + pList->p->Draw(flTime); + } + else + { // it's an intermission, so only draw hud elements that are set to draw during intermissions + if ( pList->p->m_iFlags & HUD_INTERMISSION ) + pList->p->Draw( flTime ); + } + + pList = pList->pNext; + } + + // are we in demo mode? do we need to draw the logo in the top corner? + if (m_iLogo) + { + int x, y, i; + + if (m_hsprLogo == 0) + m_hsprLogo = LoadSprite("sprites/%d_logo.spr"); + + SPR_Set(m_hsprLogo, 250, 250, 250 ); + + x = SPR_Width(m_hsprLogo, 0); + x = ScreenWidth - x; + y = SPR_Height(m_hsprLogo, 0)/2; + + // Draw the logo at 20 fps + int iFrame = (int)(flTime * 20) % MAX_LOGO_FRAMES; + i = grgLogoFrame[iFrame] - 1; + + SPR_DrawAdditive(i, x, y, NULL); + } + + return 1; +} + +void ScaleColors( int &r, int &g, int &b, int a ) +{ + float x = (float)a / 255; + r = (int)(r * x); + g = (int)(g * x); + b = (int)(b * x); +} + +int CHud :: DrawHudString(int xpos, int ypos, int iMaxX, char *szIt, int r, int g, int b ) +{ + // draw the string until we hit the null character or a newline character + for ( ; *szIt != 0 && *szIt != '\n'; szIt++ ) + { + int next = xpos + gHUD.m_scrinfo.charWidths[ *szIt ]; // variable-width fonts look cool + if ( next > iMaxX ) + return xpos; + + TextMessageDrawChar( xpos, ypos, *szIt, r, g, b ); + xpos = next; + } + + return xpos; +} + +int CHud :: DrawHudNumberString( int xpos, int ypos, int iMinX, int iNumber, int r, int g, int b ) +{ + char szString[32]; + sprintf( szString, "%d", iNumber ); + return DrawHudStringReverse( xpos, ypos, iMinX, szString, r, g, b ); + +} + +// draws a string from right to left (right-aligned) +int CHud :: DrawHudStringReverse( int xpos, int ypos, int iMinX, char *szString, int r, int g, int b ) +{ + // find the end of the string + char *szIt; + for ( szIt = szString; *szIt != 0; szIt++ ) + { // we should count the length? + } + + // iterate throug the string in reverse + for ( szIt--; szIt != (szString-1); szIt-- ) + { + int next = xpos - gHUD.m_scrinfo.charWidths[ *szIt ]; // variable-width fonts look cool + if ( next < iMinX ) + return xpos; + xpos = next; + + TextMessageDrawChar( xpos, ypos, *szIt, r, g, b ); + } + + return xpos; +} + +int CHud :: DrawHudNumber( int x, int y, int iFlags, int iNumber, int r, int g, int b) +{ + int iWidth = GetSpriteRect(m_HUD_number_0).right - GetSpriteRect(m_HUD_number_0).left; + int k; + + if (iNumber > 0) + { + // SPR_Draw 100's + if (iNumber >= 100) + { + k = iNumber/100; + SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); + SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + x += iWidth; + } + else if (iFlags & (DHN_3DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + // SPR_Draw 10's + if (iNumber >= 10) + { + k = (iNumber % 100)/10; + SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); + SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + x += iWidth; + } + else if (iFlags & (DHN_3DIGITS | DHN_2DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + // SPR_Draw ones + k = iNumber % 10; + SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); + SPR_DrawAdditive(0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + x += iWidth; + } + else if (iFlags & DHN_DRAWZERO) + { + SPR_Set(GetSprite(m_HUD_number_0), r, g, b ); + + // SPR_Draw 100's + if (iFlags & (DHN_3DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + if (iFlags & (DHN_3DIGITS | DHN_2DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + // SPR_Draw ones + + SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0)); + x += iWidth; + } + + return x; +} + + +int CHud::GetNumWidth( int iNumber, int iFlags ) +{ + if (iFlags & (DHN_3DIGITS)) + return 3; + + if (iFlags & (DHN_2DIGITS)) + return 2; + + if (iNumber <= 0) + { + if (iFlags & (DHN_DRAWZERO)) + return 1; + else + return 0; + } + + if (iNumber < 10) + return 1; + + if (iNumber < 100) + return 2; + + return 3; + +} + + diff --git a/ricochet/cl_dll/hud_servers.cpp b/ricochet/cl_dll/hud_servers.cpp new file mode 100644 index 0000000..ff478c3 --- /dev/null +++ b/ricochet/cl_dll/hud_servers.cpp @@ -0,0 +1,1247 @@ +// hud_servers.cpp +#include "hud.h" +#include "cl_util.h" +#include "hud_servers_priv.h" +#include "hud_servers.h" +#include "net_api.h" +#include +#ifdef _WIN32 +#include +#else +#include +#endif +static int context_id; + +// Default master server address in case we can't read any from valvecomm.lst file +#define VALVE_MASTER_ADDRESS "half-life.east.won.net" +#define PORT_MASTER 27010 +#define PORT_SERVER 27015 + +// File where we really should look for master servers +#define MASTER_PARSE_FILE "valvecomm.lst" + +#define MAX_QUERIES 20 + +#define NET_API gEngfuncs.pNetAPI + +static CHudServers *g_pServers = NULL; + +/* +=================== +ListResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK ListResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->ListResponse( response ); + } +} + +/* +=================== +ServerResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK ServerResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->ServerResponse( response ); + } +} + +/* +=================== +PingResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK PingResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->PingResponse( response ); + } +} + +/* +=================== +RulesResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK RulesResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->RulesResponse( response ); + } +} +/* +=================== +PlayersResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK PlayersResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->PlayersResponse( response ); + } +} +/* +=================== +ListResponse + +=================== +*/ +void CHudServers::ListResponse( struct net_response_s *response ) +{ + request_t *list; + request_t *p; + int c = 0; + + if ( !( response->error == NET_SUCCESS ) ) + return; + + if ( response->type != NETAPI_REQUEST_SERVERLIST ) + return; + + if ( response->response ) + { + list = ( request_t * ) response->response; + while ( list ) + { + c++; + + //if ( c < 40 ) + { + // Copy from parsed stuff + p = new request_t; + p->context = -1; + p->remote_address = list->remote_address; + p->next = m_pServerList; + m_pServerList = p; + } + + // Move on + list = list->next; + } + } + + gEngfuncs.Con_Printf( "got list\n" ); + + m_nQuerying = 1; + m_nActiveQueries = 0; +} + +/* +=================== +ServerResponse + +=================== +*/ +void CHudServers::ServerResponse( struct net_response_s *response ) +{ + char *szresponse; + request_t *p; + server_t *browser; + int len; + char sz[ 32 ]; + + // Remove from active list + p = FindRequest( response->context, m_pActiveList ); + if ( p ) + { + static int first = 0; + + RemoveServerFromList( &m_pActiveList, p ); + m_nActiveQueries--; + + if ( !first ) + { + gEngfuncs.Con_Printf( "recv first %f\n", gEngfuncs.GetClientTime() ); + first = 1; + } + } + + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_DETAILS: + if ( response->response ) + { + szresponse = (char *)response->response; + len = strlen( szresponse ) + 100 + 1; + sprintf( sz, "%i", (int)( 1000.0 * response->ping ) ); + + browser = new server_t; + browser->remote_address = response->remote_address; + browser->info = new char[ len ]; + browser->ping = (int)( 1000.0 * response->ping ); + strcpy( browser->info, szresponse ); + + NET_API->SetValueForKey( browser->info, "address", gEngfuncs.pNetAPI->AdrToString( &response->remote_address ), len ); + NET_API->SetValueForKey( browser->info, "ping", sz, len ); + + AddServer( &m_pServers, browser ); + } + break; + default: + break; + } +} + +/* +=================== +PingResponse + +=================== +*/ +void CHudServers::PingResponse( struct net_response_s *response ) +{ + char sz[ 32 ]; + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_PING: + sprintf( sz, "%.2f", 1000.0 * response->ping ); + + gEngfuncs.Con_Printf( "ping == %s\n", sz ); + break; + default: + break; + } +} + +/* +=================== +RulesResponse + +=================== +*/ +void CHudServers::RulesResponse( struct net_response_s *response ) +{ + char *szresponse; + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_RULES: + if ( response->response ) + { + szresponse = (char *)response->response; + + gEngfuncs.Con_Printf( "rules %s\n", szresponse ); + } + break; + default: + break; + } +} + +/* +=================== +PlayersResponse + +=================== +*/ +void CHudServers::PlayersResponse( struct net_response_s *response ) +{ + char *szresponse; + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_PLAYERS: + if ( response->response ) + { + szresponse = (char *)response->response; + + gEngfuncs.Con_Printf( "players %s\n", szresponse ); + } + break; + default: + break; + } +} + +/* +=================== +CompareServers + +Return 1 if p1 is "less than" p2, 0 otherwise +=================== +*/ +int CHudServers::CompareServers( server_t *p1, server_t *p2 ) +{ + const char *n1, *n2; + + if ( p1->ping < p2->ping ) + return 1; + + if ( p1->ping == p2->ping ) + { + // Pings equal, sort by second key: hostname + if ( p1->info && p2->info ) + { + n1 = NET_API->ValueForKey( p1->info, "hostname" ); + n2 = NET_API->ValueForKey( p2->info, "hostname" ); + + if ( n1 && n2 ) + { + if ( stricmp( n1, n2 ) < 0 ) + return 1; + } + } + } + + return 0; +} + +/* +=================== +AddServer + +=================== +*/ +void CHudServers::AddServer( server_t **ppList, server_t *p ) +{ +server_t *list; + + if ( !ppList || ! p ) + return; + + m_nServerCount++; + + // What sort key? Ping? + list = *ppList; + + // Head of list? + if ( !list ) + { + p->next = NULL; + *ppList = p; + return; + } + + // Put on head of list + if ( CompareServers( p, list ) ) + { + p->next = *ppList; + *ppList = p; + } + else + { + while ( list->next ) + { + // Insert before list next + if ( CompareServers( p, list->next ) ) + { + p->next = list->next->next; + list->next = p; + return; + } + + list = list->next; + } + + // Just add at end + p->next = NULL; + list->next = p; + } +} + +/* +=================== +Think + +=================== +*/ +void CHudServers::Think( double time ) +{ + m_fElapsed += time; + + if ( !m_nRequesting ) + return; + + if ( !m_nQuerying ) + return; + + QueryThink(); + + if ( ServerListSize() > 0 ) + return; + + m_dStarted = 0.0; + m_nRequesting = 0; + m_nDone = 0; + m_nQuerying = 0; + m_nActiveQueries = 0; +} + +/* +=================== +QueryThink + +=================== +*/ +void CHudServers::QueryThink( void ) +{ + request_t *p; + + if ( !m_nRequesting || m_nDone ) + return; + + if ( !m_nQuerying ) + return; + + if ( m_nActiveQueries > MAX_QUERIES ) + return; + + // Nothing left + if ( !m_pServerList ) + return; + + while ( 1 ) + { + static int first = 0; + p = m_pServerList; + + // No more in list? + if ( !p ) + break; + + // Move to next + m_pServerList = m_pServerList->next; + + // Setup context_id + p->context = context_id; + + // Make sure networking system has started. + // NET_API->InitNetworking(); + + if ( !first ) + { + gEngfuncs.Con_Printf( "send first %f\n", gEngfuncs.GetClientTime() ); + first = 1; + } + + // Start up query on this one + NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, 0, 2.0, &p->remote_address, ::ServerResponse ); + + // Increment active list + m_nActiveQueries++; + + // Add to active list + p->next = m_pActiveList; + m_pActiveList = p; + + // Too many active? + if ( m_nActiveQueries > MAX_QUERIES ) + break; + } +} + +/* +================== +ServerListSize + +# of servers in active query and in pending to be queried lists +================== +*/ +int CHudServers::ServerListSize( void ) +{ + int c = 0; + request_t *p; + + p = m_pServerList; + while ( p ) + { + c++; + p = p->next; + } + + p = m_pActiveList; + while ( p ) + { + c++; + p = p->next; + } + + return c; +} + +/* +=================== +FindRequest + +Look up a request by context id +=================== +*/ +CHudServers::request_t *CHudServers::FindRequest( int context, request_t *pList ) +{ + request_t *p; + p = pList; + while ( p ) + { + if ( context == p->context ) + return p; + + p = p->next; + } + return NULL; +} + +/* +=================== +RemoveServerFromList + +Remote, but don't delete, item from *ppList +=================== +*/ +void CHudServers::RemoveServerFromList( request_t **ppList, request_t *item ) +{ + request_t *p, *n; + request_t *newlist = NULL; + + if ( !ppList ) + return; + + p = *ppList; + while ( p ) + { + n = p->next; + if ( p != item ) + { + p->next = newlist; + newlist = p; + } + p = n; + } + *ppList = newlist; +} + +/* +=================== +ClearRequestList + +=================== +*/ +void CHudServers::ClearRequestList( request_t **ppList ) +{ + request_t *p, *n; + + if ( !ppList ) + return; + + p = *ppList; + while ( p ) + { + n = p->next; + delete p; + p = n; + } + *ppList = NULL; +} + +/* +=================== +ClearServerList + +=================== +*/ +void CHudServers::ClearServerList( server_t **ppList ) +{ + server_t *p, *n; + + if ( !ppList ) + return; + + p = *ppList; + while ( p ) + { + n = p->next; + delete[] p->info; + delete p; + p = n; + } + *ppList = NULL; +} + +int CompareField( CHudServers::server_t *p1, CHudServers::server_t *p2, const char *fieldname, int iSortOrder ) +{ + const char *sz1, *sz2; + float fv1, fv2; + + sz1 = NET_API->ValueForKey( p1->info, fieldname ); + sz2 = NET_API->ValueForKey( p2->info, fieldname ); + + fv1 = atof( sz1 ); + fv2 = atof( sz2 ); + + if ( fv1 && fv2 ) + { + if ( fv1 > fv2 ) + return iSortOrder; + else if ( fv1 < fv2 ) + return -iSortOrder; + else + return 0; + } + + // String compare + return stricmp( sz1, sz2 ); +} + +int ServerListCompareFunc( CHudServers::server_t *p1, CHudServers::server_t *p2, const char *fieldname ) +{ + if (!p1 || !p2) // No meaningful comparison + return 0; + + int iSortOrder = 1; + + int retval = 0; + + retval = CompareField( p1, p2, fieldname, iSortOrder ); + + return retval; +} +#ifndef _WIN32 +#define __cdecl +#endif +static char g_fieldname[ 256 ]; +int __cdecl FnServerCompare(const void *elem1, const void *elem2 ) +{ + CHudServers::server_t *list1, *list2; + + list1 = *(CHudServers::server_t **)elem1; + list2 = *(CHudServers::server_t **)elem2; + + return ServerListCompareFunc( list1, list2, g_fieldname ); +} + +void CHudServers::SortServers( const char *fieldname ) +{ + server_t *p; + // Create a list + if ( !m_pServers ) + return; + + strcpy( g_fieldname, fieldname ); + + int i; + int c = 0; + + p = m_pServers; + while ( p ) + { + c++; + p = p->next; + } + + server_t **pSortArray; + + pSortArray = new server_t *[ c ]; + memset( pSortArray, 0, c * sizeof( server_t * ) ); + + // Now copy the list into the pSortArray: + p = m_pServers; + i = 0; + while ( p ) + { + pSortArray[ i++ ] = p; + p = p->next; + } + + // Now do that actual sorting. + size_t nCount = c; + size_t nSize = sizeof( server_t * ); + + qsort( + pSortArray, + (size_t)nCount, + (size_t)nSize, + FnServerCompare + ); + + // Now rebuild the list. + m_pServers = pSortArray[0]; + for ( i = 0; i < c - 1; i++ ) + { + pSortArray[ i ]->next = pSortArray[ i + 1 ]; + } + pSortArray[ c - 1 ]->next = NULL; + + // Clean Up. + delete[] pSortArray; +} + +/* +=================== +GetServer + +Return particular server +=================== +*/ +CHudServers::server_t *CHudServers::GetServer( int server ) +{ + int c = 0; + server_t *p; + + p = m_pServers; + while ( p ) + { + if ( c == server ) + return p; + + c++; + p = p->next; + } + return NULL; +} + +/* +=================== +GetServerInfo + +Return info ( key/value ) string for particular server +=================== +*/ +char *CHudServers::GetServerInfo( int server ) +{ + server_t *p = GetServer( server ); + if ( p ) + { + return p->info; + } + return NULL; +} + +/* +=================== +CancelRequest + +Kill all pending requests in engine +=================== +*/ +void CHudServers::CancelRequest( void ) +{ + m_nRequesting = 0; + m_nQuerying = 0; + m_nDone = 1; + + NET_API->CancelAllRequests(); +} + +/* +================== +LoadMasterAddresses + +Loads the master server addresses from file and into the passed in array +================== +*/ +int CHudServers::LoadMasterAddresses( int maxservers, int *count, netadr_t *padr ) +{ + int i; + char szMaster[ 256 ]; + char szMasterFile[256]; + char *pbuffer = NULL; + char *pstart = NULL ; + netadr_t adr; + char szAdr[64]; + int nPort; + int nCount = 0; + bool bIgnore; + int nDefaultPort; + + // Assume default master and master file + strcpy( szMaster, VALVE_MASTER_ADDRESS ); // IP:PORT string + strcpy( szMasterFile, MASTER_PARSE_FILE ); + + // See if there is a command line override + i = gEngfuncs.CheckParm( "-comm", &pstart ); + if ( i && pstart ) + { + strcpy (szMasterFile, pstart ); + } + + // Read them in from proper file + pbuffer = (char *)gEngfuncs.COM_LoadFile( szMasterFile, 5, NULL ); // Use malloc + if ( !pbuffer ) + { + goto finish_master; + } + + pstart = pbuffer; + + while ( nCount < maxservers ) + { + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if ( strlen(m_szToken) <= 0) + break; + + bIgnore = true; + + if ( !stricmp( m_szToken, "Master" ) ) + { + nDefaultPort = PORT_MASTER; + bIgnore = FALSE; + } + + // Now parse all addresses between { } + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + if ( strlen(m_szToken) <= 0 ) + break; + + if ( stricmp ( m_szToken, "{" ) ) + break; + + // Parse addresses until we get to "}" + while ( nCount < maxservers ) + { + char base[256]; + + // Now parse all addresses between { } + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if (strlen(m_szToken) <= 0) + break; + + if ( !stricmp ( m_szToken, "}" ) ) + break; + + sprintf( base, "%s", m_szToken ); + + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if (strlen(m_szToken) <= 0) + break; + + if ( stricmp( m_szToken, ":" ) ) + break; + + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if (strlen(m_szToken) <= 0) + break; + + nPort = atoi ( m_szToken ); + if ( !nPort ) + nPort = nDefaultPort; + + sprintf( szAdr, "%s:%i", base, nPort ); + + // Can we resolve it any better + if ( !NET_API->StringToAdr( szAdr, &adr ) ) + bIgnore = true; + + if ( !bIgnore ) + { + padr[ nCount++ ] = adr; + } + } + } + +finish_master: + if ( !nCount ) + { + sprintf( szMaster, VALVE_MASTER_ADDRESS ); // IP:PORT string + + // Convert to netadr_t + if ( NET_API->StringToAdr ( szMaster, &adr ) ) + { + + padr[ nCount++ ] = adr; + } + } + + *count = nCount; + + if ( pbuffer ) + { + gEngfuncs.COM_FreeFile( pbuffer ); + } + + return ( nCount > 0 ) ? 1 : 0; +} + +/* +=================== +RequestList + +Request list of game servers from master +=================== +*/ +void CHudServers::RequestList( void ) +{ + m_nRequesting = 1; + m_nDone = 0; + m_dStarted = m_fElapsed; + + int count = 0; + netadr_t adr; + + if ( !LoadMasterAddresses( 1, &count, &adr ) ) + { + gEngfuncs.Con_DPrintf( "SendRequest: Unable to read master server addresses\n" ); + return; + } + + ClearRequestList( &m_pActiveList ); + ClearRequestList( &m_pServerList ); + ClearServerList( &m_pServers ); + + m_nServerCount = 0; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Kill off left overs if any + NET_API->CancelAllRequests(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_SERVERLIST, 0, 5.0, &adr, ::ListResponse ); +} + +void CHudServers::RequestBroadcastList( int clearpending ) +{ + m_nRequesting = 1; + m_nDone = 0; + m_dStarted = m_fElapsed; + + netadr_t adr; + memset( &adr, 0, sizeof( adr ) ); + + if ( clearpending ) + { + ClearRequestList( &m_pActiveList ); + ClearRequestList( &m_pServerList ); + ClearServerList( &m_pServers ); + + m_nServerCount = 0; + } + + // Make sure to byte swap server if necessary ( using "host" to "net" conversion + adr.port = htons( PORT_SERVER ); + + // Make sure networking system has started. + NET_API->InitNetworking(); + + if ( clearpending ) + { + // Kill off left overs if any + NET_API->CancelAllRequests(); + } + + adr.type = NA_BROADCAST; + + // Request Servers from LAN via IP + NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, FNETAPI_MULTIPLE_RESPONSE, 5.0, &adr, ::ServerResponse ); + + adr.type = NA_BROADCAST_IPX; + + // Request Servers from LAN via IPX ( if supported ) + NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, FNETAPI_MULTIPLE_RESPONSE, 5.0, &adr, ::ServerResponse ); +} + +void CHudServers::ServerPing( int server ) +{ + server_t *p; + + p = GetServer( server ); + if ( !p ) + return; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_PING, 0, 5.0, &p->remote_address, ::PingResponse ); +} + +void CHudServers::ServerRules( int server ) +{ + server_t *p; + + p = GetServer( server ); + if ( !p ) + return; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_RULES, 0, 5.0, &p->remote_address, ::RulesResponse ); +} + +void CHudServers::ServerPlayers( int server ) +{ + server_t *p; + + p = GetServer( server ); + if ( !p ) + return; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_PLAYERS, 0, 5.0, &p->remote_address, ::PlayersResponse ); +} + +int CHudServers::isQuerying() +{ + return m_nRequesting ? 1 : 0; +} + + +/* +=================== +GetServerCount + +Return number of servers in browser list +=================== +*/ +int CHudServers::GetServerCount( void ) +{ + return m_nServerCount; +} + +/* +=================== +CHudServers + +=================== +*/ +CHudServers::CHudServers( void ) +{ + m_nRequesting = 0; + m_dStarted = 0.0; + m_nDone = 0; + m_pServerList = NULL; + m_pServers = NULL; + m_pActiveList = NULL; + m_nQuerying = 0; + m_nActiveQueries = 0; + + m_fElapsed = 0.0; + + + m_pPingRequest = NULL; + m_pRulesRequest = NULL; + m_pPlayersRequest = NULL; +} + +/* +=================== +~CHudServers + +=================== +*/ +CHudServers::~CHudServers( void ) +{ + ClearRequestList( &m_pActiveList ); + ClearRequestList( &m_pServerList ); + ClearServerList( &m_pServers ); + + m_nServerCount = 0; + + if ( m_pPingRequest ) + { + delete m_pPingRequest; + m_pPingRequest = NULL; + + } + + if ( m_pRulesRequest ) + { + delete m_pRulesRequest; + m_pRulesRequest = NULL; + } + + if ( m_pPlayersRequest ) + { + delete m_pPlayersRequest; + m_pPlayersRequest = NULL; + } +} + +/////////////////////////////// +// +// PUBLIC APIs +// +/////////////////////////////// + +/* +=================== +ServersGetCount + +=================== +*/ +int ServersGetCount( void ) +{ + if ( g_pServers ) + { + return g_pServers->GetServerCount(); + } + return 0; +} + +int ServersIsQuerying( void ) +{ + if ( g_pServers ) + { + return g_pServers->isQuerying(); + } + return 0; +} + +/* +=================== +ServersGetInfo + +=================== +*/ +const char *ServersGetInfo( int server ) +{ + if ( g_pServers ) + { + return g_pServers->GetServerInfo( server ); + } + + return NULL; +} + +void SortServers( const char *fieldname ) +{ + if ( g_pServers ) + { + g_pServers->SortServers( fieldname ); + } +} + +/* +=================== +ServersShutdown + +=================== +*/ +void ServersShutdown( void ) +{ + if ( g_pServers ) + { + delete g_pServers; + g_pServers = NULL; + } +} + +/* +=================== +ServersInit + +=================== +*/ +void ServersInit( void ) +{ + // Kill any previous instance + ServersShutdown(); + + g_pServers = new CHudServers(); +} + +/* +=================== +ServersThink + +=================== +*/ +void ServersThink( double time ) +{ + if ( g_pServers ) + { + g_pServers->Think( time ); + } +} + +/* +=================== +ServersCancel + +=================== +*/ +void ServersCancel( void ) +{ + if ( g_pServers ) + { + g_pServers->CancelRequest(); + } +} + +// Requests +/* +=================== +ServersList + +=================== +*/ +void ServersList( void ) +{ + if ( g_pServers ) + { + g_pServers->RequestList(); + } +} + +void BroadcastServersList( int clearpending ) +{ + if ( g_pServers ) + { + g_pServers->RequestBroadcastList( clearpending ); + } +} + +void ServerPing( int server ) +{ + if ( g_pServers ) + { + g_pServers->ServerPing( server ); + } +} + +void ServerRules( int server ) +{ + if ( g_pServers ) + { + g_pServers->ServerRules( server ); + } +} + +void ServerPlayers( int server ) +{ + if ( g_pServers ) + { + g_pServers->ServerPlayers( server ); + } +} diff --git a/ricochet/cl_dll/hud_servers.h b/ricochet/cl_dll/hud_servers.h new file mode 100644 index 0000000..577b555 --- /dev/null +++ b/ricochet/cl_dll/hud_servers.h @@ -0,0 +1,34 @@ +#if !defined( HUD_SERVERSH ) +#define HUD_SERVERSH +#pragma once + +#define NET_CALLBACK /* */ + +// Dispatchers +void NET_CALLBACK ListResponse( struct net_response_s *response ); +void NET_CALLBACK ServerResponse( struct net_response_s *response ); +void NET_CALLBACK PingResponse( struct net_response_s *response ); +void NET_CALLBACK RulesResponse( struct net_response_s *response ); +void NET_CALLBACK PlayersResponse( struct net_response_s *response ); + +void ServersInit( void ); +void ServersShutdown( void ); +void ServersThink( double time ); +void ServersCancel( void ); + +// Get list and get server info from each +void ServersList( void ); + +// Query for IP / IPX LAN servers +void BroadcastServersList( int clearpending ); + +void ServerPing( int server ); +void ServerRules( int server ); +void ServerPlayers( int server ); + +int ServersGetCount( void ); +const char *ServersGetInfo( int server ); +int ServersIsQuerying( void ); +void SortServers( const char *fieldname ); + +#endif // HUD_SERVERSH \ No newline at end of file diff --git a/ricochet/cl_dll/hud_servers_priv.h b/ricochet/cl_dll/hud_servers_priv.h new file mode 100644 index 0000000..84f1e2a --- /dev/null +++ b/ricochet/cl_dll/hud_servers_priv.h @@ -0,0 +1,91 @@ +#if !defined( HUD_SERVERS_PRIVH ) +#define HUD_SERVERS_PRIVH +#pragma once + +#include "netadr.h" + +class CHudServers +{ +public: + typedef struct request_s + { + struct request_s *next; + netadr_t remote_address; + int context; + } request_t; + + typedef struct server_s + { + struct server_s *next; + netadr_t remote_address; + char *info; + int ping; + } server_t; + + CHudServers(); + ~CHudServers(); + + void Think( double time ); + void QueryThink( void ); + int isQuerying( void ); + + int LoadMasterAddresses( int maxservers, int *count, netadr_t *padr ); + + void RequestList( void ); + void RequestBroadcastList( int clearpending ); + + void ServerPing( int server ); + void ServerRules( int server ); + void ServerPlayers( int server ); + + void CancelRequest( void ); + + int CompareServers( server_t *p1, server_t *p2 ); + + void ClearServerList( server_t **ppList ); + void ClearRequestList( request_t **ppList ); + + void AddServer( server_t **ppList, server_t *p ); + + void RemoveServerFromList( request_t **ppList, request_t *item ); + + request_t *FindRequest( int context, request_t *pList ); + + int ServerListSize( void ); + char *GetServerInfo( int server ); + int GetServerCount( void ); + void SortServers( const char *fieldname ); + + void ListResponse( struct net_response_s *response ); + void ServerResponse( struct net_response_s *response ); + void PingResponse( struct net_response_s *response ); + void RulesResponse( struct net_response_s *response ); + void PlayersResponse( struct net_response_s *response ); +private: + + server_t *GetServer( int server ); + + // + char m_szToken[ 1024 ]; + int m_nRequesting; + int m_nDone; + + double m_dStarted; + + request_t *m_pServerList; + request_t *m_pActiveList; + + server_t *m_pServers; + + int m_nServerCount; + + int m_nActiveQueries; + int m_nQuerying; + double m_fElapsed; + + request_t *m_pPingRequest; + request_t *m_pRulesRequest; + request_t *m_pPlayersRequest; +}; + +#endif // HUD_SERVERS_PRIVH \ No newline at end of file diff --git a/ricochet/cl_dll/hud_update.cpp b/ricochet/cl_dll/hud_update.cpp new file mode 100644 index 0000000..3a69a00 --- /dev/null +++ b/ricochet/cl_dll/hud_update.cpp @@ -0,0 +1,54 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud_update.cpp +// + +#include +#include "hud.h" +#include "cl_util.h" +#include +#include + +int CL_ButtonBits( int ); +void CL_ResetButtonBits( int bits ); + +extern float v_idlescale; +float in_fov; +extern void HUD_SetCmdBits( int bits ); + +int CHud::UpdateClientData(client_data_t *cdata, float time) +{ + memcpy(m_vecOrigin, cdata->origin, sizeof(vec3_t)); + memcpy(m_vecAngles, cdata->viewangles, sizeof(vec3_t)); + + m_iKeyBits = CL_ButtonBits( 0 ); + m_iWeaponBits = cdata->iWeaponBits; + + in_fov = cdata->fov; + + Think(); + + cdata->fov = m_iFOV; + + v_idlescale = m_iConcussionEffect; + + CL_ResetButtonBits( m_iKeyBits ); + + // return 1 if in anything in the client_data struct has been changed, 0 otherwise + return 1; +} + + diff --git a/ricochet/cl_dll/in_camera.cpp b/ricochet/cl_dll/in_camera.cpp new file mode 100644 index 0000000..0d4f854 --- /dev/null +++ b/ricochet/cl_dll/in_camera.cpp @@ -0,0 +1,630 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" + +#include "SDL2/SDL_mouse.h" +#include "port.h" + +float CL_KeyState (kbutton_t *key); + +extern "C" +{ + void EXPORT CAM_Think( void ); + int EXPORT CL_IsThirdPerson( void ); + void EXPORT CL_CameraOffset( float *ofs ); +} + +extern cl_enginefunc_t gEngfuncs; + +//-------------------------------------------------- Constants + +#define CAM_DIST_DELTA 1.0 +#define CAM_ANGLE_DELTA 2.5 +#define CAM_ANGLE_SPEED 2.5 +#define CAM_MIN_DIST 30.0 +#define CAM_ANGLE_MOVE .5 +#define MAX_ANGLE_DIFF 10.0 +#define PITCH_MAX 90.0 +#define PITCH_MIN 0 +#define YAW_MAX 135.0 +#define YAW_MIN -135.0 + +enum ECAM_Command +{ + CAM_COMMAND_NONE = 0, + CAM_COMMAND_TOTHIRDPERSON = 1, + CAM_COMMAND_TOFIRSTPERSON = 2 +}; + +//-------------------------------------------------- Global Variables + +cvar_t *cam_command; +cvar_t *cam_snapto; +cvar_t *cam_idealyaw; +cvar_t *cam_idealpitch; +cvar_t *cam_idealdist; +cvar_t *cam_contain; + +cvar_t *c_maxpitch; +cvar_t *c_minpitch; +cvar_t *c_maxyaw; +cvar_t *c_minyaw; +cvar_t *c_maxdistance; +cvar_t *c_mindistance; + +// pitch, yaw, dist +vec3_t cam_ofs; + + +// In third person +int cam_thirdperson; +int cam_mousemove; //true if we are moving the cam with the mouse, False if not +int iMouseInUse=0; +int cam_distancemove; +extern int mouse_x, mouse_y; //used to determine what the current x and y values are +int cam_old_mouse_x, cam_old_mouse_y; //holds the last ticks mouse movement +POINT cam_mouse; +//-------------------------------------------------- Local Variables + +static kbutton_t cam_pitchup, cam_pitchdown, cam_yawleft, cam_yawright; +static kbutton_t cam_in, cam_out, cam_move; + +//-------------------------------------------------- Prototypes + +void CAM_ToThirdPerson(void); +void CAM_ToFirstPerson(void); +void CAM_StartDistance(void); +void CAM_EndDistance(void); + +void SDL_GetCursorPos( POINT *p ) +{ + SDL_GetMouseState( (int *)&p->x, (int *)&p->y ); +} + +void SDL_SetCursorPos( const int x, const int y ) +{ +} + + +//-------------------------------------------------- Local Functions + +float MoveToward( float cur, float goal, float maxspeed ) +{ + if( cur != goal ) + { + if( abs( cur - goal ) > 180.0 ) + { + if( cur < goal ) + cur += 360.0; + else + cur -= 360.0; + } + + if( cur < goal ) + { + if( cur < goal - 1.0 ) + cur += ( goal - cur ) / 4.0; + else + cur = goal; + } + else + { + if( cur > goal + 1.0 ) + cur -= ( cur - goal ) / 4.0; + else + cur = goal; + } + } + + + // bring cur back into range + if( cur < 0 ) + cur += 360.0; + else if( cur >= 360 ) + cur -= 360; + + return cur; +} + + +//-------------------------------------------------- Gobal Functions + +typedef struct +{ + vec3_t boxmins, boxmaxs;// enclose the test object along entire move + float *mins, *maxs; // size of the moving object + vec3_t mins2, maxs2; // size when clipping against mosnters + float *start, *end; + trace_t trace; + int type; + edict_t *passedict; + qboolean monsterclip; +} moveclip_t; + +extern trace_t SV_ClipMoveToEntity (edict_t *ent, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end); + +void EXPORT CAM_Think( void ) +{ + vec3_t origin; + vec3_t ext, pnt, camForward, camRight, camUp; + moveclip_t clip; + float dist; + vec3_t camAngles; + float flSensitivity; +#ifdef LATER + int i; +#endif + vec3_t viewangles; + + switch( (int) cam_command->value ) + { + case CAM_COMMAND_TOTHIRDPERSON: + CAM_ToThirdPerson(); + break; + + case CAM_COMMAND_TOFIRSTPERSON: + CAM_ToFirstPerson(); + break; + + case CAM_COMMAND_NONE: + default: + break; + } + + if( !cam_thirdperson ) + return; + +#ifdef LATER + if ( cam_contain->value ) + { + gEngfuncs.GetClientOrigin( origin ); + ext[0] = ext[1] = ext[2] = 0.0; + } +#endif + + camAngles[ PITCH ] = cam_idealpitch->value; + camAngles[ YAW ] = cam_idealyaw->value; + dist = cam_idealdist->value; + // + //movement of the camera with the mouse + // + if (cam_mousemove) + { + //get windows cursor position + SDL_GetCursorPos (&cam_mouse); + //check for X delta values and adjust accordingly + //eventually adjust YAW based on amount of movement + //don't do any movement of the cam using YAW/PITCH if we are zooming in/out the camera + if (!cam_distancemove) + { + + //keep the camera within certain limits around the player (ie avoid certain bad viewing angles) + if (cam_mouse.x>gEngfuncs.GetWindowCenterX()) + { + //if ((camAngles[YAW]>=225.0)||(camAngles[YAW]<135.0)) + if (camAngles[YAW]value) + { + camAngles[ YAW ] += (CAM_ANGLE_MOVE)*((cam_mouse.x-gEngfuncs.GetWindowCenterX())/2); + } + if (camAngles[YAW]>c_maxyaw->value) + { + + camAngles[YAW]=c_maxyaw->value; + } + } + else if (cam_mouse.x225.0)) + if (camAngles[YAW]>c_minyaw->value) + { + camAngles[ YAW ] -= (CAM_ANGLE_MOVE)* ((gEngfuncs.GetWindowCenterX()-cam_mouse.x)/2); + + } + if (camAngles[YAW]value) + { + camAngles[YAW]=c_minyaw->value; + + } + } + + //check for y delta values and adjust accordingly + //eventually adjust PITCH based on amount of movement + //also make sure camera is within bounds + if (cam_mouse.y>gEngfuncs.GetWindowCenterY()) + { + if(camAngles[PITCH]value) + { + camAngles[PITCH] +=(CAM_ANGLE_MOVE)* ((cam_mouse.y-gEngfuncs.GetWindowCenterY())/2); + } + if (camAngles[PITCH]>c_maxpitch->value) + { + camAngles[PITCH]=c_maxpitch->value; + } + } + else if (cam_mouse.yc_minpitch->value) + { + camAngles[PITCH] -= (CAM_ANGLE_MOVE)*((gEngfuncs.GetWindowCenterY()-cam_mouse.y)/2); + } + if (camAngles[PITCH]value) + { + camAngles[PITCH]=c_minpitch->value; + } + } + + //set old mouse coordinates to current mouse coordinates + //since we are done with the mouse + + if ( ( flSensitivity = gHUD.GetSensitivity() ) != 0 ) + { + cam_old_mouse_x=cam_mouse.x*flSensitivity; + cam_old_mouse_y=cam_mouse.y*flSensitivity; + } + else + { + cam_old_mouse_x=cam_mouse.x; + cam_old_mouse_y=cam_mouse.y; + } + SDL_SetCursorPos (gEngfuncs.GetWindowCenterX(), gEngfuncs.GetWindowCenterY()); + } + } + + //Nathan code here + if( CL_KeyState( &cam_pitchup ) ) + camAngles[ PITCH ] += CAM_ANGLE_DELTA; + else if( CL_KeyState( &cam_pitchdown ) ) + camAngles[ PITCH ] -= CAM_ANGLE_DELTA; + + if( CL_KeyState( &cam_yawleft ) ) + camAngles[ YAW ] -= CAM_ANGLE_DELTA; + else if( CL_KeyState( &cam_yawright ) ) + camAngles[ YAW ] += CAM_ANGLE_DELTA; + + if( CL_KeyState( &cam_in ) ) + { + dist -= CAM_DIST_DELTA; + if( dist < CAM_MIN_DIST ) + { + // If we go back into first person, reset the angle + camAngles[ PITCH ] = 0; + camAngles[ YAW ] = 0; + dist = CAM_MIN_DIST; + } + + } + else if( CL_KeyState( &cam_out ) ) + dist += CAM_DIST_DELTA; + + if (cam_distancemove) + { + if (cam_mouse.y>gEngfuncs.GetWindowCenterY()) + { + if(distvalue) + { + dist +=CAM_DIST_DELTA * ((cam_mouse.y-gEngfuncs.GetWindowCenterY())/2); + } + if (dist>c_maxdistance->value) + { + dist=c_maxdistance->value; + } + } + else if (cam_mouse.yc_mindistance->value) + { + dist -= (CAM_DIST_DELTA)*((gEngfuncs.GetWindowCenterY()-cam_mouse.y)/2); + } + if (distvalue) + { + dist=c_mindistance->value; + } + } + //set old mouse coordinates to current mouse coordinates + //since we are done with the mouse + cam_old_mouse_x=cam_mouse.x*gHUD.GetSensitivity(); + cam_old_mouse_y=cam_mouse.y*gHUD.GetSensitivity(); + SDL_SetCursorPos (gEngfuncs.GetWindowCenterX(), gEngfuncs.GetWindowCenterY()); + } +#ifdef LATER + if( cam_contain->value ) + { + // check new ideal + VectorCopy( origin, pnt ); + AngleVectors( camAngles, camForward, camRight, camUp ); + for (i=0 ; i<3 ; i++) + pnt[i] += -dist*camForward[i]; + + // check line from r_refdef.vieworg to pnt + memset ( &clip, 0, sizeof ( moveclip_t ) ); + clip.trace = SV_ClipMoveToEntity( sv.edicts, r_refdef.vieworg, ext, ext, pnt ); + if( clip.trace.fraction == 1.0 ) + { + // update ideal + cam_idealpitch->value = camAngles[ PITCH ]; + cam_idealyaw->value = camAngles[ YAW ]; + cam_idealdist->value = dist; + } + } + else +#endif + { + // update ideal + cam_idealpitch->value = camAngles[ PITCH ]; + cam_idealyaw->value = camAngles[ YAW ]; + cam_idealdist->value = dist; + } + + // Move towards ideal + VectorCopy( cam_ofs, camAngles ); + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + if( cam_snapto->value ) + { + camAngles[ YAW ] = cam_idealyaw->value + viewangles[ YAW ]; + camAngles[ PITCH ] = cam_idealpitch->value + viewangles[ PITCH ]; + camAngles[ 2 ] = cam_idealdist->value; + } + else + { + if( camAngles[ YAW ] - viewangles[ YAW ] != cam_idealyaw->value ) + camAngles[ YAW ] = MoveToward( camAngles[ YAW ], cam_idealyaw->value + viewangles[ YAW ], CAM_ANGLE_SPEED ); + + if( camAngles[ PITCH ] - viewangles[ PITCH ] != cam_idealpitch->value ) + camAngles[ PITCH ] = MoveToward( camAngles[ PITCH ], cam_idealpitch->value + viewangles[ PITCH ], CAM_ANGLE_SPEED ); + + if( abs( camAngles[ 2 ] - cam_idealdist->value ) < 2.0 ) + camAngles[ 2 ] = cam_idealdist->value; + else + camAngles[ 2 ] += ( cam_idealdist->value - camAngles[ 2 ] ) / 4.0; + } +#ifdef LATER + if( cam_contain->value ) + { + // Test new position + dist = camAngles[ ROLL ]; + camAngles[ ROLL ] = 0; + + VectorCopy( origin, pnt ); + AngleVectors( camAngles, camForward, camRight, camUp ); + for (i=0 ; i<3 ; i++) + pnt[i] += -dist*camForward[i]; + + // check line from r_refdef.vieworg to pnt + memset ( &clip, 0, sizeof ( moveclip_t ) ); + ext[0] = ext[1] = ext[2] = 0.0; + clip.trace = SV_ClipMoveToEntity( sv.edicts, r_refdef.vieworg, ext, ext, pnt ); + if( clip.trace.fraction != 1.0 ) + return; + } +#endif + cam_ofs[ 0 ] = camAngles[ 0 ]; + cam_ofs[ 1 ] = camAngles[ 1 ]; + cam_ofs[ 2 ] = dist; +} + +extern void KeyDown (kbutton_t *b); // HACK +extern void KeyUp (kbutton_t *b); // HACK + +void CAM_PitchUpDown(void) { KeyDown( &cam_pitchup ); } +void CAM_PitchUpUp(void) { KeyUp( &cam_pitchup ); } +void CAM_PitchDownDown(void) { KeyDown( &cam_pitchdown ); } +void CAM_PitchDownUp(void) { KeyUp( &cam_pitchdown ); } +void CAM_YawLeftDown(void) { KeyDown( &cam_yawleft ); } +void CAM_YawLeftUp(void) { KeyUp( &cam_yawleft ); } +void CAM_YawRightDown(void) { KeyDown( &cam_yawright ); } +void CAM_YawRightUp(void) { KeyUp( &cam_yawright ); } +void CAM_InDown(void) { KeyDown( &cam_in ); } +void CAM_InUp(void) { KeyUp( &cam_in ); } +void CAM_OutDown(void) { KeyDown( &cam_out ); } +void CAM_OutUp(void) { KeyUp( &cam_out ); } + +void CAM_ToThirdPerson(void) +{ + vec3_t viewangles; + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + if( !cam_thirdperson ) + { + cam_thirdperson = 1; + + cam_ofs[ YAW ] = viewangles[ YAW ]; + cam_ofs[ PITCH ] = viewangles[ PITCH ]; + cam_ofs[ 2 ] = CAM_MIN_DIST; + } + + gEngfuncs.Cvar_SetValue( "cam_command", 0 ); +} + +void CAM_ToFirstPerson(void) +{ + cam_thirdperson = 0; + + gEngfuncs.Cvar_SetValue( "cam_command", 0 ); +} + +void CAM_ToggleSnapto( void ) +{ + cam_snapto->value = !cam_snapto->value; +} + +void CAM_Init( void ) +{ + gEngfuncs.pfnAddCommand( "+campitchup", CAM_PitchUpDown ); + gEngfuncs.pfnAddCommand( "-campitchup", CAM_PitchUpUp ); + gEngfuncs.pfnAddCommand( "+campitchdown", CAM_PitchDownDown ); + gEngfuncs.pfnAddCommand( "-campitchdown", CAM_PitchDownUp ); + gEngfuncs.pfnAddCommand( "+camyawleft", CAM_YawLeftDown ); + gEngfuncs.pfnAddCommand( "-camyawleft", CAM_YawLeftUp ); + gEngfuncs.pfnAddCommand( "+camyawright", CAM_YawRightDown ); + gEngfuncs.pfnAddCommand( "-camyawright", CAM_YawRightUp ); + gEngfuncs.pfnAddCommand( "+camin", CAM_InDown ); + gEngfuncs.pfnAddCommand( "-camin", CAM_InUp ); + gEngfuncs.pfnAddCommand( "+camout", CAM_OutDown ); + gEngfuncs.pfnAddCommand( "-camout", CAM_OutUp ); + gEngfuncs.pfnAddCommand( "thirdperson", CAM_ToThirdPerson ); + gEngfuncs.pfnAddCommand( "firstperson", CAM_ToFirstPerson ); + gEngfuncs.pfnAddCommand( "+cammousemove",CAM_StartMouseMove); + gEngfuncs.pfnAddCommand( "-cammousemove",CAM_EndMouseMove); + gEngfuncs.pfnAddCommand( "+camdistance", CAM_StartDistance ); + gEngfuncs.pfnAddCommand( "-camdistance", CAM_EndDistance ); + gEngfuncs.pfnAddCommand( "snapto", CAM_ToggleSnapto ); + + cam_command = gEngfuncs.pfnRegisterVariable ( "cam_command", "0", 0 ); // tells camera to go to thirdperson + cam_snapto = gEngfuncs.pfnRegisterVariable ( "cam_snapto", "0", 0 ); // snap to thirdperson view + cam_idealyaw = gEngfuncs.pfnRegisterVariable ( "cam_idealyaw", "0", 0 ); // ? yaw + cam_idealpitch = gEngfuncs.pfnRegisterVariable ( "cam_idealpitch", "0", 0 ); // thirperson pitch + cam_idealdist = gEngfuncs.pfnRegisterVariable ( "cam_idealdist", "196", 0 ); // thirdperson distance + cam_contain = gEngfuncs.pfnRegisterVariable ( "cam_contain", "0", 0 ); // contain camera to world + + c_maxpitch = gEngfuncs.pfnRegisterVariable ( "c_maxpitch", "90.0", 0 ); + c_minpitch = gEngfuncs.pfnRegisterVariable ( "c_minpitch", "0.0", 0 ); + c_maxyaw = gEngfuncs.pfnRegisterVariable ( "c_maxyaw", "135.0", 0 ); + c_minyaw = gEngfuncs.pfnRegisterVariable ( "c_minyaw", "-135.0", 0 ); + c_maxdistance = gEngfuncs.pfnRegisterVariable ( "c_maxdistance", "200.0", 0 ); + c_mindistance = gEngfuncs.pfnRegisterVariable ( "c_mindistance", "30.0", 0 ); +} + +void CAM_ClearStates( void ) +{ + vec3_t viewangles; + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + cam_pitchup.state = 0; + cam_pitchdown.state = 0; + cam_yawleft.state = 0; + cam_yawright.state = 0; + cam_in.state = 0; + cam_out.state = 0; + + cam_thirdperson = 0; + cam_command->value = 0; + cam_mousemove=0; + + cam_snapto->value = 0; + cam_distancemove = 0; + + cam_ofs[ 0 ] = 0.0; + cam_ofs[ 1 ] = 0.0; + cam_ofs[ 2 ] = CAM_MIN_DIST; + + cam_idealpitch->value = viewangles[ PITCH ]; + cam_idealyaw->value = viewangles[ YAW ]; + cam_idealdist->value = CAM_MIN_DIST; +} + +void CAM_StartMouseMove(void) +{ + float flSensitivity; + + //only move the cam with mouse if we are in third person. + if (cam_thirdperson) + { + //set appropriate flags and initialize the old mouse position + //variables for mouse camera movement + if (!cam_mousemove) + { + cam_mousemove=1; + iMouseInUse=1; + SDL_GetCursorPos (&cam_mouse); + + if ( ( flSensitivity = gHUD.GetSensitivity() ) != 0 ) + { + cam_old_mouse_x=cam_mouse.x*flSensitivity; + cam_old_mouse_y=cam_mouse.y*flSensitivity; + } + else + { + cam_old_mouse_x=cam_mouse.x; + cam_old_mouse_y=cam_mouse.y; + } + } + } + //we are not in 3rd person view..therefore do not allow camera movement + else + { + cam_mousemove=0; + iMouseInUse=0; + } +} + +//the key has been released for camera movement +//tell the engine that mouse camera movement is off +void CAM_EndMouseMove(void) +{ + cam_mousemove=0; + iMouseInUse=0; +} + + +//---------------------------------------------------------- +//routines to start the process of moving the cam in or out +//using the mouse +//---------------------------------------------------------- +void CAM_StartDistance(void) +{ + //only move the cam with mouse if we are in third person. + if (cam_thirdperson) + { + //set appropriate flags and initialize the old mouse position + //variables for mouse camera movement + if (!cam_distancemove) + { + cam_distancemove=1; + cam_mousemove=1; + iMouseInUse=1; + SDL_GetCursorPos (&cam_mouse); + cam_old_mouse_x=cam_mouse.x*gHUD.GetSensitivity(); + cam_old_mouse_y=cam_mouse.y*gHUD.GetSensitivity(); + } + } + //we are not in 3rd person view..therefore do not allow camera movement + else + { + cam_distancemove=0; + cam_mousemove=0; + iMouseInUse=0; + } +} + +//the key has been released for camera movement +//tell the engine that mouse camera movement is off +void CAM_EndDistance(void) +{ + cam_distancemove=0; + cam_mousemove=0; + iMouseInUse=0; +} + +int EXPORT CL_IsThirdPerson( void ) +{ + return cam_thirdperson ? 1 : 0; +} + +void EXPORT CL_CameraOffset( float *ofs ) +{ + VectorCopy( cam_ofs, ofs ); +} diff --git a/ricochet/cl_dll/in_defs.h b/ricochet/cl_dll/in_defs.h new file mode 100644 index 0000000..18ec79e --- /dev/null +++ b/ricochet/cl_dll/in_defs.h @@ -0,0 +1,27 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( IN_DEFSH ) +#define IN_DEFSH +#pragma once + +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 + + +#endif \ No newline at end of file diff --git a/ricochet/cl_dll/input.cpp b/ricochet/cl_dll/input.cpp new file mode 100644 index 0000000..f3e3de5 --- /dev/null +++ b/ricochet/cl_dll/input.cpp @@ -0,0 +1,988 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// input.cpp -- builds an intended movement command to send to the server +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +extern "C" +{ +#include "kbutton.h" +} +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "view.h" +#include +#include + +#include "vgui_TeamFortressViewport.h" +#include "vgui_discobjects.h" + +// Observer Movement modes (stored in pev->iuser1, so the physics code can get at them) +#define OBS_CHASE_LOCKED 1 +#define OBS_CHASE_FREE 2 +#define OBS_ROAMING 3 +#define OBS_LOCKEDVIEW 4 + +extern "C" +{ + struct kbutton_s EXPORT *KB_Find( const char *name ); + void EXPORT CL_CreateMove ( float frametime, struct usercmd_s *cmd, int active ); + void EXPORT HUD_Shutdown( void ); + int EXPORT HUD_Key_Event( int eventcode, int keynum, const char *pszCurrentBinding ); +} + +extern int g_weaponselect; +extern cl_enginefunc_t gEngfuncs; + +// Defined in pm_math.c +extern "C" float anglemod( float a ); + +void IN_Init (void); +void IN_Move ( float frametime, usercmd_t *cmd); +void IN_Shutdown( void ); +void V_Init( void ); +int CL_ButtonBits( int ); + +// xxx need client dll function to get and clear impuse +extern cvar_t *in_joystick; + +int in_impulse = 0; +int in_cancel = 0; + +cvar_t *m_pitch; +cvar_t *m_yaw; +cvar_t *m_forward; +cvar_t *m_side; + +cvar_t *lookstrafe; +cvar_t *lookspring; +cvar_t *cl_pitchup; +cvar_t *cl_pitchdown; +cvar_t *cl_upspeed; +cvar_t *cl_forwardspeed; +cvar_t *cl_backspeed; +cvar_t *cl_sidespeed; +cvar_t *cl_movespeedkey; +cvar_t *cl_yawspeed; +cvar_t *cl_pitchspeed; +cvar_t *cl_anglespeedkey; +cvar_t *cl_vsmoothing; +/* +=============================================================================== + +KEY BUTTONS + +Continuous button event tracking is complicated by the fact that two different +input sources (say, mouse button 1 and the control key) can both press the +same button, but the button should only be released when both of the +pressing key have been released. + +When a key event issues a button command (+forward, +attack, etc), it appends +its key number as a parameter to the command so it can be matched up with +the release. + +state bit 0 is the current state of the key +state bit 1 is edge triggered on the up to down transition +state bit 2 is edge triggered on the down to up transition + +=============================================================================== +*/ + + +kbutton_t in_mlook; +kbutton_t in_klook; +kbutton_t in_jlook; +kbutton_t in_left; +kbutton_t in_right; +kbutton_t in_forward; +kbutton_t in_back; +kbutton_t in_lookup; +kbutton_t in_lookdown; +kbutton_t in_moveleft; +kbutton_t in_moveright; +kbutton_t in_strafe; +kbutton_t in_speed; +kbutton_t in_use; +kbutton_t in_jump; +kbutton_t in_attack; +kbutton_t in_attack2; +kbutton_t in_up; +kbutton_t in_down; +kbutton_t in_duck; +kbutton_t in_reload; +kbutton_t in_alt1; +kbutton_t in_score; +kbutton_t in_break; +kbutton_t in_graph; // Display the netgraph + +typedef struct kblist_s +{ + struct kblist_s *next; + kbutton_t *pkey; + char name[32]; +} kblist_t; + +kblist_t *g_kbkeys = NULL; + +/* +============ +KB_ConvertString + +Removes references to +use and replaces them with the keyname in the output string. If + a binding is unfound, then the original text is retained. +NOTE: Only works for text with +word in it. +============ +*/ +int KB_ConvertString( char *in, char **ppout ) +{ + char sz[ 4096 ]; + char binding[ 64 ]; + char *p; + char *pOut; + char *pEnd; + const char *pBinding; + + if ( !ppout ) + return 0; + + *ppout = NULL; + p = in; + pOut = sz; + while ( *p ) + { + if ( *p == '+' ) + { + pEnd = binding; + while ( *p && ( isalnum( *p ) || ( pEnd == binding ) ) && ( ( pEnd - binding ) < 63 ) ) + { + *pEnd++ = *p++; + } + + *pEnd = '\0'; + + pBinding = NULL; + if ( strlen( binding + 1 ) > 0 ) + { + // See if there is a binding for binding? + pBinding = gEngfuncs.Key_LookupBinding( binding + 1 ); + } + + if ( pBinding ) + { + *pOut++ = '['; + pEnd = (char *)pBinding; + } + else + { + pEnd = binding; + } + + while ( *pEnd ) + { + *pOut++ = *pEnd++; + } + + if ( pBinding ) + { + *pOut++ = ']'; + } + } + else + { + *pOut++ = *p++; + } + } + + *pOut = '\0'; + + pOut = ( char * )malloc( strlen( sz ) + 1 ); + strcpy( pOut, sz ); + *ppout = pOut; + + return 1; +} + +/* +============ +KB_Find + +Allows the engine to get a kbutton_t directly ( so it can check +mlook state, etc ) for saving out to .cfg files +============ +*/ +struct kbutton_s EXPORT *KB_Find( const char *name ) +{ + kblist_t *p; + p = g_kbkeys; + while ( p ) + { + if ( !stricmp( name, p->name ) ) + return p->pkey; + + p = p->next; + } + return NULL; +} + +/* +============ +KB_Add + +Add a kbutton_t * to the list of pointers the engine can retrieve via KB_Find +============ +*/ +void KB_Add( const char *name, kbutton_t *pkb ) +{ + kblist_t *p; + kbutton_t *kb; + + kb = KB_Find( name ); + + if ( kb ) + return; + + p = ( kblist_t * )malloc( sizeof( kblist_t ) ); + memset( p, 0, sizeof( *p ) ); + + strcpy( p->name, name ); + p->pkey = pkb; + + p->next = g_kbkeys; + g_kbkeys = p; +} + +/* +============ +KB_Init + +Add kbutton_t definitions that the engine can query if needed +============ +*/ +void KB_Init( void ) +{ + g_kbkeys = NULL; + + KB_Add( "in_graph", &in_graph ); + KB_Add( "in_mlook", &in_mlook ); + KB_Add( "in_jlook", &in_jlook ); +} + +/* +============ +KB_Shutdown + +Clear kblist +============ +*/ +void KB_Shutdown( void ) +{ + kblist_t *p, *n; + p = g_kbkeys; + while ( p ) + { + n = p->next; + free( p ); + p = n; + } + g_kbkeys = NULL; +} + +/* +============ +KeyDown +============ +*/ +void KeyDown (kbutton_t *b) +{ + int k; + char *c; + + c = gEngfuncs.Cmd_Argv(1); + if (c[0]) + k = atoi(c); + else + k = -1; // typed manually at the console for continuous down + + if (k == b->down[0] || k == b->down[1]) + return; // repeating key + + if (!b->down[0]) + b->down[0] = k; + else if (!b->down[1]) + b->down[1] = k; + else + { + gEngfuncs.Con_DPrintf ("Three keys down for a button '%c' '%c' '%c'!\n", b->down[0], b->down[1], c); + return; + } + + if (b->state & 1) + return; // still down + b->state |= 1 + 2; // down + impulse down +} + +/* +============ +KeyUp +============ +*/ +void KeyUp (kbutton_t *b) +{ + int k; + char *c; + + c = gEngfuncs.Cmd_Argv(1); + if (c[0]) + k = atoi(c); + else + { // typed manually at the console, assume for unsticking, so clear all + b->down[0] = b->down[1] = 0; + b->state = 4; // impulse up + return; + } + + if (b->down[0] == k) + b->down[0] = 0; + else if (b->down[1] == k) + b->down[1] = 0; + else + return; // key up without coresponding down (menu pass through) + if (b->down[0] || b->down[1]) + { + return; // some other key is still holding it down + } + + if (!(b->state & 1)) + return; // still up (this should not happen) + + b->state &= ~1; // now up + b->state |= 4; // impulse up +} +/* +============ +HUD_Key_Event + +Return 1 to allow engine to process the key, otherwise, act on it as needed +============ +*/ +int EXPORT HUD_Key_Event( int down, int keynum, const char *pszCurrentBinding ) +{ + if (gViewPort) + return gViewPort->KeyInput(down, keynum, pszCurrentBinding); + + return 1; +} + +void IN_BreakDown( void ) { KeyDown( &in_break );}; +void IN_BreakUp( void ) { KeyUp( &in_break ); }; +void IN_KLookDown (void) {KeyDown(&in_klook);} +void IN_KLookUp (void) {KeyUp(&in_klook);} +void IN_JLookDown (void) {KeyDown(&in_jlook);} +void IN_JLookUp (void) {KeyUp(&in_jlook);} +void IN_MLookDown (void) {KeyDown(&in_mlook);} +void IN_UpDown(void) {KeyDown(&in_up);} +void IN_UpUp(void) {KeyUp(&in_up);} +void IN_DownDown(void) {KeyDown(&in_down);} +void IN_DownUp(void) {KeyUp(&in_down);} +void IN_LeftDown(void) {KeyDown(&in_left);} +void IN_LeftUp(void) {KeyUp(&in_left);} +void IN_RightDown(void) {KeyDown(&in_right);} +void IN_RightUp(void) {KeyUp(&in_right);} +void IN_ForwardDown(void) {KeyDown(&in_forward);} +void IN_ForwardUp(void) {KeyUp(&in_forward);} +void IN_BackDown(void) {KeyDown(&in_back);} +void IN_BackUp(void) {KeyUp(&in_back);} +void IN_LookupDown(void) {KeyDown(&in_lookup);} +void IN_LookupUp(void) {KeyUp(&in_lookup);} +void IN_LookdownDown(void) {KeyDown(&in_lookdown);} +void IN_LookdownUp(void) {KeyUp(&in_lookdown);} +void IN_MoveleftDown(void) {KeyDown(&in_moveleft);} +void IN_MoveleftUp(void) {KeyUp(&in_moveleft);} +void IN_MoverightDown(void) {KeyDown(&in_moveright);} +void IN_MoverightUp(void) {KeyUp(&in_moveright);} +void IN_SpeedDown(void) {KeyDown(&in_speed);} +void IN_SpeedUp(void) {KeyUp(&in_speed);} +void IN_StrafeDown(void) {KeyDown(&in_strafe);} +void IN_StrafeUp(void) {KeyUp(&in_strafe);} +void IN_Attack2Down(void) {KeyDown(&in_attack2);} +void IN_Attack2Up(void) {KeyUp(&in_attack2);} +void IN_UseDown (void) {KeyDown(&in_use);} +void IN_UseUp (void) {KeyUp(&in_use);} +void IN_JumpDown (void) {KeyDown(&in_jump);} +void IN_JumpUp (void) {KeyUp(&in_jump);} +void IN_DuckDown(void) {KeyDown(&in_duck);} +void IN_DuckUp(void) {KeyUp(&in_duck);} +void IN_ReloadDown(void) {KeyDown(&in_reload);} +void IN_ReloadUp(void) {KeyUp(&in_reload);} +void IN_Alt1Down(void) {KeyDown(&in_alt1);} +void IN_Alt1Up(void) {KeyUp(&in_alt1);} +void IN_GraphDown(void) {KeyDown(&in_graph);} +void IN_GraphUp(void) {KeyUp(&in_graph);} + +void IN_AttackDown(void) +{ + KeyDown( &in_attack ); +} + +void IN_AttackUp(void) +{ + KeyUp( &in_attack ); + in_cancel = 0; +} + +// Special handling +void IN_Cancel(void) +{ + in_cancel = 1; +} + +void IN_Impulse (void) +{ + in_impulse = atoi( gEngfuncs.Cmd_Argv(1) ); +} + +void IN_ScoreDown(void) +{ + KeyDown(&in_score); + if ( gViewPort ) + { + gViewPort->ShowScoreBoard(); + } +} + +void IN_ScoreUp(void) +{ + KeyUp(&in_score); + if ( gViewPort ) + { + gViewPort->HideScoreBoard(); + } +} + +void IN_MLookUp (void) +{ + KeyUp( &in_mlook ); + if ( !( in_mlook.state & 1 ) && lookspring->value ) + { + V_StartPitchDrift(); + } +} + +/* +=============== +CL_KeyState + +Returns 0.25 if a key was pressed and released during the frame, +0.5 if it was pressed and held +0 if held then released, and +1.0 if held for the entire time +=============== +*/ +float CL_KeyState (kbutton_t *key) +{ + float val = 0.0; + int impulsedown, impulseup, down; + + impulsedown = key->state & 2; + impulseup = key->state & 4; + down = key->state & 1; + + if ( impulsedown && !impulseup ) + { + // pressed and held this frame? + val = down ? 0.5 : 0.0; + } + + if ( impulseup && !impulsedown ) + { + // released this frame? + val = down ? 0.0 : 0.0; + } + + if ( !impulsedown && !impulseup ) + { + // held the entire frame? + val = down ? 1.0 : 0.0; + } + + if ( impulsedown && impulseup ) + { + if ( down ) + { + // released and re-pressed this frame + val = 0.75; + } + else + { + // pressed and released this frame + val = 0.25; + } + } + + // clear impulses + key->state &= 1; + return val; +} + +bool bCanMoveMouse ( void ) +{ + if ( gViewPort && ( gViewPort->m_iUser1 == OBS_ROAMING || gViewPort->m_iUser1 == OBS_CHASE_FREE ) ) + return TRUE; + + if ( gViewPort && gViewPort->m_iUser1 == OBS_LOCKEDVIEW ) + return FALSE; + + return TRUE; +} +/* +================ +CL_AdjustAngles + +Moves the local angle positions +================ +*/ +void CL_AdjustAngles ( float frametime, float *viewangles ) +{ + float speed; + float up, down; + + if (in_speed.state & 1) + { + speed = frametime * cl_anglespeedkey->value; + } + else + { + speed = frametime; + } + + // Ricochet: Don't let them move the mouse when they're in spectator mode + if ( bCanMoveMouse() == FALSE ) + return; + + if (!(in_strafe.state & 1)) + { + viewangles[YAW] -= speed*cl_yawspeed->value*CL_KeyState (&in_right); + viewangles[YAW] += speed*cl_yawspeed->value*CL_KeyState (&in_left); + viewangles[YAW] = anglemod(viewangles[YAW]); + } + if (in_klook.state & 1) + { + V_StopPitchDrift (); + viewangles[PITCH] -= speed*cl_pitchspeed->value * CL_KeyState (&in_forward); + viewangles[PITCH] += speed*cl_pitchspeed->value * CL_KeyState (&in_back); + } + + up = CL_KeyState (&in_lookup); + down = CL_KeyState(&in_lookdown); + + viewangles[PITCH] -= speed*cl_pitchspeed->value * up; + viewangles[PITCH] += speed*cl_pitchspeed->value * down; + + if (up || down) + V_StopPitchDrift (); + + if (viewangles[PITCH] > cl_pitchdown->value) + viewangles[PITCH] = cl_pitchdown->value; + if (viewangles[PITCH] < -cl_pitchup->value) + viewangles[PITCH] = -cl_pitchup->value; + + if (viewangles[ROLL] > 50) + viewangles[ROLL] = 50; + if (viewangles[ROLL] < -50) + viewangles[ROLL] = -50; +} + +/* +================ +CL_CreateMove + +Send the intended movement message to the server +if active == 1 then we are 1) not playing back demos ( where our commands are ignored ) and +2 ) we have finished signing on to server +================ +*/ +void EXPORT CL_CreateMove ( float frametime, struct usercmd_s *cmd, int active ) +{ + float spd; + vec3_t viewangles; + static vec3_t oldangles; + + if ( active ) + { + //memset( viewangles, 0, sizeof( vec3_t ) ); + //viewangles[ 0 ] = viewangles[ 1 ] = viewangles[ 2 ] = 0.0; + gEngfuncs.GetViewAngles( (float *)viewangles ); + + CL_AdjustAngles ( frametime, viewangles ); + + memset (cmd, 0, sizeof(*cmd)); + + gEngfuncs.SetViewAngles( (float *)viewangles ); + + if ( in_strafe.state & 1 ) + { + cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_right); + cmd->sidemove -= cl_sidespeed->value * CL_KeyState (&in_left); + } + + cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_moveright); + cmd->sidemove -= cl_sidespeed->value * CL_KeyState (&in_moveleft); + + cmd->upmove += cl_upspeed->value * CL_KeyState (&in_up); + cmd->upmove -= cl_upspeed->value * CL_KeyState (&in_down); + + if ( !(in_klook.state & 1 ) ) + { + cmd->forwardmove += cl_forwardspeed->value * CL_KeyState (&in_forward); + cmd->forwardmove -= cl_backspeed->value * CL_KeyState (&in_back); + } + + // adjust for speed key + if ( in_speed.state & 1 ) + { + cmd->forwardmove *= cl_movespeedkey->value; + cmd->sidemove *= cl_movespeedkey->value; + cmd->upmove *= cl_movespeedkey->value; + } + + // clip to maxspeed + spd = gEngfuncs.GetClientMaxspeed(); + if ( spd != 0.0 ) + { + // scale the 3 speeds so that the total velocity is not > cl.maxspeed + float fmov = sqrt( (cmd->forwardmove*cmd->forwardmove) + (cmd->sidemove*cmd->sidemove) + (cmd->upmove*cmd->upmove) ); + + if ( fmov > spd ) + { + float fratio = spd / fmov; + cmd->forwardmove *= fratio; + cmd->sidemove *= fratio; + cmd->upmove *= fratio; + } + } + + // Allow mice and other controllers to add their inputs + IN_Move ( frametime, cmd ); + } + + cmd->impulse = in_impulse; + in_impulse = 0; + + cmd->weaponselect = g_weaponselect; + g_weaponselect = 0; + // + // set button and flag bits + // + cmd->buttons = CL_ButtonBits( 1 ); + + // If they're in a modal dialog, ignore the attack button. + if( GetClientVoiceMgr()->IsInSquelchMode() ) + cmd->buttons &= ~IN_ATTACK; + + // Using joystick? + if ( in_joystick->value ) + { + if ( cmd->forwardmove > 0 ) + { + cmd->buttons |= IN_FORWARD; + } + else if ( cmd->forwardmove < 0 ) + { + cmd->buttons |= IN_BACK; + } + } + + gEngfuncs.GetViewAngles( (float *)viewangles ); + // Set current view angles. + + if ( gHUD.m_Health.m_iHealth > 0 ) + { + VectorCopy( viewangles, cmd->viewangles ); + VectorCopy( viewangles, oldangles ); + } + else + { + VectorCopy( oldangles, cmd->viewangles ); + } +} + +/* +============ +CL_IsDead + +Returns 1 if health is <= 0 +============ +*/ +int CL_IsDead( void ) +{ + return ( gHUD.m_Health.m_iHealth <= 0 ) ? 1 : 0; +} + +/* +============ +CL_ButtonBits + +Returns appropriate button info for keyboard and mouse state +Set bResetState to 1 to clear old state info +============ +*/ +int CL_ButtonBits( int bResetState ) +{ + int bits = 0; + + if ( in_attack.state & 3 ) + { + bits |= IN_ATTACK; + } + + if (in_duck.state & 3) + { + bits |= IN_DUCK; + } + + if (in_jump.state & 3) + { + bits |= IN_JUMP; + } + + if ( in_forward.state & 3 ) + { + bits |= IN_FORWARD; + } + + if (in_back.state & 3) + { + bits |= IN_BACK; + } + + if (in_use.state & 3) + { + bits |= IN_USE; + } + + if (in_cancel) + { + bits |= IN_CANCEL; + } + + if ( in_left.state & 3 ) + { + bits |= IN_LEFT; + } + + if (in_right.state & 3) + { + bits |= IN_RIGHT; + } + + if ( in_moveleft.state & 3 ) + { + bits |= IN_MOVELEFT; + } + + if (in_moveright.state & 3) + { + bits |= IN_MOVERIGHT; + } + + if (in_attack2.state & 3) + { + bits |= IN_ATTACK2; + } + + if (in_reload.state & 3) + { + bits |= IN_RELOAD; + } + + if (in_alt1.state & 3) + { + bits |= IN_ALT1; + } + + if ( in_score.state & 3 ) + { + bits |= IN_SCORE; + } + + // Dead or in intermission? Shore scoreboard, too + if ( CL_IsDead() || gHUD.m_iIntermission ) + { + bits |= IN_SCORE; + } + + if ( bResetState ) + { + in_attack.state &= ~2; + in_duck.state &= ~2; + in_jump.state &= ~2; + in_forward.state &= ~2; + in_back.state &= ~2; + in_use.state &= ~2; + in_left.state &= ~2; + in_right.state &= ~2; + in_moveleft.state &= ~2; + in_moveright.state &= ~2; + in_attack2.state &= ~2; + in_reload.state &= ~2; + in_alt1.state &= ~2; + in_score.state &= ~2; + } + + return bits; +} + +/* +============ +CL_ResetButtonBits + +============ +*/ +void CL_ResetButtonBits( int bits ) +{ + int bitsNew = CL_ButtonBits( 0 ) ^ bits; + + // Has the attack button been changed + if ( bitsNew & IN_ATTACK ) + { + // Was it pressed? or let go? + if ( bits & IN_ATTACK ) + { + KeyDown( &in_attack ); + } + else + { + // totally clear state + in_attack.state &= ~7; + } + } +} + +/* +============ +InitInput +============ +*/ +void InitInput (void) +{ + gEngfuncs.pfnAddCommand ("+moveup",IN_UpDown); + gEngfuncs.pfnAddCommand ("-moveup",IN_UpUp); + gEngfuncs.pfnAddCommand ("+movedown",IN_DownDown); + gEngfuncs.pfnAddCommand ("-movedown",IN_DownUp); + gEngfuncs.pfnAddCommand ("+left",IN_LeftDown); + gEngfuncs.pfnAddCommand ("-left",IN_LeftUp); + gEngfuncs.pfnAddCommand ("+right",IN_RightDown); + gEngfuncs.pfnAddCommand ("-right",IN_RightUp); + gEngfuncs.pfnAddCommand ("+forward",IN_ForwardDown); + gEngfuncs.pfnAddCommand ("-forward",IN_ForwardUp); + gEngfuncs.pfnAddCommand ("+back",IN_BackDown); + gEngfuncs.pfnAddCommand ("-back",IN_BackUp); + gEngfuncs.pfnAddCommand ("+lookup", IN_LookupDown); + gEngfuncs.pfnAddCommand ("-lookup", IN_LookupUp); + gEngfuncs.pfnAddCommand ("+lookdown", IN_LookdownDown); + gEngfuncs.pfnAddCommand ("-lookdown", IN_LookdownUp); + gEngfuncs.pfnAddCommand ("+strafe", IN_StrafeDown); + gEngfuncs.pfnAddCommand ("-strafe", IN_StrafeUp); + gEngfuncs.pfnAddCommand ("+moveleft", IN_MoveleftDown); + gEngfuncs.pfnAddCommand ("-moveleft", IN_MoveleftUp); + gEngfuncs.pfnAddCommand ("+moveright", IN_MoverightDown); + gEngfuncs.pfnAddCommand ("-moveright", IN_MoverightUp); + gEngfuncs.pfnAddCommand ("+speed", IN_SpeedDown); + gEngfuncs.pfnAddCommand ("-speed", IN_SpeedUp); + gEngfuncs.pfnAddCommand ("+attack", IN_AttackDown); + gEngfuncs.pfnAddCommand ("-attack", IN_AttackUp); + gEngfuncs.pfnAddCommand ("+attack2", IN_Attack2Down); + gEngfuncs.pfnAddCommand ("-attack2", IN_Attack2Up); + gEngfuncs.pfnAddCommand ("+use", IN_UseDown); + gEngfuncs.pfnAddCommand ("-use", IN_UseUp); + gEngfuncs.pfnAddCommand ("+jump", IN_JumpDown); + gEngfuncs.pfnAddCommand ("-jump", IN_JumpUp); + gEngfuncs.pfnAddCommand ("impulse", IN_Impulse); + gEngfuncs.pfnAddCommand ("+klook", IN_KLookDown); + gEngfuncs.pfnAddCommand ("-klook", IN_KLookUp); + gEngfuncs.pfnAddCommand ("+mlook", IN_MLookDown); + gEngfuncs.pfnAddCommand ("-mlook", IN_MLookUp); + gEngfuncs.pfnAddCommand ("+jlook", IN_JLookDown); + gEngfuncs.pfnAddCommand ("-jlook", IN_JLookUp); + gEngfuncs.pfnAddCommand ("+duck", IN_DuckDown); + gEngfuncs.pfnAddCommand ("-duck", IN_DuckUp); + gEngfuncs.pfnAddCommand ("+reload", IN_ReloadDown); + gEngfuncs.pfnAddCommand ("-reload", IN_ReloadUp); + gEngfuncs.pfnAddCommand ("+alt1", IN_Alt1Down); + gEngfuncs.pfnAddCommand ("-alt1", IN_Alt1Up); + gEngfuncs.pfnAddCommand ("+score", IN_ScoreDown); + gEngfuncs.pfnAddCommand ("-score", IN_ScoreUp); + gEngfuncs.pfnAddCommand ("+showscores", IN_ScoreDown); + gEngfuncs.pfnAddCommand ("-showscores", IN_ScoreUp); + gEngfuncs.pfnAddCommand ("+graph", IN_GraphDown); + gEngfuncs.pfnAddCommand ("-graph", IN_GraphUp); + gEngfuncs.pfnAddCommand ("+break",IN_BreakDown); + gEngfuncs.pfnAddCommand ("-break",IN_BreakUp); + + lookstrafe = gEngfuncs.pfnRegisterVariable ( "lookstrafe", "0", FCVAR_ARCHIVE ); + lookspring = gEngfuncs.pfnRegisterVariable ( "lookspring", "0", FCVAR_ARCHIVE ); + cl_anglespeedkey = gEngfuncs.pfnRegisterVariable ( "cl_anglespeedkey", "0.67", 0 ); + cl_yawspeed = gEngfuncs.pfnRegisterVariable ( "cl_yawspeed", "210", 0 ); + cl_pitchspeed = gEngfuncs.pfnRegisterVariable ( "cl_pitchspeed", "225", 0 ); + cl_upspeed = gEngfuncs.pfnRegisterVariable ( "cl_upspeed", "320", 0 ); + cl_forwardspeed = gEngfuncs.pfnRegisterVariable ( "cl_forwardspeed", "400", FCVAR_ARCHIVE ); + cl_backspeed = gEngfuncs.pfnRegisterVariable ( "cl_backspeed", "400", FCVAR_ARCHIVE ); + cl_sidespeed = gEngfuncs.pfnRegisterVariable ( "cl_sidespeed", "400", 0 ); + cl_movespeedkey = gEngfuncs.pfnRegisterVariable ( "cl_movespeedkey", "0.3", 0 ); + cl_pitchup = gEngfuncs.pfnRegisterVariable ( "cl_pitchup", "89", 0 ); + cl_pitchdown = gEngfuncs.pfnRegisterVariable ( "cl_pitchdown", "89", 0 ); + + cl_vsmoothing = gEngfuncs.pfnRegisterVariable ( "cl_vsmoothing", "0.05", FCVAR_ARCHIVE ); + + m_pitch = gEngfuncs.pfnRegisterVariable ( "m_pitch","0.022", FCVAR_ARCHIVE ); + m_yaw = gEngfuncs.pfnRegisterVariable ( "m_yaw","0.022", FCVAR_ARCHIVE ); + m_forward = gEngfuncs.pfnRegisterVariable ( "m_forward","1", FCVAR_ARCHIVE ); + m_side = gEngfuncs.pfnRegisterVariable ( "m_side","0.8", FCVAR_ARCHIVE ); + + // Initialize third person camera controls. + CAM_Init(); + // Initialize inputs + IN_Init(); + // Initialize keyboard + KB_Init(); + // Initialize view system + V_Init(); +} + +/* +============ +ShutdownInput +============ +*/ +void ShutdownInput (void) +{ + IN_Shutdown(); + KB_Shutdown(); +} + +#include "interface.h" + +void EXPORT HUD_Shutdown( void ) +{ + ShutdownInput(); + + extern CSysModule *g_hTrackerModule; + if (g_hTrackerModule) + { + Sys_UnloadModule(g_hTrackerModule); + } +} \ No newline at end of file diff --git a/ricochet/cl_dll/inputw32.cpp b/ricochet/cl_dll/inputw32.cpp new file mode 100644 index 0000000..101123d --- /dev/null +++ b/ricochet/cl_dll/inputw32.cpp @@ -0,0 +1,938 @@ +// in_win.c -- windows 95 mouse and joystick code +// 02/21/97 JCB Added extended DirectInput code to support external controllers. + +#include "port.h" +#include +#include + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "../public/keydefs.h" +#include "view.h" +#include "Exports.h" + +#define MOUSE_BUTTON_COUNT 5 + +// Set this to 1 to show mouse cursor. Experimental +int g_iVisibleMouse = 0; + +extern cl_enginefunc_t gEngfuncs; + +extern int iMouseInUse; + +extern kbutton_t in_strafe; +extern kbutton_t in_mlook; +extern kbutton_t in_speed; +extern kbutton_t in_jlook; + +extern cvar_t *m_pitch; +extern cvar_t *m_yaw; +extern cvar_t *m_forward; +extern cvar_t *m_side; + +extern cvar_t *lookstrafe; +extern cvar_t *lookspring; +extern cvar_t *cl_pitchdown; +extern cvar_t *cl_pitchup; +extern cvar_t *cl_yawspeed; +extern cvar_t *cl_sidespeed; +extern cvar_t *cl_forwardspeed; +extern cvar_t *cl_pitchspeed; +extern cvar_t *cl_movespeedkey; + +// mouse variables +cvar_t *m_filter; +cvar_t *sensitivity; + +// Custom mouse acceleration (0 disable, 1 to enable, 2 enable with separate yaw/pitch rescale) +static cvar_t *m_customaccel; +//Formula: mousesensitivity = ( rawmousedelta^m_customaccel_exponent ) * m_customaccel_scale + sensitivity +// If mode is 2, then x and y sensitivity are scaled by m_pitch and m_yaw respectively. +// Custom mouse acceleration value. +static cvar_t *m_customaccel_scale; +//Max mouse move scale factor, 0 for no limit +static cvar_t *m_customaccel_max; +//Mouse move is raised to this power before being scaled by scale factor +static cvar_t *m_customaccel_exponent; + +int mouse_buttons; +int mouse_oldbuttonstate; +POINT current_pos; +int old_mouse_x, old_mouse_y, mx_accum, my_accum; +float mouse_x, mouse_y; + +static int restore_spi; +static int originalmouseparms[3], newmouseparms[3] = {0, 0, 1}; +static int mouseactive; +int mouseinitialized; +static int mouseparmsvalid; +static int mouseshowtoggle = 1; + +// joystick defines and variables +// where should defines be moved? +#define JOY_ABSOLUTE_AXIS 0x00000000 // control like a joystick +#define JOY_RELATIVE_AXIS 0x00000010 // control like a mouse, spinner, trackball +#define JOY_MAX_AXES 6 // X, Y, Z, R, U, V +#define JOY_AXIS_X 0 +#define JOY_AXIS_Y 1 +#define JOY_AXIS_Z 2 +#define JOY_AXIS_R 3 +#define JOY_AXIS_U 4 +#define JOY_AXIS_V 5 + +enum _ControlList +{ + AxisNada = 0, + AxisForward, + AxisLook, + AxisSide, + AxisTurn +}; + + +DWORD dwAxisMap[ JOY_MAX_AXES ]; +DWORD dwControlMap[ JOY_MAX_AXES ]; +DWORD pdwRawValue[ JOY_MAX_AXES ]; +DWORD joy_oldbuttonstate, joy_oldpovstate; + +int joy_id; +DWORD joy_numbuttons; +SDL_GameController *s_pJoystick = NULL; + + +// none of these cvars are saved over a session +// this means that advanced controller configuration needs to be executed +// each time. this avoids any problems with getting back to a default usage +// or when changing from one controller to another. this way at least something +// works. +cvar_t *in_joystick; +cvar_t *joy_name; +cvar_t *joy_advanced; +cvar_t *joy_advaxisx; +cvar_t *joy_advaxisy; +cvar_t *joy_advaxisz; +cvar_t *joy_advaxisr; +cvar_t *joy_advaxisu; +cvar_t *joy_advaxisv; +cvar_t *joy_forwardthreshold; +cvar_t *joy_sidethreshold; +cvar_t *joy_pitchthreshold; +cvar_t *joy_yawthreshold; +cvar_t *joy_forwardsensitivity; +cvar_t *joy_sidesensitivity; +cvar_t *joy_pitchsensitivity; +cvar_t *joy_yawsensitivity; +cvar_t *joy_wwhack1; +cvar_t *joy_wwhack2; + +int joy_avail, joy_advancedinit, joy_haspov; + +/* +=========== +Force_CenterView_f +=========== +*/ +void Force_CenterView_f (void) +{ + vec3_t viewangles; + + if (!iMouseInUse) + { + gEngfuncs.GetViewAngles( (float *)viewangles ); + viewangles[PITCH] = 0; + gEngfuncs.SetViewAngles( (float *)viewangles ); + } +} + +/* +=========== +IN_ActivateMouse +=========== +*/ +void CL_DLLEXPORT IN_ActivateMouse (void) +{ + if (mouseinitialized) + { +#ifdef _WIN32 + if (mouseparmsvalid) + restore_spi = SystemParametersInfo (SPI_SETMOUSE, 0, newmouseparms, 0); +#endif + mouseactive = 1; + } +} + +/* +=========== +IN_DeactivateMouse +=========== +*/ +void CL_DLLEXPORT IN_DeactivateMouse (void) +{ + if (mouseinitialized) + { +#ifdef _WIN32 + if (restore_spi) + SystemParametersInfo (SPI_SETMOUSE, 0, originalmouseparms, 0); +#endif + + mouseactive = 0; + } +} + +/* +=========== +IN_StartupMouse +=========== +*/ +void IN_StartupMouse (void) +{ + if ( gEngfuncs.CheckParm ("-nomouse", NULL ) ) + return; + + mouseinitialized = 1; +#ifdef _WIN32 + mouseparmsvalid = SystemParametersInfo (SPI_GETMOUSE, 0, originalmouseparms, 0); + + if (mouseparmsvalid) + { + if ( gEngfuncs.CheckParm ("-noforcemspd", NULL ) ) + newmouseparms[2] = originalmouseparms[2]; + + if ( gEngfuncs.CheckParm ("-noforcemaccel", NULL ) ) + { + newmouseparms[0] = originalmouseparms[0]; + newmouseparms[1] = originalmouseparms[1]; + } + + if ( gEngfuncs.CheckParm ("-noforcemparms", NULL ) ) + { + newmouseparms[0] = originalmouseparms[0]; + newmouseparms[1] = originalmouseparms[1]; + newmouseparms[2] = originalmouseparms[2]; + } + } +#endif + + mouse_buttons = MOUSE_BUTTON_COUNT; +} + +/* +=========== +IN_Shutdown +=========== +*/ +void IN_Shutdown (void) +{ + IN_DeactivateMouse (); +} + +/* +=========== +IN_GetMousePos + +Ask for mouse position from engine +=========== +*/ +void IN_GetMousePos( int *mx, int *my ) +{ + gEngfuncs.GetMousePosition( mx, my ); +} + +/* +=========== +IN_ResetMouse + +FIXME: Call through to engine? +=========== +*/ +void IN_ResetMouse( void ) +{ +} + +/* +=========== +IN_MouseEvent +=========== +*/ +void CL_DLLEXPORT IN_MouseEvent (int mstate) +{ + int i; + + if ( iMouseInUse || g_iVisibleMouse ) + return; + + // perform button actions + for (i=0 ; ivalue; + + // Using special accleration values + if ( m_customaccel->value != 0 ) + { + float raw_mouse_movement_distance = sqrt( mx * mx + my * my ); + float acceleration_scale = m_customaccel_scale->value; + float accelerated_sensitivity_max = m_customaccel_max->value; + float accelerated_sensitivity_exponent = m_customaccel_exponent->value; + float accelerated_sensitivity = ( (float)pow( raw_mouse_movement_distance, accelerated_sensitivity_exponent ) * acceleration_scale + mouse_senstivity ); + + if ( accelerated_sensitivity_max > 0.0001f && + accelerated_sensitivity > accelerated_sensitivity_max ) + { + accelerated_sensitivity = accelerated_sensitivity_max; + } + + *x *= accelerated_sensitivity; + *y *= accelerated_sensitivity; + + // Further re-scale by yaw and pitch magnitude if user requests alternate mode 2 + // This means that they will need to up their value for m_customaccel_scale greatly (>40x) since m_pitch/yaw default + // to 0.022 + if ( m_customaccel->value == 2 ) + { + *x *= m_yaw->value; + *y *= m_pitch->value; + } + } + else + { + // Just apply the default + *x *= mouse_senstivity; + *y *= mouse_senstivity; + } +} + +/* +=========== +IN_MouseMove +=========== +*/ +bool bCanMoveMouse ( void ); +void IN_MouseMove ( float frametime, usercmd_t *cmd) +{ + int mx, my; + vec3_t viewangles; + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + if ( in_mlook.state & 1) + { + V_StopPitchDrift (); + } + + // Ricochet: Don't let them move the mouse when they're in spectator mode + int iSpectator = !bCanMoveMouse(); + + //jjb - this disbles normal mouse control if the user is trying to + // move the camera, or if the mouse cursor is visible or if we're in intermission + if ( !iMouseInUse && !gHUD.m_iIntermission && !g_iVisibleMouse && !iSpectator ) + { + int deltaX, deltaY; + SDL_GetRelativeMouseState( &deltaX, &deltaY ); + current_pos.x = deltaX; + current_pos.y = deltaY; + mx = deltaX + mx_accum; + my = deltaY + my_accum; + + mx_accum = 0; + my_accum = 0; + + if (m_filter->value) + { + mouse_x = (mx + old_mouse_x) * 0.5; + mouse_y = (my + old_mouse_y) * 0.5; + } + else + { + mouse_x = mx; + mouse_y = my; + } + + old_mouse_x = mx; + old_mouse_y = my; + + // Apply custom mouse scaling/acceleration + IN_ScaleMouse( &mouse_x, &mouse_y ); + + // add mouse X/Y movement to cmd + if ( (in_strafe.state & 1) || (lookstrafe->value && (in_mlook.state & 1) )) + cmd->sidemove += m_side->value * mouse_x; + else + viewangles[YAW] -= m_yaw->value * mouse_x; + + if ( (in_mlook.state & 1) && !(in_strafe.state & 1)) + { + viewangles[PITCH] += m_pitch->value * mouse_y; + if (viewangles[PITCH] > cl_pitchdown->value) + viewangles[PITCH] = cl_pitchdown->value; + if (viewangles[PITCH] < -cl_pitchup->value) + viewangles[PITCH] = -cl_pitchup->value; + } + else + { + if ((in_strafe.state & 1) && gEngfuncs.IsNoClipping() ) + { + cmd->upmove -= m_forward->value * mouse_y; + } + else + { + cmd->forwardmove -= m_forward->value * mouse_y; + } + } + + // if the mouse has moved, force it to the center, so there's room to move + if ( mx || my ) + { + IN_ResetMouse(); + } + } + + gEngfuncs.SetViewAngles( (float *)viewangles ); + +/* +//#define TRACE_TEST +#if defined( TRACE_TEST ) + { + int mx, my; + void V_Move( int mx, int my ); + IN_GetMousePos( &mx, &my ); + V_Move( mx, my ); + } +#endif +*/ +} + +/* +=========== +IN_Accumulate +=========== +*/ +void CL_DLLEXPORT IN_Accumulate (void) +{ + //only accumulate mouse if we are not moving the camera with the mouse + if ( !iMouseInUse && !g_iVisibleMouse) + { + if (mouseactive) + { + int deltaX, deltaY; + SDL_GetRelativeMouseState( &deltaX, &deltaY ); + mx_accum += deltaX; + my_accum += deltaY; + // force the mouse to the center, so there's room to move + IN_ResetMouse(); + + } + } + +} + +/* +=================== +IN_ClearStates +=================== +*/ +void CL_DLLEXPORT IN_ClearStates (void) +{ + if ( !mouseactive ) + return; + + mx_accum = 0; + my_accum = 0; + mouse_oldbuttonstate = 0; +} + +/* +=============== +IN_StartupJoystick +=============== +*/ +void IN_StartupJoystick (void) +{ + // abort startup if user requests no joystick + if ( gEngfuncs.CheckParm ("-nojoy", NULL ) ) + return; + + // assume no joystick + joy_avail = 0; + + int nJoysticks = SDL_NumJoysticks(); + if ( nJoysticks > 0 ) + { + for ( int i = 0; i < nJoysticks; i++ ) + { + if ( SDL_IsGameController( i ) ) + { + s_pJoystick = SDL_GameControllerOpen( i ); + if ( s_pJoystick ) + { + //save the joystick's number of buttons and POV status + joy_numbuttons = SDL_CONTROLLER_BUTTON_MAX; + joy_haspov = 0; + + // old button and POV states default to no buttons pressed + joy_oldbuttonstate = joy_oldpovstate = 0; + + // mark the joystick as available and advanced initialization not completed + // this is needed as cvars are not available during initialization + gEngfuncs.Con_Printf ("joystick found\n\n", SDL_GameControllerName(s_pJoystick)); + joy_avail = 1; + joy_advancedinit = 0; + break; + } + + } + } + } + else + { + gEngfuncs.Con_DPrintf ("joystick not found -- driver not present\n\n"); + } + +} + +int RawValuePointer (int axis) +{ + switch (axis) + { + default: + case JOY_AXIS_X: + return SDL_GameControllerGetAxis( s_pJoystick, SDL_CONTROLLER_AXIS_LEFTX ); + case JOY_AXIS_Y: + return SDL_GameControllerGetAxis( s_pJoystick, SDL_CONTROLLER_AXIS_LEFTY ); + case JOY_AXIS_Z: + return SDL_GameControllerGetAxis( s_pJoystick, SDL_CONTROLLER_AXIS_RIGHTX ); + case JOY_AXIS_R: + return SDL_GameControllerGetAxis( s_pJoystick, SDL_CONTROLLER_AXIS_RIGHTY ); + + } +} + + +/* +=========== +Joy_AdvancedUpdate_f +=========== +*/ +void Joy_AdvancedUpdate_f (void) +{ + + // called once by IN_ReadJoystick and by user whenever an update is needed + // cvars are now available + int i; + DWORD dwTemp; + + // initialize all the maps + for (i = 0; i < JOY_MAX_AXES; i++) + { + dwAxisMap[i] = AxisNada; + dwControlMap[i] = JOY_ABSOLUTE_AXIS; + pdwRawValue[i] = RawValuePointer(i); + } + + if( joy_advanced->value == 0.0) + { + // default joystick initialization + // 2 axes only with joystick control + dwAxisMap[JOY_AXIS_X] = AxisTurn; + // dwControlMap[JOY_AXIS_X] = JOY_ABSOLUTE_AXIS; + dwAxisMap[JOY_AXIS_Y] = AxisForward; + // dwControlMap[JOY_AXIS_Y] = JOY_ABSOLUTE_AXIS; + } + else + { + if ( strcmp ( joy_name->string, "joystick") != 0 ) + { + // notify user of advanced controller + gEngfuncs.Con_Printf ("\n%s configured\n\n", joy_name->string); + } + + // advanced initialization here + // data supplied by user via joy_axisn cvars + dwTemp = (DWORD) joy_advaxisx->value; + dwAxisMap[JOY_AXIS_X] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_X] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisy->value; + dwAxisMap[JOY_AXIS_Y] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_Y] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisz->value; + dwAxisMap[JOY_AXIS_Z] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_Z] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisr->value; + dwAxisMap[JOY_AXIS_R] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_R] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisu->value; + dwAxisMap[JOY_AXIS_U] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_U] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisv->value; + dwAxisMap[JOY_AXIS_V] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_V] = dwTemp & JOY_RELATIVE_AXIS; + } + +} + + +/* +=========== +IN_Commands +=========== +*/ +void IN_Commands (void) +{ + int i, key_index; + + if (!joy_avail) + { + return; + } + + DWORD buttonstate, povstate; + + // loop through the joystick buttons + // key a joystick event or auxillary event for higher number buttons for each state change + buttonstate = 0; + for ( i = 0; i < SDL_CONTROLLER_BUTTON_MAX; i++ ) + { + if ( SDL_GameControllerGetButton( s_pJoystick, (SDL_GameControllerButton)i ) ) + { + buttonstate |= 1<value) + { + return; + } + + // collect the joystick data, if possible + if (IN_ReadJoystick () != 1) + { + return; + } + + if (in_speed.state & 1) + speed = cl_movespeedkey->value; + else + speed = 1; + + aspeed = speed * frametime; + + // loop through the axes + for (i = 0; i < JOY_MAX_AXES; i++) + { + // get the floating point zero-centered, potentially-inverted data for the current axis + fAxisValue = (float) pdwRawValue[i]; + // move centerpoint to zero + fAxisValue -= 32768.0; + + if (joy_wwhack2->value != 0.0) + { + if (dwAxisMap[i] == AxisTurn) + { + // this is a special formula for the Logitech WingMan Warrior + // y=ax^b; where a = 300 and b = 1.3 + // also x values are in increments of 800 (so this is factored out) + // then bounds check result to level out excessively high spin rates + fTemp = 300.0 * pow(abs(fAxisValue) / 800.0, 1.3); + if (fTemp > 14000.0) + fTemp = 14000.0; + // restore direction information + fAxisValue = (fAxisValue > 0.0) ? fTemp : -fTemp; + } + } + + // convert range from -32768..32767 to -1..1 + fAxisValue /= 32768.0; + + switch (dwAxisMap[i]) + { + case AxisForward: + if ((joy_advanced->value == 0.0) && (in_jlook.state & 1)) + { + // user wants forward control to become look control + if (fabs(fAxisValue) > joy_pitchthreshold->value) + { + // if mouse invert is on, invert the joystick pitch value + // only absolute control support here (joy_advanced is 0) + if (m_pitch->value < 0.0) + { + viewangles[PITCH] -= (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; + } + else + { + viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; + } + V_StopPitchDrift(); + } + else + { + // no pitch movement + // disable pitch return-to-center unless requested by user + // *** this code can be removed when the lookspring bug is fixed + // *** the bug always has the lookspring feature on + if(lookspring->value == 0.0) + { + V_StopPitchDrift(); + } + } + } + else + { + // user wants forward control to be forward control + if (fabs(fAxisValue) > joy_forwardthreshold->value) + { + cmd->forwardmove += (fAxisValue * joy_forwardsensitivity->value) * speed * cl_forwardspeed->value; + } + } + break; + + case AxisSide: + if (fabs(fAxisValue) > joy_sidethreshold->value) + { + cmd->sidemove += (fAxisValue * joy_sidesensitivity->value) * speed * cl_sidespeed->value; + } + break; + + case AxisTurn: + if ((in_strafe.state & 1) || (lookstrafe->value && (in_jlook.state & 1))) + { + // user wants turn control to become side control + if (fabs(fAxisValue) > joy_sidethreshold->value) + { + cmd->sidemove -= (fAxisValue * joy_sidesensitivity->value) * speed * cl_sidespeed->value; + } + } + else + { + // user wants turn control to be turn control + if (fabs(fAxisValue) > joy_yawthreshold->value) + { + if(dwControlMap[i] == JOY_ABSOLUTE_AXIS) + { + viewangles[YAW] += (fAxisValue * joy_yawsensitivity->value) * aspeed * cl_yawspeed->value; + } + else + { + viewangles[YAW] += (fAxisValue * joy_yawsensitivity->value) * speed * 180.0; + } + + } + } + break; + + case AxisLook: + if (in_jlook.state & 1) + { + if (fabs(fAxisValue) > joy_pitchthreshold->value) + { + // pitch movement detected and pitch movement desired by user + if(dwControlMap[i] == JOY_ABSOLUTE_AXIS) + { + viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; + } + else + { + viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * speed * 180.0; + } + V_StopPitchDrift(); + } + else + { + // no pitch movement + // disable pitch return-to-center unless requested by user + // *** this code can be removed when the lookspring bug is fixed + // *** the bug always has the lookspring feature on + if( lookspring->value == 0.0 ) + { + V_StopPitchDrift(); + } + } + } + break; + + default: + break; + } + } + + // bounds check pitch + if (viewangles[PITCH] > cl_pitchdown->value) + viewangles[PITCH] = cl_pitchdown->value; + if (viewangles[PITCH] < -cl_pitchup->value) + viewangles[PITCH] = -cl_pitchup->value; + + gEngfuncs.SetViewAngles( (float *)viewangles ); +#endif +} + +/* +=========== +IN_Move +=========== +*/ +void IN_Move ( float frametime, usercmd_t *cmd) +{ + if ( !iMouseInUse && mouseactive ) + { + IN_MouseMove ( frametime, cmd); + } + + IN_JoyMove ( frametime, cmd); +} + +/* +=========== +IN_Init +=========== +*/ +void IN_Init (void) +{ + m_filter = gEngfuncs.pfnRegisterVariable ( "m_filter","0", FCVAR_ARCHIVE ); + sensitivity = gEngfuncs.pfnRegisterVariable ( "sensitivity","3", FCVAR_ARCHIVE ); // user mouse sensitivity setting. + + in_joystick = gEngfuncs.pfnRegisterVariable ( "joystick","0", FCVAR_ARCHIVE ); + joy_name = gEngfuncs.pfnRegisterVariable ( "joyname", "joystick", 0 ); + joy_advanced = gEngfuncs.pfnRegisterVariable ( "joyadvanced", "0", 0 ); + joy_advaxisx = gEngfuncs.pfnRegisterVariable ( "joyadvaxisx", "0", 0 ); + joy_advaxisy = gEngfuncs.pfnRegisterVariable ( "joyadvaxisy", "0", 0 ); + joy_advaxisz = gEngfuncs.pfnRegisterVariable ( "joyadvaxisz", "0", 0 ); + joy_advaxisr = gEngfuncs.pfnRegisterVariable ( "joyadvaxisr", "0", 0 ); + joy_advaxisu = gEngfuncs.pfnRegisterVariable ( "joyadvaxisu", "0", 0 ); + joy_advaxisv = gEngfuncs.pfnRegisterVariable ( "joyadvaxisv", "0", 0 ); + joy_forwardthreshold = gEngfuncs.pfnRegisterVariable ( "joyforwardthreshold", "0.15", 0 ); + joy_sidethreshold = gEngfuncs.pfnRegisterVariable ( "joysidethreshold", "0.15", 0 ); + joy_pitchthreshold = gEngfuncs.pfnRegisterVariable ( "joypitchthreshold", "0.15", 0 ); + joy_yawthreshold = gEngfuncs.pfnRegisterVariable ( "joyyawthreshold", "0.15", 0 ); + joy_forwardsensitivity = gEngfuncs.pfnRegisterVariable ( "joyforwardsensitivity", "-1.0", 0 ); + joy_sidesensitivity = gEngfuncs.pfnRegisterVariable ( "joysidesensitivity", "-1.0", 0 ); + joy_pitchsensitivity = gEngfuncs.pfnRegisterVariable ( "joypitchsensitivity", "1.0", 0 ); + joy_yawsensitivity = gEngfuncs.pfnRegisterVariable ( "joyyawsensitivity", "-1.0", 0 ); + joy_wwhack1 = gEngfuncs.pfnRegisterVariable ( "joywwhack1", "0.0", 0 ); + joy_wwhack2 = gEngfuncs.pfnRegisterVariable ( "joywwhack2", "0.0", 0 ); + + m_customaccel = gEngfuncs.pfnRegisterVariable ( "m_customaccel", "0", FCVAR_ARCHIVE ); + m_customaccel_scale = gEngfuncs.pfnRegisterVariable ( "m_customaccel_scale", "0.04", FCVAR_ARCHIVE ); + m_customaccel_max = gEngfuncs.pfnRegisterVariable ( "m_customaccel_max", "0", FCVAR_ARCHIVE ); + m_customaccel_exponent = gEngfuncs.pfnRegisterVariable ( "m_customaccel_exponent", "1", FCVAR_ARCHIVE ); + + gEngfuncs.pfnAddCommand ("force_centerview", Force_CenterView_f); + gEngfuncs.pfnAddCommand ("joyadvancedupdate", Joy_AdvancedUpdate_f); + + IN_StartupMouse (); + IN_StartupJoystick (); +} diff --git a/ricochet/cl_dll/kbutton.h b/ricochet/cl_dll/kbutton.h new file mode 100644 index 0000000..f4ccf85 --- /dev/null +++ b/ricochet/cl_dll/kbutton.h @@ -0,0 +1,25 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( KBUTTONH ) +#define KBUTTONH +#pragma once + +typedef struct kbutton_s +{ + int down[2]; // key nums holding it down + int state; // low bit is down state +} kbutton_t; + +#endif // !KBUTTONH \ No newline at end of file diff --git a/ricochet/cl_dll/menu.cpp b/ricochet/cl_dll/menu.cpp new file mode 100644 index 0000000..222dae3 --- /dev/null +++ b/ricochet/cl_dll/menu.cpp @@ -0,0 +1,188 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// menu.cpp +// +// generic menu handler +// +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "vgui_TeamFortressViewport.h" + +#define MAX_MENU_STRING 512 +char g_szMenuString[MAX_MENU_STRING]; +char g_szPrelocalisedMenuString[MAX_MENU_STRING]; + +int KB_ConvertString( char *in, char **ppout ); + +DECLARE_MESSAGE( m_Menu, ShowMenu ); + +int CHudMenu :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( ShowMenu ); + + InitHUDData(); + + return 1; +} + +void CHudMenu :: InitHUDData( void ) +{ + m_fMenuDisplayed = 0; + m_bitsValidSlots = 0; + Reset(); +} + +void CHudMenu :: Reset( void ) +{ + g_szPrelocalisedMenuString[0] = 0; + m_fWaitingForMore = FALSE; +} + +int CHudMenu :: VidInit( void ) +{ + return 1; +} + +int CHudMenu :: Draw( float flTime ) +{ + // check for if menu is set to disappear + if ( m_flShutoffTime > 0 ) + { + if ( m_flShutoffTime <= gHUD.m_flTime ) + { // times up, shutoff + m_fMenuDisplayed = 0; + m_iFlags &= ~HUD_ACTIVE; + return 1; + } + } + + // don't draw the menu if the scoreboard is being shown + if ( gViewPort && gViewPort->IsScoreBoardVisible() ) + return 1; + + // draw the menu, along the left-hand side of the screen + + // count the number of newlines + int nlc = 0; + int i; + for ( i = 0; i < MAX_MENU_STRING && g_szMenuString[i] != '\0'; i++ ) + { + if ( g_szMenuString[i] == '\n' ) + nlc++; + } + + // center it + int y = (ScreenHeight/2) - ((nlc/2)*12) - 40; // make sure it is above the say text + int x = 20; + + i = 0; + while ( i < MAX_MENU_STRING && g_szMenuString[i] != '\0' ) + { + gHUD.DrawHudString( x, y, 320, g_szMenuString + i, 255, 255, 255 ); + y += 12; + + while ( i < MAX_MENU_STRING && g_szMenuString[i] != '\0' && g_szMenuString[i] != '\n' ) + i++; + if ( g_szMenuString[i] == '\n' ) + i++; + } + + return 1; +} + +// selects an item from the menu +void CHudMenu :: SelectMenuItem( int menu_item ) +{ + // if menu_item is in a valid slot, send a menuselect command to the server + if ( (menu_item > 0) && (m_bitsValidSlots & (1 << (menu_item-1))) ) + { + char szbuf[32]; + sprintf( szbuf, "menuselect %d\n", menu_item ); + ClientCmd( szbuf ); + + // remove the menu + m_fMenuDisplayed = 0; + m_iFlags &= ~HUD_ACTIVE; + } +} + + +// Message handler for ShowMenu message +// takes four values: +// short: a bitfield of keys that are valid input +// char : the duration, in seconds, the menu should stay up. -1 means is stays until something is chosen. +// byte : a boolean, TRUE if there is more string yet to be received before displaying the menu, FALSE if it's the last string +// string: menu string to display +// if this message is never received, then scores will simply be the combined totals of the players. +int CHudMenu :: MsgFunc_ShowMenu( const char *pszName, int iSize, void *pbuf ) +{ + char *temp = NULL; + + BEGIN_READ( pbuf, iSize ); + + m_bitsValidSlots = READ_SHORT(); + int DisplayTime = READ_CHAR(); + int NeedMore = READ_BYTE(); + + if ( DisplayTime > 0 ) + m_flShutoffTime = DisplayTime + gHUD.m_flTime; + else + m_flShutoffTime = -1; + + if ( m_bitsValidSlots ) + { + if ( !m_fWaitingForMore ) // this is the start of a new menu + { + strncpy( g_szPrelocalisedMenuString, READ_STRING(), MAX_MENU_STRING ); + } + else + { // append to the current menu string + strncat( g_szPrelocalisedMenuString, READ_STRING(), MAX_MENU_STRING - strlen(g_szPrelocalisedMenuString) ); + } + g_szPrelocalisedMenuString[MAX_MENU_STRING-1] = 0; // ensure null termination (strncat/strncpy does not) + + if ( !NeedMore ) + { // we have the whole string, so we can localise it now + strcpy( g_szMenuString, gHUD.m_TextMessage.BufferedLocaliseTextString( g_szPrelocalisedMenuString ) ); + + // Swap in characters + if ( KB_ConvertString( g_szMenuString, &temp ) ) + { + strcpy( g_szMenuString, temp ); + free( temp ); + } + } + + m_fMenuDisplayed = 1; + m_iFlags |= HUD_ACTIVE; + } + else + { + m_fMenuDisplayed = 0; // no valid slots means that the menu should be turned off + m_iFlags &= ~HUD_ACTIVE; + } + + m_fWaitingForMore = NeedMore; + + return 1; +} diff --git a/ricochet/cl_dll/message.cpp b/ricochet/cl_dll/message.cpp new file mode 100644 index 0000000..3307b25 --- /dev/null +++ b/ricochet/cl_dll/message.cpp @@ -0,0 +1,463 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Message.cpp +// +// implementation of CHudMessage class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE( m_Message, HudText ) +DECLARE_MESSAGE( m_Message, GameTitle ) + + +int CHudMessage::Init(void) +{ + HOOK_MESSAGE( HudText ); + HOOK_MESSAGE( GameTitle ); + + gHUD.AddHudElem(this); + + Reset(); + + return 1; +}; + +int CHudMessage::VidInit( void ) +{ + m_HUD_title_half = gHUD.GetSpriteIndex( "title_half" ); + m_HUD_title_life = gHUD.GetSpriteIndex( "title_life" ); + + return 1; +}; + + +void CHudMessage::Reset( void ) +{ + memset( m_pMessages, 0, sizeof( m_pMessages[0] ) * maxHUDMessages ); + memset( m_startTime, 0, sizeof( m_startTime[0] ) * maxHUDMessages ); + + m_gameTitleTime = 0; + m_pGameTitle = NULL; +} + + +float CHudMessage::FadeBlend( float fadein, float fadeout, float hold, float localTime ) +{ + float fadeTime = fadein + hold; + float fadeBlend; + + if ( localTime < 0 ) + return 0; + + if ( localTime < fadein ) + { + fadeBlend = 1 - ((fadein - localTime) / fadein); + } + else if ( localTime > fadeTime ) + { + if ( fadeout > 0 ) + fadeBlend = 1 - ((localTime - fadeTime) / fadeout); + else + fadeBlend = 0; + } + else + fadeBlend = 1; + + return fadeBlend; +} + + +int CHudMessage::XPosition( float x, int width, int totalWidth ) +{ + int xPos; + + if ( x == -1 ) + { + xPos = (ScreenWidth - width) / 2; + } + else + { + if ( x < 0 ) + xPos = (1.0 + x) * ScreenWidth - totalWidth; // Alight right + else + xPos = x * ScreenWidth; + } + + if ( xPos + width > ScreenWidth ) + xPos = ScreenWidth - width; + else if ( xPos < 0 ) + xPos = 0; + + return xPos; +} + + +int CHudMessage::YPosition( float y, int height ) +{ + int yPos; + + if ( y == -1 ) // Centered? + yPos = (ScreenHeight - height) * 0.5; + else + { + // Alight bottom? + if ( y < 0 ) + yPos = (1.0 + y) * ScreenHeight - height; // Alight bottom + else // align top + yPos = y * ScreenHeight; + } + + if ( yPos + height > ScreenHeight ) + yPos = ScreenHeight - height; + else if ( yPos < 0 ) + yPos = 0; + + return yPos; +} + + +void CHudMessage::MessageScanNextChar( void ) +{ + int srcRed, srcGreen, srcBlue, destRed, destGreen, destBlue; + int blend; + + srcRed = m_parms.pMessage->r1; + srcGreen = m_parms.pMessage->g1; + srcBlue = m_parms.pMessage->b1; + blend = 0; // Pure source + + switch( m_parms.pMessage->effect ) + { + // Fade-in / Fade-out + case 0: + case 1: + destRed = destGreen = destBlue = 0; + blend = m_parms.fadeBlend; + break; + + case 2: + m_parms.charTime += m_parms.pMessage->fadein; + if ( m_parms.charTime > m_parms.time ) + { + srcRed = srcGreen = srcBlue = 0; + blend = 0; // pure source + } + else + { + float deltaTime = m_parms.time - m_parms.charTime; + + destRed = destGreen = destBlue = 0; + if ( m_parms.time > m_parms.fadeTime ) + { + blend = m_parms.fadeBlend; + } + else if ( deltaTime > m_parms.pMessage->fxtime ) + blend = 0; // pure dest + else + { + destRed = m_parms.pMessage->r2; + destGreen = m_parms.pMessage->g2; + destBlue = m_parms.pMessage->b2; + blend = 255 - (deltaTime * (1.0/m_parms.pMessage->fxtime) * 255.0 + 0.5); + } + } + break; + } + if ( blend > 255 ) + blend = 255; + else if ( blend < 0 ) + blend = 0; + + m_parms.r = ((srcRed * (255-blend)) + (destRed * blend)) >> 8; + m_parms.g = ((srcGreen * (255-blend)) + (destGreen * blend)) >> 8; + m_parms.b = ((srcBlue * (255-blend)) + (destBlue * blend)) >> 8; + + if ( m_parms.pMessage->effect == 1 && m_parms.charTime != 0 ) + { + if ( m_parms.x >= 0 && m_parms.y >= 0 && (m_parms.x + gHUD.m_scrinfo.charWidths[ m_parms.text ]) <= ScreenWidth ) + TextMessageDrawChar( m_parms.x, m_parms.y, m_parms.text, m_parms.pMessage->r2, m_parms.pMessage->g2, m_parms.pMessage->b2 ); + } +} + + +void CHudMessage::MessageScanStart( void ) +{ + switch( m_parms.pMessage->effect ) + { + // Fade-in / out with flicker + case 1: + case 0: + m_parms.fadeTime = m_parms.pMessage->fadein + m_parms.pMessage->holdtime; + + + if ( m_parms.time < m_parms.pMessage->fadein ) + { + m_parms.fadeBlend = ((m_parms.pMessage->fadein - m_parms.time) * (1.0/m_parms.pMessage->fadein) * 255); + } + else if ( m_parms.time > m_parms.fadeTime ) + { + if ( m_parms.pMessage->fadeout > 0 ) + m_parms.fadeBlend = (((m_parms.time - m_parms.fadeTime) / m_parms.pMessage->fadeout) * 255); + else + m_parms.fadeBlend = 255; // Pure dest (off) + } + else + m_parms.fadeBlend = 0; // Pure source (on) + m_parms.charTime = 0; + + if ( m_parms.pMessage->effect == 1 && (rand()%100) < 10 ) + m_parms.charTime = 1; + break; + + case 2: + m_parms.fadeTime = (m_parms.pMessage->fadein * m_parms.length) + m_parms.pMessage->holdtime; + + if ( m_parms.time > m_parms.fadeTime && m_parms.pMessage->fadeout > 0 ) + m_parms.fadeBlend = (((m_parms.time - m_parms.fadeTime) / m_parms.pMessage->fadeout) * 255); + else + m_parms.fadeBlend = 0; + break; + } +} + + +void CHudMessage::MessageDrawScan( client_textmessage_t *pMessage, float time ) +{ + int i, j, length, width; + const char *pText; + unsigned char line[80]; + + pText = pMessage->pMessage; + // Count lines + m_parms.lines = 1; + m_parms.time = time; + m_parms.pMessage = pMessage; + length = 0; + width = 0; + m_parms.totalWidth = 0; + while ( *pText ) + { + if ( *pText == '\n' ) + { + m_parms.lines++; + if ( width > m_parms.totalWidth ) + m_parms.totalWidth = width; + width = 0; + } + else + width += gHUD.m_scrinfo.charWidths[*pText]; + pText++; + length++; + } + m_parms.length = length; + m_parms.totalHeight = (m_parms.lines * gHUD.m_scrinfo.iCharHeight); + + + m_parms.y = YPosition( pMessage->y, m_parms.totalHeight ); + pText = pMessage->pMessage; + + m_parms.charTime = 0; + + MessageScanStart(); + + for ( i = 0; i < m_parms.lines; i++ ) + { + m_parms.lineLength = 0; + m_parms.width = 0; + while ( *pText && *pText != '\n' ) + { + unsigned char c = *pText; + line[m_parms.lineLength] = c; + m_parms.width += gHUD.m_scrinfo.charWidths[c]; + m_parms.lineLength++; + pText++; + } + pText++; // Skip LF + line[m_parms.lineLength] = 0; + + m_parms.x = XPosition( pMessage->x, m_parms.width, m_parms.totalWidth ); + + for ( j = 0; j < m_parms.lineLength; j++ ) + { + m_parms.text = line[j]; + int next = m_parms.x + gHUD.m_scrinfo.charWidths[ m_parms.text ]; + MessageScanNextChar(); + + if ( m_parms.x >= 0 && m_parms.y >= 0 && next <= ScreenWidth ) + TextMessageDrawChar( m_parms.x, m_parms.y, m_parms.text, m_parms.r, m_parms.g, m_parms.b ); + m_parms.x = next; + } + + m_parms.y += gHUD.m_scrinfo.iCharHeight; + } +} + + +int CHudMessage::Draw( float fTime ) +{ + int i, drawn; + client_textmessage_t *pMessage; + float endTime; + + drawn = 0; + + if ( m_gameTitleTime > 0 ) + { + float localTime = gHUD.m_flTime - m_gameTitleTime; + float brightness; + + // Maybe timer isn't set yet + if ( m_gameTitleTime > gHUD.m_flTime ) + m_gameTitleTime = gHUD.m_flTime; + + if ( localTime > (m_pGameTitle->fadein + m_pGameTitle->holdtime + m_pGameTitle->fadeout) ) + m_gameTitleTime = 0; + else + { + brightness = FadeBlend( m_pGameTitle->fadein, m_pGameTitle->fadeout, m_pGameTitle->holdtime, localTime ); + + int halfWidth = gHUD.GetSpriteRect(m_HUD_title_half).right - gHUD.GetSpriteRect(m_HUD_title_half).left; + int fullWidth = halfWidth + gHUD.GetSpriteRect(m_HUD_title_life).right - gHUD.GetSpriteRect(m_HUD_title_life).left; + int fullHeight = gHUD.GetSpriteRect(m_HUD_title_half).bottom - gHUD.GetSpriteRect(m_HUD_title_half).top; + + int x = XPosition( m_pGameTitle->x, fullWidth, fullWidth ); + int y = YPosition( m_pGameTitle->y, fullHeight ); + + + SPR_Set( gHUD.GetSprite(m_HUD_title_half), brightness * m_pGameTitle->r1, brightness * m_pGameTitle->g1, brightness * m_pGameTitle->b1 ); + SPR_DrawAdditive( 0, x, y, &gHUD.GetSpriteRect(m_HUD_title_half) ); + + SPR_Set( gHUD.GetSprite(m_HUD_title_life), brightness * m_pGameTitle->r1, brightness * m_pGameTitle->g1, brightness * m_pGameTitle->b1 ); + SPR_DrawAdditive( 0, x + halfWidth, y, &gHUD.GetSpriteRect(m_HUD_title_life) ); + + drawn = 1; + } + } + // Fixup level transitions + for ( i = 0; i < maxHUDMessages; i++ ) + { + // Assume m_parms.time contains last time + if ( m_pMessages[i] ) + { + pMessage = m_pMessages[i]; + if ( m_startTime[i] > gHUD.m_flTime ) + m_startTime[i] = gHUD.m_flTime + m_parms.time - m_startTime[i] + 0.2; // Server takes 0.2 seconds to spawn, adjust for this + } + } + + for ( i = 0; i < maxHUDMessages; i++ ) + { + if ( m_pMessages[i] ) + { + pMessage = m_pMessages[i]; + + // This is when the message is over + switch( pMessage->effect ) + { + case 0: + case 1: + endTime = m_startTime[i] + pMessage->fadein + pMessage->fadeout + pMessage->holdtime; + break; + + // Fade in is per character in scanning messages + case 2: + endTime = m_startTime[i] + (pMessage->fadein * strlen( pMessage->pMessage )) + pMessage->fadeout + pMessage->holdtime; + break; + } + + if ( fTime <= endTime ) + { + float messageTime = fTime - m_startTime[i]; + + // Draw the message + // effect 0 is fade in/fade out + // effect 1 is flickery credits + // effect 2 is write out (training room) + MessageDrawScan( pMessage, messageTime ); + + drawn++; + } + else + { + // The message is over + m_pMessages[i] = NULL; + } + } + } + + // Remember the time -- to fix up level transitions + m_parms.time = gHUD.m_flTime; + // Don't call until we get another message + if ( !drawn ) + m_iFlags &= ~HUD_ACTIVE; + + return 1; +} + + +void CHudMessage::MessageAdd( const char *pName, float time ) +{ + int i; + + for ( i = 0; i < maxHUDMessages; i++ ) + { + if ( !m_pMessages[i] ) + { + m_pMessages[i] = TextMessageGet( pName ); + m_startTime[i] = time; + return; + } + } +} + + +int CHudMessage::MsgFunc_HudText( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + char *pString = READ_STRING(); + + MessageAdd( pString, gHUD.m_flTime ); + // Remember the time -- to fix up level transitions + m_parms.time = gHUD.m_flTime; + + // Turn on drawing + if ( !(m_iFlags & HUD_ACTIVE) ) + m_iFlags |= HUD_ACTIVE; + + return 1; +} + + +int CHudMessage::MsgFunc_GameTitle( const char *pszName, int iSize, void *pbuf ) +{ + m_pGameTitle = TextMessageGet( "GAMETITLE" ); + if ( m_pGameTitle != NULL ) + { + m_gameTitleTime = gHUD.m_flTime; + + // Turn on drawing + if ( !(m_iFlags & HUD_ACTIVE) ) + m_iFlags |= HUD_ACTIVE; + } + + return 1; +} diff --git a/ricochet/cl_dll/readme.txt b/ricochet/cl_dll/readme.txt new file mode 100644 index 0000000..249205c --- /dev/null +++ b/ricochet/cl_dll/readme.txt @@ -0,0 +1,107 @@ + client dll readme.txt +------------------------- + +This file details the structure of the half-life client dll, and +how it communicates with the half-life game engine. + + +Engine callback functions: + +Drawing functions: + HSPRITE SPR_Load( char *picname ); + Loads a sprite into memory, and returns a handle to it. + + int SPR_Frames( HSPRITE sprite ); + Returns the number of frames stored in the specified sprite. + + int SPR_Height( HSPRITE x, int frame ) + Returns the height, in pixels, of a sprite at the specified frame. + Returns 0 is the frame number or the sprite handle is invalid. + + int SPR_Width( HSPRITE x, int f ) + Returns the width, in pixels, of a sprite at the specified frame. + Returns 0 is the frame number or the sprite handle is invalid. + + int SPR_Set( HSPRITE sprite, int r, int g, int b ); + Prepares a sprite about to be drawn. RBG color values are applied to the sprite at this time. + + + void SPR_Draw( int frame, int x, int y ); + Precondition: SPR_Set has already been called for a sprite. + Draws the currently active sprite to the screen, at position (x,y), where (0,0) is + the top left-hand corner of the screen. + + + void SPR_DrawHoles( int frame, int x, int y ); + Precondition: SPR_Set has already been called for a sprite. + Draws the currently active sprite to the screen. Color index #255 is treated as transparent. + + void SPR_DrawAdditive( int frame, int x, int y ); + Precondition: SPR_Set has already been called for a sprite. + Draws the currently active sprite to the screen, adding it's color values to the background. + + void SPR_EnableScissor( int x, int y, int width, int height ); + Creates a clipping rectangle. No pixels will be drawn outside the specified area. Will + stay in effect until either the next frame, or SPR_DisableScissor is called. + + void SPR_DisableScissor( void ); + Disables the effect of an SPR_EnableScissor call. + + int IsHighRes( void ); + returns 1 if the res mode is 640x480 or higher; 0 otherwise. + + int ScreenWidth( void ); + returns the screen width, in pixels. + + int ScreenHeight( void ); + returns the screen height, in pixels. + +// Sound functions + void PlaySound( char *szSound, int volume ) + plays the sound 'szSound' at the specified volume. Loads the sound if it hasn't been cached. + If it can't find the sound, it displays an error message and plays no sound. + + void PlaySound( int iSound, int volume ) + Precondition: iSound has been precached. + Plays the sound, from the precache list. + + +// Communication functions + void SendClientCmd( char *szCmdString ); + sends a command to the server, just as if the client had typed the szCmdString at the console. + + char *GetPlayerName( int entity_number ); + returns a pointer to a string, that contains the name of the specified client. + Returns NULL if the entity_number is not a client. + + + DECLARE_MESSAGE(), HOOK_MESSAGE() + These two macros bind the message sending between the entity DLL and the client DLL to + the CHud object. + + HOOK_MESSAGE( message_name ) + This is used inside CHud::Init(). It calls into the engine to hook that message + from the incoming message stream. + Precondition: There must be a function of name UserMsg_message_name declared + for CHud. Eg, CHud::UserMsg_Health() must be declared if you want to + use HOOK_MESSAGE( Health ); + + DECLARE_MESSAGE( message_name ) + For each HOOK_MESSAGE you must have an equivalent DECLARE_MESSAGE. This creates + a function which passes the hooked messages into the CHud object. + + + HOOK_COMMAND(), DECLARE_COMMAND() + These two functions declare and hook console commands into the client dll. + + HOOK_COMMAND( char *command, command_name ) + Whenever the user types the 'command' at the console, the function 'command_name' + will be called. + Precondition: There must be a function of the name UserCmd_command_name declared + for CHud. Eg, CHud::UserMsg_ShowScores() must be declared if you want to + use HOOK_COMMAND( "+showscores", ShowScores ); + + DECLARE_COMMAND( command_name ) + For each HOOK_COMMAND you must have an equivelant DECLARE_COMMAND. This creates + a function which passes the hooked commands into the CHud object. + diff --git a/ricochet/cl_dll/saytext.cpp b/ricochet/cl_dll/saytext.cpp new file mode 100644 index 0000000..00e043c --- /dev/null +++ b/ricochet/cl_dll/saytext.cpp @@ -0,0 +1,308 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// saytext.cpp +// +// implementation of CHudSayText class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +extern float *GetClientColor( int clientIndex ); + +#define MAX_LINES 5 +#define MAX_CHARS_PER_LINE 256 /* it can be less than this, depending on char size */ + +// allow 20 pixels on either side of the text +#define MAX_LINE_WIDTH ( ScreenWidth - 40 ) +#define LINE_START 10 +static float SCROLL_SPEED = 5; + +static char g_szLineBuffer[ MAX_LINES + 1 ][ MAX_CHARS_PER_LINE ]; +static float *g_pflNameColors[ MAX_LINES + 1 ]; +static int g_iNameLengths[ MAX_LINES + 1 ]; +static float flScrollTime = 0; // the time at which the lines next scroll up + +static int Y_START = 0; +static int line_height = 0; + +DECLARE_MESSAGE( m_SayText, SayText ); + +int CHudSayText :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( SayText ); + + InitHUDData(); + + CVAR_CREATE( "hud_saytext_time", "5", 0 ); + + return 1; +} + + +void CHudSayText :: InitHUDData( void ) +{ + memset( g_szLineBuffer, 0, sizeof g_szLineBuffer ); + memset( g_pflNameColors, 0, sizeof g_pflNameColors ); + memset( g_iNameLengths, 0, sizeof g_iNameLengths ); +} + +int CHudSayText :: VidInit( void ) +{ + return 1; +} + + +int ScrollTextUp( void ) +{ + ConsolePrint( g_szLineBuffer[0] ); // move the first line into the console buffer + g_szLineBuffer[MAX_LINES][0] = 0; + memmove( g_szLineBuffer[0], g_szLineBuffer[1], sizeof(g_szLineBuffer) - sizeof(g_szLineBuffer[0]) ); // overwrite the first line + memmove( &g_pflNameColors[0], &g_pflNameColors[1], sizeof(g_pflNameColors) - sizeof(g_pflNameColors[0]) ); + memmove( &g_iNameLengths[0], &g_iNameLengths[1], sizeof(g_iNameLengths) - sizeof(g_iNameLengths[0]) ); + g_szLineBuffer[MAX_LINES-1][0] = 0; + + if ( g_szLineBuffer[0][0] == ' ' ) // also scroll up following lines + { + g_szLineBuffer[0][0] = 2; + return 1 + ScrollTextUp(); + } + + return 1; +} + +int CHudSayText :: Draw( float flTime ) +{ + int y = Y_START; + + // make sure the scrolltime is within reasonable bounds, to guard against the clock being reset + flScrollTime = min( flScrollTime, flTime + SCROLL_SPEED ); + + // make sure the scrolltime is within reasonable bounds, to guard against the clock being reset + flScrollTime = min( flScrollTime, flTime + SCROLL_SPEED ); + + if ( flScrollTime <= flTime ) + { + if ( *g_szLineBuffer[0] ) + { + flScrollTime = flTime + SCROLL_SPEED; + // push the console up + ScrollTextUp(); + } + else + { // buffer is empty, just disable drawing of this section + m_iFlags &= ~HUD_ACTIVE; + } + } + + for ( int i = 0; i < MAX_LINES; i++ ) + { + if ( *g_szLineBuffer[i] ) + { + if ( *g_szLineBuffer[i] == 2 && g_pflNameColors[i] ) + { + // it's a saytext string + static char buf[MAX_PLAYER_NAME_LENGTH+32]; + + // draw the first x characters in the player color + strncpy( buf, g_szLineBuffer[i], min(g_iNameLengths[i], MAX_PLAYER_NAME_LENGTH+32) ); + buf[ min(g_iNameLengths[i], MAX_PLAYER_NAME_LENGTH+31) ] = 0; + gEngfuncs.pfnDrawSetTextColor( g_pflNameColors[i][0], g_pflNameColors[i][1], g_pflNameColors[i][2] ); + int x = DrawConsoleString( LINE_START, y, buf ); + + // color is reset after each string draw + DrawConsoleString( x, y, g_szLineBuffer[i] + g_iNameLengths[i] ); + } + else + { + // normal draw + DrawConsoleString( LINE_START, y, g_szLineBuffer[i] ); + } + } + + y += line_height; + } + + + return 1; +} + +int CHudSayText :: MsgFunc_SayText( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int client_index = READ_BYTE(); // the client who spoke the message + SayTextPrint( READ_STRING(), iSize - 1, client_index ); + + return 1; +} + +void CHudSayText :: SayTextPrint( const char *pszBuf, int iBufSize, int clientIndex ) +{ + // find an empty string slot + int i; + for ( i = 0; i < MAX_LINES; i++ ) + { + if ( ! *g_szLineBuffer[i] ) + break; + } + if ( i == MAX_LINES ) + { + // force scroll buffer up + ScrollTextUp(); + i = MAX_LINES - 1; + } + + g_iNameLengths[i] = 0; + g_pflNameColors[i] = NULL; + + // if it's a say message, search for the players name in the string + if ( *pszBuf == 2 && clientIndex > 0 ) + { + GetPlayerInfo( clientIndex, &g_PlayerInfoList[clientIndex] ); + const char *pName = g_PlayerInfoList[clientIndex].name; + + if ( pName ) + { + const char *nameInString = strstr( pszBuf, pName ); + + if ( nameInString ) + { + g_iNameLengths[i] = strlen( pName ) + (nameInString - pszBuf); + g_pflNameColors[i] = GetClientColor( clientIndex ); + } + } + } + + strncpy( g_szLineBuffer[i], pszBuf, max(iBufSize -1, MAX_CHARS_PER_LINE-1) ); + + // make sure the text fits in one line + EnsureTextFitsInOneLineAndWrapIfHaveTo( i ); + + // Set scroll time + if ( i == 0 ) + { + SCROLL_SPEED = CVAR_GET_FLOAT( "hud_saytext_time" ); + flScrollTime = gHUD.m_flTime + SCROLL_SPEED; + } + + m_iFlags |= HUD_ACTIVE; + PlaySound( "misc/talk.wav", 1 ); + + if ( ScreenHeight >= 480 ) + Y_START = ScreenHeight - 45; + else + Y_START = ScreenHeight - 35; + Y_START -= (line_height * (MAX_LINES+1)); + +} + +void CHudSayText :: EnsureTextFitsInOneLineAndWrapIfHaveTo( int line ) +{ + int line_width = 0; + GetConsoleStringSize( g_szLineBuffer[line], &line_width, &line_height ); + + if ( (line_width + LINE_START) > MAX_LINE_WIDTH ) + { // string is too long to fit on line + // scan the string until we find what word is too long, and wrap the end of the sentence after the word + int length = LINE_START; + int tmp_len = 0; + char *last_break = NULL; + for ( char *x = g_szLineBuffer[line]; *x != 0; x++ ) + { + // check for a color change, if so skip past it + if ( x[0] == '/' && x[1] == '(' ) + { + x += 2; + // skip forward until past mode specifier + while ( *x != 0 && *x != ')' ) + x++; + + if ( *x != 0 ) + x++; + + if ( *x == 0 ) + break; + } + + char buf[2]; + buf[1] = 0; + + if ( *x == ' ' && x != g_szLineBuffer[line] ) // store each line break, except for the very first character + last_break = x; + + buf[0] = *x; // get the length of the current character + GetConsoleStringSize( buf, &tmp_len, &line_height ); + length += tmp_len; + + if ( length > MAX_LINE_WIDTH ) + { // needs to be broken up + if ( !last_break ) + last_break = x-1; + + x = last_break; + + // find an empty string slot + int j; + do + { + for ( j = 0; j < MAX_LINES; j++ ) + { + if ( ! *g_szLineBuffer[j] ) + break; + } + if ( j == MAX_LINES ) + { + // need to make more room to display text, scroll stuff up then fix the pointers + int linesmoved = ScrollTextUp(); + line -= linesmoved; + last_break = last_break - (sizeof(g_szLineBuffer[0]) * linesmoved); + } + } + while ( j == MAX_LINES ); + + // copy remaining string into next buffer, making sure it starts with a space character + if ( (char)*last_break == (char)' ' ) + { + int linelen = strlen(g_szLineBuffer[j]); + int remaininglen = strlen(last_break); + + if ( (linelen - remaininglen) <= MAX_CHARS_PER_LINE ) + strcat( g_szLineBuffer[j], last_break ); + } + else + { + if ( (strlen(g_szLineBuffer[j]) - strlen(last_break) - 2) < MAX_CHARS_PER_LINE ) + { + strcat( g_szLineBuffer[j], " " ); + strcat( g_szLineBuffer[j], last_break ); + } + } + + *last_break = 0; // cut off the last string + + EnsureTextFitsInOneLineAndWrapIfHaveTo( j ); + break; + } + } + } +} \ No newline at end of file diff --git a/ricochet/cl_dll/status_icons.cpp b/ricochet/cl_dll/status_icons.cpp new file mode 100644 index 0000000..bf0b298 --- /dev/null +++ b/ricochet/cl_dll/status_icons.cpp @@ -0,0 +1,150 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// status_icons.cpp +// +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE( m_StatusIcons, StatusIcon ); + +int CHudStatusIcons::Init( void ) +{ + HOOK_MESSAGE( StatusIcon ); + + gHUD.AddHudElem( this ); + + Reset(); + + return 1; +} + +int CHudStatusIcons::VidInit( void ) +{ + + return 1; +} + +void CHudStatusIcons::Reset( void ) +{ + memset( m_IconList, 0, sizeof m_IconList ); + m_iFlags &= ~HUD_ACTIVE; +} + +// Draw status icons along the left-hand side of the screen +int CHudStatusIcons::Draw( float flTime ) +{ + // find starting position to draw from, along right-hand side of screen + int x = 5; + int y = ScreenHeight / 2; + + // loop through icon list, and draw any valid icons drawing up from the middle of screen + for ( int i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( m_IconList[i].spr ) + { + y -= ( m_IconList[i].rc.bottom - m_IconList[i].rc.top ) + 5; + + SPR_Set( m_IconList[i].spr, m_IconList[i].r, m_IconList[i].g, m_IconList[i].b ); + SPR_DrawAdditive( 0, x, y, &m_IconList[i].rc ); + } + } + + return 1; +} + +// Message handler for StatusIcon message +// accepts five values: +// byte : TRUE = ENABLE icon, FALSE = DISABLE icon +// string : the sprite name to display +// byte : red +// byte : green +// byte : blue +int CHudStatusIcons::MsgFunc_StatusIcon( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int ShouldEnable = READ_BYTE(); + char *pszIconName = READ_STRING(); + if ( ShouldEnable ) + { + int r = READ_BYTE(); + int g = READ_BYTE(); + int b = READ_BYTE(); + EnableIcon( pszIconName, r, g, b ); + m_iFlags |= HUD_ACTIVE; + } + else + { + DisableIcon( pszIconName ); + } + + return 1; +} + +// add the icon to the icon list, and set it's drawing color +void CHudStatusIcons::EnableIcon( char *pszIconName, unsigned char red, unsigned char green, unsigned char blue ) +{ + // check to see if the sprite is in the current list + int i; + for ( i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( !stricmp( m_IconList[i].szSpriteName, pszIconName ) ) + break; + } + + if ( i == MAX_ICONSPRITES ) + { + // icon not in list, so find an empty slot to add to + for ( i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( !m_IconList[i].spr ) + break; + } + } + + // if we've run out of space in the list, overwrite the first icon + if ( i == MAX_ICONSPRITES ) + { + i = 0; + } + + // Load the sprite and add it to the list + // the sprite must be listed in hud.txt + int spr_index = gHUD.GetSpriteIndex( pszIconName ); + m_IconList[i].spr = gHUD.GetSprite( spr_index ); + m_IconList[i].rc = gHUD.GetSpriteRect( spr_index ); + m_IconList[i].r = red; + m_IconList[i].g = green; + m_IconList[i].b = blue; + strcpy( m_IconList[i].szSpriteName, pszIconName ); +} + +void CHudStatusIcons::DisableIcon( char *pszIconName ) +{ + // find the sprite is in the current list + for ( int i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( !stricmp( m_IconList[i].szSpriteName, pszIconName ) ) + { + // clear the item from the list + memset( &m_IconList[i], 0, sizeof(icon_sprite_t) ); + return; + } + } +} diff --git a/ricochet/cl_dll/statusbar.cpp b/ricochet/cl_dll/statusbar.cpp new file mode 100644 index 0000000..41b8de9 --- /dev/null +++ b/ricochet/cl_dll/statusbar.cpp @@ -0,0 +1,252 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// statusbar.cpp +// +// generic text status bar, set by game dll +// runs across bottom of screen +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +DECLARE_MESSAGE( m_StatusBar, StatusText ); +DECLARE_MESSAGE( m_StatusBar, StatusValue ); + +#define STATUSBAR_ID_LINE 1 + +int CHudStatusBar :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( StatusText ); + HOOK_MESSAGE( StatusValue ); + + Reset(); + + CVAR_CREATE( "hud_centerid", "0", FCVAR_ARCHIVE ); + + return 1; +} + +int CHudStatusBar :: VidInit( void ) +{ + // Load sprites here + + return 1; +} + +void CHudStatusBar :: Reset( void ) +{ + m_iFlags &= ~HUD_ACTIVE; // start out inactive + for ( int i = 0; i < MAX_STATUSBAR_LINES; i++ ) + m_szStatusText[i][0] = 0; + memset( m_iStatusValues, 0, sizeof m_iStatusValues ); + + m_iStatusValues[0] = 1; // 0 is the special index, which always returns true +} + +void CHudStatusBar :: ParseStatusString( int line_num ) +{ + // localise string first + char szBuffer[MAX_STATUSTEXT_LENGTH]; + memset( szBuffer, 0, sizeof szBuffer ); + gHUD.m_TextMessage.LocaliseTextString( m_szStatusText[line_num], szBuffer, MAX_STATUSTEXT_LENGTH ); + + // parse m_szStatusText & m_iStatusValues into m_szStatusBar + memset( m_szStatusBar[line_num], 0, MAX_STATUSTEXT_LENGTH ); + char *src = szBuffer; + char *dst = m_szStatusBar[line_num]; + + char *src_start = src, *dst_start = dst; + + while ( *src != 0 ) + { + while ( *src == '\n' ) + src++; // skip over any newlines + + if ( ((src - src_start) >= MAX_STATUSTEXT_LENGTH) || ((dst - dst_start) >= MAX_STATUSTEXT_LENGTH) ) + break; + + int index = atoi( src ); + // should we draw this line? + if ( (index >= 0 && index < MAX_STATUSBAR_VALUES) && (m_iStatusValues[index] != 0) ) + { // parse this line and append result to the status bar + while ( *src >= '0' && *src <= '9' ) + src++; + + if ( *src == '\n' || *src == 0 ) + continue; // no more left in this text line + + // copy the text, char by char, until we hit a % or a \n + while ( *src != '\n' && *src != 0 ) + { + if ( *src != '%' ) + { // just copy the character + *dst = *src; + dst++, src++; + } + else + { + // get the descriptor + char valtype = *(++src); // move over % + + // if it's a %, draw a % sign + if ( valtype == '%' ) + { + *dst = valtype; + dst++, src++; + continue; + } + + // move over descriptor, then get and move over the index + index = atoi( ++src ); + while ( *src >= '0' && *src <= '9' ) + src++; + + if ( index >= 0 && index < MAX_STATUSBAR_VALUES ) + { + int indexval = m_iStatusValues[index]; + + // get the string to substitute in place of the %XX + char szRepString[MAX_PLAYER_NAME_LENGTH]; + switch ( valtype ) + { + case 'p': // player name + GetPlayerInfo( indexval, &g_PlayerInfoList[indexval] ); + if ( g_PlayerInfoList[indexval].name != NULL ) + { + strncpy( szRepString, g_PlayerInfoList[indexval].name, MAX_PLAYER_NAME_LENGTH ); + } + else + { + strcpy( szRepString, "******" ); + } + break; + case 'i': // number + sprintf( szRepString, "%d", indexval ); + break; + default: + szRepString[0] = 0; + } + + for ( char *cp = szRepString; *cp != 0 && ((dst - dst_start) < MAX_STATUSTEXT_LENGTH); cp++, dst++ ) + *dst = *cp; + } + } + } + } + else + { + // skip to next line of text + while ( *src != 0 && *src != '\n' ) + src++; + } + } +} + +int CHudStatusBar :: Draw( float fTime ) +{ + if ( m_bReparseString ) + { + for ( int i = 0; i < MAX_STATUSBAR_LINES; i++ ) + ParseStatusString( i ); + m_bReparseString = FALSE; + } + + // Draw the status bar lines + for ( int i = 0; i < MAX_STATUSBAR_LINES; i++ ) + { + int TextHeight, TextWidth; + GetConsoleStringSize( m_szStatusBar[i], &TextWidth, &TextHeight ); + + int Y_START; + if ( ScreenHeight >= 480 ) + Y_START = ScreenHeight - 45; + else + Y_START = ScreenHeight - 35; + + int x = 5; + int y = Y_START - ( TextHeight * i ); // draw along bottom of screen + + // let user set status ID bar centering + if ( (i == STATUSBAR_ID_LINE) && CVAR_GET_FLOAT("hud_centerid") ) + { + x = max( 0, max(2, (ScreenWidth - TextWidth)) / 2 ); + y = (ScreenHeight / 2) + (TextHeight*CVAR_GET_FLOAT("hud_centerid")); + } + + DrawConsoleString( x, y, m_szStatusBar[i] ); + } + + return 1; +} + +// Message handler for StatusText message +// accepts two values: +// byte: line number of status bar text +// string: status bar text +// this string describes how the status bar should be drawn +// a semi-regular expression: +// ( slotnum ([a..z] [%pX] [%iX])*)* +// where slotnum is an index into the Value table (see below) +// if slotnum is 0, the string is always drawn +// if StatusValue[slotnum] != 0, the following string is drawn, upto the next newline - otherwise the text is skipped upto next newline +// %pX, where X is an integer, will substitute a player name here, getting the player index from StatusValue[X] +// %iX, where X is an integer, will substitute a number here, getting the number from StatusValue[X] +int CHudStatusBar :: MsgFunc_StatusText( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int line = READ_BYTE(); + + if ( line < 0 || line >= MAX_STATUSBAR_LINES ) + return 1; + + strncpy( m_szStatusText[line], READ_STRING(), MAX_STATUSTEXT_LENGTH ); + m_szStatusText[line][MAX_STATUSTEXT_LENGTH-1] = 0; // ensure it's null terminated ( strncpy() won't null terminate if read string too long) + + if ( m_szStatusText[0] == 0 ) + m_iFlags &= ~HUD_ACTIVE; + else + m_iFlags |= HUD_ACTIVE; // we have status text, so turn on the status bar + + m_bReparseString = TRUE; + + return 1; +} + +// Message handler for StatusText message +// accepts two values: +// byte: index into the status value array +// short: value to store +int CHudStatusBar :: MsgFunc_StatusValue( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int index = READ_BYTE(); + if ( index < 1 || index >= MAX_STATUSBAR_VALUES ) + return 1; // index out of range + + m_iStatusValues[index] = READ_SHORT(); + + m_bReparseString = TRUE; + + return 1; +} \ No newline at end of file diff --git a/ricochet/cl_dll/studio_util.cpp b/ricochet/cl_dll/studio_util.cpp new file mode 100644 index 0000000..71a0cfd --- /dev/null +++ b/ricochet/cl_dll/studio_util.cpp @@ -0,0 +1,244 @@ +#include +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include "studio_util.h" + +/* +==================== +AngleMatrix + +==================== +*/ +void AngleMatrix (const float *angles, float (*matrix)[4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + // matrix = (YAW * PITCH) * ROLL + matrix[0][0] = cp*cy; + matrix[1][0] = cp*sy; + matrix[2][0] = -sp; + matrix[0][1] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[2][1] = sr*cp; + matrix[0][2] = (cr*sp*cy+-sr*-sy); + matrix[1][2] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + +/* +==================== +VectorCompare + +==================== +*/ +int VectorCompare (const float *v1, const float *v2) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (v1[i] != v2[i]) + return 0; + + return 1; +} + +/* +==================== +CrossProduct + +==================== +*/ +void CrossProduct (const float *v1, const float *v2, float *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]; +} + +/* +==================== +VectorTransform + +==================== +*/ +void VectorTransform (const float *in1, float in2[3][4], float *out) +{ + out[0] = DotProduct(in1, in2[0]) + in2[0][3]; + out[1] = DotProduct(in1, in2[1]) + in2[1][3]; + out[2] = DotProduct(in1, in2[2]) + in2[2][3]; +} + +/* +================ +ConcatTransforms + +================ +*/ +void ConcatTransforms (float in1[3][4], 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]; +} + +// angles index are not the same as ROLL, PITCH, YAW + +/* +==================== +AngleQuaternion + +==================== +*/ +void AngleQuaternion( float *angles, vec4_t quaternion ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + // FIXME: rescale the inputs to 1/2 angle + angle = angles[2] * 0.5; + sy = sin(angle); + cy = cos(angle); + angle = angles[1] * 0.5; + sp = sin(angle); + cp = cos(angle); + angle = angles[0] * 0.5; + sr = sin(angle); + cr = cos(angle); + + quaternion[0] = sr*cp*cy-cr*sp*sy; // X + quaternion[1] = cr*sp*cy+sr*cp*sy; // Y + quaternion[2] = cr*cp*sy-sr*sp*cy; // Z + quaternion[3] = cr*cp*cy+sr*sp*sy; // W +} + +/* +==================== +QuaternionSlerp + +==================== +*/ +void QuaternionSlerp( vec4_t p, vec4_t q, float t, vec4_t qt ) +{ + int i; + float omega, cosom, sinom, sclp, sclq; + + // decide if one of the quaternions is backwards + float a = 0; + float b = 0; + + for (i = 0; i < 4; i++) + { + a += (p[i]-q[i])*(p[i]-q[i]); + b += (p[i]+q[i])*(p[i]+q[i]); + } + if (a > b) + { + for (i = 0; i < 4; i++) + { + q[i] = -q[i]; + } + } + + cosom = p[0]*q[0] + p[1]*q[1] + p[2]*q[2] + p[3]*q[3]; + + if ((1.0 + cosom) > 0.000001) + { + if ((1.0 - cosom) > 0.000001) + { + omega = acos( cosom ); + sinom = sin( omega ); + sclp = sin( (1.0 - t)*omega) / sinom; + sclq = sin( t*omega ) / sinom; + } + else + { + sclp = 1.0 - t; + sclq = t; + } + for (i = 0; i < 4; i++) { + qt[i] = sclp * p[i] + sclq * q[i]; + } + } + else + { + qt[0] = -q[1]; + qt[1] = q[0]; + qt[2] = -q[3]; + qt[3] = q[2]; + sclp = sin( (1.0 - t) * (0.5 * M_PI)); + sclq = sin( t * (0.5 * M_PI)); + for (i = 0; i < 3; i++) + { + qt[i] = sclp * p[i] + sclq * qt[i]; + } + } +} + +/* +==================== +QuaternionMatrix + +==================== +*/ +void QuaternionMatrix( vec4_t quaternion, float (*matrix)[4] ) +{ + matrix[0][0] = 1.0 - 2.0 * quaternion[1] * quaternion[1] - 2.0 * quaternion[2] * quaternion[2]; + matrix[1][0] = 2.0 * quaternion[0] * quaternion[1] + 2.0 * quaternion[3] * quaternion[2]; + matrix[2][0] = 2.0 * quaternion[0] * quaternion[2] - 2.0 * quaternion[3] * quaternion[1]; + + matrix[0][1] = 2.0 * quaternion[0] * quaternion[1] - 2.0 * quaternion[3] * quaternion[2]; + matrix[1][1] = 1.0 - 2.0 * quaternion[0] * quaternion[0] - 2.0 * quaternion[2] * quaternion[2]; + matrix[2][1] = 2.0 * quaternion[1] * quaternion[2] + 2.0 * quaternion[3] * quaternion[0]; + + matrix[0][2] = 2.0 * quaternion[0] * quaternion[2] + 2.0 * quaternion[3] * quaternion[1]; + matrix[1][2] = 2.0 * quaternion[1] * quaternion[2] - 2.0 * quaternion[3] * quaternion[0]; + matrix[2][2] = 1.0 - 2.0 * quaternion[0] * quaternion[0] - 2.0 * quaternion[1] * quaternion[1]; +} + +/* +==================== +MatrixCopy + +==================== +*/ +void MatrixCopy( float in[3][4], float out[3][4] ) +{ + memcpy( out, in, sizeof( float ) * 3 * 4 ); +} \ No newline at end of file diff --git a/ricochet/cl_dll/studio_util.h b/ricochet/cl_dll/studio_util.h new file mode 100644 index 0000000..49e506f --- /dev/null +++ b/ricochet/cl_dll/studio_util.h @@ -0,0 +1,33 @@ +#if !defined( STUDIO_UTIL_H ) +#define STUDIO_UTIL_H +#if defined( WIN32 ) +#pragma once +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +#ifndef PITCH +// MOVEMENT INFO +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 +#endif + +#define FDotProduct( a, b ) (fabs((a[0])*(b[0])) + fabs((a[1])*(b[1])) + fabs((a[2])*(b[2]))) + +void AngleMatrix (const float *angles, float (*matrix)[4] ); +int VectorCompare (const float *v1, const float *v2); +void CrossProduct (const float *v1, const float *v2, float *cross); +void VectorTransform (const float *in1, float in2[3][4], float *out); +void ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]); +void MatrixCopy( float in[3][4], float out[3][4] ); +void QuaternionMatrix( vec4_t quaternion, float (*matrix)[4] ); +void QuaternionSlerp( vec4_t p, vec4_t q, float t, vec4_t qt ); +void AngleQuaternion( float *angles, vec4_t quaternion ); + +#endif // STUDIO_UTIL_H \ No newline at end of file diff --git a/ricochet/cl_dll/text_message.cpp b/ricochet/cl_dll/text_message.cpp new file mode 100644 index 0000000..e2283d2 --- /dev/null +++ b/ricochet/cl_dll/text_message.cpp @@ -0,0 +1,208 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// text_message.cpp +// +// implementation of CHudTextMessage class +// +// this class routes messages through titles.txt for localisation +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE( m_TextMessage, TextMsg ); + +int CHudTextMessage::Init(void) +{ + HOOK_MESSAGE( TextMsg ); + + gHUD.AddHudElem( this ); + + Reset(); + + return 1; +}; + +// Searches through the string for any msg names (indicated by a '#') +// any found are looked up in titles.txt and the new message substituted +// the new value is pushed into dst_buffer +char *CHudTextMessage::LocaliseTextString( const char *msg, char *dst_buffer, int buffer_size ) +{ + char *dst = dst_buffer; + for ( char *src = (char*)msg; *src != 0 && buffer_size > 0; buffer_size-- ) + { + if ( *src == '#' ) + { + // cut msg name out of string + static char word_buf[255]; + char *wdst = word_buf, *word_start = src; + for ( ++src ; (*src >= 'A' && *src <= 'z') || (*src >= '0' && *src <= '9'); wdst++, src++ ) + { + *wdst = *src; + } + *wdst = 0; + + // lookup msg name in titles.txt + client_textmessage_t *clmsg = TextMessageGet( word_buf ); + if ( !clmsg || !(clmsg->pMessage) ) + { + src = word_start; + *dst = *src; + dst++, src++; + continue; + } + + // copy string into message over the msg name + for ( char *wsrc = (char*)clmsg->pMessage; *wsrc != 0; wsrc++, dst++ ) + { + *dst = *wsrc; + } + *dst = 0; + } + else + { + *dst = *src; + dst++, src++; + *dst = 0; + } + } + + dst_buffer[buffer_size-1] = 0; // ensure null termination + return dst_buffer; +} + +// As above, but with a local static buffer +char *CHudTextMessage::BufferedLocaliseTextString( const char *msg ) +{ + char dst_buffer[1024]; + return LocaliseTextString( msg, dst_buffer, 1024 ); +} + +// Simplified version of LocaliseTextString; assumes string is only one word +char *CHudTextMessage::LookupString( const char *msg, int *msg_dest ) +{ + if ( !msg ) + return ""; + + // '#' character indicates this is a reference to a string in titles.txt, and not the string itself + if ( msg[0] == '#' ) + { + // this is a message name, so look up the real message + client_textmessage_t *clmsg = TextMessageGet( msg+1 ); + + if ( !clmsg || !(clmsg->pMessage) ) + return (char*)msg; // lookup failed, so return the original string + + if ( msg_dest ) + { + // check to see if titles.txt info overrides msg destination + // if clmsg->effect is less than 0, then clmsg->effect holds -1 * message_destination + if ( clmsg->effect < 0 ) // + *msg_dest = -clmsg->effect; + } + + return (char*)clmsg->pMessage; + } + else + { // nothing special about this message, so just return the same string + return (char*)msg; + } +} + +void StripEndNewlineFromString( char *str ) +{ + int s = strlen( str ) - 1; + if ( str[s] == '\n' || str[s] == '\r' ) + str[s] = 0; +} + +// converts all '\r' characters to '\n', so that the engine can deal with the properly +// returns a pointer to str +char* ConvertCRtoNL( char *str ) +{ + for ( char *ch = str; *ch != 0; ch++ ) + if ( *ch == '\r' ) + *ch = '\n'; + return str; +} + +// Message handler for text messages +// displays a string, looking them up from the titles.txt file, which can be localised +// parameters: +// byte: message direction ( HUD_PRINTCONSOLE, HUD_PRINTNOTIFY, HUD_PRINTCENTER, HUD_PRINTTALK ) +// string: message +// optional parameters: +// string: message parameter 1 +// string: message parameter 2 +// string: message parameter 3 +// string: message parameter 4 +// any string that starts with the character '#' is a message name, and is used to look up the real message in titles.txt +// the next (optional) one to four strings are parameters for that string (which can also be message names if they begin with '#') +int CHudTextMessage::MsgFunc_TextMsg( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int msg_dest = READ_BYTE(); + +#define MSG_BUF_SIZE 128 + static char szBuf[6][MSG_BUF_SIZE]; + char *msg_text = LookupString( READ_STRING(), &msg_dest ); + msg_text = safe_strcpy( szBuf[0], msg_text, MSG_BUF_SIZE ); + + // keep reading strings and using C format strings for subsituting the strings into the localised text string + char *sstr1 = LookupString( READ_STRING() ); + sstr1 = safe_strcpy( szBuf[1], sstr1 , MSG_BUF_SIZE ); + StripEndNewlineFromString( sstr1 ); // these strings are meant for subsitution into the main strings, so cull the automatic end newlines + char *sstr2 = LookupString( READ_STRING() ); + sstr2 = safe_strcpy( szBuf[2], sstr2 , MSG_BUF_SIZE ); + StripEndNewlineFromString( sstr2 ); + char *sstr3 = LookupString( READ_STRING() ); + sstr3 = safe_strcpy( szBuf[3], sstr3 , MSG_BUF_SIZE ); + StripEndNewlineFromString( sstr3 ); + char *sstr4 = LookupString( READ_STRING() ); + sstr4 = safe_strcpy( szBuf[4], sstr4 , MSG_BUF_SIZE ); + StripEndNewlineFromString( sstr4 ); + char *psz = szBuf[5]; + + switch ( msg_dest ) + { + case HUD_PRINTCENTER: + safe_sprintf( psz, MSG_BUF_SIZE, msg_text, sstr1, sstr2, sstr3, sstr4 ); + CenterPrint( ConvertCRtoNL( psz ) ); + break; + + case HUD_PRINTNOTIFY: + psz[0] = 1; // mark this message to go into the notify buffer + safe_sprintf( psz+1, MSG_BUF_SIZE, msg_text, sstr1, sstr2, sstr3, sstr4 ); + ConsolePrint( ConvertCRtoNL( psz ) ); + break; + + case HUD_PRINTTALK: + safe_sprintf( psz, MSG_BUF_SIZE, msg_text, sstr1, sstr2, sstr3, sstr4 ); + gHUD.m_SayText.SayTextPrint( ConvertCRtoNL( psz ), 128 ); + break; + + case HUD_PRINTCONSOLE: + safe_sprintf( psz, MSG_BUF_SIZE, msg_text, sstr1, sstr2, sstr3, sstr4 ); + ConsolePrint( ConvertCRtoNL( psz ) ); + break; + } + + return 1; +} diff --git a/ricochet/cl_dll/tf_defs.h b/ricochet/cl_dll/tf_defs.h new file mode 100644 index 0000000..5d7c227 --- /dev/null +++ b/ricochet/cl_dll/tf_defs.h @@ -0,0 +1,1357 @@ +/*** +* +* Copyright (c) 1998, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#ifndef __TF_DEFS_H +#define __TF_DEFS_H + +//=========================================================================== +// OLD OPTIONS.QC +//=========================================================================== +#define DEFAULT_AUTOZOOM FALSE +#define WEINER_SNIPER // autoaiming for sniper rifle +#define FLAME_MAXWORLDNUM 20 // maximum number of flames in the world. DO NOT PUT BELOW 20. + +//#define MAX_WORLD_PIPEBOMBS 15 // This is divided between teams - this is the most you should have on a net server +#define MAX_PLAYER_PIPEBOMBS 8 // maximum number of pipebombs any 1 player can have active +#define MAX_PLAYER_AMMOBOXES 3 // maximum number of ammoboxes any 1 player can have active + +//#define MAX_WORLD_FLARES 9 // This is the total number of flares allowed in the world at one time +//#define MAX_WORLD_AMMOBOXES 20 // This is divided between teams - this is the most you should have on a net server +#define GR_TYPE_MIRV_NO 4 // Number of Mirvs a Mirv Grenade breaks into +#define GR_TYPE_NAPALM_NO 8 // Number of flames napalm grenade breaks into (unused if net server) +#define MEDIKIT_IS_BIOWEAPON // Medikit acts as a bioweapon against enemies + +#define TEAM_HELP_RATE 60 // used only if teamplay bit 64 (help team with lower score) is set. + // 60 is a mild setting, and won't make too much difference + // increasing it _decreases_ the amount of help the losing team gets + // Minimum setting is 1, which would really help the losing team + +#define DISPLAY_CLASS_HELP TRUE // Change this to #OFF if you don't want the class help to + // appear whenever a player connects +#define NEVER_TEAMFRAGS FALSE // teamfrags options always off +#define ALWAYS_TEAMFRAGS FALSE // teamfrags options always on +#define CHECK_SPEEDS TRUE // makes sure players aren't moving too fast +#define SNIPER_RIFLE_RELOAD_TIME 1.5 // seconds + +#define MAPBRIEFING_MAXTEXTLENGTH 512 +#define PLAYER_PUSH_VELOCITY 50 // Players push teammates if they're moving under this speed + +// Debug Options +//#define MAP_DEBUG // Debug for Map code. I suggest running in a hi-res + // mode and/or piping the output from the server to a file. +#ifdef MAP_DEBUG + #define MDEBUG(x) x +#else + #define MDEBUG(x) +#endif +//#define VERBOSE // Verbose Debugging on/off + +//=========================================================================== +// OLD QUAKE Defs +//=========================================================================== +// items +#define IT_AXE 4096 +#define IT_SHOTGUN 1 +#define IT_SUPER_SHOTGUN 2 +#define IT_NAILGUN 4 +#define IT_SUPER_NAILGUN 8 +#define IT_GRENADE_LAUNCHER 16 +#define IT_ROCKET_LAUNCHER 32 +#define IT_LIGHTNING 64 +#define IT_EXTRA_WEAPON 128 + +#define IT_SHELLS 256 +#define IT_NAILS 512 +#define IT_ROCKETS 1024 +#define IT_CELLS 2048 + +#define IT_ARMOR1 8192 +#define IT_ARMOR2 16384 +#define IT_ARMOR3 32768 +#define IT_SUPERHEALTH 65536 + +#define IT_KEY1 131072 +#define IT_KEY2 262144 + +#define IT_INVISIBILITY 524288 +#define IT_INVULNERABILITY 1048576 +#define IT_SUIT 2097152 +#define IT_QUAD 4194304 +#define IT_HOOK 8388608 + +#define IT_KEY3 16777216 // Stomp invisibility +#define IT_KEY4 33554432 // Stomp invulnerability + +//=========================================================================== +// TEAMFORTRESS Defs +//=========================================================================== +// TeamFortress State Flags +#define TFSTATE_GRENPRIMED 1 // Whether the player has a primed grenade +#define TFSTATE_RELOADING 2 // Whether the player is reloading +#define TFSTATE_ALTKILL 4 // #TRUE if killed with a weapon not in self.weapon: NOT USED ANYMORE +#define TFSTATE_RANDOMPC 8 // Whether Playerclass is random, new one each respawn +#define TFSTATE_INFECTED 16 // set when player is infected by the bioweapon +#define TFSTATE_INVINCIBLE 32 // Player has permanent Invincibility (Usually by GoalItem) +#define TFSTATE_INVISIBLE 64 // Player has permanent Invisibility (Usually by GoalItem) +#define TFSTATE_QUAD 128 // Player has permanent Quad Damage (Usually by GoalItem) +#define TFSTATE_RADSUIT 256 // Player has permanent Radsuit (Usually by GoalItem) +#define TFSTATE_BURNING 512 // Is on fire +#define TFSTATE_GRENTHROWING 1024 // is throwing a grenade +#define TFSTATE_AIMING 2048 // is using the laser sight +#define TFSTATE_ZOOMOFF 4096 // doesn't want the FOV changed when zooming +#define TFSTATE_RESPAWN_READY 8192 // is waiting for respawn, and has pressed fire +#define TFSTATE_HALLUCINATING 16384 // set when player is hallucinating +#define TFSTATE_TRANQUILISED 32768 // set when player is tranquilised +#define TFSTATE_CANT_MOVE 65536 // set when player is setting a detpack +#define TFSTATE_RESET_FLAMETIME 131072 // set when the player has to have his flames increased in health + +// Defines used by TF_T_Damage (see combat.qc) +#define TF_TD_IGNOREARMOUR 1 // Bypasses the armour of the target +#define TF_TD_NOTTEAM 2 // Doesn't damage a team member (indicates direct fire weapon) +#define TF_TD_NOTSELF 4 // Doesn't damage self + +#define TF_TD_OTHER 0 // Ignore armorclass +#define TF_TD_SHOT 1 // Bullet damage +#define TF_TD_NAIL 2 // Nail damage +#define TF_TD_EXPLOSION 4 // Explosion damage +#define TF_TD_ELECTRICITY 8 // Electric damage +#define TF_TD_FIRE 16 // Fire damage +#define TF_TD_NOSOUND 256 // Special damage. Makes no sound/painframe, etc + +/*==================================================*/ +/* Toggleable Game Settings */ +/*==================================================*/ +#define TF_RESPAWNDELAY1 5 // seconds of waiting before player can respawn +#define TF_RESPAWNDELAY2 10 // seconds of waiting before player can respawn +#define TF_RESPAWNDELAY3 20 // seconds of waiting before player can respawn + +#define TEAMPLAY_NORMAL 1 +#define TEAMPLAY_HALFDIRECT 2 +#define TEAMPLAY_NODIRECT 4 +#define TEAMPLAY_HALFEXPLOSIVE 8 +#define TEAMPLAY_NOEXPLOSIVE 16 +#define TEAMPLAY_LESSPLAYERSHELP 32 +#define TEAMPLAY_LESSSCOREHELP 64 +#define TEAMPLAY_HALFDIRECTARMOR 128 +#define TEAMPLAY_NODIRECTARMOR 256 +#define TEAMPLAY_HALFEXPARMOR 512 +#define TEAMPLAY_NOEXPARMOR 1024 +#define TEAMPLAY_HALFDIRMIRROR 2048 +#define TEAMPLAY_FULLDIRMIRROR 4096 +#define TEAMPLAY_HALFEXPMIRROR 8192 +#define TEAMPLAY_FULLEXPMIRROR 16384 + +#define TEAMPLAY_TEAMDAMAGE (TEAMPLAY_NODIRECT | TEAMPLAY_HALFDIRECT | TEAMPLAY_HALFEXPLOSIVE | TEAMPLAY_NOEXPLOSIVE) +// FortressMap stuff +#define TEAM1_CIVILIANS 1 +#define TEAM2_CIVILIANS 2 +#define TEAM3_CIVILIANS 4 +#define TEAM4_CIVILIANS 8 + +// Defines for the playerclass +#define PC_UNDEFINED 0 + +#define PC_SCOUT 1 +#define PC_SNIPER 2 +#define PC_SOLDIER 3 +#define PC_DEMOMAN 4 +#define PC_MEDIC 5 +#define PC_HVYWEAP 6 +#define PC_PYRO 7 +#define PC_SPY 8 +#define PC_ENGINEER 9 + +// Insert new class definitions here + +// PC_RANDOM _MUST_ be the third last class +#define PC_RANDOM 10 // Random playerclass +#define PC_CIVILIAN 11 // Civilians are a special class. They cannot + // be chosen by players, only enforced by maps +#define PC_LASTCLASS 12 // Use this as the high-boundary for any loops + // through the playerclass. + +// These are just for the scanner +#define SCAN_SENTRY 13 +#define SCAN_GOALITEM 14 + +// Values returned by CheckArea +enum +{ + CAREA_CLEAR, + CAREA_BLOCKED, + CAREA_NOBUILD +}; + +/*==================================================*/ +/* Impulse Defines */ +/*==================================================*/ +// Alias check to see whether they already have the aliases +#define TF_ALIAS_CHECK 13 + +// CTF Support Impulses +#define HOOK_IMP1 22 +#define FLAG_INFO 23 +#define HOOK_IMP2 39 + +// Axe +#define AXE_IMP 40 + +// Camera Impulse +#define TF_CAM_TARGET 50 +#define TF_CAM_ZOOM 51 +#define TF_CAM_ANGLE 52 +#define TF_CAM_VEC 53 +#define TF_CAM_PROJECTILE 54 +#define TF_CAM_PROJECTILE_Z 55 +#define TF_CAM_REVANGLE 56 +#define TF_CAM_OFFSET 57 +#define TF_CAM_DROP 58 +#define TF_CAM_FADETOBLACK 59 +#define TF_CAM_FADEFROMBLACK 60 +#define TF_CAM_FADETOWHITE 61 +#define TF_CAM_FADEFROMWHITE 62 + +// Last Weapon impulse +#define TF_LAST_WEAPON 69 + +// Status Bar Resolution Settings. Same as CTF to maintain ease of use. +#define TF_STATUSBAR_RES_START 71 +#define TF_STATUSBAR_RES_END 81 + +// Clan Messages +#define TF_MESSAGE_1 82 +#define TF_MESSAGE_2 83 +#define TF_MESSAGE_3 84 +#define TF_MESSAGE_4 85 +#define TF_MESSAGE_5 86 + +#define TF_CHANGE_CLASS 99 // Bring up the Class Change menu + +// Added to PC_??? to get impulse to use if this clashes with your +// own impulses, just change this value, not the PC_?? +#define TF_CHANGEPC 100 +// The next few impulses are all the class selections +//PC_SCOUT 101 +//PC_SNIPER 102 +//PC_SOLDIER 103 +//PC_DEMOMAN 104 +//PC_MEDIC 105 +//PC_HVYWEAP 106 +//PC_PYRO 107 +//PC_RANDOM 108 +//PC_CIVILIAN 109 // Cannot be used +//PC_SPY 110 +//PC_ENGINEER 111 + +// Help impulses +#define TF_DISPLAYLOCATION 118 +#define TF_STATUS_QUERY 119 + +#define TF_HELP_MAP 131 + +// Information impulses +#define TF_INVENTORY 135 +#define TF_SHOWTF 136 +#define TF_SHOWLEGALCLASSES 137 + +// Team Impulses +#define TF_TEAM_1 140 // Join Team 1 +#define TF_TEAM_2 141 // Join Team 2 +#define TF_TEAM_3 142 // Join Team 3 +#define TF_TEAM_4 143 // Join Team 4 +#define TF_TEAM_CLASSES 144 // Impulse to display team classes +#define TF_TEAM_SCORES 145 // Impulse to display team scores +#define TF_TEAM_LIST 146 // Impulse to display the players in each team. + +// Grenade Impulses +#define TF_GRENADE_1 150 // Prime grenade type 1 +#define TF_GRENADE_2 151 // Prime grenade type 2 +#define TF_GRENADE_T 152 // Throw primed grenade + +// Impulses for new items +//#define TF_SCAN 159 // Scanner Pre-Impulse +#define TF_AUTO_SCAN 159 // Scanner On/Off +#define TF_SCAN_ENEMY 160 // Impulses to toggle scanning of enemies +#define TF_SCAN_FRIENDLY 161 // Impulses to toggle scanning of friendlies +//#define TF_SCAN_10 162 // Scan using 10 enery (1 cell) +#define TF_SCAN_SOUND 162 // Scanner sounds on/off +#define TF_SCAN_30 163 // Scan using 30 energy (2 cells) +#define TF_SCAN_100 164 // Scan using 100 energy (5 cells) +#define TF_DETPACK_5 165 // Detpack set to 5 seconds +#define TF_DETPACK_20 166 // Detpack set to 20 seconds +#define TF_DETPACK_50 167 // Detpack set to 50 seconds +#define TF_DETPACK 168 // Detpack Pre-Impulse +#define TF_DETPACK_STOP 169 // Impulse to stop setting detpack +#define TF_PB_DETONATE 170 // Detonate Pipebombs + +// Special skill +#define TF_SPECIAL_SKILL 171 + +// Ammo Drop impulse +#define TF_DROP_AMMO 172 + +// Reload impulse +#define TF_RELOAD 173 + +// auto-zoom toggle +#define TF_AUTOZOOM 174 + +// drop/pass commands +#define TF_DROPKEY 175 + +// Select Medikit +#define TF_MEDIKIT 176 + +// Spy Impulses +#define TF_SPY_SPY 177 // On net, go invisible, on LAN, change skin/color +#define TF_SPY_DIE 178 // Feign Death + +// Engineer Impulses +#define TF_ENGINEER_BUILD 179 +#define TF_ENGINEER_SANDBAG 180 + +// Medic +#define TF_MEDIC_HELPME 181 + +// Status bar +#define TF_STATUSBAR_ON 182 +#define TF_STATUSBAR_OFF 183 + +// Discard impulse +#define TF_DISCARD 184 + +// ID Player impulse +#define TF_ID 185 + +// Clan Battle impulses +#define TF_SHOWIDS 186 + +// More Engineer Impulses +#define TF_ENGINEER_DETDISP 187 +#define TF_ENGINEER_DETSENT 188 + +// Admin Commands +#define TF_ADMIN_DEAL_CYCLE 189 +#define TF_ADMIN_KICK 190 +#define TF_ADMIN_BAN 191 +#define TF_ADMIN_COUNTPLAYERS 192 +#define TF_ADMIN_CEASEFIRE 193 + +// Drop Goal Items +#define TF_DROPGOALITEMS 194 + +// More Admin Commands +#define TF_ADMIN_NEXT 195 + +// More Engineer Impulses +#define TF_ENGINEER_DETEXIT 196 +#define TF_ENGINEER_DETENTRANCE 197 + +// Yet MORE Admin Commands +#define TF_ADMIN_LISTIPS 198 + +// Silent Spy Feign +#define TF_SPY_SILENTDIE 199 + + +/*==================================================*/ +/* Colors */ +/*==================================================*/ +#define TEAM1_COLOR 150 +#define TEAM2_COLOR 250 +#define TEAM3_COLOR 45 +#define TEAM4_COLOR 100 + +/*==================================================*/ +/* Defines for the ENGINEER's Building ability */ +/*==================================================*/ +// Ammo costs +#define AMMO_COST_SHELLS 2 // Metal needed to make 1 shell +#define AMMO_COST_NAILS 1 +#define AMMO_COST_ROCKETS 2 +#define AMMO_COST_CELLS 2 + +// Building types +#define BUILD_DISPENSER 1 +#define BUILD_SENTRYGUN 2 +#define BUILD_MORTAR 3 +#define BUILD_TELEPORTER_ENTRANCE 4 +#define BUILD_TELEPORTER_EXIT 5 + +// Building metal costs +#define BUILD_COST_DISPENSER 100 // Metal needed to built +#define BUILD_COST_SENTRYGUN 130 +#define BUILD_COST_MORTAR 150 +#define BUILD_COST_TELEPORTER 125 + +#define BUILD_COST_SANDBAG 20 // Built with a separate alias + +// Building times +#define BUILD_TIME_DISPENSER 2 // seconds to build +#define BUILD_TIME_SENTRYGUN 5 +#define BUILD_TIME_MORTAR 5 +#define BUILD_TIME_TELEPORTER 4 + +// Building health levels +#define BUILD_HEALTH_DISPENSER 150 // Health of the building +#define BUILD_HEALTH_SENTRYGUN 150 +#define BUILD_HEALTH_MORTAR 200 +#define BUILD_HEALTH_TELEPORTER 80 + +// Dispenser's maximum carrying capability +#define BUILD_DISPENSER_MAX_SHELLS 400 +#define BUILD_DISPENSER_MAX_NAILS 600 +#define BUILD_DISPENSER_MAX_ROCKETS 300 +#define BUILD_DISPENSER_MAX_CELLS 400 +#define BUILD_DISPENSER_MAX_ARMOR 500 + +// Build state sent down to client +#define BS_BUILDING (1<<0) +#define BS_HAS_DISPENSER (1<<1) +#define BS_HAS_SENTRYGUN (1<<2) +#define BS_CANB_DISPENSER (1<<3) +#define BS_CANB_SENTRYGUN (1<<4) +/*==================================================*/ +/* Ammo quantities for dropping & dispenser use */ +/*==================================================*/ +#define DROP_SHELLS 20 +#define DROP_NAILS 20 +#define DROP_ROCKETS 10 +#define DROP_CELLS 10 +#define DROP_ARMOR 40 + +/*==================================================*/ +/* Team Defines */ +/*==================================================*/ +#define TM_MAX_NO 4 // Max number of teams. Simply changing this value isn't enough. + // A new global to hold new team colors is needed, and more flags + // in the spawnpoint spawnflags may need to be used. + // Basically, don't change this unless you know what you're doing :) + +/*==================================================*/ +/* New Weapon Defines */ +/*==================================================*/ +#define WEAP_HOOK 1 +#define WEAP_BIOWEAPON 2 +#define WEAP_MEDIKIT 4 +#define WEAP_SPANNER 8 +#define WEAP_AXE 16 +#define WEAP_SNIPER_RIFLE 32 +#define WEAP_AUTO_RIFLE 64 +#define WEAP_SHOTGUN 128 +#define WEAP_SUPER_SHOTGUN 256 +#define WEAP_NAILGUN 512 +#define WEAP_SUPER_NAILGUN 1024 +#define WEAP_GRENADE_LAUNCHER 2048 +#define WEAP_FLAMETHROWER 4096 +#define WEAP_ROCKET_LAUNCHER 8192 +#define WEAP_INCENDIARY 16384 +#define WEAP_ASSAULT_CANNON 32768 +#define WEAP_LIGHTNING 65536 +#define WEAP_DETPACK 131072 +#define WEAP_TRANQ 262144 +#define WEAP_LASER 524288 +// still room for 12 more weapons +// but we can remove some by giving the weapons +// a weapon mode (like the rifle) + +// HL-compatible weapon numbers +#define WEAPON_HOOK 1 +#define WEAPON_BIOWEAPON (WEAPON_HOOK+1) +#define WEAPON_MEDIKIT (WEAPON_HOOK+2) +#define WEAPON_SPANNER (WEAPON_HOOK+3) +#define WEAPON_AXE (WEAPON_HOOK+4) +#define WEAPON_SNIPER_RIFLE (WEAPON_HOOK+5) +#define WEAPON_AUTO_RIFLE (WEAPON_HOOK+6) +#define WEAPON_TF_SHOTGUN (WEAPON_HOOK+7) +#define WEAPON_SUPER_SHOTGUN (WEAPON_HOOK+8) +#define WEAPON_NAILGUN (WEAPON_HOOK+9) +#define WEAPON_SUPER_NAILGUN (WEAPON_HOOK+10) +#define WEAPON_GRENADE_LAUNCHER (WEAPON_HOOK+11) +#define WEAPON_FLAMETHROWER (WEAPON_HOOK+12) +#define WEAPON_ROCKET_LAUNCHER (WEAPON_HOOK+13) +#define WEAPON_INCENDIARY (WEAPON_HOOK+14) +#define WEAPON_ASSAULT_CANNON (WEAPON_HOOK+16) +#define WEAPON_LIGHTNING (WEAPON_HOOK+17) +#define WEAPON_DETPACK (WEAPON_HOOK+18) +#define WEAPON_TRANQ (WEAPON_HOOK+19) +#define WEAPON_LASER (WEAPON_HOOK+20) +#define WEAPON_PIPEBOMB_LAUNCHER (WEAPON_HOOK+21) +#define WEAPON_KNIFE (WEAPON_HOOK+22) +#define WEAPON_BENCHMARK (WEAPON_HOOK+23) + +/*==================================================*/ +/* New Weapon Related Defines */ +/*==================================================*/ +// shots per reload +#define RE_SHOTGUN 8 +#define RE_SUPER_SHOTGUN 16 // 8 shots +#define RE_GRENADE_LAUNCHER 6 +#define RE_ROCKET_LAUNCHER 4 + +// reload times +#define RE_SHOTGUN_TIME 2 +#define RE_SUPER_SHOTGUN_TIME 3 +#define RE_GRENADE_LAUNCHER_TIME 4 +#define RE_ROCKET_LAUNCHER_TIME 5 + +// Maximum velocity you can move and fire the Sniper Rifle +#define WEAP_SNIPER_RIFLE_MAX_MOVE 50 + +// Medikit +#define WEAP_MEDIKIT_HEAL 200 // Amount medikit heals per hit +#define WEAP_MEDIKIT_OVERHEAL 50 // Amount of superhealth over max_health the medikit will dispense + +// Spanner +#define WEAP_SPANNER_REPAIR 10 + +// Detpack +#define WEAP_DETPACK_DISARMTIME 3 // Time it takes to disarm a Detpack +#define WEAP_DETPACK_SETTIME 3 // Time it takes to set a Detpack +#define WEAP_DETPACK_SIZE 700 // Explosion Size +#define WEAP_DETPACK_GOAL_SIZE 1500 // Explosion Size for goal triggering +#define WEAP_DETPACK_BITS_NO 12 // Bits that detpack explodes into + +// Tranquiliser Gun +#define TRANQ_TIME 15 + +// Grenades +#define GR_PRIMETIME 3 +#define GR_CALTROP_PRIME 0.5 +#define GR_TYPE_NONE 0 +#define GR_TYPE_NORMAL 1 +#define GR_TYPE_CONCUSSION 2 +#define GR_TYPE_NAIL 3 +#define GR_TYPE_MIRV 4 +#define GR_TYPE_NAPALM 5 +//#define GR_TYPE_FLARE 6 +#define GR_TYPE_GAS 7 +#define GR_TYPE_EMP 8 +#define GR_TYPE_CALTROP 9 +//#define GR_TYPE_FLASH 10 + +// Defines for WeaponMode +#define GL_NORMAL 0 +#define GL_PIPEBOMB 1 + +// Defines for OLD Concussion Grenade +#define GR_OLD_CONCUSS_TIME 5 +#define GR_OLD_CONCUSS_DEC 20 + +// Defines for Concussion Grenade +#define GR_CONCUSS_TIME 0.25 +#define GR_CONCUSS_DEC 10 +#define MEDIUM_PING 150 +#define HIGH_PING 200 + +// Defines for the Gas Grenade +#define GR_HALLU_TIME 0.3 +#define GR_OLD_HALLU_TIME 0.5 +#define GR_HALLU_DEC 2.5 + +// Defines for the BioInfection +#define BIO_JUMP_RADIUS 128 // The distance the bioinfection can jump between players + +/*==================================================*/ +/* New Items */ +/*==================================================*/ +#define NIT_SCANNER 1 + +#define NIT_SILVER_DOOR_OPENED #IT_KEY1 // 131072 +#define NIT_GOLD_DOOR_OPENED #IT_KEY2 // 262144 + +/*==================================================*/ +/* New Item Flags */ +/*==================================================*/ +#define NIT_SCANNER_ENEMY 1 // Detect enemies +#define NIT_SCANNER_FRIENDLY 2 // Detect friendlies (team members) +#define NIT_SCANNER_SOUND 4 // Motion detection. Only report moving entities. + +/*==================================================*/ +/* New Item Related Defines */ +/*==================================================*/ +#define NIT_SCANNER_POWER 25 // The amount of power spent on a scan with the scanner + // is multiplied by this to get the scanrange. +#define NIT_SCANNER_MAXCELL 50 // The maximum number of cells than can be used in one scan +#define NIT_SCANNER_MIN_MOVEMENT 50 // The minimum velocity an entity must have to be detected + // by scanners that only detect movement + +/*==================================================*/ +/* Variables used for New Weapons and Reloading */ +/*==================================================*/ +// Armor Classes : Bitfields. Use the "armorclass" of armor for the Armor Type. +#define AT_SAVESHOT 1 // Kevlar : Reduces bullet damage by 15% +#define AT_SAVENAIL 2 // Wood :) : Reduces nail damage by 15% +#define AT_SAVEEXPLOSION 4 // Blast : Reduces explosion damage by 15% +#define AT_SAVEELECTRICITY 8 // Shock : Reduces electricity damage by 15% +#define AT_SAVEFIRE 16 // Asbestos : Reduces fire damage by 15% + +/*==========================================================================*/ +/* TEAMFORTRESS CLASS DETAILS */ +/*==========================================================================*/ +// Class Details for SCOUT +#define PC_SCOUT_SKIN 4 // Skin for this class when Classkin is on. +#define PC_SCOUT_MAXHEALTH 75 // Maximum Health Level +#define PC_SCOUT_MAXSPEED 400 // Maximum movement speed +#define PC_SCOUT_MAXSTRAFESPEED 400 // Maximum strafing movement speed +#define PC_SCOUT_MAXARMOR 50 // Maximum Armor Level, of any armor class +#define PC_SCOUT_INITARMOR 25 // Armor level when respawned +#define PC_SCOUT_MAXARMORTYPE 0.3 // Maximum level of Armor absorption +#define PC_SCOUT_INITARMORTYPE 0.3 // Absorption Level of armor when respawned +#define PC_SCOUT_ARMORCLASSES 3 // #AT_SAVESHOT | #AT_SAVENAIL <-Armor Classes allowed for this class +#define PC_SCOUT_INITARMORCLASS 0 // Armorclass worn when respawned +#define PC_SCOUT_WEAPONS WEAP_AXE | WEAP_SHOTGUN | WEAP_NAILGUN +#define PC_SCOUT_MAXAMMO_SHOT 50 // Maximum amount of shot ammo this class can carry +#define PC_SCOUT_MAXAMMO_NAIL 200 // Maximum amount of nail ammo this class can carry +#define PC_SCOUT_MAXAMMO_CELL 100 // Maximum amount of cell ammo this class can carry +#define PC_SCOUT_MAXAMMO_ROCKET 25 // Maximum amount of rocket ammo this class can carry +#define PC_SCOUT_INITAMMO_SHOT 25 // Amount of shot ammo this class has when respawned +#define PC_SCOUT_INITAMMO_NAIL 100 // Amount of nail ammo this class has when respawned +#define PC_SCOUT_INITAMMO_CELL 50 // Amount of cell ammo this class has when respawned +#define PC_SCOUT_INITAMMO_ROCKET 0 // Amount of rocket ammo this class has when respawned +#define PC_SCOUT_GRENADE_TYPE_1 GR_TYPE_CALTROP // <- 1st Type of Grenade this class has +#define PC_SCOUT_GRENADE_TYPE_2 GR_TYPE_CONCUSSION // <- 2nd Type of Grenade this class has +#define PC_SCOUT_GRENADE_INIT_1 2 // Number of grenades of Type 1 this class has when respawned +#define PC_SCOUT_GRENADE_INIT_2 3 // Number of grenades of Type 2 this class has when respawned +#define PC_SCOUT_TF_ITEMS NIT_SCANNER // <- TeamFortress Items this class has + +#define PC_SCOUT_MOTION_MIN_I 0.5 // < Short range +#define PC_SCOUT_MOTION_MIN_MOVE 50 // Minimum vlen of player velocity to be picked up by motion detector +#define PC_SCOUT_SCAN_TIME 2 // # of seconds between each scan pulse +#define PC_SCOUT_SCAN_RANGE 100 // Default scanner range +#define PC_SCOUT_SCAN_COST 2 // Default scanner cell useage per scan + +// Class Details for SNIPER +#define PC_SNIPER_SKIN 5 +#define PC_SNIPER_MAXHEALTH 90 +#define PC_SNIPER_MAXSPEED 300 +#define PC_SNIPER_MAXSTRAFESPEED 300 +#define PC_SNIPER_MAXARMOR 50 +#define PC_SNIPER_INITARMOR 0 +#define PC_SNIPER_MAXARMORTYPE 0.3 +#define PC_SNIPER_INITARMORTYPE 0.3 +#define PC_SNIPER_ARMORCLASSES 3 // #AT_SAVESHOT | #AT_SAVENAIL +#define PC_SNIPER_INITARMORCLASS 0 +#define PC_SNIPER_WEAPONS WEAP_SNIPER_RIFLE | WEAP_AUTO_RIFLE | WEAP_AXE | WEAP_NAILGUN +#define PC_SNIPER_MAXAMMO_SHOT 75 +#define PC_SNIPER_MAXAMMO_NAIL 100 +#define PC_SNIPER_MAXAMMO_CELL 50 +#define PC_SNIPER_MAXAMMO_ROCKET 25 +#define PC_SNIPER_INITAMMO_SHOT 60 +#define PC_SNIPER_INITAMMO_NAIL 50 +#define PC_SNIPER_INITAMMO_CELL 0 +#define PC_SNIPER_INITAMMO_ROCKET 0 +#define PC_SNIPER_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_SNIPER_GRENADE_TYPE_2 GR_TYPE_NONE +#define PC_SNIPER_GRENADE_INIT_1 2 +#define PC_SNIPER_GRENADE_INIT_2 0 +#define PC_SNIPER_TF_ITEMS 0 + +// Class Details for SOLDIER +#define PC_SOLDIER_SKIN 6 +#define PC_SOLDIER_MAXHEALTH 100 +#define PC_SOLDIER_MAXSPEED 240 +#define PC_SOLDIER_MAXSTRAFESPEED 240 +#define PC_SOLDIER_MAXARMOR 200 +#define PC_SOLDIER_INITARMOR 100 +#define PC_SOLDIER_MAXARMORTYPE 0.8 +#define PC_SOLDIER_INITARMORTYPE 0.8 +#define PC_SOLDIER_ARMORCLASSES 31 // ALL +#define PC_SOLDIER_INITARMORCLASS 0 +#define PC_SOLDIER_WEAPONS WEAP_AXE | WEAP_SHOTGUN | WEAP_SUPER_SHOTGUN | WEAP_ROCKET_LAUNCHER +#define PC_SOLDIER_MAXAMMO_SHOT 100 +#define PC_SOLDIER_MAXAMMO_NAIL 100 +#define PC_SOLDIER_MAXAMMO_CELL 50 +#define PC_SOLDIER_MAXAMMO_ROCKET 50 +#define PC_SOLDIER_INITAMMO_SHOT 50 +#define PC_SOLDIER_INITAMMO_NAIL 0 +#define PC_SOLDIER_INITAMMO_CELL 0 +#define PC_SOLDIER_INITAMMO_ROCKET 10 +#define PC_SOLDIER_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_SOLDIER_GRENADE_TYPE_2 GR_TYPE_NAIL +#define PC_SOLDIER_GRENADE_INIT_1 4 +#define PC_SOLDIER_GRENADE_INIT_2 1 +#define PC_SOLDIER_TF_ITEMS 0 + +#define MAX_NAIL_GRENS 2 // Can only have 2 Nail grens active +#define MAX_NAPALM_GRENS 2 // Can only have 2 Napalm grens active +#define MAX_GAS_GRENS 2 // Can only have 2 Gas grenades active +#define MAX_MIRV_GRENS 2 // Can only have 2 Mirv's +#define MAX_CONCUSSION_GRENS 3 +#define MAX_CALTROP_CANS 3 + +// Class Details for DEMOLITION MAN +#define PC_DEMOMAN_SKIN 1 +#define PC_DEMOMAN_MAXHEALTH 90 +#define PC_DEMOMAN_MAXSPEED 280 +#define PC_DEMOMAN_MAXSTRAFESPEED 280 +#define PC_DEMOMAN_MAXARMOR 120 +#define PC_DEMOMAN_INITARMOR 50 +#define PC_DEMOMAN_MAXARMORTYPE 0.6 +#define PC_DEMOMAN_INITARMORTYPE 0.6 +#define PC_DEMOMAN_ARMORCLASSES 31 // ALL +#define PC_DEMOMAN_INITARMORCLASS 0 +#define PC_DEMOMAN_WEAPONS WEAP_AXE | WEAP_SHOTGUN | WEAP_GRENADE_LAUNCHER | WEAP_DETPACK +#define PC_DEMOMAN_MAXAMMO_SHOT 75 +#define PC_DEMOMAN_MAXAMMO_NAIL 50 +#define PC_DEMOMAN_MAXAMMO_CELL 50 +#define PC_DEMOMAN_MAXAMMO_ROCKET 50 +#define PC_DEMOMAN_MAXAMMO_DETPACK 1 +#define PC_DEMOMAN_INITAMMO_SHOT 30 +#define PC_DEMOMAN_INITAMMO_NAIL 0 +#define PC_DEMOMAN_INITAMMO_CELL 0 +#define PC_DEMOMAN_INITAMMO_ROCKET 20 +#define PC_DEMOMAN_INITAMMO_DETPACK 1 +#define PC_DEMOMAN_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_DEMOMAN_GRENADE_TYPE_2 GR_TYPE_MIRV +#define PC_DEMOMAN_GRENADE_INIT_1 4 +#define PC_DEMOMAN_GRENADE_INIT_2 4 +#define PC_DEMOMAN_TF_ITEMS 0 + +// Class Details for COMBAT MEDIC +#define PC_MEDIC_SKIN 3 +#define PC_MEDIC_MAXHEALTH 90 +#define PC_MEDIC_MAXSPEED 320 +#define PC_MEDIC_MAXSTRAFESPEED 320 +#define PC_MEDIC_MAXARMOR 100 +#define PC_MEDIC_INITARMOR 50 +#define PC_MEDIC_MAXARMORTYPE 0.6 +#define PC_MEDIC_INITARMORTYPE 0.3 +#define PC_MEDIC_ARMORCLASSES 11 // ALL except EXPLOSION +#define PC_MEDIC_INITARMORCLASS 0 +#define PC_MEDIC_WEAPONS WEAP_BIOWEAPON | WEAP_MEDIKIT | WEAP_SHOTGUN | WEAP_SUPER_SHOTGUN | WEAP_SUPER_NAILGUN +#define PC_MEDIC_MAXAMMO_SHOT 75 +#define PC_MEDIC_MAXAMMO_NAIL 150 +#define PC_MEDIC_MAXAMMO_CELL 50 +#define PC_MEDIC_MAXAMMO_ROCKET 25 +#define PC_MEDIC_MAXAMMO_MEDIKIT 100 +#define PC_MEDIC_INITAMMO_SHOT 50 +#define PC_MEDIC_INITAMMO_NAIL 50 +#define PC_MEDIC_INITAMMO_CELL 0 +#define PC_MEDIC_INITAMMO_ROCKET 0 +#define PC_MEDIC_INITAMMO_MEDIKIT 50 +#define PC_MEDIC_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_MEDIC_GRENADE_TYPE_2 GR_TYPE_CONCUSSION +#define PC_MEDIC_GRENADE_INIT_1 3 +#define PC_MEDIC_GRENADE_INIT_2 2 +#define PC_MEDIC_TF_ITEMS 0 +#define PC_MEDIC_REGEN_TIME 3 // Number of seconds between each regen. +#define PC_MEDIC_REGEN_AMOUNT 2 // Amount of health regenerated each regen. + +// Class Details for HVYWEAP +#define PC_HVYWEAP_SKIN 2 +#define PC_HVYWEAP_MAXHEALTH 100 +#define PC_HVYWEAP_MAXSPEED 230 +#define PC_HVYWEAP_MAXSTRAFESPEED 230 +#define PC_HVYWEAP_MAXARMOR 300 +#define PC_HVYWEAP_INITARMOR 150 +#define PC_HVYWEAP_MAXARMORTYPE 0.8 +#define PC_HVYWEAP_INITARMORTYPE 0.8 +#define PC_HVYWEAP_ARMORCLASSES 31 // ALL +#define PC_HVYWEAP_INITARMORCLASS 0 +#define PC_HVYWEAP_WEAPONS WEAP_ASSAULT_CANNON | WEAP_AXE | WEAP_SHOTGUN | WEAP_SUPER_SHOTGUN +#define PC_HVYWEAP_MAXAMMO_SHOT 200 +#define PC_HVYWEAP_MAXAMMO_NAIL 200 +#define PC_HVYWEAP_MAXAMMO_CELL 50 +#define PC_HVYWEAP_MAXAMMO_ROCKET 25 +#define PC_HVYWEAP_INITAMMO_SHOT 200 +#define PC_HVYWEAP_INITAMMO_NAIL 0 +#define PC_HVYWEAP_INITAMMO_CELL 30 +#define PC_HVYWEAP_INITAMMO_ROCKET 0 +#define PC_HVYWEAP_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_HVYWEAP_GRENADE_TYPE_2 GR_TYPE_MIRV +#define PC_HVYWEAP_GRENADE_INIT_1 4 +#define PC_HVYWEAP_GRENADE_INIT_2 1 +#define PC_HVYWEAP_TF_ITEMS 0 +#define PC_HVYWEAP_CELL_USAGE 7 // Amount of cells spent to power up assault cannon + + + +// Class Details for PYRO +#define PC_PYRO_SKIN 21 +#define PC_PYRO_MAXHEALTH 100 +#define PC_PYRO_MAXSPEED 300 +#define PC_PYRO_MAXSTRAFESPEED 300 +#define PC_PYRO_MAXARMOR 150 +#define PC_PYRO_INITARMOR 50 +#define PC_PYRO_MAXARMORTYPE 0.6 +#define PC_PYRO_INITARMORTYPE 0.6 +#define PC_PYRO_ARMORCLASSES 27 // ALL except EXPLOSION +#define PC_PYRO_INITARMORCLASS 16 // #AT_SAVEFIRE +#define PC_PYRO_WEAPONS WEAP_INCENDIARY | WEAP_FLAMETHROWER | WEAP_AXE | WEAP_SHOTGUN +#define PC_PYRO_MAXAMMO_SHOT 40 +#define PC_PYRO_MAXAMMO_NAIL 50 +#define PC_PYRO_MAXAMMO_CELL 200 +#define PC_PYRO_MAXAMMO_ROCKET 20 +#define PC_PYRO_INITAMMO_SHOT 20 +#define PC_PYRO_INITAMMO_NAIL 0 +#define PC_PYRO_INITAMMO_CELL 120 +#define PC_PYRO_INITAMMO_ROCKET 5 +#define PC_PYRO_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_PYRO_GRENADE_TYPE_2 GR_TYPE_NAPALM +#define PC_PYRO_GRENADE_INIT_1 1 +#define PC_PYRO_GRENADE_INIT_2 4 +#define PC_PYRO_TF_ITEMS 0 +#define PC_PYRO_ROCKET_USAGE 3 // Number of rockets per incendiary cannon shot + +// Class Details for SPY +#define PC_SPY_SKIN 22 +#define PC_SPY_MAXHEALTH 90 +#define PC_SPY_MAXSPEED 300 +#define PC_SPY_MAXSTRAFESPEED 300 +#define PC_SPY_MAXARMOR 100 +#define PC_SPY_INITARMOR 25 +#define PC_SPY_MAXARMORTYPE 0.6 // Was 0.3 +#define PC_SPY_INITARMORTYPE 0.6 // Was 0.3 +#define PC_SPY_ARMORCLASSES 27 // ALL except EXPLOSION +#define PC_SPY_INITARMORCLASS 0 +#define PC_SPY_WEAPONS WEAP_AXE | WEAP_TRANQ | WEAP_SUPER_SHOTGUN | WEAP_NAILGUN +#define PC_SPY_MAXAMMO_SHOT 40 +#define PC_SPY_MAXAMMO_NAIL 100 +#define PC_SPY_MAXAMMO_CELL 30 +#define PC_SPY_MAXAMMO_ROCKET 15 +#define PC_SPY_INITAMMO_SHOT 40 +#define PC_SPY_INITAMMO_NAIL 50 +#define PC_SPY_INITAMMO_CELL 10 +#define PC_SPY_INITAMMO_ROCKET 0 +#define PC_SPY_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_SPY_GRENADE_TYPE_2 GR_TYPE_GAS +#define PC_SPY_GRENADE_INIT_1 2 +#define PC_SPY_GRENADE_INIT_2 2 +#define PC_SPY_TF_ITEMS 0 +#define PC_SPY_CELL_REGEN_TIME 5 +#define PC_SPY_CELL_REGEN_AMOUNT 1 +#define PC_SPY_CELL_USAGE 3 // Amount of cells spent while invisible +#define PC_SPY_GO_UNDERCOVER_TIME 4 // Time it takes to go undercover + +// Class Details for ENGINEER +#define PC_ENGINEER_SKIN 22 // Not used anymore +#define PC_ENGINEER_MAXHEALTH 80 +#define PC_ENGINEER_MAXSPEED 300 +#define PC_ENGINEER_MAXSTRAFESPEED 300 +#define PC_ENGINEER_MAXARMOR 50 +#define PC_ENGINEER_INITARMOR 25 +#define PC_ENGINEER_MAXARMORTYPE 0.6 +#define PC_ENGINEER_INITARMORTYPE 0.3 +#define PC_ENGINEER_ARMORCLASSES 31 // ALL +#define PC_ENGINEER_INITARMORCLASS 0 +#define PC_ENGINEER_WEAPONS WEAP_SPANNER | WEAP_LASER | WEAP_SUPER_SHOTGUN +#define PC_ENGINEER_MAXAMMO_SHOT 50 +#define PC_ENGINEER_MAXAMMO_NAIL 50 +#define PC_ENGINEER_MAXAMMO_CELL 200 // synonymous with metal +#define PC_ENGINEER_MAXAMMO_ROCKET 30 +#define PC_ENGINEER_INITAMMO_SHOT 20 +#define PC_ENGINEER_INITAMMO_NAIL 25 +#define PC_ENGINEER_INITAMMO_CELL 100 // synonymous with metal +#define PC_ENGINEER_INITAMMO_ROCKET 0 +#define PC_ENGINEER_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_ENGINEER_GRENADE_TYPE_2 GR_TYPE_EMP +#define PC_ENGINEER_GRENADE_INIT_1 2 +#define PC_ENGINEER_GRENADE_INIT_2 2 +#define PC_ENGINEER_TF_ITEMS 0 + +// Class Details for CIVILIAN +#define PC_CIVILIAN_SKIN 22 +#define PC_CIVILIAN_MAXHEALTH 50 +#define PC_CIVILIAN_MAXSPEED 240 +#define PC_CIVILIAN_MAXSTRAFESPEED 240 +#define PC_CIVILIAN_MAXARMOR 0 +#define PC_CIVILIAN_INITARMOR 0 +#define PC_CIVILIAN_MAXARMORTYPE 0 +#define PC_CIVILIAN_INITARMORTYPE 0 +#define PC_CIVILIAN_ARMORCLASSES 0 +#define PC_CIVILIAN_INITARMORCLASS 0 +#define PC_CIVILIAN_WEAPONS WEAP_AXE +#define PC_CIVILIAN_MAXAMMO_SHOT 0 +#define PC_CIVILIAN_MAXAMMO_NAIL 0 +#define PC_CIVILIAN_MAXAMMO_CELL 0 +#define PC_CIVILIAN_MAXAMMO_ROCKET 0 +#define PC_CIVILIAN_INITAMMO_SHOT 0 +#define PC_CIVILIAN_INITAMMO_NAIL 0 +#define PC_CIVILIAN_INITAMMO_CELL 0 +#define PC_CIVILIAN_INITAMMO_ROCKET 0 +#define PC_CIVILIAN_GRENADE_TYPE_1 0 +#define PC_CIVILIAN_GRENADE_TYPE_2 0 +#define PC_CIVILIAN_GRENADE_INIT_1 0 +#define PC_CIVILIAN_GRENADE_INIT_2 0 +#define PC_CIVILIAN_TF_ITEMS 0 + + +/*==========================================================================*/ +/* TEAMFORTRESS GOALS */ +/*==========================================================================*/ +// For all these defines, see the tfortmap.txt that came with the zip +// for complete descriptions. +// Defines for Goal Activation types : goal_activation (in goals) +#define TFGA_TOUCH 1 // Activated when touched +#define TFGA_TOUCH_DETPACK 2 // Activated when touched by a detpack explosion +#define TFGA_REVERSE_AP 4 // Activated when AP details are _not_ met +#define TFGA_SPANNER 8 // Activated when hit by an engineer's spanner +#define TFGA_DROPTOGROUND 2048 // Drop to Ground when spawning + +// Defines for Goal Effects types : goal_effect +#define TFGE_AP 1 // AP is affected. Default. +#define TFGE_AP_TEAM 2 // All of the AP's team. +#define TFGE_NOT_AP_TEAM 4 // All except AP's team. +#define TFGE_NOT_AP 8 // All except AP. +#define TFGE_WALL 16 // If set, walls stop the Radius effects +#define TFGE_SAME_ENVIRONMENT 32 // If set, players in a different environment to the Goal are not affected +#define TFGE_TIMER_CHECK_AP 64 // If set, Timer Goals check their critera for all players fitting their effects + +// Defines for Goal Result types : goal_result +#define TFGR_SINGLE 1 // Goal can only be activated once +#define TFGR_ADD_BONUSES 2 // Any Goals activated by this one give their bonuses +#define TFGR_ENDGAME 4 // Goal fires Intermission, displays scores, and ends level +#define TFGR_NO_ITEM_RESULTS 8 // GoalItems given by this Goal don't do results +#define TFGR_REMOVE_DISGUISE 16 // Prevent/Remove undercover from any Spy +#define TFGR_FORCE_RESPAWN 32 // Forces the player to teleport to a respawn point +#define TFGR_DESTROY_BUILDINGS 64 // Destroys this player's buildings, if anys + +// Defines for Goal Group Result types : goal_group +// None! +// But I'm leaving this variable in there, since it's fairly likely +// that some will show up sometime. + +// Defines for Goal Item types, : goal_activation (in items) +#define TFGI_GLOW 1 // Players carrying this GoalItem will glow +#define TFGI_SLOW 2 // Players carrying this GoalItem will move at half-speed +#define TFGI_DROP 4 // Players dying with this item will drop it +#define TFGI_RETURN_DROP 8 // Return if a player with it dies +#define TFGI_RETURN_GOAL 16 // Return if a player with it has it removed by a goal's activation +#define TFGI_RETURN_REMOVE 32 // Return if it is removed by TFGI_REMOVE +#define TFGI_REVERSE_AP 64 // Only pickup if the player _doesn't_ match AP Details +#define TFGI_REMOVE 128 // Remove if left untouched for 2 minutes after being dropped +#define TFGI_KEEP 256 // Players keep this item even when they die +#define TFGI_ITEMGLOWS 512 // Item glows when on the ground +#define TFGI_DONTREMOVERES 1024 // Don't remove results when the item is removed +#define TFGI_DROPTOGROUND 2048 // Drop To Ground when spawning +#define TFGI_CANBEDROPPED 4096 // Can be voluntarily dropped by players +#define TFGI_SOLID 8192 // Is solid... blocks bullets, etc + +// Defines for methods of GoalItem returning +#define GI_RET_DROP_DEAD 0 // Dropped by a dead player +#define GI_RET_DROP_LIVING 1 // Dropped by a living player +#define GI_RET_GOAL 2 // Returned by a Goal +#define GI_RET_TIME 3 // Returned due to timeout + +// Defines for TeamSpawnpoints : goal_activation (in teamspawns) +#define TFSP_MULTIPLEITEMS 1 // Give out the GoalItem multiple times +#define TFSP_MULTIPLEMSGS 2 // Display the message multiple times + +// Defines for TeamSpawnpoints : goal_effects (in teamspawns) +#define TFSP_REMOVESELF 1 // Remove itself after being spawned on + +// Defines for Goal States +#define TFGS_ACTIVE 1 +#define TFGS_INACTIVE 2 +#define TFGS_REMOVED 3 +#define TFGS_DELAYED 4 + +// Defines for GoalItem Removing from Player Methods +#define GI_DROP_PLAYERDEATH 0 // Dropped by a dying player +#define GI_DROP_REMOVEGOAL 1 // Removed by a Goal +#define GI_DROP_PLAYERDROP 2 // Dropped by a player + +// Legal Playerclass Handling +#define TF_ILL_SCOUT 1 +#define TF_ILL_SNIPER 2 +#define TF_ILL_SOLDIER 4 +#define TF_ILL_DEMOMAN 8 +#define TF_ILL_MEDIC 16 +#define TF_ILL_HVYWEP 32 +#define TF_ILL_PYRO 64 +#define TF_ILL_RANDOMPC 128 +#define TF_ILL_SPY 256 +#define TF_ILL_ENGINEER 512 + +// Addition classes +#define CLASS_TFGOAL 128 +#define CLASS_TFGOAL_TIMER 129 +#define CLASS_TFGOAL_ITEM 130 +#define CLASS_TFSPAWN 131 + +/*==========================================================================*/ +/* Flamethrower */ +/*==========================================================================*/ +#define FLAME_PLYRMAXTIME 4.5 // lifetime in seconds of a flame on a player +#define FLAME_MAXBURNTIME 8 // lifetime in seconds of a flame on the world (big ones) +#define NAPALM_MAXBURNTIME 20 // lifetime in seconds of flame from a napalm grenade +#define FLAME_MAXPLYRFLAMES 4 // maximum number of flames on a player +#define FLAME_NUMLIGHTS 1 // maximum number of light flame +#define FLAME_BURNRATIO 0.3 // the chance of a flame not 'sticking' +#define GR_TYPE_FLAMES_NO 15 // number of flames spawned when a grenade explode +#define FLAME_DAMAGE_TIME 1 // Interval between damage burns from flames +#define FLAME_EFFECT_TIME 0.2 // frequency at which we display flame effects. +#define FLAME_THINK_TIME 0.1 // Seconds between times the flame checks burn + +/*==================================================*/ +/* CTF Support defines */ +/*==================================================*/ +#define CTF_FLAG1 1 +#define CTF_FLAG2 2 +#define CTF_DROPOFF1 3 +#define CTF_DROPOFF2 4 +#define CTF_SCORE1 5 +#define CTF_SCORE2 6 + +//.float hook_out; + +/*==================================================*/ +/* Camera defines */ +/*==================================================*/ +/* +float live_camera; +.float camdist; +.vector camangle; +.entity camera_list; +*/ + +/*==================================================*/ +/* QuakeWorld defines */ +/*==================================================*/ +/* +float already_chosen_map; + +// grappling hook variables +.entity hook; +.float on_hook; +.float fire_held_down;// flag - TRUE if player is still holding down the + // fire button after throwing a hook. +*/ +/*==================================================*/ +/* Server Settings */ +/*==================================================*/ +// Admin modes +#define ADMIN_MODE_NONE 0 +#define ADMIN_MODE_DEAL 1 + +/*==================================================*/ +/* Death Message defines */ +/*==================================================*/ +#define DMSG_SHOTGUN 1 +#define DMSG_SSHOTGUN 2 +#define DMSG_NAILGUN 3 +#define DMSG_SNAILGUN 4 +#define DMSG_GRENADEL 5 +#define DMSG_ROCKETL 6 +#define DMSG_LIGHTNING 7 +#define DMSG_GREN_HAND 8 +#define DMSG_GREN_NAIL 9 +#define DMSG_GREN_MIRV 10 +#define DMSG_GREN_PIPE 11 +#define DMSG_DETPACK 12 +#define DMSG_BIOWEAPON 13 +#define DMSG_BIOWEAPON_ATT 14 +#define DMSG_FLAME 15 +#define DMSG_DETPACK_DIS 16 +#define DMSG_AXE 17 +#define DMSG_SNIPERRIFLE 18 +#define DMSG_AUTORIFLE 19 +#define DMSG_ASSAULTCANNON 20 +#define DMSG_HOOK 21 +#define DMSG_BACKSTAB 22 +#define DMSG_MEDIKIT 23 +#define DMSG_GREN_GAS 24 +#define DMSG_TRANQ 25 +#define DMSG_LASERBOLT 26 +#define DMSG_SENTRYGUN_BULLET 27 +#define DMSG_SNIPERLEGSHOT 28 +#define DMSG_SNIPERHEADSHOT 29 +#define DMSG_GREN_EMP 30 +#define DMSG_GREN_EMP_AMMO 31 +#define DMSG_SPANNER 32 +#define DMSG_INCENDIARY 33 +#define DMSG_SENTRYGUN_ROCKET 34 +#define DMSG_GREN_FLASH 35 +#define DMSG_TRIGGER 36 +#define DMSG_MIRROR 37 +#define DMSG_SENTRYDEATH 38 +#define DMSG_DISPENSERDEATH 39 +#define DMSG_GREN_AIRPIPE 40 +#define DMSG_CALTROP 41 + +/*==================================================*/ +// TOGGLEFLAGS +/*==================================================*/ +// Some of the toggleflags aren't used anymore, but the bits are still +// there to provide compatability with old maps +#define TFLAG_CLASS_PERSIST (1 << 0) // Persistent Classes Bit +#define TFLAG_CHEATCHECK (1 << 1) // Cheatchecking Bit +#define TFLAG_RESPAWNDELAY (1 << 2) // RespawnDelay bit +//#define TFLAG_UN (1 << 3) // NOT USED ANYMORE +#define TFLAG_OLD_GRENS (1 << 3) // Use old concussion grenade and flash grenade +#define TFLAG_UN2 (1 << 4) // NOT USED ANYMORE +#define TFLAG_UN3 (1 << 5) // NOT USED ANYMORE +#define TFLAG_UN4 (1 << 6) // NOT USED ANYMORE: Was Autoteam. CVAR tfc_autoteam used now. +#define TFLAG_TEAMFRAGS (1 << 7) // Individual Frags, or Frags = TeamScore +#define TFLAG_FIRSTENTRY (1 << 8) // Used to determine the first time toggleflags is set + // In a map. Cannot be toggled by players. +#define TFLAG_SPYINVIS (1 << 9) // Spy invisible only +#define TFLAG_GRAPPLE (1 << 10) // Grapple on/off +//#define TFLAG_FULLTEAMSCORE (1 << 11) // Each Team's score is TeamScore + Frags +#define TFLAG_FLAGEMULATION (1 << 12) // Flag emulation on for old TF maps +#define TFLAG_USE_STANDARD (1 << 13) // Use the TF War standard for Flag emulation + +#define TFLAG_FRAGSCORING (1 << 14) // Use frag scoring only + +/*======================*/ +// Menu stuff // +/*======================*/ + +#define MENU_DEFAULT 1 +#define MENU_TEAM 2 +#define MENU_CLASS 3 +#define MENU_MAPBRIEFING 4 +#define MENU_INTRO 5 +#define MENU_CLASSHELP 6 +#define MENU_CLASSHELP2 7 +#define MENU_REPEATHELP 8 + + + +#define MENU_SPY 12 +#define MENU_SPY_SKIN 13 +#define MENU_SPY_COLOR 14 +#define MENU_ENGINEER 15 +#define MENU_ENGINEER_FIX_DISPENSER 16 +#define MENU_ENGINEER_FIX_SENTRYGUN 17 +#define MENU_ENGINEER_FIX_MORTAR 18 +#define MENU_DISPENSER 19 +#define MENU_CLASS_CHANGE 20 +#define MENU_TEAM_CHANGE 21 + +#define MENU_REFRESH_RATE 25 + +//============================ +// Timer Types +#define TF_TIMER_ANY 0 +#define TF_TIMER_CONCUSSION 1 +#define TF_TIMER_INFECTION 2 +#define TF_TIMER_HALLUCINATION 3 +#define TF_TIMER_TRANQUILISATION 4 +#define TF_TIMER_ROTHEALTH 5 +#define TF_TIMER_REGENERATION 6 +#define TF_TIMER_GRENPRIME 7 +#define TF_TIMER_CELLREGENERATION 8 +#define TF_TIMER_DETPACKSET 9 +#define TF_TIMER_DETPACKDISARM 10 +#define TF_TIMER_BUILD 11 +#define TF_TIMER_CHECKBUILDDISTANCE 12 +#define TF_TIMER_DISGUISE 13 + +// Non Player timers +#define TF_TIMER_RETURNITEM 100 +#define TF_TIMER_DELAYEDGOAL 101 + +//============================ +// Teamscore printing +#define TS_PRINT_SHORT 1 +#define TS_PRINT_LONG 2 +#define TS_PRINT_LONG_TO_ALL 3 + +#ifndef TF_DEFS_ONLY +/*==================================================*/ +/* GLOBAL VARIABLES */ +/*==================================================*/ +// FortressMap stuff +extern float number_of_teams; // number of teams supported by the map +extern int illegalclasses[5]; // Illegal playerclasses for all teams +extern int civilianteams; // Bitfield holding Civilian teams +extern Vector rgbcolors[5]; // RGB colors for each of the 4 teams +extern int teamcolors[5]; // Colours for each of the 4 teams +extern int teamscores[5]; // Goal Score of each team +extern int g_iOrderedTeams[5]; // Teams ordered into order of winners->losers +extern int teamfrags[5]; // Total Frags for each team +extern int teamlives[5]; // Number of lives each team's players have +extern int teammaxplayers[5]; // Max number of players allowed in each team +extern float teamadvantage[5]; // only used if the teamplay equalisation bits are set + // stores the damage ratio players take/give +extern int teamallies[5]; // Keeps track of which teams are allied +extern string_t team_names[5]; + +extern BOOL CTF_Map; +extern BOOL birthday; +extern BOOL christmas; + +extern float num_world_flames; + +// Clan Battle stuff +extern float clan_scores_dumped; +extern float cb_prematch_time; +extern float fOldPrematch; +extern float fOldCeaseFire; +extern float cb_ceasefire_time; +extern float last_id; +extern float spy_off; +extern float old_grens; +extern float flagem_checked; +extern float flNextEqualisationCalc; +extern BOOL cease_fire; +extern BOOL initial_cease_fire; +extern BOOL last_cease_fire; +// Autokick stuff +extern float autokick_kills; + +extern float deathmsg; // Global, which is set before every T_Damage, to indicate + // the death message that should be used. + +extern char *sTeamSpawnNames[]; +extern char *sClassNames[]; +extern char *sClassModelFiles[]; +extern char *sClassModels[]; +extern char *sClassCfgs[]; +extern char *sGrenadeNames[]; +extern string_t team_menu_string; + +extern int toggleflags; // toggleable flags + +extern CBaseEntity* g_pLastSpawns[5]; +extern BOOL g_bFirstClient; + +extern float g_fNextPrematchAlert; + +typedef struct +{ + int ip; + edict_t *pEdict; +} ip_storage_t; + +extern ip_storage_t g_IpStorage[32]; + +class CGhost; +/*==========================================================================*/ +BOOL ClassIsRestricted(float tno, int pc); +char* GetTeamName(int tno); +int TeamFortress_GetNoPlayers(); +void DestroyBuilding(CBaseEntity *eng, char *bld); +void teamsprint( int tno, CBaseEntity *ignore, int msg_dest, const char *st, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL ); +float anglemod( float v ); + +// Team Funcs +BOOL TeamFortress_TeamIsCivilian(float tno); +void TeamFortress_TeamShowScores(BOOL bLong, CBasePlayer *pPlayer); +BOOL TeamFortress_TeamPutPlayerInTeam(); +void TeamFortress_TeamSetColor(int tno); +void TeamFortress_TeamIncreaseScore(int tno, int scoretoadd); +int TeamFortress_TeamGetScoreFrags(int tno); +int TeamFortress_TeamGetNoPlayers(int tno); +float TeamEqualiseDamage(CBaseEntity *targ, CBaseEntity *attacker, float damage); +BOOL IsSpawnPointValid( Vector &pos ); +BOOL TeamFortress_SortTeams( void ); +void DumpClanScores( void ); +void CalculateTeamEqualiser(); + +// mapscript funcs +void ParseTFServerSettings(); +void ParseTFMapSettings(); +CBaseEntity* Finditem(int ino); +CBaseEntity* Findgoal(int gno); +CBaseEntity* Findteamspawn(int gno); +void RemoveGoal(CBaseEntity *Goal); +void tfgoalitem_GiveToPlayer(CBaseEntity *Item, CBasePlayer *AP, CBaseEntity *Goal); +void dremove( CBaseEntity *te ); +void tfgoalitem_RemoveFromPlayer(CBaseEntity *Item, CBasePlayer *AP, int iMethod); +void tfgoalitem_drop(CBaseEntity *Item, BOOL PAlive, CBasePlayer *P); +void DisplayItemStatus(CBaseEntity *Goal, CBasePlayer *Player, CBaseEntity *Item); +void tfgoalitem_checkgoalreturn(CBaseEntity *Item); +void DoGoalWork(CBaseEntity *Goal, CBasePlayer *AP); +void DoResults(CBaseEntity *Goal, CBasePlayer *AP, BOOL bAddBonuses); +void DoGroupWork(CBaseEntity *Goal, CBasePlayer *AP); +// hooks into the mapscript for all entities +BOOL ActivateDoResults(CBaseEntity *Goal, CBasePlayer *AP, CBaseEntity *ActivatingGoal); +BOOL ActivationSucceeded(CBaseEntity *Goal, CBasePlayer *AP, CBaseEntity *ActivatingGoal); + +// prematch & ceasefire +void Display_Prematch(); +void Check_Ceasefire(); + +// admin +void KickPlayer( CBaseEntity *pTarget ); +void BanPlayer( CBaseEntity *pTarget ); +CGhost *FindGhost( int iGhostID ); +int GetBattleID( edict_t *pEntity ); + +extern cvar_t tfc_spam_penalty1;// the initial gag penalty for a spammer (seconds) +extern cvar_t tfc_spam_penalty2;// incremental gag penalty (seconds) for each time gagged spammer continues to speak. +extern cvar_t tfc_spam_limit; // at this many points, gag the spammer +extern cvar_t tfc_clanbattle, tfc_clanbattle_prematch, tfc_prematch, tfc_clanbattle_ceasefire, tfc_balance_teams, tfc_balance_scores; +extern cvar_t tfc_clanbattle_locked, tfc_birthday, tfc_autokick_kills, tfc_fragscoring, tfc_autokick_time, tfc_adminpwd; +extern cvar_t weaponstay, footsteps, flashlight, aimcrosshair, falldamage, teamplay; + +/*==========================================================================*/ +class CTFFlame : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT FlameThink( void ); + static CTFFlame *FlameSpawn( CBaseEntity *pOwner, CBaseEntity *pTarget ); + void FlameDestroy( void ); + + float m_flNextDamageTime; +}; + +/*==========================================================================*/ +// MAPSCRIPT CLASSES +class CTFGoal : public CBaseAnimating +{ +public: + void Spawn( void ); + void StartGoal( void ); + void EXPORT PlaceGoal( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int Classify ( void ) { return CLASS_TFGOAL; } + + void SetObjectCollisionBox( void ); +}; + +class CTFGoalItem : public CTFGoal +{ +public: + void Spawn( void ); + void StartItem( void ); + void EXPORT PlaceItem( void ); + int Classify ( void ) { return CLASS_TFGOAL_ITEM; } + + float m_flDroppedAt; +}; + +class CTFTimerGoal : public CTFGoal +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_TFGOAL_TIMER; } +}; + +class CTFSpawn : public CBaseEntity +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_TFSPAWN; } +}; + +class CTFDetect : public CBaseEntity +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_TFGOAL; } +}; + +class CTelefragDeath : public CBaseEntity +{ +public: + void Spawn( void ); + void EXPORT DeathTouch( CBaseEntity *pOther ); +}; + +#endif // TF_DEFS_ONLY +#endif // __TF_DEFS_H + + diff --git a/ricochet/cl_dll/train.cpp b/ricochet/cl_dll/train.cpp new file mode 100644 index 0000000..9e15e16 --- /dev/null +++ b/ricochet/cl_dll/train.cpp @@ -0,0 +1,85 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Train.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE(m_Train, Train ) + + +int CHudTrain::Init(void) +{ + HOOK_MESSAGE( Train ); + + m_iPos = 0; + m_iFlags = 0; + gHUD.AddHudElem(this); + + return 1; +}; + +int CHudTrain::VidInit(void) +{ + m_hSprite = 0; + + return 1; +}; + +int CHudTrain::Draw(float fTime) +{ + if ( !m_hSprite ) + m_hSprite = LoadSprite("sprites/%d_train.spr"); + + if (m_iPos) + { + int r, g, b, x, y; + + UnpackRGB(r,g,b, RGB_YELLOWISH); + SPR_Set(m_hSprite, r, g, b ); + + // This should show up to the right and part way up the armor number + y = ScreenHeight - SPR_Height(m_hSprite,0) - gHUD.m_iFontHeight; + x = ScreenWidth/3 + SPR_Width(m_hSprite,0)/4; + + SPR_DrawAdditive( m_iPos - 1, x, y, NULL); + + } + + return 1; +} + + +int CHudTrain::MsgFunc_Train(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + // update Train data + m_iPos = READ_BYTE(); + + if (m_iPos) + m_iFlags |= HUD_ACTIVE; + else + m_iFlags &= ~HUD_ACTIVE; + + return 1; +} diff --git a/ricochet/cl_dll/tri.cpp b/ricochet/cl_dll/tri.cpp new file mode 100644 index 0000000..5290b9e --- /dev/null +++ b/ricochet/cl_dll/tri.cpp @@ -0,0 +1,127 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Triangle rendering, if any + +#include "hud.h" +#include "cl_util.h" + +// Triangle rendering apis are in gEngfuncs.pTriAPI + +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "triangleapi.h" + +extern "C" +{ + void EXPORT HUD_DrawNormalTriangles( void ); + void EXPORT HUD_DrawTransparentTriangles( void ); +}; + +//#define TEST_IT +#if defined( TEST_IT ) + +/* +================= +Draw_Triangles + +Example routine. Draws a sprite offset from the player origin. +================= +*/ +void Draw_Triangles( void ) +{ + cl_entity_t *player; + vec3_t org; + + // Load it up with some bogus data + player = gEngfuncs.GetLocalPlayer(); + if ( !player ) + return; + + org = player->origin; + + org.x += 50; + org.y += 50; + + if (gHUD.m_hsprCursor == 0) + { + char sz[256]; + sprintf( sz, "sprites/cursor.spr" ); + gHUD.m_hsprCursor = SPR_Load( sz ); + } + + if ( !gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *)gEngfuncs.GetSpritePointer( gHUD.m_hsprCursor ), 0 )) + { + return; + } + + // Create a triangle, sigh + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); + gEngfuncs.pTriAPI->Begin( TRI_QUADS ); + // Overload p->color with index into tracer palette, p->packedColor with brightness + gEngfuncs.pTriAPI->Color4f( 1.0, 1.0, 1.0, 1.0 ); + // UNDONE: This gouraud shading causes tracers to disappear on some cards (permedia2) + gEngfuncs.pTriAPI->Brightness( 1 ); + gEngfuncs.pTriAPI->TexCoord2f( 0, 0 ); + gEngfuncs.pTriAPI->Vertex3f( org.x, org.y, org.z ); + + gEngfuncs.pTriAPI->Brightness( 1 ); + gEngfuncs.pTriAPI->TexCoord2f( 0, 1 ); + gEngfuncs.pTriAPI->Vertex3f( org.x, org.y + 50, org.z ); + + gEngfuncs.pTriAPI->Brightness( 1 ); + gEngfuncs.pTriAPI->TexCoord2f( 1, 1 ); + gEngfuncs.pTriAPI->Vertex3f( org.x + 50, org.y + 50, org.z ); + + gEngfuncs.pTriAPI->Brightness( 1 ); + gEngfuncs.pTriAPI->TexCoord2f( 1, 0 ); + gEngfuncs.pTriAPI->Vertex3f( org.x + 50, org.y, org.z ); + + gEngfuncs.pTriAPI->End(); + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); +} + +#endif + +/* +================= +HUD_DrawNormalTriangles + +Non-transparent triangles-- add them here +================= +*/ +void EXPORT HUD_DrawNormalTriangles( void ) +{ + +#if defined( TEST_IT ) +// Draw_Triangles(); +#endif +} + +/* +================= +HUD_DrawTransparentTriangles + +Render any triangles with transparent rendermode needs here +================= +*/ +void EXPORT HUD_DrawTransparentTriangles( void ) +{ + +#if defined( TEST_IT ) +// Draw_Triangles(); +#endif +} \ No newline at end of file diff --git a/ricochet/cl_dll/util.cpp b/ricochet/cl_dll/util.cpp new file mode 100644 index 0000000..883d2b0 --- /dev/null +++ b/ricochet/cl_dll/util.cpp @@ -0,0 +1,101 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// util.cpp +// +// implementation of class-less helper functions +// + +#include "stdio.h" +#include "stdlib.h" +#include "math.h" + +#include "hud.h" +#include "cl_util.h" +#include + +vec3_t vec3_origin( 0, 0, 0 ); + +double sqrt(double x); + +float Length(const float *v) +{ + int i; + float length; + + length = 0; + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); // FIXME + + return length; +} + +float VectorNormalize (float *v) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; + +} + +void VectorInverse ( float *v ) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void VectorScale (const float *in, float scale, float *out) +{ + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; +} + +void VectorMA (const float *veca, float scale, const float *vecb, float *vecc) +{ + vecc[0] = veca[0] + scale*vecb[0]; + vecc[1] = veca[1] + scale*vecb[1]; + vecc[2] = veca[2] + scale*vecb[2]; +} + + +HSPRITE LoadSprite(const char *pszName) +{ + int i; + char sz[256]; + + if (ScreenWidth < 640) + i = 320; + else + i = 640; + + sprintf(sz, pszName, i); + + return SPR_Load(sz); +} + diff --git a/ricochet/cl_dll/util_vector.h b/ricochet/cl_dll/util_vector.h new file mode 100644 index 0000000..1505dc1 --- /dev/null +++ b/ricochet/cl_dll/util_vector.h @@ -0,0 +1,121 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Vector.h +// A subset of the extdll.h in the project HL Entity DLL +// + +// Misc C-runtime library headers +#include "stdio.h" +#include "stdlib.h" +#include "math.h" + +// Header file containing definition of globalvars_t and entvars_t +typedef unsigned int func_t; // +typedef unsigned int string_t; // from engine's pr_comp.h; +typedef float vec_t; // needed before including progdefs.h + +//========================================================= +// 2DVector - used for many pathfinding and many other +// operations that are treated as planar rather than 3d. +//========================================================= +class Vector2D +{ +public: + inline Vector2D(void) { } + inline Vector2D(float X, float Y) { x = X; y = Y; } + inline Vector2D operator+(const Vector2D& v) const { return Vector2D(x+v.x, y+v.y); } + inline Vector2D operator-(const Vector2D& v) const { return Vector2D(x-v.x, y-v.y); } + inline Vector2D operator*(float fl) const { return Vector2D(x*fl, y*fl); } + inline Vector2D operator/(float fl) const { return Vector2D(x/fl, y/fl); } + + inline float Length(void) const { return (float)sqrt(x*x + y*y ); } + + inline Vector2D Normalize ( void ) const + { + Vector2D vec2; + + float flLen = Length(); + if ( flLen == 0 ) + { + return Vector2D( (float)0, (float)0 ); + } + else + { + flLen = 1 / flLen; + return Vector2D( x * flLen, y * flLen ); + } + } + + vec_t x, y; +}; + +inline float DotProduct(const Vector2D& a, const Vector2D& b) { return( a.x*b.x + a.y*b.y ); } +inline Vector2D operator*(float fl, const Vector2D& v) { return v * fl; } + +//========================================================= +// 3D Vector +//========================================================= +class Vector // same data-layout as engine's vec3_t, +{ // which is a vec_t[3] +public: + // Construction/destruction + inline Vector(void) { } + inline Vector(float X, float Y, float Z) { x = X; y = Y; z = Z; } + inline Vector(double X, double Y, double Z) { x = (float)X; y = (float)Y; z = (float)Z; } + inline Vector(int X, int Y, int Z) { x = (float)X; y = (float)Y; z = (float)Z; } + inline Vector(const Vector& v) { x = v.x; y = v.y; z = v.z; } + inline Vector(float rgfl[3]) { x = rgfl[0]; y = rgfl[1]; z = rgfl[2]; } + + // Operators + inline Vector operator-(void) const { return Vector(-x,-y,-z); } + inline int operator==(const Vector& v) const { return x==v.x && y==v.y && z==v.z; } + inline int operator!=(const Vector& v) const { return !(*this==v); } + inline Vector operator+(const Vector& v) const { return Vector(x+v.x, y+v.y, z+v.z); } + inline Vector operator-(const Vector& v) const { return Vector(x-v.x, y-v.y, z-v.z); } + inline Vector operator*(float fl) const { return Vector(x*fl, y*fl, z*fl); } + inline Vector operator/(float fl) const { return Vector(x/fl, y/fl, z/fl); } + + // Methods + inline void CopyToArray(float* rgfl) const { rgfl[0] = x, rgfl[1] = y, rgfl[2] = z; } + inline float Length(void) const { return (float)sqrt(x*x + y*y + z*z); } + operator float *() { return &x; } // Vectors will now automatically convert to float * when needed + operator const float *() const { return &x; } // Vectors will now automatically convert to float * when needed + inline Vector Normalize(void) const + { + float flLen = Length(); + if (flLen == 0) return Vector(0,0,1); // ???? + flLen = 1 / flLen; + return Vector(x * flLen, y * flLen, z * flLen); + } + + inline Vector2D Make2D ( void ) const + { + Vector2D Vec2; + + Vec2.x = x; + Vec2.y = y; + + return Vec2; + } + inline float Length2D(void) const { return (float)sqrt(x*x + y*y); } + + // Members + vec_t x, y, z; +}; +inline Vector operator*(float fl, const Vector& v) { return v * fl; } +inline float DotProduct(const Vector& a, const Vector& b) { return(a.x*b.x+a.y*b.y+a.z*b.z); } +inline Vector CrossProduct(const Vector& a, const Vector& b) { return Vector( a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x ); } + +#define vec3_t Vector diff --git a/ricochet/cl_dll/vgui_ConsolePanel.cpp b/ricochet/cl_dll/vgui_ConsolePanel.cpp new file mode 100644 index 0000000..172ec1f --- /dev/null +++ b/ricochet/cl_dll/vgui_ConsolePanel.cpp @@ -0,0 +1,95 @@ + +#include"vgui_ConsolePanel.h" +#include"hud.h" +#include +#include +#include +#include +#include + +using namespace vgui; + + +namespace +{ + +class Handler : public ActionSignal +{ +private: + + ConsolePanel* _consolePanel; + +public: + + Handler(ConsolePanel* consolePanel) + { + _consolePanel=consolePanel; + } + +public: + + virtual void actionPerformed(Panel* panel) + { + _consolePanel->doExecCommand(); + } + +}; + +} + + + +ConsolePanel::ConsolePanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) +{ + setBorder(new EtchedBorder()); + + _textGrid=new TextGrid(80,21,5,5,200,100); + _textGrid->setBorder(new LoweredBorder()); + _textGrid->setParent(this); + + _textEntry=new TextEntry("",5,5,200,20); + _textEntry->setParent(this); + _textEntry->addActionSignal(new Handler(this)); +} + +int ConsolePanel::print(const char* text) +{ + return _textGrid->printf("%s",text); +} + +int ConsolePanel::vprintf(const char* format,va_list argList) +{ + return _textGrid->vprintf(format,argList); +} + +int ConsolePanel::printf(const char* format,...) +{ + va_list argList; + va_start(argList,format); + int ret=vprintf(format,argList); + va_end(argList); + return ret; +} + +void ConsolePanel::doExecCommand() +{ + char buf[2048]; + _textEntry->getText(0,buf,2048); + _textEntry->setText(null,0); + gEngfuncs.pfnClientCmd(buf); +} + +void ConsolePanel::setSize(int wide,int tall) +{ + Panel::setSize(wide,tall); + + getPaintSize(wide,tall); + + _textGrid->setBounds(5,5,wide-10,tall-35); + _textEntry->setBounds(5,tall-25,wide-10,20); +} + + + + + diff --git a/ricochet/cl_dll/vgui_ConsolePanel.h b/ricochet/cl_dll/vgui_ConsolePanel.h new file mode 100644 index 0000000..7e545e7 --- /dev/null +++ b/ricochet/cl_dll/vgui_ConsolePanel.h @@ -0,0 +1,32 @@ + +#ifndef CONSOLEPANEL_H +#define CONSOLEPANEL_H + +#include +#include + +namespace vgui +{ +class TextGrid; +class TextEntry; +} + + +class ConsolePanel : public vgui::Panel +{ +private: + vgui::TextGrid* _textGrid; + vgui::TextEntry* _textEntry; +public: + ConsolePanel(int x,int y,int wide,int tall); +public: + virtual void setSize(int wide,int tall); + virtual int print(const char* text); + virtual int vprintf(const char* format,va_list argList); + virtual int printf(const char* format,...); + virtual void doExecCommand(); +}; + + + +#endif \ No newline at end of file diff --git a/ricochet/cl_dll/vgui_ControlConfigPanel.cpp b/ricochet/cl_dll/vgui_ControlConfigPanel.cpp new file mode 100644 index 0000000..4c53f5c --- /dev/null +++ b/ricochet/cl_dll/vgui_ControlConfigPanel.cpp @@ -0,0 +1,206 @@ + +#include +#include"vgui_ControlConfigPanel.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace vgui; + +namespace +{ +class FooTablePanel : public TablePanel +{ +private: + Label* _label; + TextEntry* _textEntry; + ControlConfigPanel* _controlConfigPanel; +public: + FooTablePanel(ControlConfigPanel* controlConfigPanel,int x,int y,int wide,int tall,int columnCount) : TablePanel(x,y,wide,tall,columnCount) + { + _controlConfigPanel=controlConfigPanel; + _label=new Label("You are a dumb monkey",0,0,100,20); + _label->setBgColor(Scheme::sc_primary3); + _label->setFgColor(Scheme::sc_primary1); + _label->setFont(Scheme::sf_primary3); + + _textEntry=new TextEntry("",0,0,100,20); + //_textEntry->setFont(Scheme::sf_primary3); + } +public: + virtual int getRowCount() + { + return _controlConfigPanel->GetCVarCount(); + } + virtual int getCellTall(int row) + { + return 12; + } + virtual Panel* getCellRenderer(int column,int row,bool columnSelected,bool rowSelected,bool cellSelected) + { + char cvar[128],desc[128],bind[128],bindAlt[128]; + _controlConfigPanel->GetCVar(row,cvar,128,desc,128); + + if(cellSelected) + { + _label->setBgColor(Scheme::sc_primary1); + _label->setFgColor(Scheme::sc_primary3); + } + else + if(rowSelected) + { + _label->setBgColor(Scheme::sc_primary2); + _label->setFgColor(Scheme::sc_primary1); + } + else + { + _label->setBgColor(Scheme::sc_primary3); + _label->setFgColor(Scheme::sc_primary1); + } + + switch(column) + { + case 0: + { + _label->setText(desc); + _label->setContentAlignment(Label::a_west); + break; + } + case 1: + { + _controlConfigPanel->GetCVarBind(cvar,bind,128,bindAlt,128); + _label->setText(bind); + _label->setContentAlignment(Label::a_center); + break; + } + case 2: + { + _controlConfigPanel->GetCVarBind(cvar,bind,128,bindAlt,128); + _label->setText(bindAlt); + _label->setContentAlignment(Label::a_center); + break; + } + default: + { + _label->setText(""); + break; + } + } + + return _label; + } + virtual Panel* startCellEditing(int column,int row) + { + _textEntry->setText("Goat",strlen("Goat")); + _textEntry->requestFocus(); + return _textEntry; + } +}; +} + +ControlConfigPanel::ControlConfigPanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) +{ + setPaintBorderEnabled(false); + setPaintBackgroundEnabled(false); + setPaintEnabled(false); + + _actionLabel=new Label("Action"); + _actionLabel->setBgColor(Scheme::sc_primary3); + _actionLabel->setFgColor(Scheme::sc_primary3); + + _keyButtonLabel=new Label("Key / Button"); + _keyButtonLabel->setBgColor(Scheme::sc_primary3); + _keyButtonLabel->setFgColor(Scheme::sc_primary3); + + _alternateLabel=new Label("Alternate"); + _alternateLabel->setBgColor(Scheme::sc_primary3); + _alternateLabel->setFgColor(Scheme::sc_primary3); + + _headerPanel=new HeaderPanel(0,0,wide,20); + _headerPanel->setParent(this); + + _headerPanel->addSectionPanel(_actionLabel); + _headerPanel->addSectionPanel(_keyButtonLabel); + _headerPanel->addSectionPanel(_alternateLabel); + + _headerPanel->setSliderPos( 0, wide/2 ); + _headerPanel->setSliderPos( 1, (wide/2) + (wide/4) ); + _headerPanel->setSliderPos( 2, wide ); + + _scrollPanel=new ScrollPanel(0,20,wide,tall-20); + _scrollPanel->setParent(this); + _scrollPanel->setPaintBorderEnabled(false); + _scrollPanel->setPaintBackgroundEnabled(false); + _scrollPanel->setPaintEnabled(false); + _scrollPanel->getClient()->setPaintBorderEnabled(false); + _scrollPanel->getClient()->setPaintBackgroundEnabled(false); + _scrollPanel->getClient()->setPaintEnabled(false); + _scrollPanel->setScrollBarVisible(false,true); + + _tablePanel=new FooTablePanel(this,0,0,_scrollPanel->getClient()->getWide(),800, 3); + _tablePanel->setParent(_scrollPanel->getClient()); + _tablePanel->setHeaderPanel(_headerPanel); + _tablePanel->setBgColor(Color(200,0,0,255)); + _tablePanel->setFgColor(Color(Scheme::sc_primary2)); + _tablePanel->setGridVisible(true,true); + _tablePanel->setGridSize(1,1); +} + +void ControlConfigPanel::AddCVar(const char* cvar,const char* desc) +{ + _cvarDar.addElement(vgui_strdup(cvar)); + _descDar.addElement(vgui_strdup(desc)); +} + +int ControlConfigPanel::GetCVarCount() +{ + return _cvarDar.getCount(); +} + +void ControlConfigPanel::GetCVar(int index,char* cvar,int cvarLen,char* desc,int descLen) +{ + vgui_strcpy(cvar,cvarLen,_cvarDar[index]); + vgui_strcpy(desc,descLen,_descDar[index]); +} + +void ControlConfigPanel::AddCVarFromInputStream(InputStream* is) +{ + if(is==null) + { + return; + } + + DataInputStream dis(is); + + bool success; + + while(1) + { + char buf[256],cvar[128],desc[128]; + dis.readLine(buf,256,success); + if(!success) + { + break; + } + if(sscanf(buf,"\"%[^\"]\" \"%[^\"]\"",cvar,desc)==2) + { + AddCVar(cvar,desc); + } + } +} + +void ControlConfigPanel::GetCVarBind(const char* cvar,char* bind,int bindLen,char* bindAlt,int bindAltLen) +{ + sprintf(bind,"%s : Bind",cvar); + sprintf(bindAlt,"%s : BindAlt",cvar); +} + +void ControlConfigPanel::SetCVarBind(const char* cvar,const char* bind,const char* bindAlt) +{ +} + diff --git a/ricochet/cl_dll/vgui_ControlConfigPanel.h b/ricochet/cl_dll/vgui_ControlConfigPanel.h new file mode 100644 index 0000000..1c362c4 --- /dev/null +++ b/ricochet/cl_dll/vgui_ControlConfigPanel.h @@ -0,0 +1,41 @@ + +#ifndef CONTROLCONFIGPANEL_H +#define CONTROLCONFIGPANEL_H + +#include +#include + +namespace vgui +{ +class HeaderPanel; +class TablePanel; +class ScrollPanel; +class InputStream; +class Label; +} + +class ControlConfigPanel : public vgui::Panel +{ +private: + vgui::HeaderPanel* _headerPanel; + vgui::TablePanel* _tablePanel; + vgui::ScrollPanel* _scrollPanel; + vgui::Dar _cvarDar; + vgui::Dar _descDar; + vgui::Label* _actionLabel; + vgui::Label* _keyButtonLabel; + vgui::Label* _alternateLabel; +public: + ControlConfigPanel(int x,int y,int wide,int tall); +public: + void AddCVar(const char* cvar,const char* desc); + void AddCVarFromInputStream(vgui::InputStream* is); + int GetCVarCount(); + void GetCVar(int index,char* cvar,int cvarLen,char* desc,int descLen); + void GetCVarBind(const char* cvar,char* bind,int bindLen,char* bindAlt,int bindAltLen); + void SetCVarBind(const char* cvar,const char* bind,const char* bindAlt); +}; + + + +#endif \ No newline at end of file diff --git a/ricochet/cl_dll/vgui_CustomObjects.cpp b/ricochet/cl_dll/vgui_CustomObjects.cpp new file mode 100644 index 0000000..9e9c730 --- /dev/null +++ b/ricochet/cl_dll/vgui_CustomObjects.cpp @@ -0,0 +1,501 @@ +//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: Contains implementation of various VGUI-derived objects +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "VGUI_Font.h" + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "parsemsg.h" + +#include "vgui_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" +#include "vgui_loadtga.h" + +// Arrow filenames +char *sArrowFilenames[] = +{ + "arrowup", + "arrowdn", + "arrowlt", + "arrowrt", +}; + +// Get the name of TGA file, without a gamedir +char *GetTGANameForRes(const char *pszName) +{ + int i; + char sz[256]; + static char gd[256]; + if (ScreenWidth < 640) + i = 320; + else + i = 640; + sprintf(sz, pszName, i); + sprintf(gd, "gfx/vgui/%s.tga", sz); + return gd; +} + +//----------------------------------------------------------------------------- +// Purpose: Loads a .tga file and returns a pointer to the VGUI tga object +//----------------------------------------------------------------------------- +BitmapTGA *LoadTGAForRes( const char* pImageName ) +{ + BitmapTGA *pTGA; + + char sz[256]; + sprintf(sz, "%%d_%s", pImageName); + pTGA = vgui_LoadTGA(GetTGANameForRes(sz)); + + return pTGA; +} + +//=========================================================== +// All TFC Hud buttons are derived from this one. +CommandButton::CommandButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight) : Button("",x,y,wide,tall) +{ + m_iPlayerClass = 0; + m_bNoHighlight = bNoHighlight; + Init(); + setText( text ); +} + +CommandButton::CommandButton( int iPlayerClass, const char* text,int x,int y,int wide,int tall) : Button("",x,y,wide,tall) +{ + m_iPlayerClass = iPlayerClass; + m_bNoHighlight = false; + Init(); + setText( text ); +} + +void CommandButton::Init( void ) +{ + m_pSubMenu = NULL; + m_pSubLabel = NULL; + m_pParentMenu = NULL; + + // Set text color to orange + setFgColor(Scheme::sc_primary1); + + // left align + setContentAlignment( vgui::Label::a_west ); + + // Add the Highlight signal + if (!m_bNoHighlight) + addInputSignal( new CHandler_CommandButtonHighlight(this) ); + + // not bound to any button yet + m_cBoundKey = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Prepends the button text with the current bound key +// if no bound key, then a clear space ' ' instead +//----------------------------------------------------------------------------- +void CommandButton::RecalculateText( void ) +{ + char szBuf[128]; + + if ( m_cBoundKey != 0 ) + { + sprintf( szBuf, " %c %s", m_cBoundKey, m_sMainText ); + szBuf[MAX_BUTTON_SIZE-1] = 0; + } + else + { + // just draw a space if no key bound + sprintf( szBuf, " %s", m_sMainText ); + szBuf[MAX_BUTTON_SIZE-1] = 0; + } + + Button::setText( szBuf ); +} + +void CommandButton::setText( const char *text ) +{ + strncpy( m_sMainText, text, MAX_BUTTON_SIZE ); + m_sMainText[MAX_BUTTON_SIZE-1] = 0; + + RecalculateText(); +} + +void CommandButton::setBoundKey( char boundKey ) +{ + m_cBoundKey = boundKey; + RecalculateText(); +} + +char CommandButton::getBoundKey( void ) +{ + return m_cBoundKey; +} + +void CommandButton::AddSubMenu( CCommandMenu *pNewMenu ) +{ + m_pSubMenu = pNewMenu; + + // Prevent this button from being pushed + setMouseClickEnabled( MOUSE_LEFT, false ); +} + +void CommandButton::UpdateSubMenus( int iAdjustment ) +{ + if ( m_pSubMenu ) + m_pSubMenu->RecalculatePositions( iAdjustment ); +} + +void CommandButton::paint() +{ + // Make the sub label paint the same as the button + if ( m_pSubLabel ) + { + if ( isSelected() ) + m_pSubLabel->PushDown(); + else + m_pSubLabel->PushUp(); + } + + // draw armed button text in white + if ( isArmed() ) + { + setFgColor( Scheme::sc_secondary2 ); + } + else + { + setFgColor( Scheme::sc_primary1 ); + } + + Button::paint(); +} + +void CommandButton::paintBackground() +{ + if ( isArmed() ) + { + // Orange highlight background + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect(0,0,_size[0],_size[1]); + } + + // Orange Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect(0,0,_size[0],_size[1]); +} + +//----------------------------------------------------------------------------- +// Purpose: Highlights the current button, and all it's parent menus +//----------------------------------------------------------------------------- +void CommandButton::cursorEntered( void ) +{ + // unarm all the other buttons in this menu + CCommandMenu *containingMenu = getParentMenu(); + if ( containingMenu ) + { + containingMenu->ClearButtonsOfArmedState(); + + // make all our higher buttons armed + CCommandMenu *pCParent = containingMenu->GetParentMenu(); + if ( pCParent ) + { + CommandButton *pParentButton = pCParent->FindButtonWithSubmenu( containingMenu ); + + pParentButton->cursorEntered(); + } + } + + // arm ourselves + setArmed( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CommandButton::cursorExited( void ) +{ + // only clear ourselves if we have do not have a containing menu + // only stay armed if we have a sub menu + // the buttons only unarm themselves when another button is armed instead + if ( !getParentMenu() || !GetSubMenu() ) + { + setArmed( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the command menu that the button is part of, if any +// Output : CCommandMenu * +//----------------------------------------------------------------------------- +CCommandMenu *CommandButton::getParentMenu( void ) +{ + return m_pParentMenu; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the menu that contains this button +// Input : *pParentMenu - +//----------------------------------------------------------------------------- +void CommandButton::setParentMenu( CCommandMenu *pParentMenu ) +{ + m_pParentMenu = pParentMenu; +} + + +//=========================================================== +int ClassButton::IsNotValid() +{ + // If this is the main ChangeClass button, remove it if the player's only able to be civilians + if ( m_iPlayerClass == -1 ) + { + if (gViewPort->GetValidClasses(g_iTeamNumber) == -1) + return true; + + return false; + } + + // Is it an illegal class? + if ((gViewPort->GetValidClasses(0) & sTFValidClassInts[ m_iPlayerClass ]) || (gViewPort->GetValidClasses(g_iTeamNumber) & sTFValidClassInts[ m_iPlayerClass ])) + return true; + + // Only check current class if they've got autokill on + bool bAutoKill = CVAR_GET_FLOAT( "hud_classautokill" ) != 0; + if ( bAutoKill ) + { + // Is it the player's current class? + if ( (gViewPort->IsRandomPC() && m_iPlayerClass == PC_RANDOM) || (!gViewPort->IsRandomPC() && (m_iPlayerClass == g_iPlayerClass)) ) + return true; + } + + return false; +} + +//=========================================================== +// Button with Class image beneath it +CImageLabel::CImageLabel( const char* pImageName,int x,int y ) : Label( "", x,y ) +{ + setContentFitted(true); + m_pTGA = LoadTGAForRes(pImageName); + setImage( m_pTGA ); +} + +CImageLabel::CImageLabel( const char* pImageName,int x,int y,int wide,int tall ) : Label( "", x,y,wide,tall ) +{ + setContentFitted(true); + m_pTGA = LoadTGAForRes(pImageName); + setImage( m_pTGA ); +} + +//=========================================================== +// Image size +int CImageLabel::getImageWide( void ) +{ + if( m_pTGA ) + { + int iXSize, iYSize; + m_pTGA->getSize( iXSize, iYSize ); + return iXSize; + } + else + { + return 1; + } +} + +int CImageLabel::getImageTall( void ) +{ + if( m_pTGA ) + { + int iXSize, iYSize; + m_pTGA->getSize( iXSize, iYSize ); + return iYSize; + } + else + { + return 1; + } +} + +void CImageLabel::LoadImage(const char * pImageName) +{ + if ( m_pTGA ) + delete m_pTGA; + + // Load the Image + m_pTGA = LoadTGAForRes(pImageName); + + if ( m_pTGA == NULL ) + { + // we didn't find a matching image file for this resolution + // try to load file resolution independent + + char sz[256]; + sprintf(sz, "%s/%s",gEngfuncs.pfnGetGameDirectory(), pImageName ); + FileInputStream* fis = new FileInputStream( sz, false ); + m_pTGA = new BitmapTGA(fis,true); + fis->close(); + } + + if ( m_pTGA == NULL ) + return; // unable to load image + + int w,t; + + m_pTGA->getSize( w, t ); + + setSize( XRES (w),YRES (t) ); + setImage( m_pTGA ); +} + +//=========================================================== +// Various overloaded paint functions for Custom VGUI objects +void CCommandMenu::paintBackground() +{ + // Transparent black background + drawSetColor(Scheme::sc_primary3); + drawFilledRect(0,0,_size[0],_size[1]); +} + +//================================================================================= +// CUSTOM SCROLLPANEL +//================================================================================= +CTFScrollButton::CTFScrollButton(int iArrow, const char* text,int x,int y,int wide,int tall) : CommandButton(text,x,y,wide,tall) +{ + // Set text color to orange + setFgColor(Scheme::sc_primary1); + + // Load in the arrow + m_pTGA = LoadTGAForRes( sArrowFilenames[iArrow] ); + setImage( m_pTGA ); + + // Highlight signal + InputSignal *pISignal = new CHandler_CommandButtonHighlight(this); + addInputSignal(pISignal); +} + +void CTFScrollButton::paint( void ) +{ + if (!m_pTGA) + return; + // draw armed button text in white + if ( isArmed() ) + { + m_pTGA->setColor( Color(255,255,255, 0) ); + } + else + { + m_pTGA->setColor( Color(255,255,255, 128) ); + } + + m_pTGA->doPaint(this); +} + +void CTFScrollButton::paintBackground( void ) +{ +/* + if ( isArmed() ) + { + // Orange highlight background + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect(0,0,_size[0],_size[1]); + } + + // Orange Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect(0,0,_size[0]-1,_size[1]); +*/ +} + +void CTFSlider::paintBackground( void ) +{ + int wide,tall,nobx,noby; + getPaintSize(wide,tall); + getNobPos(nobx,noby); + + // Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect( 0,0,wide,tall ); + + if( isVertical() ) + { + // Nob Fill + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect( 0,nobx,wide,noby ); + + // Nob Outline + drawSetColor( Scheme::sc_primary1 ); + drawOutlinedRect( 0,nobx,wide,noby ); + } + else + { + // Nob Fill + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect( nobx,0,noby,tall ); + + // Nob Outline + drawSetColor( Scheme::sc_primary1 ); + drawOutlinedRect( nobx,0,noby,tall ); + } +} + +CTFScrollPanel::CTFScrollPanel(int x,int y,int wide,int tall) : ScrollPanel(x,y,wide,tall) +{ + ScrollBar *pScrollBar = getVerticalScrollBar(); + pScrollBar->setButton( new CTFScrollButton( ARROW_UP, "", 0,0,16,16 ), 0 ); + pScrollBar->setButton( new CTFScrollButton( ARROW_DOWN, "", 0,0,16,16 ), 1 ); + pScrollBar->setSlider( new CTFSlider(0,wide-1,wide,(tall-(wide*2))+2,true) ); + pScrollBar->setPaintBorderEnabled(false); + pScrollBar->setPaintBackgroundEnabled(false); + pScrollBar->setPaintEnabled(false); + + pScrollBar = getHorizontalScrollBar(); + pScrollBar->setButton( new CTFScrollButton( ARROW_LEFT, "", 0,0,16,16 ), 0 ); + pScrollBar->setButton( new CTFScrollButton( ARROW_RIGHT, "", 0,0,16,16 ), 1 ); + pScrollBar->setSlider( new CTFSlider(tall,0,wide-(tall*2),tall,false) ); + pScrollBar->setPaintBorderEnabled(false); + pScrollBar->setPaintBackgroundEnabled(false); + pScrollBar->setPaintEnabled(false); +} + + +//================================================================================= +// CUSTOM HANDLERS +//================================================================================= +void CHandler_MenuButtonOver::cursorEntered(Panel *panel) +{ + if ( gViewPort && m_pMenuPanel ) + { + m_pMenuPanel->SetActiveInfo( m_iButton ); + } +} + +void CMenuHandler_StringCommandClassSelect::actionPerformed(Panel* panel) +{ + CMenuHandler_StringCommand::actionPerformed( panel ); + + bool bAutoKill = CVAR_GET_FLOAT( "hud_classautokill" ) != 0; + if ( bAutoKill && g_iPlayerClass != 0 ) + gEngfuncs.pfnClientCmd("kill"); +} + + diff --git a/ricochet/cl_dll/vgui_MOTDWindow.cpp b/ricochet/cl_dll/vgui_MOTDWindow.cpp new file mode 100644 index 0000000..ae2f026 --- /dev/null +++ b/ricochet/cl_dll/vgui_MOTDWindow.cpp @@ -0,0 +1,154 @@ +//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "VGUI_Font.h" +#include "VGUI_ScrollPanel.h" +#include "VGUI_TextImage.h" + +#include + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "const.h" + +#include "vgui_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" + +#define MOTD_TITLE_X XRES(16) +#define MOTD_TITLE_Y YRES(16) + +#define MOTD_WINDOW_X XRES(112) +#define MOTD_WINDOW_Y YRES(80) +#define MOTD_WINDOW_SIZE_X XRES(424) +#define MOTD_WINDOW_SIZE_Y YRES(312) + +//----------------------------------------------------------------------------- +// Purpose: Displays the MOTD and basic server information +//----------------------------------------------------------------------------- +class CMessageWindowPanel : public CMenuPanel +{ +public: + CMessageWindowPanel( const char *szMOTD, const char *szTitle, int iShadeFullScreen, int iRemoveMe, int x, int y, int wide, int tall ); + +private: + CTransparentPanel *m_pBackgroundPanel; + +}; + +//----------------------------------------------------------------------------- +// Purpose: Creates a new CMessageWindowPanel +// Output : CMenuPanel - interface to the panel +//----------------------------------------------------------------------------- +CMenuPanel *CMessageWindowPanel_Create( const char *szMOTD, const char *szTitle, int iShadeFullscreen, int iRemoveMe, int x, int y, int wide, int tall ) +{ + return new CMessageWindowPanel( szMOTD, szTitle, iShadeFullscreen, iRemoveMe, x, y, wide, tall ); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructs a message panel +//----------------------------------------------------------------------------- +CMessageWindowPanel::CMessageWindowPanel( const char *szMOTD, const char *szTitle, int iShadeFullscreen, int iRemoveMe, int x, int y, int wide, int tall ) : CMenuPanel( iShadeFullscreen ? 100 : 255, iRemoveMe, x, y, wide, tall ) +{ + // Get the scheme used for the Titles + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + + // schemes + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle( "Title Font" ); + SchemeHandle_t hMOTDText = pSchemes->getSchemeHandle( "Briefing Text" ); + + // color schemes + int r, g, b, a; + + // Create the window + m_pBackgroundPanel = new CTransparentPanel( iShadeFullscreen ? 255 : 100, MOTD_WINDOW_X, MOTD_WINDOW_Y, MOTD_WINDOW_SIZE_X, MOTD_WINDOW_SIZE_Y ); + m_pBackgroundPanel->setParent( this ); + m_pBackgroundPanel->setBorder( new LineBorder( Color(255 * 0.7,170 * 0.7,0,0)) ); + m_pBackgroundPanel->setVisible( true ); + + int iXSize,iYSize,iXPos,iYPos; + m_pBackgroundPanel->getPos( iXPos,iYPos ); + m_pBackgroundPanel->getSize( iXSize,iYSize ); + + // Create the title + Label *pLabel = new Label( "", iXPos + MOTD_TITLE_X, iYPos + MOTD_TITLE_Y ); + pLabel->setParent( this ); + pLabel->setFont( pSchemes->getFont(hTitleScheme) ); + pLabel->setFont( Scheme::sf_primary1 ); + + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + pLabel->setFgColor( r, g, b, a ); + pLabel->setFgColor( Scheme::sc_primary1 ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + pLabel->setBgColor( r, g, b, a ); + pLabel->setContentAlignment( vgui::Label::a_west ); + pLabel->setText(szTitle); + + // Create the Scroll panel + ScrollPanel *pScrollPanel = new CTFScrollPanel( iXPos + XRES(16), iYPos + MOTD_TITLE_Y*2 + YRES(16), iXSize - XRES(32), iYSize - (YRES(48) + BUTTON_SIZE_Y*2) ); + pScrollPanel->setParent(this); + + //force the scrollbars on so clientClip will take them in account after the validate + pScrollPanel->setScrollBarAutoVisible(false, false); + pScrollPanel->setScrollBarVisible(true, true); + pScrollPanel->validate(); + + // Create the text panel + TextPanel *pText = new TextPanel( "", 0,0, 64,64); + pText->setParent( pScrollPanel->getClient() ); + + // get the font and colors from the scheme + pText->setFont( pSchemes->getFont(hMOTDText) ); + pSchemes->getFgColor( hMOTDText, r, g, b, a ); + pText->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hMOTDText, r, g, b, a ); + pText->setBgColor( r, g, b, a ); + pText->setText(szMOTD); + + // Get the total size of the MOTD text and resize the text panel + int iScrollSizeX, iScrollSizeY; + + // First, set the size so that the client's wdith is correct at least because the + // width is critical for getting the "wrapped" size right. + // You'll see a horizontal scroll bar if there is a single word that won't wrap in the + // specified width. + pText->getTextImage()->setSize(pScrollPanel->getClientClip()->getWide(), pScrollPanel->getClientClip()->getTall()); + pText->getTextImage()->getTextSizeWrapped( iScrollSizeX, iScrollSizeY ); + + // Now resize the textpanel to fit the scrolled size + pText->setSize( iScrollSizeX , iScrollSizeY ); + + //turn the scrollbars back into automode + pScrollPanel->setScrollBarAutoVisible(true, true); + pScrollPanel->setScrollBarVisible(false, false); + + pScrollPanel->validate(); + + CommandButton *pButton = new CommandButton( CHudTextMessage::BufferedLocaliseTextString( "#Menu_OK" ), iXPos + XRES(16), iYPos + iYSize - YRES(16) - BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_TextWindow(HIDE_TEXTWINDOW)); + pButton->setParent(this); + +} + + + + + + diff --git a/ricochet/cl_dll/vgui_SchemeManager.cpp b/ricochet/cl_dll/vgui_SchemeManager.cpp new file mode 100644 index 0000000..c61d1f7 --- /dev/null +++ b/ricochet/cl_dll/vgui_SchemeManager.cpp @@ -0,0 +1,556 @@ +//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "hud.h" +#include "vgui_SchemeManager.h" +#include "cvardef.h" + +#include + + +cvar_t *g_CV_BitmapFonts; + + +void Scheme_Init() +{ + g_CV_BitmapFonts = gEngfuncs.pfnRegisterVariable("bitmapfonts", "1", 0); +} + + + +//----------------------------------------------------------------------------- +// Purpose: Scheme managers data container +//----------------------------------------------------------------------------- +class CSchemeManager::CScheme +{ +public: + enum { + SCHEME_NAME_LENGTH = 32, + FONT_NAME_LENGTH = 48, + FONT_FILENAME_LENGTH = 64, + }; + + // name + char schemeName[SCHEME_NAME_LENGTH]; + + // font + char fontName[FONT_NAME_LENGTH]; + + int fontSize; + int fontWeight; + + vgui::Font *font; + int ownFontPointer; // true if the font is ours to delete + + // scheme + byte fgColor[4]; + byte bgColor[4]; + byte armedFgColor[4]; + byte armedBgColor[4]; + byte mousedownFgColor[4]; + byte mousedownBgColor[4]; + byte borderColor[4]; + + // construction/destruction + CScheme(); + ~CScheme(); +}; + +CSchemeManager::CScheme::CScheme() +{ + schemeName[0] = 0; + fontName[0] = 0; + fontSize = 0; + fontWeight = 0; + font = NULL; + ownFontPointer = false; +} + +CSchemeManager::CScheme::~CScheme() +{ + // only delete our font pointer if we own it + if ( ownFontPointer ) + { + delete font; + } +} + +//----------------------------------------------------------------------------- +// Purpose: resolution information +// !! needs to be shared out +//----------------------------------------------------------------------------- +static int g_ResArray[] = +{ + 320, + 400, + 512, + 640, + 800, + 1024, + 1152, + 1280, + 1600 +}; +static int g_NumReses = sizeof(g_ResArray) / sizeof(int); + +static byte *LoadFileByResolution( const char *filePrefix, int xRes, const char *filePostfix ) +{ + // find our resolution in the res array + int resNum = g_NumReses - 1; + while ( g_ResArray[resNum] > xRes ) + { + resNum--; + + if ( resNum < 0 ) + return NULL; + } + + // try open the file + byte *pFile = NULL; + while ( 1 ) + { + + // try load + char fname[256]; + sprintf( fname, "%s%d%s", filePrefix, g_ResArray[resNum], filePostfix ); + pFile = gEngfuncs.COM_LoadFile( fname, 5, NULL ); + + if ( pFile ) + break; + + if ( resNum == 0 ) + return NULL; + + resNum--; + }; + + return pFile; +} + +static void ParseRGBAFromString( byte colorArray[4], const char *colorVector ) +{ + int r, g, b, a; + sscanf( colorVector, "%d %d %d %d", &r, &g, &b, &a ); + colorArray[0] = r; + colorArray[1] = g; + colorArray[2] = b; + colorArray[3] = a; +} + +//----------------------------------------------------------------------------- +// Purpose: initializes the scheme manager +// loading the scheme files for the current resolution +// Input : xRes - +// yRes - dimensions of output window +//----------------------------------------------------------------------------- +CSchemeManager::CSchemeManager( int xRes, int yRes ) +{ + // basic setup + m_pSchemeList = NULL; + m_iNumSchemes = 0; + + // find the closest matching scheme file to our resolution + char token[1024]; + char *pFile = (char*)LoadFileByResolution( "", xRes, "_textscheme.txt" ); + m_xRes = xRes; + + char *pFileStart = pFile; + + byte *pFontData; + int fontFileLength; + char fontFilename[512]; + + // + // Read the scheme descriptions from the text file, into a temporary array + // format is simply: + // = + // + // a of "SchemeName" signals a new scheme is being described + // + + const static int numTmpSchemes = 64; + static CScheme tmpSchemes[numTmpSchemes]; + memset( tmpSchemes, 0, sizeof(tmpSchemes) ); + int currentScheme = -1; + CScheme *pScheme = NULL; + + if ( !pFile ) + { + gEngfuncs.Con_DPrintf( "Unable to find *_textscheme.txt\n"); + goto buildDefaultFont; + } + + // record what has been entered so we can create defaults from the different values + bool hasFgColor, hasBgColor, hasArmedFgColor, hasArmedBgColor, hasMouseDownFgColor, hasMouseDownBgColor; + + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + while ( strlen(token) > 0 && (currentScheme < numTmpSchemes) ) + { + // get the paramName name + static const int tokenSize = 64; + char paramName[tokenSize], paramValue[tokenSize]; + + strncpy( paramName, token, tokenSize ); + paramName[tokenSize-1] = 0; // ensure null termination + + // get the '=' character + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + if ( stricmp( token, "=" ) ) + { + if ( currentScheme < 0 ) + { + gEngfuncs.Con_Printf( "error parsing font scheme text file at file start - expected '=', found '%s''\n", token ); + } + else + { + gEngfuncs.Con_Printf( "error parsing font scheme text file at scheme '%s' - expected '=', found '%s''\n", tmpSchemes[currentScheme].schemeName, token ); + } + break; + } + + // get paramValue + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + strncpy( paramValue, token, tokenSize ); + paramValue[tokenSize-1] = 0; // ensure null termination + + // is this a new scheme? + if ( !stricmp(paramName, "SchemeName") ) + { + // setup the defaults for the current scheme + if ( pScheme ) + { + // foreground color defaults (normal -> armed -> mouse down) + if ( !hasFgColor ) + { + pScheme->fgColor[0] = pScheme->fgColor[1] = pScheme->fgColor[2] = pScheme->fgColor[3] = 255; + } + if ( !hasArmedFgColor ) + { + memcpy( pScheme->armedFgColor, pScheme->fgColor, sizeof(pScheme->armedFgColor) ); + } + if ( !hasMouseDownFgColor ) + { + memcpy( pScheme->mousedownFgColor, pScheme->armedFgColor, sizeof(pScheme->mousedownFgColor) ); + } + + // background color (normal -> armed -> mouse down) + if ( !hasBgColor ) + { + pScheme->bgColor[0] = pScheme->bgColor[1] = pScheme->bgColor[2] = pScheme->bgColor[3] = 0; + } + if ( !hasArmedBgColor ) + { + memcpy( pScheme->armedBgColor, pScheme->bgColor, sizeof(pScheme->armedBgColor) ); + } + if ( !hasMouseDownBgColor ) + { + memcpy( pScheme->mousedownBgColor, pScheme->armedBgColor, sizeof(pScheme->mousedownBgColor) ); + } + + // font size + if ( !pScheme->fontSize ) + { + pScheme->fontSize = 17; + } + if ( !pScheme->fontName[0] ) + { + strcpy( pScheme->fontName, "Arial" ); + } + } + + // create the new scheme + currentScheme++; + pScheme = &tmpSchemes[currentScheme]; + hasFgColor = hasBgColor = hasArmedFgColor = hasArmedBgColor = hasMouseDownFgColor = hasMouseDownBgColor = false; + + strncpy( pScheme->schemeName, paramValue, CScheme::SCHEME_NAME_LENGTH ); + pScheme->schemeName[CScheme::SCHEME_NAME_LENGTH-1] = '\0'; // ensure null termination of string + } + + if ( !pScheme ) + { + gEngfuncs.Con_Printf( "font scheme text file MUST start with a 'SchemeName'\n"); + break; + } + + // pull the data out into the scheme + if ( !stricmp(paramName, "FontName") ) + { + strncpy( pScheme->fontName, paramValue, CScheme::FONT_NAME_LENGTH ); + pScheme->fontName[CScheme::FONT_NAME_LENGTH-1] = 0; + } + else if ( !stricmp(paramName, "FontSize") ) + { + pScheme->fontSize = atoi( paramValue ); + } + else if ( !stricmp(paramName, "FontWeight") ) + { + pScheme->fontWeight = atoi( paramValue ); + } + else if ( !stricmp(paramName, "FgColor") ) + { + ParseRGBAFromString( pScheme->fgColor, paramValue ); + hasFgColor = true; + } + else if ( !stricmp(paramName, "BgColor") ) + { + ParseRGBAFromString( pScheme->bgColor, paramValue ); + hasBgColor = true; + } + else if ( !stricmp(paramName, "FgColorArmed") ) + { + ParseRGBAFromString( pScheme->armedFgColor, paramValue ); + hasArmedFgColor = true; + } + else if ( !stricmp(paramName, "BgColorArmed") ) + { + ParseRGBAFromString( pScheme->armedBgColor, paramValue ); + hasArmedBgColor = true; + } + else if ( !stricmp(paramName, "FgColorMousedown") ) + { + ParseRGBAFromString( pScheme->mousedownFgColor, paramValue ); + hasMouseDownFgColor = true; + } + else if ( !stricmp(paramName, "BgColorMousedown") ) + { + ParseRGBAFromString( pScheme->mousedownBgColor, paramValue ); + hasMouseDownBgColor = true; + } + else if ( !stricmp(paramName, "BorderColor") ) + { + ParseRGBAFromString( pScheme->borderColor, paramValue ); + hasMouseDownBgColor = true; + } + + // get the new token last, so we now if the loop needs to be continued or not + pFile = gEngfuncs.COM_ParseFile( pFile, token ); + } + + // free the file + gEngfuncs.COM_FreeFile( pFileStart ); + + +buildDefaultFont: + + // make sure we have at least 1 valid font + if ( currentScheme < 0 ) + { + currentScheme = 0; + strcpy( tmpSchemes[0].schemeName, "Default Scheme" ); + strcpy( tmpSchemes[0].fontName, "Arial" ); + tmpSchemes[0].fontSize = 0; + tmpSchemes[0].fgColor[0] = tmpSchemes[0].fgColor[1] = tmpSchemes[0].fgColor[2] = tmpSchemes[0].fgColor[3] = 255; + tmpSchemes[0].armedFgColor[0] = tmpSchemes[0].armedFgColor[1] = tmpSchemes[0].armedFgColor[2] = tmpSchemes[0].armedFgColor[3] = 255; + tmpSchemes[0].mousedownFgColor[0] = tmpSchemes[0].mousedownFgColor[1] = tmpSchemes[0].mousedownFgColor[2] = tmpSchemes[0].mousedownFgColor[3] = 255; + } + + // we have the full list of schemes in the tmpSchemes array + // now allocate the correct sized list + m_iNumSchemes = currentScheme + 1; // 0-based index + m_pSchemeList = new CScheme[ m_iNumSchemes ]; + + // copy in the data + memcpy( m_pSchemeList, tmpSchemes, sizeof(CScheme) * m_iNumSchemes ); + + // create the fonts + for ( int i = 0; i < m_iNumSchemes; i++ ) + { + m_pSchemeList[i].font = NULL; + + // see if the current font values exist in a previously loaded font + for ( int j = 0; j < i; j++ ) + { + // check if the font name, size, and weight are the same + if ( !stricmp(m_pSchemeList[i].fontName, m_pSchemeList[j].fontName) + && m_pSchemeList[i].fontSize == m_pSchemeList[j].fontSize + && m_pSchemeList[i].fontWeight == m_pSchemeList[j].fontWeight ) + { + // copy the pointer, but mark i as not owning it + m_pSchemeList[i].font = m_pSchemeList[j].font; + m_pSchemeList[i].ownFontPointer = false; + } + } + + // if we haven't found the font already, load it ourselves + if ( !m_pSchemeList[i].font ) + { + fontFileLength = -1; + pFontData = NULL; + + if(g_CV_BitmapFonts && g_CV_BitmapFonts->value) + { + int fontRes = 640; + if ( m_xRes >= 1600 ) + fontRes = 1600; + else if ( m_xRes >= 1280 ) + fontRes = 1280; + else if ( m_xRes >= 1152 ) + fontRes = 1152; + else if ( m_xRes >= 1024 ) + fontRes = 1024; + else if ( m_xRes >= 800 ) + fontRes = 800; + + sprintf(fontFilename, "gfx\\vgui\\fonts\\%d_%s.tga", fontRes, m_pSchemeList[i].schemeName); + pFontData = gEngfuncs.COM_LoadFile( fontFilename, 5, &fontFileLength ); + if(!pFontData) + gEngfuncs.Con_Printf("Missing bitmap font: %s\n", fontFilename); + } + + m_pSchemeList[i].font = new vgui::Font( + m_pSchemeList[i].fontName, + pFontData, + fontFileLength, + m_pSchemeList[i].fontSize, + 0, + 0, + m_pSchemeList[i].fontWeight, + false, + false, + false, + false); + + m_pSchemeList[i].ownFontPointer = true; + } + + // fix up alpha values; VGUI uses 1-A (A=0 being solid, A=255 transparent) + m_pSchemeList[i].fgColor[3] = 255 - m_pSchemeList[i].fgColor[3]; + m_pSchemeList[i].bgColor[3] = 255 - m_pSchemeList[i].bgColor[3]; + m_pSchemeList[i].armedFgColor[3] = 255 - m_pSchemeList[i].armedFgColor[3]; + m_pSchemeList[i].armedBgColor[3] = 255 - m_pSchemeList[i].armedBgColor[3]; + m_pSchemeList[i].mousedownFgColor[3] = 255 - m_pSchemeList[i].mousedownFgColor[3]; + m_pSchemeList[i].mousedownBgColor[3] = 255 - m_pSchemeList[i].mousedownBgColor[3]; + } +} + +//----------------------------------------------------------------------------- +// Purpose: frees all the memory used by the scheme manager +//----------------------------------------------------------------------------- +CSchemeManager::~CSchemeManager() +{ + delete [] m_pSchemeList; + m_iNumSchemes = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Finds a scheme in the list, by name +// Input : char *schemeName - string name of the scheme +// Output : SchemeHandle_t handle to the scheme +//----------------------------------------------------------------------------- +SchemeHandle_t CSchemeManager::getSchemeHandle( const char *schemeName ) +{ + // iterate through the list + for ( int i = 0; i < m_iNumSchemes; i++ ) + { + if ( !stricmp(schemeName, m_pSchemeList[i].schemeName) ) + return i; + } + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: always returns a valid scheme handle +// Input : schemeHandle - +// Output : CScheme +//----------------------------------------------------------------------------- +CSchemeManager::CScheme *CSchemeManager::getSafeScheme( SchemeHandle_t schemeHandle ) +{ + if ( schemeHandle < m_iNumSchemes ) + return m_pSchemeList + schemeHandle; + + return m_pSchemeList; +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns the schemes pointer to a font +// Input : schemeHandle - +// Output : vgui::Font +//----------------------------------------------------------------------------- +vgui::Font *CSchemeManager::getFont( SchemeHandle_t schemeHandle ) +{ + return getSafeScheme( schemeHandle )->font; +} + +void CSchemeManager::getFgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->fgColor[0]; + g = pScheme->fgColor[1]; + b = pScheme->fgColor[2]; + a = pScheme->fgColor[3]; +} + +void CSchemeManager::getBgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->bgColor[0]; + g = pScheme->bgColor[1]; + b = pScheme->bgColor[2]; + a = pScheme->bgColor[3]; +} + +void CSchemeManager::getFgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->armedFgColor[0]; + g = pScheme->armedFgColor[1]; + b = pScheme->armedFgColor[2]; + a = pScheme->armedFgColor[3]; +} + +void CSchemeManager::getBgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->armedBgColor[0]; + g = pScheme->armedBgColor[1]; + b = pScheme->armedBgColor[2]; + a = pScheme->armedBgColor[3]; +} + +void CSchemeManager::getFgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->mousedownFgColor[0]; + g = pScheme->mousedownFgColor[1]; + b = pScheme->mousedownFgColor[2]; + a = pScheme->mousedownFgColor[3]; +} + +void CSchemeManager::getBgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->mousedownBgColor[0]; + g = pScheme->mousedownBgColor[1]; + b = pScheme->mousedownBgColor[2]; + a = pScheme->mousedownBgColor[3]; +} + +void CSchemeManager::getBorderColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + CScheme *pScheme = getSafeScheme( schemeHandle ); + r = pScheme->borderColor[0]; + g = pScheme->borderColor[1]; + b = pScheme->borderColor[2]; + a = pScheme->borderColor[3]; +} + + + diff --git a/ricochet/cl_dll/vgui_SchemeManager.h b/ricochet/cl_dll/vgui_SchemeManager.h new file mode 100644 index 0000000..b1da639 --- /dev/null +++ b/ricochet/cl_dll/vgui_SchemeManager.h @@ -0,0 +1,47 @@ +#include + + +// handle to an individual scheme +typedef int SchemeHandle_t; + + +// Register console variables, etc.. +void Scheme_Init(); + + +//----------------------------------------------------------------------------- +// Purpose: Handles the loading of text scheme description from disk +// supports different font/color/size schemes at different resolutions +//----------------------------------------------------------------------------- +class CSchemeManager +{ +public: + // initialization + CSchemeManager( int xRes, int yRes ); + virtual ~CSchemeManager(); + + // scheme handling + SchemeHandle_t getSchemeHandle( const char *schemeName ); + + // getting info from schemes + vgui::Font *getFont( SchemeHandle_t schemeHandle ); + void getFgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getFgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getFgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBorderColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + +private: + class CScheme; + CScheme *m_pSchemeList; + int m_iNumSchemes; + + // Resolution we were initted at. + int m_xRes; + + CScheme *getSafeScheme( SchemeHandle_t schemeHandle ); +}; + + diff --git a/ricochet/cl_dll/vgui_ScorePanel.cpp b/ricochet/cl_dll/vgui_ScorePanel.cpp new file mode 100644 index 0000000..d061029 --- /dev/null +++ b/ricochet/cl_dll/vgui_ScorePanel.cpp @@ -0,0 +1,1145 @@ +//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: VGUI scoreboard +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + + +#include + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ScorePanel.h" +#include "vgui_helpers.h" +#include "vgui_loadtga.h" +//#include "ITrackerUser.h" +//extern ITrackerUser *g_pTrackerUser; + +hud_player_info_t g_PlayerInfoList[MAX_PLAYERS+1]; // player info from the engine +extra_player_info_t g_PlayerExtraInfo[MAX_PLAYERS+1]; // additional player info sent directly to the client dll +team_info_t g_TeamInfo[MAX_TEAMS+1]; +int g_IsSpectator[MAX_PLAYERS+1]; + +int HUD_IsGame( const char *game ); +int EV_TFC_IsAllyTeam( int iTeam1, int iTeam2 ){ return 1; } + +int iNumberOfTeamColors = 33; + +// Scoreboard dimensions +#define SBOARD_TITLE_SIZE_Y YRES(22) + +#define X_BORDER XRES(4) + +// Column sizes +class SBColumnInfo +{ +public: + char *m_pTitle; // If null, ignore, if starts with #, it's localized, otherwise use the string directly. + int m_Width; // Based on 640 width. Scaled to fit other resolutions. + Label::Alignment m_Alignment; +}; + +// grid size is marked out for 640x480 screen + +SBColumnInfo g_ColumnInfo[NUM_COLUMNS] = +{ + {NULL, 24, Label::a_east}, // tracker column + {NULL, 140, Label::a_east}, // name + {"#QUEUE", 56, Label::a_east}, // class + {"#WINS", 40, Label::a_east}, + {"#POINTS", 46, Label::a_east}, + {"#LATENCY", 46, Label::a_east}, + {"#VOICE", 40, Label::a_east}, + {NULL, 2, Label::a_east}, // blank column to take up the slack +}; + +extern float g_iaDiscColors[33][3]; + +#define TEAM_NO 0 +#define TEAM_YES 1 +#define TEAM_UNASSIGNED 2 +#define TEAM_SPECTATORS 3 + + +//----------------------------------------------------------------------------- +// ScorePanel::HitTestPanel. +//----------------------------------------------------------------------------- + +void ScorePanel::HitTestPanel::internalMousePressed(MouseCode code) +{ + for(int i=0;i<_inputSignalDar.getCount();i++) + { + _inputSignalDar[i]->mousePressed(code,this); + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: Create the ScoreBoard panel +//----------------------------------------------------------------------------- +ScorePanel::ScorePanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) +{ + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle("Scoreboard Title Text"); + SchemeHandle_t hSmallScheme = pSchemes->getSchemeHandle("Scoreboard Small Text"); + Font *tfont = pSchemes->getFont(hTitleScheme); + Font *smallfont = pSchemes->getFont(hSmallScheme); + + setBgColor(0, 0, 0, 96); + m_pCurrentHighlightLabel = NULL; + m_iHighlightRow = -1; + + m_pTrackerIcon = vgui_LoadTGANoInvertAlpha("gfx/vgui/640_scoreboardtracker.tga"); + + // Initialize the top title. + m_TitleLabel.setFont(tfont); + m_TitleLabel.setText(""); + m_TitleLabel.setBgColor( 0, 0, 0, 255 ); + m_TitleLabel.setFgColor( Scheme::sc_primary1 ); + m_TitleLabel.setContentAlignment( vgui::Label::a_west ); + + LineBorder *border = new LineBorder(Color(60, 60, 60, 128)); + setBorder(border); + setPaintBorderEnabled(true); + + int xpos = g_ColumnInfo[0].m_Width + 3; + if (ScreenWidth >= 640) + { + // only expand column size for res greater than 640 + xpos = XRES(xpos); + } + m_TitleLabel.setBounds(xpos, 4, wide, SBOARD_TITLE_SIZE_Y); + m_TitleLabel.setContentFitted(false); + m_TitleLabel.setParent(this); + + // Setup the header (labels like "name", "class", etc..). + m_HeaderGrid.SetDimensions(NUM_COLUMNS, 1); + m_HeaderGrid.SetSpacing(0, 0); + + for(int i=0; i < NUM_COLUMNS; i++) + { + if (g_ColumnInfo[i].m_pTitle && g_ColumnInfo[i].m_pTitle[0] == '#') + m_HeaderLabels[i].setText(CHudTextMessage::BufferedLocaliseTextString(g_ColumnInfo[i].m_pTitle)); + else if(g_ColumnInfo[i].m_pTitle) + m_HeaderLabels[i].setText(g_ColumnInfo[i].m_pTitle); + + int xwide = g_ColumnInfo[i].m_Width; + if (ScreenWidth >= 640) + { + xwide = XRES(xwide); + } + else if (ScreenWidth == 400) + { + // hack to make 400x300 resolution scoreboard fit + if (i == 1) + { + // reduces size of player name cell + xwide -= 28; + } + else if (i == 0) + { + // tracker icon cell + xwide -= 8; + } + } + + m_HeaderGrid.SetColumnWidth(i, xwide); + m_HeaderGrid.SetEntry(i, 0, &m_HeaderLabels[i]); + + m_HeaderLabels[i].setBgColor(0,0,0,255); + m_HeaderLabels[i].setFgColor(Scheme::sc_primary1); + m_HeaderLabels[i].setFont(smallfont); + m_HeaderLabels[i].setContentAlignment(g_ColumnInfo[i].m_Alignment); + + int yres = 12; + if (ScreenHeight >= 480) + { + yres = YRES(yres); + } + m_HeaderLabels[i].setSize(50, yres); + } + + // Set the width of the last column to be the remaining space. + int ex, ey, ew, eh; + m_HeaderGrid.GetEntryBox(NUM_COLUMNS - 2, 0, ex, ey, ew, eh); + m_HeaderGrid.SetColumnWidth(NUM_COLUMNS - 1, (wide - X_BORDER) - (ex + ew)); + + m_HeaderGrid.AutoSetRowHeights(); + m_HeaderGrid.setBounds(X_BORDER, SBOARD_TITLE_SIZE_Y, wide - X_BORDER*2, m_HeaderGrid.GetRowHeight(0)); + m_HeaderGrid.setParent(this); + m_HeaderGrid.setBgColor(0,0,0,255); + + + // Now setup the listbox with the actual player data in it. + int headerX, headerY, headerWidth, headerHeight; + m_HeaderGrid.getBounds(headerX, headerY, headerWidth, headerHeight); + m_PlayerList.setBounds(headerX, headerY+headerHeight, headerWidth, tall - headerY - headerHeight - 6); + m_PlayerList.setBgColor(0,0,0,255); + m_PlayerList.setParent(this); + + for(int row=0; row < NUM_ROWS; row++) + { + CGrid *pGridRow = &m_PlayerGrids[row]; + + pGridRow->SetDimensions(NUM_COLUMNS, 1); + + for(int col=0; col < NUM_COLUMNS; col++) + { + m_PlayerEntries[col][row].setContentFitted(false); + m_PlayerEntries[col][row].setRow(row); + m_PlayerEntries[col][row].addInputSignal(this); + pGridRow->SetEntry(col, 0, &m_PlayerEntries[col][row]); + } + + pGridRow->setBgColor(0,0,0,255); + pGridRow->SetSpacing(0, 0); + pGridRow->CopyColumnWidths(&m_HeaderGrid); + pGridRow->AutoSetRowHeights(); + pGridRow->setSize(PanelWidth(pGridRow), pGridRow->CalcDrawHeight()); + pGridRow->RepositionContents(); + + m_PlayerList.AddItem(pGridRow); + } + + + // Add the hit test panel. It is invisible and traps mouse clicks so we can go into squelch mode. + m_HitTestPanel.setBgColor(0,0,0,255); + m_HitTestPanel.setParent(this); + m_HitTestPanel.setBounds(0, 0, wide, tall); + m_HitTestPanel.addInputSignal(this); + +/* m_pCloseButton = new CommandButton( "x", wide-XRES(12 + 4), YRES(2), XRES( 12 ) , YRES( 12 ) ); + m_pCloseButton->setParent( this ); + m_pCloseButton->addActionSignal( new CMenuHandler_StringCommandWatch( "-showscores", true ) ); + m_pCloseButton->setBgColor(0,0,0,255); + m_pCloseButton->setFgColor( 255, 255, 255, 0 ); + m_pCloseButton->setFont(tfont); + m_pCloseButton->setBoundKey( (char)255 ); + m_pCloseButton->setContentAlignment(Label::a_center);*/ + + + Initialize(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Called each time a new level is started. +//----------------------------------------------------------------------------- +void ScorePanel::Initialize( void ) +{ + // Clear out scoreboard data + m_iLastKilledBy = 0; + m_fLastKillTime = 0; + m_iPlayerNum = 0; + m_iNumTeams = 0; + memset( g_PlayerExtraInfo, 0, sizeof g_PlayerExtraInfo ); + memset( g_TeamInfo, 0, sizeof g_TeamInfo ); +} + +bool HACK_GetPlayerUniqueID( int iPlayer, char playerID[16] ) +{ + return !!gEngfuncs.GetPlayerUniqueID( iPlayer, playerID ); // TODO remove after testing +} + +extern int g_iArenaMode; + +//----------------------------------------------------------------------------- +// Purpose: Recalculate the internal scoreboard data +//----------------------------------------------------------------------------- +void ScorePanel::Update() +{ + int i; + + // Set the title + if (gViewPort->m_szServerName) + { + char sz[MAX_SERVERNAME_LENGTH + 16]; + sprintf(sz, "%s", gViewPort->m_szServerName ); + m_TitleLabel.setText(sz); + } + + m_iRows = 0; + gViewPort->GetAllPlayersInfo(); + + + //Hide or show the QUEUE and WINS labels. + if ( g_iArenaMode == TRUE ) + { + m_HeaderLabels[2].setVisible ( true ); + m_HeaderLabels[3].setVisible ( true ); + } + else + { + m_HeaderLabels[2].setVisible ( false ); + m_HeaderLabels[3].setVisible ( false ); + } + + // Clear out sorts + for (i = 0; i < NUM_ROWS; i++) + { + m_iSortedRows[i] = 0; + m_iIsATeam[i] = TEAM_NO; + } + for (i = 0; i < MAX_PLAYERS; i++) + { + m_bHasBeenSorted[i] = false; + } + + SortTeams(); + + // If it's not teamplay, sort all the players. Otherwise, sort the teams. +// if ( !gHUD.m_Teamplay ) + SortPlayers( 0, NULL ); +// else + + + // set scrollbar range + m_PlayerList.SetScrollRange(m_iRows); + + FillGrid(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sort all the teams +//----------------------------------------------------------------------------- +void ScorePanel::SortTeams() +{ + // clear out team scores + int i; + for ( i = 1; i <= m_iNumTeams; i++ ) + { + if ( !g_TeamInfo[i].scores_overriden ) + g_TeamInfo[i].frags = g_TeamInfo[i].deaths = 0; + g_TeamInfo[i].ping = g_TeamInfo[i].packetloss = 0; + } + + // recalc the team scores, then draw them + for ( i = 1; i < MAX_PLAYERS; i++ ) + { + if ( g_PlayerInfoList[i].name == NULL ) + continue; // empty player slot, skip + + if ( g_PlayerExtraInfo[i].teamname[0] == 0 ) + continue; // skip over players who are not in a team + + // find what team this player is in + int j; + for ( j = 1; j <= m_iNumTeams; j++ ) + { + if ( !stricmp( g_PlayerExtraInfo[i].teamname, g_TeamInfo[j].name ) ) + break; + } + if ( j > m_iNumTeams ) // player is not in a team, skip to the next guy + continue; + + if ( !g_TeamInfo[j].scores_overriden ) + { + g_TeamInfo[j].frags += g_PlayerExtraInfo[i].frags; + g_TeamInfo[j].deaths += g_PlayerExtraInfo[i].deaths; + } + + g_TeamInfo[j].ping += g_PlayerInfoList[i].ping; + g_TeamInfo[j].packetloss += g_PlayerInfoList[i].packetloss; + + if ( g_PlayerInfoList[i].thisplayer ) + g_TeamInfo[j].ownteam = TRUE; + else + g_TeamInfo[j].ownteam = FALSE; + + // Set the team's number (used for team colors) + g_TeamInfo[j].teamnumber = g_PlayerExtraInfo[i].teamnumber; + } + + // find team ping/packetloss averages + for ( i = 1; i <= m_iNumTeams; i++ ) + { + g_TeamInfo[i].already_drawn = FALSE; + + if ( g_TeamInfo[i].players > 0 ) + { + g_TeamInfo[i].ping /= g_TeamInfo[i].players; // use the average ping of all the players in the team as the teams ping + g_TeamInfo[i].packetloss /= g_TeamInfo[i].players; // use the average ping of all the players in the team as the teams ping + } + } + + // Draw the teams + while ( 1 ) + { + int highest_frags = -99999; int lowest_deaths = 99999; + int best_team = 0; + + for ( i = 1; i <= m_iNumTeams; i++ ) + { + if ( g_TeamInfo[i].players < 1 ) + continue; + + if ( !g_TeamInfo[i].already_drawn && g_TeamInfo[i].frags >= highest_frags ) + { + if ( g_TeamInfo[i].frags > highest_frags || g_TeamInfo[i].deaths < lowest_deaths ) + { + best_team = i; + lowest_deaths = g_TeamInfo[i].deaths; + highest_frags = g_TeamInfo[i].frags; + } + } + } + + // draw the best team on the scoreboard + if ( !best_team ) + break; + + // Put this team in the sorted list + m_iSortedRows[ m_iRows ] = best_team; + m_iIsATeam[ m_iRows ] = TEAM_YES; + g_TeamInfo[best_team].already_drawn = TRUE; // set the already_drawn to be TRUE, so this team won't get sorted again + m_iRows++; + + // Now sort all the players on this team + SortPlayers( 0, g_TeamInfo[best_team].name ); + } + + // Add all the players who aren't in a team yet into spectators + SortPlayers( TEAM_SPECTATORS, NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sort a list of players +//----------------------------------------------------------------------------- +void ScorePanel::SortPlayers( int iTeam, char *team ) +{ + bool bCreatedTeam = false; + + // draw the players, in order, and restricted to team if set + while ( 1 ) + { + // Find the top ranking player + int highest_frags = -99999; int lowest_deaths = 99999; + int best_player; + best_player = 0; + + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + if ( m_bHasBeenSorted[i] == false && g_PlayerInfoList[i].name && g_PlayerExtraInfo[i].frags >= highest_frags ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( i ); + + if ( ent && !(team && stricmp(g_PlayerExtraInfo[i].teamname, team)) ) + { + extra_player_info_t *pl_info = &g_PlayerExtraInfo[i]; + if ( pl_info->frags > highest_frags || pl_info->deaths < lowest_deaths ) + { + best_player = i; + lowest_deaths = pl_info->deaths; + highest_frags = pl_info->frags; + } + } + } + } + + if ( !best_player ) + break; + + // If we haven't created the Team yet, do it first + if (!bCreatedTeam && iTeam) + { + m_iIsATeam[ m_iRows ] = iTeam; + m_iRows++; + + bCreatedTeam = true; + } + + // Put this player in the sorted list + m_iSortedRows[ m_iRows ] = best_player; + m_bHasBeenSorted[ best_player ] = true; + m_iRows++; + } + + if (team) + { + m_iIsATeam[m_iRows++] = TEAM_SPECTATORS; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Recalculate the existing teams in the match +//----------------------------------------------------------------------------- +void ScorePanel::RebuildTeams() +{ + // clear out player counts from teams + int i; + for ( i = 1; i <= m_iNumTeams; i++ ) + { + g_TeamInfo[i].players = 0; + } + + // rebuild the team list + gViewPort->GetAllPlayersInfo(); + m_iNumTeams = 0; + for ( i = 1; i < MAX_PLAYERS; i++ ) + { + if ( g_PlayerInfoList[i].name == NULL ) + continue; + + if ( g_PlayerExtraInfo[i].teamname[0] == 0 ) + continue; // skip over players who are not in a team + + // is this player in an existing team? + int j; + for ( j = 1; j <= m_iNumTeams; j++ ) + { + if ( g_TeamInfo[j].name[0] == '\0' ) + break; + + if ( !stricmp( g_PlayerExtraInfo[i].teamname, g_TeamInfo[j].name ) ) + break; + } + + if ( j > m_iNumTeams ) + { // they aren't in a listed team, so make a new one + // search through for an empty team slot + for ( int j = 1; j <= m_iNumTeams; j++ ) + { + if ( g_TeamInfo[j].name[0] == '\0' ) + break; + } + m_iNumTeams = max( j, m_iNumTeams ); + + strncpy( g_TeamInfo[j].name, g_PlayerExtraInfo[i].teamname, MAX_TEAM_NAME ); + g_TeamInfo[j].players = 0; + } + + g_TeamInfo[j].players++; + } + + // clear out any empty teams + for ( i = 1; i <= m_iNumTeams; i++ ) + { + if ( g_TeamInfo[i].players < 1 ) + memset( &g_TeamInfo[i], 0, sizeof(team_info_t) ); + } + + // Update the scoreboard + Update(); +} + + +void ScorePanel::FillGrid() +{ + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + SchemeHandle_t hScheme = pSchemes->getSchemeHandle("Scoreboard Text"); + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle("Scoreboard Title Text"); + SchemeHandle_t hSmallScheme = pSchemes->getSchemeHandle("Scoreboard Small Text"); + + Font *sfont = pSchemes->getFont(hScheme); + Font *tfont = pSchemes->getFont(hTitleScheme); + Font *smallfont = pSchemes->getFont(hSmallScheme); + int i = 0; + + // update highlight position + int x, y; + getApp()->getCursorPos(x, y); + cursorMoved(x, y, this); + + // remove highlight row if we're not in squelch mode + if (!GetClientVoiceMgr()->IsInSquelchMode()) + { + m_iHighlightRow = -1; + } + + bool bNextRowIsGap = false; + + int row; + for(row=0; row < NUM_ROWS; row++) + { + CGrid *pGridRow = &m_PlayerGrids[row]; + pGridRow->SetRowUnderline(0, false, 0, 0, 0, 0, 0); + + if(row >= m_iRows) + { + for(int col=0; col < NUM_COLUMNS; col++) + m_PlayerEntries[col][row].setVisible(false); + + continue; + } + + bool bRowIsGap = false; + if (bNextRowIsGap) + { + bNextRowIsGap = false; + bRowIsGap = true; + } + + for(int col=0; col < NUM_COLUMNS; col++) + { + CLabelHeader *pLabel = &m_PlayerEntries[col][row]; + + pLabel->setVisible(true); + pLabel->setText2(""); + pLabel->setImage(NULL); + pLabel->setFont(sfont); + pLabel->setTextOffset(0, 0); + + int rowheight = 13; + if (ScreenHeight > 480) + { + rowheight = YRES(rowheight); + } + else + { + // more tweaking, make sure icons fit at low res + rowheight = 15; + } + pLabel->setSize(pLabel->getWide(), rowheight); + pLabel->setBgColor(0, 0, 0, 255); + + char sz[128]; + hud_player_info_t *pl_info = NULL; + team_info_t *team_info = NULL; + + if (m_iIsATeam[row] == TEAM_SPECTATORS) + { + pLabel->setText(" "); + continue; + } + else if ( m_iIsATeam[row] == TEAM_YES ) + { + // Get the team's data + team_info = &g_TeamInfo[ m_iSortedRows[row] ]; + + // team color text for team names + pLabel->setFgColor( iTeamColors[team_info->teamnumber % 5][0], + iTeamColors[team_info->teamnumber % 5][1], + iTeamColors[team_info->teamnumber % 5][2], + 0 ); + + // different height for team header rows + rowheight = 20; + if (ScreenHeight >= 480) + { + rowheight = YRES(rowheight); + } + pLabel->setSize(pLabel->getWide(), rowheight); + pLabel->setFont(tfont); + + pGridRow->SetRowUnderline( 0, + true, + YRES(3), + iTeamColors[team_info->teamnumber % 5][0], + iTeamColors[team_info->teamnumber % 5][1], + iTeamColors[team_info->teamnumber % 5][2], + 0 ); + } + else if ( m_iIsATeam[row] == TEAM_SPECTATORS ) + { + // grey text for spectators + pLabel->setFgColor(100, 100, 100, 0); + + // different height for team header rows + rowheight = 20; + if (ScreenHeight >= 480) + { + rowheight = YRES(rowheight); + } + pLabel->setSize(pLabel->getWide(), rowheight); + pLabel->setFont(tfont); + + pGridRow->SetRowUnderline(0, true, YRES(3), 100, 100, 100, 0); + } + else + { + // team color text for player names + pLabel->setFgColor( g_iaDiscColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][0], + g_iaDiscColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][1], + g_iaDiscColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][2], + 0 ); + + // Get the player's data + pl_info = &g_PlayerInfoList[ m_iSortedRows[row] ]; + + // Set background color + if ( pl_info->thisplayer ) // if it is their name, draw it a different color + { + // Highlight this player + pLabel->setFgColor(Scheme::sc_white); + pLabel->setBgColor( g_iaDiscColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][0], + g_iaDiscColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][1], + g_iaDiscColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][2], + 196 ); + } + else if ( m_iSortedRows[row] == m_iLastKilledBy && m_fLastKillTime && m_fLastKillTime > gHUD.m_flTime ) + { + // Killer's name + pLabel->setBgColor( 255,0,0, 255 - ((float)15 * (float)(m_fLastKillTime - gHUD.m_flTime)) ); + } + } + + // Align + if (col == COLUMN_NAME ) + { + pLabel->setContentAlignment( vgui::Label::a_west ); + } + else if (col == COLUMN_TRACKER) + { + pLabel->setContentAlignment( vgui::Label::a_center ); + } + else + { + pLabel->setContentAlignment( vgui::Label::a_east ); + } + + // Fill out with the correct data + strcpy(sz, ""); + if ( m_iIsATeam[row] ) + { + char sz2[128]; + + switch (col) + { + case COLUMN_NAME: + if ( m_iIsATeam[row] == TEAM_UNASSIGNED ) + { + sprintf( sz2, CHudTextMessage::BufferedLocaliseTextString( "#Queue" ) ); + } + else if ( m_iIsATeam[row] == TEAM_SPECTATORS ) + { + sprintf( sz2, CHudTextMessage::BufferedLocaliseTextString( "#Spectators" ) ); + } + else + { + sprintf( sz2, CHudTextMessage::BufferedLocaliseTextString( team_info->name ) ); + } + + // Uppercase it + for ( i = 0; i < (int)strlen(sz2); i++) + { + if ( *(sz2 + i) ) + sz[i] = toupper( *(sz2 + i) ); + } + sz[i] = '\0'; + + // Append the number of players + if ( m_iIsATeam[row] == TEAM_YES ) + { + char *pszTemp = NULL; + + if ( team_info->players == 1 ) + { + pszTemp = "#Player"; + } + else + { + pszTemp = "#Player_plural"; + } + + sprintf(sz, "%s (%d %s)", sz, team_info->players, CHudTextMessage::BufferedLocaliseTextString( pszTemp ) ); + } + + break; + case COLUMN_VOICE: + break; + case COLUMN_CLASS: + break; + case COLUMN_KILLS: //Wins + if ( g_iArenaMode == FALSE ) + strcpy ( sz, " " ); + else + { + if ( m_iIsATeam[row] == TEAM_YES ) + sprintf(sz, "%d", team_info->deaths ); + } + break; + case COLUMN_DEATHS: //Points + if ( m_iIsATeam[row] == TEAM_YES ) + sprintf(sz, "%d", team_info->frags ); + break; + case COLUMN_LATENCY: + if ( m_iIsATeam[row] == TEAM_YES ) + sprintf(sz, "%d", team_info->ping ); + break; + default: + break; + } + } + else + { + bool bShowClass = false; + + switch (col) + { + case COLUMN_NAME: + /* + if (g_pTrackerUser) + { + int playerSlot = m_iSortedRows[row]; + int trackerID = gEngfuncs.GetTrackerIDForPlayer(playerSlot); + const char *trackerName = g_pTrackerUser->GetUserName(trackerID); + if (trackerName && *trackerName) + { + sprintf(sz, " (%s)", trackerName); + pLabel->setText2(sz); + } + } + */ + sprintf(sz, "%s ", pl_info->name); + break; + case COLUMN_VOICE: + sz[0] = 0; + GetClientVoiceMgr()->UpdateSpeakerImage(pLabel, m_iSortedRows[row]); + break; + case COLUMN_CLASS: + + if ( g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber == 0 && g_iArenaMode == TRUE ) + { + sprintf(sz, "%d", g_PlayerExtraInfo[ m_iSortedRows[row] ].playerclass ); + } + else + { + strcpy(sz, ""); + } + + break; + + case COLUMN_TRACKER: + /* + if (g_pTrackerUser) + { + int playerSlot = m_iSortedRows[row]; + int trackerID = gEngfuncs.GetTrackerIDForPlayer(playerSlot); + + if (g_pTrackerUser->IsFriend(trackerID) && trackerID != g_pTrackerUser->GetTrackerID()) + { + pLabel->setImage(m_pTrackerIcon); + pLabel->setFgColorAsImageColor(false); + m_pTrackerIcon->setColor(Color(255, 255, 255, 0)); + } + }*/ + break; + + case COLUMN_KILLS: //Wins + if ( g_iArenaMode == TRUE ) + sprintf(sz, "%d", g_PlayerExtraInfo[ m_iSortedRows[row] ].deaths ); + break; + case COLUMN_DEATHS: //Points + sprintf(sz, "%d", g_PlayerExtraInfo[ m_iSortedRows[row] ].frags ); + break; + case COLUMN_LATENCY: + sprintf(sz, "%d", g_PlayerInfoList[ m_iSortedRows[row] ].ping ); + break; + default: + break; + } + } + + pLabel->setText(sz); + } + } + + for(row=0; row < NUM_ROWS; row++) + { + CGrid *pGridRow = &m_PlayerGrids[row]; + + pGridRow->AutoSetRowHeights(); + pGridRow->setSize(PanelWidth(pGridRow), pGridRow->CalcDrawHeight()); + pGridRow->RepositionContents(); + } + + // hack, for the thing to resize + m_PlayerList.getSize(x, y); + m_PlayerList.setSize(x, y); +} + + +//----------------------------------------------------------------------------- +// Purpose: Setup highlights for player names in scoreboard +//----------------------------------------------------------------------------- +void ScorePanel::DeathMsg( int killer, int victim ) +{ + // if we were the one killed, or the world killed us, set the scoreboard to indicate suicide + if ( victim == m_iPlayerNum || killer == 0 ) + { + m_iLastKilledBy = killer ? killer : m_iPlayerNum; + m_fLastKillTime = gHUD.m_flTime + 10; // display who we were killed by for 10 seconds + + if ( killer == m_iPlayerNum ) + m_iLastKilledBy = m_iPlayerNum; + } +} + + +void ScorePanel::Open( void ) +{ + RebuildTeams(); + setVisible(true); + m_HitTestPanel.setVisible(true); +} + + +void ScorePanel::mousePressed(MouseCode code, Panel* panel) +{ + if(gHUD.m_iIntermission) + return; + + if (!GetClientVoiceMgr()->IsInSquelchMode()) + { + GetClientVoiceMgr()->StartSquelchMode(); + m_HitTestPanel.setVisible(false); + } + else if (m_iHighlightRow >= 0) + { + // mouse has been pressed, toggle mute state + int iPlayer = m_iSortedRows[m_iHighlightRow]; + if (iPlayer > 0) + { + // print text message + hud_player_info_t *pl_info = &g_PlayerInfoList[iPlayer]; + + if (pl_info && pl_info->name && pl_info->name[0]) + { + char string[256]; + if (GetClientVoiceMgr()->IsPlayerBlocked(iPlayer)) + { + char string1[1024]; + + // remove mute + GetClientVoiceMgr()->SetPlayerBlockedState(iPlayer, false); + + sprintf( string1, CHudTextMessage::BufferedLocaliseTextString( "#Unmuted" ), pl_info->name ); + sprintf( string, "%c** %s\n", HUD_PRINTTALK, string1 ); + + gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); + } + else + { + char string1[1024]; + char string2[1024]; + + // mute the player + GetClientVoiceMgr()->SetPlayerBlockedState(iPlayer, true); + + sprintf( string1, CHudTextMessage::BufferedLocaliseTextString( "#Muted" ), pl_info->name ); + sprintf( string2, CHudTextMessage::BufferedLocaliseTextString( "#No_longer_hear_that_player" ) ); + sprintf( string, "%c** %s %s\n", HUD_PRINTTALK, string1, string2 ); + + gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); + } + } + } + } +} + +void ScorePanel::cursorMoved(int x, int y, Panel *panel) +{ + if (GetClientVoiceMgr()->IsInSquelchMode()) + { + // look for which cell the mouse is currently over + for (int i = 0; i < NUM_ROWS; i++) + { + int row, col; + if (m_PlayerGrids[i].getCellAtPoint(x, y, row, col)) + { + MouseOverCell(i, col); + return; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handles mouse movement over a cell +// Input : row - +// col - +//----------------------------------------------------------------------------- +void ScorePanel::MouseOverCell(int row, int col) +{ + CLabelHeader *label = &m_PlayerEntries[col][row]; + + // clear the previously highlighted label + if (m_pCurrentHighlightLabel != label) + { + m_pCurrentHighlightLabel = NULL; + m_iHighlightRow = -1; + } + if (!label) + return; + + // don't act on teams + if (m_iIsATeam[row] != TEAM_NO) + return; + + // don't act on disconnected players or ourselves + hud_player_info_t *pl_info = &g_PlayerInfoList[ m_iSortedRows[row] ]; + if (!pl_info->name || !pl_info->name[0]) + return; + + if (pl_info->thisplayer && !gEngfuncs.IsSpectateOnly() ) + return; + + // setup the new highlight + m_pCurrentHighlightLabel = label; + m_iHighlightRow = row; +} + +//----------------------------------------------------------------------------- +// Purpose: Label paint functions - take into account current highligh status +//----------------------------------------------------------------------------- +void CLabelHeader::paintBackground() +{ + Color oldBg; + getBgColor(oldBg); + + if (gViewPort->GetScoreBoard()->m_iHighlightRow == _row) + { + setBgColor(134, 91, 19, 0); + } + + Panel::paintBackground(); + + setBgColor(oldBg); +} + + +//----------------------------------------------------------------------------- +// Purpose: Label paint functions - take into account current highligh status +//----------------------------------------------------------------------------- +void CLabelHeader::paint() +{ + Color oldFg; + getFgColor(oldFg); + + if (gViewPort->GetScoreBoard()->m_iHighlightRow == _row) + { + setFgColor(255, 255, 255, 0); + } + + // draw text + int x, y, iwide, itall; + getTextSize(iwide, itall); + calcAlignment(iwide, itall, x, y); + _dualImage->setPos(x, y); + + int x1, y1; + _dualImage->GetImage(1)->getPos(x1, y1); + _dualImage->GetImage(1)->setPos(_gap, y1); + + _dualImage->doPaint(this); + + // get size of the panel and the image + if (_image) + { + Color imgColor; + getFgColor( imgColor ); + if( _useFgColorAsImageColor ) + { + _image->setColor( imgColor ); + } + + _image->getSize(iwide, itall); + calcAlignment(iwide, itall, x, y); + _image->setPos(x, y); + _image->doPaint(this); + } + + setFgColor(oldFg[0], oldFg[1], oldFg[2], oldFg[3]); +} + + +void CLabelHeader::calcAlignment(int iwide, int itall, int &x, int &y) +{ + // calculate alignment ourselves, since vgui is so broken + int wide, tall; + getSize(wide, tall); + + x = 0, y = 0; + + // align left/right + switch (_contentAlignment) + { + // left + case Label::a_northwest: + case Label::a_west: + case Label::a_southwest: + { + x = 0; + break; + } + + // center + case Label::a_north: + case Label::a_center: + case Label::a_south: + { + x = (wide - iwide) / 2; + break; + } + + // right + case Label::a_northeast: + case Label::a_east: + case Label::a_southeast: + { + x = wide - iwide; + break; + } + } + + // top/down + switch (_contentAlignment) + { + // top + case Label::a_northwest: + case Label::a_north: + case Label::a_northeast: + { + y = 0; + break; + } + + // center + case Label::a_west: + case Label::a_center: + case Label::a_east: + { + y = (tall - itall) / 2; + break; + } + + // south + case Label::a_southwest: + case Label::a_south: + case Label::a_southeast: + { + y = tall - itall; + break; + } + } + +// don't clip to Y +// if (y < 0) +// { +// y = 0; +// } + if (x < 0) + { + x = 0; + } + + x += _offset[0]; + y += _offset[1]; +} diff --git a/ricochet/cl_dll/vgui_ScorePanel.h b/ricochet/cl_dll/vgui_ScorePanel.h new file mode 100644 index 0000000..ecb6e6f --- /dev/null +++ b/ricochet/cl_dll/vgui_ScorePanel.h @@ -0,0 +1,307 @@ + +#ifndef SCOREPANEL_H +#define SCOREPANEL_H + +#include +#include +#include +#include +#include +#include +#include "vgui_listbox.h" + +#include + +#define MAX_SCORES 10 +#define MAX_SCOREBOARD_TEAMS 5 + +// Scoreboard cells +#define COLUMN_TRACKER 0 +#define COLUMN_NAME 1 +#define COLUMN_CLASS 2 +#define COLUMN_KILLS 3 +#define COLUMN_DEATHS 4 +#define COLUMN_LATENCY 5 +#define COLUMN_VOICE 6 +#define COLUMN_BLANK 7 +#define NUM_COLUMNS 8 +#define NUM_ROWS (MAX_PLAYERS + (MAX_SCOREBOARD_TEAMS * 2)) + +using namespace vgui; + +class CTextImage2 : public Image +{ +public: + CTextImage2() + { + _image[0] = new TextImage(""); + _image[1] = new TextImage(""); + } + + ~CTextImage2() + { + delete _image[0]; + delete _image[1]; + } + + TextImage *GetImage(int image) + { + return _image[image]; + } + + void getSize(int &wide, int &tall) + { + int w1, w2, t1, t2; + _image[0]->getTextSize(w1, t1); + _image[1]->getTextSize(w2, t2); + + wide = w1 + w2; + tall = max(t1, t2); + setSize(wide, tall); + } + + void doPaint(Panel *panel) + { + _image[0]->doPaint(panel); + _image[1]->doPaint(panel); + } + + void setPos(int x, int y) + { + _image[0]->setPos(x, y); + + int swide, stall; + _image[0]->getSize(swide, stall); + + int wide, tall; + _image[1]->getSize(wide, tall); + _image[1]->setPos(x + wide, y + (stall * 0.9) - tall); + } + + void setColor(Color color) + { + _image[0]->setColor(color); + } + + void setColor2(Color color) + { + _image[1]->setColor(color); + } + +private: + TextImage *_image[2]; + +}; + +//----------------------------------------------------------------------------- +// Purpose: Custom label for cells in the Scoreboard's Table Header +//----------------------------------------------------------------------------- +class CLabelHeader : public Label +{ +public: + CLabelHeader() : Label("") + { + _dualImage = new CTextImage2(); + _dualImage->setColor2(Color(255, 170, 0, 0)); + _row = -2; + _useFgColorAsImageColor = true; + _offset[0] = 0; + _offset[1] = 0; + } + + ~CLabelHeader() + { + delete _dualImage; + } + + void setRow(int row) + { + _row = row; + } + + void setFgColorAsImageColor(bool state) + { + _useFgColorAsImageColor = state; + } + + virtual void setText(int textBufferLen, const char* text) + { + _dualImage->GetImage(0)->setText(text); + + // calculate the text size + Font *font = _dualImage->GetImage(0)->getFont(); + _gap = 0; + for (const char *ch = text; *ch != 0; ch++) + { + int a, b, c; + font->getCharABCwide(*ch, a, b, c); + _gap += (a + b + c); + } + + _gap += XRES(5); + } + + virtual void setText(const char* text) + { + // strip any non-alnum characters from the end + char buf[512]; + strcpy(buf, text); + + int len = strlen(buf); + while (len && isspace(buf[--len])) + { + buf[len] = 0; + } + + CLabelHeader::setText(0, buf); + } + + void setText2(const char *text) + { + _dualImage->GetImage(1)->setText(text); + } + + void getTextSize(int &wide, int &tall) + { + _dualImage->getSize(wide, tall); + } + + void setFgColor(int r,int g,int b,int a) + { + Label::setFgColor(r,g,b,a); + Color color(r,g,b,a); + _dualImage->setColor(color); + _dualImage->setColor2(color); + repaint(); + } + + void setFgColor(Scheme::SchemeColor sc) + { + int r, g, b, a; + Label::setFgColor(sc); + Label::getFgColor( r, g, b, a ); + + // Call the r,g,b,a version so it sets the color in the dualImage.. + setFgColor( r, g, b, a ); + } + + void setFont(Font *font) + { + _dualImage->GetImage(0)->setFont(font); + } + + void setFont2(Font *font) + { + _dualImage->GetImage(1)->setFont(font); + } + + // this adjust the absolute position of the text after alignment is calculated + void setTextOffset(int x, int y) + { + _offset[0] = x; + _offset[1] = y; + } + + void paint(); + void paintBackground(); + void calcAlignment(int iwide, int itall, int &x, int &y); + +private: + CTextImage2 *_dualImage; + int _row; + int _gap; + int _offset[2]; + bool _useFgColorAsImageColor; +}; + +class ScoreTablePanel; + +#include "vgui_grid.h" +#include "vgui_defaultinputsignal.h" + +//----------------------------------------------------------------------------- +// Purpose: Scoreboard back panel +//----------------------------------------------------------------------------- +class ScorePanel : public Panel, public vgui::CDefaultInputSignal +{ +private: + // Default panel implementation doesn't forward mouse messages when there is no cursor and we need them. + class HitTestPanel : public Panel + { + public: + virtual void internalMousePressed(MouseCode code); + }; + + +private: + + Label m_TitleLabel; + + // Here is how these controls are arranged hierarchically. + // m_HeaderGrid + // m_HeaderLabels + + // m_PlayerGridScroll + // m_PlayerGrid + // m_PlayerEntries + + CGrid m_HeaderGrid; + CLabelHeader m_HeaderLabels[NUM_COLUMNS]; // Labels above the + CLabelHeader *m_pCurrentHighlightLabel; + int m_iHighlightRow; + + vgui::CListBox m_PlayerList; + CGrid m_PlayerGrids[NUM_ROWS]; // The grid with player and team info. + CLabelHeader m_PlayerEntries[NUM_COLUMNS][NUM_ROWS]; // Labels for the grid entries. + + ScorePanel::HitTestPanel m_HitTestPanel; +// CommandButton *m_pCloseButton; + CLabelHeader* GetPlayerEntry(int x, int y) {return &m_PlayerEntries[x][y];} + + vgui::BitmapTGA *m_pTrackerIcon; + + +public: + + int m_iNumTeams; + int m_iPlayerNum; + int m_iShowscoresHeld; + + int m_iRows; + int m_iSortedRows[NUM_ROWS]; + int m_iIsATeam[NUM_ROWS]; + bool m_bHasBeenSorted[MAX_PLAYERS]; + int m_iLastKilledBy; + int m_fLastKillTime; + + +public: + + ScorePanel(int x,int y,int wide,int tall); + + void Update( void ); + + void SortTeams( void ); + void SortPlayers( int iTeam, char *team ); + void RebuildTeams( void ); + + void FillGrid(); + + void DeathMsg( int killer, int victim ); + + void Initialize( void ); + + void Open( void ); + + void MouseOverCell(int row, int col); + +// InputSignal overrides. +public: + + virtual void mousePressed(MouseCode code, Panel* panel); + virtual void cursorMoved(int x, int y, Panel *panel); + + friend class CLabelHeader; +}; + +#endif diff --git a/ricochet/cl_dll/vgui_ServerBrowser.cpp b/ricochet/cl_dll/vgui_ServerBrowser.cpp new file mode 100644 index 0000000..009b137 --- /dev/null +++ b/ricochet/cl_dll/vgui_ServerBrowser.cpp @@ -0,0 +1,616 @@ + +#include +#include +#include +#include +#include +#include + +#include "hud.h" +#include "cl_util.h" +#include "hud_servers.h" +#include "net_api.h" + +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" + +using namespace vgui; + +namespace +{ + +#define MAX_SB_ROWS 24 + +#define NUM_COLUMNS 5 + +#define HEADER_SIZE_Y YRES(18) + +// Column sizes +#define CSIZE_ADDRESS XRES(200) +#define CSIZE_SERVER XRES(400) +#define CSIZE_MAP XRES(500) +#define CSIZE_CURRENT XRES(570) +#define CSIZE_PING XRES(640) + +#define CELL_HEIGHT YRES(15) + +class ServerBrowserTablePanel; + +class CBrowser_InputSignal : public InputSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; +public: + CBrowser_InputSignal( ServerBrowserTablePanel *pBrowser ) + { + m_pBrowser = pBrowser; + } + + virtual void cursorMoved(int x,int y,Panel* panel) {}; + virtual void cursorEntered(Panel* panel){}; + virtual void cursorExited(Panel* Panel) {}; + + virtual void mousePressed(MouseCode code,Panel* panel); + + virtual void mouseDoublePressed(MouseCode code,Panel* panel); + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; +}; + +class ServerBrowserTablePanel : public TablePanel +{ +private: + Label *m_pLabel; + int m_nMouseOverRow; + +public: + + ServerBrowserTablePanel( int x,int y,int wide,int tall,int columnCount) : TablePanel( x,y,wide,tall,columnCount) + { + m_pLabel = new Label( "", 0, 0 /*,wide, tall*/ ); + + m_nMouseOverRow = 0; + } + +public: + void setMouseOverRow( int row ) + { + m_nMouseOverRow = row; + } + + void DoSort( char *sortkey ) + { + // Request server list and refresh servers... + SortServers( sortkey ); + } + + void DoRefresh( void ) + { + // Request server list and refresh servers... + ServersList(); + BroadcastServersList( 0 ); + } + + void DoBroadcastRefresh( void ) + { + // Request server list and refresh servers... + BroadcastServersList( 1 ); + } + + void DoStop( void ) + { + // Stop requesting + ServersCancel(); + } + + void DoCancel( void ) + { + ClientCmd( "togglebrowser\n" ); + } + + void DoConnect( void ) + { + const char *info; + const char *address; + char sz[ 256 ]; + + info = ServersGetInfo( m_nMouseOverRow ); + if ( !info ) + return; + + address = gEngfuncs.pNetAPI->ValueForKey( info, "address" ); + //gEngfuncs.Con_Printf( "Connecting to %s\n", address ); + + sprintf( sz, "connect %s\n", address ); + + ClientCmd( sz ); + + DoCancel(); + } + + void DoPing( void ) + { + ServerPing( 0 ); + ServerRules( 0 ); + ServerPlayers( 0 ); + } + + virtual int getRowCount() + { + int rowcount; + int height, width; + + getSize( width, height ); + + // Space for buttons + height -= YRES(20); + height = max( 0, height ); + + rowcount = height / CELL_HEIGHT; + + return rowcount; + } + + virtual int getCellTall(int row) + { + return CELL_HEIGHT - 2; + } + + virtual Panel* getCellRenderer(int column,int row,bool columnSelected,bool rowSelected,bool cellSelected) + { + const char *info; + const char *val, *val2; + char sz[ 32 ]; + + info = ServersGetInfo( row ); + + if ( row == m_nMouseOverRow ) + { + m_pLabel->setFgColor( 200, 240, 63, 100 ); + } + else + { + m_pLabel->setFgColor( 255, 255, 255, 0 ); + } + m_pLabel->setBgColor( 0, 0, 0, 200 ); + m_pLabel->setContentAlignment( vgui::Label::a_west ); + m_pLabel->setFont( Scheme::sf_primary2 ); + + if ( info ) + { + // Fill out with the correct data + switch ( column ) + { + case 0: + val = gEngfuncs.pNetAPI->ValueForKey( info, "address" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Name; + m_pLabel->setText( sz ); + } + break; + case 1: + val = gEngfuncs.pNetAPI->ValueForKey( info, "hostname" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Map; + m_pLabel->setText( sz ); + } + break; + case 2: + val = gEngfuncs.pNetAPI->ValueForKey( info, "map" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Name; + m_pLabel->setText( sz ); + } + break; + case 3: + val = gEngfuncs.pNetAPI->ValueForKey( info, "current" ); + val2 = gEngfuncs.pNetAPI->ValueForKey( info, "max" ); + if ( val && val2 ) + { + sprintf( sz, "%s/%s", val, val2 ); + sz[ 31 ] = '\0'; + // Server Map; + m_pLabel->setText( sz ); + } + break; + case 4: + val = gEngfuncs.pNetAPI->ValueForKey( info, "ping" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Name; + m_pLabel->setText( sz ); + } + break; + default: + break; + } + } + else + { + if ( !row && !column ) + { + if ( ServersIsQuerying() ) + { + m_pLabel->setText( "Waiting for servers to respond..." ); + } + else + { + m_pLabel->setText( "Press 'Refresh' to search for servers..." ); + } + } + else + { + m_pLabel->setText( "" ); + } + } + + return m_pLabel; + } + + virtual Panel* startCellEditing(int column,int row) + { + return null; + } + +}; + +class ConnectHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + ConnectHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoConnect(); + } +}; + +class RefreshHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + RefreshHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoRefresh(); + } +}; + +class BroadcastRefreshHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + BroadcastRefreshHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoBroadcastRefresh(); + } +}; + +class StopHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + StopHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoStop(); + } +}; + +class CancelHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + CancelHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoCancel(); + } +}; + +class PingHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + PingHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoPing(); + } +}; + +class SortHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + SortHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoSort( "map" ); + } +}; + +} + +class LabelSortInputHandler : public InputSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + char m_szSortKey[ 64 ]; + +public: + LabelSortInputHandler( ServerBrowserTablePanel *pBrowser, char *name ) + { + m_pBrowser = pBrowser; + strcpy( m_szSortKey, name ); + } + + virtual void cursorMoved(int x,int y,Panel* panel) {}; + virtual void cursorEntered(Panel* panel){}; + virtual void cursorExited(Panel* Panel) {}; + + virtual void mousePressed(MouseCode code,Panel* panel) + { + m_pBrowser->DoSort( m_szSortKey ); + } + + virtual void mouseDoublePressed(MouseCode code,Panel* panel) + { + m_pBrowser->DoSort( m_szSortKey ); + } + + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; +}; + +class CSBLabel : public Label +{ + +private: + char m_szSortKey[ 64 ]; + ServerBrowserTablePanel *m_pBrowser; + +public: + CSBLabel( char *name, char *sortkey ) : Label( name ) + { + m_pBrowser = NULL; + + strcpy( m_szSortKey, sortkey ); + + int label_bg_r = 120, + label_bg_g = 75, + label_bg_b = 32, + label_bg_a = 200; + + int label_fg_r = 255, + label_fg_g = 0, + label_fg_b = 0, + label_fg_a = 0; + + setContentAlignment( vgui::Label::a_west ); + setFgColor( label_fg_r, label_fg_g, label_fg_b, label_fg_a ); + setBgColor( label_bg_r, label_bg_g, label_bg_b, label_bg_a ); + setFont( Scheme::sf_primary2 ); + + } + + void setTable( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + + addInputSignal( new LabelSortInputHandler( (ServerBrowserTablePanel * )m_pBrowser, m_szSortKey ) ); + } +}; + +ServerBrowser::ServerBrowser(int x,int y,int wide,int tall) : CTransparentPanel( 100, x,y,wide,tall ) +{ + int i; + + _headerPanel = new HeaderPanel(0,0,wide,HEADER_SIZE_Y); + _headerPanel->setParent(this); + _headerPanel->setFgColor( 100,100,100, 100 ); + _headerPanel->setBgColor( 0, 0, 0, 100 ); + + CSBLabel *pLabel[5]; + + pLabel[0] = new CSBLabel( "Address", "address" ); + pLabel[1] = new CSBLabel( "Server", "hostname" ); + pLabel[2] = new CSBLabel( "Map", "map" ); + pLabel[3] = new CSBLabel( "Current", "current" ); + pLabel[4] = new CSBLabel( "Latency", "ping" ); + + for ( i = 0; i < 5; i++ ) + { + _headerPanel->addSectionPanel( pLabel[i] ); + } + + // _headerPanel->setFont( Scheme::sf_primary1 ); + + _headerPanel->setSliderPos( 0, CSIZE_ADDRESS ); + _headerPanel->setSliderPos( 1, CSIZE_SERVER ); + _headerPanel->setSliderPos( 2, CSIZE_MAP ); + _headerPanel->setSliderPos( 3, CSIZE_CURRENT ); + _headerPanel->setSliderPos( 4, CSIZE_PING ); + + _tablePanel = new ServerBrowserTablePanel( 0, HEADER_SIZE_Y, wide, tall - HEADER_SIZE_Y, NUM_COLUMNS ); + _tablePanel->setParent(this); + _tablePanel->setHeaderPanel(_headerPanel); + _tablePanel->setFgColor( 100,100,100, 100 ); + _tablePanel->setBgColor( 0, 0, 0, 100 ); + + _tablePanel->addInputSignal( new CBrowser_InputSignal( (ServerBrowserTablePanel *)_tablePanel ) ); + + for ( i = 0; i < 5; i++ ) + { + pLabel[i]->setTable( (ServerBrowserTablePanel * )_tablePanel ); + } + + int bw = 80, bh = 15; + int by = tall - HEADER_SIZE_Y; + + int btnx = 10; + + _connectButton = new CommandButton( "Connect", btnx, by, bw, bh ); + _connectButton->setParent( this ); + _connectButton->addActionSignal( new ConnectHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + btnx += bw; + + _refreshButton = new CommandButton( "Refresh", btnx, by, bw, bh ); + _refreshButton->setParent( this ); + _refreshButton->addActionSignal( new RefreshHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + /* + btnx += bw; + + _broadcastRefreshButton = new CommandButton( "LAN", btnx, by, bw, bh ); + _broadcastRefreshButton->setParent( this ); + _broadcastRefreshButton->addActionSignal( new BroadcastRefreshHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + */ + + btnx += bw; + + _stopButton = new CommandButton( "Stop", btnx, by, bw, bh ); + _stopButton->setParent( this ); + _stopButton->addActionSignal( new StopHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + /* + btnx += bw; + + _pingButton = new CommandButton( "Test", btnx, by, bw, bh ); + _pingButton->setParent( this ); + _pingButton->addActionSignal( new PingHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + btnx += bw; + + _sortButton = new CommandButton( "Sort", btnx, by, bw, bh ); + _sortButton->setParent( this ); + _sortButton->addActionSignal( new SortHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + */ + + btnx += bw; + + _cancelButton = new CommandButton( "Close", btnx, by, bw, bh ); + _cancelButton->setParent( this ); + _cancelButton->addActionSignal( new CancelHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + setPaintBorderEnabled(false); + setPaintBackgroundEnabled(false); + setPaintEnabled(false); +} + +void ServerBrowser::setSize(int wide,int tall) +{ + Panel::setSize(wide,tall); + + _headerPanel->setBounds(0,0,wide,HEADER_SIZE_Y); + _tablePanel->setBounds(0,HEADER_SIZE_Y,wide,tall - HEADER_SIZE_Y); + + _connectButton->setBounds( 5, tall - HEADER_SIZE_Y, 75, 15 ); + _refreshButton->setBounds( 85, tall - HEADER_SIZE_Y, 75, 15 ); + /* + _broadcastRefreshButton->setBounds( 165, tall - HEADER_SIZE_Y, 75, 15 ); + */ + _stopButton->setBounds( 165, tall - HEADER_SIZE_Y, 75, 15 ); + /* + _pingButton->setBounds( 325, tall - HEADER_SIZE_Y, 75, 15 ); + */ + _cancelButton->setBounds( 245, tall - HEADER_SIZE_Y, 75, 15 ); +} + +void CBrowser_InputSignal::mousePressed(MouseCode code,Panel* panel) +{ + int x, y; + int therow = 2; + + if ( code != MOUSE_LEFT ) + return; + + panel->getApp()->getCursorPos(x,y); + panel->screenToLocal( x, y ); + + therow = y / CELL_HEIGHT; + + // Figure out which row it's on + m_pBrowser->setMouseOverRow( therow ); +} + +void CBrowser_InputSignal::mouseDoublePressed(MouseCode code,Panel* panel) +{ + int x, y; + int therow = 2; + + if ( code != MOUSE_LEFT ) + return; + + panel->getApp()->getCursorPos(x,y); + panel->screenToLocal( x, y ); + + therow = y / CELL_HEIGHT; + + // Figure out which row it's on + m_pBrowser->setMouseOverRow( therow ); + m_pBrowser->DoConnect(); +} \ No newline at end of file diff --git a/ricochet/cl_dll/vgui_ServerBrowser.h b/ricochet/cl_dll/vgui_ServerBrowser.h new file mode 100644 index 0000000..b826770 --- /dev/null +++ b/ricochet/cl_dll/vgui_ServerBrowser.h @@ -0,0 +1,44 @@ + +#ifndef ServerBrowser_H +#define ServerBrowser_H + +#include + +namespace vgui +{ +class Button; +class TablePanel; +class HeaderPanel; +} + +class CTransparentPanel; +class CommandButton; + +// Scoreboard positions +#define SB_X_INDENT (20 * ((float)ScreenHeight / 640)) +#define SB_Y_INDENT (20 * ((float)ScreenHeight / 480)) + +class ServerBrowser : public CTransparentPanel +{ +private: + HeaderPanel * _headerPanel; + TablePanel* _tablePanel; + + CommandButton* _connectButton; + CommandButton* _refreshButton; + CommandButton* _broadcastRefreshButton; + CommandButton* _stopButton; + CommandButton* _sortButton; + CommandButton* _cancelButton; + + CommandButton* _pingButton; + +public: + ServerBrowser(int x,int y,int wide,int tall); +public: + virtual void setSize(int wide,int tall); +}; + + + +#endif \ No newline at end of file diff --git a/ricochet/cl_dll/vgui_TeamFortressViewport.cpp b/ricochet/cl_dll/vgui_TeamFortressViewport.cpp new file mode 100644 index 0000000..2eac79b --- /dev/null +++ b/ricochet/cl_dll/vgui_TeamFortressViewport.cpp @@ -0,0 +1,2220 @@ +//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: Client DLL VGUI Viewport +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "parsemsg.h" +#include "keydefs.h" +#include "demo.h" +#include "demo_api.h" +#include "ammohistory.h" + +#include "vgui_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" +#include "vgui_ScorePanel.h" +#include "vgui_discobjects.h" + +extern WeaponsResource gWR; +extern int g_iVisibleMouse; +class CCommandMenu; +int g_iPlayerClass; +int g_iTeamNumber; +int g_iUser1; +int g_iUser2; + +// Scoreboard positions +#define SBOARD_INDENT_X XRES(104) +#define SBOARD_INDENT_Y YRES(40) + +// low-res scoreboard indents +#define SBOARD_INDENT_X_512 30 +#define SBOARD_INDENT_Y_512 30 + +#define SBOARD_INDENT_X_400 0 +#define SBOARD_INDENT_Y_400 20 + +void IN_ResetMouse( void ); +extern CMenuPanel *CMessageWindowPanel_Create( const char *szMOTD, const char *szTitle, int iShadeFullscreen, int iRemoveMe, int x, int y, int wide, int tall ); + +using namespace vgui; + +// Team Colors +int iTeamColors[5][3] = +{ + { 255, 255, 255 }, + { 66, 115, 247 }, + { 220, 51, 38 }, + { 240, 135, 0 }, + { 115, 240, 115 }, +}; + +// Used for Class specific buttons +char *sTFClasses[] = +{ + "", + "SCOUT", + "SNIPER", + "SOLDIER", + "DEMOMAN", + "MEDIC", + "HWGUY", + "PYRO", + "SPY", + "ENGINEER", + "CIVILIAN", +}; + +char *sLocalisedClasses[] = +{ + "#Civilian", + "#Scout", + "#Sniper", + "#Soldier", + "#Demoman", + "#Medic", + "#HWGuy", + "#Pyro", + "#Spy", + "#Engineer", + "#Random", + "#Civilian", +}; + +char *sTFClassSelection[] = +{ + "civilian", + "scout", + "sniper", + "soldier", + "demoman", + "medic", + "hwguy", + "pyro", + "spy", + "engineer", + "randompc", + "civilian", +}; + +int iBuildingCosts[] = +{ + BUILD_COST_DISPENSER, + BUILD_COST_SENTRYGUN +}; + +// This maps class numbers to the Invalid Class bit. +// This is needed for backwards compatability in maps that were finished before +// all the classes were in TF. Hence the wacky sequence. +int sTFValidClassInts[] = +{ + 0, + TF_ILL_SCOUT, + TF_ILL_SNIPER, + TF_ILL_SOLDIER, + TF_ILL_DEMOMAN, + TF_ILL_MEDIC, + TF_ILL_HVYWEP, + TF_ILL_PYRO, + TF_ILL_SPY, + TF_ILL_ENGINEER, + TF_ILL_RANDOMPC, +}; + +// Get the name of TGA file, based on GameDir +char* GetVGUITGAName(const char *pszName) +{ + int i; + char sz[256]; + static char gd[256]; + const char *gamedir; + + if (ScreenWidth < 640) + i = 320; + else + i = 640; + sprintf(sz, pszName, i); + + gamedir = gEngfuncs.pfnGetGameDirectory(); + sprintf(gd, "%s/gfx/vgui/%s.tga",gamedir,sz); + + return gd; +} + +//================================================================ +// COMMAND MENU +//================================================================ +void CCommandMenu::AddButton( CommandButton *pButton ) +{ + if (m_iButtons >= MAX_BUTTONS) + return; + + m_aButtons[m_iButtons] = pButton; + m_iButtons++; + pButton->setParent( this ); + pButton->setFont( Scheme::sf_primary3 ); + + // give the button a default key binding + if ( m_iButtons < 10 ) + { + pButton->setBoundKey( m_iButtons + '0' ); + } + else if ( m_iButtons == 10 ) + { + pButton->setBoundKey( '0' ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Tries to find a button that has a key bound to the input, and +// presses the button if found +// Input : keyNum - the character number of the input key +// Output : Returns true if the command menu should close, false otherwise +//----------------------------------------------------------------------------- +bool CCommandMenu::KeyInput( int keyNum ) +{ + // loop through all our buttons looking for one bound to keyNum + for ( int i = 0; i < m_iButtons; i++ ) + { + if ( !m_aButtons[i]->IsNotValid() ) + { + if ( m_aButtons[i]->getBoundKey() == keyNum ) + { + // hit the button + if ( m_aButtons[i]->GetSubMenu() ) + { + // open the sub menu + gViewPort->SetCurrentCommandMenu( m_aButtons[i]->GetSubMenu() ); + return false; + } + else + { + // run the bound command + m_aButtons[i]->fireActionSignal(); + return true; + } + } + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: clears the current menus buttons of any armed (highlighted) +// state, and all their sub buttons +//----------------------------------------------------------------------------- +void CCommandMenu::ClearButtonsOfArmedState( void ) +{ + for ( int i = 0; i < GetNumButtons(); i++ ) + { + m_aButtons[i]->setArmed( false ); + + if ( m_aButtons[i]->GetSubMenu() ) + { + m_aButtons[i]->GetSubMenu()->ClearButtonsOfArmedState(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pSubMenu - +// Output : CommandButton +//----------------------------------------------------------------------------- +CommandButton *CCommandMenu::FindButtonWithSubmenu( CCommandMenu *pSubMenu ) +{ + for ( int i = 0; i < GetNumButtons(); i++ ) + { + if ( m_aButtons[i]->GetSubMenu() == pSubMenu ) + return m_aButtons[i]; + } + + return NULL; +} + +// Recalculate the visible buttons +bool CCommandMenu::RecalculateVisibles( int iNewYPos, bool bHideAll ) +{ + int iCurrentY = 0; + int iXPos, iYPos; + bool bHasButton = false; + + if (iNewYPos) + setPos( _pos[0], iNewYPos ); + + // Cycle through all the buttons in this menu, and see which will be visible + for (int i = 0; i < m_iButtons; i++) + { + int iClass = m_aButtons[i]->GetPlayerClass(); + if ( (iClass && iClass != g_iPlayerClass ) || ( m_aButtons[i]->IsNotValid() ) || bHideAll ) + { + m_aButtons[i]->setVisible( false ); + if ( m_aButtons[i]->GetSubMenu() != NULL ) + { + (m_aButtons[i]->GetSubMenu())->RecalculateVisibles( _pos[1] + iCurrentY, true ); + } + } + else + { + // If it's got a submenu, force it to check visibilities + if ( m_aButtons[i]->GetSubMenu() != NULL ) + { + if ( !(m_aButtons[i]->GetSubMenu())->RecalculateVisibles( _pos[1] + iCurrentY, false ) ) + { + // The submenu had no visible buttons, so don't display this button + m_aButtons[i]->setVisible( false ); + continue; + } + } + + m_aButtons[i]->setVisible( true ); + + // Make sure it's at the right Y position + m_aButtons[i]->getPos( iXPos, iYPos ); + m_aButtons[i]->setPos( iXPos, iCurrentY ); + + iCurrentY += BUTTON_SIZE_Y - 1; + bHasButton = true; + } + } + + // Set Size + setSize( _size[0], iCurrentY + 1 ); + + return bHasButton; +} + +// Make sure all submenus can fit on the screen +void CCommandMenu::RecalculatePositions( int iYOffset ) +{ + int iNewYPos = _pos[1] + iYOffset; + int iAdjust = 0; + + // Calculate if this is going to fit onscreen, and shuffle it up if it won't + int iBottom = iNewYPos + _size[1]; + if ( iBottom > ScreenHeight ) + { + // Move in increments of button sizes + while (iAdjust < (iBottom - ScreenHeight)) + { + iAdjust += BUTTON_SIZE_Y - 1; + } + iNewYPos -= iAdjust; + + // Make sure it doesn't move off the top of the screen (the menu's too big to fit it all) + if ( iNewYPos < 0 ) + { + iAdjust -= (0 - iNewYPos); + iNewYPos = 0; + } + } + + // We need to force all menus below this one to update their positions now, because they + // might have submenus riding off buttons in this menu that have just shifted. + for (int i = 0; i < m_iButtons; i++) + m_aButtons[i]->UpdateSubMenus( iAdjust ); + + setPos( _pos[0], iNewYPos ); +} + + +// Make this menu and all menus above it in the chain visible +void CCommandMenu::MakeVisible( CCommandMenu *pChildMenu ) +{ +/* + // Push down the button leading to the child menu + for (int i = 0; i < m_iButtons; i++) + { + if ( (pChildMenu != NULL) && (m_aButtons[i]->GetSubMenu() == pChildMenu) ) + { + m_aButtons[i]->setArmed( true ); + } + else + { + m_aButtons[i]->setArmed( false ); + } + } +*/ + + setVisible(true); + if (m_pParentMenu) + m_pParentMenu->MakeVisible( this ); +} + +//================================================================ +// CreateSubMenu +CCommandMenu *TeamFortressViewport::CreateSubMenu( CommandButton *pButton, CCommandMenu *pParentMenu ) +{ + int iXPos = 0; + int iYPos = 0; + int iWide = CMENU_SIZE_X; + int iTall = 0; + + if (pParentMenu) + { + iXPos = pParentMenu->GetXOffset() + CMENU_SIZE_X - 1; + iYPos = pParentMenu->GetYOffset() + BUTTON_SIZE_Y * (m_pCurrentCommandMenu->GetNumButtons() - 1); + } + + CCommandMenu *pMenu = new CCommandMenu(pParentMenu, iXPos, iYPos, iWide, iTall ); + pMenu->setParent(this); + pButton->AddSubMenu( pMenu ); + pButton->setFont( Scheme::sf_primary3 ); + + // Create the Submenu-open signal + InputSignal *pISignal = new CMenuHandler_PopupSubMenuInput(pButton, pMenu); + pButton->addInputSignal(pISignal); + + // Put a > to show it's a submenu + CImageLabel *pLabel = new CImageLabel( "arrow", CMENU_SIZE_X - SUBMENU_SIZE_X, SUBMENU_SIZE_Y ); + pLabel->setParent(pButton); + pLabel->addInputSignal(pISignal); + + // Reposition + pLabel->getPos( iXPos, iYPos ); + pLabel->setPos( CMENU_SIZE_X - pLabel->getImageWide(), (BUTTON_SIZE_Y - pLabel->getImageTall()) / 2 ); + + // Create the mouse off signal for the Label too + if (!pButton->m_bNoHighlight) + pLabel->addInputSignal( new CHandler_CommandButtonHighlight(pButton) ); + + return pMenu; +} + +//----------------------------------------------------------------------------- +// Purpose: Makes sure the memory allocated for TeamFortressViewport is nulled out +// Input : stAllocateBlock - +// Output : void * +//----------------------------------------------------------------------------- +void *TeamFortressViewport::operator new( size_t stAllocateBlock ) +{ +// void *mem = Panel::operator new( stAllocateBlock ); + void *mem = ::operator new( stAllocateBlock ); + memset( mem, 0, stAllocateBlock ); + return mem; +} + +//----------------------------------------------------------------------------- +// Purpose: InputSignal handler for the main viewport +//----------------------------------------------------------------------------- +class CViewPortInputHandler : public InputSignal +{ +public: + bool bPressed; + + CViewPortInputHandler() + { + } + + virtual void cursorMoved(int x,int y,Panel* panel) {} + virtual void cursorEntered(Panel* panel) {} + virtual void cursorExited(Panel* panel) {} + virtual void mousePressed(MouseCode code,Panel* panel) + { + if ( code != MOUSE_LEFT ) + { + // send a message to close the command menu + // this needs to be a message, since a direct call screws the timing + gEngfuncs.pfnClientCmd( "ForceCloseCommandMenu\n" ); + } + } + virtual void mouseReleased(MouseCode code,Panel* panel) + { + } + + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {} + virtual void mouseWheeled(int delta,Panel* panel) {} + virtual void keyPressed(KeyCode code,Panel* panel) {} + virtual void keyTyped(KeyCode code,Panel* panel) {} + virtual void keyReleased(KeyCode code,Panel* panel) {} + virtual void keyFocusTicked(Panel* panel) {} +}; + + +//================================================================ +TeamFortressViewport::TeamFortressViewport(int x,int y,int wide,int tall) : Panel(x,y,wide,tall), m_SchemeManager(wide,tall) +{ + gViewPort = this; + m_iInitialized = false; + m_pScoreBoard = NULL; + m_pSpectatorMenu = NULL; + m_pCurrentMenu = NULL; + m_pCurrentCommandMenu = NULL; + + CVAR_CREATE( "hud_classautokill", "1", FCVAR_ARCHIVE ); // controls whether or not to suicide immediately on TF class switch + CVAR_CREATE( "hud_takesshots", "0", FCVAR_ARCHIVE ); // controls whether or not to automatically take screenshots at the end of a round + + Initialize(); + addInputSignal( new CViewPortInputHandler ); + + int r, g, b, a; + + Scheme* pScheme = App::getInstance()->getScheme(); + + // primary text color + // Get the colors + //!! two different types of scheme here, need to integrate + SchemeHandle_t hPrimaryScheme = m_SchemeManager.getSchemeHandle( "Primary Button Text" ); + { + // font + pScheme->setFont( Scheme::sf_primary1, m_SchemeManager.getFont(hPrimaryScheme) ); + + // text color + m_SchemeManager.getFgColor( hPrimaryScheme, r, g, b, a ); + pScheme->setColor(Scheme::sc_primary1, r, g, b, a ); // sc_primary1 is non-transparent orange + + // background color (transparent black) + m_SchemeManager.getBgColor( hPrimaryScheme, r, g, b, a ); + pScheme->setColor(Scheme::sc_primary3, r, g, b, a ); + + // armed foreground color + m_SchemeManager.getFgArmedColor( hPrimaryScheme, r, g, b, a ); + pScheme->setColor(Scheme::sc_secondary2, r, g, b, a ); + + // armed background color + m_SchemeManager.getBgArmedColor( hPrimaryScheme, r, g, b, a ); + pScheme->setColor(Scheme::sc_primary2, r, g, b, a ); + + //!! need to get this color from scheme file + // used for orange borders around buttons + m_SchemeManager.getBorderColor( hPrimaryScheme, r, g, b, a ); + // pScheme->setColor(Scheme::sc_secondary1, r, g, b, a ); + pScheme->setColor(Scheme::sc_secondary1, 255*0.7, 170*0.7, 0, 0); + } + + // Change the second primary font (used in the scoreboard) + SchemeHandle_t hScoreboardScheme = m_SchemeManager.getSchemeHandle( "Scoreboard Text" ); + { + pScheme->setFont(Scheme::sf_primary2, m_SchemeManager.getFont(hScoreboardScheme) ); + } + + // Change the third primary font (used in command menu) + SchemeHandle_t hCommandMenuScheme = m_SchemeManager.getSchemeHandle( "CommandMenu Text" ); + { + pScheme->setFont(Scheme::sf_primary3, m_SchemeManager.getFont(hCommandMenuScheme) ); + } + + App::getInstance()->setScheme(pScheme); + + // VGUI MENUS + CreateScoreBoard(); + CreateCommandMenu(); + CreateServerBrowser(); + CreateSpectatorMenu(); + + // Discwar + CreateDiscIcons(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called everytime a new level is started. Viewport clears out it's data. +//----------------------------------------------------------------------------- +void TeamFortressViewport::Initialize( void ) +{ + // Force each menu to Initialize + if (m_pScoreBoard) + { + m_pScoreBoard->Initialize(); + HideScoreBoard(); + } + if (m_pSpectatorMenu) + { + // Spectator menu doesn't need initializing + m_pSpectatorMenu->setVisible( false ); + } + + // Make sure all menus are hidden + HideVGUIMenu(); + HideCommandMenu(); + + // Clear out some data + m_iGotAllMOTD = true; + m_iRandomPC = false; + m_flScoreBoardLastUpdated = 0; + + // reset player info + g_iPlayerClass = 0; + g_iTeamNumber = 0; + + strcpy(m_sMapName, ""); + strcpy(m_szServerName, ""); + for (int i = 0; i < 5; i++) + { + m_iValidClasses[i] = 0; + strcpy(m_sTeamNames[i], ""); + } + + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::scu_none) ); +} + +class CException; +//----------------------------------------------------------------------------- +// Purpose: Read the Command Menu structure from the txt file and create the menu. +//----------------------------------------------------------------------------- +void TeamFortressViewport::CreateCommandMenu( void ) +{ + // COMMAND MENU + // Create the root of the Command Menu + m_pCommandMenus[0] = new CCommandMenu(NULL, 0, CMENU_TOP, CMENU_SIZE_X, 300); // This will be resized once we know how many items are in it + m_pCommandMenus[0]->setParent(this); + m_pCommandMenus[0]->setVisible(false); + m_iNumMenus = 1; + m_iCurrentTeamNumber = m_iUser1 = m_iUser2 = 0; + + // Read Command Menu from the txt file + char token[1024]; + char *pfile = (char*)gEngfuncs.COM_LoadFile("commandmenu.txt", 5, NULL); + if (!pfile) + { + gEngfuncs.Con_DPrintf( "Unable to open commandmenu.txt\n"); + SetCurrentCommandMenu( NULL ); + return; + } + +#ifdef _WIN32 +try +{ +#endif + // First, read in the localisation strings + + // Detpack strings + gHUD.m_TextMessage.LocaliseTextString( "#DetpackSet_For5Seconds", m_sDetpackStrings[0], MAX_BUTTON_SIZE ); + gHUD.m_TextMessage.LocaliseTextString( "#DetpackSet_For20Seconds", m_sDetpackStrings[1], MAX_BUTTON_SIZE ); + gHUD.m_TextMessage.LocaliseTextString( "#DetpackSet_For50Seconds", m_sDetpackStrings[2], MAX_BUTTON_SIZE ); + + // Now start parsing the menu structure + m_pCurrentCommandMenu = m_pCommandMenus[0]; + char szLastButtonText[32] = "file start"; + pfile = gEngfuncs.COM_ParseFile(pfile, token); + while ( ( strlen ( token ) > 0 ) && ( m_iNumMenus < MAX_MENUS ) ) + { + // Keep looping until we hit the end of this menu + while ( token[0] != '}' && ( strlen( token ) > 0 ) ) + { + char cText[32] = ""; + char cBoundKey[32] = ""; + char cCustom[32] = ""; + static const int cCommandLength = 128; + char cCommand[cCommandLength] = ""; + char szMap[MAX_MAPNAME] = ""; + int iPlayerClass = 0; + int iCustom = false; + int iTeamOnly = 0; + bool bGetExtraToken = true; + CommandButton *pButton = NULL; + + // We should never be here without a Command Menu + if (!m_pCurrentCommandMenu) + { + gEngfuncs.Con_Printf("Error in Commandmenu.txt file after '%s'.\n", szLastButtonText ); + m_iInitialized = false; + return; + } + + // token should already be the bound key, or the custom name + strncpy( cCustom, token, 32 ); + cCustom[31] = '\0'; + + // See if it's a custom button + if (!strcmp(cCustom, "CUSTOM") ) + { + iCustom = true; + + // Get the next token + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + // See if it's a map + else if (!strcmp(cCustom, "MAP") ) + { + // Get the mapname + pfile = gEngfuncs.COM_ParseFile(pfile, token); + strncpy( szMap, token, MAX_MAPNAME ); + szMap[MAX_MAPNAME-1] = '\0'; + + // Get the next token + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + else if ( !strncmp(cCustom, "TEAM", 4) ) // TEAM1, TEAM2, TEAM3, TEAM4 + { + // make it a team only button + iTeamOnly = atoi( cCustom + 4 ); + + // Get the next token + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + else + { + // See if it's a Class + for (int i = 1; i <= PC_ENGINEER; i++) + { + if ( !strcmp(token, sTFClasses[i]) ) + { + // Save it off + iPlayerClass = i; + + // Get the button text + pfile = gEngfuncs.COM_ParseFile(pfile, token); + break; + } + } + } + + // Get the button bound key + strncpy( cBoundKey, token, 32 ); + cText[31] = '\0'; + + // Get the button text + pfile = gEngfuncs.COM_ParseFile(pfile, token); + strncpy( cText, token, 32 ); + cText[31] = '\0'; + + // save off the last button text we've come across (for error reporting) + strcpy( szLastButtonText, cText ); + + // Get the button command + pfile = gEngfuncs.COM_ParseFile(pfile, token); + strncpy( cCommand, token, cCommandLength ); + cCommand[cCommandLength - 1] = '\0'; + + // Custom button handling + if ( iCustom ) + { + pButton = CreateCustomButton( cText, cCommand ); + + // Get the next token to see if we're a menu + pfile = gEngfuncs.COM_ParseFile(pfile, token); + + if ( token[0] == '{' ) + { + strcpy( cCommand, token ); + } + else + { + bGetExtraToken = false; + } + } + else if ( szMap[0] != '\0' ) + { + // create a map button + pButton = new MapButton(szMap, cText,0, BUTTON_SIZE_Y * m_pCurrentCommandMenu->GetNumButtons(), CMENU_SIZE_X, BUTTON_SIZE_Y); + } + else if ( iTeamOnly ) + { + // button that only shows up if the player is on team iTeamOnly + pButton = new TeamOnlyCommandButton( iTeamOnly, cText,0, BUTTON_SIZE_Y * m_pCurrentCommandMenu->GetNumButtons(), CMENU_SIZE_X, BUTTON_SIZE_Y ); + } + else + { + // normal button + pButton = new CommandButton( iPlayerClass, cText,0, BUTTON_SIZE_Y * m_pCurrentCommandMenu->GetNumButtons(), CMENU_SIZE_X, BUTTON_SIZE_Y ); + } + + // add the button into the command menu + if ( pButton ) + { + m_pCurrentCommandMenu->AddButton( pButton ); + pButton->setBoundKey( cBoundKey[0] ); + pButton->setParentMenu( m_pCurrentCommandMenu ); + + // Override font in CommandMenu + pButton->setFont( Scheme::sf_primary3 ); + } + + // Find out if it's a submenu or a button we're dealing with + if ( cCommand[0] == '{' ) + { + if ( m_iNumMenus >= MAX_MENUS ) + { + gEngfuncs.Con_Printf( "Too many menus in commandmenu.txt past '%s'\n", szLastButtonText ); + } + else + { + // Create the menu + m_pCommandMenus[m_iNumMenus] = CreateSubMenu(pButton, m_pCurrentCommandMenu); + m_pCurrentCommandMenu = m_pCommandMenus[m_iNumMenus]; + m_iNumMenus++; + } + } + else if ( !iCustom ) + { + // Create the button and attach it to the current menu + pButton->addActionSignal(new CMenuHandler_StringCommand(cCommand)); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + + // Get the next token + if ( bGetExtraToken ) + { + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + } + + // Move back up a menu + m_pCurrentCommandMenu = m_pCurrentCommandMenu->GetParentMenu(); + + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } +#ifdef _WIN32 +} +catch( CException *e ) +{ + e; + //e->Delete(); + e = NULL; + m_iInitialized = false; + return; +} +#endif + + SetCurrentMenu( NULL ); + SetCurrentCommandMenu( NULL ); + gEngfuncs.COM_FreeFile( pfile ); + + m_iInitialized = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Creates all the class choices under a spy's disguise menus, and +// maps a command to them +// Output : CCommandMenu +//----------------------------------------------------------------------------- +CCommandMenu *TeamFortressViewport::CreateDisguiseSubmenu( CommandButton *pButton, CCommandMenu *pParentMenu, const char *commandText ) +{ + // create the submenu, under which the class choices will be listed + CCommandMenu *pMenu = CreateSubMenu( pButton, pParentMenu ); + m_pCommandMenus[m_iNumMenus] = pMenu; + m_iNumMenus++; + + // create the class choice buttons + for ( int i = PC_SCOUT; i <= PC_ENGINEER; i++ ) + { + CommandButton *pDisguiseButton = new CommandButton( CHudTextMessage::BufferedLocaliseTextString( sLocalisedClasses[i] ), 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y ); + + char sz[256]; + sprintf(sz, "%s %d", commandText, i ); + pDisguiseButton->addActionSignal(new CMenuHandler_StringCommand(sz)); + + pMenu->AddButton( pDisguiseButton ); + } + + return pMenu; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pButtonText - +// *pButtonName - +// Output : CommandButton +//----------------------------------------------------------------------------- +CommandButton *TeamFortressViewport::CreateCustomButton( char *pButtonText, char *pButtonName ) +{ + CommandButton *pButton = NULL; + CCommandMenu *pMenu = NULL; + + // ChangeTeam + if ( !strcmp( pButtonName, "!CHANGETEAM" ) ) + { + // ChangeTeam Submenu + pButton = new CommandButton(pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + + // Create the submenu + pMenu = CreateSubMenu(pButton, m_pCurrentCommandMenu); + m_pCommandMenus[m_iNumMenus] = pMenu; + m_iNumMenus++; + + // ChangeTeam buttons + for (int i = 0; i < 4; i++) + { + char sz[256]; + sprintf(sz, "jointeam %d", i+1); + m_pTeamButtons[i] = new TeamButton(i+1, "teamname", 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + m_pTeamButtons[i]->addActionSignal(new CMenuHandler_StringCommandWatch( sz )); + pMenu->AddButton( m_pTeamButtons[i] ); + } + + // Auto Assign button + m_pTeamButtons[4] = new TeamButton(5, gHUD.m_TextMessage.BufferedLocaliseTextString( "#Team_AutoAssign" ), 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + m_pTeamButtons[4]->addActionSignal(new CMenuHandler_StringCommand( "jointeam 5" )); + pMenu->AddButton( m_pTeamButtons[4] ); + + // Spectate button + m_pTeamButtons[5] = new SpectateButton( CHudTextMessage::BufferedLocaliseTextString( "#Menu_Spectate" ), 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y, false); + m_pTeamButtons[5]->addActionSignal(new CMenuHandler_StringCommand( "spectate" )); + pMenu->AddButton( m_pTeamButtons[5] ); + } + // ChangeClass + else if ( !strcmp( pButtonName, "!CHANGECLASS" ) ) + { + // Create the Change class menu + pButton = new ClassButton(-1, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y, false); + + // ChangeClass Submenu + pMenu = CreateSubMenu(pButton, m_pCurrentCommandMenu); + m_pCommandMenus[m_iNumMenus] = pMenu; + m_iNumMenus++; + + for (int i = PC_SCOUT; i <= PC_RANDOM; i++ ) + { + char sz[256]; + + // ChangeClass buttons + CHudTextMessage::LocaliseTextString( sLocalisedClasses[i], sz, 256 ); + ClassButton *pClassButton = new ClassButton( i, sz, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y, false); + + sprintf(sz, "%s", sTFClassSelection[i]); + pClassButton->addActionSignal(new CMenuHandler_StringCommandClassSelect(sz)); + pMenu->AddButton( pClassButton ); + } + } + // Map Briefing + else if ( !strcmp( pButtonName, "!MAPBRIEFING" ) ) + { + pButton = new CommandButton(pButtonText, 0, BUTTON_SIZE_Y * m_pCurrentCommandMenu->GetNumButtons(), CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_TextWindow(MENU_MAPBRIEFING)); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Class Descriptions + else if ( !strcmp( pButtonName, "!CLASSDESC" ) ) + { + pButton = new ClassButton(0, pButtonText, 0, BUTTON_SIZE_Y * m_pCurrentCommandMenu->GetNumButtons(), CMENU_SIZE_X, BUTTON_SIZE_Y, false); + pButton->addActionSignal(new CMenuHandler_TextWindow(MENU_CLASSHELP)); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!SERVERINFO" ) ) + { + pButton = new ClassButton(0, pButtonText, 0, BUTTON_SIZE_Y * m_pCurrentCommandMenu->GetNumButtons(), CMENU_SIZE_X, BUTTON_SIZE_Y, false); + pButton->addActionSignal(new CMenuHandler_TextWindow(MENU_INTRO)); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Spy abilities + else if ( !strcmp( pButtonName, "!SPY" ) ) + { + pButton = new DisguiseButton( 0, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y ); + } + // Feign + else if ( !strcmp( pButtonName, "!FEIGN" ) ) + { + pButton = new FeignButton(FALSE, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand( "feign" )); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Feign Silently + else if ( !strcmp( pButtonName, "!FEIGNSILENT" ) ) + { + pButton = new FeignButton(FALSE, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand( "sfeign" )); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Stop Feigning + else if ( !strcmp( pButtonName, "!FEIGNSTOP" ) ) + { + pButton = new FeignButton(TRUE, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand( "feign" )); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Disguise + else if ( !strcmp( pButtonName, "!DISGUISEENEMY" ) ) + { + // Create the disguise enemy button, which active only if there are 2 teams + pButton = new DisguiseButton(DISGUISE_TEAM2, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + CreateDisguiseSubmenu( pButton, m_pCurrentCommandMenu, "disguise_enemy" ); + } + else if ( !strcmp( pButtonName, "!DISGUISEFRIENDLY" ) ) + { + // Create the disguise friendly button, which active only if there are 1 or 2 teams + pButton = new DisguiseButton(DISGUISE_TEAM1 | DISGUISE_TEAM2, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + CreateDisguiseSubmenu( pButton, m_pCurrentCommandMenu, "disguise_friendly" ); + } + else if ( !strcmp( pButtonName, "!DISGUISE" ) ) + { + // Create the Disguise button + pButton = new DisguiseButton( DISGUISE_TEAM3 | DISGUISE_TEAM4, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + CCommandMenu *pDisguiseMenu = CreateSubMenu( pButton, m_pCurrentCommandMenu ); + m_pCommandMenus[m_iNumMenus] = pDisguiseMenu; + m_iNumMenus++; + + // Disguise Enemy submenu buttons + for ( int i = 1; i <= 4; i++ ) + { + // only show the 4th disguise button if we have 4 teams + m_pDisguiseButtons[i] = new DisguiseButton( ((i < 4) ? DISGUISE_TEAM3 : 0) | DISGUISE_TEAM4, "Disguise", 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + + pDisguiseMenu->AddButton( m_pDisguiseButtons[i] ); + + char sz[256]; + sprintf( sz, "disguise %d", i ); + CreateDisguiseSubmenu( m_pDisguiseButtons[i], pDisguiseMenu, sz ); + } + } + // Start setting a Detpack + else if ( !strcmp( pButtonName, "!DETPACKSTART" ) ) + { + // Detpack Submenu + pButton = new DetpackButton(2, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + + // Create the submenu + pMenu = CreateSubMenu(pButton, m_pCurrentCommandMenu); + m_pCommandMenus[m_iNumMenus] = pMenu; + m_iNumMenus++; + + // Set detpack buttons + CommandButton *pDetButton; + pDetButton = new CommandButton(m_sDetpackStrings[0], 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pDetButton->addActionSignal(new CMenuHandler_StringCommand("detstart 5")); + pMenu->AddButton( pDetButton ); + pDetButton = new CommandButton(m_sDetpackStrings[1], 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pDetButton->addActionSignal(new CMenuHandler_StringCommand("detstart 20")); + pMenu->AddButton( pDetButton ); + pDetButton = new CommandButton(m_sDetpackStrings[2], 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pDetButton->addActionSignal(new CMenuHandler_StringCommand("detstart 50")); + pMenu->AddButton( pDetButton ); + } + // Stop setting a Detpack + else if ( !strcmp( pButtonName, "!DETPACKSTOP" ) ) + { + pButton = new DetpackButton(1, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand( "detstop" )); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Engineer building + else if ( !strcmp( pButtonName, "!BUILD" ) ) + { + // only appears if the player is an engineer, and either they have built something or have enough metal to build + pButton = new BuildButton( BUILDSTATE_BASE, 0, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + } + else if ( !strcmp( pButtonName, "!BUILDSENTRY" ) ) + { + pButton = new BuildButton( BUILDSTATE_CANBUILD, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("build 2")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!BUILDDISPENSER" ) ) + { + pButton = new BuildButton( BUILDSTATE_CANBUILD, BuildButton::DISPENSER, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("build 1")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!ROTATESENTRY180" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("rotatesentry180")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!ROTATESENTRY" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("rotatesentry")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DISMANTLEDISPENSER" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::DISPENSER, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("dismantle 1")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DISMANTLESENTRY" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("dismantle 2")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DETONATEDISPENSER" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::DISPENSER, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("detdispenser")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DETONATESENTRY" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("detsentry")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Stop building + else if ( !strcmp( pButtonName, "!BUILDSTOP" ) ) + { + pButton = new BuildButton( BUILDSTATE_BUILDING, 0, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("build")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + + return pButton; +} + +void TeamFortressViewport::ToggleServerBrowser() +{ + if (!m_iInitialized) + return; + + if ( !m_pServerBrowser ) + return; + + if ( m_pServerBrowser->isVisible() ) + { + m_pServerBrowser->setVisible( false ); + } + else + { + m_pServerBrowser->setVisible( true ); + } + + UpdateCursorState(); +} + +//======================================================================= +void TeamFortressViewport::ShowCommandMenu() +{ + if (!m_iInitialized) + return; + + // Not visible while undefined + if (g_iPlayerClass == 0) + return; + + // is the command menu open? + if ( m_pCurrentCommandMenu ) + { + HideCommandMenu(); + return; + } + + // Not visible while in intermission + if ( gHUD.m_iIntermission ) + return; + + // Recalculate visible menus + UpdateCommandMenu(); + HideVGUIMenu(); + + SetCurrentCommandMenu( m_pCommandMenus[0] ); + m_flMenuOpenTime = gHUD.m_flTime; + UpdateCursorState(); + + // get command menu parameters + for ( int i = 2; i < gEngfuncs.Cmd_Argc(); i++ ) + { + const char *param = gEngfuncs.Cmd_Argv( i - 1 ); + if ( param ) + { + if ( m_pCurrentCommandMenu->KeyInput(param[0]) ) + { + // kill the menu open time, since the key input is final + HideCommandMenu(); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handles the key input of "-commandmenu" +// Input : +//----------------------------------------------------------------------------- +void TeamFortressViewport::InputSignalHideCommandMenu() +{ + if (!m_iInitialized) + return; + + // if they've just tapped the command menu key, leave it open + if ( (m_flMenuOpenTime + 0.3) > gHUD.m_flTime ) + return; + + HideCommandMenu(); +} + +//----------------------------------------------------------------------------- +// Purpose: Hides the command menu +//----------------------------------------------------------------------------- +void TeamFortressViewport::HideCommandMenu( void ) +{ + if (!m_iInitialized) + return; + + if ( m_pCommandMenus[0] ) + { + m_pCommandMenus[0]->ClearButtonsOfArmedState(); + } + + m_flMenuOpenTime = 0.0f; + SetCurrentCommandMenu( NULL ); + UpdateCursorState(); +} + +//----------------------------------------------------------------------------- +// Purpose: Bring up the scoreboard +//----------------------------------------------------------------------------- +void TeamFortressViewport::ShowScoreBoard( void ) +{ + if (m_pScoreBoard) + { + // No Scoreboard in single-player + if ( gEngfuncs.GetMaxClients() > 1 ) + { + m_pScoreBoard->Open(); + UpdateCursorState(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if the scoreboard is up +//----------------------------------------------------------------------------- +bool TeamFortressViewport::IsScoreBoardVisible( void ) +{ + if (m_pScoreBoard) + return m_pScoreBoard->isVisible(); + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Hide the scoreboard +//----------------------------------------------------------------------------- +void TeamFortressViewport::HideScoreBoard( void ) +{ + // Prevent removal of scoreboard during intermission + if ( gHUD.m_iIntermission ) + return; + + if (m_pScoreBoard) + { + m_pScoreBoard->setVisible(false); + + GetClientVoiceMgr()->StopSquelchMode(); + + UpdateCursorState(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Activate's the player special ability +// called when the player hits their "special" key +//----------------------------------------------------------------------------- +void TeamFortressViewport::InputPlayerSpecial( void ) +{ + if (!m_iInitialized) + return; + + if ( g_iPlayerClass == PC_ENGINEER || g_iPlayerClass == PC_SPY ) + { + ShowCommandMenu(); + + if ( m_pCurrentCommandMenu ) + { + m_pCurrentCommandMenu->KeyInput( '7' ); + } + } + else + { + // if it's any other class, just send the command down to the server + ClientCmd( "_special" ); + } +} + +// Set the submenu of the Command Menu +void TeamFortressViewport::SetCurrentCommandMenu( CCommandMenu *pNewMenu ) +{ + for (int i = 0; i < m_iNumMenus; i++) + m_pCommandMenus[i]->setVisible(false); + + m_pCurrentCommandMenu = pNewMenu; + + if (m_pCurrentCommandMenu) + m_pCurrentCommandMenu->MakeVisible( NULL ); +} + +void TeamFortressViewport::UpdateCommandMenu() +{ + m_pCommandMenus[0]->RecalculateVisibles( 0, false ); + m_pCommandMenus[0]->RecalculatePositions( 0 ); +} + +void TeamFortressViewport::UpdateSpectatorMenu() +{ + char sz[64]; + + if (!m_pSpectatorMenu) + return; + + // Don't pop up if the round windows are up + if ( m_pDiscStartRound && m_pDiscStartRound->isVisible() ) + return; + if ( m_pDiscEndRound && m_pDiscEndRound->isVisible() ) + return; + + if (m_iUser1) + { + m_pSpectatorMenu->setVisible( true ); + + if (m_iUser2 > 0 && m_iUser1 != 4 ) + { + // Locked onto a target, show the player's name + sprintf(sz, "#Spec_Mode%d : %s", m_iUser1, g_PlayerInfoList[ m_iUser2 ].name); + m_pSpectatorLabel->setText( CHudTextMessage::BufferedLocaliseTextString( sz ) ); + } + else + { + sprintf(sz, "#Spec_Mode%d", m_iUser1); + m_pSpectatorLabel->setText( CHudTextMessage::BufferedLocaliseTextString( sz ) ); + } + } + else + { + m_pSpectatorMenu->setVisible( false ); + } +} + +//====================================================================== +void TeamFortressViewport::CreateScoreBoard( void ) +{ + m_pScoreBoard = new ScorePanel(SBOARD_INDENT_X,SBOARD_INDENT_Y, ScreenWidth - (SBOARD_INDENT_X * 2), ScreenHeight - (SBOARD_INDENT_Y * 2)); + m_pScoreBoard->setParent(this); + m_pScoreBoard->setVisible(false); +} + +void TeamFortressViewport::CreateServerBrowser( void ) +{ + m_pServerBrowser = new ServerBrowser( 0, 0, ScreenWidth, ScreenHeight ); + m_pServerBrowser->setParent(this); + m_pServerBrowser->setVisible(false); +} + +//====================================================================== +// Disc vgui objects +void TeamFortressViewport::CreateDiscIcons( void ) +{ + m_iDiscPowerup = 0; + + // Create the disc ammo icons + int iX = (ScreenWidth - DISC_ICON_WIDTH) / 2; + int iXPos = iX - DISC_ICON_SPACER; + for (int i = 0; i < MAX_DISCS; i++) + { + m_pDiscIcons[i] = new CDiscPanel( iXPos + (i * DISC_ICON_SPACER), ScreenHeight - YRES(48), DISC_ICON_WIDTH, YRES(32) ); + m_pDiscIcons[i]->setParent(this); + } + + // Create the Arena Windows + m_pDiscStartRound = new CDiscArena_RoundStart(); + m_pDiscStartRound->setParent(this); + m_pDiscEndRound = new CDiscArena_RoundEnd(); + m_pDiscEndRound->setParent(this); + + // Create the Powerup window + m_pDiscPowerupWindow = new CDiscPowerups(); + m_pDiscPowerupWindow->setParent(this); + + // Create the Reward window + m_pDiscRewardWindow = new CDiscRewards(); + m_pDiscRewardWindow->setParent(this); +} + +//====================================================================== +// Set the VGUI Menu +void TeamFortressViewport::SetCurrentMenu( CMenuPanel *pMenu ) +{ + m_pCurrentMenu = pMenu; + if ( m_pCurrentMenu ) + { + // Don't open menus in demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + return; + + m_pCurrentMenu->Open(); + } +} + +//================================================================ +// Text Window +CMenuPanel* TeamFortressViewport::CreateTextWindow( int iTextToShow ) +{ + char sz[256]; + char *cText; + char *pfile = NULL; + static const int MAX_TITLE_LENGTH = 32; + char cTitle[MAX_TITLE_LENGTH]; + + if ( iTextToShow == SHOW_MOTD ) + { + if (!m_szServerName || !m_szServerName[0]) + strcpy( cTitle, "Half-Life" ); + else + strncpy( cTitle, m_szServerName, MAX_TITLE_LENGTH ); + cTitle[MAX_TITLE_LENGTH-1] = 0; + cText = m_szMOTD; + } + else if ( iTextToShow == SHOW_MAPBRIEFING ) + { + // Get the current mapname, and open it's map briefing text + if (m_sMapName && m_sMapName[0]) + { + strcpy( sz, "maps/"); + strcat( sz, m_sMapName ); + strcat( sz, ".txt" ); + } + else + { + const char *level = gEngfuncs.pfnGetLevelName(); + if (!level) + return NULL; + + strcpy( sz, level ); + char *ch = strchr( sz, '.' ); + *ch = '\0'; + strcat( sz, ".txt" ); + + // pull out the map name + strcpy( m_sMapName, level ); + ch = strchr( m_sMapName, '.' ); + if ( ch ) + { + *ch = 0; + } + + ch = strchr( m_sMapName, '/' ); + if ( ch ) + { + // move the string back over the '/' + memmove( m_sMapName, ch+1, strlen(ch)+1 ); + } + } + + pfile = (char*)gEngfuncs.COM_LoadFile( sz, 5, NULL ); + + if (!pfile) + return NULL; + + cText = pfile; + + strncpy( cTitle, m_sMapName, MAX_TITLE_LENGTH ); + cTitle[MAX_TITLE_LENGTH-1] = 0; + } + else if ( iTextToShow == SHOW_CLASSDESC ) + { + switch ( g_iPlayerClass ) + { + case PC_SCOUT: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_scout" ); + CHudTextMessage::LocaliseTextString( "#Title_scout", cTitle, MAX_TITLE_LENGTH ); break; + case PC_SNIPER: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_sniper" ); + CHudTextMessage::LocaliseTextString( "#Title_sniper", cTitle, MAX_TITLE_LENGTH ); break; + case PC_SOLDIER: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_soldier" ); + CHudTextMessage::LocaliseTextString( "#Title_soldier", cTitle, MAX_TITLE_LENGTH ); break; + case PC_DEMOMAN: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_demoman" ); + CHudTextMessage::LocaliseTextString( "#Title_demoman", cTitle, MAX_TITLE_LENGTH ); break; + case PC_MEDIC: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_medic" ); + CHudTextMessage::LocaliseTextString( "#Title_medic", cTitle, MAX_TITLE_LENGTH ); break; + case PC_HVYWEAP: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_hwguy" ); + CHudTextMessage::LocaliseTextString( "#Title_hwguy", cTitle, MAX_TITLE_LENGTH ); break; + case PC_PYRO: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_pyro" ); + CHudTextMessage::LocaliseTextString( "#Title_pyro", cTitle, MAX_TITLE_LENGTH ); break; + case PC_SPY: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_spy" ); + CHudTextMessage::LocaliseTextString( "#Title_spy", cTitle, MAX_TITLE_LENGTH ); break; + case PC_ENGINEER: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_engineer" ); + CHudTextMessage::LocaliseTextString( "#Title_engineer", cTitle, MAX_TITLE_LENGTH ); break; + case PC_CIVILIAN: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_civilian" ); + CHudTextMessage::LocaliseTextString( "#Title_civilian", cTitle, MAX_TITLE_LENGTH ); break; + default: + return NULL; + } + + if ( g_iPlayerClass == PC_CIVILIAN ) + { + sprintf(sz, "classes/long_civilian.txt"); + } + else + { + sprintf(sz, "classes/long_%s.txt", sTFClassSelection[ g_iPlayerClass ]); + } + char *pfile = (char*)gEngfuncs.COM_LoadFile( sz, 5, NULL ); + if (pfile) + { + cText = pfile; + } + } + + // if we're in the game (ie. have selected a class), flag the menu to be only grayed in the dialog box, instead of full screen + CMenuPanel *pMOTDPanel = CMessageWindowPanel_Create( cText, cTitle, g_iPlayerClass == PC_UNDEFINED, false, 0, 0, ScreenWidth, ScreenHeight ); + pMOTDPanel->setParent( this ); + + if ( pfile ) + gEngfuncs.COM_FreeFile( pfile ); + + return pMOTDPanel; +} + +//================================================================ +// VGUI Menus +void TeamFortressViewport::ShowVGUIMenu( int iMenu ) +{ + CMenuPanel *pNewMenu = NULL; + + // Don't open menus in demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + return; + + // Don't create one if it's already in the list + if (m_pCurrentMenu) + { + CMenuPanel *pMenu = m_pCurrentMenu; + while (pMenu != NULL) + { + if (pMenu->GetMenuID() == iMenu) + return; + pMenu = pMenu->GetNextMenu(); + } + } + + switch ( iMenu ) + { + // Map Briefing removed now that it appears in the team menu + case MENU_MAPBRIEFING: + pNewMenu = CreateTextWindow( SHOW_MAPBRIEFING ); + break; + + case MENU_INTRO: + pNewMenu = CreateTextWindow( SHOW_MOTD ); + break; + + case MENU_CLASSHELP: + pNewMenu = CreateTextWindow( SHOW_CLASSDESC ); + break; + + default: + break; + } + + if (!pNewMenu) + return; + + // Close the Command Menu if it's open + HideCommandMenu(); + + pNewMenu->SetMenuID( iMenu ); + pNewMenu->SetActive( true ); + + // See if another menu is visible, and if so, cache this one for display once the other one's finished + if (m_pCurrentMenu) + { + m_pCurrentMenu->SetNextMenu( pNewMenu ); + } + else + { + m_pCurrentMenu = pNewMenu; + m_pCurrentMenu->Open(); + UpdateCursorState(); + } +} + +// Removes all VGUI Menu's onscreen +void TeamFortressViewport::HideVGUIMenu() +{ + while (m_pCurrentMenu) + { + HideTopMenu(); + } +} + +// Remove the top VGUI menu, and bring up the next one +void TeamFortressViewport::HideTopMenu() +{ + if (m_pCurrentMenu) + { + // Close the top one + m_pCurrentMenu->Close(); + + // Bring up the next one + gViewPort->SetCurrentMenu( m_pCurrentMenu->GetNextMenu() ); + } + + UpdateCursorState(); +} + +// Return TRUE if the HUD's allowed to print text messages +bool TeamFortressViewport::AllowedToPrintText( void ) +{ + // Prevent text messages when fullscreen menus are up + if ( m_pCurrentMenu && g_iPlayerClass == 0 ) + { + int iId = m_pCurrentMenu->GetMenuID(); + if ( iId == MENU_TEAM || iId == MENU_CLASS || iId == MENU_INTRO || iId == MENU_CLASSHELP ) + return FALSE; + } + + return TRUE; +} + +//====================================================================================== +// SPECTATOR MENU +//====================================================================================== +// Spectator "Menu" explaining the Spectator buttons +void TeamFortressViewport::CreateSpectatorMenu() +{ + // Create the Panel + m_pSpectatorMenu = new CTransparentPanel(100, 0, ScreenHeight - YRES(60), ScreenWidth, YRES(60)); + m_pSpectatorMenu->setParent(this); + m_pSpectatorMenu->setVisible(false); + + // Get the scheme used for the Titles + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + + // schemes + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle( "Title Font" ); + SchemeHandle_t hHelpText = pSchemes->getSchemeHandle( "Primary Button Text" ); + + // color schemes + int r, g, b, a; + + // Create the title + m_pSpectatorLabel = new Label( "Spectator", 0, 0, ScreenWidth, YRES(25) ); + m_pSpectatorLabel->setParent( m_pSpectatorMenu ); + m_pSpectatorLabel->setFont( pSchemes->getFont(hTitleScheme) ); + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + m_pSpectatorLabel->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + m_pSpectatorLabel->setBgColor( r, g, b, 255 ); + m_pSpectatorLabel->setContentAlignment( vgui::Label::a_north ); + + // Create the Help + Label *pLabel = new Label( CHudTextMessage::BufferedLocaliseTextString( "#Spec_Help" ), 0, YRES(25), ScreenWidth, YRES(15) ); + pLabel->setParent( m_pSpectatorMenu ); + pLabel->setFont( pSchemes->getFont(hHelpText) ); + pSchemes->getFgColor( hHelpText, r, g, b, a ); + pLabel->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hHelpText, r, g, b, a ); + pLabel->setBgColor( r, g, b, 255 ); + pLabel->setContentAlignment( vgui::Label::a_north ); + + pLabel = new Label( CHudTextMessage::BufferedLocaliseTextString( "#Spec_Help2" ), 0, YRES(40), ScreenWidth, YRES(20) ); + pLabel->setParent( m_pSpectatorMenu ); + pLabel->setFont( pSchemes->getFont(hHelpText) ); + pSchemes->getFgColor( hHelpText, r, g, b, a ); + pLabel->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hHelpText, r, g, b, a ); + pLabel->setBgColor( r, g, b, 255 ); + pLabel->setContentAlignment( vgui::Label::a_center ); +} + +//====================================================================================== +// UPDATE HUD SECTIONS +//====================================================================================== +// We've got an update on player info +// Recalculate any menus that use it. +void TeamFortressViewport::UpdateOnPlayerInfo() +{ + if (m_pScoreBoard) + m_pScoreBoard->Update(); +} + +void TeamFortressViewport::UpdateCursorState() +{ + // Need cursor if any VGUI window is up + if ( m_pCurrentMenu || m_pServerBrowser->isVisible() || GetClientVoiceMgr()->IsInSquelchMode() ) + { + g_iVisibleMouse = true; + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::scu_arrow) ); + return; + } + else if ( m_pCurrentCommandMenu ) + { + // commandmenu doesn't have cursor if hud_capturemouse is turned off + if ( gHUD.m_pCvarStealMouse->value != 0.0f ) + { + g_iVisibleMouse = true; + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::scu_arrow) ); + return; + } + } + + IN_ResetMouse(); + g_iVisibleMouse = false; + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::scu_none) ); +} + +void TeamFortressViewport::UpdateHighlights() +{ + if (m_pCurrentCommandMenu) + m_pCurrentCommandMenu->MakeVisible( NULL ); +} + +void TeamFortressViewport::GetAllPlayersInfo( void ) +{ + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + GetPlayerInfo( i, &g_PlayerInfoList[i] ); + + if ( g_PlayerInfoList[i].thisplayer ) + m_pScoreBoard->m_iPlayerNum = i; // !!!HACK: this should be initialized elsewhere... maybe gotten from the engine + } +} + +void TeamFortressViewport::paintBackground() +{ + int wide, tall; + getParent()->getSize( wide, tall ); + setSize( wide, tall ); + + if (m_pScoreBoard) + { + int x, y; + getApp()->getCursorPos(x, y); + m_pScoreBoard->cursorMoved(x, y, m_pScoreBoard); + } + + // See if the command menu is visible and needs recalculating due to some external change + if ( g_iTeamNumber != m_iCurrentTeamNumber ) + { + UpdateCommandMenu(); + for (int i = 0; i < MAX_DISCS; i++) + { + if ( m_pDiscIcons[i] ) + m_pDiscIcons[i]->Update( i, false, m_iDiscPowerup ); + } + + m_iCurrentTeamNumber = g_iTeamNumber; + } + + if ( g_iPlayerClass != m_iCurrentPlayerClass ) + { + UpdateCommandMenu(); + + m_iCurrentPlayerClass = g_iPlayerClass; + } + + // Update the Reward window + if ( m_flRewardOpenTime && ( m_flRewardOpenTime < gHUD.m_flTime ) ) + { + m_pDiscRewardWindow->setVisible(false); + m_flRewardOpenTime = 0; + } + + // Update the disc icons the player has + bool bVisible = true; + if ( m_pDiscStartRound && m_pDiscStartRound->isVisible() ) + bVisible = false; + if ( m_pDiscEndRound && m_pDiscEndRound->isVisible() ) + bVisible = false; + if ( m_pSpectatorMenu && m_pSpectatorMenu->isVisible() ) + bVisible = false; + for (int i = 0; i < MAX_DISCS; i++) + { + if ( bVisible == false ) + { + m_pDiscIcons[i]->setVisible(false); + } + else + { + m_pDiscIcons[i]->Update(i, true, m_iDiscPowerup); + m_pDiscIcons[i]->setVisible(true); + } + } + + // See if the Spectator Menu needs to be update + if ( g_iUser1 != m_iUser1 || g_iUser2 != m_iUser2 ) + { + m_iUser1 = g_iUser1; + m_iUser2 = g_iUser2; + UpdateSpectatorMenu(); + } + + // Update the Scoreboard, if it's visible + if ( m_pScoreBoard->isVisible() && (m_flScoreBoardLastUpdated < gHUD.m_flTime) ) + { + m_pScoreBoard->Update(); + m_flScoreBoardLastUpdated = gHUD.m_flTime + 0.5; + } + + int extents[4]; + getAbsExtents(extents[0],extents[1],extents[2],extents[3]); + VGui_ViewportPaintBackground(extents); +} + +//================================================================ +// Input Handler for Drag N Drop panels +void CDragNDropHandler::cursorMoved(int x,int y,Panel* panel) +{ + if(m_bDragging) + { + App::getInstance()->getCursorPos(x,y); + m_pPanel->setPos(m_iaDragOrgPos[0]+(x-m_iaDragStart[0]),m_iaDragOrgPos[1]+(y-m_iaDragStart[1])); + + if(m_pPanel->getParent()!=null) + { + m_pPanel->getParent()->repaint(); + } + } +} + +void CDragNDropHandler::mousePressed(MouseCode code,Panel* panel) +{ + int x,y; + App::getInstance()->getCursorPos(x,y); + m_bDragging=true; + m_iaDragStart[0]=x; + m_iaDragStart[1]=y; + m_pPanel->getPos(m_iaDragOrgPos[0],m_iaDragOrgPos[1]); + App::getInstance()->setMouseCapture(panel); + + m_pPanel->setDragged(m_bDragging); + m_pPanel->requestFocus(); +} + +void CDragNDropHandler::mouseReleased(MouseCode code,Panel* panel) +{ + m_bDragging=false; + m_pPanel->setDragged(m_bDragging); + App::getInstance()->setMouseCapture(null); +} + +//================================================================ +// Number Key Input +bool TeamFortressViewport::SlotInput( int iSlot ) +{ + // If there's a menu up, give it the input + if ( m_pCurrentMenu ) + return m_pCurrentMenu->SlotInput( iSlot ); + + return FALSE; +} + +// Direct Key Input +int TeamFortressViewport::KeyInput( int down, int keynum, const char *pszCurrentBinding ) +{ + // Enter gets out of Spectator Mode by bringing up the Team Menu + if (m_iUser1 && gEngfuncs.Con_IsVisible() == false ) + { + if ( down && keynum == K_SPACE ) + gEngfuncs.pfnClientCmd("ob_next"); + if ( down && keynum == K_ENTER ) + gEngfuncs.pfnClientCmd("ob_mode"); + if ( down && keynum == K_CTRL ) + gEngfuncs.pfnClientCmd("ob_prev"); + } + + // Open Text Window? + if (m_pCurrentMenu && gEngfuncs.Con_IsVisible() == false) + { + int iMenuID = m_pCurrentMenu->GetMenuID(); + + // Get number keys as Input for Team/Class menus + if (iMenuID == MENU_TEAM || iMenuID == MENU_CLASS) + { + // Escape gets you out of Team/Class menus if the Cancel button is visible + if ( keynum == K_ESCAPE ) + { + if ( (iMenuID == MENU_TEAM && g_iTeamNumber) || (iMenuID == MENU_CLASS && g_iPlayerClass) ) + { + HideTopMenu(); + return 0; + } + } + + for (int i = '0'; i <= '9'; i++) + { + if ( down && (keynum == i) ) + { + SlotInput( i - '0' ); + return 0; + } + } + } + + // Grab enter keys to close TextWindows + if ( down && (keynum == K_ENTER || keynum == K_KP_ENTER || keynum == K_SPACE || keynum == K_ESCAPE) ) + { + if ( iMenuID == MENU_MAPBRIEFING || iMenuID == MENU_INTRO || iMenuID == MENU_CLASSHELP ) + { + HideTopMenu(); + return 0; + } + } + + } + + // if we're in a command menu, try hit one of it's buttons + if ( down && m_pCurrentCommandMenu ) + { + // Escape hides the command menu + if ( keynum == K_ESCAPE ) + { + HideCommandMenu(); + return 0; + } + + // only trap the number keys + if ( keynum >= '0' && keynum <= '9' ) + { + if ( m_pCurrentCommandMenu->KeyInput(keynum) ) + { + // a final command has been issued, so close the command menu + HideCommandMenu(); + } + + return 0; + } + } + + return 1; +} + +//================================================================ +// Message Handlers +int TeamFortressViewport::MsgFunc_ValClass(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + for (int i = 0; i < 5; i++) + m_iValidClasses[i] = READ_SHORT(); + + // Force the menu to update + UpdateCommandMenu(); + + return 1; +} + +int TeamFortressViewport::MsgFunc_TeamNames(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iNumberOfTeams = READ_BYTE(); + + for (int i = 0; i < m_iNumberOfTeams; i++) + { + int teamNum = i + 1; + + gHUD.m_TextMessage.LocaliseTextString( READ_STRING(), m_sTeamNames[teamNum], MAX_TEAMNAME_SIZE ); + + // Set the team name buttons + if (m_pTeamButtons[i]) + m_pTeamButtons[i]->setText( m_sTeamNames[teamNum] ); + + // Set the disguise buttons + if (m_pDisguiseButtons[i]) + m_pDisguiseButtons[i]->setText( m_sTeamNames[teamNum] ); + } + + return 1; +} + +int TeamFortressViewport::MsgFunc_Feign(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iIsFeigning = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu(); + + return 1; +} + +int TeamFortressViewport::MsgFunc_Detpack(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iIsSettingDetpack = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu(); + + return 1; +} + +int TeamFortressViewport::MsgFunc_VGUIMenu(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int iMenu = READ_BYTE(); + + // Map briefing includes the name of the map (because it's sent down before the client knows what map it is) + if (iMenu == MENU_MAPBRIEFING) + { + strncpy( m_sMapName, READ_STRING(), sizeof(m_sMapName) ); + m_sMapName[ sizeof(m_sMapName) - 1 ] = '\0'; + } + + // Bring up the menu6 + ShowVGUIMenu( iMenu ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_MOTD( const char *pszName, int iSize, void *pbuf ) +{ + if (m_iGotAllMOTD) + m_szMOTD[0] = 0; + + BEGIN_READ( pbuf, iSize ); + + m_iGotAllMOTD = READ_BYTE(); + strncat( m_szMOTD, READ_STRING(), sizeof(m_szMOTD) - strlen(m_szMOTD) ); + m_szMOTD[ sizeof(m_szMOTD)-1 ] = '\0'; + + if ( m_iGotAllMOTD ) + { + ShowVGUIMenu( MENU_INTRO ); + } + + return 1; +} + +int TeamFortressViewport::MsgFunc_BuildSt( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iBuildState = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu(); + + return 1; +} + +int TeamFortressViewport::MsgFunc_RandomPC( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iRandomPC = READ_BYTE(); + + return 1; +} + +int TeamFortressViewport::MsgFunc_ServerName( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + strncpy( m_szServerName, READ_STRING(), MAX_SERVERNAME_LENGTH ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_ScoreInfo( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + short cl = READ_BYTE(); + short frags = READ_SHORT(); + short deaths = READ_SHORT(); + short playerclass = READ_SHORT(); + short teamnumber = READ_SHORT(); + + if ( cl > 0 && cl <= MAX_PLAYERS ) + { + g_PlayerExtraInfo[cl].frags = frags; + g_PlayerExtraInfo[cl].deaths = deaths; + g_PlayerExtraInfo[cl].playerclass = playerclass; + g_PlayerExtraInfo[cl].teamnumber = teamnumber; + + UpdateOnPlayerInfo(); + } + + return 1; +} + +// Message handler for TeamScore message +// accepts three values: +// string: team name +// short: teams kills +// short: teams deaths +// if this message is never received, then scores will simply be the combined totals of the players. +int TeamFortressViewport::MsgFunc_TeamScore( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + char *TeamName = READ_STRING(); + + // find the team matching the name + int i; + for ( i = 1; i <= m_pScoreBoard->m_iNumTeams; i++ ) + { + if ( !stricmp( TeamName, g_TeamInfo[i].name ) ) + break; + } + + if ( i > m_pScoreBoard->m_iNumTeams ) + return 1; + + // use this new score data instead of combined player scoresw + g_TeamInfo[i].scores_overriden = TRUE; + g_TeamInfo[i].frags = READ_SHORT(); + g_TeamInfo[i].deaths = READ_SHORT(); + + return 1; +} + +// Message handler for TeamInfo message +// accepts two values: +// byte: client number +// string: client team name +int TeamFortressViewport::MsgFunc_TeamInfo( const char *pszName, int iSize, void *pbuf ) +{ + if (!m_pScoreBoard) + return 1; + + BEGIN_READ( pbuf, iSize ); + short cl = READ_BYTE(); + + if ( cl > 0 && cl <= MAX_PLAYERS ) + { + // set the players team + strncpy( g_PlayerExtraInfo[cl].teamname, READ_STRING(), MAX_TEAM_NAME ); + } + + // rebuild the list of teams + m_pScoreBoard->RebuildTeams(); + + return 1; +} + +void TeamFortressViewport::DeathMsg( int killer, int victim ) +{ + m_pScoreBoard->DeathMsg(killer,victim); +} + +int TeamFortressViewport::MsgFunc_Spectator( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + short cl = READ_BYTE(); + if ( cl > 0 && cl <= MAX_PLAYERS ) + { + g_IsSpectator[cl] = READ_BYTE(); + } + + return 1; +} + +int TeamFortressViewport::MsgFunc_AllowSpec( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iAllowSpectators = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu(); + + return 1; +} + +int TeamFortressViewport::MsgFunc_StartRnd(const char *pszName, int iSize, void *pbuf ) +{ + if (m_pDiscStartRound) + return m_pDiscStartRound->MsgFunc_GetPlayers( pszName, iSize, pbuf ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_EndRnd(const char *pszName, int iSize, void *pbuf ) +{ + if (m_pDiscEndRound) + return m_pDiscEndRound->MsgFunc_GetPlayers( pszName, iSize, pbuf ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_Frozen(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + int iFrozen = READ_BYTE(); + + if ( iFrozen ) + { + gHUD.m_Health.m_bitsDamage = DMG_FREEZE; + gHUD.m_Health.m_iFlags |= HUD_ACTIVE; + } + else + { + gHUD.m_Health.m_bitsDamage = 0; + gHUD.m_Health.m_iFlags &= ~HUD_ACTIVE; + } + + return 1; +} + +int TeamFortressViewport::MsgFunc_Powerup( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iDiscPowerup = READ_BYTE(); + + // Force the disc icons to update + for (int i = 0; i < MAX_DISCS; i++) + { + if ( m_pDiscIcons[i] ) + m_pDiscIcons[i]->Update( i, false, m_iDiscPowerup ); + } + + // Update the powerup window + m_pDiscPowerupWindow->RecalculateText( m_iDiscPowerup ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_Reward( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int iReward = READ_SHORT(); + + // Update the reward window + m_pDiscRewardWindow->RecalculateText( iReward ); + m_flRewardOpenTime = gHUD.m_flTime + 2.0; + + return 1; +} + + diff --git a/ricochet/cl_dll/vgui_TeamFortressViewport.h b/ricochet/cl_dll/vgui_TeamFortressViewport.h new file mode 100644 index 0000000..b1415cf --- /dev/null +++ b/ricochet/cl_dll/vgui_TeamFortressViewport.h @@ -0,0 +1,1207 @@ + +#ifndef TEAMFORTRESSVIEWPORT_H +#define TEAMFORTRESSVIEWPORT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// custom scheme handling +#include "vgui_SchemeManager.h" + +#define TF_DEFS_ONLY +#include "tf_defs.h" + +#include "discwar.h" + +using namespace vgui; + +class Cursor; +class ScorePanel; +class CCommandMenu; +class CommandLabel; +class CommandButton; +class BuildButton; +class ClassButton; +class CMenuPanel; +class ServerBrowser; +class DragNDropPanel; +class CTransparentPanel; +class CDiscPanel; +class CDiscArena_RoundStart; +class CDiscArena_RoundEnd; +class CDiscPowerups; +class CDiscRewards; + +char* GetVGUITGAName(const char *pszName); +BitmapTGA *LoadTGAForRes( const char* pImageName ); +void ScaleColors( int &r, int &g, int &b, int a ); +extern char *sTFClassSelection[]; +extern int sTFValidClassInts[]; +extern char *sLocalisedClasses[]; +extern int iTeamColors[5][3]; + +#define MAX_SERVERNAME_LENGTH 32 + +// Use this to set any co-ords in 640x480 space +#define XRES(x) (x * ((float)ScreenWidth / 640)) +#define YRES(y) (y * ((float)ScreenHeight / 480)) + +// Command Menu positions +#define MAX_MENUS 40 +#define MAX_BUTTONS 100 + +#define BUTTON_SIZE_Y YRES(30) +#define CMENU_SIZE_X XRES(160) + +#define SUBMENU_SIZE_X (CMENU_SIZE_X / 8) +#define SUBMENU_SIZE_Y (BUTTON_SIZE_Y / 6) + +#define CMENU_TOP (BUTTON_SIZE_Y * 4) + +#define MAX_TEAMNAME_SIZE 64 +#define MAX_BUTTON_SIZE 32 + +// Map Briefing Window +#define MAPBRIEF_INDENT 30 + +// Team Menu +#define TMENU_INDENT_X (30 * ((float)ScreenHeight / 640)) +#define TMENU_HEADER 100 +#define TMENU_SIZE_X (ScreenWidth - (TMENU_INDENT_X * 2)) +#define TMENU_SIZE_Y (TMENU_HEADER + BUTTON_SIZE_Y * 7) +#define TMENU_PLAYER_INDENT (((float)TMENU_SIZE_X / 3) * 2) +#define TMENU_INDENT_Y (((float)ScreenHeight - TMENU_SIZE_Y) / 2) + +// Class Menu +#define CLMENU_INDENT_X (30 * ((float)ScreenHeight / 640)) +#define CLMENU_HEADER 100 +#define CLMENU_SIZE_X (ScreenWidth - (CLMENU_INDENT_X * 2)) +#define CLMENU_SIZE_Y (CLMENU_HEADER + BUTTON_SIZE_Y * 11) +#define CLMENU_PLAYER_INDENT (((float)CLMENU_SIZE_X / 3) * 2) +#define CLMENU_INDENT_Y (((float)ScreenHeight - CLMENU_SIZE_Y) / 2) + +// Discwar icons +#define DISC_ICON_WIDTH XRES(32) +#define DISC_ICON_SPACER XRES(72) + +// Arrows +enum +{ + ARROW_UP, + ARROW_DOWN, + ARROW_LEFT, + ARROW_RIGHT, +}; + +//============================================================================== +// VIEWPORT PIECES +//============================================================ +// Wrapper for an Image Label without a background +class CImageLabel : public Label +{ +public: + BitmapTGA *m_pTGA; + +public: + void LoadImage(const char * pImageName); + CImageLabel( const char* pImageName,int x,int y ); + CImageLabel( const char* pImageName,int x,int y,int wide,int tall ); + + virtual int getImageTall(); + virtual int getImageWide(); + + virtual void paintBackground() + { + // Do nothing, so the background's left transparent. + } +}; + +// Command Label +// Overridden label so we can darken it when submenus open +class CommandLabel : public Label +{ +private: + int m_iState; + +public: + CommandLabel(const char* text,int x,int y,int wide,int tall) : Label(text,x,y,wide,tall) + { + m_iState = false; + } + + void PushUp() + { + m_iState = false; + repaint(); + } + + void PushDown() + { + m_iState = true; + repaint(); + } +}; + +//============================================================ +// Command Buttons +class CommandButton : public Button +{ +private: + int m_iPlayerClass; + + // Submenus under this button + CCommandMenu *m_pSubMenu; + CCommandMenu *m_pParentMenu; + CommandLabel *m_pSubLabel; + + char m_sMainText[MAX_BUTTON_SIZE]; + char m_cBoundKey; + + SchemeHandle_t m_hTextScheme; + + void RecalculateText( void ); + +public: + bool m_bNoHighlight; + +public: + // Constructors + CommandButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight = false); + CommandButton( int iPlayerClass, const char* text,int x,int y,int wide,int tall); + + void Init( void ); + + // Menu Handling + void AddSubMenu( CCommandMenu *pNewMenu ); + void AddSubLabel( CommandLabel *pSubLabel ) + { + m_pSubLabel = pSubLabel; + } + + virtual int IsNotValid( void ) + { + return false; + } + + void UpdateSubMenus( int iAdjustment ); + int GetPlayerClass() { return m_iPlayerClass; }; + CCommandMenu *GetSubMenu() { return m_pSubMenu; }; + + CCommandMenu *getParentMenu( void ); + void setParentMenu( CCommandMenu *pParentMenu ); + + // Overloaded vgui functions + virtual void paint(); + virtual void setText( const char *text ); + virtual void paintBackground(); + + void cursorEntered( void ); + void cursorExited( void ); + + void setBoundKey( char boundKey ); + char getBoundKey( void ); +}; + +//============================================================ +// Command Menus +class CCommandMenu : public Panel +{ +private: + CCommandMenu *m_pParentMenu; + int m_iXOffset; + int m_iYOffset; + + // Buttons in this menu + CommandButton *m_aButtons[ MAX_BUTTONS ]; + int m_iButtons; + +public: + CCommandMenu( CCommandMenu *pParentMenu, int x,int y,int wide,int tall ) : Panel(x,y,wide,tall) + { + m_pParentMenu = pParentMenu; + m_iXOffset = x; + m_iYOffset = y; + m_iButtons = 0; + } + + void AddButton( CommandButton *pButton ); + bool RecalculateVisibles( int iNewYPos, bool bHideAll ); + void RecalculatePositions( int iYOffset ); + void MakeVisible( CCommandMenu *pChildMenu ); + + CCommandMenu *GetParentMenu() { return m_pParentMenu; }; + int GetXOffset() { return m_iXOffset; }; + int GetYOffset() { return m_iYOffset; }; + int GetNumButtons() { return m_iButtons; }; + CommandButton *FindButtonWithSubmenu( CCommandMenu *pSubMenu ); + + void ClearButtonsOfArmedState( void ); + + + bool KeyInput( int keyNum ); + + virtual void paintBackground(); +}; + +//============================================================================== +class TeamFortressViewport : public Panel +{ +private: + vgui::Cursor* _cursorNone; + vgui::Cursor* _cursorArrow; + + int m_iInitialized; + + CCommandMenu *m_pCommandMenus[ MAX_MENUS ]; + CCommandMenu *m_pCurrentCommandMenu; + float m_flMenuOpenTime; + float m_flScoreBoardLastUpdated; + int m_iNumMenus; + int m_iCurrentTeamNumber; + int m_iCurrentPlayerClass; + + // VGUI Menus + void CreateSpectatorMenu( void ); + + // Scheme handler + CSchemeManager m_SchemeManager; + + // MOTD + int m_iGotAllMOTD; + char m_szMOTD[ MAX_MOTD_LENGTH ]; + + // Command Menu Team buttons + CommandButton *m_pTeamButtons[6]; + CommandButton *m_pDisguiseButtons[5]; + BuildButton *m_pBuildButtons[3]; + BuildButton *m_pBuildActiveButtons[3]; + + // Server Browser + ServerBrowser *m_pServerBrowser; + + // Spectator "menu" + Label *m_pSpectatorLabel; + int m_iAllowSpectators; + + // Data for specific sections of the Command Menu + int m_iValidClasses[5]; + int m_iIsFeigning; + int m_iIsSettingDetpack; + int m_iNumberOfTeams; + int m_iBuildState; + int m_iRandomPC; + char m_sTeamNames[5][MAX_TEAMNAME_SIZE]; + + // Localisation strings + char m_sDetpackStrings[3][MAX_BUTTON_SIZE]; + + char m_sMapName[64]; +public: + TeamFortressViewport(int x,int y,int wide,int tall); + void Initialize( void ); + + void CreateCommandMenu( void ); + void CreateScoreBoard( void ); + void CreateServerBrowser( void ); + CommandButton *CreateCustomButton( char *pButtonText, char *pButtonName ); + CCommandMenu *CreateDisguiseSubmenu( CommandButton *pButton, CCommandMenu *pParentMenu, const char *commandText ); + void CreateDiscIcons( void ); + + void UpdateCursorState( void ); + void UpdateCommandMenu( void ); + void UpdateOnPlayerInfo( void ); + void UpdateHighlights( void ); + void UpdateSpectatorMenu( void ); + + int KeyInput( int down, int keynum, const char *pszCurrentBinding ); + void InputPlayerSpecial( void ); + void GetAllPlayersInfo( void ); + void DeathMsg( int killer, int victim ); + + void ShowCommandMenu( void ); + void InputSignalHideCommandMenu( void ); + void HideCommandMenu( void ); + void SetCurrentCommandMenu( CCommandMenu *pNewMenu ); + void SetCurrentMenu( CMenuPanel *pMenu ); + + void ShowScoreBoard( void ); + void HideScoreBoard( void ); + bool IsScoreBoardVisible( void ); + + bool AllowedToPrintText( void ); + + void ShowVGUIMenu( int iMenu ); + void HideVGUIMenu( void ); + void HideTopMenu( void ); + + void ToggleServerBrowser( void ); + + CMenuPanel* CreateTextWindow( int iTextToShow ); + + CCommandMenu *CreateSubMenu( CommandButton *pButton, CCommandMenu *pParentMenu ); + + // Data Handlers + int GetValidClasses(int iTeam) { return m_iValidClasses[iTeam]; }; + int GetNumberOfTeams() { return m_iNumberOfTeams; }; + int GetIsFeigning() { return m_iIsFeigning; }; + int GetIsSettingDetpack() { return m_iIsSettingDetpack; }; + int GetBuildState() { return m_iBuildState; }; + int IsRandomPC() { return m_iRandomPC; }; + char *GetTeamName( int iTeam ) { return m_sTeamNames[iTeam]; }; + int GetAllowSpectators() { return m_iAllowSpectators; }; + + // Message Handlers + int MsgFunc_ValClass(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_TeamNames(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Feign(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Detpack(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_VGUIMenu(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_MOTD( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_BuildSt( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_RandomPC( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_ServerName( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_ScoreInfo( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_TeamScore( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_TeamInfo( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Spectator( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_AllowSpec( const char *pszName, int iSize, void *pbuf ); + // Discwar + int MsgFunc_StartRnd(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_EndRnd(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Powerup( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Reward( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Frozen( const char *pszName, int iSize, void *pbuf ); + + // Input + bool SlotInput( int iSlot ); + + virtual void paintBackground(); + + CSchemeManager *GetSchemeManager( void ) { return &m_SchemeManager; } + ScorePanel *GetScoreBoard( void ) { return m_pScoreBoard; } + + void *operator new( size_t stAllocateBlock ); + +public: + // VGUI Menus + CMenuPanel *m_pCurrentMenu; + ScorePanel *m_pScoreBoard; + char m_szServerName[ MAX_SERVERNAME_LENGTH ]; + CDiscPanel *m_pDiscIcons[MAX_DISCS]; + CDiscArena_RoundStart *m_pDiscStartRound; + CDiscArena_RoundEnd *m_pDiscEndRound; + CDiscPowerups *m_pDiscPowerupWindow; + CDiscRewards *m_pDiscRewardWindow; + + CTransparentPanel *m_pSpectatorMenu; + float m_flRewardOpenTime; + int m_iUser1; + int m_iUser2; + int m_iDiscPowerup; +}; + +//============================================================ +// Command Menu Button Handlers +#define MAX_COMMAND_SIZE 256 + +class CMenuHandler_StringCommand : public ActionSignal +{ +protected: + char m_pszCommand[MAX_COMMAND_SIZE]; + int m_iCloseVGUIMenu; +public: + CMenuHandler_StringCommand( char *pszCommand ) + { + strncpy( m_pszCommand, pszCommand, MAX_COMMAND_SIZE); + m_pszCommand[MAX_COMMAND_SIZE-1] = '\0'; + m_iCloseVGUIMenu = false; + } + + CMenuHandler_StringCommand( char *pszCommand, int iClose ) + { + strncpy( m_pszCommand, pszCommand, MAX_COMMAND_SIZE); + m_pszCommand[MAX_COMMAND_SIZE-1] = '\0'; + m_iCloseVGUIMenu = true; + } + + virtual void actionPerformed(Panel* panel) + { + gEngfuncs.pfnClientCmd(m_pszCommand); + + if (m_iCloseVGUIMenu) + gViewPort->HideTopMenu(); + else + gViewPort->HideCommandMenu(); + } +}; + +// This works the same as CMenuHandler_StringCommand, except it watches the string command +// for specific commands, and modifies client vars based upon them. +class CMenuHandler_StringCommandWatch : public CMenuHandler_StringCommand +{ +private: +public: + CMenuHandler_StringCommandWatch( char *pszCommand ) : CMenuHandler_StringCommand( pszCommand ) + { + } + + CMenuHandler_StringCommandWatch( char *pszCommand, int iClose ) : CMenuHandler_StringCommand( pszCommand, iClose ) + { + } + + virtual void actionPerformed(Panel* panel) + { + CMenuHandler_StringCommand::actionPerformed( panel ); + + // Try to guess the player's new team (it'll be corrected if it's wrong) + if ( !strcmp( m_pszCommand, "jointeam 1" ) ) + g_iTeamNumber = 1; + else if ( !strcmp( m_pszCommand, "jointeam 2" ) ) + g_iTeamNumber = 2; + else if ( !strcmp( m_pszCommand, "jointeam 3" ) ) + g_iTeamNumber = 3; + else if ( !strcmp( m_pszCommand, "jointeam 4" ) ) + g_iTeamNumber = 4; + } +}; + +// Used instead of CMenuHandler_StringCommand for Class Selection buttons. +// Checks the state of hud_classautokill and kills the player if set +class CMenuHandler_StringCommandClassSelect : public CMenuHandler_StringCommand +{ +private: +public: + CMenuHandler_StringCommandClassSelect( char *pszCommand ) : CMenuHandler_StringCommand( pszCommand ) + { + } + + CMenuHandler_StringCommandClassSelect( char *pszCommand, int iClose ) : CMenuHandler_StringCommand( pszCommand, iClose ) + { + } + + virtual void actionPerformed(Panel* panel); +}; + +class CMenuHandler_PopupSubMenuInput : public InputSignal +{ +private: + CCommandMenu *m_pSubMenu; + Button *m_pButton; +public: + CMenuHandler_PopupSubMenuInput( Button *pButton, CCommandMenu *pSubMenu ) + { + m_pSubMenu = pSubMenu; + m_pButton = pButton; + } + + virtual void cursorMoved(int x,int y,Panel* panel) + { + //gViewPort->SetCurrentCommandMenu( m_pSubMenu ); + } + + virtual void cursorEntered(Panel* panel) + { + gViewPort->SetCurrentCommandMenu( m_pSubMenu ); + + if (m_pButton) + m_pButton->setArmed(true); + }; + virtual void cursorExited(Panel* Panel) {}; + virtual void mousePressed(MouseCode code,Panel* panel) {}; + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {}; + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; +}; + +class CMenuHandler_LabelInput : public InputSignal +{ +private: + ActionSignal *m_pActionSignal; +public: + CMenuHandler_LabelInput( ActionSignal *pSignal ) + { + m_pActionSignal = pSignal; + } + + virtual void mousePressed(MouseCode code,Panel* panel) + { + m_pActionSignal->actionPerformed( panel ); + } + + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void cursorEntered(Panel* panel) {}; + virtual void cursorExited(Panel* Panel) {}; + virtual void cursorMoved(int x,int y,Panel* panel) {}; + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; +}; + +#define HIDE_TEXTWINDOW 0 +#define SHOW_MAPBRIEFING 1 +#define SHOW_CLASSDESC 2 +#define SHOW_MOTD 3 + +class CMenuHandler_TextWindow : public ActionSignal +{ +private: + int m_iState; +public: + CMenuHandler_TextWindow( int iState ) + { + m_iState = iState; + } + + virtual void actionPerformed(Panel* panel) + { + if (m_iState == HIDE_TEXTWINDOW) + { + gViewPort->HideTopMenu(); + } + else + { + gViewPort->HideCommandMenu(); + gViewPort->ShowVGUIMenu( m_iState ); + } + } +}; + +class CDragNDropHandler : public InputSignal +{ +private: + DragNDropPanel* m_pPanel; + bool m_bDragging; + int m_iaDragOrgPos[2]; + int m_iaDragStart[2]; + +public: + CDragNDropHandler(DragNDropPanel* pPanel) + { + m_pPanel = pPanel; + m_bDragging = false; + } + + void cursorMoved(int x,int y,Panel* panel); + void mousePressed(MouseCode code,Panel* panel); + void mouseReleased(MouseCode code,Panel* panel); + + void mouseDoublePressed(MouseCode code,Panel* panel) {}; + void cursorEntered(Panel* panel) {}; + void cursorExited(Panel* panel) {}; + void mouseWheeled(int delta,Panel* panel) {}; + void keyPressed(KeyCode code,Panel* panel) {}; + void keyTyped(KeyCode code,Panel* panel) {}; + void keyReleased(KeyCode code,Panel* panel) {}; + void keyFocusTicked(Panel* panel) {}; +}; + +class CHandler_MenuButtonOver : public InputSignal +{ +private: + int m_iButton; + CMenuPanel *m_pMenuPanel; +public: + CHandler_MenuButtonOver( CMenuPanel *pPanel, int iButton ) + { + m_iButton = iButton; + m_pMenuPanel = pPanel; + } + + void cursorEntered(Panel *panel); + + void cursorMoved(int x,int y,Panel* panel) {}; + void mousePressed(MouseCode code,Panel* panel) {}; + void mouseReleased(MouseCode code,Panel* panel) {}; + void mouseDoublePressed(MouseCode code,Panel* panel) {}; + void cursorExited(Panel* panel) {}; + void mouseWheeled(int delta,Panel* panel) {}; + void keyPressed(KeyCode code,Panel* panel) {}; + void keyTyped(KeyCode code,Panel* panel) {}; + void keyReleased(KeyCode code,Panel* panel) {}; + void keyFocusTicked(Panel* panel) {}; +}; + +class CHandler_ButtonHighlight : public InputSignal +{ +private: + Button *m_pButton; +public: + CHandler_ButtonHighlight( Button *pButton ) + { + m_pButton = pButton; + } + + virtual void cursorEntered(Panel* panel) + { + m_pButton->setArmed(true); + }; + virtual void cursorExited(Panel* Panel) + { + m_pButton->setArmed(false); + }; + virtual void mousePressed(MouseCode code,Panel* panel) {}; + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void cursorMoved(int x,int y,Panel* panel) {}; + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; +}; + +//----------------------------------------------------------------------------- +// Purpose: Special handler for highlighting of command menu buttons +//----------------------------------------------------------------------------- +class CHandler_CommandButtonHighlight : public CHandler_ButtonHighlight +{ +private: + CommandButton *m_pCommandButton; +public: + CHandler_CommandButtonHighlight( CommandButton *pButton ) : CHandler_ButtonHighlight( pButton ) + { + m_pCommandButton = pButton; + } + + virtual void cursorEntered( Panel *panel ) + { + m_pCommandButton->cursorEntered(); + } + + virtual void cursorExited( Panel *panel ) + { + m_pCommandButton->cursorExited(); + } +}; + + +//================================================================ +// Overidden Command Buttons for special visibilities +class ClassButton : public CommandButton +{ +protected: + int m_iPlayerClass; + +public: + ClassButton( int iClass, const char* text,int x,int y,int wide,int tall, bool bNoHighlight ) : CommandButton( text,x,y,wide,tall, bNoHighlight) + { + m_iPlayerClass = iClass; + } + + virtual int IsNotValid(); +}; + +class TeamButton : public CommandButton +{ +private: + int m_iTeamNumber; +public: + TeamButton( int iTeam, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iTeamNumber = iTeam; + } + + virtual int IsNotValid() + { + int iTeams = gViewPort->GetNumberOfTeams(); + // Never valid if there's only 1 team + if (iTeams == 1) + return true; + + // Auto Team's always visible + if (m_iTeamNumber == 5) + return false; + + if (iTeams >= m_iTeamNumber && m_iTeamNumber != g_iTeamNumber) + return false; + + return true; + } +}; + +class FeignButton : public CommandButton +{ +private: + int m_iFeignState; +public: + FeignButton( int iState, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iFeignState = iState; + } + + virtual int IsNotValid() + { + // Only visible for spies + if (g_iPlayerClass != PC_SPY) + return true; + + if (m_iFeignState == gViewPort->GetIsFeigning()) + return false; + + return true; + } +}; + +class SpectateButton : public CommandButton +{ +public: + SpectateButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight ) : CommandButton( text,x,y,wide,tall, bNoHighlight) + { + } + + virtual int IsNotValid() + { + // Only visible if the server allows it + if ( gViewPort->GetAllowSpectators() != 0 ) + return false; + + return true; + } +}; + +#define DISGUISE_TEAM1 (1<<0) +#define DISGUISE_TEAM2 (1<<1) +#define DISGUISE_TEAM3 (1<<2) +#define DISGUISE_TEAM4 (1<<3) + +class DisguiseButton : public CommandButton +{ +private: + int m_iValidTeamsBits; + int m_iThisTeam; +public: + DisguiseButton( int iValidTeamNumsBits, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall,false ) + { + m_iValidTeamsBits = iValidTeamNumsBits; + } + + virtual int IsNotValid() + { + // Only visible for spies + if ( g_iPlayerClass != PC_SPY ) + return true; + + // if it's not tied to a specific team, then always show (for spies) + if ( !m_iValidTeamsBits ) + return false; + + // if we're tied to a team make sure we can change to that team + int iTmp = 1 << (gViewPort->GetNumberOfTeams() - 1); + if ( m_iValidTeamsBits & iTmp ) + return false; + + return true; + } +}; + +class DetpackButton : public CommandButton +{ +private: + int m_iDetpackState; +public: + DetpackButton( int iState, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iDetpackState = iState; + } + + virtual int IsNotValid() + { + // Only visible for demomen + if (g_iPlayerClass != PC_DEMOMAN) + return true; + + if (m_iDetpackState == gViewPort->GetIsSettingDetpack()) + return false; + + return true; + } +}; + +extern int iBuildingCosts[]; +#define BUILDSTATE_HASBUILDING (1<<0) // Data is building ID (1 = Dispenser, 2 = Sentry) +#define BUILDSTATE_BUILDING (1<<1) +#define BUILDSTATE_BASE (1<<2) +#define BUILDSTATE_CANBUILD (1<<3) // Data is building ID (0 = Dispenser, 1 = Sentry) + +class BuildButton : public CommandButton +{ +private: + int m_iBuildState; + int m_iBuildData; + +public: + enum Buildings + { + DISPENSER = 0, + SENTRYGUN = 1, + }; + + BuildButton( int iState, int iData, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iBuildState = iState; + m_iBuildData = iData; + } + + virtual int IsNotValid() + { + // Only visible for engineers + if (g_iPlayerClass != PC_ENGINEER) + return true; + + // If this isn't set, it's only active when they're not building + if (m_iBuildState & BUILDSTATE_BUILDING) + { + // Make sure the player's building + if ( !(gViewPort->GetBuildState() & BS_BUILDING) ) + return true; + } + else + { + // Make sure the player's not building + if ( gViewPort->GetBuildState() & BS_BUILDING ) + return true; + } + + if (m_iBuildState & BUILDSTATE_BASE) + { + // Only appear if we've got enough metal to build something, or something already built + if ( gViewPort->GetBuildState() & (BS_HAS_SENTRYGUN | BS_HAS_DISPENSER | BS_CANB_SENTRYGUN | BS_CANB_DISPENSER) ) + return false; + + return true; + } + + // Must have a building + if (m_iBuildState & BUILDSTATE_HASBUILDING) + { + if ( m_iBuildData == BuildButton::DISPENSER && !(gViewPort->GetBuildState() & BS_HAS_DISPENSER) ) + return true; + if ( m_iBuildData == BuildButton::SENTRYGUN && !(gViewPort->GetBuildState() & BS_HAS_SENTRYGUN) ) + return true; + } + + // Can build something + if (m_iBuildState & BUILDSTATE_CANBUILD) + { + // Make sure they've got the ammo and don't have one already + if ( m_iBuildData == BuildButton::DISPENSER && (gViewPort->GetBuildState() & BS_CANB_DISPENSER) ) + return false; + if ( m_iBuildData == BuildButton::SENTRYGUN && (gViewPort->GetBuildState() & BS_CANB_SENTRYGUN) ) + return false; + + return true; + } + + return false; + } +}; + +#define MAX_MAPNAME 256 + +class MapButton : public CommandButton +{ +private: + char m_szMapName[ MAX_MAPNAME ]; + +public: + MapButton( const char *pMapName, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + sprintf( m_szMapName, "maps/%s.bsp", pMapName ); + } + + virtual int IsNotValid() + { + const char *level = gEngfuncs.pfnGetLevelName(); + if (!level) + return true; + + // Does it match the current map name? + if ( strcmp(m_szMapName, level) ) + return true; + + return false; + } +}; + +//----------------------------------------------------------------------------- +// Purpose: CommandButton which is only displayed if the player is on team X +//----------------------------------------------------------------------------- +class TeamOnlyCommandButton : public CommandButton +{ +private: + int m_iTeamNum; + +public: + TeamOnlyCommandButton( int iTeamNum, const char* text,int x,int y,int wide,int tall ) : + CommandButton( text, x, y, wide, tall ), m_iTeamNum(iTeamNum) {} + + virtual int IsNotValid() + { + if ( g_iTeamNumber != m_iTeamNum ) + return true; + + return CommandButton::IsNotValid(); + } +}; + +//============================================================ +// Panel that can be dragged around +class DragNDropPanel : public Panel +{ +private: + bool m_bBeingDragged; + LineBorder *m_pBorder; +public: + DragNDropPanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) + { + m_bBeingDragged = false; + + // Create the Drag Handler + addInputSignal( new CDragNDropHandler(this) ); + + // Create the border (for dragging) + m_pBorder = new LineBorder(); + } + + virtual void setDragged( bool bState ) + { + m_bBeingDragged = bState; + + if (m_bBeingDragged) + setBorder(m_pBorder); + else + setBorder(NULL); + } +}; + +//================================================================ +// Panel that draws itself with a transparent black background +class CTransparentPanel : public Panel +{ +private: + int m_iTransparency; +public: + CTransparentPanel(int iTrans, int x,int y,int wide,int tall) : Panel(x,y,wide,tall) + { + m_iTransparency = iTrans; + } + + virtual void paintBackground() + { + if (m_iTransparency) + { + // Transparent black background + drawSetColor( 0,0,0, m_iTransparency ); + drawFilledRect(0,0,_size[0],_size[1]); + } + } +}; + +//================================================================ +// Menu Panel that supports buffering of menus +class CMenuPanel : public CTransparentPanel +{ +private: + CMenuPanel *m_pNextMenu; + int m_iMenuID; + int m_iRemoveMe; + int m_iIsActive; + float m_flOpenTime; +public: + CMenuPanel(int iRemoveMe, int x,int y,int wide,int tall) : CTransparentPanel(100, x,y,wide,tall) + { + Reset(); + m_iRemoveMe = iRemoveMe; + } + + CMenuPanel(int iTrans, int iRemoveMe, int x,int y,int wide,int tall) : CTransparentPanel(iTrans, x,y,wide,tall) + { + Reset(); + m_iRemoveMe = iRemoveMe; + } + + virtual void Reset( void ) + { + m_pNextMenu = NULL; + m_iIsActive = false; + m_flOpenTime = 0; + } + + void SetNextMenu( CMenuPanel *pNextPanel ) + { + if (m_pNextMenu) + m_pNextMenu->SetNextMenu( pNextPanel ); + else + m_pNextMenu = pNextPanel; + } + + void SetMenuID( int iID ) + { + m_iMenuID = iID; + } + + void SetActive( int iState ) + { + m_iIsActive = iState; + } + + virtual void Open( void ) + { + setVisible( true ); + + // Note the open time, so we can delay input for a bit + m_flOpenTime = gHUD.m_flTime; + } + + virtual void Close( void ) + { + setVisible( false ); + m_iIsActive = false; + + if ( m_iRemoveMe ) + gViewPort->removeChild( this ); + + // This MenuPanel has now been deleted. Don't append code here. + } + + int ShouldBeRemoved() { return m_iRemoveMe; }; + CMenuPanel* GetNextMenu() { return m_pNextMenu; }; + int GetMenuID() { return m_iMenuID; }; + int IsActive() { return m_iIsActive; }; + float GetOpenTime() { return m_flOpenTime; }; + + // Numeric input + virtual bool SlotInput( int iSlot ) { return false; }; + virtual void SetActiveInfo( int iInput ) {}; +}; + +//================================================================ +// Custom drawn scroll bars +class CTFScrollButton : public CommandButton +{ +private: + BitmapTGA *m_pTGA; + +public: + CTFScrollButton(int iArrow, const char* text,int x,int y,int wide,int tall); + + virtual void paint( void ); + virtual void paintBackground( void ); +}; + +// Custom drawn slider bar +class CTFSlider : public Slider +{ +public: + CTFSlider(int x,int y,int wide,int tall,bool vertical) : Slider(x,y,wide,tall,vertical) + { + }; + + virtual void paintBackground( void ); +}; + +// Custom drawn scrollpanel +class CTFScrollPanel : public ScrollPanel +{ +public: + CTFScrollPanel(int x,int y,int wide,int tall); +}; + +//================================================================ +// Specific Menus to handle old HUD sections +class CHealthPanel : public DragNDropPanel +{ +private: + BitmapTGA *m_pHealthTGA; + Label *m_pHealthLabel; +public: + CHealthPanel(int x,int y,int wide,int tall) : DragNDropPanel(x,y,wide,tall) + { + // Load the Health icon + FileInputStream* fis = new FileInputStream( GetVGUITGAName("%d_hud_health"), false); + m_pHealthTGA = new BitmapTGA(fis,true); + fis->close(); + + // Create the Health Label + int iXSize,iYSize; + m_pHealthTGA->getSize(iXSize,iYSize); + m_pHealthLabel = new Label("",0,0,iXSize,iYSize); + m_pHealthLabel->setImage(m_pHealthTGA); + m_pHealthLabel->setParent(this); + + // Set panel dimension + // Shouldn't be needed once Billy's fized setImage not recalculating the size + //setSize( iXSize + 100, gHUD.m_iFontHeight + 10 ); + //m_pHealthLabel->setPos( 10, (getTall() - iYSize) / 2 ); + } + + virtual void paintBackground() + { + } + + void paint() + { + // Get the paint color + int r,g,b,a; + // Has health changed? Flash the health # + if (gHUD.m_Health.m_fFade) + { + gHUD.m_Health.m_fFade -= (gHUD.m_flTimeDelta * 20); + if (gHUD.m_Health.m_fFade <= 0) + { + a = MIN_ALPHA; + gHUD.m_Health.m_fFade = 0; + } + + // Fade the health number back to dim + a = MIN_ALPHA + (gHUD.m_Health.m_fFade/FADE_TIME) * 128; + } + else + a = MIN_ALPHA; + + gHUD.m_Health.GetPainColor( r, g, b ); + ScaleColors(r, g, b, a ); + + // If health is getting low, make it bright red + if (gHUD.m_Health.m_iHealth <= 15) + a = 255; + + int iXSize,iYSize, iXPos, iYPos; + m_pHealthTGA->getSize(iXSize,iYSize); + m_pHealthTGA->getPos(iXPos, iYPos); + + // Paint the player's health + int x = gHUD.DrawHudNumber( iXPos + iXSize + 5, iYPos + 5, DHN_3DIGITS | DHN_DRAWZERO, gHUD.m_Health.m_iHealth, r, g, b); + + // Draw the vertical line + int HealthWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; + x += HealthWidth / 2; + FillRGBA(x, iYPos + 5, HealthWidth / 10, gHUD.m_iFontHeight, 255, 160, 0, a); + } +}; + +#endif diff --git a/ricochet/cl_dll/vgui_discobjects.cpp b/ricochet/cl_dll/vgui_discobjects.cpp new file mode 100644 index 0000000..39401e8 --- /dev/null +++ b/ricochet/cl_dll/vgui_discobjects.cpp @@ -0,0 +1,593 @@ +//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: VGUI objects for Discwar +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "VGUI_Font.h" + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "parsemsg.h" +#include "ammo.h" +#include "string.h" +#include "ammohistory.h" + +#include "vgui_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" +#include "vgui_discobjects.h" + +// Positions and Dimensions +#define ARENAWINDOW_SIZE_X (ScreenWidth) +#define ARENAWINDOW_SIZE_Y YRES(128) +#define ARENAWINDOW_X ((ScreenWidth - ARENAWINDOW_SIZE_X) / 2) +#define ARENAWINDOW_Y (ScreenHeight - ARENAWINDOW_SIZE_Y) + +#define POWERUP_SIZE_X (ScreenWidth) +#define POWERUP_SIZE_Y YRES(32) +#define POWERUP_X ((ScreenWidth - POWERUP_SIZE_X) / 2) +#define POWERUP_Y (ScreenHeight - POWERUP_SIZE_Y) + +#define REWARD_SIZE_X (ScreenWidth) +#define REWARD_SIZE_Y YRES(48) +#define REWARD_X ((ScreenWidth - POWERUP_SIZE_X) / 2) +#define REWARD_Y (ScreenHeight / 6) + +extern WeaponsResource gWR; +int g_iCannotFire; + +//=========================================================== +// Disc ammo icon +CDiscPanel::CDiscPanel(int x,int y,int wide,int tall) : Label("", x,y,wide,tall) +{ + setContentFitted(true); + + // Standard discs + m_pDiscTGA_Red = LoadTGAForRes("discred"); + m_pDiscTGA_RedGlow = LoadTGAForRes("discred2"); + m_pDiscTGA_Blue = LoadTGAForRes("discblue"); + m_pDiscTGA_BlueGlow = LoadTGAForRes("discblue2"); + m_pDiscTGA_Grey = LoadTGAForRes("discgrey"); + + // Powerup discs + m_pDiscTGA_Fast = LoadTGAForRes("fast"); + m_pDiscTGA_Freeze = LoadTGAForRes("freeze"); + m_pDiscTGA_Hard = LoadTGAForRes("hard"); + m_pDiscTGA_Triple = LoadTGAForRes("triple"); + + setImage( m_pDiscTGA_Red ); +} + +void CDiscPanel::Update( int iDiscNo, bool bGlow, int iPowerup ) +{ + int iDiscs = gWR.GetAmmo( 1 ); + + // Grey disc for missing discs + if ( iDiscs < iDiscNo+1 ) + { + setImage( m_pDiscTGA_Grey ); + } + // Powerups override team colored discs + else if ( iPowerup & POW_TRIPLE ) + { + setImage( m_pDiscTGA_Triple ); + } + else if ( iPowerup & POW_FAST ) + { + setImage( m_pDiscTGA_Fast ); + } + else if ( iPowerup & POW_FREEZE ) + { + setImage( m_pDiscTGA_Freeze ); + } + else if ( iPowerup & POW_HARD ) + { + setImage( m_pDiscTGA_Hard ); + } + else if (g_iTeamNumber == 1) + { + if ( gWR.GetAmmo( 1 ) == 3 ) + setImage( m_pDiscTGA_RedGlow ); + else + setImage( m_pDiscTGA_Red ); + } + else + { + if ( gWR.GetAmmo( 1 ) == 3 ) + setImage( m_pDiscTGA_BlueGlow ); + else + setImage( m_pDiscTGA_Blue ); + } +} + +//=========================================================== +// Arena window +CDiscArenaPanel::CDiscArenaPanel( int x, int y, int wide, int tall ) : CTransparentPanel(255, x,y,wide,tall) +{ + m_iNumPlayers = 0; +} + +//=========================================================== +// Message handler. Gets the Ids of the players in the round. +int CDiscArenaPanel::MsgFunc_GetPlayers(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iRoundNumber = READ_BYTE(); + m_iSecondsToGo = READ_BYTE(); + + m_iNumPlayers = READ_BYTE(); + if ( m_iNumPlayers > 0 && m_iNumPlayers <= MAX_PLAYERS ) + { + for (int i = 0; i < m_iNumPlayers; i++) + m_iClients[i] = READ_SHORT(); + } + + RecalculateText(); + + return 1; +} + +//=========================================================== +// Message handler. Gets the Ids of the players in the round. +void CDiscArenaPanel::GetClientList( char *pszString ) +{ + strcpy( pszString, "" ); + for (int i = 0; i < m_iNumPlayers; i++ ) + { + if ( m_iClients[i] <= 0 || m_iClients[i] > MAX_PLAYERS ) + { + gEngfuncs.Con_Printf( "Combatant %d out of range: %d\n", i, m_iClients[i] ); + continue; + } + + if ( g_PlayerInfoList[ m_iClients[i] ].name && g_PlayerInfoList[ m_iClients[i] ].name[0] ) + { + if ( i > 0 ) + { + if ( i == (m_iNumPlayers - 1) ) + { + strcat( pszString, CHudTextMessage::BufferedLocaliseTextString( "#And" ) ); + } + else + { + strcat( pszString, ", " ); + } + } + + strcat( pszString, g_PlayerInfoList[ m_iClients[i] ].name ); + } + } +} + +//=========================================================== +// Round start window +#define ROUND_Y YRES(0) +#define TEAMONE_Y (ROUND_Y + YRES(32)) +#define VERSUS_Y (TEAMONE_Y + YRES(32)) +#define TEAMTWO_Y (VERSUS_Y + YRES(32)) + +CDiscArena_RoundStart::CDiscArena_RoundStart( void ) : CDiscArenaPanel( ARENAWINDOW_X, ARENAWINDOW_Y, ARENAWINDOW_SIZE_X, ARENAWINDOW_SIZE_Y ) +{ + m_pRound = new Label( "Round 1", 0, ROUND_Y, getWide(), YRES(32) ); + m_pRound->setParent( this ); + m_pRound->setBgColor( 0, 0, 0, 128 ); + m_pRound->setFgColor( 255,255,255, 0 ); + m_pRound->setContentAlignment( vgui::Label::a_center ); + + m_pTeamOne = new Label( "Team One", 0, TEAMONE_Y, getWide(), YRES(32) ); + m_pTeamOne->setParent( this ); + m_pTeamOne->setBgColor( 128, 0, 0, 128 ); + m_pTeamOne->setFgColor( 255,255,255, 0 ); + m_pTeamOne->setContentAlignment( vgui::Label::a_center ); + + // Trim the trailing \n from the VS string + char sz[32]; + strcpy( sz, CHudTextMessage::BufferedLocaliseTextString( "#Versus" ) ); + sz[ strlen(sz) - 1 ] = '\0'; + Label *pLabel = new Label( sz, 0, VERSUS_Y, getWide(), YRES(32) ); + pLabel->setParent( this ); + pLabel->setBgColor( 0, 0, 0, 255 ); + pLabel->setFgColor( 255,255,255, 0 ); + pLabel->setContentAlignment( vgui::Label::a_center ); + + m_pTeamTwo = new Label( "Team Two", 0, TEAMTWO_Y, getWide(), YRES(32) ); + m_pTeamTwo->setParent( this ); + m_pTeamTwo->setBgColor( 0, 0, 128, 128 ); + m_pTeamTwo->setFgColor( 255,255,255, 0 ); + m_pTeamTwo->setContentAlignment( vgui::Label::a_center ); + + setVisible(false); +} + +// Recalculate the Text in the window +void CDiscArena_RoundStart::RecalculateText( void ) +{ + char sz[1024]; + char szOpponents[1024]; + char szTemp[256]; + char szTemp2[256]; + char szTemp3[256]; + char *pszLocalized = NULL; + + // Round started? + if (m_iSecondsToGo == 0) + { + setVisible(false); + g_iCannotFire = FALSE; + + // Force spectator menu to update + if (gViewPort) + gViewPort->m_iUser1 = 0; + return; + } + + g_iCannotFire = TRUE; + + // Round Number + if ( m_iSecondsToGo != 1 ) + { + pszLocalized = "#Round_Start_n_Seconds"; + } + else + { + pszLocalized = "#Round_Start_1_Second"; + } + + strncpy( szTemp3, CHudTextMessage::BufferedLocaliseTextString( pszLocalized ), sizeof( szTemp3 ) - 1 ); + szTemp3[ sizeof( szTemp3 ) - 1 ] = '\0'; + sprintf( sz, szTemp3, m_iRoundNumber, m_iSecondsToGo ); + + m_pRound->setText( sz ); + + // We may have just got an update for the time to go. If so, m_iNumPlayers will be 0. + if ( !m_iNumPlayers ) + return; + + if (gViewPort) + gViewPort->GetAllPlayersInfo(); + + // Find out what team this client's on (if a new battle's just starting) + strcpy( szOpponents, "" ); + int iMyTeamNumber = 0; + if ( m_iRoundNumber == 1 ) + { + for (int i = 0; i < m_iNumPlayers; i++ ) + { + if ( g_PlayerInfoList[ m_iClients[i] ].thisplayer ) + iMyTeamNumber = (i < (m_iNumPlayers / 2)) ? 1 : 2; + } + } + + // Team 1 + strcpy( sz, "" ); + int i; + for (i = 0; i < (m_iNumPlayers / 2); i++ ) + { + if ( g_PlayerInfoList[ m_iClients[i] ].name && g_PlayerInfoList[ m_iClients[i] ].name[0] ) + strcat( sz, g_PlayerInfoList[ m_iClients[i] ].name ); + + if ( iMyTeamNumber == 2 ) + { + strcpy( szTemp, CHudTextMessage::BufferedLocaliseTextString( "#Opponent" ) ); + sprintf( szTemp2, szTemp, g_PlayerInfoList[ m_iClients[i] ].name, g_PlayerExtraInfo[ m_iClients[i] ].deaths, g_PlayerExtraInfo[ m_iClients[i] ].frags ); + strcat( szOpponents, szTemp2 ); + } + } + m_pTeamOne->setText( sz ); + + // Team 2 + strcpy( sz, "" ); + for ( ; i < m_iNumPlayers; i++ ) + { + if ( g_PlayerInfoList[ m_iClients[i] ].name && g_PlayerInfoList[ m_iClients[i] ].name[0] ) + strcat( sz, g_PlayerInfoList[ m_iClients[i] ].name ); + + if ( iMyTeamNumber == 1 ) + { + strcpy( szTemp, CHudTextMessage::BufferedLocaliseTextString( "#Opponent" ) ); + sprintf( szTemp2, szTemp, g_PlayerInfoList[ m_iClients[i] ].name, g_PlayerExtraInfo[ m_iClients[i] ].deaths, g_PlayerExtraInfo[ m_iClients[i] ].frags ); + strcat( szOpponents, szTemp2 ); + } + } + m_pTeamTwo->setText( sz ); + + // Bring up the Opponent details + if (gViewPort) + gViewPort->m_pDiscRewardWindow->SetMessage( szOpponents ); + + // Become visible + setVisible(true); + + // Hide the other windows if it's up + if (gViewPort) + { + gViewPort->m_pSpectatorMenu->setVisible( false ); + gViewPort->m_pDiscPowerupWindow->setVisible( false ); + gViewPort->m_pDiscEndRound->setVisible( false ); + } +} + +//=========================================================== +// Round end window +CDiscArena_RoundEnd::CDiscArena_RoundEnd( void ) : CDiscArenaPanel( ARENAWINDOW_X, ARENAWINDOW_Y, ARENAWINDOW_SIZE_X, ARENAWINDOW_SIZE_Y ) +{ + m_pRound = new Label( "Round 1 Won By", 0, ROUND_Y, getWide(), YRES(32) ); + m_pRound->setParent( this ); + m_pRound->setBgColor( 0, 0, 0, 128 ); + m_pRound->setFgColor( 255,255,255, 0 ); + m_pRound->setContentAlignment( vgui::Label::a_center ); + + m_pWinners = new Label( "Winners", 0, TEAMONE_Y, getWide(), YRES(32) ); + m_pWinners->setParent( this ); + m_pWinners->setBgColor( 128, 0, 0, 128 ); + m_pWinners->setFgColor( 255,255,255, 0 ); + m_pWinners->setContentAlignment( vgui::Label::a_center ); + + m_pWinningTeam = new Label( "Winners", 0, TEAMTWO_Y, getWide(), YRES(32) ); + m_pWinningTeam->setParent( this ); + m_pWinningTeam->setBgColor( 128, 0, 0, 128 ); + m_pWinningTeam->setFgColor( 255,255,255, 0 ); + m_pWinningTeam->setContentAlignment( vgui::Label::a_center ); + + setVisible(false); +} + +// Recalculate the Text in the window +void CDiscArena_RoundEnd::RecalculateText( void ) +{ + char sz[1024]; + char szTemp1[256]; + char szTemp2[256]; + + // Sends down a 0 for time when this should be removed + if (m_iSecondsToGo == 0) + { + setVisible(false); + g_iCannotFire = FALSE; + + // Force spectator menu to update + if (gViewPort) + gViewPort->m_iUser1 = 0; + return; + } + + g_iCannotFire = TRUE; + + // Round Number + strncpy( szTemp1, CHudTextMessage::BufferedLocaliseTextString( "#Round_Won" ), sizeof( szTemp1 ) - 1 ); + szTemp1[ sizeof( szTemp1 ) - 1 ] = '\0'; + sprintf( sz, szTemp1, m_iRoundNumber ); + + m_pRound->setText( sz ); + + if (gViewPort) + gViewPort->GetAllPlayersInfo(); + + // Winners + GetClientList( sz ); + m_pWinners->setText( sz ); + + // Scores + m_iNumPlayers = READ_BYTE(); + if ( m_iNumPlayers >= 0 && m_iNumPlayers <= MAX_PLAYERS ) + { + for (int i = 0; i < m_iNumPlayers; i++) + m_iClients[i] = READ_SHORT(); + + int iWinningScore = READ_BYTE(); + int iLosingScore = READ_BYTE(); + int iBattleOver = READ_BYTE(); + + // Battle over? + if ( iBattleOver ) + { + GetClientList( sz ); + + strncpy( szTemp2, CHudTextMessage::BufferedLocaliseTextString( "#Round_Won_Scores" ), sizeof( szTemp2 ) - 1 ); + szTemp2[ sizeof( szTemp2 ) - 1 ] = '\0'; + + _snprintf( sz, sizeof( sz ) - 1, szTemp2, sz, iWinningScore, iLosingScore ); + } + // Tied? + else if ( iWinningScore == iLosingScore ) + { + strncpy( szTemp2, CHudTextMessage::BufferedLocaliseTextString( "#Round_Tied" ), sizeof( szTemp2 ) - 1 ); + szTemp2[ sizeof( szTemp2 ) - 1 ] = '\0'; + + _snprintf( sz, sizeof( sz ) - 1, szTemp2, iWinningScore ); + } + else + { + char *pszTemp = NULL; + + GetClientList( sz ); + + if ( m_iNumPlayers == 1 ) + { + pszTemp = "#Round_Leads"; + } + else + { + pszTemp = "#Round_Lead"; + } + + strncpy( szTemp2, CHudTextMessage::BufferedLocaliseTextString( pszTemp ), sizeof( szTemp2 ) - 1 ); + szTemp2[ sizeof( szTemp2 ) - 1 ] = '\0'; + + _snprintf( sz, sizeof( sz ) - 1, szTemp2, sz, iWinningScore, iLosingScore ); + } + + sz[ sizeof( sz ) - 1 ] = '\0'; + m_pWinningTeam->setText( sz ); + } + + // Become visible + setVisible(true); + + // Hide the other windows if it's up + if (gViewPort) + { + gViewPort->m_pSpectatorMenu->setVisible( false ); + gViewPort->m_pDiscPowerupWindow->setVisible( false ); + gViewPort->m_pDiscStartRound->setVisible( false ); + } +} + +//=========================================================== +// Powerup name window +CDiscPowerups::CDiscPowerups() : CTransparentPanel( 255, POWERUP_X, POWERUP_Y, POWERUP_SIZE_X, POWERUP_SIZE_Y ) +{ + m_pLabel = new Label( "Powerups Go Here", 0, ROUND_Y, getWide(), YRES(32) ); + m_pLabel->setParent( this ); + m_pLabel->setBgColor( 0, 0, 0, 255 ); + m_pLabel->setFgColor( 255,255,255, 0 ); + m_pLabel->setContentAlignment( vgui::Label::a_center ); + setVisible(false); +}; + +void CDiscPowerups::RecalculateText( int iPowerup ) +{ + char sz[512]; + bool bAnd = false; + + // Don't appear if a round message is up + if (gViewPort) + { + if ( gViewPort->m_pDiscStartRound->isVisible() || gViewPort->m_pDiscEndRound->isVisible() ) + return; + } + + sprintf(sz, ""); + + if ( iPowerup & POW_TRIPLE ) + { + strcat(sz, CHudTextMessage::BufferedLocaliseTextString("#Triple") ); + bAnd = true; + } + + if ( iPowerup & POW_FAST ) + { + if (bAnd) + strcat(sz, ", "); + strcat(sz, CHudTextMessage::BufferedLocaliseTextString("#Fast") ); + bAnd = true; + } + + if ( iPowerup & POW_FREEZE ) + { + if (bAnd) + strcat(sz, ", "); + strcat(sz, CHudTextMessage::BufferedLocaliseTextString("#Freeze") ); + bAnd = true; + } + + if ( iPowerup & POW_HARD ) + { + if (bAnd) + strcat(sz, ", "); + strcat(sz, CHudTextMessage::BufferedLocaliseTextString("#Hard") ); + } + + m_pLabel->setText( sz ); + + // Become visible + if (sz && sz[0]) + setVisible(true); + else + setVisible(false); +} + +//=========================================================== +// Reward menu +CDiscRewards::CDiscRewards() : CTransparentPanel( 255, REWARD_X, REWARD_Y, REWARD_SIZE_X, REWARD_SIZE_Y ) +{ + m_pReward = new Label( "Well Done!", 0, ROUND_Y, getWide(), (REWARD_SIZE_Y / 2) ); + m_pReward->setParent( this ); + m_pReward->setBgColor( 0, 0, 0, 255 ); + m_pReward->setFgColor( 255,255,255, 0 ); + m_pReward->setContentAlignment( vgui::Label::a_center ); + setVisible(false); + + m_pTeleBonus = new Label( CHudTextMessage::BufferedLocaliseTextString( "#Hit_Tele" ), 0, (REWARD_SIZE_Y / 2), getWide(), (REWARD_SIZE_Y / 2) ); + m_pTeleBonus->setParent( this ); + m_pTeleBonus->setBgColor( 0, 0, 0, 255 ); + m_pTeleBonus->setFgColor( 255,255,255, 0 ); + m_pTeleBonus->setContentAlignment( vgui::Label::a_center ); +}; + +void CDiscRewards::RecalculateText( int iReward ) +{ + char sz[512]; + + // Don't appear if a round message is up + if (gViewPort) + { + if ( gViewPort->m_pDiscStartRound->isVisible() || gViewPort->m_pDiscEndRound->isVisible() ) + return; + } + + if ( !iReward ) + { + setVisible( false ); + return; + } + + // Rewards + if ( iReward & REWARD_BOUNCE_NONE ) + sprintf( sz, CHudTextMessage::BufferedLocaliseTextString( "#Hit_Direct" ) ); + if ( iReward & REWARD_BOUNCE_ONE ) + sprintf( sz, CHudTextMessage::BufferedLocaliseTextString( "#Hit_One" ) ); + if ( iReward & REWARD_BOUNCE_TWO ) + sprintf( sz, CHudTextMessage::BufferedLocaliseTextString( "#Hit_Two" ) ); + if ( iReward & REWARD_BOUNCE_THREE ) + sprintf( sz, CHudTextMessage::BufferedLocaliseTextString( "#Hit_Three" ) ); + if ( iReward & REWARD_DECAPITATE ) + sprintf( sz, CHudTextMessage::BufferedLocaliseTextString( "#Hit_Decap" ) ); + if ( iReward & REWARD_DOUBLEKILL ) + sprintf( sz, CHudTextMessage::BufferedLocaliseTextString( "#Hit_Multiple" ) ); + + if ( iReward & REWARD_TELEPORT ) + m_pTeleBonus->setVisible( true ); + else + m_pTeleBonus->setVisible( false ); + + m_pReward->setText( sz ); + setVisible( true ); +} + +void CDiscRewards::SetMessage( char *pMessage ) +{ + if (!pMessage) + { + setVisible(false); + return; + } + + m_pTeleBonus->setVisible( false ); + m_pReward->setText( pMessage ); + setVisible( true ); + + if (gViewPort) + gViewPort->m_flRewardOpenTime = gHUD.m_flTime + 5.0; +} \ No newline at end of file diff --git a/ricochet/cl_dll/vgui_discobjects.h b/ricochet/cl_dll/vgui_discobjects.h new file mode 100644 index 0000000..0290a59 --- /dev/null +++ b/ricochet/cl_dll/vgui_discobjects.h @@ -0,0 +1,109 @@ +//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: VGUI objects for Discwar +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_DISCOBJECTS_H +#define VGUI_DISCOBJECTS_H +#pragma once + +//=========================================================== +// Disc ammo icon +class CDiscPanel : public Label +{ +private: + BitmapTGA *m_pDiscTGA_Red; + BitmapTGA *m_pDiscTGA_RedGlow; + BitmapTGA *m_pDiscTGA_Blue; + BitmapTGA *m_pDiscTGA_BlueGlow; + BitmapTGA *m_pDiscTGA_Grey; + BitmapTGA *m_pDiscTGA_Fast; + BitmapTGA *m_pDiscTGA_Freeze; + BitmapTGA *m_pDiscTGA_Hard; + BitmapTGA *m_pDiscTGA_Triple; +public: + CDiscPanel(int x,int y,int wide,int tall); + void Update( int iDiscNo, bool bGlow, int iPowerup ); + + virtual void paintBackground() + { + // Do nothing, so the background's left transparent. + } +}; + +//=========================================================== +// Powerup +class CDiscPowerups : public CTransparentPanel +{ +public: + CDiscPowerups(); + + void RecalculateText( int iPowerup ); + Label *m_pLabel; +}; + +class CDiscRewards : public CTransparentPanel +{ +public: + CDiscRewards(); + + void RecalculateText( int iReward ); + void SetMessage( char *pMessage ); + Label *m_pReward; + Label *m_pTeleBonus; +}; + +//=========================================================== +// Arena windows +class CDiscArenaPanel : public CTransparentPanel +{ +public: + CDiscArenaPanel( int x, int y, int wide, int tall ); + int MsgFunc_GetPlayers(const char *pszName, int iSize, void *pbuf ); + virtual void RecalculateText( void ) {}; + void GetClientList( char *pszString ); + + int m_iNumPlayers; + int m_iClients[ MAX_PLAYERS ]; + int m_iRoundNumber; + int m_iSecondsToGo; +}; + +class CDiscArena_RoundStart : public CDiscArenaPanel +{ +public: + CDiscArena_RoundStart(); + + void RecalculateText( void ); + + Label *m_pRound; + Label *m_pTeamOne; + Label *m_pTeamTwo; +}; + +class CDiscArena_RoundEnd : public CDiscArenaPanel +{ +public: + CDiscArena_RoundEnd(); + + void RecalculateText( void ); + + Label *m_pRound; + Label *m_pWinners; + Label *m_pWinningTeam; +}; + +#endif // VGUI_DISCOBJECTS_H diff --git a/ricochet/cl_dll/vgui_int.cpp b/ricochet/cl_dll/vgui_int.cpp new file mode 100644 index 0000000..cb30561 --- /dev/null +++ b/ricochet/cl_dll/vgui_int.cpp @@ -0,0 +1,122 @@ + +#include"vgui_int.h" +#include +#include +#include +#include +#include +#include +#include +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ControlConfigPanel.h" + +namespace +{ + +class TexturePanel : public Panel , public ActionSignal +{ +private: + int _bindIndex; + TextEntry* _textEntry; +public: + TexturePanel() : Panel(0,0,256,276) + { + _bindIndex=2700; + _textEntry=new TextEntry("2700",0,0,128,20); + _textEntry->setParent(this); + _textEntry->addActionSignal(this); + } +public: + virtual bool isWithin(int x,int y) + { + return _textEntry->isWithin(x,y); + } +public: + virtual void actionPerformed(Panel* panel) + { + char buf[256]; + _textEntry->getText(0,buf,256); + sscanf(buf,"%d",&_bindIndex); + } +protected: + virtual void paintBackground() + { + Panel::paintBackground(); + + int wide,tall; + getPaintSize(wide,tall); + + drawSetColor(0,0,255,0); + drawSetTexture(_bindIndex); + drawTexturedRect(0,19,257,257); + } + +}; + +} + +using namespace vgui; + +void VGui_ViewportPaintBackground(int extents[4]) +{ + gEngfuncs.VGui_ViewportPaintBackground(extents); +} + +void* VGui_GetPanel() +{ + return (Panel*)gEngfuncs.VGui_GetPanel(); +} + +void VGui_Startup() +{ + Panel* root=(Panel*)VGui_GetPanel(); + root->setBgColor(128,128,0,0); + //root->setNonPainted(false); + //root->setBorder(new LineBorder()); + root->setLayout(new BorderLayout(0)); + + + //root->getSurfaceBase()->setEmulatedCursorVisible(true); + + if (gViewPort != NULL) + { +// root->removeChild(gViewPort); + + // free the memory +// delete gViewPort; +// gViewPort = NULL; + + gViewPort->Initialize(); + } + else + { + gViewPort = new TeamFortressViewport(0,0,root->getWide(),root->getTall()); + gViewPort->setParent(root); + } + + /* + TexturePanel* texturePanel=new TexturePanel(); + texturePanel->setParent(gViewPort); + */ + +} + +void VGui_Shutdown() +{ + delete gViewPort; + gViewPort = NULL; +} + + + + + diff --git a/ricochet/cl_dll/vgui_int.h b/ricochet/cl_dll/vgui_int.h new file mode 100644 index 0000000..a90f80c --- /dev/null +++ b/ricochet/cl_dll/vgui_int.h @@ -0,0 +1,15 @@ + +#ifndef VGUI_INT_H +#define VGUI_INT_H + +extern "C" +{ +void VGui_Startup(); +void VGui_Shutdown(); + +//Only safe to call from inside subclass of Panel::paintBackground +void VGui_ViewportPaintBackground(int extents[4]); +} + + +#endif \ No newline at end of file diff --git a/ricochet/cl_dll/view.cpp b/ricochet/cl_dll/view.cpp new file mode 100644 index 0000000..5b4db0a --- /dev/null +++ b/ricochet/cl_dll/view.cpp @@ -0,0 +1,1061 @@ +// view/refresh setup functions + +#include "hud.h" +#include "cl_util.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" + +#include "entity_state.h" +#include "cl_entity.h" +#include "ref_params.h" +#include "in_defs.h" // PITCH YAW ROLL +#include "pm_movevars.h" +#include "pm_shared.h" +#include "pmtrace.h" +#include "screenfade.h" +#include "shake.h" + +// Spectator Mode +extern "C" +{ + float vecNewViewAngles[3]; + int iHasNewViewAngles; + float vecNewViewOrigin[3]; + int iHasNewViewOrigin; + int iIsSpectator; +} + +extern float g_flStartScaleTime; +extern int iMouseInUse; +void CAM_ToThirdPerson(void); +void CAM_ToFirstPerson(void); + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +extern "C" +{ + int CL_IsThirdPerson( void ); + void CL_CameraOffset( float *ofs ); + + void EXPORT V_CalcRefdef( struct ref_params_s *pparams ); + + void PM_ParticleLine( float *start, float *end, int pcolor, float life, float vert); + int PM_GetInfo( int ent ); + +} + +void V_DropPunchAngle ( float frametime, float *ev_punchangle ); +void VectorAngles( const float *forward, float *angles ); + +/* +The view is allowed to move slightly from it's true position for bobbing, +but if it exceeds 8 pixels linear distance (spherical, not box), the list of +entities sent from the server may not include everything in the pvs, especially +when crossing a water boudnary. +*/ + +extern cvar_t *cl_forwardspeed; +extern cvar_t *chase_active; +extern cvar_t *scr_ofsx, *scr_ofsy, *scr_ofsz; +extern cvar_t *cl_vsmoothing; + +vec3_t v_origin, v_angles; + +vec3_t ev_punchangle; + +cvar_t *scr_ofsx; +cvar_t *scr_ofsy; +cvar_t *scr_ofsz; + +cvar_t *v_centermove; +cvar_t *v_centerspeed; + +cvar_t *cl_bobcycle; +cvar_t *cl_bob; +cvar_t *cl_bobup; +cvar_t *cl_waterdist; + +// These cvars are not registered (so users can't cheat), so set the ->value field directly +// Register these cvars in V_Init() if needed for easy tweaking +cvar_t v_iyaw_cycle = {"v_iyaw_cycle", "2", 0, 2}; +cvar_t v_iroll_cycle = {"v_iroll_cycle", "0.5", 0, 0.5}; +cvar_t v_ipitch_cycle = {"v_ipitch_cycle", "1", 0, 1}; +cvar_t v_iyaw_level = {"v_iyaw_level", "0.3", 0, 0.3}; +cvar_t v_iroll_level = {"v_iroll_level", "0.1", 0, 0.1}; +cvar_t v_ipitch_level = {"v_ipitch_level", "0.3", 0, 0.3}; + +float v_idlescale; // used by TFC for concussion grenade effect + +//============================================================================= +void V_NormalizeAngles( float *angles ) +{ + int i; + // Normalize angles + for ( i = 0; i < 3; i++ ) + { + if ( angles[i] > 180.0 ) + { + angles[i] -= 360.0; + } + else if ( angles[i] < -180.0 ) + { + angles[i] += 360.0; + } + } +} + +/* +=================== +V_InterpolateAngles + +Interpolate Euler angles. +FIXME: Use Quaternions to avoid discontinuities +Frac is 0.0 to 1.0 ( i.e., should probably be clamped, but doesn't have to be ) +=================== +*/ +void V_InterpolateAngles( float *start, float *end, float *output, float frac ) +{ + int i; + float ang1, ang2; + float d; + + V_NormalizeAngles( start ); + V_NormalizeAngles( end ); + + for ( i = 0 ; i < 3 ; i++ ) + { + ang1 = start[i]; + ang2 = end[i]; + + d = ang2 - ang1; + if ( d > 180 ) + { + d -= 360; + } + else if ( d < -180 ) + { + d += 360; + } + + output[i] = ang1 + d * frac; + } + + V_NormalizeAngles( output ); +} + +// Quakeworld bob code, this fixes jitters in the mutliplayer since the clock (pparams->time) isn't quite linear +float V_CalcBob ( struct ref_params_s *pparams ) +{ + static double bobtime; + static float bob; + float cycle; + static float lasttime; + vec3_t vel; + + if ( pparams->spectator || iIsSpectator ) + return 0; + + if ( pparams->onground == -1 || + pparams->time == lasttime ) + { + // just use old value + return bob; + } + + lasttime = pparams->time; + + bobtime += pparams->frametime; + cycle = bobtime - (int)( bobtime / cl_bobcycle->value ) * cl_bobcycle->value; + cycle /= cl_bobcycle->value; + + if ( cycle < cl_bobup->value ) + { + cycle = M_PI * cycle / cl_bobup->value; + } + else + { + cycle = M_PI + M_PI * ( cycle - cl_bobup->value )/( 1.0 - cl_bobup->value ); + } + + // bob is proportional to simulated velocity in the xy plane + // (don't count Z, or jumping messes it up) + VectorCopy( pparams->simvel, vel ); + vel[2] = 0; + + bob = sqrt( vel[0] * vel[0] + vel[1] * vel[1] ) * cl_bob->value; + bob = bob * 0.3 + bob * 0.7 * sin(cycle); + bob = min( bob, 4 ); + bob = max( bob, -7 ); + return bob; + +} + +/* +=============== +V_CalcRoll +Used by view and sv_user +=============== +*/ +float V_CalcRoll (vec3_t angles, vec3_t velocity, float rollangle, float rollspeed ) +{ + float sign; + float side; + float value; + vec3_t forward, right, up; + + AngleVectors ( angles, forward, right, up ); + + side = DotProduct (velocity, right); + sign = side < 0 ? -1 : 1; + side = fabs( side ); + + value = rollangle; + if (side < rollspeed) + { + side = side * value / rollspeed; + } + else + { + side = value; + } + return side * sign; +} + +typedef struct pitchdrift_s +{ + float pitchvel; + int nodrift; + float driftmove; + double laststop; +} pitchdrift_t; + +static pitchdrift_t pd; + +void V_StartPitchDrift( void ) +{ + if ( pd.laststop == gEngfuncs.GetClientTime() ) + { + return; // something else is keeping it from drifting + } + + if ( pd.nodrift || !pd.pitchvel ) + { + pd.pitchvel = v_centerspeed->value; + pd.nodrift = 0; + pd.driftmove = 0; + } +} + +void V_StopPitchDrift ( void ) +{ + pd.laststop = gEngfuncs.GetClientTime(); + pd.nodrift = 1; + pd.pitchvel = 0; +} + +/* +=============== +V_DriftPitch + +Moves the client pitch angle towards idealpitch sent by the server. + +If the user is adjusting pitch manually, either with lookup/lookdown, +mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped. +=============== +*/ +void V_DriftPitch ( struct ref_params_s *pparams ) +{ + float delta, move; + + if ( gEngfuncs.IsNoClipping() || !pparams->onground || pparams->demoplayback || pparams->spectator ) + { + pd.driftmove = 0; + pd.pitchvel = 0; + return; + } + + // don't count small mouse motion + if (pd.nodrift) + { + if ( fabs( pparams->cmd->forwardmove ) < cl_forwardspeed->value ) + pd.driftmove = 0; + else + pd.driftmove += pparams->frametime; + + if ( pd.driftmove > v_centermove->value) + { + V_StartPitchDrift (); + } + return; + } + + delta = pparams->idealpitch - pparams->cl_viewangles[PITCH]; + + if (!delta) + { + pd.pitchvel = 0; + return; + } + + move = pparams->frametime * pd.pitchvel; + pd.pitchvel += pparams->frametime * v_centerspeed->value; + +//Con_Printf ("move: %f (%f)\n", move, pparams->frametime); + + if (delta > 0) + { + if (move > delta) + { + pd.pitchvel = 0; + move = delta; + } + pparams->cl_viewangles[PITCH] += move; + } + else if (delta < 0) + { + if (move > -delta) + { + pd.pitchvel = 0; + move = -delta; + } + pparams->cl_viewangles[PITCH] -= move; + } +} + +/* +============================================================================== + VIEW RENDERING +============================================================================== +*/ + +/* +================== +V_CalcGunAngle +================== +*/ +void V_CalcGunAngle ( struct ref_params_s *pparams ) +{ + cl_entity_t *viewent; + + viewent = gEngfuncs.GetViewModel(); + if ( !viewent ) + return; + + viewent->angles[YAW] = pparams->viewangles[YAW] + pparams->crosshairangle[YAW]; + viewent->angles[PITCH] = -pparams->viewangles[PITCH] + pparams->crosshairangle[PITCH] * 0.25; + viewent->angles[ROLL] -= v_idlescale * sin(pparams->time*v_iroll_cycle.value) * v_iroll_level.value; + + // don't apply all of the v_ipitch to prevent normally unseen parts of viewmodel from coming into view. + viewent->angles[PITCH] -= v_idlescale * sin(pparams->time*v_ipitch_cycle.value) * (v_ipitch_level.value * 0.5); + viewent->angles[YAW] -= v_idlescale * sin(pparams->time*v_iyaw_cycle.value) * v_iyaw_level.value; + + VectorCopy( viewent->angles, viewent->curstate.angles ); + VectorCopy( viewent->angles, viewent->latched.prevangles ); +} + +/* +============== +V_AddIdle + +Idle swaying +============== +*/ +void V_AddIdle ( struct ref_params_s *pparams ) +{ + pparams->viewangles[ROLL] += v_idlescale * sin(pparams->time*v_iroll_cycle.value) * v_iroll_level.value; + pparams->viewangles[PITCH] += v_idlescale * sin(pparams->time*v_ipitch_cycle.value) * v_ipitch_level.value; + pparams->viewangles[YAW] += v_idlescale * sin(pparams->time*v_iyaw_cycle.value) * v_iyaw_level.value; +} + + +/* +============== +V_CalcViewRoll + +Roll is induced by movement and damage +============== +*/ +void V_CalcViewRoll ( struct ref_params_s *pparams ) +{ + float side; + cl_entity_t *viewentity; + + viewentity = gEngfuncs.GetEntityByIndex( pparams->viewentity ); + if ( !viewentity ) + return; + + side = V_CalcRoll ( viewentity->angles, pparams->simvel, pparams->movevars->rollangle, pparams->movevars->rollspeed ); + + pparams->viewangles[ROLL] += side; + + if ( pparams->health <= 0 && ( pparams->viewheight[2] != 0 ) ) + { + // only roll the view if the player is dead and the viewheight[2] is nonzero + // this is so deadcam in multiplayer will work. + pparams->viewangles[ROLL] = 80; // dead view angle + return; + } +} + + +/* +================== +V_CalcIntermissionRefdef + +================== +*/ +void V_CalcIntermissionRefdef ( struct ref_params_s *pparams ) +{ + cl_entity_t *ent, *view; + float old; + +// don't allow cheats in multiplayer +#if !defined( _DEBUG ) + if ( pparams->maxclients > 1 ) + { + gEngfuncs.Cvar_SetValue ("scr_ofsx", 0); + gEngfuncs.Cvar_SetValue ("scr_ofsy", 0); + gEngfuncs.Cvar_SetValue ("scr_ofsz", 0); + } +#endif + + // ent is the player model ( visible when out of body ) + ent = gEngfuncs.GetLocalPlayer(); + + // view is the weapon model (only visible from inside body ) + view = gEngfuncs.GetViewModel(); + + VectorCopy ( pparams->simorg, pparams->vieworg ); + VectorCopy ( pparams->cl_viewangles, pparams->viewangles ); + + view->model = NULL; + + // allways idle in intermission + old = v_idlescale; + v_idlescale = 1; + + V_AddIdle ( pparams ); + + v_idlescale = old; + + v_origin = pparams->vieworg; + v_angles = pparams->viewangles; +} + +#define ORIGIN_BACKUP 64 +#define ORIGIN_MASK ( ORIGIN_BACKUP - 1 ) + +typedef struct +{ + float Origins[ ORIGIN_BACKUP ][3]; + float OriginTime[ ORIGIN_BACKUP ]; + + float Angles[ ORIGIN_BACKUP ][3]; + float AngleTime[ ORIGIN_BACKUP ]; + + int CurrentOrigin; + int CurrentAngle; +} viewinterp_t; + +/* +================== +V_CalcRefdef + +================== +*/ +void V_CalcNormalRefdef ( struct ref_params_s *pparams ) +{ + cl_entity_t *ent, *view; + int i; + vec3_t angles; + float bob, waterOffset; + static viewinterp_t ViewInterp; + + static float oldz = 0; + static float lasttime; + + static float lastang[3]; + vec3_t angdelta; + + vec3_t camAngles, camForward, camRight, camUp; + cl_entity_t *pwater; + + // don't allow cheats in multiplayer + if ( pparams->maxclients > 1 ) + { + scr_ofsx->value = 0.0; + scr_ofsy->value = 0.0; + scr_ofsz->value = 0.0; + } + + + V_DriftPitch ( pparams ); + + // ent is the player model ( visible when out of body ) + ent = gEngfuncs.GetLocalPlayer(); + + // view is the weapon model (only visible from inside body ) + view = gEngfuncs.GetViewModel(); + + // transform the view offset by the model's matrix to get the offset from + // model origin for the view + bob = V_CalcBob ( pparams ); + + // Observer angle capturing and smoothing + if ( iHasNewViewOrigin ) + { + // Get the angles from the physics code + VectorCopy( vecNewViewOrigin, pparams->vieworg ); + VectorCopy( vecNewViewOrigin, pparams->simorg ); + } + + // refresh position + VectorCopy ( pparams->simorg, pparams->vieworg ); + pparams->vieworg[2] += ( bob ); + VectorAdd( pparams->vieworg, pparams->viewheight, pparams->vieworg ); + + // Observer angle capturing and smoothing + if ( iHasNewViewAngles ) + { + // Get the angles from the physics code + VectorCopy( vecNewViewAngles, pparams->cl_viewangles ); + } + else if ( pparams->health == -5 ) + { + CAM_ToThirdPerson(); + + // Lock mouse movement + iMouseInUse=1; + + pparams->cl_viewangles[0] = 89; + + // Spin the view + float flTimeDelta = (pparams->time - g_flStartScaleTime); + if ( flTimeDelta > 0 ) + { + float flROFSpin = 1.0 + (flTimeDelta * 2.0); + float flSpin = flTimeDelta * 45; + + pparams->cl_viewangles[1] = flSpin * flROFSpin; + } + } + else + { + CAM_ToFirstPerson(); + + // Unlock mouse movement + iMouseInUse=0; + } + + VectorSubtract( pparams->cl_viewangles, lastang, angdelta ); + if ( Length( angdelta ) != 0.0 ) + { + VectorCopy( pparams->cl_viewangles, ViewInterp.Angles[ ViewInterp.CurrentAngle & ORIGIN_MASK ] ); + ViewInterp.AngleTime[ ViewInterp.CurrentAngle & ORIGIN_MASK ] = pparams->time; + ViewInterp.CurrentAngle++; + + VectorCopy( pparams->cl_viewangles, lastang ); + } + + if ( cl_vsmoothing && cl_vsmoothing->value && ( iIsSpectator & SPEC_SMOOTH_ANGLES ) ) + { + int foundidx; + int i; + float t; + + if ( cl_vsmoothing->value < 0.0 ) + { + gEngfuncs.Cvar_SetValue( "cl_vsmoothing", 0.0 ); + } + + t = pparams->time - cl_vsmoothing->value; + + for ( i = 1; i < ORIGIN_MASK; i++ ) + { + foundidx = ViewInterp.CurrentAngle - 1 - i; + if ( ViewInterp.AngleTime[ foundidx & ORIGIN_MASK ] <= t ) + break; + } + + if ( i < ORIGIN_MASK && ViewInterp.AngleTime[ foundidx & ORIGIN_MASK ] != 0.0 ) + { + // Interpolate + double dt; + + dt = ViewInterp.AngleTime[ (foundidx + 1) & ORIGIN_MASK ] - ViewInterp.AngleTime[ foundidx & ORIGIN_MASK ]; + if ( dt > 0.0 ) + { + double frac; + + frac = ( t - ViewInterp.AngleTime[ foundidx & ORIGIN_MASK] ) / dt; + frac = min( 1.0, frac ); + + // interpolate angles + V_InterpolateAngles( ViewInterp.Angles[ foundidx & ORIGIN_MASK ], ViewInterp.Angles[ (foundidx + 1) & ORIGIN_MASK ], pparams->cl_viewangles, frac ); + + VectorCopy( pparams->cl_viewangles, vecNewViewAngles ); + } + } + } + + VectorCopy ( pparams->cl_viewangles, pparams->viewangles ); + + gEngfuncs.V_CalcShake(); + gEngfuncs.V_ApplyShake( pparams->vieworg, pparams->viewangles, 1.0 ); + + // never let view origin sit exactly on a node line, because a water plane can + // dissapear when viewed with the eye exactly on it. + // FIXME, we send origin at 1/128 now, change this? + // the server protocol only specifies to 1/16 pixel, so add 1/32 in each axis + + pparams->vieworg[0] += 1.0/32; + pparams->vieworg[1] += 1.0/32; + pparams->vieworg[2] += 1.0/32; + + // Check for problems around water, move the viewer artificially if necessary + // -- this prevents drawing errors in GL due to waves + + waterOffset = 0; + if ( pparams->waterlevel >= 2 ) + { + int i, contents, waterDist, waterEntity; + vec3_t point; + waterDist = cl_waterdist->value; + + if ( pparams->hardware ) + { + waterEntity = gEngfuncs.PM_WaterEntity( pparams->simorg ); + if ( waterEntity >= 0 && waterEntity < pparams->max_entities ) + { + pwater = gEngfuncs.GetEntityByIndex( waterEntity ); + if ( pwater && ( pwater->model != NULL ) ) + { + waterDist += ( pwater->curstate.scale * 16 ); // Add in wave height + } + } + } + else + { + waterEntity = 0; // Don't need this in software + } + + VectorCopy( pparams->vieworg, point ); + + // Eyes are above water, make sure we're above the waves + if ( pparams->waterlevel == 2 ) + { + point[2] -= waterDist; + for ( i = 0; i < waterDist; i++ ) + { + contents = gEngfuncs.PM_PointContents( point, NULL ); + if ( contents > CONTENTS_WATER ) + break; + point[2] += 1; + } + waterOffset = (point[2] + waterDist) - pparams->vieworg[2]; + } + else + { + // eyes are under water. Make sure we're far enough under + point[2] += waterDist; + + for ( i = 0; i < waterDist; i++ ) + { + contents = gEngfuncs.PM_PointContents( point, NULL ); + if ( contents <= CONTENTS_WATER ) + break; + point[2] -= 1; + } + waterOffset = (point[2] - waterDist) - pparams->vieworg[2]; + } + } + + pparams->vieworg[2] += waterOffset; + + V_CalcViewRoll ( pparams ); + + V_AddIdle ( pparams ); + + // offsets + VectorCopy( pparams->cl_viewangles, angles ); + + AngleVectors ( angles, pparams->forward, pparams->right, pparams->up ); + + for ( i=0 ; i<3 ; i++ ) + { + pparams->vieworg[i] += scr_ofsx->value*pparams->forward[i] + scr_ofsy->value*pparams->right[i] + scr_ofsz->value*pparams->up[i]; + } + + // Treating cam_ofs[2] as the distance + if( CL_IsThirdPerson() ) + { + vec3_t ofs; + ofs[0] = ofs[1] = ofs[2] = 0.0; + CL_CameraOffset( (float *)&ofs ); + + VectorCopy( ofs, camAngles ); + camAngles[ ROLL ] = 0; + AngleVectors( camAngles, camForward, camRight, camUp ); + + // Is the player in a falling anim? If so, raise camera above and look down + if ( pparams->health == -5 ) + { + pparams->vieworg[2] += 92; + } + else + { + for ( i = 0; i < 3; i++ ) + { + pparams->vieworg[ i ] += -ofs[2] * camForward[ i ]; + } + + pparams->vieworg[2] += 20; + } + } + + // Give gun our viewangles + VectorCopy ( pparams->cl_viewangles, view->angles ); + + // set up gun position + V_CalcGunAngle ( pparams ); + + // Use predicted origin as view origin. + VectorCopy ( pparams->simorg, view->origin ); + view->origin[2] += ( waterOffset ); + VectorAdd( view->origin, pparams->viewheight, view->origin ); + + // Let the viewmodel shake at about 10% of the amplitude + gEngfuncs.V_ApplyShake( view->origin, view->angles, 0.9 ); + + for ( i = 0; i < 3; i++ ) + { + view->origin[ i ] += bob * 0.4 * pparams->forward[ i ]; + } + view->origin[2] += bob; + + // throw in a little tilt. + view->angles[YAW] -= bob * 0.5; + view->angles[ROLL] -= bob * 1; + view->angles[PITCH] -= bob * 0.3; + + // pushing the view origin down off of the same X/Z plane as the ent's origin will give the + // gun a very nice 'shifting' effect when the player looks up/down. If there is a problem + // with view model distortion, this may be a cause. (SJB). + view->origin[2] -= 1; + + // fudge position around to keep amount of weapon visible + // roughly equal with different FOV + if (pparams->viewsize == 110) + { + view->origin[2] += 1; + } + else if (pparams->viewsize == 100) + { + view->origin[2] += 2; + } + else if (pparams->viewsize == 90) + { + view->origin[2] += 1; + } + else if (pparams->viewsize == 80) + { + view->origin[2] += 0.5; + } + + // Add in the punchangle, if any + VectorAdd ( pparams->viewangles, pparams->punchangle, pparams->viewangles ); + + // Include client side punch, too + VectorAdd ( pparams->viewangles, (float *)&ev_punchangle, pparams->viewangles); + + V_DropPunchAngle ( pparams->frametime, (float *)&ev_punchangle ); + + // smooth out stair step ups +#if 1 + if ( !pparams->smoothing && pparams->onground && pparams->simorg[2] - oldz > 0) + { + float steptime; + + steptime = pparams->time - lasttime; + if (steptime < 0) + //FIXME I_Error ("steptime < 0"); + steptime = 0; + + oldz += steptime * 150; + if (oldz > pparams->simorg[2]) + oldz = pparams->simorg[2]; + if (pparams->simorg[2] - oldz > 18) + oldz = pparams->simorg[2]- 18; + pparams->vieworg[2] += oldz - pparams->simorg[2]; + view->origin[2] += oldz - pparams->simorg[2]; + } + else + { + oldz = pparams->simorg[2]; + } +#endif + + { + static float lastorg[3]; + vec3_t delta; + + VectorSubtract( pparams->simorg, lastorg, delta ); + + if ( Length( delta ) != 0.0 ) + { + VectorCopy( pparams->simorg, ViewInterp.Origins[ ViewInterp.CurrentOrigin & ORIGIN_MASK ] ); + ViewInterp.OriginTime[ ViewInterp.CurrentOrigin & ORIGIN_MASK ] = pparams->time; + ViewInterp.CurrentOrigin++; + + VectorCopy( pparams->simorg, lastorg ); + } + } + + // Smooth out whole view in multiplayer when on trains, lifts + if ( cl_vsmoothing && cl_vsmoothing->value && + ( ( iIsSpectator & SPEC_SMOOTH_ORIGIN ) || (pparams->smoothing && ( pparams->maxclients > 1 ) ) ) ) + { + int foundidx; + int i; + float t; + + if ( cl_vsmoothing->value < 0.0 ) + { + gEngfuncs.Cvar_SetValue( "cl_vsmoothing", 0.0 ); + } + + t = pparams->time - cl_vsmoothing->value; + + for ( i = 1; i < ORIGIN_MASK; i++ ) + { + foundidx = ViewInterp.CurrentOrigin - 1 - i; + if ( ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ] <= t ) + break; + } + + if ( i < ORIGIN_MASK && ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ] != 0.0 ) + { + // Interpolate + vec3_t delta; + double frac; + double dt; + vec3_t neworg; + + dt = ViewInterp.OriginTime[ (foundidx + 1) & ORIGIN_MASK ] - ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ]; + if ( dt > 0.0 ) + { + frac = ( t - ViewInterp.OriginTime[ foundidx & ORIGIN_MASK] ) / dt; + frac = min( 1.0, frac ); + VectorSubtract( ViewInterp.Origins[ ( foundidx + 1 ) & ORIGIN_MASK ], ViewInterp.Origins[ foundidx & ORIGIN_MASK ], delta ); + VectorMA( ViewInterp.Origins[ foundidx & ORIGIN_MASK ], frac, delta, neworg ); + + // Dont interpolate large changes + if ( Length( delta ) < 64 ) + { + VectorSubtract( neworg, pparams->simorg, delta ); + + VectorAdd( pparams->simorg, delta, pparams->simorg ); + VectorAdd( pparams->vieworg, delta, pparams->vieworg ); + VectorAdd( view->origin, delta, view->origin ); + + VectorCopy( pparams->simorg, vecNewViewOrigin ); + } + } + } + } + + // Store off v_angles before munging for third person + v_angles = pparams->viewangles; + + if ( CL_IsThirdPerson() ) + { + VectorCopy( camAngles, pparams->viewangles); + } + + // override all previous settings if the viewent isn't the client + if ( pparams->viewentity > pparams->maxclients ) + { + cl_entity_t *viewentity; + viewentity = gEngfuncs.GetEntityByIndex( pparams->viewentity ); + if ( viewentity ) + { + VectorCopy( viewentity->origin, pparams->vieworg ); + VectorCopy( viewentity->angles, pparams->viewangles ); + + // Store off overridden viewangles + v_angles = pparams->viewangles; + } + } + + lasttime = pparams->time; + + v_origin = pparams->vieworg; +} + +void EXPORT V_CalcRefdef( struct ref_params_s *pparams ) +{ + // intermission / finale rendering + if ( pparams->intermission ) + { + V_CalcIntermissionRefdef ( pparams ); + } + else if ( !pparams->paused ) + { + V_CalcNormalRefdef ( pparams ); + } + +/* +// Example of how to overlay the whole screen with red at 50 % alpha +#define SF_TEST +#if defined SF_TEST + { + screenfade_t sf; + gEngfuncs.pfnGetScreenFade( &sf ); + + sf.fader = 255; + sf.fadeg = 0; + sf.fadeb = 0; + sf.fadealpha = 128; + sf.fadeFlags = FFADE_STAYOUT | FFADE_OUT; + + gEngfuncs.pfnSetScreenFade( &sf ); + } +#endif +*/ +} + +/* +============= +V_DropPunchAngle + +============= +*/ +void V_DropPunchAngle ( float frametime, float *ev_punchangle ) +{ + float len; + + len = VectorNormalize ( ev_punchangle ); + len -= (10.0 + len * 0.5) * frametime; + len = max( len, 0.0 ); + VectorScale ( ev_punchangle, len, ev_punchangle ); +} + +/* +============= +V_PunchAxis + +Client side punch effect +============= +*/ +void V_PunchAxis( int axis, float punch ) +{ + ev_punchangle[ axis ] = punch; +} + +/* +============= +V_Init +============= +*/ +void V_Init (void) +{ + gEngfuncs.pfnAddCommand ("centerview", V_StartPitchDrift ); + + scr_ofsx = gEngfuncs.pfnRegisterVariable( "scr_ofsx","0", 0 ); + scr_ofsy = gEngfuncs.pfnRegisterVariable( "scr_ofsy","0", 0 ); + scr_ofsz = gEngfuncs.pfnRegisterVariable( "scr_ofsz","0", 0 ); + + v_centermove = gEngfuncs.pfnRegisterVariable( "v_centermove", "0.15", 0 ); + v_centerspeed = gEngfuncs.pfnRegisterVariable( "v_centerspeed","500", 0 ); + + cl_bobcycle = gEngfuncs.pfnRegisterVariable( "cl_bobcycle","0.8", 0 );// best default for my experimental gun wag (sjb) + cl_bob = gEngfuncs.pfnRegisterVariable( "cl_bob","0.01", 0 );// best default for my experimental gun wag (sjb) + cl_bobup = gEngfuncs.pfnRegisterVariable( "cl_bobup","0.5", 0 ); + cl_waterdist = gEngfuncs.pfnRegisterVariable( "cl_waterdist","4", 0 ); +} + + +//#define TRACE_TEST +#if defined( TRACE_TEST ) + +extern float in_fov; +/* +==================== +CalcFov +==================== +*/ +float CalcFov (float fov_x, float width, float height) +{ + float a; + float x; + + if (fov_x < 1 || fov_x > 179) + fov_x = 90; // error, set to 90 + + x = width/tan(fov_x/360*M_PI); + + a = atan (height/x); + + a = a*360/M_PI; + + return a; +} + +int hitent = -1; + +void V_Move( int mx, int my ) +{ + float fov; + float fx, fy; + float dx, dy; + float c_x, c_y; + float dX, dY; + vec3_t forward, up, right; + vec3_t newangles; + + vec3_t farpoint; + pmtrace_t tr; + + fov = CalcFov( in_fov, (float)ScreenWidth, (float)ScreenHeight ); + + c_x = (float)ScreenWidth / 2.0; + c_y = (float)ScreenHeight / 2.0; + + dx = (float)mx - c_x; + dy = (float)my - c_y; + + // Proportion we moved in each direction + fx = dx / c_x; + fy = dy / c_y; + + dX = fx * in_fov / 2.0 ; + dY = fy * fov / 2.0; + + newangles = v_angles; + + newangles[ YAW ] -= dX; + newangles[ PITCH ] += dY; + + // Now rotate v_forward around that point + AngleVectors ( newangles, forward, right, up ); + + farpoint = v_origin + 8192 * forward; + + // Trace + tr = *(gEngfuncs.PM_TraceLine( (float *)&v_origin, (float *)&farpoint, PM_TRACELINE_PHYSENTSONLY, 2 /*point sized hull*/, -1 )); + + if ( tr.fraction != 1.0 && tr.ent != 0 ) + { + hitent = PM_GetInfo( tr.ent ); + PM_ParticleLine( (float *)&v_origin, (float *)&tr.endpos, 5, 1.0, 0.0 ); + } + else + { + hitent = -1; + } +} + +#endif \ No newline at end of file diff --git a/ricochet/cl_dll/view.h b/ricochet/cl_dll/view.h new file mode 100644 index 0000000..24d444f --- /dev/null +++ b/ricochet/cl_dll/view.h @@ -0,0 +1,22 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined ( VIEWH ) +#define VIEWH +#pragma once + +void V_StartPitchDrift( void ); +void V_StopPitchDrift( void ); + +#endif // !VIEWH \ No newline at end of file diff --git a/ricochet/cl_dll/voice_status.cpp b/ricochet/cl_dll/voice_status.cpp new file mode 100644 index 0000000..f6cda4d --- /dev/null +++ b/ricochet/cl_dll/voice_status.cpp @@ -0,0 +1,885 @@ +//========= Copyright � 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// There are hud.h's coming out of the woodwork so this ensures that we get the right one. +#if defined(THREEWAVE) || defined(DMC_BUILD) + #include "../dmc/cl_dll/hud.h" +#elif defined(CSTRIKE) + #include "../cstrike/cl_dll/hud.h" +#elif defined(DOD) + #include "../dod/cl_dll/hud.h" +#else + #include "hud.h" +#endif + +#include "cl_util.h" +#include +#include +#include +#include "parsemsg.h" +#include "hud_servers.h" +#include "demo.h" +#include "demo_api.h" +#include "voice_status.h" +#include "r_efx.h" +#include "entity_types.h" +#include "VGUI_ActionSignal.h" +#include "VGUI_Scheme.h" +#include "VGUI_TextImage.h" +#include "vgui_loadtga.h" +#include "vgui_helpers.h" +#include "VGUI_MouseCode.h" + + + +using namespace vgui; + + +extern int cam_thirdperson; + + +#define VOICE_MODEL_INTERVAL 0.3 +#define SCOREBOARD_BLINK_FREQUENCY 0.3 // How often to blink the scoreboard icons. +#define SQUELCHOSCILLATE_PER_SECOND 2.0f + + +extern BitmapTGA *LoadTGA( const char* pImageName ); + + + +// ---------------------------------------------------------------------- // +// The voice manager for the client. +// ---------------------------------------------------------------------- // +CVoiceStatus g_VoiceStatus; + +CVoiceStatus* GetClientVoiceMgr() +{ + return &g_VoiceStatus; +} + + + +// ---------------------------------------------------------------------- // +// CVoiceStatus. +// ---------------------------------------------------------------------- // + +static CVoiceStatus *g_pInternalVoiceStatus = NULL; + +int __MsgFunc_VoiceMask(const char *pszName, int iSize, void *pbuf) +{ + if(g_pInternalVoiceStatus) + g_pInternalVoiceStatus->HandleVoiceMaskMsg(iSize, pbuf); + + return 1; +} + +int __MsgFunc_ReqState(const char *pszName, int iSize, void *pbuf) +{ + if(g_pInternalVoiceStatus) + g_pInternalVoiceStatus->HandleReqStateMsg(iSize, pbuf); + + return 1; +} + + +int g_BannedPlayerPrintCount; +void ForEachBannedPlayer(char id[16]) +{ + char str[256]; + sprintf(str, "Ban %d: %2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x\n", + g_BannedPlayerPrintCount++, + id[0], id[1], id[2], id[3], + id[4], id[5], id[6], id[7], + id[8], id[9], id[10], id[11], + id[12], id[13], id[14], id[15] + ); +#ifdef _WIN32 + strupr(str); +#endif + gEngfuncs.pfnConsolePrint(str); +} + + +void ShowBannedCallback() +{ + if(g_pInternalVoiceStatus) + { + g_BannedPlayerPrintCount = 0; + gEngfuncs.pfnConsolePrint("------- BANNED PLAYERS -------\n"); + g_pInternalVoiceStatus->m_BanMgr.ForEachBannedPlayer(ForEachBannedPlayer); + gEngfuncs.pfnConsolePrint("------------------------------\n"); + } +} + + +// ---------------------------------------------------------------------- // +// CVoiceStatus. +// ---------------------------------------------------------------------- // + +CVoiceStatus::CVoiceStatus() +{ + m_bBanMgrInitialized = false; + m_LastUpdateServerState = 0; + + m_pSpeakerLabelIcon = NULL; + m_pScoreboardNeverSpoken = NULL; + m_pScoreboardNotSpeaking = NULL; + m_pScoreboardSpeaking = NULL; + m_pScoreboardSpeaking2 = NULL; + m_pScoreboardSquelch = NULL; + m_pScoreboardBanned = NULL; + + m_pLocalBitmap = NULL; + m_pAckBitmap = NULL; + + m_bTalking = m_bServerAcked = false; + + memset(m_pBanButtons, 0, sizeof(m_pBanButtons)); + + m_pParentPanel = NULL; + + m_bServerModEnable = -1; + + m_pchGameDir = NULL; +} + + +CVoiceStatus::~CVoiceStatus() +{ + g_pInternalVoiceStatus = NULL; + + for(int i=0; i < MAX_VOICE_SPEAKERS; i++) + { + delete m_Labels[i].m_pLabel; + m_Labels[i].m_pLabel = NULL; + + delete m_Labels[i].m_pIcon; + m_Labels[i].m_pIcon = NULL; + + delete m_Labels[i].m_pBackground; + m_Labels[i].m_pBackground = NULL; + } + + delete m_pLocalLabel; + m_pLocalLabel = NULL; + + FreeBitmaps(); + + if(m_pchGameDir) + { + if(m_bBanMgrInitialized) + { + m_BanMgr.SaveState(m_pchGameDir); + } + + free(m_pchGameDir); + } +} + + +int CVoiceStatus::Init( + IVoiceStatusHelper *pHelper, + Panel **pParentPanel) +{ + // Setup the voice_modenable cvar. + gEngfuncs.pfnRegisterVariable("voice_modenable", "1", FCVAR_ARCHIVE); + + gEngfuncs.pfnRegisterVariable("voice_clientdebug", "0", 0); + + gEngfuncs.pfnAddCommand("voice_showbanned", ShowBannedCallback); + + if(gEngfuncs.pfnGetGameDirectory()) + { + m_BanMgr.Init(gEngfuncs.pfnGetGameDirectory()); + m_bBanMgrInitialized = true; + } + + assert(!g_pInternalVoiceStatus); + g_pInternalVoiceStatus = this; + + m_BlinkTimer = 0; + m_VoiceHeadModel = NULL; + memset(m_Labels, 0, sizeof(m_Labels)); + + for(int i=0; i < MAX_VOICE_SPEAKERS; i++) + { + CVoiceLabel *pLabel = &m_Labels[i]; + + pLabel->m_pBackground = new Label(""); + + if(pLabel->m_pLabel = new Label("")) + { + pLabel->m_pLabel->setVisible( true ); + pLabel->m_pLabel->setFont( Scheme::sf_primary2 ); + pLabel->m_pLabel->setTextAlignment( Label::a_east ); + pLabel->m_pLabel->setContentAlignment( Label::a_east ); + pLabel->m_pLabel->setParent( pLabel->m_pBackground ); + } + + if( pLabel->m_pIcon = new ImagePanel( NULL ) ) + { + pLabel->m_pIcon->setVisible( true ); + pLabel->m_pIcon->setParent( pLabel->m_pBackground ); + } + + pLabel->m_clientindex = -1; + } + + m_pLocalLabel = new ImagePanel(NULL); + + m_bInSquelchMode = false; + + m_pHelper = pHelper; + m_pParentPanel = pParentPanel; + gHUD.AddHudElem(this); + m_iFlags = HUD_ACTIVE; + HOOK_MESSAGE(VoiceMask); + HOOK_MESSAGE(ReqState); + + // Cache the game directory for use when we shut down + const char *pchGameDirT = gEngfuncs.pfnGetGameDirectory(); + m_pchGameDir = (char *)malloc(strlen(pchGameDirT) + 1); + strcpy(m_pchGameDir, pchGameDirT); + + return 1; +} + + +int CVoiceStatus::VidInit() +{ + FreeBitmaps(); + + + if( m_pLocalBitmap = vgui_LoadTGA("gfx/vgui/icntlk_pl.tga") ) + { + m_pLocalBitmap->setColor(Color(255,255,255,135)); + } + + if( m_pAckBitmap = vgui_LoadTGA("gfx/vgui/icntlk_sv.tga") ) + { + m_pAckBitmap->setColor(Color(255,255,255,135)); // Give just a tiny bit of translucency so software draws correctly. + } + + m_pLocalLabel->setImage( m_pLocalBitmap ); + m_pLocalLabel->setVisible( false ); + + + if( m_pSpeakerLabelIcon = vgui_LoadTGANoInvertAlpha("gfx/vgui/speaker4.tga" ) ) + m_pSpeakerLabelIcon->setColor( Color(255,255,255,1) ); // Give just a tiny bit of translucency so software draws correctly. + + if (m_pScoreboardNeverSpoken = vgui_LoadTGANoInvertAlpha("gfx/vgui/640_speaker1.tga")) + m_pScoreboardNeverSpoken->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardNotSpeaking = vgui_LoadTGANoInvertAlpha("gfx/vgui/640_speaker2.tga")) + m_pScoreboardNotSpeaking->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardSpeaking = vgui_LoadTGANoInvertAlpha("gfx/vgui/640_speaker3.tga")) + m_pScoreboardSpeaking->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardSpeaking2 = vgui_LoadTGANoInvertAlpha("gfx/vgui/640_speaker4.tga")) + m_pScoreboardSpeaking2->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardSquelch = vgui_LoadTGA("gfx/vgui/icntlk_squelch.tga")) + m_pScoreboardSquelch->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardBanned = vgui_LoadTGA("gfx/vgui/640_voiceblocked.tga")) + m_pScoreboardBanned->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + // Figure out the voice head model height. + m_VoiceHeadModelHeight = 45; + char *pFile = (char *)gEngfuncs.COM_LoadFile("scripts/voicemodel.txt", 5, NULL); + if(pFile) + { + char token[4096]; + gEngfuncs.COM_ParseFile(pFile, token); + if(token[0] >= '0' && token[0] <= '9') + { + m_VoiceHeadModelHeight = (float)atof(token); + } + + gEngfuncs.COM_FreeFile(pFile); + } + + m_VoiceHeadModel = gEngfuncs.pfnSPR_Load("sprites/voiceicon.spr"); + return TRUE; +} + + +void CVoiceStatus::Frame(double frametime) +{ + // check server banned players once per second + if(gEngfuncs.GetClientTime() - m_LastUpdateServerState > 1) + { + UpdateServerState(false); + } + + m_BlinkTimer += frametime; + + // Update speaker labels. + if( m_pHelper->CanShowSpeakerLabels() ) + { + for( int i=0; i < MAX_VOICE_SPEAKERS; i++ ) + m_Labels[i].m_pBackground->setVisible( m_Labels[i].m_clientindex != -1 ); + } + else + { + for( int i=0; i < MAX_VOICE_SPEAKERS; i++ ) + m_Labels[i].m_pBackground->setVisible( false ); + } + + for(int i=0; i < VOICE_MAX_PLAYERS; i++) + UpdateBanButton(i); +} + + +void CVoiceStatus::CreateEntities() +{ + if(!m_VoiceHeadModel) + return; + + cl_entity_t *localPlayer = gEngfuncs.GetLocalPlayer(); + + int iOutModel = 0; + for(int i=0; i < VOICE_MAX_PLAYERS; i++) + { + if(!m_VoicePlayers[i]) + continue; + + cl_entity_s *pClient = gEngfuncs.GetEntityByIndex(i+1); + + // Don't show an icon if the player is not in our PVS. + if(!pClient || pClient->curstate.messagenum < localPlayer->curstate.messagenum) + continue; + + // Don't show an icon for dead or spectating players (ie: invisible entities). + if(pClient->curstate.effects & EF_NODRAW) + continue; + + // Don't show an icon for the local player unless we're in thirdperson mode. + if(pClient == localPlayer && !cam_thirdperson) + continue; + + cl_entity_s *pEnt = &m_VoiceHeadModels[iOutModel]; + ++iOutModel; + + memset(pEnt, 0, sizeof(*pEnt)); + + pEnt->curstate.rendermode = kRenderTransAdd; + pEnt->curstate.renderamt = 255; + pEnt->baseline.renderamt = 255; + pEnt->curstate.renderfx = kRenderFxNoDissipation; + pEnt->curstate.framerate = 1; + pEnt->curstate.frame = 0; + pEnt->model = (struct model_s*)gEngfuncs.GetSpritePointer(m_VoiceHeadModel); + pEnt->angles[0] = pEnt->angles[1] = pEnt->angles[2] = 0; + pEnt->curstate.scale = 0.5f; + + pEnt->origin[0] = pEnt->origin[1] = 0; + pEnt->origin[2] = 45; + + VectorAdd(pEnt->origin, pClient->origin, pEnt->origin); + + // Tell the engine. + gEngfuncs.CL_CreateVisibleEntity(ET_NORMAL, pEnt); + } +} + + +void CVoiceStatus::UpdateSpeakerStatus( int entindex, qboolean bTalking ) +{ + cvar_t *pVoiceLoopback = NULL; + + if ( !m_pParentPanel || !*m_pParentPanel ) + { + return; + } + + if ( gEngfuncs.pfnGetCvarFloat( "voice_clientdebug" ) ) + { + char msg[256]; + _snprintf( msg, sizeof( msg ), "CVoiceStatus::UpdateSpeakerStatus: ent %d talking = %d\n", entindex, bTalking ); + gEngfuncs.pfnConsolePrint( msg ); + } + + int iLocalPlayerIndex = gEngfuncs.GetLocalPlayer()->index; + + // Is it the local player talking? + if ( entindex == -1 ) + { + m_bTalking = !!bTalking; + if( bTalking ) + { + // Enable voice for them automatically if they try to talk. + gEngfuncs.pfnClientCmd( "voice_modenable 1" ); + } + + // now set the player index to the correct index for the local player + // this will allow us to have the local player's icon flash in the scoreboard + entindex = iLocalPlayerIndex; + + pVoiceLoopback = gEngfuncs.pfnGetCvarPointer( "voice_loopback" ); + } + else if ( entindex == -2 ) + { + m_bServerAcked = !!bTalking; + } + + if ( entindex >= 0 && entindex <= VOICE_MAX_PLAYERS ) + { + int iClient = entindex - 1; + if ( iClient < 0 ) + { + return; + } + + CVoiceLabel *pLabel = FindVoiceLabel( iClient ); + if ( bTalking ) + { + m_VoicePlayers[iClient] = true; + m_VoiceEnabledPlayers[iClient] = true; + + // If we don't have a label for this guy yet, then create one. + if ( !pLabel ) + { + // if this isn't the local player (unless they have voice_loopback on) + if ( ( entindex != iLocalPlayerIndex ) || ( pVoiceLoopback && pVoiceLoopback->value ) ) + { + if ( pLabel = GetFreeVoiceLabel() ) + { + // Get the name from the engine. + hud_player_info_t info; + memset( &info, 0, sizeof( info ) ); + GetPlayerInfo( entindex, &info ); + + char paddedName[512]; + _snprintf( paddedName, sizeof( paddedName ), "%s ", info.name ); + + int color[3]; + m_pHelper->GetPlayerTextColor( entindex, color ); + + if ( pLabel->m_pBackground ) + { + pLabel->m_pBackground->setBgColor( color[0], color[1], color[2], 135 ); + pLabel->m_pBackground->setParent( *m_pParentPanel ); + pLabel->m_pBackground->setVisible( m_pHelper->CanShowSpeakerLabels() ); + } + + if ( pLabel->m_pLabel ) + { + pLabel->m_pLabel->setFgColor( 255, 255, 255, 0 ); + pLabel->m_pLabel->setBgColor( 0, 0, 0, 255 ); + pLabel->m_pLabel->setText( paddedName ); + } + + pLabel->m_clientindex = iClient; + } + } + } + } + else + { + m_VoicePlayers[iClient] = false; + + // If we have a label for this guy, kill it. + if ( pLabel ) + { + pLabel->m_pBackground->setVisible( false ); + pLabel->m_clientindex = -1; + } + } + } + + RepositionLabels(); +} + + +void CVoiceStatus::UpdateServerState(bool bForce) +{ + // Can't do anything when we're not in a level. + char const *pLevelName = gEngfuncs.pfnGetLevelName(); + if( pLevelName[0] == 0 ) + { + if( gEngfuncs.pfnGetCvarFloat("voice_clientdebug") ) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::UpdateServerState: pLevelName[0]==0\n" ); + } + + return; + } + + int bCVarModEnable = !!gEngfuncs.pfnGetCvarFloat("voice_modenable"); + if(bForce || m_bServerModEnable != bCVarModEnable) + { + m_bServerModEnable = bCVarModEnable; + + char str[256]; + _snprintf(str, sizeof(str), "VModEnable %d", m_bServerModEnable); + ServerCmd(str); + + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char msg[256]; + sprintf(msg, "CVoiceStatus::UpdateServerState: Sending '%s'\n", str); + gEngfuncs.pfnConsolePrint(msg); + } + } + + char str[2048]; + sprintf(str, "vban"); + bool bChange = false; + + for(unsigned long dw=0; dw < VOICE_MAX_PLAYERS_DW; dw++) + { + unsigned long serverBanMask = 0; + unsigned long banMask = 0; + for(unsigned long i=0; i < 32; i++) + { + char playerID[16]; + if(!gEngfuncs.GetPlayerUniqueID(i+1, playerID)) + continue; + + if(m_BanMgr.GetPlayerBan(playerID)) + banMask |= 1 << i; + + if(m_ServerBannedPlayers[dw*32 + i]) + serverBanMask |= 1 << i; + } + + if(serverBanMask != banMask) + bChange = true; + + // Ok, the server needs to be updated. + char numStr[512]; + sprintf(numStr, " %x", banMask); + strcat(str, numStr); + } + + if(bChange || bForce) + { + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char msg[256]; + sprintf(msg, "CVoiceStatus::UpdateServerState: Sending '%s'\n", str); + gEngfuncs.pfnConsolePrint(msg); + } + + gEngfuncs.pfnServerCmdUnreliable(str); // Tell the server.. + } + else + { + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::UpdateServerState: no change\n" ); + } + } + + m_LastUpdateServerState = gEngfuncs.GetClientTime(); +} + +void CVoiceStatus::UpdateSpeakerImage(Label *pLabel, int iPlayer) +{ + m_pBanButtons[iPlayer-1] = pLabel; + UpdateBanButton(iPlayer-1); +} + +void CVoiceStatus::UpdateBanButton(int iClient) +{ + Label *pPanel = m_pBanButtons[iClient]; + + if (!pPanel) + return; + + char playerID[16]; + extern bool HACK_GetPlayerUniqueID( int iPlayer, char playerID[16] ); + if(!HACK_GetPlayerUniqueID(iClient+1, playerID)) + return; + + // Figure out if it's blinking or not. + bool bBlink = fmod(m_BlinkTimer, SCOREBOARD_BLINK_FREQUENCY*2) < SCOREBOARD_BLINK_FREQUENCY; + bool bTalking = !!m_VoicePlayers[iClient]; + bool bBanned = m_BanMgr.GetPlayerBan(playerID); + bool bNeverSpoken = !m_VoiceEnabledPlayers[iClient]; + + // Get the appropriate image to display on the panel. + if (bBanned) + { + pPanel->setImage(m_pScoreboardBanned); + } + else if (bTalking) + { + if (bBlink) + { + pPanel->setImage(m_pScoreboardSpeaking2); + } + else + { + pPanel->setImage(m_pScoreboardSpeaking); + } + pPanel->setFgColor(255, 170, 0, 1); + } + else if (bNeverSpoken) + { + pPanel->setImage(m_pScoreboardNeverSpoken); + pPanel->setFgColor(100, 100, 100, 1); + } + else + { + pPanel->setImage(m_pScoreboardNotSpeaking); + } +} + + +void CVoiceStatus::HandleVoiceMaskMsg(int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + unsigned long dw; + for(dw=0; dw < VOICE_MAX_PLAYERS_DW; dw++) + { + m_AudiblePlayers.SetDWord(dw, (unsigned long)READ_LONG()); + m_ServerBannedPlayers.SetDWord(dw, (unsigned long)READ_LONG()); + + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char str[256]; + gEngfuncs.pfnConsolePrint("CVoiceStatus::HandleVoiceMaskMsg\n"); + + sprintf(str, " - m_AudiblePlayers[%d] = %lu\n", dw, m_AudiblePlayers.GetDWord(dw)); + gEngfuncs.pfnConsolePrint(str); + + sprintf(str, " - m_ServerBannedPlayers[%d] = %lu\n", dw, m_ServerBannedPlayers.GetDWord(dw)); + gEngfuncs.pfnConsolePrint(str); + } + } + + m_bServerModEnable = READ_BYTE(); +} + +void CVoiceStatus::HandleReqStateMsg(int iSize, void *pbuf) +{ + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint("CVoiceStatus::HandleReqStateMsg\n"); + } + + UpdateServerState(true); +} + +void CVoiceStatus::StartSquelchMode() +{ + if(m_bInSquelchMode) + return; + + m_bInSquelchMode = true; + m_pHelper->UpdateCursorState(); +} + +void CVoiceStatus::StopSquelchMode() +{ + m_bInSquelchMode = false; + m_pHelper->UpdateCursorState(); +} + +bool CVoiceStatus::IsInSquelchMode() +{ + return m_bInSquelchMode; +} + +CVoiceLabel* CVoiceStatus::FindVoiceLabel(int clientindex) +{ + for(int i=0; i < MAX_VOICE_SPEAKERS; i++) + { + if(m_Labels[i].m_clientindex == clientindex) + return &m_Labels[i]; + } + + return NULL; +} + + +CVoiceLabel* CVoiceStatus::GetFreeVoiceLabel() +{ + return FindVoiceLabel(-1); +} + + +void CVoiceStatus::RepositionLabels() +{ + // find starting position to draw from, along right-hand side of screen + int y = ScreenHeight / 2; + + int iconWide = 8, iconTall = 8; + if( m_pSpeakerLabelIcon ) + { + m_pSpeakerLabelIcon->getSize( iconWide, iconTall ); + } + + // Reposition active labels. + for(int i = 0; i < MAX_VOICE_SPEAKERS; i++) + { + CVoiceLabel *pLabel = &m_Labels[i]; + + if( pLabel->m_clientindex == -1 || !pLabel->m_pLabel ) + { + if( pLabel->m_pBackground ) + pLabel->m_pBackground->setVisible( false ); + + continue; + } + + int textWide, textTall; + pLabel->m_pLabel->getContentSize( textWide, textTall ); + + // Don't let it stretch too far across their screen. + if( textWide > (ScreenWidth*2)/3 ) + textWide = (ScreenWidth*2)/3; + + // Setup the background label to fit everything in. + int border = 2; + int bgWide = textWide + iconWide + border*3; + int bgTall = max( textTall, iconTall ) + border*2; + pLabel->m_pBackground->setBounds( ScreenWidth - bgWide - 8, y, bgWide, bgTall ); + + // Put the text at the left. + pLabel->m_pLabel->setBounds( border, (bgTall - textTall) / 2, textWide, textTall ); + + // Put the icon at the right. + int iconLeft = border + textWide + border; + int iconTop = (bgTall - iconTall) / 2; + if( pLabel->m_pIcon ) + { + pLabel->m_pIcon->setImage( m_pSpeakerLabelIcon ); + pLabel->m_pIcon->setBounds( iconLeft, iconTop, iconWide, iconTall ); + } + + y += bgTall + 2; + } + + if( m_pLocalBitmap && m_pAckBitmap && m_pLocalLabel && (m_bTalking || m_bServerAcked) ) + { + m_pLocalLabel->setParent(*m_pParentPanel); + m_pLocalLabel->setVisible( true ); + + if( m_bServerAcked && !!gEngfuncs.pfnGetCvarFloat("voice_clientdebug") ) + m_pLocalLabel->setImage( m_pAckBitmap ); + else + m_pLocalLabel->setImage( m_pLocalBitmap ); + + int sizeX, sizeY; + m_pLocalBitmap->getSize(sizeX, sizeY); + + int local_xPos = ScreenWidth - sizeX - 10; + int local_yPos = m_pHelper->GetAckIconHeight() - sizeY; + + m_pLocalLabel->setPos( local_xPos, local_yPos ); + } + else + { + m_pLocalLabel->setVisible( false ); + } +} + + +void CVoiceStatus::FreeBitmaps() +{ + // Delete all the images we have loaded. + delete m_pLocalBitmap; + m_pLocalBitmap = NULL; + + delete m_pAckBitmap; + m_pAckBitmap = NULL; + + delete m_pSpeakerLabelIcon; + m_pSpeakerLabelIcon = NULL; + + delete m_pScoreboardNeverSpoken; + m_pScoreboardNeverSpoken = NULL; + + delete m_pScoreboardNotSpeaking; + m_pScoreboardNotSpeaking = NULL; + + delete m_pScoreboardSpeaking; + m_pScoreboardSpeaking = NULL; + + delete m_pScoreboardSpeaking2; + m_pScoreboardSpeaking2 = NULL; + + delete m_pScoreboardSquelch; + m_pScoreboardSquelch = NULL; + + delete m_pScoreboardBanned; + m_pScoreboardBanned = NULL; + + // Clear references to the images in panels. + for(int i=0; i < VOICE_MAX_PLAYERS; i++) + { + if (m_pBanButtons[i]) + { + m_pBanButtons[i]->setImage(NULL); + } + } + + if(m_pLocalLabel) + m_pLocalLabel->setImage(NULL); +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the target client has been banned +// Input : playerID - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CVoiceStatus::IsPlayerBlocked(int iPlayer) +{ + char playerID[16]; + if (!gEngfuncs.GetPlayerUniqueID(iPlayer, playerID)) + return false; + + return m_BanMgr.GetPlayerBan(playerID); +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the player can't hear the other client due to game rules (eg. the other team) +// Input : playerID - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CVoiceStatus::IsPlayerAudible(int iPlayer) +{ + return !!m_AudiblePlayers[iPlayer-1]; +} + +//----------------------------------------------------------------------------- +// Purpose: blocks/unblocks the target client from being heard +// Input : playerID - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +void CVoiceStatus::SetPlayerBlockedState(int iPlayer, bool blocked) +{ + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::SetPlayerBlockedState part 1\n" ); + } + + char playerID[16]; + if (!gEngfuncs.GetPlayerUniqueID(iPlayer, playerID)) + return; + + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::SetPlayerBlockedState part 2\n" ); + } + + // Squelch or (try to) unsquelch this player. + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char str[256]; + sprintf(str, "CVoiceStatus::SetPlayerBlockedState: setting player %d ban to %d\n", iPlayer, !m_BanMgr.GetPlayerBan(playerID)); + gEngfuncs.pfnConsolePrint(str); + } + + m_BanMgr.SetPlayerBan( playerID, blocked ); + UpdateServerState(false); +} diff --git a/ricochet/cl_dll/voice_status.h b/ricochet/cl_dll/voice_status.h new file mode 100644 index 0000000..8edf58c --- /dev/null +++ b/ricochet/cl_dll/voice_status.h @@ -0,0 +1,228 @@ +//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VOICE_STATUS_H +#define VOICE_STATUS_H +#pragma once + + +#include "VGUI_Label.h" +#include "VGUI_LineBorder.h" +#include "VGUI_ImagePanel.h" +#include "VGUI_BitmapTGA.h" +#include "VGUI_InputSignal.h" +#include "VGUI_Button.h" +#include "voice_common.h" +#include "cl_entity.h" +#include "voice_banmgr.h" +#include "vgui_checkbutton2.h" +#include "vgui_defaultinputsignal.h" + + +class CVoiceStatus; + + +class CVoiceLabel +{ +public: + vgui::Label *m_pLabel; + vgui::Label *m_pBackground; + vgui::ImagePanel *m_pIcon; // Voice icon next to player name. + int m_clientindex; // Client index of the speaker. -1 if this label isn't being used. +}; + + +// This is provided by each mod to access data that may not be the same across mods. +class IVoiceStatusHelper +{ +public: + virtual ~IVoiceStatusHelper() {} + + // Get RGB color for voice status text about this player. + virtual void GetPlayerTextColor(int entindex, int color[3]) = 0; + + // Force it to update the cursor state. + virtual void UpdateCursorState() = 0; + + // Return the height above the bottom that the voice ack icons should be drawn at. + virtual int GetAckIconHeight() = 0; + + // Return true if the voice manager is allowed to show speaker labels + // (mods usually return false when the scoreboard is up). + virtual bool CanShowSpeakerLabels() = 0; +}; + +//----------------------------------------------------------------------------- +// Purpose: Holds a color for the shared image +//----------------------------------------------------------------------------- +class VoiceImagePanel : public vgui::ImagePanel +{ + virtual void paintBackground() + { + if (_image!=null) + { + vgui::Color col; + getFgColor(col); + _image->setColor(col); + _image->doPaint(this); + } + } +}; + + +class CVoiceStatus : public CHudBase, public vgui::CDefaultInputSignal +{ +public: + CVoiceStatus(); + virtual ~CVoiceStatus(); + +// CHudBase overrides. +public: + + // Initialize the cl_dll's voice manager. + virtual int Init( + IVoiceStatusHelper *m_pHelper, + vgui::Panel **pParentPanel); + + // ackPosition is the bottom position of where CVoiceStatus will draw the voice acknowledgement labels. + virtual int VidInit(); + + +public: + + // Call from HUD_Frame each frame. + void Frame(double frametime); + + // Called when a player starts or stops talking. + // entindex is -1 to represent the local client talking (before the data comes back from the server). + // When the server acknowledges that the local client is talking, then entindex will be gEngfuncs.GetLocalPlayer(). + // entindex is -2 to represent the local client's voice being acked by the server. + void UpdateSpeakerStatus(int entindex, qboolean bTalking); + + // sets the correct image in the label for the player + void UpdateSpeakerImage(vgui::Label *pLabel, int iPlayer); + + // Call from the HUD_CreateEntities function so it can add sprites above player heads. + void CreateEntities(); + + // Called when the server registers a change to who this client can hear. + void HandleVoiceMaskMsg(int iSize, void *pbuf); + + // The server sends this message initially to tell the client to send their state. + void HandleReqStateMsg(int iSize, void *pbuf); + + +// Squelch mode functions. +public: + + // When you enter squelch mode, pass in + void StartSquelchMode(); + void StopSquelchMode(); + bool IsInSquelchMode(); + + // returns true if the target client has been banned + // playerIndex is of range 1..maxplayers + bool IsPlayerBlocked(int iPlayerIndex); + + // returns false if the player can't hear the other client due to game rules (eg. the other team) + bool IsPlayerAudible(int iPlayerIndex); + + // blocks the target client from being heard + void SetPlayerBlockedState(int iPlayerIndex, bool blocked); + +public: + + CVoiceLabel* FindVoiceLabel(int clientindex); // Find a CVoiceLabel representing the specified speaker. + // Returns NULL if none. + // entindex can be -1 if you want a currently-unused voice label. + CVoiceLabel* GetFreeVoiceLabel(); // Get an unused voice label. Returns NULL if none. + + void RepositionLabels(); + + void FreeBitmaps(); + + void UpdateServerState(bool bForce); + + // Update the button artwork to reflect the client's current state. + void UpdateBanButton(int iClient); + + +public: + + enum {MAX_VOICE_SPEAKERS=7}; + + float m_LastUpdateServerState; // Last time we called this function. + int m_bServerModEnable; // What we've sent to the server about our "voice_modenable" cvar. + + vgui::Panel **m_pParentPanel; + CPlayerBitVec m_VoicePlayers; // Who is currently talking. Indexed by client index. + + // This is the gamerules-defined list of players that you can hear. It is based on what teams people are on + // and is totally separate from the ban list. Indexed by client index. + CPlayerBitVec m_AudiblePlayers; + + // Players who have spoken at least once in the game so far + CPlayerBitVec m_VoiceEnabledPlayers; + + // This is who the server THINKS we have banned (it can become incorrect when a new player arrives on the server). + // It is checked periodically, and the server is told to squelch or unsquelch the appropriate players. + CPlayerBitVec m_ServerBannedPlayers; + + cl_entity_s m_VoiceHeadModels[VOICE_MAX_PLAYERS]; // These aren't necessarily in the order of players. They are just + // a place for it to put data in during CreateEntities. + + IVoiceStatusHelper *m_pHelper; // Each mod provides an implementation of this. + + + // Scoreboard icons. + double m_BlinkTimer; // Blink scoreboard icons.. + vgui::BitmapTGA *m_pScoreboardNeverSpoken; + vgui::BitmapTGA *m_pScoreboardNotSpeaking; + vgui::BitmapTGA *m_pScoreboardSpeaking; + vgui::BitmapTGA *m_pScoreboardSpeaking2; + vgui::BitmapTGA *m_pScoreboardSquelch; + vgui::BitmapTGA *m_pScoreboardBanned; + + vgui::Label *m_pBanButtons[VOICE_MAX_PLAYERS]; // scoreboard buttons. + + // Squelch mode stuff. + bool m_bInSquelchMode; + + HSPRITE m_VoiceHeadModel; // Voice head model (goes above players who are speaking). + float m_VoiceHeadModelHeight; // Height above their head to place the model. + + vgui::Image *m_pSpeakerLabelIcon; // Icon next to speaker labels. + + // Lower-right icons telling when the local player is talking.. + vgui::BitmapTGA *m_pLocalBitmap; // Represents the local client talking. + vgui::BitmapTGA *m_pAckBitmap; // Represents the server ack'ing the client talking. + vgui::ImagePanel *m_pLocalLabel; // Represents the local client talking. + + bool m_bTalking; // Set to true when the client thinks it's talking. + bool m_bServerAcked; // Set to true when the server knows the client is talking. + +public: + + CVoiceBanMgr m_BanMgr; // Tracks which users we have squelched and don't want to hear. + +public: + + bool m_bBanMgrInitialized; + + // Labels telling who is speaking. + CVoiceLabel m_Labels[MAX_VOICE_SPEAKERS]; + + // Cache the game directory for use when we shut down + char * m_pchGameDir; +}; + + +// Get the (global) voice manager. +CVoiceStatus* GetClientVoiceMgr(); + + +#endif // VOICE_STATUS_H diff --git a/ricochet/cl_dll/wrect.h b/ricochet/cl_dll/wrect.h new file mode 100644 index 0000000..f2c4f12 --- /dev/null +++ b/ricochet/cl_dll/wrect.h @@ -0,0 +1,23 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( WRECTH ) +#define WRECTH + +typedef struct rect_s +{ + int left, right, top, bottom; +} wrect_t; + +#endif \ No newline at end of file diff --git a/ricochet/dlls/Makefile b/ricochet/dlls/Makefile new file mode 100644 index 0000000..b16c1b5 --- /dev/null +++ b/ricochet/dlls/Makefile @@ -0,0 +1,136 @@ +# +# Half-Life Ricochet SDK 2.3 ricochet_i386.so Makefile for x86 Linux +# +# October 2002 by Leon Hartwig (hartwig@valvesoftware.com) +# + +DLLNAME=ricochet + +ARCH=i386 + +#make sure this is the correct compiler for your system +CC=gcc + +DLL_SRCDIR=. +ENGINE_SRCDIR=../../engine +COMMON_SRCDIR=../../common +PM_SHARED_SRCDIR=../pm_shared +GAME_SHARED_SRCDIR=../../game_shared +WPN_SRC_DIR=$(DLL_SRCDIR)/wpn_shared + +DLL_OBJDIR=$(DLL_SRCDIR)/obj +PM_SHARED_OBJDIR=$(DLL_OBJDIR)/pm_shared +GAME_SHARED_OBJDIR=$(DLL_OBJDIR)/game_shared +WPN_OBJ_DIR=$(DLL_OBJDIR)/wpn_shared + +BASE_CFLAGS=-Dstricmp=strcasecmp -D_strnicmp=strncasecmp -Dstrnicmp=strncasecmp -D_vsnprintf=vsnprintf + +#safe optimization +CFLAGS=$(BASE_CFLAGS) -w -m486 -O1 + +#full optimization +#CFLAGS=$(BASE_CFLAGS) -w -O2 -m486 -ffast-math -funroll-loops \ + -fomit-frame-pointer -fexpensive-optimizations \ + -malign-loops=2 -malign-jumps=2 -malign-functions=2 + +#use these when debugging +#CFLAGS=$(BASE_CFLAGS) -g + +INCLUDEDIRS=-I. -I$(ENGINE_SRCDIR) -I$(COMMON_SRCDIR) -I$(PM_SHARED_SRCDIR) -I$(GAME_SHARED_SRCDIR) + +LDFLAGS= + +SHLIBEXT=so +SHLIBCFLAGS=-fPIC +SHLIBLDFLAGS=-shared + +DO_CC=$(CC) $(CFLAGS) $(SHLIBCFLAGS) $(INCLUDEDIRS) -o $@ -c $< + +############################################################################# +# SETUP AND BUILD +# GAME +############################################################################# + +$(DLL_OBJDIR)/%.o: $(DLL_SRCDIR)/%.cpp + $(DO_CC) + +$(GAME_SHARED_OBJDIR)/%.o: $(GAME_SHARED_SRCDIR)/%.cpp + $(DO_CC) + +$(PM_SHARED_OBJDIR)/%.o: $(PM_SHARED_SRCDIR)/%.c + $(DO_CC) + +$(WPN_OBJ_DIR)/%.o : $(WPN_SRC_DIR)/%.cpp + $(DO_CC) + +OBJ = \ + $(DLL_OBJDIR)/airtank.o \ + $(DLL_OBJDIR)/animating.o \ + $(DLL_OBJDIR)/animation.o \ + $(DLL_OBJDIR)/bmodels.o \ + $(DLL_OBJDIR)/buttons.o \ + $(DLL_OBJDIR)/cbase.o \ + $(DLL_OBJDIR)/client.o \ + $(DLL_OBJDIR)/combat.o \ + $(DLL_OBJDIR)/disc_arena.o \ + $(DLL_OBJDIR)/disc_powerups.o \ + $(DLL_OBJDIR)/doors.o \ + $(DLL_OBJDIR)/effects.o \ + $(DLL_OBJDIR)/explode.o \ + $(DLL_OBJDIR)/func_break.o \ + $(DLL_OBJDIR)/func_tank.o \ + $(DLL_OBJDIR)/game.o \ + $(DLL_OBJDIR)/gamerules.o \ + $(DLL_OBJDIR)/ggrenade.o \ + $(DLL_OBJDIR)/globals.o \ + $(DLL_OBJDIR)/h_ai.o \ + $(DLL_OBJDIR)/h_battery.o \ + $(DLL_OBJDIR)/h_cycler.o \ + $(DLL_OBJDIR)/h_export.o \ + $(DLL_OBJDIR)/healthkit.o \ + $(DLL_OBJDIR)/items.o \ + $(DLL_OBJDIR)/lights.o \ + $(DLL_OBJDIR)/maprules.o \ + $(DLL_OBJDIR)/mortar.o \ + $(DLL_OBJDIR)/mpstubb.o \ + $(DLL_OBJDIR)/multiplay_gamerules.o \ + $(DLL_OBJDIR)/observer.o \ + $(DLL_OBJDIR)/pathcorner.o \ + $(DLL_OBJDIR)/plane.o \ + $(DLL_OBJDIR)/plats.o \ + $(DLL_OBJDIR)/player.o \ + $(DLL_OBJDIR)/singleplay_gamerules.o \ + $(DLL_OBJDIR)/skill.o \ + $(DLL_OBJDIR)/sound.o \ + $(DLL_OBJDIR)/soundent.o \ + $(DLL_OBJDIR)/spectator.o \ + $(DLL_OBJDIR)/subs.o \ + $(DLL_OBJDIR)/teamplay_gamerules.o \ + $(DLL_OBJDIR)/triggers.o \ + $(DLL_OBJDIR)/util.o \ + $(DLL_OBJDIR)/weapons.o \ + $(DLL_OBJDIR)/world.o \ + $(DLL_OBJDIR)/xen.o \ + $(WPN_OBJ_DIR)/disc_weapon_disc.o \ + $(PM_SHARED_OBJDIR)/pm_shared.o \ + $(PM_SHARED_OBJDIR)/pm_math.o \ + $(PM_SHARED_OBJDIR)/pm_debug.o \ + $(GAME_SHARED_OBJDIR)/voice_gamemgr.o + +$(DLLNAME)_$(ARCH).$(SHLIBEXT) : neat $(OBJ) + $(CC) $(CFLAGS) $(SHLIBLDFLAGS) $(LDFLAGS) -o $@ $(OBJ) + +neat: + -mkdir $(DLL_OBJDIR) + -mkdir $(GAME_SHARED_OBJDIR) + -mkdir $(PM_SHARED_OBJDIR) + -mkdir $(WPN_OBJ_DIR) +clean: + -rm -f $(OBJ) + -rm -f $(DLLNAME)_$(ARCH).$(SHLIBEXT) +spotless: clean + -rm -r $(WPN_OBJ_DIR) + -rm -r $(GAME_SHARED_OBJDIR) + -rm -r $(PM_SHARED_OBJDIR) + -rm -r $(DLL_OBJDIR) + diff --git a/ricochet/dlls/activity.h b/ricochet/dlls/activity.h new file mode 100644 index 0000000..a496c12 --- /dev/null +++ b/ricochet/dlls/activity.h @@ -0,0 +1,79 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef ACTIVITY_H +#define ACTIVITY_H + + +typedef enum { + ACT_RESET = 0, // Set m_Activity to this invalid value to force a reset to m_IdealActivity + ACT_IDLE, + ACT_HOP, + ACT_HOP_LEFT_FOOT, + ACT_LEAP, + + ACT_TURN_LEFT, + ACT_TURN_RIGHT, + + ACT_BASE_STAND, + ACT_BASE_STAND_THROW, + ACT_FREEZE_STAND, + ACT_FREEZE_STAND_THROW, + ACT_HARD_STAND, + ACT_HARD_STAND_THROW, + ACT_TRIPLE_STAND, + ACT_TRIPLE_STAND_THROW, + + ACT_UNARMED_WALK, + ACT_UNARMED_RUN, + ACT_UNARMED_BACKPEDAL, + + ACT_BASE_WALK, + ACT_BASE_RUN, + ACT_BASE_THROW, + ACT_BASE_BACKUP, + ACT_BASE_BACKUP_THROW, + + ACT_BASE_REVERSE, + ACT_BASE_REVERSE_THROW, + + ACT_FALL, + ACT_FALL_FORWARD, + ACT_FALL_BACKWARD, + ACT_FALL_LEFT, + ACT_FALL_RIGHT, + + ACT_FLINCH_CLOCKWISE, + ACT_FLINCH_COUNTERCLOCKWISE, + ACT_FLINCH_BACK, + ACT_FLINCH_LEFT, + ACT_FLINCH_RIGHT, + ACT_FLINCH_FORWARD, + + ACT_DIE_HEADSHOT, + ACT_DIEFORWARD, + ACT_DIEBACKWARD, +} Activity; + + +typedef struct { + int type; + char *name; +} activity_map_t; + +extern activity_map_t activity_map[]; + + +#endif //ACTIVITY_H diff --git a/ricochet/dlls/activitymap.h b/ricochet/dlls/activitymap.h new file mode 100644 index 0000000..390d634 --- /dev/null +++ b/ricochet/dlls/activitymap.h @@ -0,0 +1,37 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#define _A( a ) { a, #a } + +activity_map_t activity_map[] = +{ +_A( ACT_IDLE ), +_A( ACT_HOP ), + +_A( ACT_FALL_FORWARD ), +_A( ACT_FALL_BACKWARD ), +_A( ACT_FALL_LEFT ), +_A( ACT_FALL_RIGHT ), + +_A( ACT_FLINCH_BACK ), +_A( ACT_FLINCH_LEFT ), +_A( ACT_FLINCH_RIGHT ), +_A( ACT_FLINCH_FORWARD ), + +_A( ACT_DIE_HEADSHOT ), +_A( ACT_DIEFORWARD ), +_A( ACT_DIEBACKWARD ), +0, NULL +}; diff --git a/ricochet/dlls/airtank.cpp b/ricochet/dlls/airtank.cpp new file mode 100644 index 0000000..fa74af4 --- /dev/null +++ b/ricochet/dlls/airtank.cpp @@ -0,0 +1,118 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" + +class CAirtank : public CGrenade +{ + void Spawn( void ); + void Precache( void ); + void EXPORT TankThink( void ); + void EXPORT TankTouch( CBaseEntity *pOther ); + int BloodColor( void ) { return DONT_BLEED; }; + void Killed( entvars_t *pevAttacker, int iGib ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_state; +}; + + +LINK_ENTITY_TO_CLASS( item_airtank, CAirtank ); +TYPEDESCRIPTION CAirtank::m_SaveData[] = +{ + DEFINE_FIELD( CAirtank, m_state, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CAirtank, CGrenade ); + + +void CAirtank :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/w_oxygen.mdl"); + UTIL_SetSize(pev, Vector( -16, -16, 0), Vector(16, 16, 36)); + UTIL_SetOrigin( pev, pev->origin ); + + SetTouch( &CAirtank::TankTouch ); + SetThink( &CAirtank::TankThink ); + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_YES; + pev->health = 20; + pev->dmg = 50; + m_state = 1; +} + +void CAirtank::Precache( void ) +{ + PRECACHE_MODEL("models/w_oxygen.mdl"); + PRECACHE_SOUND("doors/aliendoor3.wav"); +} + + +void CAirtank :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->owner = ENT( pevAttacker ); + + // UNDONE: this should make a big bubble cloud, not an explosion + + Explode( pev->origin, Vector( 0, 0, -1 ) ); +} + + +void CAirtank::TankThink( void ) +{ + // Fire trigger + m_state = 1; + SUB_UseTargets( this, USE_TOGGLE, 0 ); +} + + +void CAirtank::TankTouch( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + return; + + if (!m_state) + { + // "no oxygen" sound + EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_swim2.wav", 1.0, ATTN_NORM ); + return; + } + + // give player 12 more seconds of air + pOther->pev->air_finished = gpGlobals->time + 12; + + // suit recharge sound + EMIT_SOUND( ENT(pev), CHAN_VOICE, "doors/aliendoor3.wav", 1.0, ATTN_NORM ); + + // recharge airtank in 30 seconds + pev->nextthink = gpGlobals->time + 30; + m_state = 0; + SUB_UseTargets( this, USE_TOGGLE, 1 ); +} diff --git a/ricochet/dlls/animating.cpp b/ricochet/dlls/animating.cpp new file mode 100644 index 0000000..7fc20c2 --- /dev/null +++ b/ricochet/dlls/animating.cpp @@ -0,0 +1,313 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== monsters.cpp ======================================================== + + Monster-related utility code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "animation.h" +#include "saverestore.h" + +TYPEDESCRIPTION CBaseAnimating::m_SaveData[] = +{ + DEFINE_FIELD( CBaseMonster, m_flFrameRate, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flGroundSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flLastEventCheck, FIELD_TIME ), + DEFINE_FIELD( CBaseMonster, m_fSequenceFinished, FIELD_BOOLEAN ), + DEFINE_FIELD( CBaseMonster, m_fSequenceLoops, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CBaseAnimating, CBaseDelay ); + + +//========================================================= +// StudioFrameAdvance - advance the animation frame up to the current time +// if an flInterval is passed in, only advance animation that number of seconds +//========================================================= +float CBaseAnimating :: StudioFrameAdvance ( float flInterval ) +{ + if (flInterval == 0.0) + { + flInterval = (gpGlobals->time - pev->animtime); + if (flInterval <= 0.001) + { + pev->animtime = gpGlobals->time; + return 0.0; + } + } + if (! pev->animtime) + flInterval = 0.0; + + pev->frame += flInterval * m_flFrameRate * pev->framerate; + pev->animtime = gpGlobals->time; + + if (pev->frame < 0.0 || pev->frame >= 256.0) + { + if (m_fSequenceLoops) + pev->frame -= (int)(pev->frame / 256.0) * 256.0; + else + pev->frame = (pev->frame < 0.0) ? 0 : 255; + m_fSequenceFinished = TRUE; // just in case it wasn't caught in GetEvents + } + + return flInterval; +} + +//========================================================= +// LookupActivity +//========================================================= +int CBaseAnimating :: LookupActivity ( int activity ) +{ + ASSERT( activity != 0 ); + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupActivity( pmodel, pev, activity ); +} + +//========================================================= +// LookupActivityHeaviest +// +// Get activity with highest 'weight' +// +//========================================================= +int CBaseAnimating :: LookupActivityHeaviest ( int activity ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupActivityHeaviest( pmodel, pev, activity ); +} + +//========================================================= +//========================================================= +int CBaseAnimating :: LookupSequence ( const char *label ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupSequence( pmodel, label ); +} + + +//========================================================= +//========================================================= +void CBaseAnimating :: ResetSequenceInfo ( ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + GetSequenceInfo( pmodel, pev, &m_flFrameRate, &m_flGroundSpeed ); + m_fSequenceLoops = ((GetSequenceFlags() & STUDIO_LOOPING) != 0); + pev->animtime = gpGlobals->time; + pev->framerate = 1.0; + m_fSequenceFinished = FALSE; + m_flLastEventCheck = gpGlobals->time; +} + + + +//========================================================= +//========================================================= +BOOL CBaseAnimating :: GetSequenceFlags( ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::GetSequenceFlags( pmodel, pev ); +} + +//========================================================= +// DispatchAnimEvents +//========================================================= +void CBaseAnimating :: DispatchAnimEvents ( float flInterval ) +{ + MonsterEvent_t event; + + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + if ( !pmodel ) + { + ALERT( at_aiconsole, "Gibbed monster is thinking!\n" ); + return; + } + + // FIXME: I have to do this or some events get missed, and this is probably causing the problem below + flInterval = 0.1; + + // FIX: this still sometimes hits events twice + float flStart = pev->frame + (m_flLastEventCheck - pev->animtime) * m_flFrameRate * pev->framerate; + float flEnd = pev->frame + flInterval * m_flFrameRate * pev->framerate; + m_flLastEventCheck = pev->animtime + flInterval; + + m_fSequenceFinished = FALSE; + if (flEnd >= 256 || flEnd <= 0.0) + m_fSequenceFinished = TRUE; + + int index = 0; + + while ( (index = GetAnimationEvent( pmodel, pev, &event, flStart, flEnd, index ) ) != 0 ) + { + HandleAnimEvent( &event ); + } +} + + +//========================================================= +//========================================================= +float CBaseAnimating :: SetBoneController ( int iController, float flValue ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return SetController( pmodel, pev, iController, flValue ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: InitBoneControllers ( void ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + SetController( pmodel, pev, 0, 0.0 ); + SetController( pmodel, pev, 1, 0.0 ); + SetController( pmodel, pev, 2, 0.0 ); + SetController( pmodel, pev, 3, 0.0 ); +} + +//========================================================= +//========================================================= +float CBaseAnimating :: SetBlending ( int iBlender, float flValue ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::SetBlending( pmodel, pev, iBlender, flValue ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: GetBonePosition ( int iBone, Vector &origin, Vector &angles ) +{ + GET_BONE_POSITION( ENT(pev), iBone, origin, angles ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: GetAttachment ( int iAttachment, Vector &origin, Vector &angles ) +{ + GET_ATTACHMENT( ENT(pev), iAttachment, origin, angles ); +} + +//========================================================= +//========================================================= +int CBaseAnimating :: FindTransition( int iEndingSequence, int iGoalSequence, int *piDir ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + if (piDir == NULL) + { + int iDir; + int sequence = ::FindTransition( pmodel, iEndingSequence, iGoalSequence, &iDir ); + if (iDir != 1) + return -1; + else + return sequence; + } + + return ::FindTransition( pmodel, iEndingSequence, iGoalSequence, piDir ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: GetAutomovement( Vector &origin, Vector &angles, float flInterval ) +{ + +} + +void CBaseAnimating :: SetBodygroup( int iGroup, int iValue ) +{ + ::SetBodygroup( GET_MODEL_PTR( ENT(pev) ), pev, iGroup, iValue ); +} + +int CBaseAnimating :: GetBodygroup( int iGroup ) +{ + return ::GetBodygroup( GET_MODEL_PTR( ENT(pev) ), pev, iGroup ); +} + + +int CBaseAnimating :: ExtractBbox( int sequence, float *mins, float *maxs ) +{ + return ::ExtractBbox( GET_MODEL_PTR( ENT(pev) ), sequence, mins, maxs ); +} + +//========================================================= +//========================================================= + +void CBaseAnimating :: SetSequenceBox( void ) +{ + Vector mins, maxs; + + // Get sequence bbox + if ( ExtractBbox( pev->sequence, mins, maxs ) ) + { + // expand box for rotation + // find min / max for rotations + float yaw = pev->angles.y * (M_PI / 180.0); + + Vector xvector, yvector; + xvector.x = cos(yaw); + xvector.y = sin(yaw); + yvector.x = -sin(yaw); + yvector.y = cos(yaw); + Vector bounds[2]; + + bounds[0] = mins; + bounds[1] = maxs; + + Vector rmin( 9999, 9999, 9999 ); + Vector rmax( -9999, -9999, -9999 ); + Vector base, transformed; + + for (int i = 0; i <= 1; i++ ) + { + base.x = bounds[i].x; + for ( int j = 0; j <= 1; j++ ) + { + base.y = bounds[j].y; + for ( int k = 0; k <= 1; k++ ) + { + base.z = bounds[k].z; + + // transform the point + transformed.x = xvector.x*base.x + yvector.x*base.y; + transformed.y = xvector.y*base.x + yvector.y*base.y; + transformed.z = base.z; + + for ( int l = 0; l < 3; l++ ) + { + if (transformed[l] < rmin[l]) + rmin[l] = transformed[l]; + if (transformed[l] > rmax[l]) + rmax[l] = transformed[l]; + } + } + } + } + rmin.z = 0; + rmax.z = rmin.z + 1; + UTIL_SetSize( pev, rmin, rmax ); + } +} + diff --git a/ricochet/dlls/animation.cpp b/ricochet/dlls/animation.cpp new file mode 100644 index 0000000..4a691a2 --- /dev/null +++ b/ricochet/dlls/animation.cpp @@ -0,0 +1,526 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include +#include +#include + +typedef bool BOOL; + +// hack into header files that we can ship +typedef int qboolean; +typedef unsigned char byte; +#include "../utils/common/mathlib.h" +#include "const.h" +#include "progdefs.h" +#include "edict.h" +#include "eiface.h" + +#include "studio.h" + +#include "../engine/studio.h" + +#ifndef ACTIVITY_H +#include "activity.h" +#endif + +#include "activitymap.h" + +#ifndef ANIMATION_H +#include "animation.h" +#endif + +#ifndef SCRIPTEVENT_H +#include "scriptevent.h" +#endif + +#ifndef ENGINECALLBACK_H +#include "enginecallback.h" +#endif + +extern globalvars_t *gpGlobals; + +#pragma warning( disable : 4244 ) + + + +int ExtractBbox( void *pmodel, int sequence, float *mins, float *maxs ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + mins[0] = pseqdesc[ sequence ].bbmin[0]; + mins[1] = pseqdesc[ sequence ].bbmin[1]; + mins[2] = pseqdesc[ sequence ].bbmin[2]; + + maxs[0] = pseqdesc[ sequence ].bbmax[0]; + maxs[1] = pseqdesc[ sequence ].bbmax[1]; + maxs[2] = pseqdesc[ sequence ].bbmax[2]; + + return 1; +} + + +int LookupActivity( void *pmodel, entvars_t *pev, int activity ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + int weighttotal = 0; + int seq = ACTIVITY_NOT_AVAILABLE; + for (int i = 0; i < pstudiohdr->numseq; i++) + { + if (pseqdesc[i].activity == activity) + { + weighttotal += pseqdesc[i].actweight; + if (!weighttotal || RANDOM_LONG(0,weighttotal-1) < pseqdesc[i].actweight) + seq = i; + } + } + + return seq; +} + + +int LookupActivityHeaviest( void *pmodel, entvars_t *pev, int activity ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr ) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + int weight = 0; + int seq = ACTIVITY_NOT_AVAILABLE; + for (int i = 0; i < pstudiohdr->numseq; i++) + { + if (pseqdesc[i].activity == activity) + { + if ( pseqdesc[i].actweight > weight ) + { + weight = pseqdesc[i].actweight; + seq = i; + } + } + } + + return seq; +} + +void GetEyePosition ( void *pmodel, float *vecEyePosition ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + + if ( !pstudiohdr ) + { + ALERT ( at_console, "GetEyePosition() Can't get pstudiohdr ptr!\n" ); + return; + } + + VectorCopy ( pstudiohdr->eyeposition, vecEyePosition ); +} + +int LookupSequence( void *pmodel, const char *label ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + for (int i = 0; i < pstudiohdr->numseq; i++) + { + if (stricmp( pseqdesc[i].label, label ) == 0) + return i; + } + + return -1; +} + + +int IsSoundEvent( int eventNumber ) +{ + if ( eventNumber == SCRIPT_EVENT_SOUND || eventNumber == SCRIPT_EVENT_SOUND_VOICE ) + return 1; + return 0; +} + + +void SequencePrecache( void *pmodel, const char *pSequenceName ) +{ + int index = LookupSequence( pmodel, pSequenceName ); + if ( index >= 0 ) + { + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || index >= pstudiohdr->numseq ) + return; + + mstudioseqdesc_t *pseqdesc; + mstudioevent_t *pevent; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + index; + pevent = (mstudioevent_t *)((byte *)pstudiohdr + pseqdesc->eventindex); + + for (int i = 0; i < pseqdesc->numevents; i++) + { + // Don't send client-side events to the server AI + if ( pevent[i].event >= EVENT_CLIENT ) + continue; + + // UNDONE: Add a callback to check to see if a sound is precached yet and don't allocate a copy + // of it's name if it is. + if ( IsSoundEvent( pevent[i].event ) ) + { + if ( !strlen(pevent[i].options) ) + { + ALERT( at_error, "Bad sound event %d in sequence %s :: %s (sound is \"%s\")\n", pevent[i].event, pstudiohdr->name, pSequenceName, pevent[i].options ); + } + + PRECACHE_SOUND( (char *)(gpGlobals->pStringBase + ALLOC_STRING(pevent[i].options) ) ); + } + } + } +} + + + +void GetSequenceInfo( void *pmodel, entvars_t *pev, float *pflFrameRate, float *pflGroundSpeed ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return; + + mstudioseqdesc_t *pseqdesc; + + if (pev->sequence >= pstudiohdr->numseq) + { + *pflFrameRate = 0.0; + *pflGroundSpeed = 0.0; + return; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + if (pseqdesc->numframes > 1) + { + *pflFrameRate = 256 * pseqdesc->fps / (pseqdesc->numframes - 1); + *pflGroundSpeed = sqrt( pseqdesc->linearmovement[0]*pseqdesc->linearmovement[0]+ pseqdesc->linearmovement[1]*pseqdesc->linearmovement[1]+ pseqdesc->linearmovement[2]*pseqdesc->linearmovement[2] ); + *pflGroundSpeed = *pflGroundSpeed * pseqdesc->fps / (pseqdesc->numframes - 1); + } + else + { + *pflFrameRate = 256.0; + *pflGroundSpeed = 0.0; + } +} + + +int GetSequenceFlags( void *pmodel, entvars_t *pev ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq ) + return 0; + + mstudioseqdesc_t *pseqdesc; + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + return pseqdesc->flags; +} + + +int GetAnimationEvent( void *pmodel, entvars_t *pev, MonsterEvent_t *pMonsterEvent, float flStart, float flEnd, int index ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq || !pMonsterEvent ) + return 0; + + int events = 0; + + mstudioseqdesc_t *pseqdesc; + mstudioevent_t *pevent; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + pevent = (mstudioevent_t *)((byte *)pstudiohdr + pseqdesc->eventindex); + + if (pseqdesc->numevents == 0 || index > pseqdesc->numevents ) + return 0; + + if (pseqdesc->numframes > 1) + { + flStart *= (pseqdesc->numframes - 1) / 256.0; + flEnd *= (pseqdesc->numframes - 1) / 256.0; + } + else + { + flStart = 0; + flEnd = 1.0; + } + + for (; index < pseqdesc->numevents; index++) + { + // Don't send client-side events to the server AI + if ( pevent[index].event >= EVENT_CLIENT ) + continue; + + if ( (pevent[index].frame >= flStart && pevent[index].frame < flEnd) || + ((pseqdesc->flags & STUDIO_LOOPING) && flEnd >= pseqdesc->numframes - 1 && pevent[index].frame < flEnd - pseqdesc->numframes + 1) ) + { + pMonsterEvent->event = pevent[index].event; + pMonsterEvent->options = pevent[index].options; + return index + 1; + } + } + return 0; +} + +float SetController( void *pmodel, entvars_t *pev, int iController, float flValue ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return flValue; + + mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)pstudiohdr + pstudiohdr->bonecontrollerindex); + + // find first controller that matches the index + int i; + for ( i = 0; i < pstudiohdr->numbonecontrollers; i++, pbonecontroller++) + { + if (pbonecontroller->index == iController) + break; + } + if (i >= pstudiohdr->numbonecontrollers) + return flValue; + + // wrap 0..360 if it's a rotational controller + + if (pbonecontroller->type & (STUDIO_XR | STUDIO_YR | STUDIO_ZR)) + { + // ugly hack, invert value if end < start + if (pbonecontroller->end < pbonecontroller->start) + flValue = -flValue; + + // does the controller not wrap? + if (pbonecontroller->start + 359.0 >= pbonecontroller->end) + { + if (flValue > ((pbonecontroller->start + pbonecontroller->end) / 2.0) + 180) + flValue = flValue - 360; + if (flValue < ((pbonecontroller->start + pbonecontroller->end) / 2.0) - 180) + flValue = flValue + 360; + } + else + { + if (flValue > 360) + flValue = flValue - (int)(flValue / 360.0) * 360.0; + else if (flValue < 0) + flValue = flValue + (int)((flValue / -360.0) + 1) * 360.0; + } + } + + int setting = 255 * (flValue - pbonecontroller->start) / (pbonecontroller->end - pbonecontroller->start); + + if (setting < 0) setting = 0; + if (setting > 255) setting = 255; + pev->controller[iController] = setting; + + return setting * (1.0 / 255.0) * (pbonecontroller->end - pbonecontroller->start) + pbonecontroller->start; +} + + +float SetBlending( void *pmodel, entvars_t *pev, int iBlender, float flValue ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return flValue; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + if (pseqdesc->blendtype[iBlender] == 0) + return flValue; + + if (pseqdesc->blendtype[iBlender] & (STUDIO_XR | STUDIO_YR | STUDIO_ZR)) + { + // ugly hack, invert value if end < start + if (pseqdesc->blendend[iBlender] < pseqdesc->blendstart[iBlender]) + flValue = -flValue; + + // does the controller not wrap? + if (pseqdesc->blendstart[iBlender] + 359.0 >= pseqdesc->blendend[iBlender]) + { + if (flValue > ((pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender]) / 2.0) + 180) + flValue = flValue - 360; + if (flValue < ((pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender]) / 2.0) - 180) + flValue = flValue + 360; + } + } + + int setting = 255 * (flValue - pseqdesc->blendstart[iBlender]) / (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender]); + + if (setting < 0) setting = 0; + if (setting > 255) setting = 255; + + pev->blending[iBlender] = setting; + + return setting * (1.0 / 255.0) * (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender]) + pseqdesc->blendstart[iBlender]; +} + + + + +int FindTransition( void *pmodel, int iEndingAnim, int iGoalAnim, int *piDir ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return iGoalAnim; + + mstudioseqdesc_t *pseqdesc; + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + // bail if we're going to or from a node 0 + if (pseqdesc[iEndingAnim].entrynode == 0 || pseqdesc[iGoalAnim].entrynode == 0) + { + return iGoalAnim; + } + + int iEndNode; + + // ALERT( at_console, "from %d to %d: ", pEndNode->iEndNode, pGoalNode->iStartNode ); + + if (*piDir > 0) + { + iEndNode = pseqdesc[iEndingAnim].exitnode; + } + else + { + iEndNode = pseqdesc[iEndingAnim].entrynode; + } + + if (iEndNode == pseqdesc[iGoalAnim].entrynode) + { + *piDir = 1; + return iGoalAnim; + } + + byte *pTransition = ((byte *)pstudiohdr + pstudiohdr->transitionindex); + + int iInternNode = pTransition[(iEndNode-1)*pstudiohdr->numtransitions + (pseqdesc[iGoalAnim].entrynode-1)]; + + if (iInternNode == 0) + return iGoalAnim; + + int i; + + // look for someone going + for (i = 0; i < pstudiohdr->numseq; i++) + { + if (pseqdesc[i].entrynode == iEndNode && pseqdesc[i].exitnode == iInternNode) + { + *piDir = 1; + return i; + } + if (pseqdesc[i].nodeflags) + { + if (pseqdesc[i].exitnode == iEndNode && pseqdesc[i].entrynode == iInternNode) + { + *piDir = -1; + return i; + } + } + } + + ALERT( at_console, "error in transition graph" ); + return iGoalAnim; +} + +void SetBodygroup( void *pmodel, entvars_t *pev, int iGroup, int iValue ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return; + + if (iGroup > pstudiohdr->numbodyparts) + return; + + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex) + iGroup; + + if (iValue >= pbodypart->nummodels) + return; + + int iCurrent = (pev->body / pbodypart->base) % pbodypart->nummodels; + + pev->body = (pev->body - (iCurrent * pbodypart->base) + (iValue * pbodypart->base)); +} + + +int GetBodygroup( void *pmodel, entvars_t *pev, int iGroup ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + if (iGroup > pstudiohdr->numbodyparts) + return 0; + + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex) + iGroup; + + if (pbodypart->nummodels <= 1) + return 0; + + int iCurrent = (pev->body / pbodypart->base) % pbodypart->nummodels; + + return iCurrent; +} diff --git a/ricochet/dlls/animation.h b/ricochet/dlls/animation.h new file mode 100644 index 0000000..3da74cb --- /dev/null +++ b/ricochet/dlls/animation.h @@ -0,0 +1,47 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef ANIMATION_H +#define ANIMATION_H + +#define ACTIVITY_NOT_AVAILABLE -1 + +#ifndef MONSTEREVENT_H +#include "monsterevent.h" +#endif + +extern int IsSoundEvent( int eventNumber ); + +int LookupActivity( void *pmodel, entvars_t *pev, int activity ); +int LookupActivityHeaviest( void *pmodel, entvars_t *pev, int activity ); +int LookupSequence( void *pmodel, const char *label ); +void GetSequenceInfo( void *pmodel, entvars_t *pev, float *pflFrameRate, float *pflGroundSpeed ); +int GetSequenceFlags( void *pmodel, entvars_t *pev ); +int LookupAnimationEvents( void *pmodel, entvars_t *pev, float flStart, float flEnd ); +float SetController( void *pmodel, entvars_t *pev, int iController, float flValue ); +float SetBlending( void *pmodel, entvars_t *pev, int iBlender, float flValue ); +void GetEyePosition( void *pmodel, float *vecEyePosition ); +void SequencePrecache( void *pmodel, const char *pSequenceName ); +int FindTransition( void *pmodel, int iEndingAnim, int iGoalAnim, int *piDir ); +void SetBodygroup( void *pmodel, entvars_t *pev, int iGroup, int iValue ); +int GetBodygroup( void *pmodel, entvars_t *pev, int iGroup ); + +int GetAnimationEvent( void *pmodel, entvars_t *pev, MonsterEvent_t *pMonsterEvent, float flStart, float flEnd, int index ); +int ExtractBbox( void *pmodel, int sequence, float *mins, float *maxs ); + +// From /engine/studio.h +#define STUDIO_LOOPING 0x0001 + + +#endif //ANIMATION_H diff --git a/ricochet/dlls/basemonster.h b/ricochet/dlls/basemonster.h new file mode 100644 index 0000000..ab516a1 --- /dev/null +++ b/ricochet/dlls/basemonster.h @@ -0,0 +1,94 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef BASEMONSTER_H +#define BASEMONSTER_H + +class CBaseMonster : public CBaseToggle +{ +public: + Activity m_Activity;// what the monster is doing (animation) + Activity m_IdealActivity;// monster should switch to this activity + int m_LastHitGroup; // the last body region that took damage + int m_bitsDamageType; // what types of damage has monster (player) taken + BYTE m_rgbTimeBasedDamage[CDMG_TIMEBASED]; + MONSTERSTATE m_MonsterState;// monster's current state + MONSTERSTATE m_IdealMonsterState;// monster should change to this state + int m_afConditions; + int m_afMemory; + float m_flNextAttack; // cannot attack again until this time + EHANDLE m_hEnemy; // the entity that the monster is fighting. + EHANDLE m_hTargetEnt; // the entity that the monster is trying to reach + float m_flFieldOfView;// width of monster's field of view ( dot product ) + int m_bloodColor; // color of blood particless + Vector m_HackedGunPos; // HACK until we can query end of gun + Vector m_vecEnemyLKP;// last known position of enemy. (enemy's origin) + + + void KeyValue( KeyValueData *pkvd ); + + void MakeIdealYaw( Vector vecTarget ); + virtual float ChangeYaw ( int speed ); + virtual BOOL HasHumanGibs( void ); + virtual BOOL HasAlienGibs( void ); + virtual void FadeMonster( void ); // Called instead of GibMonster() when gibs are disabled + virtual void GibMonster( void ); + virtual Activity GetDeathActivity ( void ); + Activity GetSmallFlinchActivity( void ); + virtual void BecomeDead( void ); + BOOL ShouldGibMonster( int iGib ); + void CallGibMonster( void ); + virtual BOOL ShouldFadeOnDeath( void ); + BOOL FCheckAITrigger( void );// checks and, if necessary, fires the monster's trigger target. + virtual int IRelationship ( CBaseEntity *pTarget ); + virtual int TakeHealth( float flHealth, int bitsDamageType ); + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + int DeadTakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + float DamageForce( float damage ); + virtual void Killed( entvars_t *pevAttacker, int iGib ); + virtual void PainSound ( void ) { return; }; + + void RadiusDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ); + void RadiusDamage(Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ); + + inline void SetConditions( int iConditions ) { m_afConditions |= iConditions; } + inline void ClearConditions( int iConditions ) { m_afConditions &= ~iConditions; } + inline BOOL HasConditions( int iConditions ) { if ( m_afConditions & iConditions ) return TRUE; return FALSE; } + inline BOOL HasAllConditions( int iConditions ) { if ( (m_afConditions & iConditions) == iConditions ) return TRUE; return FALSE; } + + inline void Remember( int iMemory ) { m_afMemory |= iMemory; } + inline void Forget( int iMemory ) { m_afMemory &= ~iMemory; } + inline BOOL HasMemory( int iMemory ) { if ( m_afMemory & iMemory ) return TRUE; return FALSE; } + inline BOOL HasAllMemories( int iMemory ) { if ( (m_afMemory & iMemory) == iMemory ) return TRUE; return FALSE; } + + // This will stop animation until you call ResetSequenceInfo() at some point in the future + inline void StopAnimation( void ) { pev->framerate = 0; } + + virtual void ReportAIState( void ); + virtual void MonsterInitDead( void ); // Call after animation/pose is set up + void EXPORT CorpseFallThink( void ); + + virtual void Look ( int iDistance );// basic sight function for monsters + virtual CBaseEntity* BestVisibleEnemy ( void );// finds best visible enemy for attack + CBaseEntity *CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ); + virtual BOOL FInViewCone ( CBaseEntity *pEntity );// see if pEntity is in monster's view cone + virtual BOOL FInViewCone ( Vector *pOrigin );// see if given location is in monster's view cone + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + void MakeDamageBloodDecal ( int cCount, float flNoise, TraceResult *ptr, const Vector &vecDir ); + virtual BOOL IsAlive( void ) { return (pev->deadflag != DEAD_DEAD); } + +}; + + +#endif diff --git a/ricochet/dlls/bmodels.cpp b/ricochet/dlls/bmodels.cpp new file mode 100644 index 0000000..a546c0c --- /dev/null +++ b/ricochet/dlls/bmodels.cpp @@ -0,0 +1,958 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== bmodels.cpp ======================================================== + + spawn, think, and use functions for entities that use brush models + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "doors.h" + +extern DLL_GLOBAL Vector g_vecAttackDir; + +#define SF_BRUSH_ACCDCC 16// brush should accelerate and decelerate when toggled +#define SF_BRUSH_HURT 32// rotating brush that inflicts pain based on rotation speed +#define SF_ROTATING_NOT_SOLID 64 // some special rotating objects are not solid. + +// covering cheesy noise1, noise2, & noise3 fields so they make more sense (for rotating fans) +#define noiseStart noise1 +#define noiseStop noise2 +#define noiseRunning noise3 + +#define SF_PENDULUM_SWING 2 // spawnflag that makes a pendulum a rope swing. +// +// BModelOrigin - calculates origin of a bmodel from absmin/size because all bmodel origins are 0 0 0 +// +Vector VecBModelOrigin( entvars_t* pevBModel ) +{ + return pevBModel->absmin + ( pevBModel->size * 0.5 ); +} + +// =================== FUNC_WALL ============================================== + +/*QUAKED func_wall (0 .5 .8) ? +This is just a solid wall if not inhibited +*/ +class CFuncWall : public CBaseEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + // Bmodels don't go across transitions + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +LINK_ENTITY_TO_CLASS( func_wall, CFuncWall ); + +void CFuncWall :: Spawn( void ) +{ + pev->angles = g_vecZero; + pev->movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + pev->solid = SOLID_BSP; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + // If it can't move/go away, it's really part of the world + pev->flags |= FL_WORLDBRUSH; +} + + +void CFuncWall :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( ShouldToggle( useType, (int)(pev->frame)) ) + pev->frame = 1 - pev->frame; +} + + +#define SF_WALL_START_OFF 0x0001 + +class CFuncWallToggle : public CFuncWall +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void TurnOff( void ); + void TurnOn( void ); + BOOL IsOn( void ); +}; + +LINK_ENTITY_TO_CLASS( func_wall_toggle, CFuncWallToggle ); + +void CFuncWallToggle :: Spawn( void ) +{ + CFuncWall::Spawn(); + if ( pev->spawnflags & SF_WALL_START_OFF ) + TurnOff(); +} + + +void CFuncWallToggle :: TurnOff( void ) +{ + pev->solid = SOLID_NOT; + pev->effects |= EF_NODRAW; + UTIL_SetOrigin( pev, pev->origin ); +} + + +void CFuncWallToggle :: TurnOn( void ) +{ + pev->solid = SOLID_BSP; + pev->effects &= ~EF_NODRAW; + UTIL_SetOrigin( pev, pev->origin ); +} + + +BOOL CFuncWallToggle :: IsOn( void ) +{ + if ( pev->solid == SOLID_NOT ) + return FALSE; + return TRUE; +} + + +void CFuncWallToggle :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int status = IsOn(); + + if ( ShouldToggle( useType, status ) ) + { + if ( status ) + TurnOff(); + else + TurnOn(); + } +} + + +#define SF_CONVEYOR_VISUAL 0x0001 +#define SF_CONVEYOR_NOTSOLID 0x0002 + +class CFuncConveyor : public CFuncWall +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void UpdateSpeed( float speed ); +}; + +LINK_ENTITY_TO_CLASS( func_conveyor, CFuncConveyor ); +void CFuncConveyor :: Spawn( void ) +{ + SetMovedir( pev ); + CFuncWall::Spawn(); + + if ( !(pev->spawnflags & SF_CONVEYOR_VISUAL) ) + SetBits( pev->flags, FL_CONVEYOR ); + + // HACKHACK - This is to allow for some special effects + if ( pev->spawnflags & SF_CONVEYOR_NOTSOLID ) + { + pev->solid = SOLID_NOT; + pev->skin = 0; // Don't want the engine thinking we've got special contents on this brush + } + + if ( pev->speed == 0 ) + pev->speed = 100; + + UpdateSpeed( pev->speed ); +} + + +// HACKHACK -- This is ugly, but encode the speed in the rendercolor to avoid adding more data to the network stream +void CFuncConveyor :: UpdateSpeed( float speed ) +{ + // Encode it as an integer with 4 fractional bits + int speedCode = (int)(fabs(speed) * 16.0); + + if ( speed < 0 ) + pev->rendercolor.x = 1; + else + pev->rendercolor.x = 0; + + pev->rendercolor.y = (speedCode >> 8); + pev->rendercolor.z = (speedCode & 0xFF); +} + + +void CFuncConveyor :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + pev->speed = -pev->speed; + UpdateSpeed( pev->speed ); +} + + + +// =================== FUNC_ILLUSIONARY ============================================== + + +/*QUAKED func_illusionary (0 .5 .8) ? +A simple entity that looks solid but lets you walk through it. +*/ +class CFuncIllusionary : public CBaseToggle +{ +public: + void Spawn( void ); + void EXPORT SloshTouch( CBaseEntity *pOther ); + void KeyValue( KeyValueData *pkvd ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +LINK_ENTITY_TO_CLASS( func_illusionary, CFuncIllusionary ); + +void CFuncIllusionary :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "skin"))//skin is used for content type + { + pev->skin = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CFuncIllusionary :: Spawn( void ) +{ + pev->angles = g_vecZero; + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT;// always solid_not + SET_MODEL( ENT(pev), STRING(pev->model) ); + + // I'd rather eat the network bandwidth of this than figure out how to save/restore + // these entities after they have been moved to the client, or respawn them ala Quake + // Perhaps we can do this in deathmatch only. + // MAKE_STATIC(ENT(pev)); +} + + +// ------------------------------------------------------------------------------- +// +// Monster only clip brush +// +// This brush will be solid for any entity who has the FL_MONSTERCLIP flag set +// in pev->flags +// +// otherwise it will be invisible and not solid. This can be used to keep +// specific monsters out of certain areas +// +// ------------------------------------------------------------------------------- +class CFuncMonsterClip : public CFuncWall +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) {} // Clear out func_wall's use function +}; + +LINK_ENTITY_TO_CLASS( func_monsterclip, CFuncMonsterClip ); + +void CFuncMonsterClip::Spawn( void ) +{ + CFuncWall::Spawn(); + if ( CVAR_GET_FLOAT("showtriggers") == 0 ) + pev->effects = EF_NODRAW; + pev->flags |= FL_MONSTERCLIP; +} + + +// =================== FUNC_ROTATING ============================================== +class CFuncRotating : public CBaseEntity +{ +public: + // basic functions + void Spawn( void ); + void Precache( void ); + void EXPORT SpinUp ( void ); + void EXPORT SpinDown ( void ); + void KeyValue( KeyValueData* pkvd); + void EXPORT HurtTouch ( CBaseEntity *pOther ); + void EXPORT RotatingUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Rotate( void ); + void RampPitchVol (int fUp ); + void Blocked( CBaseEntity *pOther ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flFanFriction; + float m_flAttenuation; + float m_flVolume; + float m_pitch; + int m_sounds; +}; + +TYPEDESCRIPTION CFuncRotating::m_SaveData[] = +{ + DEFINE_FIELD( CFuncRotating, m_flFanFriction, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_flAttenuation, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_flVolume, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_pitch, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_sounds, FIELD_INTEGER ) +}; + +IMPLEMENT_SAVERESTORE( CFuncRotating, CBaseEntity ); + + +LINK_ENTITY_TO_CLASS( func_rotating, CFuncRotating ); + +void CFuncRotating :: KeyValue( KeyValueData* pkvd) +{ + if (FStrEq(pkvd->szKeyName, "fanfriction")) + { + m_flFanFriction = atof(pkvd->szValue)/100; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "Volume")) + { + m_flVolume = atof(pkvd->szValue)/10.0; + + if (m_flVolume > 1.0) + m_flVolume = 1.0; + if (m_flVolume < 0.0) + m_flVolume = 0.0; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spawnorigin")) + { + Vector tmp; + UTIL_StringToVector( (float *)tmp, pkvd->szValue ); + if ( tmp != g_vecZero ) + pev->origin = tmp; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +/*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS +You need to have an origin brush as part of this entity. The +center of that brush will be +the point around which it is rotated. It will rotate around the Z +axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"speed" determines how fast it moves; default value is 100. +"dmg" damage to inflict when blocked (2 default) + +REVERSE will cause the it to rotate in the opposite direction. +*/ + + +void CFuncRotating :: Spawn( ) +{ + // set final pitch. Must not be PITCH_NORM, since we + // plan on pitch shifting later. + + m_pitch = PITCH_NORM - 1; + + // maintain compatibility with previous maps + if (m_flVolume == 0.0) + m_flVolume = 1.0; + + // if the designer didn't set a sound attenuation, default to one. + m_flAttenuation = ATTN_NORM; + + if ( FBitSet ( pev->spawnflags, SF_BRUSH_ROTATE_SMALLRADIUS) ) + { + m_flAttenuation = ATTN_IDLE; + } + else if ( FBitSet ( pev->spawnflags, SF_BRUSH_ROTATE_MEDIUMRADIUS) ) + { + m_flAttenuation = ATTN_STATIC; + } + else if ( FBitSet ( pev->spawnflags, SF_BRUSH_ROTATE_LARGERADIUS) ) + { + m_flAttenuation = ATTN_NORM; + } + + // prevent divide by zero if level designer forgets friction! + if ( m_flFanFriction == 0 ) + { + m_flFanFriction = 1; + } + + if ( FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_Z_AXIS) ) + pev->movedir = Vector(0,0,1); + else if ( FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_X_AXIS) ) + pev->movedir = Vector(1,0,0); + else + pev->movedir = Vector(0,1,0); // y-axis + + // check for reverse rotation + if ( FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_BACKWARDS) ) + pev->movedir = pev->movedir * -1; + + // some rotating objects like fake volumetric lights will not be solid. + if ( FBitSet(pev->spawnflags, SF_ROTATING_NOT_SOLID) ) + { + pev->solid = SOLID_NOT; + pev->skin = CONTENTS_EMPTY; + pev->movetype = MOVETYPE_PUSH; + } + else + { + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + } + + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + SetUse( &CFuncRotating::RotatingUse ); + // did level designer forget to assign speed? + if (pev->speed <= 0) + pev->speed = 0; + + // Removed this per level designers request. -- JAY + // if (pev->dmg == 0) + // pev->dmg = 2; + + // instant-use brush? + if ( FBitSet( pev->spawnflags, SF_BRUSH_ROTATE_INSTANT) ) + { + SetThink( &CFuncRotating::SUB_CallUseToggle ); + pev->nextthink = pev->ltime + 1.5; // leave a magic delay for client to start up + } + // can this brush inflict pain? + if ( FBitSet (pev->spawnflags, SF_BRUSH_HURT) ) + { + SetTouch( &CFuncRotating::HurtTouch ); + } + + Precache( ); +} + + +void CFuncRotating :: Precache( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + + // set up fan sounds + + if (!FStringNull( pev->message ) && strlen( szSoundFile ) > 0) + { + // if a path is set for a wave, use it + + PRECACHE_SOUND(szSoundFile); + + pev->noiseRunning = ALLOC_STRING(szSoundFile); + } else + { + // otherwise use preset sound + switch (m_sounds) + { + case 1: + PRECACHE_SOUND ("fans/fan1.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan1.wav"); + break; + case 2: + PRECACHE_SOUND ("fans/fan2.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan2.wav"); + break; + case 3: + PRECACHE_SOUND ("fans/fan3.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan3.wav"); + break; + case 4: + PRECACHE_SOUND ("fans/fan4.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan4.wav"); + break; + case 5: + PRECACHE_SOUND ("fans/fan5.wav"); + pev->noiseRunning = ALLOC_STRING("fans/fan5.wav"); + break; + + case 0: + default: + if (!FStringNull( pev->message ) && strlen( szSoundFile ) > 0) + { + PRECACHE_SOUND(szSoundFile); + + pev->noiseRunning = ALLOC_STRING(szSoundFile); + break; + } else + { + pev->noiseRunning = ALLOC_STRING("common/null.wav"); + break; + } + } + } + + if (pev->avelocity != g_vecZero ) + { + // if fan was spinning, and we went through transition or save/restore, + // make sure we restart the sound. 1.5 sec delay is magic number. KDB + + SetThink ( &CFuncRotating::SpinUp ); + pev->nextthink = pev->ltime + 1.5; + } +} + + + +// +// Touch - will hurt others based on how fast the brush is spinning +// +void CFuncRotating :: HurtTouch ( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + // we can't hurt this thing, so we're not concerned with it + if ( !pevOther->takedamage ) + return; + + // calculate damage based on rotation speed + pev->dmg = pev->avelocity.Length() / 10; + + pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH); + + pevOther->velocity = (pevOther->origin - VecBModelOrigin(pev) ).Normalize() * pev->dmg; +} + +// +// RampPitchVol - ramp pitch and volume up to final values, based on difference +// between how fast we're going vs how fast we plan to go +// +#define FANPITCHMIN 30 +#define FANPITCHMAX 100 + +void CFuncRotating :: RampPitchVol (int fUp) +{ + + Vector vecAVel = pev->avelocity; + vec_t vecCur; + vec_t vecFinal; + float fpct; + float fvol; + float fpitch; + int pitch; + + // get current angular velocity + + vecCur = abs(vecAVel.x != 0 ? vecAVel.x : (vecAVel.y != 0 ? vecAVel.y : vecAVel.z)); + + // get target angular velocity + + vecFinal = (pev->movedir.x != 0 ? pev->movedir.x : (pev->movedir.y != 0 ? pev->movedir.y : pev->movedir.z)); + vecFinal *= pev->speed; + vecFinal = abs(vecFinal); + + // calc volume and pitch as % of final vol and pitch + + fpct = vecCur / vecFinal; +// if (fUp) +// fvol = m_flVolume * (0.5 + fpct/2.0); // spinup volume ramps up from 50% max vol +// else + fvol = m_flVolume * fpct; // slowdown volume ramps down to 0 + + fpitch = FANPITCHMIN + (FANPITCHMAX - FANPITCHMIN) * fpct; + + pitch = (int) fpitch; + if (pitch == PITCH_NORM) + pitch = PITCH_NORM-1; + + // change the fan's vol and pitch + + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + fvol, m_flAttenuation, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch); + +} + +// +// SpinUp - accelerates a non-moving func_rotating up to it's speed +// +void CFuncRotating :: SpinUp( void ) +{ + Vector vecAVel;//rotational velocity + + pev->nextthink = pev->ltime + 0.1; + pev->avelocity = pev->avelocity + ( pev->movedir * ( pev->speed * m_flFanFriction ) ); + + vecAVel = pev->avelocity;// cache entity's rotational velocity + + // if we've met or exceeded target speed, set target speed and stop thinking + if ( abs(vecAVel.x) >= abs(pev->movedir.x * pev->speed) && + abs(vecAVel.y) >= abs(pev->movedir.y * pev->speed) && + abs(vecAVel.z) >= abs(pev->movedir.z * pev->speed) ) + { + pev->avelocity = pev->movedir * pev->speed;// set speed in case we overshot + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + m_flVolume, m_flAttenuation, SND_CHANGE_PITCH | SND_CHANGE_VOL, FANPITCHMAX); + + SetThink( &CFuncRotating::Rotate ); + Rotate(); + } + else + { + RampPitchVol(TRUE); + } +} + +// +// SpinDown - decelerates a moving func_rotating to a standstill. +// +void CFuncRotating :: SpinDown( void ) +{ + Vector vecAVel;//rotational velocity + vec_t vecdir; + + pev->nextthink = pev->ltime + 0.1; + + pev->avelocity = pev->avelocity - ( pev->movedir * ( pev->speed * m_flFanFriction ) );//spin down slower than spinup + + vecAVel = pev->avelocity;// cache entity's rotational velocity + + if (pev->movedir.x != 0) + vecdir = pev->movedir.x; + else if (pev->movedir.y != 0) + vecdir = pev->movedir.y; + else + vecdir = pev->movedir.z; + + // if we've met or exceeded target speed, set target speed and stop thinking + // (note: must check for movedir > 0 or < 0) + if (((vecdir > 0) && (vecAVel.x <= 0 && vecAVel.y <= 0 && vecAVel.z <= 0)) || + ((vecdir < 0) && (vecAVel.x >= 0 && vecAVel.y >= 0 && vecAVel.z >= 0))) + { + pev->avelocity = g_vecZero;// set speed in case we overshot + + // stop sound, we're done + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning /* Stop */), + 0, 0, SND_STOP, m_pitch); + + SetThink( &CFuncRotating::Rotate ); + Rotate(); + } + else + { + RampPitchVol(FALSE); + } +} + +void CFuncRotating :: Rotate( void ) +{ + pev->nextthink = pev->ltime + 10; +} + +//========================================================= +// Rotating Use - when a rotating brush is triggered +//========================================================= +void CFuncRotating :: RotatingUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // is this a brush that should accelerate and decelerate when turned on/off (fan)? + if ( FBitSet ( pev->spawnflags, SF_BRUSH_ACCDCC ) ) + { + // fan is spinning, so stop it. + if ( pev->avelocity != g_vecZero ) + { + SetThink ( &CFuncRotating::SpinDown ); + //EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, (char *)STRING(pev->noiseStop), + // m_flVolume, m_flAttenuation, 0, m_pitch); + + pev->nextthink = pev->ltime + 0.1; + } + else// fan is not moving, so start it + { + SetThink ( &CFuncRotating::SpinUp ); + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + 0.01, m_flAttenuation, 0, FANPITCHMIN); + + pev->nextthink = pev->ltime + 0.1; + } + } + else if ( !FBitSet ( pev->spawnflags, SF_BRUSH_ACCDCC ) )//this is a normal start/stop brush. + { + if ( pev->avelocity != g_vecZero ) + { + // play stopping sound here + SetThink ( &CFuncRotating::SpinDown ); + + // EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, (char *)STRING(pev->noiseStop), + // m_flVolume, m_flAttenuation, 0, m_pitch); + + pev->nextthink = pev->ltime + 0.1; + // pev->avelocity = g_vecZero; + } + else + { + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + m_flVolume, m_flAttenuation, 0, FANPITCHMAX); + pev->avelocity = pev->movedir * pev->speed; + + SetThink( &CFuncRotating::Rotate ); + Rotate(); + } + } +} + + +// +// RotatingBlocked - An entity has blocked the brush +// +void CFuncRotating :: Blocked( CBaseEntity *pOther ) + +{ + pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH); +} + + + + + + +//#endif + + +class CPendulum : public CBaseEntity +{ +public: + void Spawn ( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT Swing( void ); + void EXPORT PendulumUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Stop( void ); + void Touch( CBaseEntity *pOther ); + void EXPORT RopeTouch ( CBaseEntity *pOther );// this touch func makes the pendulum a rope + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + void Blocked( CBaseEntity *pOther ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_accel; // Acceleration + float m_distance; // + float m_time; + float m_damp; + float m_maxSpeed; + float m_dampSpeed; + vec3_t m_center; + vec3_t m_start; +}; + +LINK_ENTITY_TO_CLASS( func_pendulum, CPendulum ); + +TYPEDESCRIPTION CPendulum::m_SaveData[] = +{ + DEFINE_FIELD( CPendulum, m_accel, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_distance, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_time, FIELD_TIME ), + DEFINE_FIELD( CPendulum, m_damp, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_maxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_dampSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_center, FIELD_VECTOR ), + DEFINE_FIELD( CPendulum, m_start, FIELD_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CPendulum, CBaseEntity ); + + + +void CPendulum :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "distance")) + { + m_distance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damp")) + { + m_damp = atof(pkvd->szValue) * 0.001; + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +void CPendulum :: Spawn( void ) +{ + // set the axis of rotation + CBaseToggle :: AxisDir( pev ); + + if ( FBitSet (pev->spawnflags, SF_DOOR_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + if ( m_distance == 0 ) + return; + + if (pev->speed == 0) + pev->speed = 100; + + m_accel = (pev->speed * pev->speed) / (2 * fabs(m_distance)); // Calculate constant acceleration from speed and distance + m_maxSpeed = pev->speed; + m_start = pev->angles; + m_center = pev->angles + (m_distance * 0.5) * pev->movedir; + + if ( FBitSet( pev->spawnflags, SF_BRUSH_ROTATE_INSTANT) ) + { + SetThink( &CPendulum::SUB_CallUseToggle ); + pev->nextthink = gpGlobals->time + 0.1; + } + pev->speed = 0; + SetUse( &CPendulum::PendulumUse ); + + if ( FBitSet( pev->spawnflags, SF_PENDULUM_SWING ) ) + { + SetTouch ( &CPendulum::RopeTouch ); + } +} + + +void CPendulum :: PendulumUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->speed ) // Pendulum is moving, stop it and auto-return if necessary + { + if ( FBitSet( pev->spawnflags, SF_PENDULUM_AUTO_RETURN ) ) + { + float delta; + + delta = CBaseToggle :: AxisDelta( pev->spawnflags, pev->angles, m_start ); + + pev->avelocity = m_maxSpeed * pev->movedir; + pev->nextthink = pev->ltime + (delta / m_maxSpeed); + SetThink( &CPendulum::Stop ); + } + else + { + pev->speed = 0; // Dead stop + SetThink( NULL ); + pev->avelocity = g_vecZero; + } + } + else + { + pev->nextthink = pev->ltime + 0.1; // Start the pendulum moving + m_time = gpGlobals->time; // Save time to calculate dt + SetThink( &CPendulum::Swing ); + m_dampSpeed = m_maxSpeed; + } +} + + +void CPendulum :: Stop( void ) +{ + pev->angles = m_start; + pev->speed = 0; + SetThink( NULL ); + pev->avelocity = g_vecZero; +} + + +void CPendulum::Blocked( CBaseEntity *pOther ) +{ + m_time = gpGlobals->time; +} + + +void CPendulum :: Swing( void ) +{ + float delta, dt; + + delta = CBaseToggle :: AxisDelta( pev->spawnflags, pev->angles, m_center ); + dt = gpGlobals->time - m_time; // How much time has passed? + m_time = gpGlobals->time; // Remember the last time called + + if ( delta > 0 && m_accel > 0 ) + pev->speed -= m_accel * dt; // Integrate velocity + else + pev->speed += m_accel * dt; + + if ( pev->speed > m_maxSpeed ) + pev->speed = m_maxSpeed; + else if ( pev->speed < -m_maxSpeed ) + pev->speed = -m_maxSpeed; + // scale the destdelta vector by the time spent traveling to get velocity + pev->avelocity = pev->speed * pev->movedir; + + // Call this again + pev->nextthink = pev->ltime + 0.1; + + if ( m_damp ) + { + m_dampSpeed -= m_damp * m_dampSpeed * dt; + if ( m_dampSpeed < 30.0 ) + { + pev->angles = m_center; + pev->speed = 0; + SetThink( NULL ); + pev->avelocity = g_vecZero; + } + else if ( pev->speed > m_dampSpeed ) + pev->speed = m_dampSpeed; + else if ( pev->speed < -m_dampSpeed ) + pev->speed = -m_dampSpeed; + + } +} + + +void CPendulum :: Touch ( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + if ( pev->dmg <= 0 ) + return; + + // we can't hurt this thing, so we're not concerned with it + if ( !pevOther->takedamage ) + return; + + // calculate damage based on rotation speed + float damage = pev->dmg * pev->speed * 0.01; + + if ( damage < 0 ) + damage = -damage; + + pOther->TakeDamage( pev, pev, damage, DMG_CRUSH ); + + pevOther->velocity = (pevOther->origin - VecBModelOrigin(pev) ).Normalize() * damage; +} + +void CPendulum :: RopeTouch ( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + if ( !pOther->IsPlayer() ) + {// not a player! + ALERT ( at_console, "Not a client\n" ); + return; + } + + if ( ENT(pevOther) == pev->enemy ) + {// this player already on the rope. + return; + } + + pev->enemy = pOther->edict(); + pevOther->velocity = g_vecZero; + pevOther->movetype = MOVETYPE_NONE; +} + + diff --git a/ricochet/dlls/buttons.cpp b/ricochet/dlls/buttons.cpp new file mode 100644 index 0000000..eb5a3e9 --- /dev/null +++ b/ricochet/dlls/buttons.cpp @@ -0,0 +1,1279 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== buttons.cpp ======================================================== + + button-related code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "doors.h" + +#if !defined ( _WIN32 ) +#include // memset()))) +#endif + +#define SF_BUTTON_DONTMOVE 1 +#define SF_ROTBUTTON_NOTSOLID 1 +#define SF_BUTTON_TOGGLE 32 // button stays pushed until reactivated +#define SF_BUTTON_SPARK_IF_OFF 64 // button sparks in OFF state +#define SF_BUTTON_TOUCH_ONLY 256 // button only fires as a result of USE key. + +#define SF_GLOBAL_SET 1 // Set global state to initial state on spawn + +class CEnvGlobal : public CPointEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + string_t m_globalstate; + int m_triggermode; + int m_initialstate; +}; + +TYPEDESCRIPTION CEnvGlobal::m_SaveData[] = +{ + DEFINE_FIELD( CEnvGlobal, m_globalstate, FIELD_STRING ), + DEFINE_FIELD( CEnvGlobal, m_triggermode, FIELD_INTEGER ), + DEFINE_FIELD( CEnvGlobal, m_initialstate, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CEnvGlobal, CBaseEntity ); + +LINK_ENTITY_TO_CLASS( env_global, CEnvGlobal ); + +void CEnvGlobal::KeyValue( KeyValueData *pkvd ) +{ + pkvd->fHandled = TRUE; + + if ( FStrEq(pkvd->szKeyName, "globalstate") ) // State name + m_globalstate = ALLOC_STRING( pkvd->szValue ); + else if ( FStrEq(pkvd->szKeyName, "triggermode") ) + m_triggermode = atoi( pkvd->szValue ); + else if ( FStrEq(pkvd->szKeyName, "initialstate") ) + m_initialstate = atoi( pkvd->szValue ); + else + CPointEntity::KeyValue( pkvd ); +} + +void CEnvGlobal::Spawn( void ) +{ + if ( !m_globalstate ) + { + REMOVE_ENTITY( ENT(pev) ); + return; + } + if ( FBitSet( pev->spawnflags, SF_GLOBAL_SET ) ) + { + if ( !gGlobalState.EntityInTable( m_globalstate ) ) + gGlobalState.EntityAdd( m_globalstate, gpGlobals->mapname, (GLOBALESTATE)m_initialstate ); + } +} + + +void CEnvGlobal::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + GLOBALESTATE oldState = gGlobalState.EntityGetState( m_globalstate ); + GLOBALESTATE newState; + + switch( m_triggermode ) + { + case 0: + newState = GLOBAL_OFF; + break; + + case 1: + newState = GLOBAL_ON; + break; + + case 2: + newState = GLOBAL_DEAD; + break; + + default: + case 3: + if ( oldState == GLOBAL_ON ) + newState = GLOBAL_OFF; + else if ( oldState == GLOBAL_OFF ) + newState = GLOBAL_ON; + else + newState = oldState; + } + + if ( gGlobalState.EntityInTable( m_globalstate ) ) + gGlobalState.EntitySetState( m_globalstate, newState ); + else + gGlobalState.EntityAdd( m_globalstate, gpGlobals->mapname, newState ); +} + + + +TYPEDESCRIPTION CMultiSource::m_SaveData[] = +{ + //!!!BUGBUG FIX + DEFINE_ARRAY( CMultiSource, m_rgEntities, FIELD_EHANDLE, MS_MAX_TARGETS ), + DEFINE_ARRAY( CMultiSource, m_rgTriggered, FIELD_INTEGER, MS_MAX_TARGETS ), + DEFINE_FIELD( CMultiSource, m_iTotal, FIELD_INTEGER ), + DEFINE_FIELD( CMultiSource, m_globalstate, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CMultiSource, CBaseEntity ); + +LINK_ENTITY_TO_CLASS( multisource, CMultiSource ); +// +// Cache user-entity-field values until spawn is called. +// + +void CMultiSource::KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "killtarget") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + pkvd->fHandled = TRUE; + else if ( FStrEq(pkvd->szKeyName, "globalstate") ) + { + m_globalstate = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +#define SF_MULTI_INIT 1 + +void CMultiSource::Spawn() +{ + // set up think for later registration + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->nextthink = gpGlobals->time + 0.1; + pev->spawnflags |= SF_MULTI_INIT; // Until it's initialized + SetThink(&CMultiSource::Register); +} + +void CMultiSource::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int i = 0; + + // Find the entity in our list + while (i < m_iTotal) + if ( m_rgEntities[i++] == pCaller ) + break; + + // if we didn't find it, report error and leave + if (i > m_iTotal) + { + ALERT(at_console, "MultiSrc:Used by non member %s.\n", STRING(pCaller->pev->classname)); + return; + } + + // CONSIDER: a Use input to the multisource always toggles. Could check useType for ON/OFF/TOGGLE + + m_rgTriggered[i-1] ^= 1; + + // + if ( IsTriggered( pActivator ) ) + { + ALERT( at_aiconsole, "Multisource %s enabled (%d inputs)\n", STRING(pev->targetname), m_iTotal ); + USE_TYPE useType = USE_TOGGLE; + if ( m_globalstate ) + useType = USE_ON; + SUB_UseTargets( NULL, useType, 0 ); + } +} + + +BOOL CMultiSource::IsTriggered( CBaseEntity * ) +{ + // Is everything triggered? + int i = 0; + + // Still initializing? + if ( pev->spawnflags & SF_MULTI_INIT ) + return 0; + + while (i < m_iTotal) + { + if (m_rgTriggered[i] == 0) + break; + i++; + } + + if (i == m_iTotal) + { + if ( !m_globalstate || gGlobalState.EntityGetState( m_globalstate ) == GLOBAL_ON ) + return 1; + } + + return 0; +} + +void CMultiSource::Register(void) +{ + edict_t *pentTarget = NULL; + + m_iTotal = 0; + memset( m_rgEntities, 0, MS_MAX_TARGETS * sizeof(EHANDLE) ); + + SetThink(&CMultiSource::SUB_DoNothing); + + // search for all entities which target this multisource (pev->targetname) + + pentTarget = FIND_ENTITY_BY_STRING(NULL, "target", STRING(pev->targetname)); + + while (!FNullEnt(pentTarget) && (m_iTotal < MS_MAX_TARGETS)) + { + CBaseEntity *pTarget = CBaseEntity::Instance(pentTarget); + if ( pTarget ) + m_rgEntities[m_iTotal++] = pTarget; + + pentTarget = FIND_ENTITY_BY_STRING( pentTarget, "target", STRING(pev->targetname)); + } + + pentTarget = FIND_ENTITY_BY_STRING(NULL, "classname", "multi_manager"); + while (!FNullEnt(pentTarget) && (m_iTotal < MS_MAX_TARGETS)) + { + CBaseEntity *pTarget = CBaseEntity::Instance(pentTarget); + if ( pTarget && pTarget->HasTarget(pev->targetname) ) + m_rgEntities[m_iTotal++] = pTarget; + + pentTarget = FIND_ENTITY_BY_STRING( pentTarget, "classname", "multi_manager" ); + } + + pev->spawnflags &= ~SF_MULTI_INIT; +} + +// CBaseButton +TYPEDESCRIPTION CBaseButton::m_SaveData[] = +{ + DEFINE_FIELD( CBaseButton, m_fStayPushed, FIELD_BOOLEAN ), + DEFINE_FIELD( CBaseButton, m_fRotating, FIELD_BOOLEAN ), + + DEFINE_FIELD( CBaseButton, m_sounds, FIELD_INTEGER ), + DEFINE_FIELD( CBaseButton, m_bLockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_bLockedSentence, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_bUnlockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_bUnlockedSentence, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_strChangeTarget, FIELD_STRING ), +// DEFINE_FIELD( CBaseButton, m_ls, FIELD_??? ), // This is restored in Precache() +}; + + +IMPLEMENT_SAVERESTORE( CBaseButton, CBaseToggle ); + +void CBaseButton::Precache( void ) +{ + char *pszSound; + + if ( FBitSet ( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) )// this button should spark in OFF state + { + PRECACHE_SOUND ("buttons/spark1.wav"); + PRECACHE_SOUND ("buttons/spark2.wav"); + PRECACHE_SOUND ("buttons/spark3.wav"); + PRECACHE_SOUND ("buttons/spark4.wav"); + PRECACHE_SOUND ("buttons/spark5.wav"); + PRECACHE_SOUND ("buttons/spark6.wav"); + } + + // get door button sounds, for doors which require buttons to open + + if (m_bLockedSound) + { + pszSound = ButtonSound( (int)m_bLockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sLockedSound = ALLOC_STRING(pszSound); + } + + if (m_bUnlockedSound) + { + pszSound = ButtonSound( (int)m_bUnlockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sUnlockedSound = ALLOC_STRING(pszSound); + } + + // get sentence group names, for doors which are directly 'touched' to open + + switch (m_bLockedSentence) + { + case 1: m_ls.sLockedSentence = MAKE_STRING("NA"); break; // access denied + case 2: m_ls.sLockedSentence = MAKE_STRING("ND"); break; // security lockout + case 3: m_ls.sLockedSentence = MAKE_STRING("NF"); break; // blast door + case 4: m_ls.sLockedSentence = MAKE_STRING("NFIRE"); break; // fire door + case 5: m_ls.sLockedSentence = MAKE_STRING("NCHEM"); break; // chemical door + case 6: m_ls.sLockedSentence = MAKE_STRING("NRAD"); break; // radiation door + case 7: m_ls.sLockedSentence = MAKE_STRING("NCON"); break; // gen containment + case 8: m_ls.sLockedSentence = MAKE_STRING("NH"); break; // maintenance door + case 9: m_ls.sLockedSentence = MAKE_STRING("NG"); break; // broken door + + default: m_ls.sLockedSentence = 0; break; + } + + switch (m_bUnlockedSentence) + { + case 1: m_ls.sUnlockedSentence = MAKE_STRING("EA"); break; // access granted + case 2: m_ls.sUnlockedSentence = MAKE_STRING("ED"); break; // security door + case 3: m_ls.sUnlockedSentence = MAKE_STRING("EF"); break; // blast door + case 4: m_ls.sUnlockedSentence = MAKE_STRING("EFIRE"); break; // fire door + case 5: m_ls.sUnlockedSentence = MAKE_STRING("ECHEM"); break; // chemical door + case 6: m_ls.sUnlockedSentence = MAKE_STRING("ERAD"); break; // radiation door + case 7: m_ls.sUnlockedSentence = MAKE_STRING("ECON"); break; // gen containment + case 8: m_ls.sUnlockedSentence = MAKE_STRING("EH"); break; // maintenance door + + default: m_ls.sUnlockedSentence = 0; break; + } +} + +// +// Cache user-entity-field values until spawn is called. +// + +void CBaseButton::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "changetarget")) + { + m_strChangeTarget = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sound")) + { + m_bLockedSound = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sentence")) + { + m_bLockedSentence = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sound")) + { + m_bUnlockedSound = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sentence")) + { + m_bUnlockedSentence = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +// +// ButtonShot +// +int CBaseButton::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + BUTTON_CODE code = ButtonResponseToTouch(); + + if ( code == BUTTON_NOTHING ) + return 0; + // Temporarily disable the touch function, until movement is finished. + SetTouch( NULL ); + + m_hActivator = CBaseEntity::Instance( pevAttacker ); + if ( m_hActivator == NULL ) + return 0; + + if ( code == BUTTON_RETURN ) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + // Toggle buttons fire when they get back to their "home" position + if ( !(pev->spawnflags & SF_BUTTON_TOGGLE) ) + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + ButtonReturn(); + } + else // code == BUTTON_ACTIVATE + ButtonActivate( ); + + return 0; +} + +/*QUAKED func_button (0 .5 .8) ? +When a button is touched, it moves some distance in the direction of it's angle, +triggers all of it's targets, waits some time, then returns to it's original position +where it can be triggered again. + +"angle" determines the opening direction +"target" all entities with a matching targetname will be used +"speed" override the default 40 speed +"wait" override the default 1 second wait (-1 = never return) +"lip" override the default 4 pixel lip remaining at end of move +"health" if set, the button must be killed instead of touched +"sounds" +0) steam metal +1) wooden clunk +2) metallic click +3) in-out +*/ +LINK_ENTITY_TO_CLASS( func_button, CBaseButton ); + + +void CBaseButton::Spawn( ) +{ + char *pszSound; + + //---------------------------------------------------- + //determine sounds for buttons + //a sound of 0 should not make a sound + //---------------------------------------------------- + pszSound = ButtonSound( m_sounds ); + PRECACHE_SOUND(pszSound); + pev->noise = ALLOC_STRING(pszSound); + + Precache(); + + if ( FBitSet ( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) )// this button should spark in OFF state + { + SetThink ( &CBaseButton::ButtonSpark ); + pev->nextthink = gpGlobals->time + 0.5;// no hurry, make sure everything else spawns + } + + SetMovedir(pev); + + pev->movetype = MOVETYPE_PUSH; + pev->solid = SOLID_BSP; + SET_MODEL(ENT(pev), STRING(pev->model)); + + if (pev->speed == 0) + pev->speed = 40; + + if (pev->health > 0) + { + pev->takedamage = DAMAGE_YES; + } + + if (m_flWait == 0) + m_flWait = 1; + if (m_flLip == 0) + m_flLip = 4; + + m_toggle_state = TS_AT_BOTTOM; + m_vecPosition1 = pev->origin; + // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big + m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); + + + // Is this a non-moving button? + if ( ((m_vecPosition2 - m_vecPosition1).Length() < 1) || (pev->spawnflags & SF_BUTTON_DONTMOVE) ) + m_vecPosition2 = m_vecPosition1; + + m_fStayPushed = (m_flWait == -1 ? TRUE : FALSE); + m_fRotating = FALSE; + + // if the button is flagged for USE button activation only, take away it's touch function and add a use function + + if ( FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // touchable button + { + SetTouch( &CBaseButton::ButtonTouch ); + } + else + { + SetTouch ( NULL ); + SetUse ( &CBaseButton::ButtonUse ); + } +} + + +// Button sound table. +// Also used by CBaseDoor to get 'touched' door lock/unlock sounds + +char *ButtonSound( int sound ) +{ + char *pszSound; + + switch ( sound ) + { + case 0: pszSound = "common/null.wav"; break; + case 1: pszSound = "buttons/button1.wav"; break; + case 2: pszSound = "buttons/button2.wav"; break; + case 3: pszSound = "buttons/button3.wav"; break; + case 4: pszSound = "buttons/button4.wav"; break; + case 5: pszSound = "buttons/button5.wav"; break; + case 6: pszSound = "buttons/button6.wav"; break; + case 7: pszSound = "buttons/button7.wav"; break; + case 8: pszSound = "buttons/button8.wav"; break; + case 9: pszSound = "buttons/button9.wav"; break; + case 10: pszSound = "buttons/button10.wav"; break; + case 11: pszSound = "buttons/button11.wav"; break; + case 12: pszSound = "buttons/latchlocked1.wav"; break; + case 13: pszSound = "buttons/latchunlocked1.wav"; break; + case 14: pszSound = "buttons/lightswitch2.wav";break; + +// next 6 slots reserved for any additional sliding button sounds we may add + + case 21: pszSound = "buttons/lever1.wav"; break; + case 22: pszSound = "buttons/lever2.wav"; break; + case 23: pszSound = "buttons/lever3.wav"; break; + case 24: pszSound = "buttons/lever4.wav"; break; + case 25: pszSound = "buttons/lever5.wav"; break; + + default:pszSound = "buttons/button9.wav"; break; + } + + return pszSound; +} + +// +// Makes flagged buttons spark when turned off +// + +void DoSpark(entvars_t *pev, const Vector &location ) +{ + Vector tmp = location + pev->size * 0.5; + UTIL_Sparks( tmp ); + + float flVolume = RANDOM_FLOAT ( 0.25 , 0.75 ) * 0.4;//random volume range + switch ( (int)(RANDOM_FLOAT(0,1) * 6) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark1.wav", flVolume, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark2.wav", flVolume, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark3.wav", flVolume, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark4.wav", flVolume, ATTN_NORM); break; + case 4: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM); break; + case 5: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM); break; + } +} + +void CBaseButton::ButtonSpark ( void ) +{ + SetThink ( &CBaseButton::ButtonSpark ); + pev->nextthink = gpGlobals->time + ( 0.1 + RANDOM_FLOAT ( 0, 1.5 ) );// spark again at random interval + + DoSpark( pev, pev->mins ); +} + + +// +// Button's Use function +// +void CBaseButton::ButtonUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Ignore touches if button is moving, or pushed-in and waiting to auto-come-out. + // UNDONE: Should this use ButtonResponseToTouch() too? + if (m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN ) + return; + + m_hActivator = pActivator; + if ( m_toggle_state == TS_AT_TOP) + { + if (!m_fStayPushed && FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE)) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + //SUB_UseTargets( m_eoActivator ); + ButtonReturn(); + } + } + else + ButtonActivate( ); +} + + +CBaseButton::BUTTON_CODE CBaseButton::ButtonResponseToTouch( void ) +{ + // Ignore touches if button is moving, or pushed-in and waiting to auto-come-out. + if (m_toggle_state == TS_GOING_UP || + m_toggle_state == TS_GOING_DOWN || + (m_toggle_state == TS_AT_TOP && !m_fStayPushed && !FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE) ) ) + return BUTTON_NOTHING; + + if (m_toggle_state == TS_AT_TOP) + { + if((FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE) ) && !m_fStayPushed) + { + return BUTTON_RETURN; + } + } + else + return BUTTON_ACTIVATE; + + return BUTTON_NOTHING; +} + + +// +// Touching a button simply "activates" it. +// +void CBaseButton:: ButtonTouch( CBaseEntity *pOther ) +{ + // Ignore touches by anything but players + if (!FClassnameIs(pOther->pev, "player")) + return; + + m_hActivator = pOther; + + BUTTON_CODE code = ButtonResponseToTouch(); + + if ( code == BUTTON_NOTHING ) + return; + + if (!UTIL_IsMasterTriggered(m_sMaster, pOther)) + { + // play button locked sound + PlayLockSounds(pev, &m_ls, TRUE, TRUE); + return; + } + + // Temporarily disable the touch function, until movement is finished. + SetTouch( NULL ); + + if ( code == BUTTON_RETURN ) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + ButtonReturn(); + } + else // code == BUTTON_ACTIVATE + ButtonActivate( ); +} + +// +// Starts the button moving "in/up". +// +void CBaseButton::ButtonActivate( ) +{ + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + { + // button is locked, play locked sound + PlayLockSounds(pev, &m_ls, TRUE, TRUE); + return; + } + else + { + // button is unlocked, play unlocked sound + PlayLockSounds(pev, &m_ls, FALSE, TRUE); + } + + ASSERT(m_toggle_state == TS_AT_BOTTOM); + m_toggle_state = TS_GOING_UP; + + SetMoveDone( &CBaseButton::TriggerAndWait ); + if (!m_fRotating) + LinearMove( m_vecPosition2, pev->speed); + else + AngularMove( m_vecAngle2, pev->speed); +} + +// +// Button has reached the "in/up" position. Activate its "targets", and pause before "popping out". +// +void CBaseButton::TriggerAndWait( void ) +{ + ASSERT(m_toggle_state == TS_GOING_UP); + + if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + return; + + m_toggle_state = TS_AT_TOP; + + // If button automatically comes back out, start it moving out. + // Else re-instate touch method + if (m_fStayPushed || FBitSet ( pev->spawnflags, SF_BUTTON_TOGGLE ) ) + { + if ( !FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // this button only works if USED, not touched! + { + // ALL buttons are now use only + SetTouch ( NULL ); + } + else + SetTouch( &CBaseButton::ButtonTouch ); + } + else + { + pev->nextthink = pev->ltime + m_flWait; + SetThink( &CBaseButton::ButtonReturn ); + } + + pev->frame = 1; // use alternate textures + + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); +} + + +// +// Starts the button moving "out/down". +// +void CBaseButton::ButtonReturn( void ) +{ + ASSERT(m_toggle_state == TS_AT_TOP); + m_toggle_state = TS_GOING_DOWN; + + SetMoveDone( &CBaseButton::ButtonBackHome ); + if (!m_fRotating) + LinearMove( m_vecPosition1, pev->speed); + else + AngularMove( m_vecAngle1, pev->speed); + + pev->frame = 0; // use normal textures +} + + +// +// Button has returned to start state. Quiesce it. +// +void CBaseButton::ButtonBackHome( void ) +{ + ASSERT(m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_AT_BOTTOM; + + if ( FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE) ) + { + //EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + } + + + if (!FStringNull(pev->target)) + { + edict_t* pentTarget = NULL; + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->target)); + + if (FNullEnt(pentTarget)) + break; + + if (!FClassnameIs(pentTarget, "multisource")) + continue; + CBaseEntity *pTarget = CBaseEntity::Instance( pentTarget ); + + if ( pTarget ) + pTarget->Use( m_hActivator, this, USE_TOGGLE, 0 ); + } + } + +// Re-instate touch method, movement cycle is complete. + if ( !FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // this button only works if USED, not touched! + { + // All buttons are now use only + SetTouch ( NULL ); + } + else + SetTouch( &CBaseButton::ButtonTouch ); + +// reset think for a sparking button + if ( FBitSet ( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) ) + { + SetThink ( &CBaseButton::ButtonSpark ); + pev->nextthink = gpGlobals->time + 0.5;// no hurry. + } +} + + + +// +// Rotating button (aka "lever") +// +class CRotButton : public CBaseButton +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( func_rot_button, CRotButton ); + +void CRotButton::Spawn( void ) +{ + char *pszSound; + //---------------------------------------------------- + //determine sounds for buttons + //a sound of 0 should not make a sound + //---------------------------------------------------- + pszSound = ButtonSound( m_sounds ); + PRECACHE_SOUND(pszSound); + pev->noise = ALLOC_STRING(pszSound); + + // set the axis of rotation + CBaseToggle::AxisDir( pev ); + + // check for clockwise rotation + if ( FBitSet (pev->spawnflags, SF_DOOR_ROTATE_BACKWARDS) ) + pev->movedir = pev->movedir * -1; + + pev->movetype = MOVETYPE_PUSH; + + if ( pev->spawnflags & SF_ROTBUTTON_NOTSOLID ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + + SET_MODEL(ENT(pev), STRING(pev->model)); + + if (pev->speed == 0) + pev->speed = 40; + + if (m_flWait == 0) + m_flWait = 1; + + if (pev->health > 0) + { + pev->takedamage = DAMAGE_YES; + } + + m_toggle_state = TS_AT_BOTTOM; + m_vecAngle1 = pev->angles; + m_vecAngle2 = pev->angles + pev->movedir * m_flMoveDistance; + ASSERTSZ(m_vecAngle1 != m_vecAngle2, "rotating button start/end positions are equal"); + + m_fStayPushed = (m_flWait == -1 ? TRUE : FALSE); + m_fRotating = TRUE; + + // if the button is flagged for USE button activation only, take away it's touch function and add a use function + if ( !FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) + { + SetTouch ( NULL ); + SetUse ( &CRotButton::ButtonUse ); + } + else // touchable button + SetTouch( &CRotButton::ButtonTouch ); + + //SetTouch( ButtonTouch ); +} + + +// Make this button behave like a door (HACKHACK) +// This will disable use and make the button solid +// rotating buttons were made SOLID_NOT by default since their were some +// collision problems with them... +#define SF_MOMENTARY_DOOR 0x0001 + +class CMomentaryRotButton : public CBaseToggle +{ +public: + void Spawn ( void ); + void KeyValue( KeyValueData *pkvd ); + virtual int ObjectCaps( void ) + { + int flags = CBaseToggle :: ObjectCaps() & (~FCAP_ACROSS_TRANSITION); + if ( pev->spawnflags & SF_MOMENTARY_DOOR ) + return flags; + return flags | FCAP_CONTINUOUS_USE; + } + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Off( void ); + void EXPORT Return( void ); + void UpdateSelf( float value ); + void UpdateSelfReturn( float value ); + void UpdateAllButtons( float value, int start ); + + void PlaySound( void ); + void UpdateTarget( float value ); + + static CMomentaryRotButton *Instance( edict_t *pent ) { return (CMomentaryRotButton *)GET_PRIVATE(pent);}; + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_lastUsed; + int m_direction; + float m_returnSpeed; + vec3_t m_start; + vec3_t m_end; + int m_sounds; +}; +TYPEDESCRIPTION CMomentaryRotButton::m_SaveData[] = +{ + DEFINE_FIELD( CMomentaryRotButton, m_lastUsed, FIELD_INTEGER ), + DEFINE_FIELD( CMomentaryRotButton, m_direction, FIELD_INTEGER ), + DEFINE_FIELD( CMomentaryRotButton, m_returnSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CMomentaryRotButton, m_start, FIELD_VECTOR ), + DEFINE_FIELD( CMomentaryRotButton, m_end, FIELD_VECTOR ), + DEFINE_FIELD( CMomentaryRotButton, m_sounds, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CMomentaryRotButton, CBaseToggle ); + +LINK_ENTITY_TO_CLASS( momentary_rot_button, CMomentaryRotButton ); + +void CMomentaryRotButton::Spawn( void ) +{ + CBaseToggle::AxisDir( pev ); + + if ( pev->speed == 0 ) + pev->speed = 100; + + if ( m_flMoveDistance < 0 ) + { + m_start = pev->angles + pev->movedir * m_flMoveDistance; + m_end = pev->angles; + m_direction = 1; // This will toggle to -1 on the first use() + m_flMoveDistance = -m_flMoveDistance; + } + else + { + m_start = pev->angles; + m_end = pev->angles + pev->movedir * m_flMoveDistance; + m_direction = -1; // This will toggle to +1 on the first use() + } + + if ( pev->spawnflags & SF_MOMENTARY_DOOR ) + pev->solid = SOLID_BSP; + else + pev->solid = SOLID_NOT; + + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + char *pszSound = ButtonSound( m_sounds ); + PRECACHE_SOUND(pszSound); + pev->noise = ALLOC_STRING(pszSound); + m_lastUsed = 0; +} + +void CMomentaryRotButton::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "returnspeed")) + { + m_returnSpeed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CMomentaryRotButton::PlaySound( void ) +{ + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); +} + +// BUGBUG: This design causes a latentcy. When the button is retriggered, the first impulse +// will send the target in the wrong direction because the parameter is calculated based on the +// current, not future position. +void CMomentaryRotButton::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + pev->ideal_yaw = CBaseToggle::AxisDelta( pev->spawnflags, pev->angles, m_start ) / m_flMoveDistance; + + UpdateAllButtons( pev->ideal_yaw, 1 ); + UpdateTarget( pev->ideal_yaw ); +} + +void CMomentaryRotButton::UpdateAllButtons( float value, int start ) +{ + // Update all rot buttons attached to the same target + edict_t *pentTarget = NULL; + for (;;) + { + + pentTarget = FIND_ENTITY_BY_STRING(pentTarget, "target", STRING(pev->target)); + if (FNullEnt(pentTarget)) + break; + + if ( FClassnameIs( VARS(pentTarget), "momentary_rot_button" ) ) + { + CMomentaryRotButton *pEntity = CMomentaryRotButton::Instance(pentTarget); + if ( pEntity ) + { + if ( start ) + pEntity->UpdateSelf( value ); + else + pEntity->UpdateSelfReturn( value ); + } + } + } +} + +void CMomentaryRotButton::UpdateSelf( float value ) +{ + BOOL fplaysound = FALSE; + + if ( !m_lastUsed ) + { + fplaysound = TRUE; + m_direction = -m_direction; + } + m_lastUsed = 1; + + pev->nextthink = pev->ltime + 0.1; + if ( m_direction > 0 && value >= 1.0 ) + { + pev->avelocity = g_vecZero; + pev->angles = m_end; + return; + } + else if ( m_direction < 0 && value <= 0 ) + { + pev->avelocity = g_vecZero; + pev->angles = m_start; + return; + } + + if (fplaysound) + PlaySound(); + + // HACKHACK -- If we're going slow, we'll get multiple player packets per frame, bump nexthink on each one to avoid stalling + if ( pev->nextthink < pev->ltime ) + pev->nextthink = pev->ltime + 0.1; + else + pev->nextthink += 0.1; + + pev->avelocity = (m_direction * pev->speed) * pev->movedir; + SetThink( &CMomentaryRotButton::Off ); +} + +void CMomentaryRotButton::UpdateTarget( float value ) +{ + if (!FStringNull(pev->target)) + { + edict_t* pentTarget = NULL; + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->target)); + if (FNullEnt(pentTarget)) + break; + CBaseEntity *pEntity = CBaseEntity::Instance(pentTarget); + if ( pEntity ) + { + pEntity->Use( this, this, USE_SET, value ); + } + } + } +} + +void CMomentaryRotButton::Off( void ) +{ + pev->avelocity = g_vecZero; + m_lastUsed = 0; + if ( FBitSet( pev->spawnflags, SF_PENDULUM_AUTO_RETURN ) && m_returnSpeed > 0 ) + { + SetThink( &CMomentaryRotButton::Return ); + pev->nextthink = pev->ltime + 0.1; + m_direction = -1; + } + else + SetThink( NULL ); +} + +void CMomentaryRotButton::Return( void ) +{ + float value = CBaseToggle::AxisDelta( pev->spawnflags, pev->angles, m_start ) / m_flMoveDistance; + + UpdateAllButtons( value, 0 ); // This will end up calling UpdateSelfReturn() n times, but it still works right + if ( value > 0 ) + UpdateTarget( value ); +} + + +void CMomentaryRotButton::UpdateSelfReturn( float value ) +{ + if ( value <= 0 ) + { + pev->avelocity = g_vecZero; + pev->angles = m_start; + pev->nextthink = -1; + SetThink( NULL ); + } + else + { + pev->avelocity = -m_returnSpeed * pev->movedir; + pev->nextthink = pev->ltime + 0.1; + } +} + + +//---------------------------------------------------------------- +// Spark +//---------------------------------------------------------------- + +class CEnvSpark : public CBaseEntity +{ +public: + void Spawn(void); + void Precache(void); + void EXPORT SparkThink(void); + void EXPORT SparkStart(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT SparkStop(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue(KeyValueData *pkvd); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flDelay; +}; + + +TYPEDESCRIPTION CEnvSpark::m_SaveData[] = +{ + DEFINE_FIELD( CEnvSpark, m_flDelay, FIELD_FLOAT), +}; + +IMPLEMENT_SAVERESTORE( CEnvSpark, CBaseEntity ); + +LINK_ENTITY_TO_CLASS(env_spark, CEnvSpark); +LINK_ENTITY_TO_CLASS(env_debris, CEnvSpark); + +void CEnvSpark::Spawn(void) +{ + SetThink( NULL ); + SetUse( NULL ); + + if (FBitSet(pev->spawnflags, 32)) // Use for on/off + { + if (FBitSet(pev->spawnflags, 64)) // Start on + { + SetThink(&CEnvSpark::SparkThink); // start sparking + SetUse(&CEnvSpark::SparkStop); // set up +USE to stop sparking + } + else + SetUse(&CEnvSpark::SparkStart); + } + else + SetThink(&CEnvSpark::SparkThink); + + pev->nextthink = gpGlobals->time + ( 0.1 + RANDOM_FLOAT ( 0, 1.5 ) ); + + if (m_flDelay <= 0) + m_flDelay = 1.5; + + Precache( ); +} + + +void CEnvSpark::Precache(void) +{ + PRECACHE_SOUND( "buttons/spark1.wav" ); + PRECACHE_SOUND( "buttons/spark2.wav" ); + PRECACHE_SOUND( "buttons/spark3.wav" ); + PRECACHE_SOUND( "buttons/spark4.wav" ); + PRECACHE_SOUND( "buttons/spark5.wav" ); + PRECACHE_SOUND( "buttons/spark6.wav" ); +} + +void CEnvSpark::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "MaxDelay")) + { + m_flDelay = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "killtarget") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + pkvd->fHandled = TRUE; + else + CBaseEntity::KeyValue( pkvd ); +} + +void EXPORT CEnvSpark::SparkThink(void) +{ + pev->nextthink = gpGlobals->time + 0.1 + RANDOM_FLOAT (0, m_flDelay); + DoSpark( pev, pev->origin ); +} + +void EXPORT CEnvSpark::SparkStart(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetUse(&CEnvSpark::SparkStop); + SetThink(&CEnvSpark::SparkThink); + pev->nextthink = gpGlobals->time + (0.1 + RANDOM_FLOAT ( 0, m_flDelay)); +} + +void EXPORT CEnvSpark::SparkStop(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetUse(&CEnvSpark::SparkStart); + SetThink(NULL); +} + +#define SF_BTARGET_USE 0x0001 +#define SF_BTARGET_ON 0x0002 + +class CButtonTarget : public CBaseEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + int ObjectCaps( void ); + +}; + +LINK_ENTITY_TO_CLASS( button_target, CButtonTarget ); + +void CButtonTarget::Spawn( void ) +{ + pev->movetype = MOVETYPE_PUSH; + pev->solid = SOLID_BSP; + SET_MODEL(ENT(pev), STRING(pev->model)); + pev->takedamage = DAMAGE_YES; + + if ( FBitSet( pev->spawnflags, SF_BTARGET_ON ) ) + pev->frame = 1; +} + +void CButtonTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, (int)pev->frame ) ) + return; + pev->frame = 1-pev->frame; + if ( pev->frame ) + SUB_UseTargets( pActivator, USE_ON, 0 ); + else + SUB_UseTargets( pActivator, USE_OFF, 0 ); +} + + +int CButtonTarget :: ObjectCaps( void ) +{ + int caps = CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; + + if ( FBitSet(pev->spawnflags, SF_BTARGET_USE) ) + return caps | FCAP_IMPULSE_USE; + else + return caps; +} + + +int CButtonTarget::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + Use( Instance(pevAttacker), this, USE_TOGGLE, 0 ); + + return 1; +} diff --git a/ricochet/dlls/cbase.cpp b/ricochet/dlls/cbase.cpp new file mode 100644 index 0000000..d50d496 --- /dev/null +++ b/ricochet/dlls/cbase.cpp @@ -0,0 +1,796 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "client.h" +#include "decals.h" +#include "gamerules.h" +#include "game.h" + +void EntvarsKeyvalue( entvars_t *pev, KeyValueData *pkvd ); + +extern "C" void PM_Move ( struct playermove_s *ppmove, int server ); +extern "C" void PM_Init ( struct playermove_s *ppmove ); +extern "C" char PM_FindTextureType( char *name ); + +void OnFreeEntPrivateData(edict_s *pEdict); + + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); +extern DLL_GLOBAL Vector g_vecAttackDir; +extern DLL_GLOBAL int g_iSkillLevel; + +static DLL_FUNCTIONS gFunctionTable = +{ + GameDLLInit, //pfnGameInit + DispatchSpawn, //pfnSpawn + DispatchThink, //pfnThink + DispatchUse, //pfnUse + DispatchTouch, //pfnTouch + DispatchBlocked, //pfnBlocked + DispatchKeyValue, //pfnKeyValue + DispatchSave, //pfnSave + DispatchRestore, //pfnRestore + DispatchObjectCollsionBox, //pfnAbsBox + + SaveWriteFields, //pfnSaveWriteFields + SaveReadFields, //pfnSaveReadFields + + SaveGlobalState, //pfnSaveGlobalState + RestoreGlobalState, //pfnRestoreGlobalState + ResetGlobalState, //pfnResetGlobalState + + ClientConnect, //pfnClientConnect + ClientDisconnect, //pfnClientDisconnect + ClientKill, //pfnClientKill + ClientPutInServer, //pfnClientPutInServer + ClientCommand, //pfnClientCommand + ClientUserInfoChanged, //pfnClientUserInfoChanged + ServerActivate, //pfnServerActivate + ServerDeactivate, //pfnServerDeactivate + + PlayerPreThink, //pfnPlayerPreThink + PlayerPostThink, //pfnPlayerPostThink + + StartFrame, //pfnStartFrame + ParmsNewLevel, //pfnParmsNewLevel + ParmsChangeLevel, //pfnParmsChangeLevel + + GetGameDescription, //pfnGetGameDescription Returns string describing current .dll game. + PlayerCustomization, //pfnPlayerCustomization Notifies .dll of new customization for player. + + SpectatorConnect, //pfnSpectatorConnect Called when spectator joins server + SpectatorDisconnect, //pfnSpectatorDisconnect Called when spectator leaves the server + SpectatorThink, //pfnSpectatorThink Called when spectator sends a command packet (usercmd_t) + + Sys_Error, //pfnSys_Error Called when engine has encountered an error + + PM_Move, //pfnPM_Move + PM_Init, //pfnPM_Init Server version of player movement initialization + PM_FindTextureType, //pfnPM_FindTextureType + + SetupVisibility, //pfnSetupVisibility Set up PVS and PAS for networking for this client + UpdateClientData, //pfnUpdateClientData Set up data sent only to specific client + AddToFullPack, //pfnAddToFullPack + CreateBaseline, //pfnCreateBaseline Tweak entity baseline for network encoding, allows setup of player baselines, too. + RegisterEncoders, //pfnRegisterEncoders Callbacks for network encoding + GetWeaponData, //pfnGetWeaponData + CmdStart, //pfnCmdStart + CmdEnd, //pfnCmdEnd + ConnectionlessPacket, //pfnConnectionlessPacket + GetHullBounds, //pfnGetHullBounds + CreateInstancedBaselines, //pfnCreateInstancedBaselines + InconsistentFile, //pfnInconsistentFile + AllowLagCompensation, //pfnAllowLagCompensation +}; + +NEW_DLL_FUNCTIONS gNewDLLFunctions = +{ + OnFreeEntPrivateData, //pfnOnFreeEntPrivateData + GameDLLShutdown, //pfnGameShutdown + ShouldCollide, //pfnShouldCollide +}; + + +static void SetObjectCollisionBox( entvars_t *pev ); + +int GetEntityAPI( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion ) +{ + if ( !pFunctionTable || interfaceVersion != INTERFACE_VERSION ) + { + return FALSE; + } + + memcpy( pFunctionTable, &gFunctionTable, sizeof( DLL_FUNCTIONS ) ); + return TRUE; +} + +int GetEntityAPI2( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ) +{ + if ( !pFunctionTable || *interfaceVersion != INTERFACE_VERSION ) + { + // Tell engine what version we had, so it can figure out who is out of date. + *interfaceVersion = INTERFACE_VERSION; + return FALSE; + } + + memcpy( pFunctionTable, &gFunctionTable, sizeof( DLL_FUNCTIONS ) ); + return TRUE; +} + +int GetNewDLLFunctions(NEW_DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion) +{ + if(!pFunctionTable || *interfaceVersion != NEW_DLL_FUNCTIONS_VERSION) + { + *interfaceVersion = NEW_DLL_FUNCTIONS_VERSION; + return FALSE; + } + + memcpy(pFunctionTable, &gNewDLLFunctions, sizeof(gNewDLLFunctions)); + return TRUE; +} + +int DispatchSpawn( edict_t *pent ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if (pEntity) + { + // Initialize these or entities who don't link to the world won't have anything in here + pEntity->pev->absmin = pEntity->pev->origin - Vector(1,1,1); + pEntity->pev->absmax = pEntity->pev->origin + Vector(1,1,1); + + pEntity->Spawn(); + + // Try to get the pointer again, in case the spawn function deleted the entity. + // UNDONE: Spawn() should really return a code to ask that the entity be deleted, but + // that would touch too much code for me to do that right now. + pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if ( pEntity ) + { + if ( g_pGameRules && !g_pGameRules->IsAllowedToSpawn( pEntity ) ) + return -1; // return that this entity should be deleted + if ( pEntity->pev->flags & FL_KILLME ) + return -1; + } + + + // Handle global stuff here + if ( pEntity && pEntity->pev->globalname ) + { + const globalentity_t *pGlobal = gGlobalState.EntityFromTable( pEntity->pev->globalname ); + if ( pGlobal ) + { + // Already dead? delete + if ( pGlobal->state == GLOBAL_DEAD ) + return -1; + else if ( !FStrEq( STRING(gpGlobals->mapname), pGlobal->levelName ) ) + pEntity->MakeDormant(); // Hasn't been moved to this level yet, wait but stay alive + // In this level & not dead, continue on as normal + } + else + { + // Spawned entities default to 'On' + gGlobalState.EntityAdd( pEntity->pev->globalname, gpGlobals->mapname, GLOBAL_ON ); +// ALERT( at_console, "Added global entity %s (%s)\n", STRING(pEntity->pev->classname), STRING(pEntity->pev->globalname) ); + } + } + + } + + return 0; +} + +void DispatchKeyValue( edict_t *pentKeyvalue, KeyValueData *pkvd ) +{ + if ( !pkvd || !pentKeyvalue ) + return; + + EntvarsKeyvalue( VARS(pentKeyvalue), pkvd ); + + // If the key was an entity variable, or there's no class set yet, don't look for the object, it may + // not exist yet. + if ( pkvd->fHandled || pkvd->szClassName == NULL ) + return; + + // Get the actualy entity object + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pentKeyvalue); + + if ( !pEntity ) + return; + + pEntity->KeyValue( pkvd ); +} + + +// HACKHACK -- this is a hack to keep the node graph entity from "touching" things (like triggers) +// while it builds the graph +BOOL gTouchDisabled = FALSE; +void DispatchTouch( edict_t *pentTouched, edict_t *pentOther ) +{ + if ( gTouchDisabled ) + return; + + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pentTouched); + CBaseEntity *pOther = (CBaseEntity *)GET_PRIVATE( pentOther ); + + if ( pEntity && pOther && ! ((pEntity->pev->flags | pOther->pev->flags) & FL_KILLME) ) + pEntity->Touch( pOther ); +} + + +void DispatchUse( edict_t *pentUsed, edict_t *pentOther ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pentUsed); + CBaseEntity *pOther = (CBaseEntity *)GET_PRIVATE(pentOther); + + if (pEntity && !(pEntity->pev->flags & FL_KILLME) ) + pEntity->Use( pOther, pOther, USE_TOGGLE, 0 ); +} + +void DispatchThink( edict_t *pent ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + if (pEntity) + { + if ( FBitSet( pEntity->pev->flags, FL_DORMANT ) ) + ALERT( at_error, "Dormant entity %s is thinking!!\n", STRING(pEntity->pev->classname) ); + + pEntity->Think(); + } +} + +void DispatchBlocked( edict_t *pentBlocked, edict_t *pentOther ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE( pentBlocked ); + CBaseEntity *pOther = (CBaseEntity *)GET_PRIVATE( pentOther ); + + if (pEntity) + pEntity->Blocked( pOther ); +} + +void DispatchSave( edict_t *pent, SAVERESTOREDATA *pSaveData ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if ( pEntity && pSaveData ) + { + ENTITYTABLE *pTable = &pSaveData->pTable[ pSaveData->currentIndex ]; + + if ( pTable->pent != pent ) + ALERT( at_error, "ENTITY TABLE OR INDEX IS WRONG!!!!\n" ); + + if ( pEntity->ObjectCaps() & FCAP_DONT_SAVE ) + return; + + // These don't use ltime & nextthink as times really, but we'll fudge around it. + if ( pEntity->pev->movetype == MOVETYPE_PUSH ) + { + float delta = pEntity->pev->nextthink - pEntity->pev->ltime; + pEntity->pev->ltime = gpGlobals->time; + pEntity->pev->nextthink = pEntity->pev->ltime + delta; + } + + pTable->location = pSaveData->size; // Remember entity position for file I/O + pTable->classname = pEntity->pev->classname; // Remember entity class for respawn + + CSave saveHelper( pSaveData ); + pEntity->Save( saveHelper ); + + pTable->size = pSaveData->size - pTable->location; // Size of entity block is data size written to block + } +} + +void OnFreeEntPrivateData(edict_s *pEdict) +{ + if(pEdict && pEdict->pvPrivateData) + { + ((CBaseEntity*)pEdict->pvPrivateData)->~CBaseEntity(); + } +} + +// Find the matching global entity. Spit out an error if the designer made entities of +// different classes with the same global name +CBaseEntity *FindGlobalEntity( string_t classname, string_t globalname ) +{ + edict_t *pent = FIND_ENTITY_BY_STRING( NULL, "globalname", STRING(globalname) ); + CBaseEntity *pReturn = CBaseEntity::Instance( pent ); + if ( pReturn ) + { + if ( !FClassnameIs( pReturn->pev, STRING(classname) ) ) + { + ALERT( at_console, "Global entity found %s, wrong class %s\n", STRING(globalname), STRING(pReturn->pev->classname) ); + pReturn = NULL; + } + } + + return pReturn; +} + + +int DispatchRestore( edict_t *pent, SAVERESTOREDATA *pSaveData, int globalEntity ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if ( pEntity && pSaveData ) + { + entvars_t tmpVars; + Vector oldOffset; + + CRestore restoreHelper( pSaveData ); + if ( globalEntity ) + { + CRestore tmpRestore( pSaveData ); + tmpRestore.PrecacheMode( 0 ); + tmpRestore.ReadEntVars( "ENTVARS", &tmpVars ); + + // HACKHACK - reset the save pointers, we're going to restore for real this time + pSaveData->size = pSaveData->pTable[pSaveData->currentIndex].location; + pSaveData->pCurrentData = pSaveData->pBaseData + pSaveData->size; + // ------------------- + + + const globalentity_t *pGlobal = gGlobalState.EntityFromTable( tmpVars.globalname ); + + // Don't overlay any instance of the global that isn't the latest + // pSaveData->szCurrentMapName is the level this entity is coming from + // pGlobla->levelName is the last level the global entity was active in. + // If they aren't the same, then this global update is out of date. + if ( !FStrEq( pSaveData->szCurrentMapName, pGlobal->levelName ) ) + return 0; + + // Compute the new global offset + oldOffset = pSaveData->vecLandmarkOffset; + CBaseEntity *pNewEntity = FindGlobalEntity( tmpVars.classname, tmpVars.globalname ); + if ( pNewEntity ) + { +// ALERT( at_console, "Overlay %s with %s\n", STRING(pNewEntity->pev->classname), STRING(tmpVars.classname) ); + // Tell the restore code we're overlaying a global entity from another level + restoreHelper.SetGlobalMode( 1 ); // Don't overwrite global fields + pSaveData->vecLandmarkOffset = (pSaveData->vecLandmarkOffset - pNewEntity->pev->mins) + tmpVars.mins; + pEntity = pNewEntity;// we're going to restore this data OVER the old entity + pent = ENT( pEntity->pev ); + // Update the global table to say that the global definition of this entity should come from this level + gGlobalState.EntityUpdate( pEntity->pev->globalname, gpGlobals->mapname ); + } + else + { + // This entity will be freed automatically by the engine. If we don't do a restore on a matching entity (below) + // or call EntityUpdate() to move it to this level, we haven't changed global state at all. + return 0; + } + + } + + if ( pEntity->ObjectCaps() & FCAP_MUST_SPAWN ) + { + pEntity->Restore( restoreHelper ); + pEntity->Spawn(); + } + else + { + pEntity->Restore( restoreHelper ); + pEntity->Precache( ); + } + + // Again, could be deleted, get the pointer again. + pEntity = (CBaseEntity *)GET_PRIVATE(pent); + +#if 0 + if ( pEntity && pEntity->pev->globalname && globalEntity ) + { + ALERT( at_console, "Global %s is %s\n", STRING(pEntity->pev->globalname), STRING(pEntity->pev->model) ); + } +#endif + + // Is this an overriding global entity (coming over the transition), or one restoring in a level + if ( globalEntity ) + { +// ALERT( at_console, "After: %f %f %f %s\n", pEntity->pev->origin.x, pEntity->pev->origin.y, pEntity->pev->origin.z, STRING(pEntity->pev->model) ); + pSaveData->vecLandmarkOffset = oldOffset; + if ( pEntity ) + { + UTIL_SetOrigin( pEntity->pev, pEntity->pev->origin ); + pEntity->OverrideReset(); + } + } + else if ( pEntity && pEntity->pev->globalname ) + { + const globalentity_t *pGlobal = gGlobalState.EntityFromTable( pEntity->pev->globalname ); + if ( pGlobal ) + { + // Already dead? delete + if ( pGlobal->state == GLOBAL_DEAD ) + return -1; + else if ( !FStrEq( STRING(gpGlobals->mapname), pGlobal->levelName ) ) + { + pEntity->MakeDormant(); // Hasn't been moved to this level yet, wait but stay alive + } + // In this level & not dead, continue on as normal + } + else + { + ALERT( at_error, "Global Entity %s (%s) not in table!!!\n", STRING(pEntity->pev->globalname), STRING(pEntity->pev->classname) ); + // Spawned entities default to 'On' + gGlobalState.EntityAdd( pEntity->pev->globalname, gpGlobals->mapname, GLOBAL_ON ); + } + } + } + return 0; +} + + +void DispatchObjectCollsionBox( edict_t *pent ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + if (pEntity) + { + pEntity->SetObjectCollisionBox(); + } + else + SetObjectCollisionBox( &pent->v ); +} + + +void SaveWriteFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + CSave saveHelper( pSaveData ); + saveHelper.WriteFields( pname, pBaseData, pFields, fieldCount ); +} + + +void SaveReadFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + CRestore restoreHelper( pSaveData ); + restoreHelper.ReadFields( pname, pBaseData, pFields, fieldCount ); +} + + +edict_t * EHANDLE::Get( void ) +{ + if (m_pent) + { + if (m_pent->serialnumber == m_serialnumber) + return m_pent; + else + return NULL; + } + return NULL; +}; + +edict_t * EHANDLE::Set( edict_t *pent ) +{ + m_pent = pent; + if (pent) + m_serialnumber = m_pent->serialnumber; + return pent; +}; + + +EHANDLE :: operator CBaseEntity *() +{ + return (CBaseEntity *)GET_PRIVATE( Get( ) ); +}; + + +CBaseEntity * EHANDLE :: operator = (CBaseEntity *pEntity) +{ + if (pEntity) + { + m_pent = ENT( pEntity->pev ); + if (m_pent) + m_serialnumber = m_pent->serialnumber; + } + else + { + m_pent = NULL; + m_serialnumber = 0; + } + return pEntity; +} + +EHANDLE :: operator int () +{ + return Get() != NULL; +} + +CBaseEntity * EHANDLE :: operator -> () +{ + return (CBaseEntity *)GET_PRIVATE( Get( ) ); +} + + +// give health +int CBaseEntity :: TakeHealth( float flHealth, int bitsDamageType ) +{ + if (!pev->takedamage) + return 0; + +// heal + if ( pev->health >= pev->max_health ) + return 0; + + pev->health += flHealth; + + if (pev->health > pev->max_health) + pev->health = pev->max_health; + + return 1; +} + +// inflict damage on this entity. bitsDamageType indicates type of damage inflicted, ie: DMG_CRUSH + +int CBaseEntity :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecTemp; + + if (!pev->takedamage) + return 0; + + // UNDONE: some entity types may be immune or resistant to some bitsDamageType + + // if Attacker == Inflictor, the attack was a melee or other instant-hit attack. + // (that is, no actual entity projectile was involved in the attack so use the shooter's origin). + if ( pevAttacker == pevInflictor ) + { + vecTemp = pevInflictor->origin - ( VecBModelOrigin(pev) ); + } + else + // an actual missile was involved. + { + vecTemp = pevInflictor->origin - ( VecBModelOrigin(pev) ); + } + +// this global is still used for glass and other non-monster killables, along with decals. + g_vecAttackDir = vecTemp.Normalize(); + +// save damage based on the target's armor level + +// figure momentum add (don't let hurt brushes or other triggers move player) + if ((!FNullEnt(pevInflictor)) && (pev->movetype == MOVETYPE_WALK || pev->movetype == MOVETYPE_STEP) && (pevAttacker->solid != SOLID_TRIGGER) ) + { + Vector vecDir = pev->origin - (pevInflictor->absmin + pevInflictor->absmax) * 0.5; + vecDir = vecDir.Normalize(); + + float flForce = flDamage * ((32 * 32 * 72.0) / (pev->size.x * pev->size.y * pev->size.z)) * 5; + + if (flForce > 1000.0) + flForce = 1000.0; + pev->velocity = pev->velocity + vecDir * flForce; + } + +// do the damage + pev->health -= flDamage; + if (pev->health <= 0) + { + Killed( pevAttacker, GIB_NORMAL ); + return 0; + } + + return 1; +} + + +void CBaseEntity :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->takedamage = DAMAGE_NO; + pev->deadflag = DEAD_DEAD; + UTIL_Remove( this ); +} + + +CBaseEntity *CBaseEntity::GetNextTarget( void ) +{ + if ( FStringNull( pev->target ) ) + return NULL; + edict_t *pTarget = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(pev->target) ); + if ( FNullEnt(pTarget) ) + return NULL; + + return Instance( pTarget ); +} + +// Global Savedata for Delay +TYPEDESCRIPTION CBaseEntity::m_SaveData[] = +{ + DEFINE_FIELD( CBaseEntity, m_pGoalEnt, FIELD_CLASSPTR ), + + DEFINE_FIELD( CBaseEntity, m_pfnThink, FIELD_FUNCTION ), // UNDONE: Build table of these!!! + DEFINE_FIELD( CBaseEntity, m_pfnTouch, FIELD_FUNCTION ), + DEFINE_FIELD( CBaseEntity, m_pfnUse, FIELD_FUNCTION ), + DEFINE_FIELD( CBaseEntity, m_pfnBlocked, FIELD_FUNCTION ), +}; + + +int CBaseEntity::Save( CSave &save ) +{ + if ( save.WriteEntVars( "ENTVARS", pev ) ) + return save.WriteFields( "BASE", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + + return 0; +} + +int CBaseEntity::Restore( CRestore &restore ) +{ + int status; + + status = restore.ReadEntVars( "ENTVARS", pev ); + if ( status ) + status = restore.ReadFields( "BASE", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + + if ( pev->modelindex != 0 && !FStringNull(pev->model) ) + { + Vector mins, maxs; + mins = pev->mins; // Set model is about to destroy these + maxs = pev->maxs; + + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL(ENT(pev), STRING(pev->model)); + UTIL_SetSize(pev, mins, maxs); // Reset them + } + + return status; +} + + +// Initialize absmin & absmax to the appropriate box +void SetObjectCollisionBox( entvars_t *pev ) +{ + if ( (pev->solid == SOLID_BSP) && + (pev->angles.x || pev->angles.y|| pev->angles.z) ) + { // expand for rotation + float max, v; + int i; + + max = 0; + for (i=0 ; i<3 ; i++) + { + v = fabs( pev->mins[i]); + if (v > max) + max = v; + v = fabs( pev->maxs[i]); + if (v > max) + max = v; + } + for (i=0 ; i<3 ; i++) + { + pev->absmin[i] = pev->origin[i] - max; + pev->absmax[i] = pev->origin[i] + max; + } + } + else + { + pev->absmin = pev->origin + pev->mins; + pev->absmax = pev->origin + pev->maxs; + } + + pev->absmin.x -= 1; + pev->absmin.y -= 1; + pev->absmin.z -= 1; + pev->absmax.x += 1; + pev->absmax.y += 1; + pev->absmax.z += 1; +} + + +void CBaseEntity::SetObjectCollisionBox( void ) +{ + ::SetObjectCollisionBox( pev ); +} + + +int CBaseEntity :: Intersects( CBaseEntity *pOther ) +{ + if ( pOther->pev->absmin.x > pev->absmax.x || + pOther->pev->absmin.y > pev->absmax.y || + pOther->pev->absmin.z > pev->absmax.z || + pOther->pev->absmax.x < pev->absmin.x || + pOther->pev->absmax.y < pev->absmin.y || + pOther->pev->absmax.z < pev->absmin.z ) + return 0; + return 1; +} + +void CBaseEntity :: MakeDormant( void ) +{ + SetBits( pev->flags, FL_DORMANT ); + + // Don't touch + pev->solid = SOLID_NOT; + // Don't move + pev->movetype = MOVETYPE_NONE; + // Don't draw + SetBits( pev->effects, EF_NODRAW ); + // Don't think + pev->nextthink = 0; + // Relink + UTIL_SetOrigin( pev, pev->origin ); +} + +int CBaseEntity :: IsDormant( void ) +{ + return FBitSet( pev->flags, FL_DORMANT ); +} + +BOOL CBaseEntity :: IsInWorld( void ) +{ + // position + if (pev->origin.x >= 4096) return FALSE; + if (pev->origin.y >= 4096) return FALSE; + if (pev->origin.z >= 4096) return FALSE; + if (pev->origin.x <= -4096) return FALSE; + if (pev->origin.y <= -4096) return FALSE; + if (pev->origin.z <= -4096) return FALSE; + // speed + if (pev->velocity.x >= 2000) return FALSE; + if (pev->velocity.y >= 2000) return FALSE; + if (pev->velocity.z >= 2000) return FALSE; + if (pev->velocity.x <= -2000) return FALSE; + if (pev->velocity.y <= -2000) return FALSE; + if (pev->velocity.z <= -2000) return FALSE; + + return TRUE; +} + +int CBaseEntity::ShouldToggle( USE_TYPE useType, BOOL currentState ) +{ + if ( useType != USE_TOGGLE && useType != USE_SET ) + { + if ( (currentState && useType == USE_ON) || (!currentState && useType == USE_OFF) ) + return 0; + } + return 1; +} + + +int CBaseEntity :: DamageDecal( int bitsDamageType ) +{ + if ( pev->rendermode == kRenderTransAlpha ) + return -1; + + if ( pev->rendermode != kRenderNormal ) + return DECAL_BPROOF1; + + return DECAL_GUNSHOT1 + RANDOM_LONG(0,4); +} + + + +// NOTE: szName must be a pointer to constant memory, e.g. "monster_class" because the entity +// will keep a pointer to it after this call. +CBaseEntity * CBaseEntity::Create( char *szName, const Vector &vecOrigin, const Vector &vecAngles, edict_t *pentOwner ) +{ + edict_t *pent; + CBaseEntity *pEntity; + + pent = CREATE_NAMED_ENTITY( MAKE_STRING( szName )); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in Create!\n" ); + return NULL; + } + pEntity = Instance( pent ); + pEntity->pev->owner = pentOwner; + pEntity->pev->origin = vecOrigin; + pEntity->pev->angles = vecAngles; + DispatchSpawn( pEntity->edict() ); + return pEntity; +} + + diff --git a/ricochet/dlls/cbase.h b/ricochet/dlls/cbase.h new file mode 100644 index 0000000..274efea --- /dev/null +++ b/ricochet/dlls/cbase.h @@ -0,0 +1,803 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +Class Hierachy + +CBaseEntity + CBaseDelay + CBaseToggle + CBaseItem + CBaseMonster + CBaseCycler + CBasePlayer + CBaseGroup +*/ + +#define MAX_PATH_SIZE 10 // max number of nodes available for a path. + +// These are caps bits to indicate what an object's capabilities (currently used for save/restore and level transitions) +#define FCAP_CUSTOMSAVE 0x00000001 +#define FCAP_ACROSS_TRANSITION 0x00000002 // should transfer between transitions +#define FCAP_MUST_SPAWN 0x00000004 // Spawn after restore +#define FCAP_DONT_SAVE 0x80000000 // Don't save this +#define FCAP_IMPULSE_USE 0x00000008 // can be used by the player +#define FCAP_CONTINUOUS_USE 0x00000010 // can be used by the player +#define FCAP_ONOFF_USE 0x00000020 // can be used by the player +#define FCAP_DIRECTIONAL_USE 0x00000040 // Player sends +/- 1 when using (currently only tracktrains) +#define FCAP_MASTER 0x00000080 // Can be used to "master" other entities (like multisource) + +// UNDONE: This will ignore transition volumes (trigger_transition), but not the PVS!!! +#define FCAP_FORCE_TRANSITION 0x00000080 // ALWAYS goes across transitions + +#include "archtypes.h" // DAL +#include "saverestore.h" +#include "schedule.h" + +#ifndef MONSTEREVENT_H +#include "monsterevent.h" +#endif + +// C functions for external declarations that call the appropriate C++ methods + +#ifndef CBASE_DLLEXPORT +#ifdef _WIN32 +#define CBASE_DLLEXPORT _declspec( dllexport ) +#else +#define CBASE_DLLEXPORT __attribute__ ((visibility("default"))) +#endif +#endif + +#define EXPORT CBASE_DLLEXPORT + +extern "C" CBASE_DLLEXPORT int GetEntityAPI( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion ); +extern "C" CBASE_DLLEXPORT int GetEntityAPI2( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ); +extern "C" CBASE_DLLEXPORT int GetNewDLLFunctions( NEW_DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ); + +extern int DispatchSpawn( edict_t *pent ); +extern void DispatchKeyValue( edict_t *pentKeyvalue, KeyValueData *pkvd ); +extern void DispatchTouch( edict_t *pentTouched, edict_t *pentOther ); +extern void DispatchUse( edict_t *pentUsed, edict_t *pentOther ); +extern void DispatchThink( edict_t *pent ); +extern void DispatchBlocked( edict_t *pentBlocked, edict_t *pentOther ); +extern void DispatchSave( edict_t *pent, SAVERESTOREDATA *pSaveData ); +extern int DispatchRestore( edict_t *pent, SAVERESTOREDATA *pSaveData, int globalEntity ); +extern void DispatchObjectCollsionBox( edict_t *pent ); +extern void SaveWriteFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); +extern void SaveReadFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); +extern void SaveGlobalState( SAVERESTOREDATA *pSaveData ); +extern void RestoreGlobalState( SAVERESTOREDATA *pSaveData ); +extern void ResetGlobalState( void ); + +typedef enum { USE_OFF = 0, USE_ON = 1, USE_SET = 2, USE_TOGGLE = 3 } USE_TYPE; + +extern void FireTargets( const char *targetName, CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +typedef void (CBaseEntity::*BASEPTR)(void); +typedef void (CBaseEntity::*ENTITYFUNCPTR)(CBaseEntity *pOther ); +typedef void (CBaseEntity::*USEPTR)( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +// For CLASSIFY +#define CLASS_NONE 0 +#define CLASS_MACHINE 1 +#define CLASS_PLAYER 2 +#define CLASS_HUMAN_PASSIVE 3 +#define CLASS_HUMAN_MILITARY 4 +#define CLASS_ALIEN_MILITARY 5 +#define CLASS_ALIEN_PASSIVE 6 +#define CLASS_ALIEN_MONSTER 7 +#define CLASS_ALIEN_PREY 8 +#define CLASS_ALIEN_PREDATOR 9 +#define CLASS_INSECT 10 +#define CLASS_PLAYER_ALLY 11 +#define CLASS_PLAYER_BIOWEAPON 12 // hornets and snarks.launched by players +#define CLASS_ALIEN_BIOWEAPON 13 // hornets and snarks.launched by the alien menace +#define CLASS_BARNACLE 99 // special because no one pays attention to it, and it eats a wide cross-section of creatures. + +class CBaseEntity; +class CBaseMonster; +class CBasePlayerItem; +class CSquadMonster; + + +#define SF_NORESPAWN ( 1 << 30 )// !!!set this bit on guns and stuff that should never respawn. + +// +// EHANDLE. Safe way to point to CBaseEntities who may die between frames +// +class EHANDLE +{ +private: + edict_t *m_pent; + int m_serialnumber; +public: + edict_t *Get( void ); + edict_t *Set( edict_t *pent ); + + operator int (); + + operator CBaseEntity *(); + + CBaseEntity * operator = (CBaseEntity *pEntity); + CBaseEntity * operator ->(); +}; + + +// +// Base Entity. All entity types derive from this +// +class CBaseEntity +{ +public: + // Constructor. Set engine to use C/C++ callback functions + // pointers to engine data + entvars_t *pev; // Don't need to save/restore this pointer, the engine resets it + + // path corners + CBaseEntity *m_pGoalEnt;// path corner we are heading towards + CBaseEntity *m_pLink;// used for temporary link-list operations. + + // initialization functions + virtual void Spawn( void ) { return; } + virtual void Precache( void ) { return; } + virtual void KeyValue( KeyValueData* pkvd) { pkvd->fHandled = FALSE; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return FCAP_ACROSS_TRANSITION; } + virtual void Activate( void ) {} + + // Setup the object->object collision box (pev->mins / pev->maxs is the object->world collision box) + virtual void SetObjectCollisionBox( void ); + +// Classify - returns the type of group (i.e, "houndeye", or "human military" so that monsters with different classnames +// still realize that they are teammates. (overridden for monsters that form groups) + virtual int Classify ( void ) { return CLASS_NONE; }; + virtual void DeathNotice ( entvars_t *pevChild ) {}// monster maker children use this to tell the monster maker that they have died. + + + static TYPEDESCRIPTION m_SaveData[]; + + virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + virtual int TakeHealth( float flHealth, int bitsDamageType ); + virtual void Killed( entvars_t *pevAttacker, int iGib ); + virtual int BloodColor( void ) { return DONT_BLEED; } + virtual void TraceBleed( float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); + virtual BOOL IsTriggered( CBaseEntity *pActivator ) {return TRUE;} + virtual CBaseMonster *MyMonsterPointer( void ) { return NULL;} + virtual CSquadMonster *MySquadMonsterPointer( void ) { return NULL;} + virtual int GetToggleState( void ) { return TS_AT_TOP; } + virtual void AddPoints( int score, BOOL bAllowNegativeScore ) {} + virtual void AddPointsToTeam( int score, BOOL bAllowNegativeScore ) {} + virtual BOOL AddPlayerItem( CBasePlayerItem *pItem ) { return 0; } + virtual BOOL RemovePlayerItem( CBasePlayerItem *pItem ) { return 0; } + virtual int GiveAmmo( int iAmount, char *szName, int iMax ) { return -1; }; + virtual float GetDelay( void ) { return 0; } + virtual int IsMoving( void ) { return pev->velocity != g_vecZero; } + virtual void OverrideReset( void ) {} + virtual int DamageDecal( int bitsDamageType ); + // This is ONLY used by the node graph to test movement through a door + virtual void SetToggleState( int state ) {} + virtual void StartSneaking( void ) {} + virtual void StopSneaking( void ) {} + virtual BOOL OnControls( entvars_t *pev ) { return FALSE; } + virtual BOOL IsSneaking( void ) { return FALSE; } + virtual BOOL IsAlive( void ) { return (pev->deadflag == DEAD_NO) && pev->health > 0; } + virtual BOOL IsBSPModel( void ) { return pev->solid == SOLID_BSP || pev->movetype == MOVETYPE_PUSHSTEP; } + virtual BOOL ReflectGauss( void ) { return ( IsBSPModel() && !pev->takedamage ); } + virtual BOOL HasTarget( string_t targetname ) { return FStrEq(STRING(targetname), STRING(pev->targetname) ); } + virtual BOOL IsInWorld( void ); + virtual BOOL IsPlayer( void ) { return FALSE; } + virtual BOOL IsNetClient( void ) { return FALSE; } + virtual const char *TeamID( void ) { return ""; } + + virtual BOOL IsDisc( void ) { return FALSE; }; + +// virtual void SetActivator( CBaseEntity *pActivator ) {} + virtual CBaseEntity *GetNextTarget( void ); + + // fundamental callbacks + void (CBaseEntity ::*m_pfnThink)(void); + void (CBaseEntity ::*m_pfnTouch)( CBaseEntity *pOther ); + void (CBaseEntity ::*m_pfnUse)( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void (CBaseEntity ::*m_pfnBlocked)( CBaseEntity *pOther ); + + virtual void Think( void ) { if (m_pfnThink) (this->*m_pfnThink)(); }; + virtual void Touch( CBaseEntity *pOther ) { if (m_pfnTouch) (this->*m_pfnTouch)( pOther ); }; + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) + { + if (m_pfnUse) + (this->*m_pfnUse)( pActivator, pCaller, useType, value ); + } + virtual void Blocked( CBaseEntity *pOther ) { if (m_pfnBlocked) (this->*m_pfnBlocked)( pOther ); }; + + // allow engine to allocate instance data + void *operator new( size_t stAllocateBlock, entvars_t *pev ) + { + return (void *)ALLOC_PRIVATE(ENT(pev), stAllocateBlock); + }; + + // don't use this. +#if _MSC_VER >= 1200 // only build this code if MSVC++ 6.0 or higher + void operator delete(void *pMem, entvars_t *pev) + { + pev->flags |= FL_KILLME; + }; +#endif + + void UpdateOnRemove( void ); + + // common member functions + void EXPORT SUB_Remove( void ); + void EXPORT SUB_DoNothing( void ); + void EXPORT SUB_StartFadeOut ( void ); + void EXPORT SUB_FadeOut ( void ); + void EXPORT SUB_CallUseToggle( void ) { this->Use( this, this, USE_TOGGLE, 0 ); } + int ShouldToggle( USE_TYPE useType, BOOL currentState ); + void FireBullets( ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq = 4, int iDamage = 0, entvars_t *pevAttacker = NULL ); + + virtual CBaseEntity *Respawn( void ) { return NULL; } + + void SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ); + // Do the bounding boxes of these two intersect? + int Intersects( CBaseEntity *pOther ); + void MakeDormant( void ); + int IsDormant( void ); + BOOL IsLockedByMaster( void ) { return FALSE; } + +#ifdef _DEBUG + static CBaseEntity *Instance( edict_t *pent ) + { + if ( !pent ) + pent = ENT(0); + CBaseEntity *pEnt = (CBaseEntity *)GET_PRIVATE(pent); + ASSERT(pEnt!=NULL); + return pEnt; + } +#else + static CBaseEntity *Instance( edict_t *pent ) + { + if ( !pent ) + pent = ENT(0); + CBaseEntity *pEnt = (CBaseEntity *)GET_PRIVATE(pent); + return pEnt; + } +#endif + + static CBaseEntity *Instance( entvars_t *pev ) { return Instance( ENT( pev ) ); } + static CBaseEntity *Instance( int eoffset) { return Instance( ENT( eoffset) ); } + + CBaseMonster *GetMonsterPointer( entvars_t *pevMonster ) + { + CBaseEntity *pEntity = Instance( pevMonster ); + if ( pEntity ) + return pEntity->MyMonsterPointer(); + return NULL; + } + CBaseMonster *GetMonsterPointer( edict_t *pentMonster ) + { + CBaseEntity *pEntity = Instance( pentMonster ); + if ( pEntity ) + return pEntity->MyMonsterPointer(); + return NULL; + } + + + // Ugly code to lookup all functions to make sure they are exported when set. +#ifdef _DEBUG + void FunctionCheck( void *pFunction, char *name ) + { +#ifdef _WIN32 + if (pFunction && !NAME_FOR_FUNCTION((uint32)pFunction) ) + ALERT( at_error, "No EXPORT: %s:%s (%08lx)\n", STRING(pev->classname), name, pFunction ); +#endif // _WIN32 + } + + BASEPTR ThinkSet( BASEPTR func, char *name ) + { + m_pfnThink = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnThink)))), name ); + return func; + } + ENTITYFUNCPTR TouchSet( ENTITYFUNCPTR func, char *name ) + { + m_pfnTouch = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnTouch)))), name ); + return func; + } + USEPTR UseSet( USEPTR func, char *name ) + { + m_pfnUse = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnUse)))), name ); + return func; + } + ENTITYFUNCPTR BlockedSet( ENTITYFUNCPTR func, char *name ) + { + m_pfnBlocked = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnBlocked)))), name ); + return func; + } + +#endif + + + // virtual functions used by a few classes + + // used by monsters that are created by the MonsterMaker + virtual void UpdateOwner( void ) { return; }; + + + // + static CBaseEntity *Create( char *szName, const Vector &vecOrigin, const Vector &vecAngles, edict_t *pentOwner = NULL ); + + virtual BOOL FBecomeProne( void ) {return FALSE;}; + edict_t *edict() { return ENT( pev ); }; + EOFFSET eoffset( ) { return OFFSET( pev ); }; + int entindex( ) { return ENTINDEX( edict() ); }; + + virtual Vector Center( ) { return (pev->absmax + pev->absmin) * 0.5; }; // center point of entity + virtual Vector EyePosition( ) { return pev->origin + pev->view_ofs; }; // position of eyes + virtual Vector EarPosition( ) { return pev->origin + pev->view_ofs; }; // position of ears + virtual Vector BodyTarget( const Vector &posSrc ) { return Center( ); }; // position to shoot at + + virtual int Illumination( ) { return GETENTITYILLUM( ENT( pev ) ); }; + + virtual BOOL FVisible ( CBaseEntity *pEntity ); + virtual BOOL FVisible ( const Vector &vecOrigin ); + + // Last touched by Jump pad + float m_flTouchedByJumpPad; +}; + + + +// Ugly technique to override base member functions +// Normally it's illegal to cast a pointer to a member function of a derived class to a pointer to a +// member function of a base class. static_cast is a sleezy way around that problem. + +#ifdef _DEBUG + +#define SetThink( a ) ThinkSet( static_cast (a), #a ) +#define SetTouch( a ) TouchSet( static_cast (a), #a ) +#define SetUse( a ) UseSet( static_cast (a), #a ) +#define SetBlocked( a ) BlockedSet( static_cast (a), #a ) + +#else + +#define SetThink( a ) m_pfnThink = static_cast (a) +#define SetTouch( a ) m_pfnTouch = static_cast (a) +#define SetUse( a ) m_pfnUse = static_cast (a) +#define SetBlocked( a ) m_pfnBlocked = static_cast (a) + +#endif + + +class CPointEntity : public CBaseEntity +{ +public: + void Spawn( void ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +private: +}; + + +typedef struct locksounds // sounds that doors and buttons make when locked/unlocked +{ + string_t sLockedSound; // sound a door makes when it's locked + string_t sLockedSentence; // sentence group played when door is locked + string_t sUnlockedSound; // sound a door makes when it's unlocked + string_t sUnlockedSentence; // sentence group played when door is unlocked + + int iLockedSentence; // which sentence in sentence group to play next + int iUnlockedSentence; // which sentence in sentence group to play next + + float flwaitSound; // time delay between playing consecutive 'locked/unlocked' sounds + float flwaitSentence; // time delay between playing consecutive sentences + BYTE bEOFLocked; // true if hit end of list of locked sentences + BYTE bEOFUnlocked; // true if hit end of list of unlocked sentences +} locksound_t; + +void PlayLockSounds(entvars_t *pev, locksound_t *pls, int flocked, int fbutton); + +// +// MultiSouce +// + +#define MAX_MULTI_TARGETS 16 // maximum number of targets a single multi_manager entity may be assigned. +#define MS_MAX_TARGETS 32 + +class CMultiSource : public CPointEntity +{ +public: + void Spawn( ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int ObjectCaps( void ) { return (CPointEntity::ObjectCaps() | FCAP_MASTER); } + BOOL IsTriggered( CBaseEntity *pActivator ); + void EXPORT Register( void ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + EHANDLE m_rgEntities[MS_MAX_TARGETS]; + int m_rgTriggered[MS_MAX_TARGETS]; + + int m_iTotal; + string_t m_globalstate; +}; + + +// +// generic Delay entity. +// +class CBaseDelay : public CBaseEntity +{ +public: + float m_flDelay; + int m_iszKillTarget; + + virtual void KeyValue( KeyValueData* pkvd); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + // common member functions + void SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ); + void EXPORT DelayThink( void ); +}; + + +class CBaseAnimating : public CBaseDelay +{ +public: + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + // Basic Monster Animation functions + float StudioFrameAdvance( float flInterval = 0.0 ); // accumulate animation frame time from last time called until now + int GetSequenceFlags( void ); + int LookupActivity ( int activity ); + int LookupActivityHeaviest ( int activity ); + int LookupSequence ( const char *label ); + void ResetSequenceInfo ( ); + void DispatchAnimEvents ( float flFutureInterval = 0.1 ); // Handle events that have happend since last time called up until X seconds into the future + virtual void HandleAnimEvent( MonsterEvent_t *pEvent ) { return; }; + float SetBoneController ( int iController, float flValue ); + void InitBoneControllers ( void ); + float SetBlending ( int iBlender, float flValue ); + void GetBonePosition ( int iBone, Vector &origin, Vector &angles ); + void GetAutomovement( Vector &origin, Vector &angles, float flInterval = 0.1 ); + int FindTransition( int iEndingSequence, int iGoalSequence, int *piDir ); + void GetAttachment ( int iAttachment, Vector &origin, Vector &angles ); + void SetBodygroup( int iGroup, int iValue ); + int GetBodygroup( int iGroup ); + int ExtractBbox( int sequence, float *mins, float *maxs ); + void SetSequenceBox( void ); + + // animation needs + float m_flFrameRate; // computed FPS for current sequence + float m_flGroundSpeed; // computed linear movement rate for current sequence + float m_flLastEventCheck; // last time the event list was checked + BOOL m_fSequenceFinished;// flag set when StudioAdvanceFrame moves across a frame boundry + BOOL m_fSequenceLoops; // true if the sequence loops +}; + + +// +// generic Toggle entity. +// +#define SF_ITEM_USE_ONLY 256 // ITEM_USE_ONLY = BUTTON_USE_ONLY = DOOR_USE_ONLY!!! + +class CBaseToggle : public CBaseAnimating +{ +public: + void KeyValue( KeyValueData *pkvd ); + + TOGGLE_STATE m_toggle_state; + float m_flActivateFinished;//like attack_finished, but for doors + float m_flMoveDistance;// how far a door should slide or rotate + float m_flWait; + float m_flLip; + float m_flTWidth;// for plats + float m_flTLength;// for plats + + Vector m_vecPosition1; + Vector m_vecPosition2; + Vector m_vecAngle1; + Vector m_vecAngle2; + + int m_cTriggersLeft; // trigger_counter only, # of activations remaining + float m_flHeight; + EHANDLE m_hActivator; + void (CBaseToggle::*m_pfnCallWhenMoveDone)(void); + Vector m_vecFinalDest; + Vector m_vecFinalAngle; + + int m_bitsDamageInflict; // DMG_ damage type that the door or tigger does + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + virtual int GetToggleState( void ) { return m_toggle_state; } + virtual float GetDelay( void ) { return m_flWait; } + + // common member functions + void LinearMove( Vector vecDest, float flSpeed ); + void EXPORT LinearMoveDone( void ); + void AngularMove( Vector vecDestAngle, float flSpeed ); + void EXPORT AngularMoveDone( void ); + BOOL IsLockedByMaster( void ); + + static float AxisValue( int flags, const Vector &angles ); + static void AxisDir( entvars_t *pev ); + static float AxisDelta( int flags, const Vector &angle1, const Vector &angle2 ); + + string_t m_sMaster; // If this button has a master switch, this is the targetname. + // A master switch must be of the multisource type. If all + // of the switches in the multisource have been triggered, then + // the button will be allowed to operate. Otherwise, it will be + // deactivated. +}; +#define SetMoveDone( a ) m_pfnCallWhenMoveDone = static_cast (a) + + +// people gib if their health is <= this at the time of death +#define GIB_HEALTH_VALUE -30 + +#define ROUTE_SIZE 8 // how many waypoints a monster can store at one time +#define MAX_OLD_ENEMIES 4 // how many old enemies to remember + +#define bits_CAP_DUCK ( 1 << 0 )// crouch +#define bits_CAP_JUMP ( 1 << 1 )// jump/leap +#define bits_CAP_STRAFE ( 1 << 2 )// strafe ( walk/run sideways) +#define bits_CAP_SQUAD ( 1 << 3 )// can form squads +#define bits_CAP_SWIM ( 1 << 4 )// proficiently navigate in water +#define bits_CAP_CLIMB ( 1 << 5 )// climb ladders/ropes +#define bits_CAP_USE ( 1 << 6 )// open doors/push buttons/pull levers +#define bits_CAP_HEAR ( 1 << 7 )// can hear forced sounds +#define bits_CAP_AUTO_DOORS ( 1 << 8 )// can trigger auto doors +#define bits_CAP_OPEN_DOORS ( 1 << 9 )// can open manual doors +#define bits_CAP_TURN_HEAD ( 1 << 10)// can turn head, always bone controller 0 + +#define bits_CAP_RANGE_ATTACK1 ( 1 << 11)// can do a range attack 1 +#define bits_CAP_RANGE_ATTACK2 ( 1 << 12)// can do a range attack 2 +#define bits_CAP_MELEE_ATTACK1 ( 1 << 13)// can do a melee attack 1 +#define bits_CAP_MELEE_ATTACK2 ( 1 << 14)// can do a melee attack 2 + +#define bits_CAP_FLY ( 1 << 15)// can fly, move all around + +#define bits_CAP_DOORS_GROUP (bits_CAP_USE | bits_CAP_AUTO_DOORS | bits_CAP_OPEN_DOORS) + +// used by suit voice to indicate damage sustained and repaired type to player + +// instant damage + +#define DMG_GENERIC 0 // generic damage was done +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FREEZE (1 << 4) // frozen +#define DMG_FALL (1 << 5) // fell too far +#define DMG_BLAST (1 << 6) // explosive blast damage +#define DMG_CLUB (1 << 7) // crowbar, punch, headbutt +#define DMG_SHOCK (1 << 8) // electric shock +#define DMG_SONIC (1 << 9) // sound pulse shockwave +#define DMG_ENERGYBEAM (1 << 10) // laser or other high energy beam +#define DMG_NEVERGIB (1 << 12) // with this bit OR'd in, no damage type will be able to gib victims upon death +#define DMG_ALWAYSGIB (1 << 13) // with this bit OR'd in, any damage type can be made to gib victims upon death. +#define DMG_DROWN (1 << 14) // Drowning +// time-based damage +#define DMG_TIMEBASED (~(0x3fff)) // mask for time-based damage + +#define DMG_PARALYZE (1 << 15) // slows affected creature down +#define DMG_NERVEGAS (1 << 16) // nerve toxins, very bad +#define DMG_POISON (1 << 17) // blood poisioning +#define DMG_RADIATION (1 << 18) // radiation exposure +#define DMG_DROWNRECOVER (1 << 19) // drowning recovery +#define DMG_ACID (1 << 20) // toxic chemicals or acid burns +#define DMG_SLOWBURN (1 << 21) // in an oven +#define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer +#define DMG_MORTAR (1 << 23) // Hit by air raid (done to distinguish grenade from mortar) + +// these are the damage types that are allowed to gib corpses +#define DMG_GIB_CORPSE ( DMG_CRUSH | DMG_FALL | DMG_BLAST | DMG_SONIC | DMG_CLUB ) + +// these are the damage types that have client hud art +#define DMG_SHOWNHUD (DMG_POISON | DMG_ACID | DMG_FREEZE | DMG_SLOWFREEZE | DMG_DROWN | DMG_BURN | DMG_SLOWBURN | DMG_NERVEGAS | DMG_RADIATION | DMG_SHOCK) + +// NOTE: tweak these values based on gameplay feedback: + +#define PARALYZE_DURATION 2 // number of 2 second intervals to take damage +#define PARALYZE_DAMAGE 1.0 // damage to take each 2 second interval + +#define NERVEGAS_DURATION 2 +#define NERVEGAS_DAMAGE 5.0 + +#define POISON_DURATION 5 +#define POISON_DAMAGE 2.0 + +#define RADIATION_DURATION 2 +#define RADIATION_DAMAGE 1.0 + +#define ACID_DURATION 2 +#define ACID_DAMAGE 5.0 + +#define SLOWBURN_DURATION 2 +#define SLOWBURN_DAMAGE 1.0 + +#define SLOWFREEZE_DURATION 2 +#define SLOWFREEZE_DAMAGE 1.0 + + +#define itbd_Paralyze 0 +#define itbd_NerveGas 1 +#define itbd_Poison 2 +#define itbd_Radiation 3 +#define itbd_DrownRecover 4 +#define itbd_Acid 5 +#define itbd_SlowBurn 6 +#define itbd_SlowFreeze 7 +#define CDMG_TIMEBASED 8 + +// when calling KILLED(), a value that governs gib behavior is expected to be +// one of these three values +#define GIB_NORMAL 0// gib if entity was overkilled +#define GIB_NEVER 1// never gib, no matter how much death damage is done ( freezing, etc ) +#define GIB_ALWAYS 2// always gib ( Houndeye Shock, Barnacle Bite ) + +class CBaseMonster; +class CCineMonster; +class CSound; + +#include "basemonster.h" + + +char *ButtonSound( int sound ); // get string of button sound number + + +// +// Generic Button +// +class CBaseButton : public CBaseToggle +{ +public: + void Spawn( void ); + virtual void Precache( void ); + void RotSpawn( void ); + virtual void KeyValue( KeyValueData* pkvd); + + void ButtonActivate( ); + void SparkSoundCache( void ); + + void EXPORT ButtonShot( void ); + void EXPORT ButtonTouch( CBaseEntity *pOther ); + void EXPORT ButtonSpark ( void ); + void EXPORT TriggerAndWait( void ); + void EXPORT ButtonReturn( void ); + void EXPORT ButtonBackHome( void ); + void EXPORT ButtonUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + enum BUTTON_CODE { BUTTON_NOTHING, BUTTON_ACTIVATE, BUTTON_RETURN }; + BUTTON_CODE ButtonResponseToTouch( void ); + + static TYPEDESCRIPTION m_SaveData[]; + // Buttons that don't take damage can be IMPULSE used + virtual int ObjectCaps( void ) { return (CBaseToggle:: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | (pev->takedamage?0:FCAP_IMPULSE_USE); } + + BOOL m_fStayPushed; // button stays pushed in until touched again? + BOOL m_fRotating; // a rotating button? default is a sliding button. + + string_t m_strChangeTarget; // if this field is not null, this is an index into the engine string array. + // when this button is touched, it's target entity's TARGET field will be set + // to the button's ChangeTarget. This allows you to make a func_train switch paths, etc. + + locksound_t m_ls; // door lock sounds + + BYTE m_bLockedSound; // ordinals from entity selection + BYTE m_bLockedSentence; + BYTE m_bUnlockedSound; + BYTE m_bUnlockedSentence; + int m_sounds; +}; + +// +// Weapons +// + +#define BAD_WEAPON 0x00007FFF + +// +// Converts a entvars_t * to a class pointer +// It will allocate the class and entity if necessary +// +template T * GetClassPtr( T *a ) +{ + entvars_t *pev = (entvars_t *)a; + + // allocate entity if necessary + if (pev == NULL) + pev = VARS(CREATE_ENTITY()); + + // get the private data + a = (T *)GET_PRIVATE(ENT(pev)); + + if (a == NULL) + { + // allocate private data + a = new(pev) T; + a->pev = pev; + } + return a; +} + + +/* +bit_PUSHBRUSH_DATA | bit_TOGGLE_DATA +bit_MONSTER_DATA +bit_DELAY_DATA +bit_TOGGLE_DATA | bit_DELAY_DATA | bit_MONSTER_DATA +bit_PLAYER_DATA | bit_MONSTER_DATA +bit_MONSTER_DATA | CYCLER_DATA +bit_LIGHT_DATA +path_corner_data +bit_MONSTER_DATA | wildcard_data +bit_MONSTER_DATA | bit_GROUP_DATA +boid_flock_data +boid_data +CYCLER_DATA +bit_ITEM_DATA +bit_ITEM_DATA | func_hud_data +bit_TOGGLE_DATA | bit_ITEM_DATA +EOFFSET +env_sound_data +env_sound_data +push_trigger_data +*/ + +#define TRACER_FREQ 4 // Tracers fire every 4 bullets + +typedef struct _SelAmmo +{ + BYTE Ammo1Type; + BYTE Ammo1; + BYTE Ammo2Type; + BYTE Ammo2; +} SelAmmo; + + +// this moved here from world.cpp, to allow classes to be derived from it +//======================= +// CWorld +// +// This spawns first when each level begins. +//======================= +class CWorld : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + int m_iArenaOff; +}; diff --git a/ricochet/dlls/cdll_dll.h b/ricochet/dlls/cdll_dll.h new file mode 100644 index 0000000..ee324ad --- /dev/null +++ b/ricochet/dlls/cdll_dll.h @@ -0,0 +1,46 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cdll_dll.h + +// this file is included by both the game-dll and the client-dll, + +#ifndef CDLL_DLL_H +#define CDLL_DLL_H + +#define MAX_WEAPONS 32 // ??? + +#define MAX_WEAPON_SLOTS 5 // hud item selection slots +#define MAX_ITEM_TYPES 6 // hud item selection slots + +#define MAX_ITEMS 5 // hard coded item types + +#define HIDEHUD_WEAPONS ( 1<<0 ) +#define HIDEHUD_FLASHLIGHT ( 1<<1 ) +#define HIDEHUD_ALL ( 1<<2 ) +#define HIDEHUD_HEALTH ( 1<<3 ) + +#define MAX_AMMO_TYPES 32 // ??? +#define MAX_AMMO_SLOTS 32 // not really slots + +#define HUD_PRINTNOTIFY 1 +#define HUD_PRINTCONSOLE 2 +#define HUD_PRINTTALK 3 +#define HUD_PRINTCENTER 4 + + +#define WEAPON_SUIT 31 + +#endif diff --git a/ricochet/dlls/client.cpp b/ricochet/dlls/client.cpp new file mode 100644 index 0000000..453e097 --- /dev/null +++ b/ricochet/dlls/client.cpp @@ -0,0 +1,1822 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Robin, 4-22-98: Moved set_suicide_frame() here from player.cpp to allow us to +// have one without a hardcoded player.mdl in tf_client.cpp + +/* + +===== client.cpp ======================================================== + + client/server game specific stuff + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "player.h" +#include "spectator.h" +#include "client.h" +#include "soundent.h" +#include "gamerules.h" +#include "customentity.h" +#include "weapons.h" +#include "weaponinfo.h" +#include "usercmd.h" +#include "netadr.h" +#include "game.h" +#include "disc_objects.h" + +#if !defined ( _WIN32 ) +#include +#endif + +edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ); + +#include "discwar.h" +#include "disc_arena.h" + +extern DLL_GLOBAL ULONG g_ulModelIndexPlayer; +extern DLL_GLOBAL BOOL g_fGameOver; +extern DLL_GLOBAL int g_iSkillLevel; +extern DLL_GLOBAL ULONG g_ulFrameCount; + +extern void CopyToBodyQue(entvars_t* pev); +extern int giPrecacheGrunt; +extern int gmsgSayText; +extern int g_iNextArenaGroupInfo; +extern void AddClientToArena( CBasePlayer *pPlayer ); + +#define SUIT_HUE_START 192 +#define SUIT_HUE_END 223 +#define PLATE_HUE_START 160 +#define PLATE_HUE_END 191 + +int GetHueFromRGB( float r, float g, float b ) +{ + float fMax = max( max( r, g ) , b ); + float fMin = min( min( r, g ) , b ); + float fSaturation = 0; + + if ( fMax != 0 ) + fSaturation = (fMax - fMin) / fMax; + + if ( fSaturation == 0 ) + return 0; + + float fDelta = fMax - fMin; + float fHue = 0; + + if ( r == fMax ) + fHue = ( g - b ) / fDelta; + else if ( g == fMax ) + fHue = 2 + ( b - r ) / fDelta; + else if ( b == fMax ) + fHue = 4 + ( r - g ) / fDelta; + + fHue *= 60; + if ( fHue < 0 ) + fHue += 360; + + // Map it to 0-255 + fHue = fHue / 360; + fHue = (255 * fHue); + + return fHue; +} + +void LinkUserMessages( void ); +/* + * used by kill command and disconnect command + * ROBIN: Moved here from player.cpp, to allow multiple player models + */ +void set_suicide_frame(entvars_t* pev) +{ + if ( !FStrEq(STRING(pev->model), "models/player/female/female.mdl") && !FStrEq(STRING(pev->model), "models/player/male/male.mdl") ) + return; // allready gibbed + +// pev->frame = $deatha11; + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_TOSS; + pev->deadflag = DEAD_DEAD; + pev->nextthink = -1; +} + + +/* +=========== +ClientConnect + +called when a player connects to a server +============ +*/ +BOOL ClientConnect( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) +{ + return g_pGameRules->ClientConnected( pEntity, pszName, pszAddress, szRejectReason ); + +// a client connecting during an intermission can cause problems +// if (intermission_running) +// ExitIntermission (); + +} + + +/* +=========== +ClientDisconnect + +called when a player disconnects from a server + +GLOBALS ASSUMED SET: g_fGameOver +============ +*/ +void ClientDisconnect( edict_t *pEntity ) +{ + // If they're in an arena, remove them + CBasePlayer *pPlayer = (CBasePlayer *)GET_PRIVATE(pEntity); + if ( pPlayer->m_pCurrentArena ) + pPlayer->m_pCurrentArena->RemoveClient( pPlayer ); + + if (g_fGameOver) + return; + +// char text[256]; +// sprintf( text, "- %s has left the game\n", STRING(pEntity->v.netname) ); +// MESSAGE_BEGIN( MSG_BROADCAST, gmsgSayText, NULL ); +// WRITE_BYTE( ENTINDEX(pEntity) ); +// WRITE_STRING( text ); +// MESSAGE_END(); + + // notify other clients the player has left + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "#Game_disconnected", STRING(pEntity->v.netname) ); + + CSound *pSound; + pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( pEntity ) ); + { + // since this client isn't around to think anymore, reset their sound. + if ( pSound ) + { + pSound->Reset(); + } + } + +// since the edict doesn't get deleted, fix it so it doesn't interfere. + pEntity->v.takedamage = DAMAGE_NO;// don't attract autoaim + pEntity->v.solid = SOLID_NOT;// nonsolid + UTIL_SetOrigin ( &pEntity->v, pEntity->v.origin ); + + g_pGameRules->ClientDisconnected( pEntity ); + + pPlayer->m_bHasDisconnected = true; +} + + +// called by ClientKill and DeadThink +void respawn(entvars_t* pev, BOOL fCopyCorpse) +{ + if (gpGlobals->coop || gpGlobals->deathmatch) + { + if ( fCopyCorpse ) + { + // make a copy of the dead body for appearances sake + CopyToBodyQue(pev); + } + + // respawn player + GetClassPtr( (CBasePlayer *)pev)->Spawn( ); + } + else + { // restart the entire server + SERVER_COMMAND("reload\n"); + } +} + +/* +============ +ClientKill + +Player entered the suicide command + +GLOBALS ASSUMED SET: g_ulModelIndexPlayer +============ +*/ +void ClientKill( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + + // Don't allow the suicide command + return; + + CBasePlayer *pl = (CBasePlayer*) CBasePlayer::Instance( pev ); + + if ( pl->m_fNextSuicideTime > gpGlobals->time ) + return; // prevent suiciding too ofter + + pl->m_fNextSuicideTime = gpGlobals->time + 1; // don't let them suicide for 5 seconds after suiciding + + // have the player kill themself + pev->health = 0; + pl->Killed( pev, GIB_NEVER ); + +// pev->modelindex = g_ulModelIndexPlayer; +// pev->frags -= 2; // extra penalty +// respawn( pev ); +} + +/* +=========== +ClientPutInServer + +called when the player is first put in the server +============ +*/ +void ClientPutInServer( edict_t *pEntity ) +{ + CBasePlayer *pPlayer; + + entvars_t *pev = &pEntity->v; + + pPlayer = GetClassPtr((CBasePlayer *)pev); + pPlayer->SetCustomDecalFrames(-1); // Assume none; + + // Allocate a CBasePlayer for pev, and call spawn + pPlayer->Spawn(); + + pPlayer->m_bHasDisconnected = FALSE; + + // Reset interpolation during first frame + pPlayer->pev->effects |= EF_NOINTERP; + pPlayer->m_pCurrentArena = NULL; + pPlayer->m_flKnownItemTime = gpGlobals->time + 0.3; + pPlayer->m_iLastGameResult = GAME_DIDNTPLAY; + + // Add to an Arena (1 maxplayer allows mapmakers to run around their map) + if ( InArenaMode() ) + { + AddClientToArena( pPlayer ); + } + else + { + // Put everyone on different teams + pPlayer->pev->team = ENTINDEX( pEntity ); + pPlayer->pev->iuser4 = pPlayer->pev->team; + + // Set colors + int iHue = GetHueFromRGB( g_iaDiscColors[ pPlayer->pev->team][0] / 255, g_iaDiscColors[pPlayer->pev->team][1] / 255, g_iaDiscColors[pPlayer->pev->team][2] / 255 ); + g_engfuncs.pfnSetClientKeyValue( ENTINDEX( pEntity ), g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "topcolor", UTIL_VarArgs("%d", iHue) ); + g_engfuncs.pfnSetClientKeyValue( ENTINDEX( pEntity ), g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "bottomcolor", UTIL_VarArgs("%d", iHue - 10) ); + } + + static char sName[128]; + strcpy(sName,STRING(pPlayer->pev->netname)); + + // First parse the name and remove any %'s + for ( char *pApersand = sName; pApersand != NULL && *pApersand != 0; pApersand++ ) + { + // Replace it with a space + if ( *pApersand == '%' ) + *pApersand = ' '; + } + + // notify other clients of player joining the game + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "#Game_connected", sName[0] != 0 ? sName : "" ); +} + +#include "voice_gamemgr.h" +extern CVoiceGameMgr g_VoiceGameMgr; + +//// HOST_SAY +// String comes in as +// say blah blah blah +// or as +// blah blah blah +// +void Host_Say( edict_t *pEntity, int teamonly ) +{ + CBasePlayer *client; + int j; + char *p; + char text[128]; + char szTemp[256]; + const char *cpSay = "say"; + const char *cpSayTeam = "say_team"; + const char *pcmd = CMD_ARGV(0); + + entvars_t *pev = &pEntity->v; + CBasePlayer* player = GetClassPtr((CBasePlayer *)pev); + + // We can get a raw string now, without the "say " prepended + if ( CMD_ARGC() == 0 ) + return; + + if ( !stricmp( pcmd, cpSay) || !stricmp( pcmd, cpSayTeam ) ) + { + if ( CMD_ARGC() >= 2 ) + { + p = (char *)CMD_ARGS(); + } + else + { + // say with a blank message, nothing to do + return; + } + } + else // Raw text, need to prepend argv[0] + { + if ( CMD_ARGC() >= 2 ) + { + sprintf( szTemp, "%s %s", ( char * )pcmd, (char *)CMD_ARGS() ); + } + else + { + // Just a one word command, use the first word...sigh + sprintf( szTemp, "%s", ( char * )pcmd ); + } + p = szTemp; + } + +// remove quotes if present + if (*p == '"') + { + p++; + p[strlen(p)-1] = 0; + } + +// make sure the text has content + char *pc; + for ( pc = p; pc != NULL && *pc != 0; pc++ ) + { + if ( isprint( *pc ) && !isspace( *pc ) ) + { + pc = NULL; // we've found an alphanumeric character, so text is valid + break; + } + } + if ( pc != NULL ) + return; // no character found, so say nothing + +// turn on color set 2 (color on, no sound) + if ( teamonly ) + sprintf( text, "%c(TEAM) %s: ", 2, STRING( pEntity->v.netname ) ); + else + sprintf( text, "%c%s: ", 2, STRING( pEntity->v.netname ) ); + + j = sizeof(text) - 2 - strlen(text); // -2 for /n and null terminator + if ( (int)strlen(p) > j ) + p[j] = 0; + + strcat( text, p ); + strcat( text, "\n" ); + + // loop through all players + // Start with the first player. + // This may return the world in single player if the client types something between levels or during spawn + // so check it, or it will infinite loop + + client = NULL; + while ( ((client = (CBasePlayer*)UTIL_FindEntityByClassname( client, "player" )) != NULL) && (!FNullEnt(client->edict())) ) + { + if ( !client->pev ) + continue; + + if ( client->edict() == pEntity ) + continue; + + if ( !(client->IsNetClient()) ) // Not a client ? (should never be true) + continue; + + // can the receiver hear the sender? or has he muted him? + if ( g_VoiceGameMgr.PlayerHasBlockedPlayer( client, player ) ) + continue; + + if ( teamonly && g_pGameRules->PlayerRelationship(client, CBaseEntity::Instance(pEntity)) != GR_TEAMMATE ) + continue; + + MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, client->pev ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + + } + + // print to the sending client + MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, &pEntity->v ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + + // echo to server console + g_engfuncs.pfnServerPrint( text ); + + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" %s \"%s\"\n", + STRING( pEntity->v.netname ), + GETPLAYERUSERID( pEntity ), + GETPLAYERAUTHID( pEntity ), + GETPLAYERUSERID( pEntity ), + teamonly ? cpSayTeam : cpSay, + p ); +} + + +/* +=========== +ClientCommand +called each time a player uses a "cmd" command +============ +*/ +extern float g_flWeaponCheat; + +// Use CMD_ARGV, CMD_ARGV, and CMD_ARGC to get pointers the character string command. +void ClientCommand( edict_t *pEntity ) +{ + const char *pcmd = CMD_ARGV(0); + const char *pstr; + + // Is the client spawned yet? + if ( !pEntity->pvPrivateData ) + return; + + entvars_t *pev = &pEntity->v; + + if ( FStrEq(pcmd, "say" ) ) + { + Host_Say( pEntity, 0 ); + } + else if ( FStrEq(pcmd, "say_team" ) ) + { + Host_Say( pEntity, 1 ); + } + else if ( FStrEq(pcmd, "spectate" ) ) + { + // Prevent this is the cvar is set + if ( allow_spectators.value ) + { + CBasePlayer *pPlayer = GetClassPtr((CBasePlayer *)pev); + edict_t *pentSpawnSpot = EntSelectSpawnPoint( pPlayer ); + pPlayer->StartObserver( VARS(pentSpawnSpot)->origin, VARS(pentSpawnSpot)->angles); + } + } + + else if ( FStrEq(pcmd, "ob_mode" ) ) + { + CBasePlayer *pPlayer = GetClassPtr((CBasePlayer *)pev); + pPlayer->ObserverInput_ChangeMode(); + } + else if ( FStrEq(pcmd, "ob_prev" ) ) + { + CBasePlayer *pPlayer = GetClassPtr((CBasePlayer *)pev); + pPlayer->ObserverInput_PrevPlayer(); + } + else if ( FStrEq(pcmd, "ob_next" ) ) + { + CBasePlayer *pPlayer = GetClassPtr((CBasePlayer *)pev); + pPlayer->ObserverInput_NextPlayer(); + } + + else if ( FStrEq(pcmd, "give" ) ) + { + if ( g_flWeaponCheat != 0.0) + { + int iszItem = ALLOC_STRING( CMD_ARGV(1) ); // Make a copy of the classname + GetClassPtr((CBasePlayer *)pev)->GiveNamedItem( STRING(iszItem) ); + } + } + + else if ( FStrEq(pcmd, "drop" ) ) + { + // player is dropping an item. + GetClassPtr((CBasePlayer *)pev)->DropPlayerItem((char *)CMD_ARGV(1)); + } + else if ( FStrEq(pcmd, "fov" ) ) + { + if ( g_flWeaponCheat && CMD_ARGC() > 1) + { + GetClassPtr((CBasePlayer *)pev)->m_iFOV = atoi( CMD_ARGV(1) ); + } + else + { + CLIENT_PRINTF( pEntity, print_console, UTIL_VarArgs( "\"fov\" is \"%d\"\n", (int)GetClassPtr((CBasePlayer *)pev)->m_iFOV ) ); + } + } + else if ( FStrEq(pcmd, "use" ) ) + { + GetClassPtr((CBasePlayer *)pev)->SelectItem((char *)CMD_ARGV(1)); + } + else if (((pstr = strstr(pcmd, "weapon_")) != NULL) && (pstr == pcmd)) + { + GetClassPtr((CBasePlayer *)pev)->SelectItem(pcmd); + } + else if (FStrEq(pcmd, "lastinv" )) + { + GetClassPtr((CBasePlayer *)pev)->SelectLastItem(); + } + else if ( g_pGameRules->ClientCommand( GetClassPtr((CBasePlayer *)pev), pcmd ) ) + { + // MenuSelect returns true only if the command is properly handled, so don't print a warning + } + else + { + // tell the user they entered an unknown command + char command[128]; + + // check the length of the command (prevents crash) + // max total length is 192 ...and we're adding a string below (#Game_unknown_command) + strncpy( command, pcmd, 127 ); + command[127] = '\0'; + + ClientPrint( &pEntity->v, HUD_PRINTCONSOLE, "#Game_unknown_command", command ); + } +} + +/* +======================== +ClientUserInfoChanged + +called after the player changes +userinfo - gives dll a chance to modify it before +it gets sent into the rest of the engine. +======================== +*/ +void ClientUserInfoChanged( edict_t *pEntity, char *infobuffer ) +{ + // Is the client spawned yet? + if ( !pEntity->pvPrivateData ) + return; + + // msg everyone if someone changes their name, and it isn't the first time (changing no name to current name) + if ( pEntity->v.netname && STRING(pEntity->v.netname)[0] != 0 && !FStrEq( STRING(pEntity->v.netname), g_engfuncs.pfnInfoKeyValue( infobuffer, "name" )) ) + { + char sName[256]; + char *pName = g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ); + strncpy( sName, pName, sizeof(sName) - 1 ); + sName[ sizeof(sName) - 1 ] = '\0'; + + // First parse the name and remove any %'s + for ( char *pApersand = sName; pApersand != NULL && *pApersand != 0; pApersand++ ) + { + // Replace it with a space + if ( *pApersand == '%' ) + *pApersand = ' '; + } + + // Set the name + g_engfuncs.pfnSetClientKeyValue( ENTINDEX(pEntity), infobuffer, "name", sName ); + + char text[256]; + sprintf( text, "* %s changed name to %s\n", STRING(pEntity->v.netname), g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ) ); + MESSAGE_BEGIN( MSG_ALL, gmsgSayText, NULL ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" changed name to \"%s\"\n", + STRING( pEntity->v.netname ), + GETPLAYERUSERID( pEntity ), + GETPLAYERAUTHID( pEntity ), + GETPLAYERUSERID( pEntity ), + g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ) ); + } + + g_pGameRules->ClientUserInfoChanged( GetClassPtr((CBasePlayer *)&pEntity->v), infobuffer ); + + // Override model + if ( (!strcmp( "models/player/female/female.mdl", g_engfuncs.pfnInfoKeyValue( infobuffer, "model" ) )) )//&& (!strcmp( "models/player/hgrunt/hgrunt.mdl" )) ) + SET_MODEL( pEntity, "models/player/male/male.mdl" ); + g_engfuncs.pfnSetClientKeyValue( ENTINDEX( pEntity ), infobuffer, "model", "male" ); + + // Set colors + int iHue = GetHueFromRGB( g_iaDiscColors[ pEntity->v.team][0] / 255, g_iaDiscColors[pEntity->v.team][1] / 255, g_iaDiscColors[pEntity->v.team][2] / 255 ); + g_engfuncs.pfnSetClientKeyValue( ENTINDEX( pEntity ), infobuffer, "topcolor", UTIL_VarArgs("%d", iHue) ); + g_engfuncs.pfnSetClientKeyValue( ENTINDEX( pEntity ), infobuffer, "bottomcolor", UTIL_VarArgs("%d", iHue - 10) ); +} + +static int g_serveractive = 0; + +void ServerDeactivate( void ) +{ + // It's possible that the engine will call this function more times than is necessary + // Therefore, only run it one time for each call to ServerActivate + if ( g_serveractive != 1 ) + { + return; + } + + g_serveractive = 0; + + // Peform any shutdown operations here... + // +} + +void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) +{ + int i; + CBaseEntity *pClass; + + // Every call to ServerActivate should be matched by a call to ServerDeactivate + g_serveractive = 1; + + // Clients have not been initialized yet + for ( i = 0; i < edictCount; i++ ) + { + if ( pEdictList[i].free ) + continue; + + // Clients aren't necessarily initialized until ClientPutInServer() + if ( i < clientMax || !pEdictList[i].pvPrivateData ) + continue; + + pClass = CBaseEntity::Instance( &pEdictList[i] ); + // Activate this entity if it's got a class & isn't dormant + if ( pClass && !(pClass->pev->flags & FL_DORMANT) ) + { + pClass->Activate(); + } + else + { + ALERT( at_console, "Can't instance %s\n", STRING(pEdictList[i].v.classname) ); + } + } + + // Link user messages here to make sure first client can get them... + LinkUserMessages(); + + // Set max speed + SERVER_COMMAND( "sv_maxspeed 320\n" ); + + // Reset Arena Count + g_iNextArenaGroupInfo = 0; +} + + +/* +================ +PlayerPreThink + +Called every frame before physics are run +================ +*/ +void PlayerPreThink( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + CBasePlayer *pPlayer = (CBasePlayer *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->PreThink( ); +} + +/* +================ +PlayerPostThink + +Called every frame after physics are run +================ +*/ +void PlayerPostThink( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + CBasePlayer *pPlayer = (CBasePlayer *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->PostThink( ); +} + + + +void ParmsNewLevel( void ) +{ +} + + +void ParmsChangeLevel( void ) +{ + // retrieve the pointer to the save data + SAVERESTOREDATA *pSaveData = (SAVERESTOREDATA *)gpGlobals->pSaveData; + + if ( pSaveData ) + pSaveData->connectionCount = BuildChangeList( pSaveData->levelList, MAX_LEVEL_CONNECTIONS ); +} + + +// +// GLOBALS ASSUMED SET: g_ulFrameCount +// +void StartFrame( void ) +{ + if ( g_pGameRules ) + g_pGameRules->Think(); + + if ( g_fGameOver ) + return; + + gpGlobals->teamplay = CVAR_GET_FLOAT("teamplay"); + g_iSkillLevel = CVAR_GET_FLOAT("skill"); + g_ulFrameCount++; +} + + +void ClientPrecache( void ) +{ + // setup precaches always needed + PRECACHE_SOUND("player/sprayer.wav"); // spray paint sound for PreAlpha + + // PRECACHE_SOUND("player/pl_jumpland2.wav"); // UNDONE: play 2x step sound + + PRECACHE_SOUND("player/pl_fallpain2.wav"); + PRECACHE_SOUND("player/pl_fallpain3.wav"); + + PRECACHE_SOUND("player/pl_step1.wav"); // walk on concrete + PRECACHE_SOUND("player/pl_step2.wav"); + PRECACHE_SOUND("player/pl_step3.wav"); + PRECACHE_SOUND("player/pl_step4.wav"); + + PRECACHE_SOUND("common/npc_step1.wav"); // NPC walk on concrete + PRECACHE_SOUND("common/npc_step2.wav"); + PRECACHE_SOUND("common/npc_step3.wav"); + PRECACHE_SOUND("common/npc_step4.wav"); + + PRECACHE_SOUND("player/pl_metal1.wav"); // walk on metal + PRECACHE_SOUND("player/pl_metal2.wav"); + PRECACHE_SOUND("player/pl_metal3.wav"); + PRECACHE_SOUND("player/pl_metal4.wav"); + + PRECACHE_SOUND("player/pl_dirt1.wav"); // walk on dirt + PRECACHE_SOUND("player/pl_dirt2.wav"); + PRECACHE_SOUND("player/pl_dirt3.wav"); + PRECACHE_SOUND("player/pl_dirt4.wav"); + + PRECACHE_SOUND("player/pl_duct1.wav"); // walk in duct + PRECACHE_SOUND("player/pl_duct2.wav"); + PRECACHE_SOUND("player/pl_duct3.wav"); + PRECACHE_SOUND("player/pl_duct4.wav"); + + PRECACHE_SOUND("player/pl_grate1.wav"); // walk on grate + PRECACHE_SOUND("player/pl_grate2.wav"); + PRECACHE_SOUND("player/pl_grate3.wav"); + PRECACHE_SOUND("player/pl_grate4.wav"); + + PRECACHE_SOUND("player/pl_slosh1.wav"); // walk in shallow water + PRECACHE_SOUND("player/pl_slosh2.wav"); + PRECACHE_SOUND("player/pl_slosh3.wav"); + PRECACHE_SOUND("player/pl_slosh4.wav"); + + PRECACHE_SOUND("player/pl_tile1.wav"); // walk on tile + PRECACHE_SOUND("player/pl_tile2.wav"); + PRECACHE_SOUND("player/pl_tile3.wav"); + PRECACHE_SOUND("player/pl_tile4.wav"); + PRECACHE_SOUND("player/pl_tile5.wav"); + + PRECACHE_SOUND("player/pl_swim1.wav"); // breathe bubbles + PRECACHE_SOUND("player/pl_swim2.wav"); + PRECACHE_SOUND("player/pl_swim3.wav"); + PRECACHE_SOUND("player/pl_swim4.wav"); + + PRECACHE_SOUND("player/pl_ladder1.wav"); // climb ladder rung + PRECACHE_SOUND("player/pl_ladder2.wav"); + PRECACHE_SOUND("player/pl_ladder3.wav"); + PRECACHE_SOUND("player/pl_ladder4.wav"); + + PRECACHE_SOUND("player/pl_wade1.wav"); // wade in water + PRECACHE_SOUND("player/pl_wade2.wav"); + PRECACHE_SOUND("player/pl_wade3.wav"); + PRECACHE_SOUND("player/pl_wade4.wav"); + + PRECACHE_SOUND("debris/wood1.wav"); // hit wood texture + PRECACHE_SOUND("debris/wood2.wav"); + PRECACHE_SOUND("debris/wood3.wav"); + + PRECACHE_SOUND("plats/train_use1.wav"); // use a train + + PRECACHE_SOUND("buttons/spark5.wav"); // hit computer texture + PRECACHE_SOUND("buttons/spark6.wav"); + PRECACHE_SOUND("debris/glass1.wav"); + PRECACHE_SOUND("debris/glass2.wav"); + PRECACHE_SOUND("debris/glass3.wav"); + + PRECACHE_SOUND( SOUND_FLASHLIGHT_ON ); + PRECACHE_SOUND( SOUND_FLASHLIGHT_OFF ); + +// player gib sounds + PRECACHE_SOUND("common/bodysplat.wav"); + +// player pain sounds + PRECACHE_SOUND("player/pl_pain2.wav"); + PRECACHE_SOUND("player/pl_pain4.wav"); + PRECACHE_SOUND("player/pl_pain5.wav"); + PRECACHE_SOUND("player/pl_pain6.wav"); + PRECACHE_SOUND("player/pl_pain7.wav"); + + //PRECACHE_MODEL("models/player/female/female.mdl"); + PRECACHE_MODEL("models/player/male/male.mdl"); + + // hud sounds + + PRECACHE_SOUND("common/wpn_hudoff.wav"); + PRECACHE_SOUND("common/wpn_hudon.wav"); + PRECACHE_SOUND("common/wpn_moveselect.wav"); + PRECACHE_SOUND("common/wpn_select.wav"); + PRECACHE_SOUND("common/wpn_denyselect.wav"); + + + // geiger sounds + + PRECACHE_SOUND("player/geiger6.wav"); + PRECACHE_SOUND("player/geiger5.wav"); + PRECACHE_SOUND("player/geiger4.wav"); + PRECACHE_SOUND("player/geiger3.wav"); + PRECACHE_SOUND("player/geiger2.wav"); + PRECACHE_SOUND("player/geiger1.wav"); + + if (giPrecacheGrunt) + UTIL_PrecacheOther("monster_human_grunt"); +} + +/* +=============== +const char *GetGameDescription() + +Returns the descriptive name of this .dll. E.g., Half-Life, or Team Fortress 2 +=============== +*/ +const char *GetGameDescription() +{ + if ( g_pGameRules ) // this function may be called before the world has spawned, and the game rules initialized + return g_pGameRules->GetGameDescription(); + else + return "Ricochet"; +} + +/* +================ +Sys_Error + +Engine is going to shut down, allows setting a breakpoint in game .dll to catch that occasion +================ +*/ +void Sys_Error( const char *error_string ) +{ + // Default case, do nothing. MOD AUTHORS: Add code ( e.g., _asm { int 3 }; here to cause a breakpoint for debugging your game .dlls +} + +/* +================ +PlayerCustomization + +A new player customization has been registered on the server +UNDONE: This only sets the # of frames of the spray can logo +animation right now. +================ +*/ +void PlayerCustomization( edict_t *pEntity, customization_t *pCust ) +{ + entvars_t *pev = &pEntity->v; + CBasePlayer *pPlayer = (CBasePlayer *)GET_PRIVATE(pEntity); + + if (!pPlayer) + { + ALERT(at_console, "PlayerCustomization: Couldn't get player!\n"); + return; + } + + if (!pCust) + { + ALERT(at_console, "PlayerCustomization: NULL customization!\n"); + return; + } + + switch (pCust->resource.type) + { + case t_decal: + pPlayer->SetCustomDecalFrames(pCust->nUserData2); // Second int is max # of frames. + break; + case t_sound: + case t_skin: + case t_model: + // Ignore for now. + break; + default: + ALERT(at_console, "PlayerCustomization: Unknown customization type!\n"); + break; + } +} + +/* +================ +SpectatorConnect + +A spectator has joined the game +================ +*/ +void SpectatorConnect( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + CBaseSpectator *pPlayer = (CBaseSpectator *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->SpectatorConnect( ); +} + +/* +================ +SpectatorConnect + +A spectator has left the game +================ +*/ +void SpectatorDisconnect( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + CBaseSpectator *pPlayer = (CBaseSpectator *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->SpectatorDisconnect( ); +} + +/* +================ +SpectatorConnect + +A spectator has sent a usercmd +================ +*/ +void SpectatorThink( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + CBaseSpectator *pPlayer = (CBaseSpectator *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->SpectatorThink( ); +} + + +//////////////////////////////////////////////////////// +// PAS and PVS routines for client messaging +// + +/* +================ +SetupVisibility + +A client can have a separate "view entity" indicating that his/her view should depend on the origin of that +view entity. If that's the case, then pViewEntity will be non-NULL and will be used. Otherwise, the current +entity's origin is used. Either is offset by the view_ofs to get the eye position. + +From the eye position, we set up the PAS and PVS to use for filtering network messages to the client. At this point, we could + override the actual PAS or PVS values, or use a different origin. + +NOTE: Do not cache the values of pas and pvs, as they depend on reusable memory in the engine, they are only good for this one frame +================ +*/ +void SetupVisibility( edict_t *pViewEntity, edict_t *pClient, unsigned char **pvs, unsigned char **pas ) +{ + Vector org; + edict_t *pView = pClient; + + // Find the client's PVS + if ( pViewEntity ) + { + pView = pViewEntity; + } + + // Tracking Spectators use the visibility of their target + CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pClient ); + if ( (pPlayer->pev->iuser2 != 0) && (pPlayer->m_hObserverTarget != NULL) ) + { + pView = pPlayer->m_hObserverTarget->edict(); + } + + org = pView->v.origin + pView->v.view_ofs; + if ( pView->v.flags & FL_DUCKING ) + { + org = org + ( VEC_HULL_MIN - VEC_DUCK_HULL_MIN ); + } + + *pvs = ENGINE_SET_PVS ( (float *)&org ); + *pas = ENGINE_SET_PAS ( (float *)&org ); +} + +#include "entity_state.h" + +/* +AddToFullPack + +Return 1 if the entity state has been filled in for the ent and the entity will be propagated to the client, 0 otherwise + +state is the server maintained copy of the state info that is transmitted to the client +a MOD could alter values copied into state to send the "host" a different look for a particular entity update, etc. +e and ent are the entity that is being added to the update, if 1 is returned +host is the player's edict of the player whom we are sending the update to +player is 1 if the ent/e is a player and 0 otherwise +pSet is either the PAS or PVS that we previous set up. We can use it to ask the engine to filter the entity against the PAS or PVS. +we could also use the pas/ pvs that we set in SetupVisibility, if we wanted to. Caching the value is valid in that case, but still only for the current frame +*/ +int AddToFullPack( struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet ) +{ + int i; + + // don't send if flagged for NODRAW and it's not the host getting the message + if ( ( ent->v.effects & EF_NODRAW ) && + ( ent != host ) ) + return 0; + + // Ignore ents without valid / visible models + if ( !ent->v.modelindex || !STRING( ent->v.model ) ) + return 0; + + // Don't send spectators to other players + if ( ( ent->v.flags & FL_SPECTATOR ) && ( ent != host ) ) + { + return 0; + } + + // Ignore if not the host and not touching a PVS/PAS leaf + // If pSet is NULL, then the test will always succeed and the entity will be added to the update + if ( ent != host ) + { + if ( !ENGINE_CHECK_VISIBILITY( (const struct edict_s *)ent, pSet ) ) + { + return 0; + } + } + + + // Don't send entity to local client if the client says it's predicting the entity itself. + if ( ent->v.flags & FL_SKIPLOCALHOST ) + { + if ( ( hostflags & 1 ) && ( ent->v.owner == host ) ) + return 0; + } + + if ( host->v.groupinfo ) + { + UTIL_SetGroupTrace( host->v.groupinfo, GROUP_OP_AND ); + + // Should always be set, of course + if ( ent->v.groupinfo ) + { + if ( g_groupop == GROUP_OP_AND ) + { + if ( !(ent->v.groupinfo & host->v.groupinfo ) ) + return 0; + } + else if ( g_groupop == GROUP_OP_NAND ) + { + if ( ent->v.groupinfo & host->v.groupinfo ) + return 0; + } + } + + UTIL_UnsetGroupTrace(); + } + + memset( state, 0, sizeof( *state ) ); + + // Assign index so we can track this entity from frame to frame and + // delta from it. + state->number = e; + state->entityType = ENTITY_NORMAL; + + // Flag custom entities. + if ( ent->v.flags & FL_CUSTOMENTITY ) + { + state->entityType = ENTITY_BEAM; + } + + // + // Copy state data + // + + // Round animtime to nearest millisecond + state->animtime = (int)(1000.0 * ent->v.animtime ) / 1000.0; + + memcpy( state->origin, ent->v.origin, 3 * sizeof( float ) ); + memcpy( state->angles, ent->v.angles, 3 * sizeof( float ) ); + memcpy( state->mins, ent->v.mins, 3 * sizeof( float ) ); + memcpy( state->maxs, ent->v.maxs, 3 * sizeof( float ) ); + + memcpy( state->startpos, ent->v.startpos, 3 * sizeof( float ) ); + memcpy( state->endpos, ent->v.endpos, 3 * sizeof( float ) ); + + state->impacttime = ent->v.impacttime; + state->starttime = ent->v.starttime; + + state->modelindex = ent->v.modelindex; + + state->frame = ent->v.frame; + + state->skin = ent->v.skin; + state->effects = ent->v.effects; + + // This non-player entity is being moved by the game .dll and not the physics simulation system + // make sure that we interpolate it's position on the client if it moves + if ( !player && + ent->v.animtime && + ent->v.velocity[ 0 ] == 0 && + ent->v.velocity[ 1 ] == 0 && + ent->v.velocity[ 2 ] == 0 ) + { + state->eflags |= EFLAG_SLERP; + } + + state->scale = ent->v.scale; + state->solid = ent->v.solid; + state->colormap = ent->v.colormap; + state->movetype = ent->v.movetype; + state->sequence = ent->v.sequence; + state->framerate = ent->v.framerate; + state->body = ent->v.body; + + for (i = 0; i < 4; i++) + { + state->controller[i] = ent->v.controller[i]; + } + + for (i = 0; i < 2; i++) + { + state->blending[i] = ent->v.blending[i]; + } + + state->rendermode = ent->v.rendermode; + state->renderamt = ent->v.renderamt; + state->renderfx = ent->v.renderfx; + state->rendercolor.r = ent->v.rendercolor[0]; + state->rendercolor.g = ent->v.rendercolor[1]; + state->rendercolor.b = ent->v.rendercolor[2]; + + state->aiment = 0; + if ( ent->v.aiment ) + { + state->aiment = ENTINDEX( ent->v.aiment ); + } + + state->owner = 0; + if ( ent->v.owner ) + { + int owner = ENTINDEX( ent->v.owner ); + + // Only care if owned by a player + if ( owner >= 1 && owner <= gpGlobals->maxClients ) + { + state->owner = owner; + } + } + + // Special stuff for players only + if ( player ) + { + memcpy( state->basevelocity, ent->v.basevelocity, 3 * sizeof( float ) ); + + state->weaponmodel = MODEL_INDEX( STRING( ent->v.weaponmodel ) ); + state->gaitsequence = ent->v.gaitsequence; + state->spectator = ent->v.flags & FL_SPECTATOR; + state->friction = ent->v.friction; + + state->gravity = ent->v.gravity; + state->team = ent->v.team; + state->playerclass = ent->v.playerclass; + state->usehull = ( ent->v.flags & FL_DUCKING ) ? 1 : 0; + state->health = ent->v.health; + } + + return 1; +} + +// defaults for clientinfo messages +#define DEFAULT_VIEWHEIGHT 28 + +/* +=================== +CreateBaseline + +Creates baselines used for network encoding, especially for player data since players are not spawned until connect time. +=================== +*/ +void CreateBaseline( int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, vec3_t player_mins, vec3_t player_maxs ) +{ + baseline->origin = entity->v.origin; + baseline->angles = entity->v.angles; + baseline->frame = entity->v.frame; + baseline->skin = (short)entity->v.skin; + + // render information + baseline->rendermode = (byte)entity->v.rendermode; + baseline->renderamt = (byte)entity->v.renderamt; + baseline->rendercolor.r = (byte)entity->v.rendercolor[0]; + baseline->rendercolor.g = (byte)entity->v.rendercolor[1]; + baseline->rendercolor.b = (byte)entity->v.rendercolor[2]; + baseline->renderfx = (byte)entity->v.renderfx; + + if ( player ) + { + baseline->mins = player_mins; + baseline->maxs = player_maxs; + + baseline->colormap = eindex; + baseline->modelindex = playermodelindex; + baseline->friction = 1.0; + baseline->movetype = MOVETYPE_WALK; + + baseline->scale = entity->v.scale; + baseline->solid = SOLID_SLIDEBOX; + baseline->framerate = 1.0; + baseline->gravity = 1.0; + + baseline->playerclass = entity->v.playerclass; + } + else + { + baseline->mins = entity->v.mins; + baseline->maxs = entity->v.maxs; + + baseline->colormap = 0; + baseline->modelindex = entity->v.modelindex;//SV_ModelIndex(pr_strings + entity->v.model); + baseline->movetype = entity->v.movetype; + + baseline->scale = entity->v.scale; + baseline->solid = entity->v.solid; + baseline->framerate = entity->v.framerate; + baseline->gravity = entity->v.gravity; + } +} + +typedef struct +{ + char name[32]; + int field; +} entity_field_alias_t; + +#define FIELD_ORIGIN0 0 +#define FIELD_ORIGIN1 1 +#define FIELD_ORIGIN2 2 +#define FIELD_ANGLES0 3 +#define FIELD_ANGLES1 4 +#define FIELD_ANGLES2 5 + +static entity_field_alias_t entity_field_alias[]= +{ + { "origin[0]", 0 }, + { "origin[1]", 0 }, + { "origin[2]", 0 }, + { "angles[0]", 0 }, + { "angles[1]", 0 }, + { "angles[2]", 0 }, +}; + +void Entity_FieldInit( struct delta_s *pFields ) +{ + entity_field_alias[ FIELD_ORIGIN0 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ORIGIN0 ].name ); + entity_field_alias[ FIELD_ORIGIN1 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ORIGIN1 ].name ); + entity_field_alias[ FIELD_ORIGIN2 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ORIGIN2 ].name ); + entity_field_alias[ FIELD_ANGLES0 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ANGLES0 ].name ); + entity_field_alias[ FIELD_ANGLES1 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ANGLES1 ].name ); + entity_field_alias[ FIELD_ANGLES2 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ANGLES2 ].name ); +} + +/* +================== +Entity_Encode + +Callback for sending entity_state_t info over network. +FIXME: Move to script +================== +*/ +void Entity_Encode( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) +{ + entity_state_t *f, *t; + + int localplayer = 0; + static int initialized = 0; + + if ( !initialized ) + { + Entity_FieldInit( pFields ); + initialized = 1; + } + + f = (entity_state_t *)from; + t = (entity_state_t *)to; + + // Never send origin to local player, it's sent with more resolution in clientdata_t structure + localplayer = ( t->number - 1 ) == ENGINE_CURRENT_PLAYER(); + if ( localplayer ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + + if ( ( t->impacttime != 0 ) && ( t->starttime != 0 ) ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ANGLES0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ANGLES1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ANGLES2 ].field ); + } + + if ( ( t->movetype == MOVETYPE_FOLLOW ) && + ( t->aiment != 0 ) ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + else if ( t->aiment != f->aiment ) + { + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } +} + +static entity_field_alias_t player_field_alias[]= +{ + { "origin[0]", 0 }, + { "origin[1]", 0 }, + { "origin[2]", 0 }, +}; + +void Player_FieldInit( struct delta_s *pFields ) +{ + player_field_alias[ FIELD_ORIGIN0 ].field = DELTA_FINDFIELD( pFields, player_field_alias[ FIELD_ORIGIN0 ].name ); + player_field_alias[ FIELD_ORIGIN1 ].field = DELTA_FINDFIELD( pFields, player_field_alias[ FIELD_ORIGIN1 ].name ); + player_field_alias[ FIELD_ORIGIN2 ].field = DELTA_FINDFIELD( pFields, player_field_alias[ FIELD_ORIGIN2 ].name ); +} + +/* +================== +Player_Encode + +Callback for sending entity_state_t for players info over network. +================== +*/ +void Player_Encode( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) +{ + entity_state_t *f, *t; + int localplayer = 0; + static int initialized = 0; + + if ( !initialized ) + { + Player_FieldInit( pFields ); + initialized = 1; + } + + f = (entity_state_t *)from; + t = (entity_state_t *)to; + + // Never send origin to local player, it's sent with more resolution in clientdata_t structure + localplayer = ( t->number - 1 ) == ENGINE_CURRENT_PLAYER(); + if ( localplayer ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + + if ( ( t->movetype == MOVETYPE_FOLLOW ) && + ( t->aiment != 0 ) ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + else if ( t->aiment != f->aiment ) + { + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } +} + +#define CUSTOMFIELD_ORIGIN0 0 +#define CUSTOMFIELD_ORIGIN1 1 +#define CUSTOMFIELD_ORIGIN2 2 +#define CUSTOMFIELD_ANGLES0 3 +#define CUSTOMFIELD_ANGLES1 4 +#define CUSTOMFIELD_ANGLES2 5 +#define CUSTOMFIELD_SKIN 6 +#define CUSTOMFIELD_SEQUENCE 7 +#define CUSTOMFIELD_ANIMTIME 8 + +entity_field_alias_t custom_entity_field_alias[]= +{ + { "origin[0]", 0 }, + { "origin[1]", 0 }, + { "origin[2]", 0 }, + { "angles[0]", 0 }, + { "angles[1]", 0 }, + { "angles[2]", 0 }, + { "skin", 0 }, + { "sequence", 0 }, + { "animtime", 0 }, +}; + +void Custom_Entity_FieldInit( struct delta_s *pFields ) +{ + custom_entity_field_alias[ CUSTOMFIELD_ORIGIN0 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN0 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ORIGIN1 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN1 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ORIGIN2 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN2 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANGLES0 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES0 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANGLES1 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES1 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANGLES2 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES2 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_SKIN ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_SKIN ].name ); + custom_entity_field_alias[ CUSTOMFIELD_SEQUENCE ].field= DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_SEQUENCE ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANIMTIME ].field= DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANIMTIME ].name ); +} + +/* +================== +Custom_Encode + +Callback for sending entity_state_t info ( for custom entities ) over network. +FIXME: Move to script +================== +*/ +void Custom_Encode( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) +{ + entity_state_t *f, *t; + int beamType; + static int initialized = 0; + + if ( !initialized ) + { + Custom_Entity_FieldInit( pFields ); + initialized = 1; + } + + f = (entity_state_t *)from; + t = (entity_state_t *)to; + + beamType = t->rendermode & 0x0f; + + if ( beamType != BEAM_POINTS && beamType != BEAM_ENTPOINT ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN2 ].field ); + } + + if ( beamType != BEAM_POINTS ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES0 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES1 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES2 ].field ); + } + + if ( beamType != BEAM_ENTS && beamType != BEAM_ENTPOINT ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_SKIN ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_SEQUENCE ].field ); + } + + // animtime is compared by rounding first + // see if we really shouldn't actually send it + if ( (int)f->animtime == (int)t->animtime ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANIMTIME ].field ); + } +} + +/* +================= +RegisterEncoders + +Allows game .dll to override network encoding of certain types of entities and tweak values, etc. +================= +*/ +void RegisterEncoders( void ) +{ + DELTA_ADDENCODER( "Entity_Encode", Entity_Encode ); + DELTA_ADDENCODER( "Custom_Encode", Custom_Encode ); + DELTA_ADDENCODER( "Player_Encode", Player_Encode ); +} + +int GetWeaponData( struct edict_s *player, struct weapon_data_s *info ) +{ + int i; + weapon_data_t *item; + entvars_t *pev = &player->v; + CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); + CBasePlayerWeapon *gun; + + ItemInfo II; + + memset( info, 0, 32 * sizeof( weapon_data_t ) ); + + if ( !pl ) + return 1; + + // go through all of the weapons and make a list of the ones to pack + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( pl->m_rgpPlayerItems[ i ] ) + { + // there's a weapon here. Should I pack it? + CBasePlayerItem *pPlayerItem = pl->m_rgpPlayerItems[ i ]; + + while ( pPlayerItem ) + { + gun = (CBasePlayerWeapon *)pPlayerItem->GetWeaponPtr(); + if ( gun && gun->UseDecrement() ) + { + // Get The ID. + memset( &II, 0, sizeof( II ) ); + gun->GetItemInfo( &II ); + + if ( II.iId >= 0 && II.iId < 32 ) + { + item = &info[ II.iId ]; + + item->m_iId = II.iId; + item->m_iClip = pl->m_rgAmmo[gun->m_iPrimaryAmmoType];//gun->m_iClip; + + item->m_flTimeWeaponIdle = max( gun->m_flTimeWeaponIdle, -0.001 ); + item->m_flNextPrimaryAttack = max( gun->m_flNextPrimaryAttack, -0.001 ); + item->m_flNextSecondaryAttack = max( gun->m_flNextSecondaryAttack, -0.001 ); + item->m_fInReload = gun->m_fInReload; + } + } + pPlayerItem = pPlayerItem->m_pNext; + } + } + } + return 1; +} + +/* +================= +UpdateClientData + +Data sent to current client only +engine sets cd to 0 before calling. +================= +*/ +void UpdateClientData ( const struct edict_s *ent, int sendweapons, struct clientdata_s *cd ) +{ + cd->flags = ent->v.flags; + cd->health = ent->v.health; + + cd->viewmodel = MODEL_INDEX( STRING( ent->v.viewmodel ) ); + cd->waterlevel = ent->v.waterlevel; + cd->watertype = ent->v.watertype; + cd->weapons = ent->v.weapons; + + // Vectors + cd->origin = ent->v.origin; + cd->velocity = ent->v.velocity; + cd->view_ofs = ent->v.view_ofs; + cd->punchangle = ent->v.punchangle; + + cd->bInDuck = ent->v.bInDuck; + cd->flTimeStepSound = ent->v.flTimeStepSound; + cd->flDuckTime = ent->v.flDuckTime; + cd->flSwimTime = ent->v.flSwimTime; + cd->waterjumptime = ent->v.teleport_time; + + strcpy( cd->physinfo, ENGINE_GETPHYSINFO( ent ) ); + + cd->maxspeed = ent->v.maxspeed; + cd->fov = ent->v.fov; + cd->weaponanim = ent->v.weaponanim; + + cd->pushmsec = ent->v.pushmsec; + + // Spectator + cd->iuser1 = ent->v.iuser1; + cd->iuser2 = ent->v.iuser2; + + if ( sendweapons ) + { + entvars_t *pev = (entvars_t *)&ent->v; + CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); + + if ( pl ) + { + cd->m_flNextAttack = pl->m_flNextAttack; + + if ( pl->m_pActiveItem ) + { + CBasePlayerWeapon *gun; + gun = (CBasePlayerWeapon *)pl->m_pActiveItem->GetWeaponPtr(); + if ( gun && gun->UseDecrement() ) + { + ItemInfo II; + memset( &II, 0, sizeof( II ) ); + gun->GetItemInfo( &II ); + + cd->m_iId = II.iId; + } + } + } + } +} + +/* +================= +CmdStart + +We're about to run this usercmd for the specified player. We can set up groupinfo and masking here, etc. +This is the time to examine the usercmd for anything extra. This call happens even if think does not. +================= +*/ +void CmdStart( const edict_t *player, const struct usercmd_s *cmd, unsigned int random_seed ) +{ + entvars_t *pev = (entvars_t *)&player->v; + CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); + + if( !pl ) + return; + + if ( pl->pev->groupinfo != 0 ) + { + UTIL_SetGroupTrace( pl->pev->groupinfo, GROUP_OP_AND ); + } + + pl->random_seed = random_seed; +} + +/* +================= +CmdEnd + +Each cmdstart is exactly matched with a cmd end, clean up any group trace flags, etc. here +================= +*/ +void CmdEnd ( const edict_t *player ) +{ + entvars_t *pev = (entvars_t *)&player->v; + CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); + + if( !pl ) + return; + if ( pl->pev->groupinfo != 0 ) + { + UTIL_UnsetGroupTrace(); + } +} + +/* +================================ +ConnectionlessPacket + + Return 1 if the packet is valid. Set response_buffer_size if you want to send a response packet. Incoming, it holds the max + size of the response_buffer, so you must zero it out if you choose not to respond. +================================ +*/ +int ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ) +{ + // Parse stuff from args + int max_buffer_size = *response_buffer_size; + + // Zero it out since we aren't going to respond. + // If we wanted to response, we'd write data into response_buffer + *response_buffer_size = 0; + + // Since we don't listen for anything here, just respond that it's a bogus message + // If we didn't reject the message, we'd return 1 for success instead. + return 0; +} + +/* +================================ +GetHullBounds + + Engine calls this to enumerate player collision hulls, for prediction. Return 0 if the hullnumber doesn't exist. +================================ +*/ +int GetHullBounds( int hullnumber, float *mins, float *maxs ) +{ + int iret = 0; + + switch ( hullnumber ) + { + case 0: // Normal player + mins = VEC_HULL_MIN; + maxs = VEC_HULL_MAX; + iret = 1; + break; + case 1: // Crouched player + mins = VEC_DUCK_HULL_MIN; + maxs = VEC_DUCK_HULL_MAX; + iret = 1; + break; + case 2: // Point based hull + mins = Vector( 0, 0, 0 ); + maxs = Vector( 0, 0, 0 ); + iret = 1; + break; + } + + return iret; +} + +/* +================================ +CreateInstancedBaselines + +Create pseudo-baselines for items that aren't placed in the map at spawn time, but which are likely +to be created during play ( e.g., grenades, ammo packs, projectiles, corpses, etc. ) +================================ +*/ +void CreateInstancedBaselines ( void ) +{ + int iret = 0; + entity_state_t state; + + memset( &state, 0, sizeof( state ) ); + + // Create any additional baselines here for things like grendates, etc. + // iret = ENGINE_INSTANCE_BASELINE( pc->pev->classname, &state ); + + // Destroy objects. + //UTIL_Remove( pc ); +} + +/* +================================ +InconsistentFile + +One of the ENGINE_FORCE_UNMODIFIED files failed the consistency check for the specified player + Return 0 to allow the client to continue, 1 to force immediate disconnection ( with an optional disconnect message of up to 256 characters ) +================================ +*/ +int InconsistentFile( const edict_t *player, const char *filename, char *disconnect_message ) +{ + // Server doesn't care? + if ( CVAR_GET_FLOAT( "mp_consistency" ) != 1 ) + return 0; + + // Default behavior is to kick the player + sprintf( disconnect_message, "Server is enforcing file consistency for %s\n", filename ); + + // Kick now with specified disconnect message. + return 1; +} + +/* +================================ +AllowLagCompensation + + The game .dll should return 1 if lag compensation should be allowed ( could also just set + the sv_unlag cvar. + Most games right now should return 0, until client-side weapon prediction code is written + and tested for them ( note you can predict weapons, but not do lag compensation, too, + if you want. +================================ +*/ +int AllowLagCompensation( void ) +{ + return 0; +} + + +/* +================================ +ShouldCollide + + Called when the engine believes two entities are about to collide. Return 0 if you + want the two entities to just pass through each other without colliding or calling the + touch function. +================================ +*/ +int ShouldCollide( edict_t *pentTouched, edict_t *pentOther ) +{ + // To make this a fast check, use iuser4 for the discs to know what other discs they should collide with + if ( pentTouched->v.iuser4 != 0 && pentOther->v.iuser4 != 0 ) + { + // Two friendly discs will have matching iuser4's + if ( pentTouched->v.iuser4 == pentOther->v.iuser4 ) + { + return 0; + } + + // Discs hitting their owners + CBaseEntity *pTouched = CBaseEntity::Instance(pentTouched); + CBaseEntity *pOther = CBaseEntity::Instance(pentOther); + /* + if ( pOther->IsDisc() && pTouched->IsPlayer() && ((CDisc*)pOther)->m_hOwner == pTouched ) + return 0; + if ( pTouched->IsDisc() && pOther->IsPlayer() && ((CDisc*)pTouched)->m_hOwner == pOther ) + return 0; + */ + //if ( pOther->IsDisc() || pTouched->IsDisc() ) + //return 0; + } + + return 1; +} diff --git a/ricochet/dlls/client.h b/ricochet/dlls/client.h new file mode 100644 index 0000000..0404b6a --- /dev/null +++ b/ricochet/dlls/client.h @@ -0,0 +1,67 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef CLIENT_H +#define CLIENT_H + +extern void respawn( entvars_t* pev, BOOL fCopyCorpse ); +extern BOOL ClientConnect( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); +extern void ClientDisconnect( edict_t *pEntity ); +extern void ClientKill( edict_t *pEntity ); +extern void ClientPutInServer( edict_t *pEntity ); +extern void ClientCommand( edict_t *pEntity ); +extern void ClientUserInfoChanged( edict_t *pEntity, char *infobuffer ); +extern void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ); +extern void ServerDeactivate( void ); +extern void StartFrame( void ); +extern void PlayerPostThink( edict_t *pEntity ); +extern void PlayerPreThink( edict_t *pEntity ); +extern void ParmsNewLevel( void ); +extern void ParmsChangeLevel( void ); + +extern void ClientPrecache( void ); + +extern const char *GetGameDescription( void ); +extern void PlayerCustomization( edict_t *pEntity, customization_t *pCust ); + +extern void SpectatorConnect ( edict_t *pEntity ); +extern void SpectatorDisconnect ( edict_t *pEntity ); +extern void SpectatorThink ( edict_t *pEntity ); + +extern void Sys_Error( const char *error_string ); + +extern void SetupVisibility( edict_t *pViewEntity, edict_t *pClient, unsigned char **pvs, unsigned char **pas ); +extern void UpdateClientData ( const struct edict_s *ent, int sendweapons, struct clientdata_s *cd ); +extern int AddToFullPack( struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet ); +extern void CreateBaseline( int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, vec3_t player_mins, vec3_t player_maxs ); +extern void RegisterEncoders( void ); + +extern int GetWeaponData( struct edict_s *player, struct weapon_data_s *info ); + +extern void CmdStart( const edict_t *player, const struct usercmd_s *cmd, unsigned int random_seed ); +extern void CmdEnd ( const edict_t *player ); + +extern int ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); + +extern int GetHullBounds( int hullnumber, float *mins, float *maxs ); + +extern void CreateInstancedBaselines ( void ); + +extern int InconsistentFile( const edict_t *player, const char *filename, char *disconnect_message ); + +extern int AllowLagCompensation( void ); + +extern int ShouldCollide( edict_t *pentTouched, edict_t *pentOther ); + +#endif // CLIENT_H diff --git a/ricochet/dlls/combat.cpp b/ricochet/dlls/combat.cpp new file mode 100644 index 0000000..286b506 --- /dev/null +++ b/ricochet/dlls/combat.cpp @@ -0,0 +1,1453 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== combat.cpp ======================================================== + + functions dealing with damage infliction & death + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "soundent.h" +#include "decals.h" +#include "animation.h" +#include "weapons.h" +#include "func_break.h" + +extern DLL_GLOBAL Vector g_vecAttackDir; +extern DLL_GLOBAL int g_iSkillLevel; + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); +extern entvars_t *g_pevLastInflictor; + +#define GERMAN_GIB_COUNT 4 +#define HUMAN_GIB_COUNT 6 +#define ALIEN_GIB_COUNT 4 + + +// HACKHACK -- The gib velocity equations don't work +void CGib :: LimitVelocity( void ) +{ + float length = pev->velocity.Length(); + + // ceiling at 1500. The gib velocity equation is not bounded properly. Rather than tune it + // in 3 separate places again, I'll just limit it here. + if ( length > 1500.0 ) + pev->velocity = pev->velocity.Normalize() * 1500; // This should really be sv_maxvelocity * 0.75 or something +} + + +void CGib :: SpawnStickyGibs( entvars_t *pevVictim, Vector vecOrigin, int cGibs ) +{ + int i; + + if ( g_Language == LANGUAGE_GERMAN ) + { + // no sticky gibs in germany right now! + return; + } + + for ( i = 0 ; i < cGibs ; i++ ) + { + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + pGib->Spawn( "models/stickygib.mdl" ); + pGib->pev->body = RANDOM_LONG(0,2); + + if ( pevVictim ) + { + pGib->pev->origin.x = vecOrigin.x + RANDOM_FLOAT( -3, 3 ); + pGib->pev->origin.y = vecOrigin.y + RANDOM_FLOAT( -3, 3 ); + pGib->pev->origin.z = vecOrigin.z + RANDOM_FLOAT( -3, 3 ); + + /* + pGib->pev->origin.x = pevVictim->absmin.x + pevVictim->size.x * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.y = pevVictim->absmin.y + pevVictim->size.y * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.z = pevVictim->absmin.z + pevVictim->size.z * (RANDOM_FLOAT ( 0 , 1 ) ); + */ + + // make the gib fly away from the attack vector + pGib->pev->velocity = g_vecAttackDir * -1; + + // mix in some noise + pGib->pev->velocity.x += RANDOM_FLOAT ( -0.15, 0.15 ); + pGib->pev->velocity.y += RANDOM_FLOAT ( -0.15, 0.15 ); + pGib->pev->velocity.z += RANDOM_FLOAT ( -0.15, 0.15 ); + + pGib->pev->velocity = pGib->pev->velocity * 900; + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 250, 400 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 250, 400 ); + + // copy owner's blood color + pGib->m_bloodColor = (CBaseEntity::Instance(pevVictim))->BloodColor(); + + if ( pevVictim->health > -50) + { + pGib->pev->velocity = pGib->pev->velocity * 0.7; + } + else if ( pevVictim->health > -200) + { + pGib->pev->velocity = pGib->pev->velocity * 2; + } + else + { + pGib->pev->velocity = pGib->pev->velocity * 4; + } + + + pGib->pev->movetype = MOVETYPE_TOSS; + pGib->pev->solid = SOLID_BBOX; + UTIL_SetSize ( pGib->pev, Vector ( 0, 0 ,0 ), Vector ( 0, 0, 0 ) ); + pGib->SetTouch ( &CGib::StickyGibTouch ); + pGib->SetThink (NULL); + } + pGib->LimitVelocity(); + } +} + +void CGib :: SpawnHeadGib( entvars_t *pevVictim ) +{ + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + if ( g_Language == LANGUAGE_GERMAN ) + { + pGib->Spawn( "models/germangibs.mdl" );// throw one head + pGib->pev->body = 0; + } + else + { + pGib->Spawn( "models/head.mdl" );// throw one head + } + + if ( pevVictim ) + { + pGib->pev->origin = pevVictim->origin + pevVictim->view_ofs; + + edict_t *pentPlayer = FIND_CLIENT_IN_PVS( pGib->edict() ); + + pGib->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(300,400)); + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + // copy owner's blood color + pGib->m_bloodColor = (CBaseEntity::Instance(pevVictim))->BloodColor(); + + // Set its groupinfo to the player's + pGib->pev->groupinfo = pevVictim->groupinfo; + // Since this only ever happens when a player is hit by a frozen decapitator disc, make the gibs glow + pGib->pev->renderfx = kRenderFxGlowShell; + pGib->pev->rendercolor = Vector( 100,100, 250 ); + pGib->pev->renderamt = 25; + } + pGib->LimitVelocity(); +} + +void CGib :: SpawnRandomGibs( entvars_t *pevVictim, int cGibs, int human ) +{ + int cSplat; + + for ( cSplat = 0 ; cSplat < cGibs ; cSplat++ ) + { + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + if ( g_Language == LANGUAGE_GERMAN ) + { + pGib->Spawn( "models/germangibs.mdl" ); + pGib->pev->body = RANDOM_LONG(0,GERMAN_GIB_COUNT-1); + } + else + { + if ( human ) + { + // human pieces + pGib->Spawn( "models/hgibs.mdl" ); + pGib->pev->body = RANDOM_LONG(1,HUMAN_GIB_COUNT-1);// start at one to avoid throwing random amounts of skulls (0th gib) + } + else + { + // aliens + pGib->Spawn( "models/agibs.mdl" ); + pGib->pev->body = RANDOM_LONG(0,ALIEN_GIB_COUNT-1); + } + } + + if ( pevVictim ) + { + // spawn the gib somewhere in the monster's bounding volume + pGib->pev->origin.x = pevVictim->absmin.x + pevVictim->size.x * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.y = pevVictim->absmin.y + pevVictim->size.y * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.z = pevVictim->absmin.z + pevVictim->size.z * (RANDOM_FLOAT ( 0 , 1 ) ) + 1; // absmin.z is in the floor because the engine subtracts 1 to enlarge the box + + // make the gib fly away from the attack vector + pGib->pev->velocity = g_vecAttackDir * -1; + + // mix in some noise + pGib->pev->velocity.x += RANDOM_FLOAT ( -0.25, 0.25 ); + pGib->pev->velocity.y += RANDOM_FLOAT ( -0.25, 0.25 ); + pGib->pev->velocity.z += RANDOM_FLOAT ( -0.25, 0.25 ); + + pGib->pev->velocity = pGib->pev->velocity * RANDOM_FLOAT ( 600, 700 ); + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + // copy owner's blood color + pGib->m_bloodColor = (CBaseEntity::Instance(pevVictim))->BloodColor(); + + if ( pevVictim->health > -50) + { + pGib->pev->velocity = pGib->pev->velocity * 0.7; + } + else if ( pevVictim->health > -200) + { + pGib->pev->velocity = pGib->pev->velocity * 2; + } + else + { + pGib->pev->velocity = pGib->pev->velocity * 4; + } + + pGib->pev->solid = SOLID_BBOX; + UTIL_SetSize ( pGib->pev, Vector( 0 , 0 , 0 ), Vector ( 0, 0, 0 ) ); + + // Set its groupinfo to the player's + pGib->pev->groupinfo = pevVictim->groupinfo; + // Since this only ever happens when a player is hit by a frozen decapitator disc, make the gibs glow + pGib->pev->renderfx = kRenderFxGlowShell; + pGib->pev->rendercolor = Vector( 150,150,250 ); + pGib->pev->renderamt = 100; + } + pGib->LimitVelocity(); + } +} + + +BOOL CBaseMonster :: HasHumanGibs( void ) +{ + int myClass = Classify(); + + if ( myClass == CLASS_HUMAN_MILITARY || + myClass == CLASS_PLAYER_ALLY || + myClass == CLASS_HUMAN_PASSIVE || + myClass == CLASS_PLAYER ) + + return TRUE; + + return FALSE; +} + + +BOOL CBaseMonster :: HasAlienGibs( void ) +{ + int myClass = Classify(); + + if ( myClass == CLASS_ALIEN_MILITARY || + myClass == CLASS_ALIEN_MONSTER || + myClass == CLASS_ALIEN_PASSIVE || + myClass == CLASS_INSECT || + myClass == CLASS_ALIEN_PREDATOR || + myClass == CLASS_ALIEN_PREY ) + + return TRUE; + + return FALSE; +} + + +void CBaseMonster::FadeMonster( void ) +{ + StopAnimation(); + pev->velocity = g_vecZero; + pev->movetype = MOVETYPE_NONE; + pev->avelocity = g_vecZero; + pev->animtime = gpGlobals->time; + pev->effects |= EF_NOINTERP; + SUB_StartFadeOut(); +} + +//========================================================= +// GibMonster - create some gore and get rid of a monster's +// model. +//========================================================= +void CBaseMonster :: GibMonster( void ) +{ + TraceResult tr; + BOOL gibbed = FALSE; + + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "common/bodysplat.wav", 1, ATTN_NORM); + + // only humans throw skulls !!!UNDONE - eventually monsters will have their own sets of gibs + if ( HasHumanGibs() ) + { + if ( CVAR_GET_FLOAT("violence_hgibs") != 0 ) // Only the player will ever get here + { + CGib::SpawnHeadGib( pev ); + CGib::SpawnRandomGibs( pev, 4, 1 ); // throw some human gibs. + } + gibbed = TRUE; + } + else if ( HasAlienGibs() ) + { + if ( CVAR_GET_FLOAT("violence_agibs") != 0 ) // Should never get here, but someone might call it directly + { + CGib::SpawnRandomGibs( pev, 4, 0 ); // Throw alien gibs + } + gibbed = TRUE; + } + + if ( !IsPlayer() ) + { + if ( gibbed ) + { + // don't remove players! + SetThink ( &CBaseMonster::SUB_Remove ); + pev->nextthink = gpGlobals->time; + } + else + { + FadeMonster(); + } + } +} + +//========================================================= +// GetDeathActivity - determines the best type of death +// anim to play. +//========================================================= +Activity CBaseMonster :: GetDeathActivity ( void ) +{ + Activity deathActivity; + + if ( pev->deadflag != DEAD_NO ) + { + // don't run this while dying. + return m_IdealActivity; + } + + switch ( m_LastHitGroup ) + { + // try to pick a region-specific death. + case HITGROUP_HEAD: + deathActivity = ACT_DIE_HEADSHOT; + break; + + default: + deathActivity = ACT_DIEFORWARD; + break; + } + + return deathActivity; +} + +//========================================================= +// GetSmallFlinchActivity - determines the best type of flinch +// anim to play. +//========================================================= +Activity CBaseMonster :: GetSmallFlinchActivity ( void ) +{ + Activity flinchActivity; + + flinchActivity = ACT_FLINCH_BACK; + + return flinchActivity; +} + + +void CBaseMonster::BecomeDead( void ) +{ + pev->takedamage = DAMAGE_YES;// don't let autoaim aim at corpses. + + // give the corpse half of the monster's original maximum health. + pev->health = pev->max_health / 2; + pev->max_health = 5; // max_health now becomes a counter for how many blood decals the corpse can place. + + // make the corpse fly away from the attack vector + pev->movetype = MOVETYPE_TOSS; + //pev->flags &= ~FL_ONGROUND; + //pev->origin.z += 2; + //pev->velocity = g_vecAttackDir * -1; + //pev->velocity = pev->velocity * RANDOM_FLOAT( 300, 400 ); +} + + +BOOL CBaseMonster::ShouldGibMonster( int iGib ) +{ + if ( ( iGib == GIB_NORMAL && pev->health < GIB_HEALTH_VALUE ) || ( iGib == GIB_ALWAYS ) ) + return TRUE; + + return FALSE; +} + + +void CBaseMonster::CallGibMonster( void ) +{ + BOOL fade = FALSE; + + if ( HasHumanGibs() ) + { + if ( CVAR_GET_FLOAT("violence_hgibs") == 0 ) + fade = TRUE; + } + else if ( HasAlienGibs() ) + { + if ( CVAR_GET_FLOAT("violence_agibs") == 0 ) + fade = TRUE; + } + + pev->takedamage = DAMAGE_NO; + pev->solid = SOLID_NOT;// do something with the body. while monster blows up + + if ( fade ) + { + FadeMonster(); + } + else + { + pev->effects = EF_NODRAW; // make the model invisible. + GibMonster(); + } + + pev->deadflag = DEAD_DEAD; + FCheckAITrigger(); + + // don't let the status bar glitch for players.with <0 health. + if (pev->health < -99) + { + pev->health = 0; + } + + if ( ShouldFadeOnDeath() && !fade ) + UTIL_Remove(this); +} + + +/* +============ +Killed +============ +*/ +void CBaseMonster :: Killed( entvars_t *pevAttacker, int iGib ) +{ + unsigned int cCount = 0; + BOOL fDone = FALSE; + + if ( HasMemory( bits_MEMORY_KILLED ) ) + { + if ( ShouldGibMonster( iGib ) ) + CallGibMonster(); + return; + } + + Remember( bits_MEMORY_KILLED ); + + // clear the deceased's sound channels.(may have been firing or reloading when killed) + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "common/null.wav", 1, ATTN_NORM); + m_IdealMonsterState = MONSTERSTATE_DEAD; + // Make sure this condition is fired too (TakeDamage breaks out before this happens on death) + SetConditions( bits_COND_LIGHT_DAMAGE ); + + // tell owner ( if any ) that we're dead.This is mostly for MonsterMaker functionality. + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + if ( pOwner ) + { + pOwner->DeathNotice( pev ); + } + + if ( ShouldGibMonster( iGib ) ) + { + CallGibMonster(); + return; + } + else if ( pev->flags & FL_MONSTER ) + { + SetTouch( NULL ); + BecomeDead(); + } + + // don't let the status bar glitch for players.with <0 health. + if (pev->health < -99) + { + pev->health = 0; + } + + //pev->enemy = ENT( pevAttacker );//why? (sjb) + + m_IdealMonsterState = MONSTERSTATE_DEAD; +} + +// +// fade out - slowly fades a entity out, then removes it. +// +// DON'T USE ME FOR GIBS AND STUFF IN MULTIPLAYER! +// SET A FUTURE THINK AND A RENDERMODE!! +void CBaseEntity :: SUB_StartFadeOut ( void ) +{ + if (pev->rendermode == kRenderNormal) + { + pev->renderamt = 255; + pev->rendermode = kRenderTransTexture; + } + + pev->solid = SOLID_NOT; + pev->avelocity = g_vecZero; + + pev->nextthink = gpGlobals->time + 0.1; + SetThink ( &CBaseEntity::SUB_FadeOut ); +} + +void CBaseEntity :: SUB_FadeOut ( void ) +{ + if ( pev->renderamt > 7 ) + { + pev->renderamt -= 7; + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + pev->renderamt = 0; + pev->nextthink = gpGlobals->time + 0.2; + SetThink ( &CBaseEntity::SUB_Remove ); + } +} + +//========================================================= +// WaitTillLand - in order to emit their meaty scent from +// the proper location, gibs should wait until they stop +// bouncing to emit their scent. That's what this function +// does. +//========================================================= +void CGib :: WaitTillLand ( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + if ( pev->velocity == g_vecZero ) + { + SetThink (&CGib::SUB_StartFadeOut); + pev->nextthink = gpGlobals->time + m_lifeTime; + + // If you bleed, you stink! + if ( m_bloodColor != DONT_BLEED ) + { + // ok, start stinkin! + CSoundEnt::InsertSound ( bits_SOUND_MEAT, pev->origin, 384, 25 ); + } + } + else + { + // wait and check again in another half second. + pev->nextthink = gpGlobals->time + 0.5; + } +} + +// +// Gib bounces on the ground or wall, sponges some blood down, too! +// +void CGib :: BounceGibTouch ( CBaseEntity *pOther ) +{ + Vector vecSpot; + TraceResult tr; + + //if ( RANDOM_LONG(0,1) ) + // return;// don't bleed everytime + + if (pev->flags & FL_ONGROUND) + { + pev->velocity = pev->velocity * 0.9; + pev->angles.x = 0; + pev->angles.z = 0; + pev->avelocity.x = 0; + pev->avelocity.z = 0; + } + else + { + if ( g_Language != LANGUAGE_GERMAN && m_cBloodDecals > 0 && m_bloodColor != DONT_BLEED ) + { + vecSpot = pev->origin + Vector ( 0 , 0 , 8 );//move up a bit, and trace down. + UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -24 ), ignore_monsters, ENT(pev), & tr); + + UTIL_BloodDecalTrace( &tr, m_bloodColor ); + + m_cBloodDecals--; + } + + if ( m_material != matNone && RANDOM_LONG(0,2) == 0 ) + { + float volume; + float zvel = fabs(pev->velocity.z); + + volume = 0.8 * min(1.0, ((float)zvel) / 450.0); + + CBreakable::MaterialSoundRandom( edict(), (Materials)m_material, volume ); + } + } +} + +// +// Sticky gib puts blood on the wall and stays put. +// +void CGib :: StickyGibTouch ( CBaseEntity *pOther ) +{ + Vector vecSpot; + TraceResult tr; + + SetThink ( &CGib::SUB_Remove ); + pev->nextthink = gpGlobals->time + 10; + + if ( !FClassnameIs( pOther->pev, "worldspawn" ) ) + { + pev->nextthink = gpGlobals->time; + return; + } + + UTIL_TraceLine ( pev->origin, pev->origin + pev->velocity * 32, ignore_monsters, ENT(pev), & tr); + + UTIL_BloodDecalTrace( &tr, m_bloodColor ); + + pev->velocity = tr.vecPlaneNormal * -1; + pev->angles = UTIL_VecToAngles ( pev->velocity ); + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + pev->movetype = MOVETYPE_NONE; +} + +// +// Throw a chunk +// +void CGib :: Spawn( const char *szGibModel ) +{ + pev->movetype = MOVETYPE_BOUNCE; + pev->friction = 0.55; // deading the bounce a bit + + // sometimes an entity inherits the edict from a former piece of glass, + // and will spawn using the same render FX or rendermode! bad! + pev->renderamt = 255; + pev->rendermode = kRenderNormal; + pev->renderfx = kRenderFxNone; + pev->solid = SOLID_SLIDEBOX;/// hopefully this will fix the VELOCITY TOO LOW crap + pev->classname = MAKE_STRING("gib"); + + SET_MODEL(ENT(pev), szGibModel); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + + pev->nextthink = gpGlobals->time + 4; + m_lifeTime = 10; + SetThink ( &CGib::WaitTillLand ); + SetTouch ( &CGib::BounceGibTouch ); + + m_material = matNone; + m_cBloodDecals = 5;// how many blood decals this gib can place (1 per bounce until none remain). +} + +// take health +int CBaseMonster :: TakeHealth (float flHealth, int bitsDamageType) +{ + if (!pev->takedamage) + return 0; + + // clear out any damage types we healed. + // UNDONE: generic health should not heal any + // UNDONE: time-based damage + + m_bitsDamageType &= ~(bitsDamageType & ~DMG_TIMEBASED); + + return CBaseEntity::TakeHealth(flHealth, bitsDamageType); +} + +/* +============ +TakeDamage + +The damage is coming from inflictor, but get mad at attacker +This should be the only function that ever reduces health. +bitsDamageType indicates the type of damage sustained, ie: DMG_SHOCK + +Time-based damage: only occurs while the monster is within the trigger_hurt. +When a monster is poisoned via an arrow etc it takes all the poison damage at once. + + + +GLOBALS ASSUMED SET: g_iSkillLevel +============ +*/ +int CBaseMonster :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + float flTake; + Vector vecDir; + + if (!pev->takedamage) + return 0; + + if ( !IsAlive() ) + { + return DeadTakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); + } + + if ( pev->deadflag == DEAD_NO ) + { + // no pain sound during death animation. + PainSound();// "Ouch!" + } + + //!!!LATER - make armor consideration here! + flTake = flDamage; + + // set damage type sustained + m_bitsDamageType |= bitsDamageType; + + // grab the vector of the incoming attack. ( pretend that the inflictor is a little lower than it really is, so the body will tend to fly upward a bit). + vecDir = Vector( 0, 0, 0 ); + if (!FNullEnt( pevInflictor )) + { + CBaseEntity *pInflictor = CBaseEntity :: Instance( pevInflictor ); + if (pInflictor) + { + vecDir = ( pInflictor->Center() - Vector ( 0, 0, 10 ) - Center() ).Normalize(); + vecDir = g_vecAttackDir = vecDir.Normalize(); + } + } + + // add to the damage total for clients, which will be sent as a single + // message at the end of the frame + // todo: remove after combining shotgun blasts? + if ( IsPlayer() ) + { + if ( pevInflictor ) + pev->dmg_inflictor = ENT(pevInflictor); + + pev->dmg_take += flTake; + + // check for godmode or invincibility + if ( pev->flags & FL_GODMODE ) + { + return 0; + } + } + + // if this is a player, move him around! + if ( ( !FNullEnt( pevInflictor ) ) && (pev->movetype == MOVETYPE_WALK) && (!pevAttacker || pevAttacker->solid != SOLID_TRIGGER) ) + { + pev->velocity = pev->velocity + vecDir * -DamageForce( flDamage ); + } + + // do the damage + pev->health -= flTake; + + // HACKHACK Don't kill monsters in a script. Let them break their scripts first + if ( m_MonsterState == MONSTERSTATE_SCRIPT ) + { + SetConditions( bits_COND_LIGHT_DAMAGE ); + return 0; + } + + if ( pev->health <= 0 ) + { + g_pevLastInflictor = pevInflictor; + + if ( bitsDamageType & DMG_ALWAYSGIB ) + { + Killed( pevAttacker, GIB_ALWAYS ); + } + else if ( bitsDamageType & DMG_NEVERGIB ) + { + Killed( pevAttacker, GIB_NEVER ); + } + else + { + Killed( pevAttacker, GIB_NORMAL ); + } + + g_pevLastInflictor = NULL; + + return 0; + } + + // react to the damage (get mad) + if ( (pev->flags & FL_MONSTER) && !FNullEnt(pevAttacker) ) + { + if ( pevAttacker->flags & (FL_MONSTER | FL_CLIENT) ) + {// only if the attack was a monster or client! + + // enemy's last known position is somewhere down the vector that the attack came from. + if (pevInflictor) + { + if (m_hEnemy == NULL || pevInflictor == m_hEnemy->pev || !HasConditions(bits_COND_SEE_ENEMY)) + { + m_vecEnemyLKP = pevInflictor->origin; + } + } + else + { + m_vecEnemyLKP = pev->origin + ( g_vecAttackDir * 64 ); + } + + MakeIdealYaw( m_vecEnemyLKP ); + + // add pain to the conditions + // !!!HACKHACK - fudged for now. Do we want to have a virtual function to determine what is light and + // heavy damage per monster class? + if ( flDamage > 0 ) + { + SetConditions(bits_COND_LIGHT_DAMAGE); + } + + if ( flDamage >= 20 ) + { + SetConditions(bits_COND_HEAVY_DAMAGE); + } + } + } + + return 1; +} + +//========================================================= +// DeadTakeDamage - takedamage function called when a monster's +// corpse is damaged. +//========================================================= +int CBaseMonster :: DeadTakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecDir; + + // grab the vector of the incoming attack. ( pretend that the inflictor is a little lower than it really is, so the body will tend to fly upward a bit). + vecDir = Vector( 0, 0, 0 ); + if (!FNullEnt( pevInflictor )) + { + CBaseEntity *pInflictor = CBaseEntity :: Instance( pevInflictor ); + if (pInflictor) + { + vecDir = ( pInflictor->Center() - Vector ( 0, 0, 10 ) - Center() ).Normalize(); + vecDir = g_vecAttackDir = vecDir.Normalize(); + } + } + +#if 0// turn this back on when the bounding box issues are resolved. + + pev->flags &= ~FL_ONGROUND; + pev->origin.z += 1; + + // let the damage scoot the corpse around a bit. + if ( !FNullEnt(pevInflictor) && (pevAttacker->solid != SOLID_TRIGGER) ) + { + pev->velocity = pev->velocity + vecDir * -DamageForce( flDamage ); + } + +#endif + + // kill the corpse if enough damage was done to destroy the corpse and the damage is of a type that is allowed to destroy the corpse. + if ( bitsDamageType & DMG_GIB_CORPSE ) + { + if ( pev->health <= flDamage ) + { + pev->health = -50; + Killed( pevAttacker, GIB_ALWAYS ); + return 0; + } + // Accumulate corpse gibbing damage, so you can gib with multiple hits + pev->health -= flDamage * 0.1; + } + + return 1; +} + + +float CBaseMonster :: DamageForce( float damage ) +{ + float force = damage * ((32 * 32 * 72.0) / (pev->size.x * pev->size.y * pev->size.z)) * 5; + + if ( force > 1000.0) + { + force = 1000.0; + } + + return force; +} + +// +// RadiusDamage - this entity is exploding, or otherwise needs to inflict damage upon entities within a certain range. +// +// only damage ents that can clearly be seen by the explosion! + + +void RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, float flRadius, int iClassIgnore, int bitsDamageType ) +{ + CBaseEntity *pEntity = NULL; + TraceResult tr; + float flAdjustedDamage, falloff; + Vector vecSpot; + + if ( flRadius ) + falloff = flDamage / flRadius; + else + falloff = 1.0; + + int bInWater = (UTIL_PointContents ( vecSrc ) == CONTENTS_WATER); + + vecSrc.z += 1;// in case grenade is lying on the ground + + if ( !pevAttacker ) + pevAttacker = pevInflictor; + + // iterate on all entities in the vicinity. + while ((pEntity = UTIL_FindEntityInSphere( pEntity, vecSrc, flRadius )) != NULL) + { + if ( pEntity->pev->takedamage != DAMAGE_NO ) + { + // UNDONE: this should check a damage mask, not an ignore + if ( iClassIgnore != CLASS_NONE && pEntity->Classify() == iClassIgnore ) + {// houndeyes don't hurt other houndeyes with their attack + continue; + } + + // blast's don't tavel into or out of water + if (bInWater && pEntity->pev->waterlevel == 0) + continue; + if (!bInWater && pEntity->pev->waterlevel == 3) + continue; + + vecSpot = pEntity->BodyTarget( vecSrc ); + + UTIL_TraceLine ( vecSrc, vecSpot, dont_ignore_monsters, ENT(pevInflictor), &tr ); + + if ( tr.flFraction == 1.0 || tr.pHit == pEntity->edict() ) + {// the explosion can 'see' this entity, so hurt them! + if (tr.fStartSolid) + { + // if we're stuck inside them, fixup the position and distance + tr.vecEndPos = vecSrc; + tr.flFraction = 0.0; + } + + // decrease damage for an ent that's farther from the bomb. + flAdjustedDamage = ( vecSrc - tr.vecEndPos ).Length() * falloff; + flAdjustedDamage = flDamage - flAdjustedDamage; + + if ( flAdjustedDamage < 0 ) + { + flAdjustedDamage = 0; + } + + // ALERT( at_console, "hit %s\n", STRING( pEntity->pev->classname ) ); + if (tr.flFraction != 1.0) + { + ClearMultiDamage( ); + pEntity->TraceAttack( pevInflictor, flAdjustedDamage, (tr.vecEndPos - vecSrc).Normalize( ), &tr, bitsDamageType ); + ApplyMultiDamage( pevInflictor, pevAttacker ); + } + else + { + pEntity->TakeDamage ( pevInflictor, pevAttacker, flAdjustedDamage, bitsDamageType ); + } + } + } + } +} + + +void CBaseMonster :: RadiusDamage(entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) +{ + ::RadiusDamage( pev->origin, pevInflictor, pevAttacker, flDamage, flDamage * 2.5, iClassIgnore, bitsDamageType ); +} + + +void CBaseMonster :: RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) +{ + ::RadiusDamage( vecSrc, pevInflictor, pevAttacker, flDamage, flDamage * 2.5, iClassIgnore, bitsDamageType ); +} + + +//========================================================= +// CheckTraceHullAttack - expects a length to trace, amount +// of damage to do, and damage type. Returns a pointer to +// the damaged entity in case the monster wishes to do +// other stuff to the victim (punchangle, etc) +// +// Used for many contact-range melee attacks. Bites, claws, etc. +//========================================================= +CBaseEntity* CBaseMonster :: CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ) +{ + TraceResult tr; + + if (IsPlayer()) + UTIL_MakeVectors( pev->angles ); + else + UTIL_MakeAimVectors( pev->angles ); + + Vector vecStart = pev->origin; + vecStart.z += pev->size.z * 0.5; + Vector vecEnd = vecStart + (gpGlobals->v_forward * flDist ); + + UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr ); + + if ( tr.pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + + if ( iDamage > 0 ) + { + pEntity->TakeDamage( pev, pev, iDamage, iDmgType ); + } + + return pEntity; + } + + return NULL; +} + + +//========================================================= +// FInViewCone - returns true is the passed ent is in +// the caller's forward view cone. The dot product is performed +// in 2d, making the view cone infinitely tall. +//========================================================= +BOOL CBaseMonster :: FInViewCone ( CBaseEntity *pEntity ) +{ + Vector2D vec2LOS; + float flDot; + + UTIL_MakeVectors ( pev->angles ); + + vec2LOS = ( pEntity->pev->origin - pev->origin ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() ); + + if ( flDot > m_flFieldOfView ) + { + return TRUE; + } + else + { + return FALSE; + } +} + +//========================================================= +// FInViewCone - returns true is the passed vector is in +// the caller's forward view cone. The dot product is performed +// in 2d, making the view cone infinitely tall. +//========================================================= +BOOL CBaseMonster :: FInViewCone ( Vector *pOrigin ) +{ + Vector2D vec2LOS; + float flDot; + + UTIL_MakeVectors ( pev->angles ); + + vec2LOS = ( *pOrigin - pev->origin ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() ); + + if ( flDot > m_flFieldOfView ) + { + return TRUE; + } + else + { + return FALSE; + } +} + +//========================================================= +// FVisible - returns true if a line can be traced from +// the caller's eyes to the target +//========================================================= +BOOL CBaseEntity :: FVisible ( CBaseEntity *pEntity ) +{ + TraceResult tr; + Vector vecLookerOrigin; + Vector vecTargetOrigin; + + if (FBitSet( pEntity->pev->flags, FL_NOTARGET )) + return FALSE; + + // don't look through water + if ((pev->waterlevel != 3 && pEntity->pev->waterlevel == 3) + || (pev->waterlevel == 3 && pEntity->pev->waterlevel == 0)) + return FALSE; + + vecLookerOrigin = pev->origin + pev->view_ofs;//look through the caller's 'eyes' + vecTargetOrigin = pEntity->EyePosition(); + + UTIL_TraceLine(vecLookerOrigin, vecTargetOrigin, ignore_monsters, ignore_glass, ENT(pev)/*pentIgnore*/, &tr); + + if (tr.flFraction != 1.0) + { + return FALSE;// Line of sight is not established + } + else + { + return TRUE;// line of sight is valid. + } +} + +//========================================================= +// FVisible - returns true if a line can be traced from +// the caller's eyes to the target vector +//========================================================= +BOOL CBaseEntity :: FVisible ( const Vector &vecOrigin ) +{ + TraceResult tr; + Vector vecLookerOrigin; + + vecLookerOrigin = EyePosition();//look through the caller's 'eyes' + + UTIL_TraceLine(vecLookerOrigin, vecOrigin, ignore_monsters, ignore_glass, ENT(pev)/*pentIgnore*/, &tr); + + if (tr.flFraction != 1.0) + { + return FALSE;// Line of sight is not established + } + else + { + return TRUE;// line of sight is valid. + } +} + +/* +================ +TraceAttack +================ +*/ +void CBaseEntity::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + Vector vecOrigin = ptr->vecEndPos - vecDir * 4; + + if ( pev->takedamage ) + { + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + + int blood = BloodColor(); + + if ( blood != DONT_BLEED ) + { + SpawnBlood(vecOrigin, blood, flDamage);// a little surface blood. + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + } + } +} + + +/* +//========================================================= +// TraceAttack +//========================================================= +void CBaseMonster::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + Vector vecOrigin = ptr->vecEndPos - vecDir * 4; + + ALERT ( at_console, "%d\n", ptr->iHitgroup ); + + + if ( pev->takedamage ) + { + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + + int blood = BloodColor(); + + if ( blood != DONT_BLEED ) + { + SpawnBlood(vecOrigin, blood, flDamage);// a little surface blood. + } + } +} +*/ + +//========================================================= +// TraceAttack +//========================================================= +void CBaseMonster :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( pev->takedamage ) + { + SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage);// a little surface blood. + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + } +} + +/* +================ +FireBullets + +Go to the trouble of combining multiple pellets into a single damage call. +================ +*/ +void CBaseEntity::FireBullets(ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq, int iDamage, entvars_t *pevAttacker ) +{ + static int tracerCount; + int tracer; + TraceResult tr; + Vector vecRight = gpGlobals->v_right; + Vector vecUp = gpGlobals->v_up; + + if ( pevAttacker == NULL ) + pevAttacker = pev; // the default attacker is ourselves + + // Vector vecSrc = pev->origin + gpGlobals->v_forward * 10; + //vecSrc.z = pevShooter->absmin.z + pevShooter->size.z * 0.7; + //vecSrc.z = pev->origin.z + (pev->view_ofs.z - 4); + ClearMultiDamage(); + gMultiDamage.type = DMG_BULLET | DMG_NEVERGIB; + + for (ULONG iShot = 1; iShot <= cShots; iShot++) + { + // get circular gaussian spread + float x, y, z; + do { + x = RANDOM_FLOAT(-0.5,0.5) + RANDOM_FLOAT(-0.5,0.5); + y = RANDOM_FLOAT(-0.5,0.5) + RANDOM_FLOAT(-0.5,0.5); + z = x*x+y*y; + } while (z > 1); + + Vector vecDir = vecDirShooting + + x * vecSpread.x * vecRight + + y * vecSpread.y * vecUp; + Vector vecEnd; + + vecEnd = vecSrc + vecDir * flDistance; + UTIL_TraceLine(vecSrc, vecEnd, dont_ignore_monsters, ENT(pev)/*pentIgnore*/, &tr); + + tracer = 0; + if (iTracerFreq != 0 && (tracerCount++ % iTracerFreq) == 0) + { + Vector vecTracerSrc; + + if ( IsPlayer() ) + {// adjust tracer position for player + vecTracerSrc = vecSrc + Vector ( 0 , 0 , -4 ) + gpGlobals->v_right * 2 + gpGlobals->v_forward * 16; + } + else + { + vecTracerSrc = vecSrc; + } + + if ( iTracerFreq != 1 ) // guns that always trace also always decal + tracer = 1; + switch( iBulletType ) + { + case BULLET_PLAYER_MP5: + case BULLET_MONSTER_MP5: + case BULLET_MONSTER_9MM: + case BULLET_MONSTER_12MM: + default: + break; + } + } + // do damage, paint decals + if (tr.flFraction != 1.0) + { + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + if ( iDamage ) + { + pEntity->TraceAttack(pevAttacker, iDamage, vecDir, &tr, DMG_BULLET | ((iDamage > 16) ? DMG_ALWAYSGIB : DMG_NEVERGIB) ); + + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + DecalGunshot( &tr, iBulletType ); + } + else switch(iBulletType) + { + default: + case BULLET_PLAYER_9MM: + pEntity->TraceAttack(pevAttacker, gSkillData.plrDmg9MM, vecDir, &tr, DMG_BULLET); + break; + + case BULLET_PLAYER_MP5: + pEntity->TraceAttack(pevAttacker, gSkillData.plrDmgMP5, vecDir, &tr, DMG_BULLET); + break; + + case BULLET_PLAYER_BUCKSHOT: + // make distance based! + pEntity->TraceAttack(pevAttacker, gSkillData.plrDmgBuckshot, vecDir, &tr, DMG_BULLET); + break; + + case BULLET_PLAYER_357: + pEntity->TraceAttack(pevAttacker, gSkillData.plrDmg357, vecDir, &tr, DMG_BULLET); + break; + + case BULLET_MONSTER_9MM: + pEntity->TraceAttack(pevAttacker, gSkillData.monDmg9MM, vecDir, &tr, DMG_BULLET); + + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + DecalGunshot( &tr, iBulletType ); + + break; + + case BULLET_MONSTER_MP5: + pEntity->TraceAttack(pevAttacker, gSkillData.monDmgMP5, vecDir, &tr, DMG_BULLET); + + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + DecalGunshot( &tr, iBulletType ); + + break; + + case BULLET_MONSTER_12MM: + pEntity->TraceAttack(pevAttacker, gSkillData.monDmg12MM, vecDir, &tr, DMG_BULLET); + if ( !tracer ) + { + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + DecalGunshot( &tr, iBulletType ); + } + break; + + case BULLET_NONE: // FIX + pEntity->TraceAttack(pevAttacker, 50, vecDir, &tr, DMG_CLUB); + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + // only decal glass + if ( !FNullEnt(tr.pHit) && VARS(tr.pHit)->rendermode != 0) + { + UTIL_DecalTrace( &tr, DECAL_GLASSBREAK1 + RANDOM_LONG(0,2) ); + } + + break; + } + } + // make bullet trails + UTIL_BubbleTrail( vecSrc, tr.vecEndPos, (flDistance * tr.flFraction) / 64.0 ); + } + ApplyMultiDamage(pev, pevAttacker); +} + + +void CBaseEntity :: TraceBleed( float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + if (BloodColor() == DONT_BLEED) + return; + + if (flDamage == 0) + return; + + if (! (bitsDamageType & (DMG_CRUSH | DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB | DMG_MORTAR))) + return; + + // make blood decal on the wall! + TraceResult Bloodtr; + Vector vecTraceDir; + float flNoise; + int cCount; + int i; + +/* + if ( !IsAlive() ) + { + // dealing with a dead monster. + if ( pev->max_health <= 0 ) + { + // no blood decal for a monster that has already decalled its limit. + return; + } + else + { + pev->max_health--; + } + } +*/ + + if (flDamage < 10) + { + flNoise = 0.1; + cCount = 1; + } + else if (flDamage < 25) + { + flNoise = 0.2; + cCount = 2; + } + else + { + flNoise = 0.3; + cCount = 4; + } + + for ( i = 0 ; i < cCount ; i++ ) + { + vecTraceDir = vecDir * -1;// trace in the opposite direction the shot came from (the direction the shot is going) + + vecTraceDir.x += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.y += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.z += RANDOM_FLOAT( -flNoise, flNoise ); + + UTIL_TraceLine( ptr->vecEndPos, ptr->vecEndPos + vecTraceDir * -172, ignore_monsters, ENT(pev), &Bloodtr); + + if ( Bloodtr.flFraction != 1.0 ) + { + UTIL_BloodDecalTrace( &Bloodtr, BloodColor() ); + } + } +} + +//========================================================= +//========================================================= +void CBaseMonster :: MakeDamageBloodDecal ( int cCount, float flNoise, TraceResult *ptr, const Vector &vecDir ) +{ + // make blood decal on the wall! + TraceResult Bloodtr; + Vector vecTraceDir; + int i; + + if ( !IsAlive() ) + { + // dealing with a dead monster. + if ( pev->max_health <= 0 ) + { + // no blood decal for a monster that has already decalled its limit. + return; + } + else + { + pev->max_health--; + } + } + + for ( i = 0 ; i < cCount ; i++ ) + { + vecTraceDir = vecDir; + + vecTraceDir.x += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.y += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.z += RANDOM_FLOAT( -flNoise, flNoise ); + + UTIL_TraceLine( ptr->vecEndPos, ptr->vecEndPos + vecTraceDir * 172, ignore_monsters, ENT(pev), &Bloodtr); + +/* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + WRITE_COORD( ptr->vecEndPos.x ); + WRITE_COORD( ptr->vecEndPos.y ); + WRITE_COORD( ptr->vecEndPos.z ); + + WRITE_COORD( Bloodtr.vecEndPos.x ); + WRITE_COORD( Bloodtr.vecEndPos.y ); + WRITE_COORD( Bloodtr.vecEndPos.z ); + MESSAGE_END(); +*/ + + if ( Bloodtr.flFraction != 1.0 ) + { + UTIL_BloodDecalTrace( &Bloodtr, BloodColor() ); + } + } +} diff --git a/ricochet/dlls/decals.h b/ricochet/dlls/decals.h new file mode 100644 index 0000000..0cb87ee --- /dev/null +++ b/ricochet/dlls/decals.h @@ -0,0 +1,75 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef DECALS_H +#define DECALS_H + +// +// Dynamic Decals +// +enum decal_e +{ + DECAL_GUNSHOT1 = 0, + DECAL_GUNSHOT2, + DECAL_GUNSHOT3, + DECAL_GUNSHOT4, + DECAL_GUNSHOT5, + DECAL_LAMBDA1, + DECAL_LAMBDA2, + DECAL_LAMBDA3, + DECAL_LAMBDA4, + DECAL_LAMBDA5, + DECAL_LAMBDA6, + DECAL_SCORCH1, + DECAL_SCORCH2, + DECAL_BLOOD1, + DECAL_BLOOD2, + DECAL_BLOOD3, + DECAL_BLOOD4, + DECAL_BLOOD5, + DECAL_BLOOD6, + DECAL_YBLOOD1, + DECAL_YBLOOD2, + DECAL_YBLOOD3, + DECAL_YBLOOD4, + DECAL_YBLOOD5, + DECAL_YBLOOD6, + DECAL_GLASSBREAK1, + DECAL_GLASSBREAK2, + DECAL_GLASSBREAK3, + DECAL_BIGSHOT1, + DECAL_BIGSHOT2, + DECAL_BIGSHOT3, + DECAL_BIGSHOT4, + DECAL_BIGSHOT5, + DECAL_SPIT1, + DECAL_SPIT2, + DECAL_BPROOF1, // Bulletproof glass decal + DECAL_GARGSTOMP1, // Gargantua stomp crack + DECAL_SMALLSCORCH1, // Small scorch mark + DECAL_SMALLSCORCH2, // Small scorch mark + DECAL_SMALLSCORCH3, // Small scorch mark + DECAL_MOMMABIRTH, // Big momma birth splatter + DECAL_MOMMASPLAT, +}; + +typedef struct +{ + char *name; + int index; +} DLL_DECALLIST; + +extern DLL_DECALLIST gDecals[]; + +#endif // DECALS_H diff --git a/ricochet/dlls/disc_arena.cpp b/ricochet/dlls/disc_arena.cpp new file mode 100644 index 0000000..30919c3 --- /dev/null +++ b/ricochet/dlls/disc_arena.cpp @@ -0,0 +1,1048 @@ +//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: Handles the arena portion of discwar +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "gamerules.h" +#include "weapons.h" +#include "discwar.h" +#include "disc_arena.h" +#include "disc_objects.h" + +int g_iNextArenaGroupInfo = 0; + +void ClientUserInfoChanged( edict_t *pEntity, char *infobuffer ); +edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ); +void respawn(entvars_t* pev, BOOL fCopyCorpse); +extern int gmsgStartRnd; +extern int gmsgEndRnd; +extern int gmsgTeamInfo; +extern int g_iPlayersPerTeam; +extern int g_iMapTurnedOffArena; + +CDiscArena *g_pArenaList[ MAX_ARENAS ]; + +char *g_szCountDownVox[6] = +{ + "die", + "one", + "two", + "three", + "four", +}; + +char *g_szTeamNames[3] = +{ + "", + "red", + "blue", +}; + +int InArenaMode() +{ + if ( g_iMapTurnedOffArena ) + return FALSE; + + if ( gpGlobals->maxClients == 1 || (CVAR_GET_FLOAT("rc_arena") == 0) ) + return FALSE; + + return TRUE; +} + +LINK_ENTITY_TO_CLASS( disc_arena, CDiscArena ); + +// NOTE: +// We store queue position in pev->playerclass, so it's automatically pushed +// down to the client. The scoreboard uses pev->playerclass to display the +// positions of the players in the queue. +void CDiscArena::Spawn( void ) +{ + pev->groupinfo = 1 << (g_iNextArenaGroupInfo++); + Reset(); + + // Initialize + m_iMaxRounds = CVAR_GET_FLOAT("rc_rounds"); + m_iPlayersPerTeam = g_iPlayersPerTeam; + SetThink( NULL ); +} + +void CDiscArena::Reset( void ) +{ + // Remove all clients in the queue + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if (pPlayer && (pPlayer->m_pCurrentArena == this) && pPlayer->m_bHasDisconnected != TRUE ) + { + RemoveClient( pPlayer ); + + // Move her into spectator mode + //MoveToSpectator( pPlayer ); + } + } + + m_pPlayerQueue = NULL; + m_iPlayers = 0; + m_flTimeLimitOver = 0; + m_bShownTimeWarning = FALSE; + m_iArenaState = ARENA_WAITING_FOR_PLAYERS; + memset( m_hCombatants, 0, sizeof( m_hCombatants ) ); + + SetThink( NULL ); + pev->nextthink = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Start a battle. Spawn all the players, and begin the countdown. +//----------------------------------------------------------------------------- +void CDiscArena::StartBattle( void ) +{ + m_iCurrRound = 0; + m_iTeamOneScore = m_iTeamTwoScore = 0; + + // First, set all players in this arena to "didn't play" + int i; + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if (pPlayer && (pPlayer->m_pCurrentArena == this) && pPlayer->m_bHasDisconnected != TRUE ) + pPlayer->m_iLastGameResult = GAME_DIDNTPLAY; + } + + // Get the players in the battle + for ( i = 0; i < (m_iPlayersPerTeam * 2); i++ ) + { + CBasePlayer *pCurr; + + // Check to see if this slot's already full + if ( m_hCombatants[ i ] ) + { + pCurr = (CBasePlayer*)(CBaseEntity*)m_hCombatants[ i ]; + } + else + { + // Pop a new player from the queue + pCurr = GetNextPlayer(); + if (!pCurr) + { + // Couldnt get enough players. Reset. + Reset(); + return; + } + } + + // Set her team number + if ( i < m_iPlayersPerTeam ) + pCurr->pev->team = 1; + else + pCurr->pev->team = 2; + pCurr->pev->iuser4 = pCurr->pev->team; + + char sz[128]; + sprintf(sz, "Arena %d", pev->groupinfo ); + MESSAGE_BEGIN( MSG_ALL, gmsgTeamInfo ); + WRITE_BYTE( pCurr->entindex() ); + WRITE_STRING( sz ); + MESSAGE_END(); + + // Add her to the list of combatants + m_hCombatants[ i ] = pCurr; + + // Force her to update her clientinfo, so her colors match her team + ClientUserInfoChanged( pCurr->edict(), g_engfuncs.pfnGetInfoKeyBuffer( pCurr->edict() ) ); + } + + // Start the first round + StartRound(); +} + +//----------------------------------------------------------------------------- +// Purpose: Start the next round in the match +//----------------------------------------------------------------------------- +void CDiscArena::StartRound( void ) +{ + m_iCurrRound++; + m_iSecondsTillStart = ARENA_TIME_PREBATTLE; + m_flTimeLimitOver = gpGlobals->time + ARENA_TIME_ROUNDLIMIT; + + RestoreWorldObjects(); + + // Tell the clients + for ( int iPlayerNum = 1; iPlayerNum <= gpGlobals->maxClients; iPlayerNum++ ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( iPlayerNum ); + + if (pPlayer && (pPlayer->pev->groupinfo & pev->groupinfo) && (pPlayer->m_bHasDisconnected != TRUE) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgStartRnd, NULL, pPlayer->edict() ); + WRITE_BYTE( m_iCurrRound ); + WRITE_BYTE( m_iSecondsTillStart ); + WRITE_BYTE( (m_iPlayersPerTeam * 2) ); + + // Send down all the players in the round + for ( int j = 0; j < (m_iPlayersPerTeam * 2); j++ ) + { + if (m_hCombatants[j]) + WRITE_SHORT( ((CBaseEntity*)m_hCombatants[j])->entindex() ); + } + + MESSAGE_END(); + } + } + + // Spawn all the players in the round + for ( int i = 0; i < (m_iPlayersPerTeam * 2); i++ ) + { + CBasePlayer *pPlayer = ((CBasePlayer*)(CBaseEntity*)m_hCombatants[i]); + + if ( pPlayer ) + { + // make sure the player's groupinfo is set the arena's groupinfo + pPlayer->pev->groupinfo = pev->groupinfo; + + // is the player an observer? + if ( pPlayer->IsObserver() ) + { + SpawnCombatant( pPlayer ); + } + + // Remove any powerups + pPlayer->RemoveAllPowerups(); + } + } + + // Start counting down + m_iArenaState = ARENA_COUNTDOWN; + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CDiscArena::CountDownThink ); +} + +//----------------------------------------------------------------------------- +// Purpose: Make sure all the Combatants in the game a valid. Return FALSE if not. +//----------------------------------------------------------------------------- +int CDiscArena::ValidateCombatants( void ) +{ + for ( int i = 0; i < (m_iPlayersPerTeam * 2); i++ ) + { + CBasePlayer *pPlayer = ((CBasePlayer*)(CBaseEntity*)m_hCombatants[i]); + + if ( !pPlayer ) + return FALSE; + if ( pPlayer->m_bHasDisconnected ) + return FALSE; + } + + return TRUE; +} + +//----------------------------------------------------------------------------- +// Purpose: Restore all the world objects +//----------------------------------------------------------------------------- +void CDiscArena::RestoreWorldObjects( void ) +{ + CBaseEntity *pFunc = NULL; + while ((pFunc = UTIL_FindEntityByClassname( pFunc, "func_plat_toggleremove" )) != NULL) + { + ((CPlatToggleRemove*)pFunc)->Reset(); + } + while ((pFunc = UTIL_FindEntityByClassname( pFunc, "func_disctoggle" )) != NULL) + { + ((CDiscTarget*)pFunc)->Reset(); + } + + // Disable powerups + while ((pFunc = UTIL_FindEntityByClassname( pFunc, "item_powerup" )) != NULL) + { + ((CDiscwarPowerup*)pFunc)->Disable(); + } +} + +void CDiscArena::BattleThink( void ) +{ + if ( gpGlobals->time >= m_flTimeLimitOver - 1.0 ) + { + pev->nextthink = gpGlobals->time + 1.0; + SetThink( &CDiscArena::TimeOver ); + return; + } + + if ( !CheckBattleOver() ) + { + pev->nextthink = gpGlobals->time + 1.0; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Countdown to the round start +//----------------------------------------------------------------------------- +void CDiscArena::CountDownThink( void ) +{ + // Freeze everyone until there's 3 seconds to go + if ( m_iSecondsTillStart == 3 ) + { + for ( int i = 0; i < (m_iPlayersPerTeam * 2); i++ ) + { + if (m_hCombatants[i]) + ((CBaseEntity*)m_hCombatants[i])->pev->maxspeed = 320; + } + } + + m_iSecondsTillStart--; + + // Play countdown VOX + if (m_iSecondsTillStart < 5) + { + // Speech + for ( int i = 0; i < (m_iPlayersPerTeam * 2); i++ ) + { + if (m_hCombatants[i]) + ((CBasePlayer*)(CBaseEntity*)m_hCombatants[i])->ClientHearVox( g_szCountDownVox[ m_iSecondsTillStart ] ); + } + } + + // Send the message to the clients in the arena + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if (pPlayer && (pPlayer->pev->groupinfo & pev->groupinfo) && pPlayer->m_bHasDisconnected != TRUE) + { + MESSAGE_BEGIN( MSG_ONE, gmsgStartRnd, NULL, pPlayer->edict() ); + WRITE_BYTE( m_iCurrRound ); + WRITE_BYTE( m_iSecondsTillStart ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + } + } + + if (m_iSecondsTillStart) + { + pev->nextthink = gpGlobals->time + 1.0; + } + else + { + m_iArenaState = ARENA_BATTLE_IN_PROGRESS; + + // Enable powerups + CBaseEntity *pFunc = NULL; + while ((pFunc = UTIL_FindEntityByClassname( pFunc, "item_powerup" )) != NULL) + { + ((CDiscwarPowerup*)pFunc)->Enable(); + } + + pev->nextthink = gpGlobals->time + 1.0; + SetThink( &CDiscArena::BattleThink ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: A player in the battle has died. See if we need to restart the battle. +//----------------------------------------------------------------------------- +void CDiscArena::PlayerKilled( CBasePlayer *pPlayer ) +{ + // Check to see if the battle's over in 5 seconds + if ( m_iArenaState == ARENA_BATTLE_IN_PROGRESS || m_iArenaState == ARENA_COUNTDOWN ) + { + if ( !CheckBattleOver() ) + { + pev->nextthink = gpGlobals->time + 0.5; + SetThink( &CDiscArena::CheckOverThink ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: A player in the battle has respawned. Move them to observer mode. +//----------------------------------------------------------------------------- +void CDiscArena::PlayerRespawned( CBasePlayer *pPlayer ) +{ + if ( m_iArenaState == ARENA_BATTLE_IN_PROGRESS ) + { + // Move the player into Spectator mode + MoveToSpectator( pPlayer ); + } + else if ( m_iArenaState == ARENA_SHOWING_SCORES && ( m_iTeamOneScore >= m_iMaxRounds || m_iTeamTwoScore >= m_iMaxRounds ) ) + { + // Battle is over. If there's only 2 players in the game, just respawn. + // Otherwise, move to spectator. + int iNumPlayers = 0; + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i ); + if (pPlayer && pPlayer->m_bHasDisconnected != TRUE) + iNumPlayers++; + } + + // Move the player into Spectator mode if there are more players + if ( iNumPlayers > 2 ) + MoveToSpectator( pPlayer ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Move the player to a spectating position +//----------------------------------------------------------------------------- +void CDiscArena::MoveToSpectator( CBasePlayer *pPlayer ) +{ + // Find the spectator spawn position + CBaseEntity *pSpot = UTIL_FindEntityByClassname( NULL, "info_player_spectator"); + if ( pSpot ) + { + pPlayer->StartObserver( pSpot->pev->origin, pSpot->pev->angles); + } + else + { + // Find any spawn point + edict_t *pentSpawnSpot = EntSelectSpawnPoint( pPlayer ); + pPlayer->StartObserver( VARS(pentSpawnSpot)->origin, VARS(pentSpawnSpot)->angles); + } + + pPlayer->pev->team = 0; + pPlayer->Observer_SetMode( OBS_LOCKEDVIEW ); +} + +//----------------------------------------------------------------------------- +// Purpose: Battle is over. +//----------------------------------------------------------------------------- +void CDiscArena::BattleOver( void ) +{ + pev->nextthink = gpGlobals->time + 3; + SetThink( &CDiscArena::FinishedThink ); + + m_iSecondsTillStart = ARENA_TIME_VIEWSCORES; + m_iArenaState = ARENA_SHOWING_SCORES; + + RestoreWorldObjects(); +} + +//----------------------------------------------------------------------------- +// Purpose: Round timelimit has been hit +//----------------------------------------------------------------------------- +void CDiscArena::TimeOver( void ) +{ + // Display the 10 second warning first + if ( !m_bShownTimeWarning ) + { + m_bShownTimeWarning = TRUE; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if (pPlayer && (pPlayer->pev->groupinfo & pev->groupinfo) && pPlayer->m_bHasDisconnected != TRUE) + ClientPrint( pPlayer->pev, HUD_PRINTCENTER, "#Time_Warning" ); + } + + pev->nextthink = gpGlobals->time + 10; + } + else + { + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if (pPlayer && (pPlayer->pev->groupinfo & pev->groupinfo) && (pPlayer->m_bHasDisconnected != TRUE) ) + ClientPrint( pPlayer->pev, HUD_PRINTCENTER, "#Time_Over" ); + } + + // Increment both scores to force the game to end + m_iTeamOneScore++; + m_iTeamTwoScore++; + + BattleOver(); + } +} + +bool CDiscArena::CheckBattleOver( void ) +{ + bool bTeamOneAlive = false; + bool bTeamTwoAlive = false; + + // See if the battle is finished + int i; + for ( i = 0; i < (m_iPlayersPerTeam * 2); i++ ) + { + if ( m_hCombatants[i] != NULL && ((CBasePlayer*)(CBaseEntity*)m_hCombatants[i])->IsAlive() ) + { + if ( ((CBaseEntity*)m_hCombatants[i])->pev->team == 1 ) + bTeamOneAlive = true; + else if ( ((CBaseEntity*)m_hCombatants[i])->pev->team == 2 ) + bTeamTwoAlive = true; + } + } + + if ( !bTeamOneAlive || !bTeamTwoAlive ) + { + // Battle is finished. + if (bTeamOneAlive) + { + m_iWinningTeam = 1; + m_iTeamOneScore++; + } + else + { + m_iWinningTeam = 2; + m_iTeamTwoScore++; + } + + int iTeamInTheLead = 0; + if ( m_iTeamOneScore > m_iTeamTwoScore ) + iTeamInTheLead = 1; + else if ( m_iTeamOneScore < m_iTeamTwoScore ) + iTeamInTheLead = 2; + + // Send the message to the clients in the arena + for ( int iPlayerNum = 1; iPlayerNum <= gpGlobals->maxClients; iPlayerNum++ ) + { + CBasePlayer *pPlayer = (CBasePlayer*)UTIL_PlayerByIndex( iPlayerNum ); + + if (pPlayer && (pPlayer->pev->groupinfo & pev->groupinfo) && (pPlayer->m_bHasDisconnected != TRUE) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgEndRnd, NULL, pPlayer->edict() ); + WRITE_BYTE( m_iCurrRound ); + WRITE_BYTE( 1 ); + WRITE_BYTE( m_iPlayersPerTeam ); + + // Send down the winners of this round + for (i = 0; i < (m_iPlayersPerTeam * 2); i++) + { + CBasePlayer *pPlayer = (CBasePlayer*)(CBaseEntity*)m_hCombatants[i]; + if ( !pPlayer || pPlayer->pev->team != m_iWinningTeam ) + continue; + + WRITE_SHORT( pPlayer->entindex() ); + } + + // Send down the team who's winning the battle now + if ( iTeamInTheLead == 0 ) + { + // It's a draw at the moment. + // No need to send down player data. + WRITE_BYTE( 0 ); + } + else + { + WRITE_BYTE( m_iPlayersPerTeam ); + // Send down the winners of this round + for (i = 0; i < (m_iPlayersPerTeam * 2); i++) + { + CBasePlayer *pPlayer = (CBasePlayer*)(CBaseEntity*)m_hCombatants[i]; + if ( !pPlayer || pPlayer->pev->team != iTeamInTheLead ) + continue; + + WRITE_SHORT( ((CBaseEntity*)m_hCombatants[i])->entindex() ); + } + } + + // Send down the scores + if ( iTeamInTheLead == 1 ) + { + WRITE_BYTE( m_iTeamOneScore ); + WRITE_BYTE( m_iTeamTwoScore ); + } + else + { + WRITE_BYTE( m_iTeamTwoScore ); + WRITE_BYTE( m_iTeamOneScore ); + } + + // Send down over or not + if ( m_iTeamOneScore == m_iMaxRounds || m_iTeamTwoScore == m_iMaxRounds ) + WRITE_BYTE( 1 ); + else + WRITE_BYTE( 0 ); + + MESSAGE_END(); + } + } + + BattleOver(); + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Check to see if the Battle is over +//----------------------------------------------------------------------------- +void CDiscArena::CheckOverThink( void ) +{ + if ( !CheckBattleOver() ) + { + if ( m_iArenaState == ARENA_COUNTDOWN ) + { + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CDiscArena::CountDownThink ); + } + else + { + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CDiscArena::BattleThink ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Show who won, and then restart the round +//----------------------------------------------------------------------------- +void CDiscArena::FinishedThink( void ) +{ + m_iSecondsTillStart--; + + if (m_iSecondsTillStart) + { + pev->nextthink = gpGlobals->time + 1.0; + } + else + { + SetThink( NULL ); + + // Tell the clients to remove the "Won" window + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if (pPlayer && (pPlayer->pev->groupinfo & pev->groupinfo) && pPlayer->m_bHasDisconnected != TRUE) + { + MESSAGE_BEGIN( MSG_ONE, gmsgEndRnd, NULL, pPlayer->edict() ); + WRITE_BYTE( m_iCurrRound ); + WRITE_BYTE( 0 ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + } + } + + // Round is over. See if the match is over too. + if ( m_iTeamOneScore >= m_iMaxRounds || m_iTeamTwoScore >= m_iMaxRounds ) + { + // Remove the losers from the combatants list + for (int i = 0; i < (m_iPlayersPerTeam * 2); i++) + { + CBasePlayer *pPlayer = (CBasePlayer*)(CBaseEntity*)m_hCombatants[i]; + if (!pPlayer) + continue; + if ( pPlayer->pev->team == m_iWinningTeam ) + { + pPlayer->m_iDeaths += 1; + pPlayer->m_iLastGameResult = GAME_WON; + continue; + } + + pPlayer->m_iLastGameResult = GAME_LOST; + m_hCombatants[i] = NULL; + } + + // Then start the next Battle + PostBattle(); + } + else + { + // Start the next round + StartRound(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Spawn a player in this battle +//----------------------------------------------------------------------------- +void CDiscArena::SpawnCombatant( CBasePlayer *pPlayer ) +{ + // Make sure she's out of spectator mode + pPlayer->StopObserver(); + + // Spawn + g_pGameRules->GetPlayerSpawnSpot( pPlayer ); + + // Prevent movement for a couple of seconds + pPlayer->pev->maxspeed = 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Start a Battle +//----------------------------------------------------------------------------- +void CDiscArena::StartBattleThink( void ) +{ + StartBattle(); +} + +//----------------------------------------------------------------------------- +// Purpose: Prevent firing during prematch +//----------------------------------------------------------------------------- +bool CDiscArena::AllowedToFire( void ) +{ + return ( m_iArenaState == ARENA_BATTLE_IN_PROGRESS ); +} + +//----------------------------------------------------------------------------- +// Purpose: New client was added to the arena +//----------------------------------------------------------------------------- +void CDiscArena::AddClient( CBasePlayer *pPlayer, BOOL bCheckStart ) +{ + // Remove them from any arena they're currently in + if ( pPlayer->m_pCurrentArena != NULL ) + pPlayer->m_pCurrentArena->RemoveClient( pPlayer ); + + m_iPlayers++; + + pPlayer->pev->groupinfo = pev->groupinfo; + + // Add her to the queue + AddPlayerToQueue( pPlayer ); + pPlayer->m_pCurrentArena = this; + + // Only check a restart if the flag is set + if ( bCheckStart ) + { + // Start a game if there's none going, and we now have enough players + // Allow starting of games if there aren't enough players, but there's more than 1 on the svr. + //if ( (m_iPlayersPerTeam > 1) && (m_iPlayers > 1) && (m_iPlayers < (m_iPlayersPerTeam * 2)) ) + //m_iPlayersPerTeam = 1; + // If we're in a battle, and the players-per-team isn't the map's setting, restart the battle + if ( (m_iArenaState == ARENA_WAITING_FOR_PLAYERS) && ( m_iPlayers >= (m_iPlayersPerTeam * 2) ) ) + { + // Start a battle in a second to let the clients learn about this new player + SetThink( &CDiscArena::StartBattleThink ); + pev->nextthink = gpGlobals->time + 1.0; + } + else + { + // Move her into spectator mode + MoveToSpectator( pPlayer ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Client was removed from the arena +//----------------------------------------------------------------------------- +void CDiscArena::RemoveClient( CBasePlayer *pPlayer ) +{ + m_iPlayers--; + + pPlayer->pev->groupinfo = 0; + pPlayer->m_pCurrentArena = NULL; + + // Is she in the current battle? + if ( pPlayer->pev->playerclass != 0 ) + { + // No, she's in the queue, so remove her. + RemovePlayerFromQueue( pPlayer ); + } + else if ( m_iArenaState != ARENA_WAITING_FOR_PLAYERS ) + { + // This team loses + m_iWinningTeam = (pPlayer->pev->team == 1) ? 2 : 1; + if ( m_iWinningTeam == 1 ) + m_iTeamOneScore = m_iMaxRounds - 1; // -1 because we'll get 1 point for winning this round in CheckOverThink + else + m_iTeamTwoScore = m_iMaxRounds - 1; // -1 because we'll get 1 point for winning this round in CheckOverThink + + // Find the player in the combatant list + for ( int i = 0; i < (m_iPlayersPerTeam * 2); i++ ) + { + // Check to see if this slot's already full + if ( m_hCombatants[ i ] == pPlayer ) + { + m_hCombatants[i] = NULL; + break; + } + } + + CheckOverThink(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Add a player to the end of the queue +//----------------------------------------------------------------------------- +void CDiscArena::AddPlayerToQueue( CBasePlayer *pPlayer ) +{ + if ( !pPlayer ) + return; + + if ( m_pPlayerQueue ) + { + CBasePlayer *pCurr = (CBasePlayer*)(CBaseEntity*)m_pPlayerQueue; + while ( pCurr->m_pNextPlayer ) + { + pCurr = (CBasePlayer*)(CBaseEntity*)pCurr->m_pNextPlayer; + } + + pCurr->m_pNextPlayer = pPlayer; + pPlayer->pev->playerclass = pCurr->pev->playerclass + 1; + } + else + { + m_pPlayerQueue = pPlayer; + pPlayer->pev->playerclass = 1; + } + + pPlayer->m_pNextPlayer = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove a player from the queue +//----------------------------------------------------------------------------- +void CDiscArena::RemovePlayerFromQueue( CBasePlayer *pPlayer ) +{ + bool bFoundHer = false; + + if ( !pPlayer ) + return; + + CBasePlayer *pCurr = (CBasePlayer*)(CBaseEntity*)m_pPlayerQueue; + CBasePlayer *pPrev = NULL; + + while ( pCurr ) + { + if (pCurr == pPlayer ) + { + bFoundHer = true; + if (pPrev) + pPrev->m_pNextPlayer = pCurr->m_pNextPlayer; + else + m_pPlayerQueue = pCurr->m_pNextPlayer; + } + else + { + pPrev = pCurr; + + // Adjust all the following player's queue positions + if (bFoundHer) + pCurr->pev->playerclass--; + } + + pCurr = (CBasePlayer*)(CBaseEntity*)pCurr->m_pNextPlayer; + } + + pPlayer->m_pNextPlayer = NULL; + pPlayer->pev->playerclass = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the next player from the queue, and shuffle the rest up +//----------------------------------------------------------------------------- +CBasePlayer *CDiscArena::GetNextPlayer( void ) +{ + if ( m_pPlayerQueue == NULL ) + return NULL; + + CBasePlayer *pCurr = (CBasePlayer*)(CBaseEntity*)m_pPlayerQueue; + RemovePlayerFromQueue( (CBasePlayer*)(CBaseEntity*)m_pPlayerQueue ); + + return pCurr; +} + +//----------------------------------------------------------------------------- +// Returns TRUE if the Arena is full +int CDiscArena::IsFull( void ) +{ + if ( m_iPlayers < (m_iPlayersPerTeam * 2) ) + return FALSE; + + return TRUE; +} + +//----------------------------------------------------------------------------- +// Returns the first player in the Arena's queue, if any +CBasePlayer *CDiscArena::GetFirstSparePlayer( void ) +{ + if ( m_pPlayerQueue == NULL ) + return NULL; + + return (CBasePlayer*)(CBaseEntity*)m_pPlayerQueue; +} + +//----------------------------------------------------------------------------- +// Add a client to an Arena. Find the first arena that doesn't have two +// players in it, and add this player to that. If we find a third player +// Spectating another arena, grab them and add them to this player's arena. +void AddClientToArena( CBasePlayer *pPlayer ) +{ + // First, find an arena for this player to be put into + for (int i = 0; i < MAX_ARENAS; i++) + { + if ( g_pArenaList[i]->IsFull() == FALSE ) + { + int iArenaNumber = i; + + g_pArenaList[iArenaNumber]->AddClient( pPlayer, TRUE ); + + bool bFoundOne = TRUE; + // Now, if this arena's not full, try to find more player to join her + while ( (g_pArenaList[iArenaNumber]->IsFull() == FALSE) && bFoundOne ) + { + bFoundOne = FALSE; + + // Cycle through all the arenas and find a spare player + for (int j = 0; j < MAX_ARENAS; j++) + { + CBasePlayer *pSparePlayer = g_pArenaList[j]->GetFirstSparePlayer(); + if (pSparePlayer && pSparePlayer != pPlayer) + { + g_pArenaList[j]->RemoveClient( pSparePlayer ); + g_pArenaList[iArenaNumber]->AddClient( pSparePlayer, TRUE ); + bFoundOne = TRUE; + break; + } + } + } + + // If we couldn't find another player for this arena, just add them to an existing arena + if ( g_pArenaList[iArenaNumber]->IsFull() == FALSE ) + { + // Add to the first full arena + for (int j = 0; j < MAX_ARENAS; j++) + { + if ( g_pArenaList[j]->IsFull() ) + { + // Remove from current + g_pArenaList[iArenaNumber]->RemoveClient( pPlayer ); + + // Add to full one + iArenaNumber = j; + g_pArenaList[iArenaNumber]->AddClient( pPlayer, TRUE ); + break; + } + } + } + + //ALERT( at_console, "ADDED %s to Arena %d\n", STRING(pPlayer->pev->netname), iArenaNumber ); + return; + } + } +} + +//----------------------------------------------------------------------------- +// Put the specified group of players into the current arena +int AddPlayers( int iPlayers, int iArenaNum ) +{ + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if (pPlayer && (pPlayer->m_pCurrentArena == NULL) && (pPlayer->m_bHasDisconnected != TRUE) ) + { + if ( pPlayer->m_iLastGameResult != iPlayers ) + continue; + + g_pArenaList[iArenaNum]->AddClient( pPlayer, FALSE ); + if ( g_pArenaList[iArenaNum]->IsFull() ) + iArenaNum++; + } + } + + return iArenaNum; +} + +//----------------------------------------------------------------------------- +// Take all the players not in battles and shuffle them into new groups +void ShufflePlayers( void ) +{ + int iArenaNum = 0; + + // Reset all Arenas + int i; + for ( i = 0; i < MAX_ARENAS; i++) + { + g_pArenaList[i]->Reset(); + } + + // Play the winners off against the other winners first + iArenaNum = AddPlayers( GAME_WON, iArenaNum ); + // First, add the players who didn't play at all + iArenaNum = AddPlayers( GAME_DIDNTPLAY, iArenaNum ); + // Then add the losers + iArenaNum = AddPlayers( GAME_LOST, iArenaNum ); + + // Then tell all full arenas to start + for (i = 0; i < MAX_ARENAS; i++) + { + if ( g_pArenaList[i]->IsFull() ) + { + g_pArenaList[i]->StartBattle(); + } + else + { + // If the arena has players in it, move them to the first full arena + if ( g_pArenaList[i]->m_iPlayers != 0 ) + { + for ( int j = 1; j <= gpGlobals->maxClients; j++ ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( j ); + + if (pPlayer && (pPlayer->m_pCurrentArena == g_pArenaList[i]) && (pPlayer->m_bHasDisconnected != TRUE) ) + { + // Add to the first arena + g_pArenaList[0]->AddClient( pPlayer, TRUE ); + + pPlayer->m_iLastGameResult = GAME_DIDNTPLAY; + } + } + } + } + } + +} + +//----------------------------------------------------------------------------- +// The Battle is over. First, check to see if there are games going on in +// other arenas. If there are, these players go watch one of them for a bit. +// If all games finish in that time, shuffle all the players and start new +// games. Otherwise, shuffle all the players who aren't still playing, and +// start new games with them. +void CDiscArena::PostBattle( void ) +{ + int iOtherGame = -1; + // First, see if there are any other games going on in other arenas + int i; + for ( i = 0; i < MAX_ARENAS; i++) + { + if ( g_pArenaList[i]->m_iArenaState != ARENA_WAITING_FOR_PLAYERS && g_pArenaList[i] != this ) + { + iOtherGame = i; + break; + } + } + + // If there are no other games, just shuffle and start again + if ( iOtherGame == -1 ) + { + ShufflePlayers(); + return; + } + + // There's another game going on. Move all the players from this arena to it to spectate. + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if (pPlayer && (pPlayer->pev->groupinfo & pev->groupinfo) && (pPlayer->m_bHasDisconnected != TRUE) ) + { + g_pArenaList[ iOtherGame ]->AddClient( (CBasePlayer*)pPlayer, TRUE ); + } + } + + m_iArenaState = ARENA_WAITING_FOR_PLAYERS; +} diff --git a/ricochet/dlls/disc_arena.h b/ricochet/dlls/disc_arena.h new file mode 100644 index 0000000..9670ef7 --- /dev/null +++ b/ricochet/dlls/disc_arena.h @@ -0,0 +1,111 @@ +//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#ifndef DISC_ARENA_H +#define DISC_ARENA_H +#pragma once + +#define MAX_ARENAS 16 + +// Arena States +#define ARENA_WAITING_FOR_PLAYERS 0 +#define ARENA_COUNTDOWN 1 +#define ARENA_BATTLE_IN_PROGRESS 2 +#define ARENA_SHOWING_SCORES 3 + +// Arena Times +#define ARENA_TIME_PREBATTLE 5 +#define ARENA_TIME_VIEWSCORES 3 +#define ARENA_TIME_ROUNDLIMIT 120 // Timelimit on rounds + +enum +{ + GAME_LOST = 0, + GAME_WON, + GAME_DIDNTPLAY, +}; + +//----------------------------------------------------------------------------- +// Arena object +class CDiscArena : public CBaseEntity +{ +public: + void Spawn( void ); + void Reset( void ); + + // Battle initialisation + void StartBattle( void ); + void StartRound( void ); + void SpawnCombatant( CBasePlayer *pPlayer ); + void MoveToSpectator( CBasePlayer *pPlayer ); + void EXPORT StartBattleThink( void ); + + // Battle running + void EXPORT CountDownThink( void ); + void PlayerKilled( CBasePlayer *pPlayer ); + void PlayerRespawned( CBasePlayer *pPlayer ); + void BattleOver( void ); + void EXPORT CheckOverThink( void ); + void EXPORT FinishedThink( void ); + void RestoreWorldObjects( void ); + int ValidateCombatants( void ); + void EXPORT TimeOver( void ); + void EXPORT BattleThink( void ); + bool CheckBattleOver( void ); + + // Client handling + void AddClient( CBasePlayer *pPlayer, BOOL bCheckStart ); + void RemoveClient( CBasePlayer *pPlayer ); + void AddPlayerToQueue( CBasePlayer *pPlayer ); + void RemovePlayerFromQueue( CBasePlayer *pPlayer ); + CBasePlayer * GetNextPlayer( void ); + + // Multiple Arena handling + int IsFull( void ); + CBasePlayer *GetFirstSparePlayer( void ); + void PostBattle( void ); + + // Game handling + bool AllowedToFire( void ); + + // Variables + int m_iArenaState; + int m_iPlayers; + int m_iMaxRounds; + int m_iCurrRound; + int m_iPlayersPerTeam; // Current players per team + int m_iSecondsTillStart; + int m_iWinningTeam; + int m_iTeamOneScore; + int m_iTeamTwoScore; + float m_flTimeLimitOver; + BOOL m_bShownTimeWarning; + + // Queue + EHANDLE m_pPlayerQueue; + + // Players in the current battle + EHANDLE m_hCombatants[ 32 ]; +}; + +extern CDiscArena *g_pArenaList[ MAX_ARENAS ]; +extern float g_iaDiscColors[33][3]; + +int InArenaMode(); + +#endif // DISC_ARENA_H diff --git a/ricochet/dlls/disc_objects.h b/ricochet/dlls/disc_objects.h new file mode 100644 index 0000000..067c56e --- /dev/null +++ b/ricochet/dlls/disc_objects.h @@ -0,0 +1,173 @@ +//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#ifndef DISK_OBJECTS_H +#define DISK_OBJECTS_H +#pragma once + +// Disc objects +class CDiscWeapon; + +class CDisc : public CGrenade +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT DiscTouch( CBaseEntity *pOther ); + void EXPORT DiscThink( void ); + static CDisc *CreateDisc( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner, CDiscWeapon *pLauncher, bool bDecapitator, int iPowerupFlags ); + + //void SetObjectCollisionBox( void ); + void ReturnToThrower( void ); + + virtual BOOL IsDisc( void ) { return TRUE; }; + + float m_fDontTouchEnemies; // Prevent enemy touches for a bit + float m_fDontTouchOwner; // Prevent friendly touches for a bit + int m_iBounces; // Number of bounces + EHANDLE m_hOwner; // Don't store in pev->owner, because it needs to hit its owner + CDiscWeapon *m_pLauncher; // pointer back to the launcher that fired me. + int m_iTrail; + int m_iSpriteTexture; + bool m_bDecapitate; // True if this is a decapitating shot + bool m_bRemoveSelf; // True if the owner of this disc has died + int m_iPowerupFlags;// Flags for any powerups active on this disc + bool m_bTeleported; // Disc has gone through a teleport + + EHANDLE m_pLockTarget; + + Vector m_vecActualVelocity; + Vector m_vecSideVelocity; + Vector m_vecOrg; +}; + +//=============================================================================== +// DISCWAR OBJECTS +//=============================================================================== +class CBaseTrigger : public CBaseToggle +{ +public: + void EXPORT TeleportTouch ( CBaseEntity *pOther ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT MultiTouch( CBaseEntity *pOther ); + void EXPORT HurtTouch ( CBaseEntity *pOther ); + void EXPORT CDAudioTouch ( CBaseEntity *pOther ); + void ActivateMultiTrigger( CBaseEntity *pActivator ); + void EXPORT MultiWaitOver( void ); + void EXPORT CounterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void InitTrigger( void ); + + virtual int ObjectCaps( void ) { return CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +// Brush that's status gets toggled by a disc hit +#define LAST_HITBY_FRIENDLY 1 +#define LAST_HITBY_ENEMY 2 + +class CDiscTarget : public CBaseTrigger +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Reset( void ); + + void EXPORT DiscToggleTouch( CBaseEntity *pOther ); + + int m_iszFriendlyHit; + int m_iszEnemyHit; + int m_iState; +}; + +//========================================================= +// Powerup object +class CDiscwarPowerup : public CBaseAnimating +{ +public: + void Spawn( void ); + void Activate( void ); + void Precache( void ); + void EXPORT PowerupTouch( CBaseEntity *pOther ); + void EXPORT ChoosePowerupThink( void ); + void EXPORT RemovePowerupThink( void ); + void EXPORT AnimateThink( void ); + void SetObjectCollisionBox( void ); + + void Disable(); + void Enable(); + + EHANDLE m_hPlayerIGaveTo; + int m_iPowerupType; +}; + +//=============================================================================== +// Brush that toggles between gone/there +#define PLAT_FADE_TIME 2.0 +class CPlatToggleRemove : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Reset( void ); + + void EXPORT PlatToggleRemoveUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT PlatRemoveThink( void ); + + float m_flRemoveAt; +}; + +//=============================================================================== +// Trigger that jumps a player to a target point +class CTriggerJump : public CBaseTrigger +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Activate( void ); + void Precache( void ); + void EXPORT JumpTouch( CBaseEntity *pOther ); + void EXPORT JumpUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + Vector m_vecTargetOrg; + float m_flHeight; + int m_iState; + +private: + unsigned short m_usJump; +}; + +//=============================================================================== +// Trigger that returns discs to their thrower immediately +class CTriggerDiscReturn : public CBaseTrigger +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT DiscReturnTouch( CBaseEntity *pOther ); +}; + +//=============================================================================== +// Trigger that starts the fall animation for players +class CTriggerFall : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT FallTouch( CBaseEntity *pOther ); +}; + +#endif // DISK_OBJECTS_H diff --git a/ricochet/dlls/disc_powerups.cpp b/ricochet/dlls/disc_powerups.cpp new file mode 100644 index 0000000..52e5c61 --- /dev/null +++ b/ricochet/dlls/disc_powerups.cpp @@ -0,0 +1,234 @@ +//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: Code for the various Discwar powerups +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "items.h" +#include "gamerules.h" +#include "discwar.h" +#include "disc_objects.h" +#include "disc_arena.h" + +extern int gmsgPowerup; + +//========================================================= +// POWERUPS +char *szPowerupModels[NUM_POWERUPS] = +{ + "models/pow_triple.mdl", + "models/pow_fast.mdl", + "models/pow_hard.mdl", + "models/pow_freeze.mdl", + //"models/pow_visual.mdl", +}; + +LINK_ENTITY_TO_CLASS( item_powerup, CDiscwarPowerup ); + +//========================================================= +void CDiscwarPowerup::Spawn( void ) +{ + Precache( ); + + // Don't fall down + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_TRIGGER; + UTIL_SetOrigin( pev, pev->origin ); + UTIL_SetSize(pev, Vector(-32, -32, -32), Vector(32, 32, 32)); + + // Use first model for now + SET_MODEL(ENT(pev), szPowerupModels[0]); + pev->effects |= EF_NODRAW; +} + +void CDiscwarPowerup::Activate( void ) +{ + // If Arena mode is on, spawn another powerup for every arena + if ( InArenaMode() ) + { + // If our groupinfo is set, we're not the first powerup + if ( pev->groupinfo == 0 ) + { + // Put this powerup in the first arena + pev->groupinfo = g_pArenaList[0]->pev->groupinfo; + + // Create a powerup for each of the other arenas + for (int i = 1; i < MAX_ARENAS; i++) + { + CBaseEntity * pPowerup; + + pPowerup = CBaseEntity::Create( "item_powerup", pev->origin, pev->angles ); + pPowerup->pev->groupinfo = g_pArenaList[i]->pev->groupinfo; + } + } + } + else + { + // Make the powerup start thinking + Enable(); + } +} + +void CDiscwarPowerup::Precache( void ) +{ + for (int i = 0; i < NUM_POWERUPS; i++) + PRECACHE_MODEL( szPowerupModels[i] ); + PRECACHE_SOUND( "powerup.wav" ); + PRECACHE_SOUND( "pspawn.wav" ); +} + +void CDiscwarPowerup::SetObjectCollisionBox( void ) +{ + pev->absmin = pev->origin + Vector( -64, -64, 0 ); + pev->absmax = pev->origin + Vector( 64, 64, 128 ); +} + +void CDiscwarPowerup::PowerupTouch( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + return; + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + + // Give the powerup to the player + pPlayer->GivePowerup( m_iPowerupType ); + m_hPlayerIGaveTo = pPlayer; + SetTouch( NULL ); + pev->effects |= EF_NODRAW; + + // Choose another powerup soon + SetThink( &CDiscwarPowerup::ChoosePowerupThink ); + pev->nextthink = gpGlobals->time + DISC_POWERUP_RESPAWN_TIME; + + // Play the powerup sound + EMIT_SOUND_DYN( pOther->edict(), CHAN_STATIC, "powerup.wav", 1.0, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); +} + +// Disappear and don't appear again until enabled +void CDiscwarPowerup::Disable() +{ + pev->effects |= EF_NODRAW; + pev->nextthink = 0; + SetThink( NULL ); + SetTouch( NULL ); +} + +// Come back and pick a new powerup +void CDiscwarPowerup::Enable() +{ + // Pick a powerup + SetThink( &CDiscwarPowerup::ChoosePowerupThink ); + pev->nextthink = gpGlobals->time + (DISC_POWERUP_RESPAWN_TIME / 2); +} + +//========================================================= +// Randomly decide what powerup to be +void CDiscwarPowerup::ChoosePowerupThink( void ) +{ + int iPowerup = RANDOM_LONG(0, NUM_POWERUPS-1); + m_iPowerupType = (1 << iPowerup); + + SET_MODEL( ENT(pev), szPowerupModels[iPowerup] ); + pev->effects &= ~EF_NODRAW; + + SetTouch(&CDiscwarPowerup::PowerupTouch); + + // Start Animating + pev->sequence = 0; + pev->frame = 0; + ResetSequenceInfo(); + + SetThink(&CDiscwarPowerup::AnimateThink); + pev->nextthink = gpGlobals->time + 0.1; + + pev->rendermode = kRenderTransAdd; + pev->renderamt = 150; + + // Play the powerup appear sound + EMIT_SOUND_DYN( edict(), CHAN_STATIC, "pspawn.wav", 1.0, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); +} + +void CDiscwarPowerup::AnimateThink( void ) +{ + StudioFrameAdvance(); + pev->nextthink = gpGlobals->time + 0.1; +} + +// Remove the powerup from the person we gave it to +void CDiscwarPowerup::RemovePowerupThink( void ) +{ + if (m_hPlayerIGaveTo == NULL) + return; + + ((CBasePlayer*)(CBaseEntity*)m_hPlayerIGaveTo)->RemovePowerup( m_iPowerupType ); + + // Pick a powerup later + SetThink( &CDiscwarPowerup::ChoosePowerupThink ); + pev->nextthink = gpGlobals->time + DISC_POWERUP_RESPAWN_TIME; +} + +//================================================================================= +// PLAYER HANDLING FOR POWERUPS +//========================================================= +void CBasePlayer::GivePowerup( int iPowerupType ) +{ + m_iPowerups |= iPowerupType; + + if ( m_iPowerups & POW_HARD ) + strcpy( m_szAnimExtention, "models/p_disc_hard.mdl" ); + + MESSAGE_BEGIN( MSG_ONE, gmsgPowerup, NULL, pev ); + WRITE_BYTE( m_iPowerups ); + MESSAGE_END(); + + m_iPowerupDiscs = MAX_DISCS; +} + +void CBasePlayer::RemovePowerup( int iPowerupType ) +{ + if ( iPowerupType & POW_HARD ) + strcpy( m_szAnimExtention, "models/p_disc.mdl" ); + + m_iPowerups &= ~iPowerupType; + + MESSAGE_BEGIN( MSG_ONE, gmsgPowerup, NULL, pev ); + WRITE_BYTE( m_iPowerups ); + MESSAGE_END(); + + m_iPowerupDiscs = 0; +} + +void CBasePlayer::RemoveAllPowerups( void ) +{ + m_iPowerups = 0; + m_iPowerupDiscs = 0; + + MESSAGE_BEGIN( MSG_ONE, gmsgPowerup, NULL, pev ); + WRITE_BYTE( m_iPowerups ); + MESSAGE_END(); +} + +bool CBasePlayer::HasPowerup( int iPowerupType ) +{ + return (m_iPowerups & iPowerupType) != 0; +} + diff --git a/ricochet/dlls/disc_weapon.h b/ricochet/dlls/disc_weapon.h new file mode 100644 index 0000000..c9c3f3b --- /dev/null +++ b/ricochet/dlls/disc_weapon.h @@ -0,0 +1,33 @@ +#if !defined( DISC_WEAPON_H ) +#define DISC_WEAPON_H +#ifdef _WIN32 +#pragma once +#endif + +class CDisc; +class CDiscWeapon : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 5; } + int GetItemInfo(ItemInfo *p); + + int AddDuplicate( CBasePlayerItem *pOriginal ); + CDisc *FireDisc( bool bDecapitator ); + void PrimaryAttack( void ); + void SecondaryAttack( void ); + BOOL Deploy( void ); + BOOL CanHolster( void ); + void Holster( int skiplocal = 0 ); + void WeaponIdle( void ); + + BOOL UseDecrement( void ) { return TRUE; }; + + int m_iSpriteTexture; + int m_iFastShotDiscs; +private: + unsigned short m_usFireDisc; +}; + +#endif // DISC_WEAPON_H diff --git a/ricochet/dlls/discwar.h b/ricochet/dlls/discwar.h new file mode 100644 index 0000000..410038d --- /dev/null +++ b/ricochet/dlls/discwar.h @@ -0,0 +1,60 @@ +//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: Header for Discwar +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#ifndef DISCWAR_H +#define DISCWAR_H +#pragma once + +#define WEAPON_DISC 1 +#define MAX_DISCS 3 // Max number of discs a player can carry +#define STARTING_DISCS MAX_DISCS // Number of discs a player starts with +#define NUM_FASTSHOT_DISCS 3 // Number of discs a player gets with the fastshot powerup per normal disc + +#define DISC_VELOCITY 1000 // Velocity multiplier for discs when thrown +#define DISC_PUSH_MULTIPLIER 1200 // Velocity multiplier used to push a player when hit by a disc + +//#define DISC_POWERUP_TIME 5 // Time (in seconds) a powerup lasts for +#define DISC_POWERUP_RESPAWN_TIME 10 // Time (in seconds) it takes after a powerup is picked up before the next one appears + +#define MAX_SCORE_TIME_AFTER_HIT 4.0 // Time (in seconds) in which a player gets a point if the enemy dies within this time + // after being hit by a disc. + +// Powerups +#define POW_TRIPLE (1<<0) +#define POW_FAST (1<<1) +#define POW_HARD (1<<2) +#define POW_FREEZE (1<<3) + +#define POW_VISUALIZE_REBOUNDS (1<<4) // Removing this one for now + +#define NUM_POWERUPS 4 // 4, not 5, because VISUALIZE_REBOUNDS is removed. + +#define FREEZE_TIME 7 +#define FREEZE_SPEED 50 + +// Rewards +#define REWARD_BOUNCE_NONE (1<<1) +#define REWARD_BOUNCE_ONE (1<<2) +#define REWARD_BOUNCE_TWO (1<<3) +#define REWARD_BOUNCE_THREE (1<<4) +#define REWARD_DECAPITATE (1<<5) +#define REWARD_TELEPORT (1<<6) +#define REWARD_DOUBLEKILL (1<<7) + +#endif // DISCWAR_H + diff --git a/ricochet/dlls/doors.cpp b/ricochet/dlls/doors.cpp new file mode 100644 index 0000000..c084dac --- /dev/null +++ b/ricochet/dlls/doors.cpp @@ -0,0 +1,1026 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== doors.cpp ======================================================== + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "doors.h" + + +extern void SetMovedir(entvars_t* ev); + +#define noiseMoving noise1 +#define noiseArrived noise2 + +class CBaseDoor : public CBaseToggle +{ +public: + void Spawn( void ); + void Precache( void ); + virtual void KeyValue( KeyValueData *pkvd ); + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void Blocked( CBaseEntity *pOther ); + + + virtual int ObjectCaps( void ) + { + if (pev->spawnflags & SF_ITEM_USE_ONLY) + return (CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_IMPULSE_USE; + else + return (CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION); + }; + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + virtual void SetToggleState( int state ); + + // used to selectivly override defaults + void EXPORT DoorTouch( CBaseEntity *pOther ); + + // local functions + int DoorActivate( ); + void EXPORT DoorGoUp( void ); + void EXPORT DoorGoDown( void ); + void EXPORT DoorHitTop( void ); + void EXPORT DoorHitBottom( void ); + + BYTE m_bHealthValue;// some doors are medi-kit doors, they give players health + + BYTE m_bMoveSnd; // sound a door makes while moving + BYTE m_bStopSnd; // sound a door makes when it stops + + locksound_t m_ls; // door lock sounds + + BYTE m_bLockedSound; // ordinals from entity selection + BYTE m_bLockedSentence; + BYTE m_bUnlockedSound; + BYTE m_bUnlockedSentence; +}; + + +TYPEDESCRIPTION CBaseDoor::m_SaveData[] = +{ + DEFINE_FIELD( CBaseDoor, m_bHealthValue, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bMoveSnd, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bStopSnd, FIELD_CHARACTER ), + + DEFINE_FIELD( CBaseDoor, m_bLockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bLockedSentence, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bUnlockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bUnlockedSentence, FIELD_CHARACTER ), + +}; + +IMPLEMENT_SAVERESTORE( CBaseDoor, CBaseToggle ); + + +#define DOOR_SENTENCEWAIT 6 +#define DOOR_SOUNDWAIT 3 +#define BUTTON_SOUNDWAIT 0.5 + +// play door or button locked or unlocked sounds. +// pass in pointer to valid locksound struct. +// if flocked is true, play 'door is locked' sound, +// otherwise play 'door is unlocked' sound +// NOTE: this routine is shared by doors and buttons + +void PlayLockSounds(entvars_t *pev, locksound_t *pls, int flocked, int fbutton) +{ + // LOCKED SOUND + + // CONSIDER: consolidate the locksound_t struct (all entries are duplicates for lock/unlock) + // CONSIDER: and condense this code. + float flsoundwait; + + if (fbutton) + flsoundwait = BUTTON_SOUNDWAIT; + else + flsoundwait = DOOR_SOUNDWAIT; + + if (flocked) + { + int fplaysound = (pls->sLockedSound && gpGlobals->time > pls->flwaitSound); + int fplaysentence = (pls->sLockedSentence && !pls->bEOFLocked && gpGlobals->time > pls->flwaitSentence); + float fvol; + + if (fplaysound && fplaysentence) + fvol = 0.25; + else + fvol = 1.0; + + // if there is a locked sound, and we've debounced, play sound + if (fplaysound) + { + // play 'door locked' sound + EMIT_SOUND(ENT(pev), CHAN_ITEM, (char*)STRING(pls->sLockedSound), fvol, ATTN_NORM); + pls->flwaitSound = gpGlobals->time + flsoundwait; + } + + // if there is a sentence, we've not played all in list, and we've debounced, play sound + if (fplaysentence) + { + // play next 'door locked' sentence in group + int iprev = pls->iLockedSentence; + + pls->iLockedSentence = SENTENCEG_PlaySequentialSz(ENT(pev), STRING(pls->sLockedSentence), + 0.85, ATTN_NORM, 0, 100, pls->iLockedSentence, FALSE); + pls->iUnlockedSentence = 0; + + // make sure we don't keep calling last sentence in list + pls->bEOFLocked = (iprev == pls->iLockedSentence); + + pls->flwaitSentence = gpGlobals->time + DOOR_SENTENCEWAIT; + } + } + else + { + // UNLOCKED SOUND + + int fplaysound = (pls->sUnlockedSound && gpGlobals->time > pls->flwaitSound); + int fplaysentence = (pls->sUnlockedSentence && !pls->bEOFUnlocked && gpGlobals->time > pls->flwaitSentence); + float fvol; + + // if playing both sentence and sound, lower sound volume so we hear sentence + if (fplaysound && fplaysentence) + fvol = 0.25; + else + fvol = 1.0; + + // play 'door unlocked' sound if set + if (fplaysound) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, (char*)STRING(pls->sUnlockedSound), fvol, ATTN_NORM); + pls->flwaitSound = gpGlobals->time + flsoundwait; + } + + // play next 'door unlocked' sentence in group + if (fplaysentence) + { + int iprev = pls->iUnlockedSentence; + + pls->iUnlockedSentence = SENTENCEG_PlaySequentialSz(ENT(pev), STRING(pls->sUnlockedSentence), + 0.85, ATTN_NORM, 0, 100, pls->iUnlockedSentence, FALSE); + pls->iLockedSentence = 0; + + // make sure we don't keep calling last sentence in list + pls->bEOFUnlocked = (iprev == pls->iUnlockedSentence); + pls->flwaitSentence = gpGlobals->time + DOOR_SENTENCEWAIT; + } + } +} + +// +// Cache user-entity-field values until spawn is called. +// + +void CBaseDoor::KeyValue( KeyValueData *pkvd ) +{ + + if (FStrEq(pkvd->szKeyName, "skin"))//skin is used for content type + { + pev->skin = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "movesnd")) + { + m_bMoveSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "stopsnd")) + { + m_bStopSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "healthvalue")) + { + m_bHealthValue = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sound")) + { + m_bLockedSound = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sentence")) + { + m_bLockedSentence = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sound")) + { + m_bUnlockedSound = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sentence")) + { + m_bUnlockedSentence = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "WaveHeight")) + { + pev->scale = atof(pkvd->szValue) * (1.0/8.0); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +/*QUAKED func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK TOGGLE +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. + +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). + +"angle" determines the opening direction +"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" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (2 default) +"sounds" +0) no sound +1) stone +2) base +3) stone chain +4) screechy metal +*/ + +LINK_ENTITY_TO_CLASS( func_door, CBaseDoor ); +// +// func_water - same as a door. +// +LINK_ENTITY_TO_CLASS( func_water, CBaseDoor ); + + +void CBaseDoor::Spawn( ) +{ + Precache(); + SetMovedir (pev); + + if ( pev->skin == 0 ) + {//normal door + if ( FBitSet (pev->spawnflags, SF_DOOR_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + } + else + {// special contents + pev->solid = SOLID_NOT; + SetBits( pev->spawnflags, SF_DOOR_SILENT ); // water is silent for now + } + + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + if (pev->speed == 0) + pev->speed = 100; + + m_vecPosition1 = pev->origin; + // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big + m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); + ASSERTSZ(m_vecPosition1 != m_vecPosition2, "door start/end positions are equal"); + if ( FBitSet (pev->spawnflags, SF_DOOR_START_OPEN) ) + { // swap pos1 and pos2, put door at pos2 + UTIL_SetOrigin(pev, m_vecPosition2); + m_vecPosition2 = m_vecPosition1; + m_vecPosition1 = pev->origin; + } + + m_toggle_state = TS_AT_BOTTOM; + + // if the door is flagged for USE button activation only, use NULL touch function + if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + { + SetTouch ( NULL ); + } + else // touchable button + SetTouch( &CBaseDoor::DoorTouch ); +} + + +void CBaseDoor :: SetToggleState( int state ) +{ + if ( state == TS_AT_TOP ) + UTIL_SetOrigin( pev, m_vecPosition2 ); + else + UTIL_SetOrigin( pev, m_vecPosition1 ); +} + + +void CBaseDoor::Precache( void ) +{ + char *pszSound; + +// set the door's "in-motion" sound + switch (m_bMoveSnd) + { + case 0: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("doors/doormove1.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove1.wav"); + break; + case 2: + PRECACHE_SOUND ("doors/doormove2.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove2.wav"); + break; + case 3: + PRECACHE_SOUND ("doors/doormove3.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove3.wav"); + break; + case 4: + PRECACHE_SOUND ("doors/doormove4.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove4.wav"); + break; + case 5: + PRECACHE_SOUND ("doors/doormove5.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove5.wav"); + break; + case 6: + PRECACHE_SOUND ("doors/doormove6.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove6.wav"); + break; + case 7: + PRECACHE_SOUND ("doors/doormove7.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove7.wav"); + break; + case 8: + PRECACHE_SOUND ("doors/doormove8.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove8.wav"); + break; + case 9: + PRECACHE_SOUND ("doors/doormove9.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove9.wav"); + break; + case 10: + PRECACHE_SOUND ("doors/doormove10.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove10.wav"); + break; + default: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + } + +// set the door's 'reached destination' stop sound + switch (m_bStopSnd) + { + case 0: + pev->noiseArrived = ALLOC_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("doors/doorstop1.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop1.wav"); + break; + case 2: + PRECACHE_SOUND ("doors/doorstop2.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop2.wav"); + break; + case 3: + PRECACHE_SOUND ("doors/doorstop3.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop3.wav"); + break; + case 4: + PRECACHE_SOUND ("doors/doorstop4.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop4.wav"); + break; + case 5: + PRECACHE_SOUND ("doors/doorstop5.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop5.wav"); + break; + case 6: + PRECACHE_SOUND ("doors/doorstop6.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop6.wav"); + break; + case 7: + PRECACHE_SOUND ("doors/doorstop7.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop7.wav"); + break; + case 8: + PRECACHE_SOUND ("doors/doorstop8.wav"); + pev->noiseArrived = ALLOC_STRING("doors/doorstop8.wav"); + break; + default: + pev->noiseArrived = ALLOC_STRING("common/null.wav"); + break; + } + + // get door button sounds, for doors which are directly 'touched' to open + + if (m_bLockedSound) + { + pszSound = ButtonSound( (int)m_bLockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sLockedSound = ALLOC_STRING(pszSound); + } + + if (m_bUnlockedSound) + { + pszSound = ButtonSound( (int)m_bUnlockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sUnlockedSound = ALLOC_STRING(pszSound); + } + + // get sentence group names, for doors which are directly 'touched' to open + + switch (m_bLockedSentence) + { + case 1: m_ls.sLockedSentence = ALLOC_STRING("NA"); break; // access denied + case 2: m_ls.sLockedSentence = ALLOC_STRING("ND"); break; // security lockout + case 3: m_ls.sLockedSentence = ALLOC_STRING("NF"); break; // blast door + case 4: m_ls.sLockedSentence = ALLOC_STRING("NFIRE"); break; // fire door + case 5: m_ls.sLockedSentence = ALLOC_STRING("NCHEM"); break; // chemical door + case 6: m_ls.sLockedSentence = ALLOC_STRING("NRAD"); break; // radiation door + case 7: m_ls.sLockedSentence = ALLOC_STRING("NCON"); break; // gen containment + case 8: m_ls.sLockedSentence = ALLOC_STRING("NH"); break; // maintenance door + case 9: m_ls.sLockedSentence = ALLOC_STRING("NG"); break; // broken door + + default: m_ls.sLockedSentence = 0; break; + } + + switch (m_bUnlockedSentence) + { + case 1: m_ls.sUnlockedSentence = ALLOC_STRING("EA"); break; // access granted + case 2: m_ls.sUnlockedSentence = ALLOC_STRING("ED"); break; // security door + case 3: m_ls.sUnlockedSentence = ALLOC_STRING("EF"); break; // blast door + case 4: m_ls.sUnlockedSentence = ALLOC_STRING("EFIRE"); break; // fire door + case 5: m_ls.sUnlockedSentence = ALLOC_STRING("ECHEM"); break; // chemical door + case 6: m_ls.sUnlockedSentence = ALLOC_STRING("ERAD"); break; // radiation door + case 7: m_ls.sUnlockedSentence = ALLOC_STRING("ECON"); break; // gen containment + case 8: m_ls.sUnlockedSentence = ALLOC_STRING("EH"); break; // maintenance door + + default: m_ls.sUnlockedSentence = 0; break; + } +} + +// +// Doors not tied to anything (e.g. button, another door) can be touched, to make them activate. +// +void CBaseDoor::DoorTouch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + + // Ignore touches by anything but players + if (!FClassnameIs(pevToucher, "player")) + return; + + // If door has master, and it's not ready to trigger, + // play 'locked' sound + + if (m_sMaster && !UTIL_IsMasterTriggered(m_sMaster, pOther)) + PlayLockSounds(pev, &m_ls, TRUE, FALSE); + + // If door is somebody's target, then touching does nothing. + // You have to activate the owner (e.g. button). + + if (!FStringNull(pev->targetname)) + { + // play locked sound + PlayLockSounds(pev, &m_ls, TRUE, FALSE); + return; + } + + m_hActivator = pOther;// remember who activated the door + + if (DoorActivate( )) + SetTouch( NULL ); // Temporarily disable the touch function, until movement is finished. +} + + +// +// Used by SUB_UseTargets, when a door is the target of a button. +// +void CBaseDoor::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_hActivator = pActivator; + // if not ready to be used, ignore "use" command. + if (m_toggle_state == TS_AT_BOTTOM || FBitSet(pev->spawnflags, SF_DOOR_NO_AUTO_RETURN) && m_toggle_state == TS_AT_TOP) + DoorActivate(); +} + +// +// Causes the door to "do its thing", i.e. start moving, and cascade activation. +// +int CBaseDoor::DoorActivate( ) +{ + if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + return 0; + + if (FBitSet(pev->spawnflags, SF_DOOR_NO_AUTO_RETURN) && m_toggle_state == TS_AT_TOP) + {// door should close + DoorGoDown(); + } + else + {// door should open + + if ( m_hActivator != NULL && m_hActivator->IsPlayer() ) + {// give health if player opened the door (medikit) + // VARS( m_eoActivator )->health += m_bHealthValue; + + m_hActivator->TakeHealth( m_bHealthValue, DMG_GENERIC ); + + } + + // play door unlock sounds + PlayLockSounds(pev, &m_ls, FALSE, FALSE); + + DoorGoUp(); + } + + return 1; +} + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); + +// +// Starts the door going to its "up" position (simply ToggleData->vecPosition2). +// +void CBaseDoor::DoorGoUp( void ) +{ + entvars_t *pevActivator; + + // It could be going-down, if blocked. + ASSERT(m_toggle_state == TS_AT_BOTTOM || m_toggle_state == TS_GOING_DOWN); + + // emit door moving and stop sounds on CHAN_STATIC so that the multicast doesn't + // filter them out and leave a client stuck with looping door sounds! + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving), 1, ATTN_NORM); + + m_toggle_state = TS_GOING_UP; + + SetMoveDone( &CBaseDoor::DoorHitTop ); + if ( FClassnameIs(pev, "func_door_rotating")) // !!! BUGBUG Triggered doors don't work with this yet + { + float sign = 1.0; + + if ( m_hActivator != NULL ) + { + pevActivator = m_hActivator->pev; + + if ( !FBitSet( pev->spawnflags, SF_DOOR_ONEWAY ) && pev->movedir.y ) // Y axis rotation, move away from the player + { + Vector vec = pevActivator->origin - pev->origin; + Vector angles = pevActivator->angles; + angles.x = 0; + angles.z = 0; + UTIL_MakeVectors (angles); + // Vector vnext = (pevToucher->origin + (pevToucher->velocity * 10)) - pev->origin; + UTIL_MakeVectors ( pevActivator->angles ); + Vector vnext = (pevActivator->origin + (gpGlobals->v_forward * 10)) - pev->origin; + if ( (vec.x*vnext.y - vec.y*vnext.x) < 0 ) + sign = -1.0; + } + } + AngularMove(m_vecAngle2*sign, pev->speed); + } + else + LinearMove(m_vecPosition2, pev->speed); +} + + +// +// The door has reached the "up" position. Either go back down, or wait for another activation. +// +void CBaseDoor::DoorHitTop( void ) +{ + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + { + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving) ); + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseArrived), 1, ATTN_NORM); + } + + ASSERT(m_toggle_state == TS_GOING_UP); + m_toggle_state = TS_AT_TOP; + + // toggle-doors don't come down automatically, they wait for refire. + if (FBitSet(pev->spawnflags, SF_DOOR_NO_AUTO_RETURN)) + { + // Re-instate touch method, movement is complete + if ( !FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + SetTouch( &CBaseDoor::DoorTouch ); + } + else + { + // In flWait seconds, DoorGoDown will fire, unless wait is -1, then door stays open + pev->nextthink = pev->ltime + m_flWait; + SetThink( &CBaseDoor::DoorGoDown ); + + if ( m_flWait == -1 ) + { + pev->nextthink = -1; + } + } + + // Fire the close target (if startopen is set, then "top" is closed) - netname is the close target + if ( pev->netname && (pev->spawnflags & SF_DOOR_START_OPEN) ) + FireTargets( STRING(pev->netname), m_hActivator, this, USE_TOGGLE, 0 ); + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); // this isn't finished +} + + +// +// Starts the door going to its "down" position (simply ToggleData->vecPosition1). +// +void CBaseDoor::DoorGoDown( void ) +{ + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving), 1, ATTN_NORM); + +#ifdef DOOR_ASSERT + ASSERT(m_toggle_state == TS_AT_TOP); +#endif // DOOR_ASSERT + m_toggle_state = TS_GOING_DOWN; + + SetMoveDone( &CBaseDoor::DoorHitBottom ); + if ( FClassnameIs(pev, "func_door_rotating"))//rotating door + AngularMove( m_vecAngle1, pev->speed); + else + LinearMove( m_vecPosition1, pev->speed); +} + +// +// The door has reached the "down" position. Back to quiescence. +// +void CBaseDoor::DoorHitBottom( void ) +{ + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + { + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving) ); + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseArrived), 1, ATTN_NORM); + } + + ASSERT(m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_AT_BOTTOM; + + // Re-instate touch method, cycle is complete + if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + {// use only door + SetTouch ( NULL ); + } + else // touchable door + SetTouch( &CBaseDoor::DoorTouch ); + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); // this isn't finished + + // Fire the close target (if startopen is set, then "top" is closed) - netname is the close target + if ( pev->netname && !(pev->spawnflags & SF_DOOR_START_OPEN) ) + FireTargets( STRING(pev->netname), m_hActivator, this, USE_TOGGLE, 0 ); +} + +void CBaseDoor::Blocked( CBaseEntity *pOther ) +{ + edict_t *pentTarget = NULL; + CBaseDoor *pDoor = NULL; + + + // Hurt the blocker a little. + if ( pev->dmg ) + pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH ); + + // if a door has a negative wait, it would never come back if blocked, + // so let it just squash the object to death real fast + + if (m_flWait >= 0) + { + if (m_toggle_state == TS_GOING_DOWN) + { + DoorGoUp(); + } + else + { + DoorGoDown(); + } + } + + // Block all door pieces with the same targetname here. + if ( !FStringNull ( pev->targetname ) ) + { + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->targetname)); + + if ( VARS( pentTarget ) != pev ) + { + if (FNullEnt(pentTarget)) + break; + + if ( FClassnameIs ( pentTarget, "func_door" ) || FClassnameIs ( pentTarget, "func_door_rotating" ) ) + { + + pDoor = GetClassPtr( (CBaseDoor *) VARS(pentTarget) ); + + if ( pDoor->m_flWait >= 0) + { + if (pDoor->pev->velocity == pev->velocity && pDoor->pev->avelocity == pev->velocity) + { + // this is the most hacked, evil, bastardized thing I've ever seen. kjb + if ( FClassnameIs ( pentTarget, "func_door" ) ) + {// set origin to realign normal doors + pDoor->pev->origin = pev->origin; + pDoor->pev->velocity = g_vecZero;// stop! + } + else + {// set angles to realign rotating doors + pDoor->pev->angles = pev->angles; + pDoor->pev->avelocity = g_vecZero; + } + } + + if ( pDoor->m_toggle_state == TS_GOING_DOWN) + pDoor->DoorGoUp(); + else + pDoor->DoorGoDown(); + } + } + } + } + } +} + + +/*QUAKED FuncRotDoorSpawn (0 .5 .8) ? START_OPEN REVERSE +DOOR_DONT_LINK TOGGLE X_AXIS Y_AXIS +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. + +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). + +You need to have an origin brush as part of this entity. The +center of that brush will be +the point around which it is rotated. It will rotate around the Z +axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"distance" is how many degrees the door will be rotated. +"speed" determines how fast the door moves; default value is 100. + +REVERSE will cause the door to rotate in the opposite direction. + +"angle" determines the opening direction +"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" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"dmg" damage to inflict when blocked (2 default) +"sounds" +0) no sound +1) stone +2) base +3) stone chain +4) screechy metal +*/ +class CRotDoor : public CBaseDoor +{ +public: + void Spawn( void ); + virtual void SetToggleState( int state ); +}; + +LINK_ENTITY_TO_CLASS( func_door_rotating, CRotDoor ); + + +void CRotDoor::Spawn( void ) +{ + Precache(); + // set the axis of rotation + CBaseToggle::AxisDir( pev ); + + // check for clockwise rotation + if ( FBitSet (pev->spawnflags, SF_DOOR_ROTATE_BACKWARDS) ) + pev->movedir = pev->movedir * -1; + + //m_flWait = 2; who the hell did this? (sjb) + m_vecAngle1 = pev->angles; + m_vecAngle2 = pev->angles + pev->movedir * m_flMoveDistance; + + ASSERTSZ(m_vecAngle1 != m_vecAngle2, "rotating door start/end positions are equal"); + + if ( FBitSet (pev->spawnflags, SF_DOOR_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + if (pev->speed == 0) + pev->speed = 100; + +// DOOR_START_OPEN is to allow an entity to be lighted in the closed position +// but spawn in the open position + if ( FBitSet (pev->spawnflags, SF_DOOR_START_OPEN) ) + { // swap pos1 and pos2, put door at pos2, invert movement direction + pev->angles = m_vecAngle2; + Vector vecSav = m_vecAngle1; + m_vecAngle2 = m_vecAngle1; + m_vecAngle1 = vecSav; + pev->movedir = pev->movedir * -1; + } + + m_toggle_state = TS_AT_BOTTOM; + + if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) ) + { + SetTouch ( NULL ); + } + else // touchable button + SetTouch( &CRotDoor::DoorTouch ); +} + + +void CRotDoor :: SetToggleState( int state ) +{ + if ( state == TS_AT_TOP ) + pev->angles = m_vecAngle2; + else + pev->angles = m_vecAngle1; + + UTIL_SetOrigin( pev, pev->origin ); +} + + +class CMomentaryDoor : public CBaseToggle +{ +public: + void Spawn( void ); + void Precache( void ); + + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + BYTE m_bMoveSnd; // sound a door makes while moving +}; + +LINK_ENTITY_TO_CLASS( momentary_door, CMomentaryDoor ); + +TYPEDESCRIPTION CMomentaryDoor::m_SaveData[] = +{ + DEFINE_FIELD( CMomentaryDoor, m_bMoveSnd, FIELD_CHARACTER ), +}; + +IMPLEMENT_SAVERESTORE( CMomentaryDoor, CBaseToggle ); + +void CMomentaryDoor::Spawn( void ) +{ + SetMovedir (pev); + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + if (pev->speed == 0) + pev->speed = 100; + if (pev->dmg == 0) + pev->dmg = 2; + + m_vecPosition1 = pev->origin; + // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big + m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); + ASSERTSZ(m_vecPosition1 != m_vecPosition2, "door start/end positions are equal"); + + if ( FBitSet (pev->spawnflags, SF_DOOR_START_OPEN) ) + { // swap pos1 and pos2, put door at pos2 + UTIL_SetOrigin(pev, m_vecPosition2); + m_vecPosition2 = m_vecPosition1; + m_vecPosition1 = pev->origin; + } + SetTouch( NULL ); + + Precache(); +} + +void CMomentaryDoor::Precache( void ) +{ + +// set the door's "in-motion" sound + switch (m_bMoveSnd) + { + case 0: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("doors/doormove1.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove1.wav"); + break; + case 2: + PRECACHE_SOUND ("doors/doormove2.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove2.wav"); + break; + case 3: + PRECACHE_SOUND ("doors/doormove3.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove3.wav"); + break; + case 4: + PRECACHE_SOUND ("doors/doormove4.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove4.wav"); + break; + case 5: + PRECACHE_SOUND ("doors/doormove5.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove5.wav"); + break; + case 6: + PRECACHE_SOUND ("doors/doormove6.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove6.wav"); + break; + case 7: + PRECACHE_SOUND ("doors/doormove7.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove7.wav"); + break; + case 8: + PRECACHE_SOUND ("doors/doormove8.wav"); + pev->noiseMoving = ALLOC_STRING("doors/doormove8.wav"); + break; + default: + pev->noiseMoving = ALLOC_STRING("common/null.wav"); + break; + } +} + +void CMomentaryDoor::KeyValue( KeyValueData *pkvd ) +{ + + if (FStrEq(pkvd->szKeyName, "movesnd")) + { + m_bMoveSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "stopsnd")) + { +// m_bStopSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "healthvalue")) + { +// m_bHealthValue = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CMomentaryDoor::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( useType != USE_SET ) // Momentary buttons will pass down a float in here + return; + + if ( value > 1.0 ) + value = 1.0; + Vector move = m_vecPosition1 + (value * (m_vecPosition2 - m_vecPosition1)); + + Vector delta = move - pev->origin; + float speed = delta.Length() * 10; + + if ( speed != 0 ) + { + // This entity only thinks when it moves, so if it's thinking, it's in the process of moving + // play the sound when it starts moving + if ( pev->nextthink < pev->ltime || pev->nextthink == 0 ) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving), 1, ATTN_NORM); + + LinearMove( move, speed ); + } + +} diff --git a/ricochet/dlls/doors.h b/ricochet/dlls/doors.h new file mode 100644 index 0000000..07447a0 --- /dev/null +++ b/ricochet/dlls/doors.h @@ -0,0 +1,33 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef DOORS_H +#define DOORS_H + +// doors +#define SF_DOOR_ROTATE_Y 0 +#define SF_DOOR_START_OPEN 1 +#define SF_DOOR_ROTATE_BACKWARDS 2 +#define SF_DOOR_PASSABLE 8 +#define SF_DOOR_ONEWAY 16 +#define SF_DOOR_NO_AUTO_RETURN 32 +#define SF_DOOR_ROTATE_Z 64 +#define SF_DOOR_ROTATE_X 128 +#define SF_DOOR_USE_ONLY 256 // door must be opened by player's use button. +#define SF_DOOR_NOMONSTERS 512 // Monster can't open +#define SF_DOOR_SILENT 0x80000000 + + + +#endif //DOORS_H diff --git a/ricochet/dlls/effects.cpp b/ricochet/dlls/effects.cpp new file mode 100644 index 0000000..e232e57 --- /dev/null +++ b/ricochet/dlls/effects.cpp @@ -0,0 +1,2268 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "customentity.h" +#include "effects.h" +#include "weapons.h" +#include "decals.h" +#include "func_break.h" +#include "shake.h" + +#define SF_GIBSHOOTER_REPEATABLE 1 // allows a gibshooter to be refired + +#define SF_FUNNEL_REVERSE 1 // funnel effect repels particles instead of attracting them. + + +// Lightning target, just alias landmark +LINK_ENTITY_TO_CLASS( info_target, CPointEntity ); + + +class CBubbling : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + void EXPORT FizzThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + static TYPEDESCRIPTION m_SaveData[]; + + int m_density; + int m_frequency; + int m_bubbleModel; + int m_state; +}; + +LINK_ENTITY_TO_CLASS( env_bubbles, CBubbling ); + +TYPEDESCRIPTION CBubbling::m_SaveData[] = +{ + DEFINE_FIELD( CBubbling, m_density, FIELD_INTEGER ), + DEFINE_FIELD( CBubbling, m_frequency, FIELD_INTEGER ), + DEFINE_FIELD( CBubbling, m_state, FIELD_INTEGER ), + // Let spawn restore this! + // DEFINE_FIELD( CBubbling, m_bubbleModel, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CBubbling, CBaseEntity ); + + +#define SF_BUBBLES_STARTOFF 0x0001 + +void CBubbling::Spawn( void ) +{ + Precache( ); + SET_MODEL( ENT(pev), STRING(pev->model) ); // Set size + + pev->solid = SOLID_NOT; // Remove model & collisions + pev->renderamt = 0; // The engine won't draw this model if this is set to 0 and blending is on + pev->rendermode = kRenderTransTexture; + int speed = pev->speed > 0 ? pev->speed : -pev->speed; + + // HACKHACK!!! - Speed in rendercolor + pev->rendercolor.x = speed >> 8; + pev->rendercolor.y = speed & 255; + pev->rendercolor.z = (pev->speed < 0) ? 1 : 0; + + + if ( !(pev->spawnflags & SF_BUBBLES_STARTOFF) ) + { + SetThink( &CBubbling::FizzThink ); + pev->nextthink = gpGlobals->time + 2.0; + m_state = 1; + } + else + m_state = 0; +} + +void CBubbling::Precache( void ) +{ + m_bubbleModel = PRECACHE_MODEL("sprites/bubble.spr"); // Precache bubble sprite +} + + +void CBubbling::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( ShouldToggle( useType, m_state ) ) + m_state = !m_state; + + if ( m_state ) + { + SetThink( & CBubbling::FizzThink ); + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + SetThink( NULL ); + pev->nextthink = 0; + } +} + + +void CBubbling::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "density")) + { + m_density = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "frequency")) + { + m_frequency = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "current")) + { + pev->speed = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +void CBubbling::FizzThink( void ) +{ + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, VecBModelOrigin(pev) ); + WRITE_BYTE( TE_FIZZ ); + WRITE_SHORT( (short)ENTINDEX( edict() ) ); + WRITE_SHORT( (short)m_bubbleModel ); + WRITE_BYTE( m_density ); + MESSAGE_END(); + + if ( m_frequency > 19 ) + pev->nextthink = gpGlobals->time + 0.5; + else + pev->nextthink = gpGlobals->time + 2.5 - (0.1 * m_frequency); +} + +// -------------------------------------------------- +// +// Beams +// +// -------------------------------------------------- + +LINK_ENTITY_TO_CLASS( beam, CBeam ); + +void CBeam::Spawn( void ) +{ + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); +} + +void CBeam::Precache( void ) +{ + if ( pev->owner ) + SetStartEntity( ENTINDEX( pev->owner ) ); + if ( pev->aiment ) + SetEndEntity( ENTINDEX( pev->aiment ) ); +} + +void CBeam::SetStartEntity( int entityIndex ) +{ + pev->sequence = (entityIndex & 0x0FFF) | ((pev->sequence&0xF000)<<12); + pev->owner = g_engfuncs.pfnPEntityOfEntIndex( entityIndex ); +} + +void CBeam::SetEndEntity( int entityIndex ) +{ + pev->skin = (entityIndex & 0x0FFF) | ((pev->skin&0xF000)<<12); + pev->aiment = g_engfuncs.pfnPEntityOfEntIndex( entityIndex ); +} + + +// These don't take attachments into account +const Vector &CBeam::GetStartPos( void ) +{ + if ( GetType() == BEAM_ENTS ) + { + edict_t *pent = g_engfuncs.pfnPEntityOfEntIndex( GetStartEntity() ); + return pent->v.origin; + } + return pev->origin; +} + + +const Vector &CBeam::GetEndPos( void ) +{ + int type = GetType(); + if ( type == BEAM_POINTS || type == BEAM_HOSE ) + { + return pev->angles; + } + + edict_t *pent = g_engfuncs.pfnPEntityOfEntIndex( GetEndEntity() ); + if ( pent ) + return pent->v.origin; + return pev->angles; +} + + +CBeam *CBeam::BeamCreate( const char *pSpriteName, int width ) +{ + // Create a new entity with CBeam private data + CBeam *pBeam = GetClassPtr( (CBeam *)NULL ); + pBeam->pev->classname = MAKE_STRING("beam"); + + pBeam->BeamInit( pSpriteName, width ); + + return pBeam; +} + + +void CBeam::BeamInit( const char *pSpriteName, int width ) +{ + pev->flags |= FL_CUSTOMENTITY; + SetColor( 255, 255, 255 ); + SetBrightness( 255 ); + SetNoise( 0 ); + SetFrame( 0 ); + SetScrollRate( 0 ); + pev->model = MAKE_STRING( pSpriteName ); + SetTexture( PRECACHE_MODEL( (char *)pSpriteName ) ); + SetWidth( width ); + pev->skin = 0; + pev->sequence = 0; + pev->rendermode = 0; +} + + +void CBeam::PointsInit( const Vector &start, const Vector &end ) +{ + SetType( BEAM_POINTS ); + SetStartPos( start ); + SetEndPos( end ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CBeam::HoseInit( const Vector &start, const Vector &direction ) +{ + SetType( BEAM_HOSE ); + SetStartPos( start ); + SetEndPos( direction ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CBeam::PointEntInit( const Vector &start, int endIndex ) +{ + SetType( BEAM_ENTPOINT ); + SetStartPos( start ); + SetEndEntity( endIndex ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + +void CBeam::EntsInit( int startIndex, int endIndex ) +{ + SetType( BEAM_ENTS ); + SetStartEntity( startIndex ); + SetEndEntity( endIndex ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CBeam::RelinkBeam( void ) +{ + const Vector &startPos = GetStartPos(), &endPos = GetEndPos(); + + pev->mins.x = min( startPos.x, endPos.x ); + pev->mins.y = min( startPos.y, endPos.y ); + pev->mins.z = min( startPos.z, endPos.z ); + pev->maxs.x = max( startPos.x, endPos.x ); + pev->maxs.y = max( startPos.y, endPos.y ); + pev->maxs.z = max( startPos.z, endPos.z ); + pev->mins = pev->mins - pev->origin; + pev->maxs = pev->maxs - pev->origin; + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); +} + +#if 0 +void CBeam::SetObjectCollisionBox( void ) +{ + const Vector &startPos = GetStartPos(), &endPos = GetEndPos(); + + pev->absmin.x = min( startPos.x, endPos.x ); + pev->absmin.y = min( startPos.y, endPos.y ); + pev->absmin.z = min( startPos.z, endPos.z ); + pev->absmax.x = max( startPos.x, endPos.x ); + pev->absmax.y = max( startPos.y, endPos.y ); + pev->absmax.z = max( startPos.z, endPos.z ); +} +#endif + + +void CBeam::TriggerTouch( CBaseEntity *pOther ) +{ + if ( pOther->pev->flags & (FL_CLIENT | FL_MONSTER) ) + { + if ( pev->owner ) + { + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + pOwner->Use( pOther, this, USE_TOGGLE, 0 ); + } + ALERT( at_console, "Firing targets!!!\n" ); + } +} + + +CBaseEntity *CBeam::RandomTargetname( const char *szName ) +{ + int total = 0; + + CBaseEntity *pEntity = NULL; + CBaseEntity *pNewEntity = NULL; + while ((pNewEntity = UTIL_FindEntityByTargetname( pNewEntity, szName )) != NULL) + { + total++; + if (RANDOM_LONG(0,total-1) < 1) + pEntity = pNewEntity; + } + return pEntity; +} + + +void CBeam::DoSparks( const Vector &start, const Vector &end ) +{ + if ( pev->spawnflags & (SF_BEAM_SPARKSTART|SF_BEAM_SPARKEND) ) + { + if ( pev->spawnflags & SF_BEAM_SPARKSTART ) + { + UTIL_Sparks( start ); + } + if ( pev->spawnflags & SF_BEAM_SPARKEND ) + { + UTIL_Sparks( end ); + } + } +} + + +class CLightning : public CBeam +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void Activate( void ); + + void EXPORT StrikeThink( void ); + void EXPORT DamageThink( void ); + void RandomArea( void ); + void RandomPoint( Vector &vecSrc ); + void Zap( const Vector &vecSrc, const Vector &vecDest ); + void EXPORT StrikeUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + inline BOOL ServerSide( void ) + { + if ( m_life == 0 && !(pev->spawnflags & SF_BEAM_RING) ) + return TRUE; + return FALSE; + } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void BeamUpdateVars( void ); + + int m_active; + int m_iszStartEntity; + int m_iszEndEntity; + float m_life; + int m_boltWidth; + int m_noiseAmplitude; + int m_brightness; + int m_speed; + float m_restrike; + int m_spriteTexture; + int m_iszSpriteName; + int m_frameStart; + + float m_radius; +}; + +LINK_ENTITY_TO_CLASS( env_lightning, CLightning ); +LINK_ENTITY_TO_CLASS( env_beam, CLightning ); + +// UNDONE: Jay -- This is only a test +#if _DEBUG +class CTripBeam : public CLightning +{ + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( trip_beam, CTripBeam ); + +void CTripBeam::Spawn( void ) +{ + CLightning::Spawn(); + SetTouch( TriggerTouch ); + pev->solid = SOLID_TRIGGER; + RelinkBeam(); +} +#endif + + + +TYPEDESCRIPTION CLightning::m_SaveData[] = +{ + DEFINE_FIELD( CLightning, m_active, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_iszStartEntity, FIELD_STRING ), + DEFINE_FIELD( CLightning, m_iszEndEntity, FIELD_STRING ), + DEFINE_FIELD( CLightning, m_life, FIELD_FLOAT ), + DEFINE_FIELD( CLightning, m_boltWidth, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_noiseAmplitude, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_brightness, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_speed, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_restrike, FIELD_FLOAT ), + DEFINE_FIELD( CLightning, m_spriteTexture, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_iszSpriteName, FIELD_STRING ), + DEFINE_FIELD( CLightning, m_frameStart, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_radius, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CLightning, CBeam ); + + +void CLightning::Spawn( void ) +{ + if ( FStringNull( m_iszSpriteName ) ) + { + SetThink( &CLightning::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); + + pev->dmgtime = gpGlobals->time; + + if ( ServerSide() ) + { + SetThink( NULL ); + if ( pev->dmg > 0 ) + { + SetThink( &CLightning::DamageThink ); + pev->nextthink = gpGlobals->time + 0.1; + } + if ( pev->targetname ) + { + if ( !(pev->spawnflags & SF_BEAM_STARTON) ) + { + pev->effects = EF_NODRAW; + m_active = 0; + pev->nextthink = 0; + } + else + m_active = 1; + + SetUse( &CLightning::ToggleUse ); + } + } + else + { + m_active = 0; + if ( !FStringNull(pev->targetname) ) + { + SetUse( &CLightning::StrikeUse ); + } + if ( FStringNull(pev->targetname) || FBitSet(pev->spawnflags, SF_BEAM_STARTON) ) + { + SetThink( &CLightning::StrikeThink ); + pev->nextthink = gpGlobals->time + 1.0; + } + } +} + +void CLightning::Precache( void ) +{ + m_spriteTexture = PRECACHE_MODEL( (char *)STRING(m_iszSpriteName) ); + CBeam::Precache(); +} + + +void CLightning::Activate( void ) +{ + if ( ServerSide() ) + BeamUpdateVars(); +} + + +void CLightning::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "LightningStart")) + { + m_iszStartEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "LightningEnd")) + { + m_iszEndEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "life")) + { + m_life = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "BoltWidth")) + { + m_boltWidth = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "NoiseAmplitude")) + { + m_noiseAmplitude = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "TextureScroll")) + { + m_speed = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "StrikeTime")) + { + m_restrike = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "texture")) + { + m_iszSpriteName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "framestart")) + { + m_frameStart = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "Radius")) + { + m_radius = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damage")) + { + pev->dmg = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBeam::KeyValue( pkvd ); +} + + +void CLightning::ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_active ) ) + return; + if ( m_active ) + { + m_active = 0; + pev->effects |= EF_NODRAW; + pev->nextthink = 0; + } + else + { + m_active = 1; + pev->effects &= ~EF_NODRAW; + DoSparks( GetStartPos(), GetEndPos() ); + if ( pev->dmg > 0 ) + { + pev->nextthink = gpGlobals->time; + pev->dmgtime = gpGlobals->time; + } + } +} + + +void CLightning::StrikeUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_active ) ) + return; + + if ( m_active ) + { + m_active = 0; + SetThink( NULL ); + } + else + { + SetThink( &CLightning::StrikeThink ); + pev->nextthink = gpGlobals->time + 0.1; + } + + if ( !FBitSet( pev->spawnflags, SF_BEAM_TOGGLE ) ) + SetUse( NULL ); +} + + +int IsPointEntity( CBaseEntity *pEnt ) +{ + if ( !pEnt->pev->modelindex ) + return 1; + if ( FClassnameIs( pEnt->pev, "info_target" ) || FClassnameIs( pEnt->pev, "info_landmark" ) || FClassnameIs( pEnt->pev, "path_corner" ) ) + return 1; + + return 0; +} + + +void CLightning::StrikeThink( void ) +{ + if ( m_life != 0 ) + { + if ( pev->spawnflags & SF_BEAM_RANDOM ) + pev->nextthink = gpGlobals->time + m_life + RANDOM_FLOAT( 0, m_restrike ); + else + pev->nextthink = gpGlobals->time + m_life + m_restrike; + } + m_active = 1; + + if (FStringNull(m_iszEndEntity)) + { + if (FStringNull(m_iszStartEntity)) + { + RandomArea( ); + } + else + { + CBaseEntity *pStart = RandomTargetname( STRING(m_iszStartEntity) ); + if (pStart != NULL) + RandomPoint( pStart->pev->origin ); + else + ALERT( at_console, "env_beam: unknown entity \"%s\"\n", STRING(m_iszStartEntity) ); + } + return; + } + + CBaseEntity *pStart = RandomTargetname( STRING(m_iszStartEntity) ); + CBaseEntity *pEnd = RandomTargetname( STRING(m_iszEndEntity) ); + + if ( pStart != NULL && pEnd != NULL ) + { + if ( IsPointEntity( pStart ) || IsPointEntity( pEnd ) ) + { + if ( pev->spawnflags & SF_BEAM_RING) + { + // don't work + return; + } + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + if ( IsPointEntity( pStart ) || IsPointEntity( pEnd ) ) + { + if ( !IsPointEntity( pEnd ) ) // One point entity must be in pEnd + { + CBaseEntity *pTemp; + pTemp = pStart; + pStart = pEnd; + pEnd = pTemp; + } + if ( !IsPointEntity( pStart ) ) // One sided + { + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( pStart->entindex() ); + WRITE_COORD( pEnd->pev->origin.x); + WRITE_COORD( pEnd->pev->origin.y); + WRITE_COORD( pEnd->pev->origin.z); + } + else + { + WRITE_BYTE( TE_BEAMPOINTS); + WRITE_COORD( pStart->pev->origin.x); + WRITE_COORD( pStart->pev->origin.y); + WRITE_COORD( pStart->pev->origin.z); + WRITE_COORD( pEnd->pev->origin.x); + WRITE_COORD( pEnd->pev->origin.y); + WRITE_COORD( pEnd->pev->origin.z); + } + + + } + else + { + if ( pev->spawnflags & SF_BEAM_RING) + WRITE_BYTE( TE_BEAMRING ); + else + WRITE_BYTE( TE_BEAMENTS ); + WRITE_SHORT( pStart->entindex() ); + WRITE_SHORT( pEnd->entindex() ); + } + + WRITE_SHORT( m_spriteTexture ); + WRITE_BYTE( m_frameStart ); // framestart + WRITE_BYTE( (int)pev->framerate); // framerate + WRITE_BYTE( (int)(m_life*10.0) ); // life + WRITE_BYTE( m_boltWidth ); // width + WRITE_BYTE( m_noiseAmplitude ); // noise + WRITE_BYTE( (int)pev->rendercolor.x ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.y ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.z ); // r, g, b + WRITE_BYTE( pev->renderamt ); // brightness + WRITE_BYTE( m_speed ); // speed + MESSAGE_END(); + DoSparks( pStart->pev->origin, pEnd->pev->origin ); + if ( pev->dmg > 0 ) + { + TraceResult tr; + UTIL_TraceLine( pStart->pev->origin, pEnd->pev->origin, dont_ignore_monsters, NULL, &tr ); + BeamDamageInstant( &tr, pev->dmg ); + } + } +} + + +void CBeam::BeamDamage( TraceResult *ptr ) +{ + RelinkBeam(); + if ( ptr->flFraction != 1.0 && ptr->pHit != NULL ) + { + CBaseEntity *pHit = CBaseEntity::Instance(ptr->pHit); + if ( pHit ) + { + ClearMultiDamage(); + pHit->TraceAttack( pev, pev->dmg * (gpGlobals->time - pev->dmgtime), (ptr->vecEndPos - pev->origin).Normalize(), ptr, DMG_ENERGYBEAM ); + ApplyMultiDamage( pev, pev ); + if ( pev->spawnflags & SF_BEAM_DECALS ) + { + if ( pHit->IsBSPModel() ) + UTIL_DecalTrace( ptr, DECAL_BIGSHOT1 + RANDOM_LONG(0,4) ); + } + } + } + pev->dmgtime = gpGlobals->time; +} + + +void CLightning::DamageThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + TraceResult tr; + UTIL_TraceLine( GetStartPos(), GetEndPos(), dont_ignore_monsters, NULL, &tr ); + BeamDamage( &tr ); +} + + + +void CLightning::Zap( const Vector &vecSrc, const Vector &vecDest ) +{ +#if 1 + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS); + WRITE_COORD(vecSrc.x); + WRITE_COORD(vecSrc.y); + WRITE_COORD(vecSrc.z); + WRITE_COORD(vecDest.x); + WRITE_COORD(vecDest.y); + WRITE_COORD(vecDest.z); + WRITE_SHORT( m_spriteTexture ); + WRITE_BYTE( m_frameStart ); // framestart + WRITE_BYTE( (int)pev->framerate); // framerate + WRITE_BYTE( (int)(m_life*10.0) ); // life + WRITE_BYTE( m_boltWidth ); // width + WRITE_BYTE( m_noiseAmplitude ); // noise + WRITE_BYTE( (int)pev->rendercolor.x ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.y ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.z ); // r, g, b + WRITE_BYTE( pev->renderamt ); // brightness + WRITE_BYTE( m_speed ); // speed + MESSAGE_END(); +#else + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE(TE_LIGHTNING); + WRITE_COORD(vecSrc.x); + WRITE_COORD(vecSrc.y); + WRITE_COORD(vecSrc.z); + WRITE_COORD(vecDest.x); + WRITE_COORD(vecDest.y); + WRITE_COORD(vecDest.z); + WRITE_BYTE(10); + WRITE_BYTE(50); + WRITE_BYTE(40); + WRITE_SHORT(m_spriteTexture); + MESSAGE_END(); +#endif + DoSparks( vecSrc, vecDest ); +} + +void CLightning::RandomArea( void ) +{ + int iLoops = 0; + + for (iLoops = 0; iLoops < 10; iLoops++) + { + Vector vecSrc = pev->origin; + + Vector vecDir1 = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + vecDir1 = vecDir1.Normalize(); + TraceResult tr1; + UTIL_TraceLine( vecSrc, vecSrc + vecDir1 * m_radius, ignore_monsters, ENT(pev), &tr1 ); + + if (tr1.flFraction == 1.0) + continue; + + Vector vecDir2; + do { + vecDir2 = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + } while (DotProduct(vecDir1, vecDir2 ) > 0); + vecDir2 = vecDir2.Normalize(); + TraceResult tr2; + UTIL_TraceLine( vecSrc, vecSrc + vecDir2 * m_radius, ignore_monsters, ENT(pev), &tr2 ); + + if (tr2.flFraction == 1.0) + continue; + + if ((tr1.vecEndPos - tr2.vecEndPos).Length() < m_radius * 0.1) + continue; + + UTIL_TraceLine( tr1.vecEndPos, tr2.vecEndPos, ignore_monsters, ENT(pev), &tr2 ); + + if (tr2.flFraction != 1.0) + continue; + + Zap( tr1.vecEndPos, tr2.vecEndPos ); + + break; + } +} + + +void CLightning::RandomPoint( Vector &vecSrc ) +{ + int iLoops = 0; + + for (iLoops = 0; iLoops < 10; iLoops++) + { + Vector vecDir1 = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + vecDir1 = vecDir1.Normalize(); + TraceResult tr1; + UTIL_TraceLine( vecSrc, vecSrc + vecDir1 * m_radius, ignore_monsters, ENT(pev), &tr1 ); + + if ((tr1.vecEndPos - vecSrc).Length() < m_radius * 0.1) + continue; + + if (tr1.flFraction == 1.0) + continue; + + Zap( vecSrc, tr1.vecEndPos ); + break; + } +} + + + +void CLightning::BeamUpdateVars( void ) +{ + int beamType; + int pointStart, pointEnd; + + edict_t *pStart = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(m_iszStartEntity) ); + edict_t *pEnd = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(m_iszEndEntity) ); + pointStart = IsPointEntity( CBaseEntity::Instance(pStart) ); + pointEnd = IsPointEntity( CBaseEntity::Instance(pEnd) ); + + pev->skin = 0; + pev->sequence = 0; + pev->rendermode = 0; + pev->flags |= FL_CUSTOMENTITY; + pev->model = m_iszSpriteName; + SetTexture( m_spriteTexture ); + + beamType = BEAM_ENTS; + if ( pointStart || pointEnd ) + { + if ( !pointStart ) // One point entity must be in pStart + { + edict_t *pTemp; + // Swap start & end + pTemp = pStart; + pStart = pEnd; + pEnd = pTemp; + int swap = pointStart; + pointStart = pointEnd; + pointEnd = swap; + } + if ( !pointEnd ) + beamType = BEAM_ENTPOINT; + else + beamType = BEAM_POINTS; + } + + SetType( beamType ); + if ( beamType == BEAM_POINTS || beamType == BEAM_ENTPOINT || beamType == BEAM_HOSE ) + { + SetStartPos( pStart->v.origin ); + if ( beamType == BEAM_POINTS || beamType == BEAM_HOSE ) + SetEndPos( pEnd->v.origin ); + else + SetEndEntity( ENTINDEX(pEnd) ); + } + else + { + SetStartEntity( ENTINDEX(pStart) ); + SetEndEntity( ENTINDEX(pEnd) ); + } + + RelinkBeam(); + + SetWidth( m_boltWidth ); + SetNoise( m_noiseAmplitude ); + SetFrame( m_frameStart ); + SetScrollRate( m_speed ); + if ( pev->spawnflags & SF_BEAM_SHADEIN ) + SetFlags( BEAM_FSHADEIN ); + else if ( pev->spawnflags & SF_BEAM_SHADEOUT ) + SetFlags( BEAM_FSHADEOUT ); +} + + +LINK_ENTITY_TO_CLASS( env_laser, CLaser ); + +TYPEDESCRIPTION CLaser::m_SaveData[] = +{ + DEFINE_FIELD( CLaser, m_pSprite, FIELD_CLASSPTR ), + DEFINE_FIELD( CLaser, m_iszSpriteName, FIELD_STRING ), + DEFINE_FIELD( CLaser, m_firePosition, FIELD_POSITION_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CLaser, CBeam ); + +void CLaser::Spawn( void ) +{ + if ( FStringNull( pev->model ) ) + { + SetThink( &CLaser::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); + + SetThink( &CLaser::StrikeThink ); + pev->flags |= FL_CUSTOMENTITY; + + PointsInit( pev->origin, pev->origin ); + + if ( !m_pSprite && m_iszSpriteName ) + m_pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteName), pev->origin, TRUE ); + else + m_pSprite = NULL; + + if ( m_pSprite ) + m_pSprite->SetTransparency( kRenderGlow, pev->rendercolor.x, pev->rendercolor.y, pev->rendercolor.z, pev->renderamt, pev->renderfx ); + + if ( pev->targetname && !(pev->spawnflags & SF_BEAM_STARTON) ) + TurnOff(); + else + TurnOn(); +} + +void CLaser::Precache( void ) +{ + pev->modelindex = PRECACHE_MODEL( (char *)STRING(pev->model) ); + if ( m_iszSpriteName ) + PRECACHE_MODEL( (char *)STRING(m_iszSpriteName) ); +} + + +void CLaser::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "LaserTarget")) + { + pev->message = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "width")) + { + SetWidth( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "NoiseAmplitude")) + { + SetNoise( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "TextureScroll")) + { + SetScrollRate( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "texture")) + { + pev->model = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "EndSprite")) + { + m_iszSpriteName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "framestart")) + { + pev->frame = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damage")) + { + pev->dmg = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBeam::KeyValue( pkvd ); +} + + +int CLaser::IsOn( void ) +{ + if (pev->effects & EF_NODRAW) + return 0; + return 1; +} + + +void CLaser::TurnOff( void ) +{ + pev->effects |= EF_NODRAW; + pev->nextthink = 0; + if ( m_pSprite ) + m_pSprite->TurnOff(); +} + + +void CLaser::TurnOn( void ) +{ + pev->effects &= ~EF_NODRAW; + if ( m_pSprite ) + m_pSprite->TurnOn(); + pev->dmgtime = gpGlobals->time; + pev->nextthink = gpGlobals->time; +} + + +void CLaser::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int active = IsOn(); + + if ( !ShouldToggle( useType, active ) ) + return; + if ( active ) + { + TurnOff(); + } + else + { + TurnOn(); + } +} + + +void CLaser::FireAtPoint( TraceResult &tr ) +{ + SetEndPos( tr.vecEndPos ); + if ( m_pSprite ) + UTIL_SetOrigin( m_pSprite->pev, tr.vecEndPos ); + + BeamDamage( &tr ); + DoSparks( GetStartPos(), tr.vecEndPos ); +} + +void CLaser::StrikeThink( void ) +{ + CBaseEntity *pEnd = RandomTargetname( STRING(pev->message) ); + + if ( pEnd ) + m_firePosition = pEnd->pev->origin; + + TraceResult tr; + + UTIL_TraceLine( pev->origin, m_firePosition, dont_ignore_monsters, NULL, &tr ); + FireAtPoint( tr ); + pev->nextthink = gpGlobals->time + 0.1; +} + + + +class CGlow : public CPointEntity +{ +public: + void Spawn( void ); + void Think( void ); + void Animate( float frames ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + float m_lastTime; + float m_maxFrame; +}; + +LINK_ENTITY_TO_CLASS( env_glow, CGlow ); + +TYPEDESCRIPTION CGlow::m_SaveData[] = +{ + DEFINE_FIELD( CGlow, m_lastTime, FIELD_TIME ), + DEFINE_FIELD( CGlow, m_maxFrame, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CGlow, CPointEntity ); + +void CGlow::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; + if ( m_maxFrame > 1.0 && pev->framerate != 0 ) + pev->nextthink = gpGlobals->time + 0.1; + + m_lastTime = gpGlobals->time; +} + + +void CGlow::Think( void ) +{ + Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); + + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; +} + + +void CGlow::Animate( float frames ) +{ + if ( m_maxFrame > 0 ) + pev->frame = fmod( pev->frame + frames, m_maxFrame ); +} + + +LINK_ENTITY_TO_CLASS( env_sprite, CSprite ); + +TYPEDESCRIPTION CSprite::m_SaveData[] = +{ + DEFINE_FIELD( CSprite, m_lastTime, FIELD_TIME ), + DEFINE_FIELD( CSprite, m_maxFrame, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CSprite, CPointEntity ); + +void CSprite::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + + Precache(); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; + if ( pev->targetname && !(pev->spawnflags & SF_SPRITE_STARTON) ) + TurnOff(); + else + TurnOn(); + + // Worldcraft only sets y rotation, copy to Z + if ( pev->angles.y != 0 && pev->angles.z == 0 ) + { + pev->angles.z = pev->angles.y; + pev->angles.y = 0; + } +} + + +void CSprite::Precache( void ) +{ + PRECACHE_MODEL( (char *)STRING(pev->model) ); + + // Reset attachment after save/restore + if ( pev->aiment ) + SetAttachment( pev->aiment, pev->body ); + else + { + // Clear attachment + pev->skin = 0; + pev->body = 0; + } +} + + +void CSprite::SpriteInit( const char *pSpriteName, const Vector &origin ) +{ + pev->model = MAKE_STRING(pSpriteName); + pev->origin = origin; + Spawn(); +} + +CSprite *CSprite::SpriteCreate( const char *pSpriteName, const Vector &origin, BOOL animate ) +{ + CSprite *pSprite = GetClassPtr( (CSprite *)NULL ); + pSprite->SpriteInit( pSpriteName, origin ); + pSprite->pev->classname = MAKE_STRING("env_sprite"); + pSprite->pev->solid = SOLID_NOT; + pSprite->pev->movetype = MOVETYPE_NOCLIP; + if ( animate ) + pSprite->TurnOn(); + + return pSprite; +} + + +void CSprite::AnimateThink( void ) +{ + Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); + + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; +} + +void CSprite::AnimateUntilDead( void ) +{ + if ( gpGlobals->time > pev->dmgtime ) + UTIL_Remove(this); + else + { + AnimateThink(); + pev->nextthink = gpGlobals->time; + } +} + +void CSprite::Expand( float scaleSpeed, float fadeSpeed ) +{ + pev->speed = scaleSpeed; + pev->health = fadeSpeed; + SetThink( &CSprite::ExpandThink ); + + pev->nextthink = gpGlobals->time; + m_lastTime = gpGlobals->time; +} + + +void CSprite::ExpandThink( void ) +{ + float frametime = gpGlobals->time - m_lastTime; + pev->scale += pev->speed * frametime; + pev->renderamt -= pev->health * frametime; + if ( pev->renderamt <= 0 ) + { + pev->renderamt = 0; + UTIL_Remove( this ); + } + else + { + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; + } +} + + +void CSprite::Animate( float frames ) +{ + pev->frame += frames; + if ( pev->frame > m_maxFrame ) + { + if ( pev->spawnflags & SF_SPRITE_ONCE ) + { + TurnOff(); + } + else + { + if ( m_maxFrame > 0 ) + pev->frame = fmod( pev->frame, m_maxFrame ); + } + } +} + + +void CSprite::TurnOff( void ) +{ + pev->effects = EF_NODRAW; + pev->nextthink = 0; +} + + +void CSprite::TurnOn( void ) +{ + pev->effects = 0; + if ( (pev->framerate && m_maxFrame > 1.0) || (pev->spawnflags & SF_SPRITE_ONCE) ) + { + SetThink( &CSprite::AnimateThink ); + pev->nextthink = gpGlobals->time; + m_lastTime = gpGlobals->time; + } + pev->frame = 0; +} + + +void CSprite::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int on = pev->effects != EF_NODRAW; + if ( ShouldToggle( useType, on ) ) + { + if ( on ) + { + TurnOff(); + } + else + { + TurnOn(); + } + } +} + + +class CGibShooter : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT ShootThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual CGib *CreateGib( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iGibs; + int m_iGibCapacity; + int m_iGibMaterial; + int m_iGibModelIndex; + float m_flGibVelocity; + float m_flVariance; + float m_flGibLife; +}; + +TYPEDESCRIPTION CGibShooter::m_SaveData[] = +{ + DEFINE_FIELD( CGibShooter, m_iGibs, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_iGibCapacity, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_iGibMaterial, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_iGibModelIndex, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_flGibVelocity, FIELD_FLOAT ), + DEFINE_FIELD( CGibShooter, m_flVariance, FIELD_FLOAT ), + DEFINE_FIELD( CGibShooter, m_flGibLife, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CGibShooter, CBaseDelay ); +LINK_ENTITY_TO_CLASS( gibshooter, CGibShooter ); + + +void CGibShooter :: Precache ( void ) +{ + if ( g_Language == LANGUAGE_GERMAN ) + { + m_iGibModelIndex = PRECACHE_MODEL ("models/germanygibs.mdl"); + } + else + { + m_iGibModelIndex = PRECACHE_MODEL ("models/hgibs.mdl"); + } +} + + +void CGibShooter::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iGibs")) + { + m_iGibs = m_iGibCapacity = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flVelocity")) + { + m_flGibVelocity = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flVariance")) + { + m_flVariance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flGibLife")) + { + m_flGibLife = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + { + CBaseDelay::KeyValue( pkvd ); + } +} + +void CGibShooter::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( &CGibShooter::ShootThink ); + pev->nextthink = gpGlobals->time; +} + +void CGibShooter::Spawn( void ) +{ + Precache(); + + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + + if ( m_flDelay == 0 ) + { + m_flDelay = 0.1; + } + + if ( m_flGibLife == 0 ) + { + m_flGibLife = 25; + } + + SetMovedir ( pev ); + pev->body = MODEL_FRAMES( m_iGibModelIndex ); +} + + +CGib *CGibShooter :: CreateGib ( void ) +{ + if ( CVAR_GET_FLOAT("violence_hgibs") == 0 ) + return NULL; + + CGib *pGib = GetClassPtr( (CGib *)NULL ); + pGib->Spawn( "models/hgibs.mdl" ); + pGib->m_bloodColor = BLOOD_COLOR_RED; + + if ( pev->body <= 1 ) + { + ALERT ( at_aiconsole, "GibShooter Body is <= 1!\n" ); + } + + pGib->pev->body = RANDOM_LONG ( 1, pev->body - 1 );// avoid throwing random amounts of the 0th gib. (skull). + + return pGib; +} + + +void CGibShooter :: ShootThink ( void ) +{ + pev->nextthink = gpGlobals->time + m_flDelay; + + Vector vecShootDir; + + vecShootDir = pev->movedir; + + vecShootDir = vecShootDir + gpGlobals->v_right * RANDOM_FLOAT( -1, 1) * m_flVariance;; + vecShootDir = vecShootDir + gpGlobals->v_forward * RANDOM_FLOAT( -1, 1) * m_flVariance;; + vecShootDir = vecShootDir + gpGlobals->v_up * RANDOM_FLOAT( -1, 1) * m_flVariance;; + + vecShootDir = vecShootDir.Normalize(); + CGib *pGib = CreateGib(); + + if ( pGib ) + { + pGib->pev->origin = pev->origin; + pGib->pev->velocity = vecShootDir * m_flGibVelocity; + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + float thinkTime = pGib->pev->nextthink - gpGlobals->time; + + pGib->m_lifeTime = (m_flGibLife * RANDOM_FLOAT( 0.95, 1.05 )); // +/- 5% + if ( pGib->m_lifeTime < thinkTime ) + { + pGib->pev->nextthink = gpGlobals->time + pGib->m_lifeTime; + pGib->m_lifeTime = 0; + } + + } + + if ( --m_iGibs <= 0 ) + { + if ( pev->spawnflags & SF_GIBSHOOTER_REPEATABLE ) + { + m_iGibs = m_iGibCapacity; + SetThink ( NULL ); + pev->nextthink = gpGlobals->time; + } + else + { + SetThink ( &CGibShooter::SUB_Remove ); + pev->nextthink = gpGlobals->time; + } + } +} + + +class CEnvShooter : public CGibShooter +{ + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + CGib *CreateGib( void ); +}; + +LINK_ENTITY_TO_CLASS( env_shooter, CEnvShooter ); + +void CEnvShooter :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "shootmodel")) + { + pev->model = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "shootsounds")) + { + int iNoise = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + switch( iNoise ) + { + case 0: + m_iGibMaterial = matGlass; + break; + case 1: + m_iGibMaterial = matWood; + break; + case 2: + m_iGibMaterial = matMetal; + break; + case 3: + m_iGibMaterial = matFlesh; + break; + case 4: + m_iGibMaterial = matRocks; + break; + + default: + case -1: + m_iGibMaterial = matNone; + break; + } + } + else + { + CGibShooter::KeyValue( pkvd ); + } +} + + +void CEnvShooter :: Precache ( void ) +{ + m_iGibModelIndex = PRECACHE_MODEL( (char *)STRING(pev->model) ); + CBreakable::MaterialSoundPrecache( (Materials)m_iGibMaterial ); +} + + +CGib *CEnvShooter :: CreateGib ( void ) +{ + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + pGib->Spawn( STRING(pev->model) ); + + int bodyPart = 0; + + if ( pev->body > 1 ) + bodyPart = RANDOM_LONG( 0, pev->body-1 ); + + pGib->pev->body = bodyPart; + pGib->m_bloodColor = DONT_BLEED; + pGib->m_material = m_iGibMaterial; + + pGib->pev->rendermode = pev->rendermode; + pGib->pev->renderamt = pev->renderamt; + pGib->pev->rendercolor = pev->rendercolor; + pGib->pev->renderfx = pev->renderfx; + pGib->pev->scale = pev->scale; + pGib->pev->skin = pev->skin; + + return pGib; +} + + + + +class CTestEffect : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + // void KeyValue( KeyValueData *pkvd ); + void EXPORT TestThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int m_iLoop; + int m_iBeam; + CBeam *m_pBeam[24]; + float m_flBeamTime[24]; + float m_flStartTime; +}; + + +LINK_ENTITY_TO_CLASS( test_effect, CTestEffect ); + +void CTestEffect::Spawn( void ) +{ + Precache( ); +} + +void CTestEffect::Precache( void ) +{ + PRECACHE_MODEL( "sprites/lgtning.spr" ); +} + +void CTestEffect::TestThink( void ) +{ + int i; + float t = (gpGlobals->time - m_flStartTime); + + if (m_iBeam < 24) + { + CBeam *pbeam = CBeam::BeamCreate( "sprites/lgtning.spr", 100 ); + + TraceResult tr; + + Vector vecSrc = pev->origin; + Vector vecDir = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + vecDir = vecDir.Normalize(); + UTIL_TraceLine( vecSrc, vecSrc + vecDir * 128, ignore_monsters, ENT(pev), &tr); + + pbeam->PointsInit( vecSrc, tr.vecEndPos ); + // pbeam->SetColor( 80, 100, 255 ); + pbeam->SetColor( 255, 180, 100 ); + pbeam->SetWidth( 100 ); + pbeam->SetScrollRate( 12 ); + + m_flBeamTime[m_iBeam] = gpGlobals->time; + m_pBeam[m_iBeam] = pbeam; + m_iBeam++; + +#if 0 + Vector vecMid = (vecSrc + tr.vecEndPos) * 0.5; + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE(TE_DLIGHT); + WRITE_COORD(vecMid.x); // X + WRITE_COORD(vecMid.y); // Y + WRITE_COORD(vecMid.z); // Z + WRITE_BYTE( 20 ); // radius * 0.1 + WRITE_BYTE( 255 ); // r + WRITE_BYTE( 180 ); // g + WRITE_BYTE( 100 ); // b + WRITE_BYTE( 20 ); // time * 10 + WRITE_BYTE( 0 ); // decay * 0.1 + MESSAGE_END( ); +#endif + } + + if (t < 3.0) + { + for (i = 0; i < m_iBeam; i++) + { + t = (gpGlobals->time - m_flBeamTime[i]) / ( 3 + m_flStartTime - m_flBeamTime[i]); + m_pBeam[i]->SetBrightness( 255 * t ); + // m_pBeam[i]->SetScrollRate( 20 * t ); + } + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + for (i = 0; i < m_iBeam; i++) + { + UTIL_Remove( m_pBeam[i] ); + } + m_flStartTime = gpGlobals->time; + m_iBeam = 0; + // pev->nextthink = gpGlobals->time; + SetThink( NULL ); + } +} + + +void CTestEffect::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( &CTestEffect::TestThink ); + pev->nextthink = gpGlobals->time + 0.1; + m_flStartTime = gpGlobals->time; +} + + + +// Blood effects +class CBlood : public CPointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline int Color( void ) { return pev->impulse; } + inline float BloodAmount( void ) { return pev->dmg; } + + inline void SetColor( int color ) { pev->impulse = color; } + inline void SetBloodAmount( float amount ) { pev->dmg = amount; } + + Vector Direction( void ); + Vector BloodPosition( CBaseEntity *pActivator ); + +private: +}; + +LINK_ENTITY_TO_CLASS( env_blood, CBlood ); + + + +#define SF_BLOOD_RANDOM 0x0001 +#define SF_BLOOD_STREAM 0x0002 +#define SF_BLOOD_PLAYER 0x0004 +#define SF_BLOOD_DECAL 0x0008 + +void CBlood::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + SetMovedir( pev ); +} + + +void CBlood::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "color")) + { + int color = atoi(pkvd->szValue); + switch( color ) + { + case 1: + SetColor( BLOOD_COLOR_YELLOW ); + break; + default: + SetColor( BLOOD_COLOR_RED ); + break; + } + + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "amount")) + { + SetBloodAmount( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +Vector CBlood::Direction( void ) +{ + if ( pev->spawnflags & SF_BLOOD_RANDOM ) + return UTIL_RandomBloodVector(); + + return pev->movedir; +} + + +Vector CBlood::BloodPosition( CBaseEntity *pActivator ) +{ + if ( pev->spawnflags & SF_BLOOD_PLAYER ) + { + edict_t *pPlayer; + + if ( pActivator && pActivator->IsPlayer() ) + { + pPlayer = pActivator->edict(); + } + else + pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + if ( pPlayer ) + return (pPlayer->v.origin + pPlayer->v.view_ofs) + Vector( RANDOM_FLOAT(-10,10), RANDOM_FLOAT(-10,10), RANDOM_FLOAT(-10,10) ); + } + + return pev->origin; +} + + +void CBlood::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->spawnflags & SF_BLOOD_STREAM ) + UTIL_BloodStream( BloodPosition(pActivator), Direction(), Color(), BloodAmount() ); + else + UTIL_BloodDrips( BloodPosition(pActivator), Direction(), Color(), BloodAmount() ); + + if ( pev->spawnflags & SF_BLOOD_DECAL ) + { + Vector forward = Direction(); + Vector start = BloodPosition( pActivator ); + TraceResult tr; + + UTIL_TraceLine( start, start + forward * BloodAmount() * 2, ignore_monsters, NULL, &tr ); + if ( tr.flFraction != 1.0 ) + UTIL_BloodDecalTrace( &tr, Color() ); + } +} + + + +// Screen shake +class CShake : public CPointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline float Amplitude( void ) { return pev->scale; } + inline float Frequency( void ) { return pev->dmg_save; } + inline float Duration( void ) { return pev->dmg_take; } + inline float Radius( void ) { return pev->dmg; } + + inline void SetAmplitude( float amplitude ) { pev->scale = amplitude; } + inline void SetFrequency( float frequency ) { pev->dmg_save = frequency; } + inline void SetDuration( float duration ) { pev->dmg_take = duration; } + inline void SetRadius( float radius ) { pev->dmg = radius; } +private: +}; + +LINK_ENTITY_TO_CLASS( env_shake, CShake ); + +// pev->scale is amplitude +// pev->dmg_save is frequency +// pev->dmg_take is duration +// pev->dmg is radius +// radius of 0 means all players +// NOTE: UTIL_ScreenShake() will only shake players who are on the ground + +#define SF_SHAKE_EVERYONE 0x0001 // Don't check radius +// UNDONE: These don't work yet +#define SF_SHAKE_DISRUPT 0x0002 // Disrupt controls +#define SF_SHAKE_INAIR 0x0004 // Shake players in air + +void CShake::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; + + if ( pev->spawnflags & SF_SHAKE_EVERYONE ) + pev->dmg = 0; +} + + +void CShake::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "amplitude")) + { + SetAmplitude( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "frequency")) + { + SetFrequency( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "duration")) + { + SetDuration( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "radius")) + { + SetRadius( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CShake::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + UTIL_ScreenShake( pev->origin, Amplitude(), Frequency(), Duration(), Radius() ); +} + + +class CFade : public CPointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline float Duration( void ) { return pev->dmg_take; } + inline float HoldTime( void ) { return pev->dmg_save; } + + inline void SetDuration( float duration ) { pev->dmg_take = duration; } + inline void SetHoldTime( float hold ) { pev->dmg_save = hold; } +private: +}; + +LINK_ENTITY_TO_CLASS( env_fade, CFade ); + +// pev->dmg_take is duration +// pev->dmg_save is hold duration +#define SF_FADE_IN 0x0001 // Fade in, not out +#define SF_FADE_MODULATE 0x0002 // Modulate, don't blend +#define SF_FADE_ONLYONE 0x0004 + +void CFade::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = 0; + pev->frame = 0; +} + + +void CFade::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "duration")) + { + SetDuration( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "holdtime")) + { + SetHoldTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CFade::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int fadeFlags = 0; + + if ( !(pev->spawnflags & SF_FADE_IN) ) + fadeFlags |= FFADE_OUT; + + if ( pev->spawnflags & SF_FADE_MODULATE ) + fadeFlags |= FFADE_MODULATE; + + if ( pev->spawnflags & SF_FADE_ONLYONE ) + { + if ( pActivator->IsNetClient() ) + { + UTIL_ScreenFade( pActivator, pev->rendercolor, Duration(), HoldTime(), pev->renderamt, fadeFlags ); + } + } + else + { + UTIL_ScreenFadeAll( pev->rendercolor, Duration(), HoldTime(), pev->renderamt, fadeFlags ); + } + SUB_UseTargets( this, USE_TOGGLE, 0 ); +} + + +class CMessage : public CPointEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); +private: +}; + +LINK_ENTITY_TO_CLASS( env_message, CMessage ); + + +void CMessage::Spawn( void ) +{ + Precache(); + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + switch( pev->impulse ) + { + case 1: // Medium radius + pev->speed = ATTN_STATIC; + break; + + case 2: // Large radius + pev->speed = ATTN_NORM; + break; + + case 3: //EVERYWHERE + pev->speed = ATTN_NONE; + break; + + default: + case 0: // Small radius + pev->speed = ATTN_IDLE; + break; + } + pev->impulse = 0; + + // No volume, use normal + if ( pev->scale <= 0 ) + pev->scale = 1.0; +} + + +void CMessage::Precache( void ) +{ + if ( pev->noise ) + PRECACHE_SOUND( (char *)STRING(pev->noise) ); +} + +void CMessage::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "messagesound")) + { + pev->noise = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "messagevolume")) + { + pev->scale = atof(pkvd->szValue) * 0.1; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "messageattenuation")) + { + pev->impulse = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CMessage::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity *pPlayer = NULL; + + if ( pev->spawnflags & SF_MESSAGE_ALL ) + UTIL_ShowMessageAll( STRING(pev->message) ); + else + { + if ( pActivator && pActivator->IsPlayer() ) + pPlayer = pActivator; + else + { + pPlayer = CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) ); + } + if ( pPlayer ) + UTIL_ShowMessage( STRING(pev->message), pPlayer ); + } + if ( pev->noise ) + { + EMIT_SOUND( edict(), CHAN_BODY, STRING(pev->noise), pev->scale, pev->speed ); + } + if ( pev->spawnflags & SF_MESSAGE_ONCE ) + UTIL_Remove( this ); + + SUB_UseTargets( this, USE_TOGGLE, 0 ); +} + + + +//========================================================= +// FunnelEffect +//========================================================= +class CEnvFunnel : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int m_iSprite; // Don't save, precache +}; + +void CEnvFunnel :: Precache ( void ) +{ + m_iSprite = PRECACHE_MODEL ( "sprites/flare6.spr" ); +} + +LINK_ENTITY_TO_CLASS( env_funnel, CEnvFunnel ); + +void CEnvFunnel::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_LARGEFUNNEL ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( m_iSprite ); + + if ( pev->spawnflags & SF_FUNNEL_REVERSE )// funnel flows in reverse? + { + WRITE_SHORT( 1 ); + } + else + { + WRITE_SHORT( 0 ); + } + + + MESSAGE_END(); + + SetThink( &CEnvFunnel::SUB_Remove ); + pev->nextthink = gpGlobals->time; +} + +void CEnvFunnel::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; +} + +//========================================================= +// Beverage Dispenser +// overloaded pev->frags, is now a flag for whether or not a can is stuck in the dispenser. +// overloaded pev->health, is now how many cans remain in the machine. +//========================================================= +class CEnvBeverage : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; + +void CEnvBeverage :: Precache ( void ) +{ + PRECACHE_MODEL( "models/can.mdl" ); + PRECACHE_SOUND( "weapons/g_bounce3.wav" ); +} + +LINK_ENTITY_TO_CLASS( env_beverage, CEnvBeverage ); + +void CEnvBeverage::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->frags != 0 || pev->health <= 0 ) + { + // no more cans while one is waiting in the dispenser, or if I'm out of cans. + return; + } + + CBaseEntity *pCan = CBaseEntity::Create( "item_sodacan", pev->origin, pev->angles, edict() ); + + if ( pev->skin == 6 ) + { + // random + pCan->pev->skin = RANDOM_LONG( 0, 5 ); + } + else + { + pCan->pev->skin = pev->skin; + } + + pev->frags = 1; + pev->health--; + + //SetThink (SUB_Remove); + //pev->nextthink = gpGlobals->time; +} + +void CEnvBeverage::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + pev->frags = 0; + + if ( pev->health == 0 ) + { + pev->health = 10; + } +} + +//========================================================= +// Soda can +//========================================================= +class CItemSoda : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT CanThink ( void ); + void EXPORT CanTouch ( CBaseEntity *pOther ); +}; + +void CItemSoda :: Precache ( void ) +{ +} + +LINK_ENTITY_TO_CLASS( item_sodacan, CItemSoda ); + +void CItemSoda::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_TOSS; + + SET_MODEL ( ENT(pev), "models/can.mdl" ); + UTIL_SetSize ( pev, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ) ); + + SetThink (&CItemSoda::CanThink); + pev->nextthink = gpGlobals->time + 0.5; +} + +void CItemSoda::CanThink ( void ) +{ + EMIT_SOUND (ENT(pev), CHAN_WEAPON, "weapons/g_bounce3.wav", 1, ATTN_NORM ); + + pev->solid = SOLID_TRIGGER; + UTIL_SetSize ( pev, Vector ( -8, -8, 0 ), Vector ( 8, 8, 8 ) ); + SetThink ( NULL ); + SetTouch ( &CItemSoda::CanTouch ); +} + +void CItemSoda::CanTouch ( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + { + return; + } + + // spoit sound here + + pOther->TakeHealth( 1, DMG_GENERIC );// a bit of health. + + if ( !FNullEnt( pev->owner ) ) + { + // tell the machine the can was taken + pev->owner->v.frags = 0; + } + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = EF_NODRAW; + SetTouch ( NULL ); + SetThink ( &CItemSoda::SUB_Remove ); + pev->nextthink = gpGlobals->time; +} diff --git a/ricochet/dlls/effects.h b/ricochet/dlls/effects.h new file mode 100644 index 0000000..d517268 --- /dev/null +++ b/ricochet/dlls/effects.h @@ -0,0 +1,209 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef EFFECTS_H +#define EFFECTS_H + +#define SF_BEAM_STARTON 0x0001 +#define SF_BEAM_TOGGLE 0x0002 +#define SF_BEAM_RANDOM 0x0004 +#define SF_BEAM_RING 0x0008 +#define SF_BEAM_SPARKSTART 0x0010 +#define SF_BEAM_SPARKEND 0x0020 +#define SF_BEAM_DECALS 0x0040 +#define SF_BEAM_SHADEIN 0x0080 +#define SF_BEAM_SHADEOUT 0x0100 +#define SF_BEAM_TEMPORARY 0x8000 + +#define SF_SPRITE_STARTON 0x0001 +#define SF_SPRITE_ONCE 0x0002 +#define SF_SPRITE_TEMPORARY 0x8000 + +class CSprite : public CPointEntity +{ +public: + void Spawn( void ); + void Precache( void ); + + int ObjectCaps( void ) + { + int flags = 0; + if ( pev->spawnflags & SF_SPRITE_TEMPORARY ) + flags = FCAP_DONT_SAVE; + return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | flags; + } + void EXPORT AnimateThink( void ); + void EXPORT ExpandThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Animate( float frames ); + void Expand( float scaleSpeed, float fadeSpeed ); + void SpriteInit( const char *pSpriteName, const Vector &origin ); + + inline void SetAttachment( edict_t *pEntity, int attachment ) + { + if ( pEntity ) + { + pev->skin = ENTINDEX(pEntity); + pev->body = attachment; + pev->aiment = pEntity; + pev->movetype = MOVETYPE_FOLLOW; + } + } + void TurnOff( void ); + void TurnOn( void ); + inline float Frames( void ) { return m_maxFrame; } + inline void SetTransparency( int rendermode, int r, int g, int b, int a, int fx ) + { + pev->rendermode = rendermode; + pev->rendercolor.x = r; + pev->rendercolor.y = g; + pev->rendercolor.z = b; + pev->renderamt = a; + pev->renderfx = fx; + } + inline void SetTexture( int spriteIndex ) { pev->modelindex = spriteIndex; } + inline void SetScale( float scale ) { pev->scale = scale; } + inline void SetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline void SetBrightness( int brightness ) { pev->renderamt = brightness; } + + inline void AnimateAndDie( float framerate ) + { + SetThink(&CSprite::AnimateUntilDead); + pev->framerate = framerate; + pev->dmgtime = gpGlobals->time + (m_maxFrame / framerate); + pev->nextthink = gpGlobals->time; + } + + void EXPORT AnimateUntilDead( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + static CSprite *SpriteCreate( const char *pSpriteName, const Vector &origin, BOOL animate ); + +private: + + float m_lastTime; + float m_maxFrame; +}; + + +class CBeam : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + int ObjectCaps( void ) + { + int flags = 0; + if ( pev->spawnflags & SF_BEAM_TEMPORARY ) + flags = FCAP_DONT_SAVE; + return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | flags; + } + + void EXPORT TriggerTouch( CBaseEntity *pOther ); + + // These functions are here to show the way beams are encoded as entities. + // Encoding beams as entities simplifies their management in the client/server architecture + inline void SetType( int type ) { pev->rendermode = (pev->rendermode & 0xF0) | (type&0x0F); } + inline void SetFlags( int flags ) { pev->rendermode = (pev->rendermode & 0x0F) | (flags&0xF0); } + inline void SetStartPos( const Vector& pos ) { pev->origin = pos; } + inline void SetEndPos( const Vector& pos ) { pev->angles = pos; } + void SetStartEntity( int entityIndex ); + void SetEndEntity( int entityIndex ); + + inline void SetStartAttachment( int attachment ) { pev->sequence = (pev->sequence & 0x0FFF) | ((attachment&0xF)<<12); } + inline void SetEndAttachment( int attachment ) { pev->skin = (pev->skin & 0x0FFF) | ((attachment&0xF)<<12); } + + inline void SetTexture( int spriteIndex ) { pev->modelindex = spriteIndex; } + inline void SetWidth( int width ) { pev->scale = width; } + inline void SetNoise( int amplitude ) { pev->body = amplitude; } + inline void SetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline void SetBrightness( int brightness ) { pev->renderamt = brightness; } + inline void SetFrame( float frame ) { pev->frame = frame; } + inline void SetScrollRate( int speed ) { pev->animtime = speed; } + + inline int GetType( void ) { return pev->rendermode & 0x0F; } + inline int GetFlags( void ) { return pev->rendermode & 0xF0; } + inline int GetStartEntity( void ) { return pev->sequence & 0xFFF; } + inline int GetEndEntity( void ) { return pev->skin & 0xFFF; } + + const Vector &GetStartPos( void ); + const Vector &GetEndPos( void ); + + Vector Center( void ) { return (GetStartPos() + GetEndPos()) * 0.5; }; // center point of beam + + inline int GetTexture( void ) { return pev->modelindex; } + inline int GetWidth( void ) { return pev->scale; } + inline int GetNoise( void ) { return pev->body; } + // inline void GetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline int GetBrightness( void ) { return pev->renderamt; } + inline int GetFrame( void ) { return pev->frame; } + inline int GetScrollRate( void ) { return pev->animtime; } + + // Call after you change start/end positions + void RelinkBeam( void ); +// void SetObjectCollisionBox( void ); + + void DoSparks( const Vector &start, const Vector &end ); + CBaseEntity *RandomTargetname( const char *szName ); + void BeamDamage( TraceResult *ptr ); + // Init after BeamCreate() + void BeamInit( const char *pSpriteName, int width ); + void PointsInit( const Vector &start, const Vector &end ); + void PointEntInit( const Vector &start, int endIndex ); + void EntsInit( int startIndex, int endIndex ); + void HoseInit( const Vector &start, const Vector &direction ); + + static CBeam *BeamCreate( const char *pSpriteName, int width ); + + inline void LiveForTime( float time ) { SetThink(&CBeam::SUB_Remove); pev->nextthink = gpGlobals->time + time; } + inline void BeamDamageInstant( TraceResult *ptr, float damage ) + { + pev->dmg = damage; + pev->dmgtime = gpGlobals->time - 1; + BeamDamage(ptr); + } +}; + + +#define SF_MESSAGE_ONCE 0x0001 // Fade in, not out +#define SF_MESSAGE_ALL 0x0002 // Send to all clients + + +class CLaser : public CBeam +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + void TurnOn( void ); + void TurnOff( void ); + int IsOn( void ); + + void FireAtPoint( TraceResult &point ); + + void EXPORT StrikeThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + CSprite *m_pSprite; + int m_iszSpriteName; + Vector m_firePosition; +}; + +#endif //EFFECTS_H diff --git a/ricochet/dlls/enginecallback.h b/ricochet/dlls/enginecallback.h new file mode 100644 index 0000000..e960e9d --- /dev/null +++ b/ricochet/dlls/enginecallback.h @@ -0,0 +1,159 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef ENGINECALLBACK_H +#define ENGINECALLBACK_H +#pragma once + +#include "event_flags.h" + +// Must be provided by user of this code +extern enginefuncs_t g_engfuncs; + +// The actual engine callbacks +#define GETPLAYERUSERID (*g_engfuncs.pfnGetPlayerUserId) +#define PRECACHE_MODEL (*g_engfuncs.pfnPrecacheModel) +#define PRECACHE_SOUND (*g_engfuncs.pfnPrecacheSound) +#define PRECACHE_GENERIC (*g_engfuncs.pfnPrecacheGeneric) +#define SET_MODEL (*g_engfuncs.pfnSetModel) +#define MODEL_INDEX (*g_engfuncs.pfnModelIndex) +#define MODEL_FRAMES (*g_engfuncs.pfnModelFrames) +#define SET_SIZE (*g_engfuncs.pfnSetSize) +#define CHANGE_LEVEL (*g_engfuncs.pfnChangeLevel) +#define GET_SPAWN_PARMS (*g_engfuncs.pfnGetSpawnParms) +#define SAVE_SPAWN_PARMS (*g_engfuncs.pfnSaveSpawnParms) +#define VEC_TO_YAW (*g_engfuncs.pfnVecToYaw) +#define VEC_TO_ANGLES (*g_engfuncs.pfnVecToAngles) +#define MOVE_TO_ORIGIN (*g_engfuncs.pfnMoveToOrigin) +#define oldCHANGE_YAW (*g_engfuncs.pfnChangeYaw) +#define CHANGE_PITCH (*g_engfuncs.pfnChangePitch) +#define MAKE_VECTORS (*g_engfuncs.pfnMakeVectors) +#define CREATE_ENTITY (*g_engfuncs.pfnCreateEntity) +#define REMOVE_ENTITY (*g_engfuncs.pfnRemoveEntity) +#define CREATE_NAMED_ENTITY (*g_engfuncs.pfnCreateNamedEntity) +#define MAKE_STATIC (*g_engfuncs.pfnMakeStatic) +#define ENT_IS_ON_FLOOR (*g_engfuncs.pfnEntIsOnFloor) +#define DROP_TO_FLOOR (*g_engfuncs.pfnDropToFloor) +#define WALK_MOVE (*g_engfuncs.pfnWalkMove) +#define SET_ORIGIN (*g_engfuncs.pfnSetOrigin) +#define EMIT_SOUND_DYN2 (*g_engfuncs.pfnEmitSound) +#define BUILD_SOUND_MSG (*g_engfuncs.pfnBuildSoundMsg) +#define TRACE_LINE (*g_engfuncs.pfnTraceLine) +#define TRACE_TOSS (*g_engfuncs.pfnTraceToss) +#define TRACE_MONSTER_HULL (*g_engfuncs.pfnTraceMonsterHull) +#define TRACE_HULL (*g_engfuncs.pfnTraceHull) +#define GET_AIM_VECTOR (*g_engfuncs.pfnGetAimVector) +#define SERVER_COMMAND (*g_engfuncs.pfnServerCommand) +#define SERVER_EXECUTE (*g_engfuncs.pfnServerExecute) +#define CLIENT_COMMAND (*g_engfuncs.pfnClientCommand) +#define PARTICLE_EFFECT (*g_engfuncs.pfnParticleEffect) +#define LIGHT_STYLE (*g_engfuncs.pfnLightStyle) +#define DECAL_INDEX (*g_engfuncs.pfnDecalIndex) +#define POINT_CONTENTS (*g_engfuncs.pfnPointContents) +#define CRC32_INIT (*g_engfuncs.pfnCRC32_Init) +#define CRC32_PROCESS_BUFFER (*g_engfuncs.pfnCRC32_ProcessBuffer) +#define CRC32_PROCESS_BYTE (*g_engfuncs.pfnCRC32_ProcessByte) +#define CRC32_FINAL (*g_engfuncs.pfnCRC32_Final) +#define RANDOM_LONG (*g_engfuncs.pfnRandomLong) +#define RANDOM_FLOAT (*g_engfuncs.pfnRandomFloat) +#define GETPLAYERAUTHID (*g_engfuncs.pfnGetPlayerAuthId) + +inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin = NULL, edict_t *ed = NULL ) { + (*g_engfuncs.pfnMessageBegin)(msg_dest, msg_type, pOrigin, ed); +} +#define MESSAGE_END (*g_engfuncs.pfnMessageEnd) +#define WRITE_BYTE (*g_engfuncs.pfnWriteByte) +#define WRITE_CHAR (*g_engfuncs.pfnWriteChar) +#define WRITE_SHORT (*g_engfuncs.pfnWriteShort) +#define WRITE_LONG (*g_engfuncs.pfnWriteLong) +#define WRITE_ANGLE (*g_engfuncs.pfnWriteAngle) +#define WRITE_COORD (*g_engfuncs.pfnWriteCoord) +#define WRITE_STRING (*g_engfuncs.pfnWriteString) +#define WRITE_ENTITY (*g_engfuncs.pfnWriteEntity) +#define CVAR_REGISTER (*g_engfuncs.pfnCVarRegister) +#define CVAR_GET_FLOAT (*g_engfuncs.pfnCVarGetFloat) +#define CVAR_GET_STRING (*g_engfuncs.pfnCVarGetString) +#define CVAR_SET_FLOAT (*g_engfuncs.pfnCVarSetFloat) +#define CVAR_SET_STRING (*g_engfuncs.pfnCVarSetString) +#define ALERT (*g_engfuncs.pfnAlertMessage) +#define ENGINE_FPRINTF (*g_engfuncs.pfnEngineFprintf) +#define ALLOC_PRIVATE (*g_engfuncs.pfnPvAllocEntPrivateData) +inline void *GET_PRIVATE( edict_t *pent ) +{ + if ( pent ) + return pent->pvPrivateData; + return NULL; +} + +#define FREE_PRIVATE (*g_engfuncs.pfnFreeEntPrivateData) +//#define STRING (*g_engfuncs.pfnSzFromIndex) +#define ALLOC_STRING (*g_engfuncs.pfnAllocString) +#define FIND_ENTITY_BY_STRING (*g_engfuncs.pfnFindEntityByString) +#define GETENTITYILLUM (*g_engfuncs.pfnGetEntityIllum) +#define FIND_ENTITY_IN_SPHERE (*g_engfuncs.pfnFindEntityInSphere) +#define FIND_CLIENT_IN_PVS (*g_engfuncs.pfnFindClientInPVS) +#define EMIT_AMBIENT_SOUND (*g_engfuncs.pfnEmitAmbientSound) +#define GET_MODEL_PTR (*g_engfuncs.pfnGetModelPtr) +#define REG_USER_MSG (*g_engfuncs.pfnRegUserMsg) +#define GET_BONE_POSITION (*g_engfuncs.pfnGetBonePosition) +#define FUNCTION_FROM_NAME (*g_engfuncs.pfnFunctionFromName) +#define NAME_FOR_FUNCTION (*g_engfuncs.pfnNameForFunction) +#define TRACE_TEXTURE (*g_engfuncs.pfnTraceTexture) +#define CLIENT_PRINTF (*g_engfuncs.pfnClientPrintf) +#define CMD_ARGS (*g_engfuncs.pfnCmd_Args) +#define CMD_ARGC (*g_engfuncs.pfnCmd_Argc) +#define CMD_ARGV (*g_engfuncs.pfnCmd_Argv) +#define GET_ATTACHMENT (*g_engfuncs.pfnGetAttachment) +#define SET_VIEW (*g_engfuncs.pfnSetView) +#define SET_CROSSHAIRANGLE (*g_engfuncs.pfnCrosshairAngle) +#define LOAD_FILE_FOR_ME (*g_engfuncs.pfnLoadFileForMe) +#define FREE_FILE (*g_engfuncs.pfnFreeFile) +#define COMPARE_FILE_TIME (*g_engfuncs.pfnCompareFileTime) +#define GET_GAME_DIR (*g_engfuncs.pfnGetGameDir) +#define IS_MAP_VALID (*g_engfuncs.pfnIsMapValid) +#define NUMBER_OF_ENTITIES (*g_engfuncs.pfnNumberOfEntities) +#define IS_DEDICATED_SERVER (*g_engfuncs.pfnIsDedicatedServer) + +#define CVAR_GET_POINTER (*g_engfuncs.pfnCVarGetPointer) + +#define PRECACHE_EVENT (*g_engfuncs.pfnPrecacheEvent) +#define PLAYBACK_EVENT_FULL (*g_engfuncs.pfnPlaybackEvent) + +#define ENGINE_SET_PVS (*g_engfuncs.pfnSetFatPVS) +#define ENGINE_SET_PAS (*g_engfuncs.pfnSetFatPAS) + +#define ENGINE_CHECK_VISIBILITY (*g_engfuncs.pfnCheckVisibility) + +#define DELTA_SET ( *g_engfuncs.pfnDeltaSetField ) +#define DELTA_UNSET ( *g_engfuncs.pfnDeltaUnsetField ) +#define DELTA_ADDENCODER ( *g_engfuncs.pfnDeltaAddEncoder ) +#define ENGINE_CURRENT_PLAYER ( *g_engfuncs.pfnGetCurrentPlayer ) + +#define ENGINE_CANSKIP ( *g_engfuncs.pfnCanSkipPlayer ) + +#define DELTA_FINDFIELD ( *g_engfuncs.pfnDeltaFindField ) +#define DELTA_SETBYINDEX ( *g_engfuncs.pfnDeltaSetFieldByIndex ) +#define DELTA_UNSETBYINDEX ( *g_engfuncs.pfnDeltaUnsetFieldByIndex ) + +#define ENGINE_GETPHYSINFO ( *g_engfuncs.pfnGetPhysicsInfoString ) + +#define ENGINE_SETGROUPMASK ( *g_engfuncs.pfnSetGroupMask ) + +#define ENGINE_INSTANCE_BASELINE ( *g_engfuncs.pfnCreateInstancedBaseline ) + +#define ENGINE_FORCE_UNMODIFIED ( *g_engfuncs.pfnForceUnmodified ) + +#define PLAYER_CNX_STATS ( *g_engfuncs.pfnGetPlayerStats ) + +#endif //ENGINECALLBACK_H diff --git a/ricochet/dlls/explode.cpp b/ricochet/dlls/explode.cpp new file mode 100644 index 0000000..adce53a --- /dev/null +++ b/ricochet/dlls/explode.cpp @@ -0,0 +1,273 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== explode.cpp ======================================================== + + Explosion-related code + +*/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "decals.h" +#include "explode.h" + +// Spark Shower +class CShower : public CBaseEntity +{ + void Spawn( void ); + void Think( void ); + void Touch( CBaseEntity *pOther ); + int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +LINK_ENTITY_TO_CLASS( spark_shower, CShower ); + +void CShower::Spawn( void ) +{ + pev->velocity = RANDOM_FLOAT( 200, 300 ) * pev->angles; + pev->velocity.x += RANDOM_FLOAT(-100.f,100.f); + pev->velocity.y += RANDOM_FLOAT(-100.f,100.f); + if ( pev->velocity.z >= 0 ) + pev->velocity.z += 200; + else + pev->velocity.z -= 200; + pev->movetype = MOVETYPE_BOUNCE; + pev->gravity = 0.5; + pev->nextthink = gpGlobals->time + 0.1; + pev->solid = SOLID_NOT; + SET_MODEL( edict(), "models/grenade.mdl"); // Need a model, just use the grenade, we don't draw it anyway + UTIL_SetSize(pev, g_vecZero, g_vecZero ); + pev->effects |= EF_NODRAW; + pev->speed = RANDOM_FLOAT( 0.5, 1.5 ); + + pev->angles = g_vecZero; +} + + +void CShower::Think( void ) +{ + UTIL_Sparks( pev->origin ); + + pev->speed -= 0.1; + if ( pev->speed > 0 ) + pev->nextthink = gpGlobals->time + 0.1; + else + UTIL_Remove( this ); + pev->flags &= ~FL_ONGROUND; +} + +void CShower::Touch( CBaseEntity *pOther ) +{ + if ( pev->flags & FL_ONGROUND ) + pev->velocity = pev->velocity * 0.1; + else + pev->velocity = pev->velocity * 0.6; + + if ( (pev->velocity.x*pev->velocity.x+pev->velocity.y*pev->velocity.y) < 10.0 ) + pev->speed = 0; +} + +class CEnvExplosion : public CBaseMonster +{ +public: + void Spawn( ); + void EXPORT Smoke ( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iMagnitude;// how large is the fireball? how much damage? + int m_spriteScale; // what's the exact fireball sprite scale? +}; + +TYPEDESCRIPTION CEnvExplosion::m_SaveData[] = +{ + DEFINE_FIELD( CEnvExplosion, m_iMagnitude, FIELD_INTEGER ), + DEFINE_FIELD( CEnvExplosion, m_spriteScale, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CEnvExplosion, CBaseMonster ); +LINK_ENTITY_TO_CLASS( env_explosion, CEnvExplosion ); + +void CEnvExplosion::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "iMagnitude")) + { + m_iMagnitude = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CEnvExplosion::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + + pev->movetype = MOVETYPE_NONE; + /* + if ( m_iMagnitude > 250 ) + { + m_iMagnitude = 250; + } + */ + + float flSpriteScale; + flSpriteScale = ( m_iMagnitude - 50) * 0.6; + + /* + if ( flSpriteScale > 50 ) + { + flSpriteScale = 50; + } + */ + if ( flSpriteScale < 10 ) + { + flSpriteScale = 10; + } + + m_spriteScale = (int)flSpriteScale; +} + +void CEnvExplosion::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + TraceResult tr; + + pev->model = iStringNull;//invisible + pev->solid = SOLID_NOT;// intangible + + Vector vecSpot;// trace starts here! + + vecSpot = pev->origin + Vector ( 0 , 0 , 8 ); + + UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -40 ), ignore_monsters, ENT(pev), & tr); + + // Pull out of the wall a bit + if ( tr.flFraction != 1.0 ) + { + pev->origin = tr.vecEndPos + (tr.vecPlaneNormal * (m_iMagnitude - 24) * 0.6); + } + else + { + pev->origin = pev->origin; + } + + // draw decal + if (! ( pev->spawnflags & SF_ENVEXPLOSION_NODECAL)) + { + if ( RANDOM_FLOAT( 0 , 1 ) < 0.5 ) + { + UTIL_DecalTrace( &tr, DECAL_SCORCH1 ); + } + else + { + UTIL_DecalTrace( &tr, DECAL_SCORCH2 ); + } + } + + // draw fireball + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NOFIREBALL ) ) + { + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( (BYTE)m_spriteScale ); // scale * 10 + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + } + else + { + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( 0 ); // no sprite + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + } + + // do damage + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NODAMAGE ) ) + { + RadiusDamage ( pev, pev, m_iMagnitude, CLASS_NONE, DMG_BLAST ); + } + + SetThink( &CEnvExplosion::Smoke ); + pev->nextthink = gpGlobals->time + 0.3; + + // draw sparks + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NOSPARKS ) ) + { + int sparkCount = RANDOM_LONG(0,3); + + for ( int i = 0; i < sparkCount; i++ ) + { + Create( "spark_shower", pev->origin, tr.vecPlaneNormal, NULL ); + } + } +} + +void CEnvExplosion::Smoke( void ) +{ + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NOSMOKE ) ) + { + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( (BYTE)m_spriteScale ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + } + + if ( !(pev->spawnflags & SF_ENVEXPLOSION_REPEATABLE) ) + { + UTIL_Remove( this ); + } +} + + +// HACKHACK -- create one of these and fake a keyvalue to get the right explosion setup +void ExplosionCreate( const Vector ¢er, const Vector &angles, edict_t *pOwner, int magnitude, BOOL doDamage ) +{ + KeyValueData kvd; + char buf[128]; + + CBaseEntity *pExplosion = CBaseEntity::Create( "env_explosion", center, angles, pOwner ); + sprintf( buf, "%3d", magnitude ); + kvd.szKeyName = "iMagnitude"; + kvd.szValue = buf; + pExplosion->KeyValue( &kvd ); + if ( !doDamage ) + pExplosion->pev->spawnflags |= SF_ENVEXPLOSION_NODAMAGE; + + pExplosion->Spawn(); + pExplosion->Use( NULL, NULL, USE_TOGGLE, 0 ); +} diff --git a/ricochet/dlls/explode.h b/ricochet/dlls/explode.h new file mode 100644 index 0000000..202a581 --- /dev/null +++ b/ricochet/dlls/explode.h @@ -0,0 +1,32 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef EXPLODE_H +#define EXPLODE_H + + +#define SF_ENVEXPLOSION_NODAMAGE ( 1 << 0 ) // when set, ENV_EXPLOSION will not actually inflict damage +#define SF_ENVEXPLOSION_REPEATABLE ( 1 << 1 ) // can this entity be refired? +#define SF_ENVEXPLOSION_NOFIREBALL ( 1 << 2 ) // don't draw the fireball +#define SF_ENVEXPLOSION_NOSMOKE ( 1 << 3 ) // don't draw the smoke +#define SF_ENVEXPLOSION_NODECAL ( 1 << 4 ) // don't make a scorch mark +#define SF_ENVEXPLOSION_NOSPARKS ( 1 << 5 ) // don't make a scorch mark + +extern DLL_GLOBAL short g_sModelIndexFireball; +extern DLL_GLOBAL short g_sModelIndexSmoke; + + +extern void ExplosionCreate( const Vector ¢er, const Vector &angles, edict_t *pOwner, int magnitude, BOOL doDamage ); + +#endif //EXPLODE_H diff --git a/ricochet/dlls/extdll.h b/ricochet/dlls/extdll.h new file mode 100644 index 0000000..672a6e4 --- /dev/null +++ b/ricochet/dlls/extdll.h @@ -0,0 +1,99 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef EXTDLL_H +#define EXTDLL_H + +#include "archtypes.h" // DAL + +// +// Global header file for extension DLLs +// + +// Allow "DEBUG" in addition to default "_DEBUG" +#ifdef _DEBUG +#define DEBUG 1 +#endif + +// Silence certain warnings +#pragma warning(disable : 4244) // int or float down-conversion +#pragma warning(disable : 4305) // int or float data truncation +#pragma warning(disable : 4201) // nameless struct/union +#pragma warning(disable : 4514) // unreferenced inline function removed +#pragma warning(disable : 4100) // unreferenced formal parameter + +#ifdef _WIN32 +// Prevent tons of unused windows definitions +#define WIN32_LEAN_AND_MEAN +#define NOWINRES +#define NOSERVICE +#define NOMCX +#define NOIME +#include "windows.h" + +#else // _WIN32 +#define FALSE 0 +#define TRUE (!FALSE) + +typedef uint32 ULONG; +typedef unsigned char BYTE; +typedef int BOOL; + +#define MAX_PATH PATH_MAX + +#include +#include +#include + +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +#define itoa(a,b,c) sprintf(b, "%d", a) +#define _snprintf snprintf +#define _vsnprintf vsnprintf +#endif //_WIN32 + +// Misc C-runtime library headers +#include "stdio.h" +#include "stdlib.h" +#include "math.h" + +// Header file containing definition of globalvars_t and entvars_t +typedef unsigned int func_t; // +typedef unsigned int string_t; // from engine's pr_comp.h; +typedef float vec_t; // needed before including progdefs.h + +// Vector class +#include "vector.h" + +// Defining it as a (bogus) struct helps enforce type-checking +#define vec3_t Vector + +// Shared engine/DLL constants +#include "const.h" +#include "progdefs.h" +#include "edict.h" + +// Shared header describing protocol between engine and DLLs +#include "eiface.h" + +// Shared header between the client DLL and the game DLLs +#include "cdll_dll.h" + +#endif //EXTDLL_H diff --git a/ricochet/dlls/func_break.cpp b/ricochet/dlls/func_break.cpp new file mode 100644 index 0000000..eb09115 --- /dev/null +++ b/ricochet/dlls/func_break.cpp @@ -0,0 +1,998 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== bmodels.cpp ======================================================== + + spawn, think, and use functions for entities that use brush models + +*/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "func_break.h" +#include "decals.h" +#include "explode.h" + +extern DLL_GLOBAL Vector g_vecAttackDir; + +// =================== FUNC_Breakable ============================================== + +// Just add more items to the bottom of this array and they will automagically be supported +// This is done instead of just a classname in the FGD so we can control which entities can +// be spawned, and still remain fairly flexible +const char *CBreakable::pSpawnObjects[] = +{ + NULL, // 0 + "item_battery", // 1 + "item_healthkit", // 2 + "weapon_9mmhandgun",// 3 + "ammo_9mmclip", // 4 + "weapon_9mmAR", // 5 + "ammo_9mmAR", // 6 + "ammo_ARgrenades", // 7 + "weapon_shotgun", // 8 + "ammo_buckshot", // 9 + "weapon_crossbow", // 10 + "ammo_crossbow", // 11 + "weapon_357", // 12 + "ammo_357", // 13 + "weapon_rpg", // 14 + "ammo_rpgclip", // 15 + "ammo_gaussclip", // 16 + "weapon_handgrenade",// 17 + "weapon_tripmine", // 18 + "weapon_satchel", // 19 + "weapon_snark", // 20 + "weapon_hornetgun", // 21 +}; + +void CBreakable::KeyValue( KeyValueData* pkvd ) +{ + // UNDONE_WC: explicitly ignoring these fields, but they shouldn't be in the map file! + if (FStrEq(pkvd->szKeyName, "explosion")) + { + if (!stricmp(pkvd->szValue, "directed")) + m_Explosion = expDirected; + else if (!stricmp(pkvd->szValue, "random")) + m_Explosion = expRandom; + else + m_Explosion = expRandom; + + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "material")) + { + int i = atoi( pkvd->szValue); + + // 0:glass, 1:metal, 2:flesh, 3:wood + + if ((i < 0) || (i >= matLastMaterial)) + m_Material = matWood; + else + m_Material = (Materials)i; + + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "deadmodel")) + { + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "shards")) + { +// m_iShards = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "gibmodel") ) + { + m_iszGibModel = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spawnobject") ) + { + int object = atoi( pkvd->szValue ); + if ( object > 0 && object < ARRAYSIZE(pSpawnObjects) ) + m_iszSpawnObject = MAKE_STRING( pSpawnObjects[object] ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "explodemagnitude") ) + { + ExplosionSetMagnitude( atoi( pkvd->szValue ) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "lip") ) + pkvd->fHandled = TRUE; + else + CBaseDelay::KeyValue( pkvd ); +} + + +// +// func_breakable - bmodel that breaks into pieces after taking damage +// +LINK_ENTITY_TO_CLASS( func_breakable, CBreakable ); +TYPEDESCRIPTION CBreakable::m_SaveData[] = +{ + DEFINE_FIELD( CBreakable, m_Material, FIELD_INTEGER ), + DEFINE_FIELD( CBreakable, m_Explosion, FIELD_INTEGER ), + +// Don't need to save/restore these because we precache after restore +// DEFINE_FIELD( CBreakable, m_idShard, FIELD_INTEGER ), + + DEFINE_FIELD( CBreakable, m_angle, FIELD_FLOAT ), + DEFINE_FIELD( CBreakable, m_iszGibModel, FIELD_STRING ), + DEFINE_FIELD( CBreakable, m_iszSpawnObject, FIELD_STRING ), + + // Explosion magnitude is stored in pev->impulse +}; + +IMPLEMENT_SAVERESTORE( CBreakable, CBaseEntity ); + +void CBreakable::Spawn( void ) +{ + Precache( ); + + if ( FBitSet( pev->spawnflags, SF_BREAK_TRIGGER_ONLY ) ) + pev->takedamage = DAMAGE_NO; + else + pev->takedamage = DAMAGE_YES; + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + m_angle = pev->angles.y; + pev->angles.y = 0; + + + SET_MODEL(ENT(pev), STRING(pev->model) );//set size and link into world. + + SetTouch( &CBreakable::BreakTouch ); + if ( FBitSet( pev->spawnflags, SF_BREAK_TRIGGER_ONLY ) ) // Only break on trigger + SetTouch( NULL ); + + // Flag unbreakable glass as "worldbrush" so it will block ALL tracelines + if ( !IsBreakable() && pev->rendermode != kRenderNormal ) + pev->flags |= FL_WORLDBRUSH; +} + + +const char *CBreakable::pSoundsWood[] = +{ + "debris/wood1.wav", + "debris/wood2.wav", + "debris/wood3.wav", +}; + +const char *CBreakable::pSoundsFlesh[] = +{ + "debris/flesh1.wav", + "debris/flesh2.wav", + "debris/flesh3.wav", + "debris/flesh5.wav", + "debris/flesh6.wav", + "debris/flesh7.wav", +}; + +const char *CBreakable::pSoundsMetal[] = +{ + "debris/metal1.wav", + "debris/metal2.wav", + "debris/metal3.wav", +}; + +const char *CBreakable::pSoundsConcrete[] = +{ + "debris/concrete1.wav", + "debris/concrete2.wav", + "debris/concrete3.wav", +}; + + +const char *CBreakable::pSoundsGlass[] = +{ + "debris/glass1.wav", + "debris/glass2.wav", + "debris/glass3.wav", +}; + +const char **CBreakable::MaterialSoundList( Materials precacheMaterial, int &soundCount ) +{ + const char **pSoundList = NULL; + + switch ( precacheMaterial ) + { + case matWood: + pSoundList = pSoundsWood; + soundCount = ARRAYSIZE(pSoundsWood); + break; + case matFlesh: + pSoundList = pSoundsFlesh; + soundCount = ARRAYSIZE(pSoundsFlesh); + break; + case matComputer: + case matUnbreakableGlass: + case matGlass: + pSoundList = pSoundsGlass; + soundCount = ARRAYSIZE(pSoundsGlass); + break; + + case matMetal: + pSoundList = pSoundsMetal; + soundCount = ARRAYSIZE(pSoundsMetal); + break; + + case matCinderBlock: + case matRocks: + pSoundList = pSoundsConcrete; + soundCount = ARRAYSIZE(pSoundsConcrete); + break; + + + case matCeilingTile: + case matNone: + default: + soundCount = 0; + break; + } + + return pSoundList; +} + +void CBreakable::MaterialSoundPrecache( Materials precacheMaterial ) +{ + const char **pSoundList; + int i, soundCount = 0; + + pSoundList = MaterialSoundList( precacheMaterial, soundCount ); + + for ( i = 0; i < soundCount; i++ ) + { + PRECACHE_SOUND( (char *)pSoundList[i] ); + } +} + +void CBreakable::MaterialSoundRandom( edict_t *pEdict, Materials soundMaterial, float volume ) +{ + const char **pSoundList; + int soundCount = 0; + + pSoundList = MaterialSoundList( soundMaterial, soundCount ); + + if ( soundCount ) + EMIT_SOUND( pEdict, CHAN_BODY, pSoundList[ RANDOM_LONG(0,soundCount-1) ], volume, 1.0 ); +} + + +void CBreakable::Precache( void ) +{ + const char *pGibName; + + switch (m_Material) + { + case matWood: + pGibName = "models/woodgibs.mdl"; + + PRECACHE_SOUND("debris/bustcrate1.wav"); + PRECACHE_SOUND("debris/bustcrate2.wav"); + break; + case matFlesh: + pGibName = "models/fleshgibs.mdl"; + + PRECACHE_SOUND("debris/bustflesh1.wav"); + PRECACHE_SOUND("debris/bustflesh2.wav"); + break; + case matComputer: + PRECACHE_SOUND("buttons/spark5.wav"); + PRECACHE_SOUND("buttons/spark6.wav"); + pGibName = "models/computergibs.mdl"; + + PRECACHE_SOUND("debris/bustmetal1.wav"); + PRECACHE_SOUND("debris/bustmetal2.wav"); + break; + + case matUnbreakableGlass: + case matGlass: + pGibName = "models/glassgibs.mdl"; + + PRECACHE_SOUND("debris/bustglass1.wav"); + PRECACHE_SOUND("debris/bustglass2.wav"); + break; + case matMetal: + pGibName = "models/metalplategibs.mdl"; + + PRECACHE_SOUND("debris/bustmetal1.wav"); + PRECACHE_SOUND("debris/bustmetal2.wav"); + break; + case matCinderBlock: + pGibName = "models/cindergibs.mdl"; + + PRECACHE_SOUND("debris/bustconcrete1.wav"); + PRECACHE_SOUND("debris/bustconcrete2.wav"); + break; + case matRocks: + pGibName = "models/rockgibs.mdl"; + + PRECACHE_SOUND("debris/bustconcrete1.wav"); + PRECACHE_SOUND("debris/bustconcrete2.wav"); + break; + case matCeilingTile: + pGibName = "models/ceilinggibs.mdl"; + + PRECACHE_SOUND ("debris/bustceiling.wav"); + break; + } + MaterialSoundPrecache( m_Material ); + if ( m_iszGibModel ) + pGibName = STRING(m_iszGibModel); + + m_idShard = PRECACHE_MODEL( (char *)pGibName ); + + // Precache the spawn item's data + if ( m_iszSpawnObject ) + UTIL_PrecacheOther( (char *)STRING( m_iszSpawnObject ) ); +} + +// play shard sound when func_breakable takes damage. +// the more damage, the louder the shard sound. + + +void CBreakable::DamageSound( void ) +{ + int pitch; + float fvol; + char *rgpsz[6]; + int i; + int material = m_Material; + +// if (RANDOM_LONG(0,1)) +// return; + + if (RANDOM_LONG(0,2)) + pitch = PITCH_NORM; + else + pitch = 95 + RANDOM_LONG(0,34); + + fvol = RANDOM_FLOAT(0.75, 1.0); + + if (material == matComputer && RANDOM_LONG(0,1)) + material = matMetal; + + switch (material) + { + case matComputer: + case matGlass: + case matUnbreakableGlass: + rgpsz[0] = "debris/glass1.wav"; + rgpsz[1] = "debris/glass2.wav"; + rgpsz[2] = "debris/glass3.wav"; + i = 3; + break; + + case matWood: + rgpsz[0] = "debris/wood1.wav"; + rgpsz[1] = "debris/wood2.wav"; + rgpsz[2] = "debris/wood3.wav"; + i = 3; + break; + + case matMetal: + rgpsz[0] = "debris/metal1.wav"; + rgpsz[1] = "debris/metal3.wav"; + rgpsz[2] = "debris/metal2.wav"; + i = 2; + break; + + case matFlesh: + rgpsz[0] = "debris/flesh1.wav"; + rgpsz[1] = "debris/flesh2.wav"; + rgpsz[2] = "debris/flesh3.wav"; + rgpsz[3] = "debris/flesh5.wav"; + rgpsz[4] = "debris/flesh6.wav"; + rgpsz[5] = "debris/flesh7.wav"; + i = 6; + break; + + case matRocks: + case matCinderBlock: + rgpsz[0] = "debris/concrete1.wav"; + rgpsz[1] = "debris/concrete2.wav"; + rgpsz[2] = "debris/concrete3.wav"; + i = 3; + break; + + case matCeilingTile: + // UNDONE: no ceiling tile shard sound yet + i = 0; + break; + } + + if (i) + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, rgpsz[RANDOM_LONG(0,i-1)], fvol, ATTN_NORM, 0, pitch); +} + +void CBreakable::BreakTouch( CBaseEntity *pOther ) +{ + float flDamage; + entvars_t* pevToucher = pOther->pev; + + // only players can break these right now + if ( !pOther->IsPlayer() || !IsBreakable() ) + { + return; + } + + if ( FBitSet ( pev->spawnflags, SF_BREAK_TOUCH ) ) + {// can be broken when run into + flDamage = pevToucher->velocity.Length() * 0.01; + + if (flDamage >= pev->health) + { + SetTouch( NULL ); + TakeDamage(pevToucher, pevToucher, flDamage, DMG_CRUSH); + + // do a little damage to player if we broke glass or computer + pOther->TakeDamage( pev, pev, flDamage/4, DMG_SLASH ); + } + } + + if ( FBitSet ( pev->spawnflags, SF_BREAK_PRESSURE ) && pevToucher->absmin.z >= pev->maxs.z - 2 ) + {// can be broken when stood upon + + // play creaking sound here. + DamageSound(); + + SetThink ( &CBreakable::Die ); + SetTouch( NULL ); + + if ( m_flDelay == 0 ) + {// !!!BUGBUG - why doesn't zero delay work? + m_flDelay = 0.1; + } + + pev->nextthink = pev->ltime + m_flDelay; + + } + +} + + +// +// Smash the our breakable object +// + +// Break when triggered +void CBreakable::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( IsBreakable() ) + { + pev->angles.y = m_angle; + UTIL_MakeVectors(pev->angles); + g_vecAttackDir = gpGlobals->v_forward; + + Die(); + } +} + + +void CBreakable::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + // random spark if this is a 'computer' object + if (RANDOM_LONG(0,1) ) + { + switch( m_Material ) + { + case matComputer: + { + UTIL_Sparks( ptr->vecEndPos ); + + float flVolume = RANDOM_FLOAT ( 0.7 , 1.0 );//random volume range + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM); break; + } + } + break; + + case matUnbreakableGlass: + UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT(0.5,1.5) ); + break; + } + } + + CBaseDelay::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + +//========================================================= +// Special takedamage for func_breakable. Allows us to make +// exceptions that are breakable-specific +// bitsDamageType indicates the type of damage sustained ie: DMG_CRUSH +//========================================================= +int CBreakable :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecTemp; + + // if Attacker == Inflictor, the attack was a melee or other instant-hit attack. + // (that is, no actual entity projectile was involved in the attack so use the shooter's origin). + if ( pevAttacker == pevInflictor ) + { + vecTemp = pevInflictor->origin - ( pev->absmin + ( pev->size * 0.5 ) ); + + // if a client hit the breakable with a crowbar, and breakable is crowbar-sensitive, break it now. + if ( FBitSet ( pevAttacker->flags, FL_CLIENT ) && + FBitSet ( pev->spawnflags, SF_BREAK_CROWBAR ) && (bitsDamageType & DMG_CLUB)) + flDamage = pev->health; + } + else + // an actual missile was involved. + { + vecTemp = pevInflictor->origin - ( pev->absmin + ( pev->size * 0.5 ) ); + } + + if (!IsBreakable()) + return 0; + + // Breakables take double damage from the crowbar + if ( bitsDamageType & DMG_CLUB ) + flDamage *= 2; + + // Boxes / glass / etc. don't take much poison damage, just the impact of the dart - consider that 10% + if ( bitsDamageType & DMG_POISON ) + flDamage *= 0.1; + +// this global is still used for glass and other non-monster killables, along with decals. + g_vecAttackDir = vecTemp.Normalize(); + +// do the damage + pev->health -= flDamage; + if (pev->health <= 0) + { + Killed( pevAttacker, GIB_NORMAL ); + Die(); + return 0; + } + + // Make a shard noise each time func breakable is hit. + // Don't play shard noise if cbreakable actually died. + + DamageSound(); + + return 1; +} + + +void CBreakable::Die( void ) +{ + Vector vecSpot;// shard origin + Vector vecVelocity;// shard velocity + CBaseEntity *pEntity = NULL; + char cFlag = 0; + int pitch; + float fvol; + + pitch = 95 + RANDOM_LONG(0,29); + + if (pitch > 97 && pitch < 103) + pitch = 100; + + // The more negative pev->health, the louder + // the sound should be. + + fvol = RANDOM_FLOAT(0.85, 1.0) + (abs(pev->health) / 100.0); + + if (fvol > 1.0) + fvol = 1.0; + + + switch (m_Material) + { + case matGlass: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustglass1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustglass2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_GLASS; + break; + + case matWood: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustcrate1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustcrate2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_WOOD; + break; + + case matComputer: + case matMetal: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustmetal1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustmetal2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_METAL; + break; + + case matFlesh: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustflesh1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustflesh2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_FLESH; + break; + + case matRocks: + case matCinderBlock: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustconcrete1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustconcrete2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_CONCRETE; + break; + + case matCeilingTile: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustceiling.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + + + if (m_Explosion == expDirected) + vecVelocity = g_vecAttackDir * 200; + else + { + vecVelocity.x = 0; + vecVelocity.y = 0; + vecVelocity.z = 0; + } + + vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z ); + + // size + WRITE_COORD( pev->size.x); + WRITE_COORD( pev->size.y); + WRITE_COORD( pev->size.z); + + // velocity + WRITE_COORD( vecVelocity.x ); + WRITE_COORD( vecVelocity.y ); + WRITE_COORD( vecVelocity.z ); + + // randomization + WRITE_BYTE( 10 ); + + // Model + WRITE_SHORT( m_idShard ); //model id# + + // # of shards + WRITE_BYTE( 0 ); // let client decide + + // duration + WRITE_BYTE( 25 );// 2.5 seconds + + // flags + WRITE_BYTE( cFlag ); + MESSAGE_END(); + + float size = pev->size.x; + if ( size < pev->size.y ) + size = pev->size.y; + if ( size < pev->size.z ) + size = pev->size.z; + + // !!! HACK This should work! + // Build a box above the entity that looks like an 8 pixel high sheet + Vector mins = pev->absmin; + Vector maxs = pev->absmax; + mins.z = pev->absmax.z; + maxs.z += 8; + + // BUGBUG -- can only find 256 entities on a breakable -- should be enough + CBaseEntity *pList[256]; + int count = UTIL_EntitiesInBox( pList, 256, mins, maxs, FL_ONGROUND ); + if ( count ) + { + for ( int i = 0; i < count; i++ ) + { + ClearBits( pList[i]->pev->flags, FL_ONGROUND ); + pList[i]->pev->groundentity = NULL; + } + } + + // Don't fire something that could fire myself + pev->targetname = 0; + + pev->solid = SOLID_NOT; + // Fire targets on break + SUB_UseTargets( NULL, USE_TOGGLE, 0 ); + + SetThink( &CBreakable::SUB_Remove ); + pev->nextthink = pev->ltime + 0.1; + if ( m_iszSpawnObject ) + CBaseEntity::Create( (char *)STRING(m_iszSpawnObject), VecBModelOrigin(pev), pev->angles, edict() ); + + + if ( Explodable() ) + { + ExplosionCreate( Center(), pev->angles, edict(), ExplosionMagnitude(), TRUE ); + } +} + + + +BOOL CBreakable :: IsBreakable( void ) +{ + return m_Material != matUnbreakableGlass; +} + + +int CBreakable :: DamageDecal( int bitsDamageType ) +{ + if ( m_Material == matGlass ) + return DECAL_GLASSBREAK1 + RANDOM_LONG(0,2); + + if ( m_Material == matUnbreakableGlass ) + return DECAL_BPROOF1; + + return CBaseEntity::DamageDecal( bitsDamageType ); +} + + +class CPushable : public CBreakable +{ +public: + void Spawn ( void ); + void Precache( void ); + void Touch ( CBaseEntity *pOther ); + void Move( CBaseEntity *pMover, int push ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT StopSound( void ); +// virtual void SetActivator( CBaseEntity *pActivator ) { m_pPusher = pActivator; } + + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_CONTINUOUS_USE; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + inline float MaxSpeed( void ) { return m_maxSpeed; } + + // breakables use an overridden takedamage + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + + static TYPEDESCRIPTION m_SaveData[]; + + static char *m_soundNames[3]; + int m_lastSound; // no need to save/restore, just keeps the same sound from playing twice in a row + float m_maxSpeed; + float m_soundTime; +}; + +TYPEDESCRIPTION CPushable::m_SaveData[] = +{ + DEFINE_FIELD( CPushable, m_maxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CPushable, m_soundTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CPushable, CBreakable ); + +LINK_ENTITY_TO_CLASS( func_pushable, CPushable ); + +char *CPushable :: m_soundNames[3] = { "debris/pushbox1.wav", "debris/pushbox2.wav", "debris/pushbox3.wav" }; + + +void CPushable :: Spawn( void ) +{ + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + CBreakable::Spawn(); + else + Precache( ); + + pev->movetype = MOVETYPE_PUSHSTEP; + pev->solid = SOLID_BBOX; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + if ( pev->friction > 399 ) + pev->friction = 399; + + m_maxSpeed = 400 - pev->friction; + SetBits( pev->flags, FL_FLOAT ); + pev->friction = 0; + + pev->origin.z += 1; // Pick up off of the floor + UTIL_SetOrigin( pev, pev->origin ); + + // Multiply by area of the box's cross-section (assume 1000 units^3 standard volume) + pev->skin = ( pev->skin * (pev->maxs.x - pev->mins.x) * (pev->maxs.y - pev->mins.y) ) * 0.0005; + m_soundTime = 0; +} + + +void CPushable :: Precache( void ) +{ + for ( int i = 0; i < 3; i++ ) + PRECACHE_SOUND( m_soundNames[i] ); + + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + CBreakable::Precache( ); +} + + +void CPushable :: KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "size") ) + { + int bbox = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + switch( bbox ) + { + case 0: // Point + UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8)); + break; + + case 2: // Big Hull!?!? !!!BUGBUG Figure out what this hull really is + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN*2, VEC_DUCK_HULL_MAX*2); + break; + + case 3: // Player duck + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + break; + + default: + case 1: // Player + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + break; + } + + } + else if ( FStrEq(pkvd->szKeyName, "buoyancy") ) + { + pev->skin = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBreakable::KeyValue( pkvd ); +} + + +// Pull the func_pushable +void CPushable :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !pActivator || !pActivator->IsPlayer() ) + { + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + this->CBreakable::Use( pActivator, pCaller, useType, value ); + return; + } + + if ( pActivator->pev->velocity != g_vecZero ) + Move( pActivator, 0 ); +} + + +void CPushable :: Touch( CBaseEntity *pOther ) +{ + if ( FClassnameIs( pOther->pev, "worldspawn" ) ) + return; + + Move( pOther, 1 ); +} + + +void CPushable :: Move( CBaseEntity *pOther, int push ) +{ + entvars_t* pevToucher = pOther->pev; + int playerTouch = 0; + + // Is entity standing on this pushable ? + if ( FBitSet(pevToucher->flags,FL_ONGROUND) && pevToucher->groundentity && VARS(pevToucher->groundentity) == pev ) + { + // Only push if floating + if ( pev->waterlevel > 0 ) + pev->velocity.z += pevToucher->velocity.z * 0.1; + + return; + } + + + if ( pOther->IsPlayer() ) + { + if ( push && !(pevToucher->button & (IN_FORWARD|IN_USE)) ) // Don't push unless the player is pushing forward and NOT use (pull) + return; + playerTouch = 1; + } + + float factor; + + if ( playerTouch ) + { + if ( !(pevToucher->flags & FL_ONGROUND) ) // Don't push away from jumping/falling players unless in water + { + if ( pev->waterlevel < 1 ) + return; + else + factor = 0.1; + } + else + factor = 1; + } + else + factor = 0.25; + + pev->velocity.x += pevToucher->velocity.x * factor; + pev->velocity.y += pevToucher->velocity.y * factor; + + float length = sqrt( pev->velocity.x * pev->velocity.x + pev->velocity.y * pev->velocity.y ); + if ( push && (length > MaxSpeed()) ) + { + pev->velocity.x = (pev->velocity.x * MaxSpeed() / length ); + pev->velocity.y = (pev->velocity.y * MaxSpeed() / length ); + } + if ( playerTouch ) + { + pevToucher->velocity.x = pev->velocity.x; + pevToucher->velocity.y = pev->velocity.y; + if ( (gpGlobals->time - m_soundTime) > 0.7 ) + { + m_soundTime = gpGlobals->time; + if ( length > 0 && FBitSet(pev->flags,FL_ONGROUND) ) + { + m_lastSound = RANDOM_LONG(0,2); + EMIT_SOUND(ENT(pev), CHAN_WEAPON, m_soundNames[m_lastSound], 0.5, ATTN_NORM); + // SetThink( StopSound ); + // pev->nextthink = pev->ltime + 0.1; + } + else + STOP_SOUND( ENT(pev), CHAN_WEAPON, m_soundNames[m_lastSound] ); + } + } +} + +#if 0 +void CPushable::StopSound( void ) +{ + Vector dist = pev->oldorigin - pev->origin; + if ( dist.Length() <= 0 ) + STOP_SOUND( ENT(pev), CHAN_WEAPON, m_soundNames[m_lastSound] ); +} +#endif + +int CPushable::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + return CBreakable::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); + + return 1; +} + diff --git a/ricochet/dlls/func_break.h b/ricochet/dlls/func_break.h new file mode 100644 index 0000000..3c773b1 --- /dev/null +++ b/ricochet/dlls/func_break.h @@ -0,0 +1,74 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef FUNC_BREAK_H +#define FUNC_BREAK_H + +typedef enum { expRandom, expDirected} Explosions; +typedef enum { matGlass = 0, matWood, matMetal, matFlesh, matCinderBlock, matCeilingTile, matComputer, matUnbreakableGlass, matRocks, matNone, matLastMaterial } Materials; + +#define NUM_SHARDS 6 // this many shards spawned when breakable objects break; + +class CBreakable : public CBaseDelay +{ +public: + // basic functions + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData* pkvd); + void EXPORT BreakTouch( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void DamageSound( void ); + + // breakables use an overridden takedamage + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + // To spark when hit + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); + + BOOL IsBreakable( void ); + BOOL SparkWhenHit( void ); + + int DamageDecal( int bitsDamageType ); + + void EXPORT Die( void ); + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + inline BOOL Explodable( void ) { return ExplosionMagnitude() > 0; } + inline int ExplosionMagnitude( void ) { return pev->impulse; } + inline void ExplosionSetMagnitude( int magnitude ) { pev->impulse = magnitude; } + + static void MaterialSoundPrecache( Materials precacheMaterial ); + static void MaterialSoundRandom( edict_t *pEdict, Materials soundMaterial, float volume ); + static const char **MaterialSoundList( Materials precacheMaterial, int &soundCount ); + + static const char *pSoundsWood[]; + static const char *pSoundsFlesh[]; + static const char *pSoundsGlass[]; + static const char *pSoundsMetal[]; + static const char *pSoundsConcrete[]; + static const char *pSpawnObjects[]; + + static TYPEDESCRIPTION m_SaveData[]; + + Materials m_Material; + Explosions m_Explosion; + int m_idShard; + float m_angle; + int m_iszGibModel; + int m_iszSpawnObject; +}; + +#endif // FUNC_BREAK_H diff --git a/ricochet/dlls/func_tank.cpp b/ricochet/dlls/func_tank.cpp new file mode 100644 index 0000000..20b24f9 --- /dev/null +++ b/ricochet/dlls/func_tank.cpp @@ -0,0 +1,1035 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "effects.h" +#include "weapons.h" +#include "explode.h" + +#include "player.h" + + +#define SF_TANK_ACTIVE 0x0001 +#define SF_TANK_PLAYER 0x0002 +#define SF_TANK_HUMANS 0x0004 +#define SF_TANK_ALIENS 0x0008 +#define SF_TANK_LINEOFSIGHT 0x0010 +#define SF_TANK_CANCONTROL 0x0020 +#define SF_TANK_SOUNDON 0x8000 + +enum TANKBULLET +{ + TANK_BULLET_NONE = 0, + TANK_BULLET_9MM = 1, + TANK_BULLET_MP5 = 2, + TANK_BULLET_12MM = 3, +}; + +// Custom damage +// env_laser (duration is 0.5 rate of fire) +// rockets +// explosion? + +class CFuncTank : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + void TrackTarget( void ); + + virtual void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); + virtual Vector UpdateTargetPosition( CBaseEntity *pTarget ) + { + return pTarget->BodyTarget( pev->origin ); + } + + void StartRotSound( void ); + void StopRotSound( void ); + + // Bmodels don't go across transitions + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + inline BOOL IsActive( void ) { return (pev->spawnflags & SF_TANK_ACTIVE)?TRUE:FALSE; } + inline void TankActivate( void ) { pev->spawnflags |= SF_TANK_ACTIVE; pev->nextthink = pev->ltime + 0.1; m_fireLast = 0; } + inline void TankDeactivate( void ) { pev->spawnflags &= ~SF_TANK_ACTIVE; m_fireLast = 0; StopRotSound(); } + inline BOOL CanFire( void ) { return (gpGlobals->time - m_lastSightTime) < m_persist; } + BOOL InRange( float range ); + + // Acquire a target. pPlayer is a player in the PVS + edict_t *FindTarget( edict_t *pPlayer ); + + void TankTrace( const Vector &vecStart, const Vector &vecForward, const Vector &vecSpread, TraceResult &tr ); + + Vector BarrelPosition( void ) + { + Vector forward, right, up; + UTIL_MakeVectorsPrivate( pev->angles, forward, right, up ); + return pev->origin + (forward * m_barrelPos.x) + (right * m_barrelPos.y) + (up * m_barrelPos.z); + } + + void AdjustAnglesForBarrel( Vector &angles, float distance ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + BOOL OnControls( entvars_t *pevTest ); + BOOL StartControl( CBasePlayer* pController ); + void StopControl( void ); + void ControllerPostFrame( void ); + + +protected: + CBasePlayer* m_pController; + float m_flNextAttack; + Vector m_vecControllerUsePos; + + float m_yawCenter; // "Center" yaw + float m_yawRate; // Max turn rate to track targets + float m_yawRange; // Range of turning motion (one-sided: 30 is +/- 30 degress from center) + // Zero is full rotation + float m_yawTolerance; // Tolerance angle + + float m_pitchCenter; // "Center" pitch + float m_pitchRate; // Max turn rate on pitch + float m_pitchRange; // Range of pitch motion as above + float m_pitchTolerance; // Tolerance angle + + float m_fireLast; // Last time I fired + float m_fireRate; // How many rounds/second + float m_lastSightTime;// Last time I saw target + float m_persist; // Persistence of firing (how long do I shoot when I can't see) + float m_minRange; // Minimum range to aim/track + float m_maxRange; // Max range to aim/track + + Vector m_barrelPos; // Length of the freakin barrel + float m_spriteScale; // Scale of any sprites we shoot + int m_iszSpriteSmoke; + int m_iszSpriteFlash; + TANKBULLET m_bulletType; // Bullet type + int m_iBulletDamage; // 0 means use Bullet type's default damage + + Vector m_sightOrigin; // Last sight of target + int m_spread; // firing spread + int m_iszMaster; // Master entity (game_team_master or multisource) +}; + + +TYPEDESCRIPTION CFuncTank::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTank, m_yawCenter, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_yawRate, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_yawRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_yawTolerance, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchCenter, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchRate, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchTolerance, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_fireLast, FIELD_TIME ), + DEFINE_FIELD( CFuncTank, m_fireRate, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_lastSightTime, FIELD_TIME ), + DEFINE_FIELD( CFuncTank, m_persist, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_minRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_maxRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_barrelPos, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTank, m_spriteScale, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_iszSpriteSmoke, FIELD_STRING ), + DEFINE_FIELD( CFuncTank, m_iszSpriteFlash, FIELD_STRING ), + DEFINE_FIELD( CFuncTank, m_bulletType, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTank, m_sightOrigin, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTank, m_spread, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTank, m_pController, FIELD_CLASSPTR ), + DEFINE_FIELD( CFuncTank, m_vecControllerUsePos, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTank, m_flNextAttack, FIELD_TIME ), + DEFINE_FIELD( CFuncTank, m_iBulletDamage, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTank, m_iszMaster, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTank, CBaseEntity ); + +static Vector gTankSpread[] = +{ + Vector( 0, 0, 0 ), // perfect + Vector( 0.025, 0.025, 0.025 ), // small cone + Vector( 0.05, 0.05, 0.05 ), // medium cone + Vector( 0.1, 0.1, 0.1 ), // large cone + Vector( 0.25, 0.25, 0.25 ), // extra-large cone +}; +#define MAX_FIRING_SPREADS ARRAYSIZE(gTankSpread) + + +void CFuncTank :: Spawn( void ) +{ + Precache(); + + pev->movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + pev->solid = SOLID_BSP; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_yawCenter = pev->angles.y; + m_pitchCenter = pev->angles.x; + + if ( IsActive() ) + pev->nextthink = pev->ltime + 1.0; + + m_sightOrigin = BarrelPosition(); // Point at the end of the barrel + + if ( m_fireRate <= 0 ) + m_fireRate = 1; + if ( m_spread > MAX_FIRING_SPREADS ) + m_spread = 0; + + pev->oldorigin = pev->origin; +} + + +void CFuncTank :: Precache( void ) +{ + if ( m_iszSpriteSmoke ) + PRECACHE_MODEL( (char *)STRING(m_iszSpriteSmoke) ); + if ( m_iszSpriteFlash ) + PRECACHE_MODEL( (char *)STRING(m_iszSpriteFlash) ); + + if ( pev->noise ) + PRECACHE_SOUND( (char *)STRING(pev->noise) ); +} + + +void CFuncTank :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "yawrate")) + { + m_yawRate = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "yawrange")) + { + m_yawRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "yawtolerance")) + { + m_yawTolerance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitchrange")) + { + m_pitchRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitchrate")) + { + m_pitchRate = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitchtolerance")) + { + m_pitchTolerance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "firerate")) + { + m_fireRate = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "barrel")) + { + m_barrelPos.x = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "barrely")) + { + m_barrelPos.y = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "barrelz")) + { + m_barrelPos.z = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spritescale")) + { + m_spriteScale = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spritesmoke")) + { + m_iszSpriteSmoke = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spriteflash")) + { + m_iszSpriteFlash = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "rotatesound")) + { + pev->noise = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "persistence")) + { + m_persist = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "bullet")) + { + m_bulletType = (TANKBULLET)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "bullet_damage" )) + { + m_iBulletDamage = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "firespread")) + { + m_spread = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "minRange")) + { + m_minRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "maxRange")) + { + m_maxRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "master")) + { + m_iszMaster = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +////////////// START NEW STUFF ////////////// + +//================================================================================== +// TANK CONTROLLING +BOOL CFuncTank :: OnControls( entvars_t *pevTest ) +{ + if ( !(pev->spawnflags & SF_TANK_CANCONTROL) ) + return FALSE; + + Vector offset = pevTest->origin - pev->origin; + + if ( (m_vecControllerUsePos - pevTest->origin).Length() < 30 ) + return TRUE; + + return FALSE; +} + +BOOL CFuncTank :: StartControl( CBasePlayer *pController ) +{ + if ( m_pController != NULL ) + return FALSE; + + // Team only or disabled? + if ( m_iszMaster ) + { + if ( !UTIL_IsMasterTriggered( m_iszMaster, pController ) ) + return FALSE; + } + + ALERT( at_console, "using TANK!\n"); + + // Holster player's weapon + m_pController = pController; + if ( m_pController->m_pActiveItem ) + { + m_pController->m_pActiveItem->Holster(); + m_pController->pev->weaponmodel = 0; + } + + m_pController->m_iHideHUD |= HIDEHUD_WEAPONS; + m_vecControllerUsePos = m_pController->pev->origin; + + pev->nextthink = pev->ltime + 0.1; + + return TRUE; +} + +void CFuncTank :: StopControl() +{ + // TODO: bring back the controllers current weapon + if ( !m_pController ) + return; + + if ( m_pController->m_pActiveItem ) + m_pController->m_pActiveItem->Deploy(); + + ALERT( at_console, "stopped using TANK\n"); + + m_pController->m_iHideHUD &= ~HIDEHUD_WEAPONS; + + pev->nextthink = 0; + m_pController = NULL; + + if ( IsActive() ) + pev->nextthink = pev->ltime + 1.0; +} + +// Called each frame by the player's ItemPostFrame +void CFuncTank :: ControllerPostFrame( void ) +{ + ASSERT(m_pController != NULL); + + if ( gpGlobals->time < m_flNextAttack ) + return; + + if ( m_pController->pev->button & IN_ATTACK ) + { + Vector vecForward; + UTIL_MakeVectorsPrivate( pev->angles, vecForward, NULL, NULL ); + + m_fireLast = gpGlobals->time - (1/m_fireRate) - 0.01; // to make sure the gun doesn't fire too many bullets + + Fire( BarrelPosition(), vecForward, m_pController->pev ); + + // HACKHACK -- make some noise (that the AI can hear) + if ( m_pController && m_pController->IsPlayer() ) + ((CBasePlayer *)m_pController)->m_iWeaponVolume = LOUD_GUN_VOLUME; + + m_flNextAttack = gpGlobals->time + (1/m_fireRate); + } +} +////////////// END NEW STUFF ////////////// + + +void CFuncTank :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->spawnflags & SF_TANK_CANCONTROL ) + { // player controlled turret + + if ( pActivator->Classify() != CLASS_PLAYER ) + return; + + if ( value == 2 && useType == USE_SET ) + { + ControllerPostFrame(); + } + else if ( !m_pController && useType != USE_OFF ) + { + ((CBasePlayer*)pActivator)->m_pTank = this; + StartControl( (CBasePlayer*)pActivator ); + } + else + { + StopControl(); + } + } + else + { + if ( !ShouldToggle( useType, IsActive() ) ) + return; + + if ( IsActive() ) + TankDeactivate(); + else + TankActivate(); + } +} + + +edict_t *CFuncTank :: FindTarget( edict_t *pPlayer ) +{ + return pPlayer; +} + + + +BOOL CFuncTank :: InRange( float range ) +{ + if ( range < m_minRange ) + return FALSE; + if ( m_maxRange > 0 && range > m_maxRange ) + return FALSE; + + return TRUE; +} + + +void CFuncTank :: Think( void ) +{ + pev->avelocity = g_vecZero; + TrackTarget(); + + if ( fabs(pev->avelocity.x) > 1 || fabs(pev->avelocity.y) > 1 ) + StartRotSound(); + else + StopRotSound(); +} + +void CFuncTank::TrackTarget( void ) +{ + TraceResult tr; + edict_t *pPlayer = FIND_CLIENT_IN_PVS( edict() ); + BOOL updateTime = FALSE, lineOfSight; + Vector angles, direction, targetPosition, barrelEnd; + edict_t *pTarget; + + // Get a position to aim for + if (m_pController) + { + // Tanks attempt to mirror the player's angles + angles = m_pController->pev->v_angle; + angles[0] = 0 - angles[0]; + pev->nextthink = pev->ltime + 0.05; + } + else + { + if ( IsActive() ) + pev->nextthink = pev->ltime + 0.1; + else + return; + + if ( FNullEnt( pPlayer ) ) + { + if ( IsActive() ) + pev->nextthink = pev->ltime + 2; // Wait 2 secs + return; + } + pTarget = FindTarget( pPlayer ); + if ( !pTarget ) + return; + + // Calculate angle needed to aim at target + barrelEnd = BarrelPosition(); + targetPosition = pTarget->v.origin + pTarget->v.view_ofs; + float range = (targetPosition - barrelEnd).Length(); + + if ( !InRange( range ) ) + return; + + UTIL_TraceLine( barrelEnd, targetPosition, dont_ignore_monsters, edict(), &tr ); + + lineOfSight = FALSE; + // No line of sight, don't track + if ( tr.flFraction == 1.0 || tr.pHit == pTarget ) + { + lineOfSight = TRUE; + + CBaseEntity *pInstance = CBaseEntity::Instance(pTarget); + if ( InRange( range ) && pInstance && pInstance->IsAlive() ) + { + updateTime = TRUE; + m_sightOrigin = UpdateTargetPosition( pInstance ); + } + } + + // Track sight origin + +// !!! I'm not sure what i changed + direction = m_sightOrigin - pev->origin; +// direction = m_sightOrigin - barrelEnd; + angles = UTIL_VecToAngles( direction ); + + // Calculate the additional rotation to point the end of the barrel at the target (not the gun's center) + AdjustAnglesForBarrel( angles, direction.Length() ); + } + + angles.x = -angles.x; + + // Force the angles to be relative to the center position + angles.y = m_yawCenter + UTIL_AngleDistance( angles.y, m_yawCenter ); + angles.x = m_pitchCenter + UTIL_AngleDistance( angles.x, m_pitchCenter ); + + // Limit against range in y + if ( angles.y > m_yawCenter + m_yawRange ) + { + angles.y = m_yawCenter + m_yawRange; + updateTime = FALSE; // Don't update if you saw the player, but out of range + } + else if ( angles.y < (m_yawCenter - m_yawRange) ) + { + angles.y = (m_yawCenter - m_yawRange); + updateTime = FALSE; // Don't update if you saw the player, but out of range + } + + if ( updateTime ) + m_lastSightTime = gpGlobals->time; + + // Move toward target at rate or less + float distY = UTIL_AngleDistance( angles.y, pev->angles.y ); + pev->avelocity.y = distY * 10; + if ( pev->avelocity.y > m_yawRate ) + pev->avelocity.y = m_yawRate; + else if ( pev->avelocity.y < -m_yawRate ) + pev->avelocity.y = -m_yawRate; + + // Limit against range in x + if ( angles.x > m_pitchCenter + m_pitchRange ) + angles.x = m_pitchCenter + m_pitchRange; + else if ( angles.x < m_pitchCenter - m_pitchRange ) + angles.x = m_pitchCenter - m_pitchRange; + + // Move toward target at rate or less + float distX = UTIL_AngleDistance( angles.x, pev->angles.x ); + pev->avelocity.x = distX * 10; + + if ( pev->avelocity.x > m_pitchRate ) + pev->avelocity.x = m_pitchRate; + else if ( pev->avelocity.x < -m_pitchRate ) + pev->avelocity.x = -m_pitchRate; + + if ( m_pController ) + return; + + if ( CanFire() && ( (fabs(distX) < m_pitchTolerance && fabs(distY) < m_yawTolerance) || (pev->spawnflags & SF_TANK_LINEOFSIGHT) ) ) + { + BOOL fire = FALSE; + Vector forward; + UTIL_MakeVectorsPrivate( pev->angles, forward, NULL, NULL ); + + if ( pev->spawnflags & SF_TANK_LINEOFSIGHT ) + { + float length = direction.Length(); + UTIL_TraceLine( barrelEnd, barrelEnd + forward * length, dont_ignore_monsters, edict(), &tr ); + if ( tr.pHit == pTarget ) + fire = TRUE; + } + else + fire = TRUE; + + if ( fire ) + { + Fire( BarrelPosition(), forward, pev ); + } + else + m_fireLast = 0; + } + else + m_fireLast = 0; +} + + +// If barrel is offset, add in additional rotation +void CFuncTank::AdjustAnglesForBarrel( Vector &angles, float distance ) +{ + float r2, d2; + + + if ( m_barrelPos.y != 0 || m_barrelPos.z != 0 ) + { + distance -= m_barrelPos.z; + d2 = distance * distance; + if ( m_barrelPos.y ) + { + r2 = m_barrelPos.y * m_barrelPos.y; + angles.y += (180.0 / M_PI) * atan2( m_barrelPos.y, sqrt( d2 - r2 ) ); + } + if ( m_barrelPos.z ) + { + r2 = m_barrelPos.z * m_barrelPos.z; + angles.x += (180.0 / M_PI) * atan2( -m_barrelPos.z, sqrt( d2 - r2 ) ); + } + } +} + + +// Fire targets and spawn sprites +void CFuncTank::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + if ( m_fireLast != 0 ) + { + if ( m_iszSpriteSmoke ) + { + CSprite *pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteSmoke), barrelEnd, TRUE ); + pSprite->AnimateAndDie( RANDOM_FLOAT( 15.0, 20.0 ) ); + pSprite->SetTransparency( kRenderTransAlpha, pev->rendercolor.x, pev->rendercolor.y, pev->rendercolor.z, 255, kRenderFxNone ); + pSprite->pev->velocity.z = RANDOM_FLOAT(40, 80); + pSprite->SetScale( m_spriteScale ); + } + if ( m_iszSpriteFlash ) + { + CSprite *pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteFlash), barrelEnd, TRUE ); + pSprite->AnimateAndDie( 60 ); + pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); + pSprite->SetScale( m_spriteScale ); + } + SUB_UseTargets( this, USE_TOGGLE, 0 ); + } + m_fireLast = gpGlobals->time; +} + + +void CFuncTank::TankTrace( const Vector &vecStart, const Vector &vecForward, const Vector &vecSpread, TraceResult &tr ) +{ + // get circular gaussian spread + float x, y, z; + do { + x = RANDOM_FLOAT(-0.5,0.5) + RANDOM_FLOAT(-0.5,0.5); + y = RANDOM_FLOAT(-0.5,0.5) + RANDOM_FLOAT(-0.5,0.5); + z = x*x+y*y; + } while (z > 1); + Vector vecDir = vecForward + + x * vecSpread.x * gpGlobals->v_right + + y * vecSpread.y * gpGlobals->v_up; + Vector vecEnd; + + vecEnd = vecStart + vecDir * 4096; + UTIL_TraceLine( vecStart, vecEnd, dont_ignore_monsters, edict(), &tr ); +} + + +void CFuncTank::StartRotSound( void ) +{ + if ( !pev->noise || (pev->spawnflags & SF_TANK_SOUNDON) ) + return; + pev->spawnflags |= SF_TANK_SOUNDON; + EMIT_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noise), 0.85, ATTN_NORM); +} + + +void CFuncTank::StopRotSound( void ) +{ + if ( pev->spawnflags & SF_TANK_SOUNDON ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noise) ); + pev->spawnflags &= ~SF_TANK_SOUNDON; +} + +class CFuncTankGun : public CFuncTank +{ +public: + void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); +}; +LINK_ENTITY_TO_CLASS( func_tank, CFuncTankGun ); + +void CFuncTankGun::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + int i; + + if ( m_fireLast != 0 ) + { + // FireBullets needs gpGlobals->v_up, etc. + UTIL_MakeAimVectors(pev->angles); + + int bulletCount = (gpGlobals->time - m_fireLast) * m_fireRate; + if ( bulletCount > 0 ) + { + for ( i = 0; i < bulletCount; i++ ) + { + switch( m_bulletType ) + { + case TANK_BULLET_9MM: + FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096, BULLET_MONSTER_9MM, 1, m_iBulletDamage, pevAttacker ); + break; + + case TANK_BULLET_MP5: + FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096, BULLET_MONSTER_MP5, 1, m_iBulletDamage, pevAttacker ); + break; + + case TANK_BULLET_12MM: + FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096, BULLET_MONSTER_12MM, 1, m_iBulletDamage, pevAttacker ); + break; + + default: + case TANK_BULLET_NONE: + break; + } + } + CFuncTank::Fire( barrelEnd, forward, pevAttacker ); + } + } + else + CFuncTank::Fire( barrelEnd, forward, pevAttacker ); +} + + + +class CFuncTankLaser : public CFuncTank +{ +public: + void Activate( void ); + void KeyValue( KeyValueData *pkvd ); + void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); + void Think( void ); + CLaser *GetLaser( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + CLaser *m_pLaser; + float m_laserTime; +}; +LINK_ENTITY_TO_CLASS( func_tanklaser, CFuncTankLaser ); + +TYPEDESCRIPTION CFuncTankLaser::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTankLaser, m_pLaser, FIELD_CLASSPTR ), + DEFINE_FIELD( CFuncTankLaser, m_laserTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTankLaser, CFuncTank ); + +void CFuncTankLaser::Activate( void ) +{ + if ( !GetLaser() ) + { + UTIL_Remove(this); + ALERT( at_error, "Laser tank with no env_laser!\n" ); + } + else + { + m_pLaser->TurnOff(); + } +} + + +void CFuncTankLaser::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "laserentity")) + { + pev->message = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CFuncTank::KeyValue( pkvd ); +} + + +CLaser *CFuncTankLaser::GetLaser( void ) +{ + if ( m_pLaser ) + return m_pLaser; + + edict_t *pentLaser; + + pentLaser = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->message) ); + while ( !FNullEnt( pentLaser ) ) + { + // Found the landmark + if ( FClassnameIs( pentLaser, "env_laser" ) ) + { + m_pLaser = (CLaser *)CBaseEntity::Instance(pentLaser); + break; + } + else + pentLaser = FIND_ENTITY_BY_TARGETNAME( pentLaser, STRING(pev->message) ); + } + + return m_pLaser; +} + + +void CFuncTankLaser::Think( void ) +{ + if ( m_pLaser && (gpGlobals->time > m_laserTime) ) + m_pLaser->TurnOff(); + + CFuncTank::Think(); +} + + +void CFuncTankLaser::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + int i; + TraceResult tr; + + if ( m_fireLast != 0 && GetLaser() ) + { + // TankTrace needs gpGlobals->v_up, etc. + UTIL_MakeAimVectors(pev->angles); + + int bulletCount = (gpGlobals->time - m_fireLast) * m_fireRate; + if ( bulletCount ) + { + for ( i = 0; i < bulletCount; i++ ) + { + m_pLaser->pev->origin = barrelEnd; + TankTrace( barrelEnd, forward, gTankSpread[m_spread], tr ); + + m_laserTime = gpGlobals->time; + m_pLaser->TurnOn(); + m_pLaser->pev->dmgtime = gpGlobals->time - 1.0; + m_pLaser->FireAtPoint( tr ); + m_pLaser->pev->nextthink = 0; + } + CFuncTank::Fire( barrelEnd, forward, pev ); + } + } + else + { + CFuncTank::Fire( barrelEnd, forward, pev ); + } +} + +class CFuncTankRocket : public CFuncTank +{ +public: + void Precache( void ); + void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); +}; +LINK_ENTITY_TO_CLASS( func_tankrocket, CFuncTankRocket ); + +void CFuncTankRocket::Precache( void ) +{ + UTIL_PrecacheOther( "rpg_rocket" ); + CFuncTank::Precache(); +} + + + +void CFuncTankRocket::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + int i; + + if ( m_fireLast != 0 ) + { + int bulletCount = (gpGlobals->time - m_fireLast) * m_fireRate; + if ( bulletCount > 0 ) + { + for ( i = 0; i < bulletCount; i++ ) + { + CBaseEntity *pRocket = CBaseEntity::Create( "rpg_rocket", barrelEnd, pev->angles, edict() ); + } + CFuncTank::Fire( barrelEnd, forward, pev ); + } + } + else + CFuncTank::Fire( barrelEnd, forward, pev ); +} + + +class CFuncTankMortar : public CFuncTank +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); +}; +LINK_ENTITY_TO_CLASS( func_tankmortar, CFuncTankMortar ); + + +void CFuncTankMortar::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "iMagnitude")) + { + pev->impulse = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CFuncTank::KeyValue( pkvd ); +} + + +void CFuncTankMortar::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ + if ( m_fireLast != 0 ) + { + int bulletCount = (gpGlobals->time - m_fireLast) * m_fireRate; + // Only create 1 explosion + if ( bulletCount > 0 ) + { + TraceResult tr; + + // TankTrace needs gpGlobals->v_up, etc. + UTIL_MakeAimVectors(pev->angles); + + TankTrace( barrelEnd, forward, gTankSpread[m_spread], tr ); + + ExplosionCreate( tr.vecEndPos, pev->angles, edict(), pev->impulse, TRUE ); + + CFuncTank::Fire( barrelEnd, forward, pev ); + } + } + else + CFuncTank::Fire( barrelEnd, forward, pev ); +} + + + +//============================================================================ +// FUNC TANK CONTROLS +//============================================================================ +class CFuncTankControls : public CBaseEntity +{ +public: + virtual int ObjectCaps( void ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + CFuncTank *m_pTank; +}; +LINK_ENTITY_TO_CLASS( func_tankcontrols, CFuncTankControls ); + +TYPEDESCRIPTION CFuncTankControls::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTankControls, m_pTank, FIELD_CLASSPTR ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTankControls, CBaseEntity ); + +int CFuncTankControls :: ObjectCaps( void ) +{ + return (CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_IMPULSE_USE; +} + + +void CFuncTankControls :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ // pass the Use command onto the controls + if ( m_pTank ) + m_pTank->Use( pActivator, pCaller, useType, value ); + + ASSERT( m_pTank != NULL ); // if this fails, most likely means save/restore hasn't worked properly +} + + +void CFuncTankControls :: Think( void ) +{ + edict_t *pTarget = NULL; + + do + { + pTarget = FIND_ENTITY_BY_TARGETNAME( pTarget, STRING(pev->target) ); + } while ( !FNullEnt(pTarget) && strncmp( STRING(pTarget->v.classname), "func_tank", 9 ) ); + + if ( FNullEnt( pTarget ) ) + { + ALERT( at_console, "No tank %s\n", STRING(pev->target) ); + return; + } + + m_pTank = (CFuncTank*)Instance(pTarget); +} + +void CFuncTankControls::Spawn( void ) +{ + pev->solid = SOLID_TRIGGER; + pev->movetype = MOVETYPE_NONE; + pev->effects |= EF_NODRAW; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); + + pev->nextthink = gpGlobals->time + 0.3; // After all the func_tank's have spawned + + CBaseEntity::Spawn(); +} diff --git a/ricochet/dlls/game.cpp b/ricochet/dlls/game.cpp new file mode 100644 index 0000000..c70b8c0 --- /dev/null +++ b/ricochet/dlls/game.cpp @@ -0,0 +1,899 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "eiface.h" +#include "util.h" +#include "game.h" + + +cvar_t displaysoundlist = {"displaysoundlist","0"}; + +// multiplayer server rules +cvar_t fragsleft = {"mp_fragsleft","0", FCVAR_SERVER | FCVAR_UNLOGGED }; // Don't spam console/log files/users with this changing +cvar_t timeleft = {"mp_timeleft","0" , FCVAR_SERVER | FCVAR_UNLOGGED }; // " " + +cvar_t allow_spectators = { "allow_spectators", "1.0", FCVAR_SERVER }; // 0 prevents players from being spectators + +// discwar +cvar_t rc_rounds = {"rc_rounds", "3", FCVAR_SERVER | FCVAR_UNLOGGED }; +cvar_t rc_playersperteam = {"rc_playersperteam", "1", FCVAR_SERVER | FCVAR_UNLOGGED }; +cvar_t rc_arena = {"rc_arena", "1", FCVAR_SERVER | FCVAR_UNLOGGED }; + +// multiplayer server rules +cvar_t teamplay = {"mp_teamplay","0", FCVAR_SERVER }; +cvar_t fraglimit = {"mp_fraglimit","0", FCVAR_SERVER }; +cvar_t timelimit = {"mp_timelimit","0", FCVAR_SERVER }; +cvar_t friendlyfire= {"mp_friendlyfire","0", FCVAR_SERVER }; +cvar_t falldamage = {"mp_falldamage","0", FCVAR_SERVER }; +cvar_t weaponstay = {"mp_weaponstay","0", FCVAR_SERVER }; +cvar_t forcerespawn= {"mp_forcerespawn","1", FCVAR_SERVER }; +cvar_t flashlight = {"mp_flashlight","0", FCVAR_SERVER }; +cvar_t aimcrosshair= {"mp_autocrosshair","1", FCVAR_SERVER }; +cvar_t decalfrequency = {"decalfrequency","30", FCVAR_SERVER }; +cvar_t teamlist = {"mp_teamlist","male", FCVAR_SERVER }; +cvar_t teamoverride = {"mp_teamoverride","1" }; +cvar_t defaultteam = {"mp_defaultteam","0" }; +cvar_t allowmonsters={"mp_allowmonsters","0", FCVAR_SERVER }; + +cvar_t *g_psv_gravity = NULL; +cvar_t *g_psv_aim = NULL; +cvar_t *g_footsteps = NULL; + +//CVARS FOR SKILL LEVEL SETTINGS +// Agrunt +cvar_t sk_agrunt_health1 = {"sk_agrunt_health1","0"}; +cvar_t sk_agrunt_health2 = {"sk_agrunt_health2","0"}; +cvar_t sk_agrunt_health3 = {"sk_agrunt_health3","0"}; + +cvar_t sk_agrunt_dmg_punch1 = {"sk_agrunt_dmg_punch1","0"}; +cvar_t sk_agrunt_dmg_punch2 = {"sk_agrunt_dmg_punch2","0"}; +cvar_t sk_agrunt_dmg_punch3 = {"sk_agrunt_dmg_punch3","0"}; + +// Apache +cvar_t sk_apache_health1 = {"sk_apache_health1","0"}; +cvar_t sk_apache_health2 = {"sk_apache_health2","0"}; +cvar_t sk_apache_health3 = {"sk_apache_health3","0"}; + +// Barney +cvar_t sk_barney_health1 = {"sk_barney_health1","0"}; +cvar_t sk_barney_health2 = {"sk_barney_health2","0"}; +cvar_t sk_barney_health3 = {"sk_barney_health3","0"}; + +// Bullsquid +cvar_t sk_bullsquid_health1 = {"sk_bullsquid_health1","0"}; +cvar_t sk_bullsquid_health2 = {"sk_bullsquid_health2","0"}; +cvar_t sk_bullsquid_health3 = {"sk_bullsquid_health3","0"}; + +cvar_t sk_bullsquid_dmg_bite1 = {"sk_bullsquid_dmg_bite1","0"}; +cvar_t sk_bullsquid_dmg_bite2 = {"sk_bullsquid_dmg_bite2","0"}; +cvar_t sk_bullsquid_dmg_bite3 = {"sk_bullsquid_dmg_bite3","0"}; + +cvar_t sk_bullsquid_dmg_whip1 = {"sk_bullsquid_dmg_whip1","0"}; +cvar_t sk_bullsquid_dmg_whip2 = {"sk_bullsquid_dmg_whip2","0"}; +cvar_t sk_bullsquid_dmg_whip3 = {"sk_bullsquid_dmg_whip3","0"}; + +cvar_t sk_bullsquid_dmg_spit1 = {"sk_bullsquid_dmg_spit1","0"}; +cvar_t sk_bullsquid_dmg_spit2 = {"sk_bullsquid_dmg_spit2","0"}; +cvar_t sk_bullsquid_dmg_spit3 = {"sk_bullsquid_dmg_spit3","0"}; + + +// Big Momma +cvar_t sk_bigmomma_health_factor1 = {"sk_bigmomma_health_factor1","1.0"}; +cvar_t sk_bigmomma_health_factor2 = {"sk_bigmomma_health_factor2","1.0"}; +cvar_t sk_bigmomma_health_factor3 = {"sk_bigmomma_health_factor3","1.0"}; + +cvar_t sk_bigmomma_dmg_slash1 = {"sk_bigmomma_dmg_slash1","50"}; +cvar_t sk_bigmomma_dmg_slash2 = {"sk_bigmomma_dmg_slash2","50"}; +cvar_t sk_bigmomma_dmg_slash3 = {"sk_bigmomma_dmg_slash3","50"}; + +cvar_t sk_bigmomma_dmg_blast1 = {"sk_bigmomma_dmg_blast1","100"}; +cvar_t sk_bigmomma_dmg_blast2 = {"sk_bigmomma_dmg_blast2","100"}; +cvar_t sk_bigmomma_dmg_blast3 = {"sk_bigmomma_dmg_blast3","100"}; + +cvar_t sk_bigmomma_radius_blast1 = {"sk_bigmomma_radius_blast1","250"}; +cvar_t sk_bigmomma_radius_blast2 = {"sk_bigmomma_radius_blast2","250"}; +cvar_t sk_bigmomma_radius_blast3 = {"sk_bigmomma_radius_blast3","250"}; + +// Gargantua +cvar_t sk_gargantua_health1 = {"sk_gargantua_health1","0"}; +cvar_t sk_gargantua_health2 = {"sk_gargantua_health2","0"}; +cvar_t sk_gargantua_health3 = {"sk_gargantua_health3","0"}; + +cvar_t sk_gargantua_dmg_slash1 = {"sk_gargantua_dmg_slash1","0"}; +cvar_t sk_gargantua_dmg_slash2 = {"sk_gargantua_dmg_slash2","0"}; +cvar_t sk_gargantua_dmg_slash3 = {"sk_gargantua_dmg_slash3","0"}; + +cvar_t sk_gargantua_dmg_fire1 = {"sk_gargantua_dmg_fire1","0"}; +cvar_t sk_gargantua_dmg_fire2 = {"sk_gargantua_dmg_fire2","0"}; +cvar_t sk_gargantua_dmg_fire3 = {"sk_gargantua_dmg_fire3","0"}; + +cvar_t sk_gargantua_dmg_stomp1 = {"sk_gargantua_dmg_stomp1","0"}; +cvar_t sk_gargantua_dmg_stomp2 = {"sk_gargantua_dmg_stomp2","0"}; +cvar_t sk_gargantua_dmg_stomp3 = {"sk_gargantua_dmg_stomp3","0"}; + + +// Hassassin +cvar_t sk_hassassin_health1 = {"sk_hassassin_health1","0"}; +cvar_t sk_hassassin_health2 = {"sk_hassassin_health2","0"}; +cvar_t sk_hassassin_health3 = {"sk_hassassin_health3","0"}; + + +// Headcrab +cvar_t sk_headcrab_health1 = {"sk_headcrab_health1","0"}; +cvar_t sk_headcrab_health2 = {"sk_headcrab_health2","0"}; +cvar_t sk_headcrab_health3 = {"sk_headcrab_health3","0"}; + +cvar_t sk_headcrab_dmg_bite1 = {"sk_headcrab_dmg_bite1","0"}; +cvar_t sk_headcrab_dmg_bite2 = {"sk_headcrab_dmg_bite2","0"}; +cvar_t sk_headcrab_dmg_bite3 = {"sk_headcrab_dmg_bite3","0"}; + + +// Hgrunt +cvar_t sk_hgrunt_health1 = {"sk_hgrunt_health1","0"}; +cvar_t sk_hgrunt_health2 = {"sk_hgrunt_health2","0"}; +cvar_t sk_hgrunt_health3 = {"sk_hgrunt_health3","0"}; + +cvar_t sk_hgrunt_kick1 = {"sk_hgrunt_kick1","0"}; +cvar_t sk_hgrunt_kick2 = {"sk_hgrunt_kick2","0"}; +cvar_t sk_hgrunt_kick3 = {"sk_hgrunt_kick3","0"}; + +cvar_t sk_hgrunt_pellets1 = {"sk_hgrunt_pellets1","0"}; +cvar_t sk_hgrunt_pellets2 = {"sk_hgrunt_pellets2","0"}; +cvar_t sk_hgrunt_pellets3 = {"sk_hgrunt_pellets3","0"}; + +cvar_t sk_hgrunt_gspeed1 = {"sk_hgrunt_gspeed1","0"}; +cvar_t sk_hgrunt_gspeed2 = {"sk_hgrunt_gspeed2","0"}; +cvar_t sk_hgrunt_gspeed3 = {"sk_hgrunt_gspeed3","0"}; + +// Houndeye +cvar_t sk_houndeye_health1 = {"sk_houndeye_health1","0"}; +cvar_t sk_houndeye_health2 = {"sk_houndeye_health2","0"}; +cvar_t sk_houndeye_health3 = {"sk_houndeye_health3","0"}; + +cvar_t sk_houndeye_dmg_blast1 = {"sk_houndeye_dmg_blast1","0"}; +cvar_t sk_houndeye_dmg_blast2 = {"sk_houndeye_dmg_blast2","0"}; +cvar_t sk_houndeye_dmg_blast3 = {"sk_houndeye_dmg_blast3","0"}; + + +// ISlave +cvar_t sk_islave_health1 = {"sk_islave_health1","0"}; +cvar_t sk_islave_health2 = {"sk_islave_health2","0"}; +cvar_t sk_islave_health3 = {"sk_islave_health3","0"}; + +cvar_t sk_islave_dmg_claw1 = {"sk_islave_dmg_claw1","0"}; +cvar_t sk_islave_dmg_claw2 = {"sk_islave_dmg_claw2","0"}; +cvar_t sk_islave_dmg_claw3 = {"sk_islave_dmg_claw3","0"}; + +cvar_t sk_islave_dmg_clawrake1 = {"sk_islave_dmg_clawrake1","0"}; +cvar_t sk_islave_dmg_clawrake2 = {"sk_islave_dmg_clawrake2","0"}; +cvar_t sk_islave_dmg_clawrake3 = {"sk_islave_dmg_clawrake3","0"}; + +cvar_t sk_islave_dmg_zap1 = {"sk_islave_dmg_zap1","0"}; +cvar_t sk_islave_dmg_zap2 = {"sk_islave_dmg_zap2","0"}; +cvar_t sk_islave_dmg_zap3 = {"sk_islave_dmg_zap3","0"}; + + +// Icthyosaur +cvar_t sk_ichthyosaur_health1 = {"sk_ichthyosaur_health1","0"}; +cvar_t sk_ichthyosaur_health2 = {"sk_ichthyosaur_health2","0"}; +cvar_t sk_ichthyosaur_health3 = {"sk_ichthyosaur_health3","0"}; + +cvar_t sk_ichthyosaur_shake1 = {"sk_ichthyosaur_shake1","0"}; +cvar_t sk_ichthyosaur_shake2 = {"sk_ichthyosaur_shake2","0"}; +cvar_t sk_ichthyosaur_shake3 = {"sk_ichthyosaur_shake3","0"}; + + +// Leech +cvar_t sk_leech_health1 = {"sk_leech_health1","0"}; +cvar_t sk_leech_health2 = {"sk_leech_health2","0"}; +cvar_t sk_leech_health3 = {"sk_leech_health3","0"}; + +cvar_t sk_leech_dmg_bite1 = {"sk_leech_dmg_bite1","0"}; +cvar_t sk_leech_dmg_bite2 = {"sk_leech_dmg_bite2","0"}; +cvar_t sk_leech_dmg_bite3 = {"sk_leech_dmg_bite3","0"}; + +// Controller +cvar_t sk_controller_health1 = {"sk_controller_health1","0"}; +cvar_t sk_controller_health2 = {"sk_controller_health2","0"}; +cvar_t sk_controller_health3 = {"sk_controller_health3","0"}; + +cvar_t sk_controller_dmgzap1 = {"sk_controller_dmgzap1","0"}; +cvar_t sk_controller_dmgzap2 = {"sk_controller_dmgzap2","0"}; +cvar_t sk_controller_dmgzap3 = {"sk_controller_dmgzap3","0"}; + +cvar_t sk_controller_speedball1 = {"sk_controller_speedball1","0"}; +cvar_t sk_controller_speedball2 = {"sk_controller_speedball2","0"}; +cvar_t sk_controller_speedball3 = {"sk_controller_speedball3","0"}; + +cvar_t sk_controller_dmgball1 = {"sk_controller_dmgball1","0"}; +cvar_t sk_controller_dmgball2 = {"sk_controller_dmgball2","0"}; +cvar_t sk_controller_dmgball3 = {"sk_controller_dmgball3","0"}; + +// Nihilanth +cvar_t sk_nihilanth_health1 = {"sk_nihilanth_health1","0"}; +cvar_t sk_nihilanth_health2 = {"sk_nihilanth_health2","0"}; +cvar_t sk_nihilanth_health3 = {"sk_nihilanth_health3","0"}; + +cvar_t sk_nihilanth_zap1 = {"sk_nihilanth_zap1","0"}; +cvar_t sk_nihilanth_zap2 = {"sk_nihilanth_zap2","0"}; +cvar_t sk_nihilanth_zap3 = {"sk_nihilanth_zap3","0"}; + +// Scientist +cvar_t sk_scientist_health1 = {"sk_scientist_health1","0"}; +cvar_t sk_scientist_health2 = {"sk_scientist_health2","0"}; +cvar_t sk_scientist_health3 = {"sk_scientist_health3","0"}; + + +// Snark +cvar_t sk_snark_health1 = {"sk_snark_health1","0"}; +cvar_t sk_snark_health2 = {"sk_snark_health2","0"}; +cvar_t sk_snark_health3 = {"sk_snark_health3","0"}; + +cvar_t sk_snark_dmg_bite1 = {"sk_snark_dmg_bite1","0"}; +cvar_t sk_snark_dmg_bite2 = {"sk_snark_dmg_bite2","0"}; +cvar_t sk_snark_dmg_bite3 = {"sk_snark_dmg_bite3","0"}; + +cvar_t sk_snark_dmg_pop1 = {"sk_snark_dmg_pop1","0"}; +cvar_t sk_snark_dmg_pop2 = {"sk_snark_dmg_pop2","0"}; +cvar_t sk_snark_dmg_pop3 = {"sk_snark_dmg_pop3","0"}; + + + +// Zombie +cvar_t sk_zombie_health1 = {"sk_zombie_health1","0"}; +cvar_t sk_zombie_health2 = {"sk_zombie_health2","0"}; +cvar_t sk_zombie_health3 = {"sk_zombie_health3","0"}; + +cvar_t sk_zombie_dmg_one_slash1 = {"sk_zombie_dmg_one_slash1","0"}; +cvar_t sk_zombie_dmg_one_slash2 = {"sk_zombie_dmg_one_slash2","0"}; +cvar_t sk_zombie_dmg_one_slash3 = {"sk_zombie_dmg_one_slash3","0"}; + +cvar_t sk_zombie_dmg_both_slash1 = {"sk_zombie_dmg_both_slash1","0"}; +cvar_t sk_zombie_dmg_both_slash2 = {"sk_zombie_dmg_both_slash2","0"}; +cvar_t sk_zombie_dmg_both_slash3 = {"sk_zombie_dmg_both_slash3","0"}; + + +//Turret +cvar_t sk_turret_health1 = {"sk_turret_health1","0"}; +cvar_t sk_turret_health2 = {"sk_turret_health2","0"}; +cvar_t sk_turret_health3 = {"sk_turret_health3","0"}; + + +// MiniTurret +cvar_t sk_miniturret_health1 = {"sk_miniturret_health1","0"}; +cvar_t sk_miniturret_health2 = {"sk_miniturret_health2","0"}; +cvar_t sk_miniturret_health3 = {"sk_miniturret_health3","0"}; + + +// Sentry Turret +cvar_t sk_sentry_health1 = {"sk_sentry_health1","0"}; +cvar_t sk_sentry_health2 = {"sk_sentry_health2","0"}; +cvar_t sk_sentry_health3 = {"sk_sentry_health3","0"}; + + +// PLAYER WEAPONS + +// Crowbar whack +cvar_t sk_plr_crowbar1 = {"sk_plr_crowbar1","0"}; +cvar_t sk_plr_crowbar2 = {"sk_plr_crowbar2","0"}; +cvar_t sk_plr_crowbar3 = {"sk_plr_crowbar3","0"}; + +// Glock Round +cvar_t sk_plr_9mm_bullet1 = {"sk_plr_9mm_bullet1","0"}; +cvar_t sk_plr_9mm_bullet2 = {"sk_plr_9mm_bullet2","0"}; +cvar_t sk_plr_9mm_bullet3 = {"sk_plr_9mm_bullet3","0"}; + +// 357 Round +cvar_t sk_plr_357_bullet1 = {"sk_plr_357_bullet1","0"}; +cvar_t sk_plr_357_bullet2 = {"sk_plr_357_bullet2","0"}; +cvar_t sk_plr_357_bullet3 = {"sk_plr_357_bullet3","0"}; + +// MP5 Round +cvar_t sk_plr_9mmAR_bullet1 = {"sk_plr_9mmAR_bullet1","0"}; +cvar_t sk_plr_9mmAR_bullet2 = {"sk_plr_9mmAR_bullet2","0"}; +cvar_t sk_plr_9mmAR_bullet3 = {"sk_plr_9mmAR_bullet3","0"}; + + +// M203 grenade +cvar_t sk_plr_9mmAR_grenade1 = {"sk_plr_9mmAR_grenade1","0"}; +cvar_t sk_plr_9mmAR_grenade2 = {"sk_plr_9mmAR_grenade2","0"}; +cvar_t sk_plr_9mmAR_grenade3 = {"sk_plr_9mmAR_grenade3","0"}; + + +// Shotgun buckshot +cvar_t sk_plr_buckshot1 = {"sk_plr_buckshot1","0"}; +cvar_t sk_plr_buckshot2 = {"sk_plr_buckshot2","0"}; +cvar_t sk_plr_buckshot3 = {"sk_plr_buckshot3","0"}; + + +// Crossbow +cvar_t sk_plr_xbow_bolt_client1 = {"sk_plr_xbow_bolt_client1","0"}; +cvar_t sk_plr_xbow_bolt_client2 = {"sk_plr_xbow_bolt_client2","0"}; +cvar_t sk_plr_xbow_bolt_client3 = {"sk_plr_xbow_bolt_client3","0"}; + +cvar_t sk_plr_xbow_bolt_monster1 = {"sk_plr_xbow_bolt_monster1","0"}; +cvar_t sk_plr_xbow_bolt_monster2 = {"sk_plr_xbow_bolt_monster2","0"}; +cvar_t sk_plr_xbow_bolt_monster3 = {"sk_plr_xbow_bolt_monster3","0"}; + + +// RPG +cvar_t sk_plr_rpg1 = {"sk_plr_rpg1","0"}; +cvar_t sk_plr_rpg2 = {"sk_plr_rpg2","0"}; +cvar_t sk_plr_rpg3 = {"sk_plr_rpg3","0"}; + + +// Zero Point Generator +cvar_t sk_plr_gauss1 = {"sk_plr_gauss1","0"}; +cvar_t sk_plr_gauss2 = {"sk_plr_gauss2","0"}; +cvar_t sk_plr_gauss3 = {"sk_plr_gauss3","0"}; + + +// Tau Cannon +cvar_t sk_plr_egon_narrow1 = {"sk_plr_egon_narrow1","0"}; +cvar_t sk_plr_egon_narrow2 = {"sk_plr_egon_narrow2","0"}; +cvar_t sk_plr_egon_narrow3 = {"sk_plr_egon_narrow3","0"}; + +cvar_t sk_plr_egon_wide1 = {"sk_plr_egon_wide1","0"}; +cvar_t sk_plr_egon_wide2 = {"sk_plr_egon_wide2","0"}; +cvar_t sk_plr_egon_wide3 = {"sk_plr_egon_wide3","0"}; + + +// Hand Grendade +cvar_t sk_plr_hand_grenade1 = {"sk_plr_hand_grenade1","0"}; +cvar_t sk_plr_hand_grenade2 = {"sk_plr_hand_grenade2","0"}; +cvar_t sk_plr_hand_grenade3 = {"sk_plr_hand_grenade3","0"}; + + +// Satchel Charge +cvar_t sk_plr_satchel1 = {"sk_plr_satchel1","0"}; +cvar_t sk_plr_satchel2 = {"sk_plr_satchel2","0"}; +cvar_t sk_plr_satchel3 = {"sk_plr_satchel3","0"}; + + +// Tripmine +cvar_t sk_plr_tripmine1 = {"sk_plr_tripmine1","0"}; +cvar_t sk_plr_tripmine2 = {"sk_plr_tripmine2","0"}; +cvar_t sk_plr_tripmine3 = {"sk_plr_tripmine3","0"}; + + +// WORLD WEAPONS +cvar_t sk_12mm_bullet1 = {"sk_12mm_bullet1","0"}; +cvar_t sk_12mm_bullet2 = {"sk_12mm_bullet2","0"}; +cvar_t sk_12mm_bullet3 = {"sk_12mm_bullet3","0"}; + +cvar_t sk_9mmAR_bullet1 = {"sk_9mmAR_bullet1","0"}; +cvar_t sk_9mmAR_bullet2 = {"sk_9mmAR_bullet2","0"}; +cvar_t sk_9mmAR_bullet3 = {"sk_9mmAR_bullet3","0"}; + +cvar_t sk_9mm_bullet1 = {"sk_9mm_bullet1","0"}; +cvar_t sk_9mm_bullet2 = {"sk_9mm_bullet2","0"}; +cvar_t sk_9mm_bullet3 = {"sk_9mm_bullet3","0"}; + + +// HORNET +cvar_t sk_hornet_dmg1 = {"sk_hornet_dmg1","0"}; +cvar_t sk_hornet_dmg2 = {"sk_hornet_dmg2","0"}; +cvar_t sk_hornet_dmg3 = {"sk_hornet_dmg3","0"}; + +// HEALTH/CHARGE +cvar_t sk_suitcharger1 = { "sk_suitcharger1","0" }; +cvar_t sk_suitcharger2 = { "sk_suitcharger2","0" }; +cvar_t sk_suitcharger3 = { "sk_suitcharger3","0" }; + +cvar_t sk_battery1 = { "sk_battery1","0" }; +cvar_t sk_battery2 = { "sk_battery2","0" }; +cvar_t sk_battery3 = { "sk_battery3","0" }; + +cvar_t sk_healthcharger1 = { "sk_healthcharger1","0" }; +cvar_t sk_healthcharger2 = { "sk_healthcharger2","0" }; +cvar_t sk_healthcharger3 = { "sk_healthcharger3","0" }; + +cvar_t sk_healthkit1 = { "sk_healthkit1","0" }; +cvar_t sk_healthkit2 = { "sk_healthkit2","0" }; +cvar_t sk_healthkit3 = { "sk_healthkit3","0" }; + +cvar_t sk_scientist_heal1 = { "sk_scientist_heal1","0" }; +cvar_t sk_scientist_heal2 = { "sk_scientist_heal2","0" }; +cvar_t sk_scientist_heal3 = { "sk_scientist_heal3","0" }; + + +// monster damage adjusters +cvar_t sk_monster_head1 = { "sk_monster_head1","2" }; +cvar_t sk_monster_head2 = { "sk_monster_head2","2" }; +cvar_t sk_monster_head3 = { "sk_monster_head3","2" }; + +cvar_t sk_monster_chest1 = { "sk_monster_chest1","1" }; +cvar_t sk_monster_chest2 = { "sk_monster_chest2","1" }; +cvar_t sk_monster_chest3 = { "sk_monster_chest3","1" }; + +cvar_t sk_monster_stomach1 = { "sk_monster_stomach1","1" }; +cvar_t sk_monster_stomach2 = { "sk_monster_stomach2","1" }; +cvar_t sk_monster_stomach3 = { "sk_monster_stomach3","1" }; + +cvar_t sk_monster_arm1 = { "sk_monster_arm1","1" }; +cvar_t sk_monster_arm2 = { "sk_monster_arm2","1" }; +cvar_t sk_monster_arm3 = { "sk_monster_arm3","1" }; + +cvar_t sk_monster_leg1 = { "sk_monster_leg1","1" }; +cvar_t sk_monster_leg2 = { "sk_monster_leg2","1" }; +cvar_t sk_monster_leg3 = { "sk_monster_leg3","1" }; + +// player damage adjusters +cvar_t sk_player_head1 = { "sk_player_head1","2" }; +cvar_t sk_player_head2 = { "sk_player_head2","2" }; +cvar_t sk_player_head3 = { "sk_player_head3","2" }; + +cvar_t sk_player_chest1 = { "sk_player_chest1","1" }; +cvar_t sk_player_chest2 = { "sk_player_chest2","1" }; +cvar_t sk_player_chest3 = { "sk_player_chest3","1" }; + +cvar_t sk_player_stomach1 = { "sk_player_stomach1","1" }; +cvar_t sk_player_stomach2 = { "sk_player_stomach2","1" }; +cvar_t sk_player_stomach3 = { "sk_player_stomach3","1" }; + +cvar_t sk_player_arm1 = { "sk_player_arm1","1" }; +cvar_t sk_player_arm2 = { "sk_player_arm2","1" }; +cvar_t sk_player_arm3 = { "sk_player_arm3","1" }; + +cvar_t sk_player_leg1 = { "sk_player_leg1","1" }; +cvar_t sk_player_leg2 = { "sk_player_leg2","1" }; +cvar_t sk_player_leg3 = { "sk_player_leg3","1" }; + +// END Cvars for Skill Level settings + +// Register your console variables here +// This gets called one time when the game is initialied +void GameDLLInit( void ) +{ + // Register cvars here: + g_psv_gravity = CVAR_GET_POINTER( "sv_gravity" ); + g_psv_aim = CVAR_GET_POINTER( "sv_aim" ); + g_footsteps = CVAR_GET_POINTER( "mp_footsteps" ); + + CVAR_REGISTER (&displaysoundlist); + + CVAR_REGISTER (&teamplay); + CVAR_REGISTER (&fraglimit); + CVAR_REGISTER (&timelimit); + + CVAR_REGISTER (&fragsleft); + CVAR_REGISTER (&timeleft); + + CVAR_REGISTER( &allow_spectators ); + + // Discwar + CVAR_REGISTER (&rc_rounds); + CVAR_REGISTER (&rc_playersperteam); + CVAR_REGISTER (&rc_arena); + + CVAR_REGISTER (&friendlyfire); + CVAR_REGISTER (&falldamage); + CVAR_REGISTER (&weaponstay); + CVAR_REGISTER (&forcerespawn); + CVAR_REGISTER (&flashlight); + CVAR_REGISTER (&aimcrosshair); + CVAR_REGISTER (&decalfrequency); + CVAR_REGISTER (&teamlist); + CVAR_REGISTER (&teamoverride); + CVAR_REGISTER (&defaultteam); + CVAR_REGISTER (&allowmonsters); + +// REGISTER CVARS FOR SKILL LEVEL STUFF + // Agrunt + CVAR_REGISTER ( &sk_agrunt_health1 );// {"sk_agrunt_health1","0"}; + CVAR_REGISTER ( &sk_agrunt_health2 );// {"sk_agrunt_health2","0"}; + CVAR_REGISTER ( &sk_agrunt_health3 );// {"sk_agrunt_health3","0"}; + + CVAR_REGISTER ( &sk_agrunt_dmg_punch1 );// {"sk_agrunt_dmg_punch1","0"}; + CVAR_REGISTER ( &sk_agrunt_dmg_punch2 );// {"sk_agrunt_dmg_punch2","0"}; + CVAR_REGISTER ( &sk_agrunt_dmg_punch3 );// {"sk_agrunt_dmg_punch3","0"}; + + // Apache + CVAR_REGISTER ( &sk_apache_health1 );// {"sk_apache_health1","0"}; + CVAR_REGISTER ( &sk_apache_health2 );// {"sk_apache_health2","0"}; + CVAR_REGISTER ( &sk_apache_health3 );// {"sk_apache_health3","0"}; + + // Barney + CVAR_REGISTER ( &sk_barney_health1 );// {"sk_barney_health1","0"}; + CVAR_REGISTER ( &sk_barney_health2 );// {"sk_barney_health2","0"}; + CVAR_REGISTER ( &sk_barney_health3 );// {"sk_barney_health3","0"}; + + // Bullsquid + CVAR_REGISTER ( &sk_bullsquid_health1 );// {"sk_bullsquid_health1","0"}; + CVAR_REGISTER ( &sk_bullsquid_health2 );// {"sk_bullsquid_health2","0"}; + CVAR_REGISTER ( &sk_bullsquid_health3 );// {"sk_bullsquid_health3","0"}; + + CVAR_REGISTER ( &sk_bullsquid_dmg_bite1 );// {"sk_bullsquid_dmg_bite1","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_bite2 );// {"sk_bullsquid_dmg_bite2","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_bite3 );// {"sk_bullsquid_dmg_bite3","0"}; + + CVAR_REGISTER ( &sk_bullsquid_dmg_whip1 );// {"sk_bullsquid_dmg_whip1","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_whip2 );// {"sk_bullsquid_dmg_whip2","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_whip3 );// {"sk_bullsquid_dmg_whip3","0"}; + + CVAR_REGISTER ( &sk_bullsquid_dmg_spit1 );// {"sk_bullsquid_dmg_spit1","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_spit2 );// {"sk_bullsquid_dmg_spit2","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_spit3 );// {"sk_bullsquid_dmg_spit3","0"}; + + + CVAR_REGISTER ( &sk_bigmomma_health_factor1 );// {"sk_bigmomma_health_factor1","1.0"}; + CVAR_REGISTER ( &sk_bigmomma_health_factor2 );// {"sk_bigmomma_health_factor2","1.0"}; + CVAR_REGISTER ( &sk_bigmomma_health_factor3 );// {"sk_bigmomma_health_factor3","1.0"}; + + CVAR_REGISTER ( &sk_bigmomma_dmg_slash1 );// {"sk_bigmomma_dmg_slash1","50"}; + CVAR_REGISTER ( &sk_bigmomma_dmg_slash2 );// {"sk_bigmomma_dmg_slash2","50"}; + CVAR_REGISTER ( &sk_bigmomma_dmg_slash3 );// {"sk_bigmomma_dmg_slash3","50"}; + + CVAR_REGISTER ( &sk_bigmomma_dmg_blast1 );// {"sk_bigmomma_dmg_blast1","100"}; + CVAR_REGISTER ( &sk_bigmomma_dmg_blast2 );// {"sk_bigmomma_dmg_blast2","100"}; + CVAR_REGISTER ( &sk_bigmomma_dmg_blast3 );// {"sk_bigmomma_dmg_blast3","100"}; + + CVAR_REGISTER ( &sk_bigmomma_radius_blast1 );// {"sk_bigmomma_radius_blast1","250"}; + CVAR_REGISTER ( &sk_bigmomma_radius_blast2 );// {"sk_bigmomma_radius_blast2","250"}; + CVAR_REGISTER ( &sk_bigmomma_radius_blast3 );// {"sk_bigmomma_radius_blast3","250"}; + + // Gargantua + CVAR_REGISTER ( &sk_gargantua_health1 );// {"sk_gargantua_health1","0"}; + CVAR_REGISTER ( &sk_gargantua_health2 );// {"sk_gargantua_health2","0"}; + CVAR_REGISTER ( &sk_gargantua_health3 );// {"sk_gargantua_health3","0"}; + + CVAR_REGISTER ( &sk_gargantua_dmg_slash1 );// {"sk_gargantua_dmg_slash1","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_slash2 );// {"sk_gargantua_dmg_slash2","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_slash3 );// {"sk_gargantua_dmg_slash3","0"}; + + CVAR_REGISTER ( &sk_gargantua_dmg_fire1 );// {"sk_gargantua_dmg_fire1","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_fire2 );// {"sk_gargantua_dmg_fire2","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_fire3 );// {"sk_gargantua_dmg_fire3","0"}; + + CVAR_REGISTER ( &sk_gargantua_dmg_stomp1 );// {"sk_gargantua_dmg_stomp1","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_stomp2 );// {"sk_gargantua_dmg_stomp2","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_stomp3 );// {"sk_gargantua_dmg_stomp3","0"}; + + + // Hassassin + CVAR_REGISTER ( &sk_hassassin_health1 );// {"sk_hassassin_health1","0"}; + CVAR_REGISTER ( &sk_hassassin_health2 );// {"sk_hassassin_health2","0"}; + CVAR_REGISTER ( &sk_hassassin_health3 );// {"sk_hassassin_health3","0"}; + + + // Headcrab + CVAR_REGISTER ( &sk_headcrab_health1 );// {"sk_headcrab_health1","0"}; + CVAR_REGISTER ( &sk_headcrab_health2 );// {"sk_headcrab_health2","0"}; + CVAR_REGISTER ( &sk_headcrab_health3 );// {"sk_headcrab_health3","0"}; + + CVAR_REGISTER ( &sk_headcrab_dmg_bite1 );// {"sk_headcrab_dmg_bite1","0"}; + CVAR_REGISTER ( &sk_headcrab_dmg_bite2 );// {"sk_headcrab_dmg_bite2","0"}; + CVAR_REGISTER ( &sk_headcrab_dmg_bite3 );// {"sk_headcrab_dmg_bite3","0"}; + + + // Hgrunt + CVAR_REGISTER ( &sk_hgrunt_health1 );// {"sk_hgrunt_health1","0"}; + CVAR_REGISTER ( &sk_hgrunt_health2 );// {"sk_hgrunt_health2","0"}; + CVAR_REGISTER ( &sk_hgrunt_health3 );// {"sk_hgrunt_health3","0"}; + + CVAR_REGISTER ( &sk_hgrunt_kick1 );// {"sk_hgrunt_kick1","0"}; + CVAR_REGISTER ( &sk_hgrunt_kick2 );// {"sk_hgrunt_kick2","0"}; + CVAR_REGISTER ( &sk_hgrunt_kick3 );// {"sk_hgrunt_kick3","0"}; + + CVAR_REGISTER ( &sk_hgrunt_pellets1 ); + CVAR_REGISTER ( &sk_hgrunt_pellets2 ); + CVAR_REGISTER ( &sk_hgrunt_pellets3 ); + + CVAR_REGISTER ( &sk_hgrunt_gspeed1 ); + CVAR_REGISTER ( &sk_hgrunt_gspeed2 ); + CVAR_REGISTER ( &sk_hgrunt_gspeed3 ); + + // Houndeye + CVAR_REGISTER ( &sk_houndeye_health1 );// {"sk_houndeye_health1","0"}; + CVAR_REGISTER ( &sk_houndeye_health2 );// {"sk_houndeye_health2","0"}; + CVAR_REGISTER ( &sk_houndeye_health3 );// {"sk_houndeye_health3","0"}; + + CVAR_REGISTER ( &sk_houndeye_dmg_blast1 );// {"sk_houndeye_dmg_blast1","0"}; + CVAR_REGISTER ( &sk_houndeye_dmg_blast2 );// {"sk_houndeye_dmg_blast2","0"}; + CVAR_REGISTER ( &sk_houndeye_dmg_blast3 );// {"sk_houndeye_dmg_blast3","0"}; + + + // ISlave + CVAR_REGISTER ( &sk_islave_health1 );// {"sk_islave_health1","0"}; + CVAR_REGISTER ( &sk_islave_health2 );// {"sk_islave_health2","0"}; + CVAR_REGISTER ( &sk_islave_health3 );// {"sk_islave_health3","0"}; + + CVAR_REGISTER ( &sk_islave_dmg_claw1 );// {"sk_islave_dmg_claw1","0"}; + CVAR_REGISTER ( &sk_islave_dmg_claw2 );// {"sk_islave_dmg_claw2","0"}; + CVAR_REGISTER ( &sk_islave_dmg_claw3 );// {"sk_islave_dmg_claw3","0"}; + + CVAR_REGISTER ( &sk_islave_dmg_clawrake1 );// {"sk_islave_dmg_clawrake1","0"}; + CVAR_REGISTER ( &sk_islave_dmg_clawrake2 );// {"sk_islave_dmg_clawrake2","0"}; + CVAR_REGISTER ( &sk_islave_dmg_clawrake3 );// {"sk_islave_dmg_clawrake3","0"}; + + CVAR_REGISTER ( &sk_islave_dmg_zap1 );// {"sk_islave_dmg_zap1","0"}; + CVAR_REGISTER ( &sk_islave_dmg_zap2 );// {"sk_islave_dmg_zap2","0"}; + CVAR_REGISTER ( &sk_islave_dmg_zap3 );// {"sk_islave_dmg_zap3","0"}; + + + // Icthyosaur + CVAR_REGISTER ( &sk_ichthyosaur_health1 );// {"sk_ichthyosaur_health1","0"}; + CVAR_REGISTER ( &sk_ichthyosaur_health2 );// {"sk_ichthyosaur_health2","0"}; + CVAR_REGISTER ( &sk_ichthyosaur_health3 );// {"sk_ichthyosaur_health3","0"}; + + CVAR_REGISTER ( &sk_ichthyosaur_shake1 );// {"sk_ichthyosaur_health3","0"}; + CVAR_REGISTER ( &sk_ichthyosaur_shake2 );// {"sk_ichthyosaur_health3","0"}; + CVAR_REGISTER ( &sk_ichthyosaur_shake3 );// {"sk_ichthyosaur_health3","0"}; + + + + // Leech + CVAR_REGISTER ( &sk_leech_health1 );// {"sk_leech_health1","0"}; + CVAR_REGISTER ( &sk_leech_health2 );// {"sk_leech_health2","0"}; + CVAR_REGISTER ( &sk_leech_health3 );// {"sk_leech_health3","0"}; + + CVAR_REGISTER ( &sk_leech_dmg_bite1 );// {"sk_leech_dmg_bite1","0"}; + CVAR_REGISTER ( &sk_leech_dmg_bite2 );// {"sk_leech_dmg_bite2","0"}; + CVAR_REGISTER ( &sk_leech_dmg_bite3 );// {"sk_leech_dmg_bite3","0"}; + + + // Controller + CVAR_REGISTER ( &sk_controller_health1 ); + CVAR_REGISTER ( &sk_controller_health2 ); + CVAR_REGISTER ( &sk_controller_health3 ); + + CVAR_REGISTER ( &sk_controller_dmgzap1 ); + CVAR_REGISTER ( &sk_controller_dmgzap2 ); + CVAR_REGISTER ( &sk_controller_dmgzap3 ); + + CVAR_REGISTER ( &sk_controller_speedball1 ); + CVAR_REGISTER ( &sk_controller_speedball2 ); + CVAR_REGISTER ( &sk_controller_speedball3 ); + + CVAR_REGISTER ( &sk_controller_dmgball1 ); + CVAR_REGISTER ( &sk_controller_dmgball2 ); + CVAR_REGISTER ( &sk_controller_dmgball3 ); + + // Nihilanth + CVAR_REGISTER ( &sk_nihilanth_health1 );// {"sk_nihilanth_health1","0"}; + CVAR_REGISTER ( &sk_nihilanth_health2 );// {"sk_nihilanth_health2","0"}; + CVAR_REGISTER ( &sk_nihilanth_health3 );// {"sk_nihilanth_health3","0"}; + + CVAR_REGISTER ( &sk_nihilanth_zap1 ); + CVAR_REGISTER ( &sk_nihilanth_zap2 ); + CVAR_REGISTER ( &sk_nihilanth_zap3 ); + + // Scientist + CVAR_REGISTER ( &sk_scientist_health1 );// {"sk_scientist_health1","0"}; + CVAR_REGISTER ( &sk_scientist_health2 );// {"sk_scientist_health2","0"}; + CVAR_REGISTER ( &sk_scientist_health3 );// {"sk_scientist_health3","0"}; + + + // Snark + CVAR_REGISTER ( &sk_snark_health1 );// {"sk_snark_health1","0"}; + CVAR_REGISTER ( &sk_snark_health2 );// {"sk_snark_health2","0"}; + CVAR_REGISTER ( &sk_snark_health3 );// {"sk_snark_health3","0"}; + + CVAR_REGISTER ( &sk_snark_dmg_bite1 );// {"sk_snark_dmg_bite1","0"}; + CVAR_REGISTER ( &sk_snark_dmg_bite2 );// {"sk_snark_dmg_bite2","0"}; + CVAR_REGISTER ( &sk_snark_dmg_bite3 );// {"sk_snark_dmg_bite3","0"}; + + CVAR_REGISTER ( &sk_snark_dmg_pop1 );// {"sk_snark_dmg_pop1","0"}; + CVAR_REGISTER ( &sk_snark_dmg_pop2 );// {"sk_snark_dmg_pop2","0"}; + CVAR_REGISTER ( &sk_snark_dmg_pop3 );// {"sk_snark_dmg_pop3","0"}; + + + + // Zombie + CVAR_REGISTER ( &sk_zombie_health1 );// {"sk_zombie_health1","0"}; + CVAR_REGISTER ( &sk_zombie_health2 );// {"sk_zombie_health3","0"}; + CVAR_REGISTER ( &sk_zombie_health3 );// {"sk_zombie_health3","0"}; + + CVAR_REGISTER ( &sk_zombie_dmg_one_slash1 );// {"sk_zombie_dmg_one_slash1","0"}; + CVAR_REGISTER ( &sk_zombie_dmg_one_slash2 );// {"sk_zombie_dmg_one_slash2","0"}; + CVAR_REGISTER ( &sk_zombie_dmg_one_slash3 );// {"sk_zombie_dmg_one_slash3","0"}; + + CVAR_REGISTER ( &sk_zombie_dmg_both_slash1 );// {"sk_zombie_dmg_both_slash1","0"}; + CVAR_REGISTER ( &sk_zombie_dmg_both_slash2 );// {"sk_zombie_dmg_both_slash2","0"}; + CVAR_REGISTER ( &sk_zombie_dmg_both_slash3 );// {"sk_zombie_dmg_both_slash3","0"}; + + + //Turret + CVAR_REGISTER ( &sk_turret_health1 );// {"sk_turret_health1","0"}; + CVAR_REGISTER ( &sk_turret_health2 );// {"sk_turret_health2","0"}; + CVAR_REGISTER ( &sk_turret_health3 );// {"sk_turret_health3","0"}; + + + // MiniTurret + CVAR_REGISTER ( &sk_miniturret_health1 );// {"sk_miniturret_health1","0"}; + CVAR_REGISTER ( &sk_miniturret_health2 );// {"sk_miniturret_health2","0"}; + CVAR_REGISTER ( &sk_miniturret_health3 );// {"sk_miniturret_health3","0"}; + + + // Sentry Turret + CVAR_REGISTER ( &sk_sentry_health1 );// {"sk_sentry_health1","0"}; + CVAR_REGISTER ( &sk_sentry_health2 );// {"sk_sentry_health2","0"}; + CVAR_REGISTER ( &sk_sentry_health3 );// {"sk_sentry_health3","0"}; + + + // PLAYER WEAPONS + + // Crowbar whack + CVAR_REGISTER ( &sk_plr_crowbar1 );// {"sk_plr_crowbar1","0"}; + CVAR_REGISTER ( &sk_plr_crowbar2 );// {"sk_plr_crowbar2","0"}; + CVAR_REGISTER ( &sk_plr_crowbar3 );// {"sk_plr_crowbar3","0"}; + + // Glock Round + CVAR_REGISTER ( &sk_plr_9mm_bullet1 );// {"sk_plr_9mm_bullet1","0"}; + CVAR_REGISTER ( &sk_plr_9mm_bullet2 );// {"sk_plr_9mm_bullet2","0"}; + CVAR_REGISTER ( &sk_plr_9mm_bullet3 );// {"sk_plr_9mm_bullet3","0"}; + + // 357 Round + CVAR_REGISTER ( &sk_plr_357_bullet1 );// {"sk_plr_357_bullet1","0"}; + CVAR_REGISTER ( &sk_plr_357_bullet2 );// {"sk_plr_357_bullet2","0"}; + CVAR_REGISTER ( &sk_plr_357_bullet3 );// {"sk_plr_357_bullet3","0"}; + + // MP5 Round + CVAR_REGISTER ( &sk_plr_9mmAR_bullet1 );// {"sk_plr_9mmAR_bullet1","0"}; + CVAR_REGISTER ( &sk_plr_9mmAR_bullet2 );// {"sk_plr_9mmAR_bullet2","0"}; + CVAR_REGISTER ( &sk_plr_9mmAR_bullet3 );// {"sk_plr_9mmAR_bullet3","0"}; + + + // M203 grenade + CVAR_REGISTER ( &sk_plr_9mmAR_grenade1 );// {"sk_plr_9mmAR_grenade1","0"}; + CVAR_REGISTER ( &sk_plr_9mmAR_grenade2 );// {"sk_plr_9mmAR_grenade2","0"}; + CVAR_REGISTER ( &sk_plr_9mmAR_grenade3 );// {"sk_plr_9mmAR_grenade3","0"}; + + + // Shotgun buckshot + CVAR_REGISTER ( &sk_plr_buckshot1 );// {"sk_plr_buckshot1","0"}; + CVAR_REGISTER ( &sk_plr_buckshot2 );// {"sk_plr_buckshot2","0"}; + CVAR_REGISTER ( &sk_plr_buckshot3 );// {"sk_plr_buckshot3","0"}; + + + // Crossbow + CVAR_REGISTER ( &sk_plr_xbow_bolt_monster1 );// {"sk_plr_xbow_bolt1","0"}; + CVAR_REGISTER ( &sk_plr_xbow_bolt_monster2 );// {"sk_plr_xbow_bolt2","0"}; + CVAR_REGISTER ( &sk_plr_xbow_bolt_monster3 );// {"sk_plr_xbow_bolt3","0"}; + + CVAR_REGISTER ( &sk_plr_xbow_bolt_client1 );// {"sk_plr_xbow_bolt1","0"}; + CVAR_REGISTER ( &sk_plr_xbow_bolt_client2 );// {"sk_plr_xbow_bolt2","0"}; + CVAR_REGISTER ( &sk_plr_xbow_bolt_client3 );// {"sk_plr_xbow_bolt3","0"}; + + + // RPG + CVAR_REGISTER ( &sk_plr_rpg1 );// {"sk_plr_rpg1","0"}; + CVAR_REGISTER ( &sk_plr_rpg2 );// {"sk_plr_rpg2","0"}; + CVAR_REGISTER ( &sk_plr_rpg3 );// {"sk_plr_rpg3","0"}; + + + // Gauss Gun + CVAR_REGISTER ( &sk_plr_gauss1 );// {"sk_plr_gauss1","0"}; + CVAR_REGISTER ( &sk_plr_gauss2 );// {"sk_plr_gauss2","0"}; + CVAR_REGISTER ( &sk_plr_gauss3 );// {"sk_plr_gauss3","0"}; + + + // Egon Gun + CVAR_REGISTER ( &sk_plr_egon_narrow1 );// {"sk_plr_egon_narrow1","0"}; + CVAR_REGISTER ( &sk_plr_egon_narrow2 );// {"sk_plr_egon_narrow2","0"}; + CVAR_REGISTER ( &sk_plr_egon_narrow3 );// {"sk_plr_egon_narrow3","0"}; + + CVAR_REGISTER ( &sk_plr_egon_wide1 );// {"sk_plr_egon_wide1","0"}; + CVAR_REGISTER ( &sk_plr_egon_wide2 );// {"sk_plr_egon_wide2","0"}; + CVAR_REGISTER ( &sk_plr_egon_wide3 );// {"sk_plr_egon_wide3","0"}; + + + // Hand Grendade + CVAR_REGISTER ( &sk_plr_hand_grenade1 );// {"sk_plr_hand_grenade1","0"}; + CVAR_REGISTER ( &sk_plr_hand_grenade2 );// {"sk_plr_hand_grenade2","0"}; + CVAR_REGISTER ( &sk_plr_hand_grenade3 );// {"sk_plr_hand_grenade3","0"}; + + + // Satchel Charge + CVAR_REGISTER ( &sk_plr_satchel1 );// {"sk_plr_satchel1","0"}; + CVAR_REGISTER ( &sk_plr_satchel2 );// {"sk_plr_satchel2","0"}; + CVAR_REGISTER ( &sk_plr_satchel3 );// {"sk_plr_satchel3","0"}; + + + // Tripmine + CVAR_REGISTER ( &sk_plr_tripmine1 );// {"sk_plr_tripmine1","0"}; + CVAR_REGISTER ( &sk_plr_tripmine2 );// {"sk_plr_tripmine2","0"}; + CVAR_REGISTER ( &sk_plr_tripmine3 );// {"sk_plr_tripmine3","0"}; + + + // WORLD WEAPONS + CVAR_REGISTER ( &sk_12mm_bullet1 );// {"sk_12mm_bullet1","0"}; + CVAR_REGISTER ( &sk_12mm_bullet2 );// {"sk_12mm_bullet2","0"}; + CVAR_REGISTER ( &sk_12mm_bullet3 );// {"sk_12mm_bullet3","0"}; + + CVAR_REGISTER ( &sk_9mmAR_bullet1 );// {"sk_9mm_bullet1","0"}; + CVAR_REGISTER ( &sk_9mmAR_bullet2 );// {"sk_9mm_bullet1","0"}; + CVAR_REGISTER ( &sk_9mmAR_bullet3 );// {"sk_9mm_bullet1","0"}; + + CVAR_REGISTER ( &sk_9mm_bullet1 );// {"sk_9mm_bullet1","0"}; + CVAR_REGISTER ( &sk_9mm_bullet2 );// {"sk_9mm_bullet2","0"}; + CVAR_REGISTER ( &sk_9mm_bullet3 );// {"sk_9mm_bullet3","0"}; + + + // HORNET + CVAR_REGISTER ( &sk_hornet_dmg1 );// {"sk_hornet_dmg1","0"}; + CVAR_REGISTER ( &sk_hornet_dmg2 );// {"sk_hornet_dmg2","0"}; + CVAR_REGISTER ( &sk_hornet_dmg3 );// {"sk_hornet_dmg3","0"}; + + // HEALTH/SUIT CHARGE DISTRIBUTION + CVAR_REGISTER ( &sk_suitcharger1 ); + CVAR_REGISTER ( &sk_suitcharger2 ); + CVAR_REGISTER ( &sk_suitcharger3 ); + + CVAR_REGISTER ( &sk_battery1 ); + CVAR_REGISTER ( &sk_battery2 ); + CVAR_REGISTER ( &sk_battery3 ); + + CVAR_REGISTER ( &sk_healthcharger1 ); + CVAR_REGISTER ( &sk_healthcharger2 ); + CVAR_REGISTER ( &sk_healthcharger3 ); + + CVAR_REGISTER ( &sk_healthkit1 ); + CVAR_REGISTER ( &sk_healthkit2 ); + CVAR_REGISTER ( &sk_healthkit3 ); + + CVAR_REGISTER ( &sk_scientist_heal1 ); + CVAR_REGISTER ( &sk_scientist_heal2 ); + CVAR_REGISTER ( &sk_scientist_heal3 ); + +// monster damage adjusters + CVAR_REGISTER ( &sk_monster_head1 ); + CVAR_REGISTER ( &sk_monster_head2 ); + CVAR_REGISTER ( &sk_monster_head3 ); + + CVAR_REGISTER ( &sk_monster_chest1 ); + CVAR_REGISTER ( &sk_monster_chest2 ); + CVAR_REGISTER ( &sk_monster_chest3 ); + + CVAR_REGISTER ( &sk_monster_stomach1 ); + CVAR_REGISTER ( &sk_monster_stomach2 ); + CVAR_REGISTER ( &sk_monster_stomach3 ); + + CVAR_REGISTER ( &sk_monster_arm1 ); + CVAR_REGISTER ( &sk_monster_arm2 ); + CVAR_REGISTER ( &sk_monster_arm3 ); + + CVAR_REGISTER ( &sk_monster_leg1 ); + CVAR_REGISTER ( &sk_monster_leg2 ); + CVAR_REGISTER ( &sk_monster_leg3 ); + +// player damage adjusters + CVAR_REGISTER ( &sk_player_head1 ); + CVAR_REGISTER ( &sk_player_head2 ); + CVAR_REGISTER ( &sk_player_head3 ); + + CVAR_REGISTER ( &sk_player_chest1 ); + CVAR_REGISTER ( &sk_player_chest2 ); + CVAR_REGISTER ( &sk_player_chest3 ); + + CVAR_REGISTER ( &sk_player_stomach1 ); + CVAR_REGISTER ( &sk_player_stomach2 ); + CVAR_REGISTER ( &sk_player_stomach3 ); + + CVAR_REGISTER ( &sk_player_arm1 ); + CVAR_REGISTER ( &sk_player_arm2 ); + CVAR_REGISTER ( &sk_player_arm3 ); + + CVAR_REGISTER ( &sk_player_leg1 ); + CVAR_REGISTER ( &sk_player_leg2 ); + CVAR_REGISTER ( &sk_player_leg3 ); +// END REGISTER CVARS FOR SKILL LEVEL STUFF + + SERVER_COMMAND( "exec skill.cfg\n" ); +} + +void GameDLLShutdown() +{ +} diff --git a/ricochet/dlls/game.h b/ricochet/dlls/game.h new file mode 100644 index 0000000..9597833 --- /dev/null +++ b/ricochet/dlls/game.h @@ -0,0 +1,44 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef GAME_H +#define GAME_H + +extern void GameDLLInit( void ); +extern void GameDLLShutdown( void ); + +extern cvar_t displaysoundlist; + +// multiplayer server rules +extern cvar_t fraglimit; +extern cvar_t timelimit; +extern cvar_t friendlyfir; +extern cvar_t falldamage; +extern cvar_t weaponstay; +extern cvar_t forcerespaw; +extern cvar_t flashlight; +extern cvar_t aimcrosshair; +extern cvar_t decalfrequency; +extern cvar_t teamlist; +extern cvar_t teamoverride; +extern cvar_t defaultteam; +extern cvar_t allow_spectators; + +// Engine Cvars +extern cvar_t *g_psv_gravity; +extern cvar_t *g_psv_aim; +extern cvar_t *g_footsteps; + +#endif // GAME_H diff --git a/ricochet/dlls/gamerules.cpp b/ricochet/dlls/gamerules.cpp new file mode 100644 index 0000000..eb10332 --- /dev/null +++ b/ricochet/dlls/gamerules.cpp @@ -0,0 +1,328 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// GameRules.cpp +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "teamplay_gamerules.h" +#include "skill.h" + +extern edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ); + +DLL_GLOBAL CGameRules* g_pGameRules = NULL; +extern DLL_GLOBAL BOOL g_fGameOver; +extern int gmsgDeathMsg; // client dll messages +extern int gmsgScoreInfo; +extern int gmsgMOTD; + +//========================================================= +//========================================================= +BOOL CGameRules::CanHaveAmmo( CBasePlayer *pPlayer, const char *pszAmmoName, int iMaxCarry ) +{ + int iAmmoIndex; + + if ( pszAmmoName ) + { + iAmmoIndex = pPlayer->GetAmmoIndex( pszAmmoName ); + + if ( iAmmoIndex > -1 ) + { + if ( pPlayer->AmmoInventory( iAmmoIndex ) < iMaxCarry ) + { + // player has room for more of this type of ammo + return TRUE; + } + } + } + + return FALSE; +} + +//========================================================= +//========================================================= +edict_t *CGameRules :: GetPlayerSpawnSpot( CBasePlayer *pPlayer ) +{ + edict_t *pentSpawnSpot = EntSelectSpawnPoint( pPlayer ); + + pPlayer->pev->origin = VARS(pentSpawnSpot)->origin + Vector(0,0,1); + pPlayer->pev->v_angle = g_vecZero; + pPlayer->pev->velocity = g_vecZero; + pPlayer->pev->angles = VARS(pentSpawnSpot)->angles; + pPlayer->pev->punchangle = g_vecZero; + pPlayer->pev->fixangle = TRUE; + + return pentSpawnSpot; +} + +//========================================================= +//========================================================= +BOOL CGameRules::CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ + // only living players can have items + if ( pPlayer->pev->deadflag != DEAD_NO ) + return FALSE; + + if ( pWeapon->pszAmmo1() ) + { + if ( !CanHaveAmmo( pPlayer, pWeapon->pszAmmo1(), pWeapon->iMaxAmmo1() ) ) + { + // we can't carry anymore ammo for this gun. We can only + // have the gun if we aren't already carrying one of this type + if ( pPlayer->HasPlayerItem( pWeapon ) ) + { + return FALSE; + } + } + } + else + { + // weapon doesn't use ammo, don't take another if you already have it. + if ( pPlayer->HasPlayerItem( pWeapon ) ) + { + return FALSE; + } + } + + // note: will fall through to here if GetItemInfo doesn't fill the struct! + return TRUE; +} + +//========================================================= +// load the SkillData struct with the proper values based on the skill level. +//========================================================= +void CGameRules::RefreshSkillData ( void ) +{ + int iSkill; + + iSkill = (int)CVAR_GET_FLOAT("skill"); + + if ( iSkill < 1 ) + { + iSkill = 1; + } + else if ( iSkill > 3 ) + { + iSkill = 3; + } + + gSkillData.iSkillLevel = iSkill; + + ALERT ( at_console, "\nGAME SKILL LEVEL:%d\n",iSkill ); + + //Agrunt + gSkillData.agruntHealth = GetSkillCvar( "sk_agrunt_health" ); + gSkillData.agruntDmgPunch = GetSkillCvar( "sk_agrunt_dmg_punch"); + + // Apache + gSkillData.apacheHealth = GetSkillCvar( "sk_apache_health"); + + // Barney + gSkillData.barneyHealth = GetSkillCvar( "sk_barney_health"); + + // Big Momma + gSkillData.bigmommaHealthFactor = GetSkillCvar( "sk_bigmomma_health_factor" ); + gSkillData.bigmommaDmgSlash = GetSkillCvar( "sk_bigmomma_dmg_slash" ); + gSkillData.bigmommaDmgBlast = GetSkillCvar( "sk_bigmomma_dmg_blast" ); + gSkillData.bigmommaRadiusBlast = GetSkillCvar( "sk_bigmomma_radius_blast" ); + + // Bullsquid + gSkillData.bullsquidHealth = GetSkillCvar( "sk_bullsquid_health"); + gSkillData.bullsquidDmgBite = GetSkillCvar( "sk_bullsquid_dmg_bite"); + gSkillData.bullsquidDmgWhip = GetSkillCvar( "sk_bullsquid_dmg_whip"); + gSkillData.bullsquidDmgSpit = GetSkillCvar( "sk_bullsquid_dmg_spit"); + + // Gargantua + gSkillData.gargantuaHealth = GetSkillCvar( "sk_gargantua_health"); + gSkillData.gargantuaDmgSlash = GetSkillCvar( "sk_gargantua_dmg_slash"); + gSkillData.gargantuaDmgFire = GetSkillCvar( "sk_gargantua_dmg_fire"); + gSkillData.gargantuaDmgStomp = GetSkillCvar( "sk_gargantua_dmg_stomp"); + + // Hassassin + gSkillData.hassassinHealth = GetSkillCvar( "sk_hassassin_health"); + + // Headcrab + gSkillData.headcrabHealth = GetSkillCvar( "sk_headcrab_health"); + gSkillData.headcrabDmgBite = GetSkillCvar( "sk_headcrab_dmg_bite"); + + // Hgrunt + gSkillData.hgruntHealth = GetSkillCvar( "sk_hgrunt_health"); + gSkillData.hgruntDmgKick = GetSkillCvar( "sk_hgrunt_kick"); + gSkillData.hgruntShotgunPellets = GetSkillCvar( "sk_hgrunt_pellets"); + gSkillData.hgruntGrenadeSpeed = GetSkillCvar( "sk_hgrunt_gspeed"); + + // Houndeye + gSkillData.houndeyeHealth = GetSkillCvar( "sk_houndeye_health"); + gSkillData.houndeyeDmgBlast = GetSkillCvar( "sk_houndeye_dmg_blast"); + + // ISlave + gSkillData.slaveHealth = GetSkillCvar( "sk_islave_health"); + gSkillData.slaveDmgClaw = GetSkillCvar( "sk_islave_dmg_claw"); + gSkillData.slaveDmgClawrake = GetSkillCvar( "sk_islave_dmg_clawrake"); + gSkillData.slaveDmgZap = GetSkillCvar( "sk_islave_dmg_zap"); + + // Icthyosaur + gSkillData.ichthyosaurHealth = GetSkillCvar( "sk_ichthyosaur_health"); + gSkillData.ichthyosaurDmgShake = GetSkillCvar( "sk_ichthyosaur_shake"); + + // Leech + gSkillData.leechHealth = GetSkillCvar( "sk_leech_health"); + + gSkillData.leechDmgBite = GetSkillCvar( "sk_leech_dmg_bite"); + + // Controller + gSkillData.controllerHealth = GetSkillCvar( "sk_controller_health"); + gSkillData.controllerDmgZap = GetSkillCvar( "sk_controller_dmgzap"); + gSkillData.controllerSpeedBall = GetSkillCvar( "sk_controller_speedball"); + gSkillData.controllerDmgBall = GetSkillCvar( "sk_controller_dmgball"); + + // Nihilanth + gSkillData.nihilanthHealth = GetSkillCvar( "sk_nihilanth_health"); + gSkillData.nihilanthZap = GetSkillCvar( "sk_nihilanth_zap"); + + // Scientist + gSkillData.scientistHealth = GetSkillCvar( "sk_scientist_health"); + + // Snark + gSkillData.snarkHealth = GetSkillCvar( "sk_snark_health"); + gSkillData.snarkDmgBite = GetSkillCvar( "sk_snark_dmg_bite"); + gSkillData.snarkDmgPop = GetSkillCvar( "sk_snark_dmg_pop"); + + // Zombie + gSkillData.zombieHealth = GetSkillCvar( "sk_zombie_health"); + gSkillData.zombieDmgOneSlash = GetSkillCvar( "sk_zombie_dmg_one_slash"); + gSkillData.zombieDmgBothSlash = GetSkillCvar( "sk_zombie_dmg_both_slash"); + + //Turret + gSkillData.turretHealth = GetSkillCvar( "sk_turret_health"); + + // MiniTurret + gSkillData.miniturretHealth = GetSkillCvar( "sk_miniturret_health"); + + // Sentry Turret + gSkillData.sentryHealth = GetSkillCvar( "sk_sentry_health"); + +// PLAYER WEAPONS + + // Crowbar whack + gSkillData.plrDmgCrowbar = GetSkillCvar( "sk_plr_crowbar"); + + // Glock Round + gSkillData.plrDmg9MM = GetSkillCvar( "sk_plr_9mm_bullet"); + + // 357 Round + gSkillData.plrDmg357 = GetSkillCvar( "sk_plr_357_bullet"); + + // MP5 Round + gSkillData.plrDmgMP5 = GetSkillCvar( "sk_plr_9mmAR_bullet"); + + // M203 grenade + gSkillData.plrDmgM203Grenade = GetSkillCvar( "sk_plr_9mmAR_grenade"); + + // Shotgun buckshot + gSkillData.plrDmgBuckshot = GetSkillCvar( "sk_plr_buckshot"); + + // Crossbow + gSkillData.plrDmgCrossbowClient = GetSkillCvar( "sk_plr_xbow_bolt_client"); + gSkillData.plrDmgCrossbowMonster = GetSkillCvar( "sk_plr_xbow_bolt_monster"); + + // RPG + gSkillData.plrDmgRPG = GetSkillCvar( "sk_plr_rpg"); + + // Gauss gun + gSkillData.plrDmgGauss = GetSkillCvar( "sk_plr_gauss"); + + // Egon Gun + gSkillData.plrDmgEgonNarrow = GetSkillCvar( "sk_plr_egon_narrow"); + gSkillData.plrDmgEgonWide = GetSkillCvar( "sk_plr_egon_wide"); + + // Hand Grendade + gSkillData.plrDmgHandGrenade = GetSkillCvar( "sk_plr_hand_grenade"); + + // Satchel Charge + gSkillData.plrDmgSatchel = GetSkillCvar( "sk_plr_satchel"); + + // Tripmine + gSkillData.plrDmgTripmine = GetSkillCvar( "sk_plr_tripmine"); + + // MONSTER WEAPONS + gSkillData.monDmg12MM = GetSkillCvar( "sk_12mm_bullet"); + gSkillData.monDmgMP5 = GetSkillCvar ("sk_9mmAR_bullet" ); + gSkillData.monDmg9MM = GetSkillCvar( "sk_9mm_bullet"); + + // MONSTER HORNET + gSkillData.monDmgHornet = GetSkillCvar( "sk_hornet_dmg"); + + // PLAYER HORNET +// Up to this point, player hornet damage and monster hornet damage were both using +// monDmgHornet to determine how much damage to do. In tuning the hivehand, we now need +// to separate player damage and monster hivehand damage. Since it's so late in the project, we've +// added plrDmgHornet to the SKILLDATA struct, but not to the engine CVar list, so it's inaccesible +// via SKILLS.CFG. Any player hivehand tuning must take place in the code. (sjb) + gSkillData.plrDmgHornet = 7; + + + // HEALTH/CHARGE + gSkillData.suitchargerCapacity = GetSkillCvar( "sk_suitcharger" ); + gSkillData.batteryCapacity = GetSkillCvar( "sk_battery" ); + gSkillData.healthchargerCapacity = GetSkillCvar ( "sk_healthcharger" ); + gSkillData.healthkitCapacity = GetSkillCvar ( "sk_healthkit" ); + gSkillData.scientistHeal = GetSkillCvar ( "sk_scientist_heal" ); + + // monster damage adj + gSkillData.monHead = GetSkillCvar( "sk_monster_head" ); + gSkillData.monChest = GetSkillCvar( "sk_monster_chest" ); + gSkillData.monStomach = GetSkillCvar( "sk_monster_stomach" ); + gSkillData.monLeg = GetSkillCvar( "sk_monster_leg" ); + gSkillData.monArm = GetSkillCvar( "sk_monster_arm" ); + + // player damage adj + gSkillData.plrHead = GetSkillCvar( "sk_player_head" ); + gSkillData.plrChest = GetSkillCvar( "sk_player_chest" ); + gSkillData.plrStomach = GetSkillCvar( "sk_player_stomach" ); + gSkillData.plrLeg = GetSkillCvar( "sk_player_leg" ); + gSkillData.plrArm = GetSkillCvar( "sk_player_arm" ); +} + +//========================================================= +// instantiate the proper game rules object +//========================================================= + +CGameRules *InstallGameRules( void ) +{ + SERVER_COMMAND( "exec game.cfg\n" ); + SERVER_EXECUTE( ); + + if ( !gpGlobals->deathmatch ) + { + // generic half-life + return new CHalfLifeRules; + } + else + { + // Discwar's always in teamplay mode. + //CVAR_SET_FLOAT( "mp_teamplay", 1 ); + //return new CHalfLifeTeamplay; + return new CHalfLifeMultiplay; + } +} + + + diff --git a/ricochet/dlls/gamerules.h b/ricochet/dlls/gamerules.h new file mode 100644 index 0000000..1ebdd41 --- /dev/null +++ b/ricochet/dlls/gamerules.h @@ -0,0 +1,360 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// GameRules +//========================================================= + +//#include "weapons.h" +//#include "items.h" +class CBasePlayerItem; +class CBasePlayer; +class CItem; +class CBasePlayerAmmo; + +// weapon respawning return codes +enum +{ + GR_NONE = 0, + + GR_WEAPON_RESPAWN_YES, + GR_WEAPON_RESPAWN_NO, + + GR_AMMO_RESPAWN_YES, + GR_AMMO_RESPAWN_NO, + + GR_ITEM_RESPAWN_YES, + GR_ITEM_RESPAWN_NO, + + GR_PLR_DROP_GUN_ALL, + GR_PLR_DROP_GUN_ACTIVE, + GR_PLR_DROP_GUN_NO, + + GR_PLR_DROP_AMMO_ALL, + GR_PLR_DROP_AMMO_ACTIVE, + GR_PLR_DROP_AMMO_NO, +}; + +// Player relationship return codes +enum +{ + GR_NOTTEAMMATE = 0, + GR_TEAMMATE, + GR_ENEMY, + GR_ALLY, + GR_NEUTRAL, +}; + +class CGameRules +{ +public: + virtual void RefreshSkillData( void );// fill skill data struct with proper values + virtual void Think( void ) = 0;// GR_Think - runs every server frame, should handle any timer tasks, periodic events, etc. + virtual BOOL IsAllowedToSpawn( CBaseEntity *pEntity ) = 0; // Can this item spawn (eg monsters don't spawn in deathmatch). + + virtual BOOL FAllowFlashlight( void ) = 0;// Are players allowed to switch on their flashlight? + virtual BOOL FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) = 0;// should the player switch to this weapon? + virtual BOOL GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) = 0;// I can't use this weapon anymore, get me the next best one. + +// Functions to verify the single/multiplayer status of a game + virtual BOOL IsMultiplayer( void ) = 0;// is this a multiplayer game? (either coop or deathmatch) + virtual BOOL IsDeathmatch( void ) = 0;//is this a deathmatch game? + virtual BOOL IsTeamplay( void ) { return FALSE; };// is this deathmatch game being played with team rules? + virtual BOOL IsCoOp( void ) = 0;// is this a coop game? + virtual const char *GetGameDescription( void ) { return "Ricochet"; } // this is the game name that gets seen in the server browser + +// Client connection/disconnection + virtual BOOL ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) = 0;// a client just connected to the server (player hasn't spawned yet) + virtual void InitHUD( CBasePlayer *pl ) = 0; // the client dll is ready for updating + virtual void ClientDisconnected( edict_t *pClient ) = 0;// a client just disconnected from the server + virtual void UpdateGameMode( CBasePlayer *pPlayer ) {} // the client needs to be informed of the current game mode + +// Client damage rules + virtual float FlPlayerFallDamage( CBasePlayer *pPlayer ) = 0;// this client just hit the ground after a fall. How much damage? + virtual BOOL FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ) {return TRUE;};// can this player take damage from this attacker? + virtual BOOL ShouldAutoAim( CBasePlayer *pPlayer, edict_t *target ) { return TRUE; } + +// Client spawn/respawn control + virtual void PlayerSpawn( CBasePlayer *pPlayer ) = 0;// called by CBasePlayer::Spawn just before releasing player into the game + virtual void PlayerThink( CBasePlayer *pPlayer ) = 0; // called by CBasePlayer::PreThink every frame, before physics are run and after keys are accepted + virtual BOOL FPlayerCanRespawn( CBasePlayer *pPlayer ) = 0;// is this player allowed to respawn now? + virtual float FlPlayerSpawnTime( CBasePlayer *pPlayer ) = 0;// When in the future will this player be able to spawn? + virtual edict_t *GetPlayerSpawnSpot( CBasePlayer *pPlayer );// Place this player on their spawnspot and face them the proper direction. + + virtual BOOL AllowAutoTargetCrosshair( void ) { return TRUE; }; + virtual BOOL ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) { return FALSE; }; // handles the user commands; returns TRUE if command handled properly + virtual void ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ) {} // the player has changed userinfo; can change it now + +// Client kills/scoring + virtual int IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) = 0;// how many points do I award whoever kills this player? + virtual void PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) = 0;// Called each time a player dies + virtual void DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor )= 0;// Call this from within a GameRules class to report an obituary. +// Weapon retrieval + virtual BOOL CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon );// The player is touching an CBasePlayerItem, do I give it to him? + virtual void PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) = 0;// Called each time a player picks up a weapon from the ground + +// Weapon spawn/respawn control + virtual int WeaponShouldRespawn( CBasePlayerItem *pWeapon ) = 0;// should this weapon respawn? + virtual float FlWeaponRespawnTime( CBasePlayerItem *pWeapon ) = 0;// when may this weapon respawn? + virtual float FlWeaponTryRespawn( CBasePlayerItem *pWeapon ) = 0; // can i respawn now, and if not, when should i try again? + virtual Vector VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ) = 0;// where in the world should this weapon respawn? + +// Item retrieval + virtual BOOL CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ) = 0;// is this player allowed to take this item? + virtual void PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ) = 0;// call each time a player picks up an item (battery, healthkit, longjump) + +// Item spawn/respawn control + virtual int ItemShouldRespawn( CItem *pItem ) = 0;// Should this item respawn? + virtual float FlItemRespawnTime( CItem *pItem ) = 0;// when may this item respawn? + virtual Vector VecItemRespawnSpot( CItem *pItem ) = 0;// where in the world should this item respawn? + +// Ammo retrieval + virtual BOOL CanHaveAmmo( CBasePlayer *pPlayer, const char *pszAmmoName, int iMaxCarry );// can this player take more of this ammo? + virtual void PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ) = 0;// called each time a player picks up some ammo in the world + +// Ammo spawn/respawn control + virtual int AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ) = 0;// should this ammo item respawn? + virtual float FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ) = 0;// when should this ammo item respawn? + virtual Vector VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ) = 0;// where in the world should this ammo item respawn? + // by default, everything spawns + +// Healthcharger respawn control + virtual float FlHealthChargerRechargeTime( void ) = 0;// how long until a depleted HealthCharger recharges itself? + virtual float FlHEVChargerRechargeTime( void ) { return 0; }// how long until a depleted HealthCharger recharges itself? + +// What happens to a dead player's weapons + virtual int DeadPlayerWeapons( CBasePlayer *pPlayer ) = 0;// what do I do with a player's weapons when he's killed? + +// What happens to a dead player's ammo + virtual int DeadPlayerAmmo( CBasePlayer *pPlayer ) = 0;// Do I drop ammo when the player dies? How much? + +// Teamplay stuff + virtual const char *GetTeamID( CBaseEntity *pEntity ) = 0;// what team is this entity on? + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) = 0;// What is the player's relationship with this entity? + virtual int GetTeamIndex( const char *pTeamName ) { return -1; } + virtual const char *GetIndexedTeamName( int teamIndex ) { return ""; } + virtual BOOL IsValidTeam( const char *pTeamName ) { return TRUE; } + virtual void ChangePlayerTeam( CBasePlayer *pPlayer, const char *pTeamName, BOOL bKill, BOOL bGib ) {} + virtual const char *SetDefaultPlayerTeam( CBasePlayer *pPlayer ) { return ""; } + +// Sounds + virtual BOOL PlayTextureSounds( void ) { return TRUE; } + virtual BOOL PlayFootstepSounds( CBasePlayer *pl, float fvol ) { return TRUE; } + +// Monsters + virtual BOOL FAllowMonsters( void ) = 0;//are monsters allowed + + // Immediately end a multiplayer game + virtual void EndMultiplayerGame( void ) {} +}; + +extern CGameRules *InstallGameRules( void ); + + +//========================================================= +// CHalfLifeRules - rules for the single player Half-Life +// game. +//========================================================= +class CHalfLifeRules : public CGameRules +{ +public: + CHalfLifeRules ( void ); + +// GR_Think + virtual void Think( void ); + virtual BOOL IsAllowedToSpawn( CBaseEntity *pEntity ); + virtual BOOL FAllowFlashlight( void ) { return TRUE; }; + + virtual BOOL FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + virtual BOOL GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ); + +// Functions to verify the single/multiplayer status of a game + virtual BOOL IsMultiplayer( void ); + virtual BOOL IsDeathmatch( void ); + virtual BOOL IsCoOp( void ); + +// Client connection/disconnection + virtual BOOL ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); + virtual void InitHUD( CBasePlayer *pl ); // the client dll is ready for updating + virtual void ClientDisconnected( edict_t *pClient ); + +// Client damage rules + virtual float FlPlayerFallDamage( CBasePlayer *pPlayer ); + +// Client spawn/respawn control + virtual void PlayerSpawn( CBasePlayer *pPlayer ); + virtual void PlayerThink( CBasePlayer *pPlayer ); + virtual BOOL FPlayerCanRespawn( CBasePlayer *pPlayer ); + virtual float FlPlayerSpawnTime( CBasePlayer *pPlayer ); + + virtual BOOL AllowAutoTargetCrosshair( void ); + +// Client kills/scoring + virtual int IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ); + virtual void PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); + virtual void DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); + +// Weapon retrieval + virtual void PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + +// Weapon spawn/respawn control + virtual int WeaponShouldRespawn( CBasePlayerItem *pWeapon ); + virtual float FlWeaponRespawnTime( CBasePlayerItem *pWeapon ); + virtual float FlWeaponTryRespawn( CBasePlayerItem *pWeapon ); + virtual Vector VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ); + +// Item retrieval + virtual BOOL CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ); + virtual void PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ); + +// Item spawn/respawn control + virtual int ItemShouldRespawn( CItem *pItem ); + virtual float FlItemRespawnTime( CItem *pItem ); + virtual Vector VecItemRespawnSpot( CItem *pItem ); + +// Ammo retrieval + virtual void PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ); + +// Ammo spawn/respawn control + virtual int AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ); + virtual float FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ); + virtual Vector VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ); + +// Healthcharger respawn control + virtual float FlHealthChargerRechargeTime( void ); + +// What happens to a dead player's weapons + virtual int DeadPlayerWeapons( CBasePlayer *pPlayer ); + +// What happens to a dead player's ammo + virtual int DeadPlayerAmmo( CBasePlayer *pPlayer ); + +// Monsters + virtual BOOL FAllowMonsters( void ); + +// Teamplay stuff + virtual const char *GetTeamID( CBaseEntity *pEntity ) {return "";}; + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); +}; + +//========================================================= +// CHalfLifeMultiplay - rules for the basic half life multiplayer +// competition +//========================================================= +class CHalfLifeMultiplay : public CGameRules +{ +public: + CHalfLifeMultiplay(); + +// GR_Think + virtual void Think( void ); + virtual void RefreshSkillData( void ); + virtual BOOL IsAllowedToSpawn( CBaseEntity *pEntity ); + virtual BOOL FAllowFlashlight( void ); + + virtual BOOL FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + virtual BOOL GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ); + +// Functions to verify the single/multiplayer status of a game + virtual BOOL IsMultiplayer( void ); + virtual BOOL IsDeathmatch( void ); + virtual BOOL IsCoOp( void ); + +// Client connection/disconnection + // If ClientConnected returns FALSE, the connection is rejected and the user is provided the reason specified in + // svRejectReason + // Only the client's name and remote address are provided to the dll for verification. + virtual BOOL ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); + virtual void InitHUD( CBasePlayer *pl ); // the client dll is ready for updating + virtual void ClientDisconnected( edict_t *pClient ); + virtual void UpdateGameMode( CBasePlayer *pPlayer ); // the client needs to be informed of the current game mode + +// Client damage rules + virtual float FlPlayerFallDamage( CBasePlayer *pPlayer ); + virtual BOOL FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ); + +// Client spawn/respawn control + virtual void PlayerSpawn( CBasePlayer *pPlayer ); + virtual void PlayerThink( CBasePlayer *pPlayer ); + virtual BOOL FPlayerCanRespawn( CBasePlayer *pPlayer ); + virtual float FlPlayerSpawnTime( CBasePlayer *pPlayer ); + virtual edict_t *GetPlayerSpawnSpot( CBasePlayer *pPlayer ); + + virtual BOOL AllowAutoTargetCrosshair( void ); + virtual BOOL ClientCommand( CBasePlayer *pPlayer, const char *pcmd ); + +// Client kills/scoring + virtual int IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ); + virtual void PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); + virtual void DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); + +// Weapon retrieval + virtual void PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + virtual BOOL CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon );// The player is touching an CBasePlayerItem, do I give it to him? + +// Weapon spawn/respawn control + virtual int WeaponShouldRespawn( CBasePlayerItem *pWeapon ); + virtual float FlWeaponRespawnTime( CBasePlayerItem *pWeapon ); + virtual float FlWeaponTryRespawn( CBasePlayerItem *pWeapon ); + virtual Vector VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ); + +// Item retrieval + virtual BOOL CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ); + virtual void PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ); + +// Item spawn/respawn control + virtual int ItemShouldRespawn( CItem *pItem ); + virtual float FlItemRespawnTime( CItem *pItem ); + virtual Vector VecItemRespawnSpot( CItem *pItem ); + +// Ammo retrieval + virtual void PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ); + +// Ammo spawn/respawn control + virtual int AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ); + virtual float FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ); + virtual Vector VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ); + +// Healthcharger respawn control + virtual float FlHealthChargerRechargeTime( void ); + virtual float FlHEVChargerRechargeTime( void ); + +// What happens to a dead player's weapons + virtual int DeadPlayerWeapons( CBasePlayer *pPlayer ); + +// What happens to a dead player's ammo + virtual int DeadPlayerAmmo( CBasePlayer *pPlayer ); + +// Teamplay stuff + virtual const char *GetTeamID( CBaseEntity *pEntity ) {return "";} + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); + + virtual BOOL PlayTextureSounds( void ) { return FALSE; } + virtual BOOL PlayFootstepSounds( CBasePlayer *pl, float fvol ); + +// Monsters + virtual BOOL FAllowMonsters( void ); + + // Immediately end a multiplayer game + virtual void EndMultiplayerGame( void ) { GoToIntermission(); } + +protected: + virtual void ChangeLevel( void ); + virtual void GoToIntermission( void ); + float m_flIntermissionEndTime; + BOOL m_iEndIntermissionButtonHit; + void SendMOTDToClient( edict_t *client ); +}; + +extern DLL_GLOBAL CGameRules* g_pGameRules; diff --git a/ricochet/dlls/ggrenade.cpp b/ricochet/dlls/ggrenade.cpp new file mode 100644 index 0000000..faa7204 --- /dev/null +++ b/ricochet/dlls/ggrenade.cpp @@ -0,0 +1,488 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== generic grenade.cpp ======================================================== + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "soundent.h" +#include "decals.h" + + +//===================grenade + + +LINK_ENTITY_TO_CLASS( grenade, CGrenade ); + +// Grenades flagged with this will be triggered when the owner calls detonateSatchelCharges +#define SF_DETONATE 0x0001 + +// +// Grenade Explode +// +void CGrenade::Explode( Vector vecSrc, Vector vecAim ) +{ + TraceResult tr; + UTIL_TraceLine ( pev->origin, pev->origin + Vector ( 0, 0, -32 ), ignore_monsters, ENT(pev), & tr); + + Explode( &tr, DMG_BLAST ); +} + +// UNDONE: temporary scorching for PreAlpha - find a less sleazy permenant solution. +void CGrenade::Explode( TraceResult *pTrace, int bitsDamageType ) +{ + float flRndSound;// sound randomizer + + pev->model = iStringNull;//invisible + pev->solid = SOLID_NOT;// intangible + + pev->takedamage = DAMAGE_NO; + + // Pull out of the wall a bit + if ( pTrace->flFraction != 1.0 ) + { + pev->origin = pTrace->vecEndPos + (pTrace->vecPlaneNormal * (pev->dmg - 24) * 0.6); + } + + int iContents = UTIL_PointContents ( pev->origin ); + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION ); // This makes a dynamic light and the explosion sprites/sound + WRITE_COORD( pev->origin.x ); // Send to PAS because of the sound + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + if (iContents != CONTENTS_WATER) + { + WRITE_SHORT( g_sModelIndexFireball ); + } + else + { + WRITE_SHORT( g_sModelIndexWExplosion ); + } + WRITE_BYTE( (pev->dmg - 50) * .60 ); // scale * 10 + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, NORMAL_EXPLOSION_VOLUME, 3.0 ); + entvars_t *pevOwner; + if ( pev->owner ) + pevOwner = VARS( pev->owner ); + else + pevOwner = NULL; + + pev->owner = NULL; // can't traceline attack owner if this is set + + RadiusDamage ( pev, pevOwner, pev->dmg, CLASS_NONE, bitsDamageType ); + + if ( RANDOM_FLOAT( 0 , 1 ) < 0.5 ) + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH1 ); + } + else + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH2 ); + } + + flRndSound = RANDOM_FLOAT( 0 , 1 ); + + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris1.wav", 0.55, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris2.wav", 0.55, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris3.wav", 0.55, ATTN_NORM); break; + } + + pev->effects |= EF_NODRAW; + SetThink( &CGrenade::Smoke ); + pev->velocity = g_vecZero; + pev->nextthink = gpGlobals->time + 0.3; + + if (iContents != CONTENTS_WATER) + { + int sparkCount = RANDOM_LONG(0,3); + for ( int i = 0; i < sparkCount; i++ ) + Create( "spark_shower", pev->origin, pTrace->vecPlaneNormal, NULL ); + } +} + + +void CGrenade::Smoke( void ) +{ + if (UTIL_PointContents ( pev->origin ) == CONTENTS_WATER) + { + UTIL_Bubbles( pev->origin - Vector( 64, 64, 64 ), pev->origin + Vector( 64, 64, 64 ), 100 ); + } + else + { + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( (pev->dmg - 50) * 0.80 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + } + UTIL_Remove( this ); +} + +void CGrenade::Killed( entvars_t *pevAttacker, int iGib ) +{ + Detonate( ); +} + + +// Timed grenade, this think is called when time runs out. +void CGrenade::DetonateUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( &CGrenade::Detonate ); + pev->nextthink = gpGlobals->time; +} + +void CGrenade::PreDetonate( void ) +{ + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin, 400, 0.3 ); + + SetThink( &CGrenade::Detonate ); + pev->nextthink = gpGlobals->time + 1; +} + + +void CGrenade::Detonate( void ) +{ + TraceResult tr; + Vector vecSpot;// trace starts here! + + vecSpot = pev->origin + Vector ( 0 , 0 , 8 ); + UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -40 ), ignore_monsters, ENT(pev), & tr); + + Explode( &tr, DMG_BLAST ); +} + + +// +// Contact grenade, explode when it touches something +// +void CGrenade::ExplodeTouch( CBaseEntity *pOther ) +{ + TraceResult tr; + Vector vecSpot;// trace starts here! + + pev->enemy = pOther->edict(); + + vecSpot = pev->origin - pev->velocity.Normalize() * 32; + UTIL_TraceLine( vecSpot, vecSpot + pev->velocity.Normalize() * 64, ignore_monsters, ENT(pev), &tr ); + + Explode( &tr, DMG_BLAST ); +} + + +void CGrenade::DangerSoundThink( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin + pev->velocity * 0.5, pev->velocity.Length( ), 0.2 ); + pev->nextthink = gpGlobals->time + 0.2; + + if (pev->waterlevel != 0) + { + pev->velocity = pev->velocity * 0.5; + } +} + + +void CGrenade::BounceTouch( CBaseEntity *pOther ) +{ + // don't hit the guy that launched this grenade + if ( pOther->edict() == pev->owner ) + return; + + // only do damage if we're moving fairly fast + if (m_flNextAttack < gpGlobals->time && pev->velocity.Length() > 100) + { + entvars_t *pevOwner = VARS( pev->owner ); + if (pevOwner) + { + TraceResult tr = UTIL_GetGlobalTrace( ); + ClearMultiDamage( ); + pOther->TraceAttack(pevOwner, 1, gpGlobals->v_forward, &tr, DMG_CLUB ); + ApplyMultiDamage( pev, pevOwner); + } + m_flNextAttack = gpGlobals->time + 1.0; // debounce + } + + Vector vecTestVelocity; + // pev->avelocity = Vector (300, 300, 300); + + // this is my heuristic for modulating the grenade velocity because grenades dropped purely vertical + // or thrown very far tend to slow down too quickly for me to always catch just by testing velocity. + // trimming the Z velocity a bit seems to help quite a bit. + vecTestVelocity = pev->velocity; + vecTestVelocity.z *= 0.45; + + if ( !m_fRegisteredSound && vecTestVelocity.Length() <= 60 ) + { + //ALERT( at_console, "Grenade Registered!: %f\n", vecTestVelocity.Length() ); + + // grenade is moving really slow. It's probably very close to where it will ultimately stop moving. + // go ahead and emit the danger sound. + + // register a radius louder than the explosion, so we make sure everyone gets out of the way + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin, pev->dmg / 0.4, 0.3 ); + m_fRegisteredSound = TRUE; + } + + if (pev->flags & FL_ONGROUND) + { + // add a bit of static friction + pev->velocity = pev->velocity * 0.8; + + pev->sequence = RANDOM_LONG( 1, 1 ); + } + else + { + // play bounce sound + BounceSound(); + } + pev->framerate = pev->velocity.Length() / 200.0; + if (pev->framerate > 1.0) + pev->framerate = 1; + else if (pev->framerate < 0.5) + pev->framerate = 0; + +} + + + +void CGrenade::SlideTouch( CBaseEntity *pOther ) +{ + // don't hit the guy that launched this grenade + if ( pOther->edict() == pev->owner ) + return; + + // pev->avelocity = Vector (300, 300, 300); + + if (pev->flags & FL_ONGROUND) + { + // add a bit of static friction + pev->velocity = pev->velocity * 0.95; + + if (pev->velocity.x != 0 || pev->velocity.y != 0) + { + // maintain sliding sound + } + } + else + { + BounceSound(); + } +} + +void CGrenade :: BounceSound( void ) +{ + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit1.wav", 0.25, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit2.wav", 0.25, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit3.wav", 0.25, ATTN_NORM); break; + } +} + +void CGrenade :: TumbleThink( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + if (pev->dmgtime - 1 < gpGlobals->time) + { + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin + pev->velocity * (pev->dmgtime - gpGlobals->time), 400, 0.1 ); + } + + if (pev->dmgtime <= gpGlobals->time) + { + SetThink( &CGrenade::Detonate ); + } + if (pev->waterlevel != 0) + { + pev->velocity = pev->velocity * 0.5; + pev->framerate = 0.2; + } +} + + +void CGrenade:: Spawn( void ) +{ + pev->movetype = MOVETYPE_BOUNCE; + pev->classname = MAKE_STRING( "grenade" ); + + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/grenade.mdl"); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + + pev->dmg = 100; + m_fRegisteredSound = FALSE; +} + + +CGrenade *CGrenade::ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ) +{ + CGrenade *pGrenade = GetClassPtr( (CGrenade *)NULL ); + pGrenade->Spawn(); + // contact grenades arc lower + pGrenade->pev->gravity = 0.5;// lower gravity since grenade is aerodynamic and engine doesn't know it. + UTIL_SetOrigin( pGrenade->pev, vecStart ); + pGrenade->pev->velocity = vecVelocity; + pGrenade->pev->angles = UTIL_VecToAngles (pGrenade->pev->velocity); + pGrenade->pev->owner = ENT(pevOwner); + + // make monsters afaid of it while in the air + pGrenade->SetThink( &CGrenade::DangerSoundThink ); + pGrenade->pev->nextthink = gpGlobals->time; + + // Tumble in air + pGrenade->pev->avelocity.x = RANDOM_FLOAT ( -100, -500 ); + + // Explode on contact + pGrenade->SetTouch( &CGrenade::ExplodeTouch ); + + pGrenade->pev->dmg = gSkillData.plrDmgM203Grenade; + + return pGrenade; +} + + +CGrenade * CGrenade:: ShootTimed( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time ) +{ + CGrenade *pGrenade = GetClassPtr( (CGrenade *)NULL ); + pGrenade->Spawn(); + UTIL_SetOrigin( pGrenade->pev, vecStart ); + pGrenade->pev->velocity = vecVelocity; + pGrenade->pev->angles = UTIL_VecToAngles(pGrenade->pev->velocity); + pGrenade->pev->owner = ENT(pevOwner); + + pGrenade->SetTouch( &CGrenade::BounceTouch ); // Bounce if touched + + // Take one second off of the desired detonation time and set the think to PreDetonate. PreDetonate + // will insert a DANGER sound into the world sound list and delay detonation for one second so that + // the grenade explodes after the exact amount of time specified in the call to ShootTimed(). + + pGrenade->pev->dmgtime = gpGlobals->time + time; + pGrenade->SetThink( &CGrenade::TumbleThink ); + pGrenade->pev->nextthink = gpGlobals->time + 0.1; + if (time < 0.1) + { + pGrenade->pev->nextthink = gpGlobals->time; + pGrenade->pev->velocity = Vector( 0, 0, 0 ); + } + + pGrenade->pev->sequence = RANDOM_LONG( 3, 6 ); + pGrenade->pev->framerate = 1.0; + + // Tumble through the air + // pGrenade->pev->avelocity.x = -400; + + pGrenade->pev->gravity = 0.5; + pGrenade->pev->friction = 0.8; + + SET_MODEL(ENT(pGrenade->pev), "models/w_grenade.mdl"); + pGrenade->pev->dmg = 100; + + return pGrenade; +} + + +CGrenade * CGrenade :: ShootSatchelCharge( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ) +{ + CGrenade *pGrenade = GetClassPtr( (CGrenade *)NULL ); + pGrenade->pev->movetype = MOVETYPE_BOUNCE; + pGrenade->pev->classname = MAKE_STRING( "grenade" ); + + pGrenade->pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pGrenade->pev), "models/grenade.mdl"); // Change this to satchel charge model + + UTIL_SetSize(pGrenade->pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + + pGrenade->pev->dmg = 200; + UTIL_SetOrigin( pGrenade->pev, vecStart ); + pGrenade->pev->velocity = vecVelocity; + pGrenade->pev->angles = g_vecZero; + pGrenade->pev->owner = ENT(pevOwner); + + // Detonate in "time" seconds + pGrenade->SetThink( &CGrenade::SUB_DoNothing ); + pGrenade->SetUse( &CGrenade::DetonateUse ); + pGrenade->SetTouch( &CGrenade::SlideTouch ); + pGrenade->pev->spawnflags = SF_DETONATE; + + pGrenade->pev->friction = 0.9; + + return pGrenade; +} + + + +void CGrenade :: UseSatchelCharges( entvars_t *pevOwner, SATCHELCODE code ) +{ + edict_t *pentFind; + edict_t *pentOwner; + + if ( !pevOwner ) + return; + + CBaseEntity *pOwner = CBaseEntity::Instance( pevOwner ); + + pentOwner = pOwner->edict(); + + pentFind = FIND_ENTITY_BY_CLASSNAME( NULL, "grenade" ); + while ( !FNullEnt( pentFind ) ) + { + CBaseEntity *pEnt = Instance( pentFind ); + if ( pEnt ) + { + if ( FBitSet( pEnt->pev->spawnflags, SF_DETONATE ) && pEnt->pev->owner == pentOwner ) + { + if ( code == SATCHEL_DETONATE ) + pEnt->Use( pOwner, pOwner, USE_ON, 0 ); + else // SATCHEL_RELEASE + pEnt->pev->owner = NULL; + } + } + pentFind = FIND_ENTITY_BY_CLASSNAME( pentFind, "grenade" ); + } +} + +//======================end grenade + diff --git a/ricochet/dlls/globals.cpp b/ricochet/dlls/globals.cpp new file mode 100644 index 0000000..9f6125c --- /dev/null +++ b/ricochet/dlls/globals.cpp @@ -0,0 +1,39 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== globals.cpp ======================================================== + + DLL-wide global variable definitions. + They're all defined here, for convenient centralization. + Source files that need them should "extern ..." declare each + variable, to better document what globals they care about. + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "soundent.h" + +DLL_GLOBAL ULONG g_ulFrameCount; +DLL_GLOBAL ULONG g_ulModelIndexEyes; +DLL_GLOBAL ULONG g_ulModelIndexPlayer; +DLL_GLOBAL Vector g_vecAttackDir; +DLL_GLOBAL int g_iSkillLevel; +DLL_GLOBAL int gDisplayTitle; +DLL_GLOBAL BOOL g_fGameOver; +DLL_GLOBAL const Vector g_vecZero = Vector(0,0,0); +DLL_GLOBAL int g_Language; diff --git a/ricochet/dlls/h_ai.cpp b/ricochet/dlls/h_ai.cpp new file mode 100644 index 0000000..d526bcf --- /dev/null +++ b/ricochet/dlls/h_ai.cpp @@ -0,0 +1,198 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + + h_ai.cpp - halflife specific ai code + +*/ + + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" + +#define NUM_LATERAL_CHECKS 13 // how many checks are made on each side of a monster looking for lateral cover +#define NUM_LATERAL_LOS_CHECKS 6 // how many checks are made on each side of a monster looking for lateral cover + +//float flRandom = RANDOM_FLOAT(0,1); + +DLL_GLOBAL BOOL g_fDrawLines = FALSE; + +//========================================================= +// +// AI UTILITY FUNCTIONS +// +// !!!UNDONE - move CBaseMonster functions to monsters.cpp +//========================================================= + +//========================================================= +// FBoxVisible - a more accurate ( and slower ) version +// of FVisible. +// +// !!!UNDONE - make this CBaseMonster? +//========================================================= +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget, Vector &vecTargetOrigin, float flSize ) +{ + // don't look through water + if ((pevLooker->waterlevel != 3 && pevTarget->waterlevel == 3) + || (pevLooker->waterlevel == 3 && pevTarget->waterlevel == 0)) + return FALSE; + + TraceResult tr; + Vector vecLookerOrigin = pevLooker->origin + pevLooker->view_ofs;//look through the monster's 'eyes' + for (int i = 0; i < 5; i++) + { + Vector vecTarget = pevTarget->origin; + vecTarget.x += RANDOM_FLOAT( pevTarget->mins.x + flSize, pevTarget->maxs.x - flSize); + vecTarget.y += RANDOM_FLOAT( pevTarget->mins.y + flSize, pevTarget->maxs.y - flSize); + vecTarget.z += RANDOM_FLOAT( pevTarget->mins.z + flSize, pevTarget->maxs.z - flSize); + + UTIL_TraceLine(vecLookerOrigin, vecTarget, ignore_monsters, ignore_glass, ENT(pevLooker)/*pentIgnore*/, &tr); + + if (tr.flFraction == 1.0) + { + vecTargetOrigin = vecTarget; + return TRUE;// line of sight is valid. + } + } + return FALSE;// Line of sight is not established +} + +// +// VecCheckToss - returns the velocity at which an object should be lobbed from vecspot1 to land near vecspot2. +// returns g_vecZero if toss is not feasible. +// +Vector VecCheckToss ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flGravityAdj ) +{ + TraceResult tr; + Vector vecMidPoint;// halfway point between Spot1 and Spot2 + Vector vecApex;// highest point + Vector vecScale; + Vector vecGrenadeVel; + Vector vecTemp; + float flGravity = CVAR_GET_FLOAT( "sv_gravity" ) * flGravityAdj; + + if (vecSpot2.z - vecSpot1.z > 500) + { + // to high, fail + return g_vecZero; + } + + UTIL_MakeVectors (pev->angles); + + // toss a little bit to the left or right, not right down on the enemy's bean (head). + vecSpot2 = vecSpot2 + gpGlobals->v_right * ( RANDOM_FLOAT(-8,8) + RANDOM_FLOAT(-16,16) ); + vecSpot2 = vecSpot2 + gpGlobals->v_forward * ( RANDOM_FLOAT(-8,8) + RANDOM_FLOAT(-16,16) ); + + // calculate the midpoint and apex of the 'triangle' + // UNDONE: normalize any Z position differences between spot1 and spot2 so that triangle is always RIGHT + + // How much time does it take to get there? + + // get a rough idea of how high it can be thrown + vecMidPoint = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5; + UTIL_TraceLine(vecMidPoint, vecMidPoint + Vector(0,0,500), ignore_monsters, ENT(pev), &tr); + vecMidPoint = tr.vecEndPos; + // (subtract 15 so the grenade doesn't hit the ceiling) + vecMidPoint.z -= 15; + + if (vecMidPoint.z < vecSpot1.z || vecMidPoint.z < vecSpot2.z) + { + // to not enough space, fail + return g_vecZero; + } + + // How high should the grenade travel to reach the apex + float distance1 = (vecMidPoint.z - vecSpot1.z); + float distance2 = (vecMidPoint.z - vecSpot2.z); + + // How long will it take for the grenade to travel this distance + float time1 = sqrt( distance1 / (0.5 * flGravity) ); + float time2 = sqrt( distance2 / (0.5 * flGravity) ); + + if (time1 < 0.1) + { + // too close + return g_vecZero; + } + + // how hard to throw sideways to get there in time. + vecGrenadeVel = (vecSpot2 - vecSpot1) / (time1 + time2); + // how hard upwards to reach the apex at the right time. + vecGrenadeVel.z = flGravity * time1; + + // find the apex + vecApex = vecSpot1 + vecGrenadeVel * time1; + vecApex.z = vecMidPoint.z; + + UTIL_TraceLine(vecSpot1, vecApex, dont_ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + // UNDONE: either ignore monsters or change it to not care if we hit our enemy + UTIL_TraceLine(vecSpot2, vecApex, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + return vecGrenadeVel; +} + + +// +// VecCheckThrow - returns the velocity vector at which an object should be thrown from vecspot1 to hit vecspot2. +// returns g_vecZero if throw is not feasible. +// +Vector VecCheckThrow ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flSpeed, float flGravityAdj ) +{ + float flGravity = CVAR_GET_FLOAT( "sv_gravity" ) * flGravityAdj; + + Vector vecGrenadeVel = (vecSpot2 - vecSpot1); + + // throw at a constant time + float time = vecGrenadeVel.Length( ) / flSpeed; + vecGrenadeVel = vecGrenadeVel * (1.0 / time); + + // adjust upward toss to compensate for gravity loss + vecGrenadeVel.z += flGravity * time * 0.5; + + Vector vecApex = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5; + vecApex.z += 0.5 * flGravity * (time * 0.5) * (time * 0.5); + + TraceResult tr; + UTIL_TraceLine(vecSpot1, vecApex, dont_ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + UTIL_TraceLine(vecSpot2, vecApex, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + return vecGrenadeVel; +} + + diff --git a/ricochet/dlls/h_battery.cpp b/ricochet/dlls/h_battery.cpp new file mode 100644 index 0000000..7a029ce --- /dev/null +++ b/ricochet/dlls/h_battery.cpp @@ -0,0 +1,200 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== h_battery.cpp ======================================================== + + battery-related code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "skill.h" +#include "gamerules.h" + +class CRecharge : public CBaseToggle +{ +public: + void Spawn( ); + void Precache( void ); + void EXPORT Off(void); + void EXPORT Recharge(void); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return (CBaseToggle :: ObjectCaps() | FCAP_CONTINUOUS_USE) & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flNextCharge; + int m_iReactivate ; // DeathMatch Delay until reactvated + int m_iJuice; + int m_iOn; // 0 = off, 1 = startup, 2 = going + float m_flSoundTime; +}; + +TYPEDESCRIPTION CRecharge::m_SaveData[] = +{ + DEFINE_FIELD( CRecharge, m_flNextCharge, FIELD_TIME ), + DEFINE_FIELD( CRecharge, m_iReactivate, FIELD_INTEGER), + DEFINE_FIELD( CRecharge, m_iJuice, FIELD_INTEGER), + DEFINE_FIELD( CRecharge, m_iOn, FIELD_INTEGER), + DEFINE_FIELD( CRecharge, m_flSoundTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CRecharge, CBaseEntity ); + +LINK_ENTITY_TO_CLASS(func_recharge, CRecharge); + + +void CRecharge::KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + { + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "dmdelay")) + { + m_iReactivate = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CRecharge::Spawn() +{ + Precache( ); + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); // set size and link into world + UTIL_SetSize(pev, pev->mins, pev->maxs); + SET_MODEL(ENT(pev), STRING(pev->model) ); + m_iJuice = gSkillData.suitchargerCapacity; + pev->frame = 0; +} + +void CRecharge::Precache() +{ + PRECACHE_SOUND("items/suitcharge1.wav"); + PRECACHE_SOUND("items/suitchargeno1.wav"); + PRECACHE_SOUND("items/suitchargeok1.wav"); +} + + +void CRecharge::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // if it's not a player, ignore + if (!FClassnameIs(pActivator->pev, "player")) + return; + + // if there is no juice left, turn it off + if (m_iJuice <= 0) + { + pev->frame = 1; + Off(); + } + + // if the player doesn't have the suit, or there is no juice left, make the deny noise + if ((m_iJuice <= 0) || (!(pActivator->pev->weapons & (1<time) + { + m_flSoundTime = gpGlobals->time + 0.62; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/suitchargeno1.wav", 0.85, ATTN_NORM ); + } + return; + } + + pev->nextthink = pev->ltime + 0.25; + SetThink(&CRecharge::Off); + + // Time to recharge yet? + + if (m_flNextCharge >= gpGlobals->time) + return; + + // Make sure that we have a caller + if (!pActivator) + return; + + m_hActivator = pActivator; + + //only recharge the player + + if (!m_hActivator->IsPlayer() ) + return; + + // Play the on sound or the looping charging sound + if (!m_iOn) + { + m_iOn++; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/suitchargeok1.wav", 0.85, ATTN_NORM ); + m_flSoundTime = 0.56 + gpGlobals->time; + } + if ((m_iOn == 1) && (m_flSoundTime <= gpGlobals->time)) + { + m_iOn++; + EMIT_SOUND(ENT(pev), CHAN_STATIC, "items/suitcharge1.wav", 0.85, ATTN_NORM ); + } + + + // charge the player + if (m_hActivator->pev->armorvalue < 100) + { + m_iJuice--; + m_hActivator->pev->armorvalue += 1; + + if (m_hActivator->pev->armorvalue > 100) + m_hActivator->pev->armorvalue = 100; + } + + // govern the rate of charge + m_flNextCharge = gpGlobals->time + 0.1; +} + +void CRecharge::Recharge(void) +{ + m_iJuice = gSkillData.suitchargerCapacity; + pev->frame = 0; + SetThink( &CRecharge::SUB_DoNothing ); +} + +void CRecharge::Off(void) +{ + // Stop looping sound. + if (m_iOn > 1) + STOP_SOUND( ENT(pev), CHAN_STATIC, "items/suitcharge1.wav" ); + + m_iOn = 0; + + if ((!m_iJuice) && ( ( m_iReactivate = g_pGameRules->FlHEVChargerRechargeTime() ) > 0) ) + { + pev->nextthink = pev->ltime + m_iReactivate; + SetThink(&CRecharge::Recharge); + } + else + SetThink( &CRecharge::SUB_DoNothing ); +} diff --git a/ricochet/dlls/h_cycler.cpp b/ricochet/dlls/h_cycler.cpp new file mode 100644 index 0000000..6822c85 --- /dev/null +++ b/ricochet/dlls/h_cycler.cpp @@ -0,0 +1,471 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== h_cycler.cpp ======================================================== + + The Halflife Cycler Monsters + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "animation.h" +#include "weapons.h" +#include "player.h" + + +#define TEMP_FOR_SCREEN_SHOTS +#ifdef TEMP_FOR_SCREEN_SHOTS //=================================================== + +class CCycler : public CBaseMonster +{ +public: + void GenericCyclerSpawn(char *szModel, Vector vecMin, Vector vecMax); + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() | FCAP_IMPULSE_USE); } + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void Spawn( void ); + void Think( void ); + //void Pain( float flDamage ); + void Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + // Don't treat as a live target + virtual BOOL IsAlive( void ) { return FALSE; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_animate; +}; + +TYPEDESCRIPTION CCycler::m_SaveData[] = +{ + DEFINE_FIELD( CCycler, m_animate, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CCycler, CBaseMonster ); + + +// +// we should get rid of all the other cyclers and replace them with this. +// +class CGenericCycler : public CCycler +{ +public: + void Spawn( void ) { GenericCyclerSpawn( (char *)STRING(pev->model), Vector(-16, -16, 0), Vector(16, 16, 72) ); } +}; +LINK_ENTITY_TO_CLASS( cycler, CGenericCycler ); + + + +// Probe droid imported for tech demo compatibility +// +// PROBE DROID +// +class CCyclerProbe : public CCycler +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( cycler_prdroid, CCyclerProbe ); +void CCyclerProbe :: Spawn( void ) +{ + pev->origin = pev->origin + Vector ( 0, 0, 16 ); + GenericCyclerSpawn( "models/prdroid.mdl", Vector(-16,-16,-16), Vector(16,16,16)); +} + + + +// Cycler member functions + +void CCycler :: GenericCyclerSpawn(char *szModel, Vector vecMin, Vector vecMax) +{ + if (!szModel || !*szModel) + { + ALERT(at_error, "cycler at %.0f %.0f %0.f missing modelname", pev->origin.x, pev->origin.y, pev->origin.z ); + REMOVE_ENTITY(ENT(pev)); + return; + } + + pev->classname = MAKE_STRING("cycler"); + PRECACHE_MODEL( szModel ); + SET_MODEL(ENT(pev), szModel); + + CCycler::Spawn( ); + + UTIL_SetSize(pev, vecMin, vecMax); +} + + +void CCycler :: Spawn( ) +{ + InitBoneControllers(); + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_NONE; + pev->takedamage = DAMAGE_YES; + pev->effects = 0; + pev->health = 80000;// no cycler should die + pev->yaw_speed = 5; + pev->ideal_yaw = pev->angles.y; + ChangeYaw( 360 ); + + m_flFrameRate = 75; + m_flGroundSpeed = 0; + + pev->nextthink += 1.0; + + ResetSequenceInfo( ); + + if (pev->sequence != 0 || pev->frame != 0) + { + m_animate = 0; + pev->framerate = 0; + } + else + { + m_animate = 1; + } +} + + + + +// +// cycler think +// +void CCycler :: Think( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if (m_animate) + { + StudioFrameAdvance ( ); + } + if (m_fSequenceFinished && !m_fSequenceLoops) + { + // ResetSequenceInfo(); + // hack to avoid reloading model every frame + pev->animtime = gpGlobals->time; + pev->framerate = 1.0; + m_fSequenceFinished = FALSE; + m_flLastEventCheck = gpGlobals->time; + pev->frame = 0; + if (!m_animate) + pev->framerate = 0.0; // FIX: don't reset framerate + } +} + +// +// CyclerUse - starts a rotation trend +// +void CCycler :: Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_animate = !m_animate; + if (m_animate) + pev->framerate = 1.0; + else + pev->framerate = 0.0; +} + +// +// CyclerPain , changes sequences when shot +// +//void CCycler :: Pain( float flDamage ) +int CCycler :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if (m_animate) + { + pev->sequence++; + + ResetSequenceInfo( ); + + if (m_flFrameRate == 0.0) + { + pev->sequence = 0; + ResetSequenceInfo( ); + } + pev->frame = 0; + } + else + { + pev->framerate = 1.0; + StudioFrameAdvance ( 0.1 ); + pev->framerate = 0; + ALERT( at_console, "sequence: %d, frame %.0f\n", pev->sequence, pev->frame ); + } + + return 0; +} + +#endif + + +class CCyclerSprite : public CBaseEntity +{ +public: + void Spawn( void ); + void Think( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() | FCAP_DONT_SAVE | FCAP_IMPULSE_USE); } + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void Animate( float frames ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + inline int ShouldAnimate( void ) { return m_animate && m_maxFrame > 1.0; } + int m_animate; + float m_lastTime; + float m_maxFrame; +}; + +LINK_ENTITY_TO_CLASS( cycler_sprite, CCyclerSprite ); + +TYPEDESCRIPTION CCyclerSprite::m_SaveData[] = +{ + DEFINE_FIELD( CCyclerSprite, m_animate, FIELD_INTEGER ), + DEFINE_FIELD( CCyclerSprite, m_lastTime, FIELD_TIME ), + DEFINE_FIELD( CCyclerSprite, m_maxFrame, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CCyclerSprite, CBaseEntity ); + + +void CCyclerSprite::Spawn( void ) +{ + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_NONE; + pev->takedamage = DAMAGE_YES; + pev->effects = 0; + + pev->frame = 0; + pev->nextthink = gpGlobals->time + 0.1; + m_animate = 1; + m_lastTime = gpGlobals->time; + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; +} + + +void CCyclerSprite::Think( void ) +{ + if ( ShouldAnimate() ) + Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); + + pev->nextthink = gpGlobals->time + 0.1; + m_lastTime = gpGlobals->time; +} + + +void CCyclerSprite::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_animate = !m_animate; + ALERT( at_console, "Sprite: %s\n", STRING(pev->model) ); +} + + +int CCyclerSprite::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( m_maxFrame > 1.0 ) + { + Animate( 1.0 ); + } + return 1; +} + +void CCyclerSprite::Animate( float frames ) +{ + pev->frame += frames; + if ( m_maxFrame > 0 ) + pev->frame = fmod( pev->frame, m_maxFrame ); +} + + + + + + + +class CWeaponCycler : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + int iItemSlot( void ) { return 1; } + int GetItemInfo(ItemInfo *p) {return 0; } + + void PrimaryAttack( void ); + void SecondaryAttack( void ); + BOOL Deploy( void ); + void Holster( int skiplocal = 0 ); + int m_iszModel; + int m_iModel; +}; +LINK_ENTITY_TO_CLASS( cycler_weapon, CWeaponCycler ); + + +void CWeaponCycler::Spawn( ) +{ + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_NONE; + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL( ENT(pev), STRING(pev->model) ); + m_iszModel = pev->model; + m_iModel = pev->modelindex; + + UTIL_SetOrigin( pev, pev->origin ); + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 16)); + SetTouch( &CWeaponCycler::DefaultTouch ); +} + + + +BOOL CWeaponCycler::Deploy( ) +{ + m_pPlayer->pev->viewmodel = m_iszModel; + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.0; + SendWeaponAnim( 0 ); + m_iClip = 0; + return TRUE; +} + + +void CWeaponCycler::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; +} + + +void CWeaponCycler::PrimaryAttack() +{ + + SendWeaponAnim( pev->sequence ); + + m_flNextPrimaryAttack = gpGlobals->time + 0.3; +} + + +void CWeaponCycler::SecondaryAttack( void ) +{ + float flFrameRate, flGroundSpeed; + + pev->sequence = (pev->sequence + 1) % 8; + + pev->modelindex = m_iModel; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + GetSequenceInfo( pmodel, pev, &flFrameRate, &flGroundSpeed ); + pev->modelindex = 0; + + if (flFrameRate == 0.0) + { + pev->sequence = 0; + } + + SendWeaponAnim( pev->sequence ); + + m_flNextSecondaryAttack = gpGlobals->time + 0.3; +} + + + +// Flaming Wreakage +class CWreckage : public CBaseMonster +{ + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + void Think( void ); + + int m_flStartTime; +}; +TYPEDESCRIPTION CWreckage::m_SaveData[] = +{ + DEFINE_FIELD( CWreckage, m_flStartTime, FIELD_TIME ), +}; +IMPLEMENT_SAVERESTORE( CWreckage, CBaseMonster ); + + +LINK_ENTITY_TO_CLASS( cycler_wreckage, CWreckage ); + +void CWreckage::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->takedamage = 0; + pev->effects = 0; + + pev->frame = 0; + pev->nextthink = gpGlobals->time + 0.1; + + if (pev->model) + { + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL( ENT(pev), STRING(pev->model) ); + } + // pev->scale = 5.0; + + m_flStartTime = gpGlobals->time; +} + +void CWreckage::Precache( ) +{ + if ( pev->model ) + PRECACHE_MODEL( (char *)STRING(pev->model) ); +} + +void CWreckage::Think( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.2; + + if (pev->dmgtime) + { + if (pev->dmgtime < gpGlobals->time) + { + UTIL_Remove( this ); + return; + } + else if (RANDOM_FLOAT( 0, pev->dmgtime - m_flStartTime ) > pev->dmgtime - gpGlobals->time) + { + return; + } + } + + Vector VecSrc; + + VecSrc.x = RANDOM_FLOAT( pev->absmin.x, pev->absmax.x ); + VecSrc.y = RANDOM_FLOAT( pev->absmin.y, pev->absmax.y ); + VecSrc.z = RANDOM_FLOAT( pev->absmin.z, pev->absmax.z ); + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, VecSrc ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( VecSrc.x ); + WRITE_COORD( VecSrc.y ); + WRITE_COORD( VecSrc.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( RANDOM_LONG(0,49) + 50 ); // scale * 10 + WRITE_BYTE( RANDOM_LONG(0, 3) + 8 ); // framerate + MESSAGE_END(); +} diff --git a/ricochet/dlls/h_export.cpp b/ricochet/dlls/h_export.cpp new file mode 100644 index 0000000..480bccd --- /dev/null +++ b/ricochet/dlls/h_export.cpp @@ -0,0 +1,61 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== h_export.cpp ======================================================== + + Entity classes exported by Halflife. + +*/ + +#include "extdll.h" +#include "util.h" + +#include "cbase.h" + +// Holds engine functionality callbacks +enginefuncs_t g_engfuncs; +globalvars_t *gpGlobals; + +#undef DLLEXPORT +#ifdef _WIN32 +#define DLLEXPORT __stdcall +#else +#define DLLEXPORT __attribute__ ((visibility("default"))) +#endif + +#ifdef _WIN32 + +// Required DLL entry point +BOOL WINAPI DllMain( + HINSTANCE hinstDLL, + DWORD fdwReason, + LPVOID lpvReserved) +{ + if (fdwReason == DLL_PROCESS_ATTACH) + { + } + else if (fdwReason == DLL_PROCESS_DETACH) + { + } + return TRUE; +} +#endif + +extern "C" void DLLEXPORT GiveFnptrsToDll( enginefuncs_t* pengfuncsFromEngine, globalvars_t *pGlobals ) +{ + memcpy(&g_engfuncs, pengfuncsFromEngine, sizeof(enginefuncs_t)); + gpGlobals = pGlobals; +} diff --git a/ricochet/dlls/healthkit.cpp b/ricochet/dlls/healthkit.cpp new file mode 100644 index 0000000..68ec4ef --- /dev/null +++ b/ricochet/dlls/healthkit.cpp @@ -0,0 +1,259 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "items.h" +#include "gamerules.h" + +extern int gmsgItemPickup; + +class CHealthKit : public CItem +{ + void Spawn( void ); + void Precache( void ); + BOOL MyTouch( CBasePlayer *pPlayer ); + +/* + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; +*/ + +}; + + +LINK_ENTITY_TO_CLASS( item_healthkit, CHealthKit ); + +/* +TYPEDESCRIPTION CHealthKit::m_SaveData[] = +{ + +}; + + +IMPLEMENT_SAVERESTORE( CHealthKit, CItem); +*/ + +void CHealthKit :: Spawn( void ) +{ + Precache( ); + SET_MODEL(ENT(pev), "models/w_medkit.mdl"); + + CItem::Spawn(); +} + +void CHealthKit::Precache( void ) +{ + PRECACHE_MODEL("models/w_medkit.mdl"); + PRECACHE_SOUND("items/smallmedkit1.wav"); +} + +BOOL CHealthKit::MyTouch( CBasePlayer *pPlayer ) +{ + if ( pPlayer->TakeHealth( gSkillData.healthkitCapacity, DMG_GENERIC ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgItemPickup, NULL, pPlayer->pev ); + WRITE_STRING( STRING(pev->classname) ); + MESSAGE_END(); + + EMIT_SOUND(ENT(pPlayer->pev), CHAN_ITEM, "items/smallmedkit1.wav", 1, ATTN_NORM); + + if ( g_pGameRules->ItemShouldRespawn( this ) ) + { + Respawn(); + } + else + { + UTIL_Remove(this); + } + + return TRUE; + } + + return FALSE; +} + + + +//------------------------------------------------------------- +// Wall mounted health kit +//------------------------------------------------------------- +class CWallHealth : public CBaseToggle +{ +public: + void Spawn( ); + void Precache( void ); + void EXPORT Off(void); + void EXPORT Recharge(void); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return (CBaseToggle :: ObjectCaps() | FCAP_CONTINUOUS_USE) & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flNextCharge; + int m_iReactivate ; // DeathMatch Delay until reactvated + int m_iJuice; + int m_iOn; // 0 = off, 1 = startup, 2 = going + float m_flSoundTime; +}; + +TYPEDESCRIPTION CWallHealth::m_SaveData[] = +{ + DEFINE_FIELD( CWallHealth, m_flNextCharge, FIELD_TIME), + DEFINE_FIELD( CWallHealth, m_iReactivate, FIELD_INTEGER), + DEFINE_FIELD( CWallHealth, m_iJuice, FIELD_INTEGER), + DEFINE_FIELD( CWallHealth, m_iOn, FIELD_INTEGER), + DEFINE_FIELD( CWallHealth, m_flSoundTime, FIELD_TIME), +}; + +IMPLEMENT_SAVERESTORE( CWallHealth, CBaseEntity ); + +LINK_ENTITY_TO_CLASS(func_healthcharger, CWallHealth); + + +void CWallHealth::KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + { + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "dmdelay")) + { + m_iReactivate = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CWallHealth::Spawn() +{ + Precache( ); + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); // set size and link into world + UTIL_SetSize(pev, pev->mins, pev->maxs); + SET_MODEL(ENT(pev), STRING(pev->model) ); + m_iJuice = gSkillData.healthchargerCapacity; + pev->frame = 0; + +} + +void CWallHealth::Precache() +{ + PRECACHE_SOUND("items/medshot4.wav"); + PRECACHE_SOUND("items/medshotno1.wav"); + PRECACHE_SOUND("items/medcharge4.wav"); +} + + +void CWallHealth::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Make sure that we have a caller + if (!pActivator) + return; + // if it's not a player, ignore + if ( !pActivator->IsPlayer() ) + return; + + // if there is no juice left, turn it off + if (m_iJuice <= 0) + { + pev->frame = 1; + Off(); + } + + // if the player doesn't have the suit, or there is no juice left, make the deny noise + if ((m_iJuice <= 0) || (!(pActivator->pev->weapons & (1<time) + { + m_flSoundTime = gpGlobals->time + 0.62; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/medshotno1.wav", 1.0, ATTN_NORM ); + } + return; + } + + pev->nextthink = pev->ltime + 0.25; + SetThink(&CWallHealth::Off); + + // Time to recharge yet? + + if (m_flNextCharge >= gpGlobals->time) + return; + + // Play the on sound or the looping charging sound + if (!m_iOn) + { + m_iOn++; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/medshot4.wav", 1.0, ATTN_NORM ); + m_flSoundTime = 0.56 + gpGlobals->time; + } + if ((m_iOn == 1) && (m_flSoundTime <= gpGlobals->time)) + { + m_iOn++; + EMIT_SOUND(ENT(pev), CHAN_STATIC, "items/medcharge4.wav", 1.0, ATTN_NORM ); + } + + + // charge the player + if ( pActivator->TakeHealth( 1, DMG_GENERIC ) ) + { + m_iJuice--; + } + + // govern the rate of charge + m_flNextCharge = gpGlobals->time + 0.1; +} + +void CWallHealth::Recharge(void) +{ + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/medshot4.wav", 1.0, ATTN_NORM ); + m_iJuice = gSkillData.healthchargerCapacity; + pev->frame = 0; + SetThink( &CWallHealth::SUB_DoNothing ); +} + +void CWallHealth::Off(void) +{ + // Stop looping sound. + if (m_iOn > 1) + STOP_SOUND( ENT(pev), CHAN_STATIC, "items/medcharge4.wav" ); + + m_iOn = 0; + + if ((!m_iJuice) && ( ( m_iReactivate = g_pGameRules->FlHealthChargerRechargeTime() ) > 0) ) + { + pev->nextthink = pev->ltime + m_iReactivate; + SetThink(&CWallHealth::Recharge); + } + else + SetThink( &CWallHealth::SUB_DoNothing ); +} diff --git a/ricochet/dlls/items.cpp b/ricochet/dlls/items.cpp new file mode 100644 index 0000000..0429a75 --- /dev/null +++ b/ricochet/dlls/items.cpp @@ -0,0 +1,337 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== items.cpp ======================================================== + + functions governing the selection/use of weapons for players + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "player.h" +#include "skill.h" +#include "items.h" +#include "gamerules.h" + +extern int gmsgItemPickup; + +class CWorldItem : public CBaseEntity +{ +public: + void KeyValue(KeyValueData *pkvd ); + void Spawn( void ); + int m_iType; +}; + +LINK_ENTITY_TO_CLASS(world_items, CWorldItem); + +void CWorldItem::KeyValue(KeyValueData *pkvd) +{ + if (FStrEq(pkvd->szKeyName, "type")) + { + m_iType = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CWorldItem::Spawn( void ) +{ + CBaseEntity *pEntity = NULL; + + switch (m_iType) + { + case 44: // ITEM_BATTERY: + pEntity = CBaseEntity::Create( "item_battery", pev->origin, pev->angles ); + break; + case 42: // ITEM_ANTIDOTE: + pEntity = CBaseEntity::Create( "item_antidote", pev->origin, pev->angles ); + break; + case 43: // ITEM_SECURITY: + pEntity = CBaseEntity::Create( "item_security", pev->origin, pev->angles ); + break; + case 45: // ITEM_SUIT: + pEntity = CBaseEntity::Create( "item_suit", pev->origin, pev->angles ); + break; + } + + if (!pEntity) + { + ALERT( at_console, "unable to create world_item %d\n", m_iType ); + } + else + { + pEntity->pev->target = pev->target; + pEntity->pev->targetname = pev->targetname; + pEntity->pev->spawnflags = pev->spawnflags; + } + + REMOVE_ENTITY(edict()); +} + + +void CItem::Spawn( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + UTIL_SetOrigin( pev, pev->origin ); + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 16)); + SetTouch(&CItem::ItemTouch); + + if (DROP_TO_FLOOR(ENT(pev)) == 0) + { + ALERT(at_error, "Item %s fell out of level at %f,%f,%f", STRING( pev->classname ), pev->origin.x, pev->origin.y, pev->origin.z); + UTIL_Remove( this ); + return; + } +} + +extern int gEvilImpulse101; + +void CItem::ItemTouch( CBaseEntity *pOther ) +{ + // if it's not a player, ignore + if ( !pOther->IsPlayer() ) + { + return; + } + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + + // ok, a player is touching this item, but can he have it? + if ( !g_pGameRules->CanHaveItem( pPlayer, this ) ) + { + // no? Ignore the touch. + return; + } + + if (MyTouch( pPlayer )) + { + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); + SetTouch( NULL ); + + // player grabbed the item. + g_pGameRules->PlayerGotItem( pPlayer, this ); + if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_YES ) + { + Respawn(); + } + else + { + UTIL_Remove( this ); + } + } + else if (gEvilImpulse101) + { + UTIL_Remove( this ); + } +} + +CBaseEntity* CItem::Respawn( void ) +{ + SetTouch( NULL ); + pev->effects |= EF_NODRAW; + + UTIL_SetOrigin( pev, g_pGameRules->VecItemRespawnSpot( this ) );// blip to whereever you should respawn. + + SetThink ( &CItem::Materialize ); + pev->nextthink = g_pGameRules->FlItemRespawnTime( this ); + return this; +} + +void CItem::Materialize( void ) +{ + if ( pev->effects & EF_NODRAW ) + { + // changing from invisible state to visible. + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "items/suitchargeok1.wav", 1, ATTN_NORM, 0, 150 ); + pev->effects &= ~EF_NODRAW; + pev->effects |= EF_MUZZLEFLASH; + } + + SetTouch( &CItem::ItemTouch ); +} + +#define SF_SUIT_SHORTLOGON 0x0001 + +class CItemSuit : public CItem +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_suit.mdl"); + CItem::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_suit.mdl"); + } + BOOL MyTouch( CBasePlayer *pPlayer ) + { + if ( pPlayer->pev->weapons & (1<spawnflags & SF_SUIT_SHORTLOGON ) + EMIT_SOUND_SUIT(pPlayer->edict(), "!HEV_A0"); // short version of suit logon, + else + EMIT_SOUND_SUIT(pPlayer->edict(), "!HEV_AAx"); // long version of suit logon + + pPlayer->pev->weapons |= (1<pev->armorvalue < MAX_NORMAL_BATTERY) && + (pPlayer->pev->weapons & (1<pev->armorvalue += gSkillData.batteryCapacity; + pPlayer->pev->armorvalue = min(pPlayer->pev->armorvalue, MAX_NORMAL_BATTERY); + + EMIT_SOUND( pPlayer->edict(), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM ); + + MESSAGE_BEGIN( MSG_ONE, gmsgItemPickup, NULL, pPlayer->pev ); + WRITE_STRING( STRING(pev->classname) ); + MESSAGE_END(); + + + // Suit reports new power level + // For some reason this wasn't working in release build -- round it. + pct = (int)( (float)(pPlayer->pev->armorvalue * 100.0) * (1.0/MAX_NORMAL_BATTERY) + 0.5); + pct = (pct / 5); + if (pct > 0) + pct--; + + sprintf( szcharge,"!HEV_%1dP", pct ); + + //EMIT_SOUND_SUIT(ENT(pev), szcharge); + pPlayer->SetSuitUpdate(szcharge, FALSE, SUIT_NEXT_IN_30SEC); + return TRUE; + } + return FALSE; + } +}; + +LINK_ENTITY_TO_CLASS(item_battery, CItemBattery); + + +class CItemAntidote : public CItem +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_antidote.mdl"); + CItem::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_antidote.mdl"); + } + BOOL MyTouch( CBasePlayer *pPlayer ) + { + pPlayer->SetSuitUpdate("!HEV_DET4", FALSE, SUIT_NEXT_IN_1MIN); + + pPlayer->m_rgItems[ITEM_ANTIDOTE] += 1; + return TRUE; + } +}; + +LINK_ENTITY_TO_CLASS(item_antidote, CItemAntidote); + + +class CItemSecurity : public CItem +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_security.mdl"); + CItem::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_security.mdl"); + } + BOOL MyTouch( CBasePlayer *pPlayer ) + { + pPlayer->m_rgItems[ITEM_SECURITY] += 1; + return TRUE; + } +}; + +LINK_ENTITY_TO_CLASS(item_security, CItemSecurity); + +class CItemLongJump : public CItem +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_longjump.mdl"); + CItem::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_longjump.mdl"); + } + BOOL MyTouch( CBasePlayer *pPlayer ) + { + if ( pPlayer->m_fLongJump ) + { + return FALSE; + } + + if ( ( pPlayer->pev->weapons & (1<m_fLongJump = TRUE;// player now has longjump module + + g_engfuncs.pfnSetPhysicsKeyValue( pPlayer->edict(), "slj", "1" ); + + MESSAGE_BEGIN( MSG_ONE, gmsgItemPickup, NULL, pPlayer->pev ); + WRITE_STRING( STRING(pev->classname) ); + MESSAGE_END(); + + EMIT_SOUND_SUIT( pPlayer->edict(), "!HEV_A1" ); // Play the longjump sound UNDONE: Kelly? correct sound? + return TRUE; + } + return FALSE; + } +}; + +LINK_ENTITY_TO_CLASS( item_longjump, CItemLongJump ); diff --git a/ricochet/dlls/items.h b/ricochet/dlls/items.h new file mode 100644 index 0000000..3fbb370 --- /dev/null +++ b/ricochet/dlls/items.h @@ -0,0 +1,29 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef ITEMS_H +#define ITEMS_H + + +class CItem : public CBaseEntity +{ +public: + void Spawn( void ); + CBaseEntity* Respawn( void ); + void EXPORT ItemTouch( CBaseEntity *pOther ); + void EXPORT Materialize( void ); + virtual BOOL MyTouch( CBasePlayer *pPlayer ) { return FALSE; }; +}; + +#endif // ITEMS_H diff --git a/ricochet/dlls/lights.cpp b/ricochet/dlls/lights.cpp new file mode 100644 index 0000000..d49e181 --- /dev/null +++ b/ricochet/dlls/lights.cpp @@ -0,0 +1,199 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== lights.cpp ======================================================== + + spawn and think functions for editor-placed lights + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" + + + +class CLight : public CPointEntity +{ +public: + virtual void KeyValue( KeyValueData* pkvd ); + virtual void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_iStyle; + int m_iszPattern; +}; +LINK_ENTITY_TO_CLASS( light, CLight ); + +TYPEDESCRIPTION CLight::m_SaveData[] = +{ + DEFINE_FIELD( CLight, m_iStyle, FIELD_INTEGER ), + DEFINE_FIELD( CLight, m_iszPattern, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CLight, CPointEntity ); + + +// +// Cache user-entity-field values until spawn is called. +// +void CLight :: KeyValue( KeyValueData* pkvd) +{ + if (FStrEq(pkvd->szKeyName, "style")) + { + m_iStyle = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitch")) + { + pev->angles.x = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pattern")) + { + m_iszPattern = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CPointEntity::KeyValue( pkvd ); + } +} + +/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) LIGHT_START_OFF +Non-displayed light. +Default light value is 300 +Default style is 0 +If targeted, it will toggle between on or off. +*/ + +void CLight :: Spawn( void ) +{ + if (FStringNull(pev->targetname)) + { // inert light + REMOVE_ENTITY(ENT(pev)); + return; + } + + if (m_iStyle >= 32) + { +// CHANGE_METHOD(ENT(pev), em_use, light_use); + if (FBitSet(pev->spawnflags, SF_LIGHT_START_OFF)) + LIGHT_STYLE(m_iStyle, "a"); + else if (m_iszPattern) + LIGHT_STYLE(m_iStyle, (char *)STRING( m_iszPattern )); + else + LIGHT_STYLE(m_iStyle, "m"); + } +} + + +void CLight :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (m_iStyle >= 32) + { + if ( !ShouldToggle( useType, !FBitSet(pev->spawnflags, SF_LIGHT_START_OFF) ) ) + return; + + if (FBitSet(pev->spawnflags, SF_LIGHT_START_OFF)) + { + if (m_iszPattern) + LIGHT_STYLE(m_iStyle, (char *)STRING( m_iszPattern )); + else + LIGHT_STYLE(m_iStyle, "m"); + ClearBits(pev->spawnflags, SF_LIGHT_START_OFF); + } + else + { + LIGHT_STYLE(m_iStyle, "a"); + SetBits(pev->spawnflags, SF_LIGHT_START_OFF); + } + } +} + +// +// shut up spawn functions for new spotlights +// +LINK_ENTITY_TO_CLASS( light_spot, CLight ); + + +class CEnvLight : public CLight +{ +public: + void KeyValue( KeyValueData* pkvd ); + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( light_environment, CEnvLight ); + +void CEnvLight::KeyValue( KeyValueData* pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "_light")) + { + int r, g, b, v, j; + char szColor[64]; + j = sscanf( pkvd->szValue, "%d %d %d %d\n", &r, &g, &b, &v ); + if (j == 1) + { + g = b = r; + } + else if (j == 4) + { + r = r * (v / 255.0); + g = g * (v / 255.0); + b = b * (v / 255.0); + } + + // simulate qrad direct, ambient,and gamma adjustments, as well as engine scaling + r = pow( r / 114.0, 0.6 ) * 264; + g = pow( g / 114.0, 0.6 ) * 264; + b = pow( b / 114.0, 0.6 ) * 264; + + pkvd->fHandled = TRUE; + sprintf( szColor, "%d", r ); + CVAR_SET_STRING( "sv_skycolor_r", szColor ); + sprintf( szColor, "%d", g ); + CVAR_SET_STRING( "sv_skycolor_g", szColor ); + sprintf( szColor, "%d", b ); + CVAR_SET_STRING( "sv_skycolor_b", szColor ); + } + else + { + CLight::KeyValue( pkvd ); + } +} + + +void CEnvLight :: Spawn( void ) +{ + char szVector[64]; + UTIL_MakeAimVectors( pev->angles ); + + sprintf( szVector, "%f", gpGlobals->v_forward.x ); + CVAR_SET_STRING( "sv_skyvec_x", szVector ); + sprintf( szVector, "%f", gpGlobals->v_forward.y ); + CVAR_SET_STRING( "sv_skyvec_y", szVector ); + sprintf( szVector, "%f", gpGlobals->v_forward.z ); + CVAR_SET_STRING( "sv_skyvec_z", szVector ); + + CLight::Spawn( ); +} diff --git a/ricochet/dlls/maprules.cpp b/ricochet/dlls/maprules.cpp new file mode 100644 index 0000000..2e92275 --- /dev/null +++ b/ricochet/dlls/maprules.cpp @@ -0,0 +1,918 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +// ------------------------------------------- +// +// maprules.cpp +// +// This module contains entities for implementing/changing game +// rules dynamically within each map (.BSP) +// +// ------------------------------------------- + +#include "extdll.h" +#include "eiface.h" +#include "util.h" +#include "gamerules.h" +#include "maprules.h" +#include "cbase.h" +#include "player.h" + +class CRuleEntity : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void SetMaster( int iszMaster ) { m_iszMaster = iszMaster; } + +protected: + BOOL CanFireForActivator( CBaseEntity *pActivator ); + +private: + string_t m_iszMaster; +}; + +TYPEDESCRIPTION CRuleEntity::m_SaveData[] = +{ + DEFINE_FIELD( CRuleEntity, m_iszMaster, FIELD_STRING), +}; + +IMPLEMENT_SAVERESTORE( CRuleEntity, CBaseEntity ); + + +void CRuleEntity::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects = EF_NODRAW; +} + + +void CRuleEntity::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "master")) + { + SetMaster( ALLOC_STRING(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +BOOL CRuleEntity::CanFireForActivator( CBaseEntity *pActivator ) +{ + if ( m_iszMaster ) + { + if ( UTIL_IsMasterTriggered( m_iszMaster, pActivator ) ) + return TRUE; + else + return FALSE; + } + + return TRUE; +} + +// +// CRulePointEntity -- base class for all rule "point" entities (not brushes) +// +class CRulePointEntity : public CRuleEntity +{ +public: + void Spawn( void ); +}; + +void CRulePointEntity::Spawn( void ) +{ + CRuleEntity::Spawn(); + pev->frame = 0; + pev->model = 0; +} + +// +// CRuleBrushEntity -- base class for all rule "brush" entities (not brushes) +// Default behavior is to set up like a trigger, invisible, but keep the model for volume testing +// +class CRuleBrushEntity : public CRuleEntity +{ +public: + void Spawn( void ); + +private: +}; + +void CRuleBrushEntity::Spawn( void ) +{ + SET_MODEL( edict(), STRING(pev->model) ); + CRuleEntity::Spawn(); +} + + +// CGameScore / game_score -- award points to player / team +// Points +/- total +// Flag: Allow negative scores SF_SCORE_NEGATIVE +// Flag: Award points to team in teamplay SF_SCORE_TEAM + +#define SF_SCORE_NEGATIVE 0x0001 +#define SF_SCORE_TEAM 0x0002 + +class CGameScore : public CRulePointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline int Points( void ) { return pev->frags; } + inline BOOL AllowNegativeScore( void ) { return pev->spawnflags & SF_SCORE_NEGATIVE; } + inline BOOL AwardToTeam( void ) { return pev->spawnflags & SF_SCORE_TEAM; } + + inline void SetPoints( int points ) { pev->frags = points; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_score, CGameScore ); + + +void CGameScore::Spawn( void ) +{ + CRulePointEntity::Spawn(); +} + + +void CGameScore::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "points")) + { + SetPoints( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CRulePointEntity::KeyValue( pkvd ); +} + + + +void CGameScore::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + // Only players can use this + if ( pActivator->IsPlayer() ) + { + if ( AwardToTeam() ) + { + pActivator->AddPointsToTeam( Points(), AllowNegativeScore() ); + } + else + { + pActivator->AddPoints( Points(), AllowNegativeScore() ); + } + } +} + + +// CGameEnd / game_end -- Ends the game in MP + +class CGameEnd : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +private: +}; + +LINK_ENTITY_TO_CLASS( game_end, CGameEnd ); + + +void CGameEnd::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + g_pGameRules->EndMultiplayerGame(); +} + + +// +// CGameText / game_text -- NON-Localized HUD Message (use env_message to display a titles.txt message) +// Flag: All players SF_ENVTEXT_ALLPLAYERS +// + + +#define SF_ENVTEXT_ALLPLAYERS 0x0001 + + +class CGameText : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + inline BOOL MessageToAll( void ) { return (pev->spawnflags & SF_ENVTEXT_ALLPLAYERS); } + inline void MessageSet( const char *pMessage ) { pev->message = ALLOC_STRING(pMessage); } + inline const char *MessageGet( void ) { return STRING(pev->message); } + +private: + + hudtextparms_t m_textParms; +}; + +LINK_ENTITY_TO_CLASS( game_text, CGameText ); + +// Save parms as a block. Will break save/restore if the structure changes, but this entity didn't ship with Half-Life, so +// it can't impact saved Half-Life games. +TYPEDESCRIPTION CGameText::m_SaveData[] = +{ + DEFINE_ARRAY( CGameText, m_textParms, FIELD_CHARACTER, sizeof(hudtextparms_t) ), +}; + +IMPLEMENT_SAVERESTORE( CGameText, CRulePointEntity ); + + +void CGameText::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "channel")) + { + m_textParms.channel = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "x")) + { + m_textParms.x = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "y")) + { + m_textParms.y = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "effect")) + { + m_textParms.effect = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "color")) + { + int color[4]; + UTIL_StringToIntArray( color, 4, pkvd->szValue ); + m_textParms.r1 = color[0]; + m_textParms.g1 = color[1]; + m_textParms.b1 = color[2]; + m_textParms.a1 = color[3]; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "color2")) + { + int color[4]; + UTIL_StringToIntArray( color, 4, pkvd->szValue ); + m_textParms.r2 = color[0]; + m_textParms.g2 = color[1]; + m_textParms.b2 = color[2]; + m_textParms.a2 = color[3]; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "fadein")) + { + m_textParms.fadeinTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "fadeout")) + { + m_textParms.fadeoutTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "holdtime")) + { + m_textParms.holdTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "fxtime")) + { + m_textParms.fxTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CRulePointEntity::KeyValue( pkvd ); +} + + +void CGameText::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( MessageToAll() ) + { + UTIL_HudMessageAll( m_textParms, MessageGet() ); + } + else + { + if ( pActivator->IsNetClient() ) + { + UTIL_HudMessage( pActivator, m_textParms, MessageGet() ); + } + } +} + + +// +// CGameTeamMaster / game_team_master -- "Masters" like multisource, but based on the team of the activator +// Only allows mastered entity to fire if the team matches my team +// +// team index (pulled from server team list "mp_teamlist" +// Flag: Remove on Fire +// Flag: Any team until set? -- Any team can use this until the team is set (otherwise no teams can use it) +// + +#define SF_TEAMMASTER_FIREONCE 0x0001 +#define SF_TEAMMASTER_ANYTEAM 0x0002 + +class CGameTeamMaster : public CRulePointEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int ObjectCaps( void ) { return CRulePointEntity:: ObjectCaps() | FCAP_MASTER; } + + BOOL IsTriggered( CBaseEntity *pActivator ); + const char *TeamID( void ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_TEAMMASTER_FIREONCE) ? TRUE : FALSE; } + inline BOOL AnyTeam( void ) { return (pev->spawnflags & SF_TEAMMASTER_ANYTEAM) ? TRUE : FALSE; } + +private: + BOOL TeamMatch( CBaseEntity *pActivator ); + + int m_teamIndex; + USE_TYPE triggerType; +}; + +LINK_ENTITY_TO_CLASS( game_team_master, CGameTeamMaster ); + +void CGameTeamMaster::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "teamindex")) + { + m_teamIndex = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "triggerstate")) + { + int type = atoi( pkvd->szValue ); + switch( type ) + { + case 0: + triggerType = USE_OFF; + break; + case 2: + triggerType = USE_TOGGLE; + break; + default: + triggerType = USE_ON; + break; + } + pkvd->fHandled = TRUE; + } + else + CRulePointEntity::KeyValue( pkvd ); +} + + +void CGameTeamMaster::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( useType == USE_SET ) + { + if ( value < 0 ) + { + m_teamIndex = -1; + } + else + { + m_teamIndex = g_pGameRules->GetTeamIndex( pActivator->TeamID() ); + } + return; + } + + if ( TeamMatch( pActivator ) ) + { + SUB_UseTargets( pActivator, triggerType, value ); + if ( RemoveOnFire() ) + UTIL_Remove( this ); + } +} + + +BOOL CGameTeamMaster::IsTriggered( CBaseEntity *pActivator ) +{ + return TeamMatch( pActivator ); +} + + +const char *CGameTeamMaster::TeamID( void ) +{ + if ( m_teamIndex < 0 ) // Currently set to "no team" + return ""; + + return g_pGameRules->GetIndexedTeamName( m_teamIndex ); // UNDONE: Fill this in with the team from the "teamlist" +} + + +BOOL CGameTeamMaster::TeamMatch( CBaseEntity *pActivator ) +{ + if ( m_teamIndex < 0 && AnyTeam() ) + return TRUE; + + if ( !pActivator ) + return FALSE; + + return UTIL_TeamsMatch( pActivator->TeamID(), TeamID() ); +} + + +// +// CGameTeamSet / game_team_set -- Changes the team of the entity it targets to the activator's team +// Flag: Fire once +// Flag: Clear team -- Sets the team to "NONE" instead of activator + +#define SF_TEAMSET_FIREONCE 0x0001 +#define SF_TEAMSET_CLEARTEAM 0x0002 + +class CGameTeamSet : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_TEAMSET_FIREONCE) ? TRUE : FALSE; } + inline BOOL ShouldClearTeam( void ) { return (pev->spawnflags & SF_TEAMSET_CLEARTEAM) ? TRUE : FALSE; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_team_set, CGameTeamSet ); + + +void CGameTeamSet::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( ShouldClearTeam() ) + { + SUB_UseTargets( pActivator, USE_SET, -1 ); + } + else + { + SUB_UseTargets( pActivator, USE_SET, 0 ); + } + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + +// +// CGamePlayerZone / game_player_zone -- players in the zone fire my target when I'm fired +// +// Needs master? +class CGamePlayerZone : public CRuleBrushEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + string_t m_iszInTarget; + string_t m_iszOutTarget; + string_t m_iszInCount; + string_t m_iszOutCount; +}; + +LINK_ENTITY_TO_CLASS( game_zone_player, CGamePlayerZone ); +TYPEDESCRIPTION CGamePlayerZone::m_SaveData[] = +{ + DEFINE_FIELD( CGamePlayerZone, m_iszInTarget, FIELD_STRING ), + DEFINE_FIELD( CGamePlayerZone, m_iszOutTarget, FIELD_STRING ), + DEFINE_FIELD( CGamePlayerZone, m_iszInCount, FIELD_STRING ), + DEFINE_FIELD( CGamePlayerZone, m_iszOutCount, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CGamePlayerZone, CRuleBrushEntity ); + +void CGamePlayerZone::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "intarget")) + { + m_iszInTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "outtarget")) + { + m_iszOutTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "incount")) + { + m_iszInCount = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "outcount")) + { + m_iszOutCount = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CRuleBrushEntity::KeyValue( pkvd ); +} + +void CGamePlayerZone::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int playersInCount = 0; + int playersOutCount = 0; + + if ( !CanFireForActivator( pActivator ) ) + return; + + CBaseEntity *pPlayer = NULL; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + { + TraceResult trace; + int hullNumber; + + hullNumber = human_hull; + if ( pPlayer->pev->flags & FL_DUCKING ) + { + hullNumber = head_hull; + } + + UTIL_TraceModel( pPlayer->pev->origin, pPlayer->pev->origin, hullNumber, edict(), &trace ); + + if ( trace.fStartSolid ) + { + playersInCount++; + if ( m_iszInTarget ) + { + FireTargets( STRING(m_iszInTarget), pPlayer, pActivator, useType, value ); + } + } + else + { + playersOutCount++; + if ( m_iszOutTarget ) + { + FireTargets( STRING(m_iszOutTarget), pPlayer, pActivator, useType, value ); + } + } + } + } + + if ( m_iszInCount ) + { + FireTargets( STRING(m_iszInCount), pActivator, this, USE_SET, playersInCount ); + } + + if ( m_iszOutCount ) + { + FireTargets( STRING(m_iszOutCount), pActivator, this, USE_SET, playersOutCount ); + } +} + + + +// +// CGamePlayerHurt / game_player_hurt -- Damages the player who fires it +// Flag: Fire once + +#define SF_PKILL_FIREONCE 0x0001 +class CGamePlayerHurt : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_PKILL_FIREONCE) ? TRUE : FALSE; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_player_hurt, CGamePlayerHurt ); + + +void CGamePlayerHurt::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( pActivator->IsPlayer() ) + { + if ( pev->dmg < 0 ) + pActivator->TakeHealth( -pev->dmg, DMG_GENERIC ); + else + pActivator->TakeDamage( pev, pev, pev->dmg, DMG_GENERIC ); + } + + SUB_UseTargets( pActivator, useType, value ); + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + + +// +// CGameCounter / game_counter -- Counts events and fires target +// Flag: Fire once +// Flag: Reset on Fire + +#define SF_GAMECOUNT_FIREONCE 0x0001 +#define SF_GAMECOUNT_RESET 0x0002 + +class CGameCounter : public CRulePointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_GAMECOUNT_FIREONCE) ? TRUE : FALSE; } + inline BOOL ResetOnFire( void ) { return (pev->spawnflags & SF_GAMECOUNT_RESET) ? TRUE : FALSE; } + + inline void CountUp( void ) { pev->frags++; } + inline void CountDown( void ) { pev->frags--; } + inline void ResetCount( void ) { pev->frags = pev->dmg; } + inline int CountValue( void ) { return pev->frags; } + inline int LimitValue( void ) { return pev->health; } + + inline BOOL HitLimit( void ) { return CountValue() == LimitValue(); } + +private: + + inline void SetCountValue( int value ) { pev->frags = value; } + inline void SetInitialValue( int value ) { pev->dmg = value; } +}; + +LINK_ENTITY_TO_CLASS( game_counter, CGameCounter ); + +void CGameCounter::Spawn( void ) +{ + // Save off the initial count + SetInitialValue( CountValue() ); + CRulePointEntity::Spawn(); +} + + +void CGameCounter::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + switch( useType ) + { + case USE_ON: + case USE_TOGGLE: + CountUp(); + break; + + case USE_OFF: + CountDown(); + break; + + case USE_SET: + SetCountValue( (int)value ); + break; + } + + if ( HitLimit() ) + { + SUB_UseTargets( pActivator, USE_TOGGLE, 0 ); + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } + + if ( ResetOnFire() ) + { + ResetCount(); + } + } +} + + + +// +// CGameCounterSet / game_counter_set -- Sets the counter's value +// Flag: Fire once + +#define SF_GAMECOUNTSET_FIREONCE 0x0001 + +class CGameCounterSet : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_GAMECOUNTSET_FIREONCE) ? TRUE : FALSE; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_counter_set, CGameCounterSet ); + + +void CGameCounterSet::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + SUB_UseTargets( pActivator, USE_SET, pev->frags ); + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + +// +// CGamePlayerEquip / game_playerequip -- Sets the default player equipment +// Flag: USE Only + +#define SF_PLAYEREQUIP_USEONLY 0x0001 +#define MAX_EQUIP 32 + +class CGamePlayerEquip : public CRulePointEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Touch( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + inline BOOL UseOnly( void ) { return (pev->spawnflags & SF_PLAYEREQUIP_USEONLY) ? TRUE : FALSE; } + +private: + + void EquipPlayer( CBaseEntity *pPlayer ); + + string_t m_weaponNames[MAX_EQUIP]; + int m_weaponCount[MAX_EQUIP]; +}; + +LINK_ENTITY_TO_CLASS( game_player_equip, CGamePlayerEquip ); + + +void CGamePlayerEquip::KeyValue( KeyValueData *pkvd ) +{ + CRulePointEntity::KeyValue( pkvd ); + + if ( !pkvd->fHandled ) + { + for ( int i = 0; i < MAX_EQUIP; i++ ) + { + if ( !m_weaponNames[i] ) + { + char tmp[128]; + + UTIL_StripToken( pkvd->szKeyName, tmp ); + + m_weaponNames[i] = ALLOC_STRING(tmp); + m_weaponCount[i] = atoi(pkvd->szValue); + m_weaponCount[i] = max(1,m_weaponCount[i]); + pkvd->fHandled = TRUE; + break; + } + } + } +} + + +void CGamePlayerEquip::Touch( CBaseEntity *pOther ) +{ + if ( !CanFireForActivator( pOther ) ) + return; + + if ( UseOnly() ) + return; + + EquipPlayer( pOther ); +} + +void CGamePlayerEquip::EquipPlayer( CBaseEntity *pEntity ) +{ + CBasePlayer *pPlayer = NULL; + + if ( pEntity->IsPlayer() ) + { + pPlayer = (CBasePlayer *)pEntity; + } + + if ( !pPlayer ) + return; + + for ( int i = 0; i < MAX_EQUIP; i++ ) + { + if ( !m_weaponNames[i] ) + break; + for ( int j = 0; j < m_weaponCount[i]; j++ ) + { + pPlayer->GiveNamedItem( STRING(m_weaponNames[i]) ); + } + } +} + + +void CGamePlayerEquip::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + EquipPlayer( pActivator ); +} + + +// +// CGamePlayerTeam / game_player_team -- Changes the team of the player who fired it +// Flag: Fire once +// Flag: Kill Player +// Flag: Gib Player + +#define SF_PTEAM_FIREONCE 0x0001 +#define SF_PTEAM_KILL 0x0002 +#define SF_PTEAM_GIB 0x0004 + +class CGamePlayerTeam : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +private: + + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_PTEAM_FIREONCE) ? TRUE : FALSE; } + inline BOOL ShouldKillPlayer( void ) { return (pev->spawnflags & SF_PTEAM_KILL) ? TRUE : FALSE; } + inline BOOL ShouldGibPlayer( void ) { return (pev->spawnflags & SF_PTEAM_GIB) ? TRUE : FALSE; } + + const char *TargetTeamName( const char *pszTargetName ); +}; + +LINK_ENTITY_TO_CLASS( game_player_team, CGamePlayerTeam ); + + +const char *CGamePlayerTeam::TargetTeamName( const char *pszTargetName ) +{ + CBaseEntity *pTeamEntity = NULL; + + while ((pTeamEntity = UTIL_FindEntityByTargetname( pTeamEntity, pszTargetName )) != NULL) + { + if ( FClassnameIs( pTeamEntity->pev, "game_team_master" ) ) + return pTeamEntity->TeamID(); + } + + return NULL; +} + + +void CGamePlayerTeam::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( pActivator->IsPlayer() ) + { + const char *pszTargetTeam = TargetTeamName( STRING(pev->target) ); + if ( pszTargetTeam ) + { + CBasePlayer *pPlayer = (CBasePlayer *)pActivator; + g_pGameRules->ChangePlayerTeam( pPlayer, pszTargetTeam, ShouldKillPlayer(), ShouldGibPlayer() ); + } + } + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + diff --git a/ricochet/dlls/maprules.h b/ricochet/dlls/maprules.h new file mode 100644 index 0000000..9c50dcc --- /dev/null +++ b/ricochet/dlls/maprules.h @@ -0,0 +1,22 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef MAPRULES_H +#define MAPRULES_H + + + +#endif // MAPRULES_H + diff --git a/ricochet/dlls/monsterevent.h b/ricochet/dlls/monsterevent.h new file mode 100644 index 0000000..27bcb05 --- /dev/null +++ b/ricochet/dlls/monsterevent.h @@ -0,0 +1,34 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef MONSTEREVENT_H +#define MONSTEREVENT_H + +typedef struct +{ + int event; + char *options; +} MonsterEvent_t; + +#define EVENT_SPECIFIC 0 +#define EVENT_SCRIPTED 1000 +#define EVENT_SHARED 2000 +#define EVENT_CLIENT 5000 + +#define MONSTER_EVENT_BODYDROP_LIGHT 2001 +#define MONSTER_EVENT_BODYDROP_HEAVY 2002 + +#define MONSTER_EVENT_SWISHSOUND 2010 + +#endif // MONSTEREVENT_H diff --git a/ricochet/dlls/monsters.h b/ricochet/dlls/monsters.h new file mode 100644 index 0000000..f46ae52 --- /dev/null +++ b/ricochet/dlls/monsters.h @@ -0,0 +1,88 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef MONSTERS_H +#include "skill.h" +#define MONSTERS_H + +/* + +===== monsters.h ======================================================== + + Header file for monster-related utility code + +*/ + +// Hit Group standards +#define HITGROUP_GENERIC 0 +#define HITGROUP_HEAD 1 +#define HITGROUP_CHEST 2 +#define HITGROUP_STOMACH 3 +#define HITGROUP_LEFTARM 4 +#define HITGROUP_RIGHTARM 5 +#define HITGROUP_LEFTLEG 6 +#define HITGROUP_RIGHTLEG 7 + + +// spawn flags 256 and above are already taken by the engine +extern void UTIL_MoveToOrigin( edict_t* pent, const Vector &vecGoal, float flDist, int iMoveType ); + +Vector VecCheckToss ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flGravityAdj = 1.0 ); +Vector VecCheckThrow ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flSpeed, float flGravityAdj = 1.0 ); +extern DLL_GLOBAL Vector g_vecAttackDir; +extern DLL_GLOBAL CONSTANT float g_flMeleeRange; +extern DLL_GLOBAL CONSTANT float g_flMediumRange; +extern DLL_GLOBAL CONSTANT float g_flLongRange; +extern void EjectBrass (const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int model, int soundtype ); +extern void ExplodeModel( const Vector &vecOrigin, float speed, int model, int count ); + +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget ); +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget, Vector &vecTargetOrigin, float flSize = 0.0 ); + +// monster to monster relationship types +#define R_AL -2 // (ALLY) pals. Good alternative to R_NO when applicable. +#define R_FR -1// (FEAR)will run +#define R_NO 0// (NO RELATIONSHIP) disregard +#define R_DL 1// (DISLIKE) will attack +#define R_HT 2// (HATE)will attack this character instead of any visible DISLIKEd characters +#define R_NM 3// (NEMESIS) A monster Will ALWAYS attack its nemsis, no matter what + + +#define bits_MEMORY_KILLED ( 1 << 7 )// HACKHACK -- remember that I've already called my Killed() + +// +// A gib is a chunk of a body, or a piece of wood/metal/rocks/etc. +// +class CGib : public CBaseEntity +{ +public: + void Spawn( const char *szGibModel ); + void EXPORT BounceGibTouch ( CBaseEntity *pOther ); + void EXPORT StickyGibTouch ( CBaseEntity *pOther ); + void EXPORT WaitTillLand( void ); + void LimitVelocity( void ); + + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DONT_SAVE; } + static void SpawnHeadGib( entvars_t *pevVictim ); + static void SpawnRandomGibs( entvars_t *pevVictim, int cGibs, int human ); + static void SpawnStickyGibs( entvars_t *pevVictim, Vector vecOrigin, int cGibs ); + + int m_bloodColor; + int m_cBloodDecals; + int m_material; + float m_lifeTime; +}; + + +#endif //MONSTERS_H diff --git a/ricochet/dlls/mortar.cpp b/ricochet/dlls/mortar.cpp new file mode 100644 index 0000000..22f48b5 --- /dev/null +++ b/ricochet/dlls/mortar.cpp @@ -0,0 +1,323 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== mortar.cpp ======================================================== + + the "LaBuznik" mortar device + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "weapons.h" +#include "decals.h" +#include "soundent.h" + +class CFuncMortarField : public CBaseToggle +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + // Bmodels don't go across transitions + virtual int ObjectCaps( void ) { return CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + void EXPORT FieldUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int m_iszXController; + int m_iszYController; + float m_flSpread; + float m_flDelay; + int m_iCount; + int m_fControl; +}; + +LINK_ENTITY_TO_CLASS( func_mortar_field, CFuncMortarField ); + +TYPEDESCRIPTION CFuncMortarField::m_SaveData[] = +{ + DEFINE_FIELD( CFuncMortarField, m_iszXController, FIELD_STRING ), + DEFINE_FIELD( CFuncMortarField, m_iszYController, FIELD_STRING ), + DEFINE_FIELD( CFuncMortarField, m_flSpread, FIELD_FLOAT ), + DEFINE_FIELD( CFuncMortarField, m_flDelay, FIELD_FLOAT ), + DEFINE_FIELD( CFuncMortarField, m_iCount, FIELD_INTEGER ), + DEFINE_FIELD( CFuncMortarField, m_fControl, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CFuncMortarField, CBaseToggle ); + + +void CFuncMortarField :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszXController")) + { + m_iszXController = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszYController")) + { + m_iszYController = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flSpread")) + { + m_flSpread = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fControl")) + { + m_fControl = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iCount")) + { + m_iCount = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } +} + + +// Drop bombs from above +void CFuncMortarField :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->movetype = MOVETYPE_NONE; + SetBits( pev->effects, EF_NODRAW ); + SetUse( &CFuncMortarField::FieldUse ); + Precache(); +} + + +void CFuncMortarField :: Precache( void ) +{ + PRECACHE_SOUND ("weapons/mortar.wav"); + PRECACHE_SOUND ("weapons/mortarhit.wav"); + PRECACHE_MODEL( "sprites/lgtning.spr" ); +} + + +// If connected to a table, then use the table controllers, else hit where the trigger is. +void CFuncMortarField :: FieldUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + Vector vecStart; + + vecStart.x = RANDOM_FLOAT( pev->mins.x, pev->maxs.x ); + vecStart.y = RANDOM_FLOAT( pev->mins.y, pev->maxs.y ); + vecStart.z = pev->maxs.z; + + switch( m_fControl ) + { + case 0: // random + break; + case 1: // Trigger Activator + if (pActivator != NULL) + { + vecStart.x = pActivator->pev->origin.x; + vecStart.y = pActivator->pev->origin.y; + } + break; + case 2: // table + { + CBaseEntity *pController; + + if (!FStringNull(m_iszXController)) + { + pController = UTIL_FindEntityByTargetname( NULL, STRING(m_iszXController)); + if (pController != NULL) + { + vecStart.x = pev->mins.x + pController->pev->ideal_yaw * (pev->size.x); + } + } + if (!FStringNull(m_iszYController)) + { + pController = UTIL_FindEntityByTargetname( NULL, STRING(m_iszYController)); + if (pController != NULL) + { + vecStart.y = pev->mins.y + pController->pev->ideal_yaw * (pev->size.y); + } + } + } + break; + } + + int pitch = RANDOM_LONG(95,124); + + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "weapons/mortar.wav", 1.0, ATTN_NONE, 0, pitch); + + float t = 2.5; + for (int i = 0; i < m_iCount; i++) + { + Vector vecSpot = vecStart; + vecSpot.x += RANDOM_FLOAT( -m_flSpread, m_flSpread ); + vecSpot.y += RANDOM_FLOAT( -m_flSpread, m_flSpread ); + + TraceResult tr; + UTIL_TraceLine( vecSpot, vecSpot + Vector( 0, 0, -1 ) * 4096, ignore_monsters, ENT(pev), &tr ); + + edict_t *pentOwner = NULL; + if (pActivator) pentOwner = pActivator->edict(); + + CBaseEntity *pMortar = Create("monster_mortar", tr.vecEndPos, Vector( 0, 0, 0 ), pentOwner ); + pMortar->pev->nextthink = gpGlobals->time + t; + t += RANDOM_FLOAT( 0.2, 0.5 ); + + if (i == 0) + CSoundEnt::InsertSound ( bits_SOUND_DANGER, tr.vecEndPos, 400, 0.3 ); + } +} + + +class CMortar : public CGrenade +{ +public: + void Spawn( void ); + void Precache( void ); + + void EXPORT MortarExplode( void ); + + int m_spriteTexture; +}; + +LINK_ENTITY_TO_CLASS( monster_mortar, CMortar ); + +void CMortar::Spawn( ) +{ + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT; + + pev->dmg = 200; + + SetThink( &CMortar::MortarExplode ); + pev->nextthink = 0; + + Precache( ); + + +} + + +void CMortar::Precache( ) +{ + m_spriteTexture = PRECACHE_MODEL( "sprites/lgtning.spr" ); +} + +void CMortar::MortarExplode( void ) +{ +#if 1 + // mortar beam + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS ); + WRITE_COORD(pev->origin.x); + WRITE_COORD(pev->origin.y); + WRITE_COORD(pev->origin.z); + WRITE_COORD(pev->origin.x); + WRITE_COORD(pev->origin.y); + WRITE_COORD(pev->origin.z + 1024); + WRITE_SHORT(m_spriteTexture ); + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 1 ); // life + WRITE_BYTE( 40 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 160 ); // r, g, b + WRITE_BYTE( 100 ); // r, g, b + WRITE_BYTE( 128 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); +#endif + +#if 0 + // blast circle + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMTORUS); + WRITE_COORD(pev->origin.x); + WRITE_COORD(pev->origin.y); + WRITE_COORD(pev->origin.z + 32); + WRITE_COORD(pev->origin.x); + WRITE_COORD(pev->origin.y); + WRITE_COORD(pev->origin.z + 32 + pev->dmg * 2 / .2); // reach damage radius over .3 seconds + WRITE_SHORT(m_spriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 2 ); // life + WRITE_BYTE( 12 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 160 ); // r, g, b + WRITE_BYTE( 100 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); +#endif + + TraceResult tr; + UTIL_TraceLine( pev->origin + Vector( 0, 0, 1024 ), pev->origin - Vector( 0, 0, 1024 ), dont_ignore_monsters, ENT(pev), &tr ); + + Explode( &tr, DMG_BLAST | DMG_MORTAR ); + UTIL_ScreenShake( tr.vecEndPos, 25.0, 150.0, 1.0, 750 ); + +#if 0 + int pitch = RANDOM_LONG(95,124); + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "weapons/mortarhit.wav", 1.0, 0.55, 0, pitch); + + // ForceSound( SNDRADIUS_MP5, bits_SOUND_COMBAT ); + + // ExplodeModel( pev->origin, 400, g_sModelIndexShrapnel, 30 ); + + RadiusDamage ( pev, VARS(pev->owner), pev->dmg, CLASS_NONE, DMG_BLAST ); + + /* + if ( RANDOM_FLOAT ( 0 , 1 ) < 0.5 ) + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH1 ); + } + else + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH2 ); + } + */ + + SetThink( &CMortar::SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; +#endif + +} + + +#if 0 +void CMortar::ShootTimed( EVARS *pevOwner, Vector vecStart, float time ) +{ + CMortar *pMortar = GetClassPtr( (CMortar *)NULL ); + pMortar->Spawn(); + + TraceResult tr; + UTIL_TraceLine( vecStart, vecStart + Vector( 0, 0, -1 ) * 4096, ignore_monsters, ENT(pMortar->pev), &tr ); + + pMortar->pev->nextthink = gpGlobals->time + time; + + UTIL_SetOrigin( pMortar->pev, tr.vecEndPos ); +} +#endif diff --git a/ricochet/dlls/mp.def b/ricochet/dlls/mp.def new file mode 100644 index 0000000..2a12a7c --- /dev/null +++ b/ricochet/dlls/mp.def @@ -0,0 +1,5 @@ +LIBRARY mp +EXPORTS + GiveFnptrsToDll @1 +SECTIONS + .data READ WRITE diff --git a/ricochet/dlls/mp.dsp b/ricochet/dlls/mp.dsp new file mode 100644 index 0000000..7cfcc19 --- /dev/null +++ b/ricochet/dlls/mp.dsp @@ -0,0 +1,516 @@ +# Microsoft Developer Studio Project File - Name="mp" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=mp - 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 "mp.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 "mp.mak" CFG="mp - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mp - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mp - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/GoldSrc/discwar/dlls", HDKCAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "mp - 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 ".\Releasemp" +# PROP Intermediate_Dir ".\Releasemp" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /G5 /MT /W3 /GX /Zi /O2 /I "." /I "..\dlls" /I "..\..\engine" /I "..\..\common" /I "..\engine" /I "..\common" /I "..\..\public" /I "..\..\game_shared" /I "..\pm_shared" /I "..\\" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "VALVE_DLL" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /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 /subsystem:windows /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /map /debug /machine:I386 /def:".\mp.def" +# SUBTRACT LINK32 /profile +# Begin Custom Build - Copying to \half-life\ricochet\dlls +InputDir=.\Releasemp +ProjDir=. +InputPath=.\Releasemp\mp.dll +InputName=mp +SOURCE="$(InputPath)" + +BuildCmds= \ + call ..\..\filecopy.bat $(InputPath) $(ProjDir)\..\..\..\game\mod\dlls\$(InputName).dll \ + call ..\..\filecopy.bat $(InputDir)\$(InputName).pdb $(ProjDir)\..\..\..\game\mod\dlls\$(InputName).pdb \ + + +"$(ProjDir)\..\..\..\game\mod\dlls\$(InputName).dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"$(ProjDir)\..\..\..\game\mod\dlls\$(InputName).pdb" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) +# End Custom Build + +!ELSEIF "$(CFG)" == "mp - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\mp___Win" +# PROP BASE Intermediate_Dir ".\mp___Win" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir ".\debugmp" +# PROP Intermediate_Dir ".\debugmp" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /G5 /MTd /W3 /Gm /GX /ZI /Od /I "..\..\engine" /I "..\..\common" /I "..\engine" /I "..\common" /I "..\..\public" /I "." /I "..\..\game_shared" /I "..\dlls" /I "..\pm_shared" /I "..\\" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "VALVE_DLL" /FR /YX /FD /c +# ADD BASE MTL /nologo /D "_DEBUG" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /i "..\engine" /d "_DEBUG" +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 /subsystem:windows /dll /debug /machine:I386 +# ADD LINK32 user32.lib advapi32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /def:".\mp.def" /implib:".\Debug\mp.lib" +# SUBTRACT LINK32 /profile +# Begin Custom Build - Copying to \quiver\ricochet\dlls +ProjDir=. +InputPath=.\debugmp\mp.dll +InputName=mp +SOURCE="$(InputPath)" + +"$(ProjDir)\..\..\..\game\mod\dlls\$(InputName).dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + call ..\..\filecopy.bat $(InputPath) $(ProjDir)\..\..\..\game\mod\dlls\$(InputName).dll + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "mp - Win32 Release" +# Name "mp - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=.\airtank.cpp +# End Source File +# Begin Source File + +SOURCE=.\animating.cpp +# End Source File +# Begin Source File + +SOURCE=.\animation.cpp +# End Source File +# Begin Source File + +SOURCE=.\bmodels.cpp +# End Source File +# Begin Source File + +SOURCE=.\buttons.cpp +# End Source File +# Begin Source File + +SOURCE=.\cbase.cpp +# End Source File +# Begin Source File + +SOURCE=.\client.cpp +# End Source File +# Begin Source File + +SOURCE=.\combat.cpp +# End Source File +# Begin Source File + +SOURCE=.\disc_arena.cpp +# End Source File +# Begin Source File + +SOURCE=.\disc_powerups.cpp +# End Source File +# Begin Source File + +SOURCE=.\wpn_shared\disc_weapon_disc.cpp +# End Source File +# Begin Source File + +SOURCE=.\doors.cpp +# End Source File +# Begin Source File + +SOURCE=.\effects.cpp +# End Source File +# Begin Source File + +SOURCE=.\explode.cpp +# End Source File +# Begin Source File + +SOURCE=.\func_break.cpp +# End Source File +# Begin Source File + +SOURCE=.\func_tank.cpp +# End Source File +# Begin Source File + +SOURCE=.\game.cpp +# End Source File +# Begin Source File + +SOURCE=.\gamerules.cpp +# End Source File +# Begin Source File + +SOURCE=.\ggrenade.cpp +# End Source File +# Begin Source File + +SOURCE=.\globals.cpp +# End Source File +# Begin Source File + +SOURCE=.\h_ai.cpp +# End Source File +# Begin Source File + +SOURCE=.\h_battery.cpp +# End Source File +# Begin Source File + +SOURCE=.\h_cycler.cpp +# End Source File +# Begin Source File + +SOURCE=.\h_export.cpp +# End Source File +# Begin Source File + +SOURCE=.\healthkit.cpp +# End Source File +# Begin Source File + +SOURCE=.\items.cpp +# End Source File +# Begin Source File + +SOURCE=.\lights.cpp +# End Source File +# Begin Source File + +SOURCE=.\maprules.cpp +# End Source File +# Begin Source File + +SOURCE=.\mortar.cpp +# End Source File +# Begin Source File + +SOURCE=.\mpstubb.cpp +# End Source File +# Begin Source File + +SOURCE=.\multiplay_gamerules.cpp +# End Source File +# Begin Source File + +SOURCE=.\observer.cpp +# End Source File +# Begin Source File + +SOURCE=.\pathcorner.cpp +# End Source File +# Begin Source File + +SOURCE=.\plane.cpp +# End Source File +# Begin Source File + +SOURCE=.\plats.cpp +# End Source File +# Begin Source File + +SOURCE=.\player.cpp +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_debug.c +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_math.c +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_shared.c +# End Source File +# Begin Source File + +SOURCE=.\singleplay_gamerules.cpp +# End Source File +# Begin Source File + +SOURCE=.\skill.cpp +# End Source File +# Begin Source File + +SOURCE=.\sound.cpp +# End Source File +# Begin Source File + +SOURCE=.\soundent.cpp +# End Source File +# Begin Source File + +SOURCE=.\spectator.cpp +# End Source File +# Begin Source File + +SOURCE=.\subs.cpp +# End Source File +# Begin Source File + +SOURCE=.\teamplay_gamerules.cpp +# End Source File +# Begin Source File + +SOURCE=.\triggers.cpp +# End Source File +# Begin Source File + +SOURCE=.\util.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\voice_gamemgr.cpp +# End Source File +# Begin Source File + +SOURCE=.\weapons.cpp +# End Source File +# Begin Source File + +SOURCE=.\world.cpp +# End Source File +# Begin Source File + +SOURCE=.\xen.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=.\activity.h +# End Source File +# Begin Source File + +SOURCE=.\activitymap.h +# End Source File +# Begin Source File + +SOURCE=.\animation.h +# End Source File +# Begin Source File + +SOURCE=.\basemonster.h +# End Source File +# Begin Source File + +SOURCE=.\cbase.h +# End Source File +# Begin Source File + +SOURCE=.\cdll_dll.h +# End Source File +# Begin Source File + +SOURCE=.\client.h +# End Source File +# Begin Source File + +SOURCE=.\decals.h +# End Source File +# Begin Source File + +SOURCE=.\disc_arena.h +# End Source File +# Begin Source File + +SOURCE=.\discwar.h +# End Source File +# Begin Source File + +SOURCE=.\doors.h +# End Source File +# Begin Source File + +SOURCE=.\effects.h +# End Source File +# Begin Source File + +SOURCE=.\enginecallback.h +# End Source File +# Begin Source File + +SOURCE=.\explode.h +# End Source File +# Begin Source File + +SOURCE=.\extdll.h +# End Source File +# Begin Source File + +SOURCE=.\func_break.h +# End Source File +# Begin Source File + +SOURCE=.\game.h +# End Source File +# Begin Source File + +SOURCE=.\gamerules.h +# End Source File +# Begin Source File + +SOURCE=.\hornet.h +# End Source File +# Begin Source File + +SOURCE=.\items.h +# End Source File +# Begin Source File + +SOURCE=.\maprules.h +# End Source File +# Begin Source File + +SOURCE=.\monsterevent.h +# End Source File +# Begin Source File + +SOURCE=.\monsters.h +# End Source File +# Begin Source File + +SOURCE=.\nodes.h +# End Source File +# Begin Source File + +SOURCE=.\plane.h +# End Source File +# Begin Source File + +SOURCE=.\player.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_debug.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_defs.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_info.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_materials.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_movevars.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_shared.h +# End Source File +# Begin Source File + +SOURCE=.\saverestore.h +# End Source File +# Begin Source File + +SOURCE=.\schedule.h +# End Source File +# Begin Source File + +SOURCE=.\scriptevent.h +# End Source File +# Begin Source File + +SOURCE=.\skill.h +# End Source File +# Begin Source File + +SOURCE=.\soundent.h +# End Source File +# Begin Source File + +SOURCE=.\spectator.h +# End Source File +# Begin Source File + +SOURCE=.\talkmonster.h +# End Source File +# Begin Source File + +SOURCE=.\teamplay_gamerules.h +# End Source File +# Begin Source File + +SOURCE=.\trains.h +# End Source File +# Begin Source File + +SOURCE=.\util.h +# End Source File +# Begin Source File + +SOURCE=.\vector.h +# End Source File +# Begin Source File + +SOURCE=.\weapons.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/ricochet/dlls/mpstubb.cpp b/ricochet/dlls/mpstubb.cpp new file mode 100644 index 0000000..41631f0 --- /dev/null +++ b/ricochet/dlls/mpstubb.cpp @@ -0,0 +1,264 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "soundent.h" +#include "nodes.h" +#include "talkmonster.h" + + +float CTalkMonster::g_talkWaitTime = 0; // time delay until it's ok to speak: used so that two NPCs don't talk at once + +/*********************************************************/ + + +CGraph WorldGraph; +void CGraph :: InitGraph( void ) { } +int CGraph :: FLoadGraph ( char *szMapName ) { return FALSE; } +int CGraph :: AllocNodes ( void ) { return FALSE; } +int CGraph :: CheckNODFile ( char *szMapName ) { return FALSE; } +int CGraph :: FSetGraphPointers ( void ) { return 0; } +void CGraph :: ShowNodeConnections ( int iNode ) { } +int CGraph :: FindNearestNode ( const Vector &vecOrigin, int afNodeTypes ) { return 0; } + + +/*********************************************************/ + + +void CBaseMonster :: ReportAIState( void ) { } +float CBaseMonster :: ChangeYaw ( int speed ) { return 0; } +void CBaseMonster :: MakeIdealYaw( Vector vecTarget ) { } + + +void CBaseMonster::CorpseFallThink( void ) +{ + if ( pev->flags & FL_ONGROUND ) + { + SetThink ( NULL ); + + SetSequenceBox( ); + UTIL_SetOrigin( pev, pev->origin );// link into world. + } + else + pev->nextthink = gpGlobals->time + 0.1; +} +// Call after animation/pose is set up +void CBaseMonster :: MonsterInitDead( void ) +{ + InitBoneControllers(); + + pev->solid = SOLID_BBOX; + pev->movetype = MOVETYPE_TOSS;// so he'll fall to ground + + pev->frame = 0; + ResetSequenceInfo( ); + pev->framerate = 0; + + // Copy health + pev->max_health = pev->health; + pev->deadflag = DEAD_DEAD; + + UTIL_SetSize(pev, g_vecZero, g_vecZero ); + UTIL_SetOrigin( pev, pev->origin ); + + // Setup health counters, etc. + BecomeDead(); + SetThink( &CBaseMonster::CorpseFallThink ); + pev->nextthink = gpGlobals->time + 0.5; +} + + +BOOL CBaseMonster :: ShouldFadeOnDeath( void ) +{ + return FALSE; +} + +BOOL CBaseMonster :: FCheckAITrigger ( void ) +{ + return FALSE; +} + +void CBaseMonster :: KeyValue( KeyValueData *pkvd ) +{ + CBaseToggle::KeyValue( pkvd ); +} + +int CBaseMonster::IRelationship ( CBaseEntity *pTarget ) +{ + static int iEnemy[14][14] = + { // NONE MACH PLYR HPASS HMIL AMIL APASS AMONST APREY APRED INSECT PLRALY PBWPN ABWPN + /*NONE*/ { R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO, R_NO, R_NO }, + /*MACHINE*/ { R_NO ,R_NO ,R_DL ,R_DL ,R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_DL, R_DL, R_DL }, + /*PLAYER*/ { R_NO ,R_DL ,R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO, R_DL, R_DL }, + /*HUMANPASSIVE*/{ R_NO ,R_NO ,R_AL ,R_AL ,R_HT ,R_FR ,R_NO ,R_HT ,R_DL ,R_FR ,R_NO ,R_AL, R_NO, R_NO }, + /*HUMANMILITAR*/{ R_NO ,R_NO ,R_HT ,R_DL ,R_NO ,R_HT ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_HT, R_NO, R_NO }, + /*ALIENMILITAR*/{ R_NO ,R_DL ,R_HT ,R_DL ,R_HT ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_DL, R_NO, R_NO }, + /*ALIENPASSIVE*/{ R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO, R_NO, R_NO }, + /*ALIENMONSTER*/{ R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_DL, R_NO, R_NO }, + /*ALIENPREY */{ R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO ,R_NO ,R_NO ,R_FR ,R_NO ,R_DL, R_NO, R_NO }, + /*ALIENPREDATO*/{ R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO ,R_NO ,R_HT ,R_DL ,R_NO ,R_DL, R_NO, R_NO }, + /*INSECT*/ { R_FR ,R_FR ,R_FR ,R_FR ,R_FR ,R_NO ,R_FR ,R_FR ,R_FR ,R_FR ,R_NO ,R_FR, R_NO, R_NO }, + /*PLAYERALLY*/ { R_NO ,R_DL ,R_AL ,R_AL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO, R_NO, R_NO }, + /*PBIOWEAPON*/ { R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_DL, R_NO, R_DL }, + /*ABIOWEAPON*/ { R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_AL ,R_NO ,R_DL ,R_DL ,R_NO ,R_NO ,R_DL, R_DL, R_NO } + }; + + return iEnemy[ Classify() ][ pTarget->Classify() ]; +} + + +//========================================================= +// Look - Base class monster function to find enemies or +// food by sight. iDistance is distance ( in units ) that the +// monster can see. +// +// Sets the sight bits of the m_afConditions mask to indicate +// which types of entities were sighted. +// Function also sets the Looker's m_pLink +// to the head of a link list that contains all visible ents. +// (linked via each ent's m_pLink field) +// +//========================================================= +void CBaseMonster :: Look ( int iDistance ) +{ + int iSighted = 0; + + // DON'T let visibility information from last frame sit around! + ClearConditions(bits_COND_SEE_HATE | bits_COND_SEE_DISLIKE | bits_COND_SEE_ENEMY | bits_COND_SEE_FEAR | bits_COND_SEE_NEMESIS | bits_COND_SEE_CLIENT); + + m_pLink = NULL; + + CBaseEntity *pSightEnt = NULL;// the current visible entity that we're dealing with + + CBaseEntity *pList[100]; + + Vector delta = Vector( iDistance, iDistance, iDistance ); + + // Find only monsters/clients in box, NOT limited to PVS + int count = UTIL_EntitiesInBox( pList, 100, pev->origin - delta, pev->origin + delta, FL_CLIENT|FL_MONSTER ); + for ( int i = 0; i < count; i++ ) + { + pSightEnt = pList[i]; + if ( pSightEnt != this && pSightEnt->pev->health > 0 ) + { + // the looker will want to consider this entity + // don't check anything else about an entity that can't be seen, or an entity that you don't care about. + if ( IRelationship( pSightEnt ) != R_NO && FInViewCone( pSightEnt ) && !FBitSet( pSightEnt->pev->flags, FL_NOTARGET ) && FVisible( pSightEnt ) ) + { + if ( pSightEnt->IsPlayer() ) + { + // if we see a client, remember that (mostly for scripted AI) + iSighted |= bits_COND_SEE_CLIENT; + } + + pSightEnt->m_pLink = m_pLink; + m_pLink = pSightEnt; + + if ( pSightEnt == m_hEnemy ) + { + // we know this ent is visible, so if it also happens to be our enemy, store that now. + iSighted |= bits_COND_SEE_ENEMY; + } + + // don't add the Enemy's relationship to the conditions. We only want to worry about conditions when + // we see monsters other than the Enemy. + switch ( IRelationship ( pSightEnt ) ) + { + case R_NM: + iSighted |= bits_COND_SEE_NEMESIS; + break; + case R_HT: + iSighted |= bits_COND_SEE_HATE; + break; + case R_DL: + iSighted |= bits_COND_SEE_DISLIKE; + break; + case R_FR: + iSighted |= bits_COND_SEE_FEAR; + break; + case R_AL: + break; + default: + ALERT ( at_aiconsole, "%s can't assess %s\n", STRING(pev->classname), STRING(pSightEnt->pev->classname ) ); + break; + } + } + } + } + + SetConditions( iSighted ); +} + + +//========================================================= +// BestVisibleEnemy - this functions searches the link +// list whose head is the caller's m_pLink field, and returns +// a pointer to the enemy entity in that list that is nearest the +// caller. +// +// !!!UNDONE - currently, this only returns the closest enemy. +// we'll want to consider distance, relationship, attack types, back turned, etc. +//========================================================= +CBaseEntity *CBaseMonster :: BestVisibleEnemy ( void ) +{ + CBaseEntity *pReturn; + CBaseEntity *pNextEnt; + int iNearest; + int iDist; + int iBestRelationship; + + iNearest = 8192;// so first visible entity will become the closest. + pNextEnt = m_pLink; + pReturn = NULL; + iBestRelationship = R_NO; + + while ( pNextEnt != NULL ) + { + if ( pNextEnt->IsAlive() ) + { + if ( IRelationship( pNextEnt) > iBestRelationship ) + { + // this entity is disliked MORE than the entity that we + // currently think is the best visible enemy. No need to do + // a distance check, just get mad at this one for now. + iBestRelationship = IRelationship ( pNextEnt ); + iNearest = ( pNextEnt->pev->origin - pev->origin ).Length(); + pReturn = pNextEnt; + } + else if ( IRelationship( pNextEnt) == iBestRelationship ) + { + // this entity is disliked just as much as the entity that + // we currently think is the best visible enemy, so we only + // get mad at it if it is closer. + iDist = ( pNextEnt->pev->origin - pev->origin ).Length(); + + if ( iDist <= iNearest ) + { + iNearest = iDist; + iBestRelationship = IRelationship ( pNextEnt ); + pReturn = pNextEnt; + } + } + } + + pNextEnt = pNextEnt->m_pLink; + } + + return pReturn; +} diff --git a/ricochet/dlls/multiplay_gamerules.cpp b/ricochet/dlls/multiplay_gamerules.cpp new file mode 100644 index 0000000..440aea4 --- /dev/null +++ b/ricochet/dlls/multiplay_gamerules.cpp @@ -0,0 +1,1620 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.cpp +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "game.h" +#include "items.h" +#include "monsters.h" +#include "discwar.h" +#include "voice_gamemgr.h" + +#if !defined ( _WIN32 ) +#include +#endif + +extern DLL_GLOBAL CGameRules *g_pGameRules; +extern DLL_GLOBAL BOOL g_fGameOver; +extern int gmsgDeathMsg; // client dll messages +extern int gmsgScoreInfo; +extern int gmsgMOTD; +extern int gmsgSpectator; +extern int gmsgReward; + +extern int g_iMapTurnedOffArena; + +#define ITEM_RESPAWN_TIME 30 +#define WEAPON_RESPAWN_TIME 20 +#define AMMO_RESPAWN_TIME 20 + +CVoiceGameMgr g_VoiceGameMgr; + +int InArenaMode( void ); + +class CMultiplayGameMgrHelper : public IVoiceGameMgrHelper +{ +public: + virtual bool CanPlayerHearPlayer(CBasePlayer *pListener, CBasePlayer *pTalker) + { + //FFA + if ( InArenaMode() == FALSE ) + return true; + + if ( pListener->m_pCurrentArena == NULL && pTalker->m_pCurrentArena == NULL ) + return true; //Both spectating + + if ( pListener->m_pCurrentArena == pTalker->m_pCurrentArena ) + return true; //Same arena + + return false; + } +}; +static CMultiplayGameMgrHelper g_GameMgrHelper; + +//********************************************************* +// Rules for the half-life multiplayer game. +//********************************************************* + +CHalfLifeMultiplay :: CHalfLifeMultiplay() +{ + g_VoiceGameMgr.Init(&g_GameMgrHelper, gpGlobals->maxClients); + + RefreshSkillData(); + m_flIntermissionEndTime = 0; + + // 11/8/98 + // Modified by YWB: Server .cfg file is now a cvar, so that + // server ops can run multiple game servers, with different server .cfg files, + // from a single installed directory. + // Mapcyclefile is already a cvar. + + // 3/31/99 + // Added lservercfg file cvar, since listen and dedicated servers should not + // share a single config file. (sjb) + if ( IS_DEDICATED_SERVER() ) + { + // this code has been moved into engine, to only run server.cfg once + } + else + { + // listen server + char *lservercfgfile = (char *)CVAR_GET_STRING( "lservercfgfile" ); + + if ( lservercfgfile && lservercfgfile[0] ) + { + char szCommand[256]; + + ALERT( at_console, "Executing listen server config file\n" ); + sprintf( szCommand, "exec %s\n", lservercfgfile ); + SERVER_COMMAND( szCommand ); + } + } +} + +BOOL CHalfLifeMultiplay::ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) +{ + if(g_VoiceGameMgr.ClientCommand(pPlayer, pcmd)) + return TRUE; + + return CGameRules::ClientCommand(pPlayer, pcmd); +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay::RefreshSkillData( void ) +{ +// load all default values + CGameRules::RefreshSkillData(); + +// override some values for multiplay. + + // suitcharger + gSkillData.suitchargerCapacity = 30; + + // Crowbar whack + gSkillData.plrDmgCrowbar = 25; + + // Glock Round + gSkillData.plrDmg9MM = 12; + + // 357 Round + gSkillData.plrDmg357 = 40; + + // MP5 Round + gSkillData.plrDmgMP5 = 12; + + // M203 grenade + gSkillData.plrDmgM203Grenade = 100; + + // Shotgun buckshot + gSkillData.plrDmgBuckshot = 20;// fewer pellets in deathmatch + + // Crossbow + gSkillData.plrDmgCrossbowClient = 20; + + // RPG + gSkillData.plrDmgRPG = 120; + + // Egon + gSkillData.plrDmgEgonWide = 20; + gSkillData.plrDmgEgonNarrow = 10; + + // Hand Grendade + gSkillData.plrDmgHandGrenade = 100; + + // Satchel Charge + gSkillData.plrDmgSatchel = 120; + + // Tripmine + gSkillData.plrDmgTripmine = 150; + + // hornet + gSkillData.plrDmgHornet = 10; +} + +// longest the intermission can last, in seconds +#define MAX_INTERMISSION_TIME 120 + +extern cvar_t timeleft, fragsleft; +//========================================================= +//========================================================= +void CHalfLifeMultiplay :: Think ( void ) +{ + g_VoiceGameMgr.Update(gpGlobals->frametime); + + ///// Check game rules ///// + static int last_frags; + static int last_time; + + int frags_remaining = 0; + int time_remaining = 0; + + if ( g_fGameOver ) // someone else quit the game already + { + if ( m_flIntermissionEndTime < gpGlobals->time ) + { + if ( m_iEndIntermissionButtonHit // check that someone has pressed a key, or the max intermission time is over + || ((m_flIntermissionEndTime + MAX_INTERMISSION_TIME) < gpGlobals->time) ) + ChangeLevel(); // intermission is over + } + return; + } + + float flTimeLimit = timelimit.value * 60; + float flFragLimit = fraglimit.value; + + time_remaining = (int)(flTimeLimit ? ( flTimeLimit - gpGlobals->time ) : 0); + + if ( flTimeLimit != 0 && gpGlobals->time >= flTimeLimit ) + { + GoToIntermission(); + return; + } + + if ( flFragLimit ) + { + int bestfrags = 9999; + int remain; + + // check if any player is over the frag limit + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + if ( pPlayer && pPlayer->pev->frags >= flFragLimit ) + { + GoToIntermission(); + return; + } + + + if ( pPlayer ) + { + remain = flFragLimit - pPlayer->pev->frags; + if ( remain < bestfrags ) + { + bestfrags = remain; + } + } + + } + frags_remaining = bestfrags; + } + + // Updates when frags change + if ( frags_remaining != last_frags ) + { + g_engfuncs.pfnCvar_DirectSet( &fragsleft, UTIL_VarArgs( "%i", frags_remaining ) ); + } + + // Updates once per second + if ( timeleft.value != last_time ) + { + g_engfuncs.pfnCvar_DirectSet( &timeleft, UTIL_VarArgs( "%i", time_remaining ) ); + } + + last_frags = frags_remaining; + last_time = time_remaining; +} + + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsMultiplayer( void ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsDeathmatch( void ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsCoOp( void ) +{ + return gpGlobals->coop; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ + if ( !pWeapon->CanDeploy() ) + { + // that weapon can't deploy anyway. + return FALSE; + } + + if ( !pPlayer->m_pActiveItem ) + { + // player doesn't have an active item! + return TRUE; + } + + if ( !pPlayer->m_pActiveItem->CanHolster() ) + { + // can't put away the active item. + return FALSE; + } + + if ( pWeapon->iWeight() > pPlayer->m_pActiveItem->iWeight() ) + { + return TRUE; + } + + return FALSE; +} + +BOOL CHalfLifeMultiplay :: GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) +{ + + CBasePlayerItem *pCheck; + CBasePlayerItem *pBest;// this will be used in the event that we don't find a weapon in the same category. + int iBestWeight; + int i; + + iBestWeight = -1;// no weapon lower than -1 can be autoswitched to + pBest = NULL; + + if ( !pCurrentWeapon->CanHolster() ) + { + // can't put this gun away right now, so can't switch. + return FALSE; + } + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pCheck = pPlayer->m_rgpPlayerItems[ i ]; + + while ( pCheck ) + { + if ( pCheck->iWeight() > -1 && pCheck->iWeight() == pCurrentWeapon->iWeight() && pCheck != pCurrentWeapon ) + { + // this weapon is from the same category. + if ( pCheck->CanDeploy() ) + { + if ( pPlayer->SwitchWeapon( pCheck ) ) + { + return TRUE; + } + } + } + else if ( pCheck->iWeight() > iBestWeight && pCheck != pCurrentWeapon )// don't reselect the weapon we're trying to get rid of + { + //ALERT ( at_console, "Considering %s\n", STRING( pCheck->pev->classname ) ); + // we keep updating the 'best' weapon just in case we can't find a weapon of the same weight + // that the player was using. This will end up leaving the player with his heaviest-weighted + // weapon. + if ( pCheck->CanDeploy() ) + { + // if this weapon is useable, flag it as the best + iBestWeight = pCheck->iWeight(); + pBest = pCheck; + } + } + + pCheck = pCheck->m_pNext; + } + } + + // if we make it here, we've checked all the weapons and found no useable + // weapon in the same catagory as the current weapon. + + // if pBest is null, we didn't find ANYTHING. Shouldn't be possible- should always + // at least get the crowbar, but ya never know. + if ( !pBest ) + { + return FALSE; + } + + pPlayer->SwitchWeapon( pBest ); + + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay :: ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) +{ + g_VoiceGameMgr.ClientConnected(pEntity); + + // Increase their speed to the Discwar speed + MESSAGE_BEGIN( MSG_ONE, SVC_STUFFTEXT, NULL, pEntity ); + WRITE_STRING( UTIL_VarArgs("cl_forwardspeed %d\ncl_backspeed %d\ncl_sidespeed %d\n", 320, 320, 320) ); + MESSAGE_END(); + + + return TRUE; +} + +extern int gmsgSayText; +extern int gmsgGameMode; + +void CHalfLifeMultiplay :: UpdateGameMode( CBasePlayer *pPlayer ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgGameMode, NULL, pPlayer->edict() ); + WRITE_BYTE( InArenaMode() ); // game mode none + MESSAGE_END(); +} + +void CHalfLifeMultiplay :: InitHUD( CBasePlayer *pl ) +{ + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" entered the game\n", + STRING( pl->pev->netname ), + GETPLAYERUSERID( pl->edict() ), + GETPLAYERAUTHID( pl->edict() ), + GETPLAYERUSERID( pl->edict() ) ); + + UpdateGameMode( pl ); + + // sending just one score makes the hud scoreboard active; otherwise + // it is just disabled for single play + MESSAGE_BEGIN( MSG_ONE, gmsgScoreInfo, NULL, pl->edict() ); + WRITE_BYTE( ENTINDEX(pl->edict()) ); + WRITE_SHORT( 0 ); + WRITE_SHORT( 0 ); + WRITE_SHORT( 0 ); + WRITE_SHORT( 0 ); + MESSAGE_END(); + + SendMOTDToClient( pl->edict() ); + + // loop through all active players and send their score info to the new client + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + // FIXME: Probably don't need to cast this just to read m_iDeaths + CBasePlayer *plr = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if ( plr ) + { + ALERT( at_console, "Sending %s data to %s\n", STRING(plr->pev->netname), STRING( pl->pev->netname ) ); + + MESSAGE_BEGIN( MSG_ONE, gmsgScoreInfo, NULL, pl->edict() ); + WRITE_BYTE( i ); // client number + WRITE_SHORT( plr->pev->frags ); + WRITE_SHORT( plr->m_iDeaths ); + WRITE_SHORT( plr->pev->playerclass ); + WRITE_SHORT( plr->pev->team ); + MESSAGE_END(); + + // Send their spectator state + MESSAGE_BEGIN( MSG_ONE, gmsgSpectator, NULL, pl->edict() ); + WRITE_BYTE( i ); + WRITE_BYTE( (plr->pev->iuser1 != 0) ); + MESSAGE_END(); + } + } + + if ( g_fGameOver ) + { + MESSAGE_BEGIN( MSG_ONE, SVC_INTERMISSION, NULL, pl->edict() ); + MESSAGE_END(); + } +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay :: ClientDisconnected( edict_t *pClient ) +{ + if ( pClient ) + { + CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pClient ); + + if ( pPlayer ) + { + FireTargets( "game_playerleave", pPlayer, pPlayer, USE_TOGGLE, 0 ); + + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" disconnected\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + GETPLAYERUSERID( pPlayer->edict() ) ); + + pPlayer->RemoveAllItems( TRUE );// destroy all of the players weapons and items + + // Tell all clients this player isn't a spectator anymore + MESSAGE_BEGIN( MSG_ALL, gmsgSpectator ); + WRITE_BYTE( ENTINDEX(pClient) ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + + CBasePlayer *client = NULL; + while ( ((client = (CBasePlayer*)UTIL_FindEntityByClassname( client, "player" )) != NULL) && (!FNullEnt(client->edict())) ) + { + if ( !client->pev ) + continue; + if ( client == pPlayer ) + continue; + + // If a spectator was chasing this player, move him/her onto the next player + if ( client->m_hObserverTarget == pPlayer ) + { + int iMode = client->pev->iuser1; + client->pev->iuser1 = 0; + client->m_hObserverTarget = NULL; + client->Observer_SetMode( iMode ); + } + } + } + } +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay :: FlPlayerFallDamage( CBasePlayer *pPlayer ) +{ + int iFallDamage = (int)CVAR_GET_FLOAT("mp_falldamage"); + + switch ( iFallDamage ) + { + case 1://progressive + pPlayer->m_flFallVelocity -= PLAYER_MAX_SAFE_FALL_SPEED; + return pPlayer->m_flFallVelocity * DAMAGE_FOR_FALL_SPEED; + break; + default: + case 0:// fixed + return 10; + break; + } +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay :: PlayerThink( CBasePlayer *pPlayer ) +{ + if ( g_fGameOver ) + { + // check for button presses + if ( pPlayer->m_afButtonPressed & ( IN_DUCK | IN_ATTACK | IN_ATTACK2 | IN_USE | IN_JUMP ) ) + m_iEndIntermissionButtonHit = TRUE; + + // clear attack/use commands from player + pPlayer->m_afButtonPressed = 0; + pPlayer->pev->button = 0; + pPlayer->m_afButtonReleased = 0; + } +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay :: PlayerSpawn( CBasePlayer *pPlayer ) +{ + BOOL addDefault; + CBaseEntity *pWeaponEntity = NULL; + + pPlayer->pev->weapons |= (1<Touch( pPlayer ); + addDefault = FALSE; + } + + if ( addDefault ) + { + pPlayer->GiveNamedItem( "weapon_disc" ); + pPlayer->GiveAmmo( MAX_DISCS, "disc", MAX_DISCS ); + } +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay :: FPlayerCanRespawn( CBasePlayer *pPlayer ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay :: FlPlayerSpawnTime( CBasePlayer *pPlayer ) +{ + return gpGlobals->time;//now! +} + +BOOL CHalfLifeMultiplay :: AllowAutoTargetCrosshair( void ) +{ + return ( CVAR_GET_FLOAT( "mp_autocrosshair" ) != 0 ); +} + +//========================================================= +// IPointsForKill - how many points awarded to anyone +// that kills this player? +//========================================================= +int CHalfLifeMultiplay :: IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) +{ + return 1; +} + + +//========================================================= +// PlayerKilled - someone/something killed this player +//========================================================= +void CHalfLifeMultiplay :: PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) +{ + DeathNotice( pVictim, pKiller, pInflictor ); + + FireTargets( "game_playerdie", pVictim, pVictim, USE_TOGGLE, 0 ); + CBasePlayer *peKiller = NULL; + CBaseEntity *ktmp = CBaseEntity::Instance( pKiller ); + if ( ktmp && (ktmp->Classify() == CLASS_PLAYER) ) + peKiller = (CBasePlayer*)ktmp; + + CBaseEntity *ep = CBaseEntity::Instance( pKiller ); + if ( ep && ep->Classify() == CLASS_PLAYER ) + { + CBasePlayer *PK = (CBasePlayer*)ep; + + // let the killer paint another decal as soon as he'd like. + PK->m_flNextDecalTime = gpGlobals->time; + } +} + +//========================================================= +// Deathnotice. +//========================================================= +void CHalfLifeMultiplay::DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pevInflictor ) +{ + // Work out what killed the player, and send a message to all clients about it + CBaseEntity *Killer = CBaseEntity::Instance( pKiller ); + + const char *killer_weapon_name = "world"; // by default, the player is killed by the world + int killer_index = 0; + + // Hack to fix name change + char *tau = "tau_cannon"; + char *gluon = "gluon gun"; + + if ( pKiller->flags & FL_CLIENT ) + { + killer_index = ENTINDEX(ENT(pKiller)); + + if ( pevInflictor ) + { + if ( pevInflictor == pKiller ) + { + // If the inflictor is the killer, then it must be their current weapon doing the damage + CBasePlayer *pPlayer = (CBasePlayer*)CBaseEntity::Instance( pKiller ); + + if ( pPlayer->m_pActiveItem ) + { + killer_weapon_name = pPlayer->m_pActiveItem->pszName(); + } + } + else + { + killer_weapon_name = STRING( pevInflictor->classname ); // it's just that easy + } + } + } + else + { + killer_weapon_name = STRING( pevInflictor->classname ); + } + + // strip the monster_* or weapon_* from the inflictor's classname + if ( strncmp( killer_weapon_name, "weapon_", 7 ) == 0 ) + killer_weapon_name += 7; + else if ( strncmp( killer_weapon_name, "monster_", 8 ) == 0 ) + killer_weapon_name += 8; + else if ( strncmp( killer_weapon_name, "func_", 5 ) == 0 ) + killer_weapon_name += 5; + + if ( pVictim->pev == pKiller ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" committed suicide with \"%s\"\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + + pKiller->frags -= 1; + } + else if ( pKiller->flags & FL_CLIENT ) + { + if ( !strcmp( killer_weapon_name, "disc" ) ) + { + int iTele = 0; + if ( (pVictim->m_flLastDiscHitTeleport != 0) && (gpGlobals->time < pVictim->m_flLastDiscHitTeleport + MAX_SCORE_TIME_AFTER_HIT) ) + iTele = REWARD_TELEPORT; + + // Decapitated? + if ( pVictim->m_LastHitGroup == HITGROUP_HEAD ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" killed \"%s<%i><%s><%i>\" with \"%s\" (decapitated)\n", + STRING( pKiller->netname ), + GETPLAYERUSERID( ENT(pKiller) ), + GETPLAYERAUTHID( ENT(pKiller) ), + GETPLAYERUSERID( ENT(pKiller) ), + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + + // Tell the client to display the death message + MESSAGE_BEGIN( MSG_ALL, gmsgDeathMsg ); + WRITE_BYTE( killer_index ); // the killer + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); // the victim + WRITE_STRING( "decapitate" ); // what they were killed by (should this be a string?) + MESSAGE_END(); + + // Bonus point for teleport hit + if ( iTele ) + pKiller->frags++; + + pKiller->frags += 1; + + // Bring up the reward window on the killer's screen + MESSAGE_BEGIN( MSG_ONE, gmsgReward, NULL, Killer->edict() ); + WRITE_SHORT( REWARD_DECAPITATE | iTele ); + MESSAGE_END(); + + return; + } + + // Otherwise, calculate number of disc bounces + if ( pVictim->m_iLastDiscBounces == 0 ) + { + // Bring up the reward window on the killer's screen + MESSAGE_BEGIN( MSG_ONE, gmsgReward, NULL, Killer->edict() ); + WRITE_SHORT( REWARD_BOUNCE_NONE | iTele ); + MESSAGE_END(); + } + else if ( pVictim->m_iLastDiscBounces == 1 ) + { + // Bring up the reward window on the killer's screen + MESSAGE_BEGIN( MSG_ONE, gmsgReward, NULL, Killer->edict() ); + WRITE_SHORT( REWARD_BOUNCE_ONE | iTele ); + MESSAGE_END(); + } + else + { + if ( pVictim->m_iLastDiscBounces == 2 ) + { + // Bring up the reward window on the killer's screen + MESSAGE_BEGIN( MSG_ONE, gmsgReward, NULL, Killer->edict() ); + WRITE_SHORT( REWARD_BOUNCE_TWO | iTele ); + MESSAGE_END(); + } + else + { + // Bring up the reward window on the killer's screen + MESSAGE_BEGIN( MSG_ONE, gmsgReward, NULL, Killer->edict() ); + WRITE_SHORT( REWARD_BOUNCE_THREE | iTele ); + MESSAGE_END(); + + // Cap the number of frags a killer can get to 4 + pVictim->m_iLastDiscBounces = 3; + } + } + + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" killed \"%s<%i><%s><%i>\" with \"%s\" (bounces \"%d\")\n", + STRING( pKiller->netname ), + GETPLAYERUSERID( ENT(pKiller) ), + GETPLAYERAUTHID( ENT(pKiller) ), + GETPLAYERUSERID( ENT(pKiller) ), + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name, + pVictim->m_iLastDiscBounces ); + + char sz[1024]; + sprintf( sz, "%dbounce", pVictim->m_iLastDiscBounces ); + + // Tell the client to display the death message + MESSAGE_BEGIN( MSG_ALL, gmsgDeathMsg ); + WRITE_BYTE( killer_index ); // the killer + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); // the victim + WRITE_STRING( sz ); // what they were killed by (should this be a string?) + MESSAGE_END(); + + pKiller->frags += (1 + pVictim->m_iLastDiscBounces); + + // Bonus point for teleport hit + if ( iTele ) + pKiller->frags++; + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" killed \"%s<%i><%s><%i>\" with \"%s\"\n", + STRING( pKiller->netname ), + GETPLAYERUSERID( ENT(pKiller) ), + GETPLAYERAUTHID( ENT(pKiller) ), + GETPLAYERUSERID( ENT(pKiller) ), + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + + // Tell the client to display the death message + MESSAGE_BEGIN( MSG_ALL, gmsgDeathMsg ); + WRITE_BYTE( killer_index ); // the killer + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); // the victim + WRITE_STRING( killer_weapon_name ); // what they were killed by (should this be a string?) + MESSAGE_END(); + + pKiller->frags += 1; + } + } + else + { + pKiller->frags -= 1; + + // Fell to their death + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" committed suicide with \"world\"\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + + // Tell the client to display the death message + MESSAGE_BEGIN( MSG_ALL, gmsgDeathMsg ); + WRITE_BYTE( killer_index ); // the killer + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); // the victim + WRITE_STRING( "falling" ); // what they were killed by (should this be a string?) + MESSAGE_END(); + } + + return; +} + +//========================================================= +// PlayerGotWeapon - player has grabbed a weapon that was +// sitting in the world +//========================================================= +void CHalfLifeMultiplay :: PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ +} + +//========================================================= +// FlWeaponRespawnTime - what is the time in the future +// at which this weapon may spawn? +//========================================================= +float CHalfLifeMultiplay :: FlWeaponRespawnTime( CBasePlayerItem *pWeapon ) +{ + if ( CVAR_GET_FLOAT("mp_weaponstay") > 0 ) + { + // make sure it's only certain weapons + if ( !(pWeapon->iFlags() & ITEM_FLAG_LIMITINWORLD) ) + { + return gpGlobals->time + 0; // weapon respawns almost instantly + } + } + + return gpGlobals->time + WEAPON_RESPAWN_TIME; +} + +// when we are within this close to running out of entities, items +// marked with the ITEM_FLAG_LIMITINWORLD will delay their respawn +#define ENTITY_INTOLERANCE 100 + +//========================================================= +// FlWeaponRespawnTime - Returns 0 if the weapon can respawn +// now, otherwise it returns the time at which it can try +// to spawn again. +//========================================================= +float CHalfLifeMultiplay :: FlWeaponTryRespawn( CBasePlayerItem *pWeapon ) +{ + if ( pWeapon && pWeapon->m_iId && (pWeapon->iFlags() & ITEM_FLAG_LIMITINWORLD) ) + { + if ( NUMBER_OF_ENTITIES() < (gpGlobals->maxEntities - ENTITY_INTOLERANCE) ) + return 0; + + // we're past the entity tolerance level, so delay the respawn + return FlWeaponRespawnTime( pWeapon ); + } + + return 0; +} + +//========================================================= +// VecWeaponRespawnSpot - where should this weapon spawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHalfLifeMultiplay :: VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ) +{ + return pWeapon->pev->origin; +} + +//========================================================= +// WeaponShouldRespawn - any conditions inhibiting the +// respawning of this weapon? +//========================================================= +int CHalfLifeMultiplay :: WeaponShouldRespawn( CBasePlayerItem *pWeapon ) +{ + if ( pWeapon->pev->spawnflags & SF_NORESPAWN ) + { + return GR_WEAPON_RESPAWN_NO; + } + + return GR_WEAPON_RESPAWN_YES; +} + +//========================================================= +// CanHaveWeapon - returns FALSE if the player is not allowed +// to pick up this weapon +//========================================================= +BOOL CHalfLifeMultiplay::CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pItem ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay::PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ) +{ +} + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::ItemShouldRespawn( CItem *pItem ) +{ + if ( pItem->pev->spawnflags & SF_NORESPAWN ) + { + return GR_ITEM_RESPAWN_NO; + } + + return GR_ITEM_RESPAWN_YES; +} + + +//========================================================= +// At what time in the future may this Item respawn? +//========================================================= +float CHalfLifeMultiplay::FlItemRespawnTime( CItem *pItem ) +{ + return gpGlobals->time + ITEM_RESPAWN_TIME; +} + +//========================================================= +// Where should this item respawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHalfLifeMultiplay::VecItemRespawnSpot( CItem *pItem ) +{ + return pItem->pev->origin; +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay::PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ) +{ +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsAllowedToSpawn( CBaseEntity *pEntity ) +{ +// if ( pEntity->pev->flags & FL_MONSTER ) +// return FALSE; + + return TRUE; +} + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ) +{ + if ( pAmmo->pev->spawnflags & SF_NORESPAWN ) + { + return GR_AMMO_RESPAWN_NO; + } + + return GR_AMMO_RESPAWN_YES; +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay::FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ) +{ + return gpGlobals->time + AMMO_RESPAWN_TIME; +} + +//========================================================= +//========================================================= +Vector CHalfLifeMultiplay::VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ) +{ + return pAmmo->pev->origin; +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay::FlHealthChargerRechargeTime( void ) +{ + return 60; +} + + +float CHalfLifeMultiplay::FlHEVChargerRechargeTime( void ) +{ + return 30; +} + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::DeadPlayerWeapons( CBasePlayer *pPlayer ) +{ + return GR_PLR_DROP_GUN_ACTIVE; +} + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::DeadPlayerAmmo( CBasePlayer *pPlayer ) +{ + return GR_PLR_DROP_AMMO_ACTIVE; +} + +edict_t *CHalfLifeMultiplay::GetPlayerSpawnSpot( CBasePlayer *pPlayer ) +{ + edict_t *pentSpawnSpot = CGameRules::GetPlayerSpawnSpot( pPlayer ); + if ( IsMultiplayer() && pentSpawnSpot->v.target ) + { + FireTargets( STRING(pentSpawnSpot->v.target), pPlayer, pPlayer, USE_TOGGLE, 0 ); + } + + return pentSpawnSpot; +} + + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) +{ + // half life deathmatch has only enemies + return GR_NOTTEAMMATE; +} + +BOOL CHalfLifeMultiplay :: PlayFootstepSounds( CBasePlayer *pl, float fvol ) +{ + if ( g_footsteps && g_footsteps->value == 0 ) + return FALSE; + + if ( pl->IsOnLadder() || pl->pev->velocity.Length2D() > 220 ) + return TRUE; // only make step sounds in multiplayer if the player is moving fast enough + + return FALSE; +} + +BOOL CHalfLifeMultiplay :: FAllowFlashlight( void ) +{ + return CVAR_GET_FLOAT( "mp_flashlight" ) != 0; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay :: FAllowMonsters( void ) +{ + return ( CVAR_GET_FLOAT( "mp_allowmonsters" ) != 0 ); +} + +//========================================================= +//======== CHalfLifeMultiplay private functions =========== +#define INTERMISSION_TIME 6 + +void CHalfLifeMultiplay :: GoToIntermission( void ) +{ + if ( g_fGameOver ) + return; // intermission has already been triggered, so ignore. + + MESSAGE_BEGIN(MSG_ALL, SVC_INTERMISSION); + MESSAGE_END(); + + m_flIntermissionEndTime = gpGlobals->time + INTERMISSION_TIME; + g_fGameOver = TRUE; + m_iEndIntermissionButtonHit = FALSE; +} + +#define MAX_RULE_BUFFER 1024 + +typedef struct mapcycle_item_s +{ + struct mapcycle_item_s *next; + + char mapname[ 32 ]; + int minplayers, maxplayers; + char rulebuffer[ MAX_RULE_BUFFER ]; +} mapcycle_item_t; + +typedef struct mapcycle_s +{ + struct mapcycle_item_s *items; + struct mapcycle_item_s *next_item; +} mapcycle_t; + +/* +============== +DestroyMapCycle + +Clean up memory used by mapcycle when switching it +============== +*/ +void DestroyMapCycle( mapcycle_t *cycle ) +{ + mapcycle_item_t *p, *n, *start; + p = cycle->items; + if ( p ) + { + start = p; + p = p->next; + while ( p != start ) + { + n = p->next; + delete p; + p = n; + } + + delete cycle->items; + } + cycle->items = NULL; + cycle->next_item = NULL; +} + +static char com_token[ 1500 ]; + +/* +============== +COM_Parse + +Parse a token out of a string +============== +*/ +char *COM_Parse (char *data) +{ + int c; + int len; + + len = 0; + com_token[0] = 0; + + if (!data) + return NULL; + +// skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + return NULL; // end of file; + data++; + } + +// skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + + +// handle quoted strings specially + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if (c=='\"' || !c) + { + com_token[len] = 0; + return data; + } + com_token[len] = c; + len++; + } + } + +// parse single characters + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c == ',' ) + { + com_token[len] = c; + len++; + com_token[len] = 0; + return data+1; + } + +// parse a regular word + do + { + com_token[len] = c; + data++; + len++; + c = *data; + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c == ',' ) + break; + } while (c>32); + + com_token[len] = 0; + return data; +} + +/* +============== +COM_TokenWaiting + +Returns 1 if additional data is waiting to be processed on this line +============== +*/ +int COM_TokenWaiting( char *buffer ) +{ + char *p; + + p = buffer; + while ( *p && *p!='\n') + { + if ( !isspace( *p ) || isalnum( *p ) ) + return 1; + + p++; + } + + return 0; +} + +/* +============== +ReloadMapCycleFile + + +Parses mapcycle.txt file into mapcycle_t structure +============== +*/ +int ReloadMapCycleFile( char *filename, mapcycle_t *cycle ) +{ + char szBuffer[ MAX_RULE_BUFFER ]; + char szMap[ 32 ]; + int length; + char *pFileList; + char *aFileList = pFileList = (char*)LOAD_FILE_FOR_ME( filename, &length ); + int hasbuffer; + mapcycle_item_s *item, *newlist = NULL, *next; + + if ( pFileList && length ) + { + // the first map name in the file becomes the default + while ( 1 ) + { + hasbuffer = 0; + memset( szBuffer, 0, MAX_RULE_BUFFER ); + + pFileList = COM_Parse( pFileList ); + if ( strlen( com_token ) <= 0 ) + break; + + strcpy( szMap, com_token ); + + // Any more tokens on this line? + if ( COM_TokenWaiting( pFileList ) ) + { + pFileList = COM_Parse( pFileList ); + if ( strlen( com_token ) > 0 ) + { + hasbuffer = 1; + strcpy( szBuffer, com_token ); + } + } + + // Check map + if ( IS_MAP_VALID( szMap ) ) + { + // Create entry + char *s; + + item = new mapcycle_item_s; + + strcpy( item->mapname, szMap ); + + item->minplayers = 0; + item->maxplayers = 0; + + memset( item->rulebuffer, 0, MAX_RULE_BUFFER ); + + if ( hasbuffer ) + { + s = g_engfuncs.pfnInfoKeyValue( szBuffer, "minplayers" ); + if ( s && s[0] ) + { + item->minplayers = atoi( s ); + item->minplayers = max( item->minplayers, 0 ); + item->minplayers = min( item->minplayers, gpGlobals->maxClients ); + } + s = g_engfuncs.pfnInfoKeyValue( szBuffer, "maxplayers" ); + if ( s && s[0] ) + { + item->maxplayers = atoi( s ); + item->maxplayers = max( item->maxplayers, 0 ); + item->maxplayers = min( item->maxplayers, gpGlobals->maxClients ); + } + + // Remove keys + // + g_engfuncs.pfnInfo_RemoveKey( szBuffer, "minplayers" ); + g_engfuncs.pfnInfo_RemoveKey( szBuffer, "maxplayers" ); + + strcpy( item->rulebuffer, szBuffer ); + } + + item->next = cycle->items; + cycle->items = item; + } + else + { + ALERT( at_console, "Skipping %s from mapcycle, not a valid map\n", szMap ); + } + + + } + + FREE_FILE( aFileList ); + } + + // Fixup circular list pointer + item = cycle->items; + + // Reverse it to get original order + while ( item ) + { + next = item->next; + item->next = newlist; + newlist = item; + item = next; + } + cycle->items = newlist; + item = cycle->items; + + // Didn't parse anything + if ( !item ) + { + return 0; + } + + while ( item->next ) + { + item = item->next; + } + item->next = cycle->items; + + cycle->next_item = item->next; + + return 1; +} + +/* +============== +CountPlayers + +Determine the current # of active players on the server for map cycling logic +============== +*/ +int CountPlayers( void ) +{ + int num = 0; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pEnt = UTIL_PlayerByIndex( i ); + + if ( pEnt ) + { + num = num + 1; + } + } + + return num; +} + +/* +============== +ExtractCommandString + +Parse commands/key value pairs to issue right after map xxx command is issued on server + level transition +============== +*/ +void ExtractCommandString( char *s, char *szCommand ) +{ + // Now make rules happen + char pkey[512]; + char value[512]; // use two buffers so compares + // work without stomping on each other + char *o; + + if ( *s == '\\' ) + s++; + + while (1) + { + o = pkey; + while ( *s != '\\' ) + { + if ( !*s ) + return; + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + + while (*s != '\\' && *s) + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + + strcat( szCommand, pkey ); + if ( strlen( value ) > 0 ) + { + strcat( szCommand, " " ); + strcat( szCommand, value ); + } + strcat( szCommand, "\n" ); + + if (!*s) + return; + s++; + } +} + +/* +============== +ChangeLevel + +Server is changing to a new level, check mapcycle.txt for map name and setup info +============== +*/ +void CHalfLifeMultiplay :: ChangeLevel( void ) +{ + static char szPreviousMapCycleFile[ 256 ]; + static mapcycle_t mapcycle; + + char szNextMap[32]; + char szFirstMapInList[32]; + char szCommands[ 1500 ]; + char szRules[ 1500 ]; + int minplayers = 0, maxplayers = 0; + strcpy( szFirstMapInList, "hldm1" ); // the absolute default level is hldm1 + + int curplayers; + BOOL do_cycle = TRUE; + + // find the map to change to + char *mapcfile = (char*)CVAR_GET_STRING( "mapcyclefile" ); + ASSERT( mapcfile != NULL ); + + szCommands[ 0 ] = '\0'; + szRules[ 0 ] = '\0'; + + curplayers = CountPlayers(); + + // Has the map cycle filename changed? + if ( stricmp( mapcfile, szPreviousMapCycleFile ) ) + { + strcpy( szPreviousMapCycleFile, mapcfile ); + + DestroyMapCycle( &mapcycle ); + + if ( !ReloadMapCycleFile( mapcfile, &mapcycle ) || ( !mapcycle.items ) ) + { + ALERT( at_console, "Unable to load map cycle file %s\n", mapcfile ); + do_cycle = FALSE; + } + } + + if ( do_cycle && mapcycle.items ) + { + BOOL keeplooking = FALSE; + BOOL found = FALSE; + mapcycle_item_s *item; + + // Assume current map + strcpy( szNextMap, STRING(gpGlobals->mapname) ); + strcpy( szFirstMapInList, STRING(gpGlobals->mapname) ); + + // Traverse list + for ( item = mapcycle.next_item; item->next != mapcycle.next_item; item = item->next ) + { + keeplooking = FALSE; + + ASSERT( item != NULL ); + + if ( item->minplayers != 0 ) + { + if ( curplayers >= item->minplayers ) + { + found = TRUE; + minplayers = item->minplayers; + } + else + { + keeplooking = TRUE; + } + } + + if ( item->maxplayers != 0 ) + { + if ( curplayers <= item->maxplayers ) + { + found = TRUE; + maxplayers = item->maxplayers; + } + else + { + keeplooking = TRUE; + } + } + + if ( keeplooking ) + continue; + + found = TRUE; + break; + } + + if ( !found ) + { + item = mapcycle.next_item; + } + + // Increment next item pointer + mapcycle.next_item = item->next; + + // Perform logic on current item + strcpy( szNextMap, item->mapname ); + + ExtractCommandString( item->rulebuffer, szCommands ); + strcpy( szRules, item->rulebuffer ); + } + + if ( !IS_MAP_VALID(szNextMap) ) + { + strcpy( szNextMap, szFirstMapInList ); + } + + g_fGameOver = TRUE; + + ALERT( at_console, "CHANGE LEVEL: %s\n", szNextMap ); + if ( minplayers || maxplayers ) + { + ALERT( at_console, "PLAYER COUNT: min %i max %i current %i\n", minplayers, maxplayers, curplayers ); + } + if ( strlen( szRules ) > 0 ) + { + ALERT( at_console, "RULES: %s\n", szRules ); + } + + CHANGE_LEVEL( szNextMap, NULL ); + if ( strlen( szCommands ) > 0 ) + { + SERVER_COMMAND( szCommands ); + } +} + +#define MAX_MOTD_CHUNK 60 +#define MAX_MOTD_LENGTH (MAX_MOTD_CHUNK * 4) + +void CHalfLifeMultiplay :: SendMOTDToClient( edict_t *client ) +{ + // read from the MOTD.txt file + int length, char_count = 0; + char *pFileList; + char *aFileList = pFileList = (char*)LOAD_FILE_FOR_ME( "motd.txt", &length ); + + // Send the message of the day + // read it chunk-by-chunk, and send it in parts + + while ( pFileList && *pFileList && char_count < MAX_MOTD_LENGTH ) + { + char chunk[MAX_MOTD_CHUNK+1]; + + if ( strlen( pFileList ) < MAX_MOTD_CHUNK ) + { + strcpy( chunk, pFileList ); + } + else + { + strncpy( chunk, pFileList, MAX_MOTD_CHUNK ); + chunk[MAX_MOTD_CHUNK] = 0; // strncpy doesn't always append the null terminator + } + + char_count += strlen( chunk ); + if ( char_count < MAX_MOTD_LENGTH ) + pFileList = aFileList + char_count; + else + *pFileList = 0; + + MESSAGE_BEGIN( MSG_ONE, gmsgMOTD, NULL, client ); + WRITE_BYTE( *pFileList ? FALSE : TRUE ); // FALSE means there is still more message to come + WRITE_STRING( chunk ); + MESSAGE_END(); + } + + FREE_FILE( aFileList ); +} + + diff --git a/ricochet/dlls/nodes.h b/ricochet/dlls/nodes.h new file mode 100644 index 0000000..d6dbbd8 --- /dev/null +++ b/ricochet/dlls/nodes.h @@ -0,0 +1,54 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// nodes.h +//========================================================= + +#ifndef NODES_H +#define NODES_H + +#define bits_NODE_GROUP_REALM 1 + +class CLink +{ +public: + entvars_t *m_pLinkEnt;// the entity that blocks this connection (doors, etc) +}; + + +class CGraph +{ +public: + BOOL m_fGraphPresent;// is the graph in memory? + BOOL m_fGraphPointersSet;// are the entity pointers for the graph all set? + + int m_cLinks;// total number of links + CLink *m_pLinkPool;// big list of all node connections + + void InitGraph( void ); + int AllocNodes ( void ); + + int CheckNODFile(char *szMapName); + int FLoadGraph(char *szMapName); + int FSetGraphPointers(void); + void ShowNodeConnections ( int iNode ); + int FindNearestNode ( const Vector &vecOrigin, CBaseEntity *pEntity ); + int FindNearestNode ( const Vector &vecOrigin, int afNodeTypes ); + +}; + +extern CGraph WorldGraph; + +#endif // NODES_H diff --git a/ricochet/dlls/observer.cpp b/ricochet/dlls/observer.cpp new file mode 100644 index 0000000..7ba4505 --- /dev/null +++ b/ricochet/dlls/observer.cpp @@ -0,0 +1,325 @@ +//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= +// observer.cpp +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" + +extern int gmsgCurWeapon; +extern int gmsgSetFOV; +extern int gmsgTeamInfo; +extern int gmsgSpectator; + +// Player has become a spectator. Set it up. +// This was moved from player.cpp. +void CBasePlayer::StartObserver( Vector vecPosition, Vector vecViewAngle ) +{ + // clear any clientside entities attached to this player + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_KILLPLAYERATTACHMENTS ); + WRITE_BYTE( (BYTE)entindex() ); + MESSAGE_END(); + + // Holster weapon immediately, to allow it to cleanup + if (m_pActiveItem) + m_pActiveItem->Holster( ); + + if ( m_pTank != NULL ) + { + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + } + + // clear out the suit message cache so we don't keep chattering + SetSuitUpdate(NULL, FALSE, 0); + + // Tell Ammo Hud that the player is dead + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(0XFF); + WRITE_BYTE(0xFF); + MESSAGE_END(); + + // reset FOV + m_iFOV = m_iClientFOV = 0; + pev->fov = m_iFOV; + MESSAGE_BEGIN( MSG_ONE, gmsgSetFOV, NULL, pev ); + WRITE_BYTE(0); + MESSAGE_END(); + + // Setup flags + m_iHideHUD = (HIDEHUD_HEALTH | HIDEHUD_WEAPONS); + m_afPhysicsFlags |= PFLAG_OBSERVER; + pev->effects = EF_NODRAW; + pev->view_ofs = g_vecZero; + pev->solid = SOLID_NOT; + pev->takedamage = DAMAGE_NO; + pev->movetype = MOVETYPE_NONE; + ClearBits( m_afPhysicsFlags, PFLAG_DUCKING ); + ClearBits( pev->flags, FL_DUCKING ); + pev->deadflag = DEAD_RESPAWNABLE; + pev->health = 1; + + // Clear out the status bar + m_fInitHUD = TRUE; + + // Update Team Status + MESSAGE_BEGIN( MSG_ALL, gmsgTeamInfo ); + WRITE_BYTE( ENTINDEX(edict()) ); + WRITE_STRING( "" ); + MESSAGE_END(); + + // Remove all the player's stuff + RemoveAllItems( FALSE ); + + // Move them to the new position + UTIL_SetOrigin( pev, vecPosition ); + + // Find a player to watch + m_flNextObserverInput = 0; + Observer_SetMode(OBS_ROAMING); + + // Tell all clients this player is now a spectator + MESSAGE_BEGIN( MSG_ALL, gmsgSpectator ); + WRITE_BYTE( ENTINDEX( edict() ) ); + WRITE_BYTE( 1 ); + MESSAGE_END(); + + pev->angles = pev->v_angle = vecViewAngle; + pev->fixangle = TRUE; +} + +// Leave observer mode +void CBasePlayer::StopObserver( void ) +{ + // Turn off spectator + if ( pev->iuser1 || pev->iuser2 ) + { + // Tell all clients this player is not a spectator anymore + MESSAGE_BEGIN( MSG_ALL, gmsgSpectator ); + WRITE_BYTE( ENTINDEX( edict() ) ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + + pev->iuser1 = pev->iuser2 = 0; + m_hObserverTarget = NULL; + } + + m_fWeapon = FALSE; // force weapon send + m_iHideHUD = 0; +} + +// Find the next client in the game for this player to spectate +void CBasePlayer::Observer_FindNextPlayer( bool bReverse ) +{ + // MOD AUTHORS: Modify the logic of this function if you want to restrict the observer to watching + // only a subset of the players. e.g. Make it check the target's team. + + int iStart; + if ( m_hObserverTarget ) + iStart = ENTINDEX( m_hObserverTarget->edict() ); + else + iStart = ENTINDEX( edict() ); + int iCurrent = iStart; + m_hObserverTarget = NULL; + int iDir = bReverse ? -1 : 1; + + do + { + iCurrent += iDir; + + // Loop through the clients + if (iCurrent > gpGlobals->maxClients) + iCurrent = 1; + if (iCurrent < 1) + iCurrent = gpGlobals->maxClients; + + CBaseEntity *pEnt = UTIL_PlayerByIndex( iCurrent ); + if ( !pEnt ) + continue; + if ( pEnt == this ) + continue; + // Don't spec observers or invisible players + if ( ((CBasePlayer*)pEnt)->IsObserver() || (pEnt->pev->effects & EF_NODRAW) ) + continue; + + // MOD AUTHORS: Add checks on target here. + + m_hObserverTarget = pEnt; + break; + + } while ( iCurrent != iStart ); + + // Did we find a target? + if ( m_hObserverTarget ) + { + // Store the target in pev so the physics DLL can get to it + pev->iuser2 = ENTINDEX( m_hObserverTarget->edict() ); + pev->groupinfo = m_hObserverTarget->pev->groupinfo; + + // Move to the target + if ( pev->iuser1 != OBS_LOCKEDVIEW ) + UTIL_SetOrigin( pev, m_hObserverTarget->pev->origin ); + + ALERT( at_console, "Now Tracking %s\n", STRING( m_hObserverTarget->pev->netname ) ); + } + else + { + ALERT( at_console, "No observer targets.\n" ); + } +} + +void CBasePlayer::ObserverInput_ChangeMode() +{ + if ( pev->iuser1 == OBS_ROAMING ) + Observer_SetMode( OBS_CHASE_FREE ); + else if ( pev->iuser1 == OBS_CHASE_FREE ) + Observer_SetMode( OBS_LOCKEDVIEW ); + else + Observer_SetMode( OBS_ROAMING ); +} + +void CBasePlayer::ObserverInput_NextPlayer() +{ + if ( pev->iuser1 != OBS_ROAMING ) + Observer_FindNextPlayer( false ); +} + +void CBasePlayer::ObserverInput_PrevPlayer() +{ + if ( pev->iuser1 != OBS_ROAMING ) + Observer_FindNextPlayer( true ); +} + +// Handle buttons in observer mode +void CBasePlayer::Observer_HandleButtons() +{ + + if ( m_flChangeAngles != -1 && m_flChangeAngles <= gpGlobals->time ) + { + if ( pev->iuser1 == OBS_LOCKEDVIEW ) + { + pev->angles = m_vecHitVelocity; + pev->fixangle = TRUE; + } + + m_flChangeAngles = -1; + } + + return; + + // Slow down mouse clicks + if ( m_flNextObserverInput > gpGlobals->time ) + return; + + // Jump changes from modes: Chase to Roaming + if ( m_afButtonPressed & IN_JUMP ) + { + if ( pev->iuser1 == OBS_ROAMING ) + Observer_SetMode( OBS_CHASE_FREE ); + else if ( pev->iuser1 == OBS_CHASE_FREE ) + Observer_SetMode( OBS_LOCKEDVIEW ); + else + Observer_SetMode( OBS_ROAMING ); + + m_flNextObserverInput = gpGlobals->time + 0.2; + } + + // Attack moves to the next player + if ( m_afButtonPressed & IN_ATTACK && pev->iuser1 != OBS_ROAMING ) + { + Observer_FindNextPlayer( false ); + + m_flNextObserverInput = gpGlobals->time + 0.2; + } + + // Attack2 moves to the prev player + if ( m_afButtonPressed & IN_ATTACK2 && pev->iuser1 != OBS_ROAMING ) + { + Observer_FindNextPlayer( true ); + + m_flNextObserverInput = gpGlobals->time + 0.2; + } +} + +// Attempt to change the observer mode +void CBasePlayer::Observer_SetMode( int iMode ) +{ + // Just abort if we're changing to the mode we're already in + if ( iMode == pev->iuser1 ) + return; + + // Changing to Roaming? + if ( iMode == OBS_ROAMING ) + { + // MOD AUTHORS: If you don't want to allow roaming observers at all in your mod, just abort here. + pev->iuser1 = OBS_ROAMING; + pev->iuser2 = 0; + pev->maxspeed = 320; + + ClientPrint( pev, HUD_PRINTCENTER, "#Spec_Mode3" ); + return; + } + + if ( iMode == OBS_LOCKEDVIEW ) + { + // Find the spectator spawn position + CBaseEntity *pSpot = UTIL_FindEntityByClassname( NULL, "info_player_spectator"); + + if ( pSpot ) + { + // Move them to the new position + UTIL_SetOrigin( pev, pSpot->pev->origin ); + + pev->iuser1 = OBS_LOCKEDVIEW; + + m_flChangeAngles = gpGlobals->time + 0.1; + m_vecHitVelocity = pSpot->pev->v_angle; + pev->iuser2 = 0; + pev->maxspeed = 1; + } + + return; + } + + // Changing to Chase Freelook? + if ( iMode == OBS_CHASE_FREE ) + { + // If changing from Roaming, or starting observing, make sure there is a target + if ( m_hObserverTarget == NULL ) + Observer_FindNextPlayer( false ); + + if (m_hObserverTarget) + { + pev->iuser1 = OBS_CHASE_FREE; + pev->iuser2 = ENTINDEX( m_hObserverTarget->edict() ); + ClientPrint( pev, HUD_PRINTCENTER, "#Spec_Mode2" ); + pev->maxspeed = 1; + } + else + { + ClientPrint( pev, HUD_PRINTCENTER, "#Spec_NoTarget" ); + Observer_SetMode(OBS_ROAMING); + } + + return; + } +} diff --git a/ricochet/dlls/pathcorner.cpp b/ricochet/dlls/pathcorner.cpp new file mode 100644 index 0000000..173b960 --- /dev/null +++ b/ricochet/dlls/pathcorner.cpp @@ -0,0 +1,428 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ========================== PATH_CORNER =========================== +// + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "trains.h" +#include "saverestore.h" + +class CPathCorner : public CPointEntity +{ +public: + void Spawn( ); + void KeyValue( KeyValueData* pkvd ); + float GetDelay( void ) { return m_flWait; } +// void Touch( CBaseEntity *pOther ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + float m_flWait; +}; + +LINK_ENTITY_TO_CLASS( path_corner, CPathCorner ); + +// Global Savedata for Delay +TYPEDESCRIPTION CPathCorner::m_SaveData[] = +{ + DEFINE_FIELD( CPathCorner, m_flWait, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CPathCorner, CPointEntity ); + +// +// Cache user-entity-field values until spawn is called. +// +void CPathCorner :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CPathCorner :: Spawn( ) +{ + ASSERTSZ(!FStringNull(pev->targetname), "path_corner without a targetname"); +} + +#if 0 +void CPathCorner :: Touch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + + if ( FBitSet ( pevToucher->flags, FL_MONSTER ) ) + {// monsters don't navigate path corners based on touch anymore + return; + } + + // If OTHER isn't explicitly looking for this path_corner, bail out + if ( pOther->m_pGoalEnt != this ) + { + return; + } + + // If OTHER has an enemy, this touch is incidental, ignore + if ( !FNullEnt(pevToucher->enemy) ) + { + return; // fighting, not following a path + } + + // UNDONE: support non-zero flWait + /* + if (m_flWait != 0) + ALERT(at_warning, "Non-zero path-cornder waits NYI"); + */ + + // Find the next "stop" on the path, make it the goal of the "toucher". + if (FStringNull(pev->target)) + { + ALERT(at_warning, "PathCornerTouch: no next stop specified"); + } + + pOther->m_pGoalEnt = CBaseEntity::Instance( FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(pev->target) ) ); + + // If "next spot" was not found (does not exist - level design error) + if ( !pOther->m_pGoalEnt ) + { + ALERT(at_console, "PathCornerTouch--%s couldn't find next stop in path: %s", STRING(pev->classname), STRING(pev->target)); + return; + } + + // Turn towards the next stop in the path. + pevToucher->ideal_yaw = UTIL_VecToYaw ( pOther->m_pGoalEnt->pev->origin - pevToucher->origin ); +} +#endif + + + +TYPEDESCRIPTION CPathTrack::m_SaveData[] = +{ + DEFINE_FIELD( CPathTrack, m_length, FIELD_FLOAT ), + DEFINE_FIELD( CPathTrack, m_pnext, FIELD_CLASSPTR ), + DEFINE_FIELD( CPathTrack, m_paltpath, FIELD_CLASSPTR ), + DEFINE_FIELD( CPathTrack, m_pprevious, FIELD_CLASSPTR ), + DEFINE_FIELD( CPathTrack, m_altName, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CPathTrack, CBaseEntity ); +LINK_ENTITY_TO_CLASS( path_track, CPathTrack ); + +// +// Cache user-entity-field values until spawn is called. +// +void CPathTrack :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "altpath")) + { + m_altName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CPathTrack :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int on; + + // Use toggles between two paths + if ( m_paltpath ) + { + on = !FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ); + if ( ShouldToggle( useType, on ) ) + { + if ( on ) + SetBits( pev->spawnflags, SF_PATH_ALTERNATE ); + else + ClearBits( pev->spawnflags, SF_PATH_ALTERNATE ); + } + } + else // Use toggles between enabled/disabled + { + on = !FBitSet( pev->spawnflags, SF_PATH_DISABLED ); + + if ( ShouldToggle( useType, on ) ) + { + if ( on ) + SetBits( pev->spawnflags, SF_PATH_DISABLED ); + else + ClearBits( pev->spawnflags, SF_PATH_DISABLED ); + } + } +} + + +void CPathTrack :: Link( void ) +{ + edict_t *pentTarget; + + if ( !FStringNull(pev->target) ) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) ); + if ( !FNullEnt(pentTarget) ) + { + m_pnext = CPathTrack::Instance( pentTarget ); + + if ( m_pnext ) // If no next pointer, this is the end of a path + { + m_pnext->SetPrevious( this ); + } + } + else + ALERT( at_console, "Dead end link %s\n", STRING(pev->target) ); + } + + // Find "alternate" path + if ( m_altName ) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_altName) ); + if ( !FNullEnt(pentTarget) ) + { + m_paltpath = CPathTrack::Instance( pentTarget ); + + if ( m_paltpath ) // If no next pointer, this is the end of a path + { + m_paltpath->SetPrevious( this ); + } + } + } +} + + +void CPathTrack :: Spawn( void ) +{ + pev->solid = SOLID_TRIGGER; + UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8)); + + m_pnext = NULL; + m_pprevious = NULL; +// DEBUGGING CODE +#if PATH_SPARKLE_DEBUG + SetThink( Sparkle ); + pev->nextthink = gpGlobals->time + 0.5; +#endif +} + + +void CPathTrack::Activate( void ) +{ + if ( !FStringNull( pev->targetname ) ) // Link to next, and back-link + Link(); +} + +CPathTrack *CPathTrack :: ValidPath( CPathTrack *ppath, int testFlag ) +{ + if ( !ppath ) + return NULL; + + if ( testFlag && FBitSet( ppath->pev->spawnflags, SF_PATH_DISABLED ) ) + return NULL; + + return ppath; +} + + +void CPathTrack :: Project( CPathTrack *pstart, CPathTrack *pend, Vector *origin, float dist ) +{ + if ( pstart && pend ) + { + Vector dir = (pend->pev->origin - pstart->pev->origin); + dir = dir.Normalize(); + *origin = pend->pev->origin + dir * dist; + } +} + +CPathTrack *CPathTrack::GetNext( void ) +{ + if ( m_paltpath && FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ) && !FBitSet( pev->spawnflags, SF_PATH_ALTREVERSE ) ) + return m_paltpath; + + return m_pnext; +} + + + +CPathTrack *CPathTrack::GetPrevious( void ) +{ + if ( m_paltpath && FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ) && FBitSet( pev->spawnflags, SF_PATH_ALTREVERSE ) ) + return m_paltpath; + + return m_pprevious; +} + + + +void CPathTrack::SetPrevious( CPathTrack *pprev ) +{ + // Only set previous if this isn't my alternate path + if ( pprev && !FStrEq( STRING(pprev->pev->targetname), STRING(m_altName) ) ) + m_pprevious = pprev; +} + + +// Assumes this is ALWAYS enabled +CPathTrack *CPathTrack :: LookAhead( Vector *origin, float dist, int move ) +{ + CPathTrack *pcurrent; + float originalDist = dist; + + pcurrent = this; + Vector currentPos = *origin; + + if ( dist < 0 ) // Travelling backwards through path + { + dist = -dist; + while ( dist > 0 ) + { + Vector dir = pcurrent->pev->origin - currentPos; + float length = dir.Length(); + if ( !length ) + { + if ( !ValidPath(pcurrent->GetPrevious(), move) ) // If there is no previous node, or it's disabled, return now. + { + if ( !move ) + Project( pcurrent->GetNext(), pcurrent, origin, dist ); + return NULL; + } + pcurrent = pcurrent->GetPrevious(); + } + else if ( length > dist ) // enough left in this path to move + { + *origin = currentPos + (dir * (dist / length)); + return pcurrent; + } + else + { + dist -= length; + currentPos = pcurrent->pev->origin; + *origin = currentPos; + if ( !ValidPath(pcurrent->GetPrevious(), move) ) // If there is no previous node, or it's disabled, return now. + return NULL; + + pcurrent = pcurrent->GetPrevious(); + } + } + *origin = currentPos; + return pcurrent; + } + else + { + while ( dist > 0 ) + { + if ( !ValidPath(pcurrent->GetNext(), move) ) // If there is no next node, or it's disabled, return now. + { + if ( !move ) + Project( pcurrent->GetPrevious(), pcurrent, origin, dist ); + return NULL; + } + Vector dir = pcurrent->GetNext()->pev->origin - currentPos; + float length = dir.Length(); + if ( !length && !ValidPath( pcurrent->GetNext()->GetNext(), move ) ) + { + if ( dist == originalDist ) // HACK -- up against a dead end + return NULL; + return pcurrent; + } + if ( length > dist ) // enough left in this path to move + { + *origin = currentPos + (dir * (dist / length)); + return pcurrent; + } + else + { + dist -= length; + currentPos = pcurrent->GetNext()->pev->origin; + pcurrent = pcurrent->GetNext(); + *origin = currentPos; + } + } + *origin = currentPos; + } + + return pcurrent; +} + + +// Assumes this is ALWAYS enabled +CPathTrack *CPathTrack :: Nearest( Vector origin ) +{ + int deadCount; + float minDist, dist; + Vector delta; + CPathTrack *ppath, *pnearest; + + + delta = origin - pev->origin; + delta.z = 0; + minDist = delta.Length(); + pnearest = this; + ppath = GetNext(); + + // Hey, I could use the old 2 racing pointers solution to this, but I'm lazy :) + deadCount = 0; + while ( ppath && ppath != this ) + { + deadCount++; + if ( deadCount > 9999 ) + { + ALERT( at_error, "Bad sequence of path_tracks from %s", STRING(pev->targetname) ); + return NULL; + } + delta = origin - ppath->pev->origin; + delta.z = 0; + dist = delta.Length(); + if ( dist < minDist ) + { + minDist = dist; + pnearest = ppath; + } + ppath = ppath->GetNext(); + } + return pnearest; +} + + +CPathTrack *CPathTrack::Instance( edict_t *pent ) +{ + if ( FClassnameIs( pent, "path_track" ) ) + return (CPathTrack *)GET_PRIVATE(pent); + return NULL; +} + + + // DEBUGGING CODE +#if PATH_SPARKLE_DEBUG +void CPathTrack :: Sparkle( void ) +{ + + pev->nextthink = gpGlobals->time + 0.2; + if ( FBitSet( pev->spawnflags, SF_PATH_DISABLED ) ) + UTIL_ParticleEffect(pev->origin, Vector(0,0,100), 210, 10); + else + UTIL_ParticleEffect(pev->origin, Vector(0,0,100), 84, 10); +} +#endif + diff --git a/ricochet/dlls/plane.cpp b/ricochet/dlls/plane.cpp new file mode 100644 index 0000000..77e2273 --- /dev/null +++ b/ricochet/dlls/plane.cpp @@ -0,0 +1,60 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "plane.h" + +//========================================================= +// Plane +//========================================================= +CPlane :: CPlane ( void ) +{ + m_fInitialized = FALSE; +} + +//========================================================= +// InitializePlane - Takes a normal for the plane and a +// point on the plane and +//========================================================= +void CPlane :: InitializePlane ( const Vector &vecNormal, const Vector &vecPoint ) +{ + m_vecNormal = vecNormal; + m_flDist = DotProduct ( m_vecNormal, vecPoint ); + m_fInitialized = TRUE; +} + + +//========================================================= +// PointInFront - determines whether the given vector is +// in front of the plane. +//========================================================= +BOOL CPlane :: PointInFront ( const Vector &vecPoint ) +{ + float flFace; + + if ( !m_fInitialized ) + { + return FALSE; + } + + flFace = DotProduct ( m_vecNormal, vecPoint ) - m_flDist; + + if ( flFace >= 0 ) + { + return TRUE; + } + + return FALSE; +} + diff --git a/ricochet/dlls/plane.h b/ricochet/dlls/plane.h new file mode 100644 index 0000000..5b64e84 --- /dev/null +++ b/ricochet/dlls/plane.h @@ -0,0 +1,43 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PLANE_H +#define PLANE_H + +//========================================================= +// Plane +//========================================================= +class CPlane +{ +public: + CPlane ( void ); + + //========================================================= + // InitializePlane - Takes a normal for the plane and a + // point on the plane and + //========================================================= + void InitializePlane ( const Vector &vecNormal, const Vector &vecPoint ); + + //========================================================= + // PointInFront - determines whether the given vector is + // in front of the plane. + //========================================================= + BOOL PointInFront ( const Vector &vecPoint ); + + Vector m_vecNormal; + float m_flDist; + BOOL m_fInitialized; +}; + +#endif // PLANE_H diff --git a/ricochet/dlls/plats.cpp b/ricochet/dlls/plats.cpp new file mode 100644 index 0000000..7fcd0e4 --- /dev/null +++ b/ricochet/dlls/plats.cpp @@ -0,0 +1,2285 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== plats.cpp ======================================================== + + spawn, think, and touch functions for trains, etc + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "trains.h" +#include "saverestore.h" + +static void PlatSpawnInsideTrigger(entvars_t* pevPlatform); + +#define SF_PLAT_TOGGLE 0x0001 + +class CBasePlatTrain : public CBaseToggle +{ +public: + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + void KeyValue( KeyValueData* pkvd); + void Precache( void ); + + // This is done to fix spawn flag collisions between this class and a derived class + virtual BOOL IsTogglePlat( void ) { return (pev->spawnflags & SF_PLAT_TOGGLE) ? TRUE : FALSE; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + BYTE m_bMoveSnd; // sound a plat makes while moving + BYTE m_bStopSnd; // sound a plat makes when it stops + float m_volume; // Sound volume +}; + +TYPEDESCRIPTION CBasePlatTrain::m_SaveData[] = +{ + DEFINE_FIELD( CBasePlatTrain, m_bMoveSnd, FIELD_CHARACTER ), + DEFINE_FIELD( CBasePlatTrain, m_bStopSnd, FIELD_CHARACTER ), + DEFINE_FIELD( CBasePlatTrain, m_volume, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CBasePlatTrain, CBaseToggle ); + +void CBasePlatTrain :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "lip")) + { + m_flLip = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "height")) + { + m_flHeight = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "rotation")) + { + m_vecFinalAngle.x = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "movesnd")) + { + m_bMoveSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "stopsnd")) + { + m_bStopSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "volume")) + { + m_volume = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +#define noiseMoving noise +#define noiseArrived noise1 + +void CBasePlatTrain::Precache( void ) +{ +// set the plat's "in-motion" sound + switch (m_bMoveSnd) + { + case 0: + pev->noiseMoving = MAKE_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("plats/bigmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/bigmove1.wav"); + break; + case 2: + PRECACHE_SOUND ("plats/bigmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/bigmove2.wav"); + break; + case 3: + PRECACHE_SOUND ("plats/elevmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/elevmove1.wav"); + break; + case 4: + PRECACHE_SOUND ("plats/elevmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/elevmove2.wav"); + break; + case 5: + PRECACHE_SOUND ("plats/elevmove3.wav"); + pev->noiseMoving = MAKE_STRING("plats/elevmove3.wav"); + break; + case 6: + PRECACHE_SOUND ("plats/freightmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/freightmove1.wav"); + break; + case 7: + PRECACHE_SOUND ("plats/freightmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/freightmove2.wav"); + break; + case 8: + PRECACHE_SOUND ("plats/heavymove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/heavymove1.wav"); + break; + case 9: + PRECACHE_SOUND ("plats/rackmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/rackmove1.wav"); + break; + case 10: + PRECACHE_SOUND ("plats/railmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/railmove1.wav"); + break; + case 11: + PRECACHE_SOUND ("plats/squeekmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/squeekmove1.wav"); + break; + case 12: + PRECACHE_SOUND ("plats/talkmove1.wav"); + pev->noiseMoving = MAKE_STRING("plats/talkmove1.wav"); + break; + case 13: + PRECACHE_SOUND ("plats/talkmove2.wav"); + pev->noiseMoving = MAKE_STRING("plats/talkmove2.wav"); + break; + default: + pev->noiseMoving = MAKE_STRING("common/null.wav"); + break; + } + +// set the plat's 'reached destination' stop sound + switch (m_bStopSnd) + { + case 0: + pev->noiseArrived = MAKE_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("plats/bigstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/bigstop1.wav"); + break; + case 2: + PRECACHE_SOUND ("plats/bigstop2.wav"); + pev->noiseArrived = MAKE_STRING("plats/bigstop2.wav"); + break; + case 3: + PRECACHE_SOUND ("plats/freightstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/freightstop1.wav"); + break; + case 4: + PRECACHE_SOUND ("plats/heavystop2.wav"); + pev->noiseArrived = MAKE_STRING("plats/heavystop2.wav"); + break; + case 5: + PRECACHE_SOUND ("plats/rackstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/rackstop1.wav"); + break; + case 6: + PRECACHE_SOUND ("plats/railstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/railstop1.wav"); + break; + case 7: + PRECACHE_SOUND ("plats/squeekstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/squeekstop1.wav"); + break; + case 8: + PRECACHE_SOUND ("plats/talkstop1.wav"); + pev->noiseArrived = MAKE_STRING("plats/talkstop1.wav"); + break; + + default: + pev->noiseArrived = MAKE_STRING("common/null.wav"); + break; + } +} + +// +//====================== PLAT code ==================================================== +// + + +#define noiseMovement noise +#define noiseStopMoving noise1 + +class CFuncPlat : public CBasePlatTrain +{ +public: + void Spawn( void ); + void Precache( void ); + void Setup( void ); + + virtual void Blocked( CBaseEntity *pOther ); + + + void EXPORT PlatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + void EXPORT CallGoDown( void ) { GoDown(); } + void EXPORT CallHitTop( void ) { HitTop(); } + void EXPORT CallHitBottom( void ) { HitBottom(); } + + virtual void GoUp( void ); + virtual void GoDown( void ); + virtual void HitTop( void ); + virtual void HitBottom( void ); +}; +LINK_ENTITY_TO_CLASS( func_plat, CFuncPlat ); + + +// UNDONE: Need to save this!!! It needs class & linkage +class CPlatTrigger : public CBaseEntity +{ +public: + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DONT_SAVE; } + void SpawnInsideTrigger( CFuncPlat *pPlatform ); + void Touch( CBaseEntity *pOther ); + CFuncPlat *m_pPlatform; +}; + + + +/*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER +speed default 150 + +Plats are always drawn in the extended position, so they will light correctly. + +If the plat is the target of another trigger or button, it will start out disabled in +the extended position until it is trigger, when it will lower and become a normal plat. + +If the "height" key is set, that will determine the amount the plat moves, instead of +being implicitly determined by the model's height. + +Set "sounds" to one of the following: +1) base fast +2) chain slow +*/ + +void CFuncPlat :: Setup( void ) +{ + //pev->noiseMovement = MAKE_STRING("plats/platmove1.wav"); + //pev->noiseStopMoving = MAKE_STRING("plats/platstop1.wav"); + + if (m_flTLength == 0) + m_flTLength = 80; + if (m_flTWidth == 0) + m_flTWidth = 10; + + pev->angles = g_vecZero; + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); // set size and link into world + UTIL_SetSize(pev, pev->mins, pev->maxs); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + // vecPosition1 is the top position, vecPosition2 is the bottom + m_vecPosition1 = pev->origin; + m_vecPosition2 = pev->origin; + if (m_flHeight != 0) + m_vecPosition2.z = pev->origin.z - m_flHeight; + else + m_vecPosition2.z = pev->origin.z - pev->size.z + 8; + if (pev->speed == 0) + pev->speed = 150; + + if ( m_volume == 0 ) + m_volume = 0.85; +} + + +void CFuncPlat :: Precache( ) +{ + CBasePlatTrain::Precache(); + //PRECACHE_SOUND("plats/platmove1.wav"); + //PRECACHE_SOUND("plats/platstop1.wav"); + if ( !IsTogglePlat() ) + PlatSpawnInsideTrigger( pev ); // the "start moving" trigger +} + + +void CFuncPlat :: Spawn( ) +{ + Setup(); + + Precache(); + + // If this platform is the target of some button, it starts at the TOP position, + // and is brought down by that button. Otherwise, it starts at BOTTOM. + if ( !FStringNull(pev->targetname) ) + { + UTIL_SetOrigin (pev, m_vecPosition1); + m_toggle_state = TS_AT_TOP; + SetUse( &CFuncPlat::PlatUse ); + } + else + { + UTIL_SetOrigin (pev, m_vecPosition2); + m_toggle_state = TS_AT_BOTTOM; + } +} + + + +static void PlatSpawnInsideTrigger(entvars_t* pevPlatform) +{ + GetClassPtr( (CPlatTrigger *)NULL)->SpawnInsideTrigger( GetClassPtr( (CFuncPlat *)pevPlatform ) ); +} + + +// +// Create a trigger entity for a platform. +// +void CPlatTrigger :: SpawnInsideTrigger( CFuncPlat *pPlatform ) +{ + m_pPlatform = pPlatform; + // Create trigger entity, "point" it at the owning platform, give it a touch method + pev->solid = SOLID_TRIGGER; + pev->movetype = MOVETYPE_NONE; + pev->origin = pPlatform->pev->origin; + + // Establish the trigger field's size + Vector vecTMin = m_pPlatform->pev->mins + Vector ( 25 , 25 , 0 ); + Vector vecTMax = m_pPlatform->pev->maxs + Vector ( 25 , 25 , 8 ); + vecTMin.z = vecTMax.z - ( m_pPlatform->m_vecPosition1.z - m_pPlatform->m_vecPosition2.z + 8 ); + if (m_pPlatform->pev->size.x <= 50) + { + vecTMin.x = (m_pPlatform->pev->mins.x + m_pPlatform->pev->maxs.x) / 2; + vecTMax.x = vecTMin.x + 1; + } + if (m_pPlatform->pev->size.y <= 50) + { + vecTMin.y = (m_pPlatform->pev->mins.y + m_pPlatform->pev->maxs.y) / 2; + vecTMax.y = vecTMin.y + 1; + } + UTIL_SetSize ( pev, vecTMin, vecTMax ); +} + + +// +// When the platform's trigger field is touched, the platform ??? +// +void CPlatTrigger :: Touch( CBaseEntity *pOther ) +{ + // Ignore touches by non-players + entvars_t* pevToucher = pOther->pev; + if ( !FClassnameIs (pevToucher, "player") ) + return; + + // Ignore touches by corpses + if (!pOther->IsAlive()) + return; + + // Make linked platform go up/down. + if (m_pPlatform->m_toggle_state == TS_AT_BOTTOM) + m_pPlatform->GoUp(); + else if (m_pPlatform->m_toggle_state == TS_AT_TOP) + m_pPlatform->pev->nextthink = m_pPlatform->pev->ltime + 1;// delay going down +} + + +// +// Used by SUB_UseTargets, when a platform is the target of a button. +// Start bringing platform down. +// +void CFuncPlat :: PlatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( IsTogglePlat() ) + { + // Top is off, bottom is on + BOOL on = (m_toggle_state == TS_AT_BOTTOM) ? TRUE : FALSE; + + if ( !ShouldToggle( useType, on ) ) + return; + + if (m_toggle_state == TS_AT_TOP) + GoDown(); + else if ( m_toggle_state == TS_AT_BOTTOM ) + GoUp(); + } + else + { + SetUse( NULL ); + + if (m_toggle_state == TS_AT_TOP) + GoDown(); + } +} + + +// +// Platform is at top, now starts moving down. +// +void CFuncPlat :: GoDown( void ) +{ + if(pev->noiseMovement) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_AT_TOP || m_toggle_state == TS_GOING_UP); + m_toggle_state = TS_GOING_DOWN; + SetMoveDone(&CFuncPlat::CallHitBottom); + LinearMove(m_vecPosition2, pev->speed); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncPlat :: HitBottom( void ) +{ + if(pev->noiseMovement) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement)); + + if (pev->noiseStopMoving) + EMIT_SOUND(ENT(pev), CHAN_WEAPON, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_AT_BOTTOM; +} + + +// +// Platform is at bottom, now starts moving up +// +void CFuncPlat :: GoUp( void ) +{ + if (pev->noiseMovement) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_AT_BOTTOM || m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_GOING_UP; + SetMoveDone(&CFuncPlat::CallHitTop); + LinearMove(m_vecPosition1, pev->speed); +} + + +// +// Platform has hit top. Pauses, then starts back down again. +// +void CFuncPlat :: HitTop( void ) +{ + if(pev->noiseMovement) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement)); + + if (pev->noiseStopMoving) + EMIT_SOUND(ENT(pev), CHAN_WEAPON, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_GOING_UP); + m_toggle_state = TS_AT_TOP; + + if ( !IsTogglePlat() ) + { + // After a delay, the platform will automatically start going down again. + SetThink( &CFuncPlat::CallGoDown ); + pev->nextthink = pev->ltime + 3; + } +} + + +void CFuncPlat :: Blocked( CBaseEntity *pOther ) +{ + ALERT( at_aiconsole, "%s Blocked by %s\n", STRING(pev->classname), STRING(pOther->pev->classname) ); + // Hurt the blocker a little + pOther->TakeDamage(pev, pev, 1, DMG_CRUSH); + + if(pev->noiseMovement) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement)); + + // Send the platform back where it came from + ASSERT(m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN); + if (m_toggle_state == TS_GOING_UP) + GoDown(); + else if (m_toggle_state == TS_GOING_DOWN) + GoUp (); +} + + +class CFuncPlatRot : public CFuncPlat +{ +public: + void Spawn( void ); + void SetupRotation( void ); + + virtual void GoUp( void ); + virtual void GoDown( void ); + virtual void HitTop( void ); + virtual void HitBottom( void ); + + void RotMove( Vector &destAngle, float time ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + Vector m_end, m_start; +}; +LINK_ENTITY_TO_CLASS( func_platrot, CFuncPlatRot ); +TYPEDESCRIPTION CFuncPlatRot::m_SaveData[] = +{ + DEFINE_FIELD( CFuncPlatRot, m_end, FIELD_VECTOR ), + DEFINE_FIELD( CFuncPlatRot, m_start, FIELD_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CFuncPlatRot, CFuncPlat ); + + +void CFuncPlatRot :: SetupRotation( void ) +{ + if ( m_vecFinalAngle.x != 0 ) // This plat rotates too! + { + CBaseToggle :: AxisDir( pev ); + m_start = pev->angles; + m_end = pev->angles + pev->movedir * m_vecFinalAngle.x; + } + else + { + m_start = g_vecZero; + m_end = g_vecZero; + } + if ( !FStringNull(pev->targetname) ) // Start at top + { + pev->angles = m_end; + } +} + + +void CFuncPlatRot :: Spawn( void ) +{ + CFuncPlat :: Spawn(); + SetupRotation(); +} + +void CFuncPlatRot :: GoDown( void ) +{ + CFuncPlat :: GoDown(); + RotMove( m_start, pev->nextthink - pev->ltime ); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncPlatRot :: HitBottom( void ) +{ + CFuncPlat :: HitBottom(); + pev->avelocity = g_vecZero; + pev->angles = m_start; +} + + +// +// Platform is at bottom, now starts moving up +// +void CFuncPlatRot :: GoUp( void ) +{ + CFuncPlat :: GoUp(); + RotMove( m_end, pev->nextthink - pev->ltime ); +} + + +// +// Platform has hit top. Pauses, then starts back down again. +// +void CFuncPlatRot :: HitTop( void ) +{ + CFuncPlat :: HitTop(); + pev->avelocity = g_vecZero; + pev->angles = m_end; +} + + +void CFuncPlatRot :: RotMove( Vector &destAngle, float time ) +{ + // set destdelta to the vector needed to move + Vector vecDestDelta = destAngle - pev->angles; + + // Travel time is so short, we're practically there already; so make it so. + if ( time >= 0.1) + pev->avelocity = vecDestDelta / time; + else + { + pev->avelocity = vecDestDelta; + pev->nextthink = pev->ltime + 1; + } +} + + +// +//====================== TRAIN code ================================================== +// + +class CFuncTrain : public CBasePlatTrain +{ +public: + void Spawn( void ); + void Precache( void ); + void Activate( void ); + void OverrideReset( void ); + + void Blocked( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + + void EXPORT Wait( void ); + void EXPORT Next( void ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + entvars_t *m_pevCurrentTarget; + int m_sounds; + BOOL m_activated; +}; + +LINK_ENTITY_TO_CLASS( func_train, CFuncTrain ); +TYPEDESCRIPTION CFuncTrain::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTrain, m_sounds, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrain, m_pevCurrentTarget, FIELD_EVARS ), + DEFINE_FIELD( CFuncTrain, m_activated, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTrain, CBasePlatTrain ); + + +void CFuncTrain :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBasePlatTrain::KeyValue( pkvd ); +} + + +void CFuncTrain :: Blocked( CBaseEntity *pOther ) + +{ + if ( gpGlobals->time < m_flActivateFinished) + return; + + m_flActivateFinished = gpGlobals->time + 0.5; + + pOther->TakeDamage(pev, pev, pev->dmg, DMG_CRUSH); +} + + +void CFuncTrain :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->spawnflags & SF_TRAIN_WAIT_RETRIGGER ) + { + // Move toward my target + pev->spawnflags &= ~SF_TRAIN_WAIT_RETRIGGER; + Next(); + } + else + { + pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER; + // Pop back to last target if it's available + if ( pev->enemy ) + pev->target = pev->enemy->v.targetname; + pev->nextthink = 0; + pev->velocity = g_vecZero; + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + } +} + + +void CFuncTrain :: Wait( void ) +{ + // Fire the pass target if there is one + if ( m_pevCurrentTarget->message ) + { + FireTargets( STRING(m_pevCurrentTarget->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( m_pevCurrentTarget->spawnflags, SF_CORNER_FIREONCE ) ) + m_pevCurrentTarget->message = 0; + } + + // need pointer to LAST target. + if ( FBitSet (m_pevCurrentTarget->spawnflags , SF_TRAIN_WAIT_RETRIGGER ) || ( pev->spawnflags & SF_TRAIN_WAIT_RETRIGGER ) ) + { + pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER; + // clear the sound channel. + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + pev->nextthink = 0; + return; + } + + // ALERT ( at_console, "%f\n", m_flWait ); + + if (m_flWait != 0) + {// -1 wait will wait forever! + pev->nextthink = pev->ltime + m_flWait; + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + SetThink( &CFuncTrain::Next ); + } + else + { + Next();// do it RIGHT now! + } +} + + +// +// Train next - path corner needs to change to next target +// +void CFuncTrain :: Next( void ) +{ + CBaseEntity *pTarg; + + + // now find our next target + pTarg = GetNextTarget(); + + if ( !pTarg ) + { + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + // Play stop sound + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + return; + } + + // Save last target in case we need to find it again + pev->message = pev->target; + + pev->target = pTarg->pev->target; + m_flWait = pTarg->GetDelay(); + + if ( m_pevCurrentTarget && m_pevCurrentTarget->speed != 0 ) + {// don't copy speed from target if it is 0 (uninitialized) + pev->speed = m_pevCurrentTarget->speed; + ALERT( at_aiconsole, "Train %s speed to %4.2f\n", STRING(pev->targetname), pev->speed ); + } + m_pevCurrentTarget = pTarg->pev;// keep track of this since path corners change our target for us. + + pev->enemy = pTarg->edict();//hack + + if(FBitSet(m_pevCurrentTarget->spawnflags, SF_CORNER_TELEPORT)) + { + // Path corner has indicated a teleport to the next corner. + SetBits(pev->effects, EF_NOINTERP); + UTIL_SetOrigin(pev, pTarg->pev->origin - (pev->mins + pev->maxs)* 0.5); + Wait(); // Get on with doing the next path corner. + } + else + { + // Normal linear move. + + // CHANGED this from CHAN_VOICE to CHAN_STATIC around OEM beta time because trains should + // use CHAN_STATIC for their movement sounds to prevent sound field problems. + // this is not a hack or temporary fix, this is how things should be. (sjb). + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + if ( pev->noiseMovement ) + EMIT_SOUND (ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement), m_volume, ATTN_NORM); + ClearBits(pev->effects, EF_NOINTERP); + SetMoveDone( &CFuncTrain::Wait ); + LinearMove (pTarg->pev->origin - (pev->mins + pev->maxs)* 0.5, pev->speed); + } +} + + +void CFuncTrain :: Activate( void ) +{ + // Not yet active, so teleport to first target + if ( !m_activated ) + { + m_activated = TRUE; + entvars_t *pevTarg = VARS( FIND_ENTITY_BY_TARGETNAME (NULL, STRING(pev->target) ) ); + + pev->target = pevTarg->target; + m_pevCurrentTarget = pevTarg;// keep track of this since path corners change our target for us. + + UTIL_SetOrigin (pev, pevTarg->origin - (pev->mins + pev->maxs) * 0.5 ); + + if ( FStringNull(pev->targetname) ) + { // not triggered, so start immediately + pev->nextthink = pev->ltime + 0.1; + SetThink( &CFuncTrain::Next ); + } + else + pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER; + } +} + +/*QUAKED func_train (0 .5 .8) ? +Trains are moving platforms that players can ride. +The targets origin specifies the min point of the train at each corner. +The train spawns at the first target it is pointing at. +If the train is the target of a button or trigger, it will not begin moving until activated. +speed default 100 +dmg default 2 +sounds +1) ratchet metal +*/ + +void CFuncTrain :: Spawn( void ) +{ + Precache(); + if (pev->speed == 0) + pev->speed = 100; + + if ( FStringNull(pev->target) ) + ALERT(at_console, "FuncTrain with no target"); + + if (pev->dmg == 0) + pev->dmg = 2; + + pev->movetype = MOVETYPE_PUSH; + + if ( FBitSet (pev->spawnflags, SF_TRACKTRAIN_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + + SET_MODEL( ENT(pev), STRING(pev->model) ); + UTIL_SetSize (pev, pev->mins, pev->maxs); + UTIL_SetOrigin(pev, pev->origin); + + m_activated = FALSE; + + if ( m_volume == 0 ) + m_volume = 0.85; +} + + +void CFuncTrain::Precache( void ) +{ + CBasePlatTrain::Precache(); + +#if 0 // obsolete + // otherwise use preset sound + switch (m_sounds) + { + case 0: + pev->noise = 0; + pev->noise1 = 0; + break; + + case 1: + PRECACHE_SOUND ("plats/train2.wav"); + PRECACHE_SOUND ("plats/train1.wav"); + pev->noise = MAKE_STRING("plats/train2.wav"); + pev->noise1 = MAKE_STRING("plats/train1.wav"); + break; + + case 2: + PRECACHE_SOUND ("plats/platmove1.wav"); + PRECACHE_SOUND ("plats/platstop1.wav"); + pev->noise = MAKE_STRING("plats/platstop1.wav"); + pev->noise1 = MAKE_STRING("plats/platmove1.wav"); + break; + } +#endif +} + + +void CFuncTrain::OverrideReset( void ) +{ + CBaseEntity *pTarg; + + // Are we moving? + if ( pev->velocity != g_vecZero && pev->nextthink != 0 ) + { + pev->target = pev->message; + // now find our next target + pTarg = GetNextTarget(); + if ( !pTarg ) + { + pev->nextthink = 0; + pev->velocity = g_vecZero; + } + else // Keep moving for 0.1 secs, then find path_corner again and restart + { + SetThink( &CFuncTrain::Next ); + pev->nextthink = pev->ltime + 0.1; + } + } +} + + + + +// --------------------------------------------------------------------- +// +// Track Train +// +// --------------------------------------------------------------------- + +TYPEDESCRIPTION CFuncTrackTrain::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTrackTrain, m_ppath, FIELD_CLASSPTR ), + DEFINE_FIELD( CFuncTrackTrain, m_length, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_height, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_speed, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_dir, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_startSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_controlMins, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTrackTrain, m_controlMaxs, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTrackTrain, m_sounds, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrackTrain, m_flVolume, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_flBank, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_oldSpeed, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTrackTrain, CBaseEntity ); +LINK_ENTITY_TO_CLASS( func_tracktrain, CFuncTrackTrain ); + +void CFuncTrackTrain :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "wheels")) + { + m_length = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "height")) + { + m_height = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "startspeed")) + { + m_startSpeed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "volume")) + { + m_flVolume = (float) (atoi(pkvd->szValue)); + m_flVolume *= 0.1; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "bank")) + { + m_flBank = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +void CFuncTrackTrain :: NextThink( float thinkTime, BOOL alwaysThink ) +{ + if ( alwaysThink ) + pev->flags |= FL_ALWAYSTHINK; + else + pev->flags &= ~FL_ALWAYSTHINK; + + pev->nextthink = thinkTime; +} + + +void CFuncTrackTrain :: Blocked( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + // Blocker is on-ground on the train + if ( FBitSet( pevOther->flags, FL_ONGROUND ) && VARS(pevOther->groundentity) == pev ) + { + float deltaSpeed = fabs(pev->speed); + if ( deltaSpeed > 50 ) + deltaSpeed = 50; + if ( !pevOther->velocity.z ) + pevOther->velocity.z += deltaSpeed; + return; + } + else + pevOther->velocity = (pevOther->origin - pev->origin ).Normalize() * pev->dmg; + + ALERT( at_aiconsole, "TRAIN(%s): Blocked by %s (dmg:%.2f)\n", STRING(pev->targetname), STRING(pOther->pev->classname), pev->dmg ); + if ( pev->dmg <= 0 ) + return; + // we can't hurt this thing, so we're not concerned with it + pOther->TakeDamage(pev, pev, pev->dmg, DMG_CRUSH); +} + + +void CFuncTrackTrain :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( useType != USE_SET ) + { + if ( !ShouldToggle( useType, (pev->speed != 0) ) ) + return; + + if ( pev->speed == 0 ) + { + pev->speed = m_speed * m_dir; + + Next(); + } + else + { + pev->speed = 0; + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + StopSound(); + SetThink( NULL ); + } + } + else + { + float delta = value; + + delta = ((int)(pev->speed * 4) / (int)m_speed)*0.25 + 0.25 * delta; + if ( delta > 1 ) + delta = 1; + else if ( delta < -1 ) + delta = -1; + if ( pev->spawnflags & SF_TRACKTRAIN_FORWARDONLY ) + { + if ( delta < 0 ) + delta = 0; + } + pev->speed = m_speed * delta; + Next(); + ALERT( at_aiconsole, "TRAIN(%s), speed to %.2f\n", STRING(pev->targetname), pev->speed ); + } +} + + +static float Fix( float angle ) +{ + while ( angle < 0 ) + angle += 360; + while ( angle > 360 ) + angle -= 360; + + return angle; +} + + +static void FixupAngles( Vector &v ) +{ + v.x = Fix( v.x ); + v.y = Fix( v.y ); + v.z = Fix( v.z ); +} + +#define TRAIN_STARTPITCH 60 +#define TRAIN_MAXPITCH 200 +#define TRAIN_MAXSPEED 1000 // approx max speed for sound pitch calculation + +void CFuncTrackTrain :: StopSound( void ) +{ + // if sound playing, stop it + if (m_soundPlaying && pev->noise) + { + unsigned short us_encode; + unsigned short us_sound = ( ( unsigned short )( m_sounds ) & 0x0007 ) << 12; + + us_encode = us_sound; + + PLAYBACK_EVENT_FULL( FEV_RELIABLE | FEV_UPDATE, edict(), m_usAdjustPitch, 0.0, + (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, us_encode, 0, 1, 0 ); + + /* + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noise)); + */ + EMIT_SOUND_DYN(ENT(pev), CHAN_ITEM, "plats/ttrain_brake1.wav", m_flVolume, ATTN_NORM, 0, 100); + } + + m_soundPlaying = 0; +} + +// update pitch based on speed, start sound if not playing +// NOTE: when train goes through transition, m_soundPlaying should go to 0, +// which will cause the looped sound to restart. + +void CFuncTrackTrain :: UpdateSound( void ) +{ + float flpitch; + + if (!pev->noise) + return; + + flpitch = TRAIN_STARTPITCH + (abs(pev->speed) * (TRAIN_MAXPITCH - TRAIN_STARTPITCH) / TRAIN_MAXSPEED); + + if (!m_soundPlaying) + { + // play startup sound for train + EMIT_SOUND_DYN(ENT(pev), CHAN_ITEM, "plats/ttrain_start1.wav", m_flVolume, ATTN_NORM, 0, 100); + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noise), m_flVolume, ATTN_NORM, 0, (int) flpitch); + m_soundPlaying = 1; + } + else + { +/* + // update pitch + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noise), m_flVolume, ATTN_NORM, SND_CHANGE_PITCH, (int) flpitch); +*/ + // volume 0.0 - 1.0 - 6 bits + // m_sounds 3 bits + // flpitch = 6 bits + // 15 bits total + + unsigned short us_encode; + unsigned short us_sound = ( ( unsigned short )( m_sounds ) & 0x0007 ) << 12; + unsigned short us_pitch = ( ( unsigned short )( flpitch / 10.0 ) & 0x003f ) << 6; + unsigned short us_volume = ( ( unsigned short )( m_flVolume * 40.0 ) & 0x003f ); + + us_encode = us_sound | us_pitch | us_volume; + + PLAYBACK_EVENT_FULL( FEV_RELIABLE | FEV_UPDATE, edict(), m_usAdjustPitch, 0.0, + (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, us_encode, 0, 0, 0 ); + } +} + + +void CFuncTrackTrain :: Next( void ) +{ + float time = 0.5; + + if ( !pev->speed ) + { + ALERT( at_aiconsole, "TRAIN(%s): Speed is 0\n", STRING(pev->targetname) ); + StopSound(); + return; + } + +// if ( !m_ppath ) +// m_ppath = CPathTrack::Instance(FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) )); + if ( !m_ppath ) + { + ALERT( at_aiconsole, "TRAIN(%s): Lost path\n", STRING(pev->targetname) ); + StopSound(); + return; + } + + UpdateSound(); + + Vector nextPos = pev->origin; + + nextPos.z -= m_height; + CPathTrack *pnext = m_ppath->LookAhead( &nextPos, pev->speed * 0.1, 1 ); + nextPos.z += m_height; + + pev->velocity = (nextPos - pev->origin) * 10; + Vector nextFront = pev->origin; + + nextFront.z -= m_height; + if ( m_length > 0 ) + m_ppath->LookAhead( &nextFront, m_length, 0 ); + else + m_ppath->LookAhead( &nextFront, 100, 0 ); + nextFront.z += m_height; + + Vector delta = nextFront - pev->origin; + Vector angles = UTIL_VecToAngles( delta ); + // The train actually points west + angles.y += 180; + + // !!! All of this crap has to be done to make the angles not wrap around, revisit this. + FixupAngles( angles ); + FixupAngles( pev->angles ); + + if ( !pnext || (delta.x == 0 && delta.y == 0) ) + angles = pev->angles; + + float vy, vx; + if ( !(pev->spawnflags & SF_TRACKTRAIN_NOPITCH) ) + vx = UTIL_AngleDistance( angles.x, pev->angles.x ); + else + vx = 0; + vy = UTIL_AngleDistance( angles.y, pev->angles.y ); + + pev->avelocity.y = vy * 10; + pev->avelocity.x = vx * 10; + + if ( m_flBank != 0 ) + { + if ( pev->avelocity.y < -5 ) + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( -m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); + else if ( pev->avelocity.y > 5 ) + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); + else + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( 0, pev->angles.z, m_flBank*4 ), pev->angles.z) * 4; + } + + if ( pnext ) + { + if ( pnext != m_ppath ) + { + CPathTrack *pFire; + if ( pev->speed >= 0 ) + pFire = pnext; + else + pFire = m_ppath; + + m_ppath = pnext; + // Fire the pass target if there is one + if ( pFire->pev->message ) + { + FireTargets( STRING(pFire->pev->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( pFire->pev->spawnflags, SF_PATH_FIREONCE ) ) + pFire->pev->message = 0; + } + + if ( pFire->pev->spawnflags & SF_PATH_DISABLE_TRAIN ) + pev->spawnflags |= SF_TRACKTRAIN_NOCONTROL; + + // Don't override speed if under user control + if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL ) + { + if ( pFire->pev->speed != 0 ) + {// don't copy speed from target if it is 0 (uninitialized) + pev->speed = pFire->pev->speed; + ALERT( at_aiconsole, "TrackTrain %s speed to %4.2f\n", STRING(pev->targetname), pev->speed ); + } + } + + } + SetThink( &CFuncTrackTrain::Next ); + NextThink( pev->ltime + time, TRUE ); + } + else // end of path, stop + { + StopSound(); + pev->velocity = (nextPos - pev->origin); + pev->avelocity = g_vecZero; + float distance = pev->velocity.Length(); + m_oldSpeed = pev->speed; + + + pev->speed = 0; + + // Move to the dead end + + // Are we there yet? + if ( distance > 0 ) + { + // no, how long to get there? + time = distance / m_oldSpeed; + pev->velocity = pev->velocity * (m_oldSpeed / distance); + SetThink( &CFuncTrackTrain::DeadEnd ); + NextThink( pev->ltime + time, FALSE ); + } + else + { + DeadEnd(); + } + } +} + + +void CFuncTrackTrain::DeadEnd( void ) +{ + // Fire the dead-end target if there is one + CPathTrack *pTrack, *pNext; + + pTrack = m_ppath; + + ALERT( at_aiconsole, "TRAIN(%s): Dead end ", STRING(pev->targetname) ); + // Find the dead end path node + // HACKHACK -- This is bugly, but the train can actually stop moving at a different node depending on it's speed + // so we have to traverse the list to it's end. + if ( pTrack ) + { + if ( m_oldSpeed < 0 ) + { + do + { + pNext = pTrack->ValidPath( pTrack->GetPrevious(), TRUE ); + if ( pNext ) + pTrack = pNext; + } while ( pNext ); + } + else + { + do + { + pNext = pTrack->ValidPath( pTrack->GetNext(), TRUE ); + if ( pNext ) + pTrack = pNext; + } while ( pNext ); + } + } + + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + if ( pTrack ) + { + ALERT( at_aiconsole, "at %s\n", STRING(pTrack->pev->targetname) ); + if ( pTrack->pev->netname ) + FireTargets( STRING(pTrack->pev->netname), this, this, USE_TOGGLE, 0 ); + } + else + ALERT( at_aiconsole, "\n" ); +} + + +void CFuncTrackTrain :: SetControls( entvars_t *pevControls ) +{ + Vector offset = pevControls->origin - pev->oldorigin; + + m_controlMins = pevControls->mins + offset; + m_controlMaxs = pevControls->maxs + offset; +} + + +BOOL CFuncTrackTrain :: OnControls( entvars_t *pevTest ) +{ + Vector offset = pevTest->origin - pev->origin; + + if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL ) + return FALSE; + + // Transform offset into local coordinates + UTIL_MakeVectors( pev->angles ); + Vector local; + local.x = DotProduct( offset, gpGlobals->v_forward ); + local.y = -DotProduct( offset, gpGlobals->v_right ); + local.z = DotProduct( offset, gpGlobals->v_up ); + + if ( local.x >= m_controlMins.x && local.y >= m_controlMins.y && local.z >= m_controlMins.z && + local.x <= m_controlMaxs.x && local.y <= m_controlMaxs.y && local.z <= m_controlMaxs.z ) + return TRUE; + + return FALSE; +} + + +void CFuncTrackTrain :: Find( void ) +{ + m_ppath = CPathTrack::Instance(FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) )); + if ( !m_ppath ) + return; + + entvars_t *pevTarget = m_ppath->pev; + if ( !FClassnameIs( pevTarget, "path_track" ) ) + { + ALERT( at_error, "func_track_train must be on a path of path_track\n" ); + m_ppath = NULL; + return; + } + + Vector nextPos = pevTarget->origin; + nextPos.z += m_height; + + Vector look = nextPos; + look.z -= m_height; + m_ppath->LookAhead( &look, m_length, 0 ); + look.z += m_height; + + pev->angles = UTIL_VecToAngles( look - nextPos ); + // The train actually points west + pev->angles.y += 180; + + if ( pev->spawnflags & SF_TRACKTRAIN_NOPITCH ) + pev->angles.x = 0; + UTIL_SetOrigin( pev, nextPos ); + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::Next ); + pev->speed = m_startSpeed; + + UpdateSound(); +} + + +void CFuncTrackTrain :: NearestPath( void ) +{ + CBaseEntity *pTrack = NULL; + CBaseEntity *pNearest = NULL; + float dist, closest; + + closest = 1024; + + while ((pTrack = UTIL_FindEntityInSphere( pTrack, pev->origin, 1024 )) != NULL) + { + // filter out non-tracks + if ( !(pTrack->pev->flags & (FL_CLIENT|FL_MONSTER)) && FClassnameIs( pTrack->pev, "path_track" ) ) + { + dist = (pev->origin - pTrack->pev->origin).Length(); + if ( dist < closest ) + { + closest = dist; + pNearest = pTrack; + } + } + } + + if ( !pNearest ) + { + ALERT( at_console, "Can't find a nearby track !!!\n" ); + SetThink(NULL); + return; + } + + ALERT( at_aiconsole, "TRAIN: %s, Nearest track is %s\n", STRING(pev->targetname), STRING(pNearest->pev->targetname) ); + // If I'm closer to the next path_track on this path, then it's my real path + pTrack = ((CPathTrack *)pNearest)->GetNext(); + if ( pTrack ) + { + if ( (pev->origin - pTrack->pev->origin).Length() < (pev->origin - pNearest->pev->origin).Length() ) + pNearest = pTrack; + } + + m_ppath = (CPathTrack *)pNearest; + + if ( pev->speed != 0 ) + { + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::Next ); + } +} + + +void CFuncTrackTrain::OverrideReset( void ) +{ + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::NearestPath ); +} + + +CFuncTrackTrain *CFuncTrackTrain::Instance( edict_t *pent ) +{ + if ( FClassnameIs( pent, "func_tracktrain" ) ) + return (CFuncTrackTrain *)GET_PRIVATE(pent); + return NULL; +} + +/*QUAKED func_train (0 .5 .8) ? +Trains are moving platforms that players can ride. +The targets origin specifies the min point of the train at each corner. +The train spawns at the first target it is pointing at. +If the train is the target of a button or trigger, it will not begin moving until activated. +speed default 100 +dmg default 2 +sounds +1) ratchet metal +*/ + +void CFuncTrackTrain :: Spawn( void ) +{ + if ( pev->speed == 0 ) + m_speed = 100; + else + m_speed = pev->speed; + + pev->speed = 0; + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + pev->impulse = m_speed; + + m_dir = 1; + + if ( FStringNull(pev->target) ) + ALERT( at_console, "FuncTrain with no target" ); + + if ( pev->spawnflags & SF_TRACKTRAIN_PASSABLE ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + SET_MODEL( ENT(pev), STRING(pev->model) ); + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); + + // Cache off placed origin for train controls + pev->oldorigin = pev->origin; + + m_controlMins = pev->mins; + m_controlMaxs = pev->maxs; + m_controlMaxs.z += 72; +// start trains on the next frame, to make sure their targets have had +// a chance to spawn/activate + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncTrackTrain::Find ); + Precache(); +} + +void CFuncTrackTrain :: Precache( void ) +{ + if (m_flVolume == 0.0) + m_flVolume = 1.0; + + switch (m_sounds) + { + default: + // no sound + pev->noise = 0; + break; + case 1: PRECACHE_SOUND("plats/ttrain1.wav"); pev->noise = MAKE_STRING("plats/ttrain1.wav");break; + case 2: PRECACHE_SOUND("plats/ttrain2.wav"); pev->noise = MAKE_STRING("plats/ttrain2.wav");break; + case 3: PRECACHE_SOUND("plats/ttrain3.wav"); pev->noise = MAKE_STRING("plats/ttrain3.wav");break; + case 4: PRECACHE_SOUND("plats/ttrain4.wav"); pev->noise = MAKE_STRING("plats/ttrain4.wav");break; + case 5: PRECACHE_SOUND("plats/ttrain6.wav"); pev->noise = MAKE_STRING("plats/ttrain6.wav");break; + case 6: PRECACHE_SOUND("plats/ttrain7.wav"); pev->noise = MAKE_STRING("plats/ttrain7.wav");break; + } + + PRECACHE_SOUND("plats/ttrain_brake1.wav"); + PRECACHE_SOUND("plats/ttrain_start1.wav"); + + m_usAdjustPitch = PRECACHE_EVENT( 1, "events/train.sc" ); +} + +// This class defines the volume of space that the player must stand in to control the train +class CFuncTrainControls : public CBaseEntity +{ +public: + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + void Spawn( void ); + void EXPORT Find( void ); +}; +LINK_ENTITY_TO_CLASS( func_traincontrols, CFuncTrainControls ); + + +void CFuncTrainControls :: Find( void ) +{ + edict_t *pTarget = NULL; + + do + { + pTarget = FIND_ENTITY_BY_TARGETNAME( pTarget, STRING(pev->target) ); + } while ( !FNullEnt(pTarget) && !FClassnameIs(pTarget, "func_tracktrain") ); + + if ( FNullEnt( pTarget ) ) + { + ALERT( at_console, "No train %s\n", STRING(pev->target) ); + return; + } + + CFuncTrackTrain *ptrain = CFuncTrackTrain::Instance(pTarget); + ptrain->SetControls( pev ); + UTIL_Remove( this ); +} + + +void CFuncTrainControls :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( pev, pev->origin ); + + SetThink(&CFuncTrainControls::Find ); + pev->nextthink = gpGlobals->time; +} + + + +// ---------------------------------------------------------------------------- +// +// Track changer / Train elevator +// +// ---------------------------------------------------------------------------- + +#define SF_TRACK_ACTIVATETRAIN 0x00000001 +#define SF_TRACK_RELINK 0x00000002 +#define SF_TRACK_ROTMOVE 0x00000004 +#define SF_TRACK_STARTBOTTOM 0x00000008 +#define SF_TRACK_DONT_MOVE 0x00000010 + +// +// This entity is a rotating/moving platform that will carry a train to a new track. +// It must be larger in X-Y planar area than the train, since it must contain the +// train within these dimensions in order to operate when the train is near it. +// + +typedef enum { TRAIN_SAFE, TRAIN_BLOCKING, TRAIN_FOLLOWING } TRAIN_CODE; + +class CFuncTrackChange : public CFuncPlatRot +{ +public: + void Spawn( void ); + void Precache( void ); + +// virtual void Blocked( void ); + virtual void EXPORT GoUp( void ); + virtual void EXPORT GoDown( void ); + + void KeyValue( KeyValueData* pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Find( void ); + TRAIN_CODE EvaluateTrain( CPathTrack *pcurrent ); + void UpdateTrain( Vector &dest ); + virtual void HitBottom( void ); + virtual void HitTop( void ); + void Touch( CBaseEntity *pOther ); + virtual void UpdateAutoTargets( int toggleState ); + virtual BOOL IsTogglePlat( void ) { return TRUE; } + + void DisableUse( void ) { m_use = 0; } + void EnableUse( void ) { m_use = 1; } + int UseEnabled( void ) { return m_use; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual void OverrideReset( void ); + + + CPathTrack *m_trackTop; + CPathTrack *m_trackBottom; + + CFuncTrackTrain *m_train; + + int m_trackTopName; + int m_trackBottomName; + int m_trainName; + TRAIN_CODE m_code; + int m_targetState; + int m_use; +}; +LINK_ENTITY_TO_CLASS( func_trackchange, CFuncTrackChange ); + +TYPEDESCRIPTION CFuncTrackChange::m_SaveData[] = +{ + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackTop, FIELD_CLASSPTR ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackBottom, FIELD_CLASSPTR ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_train, FIELD_CLASSPTR ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackTopName, FIELD_STRING ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackBottomName, FIELD_STRING ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trainName, FIELD_STRING ), + DEFINE_FIELD( CFuncTrackChange, m_code, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrackChange, m_targetState, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrackChange, m_use, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTrackChange, CFuncPlatRot ); + +void CFuncTrackChange :: Spawn( void ) +{ + Setup(); + if ( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) ) + m_vecPosition2.z = pev->origin.z; + + SetupRotation(); + + if ( FBitSet( pev->spawnflags, SF_TRACK_STARTBOTTOM ) ) + { + UTIL_SetOrigin (pev, m_vecPosition2); + m_toggle_state = TS_AT_BOTTOM; + pev->angles = m_start; + m_targetState = TS_AT_TOP; + } + else + { + UTIL_SetOrigin (pev, m_vecPosition1); + m_toggle_state = TS_AT_TOP; + pev->angles = m_end; + m_targetState = TS_AT_BOTTOM; + } + + EnableUse(); + pev->nextthink = pev->ltime + 2.0; + SetThink( &CFuncTrackChange::Find ); + Precache(); +} + +void CFuncTrackChange :: Precache( void ) +{ + // Can't trigger sound + PRECACHE_SOUND( "buttons/button11.wav" ); + + CFuncPlatRot::Precache(); +} + + +// UNDONE: Filter touches before re-evaluating the train. +void CFuncTrackChange :: Touch( CBaseEntity *pOther ) +{ +#if 0 + TRAIN_CODE code; + entvars_t *pevToucher = pOther->pev; +#endif +} + + + +void CFuncTrackChange :: KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "train") ) + { + m_trainName = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "toptrack") ) + { + m_trackTopName = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "bottomtrack") ) + { + m_trackBottomName = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CFuncPlatRot::KeyValue( pkvd ); // Pass up to base class + } +} + + +void CFuncTrackChange::OverrideReset( void ) +{ + pev->nextthink = pev->ltime + 1.0; + SetThink( &CFuncTrackChange::Find ); +} + +void CFuncTrackChange :: Find( void ) +{ + // Find track entities + edict_t *target; + + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trackTopName) ); + if ( !FNullEnt(target) ) + { + m_trackTop = CPathTrack::Instance( target ); + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trackBottomName) ); + if ( !FNullEnt(target) ) + { + m_trackBottom = CPathTrack::Instance( target ); + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trainName) ); + if ( !FNullEnt(target) ) + { + m_train = CFuncTrackTrain::Instance( FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trainName) ) ); + if ( !m_train ) + { + ALERT( at_error, "Can't find train for track change! %s\n", STRING(m_trainName) ); + return; + } + Vector center = (pev->absmin + pev->absmax) * 0.5; + m_trackBottom = m_trackBottom->Nearest( center ); + m_trackTop = m_trackTop->Nearest( center ); + UpdateAutoTargets( m_toggle_state ); + SetThink( NULL ); + return; + } + else + { + ALERT( at_error, "Can't find train for track change! %s\n", STRING(m_trainName) ); + target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_trainName) ); + } + } + else + ALERT( at_error, "Can't find bottom track for track change! %s\n", STRING(m_trackBottomName) ); + } + else + ALERT( at_error, "Can't find top track for track change! %s\n", STRING(m_trackTopName) ); +} + + + +TRAIN_CODE CFuncTrackChange :: EvaluateTrain( CPathTrack *pcurrent ) +{ + // Go ahead and work, we don't have anything to switch, so just be an elevator + if ( !pcurrent || !m_train ) + return TRAIN_SAFE; + + if ( m_train->m_ppath == pcurrent || (pcurrent->m_pprevious && m_train->m_ppath == pcurrent->m_pprevious) || + (pcurrent->m_pnext && m_train->m_ppath == pcurrent->m_pnext) ) + { + if ( m_train->pev->speed != 0 ) + return TRAIN_BLOCKING; + + Vector dist = pev->origin - m_train->pev->origin; + float length = dist.Length2D(); + if ( length < m_train->m_length ) // Empirically determined close distance + return TRAIN_FOLLOWING; + else if ( length > (150 + m_train->m_length) ) + return TRAIN_SAFE; + + return TRAIN_BLOCKING; + } + + return TRAIN_SAFE; +} + + +void CFuncTrackChange :: UpdateTrain( Vector &dest ) +{ + float time = (pev->nextthink - pev->ltime); + + m_train->pev->velocity = pev->velocity; + m_train->pev->avelocity = pev->avelocity; + m_train->NextThink( m_train->pev->ltime + time, FALSE ); + + // Attempt at getting the train to rotate properly around the origin of the trackchange + if ( time <= 0 ) + return; + + Vector offset = m_train->pev->origin - pev->origin; + Vector delta = dest - pev->angles; + // Transform offset into local coordinates + UTIL_MakeInvVectors( delta, gpGlobals ); + Vector local; + local.x = DotProduct( offset, gpGlobals->v_forward ); + local.y = DotProduct( offset, gpGlobals->v_right ); + local.z = DotProduct( offset, gpGlobals->v_up ); + + local = local - offset; + m_train->pev->velocity = pev->velocity + (local * (1.0/time)); +} + +void CFuncTrackChange :: GoDown( void ) +{ + if ( m_code == TRAIN_BLOCKING ) + return; + + // HitBottom may get called during CFuncPlat::GoDown(), so set up for that + // before you call GoDown() + + UpdateAutoTargets( TS_GOING_DOWN ); + // If ROTMOVE, move & rotate + if ( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) ) + { + SetMoveDone( &CFuncTrackChange::CallHitBottom ); + m_toggle_state = TS_GOING_DOWN; + AngularMove( m_start, pev->speed ); + } + else + { + CFuncPlat :: GoDown(); + SetMoveDone( &CFuncTrackChange::CallHitBottom ); + RotMove( m_start, pev->nextthink - pev->ltime ); + } + // Otherwise, rotate first, move second + + // If the train is moving with the platform, update it + if ( m_code == TRAIN_FOLLOWING ) + { + UpdateTrain( m_start ); + m_train->m_ppath = NULL; + } +} + + +// +// Platform is at bottom, now starts moving up +// +void CFuncTrackChange :: GoUp( void ) +{ + if ( m_code == TRAIN_BLOCKING ) + return; + + // HitTop may get called during CFuncPlat::GoUp(), so set up for that + // before you call GoUp(); + + UpdateAutoTargets( TS_GOING_UP ); + if ( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) ) + { + m_toggle_state = TS_GOING_UP; + SetMoveDone( &CFuncTrackChange::CallHitTop ); + AngularMove( m_end, pev->speed ); + } + else + { + // If ROTMOVE, move & rotate + CFuncPlat :: GoUp(); + SetMoveDone( &CFuncTrackChange::CallHitTop ); + RotMove( m_end, pev->nextthink - pev->ltime ); + } + + // Otherwise, move first, rotate second + + // If the train is moving with the platform, update it + if ( m_code == TRAIN_FOLLOWING ) + { + UpdateTrain( m_end ); + m_train->m_ppath = NULL; + } +} + + +// Normal track change +void CFuncTrackChange :: UpdateAutoTargets( int toggleState ) +{ + if ( !m_trackTop || !m_trackBottom ) + return; + + if ( toggleState == TS_AT_TOP ) + ClearBits( m_trackTop->pev->spawnflags, SF_PATH_DISABLED ); + else + SetBits( m_trackTop->pev->spawnflags, SF_PATH_DISABLED ); + + if ( toggleState == TS_AT_BOTTOM ) + ClearBits( m_trackBottom->pev->spawnflags, SF_PATH_DISABLED ); + else + SetBits( m_trackBottom->pev->spawnflags, SF_PATH_DISABLED ); +} + + +void CFuncTrackChange :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( m_toggle_state != TS_AT_TOP && m_toggle_state != TS_AT_BOTTOM ) + return; + + // If train is in "safe" area, but not on the elevator, play alarm sound + if ( m_toggle_state == TS_AT_TOP ) + m_code = EvaluateTrain( m_trackTop ); + else if ( m_toggle_state == TS_AT_BOTTOM ) + m_code = EvaluateTrain( m_trackBottom ); + else + m_code = TRAIN_BLOCKING; + if ( m_code == TRAIN_BLOCKING ) + { + // Play alarm and return + EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/button11.wav", 1, ATTN_NORM); + return; + } + + // Otherwise, it's safe to move + // If at top, go down + // at bottom, go up + + DisableUse(); + if (m_toggle_state == TS_AT_TOP) + GoDown(); + else + GoUp(); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncTrackChange :: HitBottom( void ) +{ + CFuncPlatRot :: HitBottom(); + if ( m_code == TRAIN_FOLLOWING ) + { +// UpdateTrain(); + m_train->SetTrack( m_trackBottom ); + } + SetThink( NULL ); + pev->nextthink = -1; + + UpdateAutoTargets( m_toggle_state ); + + EnableUse(); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncTrackChange :: HitTop( void ) +{ + CFuncPlatRot :: HitTop(); + if ( m_code == TRAIN_FOLLOWING ) + { +// UpdateTrain(); + m_train->SetTrack( m_trackTop ); + } + + // Don't let the plat go back down + SetThink( NULL ); + pev->nextthink = -1; + UpdateAutoTargets( m_toggle_state ); + EnableUse(); +} + + + +class CFuncTrackAuto : public CFuncTrackChange +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void UpdateAutoTargets( int toggleState ); +}; + +LINK_ENTITY_TO_CLASS( func_trackautochange, CFuncTrackAuto ); + +// Auto track change +void CFuncTrackAuto :: UpdateAutoTargets( int toggleState ) +{ + CPathTrack *pTarget, *pNextTarget; + + if ( !m_trackTop || !m_trackBottom ) + return; + + if ( m_targetState == TS_AT_TOP ) + { + pTarget = m_trackTop->GetNext(); + pNextTarget = m_trackBottom->GetNext(); + } + else + { + pTarget = m_trackBottom->GetNext(); + pNextTarget = m_trackTop->GetNext(); + } + if ( pTarget ) + { + ClearBits( pTarget->pev->spawnflags, SF_PATH_DISABLED ); + if ( m_code == TRAIN_FOLLOWING && m_train && m_train->pev->speed == 0 ) + m_train->Use( this, this, USE_ON, 0 ); + } + + if ( pNextTarget ) + SetBits( pNextTarget->pev->spawnflags, SF_PATH_DISABLED ); + +} + + +void CFuncTrackAuto :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CPathTrack *pTarget; + + if ( !UseEnabled() ) + return; + + if ( m_toggle_state == TS_AT_TOP ) + pTarget = m_trackTop; + else if ( m_toggle_state == TS_AT_BOTTOM ) + pTarget = m_trackBottom; + else + pTarget = NULL; + + if ( FClassnameIs( pActivator->pev, "func_tracktrain" ) ) + { + m_code = EvaluateTrain( pTarget ); + // Safe to fire? + if ( m_code == TRAIN_FOLLOWING && m_toggle_state != m_targetState ) + { + DisableUse(); + if (m_toggle_state == TS_AT_TOP) + GoDown(); + else + GoUp(); + } + } + else + { + if ( pTarget ) + pTarget = pTarget->GetNext(); + if ( pTarget && m_train->m_ppath != pTarget && ShouldToggle( useType, m_targetState ) ) + { + if ( m_targetState == TS_AT_TOP ) + m_targetState = TS_AT_BOTTOM; + else + m_targetState = TS_AT_TOP; + } + + UpdateAutoTargets( m_targetState ); + } +} + + +// ---------------------------------------------------------- +// +// +// pev->speed is the travel speed +// pev->health is current health +// pev->max_health is the amount to reset to each time it starts + +#define FGUNTARGET_START_ON 0x0001 + +class CGunTarget : public CBaseMonster +{ +public: + void Spawn( void ); + void Activate( void ); + void EXPORT Next( void ); + void EXPORT Start( void ); + void EXPORT Wait( void ); + void Stop( void ); + + int BloodColor( void ) { return DONT_BLEED; } + int Classify( void ) { return CLASS_MACHINE; } + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + Vector BodyTarget( const Vector &posSrc ) { return pev->origin; } + + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + BOOL m_on; +}; + + +LINK_ENTITY_TO_CLASS( func_guntarget, CGunTarget ); + +TYPEDESCRIPTION CGunTarget::m_SaveData[] = +{ + DEFINE_FIELD( CGunTarget, m_on, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CGunTarget, CBaseMonster ); + + +void CGunTarget::Spawn( void ) +{ + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + if ( pev->speed == 0 ) + pev->speed = 100; + + // Don't take damage until "on" + pev->takedamage = DAMAGE_NO; + pev->flags |= FL_MONSTER; + + m_on = FALSE; + pev->max_health = pev->health; + + if ( pev->spawnflags & FGUNTARGET_START_ON ) + { + SetThink( &CGunTarget::Start ); + pev->nextthink = pev->ltime + 0.3; + } +} + + +void CGunTarget::Activate( void ) +{ + CBaseEntity *pTarg; + + // now find our next target + pTarg = GetNextTarget(); + if ( pTarg ) + { + m_hTargetEnt = pTarg; + UTIL_SetOrigin( pev, pTarg->pev->origin - (pev->mins + pev->maxs) * 0.5 ); + } +} + + +void CGunTarget::Start( void ) +{ + Use( this, this, USE_ON, 0 ); +} + + +void CGunTarget::Next( void ) +{ + SetThink( NULL ); + + m_hTargetEnt = GetNextTarget(); + CBaseEntity *pTarget = m_hTargetEnt; + + if ( !pTarget ) + { + Stop(); + return; + } + SetMoveDone( &CGunTarget::Wait ); + LinearMove( pTarget->pev->origin - (pev->mins + pev->maxs) * 0.5, pev->speed ); +} + + +void CGunTarget::Wait( void ) +{ + CBaseEntity *pTarget = m_hTargetEnt; + + if ( !pTarget ) + { + Stop(); + return; + } + + // Fire the pass target if there is one + if ( pTarget->pev->message ) + { + FireTargets( STRING(pTarget->pev->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( pTarget->pev->spawnflags, SF_CORNER_FIREONCE ) ) + pTarget->pev->message = 0; + } + + m_flWait = pTarget->GetDelay(); + + pev->target = pTarget->pev->target; + SetThink( &CGunTarget::Next ); + if (m_flWait != 0) + {// -1 wait will wait forever! + pev->nextthink = pev->ltime + m_flWait; + } + else + { + Next();// do it RIGHT now! + } +} + + +void CGunTarget::Stop( void ) +{ + pev->velocity = g_vecZero; + pev->nextthink = 0; + pev->takedamage = DAMAGE_NO; +} + + +int CGunTarget::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( pev->health > 0 ) + { + pev->health -= flDamage; + if ( pev->health <= 0 ) + { + pev->health = 0; + Stop(); + if ( pev->message ) + FireTargets( STRING(pev->message), this, this, USE_TOGGLE, 0 ); + } + } + return 0; +} + + +void CGunTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_on ) ) + return; + + if ( m_on ) + { + Stop(); + } + else + { + pev->takedamage = DAMAGE_AIM; + m_hTargetEnt = GetNextTarget(); + if ( m_hTargetEnt == NULL ) + return; + pev->health = pev->max_health; + Next(); + } +} + + + diff --git a/ricochet/dlls/player.cpp b/ricochet/dlls/player.cpp new file mode 100644 index 0000000..5ee7b82 --- /dev/null +++ b/ricochet/dlls/player.cpp @@ -0,0 +1,4901 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== player.cpp ======================================================== + + functions dealing with the player + +*/ + +#include "extdll.h" +#include "util.h" + +#include "cbase.h" +#include "player.h" +#include "trains.h" +#include "nodes.h" +#include "weapons.h" +#include "soundent.h" +#include "monsters.h" +#include "../engine/shake.h" +#include "decals.h" +#include "gamerules.h" +#include "animation.h" +#include "discwar.h" +#include "disc_objects.h" +#include "disc_arena.h" + +// #define DUCKFIX + +extern DLL_GLOBAL ULONG g_ulModelIndexPlayer; +extern DLL_GLOBAL BOOL g_fGameOver; +extern DLL_GLOBAL BOOL g_fDrawLines; +int gEvilImpulse101; +extern DLL_GLOBAL int g_iSkillLevel, gDisplayTitle; +BOOL gInitHUD = FALSE; + +extern void CopyToBodyQue(entvars_t* pev); +extern void respawn(entvars_t *pev, BOOL fCopyCorpse); +extern Vector VecBModelOrigin(entvars_t *pevBModel ); +extern edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ); +int MapTextureTypeStepType(char chTextureType); + +entvars_t *g_pevLastInflictor; // Set in combat.cpp. Used to pass the damage inflictor for death messages. + // Better solution: Add as parameter to all Killed() functions. + +// the world node graph +extern CGraph WorldGraph; + +#define PLAYER_WALLJUMP_SPEED 300 // how fast we can spring off walls +#define PLAYER_LONGJUMP_SPEED 350 // how fast we longjump + +#define TRAIN_ACTIVE 0x80 +#define TRAIN_NEW 0xc0 +#define TRAIN_OFF 0x00 +#define TRAIN_NEUTRAL 0x01 +#define TRAIN_SLOW 0x02 +#define TRAIN_MEDIUM 0x03 +#define TRAIN_FAST 0x04 +#define TRAIN_BACK 0x05 + +#define FLASH_DRAIN_TIME 1.2 //100 units/3 minutes +#define FLASH_CHARGE_TIME 0.2 // 100 units/20 seconds (seconds per unit) + + +//#define PLAYER_MAX_SAFE_FALL_DIST 20// falling any farther than this many feet will inflict damage +//#define PLAYER_FATAL_FALL_DIST 60// 100% damage inflicted if player falls this many feet +//#define DAMAGE_PER_UNIT_FALLEN (float)( 100 ) / ( ( PLAYER_FATAL_FALL_DIST - PLAYER_MAX_SAFE_FALL_DIST ) * 12 ) +//#define MAX_SAFE_FALL_UNITS ( PLAYER_MAX_SAFE_FALL_DIST * 12 ) + +// Global Savedata for player +TYPEDESCRIPTION CBasePlayer::m_playerSaveData[] = +{ + DEFINE_FIELD( CBasePlayer, m_flFlashLightTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_iFlashBattery, FIELD_INTEGER ), + + DEFINE_FIELD( CBasePlayer, m_afButtonLast, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_afButtonPressed, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_afButtonReleased, FIELD_INTEGER ), + + DEFINE_ARRAY( CBasePlayer, m_rgItems, FIELD_INTEGER, MAX_ITEMS ), + DEFINE_FIELD( CBasePlayer, m_afPhysicsFlags, FIELD_INTEGER ), + + DEFINE_FIELD( CBasePlayer, m_flTimeStepSound, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flTimeWeaponIdle, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flSwimTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flDuckTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flWallJumpTime, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_flSuitUpdate, FIELD_TIME ), + DEFINE_ARRAY( CBasePlayer, m_rgSuitPlayList, FIELD_INTEGER, CSUITPLAYLIST ), + DEFINE_FIELD( CBasePlayer, m_iSuitPlayNext, FIELD_INTEGER ), + DEFINE_ARRAY( CBasePlayer, m_rgiSuitNoRepeat, FIELD_INTEGER, CSUITNOREPEAT ), + DEFINE_ARRAY( CBasePlayer, m_rgflSuitNoRepeatTime, FIELD_TIME, CSUITNOREPEAT ), + DEFINE_FIELD( CBasePlayer, m_lastDamageAmount, FIELD_INTEGER ), + + DEFINE_ARRAY( CBasePlayer, m_rgpPlayerItems, FIELD_CLASSPTR, MAX_ITEM_TYPES ), + DEFINE_FIELD( CBasePlayer, m_pActiveItem, FIELD_CLASSPTR ), + DEFINE_FIELD( CBasePlayer, m_pLastItem, FIELD_CLASSPTR ), + + DEFINE_ARRAY( CBasePlayer, m_rgAmmo, FIELD_INTEGER, MAX_AMMO_SLOTS ), + DEFINE_FIELD( CBasePlayer, m_idrowndmg, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_idrownrestored, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_tSneaking, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_iTrain, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_bitsHUDDamage, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_flFallVelocity, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayer, m_iTargetVolume, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iWeaponVolume, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iExtraSoundTypes, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iWeaponFlash, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_fLongJump, FIELD_BOOLEAN ), + DEFINE_FIELD( CBasePlayer, m_fInitHUD, FIELD_BOOLEAN ), + DEFINE_FIELD( CBasePlayer, m_tbdPrev, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_pTank, FIELD_EHANDLE ), + DEFINE_FIELD( CBasePlayer, m_iHideHUD, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iFOV, FIELD_INTEGER ), + + //DEFINE_FIELD( CBasePlayer, m_fDeadTime, FIELD_FLOAT ), // only used in multiplayer games + //DEFINE_FIELD( CBasePlayer, m_fGameHUDInitialized, FIELD_INTEGER ), // only used in multiplayer games + //DEFINE_FIELD( CBasePlayer, m_flStopExtraSoundTime, FIELD_TIME ), + //DEFINE_FIELD( CBasePlayer, m_fKnownItem, FIELD_INTEGER ), // reset to zero on load + //DEFINE_FIELD( CBasePlayer, m_iPlayerSound, FIELD_INTEGER ), // Don't restore, set in Precache() + //DEFINE_FIELD( CBasePlayer, m_pentSndLast, FIELD_EDICT ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_flSndRoomtype, FIELD_FLOAT ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_flSndRange, FIELD_FLOAT ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_fNewAmmo, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_flgeigerRange, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_flgeigerDelay, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_igeigerRangePrev, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_iStepLeft, FIELD_INTEGER ), // Don't need to restore + //DEFINE_ARRAY( CBasePlayer, m_szTextureName, FIELD_CHARACTER, CBTEXTURENAMEMAX ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_chTextureType, FIELD_CHARACTER ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_fNoPlayerSound, FIELD_BOOLEAN ), // Don't need to restore, debug + //DEFINE_FIELD( CBasePlayer, m_iUpdateTime, FIELD_INTEGER ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_iClientHealth, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_iClientBattery, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_iClientHideHUD, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_fWeapon, FIELD_BOOLEAN ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_nCustomSprayFrames, FIELD_INTEGER ), // Don't restore, depends on server message after spawning and only matters in multiplayer + //DEFINE_FIELD( CBasePlayer, m_vecAutoAim, FIELD_VECTOR ), // Don't save/restore - this is recomputed + //DEFINE_ARRAY( CBasePlayer, m_rgAmmoLast, FIELD_INTEGER, MAX_AMMO_SLOTS ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_fOnTarget, FIELD_BOOLEAN ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_nCustomSprayFrames, FIELD_INTEGER ), // Don't need to restore + +}; + + +int giPrecacheGrunt = 0; +int gmsgShake = 0; +int gmsgFade = 0; +int gmsgSelAmmo = 0; +int gmsgFlashlight = 0; +int gmsgFlashBattery = 0; +int gmsgResetHUD = 0; +int gmsgInitHUD = 0; +int gmsgShowGameTitle = 0; +int gmsgCurWeapon = 0; +int gmsgHealth = 0; +int gmsgDamage = 0; +int gmsgBattery = 0; +int gmsgTrain = 0; +int gmsgLogo = 0; +int gmsgWeaponList = 0; +int gmsgAmmoX = 0; +int gmsgHudText = 0; +int gmsgDeathMsg = 0; +int gmsgScoreInfo = 0; +int gmsgTeamInfo = 0; +int gmsgTeamScore = 0; +int gmsgGameMode = 0; +int gmsgMOTD = 0; +int gmsgAmmoPickup = 0; +int gmsgWeapPickup = 0; +int gmsgItemPickup = 0; +int gmsgHideWeapon = 0; +int gmsgSetCurWeap = 0; +int gmsgSayText = 0; +int gmsgTextMsg = 0; +int gmsgSetFOV = 0; +int gmsgShowMenu = 0; +int gmsgGeigerRange = 0; +int gmsgSpectator = 0; +int gmsgStartRnd = 0; +int gmsgEndRnd = 0; +int gmsgPowerup = 0; +int gmsgReward = 0; +int gmsgFrozen = 0; + +void LinkUserMessages( void ) +{ + // Already taken care of? + if ( gmsgSelAmmo ) + { + return; + } + + gmsgSelAmmo = REG_USER_MSG("SelAmmo", sizeof(SelAmmo)); + gmsgCurWeapon = REG_USER_MSG("CurWeapon", 3); + gmsgGeigerRange = REG_USER_MSG("Geiger", 1); + gmsgFlashlight = REG_USER_MSG("Flashlight", 2); + gmsgFlashBattery = REG_USER_MSG("FlashBat", 1); + gmsgHealth = REG_USER_MSG( "Health", 1 ); + gmsgDamage = REG_USER_MSG( "Damage", 12 ); + gmsgBattery = REG_USER_MSG( "Battery", 2); + gmsgTrain = REG_USER_MSG( "Train", 1); + gmsgHudText = REG_USER_MSG( "HudText", -1 ); + gmsgSayText = REG_USER_MSG( "SayText", -1 ); + gmsgTextMsg = REG_USER_MSG( "TextMsg", -1 ); + gmsgWeaponList = REG_USER_MSG("WeaponList", -1); + gmsgResetHUD = REG_USER_MSG("ResetHUD", 1); // called every respawn + gmsgInitHUD = REG_USER_MSG("InitHUD", 0 ); // called every time a new player joins the server + gmsgShowGameTitle = REG_USER_MSG("GameTitle", 1); + gmsgDeathMsg = REG_USER_MSG( "DeathMsg", -1 ); + gmsgScoreInfo = REG_USER_MSG( "ScoreInfo", 9 ); + gmsgTeamInfo = REG_USER_MSG( "TeamInfo", -1 ); // sets the name of a player's team + gmsgTeamScore = REG_USER_MSG( "TeamScore", -1 ); // sets the score of a team on the scoreboard + gmsgGameMode = REG_USER_MSG( "GameMode", 1 ); + gmsgMOTD = REG_USER_MSG( "MOTD", -1 ); + gmsgAmmoPickup = REG_USER_MSG( "AmmoPickup", 2 ); + gmsgWeapPickup = REG_USER_MSG( "WeapPickup", 1 ); + gmsgItemPickup = REG_USER_MSG( "ItemPickup", -1 ); + gmsgHideWeapon = REG_USER_MSG( "HideWeapon", 1 ); + gmsgSetFOV = REG_USER_MSG( "SetFOV", 1 ); + gmsgShowMenu = REG_USER_MSG( "ShowMenu", -1 ); + gmsgShake = REG_USER_MSG("ScreenShake", sizeof(ScreenShake)); + gmsgFade = REG_USER_MSG("ScreenFade", sizeof(ScreenFade)); + gmsgAmmoX = REG_USER_MSG("AmmoX", 2); + + gmsgSpectator = REG_USER_MSG( "Spectator", 2 ); + + // Discwar + gmsgStartRnd = REG_USER_MSG( "StartRnd", -1 ); + gmsgEndRnd = REG_USER_MSG( "EndRnd", -1 ); + gmsgPowerup = REG_USER_MSG( "Powerup", 1 ); + gmsgReward = REG_USER_MSG( "Reward", 2 ); + gmsgFrozen = REG_USER_MSG( "Frozen", 1 ); +} + +LINK_ENTITY_TO_CLASS( player, CBasePlayer ); + + + +void CBasePlayer :: Pain( void ) +{ + float flRndSound;//sound randomizer + + flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.33 ) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain5.wav", 1, ATTN_NORM); + else if ( flRndSound <= 0.66 ) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain6.wav", 1, ATTN_NORM); + else + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain7.wav", 1, ATTN_NORM); +} + +/* + * + */ +Vector VecVelocityForDamage(float flDamage) +{ + Vector vec(RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + + if (flDamage > -50) + vec = vec * 0.7; + else if (flDamage > -200) + vec = vec * 2; + else + vec = vec * 10; + + return vec; +} + +#if 0 /* +static void ThrowGib(entvars_t *pev, char *szGibModel, float flDamage) +{ + edict_t *pentNew = CREATE_ENTITY(); + entvars_t *pevNew = VARS(pentNew); + + pevNew->origin = pev->origin; + SET_MODEL(ENT(pevNew), szGibModel); + UTIL_SetSize(pevNew, g_vecZero, g_vecZero); + + pevNew->velocity = VecVelocityForDamage(flDamage); + pevNew->movetype = MOVETYPE_BOUNCE; + pevNew->solid = SOLID_NOT; + pevNew->avelocity.x = RANDOM_FLOAT(0,600); + pevNew->avelocity.y = RANDOM_FLOAT(0,600); + pevNew->avelocity.z = RANDOM_FLOAT(0,600); + CHANGE_METHOD(ENT(pevNew), em_think, SUB_Remove); + pevNew->ltime = gpGlobals->time; + pevNew->nextthink = gpGlobals->time + RANDOM_FLOAT(10,20); + pevNew->frame = 0; + pevNew->flags = 0; +} + + +static void ThrowHead(entvars_t *pev, char *szGibModel, floatflDamage) +{ + SET_MODEL(ENT(pev), szGibModel); + pev->frame = 0; + pev->nextthink = -1; + pev->movetype = MOVETYPE_BOUNCE; + pev->takedamage = DAMAGE_NO; + pev->solid = SOLID_NOT; + pev->view_ofs = Vector(0,0,8); + UTIL_SetSize(pev, Vector(-16,-16,0), Vector(16,16,56)); + pev->velocity = VecVelocityForDamage(flDamage); + pev->avelocity = RANDOM_FLOAT(-1,1) * Vector(0,600,0); + pev->origin.z -= 24; + ClearBits(pev->flags, FL_ONGROUND); +} + + +*/ +#endif + +int TrainSpeed(int iSpeed, int iMax) +{ + float fSpeed, fMax; + int iRet = 0; + + fMax = (float)iMax; + fSpeed = iSpeed; + + fSpeed = fSpeed/fMax; + + if (iSpeed < 0) + iRet = TRAIN_BACK; + else if (iSpeed == 0) + iRet = TRAIN_NEUTRAL; + else if (fSpeed < 0.33) + iRet = TRAIN_SLOW; + else if (fSpeed < 0.66) + iRet = TRAIN_MEDIUM; + else + iRet = TRAIN_FAST; + + return iRet; +} + +void CBasePlayer :: DeathSound( void ) +{ +} + +// override takehealth +// bitsDamageType indicates type of damage healed. + +int CBasePlayer :: TakeHealth( float flHealth, int bitsDamageType ) +{ + return CBaseMonster :: TakeHealth (flHealth, bitsDamageType); + +} + +Vector CBasePlayer :: GetGunPosition( ) +{ +// UTIL_MakeVectors(pev->v_angle); +// m_HackedGunPos = pev->view_ofs; + Vector origin; + + origin = pev->origin + pev->view_ofs; + return origin; +} + +//========================================================= +// TraceAttack +//========================================================= +void CBasePlayer :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( pev->takedamage ) + { + m_LastHitGroup = ptr->iHitgroup; + + switch ( ptr->iHitgroup ) + { + case HITGROUP_GENERIC: + break; + case HITGROUP_HEAD: + flDamage *= gSkillData.plrHead; + break; + case HITGROUP_CHEST: + flDamage *= gSkillData.plrChest; + break; + case HITGROUP_STOMACH: + flDamage *= gSkillData.plrStomach; + break; + case HITGROUP_LEFTARM: + case HITGROUP_RIGHTARM: + flDamage *= gSkillData.plrArm; + break; + case HITGROUP_LEFTLEG: + case HITGROUP_RIGHTLEG: + flDamage *= gSkillData.plrLeg; + break; + default: + break; + } + + SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage);// a little surface blood. + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + } +} + +/* + Take some damage. + NOTE: each call to TakeDamage with bitsDamageType set to a time-based damage + type will cause the damage time countdown to be reset. Thus the ongoing effects of poison, radiation + etc are implemented with subsequent calls to TakeDamage using DMG_GENERIC. +*/ + +#define ARMOR_RATIO 0.2 // Armor Takes 80% of the damage +#define ARMOR_BONUS 0.5 // Each Point of Armor is work 1/x points of health + +int CBasePlayer :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // Abort if its the world doing damage + //if ( ENTINDEX( ENT(pevInflictor) ) == 0 ) + //return 0; + + if (pev->takedamage == DAMAGE_NO) + return 0; + + // Discwar instantly kills everyone when they take damage + pev->health = -1; + g_pevLastInflictor = pevInflictor; + + if ( bitsDamageType & DMG_ALWAYSGIB ) + { + Killed( pevAttacker, GIB_ALWAYS ); + } + else if ( bitsDamageType & DMG_NEVERGIB ) + { + Killed( pevAttacker, GIB_NEVER ); + } + else + { + Killed( pevAttacker, GIB_NORMAL ); + } + + g_pevLastInflictor = NULL; + return 1; +} + +void CBasePlayer::RemoveAllItems( BOOL removeSuit ) +{ + if (m_pActiveItem) + { + ResetAutoaim( ); + m_pActiveItem->Holster( ); + m_pActiveItem = NULL; + } + + m_pLastItem = NULL; + + int i; + CBasePlayerItem *pPendingItem; + for (i = 0; i < MAX_ITEM_TYPES; i++) + { + m_pActiveItem = m_rgpPlayerItems[i]; + while (m_pActiveItem) + { + pPendingItem = m_pActiveItem->m_pNext; + m_pActiveItem->Drop( ); + m_pActiveItem = pPendingItem; + } + m_rgpPlayerItems[i] = NULL; + } + m_pActiveItem = NULL; + + pev->viewmodel = 0; + pev->weaponmodel = 0; + + if ( removeSuit ) + pev->weapons = 0; + else + pev->weapons &= ~WEAPON_ALLWEAPONS; + + for ( i = 0; i < MAX_AMMO_SLOTS;i++) + m_rgAmmo[i] = 0; + + UpdateClientData(); + // send Selected Weapon Message to our client + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(0); + WRITE_BYTE(0); + MESSAGE_END(); +} + +/* + * GLOBALS ASSUMED SET: g_ulModelIndexPlayer + * + * ENTITY_METHOD(PlayerDie) + */ +void CBasePlayer::Killed( entvars_t *pevAttacker, int iGib ) +{ + CSound *pSound; + + // Discwars scoring system + if ( (m_flLastDiscHit != 0) && (gpGlobals->time < m_flLastDiscHit + MAX_SCORE_TIME_AFTER_HIT) ) + { + pevAttacker = ((CBaseEntity*)m_hLastPlayerToHitMe)->pev; + g_pevLastInflictor = ((CBaseEntity*)m_hLastPlayerToHitMe)->pev; + } + + g_pGameRules->PlayerKilled( this, pevAttacker, g_pevLastInflictor ); + + if ( m_pTank != NULL ) + { + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + } + + // this client isn't going to be thinking for a while, so reset the sound until they respawn + pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( edict() ) ); + { + if ( pSound ) + { + pSound->Reset(); + } + } + + SetAnimation( PLAYER_DIE ); + + m_iRespawnFrames = 0; + + pev->modelindex = g_ulModelIndexPlayer; // don't use eyes + + pev->takedamage = DAMAGE_NO; + pev->deadflag = DEAD_DYING; + pev->movetype = MOVETYPE_TOSS; + ClearBits(pev->flags, FL_ONGROUND); + if (pev->velocity.z < 10) + pev->velocity.z += RANDOM_FLOAT(0,300); + + // clear out the suit message cache so we don't keep chattering + SetSuitUpdate(NULL, FALSE, 0); + + // send "health" update message to zero + m_iClientHealth = 0; + MESSAGE_BEGIN( MSG_ONE, gmsgHealth, NULL, pev ); + WRITE_BYTE( m_iClientHealth ); + MESSAGE_END(); + + // Tell Ammo Hud that the player is dead + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(0XFF); + WRITE_BYTE(0xFF); + MESSAGE_END(); + + // reset FOV + m_iFOV = m_iClientFOV = 0; + + MESSAGE_BEGIN( MSG_ONE, gmsgSetFOV, NULL, pev ); + WRITE_BYTE(0); + MESSAGE_END(); + + + // UNDONE: Put this in, but add FFADE_PERMANENT and make fade time 8.8 instead of 4.12 + // UTIL_ScreenFade( edict(), Vector(128,0,0), 6, 15, 255, FFADE_OUT | FFADE_MODULATE ); + + if ( ( pev->health < -40 && iGib != GIB_NEVER ) || iGib == GIB_ALWAYS ) + { + pev->solid = SOLID_NOT; + GibMonster(); // This clears pev->model + pev->effects |= EF_NODRAW; + + // Tell the arena that this player's died + if (m_pCurrentArena) + m_pCurrentArena->PlayerKilled( this ); + + return; + } + + // Tell the arena that this player's died + if (m_pCurrentArena) + m_pCurrentArena->PlayerKilled( this ); + + DeathSound(); + + pev->angles.x = 0; + pev->angles.z = 0; + + SetThink(&CBasePlayer::PlayerDeathThink); + pev->nextthink = gpGlobals->time + 0.1; + + // Tell all this player's discs to remove themselves after the 3rd bounce + edict_t *pFind = FIND_ENTITY_BY_CLASSNAME( NULL, "disc" ); + while ( !FNullEnt( pFind ) ) + { + CBaseEntity *pEnt = CBaseEntity::Instance( pFind ); + CDisc *pDisc = (CDisc *)pEnt; + + if ( pDisc ) + { + if ( ((CBaseEntity*)pDisc->m_hOwner) == this ) + pDisc->m_bRemoveSelf = true; + } + + pFind = FIND_ENTITY_BY_CLASSNAME( pFind, "disc" ); + } +} + +// Hit by a decapitator disc +void CBasePlayer::Decapitate( entvars_t *pevKiller ) +{ + // If the player is frozen, shatter instead of decapitating + if ( m_iFrozen ) + { + Shatter( pevKiller ); + return; + } + + if ( IsAlive() ) + { + m_LastHitGroup = HITGROUP_HEAD; + m_hLastPlayerToHitMe = CBaseEntity::Instance( pevKiller ); + m_flLastDiscHit = gpGlobals->time; + TakeDamage( pev, pevKiller, 500, DMG_NEVERGIB ); + + EMIT_SOUND(ENT(pev), CHAN_AUTO, "decap.wav", 1, ATTN_NORM); + + // Remove the Head + SetBodygroup( 1, 2 ); + + // Spawn a head + CGib *pGib = GetClassPtr( (CGib *)NULL ); + pGib->Spawn( "models/head.mdl" );// throw one head + pGib->pev->solid = SOLID_NOT; + pGib->pev->groupinfo = pev->groupinfo; + pGib->pev->origin = pev->origin + pev->view_ofs; + pGib->pev->velocity = Vector (RANDOM_FLOAT(-10,10), RANDOM_FLOAT(-10,10), 300); + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + pGib->m_bloodColor = BloodColor(); + } +} + +// Hit by a frozen decapitator disc +void CBasePlayer::Shatter( entvars_t *pevKiller ) +{ + if ( IsAlive() ) + { + m_LastHitGroup = HITGROUP_HEAD; + m_hLastPlayerToHitMe = CBaseEntity::Instance( pevKiller ); + m_flLastDiscHit = gpGlobals->time; + TakeDamage( pev, pevKiller, 500, DMG_NEVERGIB ); + + EMIT_SOUND(ENT(pev), CHAN_AUTO, "shatter.wav", 1, ATTN_NORM); + + // make myself invisible + pev->effects |= EF_NODRAW; + pev->solid = SOLID_NOT; + UTIL_SetOrigin( pev, pev->origin ); + + // Spawn a head + CGib::SpawnHeadGib( pev ); + CGib::SpawnRandomGibs( pev, 6, 1 ); + } +} + +#define WALK_SPEED 100 + +// Set the activity based on an event or current state +void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) +{ + int animDesired; + float speed; + + speed = pev->velocity.Length2D(); + + if (pev->flags & FL_FROZEN) + { + speed = 0; + playerAnim = PLAYER_IDLE; + } + + switch (playerAnim) + { + case PLAYER_JUMP: + m_IdealActivity = ACT_HOP; + break; + + case PLAYER_SUPERJUMP: + m_IdealActivity = ACT_LEAP; + break; + + case PLAYER_DIE: + m_IdealActivity = GetDeathActivity(); + break; + + case PLAYER_ATTACK1: + m_IdealActivity = ACT_BASE_THROW; + break; + + case PLAYER_FALL: + m_IdealActivity = ACT_FALL; + break; + + case PLAYER_IDLE: + case PLAYER_WALK: + if ( !FBitSet( pev->flags, FL_ONGROUND ) && (m_Activity == ACT_HOP) ) // Still jumping + { + m_IdealActivity = m_Activity; + } + else + { + m_IdealActivity = ACT_BASE_WALK; + } + break; + } + + Vector vecNormVel; + float flDot, flSideDot, flVelDot; + bool bInReverse; + int iFrame; + + // Decide which sequence to play based upon the activity + switch (m_IdealActivity) + { + case ACT_DIEFORWARD: + case ACT_FALL: + default: + if ( m_Activity == m_IdealActivity) + return; + m_Activity = ACT_FALL; + + animDesired = GetFallAnimation(); + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + pev->gaitsequence = 0; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + + case ACT_LEAP: + UTIL_MakeVectors( pev->angles ); + vecNormVel = pev->velocity.Normalize(); + flDot = DotProduct( vecNormVel, gpGlobals->v_forward ); + if ( flDot < -0.6 ) + { + // Use non-blended backflip + animDesired = LookupSequence( "backflip" ); + m_Activity = m_IdealActivity; + pev->gaitsequence = 0; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + } + else + { + // Use blended longjump + animDesired = LookupSequence( "longjump" ); + m_Activity = ACT_LEAP; + pev->gaitsequence = animDesired; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + } + break; + + case ACT_DIE_HEADSHOT: + animDesired = LookupSequence( "die_simple" ); + m_Activity = m_IdealActivity; + + pev->gaitsequence = 0; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + + case ACT_HOP: + iFrame = pev->frame / 18; + if ( iFrame >= 2 && iFrame <= 11 ) + animDesired = LookupSequence( "jump" ); + else + animDesired = LookupSequence( "jumpl" ); + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + + case ACT_BASE_THROW: + // No throw animation during backflip + if ( pev->sequence == LookupSequence( "backflip" ) ) + return; + + // If we're in the air, we need to use the blended longjump throw + if ( pev->sequence == LookupSequence( "longjump" ) ) + { + // Use blended longjump + animDesired = LookupSequence( "longjump_throw" ); + m_Activity = ACT_FLINCH_CLOCKWISE; + pev->gaitsequence = animDesired; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + } + + animDesired = GetThrowAnim(); + m_Activity = m_IdealActivity; + m_flThrowTime = gpGlobals->time; + break; + + case ACT_BASE_WALK: + UTIL_MakeVectors( pev->angles ); + bInReverse = ( pev->sequence == LookupSequence("base_reverse") ); + vecNormVel = pev->velocity.Normalize(); + flDot = DotProduct( vecNormVel, gpGlobals->v_forward ); + flSideDot = DotProduct( vecNormVel, gpGlobals->v_right ); + flVelDot = DotProduct( m_vecOldVelocity, vecNormVel ); + if ( ( m_flBackupTime < gpGlobals->time ) && (m_Activity != ACT_BASE_THROW) || m_fSequenceFinished ) + { + //UTIL_MakeVectors( pev->angles ); + //ALERT(at_console, "%f\n", flDot ); + //ALERT(at_console, "%f\n", flSideDot ); + //ALERT(at_console, "%f\n", flVelDot ); + //ALERT(at_console, "%f %f\n", flVelDot, flDot ); + + if ( speed == 0 ) + { + animDesired = LookupSequence( "base_stand" ); + } + /* + else if ( ( pev->sequence == LookupSequence("base_backup") ) && ( flSideDot < -0.3 || flSideDot > 0.3 ) && (flDot < -0.3) ) + { + // Detect running backwards -> strafe transition + animDesired = LookupSequence( "base_reverse" ); + m_flTransitionTime = gpGlobals->time + 0.5; + ALERT(at_console, "HERE.. "); + } + */ + else if ( flDot < -0.6 ) // && m_flTransitionTime < gpGlobals->time ) + { + animDesired = LookupSequence( "base_backup" ); + } + else if ( ( flVelDot <= 0 ) && ( flDot <= 0.6 ) ) + { + animDesired = LookupSequence( "base_reverse" ); + m_flBackupTime = gpGlobals->time + 0.7; + pev->effects |= EF_NOINTERP; + } + else + { + if ( speed > WALK_SPEED ) + animDesired = LookupSequence( "base_run" ); + else + animDesired = LookupSequence( "base_walk" ); + } + + if (animDesired == -1) + { + animDesired = 0; + } + m_Activity = ACT_BASE_WALK; + } + else if ( bInReverse ) + { + // Don't play the backup run if we're still in the backup run + if ( DotProduct( m_vecOldVelocity, vecNormVel ) < 0 ) + { + m_flBackupTime = 0; + if ( speed > WALK_SPEED ) + animDesired = LookupSequence( "base_run" ); + else + animDesired = LookupSequence( "base_walk" ); + pev->effects |= EF_NOINTERP; + } + else + { + animDesired = pev->sequence; + } + } + else + { + animDesired = pev->sequence; + } + break; + } + + // Set gait animation + if ( m_flBackupTime > gpGlobals->time ) + { + pev->gaitsequence = LookupSequence( "base_backup" ); + } + else + { + if ( speed > WALK_SPEED ) + { + pev->gaitsequence = LookupSequence( "base_run" ); + } + else if (speed > 0) + { + pev->gaitsequence = LookupSequence( "base_walk" ); + } + } + + // Idle? + if (speed <= 0) + { + pev->gaitsequence = LookupSequence( "base_stand" ); + } + + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + // Reset to first frame of desired animation + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); +} + +int CBasePlayer::GetThrowAnim( void ) +{ + int throwAnim; + + if ( pev->velocity.Length2D() == 0 ) + throwAnim = LookupSequence( "base_stand_throw" ); + /* + // Choose throw anim based upon powerups. + // Multiple Powerups can be had, in which case the one considered more dangerous has the throw animation. + else if ( m_iPowerups & POW_TRIPLE ) + throwAnim = LookupSequence( "triple_throw" ); + else if ( m_iPowerups & POW_FAST ) + throwAnim = LookupSequence( "kill_throw" ); + else if ( m_iPowerups & POW_HARD ) + throwAnim = LookupSequence( "hard_throw" ); + else if ( m_iPowerups & POW_FREEZE ) + throwAnim = LookupSequence( "freeze_throw" ); + */ + else + throwAnim = LookupSequence( "base_throw" ); + + return throwAnim; +} + +int CBasePlayer::GetHoldAnim( void ) +{ + int holdAnim; + + // Choose hold anim based upon powerups. + // Multiple Powerups can be had, in which case the one considered more dangerous has the animation. + if ( m_iPowerups & POW_TRIPLE ) + holdAnim = LookupSequence( "triple_ready" ); + else if ( m_iPowerups & POW_FAST ) + holdAnim = LookupSequence( "kill_ready" ); + else if ( m_iPowerups & POW_HARD ) + holdAnim = LookupSequence( "hard_ready" ); + else if ( m_iPowerups & POW_FREEZE ) + holdAnim = LookupSequence( "freeze_ready" ); + else + holdAnim = LookupSequence( "base_ready" ); + + return holdAnim; +} + +int CBasePlayer::GetFallAnimation( void ) +{ + Vector vecNormVel = pev->velocity.Normalize(); + int fallAnim; + + UTIL_MakeVectors( pev->angles ); + float flDot = DotProduct( vecNormVel, gpGlobals->v_forward ); + float flSideDot = DotProduct( vecNormVel, gpGlobals->v_right ); + // Choose a falling animation based upon the velocity vector + if ( flDot < -0.6 ) + fallAnim = LookupSequence( "fall_b" ); + else if ( flSideDot < -0.6 ) + fallAnim = LookupSequence( "fall_r" ); + else if ( flSideDot > 0.6 ) + fallAnim = LookupSequence( "fall_l" ); + else + fallAnim = LookupSequence( "fall_f" ); + + return fallAnim; +} + +/* +=========== +WaterMove +============ +*/ +#define AIRTIME 12 // lung full of air lasts this many seconds + +void CBasePlayer::WaterMove() +{ + int air; + + if (pev->movetype == MOVETYPE_NOCLIP) + return; + + if (pev->health < 0) + return; + + // waterlevel 0 - not in water + // waterlevel 1 - feet in water + // waterlevel 2 - waist in water + // waterlevel 3 - head in water + + if (pev->waterlevel != 3) + { + // not underwater + + // play 'up for air' sound + if (pev->air_finished < gpGlobals->time) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_wade1.wav", 1, ATTN_NORM); + else if (pev->air_finished < gpGlobals->time + 9) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_wade2.wav", 1, ATTN_NORM); + + pev->air_finished = gpGlobals->time + AIRTIME; + pev->dmg = 2; + + // if we took drowning damage, give it back slowly + if (m_idrowndmg > m_idrownrestored) + { + // set drowning damage bit. hack - dmg_drownrecover actually + // makes the time based damage code 'give back' health over time. + // make sure counter is cleared so we start count correctly. + + // NOTE: this actually causes the count to continue restarting + // until all drowning damage is healed. + + m_bitsDamageType |= DMG_DROWNRECOVER; + m_bitsDamageType &= ~DMG_DROWN; + m_rgbTimeBasedDamage[itbd_DrownRecover] = 0; + } + + } + else + { // fully under water + // stop restoring damage while underwater + m_bitsDamageType &= ~DMG_DROWNRECOVER; + m_rgbTimeBasedDamage[itbd_DrownRecover] = 0; + + if (pev->air_finished < gpGlobals->time) // drown! + { + if (pev->pain_finished < gpGlobals->time) + { + // take drowning damage + pev->dmg += 1; + if (pev->dmg > 5) + pev->dmg = 5; + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), pev->dmg, DMG_DROWN); + pev->pain_finished = gpGlobals->time + 1; + + // track drowning damage, give it back when + // player finally takes a breath + + m_idrowndmg += pev->dmg; + } + } + else + { + m_bitsDamageType &= ~DMG_DROWN; + } + } + + if (!pev->waterlevel) + { + if (FBitSet(pev->flags, FL_INWATER)) + { +#if 0 + // play leave water sound + switch (RANDOM_LONG(0,3)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM); break; + } +#endif + + ClearBits(pev->flags, FL_INWATER); + } + return; + } + + // make bubbles + + air = (int)(pev->air_finished - gpGlobals->time); + if (!RANDOM_LONG(0,0x1f) && RANDOM_LONG(0,AIRTIME-1) >= air) + { + switch (RANDOM_LONG(0,3)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim1.wav", 0.8, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim2.wav", 0.8, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim3.wav", 0.8, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim4.wav", 0.8, ATTN_NORM); break; + } + } + + if (pev->watertype == CONTENT_LAVA) // do damage + { + if (pev->dmgtime < gpGlobals->time) + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), 10 * pev->waterlevel, DMG_BURN); + } + else if (pev->watertype == CONTENT_SLIME) // do damage + { + pev->dmgtime = gpGlobals->time + 1; + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), 4 * pev->waterlevel, DMG_ACID); + } + + if (!FBitSet(pev->flags, FL_INWATER)) + { +#if 0 + // player enter water sound + if (pev->watertype == CONTENT_WATER) + { + switch (RANDOM_LONG(0,3)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM); break; + } + } +#endif + + SetBits(pev->flags, FL_INWATER); + pev->dmgtime = 0; + } + + if (!FBitSet(pev->flags, FL_WATERJUMP)) + pev->velocity = pev->velocity - 0.8 * pev->waterlevel * gpGlobals->frametime * pev->velocity; +} + + +// TRUE if the player is attached to a ladder +BOOL CBasePlayer::IsOnLadder( void ) +{ + return (pev->movetype == MOVETYPE_FLY); +} + +void CBasePlayer::PlayerDeathThink(void) +{ + float flForward; + + if (FBitSet(pev->flags, FL_ONGROUND)) + { + flForward = pev->velocity.Length() - 20; + if (flForward <= 0) + pev->velocity = g_vecZero; + else + pev->velocity = flForward * pev->velocity.Normalize(); + } + + if (pev->modelindex && (!m_fSequenceFinished) && (pev->deadflag == DEAD_DYING)) + { + StudioFrameAdvance( ); + + m_iRespawnFrames++; // Note, these aren't necessarily real "frames", so behavior is dependent on # of client movement commands + if ( m_iRespawnFrames < 120 ) // Animations should be no longer than this + return; + } + + if (pev->deadflag == DEAD_DYING) + pev->deadflag = DEAD_DEAD; + + StopAnimation(); + + pev->effects |= EF_NOINTERP; + pev->framerate = 0.0; + + if (pev->deadflag == DEAD_DEAD) + { + if ( g_pGameRules->FPlayerCanRespawn( this ) ) + { + m_fDeadTime = gpGlobals->time; + pev->deadflag = DEAD_RESPAWNABLE; + } + + return; + } + +// if the player has been dead for one second longer than allowed by forcerespawn, +// forcerespawn isn't on. Send the player off to an intermission camera until they +// choose to respawn. + if ( g_pGameRules->IsMultiplayer() && ( gpGlobals->time > (m_fDeadTime + 6) ) && !(m_afPhysicsFlags & PFLAG_OBSERVER) ) + { + // go to dead camera. + StartDeathCam(); + } + + // wait three seconds to respawn + if ( gpGlobals->time <= ( m_fDeadTime + 3 ) ) + return; + + pev->button = 0; + m_iRespawnFrames = 0; + + //ALERT(at_console, "Respawn\n"); + respawn(pev, !(m_afPhysicsFlags & PFLAG_OBSERVER) );// don't copy a corpse if we're in deathcam. + pev->nextthink = -1; + + // Tell the arena that this player's died + if (m_pCurrentArena) + m_pCurrentArena->PlayerRespawned( this ); +} + +//========================================================= +// StartDeathCam - find an intermission spot and send the +// player off into observer mode +//========================================================= +void CBasePlayer::StartDeathCam( void ) +{ + edict_t *pSpot, *pNewSpot; + int iRand; + + if ( pev->view_ofs == g_vecZero ) + { + // don't accept subsequent attempts to StartDeathCam() + return; + } + + pSpot = FIND_ENTITY_BY_CLASSNAME( NULL, "info_intermission"); + + if ( !FNullEnt( pSpot ) ) + { + // at least one intermission spot in the world. + iRand = RANDOM_LONG( 0, 3 ); + + while ( iRand > 0 ) + { + pNewSpot = FIND_ENTITY_BY_CLASSNAME( pSpot, "info_intermission"); + + if ( pNewSpot ) + { + pSpot = pNewSpot; + } + + iRand--; + } + + CopyToBodyQue( pev ); + StartObserver( pSpot->v.origin, pSpot->v.v_angle ); + } + else + { + // no intermission spot. Push them up in the air, looking down at their corpse + TraceResult tr; + CopyToBodyQue( pev ); + UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, 128 ), ignore_monsters, edict(), &tr ); + StartObserver( tr.vecEndPos, UTIL_VecToAngles( tr.vecEndPos - pev->origin ) ); + return; + } +} + +// +// PlayerUse - handles USE keypress +// +#define PLAYER_SEARCH_RADIUS (float)64 + +void CBasePlayer::PlayerUse ( void ) +{ + // Was use pressed or released? + if ( ! ((pev->button | m_afButtonPressed | m_afButtonReleased) & IN_USE) ) + return; + + // Hit Use on a train? + if ( m_afButtonPressed & IN_USE ) + { + if ( m_pTank != NULL ) + { + // Stop controlling the tank + // TODO: Send HUD Update + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + return; + } + else + { + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + { + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + else + { // Start controlling the train! + CBaseEntity *pTrain = CBaseEntity::Instance( pev->groundentity ); + + if ( pTrain && !(pev->button & IN_JUMP) && FBitSet(pev->flags, FL_ONGROUND) && (pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) && pTrain->OnControls(pev) ) + { + m_afPhysicsFlags |= PFLAG_ONTRAIN; + m_iTrain = TrainSpeed(pTrain->pev->speed, pTrain->pev->impulse); + m_iTrain |= TRAIN_NEW; + EMIT_SOUND( ENT(pev), CHAN_ITEM, "plats/train_use1.wav", 0.8, ATTN_NORM); + return; + } + } + } + } + + CBaseEntity *pObject = NULL; + CBaseEntity *pClosest = NULL; + Vector vecLOS; + float flMaxDot = VIEW_FIELD_NARROW; + float flDot; + + UTIL_MakeVectors ( pev->v_angle );// so we know which way we are facing + + while ((pObject = UTIL_FindEntityInSphere( pObject, pev->origin, PLAYER_SEARCH_RADIUS )) != NULL) + { + + if (pObject->ObjectCaps() & (FCAP_IMPULSE_USE | FCAP_CONTINUOUS_USE | FCAP_ONOFF_USE)) + { + // !!!PERFORMANCE- should this check be done on a per case basis AFTER we've determined that + // this object is actually usable? This dot is being done for every object within PLAYER_SEARCH_RADIUS + // when player hits the use key. How many objects can be in that area, anyway? (sjb) + vecLOS = (VecBModelOrigin( pObject->pev ) - (pev->origin + pev->view_ofs)); + + // This essentially moves the origin of the target to the corner nearest the player to test to see + // if it's "hull" is in the view cone + vecLOS = UTIL_ClampVectorToBox( vecLOS, pObject->pev->size * 0.5 ); + + flDot = DotProduct (vecLOS , gpGlobals->v_forward); + if (flDot > flMaxDot ) + {// only if the item is in front of the user + pClosest = pObject; + flMaxDot = flDot; +// ALERT( at_console, "%s : %f\n", STRING( pObject->pev->classname ), flDot ); + } +// ALERT( at_console, "%s : %f\n", STRING( pObject->pev->classname ), flDot ); + } + } + pObject = pClosest; + + // Found an object + if (pObject ) + { + //!!!UNDONE: traceline here to prevent USEing buttons through walls + int caps = pObject->ObjectCaps(); + + if ( m_afButtonPressed & IN_USE ) + EMIT_SOUND( ENT(pev), CHAN_ITEM, "common/wpn_select.wav", 0.4, ATTN_NORM); + + if ( ( (pev->button & IN_USE) && (caps & FCAP_CONTINUOUS_USE) ) || + ( (m_afButtonPressed & IN_USE) && (caps & (FCAP_IMPULSE_USE|FCAP_ONOFF_USE)) ) ) + { + if ( caps & FCAP_CONTINUOUS_USE ) + m_afPhysicsFlags |= PFLAG_USING; + + pObject->Use( this, this, USE_SET, 1 ); + } + // UNDONE: Send different USE codes for ON/OFF. Cache last ONOFF_USE object to send 'off' if you turn away + else if ( (m_afButtonReleased & IN_USE) && (pObject->ObjectCaps() & FCAP_ONOFF_USE) ) // BUGBUG This is an "off" use + { + pObject->Use( this, this, USE_SET, 0 ); + } + } + else + { + if ( m_afButtonPressed & IN_USE ) + EMIT_SOUND( ENT(pev), CHAN_ITEM, "common/wpn_denyselect.wav", 0.4, ATTN_NORM); + } +} + + + +void CBasePlayer::Jump() +{ + Vector vecWallCheckDir;// direction we're tracing a line to find a wall when walljumping + Vector vecAdjustedVelocity; + Vector vecSpot; + TraceResult tr; + + if (FBitSet(pev->flags, FL_WATERJUMP)) + return; + + if (pev->waterlevel >= 2) + { + return; + } + + // jump velocity is sqrt( height * gravity * 2) + + // If this isn't the first frame pressing the jump button, break out. + if ( !FBitSet( m_afButtonPressed, IN_JUMP ) ) + return; // don't pogo stick + + if ( !(pev->flags & FL_ONGROUND) || !pev->groundentity ) + { + return; + } + +// many features in this function use v_forward, so makevectors now. + UTIL_MakeVectors (pev->angles); + + SetAnimation( PLAYER_JUMP ); + + // If you're standing on a conveyor, add it's velocity to yours (for momentum) + entvars_t *pevGround = VARS(pev->groundentity); + if ( pevGround && (pevGround->flags & FL_CONVEYOR) ) + { + pev->velocity = pev->velocity + pev->basevelocity; + } +} + + + +// This is a glorious hack to find free space when you've crouched into some solid space +// Our crouching collisions do not work correctly for some reason and this is easier +// than fixing the problem :( +void FixPlayerCrouchStuck( edict_t *pPlayer ) +{ + TraceResult trace; + + // Move up as many as 18 pixels if the player is stuck. + for ( int i = 0; i < 18; i++ ) + { + UTIL_TraceHull( pPlayer->v.origin, pPlayer->v.origin, dont_ignore_monsters, head_hull, pPlayer, &trace ); + if ( trace.fStartSolid ) + pPlayer->v.origin.z ++; + else + break; + } +} + +void CBasePlayer::Duck( ) +{ + return; +} + +// +// ID's player as such. +// +int CBasePlayer::Classify ( void ) +{ + return CLASS_PLAYER; +} + + +void CBasePlayer::AddPoints( int score, BOOL bAllowNegativeScore ) +{ + // Positive score always adds + if ( score < 0 ) + { + if ( !bAllowNegativeScore ) + { + if ( pev->frags < 0 ) // Can't go more negative + return; + + if ( -score > pev->frags ) // Will this go negative? + { + score = -pev->frags; // Sum will be 0 + } + } + } + + pev->frags += score; +} + + +void CBasePlayer::AddPointsToTeam( int score, BOOL bAllowNegativeScore ) +{ + int index = entindex(); + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + if ( pPlayer && i != index ) + { + if ( g_pGameRules->PlayerRelationship( this, pPlayer ) == GR_TEAMMATE ) + { + pPlayer->AddPoints( score, bAllowNegativeScore ); + } + } + } +} + +#if 0 +void CBasePlayer::CheckWeapon(void) +{ + // play a weapon idle anim if it's time! + if ( gpGlobals->time > m_flTimeWeaponIdle ) + { + WeaponIdle ( ); + } +} +#endif + + +// play a footstep if it's time - this will eventually be frame-based. not time based. + +#define STEP_CONCRETE 0 // default step sound +#define STEP_METAL 1 // metal floor +#define STEP_DIRT 2 // dirt, sand, rock +#define STEP_VENT 3 // ventillation duct +#define STEP_GRATE 4 // metal grating +#define STEP_TILE 5 // floor tiles +#define STEP_SLOSH 6 // shallow liquid puddle +#define STEP_WADE 7 // wading in liquid +#define STEP_LADDER 8 // climbing ladder + +// Play correct step sound for material we're on or in + +void CBasePlayer :: PlayStepSound(int step, float fvol) +{ + static int iSkipStep = 0; + + if ( !g_pGameRules->PlayFootstepSounds( this, fvol ) ) + return; + + // irand - 0,1 for right foot, 2,3 for left foot + // used to alternate left and right foot + int irand = RANDOM_LONG(0,1) + (m_iStepLeft * 2); + + m_iStepLeft = !m_iStepLeft; + + switch (step) + { + default: + case STEP_CONCRETE: + switch (irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_METAL: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_DIRT: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_VENT: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_GRATE: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_TILE: + if (!RANDOM_LONG(0,4)) + irand = 4; + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile4.wav", fvol, ATTN_NORM); break; + case 4: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile5.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_SLOSH: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_WADE: + if ( iSkipStep == 0 ) + { + iSkipStep++; + break; + } + + if ( iSkipStep++ == 3 ) + { + iSkipStep = 0; + } + + switch (irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade2.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade3.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_LADDER: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder4.wav", fvol, ATTN_NORM); break; + } + break; + } +} + +// Simple mapping from texture type character to step type + +int MapTextureTypeStepType(char chTextureType) +{ +switch (chTextureType) + { + default: + case CHAR_TEX_CONCRETE: return STEP_CONCRETE; + case CHAR_TEX_METAL: return STEP_METAL; + case CHAR_TEX_DIRT: return STEP_DIRT; + case CHAR_TEX_VENT: return STEP_VENT; + case CHAR_TEX_GRATE: return STEP_GRATE; + case CHAR_TEX_TILE: return STEP_TILE; + case CHAR_TEX_SLOSH: return STEP_SLOSH; + } +} + +// Play left or right footstep based on material player is on or in + +void CBasePlayer :: UpdateStepSound( void ) +{ + int fWalking; + float fvol; + char szbuffer[64]; + const char *pTextureName; + Vector start, end; + float rgfl1[3]; + float rgfl2[3]; + Vector knee; + Vector feet; + Vector center; + float height; + float speed; + float velrun; + float velwalk; + float flduck; + int fLadder; + int step; + + if (gpGlobals->time <= m_flTimeStepSound) + return; + + if (pev->flags & FL_FROZEN) + return; + + if (m_flTouchedByJumpPad > gpGlobals->time) + return; + + speed = pev->velocity.Length(); + + // determine if we are on a ladder + fLadder = IsOnLadder(); + + // UNDONE: need defined numbers for run, walk, crouch, crouch run velocities!!!! + if (FBitSet(pev->flags, FL_DUCKING) || fLadder) + { + velwalk = 60; // These constants should be based on cl_movespeedkey * cl_forwardspeed somehow + velrun = 80; // UNDONE: Move walking to server + flduck = 0.1; + } + else + { + velwalk = 120; + velrun = 210; + flduck = 0.0; + } + + // ALERT (at_console, "vel: %f\n", vecVel.Length()); + + // if we're on a ladder or on the ground, and we're moving fast enough, + // play step sound. Also, if m_flTimeStepSound is zero, get the new + // sound right away - we just started moving in new level. + + if ((fLadder || FBitSet (pev->flags, FL_ONGROUND)) && pev->velocity != g_vecZero + && (speed >= velwalk || !m_flTimeStepSound)) + { + SetAnimation( PLAYER_WALK ); + + fWalking = speed < velrun; + + center = knee = feet = (pev->absmin + pev->absmax) * 0.5; + height = pev->absmax.z - pev->absmin.z; + + knee.z = pev->absmin.z + height * 0.2; + feet.z = pev->absmin.z; + + // find out what we're stepping in or on... + if (fLadder) + { + step = STEP_LADDER; + fvol = 0.35; + m_flTimeStepSound = gpGlobals->time + 0.35; + } + else if ( UTIL_PointContents ( knee ) == CONTENTS_WATER ) + { + step = STEP_WADE; + fvol = 0.65; + m_flTimeStepSound = gpGlobals->time + 0.6; + } + else if (UTIL_PointContents ( feet ) == CONTENTS_WATER ) + { + step = STEP_SLOSH; + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + } + else + { + // find texture under player, if different from current texture, + // get material type + + start = end = center; // center point of player BB + start.z = end.z = pev->absmin.z; // copy zmin + start.z += 4.0; // extend start up + end.z -= 24.0; // extend end down + + start.CopyToArray(rgfl1); + end.CopyToArray(rgfl2); + + pTextureName = TRACE_TEXTURE( ENT( pev->groundentity), rgfl1, rgfl2 ); + if ( pTextureName ) + { + // strip leading '-0' or '{' or '!' + if (*pTextureName == '-') + pTextureName += 2; + if (*pTextureName == '{' || *pTextureName == '!') + pTextureName++; + + if (_strnicmp(pTextureName, m_szTextureName, CBTEXTURENAMEMAX-1)) + { + // current texture is different from texture player is on... + // set current texture + strcpy(szbuffer, pTextureName); + szbuffer[CBTEXTURENAMEMAX - 1] = 0; + strcpy(m_szTextureName, szbuffer); + + // ALERT ( at_aiconsole, "texture: %s\n", m_szTextureName ); + + // get texture type + m_chTextureType = TEXTURETYPE_Find(m_szTextureName); + } + } + + step = MapTextureTypeStepType(m_chTextureType); + + switch (m_chTextureType) + { + default: + case CHAR_TEX_CONCRETE: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_METAL: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_DIRT: + fvol = fWalking ? 0.25 : 0.55; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_VENT: + fvol = fWalking ? 0.4 : 0.7; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_GRATE: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_TILE: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_SLOSH: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + } + } + + m_flTimeStepSound += flduck; // slower step time if ducking + + // play the sound + + // 35% volume if ducking + if ( pev->flags & FL_DUCKING ) + fvol *= 0.35; + } +} + + +#define CLIMB_SHAKE_FREQUENCY 22 // how many frames in between screen shakes when climbing +#define MAX_CLIMB_SPEED 200 // fastest vertical climbing speed possible +#define CLIMB_SPEED_DEC 15 // climbing deceleration rate +#define CLIMB_PUNCH_X -7 // how far to 'punch' client X axis when climbing +#define CLIMB_PUNCH_Z 7 // how far to 'punch' client Z axis when climbing + +void CBasePlayer::PreThink(void) +{ + int buttonsChanged = (m_afButtonLast ^ pev->button); // These buttons have changed this frame + + // Fade away the renderfx + if ( pev->renderamt ) + { + if ( !m_iFrozen ) + pev->renderamt -= 25; + + if (pev->renderamt <= 0 || (m_flFreezeTime < gpGlobals->time && m_iFrozen )) + ClearFreezeAndRender(); + } + + if ( m_flSendArenaStatus != -1 && m_flSendArenaStatus <= gpGlobals->time ) + { + g_pGameRules->UpdateGameMode( this ); + m_flSendArenaStatus = -1; + } + + // Hack to pass down the weapon information to the client after they've connected. + // Needed for listen servers because they lose msgs while bringing up the svr. + if ( m_flKnownItemTime && m_flKnownItemTime < gpGlobals->time ) + { + CLIENT_COMMAND( edict(), "r_drawviewmodel 0\n" ); + m_fKnownItem = FALSE; + m_flKnownItemTime = 0; + } + + // Debounced button codes for pressed/released + // UNDONE: Do we need auto-repeat? + m_afButtonPressed = buttonsChanged & pev->button; // The changed ones still down are "pressed" + m_afButtonReleased = buttonsChanged & (~pev->button); // The ones not down are "released" + + g_pGameRules->PlayerThink( this ); + + if ( g_fGameOver ) + return; // intermission or finale + + UTIL_MakeVectors(pev->v_angle); // is this still used? + + ItemPreFrame( ); + WaterMove(); + + if ( g_pGameRules && g_pGameRules->FAllowFlashlight() ) + m_iHideHUD &= ~HIDEHUD_FLASHLIGHT; + else + m_iHideHUD |= HIDEHUD_FLASHLIGHT; + + + // JOHN: checks if new client data (for HUD and view control) needs to be sent to the client + UpdateClientData(); + + CheckTimeBasedDamage(); + + // Observer Button Handling + if ( IsObserver() ) + { + Observer_HandleButtons(); + pev->impulse = 0; + return; + } + + CheckSuitUpdate(); + + if (pev->deadflag >= DEAD_DYING) + { + PlayerDeathThink(); + return; + } + + // So the correct flags get sent to client asap. + // + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + pev->flags |= FL_ONTRAIN; + else + pev->flags &= ~FL_ONTRAIN; + + // Train speed control + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + { + CBaseEntity *pTrain = CBaseEntity::Instance( pev->groundentity ); + float vel; + + if ( !pTrain ) + { + TraceResult trainTrace; + // Maybe this is on the other side of a level transition + UTIL_TraceLine( pev->origin, pev->origin + Vector(0,0,-38), ignore_monsters, ENT(pev), &trainTrace ); + + // HACKHACK - Just look for the func_tracktrain classname + if ( trainTrace.flFraction != 1.0 && trainTrace.pHit ) + pTrain = CBaseEntity::Instance( trainTrace.pHit ); + + + if ( !pTrain || !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) || !pTrain->OnControls(pev) ) + { + //ALERT( at_error, "In train mode with no train!\n" ); + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + } + else if ( !FBitSet( pev->flags, FL_ONGROUND ) || FBitSet( pTrain->pev->spawnflags, SF_TRACKTRAIN_NOCONTROL ) || (pev->button & (IN_MOVELEFT|IN_MOVERIGHT) ) ) + { + // Turn off the train if you jump, strafe, or the train controls go dead + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + + pev->velocity = g_vecZero; + vel = 0; + if ( m_afButtonPressed & IN_FORWARD ) + { + vel = 1; + pTrain->Use( this, this, USE_SET, (float)vel ); + } + else if ( m_afButtonPressed & IN_BACK ) + { + vel = -1; + pTrain->Use( this, this, USE_SET, (float)vel ); + } + + if (vel) + { + m_iTrain = TrainSpeed(pTrain->pev->speed, pTrain->pev->impulse); + m_iTrain |= TRAIN_ACTIVE|TRAIN_NEW; + } + + } else if (m_iTrain & TRAIN_ACTIVE) + m_iTrain = TRAIN_NEW; // turn off train + + // DISCWAR: No Jumping or Ducking + /* + if (pev->button & IN_JUMP) + { + // If on a ladder, jump off the ladder + // else Jump + Jump(); + } + // If trying to duck, already ducked, or in the process of ducking + if ((pev->button & IN_DUCK) || FBitSet(pev->flags,FL_DUCKING) || (m_afPhysicsFlags & PFLAG_DUCKING) ) + Duck(); + */ + + // DISCWAR: Impart the velocity from a disc hit onto the player + if ( m_vecHitVelocity != g_vecZero ) + { + pev->velocity = m_vecHitVelocity; + m_vecHitVelocity = g_vecZero; + } + + // play a footstep if it's time - this will eventually be frame-based. not time based. + + UpdateStepSound(); + + if ( !FBitSet ( pev->flags, FL_ONGROUND ) ) + { + m_flFallVelocity = -pev->velocity.z; + } + + // StudioFrameAdvance( );//!!!HACKHACK!!! Can't be hit by traceline when not animating? + + // Clear out ladder pointer + m_hEnemy = NULL; + + if ( m_afPhysicsFlags & PFLAG_ONBARNACLE ) + { + pev->velocity = g_vecZero; + } +} +/* Time based Damage works as follows: + 1) There are several types of timebased damage: + + #define DMG_PARALYZE (1 << 14) // slows affected creature down + #define DMG_NERVEGAS (1 << 15) // nerve toxins, very bad + #define DMG_POISON (1 << 16) // blood poisioning + #define DMG_RADIATION (1 << 17) // radiation exposure + #define DMG_DROWNRECOVER (1 << 18) // drown recovery + #define DMG_ACID (1 << 19) // toxic chemicals or acid burns + #define DMG_SLOWBURN (1 << 20) // in an oven + #define DMG_SLOWFREEZE (1 << 21) // in a subzero freezer + + 2) A new hit inflicting tbd restarts the tbd counter - each monster has an 8bit counter, + per damage type. The counter is decremented every second, so the maximum time + an effect will last is 255/60 = 4.25 minutes. Of course, staying within the radius + of a damaging effect like fire, nervegas, radiation will continually reset the counter to max. + + 3) Every second that a tbd counter is running, the player takes damage. The damage + is determined by the type of tdb. + Paralyze - 1/2 movement rate, 30 second duration. + Nervegas - 5 points per second, 16 second duration = 80 points max dose. + Poison - 2 points per second, 25 second duration = 50 points max dose. + Radiation - 1 point per second, 50 second duration = 50 points max dose. + Drown - 5 points per second, 2 second duration. + Acid/Chemical - 5 points per second, 10 second duration = 50 points max. + Burn - 10 points per second, 2 second duration. + Freeze - 3 points per second, 10 second duration = 30 points max. + + 4) Certain actions or countermeasures counteract the damaging effects of tbds: + + Armor/Heater/Cooler - Chemical(acid),burn, freeze all do damage to armor power, then to body + - recharged by suit recharger + Air In Lungs - drowning damage is done to air in lungs first, then to body + - recharged by poking head out of water + - 10 seconds if swiming fast + Air In SCUBA - drowning damage is done to air in tanks first, then to body + - 2 minutes in tanks. Need new tank once empty. + Radiation Syringe - Each syringe full provides protection vs one radiation dosage + Antitoxin Syringe - Each syringe full provides protection vs one poisoning (nervegas or poison). + Health kit - Immediate stop to acid/chemical, fire or freeze damage. + Radiation Shower - Immediate stop to radiation damage, acid/chemical or fire damage. + + +*/ + +// If player is taking time based damage, continue doing damage to player - +// this simulates the effect of being poisoned, gassed, dosed with radiation etc - +// anything that continues to do damage even after the initial contact stops. +// Update all time based damage counters, and shut off any that are done. + +// The m_bitsDamageType bit MUST be set if any damage is to be taken. +// This routine will detect the initial on value of the m_bitsDamageType +// and init the appropriate counter. Only processes damage every second. + +//#define PARALYZE_DURATION 30 // number of 2 second intervals to take damage +//#define PARALYZE_DAMAGE 0.0 // damage to take each 2 second interval + +//#define NERVEGAS_DURATION 16 +//#define NERVEGAS_DAMAGE 5.0 + +//#define POISON_DURATION 25 +//#define POISON_DAMAGE 2.0 + +//#define RADIATION_DURATION 50 +//#define RADIATION_DAMAGE 1.0 + +//#define ACID_DURATION 10 +//#define ACID_DAMAGE 5.0 + +//#define SLOWBURN_DURATION 2 +//#define SLOWBURN_DAMAGE 1.0 + +//#define SLOWFREEZE_DURATION 1.0 +//#define SLOWFREEZE_DAMAGE 3.0 + +/* */ + + +void CBasePlayer::CheckTimeBasedDamage() +{ + int i; + BYTE bDuration = 0; + + static float gtbdPrev = 0.0; + + if (!(m_bitsDamageType & DMG_TIMEBASED)) + return; + + // only check for time based damage approx. every 2 seconds + if (abs(gpGlobals->time - m_tbdPrev) < 2.0) + return; + + m_tbdPrev = gpGlobals->time; + + for (i = 0; i < CDMG_TIMEBASED; i++) + { + // make sure bit is set for damage type + if (m_bitsDamageType & (DMG_PARALYZE << i)) + { + switch (i) + { + case itbd_Paralyze: + // UNDONE - flag movement as half-speed + bDuration = PARALYZE_DURATION; + break; + case itbd_NerveGas: +// TakeDamage(pev, pev, NERVEGAS_DAMAGE, DMG_GENERIC); + bDuration = NERVEGAS_DURATION; + break; + case itbd_Poison: + TakeDamage(pev, pev, POISON_DAMAGE, DMG_GENERIC); + bDuration = POISON_DURATION; + break; + case itbd_Radiation: +// TakeDamage(pev, pev, RADIATION_DAMAGE, DMG_GENERIC); + bDuration = RADIATION_DURATION; + break; + case itbd_DrownRecover: + // NOTE: this hack is actually used to RESTORE health + // after the player has been drowning and finally takes a breath + if (m_idrowndmg > m_idrownrestored) + { + int idif = min(m_idrowndmg - m_idrownrestored, 10); + + TakeHealth(idif, DMG_GENERIC); + m_idrownrestored += idif; + } + bDuration = 4; // get up to 5*10 = 50 points back + break; + case itbd_Acid: +// TakeDamage(pev, pev, ACID_DAMAGE, DMG_GENERIC); + bDuration = ACID_DURATION; + break; + case itbd_SlowBurn: +// TakeDamage(pev, pev, SLOWBURN_DAMAGE, DMG_GENERIC); + bDuration = SLOWBURN_DURATION; + break; + case itbd_SlowFreeze: +// TakeDamage(pev, pev, SLOWFREEZE_DAMAGE, DMG_GENERIC); + bDuration = SLOWFREEZE_DURATION; + break; + default: + bDuration = 0; + } + + if (m_rgbTimeBasedDamage[i]) + { + // use up an antitoxin on poison or nervegas after a few seconds of damage + if (((i == itbd_NerveGas) && (m_rgbTimeBasedDamage[i] < NERVEGAS_DURATION)) || + ((i == itbd_Poison) && (m_rgbTimeBasedDamage[i] < POISON_DURATION))) + { + if (m_rgItems[ITEM_ANTIDOTE]) + { + m_rgbTimeBasedDamage[i] = 0; + m_rgItems[ITEM_ANTIDOTE]--; + SetSuitUpdate("!HEV_HEAL4", FALSE, SUIT_REPEAT_OK); + } + } + + + // decrement damage duration, detect when done. + if (!m_rgbTimeBasedDamage[i] || --m_rgbTimeBasedDamage[i] == 0) + { + m_rgbTimeBasedDamage[i] = 0; + // if we're done, clear damage bits + m_bitsDamageType &= ~(DMG_PARALYZE << i); + } + } + else + // first time taking this damage type - init damage duration + m_rgbTimeBasedDamage[i] = bDuration; + } + } +} + +/* +THE POWER SUIT + +The Suit provides 3 main functions: Protection, Notification and Augmentation. +Some functions are automatic, some require power. +The player gets the suit shortly after getting off the train in C1A0 and it stays +with him for the entire game. + +Protection + + Heat/Cold + When the player enters a hot/cold area, the heating/cooling indicator on the suit + will come on and the battery will drain while the player stays in the area. + After the battery is dead, the player starts to take damage. + This feature is built into the suit and is automatically engaged. + Radiation Syringe + This will cause the player to be immune from the effects of radiation for N seconds. Single use item. + Anti-Toxin Syringe + This will cure the player from being poisoned. Single use item. + Health + Small (1st aid kits, food, etc.) + Large (boxes on walls) + Armor + The armor works using energy to create a protective field that deflects a + percentage of damage projectile and explosive attacks. After the armor has been deployed, + it will attempt to recharge itself to full capacity with the energy reserves from the battery. + It takes the armor N seconds to fully charge. + +Notification (via the HUD) + +x Health +x Ammo +x Automatic Health Care + Notifies the player when automatic healing has been engaged. +x Geiger counter + Classic Geiger counter sound and status bar at top of HUD + alerts player to dangerous levels of radiation. This is not visible when radiation levels are normal. +x Poison + Armor + Displays the current level of armor. + +Augmentation + + Reanimation (w/adrenaline) + Causes the player to come back to life after he has been dead for 3 seconds. + Will not work if player was gibbed. Single use. + Long Jump + Used by hitting the ??? key(s). Caused the player to further than normal. + SCUBA + Used automatically after picked up and after player enters the water. + Works for N seconds. Single use. + +Things powered by the battery + + Armor + Uses N watts for every M units of damage. + Heat/Cool + Uses N watts for every second in hot/cold area. + Long Jump + Uses N watts for every jump. + Alien Cloak + Uses N watts for each use. Each use lasts M seconds. + Alien Shield + Augments armor. Reduces Armor drain by one half + +*/ + +// if in range of radiation source, ping geiger counter +#define GEIGERDELAY 0.25 + +void CBasePlayer :: UpdateGeigerCounter( void ) +{ + BYTE range; + + // delay per update ie: don't flood net with these msgs + if (gpGlobals->time < m_flgeigerDelay) + return; + + m_flgeigerDelay = gpGlobals->time + GEIGERDELAY; + + // send range to radition source to client + + range = (BYTE) (m_flgeigerRange / 4); + + if (range != m_igeigerRangePrev) + { + m_igeigerRangePrev = range; + + MESSAGE_BEGIN( MSG_ONE, gmsgGeigerRange, NULL, pev ); + WRITE_BYTE( range ); + MESSAGE_END(); + } + + // reset counter and semaphore + if (!RANDOM_LONG(0,3)) + m_flgeigerRange = 1000; + +} + +/* +================ +CheckSuitUpdate + +Play suit update if it's time +================ +*/ + +#define SUITUPDATETIME 3.5 +#define SUITFIRSTUPDATETIME 0.1 + +void CBasePlayer::CheckSuitUpdate() +{ + int i; + int isentence = 0; + int isearch = m_iSuitPlayNext; + + // Ignore suit updates if no suit + if ( !(pev->weapons & (1<IsMultiplayer() ) + { + // don't bother updating HEV voice in multiplayer. + return; + } + + if ( gpGlobals->time >= m_flSuitUpdate && m_flSuitUpdate > 0) + { + // play a sentence off of the end of the queue + for (i = 0; i < CSUITPLAYLIST; i++) + { + if (isentence = m_rgSuitPlayList[isearch]) + break; + + if (++isearch == CSUITPLAYLIST) + isearch = 0; + } + + if (isentence) + { + m_rgSuitPlayList[isearch] = 0; + if (isentence > 0) + { + // play sentence number + + char sentence[CBSENTENCENAME_MAX+1]; + strcpy(sentence, "!"); + strcat(sentence, gszallsentencenames[isentence]); + EMIT_SOUND_SUIT(ENT(pev), sentence); + } + else + { + // play sentence group + EMIT_GROUPID_SUIT(ENT(pev), -isentence); + } + m_flSuitUpdate = gpGlobals->time + SUITUPDATETIME; + } + else + // queue is empty, don't check + m_flSuitUpdate = 0; + } +} + +// add sentence to suit playlist queue. if fgroup is true, then +// name is a sentence group (HEV_AA), otherwise name is a specific +// sentence name ie: !HEV_AA0. If iNoRepeat is specified in +// seconds, then we won't repeat playback of this word or sentence +// for at least that number of seconds. + +void CBasePlayer::SetSuitUpdate(char *name, int fgroup, int iNoRepeatTime) +{ + int i; + int isentence; + int iempty = -1; + + + // Ignore suit updates if no suit + if ( !(pev->weapons & (1<IsMultiplayer() ) + { + // due to static channel design, etc. We don't play HEV sounds in multiplayer right now. + return; + } + + // if name == NULL, then clear out the queue + + if (!name) + { + for (i = 0; i < CSUITPLAYLIST; i++) + m_rgSuitPlayList[i] = 0; + return; + } + // get sentence or group number + if (!fgroup) + { + isentence = SENTENCEG_Lookup(name, NULL); + if (isentence < 0) + return; + } + else + // mark group number as negative + isentence = -SENTENCEG_GetIndex(name); + + // check norepeat list - this list lets us cancel + // the playback of words or sentences that have already + // been played within a certain time. + + for (i = 0; i < CSUITNOREPEAT; i++) + { + if (isentence == m_rgiSuitNoRepeat[i]) + { + // this sentence or group is already in + // the norepeat list + + if (m_rgflSuitNoRepeatTime[i] < gpGlobals->time) + { + // norepeat time has expired, clear it out + m_rgiSuitNoRepeat[i] = 0; + m_rgflSuitNoRepeatTime[i] = 0.0; + iempty = i; + break; + } + else + { + // don't play, still marked as norepeat + return; + } + } + // keep track of empty slot + if (!m_rgiSuitNoRepeat[i]) + iempty = i; + } + + // sentence is not in norepeat list, save if norepeat time was given + + if (iNoRepeatTime) + { + if (iempty < 0) + iempty = RANDOM_LONG(0, CSUITNOREPEAT-1); // pick random slot to take over + m_rgiSuitNoRepeat[iempty] = isentence; + m_rgflSuitNoRepeatTime[iempty] = iNoRepeatTime + gpGlobals->time; + } + + // find empty spot in queue, or overwrite last spot + + m_rgSuitPlayList[m_iSuitPlayNext++] = isentence; + if (m_iSuitPlayNext == CSUITPLAYLIST) + m_iSuitPlayNext = 0; + + if (m_flSuitUpdate <= gpGlobals->time) + { + if (m_flSuitUpdate == 0) + // play queue is empty, don't delay too long before playback + m_flSuitUpdate = gpGlobals->time + SUITFIRSTUPDATETIME; + else + m_flSuitUpdate = gpGlobals->time + SUITUPDATETIME; + } + +} + +/* +================ +CheckPowerups + +Check for turning off powerups + +GLOBALS ASSUMED SET: g_ulModelIndexPlayer +================ +*/ + static void +CheckPowerups(entvars_t *pev) +{ + if (pev->health <= 0) + return; + + pev->modelindex = g_ulModelIndexPlayer; // don't use eyes +} + + +//========================================================= +// UpdatePlayerSound - updates the position of the player's +// reserved sound slot in the sound list. +//========================================================= +void CBasePlayer :: UpdatePlayerSound ( void ) +{ + int iBodyVolume; + int iVolume; + CSound *pSound; + + pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt :: ClientSoundIndex( edict() ) ); + + if ( !pSound ) + { + ALERT ( at_console, "Client lost reserved sound!\n" ); + return; + } + + pSound->m_iType = bits_SOUND_NONE; + + // now calculate the best target volume for the sound. If the player's weapon + // is louder than his body/movement, use the weapon volume, else, use the body volume. + + if ( FBitSet ( pev->flags, FL_ONGROUND ) ) + { + iBodyVolume = pev->velocity.Length(); + + // clamp the noise that can be made by the body, in case a push trigger, + // weapon recoil, or anything shoves the player abnormally fast. + if ( iBodyVolume > 512 ) + { + iBodyVolume = 512; + } + } + else + { + iBodyVolume = 0; + } + + if ( pev->button & IN_JUMP ) + { + iBodyVolume += 100; + } + +// convert player move speed and actions into sound audible by monsters. + if ( m_iWeaponVolume > iBodyVolume ) + { + m_iTargetVolume = m_iWeaponVolume; + + // OR in the bits for COMBAT sound if the weapon is being louder than the player. + pSound->m_iType |= bits_SOUND_COMBAT; + } + else + { + m_iTargetVolume = iBodyVolume; + } + + // decay weapon volume over time so bits_SOUND_COMBAT stays set for a while + m_iWeaponVolume -= 250 * gpGlobals->frametime; + if ( m_iWeaponVolume < 0 ) + { + iVolume = 0; + } + + + // if target volume is greater than the player sound's current volume, we paste the new volume in + // immediately. If target is less than the current volume, current volume is not set immediately to the + // lower volume, rather works itself towards target volume over time. This gives monsters a much better chance + // to hear a sound, especially if they don't listen every frame. + iVolume = pSound->m_iVolume; + + if ( m_iTargetVolume > iVolume ) + { + iVolume = m_iTargetVolume; + } + else if ( iVolume > m_iTargetVolume ) + { + iVolume -= 250 * gpGlobals->frametime; + + if ( iVolume < m_iTargetVolume ) + { + iVolume = 0; + } + } + + if ( m_fNoPlayerSound ) + { + // debugging flag, lets players move around and shoot without monsters hearing. + iVolume = 0; + } + + if ( gpGlobals->time > m_flStopExtraSoundTime ) + { + // since the extra sound that a weapon emits only lasts for one client frame, we keep that sound around for a server frame or two + // after actual emission to make sure it gets heard. + m_iExtraSoundTypes = 0; + } + + if ( pSound ) + { + pSound->m_vecOrigin = pev->origin; + pSound->m_iType |= ( bits_SOUND_PLAYER | m_iExtraSoundTypes ); + pSound->m_iVolume = iVolume; + } + + // keep track of virtual muzzle flash + m_iWeaponFlash -= 256 * gpGlobals->frametime; + if (m_iWeaponFlash < 0) + m_iWeaponFlash = 0; + + UTIL_MakeVectors ( pev->angles ); + gpGlobals->v_forward.z = 0; + + // Below are a couple of useful little bits that make it easier to determine just how much noise the + // player is making. + // UTIL_ParticleEffect ( pev->origin + gpGlobals->v_forward * iVolume, g_vecZero, 255, 25 ); + //ALERT ( at_console, "%d/%d\n", iVolume, m_iTargetVolume ); +} + + +void CBasePlayer::PostThink() +{ + if ( g_fGameOver ) + goto pt_end; // intermission or finale + + if (!IsAlive()) + goto pt_end; + + // Handle Tank controlling + if ( m_pTank != NULL ) + { // if they've moved too far from the gun, or selected a weapon, unuse the gun + if ( m_pTank->OnControls( pev ) && !pev->weaponmodel ) + { + m_pTank->Use( this, this, USE_SET, 2 ); // try fire the gun + } + else + { // they've moved off the platform + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + } + } + +// do weapon stuff + ItemPostFrame( ); + +// check to see if player landed hard enough to make a sound +// falling farther than half of the maximum safe distance, but not as far a max safe distance will +// play a bootscrape sound, and no damage will be inflicted. Fallling a distance shorter than half +// of maximum safe distance will make no sound. Falling farther than max safe distance will play a +// fallpain sound, and damage will be inflicted based on how far the player fell + + if ( (FBitSet(pev->flags, FL_ONGROUND)) && (pev->health > 0) && m_flFallVelocity >= PLAYER_FALL_PUNCH_THRESHHOLD ) + { + float fvol = 0.5; + + // ALERT ( at_console, "%f\n", m_flFallVelocity ); + + if (pev->watertype == CONTENT_WATER) + { + // Did he hit the world or a non-moving entity? + // BUG - this happens all the time in water, especially when + // BUG - water has current force + // if ( !pev->groundentity || VARS(pev->groundentity)->velocity.z == 0 ) + // EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM); + } + else if ( m_flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED ) + {// after this point, we start doing damage + + // No falling damage in discwar + /* + float flFallDamage = g_pGameRules->FlPlayerFallDamage( this ); + + if ( flFallDamage > pev->health ) + {//splat + // note: play on item channel because we play footstep landing on body channel + EMIT_SOUND(ENT(pev), CHAN_ITEM, "common/bodysplat.wav", 1, ATTN_NORM); + } + + if ( flFallDamage > 0 ) + { + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), flFallDamage, DMG_FALL ); + pev->punchangle.x = 0; + } + */ + + fvol = 1.0; + } + else if ( m_flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED / 2 ) + { + // EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_jumpland2.wav", 1, ATTN_NORM); + fvol = 0.85; + } + else if ( m_flFallVelocity < PLAYER_MIN_BOUNCE_SPEED ) + { + fvol = 0; + } + + if ( fvol > 0.0 ) + { + // get current texture under player right away + m_flTimeStepSound = 0; + UpdateStepSound(); + } + + if ( IsAlive() ) + { + SetAnimation( PLAYER_WALK ); + } + } + + if (FBitSet(pev->flags, FL_ONGROUND)) + { + if (m_flFallVelocity > 64 && !g_pGameRules->IsMultiplayer()) + { + CSoundEnt::InsertSound ( bits_SOUND_PLAYER, pev->origin, m_flFallVelocity, 0.2 ); + // ALERT( at_console, "fall %f\n", m_flFallVelocity ); + } + m_flFallVelocity = 0; + } + + // select the proper animation for the player character + if ( IsAlive() && (m_flTouchedByJumpPad < gpGlobals->time) ) + { + if (!pev->velocity.x && !pev->velocity.y) + SetAnimation( PLAYER_IDLE ); + else if ((pev->velocity.x || pev->velocity.y) && (FBitSet(pev->flags, FL_ONGROUND))) + SetAnimation( PLAYER_WALK ); + else if (pev->waterlevel > 1) + SetAnimation( PLAYER_WALK ); + } + + if ( IsAlive() && ( m_flThrowTime ) && ( m_flThrowTime + 0.25 < gpGlobals->time ) ) + { + m_Activity = ACT_BASE_WALK; + m_flThrowTime = 0.0; + if (!pev->velocity.x && !pev->velocity.y) + { + SetAnimation( PLAYER_IDLE ); + } + else + { + SetAnimation( PLAYER_WALK ); + } + } + + StudioFrameAdvance( ); + CheckPowerups(pev); + + UpdatePlayerSound(); + +pt_end: + + // Store old velocity for use in backpedalling animations + m_vecOldVelocity = pev->velocity.Normalize(); + + // Decay timers on weapons + // go through all of the weapons and make a list of the ones to pack + for ( int i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + CBasePlayerItem *pPlayerItem = m_rgpPlayerItems[ i ]; + + while ( pPlayerItem ) + { + CBasePlayerWeapon *gun; + + gun = (CBasePlayerWeapon *)pPlayerItem->GetWeaponPtr(); + + if ( gun && gun->UseDecrement() ) + { + gun->m_flNextPrimaryAttack = max( gun->m_flNextPrimaryAttack - gpGlobals->frametime, -1.0 ); + gun->m_flNextSecondaryAttack = max( gun->m_flNextSecondaryAttack - gpGlobals->frametime, -0.001 ); + + if ( gun->m_flTimeWeaponIdle != 1000 ) + { + gun->m_flTimeWeaponIdle = max( gun->m_flTimeWeaponIdle - gpGlobals->frametime, -0.001 ); + } + } + + pPlayerItem = pPlayerItem->m_pNext; + } + } + } + + m_flNextAttack -= gpGlobals->frametime; + if ( m_flNextAttack < -0.001 ) + m_flNextAttack = -0.001; + + // Track button info so we can detect 'pressed' and 'released' buttons next frame + m_afButtonLast = pev->button; +} + + +// checks if the spot is clear of players +BOOL IsSpawnPointValid( CBaseEntity *pPlayer, CBaseEntity *pSpot ) +{ + CBaseEntity *ent = NULL; + + if ( !pSpot->IsTriggered( pPlayer ) ) + { + return FALSE; + } + + while ( (ent = UTIL_FindEntityInSphere( ent, pSpot->pev->origin, 96 )) != NULL ) + { + // if ent is a client, don't spawn on 'em + if ( ent->IsPlayer() && ent != pPlayer ) + { + if ( (pPlayer->pev->groupinfo == 0) || (ent->pev->groupinfo & pPlayer->pev->groupinfo) ) + return FALSE; + } + } + + // DISCWAR + // Make sure they're on the right team + if ( InArenaMode() && pSpot->pev->team != 0 && pSpot->pev->team != pPlayer->pev->team ) + return FALSE; + + return TRUE; +} + + +DLL_GLOBAL CBaseEntity *g_pLastSpawn; +inline int FNullEnt( CBaseEntity *ent ) { return (ent == NULL) || FNullEnt( ent->edict() ); } + +/* +============ +EntSelectSpawnPoint + +Returns the entity to spawn at + +USES AND SETS GLOBAL g_pLastSpawn +============ +*/ +edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ) +{ + CBaseEntity *pSpot; + edict_t *player; + + player = pPlayer->edict(); + + EMIT_SOUND( player, CHAN_AUTO, "r_tele1.wav", 1, ATTN_NORM ); + +// choose a info_player_deathmatch point + if (g_pGameRules->IsCoOp()) + { + pSpot = UTIL_FindEntityByClassname( g_pLastSpawn, "info_player_coop"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + pSpot = UTIL_FindEntityByClassname( g_pLastSpawn, "info_player_start"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + else if ( g_pGameRules->IsDeathmatch() ) + { + pSpot = g_pLastSpawn; + // Randomize the start spot + for ( int i = RANDOM_LONG(1,5); i > 0; i-- ) + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + if ( FNullEnt( pSpot ) ) // skip over the null point + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + + CBaseEntity *pFirstSpot = pSpot; + + do + { + if ( pSpot ) + { + // check if pSpot is valid + if ( IsSpawnPointValid( pPlayer, pSpot ) ) + { + if ( pSpot->pev->origin == Vector( 0, 0, 0 ) ) + { + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + continue; + } + + // if so, go to pSpot + goto ReturnSpot; + } + } + // increment pSpot + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + } while ( pSpot != pFirstSpot ); // loop if we're not back to the start + + // we haven't found a place to spawn yet, so kill any guy at the first spawn point and spawn there + if ( !FNullEnt( pSpot ) ) + { + CBaseEntity *ent = NULL; + while ( (ent = UTIL_FindEntityInSphere( ent, pSpot->pev->origin, 128 )) != NULL ) + { + // if ent is a client, kill em (unless they are ourselves) + if ( ent->IsPlayer() && !(ent->edict() == player) && (ent->pev->groupinfo & player->v.groupinfo) && ((CBasePlayer*)pPlayer)->IsObserver() == false) + ent->TakeDamage( VARS(INDEXENT(0)), VARS(INDEXENT(0)), 300, DMG_GENERIC | DMG_ALWAYSGIB ); + } + goto ReturnSpot; + } + } + + // If startspot is set, (re)spawn there. + if ( FStringNull( gpGlobals->startspot ) || !strlen(STRING(gpGlobals->startspot))) + { + pSpot = UTIL_FindEntityByClassname(NULL, "info_player_start"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + else + { + pSpot = UTIL_FindEntityByTargetname( NULL, STRING(gpGlobals->startspot) ); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + +ReturnSpot: + if ( FNullEnt( pSpot ) ) + { + ALERT(at_error, "PutClientInServer: no info_player_start on level"); + return INDEXENT(0); + } + + g_pLastSpawn = pSpot; + return pSpot->edict(); +} + + +void CBasePlayer::Spawn( void ) +{ + pev->classname = MAKE_STRING("player"); + pev->health = 100; + pev->armorvalue = 0; + pev->takedamage = DAMAGE_AIM; + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_WALK; + pev->max_health = pev->health; + pev->flags = FL_CLIENT; + pev->air_finished = gpGlobals->time + 12; + pev->dmg = 2; // initial water damage + pev->effects = 0; + pev->deadflag = DEAD_NO; + pev->dmg_take = 0; + pev->dmg_save = 0; + pev->friction = 1.0; + pev->gravity = 1.0; + m_bitsHUDDamage = -1; + m_bitsDamageType = 0; + m_afPhysicsFlags = 0; + m_fLongJump = FALSE;// no longjump module. + m_flTouchedByJumpPad = 0; + m_flNextAttack = gpGlobals->time + 0.5; // Prevent fire + + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "slj", "0" ); + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "hl", "1" ); + + m_iFOV = 0;// init field of view. + m_iClientFOV = -1; // make sure fov reset is sent + + m_flNextDecalTime = 0;// let this player decal as soon as he spawns. + + m_flgeigerDelay = gpGlobals->time + 2.0; // wait a few seconds until user-defined message registrations + // are recieved by all clients + + m_flTimeStepSound = 0; + m_iStepLeft = 0; + m_flFieldOfView = 0.5;// some monsters use this to determine whether or not the player is looking at them. + + m_bloodColor = BLOOD_COLOR_RED; + m_flNextAttack = UTIL_WeaponTimeBase(); + StartSneaking(); + + m_iFlashBattery = 99; + m_flFlashLightTime = 1; // force first message + +// dont let uninitialized value here hurt the player + m_flFallVelocity = 0; + + g_pGameRules->GetPlayerSpawnSpot( this ); + + SET_MODEL(ENT(pev), "models/player/male/male.mdl"); + g_ulModelIndexPlayer = pev->modelindex; + pev->sequence = LookupActivity( ACT_IDLE ); + + if ( FBitSet(pev->flags, FL_DUCKING) ) + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + else + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + + pev->view_ofs = VEC_VIEW; + Precache(); + m_HackedGunPos = Vector( 0, 32, 0 ); + + if ( m_iPlayerSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_console, "Couldn't alloc player sound slot!\n" ); + } + + m_fNoPlayerSound = FALSE;// normal sound behavior. + + m_pLastItem = NULL; + m_iClientHideHUD = -1; // force this to be recalculated + m_fWeapon = FALSE; + m_pClientActiveItem = NULL; + m_iClientBattery = -1; + + // reset all ammo values to 0 + for ( int i = 0; i < MAX_AMMO_SLOTS; i++ ) + { + m_rgAmmo[i] = 0; + m_rgAmmoLast[i] = 0; // client ammo values also have to be reset (the death hud clear messages does on the client side) + } + + m_lastx = m_lasty = 0; + + g_pGameRules->PlayerSpawn( this ); + + SetBodygroup( 1, 1 ); + ClearFreezeAndRender(); +} + + +void CBasePlayer :: Precache( void ) +{ + // in the event that the player JUST spawned, and the level node graph + // was loaded, fix all of the node graph pointers before the game starts. + + // !!!BUGBUG - now that we have multiplayer, this needs to be moved! + if ( WorldGraph.m_fGraphPresent && !WorldGraph.m_fGraphPointersSet ) + { + if ( !WorldGraph.FSetGraphPointers() ) + { + ALERT ( at_console, "**Graph pointers were not set!\n"); + } + else + { + ALERT ( at_console, "**Graph Pointers Set!\n" ); + } + } + + // SOUNDS / MODELS ARE PRECACHED in ClientPrecache() (game specific) + // because they need to precache before any clients have connected + + // init geiger counter vars during spawn and each time + // we cross a level transition + + m_flgeigerRange = 1000; + m_igeigerRangePrev = 1000; + + m_bitsDamageType = 0; + m_bitsHUDDamage = -1; + + m_iClientBattery = -1; + + m_iTrain = TRAIN_NEW; + + // Make sure any necessary user messages have been registered + LinkUserMessages(); + + m_iUpdateTime = 5; // won't update for 1/2 a second + + if ( gInitHUD ) + m_fInitHUD = TRUE; +} + + +int CBasePlayer::Save( CSave &save ) +{ + if ( !CBaseMonster::Save(save) ) + return 0; + + return save.WriteFields( "PLAYER", this, m_playerSaveData, ARRAYSIZE(m_playerSaveData) ); +} + + +// +// Marks everything as new so the player will resend this to the hud. +// +void CBasePlayer::RenewItems(void) +{ + +} + + +int CBasePlayer::Restore( CRestore &restore ) +{ + if ( !CBaseMonster::Restore(restore) ) + return 0; + + int status = restore.ReadFields( "PLAYER", this, m_playerSaveData, ARRAYSIZE(m_playerSaveData) ); + + SAVERESTOREDATA *pSaveData = (SAVERESTOREDATA *)gpGlobals->pSaveData; + // landmark isn't present. + if ( !pSaveData->fUseLandmark ) + { + ALERT( at_console, "No Landmark:%s\n", pSaveData->szLandmarkName ); + + // default to normal spawn + edict_t* pentSpawnSpot = EntSelectSpawnPoint( this ); + pev->origin = VARS(pentSpawnSpot)->origin + Vector(0,0,1); + pev->angles = VARS(pentSpawnSpot)->angles; + } + pev->v_angle.z = 0; // Clear out roll + pev->angles = pev->v_angle; + + pev->fixangle = TRUE; // turn this way immediately + +// Copied from spawn() for now + m_bloodColor = BLOOD_COLOR_RED; + + g_ulModelIndexPlayer = pev->modelindex; + + if ( FBitSet(pev->flags, FL_DUCKING) ) + { + // Use the crouch HACK + // FixPlayerCrouchStuck( edict() ); + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + } + else + { + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + } + + RenewItems(); + + return status; +} + + + +void CBasePlayer::SelectNextItem( int iItem ) +{ + CBasePlayerItem *pItem; + + pItem = m_rgpPlayerItems[ iItem ]; + + if (!pItem) + return; + + if (pItem == m_pActiveItem) + { + // select the next one in the chain + pItem = m_pActiveItem->m_pNext; + if (! pItem) + { + return; + } + + CBasePlayerItem *pLast; + pLast = pItem; + while (pLast->m_pNext) + pLast = pLast->m_pNext; + + // relink chain + pLast->m_pNext = m_pActiveItem; + m_pActiveItem->m_pNext = NULL; + m_rgpPlayerItems[ iItem ] = pItem; + } + + ResetAutoaim( ); + + // FIX, this needs to queue them up and delay + if (m_pActiveItem) + { + m_pActiveItem->Holster( ); + } + + m_pActiveItem = pItem; + + if (m_pActiveItem) + { + m_pActiveItem->Deploy( ); + m_pActiveItem->UpdateItemInfo( ); + } +} + +void CBasePlayer::SelectItem(const char *pstr) +{ + if (!pstr) + return; + + CBasePlayerItem *pItem = NULL; + + for (int i = 0; i < MAX_ITEM_TYPES; i++) + { + if (m_rgpPlayerItems[i]) + { + pItem = m_rgpPlayerItems[i]; + + while (pItem) + { + if (FClassnameIs(pItem->pev, pstr)) + break; + pItem = pItem->m_pNext; + } + } + + if (pItem) + break; + } + + if (!pItem) + return; + + + if (pItem == m_pActiveItem) + return; + + ResetAutoaim( ); + + // FIX, this needs to queue them up and delay + if (m_pActiveItem) + m_pActiveItem->Holster( ); + + m_pLastItem = m_pActiveItem; + m_pActiveItem = pItem; + + if (m_pActiveItem) + { + m_pActiveItem->Deploy( ); + m_pActiveItem->UpdateItemInfo( ); + } +} + + +void CBasePlayer::SelectLastItem(void) +{ + if (!m_pLastItem) + { + return; + } + + if ( m_pActiveItem && !m_pActiveItem->CanHolster() ) + { + return; + } + + ResetAutoaim( ); + + // FIX, this needs to queue them up and delay + if (m_pActiveItem) + m_pActiveItem->Holster( ); + + CBasePlayerItem *pTemp = m_pActiveItem; + m_pActiveItem = m_pLastItem; + m_pLastItem = pTemp; + m_pActiveItem->Deploy( ); + m_pActiveItem->UpdateItemInfo( ); +} + +//============================================== +// HasWeapons - do I have any weapons at all? +//============================================== +BOOL CBasePlayer::HasWeapons( void ) +{ + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + return TRUE; + } + } + + return FALSE; +} + +void CBasePlayer::SelectPrevItem( int iItem ) +{ +} + + +const char *CBasePlayer::TeamID( void ) +{ + if ( pev == NULL ) // Not fully connected yet + return ""; + + // return their team name + return m_szTeamName; +} + + +//============================================== +// !!!UNDONE:ultra temporary SprayCan entity to apply +// decal frame at a time. For PreAlpha CD +//============================================== +class CSprayCan : public CBaseEntity +{ +public: + void Spawn ( entvars_t *pevOwner ); + void Think( void ); + + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +void CSprayCan::Spawn ( entvars_t *pevOwner ) +{ + pev->origin = pevOwner->origin + Vector ( 0 , 0 , 32 ); + pev->angles = pevOwner->v_angle; + pev->owner = ENT(pevOwner); + pev->frame = 0; + + pev->nextthink = gpGlobals->time + 0.1; + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/sprayer.wav", 1, ATTN_NORM); +} + +void CSprayCan::Think( void ) +{ + TraceResult tr; + int playernum; + int nFrames; + CBasePlayer *pPlayer; + + pPlayer = (CBasePlayer *)GET_PRIVATE(pev->owner); + + if (pPlayer) + nFrames = pPlayer->GetCustomDecalFrames(); + else + nFrames = -1; + + playernum = ENTINDEX(pev->owner); + + // ALERT(at_console, "Spray by player %i, %i of %i\n", playernum, (int)(pev->frame + 1), nFrames); + + UTIL_MakeVectors(pev->angles); + UTIL_TraceLine ( pev->origin, pev->origin + gpGlobals->v_forward * 128, ignore_monsters, pev->owner, & tr); + + // No customization present. + if (nFrames == -1) + { + UTIL_DecalTrace( &tr, DECAL_LAMBDA6 ); + UTIL_Remove( this ); + } + else + { + UTIL_PlayerDecalTrace( &tr, playernum, pev->frame, TRUE ); + // Just painted last custom frame. + if ( pev->frame++ >= (nFrames - 1)) + UTIL_Remove( this ); + } + + pev->nextthink = gpGlobals->time + 0.1; +} + +class CBloodSplat : public CBaseEntity +{ +public: + void Spawn ( entvars_t *pevOwner ); + void Spray ( void ); +}; + +void CBloodSplat::Spawn ( entvars_t *pevOwner ) +{ + pev->origin = pevOwner->origin + Vector ( 0 , 0 , 32 ); + pev->angles = pevOwner->v_angle; + pev->owner = ENT(pevOwner); + + SetThink ( &CBloodSplat::Spray ); + pev->nextthink = gpGlobals->time + 0.1; +} + +void CBloodSplat::Spray ( void ) +{ + TraceResult tr; + + if ( g_Language != LANGUAGE_GERMAN ) + { + UTIL_MakeVectors(pev->angles); + UTIL_TraceLine ( pev->origin, pev->origin + gpGlobals->v_forward * 128, ignore_monsters, pev->owner, & tr); + + UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_RED ); + } + SetThink ( &CBloodSplat::SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; +} + +//============================================== + + + +void CBasePlayer::GiveNamedItem( const char *pszName ) +{ + edict_t *pent; + + int istr = MAKE_STRING(pszName); + + pent = CREATE_NAMED_ENTITY(istr); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in GiveNamedItem!\n" ); + return; + } + VARS( pent )->origin = pev->origin; + pent->v.spawnflags |= SF_NORESPAWN; + + DispatchSpawn( pent ); + DispatchTouch( pent, ENT( pev ) ); +} + + + +CBaseEntity *FindEntityForward( CBaseEntity *pMe ) +{ + TraceResult tr; + + UTIL_MakeVectors(pMe->pev->v_angle); + UTIL_TraceLine(pMe->pev->origin + pMe->pev->view_ofs,pMe->pev->origin + pMe->pev->view_ofs + gpGlobals->v_forward * 8192,dont_ignore_monsters, pMe->edict(), &tr ); + if ( tr.flFraction != 1.0 && !FNullEnt( tr.pHit) ) + { + CBaseEntity *pHit = CBaseEntity::Instance( tr.pHit ); + return pHit; + } + return NULL; +} + + +BOOL CBasePlayer :: FlashlightIsOn( void ) +{ + return FBitSet(pev->effects, EF_DIMLIGHT); +} + + +void CBasePlayer :: FlashlightTurnOn( void ) +{ + if ( !g_pGameRules->FAllowFlashlight() ) + { + return; + } + + if ( (pev->weapons & (1<effects, EF_DIMLIGHT); + MESSAGE_BEGIN( MSG_ONE, gmsgFlashlight, NULL, pev ); + WRITE_BYTE(1); + WRITE_BYTE(m_iFlashBattery); + MESSAGE_END(); + + m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->time; + + } +} + + +void CBasePlayer :: FlashlightTurnOff( void ) +{ + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, SOUND_FLASHLIGHT_OFF, 1.0, ATTN_NORM, 0, PITCH_NORM ); + ClearBits(pev->effects, EF_DIMLIGHT); + MESSAGE_BEGIN( MSG_ONE, gmsgFlashlight, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(m_iFlashBattery); + MESSAGE_END(); + + m_flFlashLightTime = FLASH_CHARGE_TIME + gpGlobals->time; + +} + +/* +=============== +ForceClientDllUpdate + +When recording a demo, we need to have the server tell us the entire client state +so that the client side .dll can behave correctly. +Reset stuff so that the state is transmitted. +=============== +*/ +void CBasePlayer :: ForceClientDllUpdate( void ) +{ + m_iClientHealth = -1; + m_iClientBattery = -1; + m_iTrain |= TRAIN_NEW; // Force new train message. + m_fWeapon = FALSE; // Force weapon send + m_fKnownItem = FALSE; // Force weaponinit messages. + + // Now force all the necessary messages + // to be sent. + UpdateClientData(); +} + +/* +============ +ImpulseCommands +============ +*/ +extern float g_flWeaponCheat; + +void CBasePlayer::ImpulseCommands( ) +{ + TraceResult tr;// UNDONE: kill me! This is temporary for PreAlpha CDs + + // Handle use events + PlayerUse(); + + int iImpulse = (int)pev->impulse; + switch (iImpulse) + { + case 99: + { + + int iOn; + + if (!gmsgLogo) + { + iOn = 1; + gmsgLogo = REG_USER_MSG("Logo", 1); + } + else + { + iOn = 0; + } + + ASSERT( gmsgLogo > 0 ); + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgLogo, NULL, pev ); + WRITE_BYTE(iOn); + MESSAGE_END(); + + if(!iOn) + gmsgLogo = 0; + break; + } + case 100: + // temporary flashlight for level designers + if ( FlashlightIsOn() ) + { + FlashlightTurnOff(); + } + else + { + FlashlightTurnOn(); + } + break; + + case 201:// paint decal + + if ( gpGlobals->time < m_flNextDecalTime ) + { + // too early! + break; + } + + UTIL_MakeVectors(pev->v_angle); + UTIL_TraceLine ( pev->origin + pev->view_ofs, pev->origin + pev->view_ofs + gpGlobals->v_forward * 128, ignore_monsters, ENT(pev), & tr); + + if ( tr.flFraction != 1.0 ) + {// line hit something, so paint a decal + m_flNextDecalTime = gpGlobals->time + CVAR_GET_FLOAT("decalfrequency"); + CSprayCan *pCan = GetClassPtr((CSprayCan *)NULL); + pCan->Spawn( pev ); + } + + break; + case 204: // Demo recording, update client dll specific data again. + ForceClientDllUpdate(); + break; + default: + // check all of the cheat impulse commands now + CheatImpulseCommands( iImpulse ); + break; + } + + pev->impulse = 0; +} + +//========================================================= +//========================================================= +void CBasePlayer::CheatImpulseCommands( int iImpulse ) +{ +#if !defined( HLDEMO_BUILD ) + if ( g_flWeaponCheat == 0.0 ) + { + return; + } + + CBaseEntity *pEntity; + TraceResult tr; + + switch ( iImpulse ) + { + case 76: + { + if (!giPrecacheGrunt) + { + giPrecacheGrunt = 1; + ALERT(at_console, "You must now restart to use Grunt-o-matic.\n"); + } + else + { + UTIL_MakeVectors( Vector( 0, pev->v_angle.y, 0 ) ); + Create("monster_human_grunt", pev->origin + gpGlobals->v_forward * 128, pev->angles); + } + break; + } + + + case 101: + gEvilImpulse101 = TRUE; + GiveNamedItem( "item_suit" ); + GiveNamedItem( "item_battery" ); + GiveNamedItem( "weapon_crowbar" ); + GiveNamedItem( "weapon_9mmhandgun" ); + GiveNamedItem( "ammo_9mmclip" ); + GiveNamedItem( "weapon_shotgun" ); + GiveNamedItem( "ammo_buckshot" ); + GiveNamedItem( "weapon_9mmAR" ); + GiveNamedItem( "ammo_9mmAR" ); + GiveNamedItem( "ammo_ARgrenades" ); + GiveNamedItem( "weapon_handgrenade" ); + GiveNamedItem( "weapon_tripmine" ); +#ifndef OEM_BUILD + GiveNamedItem( "weapon_357" ); + GiveNamedItem( "ammo_357" ); + GiveNamedItem( "weapon_crossbow" ); + GiveNamedItem( "ammo_crossbow" ); + GiveNamedItem( "weapon_egon" ); + GiveNamedItem( "weapon_gauss" ); + GiveNamedItem( "ammo_gaussclip" ); + GiveNamedItem( "weapon_rpg" ); + GiveNamedItem( "ammo_rpgclip" ); + GiveNamedItem( "weapon_satchel" ); + GiveNamedItem( "weapon_snark" ); + GiveNamedItem( "weapon_hornetgun" ); +#endif + gEvilImpulse101 = FALSE; + break; + + case 102: + // Gibbage!!! + CGib::SpawnRandomGibs( pev, 1, 1 ); + break; + + case 103: + // What the hell are you doing? + pEntity = FindEntityForward( this ); + if ( pEntity ) + { + CBaseMonster *pMonster = pEntity->MyMonsterPointer(); + if ( pMonster ) + pMonster->ReportAIState(); + } + break; + + case 104: + // Dump all of the global state varaibles (and global entity names) + gGlobalState.DumpGlobals(); + break; + + case 105:// player makes no sound for monsters to hear. + { + if ( m_fNoPlayerSound ) + { + ALERT ( at_console, "Player is audible\n" ); + m_fNoPlayerSound = FALSE; + } + else + { + ALERT ( at_console, "Player is silent\n" ); + m_fNoPlayerSound = TRUE; + } + break; + } + + case 106: + // Give me the classname and targetname of this entity. + pEntity = FindEntityForward( this ); + if ( pEntity ) + { + ALERT ( at_console, "Classname: %s", STRING( pEntity->pev->classname ) ); + + if ( !FStringNull ( pEntity->pev->targetname ) ) + { + ALERT ( at_console, " - Targetname: %s\n", STRING( pEntity->pev->targetname ) ); + } + else + { + ALERT ( at_console, " - TargetName: No Targetname\n" ); + } + + ALERT ( at_console, "Model: %s\n", STRING( pEntity->pev->model ) ); + if ( pEntity->pev->globalname ) + ALERT ( at_console, "Globalname: %s\n", STRING( pEntity->pev->globalname ) ); + } + break; + + case 107: + { + TraceResult tr; + + edict_t *pWorld = g_engfuncs.pfnPEntityOfEntIndex( 0 ); + + Vector start = pev->origin + pev->view_ofs; + Vector end = start + gpGlobals->v_forward * 1024; + UTIL_TraceLine( start, end, ignore_monsters, edict(), &tr ); + if ( tr.pHit ) + pWorld = tr.pHit; + const char *pTextureName = TRACE_TEXTURE( pWorld, start, end ); + if ( pTextureName ) + ALERT( at_console, "Texture: %s\n", pTextureName ); + } + break; + case 195:// show shortest paths for entire level to nearest node + { + Create("node_viewer_fly", pev->origin, pev->angles); + } + break; + case 196:// show shortest paths for entire level to nearest node + { + Create("node_viewer_large", pev->origin, pev->angles); + } + break; + case 197:// show shortest paths for entire level to nearest node + { + Create("node_viewer_human", pev->origin, pev->angles); + } + break; + case 199:// show nearest node and all connections + { + ALERT ( at_console, "%d\n", WorldGraph.FindNearestNode ( pev->origin, bits_NODE_GROUP_REALM ) ); + WorldGraph.ShowNodeConnections ( WorldGraph.FindNearestNode ( pev->origin, bits_NODE_GROUP_REALM ) ); + } + break; + case 202:// Random blood splatter + UTIL_MakeVectors(pev->v_angle); + UTIL_TraceLine ( pev->origin + pev->view_ofs, pev->origin + pev->view_ofs + gpGlobals->v_forward * 128, ignore_monsters, ENT(pev), & tr); + + if ( tr.flFraction != 1.0 ) + {// line hit something, so paint a decal + CBloodSplat *pBlood = GetClassPtr((CBloodSplat *)NULL); + pBlood->Spawn( pev ); + } + break; + case 203:// remove creature. + pEntity = FindEntityForward( this ); + if ( pEntity ) + { + if ( pEntity->pev->takedamage ) + pEntity->SetThink(&CBaseEntity::SUB_Remove); + } + break; + } +#endif // HLDEMO_BUILD +} + +// +// Add a weapon to the player (Item == Weapon == Selectable Object) +// +int CBasePlayer::AddPlayerItem( CBasePlayerItem *pItem ) +{ + CBasePlayerItem *pInsert; + + pInsert = m_rgpPlayerItems[pItem->iItemSlot()]; + + while (pInsert) + { + if (FClassnameIs( pInsert->pev, STRING( pItem->pev->classname) )) + { + if (pItem->AddDuplicate( pInsert )) + { + g_pGameRules->PlayerGotWeapon ( this, pItem ); + pItem->CheckRespawn(); + + // ugly hack to update clip w/o an update clip message + pInsert->UpdateItemInfo( ); + if (m_pActiveItem) + m_pActiveItem->UpdateItemInfo( ); + + pItem->Kill( ); + } + else if (gEvilImpulse101) + { + // FIXME: remove anyway for deathmatch testing + pItem->Kill( ); + } + return FALSE; + } + pInsert = pInsert->m_pNext; + } + + + if (pItem->AddToPlayer( this )) + { + g_pGameRules->PlayerGotWeapon ( this, pItem ); + pItem->CheckRespawn(); + + pItem->m_pNext = m_rgpPlayerItems[pItem->iItemSlot()]; + m_rgpPlayerItems[pItem->iItemSlot()] = pItem; + + // should we switch to this item? + if ( g_pGameRules->FShouldSwitchWeapon( this, pItem ) ) + { + SwitchWeapon( pItem ); + } + + return TRUE; + } + else if (gEvilImpulse101) + { + // FIXME: remove anyway for deathmatch testing + pItem->Kill( ); + } + return FALSE; +} + + + +int CBasePlayer::RemovePlayerItem( CBasePlayerItem *pItem ) +{ + if (m_pActiveItem == pItem) + { + ResetAutoaim( ); + pItem->Holster( ); + pItem->pev->nextthink = 0;// crowbar may be trying to swing again, etc. + pItem->SetThink( NULL ); + m_pActiveItem = NULL; + pev->viewmodel = 0; + pev->weaponmodel = 0; + } + else if ( m_pLastItem == pItem ) + m_pLastItem = NULL; + + CBasePlayerItem *pPrev = m_rgpPlayerItems[pItem->iItemSlot()]; + + if (pPrev == pItem) + { + m_rgpPlayerItems[pItem->iItemSlot()] = pItem->m_pNext; + return TRUE; + } + else + { + while (pPrev && pPrev->m_pNext != pItem) + { + pPrev = pPrev->m_pNext; + } + if (pPrev) + { + pPrev->m_pNext = pItem->m_pNext; + return TRUE; + } + } + return FALSE; +} + + +// +// Returns the unique ID for the ammo, or -1 if error +// +int CBasePlayer :: GiveAmmo( int iCount, char *szName, int iMax ) +{ + if ( !szName ) + { + // no ammo. + return -1; + } + + if ( !g_pGameRules->CanHaveAmmo( this, szName, iMax ) ) + { + // game rules say I can't have any more of this ammo type. + return -1; + } + + int i = 0; + + i = GetAmmoIndex( szName ); + + if ( i < 0 || i >= MAX_AMMO_SLOTS ) + return -1; + + int iAdd = min( iCount, iMax - m_rgAmmo[i] ); + if ( iAdd < 1 ) + return i; + + m_rgAmmo[ i ] += iAdd; + + + if ( gmsgAmmoPickup ) // make sure the ammo messages have been linked first + { + // Send the message that ammo has been picked up + MESSAGE_BEGIN( MSG_ONE, gmsgAmmoPickup, NULL, pev ); + WRITE_BYTE( GetAmmoIndex(szName) ); // ammo ID + WRITE_BYTE( iAdd ); // amount + MESSAGE_END(); + } + + return i; +} + + +/* +============ +ItemPreFrame + +Called every frame by the player PreThink +============ +*/ +void CBasePlayer::ItemPreFrame() +{ + if ( m_flNextAttack > 0 ) + { + return; + } + + if (!m_pActiveItem) + return; + + m_pActiveItem->ItemPreFrame( ); +} + + +/* +============ +ItemPostFrame + +Called every frame by the player PostThink +============ +*/ +void CBasePlayer::ItemPostFrame() +{ + static int fInSelect = FALSE; + + // check if the player is using a tank + if ( m_pTank != NULL ) + return; + + if ( m_flNextAttack > 0 ) + { + return; + } + + ImpulseCommands(); + + if (!m_pActiveItem) + return; + + m_pActiveItem->ItemPostFrame( ); +} + +int CBasePlayer::AmmoInventory( int iAmmoIndex ) +{ + if (iAmmoIndex == -1) + { + return -1; + } + + return m_rgAmmo[ iAmmoIndex ]; +} + +int CBasePlayer::GetAmmoIndex(const char *psz) +{ + int i; + + if (!psz) + return -1; + + for (i = 1; i < MAX_AMMO_SLOTS; i++) + { + if ( !CBasePlayerItem::AmmoInfoArray[i].pszName ) + continue; + + if (stricmp( psz, CBasePlayerItem::AmmoInfoArray[i].pszName ) == 0) + return i; + } + + return -1; +} + +// Called from UpdateClientData +// makes sure the client has all the necessary ammo info, if values have changed +void CBasePlayer::SendAmmoUpdate(void) +{ + for (int i=0; i < MAX_AMMO_SLOTS;i++) + { + if (m_rgAmmo[i] != m_rgAmmoLast[i]) + { + m_rgAmmoLast[i] = m_rgAmmo[i]; + + ASSERT( m_rgAmmo[i] >= 0 ); + ASSERT( m_rgAmmo[i] < 255 ); + + // send "Ammo" update message + MESSAGE_BEGIN( MSG_ONE, gmsgAmmoX, NULL, pev ); + WRITE_BYTE( i ); + WRITE_BYTE( max( min( m_rgAmmo[i], 254 ), 0 ) ); // clamp the value to one byte + MESSAGE_END(); + } + } +} + +/* +========================================================= + UpdateClientData + +resends any changed player HUD info to the client. +Called every frame by PlayerPreThink +Also called at start of demo recording and playback by +ForceClientDllUpdate to ensure the demo gets messages +reflecting all of the HUD state info. +========================================================= +*/ +void CBasePlayer :: UpdateClientData( void ) +{ + if (m_fInitHUD) + { + m_fInitHUD = FALSE; + gInitHUD = FALSE; + m_flSendArenaStatus = gpGlobals->time = 1.0f; + + MESSAGE_BEGIN( MSG_ONE, gmsgResetHUD, NULL, pev ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + + if ( !m_fGameHUDInitialized ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgInitHUD, NULL, pev ); + MESSAGE_END(); + + g_pGameRules->InitHUD( this ); + m_fGameHUDInitialized = TRUE; + if ( g_pGameRules->IsMultiplayer() ) + { + FireTargets( "game_playerjoin", this, this, USE_TOGGLE, 0 ); + } + } + + FireTargets( "game_playerspawn", this, this, USE_TOGGLE, 0 ); + } + + if ( m_iHideHUD != m_iClientHideHUD ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgHideWeapon, NULL, pev ); + WRITE_BYTE( m_iHideHUD ); + MESSAGE_END(); + + m_iClientHideHUD = m_iHideHUD; + } + + if ( m_iFOV != m_iClientFOV ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgSetFOV, NULL, pev ); + WRITE_BYTE( m_iFOV ); + MESSAGE_END(); + + // cache FOV change at end of function, so weapon updates can see that FOV has changed + } + + // HACKHACK -- send the message to display the game title + if (gDisplayTitle) + { + MESSAGE_BEGIN( MSG_ONE, gmsgShowGameTitle, NULL, pev ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + gDisplayTitle = 0; + } + + if (pev->health != m_iClientHealth) + { +#define clamp( val, min, max ) ( ((val) > (max)) ? (max) : ( ((val) < (min)) ? (min) : (val) ) ) + int iHealth = clamp( pev->health, 0, 255 ); // make sure that no negative health values are sent + if ( pev->health > 0.0f && pev->health <= 1.0f ) + iHealth = 1; + + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgHealth, NULL, pev ); + WRITE_BYTE( iHealth ); + MESSAGE_END(); + + m_iClientHealth = pev->health; + } + + + if (pev->armorvalue != m_iClientBattery) + { + m_iClientBattery = pev->armorvalue; + + ASSERT( gmsgBattery > 0 ); + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgBattery, NULL, pev ); + WRITE_SHORT( (int)pev->armorvalue); + MESSAGE_END(); + } + + if (pev->dmg_take || pev->dmg_save || m_bitsHUDDamage != m_bitsDamageType) + { + // Comes from inside me if not set + Vector damageOrigin = pev->origin; + // send "damage" message + // causes screen to flash, and pain compass to show direction of damage + edict_t *other = pev->dmg_inflictor; + if ( other ) + { + CBaseEntity *pEntity = CBaseEntity::Instance(other); + if ( pEntity ) + damageOrigin = pEntity->Center(); + } + + // only send down damage type that have hud art + int visibleDamageBits = m_bitsDamageType & DMG_SHOWNHUD; + + MESSAGE_BEGIN( MSG_ONE, gmsgDamage, NULL, pev ); + WRITE_BYTE( pev->dmg_save ); + WRITE_BYTE( pev->dmg_take ); + WRITE_LONG( visibleDamageBits ); + WRITE_COORD( damageOrigin.x ); + WRITE_COORD( damageOrigin.y ); + WRITE_COORD( damageOrigin.z ); + MESSAGE_END(); + + pev->dmg_take = 0; + pev->dmg_save = 0; + m_bitsHUDDamage = m_bitsDamageType; + + // Clear off non-time-based damage indicators + m_bitsDamageType &= DMG_TIMEBASED; + } + + // Update Flashlight + if ((m_flFlashLightTime) && (m_flFlashLightTime <= gpGlobals->time)) + { + if (FlashlightIsOn()) + { + if (m_iFlashBattery) + { + m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->time; + m_iFlashBattery--; + + if (!m_iFlashBattery) + FlashlightTurnOff(); + } + } + else + { + if (m_iFlashBattery < 100) + { + m_flFlashLightTime = FLASH_CHARGE_TIME + gpGlobals->time; + m_iFlashBattery++; + } + else + m_flFlashLightTime = 0; + } + + MESSAGE_BEGIN( MSG_ONE, gmsgFlashBattery, NULL, pev ); + WRITE_BYTE(m_iFlashBattery); + MESSAGE_END(); + } + + + if (m_iTrain & TRAIN_NEW) + { + ASSERT( gmsgTrain > 0 ); + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgTrain, NULL, pev ); + WRITE_BYTE(m_iTrain & 0xF); + MESSAGE_END(); + + m_iTrain &= ~TRAIN_NEW; + } + + // + // New Weapon? + // + if (!m_fKnownItem) + { + m_fKnownItem = TRUE; + + // WeaponInit Message + // byte = # of weapons + // + // for each weapon: + // byte name str length (not including null) + // bytes... name + // byte Ammo Type + // byte Ammo2 Type + // byte bucket + // byte bucket pos + // byte flags + // ???? Icons + + // Send ALL the weapon info now + int i; + + for (i = 0; i < MAX_WEAPONS; i++) + { + ItemInfo& II = CBasePlayerItem::ItemInfoArray[i]; + + if ( !II.iId ) + continue; + + const char *pszName; + if (!II.pszName) + pszName = "Empty"; + else + pszName = II.pszName; + + MESSAGE_BEGIN( MSG_ONE, gmsgWeaponList, NULL, pev ); + WRITE_STRING(pszName); // string weapon name + WRITE_BYTE(GetAmmoIndex(II.pszAmmo1)); // byte Ammo Type + WRITE_BYTE(II.iMaxAmmo1); // byte Max Ammo 1 + WRITE_BYTE(GetAmmoIndex(II.pszAmmo2)); // byte Ammo2 Type + WRITE_BYTE(II.iMaxAmmo2); // byte Max Ammo 2 + WRITE_BYTE(II.iSlot); // byte bucket + WRITE_BYTE(II.iPosition); // byte bucket pos + WRITE_BYTE(II.iId); // byte id (bit index into pev->weapons) + WRITE_BYTE(II.iFlags); // byte Flags + MESSAGE_END(); + } + } + + + SendAmmoUpdate(); + + // Update all the items + for ( int i = 0; i < MAX_ITEM_TYPES; i++ ) + { + if ( m_rgpPlayerItems[i] ) // each item updates it's successors + m_rgpPlayerItems[i]->UpdateClientData( this ); + } + + // Cache and client weapon change + m_pClientActiveItem = m_pActiveItem; + m_iClientFOV = m_iFOV; + + if ( (m_iClientFrags != pev->frags) || (m_iClientDeaths != m_iDeaths) || (m_iClientPlayerClass != pev->playerclass) || (m_iClientTeam != pev->team) ) + { + // update all players with this info, for their scoreboards + MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo ); + WRITE_BYTE( ENTINDEX(edict()) ); + WRITE_SHORT( pev->frags ); + WRITE_SHORT( m_iDeaths ); + WRITE_SHORT( pev->playerclass ); + WRITE_SHORT( pev->team ); + MESSAGE_END(); + + m_iClientPlayerClass = pev->playerclass; + m_iClientTeam = pev->team; + m_iClientDeaths = m_iDeaths; + m_iClientFrags = pev->frags; + } + + // Update Freeze + if ( m_iFrozen != m_iClientFrozen ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgFrozen, NULL, pev ); + WRITE_BYTE( m_iFrozen ); + MESSAGE_END(); + + m_iClientFrozen = m_iFrozen; + } +} + + +//========================================================= +// FBecomeProne - Overridden for the player to set the proper +// physics flags when a barnacle grabs player. +//========================================================= +BOOL CBasePlayer :: FBecomeProne ( void ) +{ + m_afPhysicsFlags |= PFLAG_ONBARNACLE; + return TRUE; +} + +//========================================================= +// BarnacleVictimBitten - bad name for a function that is called +// by Barnacle victims when the barnacle pulls their head +// into its mouth. For the player, just die. +//========================================================= +void CBasePlayer :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) +{ + TakeDamage ( pevBarnacle, pevBarnacle, pev->health + pev->armorvalue, DMG_SLASH | DMG_ALWAYSGIB ); +} + +//========================================================= +// BarnacleVictimReleased - overridden for player who has +// physics flags concerns. +//========================================================= +void CBasePlayer :: BarnacleVictimReleased ( void ) +{ + m_afPhysicsFlags &= ~PFLAG_ONBARNACLE; +} + + +//========================================================= +// Illumination +// return player light level plus virtual muzzle flash +//========================================================= +int CBasePlayer :: Illumination( void ) +{ + int iIllum = CBaseEntity::Illumination( ); + + iIllum += m_iWeaponFlash; + if (iIllum > 255) + return 255; + return iIllum; +} + + +void CBasePlayer :: EnableControl(BOOL fControl) +{ + if (!fControl) + pev->flags |= FL_FROZEN; + else + pev->flags &= ~FL_FROZEN; + +} + + +#define DOT_1DEGREE 0.9998476951564 +#define DOT_2DEGREE 0.9993908270191 +#define DOT_3DEGREE 0.9986295347546 +#define DOT_4DEGREE 0.9975640502598 +#define DOT_5DEGREE 0.9961946980917 +#define DOT_6DEGREE 0.9945218953683 +#define DOT_7DEGREE 0.9925461516413 +#define DOT_8DEGREE 0.9902680687416 +#define DOT_9DEGREE 0.9876883405951 +#define DOT_10DEGREE 0.9848077530122 +#define DOT_15DEGREE 0.9659258262891 +#define DOT_20DEGREE 0.9396926207859 +#define DOT_25DEGREE 0.9063077870367 + +//========================================================= +// Autoaim +// set crosshair position to point to enemey +//========================================================= +Vector CBasePlayer :: GetAutoaimVector( float flDelta ) +{ + if (g_iSkillLevel == SKILL_HARD) + { + UTIL_MakeVectors( pev->v_angle + pev->punchangle ); + return gpGlobals->v_forward; + } + + Vector vecSrc = GetGunPosition( ); + float flDist = 8192; + + // always use non-sticky autoaim + // UNDONE: use sever variable to chose! + if (1 || g_iSkillLevel == SKILL_MEDIUM) + { + m_vecAutoAim = Vector( 0, 0, 0 ); + // flDelta *= 0.5; + } + + BOOL m_fOldTargeting = m_fOnTarget; + Vector angles = AutoaimDeflection(vecSrc, flDist, flDelta ); + + // update ontarget if changed + if ( !g_pGameRules->AllowAutoTargetCrosshair() ) + m_fOnTarget = 0; + else if (m_fOldTargeting != m_fOnTarget) + { + m_pActiveItem->UpdateItemInfo( ); + } + + if (angles.x > 180) + angles.x -= 360; + if (angles.x < -180) + angles.x += 360; + if (angles.y > 180) + angles.y -= 360; + if (angles.y < -180) + angles.y += 360; + + if (angles.x > 25) + angles.x = 25; + if (angles.x < -25) + angles.x = -25; + if (angles.y > 12) + angles.y = 12; + if (angles.y < -12) + angles.y = -12; + + + // always use non-sticky autoaim + // UNDONE: use sever variable to chose! + if (0 || g_iSkillLevel == SKILL_EASY) + { + m_vecAutoAim = m_vecAutoAim * 0.67 + angles * 0.33; + } + else + { + m_vecAutoAim = angles * 0.9; + } + + // m_vecAutoAim = m_vecAutoAim * 0.99; + + // Don't send across network if sv_aim is 0 + if ( CVAR_GET_FLOAT( "sv_aim" ) != 0 ) + { + if ( m_vecAutoAim.x != m_lastx || + m_vecAutoAim.y != m_lasty ) + { + SET_CROSSHAIRANGLE( edict(), -m_vecAutoAim.x, m_vecAutoAim.y ); + + m_lastx = m_vecAutoAim.x; + m_lasty = m_vecAutoAim.y; + } + } + + // ALERT( at_console, "%f %f\n", angles.x, angles.y ); + + UTIL_MakeVectors( pev->v_angle + pev->punchangle + m_vecAutoAim ); + return gpGlobals->v_forward; +} + + +Vector CBasePlayer :: AutoaimDeflection( Vector &vecSrc, float flDist, float flDelta ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + float bestdot; + Vector bestdir; + edict_t *bestent; + TraceResult tr; + + if ( CVAR_GET_FLOAT("sv_aim") == 0 ) + { + m_fOnTarget = FALSE; + return g_vecZero; + } + + UTIL_MakeVectors( pev->v_angle + pev->punchangle + m_vecAutoAim ); + + // try all possible entities + bestdir = gpGlobals->v_forward; + bestdot = flDelta; // +- 10 degrees + bestent = NULL; + + m_fOnTarget = FALSE; + + UTIL_TraceLine( vecSrc, vecSrc + bestdir * flDist, dont_ignore_monsters, edict(), &tr ); + + + if ( tr.pHit && tr.pHit->v.takedamage != DAMAGE_NO) + { + // don't look through water + if (!((pev->waterlevel != 3 && tr.pHit->v.waterlevel == 3) + || (pev->waterlevel == 3 && tr.pHit->v.waterlevel == 0))) + { + if (tr.pHit->v.takedamage == DAMAGE_AIM) + m_fOnTarget = TRUE; + + return m_vecAutoAim; + } + } + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + Vector center; + Vector dir; + float dot; + + if ( pEdict->free ) // Not in use + continue; + + if (pEdict->v.takedamage != DAMAGE_AIM) + continue; + if (pEdict == edict()) + continue; +// if (pev->team > 0 && pEdict->v.team == pev->team) +// continue; // don't aim at teammate + if ( !g_pGameRules->ShouldAutoAim( this, pEdict ) ) + continue; + + pEntity = Instance( pEdict ); + if (pEntity == NULL) + continue; + + if (!pEntity->IsAlive()) + continue; + + // don't look through water + if ((pev->waterlevel != 3 && pEntity->pev->waterlevel == 3) + || (pev->waterlevel == 3 && pEntity->pev->waterlevel == 0)) + continue; + + center = pEntity->BodyTarget( vecSrc ); + + dir = (center - vecSrc).Normalize( ); + + // make sure it's in front of the player + if (DotProduct (dir, gpGlobals->v_forward ) < 0) + continue; + + dot = fabs( DotProduct (dir, gpGlobals->v_right ) ) + + fabs( DotProduct (dir, gpGlobals->v_up ) ) * 0.5; + + // tweek for distance + dot *= 1.0 + 0.2 * ((center - vecSrc).Length() / flDist); + + if (dot > bestdot) + continue; // to far to turn + + UTIL_TraceLine( vecSrc, center, dont_ignore_monsters, edict(), &tr ); + if (tr.flFraction != 1.0 && tr.pHit != pEdict) + { + // ALERT( at_console, "hit %s, can't see %s\n", STRING( tr.pHit->v.classname ), STRING( pEdict->v.classname ) ); + continue; + } + + // don't shoot at friends + if (IRelationship( pEntity ) < 0) + { + if ( !pEntity->IsPlayer() && !g_pGameRules->IsDeathmatch()) + // ALERT( at_console, "friend\n"); + continue; + } + + // can shoot at this one + bestdot = dot; + bestent = pEdict; + bestdir = dir; + } + + if (bestent) + { + bestdir = UTIL_VecToAngles (bestdir); + bestdir.x = -bestdir.x; + bestdir = bestdir - pev->v_angle - pev->punchangle; + + if (bestent->v.takedamage == DAMAGE_AIM) + m_fOnTarget = TRUE; + + return bestdir; + } + + return Vector( 0, 0, 0 ); +} + + +void CBasePlayer :: ResetAutoaim( ) +{ + if (m_vecAutoAim.x != 0 || m_vecAutoAim.y != 0) + { + m_vecAutoAim = Vector( 0, 0, 0 ); + SET_CROSSHAIRANGLE( edict(), 0, 0 ); + } + m_fOnTarget = FALSE; +} + +/* +============= +SetCustomDecalFrames + + UNDONE: Determine real frame limit, 8 is a placeholder. + Note: -1 means no custom frames present. +============= +*/ +void CBasePlayer :: SetCustomDecalFrames( int nFrames ) +{ + if (nFrames > 0 && + nFrames < 8) + m_nCustomSprayFrames = nFrames; + else + m_nCustomSprayFrames = -1; +} + +/* +============= +GetCustomDecalFrames + + Returns the # of custom frames this player's custom clan logo contains. +============= +*/ +int CBasePlayer :: GetCustomDecalFrames( void ) +{ + return m_nCustomSprayFrames; +} + + +//========================================================= +// DropPlayerItem - drop the named item, or if no name, +// the active item. +//========================================================= +void CBasePlayer::DropPlayerItem ( char *pszItemName ) +{ + // no dropping in disc war + return; +} + +//========================================================= +// HasPlayerItem Does the player already have this item? +//========================================================= +BOOL CBasePlayer::HasPlayerItem( CBasePlayerItem *pCheckItem ) +{ + CBasePlayerItem *pItem = m_rgpPlayerItems[pCheckItem->iItemSlot()]; + + while (pItem) + { + if (FClassnameIs( pItem->pev, STRING( pCheckItem->pev->classname) )) + { + return TRUE; + } + pItem = pItem->m_pNext; + } + + return FALSE; +} + +//========================================================= +// HasNamedPlayerItem Does the player already have this item? +//========================================================= +BOOL CBasePlayer::HasNamedPlayerItem( const char *pszItemName ) +{ + CBasePlayerItem *pItem; + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pItem = m_rgpPlayerItems[ i ]; + + while (pItem) + { + if ( !strcmp( pszItemName, STRING( pItem->pev->classname ) ) ) + { + return TRUE; + } + pItem = pItem->m_pNext; + } + } + + return FALSE; +} + +//========================================================= +// +//========================================================= +BOOL CBasePlayer :: SwitchWeapon( CBasePlayerItem *pWeapon ) +{ + if ( !pWeapon->CanDeploy() ) + { + return FALSE; + } + + ResetAutoaim( ); + + if (m_pActiveItem) + { + m_pActiveItem->Holster( ); + } + + m_pActiveItem = pWeapon; + pWeapon->Deploy( ); + + return TRUE; +} + +//========================================================= +// Dead HEV suit prop +//========================================================= +class CDeadHEV : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_HUMAN_MILITARY; } + + void KeyValue( KeyValueData *pkvd ); + + int m_iPose;// which sequence to display -- temporary, don't need to save + static char *m_szPoses[4]; +}; + +char *CDeadHEV::m_szPoses[] = { "deadback", "deadsitting", "deadstomach", "deadtable" }; + +void CDeadHEV::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "pose")) + { + m_iPose = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +LINK_ENTITY_TO_CLASS( monster_hevsuit_dead, CDeadHEV ); + +//========================================================= +// ********** DeadHEV SPAWN ********** +//========================================================= +void CDeadHEV :: Spawn( void ) +{ + PRECACHE_MODEL("models/player.mdl"); + SET_MODEL(ENT(pev), "models/player.mdl"); + + pev->effects = 0; + pev->yaw_speed = 8; + pev->sequence = 0; + pev->body = 1; + m_bloodColor = BLOOD_COLOR_RED; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + + if (pev->sequence == -1) + { + ALERT ( at_console, "Dead hevsuit with bad pose\n" ); + pev->sequence = 0; + pev->effects = EF_BRIGHTFIELD; + } + + // Corpses have less health + pev->health = 8; + + MonsterInitDead(); +} + + +class CStripWeapons : public CPointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +private: +}; + +LINK_ENTITY_TO_CLASS( player_weaponstrip, CStripWeapons ); + +void CStripWeapons :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBasePlayer *pPlayer = NULL; + + if ( pActivator && pActivator->IsPlayer() ) + { + pPlayer = (CBasePlayer *)pActivator; + } + else if ( !g_pGameRules->IsDeathmatch() ) + { + pPlayer = (CBasePlayer *)CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) ); + } + + if ( pPlayer ) + pPlayer->RemoveAllItems( FALSE ); +} + + +class CRevertSaved : public CPointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT MessageThink( void ); + void EXPORT LoadThink( void ); + void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + inline float Duration( void ) { return pev->dmg_take; } + inline float HoldTime( void ) { return pev->dmg_save; } + inline float MessageTime( void ) { return m_messageTime; } + inline float LoadTime( void ) { return m_loadTime; } + + inline void SetDuration( float duration ) { pev->dmg_take = duration; } + inline void SetHoldTime( float hold ) { pev->dmg_save = hold; } + inline void SetMessageTime( float time ) { m_messageTime = time; } + inline void SetLoadTime( float time ) { m_loadTime = time; } + +private: + float m_messageTime; + float m_loadTime; +}; + +LINK_ENTITY_TO_CLASS( player_loadsaved, CRevertSaved ); + +TYPEDESCRIPTION CRevertSaved::m_SaveData[] = +{ + DEFINE_FIELD( CRevertSaved, m_messageTime, FIELD_FLOAT ), // These are not actual times, but durations, so save as floats + DEFINE_FIELD( CRevertSaved, m_loadTime, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CRevertSaved, CPointEntity ); + +void CRevertSaved :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "duration")) + { + SetDuration( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "holdtime")) + { + SetHoldTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "messagetime")) + { + SetMessageTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "loadtime")) + { + SetLoadTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CRevertSaved :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + UTIL_ScreenFadeAll( pev->rendercolor, Duration(), HoldTime(), pev->renderamt, FFADE_OUT ); + pev->nextthink = gpGlobals->time + MessageTime(); + SetThink( &CRevertSaved::MessageThink ); +} + + +void CRevertSaved :: MessageThink( void ) +{ + UTIL_ShowMessageAll( STRING(pev->message) ); + float nextThink = LoadTime() - MessageTime(); + if ( nextThink > 0 ) + { + pev->nextthink = gpGlobals->time + nextThink; + SetThink( &CRevertSaved::LoadThink ); + } + else + LoadThink(); +} + + +void CRevertSaved :: LoadThink( void ) +{ + if ( !gpGlobals->deathmatch ) + { + SERVER_COMMAND("reload\n"); + } +} + + +//========================================================= +// Multiplayer intermission spots. +//========================================================= +class CInfoIntermission:public CPointEntity +{ + void Spawn( void ); + void Think( void ); +}; + +void CInfoIntermission::Spawn( void ) +{ + UTIL_SetOrigin( pev, pev->origin ); + pev->solid = SOLID_NOT; + pev->effects = EF_NODRAW; + pev->v_angle = g_vecZero; + + pev->nextthink = gpGlobals->time + 2;// let targets spawn! + +} + +void CInfoIntermission::Think ( void ) +{ + edict_t *pTarget; + + // find my target + pTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) ); + + if ( !FNullEnt(pTarget) ) + { + pev->v_angle = UTIL_VecToAngles( (pTarget->v.origin - pev->origin).Normalize() ); + pev->v_angle.x = -pev->v_angle.x; + } +} + +LINK_ENTITY_TO_CLASS( info_intermission, CInfoIntermission ); + + +//========================================================= +// Freeze +//========================================================= +void CBasePlayer::Freeze( void ) +{ + // Glow blue + pev->renderfx = kRenderFxGlowShell; + pev->rendercolor.z = 200; + pev->renderamt = 25; + + pev->maxspeed = FREEZE_SPEED; + m_iFrozen = 1; + + m_flFreezeTime = gpGlobals->time + FREEZE_TIME; +} + +void CBasePlayer::ClearFreezeAndRender() +{ + pev->renderfx = kRenderFxNone; + pev->rendercolor = g_vecZero; + pev->renderamt = 0; + pev->nextthink = 0; + + m_iFrozen = 0; + + if ( IsObserver() ) + pev->maxspeed = 1; + else + pev->maxspeed = 320; +} + +// Force a client to hear a specific VOX sentence. +// This should eventually be moved into the engine. +void CBasePlayer::ClientHearVox( const char *pSentence ) +{ + MESSAGE_BEGIN( MSG_ONE, SVC_STUFFTEXT, NULL, pev ); + + // If it's a sentence, don't put quotes around it + if (pSentence[0] == '#') + { + WRITE_STRING( UTIL_VarArgs("spk %s\n", pSentence ) ); + } + else + { + WRITE_STRING( UTIL_VarArgs("spk \"%s\"\n", pSentence ) ); + } + + MESSAGE_END(); +} diff --git a/ricochet/dlls/player.h b/ricochet/dlls/player.h new file mode 100644 index 0000000..4490c8a --- /dev/null +++ b/ricochet/dlls/player.h @@ -0,0 +1,356 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PLAYER_H +#define PLAYER_H + +#define PLAYER_FATAL_FALL_SPEED 1024// approx 60 feet +#define PLAYER_MAX_SAFE_FALL_SPEED 580// approx 20 feet +#define DAMAGE_FOR_FALL_SPEED (float) 100 / ( PLAYER_FATAL_FALL_SPEED - PLAYER_MAX_SAFE_FALL_SPEED )// damage per unit per second. +#define PLAYER_MIN_BOUNCE_SPEED 200 +#define PLAYER_FALL_PUNCH_THRESHHOLD (float)350 // won't punch player's screen/make scrape noise unless player falling at least this fast. + +// +// Player PHYSICS FLAGS bits +// +#define PFLAG_ONLADDER ( 1<<0 ) +#define PFLAG_ONSWING ( 1<<0 ) +#define PFLAG_ONTRAIN ( 1<<1 ) +#define PFLAG_ONBARNACLE ( 1<<2 ) +#define PFLAG_DUCKING ( 1<<3 ) // In the process of ducking, but totally squatted yet +#define PFLAG_USING ( 1<<4 ) // Using a continuous entity +#define PFLAG_OBSERVER ( 1<<5 ) // player is locked in stationary cam mode. Spectators can move, observers can't. + +// +// generic player +// +//----------------------------------------------------- +//This is Half-Life player entity +//----------------------------------------------------- +#define CSUITPLAYLIST 4 // max of 4 suit sentences queued up at any time + +#define SUIT_GROUP TRUE +#define SUIT_SENTENCE FALSE + +#define SUIT_REPEAT_OK 0 +#define SUIT_NEXT_IN_30SEC 30 +#define SUIT_NEXT_IN_1MIN 60 +#define SUIT_NEXT_IN_5MIN 300 +#define SUIT_NEXT_IN_10MIN 600 +#define SUIT_NEXT_IN_30MIN 1800 +#define SUIT_NEXT_IN_1HOUR 3600 + +#define CSUITNOREPEAT 32 + +#define SOUND_FLASHLIGHT_ON "items/flashlight1.wav" +#define SOUND_FLASHLIGHT_OFF "items/flashlight1.wav" + +#define TEAM_NAME_LENGTH 16 + +typedef enum +{ + PLAYER_IDLE, + PLAYER_WALK, + PLAYER_JUMP, + PLAYER_SUPERJUMP, + PLAYER_DIE, + PLAYER_ATTACK1, + PLAYER_FALL, +} PLAYER_ANIM; + +class CDiscArena; + +class CBasePlayer : public CBaseMonster +{ +public: + int random_seed; // See that is shared between client & server for shared weapons code + + int m_iPlayerSound;// the index of the sound list slot reserved for this player + int m_iTargetVolume;// ideal sound volume. + int m_iWeaponVolume;// how loud the player's weapon is right now. + int m_iExtraSoundTypes;// additional classification for this weapon's sound + int m_iWeaponFlash;// brightness of the weapon flash + float m_flStopExtraSoundTime; + + float m_flFlashLightTime; // Time until next battery draw/Recharge + int m_iFlashBattery; // Flashlight Battery Draw + + int m_afButtonLast; + int m_afButtonPressed; + int m_afButtonReleased; + + edict_t *m_pentSndLast; // last sound entity to modify player room type + float m_flSndRoomtype; // last roomtype set by sound entity + float m_flSndRange; // dist from player to sound entity + + float m_flFallVelocity; + + int m_rgItems[MAX_ITEMS]; + int m_fKnownItem; // True when a new item needs to be added + int m_fNewAmmo; // True when a new item has been added + + unsigned int m_afPhysicsFlags; // physics flags - set when 'normal' physics should be revisited or overriden + float m_fNextSuicideTime; // the time after which the player can next use the suicide command + + +// these are time-sensitive things that we keep track of + float m_flTimeStepSound; // when the last stepping sound was made + float m_flTimeWeaponIdle; // when to play another weapon idle animation. + float m_flSwimTime; // how long player has been underwater + float m_flDuckTime; // how long we've been ducking + float m_flWallJumpTime; // how long until next walljump + + float m_flSuitUpdate; // when to play next suit update + int m_rgSuitPlayList[CSUITPLAYLIST];// next sentencenum to play for suit update + int m_iSuitPlayNext; // next sentence slot for queue storage; + int m_rgiSuitNoRepeat[CSUITNOREPEAT]; // suit sentence no repeat list + float m_rgflSuitNoRepeatTime[CSUITNOREPEAT]; // how long to wait before allowing repeat + int m_lastDamageAmount; // Last damage taken + float m_tbdPrev; // Time-based damage timer + + float m_flgeigerRange; // range to nearest radiation source + float m_flgeigerDelay; // delay per update of range msg to client + int m_igeigerRangePrev; + int m_iStepLeft; // alternate left/right foot stepping sound + char m_szTextureName[CBTEXTURENAMEMAX]; // current texture name we're standing on + char m_chTextureType; // current texture type + + int m_idrowndmg; // track drowning damage taken + int m_idrownrestored; // track drowning damage restored + + int m_bitsHUDDamage; // Damage bits for the current fame. These get sent to + // the hude via the DAMAGE message + BOOL m_fInitHUD; // True when deferred HUD restart msg needs to be sent + BOOL m_fGameHUDInitialized; + int m_iTrain; // Train control position + BOOL m_fWeapon; // Set this to FALSE to force a reset of the current weapon HUD info + + EHANDLE m_pTank; // the tank which the player is currently controlling, NULL if no tank + float m_fDeadTime; // the time at which the player died (used in PlayerDeathThink()) + + BOOL m_fNoPlayerSound; // a debugging feature. Player makes no sound if this is true. + BOOL m_fLongJump; // does this player have the longjump module? + + float m_tSneaking; + int m_iUpdateTime; // stores the number of frame ticks before sending HUD update messages + int m_iClientHealth; // the health currently known by the client. If this changes, send a new + int m_iClientBattery; // the Battery currently known by the client. If this changes, send a new + int m_iHideHUD; // the players hud weapon info is to be hidden + int m_iClientHideHUD; + int m_iFOV; // field of view + int m_iClientFOV; // client's known FOV + // usable player items + CBasePlayerItem *m_rgpPlayerItems[MAX_ITEM_TYPES]; + CBasePlayerItem *m_pActiveItem; + CBasePlayerItem *m_pClientActiveItem; // client version of the active item + CBasePlayerItem *m_pLastItem; + // shared ammo slots + int m_rgAmmo[MAX_AMMO_SLOTS]; + int m_rgAmmoLast[MAX_AMMO_SLOTS]; + + Vector m_vecAutoAim; + BOOL m_fOnTarget; + int m_iDeaths; + float m_iRespawnFrames; // used in PlayerDeathThink() to make sure players can always respawn + + int m_lastx, m_lasty; // These are the previous update's crosshair angles, DON"T SAVE/RESTORE + + int m_nCustomSprayFrames;// Custom clan logo frames for this player + float m_flNextDecalTime;// next time this player can spray a decal + + char m_szTeamName[TEAM_NAME_LENGTH]; + + virtual void Spawn( void ); + void Pain( void ); + +// virtual void Think( void ); + virtual void Jump( void ); + virtual void Duck( void ); + virtual void PreThink( void ); + virtual void PostThink( void ); + virtual Vector GetGunPosition( void ); + virtual int TakeHealth( float flHealth, int bitsDamageType ); + virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + virtual void Killed( entvars_t *pevAttacker, int iGib ); + virtual Vector BodyTarget( const Vector &posSrc ) { return Center( ) + pev->view_ofs * RANDOM_FLOAT( 0.5, 1.1 ); }; // position to shoot at + virtual void StartSneaking( void ) { m_tSneaking = gpGlobals->time - 1; } + virtual void StopSneaking( void ) { m_tSneaking = gpGlobals->time + 30; } + virtual BOOL IsSneaking( void ) { return m_tSneaking <= gpGlobals->time; } + virtual BOOL IsAlive( void ) { return (pev->deadflag == DEAD_NO) && pev->health > 0; } + virtual BOOL ShouldFadeOnDeath( void ) { return FALSE; } + virtual BOOL IsPlayer( void ) { return TRUE; } // Spectators should return FALSE for this, they aren't "players" as far as game logic is concerned + + virtual BOOL IsNetClient( void ) { return TRUE; } // Bots should return FALSE for this, they can't receive NET messages + // Spectators should return TRUE for this + virtual const char *TeamID( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + void RenewItems(void); + void RemoveAllItems( BOOL removeSuit ); + BOOL SwitchWeapon( CBasePlayerItem *pWeapon ); + + // JOHN: sends custom messages if player HUD data has changed (eg health, ammo) + virtual void UpdateClientData( void ); + + static TYPEDESCRIPTION m_playerSaveData[]; + + // Player is moved across the transition by other means + virtual int ObjectCaps( void ) { return CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual void Precache( void ); + BOOL IsOnLadder( void ); + BOOL FlashlightIsOn( void ); + void FlashlightTurnOn( void ); + void FlashlightTurnOff( void ); + + void UpdatePlayerSound ( void ); + void DeathSound ( void ); + + int Classify ( void ); + void SetAnimation( PLAYER_ANIM playerAnim ); + void SetWeaponAnimType( const char *szExtention ); + char m_szAnimExtention[32]; + + // custom player functions + virtual void ImpulseCommands( void ); + void CheatImpulseCommands( int iImpulse ); + + void StartDeathCam( void ); + void StartObserver( Vector vecPosition, Vector vecViewAngle ); + void StopObserver( void ); + void Observer_FindNextPlayer( bool bReverse ); + void Observer_HandleButtons(); + void Observer_SetMode( int iMode ); + EHANDLE m_hObserverTarget; + float m_flNextObserverInput; + int IsObserver() { return pev->iuser1; }; + + void AddPoints( int score, BOOL bAllowNegativeScore ); + void AddPointsToTeam( int score, BOOL bAllowNegativeScore ); + BOOL AddPlayerItem( CBasePlayerItem *pItem ); + BOOL RemovePlayerItem( CBasePlayerItem *pItem ); + void DropPlayerItem ( char *pszItemName ); + BOOL HasPlayerItem( CBasePlayerItem *pCheckItem ); + BOOL HasNamedPlayerItem( const char *pszItemName ); + BOOL HasWeapons( void );// do I have ANY weapons? + void SelectPrevItem( int iItem ); + void SelectNextItem( int iItem ); + void SelectLastItem(void); + void SelectItem(const char *pstr); + void ItemPreFrame( void ); + void ItemPostFrame( void ); + void GiveNamedItem( const char *szName ); + void EnableControl(BOOL fControl); + + int GiveAmmo( int iAmount, char *szName, int iMax ); + void SendAmmoUpdate(void); + + void WaterMove( void ); + void EXPORT PlayerDeathThink( void ); + void PlayerUse( void ); + + void CheckSuitUpdate(); + void SetSuitUpdate(char *name, int fgroup, int iNoRepeat); + void UpdateGeigerCounter( void ); + void CheckTimeBasedDamage( void ); + void UpdateStepSound( void ); + void PlayStepSound(int step, float fvol); + + BOOL FBecomeProne ( void ); + void BarnacleVictimBitten ( entvars_t *pevBarnacle ); + void BarnacleVictimReleased ( void ); + static int GetAmmoIndex(const char *psz); + int AmmoInventory( int iAmmoIndex ); + int Illumination( void ); + + void ResetAutoaim( void ); + Vector GetAutoaimVector( float flDelta ); + Vector AutoaimDeflection( Vector &vecSrc, float flDist, float flDelta ); + + void ForceClientDllUpdate( void ); // Forces all client .dll specific data to be resent to client. + + void DeathMessage( entvars_t *pevKiller ); + + void SetCustomDecalFrames( int nFrames ); + int GetCustomDecalFrames( void ); + + // Discwar + void GivePowerup( int iPowerupType ); + void RemovePowerup( int iPowerupType ); + void RemoveAllPowerups( void ); + bool HasPowerup( int iPowerupType ); + void ClearFreezeAndRender( void ); + int m_iPowerups; + int m_iPowerupDiscs; + + void Freeze( void ); + int m_iFrozen; + int m_iClientFrozen; + float m_flFreezeTime; + EHANDLE m_hLastPlayerToHitMe; + float m_flLastDiscHit; + int m_iLastDiscBounces; + float m_flLastDiscHitTeleport; + + Vector m_vecOldVelocity; + + int m_iClientDeaths, m_iClientFrags, m_iClientPlayerClass, m_iClientTeam; + + // Discwar Arena Handling + EHANDLE m_pNextPlayer; + CDiscArena *m_pCurrentArena; + int m_iArenaCombatantNumber; + int m_iLastGameResult; + + // Discwar animation + int GetThrowAnim( void ); + int GetHoldAnim( void ); + int GetFallAnimation( void ); + void Decapitate( entvars_t *pevKiller ); + void Shatter( entvars_t *pevKiller ); + + float m_flThrowTime; + float m_flBackupTime; + float m_flTransitionTime; + Vector m_vecHitVelocity; + BOOL m_bHasDisconnected; + float m_flKnownItemTime; + + void ObserverInput_ChangeMode(); + void ObserverInput_PrevPlayer(); + void ObserverInput_NextPlayer(); + + void ClientHearVox( const char *pSentence ); + + float m_flSendArenaStatus; //Sigh. + float m_flChangeAngles; //Double sigh. +}; + +#define AUTOAIM_2DEGREES 0.0348994967025 +#define AUTOAIM_5DEGREES 0.08715574274766 +#define AUTOAIM_8DEGREES 0.1391731009601 +#define AUTOAIM_10DEGREES 0.1736481776669 + + +extern int gmsgHudText; +extern BOOL gInitHUD; + +// Observer Movement modes (stored in pev->iuser1, so the physics code can get at them) +#define OBS_CHASE_LOCKED 1 +#define OBS_CHASE_FREE 2 +#define OBS_ROAMING 3 +#define OBS_LOCKEDVIEW 4 + +#endif // PLAYER_H diff --git a/ricochet/dlls/saverestore.h b/ricochet/dlls/saverestore.h new file mode 100644 index 0000000..4065b04 --- /dev/null +++ b/ricochet/dlls/saverestore.h @@ -0,0 +1,170 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Implementation in UTIL.CPP +#ifndef SAVERESTORE_H +#define SAVERESTORE_H + +class CBaseEntity; + +class CSaveRestoreBuffer +{ +public: + CSaveRestoreBuffer( void ); + CSaveRestoreBuffer( SAVERESTOREDATA *pdata ); + ~CSaveRestoreBuffer( void ); + + int EntityIndex( entvars_t *pevLookup ); + int EntityIndex( edict_t *pentLookup ); + int EntityIndex( EOFFSET eoLookup ); + int EntityIndex( CBaseEntity *pEntity ); + + int EntityFlags( int entityIndex, int flags ) { return EntityFlagsSet( entityIndex, 0 ); } + int EntityFlagsSet( int entityIndex, int flags ); + + edict_t *EntityFromIndex( int entityIndex ); + + unsigned short TokenHash( const char *pszToken ); + +protected: + SAVERESTOREDATA *m_pdata; + void BufferRewind( int size ); + unsigned int HashString( const char *pszToken ); +}; + + +class CSave : public CSaveRestoreBuffer +{ +public: + CSave( SAVERESTOREDATA *pdata ) : CSaveRestoreBuffer( pdata ) {}; + + void WriteShort( const char *pname, const short *value, int count ); + void WriteInt( const char *pname, const int *value, int count ); // Save an int + void WriteFloat( const char *pname, const float *value, int count ); // Save a float + void WriteTime( const char *pname, const float *value, int count ); // Save a float (timevalue) + void WriteData( const char *pname, int size, const char *pdata ); // Save a binary data block + void WriteString( const char *pname, const char *pstring ); // Save a null-terminated string + void WriteString( const char *pname, const int *stringId, int count ); // Save a null-terminated string (engine string) + void WriteVector( const char *pname, const Vector &value ); // Save a vector + void WriteVector( const char *pname, const float *value, int count ); // Save a vector + void WritePositionVector( const char *pname, const Vector &value ); // Offset for landmark if necessary + void WritePositionVector( const char *pname, const float *value, int count ); // array of pos vectors + void WriteFunction( const char *pname, void **value, int count ); // Save a function pointer + int WriteEntVars( const char *pname, entvars_t *pev ); // Save entvars_t (entvars_t) + int WriteFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); + +private: + int DataEmpty( const char *pdata, int size ); + void BufferField( const char *pname, int size, const char *pdata ); + void BufferString( char *pdata, int len ); + void BufferData( const char *pdata, int size ); + void BufferHeader( const char *pname, int size ); +}; + +typedef struct +{ + unsigned short size; + unsigned short token; + char *pData; +} HEADER; + +class CRestore : public CSaveRestoreBuffer +{ +public: + CRestore( SAVERESTOREDATA *pdata ) : CSaveRestoreBuffer( pdata ) { m_global = 0; m_precache = TRUE; } + + int ReadEntVars( const char *pname, entvars_t *pev ); // entvars_t + int ReadFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); + int ReadField( void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount, int startField, int size, char *pName, void *pData ); + int ReadInt( void ); + short ReadShort( void ); + int ReadNamedInt( const char *pName ); + char *ReadNamedString( const char *pName ); + int Empty( void ) { return (m_pdata == NULL) || ((m_pdata->pCurrentData-m_pdata->pBaseData)>=m_pdata->bufferSize); } + inline void SetGlobalMode( int global ) { m_global = global; } + void PrecacheMode( BOOL mode ) { m_precache = mode; } + +private: + char *BufferPointer( void ); + void BufferReadBytes( char *pOutput, int size ); + void BufferSkipBytes( int bytes ); + int BufferSkipZString( void ); + int BufferCheckZString( const char *string ); + + void BufferReadHeader( HEADER *pheader ); + + int m_global; // Restoring a global entity? + BOOL m_precache; +}; + +#define MAX_ENTITYARRAY 64 + +//#define ARRAYSIZE(p) (sizeof(p)/sizeof(p[0])) + +#define IMPLEMENT_SAVERESTORE(derivedClass,baseClass) \ + int derivedClass::Save( CSave &save )\ + {\ + if ( !baseClass::Save(save) )\ + return 0;\ + return save.WriteFields( #derivedClass, this, m_SaveData, ARRAYSIZE(m_SaveData) );\ + }\ + int derivedClass::Restore( CRestore &restore )\ + {\ + if ( !baseClass::Restore(restore) )\ + return 0;\ + return restore.ReadFields( #derivedClass, this, m_SaveData, ARRAYSIZE(m_SaveData) );\ + } + + +typedef enum { GLOBAL_OFF = 0, GLOBAL_ON = 1, GLOBAL_DEAD = 2 } GLOBALESTATE; + +typedef struct globalentity_s globalentity_t; + +struct globalentity_s +{ + char name[64]; + char levelName[32]; + GLOBALESTATE state; + globalentity_t *pNext; +}; + +class CGlobalState +{ +public: + CGlobalState(); + void Reset( void ); + void ClearStates( void ); + void EntityAdd( string_t globalname, string_t mapName, GLOBALESTATE state ); + void EntitySetState( string_t globalname, GLOBALESTATE state ); + void EntityUpdate( string_t globalname, string_t mapname ); + const globalentity_t *EntityFromTable( string_t globalname ); + GLOBALESTATE EntityGetState( string_t globalname ); + int EntityInTable( string_t globalname ) { return (Find( globalname ) != NULL) ? 1 : 0; } + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +//#ifdef _DEBUG + void DumpGlobals( void ); +//#endif + +private: + globalentity_t *Find( string_t globalname ); + globalentity_t *m_pList; + int m_listCount; +}; + +extern CGlobalState gGlobalState; + +#endif //SAVERESTORE_H diff --git a/ricochet/dlls/schedule.h b/ricochet/dlls/schedule.h new file mode 100644 index 0000000..f8ee15a --- /dev/null +++ b/ricochet/dlls/schedule.h @@ -0,0 +1,31 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// Scheduling +//========================================================= +#ifndef SCHEDULE_H +#define SCHEDULE_H + +#define bits_COND_SEE_HATE ( 1 << 1 ) // see something that you hate +#define bits_COND_SEE_FEAR ( 1 << 2 ) // see something that you are afraid of +#define bits_COND_SEE_DISLIKE ( 1 << 3 ) // see something that you dislike +#define bits_COND_SEE_ENEMY ( 1 << 4 ) // target entity is in full view. +#define bits_COND_LIGHT_DAMAGE ( 1 << 8 ) // hurt a little +#define bits_COND_HEAVY_DAMAGE ( 1 << 9 ) // hurt a lot +#define bits_COND_SEE_CLIENT ( 1 << 21) // see a client +#define bits_COND_SEE_NEMESIS ( 1 << 22) // see my nemesis + + +#endif // SCHEDULE_H diff --git a/ricochet/dlls/scriptevent.h b/ricochet/dlls/scriptevent.h new file mode 100644 index 0000000..9aa7140 --- /dev/null +++ b/ricochet/dlls/scriptevent.h @@ -0,0 +1,29 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef SCRIPTEVENT_H +#define SCRIPTEVENT_H + +#define SCRIPT_EVENT_DEAD 1000 // character is now dead +#define SCRIPT_EVENT_NOINTERRUPT 1001 // does not allow interrupt +#define SCRIPT_EVENT_CANINTERRUPT 1002 // will allow interrupt +#define SCRIPT_EVENT_FIREEVENT 1003 // event now fires +#define SCRIPT_EVENT_SOUND 1004 // Play named wave file (on CHAN_BODY) +#define SCRIPT_EVENT_SENTENCE 1005 // Play named sentence +#define SCRIPT_EVENT_INAIR 1006 // Leave the character in air at the end of the sequence (don't find the floor) +#define SCRIPT_EVENT_ENDANIMATION 1007 // Set the animation by name after the sequence completes +#define SCRIPT_EVENT_SOUND_VOICE 1008 // Play named wave file (on CHAN_VOICE) +#define SCRIPT_EVENT_SENTENCE_RND1 1009 // Play sentence group 25% of the time +#define SCRIPT_EVENT_NOT_DEAD 1010 // Bring back to life (for life/death sequences) +#endif //SCRIPTEVENT_H diff --git a/ricochet/dlls/singleplay_gamerules.cpp b/ricochet/dlls/singleplay_gamerules.cpp new file mode 100644 index 0000000..43d5df7 --- /dev/null +++ b/ricochet/dlls/singleplay_gamerules.cpp @@ -0,0 +1,331 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.cpp +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "skill.h" +#include "items.h" +#include "discwar.h" + +extern DLL_GLOBAL CGameRules *g_pGameRules; +extern DLL_GLOBAL BOOL g_fGameOver; +extern int gmsgDeathMsg; // client dll messages +extern int gmsgScoreInfo; +extern int gmsgMOTD; + +//========================================================= +//========================================================= +CHalfLifeRules::CHalfLifeRules( void ) +{ + RefreshSkillData(); +} + +//========================================================= +//========================================================= +void CHalfLifeRules::Think ( void ) +{ +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::IsMultiplayer( void ) +{ + return FALSE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::IsDeathmatch ( void ) +{ + return FALSE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::IsCoOp( void ) +{ + return FALSE; +} + + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ + if ( !pPlayer->m_pActiveItem ) + { + // player doesn't have an active item! + return TRUE; + } + + if ( !pPlayer->m_pActiveItem->CanHolster() ) + { + return FALSE; + } + + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) +{ + return FALSE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) +{ + return TRUE; +} + +void CHalfLifeRules :: InitHUD( CBasePlayer *pl ) +{ +} + +//========================================================= +//========================================================= +void CHalfLifeRules :: ClientDisconnected( edict_t *pClient ) +{ +} + +//========================================================= +//========================================================= +float CHalfLifeRules::FlPlayerFallDamage( CBasePlayer *pPlayer ) +{ + // subtract off the speed at which a player is allowed to fall without being hurt, + // so damage will be based on speed beyond that, not the entire fall + pPlayer->m_flFallVelocity -= PLAYER_MAX_SAFE_FALL_SPEED; + return pPlayer->m_flFallVelocity * DAMAGE_FOR_FALL_SPEED; +} + +//========================================================= +//========================================================= +void CHalfLifeRules :: PlayerSpawn( CBasePlayer *pPlayer ) +{ + pPlayer->GiveNamedItem( "weapon_disc" ); + pPlayer->GiveAmmo( MAX_DISCS, "disc", MAX_DISCS ); +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: AllowAutoTargetCrosshair( void ) +{ + return ( g_iSkillLevel == SKILL_EASY ); +} + +//========================================================= +//========================================================= +void CHalfLifeRules :: PlayerThink( CBasePlayer *pPlayer ) +{ +} + + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: FPlayerCanRespawn( CBasePlayer *pPlayer ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +float CHalfLifeRules :: FlPlayerSpawnTime( CBasePlayer *pPlayer ) +{ + return gpGlobals->time;//now! +} + +//========================================================= +// IPointsForKill - how many points awarded to anyone +// that kills this player? +//========================================================= +int CHalfLifeRules :: IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) +{ + return 1; +} + +//========================================================= +// PlayerKilled - someone/something killed this player +//========================================================= +void CHalfLifeRules :: PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) +{ +} + +//========================================================= +// Deathnotice +//========================================================= +void CHalfLifeRules::DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) +{ +} + +//========================================================= +// PlayerGotWeapon - player has grabbed a weapon that was +// sitting in the world +//========================================================= +void CHalfLifeRules :: PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ +} + +//========================================================= +// FlWeaponRespawnTime - what is the time in the future +// at which this weapon may spawn? +//========================================================= +float CHalfLifeRules :: FlWeaponRespawnTime( CBasePlayerItem *pWeapon ) +{ + return -1; +} + +//========================================================= +// FlWeaponRespawnTime - Returns 0 if the weapon can respawn +// now, otherwise it returns the time at which it can try +// to spawn again. +//========================================================= +float CHalfLifeRules :: FlWeaponTryRespawn( CBasePlayerItem *pWeapon ) +{ + return 0; +} + +//========================================================= +// VecWeaponRespawnSpot - where should this weapon spawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHalfLifeRules :: VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ) +{ + return pWeapon->pev->origin; +} + +//========================================================= +// WeaponShouldRespawn - any conditions inhibiting the +// respawning of this weapon? +//========================================================= +int CHalfLifeRules :: WeaponShouldRespawn( CBasePlayerItem *pWeapon ) +{ + return GR_WEAPON_RESPAWN_NO; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +void CHalfLifeRules::PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ) +{ +} + +//========================================================= +//========================================================= +int CHalfLifeRules::ItemShouldRespawn( CItem *pItem ) +{ + return GR_ITEM_RESPAWN_NO; +} + + +//========================================================= +// At what time in the future may this Item respawn? +//========================================================= +float CHalfLifeRules::FlItemRespawnTime( CItem *pItem ) +{ + return -1; +} + +//========================================================= +// Where should this item respawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHalfLifeRules::VecItemRespawnSpot( CItem *pItem ) +{ + return pItem->pev->origin; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::IsAllowedToSpawn( CBaseEntity *pEntity ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +void CHalfLifeRules::PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ) +{ +} + +//========================================================= +//========================================================= +int CHalfLifeRules::AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ) +{ + return GR_AMMO_RESPAWN_NO; +} + +//========================================================= +//========================================================= +float CHalfLifeRules::FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ) +{ + return -1; +} + +//========================================================= +//========================================================= +Vector CHalfLifeRules::VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ) +{ + return pAmmo->pev->origin; +} + +//========================================================= +//========================================================= +float CHalfLifeRules::FlHealthChargerRechargeTime( void ) +{ + return 0;// don't recharge +} + +//========================================================= +//========================================================= +int CHalfLifeRules::DeadPlayerWeapons( CBasePlayer *pPlayer ) +{ + return GR_PLR_DROP_GUN_NO; +} + +//========================================================= +//========================================================= +int CHalfLifeRules::DeadPlayerAmmo( CBasePlayer *pPlayer ) +{ + return GR_PLR_DROP_AMMO_NO; +} + +//========================================================= +//========================================================= +int CHalfLifeRules::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) +{ + // why would a single player in half life need this? + return GR_NOTTEAMMATE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: FAllowMonsters( void ) +{ + return TRUE; +} diff --git a/ricochet/dlls/skill.cpp b/ricochet/dlls/skill.cpp new file mode 100644 index 0000000..5e06914 --- /dev/null +++ b/ricochet/dlls/skill.cpp @@ -0,0 +1,47 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// skill.cpp - code for skill level concerns +//========================================================= +#include "extdll.h" +#include "util.h" +#include "skill.h" + + +skilldata_t gSkillData; + + +//========================================================= +// take the name of a cvar, tack a digit for the skill level +// on, and return the value.of that Cvar +//========================================================= +float GetSkillCvar( char *pName ) +{ + int iCount; + float flValue; + char szBuffer[ 64 ]; + + iCount = sprintf( szBuffer, "%s%d",pName, gSkillData.iSkillLevel ); + + flValue = CVAR_GET_FLOAT ( szBuffer ); + + if ( flValue <= 0 ) + { + ALERT ( at_console, "\n\n** GetSkillCVar Got a zero for %s **\n\n", szBuffer ); + } + + return flValue; +} + diff --git a/ricochet/dlls/skill.h b/ricochet/dlls/skill.h new file mode 100644 index 0000000..30e25f8 --- /dev/null +++ b/ricochet/dlls/skill.h @@ -0,0 +1,147 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// skill.h - skill level concerns +//========================================================= + +struct skilldata_t +{ + + int iSkillLevel; // game skill level + +// Monster Health & Damage + float agruntHealth; + float agruntDmgPunch; + + float apacheHealth; + + float barneyHealth; + + float bigmommaHealthFactor; // Multiply each node's health by this + float bigmommaDmgSlash; // melee attack damage + float bigmommaDmgBlast; // mortar attack damage + float bigmommaRadiusBlast; // mortar attack radius + + float bullsquidHealth; + float bullsquidDmgBite; + float bullsquidDmgWhip; + float bullsquidDmgSpit; + + float gargantuaHealth; + float gargantuaDmgSlash; + float gargantuaDmgFire; + float gargantuaDmgStomp; + + float hassassinHealth; + + float headcrabHealth; + float headcrabDmgBite; + + float hgruntHealth; + float hgruntDmgKick; + float hgruntShotgunPellets; + float hgruntGrenadeSpeed; + + float houndeyeHealth; + float houndeyeDmgBlast; + + float slaveHealth; + float slaveDmgClaw; + float slaveDmgClawrake; + float slaveDmgZap; + + float ichthyosaurHealth; + float ichthyosaurDmgShake; + + float leechHealth; + float leechDmgBite; + + float controllerHealth; + float controllerDmgZap; + float controllerSpeedBall; + float controllerDmgBall; + + float nihilanthHealth; + float nihilanthZap; + + float scientistHealth; + + float snarkHealth; + float snarkDmgBite; + float snarkDmgPop; + + float zombieHealth; + float zombieDmgOneSlash; + float zombieDmgBothSlash; + + float turretHealth; + float miniturretHealth; + float sentryHealth; + + +// Player Weapons + float plrDmgCrowbar; + float plrDmg9MM; + float plrDmg357; + float plrDmgMP5; + float plrDmgM203Grenade; + float plrDmgBuckshot; + float plrDmgCrossbowClient; + float plrDmgCrossbowMonster; + float plrDmgRPG; + float plrDmgGauss; + float plrDmgEgonNarrow; + float plrDmgEgonWide; + float plrDmgHornet; + float plrDmgHandGrenade; + float plrDmgSatchel; + float plrDmgTripmine; + +// weapons shared by monsters + float monDmg9MM; + float monDmgMP5; + float monDmg12MM; + float monDmgHornet; + +// health/suit charge + float suitchargerCapacity; + float batteryCapacity; + float healthchargerCapacity; + float healthkitCapacity; + float scientistHeal; + +// monster damage adj + float monHead; + float monChest; + float monStomach; + float monLeg; + float monArm; + +// player damage adj + float plrHead; + float plrChest; + float plrStomach; + float plrLeg; + float plrArm; +}; + +extern DLL_GLOBAL skilldata_t gSkillData; +float GetSkillCvar( char *pName ); + +extern DLL_GLOBAL int g_iSkillLevel; + +#define SKILL_EASY 1 +#define SKILL_MEDIUM 2 +#define SKILL_HARD 3 diff --git a/ricochet/dlls/sound.cpp b/ricochet/dlls/sound.cpp new file mode 100644 index 0000000..ab3fb8b --- /dev/null +++ b/ricochet/dlls/sound.cpp @@ -0,0 +1,1985 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// sound.cpp +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "player.h" +#include "talkmonster.h" +#include "gamerules.h" + +#if !defined ( _WIN32 ) +#include +#endif + +static char *memfgets( byte *pMemFile, int fileSize, int &filePos, char *pBuffer, int bufferSize ); + + +// ==================== GENERIC AMBIENT SOUND ====================================== + +// runtime pitch shift and volume fadein/out structure + +// NOTE: IF YOU CHANGE THIS STRUCT YOU MUST CHANGE THE SAVE/RESTORE VERSION NUMBER +// SEE BELOW (in the typedescription for the class) +typedef struct dynpitchvol +{ + // NOTE: do not change the order of these parameters + // NOTE: unless you also change order of rgdpvpreset array elements! + int preset; + + int pitchrun; // pitch shift % when sound is running 0 - 255 + int pitchstart; // pitch shift % when sound stops or starts 0 - 255 + int spinup; // spinup time 0 - 100 + int spindown; // spindown time 0 - 100 + + int volrun; // volume change % when sound is running 0 - 10 + int volstart; // volume change % when sound stops or starts 0 - 10 + int fadein; // volume fade in time 0 - 100 + int fadeout; // volume fade out time 0 - 100 + + // Low Frequency Oscillator + int lfotype; // 0) off 1) square 2) triangle 3) random + int lforate; // 0 - 1000, how fast lfo osciallates + + int lfomodpitch; // 0-100 mod of current pitch. 0 is off. + int lfomodvol; // 0-100 mod of current volume. 0 is off. + + int cspinup; // each trigger hit increments counter and spinup pitch + + + int cspincount; + + int pitch; + int spinupsav; + int spindownsav; + int pitchfrac; + + int vol; + int fadeinsav; + int fadeoutsav; + int volfrac; + + int lfofrac; + int lfomult; + + +} dynpitchvol_t; + +#define CDPVPRESETMAX 27 + +// presets for runtime pitch and vol modulation of ambient sounds + +dynpitchvol_t rgdpvpreset[CDPVPRESETMAX] = +{ +// pitch pstart spinup spindwn volrun volstrt fadein fadeout lfotype lforate modptch modvol cspnup +{1, 255, 75, 95, 95, 10, 1, 50, 95, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{2, 255, 85, 70, 88, 10, 1, 20, 88, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{3, 255, 100, 50, 75, 10, 1, 10, 75, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{4, 100, 100, 0, 0, 10, 1, 90, 90, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{5, 100, 100, 0, 0, 10, 1, 80, 80, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{6, 100, 100, 0, 0, 10, 1, 50, 70, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{7, 100, 100, 0, 0, 5, 1, 40, 50, 1, 50, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{8, 100, 100, 0, 0, 5, 1, 40, 50, 1, 150, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{9, 100, 100, 0, 0, 5, 1, 40, 50, 1, 750, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{10,128, 100, 50, 75, 10, 1, 30, 40, 2, 8, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{11,128, 100, 50, 75, 10, 1, 30, 40, 2, 25, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{12,128, 100, 50, 75, 10, 1, 30, 40, 2, 70, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{13,50, 50, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{14,70, 70, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{15,90, 90, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{16,120, 120, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{17,180, 180, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{18,255, 255, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{19,200, 75, 90, 90, 10, 1, 50, 90, 2, 100, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{20,255, 75, 97, 90, 10, 1, 50, 90, 1, 40, 50, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{21,100, 100, 0, 0, 10, 1, 30, 50, 3, 15, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{22,160, 160, 0, 0, 10, 1, 50, 50, 3, 500, 25, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{23,255, 75, 88, 0, 10, 1, 40, 0, 0, 0, 0, 0, 5, 0,0,0,0,0,0,0,0,0,0}, +{24,200, 20, 95, 70, 10, 1, 70, 70, 3, 20, 50, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{25,180, 100, 50, 60, 10, 1, 40, 60, 2, 90, 100, 100, 0, 0,0,0,0,0,0,0,0,0,0}, +{26,60, 60, 0, 0, 10, 1, 40, 70, 3, 80, 20, 50, 0, 0,0,0,0,0,0,0,0,0,0}, +{27,128, 90, 10, 10, 10, 1, 20, 40, 1, 5, 10, 20, 0, 0,0,0,0,0,0,0,0,0,0} +}; + +class CAmbientGeneric : public CBaseEntity +{ +public: + void KeyValue( KeyValueData* pkvd); + void Spawn( void ); + void Precache( void ); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT RampThink( void ); + void InitModulationParms(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + float m_flAttenuation; // attenuation value + dynpitchvol_t m_dpv; + + BOOL m_fActive; // only TRUE when the entity is playing a looping sound + BOOL m_fLooping; // TRUE when the sound played will loop +}; + +LINK_ENTITY_TO_CLASS( ambient_generic, CAmbientGeneric ); +TYPEDESCRIPTION CAmbientGeneric::m_SaveData[] = +{ + DEFINE_FIELD( CAmbientGeneric, m_flAttenuation, FIELD_FLOAT ), + DEFINE_FIELD( CAmbientGeneric, m_fActive, FIELD_BOOLEAN ), + DEFINE_FIELD( CAmbientGeneric, m_fLooping, FIELD_BOOLEAN ), + + // HACKHACK - This is not really in the spirit of the save/restore design, but save this + // out as a binary data block. If the dynpitchvol_t is changed, old saved games will NOT + // load these correctly, so bump the save/restore version if you change the size of the struct + // The right way to do this is to split the input parms (read in keyvalue) into members and re-init this + // struct in Precache(), but it's unlikely that the struct will change, so it's not worth the time right now. + DEFINE_ARRAY( CAmbientGeneric, m_dpv, FIELD_CHARACTER, sizeof(dynpitchvol_t) ), +}; + +IMPLEMENT_SAVERESTORE( CAmbientGeneric, CBaseEntity ); + +// +// ambient_generic - general-purpose user-defined static sound +// +void CAmbientGeneric :: Spawn( void ) +{ +/* + -1 : "Default" + 0 : "Everywhere" + 200 : "Small Radius" + 125 : "Medium Radius" + 80 : "Large Radius" +*/ + + if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_EVERYWHERE) ) + { + m_flAttenuation = ATTN_NONE; + } + else if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_SMALLRADIUS) ) + { + m_flAttenuation = ATTN_IDLE; + } + else if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_MEDIUMRADIUS) ) + { + m_flAttenuation = ATTN_STATIC; + } + else if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_LARGERADIUS) ) + { + m_flAttenuation = ATTN_NORM; + } + else + {// if the designer didn't set a sound attenuation, default to one. + m_flAttenuation = ATTN_STATIC; + } + + char* szSoundFile = (char*) STRING(pev->message); + + if ( FStringNull( pev->message ) || strlen( szSoundFile ) < 1 ) + { + ALERT( at_error, "EMPTY AMBIENT AT: %f, %f, %f\n", pev->origin.x, pev->origin.y, pev->origin.z ); + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CAmbientGeneric::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + // Set up think function for dynamic modification + // of ambient sound's pitch or volume. Don't + // start thinking yet. + + SetThink(&CAmbientGeneric::RampThink); + pev->nextthink = 0; + + // allow on/off switching via 'use' function. + + SetUse ( &CAmbientGeneric::ToggleUse ); + + m_fActive = FALSE; + + if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_NOT_LOOPING ) ) + m_fLooping = FALSE; + else + m_fLooping = TRUE; + Precache( ); +} + + +void CAmbientGeneric :: Precache( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + + if ( !FStringNull( pev->message ) && strlen( szSoundFile ) > 1 ) + { + if (*szSoundFile != '!') + PRECACHE_SOUND(szSoundFile); + } + // init all dynamic modulation parms + InitModulationParms(); + + if ( !FBitSet (pev->spawnflags, AMBIENT_SOUND_START_SILENT ) ) + { + // start the sound ASAP + if (m_fLooping) + m_fActive = TRUE; + } + if ( m_fActive ) + { + UTIL_EmitAmbientSound ( ENT(pev), pev->origin, szSoundFile, + (m_dpv.vol * 0.01), m_flAttenuation, SND_SPAWNING, m_dpv.pitch); + + pev->nextthink = gpGlobals->time + 0.1; + } +} + +// RampThink - Think at 5hz if we are dynamically modifying +// pitch or volume of the playing sound. This function will +// ramp pitch and/or volume up or down, modify pitch/volume +// with lfo if active. + +void CAmbientGeneric :: RampThink( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + int pitch = m_dpv.pitch; + int vol = m_dpv.vol; + int flags = 0; + int fChanged = 0; // FALSE if pitch and vol remain unchanged this round + int prev; + + if (!m_dpv.spinup && !m_dpv.spindown && !m_dpv.fadein && !m_dpv.fadeout && !m_dpv.lfotype) + return; // no ramps or lfo, stop thinking + + // ============== + // pitch envelope + // ============== + if (m_dpv.spinup || m_dpv.spindown) + { + prev = m_dpv.pitchfrac >> 8; + + if (m_dpv.spinup > 0) + m_dpv.pitchfrac += m_dpv.spinup; + else if (m_dpv.spindown > 0) + m_dpv.pitchfrac -= m_dpv.spindown; + + pitch = m_dpv.pitchfrac >> 8; + + if (pitch > m_dpv.pitchrun) + { + pitch = m_dpv.pitchrun; + m_dpv.spinup = 0; // done with ramp up + } + + if (pitch < m_dpv.pitchstart) + { + pitch = m_dpv.pitchstart; + m_dpv.spindown = 0; // done with ramp down + + // shut sound off + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + + // return without setting nextthink + return; + } + + if (pitch > 255) pitch = 255; + if (pitch < 1) pitch = 1; + + m_dpv.pitch = pitch; + + fChanged |= (prev != pitch); + flags |= SND_CHANGE_PITCH; + } + + // ================== + // amplitude envelope + // ================== + if (m_dpv.fadein || m_dpv.fadeout) + { + prev = m_dpv.volfrac >> 8; + + if (m_dpv.fadein > 0) + m_dpv.volfrac += m_dpv.fadein; + else if (m_dpv.fadeout > 0) + m_dpv.volfrac -= m_dpv.fadeout; + + vol = m_dpv.volfrac >> 8; + + if (vol > m_dpv.volrun) + { + vol = m_dpv.volrun; + m_dpv.fadein = 0; // done with ramp up + } + + if (vol < m_dpv.volstart) + { + vol = m_dpv.volstart; + m_dpv.fadeout = 0; // done with ramp down + + // shut sound off + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + + // return without setting nextthink + return; + } + + if (vol > 100) vol = 100; + if (vol < 1) vol = 1; + + m_dpv.vol = vol; + + fChanged |= (prev != vol); + flags |= SND_CHANGE_VOL; + } + + // =================== + // pitch/amplitude LFO + // =================== + if (m_dpv.lfotype) + { + int pos; + + if (m_dpv.lfofrac > 0x6fffffff) + m_dpv.lfofrac = 0; + + // update lfo, lfofrac/255 makes a triangle wave 0-255 + m_dpv.lfofrac += m_dpv.lforate; + pos = m_dpv.lfofrac >> 8; + + if (m_dpv.lfofrac < 0) + { + m_dpv.lfofrac = 0; + m_dpv.lforate = abs(m_dpv.lforate); + pos = 0; + } + else if (pos > 255) + { + pos = 255; + m_dpv.lfofrac = (255 << 8); + m_dpv.lforate = -abs(m_dpv.lforate); + } + + switch(m_dpv.lfotype) + { + case LFO_SQUARE: + if (pos < 128) + m_dpv.lfomult = 255; + else + m_dpv.lfomult = 0; + + break; + case LFO_RANDOM: + if (pos == 255) + m_dpv.lfomult = RANDOM_LONG(0, 255); + break; + case LFO_TRIANGLE: + default: + m_dpv.lfomult = pos; + break; + } + + if (m_dpv.lfomodpitch) + { + prev = pitch; + + // pitch 0-255 + pitch += ((m_dpv.lfomult - 128) * m_dpv.lfomodpitch) / 100; + + if (pitch > 255) pitch = 255; + if (pitch < 1) pitch = 1; + + + fChanged |= (prev != pitch); + flags |= SND_CHANGE_PITCH; + } + + if (m_dpv.lfomodvol) + { + // vol 0-100 + prev = vol; + + vol += ((m_dpv.lfomult - 128) * m_dpv.lfomodvol) / 100; + + if (vol > 100) vol = 100; + if (vol < 0) vol = 0; + + fChanged |= (prev != vol); + flags |= SND_CHANGE_VOL; + } + + } + + // Send update to playing sound only if we actually changed + // pitch or volume in this routine. + + if (flags && fChanged) + { + if (pitch == PITCH_NORM) + pitch = PITCH_NORM + 1; // don't send 'no pitch' ! + + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + (vol * 0.01), m_flAttenuation, flags, pitch); + } + + // update ramps at 5hz + pev->nextthink = gpGlobals->time + 0.2; + return; +} + +// Init all ramp params in preparation to +// play a new sound + +void CAmbientGeneric :: InitModulationParms(void) +{ + int pitchinc; + + m_dpv.volrun = pev->health * 10; // 0 - 100 + if (m_dpv.volrun > 100) m_dpv.volrun = 100; + if (m_dpv.volrun < 0) m_dpv.volrun = 0; + + // get presets + if (m_dpv.preset != 0 && m_dpv.preset <= CDPVPRESETMAX) + { + // load preset values + m_dpv = rgdpvpreset[m_dpv.preset - 1]; + + // fixup preset values, just like + // fixups in KeyValue routine. + if (m_dpv.spindown > 0) + m_dpv.spindown = (101 - m_dpv.spindown) * 64; + if (m_dpv.spinup > 0) + m_dpv.spinup = (101 - m_dpv.spinup) * 64; + + m_dpv.volstart *= 10; + m_dpv.volrun *= 10; + + if (m_dpv.fadein > 0) + m_dpv.fadein = (101 - m_dpv.fadein) * 64; + if (m_dpv.fadeout > 0) + m_dpv.fadeout = (101 - m_dpv.fadeout) * 64; + + m_dpv.lforate *= 256; + + m_dpv.fadeinsav = m_dpv.fadein; + m_dpv.fadeoutsav = m_dpv.fadeout; + m_dpv.spinupsav = m_dpv.spinup; + m_dpv.spindownsav = m_dpv.spindown; + } + + m_dpv.fadein = m_dpv.fadeinsav; + m_dpv.fadeout = 0; + + if (m_dpv.fadein) + m_dpv.vol = m_dpv.volstart; + else + m_dpv.vol = m_dpv.volrun; + + m_dpv.spinup = m_dpv.spinupsav; + m_dpv.spindown = 0; + + if (m_dpv.spinup) + m_dpv.pitch = m_dpv.pitchstart; + else + m_dpv.pitch = m_dpv.pitchrun; + + if (m_dpv.pitch == 0) + m_dpv.pitch = PITCH_NORM; + + m_dpv.pitchfrac = m_dpv.pitch << 8; + m_dpv.volfrac = m_dpv.vol << 8; + + m_dpv.lfofrac = 0; + m_dpv.lforate = abs(m_dpv.lforate); + + m_dpv.cspincount = 1; + + if (m_dpv.cspinup) + { + pitchinc = (255 - m_dpv.pitchstart) / m_dpv.cspinup; + + m_dpv.pitchrun = m_dpv.pitchstart + pitchinc; + if (m_dpv.pitchrun > 255) m_dpv.pitchrun = 255; + } + + if ((m_dpv.spinupsav || m_dpv.spindownsav || (m_dpv.lfotype && m_dpv.lfomodpitch)) + && (m_dpv.pitch == PITCH_NORM)) + m_dpv.pitch = PITCH_NORM + 1; // must never send 'no pitch' as first pitch + // if we intend to pitch shift later! +} + +// +// ToggleUse - turns an ambient sound on or off. If the +// ambient is a looping sound, mark sound as active (m_fActive) +// if it's playing, innactive if not. If the sound is not +// a looping sound, never mark it as active. +// +void CAmbientGeneric :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + char* szSoundFile = (char*) STRING(pev->message); + float fraction; + + if ( useType != USE_TOGGLE ) + { + if ( (m_fActive && useType == USE_ON) || (!m_fActive && useType == USE_OFF) ) + return; + } + // Directly change pitch if arg passed. Only works if sound is already playing. + + if (useType == USE_SET && m_fActive) // Momentary buttons will pass down a float in here + { + + fraction = value; + + if ( fraction > 1.0 ) + fraction = 1.0; + if (fraction < 0.0) + fraction = 0.01; + + m_dpv.pitch = fraction * 255; + + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_CHANGE_PITCH, m_dpv.pitch); + + return; + } + + // Toggle + + // m_fActive is TRUE only if a looping sound is playing. + + if ( m_fActive ) + {// turn sound off + + if (m_dpv.cspinup) + { + // Don't actually shut off. Each toggle causes + // incremental spinup to max pitch + + if (m_dpv.cspincount <= m_dpv.cspinup) + { + int pitchinc; + + // start a new spinup + m_dpv.cspincount++; + + pitchinc = (255 - m_dpv.pitchstart) / m_dpv.cspinup; + + m_dpv.spinup = m_dpv.spinupsav; + m_dpv.spindown = 0; + + m_dpv.pitchrun = m_dpv.pitchstart + pitchinc * m_dpv.cspincount; + if (m_dpv.pitchrun > 255) m_dpv.pitchrun = 255; + + pev->nextthink = gpGlobals->time + 0.1; + } + + } + else + { + m_fActive = FALSE; + + // HACKHACK - this makes the code in Precache() work properly after a save/restore + pev->spawnflags |= AMBIENT_SOUND_START_SILENT; + + if (m_dpv.spindownsav || m_dpv.fadeoutsav) + { + // spin it down (or fade it) before shutoff if spindown is set + m_dpv.spindown = m_dpv.spindownsav; + m_dpv.spinup = 0; + + m_dpv.fadeout = m_dpv.fadeoutsav; + m_dpv.fadein = 0; + pev->nextthink = gpGlobals->time + 0.1; + } + else + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + } + } + else + {// turn sound on + + // only toggle if this is a looping sound. If not looping, each + // trigger will cause the sound to play. If the sound is still + // playing from a previous trigger press, it will be shut off + // and then restarted. + + if (m_fLooping) + m_fActive = TRUE; + else + // shut sound off now - may be interrupting a long non-looping sound + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + + // init all ramp params for startup + + InitModulationParms(); + + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + (m_dpv.vol * 0.01), m_flAttenuation, 0, m_dpv.pitch); + + pev->nextthink = gpGlobals->time + 0.1; + + } +} +// KeyValue - load keyvalue pairs into member data of the +// ambient generic. NOTE: called BEFORE spawn! + +void CAmbientGeneric :: KeyValue( KeyValueData *pkvd ) +{ + // NOTE: changing any of the modifiers in this code + // NOTE: also requires changing InitModulationParms code. + + // preset + if (FStrEq(pkvd->szKeyName, "preset")) + { + m_dpv.preset = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + + // pitchrun + else if (FStrEq(pkvd->szKeyName, "pitch")) + { + m_dpv.pitchrun = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + if (m_dpv.pitchrun > 255) m_dpv.pitchrun = 255; + if (m_dpv.pitchrun < 0) m_dpv.pitchrun = 0; + } + + // pitchstart + else if (FStrEq(pkvd->szKeyName, "pitchstart")) + { + m_dpv.pitchstart = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + if (m_dpv.pitchstart > 255) m_dpv.pitchstart = 255; + if (m_dpv.pitchstart < 0) m_dpv.pitchstart = 0; + } + + // spinup + else if (FStrEq(pkvd->szKeyName, "spinup")) + { + m_dpv.spinup = atoi(pkvd->szValue); + + if (m_dpv.spinup > 100) m_dpv.spinup = 100; + if (m_dpv.spinup < 0) m_dpv.spinup = 0; + + if (m_dpv.spinup > 0) + m_dpv.spinup = (101 - m_dpv.spinup) * 64; + m_dpv.spinupsav = m_dpv.spinup; + pkvd->fHandled = TRUE; + } + + // spindown + else if (FStrEq(pkvd->szKeyName, "spindown")) + { + m_dpv.spindown = atoi(pkvd->szValue); + + if (m_dpv.spindown > 100) m_dpv.spindown = 100; + if (m_dpv.spindown < 0) m_dpv.spindown = 0; + + if (m_dpv.spindown > 0) + m_dpv.spindown = (101 - m_dpv.spindown) * 64; + m_dpv.spindownsav = m_dpv.spindown; + pkvd->fHandled = TRUE; + } + + // volstart + else if (FStrEq(pkvd->szKeyName, "volstart")) + { + m_dpv.volstart = atoi(pkvd->szValue); + + if (m_dpv.volstart > 10) m_dpv.volstart = 10; + if (m_dpv.volstart < 0) m_dpv.volstart = 0; + + m_dpv.volstart *= 10; // 0 - 100 + + pkvd->fHandled = TRUE; + } + + // fadein + else if (FStrEq(pkvd->szKeyName, "fadein")) + { + m_dpv.fadein = atoi(pkvd->szValue); + + if (m_dpv.fadein > 100) m_dpv.fadein = 100; + if (m_dpv.fadein < 0) m_dpv.fadein = 0; + + if (m_dpv.fadein > 0) + m_dpv.fadein = (101 - m_dpv.fadein) * 64; + m_dpv.fadeinsav = m_dpv.fadein; + pkvd->fHandled = TRUE; + } + + // fadeout + else if (FStrEq(pkvd->szKeyName, "fadeout")) + { + m_dpv.fadeout = atoi(pkvd->szValue); + + if (m_dpv.fadeout > 100) m_dpv.fadeout = 100; + if (m_dpv.fadeout < 0) m_dpv.fadeout = 0; + + if (m_dpv.fadeout > 0) + m_dpv.fadeout = (101 - m_dpv.fadeout) * 64; + m_dpv.fadeoutsav = m_dpv.fadeout; + pkvd->fHandled = TRUE; + } + + // lfotype + else if (FStrEq(pkvd->szKeyName, "lfotype")) + { + m_dpv.lfotype = atoi(pkvd->szValue); + if (m_dpv.lfotype > 4) m_dpv.lfotype = LFO_TRIANGLE; + pkvd->fHandled = TRUE; + } + + // lforate + else if (FStrEq(pkvd->szKeyName, "lforate")) + { + m_dpv.lforate = atoi(pkvd->szValue); + + if (m_dpv.lforate > 1000) m_dpv.lforate = 1000; + if (m_dpv.lforate < 0) m_dpv.lforate = 0; + + m_dpv.lforate *= 256; + + pkvd->fHandled = TRUE; + } + // lfomodpitch + else if (FStrEq(pkvd->szKeyName, "lfomodpitch")) + { + m_dpv.lfomodpitch = atoi(pkvd->szValue); + if (m_dpv.lfomodpitch > 100) m_dpv.lfomodpitch = 100; + if (m_dpv.lfomodpitch < 0) m_dpv.lfomodpitch = 0; + + + pkvd->fHandled = TRUE; + } + + // lfomodvol + else if (FStrEq(pkvd->szKeyName, "lfomodvol")) + { + m_dpv.lfomodvol = atoi(pkvd->szValue); + if (m_dpv.lfomodvol > 100) m_dpv.lfomodvol = 100; + if (m_dpv.lfomodvol < 0) m_dpv.lfomodvol = 0; + + pkvd->fHandled = TRUE; + } + + // cspinup + else if (FStrEq(pkvd->szKeyName, "cspinup")) + { + m_dpv.cspinup = atoi(pkvd->szValue); + if (m_dpv.cspinup > 100) m_dpv.cspinup = 100; + if (m_dpv.cspinup < 0) m_dpv.cspinup = 0; + + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +// =================== ROOM SOUND FX ========================================== + +class CEnvSound : public CPointEntity +{ +public: + void KeyValue( KeyValueData* pkvd); + void Spawn( void ); + + void Think( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + float m_flRadius; + float m_flRoomtype; +}; + +LINK_ENTITY_TO_CLASS( env_sound, CEnvSound ); +TYPEDESCRIPTION CEnvSound::m_SaveData[] = +{ + DEFINE_FIELD( CEnvSound, m_flRadius, FIELD_FLOAT ), + DEFINE_FIELD( CEnvSound, m_flRoomtype, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CEnvSound, CBaseEntity ); + + +void CEnvSound :: KeyValue( KeyValueData *pkvd ) +{ + + if (FStrEq(pkvd->szKeyName, "radius")) + { + m_flRadius = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + if (FStrEq(pkvd->szKeyName, "roomtype")) + { + m_flRoomtype = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } +} + +// returns TRUE if the given sound entity (pev) is in range +// and can see the given player entity (pevTarget) + +BOOL FEnvSoundInRange(entvars_t *pev, entvars_t *pevTarget, float *pflRange) +{ + CEnvSound *pSound = GetClassPtr( (CEnvSound *)pev ); + Vector vecSpot1 = pev->origin + pev->view_ofs; + Vector vecSpot2 = pevTarget->origin + pevTarget->view_ofs; + Vector vecRange; + float flRange; + TraceResult tr; + + UTIL_TraceLine(vecSpot1, vecSpot2, ignore_monsters, ENT(pev), &tr); + + // check if line of sight crosses water boundary, or is blocked + + if ((tr.fInOpen && tr.fInWater) || tr.flFraction != 1) + return FALSE; + + // calc range from sound entity to player + + vecRange = tr.vecEndPos - vecSpot1; + flRange = vecRange.Length(); + + if (pSound->m_flRadius < flRange) + return FALSE; + + if (pflRange) + *pflRange = flRange; + + return TRUE; +} + +// +// A client that is visible and in range of a sound entity will +// have its room_type set by that sound entity. If two or more +// sound entities are contending for a client, then the nearest +// sound entity to the client will set the client's room_type. +// A client's room_type will remain set to its prior value until +// a new in-range, visible sound entity resets a new room_type. +// + +// CONSIDER: if player in water state, autoset roomtype to 14,15 or 16. + +void CEnvSound :: Think( void ) +{ + // get pointer to client if visible; FIND_CLIENT_IN_PVS will + // cycle through visible clients on consecutive calls. + + edict_t *pentPlayer = FIND_CLIENT_IN_PVS(edict()); + CBasePlayer *pPlayer = NULL; + + if (FNullEnt(pentPlayer)) + goto env_sound_Think_slow; // no player in pvs of sound entity, slow it down + + pPlayer = GetClassPtr( (CBasePlayer *)VARS(pentPlayer)); + float flRange; + + // check to see if this is the sound entity that is + // currently affecting this player + + if(!FNullEnt(pPlayer->m_pentSndLast) && (pPlayer->m_pentSndLast == ENT(pev))) { + + // this is the entity currently affecting player, check + // for validity + + if (pPlayer->m_flSndRoomtype != 0 && pPlayer->m_flSndRange != 0) { + + // we're looking at a valid sound entity affecting + // player, make sure it's still valid, update range + + if (FEnvSoundInRange(pev, VARS(pentPlayer), &flRange)) { + pPlayer->m_flSndRange = flRange; + goto env_sound_Think_fast; + } else { + + // current sound entity affecting player is no longer valid, + // flag this state by clearing room_type and range. + // NOTE: we do not actually change the player's room_type + // NOTE: until we have a new valid room_type to change it to. + + pPlayer->m_flSndRange = 0; + pPlayer->m_flSndRoomtype = 0; + goto env_sound_Think_slow; + } + } else { + // entity is affecting player but is out of range, + // wait passively for another entity to usurp it... + goto env_sound_Think_slow; + } + } + + // if we got this far, we're looking at an entity that is contending + // for current player sound. the closest entity to player wins. + + if (FEnvSoundInRange(pev, VARS(pentPlayer), &flRange)) + { + if (flRange < pPlayer->m_flSndRange || pPlayer->m_flSndRange == 0) + { + // new entity is closer to player, so it wins. + pPlayer->m_pentSndLast = ENT(pev); + pPlayer->m_flSndRoomtype = m_flRoomtype; + pPlayer->m_flSndRange = flRange; + + // send room_type command to player's server. + // this should be a rare event - once per change of room_type + // only! + + //CLIENT_COMMAND(pentPlayer, "room_type %f", m_flRoomtype); + + MESSAGE_BEGIN( MSG_ONE, SVC_ROOMTYPE, NULL, pentPlayer ); // use the magic #1 for "one client" + WRITE_SHORT( (short)m_flRoomtype ); // sequence number + MESSAGE_END(); + + // crank up nextthink rate for new active sound entity + // by falling through to think_fast... + } + // player is not closer to the contending sound entity, + // just fall through to think_fast. this effectively + // cranks up the think_rate of entities near the player. + } + + // player is in pvs of sound entity, but either not visible or + // not in range. do nothing, fall through to think_fast... + +env_sound_Think_fast: + pev->nextthink = gpGlobals->time + 0.25; + return; + +env_sound_Think_slow: + pev->nextthink = gpGlobals->time + 0.75; + return; +} + +// +// env_sound - spawn a sound entity that will set player roomtype +// when player moves in range and sight. +// +// +void CEnvSound :: Spawn( ) +{ + // spread think times + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(0.0, 0.5); +} + +// ==================== SENTENCE GROUPS, UTILITY FUNCTIONS ====================================== + +#define CSENTENCE_LRU_MAX 32 // max number of elements per sentence group + +// group of related sentences + +typedef struct sentenceg +{ + char szgroupname[CBSENTENCENAME_MAX]; + int count; + unsigned char rgblru[CSENTENCE_LRU_MAX]; + +} SENTENCEG; + +#define CSENTENCEG_MAX 200 // max number of sentence groups +// globals + +SENTENCEG rgsentenceg[CSENTENCEG_MAX]; +int fSentencesInit = FALSE; + +char gszallsentencenames[CVOXFILESENTENCEMAX][CBSENTENCENAME_MAX]; +int gcallsentences = 0; + +// randomize list of sentence name indices + +void USENTENCEG_InitLRU(unsigned char *plru, int count) +{ + int i, j, k; + unsigned char temp; + + if (!fSentencesInit) + return; + + if (count > CSENTENCE_LRU_MAX) + count = CSENTENCE_LRU_MAX; + + for (i = 0; i < count; i++) + plru[i] = (unsigned char) i; + + // randomize array + for (i = 0; i < (count * 4); i++) + { + j = RANDOM_LONG(0,count-1); + k = RANDOM_LONG(0,count-1); + temp = plru[j]; + plru[j] = plru[k]; + plru[k] = temp; + } +} + +// ignore lru. pick next sentence from sentence group. Go in order until we hit the last sentence, +// then repeat list if freset is true. If freset is false, then repeat last sentence. +// ipick is passed in as the requested sentence ordinal. +// ipick 'next' is returned. +// return of -1 indicates an error. + +int USENTENCEG_PickSequential(int isentenceg, char *szfound, int ipick, int freset) +{ + char *szgroupname; + unsigned char count; + char sznum[8]; + + if (!fSentencesInit) + return -1; + + if (isentenceg < 0) + return -1; + + szgroupname = rgsentenceg[isentenceg].szgroupname; + count = rgsentenceg[isentenceg].count; + + if (count == 0) + return -1; + + if (ipick >= count) + ipick = count-1; + + strcpy(szfound, "!"); + strcat(szfound, szgroupname); + itoa(ipick, sznum, 10); + strcat(szfound, sznum); + + if (ipick >= count) + { + if (freset) + // reset at end of list + return 0; + else + return count; + } + + return ipick + 1; +} + + + +// pick a random sentence from rootname0 to rootnameX. +// picks from the rgsentenceg[isentenceg] least +// recently used, modifies lru array. returns the sentencename. +// note, lru must be seeded with 0-n randomized sentence numbers, with the +// rest of the lru filled with -1. The first integer in the lru is +// actually the size of the list. Returns ipick, the ordinal +// of the picked sentence within the group. + +int USENTENCEG_Pick(int isentenceg, char *szfound) +{ + char *szgroupname; + unsigned char *plru; + unsigned char i; + unsigned char count; + char sznum[8]; + unsigned char ipick; + int ffound = FALSE; + + if (!fSentencesInit) + return -1; + + if (isentenceg < 0) + return -1; + + szgroupname = rgsentenceg[isentenceg].szgroupname; + count = rgsentenceg[isentenceg].count; + plru = rgsentenceg[isentenceg].rgblru; + + while (!ffound) + { + for (i = 0; i < count; i++) + if (plru[i] != 0xFF) + { + ipick = plru[i]; + plru[i] = 0xFF; + ffound = TRUE; + break; + } + + if (!ffound) + USENTENCEG_InitLRU(plru, count); + else + { + strcpy(szfound, "!"); + strcat(szfound, szgroupname); + itoa(ipick, sznum, 10); + strcat(szfound, sznum); + return ipick; + } + } + return -1; +} + +// ===================== SENTENCE GROUPS, MAIN ROUTINES ======================== + +// Given sentence group rootname (name without number suffix), +// get sentence group index (isentenceg). Returns -1 if no such name. + +int SENTENCEG_GetIndex(const char *szgroupname) +{ + int i; + + if (!fSentencesInit || !szgroupname) + return -1; + + // search rgsentenceg for match on szgroupname + + i = 0; + while (rgsentenceg[i].count) + { + if (!strcmp(szgroupname, rgsentenceg[i].szgroupname)) + return i; + i++; + } + + return -1; +} + +// given sentence group index, play random sentence for given entity. +// returns ipick - which sentence was picked to +// play from the group. Ipick is only needed if you plan on stopping +// the sound before playback is done (see SENTENCEG_Stop). + +int SENTENCEG_PlayRndI(edict_t *entity, int isentenceg, + float volume, float attenuation, int flags, int pitch) +{ + char name[64]; + int ipick; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + ipick = USENTENCEG_Pick(isentenceg, name); + if (ipick > 0 && name) + EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch); + return ipick; +} + +// same as above, but takes sentence group name instead of index + +int SENTENCEG_PlayRndSz(edict_t *entity, const char *szgroupname, + float volume, float attenuation, int flags, int pitch) +{ + char name[64]; + int ipick; + int isentenceg; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + isentenceg = SENTENCEG_GetIndex(szgroupname); + if (isentenceg < 0) + { + ALERT( at_console, "No such sentence group %s\n", szgroupname ); + return -1; + } + + ipick = USENTENCEG_Pick(isentenceg, name); + if (ipick >= 0 && name[0]) + EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch); + + return ipick; +} + +// play sentences in sequential order from sentence group. Reset after last sentence. + +int SENTENCEG_PlaySequentialSz(edict_t *entity, const char *szgroupname, + float volume, float attenuation, int flags, int pitch, int ipick, int freset) +{ + char name[64]; + int ipicknext; + int isentenceg; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + isentenceg = SENTENCEG_GetIndex(szgroupname); + if (isentenceg < 0) + return -1; + + ipicknext = USENTENCEG_PickSequential(isentenceg, name, ipick, freset); + if (ipicknext >= 0 && name[0]) + EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch); + return ipicknext; +} + + +// for this entity, for the given sentence within the sentence group, stop +// the sentence. + +void SENTENCEG_Stop(edict_t *entity, int isentenceg, int ipick) +{ + char buffer[64]; + char sznum[8]; + + if (!fSentencesInit) + return; + + if (isentenceg < 0 || ipick < 0) + return; + + strcpy(buffer, "!"); + strcat(buffer, rgsentenceg[isentenceg].szgroupname); + itoa(ipick, sznum, 10); + strcat(buffer, sznum); + + STOP_SOUND(entity, CHAN_VOICE, buffer); +} + +// open sentences.txt, scan for groups, build rgsentenceg +// Should be called from world spawn, only works on the +// first call and is ignored subsequently. + +void SENTENCEG_Init() +{ + char buffer[512]; + char szgroup[64]; + int i, j; + int isentencegs; + + if (fSentencesInit) + return; + + memset(gszallsentencenames, 0, CVOXFILESENTENCEMAX * CBSENTENCENAME_MAX); + gcallsentences = 0; + + memset(rgsentenceg, 0, CSENTENCEG_MAX * sizeof(SENTENCEG)); + memset(buffer, 0, 512); + memset(szgroup, 0, 64); + isentencegs = -1; + + + int filePos = 0, fileSize; + byte *pMemFile = g_engfuncs.pfnLoadFileForMe( "sound/sentences.txt", &fileSize ); + if ( !pMemFile ) + return; + + // for each line in the file... + while ( memfgets(pMemFile, fileSize, filePos, buffer, 511) != NULL ) + { + // skip whitespace + i = 0; + while(buffer[i] && buffer[i] == ' ') + i++; + + if (!buffer[i]) + continue; + + if (buffer[i] == '/' || !isalpha(buffer[i])) + continue; + + // get sentence name + j = i; + while (buffer[j] && buffer[j] != ' ') + j++; + + if (!buffer[j]) + continue; + + if (gcallsentences > CVOXFILESENTENCEMAX) + { + ALERT (at_error, "Too many sentences in sentences.txt!\n"); + break; + } + + // null-terminate name and save in sentences array + buffer[j] = 0; + const char *pString = buffer + i; + + if ( strlen( pString ) >= CBSENTENCENAME_MAX ) + ALERT( at_warning, "Sentence %s longer than %d letters\n", pString, CBSENTENCENAME_MAX-1 ); + + strcpy( gszallsentencenames[gcallsentences++], pString ); + + j--; + if (j <= i) + continue; + if (!isdigit(buffer[j])) + continue; + + // cut out suffix numbers + while (j > i && isdigit(buffer[j])) + j--; + + if (j <= i) + continue; + + buffer[j+1] = 0; + + // if new name doesn't match previous group name, + // make a new group. + + if (strcmp(szgroup, &(buffer[i]))) + { + // name doesn't match with prev name, + // copy name into group, init count to 1 + isentencegs++; + if (isentencegs >= CSENTENCEG_MAX) + { + ALERT (at_error, "Too many sentence groups in sentences.txt!\n"); + break; + } + + strcpy(rgsentenceg[isentencegs].szgroupname, &(buffer[i])); + rgsentenceg[isentencegs].count = 1; + + strcpy(szgroup, &(buffer[i])); + + continue; + } + else + { + //name matches with previous, increment group count + if (isentencegs >= 0) + rgsentenceg[isentencegs].count++; + } + } + + g_engfuncs.pfnFreeFile( pMemFile ); + + fSentencesInit = TRUE; + + // init lru lists + + i = 0; + + while (rgsentenceg[i].count && i < CSENTENCEG_MAX) + { + USENTENCEG_InitLRU(&(rgsentenceg[i].rgblru[0]), rgsentenceg[i].count); + i++; + } + +} + +// convert sentence (sample) name to !sentencenum, return !sentencenum + +int SENTENCEG_Lookup(const char *sample, char *sentencenum) +{ + char sznum[8]; + +//#ifdef GERMANY +// return -1; // no constructed sentences in international versions +//#endif // GERMANY + + int i; + // this is a sentence name; lookup sentence number + // and give to engine as string. + for (i = 0; i < gcallsentences; i++) + if (!stricmp(gszallsentencenames[i], sample+1)) + { + if (sentencenum) + { + strcpy(sentencenum, "!"); + itoa(i, sznum, 10); + strcat(sentencenum, sznum); + } + return i; + } + // sentence name not found! + return -1; +} + +void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volume, float attenuation, + int flags, int pitch) +{ + if (sample && *sample == '!') + { + char name[32]; + if (SENTENCEG_Lookup(sample, name) >= 0) + EMIT_SOUND_DYN2(entity, channel, name, volume, attenuation, flags, pitch); + else + ALERT( at_aiconsole, "Unable to find %s in sentences.txt\n", sample ); + } + else + EMIT_SOUND_DYN2(entity, channel, sample, volume, attenuation, flags, pitch); +} + +// play a specific sentence over the HEV suit speaker - just pass player entity, and !sentencename + +void EMIT_SOUND_SUIT(edict_t *entity, const char *sample) +{ + float fvol; + int pitch = PITCH_NORM; + + fvol = CVAR_GET_FLOAT("suitvolume"); + if (RANDOM_LONG(0,1)) + pitch = RANDOM_LONG(0,6) + 98; + + if (fvol > 0.05) + EMIT_SOUND_DYN(entity, CHAN_STATIC, sample, fvol, ATTN_NORM, 0, pitch); +} + +// play a sentence, randomly selected from the passed in group id, over the HEV suit speaker + +void EMIT_GROUPID_SUIT(edict_t *entity, int isentenceg) +{ + float fvol; + int pitch = PITCH_NORM; + + fvol = CVAR_GET_FLOAT("suitvolume"); + if (RANDOM_LONG(0,1)) + pitch = RANDOM_LONG(0,6) + 98; + + if (fvol > 0.05) + SENTENCEG_PlayRndI(entity, isentenceg, fvol, ATTN_NORM, 0, pitch); +} + +// play a sentence, randomly selected from the passed in groupname + +void EMIT_GROUPNAME_SUIT(edict_t *entity, const char *groupname) +{ + float fvol; + int pitch = PITCH_NORM; + + fvol = CVAR_GET_FLOAT("suitvolume"); + if (RANDOM_LONG(0,1)) + pitch = RANDOM_LONG(0,6) + 98; + + if (fvol > 0.05) + SENTENCEG_PlayRndSz(entity, groupname, fvol, ATTN_NORM, 0, pitch); +} + +// ===================== MATERIAL TYPE DETECTION, MAIN ROUTINES ======================== +// +// Used to detect the texture the player is standing on, map the +// texture name to a material type. Play footstep sound based +// on material type. + +int fTextureTypeInit = FALSE; + +#define CTEXTURESMAX 512 // max number of textures loaded + +int gcTextures = 0; +char grgszTextureName[CTEXTURESMAX][CBTEXTURENAMEMAX]; // texture names +char grgchTextureType[CTEXTURESMAX]; // parallel array of texture types + +// open materials.txt, get size, alloc space, +// save in array. Only works first time called, +// ignored on subsequent calls. + +static char *memfgets( byte *pMemFile, int fileSize, int &filePos, char *pBuffer, int bufferSize ) +{ + // Bullet-proofing + if ( !pMemFile || !pBuffer ) + return NULL; + + if ( filePos >= fileSize ) + return NULL; + + int i = filePos; + int last = fileSize; + + // fgets always NULL terminates, so only read bufferSize-1 characters + if ( last - filePos > (bufferSize-1) ) + last = filePos + (bufferSize-1); + + int stop = 0; + + // Stop at the next newline (inclusive) or end of buffer + while ( i < last && !stop ) + { + if ( pMemFile[i] == '\n' ) + stop = 1; + i++; + } + + + // If we actually advanced the pointer, copy it over + if ( i != filePos ) + { + // We read in size bytes + int size = i - filePos; + // copy it out + memcpy( pBuffer, pMemFile + filePos, sizeof(byte)*size ); + + // If the buffer isn't full, terminate (this is always true) + if ( size < bufferSize ) + pBuffer[size] = 0; + + // Update file pointer + filePos = i; + return pBuffer; + } + + // No data read, bail + return NULL; +} + + +void TEXTURETYPE_Init() +{ + char buffer[512]; + int i, j; + byte *pMemFile; + int fileSize, filePos; + + if (fTextureTypeInit) + return; + + memset(&(grgszTextureName[0][0]), 0, CTEXTURESMAX * CBTEXTURENAMEMAX); + memset(grgchTextureType, 0, CTEXTURESMAX); + + gcTextures = 0; + memset(buffer, 0, 512); + + pMemFile = g_engfuncs.pfnLoadFileForMe( "sound/materials.txt", &fileSize ); + if ( !pMemFile ) + return; + + // for each line in the file... + while (memfgets(pMemFile, fileSize, filePos, buffer, 511) != NULL && (gcTextures < CTEXTURESMAX)) + { + // skip whitespace + i = 0; + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // skip comment lines + if (buffer[i] == '/' || !isalpha(buffer[i])) + continue; + + // get texture type + grgchTextureType[gcTextures] = toupper(buffer[i++]); + + // skip whitespace + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // get sentence name + j = i; + while (buffer[j] && !isspace(buffer[j])) + j++; + + if (!buffer[j]) + continue; + + // null-terminate name and save in sentences array + j = min (j, CBTEXTURENAMEMAX-1+i); + buffer[j] = 0; + strcpy(&(grgszTextureName[gcTextures++][0]), &(buffer[i])); + } + + g_engfuncs.pfnFreeFile( pMemFile ); + + fTextureTypeInit = TRUE; +} + +// given texture name, find texture type +// if not found, return type 'concrete' + +// NOTE: this routine should ONLY be called if the +// current texture under the player changes! + +char TEXTURETYPE_Find(char *name) +{ + // CONSIDER: pre-sort texture names and perform faster binary search here + + for (int i = 0; i < gcTextures; i++) + { + if (!_strnicmp(name, &(grgszTextureName[i][0]), CBTEXTURENAMEMAX-1)) + return (grgchTextureType[i]); + } + + return CHAR_TEX_CONCRETE; +} + +// play a strike sound based on the texture that was hit by the attack traceline. VecSrc/VecEnd are the +// original traceline endpoints used by the attacker, iBulletType is the type of bullet that hit the texture. +// returns volume of strike instrument (crowbar) to play + +float TEXTURETYPE_PlaySound(TraceResult *ptr, Vector vecSrc, Vector vecEnd, int iBulletType) +{ +// hit the world, try to play sound based on texture material type + + char chTextureType; + float fvol; + float fvolbar; + char szbuffer[64]; + const char *pTextureName; + float rgfl1[3]; + float rgfl2[3]; + char *rgsz[4]; + int cnt; + float fattn = ATTN_NORM; + + if ( !g_pGameRules->PlayTextureSounds() ) + return 0.0; + + CBaseEntity *pEntity = CBaseEntity::Instance(ptr->pHit); + + chTextureType = 0; + + if (pEntity && pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE) + // hit body + chTextureType = CHAR_TEX_FLESH; + else + { + // hit world + + // find texture under strike, get material type + + // copy trace vector into array for trace_texture + + vecSrc.CopyToArray(rgfl1); + vecEnd.CopyToArray(rgfl2); + + // get texture from entity or world (world is ent(0)) + if (pEntity) + pTextureName = TRACE_TEXTURE( ENT(pEntity->pev), rgfl1, rgfl2 ); + else + pTextureName = TRACE_TEXTURE( ENT(0), rgfl1, rgfl2 ); + + if ( pTextureName ) + { + // strip leading '-0' or '+0~' or '{' or '!' + if (*pTextureName == '-' || *pTextureName == '+') + pTextureName += 2; + + if (*pTextureName == '{' || *pTextureName == '!' || *pTextureName == '~' || *pTextureName == ' ') + pTextureName++; + // '}}' + strcpy(szbuffer, pTextureName); + szbuffer[CBTEXTURENAMEMAX - 1] = 0; + + // ALERT ( at_console, "texture hit: %s\n", szbuffer); + + // get texture type + chTextureType = TEXTURETYPE_Find(szbuffer); + } + } + + switch (chTextureType) + { + default: + case CHAR_TEX_CONCRETE: fvol = 0.9; fvolbar = 0.6; + rgsz[0] = "player/pl_step1.wav"; + rgsz[1] = "player/pl_step2.wav"; + cnt = 2; + break; + case CHAR_TEX_METAL: fvol = 0.9; fvolbar = 0.3; + rgsz[0] = "player/pl_metal1.wav"; + rgsz[1] = "player/pl_metal2.wav"; + cnt = 2; + break; + case CHAR_TEX_DIRT: fvol = 0.9; fvolbar = 0.1; + rgsz[0] = "player/pl_dirt1.wav"; + rgsz[1] = "player/pl_dirt2.wav"; + rgsz[2] = "player/pl_dirt3.wav"; + cnt = 3; + break; + case CHAR_TEX_VENT: fvol = 0.5; fvolbar = 0.3; + rgsz[0] = "player/pl_duct1.wav"; + rgsz[1] = "player/pl_duct1.wav"; + cnt = 2; + break; + case CHAR_TEX_GRATE: fvol = 0.9; fvolbar = 0.5; + rgsz[0] = "player/pl_grate1.wav"; + rgsz[1] = "player/pl_grate4.wav"; + cnt = 2; + break; + case CHAR_TEX_TILE: fvol = 0.8; fvolbar = 0.2; + rgsz[0] = "player/pl_tile1.wav"; + rgsz[1] = "player/pl_tile3.wav"; + rgsz[2] = "player/pl_tile2.wav"; + rgsz[3] = "player/pl_tile4.wav"; + cnt = 4; + break; + case CHAR_TEX_SLOSH: fvol = 0.9; fvolbar = 0.0; + rgsz[0] = "player/pl_slosh1.wav"; + rgsz[1] = "player/pl_slosh3.wav"; + rgsz[2] = "player/pl_slosh2.wav"; + rgsz[3] = "player/pl_slosh4.wav"; + cnt = 4; + break; + case CHAR_TEX_WOOD: fvol = 0.9; fvolbar = 0.2; + rgsz[0] = "debris/wood1.wav"; + rgsz[1] = "debris/wood2.wav"; + rgsz[2] = "debris/wood3.wav"; + cnt = 3; + break; + case CHAR_TEX_GLASS: + case CHAR_TEX_COMPUTER: + fvol = 0.8; fvolbar = 0.2; + rgsz[0] = "debris/glass1.wav"; + rgsz[1] = "debris/glass2.wav"; + rgsz[2] = "debris/glass3.wav"; + cnt = 3; + break; + case CHAR_TEX_FLESH: + if (iBulletType == BULLET_PLAYER_CROWBAR) + return 0.0; // crowbar already makes this sound + fvol = 1.0; fvolbar = 0.2; + rgsz[0] = "weapons/bullet_hit1.wav"; + rgsz[1] = "weapons/bullet_hit2.wav"; + fattn = 1.0; + cnt = 2; + break; + } + + // did we hit a breakable? + + if (pEntity && FClassnameIs(pEntity->pev, "func_breakable")) + { + // drop volumes, the object will already play a damaged sound + fvol /= 1.5; + fvolbar /= 2.0; + } + else if (chTextureType == CHAR_TEX_COMPUTER) + { + // play random spark if computer + + if ( ptr->flFraction != 1.0 && RANDOM_LONG(0,1)) + { + UTIL_Sparks( ptr->vecEndPos ); + + float flVolume = RANDOM_FLOAT ( 0.7 , 1.0 );//random volume range + switch ( RANDOM_LONG(0,1) ) + { + case 0: UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, "buttons/spark5.wav", flVolume, ATTN_NORM, 0, 100); break; + case 1: UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, "buttons/spark6.wav", flVolume, ATTN_NORM, 0, 100); break; + // case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM); break; + // case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM); break; + } + } + } + + // play material hit sound + UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, rgsz[RANDOM_LONG(0,cnt-1)], fvol, fattn, 0, 96 + RANDOM_LONG(0,0xf)); + //EMIT_SOUND_DYN( ENT(m_pPlayer->pev), CHAN_WEAPON, rgsz[RANDOM_LONG(0,cnt-1)], fvol, ATTN_NORM, 0, 96 + RANDOM_LONG(0,0xf)); + + return fvolbar; +} + +// =================================================================================== +// +// Speaker class. Used for announcements per level, for door lock/unlock spoken voice. +// + +class CSpeaker : public CBaseEntity +{ +public: + void KeyValue( KeyValueData* pkvd); + void Spawn( void ); + void Precache( void ); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT SpeakerThink( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + int m_preset; // preset number +}; + +LINK_ENTITY_TO_CLASS( speaker, CSpeaker ); +TYPEDESCRIPTION CSpeaker::m_SaveData[] = +{ + DEFINE_FIELD( CSpeaker, m_preset, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CSpeaker, CBaseEntity ); + +// +// ambient_generic - general-purpose user-defined static sound +// +void CSpeaker :: Spawn( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + + if ( !m_preset && (FStringNull( pev->message ) || strlen( szSoundFile ) < 1 )) + { + ALERT( at_error, "SPEAKER with no Level/Sentence! at: %f, %f, %f\n", pev->origin.x, pev->origin.y, pev->origin.z ); + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CSpeaker::SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + + SetThink(&CSpeaker::SpeakerThink); + pev->nextthink = 0.0; + + // allow on/off switching via 'use' function. + + SetUse ( &CSpeaker::ToggleUse ); + + Precache( ); +} + +#define ANNOUNCE_MINUTES_MIN 0.25 +#define ANNOUNCE_MINUTES_MAX 2.25 + +void CSpeaker :: Precache( void ) +{ + if ( !FBitSet (pev->spawnflags, SPEAKER_START_SILENT ) ) + // set first announcement time for random n second + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(5.0, 15.0); +} +void CSpeaker :: SpeakerThink( void ) +{ + char* szSoundFile; + float flvolume = pev->health * 0.1; + float flattenuation = 0.3; + int flags = 0; + int pitch = 100; + + + // Wait for the talkmonster to finish first. + if (gpGlobals->time <= CTalkMonster::g_talkWaitTime) + { + pev->nextthink = CTalkMonster::g_talkWaitTime + RANDOM_FLOAT( 5, 10 ); + return; + } + + if (m_preset) + { + // go lookup preset text, assign szSoundFile + switch (m_preset) + { + case 1: szSoundFile = "C1A0_"; break; + case 2: szSoundFile = "C1A1_"; break; + case 3: szSoundFile = "C1A2_"; break; + case 4: szSoundFile = "C1A3_"; break; + case 5: szSoundFile = "C1A4_"; break; + case 6: szSoundFile = "C2A1_"; break; + case 7: szSoundFile = "C2A2_"; break; + case 8: szSoundFile = "C2A3_"; break; + case 9: szSoundFile = "C2A4_"; break; + case 10: szSoundFile = "C2A5_"; break; + case 11: szSoundFile = "C3A1_"; break; + case 12: szSoundFile = "C3A2_"; break; + } + } else + szSoundFile = (char*) STRING(pev->message); + + if (szSoundFile[0] == '!') + { + // play single sentence, one shot + UTIL_EmitAmbientSound ( ENT(pev), pev->origin, szSoundFile, + flvolume, flattenuation, flags, pitch); + + // shut off and reset + pev->nextthink = 0.0; + } + else + { + // make random announcement from sentence group + + if (SENTENCEG_PlayRndSz(ENT(pev), szSoundFile, flvolume, flattenuation, flags, pitch) < 0) + ALERT(at_console, "Level Design Error!\nSPEAKER has bad sentence group name: %s\n",szSoundFile); + + // set next announcement time for random 5 to 10 minute delay + pev->nextthink = gpGlobals->time + + RANDOM_FLOAT(ANNOUNCE_MINUTES_MIN * 60.0, ANNOUNCE_MINUTES_MAX * 60.0); + + CTalkMonster::g_talkWaitTime = gpGlobals->time + 5; // time delay until it's ok to speak: used so that two NPCs don't talk at once + } + + return; +} + + +// +// ToggleUse - if an announcement is pending, cancel it. If no announcement is pending, start one. +// +void CSpeaker :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int fActive = (pev->nextthink > 0.0); + + // fActive is TRUE only if an announcement is pending + + if ( useType != USE_TOGGLE ) + { + // ignore if we're just turning something on that's already on, or + // turning something off that's already off. + if ( (fActive && useType == USE_ON) || (!fActive && useType == USE_OFF) ) + return; + } + + if ( useType == USE_ON ) + { + // turn on announcements + pev->nextthink = gpGlobals->time + 0.1; + return; + } + + if ( useType == USE_OFF ) + { + // turn off announcements + pev->nextthink = 0.0; + return; + + } + + // Toggle announcements + + + if ( fActive ) + { + // turn off announcements + pev->nextthink = 0.0; + } + else + { + // turn on announcements + pev->nextthink = gpGlobals->time + 0.1; + } +} + +// KeyValue - load keyvalue pairs into member data +// NOTE: called BEFORE spawn! + +void CSpeaker :: KeyValue( KeyValueData *pkvd ) +{ + + // preset + if (FStrEq(pkvd->szKeyName, "preset")) + { + m_preset = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} diff --git a/ricochet/dlls/soundent.cpp b/ricochet/dlls/soundent.cpp new file mode 100644 index 0000000..65dac45 --- /dev/null +++ b/ricochet/dlls/soundent.cpp @@ -0,0 +1,379 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "soundent.h" + + +LINK_ENTITY_TO_CLASS( soundent, CSoundEnt ); + +CSoundEnt *pSoundEnt; + +//========================================================= +// CSound - Clear - zeros all fields for a sound +//========================================================= +void CSound :: Clear ( void ) +{ + m_vecOrigin = g_vecZero; + m_iType = 0; + m_iVolume = 0; + m_flExpireTime = 0; + m_iNext = SOUNDLIST_EMPTY; + m_iNextAudible = 0; +} + +//========================================================= +// Reset - clears the volume, origin, and type for a sound, +// but doesn't expire or unlink it. +//========================================================= +void CSound :: Reset ( void ) +{ + m_vecOrigin = g_vecZero; + m_iType = 0; + m_iVolume = 0; + m_iNext = SOUNDLIST_EMPTY; +} + +//========================================================= +// FIsSound - returns TRUE if the sound is an Audible sound +//========================================================= +BOOL CSound :: FIsSound ( void ) +{ + if ( m_iType & ( bits_SOUND_COMBAT | bits_SOUND_WORLD | bits_SOUND_PLAYER | bits_SOUND_DANGER ) ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// FIsScent - returns TRUE if the sound is actually a scent +//========================================================= +BOOL CSound :: FIsScent ( void ) +{ + if ( m_iType & ( bits_SOUND_CARCASS | bits_SOUND_MEAT | bits_SOUND_GARBAGE ) ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// Spawn +//========================================================= +void CSoundEnt :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + Initialize(); + + pev->nextthink = gpGlobals->time + 1; +} + +//========================================================= +// Think - at interval, the entire active sound list is checked +// for sounds that have ExpireTimes less than or equal +// to the current world time, and these sounds are deallocated. +//========================================================= +void CSoundEnt :: Think ( void ) +{ + int iSound; + int iPreviousSound; + + pev->nextthink = gpGlobals->time + 0.3;// how often to check the sound list. + + iPreviousSound = SOUNDLIST_EMPTY; + iSound = m_iActiveSound; + + while ( iSound != SOUNDLIST_EMPTY ) + { + if ( m_SoundPool[ iSound ].m_flExpireTime <= gpGlobals->time && m_SoundPool[ iSound ].m_flExpireTime != SOUND_NEVER_EXPIRE ) + { + int iNext = m_SoundPool[ iSound ].m_iNext; + + // move this sound back into the free list + FreeSound( iSound, iPreviousSound ); + + iSound = iNext; + } + else + { + iPreviousSound = iSound; + iSound = m_SoundPool[ iSound ].m_iNext; + } + } + + if ( m_fShowReport ) + { + ALERT ( at_aiconsole, "Soundlist: %d / %d (%d)\n", ISoundsInList( SOUNDLISTTYPE_ACTIVE ),ISoundsInList( SOUNDLISTTYPE_FREE ), ISoundsInList( SOUNDLISTTYPE_ACTIVE ) - m_cLastActiveSounds ); + m_cLastActiveSounds = ISoundsInList ( SOUNDLISTTYPE_ACTIVE ); + } + +} + +//========================================================= +// Precache - dummy function +//========================================================= +void CSoundEnt :: Precache ( void ) +{ +} + +//========================================================= +// FreeSound - clears the passed active sound and moves it +// to the top of the free list. TAKE CARE to only call this +// function for sounds in the Active list!! +//========================================================= +void CSoundEnt :: FreeSound ( int iSound, int iPrevious ) +{ + if ( !pSoundEnt ) + { + // no sound ent! + return; + } + + if ( iPrevious != SOUNDLIST_EMPTY ) + { + // iSound is not the head of the active list, so + // must fix the index for the Previous sound +// pSoundEnt->m_SoundPool[ iPrevious ].m_iNext = m_SoundPool[ iSound ].m_iNext; + pSoundEnt->m_SoundPool[ iPrevious ].m_iNext = pSoundEnt->m_SoundPool[ iSound ].m_iNext; + } + else + { + // the sound we're freeing IS the head of the active list. + pSoundEnt->m_iActiveSound = pSoundEnt->m_SoundPool [ iSound ].m_iNext; + } + + // make iSound the head of the Free list. + pSoundEnt->m_SoundPool[ iSound ].m_iNext = pSoundEnt->m_iFreeSound; + pSoundEnt->m_iFreeSound = iSound; +} + +//========================================================= +// IAllocSound - moves a sound from the Free list to the +// Active list returns the index of the alloc'd sound +//========================================================= +int CSoundEnt :: IAllocSound( void ) +{ + int iNewSound; + + if ( m_iFreeSound == SOUNDLIST_EMPTY ) + { + // no free sound! + ALERT ( at_console, "Free Sound List is full!\n" ); + return SOUNDLIST_EMPTY; + } + + // there is at least one sound available, so move it to the + // Active sound list, and return its SoundPool index. + + iNewSound = m_iFreeSound;// copy the index of the next free sound + + m_iFreeSound = m_SoundPool[ m_iFreeSound ].m_iNext;// move the index down into the free list. + + m_SoundPool[ iNewSound ].m_iNext = m_iActiveSound;// point the new sound at the top of the active list. + + m_iActiveSound = iNewSound;// now make the new sound the top of the active list. You're done. + + return iNewSound; +} + +//========================================================= +// InsertSound - Allocates a free sound and fills it with +// sound info. +//========================================================= +void CSoundEnt :: InsertSound ( int iType, const Vector &vecOrigin, int iVolume, float flDuration ) +{ + int iThisSound; + + if ( !pSoundEnt ) + { + // no sound ent! + return; + } + + iThisSound = pSoundEnt->IAllocSound(); + + if ( iThisSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_console, "Could not AllocSound() for InsertSound() (DLL)\n" ); + return; + } + + pSoundEnt->m_SoundPool[ iThisSound ].m_vecOrigin = vecOrigin; + pSoundEnt->m_SoundPool[ iThisSound ].m_iType = iType; + pSoundEnt->m_SoundPool[ iThisSound ].m_iVolume = iVolume; + pSoundEnt->m_SoundPool[ iThisSound ].m_flExpireTime = gpGlobals->time + flDuration; +} + +//========================================================= +// Initialize - clears all sounds and moves them into the +// free sound list. +//========================================================= +void CSoundEnt :: Initialize ( void ) +{ + int i; + int iSound; + + m_cLastActiveSounds; + m_iFreeSound = 0; + m_iActiveSound = SOUNDLIST_EMPTY; + + for ( i = 0 ; i < MAX_WORLD_SOUNDS ; i++ ) + {// clear all sounds, and link them into the free sound list. + m_SoundPool[ i ].Clear(); + m_SoundPool[ i ].m_iNext = i + 1; + } + + m_SoundPool[ i - 1 ].m_iNext = SOUNDLIST_EMPTY;// terminate the list here. + + + // now reserve enough sounds for each client + for ( i = 0 ; i < gpGlobals->maxClients ; i++ ) + { + iSound = pSoundEnt->IAllocSound(); + + if ( iSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_console, "Could not AllocSound() for Client Reserve! (DLL)\n" ); + return; + } + + pSoundEnt->m_SoundPool[ iSound ].m_flExpireTime = SOUND_NEVER_EXPIRE; + } + + if ( CVAR_GET_FLOAT("displaysoundlist") == 1 ) + { + m_fShowReport = TRUE; + } + else + { + m_fShowReport = FALSE; + } +} + +//========================================================= +// ISoundsInList - returns the number of sounds in the desired +// sound list. +//========================================================= +int CSoundEnt :: ISoundsInList ( int iListType ) +{ + int i; + int iThisSound; + + if ( iListType == SOUNDLISTTYPE_FREE ) + { + iThisSound = m_iFreeSound; + } + else if ( iListType == SOUNDLISTTYPE_ACTIVE ) + { + iThisSound = m_iActiveSound; + } + else + { + ALERT ( at_console, "Unknown Sound List Type!\n" ); + } + + if ( iThisSound == SOUNDLIST_EMPTY ) + { + return 0; + } + + i = 0; + + while ( iThisSound != SOUNDLIST_EMPTY ) + { + i++; + + iThisSound = m_SoundPool[ iThisSound ].m_iNext; + } + + return i; +} + +//========================================================= +// ActiveList - returns the head of the active sound list +//========================================================= +int CSoundEnt :: ActiveList ( void ) +{ + if ( !pSoundEnt ) + { + return SOUNDLIST_EMPTY; + } + + return pSoundEnt->m_iActiveSound; +} + +//========================================================= +// FreeList - returns the head of the free sound list +//========================================================= +int CSoundEnt :: FreeList ( void ) +{ + if ( !pSoundEnt ) + { + return SOUNDLIST_EMPTY; + } + + return pSoundEnt->m_iFreeSound; +} + +//========================================================= +// SoundPointerForIndex - returns a pointer to the instance +// of CSound at index's position in the sound pool. +//========================================================= +CSound* CSoundEnt :: SoundPointerForIndex( int iIndex ) +{ + if ( !pSoundEnt ) + { + return NULL; + } + + if ( iIndex > ( MAX_WORLD_SOUNDS - 1 ) ) + { + ALERT ( at_console, "SoundPointerForIndex() - Index too large!\n" ); + return NULL; + } + + if ( iIndex < 0 ) + { + ALERT ( at_console, "SoundPointerForIndex() - Index < 0!\n" ); + return NULL; + } + + return &pSoundEnt->m_SoundPool[ iIndex ]; +} + +//========================================================= +// Clients are numbered from 1 to MAXCLIENTS, but the client +// reserved sounds in the soundlist are from 0 to MAXCLIENTS - 1, +// so this function ensures that a client gets the proper index +// to his reserved sound in the soundlist. +//========================================================= +int CSoundEnt :: ClientSoundIndex ( edict_t *pClient ) +{ + int iReturn = ENTINDEX( pClient ) - 1; + +#ifdef _DEBUG + if ( iReturn < 0 || iReturn > gpGlobals->maxClients ) + { + ALERT ( at_console, "** ClientSoundIndex returning a bogus value! **\n" ); + } +#endif // _DEBUG + + return iReturn; +} \ No newline at end of file diff --git a/ricochet/dlls/soundent.h b/ricochet/dlls/soundent.h new file mode 100644 index 0000000..1967e97 --- /dev/null +++ b/ricochet/dlls/soundent.h @@ -0,0 +1,95 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// Soundent.h - the entity that spawns when the world +// spawns, and handles the world's active and free sound +// lists. +//========================================================= + +#define MAX_WORLD_SOUNDS 64 // maximum number of sounds handled by the world at one time. + +#define bits_SOUND_NONE 0 +#define bits_SOUND_COMBAT ( 1 << 0 )// gunshots, explosions +#define bits_SOUND_WORLD ( 1 << 1 )// door opening/closing, glass breaking +#define bits_SOUND_PLAYER ( 1 << 2 )// all noises generated by player. walking, shooting, falling, splashing +#define bits_SOUND_CARCASS ( 1 << 3 )// dead body +#define bits_SOUND_MEAT ( 1 << 4 )// gib or pork chop +#define bits_SOUND_DANGER ( 1 << 5 )// pending danger. Grenade that is about to explode, explosive barrel that is damaged, falling crate +#define bits_SOUND_GARBAGE ( 1 << 6 )// trash cans, banana peels, old fast food bags. + +#define bits_ALL_SOUNDS 0xFFFFFFFF + +#define SOUNDLIST_EMPTY -1 + +#define SOUNDLISTTYPE_FREE 1// identifiers passed to functions that can operate on either list, to indicate which list to operate on. +#define SOUNDLISTTYPE_ACTIVE 2 + +#define SOUND_NEVER_EXPIRE -1 // with this set as a sound's ExpireTime, the sound will never expire. + +//========================================================= +// CSound - an instance of a sound in the world. +//========================================================= +class CSound +{ +public: + + void Clear ( void ); + void Reset ( void ); + + Vector m_vecOrigin; // sound's location in space + int m_iType; // what type of sound this is + int m_iVolume; // how loud the sound is + float m_flExpireTime; // when the sound should be purged from the list + int m_iNext; // index of next sound in this list ( Active or Free ) + int m_iNextAudible; // temporary link that monsters use to build a list of audible sounds + + BOOL FIsSound( void ); + BOOL FIsScent( void ); +}; + +//========================================================= +// CSoundEnt - a single instance of this entity spawns when +// the world spawns. The SoundEnt's job is to update the +// world's Free and Active sound lists. +//========================================================= +class CSoundEnt : public CBaseEntity +{ +public: + + void Precache ( void ); + void Spawn( void ); + void Think( void ); + void Initialize ( void ); + + static void InsertSound ( int iType, const Vector &vecOrigin, int iVolume, float flDuration ); + static void FreeSound ( int iSound, int iPrevious ); + static int ActiveList( void );// return the head of the active list + static int FreeList( void );// return the head of the free list + static CSound* SoundPointerForIndex( int iIndex );// return a pointer for this index in the sound list + static int ClientSoundIndex ( edict_t *pClient ); + + BOOL IsEmpty( void ) { return m_iActiveSound == SOUNDLIST_EMPTY; } + int ISoundsInList ( int iListType ); + int IAllocSound ( void ); + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } + + int m_iFreeSound; // index of the first sound in the free sound list + int m_iActiveSound; // indes of the first sound in the active sound list + int m_cLastActiveSounds; // keeps track of the number of active sounds at the last update. (for diagnostic work) + BOOL m_fShowReport; // if true, dump information about free/active sounds. + +private: + CSound m_SoundPool[ MAX_WORLD_SOUNDS ]; +}; diff --git a/ricochet/dlls/spectator.cpp b/ricochet/dlls/spectator.cpp new file mode 100644 index 0000000..7d7f4a8 --- /dev/null +++ b/ricochet/dlls/spectator.cpp @@ -0,0 +1,149 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// CBaseSpectator + +// YWB: UNDONE + +// Spectator functions +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "spectator.h" + +/* +=========== +SpectatorConnect + +called when a spectator connects to a server +============ +*/ +void CBaseSpectator::SpectatorConnect(void) +{ + pev->flags = FL_SPECTATOR; + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NOCLIP; + + m_pGoalEnt = NULL; +} + +/* +=========== +SpectatorDisconnect + +called when a spectator disconnects from a server +============ +*/ +void CBaseSpectator::SpectatorDisconnect(void) +{ +} + +/* +================ +SpectatorImpulseCommand + +Called by SpectatorThink if the spectator entered an impulse +================ +*/ +void CBaseSpectator::SpectatorImpulseCommand(void) +{ + static edict_t *pGoal = NULL; + edict_t *pPreviousGoal; + edict_t *pCurrentGoal; + BOOL bFound; + + switch (pev->impulse) + { + case 1: + // teleport the spectator to the next spawn point + // note that if the spectator is tracking, this doesn't do + // much + pPreviousGoal = pGoal; + pCurrentGoal = pGoal; + // Start at the current goal, skip the world, and stop if we looped + // back around + + bFound = FALSE; + while (1) + { + pCurrentGoal = FIND_ENTITY_BY_CLASSNAME(pCurrentGoal, "info_player_deathmatch"); + // Looped around, failure + if (pCurrentGoal == pPreviousGoal) + { + ALERT(at_console, "Could not find a spawn spot.\n"); + break; + } + // Found a non-world entity, set success, otherwise, look for the next one. + if (!FNullEnt(pCurrentGoal)) + { + bFound = TRUE; + break; + } + } + + if (!bFound) // Didn't find a good spot. + break; + + pGoal = pCurrentGoal; + UTIL_SetOrigin( pev, pGoal->v.origin ); + pev->angles = pGoal->v.angles; + pev->fixangle = FALSE; + break; + default: + ALERT(at_console, "Unknown spectator impulse\n"); + break; + } + + pev->impulse = 0; +} + +/* +================ +SpectatorThink + +Called every frame after physics are run +================ +*/ +void CBaseSpectator::SpectatorThink(void) +{ + if (!(pev->flags & FL_SPECTATOR)) + { + pev->flags = FL_SPECTATOR; + } + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NOCLIP; + + if (pev->impulse) + SpectatorImpulseCommand(); +} + +/* +=========== +Spawn + + Called when spectator is initialized: + UNDONE: Is this actually being called because spectators are not allocated in normal fashion? +============ +*/ +void CBaseSpectator::Spawn() +{ + pev->flags = FL_SPECTATOR; + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NOCLIP; + + m_pGoalEnt = NULL; +} diff --git a/ricochet/dlls/spectator.h b/ricochet/dlls/spectator.h new file mode 100644 index 0000000..cf27d0c --- /dev/null +++ b/ricochet/dlls/spectator.h @@ -0,0 +1,27 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Spectator.h + +class CBaseSpectator : public CBaseEntity +{ +public: + void Spawn(); + void SpectatorConnect(void); + void SpectatorDisconnect(void); + void SpectatorThink(void); + +private: + void SpectatorImpulseCommand(void); +}; diff --git a/ricochet/dlls/subs.cpp b/ricochet/dlls/subs.cpp new file mode 100644 index 0000000..b9cc133 --- /dev/null +++ b/ricochet/dlls/subs.cpp @@ -0,0 +1,582 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== subs.cpp ======================================================== + + frequently used global functions + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "nodes.h" +#include "doors.h" + +extern CGraph WorldGraph; + +extern BOOL FEntIsVisible(entvars_t* pev, entvars_t* pevTarget); + +extern DLL_GLOBAL int g_iSkillLevel; + + +// Landmark class +void CPointEntity :: Spawn( void ) +{ + pev->solid = SOLID_NOT; +// UTIL_SetSize(pev, g_vecZero, g_vecZero); +} + + +class CNullEntity : public CBaseEntity +{ +public: + void Spawn( void ); +}; + + +// Null Entity, remove on startup +void CNullEntity :: Spawn( void ) +{ + REMOVE_ENTITY(ENT(pev)); +} +LINK_ENTITY_TO_CLASS(info_null,CNullEntity); + +class CBaseDMStart : public CPointEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + BOOL IsTriggered( CBaseEntity *pEntity ); + +private: + int m_iPitch; +}; + +// These are the new entry points to entities. +LINK_ENTITY_TO_CLASS(info_player_deathmatch,CBaseDMStart); +LINK_ENTITY_TO_CLASS(info_player_start,CPointEntity); +LINK_ENTITY_TO_CLASS(info_player_spectator,CBaseDMStart); +LINK_ENTITY_TO_CLASS(info_landmark,CPointEntity); + +void CBaseDMStart::Spawn( void ) +{ + pev->angles.x = -m_iPitch; +} + +void CBaseDMStart::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "master")) + { + pev->netname = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitch")) + { + m_iPitch = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +BOOL CBaseDMStart::IsTriggered( CBaseEntity *pEntity ) +{ + BOOL master = UTIL_IsMasterTriggered( pev->netname, pEntity ); + + return master; +} + +// This updates global tables that need to know about entities being removed +void CBaseEntity::UpdateOnRemove( void ) +{ + int i; + + if ( FBitSet( pev->flags, FL_GRAPHED ) ) + { + // this entity was a LinkEnt in the world node graph, so we must remove it from + // the graph since we are removing it from the world. + for ( i = 0 ; i < WorldGraph.m_cLinks ; i++ ) + { + if ( WorldGraph.m_pLinkPool [ i ].m_pLinkEnt == pev ) + { + // if this link has a link ent which is the same ent that is removing itself, remove it! + WorldGraph.m_pLinkPool [ i ].m_pLinkEnt = NULL; + } + } + } + if ( pev->globalname ) + gGlobalState.EntitySetState( pev->globalname, GLOBAL_DEAD ); +} + +// Convenient way to delay removing oneself +void CBaseEntity :: SUB_Remove( void ) +{ + UpdateOnRemove(); + if (pev->health > 0) + { + // this situation can screw up monsters who can't tell their entity pointers are invalid. + pev->health = 0; + ALERT( at_aiconsole, "SUB_Remove called on entity with health > 0\n"); + } + + REMOVE_ENTITY(ENT(pev)); +} + + +// Convenient way to explicitly do nothing (passed to functions that require a method) +void CBaseEntity :: SUB_DoNothing( void ) +{ +} + + +// Global Savedata for Delay +TYPEDESCRIPTION CBaseDelay::m_SaveData[] = +{ + DEFINE_FIELD( CBaseDelay, m_flDelay, FIELD_FLOAT ), + DEFINE_FIELD( CBaseDelay, m_iszKillTarget, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CBaseDelay, CBaseEntity ); + +void CBaseDelay :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "delay")) + { + m_flDelay = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "killtarget")) + { + m_iszKillTarget = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + { + CBaseEntity::KeyValue( pkvd ); + } +} + + +/* +============================== +SUB_UseTargets + +If self.delay is set, a DelayedUse entity will be created that will actually +do the SUB_UseTargets after that many seconds have passed. + +Removes all entities with a targetname that match self.killtarget, +and removes them, so some events can remove other triggers. + +Search for (string)targetname in all entities that +match (string)self.target and call their .use function (if they have one) + +============================== +*/ +void CBaseEntity :: SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ) +{ + // + // fire targets + // + if (!FStringNull(pev->target)) + { + FireTargets( STRING(pev->target), pActivator, this, useType, value ); + } +} + + +void FireTargets( const char *targetName, CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + edict_t *pentTarget = NULL; + if ( !targetName ) + return; + + ALERT( at_aiconsole, "Firing: (%s)\n", targetName ); + + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, targetName); + if (FNullEnt(pentTarget)) + break; + + CBaseEntity *pTarget = CBaseEntity::Instance( pentTarget ); + if ( pTarget && !(pTarget->pev->flags & FL_KILLME) ) // Don't use dying ents + { + ALERT( at_aiconsole, "Found: %s, firing (%s)\n", STRING(pTarget->pev->classname), targetName ); + + // Discwar: Only fire ents that are in the same groupinfo as the activator + if ( pActivator && pActivator->pev->groupinfo && pTarget->pev->groupinfo ) + { + if ( pActivator->pev->groupinfo & pTarget->pev->groupinfo ) + pTarget->Use( pActivator, pCaller, useType, value ); + } + else + { + pTarget->Use( pActivator, pCaller, useType, value ); + } + } + } +} + +LINK_ENTITY_TO_CLASS( DelayedUse, CBaseDelay ); + + +void CBaseDelay :: SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ) +{ + // + // exit immediatly if we don't have a target or kill target + // + if (FStringNull(pev->target) && !m_iszKillTarget) + return; + + // + // check for a delay + // + if (m_flDelay != 0) + { + // create a temp object to fire at a later time + CBaseDelay *pTemp = GetClassPtr( (CBaseDelay *)NULL); + pTemp->pev->classname = MAKE_STRING("DelayedUse"); + + pTemp->pev->nextthink = gpGlobals->time + m_flDelay; + + pTemp->SetThink( &CBaseDelay::DelayThink ); + + // Save the useType + pTemp->pev->button = (int)useType; + pTemp->m_iszKillTarget = m_iszKillTarget; + pTemp->m_flDelay = 0; // prevent "recursion" + pTemp->pev->target = pev->target; + + // HACKHACK + // This wasn't in the release build of Half-Life. We should have moved m_hActivator into this class + // but changing member variable hierarchy would break save/restore without some ugly code. + // This code is not as ugly as that code + if ( pActivator && pActivator->IsPlayer() ) // If a player activates, then save it + { + pTemp->pev->owner = pActivator->edict(); + } + else + { + pTemp->pev->owner = NULL; + } + + return; + } + + // + // kill the killtargets + // + + if ( m_iszKillTarget ) + { + edict_t *pentKillTarget = NULL; + + ALERT( at_aiconsole, "KillTarget: %s\n", STRING(m_iszKillTarget) ); + pentKillTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING(m_iszKillTarget) ); + while ( !FNullEnt(pentKillTarget) ) + { + UTIL_Remove( CBaseEntity::Instance(pentKillTarget) ); + + ALERT( at_aiconsole, "killing %s\n", STRING( pentKillTarget->v.classname ) ); + pentKillTarget = FIND_ENTITY_BY_TARGETNAME( pentKillTarget, STRING(m_iszKillTarget) ); + } + } + + // + // fire targets + // + if (!FStringNull(pev->target)) + { + FireTargets( STRING(pev->target), pActivator, this, useType, value ); + } +} + + +/* +void CBaseDelay :: SUB_UseTargetsEntMethod( void ) +{ + SUB_UseTargets(pev); +} +*/ + +/* +QuakeEd only writes a single float for angles (bad idea), so up and down are +just constant angles. +*/ +void SetMovedir( entvars_t *pev ) +{ + if (pev->angles == Vector(0, -1, 0)) + { + pev->movedir = Vector(0, 0, 1); + } + else if (pev->angles == Vector(0, -2, 0)) + { + pev->movedir = Vector(0, 0, -1); + } + else + { + UTIL_MakeVectors(pev->angles); + pev->movedir = gpGlobals->v_forward; + } + + pev->angles = g_vecZero; +} + + + + +void CBaseDelay::DelayThink( void ) +{ + CBaseEntity *pActivator = NULL; + + if ( pev->owner != NULL ) // A player activated this on delay + { + pActivator = CBaseEntity::Instance( pev->owner ); + } + // The use type is cached (and stashed) in pev->button + SUB_UseTargets( pActivator, (USE_TYPE)pev->button, 0 ); + REMOVE_ENTITY(ENT(pev)); +} + + +// Global Savedata for Toggle +TYPEDESCRIPTION CBaseToggle::m_SaveData[] = +{ + DEFINE_FIELD( CBaseToggle, m_toggle_state, FIELD_INTEGER ), + DEFINE_FIELD( CBaseToggle, m_flActivateFinished, FIELD_TIME ), + DEFINE_FIELD( CBaseToggle, m_flMoveDistance, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flWait, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flLip, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flTWidth, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flTLength, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_vecPosition1, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_vecPosition2, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_vecAngle1, FIELD_VECTOR ), // UNDONE: Position could go through transition, but also angle? + DEFINE_FIELD( CBaseToggle, m_vecAngle2, FIELD_VECTOR ), // UNDONE: Position could go through transition, but also angle? + DEFINE_FIELD( CBaseToggle, m_cTriggersLeft, FIELD_INTEGER ), + DEFINE_FIELD( CBaseToggle, m_flHeight, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_hActivator, FIELD_EHANDLE ), + DEFINE_FIELD( CBaseToggle, m_pfnCallWhenMoveDone, FIELD_FUNCTION ), + DEFINE_FIELD( CBaseToggle, m_vecFinalDest, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_vecFinalAngle, FIELD_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_sMaster, FIELD_STRING), + DEFINE_FIELD( CBaseToggle, m_bitsDamageInflict, FIELD_INTEGER ), // damage type inflicted +}; +IMPLEMENT_SAVERESTORE( CBaseToggle, CBaseAnimating ); + + +void CBaseToggle::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "lip")) + { + m_flLip = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "master")) + { + m_sMaster = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "distance")) + { + m_flMoveDistance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + +/* +============= +LinearMove + +calculate pev->velocity and pev->nextthink to reach vecDest from +pev->origin traveling at flSpeed +=============== +*/ +void CBaseToggle :: LinearMove( Vector vecDest, float flSpeed ) +{ + ASSERTSZ(flSpeed != 0, "LinearMove: no speed is defined!"); +// ASSERTSZ(m_pfnCallWhenMoveDone != NULL, "LinearMove: no post-move function defined"); + + m_vecFinalDest = vecDest; + + // Already there? + if (vecDest == pev->origin) + { + LinearMoveDone(); + return; + } + + // set destdelta to the vector needed to move + Vector vecDestDelta = vecDest - pev->origin; + + // divide vector length by speed to get time to reach dest + float flTravelTime = vecDestDelta.Length() / flSpeed; + + // set nextthink to trigger a call to LinearMoveDone when dest is reached + pev->nextthink = pev->ltime + flTravelTime; + SetThink( &CBaseToggle::LinearMoveDone ); + + // scale the destdelta vector by the time spent traveling to get velocity + pev->velocity = vecDestDelta / flTravelTime; +} + + +/* +============ +After moving, set origin to exact final destination, call "move done" function +============ +*/ +void CBaseToggle :: LinearMoveDone( void ) +{ + UTIL_SetOrigin(pev, m_vecFinalDest); + pev->velocity = g_vecZero; + pev->nextthink = -1; + if ( m_pfnCallWhenMoveDone ) + (this->*m_pfnCallWhenMoveDone)(); +} + +BOOL CBaseToggle :: IsLockedByMaster( void ) +{ + if (m_sMaster && !UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + return TRUE; + else + return FALSE; +} + +/* +============= +AngularMove + +calculate pev->velocity and pev->nextthink to reach vecDest from +pev->origin traveling at flSpeed +Just like LinearMove, but rotational. +=============== +*/ +void CBaseToggle :: AngularMove( Vector vecDestAngle, float flSpeed ) +{ + ASSERTSZ(flSpeed != 0, "AngularMove: no speed is defined!"); +// ASSERTSZ(m_pfnCallWhenMoveDone != NULL, "AngularMove: no post-move function defined"); + + m_vecFinalAngle = vecDestAngle; + + // Already there? + if (vecDestAngle == pev->angles) + { + AngularMoveDone(); + return; + } + + // set destdelta to the vector needed to move + Vector vecDestDelta = vecDestAngle - pev->angles; + + // divide by speed to get time to reach dest + float flTravelTime = vecDestDelta.Length() / flSpeed; + + // set nextthink to trigger a call to AngularMoveDone when dest is reached + pev->nextthink = pev->ltime + flTravelTime; + SetThink( &CBaseToggle::AngularMoveDone ); + + // scale the destdelta vector by the time spent traveling to get velocity + pev->avelocity = vecDestDelta / flTravelTime; +} + + +/* +============ +After rotating, set angle to exact final angle, call "move done" function +============ +*/ +void CBaseToggle :: AngularMoveDone( void ) +{ + pev->angles = m_vecFinalAngle; + pev->avelocity = g_vecZero; + pev->nextthink = -1; + if ( m_pfnCallWhenMoveDone ) + (this->*m_pfnCallWhenMoveDone)(); +} + + +float CBaseToggle :: AxisValue( int flags, const Vector &angles ) +{ + if ( FBitSet(flags, SF_DOOR_ROTATE_Z) ) + return angles.z; + if ( FBitSet(flags, SF_DOOR_ROTATE_X) ) + return angles.x; + + return angles.y; +} + + +void CBaseToggle :: AxisDir( entvars_t *pev ) +{ + if ( FBitSet(pev->spawnflags, SF_DOOR_ROTATE_Z) ) + pev->movedir = Vector ( 0, 0, 1 ); // around z-axis + else if ( FBitSet(pev->spawnflags, SF_DOOR_ROTATE_X) ) + pev->movedir = Vector ( 1, 0, 0 ); // around x-axis + else + pev->movedir = Vector ( 0, 1, 0 ); // around y-axis +} + + +float CBaseToggle :: AxisDelta( int flags, const Vector &angle1, const Vector &angle2 ) +{ + if ( FBitSet (flags, SF_DOOR_ROTATE_Z) ) + return angle1.z - angle2.z; + + if ( FBitSet (flags, SF_DOOR_ROTATE_X) ) + return angle1.x - angle2.x; + + return angle1.y - angle2.y; +} + + +/* +============= +FEntIsVisible + +returns TRUE if the passed entity is visible to caller, even if not infront () +============= +*/ + BOOL +FEntIsVisible( + entvars_t* pev, + entvars_t* pevTarget) + { + Vector vecSpot1 = pev->origin + pev->view_ofs; + Vector vecSpot2 = pevTarget->origin + pevTarget->view_ofs; + TraceResult tr; + + UTIL_TraceLine(vecSpot1, vecSpot2, ignore_monsters, ENT(pev), &tr); + + if (tr.fInOpen && tr.fInWater) + return FALSE; // sight line crossed contents + + if (tr.flFraction == 1) + return TRUE; + + return FALSE; + } + + diff --git a/ricochet/dlls/talkmonster.h b/ricochet/dlls/talkmonster.h new file mode 100644 index 0000000..d8a92eb --- /dev/null +++ b/ricochet/dlls/talkmonster.h @@ -0,0 +1,26 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef TALKMONSTER_H +#define TALKMONSTER_H + +class CTalkMonster : public CBaseMonster +{ +public: + static float g_talkWaitTime; + +}; + +#endif //TALKMONSTER_H diff --git a/ricochet/dlls/teamplay_gamerules.cpp b/ricochet/dlls/teamplay_gamerules.cpp new file mode 100644 index 0000000..60338e4 --- /dev/null +++ b/ricochet/dlls/teamplay_gamerules.cpp @@ -0,0 +1,611 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.cpp +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "teamplay_gamerules.h" +#include "game.h" + +static char team_names[MAX_TEAMS][MAX_TEAMNAME_LENGTH]; +static int team_scores[MAX_TEAMS]; +static int num_teams = 0; + +extern DLL_GLOBAL BOOL g_fGameOver; + +CHalfLifeTeamplay :: CHalfLifeTeamplay() +{ + m_DisableDeathMessages = FALSE; + m_DisableDeathPenalty = FALSE; + + memset( team_names, 0, sizeof(team_names) ); + memset( team_scores, 0, sizeof(team_scores) ); + num_teams = 0; + + // Copy over the team from the server config + m_szTeamList[0] = 0; + + // Cache this because the team code doesn't want to deal with changing this in the middle of a game + strncpy( m_szTeamList, teamlist.string, TEAMPLAY_TEAMLISTLENGTH ); + + edict_t *pWorld = INDEXENT(0); + if ( pWorld && pWorld->v.team ) + { + if ( teamoverride.value ) + { + const char *pTeamList = STRING(pWorld->v.team); + if ( pTeamList && strlen(pTeamList) ) + { + strncpy( m_szTeamList, pTeamList, TEAMPLAY_TEAMLISTLENGTH ); + } + } + } + // Has the server set teams + if ( strlen( m_szTeamList ) ) + m_teamLimit = TRUE; + else + m_teamLimit = FALSE; + + RecountTeams(); +} + +extern cvar_t timeleft, fragsleft; + +#include "voice_gamemgr.h" +extern CVoiceGameMgr g_VoiceGameMgr; + +void CHalfLifeTeamplay :: Think ( void ) +{ + ///// Check game rules ///// + static int last_frags; + static int last_time; + + int frags_remaining = 0; + int time_remaining = 0; + + g_VoiceGameMgr.Update(gpGlobals->frametime); + + if ( g_fGameOver ) // someone else quit the game already + { + CHalfLifeMultiplay::Think(); + return; + } + + float flTimeLimit = CVAR_GET_FLOAT("mp_timelimit") * 60; + + time_remaining = (int)(flTimeLimit ? ( flTimeLimit - gpGlobals->time ) : 0); + + if ( flTimeLimit != 0 && gpGlobals->time >= flTimeLimit ) + { + GoToIntermission(); + return; + } + + float flFragLimit = fraglimit.value; + if ( flFragLimit ) + { + int bestfrags = 9999; + int remain; + + // check if any team is over the frag limit + for ( int i = 0; i < num_teams; i++ ) + { + if ( team_scores[i] >= flFragLimit ) + { + GoToIntermission(); + return; + } + + remain = flFragLimit - team_scores[i]; + if ( remain < bestfrags ) + { + bestfrags = remain; + } + } + frags_remaining = bestfrags; + } + + // Updates when frags change + if ( frags_remaining != last_frags ) + { + g_engfuncs.pfnCvar_DirectSet( &fragsleft, UTIL_VarArgs( "%i", frags_remaining ) ); + } + + // Updates once per second + if ( timeleft.value != last_time ) + { + g_engfuncs.pfnCvar_DirectSet( &timeleft, UTIL_VarArgs( "%i", time_remaining ) ); + } + + last_frags = frags_remaining; + last_time = time_remaining; +} + +//========================================================= +// ClientCommand +// the user has typed a command which is unrecognized by everything else; +// this check to see if the gamerules knows anything about the command +//========================================================= +BOOL CHalfLifeTeamplay :: ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) +{ + if(g_VoiceGameMgr.ClientCommand(pPlayer, pcmd)) + return TRUE; + + if ( FStrEq( pcmd, "menuselect" ) ) + { + if ( CMD_ARGC() < 2 ) + return TRUE; + + int slot = atoi( CMD_ARGV(1) ); + + // select the item from the current menu + + return TRUE; + } + + return FALSE; +} + +extern int gmsgGameMode; +extern int gmsgSayText; +extern int gmsgTeamInfo; + + +void CHalfLifeTeamplay :: UpdateGameMode( CBasePlayer *pPlayer ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgGameMode, NULL, pPlayer->edict() ); + WRITE_BYTE( 1 ); // game mode teamplay + MESSAGE_END(); +} + + +const char *CHalfLifeTeamplay::SetDefaultPlayerTeam( CBasePlayer *pPlayer ) +{ + // copy out the team name from the model + char *mdls = g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model" ); + strncpy( pPlayer->m_szTeamName, mdls, TEAM_NAME_LENGTH ); + + RecountTeams(); + + // update the current player of the team he is joining + if ( pPlayer->m_szTeamName[0] == '\0' || !IsValidTeam( pPlayer->m_szTeamName ) || defaultteam.value ) + { + const char *pTeamName = NULL; + + if ( defaultteam.value ) + { + pTeamName = team_names[0]; + } + else + { + pTeamName = TeamWithFewestPlayers(); + } + strncpy( pPlayer->m_szTeamName, pTeamName, TEAM_NAME_LENGTH ); + } + + return pPlayer->m_szTeamName; +} + + +//========================================================= +// InitHUD +//========================================================= +void CHalfLifeTeamplay::InitHUD( CBasePlayer *pPlayer ) +{ + SetDefaultPlayerTeam( pPlayer ); + CHalfLifeMultiplay::InitHUD( pPlayer ); + + RecountTeams(); + + char *mdls = g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model" ); + // update the current player of the team he is joining + char text[1024]; + if ( !strcmp( mdls, pPlayer->m_szTeamName ) ) + { + sprintf( text, "* you are on team \'%s\'\n", pPlayer->m_szTeamName ); + } + else + { + sprintf( text, "* assigned to team %s\n", pPlayer->m_szTeamName ); + } + + ChangePlayerTeam( pPlayer, pPlayer->m_szTeamName, FALSE, FALSE ); + UTIL_SayText( text, pPlayer ); + int clientIndex = pPlayer->entindex(); + RecountTeams(); + // update this player with all the other players team info + // loop through all active players and send their team info to the new client + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *plr = UTIL_PlayerByIndex( i ); + if ( plr && IsValidTeam( plr->TeamID() ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgTeamInfo, NULL, pPlayer->edict() ); + WRITE_BYTE( plr->entindex() ); + WRITE_STRING( plr->TeamID() ); + MESSAGE_END(); + } + } +} + + +void CHalfLifeTeamplay::ChangePlayerTeam( CBasePlayer *pPlayer, const char *pTeamName, BOOL bKill, BOOL bGib ) +{ + int damageFlags = DMG_GENERIC; + int clientIndex = pPlayer->entindex(); + + if ( !bGib ) + { + damageFlags |= DMG_NEVERGIB; + } + else + { + damageFlags |= DMG_ALWAYSGIB; + } + + if ( bKill ) + { + // kill the player, remove a death, and let them start on the new team + m_DisableDeathMessages = TRUE; + m_DisableDeathPenalty = TRUE; + + entvars_t *pevWorld = VARS( INDEXENT(0) ); + pPlayer->TakeDamage( pevWorld, pevWorld, 900, damageFlags ); + + m_DisableDeathMessages = FALSE; + m_DisableDeathPenalty = FALSE; + } + + // copy out the team name from the model + strncpy( pPlayer->m_szTeamName, pTeamName, TEAM_NAME_LENGTH ); + + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model", pPlayer->m_szTeamName ); + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "team", pPlayer->m_szTeamName ); + + // notify everyone's HUD of the team change + MESSAGE_BEGIN( MSG_ALL, gmsgTeamInfo ); + WRITE_BYTE( clientIndex ); + WRITE_STRING( pPlayer->m_szTeamName ); + MESSAGE_END(); +} + + +//========================================================= +// ClientUserInfoChanged +//========================================================= +void CHalfLifeTeamplay::ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ) +{ + char text[1024]; + + // prevent skin/color/model changes + char *mdls = g_engfuncs.pfnInfoKeyValue( infobuffer, "model" ); + + if ( !stricmp( mdls, pPlayer->m_szTeamName ) ) + return; + + if ( defaultteam.value ) + { + int clientIndex = pPlayer->entindex(); + + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model", pPlayer->m_szTeamName ); + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "team", pPlayer->m_szTeamName ); + sprintf( text, "* Not allowed to change teams in this game!\n" ); + UTIL_SayText( text, pPlayer ); + return; + } + + if ( defaultteam.value || !IsValidTeam( mdls ) ) + { + int clientIndex = pPlayer->entindex(); + + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model", pPlayer->m_szTeamName ); + sprintf( text, "* Can't change team to \'%s\'\n", mdls ); + UTIL_SayText( text, pPlayer ); + sprintf( text, "* Server limits teams to \'%s\'\n", m_szTeamList ); + UTIL_SayText( text, pPlayer ); + return; + } + + // notify everyone of the team change + sprintf( text, "* %s has changed to team \'%s\'\n", STRING(pPlayer->pev->netname), mdls ); + UTIL_SayTextAll( text, pPlayer ); + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" joined team \"%s\"\n", + STRING(pPlayer->pev->netname), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + pPlayer->m_szTeamName, + mdls ); + + ChangePlayerTeam( pPlayer, mdls, TRUE, TRUE ); + // recound stuff + RecountTeams( TRUE ); +} + +extern int gmsgDeathMsg; + +//========================================================= +// Deathnotice. +//========================================================= +void CHalfLifeTeamplay::DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pevInflictor ) +{ + if ( m_DisableDeathMessages ) + return; + + if ( pVictim && pKiller && pKiller->flags & FL_CLIENT ) + { + CBasePlayer *pk = (CBasePlayer*) CBaseEntity::Instance( pKiller ); + + if ( pk ) + { + if ( (pk != pVictim) && (PlayerRelationship( pVictim, pk ) == GR_TEAMMATE) ) + { + MESSAGE_BEGIN( MSG_ALL, gmsgDeathMsg ); + WRITE_BYTE( ENTINDEX(ENT(pKiller)) ); // the killer + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); // the victim + WRITE_STRING( "teammate" ); // flag this as a teammate kill + MESSAGE_END(); + return; + } + } + } + + CHalfLifeMultiplay::DeathNotice( pVictim, pKiller, pevInflictor ); +} + +//========================================================= +//========================================================= +void CHalfLifeTeamplay :: PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) +{ + if ( !m_DisableDeathPenalty ) + { + CHalfLifeMultiplay::PlayerKilled( pVictim, pKiller, pInflictor ); + RecountTeams(); + } +} + + +//========================================================= +// IsTeamplay +//========================================================= +BOOL CHalfLifeTeamplay::IsTeamplay( void ) +{ + return TRUE; +} + +BOOL CHalfLifeTeamplay::FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ) +{ + if ( pAttacker && PlayerRelationship( pPlayer, pAttacker ) == GR_TEAMMATE ) + { + // my teammate hit me. + if ( (CVAR_GET_FLOAT("mp_friendlyfire") == 0) && (pAttacker != pPlayer) ) + { + // friendly fire is off, and this hit came from someone other than myself, then don't get hurt + return FALSE; + } + } + + return CHalfLifeMultiplay::FPlayerCanTakeDamage( pPlayer, pAttacker ); +} + +//========================================================= +//========================================================= +int CHalfLifeTeamplay::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) +{ + // half life multiplay has a simple concept of Player Relationships. + // you are either on another player's team, or you are not. + if ( !pPlayer || !pTarget || !pTarget->IsPlayer() ) + return GR_NOTTEAMMATE; + + if ( (*GetTeamID(pPlayer) != '\0') && (*GetTeamID(pTarget) != '\0') && !stricmp( GetTeamID(pPlayer), GetTeamID(pTarget) ) ) + { + return GR_TEAMMATE; + } + + return GR_NOTTEAMMATE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeTeamplay::ShouldAutoAim( CBasePlayer *pPlayer, edict_t *target ) +{ + // always autoaim, unless target is a teammate + CBaseEntity *pTgt = CBaseEntity::Instance( target ); + if ( pTgt && pTgt->IsPlayer() ) + { + if ( PlayerRelationship( pPlayer, pTgt ) == GR_TEAMMATE ) + return FALSE; // don't autoaim at teammates + } + + return CHalfLifeMultiplay::ShouldAutoAim( pPlayer, target ); +} + +//========================================================= +//========================================================= +int CHalfLifeTeamplay::IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) +{ + if ( !pKilled ) + return 0; + + if ( !pAttacker ) + return 1; + + if ( pAttacker != pKilled && PlayerRelationship( pAttacker, pKilled ) == GR_TEAMMATE ) + return -1; + + return 1; +} + +//========================================================= +//========================================================= +const char *CHalfLifeTeamplay::GetTeamID( CBaseEntity *pEntity ) +{ + if ( pEntity == NULL || pEntity->pev == NULL ) + return ""; + + // return their team name + return pEntity->TeamID(); +} + + +int CHalfLifeTeamplay::GetTeamIndex( const char *pTeamName ) +{ + if ( pTeamName && *pTeamName != 0 ) + { + // try to find existing team + for ( int tm = 0; tm < num_teams; tm++ ) + { + if ( !stricmp( team_names[tm], pTeamName ) ) + return tm; + } + } + + return -1; // No match +} + + +const char *CHalfLifeTeamplay::GetIndexedTeamName( int teamIndex ) +{ + if ( teamIndex < 0 || teamIndex >= num_teams ) + return ""; + + return team_names[ teamIndex ]; +} + + +BOOL CHalfLifeTeamplay::IsValidTeam( const char *pTeamName ) +{ + if ( !m_teamLimit ) // Any team is valid if the teamlist isn't set + return TRUE; + + return ( GetTeamIndex( pTeamName ) != -1 ) ? TRUE : FALSE; +} + +const char *CHalfLifeTeamplay::TeamWithFewestPlayers( void ) +{ + int i; + int minPlayers = MAX_TEAMS; + int teamCount[ MAX_TEAMS ]; + char *pTeamName = NULL; + + memset( teamCount, 0, MAX_TEAMS * sizeof(int) ); + + // loop through all clients, count number of players on each team + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *plr = UTIL_PlayerByIndex( i ); + + if ( plr ) + { + int team = GetTeamIndex( plr->TeamID() ); + if ( team >= 0 ) + teamCount[team] ++; + } + } + + // Find team with least players + for ( i = 0; i < num_teams; i++ ) + { + if ( teamCount[i] < minPlayers ) + { + minPlayers = teamCount[i]; + pTeamName = team_names[i]; + } + } + + return pTeamName; +} + + +//========================================================= +//========================================================= +void CHalfLifeTeamplay::RecountTeams( bool bResendInfo ) +{ + char *pName; + char teamlist[TEAMPLAY_TEAMLISTLENGTH]; + + // loop through all teams, recounting everything + num_teams = 0; + + // Copy all of the teams from the teamlist + // make a copy because strtok is destructive + strcpy( teamlist, m_szTeamList ); + pName = teamlist; + pName = strtok( pName, ";" ); + while ( pName != NULL && *pName ) + { + if ( GetTeamIndex( pName ) < 0 ) + { + strcpy( team_names[num_teams], pName ); + num_teams++; + } + pName = strtok( NULL, ";" ); + } + + if ( num_teams < 2 ) + { + num_teams = 0; + m_teamLimit = FALSE; + } + + // Sanity check + memset( team_scores, 0, sizeof(team_scores) ); + + // loop through all clients + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *plr = UTIL_PlayerByIndex( i ); + + if ( plr ) + { + const char *pTeamName = plr->TeamID(); + // try add to existing team + int tm = GetTeamIndex( pTeamName ); + + if ( tm < 0 ) // no team match found + { + if ( !m_teamLimit ) + { + // add to new team + tm = num_teams; + num_teams++; + team_scores[tm] = 0; + strncpy( team_names[tm], pTeamName, MAX_TEAMNAME_LENGTH ); + } + } + + if ( tm >= 0 ) + { + team_scores[tm] += plr->pev->frags; + } + + if ( bResendInfo ) //Someone's info changed, let's send the team info again. + { + if ( plr && IsValidTeam( plr->TeamID() ) ) + { + MESSAGE_BEGIN( MSG_ALL, gmsgTeamInfo, NULL ); + WRITE_BYTE( plr->entindex() ); + WRITE_STRING( plr->TeamID() ); + MESSAGE_END(); + } + } + } + } +} diff --git a/ricochet/dlls/teamplay_gamerules.h b/ricochet/dlls/teamplay_gamerules.h new file mode 100644 index 0000000..86d0814 --- /dev/null +++ b/ricochet/dlls/teamplay_gamerules.h @@ -0,0 +1,57 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.h +// + +#define MAX_TEAMNAME_LENGTH 16 +#define MAX_TEAMS 32 + +#define TEAMPLAY_TEAMLISTLENGTH MAX_TEAMS*MAX_TEAMNAME_LENGTH + +class CHalfLifeTeamplay : public CHalfLifeMultiplay +{ +public: + CHalfLifeTeamplay(); + + virtual BOOL ClientCommand( CBasePlayer *pPlayer, const char *pcmd ); + virtual void ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ); + virtual BOOL IsTeamplay( void ); + virtual BOOL FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ); + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); + virtual const char *GetTeamID( CBaseEntity *pEntity ); + virtual BOOL ShouldAutoAim( CBasePlayer *pPlayer, edict_t *target ); + virtual int IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ); + virtual void InitHUD( CBasePlayer *pl ); + virtual void DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pevInflictor ); + virtual const char *GetGameDescription( void ) { return "HL Teamplay"; } // this is the game name that gets seen in the server browser + virtual void UpdateGameMode( CBasePlayer *pPlayer ); // the client needs to be informed of the current game mode + virtual void PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); + virtual void Think ( void ); + virtual int GetTeamIndex( const char *pTeamName ); + virtual const char *GetIndexedTeamName( int teamIndex ); + virtual BOOL IsValidTeam( const char *pTeamName ); + const char *SetDefaultPlayerTeam( CBasePlayer *pPlayer ); + virtual void ChangePlayerTeam( CBasePlayer *pPlayer, const char *pTeamName, BOOL bKill, BOOL bGib ); + +private: + void RecountTeams( bool bResendInfo = FALSE ); + const char *TeamWithFewestPlayers( void ); + + BOOL m_DisableDeathMessages; + BOOL m_DisableDeathPenalty; + BOOL m_teamLimit; // This means the server set only some teams as valid + char m_szTeamList[TEAMPLAY_TEAMLISTLENGTH]; +}; diff --git a/ricochet/dlls/trains.h b/ricochet/dlls/trains.h new file mode 100644 index 0000000..5912441 --- /dev/null +++ b/ricochet/dlls/trains.h @@ -0,0 +1,127 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef TRAINS_H +#define TRAINS_H + +// Tracktrain spawn flags +#define SF_TRACKTRAIN_NOPITCH 0x0001 +#define SF_TRACKTRAIN_NOCONTROL 0x0002 +#define SF_TRACKTRAIN_FORWARDONLY 0x0004 +#define SF_TRACKTRAIN_PASSABLE 0x0008 + +// Spawnflag for CPathTrack +#define SF_PATH_DISABLED 0x00000001 +#define SF_PATH_FIREONCE 0x00000002 +#define SF_PATH_ALTREVERSE 0x00000004 +#define SF_PATH_DISABLE_TRAIN 0x00000008 +#define SF_PATH_ALTERNATE 0x00008000 + +// Spawnflags of CPathCorner +#define SF_CORNER_WAITFORTRIG 0x001 +#define SF_CORNER_TELEPORT 0x002 +#define SF_CORNER_FIREONCE 0x004 + +//#define PATH_SPARKLE_DEBUG 1 // This makes a particle effect around path_track entities for debugging +class CPathTrack : public CPointEntity +{ +public: + void Spawn( void ); + void Activate( void ); + void KeyValue( KeyValueData* pkvd); + + void SetPrevious( CPathTrack *pprevious ); + void Link( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + CPathTrack *ValidPath( CPathTrack *ppath, int testFlag ); // Returns ppath if enabled, NULL otherwise + void Project( CPathTrack *pstart, CPathTrack *pend, Vector *origin, float dist ); + + static CPathTrack *Instance( edict_t *pent ); + + CPathTrack *LookAhead( Vector *origin, float dist, int move ); + CPathTrack *Nearest( Vector origin ); + + CPathTrack *GetNext( void ); + CPathTrack *GetPrevious( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; +#if PATH_SPARKLE_DEBUG + void EXPORT Sparkle(void); +#endif + + float m_length; + string_t m_altName; + CPathTrack *m_pnext; + CPathTrack *m_pprevious; + CPathTrack *m_paltpath; +}; + + +class CFuncTrackTrain : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + + void Blocked( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData* pkvd ); + + void EXPORT Next( void ); + void EXPORT Find( void ); + void EXPORT NearestPath( void ); + void EXPORT DeadEnd( void ); + + void NextThink( float thinkTime, BOOL alwaysThink ); + + void SetTrack( CPathTrack *track ) { m_ppath = track->Nearest(pev->origin); } + void SetControls( entvars_t *pevControls ); + BOOL OnControls( entvars_t *pev ); + + void StopSound ( void ); + void UpdateSound ( void ); + + static CFuncTrackTrain *Instance( edict_t *pent ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DIRECTIONAL_USE; } + + virtual void OverrideReset( void ); + + CPathTrack *m_ppath; + float m_length; + float m_height; + float m_speed; + float m_dir; + float m_startSpeed; + Vector m_controlMins; + Vector m_controlMaxs; + int m_soundPlaying; + int m_sounds; + float m_flVolume; + float m_flBank; + float m_oldSpeed; + +private: + unsigned short m_usAdjustPitch; +}; + +#endif diff --git a/ricochet/dlls/triggers.cpp b/ricochet/dlls/triggers.cpp new file mode 100644 index 0000000..8f275eb --- /dev/null +++ b/ricochet/dlls/triggers.cpp @@ -0,0 +1,2790 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== triggers.cpp ======================================================== + + spawn and use functions for editor-placed triggers + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "effects.h" +#include "player.h" +#include "saverestore.h" +#include "trains.h" // trigger_camera has train functionality +#include "gamerules.h" +#include "weapons.h" +#include "discwar.h" +#include "disc_arena.h" +#include "disc_objects.h" + +#define SF_TRIGGER_PUSH_START_OFF 2//spawnflag that makes trigger_push spawn turned OFF +#define SF_TRIGGER_HURT_TARGETONCE 1// Only fire hurt target once +#define SF_TRIGGER_HURT_START_OFF 2//spawnflag that makes trigger_push spawn turned OFF +#define SF_TRIGGER_HURT_NO_CLIENTS 8//spawnflag that makes trigger_push spawn turned OFF +#define SF_TRIGGER_HURT_CLIENTONLYFIRE 16// trigger hurt will only fire its target if it is hurting a client +#define SF_TRIGGER_HURT_CLIENTONLYTOUCH 32// only clients may touch this trigger. + +extern DLL_GLOBAL BOOL g_fGameOver; + +extern void SetMovedir(entvars_t* pev); +extern Vector VecBModelOrigin( entvars_t* pevBModel ); + +class CFrictionModifier : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT ChangeFriction( CBaseEntity *pOther ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + static TYPEDESCRIPTION m_SaveData[]; + + float m_frictionFraction; // Sorry, couldn't resist this name :) +}; + +LINK_ENTITY_TO_CLASS( func_friction, CFrictionModifier ); + +// Global Savedata for changelevel friction modifier +TYPEDESCRIPTION CFrictionModifier::m_SaveData[] = +{ + DEFINE_FIELD( CFrictionModifier, m_frictionFraction, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE(CFrictionModifier,CBaseEntity); + + +// Modify an entity's friction +void CFrictionModifier :: Spawn( void ) +{ + pev->solid = SOLID_TRIGGER; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->movetype = MOVETYPE_NONE; + SetTouch( &CFrictionModifier::ChangeFriction ); +} + + +// Sets toucher's friction to m_frictionFraction (1.0 = normal friction) +void CFrictionModifier :: ChangeFriction( CBaseEntity *pOther ) +{ + if ( pOther->pev->movetype != MOVETYPE_BOUNCEMISSILE && pOther->pev->movetype != MOVETYPE_BOUNCE ) + pOther->pev->friction = m_frictionFraction; +} + + + +// Sets toucher's friction to m_frictionFraction (1.0 = normal friction) +void CFrictionModifier :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "modifier")) + { + m_frictionFraction = atof(pkvd->szValue) / 100.0; + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +// This trigger will fire when the level spawns (or respawns if not fire once) +// It will check a global state before firing. It supports delay and killtargets + +#define SF_AUTO_FIREONCE 0x0001 + +class CAutoTrigger : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Precache( void ); + void Think( void ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_globalstate; + USE_TYPE triggerType; +}; +LINK_ENTITY_TO_CLASS( trigger_auto, CAutoTrigger ); + +TYPEDESCRIPTION CAutoTrigger::m_SaveData[] = +{ + DEFINE_FIELD( CAutoTrigger, m_globalstate, FIELD_STRING ), + DEFINE_FIELD( CAutoTrigger, triggerType, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE(CAutoTrigger,CBaseDelay); + +void CAutoTrigger::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "globalstate")) + { + m_globalstate = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "triggerstate")) + { + int type = atoi( pkvd->szValue ); + switch( type ) + { + case 0: + triggerType = USE_OFF; + break; + case 2: + triggerType = USE_TOGGLE; + break; + default: + triggerType = USE_ON; + break; + } + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + + +void CAutoTrigger::Spawn( void ) +{ + Precache(); +} + + +void CAutoTrigger::Precache( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CAutoTrigger::Think( void ) +{ + if ( !m_globalstate || gGlobalState.EntityGetState( m_globalstate ) == GLOBAL_ON ) + { + SUB_UseTargets( this, triggerType, 0 ); + if ( pev->spawnflags & SF_AUTO_FIREONCE ) + UTIL_Remove( this ); + } +} + + + +#define SF_RELAY_FIREONCE 0x0001 + +class CTriggerRelay : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + USE_TYPE triggerType; +}; +LINK_ENTITY_TO_CLASS( trigger_relay, CTriggerRelay ); + +TYPEDESCRIPTION CTriggerRelay::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerRelay, triggerType, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerRelay,CBaseDelay); + +void CTriggerRelay::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "triggerstate")) + { + int type = atoi( pkvd->szValue ); + switch( type ) + { + case 0: + triggerType = USE_OFF; + break; + case 2: + triggerType = USE_TOGGLE; + break; + default: + triggerType = USE_ON; + break; + } + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + + +void CTriggerRelay::Spawn( void ) +{ +} + + + + +void CTriggerRelay::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SUB_UseTargets( this, triggerType, 0 ); + if ( pev->spawnflags & SF_RELAY_FIREONCE ) + UTIL_Remove( this ); +} + + +//********************************************************** +// The Multimanager Entity - when fired, will fire up to 16 targets +// at specified times. +// FLAG: THREAD (create clones when triggered) +// FLAG: CLONE (this is a clone for a threaded execution) + +#define SF_MULTIMAN_CLONE 0x80000000 +#define SF_MULTIMAN_THREAD 0x00000001 + +class CMultiManager : public CBaseToggle +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn ( void ); + void EXPORT ManagerThink ( void ); + void EXPORT ManagerUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +#if _DEBUG + void EXPORT ManagerReport( void ); +#endif + + BOOL HasTarget( string_t targetname ); + + int ObjectCaps( void ) { return CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_cTargets; // the total number of targets in this manager's fire list. + int m_index; // Current target + float m_startTime;// Time we started firing + int m_iTargetName [ MAX_MULTI_TARGETS ];// list if indexes into global string array + float m_flTargetDelay [ MAX_MULTI_TARGETS ];// delay (in seconds) from time of manager fire to target fire +private: + inline BOOL IsClone( void ) { return (pev->spawnflags & SF_MULTIMAN_CLONE) ? TRUE : FALSE; } + inline BOOL ShouldClone( void ) + { + if ( IsClone() ) + return FALSE; + + return (pev->spawnflags & SF_MULTIMAN_THREAD) ? TRUE : FALSE; + } + + CMultiManager *Clone( void ); +}; +LINK_ENTITY_TO_CLASS( multi_manager, CMultiManager ); + +// Global Savedata for multi_manager +TYPEDESCRIPTION CMultiManager::m_SaveData[] = +{ + DEFINE_FIELD( CMultiManager, m_cTargets, FIELD_INTEGER ), + DEFINE_FIELD( CMultiManager, m_index, FIELD_INTEGER ), + DEFINE_FIELD( CMultiManager, m_startTime, FIELD_TIME ), + DEFINE_ARRAY( CMultiManager, m_iTargetName, FIELD_STRING, MAX_MULTI_TARGETS ), + DEFINE_ARRAY( CMultiManager, m_flTargetDelay, FIELD_FLOAT, MAX_MULTI_TARGETS ), +}; + +IMPLEMENT_SAVERESTORE(CMultiManager,CBaseToggle); + +void CMultiManager :: KeyValue( KeyValueData *pkvd ) +{ + // UNDONE: Maybe this should do something like this: + //CBaseToggle::KeyValue( pkvd ); + // if ( !pkvd->fHandled ) + // ... etc. + + if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else // add this field to the target list + { + // this assumes that additional fields are targetnames and their values are delay values. + if ( m_cTargets < MAX_MULTI_TARGETS ) + { + char tmp[128]; + + UTIL_StripToken( pkvd->szKeyName, tmp ); + m_iTargetName [ m_cTargets ] = ALLOC_STRING( tmp ); + m_flTargetDelay [ m_cTargets ] = atof (pkvd->szValue); + m_cTargets++; + pkvd->fHandled = TRUE; + } + } +} + + +void CMultiManager :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + SetUse ( &CMultiManager::ManagerUse ); + SetThink ( &CMultiManager::ManagerThink); + + // Sort targets + // Quick and dirty bubble sort + int swapped = 1; + + while ( swapped ) + { + swapped = 0; + for ( int i = 1; i < m_cTargets; i++ ) + { + if ( m_flTargetDelay[i] < m_flTargetDelay[i-1] ) + { + // Swap out of order elements + int name = m_iTargetName[i]; + float delay = m_flTargetDelay[i]; + m_iTargetName[i] = m_iTargetName[i-1]; + m_flTargetDelay[i] = m_flTargetDelay[i-1]; + m_iTargetName[i-1] = name; + m_flTargetDelay[i-1] = delay; + swapped = 1; + } + } + } +} + + +BOOL CMultiManager::HasTarget( string_t targetname ) +{ + for ( int i = 0; i < m_cTargets; i++ ) + if ( FStrEq(STRING(targetname), STRING(m_iTargetName[i])) ) + return TRUE; + + return FALSE; +} + + +// Designers were using this to fire targets that may or may not exist -- +// so I changed it to use the standard target fire code, made it a little simpler. +void CMultiManager :: ManagerThink ( void ) +{ + float time; + + time = gpGlobals->time - m_startTime; + while ( m_index < m_cTargets && m_flTargetDelay[ m_index ] <= time ) + { + FireTargets( STRING( m_iTargetName[ m_index ] ), m_hActivator, this, USE_TOGGLE, 0 ); + m_index++; + } + + if ( m_index >= m_cTargets )// have we fired all targets? + { + SetThink( NULL ); + if ( IsClone() ) + { + UTIL_Remove( this ); + return; + } + SetUse ( &CMultiManager::ManagerUse );// allow manager re-use + } + else + pev->nextthink = m_startTime + m_flTargetDelay[ m_index ]; +} + +CMultiManager *CMultiManager::Clone( void ) +{ + CMultiManager *pMulti = GetClassPtr( (CMultiManager *)NULL ); + + edict_t *pEdict = pMulti->pev->pContainingEntity; + memcpy( pMulti->pev, pev, sizeof(*pev) ); + pMulti->pev->pContainingEntity = pEdict; + + pMulti->pev->spawnflags |= SF_MULTIMAN_CLONE; + pMulti->m_cTargets = m_cTargets; + memcpy( pMulti->m_iTargetName, m_iTargetName, sizeof( m_iTargetName ) ); + memcpy( pMulti->m_flTargetDelay, m_flTargetDelay, sizeof( m_flTargetDelay ) ); + + return pMulti; +} + + +// The USE function builds the time table and starts the entity thinking. +void CMultiManager :: ManagerUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // In multiplayer games, clone the MM and execute in the clone (like a thread) + // to allow multiple players to trigger the same multimanager + if ( ShouldClone() ) + { + CMultiManager *pClone = Clone(); + pClone->ManagerUse( pActivator, pCaller, useType, value ); + return; + } + + m_hActivator = pActivator; + m_index = 0; + m_startTime = gpGlobals->time; + + SetUse( NULL );// disable use until all targets have fired + + SetThink ( &CMultiManager::ManagerThink ); + pev->nextthink = gpGlobals->time; +} + +#if _DEBUG +void CMultiManager :: ManagerReport ( void ) +{ + int cIndex; + + for ( cIndex = 0 ; cIndex < m_cTargets ; cIndex++ ) + { + ALERT ( at_console, "%s %f\n", STRING(m_iTargetName[cIndex]), m_flTargetDelay[cIndex] ); + } +} +#endif + +//*********************************************************** + + +// +// Render parameters trigger +// +// This entity will copy its render parameters (renderfx, rendermode, rendercolor, renderamt) +// to its targets when triggered. +// + + +// Flags to indicate masking off various render parameters that are normally copied to the targets +#define SF_RENDER_MASKFX (1<<0) +#define SF_RENDER_MASKAMT (1<<1) +#define SF_RENDER_MASKMODE (1<<2) +#define SF_RENDER_MASKCOLOR (1<<3) + +class CRenderFxManager : public CBaseEntity +{ +public: + void Spawn( void ); + void Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; + +LINK_ENTITY_TO_CLASS( env_render, CRenderFxManager ); + + +void CRenderFxManager :: Spawn ( void ) +{ + pev->solid = SOLID_NOT; +} + +void CRenderFxManager :: Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (!FStringNull(pev->target)) + { + edict_t* pentTarget = NULL; + while ( 1 ) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->target)); + if (FNullEnt(pentTarget)) + break; + + entvars_t *pevTarget = VARS( pentTarget ); + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKFX ) ) + pevTarget->renderfx = pev->renderfx; + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKAMT ) ) + pevTarget->renderamt = pev->renderamt; + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKMODE ) ) + pevTarget->rendermode = pev->rendermode; + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKCOLOR ) ) + pevTarget->rendercolor = pev->rendercolor; + } + } +} + + + +LINK_ENTITY_TO_CLASS( trigger, CBaseTrigger ); + +/* +================ +InitTrigger +================ +*/ +void CBaseTrigger::InitTrigger( ) +{ + // trigger angles are used for one-way touches. An angle of 0 is assumed + // to mean no restrictions, so use a yaw of 360 instead. + if (pev->angles != g_vecZero) + SetMovedir(pev); + pev->solid = SOLID_TRIGGER; + pev->movetype = MOVETYPE_NONE; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + if ( CVAR_GET_FLOAT("showtriggers") == 0 ) + SetBits( pev->effects, EF_NODRAW ); +} + + +// +// Cache user-entity-field values until spawn is called. +// + +void CBaseTrigger :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "damage")) + { + pev->dmg = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "count")) + { + m_cTriggersLeft = (int) atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damagetype")) + { + m_bitsDamageInflict = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +class CTriggerHurt : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT RadiationThink( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_hurt, CTriggerHurt ); + +// +// trigger_monsterjump +// +class CTriggerMonsterJump : public CBaseTrigger +{ +public: + void Spawn( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_monsterjump, CTriggerMonsterJump ); + + +void CTriggerMonsterJump :: Spawn ( void ) +{ + SetMovedir ( pev ); + + InitTrigger (); + + pev->nextthink = 0; + pev->speed = 200; + m_flHeight = 150; + + if ( !FStringNull ( pev->targetname ) ) + {// if targetted, spawn turned off + pev->solid = SOLID_NOT; + UTIL_SetOrigin( pev, pev->origin ); // Unlink from trigger list + SetUse( &CTriggerMonsterJump::ToggleUse ); + } +} + + +void CTriggerMonsterJump :: Think( void ) +{ + pev->solid = SOLID_NOT;// kill the trigger for now !!!UNDONE + UTIL_SetOrigin( pev, pev->origin ); // Unlink from trigger list + SetThink( NULL ); +} + +void CTriggerMonsterJump :: Touch( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + if ( !FBitSet ( pevOther->flags , FL_MONSTER ) ) + {// touched by a non-monster. + return; + } + + pevOther->origin.z += 1; + + if ( FBitSet ( pevOther->flags, FL_ONGROUND ) ) + {// clear the onground so physics don't bitch + pevOther->flags &= ~FL_ONGROUND; + } + + // toss the monster! + pevOther->velocity = pev->movedir * pev->speed; + pevOther->velocity.z += m_flHeight; + pev->nextthink = gpGlobals->time; +} + + +//===================================== +// +// trigger_cdaudio - starts/stops cd audio tracks +// +class CTriggerCDAudio : public CBaseTrigger +{ +public: + void Spawn( void ); + + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void PlayTrack( void ); + void Touch ( CBaseEntity *pOther ); +}; + +LINK_ENTITY_TO_CLASS( trigger_cdaudio, CTriggerCDAudio ); + +// +// Changes tracks or stops CD when player touches +// +// !!!HACK - overloaded HEALTH to avoid adding new field +void CTriggerCDAudio :: Touch ( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + {// only clients may trigger these events + return; + } + + PlayTrack(); +} + +void CTriggerCDAudio :: Spawn( void ) +{ + InitTrigger(); +} + +void CTriggerCDAudio::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + PlayTrack(); +} + +void PlayCDTrack( int iTrack ) +{ + edict_t *pClient; + + // manually find the single player. + pClient = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + // Can't play if the client is not connected! + if ( !pClient ) + return; + + if ( iTrack < -1 || iTrack > 30 ) + { + ALERT ( at_console, "TriggerCDAudio - Track %d out of range\n" ); + return; + } + + if ( iTrack == -1 ) + { + CLIENT_COMMAND ( pClient, "cd stop\n"); + } + else + { + char string [ 64 ]; + + sprintf( string, "cd play %3d\n", iTrack ); + CLIENT_COMMAND ( pClient, string); + } +} + + +// only plays for ONE client, so only use in single play! +void CTriggerCDAudio :: PlayTrack( void ) +{ + PlayCDTrack( (int)pev->health ); + + SetTouch( NULL ); + UTIL_Remove( this ); +} + + +// This plays a CD track when fired or when the player enters it's radius +class CTargetCDAudio : public CPointEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + void Play( void ); +}; + +LINK_ENTITY_TO_CLASS( target_cdaudio, CTargetCDAudio ); + +void CTargetCDAudio :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "radius")) + { + pev->scale = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CTargetCDAudio :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + if ( pev->scale > 0 ) + pev->nextthink = gpGlobals->time + 1.0; +} + +void CTargetCDAudio::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + Play(); +} + +// only plays for ONE client, so only use in single play! +void CTargetCDAudio::Think( void ) +{ + edict_t *pClient; + + // manually find the single player. + pClient = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + // Can't play if the client is not connected! + if ( !pClient ) + return; + + pev->nextthink = gpGlobals->time + 0.5; + + if ( (pClient->v.origin - pev->origin).Length() <= pev->scale ) + Play(); + +} + +void CTargetCDAudio::Play( void ) +{ + PlayCDTrack( (int)pev->health ); + UTIL_Remove(this); +} + +//===================================== + +// +// trigger_hurt - hurts anything that touches it. if the trigger has a targetname, firing it will toggle state +// +//int gfToggleState = 0; // used to determine when all radiation trigger hurts have called 'RadiationThink' + +void CTriggerHurt :: Spawn( void ) +{ + InitTrigger(); + SetTouch ( &CTriggerHurt::HurtTouch ); + + if ( !FStringNull ( pev->targetname ) ) + { + SetUse ( &CTriggerHurt::ToggleUse ); + } + else + { + SetUse ( NULL ); + } + + if (m_bitsDamageInflict & DMG_RADIATION) + { + SetThink ( &CTriggerHurt::RadiationThink ); + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(0.0, 0.5); + } + + if ( FBitSet (pev->spawnflags, SF_TRIGGER_HURT_START_OFF) )// if flagged to Start Turned Off, make trigger nonsolid. + pev->solid = SOLID_NOT; + + UTIL_SetOrigin( pev, pev->origin ); // Link into the list +} + +// trigger hurt that causes radiation will do a radius +// check and set the player's geiger counter level +// according to distance from center of trigger + +void CTriggerHurt :: RadiationThink( void ) +{ + + edict_t *pentPlayer; + CBasePlayer *pPlayer = NULL; + float flRange; + entvars_t *pevTarget; + Vector vecSpot1; + Vector vecSpot2; + Vector vecRange; + Vector origin; + Vector view_ofs; + + // check to see if a player is in pvs + // if not, continue + + // set origin to center of trigger so that this check works + origin = pev->origin; + view_ofs = pev->view_ofs; + + pev->origin = (pev->absmin + pev->absmax) * 0.5; + pev->view_ofs = pev->view_ofs * 0.0; + + pentPlayer = FIND_CLIENT_IN_PVS(edict()); + + pev->origin = origin; + pev->view_ofs = view_ofs; + + // reset origin + + if (!FNullEnt(pentPlayer)) + { + + pPlayer = GetClassPtr( (CBasePlayer *)VARS(pentPlayer)); + + pevTarget = VARS(pentPlayer); + + // get range to player; + + vecSpot1 = (pev->absmin + pev->absmax) * 0.5; + vecSpot2 = (pevTarget->absmin + pevTarget->absmax) * 0.5; + + vecRange = vecSpot1 - vecSpot2; + flRange = vecRange.Length(); + + // if player's current geiger counter range is larger + // than range to this trigger hurt, reset player's + // geiger counter range + + if (pPlayer->m_flgeigerRange >= flRange) + pPlayer->m_flgeigerRange = flRange; + } + + pev->nextthink = gpGlobals->time + 0.25; +} + +// +// ToggleUse - If this is the USE function for a trigger, its state will toggle every time it's fired +// +void CBaseTrigger :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (pev->solid == SOLID_NOT) + {// if the trigger is off, turn it on + pev->solid = SOLID_TRIGGER; + + // Force retouch + gpGlobals->force_retouch++; + } + else + {// turn the trigger off + pev->solid = SOLID_NOT; + } + UTIL_SetOrigin( pev, pev->origin ); +} + +// When touched, a hurt trigger does DMG points of damage each half-second +void CBaseTrigger :: HurtTouch ( CBaseEntity *pOther ) +{ + float fldmg; + + if ( !pOther->pev->takedamage || pOther->IsAlive() == false ) + return; + + if ( (pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYTOUCH) && !pOther->IsPlayer() ) + { + // this trigger is only allowed to touch clients, and this ain't a client. + return; + } + + if ( (pev->spawnflags & SF_TRIGGER_HURT_NO_CLIENTS) && pOther->IsPlayer() ) + return; + + // HACKHACK -- In multiplayer, players touch this based on packet receipt. + // So the players who send packets later aren't always hurt. Keep track of + // how much time has passed and whether or not you've touched that player + if ( g_pGameRules->IsMultiplayer() ) + { + if ( pev->dmgtime > gpGlobals->time ) + { + if ( gpGlobals->time != pev->pain_finished ) + {// too early to hurt again, and not same frame with a different entity + if ( pOther->IsPlayer() ) + { + int playerMask = 1 << (pOther->entindex() - 1); + + // If I've already touched this player (this time), then bail out + if ( pev->impulse & playerMask ) + return; + + // Mark this player as touched + // BUGBUG - There can be only 32 players! + pev->impulse |= playerMask; + } + else + { + return; + } + } + } + else + { + // New clock, "un-touch" all players + pev->impulse = 0; + if ( pOther->IsPlayer() ) + { + int playerMask = 1 << (pOther->entindex() - 1); + + // Mark this player as touched + // BUGBUG - There can be only 32 players! + pev->impulse |= playerMask; + } + } + } + else // Original code -- single player + { + if ( pev->dmgtime > gpGlobals->time && gpGlobals->time != pev->pain_finished ) + {// too early to hurt again, and not same frame with a different entity + return; + } + } + + + + // If this is time_based damage (poison, radiation), override the pev->dmg with a + // default for the given damage type. Monsters only take time-based damage + // while touching the trigger. Player continues taking damage for a while after + // leaving the trigger + + fldmg = pev->dmg * 0.5; // 0.5 seconds worth of damage, pev->dmg is damage/second + + + // JAY: Cut this because it wasn't fully realized. Damage is simpler now. +#if 0 + switch (m_bitsDamageInflict) + { + default: break; + case DMG_POISON: fldmg = POISON_DAMAGE/4; break; + case DMG_NERVEGAS: fldmg = NERVEGAS_DAMAGE/4; break; + case DMG_RADIATION: fldmg = RADIATION_DAMAGE/4; break; + case DMG_PARALYZE: fldmg = PARALYZE_DAMAGE/4; break; // UNDONE: cut this? should slow movement to 50% + case DMG_ACID: fldmg = ACID_DAMAGE/4; break; + case DMG_SLOWBURN: fldmg = SLOWBURN_DAMAGE/4; break; + case DMG_SLOWFREEZE: fldmg = SLOWFREEZE_DAMAGE/4; break; + } +#endif + + if ( fldmg < 0 ) + pOther->TakeHealth( -fldmg, m_bitsDamageInflict ); + else + pOther->TakeDamage( pev, pev, fldmg, m_bitsDamageInflict ); + + // Store pain time so we can get all of the other entities on this frame + pev->pain_finished = gpGlobals->time; + + // Apply damage every half second + pev->dmgtime = gpGlobals->time + 0.5;// half second delay until this trigger can hurt toucher again + + + + if ( pev->target ) + { + // trigger has a target it wants to fire. + if ( pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYFIRE ) + { + // if the toucher isn't a client, don't fire the target! + if ( !pOther->IsPlayer() ) + { + return; + } + } + + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); + if ( pev->spawnflags & SF_TRIGGER_HURT_TARGETONCE ) + pev->target = 0; + } +} + + +/*QUAKED trigger_multiple (.5 .5 .5) ? notouch +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. +"wait" : Seconds between triggerings. (.2 default) +If notouch is set, the trigger is only fired by other entities, not by touching. +NOTOUCH has been obsoleted by trigger_relay! +sounds +1) secret +2) beep beep +3) large switch +4) +NEW +if a trigger has a NETNAME, that NETNAME will become the TARGET of the triggered object. +*/ +class CTriggerMultiple : public CBaseTrigger +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_multiple, CTriggerMultiple ); + + +void CTriggerMultiple :: Spawn( void ) +{ + if (m_flWait == 0) + m_flWait = 0.2; + + InitTrigger(); + + ASSERTSZ(pev->health == 0, "trigger_multiple with health"); +// UTIL_SetOrigin(pev, pev->origin); +// SET_MODEL( ENT(pev), STRING(pev->model) ); +// if (pev->health > 0) +// { +// if (FBitSet(pev->spawnflags, SPAWNFLAG_NOTOUCH)) +// ALERT(at_error, "trigger_multiple spawn: health and notouch don't make sense"); +// pev->max_health = pev->health; +//UNDONE: where to get pfnDie from? +// pev->pfnDie = multi_killed; +// pev->takedamage = DAMAGE_YES; +// pev->solid = SOLID_BBOX; +// UTIL_SetOrigin(pev, pev->origin); // make sure it links into the world +// } +// else + { + SetTouch( &CTriggerMultiple::MultiTouch ); + } + } + + +/*QUAKED trigger_once (.5 .5 .5) ? notouch +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 +"targetname". If "health" is set, the trigger must be killed to activate. +If notouch is set, the trigger is only fired by other entities, not by touching. +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. Use "360" for an angle of 0. +sounds +1) secret +2) beep beep +3) large switch +4) +*/ +class CTriggerOnce : public CTriggerMultiple +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_once, CTriggerOnce ); +void CTriggerOnce::Spawn( void ) +{ + m_flWait = -1; + + CTriggerMultiple :: Spawn(); +} + + + +void CBaseTrigger :: MultiTouch( CBaseEntity *pOther ) +{ + entvars_t *pevToucher; + + pevToucher = pOther->pev; + + // Only touch clients, monsters, or pushables (depending on flags) + if ( ((pevToucher->flags & FL_CLIENT) && !(pev->spawnflags & SF_TRIGGER_NOCLIENTS)) || + ((pevToucher->flags & FL_MONSTER) && (pev->spawnflags & SF_TRIGGER_ALLOWMONSTERS)) || + (pev->spawnflags & SF_TRIGGER_PUSHABLES) && FClassnameIs(pevToucher,"func_pushable") ) + { + +#if 0 + // if the trigger has an angles field, check player's facing direction + if (pev->movedir != g_vecZero) + { + UTIL_MakeVectors( pevToucher->angles ); + if ( DotProduct( gpGlobals->v_forward, pev->movedir ) < 0 ) + return; // not facing the right way + } +#endif + + ActivateMultiTrigger( pOther ); + } +} + + +// +// the trigger was just touched/killed/used +// self.enemy should be set to the activator so it can be held through a delay +// so wait for the delay time before firing +// +void CBaseTrigger :: ActivateMultiTrigger( CBaseEntity *pActivator ) +{ + if (pev->nextthink > gpGlobals->time) + return; // still waiting for reset time + + if (!UTIL_IsMasterTriggered(m_sMaster,pActivator)) + return; + + if (FClassnameIs(pev, "trigger_secret")) + { + if ( pev->enemy == NULL || !FClassnameIs(pev->enemy, "player")) + return; + gpGlobals->found_secrets++; + } + + if (!FStringNull(pev->noise)) + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + +// don't trigger again until reset +// pev->takedamage = DAMAGE_NO; + + m_hActivator = pActivator; + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + + if ( pev->message && pActivator->IsPlayer() ) + { + UTIL_ShowMessage( STRING(pev->message), pActivator ); +// CLIENT_PRINTF( ENT( pActivator->pev ), print_center, STRING(pev->message) ); + } + + if (m_flWait > 0) + { + SetThink( &CBaseTrigger::MultiWaitOver ); + pev->nextthink = gpGlobals->time + m_flWait; + } + else + { + // we can't just remove (self) here, because this is a touch function + // called while C code is looping through area links... + SetTouch( NULL ); + pev->nextthink = gpGlobals->time + 0.1; + SetThink( &CBaseTrigger::SUB_Remove ); + } +} + + +// the wait time has passed, so set back up for another activation +void CBaseTrigger :: MultiWaitOver( void ) +{ +// if (pev->max_health) +// { +// pev->health = pev->max_health; +// pev->takedamage = DAMAGE_YES; +// pev->solid = SOLID_BBOX; +// } + SetThink( NULL ); +} + + +// ========================= COUNTING TRIGGER ===================================== + +// +// GLOBALS ASSUMED SET: g_eoActivator +// +void CBaseTrigger::CounterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_cTriggersLeft--; + m_hActivator = pActivator; + + if (m_cTriggersLeft < 0) + return; + + BOOL fTellActivator = + (m_hActivator != 0) && + FClassnameIs(m_hActivator->pev, "player") && + !FBitSet(pev->spawnflags, SPAWNFLAG_NOMESSAGE); + if (m_cTriggersLeft != 0) + { + if (fTellActivator) + { + // UNDONE: I don't think we want these Quakesque messages + switch (m_cTriggersLeft) + { + case 1: ALERT(at_console, "Only 1 more to go..."); break; + case 2: ALERT(at_console, "Only 2 more to go..."); break; + case 3: ALERT(at_console, "Only 3 more to go..."); break; + default: ALERT(at_console, "There are more to go..."); break; + } + } + return; + } + + // !!!UNDONE: I don't think we want these Quakesque messages + if (fTellActivator) + ALERT(at_console, "Sequence completed!"); + + ActivateMultiTrigger( m_hActivator ); +} + + +/*QUAKED trigger_counter (.5 .5 .5) ? nomessage +Acts as an intermediary for an action that takes multiple inputs. +If nomessage is not set, it will print "1 more.. " etc when triggered and +"sequence complete" when finished. After the counter has been triggered "cTriggersLeft" +times (default 2), it will fire all of it's targets and remove itself. +*/ +class CTriggerCounter : public CBaseTrigger +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( trigger_counter, CTriggerCounter ); + +void CTriggerCounter :: Spawn( void ) +{ + // By making the flWait be -1, this counter-trigger will disappear after it's activated + // (but of course it needs cTriggersLeft "uses" before that happens). + m_flWait = -1; + + if (m_cTriggersLeft == 0) + m_cTriggersLeft = 2; + SetUse( &CTriggerCounter::CounterUse ); +} + +// ====================== TRIGGER_CHANGELEVEL ================================ + +class CTriggerVolume : public CPointEntity // Derive from point entity so this doesn't move across levels +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_transition, CTriggerVolume ); + +// Define space that travels across a level transition +void CTriggerVolume :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->model = NULL; + pev->modelindex = 0; +} + + +// Fires a target after level transition and then dies +class CFireAndDie : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void Think( void ); + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() | FCAP_FORCE_TRANSITION; } // Always go across transitions +}; +LINK_ENTITY_TO_CLASS( fireanddie, CFireAndDie ); + +void CFireAndDie::Spawn( void ) +{ + pev->classname = MAKE_STRING("fireanddie"); + // Don't call Precache() - it should be called on restore +} + + +void CFireAndDie::Precache( void ) +{ + // This gets called on restore + pev->nextthink = gpGlobals->time + m_flDelay; +} + + +void CFireAndDie::Think( void ) +{ + SUB_UseTargets( this, USE_TOGGLE, 0 ); + UTIL_Remove( this ); +} + + +#define SF_CHANGELEVEL_USEONLY 0x0002 +class CChangeLevel : public CBaseTrigger +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT UseChangeLevel ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT TriggerChangeLevel( void ); + void EXPORT ExecuteChangeLevel( void ); + void EXPORT TouchChangeLevel( CBaseEntity *pOther ); + void ChangeLevelNow( CBaseEntity *pActivator ); + + static edict_t *FindLandmark( const char *pLandmarkName ); + static int ChangeList( LEVELLIST *pLevelList, int maxList ); + static int AddTransitionToList( LEVELLIST *pLevelList, int listCount, const char *pMapName, const char *pLandmarkName, edict_t *pentLandmark ); + static int InTransitionVolume( CBaseEntity *pEntity, char *pVolumeName ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + char m_szMapName[cchMapNameMost]; // trigger_changelevel only: next map + char m_szLandmarkName[cchMapNameMost]; // trigger_changelevel only: landmark on next map + int m_changeTarget; + float m_changeTargetDelay; +}; +LINK_ENTITY_TO_CLASS( trigger_changelevel, CChangeLevel ); + +// Global Savedata for changelevel trigger +TYPEDESCRIPTION CChangeLevel::m_SaveData[] = +{ + DEFINE_ARRAY( CChangeLevel, m_szMapName, FIELD_CHARACTER, cchMapNameMost ), + DEFINE_ARRAY( CChangeLevel, m_szLandmarkName, FIELD_CHARACTER, cchMapNameMost ), + DEFINE_FIELD( CChangeLevel, m_changeTarget, FIELD_STRING ), + DEFINE_FIELD( CChangeLevel, m_changeTargetDelay, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE(CChangeLevel,CBaseTrigger); + +// +// Cache user-entity-field values until spawn is called. +// + +void CChangeLevel :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "map")) + { + if (strlen(pkvd->szValue) >= cchMapNameMost) + ALERT( at_error, "Map name '%s' too long (32 chars)\n", pkvd->szValue ); + strcpy(m_szMapName, pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "landmark")) + { + if (strlen(pkvd->szValue) >= cchMapNameMost) + ALERT( at_error, "Landmark name '%s' too long (32 chars)\n", pkvd->szValue ); + strcpy(m_szLandmarkName, pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "changetarget")) + { + m_changeTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "changedelay")) + { + m_changeTargetDelay = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseTrigger::KeyValue( pkvd ); +} + + +/*QUAKED trigger_changelevel (0.5 0.5 0.5) ? NO_INTERMISSION +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. +*/ + +void CChangeLevel :: Spawn( void ) +{ + if ( FStrEq( m_szMapName, "" ) ) + ALERT( at_console, "a trigger_changelevel doesn't have a map" ); + + if ( FStrEq( m_szLandmarkName, "" ) ) + ALERT( at_console, "trigger_changelevel to %s doesn't have a landmark", m_szMapName ); + + if (!FStringNull ( pev->targetname ) ) + { + SetUse ( &CChangeLevel::UseChangeLevel ); + } + InitTrigger(); + if ( !(pev->spawnflags & SF_CHANGELEVEL_USEONLY) ) + SetTouch( &CChangeLevel::TouchChangeLevel ); +// ALERT( at_console, "TRANSITION: %s (%s)\n", m_szMapName, m_szLandmarkName ); +} + + +void CChangeLevel :: ExecuteChangeLevel( void ) +{ + MESSAGE_BEGIN( MSG_ALL, SVC_CDTRACK ); + WRITE_BYTE( 3 ); + WRITE_BYTE( 3 ); + MESSAGE_END(); + + MESSAGE_BEGIN(MSG_ALL, SVC_INTERMISSION); + MESSAGE_END(); +} + + +FILE_GLOBAL char st_szNextMap[cchMapNameMost]; +FILE_GLOBAL char st_szNextSpot[cchMapNameMost]; + +edict_t *CChangeLevel :: FindLandmark( const char *pLandmarkName ) +{ + edict_t *pentLandmark; + + pentLandmark = FIND_ENTITY_BY_STRING( NULL, "targetname", pLandmarkName ); + while ( !FNullEnt( pentLandmark ) ) + { + // Found the landmark + if ( FClassnameIs( pentLandmark, "info_landmark" ) ) + return pentLandmark; + else + pentLandmark = FIND_ENTITY_BY_STRING( pentLandmark, "targetname", pLandmarkName ); + } + ALERT( at_error, "Can't find landmark %s\n", pLandmarkName ); + return NULL; +} + + +//========================================================= +// CChangeLevel :: Use - allows level transitions to be +// triggered by buttons, etc. +// +//========================================================= +void CChangeLevel :: UseChangeLevel ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + ChangeLevelNow( pActivator ); +} + +void CChangeLevel :: ChangeLevelNow( CBaseEntity *pActivator ) +{ + edict_t *pentLandmark; + LEVELLIST levels[16]; + + ASSERT(!FStrEq(m_szMapName, "")); + + // Don't work in deathmatch + if ( g_pGameRules->IsDeathmatch() ) + return; + + // Some people are firing these multiple times in a frame, disable + if ( gpGlobals->time == pev->dmgtime ) + return; + + pev->dmgtime = gpGlobals->time; + + + CBaseEntity *pPlayer = CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) ); + if ( !InTransitionVolume( pPlayer, m_szLandmarkName ) ) + { + ALERT( at_aiconsole, "Player isn't in the transition volume %s, aborting\n", m_szLandmarkName ); + return; + } + + // Create an entity to fire the changetarget + if ( m_changeTarget ) + { + CFireAndDie *pFireAndDie = GetClassPtr( (CFireAndDie *)NULL ); + if ( pFireAndDie ) + { + // Set target and delay + pFireAndDie->pev->target = m_changeTarget; + pFireAndDie->m_flDelay = m_changeTargetDelay; + pFireAndDie->pev->origin = pPlayer->pev->origin; + // Call spawn + DispatchSpawn( pFireAndDie->edict() ); + } + } + // This object will get removed in the call to CHANGE_LEVEL, copy the params into "safe" memory + strcpy(st_szNextMap, m_szMapName); + + m_hActivator = pActivator; + SUB_UseTargets( pActivator, USE_TOGGLE, 0 ); + st_szNextSpot[0] = 0; // Init landmark to NULL + + // look for a landmark entity + pentLandmark = FindLandmark( m_szLandmarkName ); + if ( !FNullEnt( pentLandmark ) ) + { + strcpy(st_szNextSpot, m_szLandmarkName); + gpGlobals->vecLandmarkOffset = VARS(pentLandmark)->origin; + } +// ALERT( at_console, "Level touches %d levels\n", ChangeList( levels, 16 ) ); + ALERT( at_console, "CHANGE LEVEL: %s %s\n", st_szNextMap, st_szNextSpot ); + CHANGE_LEVEL( st_szNextMap, st_szNextSpot ); +} + +// +// GLOBALS ASSUMED SET: st_szNextMap +// +void CChangeLevel :: TouchChangeLevel( CBaseEntity *pOther ) +{ + if (!FClassnameIs(pOther->pev, "player")) + return; + + ChangeLevelNow( pOther ); +} + + +// Add a transition to the list, but ignore duplicates +// (a designer may have placed multiple trigger_changelevels with the same landmark) +int CChangeLevel::AddTransitionToList( LEVELLIST *pLevelList, int listCount, const char *pMapName, const char *pLandmarkName, edict_t *pentLandmark ) +{ + int i; + + if ( !pLevelList || !pMapName || !pLandmarkName || !pentLandmark ) + return 0; + + for ( i = 0; i < listCount; i++ ) + { + if ( pLevelList[i].pentLandmark == pentLandmark && strcmp( pLevelList[i].mapName, pMapName ) == 0 ) + return 0; + } + strcpy( pLevelList[listCount].mapName, pMapName ); + strcpy( pLevelList[listCount].landmarkName, pLandmarkName ); + pLevelList[listCount].pentLandmark = pentLandmark; + pLevelList[listCount].vecLandmarkOrigin = VARS(pentLandmark)->origin; + + return 1; +} + +int BuildChangeList( LEVELLIST *pLevelList, int maxList ) +{ + return CChangeLevel::ChangeList( pLevelList, maxList ); +} + + +int CChangeLevel::InTransitionVolume( CBaseEntity *pEntity, char *pVolumeName ) +{ + edict_t *pentVolume; + + + if ( pEntity->ObjectCaps() & FCAP_FORCE_TRANSITION ) + return 1; + + // If you're following another entity, follow it through the transition (weapons follow the player) + if ( pEntity->pev->movetype == MOVETYPE_FOLLOW ) + { + if ( pEntity->pev->aiment != NULL ) + pEntity = CBaseEntity::Instance( pEntity->pev->aiment ); + } + + int inVolume = 1; // Unless we find a trigger_transition, everything is in the volume + + pentVolume = FIND_ENTITY_BY_TARGETNAME( NULL, pVolumeName ); + while ( !FNullEnt( pentVolume ) ) + { + CBaseEntity *pVolume = CBaseEntity::Instance( pentVolume ); + + if ( pVolume && FClassnameIs( pVolume->pev, "trigger_transition" ) ) + { + if ( pVolume->Intersects( pEntity ) ) // It touches one, it's in the volume + return 1; + else + inVolume = 0; // Found a trigger_transition, but I don't intersect it -- if I don't find another, don't go! + } + pentVolume = FIND_ENTITY_BY_TARGETNAME( pentVolume, pVolumeName ); + } + + return inVolume; +} + + +// We can only ever move 512 entities across a transition +#define MAX_ENTITY 512 + +// This has grown into a complicated beast +// Can we make this more elegant? +// This builds the list of all transitions on this level and which entities are in their PVS's and can / should +// be moved across. +int CChangeLevel::ChangeList( LEVELLIST *pLevelList, int maxList ) +{ + edict_t *pentChangelevel, *pentLandmark; + int i, count; + + count = 0; + + // Find all of the possible level changes on this BSP + pentChangelevel = FIND_ENTITY_BY_STRING( NULL, "classname", "trigger_changelevel" ); + if ( FNullEnt( pentChangelevel ) ) + return 0; + while ( !FNullEnt( pentChangelevel ) ) + { + CChangeLevel *pTrigger; + + pTrigger = GetClassPtr((CChangeLevel *)VARS(pentChangelevel)); + if ( pTrigger ) + { + // Find the corresponding landmark + pentLandmark = FindLandmark( pTrigger->m_szLandmarkName ); + if ( pentLandmark ) + { + // Build a list of unique transitions + if ( AddTransitionToList( pLevelList, count, pTrigger->m_szMapName, pTrigger->m_szLandmarkName, pentLandmark ) ) + { + count++; + if ( count >= maxList ) // FULL!! + break; + } + } + } + pentChangelevel = FIND_ENTITY_BY_STRING( pentChangelevel, "classname", "trigger_changelevel" ); + } + + if ( gpGlobals->pSaveData && ((SAVERESTOREDATA *)gpGlobals->pSaveData)->pTable ) + { + CSave saveHelper( (SAVERESTOREDATA *)gpGlobals->pSaveData ); + + for ( i = 0; i < count; i++ ) + { + int j, entityCount = 0; + CBaseEntity *pEntList[ MAX_ENTITY ]; + int entityFlags[ MAX_ENTITY ]; + + // Follow the linked list of entities in the PVS of the transition landmark + edict_t *pent = UTIL_EntitiesInPVS( pLevelList[i].pentLandmark ); + + // Build a list of valid entities in this linked list (we're going to use pent->v.chain again) + while ( !FNullEnt( pent ) ) + { + CBaseEntity *pEntity = CBaseEntity::Instance(pent); + if ( pEntity ) + { +// ALERT( at_console, "Trying %s\n", STRING(pEntity->pev->classname) ); + int caps = pEntity->ObjectCaps(); + if ( !(caps & FCAP_DONT_SAVE) ) + { + int flags = 0; + + // If this entity can be moved or is global, mark it + if ( caps & FCAP_ACROSS_TRANSITION ) + flags |= FENTTABLE_MOVEABLE; + if ( pEntity->pev->globalname && !pEntity->IsDormant() ) + flags |= FENTTABLE_GLOBAL; + if ( flags ) + { + pEntList[ entityCount ] = pEntity; + entityFlags[ entityCount ] = flags; + entityCount++; + if ( entityCount > MAX_ENTITY ) + ALERT( at_error, "Too many entities across a transition!" ); + } +// else +// ALERT( at_console, "Failed %s\n", STRING(pEntity->pev->classname) ); + } +// else +// ALERT( at_console, "DON'T SAVE %s\n", STRING(pEntity->pev->classname) ); + } + pent = pent->v.chain; + } + + for ( j = 0; j < entityCount; j++ ) + { + // Check to make sure the entity isn't screened out by a trigger_transition + if ( entityFlags[j] && InTransitionVolume( pEntList[j], pLevelList[i].landmarkName ) ) + { + // Mark entity table with 1<pev->classname) ); + + } + } + } + + return count; +} + +/* +go to the next level for deathmatch +only called if a time or frag limit has expired +*/ +void NextLevel( void ) +{ + edict_t* pent; + CChangeLevel *pChange; + + // find a trigger_changelevel + pent = FIND_ENTITY_BY_CLASSNAME(NULL, "trigger_changelevel"); + + // go back to start if no trigger_changelevel + if (FNullEnt(pent)) + { + gpGlobals->mapname = ALLOC_STRING("start"); + pChange = GetClassPtr( (CChangeLevel *)NULL ); + strcpy(pChange->m_szMapName, "start"); + } + else + pChange = GetClassPtr( (CChangeLevel *)VARS(pent)); + + strcpy(st_szNextMap, pChange->m_szMapName); + g_fGameOver = TRUE; + + if (pChange->pev->nextthink < gpGlobals->time) + { + pChange->SetThink( &CChangeLevel::ExecuteChangeLevel ); + pChange->pev->nextthink = gpGlobals->time + 0.1; + } +} + + +// ============================== LADDER ======================================= + +class CLadder : public CBaseTrigger +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS( func_ladder, CLadder ); + + +void CLadder :: KeyValue( KeyValueData *pkvd ) +{ + CBaseTrigger::KeyValue( pkvd ); +} + + +//========================================================= +// func_ladder - makes an area vertically negotiable +//========================================================= +void CLadder :: Precache( void ) +{ + // Do all of this in here because we need to 'convert' old saved games + pev->solid = SOLID_NOT; + pev->skin = CONTENTS_LADDER; + if ( CVAR_GET_FLOAT("showtriggers") == 0 ) + { + pev->rendermode = kRenderTransTexture; + pev->renderamt = 0; + } + pev->effects &= ~EF_NODRAW; +} + + +void CLadder :: Spawn( void ) +{ + Precache(); + + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->movetype = MOVETYPE_PUSH; +} + + +// ========================== A TRIGGER THAT PUSHES YOU =============================== + +class CTriggerPush : public CBaseTrigger +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Touch( CBaseEntity *pOther ); +}; +LINK_ENTITY_TO_CLASS( trigger_push, CTriggerPush ); + + +void CTriggerPush :: KeyValue( KeyValueData *pkvd ) +{ + CBaseTrigger::KeyValue( pkvd ); +} + + +/*QUAKED trigger_push (.5 .5 .5) ? TRIG_PUSH_ONCE +Pushes the player +*/ + +void CTriggerPush :: Spawn( ) +{ + if ( pev->angles == g_vecZero ) + pev->angles.y = 360; + InitTrigger(); + + if (pev->speed == 0) + pev->speed = 100; + + if ( FBitSet (pev->spawnflags, SF_TRIGGER_PUSH_START_OFF) )// if flagged to Start Turned Off, make trigger nonsolid. + pev->solid = SOLID_NOT; + + SetUse( &CTriggerPush::ToggleUse ); + + UTIL_SetOrigin( pev, pev->origin ); // Link into the list +} + + +void CTriggerPush :: Touch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + + // UNDONE: Is there a better way than health to detect things that have physics? (clients/monsters) + switch( pevToucher->movetype ) + { + case MOVETYPE_NONE: + case MOVETYPE_PUSH: + case MOVETYPE_NOCLIP: + case MOVETYPE_FOLLOW: + return; + } + + if ( pevToucher->solid != SOLID_NOT && pevToucher->solid != SOLID_BSP ) + { + // Instant trigger, just transfer velocity and remove + if (FBitSet(pev->spawnflags, SF_TRIG_PUSH_ONCE)) + { + pevToucher->velocity = pevToucher->velocity + (pev->speed * pev->movedir); + if ( pevToucher->velocity.z > 0 ) + pevToucher->flags &= ~FL_ONGROUND; + UTIL_Remove( this ); + } + else + { // Push field, transfer to base velocity + Vector vecPush = (pev->speed * pev->movedir); + if ( pevToucher->flags & FL_BASEVELOCITY ) + vecPush = vecPush + pevToucher->basevelocity; + + pevToucher->basevelocity = vecPush; + + pevToucher->flags |= FL_BASEVELOCITY; +// ALERT( at_console, "Vel %f, base %f\n", pevToucher->velocity.z, pevToucher->basevelocity.z ); + } + } +} + + +//====================================== +// teleport trigger +// +// + +void CBaseTrigger :: TeleportTouch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + edict_t *pentTarget = NULL; + + if (!UTIL_IsMasterTriggered(m_sMaster, pOther)) + return; + + if ( !( pev->spawnflags & SF_TRIGGER_ALLOWMONSTERS ) ) + {// no monsters allowed! + if ( FBitSet( pevToucher->flags, FL_MONSTER ) ) + { + return; + } + } + + if ( ( pev->spawnflags & SF_TRIGGER_NOCLIENTS ) ) + {// no clients allowed + if ( pOther->IsPlayer() ) + { + return; + } + } + + pentTarget = FIND_ENTITY_BY_TARGETNAME( pentTarget, STRING(pev->target) ); + if (FNullEnt(pentTarget)) + return; + + Vector tmp = VARS( pentTarget )->origin; + + if ( pOther->IsPlayer() ) + { + tmp.z -= pOther->pev->mins.z;// make origin adjustments in case the teleportee is a player. (origin in center, not at feet) + } + + tmp.z++; + + // Sprite in old position + CSprite *pSprite = CSprite::SpriteCreate( "sprites/discreturn.spr", pOther->pev->origin, TRUE ); + pSprite->AnimateAndDie( 60 ); + pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); + pSprite->SetScale( 1 ); + pSprite->pev->groupinfo = pOther->pev->groupinfo; + + pevToucher->flags &= ~FL_ONGROUND; + //UTIL_SetOrigin( pevToucher, tmp ); + pevToucher->origin = tmp; + + // Play a warp sound and sprite + pSprite = CSprite::SpriteCreate( "sprites/discreturn.spr", pOther->pev->origin, TRUE ); + pSprite->AnimateAndDie( 60 ); + pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); + pSprite->SetScale( 1 ); + pSprite->pev->groupinfo = pOther->pev->groupinfo; + EMIT_SOUND_DYN( pOther->edict(), CHAN_AUTO, "discreturn.wav", 1.0, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + + pevToucher->angles = pentTarget->v.angles; + + if ( pOther->IsPlayer() ) + { + pevToucher->v_angle = pentTarget->v.angles; + pevToucher->fixangle = TRUE; + } + + // Discs keep their velocity + if ( strcmp( STRING(pOther->pev->classname), "disc" ) ) + { + pevToucher->velocity = pevToucher->basevelocity = g_vecZero; + } + else + { + UTIL_MakeVectors( pevToucher->angles ); + if ( ((CDisc*)pOther)->m_iPowerupFlags & POW_FAST ) + pevToucher->velocity = pevToucher->basevelocity = gpGlobals->v_forward * DISC_VELOCITY * 1.5; + else + pevToucher->velocity = pevToucher->basevelocity = gpGlobals->v_forward * DISC_VELOCITY; + + ((CDisc*)pOther)->m_bTeleported = true; + } +} + + +class CTriggerTeleport : public CBaseTrigger +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( trigger_teleport, CTriggerTeleport ); + +void CTriggerTeleport :: Spawn( void ) +{ + InitTrigger(); + + SetTouch( &CTriggerTeleport::TeleportTouch ); +} + + +LINK_ENTITY_TO_CLASS( info_teleport_destination, CPointEntity ); + + + +class CTriggerSave : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT SaveTouch( CBaseEntity *pOther ); +}; +LINK_ENTITY_TO_CLASS( trigger_autosave, CTriggerSave ); + +void CTriggerSave::Spawn( void ) +{ + if ( g_pGameRules->IsDeathmatch() ) + { + REMOVE_ENTITY( ENT(pev) ); + return; + } + + InitTrigger(); + SetTouch( &CTriggerSave::SaveTouch ); +} + +void CTriggerSave::SaveTouch( CBaseEntity *pOther ) +{ + if ( !UTIL_IsMasterTriggered( m_sMaster, pOther ) ) + return; + + // Only save on clients + if ( !pOther->IsPlayer() ) + return; + + SetTouch( NULL ); + UTIL_Remove( this ); + SERVER_COMMAND( "autosave\n" ); +} + +#define SF_ENDSECTION_USEONLY 0x0001 + +class CTriggerEndSection : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT EndSectionTouch( CBaseEntity *pOther ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT EndSectionUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; +LINK_ENTITY_TO_CLASS( trigger_endsection, CTriggerEndSection ); + + +void CTriggerEndSection::EndSectionUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Only save on clients + if ( !pActivator->IsNetClient() ) + return; + + SetUse( NULL ); + + if ( pev->message ) + { + g_engfuncs.pfnEndSection(STRING(pev->message)); + } + UTIL_Remove( this ); +} + +void CTriggerEndSection::Spawn( void ) +{ + if ( g_pGameRules->IsDeathmatch() ) + { + REMOVE_ENTITY( ENT(pev) ); + return; + } + + InitTrigger(); + + SetUse ( &CTriggerEndSection::EndSectionUse ); + // If it is a "use only" trigger, then don't set the touch function. + if ( ! (pev->spawnflags & SF_ENDSECTION_USEONLY) ) + SetTouch( &CTriggerEndSection::EndSectionTouch ); +} + +void CTriggerEndSection::EndSectionTouch( CBaseEntity *pOther ) +{ + // Only save on clients + if ( !pOther->IsNetClient() ) + return; + + SetTouch( NULL ); + + if (pev->message) + { + g_engfuncs.pfnEndSection(STRING(pev->message)); + } + UTIL_Remove( this ); +} + +void CTriggerEndSection :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "section")) + { +// m_iszSectionName = ALLOC_STRING( pkvd->szValue ); + // Store this in message so we don't have to write save/restore for this ent + pev->message = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseTrigger::KeyValue( pkvd ); +} + + +class CTriggerGravity : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT GravityTouch( CBaseEntity *pOther ); +}; +LINK_ENTITY_TO_CLASS( trigger_gravity, CTriggerGravity ); + +void CTriggerGravity::Spawn( void ) +{ + InitTrigger(); + SetTouch( &CTriggerGravity::GravityTouch ); +} + +void CTriggerGravity::GravityTouch( CBaseEntity *pOther ) +{ + // Only save on clients + if ( !pOther->IsPlayer() ) + return; + + pOther->pev->gravity = pev->gravity; +} + + + + + + + +// this is a really bad idea. +class CTriggerChangeTarget : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_iszNewTarget; +}; +LINK_ENTITY_TO_CLASS( trigger_changetarget, CTriggerChangeTarget ); + +TYPEDESCRIPTION CTriggerChangeTarget::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerChangeTarget, m_iszNewTarget, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerChangeTarget,CBaseDelay); + +void CTriggerChangeTarget::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszNewTarget")) + { + m_iszNewTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + +void CTriggerChangeTarget::Spawn( void ) +{ +} + + +void CTriggerChangeTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity *pTarget = UTIL_FindEntityByString( NULL, "targetname", STRING( pev->target ) ); + + if (pTarget) + { + pTarget->pev->target = m_iszNewTarget; + CBaseMonster *pMonster = pTarget->MyMonsterPointer( ); + if (pMonster) + { + pMonster->m_pGoalEnt = NULL; + } + } +} + + + + +#define SF_CAMERA_PLAYER_POSITION 1 +#define SF_CAMERA_PLAYER_TARGET 2 +#define SF_CAMERA_PLAYER_TAKECONTROL 4 + +class CTriggerCamera : public CBaseDelay +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT FollowTarget( void ); + void Move(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + static TYPEDESCRIPTION m_SaveData[]; + + EHANDLE m_hPlayer; + EHANDLE m_hTarget; + CBaseEntity *m_pentPath; + int m_sPath; + float m_flWait; + float m_flReturnTime; + float m_flStopTime; + float m_moveDistance; + float m_targetSpeed; + float m_initialSpeed; + float m_acceleration; + float m_deceleration; + int m_state; + +}; +LINK_ENTITY_TO_CLASS( trigger_camera, CTriggerCamera ); + +// Global Savedata for changelevel friction modifier +TYPEDESCRIPTION CTriggerCamera::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerCamera, m_hPlayer, FIELD_EHANDLE ), + DEFINE_FIELD( CTriggerCamera, m_hTarget, FIELD_EHANDLE ), + DEFINE_FIELD( CTriggerCamera, m_pentPath, FIELD_CLASSPTR ), + DEFINE_FIELD( CTriggerCamera, m_sPath, FIELD_STRING ), + DEFINE_FIELD( CTriggerCamera, m_flWait, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_flReturnTime, FIELD_TIME ), + DEFINE_FIELD( CTriggerCamera, m_flStopTime, FIELD_TIME ), + DEFINE_FIELD( CTriggerCamera, m_moveDistance, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_targetSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_initialSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_acceleration, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_deceleration, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_state, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerCamera,CBaseDelay); + +void CTriggerCamera::Spawn( void ) +{ + pev->movetype = MOVETYPE_NOCLIP; + pev->solid = SOLID_NOT; // Remove model & collisions + pev->renderamt = 0; // The engine won't draw this model if this is set to 0 and blending is on + pev->rendermode = kRenderTransTexture; + + m_initialSpeed = pev->speed; + if ( m_acceleration == 0 ) + m_acceleration = 500; + if ( m_deceleration == 0 ) + m_deceleration = 500; +} + + +void CTriggerCamera :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "moveto")) + { + m_sPath = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "acceleration")) + { + m_acceleration = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "deceleration")) + { + m_deceleration = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + + + +void CTriggerCamera::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_state ) ) + return; + + // Toggle state + m_state = !m_state; + if (m_state == 0) + { + m_flReturnTime = gpGlobals->time; + return; + } + if ( !pActivator || !pActivator->IsPlayer() ) + { + pActivator = CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex( 1 )); + } + + m_hPlayer = pActivator; + + m_flReturnTime = gpGlobals->time + m_flWait; + pev->speed = m_initialSpeed; + m_targetSpeed = m_initialSpeed; + + if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TARGET ) ) + { + m_hTarget = m_hPlayer; + } + else + { + m_hTarget = GetNextTarget(); + } + + // Nothing to look at! + if ( m_hTarget == NULL ) + { + return; + } + + + if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TAKECONTROL ) ) + { + ((CBasePlayer *)pActivator)->EnableControl(FALSE); + } + + if ( m_sPath ) + { + m_pentPath = Instance( FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(m_sPath)) ); + } + else + { + m_pentPath = NULL; + } + + m_flStopTime = gpGlobals->time; + if ( m_pentPath ) + { + if ( m_pentPath->pev->speed != 0 ) + m_targetSpeed = m_pentPath->pev->speed; + + m_flStopTime += m_pentPath->GetDelay(); + } + + // copy over player information + if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_POSITION ) ) + { + UTIL_SetOrigin( pev, pActivator->pev->origin + pActivator->pev->view_ofs ); + pev->angles.x = -pActivator->pev->angles.x; + pev->angles.y = pActivator->pev->angles.y; + pev->angles.z = 0; + pev->velocity = pActivator->pev->velocity; + } + else + { + pev->velocity = Vector( 0, 0, 0 ); + } + + SET_VIEW( pActivator->edict(), edict() ); + + SET_MODEL(ENT(pev), STRING(pActivator->pev->model) ); + + // follow the player down + SetThink( &CTriggerCamera::FollowTarget ); + pev->nextthink = gpGlobals->time; + + m_moveDistance = 0; + Move(); +} + + +void CTriggerCamera::FollowTarget( ) +{ + if (m_hPlayer == NULL) + return; + + if (m_hTarget == NULL || m_flReturnTime < gpGlobals->time) + { + if (m_hPlayer->IsAlive( )) + { + SET_VIEW( m_hPlayer->edict(), m_hPlayer->edict() ); + ((CBasePlayer *)((CBaseEntity *)m_hPlayer))->EnableControl(TRUE); + } + SUB_UseTargets( this, USE_TOGGLE, 0 ); + pev->avelocity = Vector( 0, 0, 0 ); + m_state = 0; + return; + } + + Vector vecGoal = UTIL_VecToAngles( m_hTarget->pev->origin - pev->origin ); + vecGoal.x = -vecGoal.x; + + if (pev->angles.y > 360) + pev->angles.y -= 360; + + if (pev->angles.y < 0) + pev->angles.y += 360; + + float dx = vecGoal.x - pev->angles.x; + float dy = vecGoal.y - pev->angles.y; + + if (dx < -180) + dx += 360; + if (dx > 180) + dx = dx - 360; + + if (dy < -180) + dy += 360; + if (dy > 180) + dy = dy - 360; + + pev->avelocity.x = dx * 40 * gpGlobals->frametime; + pev->avelocity.y = dy * 40 * gpGlobals->frametime; + + + if (!(FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TAKECONTROL))) + { + pev->velocity = pev->velocity * 0.8; + if (pev->velocity.Length( ) < 10.0) + pev->velocity = g_vecZero; + } + + pev->nextthink = gpGlobals->time; + + Move(); +} + +void CTriggerCamera::Move() +{ + // Not moving on a path, return + if (!m_pentPath) + return; + + // Subtract movement from the previous frame + m_moveDistance -= pev->speed * gpGlobals->frametime; + + // Have we moved enough to reach the target? + if ( m_moveDistance <= 0 ) + { + // Fire the passtarget if there is one + if ( m_pentPath->pev->message ) + { + FireTargets( STRING(m_pentPath->pev->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( m_pentPath->pev->spawnflags, SF_CORNER_FIREONCE ) ) + m_pentPath->pev->message = 0; + } + // Time to go to the next target + m_pentPath = m_pentPath->GetNextTarget(); + + // Set up next corner + if ( !m_pentPath ) + { + pev->velocity = g_vecZero; + } + else + { + if ( m_pentPath->pev->speed != 0 ) + m_targetSpeed = m_pentPath->pev->speed; + + Vector delta = m_pentPath->pev->origin - pev->origin; + m_moveDistance = delta.Length(); + pev->movedir = delta.Normalize(); + m_flStopTime = gpGlobals->time + m_pentPath->GetDelay(); + } + } + + if ( m_flStopTime > gpGlobals->time ) + pev->speed = UTIL_Approach( 0, pev->speed, m_deceleration * gpGlobals->frametime ); + else + pev->speed = UTIL_Approach( m_targetSpeed, pev->speed, m_acceleration * gpGlobals->frametime ); + + float fraction = 2 * gpGlobals->frametime; + pev->velocity = ((pev->movedir * pev->speed) * fraction) + (pev->velocity * (1-fraction)); +} + + +//=============================================================================== +// DISCWAR OBJECTS +//=============================================================================== +// Brush that's status gets toggled by a disc hit +LINK_ENTITY_TO_CLASS( func_disctoggle, CDiscTarget ); + +void CDiscTarget::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "hitby_friendly") ) + { + m_iszFriendlyHit = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "hitby_enemy") ) + { + m_iszEnemyHit = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "arena") ) + { + pev->groupinfo = 1 << (atoi( pkvd->szValue ) - 1); + pkvd->fHandled = TRUE; + } + else + CBaseTrigger::KeyValue( pkvd ); +} + +void CDiscTarget::Spawn( void ) +{ + Precache(); + + pev->solid = SOLID_TRIGGER; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + + m_iState = 0; + SetTouch( &CDiscTarget::DiscToggleTouch ); +} + +void CDiscTarget::Reset( void ) +{ + m_iState = 0; + pev->frame = 0; + pev->target = m_iszFriendlyHit; +} + +void CDiscTarget::DiscToggleTouch( CBaseEntity *pOther ) +{ + if ( strcmp( STRING(pOther->pev->classname), "disc" ) ) + return; + + int iHitBy = 0; + CBaseEntity *pOwner = (CBaseEntity *)((CDisc *)pOther)->m_hOwner; + + // Hit by friendly if the teams match. Enemy otherwise + if ( pOwner && pOwner->pev->team == pev->team ) + { + Reset(); + iHitBy = LAST_HITBY_FRIENDLY; + } + else + { + pev->target = m_iszEnemyHit; + iHitBy = LAST_HITBY_ENEMY; + + pev->frame = 1; + } + + // Don't toggle if we were last hit by the same team + if (m_iState == iHitBy) + return; + + // Fire the target + SUB_UseTargets( pOwner, USE_TOGGLE, iHitBy ); + m_iState = iHitBy; +} + +//=============================================================================== +// Brush that toggles between gone/there +LINK_ENTITY_TO_CLASS( func_plat_toggleremove, CPlatToggleRemove ); + +void CPlatToggleRemove::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "arena") ) + { + pev->groupinfo = 1 << (atoi( pkvd->szValue ) - 1); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CPlatToggleRemove::Spawn( void ) +{ + Precache(); + + // Remove all but one copy of this ent if we're not in arena mode + if ( InArenaMode() == FALSE ) + { + if ( pev->groupinfo > 1 ) + { + pev->flags |= FL_KILLME; + return; + } + + // Clear groupinfo + pev->groupinfo = 0; + } + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + SET_MODEL(ENT(pev), STRING(pev->model)); + + SetUse( &CPlatToggleRemove::PlatToggleRemoveUse ); +} + +void CPlatToggleRemove::Reset( void ) +{ + pev->rendermode = kRenderNormal; + pev->renderamt = 255; + + pev->effects &= ~EF_NODRAW; + pev->solid = SOLID_BSP; + SET_MODEL(ENT(pev), STRING(pev->model)); + + SetThink( NULL ); + pev->nextthink = 0; +} + +void CPlatToggleRemove::PlatToggleRemoveUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( value == LAST_HITBY_ENEMY ) + { + // Make it fade out + SetThink( &CPlatToggleRemove::PlatRemoveThink ); + pev->nextthink = pev->ltime + 0.1; + m_flRemoveAt = gpGlobals->time + PLAT_FADE_TIME; + + pev->rendermode = kRenderTransAdd; + pev->renderamt = 250; + } + else + { + Reset(); + } +} + +void CPlatToggleRemove::PlatRemoveThink( void ) +{ + float flTimeLeft = m_flRemoveAt - gpGlobals->time; + + // Fade until m_flRemoveAt + if ( flTimeLeft > 0 ) + { + float flPercent = flTimeLeft / PLAT_FADE_TIME; + pev->renderamt = 250 * flPercent; + pev->nextthink = pev->ltime + 0.1; + } + else + { + // Fully gone + pev->effects |= EF_NODRAW; + pev->solid = SOLID_NOT; + SET_MODEL(ENT(pev), STRING(pev->model)); + } +} + +//=============================================================================== +// Brush that jumps a player to a target point +LINK_ENTITY_TO_CLASS( trigger_jump, CTriggerJump ); + +void CTriggerJump::Spawn( void ) +{ + Precache(); + InitTrigger(); + SetTouch( &CTriggerJump::JumpTouch ); + SetUse( &CTriggerJump::JumpUse ); + + if (!m_flHeight) + m_flHeight = 150; +} + +void CTriggerJump::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "height") ) + { + m_flHeight = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseTrigger::KeyValue( pkvd ); +} + +void CTriggerJump::Precache( void ) +{ + PRECACHE_SOUND( "triggerjump.wav" ); + + m_usJump = PRECACHE_EVENT( 1, "events/jump.sc" ); +} + +void CTriggerJump::Activate( void ) +{ + // Find the target point + if (!FStringNull(pev->target)) + { + edict_t* pentTarget = NULL; + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(pev->target)); + if (FNullEnt(pentTarget)) + { + ALERT ( at_console, "trigger_jump - Could not find target %s\n", STRING(pev->target) ); + pev->flags |= FL_KILLME; + } + else + { + m_vecTargetOrg = pentTarget->v.origin; + } + } +} + +void CTriggerJump::JumpUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( m_iState == 0 ) + { + m_iState = 1; + SetTouch( &CTriggerJump::JumpTouch ); + } + else + { + m_iState = 0; + SetTouch( NULL ); + } +} + +void CTriggerJump::JumpTouch( CBaseEntity *pOther ) +{ + TraceResult tr; + float flGravity = CVAR_GET_FLOAT( "sv_gravity" ); + + // Don't touch again immediately + if ( pOther->m_flTouchedByJumpPad > gpGlobals->time ) + { + m_flTouchedByJumpPad = gpGlobals->time + 0.1; + return; + } + + // get a rough idea of how high to launch + Vector vecMidPoint = pOther->pev->origin + (m_vecTargetOrg - pOther->pev->origin) * 0.5; + UTIL_TraceLine(vecMidPoint, vecMidPoint + Vector(0,0,m_flHeight), ignore_monsters, ENT(pev), &tr); + vecMidPoint = tr.vecEndPos; + // (subtract 15 so we don't hit the ceiling) + vecMidPoint.z -= 15; + + // How high should we travel to reach the apex + float distance1 = (vecMidPoint.z - pOther->pev->origin.z); + float distance2 = (vecMidPoint.z - m_vecTargetOrg.z); + + // How long will it take to travel this distance + float time1 = sqrt( distance1 / (0.5 * flGravity) ); + float time2 = sqrt( distance2 / (0.5 * flGravity) ); + if (time1 < 0.1) + return; + + // how hard to launch to get there in time. + Vector vecTargetVel = (m_vecTargetOrg - pOther->pev->origin) / (time1 + time2); + vecTargetVel.z = flGravity * time1; + + // don't affect the player again for a bit + pOther->m_flTouchedByJumpPad = gpGlobals->time + 0.2; + pOther->pev->velocity = vecTargetVel; + if ( pOther->IsPlayer() ) + ((CBasePlayer*)pOther)->SetAnimation( PLAYER_SUPERJUMP ); + + // Play a sound + PLAYBACK_EVENT_FULL( FEV_NOTHOST, pOther->edict(), m_usJump, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); +} + +//=============================================================================== +// Trigger that returns discs to their thrower immediately +LINK_ENTITY_TO_CLASS( trigger_discreturn, CTriggerDiscReturn ); + +void CTriggerDiscReturn::Spawn( void ) +{ + Precache(); + InitTrigger(); + SetTouch( &CTriggerDiscReturn::DiscReturnTouch ); +} + +void CTriggerDiscReturn::Precache( void ) +{ + PRECACHE_MODEL( "sprites/discreturn.spr" ); + PRECACHE_SOUND( "discreturn.wav" ); +} + +void CTriggerDiscReturn::DiscReturnTouch( CBaseEntity *pOther ) +{ + if ( strcmp( STRING(pOther->pev->classname), "disc" ) ) + return; + + // Play a warp sound and sprite + CSprite *pSprite = CSprite::SpriteCreate( "sprites/discreturn.spr", pOther->pev->origin, TRUE ); + pSprite->AnimateAndDie( 60 ); + pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); + pSprite->SetScale( 1 ); + pSprite->pev->groupinfo = pOther->pev->groupinfo; + + EMIT_SOUND_DYN( pOther->edict(), CHAN_AUTO, "discreturn.wav", 0.2, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + + // Return + ((CDisc*)pOther)->ReturnToThrower(); +} + +//=============================================================================== +// Trigger that starts the fall animation for players +LINK_ENTITY_TO_CLASS( trigger_fall, CTriggerFall ); + +void CTriggerFall::Spawn( void ) +{ + Precache(); + InitTrigger(); + SetTouch( &CTriggerFall::FallTouch ); +} + +void CTriggerFall::FallTouch( CBaseEntity *pOther ) +{ + if ( pOther->IsPlayer() == FALSE ) + return; + + if ( pOther->IsAlive() == FALSE ) + return; + + switch( RANDOM_LONG(0,2) ) + { + default: + case 0: EMIT_SOUND(ENT(pOther->pev), CHAN_STATIC, "scream1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pOther->pev), CHAN_STATIC, "scream2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pOther->pev), CHAN_STATIC, "scream3.wav", 1, ATTN_NORM); break; + } + + pOther->TakeDamage( pev, pev, 500, DMG_NEVERGIB ); + ((CBasePlayer*)pOther)->SetAnimation( PLAYER_FALL ); + + // Make their model shrink away to nothing + pOther->pev->renderfx = kRenderFxExplode; + pOther->pev->health = -5; // Setting this to -5 starts the client-side fall animation +} + diff --git a/ricochet/dlls/util.cpp b/ricochet/dlls/util.cpp new file mode 100644 index 0000000..0db8077 --- /dev/null +++ b/ricochet/dlls/util.cpp @@ -0,0 +1,2565 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== util.cpp ======================================================== + + Utility code. Really not optional after all. + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include +#include "../engine/shake.h" +#include "decals.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" + +/* +===================== +UTIL_WeaponTimeBase + +Time basis for weapons ( zero based of predicting client weapons ) +===================== +*/ +float UTIL_WeaponTimeBase( void ) +{ + return 0.0; +} + +static unsigned int glSeed = 0; + +unsigned int seed_table[ 256 ] = +{ + 28985, 27138, 26457, 9451, 17764, 10909, 28790, 8716, 6361, 4853, 17798, 21977, 19643, 20662, 10834, 20103, + 27067, 28634, 18623, 25849, 8576, 26234, 23887, 18228, 32587, 4836, 3306, 1811, 3035, 24559, 18399, 315, + 26766, 907, 24102, 12370, 9674, 2972, 10472, 16492, 22683, 11529, 27968, 30406, 13213, 2319, 23620, 16823, + 10013, 23772, 21567, 1251, 19579, 20313, 18241, 30130, 8402, 20807, 27354, 7169, 21211, 17293, 5410, 19223, + 10255, 22480, 27388, 9946, 15628, 24389, 17308, 2370, 9530, 31683, 25927, 23567, 11694, 26397, 32602, 15031, + 18255, 17582, 1422, 28835, 23607, 12597, 20602, 10138, 5212, 1252, 10074, 23166, 19823, 31667, 5902, 24630, + 18948, 14330, 14950, 8939, 23540, 21311, 22428, 22391, 3583, 29004, 30498, 18714, 4278, 2437, 22430, 3439, + 28313, 23161, 25396, 13471, 19324, 15287, 2563, 18901, 13103, 16867, 9714, 14322, 15197, 26889, 19372, 26241, + 31925, 14640, 11497, 8941, 10056, 6451, 28656, 10737, 13874, 17356, 8281, 25937, 1661, 4850, 7448, 12744, + 21826, 5477, 10167, 16705, 26897, 8839, 30947, 27978, 27283, 24685, 32298, 3525, 12398, 28726, 9475, 10208, + 617, 13467, 22287, 2376, 6097, 26312, 2974, 9114, 21787, 28010, 4725, 15387, 3274, 10762, 31695, 17320, + 18324, 12441, 16801, 27376, 22464, 7500, 5666, 18144, 15314, 31914, 31627, 6495, 5226, 31203, 2331, 4668, + 12650, 18275, 351, 7268, 31319, 30119, 7600, 2905, 13826, 11343, 13053, 15583, 30055, 31093, 5067, 761, + 9685, 11070, 21369, 27155, 3663, 26542, 20169, 12161, 15411, 30401, 7580, 31784, 8985, 29367, 20989, 14203, + 29694, 21167, 10337, 1706, 28578, 887, 3373, 19477, 14382, 675, 7033, 15111, 26138, 12252, 30996, 21409, + 25678, 18555, 13256, 23316, 22407, 16727, 991, 9236, 5373, 29402, 6117, 15241, 27715, 19291, 19888, 19847 +}; + +unsigned int U_Random( void ) +{ + glSeed *= 69069; + glSeed += seed_table[ glSeed & 0xff ]; + + return ( ++glSeed & 0x0fffffff ); +} + +void U_Srand( unsigned int seed ) +{ + glSeed = seed_table[ seed & 0xff ]; +} + +/* +===================== +UTIL_SharedRandomLong +===================== +*/ +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ) +{ + + unsigned int range; + + U_Srand( (int)seed + low + high ); + + range = high - low + 1; + if ( !(range - 1) ) + { + return low; + } + else + { + int offset; + int rnum; + + rnum = U_Random(); + + offset = rnum % range; + + return (low + offset); + } +} + +/* +===================== +UTIL_SharedRandomFloat +===================== +*/ +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ) +{ + // + unsigned int range; + + U_Srand( (int)seed + *(int *)&low + *(int *)&high ); + + U_Random(); + U_Random(); + + range = high - low; + if ( !range ) + { + return low; + } + else + { + int tensixrand; + float offset; + + tensixrand = U_Random() & 65535; + + offset = (float)tensixrand / 65536.0; + + return (low + offset * range ); + } +} + +void UTIL_ParametricRocket( entvars_t *pev, Vector vecOrigin, Vector vecAngles, edict_t *owner ) +{ + pev->startpos = vecOrigin; + // Trace out line to end pos + TraceResult tr; + UTIL_MakeVectors( vecAngles ); + UTIL_TraceLine( pev->startpos, pev->startpos + gpGlobals->v_forward * 8192, ignore_monsters, owner, &tr); + pev->endpos = tr.vecEndPos; + + // Now compute how long it will take based on current velocity + Vector vecTravel = pev->endpos - pev->startpos; + float travelTime = 0.0; + if ( pev->velocity.Length() > 0 ) + { + travelTime = vecTravel.Length() / pev->velocity.Length(); + } + pev->starttime = gpGlobals->time; + pev->impacttime = gpGlobals->time + travelTime; +} + +int g_groupmask = 0; +int g_groupop = 0; + +// Normal overrides +void UTIL_SetGroupTrace( int groupmask, int op ) +{ + g_groupmask = groupmask; + g_groupop = op; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + +void UTIL_UnsetGroupTrace( void ) +{ + g_groupmask = 0; + g_groupop = 0; + + ENGINE_SETGROUPMASK( 0, 0 ); +} + +// Smart version, it'll clean itself up when it pops off stack +UTIL_GroupTrace::UTIL_GroupTrace( int groupmask, int op ) +{ + m_oldgroupmask = g_groupmask; + m_oldgroupop = g_groupop; + + g_groupmask = groupmask; + g_groupop = op; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + +UTIL_GroupTrace::~UTIL_GroupTrace( void ) +{ + g_groupmask = m_oldgroupmask; + g_groupop = m_oldgroupop; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + +TYPEDESCRIPTION gEntvarsDescription[] = +{ + DEFINE_ENTITY_FIELD( classname, FIELD_STRING ), + DEFINE_ENTITY_GLOBAL_FIELD( globalname, FIELD_STRING ), + + DEFINE_ENTITY_FIELD( origin, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_FIELD( oldorigin, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_FIELD( velocity, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( basevelocity, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( movedir, FIELD_VECTOR ), + + DEFINE_ENTITY_FIELD( angles, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( avelocity, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( punchangle, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( v_angle, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( fixangle, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( idealpitch, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( pitch_speed, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( ideal_yaw, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( yaw_speed, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( modelindex, FIELD_INTEGER ), + DEFINE_ENTITY_GLOBAL_FIELD( model, FIELD_MODELNAME ), + + DEFINE_ENTITY_FIELD( viewmodel, FIELD_MODELNAME ), + DEFINE_ENTITY_FIELD( weaponmodel, FIELD_MODELNAME ), + + DEFINE_ENTITY_FIELD( absmin, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_FIELD( absmax, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_GLOBAL_FIELD( mins, FIELD_VECTOR ), + DEFINE_ENTITY_GLOBAL_FIELD( maxs, FIELD_VECTOR ), + DEFINE_ENTITY_GLOBAL_FIELD( size, FIELD_VECTOR ), + + DEFINE_ENTITY_FIELD( ltime, FIELD_TIME ), + DEFINE_ENTITY_FIELD( nextthink, FIELD_TIME ), + + DEFINE_ENTITY_FIELD( solid, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( movetype, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( skin, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( body, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( effects, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( gravity, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( friction, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( light_level, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( frame, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( scale, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( sequence, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( animtime, FIELD_TIME ), + DEFINE_ENTITY_FIELD( framerate, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( controller, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( blending, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( rendermode, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( renderamt, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( rendercolor, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( renderfx, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( health, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( frags, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( weapons, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( takedamage, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( deadflag, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( view_ofs, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( button, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( impulse, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( chain, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( dmg_inflictor, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( enemy, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( aiment, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( owner, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( groundentity, FIELD_EDICT ), + + DEFINE_ENTITY_FIELD( spawnflags, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( flags, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( colormap, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( team, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( max_health, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( teleport_time, FIELD_TIME ), + DEFINE_ENTITY_FIELD( armortype, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( armorvalue, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( waterlevel, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( watertype, FIELD_INTEGER ), + + // Having these fields be local to the individual levels makes it easier to test those levels individually. + DEFINE_ENTITY_GLOBAL_FIELD( target, FIELD_STRING ), + DEFINE_ENTITY_GLOBAL_FIELD( targetname, FIELD_STRING ), + DEFINE_ENTITY_FIELD( netname, FIELD_STRING ), + DEFINE_ENTITY_FIELD( message, FIELD_STRING ), + + DEFINE_ENTITY_FIELD( dmg_take, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( dmg_save, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( dmg, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( dmgtime, FIELD_TIME ), + + DEFINE_ENTITY_FIELD( noise, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise1, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise2, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise3, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( speed, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( air_finished, FIELD_TIME ), + DEFINE_ENTITY_FIELD( pain_finished, FIELD_TIME ), + DEFINE_ENTITY_FIELD( radsuit_finished, FIELD_TIME ), +}; + +#define ENTVARS_COUNT (sizeof(gEntvarsDescription)/sizeof(gEntvarsDescription[0])) + + +#ifdef DEBUG +edict_t *DBG_EntOfVars( const entvars_t *pev ) +{ + if (pev->pContainingEntity != NULL) + return pev->pContainingEntity; + ALERT(at_console, "entvars_t pContainingEntity is NULL, calling into engine"); + edict_t* pent = (*g_engfuncs.pfnFindEntityByVars)((entvars_t*)pev); + if (pent == NULL) + ALERT(at_console, "DAMN! Even the engine couldn't FindEntityByVars!"); + ((entvars_t *)pev)->pContainingEntity = pent; + return pent; +} +#endif //DEBUG + + +#ifdef DEBUG + void +DBG_AssertFunction( + BOOL fExpr, + const char* szExpr, + const char* szFile, + int szLine, + const char* szMessage) + { + if (fExpr) + return; + char szOut[512]; + if (szMessage != NULL) + sprintf(szOut, "ASSERT FAILED:\n %s \n(%s@%d)\n%s", szExpr, szFile, szLine, szMessage); + else + sprintf(szOut, "ASSERT FAILED:\n %s \n(%s@%d)", szExpr, szFile, szLine); +// ALERT(at_console, szOut); + } +#endif // DEBUG + +BOOL UTIL_GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) +{ + return g_pGameRules->GetNextBestWeapon( pPlayer, pCurrentWeapon ); +} + +// ripped this out of the engine +float UTIL_AngleMod(float a) +{ + if (a < 0) + { + a = a + 360 * ((int)(a / 360) + 1); + } + else if (a >= 360) + { + a = a - 360 * ((int)(a / 360)); + } + // a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + +float UTIL_AngleDiff( float destAngle, float srcAngle ) +{ + float delta; + + delta = destAngle - srcAngle; + if ( destAngle > srcAngle ) + { + if ( delta >= 180 ) + delta -= 360; + } + else + { + if ( delta <= -180 ) + delta += 360; + } + return delta; +} + +Vector UTIL_VecToAngles( const Vector &vec ) +{ + float rgflVecOut[3]; + VEC_TO_ANGLES(vec, rgflVecOut); + return Vector(rgflVecOut); +} + +// float UTIL_MoveToOrigin( edict_t *pent, const Vector vecGoal, float flDist, int iMoveType ) +void UTIL_MoveToOrigin( edict_t *pent, const Vector &vecGoal, float flDist, int iMoveType ) +{ + float rgfl[3]; + vecGoal.CopyToArray(rgfl); +// return MOVE_TO_ORIGIN ( pent, rgfl, flDist, iMoveType ); + MOVE_TO_ORIGIN ( pent, rgfl, flDist, iMoveType ); +} + + +int UTIL_EntitiesInBox( CBaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + int count; + + count = 0; + + if ( !pEdict ) + return count; + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + if ( pEdict->free ) // Not in use + continue; + + if ( flagMask && !(pEdict->v.flags & flagMask) ) // Does it meet the criteria? + continue; + + if ( mins.x > pEdict->v.absmax.x || + mins.y > pEdict->v.absmax.y || + mins.z > pEdict->v.absmax.z || + maxs.x < pEdict->v.absmin.x || + maxs.y < pEdict->v.absmin.y || + maxs.z < pEdict->v.absmin.z ) + continue; + + pEntity = CBaseEntity::Instance(pEdict); + if ( !pEntity ) + continue; + + pList[ count ] = pEntity; + count++; + + if ( count >= listMax ) + return count; + } + + return count; +} + + +int UTIL_MonstersInSphere( CBaseEntity **pList, int listMax, const Vector ¢er, float radius ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + int count; + float distance, delta; + + count = 0; + float radiusSquared = radius * radius; + + if ( !pEdict ) + return count; + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + if ( pEdict->free ) // Not in use + continue; + + if ( !(pEdict->v.flags & (FL_CLIENT|FL_MONSTER)) ) // Not a client/monster ? + continue; + + // Use origin for X & Y since they are centered for all monsters + // Now X + delta = center.x - pEdict->v.origin.x;//(pEdict->v.absmin.x + pEdict->v.absmax.x)*0.5; + delta *= delta; + + if ( delta > radiusSquared ) + continue; + distance = delta; + + // Now Y + delta = center.y - pEdict->v.origin.y;//(pEdict->v.absmin.y + pEdict->v.absmax.y)*0.5; + delta *= delta; + + distance += delta; + if ( distance > radiusSquared ) + continue; + + // Now Z + delta = center.z - (pEdict->v.absmin.z + pEdict->v.absmax.z)*0.5; + delta *= delta; + + distance += delta; + if ( distance > radiusSquared ) + continue; + + pEntity = CBaseEntity::Instance(pEdict); + if ( !pEntity ) + continue; + + pList[ count ] = pEntity; + count++; + + if ( count >= listMax ) + return count; + } + + + return count; +} + + +CBaseEntity *UTIL_FindEntityInSphere( CBaseEntity *pStartEntity, const Vector &vecCenter, float flRadius ) +{ + edict_t *pentEntity; + + if (pStartEntity) + pentEntity = pStartEntity->edict(); + else + pentEntity = NULL; + + pentEntity = FIND_ENTITY_IN_SPHERE( pentEntity, vecCenter, flRadius); + + if (!FNullEnt(pentEntity)) + return CBaseEntity::Instance(pentEntity); + return NULL; +} + + +CBaseEntity *UTIL_FindEntityByString( CBaseEntity *pStartEntity, const char *szKeyword, const char *szValue ) +{ + edict_t *pentEntity; + + if (pStartEntity) + pentEntity = pStartEntity->edict(); + else + pentEntity = NULL; + + pentEntity = FIND_ENTITY_BY_STRING( pentEntity, szKeyword, szValue ); + + if (!FNullEnt(pentEntity)) + return CBaseEntity::Instance(pentEntity); + return NULL; +} + +CBaseEntity *UTIL_FindEntityByClassname( CBaseEntity *pStartEntity, const char *szName ) +{ + return UTIL_FindEntityByString( pStartEntity, "classname", szName ); +} + +CBaseEntity *UTIL_FindEntityByTargetname( CBaseEntity *pStartEntity, const char *szName ) +{ + return UTIL_FindEntityByString( pStartEntity, "targetname", szName ); +} + + +CBaseEntity *UTIL_FindEntityGeneric( const char *szWhatever, Vector &vecSrc, float flRadius ) +{ + CBaseEntity *pEntity = NULL; + + pEntity = UTIL_FindEntityByTargetname( NULL, szWhatever ); + if (pEntity) + return pEntity; + + CBaseEntity *pSearch = NULL; + float flMaxDist2 = flRadius * flRadius; + while ((pSearch = UTIL_FindEntityByClassname( pSearch, szWhatever )) != NULL) + { + float flDist2 = (pSearch->pev->origin - vecSrc).Length(); + flDist2 = flDist2 * flDist2; + if (flMaxDist2 > flDist2) + { + pEntity = pSearch; + flMaxDist2 = flDist2; + } + } + return pEntity; +} + + +// returns a CBaseEntity pointer to a player by index. Only returns if the player is spawned and connected +// otherwise returns NULL +// Index is 1 based +CBaseEntity *UTIL_PlayerByIndex( int playerIndex ) +{ + CBaseEntity *pPlayer = NULL; + + if ( playerIndex > 0 && playerIndex <= gpGlobals->maxClients ) + { + edict_t *pPlayerEdict = INDEXENT( playerIndex ); + if ( pPlayerEdict && !pPlayerEdict->free ) + { + pPlayer = CBaseEntity::Instance( pPlayerEdict ); + } + } + + return pPlayer; +} + + +void UTIL_MakeVectors( const Vector &vecAngles ) +{ + MAKE_VECTORS( vecAngles ); +} + + +void UTIL_MakeAimVectors( const Vector &vecAngles ) +{ + float rgflVec[3]; + vecAngles.CopyToArray(rgflVec); + rgflVec[0] = -rgflVec[0]; + MAKE_VECTORS(rgflVec); +} + + +#define SWAP(a,b,temp) ((temp)=(a),(a)=(b),(b)=(temp)) + +void UTIL_MakeInvVectors( const Vector &vec, globalvars_t *pgv ) +{ + MAKE_VECTORS(vec); + + float tmp; + pgv->v_right = pgv->v_right * -1; + + SWAP(pgv->v_forward.y, pgv->v_right.x, tmp); + SWAP(pgv->v_forward.z, pgv->v_up.x, tmp); + SWAP(pgv->v_right.z, pgv->v_up.y, tmp); +} + + +void UTIL_EmitAmbientSound( edict_t *entity, const Vector &vecOrigin, const char *samp, float vol, float attenuation, int fFlags, int pitch ) +{ + float rgfl[3]; + vecOrigin.CopyToArray(rgfl); + + if (samp && *samp == '!') + { + char name[32]; + if (SENTENCEG_Lookup(samp, name) >= 0) + EMIT_AMBIENT_SOUND(entity, rgfl, name, vol, attenuation, fFlags, pitch); + } + else + EMIT_AMBIENT_SOUND(entity, rgfl, samp, vol, attenuation, fFlags, pitch); +} + +static unsigned short FixedUnsigned16( float value, float scale ) +{ + int output; + + output = value * scale; + if ( output < 0 ) + output = 0; + if ( output > 0xFFFF ) + output = 0xFFFF; + + return (unsigned short)output; +} + +static short FixedSigned16( float value, float scale ) +{ + int output; + + output = value * scale; + + if ( output > 32767 ) + output = 32767; + + if ( output < -32768 ) + output = -32768; + + return (short)output; +} + +// Shake the screen of all clients within radius +// radius == 0, shake all clients +// UNDONE: Allow caller to shake clients not ONGROUND? +// UNDONE: Fix falloff model (disabled)? +// UNDONE: Affect user controls? +void UTIL_ScreenShake( const Vector ¢er, float amplitude, float frequency, float duration, float radius ) +{ + int i; + float localAmplitude; + ScreenShake shake; + + shake.duration = FixedUnsigned16( duration, 1<<12 ); // 4.12 fixed + shake.frequency = FixedUnsigned16( frequency, 1<<8 ); // 8.8 fixed + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + if ( !pPlayer || !(pPlayer->pev->flags & FL_ONGROUND) ) // Don't shake if not onground + continue; + + localAmplitude = 0; + + if ( radius <= 0 ) + localAmplitude = amplitude; + else + { + Vector delta = center - pPlayer->pev->origin; + float distance = delta.Length(); + + // Had to get rid of this falloff - it didn't work well + if ( distance < radius ) + localAmplitude = amplitude;//radius - distance; + } + if ( localAmplitude ) + { + shake.amplitude = FixedUnsigned16( localAmplitude, 1<<12 ); // 4.12 fixed + + MESSAGE_BEGIN( MSG_ONE, gmsgShake, NULL, pPlayer->edict() ); // use the magic #1 for "one client" + + WRITE_SHORT( shake.amplitude ); // shake amount + WRITE_SHORT( shake.duration ); // shake lasts this long + WRITE_SHORT( shake.frequency ); // shake noise frequency + + MESSAGE_END(); + } + } +} + + + +void UTIL_ScreenShakeAll( const Vector ¢er, float amplitude, float frequency, float duration ) +{ + UTIL_ScreenShake( center, amplitude, frequency, duration, 0 ); +} + + +void UTIL_ScreenFadeBuild( ScreenFade &fade, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + fade.duration = FixedUnsigned16( fadeTime, 1<<12 ); // 4.12 fixed + fade.holdTime = FixedUnsigned16( fadeHold, 1<<12 ); // 4.12 fixed + fade.r = (int)color.x; + fade.g = (int)color.y; + fade.b = (int)color.z; + fade.a = alpha; + fade.fadeFlags = flags; +} + + +void UTIL_ScreenFadeWrite( const ScreenFade &fade, CBaseEntity *pEntity ) +{ + if ( !pEntity || !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsgFade, NULL, pEntity->edict() ); // use the magic #1 for "one client" + + WRITE_SHORT( fade.duration ); // fade lasts this long + WRITE_SHORT( fade.holdTime ); // fade lasts this long + WRITE_SHORT( fade.fadeFlags ); // fade type (in / out) + WRITE_BYTE( fade.r ); // fade red + WRITE_BYTE( fade.g ); // fade green + WRITE_BYTE( fade.b ); // fade blue + WRITE_BYTE( fade.a ); // fade blue + + MESSAGE_END(); +} + + +void UTIL_ScreenFadeAll( const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + int i; + ScreenFade fade; + + + UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, alpha, flags ); + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + UTIL_ScreenFadeWrite( fade, pPlayer ); + } +} + + +void UTIL_ScreenFade( CBaseEntity *pEntity, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + ScreenFade fade; + + UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, alpha, flags ); + UTIL_ScreenFadeWrite( fade, pEntity ); +} + + +void UTIL_HudMessage( CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage ) +{ + if ( !pEntity || !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, SVC_TEMPENTITY, NULL, pEntity->edict() ); + WRITE_BYTE( TE_TEXTMESSAGE ); + WRITE_BYTE( textparms.channel & 0xFF ); + + WRITE_SHORT( FixedSigned16( textparms.x, 1<<13 ) ); + WRITE_SHORT( FixedSigned16( textparms.y, 1<<13 ) ); + WRITE_BYTE( textparms.effect ); + + WRITE_BYTE( textparms.r1 ); + WRITE_BYTE( textparms.g1 ); + WRITE_BYTE( textparms.b1 ); + WRITE_BYTE( textparms.a1 ); + + WRITE_BYTE( textparms.r2 ); + WRITE_BYTE( textparms.g2 ); + WRITE_BYTE( textparms.b2 ); + WRITE_BYTE( textparms.a2 ); + + WRITE_SHORT( FixedUnsigned16( textparms.fadeinTime, 1<<8 ) ); + WRITE_SHORT( FixedUnsigned16( textparms.fadeoutTime, 1<<8 ) ); + WRITE_SHORT( FixedUnsigned16( textparms.holdTime, 1<<8 ) ); + + if ( textparms.effect == 2 ) + WRITE_SHORT( FixedUnsigned16( textparms.fxTime, 1<<8 ) ); + + if ( strlen( pMessage ) < 512 ) + { + WRITE_STRING( pMessage ); + } + else + { + char tmp[512]; + strncpy( tmp, pMessage, 511 ); + tmp[511] = 0; + WRITE_STRING( tmp ); + } + MESSAGE_END(); +} + +void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage ) +{ + int i; + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + UTIL_HudMessage( pPlayer, textparms, pMessage ); + } +} + + +extern int gmsgTextMsg, gmsgSayText; +void UTIL_ClientPrintAll( int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) +{ + MESSAGE_BEGIN( MSG_ALL, gmsgTextMsg ); + WRITE_BYTE( msg_dest ); + WRITE_STRING( msg_name ); + + if ( param1 ) + WRITE_STRING( param1 ); + if ( param2 ) + WRITE_STRING( param2 ); + if ( param3 ) + WRITE_STRING( param3 ); + if ( param4 ) + WRITE_STRING( param4 ); + + MESSAGE_END(); +} + +void ClientPrint( entvars_t *client, int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgTextMsg, NULL, client ); + WRITE_BYTE( msg_dest ); + WRITE_STRING( msg_name ); + + if ( param1 ) + WRITE_STRING( param1 ); + if ( param2 ) + WRITE_STRING( param2 ); + if ( param3 ) + WRITE_STRING( param3 ); + if ( param4 ) + WRITE_STRING( param4 ); + + MESSAGE_END(); +} + +void UTIL_SayText( const char *pText, CBaseEntity *pEntity ) +{ + if ( !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, pEntity->edict() ); + WRITE_BYTE( pEntity->entindex() ); + WRITE_STRING( pText ); + MESSAGE_END(); +} + +void UTIL_SayTextAll( const char *pText, CBaseEntity *pEntity ) +{ + MESSAGE_BEGIN( MSG_ALL, gmsgSayText, NULL ); + WRITE_BYTE( pEntity->entindex() ); + WRITE_STRING( pText ); + MESSAGE_END(); +} + + +char *UTIL_dtos1( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos2( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos3( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos4( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +void UTIL_ShowMessage( const char *pString, CBaseEntity *pEntity ) +{ + if ( !pEntity || !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsgHudText, NULL, pEntity->edict() ); + WRITE_STRING( pString ); + MESSAGE_END(); +} + + +void UTIL_ShowMessageAll( const char *pString ) +{ + int i; + + // loop through all players + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + UTIL_ShowMessage( pString, pPlayer ); + } +} + +// Overloaded to add IGNORE_GLASS +void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, IGNORE_GLASS ignoreGlass, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_LINE( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE) | (ignoreGlass?0x100:0), pentIgnore, ptr ); +} + + +void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_LINE( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE), pentIgnore, ptr ); +} + + +void UTIL_TraceHull( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, int hullNumber, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_HULL( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE), hullNumber, pentIgnore, ptr ); +} + +void UTIL_TraceModel( const Vector &vecStart, const Vector &vecEnd, int hullNumber, edict_t *pentModel, TraceResult *ptr ) +{ + g_engfuncs.pfnTraceModel( vecStart, vecEnd, hullNumber, pentModel, ptr ); +} + + +TraceResult UTIL_GetGlobalTrace( ) +{ + TraceResult tr; + + tr.fAllSolid = gpGlobals->trace_allsolid; + tr.fStartSolid = gpGlobals->trace_startsolid; + tr.fInOpen = gpGlobals->trace_inopen; + tr.fInWater = gpGlobals->trace_inwater; + tr.flFraction = gpGlobals->trace_fraction; + tr.flPlaneDist = gpGlobals->trace_plane_dist; + tr.pHit = gpGlobals->trace_ent; + tr.vecEndPos = gpGlobals->trace_endpos; + tr.vecPlaneNormal = gpGlobals->trace_plane_normal; + tr.iHitgroup = gpGlobals->trace_hitgroup; + return tr; +} + + +void UTIL_SetSize( entvars_t *pev, const Vector &vecMin, const Vector &vecMax ) +{ + SET_SIZE( ENT(pev), vecMin, vecMax ); +} + + +float UTIL_VecToYaw( const Vector &vec ) +{ + return VEC_TO_YAW(vec); +} + + +void UTIL_SetOrigin( entvars_t *pev, const Vector &vecOrigin ) +{ + SET_ORIGIN(ENT(pev), vecOrigin ); +} + +void UTIL_ParticleEffect( const Vector &vecOrigin, const Vector &vecDirection, ULONG ulColor, ULONG ulCount ) +{ + PARTICLE_EFFECT( vecOrigin, vecDirection, (float)ulColor, (float)ulCount ); +} + + +float UTIL_Approach( float target, float value, float speed ) +{ + float delta = target - value; + + if ( delta > speed ) + value += speed; + else if ( delta < -speed ) + value -= speed; + else + value = target; + + return value; +} + + +float UTIL_ApproachAngle( float target, float value, float speed ) +{ + target = UTIL_AngleMod( target ); + value = UTIL_AngleMod( target ); + + float delta = target - value; + + // Speed is assumed to be positive + if ( speed < 0 ) + speed = -speed; + + if ( delta < -180 ) + delta += 360; + else if ( delta > 180 ) + delta -= 360; + + if ( delta > speed ) + value += speed; + else if ( delta < -speed ) + value -= speed; + else + value = target; + + return value; +} + + +float UTIL_AngleDistance( float next, float cur ) +{ + float delta = next - cur; + + if ( delta < -180 ) + delta += 360; + else if ( delta > 180 ) + delta -= 360; + + return delta; +} + + +float UTIL_SplineFraction( float value, float scale ) +{ + value = scale * value; + float valueSquared = value * value; + + // Nice little ease-in, ease-out spline-like curve + return 3 * valueSquared - 2 * valueSquared * value; +} + + +char* UTIL_VarArgs( char *format, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start (argptr, format); + vsprintf (string, format,argptr); + va_end (argptr); + + return string; +} + +Vector UTIL_GetAimVector( edict_t *pent, float flSpeed ) +{ + Vector tmp; + GET_AIM_VECTOR(pent, flSpeed, tmp); + return tmp; +} + +int UTIL_IsMasterTriggered(string_t sMaster, CBaseEntity *pActivator) +{ + if (sMaster) + { + edict_t *pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(sMaster)); + + if ( !FNullEnt(pentTarget) ) + { + CBaseEntity *pMaster = CBaseEntity::Instance(pentTarget); + if ( pMaster && (pMaster->ObjectCaps() & FCAP_MASTER) ) + return pMaster->IsTriggered( pActivator ); + } + + ALERT(at_console, "Master was null or not a master!\n"); + } + + // if this isn't a master entity, just say yes. + return 1; +} + +BOOL UTIL_ShouldShowBlood( int color ) +{ + if ( color != DONT_BLEED ) + { + if ( color == BLOOD_COLOR_RED ) + { + if ( CVAR_GET_FLOAT("violence_hblood") != 0 ) + return TRUE; + } + else + { + if ( CVAR_GET_FLOAT("violence_ablood") != 0 ) + return TRUE; + } + } + return FALSE; +} + +int UTIL_PointContents( const Vector &vec ) +{ + return POINT_CONTENTS(vec); +} + +void UTIL_BloodStream( const Vector &origin, const Vector &direction, int color, int amount ) +{ + if ( !UTIL_ShouldShowBlood( color ) ) + return; + + if ( g_Language == LANGUAGE_GERMAN && color == BLOOD_COLOR_RED ) + color = 0; + + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, origin ); + WRITE_BYTE( TE_BLOODSTREAM ); + WRITE_COORD( origin.x ); + WRITE_COORD( origin.y ); + WRITE_COORD( origin.z ); + WRITE_COORD( direction.x ); + WRITE_COORD( direction.y ); + WRITE_COORD( direction.z ); + WRITE_BYTE( color ); + WRITE_BYTE( min( amount, 255 ) ); + MESSAGE_END(); +} + +void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, int amount ) +{ + if ( !UTIL_ShouldShowBlood( color ) ) + return; + + if ( color == DONT_BLEED || amount == 0 ) + return; + + if ( g_Language == LANGUAGE_GERMAN && color == BLOOD_COLOR_RED ) + color = 0; + + if ( g_pGameRules->IsMultiplayer() ) + { + // scale up blood effect in multiplayer for better visibility + amount *= 2; + } + + if ( amount > 255 ) + amount = 255; + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, origin ); + WRITE_BYTE( TE_BLOODSPRITE ); + WRITE_COORD( origin.x); // pos + WRITE_COORD( origin.y); + WRITE_COORD( origin.z); + WRITE_SHORT( g_sModelIndexBloodSpray ); // initial sprite model + WRITE_SHORT( g_sModelIndexBloodDrop ); // droplet sprite models + WRITE_BYTE( color ); // color index into host_basepal + WRITE_BYTE( min( max( 3, amount / 10 ), 16 ) ); // size + MESSAGE_END(); +} + +Vector UTIL_RandomBloodVector( void ) +{ + Vector direction; + + direction.x = RANDOM_FLOAT ( -1, 1 ); + direction.y = RANDOM_FLOAT ( -1, 1 ); + direction.z = RANDOM_FLOAT ( 0, 1 ); + + return direction; +} + + +void UTIL_BloodDecalTrace( TraceResult *pTrace, int bloodColor ) +{ + if ( UTIL_ShouldShowBlood( bloodColor ) ) + { + if ( bloodColor == BLOOD_COLOR_RED ) + UTIL_DecalTrace( pTrace, DECAL_BLOOD1 + RANDOM_LONG(0,5) ); + else + UTIL_DecalTrace( pTrace, DECAL_YBLOOD1 + RANDOM_LONG(0,5) ); + } +} + + +void UTIL_DecalTrace( TraceResult *pTrace, int decalNumber ) +{ + short entityIndex; + int index; + int message; + + if ( decalNumber < 0 ) + return; + + index = gDecals[ decalNumber ].index; + + if ( index < 0 ) + return; + + if (pTrace->flFraction == 1.0) + return; + + // Only decal BSP models + if ( pTrace->pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( pTrace->pHit ); + if ( pEntity && !pEntity->IsBSPModel() ) + return; + entityIndex = ENTINDEX( pTrace->pHit ); + } + else + entityIndex = 0; + + message = TE_DECAL; + if ( entityIndex != 0 ) + { + if ( index > 255 ) + { + message = TE_DECALHIGH; + index -= 256; + } + } + else + { + message = TE_WORLDDECAL; + if ( index > 255 ) + { + message = TE_WORLDDECALHIGH; + index -= 256; + } + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( message ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_BYTE( index ); + if ( entityIndex ) + WRITE_SHORT( entityIndex ); + MESSAGE_END(); +} + +/* +============== +UTIL_PlayerDecalTrace + +A player is trying to apply his custom decal for the spray can. +Tell connected clients to display it, or use the default spray can decal +if the custom can't be loaded. +============== +*/ +void UTIL_PlayerDecalTrace( TraceResult *pTrace, int playernum, int decalNumber, BOOL bIsCustom ) +{ + int index; + + if (!bIsCustom) + { + if ( decalNumber < 0 ) + return; + + index = gDecals[ decalNumber ].index; + if ( index < 0 ) + return; + } + else + index = decalNumber; + + if (pTrace->flFraction == 1.0) + return; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_PLAYERDECAL ); + WRITE_BYTE ( playernum ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_SHORT( (short)ENTINDEX(pTrace->pHit) ); + WRITE_BYTE( index ); + MESSAGE_END(); +} + +void UTIL_GunshotDecalTrace( TraceResult *pTrace, int decalNumber ) +{ + if ( decalNumber < 0 ) + return; + + int index = gDecals[ decalNumber ].index; + if ( index < 0 ) + return; + + if (pTrace->flFraction == 1.0) + return; + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pTrace->vecEndPos ); + WRITE_BYTE( TE_GUNSHOTDECAL ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_SHORT( (short)ENTINDEX(pTrace->pHit) ); + WRITE_BYTE( index ); + MESSAGE_END(); +} + + +void UTIL_Sparks( const Vector &position, edict_t * ed ) +{ +// MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, position ); +// WRITE_BYTE( TE_SPARKS ); +// WRITE_COORD( position.x ); +// WRITE_COORD( position.y ); +// WRITE_COORD( position.z ); +// MESSAGE_END(); + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer * pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if ( + pPlayer && + ( pPlayer->m_bHasDisconnected != TRUE ) && + ( !ed || ( pPlayer->pev->groupinfo & VARS(ed)->groupinfo ) ) + ) + { + MESSAGE_BEGIN( MSG_ONE, SVC_TEMPENTITY, position, pPlayer->edict() ); + WRITE_BYTE( TE_SPARKS ); + WRITE_COORD( position.x ); + WRITE_COORD( position.y ); + WRITE_COORD( position.z ); + MESSAGE_END(); + } + } +} + + +void UTIL_Ricochet( const Vector &position, float scale ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, position ); + WRITE_BYTE( TE_ARMOR_RICOCHET ); + WRITE_COORD( position.x ); + WRITE_COORD( position.y ); + WRITE_COORD( position.z ); + WRITE_BYTE( (int)(scale*10) ); + MESSAGE_END(); +} + + +BOOL UTIL_TeamsMatch( const char *pTeamName1, const char *pTeamName2 ) +{ + // Everyone matches unless it's teamplay + if ( !g_pGameRules->IsTeamplay() ) + return TRUE; + + // Both on a team? + if ( *pTeamName1 != 0 && *pTeamName2 != 0 ) + { + if ( !stricmp( pTeamName1, pTeamName2 ) ) // Same Team? + return TRUE; + } + + return FALSE; +} + + +void UTIL_StringToVector( float *pVector, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + + strcpy( tempString, pString ); + pstr = pfront = tempString; + + for ( j = 0; j < 3; j++ ) // lifted from pr_edict.c + { + pVector[j] = atof( pfront ); + + while ( *pstr && *pstr != ' ' ) + pstr++; + if (!*pstr) + break; + pstr++; + pfront = pstr; + } + if (j < 2) + { + /* + ALERT( at_error, "Bad field in entity!! %s:%s == \"%s\"\n", + pkvd->szClassName, pkvd->szKeyName, pkvd->szValue ); + */ + for (j = j+1;j < 3; j++) + pVector[j] = 0; + } +} + + +void UTIL_StringToIntArray( int *pVector, int count, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + + strcpy( tempString, pString ); + pstr = pfront = tempString; + + for ( j = 0; j < count; j++ ) // lifted from pr_edict.c + { + pVector[j] = atoi( pfront ); + + while ( *pstr && *pstr != ' ' ) + pstr++; + if (!*pstr) + break; + pstr++; + pfront = pstr; + } + + for ( j++; j < count; j++ ) + { + pVector[j] = 0; + } +} + +Vector UTIL_ClampVectorToBox( const Vector &input, const Vector &clampSize ) +{ + Vector sourceVector = input; + + if ( sourceVector.x > clampSize.x ) + sourceVector.x -= clampSize.x; + else if ( sourceVector.x < -clampSize.x ) + sourceVector.x += clampSize.x; + else + sourceVector.x = 0; + + if ( sourceVector.y > clampSize.y ) + sourceVector.y -= clampSize.y; + else if ( sourceVector.y < -clampSize.y ) + sourceVector.y += clampSize.y; + else + sourceVector.y = 0; + + if ( sourceVector.z > clampSize.z ) + sourceVector.z -= clampSize.z; + else if ( sourceVector.z < -clampSize.z ) + sourceVector.z += clampSize.z; + else + sourceVector.z = 0; + + return sourceVector.Normalize(); +} + + +float UTIL_WaterLevel( const Vector &position, float minz, float maxz ) +{ + Vector midUp = position; + midUp.z = minz; + + if (UTIL_PointContents(midUp) != CONTENTS_WATER) + return minz; + + midUp.z = maxz; + if (UTIL_PointContents(midUp) == CONTENTS_WATER) + return maxz; + + float diff = maxz - minz; + while (diff > 1.0) + { + midUp.z = minz + diff/2.0; + if (UTIL_PointContents(midUp) == CONTENTS_WATER) + { + minz = midUp.z; + } + else + { + maxz = midUp.z; + } + diff = maxz - minz; + } + + return midUp.z; +} + + +extern DLL_GLOBAL short g_sModelIndexBubbles;// holds the index for the bubbles model + +void UTIL_Bubbles( Vector mins, Vector maxs, int count ) +{ + Vector mid = (mins + maxs) * 0.5; + + float flHeight = UTIL_WaterLevel( mid, mid.z, mid.z + 1024 ); + flHeight = flHeight - mins.z; + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, mid ); + WRITE_BYTE( TE_BUBBLES ); + WRITE_COORD( mins.x ); // mins + WRITE_COORD( mins.y ); + WRITE_COORD( mins.z ); + WRITE_COORD( maxs.x ); // maxz + WRITE_COORD( maxs.y ); + WRITE_COORD( maxs.z ); + WRITE_COORD( flHeight ); // height + WRITE_SHORT( g_sModelIndexBubbles ); + WRITE_BYTE( count ); // count + WRITE_COORD( 8 ); // speed + MESSAGE_END(); +} + +void UTIL_BubbleTrail( Vector from, Vector to, int count ) +{ + float flHeight = UTIL_WaterLevel( from, from.z, from.z + 256 ); + flHeight = flHeight - from.z; + + if (flHeight < 8) + { + flHeight = UTIL_WaterLevel( to, to.z, to.z + 256 ); + flHeight = flHeight - to.z; + if (flHeight < 8) + return; + + // UNDONE: do a ploink sound + flHeight = flHeight + to.z - from.z; + } + + if (count > 255) + count = 255; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BUBBLETRAIL ); + WRITE_COORD( from.x ); // mins + WRITE_COORD( from.y ); + WRITE_COORD( from.z ); + WRITE_COORD( to.x ); // maxz + WRITE_COORD( to.y ); + WRITE_COORD( to.z ); + WRITE_COORD( flHeight ); // height + WRITE_SHORT( g_sModelIndexBubbles ); + WRITE_BYTE( count ); // count + WRITE_COORD( 8 ); // speed + MESSAGE_END(); +} + + +void UTIL_Remove( CBaseEntity *pEntity ) +{ + if ( !pEntity ) + return; + + pEntity->UpdateOnRemove(); + pEntity->pev->flags |= FL_KILLME; + pEntity->pev->targetname = 0; +} + + +BOOL UTIL_IsValidEntity( edict_t *pent ) +{ + if ( !pent || pent->free || (pent->v.flags & FL_KILLME) ) + return FALSE; + return TRUE; +} + + +void UTIL_PrecacheOther( const char *szClassname ) +{ + edict_t *pent; + + pent = CREATE_NAMED_ENTITY( MAKE_STRING( szClassname ) ); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in UTIL_PrecacheOther\n" ); + return; + } + + CBaseEntity *pEntity = CBaseEntity::Instance (VARS( pent )); + if (pEntity) + pEntity->Precache( ); + REMOVE_ENTITY(pent); +} + +//========================================================= +// UTIL_LogPrintf - Prints a logged message to console. +// Preceded by LOG: ( timestamp ) < message > +//========================================================= +void UTIL_LogPrintf( char *fmt, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start ( argptr, fmt ); + vsprintf ( string, fmt, argptr ); + va_end ( argptr ); + + // Print to server console + ALERT( at_logged, "%s", string ); +} + +//========================================================= +// UTIL_DotPoints - returns the dot product of a line from +// src to check and vecdir. +//========================================================= +float UTIL_DotPoints ( const Vector &vecSrc, const Vector &vecCheck, const Vector &vecDir ) +{ + Vector2D vec2LOS; + + vec2LOS = ( vecCheck - vecSrc ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + return DotProduct (vec2LOS , ( vecDir.Make2D() ) ); +} + + +//========================================================= +// UTIL_StripToken - for redundant keynames +//========================================================= +void UTIL_StripToken( const char *pKey, char *pDest ) +{ + int i = 0; + + while ( pKey[i] && pKey[i] != '#' ) + { + pDest[i] = pKey[i]; + i++; + } + pDest[i] = 0; +} + + +// -------------------------------------------------------------- +// +// CSave +// +// -------------------------------------------------------------- +static int gSizes[FIELD_TYPECOUNT] = +{ + sizeof(float), // FIELD_FLOAT + sizeof(int), // FIELD_STRING + sizeof(int), // FIELD_ENTITY + sizeof(int), // FIELD_CLASSPTR + sizeof(int), // FIELD_EHANDLE + sizeof(int), // FIELD_entvars_t + sizeof(int), // FIELD_EDICT + sizeof(float)*3, // FIELD_VECTOR + sizeof(float)*3, // FIELD_POSITION_VECTOR + sizeof(int *), // FIELD_POINTER + sizeof(int), // FIELD_INTEGER + sizeof(int *), // FIELD_FUNCTION + sizeof(int), // FIELD_BOOLEAN + sizeof(short), // FIELD_SHORT + sizeof(char), // FIELD_CHARACTER + sizeof(float), // FIELD_TIME + sizeof(int), // FIELD_MODELNAME + sizeof(int), // FIELD_SOUNDNAME +}; + + +// Base class includes common SAVERESTOREDATA pointer, and manages the entity table +CSaveRestoreBuffer :: CSaveRestoreBuffer( void ) +{ + m_pdata = NULL; +} + + +CSaveRestoreBuffer :: CSaveRestoreBuffer( SAVERESTOREDATA *pdata ) +{ + m_pdata = pdata; +} + + +CSaveRestoreBuffer :: ~CSaveRestoreBuffer( void ) +{ +} + +int CSaveRestoreBuffer :: EntityIndex( CBaseEntity *pEntity ) +{ + if ( pEntity == NULL ) + return -1; + return EntityIndex( pEntity->pev ); +} + + +int CSaveRestoreBuffer :: EntityIndex( entvars_t *pevLookup ) +{ + if ( pevLookup == NULL ) + return -1; + return EntityIndex( ENT( pevLookup ) ); +} + +int CSaveRestoreBuffer :: EntityIndex( EOFFSET eoLookup ) +{ + return EntityIndex( ENT( eoLookup ) ); +} + + +int CSaveRestoreBuffer :: EntityIndex( edict_t *pentLookup ) +{ + if ( !m_pdata || pentLookup == NULL ) + return -1; + + int i; + ENTITYTABLE *pTable; + + for ( i = 0; i < m_pdata->tableCount; i++ ) + { + pTable = m_pdata->pTable + i; + if ( pTable->pent == pentLookup ) + return i; + } + return -1; +} + + +edict_t *CSaveRestoreBuffer :: EntityFromIndex( int entityIndex ) +{ + if ( !m_pdata || entityIndex < 0 ) + return NULL; + + int i; + ENTITYTABLE *pTable; + + for ( i = 0; i < m_pdata->tableCount; i++ ) + { + pTable = m_pdata->pTable + i; + if ( pTable->id == entityIndex ) + return pTable->pent; + } + return NULL; +} + + +int CSaveRestoreBuffer :: EntityFlagsSet( int entityIndex, int flags ) +{ + if ( !m_pdata || entityIndex < 0 ) + return 0; + if ( entityIndex > m_pdata->tableCount ) + return 0; + + m_pdata->pTable[ entityIndex ].flags |= flags; + + return m_pdata->pTable[ entityIndex ].flags; +} + + +void CSaveRestoreBuffer :: BufferRewind( int size ) +{ + if ( !m_pdata ) + return; + + if ( m_pdata->size < size ) + size = m_pdata->size; + + m_pdata->pCurrentData -= size; + m_pdata->size -= size; +} + +#ifndef _WIN32 +extern "C" { +unsigned _rotr ( unsigned val, int shift) +{ + register unsigned lobit; /* non-zero means lo bit set */ + register unsigned num = val; /* number to rotate */ + + shift &= 0x1f; /* modulo 32 -- this will also make + negative shifts work */ + + while (shift--) { + lobit = num & 1; /* get high bit */ + num >>= 1; /* shift right one bit */ + if (lobit) + num |= 0x80000000; /* set hi bit if lo bit was set */ + } + + return num; +} +} +#endif + +unsigned int CSaveRestoreBuffer :: HashString( const char *pszToken ) +{ + unsigned int hash = 0; + + while ( *pszToken ) + hash = _rotr( hash, 4 ) ^ *pszToken++; + + return hash; +} + +unsigned short CSaveRestoreBuffer :: TokenHash( const char *pszToken ) +{ + unsigned short hash = (unsigned short)(HashString( pszToken ) % (unsigned)m_pdata->tokenCount ); + +#if _DEBUG + static int tokensparsed = 0; + tokensparsed++; + if ( !m_pdata->tokenCount || !m_pdata->pTokens ) + ALERT( at_error, "No token table array in TokenHash()!" ); +#endif + + for ( int i=0; itokenCount; i++ ) + { +#if _DEBUG + static qboolean beentheredonethat = FALSE; + if ( i > 50 && !beentheredonethat ) + { + beentheredonethat = TRUE; + ALERT( at_error, "CSaveRestoreBuffer :: TokenHash() is getting too full!" ); + } +#endif + + int index = hash + i; + if ( index >= m_pdata->tokenCount ) + index -= m_pdata->tokenCount; + + if ( !m_pdata->pTokens[index] || strcmp( pszToken, m_pdata->pTokens[index] ) == 0 ) + { + m_pdata->pTokens[index] = (char *)pszToken; + return index; + } + } + + // Token hash table full!!! + // [Consider doing overflow table(s) after the main table & limiting linear hash table search] + ALERT( at_error, "CSaveRestoreBuffer :: TokenHash() is COMPLETELY FULL!" ); + return 0; +} + +void CSave :: WriteData( const char *pname, int size, const char *pdata ) +{ + BufferField( pname, size, pdata ); +} + + +void CSave :: WriteShort( const char *pname, const short *data, int count ) +{ + BufferField( pname, sizeof(short) * count, (const char *)data ); +} + + +void CSave :: WriteInt( const char *pname, const int *data, int count ) +{ + BufferField( pname, sizeof(int) * count, (const char *)data ); +} + + +void CSave :: WriteFloat( const char *pname, const float *data, int count ) +{ + BufferField( pname, sizeof(float) * count, (const char *)data ); +} + + +void CSave :: WriteTime( const char *pname, const float *data, int count ) +{ + int i; + Vector tmp, input; + + BufferHeader( pname, sizeof(float) * count ); + for ( i = 0; i < count; i++ ) + { + float tmp = data[0]; + + // Always encode time as a delta from the current time so it can be re-based if loaded in a new level + // Times of 0 are never written to the file, so they will be restored as 0, not a relative time + if ( m_pdata ) + tmp -= m_pdata->time; + + BufferData( (const char *)&tmp, sizeof(float) ); + data ++; + } +} + + +void CSave :: WriteString( const char *pname, const char *pdata ) +{ +#ifdef TOKENIZE + short token = (short)TokenHash( pdata ); + WriteShort( pname, &token, 1 ); +#else + BufferField( pname, strlen(pdata) + 1, pdata ); +#endif +} + + +void CSave :: WriteString( const char *pname, const int *stringId, int count ) +{ + int i, size; + +#ifdef TOKENIZE + short token = (short)TokenHash( STRING( *stringId ) ); + WriteShort( pname, &token, 1 ); +#else +#if 0 + if ( count != 1 ) + ALERT( at_error, "No string arrays!\n" ); + WriteString( pname, (char *)STRING(*stringId) ); +#endif + + size = 0; + for ( i = 0; i < count; i++ ) + size += strlen( STRING( stringId[i] ) ) + 1; + + BufferHeader( pname, size ); + for ( i = 0; i < count; i++ ) + { + const char *pString = STRING(stringId[i]); + BufferData( pString, strlen(pString)+1 ); + } +#endif +} + + +void CSave :: WriteVector( const char *pname, const Vector &value ) +{ + WriteVector( pname, &value.x, 1 ); +} + + +void CSave :: WriteVector( const char *pname, const float *value, int count ) +{ + BufferHeader( pname, sizeof(float) * 3 * count ); + BufferData( (const char *)value, sizeof(float) * 3 * count ); +} + + + +void CSave :: WritePositionVector( const char *pname, const Vector &value ) +{ + + if ( m_pdata && m_pdata->fUseLandmark ) + { + Vector tmp = value - m_pdata->vecLandmarkOffset; + WriteVector( pname, tmp ); + } + + WriteVector( pname, value ); +} + + +void CSave :: WritePositionVector( const char *pname, const float *value, int count ) +{ + int i; + Vector tmp, input; + + BufferHeader( pname, sizeof(float) * 3 * count ); + for ( i = 0; i < count; i++ ) + { + Vector tmp( value[0], value[1], value[2] ); + + if ( m_pdata && m_pdata->fUseLandmark ) + tmp = tmp - m_pdata->vecLandmarkOffset; + + BufferData( (const char *)&tmp.x, sizeof(float) * 3 ); + value += 3; + } +} + + +void CSave :: WriteFunction( const char *pname, void **data, int count ) +{ + const char *functionName; + + functionName = NAME_FOR_FUNCTION( (uint32)*data ); + if ( functionName ) + BufferField( pname, strlen(functionName) + 1, functionName ); + else + ALERT( at_error, "Invalid function pointer in entity!" ); +} + + +void EntvarsKeyvalue( entvars_t *pev, KeyValueData *pkvd ) +{ + int i; + TYPEDESCRIPTION *pField; + + for ( i = 0; i < ENTVARS_COUNT; i++ ) + { + pField = &gEntvarsDescription[i]; + + if ( !stricmp( pField->fieldName, pkvd->szKeyName ) ) + { + switch( pField->fieldType ) + { + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + case FIELD_STRING: + (*(int *)((char *)pev + pField->fieldOffset)) = ALLOC_STRING( pkvd->szValue ); + break; + + case FIELD_TIME: + case FIELD_FLOAT: + (*(float *)((char *)pev + pField->fieldOffset)) = atof( pkvd->szValue ); + break; + + case FIELD_INTEGER: + (*(int *)((char *)pev + pField->fieldOffset)) = atoi( pkvd->szValue ); + break; + + case FIELD_POSITION_VECTOR: + case FIELD_VECTOR: + UTIL_StringToVector( (float *)((char *)pev + pField->fieldOffset), pkvd->szValue ); + break; + + default: + case FIELD_EVARS: + case FIELD_CLASSPTR: + case FIELD_EDICT: + case FIELD_ENTITY: + case FIELD_POINTER: + ALERT( at_error, "Bad field in entity!!\n" ); + break; + } + pkvd->fHandled = TRUE; + return; + } + } +} + + + +int CSave :: WriteEntVars( const char *pname, entvars_t *pev ) +{ + return WriteFields( pname, pev, gEntvarsDescription, ENTVARS_COUNT ); +} + + + +int CSave :: WriteFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + int i, j, actualCount, emptyCount; + TYPEDESCRIPTION *pTest; + int entityArray[MAX_ENTITYARRAY]; + + // Precalculate the number of empty fields + emptyCount = 0; + for ( i = 0; i < fieldCount; i++ ) + { + void *pOutputData; + pOutputData = ((char *)pBaseData + pFields[i].fieldOffset ); + if ( DataEmpty( (const char *)pOutputData, pFields[i].fieldSize * gSizes[pFields[i].fieldType] ) ) + emptyCount++; + } + + // Empty fields will not be written, write out the actual number of fields to be written + actualCount = fieldCount - emptyCount; + WriteInt( pname, &actualCount, 1 ); + + for ( i = 0; i < fieldCount; i++ ) + { + void *pOutputData; + pTest = &pFields[ i ]; + pOutputData = ((char *)pBaseData + pTest->fieldOffset ); + + // UNDONE: Must we do this twice? + if ( DataEmpty( (const char *)pOutputData, pTest->fieldSize * gSizes[pTest->fieldType] ) ) + continue; + + switch( pTest->fieldType ) + { + case FIELD_FLOAT: + WriteFloat( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + case FIELD_TIME: + WriteTime( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + case FIELD_STRING: + WriteString( pTest->fieldName, (int *)pOutputData, pTest->fieldSize ); + break; + case FIELD_CLASSPTR: + case FIELD_EVARS: + case FIELD_EDICT: + case FIELD_ENTITY: + case FIELD_EHANDLE: + if ( pTest->fieldSize > MAX_ENTITYARRAY ) + ALERT( at_error, "Can't save more than %d entities in an array!!!\n", MAX_ENTITYARRAY ); + for ( j = 0; j < pTest->fieldSize; j++ ) + { + switch( pTest->fieldType ) + { + case FIELD_EVARS: + entityArray[j] = EntityIndex( ((entvars_t **)pOutputData)[j] ); + break; + case FIELD_CLASSPTR: + entityArray[j] = EntityIndex( ((CBaseEntity **)pOutputData)[j] ); + break; + case FIELD_EDICT: + entityArray[j] = EntityIndex( ((edict_t **)pOutputData)[j] ); + break; + case FIELD_ENTITY: + entityArray[j] = EntityIndex( ((EOFFSET *)pOutputData)[j] ); + break; + case FIELD_EHANDLE: + entityArray[j] = EntityIndex( (CBaseEntity *)(((EHANDLE *)pOutputData)[j]) ); + break; + } + } + WriteInt( pTest->fieldName, entityArray, pTest->fieldSize ); + break; + case FIELD_POSITION_VECTOR: + WritePositionVector( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + case FIELD_VECTOR: + WriteVector( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + + case FIELD_BOOLEAN: + case FIELD_INTEGER: + WriteInt( pTest->fieldName, (int *)pOutputData, pTest->fieldSize ); + break; + + case FIELD_SHORT: + WriteData( pTest->fieldName, 2 * pTest->fieldSize, ((char *)pOutputData) ); + break; + + case FIELD_CHARACTER: + WriteData( pTest->fieldName, pTest->fieldSize, ((char *)pOutputData) ); + break; + + // For now, just write the address out, we're not going to change memory while doing this yet! + case FIELD_POINTER: + WriteInt( pTest->fieldName, (int *)(char *)pOutputData, pTest->fieldSize ); + break; + + case FIELD_FUNCTION: + WriteFunction( pTest->fieldName, (void **)pOutputData, pTest->fieldSize ); + break; + default: + ALERT( at_error, "Bad field type\n" ); + } + } + + return 1; +} + + +void CSave :: BufferString( char *pdata, int len ) +{ + char c = 0; + + BufferData( pdata, len ); // Write the string + BufferData( &c, 1 ); // Write a null terminator +} + + +int CSave :: DataEmpty( const char *pdata, int size ) +{ + for ( int i = 0; i < size; i++ ) + { + if ( pdata[i] ) + return 0; + } + return 1; +} + + +void CSave :: BufferField( const char *pname, int size, const char *pdata ) +{ + BufferHeader( pname, size ); + BufferData( pdata, size ); +} + + +void CSave :: BufferHeader( const char *pname, int size ) +{ + short hashvalue = TokenHash( pname ); + if ( size > 1<<(sizeof(short)*8) ) + ALERT( at_error, "CSave :: BufferHeader() size parameter exceeds 'short'!" ); + BufferData( (const char *)&size, sizeof(short) ); + BufferData( (const char *)&hashvalue, sizeof(short) ); +} + + +void CSave :: BufferData( const char *pdata, int size ) +{ + if ( !m_pdata ) + return; + + if ( m_pdata->size + size > m_pdata->bufferSize ) + { + ALERT( at_error, "Save/Restore overflow!" ); + m_pdata->size = m_pdata->bufferSize; + return; + } + + memcpy( m_pdata->pCurrentData, pdata, size ); + m_pdata->pCurrentData += size; + m_pdata->size += size; +} + + + +// -------------------------------------------------------------- +// +// CRestore +// +// -------------------------------------------------------------- + +int CRestore::ReadField( void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount, int startField, int size, char *pName, void *pData ) +{ + int i, j, stringCount, fieldNumber, entityIndex; + TYPEDESCRIPTION *pTest; + float time, timeData; + Vector position; + edict_t *pent; + char *pString; + + time = 0; + position = Vector(0,0,0); + + if ( m_pdata ) + { + time = m_pdata->time; + if ( m_pdata->fUseLandmark ) + position = m_pdata->vecLandmarkOffset; + } + + for ( i = 0; i < fieldCount; i++ ) + { + fieldNumber = (i+startField)%fieldCount; + pTest = &pFields[ fieldNumber ]; + if ( !stricmp( pTest->fieldName, pName ) ) + { + if ( !m_global || !(pTest->flags & FTYPEDESC_GLOBAL) ) + { + for ( j = 0; j < pTest->fieldSize; j++ ) + { + void *pOutputData = ((char *)pBaseData + pTest->fieldOffset + (j*gSizes[pTest->fieldType]) ); + void *pInputData = (char *)pData + j * gSizes[pTest->fieldType]; + + switch( pTest->fieldType ) + { + case FIELD_TIME: + timeData = *(float *)pInputData; + // Re-base time variables + timeData += time; + *((float *)pOutputData) = timeData; + break; + case FIELD_FLOAT: + *((float *)pOutputData) = *(float *)pInputData; + break; + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + case FIELD_STRING: + // Skip over j strings + pString = (char *)pData; + for ( stringCount = 0; stringCount < j; stringCount++ ) + { + while (*pString) + pString++; + pString++; + } + pInputData = pString; + if ( strlen( (char *)pInputData ) == 0 ) + *((int *)pOutputData) = 0; + else + { + int string; + + string = ALLOC_STRING( (char *)pInputData ); + + *((int *)pOutputData) = string; + + if ( !FStringNull( string ) && m_precache ) + { + if ( pTest->fieldType == FIELD_MODELNAME ) + PRECACHE_MODEL( (char *)STRING( string ) ); + else if ( pTest->fieldType == FIELD_SOUNDNAME ) + PRECACHE_SOUND( (char *)STRING( string ) ); + } + } + break; + case FIELD_EVARS: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((entvars_t **)pOutputData) = VARS(pent); + else + *((entvars_t **)pOutputData) = NULL; + break; + case FIELD_CLASSPTR: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((CBaseEntity **)pOutputData) = CBaseEntity::Instance(pent); + else + *((CBaseEntity **)pOutputData) = NULL; + break; + case FIELD_EDICT: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + *((edict_t **)pOutputData) = pent; + break; + case FIELD_EHANDLE: + // Input and Output sizes are different! + pOutputData = (char *)pOutputData + j*(sizeof(EHANDLE) - gSizes[pTest->fieldType]); + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((EHANDLE *)pOutputData) = CBaseEntity::Instance(pent); + else + *((EHANDLE *)pOutputData) = NULL; + break; + case FIELD_ENTITY: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((EOFFSET *)pOutputData) = OFFSET(pent); + else + *((EOFFSET *)pOutputData) = 0; + break; + case FIELD_VECTOR: + ((float *)pOutputData)[0] = ((float *)pInputData)[0]; + ((float *)pOutputData)[1] = ((float *)pInputData)[1]; + ((float *)pOutputData)[2] = ((float *)pInputData)[2]; + break; + case FIELD_POSITION_VECTOR: + ((float *)pOutputData)[0] = ((float *)pInputData)[0] + position.x; + ((float *)pOutputData)[1] = ((float *)pInputData)[1] + position.y; + ((float *)pOutputData)[2] = ((float *)pInputData)[2] + position.z; + break; + + case FIELD_BOOLEAN: + case FIELD_INTEGER: + *((int *)pOutputData) = *( int *)pInputData; + break; + + case FIELD_SHORT: + *((short *)pOutputData) = *( short *)pInputData; + break; + + case FIELD_CHARACTER: + *((char *)pOutputData) = *( char *)pInputData; + break; + + case FIELD_POINTER: + *((int *)pOutputData) = *( int *)pInputData; + break; + case FIELD_FUNCTION: + if ( strlen( (char *)pInputData ) == 0 ) + *((int *)pOutputData) = 0; + else + *((int *)pOutputData) = FUNCTION_FROM_NAME( (char *)pInputData ); + break; + + default: + ALERT( at_error, "Bad field type\n" ); + } + } + } +#if 0 + else + { + ALERT( at_console, "Skipping global field %s\n", pName ); + } +#endif + return fieldNumber; + } + } + + return -1; +} + + +int CRestore::ReadEntVars( const char *pname, entvars_t *pev ) +{ + return ReadFields( pname, pev, gEntvarsDescription, ENTVARS_COUNT ); +} + + +int CRestore::ReadFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + unsigned short i, token; + int lastField, fileCount; + HEADER header; + + i = ReadShort(); + ASSERT( i == sizeof(int) ); // First entry should be an int + + token = ReadShort(); + + // Check the struct name + if ( token != TokenHash(pname) ) // Field Set marker + { +// ALERT( at_error, "Expected %s found %s!\n", pname, BufferPointer() ); + BufferRewind( 2*sizeof(short) ); + return 0; + } + + // Skip over the struct name + fileCount = ReadInt(); // Read field count + + lastField = 0; // Make searches faster, most data is read/written in the same order + + // Clear out base data + for ( i = 0; i < fieldCount; i++ ) + { + // Don't clear global fields + if ( !m_global || !(pFields[i].flags & FTYPEDESC_GLOBAL) ) + memset( ((char *)pBaseData + pFields[i].fieldOffset), 0, pFields[i].fieldSize * gSizes[pFields[i].fieldType] ); + } + + for ( i = 0; i < fileCount; i++ ) + { + BufferReadHeader( &header ); + lastField = ReadField( pBaseData, pFields, fieldCount, lastField, header.size, m_pdata->pTokens[header.token], header.pData ); + lastField++; + } + + return 1; +} + + +void CRestore::BufferReadHeader( HEADER *pheader ) +{ + ASSERT( pheader!=NULL ); + pheader->size = ReadShort(); // Read field size + pheader->token = ReadShort(); // Read field name token + pheader->pData = BufferPointer(); // Field Data is next + BufferSkipBytes( pheader->size ); // Advance to next field +} + + +short CRestore::ReadShort( void ) +{ + short tmp = 0; + + BufferReadBytes( (char *)&tmp, sizeof(short) ); + + return tmp; +} + +int CRestore::ReadInt( void ) +{ + int tmp = 0; + + BufferReadBytes( (char *)&tmp, sizeof(int) ); + + return tmp; +} + +int CRestore::ReadNamedInt( const char *pName ) +{ + HEADER header; + + BufferReadHeader( &header ); + return ((int *)header.pData)[0]; +} + +char *CRestore::ReadNamedString( const char *pName ) +{ + HEADER header; + + BufferReadHeader( &header ); +#ifdef TOKENIZE + return (char *)(m_pdata->pTokens[*(short *)header.pData]); +#else + return (char *)header.pData; +#endif +} + + +char *CRestore::BufferPointer( void ) +{ + if ( !m_pdata ) + return NULL; + + return m_pdata->pCurrentData; +} + +void CRestore::BufferReadBytes( char *pOutput, int size ) +{ + ASSERT( m_pdata !=NULL ); + + if ( !m_pdata || Empty() ) + return; + + if ( (m_pdata->size + size) > m_pdata->bufferSize ) + { + ALERT( at_error, "Restore overflow!" ); + m_pdata->size = m_pdata->bufferSize; + return; + } + + if ( pOutput ) + memcpy( pOutput, m_pdata->pCurrentData, size ); + m_pdata->pCurrentData += size; + m_pdata->size += size; +} + + +void CRestore::BufferSkipBytes( int bytes ) +{ + BufferReadBytes( NULL, bytes ); +} + +int CRestore::BufferSkipZString( void ) +{ + char *pszSearch; + int len; + + if ( !m_pdata ) + return 0; + + int maxLen = m_pdata->bufferSize - m_pdata->size; + + len = 0; + pszSearch = m_pdata->pCurrentData; + while ( *pszSearch++ && len < maxLen ) + len++; + + len++; + + BufferSkipBytes( len ); + + return len; +} + +int CRestore::BufferCheckZString( const char *string ) +{ + if ( !m_pdata ) + return 0; + + int maxLen = m_pdata->bufferSize - m_pdata->size; + int len = strlen( string ); + if ( len <= maxLen ) + { + if ( !strncmp( string, m_pdata->pCurrentData, len ) ) + return 1; + } + return 0; +} diff --git a/ricochet/dlls/util.h b/ricochet/dlls/util.h new file mode 100644 index 0000000..2c53ef6 --- /dev/null +++ b/ricochet/dlls/util.h @@ -0,0 +1,564 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "archtypes.h" // DAL + +// +// Misc utility code +// +#ifndef ACTIVITY_H +#include "activity.h" +#endif + +#ifndef ENGINECALLBACK_H +#include "enginecallback.h" +#endif +inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin, entvars_t *ent ); // implementation later in this file + +extern globalvars_t *gpGlobals; + +// Use this instead of ALLOC_STRING on constant strings +#define STRING(offset) ((const char *)(gpGlobals->pStringBase + (unsigned int)(offset))) +#define MAKE_STRING(str) ((uint64)(str) - (uint64)STRING(0)) + + +inline edict_t *FIND_ENTITY_BY_CLASSNAME(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "classname", pszName); +} + +inline edict_t *FIND_ENTITY_BY_TARGETNAME(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "targetname", pszName); +} + +// for doing a reverse lookup. Say you have a door, and want to find its button. +inline edict_t *FIND_ENTITY_BY_TARGET(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "target", pszName); +} + +// Keeps clutter down a bit, when writing key-value pairs +#define WRITEKEY_INT(pf, szKeyName, iKeyValue) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%d\"\n", szKeyName, iKeyValue) +#define WRITEKEY_FLOAT(pf, szKeyName, flKeyValue) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%f\"\n", szKeyName, flKeyValue) +#define WRITEKEY_STRING(pf, szKeyName, szKeyValue) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%s\"\n", szKeyName, szKeyValue) +#define WRITEKEY_VECTOR(pf, szKeyName, flX, flY, flZ) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%f %f %f\"\n", szKeyName, flX, flY, flZ) + +// Keeps clutter down a bit, when using a float as a bit-vector +#define SetBits(flBitVector, bits) ((flBitVector) = (int)(flBitVector) | (bits)) +#define ClearBits(flBitVector, bits) ((flBitVector) = (int)(flBitVector) & ~(bits)) +#define FBitSet(flBitVector, bit) ((int)(flBitVector) & (bit)) + +// Makes these more explicit, and easier to find +#define FILE_GLOBAL static +#define DLL_GLOBAL + +// Until we figure out why "const" gives the compiler problems, we'll just have to use +// this bogus "empty" define to mark things as constant. +#define CONSTANT + +// More explicit than "int" +typedef int EOFFSET; + +// In case it's not alread defined +typedef int BOOL; + +// In case this ever changes +#define M_PI 3.14159265358979323846 + +#ifndef UTIL_DLLEXPORT +#ifdef _WIN32 +#define UTIL_DLLEXPORT _declspec( dllexport ) +#else +#define UTIL_DLLEXPORT __attribute__ ((visibility("default"))) +#endif +#endif + +// Keeps clutter down a bit, when declaring external entity/global method prototypes +#define DECLARE_GLOBAL_METHOD(MethodName) \ + extern void UTIL_DLLEXPORT MethodName( void ) +#define GLOBAL_METHOD(funcname) void UTIL_DLLEXPORT funcname(void) + +// This is the glue that hooks .MAP entity class names to our CPP classes +// The _declspec forces them to be exported by name so we can do a lookup with GetProcAddress() +// The function is used to intialize / allocate the object for the entity +#define LINK_ENTITY_TO_CLASS(mapClassName,DLLClassName) \ + extern "C" UTIL_DLLEXPORT void mapClassName( entvars_t *pev ); \ + void mapClassName( entvars_t *pev ) { GetClassPtr( (DLLClassName *)pev ); } + + +// +// Conversion among the three types of "entity", including identity-conversions. +// +#ifdef DEBUG + extern edict_t *DBG_EntOfVars(const entvars_t *pev); + inline edict_t *ENT(const entvars_t *pev) { return DBG_EntOfVars(pev); } +#else + inline edict_t *ENT(const entvars_t *pev) { return pev->pContainingEntity; } +#endif +inline edict_t *ENT(edict_t *pent) { return pent; } +inline edict_t *ENT(EOFFSET eoffset) { return (*g_engfuncs.pfnPEntityOfEntOffset)(eoffset); } +inline EOFFSET OFFSET(EOFFSET eoffset) { return eoffset; } +inline EOFFSET OFFSET(const edict_t *pent) +{ +#if _DEBUG + if ( !pent ) + ALERT( at_error, "Bad ent in OFFSET()\n" ); +#endif + return (*g_engfuncs.pfnEntOffsetOfPEntity)(pent); +} +inline EOFFSET OFFSET(entvars_t *pev) +{ +#if _DEBUG + if ( !pev ) + ALERT( at_error, "Bad pev in OFFSET()\n" ); +#endif + return OFFSET(ENT(pev)); +} +inline entvars_t *VARS(entvars_t *pev) { return pev; } + +inline entvars_t *VARS(edict_t *pent) +{ + if ( !pent ) + return NULL; + + return &pent->v; +} + +inline entvars_t* VARS(EOFFSET eoffset) { return VARS(ENT(eoffset)); } +inline int ENTINDEX(edict_t *pEdict) { return (*g_engfuncs.pfnIndexOfEdict)(pEdict); } +inline edict_t* INDEXENT( int iEdictNum ) { return (*g_engfuncs.pfnPEntityOfEntIndex)(iEdictNum); } +inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin, entvars_t *ent ) { + (*g_engfuncs.pfnMessageBegin)(msg_dest, msg_type, pOrigin, ENT(ent)); +} + +// Testing the three types of "entity" for nullity +#define eoNullEntity 0 +inline BOOL FNullEnt(EOFFSET eoffset) { return eoffset == 0; } +inline BOOL FNullEnt(const edict_t* pent) { return pent == NULL || FNullEnt(OFFSET(pent)); } +inline BOOL FNullEnt(entvars_t* pev) { return pev == NULL || FNullEnt(OFFSET(pev)); } + +// Testing strings for nullity +#define iStringNull 0 +inline BOOL FStringNull(int iString) { return iString == iStringNull; } + +#define cchMapNameMost 32 + +// Dot products for view cone checking +#define VIEW_FIELD_FULL (float)-1.0 // +-180 degrees +#define VIEW_FIELD_WIDE (float)-0.7 // +-135 degrees 0.1 // +-85 degrees, used for full FOV checks +#define VIEW_FIELD_NARROW (float)0.7 // +-45 degrees, more narrow check used to set up ranged attacks +#define VIEW_FIELD_ULTRA_NARROW (float)0.9 // +-25 degrees, more narrow check used to set up ranged attacks + +// All monsters need this data +#define DONT_BLEED -1 +#define BLOOD_COLOR_RED (BYTE)247 +#define BLOOD_COLOR_YELLOW (BYTE)195 +#define BLOOD_COLOR_GREEN BLOOD_COLOR_YELLOW + +typedef enum +{ + + MONSTERSTATE_NONE = 0, + MONSTERSTATE_IDLE, + MONSTERSTATE_COMBAT, + MONSTERSTATE_ALERT, + MONSTERSTATE_HUNT, + MONSTERSTATE_PRONE, + MONSTERSTATE_SCRIPT, + MONSTERSTATE_PLAYDEAD, + MONSTERSTATE_DEAD + +} MONSTERSTATE; + + + +// Things that toggle (buttons/triggers/doors) need this +typedef enum + { + TS_AT_TOP, + TS_AT_BOTTOM, + TS_GOING_UP, + TS_GOING_DOWN + } TOGGLE_STATE; + +// Misc useful +inline BOOL FStrEq(const char*sz1, const char*sz2) + { return (strcmp(sz1, sz2) == 0); } +inline BOOL FClassnameIs(edict_t* pent, const char* szClassname) + { return FStrEq(STRING(VARS(pent)->classname), szClassname); } +inline BOOL FClassnameIs(entvars_t* pev, const char* szClassname) + { return FStrEq(STRING(pev->classname), szClassname); } + +class CBaseEntity; + +// Misc. Prototypes +extern void UTIL_SetSize (entvars_t* pev, const Vector &vecMin, const Vector &vecMax); +extern float UTIL_VecToYaw (const Vector &vec); +extern Vector UTIL_VecToAngles (const Vector &vec); +extern float UTIL_AngleMod (float a); +extern float UTIL_AngleDiff ( float destAngle, float srcAngle ); + +extern CBaseEntity *UTIL_FindEntityInSphere(CBaseEntity *pStartEntity, const Vector &vecCenter, float flRadius); +extern CBaseEntity *UTIL_FindEntityByString(CBaseEntity *pStartEntity, const char *szKeyword, const char *szValue ); +extern CBaseEntity *UTIL_FindEntityByClassname(CBaseEntity *pStartEntity, const char *szName ); +extern CBaseEntity *UTIL_FindEntityByTargetname(CBaseEntity *pStartEntity, const char *szName ); +extern CBaseEntity *UTIL_FindEntityGeneric(const char *szName, Vector &vecSrc, float flRadius ); + +// returns a CBaseEntity pointer to a player by index. Only returns if the player is spawned and connected +// otherwise returns NULL +// Index is 1 based +extern CBaseEntity *UTIL_PlayerByIndex( int playerIndex ); + +#define UTIL_EntitiesInPVS(pent) (*g_engfuncs.pfnEntitiesInPVS)(pent) +extern void UTIL_MakeVectors (const Vector &vecAngles); + +// Pass in an array of pointers and an array size, it fills the array and returns the number inserted +extern int UTIL_MonstersInSphere( CBaseEntity **pList, int listMax, const Vector ¢er, float radius ); +extern int UTIL_EntitiesInBox( CBaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask ); + +inline void UTIL_MakeVectorsPrivate( const Vector &vecAngles, float *p_vForward, float *p_vRight, float *p_vUp ) +{ + g_engfuncs.pfnAngleVectors( vecAngles, p_vForward, p_vRight, p_vUp ); +} + +extern void UTIL_MakeAimVectors ( const Vector &vecAngles ); // like MakeVectors, but assumes pitch isn't inverted +extern void UTIL_MakeInvVectors ( const Vector &vec, globalvars_t *pgv ); + +extern void UTIL_SetOrigin ( entvars_t* pev, const Vector &vecOrigin ); +extern void UTIL_EmitAmbientSound ( edict_t *entity, const Vector &vecOrigin, const char *samp, float vol, float attenuation, int fFlags, int pitch ); +extern void UTIL_ParticleEffect ( const Vector &vecOrigin, const Vector &vecDirection, ULONG ulColor, ULONG ulCount ); +extern void UTIL_ScreenShake ( const Vector ¢er, float amplitude, float frequency, float duration, float radius ); +extern void UTIL_ScreenShakeAll ( const Vector ¢er, float amplitude, float frequency, float duration ); +extern void UTIL_ShowMessage ( const char *pString, CBaseEntity *pPlayer ); +extern void UTIL_ShowMessageAll ( const char *pString ); +extern void UTIL_ScreenFadeAll ( const Vector &color, float fadeTime, float holdTime, int alpha, int flags ); +extern void UTIL_ScreenFade ( CBaseEntity *pEntity, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ); + +typedef enum { ignore_monsters=1, dont_ignore_monsters=0, missile=2 } IGNORE_MONSTERS; +typedef enum { ignore_glass=1, dont_ignore_glass=0 } IGNORE_GLASS; +extern void UTIL_TraceLine (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, edict_t *pentIgnore, TraceResult *ptr); +extern void UTIL_TraceLine (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, IGNORE_GLASS ignoreGlass, edict_t *pentIgnore, TraceResult *ptr); +typedef enum { point_hull=0, human_hull=1, large_hull=2, head_hull=3 }; +extern void UTIL_TraceHull (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, int hullNumber, edict_t *pentIgnore, TraceResult *ptr); +extern TraceResult UTIL_GetGlobalTrace (void); +extern void UTIL_TraceModel (const Vector &vecStart, const Vector &vecEnd, int hullNumber, edict_t *pentModel, TraceResult *ptr); +extern Vector UTIL_GetAimVector (edict_t* pent, float flSpeed); +extern int UTIL_PointContents (const Vector &vec); + +extern int UTIL_IsMasterTriggered (string_t sMaster, CBaseEntity *pActivator); +extern void UTIL_BloodStream( const Vector &origin, const Vector &direction, int color, int amount ); +extern void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, int amount ); +extern Vector UTIL_RandomBloodVector( void ); +extern BOOL UTIL_ShouldShowBlood( int bloodColor ); +extern void UTIL_BloodDecalTrace( TraceResult *pTrace, int bloodColor ); +extern void UTIL_DecalTrace( TraceResult *pTrace, int decalNumber ); +extern void UTIL_PlayerDecalTrace( TraceResult *pTrace, int playernum, int decalNumber, BOOL bIsCustom ); +extern void UTIL_GunshotDecalTrace( TraceResult *pTrace, int decalNumber ); +extern void UTIL_Sparks( const Vector &position, edict_t * ed = NULL ); +extern void UTIL_Ricochet( const Vector &position, float scale ); +extern void UTIL_StringToVector( float *pVector, const char *pString ); +extern void UTIL_StringToIntArray( int *pVector, int count, const char *pString ); +extern Vector UTIL_ClampVectorToBox( const Vector &input, const Vector &clampSize ); +extern float UTIL_Approach( float target, float value, float speed ); +extern float UTIL_ApproachAngle( float target, float value, float speed ); +extern float UTIL_AngleDistance( float next, float cur ); + +extern char *UTIL_VarArgs( char *format, ... ); +extern void UTIL_Remove( CBaseEntity *pEntity ); +extern BOOL UTIL_IsValidEntity( edict_t *pent ); +extern BOOL UTIL_TeamsMatch( const char *pTeamName1, const char *pTeamName2 ); + +// Use for ease-in, ease-out style interpolation (accel/decel) +extern float UTIL_SplineFraction( float value, float scale ); + +// Search for water transition along a vertical line +extern float UTIL_WaterLevel( const Vector &position, float minz, float maxz ); +extern void UTIL_Bubbles( Vector mins, Vector maxs, int count ); +extern void UTIL_BubbleTrail( Vector from, Vector to, int count ); + +// allows precacheing of other entities +extern void UTIL_PrecacheOther( const char *szClassname ); + +// prints a message to each client +extern void UTIL_ClientPrintAll( int msg_dest, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ); +inline void UTIL_CenterPrintAll( const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ) +{ + UTIL_ClientPrintAll( HUD_PRINTCENTER, msg_name, param1, param2, param3, param4 ); +} + +class CBasePlayerItem; +class CBasePlayer; +extern BOOL UTIL_GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ); + + +// prints messages through the HUD +extern void ClientPrint( entvars_t *client, int msg_dest, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ); + +// prints a message to the HUD say (chat) +extern void UTIL_SayText( const char *pText, CBaseEntity *pEntity ); +extern void UTIL_SayTextAll( const char *pText, CBaseEntity *pEntity ); + + +typedef struct hudtextparms_s +{ + float x; + float y; + int effect; + byte r1, g1, b1, a1; + byte r2, g2, b2, a2; + float fadeinTime; + float fadeoutTime; + float holdTime; + float fxTime; + int channel; +} hudtextparms_t; + +// prints as transparent 'title' to the HUD +extern void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage ); +extern void UTIL_HudMessage( CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage ); + +// for handy use with ClientPrint params +extern char *UTIL_dtos1( int d ); +extern char *UTIL_dtos2( int d ); +extern char *UTIL_dtos3( int d ); +extern char *UTIL_dtos4( int d ); + +// Writes message to console with timestamp and FragLog header. +extern void UTIL_LogPrintf( char *fmt, ... ); + +// Sorta like FInViewCone, but for nonmonsters. +extern float UTIL_DotPoints ( const Vector &vecSrc, const Vector &vecCheck, const Vector &vecDir ); + +extern void UTIL_StripToken( const char *pKey, char *pDest );// for redundant keynames + +// Misc functions +extern void SetMovedir(entvars_t* pev); +extern Vector VecBModelOrigin( entvars_t* pevBModel ); +extern int BuildChangeList( LEVELLIST *pLevelList, int maxList ); + +// +// How did I ever live without ASSERT? +// +#ifdef DEBUG +void DBG_AssertFunction(BOOL fExpr, const char* szExpr, const char* szFile, int szLine, const char* szMessage); +#define ASSERT(f) DBG_AssertFunction(f, #f, __FILE__, __LINE__, NULL) +#define ASSERTSZ(f, sz) DBG_AssertFunction(f, #f, __FILE__, __LINE__, sz) +#else // !DEBUG +#define ASSERT(f) +#define ASSERTSZ(f, sz) +#endif // !DEBUG + + +extern DLL_GLOBAL const Vector g_vecZero; + +// +// Constants that were used only by QC (maybe not used at all now) +// +// Un-comment only as needed +// +#define LANGUAGE_ENGLISH 0 +#define LANGUAGE_GERMAN 1 +#define LANGUAGE_FRENCH 2 +#define LANGUAGE_BRITISH 3 + +extern DLL_GLOBAL int g_Language; + +#define AMBIENT_SOUND_STATIC 0 // medium radius attenuation +#define AMBIENT_SOUND_EVERYWHERE 1 +#define AMBIENT_SOUND_SMALLRADIUS 2 +#define AMBIENT_SOUND_MEDIUMRADIUS 4 +#define AMBIENT_SOUND_LARGERADIUS 8 +#define AMBIENT_SOUND_START_SILENT 16 +#define AMBIENT_SOUND_NOT_LOOPING 32 + +#define SPEAKER_START_SILENT 1 // wait for trigger 'on' to start announcements + +#define SND_SPAWNING (1<<8) // duplicated in protocol.h we're spawing, used in some cases for ambients +#define SND_STOP (1<<5) // duplicated in protocol.h stop sound +#define SND_CHANGE_VOL (1<<6) // duplicated in protocol.h change sound vol +#define SND_CHANGE_PITCH (1<<7) // duplicated in protocol.h change sound pitch + +#define LFO_SQUARE 1 +#define LFO_TRIANGLE 2 +#define LFO_RANDOM 3 + +// func_rotating +#define SF_BRUSH_ROTATE_Y_AXIS 0 +#define SF_BRUSH_ROTATE_INSTANT 1 +#define SF_BRUSH_ROTATE_BACKWARDS 2 +#define SF_BRUSH_ROTATE_Z_AXIS 4 +#define SF_BRUSH_ROTATE_X_AXIS 8 +#define SF_PENDULUM_AUTO_RETURN 16 +#define SF_PENDULUM_PASSABLE 32 + + +#define SF_BRUSH_ROTATE_SMALLRADIUS 128 +#define SF_BRUSH_ROTATE_MEDIUMRADIUS 256 +#define SF_BRUSH_ROTATE_LARGERADIUS 512 + +#define PUSH_BLOCK_ONLY_X 1 +#define PUSH_BLOCK_ONLY_Y 2 + +#define VEC_HULL_MIN Vector(-16, -16, -36) +#define VEC_HULL_MAX Vector( 16, 16, 36) +#define VEC_HUMAN_HULL_MIN Vector( -16, -16, 0 ) +#define VEC_HUMAN_HULL_MAX Vector( 16, 16, 72 ) +#define VEC_HUMAN_HULL_DUCK Vector( 16, 16, 36 ) + +#define VEC_VIEW Vector( 0, 0, 28 ) + +#define VEC_DUCK_HULL_MIN Vector(-16, -16, -18 ) +#define VEC_DUCK_HULL_MAX Vector( 16, 16, 18) +#define VEC_DUCK_VIEW Vector( 0, 0, 12 ) + +#define SVC_TEMPENTITY 23 +#define SVC_INTERMISSION 30 +#define SVC_CDTRACK 32 +#define SVC_WEAPONANIM 35 +#define SVC_ROOMTYPE 37 + +// Added to stuff text to the clients +#define SVC_STUFFTEXT 9 // [string] stuffed into client's console buffer + +// triggers +#define SF_TRIGGER_ALLOWMONSTERS 1// monsters allowed to fire this trigger +#define SF_TRIGGER_NOCLIENTS 2// players not allowed to fire this trigger +#define SF_TRIGGER_PUSHABLES 4// only pushables can fire this trigger + +// func breakable +#define SF_BREAK_TRIGGER_ONLY 1// may only be broken by trigger +#define SF_BREAK_TOUCH 2// can be 'crashed through' by running player (plate glass) +#define SF_BREAK_PRESSURE 4// can be broken by a player standing on it +#define SF_BREAK_CROWBAR 256// instant break if hit with crowbar + +// func_pushable (it's also func_breakable, so don't collide with those flags) +#define SF_PUSH_BREAKABLE 128 + +#define SF_LIGHT_START_OFF 1 + +#define SPAWNFLAG_NOMESSAGE 1 +#define SPAWNFLAG_NOTOUCH 1 +#define SPAWNFLAG_DROIDONLY 4 + +#define SPAWNFLAG_USEONLY 1 // can't be touched, must be used (buttons) + +#define TELE_PLAYER_ONLY 1 +#define TELE_SILENT 2 + +#define SF_TRIG_PUSH_ONCE 1 + + +// Sound Utilities + +// sentence groups +#define CBSENTENCENAME_MAX 16 +#define CVOXFILESENTENCEMAX 1536 // max number of sentences in game. NOTE: this must match + // CVOXFILESENTENCEMAX in engine\sound.h!!! + +extern char gszallsentencenames[CVOXFILESENTENCEMAX][CBSENTENCENAME_MAX]; +extern int gcallsentences; + +int USENTENCEG_Pick(int isentenceg, char *szfound); +int USENTENCEG_PickSequential(int isentenceg, char *szfound, int ipick, int freset); +void USENTENCEG_InitLRU(unsigned char *plru, int count); + +void SENTENCEG_Init(); +void SENTENCEG_Stop(edict_t *entity, int isentenceg, int ipick); +int SENTENCEG_PlayRndI(edict_t *entity, int isentenceg, float volume, float attenuation, int flags, int pitch); +int SENTENCEG_PlayRndSz(edict_t *entity, const char *szrootname, float volume, float attenuation, int flags, int pitch); +int SENTENCEG_PlaySequentialSz(edict_t *entity, const char *szrootname, float volume, float attenuation, int flags, int pitch, int ipick, int freset); +int SENTENCEG_GetIndex(const char *szrootname); +int SENTENCEG_Lookup(const char *sample, char *sentencenum); + +void TEXTURETYPE_Init(); +char TEXTURETYPE_Find(char *name); +float TEXTURETYPE_PlaySound(TraceResult *ptr, Vector vecSrc, Vector vecEnd, int iBulletType); + +#define CBTEXTURENAMEMAX 13 // only load first n chars of name + +#define CHAR_TEX_CONCRETE 'C' // texture types +#define CHAR_TEX_METAL 'M' +#define CHAR_TEX_DIRT 'D' +#define CHAR_TEX_VENT 'V' +#define CHAR_TEX_GRATE 'G' +#define CHAR_TEX_TILE 'T' +#define CHAR_TEX_SLOSH 'S' +#define CHAR_TEX_WOOD 'W' +#define CHAR_TEX_COMPUTER 'P' +#define CHAR_TEX_GLASS 'Y' +#define CHAR_TEX_FLESH 'F' + +// NOTE: use EMIT_SOUND_DYN to set the pitch of a sound. Pitch of 100 +// is no pitch shift. Pitch > 100 up to 255 is a higher pitch, pitch < 100 +// down to 1 is a lower pitch. 150 to 70 is the realistic range. +// EMIT_SOUND_DYN with pitch != 100 should be used sparingly, as it's not quite as +// fast as EMIT_SOUND (the pitchshift mixer is not native coded). + +void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volume, float attenuation, + int flags, int pitch); + + +inline void EMIT_SOUND(edict_t *entity, int channel, const char *sample, float volume, float attenuation) +{ + EMIT_SOUND_DYN(entity, channel, sample, volume, attenuation, 0, PITCH_NORM); +} + +inline void STOP_SOUND(edict_t *entity, int channel, const char *sample) +{ + EMIT_SOUND_DYN(entity, channel, sample, 0, 0, SND_STOP, PITCH_NORM); +} + +void EMIT_SOUND_SUIT(edict_t *entity, const char *sample); +void EMIT_GROUPID_SUIT(edict_t *entity, int isentenceg); +void EMIT_GROUPNAME_SUIT(edict_t *entity, const char *groupname); + +#define PRECACHE_SOUND_ARRAY( a ) \ + { for (int i = 0; i < ARRAYSIZE( a ); i++ ) PRECACHE_SOUND((char *) a [i]); } + +#define EMIT_SOUND_ARRAY_DYN( chan, array ) \ + EMIT_SOUND_DYN ( ENT(pev), chan , array [ RANDOM_LONG(0,ARRAYSIZE( array )-1) ], 1.0, ATTN_NORM, 0, RANDOM_LONG(95,105) ); + +#define RANDOM_SOUND_ARRAY( array ) (array) [ RANDOM_LONG(0,ARRAYSIZE( (array) )-1) ] + +#define PLAYBACK_EVENT( flags, who, index ) PLAYBACK_EVENT_FULL( flags, who, index, 0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); +#define PLAYBACK_EVENT_DELAY( flags, who, index, delay ) PLAYBACK_EVENT_FULL( flags, who, index, delay, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); + +#define GROUP_OP_AND 0 +#define GROUP_OP_NAND 1 + +extern int g_groupmask; +extern int g_groupop; + +class UTIL_GroupTrace +{ +public: + UTIL_GroupTrace( int groupmask, int op ); + ~UTIL_GroupTrace( void ); + +private: + int m_oldgroupmask, m_oldgroupop; +}; + +void UTIL_SetGroupTrace( int groupmask, int op ); +void UTIL_UnsetGroupTrace( void ); + +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ); +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ); + +float UTIL_WeaponTimeBase( void ); diff --git a/ricochet/dlls/vector.h b/ricochet/dlls/vector.h new file mode 100644 index 0000000..adfb436 --- /dev/null +++ b/ricochet/dlls/vector.h @@ -0,0 +1,112 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef VECTOR_H +#define VECTOR_H + +//========================================================= +// 2DVector - used for many pathfinding and many other +// operations that are treated as planar rather than 3d. +//========================================================= +class Vector2D +{ +public: + inline Vector2D(void) { } + inline Vector2D(float X, float Y) { x = X; y = Y; } + inline Vector2D operator+(const Vector2D& v) const { return Vector2D(x+v.x, y+v.y); } + inline Vector2D operator-(const Vector2D& v) const { return Vector2D(x-v.x, y-v.y); } + inline Vector2D operator*(float fl) const { return Vector2D(x*fl, y*fl); } + inline Vector2D operator/(float fl) const { return Vector2D(x/fl, y/fl); } + + inline float Length(void) const { return sqrt(x*x + y*y ); } + + inline Vector2D Normalize ( void ) const + { + Vector2D vec2; + + float flLen = Length(); + if ( flLen == 0 ) + { + return Vector2D( 0, 0 ); + } + else + { + flLen = 1 / flLen; + return Vector2D( x * flLen, y * flLen ); + } + } + + vec_t x, y; +}; + +inline float DotProduct(const Vector2D& a, const Vector2D& b) { return( a.x*b.x + a.y*b.y ); } +inline Vector2D operator*(float fl, const Vector2D& v) { return v * fl; } + +//========================================================= +// 3D Vector +//========================================================= +class Vector // same data-layout as engine's vec3_t, +{ // which is a vec_t[3] +public: + // Construction/destruction + inline Vector(void) { } + inline Vector(float X, float Y, float Z) { x = X; y = Y; z = Z; } + //inline Vector(double X, double Y, double Z) { x = (float)X; y = (float)Y; z = (float)Z; } + //inline Vector(int X, int Y, int Z) { x = (float)X; y = (float)Y; z = (float)Z; } + inline Vector(const Vector& v) { x = v.x; y = v.y; z = v.z; } + inline Vector(float rgfl[3]) { x = rgfl[0]; y = rgfl[1]; z = rgfl[2]; } + + // Operators + inline Vector operator-(void) const { return Vector(-x,-y,-z); } + inline int operator==(const Vector& v) const { return x==v.x && y==v.y && z==v.z; } + inline int operator!=(const Vector& v) const { return !(*this==v); } + inline Vector operator+(const Vector& v) const { return Vector(x+v.x, y+v.y, z+v.z); } + inline Vector operator-(const Vector& v) const { return Vector(x-v.x, y-v.y, z-v.z); } + inline Vector operator*(float fl) const { return Vector(x*fl, y*fl, z*fl); } + inline Vector operator/(float fl) const { return Vector(x/fl, y/fl, z/fl); } + + // Methods + inline void CopyToArray(float* rgfl) const { rgfl[0] = x, rgfl[1] = y, rgfl[2] = z; } + inline float Length(void) const { return sqrt(x*x + y*y + z*z); } + operator float *() { return &x; } // Vectors will now automatically convert to float * when needed + operator const float *() const { return &x; } // Vectors will now automatically convert to float * when needed + inline Vector Normalize(void) const + { + float flLen = Length(); + if (flLen == 0) return Vector(0,0,1); // ???? + flLen = 1 / flLen; + return Vector(x * flLen, y * flLen, z * flLen); + } + + inline Vector2D Make2D ( void ) const + { + Vector2D Vec2; + + Vec2.x = x; + Vec2.y = y; + + return Vec2; + } + inline float Length2D(void) const { return sqrt(x*x + y*y); } + + // Members + vec_t x, y, z; +}; +inline Vector operator*(float fl, const Vector& v) { return v * fl; } +inline float DotProduct(const Vector& a, const Vector& b) { return(a.x*b.x+a.y*b.y+a.z*b.z); } +inline Vector CrossProduct(const Vector& a, const Vector& b) { return Vector( a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x ); } + + + +#endif diff --git a/ricochet/dlls/weapons.cpp b/ricochet/dlls/weapons.cpp new file mode 100644 index 0000000..2805abc --- /dev/null +++ b/ricochet/dlls/weapons.cpp @@ -0,0 +1,1058 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== weapons.cpp ======================================================== + + functions governing the selection/use of weapons for players + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "soundent.h" +#include "decals.h" +#include "gamerules.h" + +extern CGraph WorldGraph; +extern int gEvilImpulse101; + + +#define NOT_USED 255 + +DLL_GLOBAL short g_sModelIndexLaser;// holds the index for the laser beam +DLL_GLOBAL const char *g_pModelNameLaser = "sprites/laserbeam.spr"; +DLL_GLOBAL short g_sModelIndexLaserDot;// holds the index for the laser beam dot +DLL_GLOBAL short g_sModelIndexFireball;// holds the index for the fireball +DLL_GLOBAL short g_sModelIndexSmoke;// holds the index for the smoke cloud +DLL_GLOBAL short g_sModelIndexWExplosion;// holds the index for the underwater explosion +DLL_GLOBAL short g_sModelIndexBubbles;// holds the index for the bubbles model +DLL_GLOBAL short g_sModelIndexBloodDrop;// holds the sprite index for the initial blood +DLL_GLOBAL short g_sModelIndexBloodSpray;// holds the sprite index for splattered blood + +ItemInfo CBasePlayerItem::ItemInfoArray[MAX_WEAPONS]; +AmmoInfo CBasePlayerItem::AmmoInfoArray[MAX_AMMO_SLOTS]; + +extern int gmsgCurWeapon; + +MULTIDAMAGE gMultiDamage; + +#define TRACER_FREQ 4 // Tracers fire every fourth bullet + + +//========================================================= +// MaxAmmoCarry - pass in a name and this function will tell +// you the maximum amount of that type of ammunition that a +// player can carry. +//========================================================= +int MaxAmmoCarry( int iszName ) +{ + for ( int i = 0; i < MAX_WEAPONS; i++ ) + { + if ( CBasePlayerItem::ItemInfoArray[i].pszAmmo1 && !strcmp( STRING(iszName), CBasePlayerItem::ItemInfoArray[i].pszAmmo1 ) ) + return CBasePlayerItem::ItemInfoArray[i].iMaxAmmo1; + if ( CBasePlayerItem::ItemInfoArray[i].pszAmmo2 && !strcmp( STRING(iszName), CBasePlayerItem::ItemInfoArray[i].pszAmmo2 ) ) + return CBasePlayerItem::ItemInfoArray[i].iMaxAmmo2; + } + + ALERT( at_console, "MaxAmmoCarry() doesn't recognize '%s'!\n", STRING( iszName ) ); + return -1; +} + + +/* +============================================================================== + +MULTI-DAMAGE + +Collects multiple small damages into a single damage + +============================================================================== +*/ + +// +// ClearMultiDamage - resets the global multi damage accumulator +// +void ClearMultiDamage(void) +{ + gMultiDamage.pEntity = NULL; + gMultiDamage.amount = 0; + gMultiDamage.type = 0; +} + + +// +// ApplyMultiDamage - inflicts contents of global multi damage register on gMultiDamage.pEntity +// +// GLOBALS USED: +// gMultiDamage + +void ApplyMultiDamage(entvars_t *pevInflictor, entvars_t *pevAttacker ) +{ + Vector vecSpot1;//where blood comes from + Vector vecDir;//direction blood should go + TraceResult tr; + + if ( !gMultiDamage.pEntity ) + return; + + gMultiDamage.pEntity->TakeDamage(pevInflictor, pevAttacker, gMultiDamage.amount, gMultiDamage.type ); +} + + +// GLOBALS USED: +// gMultiDamage + +void AddMultiDamage( entvars_t *pevInflictor, CBaseEntity *pEntity, float flDamage, int bitsDamageType) +{ + if ( !pEntity ) + return; + + gMultiDamage.type |= bitsDamageType; + + if ( pEntity != gMultiDamage.pEntity ) + { + ApplyMultiDamage(pevInflictor,pevInflictor); // UNDONE: wrong attacker! + gMultiDamage.pEntity = pEntity; + gMultiDamage.amount = 0; + } + + gMultiDamage.amount += flDamage; +} + +/* +================ +SpawnBlood +================ +*/ +void SpawnBlood(Vector vecSpot, int bloodColor, float flDamage) +{ + UTIL_BloodDrips( vecSpot, g_vecAttackDir, bloodColor, (int)flDamage ); +} + + +int DamageDecal( CBaseEntity *pEntity, int bitsDamageType ) +{ + if ( !pEntity ) + return (DECAL_GUNSHOT1 + RANDOM_LONG(0,4)); + + return pEntity->DamageDecal( bitsDamageType ); +} + +void DecalGunshot( TraceResult *pTrace, int iBulletType ) +{ + // Is the entity valid + if ( !UTIL_IsValidEntity( pTrace->pHit ) ) + return; + + if ( VARS(pTrace->pHit)->solid == SOLID_BSP || VARS(pTrace->pHit)->movetype == MOVETYPE_PUSHSTEP ) + { + CBaseEntity *pEntity = NULL; + // Decal the wall with a gunshot + if ( !FNullEnt(pTrace->pHit) ) + pEntity = CBaseEntity::Instance(pTrace->pHit); + + switch( iBulletType ) + { + case BULLET_PLAYER_9MM: + case BULLET_MONSTER_9MM: + case BULLET_PLAYER_MP5: + case BULLET_MONSTER_MP5: + case BULLET_PLAYER_BUCKSHOT: + case BULLET_PLAYER_357: + default: + // smoke and decal + UTIL_GunshotDecalTrace( pTrace, DamageDecal( pEntity, DMG_BULLET ) ); + break; + case BULLET_MONSTER_12MM: + // smoke and decal + UTIL_GunshotDecalTrace( pTrace, DamageDecal( pEntity, DMG_BULLET ) ); + break; + case BULLET_PLAYER_CROWBAR: + // wall decal + UTIL_DecalTrace( pTrace, DamageDecal( pEntity, DMG_CLUB ) ); + break; + } + } +} + + + +// +// EjectBrass - tosses a brass shell from passed origin at passed velocity +// +void EjectBrass ( const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int model, int soundtype ) +{ + // FIX: when the player shoots, their gun isn't in the same position as it is on the model other players see. + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecOrigin ); + WRITE_BYTE( TE_MODEL); + WRITE_COORD( vecOrigin.x); + WRITE_COORD( vecOrigin.y); + WRITE_COORD( vecOrigin.z); + WRITE_COORD( vecVelocity.x); + WRITE_COORD( vecVelocity.y); + WRITE_COORD( vecVelocity.z); + WRITE_ANGLE( rotation ); + WRITE_SHORT( model ); + WRITE_BYTE ( soundtype); + WRITE_BYTE ( 25 );// 2.5 seconds + MESSAGE_END(); +} + + +#if 0 +// UNDONE: This is no longer used? +void ExplodeModel( const Vector &vecOrigin, float speed, int model, int count ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecOrigin ); + WRITE_BYTE ( TE_EXPLODEMODEL ); + WRITE_COORD( vecOrigin.x ); + WRITE_COORD( vecOrigin.y ); + WRITE_COORD( vecOrigin.z ); + WRITE_COORD( speed ); + WRITE_SHORT( model ); + WRITE_SHORT( count ); + WRITE_BYTE ( 15 );// 1.5 seconds + MESSAGE_END(); +} +#endif + + +int giAmmoIndex = 0; + +// Precaches the ammo and queues the ammo info for sending to clients +void AddAmmoNameToAmmoRegistry( const char *szAmmoname ) +{ + // make sure it's not already in the registry + for ( int i = 0; i < MAX_AMMO_SLOTS; i++ ) + { + if ( !CBasePlayerItem::AmmoInfoArray[i].pszName) + continue; + + if ( stricmp( CBasePlayerItem::AmmoInfoArray[i].pszName, szAmmoname ) == 0 ) + return; // ammo already in registry, just quite + } + + + giAmmoIndex++; + ASSERT( giAmmoIndex < MAX_AMMO_SLOTS ); + if ( giAmmoIndex >= MAX_AMMO_SLOTS ) + giAmmoIndex = 0; + + CBasePlayerItem::AmmoInfoArray[giAmmoIndex].pszName = szAmmoname; + CBasePlayerItem::AmmoInfoArray[giAmmoIndex].iId = giAmmoIndex; // yes, this info is redundant +} + + +// Precaches the weapon and queues the weapon info for sending to clients +void UTIL_PrecacheOtherWeapon( const char *szClassname ) +{ + edict_t *pent; + + pent = CREATE_NAMED_ENTITY( MAKE_STRING( szClassname ) ); + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in UTIL_PrecacheOtherWeapon\n" ); + return; + } + + CBaseEntity *pEntity = CBaseEntity::Instance (VARS( pent )); + + if (pEntity) + { + ItemInfo II; + pEntity->Precache( ); + memset( &II, 0, sizeof II ); + if ( ((CBasePlayerItem*)pEntity)->GetItemInfo( &II ) ) + { + CBasePlayerItem::ItemInfoArray[II.iId] = II; + + if ( II.pszAmmo1 && *II.pszAmmo1 ) + { + AddAmmoNameToAmmoRegistry( II.pszAmmo1 ); + } + + if ( II.pszAmmo2 && *II.pszAmmo2 ) + { + AddAmmoNameToAmmoRegistry( II.pszAmmo2 ); + } + + memset( &II, 0, sizeof II ); + } + } + + REMOVE_ENTITY(pent); +} + +// called by worldspawn +void W_Precache(void) +{ + memset( CBasePlayerItem::ItemInfoArray, 0, sizeof(CBasePlayerItem::ItemInfoArray) ); + memset( CBasePlayerItem::AmmoInfoArray, 0, sizeof(CBasePlayerItem::AmmoInfoArray) ); + giAmmoIndex = 0; + + // Discwar + UTIL_PrecacheOtherWeapon( "weapon_disc" ); + UTIL_PrecacheOther( "disc" ); +} + + + + +TYPEDESCRIPTION CBasePlayerItem::m_SaveData[] = +{ + DEFINE_FIELD( CBasePlayerItem, m_pPlayer, FIELD_CLASSPTR ), + DEFINE_FIELD( CBasePlayerItem, m_pNext, FIELD_CLASSPTR ), + //DEFINE_FIELD( CBasePlayerItem, m_fKnown, FIELD_INTEGER ),Reset to zero on load + DEFINE_FIELD( CBasePlayerItem, m_iId, FIELD_INTEGER ), + // DEFINE_FIELD( CBasePlayerItem, m_iIdPrimary, FIELD_INTEGER ), + // DEFINE_FIELD( CBasePlayerItem, m_iIdSecondary, FIELD_INTEGER ), +}; +IMPLEMENT_SAVERESTORE( CBasePlayerItem, CBaseAnimating ); + + +TYPEDESCRIPTION CBasePlayerWeapon::m_SaveData[] = +{ + DEFINE_FIELD( CBasePlayerWeapon, m_flNextPrimaryAttack, FIELD_TIME ), + DEFINE_FIELD( CBasePlayerWeapon, m_flNextSecondaryAttack, FIELD_TIME ), + DEFINE_FIELD( CBasePlayerWeapon, m_flTimeWeaponIdle, FIELD_TIME ), + DEFINE_FIELD( CBasePlayerWeapon, m_iPrimaryAmmoType, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerWeapon, m_iSecondaryAmmoType, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerWeapon, m_iClip, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerWeapon, m_iDefaultAmmo, FIELD_INTEGER ), +// DEFINE_FIELD( CBasePlayerWeapon, m_iClientClip, FIELD_INTEGER ) , reset to zero on load so hud gets updated correctly +// DEFINE_FIELD( CBasePlayerWeapon, m_iClientWeaponState, FIELD_INTEGER ), reset to zero on load so hud gets updated correctly +}; + +IMPLEMENT_SAVERESTORE( CBasePlayerWeapon, CBasePlayerItem ); + + +void CBasePlayerItem :: SetObjectCollisionBox( void ) +{ + pev->absmin = pev->origin + Vector(-24, -24, 0); + pev->absmax = pev->origin + Vector(24, 24, 16); +} + + +//========================================================= +// Sets up movetype, size, solidtype for a new weapon. +//========================================================= +void CBasePlayerItem :: FallInit( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_BBOX; + + UTIL_SetOrigin( pev, pev->origin ); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0) );//pointsize until it lands on the ground. + + SetTouch( &CBasePlayerItem::DefaultTouch ); + SetThink( &CBasePlayerItem::FallThink ); + + pev->nextthink = gpGlobals->time + 0.1; +} + +//========================================================= +// FallThink - Items that have just spawned run this think +// to catch them when they hit the ground. Once we're sure +// that the object is grounded, we change its solid type +// to trigger and set it in a large box that helps the +// player get it. +//========================================================= +void CBasePlayerItem::FallThink ( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if ( pev->flags & FL_ONGROUND ) + { + // clatter if we have an owner (i.e., dropped by someone) + // don't clatter if the gun is waiting to respawn (if it's waiting, it is invisible!) + if ( !FNullEnt( pev->owner ) ) + { + int pitch = 95 + RANDOM_LONG(0,29); + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "items/weapondrop1.wav", 1, ATTN_NORM, 0, pitch); + } + + // lie flat + pev->angles.x = 0; + pev->angles.z = 0; + + Materialize(); + } +} + +//========================================================= +// Materialize - make a CBasePlayerItem visible and tangible +//========================================================= +void CBasePlayerItem::Materialize( void ) +{ + if ( pev->effects & EF_NODRAW ) + { + // changing from invisible state to visible. + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "items/suitchargeok1.wav", 1, ATTN_NORM, 0, 150 ); + pev->effects &= ~EF_NODRAW; + pev->effects |= EF_MUZZLEFLASH; + } + + pev->solid = SOLID_TRIGGER; + + UTIL_SetOrigin( pev, pev->origin );// link into world. + SetTouch (&CBasePlayerItem::DefaultTouch); + SetThink (NULL); + +} + +//========================================================= +// AttemptToMaterialize - the item is trying to rematerialize, +// should it do so now or wait longer? +//========================================================= +void CBasePlayerItem::AttemptToMaterialize( void ) +{ + float time = g_pGameRules->FlWeaponTryRespawn( this ); + + if ( time == 0 ) + { + Materialize(); + return; + } + + pev->nextthink = gpGlobals->time + time; +} + +//========================================================= +// CheckRespawn - a player is taking this weapon, should +// it respawn? +//========================================================= +void CBasePlayerItem :: CheckRespawn ( void ) +{ + switch ( g_pGameRules->WeaponShouldRespawn( this ) ) + { + case GR_WEAPON_RESPAWN_YES: + Respawn(); + break; + case GR_WEAPON_RESPAWN_NO: + return; + break; + } +} + +//========================================================= +// Respawn- this item is already in the world, but it is +// invisible and intangible. Make it visible and tangible. +//========================================================= +CBaseEntity* CBasePlayerItem::Respawn( void ) +{ + // make a copy of this weapon that is invisible and inaccessible to players (no touch function). The weapon spawn/respawn code + // will decide when to make the weapon visible and touchable. + CBaseEntity *pNewWeapon = CBaseEntity::Create( (char *)STRING( pev->classname ), g_pGameRules->VecWeaponRespawnSpot( this ), pev->angles, pev->owner ); + + if ( pNewWeapon ) + { + pNewWeapon->pev->effects |= EF_NODRAW;// invisible for now + pNewWeapon->SetTouch( NULL );// no touch + pNewWeapon->SetThink( &CBasePlayerItem::AttemptToMaterialize ); + + DROP_TO_FLOOR ( ENT(pev) ); + + // not a typo! We want to know when the weapon the player just picked up should respawn! This new entity we created is the replacement, + // but when it should respawn is based on conditions belonging to the weapon that was taken. + pNewWeapon->pev->nextthink = g_pGameRules->FlWeaponRespawnTime( this ); + } + else + { + ALERT ( at_console, "Respawn failed to create %s!\n", STRING( pev->classname ) ); + } + + return pNewWeapon; +} + +void CBasePlayerItem::DefaultTouch( CBaseEntity *pOther ) +{ + // if it's not a player, ignore + if ( !pOther->IsPlayer() ) + return; + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + + // can I have this? + if ( !g_pGameRules->CanHavePlayerItem( pPlayer, this ) ) + { + if ( gEvilImpulse101 ) + { + UTIL_Remove( this ); + } + return; + } + + if (pOther->AddPlayerItem( this )) + { + AttachToPlayer( pPlayer ); + EMIT_SOUND(ENT(pPlayer->pev), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM); + } + + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); // UNDONE: when should this happen? +} + +BOOL CanAttack( float attack_time, float curtime, BOOL isPredicted ) +{ + if ( !isPredicted ) + { + return ( attack_time <= curtime ) ? TRUE : FALSE; + } + else + { + return ( attack_time <= 0.0 ) ? TRUE : FALSE; + } +} + +void CBasePlayerWeapon::ItemPostFrame( void ) +{ + if ((m_fInReload) && ( m_pPlayer->m_flNextAttack <= UTIL_WeaponTimeBase() ) ) + { + // complete the reload. + int j = min( iMaxClip() - m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); + + // Add them to the clip + m_iClip += j; + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= j; + + m_fInReload = FALSE; + } + + if ((m_pPlayer->pev->button & IN_ATTACK2) && CanAttack( m_flNextSecondaryAttack, gpGlobals->time, UseDecrement() ) ) + { + if ( pszAmmo2() && !m_pPlayer->m_rgAmmo[SecondaryAmmoIndex()] ) + { + m_fFireOnEmpty = TRUE; + } + + SecondaryAttack(); + m_pPlayer->pev->button &= ~IN_ATTACK2; + } + else if ((m_pPlayer->pev->button & IN_ATTACK) && CanAttack( m_flNextPrimaryAttack, gpGlobals->time, UseDecrement() ) ) + { + if ( (m_iClip == 0 && pszAmmo1()) || (iMaxClip() == -1 && !m_pPlayer->m_rgAmmo[PrimaryAmmoIndex()] ) ) + { + m_fFireOnEmpty = TRUE; + } + + PrimaryAttack(); + } + else if ( m_pPlayer->pev->button & IN_RELOAD && iMaxClip() != WEAPON_NOCLIP && !m_fInReload ) + { + // reload when reload is pressed, or if no buttons are down and weapon is empty. + Reload(); + } + else if ( !(m_pPlayer->pev->button & (IN_ATTACK|IN_ATTACK2) ) ) + { + // no fire buttons down + + m_fFireOnEmpty = FALSE; + + if ( !IsUseable() && m_flNextPrimaryAttack < ( UseDecrement() ? 0.0 : gpGlobals->time ) ) + { + // weapon isn't useable, switch. + if ( !(iFlags() & ITEM_FLAG_NOAUTOSWITCHEMPTY) && g_pGameRules->GetNextBestWeapon( m_pPlayer, this ) ) + { + m_flNextPrimaryAttack = ( UseDecrement() ? 0.0 : gpGlobals->time ) + 0.3; + return; + } + } + else + { + // weapon is useable. Reload if empty and weapon has waited as long as it has to after firing + if ( m_iClip == 0 && !(iFlags() & ITEM_FLAG_NOAUTORELOAD) && m_flNextPrimaryAttack < ( UseDecrement() ? 0.0 : gpGlobals->time ) ) + { + Reload(); + return; + } + } + + WeaponIdle( ); + return; + } + + // catch all + if ( ShouldWeaponIdle() ) + { + WeaponIdle(); + } +} + +void CBasePlayerItem::DestroyItem( void ) +{ + if ( m_pPlayer ) + { + // if attached to a player, remove. + m_pPlayer->RemovePlayerItem( this ); + } + + Kill( ); +} + +int CBasePlayerItem::AddToPlayer( CBasePlayer *pPlayer ) +{ + m_pPlayer = pPlayer; + + return TRUE; +} + +void CBasePlayerItem::Drop( void ) +{ + SetTouch( NULL ); + SetThink(&CBasePlayerItem::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; +} + +void CBasePlayerItem::Kill( void ) +{ + SetTouch( NULL ); + SetThink(&CBasePlayerItem::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; +} + +void CBasePlayerItem::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->pev->viewmodel = 0; + m_pPlayer->pev->weaponmodel = 0; +} + +void CBasePlayerItem::AttachToPlayer ( CBasePlayer *pPlayer ) +{ + pev->movetype = MOVETYPE_FOLLOW; + pev->solid = SOLID_NOT; + pev->aiment = pPlayer->edict(); + pev->effects = EF_NODRAW; // ?? + pev->modelindex = 0;// server won't send down to clients if modelindex == 0 + pev->model = iStringNull; + pev->owner = pPlayer->edict(); + pev->nextthink = gpGlobals->time + .1; + SetTouch( NULL ); +} + +// CALLED THROUGH the newly-touched weapon's instance. The existing player weapon is pOriginal +int CBasePlayerWeapon::AddDuplicate( CBasePlayerItem *pOriginal ) +{ + if ( m_iDefaultAmmo ) + { + return ExtractAmmo( (CBasePlayerWeapon *)pOriginal ); + } + else + { + // a dead player dropped this. + return ExtractClipAmmo( (CBasePlayerWeapon *)pOriginal ); + } +} + + +int CBasePlayerWeapon::AddToPlayer( CBasePlayer *pPlayer ) +{ + int bResult = CBasePlayerItem::AddToPlayer( pPlayer ); + + pPlayer->pev->weapons |= (1<GetAmmoIndex( pszAmmo1() ); + m_iSecondaryAmmoType = pPlayer->GetAmmoIndex( pszAmmo2() ); + } + + + if (bResult) + return AddWeapon( ); + return FALSE; +} + +int CBasePlayerWeapon::UpdateClientData( CBasePlayer *pPlayer ) +{ + BOOL bSend = FALSE; + int state = 0; + if ( pPlayer->m_pActiveItem == this ) + { + if ( pPlayer->m_fOnTarget ) + state = WEAPON_IS_ONTARGET; + else + state = 1; + } + + // Forcing send of all data! + if ( !pPlayer->m_fWeapon ) + { + bSend = TRUE; + } + + // This is the current or last weapon, so the state will need to be updated + if ( this == pPlayer->m_pActiveItem || + this == pPlayer->m_pClientActiveItem ) + { + if ( pPlayer->m_pActiveItem != pPlayer->m_pClientActiveItem ) + { + bSend = TRUE; + } + } + + // If the ammo, state, or fov has changed, update the weapon + if ( m_iClip != m_iClientClip || + state != m_iClientWeaponState || + pPlayer->m_iFOV != pPlayer->m_iClientFOV ) + { + bSend = TRUE; + } + + if ( bSend ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pPlayer->pev ); + WRITE_BYTE( state ); + WRITE_BYTE( m_iId ); + WRITE_BYTE( m_iClip ); + MESSAGE_END(); + + m_iClientClip = m_iClip; + m_iClientWeaponState = state; + pPlayer->m_fWeapon = TRUE; + } + + if ( m_pNext ) + m_pNext->UpdateClientData( pPlayer ); + + return 1; +} + + +void CBasePlayerWeapon::SendWeaponAnim( int iAnim, int skiplocal ) +{ + m_pPlayer->pev->weaponanim = iAnim; + + if ( skiplocal && ENGINE_CANSKIP( m_pPlayer->edict() ) ) + return; + + MESSAGE_BEGIN( MSG_ONE, SVC_WEAPONANIM, NULL, m_pPlayer->pev ); + WRITE_BYTE( iAnim ); // sequence number + WRITE_BYTE( pev->body ); // weaponmodel bodygroup. + MESSAGE_END(); +} + +BOOL CBasePlayerWeapon :: AddPrimaryAmmo( int iCount, char *szName, int iMaxClip, int iMaxCarry ) +{ + int iIdAmmo; + + if (iMaxClip < 1) + { + m_iClip = -1; + iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName, iMaxCarry ); + } + else if (m_iClip == 0) + { + int i; + i = min( m_iClip + iCount, iMaxClip ) - m_iClip; + m_iClip += i; + iIdAmmo = m_pPlayer->GiveAmmo( iCount - i, szName, iMaxCarry ); + } + else + { + iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName, iMaxCarry ); + } + + // m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] = iMaxCarry; // hack for testing + + if (iIdAmmo > 0) + { + m_iPrimaryAmmoType = iIdAmmo; + if (m_pPlayer->HasPlayerItem( this ) ) + { + // play the "got ammo" sound only if we gave some ammo to a player that already had this gun. + // if the player is just getting this gun for the first time, DefaultTouch will play the "picked up gun" sound for us. + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + } + } + + return iIdAmmo > 0 ? TRUE : FALSE; +} + + +BOOL CBasePlayerWeapon :: AddSecondaryAmmo( int iCount, char *szName, int iMax ) +{ + int iIdAmmo; + + iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName, iMax ); + + //m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] = iMax; // hack for testing + + if (iIdAmmo > 0) + { + m_iSecondaryAmmoType = iIdAmmo; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + } + return iIdAmmo > 0 ? TRUE : FALSE; +} + +//========================================================= +// IsUseable - this function determines whether or not a +// weapon is useable by the player in its current state. +// (does it have ammo loaded? do I have any ammo for the +// weapon?, etc) +//========================================================= +BOOL CBasePlayerWeapon :: IsUseable( void ) +{ + if ( m_iClip <= 0 ) + { + if ( m_pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] <= 0 && iMaxAmmo1() != -1 ) + { + // clip is empty (or nonexistant) and the player has no more ammo of this type. + return FALSE; + } + } + + return TRUE; +} + +BOOL CBasePlayerWeapon :: CanDeploy( void ) +{ + BOOL bHasAmmo = 0; + + if ( !pszAmmo1() ) + { + // this weapon doesn't use ammo, can always deploy. + return TRUE; + } + + if ( pszAmmo1() ) + { + bHasAmmo |= (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] != 0); + } + if ( pszAmmo2() ) + { + bHasAmmo |= (m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] != 0); + } + if (m_iClip > 0) + { + bHasAmmo |= 1; + } + if (!bHasAmmo) + { + return FALSE; + } + + return TRUE; +} + +BOOL CBasePlayerWeapon :: DefaultDeploy( char *szViewModel, char *szWeaponModel, int iAnim, char *szAnimExt, int skiplocal /* = 0 */ ) +{ + if (!CanDeploy( )) + return FALSE; + + m_pPlayer->pev->viewmodel = MAKE_STRING(szViewModel); + m_pPlayer->pev->weaponmodel = MAKE_STRING(szWeaponModel); + strcpy( m_pPlayer->m_szAnimExtention, szAnimExt ); + SendWeaponAnim( iAnim, skiplocal ); + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.0; + + return TRUE; +} + + +BOOL CBasePlayerWeapon :: DefaultReload( int iClipSize, int iAnim, float fDelay ) +{ + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + return FALSE; + + int j = min(iClipSize - m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); + + if (j == 0) + return FALSE; + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + fDelay; + + //!!UNDONE -- reload sound goes here !!! + SendWeaponAnim( iAnim, UseDecrement() ? 1 : 0 ); + + m_fInReload = TRUE; + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 3; + return TRUE; +} + +BOOL CBasePlayerWeapon :: PlayEmptySound( void ) +{ + if (m_iPlayEmptySound) + { + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/357_cock1.wav", 0.8, ATTN_NORM); + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +void CBasePlayerWeapon :: ResetEmptySound( void ) +{ + m_iPlayEmptySound = 1; +} + +//========================================================= +//========================================================= +int CBasePlayerWeapon::PrimaryAmmoIndex( void ) +{ + return m_iPrimaryAmmoType; +} + +//========================================================= +//========================================================= +int CBasePlayerWeapon::SecondaryAmmoIndex( void ) +{ + return -1; +} + +void CBasePlayerWeapon::Holster( int skiplocal /* = 0 */ ) +{ + m_fInReload = FALSE; // cancel any reload in progress. + m_pPlayer->pev->viewmodel = 0; + m_pPlayer->pev->weaponmodel = 0; +} + +void CBasePlayerAmmo::Spawn( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 16)); + UTIL_SetOrigin( pev, pev->origin ); + + SetTouch( &CBasePlayerAmmo::DefaultTouch ); +} + +CBaseEntity* CBasePlayerAmmo::Respawn( void ) +{ + pev->effects |= EF_NODRAW; + SetTouch( NULL ); + + UTIL_SetOrigin( pev, g_pGameRules->VecAmmoRespawnSpot( this ) );// move to wherever I'm supposed to repawn. + + SetThink( &CBasePlayerAmmo::Materialize ); + pev->nextthink = g_pGameRules->FlAmmoRespawnTime( this ); + + return this; +} + +void CBasePlayerAmmo::Materialize( void ) +{ + if ( pev->effects & EF_NODRAW ) + { + // changing from invisible state to visible. + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "items/suitchargeok1.wav", 1, ATTN_NORM, 0, 150 ); + pev->effects &= ~EF_NODRAW; + pev->effects |= EF_MUZZLEFLASH; + } + + SetTouch( &CBasePlayerAmmo::DefaultTouch ); +} + +void CBasePlayerAmmo :: DefaultTouch( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + { + return; + } + + if (AddAmmo( pOther )) + { + if ( g_pGameRules->AmmoShouldRespawn( this ) == GR_AMMO_RESPAWN_YES ) + { + Respawn(); + } + else + { + SetTouch( NULL ); + SetThink(&CBasePlayerAmmo::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; + } + } + else if (gEvilImpulse101) + { + // evil impulse 101 hack, kill always + SetTouch( NULL ); + SetThink(&CBasePlayerAmmo::SUB_Remove); + pev->nextthink = gpGlobals->time + .1; + } +} + +//========================================================= +// called by the new item with the existing item as parameter +// +// if we call ExtractAmmo(), it's because the player is picking up this type of weapon for +// the first time. If it is spawned by the world, m_iDefaultAmmo will have a default ammo amount in it. +// if this is a weapon dropped by a dying player, has 0 m_iDefaultAmmo, which means only the ammo in +// the weapon clip comes along. +//========================================================= +int CBasePlayerWeapon::ExtractAmmo( CBasePlayerWeapon *pWeapon ) +{ + int iReturn; + + if ( pszAmmo1() != NULL ) + { + // blindly call with m_iDefaultAmmo. It's either going to be a value or zero. If it is zero, + // we only get the ammo in the weapon's clip, which is what we want. + iReturn = pWeapon->AddPrimaryAmmo( m_iDefaultAmmo, (char *)pszAmmo1(), iMaxClip(), iMaxAmmo1() ); + m_iDefaultAmmo = 0; + } + + if ( pszAmmo2() != NULL ) + { + iReturn = pWeapon->AddSecondaryAmmo( 0, (char *)pszAmmo2(), iMaxAmmo2() ); + } + + return iReturn; +} + +//========================================================= +// called by the new item's class with the existing item as parameter +//========================================================= +int CBasePlayerWeapon::ExtractClipAmmo( CBasePlayerWeapon *pWeapon ) +{ + int iAmmo; + + if ( m_iClip == WEAPON_NOCLIP ) + { + iAmmo = 0;// guns with no clips always come empty if they are second-hand + } + else + { + iAmmo = m_iClip; + } + + return pWeapon->m_pPlayer->GiveAmmo( iAmmo, (char *)pszAmmo1(), iMaxAmmo1() ); // , &m_iPrimaryAmmoType +} + +//========================================================= +// RetireWeapon - no more ammo for this gun, put it away. +//========================================================= +void CBasePlayerWeapon::RetireWeapon( void ) +{ + // first, no viewmodel at all. + m_pPlayer->pev->viewmodel = iStringNull; + m_pPlayer->pev->weaponmodel = iStringNull; + //m_pPlayer->pev->viewmodelindex = NULL; + + g_pGameRules->GetNextBestWeapon( m_pPlayer, this ); +} + +void CBasePlayerWeapon::PrintState( void ) +{ +} diff --git a/ricochet/dlls/weapons.h b/ricochet/dlls/weapons.h new file mode 100644 index 0000000..533ee2d --- /dev/null +++ b/ricochet/dlls/weapons.h @@ -0,0 +1,451 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef WEAPONS_H +#define WEAPONS_H + + +class CBasePlayer; +extern int gmsgWeapPickup; + +// Contact Grenade / Timed grenade / Satchel Charge +class CGrenade : public CBaseMonster +{ +public: + void Spawn( void ); + + typedef enum { SATCHEL_DETONATE = 0, SATCHEL_RELEASE } SATCHELCODE; + + static CGrenade *ShootTimed( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time ); + static CGrenade *ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + static CGrenade *ShootSatchelCharge( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + static void UseSatchelCharges( entvars_t *pevOwner, SATCHELCODE code ); + + void Explode( Vector vecSrc, Vector vecAim ); + void Explode( TraceResult *pTrace, int bitsDamageType ); + void EXPORT Smoke( void ); + + void EXPORT BounceTouch( CBaseEntity *pOther ); + void EXPORT SlideTouch( CBaseEntity *pOther ); + void EXPORT ExplodeTouch( CBaseEntity *pOther ); + void EXPORT DangerSoundThink( void ); + void EXPORT PreDetonate( void ); + void EXPORT Detonate( void ); + void EXPORT DetonateUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT TumbleThink( void ); + + virtual void BounceSound( void ); + virtual int BloodColor( void ) { return DONT_BLEED; } + virtual void Killed( entvars_t *pevAttacker, int iGib ); + + BOOL m_fRegisteredSound;// whether or not this grenade has issued its DANGER sound to the world sound list yet. +}; + + +// constant items +#define ITEM_HEALTHKIT 1 +#define ITEM_ANTIDOTE 2 +#define ITEM_SECURITY 3 +#define ITEM_BATTERY 4 + +#define WEAPON_NONE 0 +#define WEAPON_CROWBAR 1 +#define WEAPON_GLOCK 2 +#define WEAPON_PYTHON 3 +#define WEAPON_MP5 4 +#define WEAPON_CHAINGUN 5 +#define WEAPON_CROSSBOW 6 +#define WEAPON_SHOTGUN 7 +#define WEAPON_RPG 8 +#define WEAPON_GAUSS 9 +#define WEAPON_EGON 10 +#define WEAPON_HORNETGUN 11 +#define WEAPON_HANDGRENADE 12 +#define WEAPON_TRIPMINE 13 +#define WEAPON_SATCHEL 14 +#define WEAPON_SNARK 15 + +#define WEAPON_ALLWEAPONS (~(1<skin < 0 || (gpGlobals->deathmatch && FBitSet( pev->spawnflags, SF_DECAL_NOTINDEATHMATCH )) ) + { + REMOVE_ENTITY(ENT(pev)); + return; + } + + if ( FStringNull ( pev->targetname ) ) + { + SetThink( &CDecal::StaticDecal ); + // if there's no targetname, the decal will spray itself on as soon as the world is done spawning. + pev->nextthink = gpGlobals->time; + } + else + { + // if there IS a targetname, the decal sprays itself on when it is triggered. + SetThink ( &CDecal::SUB_DoNothing ); + SetUse(&CDecal::TriggerDecal); + } +} + +void CDecal :: TriggerDecal ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // this is set up as a USE function for infodecals that have targetnames, so that the + // decal doesn't get applied until it is fired. (usually by a scripted sequence) + TraceResult trace; + int entityIndex; + + UTIL_TraceLine( pev->origin - Vector(5,5,5), pev->origin + Vector(5,5,5), ignore_monsters, ENT(pev), &trace ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY); + WRITE_BYTE( TE_BSPDECAL ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( (int)pev->skin ); + entityIndex = (short)ENTINDEX(trace.pHit); + WRITE_SHORT( entityIndex ); + if ( entityIndex ) + WRITE_SHORT( (int)VARS(trace.pHit)->modelindex ); + MESSAGE_END(); + + SetThink( &CDecal::SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CDecal :: StaticDecal( void ) +{ + TraceResult trace; + int entityIndex, modelIndex; + + UTIL_TraceLine( pev->origin - Vector(5,5,5), pev->origin + Vector(5,5,5), ignore_monsters, ENT(pev), &trace ); + + entityIndex = (short)ENTINDEX(trace.pHit); + if ( entityIndex ) + modelIndex = (int)VARS(trace.pHit)->modelindex; + else + modelIndex = 0; + + g_engfuncs.pfnStaticDecal( pev->origin, (int)pev->skin, entityIndex, modelIndex ); + + SUB_Remove(); +} + + +void CDecal :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "texture")) + { + pev->skin = DECAL_INDEX( pkvd->szValue ); + + // Found + if ( pev->skin >= 0 ) + return; + ALERT( at_console, "Can't find decal %s\n", pkvd->szValue ); + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +// Body queue class here.... It's really just CBaseEntity +class CCorpse : public CBaseEntity +{ + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +LINK_ENTITY_TO_CLASS( bodyque, CCorpse ); + +static void InitBodyQue(void) +{ + string_t istrClassname = MAKE_STRING("bodyque"); + + g_pBodyQueueHead = CREATE_NAMED_ENTITY( istrClassname ); + entvars_t *pev = VARS(g_pBodyQueueHead); + + // Reserve 3 more slots for dead bodies + for ( int i = 0; i < 3; i++ ) + { + pev->owner = CREATE_NAMED_ENTITY( istrClassname ); + pev = VARS(pev->owner); + } + + pev->owner = g_pBodyQueueHead; +} + + +// +// make a body que entry for the given ent so the ent can be respawned elsewhere +// +// GLOBALS ASSUMED SET: g_eoBodyQueueHead +// +void CopyToBodyQue(entvars_t *pev) +{ + // DISCWAR: No corpses + return; + + if (pev->effects & EF_NODRAW) + return; + + entvars_t *pevHead = VARS(g_pBodyQueueHead); + + pevHead->angles = pev->angles; + pevHead->model = pev->model; + pevHead->modelindex = pev->modelindex; + pevHead->frame = pev->frame; + pevHead->colormap = pev->colormap; + pevHead->movetype = MOVETYPE_TOSS; + pevHead->velocity = pev->velocity; + pevHead->flags = 0; + pevHead->deadflag = pev->deadflag; + pevHead->renderfx = kRenderFxDeadPlayer; + pevHead->renderamt = ENTINDEX( ENT( pev ) ); + + pevHead->effects = pev->effects | EF_NOINTERP; + //pevHead->goalstarttime = pev->goalstarttime; + //pevHead->goalframe = pev->goalframe; + //pevHead->goalendtime = pev->goalendtime ; + + pevHead->sequence = pev->sequence; + pevHead->animtime = pev->animtime; + + UTIL_SetOrigin(pevHead, pev->origin); + UTIL_SetSize(pevHead, pev->mins, pev->maxs); + g_pBodyQueueHead = pevHead->owner; +} + + +CGlobalState::CGlobalState( void ) +{ + Reset(); +} + +void CGlobalState::Reset( void ) +{ + m_pList = NULL; + m_listCount = 0; +} + +globalentity_t *CGlobalState :: Find( string_t globalname ) +{ + if ( !globalname ) + return NULL; + + globalentity_t *pTest; + const char *pEntityName = STRING(globalname); + + + pTest = m_pList; + while ( pTest ) + { + if ( FStrEq( pEntityName, pTest->name ) ) + break; + + pTest = pTest->pNext; + } + + return pTest; +} + + +// This is available all the time now on impulse 104, remove later +//#ifdef _DEBUG +void CGlobalState :: DumpGlobals( void ) +{ + static char *estates[] = { "Off", "On", "Dead" }; + globalentity_t *pTest; + + ALERT( at_console, "-- Globals --\n" ); + pTest = m_pList; + while ( pTest ) + { + ALERT( at_console, "%s: %s (%s)\n", pTest->name, pTest->levelName, estates[pTest->state] ); + pTest = pTest->pNext; + } +} +//#endif + + +void CGlobalState :: EntityAdd( string_t globalname, string_t mapName, GLOBALESTATE state ) +{ + ASSERT( !Find(globalname) ); + + globalentity_t *pNewEntity = (globalentity_t *)calloc( sizeof( globalentity_t ), 1 ); + ASSERT( pNewEntity != NULL ); + pNewEntity->pNext = m_pList; + m_pList = pNewEntity; + strcpy( pNewEntity->name, STRING( globalname ) ); + strcpy( pNewEntity->levelName, STRING(mapName) ); + pNewEntity->state = state; + m_listCount++; +} + + +void CGlobalState :: EntitySetState( string_t globalname, GLOBALESTATE state ) +{ + globalentity_t *pEnt = Find( globalname ); + + if ( pEnt ) + pEnt->state = state; +} + + +const globalentity_t *CGlobalState :: EntityFromTable( string_t globalname ) +{ + globalentity_t *pEnt = Find( globalname ); + + return pEnt; +} + + +GLOBALESTATE CGlobalState :: EntityGetState( string_t globalname ) +{ + globalentity_t *pEnt = Find( globalname ); + if ( pEnt ) + return pEnt->state; + + return GLOBAL_OFF; +} + + +// Global Savedata for Delay +TYPEDESCRIPTION CGlobalState::m_SaveData[] = +{ + DEFINE_FIELD( CGlobalState, m_listCount, FIELD_INTEGER ), +}; + +// Global Savedata for Delay +TYPEDESCRIPTION gGlobalEntitySaveData[] = +{ + DEFINE_ARRAY( globalentity_t, name, FIELD_CHARACTER, 64 ), + DEFINE_ARRAY( globalentity_t, levelName, FIELD_CHARACTER, 32 ), + DEFINE_FIELD( globalentity_t, state, FIELD_INTEGER ), +}; + + +int CGlobalState::Save( CSave &save ) +{ + int i; + globalentity_t *pEntity; + + if ( !save.WriteFields( "GLOBAL", this, m_SaveData, ARRAYSIZE(m_SaveData) ) ) + return 0; + + pEntity = m_pList; + for ( i = 0; i < m_listCount && pEntity; i++ ) + { + if ( !save.WriteFields( "GENT", pEntity, gGlobalEntitySaveData, ARRAYSIZE(gGlobalEntitySaveData) ) ) + return 0; + + pEntity = pEntity->pNext; + } + + return 1; +} + +int CGlobalState::Restore( CRestore &restore ) +{ + int i, listCount; + globalentity_t tmpEntity; + + + ClearStates(); + if ( !restore.ReadFields( "GLOBAL", this, m_SaveData, ARRAYSIZE(m_SaveData) ) ) + return 0; + + listCount = m_listCount; // Get new list count + m_listCount = 0; // Clear loaded data + + for ( i = 0; i < listCount; i++ ) + { + if ( !restore.ReadFields( "GENT", &tmpEntity, gGlobalEntitySaveData, ARRAYSIZE(gGlobalEntitySaveData) ) ) + return 0; + EntityAdd( MAKE_STRING(tmpEntity.name), MAKE_STRING(tmpEntity.levelName), tmpEntity.state ); + } + return 1; +} + +void CGlobalState::EntityUpdate( string_t globalname, string_t mapname ) +{ + globalentity_t *pEnt = Find( globalname ); + + if ( pEnt ) + strcpy( pEnt->levelName, STRING(mapname) ); +} + + +void CGlobalState::ClearStates( void ) +{ + globalentity_t *pFree = m_pList; + while ( pFree ) + { + globalentity_t *pNext = pFree->pNext; + free( pFree ); + pFree = pNext; + } + Reset(); +} + + +void SaveGlobalState( SAVERESTOREDATA *pSaveData ) +{ + CSave saveHelper( pSaveData ); + gGlobalState.Save( saveHelper ); +} + + +void RestoreGlobalState( SAVERESTOREDATA *pSaveData ) +{ + CRestore restoreHelper( pSaveData ); + gGlobalState.Restore( restoreHelper ); +} + + +void ResetGlobalState( void ) +{ + gGlobalState.ClearStates(); + gInitHUD = TRUE; // Init the HUD on a new game / load game + g_iPlayersPerTeam = 0; +} + +// moved CWorld class definition to cbase.h +//======================= +// CWorld +// +// This spawns first when each level begins. +//======================= + +LINK_ENTITY_TO_CLASS( worldspawn, CWorld ); + +#define SF_WORLD_DARK 0x0001 // Fade from black at startup +#define SF_WORLD_TITLE 0x0002 // Display game title at startup +#define SF_WORLD_FORCETEAM 0x0004 // Force teams + +extern DLL_GLOBAL BOOL g_fGameOver; +float g_flWeaponCheat; + +void CWorld :: Spawn( void ) +{ + g_fGameOver = FALSE; + Precache( ); + g_flWeaponCheat = CVAR_GET_FLOAT( "sv_cheats" ); // Is the impulse 101 command allowed? + + if ( m_iArenaOff ) + g_iMapTurnedOffArena = TRUE; + else + g_iMapTurnedOffArena = FALSE; +} + +void CWorld :: Precache( void ) +{ + g_pLastSpawn = NULL; + + CVAR_SET_STRING("sv_gravity", "800"); // 67ft/sec + CVAR_SET_STRING("sv_stepsize", "18"); + CVAR_SET_STRING("room_type", "0"); // clear DSP + + // Create all the arenas + int i; + for ( i = 0; i < MAX_ARENAS; i++) + { + g_pArenaList[i] = GetClassPtr( ( CDiscArena *)NULL ); + g_pArenaList[i]->Spawn(); + } + + // Set up game rules + if (g_pGameRules) + { + delete g_pGameRules; + } + + g_pGameRules = InstallGameRules( ); + + //!!!UNDONE why is there so much Spawn code in the Precache function? I'll just keep it here + + ///!!!LATER - do we want a sound ent in deathmatch? (sjb) + //pSoundEnt = CBaseEntity::Create( "soundent", g_vecZero, g_vecZero, edict() ); + pSoundEnt = GetClassPtr( ( CSoundEnt *)NULL ); + pSoundEnt->Spawn(); + + if ( !pSoundEnt ) + { + ALERT ( at_console, "**COULD NOT CREATE SOUNDENT**\n" ); + } + + InitBodyQue(); + +// init sentence group playback stuff from sentences.txt. +// ok to call this multiple times, calls after first are ignored. + + SENTENCEG_Init(); + +// init texture type array from materials.txt + + TEXTURETYPE_Init(); + + +// the area based ambient sounds MUST be the first precache_sounds + +// player precaches + W_Precache (); // get weapon precaches + + ClientPrecache(); + +// sounds used from C physics code + PRECACHE_SOUND("common/null.wav"); // clears sound channels + + PRECACHE_SOUND( "items/suitchargeok1.wav" );//!!! temporary sound for respawning weapons. + PRECACHE_SOUND( "items/gunpickup2.wav" );// player picks up a gun. + + PRECACHE_SOUND( "common/bodydrop3.wav" );// dead bodies hitting the ground (animation events) + PRECACHE_SOUND( "common/bodydrop4.wav" ); + + PRECACHE_SOUND( "r_tele1.wav" ); // respawn sound + PRECACHE_SOUND( "scream1.wav" ); // falling scream sound + PRECACHE_SOUND( "scream2.wav" ); // falling scream sound + PRECACHE_SOUND( "scream3.wav" ); // falling scream sound + PRECACHE_SOUND( "decap.wav" ); // decapitation sound + PRECACHE_SOUND( "shatter.wav" ); // freeze decapitation sound + PRECACHE_MODEL( "models/head.mdl" ); // head + + g_Language = (int)CVAR_GET_FLOAT( "sv_language" ); + if ( g_Language == LANGUAGE_GERMAN ) + { + PRECACHE_MODEL( "models/germangibs.mdl" ); + } + else + { + PRECACHE_MODEL( "models/hgibs.mdl" ); + PRECACHE_MODEL( "models/agibs.mdl" ); + } + + PRECACHE_SOUND ("weapons/ric1.wav"); + PRECACHE_SOUND ("weapons/ric2.wav"); + PRECACHE_SOUND ("weapons/ric3.wav"); + PRECACHE_SOUND ("weapons/ric4.wav"); + PRECACHE_SOUND ("weapons/ric5.wav"); +// +// Setup light animation tables. 'a' is total darkness, 'z' is maxbright. +// + + // 0 normal + LIGHT_STYLE(0, "m"); + + // 1 FLICKER (first variety) + LIGHT_STYLE(1, "mmnmmommommnonmmonqnmmo"); + + // 2 SLOW STRONG PULSE + LIGHT_STYLE(2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); + + // 3 CANDLE (first variety) + LIGHT_STYLE(3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"); + + // 4 FAST STROBE + LIGHT_STYLE(4, "mamamamamama"); + + // 5 GENTLE PULSE 1 + LIGHT_STYLE(5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj"); + + // 6 FLICKER (second variety) + LIGHT_STYLE(6, "nmonqnmomnmomomno"); + + // 7 CANDLE (second variety) + LIGHT_STYLE(7, "mmmaaaabcdefgmmmmaaaammmaamm"); + + // 8 CANDLE (third variety) + LIGHT_STYLE(8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"); + + // 9 SLOW STROBE (fourth variety) + LIGHT_STYLE(9, "aaaaaaaazzzzzzzz"); + + // 10 FLUORESCENT FLICKER + LIGHT_STYLE(10, "mmamammmmammamamaaamammma"); + + // 11 SLOW PULSE NOT FADE TO BLACK + LIGHT_STYLE(11, "abcdefghijklmnopqrrqponmlkjihgfedcba"); + + // 12 UNDERWATER LIGHT MUTATION + // this light only distorts the lightmap - no contribution + // is made to the brightness of affected surfaces + LIGHT_STYLE(12, "mmnnmmnnnmmnn"); + + // styles 32-62 are assigned by the light program for switchable lights + + // 63 testing + LIGHT_STYLE(63, "a"); + + for ( i = 0; i < ARRAYSIZE(gDecals); i++ ) + gDecals[i].index = DECAL_INDEX( gDecals[i].name ); + +// init the WorldGraph. + WorldGraph.InitGraph(); + +// make sure the .NOD file is newer than the .BSP file. + if ( !WorldGraph.CheckNODFile ( ( char * )STRING( gpGlobals->mapname ) ) ) + {// NOD file is not present, or is older than the BSP file. + WorldGraph.AllocNodes (); + } + else + {// Load the node graph for this level + if ( !WorldGraph.FLoadGraph ( (char *)STRING( gpGlobals->mapname ) ) ) + {// couldn't load, so alloc and prepare to build a graph. + ALERT ( at_console, "*Error opening .NOD file\n" ); + WorldGraph.AllocNodes (); + } + else + { + ALERT ( at_console, "\n*Graph Loaded!\n" ); + } + } + + if ( pev->speed > 0 ) + CVAR_SET_FLOAT( "sv_zmax", pev->speed ); + else + CVAR_SET_FLOAT( "sv_zmax", 4096 ); + + if ( pev->netname ) + { + ALERT( at_aiconsole, "Chapter title: %s\n", STRING(pev->netname) ); + CBaseEntity *pEntity = CBaseEntity::Create( "env_message", g_vecZero, g_vecZero, NULL ); + if ( pEntity ) + { + pEntity->SetThink( &CBaseEntity::SUB_CallUseToggle ); + pEntity->pev->message = pev->netname; + pev->netname = 0; + pEntity->pev->nextthink = gpGlobals->time + 0.3; + pEntity->pev->spawnflags = SF_MESSAGE_ONCE; + } + } + + if ( pev->spawnflags & SF_WORLD_DARK ) + CVAR_SET_FLOAT( "v_dark", 1.0 ); + else + CVAR_SET_FLOAT( "v_dark", 0.0 ); + + if ( pev->spawnflags & SF_WORLD_TITLE ) + gDisplayTitle = TRUE; // display the game title if this key is set + else + gDisplayTitle = FALSE; + + if ( pev->spawnflags & SF_WORLD_FORCETEAM ) + { + CVAR_SET_FLOAT( "mp_defaultteam", 1 ); + } + else + { + CVAR_SET_FLOAT( "mp_defaultteam", 0 ); + } + + // Discwar + if ( g_iPlayersPerTeam < 1 ) + g_iPlayersPerTeam = CVAR_GET_FLOAT("rc_playersperteam"); +} + + +// +// Just to ignore the "wad" field. +// +void CWorld :: KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "skyname") ) + { + // Sent over net now. + CVAR_SET_STRING( "sv_skyname", pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "sounds") ) + { + gpGlobals->cdAudioTrack = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "WaveHeight") ) + { + // Sent over net now. + pev->scale = atof(pkvd->szValue) * (1.0/8.0); + pkvd->fHandled = TRUE; + CVAR_SET_FLOAT( "sv_wateramp", pev->scale ); + } + else if ( FStrEq(pkvd->szKeyName, "MaxRange") ) + { + pev->speed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "chaptertitle") ) + { + pev->netname = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "startdark") ) + { + // UNDONE: This is a gross hack!!! The CVAR is NOT sent over the client/sever link + // but it will work for single player + int flag = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + if ( flag ) + pev->spawnflags |= SF_WORLD_DARK; + } + else if ( FStrEq(pkvd->szKeyName, "newunit") ) + { + // Single player only. Clear save directory if set + if ( atoi(pkvd->szValue) ) + CVAR_SET_FLOAT( "sv_newunit", 1 ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "gametitle") ) + { + if ( atoi(pkvd->szValue) ) + pev->spawnflags |= SF_WORLD_TITLE; + + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "mapteams") ) + { + pev->team = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "defaultteam") ) + { + if ( atoi(pkvd->szValue) ) + { + pev->spawnflags |= SF_WORLD_FORCETEAM; + } + pkvd->fHandled = TRUE; + } + + // Discwar + else if ( FStrEq(pkvd->szKeyName, "playersperteam") ) + { + g_iPlayersPerTeam = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "no_arena") ) + { + if ( atoi(pkvd->szValue) ) + m_iArenaOff = TRUE; + pkvd->fHandled = TRUE; + } + + else + CBaseEntity::KeyValue( pkvd ); +} diff --git a/ricochet/dlls/wpn_shared/disc_weapon_disc.cpp b/ricochet/dlls/wpn_shared/disc_weapon_disc.cpp new file mode 100644 index 0000000..5f7a170 --- /dev/null +++ b/ricochet/dlls/wpn_shared/disc_weapon_disc.cpp @@ -0,0 +1,753 @@ + +//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: Weapon functionality for Discwar +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "effects.h" +#include "discwar.h" +#include "disc_objects.h" +#include "disc_arena.h" + +// Disc trail colors +float g_iaDiscColors[33][3] = +{ + { 255, 255, 255, }, + { 250, 0, 0 }, + { 0, 0, 250 }, + { 0, 250, 0 }, + { 128, 128, 0 }, + { 128, 0, 128 }, + { 0, 128, 128 }, + { 128, 128, 128 }, + { 64, 128, 0 }, + { 128, 64, 0 }, + { 128, 0, 64 }, + { 64, 0, 128 }, + { 0, 64, 128 }, + { 64, 64, 128 }, + { 128, 64, 64 }, + { 64, 128, 64 }, + { 128, 128, 64 }, + { 128, 64, 128 }, + { 64, 128, 128 }, + { 250, 128, 0 }, + { 128, 250, 0 }, + { 128, 0, 250 }, + { 250, 0, 128 }, + { 0, 250, 128 }, + { 250, 250, 128 }, + { 250, 128, 250 }, + { 128, 250, 250 }, + { 250, 128, 64 }, + { 250, 64, 128 }, + { 128, 250, 64 }, + { 64, 128, 250 }, + { 128, 64, 250 }, +}; + +enum disc_e +{ + DISC_IDLE = 0, + DISC_FIDGET, + DISC_PINPULL, + DISC_THROW1, // toss + DISC_THROW2, // medium + DISC_THROW3, // hard + DISC_HOLSTER, + DISC_DRAW +}; + +#include "disc_weapon.h" + +LINK_ENTITY_TO_CLASS( weapon_disc, CDiscWeapon ); + +#if !defined( CLIENT_DLL ) +LINK_ENTITY_TO_CLASS( disc, CDisc ); + +//======================================================================================== +// DISC +//======================================================================================== +void CDisc::Spawn( void ) +{ + Precache( ); + + pev->classname = MAKE_STRING("disc"); + pev->movetype = MOVETYPE_BOUNCEMISSILE; + pev->solid = SOLID_TRIGGER; + + // Setup model + if ( m_iPowerupFlags & POW_HARD ) + SET_MODEL(ENT(pev), "models/disc_hard.mdl"); + else + SET_MODEL(ENT(pev), "models/disc.mdl"); + UTIL_SetSize(pev, Vector( -4,-4,-4 ), Vector(4, 4, 4)); + + UTIL_SetOrigin( pev, pev->origin ); + SetTouch( &CDisc::DiscTouch ); + SetThink( &CDisc::DiscThink ); + + m_iBounces = 0; + m_fDontTouchOwner = gpGlobals->time + 0.2; + m_fDontTouchEnemies = 0; + m_bRemoveSelf = false; + m_bTeleported = false; + m_pLockTarget = NULL; + + UTIL_MakeVectors( pev->angles ); + + // Fast powerup makes discs go faster + if ( m_iPowerupFlags & POW_FAST ) + pev->velocity = gpGlobals->v_forward * DISC_VELOCITY * 1.5; + else + pev->velocity = gpGlobals->v_forward * DISC_VELOCITY; + + // Pull our owner out so we will still touch it + if ( pev->owner ) + m_hOwner = Instance(pev->owner); + pev->owner = NULL; + + // Trail + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMFOLLOW ); + WRITE_SHORT(entindex()); // entity + WRITE_SHORT(m_iTrail ); // model + + if (m_bDecapitate) + WRITE_BYTE( 5 ); // life + else + WRITE_BYTE( 3 ); // life + + WRITE_BYTE( 5 ); // width + + WRITE_BYTE( g_iaDiscColors[pev->team][0] ); // r, g, b + WRITE_BYTE( g_iaDiscColors[pev->team][1] ); // r, g, b + WRITE_BYTE( g_iaDiscColors[pev->team][2] ); // r, g, b + + WRITE_BYTE( 250 ); // brightness + MESSAGE_END(); + + // Decapitator's make sound + if (m_bDecapitate) + EMIT_SOUND( ENT(pev), CHAN_VOICE, "weapons/rocket1.wav", 0.5, 0.5 ); + + // Highlighter + pev->renderfx = kRenderFxGlowShell; + for (int i = 0; i <= 2;i ++) + pev->rendercolor[i] = g_iaDiscColors[pev->team][i]; + pev->renderamt = 100; + + pev->nextthink = gpGlobals->time + 0.1; +} + +void CDisc::Precache( void ) +{ + PRECACHE_MODEL("models/disc.mdl"); + PRECACHE_MODEL("models/disc_hard.mdl"); + PRECACHE_SOUND("weapons/cbar_hitbod1.wav"); + PRECACHE_SOUND("weapons/cbar_hitbod2.wav"); + PRECACHE_SOUND("weapons/cbar_hitbod3.wav"); + PRECACHE_SOUND("weapons/altfire.wav"); + PRECACHE_SOUND("items/gunpickup2.wav"); + PRECACHE_SOUND("weapons/electro5.wav"); + PRECACHE_SOUND("weapons/xbow_hit1.wav"); + PRECACHE_SOUND("weapons/xbow_hit2.wav"); + PRECACHE_SOUND("weapons/rocket1.wav"); + PRECACHE_SOUND("dischit.wav"); + m_iTrail = PRECACHE_MODEL("sprites/smoke.spr"); + m_iSpriteTexture = PRECACHE_MODEL( "sprites/lgtning.spr" ); +} + +/* +void CDisc::SetObjectCollisionBox( void ) +{ + pev->absmin = pev->origin + Vector( -8, -8, 8 ); + pev->absmax = pev->origin + Vector( 8, 8, 8 ); +} +*/ + +// Give the disc back to it's owner +void CDisc::ReturnToThrower( void ) +{ + if (m_bDecapitate) + { + STOP_SOUND( edict(), CHAN_VOICE, "weapons/rocket1.wav" ); + if ( !m_bRemoveSelf ) + ((CBasePlayer*)(CBaseEntity*)m_hOwner)->GiveAmmo( MAX_DISCS, "disc", MAX_DISCS ); + } + else + { + if ( !m_bRemoveSelf ) + ((CBasePlayer*)(CBaseEntity*)m_hOwner)->GiveAmmo( 1, "disc", MAX_DISCS ); + } + + UTIL_Remove( this ); +} + +void CDisc::DiscTouch ( CBaseEntity *pOther ) +{ + // Push players backwards + if ( pOther->IsPlayer() ) + { + if ( ((CBaseEntity*)m_hOwner) == pOther ) + { + if (m_fDontTouchOwner < gpGlobals->time) + { + // Play catch sound + EMIT_SOUND_DYN( pOther->edict(), CHAN_WEAPON, "items/gunpickup2.wav", 1.0, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + + ReturnToThrower(); + } + + return; + } + else if ( m_fDontTouchEnemies < gpGlobals->time) + { + if ( pev->team != pOther->pev->team ) + { + ((CBasePlayer*)pOther)->m_LastHitGroup = HITGROUP_GENERIC; + + // Do freeze seperately so you can freeze and shatter a person with a single shot + if ( m_iPowerupFlags & POW_FREEZE && ((CBasePlayer*)pOther)->m_iFrozen == FALSE ) + { + // Freeze the player and make them glow blue + EMIT_SOUND_DYN( pOther->edict(), CHAN_WEAPON, "weapons/electro5.wav", 1.0, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + ((CBasePlayer*)pOther)->Freeze(); + + // If it's not a decap, return now. If it's a decap, continue to shatter + if ( !m_bDecapitate ) + { + m_fDontTouchEnemies = gpGlobals->time + 2.0; + return; + } + } + + // Decap or push + if (m_bDecapitate) + { + // Decapitate! + if ( m_bTeleported ) + ((CBasePlayer*)pOther)->m_flLastDiscHitTeleport = gpGlobals->time; + ((CBasePlayer*)pOther)->Decapitate( ((CBaseEntity*)m_hOwner)->pev ); + + m_fDontTouchEnemies = gpGlobals->time + 0.5; + } + else + { + // Play thwack sound + switch( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND_DYN( pOther->edict(), CHAN_ITEM, "weapons/cbar_hitbod1.wav", 1.0, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + break; + case 1: + EMIT_SOUND_DYN( pOther->edict(), CHAN_ITEM, "weapons/cbar_hitbod2.wav", 1.0, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + break; + case 2: + EMIT_SOUND_DYN( pOther->edict(), CHAN_ITEM, "weapons/cbar_hitbod3.wav", 1.0, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + break; + } + + // Push the player + Vector vecDir = pev->velocity.Normalize(); + pOther->pev->flags &= ~FL_ONGROUND; + ((CBasePlayer*)pOther)->m_vecHitVelocity = vecDir * DISC_PUSH_MULTIPLIER; + + // Shield flash only if the player isnt frozen + if ( ((CBasePlayer*)pOther)->m_iFrozen == false ) + { + pOther->pev->renderfx = kRenderFxGlowShell; + pOther->pev->rendercolor.x = 255; + pOther->pev->renderamt = 150; + } + + ((CBasePlayer*)pOther)->m_hLastPlayerToHitMe = m_hOwner; + ((CBasePlayer*)pOther)->m_flLastDiscHit = gpGlobals->time; + ((CBasePlayer*)pOther)->m_iLastDiscBounces = m_iBounces; + if ( m_bTeleported ) + ((CBasePlayer*)pOther)->m_flLastDiscHitTeleport = gpGlobals->time; + + m_fDontTouchEnemies = gpGlobals->time + 2.0; + } + } + } + } + // Hit a disc? + else if ( pOther->pev->iuser4 ) + { + // Enemy Discs destroy each other + if ( pOther->pev->iuser4 != pev->iuser4 ) + { + // Play a warp sound and sprite + CSprite *pSprite = CSprite::SpriteCreate( "sprites/discreturn.spr", pev->origin, TRUE ); + pSprite->AnimateAndDie( 60 ); + pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); + pSprite->SetScale( 1 ); + EMIT_SOUND_DYN( edict(), CHAN_ITEM, "dischit.wav", 1.0, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + + // Return both discs to their owners + ((CDisc*)pOther)->ReturnToThrower(); + ReturnToThrower(); + } + else + { + // Friendly discs just pass through each other + } + } + else + { + m_iBounces++; + + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: EMIT_SOUND_DYN( edict(), CHAN_ITEM, "weapons/xbow_hit1.wav", 1.0, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); break; + case 1: EMIT_SOUND_DYN( edict(), CHAN_ITEM, "weapons/xbow_hit2.wav", 1.0, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); break; + } + + UTIL_Sparks( pev->origin, edict() ); + } +} + +void CDisc::DiscThink() +{ + // Make Freeze discs home towards any player ahead of them + if ( (m_iPowerupFlags & POW_FREEZE) && (m_iBounces == 0) ) + { + // Use an existing target if he's still in the view cone + if ( m_pLockTarget != NULL ) + { + Vector vecDir = (m_pLockTarget->pev->origin - pev->origin).Normalize(); + UTIL_MakeVectors( pev->angles ); + float flDot = DotProduct( gpGlobals->v_forward, vecDir ); + if ( flDot < 0.6 ) + m_pLockTarget = NULL; + } + + // Get a new target if we don't have one + if ( m_pLockTarget == NULL ) + { + CBaseEntity *pOther = NULL; + + // Examine all entities within a reasonable radius + while ((pOther = UTIL_FindEntityByClassname( pOther, "player" )) != NULL) + { + // Skip the guy who threw this + if ( ((CBaseEntity*)m_hOwner) == pOther ) + continue; + // Skip observers + if ( ((CBasePlayer*)pOther)->IsObserver() ) + continue; + + // Make sure the enemy's in a cone ahead of us + Vector vecDir = (pOther->pev->origin - pev->origin).Normalize(); + UTIL_MakeVectors( pev->angles ); + float flDot = DotProduct( gpGlobals->v_forward, vecDir ); + if ( flDot > 0.6 ) + { + m_pLockTarget = pOther; + break; + } + } + } + + // Track towards our target + if ( m_pLockTarget != NULL ) + { + // Calculate new velocity + Vector vecDir = (m_pLockTarget->pev->origin - pev->origin).Normalize(); + pev->velocity = ( pev->velocity.Normalize() + (vecDir.Normalize() * 0.25)).Normalize(); + pev->velocity = pev->velocity * DISC_VELOCITY; + pev->angles = UTIL_VecToAngles( pev->velocity ); + } + } + + // Track the player if we've bounced 3 or more times ( Fast discs remove immediately ) + if ( m_iBounces >= 3 || (m_iPowerupFlags & POW_FAST && m_iBounces >= 1) ) + { + // Remove myself if my owner's died + if (m_bRemoveSelf) + { + STOP_SOUND( edict(), CHAN_VOICE, "weapons/rocket1.wav" ); + UTIL_Remove( this ); + return; + } + + // 7 Bounces, just remove myself + if ( m_iBounces > 7 ) + { + ReturnToThrower(); + return; + } + + // Start heading for the player + if ( m_hOwner ) + { + Vector vecDir = ( m_hOwner->pev->origin - pev->origin ); + vecDir = vecDir.Normalize(); + pev->velocity = vecDir * DISC_VELOCITY; + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + UTIL_Remove( this ); + } + } + + // Sanity check + if ( pev->velocity == g_vecZero ) + ReturnToThrower(); + + pev->nextthink = gpGlobals->time + 0.1; +} + +CDisc *CDisc::CreateDisc( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner, CDiscWeapon *pLauncher, bool bDecapitator, int iPowerupFlags ) +{ + CDisc *pDisc = GetClassPtr( (CDisc *)NULL ); + + UTIL_SetOrigin( pDisc->pev, vecOrigin ); + pDisc->m_iPowerupFlags = iPowerupFlags; + // Hard shots always decapitate + if ( pDisc->m_iPowerupFlags & POW_HARD ) + pDisc->m_bDecapitate = TRUE; + else + pDisc->m_bDecapitate = bDecapitator; + + pDisc->pev->angles = vecAngles; + pDisc->pev->owner = pOwner->edict(); + pDisc->pev->team = pOwner->pev->team; + pDisc->pev->iuser4 = pOwner->pev->iuser4; + + // Set the Group Info + pDisc->pev->groupinfo = pOwner->pev->groupinfo; + + pDisc->m_pLauncher = pLauncher; + + pDisc->Spawn(); + + return pDisc; +} +#endif // !CLIENT_DLL + +//======================================================================================== +// DISC WEAPON +//======================================================================================== +void CDiscWeapon::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_DISC; + SET_MODEL(ENT(pev), "models/disc.mdl"); + +#if !defined( CLIENT_DLL ) + pev->dmg = gSkillData.plrDmgHandGrenade; +#endif + + m_iDefaultAmmo = STARTING_DISCS; + m_iFastShotDiscs = NUM_FASTSHOT_DISCS; + + FallInit();// get ready to fall down. +} + + +void CDiscWeapon::Precache( void ) +{ + PRECACHE_MODEL("models/disc.mdl"); + PRECACHE_MODEL("models/disc_hard.mdl"); + PRECACHE_MODEL("models/v_disc.mdl"); + PRECACHE_MODEL("models/p_disc.mdl"); + PRECACHE_SOUND("weapons/cbar_miss1.wav"); + m_iSpriteTexture = PRECACHE_MODEL( "sprites/lgtning.spr" ); + + m_usFireDisc = PRECACHE_EVENT( 1, "events/firedisc.sc" ); +} + +int CDiscWeapon::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "disc"; + p->iMaxAmmo1 = MAX_DISCS; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 4; + p->iPosition = 0; + p->iId = WEAPON_DISC; + p->iWeight = 100; + p->iFlags = ITEM_FLAG_NOAUTORELOAD | ITEM_FLAG_NOAUTOSWITCHEMPTY; + + return 1; +} + + +BOOL CDiscWeapon::Deploy( ) +{ + return DefaultDeploy( "models/v_disc.mdl", "models/p_disc.mdl", DISC_DRAW, "crowbar" ); +} + +BOOL CDiscWeapon::CanHolster( void ) +{ + return TRUE; +} + +void CDiscWeapon::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + SendWeaponAnim( DISC_HOLSTER, 1 ); + } + else + { + // no more grenades! + m_pPlayer->pev->weapons &= ~(1<nextthink = gpGlobals->time + 0.1; + } + + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM); +} + +CDisc *CDiscWeapon::FireDisc( bool bDecapitator ) +{ + CDisc *pReturnDisc = NULL; + + SendWeaponAnim( DISC_THROW1, 1 ); + + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + +#if !defined( CLIENT_DLL ) + Vector vecFireDir = g_vecZero; + vecFireDir[1] = m_pPlayer->pev->v_angle[1]; + UTIL_MakeVectors( vecFireDir ); + Vector vecSrc = m_pPlayer->pev->origin + (m_pPlayer->pev->view_ofs * 0.25) + gpGlobals->v_forward * 16; + CDisc *pDisc = CDisc::CreateDisc( vecSrc, vecFireDir, m_pPlayer, this, bDecapitator, m_pPlayer->m_iPowerups ); + pReturnDisc = pDisc; + + // Triple shot fires 2 more disks + if ( m_pPlayer->HasPowerup( POW_TRIPLE ) ) + { + // The 2 extra discs from triple shot are removed after their 3rd bounce + vecFireDir[1] = m_pPlayer->pev->v_angle[1] - 7; + UTIL_MakeVectors( vecFireDir ); + vecSrc = m_pPlayer->pev->origin + (m_pPlayer->pev->view_ofs * 0.25) + gpGlobals->v_forward * 16; + pDisc = CDisc::CreateDisc( vecSrc, vecFireDir, m_pPlayer, this, bDecapitator, POW_TRIPLE ); + pDisc->m_bRemoveSelf = true; + + vecFireDir[1] = m_pPlayer->pev->v_angle[1] + 7; + UTIL_MakeVectors( vecFireDir ); + vecSrc = m_pPlayer->pev->origin + (m_pPlayer->pev->view_ofs * 0.25) + gpGlobals->v_forward * 16; + pDisc = CDisc::CreateDisc( vecSrc, vecFireDir, m_pPlayer, this, bDecapitator, POW_TRIPLE ); + pDisc->m_bRemoveSelf = true; + } + +#endif + + // Fast shot allows faster throwing + float flTimeToNextShot = 0.5; + if ( m_pPlayer->HasPowerup( POW_FAST ) ) + flTimeToNextShot = 0.2; + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + flTimeToNextShot; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + flTimeToNextShot; + + return pReturnDisc; +} + +void CDiscWeapon::PrimaryAttack() +{ +#if !defined( CLIENT_DLL ) + if ( m_pPlayer->m_pCurrentArena ) + { + if ( m_pPlayer->m_pCurrentArena->AllowedToFire() == false ) + return; + } +#endif + + if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] ) + { + PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_usFireDisc, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); + CDisc *pDisc = FireDisc( false ); + + // Fast powerup has a number of discs per 1 normal disc + if ( m_pPlayer->HasPowerup( POW_FAST ) ) + { + m_iFastShotDiscs--; + if ( m_iFastShotDiscs ) + { + // Make this disc remove itself + pDisc->m_bRemoveSelf = true; + return; + } + + m_iFastShotDiscs = NUM_FASTSHOT_DISCS; + } + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + // If we have powered discs, remove one + if ( m_pPlayer->m_iPowerupDiscs ) + { + m_pPlayer->m_iPowerupDiscs--; + if ( !m_pPlayer->m_iPowerupDiscs ) + m_pPlayer->RemoveAllPowerups(); + } + } +} + +void CDiscWeapon::SecondaryAttack() +{ +#if !defined( CLIENT_DLL ) + if ( m_pPlayer->m_pCurrentArena ) + { + if ( m_pPlayer->m_pCurrentArena->AllowedToFire() == false ) + return; + } +#endif + + // Fast powerup has a number of discs per 1 normal disc (so it can throw a decap when it has at least 1 real disc) + if ( (m_pPlayer->HasPowerup( POW_FAST ) && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] > 0 ) || + ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] == MAX_DISCS ) ) + { + PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_usFireDisc, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 1, 0 ); + + FireDisc( true ); + + // Deduct MAX_DISCS from fast shot, or deduct all discs if we don't have fast shot + if ( m_pPlayer->HasPowerup( POW_FAST ) ) + { + for ( int i = 1; i <= MAX_DISCS; i++ ) + { + m_iFastShotDiscs--; + if ( m_iFastShotDiscs == 0 ) + { + m_iFastShotDiscs = NUM_FASTSHOT_DISCS; + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + // Remove a powered disc + m_pPlayer->m_iPowerupDiscs--; + if ( !m_pPlayer->m_iPowerupDiscs ) + m_pPlayer->RemoveAllPowerups(); + } + } + } + else + { + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] = 0; + + // If we have powered discs, remove one + if ( m_pPlayer->m_iPowerupDiscs ) + { + m_pPlayer->m_iPowerupDiscs--; + if ( !m_pPlayer->m_iPowerupDiscs ) + m_pPlayer->RemoveAllPowerups(); + } + } + } +} + + +void CDiscWeapon::WeaponIdle( void ) +{ +#if !defined( CLIENT_DLL ) + if ( m_pPlayer->HasPowerup(POW_VISUALIZE_REBOUNDS) ) + { + Vector vecFireDir = g_vecZero; + Vector vecSrc = m_pPlayer->pev->origin + (m_pPlayer->pev->view_ofs * 0.25); + vecFireDir[1] = m_pPlayer->pev->v_angle[1]; + + // Draw beams to show where rebounds will go + for (int i = 0; i < 3; i++) + { + TraceResult tr; + UTIL_MakeVectors( vecFireDir ); + UTIL_TraceLine( vecSrc, (vecSrc + gpGlobals->v_forward * 2048), ignore_monsters, ENT(pev), &tr ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS ); + WRITE_COORD( vecSrc.x); + WRITE_COORD( vecSrc.y); + WRITE_COORD( vecSrc.z); + WRITE_COORD( tr.vecEndPos.x); + WRITE_COORD( tr.vecEndPos.y); + WRITE_COORD( tr.vecEndPos.z); + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 1 ); // life + WRITE_BYTE( 40 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( i * 50 ); // r, g, b + WRITE_BYTE( i * 50 ); // r, g, b + WRITE_BYTE( 200 - (i * 50)); // r, g, b + WRITE_BYTE( 128 - (i * 30) ); // r, g, b + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + // Calculate rebound angle + Vector vecOut; + Vector vecIn = tr.vecEndPos - (tr.vecEndPos - (gpGlobals->v_forward * 5)); + float backoff = DotProduct( vecIn, tr.vecPlaneNormal ) * 2.0; + for (int i=0 ; i<3 ; i++) + { + float change = tr.vecPlaneNormal[i] * backoff; + vecOut[i] = vecIn[i] - change; + if (vecOut[i] > -0.1 && vecOut[i] < 0.1) + vecOut[i] = 0; + } + + vecOut = vecOut.Normalize(); + vecSrc = tr.vecEndPos; + vecFireDir = UTIL_VecToAngles(vecOut); + } + } +#endif + + if (m_flTimeWeaponIdle > UTIL_WeaponTimeBase()) + return; + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + int iAnim; + float flRand = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0, 1 ); + if (flRand <= 0.75) + { + iAnim = DISC_IDLE; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 );// how long till we do this again. + } + else + { + iAnim = DISC_FIDGET; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 75.0 / 30.0; + } + + SendWeaponAnim( iAnim, 1 ); + } +} + +// Prevent disc weapons lying around on the ground +int CDiscWeapon::AddDuplicate( CBasePlayerItem *pOriginal ) +{ + pev->flags |= FL_KILLME; + return FALSE; +} + + + diff --git a/ricochet/dlls/xen.cpp b/ricochet/dlls/xen.cpp new file mode 100644 index 0000000..ebd9b44 --- /dev/null +++ b/ricochet/dlls/xen.cpp @@ -0,0 +1,530 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "animation.h" +#include "effects.h" + + +#define XEN_PLANT_GLOW_SPRITE "sprites/flare3.spr" +#define XEN_PLANT_HIDE_TIME 5 + + +class CActAnimating : public CBaseAnimating +{ +public: + void SetActivity( Activity act ); + inline Activity GetActivity( void ) { return m_Activity; } + + virtual int ObjectCaps( void ) { return CBaseAnimating :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + Activity m_Activity; +}; + +TYPEDESCRIPTION CActAnimating::m_SaveData[] = +{ + DEFINE_FIELD( CActAnimating, m_Activity, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CActAnimating, CBaseAnimating ); + +void CActAnimating :: SetActivity( Activity act ) +{ + int sequence = LookupActivity( act ); + if ( sequence != ACTIVITY_NOT_AVAILABLE ) + { + pev->sequence = sequence; + m_Activity = act; + pev->frame = 0; + ResetSequenceInfo( ); + } +} + + + + +class CXenPLight : public CActAnimating +{ +public: + void Spawn( void ); + void Precache( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); + + void LightOn( void ); + void LightOff( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + CSprite *m_pGlow; +}; + +LINK_ENTITY_TO_CLASS( xen_plantlight, CXenPLight ); + +TYPEDESCRIPTION CXenPLight::m_SaveData[] = +{ + DEFINE_FIELD( CXenPLight, m_pGlow, FIELD_CLASSPTR ), +}; + +IMPLEMENT_SAVERESTORE( CXenPLight, CActAnimating ); + +void CXenPLight :: Spawn( void ) +{ + Precache(); + + SET_MODEL( ENT(pev), "models/light.mdl" ); + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_TRIGGER; + + UTIL_SetSize( pev, Vector(-80,-80,0), Vector(80,80,32)); + SetActivity( ACT_IDLE ); + pev->nextthink = gpGlobals->time + 0.1; + pev->frame = RANDOM_FLOAT(0,255); + + m_pGlow = CSprite::SpriteCreate( XEN_PLANT_GLOW_SPRITE, pev->origin + Vector(0,0,(pev->mins.z+pev->maxs.z)*0.5), FALSE ); + m_pGlow->SetTransparency( kRenderGlow, pev->rendercolor.x, pev->rendercolor.y, pev->rendercolor.z, pev->renderamt, pev->renderfx ); + m_pGlow->SetAttachment( edict(), 1 ); +} + + +void CXenPLight :: Precache( void ) +{ + PRECACHE_MODEL( "models/light.mdl" ); + PRECACHE_MODEL( XEN_PLANT_GLOW_SPRITE ); +} + + +void CXenPLight :: Think( void ) +{ + StudioFrameAdvance(); + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CXenPLight :: Touch( CBaseEntity *pOther ) +{ + if ( pOther->IsPlayer() ) + { + pev->dmgtime = gpGlobals->time + XEN_PLANT_HIDE_TIME; + } +} + + +void CXenPLight :: LightOn( void ) +{ + SUB_UseTargets( this, USE_ON, 0 ); + if ( m_pGlow ) + m_pGlow->pev->effects &= ~EF_NODRAW; +} + + +void CXenPLight :: LightOff( void ) +{ + SUB_UseTargets( this, USE_OFF, 0 ); + if ( m_pGlow ) + m_pGlow->pev->effects |= EF_NODRAW; +} + + + +class CXenHair : public CActAnimating +{ +public: + void Spawn( void ); + void Precache( void ); + void Think( void ); +}; + +LINK_ENTITY_TO_CLASS( xen_hair, CXenHair ); + +#define SF_HAIR_SYNC 0x0001 + +void CXenHair::Spawn( void ) +{ + Precache(); + SET_MODEL( edict(), "models/hair.mdl" ); + UTIL_SetSize( pev, Vector(-4,-4,0), Vector(4,4,32)); + pev->sequence = 0; + + if ( !(pev->spawnflags & SF_HAIR_SYNC) ) + { + pev->frame = RANDOM_FLOAT(0,255); + pev->framerate = RANDOM_FLOAT( 0.7, 1.4 ); + } + ResetSequenceInfo( ); + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->nextthink = gpGlobals->time + RANDOM_FLOAT( 0.1, 0.4 ); // Load balance these a bit +} + + +void CXenHair::Think( void ) +{ + StudioFrameAdvance(); + pev->nextthink = gpGlobals->time + 0.5; +} + + +void CXenHair::Precache( void ) +{ + PRECACHE_MODEL( "models/hair.mdl" ); +} + + +class CXenTreeTrigger : public CBaseEntity +{ +public: + void Touch( CBaseEntity *pOther ); + static CXenTreeTrigger *TriggerCreate( edict_t *pOwner, const Vector &position ); +}; +LINK_ENTITY_TO_CLASS( xen_ttrigger, CXenTreeTrigger ); + +CXenTreeTrigger *CXenTreeTrigger :: TriggerCreate( edict_t *pOwner, const Vector &position ) +{ + CXenTreeTrigger *pTrigger = GetClassPtr( (CXenTreeTrigger *)NULL ); + pTrigger->pev->origin = position; + pTrigger->pev->classname = MAKE_STRING("xen_ttrigger"); + pTrigger->pev->solid = SOLID_TRIGGER; + pTrigger->pev->movetype = MOVETYPE_NONE; + pTrigger->pev->owner = pOwner; + + return pTrigger; +} + + +void CXenTreeTrigger::Touch( CBaseEntity *pOther ) +{ + if ( pev->owner ) + { + CBaseEntity *pEntity = CBaseEntity::Instance(pev->owner); + pEntity->Touch( pOther ); + } +} + + +#define TREE_AE_ATTACK 1 + +class CXenTree : public CActAnimating +{ +public: + void Spawn( void ); + void Precache( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { Attack(); return 0; } + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void Attack( void ); + int Classify( void ) { return CLASS_BARNACLE; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + static const char *pAttackHitSounds[]; + static const char *pAttackMissSounds[]; + +private: + CXenTreeTrigger *m_pTrigger; +}; + +LINK_ENTITY_TO_CLASS( xen_tree, CXenTree ); + +TYPEDESCRIPTION CXenTree::m_SaveData[] = +{ + DEFINE_FIELD( CXenTree, m_pTrigger, FIELD_CLASSPTR ), +}; + +IMPLEMENT_SAVERESTORE( CXenTree, CActAnimating ); + +void CXenTree :: Spawn( void ) +{ + Precache(); + + SET_MODEL( ENT(pev), "models/tree.mdl" ); + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_BBOX; + + pev->takedamage = DAMAGE_YES; + + UTIL_SetSize( pev, Vector(-30,-30,0), Vector(30,30,188)); + SetActivity( ACT_IDLE ); + pev->nextthink = gpGlobals->time + 0.1; + pev->frame = RANDOM_FLOAT(0,255); + pev->framerate = RANDOM_FLOAT( 0.7, 1.4 ); + + Vector triggerPosition; + UTIL_MakeVectorsPrivate( pev->angles, triggerPosition, NULL, NULL ); + triggerPosition = pev->origin + (triggerPosition * 64); + // Create the trigger + m_pTrigger = CXenTreeTrigger::TriggerCreate( edict(), triggerPosition ); + UTIL_SetSize( m_pTrigger->pev, Vector( -24, -24, 0 ), Vector( 24, 24, 128 ) ); +} + +const char *CXenTree::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CXenTree::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +void CXenTree :: Precache( void ) +{ + PRECACHE_MODEL( "models/tree.mdl" ); + PRECACHE_MODEL( XEN_PLANT_GLOW_SPRITE ); + PRECACHE_SOUND_ARRAY( pAttackHitSounds ); + PRECACHE_SOUND_ARRAY( pAttackMissSounds ); +} + + +void CXenTree :: Touch( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() && FClassnameIs( pOther->pev, "monster_bigmomma" ) ) + return; + + Attack(); +} + + +void CXenTree :: Attack( void ) +{ +} + + +void CXenTree :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case TREE_AE_ATTACK: + { + CBaseEntity *pList[8]; + BOOL sound = FALSE; + int count = UTIL_EntitiesInBox( pList, 8, m_pTrigger->pev->absmin, m_pTrigger->pev->absmax, FL_MONSTER|FL_CLIENT ); + Vector forward; + + UTIL_MakeVectorsPrivate( pev->angles, forward, NULL, NULL ); + + for ( int i = 0; i < count; i++ ) + { + if ( pList[i] != this ) + { + if ( pList[i]->pev->owner != edict() ) + { + sound = TRUE; + pList[i]->TakeDamage( pev, pev, 25, DMG_CRUSH | DMG_SLASH ); + pList[i]->pev->punchangle.x = 15; + pList[i]->pev->velocity = pList[i]->pev->velocity + forward * 100; + } + } + } + + if ( sound ) + { + EMIT_SOUND_ARRAY_DYN( CHAN_WEAPON, pAttackHitSounds ); + } + } + return; + } + + CActAnimating::HandleAnimEvent( pEvent ); +} + +void CXenTree :: Think( void ) +{ + float flInterval = StudioFrameAdvance(); + pev->nextthink = gpGlobals->time + 0.1; + DispatchAnimEvents( flInterval ); +} + + +// UNDONE: These need to smoke somehow when they take damage +// Touch behavior? +// Cause damage in smoke area + +// +// Spores +// +class CXenSpore : public CActAnimating +{ +public: + void Spawn( void ); + void Precache( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { Attack(); return 0; } +// void HandleAnimEvent( MonsterEvent_t *pEvent ); + void Attack( void ) {} + + static const char *pModelNames[]; +}; + +class CXenSporeSmall : public CXenSpore +{ + void Spawn( void ); +}; + +class CXenSporeMed : public CXenSpore +{ + void Spawn( void ); +}; + +class CXenSporeLarge : public CXenSpore +{ + void Spawn( void ); + + static const Vector m_hullSizes[]; +}; + +// Fake collision box for big spores +class CXenHull : public CPointEntity +{ +public: + static CXenHull *CreateHull( CBaseEntity *source, const Vector &mins, const Vector &maxs, const Vector &offset ); + int Classify( void ) { return CLASS_BARNACLE; } +}; + +CXenHull *CXenHull :: CreateHull( CBaseEntity *source, const Vector &mins, const Vector &maxs, const Vector &offset ) +{ + CXenHull *pHull = GetClassPtr( (CXenHull *)NULL ); + + UTIL_SetOrigin( pHull->pev, source->pev->origin + offset ); + SET_MODEL( pHull->edict(), STRING(source->pev->model) ); + pHull->pev->solid = SOLID_BBOX; + pHull->pev->classname = MAKE_STRING("xen_hull"); + pHull->pev->movetype = MOVETYPE_NONE; + pHull->pev->owner = source->edict(); + UTIL_SetSize( pHull->pev, mins, maxs ); + pHull->pev->renderamt = 0; + pHull->pev->rendermode = kRenderTransTexture; + // pHull->pev->effects = EF_NODRAW; + + return pHull; +} + + +LINK_ENTITY_TO_CLASS( xen_spore_small, CXenSporeSmall ); +LINK_ENTITY_TO_CLASS( xen_spore_medium, CXenSporeMed ); +LINK_ENTITY_TO_CLASS( xen_spore_large, CXenSporeLarge ); +LINK_ENTITY_TO_CLASS( xen_hull, CXenHull ); + +void CXenSporeSmall::Spawn( void ) +{ + pev->skin = 0; + CXenSpore::Spawn(); + UTIL_SetSize( pev, Vector(-16,-16,0), Vector(16,16,64)); +} +void CXenSporeMed::Spawn( void ) +{ + pev->skin = 1; + CXenSpore::Spawn(); + UTIL_SetSize( pev, Vector(-40,-40,0), Vector(40,40,120)); +} + + +// I just eyeballed these -- fill in hulls for the legs +const Vector CXenSporeLarge::m_hullSizes[] = +{ + Vector( 90, -25, 0 ), + Vector( 25, 75, 0 ), + Vector( -15, -100, 0 ), + Vector( -90, -35, 0 ), + Vector( -90, 60, 0 ), +}; + +void CXenSporeLarge::Spawn( void ) +{ + pev->skin = 2; + CXenSpore::Spawn(); + UTIL_SetSize( pev, Vector(-48,-48,110), Vector(48,48,240)); + + Vector forward, right; + + UTIL_MakeVectorsPrivate( pev->angles, forward, right, NULL ); + + // Rotate the leg hulls into position + for ( int i = 0; i < ARRAYSIZE(m_hullSizes); i++ ) + CXenHull :: CreateHull( this, Vector(-12, -12, 0 ), Vector( 12, 12, 120 ), (m_hullSizes[i].x * forward) + (m_hullSizes[i].y * right) ); +} + +void CXenSpore :: Spawn( void ) +{ + Precache(); + + SET_MODEL( ENT(pev), pModelNames[pev->skin] ); + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_BBOX; + pev->takedamage = DAMAGE_YES; + +// SetActivity( ACT_IDLE ); + pev->sequence = 0; + pev->frame = RANDOM_FLOAT(0,255); + pev->framerate = RANDOM_FLOAT( 0.7, 1.4 ); + ResetSequenceInfo( ); + pev->nextthink = gpGlobals->time + RANDOM_FLOAT( 0.1, 0.4 ); // Load balance these a bit +} + +const char *CXenSpore::pModelNames[] = +{ + "models/fungus(small).mdl", + "models/fungus.mdl", + "models/fungus(large).mdl", +}; + + +void CXenSpore :: Precache( void ) +{ + PRECACHE_MODEL( (char *)pModelNames[pev->skin] ); +} + + +void CXenSpore :: Touch( CBaseEntity *pOther ) +{ +} + + +void CXenSpore :: Think( void ) +{ + float flInterval = StudioFrameAdvance(); + pev->nextthink = gpGlobals->time + 0.1; + +#if 0 + DispatchAnimEvents( flInterval ); + + switch( GetActivity() ) + { + default: + case ACT_IDLE: + break; + + } +#endif +} + + diff --git a/ricochet/pm_shared/pm_debug.c b/ricochet/pm_shared/pm_debug.c new file mode 100644 index 0000000..2ccb8a5 --- /dev/null +++ b/ricochet/pm_shared/pm_debug.c @@ -0,0 +1,305 @@ +#include "mathlib.h" +#include "const.h" +#include "usercmd.h" +#include "pm_defs.h" +#include "pm_shared.h" +#include "pm_movevars.h" +#include "pm_debug.h" + +#include + +#pragma warning(disable : 4244) +#pragma warning(disable : 4305) + +extern playermove_t *pmove; + +// Expand debugging BBOX particle hulls by this many units. +#define BOX_GAP 0.0f + +static int PM_boxpnt[6][4] = +{ + { 0, 4, 6, 2 }, // +X + { 0, 1, 5, 4 }, // +Y + { 0, 2, 3, 1 }, // +Z + { 7, 5, 1, 3 }, // -X + { 7, 3, 2, 6 }, // -Y + { 7, 6, 4, 5 }, // -Z +}; + +void PM_ShowClipBox( void ) +{ +#if defined( _DEBUG ) + vec3_t org; + vec3_t offset = { 0, 0, 0 }; + + if ( !pmove->runfuncs ) + return; + + // More debugging, draw the particle bbox for player and for the entity we are looking directly at. + // aslo prints entity info to the console overlay. + //if ( !pmove->server ) + // return; + + // Draw entity in center of view + // Also draws the normal to the clip plane that intersects our movement ray. Leaves a particle + // trail at the intersection point. + PM_ViewEntity(); + + VectorCopy( pmove->origin, org ); + + if ( pmove->server ) + { + VectorAdd( org, offset, org ); + } + else + { + VectorSubtract( org, offset, org ); + } + + // Show our BBOX in particles. + PM_DrawBBox( pmove->player_mins[pmove->usehull], pmove->player_maxs[pmove->usehull], org, pmove->server ? 132 : 0, 0.1 ); + + PM_ParticleLine( org, org, pmove->server ? 132 : 0, 0.1, 5.0 ); +/* + { + int i; + for ( i = 0; i < pmove->numphysent; i++ ) + { + if ( pmove->physents[ i ].info >= 1 && pmove->physents[ i ].info <= 4 ) + { + PM_DrawBBox( pmove->player_mins[pmove->usehull], pmove->player_maxs[pmove->usehull], pmove->physents[i].origin, 132, 0.1 ); + } + } + } +*/ +#endif +} + +/* +=============== +PM_ParticleLine(vec3_t start, vec3_t end, int color, float life) + +================ +*/ +void PM_ParticleLine(vec3_t start, vec3_t end, int pcolor, float life, float vert) +{ + float linestep = 2.0f; + float curdist; + float len; + vec3_t curpos; + vec3_t diff; + int i; + // Determine distance; + + VectorSubtract(end, start, diff); + + len = VectorNormalize(diff); + + curdist = 0; + while (curdist <= len) + { + for (i = 0; i < 3; i++) + curpos[i] = start[i] + curdist * diff[i]; + + pmove->PM_Particle( curpos, pcolor, life, 0, vert); + curdist += linestep; + } + +} + +/* +================ +PM_DrawRectangle(vec3_t tl, vec3_t br) + +================ +*/ +void PM_DrawRectangle(vec3_t tl, vec3_t bl, vec3_t tr, vec3_t br, int pcolor, float life) +{ + PM_ParticleLine(tl, bl, pcolor, life, 0); + PM_ParticleLine(bl, br, pcolor, life, 0); + PM_ParticleLine(br, tr, pcolor, life, 0); + PM_ParticleLine(tr, tl, pcolor, life, 0); +} + +/* +================ +PM_DrawPhysEntBBox(int num) + +================ +*/ +void PM_DrawPhysEntBBox(int num, int pcolor, float life) +{ + physent_t *pe; + vec3_t org; + int j; + vec3_t tmp; + vec3_t p[8]; + float gap = BOX_GAP; + vec3_t modelmins, modelmaxs; + + if (num >= pmove->numphysent || + num <= 0) + return; + + pe = &pmove->physents[num]; + + if (pe->model) + { + VectorCopy(pe->origin, org); + + pmove->PM_GetModelBounds( pe->model, modelmins, modelmaxs ); + for (j = 0; j < 8; j++) + { + tmp[0] = (j & 1) ? modelmins[0] - gap : modelmaxs[0] + gap; + tmp[1] = (j & 2) ? modelmins[1] - gap : modelmaxs[1] + gap; + tmp[2] = (j & 4) ? modelmins[2] - gap : modelmaxs[2] + gap; + + VectorCopy(tmp, p[j]); + } + + // If the bbox should be rotated, do that + if (pe->angles[0] || pe->angles[1] || pe->angles[2]) + { + vec3_t forward, right, up; + + AngleVectorsTranspose(pe->angles, forward, right, up); + for (j = 0; j < 8; j++) + { + VectorCopy(p[j], tmp); + p[j][0] = DotProduct ( tmp, forward ); + p[j][1] = DotProduct ( tmp, right ); + p[j][2] = DotProduct ( tmp, up ); + } + } + + // Offset by entity origin, if any. + for (j = 0; j < 8; j++) + VectorAdd(p[j], org, p[j]); + + for (j = 0; j < 6; j++) + { + PM_DrawRectangle( + p[PM_boxpnt[j][1]], + p[PM_boxpnt[j][0]], + p[PM_boxpnt[j][2]], + p[PM_boxpnt[j][3]], + pcolor, life); + } + } + else + { + for (j = 0; j < 8; j++) + { + tmp[0] = (j & 1) ? pe->mins[0] : pe->maxs[0]; + tmp[1] = (j & 2) ? pe->mins[1] : pe->maxs[1]; + tmp[2] = (j & 4) ? pe->mins[2] : pe->maxs[2]; + + VectorAdd(tmp, pe->origin, tmp); + VectorCopy(tmp, p[j]); + } + + for (j = 0; j < 6; j++) + { + PM_DrawRectangle( + p[PM_boxpnt[j][1]], + p[PM_boxpnt[j][0]], + p[PM_boxpnt[j][2]], + p[PM_boxpnt[j][3]], + pcolor, life); + } + + } +} + +/* +================ +PM_DrawBBox(vec3_t mins, vec3_t maxs, vec3_t origin, int pcolor, float life) + +================ +*/ +void PM_DrawBBox(vec3_t mins, vec3_t maxs, vec3_t origin, int pcolor, float life) +{ + int j; + + vec3_t tmp; + vec3_t p[8]; + float gap = BOX_GAP; + + for (j = 0; j < 8; j++) + { + tmp[0] = (j & 1) ? mins[0] - gap : maxs[0] + gap; + tmp[1] = (j & 2) ? mins[1] - gap : maxs[1] + gap ; + tmp[2] = (j & 4) ? mins[2] - gap : maxs[2] + gap ; + + VectorAdd(tmp, origin, tmp); + VectorCopy(tmp, p[j]); + } + + for (j = 0; j < 6; j++) + { + PM_DrawRectangle( + p[PM_boxpnt[j][1]], + p[PM_boxpnt[j][0]], + p[PM_boxpnt[j][2]], + p[PM_boxpnt[j][3]], + pcolor, life); + } +} + + +#ifndef DEDICATED + +/* +================ +PM_ViewEntity + +Shows a particle trail from player to entity in crosshair. +Shows particles at that entities bbox + +Tries to shoot a ray out by about 128 units. +================ +*/ +void PM_ViewEntity( void ) +{ + vec3_t forward, right, up; + float raydist = 256.0f; + vec3_t origin; + vec3_t end; + int i; + pmtrace_t trace; + int pcolor = 77; + float fup; + +#if 0 + if ( !pm_showclip.value ) + return; +#endif + + AngleVectors (pmove->angles, forward, right, up); // Determine movement angles + + VectorCopy( pmove->origin, origin); + + fup = 0.5*( pmove->player_mins[pmove->usehull][2] + pmove->player_maxs[pmove->usehull][2] ); + fup += pmove->view_ofs[2]; + fup -= 4; + + for (i = 0; i < 3; i++) + { + end[i] = origin[i] + raydist * forward[i]; + } + + trace = pmove->PM_PlayerTrace( origin, end, PM_STUDIO_BOX, -1 ); + + if (trace.ent > 0) // Not the world + { + pcolor = 111; + } + + // Draw the hull or bbox. + if (trace.ent > 0) + { + PM_DrawPhysEntBBox(trace.ent, pcolor, 0.3f); + } +} + +#endif \ No newline at end of file diff --git a/ricochet/pm_shared/pm_debug.h b/ricochet/pm_shared/pm_debug.h new file mode 100644 index 0000000..18db48b --- /dev/null +++ b/ricochet/pm_shared/pm_debug.h @@ -0,0 +1,10 @@ +#ifndef PM_DEBUG_H +#define PM_DEBUG_H +#pragma once + +void PM_ViewEntity( void ); +void PM_DrawBBox(vec3_t mins, vec3_t maxs, vec3_t origin, int pcolor, float life); +void PM_ParticleLine(vec3_t start, vec3_t end, int pcolor, float life, float vert); +void PM_ShowClipBox( void ); + +#endif // PMOVEDBG_H \ No newline at end of file diff --git a/ricochet/pm_shared/pm_defs.h b/ricochet/pm_shared/pm_defs.h new file mode 100644 index 0000000..d445ad1 --- /dev/null +++ b/ricochet/pm_shared/pm_defs.h @@ -0,0 +1,208 @@ +// pm_defs.h +#if !defined( PM_DEFSH ) +#define PM_DEFSH +#pragma once + +#define MAX_PHYSENTS 600 // Must have room for all entities in the world. +#define MAX_MOVEENTS 64 +#define MAX_CLIP_PLANES 5 + +#define PM_NORMAL 0x00000000 +#define PM_STUDIO_IGNORE 0x00000001 // Skip studio models +#define PM_STUDIO_BOX 0x00000002 // Use boxes for non-complex studio models (even in traceline) +#define PM_GLASS_IGNORE 0x00000004 // Ignore entities with non-normal rendermode +#define PM_WORLD_ONLY 0x00000008 // Only trace against the world + +// Values for flags parameter of PM_TraceLine +#define PM_TRACELINE_ANYVISIBLE 0 +#define PM_TRACELINE_PHYSENTSONLY 1 + +#include "archtypes.h" // DAL +#include "pm_info.h" + +// PM_PlayerTrace results. +#include "pmtrace.h" + +#if !defined ( USERCMD_H ) +#include "usercmd.h" +#endif + +// physent_t +typedef struct physent_s +{ + char name[32]; // Name of model, or "player" or "world". + int player; + vec3_t origin; // Model's origin in world coordinates. + struct model_s *model; // only for bsp models + struct model_s *studiomodel; // SOLID_BBOX, but studio clip intersections. + vec3_t mins, maxs; // only for non-bsp models + int info; // For client or server to use to identify (index into edicts or cl_entities) + vec3_t angles; // rotated entities need this info for hull testing to work. + + int solid; // Triggers and func_door type WATER brushes are SOLID_NOT + int skin; // BSP Contents for such things like fun_door water brushes. + int rendermode; // So we can ignore glass + + // Complex collision detection. + float frame; + int sequence; + byte controller[4]; + byte blending[2]; + + int movetype; + int takedamage; + int blooddecal; + int team; + int classnumber; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; +} physent_t; + + +typedef struct playermove_s +{ + int player_index; // So we don't try to run the PM_CheckStuck nudging too quickly. + qboolean server; // For debugging, are we running physics code on server side? + + qboolean multiplayer; // 1 == multiplayer server + float time; // realtime on host, for reckoning duck timing + float frametime; // Duration of this frame + + vec3_t forward, right, up; // Vectors for angles + // player state + vec3_t origin; // Movement origin. + vec3_t angles; // Movement view angles. + vec3_t oldangles; // Angles before movement view angles were looked at. + vec3_t velocity; // Current movement direction. + vec3_t movedir; // For waterjumping, a forced forward velocity so we can fly over lip of ledge. + vec3_t basevelocity; // Velocity of the conveyor we are standing, e.g. + + // For ducking/dead + vec3_t view_ofs; // Our eye position. + float flDuckTime; // Time we started duck + qboolean bInDuck; // In process of ducking or ducked already? + + // For walking/falling + int flTimeStepSound; // Next time we can play a step sound + int iStepLeft; + + float flFallVelocity; + vec3_t punchangle; + + float flSwimTime; + + float flNextPrimaryAttack; + + int effects; // MUZZLE FLASH, e.g. + + int flags; // FL_ONGROUND, FL_DUCKING, etc. + int usehull; // 0 = regular player hull, 1 = ducked player hull, 2 = point hull + float gravity; // Our current gravity and friction. + float friction; + int oldbuttons; // Buttons last usercmd + float waterjumptime; // Amount of time left in jumping out of water cycle. + qboolean dead; // Are we a dead player? + int deadflag; + int spectator; // Should we use spectator physics model? + int movetype; // Our movement type, NOCLIP, WALK, FLY + + int onground; + int waterlevel; + int watertype; + int oldwaterlevel; + + char sztexturename[256]; + char chtexturetype; + + float maxspeed; + float clientmaxspeed; // Player specific maxspeed + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; + // world state + // Number of entities to clip against. + int numphysent; + physent_t physents[MAX_PHYSENTS]; + // Number of momvement entities (ladders) + int nummoveent; + // just a list of ladders + physent_t moveents[MAX_MOVEENTS]; + + // All things being rendered, for tracing against things you don't actually collide with + int numvisent; + physent_t visents[ MAX_PHYSENTS ]; + + // input to run through physics. + usercmd_t cmd; + + // Trace results for objects we collided with. + int numtouch; + pmtrace_t touchindex[MAX_PHYSENTS]; + + char physinfo[ MAX_PHYSINFO_STRING ]; // Physics info string + + struct movevars_s *movevars; + vec3_t player_mins[ 4 ]; + vec3_t player_maxs[ 4 ]; + + // Common functions + const char *(*PM_Info_ValueForKey) ( const char *s, const char *key ); + void (*PM_Particle)( float *origin, int color, float life, int zpos, int zvel); + int (*PM_TestPlayerPosition) (float *pos, pmtrace_t *ptrace ); + void (*Con_NPrintf)( int idx, char *fmt, ... ); + void (*Con_DPrintf)( char *fmt, ... ); + void (*Con_Printf)( char *fmt, ... ); + double (*Sys_FloatTime)( void ); + void (*PM_StuckTouch)( int hitent, pmtrace_t *ptraceresult ); + int (*PM_PointContents) (float *p, int *truecontents /*filled in if this is non-null*/ ); + int (*PM_TruePointContents) (float *p); + int (*PM_HullPointContents) ( struct hull_s *hull, int num, float *p); + pmtrace_t (*PM_PlayerTrace) (float *start, float *end, int traceFlags, int ignore_pe ); + struct pmtrace_s *(*PM_TraceLine)( float *start, float *end, int flags, int usehulll, int ignore_pe ); + int32 (*RandomLong)( int32 lLow, int32 lHigh ); + float (*RandomFloat)( float flLow, float flHigh ); + int (*PM_GetModelType)( struct model_s *mod ); + void (*PM_GetModelBounds)( struct model_s *mod, float *mins, float *maxs ); + void *(*PM_HullForBsp)( physent_t *pe, float *offset ); + float (*PM_TraceModel)( physent_t *pEnt, float *start, float *end, trace_t *trace ); + int (*COM_FileSize)(char *filename); + byte *(*COM_LoadFile) (char *path, int usehunk, int *pLength); + void (*COM_FreeFile) ( void *buffer ); + char *(*memfgets)( byte *pMemFile, int fileSize, int *pFilePos, char *pBuffer, int bufferSize ); + + // Functions + // Run functions for this frame? + qboolean runfuncs; + void (*PM_PlaySound) ( int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch ); + const char *(*PM_TraceTexture) ( int ground, float *vstart, float *vend ); + void (*PM_PlaybackEventFull) ( int flags, int clientindex, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); + + pmtrace_t (*PM_PlayerTraceEx) (float *start, float *end, int traceFlags, int (*pfnIgnore)( physent_t *pe ) ); + int (*PM_TestPlayerPositionEx) (float *pos, pmtrace_t *ptrace, int (*pfnIgnore)( physent_t *pe ) ); + struct pmtrace_s *(*PM_TraceLineEx)( float *start, float *end, int flags, int usehulll, int (*pfnIgnore)( physent_t *pe ) ); +} playermove_t; + +#endif diff --git a/ricochet/pm_shared/pm_info.h b/ricochet/pm_shared/pm_info.h new file mode 100644 index 0000000..bb08d4e --- /dev/null +++ b/ricochet/pm_shared/pm_info.h @@ -0,0 +1,10 @@ +// Physics info string definition +#if !defined( PM_INFOH ) +#define PM_INFOH +#ifdef _WIN32 +#pragma once +#endif + +#define MAX_PHYSINFO_STRING 256 + +#endif // PM_INFOH diff --git a/ricochet/pm_shared/pm_materials.h b/ricochet/pm_shared/pm_materials.h new file mode 100644 index 0000000..67deed6 --- /dev/null +++ b/ricochet/pm_shared/pm_materials.h @@ -0,0 +1,19 @@ +#if !defined( PM_MATERIALSH ) +#define PM_MATERIALSH +#pragma once + +#define CBTEXTURENAMEMAX 13 // only load first n chars of name + +#define CHAR_TEX_CONCRETE 'C' // texture types +#define CHAR_TEX_METAL 'M' +#define CHAR_TEX_DIRT 'D' +#define CHAR_TEX_VENT 'V' +#define CHAR_TEX_GRATE 'G' +#define CHAR_TEX_TILE 'T' +#define CHAR_TEX_SLOSH 'S' +#define CHAR_TEX_WOOD 'W' +#define CHAR_TEX_COMPUTER 'P' +#define CHAR_TEX_GLASS 'Y' +#define CHAR_TEX_FLESH 'F' + +#endif // !PM_MATERIALSH \ No newline at end of file diff --git a/ricochet/pm_shared/pm_math.c b/ricochet/pm_shared/pm_math.c new file mode 100644 index 0000000..1deed2b --- /dev/null +++ b/ricochet/pm_shared/pm_math.c @@ -0,0 +1,326 @@ +// mathlib.c -- math primitives + +#include "mathlib.h" +#include "const.h" +#include + +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 + +#pragma warning(disable : 4244) + +#ifndef DISABLE_VEC_ORIGIN +vec3_t vec3_origin = {0,0,0}; +#endif +int nanmask = 255<<23; + +float anglemod(float a) +{ + a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + +void AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + if (forward) + { + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + } + if (right) + { + right[0] = (-1*sr*sp*cy+-1*cr*-sy); + right[1] = (-1*sr*sp*sy+-1*cr*cy); + right[2] = -1*sr*cp; + } + if (up) + { + up[0] = (cr*sp*cy+-sr*-sy); + up[1] = (cr*sp*sy+-sr*cy); + up[2] = cr*cp; + } +} + +void AngleVectorsTranspose (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + if (forward) + { + forward[0] = cp*cy; + forward[1] = (sr*sp*cy+cr*-sy); + forward[2] = (cr*sp*cy+-sr*-sy); + } + if (right) + { + right[0] = cp*sy; + right[1] = (sr*sp*sy+cr*cy); + right[2] = (cr*sp*sy+-sr*cy); + } + if (up) + { + up[0] = -sp; + up[1] = sr*cp; + up[2] = cr*cp; + } +} + + +void AngleMatrix (const vec3_t angles, float (*matrix)[4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + // matrix = (YAW * PITCH) * ROLL + matrix[0][0] = cp*cy; + matrix[1][0] = cp*sy; + matrix[2][0] = -sp; + matrix[0][1] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[2][1] = sr*cp; + matrix[0][2] = (cr*sp*cy+-sr*-sy); + matrix[1][2] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + +void AngleIMatrix (const vec3_t angles, float matrix[3][4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + // matrix = (YAW * PITCH) * ROLL + matrix[0][0] = cp*cy; + matrix[0][1] = cp*sy; + matrix[0][2] = -sp; + matrix[1][0] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[1][2] = sr*cp; + matrix[2][0] = (cr*sp*cy+-sr*-sy); + matrix[2][1] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + + +void VectorTransform (const vec3_t in1, float in2[3][4], vec3_t out) +{ + out[0] = DotProduct(in1, in2[0]) + in2[0][3]; + out[1] = DotProduct(in1, in2[1]) + in2[1][3]; + out[2] = DotProduct(in1, in2[2]) + in2[2][3]; +} + + +int VectorCompare (const vec3_t v1, const vec3_t v2) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (v1[i] != v2[i]) + return 0; + + return 1; +} + +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 (vec3_t v1, vec3_t v2) +{ + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]-vecb[0]; + out[1] = veca[1]-vecb[1]; + out[2] = veca[2]-vecb[2]; +} + +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]+vecb[0]; + out[1] = veca[1]+vecb[1]; + out[2] = veca[2]+vecb[2]; +} + +void _VectorCopy (vec3_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +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]; +} + +double sqrt(double x); + +float Length(const vec3_t v) +{ + int i; + float length; + + length = 0; + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); // FIXME + + return length; +} + +float VectorNormalize (vec3_t v) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; + +} + +void VectorInverse (vec3_t v) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[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; +} + + +int Q_log2(int val) +{ + int answer=0; + while (val>>=1) + answer++; + return answer; +} + +void VectorMatrix( vec3_t forward, vec3_t right, vec3_t up) +{ + vec3_t tmp; + + if (forward[0] == 0 && forward[1] == 0) + { + right[0] = 1; + right[1] = 0; + right[2] = 0; + up[0] = -forward[2]; + up[1] = 0; + up[2] = 0; + return; + } + + tmp[0] = 0; tmp[1] = 0; tmp[2] = 1.0; + CrossProduct( forward, tmp, right ); + VectorNormalize( right ); + CrossProduct( right, forward, up ); + VectorNormalize( up ); +} + + +void VectorAngles( const vec3_t forward, vec3_t angles ) +{ + float tmp, yaw, pitch; + + if (forward[1] == 0 && forward[0] == 0) + { + yaw = 0; + if (forward[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + yaw = (atan2(forward[1], forward[0]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + + tmp = sqrt (forward[0]*forward[0] + forward[1]*forward[1]); + pitch = (atan2(forward[2], tmp) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + angles[0] = pitch; + angles[1] = yaw; + angles[2] = 0; +} \ No newline at end of file diff --git a/ricochet/pm_shared/pm_movevars.h b/ricochet/pm_shared/pm_movevars.h new file mode 100644 index 0000000..71f2179 --- /dev/null +++ b/ricochet/pm_shared/pm_movevars.h @@ -0,0 +1,40 @@ +// pm_movevars.h +#if !defined( PM_MOVEVARSH ) +#define PM_MOVEVARSH + +// movevars_t // Physics variables. +typedef struct movevars_s movevars_t; + +struct movevars_s +{ + float gravity; // Gravity for map + float stopspeed; // Deceleration when not moving + float maxspeed; // Max allowed speed + float spectatormaxspeed; + float accelerate; // Acceleration factor + float airaccelerate; // Same for when in open air + float wateraccelerate; // Same for when in water + float friction; + float edgefriction; // Extra friction near dropofs + float waterfriction; // Less in water + float entgravity; // 1.0 + float bounce; // Wall bounce value. 1.0 + float stepsize; // sv_stepsize; + float maxvelocity; // maximum server velocity. + float zmax; // Max z-buffer range (for GL) + float waveHeight; // Water wave height (for GL) + qboolean footsteps; // Play footstep sounds + char skyName[32]; // Name of the sky map + float rollangle; + float rollspeed; + float skycolor_r; // Sky color + float skycolor_g; // + float skycolor_b; // + float skyvec_x; // Sky vector + float skyvec_y; // + float skyvec_z; // +}; + +extern movevars_t movevars; + +#endif \ No newline at end of file diff --git a/ricochet/pm_shared/pm_shared.c b/ricochet/pm_shared/pm_shared.c new file mode 100644 index 0000000..d08b0eb --- /dev/null +++ b/ricochet/pm_shared/pm_shared.c @@ -0,0 +1,3374 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include +#include "mathlib.h" +#include "const.h" +#include "usercmd.h" +#include "pm_defs.h" +#include "pm_shared.h" +#include "pm_movevars.h" +#include "pm_debug.h" +#include // NULL +#include // sqrt +#include // strcpy +#include // atoi +#include // isspace + +#ifdef CLIENT_DLL + // Spectator Mode + float vecNewViewAngles[3]; + float vecNewViewOrigin[3]; + int iHasNewViewAngles; + int iHasNewViewOrigin; + int iIsSpectator; +#endif + +static int pm_shared_initialized = 0; + +#pragma warning( disable : 4305 ) + +typedef enum {mod_brush, mod_sprite, mod_alias, mod_studio} modtype_t; + +playermove_t *pmove = NULL; + +typedef struct +{ + int planenum; + short children[2]; // negative numbers are contents +} dclipnode_t; + +typedef struct mplane_s +{ + vec3_t normal; // surface normal + float dist; // closest appoach to origin + byte type; // for texture axis selection and fast side tests + byte signbits; // signx + signy<<1 + signz<<1 + byte pad[2]; +} mplane_t; + +typedef struct hull_s +{ + dclipnode_t *clipnodes; + mplane_t *planes; + int firstclipnode; + int lastclipnode; + vec3_t clip_mins; + vec3_t clip_maxs; +} hull_t; + +// Ducking time +#define TIME_TO_DUCK 0.4 +#define VEC_DUCK_HULL_MIN -18 +#define VEC_DUCK_HULL_MAX 18 +#define VEC_DUCK_VIEW 12 +#define PM_DEAD_VIEWHEIGHT -8 +#define MAX_CLIMB_SPEED 200 +#define STUCK_MOVEUP 1 +#define STUCK_MOVEDOWN -1 +#define VEC_HULL_MIN -36 +#define VEC_HULL_MAX 36 +#define VEC_VIEW 28 +#define STOP_EPSILON 0.1 + +#define CTEXTURESMAX 512 // max number of textures loaded +#define CBTEXTURENAMEMAX 13 // only load first n chars of name + +#define CHAR_TEX_CONCRETE 'C' // texture types +#define CHAR_TEX_METAL 'M' +#define CHAR_TEX_DIRT 'D' +#define CHAR_TEX_VENT 'V' +#define CHAR_TEX_GRATE 'G' +#define CHAR_TEX_TILE 'T' +#define CHAR_TEX_SLOSH 'S' +#define CHAR_TEX_WOOD 'W' +#define CHAR_TEX_COMPUTER 'P' +#define CHAR_TEX_GLASS 'Y' +#define CHAR_TEX_FLESH 'F' + +#define STEP_CONCRETE 0 // default step sound +#define STEP_METAL 1 // metal floor +#define STEP_DIRT 2 // dirt, sand, rock +#define STEP_VENT 3 // ventillation duct +#define STEP_GRATE 4 // metal grating +#define STEP_TILE 5 // floor tiles +#define STEP_SLOSH 6 // shallow liquid puddle +#define STEP_WADE 7 // wading in liquid +#define STEP_LADDER 8 // climbing ladder + +#define PLAYER_FATAL_FALL_SPEED 1024// approx 60 feet +#define PLAYER_MAX_SAFE_FALL_SPEED 580// approx 20 feet +#define DAMAGE_FOR_FALL_SPEED (float) 100 / ( PLAYER_FATAL_FALL_SPEED - PLAYER_MAX_SAFE_FALL_SPEED )// damage per unit per second. +#define PLAYER_MIN_BOUNCE_SPEED 200 +#define PLAYER_FALL_PUNCH_THRESHHOLD (float)350 // won't punch player's screen/make scrape noise unless player falling at least this fast. + +#define PLAYER_LONGJUMP_SPEED 350 // how fast we longjump + +#define PLAYER_DUCKING_MULTIPLIER 0.333 + +// double to float warning +#pragma warning(disable : 4244) +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) < (b)) ? (a) : (b)) +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 + +#define MAX_CLIENTS 32 + +#define CONTENTS_CURRENT_0 -9 +#define CONTENTS_CURRENT_90 -10 +#define CONTENTS_CURRENT_180 -11 +#define CONTENTS_CURRENT_270 -12 +#define CONTENTS_CURRENT_UP -13 +#define CONTENTS_CURRENT_DOWN -14 + +#define CONTENTS_TRANSLUCENT -15 + +static vec3_t rgv3tStuckTable[54]; +static int rgStuckLast[MAX_CLIENTS][2]; + +// Texture names +static int gcTextures = 0; +static char grgszTextureName[CTEXTURESMAX][CBTEXTURENAMEMAX]; +static char grgchTextureType[CTEXTURESMAX]; + +int g_onladder = 0; + +int PM_Ignore( physent_t *pe ) +{ + //if ( !stricmp( pe->name, "models/disc.mdl" ) ) + // return 1; + return 0; +} + +void PM_SwapTextures( int i, int j ) +{ + char chTemp; + char szTemp[ CBTEXTURENAMEMAX ]; + + strcpy( szTemp, grgszTextureName[ i ] ); + chTemp = grgchTextureType[ i ]; + + strcpy( grgszTextureName[ i ], grgszTextureName[ j ] ); + grgchTextureType[ i ] = grgchTextureType[ j ]; + + strcpy( grgszTextureName[ j ], szTemp ); + grgchTextureType[ j ] = chTemp; +} + +void PM_SortTextures( void ) +{ + // Bubble sort, yuck, but this only occurs at startup and it's only 512 elements... + // + int i, j; + + for ( i = 0 ; i < gcTextures; i++ ) + { + for ( j = i + 1; j < gcTextures; j++ ) + { + if ( stricmp( grgszTextureName[ i ], grgszTextureName[ j ] ) > 0 ) + { + // Swap + // + PM_SwapTextures( i, j ); + } + } + } +} + +void PM_InitTextureTypes() +{ + char buffer[512]; + int i, j; + byte *pMemFile; + int fileSize, filePos; + static qboolean bTextureTypeInit = false; + + if ( bTextureTypeInit ) + return; + + memset(&(grgszTextureName[0][0]), 0, CTEXTURESMAX * CBTEXTURENAMEMAX); + memset(grgchTextureType, 0, CTEXTURESMAX); + + gcTextures = 0; + memset(buffer, 0, 512); + + fileSize = pmove->COM_FileSize( "sound/materials.txt" ); + pMemFile = pmove->COM_LoadFile( "sound/materials.txt", 5, NULL ); + if ( !pMemFile ) + return; + + filePos = 0; + // for each line in the file... + while ( pmove->memfgets( pMemFile, fileSize, &filePos, buffer, 511 ) != NULL && (gcTextures < CTEXTURESMAX) ) + { + // skip whitespace + i = 0; + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // skip comment lines + if (buffer[i] == '/' || !isalpha(buffer[i])) + continue; + + // get texture type + grgchTextureType[gcTextures] = toupper(buffer[i++]); + + // skip whitespace + while(buffer[i] && isspace(buffer[i])) + i++; + + if (!buffer[i]) + continue; + + // get sentence name + j = i; + while (buffer[j] && !isspace(buffer[j])) + j++; + + if (!buffer[j]) + continue; + + // null-terminate name and save in sentences array + j = min (j, CBTEXTURENAMEMAX-1+i); + buffer[j] = 0; + strcpy(&(grgszTextureName[gcTextures++][0]), &(buffer[i])); + } + + // Must use engine to free since we are in a .dll + pmove->COM_FreeFile ( pMemFile ); + + PM_SortTextures(); + + bTextureTypeInit = true; +} + +char PM_FindTextureType( char *name ) +{ + int left, right, pivot; + int val; + + assert( pm_shared_initialized ); + + left = 0; + right = gcTextures - 1; + + while ( left <= right ) + { + pivot = ( left + right ) / 2; + + val = strnicmp( name, grgszTextureName[ pivot ], CBTEXTURENAMEMAX-1 ); + if ( val == 0 ) + { + return grgchTextureType[ pivot ]; + } + else if ( val > 0 ) + { + left = pivot + 1; + } + else if ( val < 0 ) + { + right = pivot - 1; + } + } + + return CHAR_TEX_CONCRETE; +} + +void PM_PlayStepSound( int step, float fvol ) +{ + static int iSkipStep = 0; + int irand; + vec3_t hvel; + + pmove->iStepLeft = !pmove->iStepLeft; + + if ( !pmove->runfuncs ) + { + return; + } + + irand = pmove->RandomLong(0,1) + ( pmove->iStepLeft * 2 ); + + // FIXME mp_footsteps needs to be a movevar + if ( pmove->multiplayer && !pmove->movevars->footsteps ) + return; + + VectorCopy( pmove->velocity, hvel ); + hvel[2] = 0.0; + + if ( pmove->multiplayer && ( !g_onladder && Length( hvel ) <= 220 ) ) + return; + + // irand - 0,1 for right foot, 2,3 for left foot + // used to alternate left and right foot + // FIXME, move to player state + + switch (step) + { + default: + case STEP_CONCRETE: + switch (irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_step1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_step3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_step2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_step4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_METAL: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_metal1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_metal3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_metal2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_metal4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_DIRT: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_dirt1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_dirt3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_dirt2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_dirt4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_VENT: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_duct1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_duct3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_duct2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_duct4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_GRATE: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_grate1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_grate3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_grate2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_grate4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_TILE: + if ( !pmove->RandomLong(0,4) ) + irand = 4; + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_tile1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_tile3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_tile2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_tile4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 4: pmove->PM_PlaySound( CHAN_BODY, "player/pl_tile5.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_SLOSH: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_slosh1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_slosh3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_slosh2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_slosh4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_WADE: + if ( iSkipStep == 0 ) + { + iSkipStep++; + break; + } + + if ( iSkipStep++ == 3 ) + { + iSkipStep = 0; + } + + switch (irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_LADDER: + switch(irand) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_ladder1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_ladder3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_ladder2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_ladder4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + } +} + +int PM_MapTextureTypeStepType(char chTextureType) +{ + switch (chTextureType) + { + default: + case CHAR_TEX_CONCRETE: return STEP_CONCRETE; + case CHAR_TEX_METAL: return STEP_METAL; + case CHAR_TEX_DIRT: return STEP_DIRT; + case CHAR_TEX_VENT: return STEP_VENT; + case CHAR_TEX_GRATE: return STEP_GRATE; + case CHAR_TEX_TILE: return STEP_TILE; + case CHAR_TEX_SLOSH: return STEP_SLOSH; + } +} + +/* +==================== +PM_CatagorizeTextureType + +Determine texture info for the texture we are standing on. +==================== +*/ +void PM_CatagorizeTextureType( void ) +{ + vec3_t start, end; + const char *pTextureName; + + VectorCopy( pmove->origin, start ); + VectorCopy( pmove->origin, end ); + + // Straight down + end[2] -= 64; + + // Fill in default values, just in case. + pmove->sztexturename[0] = '\0'; + pmove->chtexturetype = CHAR_TEX_CONCRETE; + + pTextureName = pmove->PM_TraceTexture( pmove->onground, start, end ); + if ( !pTextureName ) + return; + + // strip leading '-0' or '+0~' or '{' or '!' + if (*pTextureName == '-' || *pTextureName == '+') + pTextureName += 2; + + if (*pTextureName == '{' || *pTextureName == '!' || *pTextureName == '~' || *pTextureName == ' ') + pTextureName++; + // '}}' + + strcpy( pmove->sztexturename, pTextureName); + pmove->sztexturename[ CBTEXTURENAMEMAX - 1 ] = 0; + + // get texture type + pmove->chtexturetype = PM_FindTextureType( pmove->sztexturename ); +} + +void PM_UpdateStepSound( void ) +{ + int fWalking; + float fvol; + vec3_t knee; + vec3_t feet; + vec3_t center; + float height; + float speed; + float velrun; + float velwalk; + float flduck; + int fLadder; + int step; + + if ( pmove->flTimeStepSound > 0 ) + return; + + if ( pmove->flags & FL_FROZEN ) + return; + + PM_CatagorizeTextureType(); + + speed = Length( pmove->velocity ); + + // determine if we are on a ladder + fLadder = ( pmove->movetype == MOVETYPE_FLY );// IsOnLadder(); + + // UNDONE: need defined numbers for run, walk, crouch, crouch run velocities!!!! + if ( ( pmove->flags & FL_DUCKING) || fLadder ) + { + velwalk = 60; // These constants should be based on cl_movespeedkey * cl_forwardspeed somehow + velrun = 80; // UNDONE: Move walking to server + flduck = 100; + } + else + { + velwalk = 120; + velrun = 210; + flduck = 0; + } + + // If we're on a ladder or on the ground, and we're moving fast enough, + // play step sound. Also, if pmove->flTimeStepSound is zero, get the new + // sound right away - we just started moving in new level. + if ( (fLadder || ( pmove->onground != -1 ) ) && + ( Length( pmove->velocity ) > 0.0 ) && + ( speed >= velwalk || !pmove->flTimeStepSound ) ) + { + fWalking = speed < velrun; + + VectorCopy( pmove->origin, center ); + VectorCopy( pmove->origin, knee ); + VectorCopy( pmove->origin, feet ); + + height = pmove->player_maxs[ pmove->usehull ][ 2 ] - pmove->player_mins[ pmove->usehull ][ 2 ]; + + knee[2] = pmove->origin[2] - 0.3 * height; + feet[2] = pmove->origin[2] - 0.5 * height; + + // find out what we're stepping in or on... + if (fLadder) + { + step = STEP_LADDER; + fvol = 0.35; + pmove->flTimeStepSound = 350; + } + else if ( pmove->PM_PointContents ( knee, NULL ) == CONTENTS_WATER ) + { + step = STEP_WADE; + fvol = 0.65; + pmove->flTimeStepSound = 600; + } + else if ( pmove->PM_PointContents ( feet, NULL ) == CONTENTS_WATER ) + { + step = STEP_SLOSH; + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + } + else + { + // find texture under player, if different from current texture, + // get material type + step = PM_MapTextureTypeStepType( pmove->chtexturetype ); + + switch ( pmove->chtexturetype ) + { + default: + case CHAR_TEX_CONCRETE: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_METAL: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_DIRT: + fvol = fWalking ? 0.25 : 0.55; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_VENT: + fvol = fWalking ? 0.4 : 0.7; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_GRATE: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_TILE: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + + case CHAR_TEX_SLOSH: + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + break; + } + } + + pmove->flTimeStepSound += flduck; // slower step time if ducking + + // play the sound + // 35% volume if ducking + if ( pmove->flags & FL_DUCKING ) + { + fvol *= 0.35; + } + + PM_PlayStepSound( step, fvol ); + } +} + +/* +================ +PM_AddToTouched + +Add's the trace result to touch list, if contact is not already in list. +================ +*/ +qboolean PM_AddToTouched(pmtrace_t tr, vec3_t impactvelocity) +{ + int i; + + for (i = 0; i < pmove->numtouch; i++) + { + if (pmove->touchindex[i].ent == tr.ent) + break; + } + if (i != pmove->numtouch) // Already in list. + return false; + + VectorCopy( impactvelocity, tr.deltavelocity ); + + if (pmove->numtouch >= MAX_PHYSENTS) + pmove->Con_DPrintf("Too many entities were touched!\n"); + + pmove->touchindex[pmove->numtouch++] = tr; + return true; +} + +/* +================ +PM_CheckVelocity + +See if the player has a bogus velocity value. +================ +*/ +void PM_CheckVelocity () +{ + int i; + +// +// bound velocity +// + for (i=0 ; i<3 ; i++) + { + // See if it's bogus. + if (IS_NAN(pmove->velocity[i])) + { + pmove->Con_Printf ("PM Got a NaN velocity %i\n", i); + pmove->velocity[i] = 0; + } + if (IS_NAN(pmove->origin[i])) + { + pmove->Con_Printf ("PM Got a NaN origin on %i\n", i); + pmove->origin[i] = 0; + } + + // Bound it. + if (pmove->velocity[i] > pmove->movevars->maxvelocity) + { + pmove->Con_DPrintf ("PM Got a velocity too high on %i\n", i); + pmove->velocity[i] = pmove->movevars->maxvelocity; + } + else if (pmove->velocity[i] < -pmove->movevars->maxvelocity) + { + pmove->Con_DPrintf ("PM Got a velocity too low on %i\n", i); + pmove->velocity[i] = -pmove->movevars->maxvelocity; + } + } +} + +/* +================== +PM_ClipVelocity + +Slide off of the impacting object +returns the blocked flags: +0x01 == floor +0x02 == step / wall +================== +*/ +int PM_ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce) +{ + float backoff; + float change; + float angle; + int i, blocked; + + angle = normal[ 2 ]; + + blocked = 0x00; // Assume unblocked. + if (angle > 0) // If the plane that is blocking us has a positive z component, then assume it's a floor. + blocked |= 0x01; // + if (!angle) // If the plane has no Z, it is vertical (wall/step) + blocked |= 0x02; // + + // Determine how far along plane to slide based on incoming direction. + // Scale by overbounce factor. + backoff = DotProduct (in, normal) * overbounce; + + for (i=0 ; i<3 ; i++) + { + change = normal[i]*backoff; + out[i] = in[i] - change; + // If out velocity is too small, zero it out. + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; + } + + // Return blocking flags. + return blocked; +} + +void PM_AddCorrectGravity () +{ + float ent_gravity; + + if ( pmove->waterjumptime ) + return; + + if (pmove->gravity) + ent_gravity = pmove->gravity; + else + ent_gravity = 1.0; + + // Add gravity so they'll be in the correct position during movement + // yes, this 0.5 looks wrong, but it's not. + pmove->velocity[2] -= (ent_gravity * pmove->movevars->gravity * 0.5 * pmove->frametime ); + pmove->velocity[2] += pmove->basevelocity[2] * pmove->frametime; + pmove->basevelocity[2] = 0; + + PM_CheckVelocity(); +} + + +void PM_FixupGravityVelocity () +{ + float ent_gravity; + + if ( pmove->waterjumptime ) + return; + + if (pmove->gravity) + ent_gravity = pmove->gravity; + else + ent_gravity = 1.0; + + // Get the correct velocity for the end of the dt + pmove->velocity[2] -= (ent_gravity * pmove->movevars->gravity * pmove->frametime * 0.5 ); + + PM_CheckVelocity(); +} + +/* +============ +PM_FlyMove + +The basic solid body movement clip that slides along multiple planes +============ +*/ +int PM_FlyMove (void) +{ + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity, original_velocity; + vec3_t new_velocity; + int i, j; + pmtrace_t trace; + vec3_t end; + float time_left, allFraction; + int blocked; + + numbumps = 4; // Bump up to four times + + blocked = 0; // Assume not blocked + numplanes = 0; // and not sliding along any planes + VectorCopy (pmove->velocity, original_velocity); // Store original velocity + VectorCopy (pmove->velocity, primal_velocity); + + allFraction = 0; + time_left = pmove->frametime; // Total time for this movement operation. + + for (bumpcount=0 ; bumpcountvelocity[0] && !pmove->velocity[1] && !pmove->velocity[2]) + break; + + // Assume we can move all the way from the current origin to the + // end point. + for (i=0 ; i<3 ; i++) + end[i] = pmove->origin[i] + time_left * pmove->velocity[i]; + + // See if we can make it from origin to end point. + trace = pmove->PM_PlayerTraceEx (pmove->origin, end, PM_NORMAL, PM_Ignore ); + + allFraction += trace.fraction; + // If we started in a solid object, or we were in solid space + // the whole way, zero out our velocity and return that we + // are blocked by floor and wall. + if (trace.allsolid) + { // entity is trapped in another solid + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf("Trapped 4\n"); + return 4; + } + + // If we moved some portion of the total distance, then + // copy the end position into the pmove->origin and + // zero the plane counter. + if (trace.fraction > 0) + { // actually covered some distance + VectorCopy (trace.endpos, pmove->origin); + VectorCopy (pmove->velocity, original_velocity); + numplanes = 0; + } + + // If we covered the entire distance, we are done + // and can return. + if (trace.fraction == 1) + break; // moved the entire distance + + //if (!trace.ent) + // Sys_Error ("PM_PlayerTrace: !trace.ent"); + + // Save entity that blocked us (since fraction was < 1.0) + // for contact + // Add it if it's not already in the list!!! + PM_AddToTouched(trace, pmove->velocity); + + // If the plane we hit has a high z component in the normal, then + // it's probably a floor + if (trace.plane.normal[2] > 0.7) + { + blocked |= 1; // floor + } + // If the plane has a zero z component in the normal, then it's a + // step or wall + if (!trace.plane.normal[2]) + { + blocked |= 2; // step / wall + //Con_DPrintf("Blocked by %i\n", trace.ent); + } + + // Reduce amount of pmove->frametime left by total time left * fraction + // that we covered. + time_left -= time_left * trace.fraction; + + // Did we run out of planes to clip against? + if (numplanes >= MAX_CLIP_PLANES) + { // this shouldn't really happen + // Stop our movement if so. + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf("Too many planes 4\n"); + + break; + } + + // Set up next clipping plane + VectorCopy (trace.plane.normal, planes[numplanes]); + numplanes++; +// + +// modify original_velocity so it parallels all of the clip planes +// + if ( pmove->movetype == MOVETYPE_WALK && + ((pmove->onground == -1) || (pmove->friction != 1)) ) // relfect player velocity + { + for ( i = 0; i < numplanes; i++ ) + { + if ( planes[i][2] > 0.7 ) + {// floor or slope + PM_ClipVelocity( original_velocity, planes[i], new_velocity, 1 ); + VectorCopy( new_velocity, original_velocity ); + } + else + PM_ClipVelocity( original_velocity, planes[i], new_velocity, 1.0 + pmove->movevars->bounce * (1-pmove->friction) ); + } + + VectorCopy( new_velocity, pmove->velocity ); + VectorCopy( new_velocity, original_velocity ); + } + else + { + for (i=0 ; ivelocity, + 1); + for (j=0 ; jvelocity, planes[j]) < 0) + break; // not ok + } + if (j == numplanes) // Didn't have to clip, so we're ok + break; + } + + // Did we go all the way through plane set + if (i != numplanes) + { // go along this plane + // pmove->velocity is set in clipping call, no need to set again. + ; + } + else + { // go along the crease + if (numplanes != 2) + { + //Con_Printf ("clip velocity, numplanes == %i\n",numplanes); + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf("Trapped 4\n"); + + break; + } + CrossProduct (planes[0], planes[1], dir); + d = DotProduct (dir, pmove->velocity); + VectorScale (dir, d, pmove->velocity ); + } + + // + // if original velocity is against the original velocity, stop dead + // to avoid tiny occilations in sloping corners + // + if (DotProduct (pmove->velocity, primal_velocity) <= 0) + { + //Con_DPrintf("Back\n"); + VectorCopy (vec3_origin, pmove->velocity); + break; + } + } + } + + if ( allFraction == 0 ) + { + VectorCopy (vec3_origin, pmove->velocity); + //Con_DPrintf( "Don't stick\n" ); + } + + return blocked; +} + +/* +============== +PM_Accelerate +============== +*/ +void PM_Accelerate (vec3_t wishdir, float wishspeed, float accel) +{ + int i; + float addspeed, accelspeed, currentspeed; + + // Dead player's don't accelerate + if (pmove->dead) + return; + + // If waterjumping, don't accelerate + if (pmove->waterjumptime) + return; + + // See if we are changing direction a bit + currentspeed = DotProduct (pmove->velocity, wishdir); + + // Reduce wishspeed by the amount of veer. + addspeed = wishspeed - currentspeed; + + // If not going to add any speed, done. + if (addspeed <= 0) + return; + + // Determine amount of accleration. + accelspeed = accel * pmove->frametime * wishspeed * pmove->friction; + + // Cap at addspeed + if (accelspeed > addspeed) + accelspeed = addspeed; + + // Adjust velocity. + for (i=0 ; i<3 ; i++) + { + pmove->velocity[i] += accelspeed * wishdir[i]; + } +} + +/* +===================== +PM_WalkMove + +Only used by players. Moves along the ground when player is a MOVETYPE_WALK. +====================== +*/ +void PM_WalkMove () +{ + int clip; + int oldonground; + int i; + + vec3_t wishvel; + float spd; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + + vec3_t dest, start; + vec3_t original, originalvel; + vec3_t down, downvel; + float downdist, updist; + + pmtrace_t trace; + + // Copy movement amounts + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + // Zero out z components of movement vectors + pmove->forward[2] = 0; + pmove->right[2] = 0; + + VectorNormalize (pmove->forward); // Normalize remainder of vectors. + VectorNormalize (pmove->right); // + + for (i=0 ; i<2 ; i++) // Determine x and y parts of velocity + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + + wishvel[2] = 0; // Zero out z part of velocity + + VectorCopy (wishvel, wishdir); // Determine maginitude of speed of move + wishspeed = VectorNormalize(wishdir); + +// +// Clamp to server defined max speed +// + if (wishspeed > pmove->maxspeed) + { + VectorScale (wishvel, pmove->maxspeed/wishspeed, wishvel); + wishspeed = pmove->maxspeed; + } + + // Set pmove velocity + pmove->velocity[2] = 0; + PM_Accelerate (wishdir, wishspeed, pmove->movevars->accelerate); + pmove->velocity[2] = 0; + + // Add in any base velocity to the current velocity. + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity ); + + spd = Length( pmove->velocity ); + + if (spd < 1.0f) + { + VectorClear( pmove->velocity ); + return; + } + + // If we are not moving, do nothing + //if (!pmove->velocity[0] && !pmove->velocity[1] && !pmove->velocity[2]) + // return; + + oldonground = pmove->onground; + +// first try just moving to the destination + dest[0] = pmove->origin[0] + pmove->velocity[0]*pmove->frametime; + dest[1] = pmove->origin[1] + pmove->velocity[1]*pmove->frametime; + dest[2] = pmove->origin[2]; + + // first try moving directly to the next spot + VectorCopy (dest, start); + trace = pmove->PM_PlayerTraceEx (pmove->origin, dest, PM_NORMAL, PM_Ignore ); + // If we made it all the way, then copy trace end + // as new player position. + if (trace.fraction == 1) + { + VectorCopy (trace.endpos, pmove->origin); + return; + } + + if (oldonground == -1 && // Don't walk up stairs if not on ground. + pmove->waterlevel == 0) + return; + + if (pmove->waterjumptime) // If we are jumping out of water, don't do anything more. + return; + + // Try sliding forward both on ground and up 16 pixels + // take the move that goes farthest + VectorCopy (pmove->origin, original); // Save out original pos & + VectorCopy (pmove->velocity, originalvel); // velocity. + + // Slide move + clip = PM_FlyMove (); + + // Copy the results out + VectorCopy (pmove->origin , down); + VectorCopy (pmove->velocity, downvel); + + // Reset original values. + VectorCopy (original, pmove->origin); + + VectorCopy (originalvel, pmove->velocity); + + // Start out up one stair height + VectorCopy (pmove->origin, dest); + dest[2] += pmove->movevars->stepsize; + + trace = pmove->PM_PlayerTraceEx (pmove->origin, dest, PM_NORMAL, PM_Ignore ); + // If we started okay and made it part of the way at least, + // copy the results to the movement start position and then + // run another move try. + if (!trace.startsolid && !trace.allsolid) + { + VectorCopy (trace.endpos, pmove->origin); + } + +// slide move the rest of the way. + clip = PM_FlyMove (); + +// Now try going back down from the end point +// press down the stepheight + VectorCopy (pmove->origin, dest); + dest[2] -= pmove->movevars->stepsize; + + trace = pmove->PM_PlayerTraceEx (pmove->origin, dest, PM_NORMAL, PM_Ignore ); + + // If we are not on the ground any more then + // use the original movement attempt + if ( trace.plane.normal[2] < 0.7) + goto usedown; + // If the trace ended up in empty space, copy the end + // over to the origin. + if (!trace.startsolid && !trace.allsolid) + { + VectorCopy (trace.endpos, pmove->origin); + } + // Copy this origion to up. + VectorCopy (pmove->origin, pmove->up); + + // decide which one went farther + downdist = (down[0] - original[0])*(down[0] - original[0]) + + (down[1] - original[1])*(down[1] - original[1]); + updist = (pmove->up[0] - original[0])*(pmove->up[0] - original[0]) + + (pmove->up[1] - original[1])*(pmove->up[1] - original[1]); + + if (downdist > updist) + { +usedown: + VectorCopy (down , pmove->origin); + VectorCopy (downvel, pmove->velocity); + } else // copy z value from slide move + pmove->velocity[2] = downvel[2]; + +} + +/* +================== +PM_Friction + +Handles both ground friction and water friction +================== +*/ +void PM_Friction (void) +{ + float *vel; + float speed, newspeed, control; + float friction; + float drop; + vec3_t newvel; + + // If we are in water jump cycle, don't apply friction + if (pmove->waterjumptime) + return; + + // Get velocity + vel = pmove->velocity; + + // Calculate speed + speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1] + vel[2]*vel[2]); + + // If too slow, return + if (speed < 0.1f) + { + return; + } + + drop = 0; + +// apply ground friction + if (pmove->onground != -1) // On an entity that is the ground + { + vec3_t start, stop; + pmtrace_t trace; + + start[0] = stop[0] = pmove->origin[0] + vel[0]/speed*16; + start[1] = stop[1] = pmove->origin[1] + vel[1]/speed*16; + start[2] = pmove->origin[2] + pmove->player_mins[pmove->usehull][2]; + stop[2] = start[2] - 34; + + trace = pmove->PM_PlayerTraceEx (start, stop, PM_NORMAL, PM_Ignore ); + + if (trace.fraction == 1.0) + friction = pmove->movevars->friction*pmove->movevars->edgefriction; + else + friction = pmove->movevars->friction; + + // Grab friction value. + //friction = pmove->movevars->friction; + + friction *= pmove->friction; // player friction? + + // Bleed off some speed, but if we have less than the bleed + // threshhold, bleed the theshold amount. + control = (speed < pmove->movevars->stopspeed) ? + pmove->movevars->stopspeed : speed; + // Add the amount to t'he drop amount. + drop += control*friction*pmove->frametime; + } + +// apply water friction +// if (pmove->waterlevel) +// drop += speed * pmove->movevars->waterfriction * waterlevel * pmove->frametime; + +// scale the velocity + newspeed = speed - drop; + if (newspeed < 0) + newspeed = 0; + + // Determine proportion of old speed we are using. + newspeed /= speed; + + // Adjust velocity according to proportion. + newvel[0] = vel[0] * newspeed; + newvel[1] = vel[1] * newspeed; + newvel[2] = vel[2] * newspeed; + + VectorCopy( newvel, pmove->velocity ); +} + +void PM_AirAccelerate (vec3_t wishdir, float wishspeed, float accel) +{ + int i; + float addspeed, accelspeed, currentspeed, wishspd = wishspeed; + + if (pmove->dead) + return; + if (pmove->waterjumptime) + return; + + // Cap speed + //wishspd = VectorNormalize (pmove->wishveloc); + + if (wishspd > 30) + wishspd = 30; + // Determine veer amount + currentspeed = DotProduct (pmove->velocity, wishdir); + // See how much to add + addspeed = wishspd - currentspeed; + // If not adding any, done. + if (addspeed <= 0) + return; + // Determine acceleration speed after acceleration + + accelspeed = accel * wishspeed * pmove->frametime * pmove->friction; + // Cap it + if (accelspeed > addspeed) + accelspeed = addspeed; + + // Adjust pmove vel. + for (i=0 ; i<3 ; i++) + { + pmove->velocity[i] += accelspeed*wishdir[i]; + } +} + +/* +=================== +PM_WaterMove + +=================== +*/ +void PM_WaterMove (void) +{ + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + vec3_t start, dest; + vec3_t temp; + pmtrace_t trace; + + float speed, newspeed, addspeed, accelspeed; + +// +// user intentions +// + for (i=0 ; i<3 ; i++) + wishvel[i] = pmove->forward[i]*pmove->cmd.forwardmove + pmove->right[i]*pmove->cmd.sidemove; + + // Sinking after no other movement occurs + if (!pmove->cmd.forwardmove && !pmove->cmd.sidemove && !pmove->cmd.upmove) + wishvel[2] -= 60; // drift towards bottom + else // Go straight up by upmove amount. + wishvel[2] += pmove->cmd.upmove; + + // Copy it over and determine speed + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + // Cap speed. + if (wishspeed > pmove->maxspeed) + { + VectorScale (wishvel, pmove->maxspeed/wishspeed, wishvel); + wishspeed = pmove->maxspeed; + } + // Slow us down a bit. + wishspeed *= 0.8; + + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity); +// Water friction + VectorCopy(pmove->velocity, temp); + speed = VectorNormalize(temp); + if (speed) + { + newspeed = speed - pmove->frametime * speed * pmove->movevars->friction * pmove->friction; + + if (newspeed < 0) + newspeed = 0; + VectorScale (pmove->velocity, newspeed/speed, pmove->velocity); + } + else + newspeed = 0; + +// +// water acceleration +// + if ( wishspeed < 0.1f ) + { + return; + } + + addspeed = wishspeed - newspeed; + if (addspeed > 0) + { + + VectorNormalize(wishvel); + accelspeed = pmove->movevars->accelerate * wishspeed * pmove->frametime * pmove->friction; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i = 0; i < 3; i++) + pmove->velocity[i] += accelspeed * wishvel[i]; + } + +// Now move +// assume it is a stair or a slope, so press down from stepheight above + VectorMA (pmove->origin, pmove->frametime, pmove->velocity, dest); + VectorCopy (dest, start); + start[2] += pmove->movevars->stepsize + 1; + trace = pmove->PM_PlayerTraceEx (start, dest, PM_NORMAL, PM_Ignore ); + if (!trace.startsolid && !trace.allsolid) // FIXME: check steep slope? + { // walked up the step, so just keep result and exit + VectorCopy (trace.endpos, pmove->origin); + return; + } + + // Try moving straight along out normal path. + PM_FlyMove (); +} + + +/* +=================== +PM_AirMove + +=================== +*/ +void PM_AirMove (void) +{ + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + + // Copy movement amounts + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + // Zero out z components of movement vectors + pmove->forward[2] = 0; + pmove->right[2] = 0; + // Renormalize + VectorNormalize (pmove->forward); + VectorNormalize (pmove->right); + + // Determine x and y parts of velocity + for (i=0 ; i<2 ; i++) + { + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + } + // Zero out z part of velocity + wishvel[2] = 0; + + // Determine maginitude of speed of move + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + // Clamp to server defined max speed + if (wishspeed > pmove->maxspeed) + { + VectorScale (wishvel, pmove->maxspeed/wishspeed, wishvel); + wishspeed = pmove->maxspeed; + } + + PM_AirAccelerate (wishdir, wishspeed, pmove->movevars->airaccelerate); + + // Add in any base velocity to the current velocity. + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity ); + + PM_FlyMove (); +} + +qboolean PM_InWater( void ) +{ + return ( pmove->waterlevel > 1 ); +} + +/* +============= +PM_CheckWater + +Sets pmove->waterlevel and pmove->watertype values. +============= +*/ +qboolean PM_CheckWater () +{ + vec3_t point; + int cont; + int truecont; + float height; + float heightover2; + + // Pick a spot just above the players feet. + point[0] = pmove->origin[0] + (pmove->player_mins[pmove->usehull][0] + pmove->player_maxs[pmove->usehull][0]) * 0.5; + point[1] = pmove->origin[1] + (pmove->player_mins[pmove->usehull][1] + pmove->player_maxs[pmove->usehull][1]) * 0.5; + point[2] = pmove->origin[2] + pmove->player_mins[pmove->usehull][2] + 1; + + // Assume that we are not in water at all. + pmove->waterlevel = 0; + pmove->watertype = CONTENTS_EMPTY; + + // Grab point contents. + cont = pmove->PM_PointContents (point, &truecont ); + // Are we under water? (not solid and not empty?) + if (cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + { + // Set water type + pmove->watertype = cont; + + // We are at least at level one + pmove->waterlevel = 1; + + height = (pmove->player_mins[pmove->usehull][2] + pmove->player_maxs[pmove->usehull][2]); + heightover2 = height * 0.5; + + // Now check a point that is at the player hull midpoint. + point[2] = pmove->origin[2] + heightover2; + cont = pmove->PM_PointContents (point, NULL ); + // If that point is also under water... + if (cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + { + // Set a higher water level. + pmove->waterlevel = 2; + + // Now check the eye position. (view_ofs is relative to the origin) + point[2] = pmove->origin[2] + pmove->view_ofs[2]; + + cont = pmove->PM_PointContents (point, NULL ); + if (cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + pmove->waterlevel = 3; // In over our eyes + } + + // Adjust velocity based on water current, if any. + if ( ( truecont <= CONTENTS_CURRENT_0 ) && + ( truecont >= CONTENTS_CURRENT_DOWN ) ) + { + // The deeper we are, the stronger the current. + static vec3_t current_table[] = + { + {1, 0, 0}, {0, 1, 0}, {-1, 0, 0}, + {0, -1, 0}, {0, 0, 1}, {0, 0, -1} + }; + + VectorMA (pmove->basevelocity, 50.0*pmove->waterlevel, current_table[CONTENTS_CURRENT_0 - truecont], pmove->basevelocity); + } + } + + return pmove->waterlevel > 1; +} + +/* +============= +PM_CatagorizePosition +============= +*/ +void PM_CatagorizePosition (void) +{ + vec3_t point; + pmtrace_t tr; + +// if the player hull point one unit down is solid, the player +// is on ground + +// see if standing on something solid + + // Doing this before we move may introduce a potential latency in water detection, but + // doing it after can get us stuck on the bottom in water if the amount we move up + // is less than the 1 pixel 'threshold' we're about to snap to. Also, we'll call + // this several times per frame, so we really need to avoid sticking to the bottom of + // water on each call, and the converse case will correct itself if called twice. + PM_CheckWater(); + + point[0] = pmove->origin[0]; + point[1] = pmove->origin[1]; + point[2] = pmove->origin[2] - 2; + + if (pmove->velocity[2] > 180) // Shooting up really fast. Definitely not on ground. + { + pmove->onground = -1; + } + else + { + // Try and move down. + tr = pmove->PM_PlayerTraceEx (pmove->origin, point, PM_NORMAL, PM_Ignore ); + // If we hit a steep plane, we are not on ground + if ( tr.plane.normal[2] < 0.7) + pmove->onground = -1; // too steep + else + pmove->onground = tr.ent; // Otherwise, point to index of ent under us. + + // If we are on something... + if (pmove->onground != -1) + { + // Then we are not in water jump sequence + pmove->waterjumptime = 0; + // If we could make the move, drop us down that 1 pixel + if (pmove->waterlevel < 2 && !tr.startsolid && !tr.allsolid) + VectorCopy (tr.endpos, pmove->origin); + } + + // Standing on an entity other than the world + if (tr.ent > 0) // So signal that we are touching something. + { + PM_AddToTouched(tr, pmove->velocity); + } + } +} + +/* +================= +PM_GetRandomStuckOffsets + +When a player is stuck, it's costly to try and unstick them +Grab a test offset for the player based on a passed in index +================= +*/ +int PM_GetRandomStuckOffsets(int nIndex, int server, vec3_t offset) +{ + // Last time we did a full + int idx; + idx = rgStuckLast[nIndex][server]++; + + VectorCopy(rgv3tStuckTable[idx % 54], offset); + + return (idx % 54); +} + +void PM_ResetStuckOffsets(int nIndex, int server) +{ + rgStuckLast[nIndex][server] = 0; +} + +/* +================= +NudgePosition + +If pmove->origin is in a solid position, +try nudging slightly on all axis to +allow for the cut precision of the net coordinates +================= +*/ +#define PM_CHECKSTUCK_MINTIME 0.05 // Don't check again too quickly. + +int PM_CheckStuck (void) +{ + vec3_t base; + vec3_t offset; + vec3_t test; + int hitent; + int idx; + float fTime; + int i; + pmtrace_t traceresult; + + static float rgStuckCheckTime[MAX_CLIENTS][2]; // Last time we did a full + + // If position is okay, exit + hitent = pmove->PM_TestPlayerPositionEx (pmove->origin, &traceresult, PM_Ignore ); + if (hitent == -1 ) + { + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + return 0; + } + + VectorCopy (pmove->origin, base); + + // + // Deal with precision error in network. + // + if (!pmove->server) + { + // World or BSP model + if ( ( hitent == 0 ) || + ( pmove->physents[hitent].model != NULL ) ) + { + int nReps = 0; + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + do + { + i = PM_GetRandomStuckOffsets(pmove->player_index, pmove->server, offset); + + VectorAdd(base, offset, test); + if (pmove->PM_TestPlayerPositionEx (test, &traceresult, PM_Ignore ) == -1) + { + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + + VectorCopy ( test, pmove->origin ); + return 0; + } + nReps++; + } while (nReps < 54); + } + } + + // Only an issue on the client. + + if (pmove->server) + idx = 0; + else + idx = 1; + + fTime = pmove->Sys_FloatTime(); + // Too soon? + if (rgStuckCheckTime[pmove->player_index][idx] >= + ( fTime - PM_CHECKSTUCK_MINTIME ) ) + { + return 1; + } + rgStuckCheckTime[pmove->player_index][idx] = fTime; + + pmove->PM_StuckTouch( hitent, &traceresult ); + + i = PM_GetRandomStuckOffsets(pmove->player_index, pmove->server, offset); + + VectorAdd(base, offset, test); + if ( ( hitent = pmove->PM_TestPlayerPositionEx ( test, NULL, PM_Ignore ) ) == -1 ) + { + //Con_DPrintf("Nudged\n"); + + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + + if (i >= 27) + VectorCopy ( test, pmove->origin ); + + return 0; + } + + // If player is flailing while stuck in another player ( should never happen ), then see + // if we can't "unstick" them forceably. + if ( pmove->cmd.buttons & ( IN_JUMP | IN_DUCK | IN_ATTACK ) && ( pmove->physents[ hitent ].player != 0 ) ) + { + float x, y, z; + float xystep = 8.0; + float zstep = 18.0; + float xyminmax = xystep; + float zminmax = 4 * zstep; + + for ( z = 0; z <= zminmax; z += zstep ) + { + for ( x = -xyminmax; x <= xyminmax; x += xystep ) + { + for ( y = -xyminmax; y <= xyminmax; y += xystep ) + { + VectorCopy( base, test ); + test[0] += x; + test[1] += y; + test[2] += z; + + if ( pmove->PM_TestPlayerPositionEx ( test, NULL, PM_Ignore ) == -1 ) + { + VectorCopy( test, pmove->origin ); + return 0; + } + } + } + } + } + + //VectorCopy (base, pmove->origin); + + return 1; +} + +#define CHASE_DISTANCE 112 // Desired distance from target +#define CHASE_PADDING 4 // Minimum allowable distance between the view and a solid face + +// Get the origin of the Observer based around the target's position and angles +void GetChaseOrigin( vec3_t targetangles, int iTargetIndex, vec3_t offset, vec3_t *returnvec ) +{ + vec3_t forward; + vec3_t vecEnd; + vec3_t vecStart; + struct pmtrace_s *trace; + physent_t *target; + + target = &(pmove->physents[ iTargetIndex ]); + + // Trace back from the target using the player's view angles + AngleVectors(targetangles, forward, NULL, NULL); + + // Without view_ofs, just guess at adding 28 (standing player) to the origin to get the eye-height + VectorCopy( target->origin, vecStart ); + vecStart[2] += 28; + VectorMA(offset, CHASE_DISTANCE, forward, vecEnd); + VectorSubtract( vecStart, vecEnd, vecEnd ); + + trace = pmove->PM_TraceLine( vecStart, vecEnd, 0, 2, iTargetIndex ); + + // Return the position + VectorMA( trace->endpos, CHASE_PADDING, trace->plane.normal, *returnvec ); + +#ifdef CLIENT_DLL + // pmove->Con_NPrintf( 9, "vecStart %f %f %f.\n", vecStart[0], vecStart[1], vecStart[2] ); + // pmove->Con_NPrintf( 10, " vecEnd %f %f %f.\n", vecEnd[0], vecEnd[1], vecEnd[2] ); + // pmove->Con_NPrintf( 11, " EndPos %f %f %f.\n", trace->endpos[0], trace->endpos[1], trace->endpos[2] ); +#endif +} + +/* +=============== +PM_SpectatorMove +=============== +*/ +void PM_SpectatorMove (void) +{ + float speed, drop, friction, control, newspeed; + //float accel; + float currentspeed, addspeed, accelspeed; + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + +#ifdef CLIENT_DLL + if ( pmove->runfuncs ) + { + // Set spectator flag + iIsSpectator = SPEC_IS_SPECTATOR; + } +#endif + + // Are we locked onto a target? + if ( pmove->iuser2 ) + { + vec3_t vecViewAngle; + vec3_t vecNewOrg; + vec3_t vecOffset; + int i; + + // Find the client this player's targeting + for (i = 0; i < pmove->numphysent; i++) + { + if ( pmove->physents[i].info == pmove->iuser2 ) + break; + } + + if (i == pmove->numphysent) + return; + + VectorCopy( vec3_origin, vecOffset ); + + // Calculate a camera position based upon the target's origin and angles + if (pmove->iuser1 == 1) + { + // Locked onto the target + VectorCopy( pmove->physents[i].angles, vecViewAngle ); + vecViewAngle[0] = 0; + +#ifdef CLIENT_DLL + if ( pmove->runfuncs ) + { + // Force the client to start smoothing both the spectator's origin and angles + iIsSpectator |= (SPEC_SMOOTH_ANGLES | SPEC_SMOOTH_ORIGIN); + } +#endif + } + else + { + // Freelooking around the target + VectorCopy( pmove->angles, vecViewAngle ); + } + + GetChaseOrigin( vecViewAngle, i, vecOffset, &vecNewOrg); + VectorCopy( vecNewOrg, pmove->origin ); + VectorCopy( vecViewAngle, pmove->angles ); + VectorCopy( vec3_origin, pmove->velocity ); + +#ifdef CLIENT_DLL + if ( pmove->runfuncs ) + { + // Copy the desired angles into the client global var so we can force them to the player's view + VectorCopy( pmove->angles, vecNewViewAngles ); + iHasNewViewAngles = true; + VectorCopy( pmove->origin, vecNewViewOrigin ); + iHasNewViewOrigin = true; + } +#endif + } + else + { + // Move around in normal spectator method + // friction + speed = Length (pmove->velocity); + if (speed < 1) + { + VectorCopy (vec3_origin, pmove->velocity) + } + else + { + drop = 0; + + friction = pmove->movevars->friction*1.5; // extra friction + control = speed < pmove->movevars->stopspeed ? pmove->movevars->stopspeed : speed; + drop += control*friction*pmove->frametime; + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + VectorScale (pmove->velocity, newspeed, pmove->velocity); + } + + // accelerate + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + VectorNormalize (pmove->forward); + VectorNormalize (pmove->right); + + for (i=0 ; i<3 ; i++) + { + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + } + wishvel[2] += pmove->cmd.upmove; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + // + // clamp to server defined max speed + // + if (wishspeed > pmove->movevars->spectatormaxspeed) + { + VectorScale (wishvel, pmove->movevars->spectatormaxspeed/wishspeed, wishvel); + wishspeed = pmove->movevars->spectatormaxspeed; + } + + currentspeed = DotProduct(pmove->velocity, wishdir); + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) + return; + accelspeed = pmove->movevars->accelerate*pmove->frametime*wishspeed; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i=0 ; i<3 ; i++) + pmove->velocity[i] += accelspeed*wishdir[i]; + + // move + VectorMA (pmove->origin, pmove->frametime, pmove->velocity, pmove->origin); + } +} + +/* +================== +PM_SplineFraction + +Use for ease-in, ease-out style interpolation (accel/decel) +Used by ducking code. +================== +*/ +float PM_SplineFraction( float value, float scale ) +{ + float valueSquared; + + value = scale * value; + valueSquared = value * value; + + // Nice little ease-in, ease-out spline-like curve + return 3 * valueSquared - 2 * valueSquared * value; +} + +void PM_FixPlayerCrouchStuck( int direction ) +{ + int hitent; + int i; + vec3_t test; + + hitent = pmove->PM_TestPlayerPositionEx ( pmove->origin, NULL, PM_Ignore ); + if (hitent == -1 ) + return; + + VectorCopy( pmove->origin, test ); + for ( i = 0; i < 36; i++ ) + { + pmove->origin[2] += direction; + hitent = pmove->PM_TestPlayerPositionEx ( pmove->origin, NULL, PM_Ignore ); + if (hitent == -1 ) + return; + } + + VectorCopy( test, pmove->origin ); // Failed +} + +void PM_Duck( void ) +{ + int i; + float time; + float duckFraction; + + int buttonsChanged = ( pmove->oldbuttons ^ pmove->cmd.buttons ); // These buttons have changed this frame + int nButtonPressed = buttonsChanged & pmove->cmd.buttons; // The changed ones still down are "pressed" + + int duckchange = buttonsChanged & IN_DUCK ? 1 : 0; + int duckpressed = nButtonPressed & IN_DUCK ? 1 : 0; + + if ( pmove->cmd.buttons & IN_DUCK ) + { + pmove->oldbuttons |= IN_DUCK; + } + else + { + pmove->oldbuttons &= ~IN_DUCK; + } + + // Discwar: Prevent ducking + return; + + if ( pmove->dead ) + return; + + if ( ( pmove->cmd.buttons & IN_DUCK ) || ( pmove->bInDuck ) || ( pmove->flags & FL_DUCKING ) ) + { + pmove->cmd.forwardmove *= PLAYER_DUCKING_MULTIPLIER; + pmove->cmd.sidemove *= PLAYER_DUCKING_MULTIPLIER; + pmove->cmd.upmove *= PLAYER_DUCKING_MULTIPLIER; + + if ( pmove->cmd.buttons & IN_DUCK ) + { + if ( (nButtonPressed & IN_DUCK ) && !( pmove->flags & FL_DUCKING ) ) + { + // Use 1 second so super long jump will work + pmove->flDuckTime = 1000; + pmove->bInDuck = true; + } + + time = max( 0.0, ( 1.0 - (float)pmove->flDuckTime / 1000.0 ) ); + + if ( pmove->bInDuck ) + { + // Finish ducking immediately if duck time is over or not on ground + if ( ( (float)pmove->flDuckTime / 1000.0 <= ( 1.0 - TIME_TO_DUCK ) ) || + ( pmove->onground == -1 ) ) + { + pmove->usehull = 1; + pmove->view_ofs[2] = VEC_DUCK_VIEW; + pmove->flags |= FL_DUCKING; + pmove->bInDuck = false; + + // HACKHACK - Fudge for collision bug - no time to fix this properly + if ( pmove->onground != -1 ) + { + for ( i = 0; i < 3; i++ ) + { + pmove->origin[i] -= ( pmove->player_mins[1][i] - pmove->player_mins[0][i] ); + } + // See if we are stuck? + PM_FixPlayerCrouchStuck( STUCK_MOVEUP ); + + // Recatagorize position since ducking can change origin + PM_CatagorizePosition(); + } + } + else + { + float fMore = (VEC_DUCK_HULL_MIN - VEC_HULL_MIN); + + // Calc parametric time + duckFraction = PM_SplineFraction( time, (1.0/TIME_TO_DUCK) ); + pmove->view_ofs[2] = ((VEC_DUCK_VIEW - fMore ) * duckFraction) + (VEC_VIEW * (1-duckFraction)); + } + } + } + else + { + pmtrace_t trace; + vec3_t newOrigin; + + VectorCopy( pmove->origin, newOrigin ); + + if ( pmove->onground != -1 ) + { + for ( i = 0; i < 3; i++ ) + { + newOrigin[i] += ( pmove->player_mins[1][i] - pmove->player_mins[0][i] ); + } + } + + trace = pmove->PM_PlayerTraceEx ( newOrigin, newOrigin, PM_NORMAL, PM_Ignore ); + + if ( !trace.startsolid ) + { + pmove->usehull = 0; + + // Oh, no, changing hulls stuck us into something, try unsticking downward first. + trace = pmove->PM_PlayerTraceEx( newOrigin, newOrigin, PM_NORMAL, PM_Ignore ); + if ( trace.startsolid ) + { + // See if we are stuck? If so, stay ducked with the duck hull until we have a clear spot + //Con_Printf( "unstick got stuck\n" ); + pmove->usehull = 1; + return; + } + + pmove->flags &= ~FL_DUCKING; + pmove->bInDuck = false; + pmove->view_ofs[2] = VEC_VIEW; + pmove->flDuckTime = 0; + + VectorCopy( newOrigin, pmove->origin ); + + // Recatagorize position since ducking can change origin + PM_CatagorizePosition(); + } + } + } +} + +void PM_LadderMove( physent_t *pLadder ) +{ + vec3_t ladderCenter; + trace_t trace; + qboolean onFloor; + vec3_t floor; + vec3_t modelmins, modelmaxs; + + if ( pmove->movetype == MOVETYPE_NOCLIP ) + return; + + pmove->PM_GetModelBounds( pLadder->model, modelmins, modelmaxs ); + + VectorAdd( modelmins, modelmaxs, ladderCenter ); + VectorScale( ladderCenter, 0.5, ladderCenter ); + + pmove->movetype = MOVETYPE_FLY; + + // On ladder, convert movement to be relative to the ladder + + VectorCopy( pmove->origin, floor ); + floor[2] += pmove->player_mins[pmove->usehull][2] - 1; + + if ( pmove->PM_PointContents( floor, NULL ) == CONTENTS_SOLID ) + onFloor = true; + else + onFloor = false; + + pmove->gravity = 0; + pmove->PM_TraceModel( pLadder, pmove->origin, ladderCenter, &trace ); + if ( trace.fraction != 1.0 ) + { + float forward = 0, right = 0; + vec3_t vpn, v_right; + float flSpeed = MAX_CLIMB_SPEED; + + // they shouldn't be able to move faster than their maxspeed + if ( flSpeed > pmove->maxspeed ) + { + flSpeed = pmove->maxspeed; + } + + AngleVectors( pmove->angles, vpn, v_right, NULL ); + + if ( pmove->flags & FL_DUCKING ) + { + flSpeed *= PLAYER_DUCKING_MULTIPLIER; + } + + if ( pmove->cmd.buttons & IN_BACK ) + { + forward -= flSpeed; + } + if ( pmove->cmd.buttons & IN_FORWARD ) + { + forward += flSpeed; + } + if ( pmove->cmd.buttons & IN_MOVELEFT ) + { + right -= flSpeed; + } + if ( pmove->cmd.buttons & IN_MOVERIGHT ) + { + right += flSpeed; + } + + if ( pmove->cmd.buttons & IN_JUMP ) + { + pmove->movetype = MOVETYPE_WALK; + VectorScale( trace.plane.normal, 270, pmove->velocity ); + } + else + { + if ( forward != 0 || right != 0 ) + { + vec3_t velocity, perp, cross, lateral, tmp; + float normal; + + //ALERT(at_console, "pev %.2f %.2f %.2f - ", + // pev->velocity.x, pev->velocity.y, pev->velocity.z); + // Calculate player's intended velocity + //Vector velocity = (forward * gpGlobals->v_forward) + (right * gpGlobals->v_right); + VectorScale( vpn, forward, velocity ); + VectorMA( velocity, right, v_right, velocity ); + + + // Perpendicular in the ladder plane + // Vector perp = CrossProduct( Vector(0,0,1), trace.vecPlaneNormal ); + // perp = perp.Normalize(); + VectorClear( tmp ); + tmp[2] = 1; + CrossProduct( tmp, trace.plane.normal, perp ); + VectorNormalize( perp ); + + + // decompose velocity into ladder plane + normal = DotProduct( velocity, trace.plane.normal ); + // This is the velocity into the face of the ladder + VectorScale( trace.plane.normal, normal, cross ); + + + // This is the player's additional velocity + VectorSubtract( velocity, cross, lateral ); + + // This turns the velocity into the face of the ladder into velocity that + // is roughly vertically perpendicular to the face of the ladder. + // NOTE: It IS possible to face up and move down or face down and move up + // because the velocity is a sum of the directional velocity and the converted + // velocity through the face of the ladder -- by design. + CrossProduct( trace.plane.normal, perp, tmp ); + VectorMA( lateral, -normal, tmp, pmove->velocity ); + if ( onFloor && normal > 0 ) // On ground moving away from the ladder + { + VectorMA( pmove->velocity, MAX_CLIMB_SPEED, trace.plane.normal, pmove->velocity ); + } + //pev->velocity = lateral - (CrossProduct( trace.vecPlaneNormal, perp ) * normal); + } + else + { + VectorClear( pmove->velocity ); + } + } + } +} + +physent_t *PM_Ladder( void ) +{ + int i; + physent_t *pe; + hull_t *hull; + int num; + vec3_t test; + + for ( i = 0; i < pmove->nummoveent; i++ ) + { + pe = &pmove->moveents[i]; + + if ( pe->model && (modtype_t)pmove->PM_GetModelType( pe->model ) == mod_brush && pe->skin == CONTENTS_LADDER ) + { + + hull = (hull_t *)pmove->PM_HullForBsp( pe, test ); + num = hull->firstclipnode; + + // Offset the test point appropriately for this hull. + VectorSubtract ( pmove->origin, test, test); + + // Test the player's hull for intersection with this model + if ( pmove->PM_HullPointContents (hull, num, test) == CONTENTS_EMPTY) + continue; + + return pe; + } + } + + return NULL; +} + + + +void PM_WaterJump (void) +{ + if ( pmove->waterjumptime > 10000 ) + { + pmove->waterjumptime = 10000; + } + + if ( !pmove->waterjumptime ) + return; + + pmove->waterjumptime -= pmove->cmd.msec; + if ( pmove->waterjumptime < 0 || + !pmove->waterlevel ) + { + pmove->waterjumptime = 0; + pmove->flags &= ~FL_WATERJUMP; + } + + pmove->velocity[0] = pmove->movedir[0]; + pmove->velocity[1] = pmove->movedir[1]; +} + +/* +============ +PM_AddGravity + +============ +*/ +void PM_AddGravity () +{ + float ent_gravity; + + if (pmove->gravity) + ent_gravity = pmove->gravity; + else + ent_gravity = 1.0; + + // Add gravity incorrectly + pmove->velocity[2] -= (ent_gravity * pmove->movevars->gravity * pmove->frametime ); + pmove->velocity[2] += pmove->basevelocity[2] * pmove->frametime; + pmove->basevelocity[2] = 0; + PM_CheckVelocity(); +} +/* +============ +PM_PushEntity + +Does not change the entities velocity at all +============ +*/ +pmtrace_t PM_PushEntity (vec3_t push) +{ + pmtrace_t trace; + vec3_t end; + + VectorAdd (pmove->origin, push, end); + + trace = pmove->PM_PlayerTraceEx (pmove->origin, end, PM_NORMAL, PM_Ignore ); + + VectorCopy (trace.endpos, pmove->origin); + + // So we can run impact function afterwards. + if (trace.fraction < 1.0 && + !trace.allsolid) + { + PM_AddToTouched(trace, pmove->velocity); + } + + return trace; +} + +/* +============ +PM_Physics_Toss() + +Dead player flying through air., e.g. +============ +*/ +void PM_Physics_Toss() +{ + pmtrace_t trace; + vec3_t move; + float backoff; + + PM_CheckWater(); + + if (pmove->velocity[2] > 0) + pmove->onground = -1; + + // If on ground and not moving, return. + if ( pmove->onground != -1 ) + { + if (VectorCompare(pmove->basevelocity, vec3_origin) && + VectorCompare(pmove->velocity, vec3_origin)) + return; + } + + PM_CheckVelocity (); + +// add gravity + if ( pmove->movetype != MOVETYPE_FLY && + pmove->movetype != MOVETYPE_BOUNCEMISSILE && + pmove->movetype != MOVETYPE_FLYMISSILE ) + PM_AddGravity (); + +// move origin + // Base velocity is not properly accounted for since this entity will move again after the bounce without + // taking it into account + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity); + + PM_CheckVelocity(); + VectorScale (pmove->velocity, pmove->frametime, move); + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity); + + trace = PM_PushEntity (move); // Should this clear basevelocity + + PM_CheckVelocity(); + + if (trace.allsolid) + { + // entity is trapped in another solid + pmove->onground = trace.ent; + VectorCopy (vec3_origin, pmove->velocity); + return; + } + + if (trace.fraction == 1) + { + PM_CheckWater(); + return; + } + + + if (pmove->movetype == MOVETYPE_BOUNCE) + backoff = 2.0 - pmove->friction; + else if (pmove->movetype == MOVETYPE_BOUNCEMISSILE) + backoff = 2.0; + else + backoff = 1; + + PM_ClipVelocity (pmove->velocity, trace.plane.normal, pmove->velocity, backoff); + + // stop if on ground + if (trace.plane.normal[2] > 0.7) + { + float vel; + vec3_t base; + + VectorClear( base ); + if (pmove->velocity[2] < pmove->movevars->gravity * pmove->frametime) + { + // we're rolling on the ground, add static friction. + pmove->onground = trace.ent; + pmove->velocity[2] = 0; + } + + vel = DotProduct( pmove->velocity, pmove->velocity ); + + // Con_DPrintf("%f %f: %.0f %.0f %.0f\n", vel, trace.fraction, ent->velocity[0], ent->velocity[1], ent->velocity[2] ); + + if (vel < (30 * 30) || (pmove->movetype != MOVETYPE_BOUNCE && pmove->movetype != MOVETYPE_BOUNCEMISSILE)) + { + pmove->onground = trace.ent; + VectorCopy (vec3_origin, pmove->velocity); + } + else + { + VectorScale (pmove->velocity, (1.0 - trace.fraction) * pmove->frametime * 0.9, move); + trace = PM_PushEntity (move); + } + VectorSubtract( pmove->velocity, base, pmove->velocity ) + } + +// check for in water + PM_CheckWater(); +} + +/* +==================== +PM_NoClip + +==================== +*/ +void PM_NoClip() +{ + int i; + vec3_t wishvel; + float fmove, smove; +// float currentspeed, addspeed, accelspeed; + + // Copy movement amounts + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + VectorNormalize ( pmove->forward ); + VectorNormalize ( pmove->right ); + + for (i=0 ; i<3 ; i++) // Determine x and y parts of velocity + { + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + } + wishvel[2] += pmove->cmd.upmove; + + VectorMA (pmove->origin, pmove->frametime, wishvel, pmove->origin); + + // Zero out the velocity so that we don't accumulate a huge downward velocity from + // gravity, etc. + VectorClear( pmove->velocity ); + +} + +/* +============= +PM_Jump +============= +*/ +void PM_Jump (void) +{ + int i; + qboolean tfc = false; + + qboolean cansuperjump = false; + + // Discwar: Prevent jumping + return; + + if (pmove->dead) + { + pmove->oldbuttons |= IN_JUMP ; // don't jump again until released + return; + } + + tfc = atoi( pmove->PM_Info_ValueForKey( pmove->physinfo, "tfc" ) ) == 1 ? true : false; + + // Spy that's feigning death cannot jump + if ( tfc && + ( pmove->deadflag == ( DEAD_DISCARDBODY + 1 ) ) ) + { + return; + } + + // See if we are waterjumping. If so, decrement count and return. + if ( pmove->waterjumptime ) + { + pmove->waterjumptime -= pmove->cmd.msec; + if (pmove->waterjumptime < 0) + { + pmove->waterjumptime = 0; + } + return; + } + + // If we are in the water most of the way... + if (pmove->waterlevel >= 2) + { // swimming, not jumping + pmove->onground = -1; + + if (pmove->watertype == CONTENTS_WATER) // We move up a certain amount + pmove->velocity[2] = 100; + else if (pmove->watertype == CONTENTS_SLIME) + pmove->velocity[2] = 80; + else // LAVA + pmove->velocity[2] = 50; + + // play swiming sound + if ( pmove->flSwimTime <= 0 ) + { + // Don't play sound again for 1 second + pmove->flSwimTime = 1000; + switch ( pmove->RandomLong( 0, 3 ) ) + { + case 0: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 1: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 2: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 3: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + } + } + + return; + } + + // No more effect + if ( pmove->onground == -1 ) + { + // Flag that we jumped. + // HACK HACK HACK + // Remove this when the game .dll no longer does physics code!!!! + pmove->oldbuttons |= IN_JUMP; // don't jump again until released + return; // in air, so no effect + } + + if ( pmove->oldbuttons & IN_JUMP ) + return; // don't pogo stick + + // In the air now. + pmove->onground = -1; + + if ( tfc ) + { + pmove->PM_PlaySound( CHAN_BODY, "player/plyrjmp8.wav", 0.5, ATTN_NORM, 0, PITCH_NORM ); + } + else + { + PM_PlayStepSound( PM_MapTextureTypeStepType( pmove->chtexturetype ), 1.0 ); + } + + // See if user can super long jump? + cansuperjump = atoi( pmove->PM_Info_ValueForKey( pmove->physinfo, "slj" ) ) == 1 ? true : false; + + // Acclerate upward + // If we are ducking... + if ( ( pmove->bInDuck ) || ( pmove->flags & FL_DUCKING ) ) + { + // Adjust for super long jump module + // UNDONE -- note this should be based on forward angles, not current velocity. + if ( cansuperjump && + ( pmove->cmd.buttons & IN_DUCK ) && + ( pmove->flDuckTime > 0 ) && + Length( pmove->velocity ) > 50 ) + { + pmove->punchangle[0] = -5; + + for (i =0; i < 2; i++) + { + pmove->velocity[i] = pmove->forward[i] * PLAYER_LONGJUMP_SPEED * 1.6; + } + + pmove->velocity[2] = sqrt(2 * 800 * 56.0); + } + else + { + pmove->velocity[2] = sqrt(2 * 800 * 45.0); + } + } + else + { + pmove->velocity[2] = sqrt(2 * 800 * 45.0); + } + + // Decay it for simulation + PM_FixupGravityVelocity(); + + // Flag that we jumped. + pmove->oldbuttons |= IN_JUMP; // don't jump again until released +} + +/* +============= +PM_CheckWaterJump +============= +*/ +#define WJ_HEIGHT 8 +void PM_CheckWaterJump (void) +{ + vec3_t vecStart, vecEnd; + vec3_t flatforward; + vec3_t flatvelocity; + float curspeed; + pmtrace_t tr; + int savehull; + + // Already water jumping. + if ( pmove->waterjumptime ) + return; + + // Don't hop out if we just jumped in + if ( pmove->velocity[2] < -180 ) + return; // only hop out if we are moving up + + // See if we are backing up + flatvelocity[0] = pmove->velocity[0]; + flatvelocity[1] = pmove->velocity[1]; + flatvelocity[2] = 0; + + // Must be moving + curspeed = VectorNormalize( flatvelocity ); + + // see if near an edge + flatforward[0] = pmove->forward[0]; + flatforward[1] = pmove->forward[1]; + flatforward[2] = 0; + VectorNormalize (flatforward); + + // Are we backing into water from steps or something? If so, don't pop forward + if ( curspeed != 0.0 && ( DotProduct( flatvelocity, flatforward ) < 0.0 ) ) + return; + + VectorCopy( pmove->origin, vecStart ); + vecStart[2] += WJ_HEIGHT; + + VectorMA ( vecStart, 24, flatforward, vecEnd ); + + // Trace, this trace should use the point sized collision hull + savehull = pmove->usehull; + pmove->usehull = 2; + tr = pmove->PM_PlayerTraceEx( vecStart, vecEnd, PM_NORMAL, PM_Ignore ); + if ( tr.fraction < 1.0 && fabs( tr.plane.normal[2] ) < 0.1f ) // Facing a near vertical wall? + { + vecStart[2] += pmove->player_maxs[ savehull ][2] - WJ_HEIGHT; + VectorMA( vecStart, 24, flatforward, vecEnd ); + VectorMA( vec3_origin, -50, tr.plane.normal, pmove->movedir ); + + tr = pmove->PM_PlayerTraceEx( vecStart, vecEnd, PM_NORMAL, PM_Ignore ); + if ( tr.fraction == 1.0 ) + { + pmove->waterjumptime = 2000; + pmove->velocity[2] = 225; + pmove->oldbuttons |= IN_JUMP; + pmove->flags |= FL_WATERJUMP; + } + } + + // Reset the collision hull + pmove->usehull = savehull; +} + +void PM_CheckFalling( void ) +{ + if ( pmove->onground != -1 && + !pmove->dead && + pmove->flFallVelocity >= PLAYER_FALL_PUNCH_THRESHHOLD ) + { + float fvol = 0.5; + + if ( pmove->waterlevel > 0 ) + { + } + else if ( pmove->flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED ) + { + // NOTE: In the original game dll , there were no breaks after these cases, causing the first one to + // cascade into the second + //switch ( RandomLong(0,1) ) + //{ + //case 0: + //pmove->PM_PlaySound( CHAN_VOICE, "player/pl_fallpain2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + //break; + //case 1: + pmove->PM_PlaySound( CHAN_VOICE, "player/pl_fallpain3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + // break; + //} + fvol = 1.0; + } + else if ( pmove->flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED / 2 ) + { + qboolean tfc = false; + tfc = atoi( pmove->PM_Info_ValueForKey( pmove->physinfo, "tfc" ) ) == 1 ? true : false; + + if ( tfc ) + { + pmove->PM_PlaySound( CHAN_VOICE, "player/pl_fallpain3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + } + + fvol = 0.85; + } + else if ( pmove->flFallVelocity < PLAYER_MIN_BOUNCE_SPEED ) + { + fvol = 0; + } + + if ( fvol > 0.0 ) + { + // Play landing step right away + pmove->flTimeStepSound = 0; + + PM_UpdateStepSound(); + + // play step sound for current texture + PM_PlayStepSound( PM_MapTextureTypeStepType( pmove->chtexturetype ), fvol ); + + // Knock the screen around a little bit, temporary effect + //pmove->punchangle[ 2 ] = pmove->flFallVelocity * 0.013; // punch z axis + + if ( pmove->punchangle[ 0 ] > 8 ) + { + pmove->punchangle[ 0 ] = 8; + } + } + } + + if ( pmove->onground != -1 ) + { + pmove->flFallVelocity = 0; + } +} + +/* +================= +PM_PlayWaterSounds + +================= +*/ +void PM_PlayWaterSounds( void ) +{ + // Did we enter or leave water? + if ( ( pmove->oldwaterlevel == 0 && pmove->waterlevel != 0 ) || + ( pmove->oldwaterlevel != 0 && pmove->waterlevel == 0 ) ) + { + switch ( pmove->RandomLong(0,3) ) + { + case 0: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 1: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 2: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 3: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + } + } +} + +/* +=============== +PM_CalcRoll + +=============== +*/ +float PM_CalcRoll (vec3_t angles, vec3_t velocity, float rollangle, float rollspeed ) +{ + float sign; + float side; + float value; + vec3_t forward, right, up; + + AngleVectors (angles, forward, right, up); + + side = DotProduct (velocity, right); + + sign = side < 0 ? -1 : 1; + + side = fabs(side); + + value = rollangle; + + if (side < rollspeed) + { + side = side * value / rollspeed; + } + else + { + side = value; + } + + return side * sign; +} + +/* +============= +PM_DropPunchAngle + +============= +*/ +void PM_DropPunchAngle ( vec3_t punchangle ) +{ + float len; + + len = VectorNormalize ( punchangle ); + len -= (10.0 + len * 0.5) * pmove->frametime; + len = max( len, 0.0 ); + VectorScale ( punchangle, len, punchangle); +} + +/* +============== +PM_CheckParamters + +============== +*/ +void PM_CheckParamters( void ) +{ + float spd; + float maxspeed; + vec3_t v_angle; + + spd = ( pmove->cmd.forwardmove * pmove->cmd.forwardmove ) + + ( pmove->cmd.sidemove * pmove->cmd.sidemove ) + + ( pmove->cmd.upmove * pmove->cmd.upmove ); + spd = sqrt( spd ); + + maxspeed = pmove->clientmaxspeed; //atof( pmove->PM_Info_ValueForKey( pmove->physinfo, "maxspd" ) ); + if ( maxspeed != 0.0 ) + { + pmove->maxspeed = min( maxspeed, pmove->maxspeed ); + } + + if ( ( spd != 0.0 ) && + ( spd > pmove->maxspeed ) ) + { + float fRatio = pmove->maxspeed / spd; + pmove->cmd.forwardmove *= fRatio; + pmove->cmd.sidemove *= fRatio; + pmove->cmd.upmove *= fRatio; + } + + if ( pmove->flags & FL_FROZEN || + pmove->flags & FL_ONTRAIN || + pmove->dead ) + { + pmove->cmd.forwardmove = 0; + pmove->cmd.sidemove = 0; + pmove->cmd.upmove = 0; + } + + + PM_DropPunchAngle( pmove->punchangle ); + + // Take angles from command. + if ( !pmove->dead ) + { + VectorCopy ( pmove->cmd.viewangles, v_angle ); + VectorAdd( v_angle, pmove->punchangle, v_angle ); + + // Set up view angles. + pmove->angles[ROLL] = PM_CalcRoll ( v_angle, pmove->velocity, pmove->movevars->rollangle, pmove->movevars->rollspeed )*4; + pmove->angles[PITCH] = v_angle[PITCH]; + pmove->angles[YAW] = v_angle[YAW]; + } + else + { + VectorCopy( pmove->oldangles, pmove->angles ); + } + + // Set dead player view_offset + if ( pmove->dead ) + { + pmove->view_ofs[2] = PM_DEAD_VIEWHEIGHT; + } + + // Adjust client view angles to match values used on server. + if (pmove->angles[YAW] > 180.0f) + { + pmove->angles[YAW] -= 360.0f; + } + +} + +void PM_ReduceTimers( void ) +{ + if ( pmove->flTimeStepSound > 0 ) + { + pmove->flTimeStepSound -= pmove->cmd.msec; + if ( pmove->flTimeStepSound < 0 ) + { + pmove->flTimeStepSound = 0; + } + } + if ( pmove->flDuckTime > 0 ) + { + pmove->flDuckTime -= pmove->cmd.msec; + if ( pmove->flDuckTime < 0 ) + { + pmove->flDuckTime = 0; + } + } + if ( pmove->flSwimTime > 0 ) + { + pmove->flSwimTime -= pmove->cmd.msec; + if ( pmove->flSwimTime < 0 ) + { + pmove->flSwimTime = 0; + } + } +} + +/* +============= +PlayerMove + +Returns with origin, angles, and velocity modified in place. + +Numtouch and touchindex[] will be set if any of the physents +were contacted during the move. +============= +*/ +void PM_PlayerMove ( qboolean server ) +{ + physent_t *pLadder = NULL; + + // Are we running server code? + pmove->server = server; + + // Adjust speeds etc. + PM_CheckParamters(); + + // Assume we don't touch anything + pmove->numtouch = 0; + + // # of msec to apply movement + pmove->frametime = pmove->cmd.msec * 0.001; + + PM_ReduceTimers(); + + // Convert view angles to vectors + AngleVectors (pmove->angles, pmove->forward, pmove->right, pmove->up); + + // PM_ShowClipBox(); + +#ifdef CLIENT_DLL + if ( pmove->runfuncs ) + { + iIsSpectator = false; + iHasNewViewAngles = false; + iHasNewViewOrigin = false; + } +#endif + + if ( pmove->iuser1 == 4 ) + return; + + // Special handling for spectator and observers. (iuser1 is set if the player's in observer mode) + if ( pmove->spectator || pmove->iuser1 > 0 ) + { + PM_SpectatorMove(); + PM_CatagorizePosition(); + return; + } + + // Always try and unstick us unless we are in NOCLIP mode + if ( pmove->movetype != MOVETYPE_NOCLIP && pmove->movetype != MOVETYPE_NONE ) + { + if ( PM_CheckStuck() ) + { + return; // Can't move, we're stuck + } + } + + // Now that we are "unstuck", see where we are ( waterlevel and type, pmove->onground ). + PM_CatagorizePosition(); + + // Store off the starting water level + pmove->oldwaterlevel = pmove->waterlevel; + + // If we are not on ground, store off how fast we are moving down + if ( pmove->onground == -1 ) + { + pmove->flFallVelocity = -pmove->velocity[2]; + } + + g_onladder = 0; + // Don't run ladder code if dead or on a train + if ( !pmove->dead && !(pmove->flags & FL_ONTRAIN) ) + { + pLadder = PM_Ladder(); + if ( pLadder ) + { + g_onladder = 1; + } + } + + PM_UpdateStepSound(); + + PM_Duck(); + + // Don't run ladder code if dead or on a train + if ( !pmove->dead && !(pmove->flags & FL_ONTRAIN) ) + { + if ( pLadder ) + { + PM_LadderMove( pLadder ); + } + else if ( pmove->movetype != MOVETYPE_WALK && + pmove->movetype != MOVETYPE_NOCLIP ) + { + // Clear ladder stuff unless player is noclipping + // it will be set immediately again next frame if necessary + pmove->movetype = MOVETYPE_WALK; + } + } + + // Slow down, I'm pulling it! (a box maybe) but only when I'm standing on ground + if ( ( pmove->onground != -1 ) && ( pmove->cmd.buttons & IN_USE) ) + { + VectorScale( pmove->velocity, 0.3, pmove->velocity ); + } + + // Handle movement + switch ( pmove->movetype ) + { + default: + pmove->Con_DPrintf("Bogus pmove player movetype %i on (%i) 0=cl 1=sv\n", pmove->movetype, pmove->server); + break; + + case MOVETYPE_NONE: + break; + + case MOVETYPE_NOCLIP: + PM_NoClip(); + break; + + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + PM_Physics_Toss(); + break; + + case MOVETYPE_FLY: + + PM_CheckWater(); + + // Was jump button pressed? + // If so, set velocity to 270 away from ladder. This is currently wrong. + // Also, set MOVE_TYPE to walk, too. + if ( pmove->cmd.buttons & IN_JUMP ) + { + if ( !pLadder ) + { + PM_Jump (); + } + } + else + { + pmove->oldbuttons &= ~IN_JUMP; + } + + // Perform the move accounting for any base velocity. + VectorAdd (pmove->velocity, pmove->basevelocity, pmove->velocity); + PM_FlyMove (); + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity); + break; + + case MOVETYPE_WALK: + if ( !PM_InWater() ) + { + PM_AddCorrectGravity(); + } + + // If we are leaping out of the water, just update the counters. + if ( pmove->waterjumptime ) + { + PM_WaterJump(); + PM_FlyMove(); + + // Make sure waterlevel is set correctly + PM_CheckWater(); + return; + } + + // If we are swimming in the water, see if we are nudging against a place we can jump up out + // of, and, if so, start out jump. Otherwise, if we are not moving up, then reset jump timer to 0 + if ( pmove->waterlevel >= 2 ) + { + if ( pmove->waterlevel == 2 ) + { + PM_CheckWaterJump(); + } + + // If we are falling again, then we must not trying to jump out of water any more. + if ( pmove->velocity[2] < 0 && pmove->waterjumptime ) + { + pmove->waterjumptime = 0; + } + + // Was jump button pressed? + if (pmove->cmd.buttons & IN_JUMP) + { + PM_Jump (); + } + else + { + pmove->oldbuttons &= ~IN_JUMP; + } + + // Perform regular water movement + PM_WaterMove(); + + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity); + + // Get a final position + PM_CatagorizePosition(); + } + else + + // Not underwater + { + // Was jump button pressed? + if ( pmove->cmd.buttons & IN_JUMP ) + { + if ( !pLadder ) + { + PM_Jump (); + } + } + else + { + pmove->oldbuttons &= ~IN_JUMP; + } + + // Fricion is handled before we add in any base velocity. That way, if we are on a conveyor, + // we don't slow when standing still, relative to the conveyor. + if ( pmove->onground != -1 ) + { + pmove->velocity[2] = 0.0; + PM_Friction(); + } + + // Make sure velocity is valid. + PM_CheckVelocity(); + + // Are we on ground now + if ( pmove->onground != -1 ) + { + PM_WalkMove(); + } + else + { + PM_AirMove(); // Take into account movement when in air. + } + + // Set final flags. + PM_CatagorizePosition(); + + // Now pull the base velocity back out. + // Base velocity is set if you are on a moving object, like + // a conveyor (or maybe another monster?) + VectorSubtract (pmove->velocity, pmove->basevelocity, pmove->velocity ); + + // Make sure velocity is valid. + PM_CheckVelocity(); + + // Add any remaining gravitational component. + if ( !PM_InWater() ) + { + PM_FixupGravityVelocity(); + } + + // If we are on ground, no downward velocity. + if ( pmove->onground != -1 ) + { + pmove->velocity[2] = 0; + } + + // See if we landed on the ground with enough force to play + // a landing sound. + PM_CheckFalling(); + } + + // Did we enter or leave the water? + PM_PlayWaterSounds(); + break; + } +} + +void PM_CreateStuckTable( void ) +{ + float x, y, z; + int idx; + int i; + float zi[3]; + + memset(rgv3tStuckTable, 0, 54 * sizeof(vec3_t)); + + idx = 0; + // Little Moves. + x = y = 0; + // Z moves + for (z = -0.125 ; z <= 0.125 ; z += 0.125) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + x = z = 0; + // Y moves + for (y = -0.125 ; y <= 0.125 ; y += 0.125) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + y = z = 0; + // X moves + for (x = -0.125 ; x <= 0.125 ; x += 0.125) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + + // Remaining multi axis nudges. + for ( x = - 0.125; x <= 0.125; x += 0.250 ) + { + for ( y = - 0.125; y <= 0.125; y += 0.250 ) + { + for ( z = - 0.125; z <= 0.125; z += 0.250 ) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + } + } + + // Big Moves. + x = y = 0; + zi[0] = 0.0f; + zi[1] = 1.0f; + zi[2] = 6.0f; + + for (i = 0; i < 3; i++) + { + // Z moves + z = zi[i]; + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + + x = z = 0; + + // Y moves + for (y = -2.0f ; y <= 2.0f ; y += 2.0) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + y = z = 0; + // X moves + for (x = -2.0f ; x <= 2.0f ; x += 2.0f) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + + // Remaining multi axis nudges. + for (i = 0 ; i < 3; i++) + { + z = zi[i]; + + for (x = -2.0f ; x <= 2.0f ; x += 2.0f) + { + for (y = -2.0f ; y <= 2.0f ; y += 2.0) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + } + } +} + + + +/* +This modume implements the shared player physics code between any particular game and +the engine. The same PM_Move routine is built into the game .dll and the client .dll and is +invoked by each side as appropriate. There should be no distinction, internally, between server +and client. This will ensure that prediction behaves appropriately. +*/ + +void PM_Move ( struct playermove_s *ppmove, int server ) +{ + assert( pm_shared_initialized ); + + pmove = ppmove; + + PM_PlayerMove( ( server != 0 ) ? true : false ); + + if ( pmove->onground != -1 ) + { + pmove->flags |= FL_ONGROUND; + } + else + { + pmove->flags &= ~FL_ONGROUND; + } + + // In single player, reset friction after each movement to FrictionModifier Triggers work still. + if ( !pmove->multiplayer && ( pmove->movetype == MOVETYPE_WALK ) ) + { + pmove->friction = 1.0f; + } +} + +int PM_GetInfo( int ent ) +{ + if ( ent >= 0 && ent <= pmove->numvisent ) + { + return pmove->visents[ ent ].info; + } + return -1; +} + +void PM_Init( struct playermove_s *ppmove ) +{ + assert( !pm_shared_initialized ); + + pmove = ppmove; + + PM_CreateStuckTable(); + PM_InitTextureTypes(); + + pm_shared_initialized = 1; +} diff --git a/ricochet/pm_shared/pm_shared.h b/ricochet/pm_shared/pm_shared.h new file mode 100644 index 0000000..9da8001 --- /dev/null +++ b/ricochet/pm_shared/pm_shared.h @@ -0,0 +1,32 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +// +// pm_shared.h +// +#if !defined( PM_SHAREDH ) +#define PM_SHAREDH +#pragma once + +void PM_Init( struct playermove_s *ppmove ); +void PM_Move ( struct playermove_s *ppmove, int server ); +char PM_FindTextureType( char *name ); + +// Spectator flags +#define SPEC_IS_SPECTATOR (1<<0) +#define SPEC_SMOOTH_ANGLES (1<<1) +#define SPEC_SMOOTH_ORIGIN (1<<2) + +#endif \ No newline at end of file diff --git a/utils/bspinfo/bspinfo.c b/utils/bspinfo/bspinfo.c new file mode 100644 index 0000000..d125c4a --- /dev/null +++ b/utils/bspinfo/bspinfo.c @@ -0,0 +1,48 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "cmdlib.h" +#include "mathlib.h" +#include "bsplib.h" + +void main (int argc, char **argv) +{ + int i; + char source[1024]; + int size; + FILE *f; + + printf( "bspinfo.exe v2.1 (%s)\n", __DATE__ ); + printf ("---- bspinfo ----\n" ); + + + if (argc == 1) + Error ("usage: bspinfo bspfile [bspfiles]"); + + for (i=1 ; i +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=bspinfo - 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 "bspinfo.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 "bspinfo.mak" CFG="bspinfo - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "bspinfo - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "bspinfo - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/SDKSrc/Tools/utils/bspinfo", HUGBAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "bspinfo - 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 Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /GX /O2 /I "..\common" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /YX /FD /c +# 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 /subsystem:console /machine:I386 +# ADD 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 /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "bspinfo - 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 Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /Gm /GX /ZI /Od /I "..\common" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /YX /FD /c +# 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 gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 +# ADD 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 /subsystem:console /debug /machine:I386 + +!ENDIF + +# Begin Target + +# Name "bspinfo - Win32 Release" +# Name "bspinfo - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=..\common\bspfile.c +# End Source File +# Begin Source File + +SOURCE=.\bspinfo.c +# End Source File +# Begin Source File + +SOURCE=..\common\cmdlib.c +# End Source File +# Begin Source File + +SOURCE=..\common\scriplib.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=..\common\bspfile.h +# End Source File +# Begin Source File + +SOURCE=..\common\cmdlib.h +# End Source File +# Begin Source File + +SOURCE=..\common\scriplib.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/utils/bspinfo/bspinfo.dsw b/utils/bspinfo/bspinfo.dsw new file mode 100644 index 0000000..f6b6ea4 --- /dev/null +++ b/utils/bspinfo/bspinfo.dsw @@ -0,0 +1,37 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "bspinfo"=.\bspinfo.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/SDKSrc/Tools/utils/bspinfo", HUGBAAAA + . + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ + begin source code control + "$/SDKSrc/Tools/utils/bspinfo", HUGBAAAA + . + end source code control +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/utils/common/bspfile.c b/utils/common/bspfile.c new file mode 100644 index 0000000..688f10d --- /dev/null +++ b/utils/common/bspfile.c @@ -0,0 +1,709 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "cmdlib.h" +#include "mathlib.h" +#include "bspfile.h" +#include "scriplib.h" + +//============================================================================= + +int nummodels; +dmodel_t dmodels[MAX_MAP_MODELS]; +int dmodels_checksum; + +int visdatasize; +byte dvisdata[MAX_MAP_VISIBILITY]; +int dvisdata_checksum; + +int lightdatasize; +byte dlightdata[MAX_MAP_LIGHTING]; +int dlightdata_checksum; + +int texdatasize; +byte dtexdata[MAX_MAP_MIPTEX]; // (dmiptexlump_t) +int dtexdata_checksum; + +int entdatasize; +char dentdata[MAX_MAP_ENTSTRING]; +int dentdata_checksum; + +int numleafs; +dleaf_t dleafs[MAX_MAP_LEAFS]; +int dleafs_checksum; + +int numplanes; +dplane_t dplanes[MAX_MAP_PLANES]; +int dplanes_checksum; + +int numvertexes; +dvertex_t dvertexes[MAX_MAP_VERTS]; +int dvertexes_checksum; + +int numnodes; +dnode_t dnodes[MAX_MAP_NODES]; +int dnodes_checksum; + +int numtexinfo; +texinfo_t texinfo[MAX_MAP_TEXINFO]; +int texinfo_checksum; + +int numfaces; +dface_t dfaces[MAX_MAP_FACES]; +int dfaces_checksum; + +int numclipnodes; +dclipnode_t dclipnodes[MAX_MAP_CLIPNODES]; +int dclipnodes_checksum; + +int numedges; +dedge_t dedges[MAX_MAP_EDGES]; +int dedges_checksum; + +int nummarksurfaces; +unsigned short dmarksurfaces[MAX_MAP_MARKSURFACES]; +int dmarksurfaces_checksum; + +int numsurfedges; +int dsurfedges[MAX_MAP_SURFEDGES]; +int dsurfedges_checksum; + +int num_entities; +entity_t entities[MAX_MAP_ENTITIES]; + +/* +=============== +FastChecksum +=============== +*/ + +int FastChecksum(void *buffer, int bytes) +{ + int checksum = 0; + + while( bytes-- ) + checksum = _rotl(checksum, 4) ^ *((char *)buffer)++; + + return checksum; +} + +/* +=============== +CompressVis +=============== +*/ +int CompressVis (byte *vis, byte *dest) +{ + int j; + int rep; + int visrow; + byte *dest_p; + + dest_p = dest; + visrow = (numleafs + 7)>>3; + + for (j=0 ; j>3; + out = decompressed; + + do + { + if (*in) + { + *out++ = *in++; + continue; + } + + c = in[1]; + in += 2; + while (c) + { + *out++ = 0; + c--; + } + } while (out - decompressed < row); +} + +//============================================================================= + +/* +============= +SwapBSPFile + +Byte swaps all data in a bsp file. +============= +*/ +void SwapBSPFile (qboolean todisk) +{ + int i, j, c; + dmodel_t *d; + dmiptexlump_t *mtl; + + +// models + for (i=0 ; iheadnode[j] = LittleLong (d->headnode[j]); + + d->visleafs = LittleLong (d->visleafs); + d->firstface = LittleLong (d->firstface); + d->numfaces = LittleLong (d->numfaces); + + for (j=0 ; j<3 ; j++) + { + d->mins[j] = LittleFloat(d->mins[j]); + d->maxs[j] = LittleFloat(d->maxs[j]); + d->origin[j] = LittleFloat(d->origin[j]); + } + } + +// +// vertexes +// + for (i=0 ; inummiptex; + else + c = LittleLong(mtl->nummiptex); + mtl->nummiptex = LittleLong (mtl->nummiptex); + for (i=0 ; idataofs[i] = LittleLong(mtl->dataofs[i]); + } + +// +// marksurfaces +// + for (i=0 ; ilumps[lump].filelen; + ofs = header->lumps[lump].fileofs; + + if (length % size) + Error ("LoadBSPFile: odd lump size"); + + memcpy (dest, (byte *)header + ofs, length); + + return length / size; +} + +/* +============= +LoadBSPFile +============= +*/ +void LoadBSPFile (char *filename) +{ + int i; + +// +// load the file header +// + LoadFile (filename, (void **)&header); + +// swap the header + for (i=0 ; i< sizeof(dheader_t)/4 ; i++) + ((int *)header)[i] = LittleLong ( ((int *)header)[i]); + + if (header->version != BSPVERSION) + Error ("%s is version %i, not %i", filename, header->version, BSPVERSION); + + nummodels = CopyLump (LUMP_MODELS, dmodels, sizeof(dmodel_t)); + numvertexes = CopyLump (LUMP_VERTEXES, dvertexes, sizeof(dvertex_t)); + numplanes = CopyLump (LUMP_PLANES, dplanes, sizeof(dplane_t)); + numleafs = CopyLump (LUMP_LEAFS, dleafs, sizeof(dleaf_t)); + numnodes = CopyLump (LUMP_NODES, dnodes, sizeof(dnode_t)); + numtexinfo = CopyLump (LUMP_TEXINFO, texinfo, sizeof(texinfo_t)); + numclipnodes = CopyLump (LUMP_CLIPNODES, dclipnodes, sizeof(dclipnode_t)); + numfaces = CopyLump (LUMP_FACES, dfaces, sizeof(dface_t)); + nummarksurfaces = CopyLump (LUMP_MARKSURFACES, dmarksurfaces, sizeof(dmarksurfaces[0])); + numsurfedges = CopyLump (LUMP_SURFEDGES, dsurfedges, sizeof(dsurfedges[0])); + numedges = CopyLump (LUMP_EDGES, dedges, sizeof(dedge_t)); + + texdatasize = CopyLump (LUMP_TEXTURES, dtexdata, 1); + visdatasize = CopyLump (LUMP_VISIBILITY, dvisdata, 1); + lightdatasize = CopyLump (LUMP_LIGHTING, dlightdata, 1); + entdatasize = CopyLump (LUMP_ENTITIES, dentdata, 1); + + free (header); // everything has been copied out + +// +// swap everything +// + SwapBSPFile (false); + + dmodels_checksum = FastChecksum( dmodels, nummodels*sizeof(dmodels[0]) ); + dvertexes_checksum = FastChecksum( dvertexes, numvertexes*sizeof(dvertexes[0]) ); + dplanes_checksum = FastChecksum( dplanes, numplanes*sizeof(dplanes[0]) ); + dleafs_checksum = FastChecksum( dleafs, numleafs*sizeof(dleafs[0]) ); + dnodes_checksum = FastChecksum( dnodes, numnodes*sizeof(dnodes[0]) ); + texinfo_checksum = FastChecksum( texinfo, numtexinfo*sizeof(texinfo[0]) ); + dclipnodes_checksum = FastChecksum( dclipnodes, numclipnodes*sizeof(dclipnodes[0]) ); + dfaces_checksum = FastChecksum( dfaces, numfaces*sizeof(dfaces[0]) ); + dmarksurfaces_checksum = FastChecksum( dmarksurfaces, nummarksurfaces*sizeof(dmarksurfaces[0]) ); + dsurfedges_checksum = FastChecksum( dsurfedges, numsurfedges*sizeof(dsurfedges[0]) ); + dedges_checksum = FastChecksum( dedges, numedges*sizeof(dedges[0]) ); + dtexdata_checksum = FastChecksum( dtexdata, numedges*sizeof(dtexdata[0]) ); + dvisdata_checksum = FastChecksum( dvisdata, visdatasize*sizeof(dvisdata[0]) ); + dlightdata_checksum = FastChecksum( dlightdata, lightdatasize*sizeof(dlightdata[0]) ); + dentdata_checksum = FastChecksum( dentdata, entdatasize*sizeof(dentdata[0]) ); + +} + +//============================================================================ + +FILE *wadfile; +dheader_t outheader; + +void AddLump (int lumpnum, void *data, int len) +{ + lump_t *lump; + + lump = &header->lumps[lumpnum]; + + lump->fileofs = LittleLong( ftell(wadfile) ); + lump->filelen = LittleLong(len); + SafeWrite (wadfile, data, (len+3)&~3); +} + +/* +============= +WriteBSPFile + +Swaps the bsp file in place, so it should not be referenced again +============= +*/ +void WriteBSPFile (char *filename) +{ + header = &outheader; + memset (header, 0, sizeof(dheader_t)); + + SwapBSPFile (true); + + header->version = LittleLong (BSPVERSION); + + wadfile = SafeOpenWrite (filename); + SafeWrite (wadfile, header, sizeof(dheader_t)); // overwritten later + + AddLump (LUMP_PLANES, dplanes, numplanes*sizeof(dplane_t)); + AddLump (LUMP_LEAFS, dleafs, numleafs*sizeof(dleaf_t)); + AddLump (LUMP_VERTEXES, dvertexes, numvertexes*sizeof(dvertex_t)); + AddLump (LUMP_NODES, dnodes, numnodes*sizeof(dnode_t)); + AddLump (LUMP_TEXINFO, texinfo, numtexinfo*sizeof(texinfo_t)); + AddLump (LUMP_FACES, dfaces, numfaces*sizeof(dface_t)); + AddLump (LUMP_CLIPNODES, dclipnodes, numclipnodes*sizeof(dclipnode_t)); + AddLump (LUMP_MARKSURFACES, dmarksurfaces, nummarksurfaces*sizeof(dmarksurfaces[0])); + AddLump (LUMP_SURFEDGES, dsurfedges, numsurfedges*sizeof(dsurfedges[0])); + AddLump (LUMP_EDGES, dedges, numedges*sizeof(dedge_t)); + AddLump (LUMP_MODELS, dmodels, nummodels*sizeof(dmodel_t)); + + AddLump (LUMP_LIGHTING, dlightdata, lightdatasize); + AddLump (LUMP_VISIBILITY, dvisdata, visdatasize); + AddLump (LUMP_ENTITIES, dentdata, entdatasize); + AddLump (LUMP_TEXTURES, dtexdata, texdatasize); + + fseek (wadfile, 0, SEEK_SET); + SafeWrite (wadfile, header, sizeof(dheader_t)); + fclose (wadfile); +} + +//============================================================================ + +#define ENTRIES(a) (sizeof(a)/sizeof(*(a))) +#define ENTRYSIZE(a) (sizeof(*(a))) + +ArrayUsage( char *szItem, int items, int maxitems, int itemsize ) +{ + float percentage = maxitems ? items * 100.0 / maxitems : 0.0; + + printf("%-12s %7i/%-7i %7i/%-7i (%4.1f%%)", + szItem, items, maxitems, items * itemsize, maxitems * itemsize, percentage ); + if ( percentage > 80.0 ) + printf( "VERY FULL!\n" ); + else if ( percentage > 95.0 ) + printf( "SIZE DANGER!\n" ); + else if ( percentage > 99.9 ) + printf( "SIZE OVERFLOW!!!\n" ); + else + printf( "\n" ); + return items * itemsize; +} + +GlobUsage( char *szItem, int itemstorage, int maxstorage ) +{ + float percentage = maxstorage ? itemstorage * 100.0 / maxstorage : 0.0; + printf("%-12s [variable] %7i/%-7i (%4.1f%%)", + szItem, itemstorage, maxstorage, percentage ); + if ( percentage > 80.0 ) + printf( "VERY FULL!\n" ); + else if ( percentage > 95.0 ) + printf( "SIZE DANGER!\n" ); + else if ( percentage > 99.9 ) + printf( "SIZE OVERFLOW!!!\n" ); + else + printf( "\n" ); + return itemstorage; +} + +/* +============= +PrintBSPFileSizes + +Dumps info about current file +============= +*/ +void PrintBSPFileSizes (void) +{ + int numtextures = texdatasize ? ((dmiptexlump_t*)dtexdata)->nummiptex : 0; + int totalmemory = 0; + + printf("\n"); + printf("Object names Objects/Maxobjs Memory / Maxmem Fullness\n" ); + printf("------------ --------------- --------------- --------\n" ); + + totalmemory += ArrayUsage( "models", nummodels, ENTRIES(dmodels), ENTRYSIZE(dmodels) ); + totalmemory += ArrayUsage( "planes", numplanes, ENTRIES(dplanes), ENTRYSIZE(dplanes) ); + totalmemory += ArrayUsage( "vertexes", numvertexes, ENTRIES(dvertexes), ENTRYSIZE(dvertexes) ); + totalmemory += ArrayUsage( "nodes", numnodes, ENTRIES(dnodes), ENTRYSIZE(dnodes) ); + totalmemory += ArrayUsage( "texinfos", numtexinfo, ENTRIES(texinfo), ENTRYSIZE(texinfo) ); + totalmemory += ArrayUsage( "faces", numfaces, ENTRIES(dfaces), ENTRYSIZE(dfaces) ); + totalmemory += ArrayUsage( "clipnodes", numclipnodes, ENTRIES(dclipnodes), ENTRYSIZE(dclipnodes) ); + totalmemory += ArrayUsage( "leaves", numleafs, ENTRIES(dleafs), ENTRYSIZE(dleafs) ); + totalmemory += ArrayUsage( "marksurfaces", nummarksurfaces,ENTRIES(dmarksurfaces), ENTRYSIZE(dmarksurfaces) ); + totalmemory += ArrayUsage( "surfedges", numsurfedges, ENTRIES(dsurfedges), ENTRYSIZE(dsurfedges) ); + totalmemory += ArrayUsage( "edges", numedges, ENTRIES(dedges), ENTRYSIZE(dedges) ); + + totalmemory += GlobUsage( "texdata", texdatasize, sizeof(dtexdata) ); + totalmemory += GlobUsage( "lightdata", lightdatasize, sizeof(dlightdata) ); + totalmemory += GlobUsage( "visdata", visdatasize, sizeof(dvisdata) ); + totalmemory += GlobUsage( "entdata", entdatasize, sizeof(dentdata) ); + + printf( "=== Total BSP file data space used: %d bytes ===\n", totalmemory ); +} + + +/* +================= +ParseEpair +================= +*/ +epair_t *ParseEpair (void) +{ + epair_t *e; + + e = malloc (sizeof(epair_t)); + memset (e, 0, sizeof(epair_t)); + + if (strlen(token) >= MAX_KEY-1) + Error ("ParseEpar: token too long"); + e->key = copystring(token); + GetToken (false); + if (strlen(token) >= MAX_VALUE-1) + Error ("ParseEpar: token too long"); + e->value = copystring(token); + + return e; +} + + +/* +================ +ParseEntity +================ +*/ +qboolean ParseEntity (void) +{ + epair_t *e; + entity_t *mapent; + + if (!GetToken (true)) + return false; + + if (strcmp (token, "{") ) + Error ("ParseEntity: { not found"); + + if (num_entities == MAX_MAP_ENTITIES) + Error ("num_entities == MAX_MAP_ENTITIES"); + + mapent = &entities[num_entities]; + num_entities++; + + do + { + if (!GetToken (true)) + Error ("ParseEntity: EOF without closing brace"); + if (!strcmp (token, "}") ) + break; + e = ParseEpair (); + e->next = mapent->epairs; + mapent->epairs = e; + } while (1); + + return true; +} + +/* +================ +ParseEntities + +Parses the dentdata string into entities +================ +*/ +void ParseEntities (void) +{ + num_entities = 0; + ParseFromMemory (dentdata, entdatasize); + + while (ParseEntity ()) + { + } +} + + +/* +================ +UnparseEntities + +Generates the dentdata string from all the entities +================ +*/ +void UnparseEntities (void) +{ + char *buf, *end; + epair_t *ep; + char line[2048]; + int i; + + buf = dentdata; + end = buf; + *end = 0; + + for (i=0 ; inext) + { + sprintf (line, "\"%s\" \"%s\"\n", ep->key, ep->value); + strcat (end, line); + end += strlen(line); + } + strcat (end,"}\n"); + end += 2; + + if (end > buf + MAX_MAP_ENTSTRING) + Error ("Entity text too long"); + } + entdatasize = end - buf + 1; +} + + + +void SetKeyValue (entity_t *ent, char *key, char *value) +{ + epair_t *ep; + + for (ep=ent->epairs ; ep ; ep=ep->next) + if (!strcmp (ep->key, key) ) + { + free (ep->value); + ep->value = copystring(value); + return; + } + ep = malloc (sizeof(*ep)); + ep->next = ent->epairs; + ent->epairs = ep; + ep->key = copystring(key); + ep->value = copystring(value); +} + +char *ValueForKey (entity_t *ent, char *key) +{ + epair_t *ep; + + for (ep=ent->epairs ; ep ; ep=ep->next) + if (!strcmp (ep->key, key) ) + return ep->value; + return ""; +} + +vec_t FloatForKey (entity_t *ent, char *key) +{ + char *k; + + k = ValueForKey (ent, key); + return atof(k); +} + +void GetVectorForKey (entity_t *ent, char *key, vec3_t vec) +{ + char *k; + double v1, v2, v3; + + k = ValueForKey (ent, key); +// scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = v3 = 0; + sscanf (k, "%lf %lf %lf", &v1, &v2, &v3); + vec[0] = v1; + vec[1] = v2; + vec[2] = v3; +} + diff --git a/utils/common/bspfile.h b/utils/common/bspfile.h new file mode 100644 index 0000000..1804d77 --- /dev/null +++ b/utils/common/bspfile.h @@ -0,0 +1,332 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + + +// upper design bounds + +#define MAX_MAP_HULLS 4 + +#define MAX_MAP_MODELS 400 +#define MAX_MAP_BRUSHES 4096 +#define MAX_MAP_ENTITIES 1024 +#define MAX_MAP_ENTSTRING (128*1024) + +#define MAX_MAP_PLANES 32767 +#define MAX_MAP_NODES 32767 // because negative shorts are contents +#define MAX_MAP_CLIPNODES 32767 // +#define MAX_MAP_LEAFS 8192 +#define MAX_MAP_VERTS 65535 +#define MAX_MAP_FACES 65535 +#define MAX_MAP_MARKSURFACES 65535 +#define MAX_MAP_TEXINFO 8192 +#define MAX_MAP_EDGES 256000 +#define MAX_MAP_SURFEDGES 512000 +#define MAX_MAP_TEXTURES 512 +#define MAX_MAP_MIPTEX 0x200000 +#define MAX_MAP_LIGHTING 0x200000 +#define MAX_MAP_VISIBILITY 0x200000 + +#define MAX_MAP_PORTALS 65536 + +// key / value pair sizes + +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +//============================================================================= + + +#define BSPVERSION 30 +#define TOOLVERSION 2 + + +typedef struct +{ + int fileofs, filelen; +} lump_t; + +#define LUMP_ENTITIES 0 +#define LUMP_PLANES 1 +#define LUMP_TEXTURES 2 +#define LUMP_VERTEXES 3 +#define LUMP_VISIBILITY 4 +#define LUMP_NODES 5 +#define LUMP_TEXINFO 6 +#define LUMP_FACES 7 +#define LUMP_LIGHTING 8 +#define LUMP_CLIPNODES 9 +#define LUMP_LEAFS 10 +#define LUMP_MARKSURFACES 11 +#define LUMP_EDGES 12 +#define LUMP_SURFEDGES 13 +#define LUMP_MODELS 14 + +#define HEADER_LUMPS 15 + +typedef struct +{ + float mins[3], maxs[3]; + float origin[3]; + int headnode[MAX_MAP_HULLS]; + int visleafs; // not including the solid leaf 0 + int firstface, numfaces; +} dmodel_t; + +typedef struct +{ + int version; + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct +{ + int nummiptex; + int dataofs[4]; // [nummiptex] +} dmiptexlump_t; + +#define MIPLEVELS 4 +typedef struct miptex_s +{ + char name[16]; + unsigned width, height; + unsigned offsets[MIPLEVELS]; // four mip maps stored +} miptex_t; + + +typedef struct +{ + float point[3]; +} dvertex_t; + + +// 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 + +typedef struct +{ + float normal[3]; + float dist; + int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate +} dplane_t; + + + +#define CONTENTS_EMPTY -1 +#define CONTENTS_SOLID -2 +#define CONTENTS_WATER -3 +#define CONTENTS_SLIME -4 +#define CONTENTS_LAVA -5 +#define CONTENTS_SKY -6 +#define CONTENTS_ORIGIN -7 // removed at csg time +#define CONTENTS_CLIP -8 // changed to contents_solid + +#define CONTENTS_CURRENT_0 -9 +#define CONTENTS_CURRENT_90 -10 +#define CONTENTS_CURRENT_180 -11 +#define CONTENTS_CURRENT_270 -12 +#define CONTENTS_CURRENT_UP -13 +#define CONTENTS_CURRENT_DOWN -14 + +#define CONTENTS_TRANSLUCENT -15 + +// !!! if this is changed, it must be changed in asm_i386.h too !!! +typedef struct +{ + int planenum; + short children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for sphere culling + short maxs[3]; + unsigned short firstface; + unsigned short numfaces; // counting both sides +} dnode_t; + +typedef struct +{ + int planenum; + short children[2]; // negative numbers are contents +} dclipnode_t; + + +typedef struct texinfo_s +{ + float vecs[2][4]; // [s/t][xyz offset] + int miptex; + int flags; +} texinfo_t; +#define TEX_SPECIAL 1 // sky or slime, no lightmap or 256 subdivision + +// note that edge 0 is never used, because negative edge nums are used for +// counterclockwise use of the edge in a face +typedef struct +{ + unsigned short v[2]; // vertex numbers +} dedge_t; + +#define MAXLIGHTMAPS 4 +typedef struct +{ + short planenum; + short side; + + int firstedge; // we must support > 64k edges + short numedges; + short texinfo; + +// lighting info + byte styles[MAXLIGHTMAPS]; + int lightofs; // start of [numstyles*surfsize] samples +} dface_t; + + + +#define AMBIENT_WATER 0 +#define AMBIENT_SKY 1 +#define AMBIENT_SLIME 2 +#define AMBIENT_LAVA 3 + +#define NUM_AMBIENTS 4 // automatic ambient sounds + +// leaf 0 is the generic CONTENTS_SOLID leaf, used for all solid areas +// all other leafs need visibility info +typedef struct +{ + int contents; + int visofs; // -1 = no visibility info + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned short firstmarksurface; + unsigned short nummarksurfaces; + + byte ambient_level[NUM_AMBIENTS]; +} dleaf_t; + + +//============================================================================ + +#ifndef QUAKE_GAME + +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + + +// the utilities get to be lazy and just use large static arrays + +extern int nummodels; +extern dmodel_t dmodels[MAX_MAP_MODELS]; +extern int dmodels_checksum; + +extern int visdatasize; +extern byte dvisdata[MAX_MAP_VISIBILITY]; +extern int dvisdata_checksum; + +extern int lightdatasize; +extern byte dlightdata[MAX_MAP_LIGHTING]; +extern int dlightdata_checksum; + +extern int texdatasize; +extern byte dtexdata[MAX_MAP_MIPTEX]; // (dmiptexlump_t) +extern int dtexdata_checksum; + +extern int entdatasize; +extern char dentdata[MAX_MAP_ENTSTRING]; +extern int dentdata_checksum; + +extern int numleafs; +extern dleaf_t dleafs[MAX_MAP_LEAFS]; +extern int dleafs_checksum; + +extern int numplanes; +extern dplane_t dplanes[MAX_MAP_PLANES]; +extern int dplanes_checksum; + +extern int numvertexes; +extern dvertex_t dvertexes[MAX_MAP_VERTS]; +extern int dvertexes_checksum; + +extern int numnodes; +extern dnode_t dnodes[MAX_MAP_NODES]; +extern int dnodes_checksum; + +extern int numtexinfo; +extern texinfo_t texinfo[MAX_MAP_TEXINFO]; +extern int texinfo_checksum; + +extern int numfaces; +extern dface_t dfaces[MAX_MAP_FACES]; +extern int dfaces_checksum; + +extern int numclipnodes; +extern dclipnode_t dclipnodes[MAX_MAP_CLIPNODES]; +extern int dclipnodes_checksum; + +extern int numedges; +extern dedge_t dedges[MAX_MAP_EDGES]; +extern int dedges_checksum; + +extern int nummarksurfaces; +extern unsigned short dmarksurfaces[MAX_MAP_MARKSURFACES]; +extern int dmarksurfaces_checksum; + +extern int numsurfedges; +extern int dsurfedges[MAX_MAP_SURFEDGES]; +extern int dsurfedges_checksum; + +int FastChecksum(void *buffer, int bytes); + +void DecompressVis (byte *in, byte *decompressed); +int CompressVis (byte *vis, byte *dest); + +void LoadBSPFile (char *filename); +void WriteBSPFile (char *filename); +void PrintBSPFileSizes (void); + +//=============== + + +typedef struct epair_s +{ + struct epair_s *next; + char *key; + char *value; +} epair_t; + +typedef struct +{ + vec3_t origin; + int firstbrush; + int numbrushes; + epair_t *epairs; +} entity_t; + +extern int num_entities; +extern entity_t entities[MAX_MAP_ENTITIES]; + +void ParseEntities (void); +void UnparseEntities (void); + +void SetKeyValue (entity_t *ent, char *key, char *value); +char *ValueForKey (entity_t *ent, char *key); +// will return "" if not present + +vec_t FloatForKey (entity_t *ent, char *key); +void GetVectorForKey (entity_t *ent, char *key, vec3_t vec); + +epair_t *ParseEpair (void); + +#endif diff --git a/utils/common/bsplib.c b/utils/common/bsplib.c new file mode 100644 index 0000000..d2bc8a7 --- /dev/null +++ b/utils/common/bsplib.c @@ -0,0 +1,709 @@ +/*** +* +* Copyright (c) 1998, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "cmdlib.h" +#include "mathlib.h" +#include "bsplib.h" +#include "scriplib.h" + +//============================================================================= + +int nummodels; +dmodel_t dmodels[MAX_MAP_MODELS]; +int dmodels_checksum; + +int visdatasize; +byte dvisdata[MAX_MAP_VISIBILITY]; +int dvisdata_checksum; + +int lightdatasize; +byte dlightdata[MAX_MAP_LIGHTING]; +int dlightdata_checksum; + +int texdatasize; +byte dtexdata[MAX_MAP_MIPTEX]; // (dmiptexlump_t) +int dtexdata_checksum; + +int entdatasize; +char dentdata[MAX_MAP_ENTSTRING]; +int dentdata_checksum; + +int numleafs; +dleaf_t dleafs[MAX_MAP_LEAFS]; +int dleafs_checksum; + +int numplanes; +dplane_t dplanes[MAX_MAP_PLANES]; +int dplanes_checksum; + +int numvertexes; +dvertex_t dvertexes[MAX_MAP_VERTS]; +int dvertexes_checksum; + +int numnodes; +dnode_t dnodes[MAX_MAP_NODES]; +int dnodes_checksum; + +int numtexinfo; +texinfo_t texinfo[MAX_MAP_TEXINFO]; +int texinfo_checksum; + +int numfaces; +dface_t dfaces[MAX_MAP_FACES]; +int dfaces_checksum; + +int numclipnodes; +dclipnode_t dclipnodes[MAX_MAP_CLIPNODES]; +int dclipnodes_checksum; + +int numedges; +dedge_t dedges[MAX_MAP_EDGES]; +int dedges_checksum; + +int nummarksurfaces; +unsigned short dmarksurfaces[MAX_MAP_MARKSURFACES]; +int dmarksurfaces_checksum; + +int numsurfedges; +int dsurfedges[MAX_MAP_SURFEDGES]; +int dsurfedges_checksum; + +int num_entities; +entity_t entities[MAX_MAP_ENTITIES]; + +/* +=============== +FastChecksum +=============== +*/ + +int FastChecksum(void *buffer, int bytes) +{ + int checksum = 0; + + while( bytes-- ) + checksum = _rotl(checksum, 4) ^ *((char *)buffer)++; + + return checksum; +} + +/* +=============== +CompressVis +=============== +*/ +int CompressVis (byte *vis, byte *dest) +{ + int j; + int rep; + int visrow; + byte *dest_p; + + dest_p = dest; + visrow = (numleafs + 7)>>3; + + for (j=0 ; j>3; + out = decompressed; + + do + { + if (*in) + { + *out++ = *in++; + continue; + } + + c = in[1]; + in += 2; + while (c) + { + *out++ = 0; + c--; + } + } while (out - decompressed < row); +} + +//============================================================================= + +/* +============= +SwapBSPFile + +Byte swaps all data in a bsp file. +============= +*/ +void SwapBSPFile (qboolean todisk) +{ + int i, j, c; + dmodel_t *d; + dmiptexlump_t *mtl; + + +// models + for (i=0 ; iheadnode[j] = LittleLong (d->headnode[j]); + + d->visleafs = LittleLong (d->visleafs); + d->firstface = LittleLong (d->firstface); + d->numfaces = LittleLong (d->numfaces); + + for (j=0 ; j<3 ; j++) + { + d->mins[j] = LittleFloat(d->mins[j]); + d->maxs[j] = LittleFloat(d->maxs[j]); + d->origin[j] = LittleFloat(d->origin[j]); + } + } + +// +// vertexes +// + for (i=0 ; inummiptex; + else + c = LittleLong(mtl->nummiptex); + mtl->nummiptex = LittleLong (mtl->nummiptex); + for (i=0 ; idataofs[i] = LittleLong(mtl->dataofs[i]); + } + +// +// marksurfaces +// + for (i=0 ; ilumps[lump].filelen; + ofs = header->lumps[lump].fileofs; + + if (length % size) + Error ("LoadBSPFile: odd lump size"); + + memcpy (dest, (byte *)header + ofs, length); + + return length / size; +} + +/* +============= +LoadBSPFile +============= +*/ +void LoadBSPFile (char *filename) +{ + int i; + +// +// load the file header +// + LoadFile (filename, (void **)&header); + +// swap the header + for (i=0 ; i< sizeof(dheader_t)/4 ; i++) + ((int *)header)[i] = LittleLong ( ((int *)header)[i]); + + if (header->version != BSPVERSION) + Error ("%s is version %i, not %i", filename, header->version, BSPVERSION); + + nummodels = CopyLump (LUMP_MODELS, dmodels, sizeof(dmodel_t)); + numvertexes = CopyLump (LUMP_VERTEXES, dvertexes, sizeof(dvertex_t)); + numplanes = CopyLump (LUMP_PLANES, dplanes, sizeof(dplane_t)); + numleafs = CopyLump (LUMP_LEAFS, dleafs, sizeof(dleaf_t)); + numnodes = CopyLump (LUMP_NODES, dnodes, sizeof(dnode_t)); + numtexinfo = CopyLump (LUMP_TEXINFO, texinfo, sizeof(texinfo_t)); + numclipnodes = CopyLump (LUMP_CLIPNODES, dclipnodes, sizeof(dclipnode_t)); + numfaces = CopyLump (LUMP_FACES, dfaces, sizeof(dface_t)); + nummarksurfaces = CopyLump (LUMP_MARKSURFACES, dmarksurfaces, sizeof(dmarksurfaces[0])); + numsurfedges = CopyLump (LUMP_SURFEDGES, dsurfedges, sizeof(dsurfedges[0])); + numedges = CopyLump (LUMP_EDGES, dedges, sizeof(dedge_t)); + + texdatasize = CopyLump (LUMP_TEXTURES, dtexdata, 1); + visdatasize = CopyLump (LUMP_VISIBILITY, dvisdata, 1); + lightdatasize = CopyLump (LUMP_LIGHTING, dlightdata, 1); + entdatasize = CopyLump (LUMP_ENTITIES, dentdata, 1); + + free (header); // everything has been copied out + +// +// swap everything +// + SwapBSPFile (false); + + dmodels_checksum = FastChecksum( dmodels, nummodels*sizeof(dmodels[0]) ); + dvertexes_checksum = FastChecksum( dvertexes, numvertexes*sizeof(dvertexes[0]) ); + dplanes_checksum = FastChecksum( dplanes, numplanes*sizeof(dplanes[0]) ); + dleafs_checksum = FastChecksum( dleafs, numleafs*sizeof(dleafs[0]) ); + dnodes_checksum = FastChecksum( dnodes, numnodes*sizeof(dnodes[0]) ); + texinfo_checksum = FastChecksum( texinfo, numtexinfo*sizeof(texinfo[0]) ); + dclipnodes_checksum = FastChecksum( dclipnodes, numclipnodes*sizeof(dclipnodes[0]) ); + dfaces_checksum = FastChecksum( dfaces, numfaces*sizeof(dfaces[0]) ); + dmarksurfaces_checksum = FastChecksum( dmarksurfaces, nummarksurfaces*sizeof(dmarksurfaces[0]) ); + dsurfedges_checksum = FastChecksum( dsurfedges, numsurfedges*sizeof(dsurfedges[0]) ); + dedges_checksum = FastChecksum( dedges, numedges*sizeof(dedges[0]) ); + dtexdata_checksum = FastChecksum( dtexdata, numedges*sizeof(dtexdata[0]) ); + dvisdata_checksum = FastChecksum( dvisdata, visdatasize*sizeof(dvisdata[0]) ); + dlightdata_checksum = FastChecksum( dlightdata, lightdatasize*sizeof(dlightdata[0]) ); + dentdata_checksum = FastChecksum( dentdata, entdatasize*sizeof(dentdata[0]) ); + +} + +//============================================================================ + +FILE *wadfile; +dheader_t outheader; + +void AddLump (int lumpnum, void *data, int len) +{ + lump_t *lump; + + lump = &header->lumps[lumpnum]; + + lump->fileofs = LittleLong( ftell(wadfile) ); + lump->filelen = LittleLong(len); + SafeWrite (wadfile, data, (len+3)&~3); +} + +/* +============= +WriteBSPFile + +Swaps the bsp file in place, so it should not be referenced again +============= +*/ +void WriteBSPFile (char *filename) +{ + header = &outheader; + memset (header, 0, sizeof(dheader_t)); + + SwapBSPFile (true); + + header->version = LittleLong (BSPVERSION); + + wadfile = SafeOpenWrite (filename); + SafeWrite (wadfile, header, sizeof(dheader_t)); // overwritten later + + AddLump (LUMP_PLANES, dplanes, numplanes*sizeof(dplane_t)); + AddLump (LUMP_LEAFS, dleafs, numleafs*sizeof(dleaf_t)); + AddLump (LUMP_VERTEXES, dvertexes, numvertexes*sizeof(dvertex_t)); + AddLump (LUMP_NODES, dnodes, numnodes*sizeof(dnode_t)); + AddLump (LUMP_TEXINFO, texinfo, numtexinfo*sizeof(texinfo_t)); + AddLump (LUMP_FACES, dfaces, numfaces*sizeof(dface_t)); + AddLump (LUMP_CLIPNODES, dclipnodes, numclipnodes*sizeof(dclipnode_t)); + AddLump (LUMP_MARKSURFACES, dmarksurfaces, nummarksurfaces*sizeof(dmarksurfaces[0])); + AddLump (LUMP_SURFEDGES, dsurfedges, numsurfedges*sizeof(dsurfedges[0])); + AddLump (LUMP_EDGES, dedges, numedges*sizeof(dedge_t)); + AddLump (LUMP_MODELS, dmodels, nummodels*sizeof(dmodel_t)); + + AddLump (LUMP_LIGHTING, dlightdata, lightdatasize); + AddLump (LUMP_VISIBILITY, dvisdata, visdatasize); + AddLump (LUMP_ENTITIES, dentdata, entdatasize); + AddLump (LUMP_TEXTURES, dtexdata, texdatasize); + + fseek (wadfile, 0, SEEK_SET); + SafeWrite (wadfile, header, sizeof(dheader_t)); + fclose (wadfile); +} + +//============================================================================ + +#define ENTRIES(a) (sizeof(a)/sizeof(*(a))) +#define ENTRYSIZE(a) (sizeof(*(a))) + +ArrayUsage( char *szItem, int items, int maxitems, int itemsize ) +{ + float percentage = maxitems ? items * 100.0 / maxitems : 0.0; + + printf("%-12s %7i/%-7i %7i/%-7i (%4.1f%%)", + szItem, items, maxitems, items * itemsize, maxitems * itemsize, percentage ); + if ( percentage > 80.0 ) + printf( "VERY FULL!\n" ); + else if ( percentage > 95.0 ) + printf( "SIZE DANGER!\n" ); + else if ( percentage > 99.9 ) + printf( "SIZE OVERFLOW!!!\n" ); + else + printf( "\n" ); + return items * itemsize; +} + +GlobUsage( char *szItem, int itemstorage, int maxstorage ) +{ + float percentage = maxstorage ? itemstorage * 100.0 / maxstorage : 0.0; + printf("%-12s [variable] %7i/%-7i (%4.1f%%)", + szItem, itemstorage, maxstorage, percentage ); + if ( percentage > 80.0 ) + printf( "VERY FULL!\n" ); + else if ( percentage > 95.0 ) + printf( "SIZE DANGER!\n" ); + else if ( percentage > 99.9 ) + printf( "SIZE OVERFLOW!!!\n" ); + else + printf( "\n" ); + return itemstorage; +} + +/* +============= +PrintBSPFileSizes + +Dumps info about current file +============= +*/ +void PrintBSPFileSizes (void) +{ + int numtextures = texdatasize ? ((dmiptexlump_t*)dtexdata)->nummiptex : 0; + int totalmemory = 0; + + printf("\n"); + printf("Object names Objects/Maxobjs Memory / Maxmem Fullness\n" ); + printf("------------ --------------- --------------- --------\n" ); + + totalmemory += ArrayUsage( "models", nummodels, ENTRIES(dmodels), ENTRYSIZE(dmodels) ); + totalmemory += ArrayUsage( "planes", numplanes, ENTRIES(dplanes), ENTRYSIZE(dplanes) ); + totalmemory += ArrayUsage( "vertexes", numvertexes, ENTRIES(dvertexes), ENTRYSIZE(dvertexes) ); + totalmemory += ArrayUsage( "nodes", numnodes, ENTRIES(dnodes), ENTRYSIZE(dnodes) ); + totalmemory += ArrayUsage( "texinfos", numtexinfo, ENTRIES(texinfo), ENTRYSIZE(texinfo) ); + totalmemory += ArrayUsage( "faces", numfaces, ENTRIES(dfaces), ENTRYSIZE(dfaces) ); + totalmemory += ArrayUsage( "clipnodes", numclipnodes, ENTRIES(dclipnodes), ENTRYSIZE(dclipnodes) ); + totalmemory += ArrayUsage( "leaves", numleafs, ENTRIES(dleafs), ENTRYSIZE(dleafs) ); + totalmemory += ArrayUsage( "marksurfaces", nummarksurfaces,ENTRIES(dmarksurfaces), ENTRYSIZE(dmarksurfaces) ); + totalmemory += ArrayUsage( "surfedges", numsurfedges, ENTRIES(dsurfedges), ENTRYSIZE(dsurfedges) ); + totalmemory += ArrayUsage( "edges", numedges, ENTRIES(dedges), ENTRYSIZE(dedges) ); + + totalmemory += GlobUsage( "texdata", texdatasize, sizeof(dtexdata) ); + totalmemory += GlobUsage( "lightdata", lightdatasize, sizeof(dlightdata) ); + totalmemory += GlobUsage( "visdata", visdatasize, sizeof(dvisdata) ); + totalmemory += GlobUsage( "entdata", entdatasize, sizeof(dentdata) ); + + printf( "=== Total BSP file data space used: %d bytes ===\n", totalmemory ); +} + + +/* +================= +ParseEpair +================= +*/ +epair_t *ParseEpair (void) +{ + epair_t *e; + + e = malloc (sizeof(epair_t)); + memset (e, 0, sizeof(epair_t)); + + if (strlen(token) >= MAX_KEY-1) + Error ("ParseEpar: token too long"); + e->key = copystring(token); + GetToken (false); + if (strlen(token) >= MAX_VALUE-1) + Error ("ParseEpar: token too long"); + e->value = copystring(token); + + return e; +} + + +/* +================ +ParseEntity +================ +*/ +qboolean ParseEntity (void) +{ + epair_t *e; + entity_t *mapent; + + if (!GetToken (true)) + return false; + + if (strcmp (token, "{") ) + Error ("ParseEntity: { not found"); + + if (num_entities == MAX_MAP_ENTITIES) + Error ("num_entities == MAX_MAP_ENTITIES"); + + mapent = &entities[num_entities]; + num_entities++; + + do + { + if (!GetToken (true)) + Error ("ParseEntity: EOF without closing brace"); + if (!strcmp (token, "}") ) + break; + e = ParseEpair (); + e->next = mapent->epairs; + mapent->epairs = e; + } while (1); + + return true; +} + +/* +================ +ParseEntities + +Parses the dentdata string into entities +================ +*/ +void ParseEntities (void) +{ + num_entities = 0; + ParseFromMemory (dentdata, entdatasize); + + while (ParseEntity ()) + { + } +} + + +/* +================ +UnparseEntities + +Generates the dentdata string from all the entities +================ +*/ +void UnparseEntities (void) +{ + char *buf, *end; + epair_t *ep; + char line[2048]; + int i; + + buf = dentdata; + end = buf; + *end = 0; + + for (i=0 ; inext) + { + sprintf (line, "\"%s\" \"%s\"\n", ep->key, ep->value); + strcat (end, line); + end += strlen(line); + } + strcat (end,"}\n"); + end += 2; + + if (end > buf + MAX_MAP_ENTSTRING) + Error ("Entity text too long"); + } + entdatasize = end - buf + 1; +} + + + +void SetKeyValue (entity_t *ent, char *key, char *value) +{ + epair_t *ep; + + for (ep=ent->epairs ; ep ; ep=ep->next) + if (!strcmp (ep->key, key) ) + { + free (ep->value); + ep->value = copystring(value); + return; + } + ep = malloc (sizeof(*ep)); + ep->next = ent->epairs; + ent->epairs = ep; + ep->key = copystring(key); + ep->value = copystring(value); +} + +char *ValueForKey (entity_t *ent, char *key) +{ + epair_t *ep; + + for (ep=ent->epairs ; ep ; ep=ep->next) + if (!strcmp (ep->key, key) ) + return ep->value; + return ""; +} + +vec_t FloatForKey (entity_t *ent, char *key) +{ + char *k; + + k = ValueForKey (ent, key); + return atof(k); +} + +void GetVectorForKey (entity_t *ent, char *key, vec3_t vec) +{ + char *k; + double v1, v2, v3; + + k = ValueForKey (ent, key); +// scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = v3 = 0; + sscanf (k, "%lf %lf %lf", &v1, &v2, &v3); + vec[0] = v1; + vec[1] = v2; + vec[2] = v3; +} + diff --git a/utils/common/bsplib.h b/utils/common/bsplib.h new file mode 100644 index 0000000..575829f --- /dev/null +++ b/utils/common/bsplib.h @@ -0,0 +1,332 @@ +/*** +* +* Copyright (c) 1998, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + + +// upper design bounds + +#define MAX_MAP_HULLS 4 + +#define MAX_MAP_MODELS 400 +#define MAX_MAP_BRUSHES 4096 +#define MAX_MAP_ENTITIES 1024 +#define MAX_MAP_ENTSTRING (128*1024) + +#define MAX_MAP_PLANES 32767 +#define MAX_MAP_NODES 32767 // because negative shorts are contents +#define MAX_MAP_CLIPNODES 32767 // +#define MAX_MAP_LEAFS 8192 +#define MAX_MAP_VERTS 65535 +#define MAX_MAP_FACES 65535 +#define MAX_MAP_MARKSURFACES 65535 +#define MAX_MAP_TEXINFO 8192 +#define MAX_MAP_EDGES 256000 +#define MAX_MAP_SURFEDGES 512000 +#define MAX_MAP_TEXTURES 512 +#define MAX_MAP_MIPTEX 0x200000 +#define MAX_MAP_LIGHTING 0x200000 +#define MAX_MAP_VISIBILITY 0x200000 + +#define MAX_MAP_PORTALS 65536 + +// key / value pair sizes + +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +//============================================================================= + + +#define BSPVERSION 30 +#define TOOLVERSION 2 + + +typedef struct +{ + int fileofs, filelen; +} lump_t; + +#define LUMP_ENTITIES 0 +#define LUMP_PLANES 1 +#define LUMP_TEXTURES 2 +#define LUMP_VERTEXES 3 +#define LUMP_VISIBILITY 4 +#define LUMP_NODES 5 +#define LUMP_TEXINFO 6 +#define LUMP_FACES 7 +#define LUMP_LIGHTING 8 +#define LUMP_CLIPNODES 9 +#define LUMP_LEAFS 10 +#define LUMP_MARKSURFACES 11 +#define LUMP_EDGES 12 +#define LUMP_SURFEDGES 13 +#define LUMP_MODELS 14 + +#define HEADER_LUMPS 15 + +typedef struct +{ + float mins[3], maxs[3]; + float origin[3]; + int headnode[MAX_MAP_HULLS]; + int visleafs; // not including the solid leaf 0 + int firstface, numfaces; +} dmodel_t; + +typedef struct +{ + int version; + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct +{ + int nummiptex; + int dataofs[4]; // [nummiptex] +} dmiptexlump_t; + +#define MIPLEVELS 4 +typedef struct miptex_s +{ + char name[16]; + unsigned width, height; + unsigned offsets[MIPLEVELS]; // four mip maps stored +} miptex_t; + + +typedef struct +{ + float point[3]; +} dvertex_t; + + +// 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 + +typedef struct +{ + float normal[3]; + float dist; + int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate +} dplane_t; + + + +#define CONTENTS_EMPTY -1 +#define CONTENTS_SOLID -2 +#define CONTENTS_WATER -3 +#define CONTENTS_SLIME -4 +#define CONTENTS_LAVA -5 +#define CONTENTS_SKY -6 +#define CONTENTS_ORIGIN -7 // removed at csg time +#define CONTENTS_CLIP -8 // changed to contents_solid + +#define CONTENTS_CURRENT_0 -9 +#define CONTENTS_CURRENT_90 -10 +#define CONTENTS_CURRENT_180 -11 +#define CONTENTS_CURRENT_270 -12 +#define CONTENTS_CURRENT_UP -13 +#define CONTENTS_CURRENT_DOWN -14 + +#define CONTENTS_TRANSLUCENT -15 + +// !!! if this is changed, it must be changed in asm_i386.h too !!! +typedef struct +{ + int planenum; + short children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for sphere culling + short maxs[3]; + unsigned short firstface; + unsigned short numfaces; // counting both sides +} dnode_t; + +typedef struct +{ + int planenum; + short children[2]; // negative numbers are contents +} dclipnode_t; + + +typedef struct texinfo_s +{ + float vecs[2][4]; // [s/t][xyz offset] + int miptex; + int flags; +} texinfo_t; +#define TEX_SPECIAL 1 // sky or slime, no lightmap or 256 subdivision + +// note that edge 0 is never used, because negative edge nums are used for +// counterclockwise use of the edge in a face +typedef struct +{ + unsigned short v[2]; // vertex numbers +} dedge_t; + +#define MAXLIGHTMAPS 4 +typedef struct +{ + short planenum; + short side; + + int firstedge; // we must support > 64k edges + short numedges; + short texinfo; + +// lighting info + byte styles[MAXLIGHTMAPS]; + int lightofs; // start of [numstyles*surfsize] samples +} dface_t; + + + +#define AMBIENT_WATER 0 +#define AMBIENT_SKY 1 +#define AMBIENT_SLIME 2 +#define AMBIENT_LAVA 3 + +#define NUM_AMBIENTS 4 // automatic ambient sounds + +// leaf 0 is the generic CONTENTS_SOLID leaf, used for all solid areas +// all other leafs need visibility info +typedef struct +{ + int contents; + int visofs; // -1 = no visibility info + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned short firstmarksurface; + unsigned short nummarksurfaces; + + byte ambient_level[NUM_AMBIENTS]; +} dleaf_t; + + +//============================================================================ + +#ifndef QUAKE_GAME + +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + + +// the utilities get to be lazy and just use large static arrays + +extern int nummodels; +extern dmodel_t dmodels[MAX_MAP_MODELS]; +extern int dmodels_checksum; + +extern int visdatasize; +extern byte dvisdata[MAX_MAP_VISIBILITY]; +extern int dvisdata_checksum; + +extern int lightdatasize; +extern byte dlightdata[MAX_MAP_LIGHTING]; +extern int dlightdata_checksum; + +extern int texdatasize; +extern byte dtexdata[MAX_MAP_MIPTEX]; // (dmiptexlump_t) +extern int dtexdata_checksum; + +extern int entdatasize; +extern char dentdata[MAX_MAP_ENTSTRING]; +extern int dentdata_checksum; + +extern int numleafs; +extern dleaf_t dleafs[MAX_MAP_LEAFS]; +extern int dleafs_checksum; + +extern int numplanes; +extern dplane_t dplanes[MAX_MAP_PLANES]; +extern int dplanes_checksum; + +extern int numvertexes; +extern dvertex_t dvertexes[MAX_MAP_VERTS]; +extern int dvertexes_checksum; + +extern int numnodes; +extern dnode_t dnodes[MAX_MAP_NODES]; +extern int dnodes_checksum; + +extern int numtexinfo; +extern texinfo_t texinfo[MAX_MAP_TEXINFO]; +extern int texinfo_checksum; + +extern int numfaces; +extern dface_t dfaces[MAX_MAP_FACES]; +extern int dfaces_checksum; + +extern int numclipnodes; +extern dclipnode_t dclipnodes[MAX_MAP_CLIPNODES]; +extern int dclipnodes_checksum; + +extern int numedges; +extern dedge_t dedges[MAX_MAP_EDGES]; +extern int dedges_checksum; + +extern int nummarksurfaces; +extern unsigned short dmarksurfaces[MAX_MAP_MARKSURFACES]; +extern int dmarksurfaces_checksum; + +extern int numsurfedges; +extern int dsurfedges[MAX_MAP_SURFEDGES]; +extern int dsurfedges_checksum; + +int FastChecksum(void *buffer, int bytes); + +void DecompressVis (byte *in, byte *decompressed); +int CompressVis (byte *vis, byte *dest); + +void LoadBSPFile (char *filename); +void WriteBSPFile (char *filename); +void PrintBSPFileSizes (void); + +//=============== + + +typedef struct epair_s +{ + struct epair_s *next; + char *key; + char *value; +} epair_t; + +typedef struct +{ + vec3_t origin; + int firstbrush; + int numbrushes; + epair_t *epairs; +} entity_t; + +extern int num_entities; +extern entity_t entities[MAX_MAP_ENTITIES]; + +void ParseEntities (void); +void UnparseEntities (void); + +void SetKeyValue (entity_t *ent, char *key, char *value); +char *ValueForKey (entity_t *ent, char *key); +// will return "" if not present + +vec_t FloatForKey (entity_t *ent, char *key); +void GetVectorForKey (entity_t *ent, char *key, vec3_t vec); + +epair_t *ParseEpair (void); + +#endif diff --git a/utils/common/cmdlib.c b/utils/common/cmdlib.c new file mode 100644 index 0000000..6e91fd9 --- /dev/null +++ b/utils/common/cmdlib.c @@ -0,0 +1,1040 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// cmdlib.c + +#include "cmdlib.h" +#include +#include + +#ifdef WIN32 +#include +#endif + +#ifdef NeXT +#include +#endif + +#ifdef WIN32 +#define PATHSEPARATOR(c) ((c) == '\\' || (c) == '/') +#else //WIN32 +#define PATHSEPARATOR(c) ((c) == '/') +#endif //WIN32 + +// set these before calling CheckParm +int myargc; +char **myargv; + +char com_token[1024]; +qboolean com_eof; + +qboolean archive; +char archivedir[1024]; + + +void COM_FixSlashes( char *pname ) +{ +#if 0 + while ( *pname ) { + if ( *pname == '/' ) + *pname = '\\'; + pname++; + } +#else + while ( *pname ) { + if ( *pname == '\\' ) + *pname = '/'; + pname++; + } +#endif +} + + +/* +================= +Error + +For abnormal program terminations +================= +*/ +void Error (char *error, ...) +{ + va_list argptr; + + printf ("\n************ ERROR ************\n"); + + va_start (argptr,error); + vprintf (error,argptr); + va_end (argptr); + printf ("\n"); + + exit (1); +} + +// only printf if in verbose mode +qboolean verbose = false; +void qprintf (char *format, ...) +{ + va_list argptr; + + if (!verbose) + return; + va_start (argptr,format); + vprintf (format,argptr); + va_end (argptr); +} + + +/* + +qdir will hold the path up to the quake directory, including the slash + + f:\quake\ + /raid/quake/ + +gamedir will hold qdir + the game directory (id1, id2, etc) + + */ + +char qproject[ 1024 ]={'\0'}; +char qdir[1024]={'\0'}; +char gamedir[1024]={'\0'}; + +void SetQdirFromPath (char *path) +{ +#ifndef OLD_BOGUS_PATH_CODE + + if ( qproject[0]=='\0' ) + { + if ( getenv("QPROJECT") ) + { + char c = qproject[ strlen(qproject)-1 ]; + strcpy( qproject, getenv("QPROJECT") ); + if ( !PATHSEPARATOR( c ) ) + strcat( qproject, "\\" ); + } + else + strcpy( qproject, "quiver\\" ); + } + if ( qproject[0] != '\\' && qproject[0] != '/' && qproject[1] != ':' ) + { + strcpy( qdir, "\\" ); + } + + strcat( qdir, qproject ); + strcpy( gamedir, qdir ); + strcat( gamedir, "\\valve\\" ); + +#else + char temp[1024]; + char *c; + + if (!(path[0] == '/' || path[0] == '\\' || path[1] == ':')) + { // path is partial + Q_getwd (temp); + strcat (temp, path); + path = temp; + } + +// search for "quake" or quiver in path + if( !qproject[0] ) { + char *pszProj; + + pszProj = getenv("QPROJECT"); + + if (pszProj != NULL) + strcpy(qproject, pszProj); + else + strcpy(qproject, "quiver"); + } + +try_again: + + for (c=path ; *c ; c++) + { + int iSize = 0; + + if (!Q_strncasecmp( c, qproject, strlen( qproject ) ) ) + iSize = strlen( qproject ) + 1; + + if (iSize > 0) + { + strncpy (qdir, path, c + iSize - path); + printf ("qdir: %s\n", qdir); + c += iSize; + while (*c) + { + if (*c == '/' || *c == '\\') + { + strncpy (gamedir, path, c+1-path); + printf ("gamedir: %s\n", gamedir); + return; + } + c++; + } + Error ("No gamedir in %s", path); + return; + } + } + + if (!strcmp(qproject, "quiver")) + { + strcpy(qproject, "prospero"); + goto try_again; + } + + Error ("SetQdirFromPath: no '%s' in %s", qproject, path); +#endif +} + + +char *ExpandArg (char *path) +{ + static char full[1024]; + + if (path[0] != '/' && path[0] != '\\' && path[1] != ':') + { + Q_getwd (full); + strcat (full, path); + } + else + strcpy (full, path); + return full; +} + +char *ExpandPath (char *path) +{ + char *psz; + static char full[1024]; + if (!qdir) + Error ("ExpandPath called without qdir set"); + if (path[0] == '/' || path[0] == '\\' || path[1] == ':') + return path; + psz = strstr(path, qdir); + if (psz) + strcpy(full, path); + else + sprintf (full, "%s%s", qdir, path); + + return full; +} + +char *ExpandPathAndArchive (char *path) +{ + char *expanded; + char archivename[1024]; + + expanded = ExpandPath (path); + + if (archive) + { + sprintf (archivename, "%s/%s", archivedir, path); + QCopyFile (expanded, archivename); + } + return expanded; +} + + +char *copystring(char *s) +{ + char *b; + b = malloc(strlen(s)+1); + strcpy (b, s); + return b; +} + + + +/* +================ +I_FloatTime +================ +*/ +double I_FloatTime (void) +{ + time_t t; + + time (&t); + + return t; +#if 0 +// more precise, less portable + struct timeval tp; + struct timezone tzp; + static int secbase; + + gettimeofday(&tp, &tzp); + + if (!secbase) + { + secbase = tp.tv_sec; + return tp.tv_usec/1000000.0; + } + + return (tp.tv_sec - secbase) + tp.tv_usec/1000000.0; +#endif +} + +void Q_getwd (char *out) +{ +#ifdef WIN32 + _getcwd (out, 256); + strcat (out, "\\"); +#else + getwd (out); +#endif +} + + +void Q_mkdir (char *path) +{ +#ifdef WIN32 + if (_mkdir (path) != -1) + return; +#else + if (mkdir (path, 0777) != -1) + return; +#endif + if (errno != EEXIST) + Error ("mkdir %s: %s",path, strerror(errno)); +} + +/* +============ +FileTime + +returns -1 if not present +============ +*/ +int FileTime (char *path) +{ + struct stat buf; + + if (stat (path,&buf) == -1) + return -1; + + return buf.st_mtime; +} + + + +/* +============== +COM_Parse + + Parse a token out of a string (into global: char com_token[1024]) +============== +*/ +char *COM_Parse (char *data) +{ + int c; + int len; + + len = 0; + com_token[0] = 0; + + if (!data) + return NULL; + +// skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + { + com_eof = true; + return NULL; // end of file; + } + data++; + } + +// skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + + +// handle quoted strings specially + if (c == '\"') + { + data++; + do + { + c = *data++; + if (c=='\"') + { + com_token[len] = 0; + return data; + } + com_token[len] = c; + len++; + } while (1); + } + +// parse single characters + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':') + { + com_token[len] = c; + len++; + com_token[len] = 0; + return data+1; + } + +// parse a regular word + do + { + com_token[len] = c; + data++; + len++; + c = *data; + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':') + break; + } while (c>32); + + com_token[len] = 0; + return data; +} + + +int Q_strncasecmp (char *s1, char *s2, int n) +{ + int c1, c2; + + while (1) + { + 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 not equal + } + if (!c1) + return 0; // strings are equal + } + + return -1; +} + +int Q_strcasecmp (char *s1, char *s2) +{ + return Q_strncasecmp (s1, s2, 99999); +} + + +char *strupr (char *start) +{ + char *in; + in = start; + while (*in) + { + *in = toupper(*in); + in++; + } + return start; +} + +char *strlower (char *start) +{ + char *in; + in = start; + while (*in) + { + *in = tolower(*in); + in++; + } + return start; +} + + +/* +============================================================================= + + MISC FUNCTIONS + +============================================================================= +*/ + + +/* +================= +CheckParm + +Checks for the given parameter in the program's command line arguments +Returns the argument number (1 to argc-1) or 0 if not present +================= +*/ +int CheckParm (char *check) +{ + int i; + + for (i = 1;i 0 && !PATHSEPARATOR(path[length])) + length--; + path[length] = 0; +} + +void StripExtension (char *path) +{ + int length; + + length = strlen(path)-1; + while (length > 0 && path[length] != '.') + { + length--; + if (path[length] == '/') + return; // no extension + } + if (length) + path[length] = 0; +} + + +/* +==================== +Extract file parts +==================== +*/ +void ExtractFilePath (char *path, char *dest) +{ + char *src; + + src = path + strlen(path) - 1; + +// +// back up until a \ or the start +// + while (src != path && !PATHSEPARATOR(*(src-1))) + src--; + + memcpy (dest, path, src-path); + dest[src-path] = 0; +} + +void ExtractFileBase (char *path, char *dest) +{ + char *src; + + src = path + strlen(path) - 1; + +// +// back up until a \ or the start +// + while (src != path && !PATHSEPARATOR(*(src-1))) + src--; + + while (*src && *src != '.') + { + *dest++ = *src++; + } + *dest = 0; +} + +void ExtractFileExtension (char *path, char *dest) +{ + char *src; + + src = path + strlen(path) - 1; + +// +// back up until a . or the start +// + while (src != path && *(src-1) != '.') + src--; + if (src == path) + { + *dest = 0; // no extension + return; + } + + strcpy (dest,src); +} + + +/* +============== +ParseNum / ParseHex +============== +*/ +int ParseHex (char *hex) +{ + 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 + Error ("Bad hex number: %s",hex); + str++; + } + + return num; +} + + +int ParseNum (char *str) +{ + if (str[0] == '$') + return ParseHex (str+1); + if (str[0] == '0' && str[1] == 'x') + return ParseHex (str+2); + return atol (str); +} + + + +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ + +#ifdef _SGI_SOURCE +#define __BIG_ENDIAN__ +#endif + +#ifdef __BIG_ENDIAN__ + +short LittleShort (short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +short BigShort (short l) +{ + return l; +} + + +int LittleLong (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 BigLong (int l) +{ + return l; +} + + +float LittleFloat (float l) +{ + union {byte b[4]; float f;} in, out; + + in.f = l; + out.b[0] = in.b[3]; + out.b[1] = in.b[2]; + out.b[2] = in.b[1]; + out.b[3] = in.b[0]; + + return out.f; +} + +float BigFloat (float l) +{ + return l; +} + + +#else + + +short BigShort (short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +short LittleShort (short l) +{ + return l; +} + + +int BigLong (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 LittleLong (int l) +{ + return l; +} + +float BigFloat (float l) +{ + union {byte b[4]; float f;} in, out; + + in.f = l; + out.b[0] = in.b[3]; + out.b[1] = in.b[2]; + out.b[2] = in.b[1]; + out.b[3] = in.b[0]; + + return out.f; +} + +float LittleFloat (float l) +{ + return l; +} + + +#endif + + +//======================================================= + + +// FIXME: byte swap? + +// this is a 16 bit, non-reflected CRC using the polynomial 0x1021 +// and the initial and final xor values shown below... in other words, the +// CCITT standard CRC used by XMODEM + +#define CRC_INIT_VALUE 0xffff +#define CRC_XOR_VALUE 0x0000 + +static unsigned short crctable[256] = +{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +}; + +void CRC_Init(unsigned short *crcvalue) +{ + *crcvalue = CRC_INIT_VALUE; +} + +void CRC_ProcessByte(unsigned short *crcvalue, byte data) +{ + *crcvalue = (*crcvalue << 8) ^ crctable[(*crcvalue >> 8) ^ data]; +} + +unsigned short CRC_Value(unsigned short crcvalue) +{ + return crcvalue ^ CRC_XOR_VALUE; +} +//============================================================================= + +/* +============ +CreatePath +============ +*/ +void CreatePath (char *path) +{ + char *ofs, c; + + for (ofs = path+1 ; *ofs ; ofs++) + { + c = *ofs; + if (c == '/' || c == '\\') + { // create the directory + *ofs = 0; + Q_mkdir (path); + *ofs = c; + } + } +} + + +/* +============ +QCopyFile + + Used to archive source files +============ +*/ +void QCopyFile (char *from, char *to) +{ + void *buffer; + int length; + + length = LoadFile (from, &buffer); + CreatePath (to); + SaveFile (to, buffer, length); + free (buffer); +} + + +/* +============ +ListPak + + Prints the contents of the specified pak file to stdout +============ +*/ + +void ListPak(char* pakname) +{ + FILE* f = SafeOpenRead(pakname); + packheader_t head; + packfile_t* pdir; + long i=0,imax=0; + long totlen=0; + + SafeRead(f,&head,sizeof(packheader_t)); + pdir=malloc(head.dirlen); + + fseek(f,head.dirofs,SEEK_SET); + SafeRead(f,pdir,head.dirlen); + + fseek(f,0,SEEK_END); + totlen=ftell(f); + + fclose(f); + + imax=head.dirlen/sizeof(packfile_t); + + for(i;i +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifndef __CMDUTIL__ +#define __CMDUTIL__ +#ifndef _NOENUMQBOOL +typedef enum {false, true} qboolean; +#else +typedef int qboolean; +#undef true +#undef false +#define true 1 +#define false 0 +#endif + +typedef unsigned char byte; +#endif + +// the dec offsetof macro doesn't work very well... +#define myoffsetof(type,identifier) ((size_t)&((type *)0)->identifier) + + +// set these before calling CheckParm +extern int myargc; +extern char **myargv; + +void COM_FixSlashes( char *pname ); +char *strupr (char *in); +char *strlower (char *in); +int Q_strncasecmp (char *s1, char *s2, int n); +int Q_strcasecmp (char *s1, char *s2); +void Q_getwd (char *out); + +int filelength (FILE *f); +int FileTime (char *path); + +void Q_mkdir (char *path); + +extern char qdir[1024]; +extern char gamedir[1024]; +void SetQdirFromPath (char *path); +char *ExpandArg (char *path); // from cmd line +char *ExpandPath (char *path); // from scripts +char *ExpandPathAndArchive (char *path); + + +double I_FloatTime (void); + +void Error (char *error, ...); +int CheckParm (char *check); + +FILE *SafeOpenWrite (char *filename); +FILE *SafeOpenRead (char *filename); +void SafeRead (FILE *f, void *buffer, int count); +void SafeWrite (FILE *f, void *buffer, int count); + +int LoadFile (char *filename, void **bufferptr); +void SaveFile (char *filename, void *buffer, int count); + +void DefaultExtension (char *path, char *extension); +void DefaultPath (char *path, char *basepath); +void StripFilename (char *path); +void StripExtension (char *path); + +void ExtractFilePath (char *path, char *dest); +void ExtractFileBase (char *path, char *dest); +void ExtractFileExtension (char *path, char *dest); + +int ParseNum (char *str); + +short BigShort (short l); +short LittleShort (short l); +int BigLong (int l); +int LittleLong (int l); +float BigFloat (float l); +float LittleFloat (float l); + +long flen(FILE* f); + + + +char *COM_Parse (char *data); + +extern char com_token[1024]; +extern qboolean com_eof; + +char *copystring(char *s); + + +void CRC_Init(unsigned short *crcvalue); +void CRC_ProcessByte(unsigned short *crcvalue, byte data); +unsigned short CRC_Value(unsigned short crcvalue); + +void CreatePath (char *path); +void QCopyFile (char *from, char *to); + +extern qboolean archive; +extern char archivedir[1024]; + + +extern qboolean verbose; +void qprintf (char *format, ...); + + +typedef struct +{ + char name[56]; + int filepos, filelen; +} packfile_t; + +typedef struct +{ + char id[4]; + int dirofs; + int dirlen; +} packheader_t; + + +void ListPak(char* pakname); + +#endif +#ifdef __cplusplus +} +#endif + diff --git a/utils/common/l3dslib.c b/utils/common/l3dslib.c new file mode 100644 index 0000000..87df498 --- /dev/null +++ b/utils/common/l3dslib.c @@ -0,0 +1,282 @@ +// +// l3dslib.c: library for loading triangles from an Alias triangle file +// + +#include +#include "cmdlib.h" +#include "mathlib.h" +#include "trilib.h" +#include "l3dslib.h" + +#define MAIN3DS 0x4D4D +#define EDIT3DS 0x3D3D // this is the start of the editor config +#define EDIT_OBJECT 0x4000 +#define OBJ_TRIMESH 0x4100 +#define TRI_VERTEXL 0x4110 +#define TRI_FACEL1 0x4120 + +#define MAXVERTS 2000 + +typedef struct { + int v[4]; +} tri; + +float fverts[MAXVERTS][3]; +tri tris[MAXTRIANGLES]; + +int bytesread, level, numtris, totaltris; +int vertsfound, trisfound; + +triangle_t *ptri; + + +// Alias stores triangles as 3 explicit vertices in .tri files, so even though we +// start out with a vertex pool and vertex indices for triangles, we have to convert +// to raw, explicit triangles +void StoreAliasTriangles (void) +{ + int i, j, k; + + if ((totaltris + numtris) > MAXTRIANGLES) + Error ("Error: Too many triangles"); + + for (i=0; i MAXVERTS) + Error ("Error: Too many vertices"); + + for (i=0 ; i MAXTRIANGLES) + Error ("Error: Too many triangles"); + + for (i=0 ; i 0) + { + w -= ParseChunk (input); + } + + retval = length; + goto Done; + + default: + // skip other chunks + while (w > 0) + { + t = w; + + if (t > BLOCK_SIZE) + t = BLOCK_SIZE; + + if (feof(input)) + Error ("Error: unexpected end of file"); + + fread (&temp, t, 1, input); + bytesread += t; + + w -= t; + } + + retval = length; + goto Done; + } + +Done: + level--; + return retval; +} + + +void Load3DSTriangleList (char *filename, triangle_t **pptri, int *numtriangles) +{ + FILE *input; + float start; + char name[256], tex[256]; + short int tshort; + + bytesread = 0; + level = 0; + numtris = 0; + totaltris = 0; + vertsfound = 0; + trisfound = 0; + + if ((input = fopen(filename, "rb")) == 0) { + fprintf(stderr,"reader: could not open file '%s'\n", filename); + exit(0); + } + + fread(&tshort, sizeof(tshort), 1, input); + +// should only be MAIN3DS, but some files seem to start with EDIT3DS, with +// no MAIN3DS + if ((tshort != MAIN3DS) && (tshort != EDIT3DS)) { + fprintf(stderr,"File is not a 3DS file.\n"); + exit(0); + } + +// back to top of file so we can parse the first chunk descriptor + fseek(input, 0, SEEK_SET); + + ptri = malloc (MAXTRIANGLES * sizeof(triangle_t)); + + *pptri = ptri; + +// parse through looking for the relevant chunk tree (MAIN3DS | EDIT3DS | EDIT_OBJECT | +// OBJ_TRIMESH | {TRI_VERTEXL, TRI_FACEL1}) and skipping other chunks + ParseChunk (input); + + if (vertsfound || trisfound) + Error ("Incomplete triangle set"); + + *numtriangles = totaltris; + + fclose (input); +} + diff --git a/utils/common/l3dslib.h b/utils/common/l3dslib.h new file mode 100644 index 0000000..f8e150f --- /dev/null +++ b/utils/common/l3dslib.h @@ -0,0 +1,5 @@ +// +// l3dslib.h: header file for loading triangles from a 3DS triangle file +// +void Load3DSTriangleList (char *filename, triangle_t **pptri, int *numtriangles); + diff --git a/utils/common/lbmlib.c b/utils/common/lbmlib.c new file mode 100644 index 0000000..045dbdf --- /dev/null +++ b/utils/common/lbmlib.c @@ -0,0 +1,744 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// lbmlib.c + +#include +#include + +#include "cmdlib.h" +#include "lbmlib.h" + + + +/* +============================================================================ + + LBM STUFF + +============================================================================ +*/ + + +#define FORMID ('F'+('O'<<8)+((int)'R'<<16)+((int)'M'<<24)) +#define ILBMID ('I'+('L'<<8)+((int)'B'<<16)+((int)'M'<<24)) +#define PBMID ('P'+('B'<<8)+((int)'M'<<16)+((int)' '<<24)) +#define BMHDID ('B'+('M'<<8)+((int)'H'<<16)+((int)'D'<<24)) +#define BODYID ('B'+('O'<<8)+((int)'D'<<16)+((int)'Y'<<24)) +#define CMAPID ('C'+('M'<<8)+((int)'A'<<16)+((int)'P'<<24)) + + +bmhd_t bmhd; + +int Align (int l) +{ + if (l&1) + return l+1; + return l; +} + + + +/* +================ += += LBMRLEdecompress += += Source must be evenly aligned! += +================ +*/ + +byte *LBMRLEDecompress (byte *source,byte *unpacked, int bpwidth) +{ + int count; + byte b,rept; + + count = 0; + + do + { + rept = *source++; + + if (rept > 0x80) + { + rept = (rept^0xff)+2; + b = *source++; + memset(unpacked,b,rept); + unpacked += rept; + } + else if (rept < 0x80) + { + rept++; + memcpy(unpacked,source,rept); + unpacked += rept; + source += rept; + } + else + rept = 0; // rept of 0x80 is NOP + + count += rept; + + } while (countbpwidth) + Error ("Decompression exceeded width!\n"); + + + return source; +} + + +#define BPLANESIZE 128 +byte bitplanes[9][BPLANESIZE]; // max size 1024 by 9 bit planes + + +/* +================= += += MungeBitPlanes8 += += This destroys the bit plane data! += +================= +*/ + +void MungeBitPlanes8 (int width, byte *dest) +{ + *dest=width; // shut up the compiler warning + Error ("MungeBitPlanes8 not rewritten!"); +#if 0 +asm les di,[dest] +asm mov si,-1 +asm mov cx,[width] +mungebyte: +asm inc si +asm mov dx,8 +mungebit: +asm shl [BYTE PTR bitplanes + BPLANESIZE*7 +si],1 +asm rcl al,1 +asm shl [BYTE PTR bitplanes + BPLANESIZE*6 +si],1 +asm rcl al,1 +asm shl [BYTE PTR bitplanes + BPLANESIZE*5 +si],1 +asm rcl al,1 +asm shl [BYTE PTR bitplanes + BPLANESIZE*4 +si],1 +asm rcl al,1 +asm shl [BYTE PTR bitplanes + BPLANESIZE*3 +si],1 +asm rcl al,1 +asm shl [BYTE PTR bitplanes + BPLANESIZE*2 +si],1 +asm rcl al,1 +asm shl [BYTE PTR bitplanes + BPLANESIZE*1 +si],1 +asm rcl al,1 +asm shl [BYTE PTR bitplanes + BPLANESIZE*0 +si],1 +asm rcl al,1 +asm stosb +asm dec cx +asm jz done +asm dec dx +asm jnz mungebit +asm jmp mungebyte + +done: +#endif +} + + +void MungeBitPlanes4 (int width, byte *dest) +{ + *dest=width; // shut up the compiler warning + Error ("MungeBitPlanes4 not rewritten!"); +#if 0 + +asm les di,[dest] +asm mov si,-1 +asm mov cx,[width] +mungebyte: +asm inc si +asm mov dx,8 +mungebit: +asm xor al,al +asm shl [BYTE PTR bitplanes + BPLANESIZE*3 +si],1 +asm rcl al,1 +asm shl [BYTE PTR bitplanes + BPLANESIZE*2 +si],1 +asm rcl al,1 +asm shl [BYTE PTR bitplanes + BPLANESIZE*1 +si],1 +asm rcl al,1 +asm shl [BYTE PTR bitplanes + BPLANESIZE*0 +si],1 +asm rcl al,1 +asm stosb +asm dec cx +asm jz done +asm dec dx +asm jnz mungebit +asm jmp mungebyte + +done: +#endif +} + + +void MungeBitPlanes2 (int width, byte *dest) +{ + *dest=width; // shut up the compiler warning + Error ("MungeBitPlanes2 not rewritten!"); +#if 0 +asm les di,[dest] +asm mov si,-1 +asm mov cx,[width] +mungebyte: +asm inc si +asm mov dx,8 +mungebit: +asm xor al,al +asm shl [BYTE PTR bitplanes + BPLANESIZE*1 +si],1 +asm rcl al,1 +asm shl [BYTE PTR bitplanes + BPLANESIZE*0 +si],1 +asm rcl al,1 +asm stosb +asm dec cx +asm jz done +asm dec dx +asm jnz mungebit +asm jmp mungebyte + +done: +#endif +} + + +void MungeBitPlanes1 (int width, byte *dest) +{ + *dest=width; // shut up the compiler warning + Error ("MungeBitPlanes1 not rewritten!"); +#if 0 +asm les di,[dest] +asm mov si,-1 +asm mov cx,[width] +mungebyte: +asm inc si +asm mov dx,8 +mungebit: +asm xor al,al +asm shl [BYTE PTR bitplanes + BPLANESIZE*0 +si],1 +asm rcl al,1 +asm stosb +asm dec cx +asm jz done +asm dec dx +asm jnz mungebit +asm jmp mungebyte + +done: +#endif +} + +int LoadBMP (const char* szFile, BYTE** ppbBits, BYTE** ppbPalette) +{ + int i, rc = 0; + FILE *pfile = NULL; + BITMAPFILEHEADER bmfh; + BITMAPINFOHEADER bmih; + RGBQUAD rgrgbPalette[256]; + ULONG cbBmpBits; + BYTE* pbBmpBits; + byte *pb, *pbPal = NULL; + ULONG cbPalBytes; + ULONG biTrueWidth; + + // Bogus parameter check + if (!(ppbPalette != NULL && ppbBits != NULL)) + { fprintf(stderr, "invalid BMP file\n"); rc = -1000; goto GetOut; } + + // File exists? + if ((pfile = fopen(szFile, "rb")) == NULL) + { fprintf(stderr, "unable to open BMP file\n"); rc = -1; goto GetOut; } + + // Read file header + if (fread(&bmfh, sizeof bmfh, 1/*count*/, pfile) != 1) + { rc = -2; goto GetOut; } + + // Bogus file header check + if (!(bmfh.bfReserved1 == 0 && bmfh.bfReserved2 == 0)) + { rc = -2000; goto GetOut; } + + // Read info header + if (fread(&bmih, sizeof bmih, 1/*count*/, pfile) != 1) + { rc = -3; goto GetOut; } + + // Bogus info header check + if (!(bmih.biSize == sizeof bmih && bmih.biPlanes == 1)) + { fprintf(stderr, "invalid BMP file header\n"); rc = -3000; goto GetOut; } + + // Bogus bit depth? Only 8-bit supported. + if (bmih.biBitCount != 8) + { fprintf(stderr, "BMP file not 8 bit\n"); rc = -4; goto GetOut; } + + // Bogus compression? Only non-compressed supported. + if (bmih.biCompression != BI_RGB) + { fprintf(stderr, "invalid BMP compression type\n"); rc = -5; goto GetOut; } + + // Figure out how many entires are actually in the table + if (bmih.biClrUsed == 0) + { + bmih.biClrUsed = 256; + cbPalBytes = (1 << bmih.biBitCount) * sizeof( RGBQUAD ); + } + else + { + cbPalBytes = bmih.biClrUsed * sizeof( RGBQUAD ); + } + + // Read palette (bmih.biClrUsed entries) + if (fread(rgrgbPalette, cbPalBytes, 1/*count*/, pfile) != 1) + { rc = -6; goto GetOut; } + + // convert to a packed 768 byte palette + pbPal = malloc(768); + if (pbPal == NULL) + { rc = -7; goto GetOut; } + + pb = pbPal; + + // Copy over used entries + for (i = 0; i < (int)bmih.biClrUsed; i++) + { + *pb++ = rgrgbPalette[i].rgbRed; + *pb++ = rgrgbPalette[i].rgbGreen; + *pb++ = rgrgbPalette[i].rgbBlue; + } + + // Fill in unused entires will 0,0,0 + for (i = bmih.biClrUsed; i < 256; i++) + { + *pb++ = 0; + *pb++ = 0; + *pb++ = 0; + } + + // Read bitmap bits (remainder of file) + cbBmpBits = bmfh.bfSize - ftell(pfile); + pb = malloc(cbBmpBits); + if (fread(pb, cbBmpBits, 1/*count*/, pfile) != 1) + { rc = -7; goto GetOut; } + + pbBmpBits = malloc(cbBmpBits); + + // data is actually stored with the width being rounded up to a multiple of 4 + biTrueWidth = (bmih.biWidth + 3) & ~3; + + // reverse the order of the data. + pb += (bmih.biHeight - 1) * biTrueWidth; + for(i = 0; i < bmih.biHeight; i++) + { + memmove(&pbBmpBits[biTrueWidth * i], pb, biTrueWidth); + pb -= biTrueWidth; + } + + pb += biTrueWidth; + free(pb); + + bmhd.w = (WORD)bmih.biWidth; + bmhd.h = (WORD)bmih.biHeight; + // Set output parameters + *ppbPalette = pbPal; + *ppbBits = pbBmpBits; + +GetOut: + if (pfile) + fclose(pfile); + + return rc; +} + + +int WriteBMPfile (char *szFile, byte *pbBits, int width, int height, byte *pbPalette) +{ + int i, rc = 0; + FILE *pfile = NULL; + BITMAPFILEHEADER bmfh; + BITMAPINFOHEADER bmih; + RGBQUAD rgrgbPalette[256]; + ULONG cbBmpBits; + BYTE* pbBmpBits; + byte *pb, *pbPal = NULL; + ULONG cbPalBytes; + ULONG biTrueWidth; + + // Bogus parameter check + if (!(pbPalette != NULL && pbBits != NULL)) + { rc = -1000; goto GetOut; } + + // File exists? + if ((pfile = fopen(szFile, "wb")) == NULL) + { rc = -1; goto GetOut; } + + biTrueWidth = ((width + 3) & ~3); + cbBmpBits = biTrueWidth * height; + cbPalBytes = 256 * sizeof( RGBQUAD ); + + // Bogus file header check + bmfh.bfType = MAKEWORD( 'B', 'M' ); + bmfh.bfSize = sizeof bmfh + sizeof bmih + cbBmpBits + cbPalBytes; + bmfh.bfReserved1 = 0; + bmfh.bfReserved2 = 0; + bmfh.bfOffBits = sizeof bmfh + sizeof bmih + cbPalBytes; + + // Write file header + if (fwrite(&bmfh, sizeof bmfh, 1/*count*/, pfile) != 1) + { rc = -2; goto GetOut; } + + // Size of structure + bmih.biSize = sizeof bmih; + // Width + bmih.biWidth = biTrueWidth; + // Height + bmih.biHeight = height; + // Only 1 plane + bmih.biPlanes = 1; + // Only 8-bit supported. + bmih.biBitCount = 8; + // Only non-compressed supported. + bmih.biCompression = BI_RGB; + bmih.biSizeImage = 0; + + // huh? + bmih.biXPelsPerMeter = 0; + bmih.biYPelsPerMeter = 0; + + // Always full palette + bmih.biClrUsed = 256; + bmih.biClrImportant = 0; + + // Write info header + if (fwrite(&bmih, sizeof bmih, 1/*count*/, pfile) != 1) + { rc = -3; goto GetOut; } + + + // convert to expanded palette + pb = pbPalette; + + // Copy over used entries + for (i = 0; i < (int)bmih.biClrUsed; i++) + { + rgrgbPalette[i].rgbRed = *pb++; + rgrgbPalette[i].rgbGreen = *pb++; + rgrgbPalette[i].rgbBlue = *pb++; + rgrgbPalette[i].rgbReserved = 0; + } + + // Write palette (bmih.biClrUsed entries) + cbPalBytes = bmih.biClrUsed * sizeof( RGBQUAD ); + if (fwrite(rgrgbPalette, cbPalBytes, 1/*count*/, pfile) != 1) + { rc = -6; goto GetOut; } + + + pbBmpBits = malloc(cbBmpBits); + + pb = pbBits; + // reverse the order of the data. + pb += (height - 1) * width; + for(i = 0; i < bmih.biHeight; i++) + { + memmove(&pbBmpBits[biTrueWidth * i], pb, width); + pb -= width; + } + + // Write bitmap bits (remainder of file) + if (fwrite(pbBmpBits, cbBmpBits, 1/*count*/, pfile) != 1) + { rc = -7; goto GetOut; } + + free(pbBmpBits); + +GetOut: + if (pfile) + fclose(pfile); + + return rc; +} + +/* +================= += += LoadLBM += +================= +*/ + +void LoadLBM (char *filename, byte **picture, byte **palette) +{ + byte *LBMbuffer, *picbuffer, *cmapbuffer; + int y,p,planes; + byte *LBM_P, *LBMEND_P; + byte *pic_p; + byte *body_p; + unsigned rowsize; + + int formtype,formlength; + int chunktype,chunklength; + void (*mungecall) (int, byte *); + +// qiet compiler warnings + picbuffer = NULL; + cmapbuffer = NULL; + mungecall = NULL; + +// +// load the LBM +// + LoadFile (filename, (void **)&LBMbuffer); + +// +// parse the LBM header +// + LBM_P = LBMbuffer; + if ( *(int *)LBMbuffer != LittleLong(FORMID) ) + Error ("No FORM ID at start of file!\n"); + + LBM_P += 4; + formlength = BigLong( *(int *)LBM_P ); + LBM_P += 4; + LBMEND_P = LBM_P + Align(formlength); + + formtype = LittleLong(*(int *)LBM_P); + + if (formtype != ILBMID && formtype != PBMID) + Error ("Unrecognized form type: %c%c%c%c\n", formtype&0xff + ,(formtype>>8)&0xff,(formtype>>16)&0xff,(formtype>>24)&0xff); + + LBM_P += 4; + +// +// parse chunks +// + + while (LBM_P < LBMEND_P) + { + chunktype = LBM_P[0] + (LBM_P[1]<<8) + (LBM_P[2]<<16) + (LBM_P[3]<<24); + LBM_P += 4; + chunklength = LBM_P[3] + (LBM_P[2]<<8) + (LBM_P[1]<<16) + (LBM_P[0]<<24); + LBM_P += 4; + + switch ( chunktype ) + { + case BMHDID: + memcpy (&bmhd,LBM_P,sizeof(bmhd)); + bmhd.w = BigShort(bmhd.w); + bmhd.h = BigShort(bmhd.h); + bmhd.x = BigShort(bmhd.x); + bmhd.y = BigShort(bmhd.y); + bmhd.pageWidth = BigShort(bmhd.pageWidth); + bmhd.pageHeight = BigShort(bmhd.pageHeight); + break; + + case CMAPID: + cmapbuffer = malloc (768); + memset (cmapbuffer, 0, 768); + memcpy (cmapbuffer, LBM_P, chunklength); + break; + + case BODYID: + body_p = LBM_P; + + pic_p = picbuffer = malloc (bmhd.w*bmhd.h); + if (formtype == PBMID) + { + // + // unpack PBM + // + for (y=0 ; y EQUAL_EPSILON) + return false; + + return true; +} + +vec_t Q_rint (vec_t in) +{ + return floor (in + 0.5); +} + +void VectorMA (vec3_t va, double scale, vec3_t vb, vec3_t vc) +{ + vc[0] = va[0] + scale*vb[0]; + vc[1] = va[1] + scale*vb[1]; + vc[2] = va[2] + scale*vb[2]; +} + +void CrossProduct (vec3_t v1, 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 _DotProduct (vec3_t v1, vec3_t v2) +{ + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract (vec3_t va, vec3_t vb, vec3_t out) +{ + out[0] = va[0]-vb[0]; + out[1] = va[1]-vb[1]; + out[2] = va[2]-vb[2]; +} + +void _VectorAdd (vec3_t va, vec3_t vb, vec3_t out) +{ + out[0] = va[0]+vb[0]; + out[1] = va[1]+vb[1]; + out[2] = va[2]+vb[2]; +} + +void _VectorCopy (vec3_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +void _VectorScale (vec3_t v, vec_t scale, vec3_t out) +{ + out[0] = v[0] * scale; + out[1] = v[1] * scale; + out[2] = v[2] * scale; +} + +vec_t VectorNormalize (vec3_t v) +{ + int i; + double length; + +if ( fabs(v[1] - 0.000215956) < 0.0001) +i=1; + + length = 0; + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); + if (length == 0) + return 0; + + for (i=0 ; i< 3 ; i++) + v[i] /= length; + + return length; +} + +void VectorInverse (vec3_t v) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void ClearBounds (vec3_t mins, vec3_t maxs) +{ + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; +} + +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs) +{ + int i; + vec_t val; + + for (i=0 ; i<3 ; i++) + { + val = v[i]; + if (val < mins[i]) + mins[i] = val; + if (val > maxs[i]) + maxs[i] = val; + } +} + +void AngleMatrix (const vec3_t angles, float (*matrix)[4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[2] * (Q_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[1] * (Q_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[0] * (Q_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + // matrix = (Z * Y) * X + matrix[0][0] = cp*cy; + matrix[1][0] = cp*sy; + matrix[2][0] = -sp; + matrix[0][1] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[2][1] = sr*cp; + matrix[0][2] = (cr*sp*cy+-sr*-sy); + matrix[1][2] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + +void AngleIMatrix (const vec3_t angles, float matrix[3][4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[2] * (Q_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[1] * (Q_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[0] * (Q_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + // matrix = (Z * Y) * X + matrix[0][0] = cp*cy; + matrix[0][1] = cp*sy; + matrix[0][2] = -sp; + matrix[1][0] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[1][2] = sr*cp; + matrix[2][0] = (cr*sp*cy+-sr*-sy); + matrix[2][1] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + +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]; +} + + + +void VectorRotate (const vec3_t in1, const float in2[3][4], vec3_t out) +{ + out[0] = DotProduct(in1, in2[0]); + out[1] = DotProduct(in1, in2[1]); + out[2] = DotProduct(in1, in2[2]); +} + + +// rotate by the inverse of the matrix +void VectorIRotate (const vec3_t in1, const float in2[3][4], vec3_t out) +{ + out[0] = in1[0]*in2[0][0] + in1[1]*in2[1][0] + in1[2]*in2[2][0]; + out[1] = in1[0]*in2[0][1] + in1[1]*in2[1][1] + in1[2]*in2[2][1]; + out[2] = in1[0]*in2[0][2] + in1[1]*in2[1][2] + in1[2]*in2[2][2]; +} + + +void VectorTransform (const vec3_t in1, const float in2[3][4], vec3_t out) +{ + out[0] = DotProduct(in1, in2[0]) + in2[0][3]; + out[1] = DotProduct(in1, in2[1]) + in2[1][3]; + out[2] = DotProduct(in1, in2[2]) + in2[2][3]; +} + + + +void AngleQuaternion( const vec3_t angles, vec4_t quaternion ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + // FIXME: rescale the inputs to 1/2 angle + angle = angles[2] * 0.5; + sy = sin(angle); + cy = cos(angle); + angle = angles[1] * 0.5; + sp = sin(angle); + cp = cos(angle); + angle = angles[0] * 0.5; + sr = sin(angle); + cr = cos(angle); + + quaternion[0] = sr*cp*cy-cr*sp*sy; // X + quaternion[1] = cr*sp*cy+sr*cp*sy; // Y + quaternion[2] = cr*cp*sy-sr*sp*cy; // Z + quaternion[3] = cr*cp*cy+sr*sp*sy; // W +} + +void QuaternionMatrix( const vec4_t quaternion, float (*matrix)[4] ) +{ + + matrix[0][0] = 1.0 - 2.0 * quaternion[1] * quaternion[1] - 2.0 * quaternion[2] * quaternion[2]; + matrix[1][0] = 2.0 * quaternion[0] * quaternion[1] + 2.0 * quaternion[3] * quaternion[2]; + matrix[2][0] = 2.0 * quaternion[0] * quaternion[2] - 2.0 * quaternion[3] * quaternion[1]; + + matrix[0][1] = 2.0 * quaternion[0] * quaternion[1] - 2.0 * quaternion[3] * quaternion[2]; + matrix[1][1] = 1.0 - 2.0 * quaternion[0] * quaternion[0] - 2.0 * quaternion[2] * quaternion[2]; + matrix[2][1] = 2.0 * quaternion[1] * quaternion[2] + 2.0 * quaternion[3] * quaternion[0]; + + matrix[0][2] = 2.0 * quaternion[0] * quaternion[2] + 2.0 * quaternion[3] * quaternion[1]; + matrix[1][2] = 2.0 * quaternion[1] * quaternion[2] - 2.0 * quaternion[3] * quaternion[0]; + matrix[2][2] = 1.0 - 2.0 * quaternion[0] * quaternion[0] - 2.0 * quaternion[1] * quaternion[1]; +} + +void QuaternionSlerp( const vec4_t p, vec4_t q, float t, vec4_t qt ) +{ + int i; + float omega, cosom, sinom, sclp, sclq; + + // decide if one of the quaternions is backwards + float a = 0; + float b = 0; + for (i = 0; i < 4; i++) { + a += (p[i]-q[i])*(p[i]-q[i]); + b += (p[i]+q[i])*(p[i]+q[i]); + } + if (a > b) { + for (i = 0; i < 4; i++) { + q[i] = -q[i]; + } + } + + cosom = p[0]*q[0] + p[1]*q[1] + p[2]*q[2] + p[3]*q[3]; + + if ((1.0 + cosom) > 0.00000001) { + if ((1.0 - cosom) > 0.00000001) { + omega = acos( cosom ); + sinom = sin( omega ); + sclp = sin( (1.0 - t)*omega) / sinom; + sclq = sin( t*omega ) / sinom; + } + else { + sclp = 1.0 - t; + sclq = t; + } + for (i = 0; i < 4; i++) { + qt[i] = sclp * p[i] + sclq * q[i]; + } + } + else { + qt[0] = -p[1]; + qt[1] = p[0]; + qt[2] = -p[3]; + qt[3] = p[2]; + sclp = sin( (1.0 - t) * 0.5 * Q_PI); + sclq = sin( t * 0.5 * Q_PI); + for (i = 0; i < 3; i++) { + qt[i] = sclp * p[i] + sclq * qt[i]; + } + } +} + + diff --git a/utils/common/mathlib.h b/utils/common/mathlib.h new file mode 100644 index 0000000..14861f1 --- /dev/null +++ b/utils/common/mathlib.h @@ -0,0 +1,89 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#ifndef __MATHLIB__ +#define __MATHLIB__ + +// mathlib.h + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef DOUBLEVEC_T +typedef double vec_t; +#else +typedef float vec_t; +#endif +typedef vec_t vec3_t[3]; // x,y,z +typedef vec_t vec4_t[4]; // x,y,z,w + +#define SIDE_FRONT 0 +#define SIDE_ON 2 +#define SIDE_BACK 1 +#define SIDE_CROSS -2 + +#define Q_PI 3.14159265358979323846 + +extern vec3_t vec3_origin; + +// Use this definition globally +#define ON_EPSILON 0.01 +#define EQUAL_EPSILON 0.001 + +int VectorCompare (vec3_t v1, vec3_t v2); + +#define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) +#define VectorFill(a,b) { (a)[0]=(b); (a)[1]=(b); (a)[2]=(b);} +#define VectorAvg(a) ( ( (a)[0] + (a)[1] + (a)[2] ) / 3 ) +#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(a,b,c) {(c)[0]=(b)*(a)[0];(c)[1]=(b)*(a)[1];(c)[2]=(b)*(a)[2];} + +vec_t Q_rint (vec_t in); +vec_t _DotProduct (vec3_t v1, vec3_t v2); +void _VectorSubtract (vec3_t va, vec3_t vb, vec3_t out); +void _VectorAdd (vec3_t va, vec3_t vb, vec3_t out); +void _VectorCopy (vec3_t in, vec3_t out); +void _VectorScale (vec3_t v, vec_t scale, vec3_t out); + +double VectorLength(vec3_t v); + +void VectorMA (vec3_t va, double scale, vec3_t vb, vec3_t vc); + +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross); +vec_t VectorNormalize (vec3_t v); +void VectorInverse (vec3_t v); + +void ClearBounds (vec3_t mins, vec3_t maxs); +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs); + +void AngleMatrix (const vec3_t angles, float matrix[3][4] ); +void AngleIMatrix (const vec3_t angles, float matrix[3][4] ); +void R_ConcatTransforms (const float in1[3][4], const float in2[3][4], float out[3][4]); + +void VectorIRotate (const vec3_t in1, const float in2[3][4], vec3_t out); +void VectorRotate (const vec3_t in1, const float in2[3][4], vec3_t out); + +void VectorTransform (const vec3_t in1, const float in2[3][4], vec3_t out); + +void AngleQuaternion( const vec3_t angles, vec4_t quaternion ); +void QuaternionMatrix( const vec4_t quaternion, float (*matrix)[4] ); +void QuaternionSlerp( const vec4_t p, vec4_t q, float t, vec4_t qt ); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/utils/common/meter.c b/utils/common/meter.c new file mode 100644 index 0000000..f6be819 --- /dev/null +++ b/utils/common/meter.c @@ -0,0 +1,34 @@ +/* + meter.c + + Implements a dorky progess meter +*/ + +int showmeter = 0; +static int meter_cur, meter_max; + +void MeterStart( int max ) +{ + meter_cur = 0; + meter_max = max; +} + + +void MeterAdvance( int amt ) +{ + float pct; + + meter_cur += amt; + + if( showmeter ) + { + pct = ( (float) meter_cur / (float) meter_max ) * 100.0; + printf( "\r%d/%d (%0.2f%%) ", meter_cur, meter_max, pct ); + } +} + + +void MeterEnd( void ) +{ + printf( "\n" ); +} diff --git a/utils/common/meter.h b/utils/common/meter.h new file mode 100644 index 0000000..2ed90aa --- /dev/null +++ b/utils/common/meter.h @@ -0,0 +1,16 @@ +#ifndef _METER_H_ +#define _METER_H_ + +/* + meter.h + + Dorky status bar stuff +*/ + +void MeterStart( int max ); +void MeterAdvance( int amt ); +void MeterEnd( void ); + +extern int showmeter; + +#endif \ No newline at end of file diff --git a/utils/common/movie.h b/utils/common/movie.h new file mode 100644 index 0000000..ac4bef7 --- /dev/null +++ b/utils/common/movie.h @@ -0,0 +1,36 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#ifndef _MOVIE_H_ +#define _MOVIE_H_ + +/* + movie.h + + definitions and such for dumping screen shots to make a movie +*/ + +typedef struct +{ + unsigned long tag; + unsigned long size; +} movieblockheader_t; + + +typedef struct +{ + short width; + short height; + short depth; +} movieframe_t; + + + +#endif _MOVIE_H_ \ No newline at end of file diff --git a/utils/common/polylib.c b/utils/common/polylib.c new file mode 100644 index 0000000..6420f5b --- /dev/null +++ b/utils/common/polylib.c @@ -0,0 +1,708 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + + +#include "cmdlib.h" +#include "mathlib.h" +#include "polylib.h" + +int c_active_windings; +int c_peak_windings; +int c_winding_allocs; +int c_winding_points; + +#define BOGUS_RANGE 8192 + +void pw(winding_t *w) +{ + int i; + for (i=0 ; inumpoints ; i++) + printf ("(%5.1f, %5.1f, %5.1f)\n",w->p[i][0], w->p[i][1],w->p[i][2]); +} + + +/* +============= +AllocWinding +============= +*/ +winding_t *AllocWinding (int points) +{ + winding_t *w; + int s; + + c_winding_allocs++; + c_winding_points += points; + c_active_windings++; + if (c_active_windings > c_peak_windings) + c_peak_windings = c_active_windings; + s = sizeof(vec_t)*3*points + sizeof(int); + s += sizeof(vec_t) - sizeof(w->numpoints); // padding + + w = malloc (s); + memset (w, 0, s); + + return w; +} + +void FreeWinding (winding_t *w) +{ + c_active_windings--; + free (w); +} + +/* +============ +RemoveColinearPoints +============ +*/ +int c_removed; + +void RemoveColinearPoints (winding_t *w) +{ + int i, j, k; + vec3_t v1, v2; + int nump; + vec3_t p[MAX_POINTS_ON_WINDING]; + + nump = 0; + for (i=0 ; inumpoints ; i++) + { + j = (i+1)%w->numpoints; + k = (i+w->numpoints-1)%w->numpoints; + VectorSubtract (w->p[j], w->p[i], v1); + VectorSubtract (w->p[i], w->p[k], v2); + VectorNormalize(v1); + VectorNormalize(v2); + if (DotProduct(v1, v2) < 1.0-ON_EPSILON) + { + VectorCopy (w->p[i], p[nump]); + nump++; + } + } + + if (nump == w->numpoints) + return; + + c_removed += w->numpoints - nump; + w->numpoints = nump; + memcpy (w->p, p, nump*sizeof(p[0])); +} + +/* +============ +WindingPlane +============ +*/ +void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist) +{ + vec3_t v1, v2; + + VectorSubtract (w->p[1], w->p[0], v1); + VectorSubtract (w->p[2], w->p[0], v2); + CrossProduct (v2, v1, normal); + VectorNormalize (normal); + *dist = DotProduct (w->p[0], normal); + +} + +/* +============= +WindingArea +============= +*/ +vec_t WindingArea (winding_t *w) +{ + int i; + vec3_t d1, d2, cross; + vec_t total; + + total = 0; + for (i=2 ; inumpoints ; i++) + { + VectorSubtract (w->p[i-1], w->p[0], d1); + VectorSubtract (w->p[i], w->p[0], d2); + CrossProduct (d1, d2, cross); + total += 0.5 * VectorLength ( cross ); + } + return total; +} + +void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs) +{ + vec_t v; + int i,j; + + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; + + for (i=0 ; inumpoints ; i++) + { + for (j=0 ; j<3 ; j++) + { + v = w->p[i][j]; + if (v < mins[j]) + mins[j] = v; + if (v > maxs[j]) + maxs[j] = v; + } + } +} + +/* +============= +WindingCenter +============= +*/ +void WindingCenter (winding_t *w, vec3_t center) +{ + int i; + vec3_t d1, d2, cross; + float scale; + + VectorCopy (vec3_origin, center); + for (i=0 ; inumpoints ; i++) + VectorAdd (w->p[i], center, center); + + scale = 1.0/w->numpoints; + VectorScale (center, scale, center); +} + +/* +================= +BaseWindingForPlane +================= +*/ +winding_t *BaseWindingForPlane (vec3_t normal, float dist) +{ + int i, x; + vec_t max, v; + vec3_t org, vright, vup; + winding_t *w; + +// find the major axis + + max = -BOGUS_RANGE; + x = -1; + for (i=0 ; i<3; i++) + { + v = fabs(normal[i]); + if (v > max) + { + x = i; + max = v; + } + } + if (x==-1) + Error ("BaseWindingForPlane: no axis found"); + + VectorCopy (vec3_origin, vup); + switch (x) + { + case 0: + case 1: + vup[2] = 1; + break; + case 2: + vup[0] = 1; + break; + } + + v = DotProduct (vup, normal); + VectorMA (vup, -v, normal, vup); + VectorNormalize (vup); + + VectorScale (normal, dist, org); + + CrossProduct (vup, normal, vright); + + VectorScale (vup, 9000, vup); + VectorScale (vright, 9000, vright); + +// project a really big axis aligned box onto the plane + w = AllocWinding (4); + + VectorSubtract (org, vright, w->p[0]); + VectorAdd (w->p[0], vup, w->p[0]); + + VectorAdd (org, vright, w->p[1]); + VectorAdd (w->p[1], vup, w->p[1]); + + VectorAdd (org, vright, w->p[2]); + VectorSubtract (w->p[2], vup, w->p[2]); + + VectorSubtract (org, vright, w->p[3]); + VectorSubtract (w->p[3], vup, w->p[3]); + + w->numpoints = 4; + + return w; +} + +/* +================== +CopyWinding +================== +*/ +winding_t *CopyWinding (winding_t *w) +{ + int size; + winding_t *c; + + size = (int)((winding_t *)0)->p[w->numpoints]; + c = malloc (size); + memcpy (c, w, size); + return c; +} + + +/* +============= +ClipWinding +============= +*/ +void ClipWinding (winding_t *in, vec3_t normal, vec_t dist, + winding_t **front, winding_t **back) +{ + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + vec_t dot; + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f, *b; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > ON_EPSILON) + sides[i] = SIDE_FRONT; + else if (dot < -ON_EPSILON) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + if (!counts[0]) + { + *back = CopyWinding (in); + return; + } + if (!counts[1]) + { + *front = CopyWinding (in); + return; + } + + maxpts = in->numpoints+4; // can't use counts[0]+2 because + // of fp grouping errors + + *front = f = AllocWinding (maxpts); + *back = b = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + if (sides[i] == SIDE_BACK) + { + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (mid, b->p[b->numpoints]); + b->numpoints++; + } + + if (f->numpoints > maxpts || b->numpoints > maxpts) + Error ("ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING) + Error ("ClipWinding: MAX_POINTS_ON_WINDING"); +} + + + +/* +============= +ClipWindingNoCopy +============= +*/ +void ClipWindingNoCopy (winding_t *in, vec3_t normal, vec_t dist, + winding_t **front, winding_t **back) +{ + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + vec_t dot; + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f, *b; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > ON_EPSILON) + sides[i] = SIDE_FRONT; + else if (dot < -ON_EPSILON) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + if (!counts[0]) + { + *back = in; + return; + } + if (!counts[1]) + { + *front = in; + return; + } + + maxpts = in->numpoints+4; // can't use counts[0]+2 because + // of fp grouping errors + + *front = f = AllocWinding (maxpts); + *back = b = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + if (sides[i] == SIDE_BACK) + { + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (mid, b->p[b->numpoints]); + b->numpoints++; + } + + if (f->numpoints > maxpts || b->numpoints > maxpts) + Error ("ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING) + Error ("ClipWinding: MAX_POINTS_ON_WINDING"); +} + + +/* +============= +ChopWindingNoFree +============= +*/ +winding_t *ChopWindingNoFree (winding_t *in, vec3_t normal, vec_t dist) +{ + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + vec_t dot; + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > ON_EPSILON) + sides[i] = SIDE_FRONT; + else if (dot < -ON_EPSILON) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + if (!counts[0]) + return NULL; + if (!counts[1]) + return in; + + maxpts = in->numpoints+4; // can't use counts[0]+2 because + // of fp grouping errors + + f = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + } + + if (f->numpoints > maxpts) + Error ("ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING) + Error ("ClipWinding: MAX_POINTS_ON_WINDING"); + + return f; +} + + +/* +================= +ChopWinding + +Returns the fragment of in that is on the front side +of the cliping plane. The original is freed. +================= +*/ +winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist) +{ + winding_t *f, *b; + + ClipWinding (in, normal, dist, &f, &b); + FreeWinding (in); + if (b) + FreeWinding (b); + return f; +} + + +/* +================= +CheckWinding + +================= +*/ +void CheckWinding (winding_t *w) +{ + int i, j; + vec_t *p1, *p2; + vec_t d, edgedist; + vec3_t dir, edgenormal, facenormal; + vec_t area; + vec_t facedist; + + if (w->numpoints < 3) + Error ("CheckWinding: %i points",w->numpoints); + + area = WindingArea(w); + if (area < 1) + Error ("CheckWinding: %f area", area); + + WindingPlane (w, facenormal, &facedist); + + for (i=0 ; inumpoints ; i++) + { + p1 = w->p[i]; + + for (j=0 ; j<3 ; j++) + if (p1[j] > BOGUS_RANGE || p1[j] < -BOGUS_RANGE) + Error ("CheckFace: BUGUS_RANGE: %f",p1[j]); + + j = i+1 == w->numpoints ? 0 : i+1; + + // check the point is on the face plane + d = DotProduct (p1, facenormal) - facedist; + if (d < -ON_EPSILON || d > ON_EPSILON) + Error ("CheckWinding: point off plane"); + + // check the edge isn't degenerate + p2 = w->p[j]; + VectorSubtract (p2, p1, dir); + + if (VectorLength (dir) < ON_EPSILON) + Error ("CheckWinding: degenerate edge"); + + CrossProduct (facenormal, dir, edgenormal); + VectorNormalize (edgenormal); + edgedist = DotProduct (p1, edgenormal); + edgedist += ON_EPSILON; + + // all other points must be on front side + for (j=0 ; jnumpoints ; j++) + { + if (j == i) + continue; + d = DotProduct (w->p[j], edgenormal); + if (d > edgedist) + Error ("CheckWinding: non-convex"); + } + } +} + + +/* +============ +WindingOnPlaneSide +============ +*/ +int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist) +{ + qboolean front, back; + int i; + vec_t d; + + front = false; + back = false; + for (i=0 ; inumpoints ; i++) + { + d = DotProduct (w->p[i], normal) - dist; + if (d < -ON_EPSILON) + { + if (front) + return SIDE_CROSS; + back = true; + continue; + } + if (d > ON_EPSILON) + { + if (back) + return SIDE_CROSS; + front = true; + continue; + } + } + + if (back) + return SIDE_BACK; + if (front) + return SIDE_FRONT; + return SIDE_ON; +} + diff --git a/utils/common/polylib.h b/utils/common/polylib.h new file mode 100644 index 0000000..b8ccff8 --- /dev/null +++ b/utils/common/polylib.h @@ -0,0 +1,36 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + + +typedef struct +{ + int numpoints; + vec3_t p[8]; // variable sized +} winding_t; + +#define MAX_POINTS_ON_WINDING 128 + +winding_t *AllocWinding (int points); +vec_t WindingArea (winding_t *w); +void WindingCenter (winding_t *w, vec3_t center); +void ClipWinding (winding_t *in, vec3_t normal, vec_t dist, + winding_t **front, winding_t **back); +void ClipWindingNoCopy (winding_t *in, vec3_t normal, vec_t dist, + winding_t **front, winding_t **back); +winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist); +winding_t *ChopWindingNoFree (winding_t *in, vec3_t normal, vec_t dist); +winding_t *CopyWinding (winding_t *w); +winding_t *BaseWindingForPlane (vec3_t normal, float dist); +void CheckWinding (winding_t *w); +void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist); +void RemoveColinearPoints (winding_t *w); +int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist); +void FreeWinding (winding_t *w); +void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs); \ No newline at end of file diff --git a/utils/common/scriplib.c b/utils/common/scriplib.c new file mode 100644 index 0000000..71bc0bd --- /dev/null +++ b/utils/common/scriplib.c @@ -0,0 +1,268 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// scriplib.c + +#include "cmdlib.h" +#include "scriplib.h" + +/* +============================================================================= + + PARSING STUFF + +============================================================================= +*/ + +typedef struct +{ + char filename[1024]; + char *buffer,*script_p,*end_p; + int line; +} script_t; + +#define MAX_INCLUDES 8 +script_t scriptstack[MAX_INCLUDES]; +script_t *script; +int scriptline; + +char token[MAXTOKEN]; +qboolean endofscript; +qboolean tokenready; // only true if UnGetToken was just called + +/* +============== +AddScriptToStack +============== +*/ +void AddScriptToStack (char *filename) +{ + int size; + + script++; + if (script == &scriptstack[MAX_INCLUDES]) + Error ("script file exceeded MAX_INCLUDES"); + strcpy (script->filename, ExpandPath (filename) ); + + size = LoadFile (script->filename, (void **)&script->buffer); + + printf ("entering %s\n", script->filename); + + script->line = 1; + + script->script_p = script->buffer; + script->end_p = script->buffer + size; +} + + +/* +============== +LoadScriptFile +============== +*/ +void LoadScriptFile (char *filename) +{ + script = scriptstack; + AddScriptToStack (filename); + + endofscript = false; + tokenready = false; +} + + +/* +============== +ParseFromMemory +============== +*/ +void ParseFromMemory (char *buffer, int size) +{ + script = scriptstack; + script++; + if (script == &scriptstack[MAX_INCLUDES]) + Error ("script file exceeded MAX_INCLUDES"); + strcpy (script->filename, "memory buffer" ); + + script->buffer = buffer; + script->line = 1; + script->script_p = script->buffer; + script->end_p = script->buffer + size; + + endofscript = false; + tokenready = false; +} + + +/* +============== +UnGetToken + +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 UnGetToken (void) +{ + tokenready = true; +} + + +qboolean EndOfScript (qboolean crossline) +{ + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + + if (!strcmp (script->filename, "memory buffer")) + { + endofscript = true; + return false; + } + + free (script->buffer); + if (script == scriptstack+1) + { + endofscript = true; + return false; + } + script--; + scriptline = script->line; + printf ("returning to %s\n", script->filename); + return GetToken (crossline); +} + +/* +============== +GetToken +============== +*/ +qboolean GetToken (qboolean crossline) +{ + char *token_p; + + if (tokenready) // is a token allready waiting? + { + tokenready = false; + return true; + } + + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + +// +// skip space +// +skipspace: + while (*script->script_p <= 32) + { + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + if (*script->script_p++ == '\n') + { + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + scriptline = script->line++; + } + } + + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + + if (*script->script_p == ';' || *script->script_p == '#' || // semicolon and # is comment field + (*script->script_p == '/' && *((script->script_p)+1) == '/')) // also make // a comment field + { + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + while (*script->script_p++ != '\n') + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + goto skipspace; + } + +// +// copy token +// + token_p = token; + + if (*script->script_p == '"') + { + // quoted token + script->script_p++; + while (*script->script_p != '"') + { + *token_p++ = *script->script_p++; + if (script->script_p == script->end_p) + break; + if (token_p == &token[MAXTOKEN]) + Error ("Token too large on line %i\n",scriptline); + } + script->script_p++; + } + else // regular token + while ( *script->script_p > 32 && *script->script_p != ';') + { + *token_p++ = *script->script_p++; + if (script->script_p == script->end_p) + break; + if (token_p == &token[MAXTOKEN]) + Error ("Token too large on line %i\n",scriptline); + } + + *token_p = 0; + + if (!strcmp (token, "$include")) + { + GetToken (false); + AddScriptToStack (token); + return GetToken (crossline); + } + + return true; +} + + +/* +============== +TokenAvailable + +Returns true if there is another token on the line +============== +*/ +qboolean TokenAvailable (void) +{ + char *search_p; + + search_p = script->script_p; + + if (search_p >= script->end_p) + return false; + + while ( *search_p <= 32) + { + if (*search_p == '\n') + return false; + search_p++; + if (search_p == script->end_p) + return false; + + } + + if (*search_p == ';') + return false; + + return true; +} + + diff --git a/utils/common/scriplib.h b/utils/common/scriplib.h new file mode 100644 index 0000000..d228d01 --- /dev/null +++ b/utils/common/scriplib.h @@ -0,0 +1,33 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// scriplib.h + +#ifndef __CMDLIB__ +#include "cmdlib.h" +#endif + +#define MAXTOKEN 512 + +extern char token[MAXTOKEN]; +extern char *scriptbuffer,*script_p,*scriptend_p; +extern int grabbed; +extern int scriptline; +extern qboolean endofscript; + + +void LoadScriptFile (char *filename); +void ParseFromMemory (char *buffer, int size); + +qboolean GetToken (qboolean crossline); +void UnGetToken (void); +qboolean TokenAvailable (void); + + diff --git a/utils/common/threads.c b/utils/common/threads.c new file mode 100644 index 0000000..51d657a --- /dev/null +++ b/utils/common/threads.c @@ -0,0 +1,330 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "cmdlib.h" +#define NO_THREAD_NAMES +#include "threads.h" + +#define MAX_THREADS 64 + +int dispatch; +int workcount; +int oldf; +qboolean pacifier; + +qboolean threaded; + +/* +============= +GetThreadWork + +============= +*/ +int GetThreadWork (void) +{ + int r; + int f; + + ThreadLock (); + + if (dispatch == workcount) + { + ThreadUnlock (); + return -1; + } + + f = 10*dispatch / workcount; + if (f != oldf) + { + oldf = f; + if (pacifier) + printf ("%i...", f); + } + + r = dispatch; + dispatch++; + ThreadUnlock (); + + return r; +} + + +void (*workfunction) (int); + +void ThreadWorkerFunction (int threadnum) +{ + int work; + + while (1) + { + work = GetThreadWork (); + if (work == -1) + break; + workfunction(work); + } +} + +void RunThreadsOnIndividual (int workcnt, qboolean showpacifier, void(*func)(int)) +{ + workfunction = func; + RunThreadsOn (workcnt, showpacifier, ThreadWorkerFunction); +} + + +/* +=================================================================== + +WIN32 + +=================================================================== +*/ +#ifdef WIN32 + +#define USED + +#include + +int numthreads = -1; +CRITICAL_SECTION crit; +static int enter; + +void ThreadSetDefault (void) +{ + SYSTEM_INFO info; + + if (numthreads == -1) // not set manually + { + GetSystemInfo (&info); + numthreads = info.dwNumberOfProcessors; + if (numthreads < 1 || numthreads > 32) + numthreads = 1; + } + + qprintf ("%i threads\n", numthreads); +} + + +void ThreadLock (void) +{ + if (!threaded) + return; + EnterCriticalSection (&crit); + if (enter) + Error ("Recursive ThreadLock\n"); + enter = 1; +} + +void ThreadUnlock (void) +{ + if (!threaded) + return; + if (!enter) + Error ("ThreadUnlock without lock\n"); + enter = 0; + LeaveCriticalSection (&crit); +} + +/* +============= +RunThreadsOn +============= +*/ +void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)) +{ + int threadid[MAX_THREADS]; + HANDLE threadhandle[MAX_THREADS]; + int i; + int start, end; + + start = I_FloatTime (); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = true; + // + // run threads in parallel + // + InitializeCriticalSection (&crit); + for (i=0 ; i + +pthread_mutex_t *my_mutex; + +void ThreadLock (void) +{ + if (my_mutex) + pthread_mutex_lock (my_mutex); +} + +void ThreadUnlock (void) +{ + if (my_mutex) + pthread_mutex_unlock (my_mutex); +} + + +/* +============= +RunThreadsOn +============= +*/ +void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)) +{ + int i; + pthread_t work_threads[MAX_THREADS]; + pthread_addr_t status; + pthread_attr_t attrib; + pthread_mutexattr_t mattrib; + int start, end; + + start = I_FloatTime (); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = true; + + if (pacifier) + setbuf (stdout, NULL); + + if (!my_mutex) + { + my_mutex = malloc (sizeof(*my_mutex)); + if (pthread_mutexattr_create (&mattrib) == -1) + Error ("pthread_mutex_attr_create failed"); + if (pthread_mutexattr_setkind_np (&mattrib, MUTEX_FAST_NP) == -1) + Error ("pthread_mutexattr_setkind_np failed"); + if (pthread_mutex_init (my_mutex, mattrib) == -1) + Error ("pthread_mutex_init failed"); + } + + if (pthread_attr_create (&attrib) == -1) + Error ("pthread_attr_create failed"); + if (pthread_attr_setstacksize (&attrib, 0x100000) == -1) + Error ("pthread_attr_setstacksize failed"); + + for (i=0 ; i +#include "cmdlib.h" +#include "mathlib.h" +#include "trilib.h" + +// on disk representation of a face + + +#define FLOAT_START 99999.0 +#define FLOAT_END -FLOAT_START +#define MAGIC 123322 + +//#define NOISY 1 + +typedef struct { + float v[3]; +} vector; + +typedef struct +{ + vector n; /* normal */ + vector p; /* point */ + vector c; /* color */ + float u; /* u */ + float v; /* v */ +} aliaspoint_t; + +typedef struct { + aliaspoint_t pt[3]; +} tf_triangle; + + +void ByteSwapTri (tf_triangle *tri) +{ + int i; + + for (i=0 ; iverts[j][k] = tri.pt[j].p.v[k]; + } + } + + ptri++; + + if ((ptri - *pptri) >= MAXTRIANGLES) + Error ("Error: too many triangles; increase MAXTRIANGLES\n"); + } + } + + *numtriangles = ptri - *pptri; + + fclose (input); +} + diff --git a/utils/common/trilib.h b/utils/common/trilib.h new file mode 100644 index 0000000..2840c68 --- /dev/null +++ b/utils/common/trilib.h @@ -0,0 +1,21 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// +// trilib.h: header file for loading triangles from an Alias triangle file +// +#define MAXTRIANGLES 2048 + +typedef struct { + vec3_t verts[3]; +} triangle_t; + +void LoadTriangleList (char *filename, triangle_t **pptri, int *numtriangles); + diff --git a/utils/common/wadlib.c b/utils/common/wadlib.c new file mode 100644 index 0000000..88b71fb --- /dev/null +++ b/utils/common/wadlib.c @@ -0,0 +1,339 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// wad2lib.c + +#include +#include +#include +#include +#include +#include +#include +//#include +#include + +#ifdef NeXT +#include +#endif +#include "cmdlib.h" +#include "wadlib.h" + +/* +============================================================================ + + WAD READING + +============================================================================ +*/ + + +lumpinfo_t *lumpinfo; // location of each lump on disk +int numlumps; + +wadinfo_t header; +FILE *wadhandle; + + +/* +==================== +W_OpenWad +==================== +*/ +void W_OpenWad (char *filename) +{ + lumpinfo_t *lump_p; + unsigned i; + int length; + +// +// open the file and add to directory +// + wadhandle = SafeOpenRead (filename); + SafeRead (wadhandle, &header, sizeof(header)); + + if (strncmp(header.identification,"WAD2",4) && + strncmp(header.identification, "WAD3", 4)) + Error ("Wad file %s doesn't have WAD2/WAD3 id\n",filename); + + header.numlumps = LittleLong(header.numlumps); + header.infotableofs = LittleLong(header.infotableofs); + + numlumps = header.numlumps; + + length = numlumps*sizeof(lumpinfo_t); + lumpinfo = malloc (length); + lump_p = lumpinfo; + + fseek (wadhandle, header.infotableofs, SEEK_SET); + SafeRead (wadhandle, lumpinfo, length); + +// +// Fill in lumpinfo +// + + for (i=0 ; ifilepos = LittleLong(lump_p->filepos); + lump_p->size = LittleLong(lump_p->size); + } +} + + + +void CleanupName (char *in, char *out) +{ + int i; + + for (i=0 ; iname ) ; i++ ) + { + if (!in[i]) + break; + + out[i] = toupper(in[i]); + } + + for ( ; iname ); i++ ) + out[i] = 0; +} + + +/* +==================== +W_CheckNumForName + +Returns -1 if name not found +==================== +*/ +int W_CheckNumForName (char *name) +{ + char cleanname[16]; + int v1,v2, v3, v4; + int i; + lumpinfo_t *lump_p; + + CleanupName (name, cleanname); + +// make the name into four integers for easy compares + + v1 = *(int *)cleanname; + v2 = *(int *)&cleanname[4]; + v3 = *(int *)&cleanname[8]; + v4 = *(int *)&cleanname[12]; + +// find it + + lump_p = lumpinfo; + for (i=0 ; iname == v1 + && *(int *)&lump_p->name[4] == v2 + && *(int *)&lump_p->name[8] == v3 + && *(int *)&lump_p->name[12] == v4) + return i; + } + + return -1; +} + + +/* +==================== +W_GetNumForName + +Calls W_CheckNumForName, but bombs out if not found +==================== +*/ +int W_GetNumForName (char *name) +{ + int i; + + i = W_CheckNumForName (name); + if (i != -1) + return i; + + Error ("W_GetNumForName: %s not found!",name); + return -1; +} + + +/* +==================== +W_LumpLength + +Returns the buffer size needed to load the given lump +==================== +*/ +int W_LumpLength (int lump) +{ + if (lump >= numlumps) + Error ("W_LumpLength: %i >= numlumps",lump); + return lumpinfo[lump].size; +} + + +/* +==================== +W_ReadLumpNum + +Loads the lump into the given buffer, which must be >= W_LumpLength() +==================== +*/ +void W_ReadLumpNum (int lump, void *dest) +{ + lumpinfo_t *l; + + if (lump >= numlumps) + Error ("W_ReadLump: %i >= numlumps",lump); + l = lumpinfo+lump; + + fseek (wadhandle, l->filepos, SEEK_SET); + SafeRead (wadhandle, dest, l->size); +} + + + +/* +==================== +W_LoadLumpNum +==================== +*/ +void *W_LoadLumpNum (int lump) +{ + void *buf; + + if ((unsigned)lump >= numlumps) + Error ("W_CacheLumpNum: %i >= numlumps",lump); + + buf = malloc (W_LumpLength (lump)); + W_ReadLumpNum (lump, buf); + + return buf; +} + + +/* +==================== +W_LoadLumpName +==================== +*/ +void *W_LoadLumpName (char *name) +{ + return W_LoadLumpNum (W_GetNumForName(name)); +} + + +/* +=============================================================================== + + WAD CREATION + +=============================================================================== +*/ + +FILE *outwad; + +lumpinfo_t outinfo[4096]; +int outlumps; + +short (*wadshort) (short l); +int (*wadlong) (int l); + +/* +=============== +NewWad +=============== +*/ + +void NewWad (char *pathname, qboolean bigendien) +{ + outwad = SafeOpenWrite (pathname); + fseek (outwad, sizeof(wadinfo_t), SEEK_SET); + memset (outinfo, 0, sizeof(outinfo)); + + if (bigendien) + { + wadshort = BigShort; + wadlong = BigLong; + } + else + { + wadshort = LittleShort; + wadlong = LittleLong; + } + + outlumps = 0; +} + + +/* +=============== +AddLump +=============== +*/ + +void AddLump (char *name, void *buffer, int length, int type, int compress) +{ + lumpinfo_t *info; + int ofs; + + info = &outinfo[outlumps]; + outlumps++; + + memset (info,0,sizeof(info)); + + strcpy (info->name, name); + strupr (info->name); + + ofs = ftell(outwad); + info->filepos = wadlong(ofs); + info->size = info->disksize = wadlong(length); + info->type = type; + info->compression = compress; + +// FIXME: do compression + + SafeWrite (outwad, buffer, length); +} + + +/* +=============== +WriteWad +=============== +*/ + +void WriteWad (int wad3) +{ + wadinfo_t header; + int ofs; + +// write the lumpingo + ofs = ftell(outwad); + + SafeWrite (outwad, outinfo, outlumps*sizeof(lumpinfo_t) ); + +// write the header + +// a program will be able to tell the ednieness of a wad by the id + header.identification[0] = 'W'; + header.identification[1] = 'A'; + header.identification[2] = 'D'; + header.identification[3] = wad3 ? '3' : '2'; + + header.numlumps = wadlong(outlumps); + header.infotableofs = wadlong(ofs); + + fseek (outwad, 0, SEEK_SET); + SafeWrite (outwad, &header, sizeof(header)); + fclose (outwad); +} + + diff --git a/utils/common/wadlib.h b/utils/common/wadlib.h new file mode 100644 index 0000000..64b6a7d --- /dev/null +++ b/utils/common/wadlib.h @@ -0,0 +1,63 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// wadlib.h + +// +// wad reading +// + +#define CMP_NONE 0 +#define CMP_LZSS 1 + +#define TYP_NONE 0 +#define TYP_LABEL 1 +#define TYP_LUMPY 64 // 64 + grab command number + +typedef struct +{ + char identification[4]; // should be WAD2 or 2DAW + int numlumps; + int infotableofs; +} wadinfo_t; + + +typedef struct +{ + int filepos; + int disksize; + int size; // uncompressed + char type; + char compression; + char pad1, pad2; + char name[16]; // must be null terminated +} lumpinfo_t; + +extern lumpinfo_t *lumpinfo; // location of each lump on disk +extern int numlumps; +extern wadinfo_t header; + +void W_OpenWad (char *filename); +int W_CheckNumForName (char *name); +int W_GetNumForName (char *name); +int W_LumpLength (int lump); +void W_ReadLumpNum (int lump, void *dest); +void *W_LoadLumpNum (int lump); +void *W_LoadLumpName (char *name); + +void CleanupName (char *in, char *out); + +// +// wad creation +// +void NewWad (char *pathname, qboolean bigendien); +void AddLump (char *name, void *buffer, int length, int type, int compress); +void WriteWad (int wad3); + diff --git a/utils/light/light.c b/utils/light/light.c new file mode 100644 index 0000000..91be9aa --- /dev/null +++ b/utils/light/light.c @@ -0,0 +1,228 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// lighting.c + +#include "light.h" + +/* + +NOTES +----- + +*/ + +float scaledist = 1.0; +float scalecos = 0.5; +float rangescale = 0.5; + +byte *filebase, *file_p, *file_end; + +dmodel_t *bspmodel; + +vec3_t bsp_origin; + +qboolean extrasamples; +qboolean hicolor; +qboolean clamp192 = true; + +float minlights[MAX_MAP_FACES]; + +lightentity_t lightentities[MAX_MAP_ENTITIES]; +int numlightentities; + + +/* +================== +LoadEntities +================== +*/ +void LoadEntities (void) +{ + char *s, *s2; + entity_t *e; + lightentity_t *le; + int i, j; + + ParseEntities (); + +// go through all the entities + for (i=1 ; iclassname, s); + s = ValueForKey( e, "_light" ); + if( s ) + { + double v1, v2, v3; + + v1 = v2 = v3 = 0; + if( sscanf( s, "%lf %lf %lf", &v1, &v2, &v3) != 3 ) + v2 = v3 = v1; + + le->light[0] = v1; + le->light[1] = v2; + le->light[2] = v3; + } + else + { + le->light[0] = DEFAULTLIGHTLEVEL; + le->light[1] = DEFAULTLIGHTLEVEL; + le->light[2] = DEFAULTLIGHTLEVEL; + } + + le->style = FloatForKey (e, "style"); + le->angle = FloatForKey (e, "angle"); + GetVectorForKey (e, "origin", le->origin); + + s = ValueForKey (e, "target"); + if (!s[0]) + continue; + + // find matching targetname + for (j=1 ; jtargetent = true; + GetVectorForKey (&entities[j], "origin", le->targetorigin); + break; + } + } + if (j == num_entities) + printf ("WARNING: entity %i has unmatched target %s\n", i, s); + } + + qprintf ("%d lightentities\n", numlightentities); + +} + + +byte *GetFileSpace (int size) +{ + byte *buf; + + ThreadLock(); + file_p = (byte *)(((long)file_p + 3)&~3); + buf = file_p; + file_p += size; + ThreadUnlock(); + if (file_p > file_end) + Error ("GetFileSpace: overrun"); + return buf; +} + + + +/* +============= +LightWorld +============= +*/ +void LightWorld (void) +{ + filebase = file_p = dlightdata; + file_end = filebase + MAX_MAP_LIGHTING; + + RunThreadsOnIndividual (numfaces, true, LightFace); + + lightdatasize = file_p - filebase; + + printf ("lightdatasize: %i\n", lightdatasize); +} + + +/* +======== +main + +light modelfile +======== +*/ +int main (int argc, char **argv) +{ + int i; + double start, end; + char source[1024]; + + printf("Light.exe Version 1.3 Id Software and valve (%s)\n", __DATE__ ); + printf ("----- LightFaces ----\n"); + + // default to 24-bit light info + hicolor = true; + + for (i=1 ; i +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=light - 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 "light.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 "light.mak" CFG="light - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "light - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "light - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "light - 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 Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /GX /O2 /I "..\common" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /YX /FD /c +# 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 /subsystem:console /machine:I386 +# ADD 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 /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "light - 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 Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /Gm /GX /ZI /Od /I "..\common" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /FR /YX /FD /c +# 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 gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 +# ADD 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 /subsystem:console /debug /machine:I386 + +!ENDIF + +# Begin Target + +# Name "light - Win32 Release" +# Name "light - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=..\common\bspfile.c +# End Source File +# Begin Source File + +SOURCE=..\common\cmdlib.c +# End Source File +# Begin Source File + +SOURCE=.\light.c +# End Source File +# Begin Source File + +SOURCE=.\ltface.c +# End Source File +# Begin Source File + +SOURCE=..\common\mathlib.c +# End Source File +# Begin Source File + +SOURCE=..\common\scriplib.c +# End Source File +# Begin Source File + +SOURCE=..\common\threads.c +# End Source File +# Begin Source File + +SOURCE=.\trace.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=.\light.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/utils/light/light.h b/utils/light/light.h new file mode 100644 index 0000000..2671f2e --- /dev/null +++ b/utils/light/light.h @@ -0,0 +1,60 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "cmdlib.h" +#include "mathlib.h" +#include "bspfile.h" +#include "threads.h" + + +#define DEFAULTLIGHTLEVEL 300 + +typedef struct entity_s +{ + char classname[64]; + vec3_t origin; + float angle; + vec3_t light; + int style; + qboolean targetent; + vec3_t targetorigin; +} lightentity_t; + +extern lightentity_t lightentities[MAX_MAP_ENTITIES]; +extern int numlightentities; + +#define ON_EPSILON 0.1 + +#define MAXLIGHTS 1024 + +void LoadNodes (char *file); +qboolean TestLine (vec3_t start, vec3_t stop); + +void LightFace (int surfnum); +void LightLeaf (dleaf_t *leaf); + +void MakeTnodes (dmodel_t *bm); + +extern float scaledist; +extern float scalecos; +extern float rangescale; + +extern int c_culldistplane, c_proper; + +byte *GetFileSpace (int size); +extern byte *filebase; + +extern vec3_t bsp_origin; +extern vec3_t bsp_xvector; +extern vec3_t bsp_yvector; + +void TransformSample (vec3_t in, vec3_t out); +void RotateSample (vec3_t in, vec3_t out); + +extern qboolean extrasamples; + +extern float minlights[MAX_MAP_FACES]; diff --git a/utils/light/ltface.c b/utils/light/ltface.c new file mode 100644 index 0000000..6a62f9c --- /dev/null +++ b/utils/light/ltface.c @@ -0,0 +1,688 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "light.h" + +extern qboolean hicolor; +extern qboolean clamp192; + +/* +============ +CastRay + +Returns the distance between the points, or -1 if blocked +============= +*/ +vec_t CastRay (vec3_t p1, vec3_t p2) +{ + int i; + vec_t t; + qboolean trace; + + trace = TestLine (p1, p2); + + if (!trace) + return -1; // ray was blocked + + t = 0; + for (i=0 ; i< 3 ; i++) + t += (p2[i]-p1[i]) * (p2[i]-p1[i]); + + if (t == 0) + t = 1; // don't blow up... + return sqrt(t); +} + +/* +=============================================================================== + +SAMPLE POINT DETERMINATION + +void SetupBlock (dface_t *f) Returns with surfpt[] set + +This is a little tricky because the lightmap covers more area than the face. +If done in the straightforward fashion, some of the +sample points will be inside walls or on the other side of walls, causing +false shadows and light bleeds. + +To solve this, I only consider a sample point valid if a line can be drawn +between it and the exact midpoint of the face. If invalid, it is adjusted +towards the center until it is valid. + +(this doesn't completely work) + +=============================================================================== +*/ + +#define SINGLEMAP (18*18*4) + +typedef struct +{ + vec3_t lightmaps[MAXLIGHTMAPS][SINGLEMAP]; + int numlightstyles; + vec_t *light; + vec_t facedist; + vec3_t facenormal; + + int numsurfpt; + vec3_t surfpt[SINGLEMAP]; + + vec3_t texorg; + vec3_t worldtotex[2]; // s = (world - texorg) . worldtotex[0] + vec3_t textoworld[2]; // world = texorg + s * textoworld[0] + + vec_t exactmins[2], exactmaxs[2]; + + int texmins[2], texsize[2]; + int lightstyles[256]; + int surfnum; + dface_t *face; +} lightinfo_t; + + +/* +================ +CalcFaceVectors + +Fills in texorg, worldtotex. and textoworld +================ +*/ +void CalcFaceVectors (lightinfo_t *l) +{ + texinfo_t *tex; + int i, j; + vec3_t texnormal; + float distscale; + vec_t dist, len; + + tex = &texinfo[l->face->texinfo]; + +// convert from float to vec_t + for (i=0 ; i<2 ; i++) + for (j=0 ; j<3 ; j++) + l->worldtotex[i][j] = tex->vecs[i][j]; + +// calculate a normal to the texture axis. points can be moved along this +// without changing their S/T + texnormal[0] = tex->vecs[1][1]*tex->vecs[0][2] + - tex->vecs[1][2]*tex->vecs[0][1]; + texnormal[1] = tex->vecs[1][2]*tex->vecs[0][0] + - tex->vecs[1][0]*tex->vecs[0][2]; + texnormal[2] = tex->vecs[1][0]*tex->vecs[0][1] + - tex->vecs[1][1]*tex->vecs[0][0]; + VectorNormalize (texnormal); + +// flip it towards plane normal + distscale = DotProduct (texnormal, l->facenormal); + if (!distscale) + Error ("Texture axis perpendicular to face"); + if (distscale < 0) + { + distscale = -distscale; + VectorSubtract (vec3_origin, texnormal, texnormal); + } + +// distscale is the ratio of the distance along the texture normal to +// the distance along the plane normal + distscale = 1/distscale; + + for (i=0 ; i<2 ; i++) + { + len = VectorLength (l->worldtotex[i]); + dist = DotProduct (l->worldtotex[i], l->facenormal); + dist *= distscale; + VectorMA (l->worldtotex[i], -dist, texnormal, l->textoworld[i]); + VectorScale (l->textoworld[i], (1/len)*(1/len), l->textoworld[i]); + } + + +// calculate texorg on the texture plane + for (i=0 ; i<3 ; i++) + l->texorg[i] = -tex->vecs[0][3]* l->textoworld[0][i] - tex->vecs[1][3] * l->textoworld[1][i]; + +// project back to the face plane + dist = DotProduct (l->texorg, l->facenormal) - l->facedist - 1; + dist *= distscale; + VectorMA (l->texorg, -dist, texnormal, l->texorg); + +} + +/* +================ +CalcFaceExtents + +Fills in s->texmins[] and s->texsize[] +also sets exactmins[] and exactmaxs[] +================ +*/ +void CalcFaceExtents (lightinfo_t *l) +{ + dface_t *s; + vec_t mins[2], maxs[2], val; + int i,j, e; + dvertex_t *v; + texinfo_t *tex; + + s = l->face; + + mins[0] = mins[1] = 999999; + maxs[0] = maxs[1] = -99999; + + tex = &texinfo[s->texinfo]; + + for (i=0 ; inumedges ; i++) + { + e = dsurfedges[s->firstedge+i]; + if (e >= 0) + v = dvertexes + dedges[e].v[0]; + else + v = dvertexes + dedges[-e].v[1]; + + for (j=0 ; j<2 ; j++) + { + val = v->point[0] * tex->vecs[j][0] + + v->point[1] * tex->vecs[j][1] + + v->point[2] * tex->vecs[j][2] + + tex->vecs[j][3]; + if (val < mins[j]) + mins[j] = val; + if (val > maxs[j]) + maxs[j] = val; + } + } + + for (i=0 ; i<2 ; i++) + { + l->exactmins[i] = mins[i]; + l->exactmaxs[i] = maxs[i]; + + mins[i] = floor(mins[i]/16); + maxs[i] = ceil(maxs[i]/16); + + l->texmins[i] = mins[i]; + l->texsize[i] = maxs[i] - mins[i]; + if (l->texsize[i] > 17) + Error ("Bad surface extents"); + } +} + +/* +================= +CalcPoints + +For each texture aligned grid point, back project onto the plane +to get the world xyz value of the sample point +================= +*/ +int c_bad; +void CalcPoints (lightinfo_t *l) +{ + int i; + int s, t, j; + int w, h, step; + vec_t starts, startt, us, ut; + vec_t *surf; + vec_t mids, midt; + vec3_t facemid, move; + +// +// fill in surforg +// the points are biased towards the center of the surface +// to help avoid edge cases just inside walls +// + surf = l->surfpt[0]; + mids = (l->exactmaxs[0] + l->exactmins[0])/2; + midt = (l->exactmaxs[1] + l->exactmins[1])/2; + + for (j=0 ; j<3 ; j++) + facemid[j] = l->texorg[j] + l->textoworld[0][j]*mids + l->textoworld[1][j]*midt; + + if (extrasamples) + { // extra filtering + h = (l->texsize[1]+1)*2; + w = (l->texsize[0]+1)*2; + starts = (l->texmins[0]-0.5)*16; + startt = (l->texmins[1]-0.5)*16; + step = 8; + } + else + { + h = l->texsize[1]+1; + w = l->texsize[0]+1; + starts = l->texmins[0]*16; + startt = l->texmins[1]*16; + step = 16; + } + + l->numsurfpt = w * h; + for (t=0 ; ttexorg[j] + l->textoworld[0][j]*us + + l->textoworld[1][j]*ut; + + if (CastRay (facemid, surf) != -1) + break; // got it + if (i & 1) + { + if (us > mids) + { + us -= 8; + if (us < mids) + us = mids; + } + else + { + us += 8; + if (us > mids) + us = mids; + } + } + else + { + if (ut > midt) + { + ut -= 8; + if (ut < midt) + ut = midt; + } + else + { + ut += 8; + if (ut > midt) + ut = midt; + } + } + + // move surf 8 pixels towards the center + VectorSubtract (facemid, surf, move); + VectorNormalize (move); + VectorMA (surf, 8, move, surf); + } + if (i == 2) + c_bad++; + } + } + +} + + +/* +=============================================================================== + +FACE LIGHTING + +=============================================================================== +*/ + +int c_culldistplane, c_proper; + +/* +================ +SingleLightFace +================ +*/ +void SingleLightFace (lightentity_t *light, lightinfo_t *l) +{ + vec_t dist; + vec3_t incoming; + vec_t angle; + vec_t add; + vec_t *surf; + qboolean hit; + int mapnum; + int size; + int c, i; + vec3_t rel; + vec3_t spotvec; + vec_t falloff; + vec3_t *lightsamp; + float intensity; + + VectorSubtract (light->origin, bsp_origin, rel); + dist = scaledist * (DotProduct (rel, l->facenormal) - l->facedist); + +// don't bother with lights behind the surface + if (dist <= 0) + return; + +// don't bother with light too far away + intensity = ( light->light[ 0 ] + light->light[ 1 ] + light->light[ 2 ] ) / 3.0; + if( dist > intensity ) + { + c_culldistplane++; + return; + } + + if (light->targetent) + { + VectorSubtract (light->targetorigin, light->origin, spotvec); + VectorNormalize (spotvec); + if (!light->angle) + falloff = -cos(20*Q_PI/180); + else + falloff = -cos(light->angle/2*Q_PI/180); + } + else + falloff = 0; // shut up compiler warnings + + mapnum = 0; + for (mapnum=0 ; mapnumnumlightstyles ; mapnum++) + if (l->lightstyles[mapnum] == light->style) + break; + lightsamp = l->lightmaps[mapnum]; + if (mapnum == l->numlightstyles) + { // init a new light map + if (mapnum == MAXLIGHTMAPS) + { + printf ("WARNING: Too many light styles on a face\n"); + return; + } + size = (l->texsize[1]+1)*(l->texsize[0]+1); + for (i=0 ; isurfpt[0]; + for (c=0 ; cnumsurfpt ; c++, surf+=3) + { + dist = CastRay(light->origin, surf)*scaledist; + if (dist < 0) + continue; // light doesn't reach + + VectorSubtract (light->origin, surf, incoming); + VectorNormalize (incoming); + angle = DotProduct (incoming, l->facenormal); + if (light->targetent) + { // spotlight cutoff + if (DotProduct (spotvec, incoming) > falloff) + continue; + } + + angle = (1.0-scalecos) + scalecos*angle; + for( i=0; i<3; i++ ) + { + add = light->light[i] - dist; + add *= angle; + if (add < 0) + continue; + + lightsamp[c][i] += add; + } + + // check intensity + intensity = ( lightsamp[ c ][ 0 ] + lightsamp[ c ][ 1 ] + lightsamp[ c ][ 2 ] ) / 3.0; + if( intensity > 1 ) // ignore real tiny lights + hit = true; + } + + if (mapnum == l->numlightstyles && hit) + { + l->lightstyles[mapnum] = light->style; + l->numlightstyles++; // the style has some real data now + } +} + +/* +============ +FixMinlight +============ +*/ +void FixMinlight (lightinfo_t *l) +{ + int i, j; + float minlight; + + minlight = minlights[l->surfnum]; + + // if minlight is set, there must be a style 0 light map + if (!minlight) + return; + + for (i=0 ; i< l->numlightstyles ; i++) + { + if (l->lightstyles[i] == 0) + break; + } + if (i == l->numlightstyles) + { + if (l->numlightstyles == MAXLIGHTMAPS) + return; // oh well.. + + for (j=0 ; jnumsurfpt ; j++) + { + l->lightmaps[i][j][0] = minlight; + l->lightmaps[i][j][1] = minlight; + l->lightmaps[i][j][2] = minlight; + } + + l->lightstyles[i] = 0; + l->numlightstyles++; + } + else + { + for (j=0 ; jnumsurfpt ; j++) + { + float intensity = ( l->lightmaps[i][j][0] + l->lightmaps[i][j][1] + l->lightmaps[i][j][2] ) / 3.0; + + if ( intensity < minlight ) + { + l->lightmaps[i][j][0] = minlight; + l->lightmaps[i][j][1] = minlight; + l->lightmaps[i][j][2] = minlight; + } + } + } +} + + +/* +============ +LightFace +============ +*/ +void LightFace (int surfnum) +{ + dface_t *f; + lightinfo_t l; + int s, t; + int i,j,c; + vec3_t total; + int size; + int lightmapwidth, lightmapsize; + byte *out; + vec3_t *light; + int w, h; + int clamp = 192; + float clampfactor = 0.75; + + if ( !clamp192 ) + { + clamp = 255; + clampfactor = 1.0; + } + + f = dfaces + surfnum; + +// +// some surfaces don't need lightmaps +// + f->lightofs = -1; + for (j=0 ; jstyles[j] = 255; + + if ( texinfo[f->texinfo].flags & TEX_SPECIAL) + { // non-lit texture + return; + } + + memset (&l, 0, sizeof(l)); + l.surfnum = surfnum; + l.face = f; + +// +// rotate plane +// + VectorCopy (dplanes[f->planenum].normal, l.facenormal); + l.facedist = dplanes[f->planenum].dist; + if (f->side) + { + VectorSubtract (vec3_origin, l.facenormal, l.facenormal); + l.facedist = -l.facedist; + } + + + + CalcFaceVectors (&l); + CalcFaceExtents (&l); + CalcPoints (&l); + + lightmapwidth = l.texsize[0]+1; + + size = lightmapwidth*(l.texsize[1]+1); + if (size > SINGLEMAP) + Error ("Bad lightmap size"); + + for (i=0 ; istyles[i] = l.lightstyles[i]; + + if( hicolor ) + lightmapsize = size*l.numlightstyles*3; + else + lightmapsize = size * l.numlightstyles; + + out = GetFileSpace (lightmapsize); + f->lightofs = out - filebase; + +// extra filtering + h = (l.texsize[1]+1)*2; + w = (l.texsize[0]+1)*2; + + for (i=0 ; i< l.numlightstyles ; i++) + { + if (l.lightstyles[i] == 0xff) + Error ("Wrote empty lightmap"); + light = l.lightmaps[i]; + c = 0; + for (t=0 ; t<=l.texsize[1] ; t++) + { + for (s=0 ; s<=l.texsize[0] ; s++, c++) + { + if (extrasamples) + { +#ifdef OLD_CODE + // filtered sample + VectorCopy( light[t*2*w+s*2], total ); + VectorAdd( total, light[t*2*w+s*2+1], total ); + VectorAdd( total, light[(t*2+1)*w+s*2], total ); + VectorAdd( total, light[(t*2+1)*w+s*2+1], total ); + VectorScale( total, 0.25, total ); +#else + int u, v; + float weight[3][3] = + { + { 5, 9, 5 }, + { 9, 16, 9 }, + { 5, 9, 5 }, + }; + float divisor = 0.0; + VectorFill(total,0); + for ( u = 0; u < 3; u++ ) + { + for ( v = 0; v < 3; v++ ) + { + if ( s+u-2>=0 && t+v-1>=0 && s+u-1 <= w && t+v-1 <= h) + { + vec3_t sample; + VectorScale( light[((t*2)+(v-1))*w + ((s*2)+(u-1))], weight[u][v], sample ); + divisor += weight[u][v]; + VectorAdd( total, sample, total ); + } + } + } +#endif + if ( divisor > 1.0 ) + VectorScale( total, 1/divisor, total ); + total[0] = max(total[0],0.0); + total[1] = max(total[1],0.0); + total[2] = max(total[2],0.0); + } + else + VectorCopy( light[ c ], total ); + + // Scale + VectorScale( total, rangescale, total ); + + // Clamp + if( hicolor ) + { + for( j=0; j<3; j++ ) + { + total[ j ] *= clampfactor; + + if( total[j] > clamp) + total[j] = clamp; + else if (total[j] < 0) + Error ("light < 0"); + + *out++ = (byte) total[j]; + } + } + else + { + int intensity = total[ 0 ] + total[ 1 ] + total[ 2 ]; + if( intensity > 255 ) + intensity = 255; + else if( intensity < 0 ) + Error( "light < 0" ); + + *out++ = (byte) intensity; + } + } + } + } +} + diff --git a/utils/light/new/light.c b/utils/light/new/light.c new file mode 100644 index 0000000..d5fd346 --- /dev/null +++ b/utils/light/new/light.c @@ -0,0 +1,193 @@ +// lighting.c + +#include "light.h" + +/* + +NOTES +----- + +*/ + +float scaledist = 1.0; +float scalecos = 0.5; +float rangescale = 0.5; + +byte *filebase, *file_p, *file_end; + +dmodel_t *bspmodel; + +vec3_t bsp_origin; + +qboolean extrasamples; + +float minlights[MAX_MAP_FACES]; + + + + +lightentity_t lightentities[MAX_MAP_ENTITIES]; +int numlightentities; + + +/* +================== +LoadEntities +================== +*/ +void LoadEntities (void) +{ + char *s, *s2; + entity_t *e; + lightentity_t *le; + int i, j; + + ParseEntities (); + +// go through all the entities + for (i=1 ; iclassname, s); + le->light = FloatForKey (e, "light"); + if (!le->light) + le->light = DEFAULTLIGHTLEVEL; + le->style = FloatForKey (e, "style"); + le->angle = FloatForKey (e, "angle"); + GetVectorForKey (e, "origin", le->origin); + + s = ValueForKey (e, "target"); + if (!s[0]) + continue; + + // find matching targetname + for (j=1 ; jtargetent = true; + GetVectorForKey (&entities[j], "origin", le->targetorigin); + break; + } + } + if (j == num_entities) + printf ("WARNING: entity %i has unmatched target %s\n", i, s); + } + + qprintf ("%d lightentities\n", numlightentities); + +} + + +byte *GetFileSpace (int size) +{ + byte *buf; + + ThreadLock(); + file_p = (byte *)(((long)file_p + 3)&~3); + buf = file_p; + file_p += size; + ThreadUnlock(); + if (file_p > file_end) + Error ("GetFileSpace: overrun"); + return buf; +} + + + +/* +============= +LightWorld +============= +*/ +void LightWorld (void) +{ + filebase = file_p = dlightdata; + file_end = filebase + MAX_MAP_LIGHTING; + + RunThreadsOnIndividual (numfaces, true, LightFace); + + lightdatasize = file_p - filebase; + + printf ("lightdatasize: %i\n", lightdatasize); +} + + +/* +======== +main + +light modelfile +======== +*/ +int main (int argc, char **argv) +{ + int i; + double start, end; + char source[1024]; + + printf ("----- LightFaces ----\n"); + + for (i=1 ; iface->texinfo]; + +// convert from float to vec_t + for (i=0 ; i<2 ; i++) + for (j=0 ; j<3 ; j++) + l->worldtotex[i][j] = tex->vecs[i][j]; + +// calculate a normal to the texture axis. points can be moved along this +// without changing their S/T + texnormal[0] = tex->vecs[1][1]*tex->vecs[0][2] + - tex->vecs[1][2]*tex->vecs[0][1]; + texnormal[1] = tex->vecs[1][2]*tex->vecs[0][0] + - tex->vecs[1][0]*tex->vecs[0][2]; + texnormal[2] = tex->vecs[1][0]*tex->vecs[0][1] + - tex->vecs[1][1]*tex->vecs[0][0]; + VectorNormalize (texnormal); + +// flip it towards plane normal + distscale = DotProduct (texnormal, l->facenormal); + if (!distscale) + Error ("Texture axis perpendicular to face"); + if (distscale < 0) + { + distscale = -distscale; + VectorSubtract (vec3_origin, texnormal, texnormal); + } + +// distscale is the ratio of the distance along the texture normal to +// the distance along the plane normal + distscale = 1/distscale; + + for (i=0 ; i<2 ; i++) + { + len = VectorLength (l->worldtotex[i]); + dist = DotProduct (l->worldtotex[i], l->facenormal); + dist *= distscale; + VectorMA (l->worldtotex[i], -dist, texnormal, l->textoworld[i]); + VectorScale (l->textoworld[i], (1/len)*(1/len), l->textoworld[i]); + } + + +// calculate texorg on the texture plane + for (i=0 ; i<3 ; i++) + l->texorg[i] = -tex->vecs[0][3]* l->textoworld[0][i] - tex->vecs[1][3] * l->textoworld[1][i]; + +// project back to the face plane + dist = DotProduct (l->texorg, l->facenormal) - l->facedist - 1; + dist *= distscale; + VectorMA (l->texorg, -dist, texnormal, l->texorg); + +} + +/* +================ +CalcFaceExtents + +Fills in s->texmins[] and s->texsize[] +also sets exactmins[] and exactmaxs[] +================ +*/ +void CalcFaceExtents (lightinfo_t *l) +{ + dface_t *s; + vec_t mins[2], maxs[2], val; + int i,j, e; + dvertex_t *v; + texinfo_t *tex; + + s = l->face; + + mins[0] = mins[1] = 999999; + maxs[0] = maxs[1] = -99999; + + tex = &texinfo[s->texinfo]; + + for (i=0 ; inumedges ; i++) + { + e = dsurfedges[s->firstedge+i]; + if (e >= 0) + v = dvertexes + dedges[e].v[0]; + else + v = dvertexes + dedges[-e].v[1]; + + for (j=0 ; j<2 ; j++) + { + val = v->point[0] * tex->vecs[j][0] + + v->point[1] * tex->vecs[j][1] + + v->point[2] * tex->vecs[j][2] + + tex->vecs[j][3]; + if (val < mins[j]) + mins[j] = val; + if (val > maxs[j]) + maxs[j] = val; + } + } + + for (i=0 ; i<2 ; i++) + { + l->exactmins[i] = mins[i]; + l->exactmaxs[i] = maxs[i]; + + mins[i] = floor(mins[i]/16); + maxs[i] = ceil(maxs[i]/16); + + l->texmins[i] = mins[i]; + l->texsize[i] = maxs[i] - mins[i]; + if (l->texsize[i] > 17) + Error ("Bad surface extents"); + } +} + +/* +================= +CalcPoints + +For each texture aligned grid point, back project onto the plane +to get the world xyz value of the sample point +================= +*/ +int c_bad; +void CalcPoints (lightinfo_t *l) +{ + int i; + int s, t, j; + int w, h, step; + vec_t starts, startt, us, ut; + vec_t *surf; + vec_t mids, midt; + vec3_t facemid, move; + +// +// fill in surforg +// the points are biased towards the center of the surface +// to help avoid edge cases just inside walls +// + surf = l->surfpt[0]; + mids = (l->exactmaxs[0] + l->exactmins[0])/2; + midt = (l->exactmaxs[1] + l->exactmins[1])/2; + + for (j=0 ; j<3 ; j++) + facemid[j] = l->texorg[j] + l->textoworld[0][j]*mids + l->textoworld[1][j]*midt; + + if (extrasamples) + { // extra filtering + h = (l->texsize[1]+1)*2; + w = (l->texsize[0]+1)*2; + starts = (l->texmins[0]-0.5)*16; + startt = (l->texmins[1]-0.5)*16; + step = 8; + } + else + { + h = l->texsize[1]+1; + w = l->texsize[0]+1; + starts = l->texmins[0]*16; + startt = l->texmins[1]*16; + step = 16; + } + + l->numsurfpt = w * h; + for (t=0 ; ttexorg[j] + l->textoworld[0][j]*us + + l->textoworld[1][j]*ut; + + if (CastRay (facemid, surf) != -1) + break; // got it + if (i & 1) + { + if (us > mids) + { + us -= 8; + if (us < mids) + us = mids; + } + else + { + us += 8; + if (us > mids) + us = mids; + } + } + else + { + if (ut > midt) + { + ut -= 8; + if (ut < midt) + ut = midt; + } + else + { + ut += 8; + if (ut > midt) + ut = midt; + } + } + + // move surf 8 pixels towards the center + VectorSubtract (facemid, surf, move); + VectorNormalize (move); + VectorMA (surf, 8, move, surf); + } + if (i == 2) + c_bad++; + } + } + +} + + +/* +=============================================================================== + +FACE LIGHTING + +=============================================================================== +*/ + +int c_culldistplane, c_proper; + +/* +================ +SingleLightFace +================ +*/ +void SingleLightFace (lightentity_t *light, lightinfo_t *l) +{ + vec_t dist; + vec3_t incoming; + vec_t angle; + vec_t add; + vec_t *surf; + qboolean hit; + int mapnum; + int size; + int c, i; + vec3_t rel; + vec3_t spotvec; + vec_t falloff; + vec_t *lightsamp; + + VectorSubtract (light->origin, bsp_origin, rel); + dist = scaledist * (DotProduct (rel, l->facenormal) - l->facedist); + +// don't bother with lights behind the surface + if (dist <= 0) + return; + +// don't bother with light too far away + if (dist > light->light) + { + c_culldistplane++; + return; + } + + if (light->targetent) + { + VectorSubtract (light->targetorigin, light->origin, spotvec); + VectorNormalize (spotvec); + if (!light->angle) + falloff = -cos(20*Q_PI/180); + else + falloff = -cos(light->angle/2*Q_PI/180); + } + else + falloff = 0; // shut up compiler warnings + + mapnum = 0; + for (mapnum=0 ; mapnumnumlightstyles ; mapnum++) + if (l->lightstyles[mapnum] == light->style) + break; + lightsamp = l->lightmaps[mapnum]; + if (mapnum == l->numlightstyles) + { // init a new light map + if (mapnum == MAXLIGHTMAPS) + { + printf ("WARNING: Too many light styles on a face\n"); + return; + } + size = (l->texsize[1]+1)*(l->texsize[0]+1); + for (i=0 ; isurfpt[0]; + for (c=0 ; cnumsurfpt ; c++, surf+=3) + { + dist = CastRay(light->origin, surf)*scaledist; + if (dist < 0) + continue; // light doesn't reach + + VectorSubtract (light->origin, surf, incoming); + VectorNormalize (incoming); + angle = DotProduct (incoming, l->facenormal); + if (light->targetent) + { // spotlight cutoff + if (DotProduct (spotvec, incoming) > falloff) + continue; + } + + angle = (1.0-scalecos) + scalecos*angle; + add = light->light - dist; + add *= angle; + if (add < 0) + continue; + lightsamp[c] += add; + if (lightsamp[c] > 1) // ignore real tiny lights + hit = true; + } + + if (mapnum == l->numlightstyles && hit) + { + l->lightstyles[mapnum] = light->style; + l->numlightstyles++; // the style has some real data now + } +} + +/* +============ +FixMinlight +============ +*/ +void FixMinlight (lightinfo_t *l) +{ + int i, j; + float minlight; + + minlight = minlights[l->surfnum]; + +// if minlight is set, there must be a style 0 light map + if (!minlight) + return; + + for (i=0 ; i< l->numlightstyles ; i++) + { + if (l->lightstyles[i] == 0) + break; + } + if (i == l->numlightstyles) + { + if (l->numlightstyles == MAXLIGHTMAPS) + return; // oh well.. + for (j=0 ; jnumsurfpt ; j++) + l->lightmaps[i][j] = minlight; + l->lightstyles[i] = 0; + l->numlightstyles++; + } + else + { + for (j=0 ; jnumsurfpt ; j++) + if ( l->lightmaps[i][j] < minlight) + l->lightmaps[i][j] = minlight; + } +} + + +/* +============ +LightFace +============ +*/ +void LightFace (int surfnum) +{ + dface_t *f; + lightinfo_t l; + int s, t; + int i,j,c; + vec_t total; + int size; + int lightmapwidth, lightmapsize; + byte *out; + vec_t *light; + int w, h; + + f = dfaces + surfnum; + +// +// some surfaces don't need lightmaps +// + f->lightofs = -1; + for (j=0 ; jstyles[j] = 255; + + if ( texinfo[f->texinfo].flags & TEX_SPECIAL) + { // non-lit texture + return; + } + + memset (&l, 0, sizeof(l)); + l.surfnum = surfnum; + l.face = f; + +// +// rotate plane +// + VectorCopy (dplanes[f->planenum].normal, l.facenormal); + l.facedist = dplanes[f->planenum].dist; + if (f->side) + { + VectorSubtract (vec3_origin, l.facenormal, l.facenormal); + l.facedist = -l.facedist; + } + + + + CalcFaceVectors (&l); + CalcFaceExtents (&l); + CalcPoints (&l); + + lightmapwidth = l.texsize[0]+1; + + size = lightmapwidth*(l.texsize[1]+1); + if (size > SINGLEMAP) + Error ("Bad lightmap size"); + + for (i=0 ; istyles[i] = l.lightstyles[i]; + + lightmapsize = size*l.numlightstyles; + + out = GetFileSpace (lightmapsize); + f->lightofs = out - filebase; + +// extra filtering + h = (l.texsize[1]+1)*2; + w = (l.texsize[0]+1)*2; + + for (i=0 ; i< l.numlightstyles ; i++) + { + if (l.lightstyles[i] == 0xff) + Error ("Wrote empty lightmap"); + light = l.lightmaps[i]; + c = 0; + for (t=0 ; t<=l.texsize[1] ; t++) + for (s=0 ; s<=l.texsize[0] ; s++, c++) + { + if (extrasamples) + { // filtered sample + total = light[t*2*w+s*2] + light[t*2*w+s*2+1] + + light[(t*2+1)*w+s*2] + light[(t*2+1)*w+s*2+1]; + total *= 0.25; + } + else + total = light[c]; + total *= rangescale; // scale before clamping + if (total > 255) + total = 255; + if (total < 0) + Error ("light < 0"); + *out++ = total; + } + } + + +} + diff --git a/utils/light/new/trace.c b/utils/light/new/trace.c new file mode 100644 index 0000000..f12b18e --- /dev/null +++ b/utils/light/new/trace.c @@ -0,0 +1,199 @@ +// trace.c + +#include "light.h" + +typedef struct tnode_s +{ + int type; + vec3_t normal; + float dist; + int children[2]; + int pad; +} tnode_t; + +tnode_t *tnodes, *tnode_p; + +/* +============== +MakeTnode + +Converts the disk node structure into the efficient tracing structure +============== +*/ +void MakeTnode (int nodenum) +{ + tnode_t *t; + dplane_t *plane; + int i; + dnode_t *node; + + t = tnode_p++; + + node = dnodes + nodenum; + plane = dplanes + node->planenum; + + t->type = plane->type; + VectorCopy (plane->normal, t->normal); + t->dist = plane->dist; + + for (i=0 ; i<2 ; i++) + { + if (node->children[i] < 0) + t->children[i] = dleafs[-node->children[i] - 1].contents; + else + { + t->children[i] = tnode_p - tnodes; + MakeTnode (node->children[i]); + } + } + +} + + +/* +============= +MakeTnodes + +Loads the node structure out of a .bsp file to be used for light occlusion +============= +*/ +void MakeTnodes (dmodel_t *bm) +{ + if (!numnodes) + Error ("Map has no nodes\n"); + tnode_p = tnodes = malloc(numnodes * sizeof(tnode_t)); + + MakeTnode (0); +} + + + +/* +============================================================================== + +LINE TRACING + +The major lighting operation is a point to point visibility test, performed +by recursive subdivision of the line by the BSP tree. + +============================================================================== +*/ + +typedef struct +{ + vec3_t backpt; + int side; + int node; +} tracestack_t; + + +/* +============== +TestLine +============== +*/ +qboolean TestLine (vec3_t start, vec3_t stop) +{ + int node; + float front, back; + tracestack_t *tstack_p; + int side; + float frontx,fronty, frontz, backx, backy, backz; + tracestack_t tracestack[64]; + tnode_t *tnode; + + frontx = start[0]; + fronty = start[1]; + frontz = start[2]; + backx = stop[0]; + backy = stop[1]; + backz = stop[2]; + + tstack_p = tracestack; + node = 0; + + while (1) + { + while (node < 0 && node != CONTENTS_SOLID) + { + // pop up the stack for a back side + tstack_p--; + if (tstack_p < tracestack) + return true; + node = tstack_p->node; + + // set the hit point for this plane + + frontx = backx; + fronty = backy; + frontz = backz; + + // go down the back side + + backx = tstack_p->backpt[0]; + backy = tstack_p->backpt[1]; + backz = tstack_p->backpt[2]; + + node = tnodes[tstack_p->node].children[!tstack_p->side]; + } + + if (node == CONTENTS_SOLID) + return false; // DONE! + + tnode = &tnodes[node]; + + switch (tnode->type) + { + case PLANE_X: + front = frontx - tnode->dist; + back = backx - tnode->dist; + break; + case PLANE_Y: + front = fronty - tnode->dist; + back = backy - tnode->dist; + break; + case PLANE_Z: + front = frontz - tnode->dist; + back = backz - tnode->dist; + break; + default: + front = (frontx*tnode->normal[0] + fronty*tnode->normal[1] + frontz*tnode->normal[2]) - tnode->dist; + back = (backx*tnode->normal[0] + backy*tnode->normal[1] + backz*tnode->normal[2]) - tnode->dist; + break; + } + + if (front > -ON_EPSILON && back > -ON_EPSILON) +// if (front > 0 && back > 0) + { + node = tnode->children[0]; + continue; + } + + if (front < ON_EPSILON && back < ON_EPSILON) +// if (front <= 0 && back <= 0) + { + node = tnode->children[1]; + continue; + } + + side = front < 0; + + front = front / (front-back); + + tstack_p->node = node; + tstack_p->side = side; + tstack_p->backpt[0] = backx; + tstack_p->backpt[1] = backy; + tstack_p->backpt[2] = backz; + + tstack_p++; + + backx = frontx + front*(backx-frontx); + backy = fronty + front*(backy-fronty); + backz = frontz + front*(backz-frontz); + + node = tnode->children[side]; + } +} + + diff --git a/utils/light/trace.c b/utils/light/trace.c new file mode 100644 index 0000000..a655cdd --- /dev/null +++ b/utils/light/trace.c @@ -0,0 +1,206 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// trace.c + +#include "light.h" + +typedef struct tnode_s +{ + int type; + vec3_t normal; + float dist; + int children[2]; + int pad; +} tnode_t; + +tnode_t *tnodes, *tnode_p; + +/* +============== +MakeTnode + +Converts the disk node structure into the efficient tracing structure +============== +*/ +void MakeTnode (int nodenum) +{ + tnode_t *t; + dplane_t *plane; + int i; + dnode_t *node; + + t = tnode_p++; + + node = dnodes + nodenum; + plane = dplanes + node->planenum; + + t->type = plane->type; + VectorCopy (plane->normal, t->normal); + t->dist = plane->dist; + + for (i=0 ; i<2 ; i++) + { + if (node->children[i] < 0) + t->children[i] = dleafs[-node->children[i] - 1].contents; + else + { + t->children[i] = tnode_p - tnodes; + MakeTnode (node->children[i]); + } + } + +} + + +/* +============= +MakeTnodes + +Loads the node structure out of a .bsp file to be used for light occlusion +============= +*/ +void MakeTnodes (dmodel_t *bm) +{ + if (!numnodes) + Error ("Map has no nodes\n"); + tnode_p = tnodes = malloc(numnodes * sizeof(tnode_t)); + + MakeTnode (0); +} + + + +/* +============================================================================== + +LINE TRACING + +The major lighting operation is a point to point visibility test, performed +by recursive subdivision of the line by the BSP tree. + +============================================================================== +*/ + +typedef struct +{ + vec3_t backpt; + int side; + int node; +} tracestack_t; + + +/* +============== +TestLine +============== +*/ +qboolean TestLine (vec3_t start, vec3_t stop) +{ + int node; + float front, back; + tracestack_t *tstack_p; + int side; + float frontx,fronty, frontz, backx, backy, backz; + tracestack_t tracestack[64]; + tnode_t *tnode; + + frontx = start[0]; + fronty = start[1]; + frontz = start[2]; + backx = stop[0]; + backy = stop[1]; + backz = stop[2]; + + tstack_p = tracestack; + node = 0; + + while (1) + { + while (node < 0 && node != CONTENTS_SOLID) + { + // pop up the stack for a back side + tstack_p--; + if (tstack_p < tracestack) + return true; + node = tstack_p->node; + + // set the hit point for this plane + + frontx = backx; + fronty = backy; + frontz = backz; + + // go down the back side + + backx = tstack_p->backpt[0]; + backy = tstack_p->backpt[1]; + backz = tstack_p->backpt[2]; + + node = tnodes[tstack_p->node].children[!tstack_p->side]; + } + + if (node == CONTENTS_SOLID) + return false; // DONE! + + tnode = &tnodes[node]; + + switch (tnode->type) + { + case PLANE_X: + front = frontx - tnode->dist; + back = backx - tnode->dist; + break; + case PLANE_Y: + front = fronty - tnode->dist; + back = backy - tnode->dist; + break; + case PLANE_Z: + front = frontz - tnode->dist; + back = backz - tnode->dist; + break; + default: + front = (frontx*tnode->normal[0] + fronty*tnode->normal[1] + frontz*tnode->normal[2]) - tnode->dist; + back = (backx*tnode->normal[0] + backy*tnode->normal[1] + backz*tnode->normal[2]) - tnode->dist; + break; + } + + if (front > -ON_EPSILON && back > -ON_EPSILON) +// if (front > 0 && back > 0) + { + node = tnode->children[0]; + continue; + } + + if (front < ON_EPSILON && back < ON_EPSILON) +// if (front <= 0 && back <= 0) + { + node = tnode->children[1]; + continue; + } + + side = front < 0; + + front = front / (front-back); + + tstack_p->node = node; + tstack_p->side = side; + tstack_p->backpt[0] = backx; + tstack_p->backpt[1] = backy; + tstack_p->backpt[2] = backz; + + tstack_p++; + + backx = frontx + front*(backx-frontx); + backy = fronty + front*(backy-fronty); + backz = frontz + front*(backz-frontz); + + node = tnode->children[side]; + } +} + + diff --git a/utils/makefont/makefont.cpp b/utils/makefont/makefont.cpp new file mode 100644 index 0000000..99c6101 --- /dev/null +++ b/utils/makefont/makefont.cpp @@ -0,0 +1,494 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#define _NOENUMQBOOL + +#include + +extern "C" +{ + #include "cmdlib.h" + #include "wadlib.h" +} +#include "qfont.h" + +#define DEFAULT_FONT "Arial" + +#define FONT_TAG 6 // Font's are the 6th tag after the TYP_LUMPY base ( 64 )...i.e., type == 70 + +BOOL bItalic = FALSE; +BOOL bBold = FALSE; +BOOL bUnderline = FALSE; + +char fontname[ 256 ]; +int pointsize[3] = { 9, 11, 15 }; + +/* +================= +zeromalloc + +Allocates and zeroes memory +================= +*/ +void *zeromalloc( size_t size ) +{ + unsigned char *pbuffer; + pbuffer = ( unsigned char * )malloc( size ); + if ( !pbuffer ) + { + printf( "Failed on allocation of %i bytes", size ); + exit( -1 ); + } + + memset( pbuffer, 0, size ); + return ( void * )pbuffer; +} + +/* +================= +Draw_SetupConsolePalette + +Set's the palette to full brightness ( 192 ) and +set's up palette entry 0 -- black +================= +*/ +void Draw_SetupConsolePalette( unsigned char *pal ) +{ + unsigned char *pPalette; + int i; + + pPalette = pal; + + *(short *)pPalette = 3 * 256; + pPalette += sizeof( short ); + + for ( i = 0; i < 256; i++ ) + { + pPalette[3 * i + 0 ] = i; + pPalette[3 * i + 1 ] = i; + pPalette[3 * i + 2 ] = i; + } + + // Set palette zero correctly + pPalette[ 0 ] = 0; + pPalette[ 1 ] = 0; + pPalette[ 2 ] = 0; +} + +/* +================= +CreateConsoleFont + +Renders TT font into memory dc and creates appropriate qfont_t structure +================= +*/ + +// YWB: Sigh, VC 6.0's global optimizer causes weird stack fixups in release builds. Disable the globabl optimizer for this function. +#pragma optimize( "g", off ) +qfont_t *CreateConsoleFont( char *pszFont, int nPointSize, BOOL bItalic, BOOL bUnderline, BOOL bBold, int *outsize ) +{ + HDC hdc; + HDC hmemDC; + HBITMAP hbm, oldbm; + RECT rc; + HFONT fnt, oldfnt; + int startchar = 32; + int c; + int i, j; + int x, y; + int nScans; + unsigned char *bits; + BITMAPINFO tempbmi; + BITMAPINFO *pbmi; + BITMAPINFOHEADER *pbmheader; + unsigned char *pqdata; + unsigned char *pCur; + int x1, y1; + unsigned char *pPalette; + qfont_t *pqf = NULL; + int fullsize; + int w = 16; + int h = (128-32)/16; + int charheight = nPointSize + 5; + int charwidth = 16; + RECT rcChar; + + // Create the font + fnt = CreateFont( -nPointSize, 0, 0, 0, bBold ? FW_HEAVY : FW_MEDIUM, bItalic, bUnderline, 0, ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, VARIABLE_PITCH | FF_DONTCARE, pszFont ); + + bits = NULL; + + fullsize = sizeof( qfont_t ) - 4 + ( 128 * w * charwidth ) + sizeof(short) + 768 + 64; + + // Store off final size + *outsize = fullsize; + + pqf = ( qfont_t * )zeromalloc( fullsize ); + pqdata = (unsigned char *)pqf + sizeof( qfont_t ) - 4; + + pPalette = pqdata + ( 128 * w * charwidth ); + + // Configure palette + Draw_SetupConsolePalette( pPalette ); + + hdc = GetDC( NULL ); + hmemDC = CreateCompatibleDC( hdc ); + + rc.top = 0; + rc.left = 0; + rc.right = charwidth * w; + rc.bottom = charheight * h; + + hbm = CreateBitmap( charwidth * w, charheight * h, 1, 1, NULL ); + oldbm = (HBITMAP)SelectObject( hmemDC, hbm ); + oldfnt = (HFONT)SelectObject( hmemDC, fnt ); + + SetTextColor( hmemDC, 0x00ffffff ); + SetBkMode( hmemDC, TRANSPARENT ); + + // Paint black background + FillRect( hmemDC, &rc, (HBRUSH)GetStockObject( BLACK_BRUSH ) ); + + // Draw character set into memory DC + for ( j = 0; j < h; j++ ) + { + for ( i = 0; i < w; i++ ) + { + x = i * charwidth; + y = j * charheight; + + c = (char)( startchar + j * w + i ); + + // Only draw printable characters, of course + if ( isprint( c ) && c <= 127 ) + { + // Draw it. + rcChar.left = x + 1; + rcChar.top = y + 1; + rcChar.right = x + charwidth - 1; + rcChar.bottom = y + charheight - 1; + + DrawText( hmemDC, (char *)&c, 1, &rcChar, DT_NOPREFIX | DT_LEFT ); + } + } + } + + // Now turn the qfont into raw format + memset( &tempbmi, 0, sizeof( BITMAPINFO ) ); + pbmheader = ( BITMAPINFOHEADER * )&tempbmi; + + pbmheader->biSize = sizeof( BITMAPINFOHEADER ); + pbmheader->biWidth = w * charwidth; + pbmheader->biHeight = -h * charheight; + pbmheader->biPlanes = 1; + pbmheader->biBitCount = 1; + pbmheader->biCompression = BI_RGB; + + // Find out how big the bitmap is + nScans = GetDIBits( hmemDC, hbm, 0, h * charheight, NULL, &tempbmi, DIB_RGB_COLORS ); + + // Allocate space for all bits + pbmi = ( BITMAPINFO * )zeromalloc( sizeof ( BITMAPINFOHEADER ) + 2 * sizeof( RGBQUAD ) + pbmheader->biSizeImage ); + + memcpy( pbmi, &tempbmi, sizeof( BITMAPINFO ) ); + + bits = ( unsigned char * )pbmi + sizeof( BITMAPINFOHEADER ) + 2 * sizeof( RGBQUAD ); + + // Now read in bits + nScans = GetDIBits( hmemDC, hbm, 0, h * charheight, bits, pbmi, DIB_RGB_COLORS ); + + if ( nScans > 0 ) + { + // Now convert to proper raw format + // + // Now get results from dib + pqf->height = 128; // Always set to 128 + pqf->width = charwidth; + pqf->rowheight = charheight; + pqf->rowcount = h; + pCur = pqdata; + + // Set everything to index 255 ( 0xff ) == transparent + memset( pCur, 0xFF, w * charwidth * pqf->height ); + + for ( j = 0; j < h; j++ ) + { + for ( i = 0; i < w; i++ ) + { + int edge = 1; + int bestwidth; + x = i * charwidth; + y = j * charheight; + + + c = (char)( startchar + j * w + i ); + + pqf->fontinfo[ c ].charwidth = charwidth; + pqf->fontinfo[ c ].startoffset = y * w * charwidth + x; + + bestwidth = 0; + + // In this first pass, place the black drop shadow so characters draw ok in the engine against + // most backgrounds. + // YWB: FIXME, apply a box filter and enable blending? + + // Make it all transparent for starters + for ( y1 = 0; y1 < charheight; y1++ ) + { + for ( x1 = 0; x1 < charwidth; x1++ ) + { + int offset; + offset = ( y + y1 ) * w * charwidth + x + x1 ; + // Dest + pCur = pqdata + offset; + // Assume transparent + pCur[0] = 255; + } + } + + // Put black pixels below and to the right of each pixel + for ( y1 = edge; y1 < charheight - edge; y1++ ) + { + for ( x1 = 0; x1 < charwidth; x1++ ) + { + int offset; + int srcoffset; + + int xx0, yy0; + + offset = ( y + y1 ) * w * charwidth + x + x1 ; + + // Dest + pCur = pqdata + offset; + + for ( xx0 = -edge; xx0 <= edge; xx0++ ) + { + for ( yy0 = -edge; yy0 <= edge; yy0++ ) + { + srcoffset = ( y + y1 + yy0 ) * w * charwidth + x + x1 + xx0; + + if ( bits[ srcoffset >> 3 ] & ( 1 << ( 7 - srcoffset & 7 ) ) ) + { + // Near Black + pCur[0] = 32; + } + } + } + } + } + + // Now copy in the actual font pixels + for ( y1 = 0; y1 < charheight; y1++ ) + { + for ( x1 = 0; x1 < charwidth; x1++ ) + { + int offset; + + offset = ( y + y1 ) * w * charwidth + x + x1; + + // Dest + pCur = pqdata + offset; + + if ( bits[ offset >> 3 ] & ( 1 << ( 7 - offset & 7 ) ) ) + { + if ( x1 > bestwidth ) + { + bestwidth = x1; + } + + // Full color + // FIXME: Enable true palette support in engine? + pCur[0] = 192; + } + } + } + + // bestwidth += 1; + /* + // Now blend it + for ( y1 = 0; y1 < charheight; y1++ ) + { + for ( x1 = 0; x1 < charwidth; x1++ ) + { + int offset; + + offset = ( y + y1 ) * w * charwidth + x + x1; + + // Dest + pCur = pqdata + offset; + + if ( bits[ offset >> 3 ] & ( 1 << ( 7 - offset & 7 ) ) ) + { + if ( x1 > bestwidth ) + { + bestwidth = x1; + } + + // Full color + // FIXME: Enable true palette support in engine? + pCur[0] = 192; + } + } + } + */ + + // Space character width + if ( c == 32 ) + { + bestwidth = 8; + } + else + { + // Small characters needs can be padded a bit so they don't run into each other + if ( bestwidth <= 14 ) + { + bestwidth += 2; + } + } + + // Store off width + pqf->fontinfo[ c ].charwidth = bestwidth; + } + } + } + + // Free memory bits + free ( pbmi ); + + SelectObject( hmemDC, oldfnt ); + DeleteObject( fnt ); + + SelectObject( hmemDC, oldbm ); + + DeleteObject( hbm ); + + DeleteDC( hmemDC ); + ReleaseDC( NULL, hdc ); + + return pqf; +} + +#pragma optimize( "g", on ) + +/* +================= +main + +================= +*/ +int main(int argc, char* argv[]) +{ + int i; + DWORD start, end; + char destfile[1024]; + char sz[ 32 ]; + int outsize[ 3 ]; + + qfont_t *fonts[ 3 ]; + + strcpy( fontname, DEFAULT_FONT ); + + printf("makefont.exe Version 1.0 by valve (%s)\n", __DATE__ ); + + printf ("----- Creating Console Font ----\n"); + + for (i=1 ; i= argc ) + { + Error( "Makefont: Insufficient point sizes specified\n" ); + } + pointsize[0] = atoi( argv[i+1] ); + pointsize[1] = atoi( argv[i+2] ); + pointsize[2] = atoi( argv[i+3] ); + i += 3; + } + else if (!strcmp(argv[i],"-italic")) + { + bItalic = TRUE; + printf ( "italic set\n"); + } + else if (!strcmp(argv[i],"-bold")) + { + bBold = TRUE; + printf ( "bold set\n"); + } + else if (!strcmp(argv[i],"-underline")) + { + bUnderline = TRUE; + printf ( "underline set\n"); + } + else if ( argv[i][0] == '-' ) + { + Error ("Unknown option \"%s\"", argv[i]); + } + else + break; + } + + if ( i != argc - 1 ) + { + Error ("usage: makefont [-font \"fontname\"] [-italic] [-underline] [-bold] [-pointsizes sm med lg] outfile"); + } + + printf( "Creating %i, %i, and %i point %s fonts\n", pointsize[0], pointsize[1], pointsize[2], fontname ); + + start = timeGetTime(); + + // Create the fonts + for ( i = 0 ; i < 3; i++ ) + { + fonts[ i ] = CreateConsoleFont( fontname, pointsize[i], bItalic, bUnderline, bBold, &outsize[ i ] ); + } + + // Create wad file + strcpy (destfile, argv[argc - 1]); + StripExtension (destfile); + DefaultExtension (destfile, ".wad"); + + NewWad( destfile, false ); + + // Add fonts as lumps + for ( i = 0; i < 3; i++ ) + { + sprintf( sz, "font%i", i ); + AddLump( sz, fonts[ i ], outsize[ i ], TYP_LUMPY + FONT_TAG, false ); + } + + // Store results as a WAD3 + // NOTE: ( should be named fonts.wad in the valve\ subdirectory ) + WriteWad( 3 ); + + // Clean up memory + for ( i = 0 ; i < 3; i++ ) + { + free( fonts[ i ] ); + } + + end = timeGetTime (); + + printf ( "%5.5f seconds elapsed\n", (float)( end - start )/1000.0 ); + + // Display for a second since it might not be running from command prompt + Sleep( 1000 ); + return 0; +} diff --git a/utils/makefont/makefont.dsp b/utils/makefont/makefont.dsp new file mode 100644 index 0000000..8664a7f --- /dev/null +++ b/utils/makefont/makefont.dsp @@ -0,0 +1,138 @@ +# Microsoft Developer Studio Project File - Name="makefont" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=makefont - Win32 Debug +!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 "makefont.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 "makefont.mak" CFG="makefont - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "makefont - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "makefont - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "makefont - 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 /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /Yu"stdafx.h" /FD /c +# ADD CPP /nologo /W3 /GX /Zi /O2 /I "../../common" /I "../common" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX"qfont.h" /FD /c +# 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 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 /subsystem:console /machine:I386 +# ADD 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 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /subsystem:console /debug /machine:I386 +# Begin Special Build Tool +TargetPath=.\Release\makefont.exe +SOURCE="$(InputPath)" +PostBuild_Desc=Copyint to valve\ +PostBuild_Cmds=xcopy $(TargetPath) ..\..\..\valve /r /i +# End Special Build Tool + +!ELSEIF "$(CFG)" == "makefont - 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 /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /Yu"stdafx.h" /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "../../common" /I "../common" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX"qfont.h" /FD /GZ /c +# 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 gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib 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 /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD 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 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# Begin Special Build Tool +TargetPath=.\Debug\makefont.exe +SOURCE="$(InputPath)" +PostBuild_Desc=Copyint to valve\ +PostBuild_Cmds=xcopy $(TargetPath) ..\..\..\valve /r /i +# End Special Build Tool + +!ENDIF + +# Begin Target + +# Name "makefont - Win32 Release" +# Name "makefont - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\common\cmdlib.c +# End Source File +# Begin Source File + +SOURCE=.\makefont.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\wadlib.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\common\cmdlib.h +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.h +# End Source File +# Begin Source File + +SOURCE=..\common\wadlib.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 +# Begin Source File + +SOURCE=.\ReadMe.txt +# End Source File +# End Target +# End Project diff --git a/utils/makefont/makefont.dsw b/utils/makefont/makefont.dsw new file mode 100644 index 0000000..f20f15b --- /dev/null +++ b/utils/makefont/makefont.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "makefont"=.\makefont.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/utils/makels/makels.cpp b/utils/makels/makels.cpp new file mode 100644 index 0000000..f3883f8 --- /dev/null +++ b/utils/makels/makels.cpp @@ -0,0 +1,173 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include +#include +#include +#include + + +char **ppszFiles = NULL; +int nFiles = 0; +int nMaxFiles = 0; + +int +string_comparator( const void *string1, const void *string2 ) +{ + char *s1 = *(char **)string1; + char *s2 = *(char **)string2; + return strcmp( s1, s2 ); +} + +void PrintUsage(char *pname) +{ + printf("\n\tusage:%s